From 1350e6473a03a737c6b9c468d32d72dfc9fb43e9 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Wed, 29 Jan 2025 17:35:26 -0500 Subject: [PATCH 001/173] Change to typetools formatting --- build.gradle | 4 +- .../checker/builder/qual/CalledMethods.java | 12 +- .../builder/qual/NotCalledMethods.java | 12 +- .../calledmethods/qual/CalledMethods.java | 17 +- .../qual/CalledMethodsBottom.java | 7 +- .../qual/CalledMethodsPredicate.java | 23 +- .../qual/EnsuresCalledMethods.java | 71 +- .../qual/EnsuresCalledMethodsIf.java | 85 +- .../qual/EnsuresCalledMethodsOnException.java | 105 +- .../qual/EnsuresCalledMethodsVarArgs.java | 12 +- .../qual/RequiresCalledMethods.java | 65 +- .../compilermsgs/qual/CompilerMessageKey.java | 3 +- .../qual/CompilerMessageKeyBottom.java | 9 +- .../qual/UnknownCompilerMessageKey.java | 7 +- .../fenum/qual/AwtAlphaCompositingRule.java | 3 +- .../checker/fenum/qual/AwtColorSpace.java | 3 +- .../checker/fenum/qual/AwtCursorType.java | 3 +- .../checker/fenum/qual/AwtFlowLayout.java | 3 +- .../checker/fenum/qual/Fenum.java | 5 +- .../checker/fenum/qual/FenumBottom.java | 9 +- .../checker/fenum/qual/FenumTop.java | 17 +- .../checker/fenum/qual/FenumUnqualified.java | 9 +- .../checker/fenum/qual/PolyFenum.java | 3 +- .../fenum/qual/SwingBoxOrientation.java | 3 +- .../fenum/qual/SwingCompassDirection.java | 3 +- .../fenum/qual/SwingElementOrientation.java | 3 +- .../qual/SwingHorizontalOrientation.java | 3 +- .../fenum/qual/SwingSplitPaneOrientation.java | 3 +- .../fenum/qual/SwingTextOrientation.java | 3 +- .../fenum/qual/SwingTitleJustification.java | 3 +- .../fenum/qual/SwingTitlePosition.java | 3 +- .../fenum/qual/SwingVerticalOrientation.java | 3 +- .../formatter/qual/ConversionCategory.java | 610 +- .../checker/formatter/qual/Format.java | 19 +- .../checker/formatter/qual/FormatBottom.java | 9 +- .../checker/formatter/qual/InvalidFormat.java | 15 +- .../checker/formatter/qual/UnknownFormat.java | 11 +- .../checker/guieffect/qual/AlwaysSafe.java | 5 +- .../checker/guieffect/qual/PolyUI.java | 3 +- .../checker/guieffect/qual/UI.java | 3 +- .../checker/i18n/qual/LocalizableKey.java | 3 +- .../i18n/qual/LocalizableKeyBottom.java | 9 +- .../checker/i18n/qual/Localized.java | 21 +- .../i18n/qual/UnknownLocalizableKey.java | 7 +- .../checker/i18n/qual/UnknownLocalized.java | 7 +- .../qual/I18nConversionCategory.java | 356 +- .../i18nformatter/qual/I18nFormat.java | 15 +- .../i18nformatter/qual/I18nFormatBottom.java | 9 +- .../i18nformatter/qual/I18nFormatFor.java | 21 +- .../i18nformatter/qual/I18nInvalidFormat.java | 13 +- .../i18nformatter/qual/I18nUnknownFormat.java | 11 +- .../checker/index/qual/EnsuresLTLengthOf.java | 91 +- .../index/qual/EnsuresLTLengthOfIf.java | 105 +- .../checker/index/qual/GTENegativeOne.java | 3 +- .../checker/index/qual/HasSubsequence.java | 21 +- .../checker/index/qual/IndexFor.java | 4 +- .../checker/index/qual/IndexOrHigh.java | 6 +- .../checker/index/qual/IndexOrLow.java | 4 +- .../checker/index/qual/LTEqLengthOf.java | 11 +- .../checker/index/qual/LTLengthOf.java | 29 +- .../checker/index/qual/LTOMLengthOf.java | 15 +- .../checker/index/qual/LengthOf.java | 4 +- .../checker/index/qual/LessThan.java | 27 +- .../checker/index/qual/LessThanBottom.java | 3 +- .../checker/index/qual/LessThanUnknown.java | 7 +- .../checker/index/qual/LowerBoundBottom.java | 7 +- .../checker/index/qual/LowerBoundUnknown.java | 7 +- .../checker/index/qual/NegativeIndexFor.java | 17 +- .../checker/index/qual/NonNegative.java | 3 +- .../checker/index/qual/PolyIndex.java | 3 +- .../checker/index/qual/PolyLength.java | 3 +- .../checker/index/qual/PolyLowerBound.java | 3 +- .../checker/index/qual/PolySameLen.java | 3 +- .../checker/index/qual/PolyUpperBound.java | 3 +- .../checker/index/qual/Positive.java | 3 +- .../checker/index/qual/SameLen.java | 11 +- .../checker/index/qual/SameLenBottom.java | 7 +- .../checker/index/qual/SameLenUnknown.java | 7 +- .../checker/index/qual/SearchIndexBottom.java | 7 +- .../checker/index/qual/SearchIndexFor.java | 17 +- .../index/qual/SearchIndexUnknown.java | 7 +- .../index/qual/SubstringIndexBottom.java | 7 +- .../checker/index/qual/SubstringIndexFor.java | 41 +- .../index/qual/SubstringIndexUnknown.java | 7 +- .../checker/index/qual/UpperBoundBottom.java | 7 +- .../checker/index/qual/UpperBoundLiteral.java | 19 +- .../checker/index/qual/UpperBoundUnknown.java | 7 +- .../initialization/qual/FBCBottom.java | 11 +- .../qual/HoldsForDefaultValue.java | 3 +- .../initialization/qual/Initialized.java | 17 +- .../initialization/qual/PolyInitialized.java | 3 +- .../qual/UnderInitialization.java | 21 +- .../qual/UnknownInitialization.java | 25 +- .../interning/qual/CompareToMethod.java | 3 +- .../checker/interning/qual/EqualsMethod.java | 3 +- .../checker/interning/qual/InternMethod.java | 3 +- .../checker/interning/qual/Interned.java | 31 +- .../interning/qual/InternedDistinct.java | 7 +- .../checker/interning/qual/PolyInterned.java | 3 +- .../interning/qual/UnknownInterned.java | 7 +- .../checker/lock/qual/EnsuresLockHeld.java | 57 +- .../checker/lock/qual/EnsuresLockHeldIf.java | 75 +- .../checker/lock/qual/GuardSatisfied.java | 33 +- .../checker/lock/qual/GuardedBy.java | 73 +- .../checker/lock/qual/GuardedByBottom.java | 7 +- .../checker/lock/qual/GuardedByUnknown.java | 3 +- .../checker/lock/qual/Holding.java | 17 +- .../checker/lock/qual/LockHeld.java | 5 +- .../checker/lock/qual/LockPossiblyHeld.java | 9 +- .../checker/lock/qual/LockingFree.java | 7 +- .../checker/lock/qual/MayReleaseLocks.java | 3 +- .../checker/lock/qual/NewObject.java | 19 +- .../checker/lock/qual/ReleasesNoLocks.java | 7 +- .../mustcall/qual/CreatesMustCallFor.java | 69 +- .../mustcall/qual/InheritableMustCall.java | 12 +- .../checker/mustcall/qual/MustCall.java | 21 +- .../mustcall/qual/MustCallUnknown.java | 3 +- .../checker/mustcall/qual/PolyMustCall.java | 3 +- .../nullness/qual/AssertNonNullIfNonNull.java | 12 +- .../checker/nullness/qual/EnsuresKeyFor.java | 75 +- .../nullness/qual/EnsuresKeyForIf.java | 79 +- .../checker/nullness/qual/EnsuresNonNull.java | 51 +- .../nullness/qual/EnsuresNonNullIf.java | 63 +- .../checker/nullness/qual/KeyFor.java | 19 +- .../checker/nullness/qual/KeyForBottom.java | 9 +- .../nullness/qual/MonotonicNonNull.java | 5 +- .../checker/nullness/qual/NonNull.java | 33 +- .../checker/nullness/qual/Nullable.java | 9 +- .../checker/nullness/qual/PolyKeyFor.java | 3 +- .../checker/nullness/qual/PolyNull.java | 3 +- .../nullness/qual/RequiresNonNull.java | 51 +- .../checker/nullness/qual/UnknownKeyFor.java | 11 +- .../checker/optional/qual/EnsuresPresent.java | 17 +- .../optional/qual/EnsuresPresentIf.java | 63 +- .../checker/optional/qual/MaybePresent.java | 5 +- .../checker/optional/qual/OptionalBottom.java | 3 +- .../optional/qual/OptionalCreator.java | 3 +- .../optional/qual/OptionalEliminator.java | 3 +- .../optional/qual/OptionalPropagator.java | 3 +- .../checker/optional/qual/PolyPresent.java | 3 +- .../checker/optional/qual/Present.java | 3 +- .../optional/qual/RequiresPresent.java | 47 +- .../checker/propkey/qual/PropertyKey.java | 3 +- .../propkey/qual/PropertyKeyBottom.java | 9 +- .../propkey/qual/UnknownPropertyKey.java | 5 +- .../checker/regex/qual/PartialRegex.java | 15 +- .../checker/regex/qual/PolyRegex.java | 3 +- .../checker/regex/qual/Regex.java | 7 +- .../checker/regex/qual/RegexBottom.java | 11 +- .../checker/regex/qual/UnknownRegex.java | 11 +- .../signature/qual/ArrayWithoutPackage.java | 3 +- .../checker/signature/qual/BinaryName.java | 3 +- .../qual/BinaryNameOrPrimitiveType.java | 3 +- .../qual/BinaryNameWithoutPackage.java | 3 +- .../checker/signature/qual/CanonicalName.java | 9 +- .../qual/CanonicalNameAndBinaryName.java | 3 +- .../signature/qual/CanonicalNameOrEmpty.java | 3 +- .../qual/CanonicalNameOrPrimitiveType.java | 3 +- .../checker/signature/qual/ClassGetName.java | 3 +- .../signature/qual/ClassGetSimpleName.java | 3 +- .../qual/DotSeparatedIdentifiers.java | 3 +- ...otSeparatedIdentifiersOrPrimitiveType.java | 3 +- .../signature/qual/FieldDescriptor.java | 3 +- .../qual/FieldDescriptorForPrimitive.java | 3 +- .../qual/FieldDescriptorWithoutPackage.java | 3 +- .../checker/signature/qual/FqBinaryName.java | 3 +- .../signature/qual/FullyQualifiedName.java | 3 +- .../checker/signature/qual/Identifier.java | 9 +- .../qual/IdentifierOrPrimitiveType.java | 3 +- .../checker/signature/qual/InternalForm.java | 3 +- .../signature/qual/MethodDescriptor.java | 3 +- .../checker/signature/qual/PolySignature.java | 3 +- .../checker/signature/qual/PrimitiveType.java | 3 +- .../signature/qual/SignatureBottom.java | 17 +- .../signature/qual/SignatureUnknown.java | 5 +- .../checker/signedness/qual/PolySigned.java | 3 +- .../checker/signedness/qual/Signed.java | 49 +- .../signedness/qual/SignedPositive.java | 3 +- .../signedness/qual/SignednessBottom.java | 11 +- .../signedness/qual/SignednessGlb.java | 3 +- .../signedness/qual/UnknownSignedness.java | 3 +- .../checker/signedness/qual/Unsigned.java | 17 +- .../checker/tainting/qual/PolyTainted.java | 3 +- .../checker/tainting/qual/Tainted.java | 5 +- .../checker/tainting/qual/Untainted.java | 11 +- .../checker/units/qual/A.java | 5 +- .../checker/units/qual/Acceleration.java | 3 +- .../checker/units/qual/Angle.java | 3 +- .../checker/units/qual/Area.java | 3 +- .../checker/units/qual/C.java | 3 +- .../checker/units/qual/Current.java | 3 +- .../checker/units/qual/Force.java | 3 +- .../checker/units/qual/K.java | 5 +- .../checker/units/qual/Length.java | 3 +- .../checker/units/qual/Luminance.java | 3 +- .../checker/units/qual/Mass.java | 3 +- .../checker/units/qual/MixedUnits.java | 3 +- .../checker/units/qual/N.java | 5 +- .../checker/units/qual/PolyUnit.java | 3 +- .../checker/units/qual/Prefix.java | 84 +- .../checker/units/qual/Speed.java | 3 +- .../checker/units/qual/Substance.java | 3 +- .../checker/units/qual/Temperature.java | 3 +- .../checker/units/qual/Time.java | 3 +- .../checker/units/qual/UnitsBottom.java | 9 +- .../checker/units/qual/UnitsMultiple.java | 24 +- .../checker/units/qual/UnitsRelations.java | 22 +- .../checker/units/qual/UnknownUnits.java | 7 +- .../checker/units/qual/Volume.java | 3 +- .../checker/units/qual/cd.java | 5 +- .../checker/units/qual/degrees.java | 3 +- .../checker/units/qual/g.java | 5 +- .../checker/units/qual/h.java | 3 +- .../checker/units/qual/kN.java | 3 +- .../checker/units/qual/kg.java | 3 +- .../checker/units/qual/km.java | 3 +- .../checker/units/qual/km2.java | 3 +- .../checker/units/qual/km3.java | 3 +- .../checker/units/qual/kmPERh.java | 3 +- .../checker/units/qual/m.java | 5 +- .../checker/units/qual/m2.java | 7 +- .../checker/units/qual/m3.java | 3 +- .../checker/units/qual/mPERs.java | 5 +- .../checker/units/qual/mPERs2.java | 5 +- .../checker/units/qual/min.java | 3 +- .../checker/units/qual/mm.java | 3 +- .../checker/units/qual/mm2.java | 3 +- .../checker/units/qual/mm3.java | 3 +- .../checker/units/qual/mol.java | 5 +- .../checker/units/qual/radians.java | 5 +- .../checker/units/qual/s.java | 5 +- .../checker/units/qual/t.java | 3 +- .../common/aliasing/qual/LeakedToResult.java | 3 +- .../common/aliasing/qual/MaybeAliased.java | 13 +- .../common/aliasing/qual/MaybeLeaked.java | 7 +- .../common/aliasing/qual/NonLeaked.java | 3 +- .../common/aliasing/qual/Unique.java | 3 +- .../qual/EnsuresInitializedFields.java | 65 +- .../qual/InitializedFields.java | 17 +- .../qual/InitializedFieldsBottom.java | 7 +- .../qual/PolyInitializedFields.java | 3 +- .../common/reflection/qual/ClassBound.java | 13 +- .../common/reflection/qual/ClassVal.java | 21 +- .../reflection/qual/ClassValBottom.java | 9 +- .../common/reflection/qual/MethodVal.java | 26 +- .../reflection/qual/MethodValBottom.java | 9 +- .../common/reflection/qual/UnknownClass.java | 11 +- .../common/reflection/qual/UnknownMethod.java | 11 +- .../returnsreceiver/qual/BottomThis.java | 7 +- .../common/returnsreceiver/qual/This.java | 7 +- .../returnsreceiver/qual/UnknownThis.java | 9 +- .../common/subtyping/qual/Bottom.java | 7 +- .../common/subtyping/qual/Unqualified.java | 5 +- .../util/report/qual/ReportUnqualified.java | 7 +- .../common/value/qual/ArrayLen.java | 7 +- .../common/value/qual/ArrayLenRange.java | 27 +- .../common/value/qual/BoolVal.java | 7 +- .../common/value/qual/BottomVal.java | 33 +- .../common/value/qual/DoesNotMatchRegex.java | 15 +- .../common/value/qual/DoubleVal.java | 7 +- .../common/value/qual/EnsuresMinLenIf.java | 83 +- .../common/value/qual/EnumVal.java | 13 +- .../common/value/qual/IntRange.java | 27 +- .../qual/IntRangeFromGTENegativeOne.java | 3 +- .../value/qual/IntRangeFromNonNegative.java | 3 +- .../value/qual/IntRangeFromPositive.java | 3 +- .../common/value/qual/IntVal.java | 7 +- .../common/value/qual/MatchesRegex.java | 15 +- .../common/value/qual/MinLen.java | 4 +- .../value/qual/MinLenFieldInvariant.java | 23 +- .../common/value/qual/PolyValue.java | 3 +- .../common/value/qual/StringVal.java | 7 +- .../common/value/qual/UnknownVal.java | 7 +- .../dataflow/qual/AssertMethod.java | 52 +- .../checkerframework/dataflow/qual/Pure.java | 14 +- .../framework/qual/AnnotatedFor.java | 20 +- .../framework/qual/CFComment.java | 16 +- .../ConditionalPostconditionAnnotation.java | 12 +- .../framework/qual/Covariant.java | 4 +- .../framework/qual/DefaultFor.java | 108 +- .../framework/qual/DefaultQualifier.java | 98 +- .../qual/DefaultQualifierForUse.java | 4 +- .../framework/qual/EnsuresQualifier.java | 58 +- .../framework/qual/EnsuresQualifierIf.java | 76 +- .../framework/qual/FieldInvariant.java | 19 +- .../framework/qual/HasQualifierParameter.java | 12 +- .../framework/qual/LiteralKind.java | 86 +- .../framework/qual/MonotonicQualifier.java | 2 +- .../qual/NoDefaultQualifierForUse.java | 4 +- .../framework/qual/NoQualifierParameter.java | 12 +- .../ParametricTypeVariableUseQualifier.java | 22 +- .../framework/qual/PolymorphicQualifier.java | 22 +- .../qual/PostconditionAnnotation.java | 12 +- .../qual/PreconditionAnnotation.java | 4 +- .../framework/qual/QualifierArgument.java | 12 +- .../framework/qual/QualifierForLiterals.java | 32 +- .../framework/qual/RelevantJavaTypes.java | 26 +- .../framework/qual/RequiresQualifier.java | 54 +- .../framework/qual/StubFiles.java | 10 +- .../framework/qual/SubtypeOf.java | 4 +- .../framework/qual/TargetLocations.java | 12 +- .../framework/qual/TypeKind.java | 84 +- .../framework/qual/TypeUseLocation.java | 270 +- .../framework/qual/Unused.java | 10 +- .../framework/qual/UpperBoundFor.java | 28 +- .../checker/formatter/util/FormatUtil.java | 556 +- .../i18nformatter/util/I18nFormatUtil.java | 723 +- .../checker/nullness/util/NullnessUtil.java | 541 +- .../checker/nullness/util/Opt.java | 201 +- .../checker/optional/util/OptionalUtil.java | 95 +- .../checker/regex/util/RegexUtil.java | 811 +- .../signedness/util/SignednessUtil.java | 1033 +- .../signedness/util/SignednessUtilExtra.java | 101 +- .../checker/units/util/UnitsTools.java | 250 +- .../optional/util/OptionalUtilTest.java | 23 +- .../checker/regex/util/RegexUtilTest.java | 511 +- checker/jtreg/SymbolNotFoundErrors.java | 2 +- .../jtreg/index/UnneededSuppressionsTest.java | 64 +- checker/jtreg/index/valuestub/Test.java | 6 +- checker/jtreg/index/valuestub/UseTest.java | 6 +- checker/jtreg/issue1133/ClassA.java | 6 +- checker/jtreg/issue1133/ClassB.java | 6 +- .../advancedcrash/CrashyInterface.java | 2 +- .../issue469/advancedcrash/LetItCrash.java | 18 +- .../issue469/advancedcrash/SomeInterface.java | 2 +- .../issue469/simplecrash/CrashyInterface.java | 2 +- .../issue469/simplecrash/LetItCrash.java | 10 +- .../issue469/simplecrash/SomeRandomClass.java | 6 +- .../multiplecheckers/NullnessInterning.java | 8 +- checker/jtreg/multipleexecutions/Main.java | 72 +- checker/jtreg/multipleexecutions/Test.java | 8 +- .../nullness/AssignmentPerformanceTest.java | 904 +- .../jtreg/nullness/DefaultNonPublicClass.java | 14 +- checker/jtreg/nullness/Issue1438.java | 4012 +++--- checker/jtreg/nullness/Issue1438b.java | 4010 +++--- checker/jtreg/nullness/Issue1438c.java | 4010 +++--- checker/jtreg/nullness/Issue1809.java | 30 +- checker/jtreg/nullness/Issue2853.java | 150 +- checker/jtreg/nullness/Issue347.java | 30 +- checker/jtreg/nullness/Issue373.java | 8 +- checker/jtreg/nullness/Issue4948.java | 2401 ++-- checker/jtreg/nullness/Issue6373.java | 208 +- checker/jtreg/nullness/PersistUtil.java | 158 +- checker/jtreg/nullness/UncheckedWarning.java | 20 +- .../UnneededSuppressionsClassPrefixed.java | 8 +- ...uppressionsClassPrefixedRequirePrefix.java | 8 +- .../UnneededSuppressionsClassUnprefixed.java | 8 +- ...pressionsClassUnprefixedRequirePrefix.java | 8 +- .../nullness/UnneededSuppressionsTest.java | 40 +- ...UnneededSuppressionsTestRequirePrefix.java | 43 +- .../nullness/annotationsOnExtends/Other.java | 8 +- .../DefaultConstructor.java | 10 +- .../NonDefaultConstructor.java | 40 +- .../nullness/defaultsPersist/Classes.java | 448 +- .../defaultsPersist/Constructors.java | 372 +- .../nullness/defaultsPersist/Driver.java | 344 +- .../nullness/defaultsPersist/Fields.java | 438 +- .../nullness/defaultsPersist/Methods.java | 374 +- .../defaultsPersist/ReferenceInfoUtil.java | 450 +- checker/jtreg/nullness/eisop673/Lib.java | 2 +- checker/jtreg/nullness/eisop673/User.java | 8 +- .../inheritDeclAnnoPersist/AbstractClass.java | 8 +- .../inheritDeclAnnoPersist/Driver.java | 156 +- .../inheritDeclAnnoPersist/Extends.java | 54 +- .../inheritDeclAnnoPersist/Implements.java | 32 +- .../ReferenceInfoUtil.java | 197 +- .../inheritDeclAnnoPersist/Super.java | 28 +- .../nullness/issue12/BinaryDefaultTest.java | 10 +- .../issue12/BinaryDefaultTestBinary.java | 6 +- .../issue12/BinaryDefaultTestWithStub.java | 10 +- .../jtreg/nullness/issue141/CharStreams.java | 4 +- checker/jtreg/nullness/issue141/Files.java | 16 +- checker/jtreg/nullness/issue1582/foo/Foo.java | 24 +- .../JavaExpressionParseError.java | 8 +- .../jtreg/nullness/issue1929/Issue1929.java | 29 +- .../jtreg/nullness/issue1958/NPE2Test.java | 28 +- .../nullness/issue1958/SupplierDefs.java | 55 +- checker/jtreg/nullness/issue2173/View.java | 6 +- .../issue2318/RequireCheckerPrefix.java | 32 +- .../nullness/issue257/ClientBuilder.java | 16 +- checker/jtreg/nullness/issue257/Module.java | 14 +- checker/jtreg/nullness/issue257/Small.java | 12 +- checker/jtreg/nullness/issue3700/Client.java | 2 +- .../nullness/issue3700/TimeUnitRange.java | 12 +- checker/jtreg/nullness/issue380/DA.java | 4 +- checker/jtreg/nullness/issue380/DB.java | 2 +- checker/jtreg/nullness/issue380/Decl.java | 2 +- checker/jtreg/nullness/issue6053/Main.java | 2 +- checker/jtreg/nullness/issue6374/Lib.java | 16 +- checker/jtreg/nullness/issue6374/User.java | 20 +- checker/jtreg/nullness/issue767/Class1.java | 36 +- checker/jtreg/nullness/issue767/Class2.java | 28 +- checker/jtreg/nullness/issue790/Class1.java | 6 +- .../nullness/issue820/AnonymousClass.java | 20 +- checker/jtreg/nullness/issue820/Class1.java | 36 +- .../jtreg/nullness/issue820/Class1Min.java | 4 +- checker/jtreg/nullness/issue820/Class2.java | 20 +- .../jtreg/nullness/issue820/Class2Min.java | 10 +- checker/jtreg/nullness/issue820/Error.java | 2 +- checker/jtreg/nullness/issue824/Class2.java | 20 +- .../jtreg/nullness/issue824/NoStubFirst.java | 8 +- .../jtreg/nullness/issue824/NoStubSecond.java | 17 +- checker/jtreg/nullness/issue824/Second.java | 14 +- .../jtreg/nullness/issue824lib/Class1.java | 14 +- checker/jtreg/nullness/issue824lib/First.java | 6 +- .../nullness/preciseErrorMsg/Class1.java | 4 +- .../preciseErrorMsg/LocateArtificialTree.java | 11 +- .../nullness/stub-arg/EisopIssue608.java | 6 +- .../jtreg/nullness/stub-typeparams/Box.java | 10 +- .../nullness/stub-typeparams/NullableBox.java | 12 +- .../stub-typeparams/StubTypeParamsClass.java | 2 +- .../StubTypeParamsClassNbl.java | 2 +- .../stub-typeparams/StubTypeParamsMeth.java | 6 +- .../StubTypeParamsMethNbl.java | 6 +- .../stub-typeparams/StubWildcards.java | 6 +- .../stub-typeparams/StubWildcardsNbl.java | 6 +- .../stub-typeparams/StubWildcardsNon.java | 6 +- .../jtreg/nullness/stub-warnings/Binary.java | 27 +- checker/jtreg/rawtypes/RawTypeFail.java | 6 +- checker/jtreg/showPrefix/ShowPrefixTest.java | 6 +- checker/jtreg/sortwarnings/ErrorOrders.java | 84 +- .../jtreg/sortwarnings/OrderOfCheckers.java | 10 +- checker/jtreg/stubs/annotatedFor/UseTest.java | 13 +- checker/jtreg/stubs/annotatedForLib/Test.java | 10 +- .../default-on-class/List.java | 2 +- .../default-on-class/MutableList.java | 4 +- .../default-on-class/Test.java | 16 +- .../stubs/defaultqualinstub/pck/Defaults.java | 12 +- .../stubs/fakeoverrides/DefineClasses.java | 14 +- checker/jtreg/stubs/fakeoverrides/Use.java | 8 +- checker/jtreg/stubs/general/Driver.java | 8 +- checker/jtreg/stubs/general/Driver2.java | 2 +- .../stubs/issue1356/mypackage/MyClass.java | 14 +- .../stubs/issue1356/mypackage/UseMyClass.java | 6 +- checker/jtreg/stubs/issue1456/Main.java | 27 +- checker/jtreg/stubs/issue1456lib/Lib.java | 12 +- .../jtreg/stubs/issue1542/ExampleAnno.java | 154 +- .../jtreg/stubs/issue1542/NeedsIntRange.java | 12 +- checker/jtreg/stubs/issue1542/Stub.java | 52 +- .../jtreg/stubs/issue1542/UsesIntRange.java | 8 +- .../jtreg/stubs/issue1565/MyListGeneric.java | 6 +- .../jtreg/stubs/issue2147/EnumStubTest.java | 10 +- checker/jtreg/stubs/issue2147/SampleEnum.java | 4 +- checker/jtreg/stubs/sample/Sample.java | 8 +- .../jtreg/stubs/stub-over-jdk/Issue321.java | 5 +- checker/jtreg/stubs/wildcards/Wildcards.java | 6 +- checker/jtreg/tainting/NewClass.java | 14 +- checker/jtreg/tainting/classes/Issue919.java | 9 +- checker/jtreg/tainting/classes/Issue919B.java | 22 +- .../unboundedWildcards/issue1275/Crash.java | 8 +- .../unboundedWildcards/issue1275/Lib.java | 28 +- .../unboundedWildcards/issue1420/Crash8.java | 8 +- .../issue1420/Crash8Lib.java | 22 +- .../jtreg/unboundedWildcards/issue1427/B.java | 32 +- .../jtreg/unboundedWildcards/issue1427/T.java | 6 +- .../jtreg/unboundedWildcards/issue1428/B.java | 2 +- .../jtreg/unboundedWildcards/issue1428/T.java | 6 +- .../calledmethods/CalledMethodsAnalysis.java | 83 +- .../CalledMethodsAnnotatedTypeFactory.java | 970 +- .../calledmethods/CalledMethodsChecker.java | 168 +- .../calledmethods/CalledMethodsTransfer.java | 590 +- .../calledmethods/CalledMethodsVisitor.java | 244 +- ...nsuresCalledMethodOnExceptionContract.java | 87 +- .../builder/AutoValueSupport.java | 759 +- .../builder/BuilderFrameworkSupport.java | 103 +- .../builder/BuilderFrameworkSupportUtils.java | 50 +- .../calledmethods/builder/LombokSupport.java | 361 +- .../CompilerMessagesAnnotatedTypeFactory.java | 26 +- .../fenum/FenumAnnotatedTypeFactory.java | 262 +- .../checker/fenum/FenumChecker.java | 16 +- .../checker/fenum/FenumVisitor.java | 146 +- .../FormatterAnnotatedTypeFactory.java | 626 +- .../checker/formatter/FormatterTransfer.java | 57 +- .../checker/formatter/FormatterTreeUtil.java | 841 +- .../checker/formatter/FormatterVisitor.java | 494 +- .../checker/guieffect/Effect.java | 215 +- .../guieffect/GuiEffectTypeFactory.java | 1113 +- .../checker/guieffect/GuiEffectVisitor.java | 1073 +- .../i18n/I18nAnnotatedTypeFactory.java | 85 +- .../checker/i18n/I18nChecker.java | 19 +- .../LocalizableKeyAnnotatedTypeFactory.java | 48 +- .../checker/i18n/LocalizableKeyChecker.java | 4 +- .../I18nFormatterAnnotatedTypeFactory.java | 609 +- .../i18nformatter/I18nFormatterChecker.java | 3 +- .../i18nformatter/I18nFormatterTransfer.java | 119 +- .../i18nformatter/I18nFormatterTreeUtil.java | 1002 +- .../i18nformatter/I18nFormatterVisitor.java | 279 +- ...seAnnotatedTypeFactoryForIndexChecker.java | 110 +- .../checker/index/IndexAbstractTransfer.java | 149 +- .../checker/index/IndexChecker.java | 4 +- .../checker/index/IndexMethodIdentifier.java | 321 +- .../checker/index/IndexRefinementInfo.java | 141 +- .../checker/index/IndexUtil.java | 56 +- .../index/OffsetDependentTypesHelper.java | 45 +- .../checker/index/Subsequence.java | 280 +- .../LessThanAnnotatedTypeFactory.java | 588 +- .../index/inequality/LessThanChecker.java | 35 +- .../index/inequality/LessThanTransfer.java | 286 +- .../index/inequality/LessThanVisitor.java | 220 +- .../LowerBoundAnnotatedTypeFactory.java | 711 +- .../index/lowerbound/LowerBoundChecker.java | 83 +- .../index/lowerbound/LowerBoundTransfer.java | 1539 +- .../index/lowerbound/LowerBoundVisitor.java | 127 +- .../samelen/SameLenAnnotatedTypeFactory.java | 645 +- .../checker/index/samelen/SameLenChecker.java | 4 +- .../index/samelen/SameLenTransfer.java | 504 +- .../checker/index/samelen/SameLenVisitor.java | 89 +- .../SearchIndexAnnotatedTypeFactory.java | 405 +- .../index/searchindex/SearchIndexChecker.java | 35 +- .../searchindex/SearchIndexTransfer.java | 147 +- .../SubstringIndexAnnotatedTypeFactory.java | 291 +- .../substringindex/SubstringIndexChecker.java | 4 +- .../index/upperbound/OffsetEquation.java | 876 +- .../checker/index/upperbound/UBQualifier.java | 2624 ++-- .../UpperBoundAnnotatedTypeFactory.java | 1657 ++- .../index/upperbound/UpperBoundChecker.java | 150 +- .../index/upperbound/UpperBoundTransfer.java | 1475 +- .../index/upperbound/UpperBoundVisitor.java | 932 +- .../InitializationAnalysis.java | 69 +- .../InitializationAnnotatedTypeFactory.java | 451 +- .../initialization/InitializationChecker.java | 158 +- ...zationFieldAccessAnnotatedTypeFactory.java | 101 +- .../InitializationFieldAccessSubchecker.java | 52 +- ...nitializationFieldAccessTreeAnnotator.java | 242 +- .../InitializationFieldAccessVisitor.java | 45 +- ...tializationParentAnnotatedTypeFactory.java | 1677 ++- .../initialization/InitializationStore.java | 369 +- .../InitializationTransfer.java | 223 +- .../initialization/InitializationVisitor.java | 675 +- .../InterningAnnotatedTypeFactory.java | 297 +- .../checker/interning/InterningChecker.java | 3 +- .../checker/interning/InterningVisitor.java | 1719 ++- .../checker/lock/LockAnalysis.java | 61 +- .../lock/LockAnnotatedTypeFactory.java | 1272 +- .../checker/lock/LockStore.java | 426 +- .../checker/lock/LockTransfer.java | 224 +- .../checker/lock/LockTreeAnnotator.java | 63 +- .../checker/lock/LockVisitor.java | 2284 ++- .../CreatesMustCallForElementSupplier.java | 27 +- .../CreatesMustCallForToJavaExpression.java | 347 +- .../MustCallAnnotatedTypeFactory.java | 1018 +- .../checker/mustcall/MustCallChecker.java | 44 +- .../MustCallNoCreatesMustCallForChecker.java | 14 +- .../checker/mustcall/MustCallTransfer.java | 535 +- .../mustcall/MustCallTypeAnnotator.java | 26 +- .../checker/mustcall/MustCallVisitor.java | 553 +- .../nullness/CollectionToArrayHeuristics.java | 380 +- .../checker/nullness/KeyForAnalysis.java | 59 +- .../nullness/KeyForAnnotatedTypeFactory.java | 402 +- .../KeyForPropagationTreeAnnotator.java | 104 +- .../checker/nullness/KeyForPropagator.java | 341 +- .../checker/nullness/KeyForStore.java | 14 +- .../checker/nullness/KeyForSubchecker.java | 3 +- .../checker/nullness/KeyForTransfer.java | 160 +- .../checker/nullness/KeyForValue.java | 191 +- .../checker/nullness/NullnessChecker.java | 109 +- .../nullness/NullnessNoInitAnalysis.java | 56 +- .../NullnessNoInitAnnotatedTypeFactory.java | 1855 ++- .../NullnessNoInitAnnotatedTypeFormatter.java | 79 +- .../checker/nullness/NullnessNoInitStore.java | 306 +- .../nullness/NullnessNoInitSubchecker.java | 80 +- .../nullness/NullnessNoInitTransfer.java | 849 +- .../checker/nullness/NullnessNoInitValue.java | 149 +- .../nullness/NullnessNoInitVisitor.java | 1820 ++- .../nullness/SystemGetPropertyHandler.java | 199 +- .../OptionalAnnotatedTypeFactory.java | 206 +- .../checker/optional/OptionalChecker.java | 7 +- .../checker/optional/OptionalTransfer.java | 136 +- .../checker/optional/OptionalVisitor.java | 1164 +- .../PropertyKeyAnnotatedTypeFactory.java | 375 +- .../checker/propkey/PropertyKeyChecker.java | 5 +- .../regex/RegexAnnotatedTypeFactory.java | 875 +- .../checker/regex/RegexChecker.java | 21 +- .../checker/regex/RegexTransfer.java | 376 +- .../checker/regex/RegexVisitor.java | 170 +- .../MustCallConsistencyAnalyzer.java | 4650 +++---- .../resourceleak/MustCallInference.java | 1707 ++- .../resourceleak/ResourceLeakAnalysis.java | 43 +- .../ResourceLeakAnnotatedTypeFactory.java | 930 +- .../resourceleak/ResourceLeakChecker.java | 498 +- .../resourceleak/ResourceLeakTransfer.java | 269 +- .../resourceleak/ResourceLeakVisitor.java | 973 +- .../checker/resourceleak/SetOfTypes.java | 136 +- .../SignatureAnnotatedTypeFactory.java | 474 +- .../checker/signature/SignatureChecker.java | 11 +- .../checker/signature/SignatureTransfer.java | 81 +- .../SignednessAnnotatedTypeFactory.java | 719 +- .../checker/signedness/SignednessChecker.java | 35 +- .../checker/signedness/SignednessShifts.java | 518 +- .../checker/signedness/SignednessVisitor.java | 672 +- .../TaintingAnnotatedTypeFactory.java | 41 +- .../checker/tainting/TaintingVisitor.java | 33 +- .../units/UnitsAnnotatedTypeFactory.java | 1173 +- .../units/UnitsAnnotatedTypeFormatter.java | 130 +- .../units/UnitsAnnotationClassLoader.java | 76 +- .../checker/units/UnitsChecker.java | 16 +- .../checker/units/UnitsRelations.java | 55 +- .../checker/units/UnitsRelationsDefault.java | 437 +- .../checker/units/UnitsRelationsTools.java | 533 +- .../checker/units/UnitsVisitor.java | 42 +- .../test/junit/AnnotatedForNullnessTest.java | 51 +- .../junit/CalledMethodsAutoValueTest.java | 43 +- ...lledMethodsDisableReturnsReceiverTest.java | 31 +- .../CalledMethodsDisableframeworksTest.java | 51 +- .../test/junit/CalledMethodsLombokTest.java | 29 +- .../junit/CalledMethodsNoDelombokTest.java | 88 +- .../checker/test/junit/CalledMethodsTest.java | 35 +- .../CalledMethodsUseValueCheckerTest.java | 29 +- .../test/junit/CompilerMessagesTest.java | 37 +- .../checker/test/junit/CustomAliasTest.java | 43 +- .../checker/test/junit/DisbarUseTest.java | 47 +- .../checker/test/junit/FenumSwingTest.java | 43 +- .../checker/test/junit/FenumTest.java | 29 +- .../junit/FormatterLubGlbCheckerTest.java | 29 +- .../checker/test/junit/FormatterTest.java | 32 +- .../junit/FormatterUncheckedDefaultsTest.java | 37 +- .../checker/test/junit/FormatterUnitTest.java | 72 +- .../checker/test/junit/GuiEffectTest.java | 34 +- .../junit/I18nFormatterLubGlbCheckerTest.java | 29 +- .../checker/test/junit/I18nFormatterTest.java | 35 +- .../I18nFormatterUncheckedDefaultsTest.java | 37 +- .../test/junit/I18nFormatterUnitTest.java | 259 +- .../checker/test/junit/I18nTest.java | 39 +- .../test/junit/I18nUncheckedDefaultsTest.java | 37 +- .../junit/IndexInitializedFieldsTest.java | 47 +- .../checker/test/junit/IndexTest.java | 39 +- .../checker/test/junit/InterningTest.java | 32 +- ...InterningWarnRedundantAnnotationsTest.java | 37 +- .../test/junit/LockSafeDefaultsTest.java | 37 +- .../checker/test/junit/LockTest.java | 47 +- .../MustCallNoLightweightOwnershipTest.java | 31 +- .../checker/test/junit/MustCallTest.java | 29 +- .../junit/NestedAggregateCheckerTest.java | 29 +- .../test/junit/NullnessAssertsTest.java | 50 +- ...llnessAssumeAssertionsAreDisabledTest.java | 39 +- .../junit/NullnessAssumeInitializedTest.java | 52 +- .../test/junit/NullnessAssumeKeyForTest.java | 50 +- .../NullnessCheckCastElementTypeTest.java | 37 +- .../test/junit/NullnessConcurrentTest.java | 37 +- .../test/junit/NullnessEnclosingExprTest.java | 29 +- .../junit/NullnessGenericWildcardTest.java | 93 +- .../junit/NullnessInvariantArraysTest.java | 37 +- .../test/junit/NullnessJavacErrorsTest.java | 36 +- .../test/junit/NullnessJavadocTest.java | 61 +- .../test/junit/NullnessNoDelombokTest.java | 87 +- .../test/junit/NullnessNullMarkedTest.java | 51 +- .../NullnessPermitClearPropertyTest.java | 33 +- .../test/junit/NullnessRecordsTest.java | 49 +- .../test/junit/NullnessReflectionTest.java | 37 +- .../NullnessSafeDefaultsBytecodeTest.java | 37 +- .../NullnessSafeDefaultsSourceCodeTest.java | 99 +- .../test/junit/NullnessSkipDefsTest.java | 37 +- .../test/junit/NullnessSkipUsesTest.java | 37 +- .../test/junit/NullnessStubfileTest.java | 43 +- .../checker/test/junit/NullnessTempTest.java | 38 +- .../checker/test/junit/NullnessTest.java | 46 +- .../NullnessWarnRedundantAnnotationsTest.java | 37 +- .../test/junit/OptionalPureGettersTest.java | 37 +- .../checker/test/junit/OptionalTest.java | 37 +- .../checker/test/junit/ParseAllJdkTest.java | 29 +- .../checker/test/junit/RegexTest.java | 29 +- ...sourceLeakCustomIgnoredExceptionsTest.java | 33 +- ...esourceLeakExtraIgnoredExceptionsTest.java | 33 +- .../ResourceLeakNoCreatesMustCallForTest.java | 33 +- ...esourceLeakNoLightweightOwnershipTest.java | 33 +- .../ResourceLeakNoResourceAliasesTest.java | 33 +- .../ResourceLeakPermitInitializationLeak.java | 33 +- .../junit/ResourceLeakPermitStaticOwning.java | 33 +- .../checker/test/junit/ResourceLeakTest.java | 31 +- .../checker/test/junit/SignatureTest.java | 32 +- .../SignednessInitializedFieldsTest.java | 43 +- .../checker/test/junit/SignednessTest.java | 39 +- .../SignednessUncheckedDefaultsTest.java | 37 +- .../test/junit/StubparserNullnessTest.java | 37 +- .../test/junit/StubparserRecordTest.java | 47 +- .../test/junit/StubparserTaintingTest.java | 39 +- .../checker/test/junit/TaintingTest.java | 29 +- .../checker/test/junit/UnitsTest.java | 29 +- .../test/junit/ValueIndexInteractionTest.java | 39 +- .../AinferIndexAjavaGenerationTest.java | 37 +- .../AinferIndexAjavaValidationTest.java | 39 +- .../AinferNullnessAjavaGenerationTest.java | 37 +- .../AinferNullnessAjavaValidationTest.java | 39 +- .../AinferNullnessJaifsGenerationTest.java | 37 +- .../AinferNullnessJaifsValidationTest.java | 43 +- ...AinferResourceLeakAjavaGenerationTest.java | 37 +- ...AinferResourceLeakAjavaValidationTest.java | 39 +- .../AinferTestCheckerAjavaGenerationTest.java | 37 +- .../AinferTestCheckerAjavaValidationTest.java | 41 +- .../AinferTestCheckerJaifsGenerationTest.java | 45 +- .../AinferTestCheckerJaifsValidationTest.java | 39 +- .../AinferTestCheckerStubsGenerationTest.java | 37 +- .../AinferTestCheckerStubsValidationTest.java | 45 +- .../testchecker/NestedAggregateChecker.java | 25 +- .../AinferTestAnnotatedTypeFactory.java | 417 +- .../testchecker/ainfer/AinferTestChecker.java | 23 +- .../testchecker/ainfer/AinferTestVisitor.java | 46 +- .../testchecker/ainfer/qual/AinferBottom.java | 5 +- .../ainfer/qual/AinferDefaultType.java | 5 +- .../ainfer/qual/AinferImplicitAnno.java | 5 +- .../testchecker/ainfer/qual/AinferParent.java | 3 +- .../ainfer/qual/AinferSibling1.java | 3 +- .../ainfer/qual/AinferSibling2.java | 3 +- .../ainfer/qual/AinferSiblingWithFields.java | 7 +- .../testchecker/ainfer/qual/AinferTop.java | 3 +- .../disbaruse/DisbarUseTypeFactory.java | 35 +- .../disbaruse/DisbarUseVisitor.java | 92 +- .../disbaruse/qual/DisbarUseBottom.java | 5 +- .../disbaruse/qual/DisbarUseTop.java | 5 +- .../lubglb/FormatterLubGlbChecker.java | 993 +- .../lubglb/I18nFormatterLubGlbChecker.java | 855 +- .../android/support/annotation/NonNull.java | 12 +- .../android/support/annotation/Nullable.java | 12 +- .../java/javax/annotation/Nonnull.java | 3 +- .../annotation/concurrent/GuardedBy.java | 19 +- .../java/javax/annotation/meta/When.java | 16 +- .../testannotations/java/lombok/NonNull.java | 10 +- .../java/net/jcip/annotations/GuardedBy.java | 19 +- .../org/jetbrains/annotations/NotNull.java | 12 +- .../org/jetbrains/annotations/Nullable.java | 12 +- checker/tests/aggregate/NullnessAndRegex.java | 22 +- .../non-annotated/Dataset6Crash.java | 84 +- .../non-annotated/Dataset9Crash.java | 148 +- .../non-annotated/DelocalizeAtCallsites.java | 98 +- ...DependentTypesViewpointAdaptationTest.java | 106 +- .../non-annotated/InternCrash.java | 20 +- .../non-annotated/LongMathTest.java | 6 +- .../non-annotated/RequireJavadocCrash.java | 21 +- .../non-annotated/TypeVarAssignment.java | 8 +- ...ork.checker.nullness.NullnessChecker.ajava | 12 +- .../ainfer-nullness/non-annotated/Bug.java | 110 +- .../MonotonicNonNullInferenceTest.java | 108 +- .../non-annotated/NullTypeVarTest.java | 32 +- .../non-annotated/TwoCtorGenericAbstract.java | 16 +- .../non-annotated/TypeVarPlumeUtil.java | 22 +- .../non-annotated/TypeVarReturnAnnotated.java | 6 +- .../non-annotated/AddNotOwning.java | 156 +- .../ClassWithTwoOwningFieldsTest.java | 52 +- .../non-annotated/DatadirCleanupManager.java | 16 +- .../non-annotated/ECMInference.java | 98 +- .../EnsuresCalledMethodsTest.java | 40 +- .../EnsuresCalledMethodsVarArgsTest.java | 88 +- .../non-annotated/HadoopCrash.java | 12 +- .../MustCallAliasOnReceiver.java | 51 +- .../MustCallAliasOnRegularExits.java | 75 +- .../non-annotated/MustCallAliasParams.java | 260 +- .../OwnershipTransferOnConstructor.java | 37 +- .../non-annotated/OwningField.java | 28 +- .../OwningFieldIndirectCall.java | 48 +- .../non-annotated/OwningParams.java | 108 +- .../non-annotated/PurgeTxnLog.java | 24 +- .../ReplaceMustCallAliasAnnotation.java | 58 +- .../non-annotated/UnwantedECMInference.java | 24 +- ...testchecker.ainfer.AinferTestChecker.ajava | 40 +- .../AddAnnosToFormalParameterTest.java | 34 +- .../non-annotated/AinferSibling1.java | 4 +- .../AnnotationWithFieldTest.java | 90 +- .../non-annotated/Anonymous.java | 42 +- .../non-annotated/AnonymousAndInnerClass.java | 58 +- .../non-annotated/AnonymousClassField.java | 2 +- .../AnonymousClassWithField.java | 36 +- .../non-annotated/AnonymousOverride.java | 24 +- .../non-annotated/ArrayAndTypevar.java | 16 +- .../non-annotated/CompoundTypeTest.java | 30 +- .../non-annotated/CompoundTypeTest2.java | 4 +- .../ConflictingAnnotationsTest.java | 36 +- .../non-annotated/ConstructorTest.java | 12 +- .../non-annotated/CrazyEnum.java | 26 +- .../non-annotated/DefaultsTest.java | 34 +- .../non-annotated/DeviceTypeTest.java | 14 +- .../non-annotated/DoubleGeneric.java | 2 +- .../EnsuresQualifierFieldDecl.java | 6 +- .../EnsuresQualifierParamsTest.java | 340 +- .../non-annotated/EnsuresQualifierTest.java | 116 +- .../non-annotated/EnumConstants.java | 18 +- .../non-annotated/EnumMapCrash.java | 28 +- .../non-annotated/EnumTest.java | 42 +- .../non-annotated/EnumWithInnerClass.java | 20 +- .../ExistingPurityAnnotations.java | 36 +- .../non-annotated/ExpectedErrors.java | 413 +- .../FieldInOtherCompilationUnit.java | 19 +- .../non-annotated/FromReceiver.java | 84 +- .../non-annotated/IShouldBeSibling1.java | 8 +- .../IgnoreMetaAnnotationTest1.java | 24 +- .../non-annotated/ImplicitAnnosTest.java | 8 +- .../non-annotated/InheritanceTest.java | 32 +- .../InnerClassFieldDeclAnno.java | 22 +- .../non-annotated/InnerTypeTest.java | 24 +- .../non-annotated/InnerTypeTest2.java | 14 +- .../non-annotated/InnerTypeTest3.java | 10 +- .../non-annotated/InterfaceTest.java | 18 +- .../non-annotated/LUBAssignmentTest.java | 86 +- .../non-annotated/LambdaParamCrash.java | 24 +- .../non-annotated/LambdaReturn.java | 20 +- .../non-annotated/LocalClassTest.java | 8 +- .../MethodDefinedInSupertype.java | 24 +- .../MethodOverrideInSubtype.java | 22 +- .../MethodOverrideInSubtype2.java | 14 +- .../MethodParameterInferenceTest.java | 12 +- .../non-annotated/MethodReturnTest.java | 70 +- .../non-annotated/MultiDimensionalArrays.java | 433 +- .../MultidimensionalAnnotatedArray.java | 8 +- .../NamedInnerClassInAnonymous.java | 24 +- .../non-annotated/OptionGroup.java | 4 +- .../non-annotated/OtherAnnotations.java | 52 +- .../OuterClassWithTypeParam.java | 6 +- .../non-annotated/OverloadedMethodsTest.java | 20 +- .../non-annotated/OverriddenMethodsTest.java | 91 +- ...hodsTestChildInAnotherCompilationUnit.java | 8 +- .../OverrideIncompatiblePurity.java | 56 +- .../non-annotated/ParameterInferenceTest.java | 28 +- .../non-annotated/Planet.java | 94 +- .../non-annotated/PublicFieldTest.java | 86 +- .../non-annotated/Purity.java | 28 +- .../non-annotated/RequiresQualifierTest.java | 90 +- .../StringConcatenationTest.java | 44 +- .../non-annotated/Tempvars.java | 8 +- .../TreatAsSibling1InferenceTest.java | 8 +- .../non-annotated/TreatAsSibling1Test.java | 6 +- .../non-annotated/TwoMethodsSameName.java | 28 +- .../non-annotated/TypeVariablesTest.java | 43 +- .../non-annotated/TypeVariablesTest2.java | 8 +- .../non-annotated/TypeVariablesTest3.java | 28 +- .../non-annotated/UsesAnnotationAsClass.java | 12 +- .../non-annotated/UsesAnonymous.java | 50 +- .../non-annotated/ValueCheck.java | 26 +- .../non-annotated/VarargsTest.java | 36 +- .../non-annotated/WildcardReturn.java | 16 +- .../tests/calledmethods-autovalue/Animal.java | 114 +- .../calledmethods-autovalue/AnimalNoSet.java | 85 +- .../BuilderGetter.java | 43 +- .../CallWithinBuilder.java | 42 +- .../FooParcelable.java | 41 +- .../calledmethods-autovalue/GetAndIs.java | 61 +- .../calledmethods-autovalue/GetAnimal.java | 87 +- .../GuavaImmutable.java | 33 +- .../GuavaImmutablePropBuilder.java | 27 +- .../calledmethods-autovalue/Inheritance.java | 63 +- .../calledmethods-autovalue/IsPreserved.java | 47 +- .../calledmethods-autovalue/NonAVBuilder.java | 17 +- .../calledmethods-autovalue/NonBuildName.java | 39 +- .../tests/calledmethods-autovalue/Parcel.java | 8 +- .../calledmethods-autovalue/Parcelable.java | 12 +- .../SetInsideBuild.java | 51 +- .../SetInsideBuildWithCM.java | 53 +- .../calledmethods-autovalue/Validation.java | 55 +- .../AnimalSimple.java | 66 +- .../LombokBuilderExample.java | 122 +- .../Parcel.java | 8 +- .../Parcelable.java | 12 +- .../SimpleFluentInference.java | 112 +- .../calledmethods-lombok/BuilderTest.java | 38 +- .../CheckerFrameworkBuilder.java | 348 +- .../calledmethods-lombok/DefaultedName.java | 20 +- .../LombokBuilderExample.java | 12 +- .../LombokBuilderSubclassExample.java | 43 +- .../LombokDefaultAssignments.java | 17 +- ...LombokNoSingularButClearMethodExample.java | 29 +- .../LombokSingularExample.java | 31 +- .../LombokToBuilderExample.java | 16 +- .../calledmethods-lombok/OldInherited.java | 70 +- .../UnsoundnessTest.java | 26 +- .../calledmethods-usevaluechecker/Cve.java | 168 +- .../calledmethods-usevaluechecker/Cve2.java | 49 +- .../GenerateDataKeyRequestExamples.java | 205 +- .../MorePreciseFilters.java | 103 +- .../OnlyOwnersFalsePositive.java | 11 +- .../RequestCreatedInCall.java | 14 +- .../SimpleFalsePositive.java | 23 +- .../SpecialNames.java | 78 +- .../WithOwnersFilter.java | 41 +- checker/tests/calledmethods/CmPredicate.java | 1116 +- .../EnsuresCalledMethodsIfRepeatable.java | 83 +- .../EnsuresCalledMethodsIfSubclass.java | 37 +- .../EnsuresCalledMethodsIfTest.java | 93 +- ...resCalledMethodsOnExceptionRepeatable.java | 67 +- ...suresCalledMethodsOnExceptionSubclass.java | 35 +- .../EnsuresCalledMethodsOnExceptionTest.java | 177 +- .../EnsuresCalledMethodsRepeatable.java | 87 +- .../EnsuresCalledMethodsSubclass.java | 23 +- .../EnsuresCalledMethodsThisLub.java | 64 +- .../EnsuresCalledMethodsVarArgsSimple.java | 51 +- .../tests/calledmethods/ExceptionalPath.java | 17 +- .../tests/calledmethods/ExceptionalPath2.java | 57 +- checker/tests/calledmethods/FinallyClose.java | 93 +- checker/tests/calledmethods/Generics.java | 81 +- checker/tests/calledmethods/Issue20.java | 44 +- checker/tests/calledmethods/Issue5402.java | 76 +- checker/tests/calledmethods/Not.java | 112 +- checker/tests/calledmethods/Parens.java | 6 +- .../tests/calledmethods/Postconditions.java | 244 +- .../RequiresCalledMethodsRepeatable.java | 46 +- .../RequiresCalledMethodsSubclass.java | 29 +- .../RequiresCalledMethodsTest.java | 24 +- .../calledmethods/SimpleFluentInference.java | 112 +- .../tests/calledmethods/SimpleInference.java | 24 +- .../calledmethods/SimpleInferenceMerge.java | 52 +- checker/tests/calledmethods/Subtyping.java | 138 +- .../calledmethods/UnparsablePredicate.java | 64 +- checker/tests/calledmethods/Xor.java | 98 +- .../command-line/issue618/TwoCheckers.java | 10 +- checker/tests/compilermsg/Basic.java | 8 +- checker/tests/compilermsg/Diamonds.java | 19 +- .../CustomAliasedAnnotations.java | 40 +- .../disbaruse-records/DisbarredClass.java | 72 +- .../disbaruse-records/DisbarredRecord.java | 28 +- .../DisbarredRecordByStubs.java | 28 +- .../DisbarredRecordByStubs2.java | 34 +- checker/tests/fenum/CastsFenum.java | 18 +- .../tests/fenum/CatchFenumUnqualified.java | 20 +- checker/tests/fenum/TestFlow.java | 48 +- checker/tests/fenum/TestInstance.java | 54 +- checker/tests/fenum/TestPrimitive.java | 58 +- checker/tests/fenum/TestStatic.java | 76 +- checker/tests/fenum/TestSwitch.java | 54 +- checker/tests/fenum/TypeVariable.java | 12 +- .../tests/fenum/UpperBoundsInByteCode.java | 32 +- checker/tests/fenumswing/FlowBreak.java | 60 +- .../fenumswing/IdentityArrayListTest.java | 51 +- checker/tests/fenumswing/PolyTest.java | 32 +- checker/tests/fenumswing/SwingTest.java | 496 +- checker/tests/fenumswing/TypeVariable.java | 12 +- .../TestUncheckedByteCode.java | 24 +- checker/tests/formatter/ConversionBasic.java | 123 +- checker/tests/formatter/ConversionNull.java | 38 +- checker/tests/formatter/ConversionNull2.java | 19 +- checker/tests/formatter/FlowFormatter.java | 131 +- checker/tests/formatter/FormatBasic.java | 54 +- checker/tests/formatter/FormatIndexing.java | 123 +- .../formatter/FormatMethodAndFormat.java | 20 +- .../formatter/FormatMethodAnnotation.java | 73 +- .../formatter/FormatMethodInvocation.java | 116 +- checker/tests/formatter/FormatNullArray.java | 14 +- .../tests/formatter/FormatNullCategory.java | 28 +- .../tests/formatter/InvalidFormatMethod.java | 30 +- checker/tests/formatter/Issue285.java | 6 +- .../formatter/ManualExampleFormatter.java | 50 +- .../formatter/TypeInferenceNonRelevant.java | 18 +- checker/tests/formatter/VarargsFormatter.java | 102 +- .../tests/guieffect/AnonInnerDefaults.java | 425 +- checker/tests/guieffect/AssignmentTests.java | 84 +- .../tests/guieffect/BadUIOverrideChild.java | 8 +- checker/tests/guieffect/FooConflict.java | 6 +- checker/tests/guieffect/GenericSubTask.java | 20 +- .../guieffect/GenericTaskSafeConsumer.java | 4 +- .../guieffect/GenericTaskUIConsumer.java | 4 +- checker/tests/guieffect/IAsyncUITask.java | 2 +- checker/tests/guieffect/IFooSafe.java | 4 +- checker/tests/guieffect/IFooUI.java | 4 +- checker/tests/guieffect/IGenericTask.java | 4 +- checker/tests/guieffect/Java8Lambdas.java | 298 +- checker/tests/guieffect/MouseTest.java | 13 +- .../tests/guieffect/NoAnnotationsTest.java | 2 +- checker/tests/guieffect/SafeParent.java | 4 +- checker/tests/guieffect/Specialization.java | 90 +- checker/tests/guieffect/TestProgram.java | 137 +- checker/tests/guieffect/ThrowCatchTest.java | 173 +- .../guieffect/TransitiveInheritance.java | 106 +- checker/tests/guieffect/UIChild.java | 52 +- checker/tests/guieffect/UIElement.java | 10 +- checker/tests/guieffect/UIParent.java | 16 +- checker/tests/guieffect/WeakeningChild.java | 12 +- .../guieffect/packagetests/SafeByDecl.java | 2 +- .../packagetests/UIByPackageDecl.java | 6 +- .../TestUncheckedByteCode.java | 24 +- .../ConversionCategoryTest.java | 96 +- checker/tests/i18n-formatter/HasFormat.java | 95 +- checker/tests/i18n-formatter/I18nFormat.java | 70 +- .../i18n-formatter/I18nFormatForTest.java | 185 +- checker/tests/i18n-formatter/IsFormat.java | 59 +- .../ManualExampleI18nFormatter.java | 52 +- checker/tests/i18n-formatter/Syntax.java | 182 +- .../TestUncheckedByteCode.java | 24 +- checker/tests/i18n/LocalizedMessage.java | 110 +- .../RequireJavadoc.java | 1707 ++- ...framework.checker.index.IndexChecker.ajava | 1647 ++- ...ker.index.inequality.LessThanChecker.ajava | 1648 ++- ...r.index.lowerbound.LowerBoundChecker.ajava | 1656 ++- ...index.searchindex.SearchIndexChecker.ajava | 1648 ++- ...substringindex.SubstringIndexChecker.ajava | 1648 ++- ...rframework.common.value.ValueChecker.ajava | 1752 ++- checker/tests/index/AndExample.java | 14 +- checker/tests/index/AnnotatedJDKTest.java | 8 +- checker/tests/index/ArrayAsList.java | 31 +- .../tests/index/ArrayAssignmentSameLen.java | 104 +- .../index/ArrayAssignmentSameLenComplex.java | 22 +- .../ArrayConstructionPositiveLength.java | 6 +- checker/tests/index/ArrayCopy.java | 10 +- checker/tests/index/ArrayCreationChecks.java | 86 +- checker/tests/index/ArrayCreationParam.java | 18 +- checker/tests/index/ArrayCreationTest.java | 8 +- checker/tests/index/ArrayIntro.java | 28 +- checker/tests/index/ArrayIntroWithCast.java | 18 +- checker/tests/index/ArrayLenTest.java | 16 +- checker/tests/index/ArrayLength.java | 8 +- checker/tests/index/ArrayLength2.java | 10 +- checker/tests/index/ArrayLength3.java | 14 +- checker/tests/index/ArrayLengthEquality.java | 24 +- checker/tests/index/ArrayLengthLBC.java | 14 +- checker/tests/index/ArrayNull.java | 2 +- checker/tests/index/ArrayWrapper.java | 74 +- checker/tests/index/ArraysSort.java | 15 +- checker/tests/index/BasicSubsequence.java | 86 +- checker/tests/index/BasicSubsequence2.java | 44 +- checker/tests/index/BasicSubsequence3.java | 32 +- checker/tests/index/BigBinaryExpr.java | 216 +- checker/tests/index/BinarySearchTest.java | 62 +- checker/tests/index/BinomialTest.java | 110 +- checker/tests/index/BitSetLowerBound.java | 21 +- checker/tests/index/Boilerplate.java | 8 +- checker/tests/index/BottomValTest.java | 18 +- checker/tests/index/CastArray.java | 6 +- .../tests/index/CharPrintedAsVariable.java | 16 +- checker/tests/index/CharSequenceTest.java | 125 +- checker/tests/index/CharToIntCast.java | 18 +- .../tests/index/CheckAgainstNegativeOne.java | 28 +- checker/tests/index/CheckNotNull1.java | 12 +- checker/tests/index/CheckNotNull2.java | 12 +- checker/tests/index/CombineFacts.java | 18 +- checker/tests/index/CompareBySubtraction.java | 30 +- .../tests/index/CompoundAssignmentCheck.java | 12 +- checker/tests/index/ComputeConst.java | 14 +- checker/tests/index/ConditionalIndex.java | 20 +- checker/tests/index/ConstantArrays.java | 46 +- checker/tests/index/ConstantOffsets.java | 28 +- checker/tests/index/ConstantsIndex.java | 12 +- .../tests/index/CustomContractWithArgs.java | 272 +- checker/tests/index/DaikonCrash.java | 19 +- checker/tests/index/DefaultingForEach.java | 20 +- checker/tests/index/Dimension.java | 26 +- checker/tests/index/DivisionTest.java | 6 +- checker/tests/index/EndsWith.java | 8 +- checker/tests/index/EndsWith2.java | 20 +- checker/tests/index/EnumValues.java | 32 +- checker/tests/index/EqualToIndex.java | 10 +- checker/tests/index/EqualToTransfer.java | 24 +- checker/tests/index/ErrorMessageCheck.java | 16 +- checker/tests/index/Errors.java | 30 +- checker/tests/index/ExampleUsage.java | 50 +- checker/tests/index/GenericAssignment.java | 35 +- .../index/GreaterThanOrEqualTransfer.java | 18 +- checker/tests/index/GreaterThanTransfer.java | 18 +- checker/tests/index/GuavaPrimitives.java | 229 +- checker/tests/index/HexEncode.java | 22 +- checker/tests/index/Index115.java | 32 +- checker/tests/index/Index118.java | 18 +- checker/tests/index/Index118NoLoop.java | 8 +- checker/tests/index/Index132.java | 12 +- checker/tests/index/Index166.java | 12 +- checker/tests/index/Index167.java | 32 +- checker/tests/index/Index176.java | 16 +- checker/tests/index/IndexByChar.java | 14 +- .../tests/index/IndexConditionalReport.java | 14 +- checker/tests/index/IndexForAverage.java | 16 +- checker/tests/index/IndexForTest.java | 116 +- checker/tests/index/IndexForTestLBC.java | 36 +- checker/tests/index/IndexForTwoArrays.java | 22 +- checker/tests/index/IndexForTwoArrays2.java | 22 +- checker/tests/index/IndexForVarargs.java | 40 +- .../tests/index/IndexIntValVsConstant.java | 28 +- checker/tests/index/IndexOf.java | 14 +- checker/tests/index/IndexOrLowTests.java | 38 +- checker/tests/index/IndexSameLen.java | 16 +- checker/tests/index/IntroAdd.java | 26 +- checker/tests/index/IntroAnd.java | 60 +- checker/tests/index/IntroRules.java | 46 +- checker/tests/index/IntroShift.java | 10 +- checker/tests/index/IntroSub.java | 30 +- checker/tests/index/InvalidSubsequence.java | 60 +- checker/tests/index/Issue1411.java | 10 +- checker/tests/index/Issue194.java | 54 +- checker/tests/index/Issue1984.java | 8 +- checker/tests/index/Issue20.java | 10 +- checker/tests/index/Issue2029.java | 43 +- checker/tests/index/Issue2030.java | 12 +- checker/tests/index/Issue21.java | 8 +- checker/tests/index/Issue2334.java | 48 +- checker/tests/index/Issue2420.java | 22 +- checker/tests/index/Issue2452.java | 47 +- checker/tests/index/Issue2493.java | 6 +- checker/tests/index/Issue2494.java | 30 +- checker/tests/index/Issue2505.java | 10 +- checker/tests/index/Issue2613.java | 26 +- checker/tests/index/Issue2629.java | 6 +- checker/tests/index/Issue3207.java | 20 +- checker/tests/index/Issue3224.java | 65 +- checker/tests/index/Issue5471.java | 26 +- checker/tests/index/Issue58Minimization.java | 80 +- checker/tests/index/Issue60.java | 22 +- checker/tests/index/IteratorVoid.java | 10 +- checker/tests/index/Kelloggm225.java | 14 +- checker/tests/index/Kelloggm228.java | 15 +- checker/tests/index/LBCSubtyping.java | 52 +- checker/tests/index/LTLDivide.java | 38 +- .../tests/index/LTLengthOfPostcondition.java | 67 +- .../tests/index/LengthOfArrayMinusOne.java | 12 +- checker/tests/index/LengthOfTest.java | 12 +- checker/tests/index/LengthTransfer.java | 28 +- checker/tests/index/LengthTransfer2.java | 22 +- .../tests/index/LengthTransferForMinLen.java | 28 +- checker/tests/index/LessThanBug.java | 14 +- .../tests/index/LessThanConstantAddition.java | 8 +- .../tests/index/LessThanCustomCollection.java | 106 +- checker/tests/index/LessThanDec.java | 16 +- checker/tests/index/LessThanFloat.java | 68 +- checker/tests/index/LessThanFloatLiteral.java | 14 +- checker/tests/index/LessThanLen.java | 92 +- checker/tests/index/LessThanLenBug.java | 28 +- .../tests/index/LessThanOrEqualTransfer.java | 18 +- checker/tests/index/LessThanTransferTest.java | 18 +- checker/tests/index/LessThanValue.java | 184 +- .../tests/index/LessThanZeroArrayLength.java | 8 +- checker/tests/index/ListAdd.java | 107 +- checker/tests/index/ListAddAll.java | 100 +- checker/tests/index/ListAddInfiniteLoop.java | 8 +- checker/tests/index/ListGet.java | 95 +- checker/tests/index/ListIterator.java | 98 +- checker/tests/index/ListLowerBound.java | 21 +- checker/tests/index/ListRemove.java | 112 +- checker/tests/index/ListSet.java | 97 +- checker/tests/index/ListSupport.java | 83 +- checker/tests/index/ListSupportLBC.java | 119 +- checker/tests/index/ListSupportML.java | 91 +- checker/tests/index/LiteralArray.java | 18 +- checker/tests/index/LiteralString.java | 18 +- .../index/LongAndIntegerBitsMethods.java | 18 +- checker/tests/index/Loops.java | 126 +- checker/tests/index/LubIndex.java | 66 +- checker/tests/index/MLEqualTo.java | 8 +- .../tests/index/MathPlumeClasscastCrash.java | 10 +- checker/tests/index/MethodOverrides.java | 14 +- checker/tests/index/MinLenFieldInvar.java | 82 +- .../tests/index/MinLenFourShenanigans.java | 32 +- checker/tests/index/MinLenFromPositive.java | 98 +- checker/tests/index/MinLenIndexFor.java | 58 +- checker/tests/index/MinLenOneAndLength.java | 10 +- .../tests/index/MinLenSameLenInteraction.java | 8 +- checker/tests/index/MinMax.java | 14 +- checker/tests/index/MinMaxIndex.java | 48 +- checker/tests/index/Modulo.java | 24 +- checker/tests/index/NegativeArray.java | 8 +- checker/tests/index/NegativeIndex.java | 8 +- checker/tests/index/NonNegArrayLength.java | 6 +- checker/tests/index/NonNegativeCharValue.java | 6 +- checker/tests/index/NonnegativeChar.java | 49 +- checker/tests/index/NotEnoughOffsets.java | 30 +- checker/tests/index/NotEqualTransfer.java | 34 +- checker/tests/index/ObjectClone.java | 37 +- checker/tests/index/Offset97.java | 14 +- checker/tests/index/OffsetAnnotations.java | 24 +- checker/tests/index/OffsetExample.java | 127 +- checker/tests/index/OffsetsAndConstants.java | 36 +- checker/tests/index/OneLTL.java | 14 +- checker/tests/index/OneOrTwo.java | 22 +- ...yCheckSubsequenceWhenAssigningToArray.java | 32 +- .../tests/index/OuterThisJavaExpression.java | 74 +- checker/tests/index/ParserOffsetTest.java | 130 +- checker/tests/index/ParsingBug.java | 14 +- checker/tests/index/Pilot2HalfLength.java | 12 +- checker/tests/index/Pilot3ArrayCreation.java | 10 +- checker/tests/index/Pilot4Subtraction.java | 18 +- checker/tests/index/PlumeFail.java | 21 +- checker/tests/index/PlumeFailMin.java | 24 +- checker/tests/index/PlusPlusBug.java | 18 +- checker/tests/index/PolyCrash.java | 6 +- checker/tests/index/PolyLengthTest.java | 24 +- checker/tests/index/Polymorphic.java | 100 +- checker/tests/index/Polymorphic2.java | 84 +- checker/tests/index/Polymorphic3.java | 90 +- checker/tests/index/Polymorphic4.java | 8 +- checker/tests/index/PreAndPostDec.java | 48 +- checker/tests/index/PredecrementTest.java | 52 +- checker/tests/index/PrimitiveWrappers.java | 14 +- checker/tests/index/PrivateSameLen.java | 16 +- checker/tests/index/RandomTest.java | 21 +- checker/tests/index/RandomTestLBC.java | 29 +- checker/tests/index/RangeIndex.java | 8 +- checker/tests/index/Reassignment.java | 12 +- checker/tests/index/RefineEq.java | 52 +- checker/tests/index/RefineGT.java | 90 +- checker/tests/index/RefineGTE.java | 90 +- checker/tests/index/RefineLT.java | 62 +- checker/tests/index/RefineLTE.java | 90 +- checker/tests/index/RefineLTE2.java | 22 +- checker/tests/index/RefineNeq.java | 58 +- checker/tests/index/RefineNeqLength.java | 116 +- checker/tests/index/RefineSubtrahend.java | 80 +- checker/tests/index/RefinementEq.java | 38 +- checker/tests/index/RefinementGT.java | 92 +- checker/tests/index/RefinementGTE.java | 76 +- checker/tests/index/RefinementLT.java | 88 +- checker/tests/index/RefinementLTE.java | 88 +- checker/tests/index/RefinementNEq.java | 42 +- checker/tests/index/ReflectArray.java | 33 +- checker/tests/index/RegexMatcher.java | 29 +- checker/tests/index/RepeatLTLengthOf.java | 170 +- .../index/RepeatLTLengthOfWithError.java | 178 +- checker/tests/index/Return.java | 6 +- checker/tests/index/SLSubtyping.java | 28 +- .../index/SameLenAssignmentTransfer.java | 10 +- .../tests/index/SameLenEqualsRefinement.java | 60 +- .../tests/index/SameLenFormalParameter2.java | 10 +- checker/tests/index/SameLenIrrelevant.java | 24 +- .../tests/index/SameLenLUBStrangeness.java | 14 +- checker/tests/index/SameLenManyArrays.java | 80 +- .../index/SameLenNewArrayWithSameLength.java | 12 +- .../tests/index/SameLenOnFormalParameter.java | 34 +- .../index/SameLenOnFormalParameterSimple.java | 8 +- checker/tests/index/SameLenSelf.java | 14 +- checker/tests/index/SameLenSimpleCase.java | 20 +- checker/tests/index/SameLenTripleThreat.java | 46 +- checker/tests/index/SameLenWithObjects.java | 22 +- checker/tests/index/SearchIndexTests.java | 83 +- checker/tests/index/ShiftRight.java | 32 +- checker/tests/index/ShiftRightAverage.java | 14 +- checker/tests/index/SimpleCollection.java | 26 +- checker/tests/index/SimpleTransferAdd.java | 20 +- checker/tests/index/SimpleTransferSub.java | 12 +- checker/tests/index/SizeVsLength.java | 12 +- checker/tests/index/SkipBufferedReader.java | 12 +- .../index/SpecialTransfersForEquality.java | 24 +- checker/tests/index/Split.java | 11 +- checker/tests/index/StartsEndsWith.java | 39 +- checker/tests/index/StaticInitializer.java | 2 +- checker/tests/index/Stopwatch.java | 23 +- checker/tests/index/StringBuilderOffset.java | 14 +- checker/tests/index/StringIndexOf.java | 62 +- checker/tests/index/StringLenRefinement.java | 60 +- checker/tests/index/StringLength.java | 111 +- checker/tests/index/StringMethods.java | 62 +- checker/tests/index/StringOffsetTest.java | 12 +- checker/tests/index/StringSameLen.java | 66 +- .../tests/index/StringTokenizerMinLen.java | 12 +- .../index/SubstringIndexForIrrelevant.java | 16 +- .../tests/index/SubtractingNonNegatives.java | 46 +- checker/tests/index/SubtractionIndex.java | 36 +- .../tests/index/SwitchDataflowRefinement.java | 32 +- checker/tests/index/SwitchTest.java | 24 +- checker/tests/index/TestAgainstLength.java | 18 +- checker/tests/index/ToArrayIndex.java | 9 +- checker/tests/index/TransferAdd.java | 102 +- checker/tests/index/TransferDivide.java | 106 +- checker/tests/index/TransferMod.java | 42 +- checker/tests/index/TransferSub.java | 138 +- checker/tests/index/TransferTimes.java | 38 +- .../index/TypeArrayLengthWithSameLen.java | 8 +- checker/tests/index/UBLiteralFlow.java | 330 +- checker/tests/index/UBPoly.java | 22 +- checker/tests/index/UBSubtyping.java | 38 +- .../UnaryOperationParsedIncorrectly.java | 14 +- checker/tests/index/UncheckedMinLen.java | 62 +- checker/tests/index/UpperBoundRefinement.java | 60 +- checker/tests/index/ValueCheckerProblem.java | 6 +- checker/tests/index/VarArgsIncompatible.java | 12 +- checker/tests/index/VarLteVar.java | 48 +- checker/tests/index/ViewpointAdaptTest.java | 14 +- checker/tests/index/VoidType.java | 2 +- checker/tests/index/ZeroMinLen.java | 18 +- .../tests/initialization/AnonymousInit.java | 30 +- checker/tests/initialization/CastInit.java | 10 +- .../initialization/ChainedInitialization.java | 12 +- checker/tests/initialization/Commitment.java | 136 +- checker/tests/initialization/Commitment2.java | 40 +- .../tests/initialization/CommitmentFlow.java | 27 +- checker/tests/initialization/FBCList.java | 80 +- .../initialization/FieldSuppressWarnings.java | 40 +- .../tests/initialization/FieldWithInit.java | 8 +- checker/tests/initialization/FlowFbc.java | 70 +- .../tests/initialization/GenericTest12b.java | 26 +- checker/tests/initialization/InstanceOf.java | 26 +- checker/tests/initialization/Issue1044.java | 96 +- checker/tests/initialization/Issue1120.java | 42 +- checker/tests/initialization/Issue1347.java | 32 +- checker/tests/initialization/Issue3407.java | 30 +- .../tests/initialization/Issue408Init.java | 34 +- checker/tests/initialization/Issue409.java | 39 +- checker/tests/initialization/Issue4567.java | 8 +- checker/tests/initialization/Issue556a.java | 12 +- checker/tests/initialization/Issue556b.java | 132 +- checker/tests/initialization/Issue574.java | 64 +- checker/tests/initialization/Issue779.java | 30 +- checker/tests/initialization/Issue813.java | 24 +- checker/tests/initialization/Issue904.java | 32 +- checker/tests/initialization/Issue905.java | 34 +- .../NotOnlyInitializedTest.java | 56 +- .../initialization/RawMethodInvocation.java | 84 +- .../tests/initialization/RawTypesInit.java | 454 +- .../ReceiverSuperInvocation.java | 12 +- checker/tests/initialization/SimpleFbc.java | 70 +- checker/tests/initialization/StaticInit.java | 8 +- checker/tests/initialization/Subtyping.java | 86 +- checker/tests/initialization/Suppression.java | 14 +- .../initialization/TestPolyInitialized.java | 80 +- checker/tests/initialization/TryFinally.java | 352 +- checker/tests/initialization/TryFinally2.java | 39 +- .../tests/initialization/TryFinallyBreak.java | 1034 +- .../initialization/TryFinallyContinue.java | 188 +- checker/tests/initialization/TypeFrames.java | 54 +- checker/tests/initialization/TypeFrames2.java | 42 +- checker/tests/initialization/TypeFrames3.java | 30 +- checker/tests/initialization/TypeFrames4.java | 28 +- checker/tests/initialization/TypeFrames5.java | 16 +- .../UnboxUninitalizedFieldTest.java | 10 +- checker/tests/initialization/Uninit.java | 4 +- checker/tests/initialization/Uninit10.java | 16 +- checker/tests/initialization/Uninit11.java | 23 +- checker/tests/initialization/Uninit12.java | 30 +- checker/tests/initialization/Uninit13.java | 12 +- checker/tests/initialization/Uninit14.java | 14 +- checker/tests/initialization/Uninit2.java | 8 +- checker/tests/initialization/Uninit3.java | 2 +- checker/tests/initialization/Uninit4.java | 48 +- checker/tests/initialization/Uninit5.java | 4 +- checker/tests/initialization/Uninit6.java | 10 +- checker/tests/initialization/Uninit7.java | 8 +- checker/tests/initialization/Uninit8.java | 18 +- checker/tests/initialization/Uninit9.java | 20 +- .../RedundantAnnotationOnField.java | 2 +- .../StaticFinalStringDefault.java | 4 +- .../tests/interning/ArrayInitializers.java | 6 +- checker/tests/interning/Arrays.java | 139 +- checker/tests/interning/ArraysMDETest.java | 12 +- checker/tests/interning/Autoboxing.java | 314 +- checker/tests/interning/BoxingInterning.java | 128 +- checker/tests/interning/Casts.java | 6 +- checker/tests/interning/ClassDefaults.java | 27 +- checker/tests/interning/Comparison.java | 62 +- .../tests/interning/CompileTimeConstants.java | 22 +- .../interning/CompileTimeConstants2.java | 14 +- .../tests/interning/ComplexComparison.java | 147 +- .../tests/interning/ConditionalInterning.java | 8 +- .../tests/interning/ConstantsInterning.java | 56 +- checker/tests/interning/Creation.java | 88 +- checker/tests/interning/Creation2.java | 14 +- checker/tests/interning/Distinct.java | 102 +- checker/tests/interning/DontCrash.java | 31 +- checker/tests/interning/Enumerations.java | 42 +- .../tests/interning/ExpressionsInterning.java | 78 +- checker/tests/interning/FieldsImplicits.java | 18 +- checker/tests/interning/FindDistinctTest.java | 40 +- checker/tests/interning/FlowInterning.java | 94 +- checker/tests/interning/FlowInterning1.java | 18 +- checker/tests/interning/Generics.java | 225 +- checker/tests/interning/HeuristicsTest.java | 439 +- checker/tests/interning/InternMethodTest.java | 35 +- checker/tests/interning/InternUnbox.java | 16 +- checker/tests/interning/InternedClass.java | 291 +- checker/tests/interning/InternedClass2.java | 121 +- .../tests/interning/InternedClassDecl.java | 6 +- checker/tests/interning/Issue2809.java | 34 +- checker/tests/interning/Issue3594.java | 12 +- checker/tests/interning/IterableGenerics.java | 14 +- checker/tests/interning/MapEntryLubError.java | 8 +- checker/tests/interning/MethodInvocation.java | 98 +- checker/tests/interning/NestedGenerics.java | 15 +- checker/tests/interning/Options.java | 143 +- checker/tests/interning/OverrideInterned.java | 86 +- checker/tests/interning/Polymorphism.java | 166 +- .../tests/interning/PrimitivesInterning.java | 209 +- checker/tests/interning/Raw3.java | 121 +- checker/tests/interning/RecursiveClass.java | 8 +- .../tests/interning/SequenceAndIndices.java | 74 +- .../tests/interning/StaticInternMethod.java | 39 +- checker/tests/interning/StringIntern.java | 99 +- checker/tests/interning/Subclass.java | 8 +- .../interning/SuppressWarningsClass.java | 6 +- .../tests/interning/SuppressWarningsVar.java | 8 +- checker/tests/interning/TVWCSuper.java | 4 +- checker/tests/interning/TestExtSup.java | 10 +- checker/tests/interning/TestInfer.java | 32 +- .../interning/ThreadUsesObjectEquals.java | 6 +- .../interning/TypeVarPrimitivesInterning.java | 25 +- checker/tests/interning/UnboxUninterned.java | 16 +- .../tests/interning/UsesObjectEqualsTest.java | 135 +- checker/tests/lock-records/LockRecord.java | 13 +- .../lock-safedefaults/BasicLockTest.java | 197 +- checker/tests/lock/ChapterExamples.java | 1051 +- checker/tests/lock/ClassLiterals.java | 40 +- checker/tests/lock/ConstructorReturnNPE.java | 4 +- checker/tests/lock/ConstructorsLock.java | 60 +- checker/tests/lock/Fields.java | 156 +- checker/tests/lock/FlowExpressionsTest.java | 52 +- checker/tests/lock/FullyQualified.java | 13 +- checker/tests/lock/GuardSatisfiedArray.java | 17 +- checker/tests/lock/GuardSatisfiedTest.java | 530 +- .../tests/lock/GuardedByLocalVariable.java | 54 +- checker/tests/lock/Issue152.java | 38 +- checker/tests/lock/Issue2163Lock.java | 10 +- checker/tests/lock/Issue523.java | 28 +- checker/tests/lock/Issue524.java | 43 +- checker/tests/lock/Issue753.java | 235 +- checker/tests/lock/Issue804.java | 27 +- checker/tests/lock/Issue805.java | 14 +- checker/tests/lock/ItselfExpressionCases.java | 230 +- checker/tests/lock/JCIPAnnotations.java | 209 +- checker/tests/lock/LockEffectAnnotations.java | 232 +- checker/tests/lock/LockExpressionIsFinal.java | 547 +- checker/tests/lock/LockInterfaceTest.java | 93 +- checker/tests/lock/Methods.java | 94 +- .../tests/lock/NestedSynchronizedBlocks.java | 60 +- checker/tests/lock/Overriding.java | 290 +- checker/tests/lock/PrimitivesLocking.java | 302 +- checker/tests/lock/ReturnsNewObjectTest.java | 20 +- checker/tests/lock/SimpleLockTest.java | 57 +- checker/tests/lock/Strings.java | 106 +- checker/tests/lock/TestAnon.java | 16 +- .../tests/lock/TestConcurrentSemantics1.java | 89 +- .../tests/lock/TestConcurrentSemantics2.java | 40 +- checker/tests/lock/TestTreeKinds.java | 903 +- checker/tests/lock/ThisPostCondition.java | 86 +- checker/tests/lock/ThisSuper.java | 143 +- checker/tests/lock/TypeVarNull.java | 2 +- checker/tests/lock/Update.java | 14 +- checker/tests/lock/ViewpointAdaptation.java | 64 +- checker/tests/lock/ViewpointAdaptation2.java | 52 +- checker/tests/lock/ViewpointAdaptation3.java | 184 +- .../BorrowOnReturn.java | 86 +- .../NonOwningPolyInteraction.java | 69 +- .../OwningParams.java | 8 +- .../tests/mustcall/BinaryInputArchive.java | 14 +- checker/tests/mustcall/BorrowOnReturn.java | 84 +- checker/tests/mustcall/ClassForNameInit.java | 48 +- checker/tests/mustcall/CommandResponse.java | 14 +- .../mustcall/CreatesMustCallForSimple.java | 98 +- .../tests/mustcall/EditLogInputStream.java | 12 +- .../FieldInitializationWithGeneric.java | 4 +- checker/tests/mustcall/FileDescriptors.java | 23 +- checker/tests/mustcall/InferTypeArgs.java | 20 +- .../mustcall/InheritableMustCallEmpty.java | 31 +- checker/tests/mustcall/ListOfMustCall.java | 47 +- checker/tests/mustcall/LogTheSocket.java | 81 +- checker/tests/mustcall/MapWrap.java | 25 +- checker/tests/mustcall/MustCallAliasImpl.java | 19 +- .../tests/mustcall/MustCallSubtypingTest.java | 244 +- checker/tests/mustcall/MyDataInputStream.java | 26 +- .../mustcall/NonOwningPolyInteraction.java | 73 +- .../NotInheritableMustCallOnClassError.java | 2 +- ...nheritableMustCallOnFinalClassNoError.java | 2 +- .../tests/mustcall/NullOutputStreamTest.java | 9 +- checker/tests/mustcall/NullableTransfer.java | 19 +- .../tests/mustcall/OwningMustCallNothing.java | 49 +- checker/tests/mustcall/OwningParams.java | 12 +- .../PlumeUtilRequiredAnnotations.java | 77 +- .../mustcall/PolyMustCallDifferentNames.java | 108 +- checker/tests/mustcall/PolyTests.java | 70 +- checker/tests/mustcall/SimpleException.java | 18 +- checker/tests/mustcall/SimpleSocketField.java | 27 +- .../tests/mustcall/SimpleStreamExample.java | 6 +- .../tests/mustcall/SocketBufferedReader.java | 27 +- checker/tests/mustcall/StreamBool.java | 8 +- checker/tests/mustcall/StringSort.java | 8 +- checker/tests/mustcall/Subtype0.java | 80 +- checker/tests/mustcall/Subtyping.java | 100 +- checker/tests/mustcall/SystemInOut.java | 15 +- checker/tests/mustcall/ToStringOnSocket.java | 18 +- .../tests/mustcall/TryWithResourcesCrash.java | 32 +- .../mustcall/TryWithResourcesSimple.java | 69 +- checker/tests/mustcall/TypeArgs.java | 171 +- .../AnnotatedForNullness.java | 124 +- .../annotated/Test.java | 8 +- .../notannotated/Test.java | 8 +- .../nullness-asserts/NonNullMapValue.java | 399 +- .../TestAssumeAssertionsAreEnabled.java | 16 +- .../TestAssumeAssertionsAreDisabled.java | 14 +- .../AssumeInitTest.java | 34 +- .../AssumeKeyForTest.java | 71 +- .../Issue1315.java | 48 +- .../Issue350.java | 54 +- .../NullnessEnclosingExprTest.java | 56 +- checker/tests/nullness-extra/Bug109_A.java | 10 +- checker/tests/nullness-extra/Bug109_B.java | 14 +- .../nullness-extra/compat/CompatTest.java | 7 +- .../tests/nullness-extra/compat/lib/Lib.java | 6 +- .../tests/nullness-extra/issue265/Delta.java | 8 +- .../issue265/ImmutableList.java | 6 +- .../nullness-extra/issue309/Issue309.java | 6 +- .../nullness-extra/issue309/lib/Lib.java | 4 +- .../nullness-extra/issue348/Issue348.java | 8 +- .../nullness-extra/issue348/lib/Lib.java | 4 +- .../nullness-extra/issue348/lib/LibSuper.java | 4 +- .../issue3597/testpkg/Issue3597A.java | 6 +- .../issue3597/testpkg/Issue3597B.java | 6 +- .../nullness-extra/issue502/Issue502.java | 8 +- .../nullness-extra/issue5174/Issue5174.java | 96 +- .../nullness-extra/issue559/Issue559.java | 12 +- .../nullness-extra/issue594/Issue594.java | 12 +- .../nullness-extra/issue607/Issue607.java | 8 +- .../issue607/Issue607Interface.java | 2 +- .../issue607/Issue607SuperClass.java | 4 +- .../nullness-extra/multiple-errors/C1.java | 2 +- .../nullness-extra/multiple-errors/C2.java | 2 +- .../nullness-extra/multiple-errors/C3.java | 12 +- .../nullness-extra/multiple-errors/C4.java | 6 +- .../test/PackageAnnotationTest.java | 4 +- .../shorthand/NullnessRegexWithErrors.java | 10 +- .../GenericWildcardInheritance.java | 4 +- .../nullness-genericwildcard/Issue511.java | 34 +- .../GwiParent.java | 2 +- .../AssignmentDuringInitialization.java | 62 +- .../EisopIssue635.java | 32 +- .../EnumFieldUninit.java | 28 +- .../nullness-initialization/FieldInit.java | 16 +- .../nullness-initialization/FinalClass.java | 36 +- .../FinalClassLambda.java | 140 +- .../FlowConstructor.java | 54 +- .../FlowConstructor2.java | 10 +- .../FlowInitialization.java | 68 +- .../nullness-initialization/Initializer.java | 148 +- .../nullness-initialization/Issue1096.java | 102 +- .../nullness-initialization/Issue1590.java | 20 +- .../nullness-initialization/Issue1590a.java | 18 +- .../nullness-initialization/Issue261.java | 22 +- .../nullness-initialization/Issue345.java | 22 +- .../nullness-initialization/Issue354.java | 28 +- .../nullness-initialization/Issue400.java | 24 +- .../nullness-initialization/Issue408.java | 34 +- .../KeyForValidation.java | 193 +- .../nullness-initialization/Listener.java | 34 +- .../MethodInvocation.java | 44 +- .../MultiConstructorInit.java | 32 +- .../ObjectArrayParam.java | 12 +- .../ObjectListParam.java | 17 +- .../PrivateMethodUnknownInit.java | 20 +- .../tests/nullness-initialization/Raw2.java | 42 +- .../nullness-initialization/RawField.java | 58 +- .../RawMethodInvocation.java | 52 +- .../RawTypesBounded.java | 348 +- .../nullness-initialization/Simple2.java | 34 +- .../StaticInitialization.java | 8 +- .../StaticInitializer.java | 62 +- .../SuperConstructorInit.java | 22 +- .../nullness-initialization/Suppression.java | 34 +- .../nullness-initialization/ThisNodeTest.java | 28 +- .../nullness-initialization/Throwing.java | 26 +- .../nullness-initialization/TryCatch.java | 55 +- .../TwoStaticInitBlocks.java | 70 +- .../nullness-initialization/ValidType.java | 12 +- .../nullness-initialization/VarInfoName.java | 16 +- .../nullness-initialization/Wellformed.java | 86 +- .../generics/AnnotatedGenerics.java | 122 +- .../generics/AnnotatedGenerics2.java | 256 +- .../generics/GenericBoundsExplicit.java | 69 +- .../generics/Issue314.java | 33 +- .../generics/Issue783c.java | 16 +- .../generics/NullableLUB.java | 44 +- .../generics/WellformedBounds.java | 44 +- .../java8/lambda/LambdaInit.java | 346 +- .../java8/lambda/ReceiversLambda.java | 34 +- .../TwoDimensionalArray.java | 12 +- .../JavadocJdkAnnotations.java | 23 +- .../nullness-nodelombok/UnsoundnessTest.java | 20 +- .../NotNullMarkedBecauseChildPackage.java | 2 +- .../NullMarkedBecausePackageIs.java | 4 +- .../PermitClearProperty.java | 235 +- .../tests/nullness-records/BasicRecord.java | 14 +- .../nullness-records/BasicRecordCanon.java | 16 +- .../nullness-records/BasicRecordNullable.java | 40 +- .../nullness-records/DefaultQualRecord.java | 60 +- .../tests/nullness-records/GenericPair.java | 10 +- checker/tests/nullness-records/Issue5200.java | 30 +- .../tests/nullness-records/LocalRecords.java | 14 +- .../nullness-records/NestedRecordTest.java | 160 +- .../nullness-records/NormalizingRecord.java | 62 +- .../tests/nullness-records/RecordPurity.java | 172 +- .../nullness-records/RecordPurityGeneric.java | 84 +- .../RecordPurityOverride.java | 56 +- .../NullnessReflectionExampleTest.java | 47 +- .../NullnessReflectionResolutionTest.java | 75 +- .../tests/nullness-reflection/VoidTest.java | 6 +- .../AnnotatedJdkTest.java | 15 +- .../ArraysMDE.java | 40 +- .../BytecodeDefaultsTest.java | 24 +- .../BasicSafeDefaultsTest.java | 70 +- .../Issue3449.java | 12 +- .../PrimitiveClassLiteral.java | 52 +- .../Lib.java | 36 +- .../tests/nullness-skipdefs/SkipDefs1.java | 18 +- .../tests/nullness-skipdefs/SkipDefs2.java | 20 +- .../tests/nullness-skipuses/SkipUses1.java | 32 +- .../tests/nullness-skipuses/SkipUses2.java | 38 +- .../tests/nullness-stubfile/Issue4598.java | 15 +- .../NullnessStubfileMerge.java | 32 +- .../AnnoOnTypeVariableCrashCase.java | 6 +- .../RedundantAnnoWithDefaultQualifier.java | 16 +- .../RedundantAnnotation.java | 101 +- .../RedundantAnnotationOptions.java | 27 +- .../tests/nullness/AliasedAnnotations.java | 136 +- checker/tests/nullness/Aliasing.java | 28 +- .../nullness/AnnotatedJdkEqualsTest.java | 20 +- checker/tests/nullness/AnnotatedJdkTest.java | 19 +- .../tests/nullness/AnnotatedSupertype.java | 23 +- checker/tests/nullness/AnonymousSkipDefs.java | 30 +- checker/tests/nullness/ArrayArgs.java | 44 +- .../tests/nullness/ArrayAssignmentFlow.java | 20 +- checker/tests/nullness/ArrayCreation.java | 14 +- .../tests/nullness/ArrayCreationNullable.java | 324 +- .../tests/nullness/ArrayCreationSubArray.java | 20 +- checker/tests/nullness/ArrayIndex.java | 22 +- checker/tests/nullness/ArrayInitBug.java | 8 +- checker/tests/nullness/ArrayLazyNN.java | 20 +- checker/tests/nullness/ArrayNew.java | 27 +- checker/tests/nullness/ArrayRefs.java | 61 +- checker/tests/nullness/AssertAfter.java | 98 +- checker/tests/nullness/AssertAfter2.java | 193 +- .../tests/nullness/AssertAfterChecked.java | 288 +- checker/tests/nullness/AssertIfChecked.java | 311 +- checker/tests/nullness/AssertIfClient.java | 98 +- checker/tests/nullness/AssertIfFalseTest.java | 98 +- .../tests/nullness/AssertIfFalseTest2.java | 38 +- .../tests/nullness/AssertIfNonNullTest.java | 18 +- checker/tests/nullness/AssertInStatic.java | 14 +- checker/tests/nullness/AssertMethodTest.java | 126 +- .../nullness/AssertNonNullIfNonNullTest.java | 72 +- checker/tests/nullness/AssertNonNullTest.java | 26 +- checker/tests/nullness/AssertNullable.java | 38 +- .../nullness/AssertParameterNullness.java | 44 +- checker/tests/nullness/AssertTwice.java | 48 +- checker/tests/nullness/AssertWithStatic.java | 98 +- checker/tests/nullness/Asserts.java | 120 +- checker/tests/nullness/BinaryOp.java | 6 +- checker/tests/nullness/BinarySearch.java | 14 +- checker/tests/nullness/BoxingNullness.java | 282 +- checker/tests/nullness/Bug102.java | 26 +- checker/tests/nullness/Bug103.java | 134 +- checker/tests/nullness/CallSuper.java | 17 +- checker/tests/nullness/CastTypeVariable.java | 16 +- checker/tests/nullness/CastsNullness.java | 166 +- checker/tests/nullness/ChainAssignment.java | 80 +- checker/tests/nullness/ChicoryPremain.java | 16 +- .../tests/nullness/ClassGetCanonicalName.java | 2 +- checker/tests/nullness/CompoundAssign.java | 20 +- .../tests/nullness/ConditionalNullness.java | 156 +- checker/tests/nullness/ConditionalOr.java | 8 +- .../tests/nullness/ConditionalPolyNull.java | 58 +- checker/tests/nullness/Conditions.java | 50 +- .../nullness/ConstructorPostcondition.java | 28 +- checker/tests/nullness/ControlFlow.java | 20 +- checker/tests/nullness/CopyOfArray.java | 17 +- checker/tests/nullness/DaikonEnhancedFor.java | 39 +- .../nullness/DaikonEnhancedForNoThis.java | 39 +- checker/tests/nullness/DaikonTests.java | 214 +- checker/tests/nullness/DefaultAnnotation.java | 209 +- checker/tests/nullness/DefaultFlow.java | 24 +- checker/tests/nullness/DefaultInterface.java | 8 +- checker/tests/nullness/DefaultLoops.java | 57 +- checker/tests/nullness/DefaultingForEach.java | 59 +- checker/tests/nullness/DefaultsNullness.java | 20 +- checker/tests/nullness/DotClass.java | 19 +- checker/tests/nullness/EisopIssue308.java | 14 +- checker/tests/nullness/EmptyConstructor.java | 12 +- .../nullness/EnsuresKeyForOverriding.java | 31 +- .../EnsuresNonNullIfInheritedTest.java | 52 +- .../tests/nullness/EnsuresNonNullIfTest.java | 72 +- .../tests/nullness/EnsuresNonNullIfTest2.java | 98 +- .../tests/nullness/EnsuresNonNullIfTest4.java | 36 +- .../nullness/EnsuresNonNullIfTestSimple.java | 68 +- checker/tests/nullness/EnumStaticBlock.java | 18 +- checker/tests/nullness/EnumsNullness.java | 56 +- checker/tests/nullness/EqualToNullness.java | 66 +- checker/tests/nullness/ExceptionParam.java | 34 +- checker/tests/nullness/Exceptions.java | 72 +- .../tests/nullness/ExplictTypeVarAnnos.java | 84 +- .../tests/nullness/ExpressionsNullness.java | 85 +- checker/tests/nullness/ExtendsArrayList.java | 10 +- checker/tests/nullness/FenumExplicit.java | 24 +- .../nullness/FieldWithAnnotatedLambda.java | 21 +- checker/tests/nullness/FinalFields.java | 66 +- checker/tests/nullness/FinalVar.java | 28 +- checker/tests/nullness/FinalVar2.java | 68 +- checker/tests/nullness/FinalVar3.java | 16 +- checker/tests/nullness/FindBugs.java | 40 +- checker/tests/nullness/FlowAssignment.java | 10 +- checker/tests/nullness/FlowCompound.java | 104 +- .../nullness/FlowCompoundConcatenation.java | 26 +- checker/tests/nullness/FlowConditions.java | 55 +- .../nullness/FlowExpressionParsingBug.java | 173 +- checker/tests/nullness/FlowField.java | 106 +- checker/tests/nullness/FlowLoop.java | 298 +- checker/tests/nullness/FlowNegation.java | 158 +- checker/tests/nullness/FlowNonThis.java | 73 +- checker/tests/nullness/FlowNullness.java | 518 +- checker/tests/nullness/FlowSelf.java | 16 +- checker/tests/nullness/ForEachMin.java | 19 +- .../nullness/FullyQualifiedAnnotation.java | 40 +- checker/tests/nullness/GeneralATFStore.java | 10 +- checker/tests/nullness/GenericCast.java | 14 +- checker/tests/nullness/GetConstantStr.java | 8 +- .../tests/nullness/GetInterfacesPurity.java | 12 +- checker/tests/nullness/GetPackage1.java | 8 +- checker/tests/nullness/GetProperty.java | 33 +- checker/tests/nullness/GetRefArg.java | 13 +- checker/tests/nullness/HasInnerClass.java | 8 +- checker/tests/nullness/HierarchicalInit.java | 20 +- .../tests/nullness/ImplementInterface.java | 10 +- checker/tests/nullness/Imports1.java | 6 +- checker/tests/nullness/Imports2.java | 6 +- checker/tests/nullness/InferListParam.java | 8 +- checker/tests/nullness/InferNullType.java | 52 +- .../InferTypeArgsConditionalExpression.java | 12 +- .../nullness/InfiniteLoopIsSameType.java | 32 +- .../tests/nullness/InitSuppressWarnings.java | 8 +- checker/tests/nullness/InitThrows.java | 14 +- .../InitializationAssertionFailure.java | 4 +- checker/tests/nullness/InitializedField.java | 27 +- checker/tests/nullness/InnerCrash.java | 16 +- checker/tests/nullness/InvariantTypes.java | 42 +- checker/tests/nullness/IsEmptyPoll.java | 31 +- checker/tests/nullness/Issue1027.java | 45 +- checker/tests/nullness/Issue1046Java7.java | 34 +- checker/tests/nullness/Issue1059.java | 22 +- checker/tests/nullness/Issue1102.java | 23 +- checker/tests/nullness/Issue1147.java | 12 +- checker/tests/nullness/Issue1307.java | 8 +- checker/tests/nullness/Issue1406.java | 55 +- checker/tests/nullness/Issue1522.java | 91 +- checker/tests/nullness/Issue1555.java | 8 +- checker/tests/nullness/Issue160.java | 90 +- checker/tests/nullness/Issue1628.java | 16 +- checker/tests/nullness/Issue1712.java | 30 +- checker/tests/nullness/Issue1797.java | 402 +- checker/tests/nullness/Issue1847.java | 42 +- checker/tests/nullness/Issue1847B.java | 27 +- checker/tests/nullness/Issue1922.java | 45 +- checker/tests/nullness/Issue1949.java | 23 +- checker/tests/nullness/Issue1981.java | 22 +- checker/tests/nullness/Issue1983.java | 51 +- checker/tests/nullness/Issue2013.java | 182 +- checker/tests/nullness/Issue2031.java | 63 +- checker/tests/nullness/Issue2048.java | 30 +- checker/tests/nullness/Issue2052.java | 20 +- checker/tests/nullness/Issue2171.java | 58 +- checker/tests/nullness/Issue2247.java | 51 +- checker/tests/nullness/Issue2407.java | 26 +- checker/tests/nullness/Issue2432.java | 205 +- checker/tests/nullness/Issue2432b.java | 63 +- checker/tests/nullness/Issue2470.java | 98 +- checker/tests/nullness/Issue2564.java | 25 +- checker/tests/nullness/Issue2565.java | 8 +- checker/tests/nullness/Issue2587.java | 47 +- checker/tests/nullness/Issue2619.java | 63 +- checker/tests/nullness/Issue2619b.java | 75 +- checker/tests/nullness/Issue266.java | 26 +- checker/tests/nullness/Issue266a.java | 26 +- checker/tests/nullness/Issue2721.java | 12 +- checker/tests/nullness/Issue273.java | 33 +- checker/tests/nullness/Issue2865.java | 32 +- checker/tests/nullness/Issue2888.java | 48 +- checker/tests/nullness/Issue289.java | 47 +- checker/tests/nullness/Issue293.java | 90 +- checker/tests/nullness/Issue295.java | 62 +- checker/tests/nullness/Issue296.java | 37 +- checker/tests/nullness/Issue3013.java | 10 +- checker/tests/nullness/Issue3015.java | 24 +- checker/tests/nullness/Issue3020.java | 24 +- checker/tests/nullness/Issue3022.java | 12 +- checker/tests/nullness/Issue3033.java | 30 +- checker/tests/nullness/Issue306.java | 66 +- checker/tests/nullness/Issue308.java | 25 +- checker/tests/nullness/Issue3150.java | 40 +- checker/tests/nullness/Issue328.java | 25 +- checker/tests/nullness/Issue331.java | 8 +- checker/tests/nullness/Issue3349.java | 11 +- checker/tests/nullness/Issue338.java | 8 +- checker/tests/nullness/Issue3443.java | 22 +- checker/tests/nullness/Issue355.java | 71 +- checker/tests/nullness/Issue3614.java | 216 +- checker/tests/nullness/Issue3622.java | 177 +- checker/tests/nullness/Issue3631.java | 16 +- checker/tests/nullness/Issue3681.java | 84 +- checker/tests/nullness/Issue369.java | 6 +- checker/tests/nullness/Issue370.java | 6 +- checker/tests/nullness/Issue372.java | 13 +- checker/tests/nullness/Issue3754.java | 24 +- checker/tests/nullness/Issue376.java | 8 +- checker/tests/nullness/Issue3792.java | 16 +- checker/tests/nullness/Issue3845.java | 32 +- checker/tests/nullness/Issue3850.java | 18 +- checker/tests/nullness/Issue388.java | 18 +- checker/tests/nullness/Issue3884.java | 28 +- checker/tests/nullness/Issue3888.java | 18 +- checker/tests/nullness/Issue391.java | 70 +- checker/tests/nullness/Issue3929.java | 33 +- checker/tests/nullness/Issue3935.java | 10 +- checker/tests/nullness/Issue3970.java | 20 +- checker/tests/nullness/Issue4007.java | 51 +- checker/tests/nullness/Issue411.java | 32 +- checker/tests/nullness/Issue414.java | 61 +- checker/tests/nullness/Issue415.java | 65 +- checker/tests/nullness/Issue419.java | 20 +- checker/tests/nullness/Issue425.java | 39 +- checker/tests/nullness/Issue427.java | 23 +- checker/tests/nullness/Issue4372.java | 24 +- checker/tests/nullness/Issue4381.java | 20 +- checker/tests/nullness/Issue4412.java | 423 +- checker/tests/nullness/Issue4523.java | 14 +- checker/tests/nullness/Issue4579.java | 45 +- checker/tests/nullness/Issue4593.java | 19 +- checker/tests/nullness/Issue4614.java | 32 +- checker/tests/nullness/Issue471.java | 8 +- checker/tests/nullness/Issue4853Nullness.java | 22 +- checker/tests/nullness/Issue4889.java | 17 +- checker/tests/nullness/Issue4923.java | 30 +- checker/tests/nullness/Issue4924.java | 20 +- checker/tests/nullness/Issue500.java | 32 +- checker/tests/nullness/Issue5042.java | 101 +- checker/tests/nullness/Issue5075NPE.java | 64 +- checker/tests/nullness/Issue5075a.java | 40 +- checker/tests/nullness/Issue5075b.java | 42 +- checker/tests/nullness/Issue5189a.java | 4 +- checker/tests/nullness/Issue5189b.java | 12 +- checker/tests/nullness/Issue520.java | 42 +- checker/tests/nullness/Issue5245.java | 4 +- checker/tests/nullness/Issue531.java | 16 +- checker/tests/nullness/Issue554.java | 88 +- checker/tests/nullness/Issue563.java | 10 +- checker/tests/nullness/Issue577.java | 84 +- checker/tests/nullness/Issue578.java | 10 +- checker/tests/nullness/Issue579Error.java | 12 +- checker/tests/nullness/Issue580.java | 10 +- checker/tests/nullness/Issue602.java | 26 +- checker/tests/nullness/Issue6260.java | 28 +- checker/tests/nullness/Issue653.java | 48 +- checker/tests/nullness/Issue67.java | 22 +- checker/tests/nullness/Issue672.java | 26 +- checker/tests/nullness/Issue679.java | 10 +- checker/tests/nullness/Issue738.java | 50 +- checker/tests/nullness/Issue741.java | 26 +- checker/tests/nullness/Issue752.java | 96 +- checker/tests/nullness/Issue759.java | 54 +- checker/tests/nullness/Issue764.java | 24 +- checker/tests/nullness/Issue765.java | 18 +- checker/tests/nullness/Issue811.java | 36 +- checker/tests/nullness/Issue829.java | 14 +- checker/tests/nullness/Issue868.java | 102 +- checker/tests/nullness/Issue906.java | 14 +- checker/tests/nullness/Issue961.java | 67 +- checker/tests/nullness/Issue986.java | 50 +- checker/tests/nullness/Issue989.java | 21 +- checker/tests/nullness/Iterate.java | 8 +- checker/tests/nullness/IteratorEarlyExit.java | 55 +- checker/tests/nullness/JUnitNull.java | 6 +- checker/tests/nullness/JavaCopExplosion.java | 213 +- checker/tests/nullness/JavaCopFlow.java | 312 +- .../tests/nullness/JavaCopRandomTests.java | 38 +- checker/tests/nullness/JavaExprContext.java | 278 +- checker/tests/nullness/KeyForAutoboxing.java | 74 +- checker/tests/nullness/KeyForChecked.java | 251 +- checker/tests/nullness/KeyForDiamond.java | 21 +- checker/tests/nullness/KeyForFlow.java | 277 +- checker/tests/nullness/KeyForIssue328.java | 25 +- .../tests/nullness/KeyForLocalSideEffect.java | 29 +- .../tests/nullness/KeyForLocalVariable.java | 41 +- checker/tests/nullness/KeyForLub.java | 67 +- checker/tests/nullness/KeyForMultiple.java | 45 +- .../tests/nullness/KeyForPolymorphism.java | 21 +- .../tests/nullness/KeyForPostcondition.java | 65 +- checker/tests/nullness/KeyForPropagation.java | 29 +- checker/tests/nullness/KeyForShadowing.java | 109 +- checker/tests/nullness/KeyForStaticField.java | 37 +- checker/tests/nullness/KeyForSubst.java | 79 +- checker/tests/nullness/KeyForSubtyping.java | 251 +- checker/tests/nullness/KeyForTypeVar.java | 11 +- .../nullness/KeyFor_DirectionsFinder.java | 51 +- checker/tests/nullness/KeyFors.java | 187 +- checker/tests/nullness/Lazy.java | 102 +- .../tests/nullness/LazyInitialization.java | 160 +- checker/tests/nullness/LogRecordTest.java | 20 +- checker/tests/nullness/LogicOperations.java | 102 +- checker/tests/nullness/LubTest.java | 32 +- checker/tests/nullness/MapGetNullable.java | 211 +- checker/tests/nullness/MapMerge.java | 34 +- checker/tests/nullness/Marino.java | 100 +- .../MethodOverloadingContractsKeyFor.java | 55 +- checker/tests/nullness/MethodTypeVars4.java | 39 +- .../nullness/MissingBoundAnnotations.java | 25 +- checker/tests/nullness/MisuseProperties.java | 145 +- .../nullness/MonotonicNonNullFieldTest.java | 28 +- .../tests/nullness/MonotonicNonNullTest.java | 14 +- checker/tests/nullness/MultiAnnotations.java | 10 +- checker/tests/nullness/MultipleErrors.java | 12 +- checker/tests/nullness/MyException.java | 30 +- checker/tests/nullness/NNOEMoreTests.java | 68 +- checker/tests/nullness/NNOEStaticFields.java | 199 +- .../nullness/NegatingConditionalNullness.java | 91 +- checker/tests/nullness/NewNullable.java | 18 +- checker/tests/nullness/NewObjectNonNull.java | 26 +- .../tests/nullness/NonEmptyCollection.java | 71 +- .../tests/nullness/NonNullInitialization.java | 10 +- .../tests/nullness/NonNullIteratorNext.java | 18 +- checker/tests/nullness/NullableArrays.java | 8 +- .../tests/nullness/NullableConstructor.java | 4 +- checker/tests/nullness/NullableObject.java | 14 +- .../tests/nullness/NullnessFieldInvar.java | 292 +- checker/tests/nullness/NullnessIssue4996.java | 28 +- .../tests/nullness/ObjectsRequireNonNull.java | 19 +- .../nullness/ObjectsRequireNonNullElse.java | 14 +- checker/tests/nullness/OptTest.java | 14 +- checker/tests/nullness/OverrideANNA.java | 36 +- checker/tests/nullness/OverrideANNA2.java | 52 +- checker/tests/nullness/OverrideANNA3.java | 44 +- checker/tests/nullness/OverrideGenerics.java | 6 +- checker/tests/nullness/OverrideNNOE.java | 30 +- checker/tests/nullness/OverrideNNOE2.java | 36 +- .../tests/nullness/ParameterExpression.java | 333 +- checker/tests/nullness/PolyTest.java | 18 +- checker/tests/nullness/Polymorphism.java | 58 +- .../tests/nullness/PolymorphismArrays.java | 104 +- checker/tests/nullness/PostconditionBug.java | 10 +- .../nullness/PreconditionFieldNotInStore.java | 26 +- .../tests/nullness/PreventClearProperty.java | 239 +- .../tests/nullness/PrimitivesNullness.java | 6 +- checker/tests/nullness/PureTest.java | 196 +- checker/tests/nullness/RawAndPrimitive.java | 22 +- checker/tests/nullness/RawInt.java | 12 +- checker/tests/nullness/RawInt2.java | 20 +- checker/tests/nullness/RawParameter.java | 12 +- checker/tests/nullness/RawSuper.java | 134 +- .../tests/nullness/RawTypesAssignment.java | 37 +- checker/tests/nullness/RawTypesNullness.java | 108 +- checker/tests/nullness/RawTypesUses.java | 90 +- checker/tests/nullness/ReadyReadLine.java | 38 +- .../tests/nullness/ReceiverAnnotation.java | 10 +- .../tests/nullness/ReferencesDefaults.java | 26 +- checker/tests/nullness/RefineArray.java | 40 +- checker/tests/nullness/RefineOverride.java | 274 +- .../tests/nullness/RepeatEnsuresKeyFor.java | 171 +- .../RepeatEnsuresKeyForWithError.java | 167 +- .../tests/nullness/RepeatEnsuresNonNull.java | 148 +- .../RepeatEnsuresNonNullWithError.java | 156 +- .../nullness/RepeatedRequiresNonNull.java | 106 +- .../tests/nullness/RequiresNonNullTest.java | 250 +- .../tests/nullness/RequiresPrivateField.java | 10 +- checker/tests/nullness/SAMLineParser.java | 8 +- checker/tests/nullness/SamFileValidator.java | 8 +- checker/tests/nullness/ScopingConstruct.java | 728 +- checker/tests/nullness/SelfAssignment.java | 18 +- checker/tests/nullness/SelfDependentType.java | 171 +- .../tests/nullness/SequenceAndIndices.java | 8 +- checker/tests/nullness/SetIteratorTest.java | 72 +- checker/tests/nullness/SortingCollection.java | 12 +- checker/tests/nullness/StaticInLoop.java | 14 +- .../tests/nullness/StaticInitializer2.java | 10 +- checker/tests/nullness/Stats.java | 15 +- .../tests/nullness/StringTernaryConcat.java | 6 +- checker/tests/nullness/SuperCall.java | 16 +- .../tests/nullness/SuppressDeprecation.java | 24 +- .../nullness/SuppressWarningsPartialKeys.java | 222 +- .../tests/nullness/SuppressWarningsTest.java | 10 +- checker/tests/nullness/SwitchTest.java | 52 +- checker/tests/nullness/Synchronization.java | 50 +- checker/tests/nullness/TernaryNested.java | 19 +- checker/tests/nullness/TernaryNullness.java | 8 +- checker/tests/nullness/TestAssignment.java | 16 +- .../nullness/TestFromPullRequest880.java | 15 +- checker/tests/nullness/TestInfer.java | 25 +- checker/tests/nullness/TestPolyNull.java | 78 +- checker/tests/nullness/TestValOf.java | 14 +- checker/tests/nullness/ThisIsNN.java | 38 +- checker/tests/nullness/ThisQualified.java | 10 +- checker/tests/nullness/ThisTest.java | 22 +- checker/tests/nullness/ThreadLocalTest.java | 32 +- checker/tests/nullness/ThreadLocalTest2.java | 88 +- .../tests/nullness/ToArrayDiagnostics.java | 42 +- checker/tests/nullness/ToArrayNullness.java | 179 +- checker/tests/nullness/ToArrayTest.java | 27 +- checker/tests/nullness/TryWithResources.java | 67 +- .../tests/nullness/TryWithResourcesAnno.java | 28 +- .../nullness/TypeVarPrimitivesNullness.java | 44 +- checker/tests/nullness/UnannoPrimitives.java | 78 +- .../nullness/UnannoPrimitivesDefaults.java | 24 +- checker/tests/nullness/UnboxConditions.java | 46 +- checker/tests/nullness/Unboxing.java | 64 +- checker/tests/nullness/UnexpectedRaw.java | 92 +- checker/tests/nullness/UnusedNullness.java | 81 +- checker/tests/nullness/UnusedOnClass.java | 15 +- checker/tests/nullness/UtilArrays.java | 79 +- checker/tests/nullness/VarargsNullness.java | 96 +- checker/tests/nullness/VarargsNullness2.java | 22 +- checker/tests/nullness/VoidUse.java | 70 +- .../tests/nullness/WeakHasherMapNonNull.java | 27 +- .../tests/nullness/WeakHasherMapNullable.java | 25 +- checker/tests/nullness/WeakIdentityPair.java | 17 +- checker/tests/nullness/WeakRef.java | 9 +- checker/tests/nullness/WhileTest.java | 100 +- checker/tests/nullness/Widening.java | 10 +- checker/tests/nullness/WildcardGLB.java | 105 +- checker/tests/nullness/WildcardSubtype.java | 94 +- checker/tests/nullness/WildcardSubtype2.java | 87 +- checker/tests/nullness/Wildcards.java | 47 +- checker/tests/nullness/ZeroVarargs.java | 4 +- .../tests/nullness/flow/EisopIssue300.java | 30 +- .../tests/nullness/flow/EisopIssue300B.java | 26 +- .../tests/nullness/flow/EisopIssue300C.java | 36 +- .../tests/nullness/flow/EisopIssue553.java | 32 +- checker/tests/nullness/flow/Issue1214.java | 204 +- checker/tests/nullness/flow/Issue1345.java | 25 +- checker/tests/nullness/flow/Issue1727.java | 36 +- checker/tests/nullness/flow/Issue3249.java | 92 +- checker/tests/nullness/flow/Issue3267.java | 50 +- checker/tests/nullness/flow/Issue3275.java | 276 +- checker/tests/nullness/flow/Issue341.java | 20 +- checker/tests/nullness/flow/Issue818.java | 94 +- checker/tests/nullness/flow/MapGet.java | 31 +- checker/tests/nullness/flow/PathJoins.java | 22 +- checker/tests/nullness/flow/PureAndFlow.java | 86 +- checker/tests/nullness/flow/PurityError.java | 16 +- .../tests/nullness/flow/TestNullnessUtil.java | 144 +- checker/tests/nullness/flow/TestOpt.java | 106 +- .../nullness/generics/AnnotatedGenerics3.java | 64 +- .../generics/AnnotatedTypeParams.java | 24 +- .../generics/AnnotatedTypeParams2.java | 24 +- .../generics/AnnotatedTypeParams4.java | 112 +- .../nullness/generics/AnonymousClass.java | 20 +- .../generics/BoundedWildcardTest.java | 53 +- .../nullness/generics/BoxingGenerics.java | 22 +- .../nullness/generics/CapturedWildcards.java | 23 +- .../generics/CollectionsAnnotations.java | 82 +- .../generics/CollectionsAnnotationsMin.java | 82 +- .../tests/nullness/generics/GenericArgs.java | 93 +- .../tests/nullness/generics/GenericArgs2.java | 75 +- .../tests/nullness/generics/GenericArgs3.java | 113 +- .../nullness/generics/GenericReturnField.java | 12 +- .../nullness/generics/GenericTest11.java | 24 +- .../nullness/generics/GenericsBounds1.java | 26 +- .../nullness/generics/GenericsBounds2.java | 38 +- .../nullness/generics/GenericsBounds3.java | 20 +- .../nullness/generics/GenericsBounds4.java | 38 +- .../nullness/generics/GenericsBounds5.java | 82 +- .../generics/GenericsConstructor.java | 20 +- .../nullness/generics/GenericsExample.java | 324 +- .../nullness/generics/GenericsExampleMin.java | 140 +- .../tests/nullness/generics/InferMethod.java | 46 +- .../nullness/generics/InferredPrimitive.java | 6 +- checker/tests/nullness/generics/Issue134.java | 28 +- .../tests/nullness/generics/Issue1838.java | 37 +- .../tests/nullness/generics/Issue1838Min.java | 9 +- checker/tests/nullness/generics/Issue269.java | 42 +- checker/tests/nullness/generics/Issue270.java | 12 +- .../tests/nullness/generics/Issue2722.java | 18 +- checker/tests/nullness/generics/Issue282.java | 25 +- .../tests/nullness/generics/Issue282Min.java | 17 +- .../tests/nullness/generics/Issue2995.java | 10 +- .../tests/nullness/generics/Issue3025.java | 10 +- .../tests/nullness/generics/Issue3027.java | 16 +- checker/tests/nullness/generics/Issue312.java | 24 +- checker/tests/nullness/generics/Issue313.java | 8 +- checker/tests/nullness/generics/Issue319.java | 70 +- checker/tests/nullness/generics/Issue326.java | 11 +- checker/tests/nullness/generics/Issue329.java | 44 +- checker/tests/nullness/generics/Issue335.java | 18 +- checker/tests/nullness/generics/Issue337.java | 42 +- checker/tests/nullness/generics/Issue339.java | 16 +- checker/tests/nullness/generics/Issue421.java | 22 +- checker/tests/nullness/generics/Issue422.java | 8 +- checker/tests/nullness/generics/Issue428.java | 6 +- checker/tests/nullness/generics/Issue459.java | 24 +- .../tests/nullness/generics/Issue5006.java | 24 +- .../tests/nullness/generics/Issue6374.java | 44 +- .../tests/nullness/generics/Issue783a.java | 14 +- .../tests/nullness/generics/Issue783b.java | 14 +- checker/tests/nullness/generics/Issue849.java | 10 +- .../nullness/generics/KeyForPolyKeyFor.java | 29 +- checker/tests/nullness/generics/MapLoop.java | 23 +- .../nullness/generics/MethodTypeVars.java | 40 +- .../nullness/generics/MethodTypeVars2.java | 40 +- .../nullness/generics/MethodTypeVars3.java | 65 +- .../nullness/generics/MethodTypeVars5.java | 160 +- .../nullness/generics/MethodTypeVars6.java | 78 +- .../nullness/generics/MethodTypeVars7.java | 136 +- .../nullness/generics/MixTypeAndDeclAnno.java | 56 +- checker/tests/nullness/generics/MyMap.java | 23 +- .../nullness/generics/NullableGeneric.java | 52 +- .../nullness/generics/NullnessBound.java | 30 +- .../tests/nullness/generics/OptionsTest.java | 36 +- .../nullness/generics/RawTypesGenerics.java | 34 +- .../tests/nullness/generics/SourceVsJdk.java | 6 +- .../tests/nullness/generics/SuperRawness.java | 12 +- .../nullness/generics/TernaryGenerics.java | 72 +- .../tests/nullness/generics/VarArgsTest.java | 14 +- .../nullness/generics/WildcardAnnos.java | 69 +- .../generics/WildcardBoundDefault.java | 14 +- .../nullness/generics/WildcardBounds.java | 218 +- .../nullness/generics/WildcardOverride.java | 25 +- .../generics/WildcardOverrideMore.java | 62 +- .../nullness/generics/WildcardSubtyping.java | 75 +- .../nullness/generics/WildcardSubtyping2.java | 34 +- .../generics/WildcardSubtypingTypeArray.java | 11 +- .../nullness/generics/WildcardSuper.java | 50 +- .../tests/nullness/java-unsound/Figure1.java | 28 +- .../tests/nullness/java-unsound/Figure3.java | 60 +- .../nullness/java-unsound/Figure3NC.java | 50 +- .../tests/nullness/java-unsound/Figure4.java | 24 +- .../tests/nullness/java-unsound/Figure6.java | 38 +- .../nullness/java-unsound/Figure6NC.java | 36 +- .../tests/nullness/java-unsound/Figure7.java | 40 +- checker/tests/nullness/java17/Greeting.java | 35 +- .../java17/InstanceOfPatternVariable.java | 13 +- checker/tests/nullness/java17/Issue5047.java | 23 +- checker/tests/nullness/java17/Issue5967.java | 33 +- .../nullness/java17/NullnessInstanceOf.java | 70 +- .../nullness/java17/NullnessSwitchArrows.java | 128 +- .../NullnessSwitchExpressionLambda.java | 27 +- .../java17/NullnessSwitchExpressions.java | 108 +- .../java17/NullnessSwitchStatementRules.java | 66 +- .../java17/SwitchExpressionInvariant.java | 39 +- .../nullness/java17/SwitchTestIssue5412.java | 214 +- checker/tests/nullness/java21/FlowSwitch.java | 158 +- checker/tests/nullness/java21/Issue6290.java | 10 +- .../tests/nullness/java21/NullRedundant.java | 174 +- .../java21/NullableSwitchSelector.java | 56 +- .../nullness/java21/SimpleCaseGuard.java | 30 +- .../tests/nullness/java8/DefaultMethods.java | 20 +- checker/tests/nullness/java8/Issue1000.java | 19 +- .../tests/nullness/java8/Issue1046Java8.java | 43 +- checker/tests/nullness/java8/Issue1098.java | 20 +- .../tests/nullness/java8/Issue1098NoJdk.java | 20 +- checker/tests/nullness/java8/Issue1633.java | 219 +- checker/tests/nullness/java8/Issue363.java | 14 +- checker/tests/nullness/java8/Issue366.java | 15 +- checker/tests/nullness/java8/Issue448.java | 8 +- checker/tests/nullness/java8/Issue448Ext.java | 20 +- checker/tests/nullness/java8/Issue496.java | 20 +- checker/tests/nullness/java8/Issue529.java | 24 +- checker/tests/nullness/java8/Issue557.java | 101 +- checker/tests/nullness/java8/Issue579.java | 28 +- checker/tests/nullness/java8/Issue596.java | 21 +- checker/tests/nullness/java8/Issue704.java | 2 +- checker/tests/nullness/java8/Issue720.java | 8 +- .../tests/nullness/java8/UnionTypeBug.java | 35 +- .../tests/nullness/java8/lambda/Dataflow.java | 22 +- .../java8/lambda/FinalLocalVariables.java | 160 +- .../nullness/java8/lambda/Issue1864.java | 14 +- .../nullness/java8/lambda/Issue1864b.java | 14 +- .../nullness/java8/lambda/Issue1897.java | 10 +- .../nullness/java8/lambda/Issue3217.java | 25 +- .../tests/nullness/java8/lambda/Issue367.java | 6 +- .../tests/nullness/java8/lambda/Issue403.java | 10 +- .../tests/nullness/java8/lambda/Issue436.java | 24 +- .../tests/nullness/java8/lambda/Issue572.java | 17 +- .../tests/nullness/java8/lambda/Issue870.java | 18 +- .../java8/lambda/Issue953bLambda.java | 29 +- .../nullness/java8/lambda/LambdaNullness.java | 180 +- .../nullness/java8/lambda/Parameters.java | 92 +- .../java8/lambda/ParametersInBody.java | 72 +- .../lambda/ParametersInBodyGenerics.java | 67 +- .../java8/lambda/RefinedLocalInLambda.java | 43 +- .../tests/nullness/java8/lambda/Returns.java | 48 +- .../tests/nullness/java8/lambda/Shadowed.java | 28 +- .../nullness/java8/lambda/TypeVarAssign.java | 10 +- .../methodref/AssignmentContextTest.java | 60 +- .../methodref/ClassTypeArgInference.java | 54 +- .../java8/methodref/FromByteCode.java | 10 +- .../java8/methodref/GenericArity.java | 20 +- .../java8/methodref/GroundTargetTypeLub.java | 22 +- .../java8/methodref/MemberReferences.java | 64 +- .../java8/methodref/PolyNullness.java | 34 +- .../java8/methodref/Postconditions.java | 32 +- .../java8/methodref/ReceiversMethodref.java | 122 +- .../java8inference/FalsePositives.java | 73 +- .../nullness/java8inference/InLambda.java | 42 +- .../java8inference/InLambdaAnnotated.java | 52 +- .../nullness/java8inference/Inference.java | 34 +- .../java8inference/InferenceSimpler.java | 18 +- .../nullness/java8inference/Issue1032.java | 49 +- .../nullness/java8inference/Issue1084.java | 22 +- .../nullness/java8inference/Issue1366.java | 12 +- .../nullness/java8inference/Issue1464.java | 22 +- .../nullness/java8inference/Issue1630.java | 24 +- .../nullness/java8inference/Issue1818.java | 11 +- .../nullness/java8inference/Issue1954.java | 33 +- .../nullness/java8inference/Issue2235.java | 34 +- .../nullness/java8inference/Issue2719.java | 23 +- .../nullness/java8inference/Issue402.java | 37 +- .../nullness/java8inference/Issue4048.java | 19 +- .../nullness/java8inference/Issue887.java | 21 +- .../java8inference/Issue953bInference.java | 31 +- .../nullness/java8inference/Issue980.java | 21 +- .../tests/nullness/java8inference/OneOf.java | 20 +- .../nullness/java8inference/SimpleLambda.java | 19 +- .../jdkannotations/EisopIssue270.java | 12 +- .../jdkannotations/HashtableTest.java | 23 +- .../nullness/jdkannotations/Issue1142.java | 13 +- .../jdkannotations/Issue1402EnumName.java | 12 +- .../nullness/jdkannotations/TreeSetTest.java | 15 +- .../optional-pure-getters/PureGetterTest.java | 132 +- .../tests/optional/EnsuresPresentIfTest.java | 133 +- .../optional/FilterIspresentMapGetTest.java | 6 +- checker/tests/optional/FlowSensitivity.java | 30 +- .../tests/optional/IfPresentRefinement.java | 20 +- checker/tests/optional/JdkCheck.java | 139 +- checker/tests/optional/JdkCheck11.java | 37 +- checker/tests/optional/MapNoNewNull.java | 13 +- checker/tests/optional/Marks1Partial.java | 110 +- checker/tests/optional/Marks2.java | 24 +- checker/tests/optional/Marks3a.java | 44 +- checker/tests/optional/Marks3aJdk11.java | 24 +- checker/tests/optional/Marks3b.java | 34 +- checker/tests/optional/Marks3bJdk11.java | 32 +- checker/tests/optional/Marks4.java | 98 +- checker/tests/optional/Marks5.java | 73 +- checker/tests/optional/Marks6.java | 32 +- checker/tests/optional/Marks7.java | 12 +- .../tests/optional/NestedOptionalTest.java | 38 +- checker/tests/optional/OptionalBoxed.java | 30 +- .../optional/OptionalMapMethodReference.java | 47 +- .../tests/optional/OptionalParameterTest.java | 12 +- .../tests/optional/RequiresPresentTest.java | 135 +- checker/tests/optional/SubtypeCheck.java | 39 +- .../tests/optional/java17/OptionalSwitch.java | 14 +- checker/tests/regex/AllowedTypes.java | 80 +- checker/tests/regex/AnnotatedTypeParams3.java | 77 +- checker/tests/regex/Annotation.java | 20 +- checker/tests/regex/Constructors.java | 34 +- checker/tests/regex/Continue.java | 83 +- checker/tests/regex/ForEach.java | 10 +- checker/tests/regex/GenericsBoundsRange.java | 43 +- checker/tests/regex/GenericsEnclosing.java | 28 +- checker/tests/regex/GroupCounts.java | 235 +- checker/tests/regex/IntCast.java | 6 +- checker/tests/regex/InvariantTypes.java | 205 +- checker/tests/regex/InvariantTypesAtm.java | 15 +- checker/tests/regex/Issue3267.java | 19 +- checker/tests/regex/Issue3281.java | 109 +- checker/tests/regex/Issue809.java | 10 +- checker/tests/regex/LubRegex.java | 96 +- checker/tests/regex/MatcherGroupCount.java | 78 +- checker/tests/regex/MyMatchResult.java | 82 +- checker/tests/regex/Nested.java | 12 +- checker/tests/regex/PartialRegex.java | 30 +- checker/tests/regex/RawTypeTest.java | 165 +- checker/tests/regex/RegexUtilClient.java | 128 +- checker/tests/regex/SimpleRegex.java | 273 +- checker/tests/regex/TestIsRegex.java | 213 +- checker/tests/regex/TestRegex.java | 20 +- checker/tests/regex/TypeParamSubtype.java | 35 +- checker/tests/regex/TypeVarMemberSelect.java | 18 +- checker/tests/regex/WildcardInvoke.java | 14 +- checker/tests/regex_poly/PolyRegexTests.java | 148 +- .../StringBuilderToStringPolyRegex.java | 6 +- .../BasicTest.java | 97 +- .../BasicTest.java | 49 +- .../ConnectingServerSockets.java | 77 +- .../ConnectingSockets.java | 99 +- .../CreatesMustCallForSimpler.java | 40 +- .../DifferentSWKeys.java | 30 +- .../SocketContainer.java | 43 +- .../ACOwning.java | 98 +- .../MustCallAliasExamples.java | 81 +- .../MustCallAliasPassthroughLocal.java | 29 +- .../InstanceInitializer.java | 77 +- .../SocketContainer.java | 33 +- .../StaticOwningField.java | 79 +- .../StaticOwningFieldOtherClass.java | 17 +- .../ACExceptionalExitPointTest.java | 58 +- .../resourceleak/ACMethodInvocationTest.java | 174 +- checker/tests/resourceleak/ACOwning.java | 120 +- .../resourceleak/ACRegularExitPointTest.java | 597 +- checker/tests/resourceleak/ACSocketTest.java | 845 +- .../AccumulationValueFieldTest.java | 56 +- .../resourceleak/AccumulationValueTest.java | 146 +- checker/tests/resourceleak/BindChannel.java | 54 +- .../tests/resourceleak/COAnonymousClass.java | 106 +- checker/tests/resourceleak/COInSubtype.java | 34 +- checker/tests/resourceleak/CheckFields.java | 286 +- checker/tests/resourceleak/CloseSuper.java | 51 +- .../tests/resourceleak/CloseableAndMore.java | 33 +- .../tests/resourceleak/CommonModuleCrash.java | 10 +- .../resourceleak/ConnectingServerSockets.java | 77 +- .../tests/resourceleak/ConnectingSockets.java | 101 +- .../resourceleak/ConnectingSockets2.java | 14 +- .../resourceleak/ConstructorAddsMustCall.java | 24 +- .../CreatesMustCallForIndirect.java | 104 +- .../CreatesMustCallForInnerClass.java | 44 +- .../CreatesMustCallForOverride.java | 42 +- .../CreatesMustCallForOverride2.java | 334 +- .../CreatesMustCallForRepeat.java | 110 +- .../CreatesMustCallForSimple.java | 112 +- .../CreatesMustCallForSimpler.java | 34 +- .../CreatesMustCallForTargets.java | 121 +- .../CreatesMustCallForTwoAliases.java | 86 +- .../tests/resourceleak/DifferentSWKeys.java | 32 +- checker/tests/resourceleak/DoubleIf.java | 77 +- .../tests/resourceleak/DuplicateError.java | 25 +- checker/tests/resourceleak/Enclosing.java | 28 +- checker/tests/resourceleak/EnhancedFor.java | 97 +- .../resourceleak/FileDescriptorTest.java | 91 +- checker/tests/resourceleak/FilesTest.java | 16 +- .../tests/resourceleak/GetChannelOnLocks.java | 57 +- checker/tests/resourceleak/HBaseReport1.java | 40 +- checker/tests/resourceleak/HDFSReport.java | 34 +- checker/tests/resourceleak/HDFSReport2.java | 24 +- checker/tests/resourceleak/HdfsReport3.java | 26 +- checker/tests/resourceleak/IOUtilsTest.java | 21 +- .../resourceleak/IgnoredExceptionECM.java | 18 +- checker/tests/resourceleak/IndexMode.java | 125 +- .../tests/resourceleak/InheritanceStream.java | 24 +- .../InitializationAfterSuperTest.java | 25 +- .../resourceleak/InputOutputStreams.java | 329 +- .../resourceleak/InstanceInitializer.java | 83 +- checker/tests/resourceleak/IsClosed.java | 31 +- checker/tests/resourceleak/Issue4815.java | 19 +- checker/tests/resourceleak/Issue6030.java | 35 +- checker/tests/resourceleak/JavaEETest.java | 6 +- checker/tests/resourceleak/LemmaStack.java | 49 +- checker/tests/resourceleak/LhsArrayCast.java | 10 +- .../resourceleak/LineNumberReaderTest.java | 47 +- .../tests/resourceleak/MCANotOwningField.java | 14 +- .../tests/resourceleak/MCAOwningField.java | 25 +- checker/tests/resourceleak/MCAWithThis.java | 18 +- .../ManualMustCallEmptyOnConstructor.java | 27 +- .../MultipleIdenticalReturns.java | 46 +- ...MultipleMethodParamsMustCallAliasTest.java | 127 +- .../resourceleak/MultipleOwnedResources.java | 35 +- ...ultipleOwnedResourcesOfDifferentTypes.java | 64 +- .../MustCallAliasDifferentMethodNames.java | 58 +- .../resourceleak/MustCallAliasExamples.java | 81 +- .../tests/resourceleak/MustCallAliasImpl.java | 25 +- .../resourceleak/MustCallAliasImplWrong1.java | 27 +- .../resourceleak/MustCallAliasImplWrong2.java | 25 +- ...allAliasInitializeWithOwningParameter.java | 35 +- .../MustCallAliasLayeredStreams.java | 29 +- .../resourceleak/MustCallAliasLocal.java | 31 +- .../resourceleak/MustCallAliasNormalExit.java | 107 +- .../resourceleak/MustCallAliasNotThis.java | 83 +- .../MustCallAliasNullConstructor.java | 31 +- .../MustCallAliasOwningField.java | 33 +- .../MustCallAliasPassthrough.java | 9 +- .../MustCallAliasPassthroughChain.java | 87 +- .../MustCallAliasPassthroughLocal.java | 27 +- .../MustCallAliasPassthroughThis.java | 15 +- .../MustCallAliasPassthroughWrong1.java | 11 +- .../MustCallAliasPassthroughWrong2.java | 27 +- .../MustCallAliasPassthroughWrong3.java | 35 +- .../MustCallAliasPassthroughWrong4.java | 13 +- .../MustCallAliasSocketException.java | 36 +- .../MustCallAliasSubstitution.java | 29 +- .../tests/resourceleak/MustCallNullStore.java | 22 +- .../resourceleak/MustCloseIntoObject.java | 8 +- .../NonFinalFieldOnlyOverwrittenIfNull.java | 119 +- .../NonFinalFieldOnlyOverwrittenIfNull2.java | 89 +- .../tests/resourceleak/OptionalSocket.java | 23 +- .../OwnershipTransferAtReassignment.java | 40 +- .../resourceleak/OwnershipWithExceptions.java | 437 +- ...ingAndEnsuresCalledMethodsOnException.java | 105 +- .../OwningEnsuresCalledMethods.java | 27 +- .../OwningFieldStringComparison.java | 37 +- checker/tests/resourceleak/OwningMCU.java | 10 +- .../tests/resourceleak/OwningOverride.java | 37 +- .../tests/resourceleak/OwningOverride2.java | 21 +- checker/tests/resourceleak/PaperExample.java | 22 +- checker/tests/resourceleak/PrimitiveCast.java | 49 +- .../resourceleak/ReassignmentWithMCA.java | 75 +- .../resourceleak/ReplicaInputStreams.java | 35 +- .../resourceleak/ReplicaInputStreams2.java | 37 +- .../RequiresCalledMethodsTest.java | 87 +- .../resourceleak/ReturnOwningObject.java | 26 +- checker/tests/resourceleak/RlcThisTest.java | 86 +- .../resourceleak/SSLSocketFactoryTest.java | 20 +- checker/tests/resourceleak/SelfAssign.java | 49 +- .../resourceleak/SimpleSocketExample.java | 16 +- .../tests/resourceleak/SneakyDestructor.java | 23 +- checker/tests/resourceleak/SneakyDrop.java | 86 +- .../tests/resourceleak/SocketContainer.java | 39 +- .../tests/resourceleak/SocketContainer2.java | 25 +- .../tests/resourceleak/SocketContainer3.java | 21 +- checker/tests/resourceleak/SocketField.java | 101 +- .../tests/resourceleak/SocketIntoList.java | 65 +- .../resourceleak/SocketNullOverwrite.java | 14 +- .../tests/resourceleak/StaticOwningField.java | 89 +- .../StaticOwningFieldOtherClass.java | 23 +- .../resourceleak/StringConcatenation.java | 24 +- .../tests/resourceleak/StringFromObject.java | 20 +- .../resourceleak/TernaryExpressions.java | 188 +- .../TryWithResourcesDeclaration.java | 52 +- .../resourceleak/TryWithResourcesFP.java | 34 +- .../TryWithResourcesMultiResources.java | 51 +- .../TryWithResourcesVariable.java | 177 +- .../TwoConstructorsCloseable.java | 18 +- .../tests/resourceleak/TwoOwningMCATest.java | 58 +- .../tests/resourceleak/TwoResourcesECM.java | 53 +- .../resourceleak/TwoSocketContainer.java | 51 +- .../resourceleak/TwoSocketContainerSafe.java | 59 +- .../tests/resourceleak/TypeProcessError.java | 41 +- .../tests/resourceleak/TypevarDefault.java | 42 +- checker/tests/resourceleak/TypevarSimple.java | 8 +- .../resourceleak/UnconnectedSocketAlias.java | 14 +- checker/tests/resourceleak/WrapperStream.java | 6 +- .../tests/resourceleak/WrapperStreamPoly.java | 19 +- .../ZookeeperByteBufferInputStream.java | 83 +- .../tests/resourceleak/ZookeeperReport1.java | 103 +- .../tests/resourceleak/ZookeeperReport1a.java | 103 +- .../tests/resourceleak/ZookeeperReport3.java | 105 +- .../ZookeeperReport3WithOptional.java | 71 +- .../tests/resourceleak/ZookeeperReport6.java | 16 +- .../resourceleak/ZookeeperTernaryCrash.java | 110 +- .../java17/SwitchExpressions.java | 320 +- checker/tests/signature/ArraysAsList.java | 9 +- .../signature/CanonicalNameNonEmptyTest.java | 38 +- .../signature/ClassGetNameBinaryName.java | 140 +- checker/tests/signature/Conversion.java | 196 +- checker/tests/signature/DiamondTest.java | 9 +- checker/tests/signature/FakeOverridePoly.java | 9 +- .../tests/signature/PolySignatureTest.java | 14 +- .../tests/signature/PolySignatureTest2.java | 17 +- .../tests/signature/RefinedReturnTest.java | 24 +- .../signature/SignatureConcatenation.java | 8 +- .../tests/signature/SignatureLiteralTest.java | 4 +- .../signature/SignatureTypeFactoryTest.java | 1750 +-- checker/tests/signature/StubLibraryTest.java | 14 +- .../SignednessFields.java | 10 +- .../TestUncheckedByteCode.java | 30 +- .../tests/signedness/AdditionWithChar.java | 8 +- .../tests/signedness/AnnoBeforeModifier.java | 138 +- checker/tests/signedness/Arrays.java | 6 +- .../tests/signedness/BinaryOperations.java | 212 +- checker/tests/signedness/BooleansTest.java | 30 +- checker/tests/signedness/BoxedPrimitives.java | 137 +- checker/tests/signedness/Cast.java | 26 +- checker/tests/signedness/CastedShifts.java | 740 +- checker/tests/signedness/CharCast.java | 40 +- checker/tests/signedness/CharCastedToInt.java | 10 +- checker/tests/signedness/CharComparisons.java | 48 +- .../tests/signedness/CharSignedObject.java | 6 +- checker/tests/signedness/CharToFloat.java | 20 +- .../tests/signedness/CombinationIterator.java | 24 +- checker/tests/signedness/CompareChars.java | 22 +- checker/tests/signedness/Comparisons.java | 94 +- .../CompoundAssignmentsSignedness.java | 222 +- .../CompoundAssignmentsSignedness2.java | 90 +- checker/tests/signedness/ConstantTests.java | 32 +- .../tests/signedness/DefaultsSignedness.java | 242 +- checker/tests/signedness/Desugar.java | 43 +- .../signedness/IrrelevantAnnotationsTest.java | 12 +- checker/tests/signedness/Issue2482.java | 104 +- checker/tests/signedness/Issue2483.java | 8 +- checker/tests/signedness/Issue2534.java | 32 +- checker/tests/signedness/Issue2543.java | 73 +- checker/tests/signedness/Issue3710.java | 28 +- checker/tests/signedness/Issue5256.java | 24 +- .../tests/signedness/JdkConstantsTest.java | 16 +- checker/tests/signedness/LiteralCast.java | 117 +- .../tests/signedness/LocalVarDefaults.java | 36 +- checker/tests/signedness/LowerUpperBound.java | 46 +- checker/tests/signedness/MaskedShifts.java | 490 +- checker/tests/signedness/ObjectCasts.java | 198 +- checker/tests/signedness/Operations.java | 96 +- .../signedness/PolymorphicReturnType.java | 8 +- checker/tests/signedness/PrimitiveCasts.java | 70 +- .../signedness/RestrictedPolymorphism.java | 22 +- checker/tests/signedness/ShiftAndMask.java | 8 +- .../tests/signedness/ShiftPropogation.java | 40 +- .../signedness/SignednessAnnotationError.java | 11 +- .../signedness/SignednessAssignments.java | 258 +- checker/tests/signedness/SignednessCast.java | 54 +- .../tests/signedness/SignednessEquals.java | 181 +- .../signedness/SignednessManualExample.java | 62 +- .../signedness/SignednessNumberCasts.java | 22 +- .../tests/signedness/SignednessRangeTest.java | 10 +- checker/tests/signedness/StringConcat.java | 8 +- checker/tests/signedness/TestPrintln.java | 22 +- checker/tests/signedness/ToHexString.java | 18 +- checker/tests/signedness/UnsignedConcat.java | 90 +- checker/tests/signedness/UnsignedConcat2.java | 27 +- .../signedness/UnsignedRightShiftTest.java | 84 +- checker/tests/signedness/Utils.java | 226 +- checker/tests/signedness/UtilsJava8.java | 174 +- .../tests/signedness/ValueIntegration.java | 974 +- .../tests/signedness/WideningConversion.java | 164 +- checker/tests/signedness/WideningFloat.java | 26 +- .../signedness/WideningInitialization.java | 48 +- .../tests/signedness/java17/Issue6100.java | 17 +- .../MultidimentionalArrayAnnotationTest.java | 386 +- .../NoExplicitAnnotations.java | 80 +- ...argConstructorParameterAnnotationTest.java | 28 +- .../tests/stubparser-records/PairRecord.java | 6 +- .../stubparser-records/RecordStubbed.java | 6 +- .../tests/stubparser-records/RecordUsage.java | 38 +- .../FakeOverrideRSuper.java | 36 +- .../FakeOverrideReturn.java | 104 +- .../TypeParamWithInner.java | 8 +- checker/tests/tainting/AnonymousProblem.java | 2 +- checker/tests/tainting/Buffer.java | 123 +- checker/tests/tainting/CaptureSubtype.java | 15 +- checker/tests/tainting/CaptureSubtype2.java | 33 +- checker/tests/tainting/Casts.java | 110 +- .../tests/tainting/ClassQPTypeVarTest.java | 50 +- checker/tests/tainting/EnumTypeArgs.java | 14 +- checker/tests/tainting/ExtendHasQual.java | 46 +- .../tests/tainting/ExtendsAndAnnotation.java | 18 +- checker/tests/tainting/GenericsEnclosing.java | 24 +- .../tests/tainting/HasQualParamDefaults.java | 299 +- .../tainting/InheritQualifierParameter.java | 14 +- .../tests/tainting/InitializerDataflow.java | 24 +- .../tainting/InnerHasQualifierParameter.java | 20 +- checker/tests/tainting/Issue1111.java | 23 +- checker/tests/tainting/Issue1705.java | 29 +- checker/tests/tainting/Issue1942.java | 23 +- checker/tests/tainting/Issue2107.java | 14 +- checker/tests/tainting/Issue2156.java | 16 +- checker/tests/tainting/Issue2159.java | 40 +- checker/tests/tainting/Issue2243.java | 10 +- checker/tests/tainting/Issue2330.java | 20 +- checker/tests/tainting/Issue3033.java | 22 +- checker/tests/tainting/Issue352.java | 8 +- checker/tests/tainting/Issue3561.java | 18 +- checker/tests/tainting/Issue3562.java | 6 +- checker/tests/tainting/Issue3776.java | 68 +- checker/tests/tainting/Issue4170.java | 103 +- checker/tests/tainting/Issue5435.java | 8 +- checker/tests/tainting/Issue6110.java | 29 +- checker/tests/tainting/Issue6113.java | 8 +- checker/tests/tainting/Issue6116.java | 14 +- .../tainting/LambdaParameterDefaulting.java | 55 +- checker/tests/tainting/NestedAnonymous.java | 14 +- .../tests/tainting/NestedTypeConstructor.java | 6 +- checker/tests/tainting/ObjectCreation.java | 66 +- checker/tests/tainting/PolyClassDecl.java | 43 +- checker/tests/tainting/PolyConstructor.java | 28 +- checker/tests/tainting/PolyReceivers.java | 56 +- checker/tests/tainting/PolyReturn.java | 28 +- checker/tests/tainting/Refine.java | 42 +- checker/tests/tainting/SameTypeBounds.java | 62 +- checker/tests/tainting/SimplePrims.java | 90 +- checker/tests/tainting/SimpleTainting.java | 84 +- checker/tests/tainting/SubClassHasQP.java | 80 +- .../tests/tainting/TaintedIntersections.java | 80 +- .../tainting/TaintingDiamondInference.java | 17 +- checker/tests/tainting/TaintingIssue6025.java | 22 +- checker/tests/tainting/TaintingIssue6060.java | 33 +- .../tests/tainting/TaintingPolyFields.java | 75 +- .../tests/tainting/TestFieldPolymorphism.java | 100 +- .../TestNoQualifierParameterConflicting.java | 10 +- checker/tests/tainting/TypeInvalid.java | 76 +- .../tests/tainting/WildcardArrayBound.java | 55 +- .../tainting/WildcardMethodArgument.java | 13 +- .../java17/TaintingBindingVariable.java | 42 +- .../tainting/withdefault/NoQualifierTest.java | 4 +- .../tainting/withdefault/WithDefault.java | 2 +- checker/tests/units/Addition.java | 774 +- checker/tests/units/BasicUnits.java | 188 +- checker/tests/units/Consistency.java | 46 +- checker/tests/units/Division.java | 246 +- checker/tests/units/Issue4549.java | 10 +- checker/tests/units/Manual.java | 10 +- checker/tests/units/Multiples.java | 366 +- checker/tests/units/PolyUnitTest.java | 22 +- checker/tests/units/SubtractionUnits.java | 830 +- checker/tests/units/TypeVarsArrays.java | 10 +- checker/tests/units/Units.java | 12 +- checker/tests/units/UnqualTest.java | 10 +- .../MethodOverrides.java | 12 +- .../MethodOverrides3.java | 15 +- .../MinLenFromPositive.java | 96 +- .../OverrideIntVal.java | 84 +- .../dataflow/analysis/AbstractAnalysis.java | 954 +- .../dataflow/analysis/AbstractValue.java | 36 +- .../dataflow/analysis/Analysis.java | 272 +- .../dataflow/analysis/AnalysisResult.java | 976 +- .../dataflow/analysis/BackwardAnalysis.java | 21 +- .../analysis/BackwardAnalysisImpl.java | 713 +- .../analysis/BackwardTransferFunction.java | 43 +- .../analysis/ConditionalTransferResult.java | 275 +- .../dataflow/analysis/ForwardAnalysis.java | 25 +- .../analysis/ForwardAnalysisImpl.java | 1005 +- .../analysis/ForwardTransferFunction.java | 21 +- .../analysis/RegularTransferResult.java | 283 +- .../dataflow/analysis/Store.java | 179 +- .../dataflow/analysis/TransferFunction.java | 2 +- .../dataflow/analysis/TransferInput.java | 526 +- .../dataflow/analysis/TransferResult.java | 247 +- .../analysis/UnusedAbstractValue.java | 16 +- .../dataflow/busyexpr/BusyExprStore.java | 245 +- .../dataflow/busyexpr/BusyExprTransfer.java | 125 +- .../dataflow/busyexpr/BusyExprValue.java | 58 +- .../dataflow/cfg/CFGProcessor.java | 308 +- .../dataflow/cfg/ControlFlowGraph.java | 849 +- .../dataflow/cfg/UnderlyingAST.java | 417 +- .../dataflow/cfg/block/Block.java | 101 +- .../dataflow/cfg/block/BlockImpl.java | 99 +- .../dataflow/cfg/block/ConditionalBlock.java | 82 +- .../cfg/block/ConditionalBlockImpl.java | 205 +- .../dataflow/cfg/block/ExceptionBlock.java | 34 +- .../cfg/block/ExceptionBlockImpl.java | 134 +- .../dataflow/cfg/block/RegularBlock.java | 20 +- .../dataflow/cfg/block/RegularBlockImpl.java | 95 +- .../cfg/block/SingleSuccessorBlock.java | 44 +- .../cfg/block/SingleSuccessorBlockImpl.java | 99 +- .../dataflow/cfg/block/SpecialBlock.java | 30 +- .../dataflow/cfg/block/SpecialBlockImpl.java | 69 +- .../dataflow/cfg/builder/CFGBuilder.java | 256 +- .../cfg/builder/CFGTranslationPhaseOne.java | 8164 ++++++----- .../cfg/builder/CFGTranslationPhaseThree.java | 614 +- .../cfg/builder/CFGTranslationPhaseTwo.java | 417 +- .../dataflow/cfg/builder/ConditionalJump.java | 146 +- .../dataflow/cfg/builder/ExtendedNode.java | 158 +- .../dataflow/cfg/builder/Label.java | 42 +- .../dataflow/cfg/builder/LabelCell.java | 72 +- .../dataflow/cfg/builder/MissingEdge.java | 125 +- .../dataflow/cfg/builder/NodeHolder.java | 46 +- .../cfg/builder/NodeWithExceptionsHolder.java | 102 +- .../dataflow/cfg/builder/PhaseOneResult.java | 361 +- .../dataflow/cfg/builder/TreeInfo.java | 48 +- .../dataflow/cfg/builder/TryCatchFrame.java | 181 +- .../dataflow/cfg/builder/TryFinallyFrame.java | 39 +- .../cfg/builder/TryFinallyScopeMap.java | 72 +- .../dataflow/cfg/builder/TryFrame.java | 13 +- .../dataflow/cfg/builder/TryStack.java | 108 +- .../cfg/builder/UnconditionalJump.java | 100 +- .../cfg/node/AbstractNodeVisitor.java | 750 +- .../dataflow/cfg/node/ArrayAccessNode.java | 210 +- .../dataflow/cfg/node/ArrayCreationNode.java | 181 +- .../dataflow/cfg/node/ArrayTypeNode.java | 81 +- .../dataflow/cfg/node/AssertionErrorNode.java | 173 +- .../dataflow/cfg/node/AssignmentNode.java | 237 +- .../cfg/node/BinaryOperationNode.java | 64 +- .../dataflow/cfg/node/BitwiseAndNode.java | 74 +- .../cfg/node/BitwiseComplementNode.java | 70 +- .../dataflow/cfg/node/BitwiseOrNode.java | 74 +- .../dataflow/cfg/node/BitwiseXorNode.java | 74 +- .../dataflow/cfg/node/BooleanLiteralNode.java | 51 +- .../dataflow/cfg/node/CaseNode.java | 223 +- .../dataflow/cfg/node/CatchMarkerNode.java | 97 +- .../cfg/node/CharacterLiteralNode.java | 51 +- .../cfg/node/ClassDeclarationNode.java | 82 +- .../dataflow/cfg/node/ClassNameNode.java | 203 +- .../dataflow/cfg/node/ConditionalAndNode.java | 74 +- .../dataflow/cfg/node/ConditionalNotNode.java | 70 +- .../dataflow/cfg/node/ConditionalOrNode.java | 74 +- .../cfg/node/DeconstructorPatternNode.java | 148 +- .../dataflow/cfg/node/DoubleLiteralNode.java | 51 +- .../dataflow/cfg/node/EqualToNode.java | 74 +- .../dataflow/cfg/node/ExplicitThisNode.java | 37 +- .../cfg/node/ExpressionStatementNode.java | 76 +- .../dataflow/cfg/node/FieldAccessNode.java | 210 +- .../dataflow/cfg/node/FloatLiteralNode.java | 51 +- .../cfg/node/FloatingDivisionNode.java | 74 +- .../cfg/node/FloatingRemainderNode.java | 74 +- .../cfg/node/FunctionalInterfaceNode.java | 102 +- .../dataflow/cfg/node/GreaterThanNode.java | 74 +- .../cfg/node/GreaterThanOrEqualNode.java | 74 +- .../dataflow/cfg/node/ImplicitThisNode.java | 46 +- .../dataflow/cfg/node/InstanceOfNode.java | 309 +- .../cfg/node/IntegerDivisionNode.java | 74 +- .../dataflow/cfg/node/IntegerLiteralNode.java | 51 +- .../cfg/node/IntegerRemainderNode.java | 74 +- .../cfg/node/LambdaResultExpressionNode.java | 134 +- .../dataflow/cfg/node/LeftShiftNode.java | 74 +- .../dataflow/cfg/node/LessThanNode.java | 74 +- .../cfg/node/LessThanOrEqualNode.java | 74 +- .../dataflow/cfg/node/LocalVariableNode.java | 189 +- .../dataflow/cfg/node/LongLiteralNode.java | 51 +- .../dataflow/cfg/node/MarkerNode.java | 92 +- .../dataflow/cfg/node/MethodAccessNode.java | 181 +- .../cfg/node/MethodInvocationNode.java | 256 +- .../cfg/node/NarrowingConversionNode.java | 89 +- .../dataflow/cfg/node/Node.java | 359 +- .../dataflow/cfg/node/NodeVisitor.java | 210 +- .../dataflow/cfg/node/NotEqualNode.java | 74 +- .../dataflow/cfg/node/NullChkNode.java | 102 +- .../dataflow/cfg/node/NullLiteralNode.java | 55 +- .../cfg/node/NumericalAdditionNode.java | 74 +- .../dataflow/cfg/node/NumericalMinusNode.java | 70 +- .../cfg/node/NumericalMultiplicationNode.java | 74 +- .../dataflow/cfg/node/NumericalPlusNode.java | 70 +- .../cfg/node/NumericalSubtractionNode.java | 74 +- .../dataflow/cfg/node/ObjectCreationNode.java | 353 +- .../dataflow/cfg/node/PackageNameNode.java | 167 +- .../cfg/node/ParameterizedTypeNode.java | 74 +- .../dataflow/cfg/node/PrimitiveTypeNode.java | 81 +- .../dataflow/cfg/node/ReturnNode.java | 113 +- .../dataflow/cfg/node/ShortLiteralNode.java | 51 +- .../cfg/node/SignedRightShiftNode.java | 74 +- .../cfg/node/StringConcatenateNode.java | 74 +- .../cfg/node/StringConversionNode.java | 91 +- .../dataflow/cfg/node/StringLiteralNode.java | 67 +- .../dataflow/cfg/node/SuperNode.java | 82 +- .../cfg/node/SwitchExpressionNode.java | 175 +- .../dataflow/cfg/node/SynchronizedNode.java | 107 +- .../cfg/node/TernaryExpressionNode.java | 244 +- .../dataflow/cfg/node/ThisNode.java | 44 +- .../dataflow/cfg/node/ThrowNode.java | 83 +- .../dataflow/cfg/node/TypeCastNode.java | 90 +- .../dataflow/cfg/node/UnaryOperationNode.java | 52 +- .../cfg/node/UnsignedRightShiftNode.java | 74 +- .../dataflow/cfg/node/ValueLiteralNode.java | 92 +- .../cfg/node/VariableDeclarationNode.java | 86 +- .../cfg/node/WideningConversionNode.java | 89 +- .../playground/BusyExpressionPlayground.java | 36 +- .../ConstantPropagationPlayground.java | 36 +- .../playground/LiveVariablePlayground.java | 36 +- .../ReachingDefinitionPlayground.java | 36 +- .../cfg/visualize/AbstractCFGVisualizer.java | 821 +- .../cfg/visualize/CFGVisualizeLauncher.java | 607 +- .../cfg/visualize/CFGVisualizeOptions.java | 464 +- .../dataflow/cfg/visualize/CFGVisualizer.java | 387 +- .../cfg/visualize/DOTCFGVisualizer.java | 646 +- .../cfg/visualize/StringCFGVisualizer.java | 347 +- .../constantpropagation/Constant.java | 213 +- .../ConstantPropagationStore.java | 267 +- .../ConstantPropagationTransfer.java | 119 +- .../dataflow/expression/ArrayAccess.java | 214 +- .../dataflow/expression/ArrayCreation.java | 289 +- .../dataflow/expression/BinaryOperation.java | 437 +- .../dataflow/expression/ClassName.java | 162 +- .../dataflow/expression/FieldAccess.java | 319 +- .../dataflow/expression/FormalParameter.java | 219 +- .../dataflow/expression/JavaExpression.java | 1429 +- .../expression/JavaExpressionConverter.java | 202 +- .../expression/JavaExpressionScanner.java | 187 +- .../expression/JavaExpressionVisitor.java | 212 +- .../dataflow/expression/LocalVariable.java | 231 +- .../dataflow/expression/MethodCall.java | 324 +- .../dataflow/expression/ThisReference.java | 107 +- .../dataflow/expression/UnaryOperation.java | 261 +- .../dataflow/expression/Unknown.java | 198 +- .../dataflow/expression/ValueLiteral.java | 320 +- .../ViewpointAdaptJavaExpression.java | 161 +- .../dataflow/livevariable/LiveVarNode.java | 62 +- .../dataflow/livevariable/LiveVarStore.java | 243 +- .../livevariable/LiveVarTransfer.java | 150 +- .../reachingdef/ReachingDefinitionNode.java | 58 +- .../reachingdef/ReachingDefinitionStore.java | 201 +- .../ReachingDefinitionTransfer.java | 83 +- .../dataflow/util/NodeUtils.java | 150 +- .../dataflow/util/PurityChecker.java | 696 +- .../dataflow/util/PurityUtils.java | 240 +- .../test/java/busyexpr/BusyExpression.java | 31 +- .../java/cfgconstruction/CFGConstruction.java | 14 +- .../ConstantPropagation.java | 30 +- .../src/test/java/livevar/LiveVariable.java | 31 +- .../java/reachingdef/ReachingDefinition.java | 30 +- dataflow/tests/busyexpr/Test.java | 40 +- dataflow/tests/cfgconstruction/Test.java | 32 +- dataflow/tests/constant-propagation/Test.java | 16 +- dataflow/tests/issue3447/Test.java | 12 +- dataflow/tests/live-variable/Test.java | 30 +- dataflow/tests/reachingdef/Test.java | 24 +- docs/examples/BazelExample/BazelExample.java | 23 +- docs/examples/InterningExample.java | 18 +- .../InterningExampleWithWarnings.java | 18 +- docs/examples/LockExample.java | 90 +- .../example/MavenExample.java | 14 +- .../example/MavenExample.java | 25 +- docs/examples/NullnessExample.java | 39 +- .../examples/NullnessExampleWithWarnings.java | 39 +- docs/examples/NullnessReleaseTests.java | 39 +- .../src/main/java/com/example/Demo.java | 8 +- docs/examples/fenum-extension/FenumDemo.java | 113 +- .../fenum-extension/qual/MyFenum.java | 7 +- .../lombok/src/main/java/lib/Foo.java | 13 +- .../lombok/src/main/java/use/User.java | 12 +- docs/examples/subtyping-extension/Demo.java | 49 +- .../subtyping-extension/qual/Encrypted.java | 7 +- .../qual/PossiblyUnencrypted.java | 5 +- .../units-extension/UnitsExtensionDemo.java | 93 +- .../units-extension/qual/Frequency.java | 7 +- .../qual/FrequencyRelations.java | 85 +- docs/examples/units-extension/qual/Hz.java | 9 +- docs/examples/units-extension/qual/kHz.java | 9 +- docs/tutorial/src/NullnessExample.java | 12 +- docs/tutorial/src/RegexExample.java | 20 +- .../src/encrypted/EncryptionDemo.java | 44 +- docs/tutorial/src/myqual/Encrypted.java | 5 +- docs/tutorial/src/myqual/PolyEncrypted.java | 3 +- .../src/myqual/PossiblyUnencrypted.java | 5 +- .../service/PersonalBlogService.java | 302 +- .../struts/action/ReadAction.java | 138 +- .../test/AinferGeneratePerDirectoryTest.java | 51 +- .../test/AinferValidatePerDirectoryTest.java | 288 +- .../CheckerFrameworkPerDirectoryTest.java | 269 +- .../test/CheckerFrameworkPerFileTest.java | 129 +- .../test/CheckerFrameworkRootedTest.java | 44 +- .../CheckerFrameworkWPIPerDirectoryTest.java | 178 +- .../framework/test/CompilationResult.java | 103 +- .../test/ImmutableTestConfiguration.java | 195 +- .../framework/test/PerDirectorySuite.java | 280 +- .../framework/test/PerFileSuite.java | 348 +- .../framework/test/RootedSuite.java | 50 +- .../framework/test/SimpleOptionMap.java | 249 +- .../framework/test/TestConfiguration.java | 141 +- .../test/TestConfigurationBuilder.java | 1186 +- .../framework/test/TestRootDirectory.java | 12 +- .../framework/test/TestUtilities.java | 1003 +- .../framework/test/TypecheckExecutor.java | 231 +- .../framework/test/TypecheckResult.java | 280 +- .../diagnostics/DetailedTestDiagnostic.java | 228 +- .../test/diagnostics/DiagnosticKind.java | 57 +- .../diagnostics/JavaDiagnosticReader.java | 438 +- .../test/diagnostics/TestDiagnostic.java | 429 +- .../test/diagnostics/TestDiagnosticLine.java | 67 +- .../test/diagnostics/TestDiagnosticUtils.java | 805 +- .../checkerframework/taglet/ManualTaglet.java | 178 +- .../checkerframework/taglet/ManualTaglet.java | 192 +- .../junit/AlternateTestRootPerDirTest.java | 95 +- .../AlternateTestRootPerFileWithDirsTest.java | 93 +- ...AlternateTestRootPerFileWithFilesTest.java | 81 +- .../tests-alt/alt-dir-a/Issue6125A.java | 4 +- .../tests-alt/alt-dir-b/Issue6125B.java | 4 +- .../jtreg/DOTCFGVisualizerForVarargsTest.java | 16 +- .../AnnotationFileParserEnumTest.java | 69 +- .../checker/qual/NotInPackageTop.java | 5 +- .../jtreg/variablenamedefault/lib/Test.java | 2 +- .../variablenamedefault/use/UseTest.java | 7 +- .../accumulation/AccumulationAnalysis.java | 53 +- .../AccumulationAnnotatedTypeFactory.java | 1230 +- .../accumulation/AccumulationChecker.java | 89 +- .../accumulation/AccumulationStore.java | 40 +- .../accumulation/AccumulationTransfer.java | 214 +- .../accumulation/AccumulationValue.java | 240 +- .../accumulation/AccumulationVisitor.java | 42 +- .../AliasingAnnotatedTypeFactory.java | 185 +- .../common/aliasing/AliasingTransfer.java | 261 +- .../common/aliasing/AliasingVisitor.java | 557 +- .../basetype/BaseAnnotatedTypeFactory.java | 28 +- .../common/basetype/BaseTypeChecker.java | 1654 ++- .../common/basetype/BaseTypeValidator.java | 1365 +- .../common/basetype/BaseTypeVisitor.java | 9335 ++++++------- .../common/basetype/TypeValidator.java | 19 +- ...InitializedFieldsAnnotatedTypeFactory.java | 413 +- .../InitializedFieldsTransfer.java | 38 +- .../ClassValAnnotatedTypeFactory.java | 627 +- .../common/reflection/ClassValChecker.java | 45 +- .../common/reflection/ClassValVisitor.java | 90 +- .../reflection/DefaultReflectionResolver.java | 1166 +- .../MethodValAnnotatedTypeFactory.java | 852 +- .../common/reflection/MethodValChecker.java | 35 +- .../common/reflection/MethodValVisitor.java | 119 +- .../common/reflection/ReflectionResolver.java | 56 +- .../returnsreceiver/FluentAPIGenerator.java | 216 +- .../ReturnsReceiverAnnotatedTypeFactory.java | 124 +- .../ReturnsReceiverVisitor.java | 80 +- .../SubtypingAnnotatedTypeFactory.java | 191 +- .../SubtypingAnnotationClassLoader.java | 24 +- .../common/subtyping/SubtypingChecker.java | 74 +- .../common/util/TypeVisualizer.java | 1027 +- .../util/count/AnnotationStatistics.java | 435 +- .../common/util/count/JavaCodeStatistics.java | 277 +- .../common/util/debug/DoNothingProcessor.java | 21 +- .../common/util/debug/SignaturePrinter.java | 519 +- .../common/util/debug/TreeDebug.java | 140 +- .../common/util/debug/TreePrinter.java | 41 +- .../util/debug/TypeOutputtingChecker.java | 438 +- .../common/util/report/ReportChecker.java | 7 +- .../common/util/report/ReportVisitor.java | 483 +- .../common/value/JavaExpressionOptimizer.java | 108 +- .../common/value/RangeOrListOfValues.java | 210 +- .../common/value/ReflectiveEvaluator.java | 675 +- .../value/ValueAnnotatedTypeFactory.java | 3291 +++-- .../common/value/ValueChecker.java | 75 +- .../common/value/ValueCheckerUtils.java | 741 +- .../common/value/ValueMethodIdentifier.java | 276 +- .../common/value/ValueQualifierHierarchy.java | 1093 +- .../common/value/ValueTransfer.java | 3205 +++-- .../common/value/ValueTreeAnnotator.java | 1210 +- .../common/value/ValueTypeAnnotator.java | 315 +- .../common/value/ValueVisitor.java | 964 +- .../common/value/util/ByteMath.java | 686 +- .../common/value/util/DoubleMath.java | 542 +- .../common/value/util/FloatMath.java | 542 +- .../common/value/util/IntegerMath.java | 686 +- .../common/value/util/LongMath.java | 686 +- .../common/value/util/NumberMath.java | 118 +- .../common/value/util/NumberUtils.java | 264 +- .../common/value/util/Range.java | 2307 ++- .../common/value/util/ShortMath.java | 686 +- .../AnnotationConverter.java | 347 +- .../SceneToStubWriter.java | 1616 ++- .../WholeProgramInference.java | 458 +- .../WholeProgramInferenceImplementation.java | 1996 ++- ...holeProgramInferenceJavaParserStorage.java | 3455 +++-- .../WholeProgramInferenceScenesStorage.java | 1923 ++- .../WholeProgramInferenceStorage.java | 448 +- .../scenelib/ASceneWrapper.java | 399 +- .../ajava/AnnotationEqualityVisitor.java | 131 +- .../framework/ajava/AnnotationFileStore.java | 102 +- ...ationMirrorToAnnotationExprConversion.java | 396 +- .../ajava/AnnotationTransferVisitor.java | 103 +- .../framework/ajava/DefaultJointVisitor.java | 364 +- .../ajava/DoubleJavaParserVisitor.java | 1598 ++- .../framework/ajava/ExpectedTreesVisitor.java | 626 +- .../ajava/InsertAjavaAnnotations.java | 1034 +- .../ajava/JointJavacJavaParserVisitor.java | 4408 +++--- .../ajava/JointVisitorWithDefaultAction.java | 886 +- .../ajava/TreeScannerWithDefaults.java | 843 +- .../framework/ajava/TypeAnnotationMover.java | 355 +- .../framework/flow/CFAbstractAnalysis.java | 417 +- .../framework/flow/CFAbstractStore.java | 2459 ++-- .../framework/flow/CFAbstractTransfer.java | 2490 ++-- .../framework/flow/CFAbstractValue.java | 1527 +- .../framework/flow/CFAnalysis.java | 49 +- .../framework/flow/CFCFGBuilder.java | 364 +- .../framework/flow/CFStore.java | 22 +- .../framework/flow/CFTransfer.java | 6 +- .../framework/flow/CFTreeBuilder.java | 336 +- .../framework/flow/CFValue.java | 29 +- .../framework/source/AggregateChecker.java | 300 +- .../framework/source/DiagMessage.java | 221 +- .../framework/source/SourceChecker.java | 5972 ++++---- .../framework/source/SourceVisitor.java | 205 +- .../source/SupportedLintOptions.java | 2 +- .../framework/source/SupportedOptions.java | 2 +- .../source/SuppressWarningsPrefix.java | 16 +- .../framework/stub/AddAnnotatedFor.java | 424 +- .../stub/AnnotationFileElementTypes.java | 1906 ++- .../framework/stub/AnnotationFileParser.java | 6392 +++++---- .../stub/AnnotationFileResource.java | 16 +- .../framework/stub/AnnotationFileUtil.java | 837 +- .../stub/FileAnnotationFileResource.java | 38 +- .../stub/JarEntryAnnotationFileResource.java | 45 +- .../framework/stub/JavaStubifier.java | 395 +- .../stub/RemoveAnnotationsForInference.java | 996 +- .../framework/stub/StubGenerator.java | 864 +- .../framework/stub/ToIndexFileConverter.java | 1195 +- .../type/AbstractViewpointAdapter.java | 915 +- .../framework/type/AnnotatedTypeCopier.java | 614 +- .../AnnotatedTypeCopierWithReplacement.java | 131 +- .../framework/type/AnnotatedTypeFactory.java | 11568 ++++++++-------- .../type/AnnotatedTypeFormatter.java | 40 +- .../framework/type/AnnotatedTypeMirror.java | 4552 +++--- .../type/AnnotatedTypeParameterBounds.java | 91 +- .../framework/type/AnnotatedTypeReplacer.java | 192 +- .../framework/type/AnnotationClassLoader.java | 1499 +- .../framework/type/AsSuperVisitor.java | 1641 ++- .../framework/type/BoundsInitializer.java | 2405 ++-- .../type/DeclarationsIntoElements.java | 88 +- .../type/DefaultAnnotatedTypeFormatter.java | 833 +- .../type/DefaultInferredTypesApplier.java | 253 +- .../framework/type/DefaultTypeHierarchy.java | 2498 ++-- .../type/ElementAnnotationApplier.java | 352 +- .../type/ElementQualifierHierarchy.java | 423 +- .../framework/type/EqualityAtmComparer.java | 99 +- .../type/GenericAnnotatedTypeFactory.java | 6093 ++++---- .../framework/type/HashcodeAtmVisitor.java | 39 +- .../MostlyNoElementQualifierHierarchy.java | 226 +- .../type/NoElementQualifierHierarchy.java | 393 +- .../framework/type/QualifierHierarchy.java | 1262 +- .../framework/type/QualifierUpperBounds.java | 272 +- .../type/StructuralEqualityComparer.java | 684 +- .../type/StructuralEqualityVisitHistory.java | 145 +- .../SubtypeIsSubsetQualifierHierarchy.java | 200 +- .../SubtypeIsSupersetQualifierHierarchy.java | 200 +- .../framework/type/SubtypeVisitHistory.java | 136 +- .../framework/type/SupertypeFinder.java | 780 +- .../framework/type/SyntheticArrays.java | 66 +- .../framework/type/TypeFromClassVisitor.java | 18 +- .../type/TypeFromExpressionVisitor.java | 689 +- .../framework/type/TypeFromMemberVisitor.java | 307 +- .../framework/type/TypeFromTree.java | 219 +- .../framework/type/TypeFromTreeVisitor.java | 23 +- .../type/TypeFromTypeTreeVisitor.java | 603 +- .../framework/type/TypeHierarchy.java | 296 +- .../type/TypeVariableSubstitutor.java | 283 +- .../framework/type/TypesIntoElements.java | 868 +- .../framework/type/ViewpointAdapter.java | 107 +- .../poly/AbstractQualifierPolymorphism.java | 1019 +- .../poly/DefaultQualifierPolymorphism.java | 96 +- .../type/poly/QualifierPolymorphism.java | 70 +- .../treeannotator/DebugListTreeAnnotator.java | 80 +- .../type/treeannotator/ListTreeAnnotator.java | 74 +- .../treeannotator/LiteralTreeAnnotator.java | 446 +- .../PropagationTreeAnnotator.java | 637 +- .../type/treeannotator/TreeAnnotator.java | 109 +- .../DefaultForTypeAnnotator.java | 587 +- .../DefaultQualifierForUseTypeAnnotator.java | 310 +- .../IrrelevantTypeAnnotator.java | 122 +- .../type/typeannotator/ListTypeAnnotator.java | 83 +- .../PropagationTypeAnnotator.java | 365 +- .../type/typeannotator/TypeAnnotator.java | 49 +- .../type/visitor/AbstractAtmComboVisitor.java | 1203 +- .../type/visitor/AnnotatedTypeCombiner.java | 113 +- .../type/visitor/AnnotatedTypeScanner.java | 475 +- .../type/visitor/AnnotatedTypeVisitor.java | 206 +- .../type/visitor/AtmComboVisitor.java | 478 +- .../visitor/DoubleAnnotatedTypeScanner.java | 342 +- .../visitor/EquivalentAtmComboScanner.java | 368 +- .../visitor/SimpleAnnotatedTypeScanner.java | 359 +- .../visitor/SimpleAnnotatedTypeVisitor.java | 182 +- .../framework/util/AnnotatedTypes.java | 3021 ++-- .../framework/util/AnnotationFormatter.java | 42 +- .../framework/util/AtmCombo.java | 1286 +- .../framework/util/AtmLubVisitor.java | 781 +- .../framework/util/CheckerMain.java | 1843 ++- .../framework/util/Contract.java | 499 +- .../framework/util/ContractsFromMethod.java | 70 +- .../util/DefaultAnnotationFormatter.java | 106 +- .../util/DefaultContractsFromMethod.java | 530 +- .../util/DefaultQualifierKindHierarchy.java | 1501 +- .../framework/util/ExecUtil.java | 126 +- .../framework/util/FieldInvariants.java | 238 +- .../framework/util/Heuristics.java | 380 +- .../util/JavaExpressionParseUtil.java | 2147 ++- .../framework/util/JavaParserUtil.java | 705 +- .../framework/util/NoContractsFromMethod.java | 95 +- .../framework/util/OptionConfiguration.java | 211 +- .../util/PurityAnnotatedTypeFactory.java | 23 +- .../framework/util/PurityChecker.java | 6 +- .../framework/util/QualifierKind.java | 195 +- .../util/QualifierKindHierarchy.java | 130 +- .../util/StringToJavaExpression.java | 595 +- .../framework/util/TreePathCacher.java | 190 +- .../framework/util/TypeArgumentMapper.java | 519 +- .../util/VoidVisitorWithDefaultAction.java | 1200 +- .../framework/util/defaults/Default.java | 120 +- .../framework/util/defaults/DefaultSet.java | 21 +- .../util/defaults/QualifierDefaults.java | 2480 ++-- .../dependenttypes/DependentTypesError.java | 220 +- .../dependenttypes/DependentTypesHelper.java | 2338 ++-- .../DependentTypesTreeAnnotator.java | 100 +- .../util/element/ClassTypeParamApplier.java | 212 +- .../util/element/ElementAnnotationUtil.java | 1096 +- .../IndexedElementAnnotationApplier.java | 91 +- .../framework/util/element/MethodApplier.java | 449 +- .../util/element/MethodTypeParamApplier.java | 266 +- .../framework/util/element/ParamApplier.java | 536 +- .../util/element/SuperTypeApplier.java | 265 +- .../TargetedElementAnnotationApplier.java | 357 +- .../util/element/TypeDeclarationApplier.java | 267 +- .../TypeParamElementAnnotationApplier.java | 407 +- .../util/element/TypeVarUseApplier.java | 592 +- .../util/element/VariableApplier.java | 232 +- .../DefaultTypeArgumentInference.java | 1510 +- .../framework/util/typeinference/GlbUtil.java | 339 +- .../typeinference/TypeArgInferenceUtil.java | 1306 +- .../typeinference/TypeArgumentInference.java | 45 +- .../util/typeinference/constraint/A2F.java | 33 +- .../typeinference/constraint/A2FReducer.java | 94 +- .../constraint/AFConstraint.java | 159 +- .../typeinference/constraint/AFReducer.java | 24 +- .../constraint/AFReducingVisitor.java | 1061 +- .../util/typeinference/constraint/F2A.java | 33 +- .../typeinference/constraint/F2AReducer.java | 112 +- .../util/typeinference/constraint/FIsA.java | 34 +- .../typeinference/constraint/FIsAReducer.java | 421 +- .../util/typeinference/constraint/TIsU.java | 23 +- .../util/typeinference/constraint/TSubU.java | 24 +- .../typeinference/constraint/TSuperU.java | 24 +- .../constraint/TUConstraint.java | 72 +- .../typeinference/solver/ConstraintMap.java | 309 +- .../solver/ConstraintMapBuilder.java | 375 +- .../solver/EqualitiesSolver.java | 848 +- .../typeinference/solver/InferenceResult.java | 278 +- .../typeinference/solver/InferredValue.java | 54 +- .../typeinference/solver/SubtypesSolver.java | 301 +- .../solver/SupertypesSolver.java | 723 +- .../solver/TargetConstraints.java | 200 +- .../AbstractTypeInformationPresenter.java | 566 +- .../LspTypeInformationPresenter.java | 370 +- .../visualize/TypeInformationPresenter.java | 14 +- .../util/visualize/TypeOccurrenceKind.java | 34 +- .../util/visualize/TypeOccurrenceRange.java | 76 +- .../AccumulationNoReturnsReceiverTest.java | 35 +- .../test/junit/AccumulationTest.java | 39 +- .../framework/test/junit/AggregateTest.java | 25 +- .../framework/test/junit/AliasingTest.java | 25 +- .../test/junit/AnnotatedForTest.java | 45 +- .../test/junit/AnnotationBuilderTest.java | 607 +- .../framework/test/junit/ClassValTest.java | 25 +- .../test/junit/CompoundCheckerTest.java | 25 +- .../test/junit/DefaultingLowerBoundTest.java | 25 +- .../test/junit/DefaultingUpperBoundTest.java | 25 +- .../test/junit/FlowExpressionCheckerTest.java | 25 +- .../framework/test/junit/FlowTest.java | 25 +- .../test/junit/FrameworkJavacErrorsTest.java | 19 +- .../framework/test/junit/FrameworkTest.java | 19 +- .../framework/test/junit/H1H2CheckerTest.java | 35 +- .../test/junit/InitializedFieldsTest.java | 29 +- .../junit/InitializedFieldsValueTest.java | 43 +- .../framework/test/junit/LubGlbTest.java | 25 +- .../framework/test/junit/MethodValTest.java | 28 +- .../test/junit/NonTopDefaultTest.java | 25 +- .../test/junit/PuritySuggestionsTest.java | 35 +- .../framework/test/junit/RangeTest.java | 1349 +- .../framework/test/junit/ReflectionTest.java | 59 +- .../test/junit/ReportModifiersTest.java | 33 +- .../framework/test/junit/ReportTest.java | 33 +- .../test/junit/ReportTreeKindsTest.java | 33 +- .../junit/ReturnsReceiverAutoValueTest.java | 44 +- .../test/junit/ReturnsReceiverLombokTest.java | 43 +- .../test/junit/ReturnsReceiverTest.java | 33 +- .../test/junit/SubtypingEncryptedTest.java | 33 +- .../SubtypingStringPatternsFullTest.java | 33 +- .../test/junit/SupportedQualsTest.java | 25 +- .../framework/test/junit/TreeParserTest.java | 159 +- .../test/junit/TypeDeclBoundsTest.java | 19 +- .../test/junit/TypeDeclDefaultTest.java | 33 +- .../junit/ValueIgnoreRangeOverflowTest.java | 39 +- .../ValueNonNullStringsConcatenationTest.java | 39 +- .../framework/test/junit/ValueTest.java | 40 +- .../junit/ValueUncheckedDefaultsTest.java | 43 +- .../test/junit/VariableNameDefaultTest.java | 25 +- .../test/junit/ViewpointTestCheckerTest.java | 25 +- .../aggregate/AggregateOfCompoundChecker.java | 13 +- .../aggregate/TestAggregateChecker.java | 13 +- .../compound/AnotherCompoundChecker.java | 47 +- ...erCompoundCheckerAnnotatedTypeFactory.java | 79 +- .../testchecker/compound/CompoundChecker.java | 39 +- .../CompoundCheckerAnnotatedTypeFactory.java | 75 +- .../testchecker/compound/qual/ACCBottom.java | 5 +- .../testchecker/compound/qual/ACCTop.java | 5 +- .../testchecker/compound/qual/CCBottom.java | 5 +- .../testchecker/compound/qual/CCTop.java | 5 +- ...aultingLowerBoundAnnotatedTypeFactory.java | 27 +- ...aultingUpperBoundAnnotatedTypeFactory.java | 31 +- .../defaulting/LowerBoundQual.java | 55 +- .../defaulting/UpperBoundQual.java | 57 +- .../FlowExpressionAnnotatedTypeFactory.java | 186 +- .../flowexpression/qual/FEBottom.java | 3 +- .../flowexpression/qual/FETop.java | 5 +- .../flowexpression/qual/FlowExp.java | 9 +- .../h1h2checker/H1H2AnnotatedTypeFactory.java | 77 +- .../testchecker/h1h2checker/H1H2Visitor.java | 58 +- .../testchecker/h1h2checker/quals/H1Bot.java | 7 +- .../h1h2checker/quals/H1Invalid.java | 3 +- .../testchecker/h1h2checker/quals/H1Poly.java | 3 +- .../testchecker/h1h2checker/quals/H1S1.java | 3 +- .../testchecker/h1h2checker/quals/H1S2.java | 3 +- .../testchecker/h1h2checker/quals/H1Top.java | 5 +- .../testchecker/h1h2checker/quals/H2Bot.java | 7 +- .../h1h2checker/quals/H2OnlyOnLB.java | 7 +- .../testchecker/h1h2checker/quals/H2Poly.java | 3 +- .../testchecker/h1h2checker/quals/H2S1.java | 17 +- .../testchecker/h1h2checker/quals/H2S2.java | 3 +- .../testchecker/h1h2checker/quals/H2Top.java | 5 +- .../testchecker/lib/Issue3105Fields.java | 10 +- .../testchecker/lib/UncheckedByteCode.java | 54 +- .../testchecker/lib/VarArgMethods.java | 42 +- .../lubglb/LubGlbAnnotatedTypeFactory.java | 41 +- .../testchecker/lubglb/LubGlbChecker.java | 117 +- .../testchecker/lubglb/quals/LubglbA.java | 5 +- .../testchecker/lubglb/quals/LubglbB.java | 3 +- .../testchecker/lubglb/quals/LubglbC.java | 3 +- .../testchecker/lubglb/quals/LubglbD.java | 3 +- .../testchecker/lubglb/quals/LubglbE.java | 3 +- .../testchecker/lubglb/quals/LubglbF.java | 7 +- .../testchecker/lubglb/quals/PolyLubglb.java | 3 +- .../NTDAnnotatedTypeFactory.java | 25 +- .../testchecker/nontopdefault/NTDVisitor.java | 35 +- .../nontopdefault/qual/NTDBottom.java | 9 +- .../nontopdefault/qual/NTDMiddle.java | 5 +- .../nontopdefault/qual/NTDSide.java | 3 +- .../nontopdefault/qual/NTDTop.java | 13 +- .../ReflectionTestAnnotatedTypeFactory.java | 27 +- .../reflection/ReflectionTestChecker.java | 8 +- .../reflection/ReflectionTestVisitor.java | 16 +- .../reflection/qual/PolyTestReflect.java | 3 +- .../reflection/qual/TestReflectBottom.java | 5 +- .../reflection/qual/TestReflectSibling1.java | 3 +- .../reflection/qual/TestReflectSibling2.java | 3 +- .../reflection/qual/TestReflectTop.java | 5 +- .../supportedquals/SupportedQualsChecker.java | 47 +- .../supportedquals/qual/BottomQualifier.java | 5 +- .../supportedquals/qual/Qualifier.java | 5 +- .../TestAccumulationAnnotatedTypeFactory.java | 114 +- ...NoReturnsReceiverAnnotatedTypeFactory.java | 18 +- ...tAccumulationNoReturnsReceiverChecker.java | 21 +- ...AccumulationNoReturnsReceiverTransfer.java | 16 +- .../TestAccumulationTransfer.java | 38 +- .../qual/PolyTestAccumulation.java | 3 +- .../qual/TestAccumulation.java | 17 +- .../qual/TestAccumulationBottom.java | 7 +- .../qual/TestAccumulationPredicate.java | 17 +- .../TypeDeclBoundsAnnotatedTypeFactory.java | 25 +- .../typedeclbounds/TypeDeclBoundsVisitor.java | 14 +- .../typedeclbounds/quals/Bottom.java | 7 +- .../testchecker/typedeclbounds/quals/S1.java | 7 +- .../testchecker/typedeclbounds/quals/S2.java | 9 +- .../testchecker/typedeclbounds/quals/Top.java | 5 +- .../TypeDeclDefaultAnnotatedTypeFactory.java | 35 +- .../quals/PolyTypeDeclDefault.java | 3 +- .../quals/TypeDeclDefaultBottom.java | 9 +- .../quals/TypeDeclDefaultMiddle.java | 3 +- .../quals/TypeDeclDefaultTop.java | 7 +- .../testchecker/util/AnnoWithStringArg.java | 7 +- .../framework/testchecker/util/Critical.java | 5 +- .../framework/testchecker/util/Encrypted.java | 5 +- .../testchecker/util/EnsuresOdd.java | 7 +- .../testchecker/util/EnsuresOddIf.java | 9 +- .../framework/testchecker/util/Even.java | 5 +- .../testchecker/util/EvenOddChecker.java | 111 +- .../testchecker/util/FactoryTestChecker.java | 379 +- .../util/FlowTestAnnotatedTypeFactory.java | 198 +- .../testchecker/util/MonotonicOdd.java | 7 +- .../framework/testchecker/util/Odd.java | 3 +- .../framework/testchecker/util/PatternA.java | 5 +- .../framework/testchecker/util/PatternAB.java | 5 +- .../framework/testchecker/util/PatternAC.java | 5 +- .../framework/testchecker/util/PatternB.java | 5 +- .../framework/testchecker/util/PatternBC.java | 5 +- .../testchecker/util/PatternBottomFull.java | 5 +- .../framework/testchecker/util/PatternC.java | 5 +- .../testchecker/util/PatternUnknown.java | 5 +- .../testchecker/util/PolyEncrypted.java | 3 +- .../testchecker/util/RequiresOdd.java | 5 +- .../framework/testchecker/util/SubQual.java | 3 +- .../framework/testchecker/util/SuperQual.java | 5 +- .../testchecker/util/ValueTypeAnno.java | 7 +- ...riableNameDefaultAnnotatedTypeFactory.java | 35 +- .../quals/PolyVariableNameDefault.java | 3 +- .../quals/VariableNameDefaultBottom.java | 5 +- .../quals/VariableNameDefaultMiddle.java | 5 +- .../quals/VariableNameDefaultTop.java | 5 +- .../ViewpointTestAnnotatedTypeFactory.java | 84 +- .../ViewpointTestQualifierHierarchy.java | 51 +- .../ViewpointTestViewpointAdapter.java | 71 +- .../viewpointtest/ViewpointTestVisitor.java | 31 +- .../src/test/java/viewpointtest/quals/A.java | 3 +- .../src/test/java/viewpointtest/quals/B.java | 3 +- .../test/java/viewpointtest/quals/Bottom.java | 7 +- .../test/java/viewpointtest/quals/Lost.java | 3 +- .../test/java/viewpointtest/quals/PolyVP.java | 3 +- .../quals/ReceiverDependentQual.java | 3 +- .../test/java/viewpointtest/quals/Top.java | 5 +- .../android/support/annotation/IntRange.java | 4 +- framework/tests/Issue6060.java | 12 +- .../tests/accumulation-norr/SimpleFluent.java | 223 +- framework/tests/accumulation/Generics.java | 63 +- framework/tests/accumulation/Not.java | 112 +- .../ObjectConstructionIssue20.java | 44 +- framework/tests/accumulation/Parens.java | 6 +- framework/tests/accumulation/Predicates.java | 1116 +- .../tests/accumulation/SimpleFluent.java | 197 +- .../tests/accumulation/SimpleInference.java | 66 +- .../accumulation/SimpleInferenceMerge.java | 52 +- .../tests/accumulation/SimplePolymorphic.java | 22 +- .../tests/accumulation/SmallPredicate.java | 18 +- framework/tests/accumulation/Subtyping.java | 122 +- .../accumulation/UnparsablePredicate.java | 62 +- .../tests/accumulation/UseSimpleFluent.java | 8 +- framework/tests/accumulation/Xor.java | 96 +- .../tests/aggregate/AggregateBasicTest.java | 18 +- framework/tests/aggregate/JavaErrorTest.java | 20 +- framework/tests/aggregate/MultiError.java | 24 +- .../aliasing/AliasingConstructorTest.java | 30 +- .../tests/aliasing/ArrayInitializerTest.java | 20 +- framework/tests/aliasing/CatchTest.java | 20 +- framework/tests/aliasing/EnumTest.java | 4 +- .../aliasing/ExplicitAnnotationTest.java | 16 +- .../tests/aliasing/ForbiddenUniqueTest.java | 27 +- .../tests/aliasing/ReceiverParameterTest.java | 110 +- framework/tests/aliasing/SuperTest.java | 24 +- framework/tests/aliasing/ThrowTest.java | 16 +- framework/tests/aliasing/TypeRefinement.java | 106 +- framework/tests/aliasing/UniqueAnnoTest.java | 70 +- .../tests/aliasing/UniqueConstructorTest.java | 24 +- framework/tests/all-systems/Annotations.java | 18 +- .../all-systems/AnonymousAndInnerClass.java | 92 +- .../tests/all-systems/AnonymousClasses.java | 38 +- .../all-systems/AnonymousFieldAccess.java | 16 +- .../tests/all-systems/ArrayComparator.java | 8 +- framework/tests/all-systems/Arrays.java | 38 +- .../tests/all-systems/AsSuperCrashes.java | 139 +- .../all-systems/AssertWithSideEffect.java | 8 +- .../tests/all-systems/AssignmentContext.java | 36 +- .../tests/all-systems/BigBinaryTrees.java | 523 +- framework/tests/all-systems/BigString.java | 504 +- .../all-systems/CaptureMethodTypeArgs.java | 16 +- framework/tests/all-systems/Catch.java | 20 +- .../all-systems/ComplexPatternEscape.java | 10 +- .../all-systems/CompoundAssignments.java | 32 +- .../all-systems/ConditionalExpressions.java | 69 +- framework/tests/all-systems/Crash.java | 40 +- framework/tests/all-systems/CrazyEnum.java | 26 +- framework/tests/all-systems/DeepEquals.java | 18 +- .../tests/all-systems/EisopIssue612.java | 14 +- .../tests/all-systems/EisopIssue612Min.java | 12 +- framework/tests/all-systems/EnumSwitch.java | 28 +- framework/tests/all-systems/Enums.java | 58 +- .../tests/all-systems/EqualityTests.java | 38 +- .../tests/all-systems/FieldAccessTest.java | 54 +- .../tests/all-systems/FieldWithInit.java | 8 +- framework/tests/all-systems/ForEach.java | 60 +- .../tests/all-systems/GenericCrazyBounds.java | 90 +- .../all-systems/GenericExtendsTypeVars.java | 30 +- framework/tests/all-systems/GenericNull.java | 24 +- .../tests/all-systems/GenericTest11full.java | 31 +- .../tests/all-systems/GenericTest12.java | 10 +- .../tests/all-systems/GenericTest12b.java | 30 +- .../tests/all-systems/GenericTest13.java | 18 +- .../tests/all-systems/GenericsBounds.java | 8 +- .../tests/all-systems/GenericsBounds2.java | 6 +- .../tests/all-systems/GenericsCasts.java | 76 +- .../tests/all-systems/GenericsEnclosing.java | 22 +- framework/tests/all-systems/GetClassTest.java | 42 +- .../all-systems/InferAndIntersection.java | 8 +- .../tests/all-systems/InferAndWildcards.java | 12 +- .../tests/all-systems/InferNullType.java | 52 +- .../tests/all-systems/InferTypeArgs.java | 28 +- .../tests/all-systems/InferTypeArgs2.java | 20 +- .../tests/all-systems/InferTypeArgs3.java | 24 +- .../InferTypeArgsConditionalExpression.java | 10 +- .../all-systems/InitializationVisitor.java | 26 +- framework/tests/all-systems/InstanceOf.java | 34 +- .../tests/all-systems/IntersectionTypes.java | 10 +- framework/tests/all-systems/IsSubarrayEq.java | 19 +- framework/tests/all-systems/Issue1003.java | 24 +- framework/tests/all-systems/Issue1006.java | 16 +- framework/tests/all-systems/Issue1039.java | 6 +- framework/tests/all-systems/Issue1043.java | 16 +- framework/tests/all-systems/Issue1049.java | 14 +- framework/tests/all-systems/Issue1102.java | 15 +- framework/tests/all-systems/Issue1111.java | 10 +- .../tests/all-systems/Issue116Graph.java | 2 +- framework/tests/all-systems/Issue1274.java | 22 +- framework/tests/all-systems/Issue1431.java | 14 +- framework/tests/all-systems/Issue1442.java | 44 +- framework/tests/all-systems/Issue1506.java | 14 +- framework/tests/all-systems/Issue1520.java | 44 +- framework/tests/all-systems/Issue1526.java | 20 +- framework/tests/all-systems/Issue1543.java | 14 +- framework/tests/all-systems/Issue1546.java | 18 +- framework/tests/all-systems/Issue1586.java | 26 +- framework/tests/all-systems/Issue1587.java | 32 +- framework/tests/all-systems/Issue1587b.java | 38 +- framework/tests/all-systems/Issue1690.java | 10 +- framework/tests/all-systems/Issue1696.java | 4 +- framework/tests/all-systems/Issue1697.java | 38 +- framework/tests/all-systems/Issue1698.java | 12 +- framework/tests/all-systems/Issue1708.java | 42 +- framework/tests/all-systems/Issue1709.java | 6 +- framework/tests/all-systems/Issue1738.java | 38 +- framework/tests/all-systems/Issue1749.java | 14 +- framework/tests/all-systems/Issue1809.java | 30 +- framework/tests/all-systems/Issue1865.java | 22 +- framework/tests/all-systems/Issue1867.java | 18 +- framework/tests/all-systems/Issue1920.java | 32 +- framework/tests/all-systems/Issue1948.java | 265 +- framework/tests/all-systems/Issue1991.java | 12 +- .../tests/all-systems/Issue1991Full.java | 22 +- framework/tests/all-systems/Issue1992.java | 28 +- framework/tests/all-systems/Issue2048.java | 12 +- framework/tests/all-systems/Issue2082.java | 2 +- framework/tests/all-systems/Issue2088.java | 28 +- framework/tests/all-systems/Issue2190.java | 28 +- framework/tests/all-systems/Issue2195.java | 18 +- framework/tests/all-systems/Issue2196.java | 18 +- framework/tests/all-systems/Issue2198.java | 18 +- framework/tests/all-systems/Issue2199.java | 16 +- framework/tests/all-systems/Issue2234.java | 8 +- framework/tests/all-systems/Issue2302.java | 32 +- framework/tests/all-systems/Issue2370.java | 34 +- framework/tests/all-systems/Issue2371.java | 8 +- framework/tests/all-systems/Issue2446.java | 14 +- framework/tests/all-systems/Issue2480.java | 8 +- framework/tests/all-systems/Issue263.java | 32 +- framework/tests/all-systems/Issue2678.java | 8 +- framework/tests/all-systems/Issue2717.java | 30 +- framework/tests/all-systems/Issue2739.java | 12 +- framework/tests/all-systems/Issue2779.java | 34 +- framework/tests/all-systems/Issue2781.java | 24 +- framework/tests/all-systems/Issue301.java | 2916 ++-- framework/tests/all-systems/Issue3021.java | 8 +- framework/tests/all-systems/Issue3055.java | 18 +- framework/tests/all-systems/Issue3120.java | 28 +- framework/tests/all-systems/Issue3128.java | 10 +- framework/tests/all-systems/Issue3232.java | 10 +- framework/tests/all-systems/Issue3277.java | 18 +- framework/tests/all-systems/Issue3295.java | 18 +- framework/tests/all-systems/Issue3302.java | 8 +- framework/tests/all-systems/Issue3377.java | 16 +- framework/tests/all-systems/Issue3569.java | 10 +- framework/tests/all-systems/Issue3570.java | 85 +- framework/tests/all-systems/Issue3598.java | 30 +- framework/tests/all-systems/Issue3785.java | 38 +- framework/tests/all-systems/Issue3791.java | 24 +- framework/tests/all-systems/Issue3826.java | 36 +- framework/tests/all-systems/Issue392.java | 6 +- framework/tests/all-systems/Issue3929.java | 12 +- framework/tests/all-systems/Issue393.java | 8 +- framework/tests/all-systems/Issue395.java | 6 +- framework/tests/all-systems/Issue396.java | 10 +- framework/tests/all-systems/Issue3994.java | 6 +- framework/tests/all-systems/Issue4083.java | 20 +- framework/tests/all-systems/Issue4115.java | 12 +- framework/tests/all-systems/Issue4170.java | 33 +- framework/tests/all-systems/Issue437.java | 30 +- framework/tests/all-systems/Issue438.java | 18 +- framework/tests/all-systems/Issue4384.java | 24 +- framework/tests/all-systems/Issue457.java | 16 +- framework/tests/all-systems/Issue478.java | 6 +- framework/tests/all-systems/Issue4829.java | 19 +- framework/tests/all-systems/Issue4849.java | 14 +- framework/tests/all-systems/Issue4852.java | 12 +- framework/tests/all-systems/Issue4853.java | 20 +- framework/tests/all-systems/Issue4876.java | 16 +- framework/tests/all-systems/Issue4877.java | 14 +- framework/tests/all-systems/Issue4878.java | 8 +- .../tests/all-systems/Issue4878Orig.java | 23 +- framework/tests/all-systems/Issue4879.java | 20 +- framework/tests/all-systems/Issue4890.java | 23 +- .../all-systems/Issue4890Interfaces.java | 42 +- framework/tests/all-systems/Issue4924.java | 18 +- framework/tests/all-systems/Issue4996.java | 24 +- framework/tests/all-systems/Issue5008.java | 12 +- framework/tests/all-systems/Issue5042.java | 101 +- framework/tests/all-systems/Issue5436.java | 28 +- framework/tests/all-systems/Issue577.java | 86 +- framework/tests/all-systems/Issue6019.java | 6 +- framework/tests/all-systems/Issue6025.java | 18 +- framework/tests/all-systems/Issue6076.java | 110 +- framework/tests/all-systems/Issue6078.java | 16 +- framework/tests/all-systems/Issue6098.java | 12 +- framework/tests/all-systems/Issue6104.java | 12 +- framework/tests/all-systems/Issue6259.java | 50 +- framework/tests/all-systems/Issue6282.java | 22 +- framework/tests/all-systems/Issue6319.java | 10 +- framework/tests/all-systems/Issue6433.java | 20 +- framework/tests/all-systems/Issue6438.java | 12 +- framework/tests/all-systems/Issue671.java | 14 +- framework/tests/all-systems/Issue689.java | 26 +- framework/tests/all-systems/Issue691.java | 2 +- framework/tests/all-systems/Issue692.java | 8 +- framework/tests/all-systems/Issue696.java | 2 +- framework/tests/all-systems/Issue703.java | 15 +- framework/tests/all-systems/Issue717.java | 22 +- framework/tests/all-systems/Issue738.java | 14 +- framework/tests/all-systems/Issue759.java | 38 +- framework/tests/all-systems/Issue807.java | 22 +- framework/tests/all-systems/Issue808.java | 24 +- framework/tests/all-systems/Issue810.java | 4 +- framework/tests/all-systems/Issue887.java | 12 +- framework/tests/all-systems/Issue888.java | 12 +- framework/tests/all-systems/Issue913.java | 16 +- framework/tests/all-systems/Issue953.java | 26 +- framework/tests/all-systems/Issue953b.java | 26 +- framework/tests/all-systems/Issue988.java | 10 +- .../tests/all-systems/LightWeightCache.java | 82 +- framework/tests/all-systems/LongDouble.java | 32 +- framework/tests/all-systems/LubRawTypes.java | 22 +- framework/tests/all-systems/MapCrashTest.java | 14 +- .../tests/all-systems/MethodTypeVars.java | 26 +- .../all-systems/MissingBoundAnnotations.java | 12 +- .../tests/all-systems/MultipleUnions.java | 54 +- framework/tests/all-systems/Options.java | 8 +- framework/tests/all-systems/PQueue.java | 14 +- .../all-systems/PolyCollectorTypeVars.java | 36 +- framework/tests/all-systems/PrintArray.java | 22 +- framework/tests/all-systems/RawSuper.java | 18 +- .../tests/all-systems/RawTypeAssignment.java | 20 +- framework/tests/all-systems/RawTypes.java | 10 +- .../tests/all-systems/ResourceVariables.java | 8 +- .../tests/all-systems/SameBoundsCrazy.java | 18 +- framework/tests/all-systems/SelfRef.java | 8 +- framework/tests/all-systems/SetOfSet.java | 12 +- framework/tests/all-systems/SimpleLog.java | 12 +- framework/tests/all-systems/StateMatch.java | 40 +- framework/tests/all-systems/SuperThis.java | 18 +- framework/tests/all-systems/Ternary.java | 108 +- framework/tests/all-systems/Throw.java | 20 +- .../TypeVarAndArrayRefinement.java | 32 +- .../TypeVarAndArrayRefinementSmall.java | 10 +- .../tests/all-systems/TypeVarInstanceOf.java | 6 +- .../tests/all-systems/TypeVarPrimitives.java | 12 +- .../tests/all-systems/TypeVarVarargs.java | 10 +- framework/tests/all-systems/TypeVars.java | 28 +- framework/tests/all-systems/UnionCrash.java | 34 +- framework/tests/all-systems/UnionTypes.java | 14 +- framework/tests/all-systems/Unions.java | 62 +- framework/tests/all-systems/VarKeyword.java | 18 +- framework/tests/all-systems/Viz.java | 18 +- .../tests/all-systems/WildCardCrash.java | 40 +- .../tests/all-systems/WildcardBounds.java | 16 +- .../all-systems/WildcardCharPrimitive.java | 36 +- framework/tests/all-systems/WildcardCon.java | 14 +- .../tests/all-systems/WildcardForEach.java | 12 +- .../tests/all-systems/WildcardInReturn.java | 16 +- .../tests/all-systems/WildcardIterable.java | 22 +- .../tests/all-systems/WildcardSuper.java | 18 +- .../tests/all-systems/WildcardSuper2.java | 14 +- .../all-systems/java17/BindingVariable.java | 12 +- .../tests/all-systems/java17/Issue5749.java | 12 +- .../tests/all-systems/java17/Issue5930.java | 26 +- .../tests/all-systems/java17/Issue6069.java | 24 +- .../tests/all-systems/java17/Issue6100.java | 11 +- .../all-systems/java17/SimpleRecord.java | 8 +- .../tests/all-systems/java17/SwitchTests.java | 288 +- .../tests/all-systems/java21/Issue6173.java | 30 +- .../tests/all-systems/java21/JEP440.java | 82 +- .../tests/all-systems/java21/JEP441.java | 472 +- .../all-systems/java8/DefaultMethods.java | 18 +- .../all-systems/java8/lambda/Issue1681.java | 22 +- .../all-systems/java8/lambda/Issue1817.java | 8 +- .../all-systems/java8/lambda/Issue450.java | 48 +- .../all-systems/java8/lambda/Issue573.java | 10 +- .../all-systems/java8/lambda/Lambda.java | 137 +- .../memberref/AssignmentContextFunction.java | 26 +- .../java8/memberref/FromByteCode.java | 4 +- .../all-systems/java8/memberref/Issue871.java | 8 +- .../all-systems/java8/memberref/Issue946.java | 46 +- .../java8/memberref/MemberReferences.java | 204 +- .../all-systems/java8/memberref/Purity.java | 28 +- .../java8/memberref/Receivers.java | 56 +- .../all-systems/java8/memberref/VarArgs.java | 30 +- .../java8inference/ArrayInits.java | 6 +- .../all-systems/java8inference/Bug1.java | 18 +- .../all-systems/java8inference/Bug10.java | 62 +- .../all-systems/java8inference/Bug11.java | 20 +- .../all-systems/java8inference/Bug12.java | 17 +- .../all-systems/java8inference/Bug13.java | 40 +- .../all-systems/java8inference/Bug14.java | 74 +- .../all-systems/java8inference/Bug15.java | 12 +- .../all-systems/java8inference/Bug16.java | 20 +- .../all-systems/java8inference/Bug2.java | 26 +- .../all-systems/java8inference/Bug3.java | 28 +- .../all-systems/java8inference/Bug4.java | 12 +- .../all-systems/java8inference/Bug5.java | 22 +- .../all-systems/java8inference/Bug6.java | 38 +- .../all-systems/java8inference/Bug7.java | 45 +- .../all-systems/java8inference/Bug8.java | 104 +- .../all-systems/java8inference/Bug9.java | 29 +- .../java8inference/CollectorsToList.java | 29 +- .../all-systems/java8inference/Issue1308.java | 28 +- .../all-systems/java8inference/Issue1312.java | 10 +- .../all-systems/java8inference/Issue1313.java | 10 +- .../all-systems/java8inference/Issue1331.java | 10 +- .../all-systems/java8inference/Issue1332.java | 30 +- .../all-systems/java8inference/Issue1334.java | 6 +- .../all-systems/java8inference/Issue1377.java | 18 +- .../all-systems/java8inference/Issue1379.java | 14 +- .../all-systems/java8inference/Issue1397.java | 18 +- .../all-systems/java8inference/Issue1398.java | 34 +- .../all-systems/java8inference/Issue1399.java | 22 +- .../all-systems/java8inference/Issue1407.java | 12 +- .../all-systems/java8inference/Issue1408.java | 14 +- .../all-systems/java8inference/Issue1415.java | 30 +- .../all-systems/java8inference/Issue1416.java | 8 +- .../all-systems/java8inference/Issue1417.java | 20 +- .../all-systems/java8inference/Issue1419.java | 14 +- .../all-systems/java8inference/Issue1424.java | 26 +- .../all-systems/java8inference/Issue1715.java | 32 +- .../all-systems/java8inference/Issue1775.java | 18 +- .../all-systems/java8inference/Issue1815.java | 16 +- .../all-systems/java8inference/Issue2975.java | 20 +- .../all-systems/java8inference/Issue3032.java | 59 +- .../all-systems/java8inference/Issue3036.java | 58 +- .../all-systems/java8inference/Issue404.java | 6 +- .../all-systems/java8inference/Issue953.java | 16 +- .../java8inference/MemRefInfere.java | 22 +- .../all-systems/java8inference/Misc.java | 26 +- .../annotationclassloader/LoaderTest.java | 10 +- framework/tests/classval/ClassNameTest.java | 34 +- .../tests/classval/ClassValInferenceTest.java | 109 +- .../tests/classval/ClassValSubtypingTest.java | 198 +- framework/tests/classval/GLBTest.java | 38 +- .../compound-checker/CompoundBasicTest.java | 18 +- .../tests/compound-checker/MultiError.java | 12 +- .../javac-error/JavaErrorTest.java | 20 +- .../annotatedfor/AnnotatedForTest.java | 426 +- .../lowerbound/LowerBoundDefaulting.java | 78 +- .../upperbound/UpperBoundDefaulting.java | 61 +- framework/tests/flow/AnnotationAliasing.java | 76 +- framework/tests/flow/ArrayFlow.java | 30 +- framework/tests/flow/Basic.java | 70 +- framework/tests/flow/Basic2.java | 435 +- framework/tests/flow/ContractsOverriding.java | 340 +- .../flow/ContractsOverridingSubtyping.java | 108 +- .../tests/flow/CustomContractWithArgs.java | 122 +- framework/tests/flow/Equal.java | 64 +- framework/tests/flow/FieldShadowing.java | 66 +- framework/tests/flow/Fields.java | 24 +- framework/tests/flow/Issue4449.java | 48 +- framework/tests/flow/Issue951.java | 220 +- framework/tests/flow/MetaPostcondition.java | 330 +- framework/tests/flow/MetaPrecondition.java | 130 +- framework/tests/flow/MethodCallFlowExpr.java | 380 +- framework/tests/flow/Monotonic.java | 72 +- framework/tests/flow/MoreFields.java | 8 +- framework/tests/flow/NonMethodCode.java | 48 +- framework/tests/flow/ParamFlowExpr.java | 40 +- framework/tests/flow/Postcondition.java | 582 +- framework/tests/flow/Precondition.java | 302 +- framework/tests/flow/Purity.java | 480 +- framework/tests/flow/StorePure.java | 260 +- framework/tests/flow/Termination.java | 20 +- framework/tests/flow/Values.java | 103 +- .../flow/flowexpression-scope/Class1.java | 2 +- .../flow/flowexpression-scope/Class2.java | 71 +- .../flow/flowexpression-scope/Issue862.java | 16 +- .../flowexpression/ArrayCreationParsing.java | 36 +- .../flowexpression/BinaryOperations.java | 6 +- .../flowexpression/Canonicalization.java | 54 +- .../flowexpression/CharAndDoubleParsing.java | 12 +- .../tests/flowexpression/ClassLiterals.java | 40 +- framework/tests/flowexpression/Complex.java | 45 +- .../tests/flowexpression/Constructor.java | 36 +- framework/tests/flowexpression/Fields.java | 23 +- .../tests/flowexpression/InnerClasses.java | 46 +- framework/tests/flowexpression/Issue1609.java | 404 +- .../tests/flowexpression/LambdaParameter.java | 117 +- framework/tests/flowexpression/Private.java | 11 +- framework/tests/flowexpression/SimpleVPA.java | 22 +- .../tests/flowexpression/Standardize.java | 174 +- .../tests/flowexpression/TestParsing.java | 10 +- .../flowexpression/ThisStaticContext.java | 63 +- framework/tests/flowexpression/ThisSuper.java | 52 +- .../tests/flowexpression/UnaryOperations.java | 8 +- .../tests/flowexpression/Unparsable.java | 16 +- .../flowexpression/UnsupportJavaCode.java | 12 +- .../tests/flowexpression/UsePrivate.java | 12 +- .../tests/flowexpression/ValueLiterals.java | 10 +- .../flowexpression/ViewPointAdaptMethods.java | 56 +- .../flowexpression/ViewpointAdaptation.java | 50 +- .../flowexpression/ViewpointAdaptation2.java | 60 +- .../framework-javac-errors/Issue346.java | 4 +- .../MissingSymbolCrash.java | 8 +- .../framework-javac-errors/ResolveError.java | 8 +- .../UnimportedExtends2.java | 4 +- .../tests/framework/AnnotatedAnnotation.java | 75 +- .../tests/framework/AnnotatedGenerics.java | 246 +- .../framework/AnnotationWithComponents.java | 10 +- .../tests/framework/AnonymousClasses.java | 26 +- framework/tests/framework/ArraySubtyping.java | 40 +- framework/tests/framework/Arrays.java | 207 +- framework/tests/framework/Assignments.java | 96 +- .../tests/framework/AssignmentsGeneric.java | 73 +- framework/tests/framework/BridgeMethods.java | 32 +- .../tests/framework/ClassAnnotations.java | 8 +- framework/tests/framework/Compound.java | 16 +- framework/tests/framework/Constructors.java | 80 +- framework/tests/framework/DeepOverride.java | 22 +- .../tests/framework/DeepOverrideAbstract.java | 30 +- .../tests/framework/DeepOverrideBug.java | 36 +- .../framework/DeepOverrideInterface.java | 30 +- framework/tests/framework/ExtendsDefault.java | 28 +- framework/tests/framework/GenericAlias.java | 28 +- .../tests/framework/GenericAliasInvalid.java | 14 +- .../framework/GenericAliasInvalidCall.java | 22 +- framework/tests/framework/GenericEnum.java | 6 +- framework/tests/framework/GenericTest1.java | 22 +- framework/tests/framework/GenericTest10.java | 30 +- framework/tests/framework/GenericTest11.java | 24 +- framework/tests/framework/GenericTest12.java | 10 +- framework/tests/framework/GenericTest2.java | 14 +- framework/tests/framework/GenericTest3.java | 14 +- framework/tests/framework/GenericTest4.java | 95 +- framework/tests/framework/GenericTest5.java | 30 +- framework/tests/framework/GenericTest6.java | 38 +- framework/tests/framework/GenericTest7.java | 92 +- framework/tests/framework/GenericTest8.java | 18 +- framework/tests/framework/GenericTest9.java | 132 +- .../tests/framework/GetReceiverLoop.java | 30 +- framework/tests/framework/InnerGenerics.java | 46 +- framework/tests/framework/MatrixBug.java | 2 +- .../framework/MethodOverrideBadParam.java | 10 +- .../framework/MethodOverrideBadReceiver.java | 12 +- .../framework/MethodOverrideBadReturn.java | 12 +- .../tests/framework/MethodOverrides.java | 122 +- framework/tests/framework/MoreVarargs.java | 22 +- .../tests/framework/MultiBoundTypeVar.java | 16 +- framework/tests/framework/OverrideCrash.java | 8 +- .../tests/framework/PrimitiveDotClass.java | 10 +- framework/tests/framework/RandomTests.java | 16 +- framework/tests/framework/RecursiveDef.java | 8 +- framework/tests/framework/Supertypes.java | 137 +- framework/tests/framework/SymbolError.java | 6 +- framework/tests/framework/TypeInference.java | 49 +- framework/tests/framework/Unboxing.java | 20 +- framework/tests/framework/Varargs.java | 62 +- framework/tests/framework/WildcardSuper.java | 11 +- framework/tests/framework/Wildcards.java | 13 +- .../tests/h1h2checker/AnonymousClasses.java | 21 +- framework/tests/h1h2checker/Catch.java | 64 +- .../h1h2checker/CompoundStringAssignment.java | 78 +- framework/tests/h1h2checker/Constructors.java | 104 +- framework/tests/h1h2checker/Defaulting.java | 286 +- .../h1h2checker/EnforceTargetLocation.java | 39 +- framework/tests/h1h2checker/ForEach.java | 136 +- framework/tests/h1h2checker/Generics.java | 50 +- .../tests/h1h2checker/GetClassStubTest.java | 22 +- .../tests/h1h2checker/IncompatibleBounds.java | 68 +- .../h1h2checker/InferTypeArgsPolyChecker.java | 335 +- framework/tests/h1h2checker/Inheritance.java | 16 +- .../tests/h1h2checker/Issue2163Final.java | 52 +- framework/tests/h1h2checker/Issue2186.java | 29 +- framework/tests/h1h2checker/Issue2264.java | 20 +- framework/tests/h1h2checker/Issue282.java | 166 +- framework/tests/h1h2checker/Issue681.java | 16 +- framework/tests/h1h2checker/Issue798.java | 22 +- framework/tests/h1h2checker/Issue849.java | 10 +- framework/tests/h1h2checker/Primitive.java | 24 +- .../tests/h1h2checker/TypeRefinement.java | 24 +- .../tests/h1h2checker/WildcardBounds.java | 209 +- .../h1h2checker/pkg1/PackageDefaulting.java | 24 +- .../tests/h1h2checker/pkg1/package-info.java | 12 +- .../pkg1/pkg2/PackageDefaulting.java | 22 +- .../ClassInitializer.java | 26 +- .../ClassInitializer2.java | 20 +- .../ClassInitializer2a.java | 16 +- .../ClassInitializer3.java | 26 +- .../DeclaredType.java | 5 +- .../ManualConstructor.java | 42 +- .../PrimitiveField.java | 96 +- .../ConstructorPostcondition.java | 26 +- .../EnsuresInitializedFieldsTest.java | 126 +- .../HelperMethodInitializesFields.java | 356 +- .../initialized-fields/SimpleConstructor.java | 36 +- framework/tests/lubglb/Dummy.java | 4 +- framework/tests/lubglb/IntersectionTypes.java | 42 +- .../tests/lubglb/Issue2432Constructor.java | 167 +- framework/tests/methodval/MethodNameTest.java | 56 +- .../methodval/MethodValInferenceTest.java | 191 +- .../tests/methodval/MethodValLUBTest.java | 165 +- .../methodval/MethodValSubtypingTest.java | 106 +- framework/tests/nontopdefault/NTDTest.java | 82 +- .../tests/nontopdefault/TestCasting.java | 26 +- .../PuritySuggestionsClass.java | 314 +- .../tests/reflection/AnonymousClassTest.java | 213 +- framework/tests/reflection/MethodTest.java | 817 +- .../reflection/ReflectionConstructorTest.java | 105 +- framework/tests/report/Accesses.java | 106 +- framework/tests/report/CallOverrides.java | 42 +- framework/tests/report/Creation.java | 48 +- framework/tests/report/Inherit.java | 14 +- framework/tests/report/Interface.java | 68 +- framework/tests/report/Overrides.java | 36 +- framework/tests/report/Package.java | 68 +- framework/tests/report/TestStub.java | 12 +- .../tests/reportmodifiers/TestModifiers.java | 10 +- .../tests/reporttreekinds/TestTreeKinds.java | 10 +- .../tests/returnsreceiver/GenericReturn.java | 42 +- .../tests/returnsreceiver/MethodRef.java | 32 +- .../returnsreceiver/NullsAndGenerics.java | 42 +- .../tests/returnsreceiver/OverrideTest.java | 54 +- .../tests/returnsreceiver/SimpleTest.java | 96 +- .../tests/returnsreceiver/SubtypingTest.java | 14 +- .../returnsreceiverautovalue/Animal.java | 99 +- .../BuilderMethodRef.java | 32 +- .../returnsreceiverlombok/BuilderTest.java | 49 +- .../StringPatternsUsage.java | 140 +- .../tests/subtyping/InvariantArrays.java | 87 +- framework/tests/subtyping/Poly.java | 159 +- framework/tests/subtyping/Simple.java | 45 +- framework/tests/subtyping/ThisType.java | 22 +- framework/tests/subtyping/ThrowCatch.java | 48 +- framework/tests/subtyping/UnusedTypes.java | 23 +- .../typedeclbounds/BooleanExpression.java | 94 +- .../StringConcatConversion.java | 33 +- .../typedecldefault/BoundsAndDefaults.java | 39 +- .../TestDefaultForTypeDecl.java | 24 +- .../value-ignore-range-overflow/Index117.java | 12 +- .../RefinementEq.java | 38 +- .../RefinementGT.java | 106 +- .../RefinementGTE.java | 86 +- .../RefinementLT.java | 100 +- .../RefinementLTE.java | 102 +- .../RefinementNEq.java | 42 +- .../TransferAdd.java | 102 +- .../TransferDivide.java | 106 +- .../TransferMod.java | 42 +- .../TransferSub.java | 96 +- .../TransferTimes.java | 38 +- .../ValueNoOverflow.java | 180 +- .../value-ignore-range-overflow/Widen.java | 30 +- .../Binaries.java | 639 +- .../CompoundAssignment.java | 90 +- .../StringLenConcats.java | 217 +- framework/tests/value/Alias.java | 12 +- framework/tests/value/AnnotationUse.java | 12 +- framework/tests/value/ArrayInit.java | 360 +- framework/tests/value/ArrayIntro.java | 28 +- framework/tests/value/Basics.java | 476 +- framework/tests/value/BigIntegerTest.java | 51 +- framework/tests/value/Binaries.java | 639 +- .../tests/value/BitsMethodsIntRange.java | 16 +- framework/tests/value/BitwiseAnd.java | 60 +- framework/tests/value/Boxing.java | 26 +- .../CharArrayWithNonLiteralConstants.java | 10 +- framework/tests/value/CharacterToString.java | 6 +- framework/tests/value/ClassNotFound.java | 20 +- framework/tests/value/CompoundAssignment.java | 168 +- framework/tests/value/DivideByZero.java | 120 +- framework/tests/value/DoubleRounding.java | 8 +- .../tests/value/EmptyAnnotationArgument.java | 20 +- framework/tests/value/EnclosingClass.java | 56 +- framework/tests/value/EnumConstants.java | 317 +- framework/tests/value/EnumValue.java | 114 +- framework/tests/value/ExceptionTest.java | 12 +- framework/tests/value/Fields.java | 77 +- framework/tests/value/GTETransferBug.java | 12 +- framework/tests/value/Issue1214.java | 110 +- framework/tests/value/Issue1218.java | 261 +- framework/tests/value/Issue1229.java | 18 +- framework/tests/value/Issue1423.java | 12 +- framework/tests/value/Issue1579.java | 10 +- framework/tests/value/Issue1580.java | 10 +- framework/tests/value/Issue1655.java | 10 +- framework/tests/value/Issue2353.java | 6 +- framework/tests/value/Issue2367.java | 26 +- framework/tests/value/Issue3001.java | 8 +- framework/tests/value/Issue3105.java | 32 +- .../value/Issue3105FieldInSameClass.java | 14 +- .../tests/value/Issue3105StaticImport.java | 6 +- framework/tests/value/Issue3307.java | 14 +- framework/tests/value/Issue867.java | 104 +- .../tests/value/LengthTransferForMinLen.java | 28 +- framework/tests/value/LiteralArray.java | 12 +- framework/tests/value/LongMax.java | 8 +- framework/tests/value/Loop.java | 51 +- framework/tests/value/LubToRange.java | 14 +- framework/tests/value/MLEqualTo.java | 8 +- framework/tests/value/MathMinMax.java | 50 +- framework/tests/value/Methods.java | 156 +- framework/tests/value/MinLenConstants.java | 6 +- framework/tests/value/MinLenEqTransfer.java | 44 +- framework/tests/value/MinLenFieldInvar.java | 82 +- framework/tests/value/MinLenFieldInvar2.java | 32 +- framework/tests/value/MinLenGTETransfer.java | 18 +- framework/tests/value/MinLenGTTransfer.java | 18 +- framework/tests/value/MinLenLTETransfer.java | 18 +- framework/tests/value/MinLenLTTransfer.java | 18 +- framework/tests/value/MinLenLUB.java | 66 +- framework/tests/value/MinLenNEqTransfer.java | 34 +- .../tests/value/MinLenPostcondition.java | 8 +- framework/tests/value/MinLenVarargs.java | 26 +- .../value/MultipleBinaryExpressions.java | 124 +- framework/tests/value/MyTree.java | 62 +- framework/tests/value/MyTree2.java | 35 +- framework/tests/value/NegativeArrayLen.java | 28 +- .../value/NestedArrayLengthInference.java | 14 +- framework/tests/value/Overflows.java | 78 +- framework/tests/value/Polymorphic.java | 44 +- framework/tests/value/Polymorphic2.java | 66 +- framework/tests/value/RefineBoolean.java | 64 +- .../tests/value/RefineUnknownToIntRange.java | 68 +- framework/tests/value/Refinement.java | 551 +- framework/tests/value/Refinement2.java | 222 +- framework/tests/value/RegexMatching.java | 256 +- framework/tests/value/RegexNonMatching.java | 484 +- .../value/RegexPatternSyntaxException.java | 8 +- framework/tests/value/RepeatMinLenIf.java | 94 +- .../tests/value/RepeatMinLenIfWithError.java | 98 +- framework/tests/value/Repo.java | 19 +- framework/tests/value/SplitAssignments.java | 24 +- framework/tests/value/StartsEndsWith.java | 80 +- framework/tests/value/StaticExTest.java | 70 +- .../value/StaticallyExecutableWarnings.java | 68 +- framework/tests/value/StringConcats.java | 74 +- framework/tests/value/StringLen.java | 206 +- framework/tests/value/StringLenConcats.java | 217 +- framework/tests/value/StringLenMethods.java | 86 +- framework/tests/value/StringLenWidening.java | 22 +- framework/tests/value/StringPolyValue.java | 16 +- framework/tests/value/StringSplit.java | 20 +- framework/tests/value/StringValCrash.java | 8 +- framework/tests/value/StringValNull.java | 119 +- .../value/StringValNullConcatLength.java | 24 +- framework/tests/value/StringValOfArrays.java | 10 +- framework/tests/value/Switch.java | 344 +- framework/tests/value/TooWideRange.java | 46 +- framework/tests/value/TypeCast.java | 102 +- framework/tests/value/TypeVars.java | 68 +- framework/tests/value/Unaries.java | 110 +- framework/tests/value/UncheckedMinLen.java | 20 +- framework/tests/value/Underflows.java | 66 +- framework/tests/value/ValueCast.java | 106 +- framework/tests/value/ValueCast2.java | 30 +- framework/tests/value/ValueOpt.java | 18 +- framework/tests/value/ValueWrapperCast.java | 106 +- framework/tests/value/VarArgRe.java | 36 +- framework/tests/value/WildcardIn.java | 8 +- .../tests/value/java17/MultiCaseConst.java | 68 +- .../value/java17/SwitchExpressionTyping.java | 174 +- .../java17/ValueSwitchExprNeedsDataflow.java | 118 +- .../java17/ValueSwitchStatementRules.java | 42 +- framework/tests/value/loops/DoWhile.java | 54 +- framework/tests/value/loops/NestedLoops.java | 88 +- .../tests/value/loops/OscillatingLoops.java | 102 +- .../tests/value/loops/WidenedUpperBound.java | 351 +- .../TestVariableNameDefault.java | 400 +- .../tests/viewpointtest/LostNonReflexive.java | 64 +- .../tests/viewpointtest/PolyConstructor.java | 92 +- .../tests/viewpointtest/PolyWithVPA.java | 26 +- .../viewpointtest/SuperConstructorCalls.java | 74 +- .../viewpointtest/TestGetAnnotatedLhs.java | 68 +- .../viewpointtest/ThisConstructorCalls.java | 38 +- .../tests/viewpointtest/VPAExamples.java | 56 +- .../viewpointtest/VarargsConstructor.java | 114 +- .../javacutil/AbstractTypeProcessor.java | 267 +- .../javacutil/AnnotationBuilder.java | 1585 ++- .../javacutil/AnnotationMirrorMap.java | 296 +- .../javacutil/AnnotationMirrorSet.java | 686 +- .../javacutil/AnnotationProvider.java | 94 +- .../javacutil/AnnotationUtils.java | 3124 ++--- .../javacutil/BasicAnnotationProvider.java | 129 +- .../javacutil/BasicTypeProcessor.java | 54 +- .../checkerframework/javacutil/BugInCF.java | 110 +- .../javacutil/CollectionUtils.java | 407 +- .../javacutil/DeepCopyable.java | 47 +- .../javacutil/ElementUtils.java | 2098 ++- .../javacutil/InternalUtils.java | 86 +- .../org/checkerframework/javacutil/Pair.java | 236 +- .../checkerframework/javacutil/Resolver.java | 1062 +- .../javacutil/SwitchExpressionScanner.java | 287 +- .../javacutil/SystemUtil.java | 229 +- .../javacutil/TreePathUtil.java | 790 +- .../checkerframework/javacutil/TreeUtils.java | 5133 ++++--- .../javacutil/TreeUtilsAfterJava11.java | 986 +- .../javacutil/TypeAnnotationUtils.java | 1184 +- .../javacutil/TypeKindUtils.java | 515 +- .../javacutil/TypeSystemError.java | 40 +- .../javacutil/TypesUtils.java | 2398 ++-- .../checkerframework/javacutil/UserError.java | 40 +- .../javacutil/trees/DetachedVarSymbol.java | 29 +- .../javacutil/trees/TreeBuilder.java | 1238 +- .../javacutil/trees/TreeParser.java | 237 +- 3904 files changed, 257897 insertions(+), 264114 deletions(-) diff --git a/build.gradle b/build.gradle index a1e60e1efa6..13f919ccc83 100644 --- a/build.gradle +++ b/build.gradle @@ -305,8 +305,8 @@ allprojects { target targets targetExclude doNotFormat - googleJavaFormat().aosp() - importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') + googleJavaFormat()// .aosp() + // importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') formatAnnotations().addTypeAnnotation("PolyInitialized").addTypeAnnotation("PolyVP").addTypeAnnotation("ReceiverDependentQual") } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java index 1944baff64e..bc457c6a797 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java @@ -18,10 +18,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface CalledMethods { - /** - * The names of methods that have definitely been called. - * - * @return the names of methods that have definitely been called - */ - String[] value(); + /** + * The names of methods that have definitely been called. + * + * @return the names of methods that have definitely been called + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java index 0e9bb18d7f6..8d831ce73c3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java @@ -22,10 +22,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface NotCalledMethods { - /** - * The names of the methods that have NOT been called. - * - * @return the names of the methods that have NOT been called - */ - String[] value(); + /** + * The names of the methods that have NOT been called. + * + * @return the names of the methods that have NOT been called + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java index 95377aa8894..cbacc8628b4 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.calledmethods.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * If an expression has type {@code @CalledMethods({"m1", "m2"})}, then methods {@code m1} and @@ -25,10 +24,10 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy public @interface CalledMethods { - /** - * Methods that have definitely been called on the expression whose type is annotated. - * - * @return methods that have definitely been called - */ - public String[] value() default {}; + /** + * Methods that have definitely been called on the expression whose type is annotated. + * + * @return methods that have definitely been called + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java index ecc5d2546f2..307be07adfa 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.calledmethods.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type for the Called Methods type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java index c0f5e8bf222..38aebe00572 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java @@ -1,11 +1,10 @@ package org.checkerframework.checker.calledmethods.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation represents a predicate on {@code @}{@link CalledMethods} annotations. If method @@ -19,14 +18,14 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({CalledMethods.class}) public @interface CalledMethodsPredicate { - /** - * A boolean expression constructed from the following grammar: - * - *

S → method name | S && S | S || S | !S | (S) - * - *

The expression uses standard Java operator precedence: "!" then "&&" then "||". - * - * @return the boolean expression - */ - String value(); + /** + * A boolean expression constructed from the following grammar: + * + *

S → method name | S && S | S || S | !S | (S) + * + *

The expression uses standard Java operator precedence: "!" then "&&" then "||". + * + * @return the boolean expression + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java index d3728957fd0..da6ce0f6441 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.calledmethods.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the method, if it terminates successfully, always invokes the given methods on the @@ -48,40 +47,40 @@ @Repeatable(EnsuresCalledMethods.List.class) @InheritedAnnotation public @interface EnsuresCalledMethods { - /** - * The Java expressions that will have methods called on them. - * - * @return the Java expressions that will have methods called on them - * @see org.checkerframework.framework.qual.EnsuresQualifier - */ - // Postconditions must use "value" as the name (conditional postconditions use "expression"). - String[] value(); + /** + * The Java expressions that will have methods called on them. + * + * @return the Java expressions that will have methods called on them + * @see org.checkerframework.framework.qual.EnsuresQualifier + */ + // Postconditions must use "value" as the name (conditional postconditions use "expression"). + String[] value(); - /** - * The methods guaranteed to be invoked on the expressions. - * - * @return the methods guaranteed to be invoked on the expressions - */ - @QualifierArgument("value") - String[] methods(); + /** + * The methods guaranteed to be invoked on the expressions. + * + * @return the methods guaranteed to be invoked on the expressions + */ + @QualifierArgument("value") + String[] methods(); + /** + * A wrapper annotation that makes the {@link EnsuresCalledMethods} annotation repeatable. This + * annotation is an implementation detail: programmers generally do not need to write this. It is + * created automatically by Java when a programmer writes more than one {@link + * EnsuresCalledMethods} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @InheritedAnnotation + @PostconditionAnnotation(qualifier = CalledMethods.class) + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresCalledMethods} annotation repeatable. This - * annotation is an implementation detail: programmers generally do not need to write this. It - * is created automatically by Java when a programmer writes more than one {@link - * EnsuresCalledMethods} annotation at the same location. + * Return the repeatable annotations. + * + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @InheritedAnnotation - @PostconditionAnnotation(qualifier = CalledMethods.class) - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresCalledMethods[] value(); - } + EnsuresCalledMethods[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java index 62177670599..001cf1a8a76 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.calledmethods.qual; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the method, if it terminates with the given result, invokes the given methods on @@ -26,49 +25,49 @@ @InheritedAnnotation @Repeatable(EnsuresCalledMethodsIf.List.class) public @interface EnsuresCalledMethodsIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns Java expressions that have had the given methods called on them after the method - * returns {@link #result}. - * - * @return an array of Java expressions - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns Java expressions that have had the given methods called on them after the method + * returns {@link #result}. + * + * @return an array of Java expressions + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * The methods guaranteed to be invoked on the expressions if the result of the method is {@link - * #result}. - * - * @return the methods guaranteed to be invoked on the expressions if the result of the method - * is {@link #result} - */ - @QualifierArgument("value") - String[] methods(); + /** + * The methods guaranteed to be invoked on the expressions if the result of the method is {@link + * #result}. + * + * @return the methods guaranteed to be invoked on the expressions if the result of the method is + * {@link #result} + */ + @QualifierArgument("value") + String[] methods(); + /** + * A wrapper annotation that makes the {@link EnsuresCalledMethodsIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresCalledMethodsIf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = CalledMethods.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresCalledMethodsIf} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresCalledMethodsIf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = CalledMethods.class) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresCalledMethodsIf[] value(); - } + EnsuresCalledMethodsIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java index 5d2d7e6f26c..f0f0cb43261 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.calledmethods.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Indicates that the method, if it terminates by throwing an {@link Exception}, always invokes the @@ -39,60 +38,60 @@ @InheritedAnnotation public @interface EnsuresCalledMethodsOnException { - /** - * Returns Java expressions that have had the given methods called on them after the method - * throws an exception. - * - * @return an array of Java expressions - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * Returns Java expressions that have had the given methods called on them after the method throws + * an exception. + * + * @return an array of Java expressions + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); - // NOTE 2023/10/6: There seems to be a fundamental limitation in the dataflow framework that - // prevent us from supporting a custom set of exceptions. Specifically, in the following code: - // - // try { - // m1(); - // } finally { - // m2(); - // } - // - // all exceptional edges out of the `m1()` call will flow to the same place: the start of the - // `m2()` call in the finally block. Any information about what `m1()` promised on specific - // exception types will be lost. - // - // /** - // * Returns the exception types under which the postcondition holds. - // * - // * @return the exception types under which the postcondition holds. - // */ - // Class[] exceptions(); + // NOTE 2023/10/6: There seems to be a fundamental limitation in the dataflow framework that + // prevent us from supporting a custom set of exceptions. Specifically, in the following code: + // + // try { + // m1(); + // } finally { + // m2(); + // } + // + // all exceptional edges out of the `m1()` call will flow to the same place: the start of the + // `m2()` call in the finally block. Any information about what `m1()` promised on specific + // exception types will be lost. + // + // /** + // * Returns the exception types under which the postcondition holds. + // * + // * @return the exception types under which the postcondition holds. + // */ + // Class[] exceptions(); - /** - * The methods guaranteed to be invoked on the expressions if the result of the method throws an - * exception. - * - * @return the methods guaranteed to be invoked on the expressions if the method throws an - * exception - */ - String[] methods(); + /** + * The methods guaranteed to be invoked on the expressions if the result of the method throws an + * exception. + * + * @return the methods guaranteed to be invoked on the expressions if the method throws an + * exception + */ + String[] methods(); + /** + * A wrapper annotation that makes the {@link EnsuresCalledMethodsOnException} annotation + * repeatable. This annotation is an implementation detail: programmers generally do not need to + * write this. It is created automatically by Java when a programmer writes more than one {@link + * EnsuresCalledMethodsOnException} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresCalledMethodsOnException} annotation - * repeatable. This annotation is an implementation detail: programmers generally do not need to - * write this. It is created automatically by Java when a programmer writes more than one {@link - * EnsuresCalledMethodsOnException} annotation at the same location. + * Return the repeatable annotations. + * + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresCalledMethodsOnException[] value(); - } + EnsuresCalledMethodsOnException[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java index 553ebc5ebd4..5437acc8f7e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java @@ -22,10 +22,10 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface EnsuresCalledMethodsVarArgs { - /** - * Returns the methods guaranteed to be invoked on the varargs parameters. - * - * @return the methods guaranteed to be invoked on the varargs parameters - */ - String[] value(); + /** + * Returns the methods guaranteed to be invoked on the varargs parameters. + * + * @return the methods guaranteed to be invoked on the varargs parameters + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java index 3b645366bf6..ac5ae5e185c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.calledmethods.qual; -import org.checkerframework.framework.qual.PreconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PreconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates a method precondition: when the method is invoked, the specified expressions must have @@ -26,39 +25,39 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Repeatable(RequiresCalledMethods.List.class) public @interface RequiresCalledMethods { - /** - * The Java expressions that must have had methods called on them. - * - * @return the Java expressions that must have had methods called on them - * @see org.checkerframework.framework.qual.EnsuresQualifier - */ - // Preconditions must use "value" as the name (conditional preconditions use "expression"). - String[] value(); + /** + * The Java expressions that must have had methods called on them. + * + * @return the Java expressions that must have had methods called on them + * @see org.checkerframework.framework.qual.EnsuresQualifier + */ + // Preconditions must use "value" as the name (conditional preconditions use "expression"). + String[] value(); - /** - * The methods guaranteed to be invoked on the expressions. - * - * @return the methods guaranteed to be invoked on the expressions - */ - @QualifierArgument("value") - String[] methods(); + /** + * The methods guaranteed to be invoked on the expressions. + * + * @return the methods guaranteed to be invoked on the expressions + */ + @QualifierArgument("value") + String[] methods(); + /** + * A wrapper annotation that makes the {@link RequiresCalledMethods} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresCalledMethods} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @PreconditionAnnotation(qualifier = CalledMethods.class) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + public static @interface List { /** - * A wrapper annotation that makes the {@link RequiresCalledMethods} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresCalledMethods} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @PreconditionAnnotation(qualifier = CalledMethods.class) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - RequiresCalledMethods[] value(); - } + RequiresCalledMethods[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java index c842d05f1f6..cd9f177bf83 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.compilermsgs.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * A string that is definitely a compiler message key. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java index f730e685a02..9413b649bf2 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.compilermsgs.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Compiler Message Key type system. Programmers should rarely write this diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java index 8de3589889b..1e8916ee106 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.compilermsgs.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * A {@code String} that might or might not be a compiler message key. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java index 273f3678414..c6f28499e24 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Basic alpha compositing rules for combining source and destination colors to achieve blending and diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java index 5488f870892..0dbe2718055 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Color space tags to identify the specific color space of a Color object or, via a ColorModel diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java index 7cfeb7681f5..7e78009279e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * AwtCursorType. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java index 838cf66f6fc..a5bc81f1a5d 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Line alignments in a flow layout (see {@link java.awt.FlowLayout} for more details). diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java index 25fc822b757..f17e9d4c932 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * A generic fake enumeration qualifier that is parameterized by a name. It is written in source @@ -20,5 +19,5 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(FenumTop.class) public @interface Fenum { - String value(); + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java index b48b127a622..43154759f49 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Fenum type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java index 8f0c43b5bff..9a50d7356c3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The top of the fake enumeration type hierarchy. @@ -20,10 +19,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({ - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.RESOURCE_VARIABLE + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.RESOURCE_VARIABLE }) @SubtypeOf({}) @DefaultFor({TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE}) diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java index d516e69de09..4eeaf969214 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** * An unqualified type. Such a type is incomparable to (that is, neither a subtype nor a supertype diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java index 47757d9b486..d203d9a861a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the fake enum type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java index 03bbf87590a..1c079d5d7fa 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * SwingBoxOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java index 1e4dc12e68a..f89cd2dbd29 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * SwingCompassDirection. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java index e76c992d386..8935db1e0d2 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * SwingElementOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java index 1b50e42af0f..569f8604fc2 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * SwingHorizontalOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java index a35095755c3..d555bf175e3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * SwingSplitPaneOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java index eac953203d6..84833e6103d 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * SwingTextOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java index cf1cad13892..ac51e16cc98 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Vertical orientations for the title text of a {@link javax.swing.border.TitledBorder}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java index bfed072887e..e3ca36f90b1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Justifications for the title text of a {@link javax.swing.border.TitledBorder}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java index f1318741fcb..7a70857ddc8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.fenum.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * SwingVerticalOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java index 294cea2abdd..7da6fe4e6fa 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java @@ -1,9 +1,5 @@ package org.checkerframework.checker.formatter.qual; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; @@ -14,6 +10,9 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.framework.qual.AnnotatedFor; /** * Elements of this enumeration are used in a {@link Format Format} annotation to indicate the valid @@ -37,340 +36,339 @@ @SuppressWarnings("unchecked") // ".class" expressions in varargs position @AnnotatedFor("nullness") public enum ConversionCategory { - /** Use if the parameter can be of any type. Applicable for conversions b, B, h, H, s, S. */ - GENERAL("bBhHsS", (Class[]) null /* everything */), + /** Use if the parameter can be of any type. Applicable for conversions b, B, h, H, s, S. */ + GENERAL("bBhHsS", (Class[]) null /* everything */), - /** - * Use if the parameter is of a basic types which represent Unicode characters: char, Character, - * byte, Byte, short, and Short. This conversion may also be applied to the types int and - * Integer when Character.isValidCodePoint(int) returns true. Applicable for conversions c, C. - */ - CHAR("cC", Character.class, Byte.class, Short.class, Integer.class), + /** + * Use if the parameter is of a basic types which represent Unicode characters: char, Character, + * byte, Byte, short, and Short. This conversion may also be applied to the types int and Integer + * when Character.isValidCodePoint(int) returns true. Applicable for conversions c, C. + */ + CHAR("cC", Character.class, Byte.class, Short.class, Integer.class), - /** - * Use if the parameter is an integral type: byte, Byte, short, Short, int and Integer, long, - * Long, and BigInteger. Applicable for conversions d, o, x, X. - */ - INT("doxX", Byte.class, Short.class, Integer.class, Long.class, BigInteger.class), + /** + * Use if the parameter is an integral type: byte, Byte, short, Short, int and Integer, long, + * Long, and BigInteger. Applicable for conversions d, o, x, X. + */ + INT("doxX", Byte.class, Short.class, Integer.class, Long.class, BigInteger.class), - /** - * Use if the parameter is a floating-point type: float, Float, double, Double, and BigDecimal. - * Applicable for conversions e, E, f, g, G, a, A. - */ - FLOAT("eEfgGaA", Float.class, Double.class, BigDecimal.class), + /** + * Use if the parameter is a floating-point type: float, Float, double, Double, and BigDecimal. + * Applicable for conversions e, E, f, g, G, a, A. + */ + FLOAT("eEfgGaA", Float.class, Double.class, BigDecimal.class), - /** - * Use if the parameter is a type which is capable of encoding a date or time: long, Long, - * Calendar, and Date. Applicable for conversions t, T. - */ - @SuppressWarnings("JdkObsolete") - TIME("tT", Long.class, Calendar.class, Date.class), + /** + * Use if the parameter is a type which is capable of encoding a date or time: long, Long, + * Calendar, and Date. Applicable for conversions t, T. + */ + @SuppressWarnings("JdkObsolete") + TIME("tT", Long.class, Calendar.class, Date.class), - /** - * Use if the parameter is both a char and an int. - * - *

In a format string, multiple conversions may be applied to the same parameter. This is - * seldom needed, but the following is an example of such use: - * - *

-     *   format("Test %1$c %1$d", (int)42);
-     * 
- * - * In this example, the first parameter is interpreted as both a character and an int, therefore - * the parameter must be compatible with both conversion, and can therefore neither be char nor - * long. This intersection of conversions is called CHAR_AND_INT. - * - *

One other conversion intersection is interesting, namely the intersection of INT and TIME, - * resulting in INT_AND_TIME. - * - *

All other intersection either lead to an already existing type, or NULL, in which case it - * is illegal to pass object's of any type as parameter. - */ - CHAR_AND_INT(null, Byte.class, Short.class, Integer.class), + /** + * Use if the parameter is both a char and an int. + * + *

In a format string, multiple conversions may be applied to the same parameter. This is + * seldom needed, but the following is an example of such use: + * + *

+   *   format("Test %1$c %1$d", (int)42);
+   * 
+ * + * In this example, the first parameter is interpreted as both a character and an int, therefore + * the parameter must be compatible with both conversion, and can therefore neither be char nor + * long. This intersection of conversions is called CHAR_AND_INT. + * + *

One other conversion intersection is interesting, namely the intersection of INT and TIME, + * resulting in INT_AND_TIME. + * + *

All other intersection either lead to an already existing type, or NULL, in which case it is + * illegal to pass object's of any type as parameter. + */ + CHAR_AND_INT(null, Byte.class, Short.class, Integer.class), - /** - * Use if the parameter is both an int and a time. - * - * @see #CHAR_AND_INT - */ - INT_AND_TIME(null, Long.class), + /** + * Use if the parameter is both an int and a time. + * + * @see #CHAR_AND_INT + */ + INT_AND_TIME(null, Long.class), - /** - * Use if no object of any type can be passed as parameter. In this case, the only legal value - * is null. This is seldomly needed, and indicates an error in most cases. For example: - * - *

-     *   format("Test %1$f %1$d", null);
-     * 
- * - * Only null can be legally passed, passing a value such as 4 or 4.2 would lead to an exception. - */ - NULL(null), + /** + * Use if no object of any type can be passed as parameter. In this case, the only legal value is + * null. This is seldomly needed, and indicates an error in most cases. For example: + * + *
+   *   format("Test %1$f %1$d", null);
+   * 
+ * + * Only null can be legally passed, passing a value such as 4 or 4.2 would lead to an exception. + */ + NULL(null), - /** - * Use if a parameter is not used by the formatter. This is seldomly needed, and indicates an - * error in most cases. For example: - * - *
-     *   format("Test %1$s %3$s", "a","unused","b");
-     * 
- * - * Only the first "a" and third "b" parameters are used, the second "unused" parameter is - * ignored. - */ - UNUSED(null, (Class[]) null /* everything */); + /** + * Use if a parameter is not used by the formatter. This is seldomly needed, and indicates an + * error in most cases. For example: + * + *
+   *   format("Test %1$s %3$s", "a","unused","b");
+   * 
+ * + * Only the first "a" and third "b" parameters are used, the second "unused" parameter is ignored. + */ + UNUSED(null, (Class[]) null /* everything */); - /** The argument types. Null means every type. */ - @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final Class @Nullable [] types; + /** The argument types. Null means every type. */ + @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! + public final Class @Nullable [] types; - /** The format specifier characters. Null means users cannot specify it directly. */ - public final @Nullable String chars; + /** The format specifier characters. Null means users cannot specify it directly. */ + public final @Nullable String chars; - /** - * Create a new conversion category. - * - * @param chars the format specifier characters. Null means users cannot specify it directly. - * @param types the argument types. Null means every type. - */ - ConversionCategory(@Nullable String chars, Class @Nullable ... types) { - this.chars = chars; - if (types == null) { - this.types = types; - } else { - List> typesWithPrimitives = new ArrayList<>(types.length); - for (Class type : types) { - typesWithPrimitives.add(type); - Class unwrapped = unwrapPrimitive(type); - if (unwrapped != null) { - typesWithPrimitives.add(unwrapped); - } - } - this.types = typesWithPrimitives.toArray(new Class[typesWithPrimitives.size()]); + /** + * Create a new conversion category. + * + * @param chars the format specifier characters. Null means users cannot specify it directly. + * @param types the argument types. Null means every type. + */ + ConversionCategory(@Nullable String chars, Class @Nullable ... types) { + this.chars = chars; + if (types == null) { + this.types = types; + } else { + List> typesWithPrimitives = new ArrayList<>(types.length); + for (Class type : types) { + typesWithPrimitives.add(type); + Class unwrapped = unwrapPrimitive(type); + if (unwrapped != null) { + typesWithPrimitives.add(unwrapped); } + } + this.types = typesWithPrimitives.toArray(new Class[typesWithPrimitives.size()]); } + } - /** - * If the given class is a primitive wrapper, return the corresponding primitive class. - * Otherwise return null. - * - * @param c a class - * @return the unwrapped primitive, or null - */ - private static @Nullable Class unwrapPrimitive(Class c) { - if (c == Byte.class) { - return byte.class; - } - if (c == Character.class) { - return char.class; - } - if (c == Short.class) { - return short.class; - } - if (c == Integer.class) { - return int.class; - } - if (c == Long.class) { - return long.class; - } - if (c == Float.class) { - return float.class; - } - if (c == Double.class) { - return double.class; - } - if (c == Boolean.class) { - return boolean.class; - } - return null; + /** + * If the given class is a primitive wrapper, return the corresponding primitive class. Otherwise + * return null. + * + * @param c a class + * @return the unwrapped primitive, or null + */ + private static @Nullable Class unwrapPrimitive(Class c) { + if (c == Byte.class) { + return byte.class; } - - /** - * The conversion categories that have a corresponding conversion character. This lacks UNUSED, - * TIME_AND_INT, etc. - */ - private static final ConversionCategory[] conversionCategoriesWithChar = - new ConversionCategory[] {GENERAL, CHAR, INT, FLOAT, TIME}; - - /** - * Converts a conversion character to a category. For example: - * - *
{@code
-     * ConversionCategory.fromConversionChar('d') == ConversionCategory.INT
-     * }
- * - * @param c a conversion character - * @return the category for the given conversion character - */ - @SuppressWarnings("nullness:dereference.of.nullable") // `chars` field is non-null for these - public static ConversionCategory fromConversionChar(char c) { - for (ConversionCategory v : conversionCategoriesWithChar) { - if (v.chars.contains(String.valueOf(c))) { - return v; - } - } - throw new IllegalArgumentException("Bad conversion character " + c); + if (c == Character.class) { + return char.class; } - - private static Set arrayToSet(E[] a) { - return new HashSet<>(Arrays.asList(a)); + if (c == Short.class) { + return short.class; + } + if (c == Integer.class) { + return int.class; + } + if (c == Long.class) { + return long.class; } + if (c == Float.class) { + return float.class; + } + if (c == Double.class) { + return double.class; + } + if (c == Boolean.class) { + return boolean.class; + } + return null; + } - public static boolean isSubsetOf(ConversionCategory a, ConversionCategory b) { - return intersect(a, b) == a; + /** + * The conversion categories that have a corresponding conversion character. This lacks UNUSED, + * TIME_AND_INT, etc. + */ + private static final ConversionCategory[] conversionCategoriesWithChar = + new ConversionCategory[] {GENERAL, CHAR, INT, FLOAT, TIME}; + + /** + * Converts a conversion character to a category. For example: + * + *
{@code
+   * ConversionCategory.fromConversionChar('d') == ConversionCategory.INT
+   * }
+ * + * @param c a conversion character + * @return the category for the given conversion character + */ + @SuppressWarnings("nullness:dereference.of.nullable") // `chars` field is non-null for these + public static ConversionCategory fromConversionChar(char c) { + for (ConversionCategory v : conversionCategoriesWithChar) { + if (v.chars.contains(String.valueOf(c))) { + return v; + } } + throw new IllegalArgumentException("Bad conversion character " + c); + } - /** Conversion categories that need to be considered by {@link #intersect}. */ - private static final ConversionCategory[] conversionCategoriesForIntersect = - new ConversionCategory[] {CHAR, INT, FLOAT, TIME, CHAR_AND_INT, INT_AND_TIME, NULL}; + private static Set arrayToSet(E[] a) { + return new HashSet<>(Arrays.asList(a)); + } - /** - * Returns the intersection of two categories. This is seldomly needed. - * - *
- * - *
-     * ConversionCategory.intersect(INT, TIME) == INT_AND_TIME;
-     * 
- * - *
- * - * @param a a category - * @param b a category - * @return the intersection of the two categories (their greatest lower bound) - */ - public static ConversionCategory intersect(ConversionCategory a, ConversionCategory b) { - if (a == UNUSED) { - return b; - } - if (b == UNUSED) { - return a; - } - if (a == GENERAL) { - return b; - } - if (b == GENERAL) { - return a; - } + public static boolean isSubsetOf(ConversionCategory a, ConversionCategory b) { + return intersect(a, b) == a; + } - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> as = arrayToSet(a.types); - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> bs = arrayToSet(b.types); - as.retainAll(bs); // intersection - for (ConversionCategory v : conversionCategoriesForIntersect) { - @SuppressWarnings( - "nullness:argument.type.incompatible" // `types` field is null only for UNUSED - // and GENERAL - ) - Set> vs = arrayToSet(v.types); - if (vs.equals(as)) { - return v; - } - } - throw new RuntimeException(); - } + /** Conversion categories that need to be considered by {@link #intersect}. */ + private static final ConversionCategory[] conversionCategoriesForIntersect = + new ConversionCategory[] {CHAR, INT, FLOAT, TIME, CHAR_AND_INT, INT_AND_TIME, NULL}; - /** Conversion categories that need to be considered by {@link #union}. */ - private static final ConversionCategory[] conversionCategoriesForUnion = - new ConversionCategory[] {NULL, CHAR_AND_INT, INT_AND_TIME, CHAR, INT, FLOAT, TIME}; + /** + * Returns the intersection of two categories. This is seldomly needed. + * + *
+ * + *
+   * ConversionCategory.intersect(INT, TIME) == INT_AND_TIME;
+   * 
+ * + *
+ * + * @param a a category + * @param b a category + * @return the intersection of the two categories (their greatest lower bound) + */ + public static ConversionCategory intersect(ConversionCategory a, ConversionCategory b) { + if (a == UNUSED) { + return b; + } + if (b == UNUSED) { + return a; + } + if (a == GENERAL) { + return b; + } + if (b == GENERAL) { + return a; + } - /** - * Returns the union of two categories. This is seldomly needed. - * - *
- * - *
-     * ConversionCategory.union(INT, TIME) == GENERAL;
-     * 
- * - *
- * - * @param a a category - * @param b a category - * @return the union of the two categories (their least upper bound) - */ - public static ConversionCategory union(ConversionCategory a, ConversionCategory b) { - if (a == UNUSED || b == UNUSED) { - return UNUSED; - } - if (a == GENERAL || b == GENERAL) { - return GENERAL; - } - if ((a == CHAR_AND_INT && b == INT_AND_TIME) || (a == INT_AND_TIME && b == CHAR_AND_INT)) { - // This is special-cased because the union of a.types and b.types - // does not include BigInteger.class, whereas the types for INT does. - // Returning INT here to prevent returning GENERAL below. - return INT; - } + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); + as.retainAll(bs); // intersection + for (ConversionCategory v : conversionCategoriesForIntersect) { + @SuppressWarnings( + "nullness:argument.type.incompatible" // `types` field is null only for UNUSED + // and GENERAL + ) + Set> vs = arrayToSet(v.types); + if (vs.equals(as)) { + return v; + } + } + throw new RuntimeException(); + } - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> as = arrayToSet(a.types); - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> bs = arrayToSet(b.types); - as.addAll(bs); // union - for (ConversionCategory v : conversionCategoriesForUnion) { - @SuppressWarnings( - "nullness:argument.type.incompatible" // `types` field is null only for UNUSED - // and GENERAL - ) - Set> vs = arrayToSet(v.types); - if (vs.equals(as)) { - return v; - } - } + /** Conversion categories that need to be considered by {@link #union}. */ + private static final ConversionCategory[] conversionCategoriesForUnion = + new ConversionCategory[] {NULL, CHAR_AND_INT, INT_AND_TIME, CHAR, INT, FLOAT, TIME}; - return GENERAL; + /** + * Returns the union of two categories. This is seldomly needed. + * + *
+ * + *
+   * ConversionCategory.union(INT, TIME) == GENERAL;
+   * 
+ * + *
+ * + * @param a a category + * @param b a category + * @return the union of the two categories (their least upper bound) + */ + public static ConversionCategory union(ConversionCategory a, ConversionCategory b) { + if (a == UNUSED || b == UNUSED) { + return UNUSED; + } + if (a == GENERAL || b == GENERAL) { + return GENERAL; + } + if ((a == CHAR_AND_INT && b == INT_AND_TIME) || (a == INT_AND_TIME && b == CHAR_AND_INT)) { + // This is special-cased because the union of a.types and b.types + // does not include BigInteger.class, whereas the types for INT does. + // Returning INT here to prevent returning GENERAL below. + return INT; } - /** - * Returns true if {@code argType} can be an argument used by this format specifier. - * - * @param argType an argument type - * @return true if {@code argType} can be an argument used by this format specifier - */ - public boolean isAssignableFrom(Class argType) { - if (types == null) { - return true; - } - if (argType == void.class) { - return true; - } - for (Class c : types) { - if (c.isAssignableFrom(argType)) { - return true; - } - } - return false; + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); + as.addAll(bs); // union + for (ConversionCategory v : conversionCategoriesForUnion) { + @SuppressWarnings( + "nullness:argument.type.incompatible" // `types` field is null only for UNUSED + // and GENERAL + ) + Set> vs = arrayToSet(v.types); + if (vs.equals(as)) { + return v; + } } - /** Returns a pretty printed {@link ConversionCategory}. */ - @Pure - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(name()); - sb.append(" conversion category"); + return GENERAL; + } - if (types == null || types.length == 0) { - return sb.toString(); - } + /** + * Returns true if {@code argType} can be an argument used by this format specifier. + * + * @param argType an argument type + * @return true if {@code argType} can be an argument used by this format specifier + */ + public boolean isAssignableFrom(Class argType) { + if (types == null) { + return true; + } + if (argType == void.class) { + return true; + } + for (Class c : types) { + if (c.isAssignableFrom(argType)) { + return true; + } + } + return false; + } - StringJoiner sj = new StringJoiner(", ", "(one of: ", ")"); - for (Class cls : types) { - sj.add(cls.getSimpleName()); - } - sb.append(" "); - sb.append(sj); + /** Returns a pretty printed {@link ConversionCategory}. */ + @Pure + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(name()); + sb.append(" conversion category"); - return sb.toString(); + if (types == null || types.length == 0) { + return sb.toString(); } + + StringJoiner sj = new StringJoiner(", ", "(one of: ", ")"); + for (Class cls : types) { + sj.add(cls.getSimpleName()); + } + sb.append(" "); + sb.append(sj); + + return sb.toString(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java index d8f1d9a2b8a..878fce1ee22 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.formatter.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a String type, indicates that the String may be passed to {@link @@ -38,12 +37,12 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownFormat.class) public @interface Format { - /** - * An array of {@link ConversionCategory}, indicating the types of legal remaining arguments - * when a value of the annotated type is used as the first argument to {@link - * java.util.Formatter#format(String, Object...) Formatter.format} and similar methods. - * - * @return types that can be used as values when a value of this type is the format string - */ - ConversionCategory[] value(); + /** + * An array of {@link ConversionCategory}, indicating the types of legal remaining arguments when + * a value of the annotated type is used as the first argument to {@link + * java.util.Formatter#format(String, Object...) Formatter.format} and similar methods. + * + * @return types that can be used as values when a value of this type is the format string + */ + ConversionCategory[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java index 9438cf76820..e6cfffcccff 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.formatter.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Format String type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java index 22c2ace3992..d6597eec381 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.formatter.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a {@link java.lang.String String} type, indicates that the string is @@ -21,10 +20,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownFormat.class) public @interface InvalidFormat { - /** - * Using a value of the annotated type as the first argument to {@link - * java.util.Formatter#format(String, Object...) Formatter.format} or similar methods will lead - * to this exception message. - */ - String value(); + /** + * Using a value of the annotated type as the first argument to {@link + * java.util.Formatter#format(String, Object...) Formatter.format} or similar methods will lead to + * this exception message. + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java index e0512950429..ccddb774a9b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.formatter.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The top qualifier. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java index d768a5bf921..07308112f71 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.guieffect.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * Annotation to override the UI effect on a class, and make a field or method safe for non-UI code diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java index 9ad0b5878c7..7586919ef90 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.guieffect.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Annotation for the polymorphic-UI effect. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java index fcf5682c66a..615b4daa42e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.guieffect.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Annotation for the UI effect. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java index da2d8c06b48..ce74615098a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.i18n.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} is a key into a property file or resource bundle containing diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java index f0727f5b9eb..ce55bf59ea4 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.i18n.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Internationalization type system. Programmers should rarely write this diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java index 6d24a80559f..663cb07dfbc 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.i18n.qual; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has been localized and formatted for the target output @@ -21,12 +20,12 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownLocalized.class) @QualifierForLiterals({ - // All literals except chars and strings, which may need to be localized. - // (null is bottom by default.) - LiteralKind.INT, - LiteralKind.LONG, - LiteralKind.FLOAT, - LiteralKind.DOUBLE, - LiteralKind.BOOLEAN + // All literals except chars and strings, which may need to be localized. + // (null is bottom by default.) + LiteralKind.INT, + LiteralKind.LONG, + LiteralKind.FLOAT, + LiteralKind.DOUBLE, + LiteralKind.BOOLEAN }) public @interface Localized {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java index 1188f3aeac2..71a8448dccf 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.i18n.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has an unknown localizable key property. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java index daccba594d7..2bb09803885 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.i18n.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has unknown localization properties. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java index afeae5e1280..09e17e4b264 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.i18nformatter.qual; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.StringJoiner; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; /** * Elements of this enumeration are used in a {@link I18nFormat} annotation to indicate the valid @@ -25,190 +24,189 @@ @AnnotatedFor("nullness") public enum I18nConversionCategory { - /** - * Use if a parameter is not used by the formatter. For example, in - * - *
-     * MessageFormat.format("{1}", a, b);
-     * 
- * - * only the second argument ("b") is used. The first argument ("a") is ignored. - */ - UNUSED(null /* everything */, null), - - /** Use if the parameter can be of any type. */ - GENERAL(null /* everything */, null), - - /** Use if the parameter can be of date, time, or number types. */ - DATE(new Class[] {Date.class, Number.class}, new String[] {"date", "time"}), - - /** - * Use if the parameter can be of number or choice types. An example of choice: - * - *
{@code
-     * format("{0, choice, 0#zero|1#one|1<{0, number} is more than 1}", 2)
-     * }
- * - * This will print "2 is more than 1". - */ - NUMBER(new Class[] {Number.class}, new String[] {"number", "choice"}); - - @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final Class @Nullable [] types; - - @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final String @Nullable [] strings; - - I18nConversionCategory(Class @Nullable [] types, String @Nullable [] strings) { - this.types = types; - this.strings = strings; + /** + * Use if a parameter is not used by the formatter. For example, in + * + *
+   * MessageFormat.format("{1}", a, b);
+   * 
+ * + * only the second argument ("b") is used. The first argument ("a") is ignored. + */ + UNUSED(null /* everything */, null), + + /** Use if the parameter can be of any type. */ + GENERAL(null /* everything */, null), + + /** Use if the parameter can be of date, time, or number types. */ + DATE(new Class[] {Date.class, Number.class}, new String[] {"date", "time"}), + + /** + * Use if the parameter can be of number or choice types. An example of choice: + * + *
{@code
+   * format("{0, choice, 0#zero|1#one|1<{0, number} is more than 1}", 2)
+   * }
+ * + * This will print "2 is more than 1". + */ + NUMBER(new Class[] {Number.class}, new String[] {"number", "choice"}); + + @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! + public final Class @Nullable [] types; + + @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! + public final String @Nullable [] strings; + + I18nConversionCategory(Class @Nullable [] types, String @Nullable [] strings) { + this.types = types; + this.strings = strings; + } + + /** Used by {@link #stringToI18nConversionCategory}. */ + private static final I18nConversionCategory[] namedCategories = + new I18nConversionCategory[] {DATE, NUMBER}; + + /** + * Creates a conversion cagetogry from a string name. + * + *
+   * I18nConversionCategory.stringToI18nConversionCategory("number") == I18nConversionCategory.NUMBER;
+   * 
+ * + * @return the I18nConversionCategory associated with the given string + */ + @SuppressWarnings( + "nullness:iterating.over.nullable") // in namedCategories, `strings` field is non-null + public static I18nConversionCategory stringToI18nConversionCategory(String string) { + string = string.toLowerCase(); + for (I18nConversionCategory v : namedCategories) { + for (String s : v.strings) { + if (s.equals(string)) { + return v; + } + } + } + throw new IllegalArgumentException("Invalid format type " + string); + } + + private static Set arrayToSet(E[] a) { + return new HashSet<>(Arrays.asList(a)); + } + + /** + * Return true if a is a subset of b. + * + * @return true if a is a subset of b + */ + public static boolean isSubsetOf(I18nConversionCategory a, I18nConversionCategory b) { + return intersect(a, b) == a; + } + + /** Conversion categories that need to be considered by {@link #intersect}. */ + private static final I18nConversionCategory[] conversionCategoriesForIntersect = + new I18nConversionCategory[] {DATE, NUMBER}; + + /** + * Returns the intersection of the two given I18nConversionCategories. + * + *
+ * + *
+   * I18nConversionCategory.intersect(DATE, NUMBER) == NUMBER;
+   * 
+ * + *
+ */ + public static I18nConversionCategory intersect( + I18nConversionCategory a, I18nConversionCategory b) { + if (a == UNUSED) { + return b; + } + if (b == UNUSED) { + return a; + } + if (a == GENERAL) { + return b; + } + if (b == GENERAL) { + return a; } - /** Used by {@link #stringToI18nConversionCategory}. */ - private static final I18nConversionCategory[] namedCategories = - new I18nConversionCategory[] {DATE, NUMBER}; - - /** - * Creates a conversion cagetogry from a string name. - * - *
-     * I18nConversionCategory.stringToI18nConversionCategory("number") == I18nConversionCategory.NUMBER;
-     * 
- * - * @return the I18nConversionCategory associated with the given string - */ @SuppressWarnings( - "nullness:iterating.over.nullable") // in namedCategories, `strings` field is non-null - public static I18nConversionCategory stringToI18nConversionCategory(String string) { - string = string.toLowerCase(); - for (I18nConversionCategory v : namedCategories) { - for (String s : v.strings) { - if (s.equals(string)) { - return v; - } - } - } - throw new IllegalArgumentException("Invalid format type " + string); + "nullness:argument.type.incompatible") // types field is only null in UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // types field is only null in UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); + as.retainAll(bs); // intersection + for (I18nConversionCategory v : conversionCategoriesForIntersect) { + @SuppressWarnings("nullness:argument.type.incompatible") // in those values, `types` field is + // non-null + Set> vs = arrayToSet(v.types); + if (vs.equals(as)) { + return v; + } } - - private static Set arrayToSet(E[] a) { - return new HashSet<>(Arrays.asList(a)); + throw new RuntimeException(); + } + + /** + * Returns the union of the two given I18nConversionCategories. + * + *
+   * I18nConversionCategory.intersect(DATE, NUMBER) == DATE;
+   * 
+ */ + public static I18nConversionCategory union(I18nConversionCategory a, I18nConversionCategory b) { + if (a == UNUSED || b == UNUSED) { + return UNUSED; } - - /** - * Return true if a is a subset of b. - * - * @return true if a is a subset of b - */ - public static boolean isSubsetOf(I18nConversionCategory a, I18nConversionCategory b) { - return intersect(a, b) == a; + if (a == GENERAL || b == GENERAL) { + return GENERAL; } - - /** Conversion categories that need to be considered by {@link #intersect}. */ - private static final I18nConversionCategory[] conversionCategoriesForIntersect = - new I18nConversionCategory[] {DATE, NUMBER}; - - /** - * Returns the intersection of the two given I18nConversionCategories. - * - *
- * - *
-     * I18nConversionCategory.intersect(DATE, NUMBER) == NUMBER;
-     * 
- * - *
- */ - public static I18nConversionCategory intersect( - I18nConversionCategory a, I18nConversionCategory b) { - if (a == UNUSED) { - return b; - } - if (b == UNUSED) { - return a; - } - if (a == GENERAL) { - return b; - } - if (b == GENERAL) { - return a; - } - - @SuppressWarnings( - "nullness:argument.type.incompatible") // types field is only null in UNUSED and - // GENERAL - Set> as = arrayToSet(a.types); - @SuppressWarnings( - "nullness:argument.type.incompatible") // types field is only null in UNUSED and - // GENERAL - Set> bs = arrayToSet(b.types); - as.retainAll(bs); // intersection - for (I18nConversionCategory v : conversionCategoriesForIntersect) { - @SuppressWarnings( - "nullness:argument.type.incompatible") // in those values, `types` field is - // non-null - Set> vs = arrayToSet(v.types); - if (vs.equals(as)) { - return v; - } - } - throw new RuntimeException(); + if (a == DATE || b == DATE) { + return DATE; } - - /** - * Returns the union of the two given I18nConversionCategories. - * - *
-     * I18nConversionCategory.intersect(DATE, NUMBER) == DATE;
-     * 
- */ - public static I18nConversionCategory union(I18nConversionCategory a, I18nConversionCategory b) { - if (a == UNUSED || b == UNUSED) { - return UNUSED; - } - if (a == GENERAL || b == GENERAL) { - return GENERAL; - } - if (a == DATE || b == DATE) { - return DATE; - } - return NUMBER; + return NUMBER; + } + + /** + * Returns true if {@code argType} can be an argument used by this format specifier. + * + * @param argType an argument type + * @return true if {@code argType} can be an argument used by this format specifier + */ + public boolean isAssignableFrom(Class argType) { + if (types == null) { + return true; } - - /** - * Returns true if {@code argType} can be an argument used by this format specifier. - * - * @param argType an argument type - * @return true if {@code argType} can be an argument used by this format specifier - */ - public boolean isAssignableFrom(Class argType) { - if (types == null) { - return true; - } - if (argType == void.class) { - return true; - } - for (Class c : types) { - if (c.isAssignableFrom(argType)) { - return true; - } - } - return false; + if (argType == void.class) { + return true; } - - /** Returns a pretty printed {@link I18nConversionCategory}. */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder(this.name()); - if (this.types == null) { - sb.append(" conversion category (all types)"); - } else { - StringJoiner sj = new StringJoiner(", ", " conversion category (one of: ", ")"); - for (Class cls : this.types) { - sj.add(cls.getCanonicalName()); - } - sb.append(sj); - } - return sb.toString(); + for (Class c : types) { + if (c.isAssignableFrom(argType)) { + return true; + } + } + return false; + } + + /** Returns a pretty printed {@link I18nConversionCategory}. */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(this.name()); + if (this.types == null) { + sb.append(" conversion category (all types)"); + } else { + StringJoiner sj = new StringJoiner(", ", " conversion category (one of: ", ")"); + for (Class cls : this.types) { + sj.add(cls.getCanonicalName()); + } + sb.append(sj); } + return sb.toString(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java index 4ac4df7b653..5e29edcc2d9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.i18nformatter.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a String type, indicates that the String may be passed to {@link @@ -34,10 +33,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(I18nUnknownFormat.class) public @interface I18nFormat { - /** - * An array of {@link I18nConversionCategory}, indicating the types of legal remaining arguments - * when a value of the annotated type is used as the first argument to {@link - * java.text.MessageFormat#format(String, Object...) Message.format}. - */ - I18nConversionCategory[] value(); + /** + * An array of {@link I18nConversionCategory}, indicating the types of legal remaining arguments + * when a value of the annotated type is used as the first argument to {@link + * java.text.MessageFormat#format(String, Object...) Message.format}. + */ + I18nConversionCategory[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java index ff45b277970..175d8502743 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.i18nformatter.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Internationalization Format String type system. Programmers should rarely diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java index 38d4aa8d05c..a0e30d6a74b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.i18nformatter.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation indicates that when a string of the annotated type is passed as the first @@ -32,13 +31,13 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(I18nUnknownFormat.class) public @interface I18nFormatFor { - /** - * Indicates which formal parameter is the arguments to the format method. The value should be - * {@code #} followed by the 1-based index of the formal parameter that is the arguments to the - * format method, e.g., {@code "#2"}. - * - * @return {@code #} followed by the 1-based index of the formal parameter that is the arguments - * to the format method - */ - String value(); + /** + * Indicates which formal parameter is the arguments to the format method. The value should be + * {@code #} followed by the 1-based index of the formal parameter that is the arguments to the + * format method, e.g., {@code "#2"}. + * + * @return {@code #} followed by the 1-based index of the formal parameter that is the arguments + * to the format method + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java index 4515d25e127..379082d48f8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.i18nformatter.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a {@link java.lang.String String} type, indicates that if the String @@ -19,9 +18,9 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(I18nUnknownFormat.class) public @interface I18nInvalidFormat { - /** - * Using a value of the annotated type as the first argument to {@link - * java.text.MessageFormat#format(String, Object...)} will lead to this exception message. - */ - String value(); + /** + * Using a value of the annotated type as the first argument to {@link + * java.text.MessageFormat#format(String, Object...)} will lead to this exception message. + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java index 66b587b2653..85b339c07f6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.i18nformatter.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The top qualifier. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java index abc186b8321..1a34f1d1a34 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the value expressions evaluate to an integer whose value is less than the lengths @@ -57,51 +56,51 @@ @InheritedAnnotation @Repeatable(EnsuresLTLengthOf.List.class) public @interface EnsuresLTLengthOf { - /** - * The Java expressions that are less than the length of the given sequences on successful - * method termination. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - String[] value(); + /** + * The Java expressions that are less than the length of the given sequences on successful method + * termination. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + String[] value(); - /** - * Sequences, each of which is longer than the each of the expressions' value on successful - * method termination. - */ - @JavaExpression - @QualifierArgument("value") - String[] targetValue(); + /** + * Sequences, each of which is longer than the each of the expressions' value on successful method + * termination. + */ + @JavaExpression + @QualifierArgument("value") + String[] targetValue(); - /** - * This expression plus each of the value expressions is less than the length of the sequence on - * successful method termination. The {@code offset} element must ether be empty or the same - * length as {@code targetValue}. - * - * @return the offset expressions - */ - @JavaExpression - @QualifierArgument("offset") - String[] offset() default {}; + /** + * This expression plus each of the value expressions is less than the length of the sequence on + * successful method termination. The {@code offset} element must ether be empty or the same + * length as {@code targetValue}. + * + * @return the offset expressions + */ + @JavaExpression + @QualifierArgument("offset") + String[] offset() default {}; + /** + * A wrapper annotation that makes the {@link EnsuresLTLengthOf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLTLengthOf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = LTLengthOf.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresLTLengthOf} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLTLengthOf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = LTLengthOf.class) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresLTLengthOf[] value(); - } + EnsuresLTLengthOf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java index b0630a0e2f3..26872a6dc95 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the given expressions evaluate to an integer whose value is less than the lengths @@ -59,59 +58,59 @@ @InheritedAnnotation @Repeatable(EnsuresLTLengthOfIf.List.class) public @interface EnsuresLTLengthOfIf { - /** - * The return value of the method that needs to hold for the postcondition to hold. - * - * @return the return value of the method that needs to hold for the postcondition to hold - */ - boolean result(); + /** + * The return value of the method that needs to hold for the postcondition to hold. + * + * @return the return value of the method that needs to hold for the postcondition to hold + */ + boolean result(); - /** - * Java expression(s) that are less than the length of the given sequences after the method - * returns the given result. - * - * @return Java expression(s) that are less than the length of the given sequences after the - * method returns the given result - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Java expression(s) that are less than the length of the given sequences after the method + * returns the given result. + * + * @return Java expression(s) that are less than the length of the given sequences after the + * method returns the given result + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Sequences, each of which is longer than each of the expressions' value after the method - * returns the given result. - */ - @JavaExpression - @QualifierArgument("value") - String[] targetValue(); + /** + * Sequences, each of which is longer than each of the expressions' value after the method returns + * the given result. + */ + @JavaExpression + @QualifierArgument("value") + String[] targetValue(); - /** - * This expression plus each of the expressions is less than the length of the sequence after - * the method returns the given result. The {@code offset} element must ether be empty or the - * same length as {@code targetValue}. - * - * @return the offset expressions - */ - @JavaExpression - @QualifierArgument("offset") - String[] offset() default {}; + /** + * This expression plus each of the expressions is less than the length of the sequence after the + * method returns the given result. The {@code offset} element must ether be empty or the same + * length as {@code targetValue}. + * + * @return the offset expressions + */ + @JavaExpression + @QualifierArgument("offset") + String[] offset() default {}; + /** + * A wrapper annotation that makes the {@link EnsuresLTLengthOfIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLTLengthOfIf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresLTLengthOfIf} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLTLengthOfIf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresLTLengthOfIf[] value(); - } + EnsuresLTLengthOfIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java index 1d7ead64650..034dc325d92 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer greater than or equal to -1. ("GTE" stands for diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java index 85e265a1940..4ef51a04538 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; /** * The annotated sequence contains a subsequence that is equal to the value of some other @@ -61,15 +60,15 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface HasSubsequence { - /** An expression that evaluates to the subsequence. */ - @JavaExpression - String subsequence(); + /** An expression that evaluates to the subsequence. */ + @JavaExpression + String subsequence(); - /** The index into this where the subsequence starts. */ - @JavaExpression - String from(); + /** The index into this where the subsequence starts. */ + @JavaExpression + String from(); - /** The index into this, immediately past where the subsequence ends. */ - @JavaExpression - String to(); + /** The index into this, immediately past where the subsequence ends. */ + @JavaExpression + String to(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java index e08773c837e..4bfc213db19 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java @@ -36,6 +36,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface IndexFor { - /** Sequences that the annotated expression is a valid index for. */ - String[] value(); + /** Sequences that the annotated expression is a valid index for. */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java index c91497e94aa..c1f20a6dc81 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java @@ -33,8 +33,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface IndexOrHigh { - /** - * Sequences that the annotated expression is a valid index for or is equal to the lengeth of. - */ - String[] value(); + /** Sequences that the annotated expression is a valid index for or is equal to the lengeth of. */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java index e4cd74d2b6d..04a956d9b5a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java @@ -32,6 +32,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface IndexOrLow { - /** Sequences that the annotated expression is a valid index for (or it's -1). */ - String[] value(); + /** Sequences that the annotated expression is a valid index for (or it's -1). */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java index 80c0ce3f45e..1035cee7257 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose value is less than or equal to the lengths @@ -28,7 +27,7 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UpperBoundUnknown.class) public @interface LTEqLengthOf { - /** Sequences, each of which is at least as long as the annotated expression's value. */ - @JavaExpression - public String[] value(); + /** Sequences, each of which is at least as long as the annotated expression's value. */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java index d022ccd4938..770e19a788e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose value is less than the lengths of all the @@ -37,17 +36,17 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(LTEqLengthOf.class) public @interface LTLengthOf { - /** Sequences, each of which is longer than the annotated expression's value. */ - @JavaExpression - public String[] value(); + /** Sequences, each of which is longer than the annotated expression's value. */ + @JavaExpression + public String[] value(); - /** - * This expression plus the annotated expression is less than the length of the sequence. The - * {@code offset} element must ether be empty or the same length as {@code value}. - * - *

The expressions in {@code offset} may be addition/subtraction of any number of Java - * expressions. For example, {@code @LessThanLengthOf(value = "a", offset = "x + y + 2"}}. - */ - @JavaExpression - String[] offset() default {}; + /** + * This expression plus the annotated expression is less than the length of the sequence. The + * {@code offset} element must ether be empty or the same length as {@code value}. + * + *

The expressions in {@code offset} may be addition/subtraction of any number of Java + * expressions. For example, {@code @LessThanLengthOf(value = "a", offset = "x + y + 2"}}. + */ + @JavaExpression + String[] offset() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java index 2d361ad1694..43a545d2ec6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose value is at least 2 less than the lengths @@ -31,9 +30,9 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(LTLengthOf.class) public @interface LTOMLengthOf { - /** - * Sequences, each of whose lengths is at least 1 larger than the annotated expression's value. - */ - @JavaExpression - public String[] value(); + /** + * Sequences, each of whose lengths is at least 1 larger than the annotated expression's value. + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java index 643ca431281..53adb1af1d4 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java @@ -29,6 +29,6 @@ // read it. @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER, ElementType.METHOD}) public @interface LengthOf { - /** Sequences that the annotated expression is equal to the length of. */ - String[] value(); + /** Sequences that the annotated expression is equal to the length of. */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java index 46b87853015..dd608315c51 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the relationship between values with a byte, short, char, int, or long @@ -33,15 +32,15 @@ // false positives, the bigger value is final or effectively final, so it can appear in a dependent // annotation without causing soundness issues. public @interface LessThan { - /** - * The annotated expression's value is less than this expression. - * - *

The expressions in {@code value} may be addition/subtraction of any number of Java - * expressions. For example, {@code @LessThan(value = "x + y + 2"}}. - * - *

The expression in {@code value} must be final or constant or the addition/subtract of - * final or constant expressions. - */ - @JavaExpression - String[] value(); + /** + * The annotated expression's value is less than this expression. + * + *

The expressions in {@code value} may be addition/subtraction of any number of Java + * expressions. For example, {@code @LessThan(value = "x + y + 2"}}. + * + *

The expression in {@code value} must be final or constant or the addition/subtract of final + * or constant expressions. + */ + @JavaExpression + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java index 3eda6aea692..166ba63551a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The bottom type in the LessThan type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java index ca5b2f1a98f..75c847b83e9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * The top qualifier for the LessThan type hierarchy. It indicates that no other expression is known diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java index 4bff20599c3..37ec6f0e062 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type of the lower bound type system. A variable annotated with this value cannot take diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java index 4d61ceab2dd..9025c8efa06 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to value that might be -2 or lower. This is the top type for diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java index 254c3281f33..2fd92db77b1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression is between {@code -1} and {@code -a.length - 1}, inclusive, for each @@ -35,10 +34,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SearchIndexFor.class) public @interface NegativeIndexFor { - /** - * Sequences for which this value is a "negative index"; that is, the expression is in the range - * {@code -1} to {@code -a.length - 1}, inclusive, for each sequence {@code a} given here. - */ - @JavaExpression - public String[] value(); + /** + * Sequences for which this value is a "negative index"; that is, the expression is in the range + * {@code -1} to {@code -a.length - 1}, inclusive, for each sequence {@code a} given here. + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java index 95d4af7435a..40806f19a32 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer greater than or equal to 0. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java index fc121728825..fa5c811ee6c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Lower Bound and Upper Bound type systems. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java index 6f266072203..4d5123723a7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Syntactic sugar for both @PolyValue and @PolySameLen. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java index 4548013e210..824609a8f5e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Lower Bound type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java index 43c2518779c..89e7fc431af 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the SameLen type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java index 54d55085599..c8eaa92a5d2 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Upper Bound type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java index 14b40101de4..7321a024340 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer greater than or equal to 1. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java index a4ab7d9bb73..db65d592afb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * An expression whose type has this annotation evaluates to a value that is a sequence, and that @@ -21,7 +20,7 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SameLenUnknown.class) public @interface SameLen { - /** A list of other sequences with the same length. */ - @JavaExpression - String[] value(); + /** A list of other sequences with the same length. */ + @JavaExpression + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java index 5f4360c00d5..a46fb49a31d 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the SameLen type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java index 181811977e6..08b1616a466 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * This type represents any variable that isn't known to have the same length as another sequence. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java index 5b36dc568ba..92fc4921645 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Search Index type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java index 978318f07f8..832adae5c7e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose length is between {@code -a.length - 1} @@ -23,10 +22,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SearchIndexUnknown.class) public @interface SearchIndexFor { - /** - * Sequences for which the annotated expression has the type of the result of a call to {@link - * java.util.Arrays#binarySearch(Object[],Object) Arrays.binarySearch}. - */ - @JavaExpression - public String[] value(); + /** + * Sequences for which the annotated expression has the type of the result of a call to {@link + * java.util.Arrays#binarySearch(Object[],Object) Arrays.binarySearch}. + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java index 87b4913b2d0..065f800d56f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * The top type for the SearchIndex type system. This indicates that the Index checker does not know diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java index 2fe7a440d4c..158bd6e9354 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Substring Index type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java index 2ce25597311..235d0fad565 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to either -1 or a non-negative integer less than the lengths @@ -37,23 +36,23 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SubstringIndexUnknown.class) public @interface SubstringIndexFor { - /** - * Sequences, each of which is longer than the annotated expression plus the corresponding - * {@code offset}. (Exception: the annotated expression is also allowed to have the value -1.) - */ - @JavaExpression - public String[] value(); + /** + * Sequences, each of which is longer than the annotated expression plus the corresponding {@code + * offset}. (Exception: the annotated expression is also allowed to have the value -1.) + */ + @JavaExpression + public String[] value(); - /** - * This expression plus the annotated expression is less than the length of the corresponding - * sequence in the {@code value} array. (Exception: the annotated expression is also allowed to - * have the value -1.) - * - *

The {@code offset} array must either be empty or the same length as {@code value}. - * - *

The expressions in {@code offset} may be addition/subtraction of any number of Java - * expressions. For example, {@code @SubstringIndexFor(value = "a", offset = "b.length() - 1")}. - */ - @JavaExpression - public String[] offset(); + /** + * This expression plus the annotated expression is less than the length of the corresponding + * sequence in the {@code value} array. (Exception: the annotated expression is also allowed to + * have the value -1.) + * + *

The {@code offset} array must either be empty or the same length as {@code value}. + * + *

The expressions in {@code offset} may be addition/subtraction of any number of Java + * expressions. For example, {@code @SubstringIndexFor(value = "a", offset = "b.length() - 1")}. + */ + @JavaExpression + public String[] offset(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java index 81410980978..6cc1921eefc 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * The top type for the Substring Index type system. This indicates that the Index Checker does not diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java index 29b373fc5bb..df7f6b5a450 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Upper Bound type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java index 2f29d0d337c..60fe6e7cbb1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * A literal value. Programmers should rarely write this type. @@ -22,10 +21,10 @@ @SubtypeOf(LTEqLengthOf.class) public @interface UpperBoundLiteral { - /** - * Returns the value of the literal. - * - * @return the value of the literal - */ - int value(); + /** + * Returns the value of the literal. + * + * @return the value of the literal + */ + int value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java index e47fd024970..87dff96d2b1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.index.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * A variable not known to have a relation to any sequence length. This is the top type of the Upper diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java index 0f54368bbc6..9ae15aeb7cd 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.initialization.qual; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the initialization type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java index b3b3112b6b8..8c2834c485e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.initialization.qual; -import org.checkerframework.framework.qual.RelevantJavaTypes; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.RelevantJavaTypes; /** * A meta-annotation that indicates that the qualifier can be applied to the default value of every diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java index f13abf0f8c5..cac3fc896f1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.initialization.qual; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** * This type qualifier belongs to the freedom-before-commitment initialization tracking type-system. @@ -30,8 +29,8 @@ @SubtypeOf(UnknownInitialization.class) @DefaultQualifierInHierarchy @DefaultFor({ - TypeUseLocation.IMPLICIT_UPPER_BOUND, - TypeUseLocation.IMPLICIT_LOWER_BOUND, - TypeUseLocation.EXCEPTION_PARAMETER + TypeUseLocation.IMPLICIT_UPPER_BOUND, + TypeUseLocation.IMPLICIT_LOWER_BOUND, + TypeUseLocation.EXCEPTION_PARAMETER }) public @interface Initialized {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java index 529f322d8ad..d1ded65f7cb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.initialization.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the freedom-before-commitment initialization tracking type-system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java index cc9b92c5625..5f3c43e0068 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.initialization.qual; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.SubtypeOf; /** * This type qualifier indicates that an object is (definitely) in the process of being @@ -42,12 +41,12 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownInitialization.class) public @interface UnderInitialization { - /** - * The type-frame down to which the expression (of this type) has been initialized at least - * (inclusive). That is, an expression of type {@code @UnderInitialization(T.class)} has all - * type-frames initialized starting at {@code Object} down to (and including) {@code T}. - * - * @return the type whose fields are fully initialized - */ - Class value() default Object.class; + /** + * The type-frame down to which the expression (of this type) has been initialized at least + * (inclusive). That is, an expression of type {@code @UnderInitialization(T.class)} has all + * type-frames initialized starting at {@code Object} down to (and including) {@code T}. + * + * @return the type whose fields are fully initialized + */ + Class value() default Object.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java index 73d66e2c4ca..0924bcec7c7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.initialization.qual; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** * This type qualifier indicates how much of an object has been fully initialized. An object is @@ -51,12 +50,12 @@ @SubtypeOf({}) @DefaultFor({TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE}) public @interface UnknownInitialization { - /** - * The type-frame down to which the expression (of this type) has been initialized at least - * (inclusive). That is, an expression of type {@code @UnknownInitialization(T.class)} has all - * type-frames initialized starting at {@code Object} down to (and including) {@code T}. - * - * @return the type whose fields are fully initialized - */ - Class value() default Object.class; + /** + * The type-frame down to which the expression (of this type) has been initialized at least + * (inclusive). That is, an expression of type {@code @UnknownInitialization(T.class)} has all + * type-frames initialized starting at {@code Object} down to (and including) {@code T}. + * + * @return the type whose fields are fully initialized + */ + Class value() default Object.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java index 968c3e406bc..8fc5212de46 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.interning.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method declaration annotation that indicates a method has a specification like {@code diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java index 921bf22d2f8..727024e2759 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.interning.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method declaration annotation that indicates a method has a specification like {@code equals()}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java index a5d618a7b0a..9157a728860 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.interning.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method declaration annotation used to indicate that this method may be invoked on an uninterned diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java index c75877f3f12..876bd794e2a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.interning.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeKind; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeKind; /** * Indicates that a variable has been interned, i.e., that the variable refers to the canonical @@ -34,14 +33,14 @@ @SubtypeOf(UnknownInterned.class) @QualifierForLiterals({LiteralKind.PRIMITIVE, LiteralKind.STRING}) // everything but NULL @DefaultFor( - typeKinds = { - TypeKind.BOOLEAN, - TypeKind.BYTE, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.INT, - TypeKind.LONG, - TypeKind.SHORT - }) + typeKinds = { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.SHORT + }) public @interface Interned {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java index ad2dd9516db..c1e7fb54a8c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.interning.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** * Indicates that no other value is {@code equals()} to the given value. Therefore, it is correct to diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java index aaa1be3c2dd..fb657e40706 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.interning.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Interning type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java index f0801cd68e1..c1a8b79cf09 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.interning.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * The top qualifier for the Interning Checker. It indicates lack of knowledge about whether values diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java index 8b76dfd8562..bd2d06e2dd9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; /** * Indicates that the given expressions are held if the method terminates successfully. @@ -24,34 +23,34 @@ @InheritedAnnotation @Repeatable(EnsuresLockHeld.List.class) public @interface EnsuresLockHeld { - /** - * Returns Java expressions whose values are locks that are held after successful method - * termination. - * - * @return Java expressions whose values are locks that are held after successful method - * termination - * @see Syntax of - * Java expressions - */ - String[] value(); + /** + * Returns Java expressions whose values are locks that are held after successful method + * termination. + * + * @return Java expressions whose values are locks that are held after successful method + * termination + * @see Syntax of Java + * expressions + */ + String[] value(); + /** + * A wrapper annotation that makes the {@link EnsuresLockHeld} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLockHeld} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = LockHeld.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresLockHeld} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLockHeld} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = LockHeld.class) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresLockHeld[] value(); - } + EnsuresLockHeld[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java index 2e4ca1c556a..d8e42a64a82 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Indicates that the given expressions are held if the method terminates successfully and returns @@ -25,44 +24,44 @@ @InheritedAnnotation @Repeatable(EnsuresLockHeldIf.List.class) public @interface EnsuresLockHeldIf { - /** - * Returns the return value of the method under which the postconditions hold. - * - * @return the return value of the method under which the postconditions hold - */ - boolean result(); + /** + * Returns the return value of the method under which the postconditions hold. + * + * @return the return value of the method under which the postconditions hold + */ + boolean result(); - /** - * Returns Java expressions whose values are locks that are held after the method returns the - * given result. - * - * @return Java expressions whose values are locks that are held after the method returns the - * given result - * @see Syntax of - * Java expressions - */ - // It would be clearer for users if this field were named "lock". - // However, method ContractsFromMethod.getConditionalPostconditions in the CF implementation - // assumes that conditional postconditions have a field named "expression". - String[] expression(); + /** + * Returns Java expressions whose values are locks that are held after the method returns the + * given result. + * + * @return Java expressions whose values are locks that are held after the method returns the + * given result + * @see Syntax of Java + * expressions + */ + // It would be clearer for users if this field were named "lock". + // However, method ContractsFromMethod.getConditionalPostconditions in the CF implementation + // assumes that conditional postconditions have a field named "expression". + String[] expression(); + /** + * A wrapper annotation that makes the {@link EnsuresLockHeldIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLockHeldIf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = LockHeld.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresLockHeldIf} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLockHeldIf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = LockHeld.class) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresLockHeldIf[] value(); - } + EnsuresLockHeldIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java index 248b373eb12..3914db10e5c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * If a variable {@code x} has type {@code @GuardSatisfied}, then all lock expressions for {@code @@ -34,20 +33,20 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) @TargetLocations({ - TypeUseLocation.RECEIVER, - TypeUseLocation.PARAMETER, - TypeUseLocation.RETURN, - TypeUseLocation.FIELD, - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.CONSTRUCTOR_RESULT + TypeUseLocation.RECEIVER, + TypeUseLocation.PARAMETER, + TypeUseLocation.RETURN, + TypeUseLocation.FIELD, + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.CONSTRUCTOR_RESULT }) @SubtypeOf(GuardedByUnknown.class) // TODO: Should @GuardSatisfied be in its own hierarchy? public @interface GuardSatisfied { - /** - * The index on the GuardSatisfied polymorphic qualifier, if any. Defaults to -1 so that, if the - * user writes 0, that is different than writing no index. Writing no index is the usual case. - * - * @return the index on the GuardSatisfied polymorphic qualifier, or -1 if none - */ - int value() default -1; + /** + * The index on the GuardSatisfied polymorphic qualifier, if any. Defaults to -1 so that, if the + * user writes 0, that is different than writing no index. Writing no index is the usual case. + * + * @return the index on the GuardSatisfied polymorphic qualifier, or -1 if none + */ + int value() default -1; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java index 9735d961fba..6cf02546997 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java @@ -1,5 +1,10 @@ package org.checkerframework.checker.lock.qual; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.JavaExpression; @@ -8,12 +13,6 @@ import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.qual.UpperBoundFor; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - /** * Indicates that a thread may dereference the value referred to by the annotated variable only if * the thread holds all the given lock expressions. @@ -43,37 +42,37 @@ // These are required because the default for local variables is @GuardedByUnknown, but if the local // variable is one of these type kinds, the default should be @GuardedByUnknown. @DefaultFor( - value = {TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.UPPER_BOUND}, - typeKinds = { - TypeKind.BOOLEAN, - TypeKind.BYTE, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.INT, - TypeKind.LONG, - TypeKind.SHORT - }, - types = {String.class, Void.class}) + value = {TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.UPPER_BOUND}, + typeKinds = { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.SHORT + }, + types = {String.class, Void.class}) @UpperBoundFor( - typeKinds = { - TypeKind.BOOLEAN, - TypeKind.BYTE, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.INT, - TypeKind.LONG, - TypeKind.SHORT - }, - types = String.class) + typeKinds = { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.SHORT + }, + types = String.class) public @interface GuardedBy { - /** - * The Java value expressions that need to be held. - * - * @see Syntax of - * Java expressions - */ - @JavaExpression - String[] value() default {}; + /** + * The Java value expressions that need to be held. + * + * @see Syntax of Java + * expressions + */ + @JavaExpression + String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java index 793eb8069c9..5c493fcb9a8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the GuardedBy type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java index e516921d9f6..08def367e70 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * It is unknown what locks guard the value referred to by the annotated variable. Therefore, the diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java index 090fe8be23b..0bbf64324fb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.PreconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PreconditionAnnotation; /** * Indicates a method precondition: the specified expressions must be held when the annotated method @@ -26,11 +25,11 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @PreconditionAnnotation(qualifier = LockHeld.class) public @interface Holding { - /** - * The Java expressions that need to be held. - * - * @see Syntax of - * Java expressions - */ - String[] value(); + /** + * The Java expressions that need to be held. + * + * @see Syntax of Java + * expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java index 4d8af9898f8..d77d8c0842f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that an expression is used as a lock and the lock is known to be held on the current diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java index 0cc89dae203..c2bb14d8b2b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java @@ -1,5 +1,9 @@ package org.checkerframework.checker.lock.qual; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; @@ -9,11 +13,6 @@ import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - /** * Indicates that an expression is not known to be {@link LockHeld}. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java index 5c8043426de..e9737ea034e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * The method neither acquires nor releases locks, nor do any of the methods that it calls. More diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java index be2247b9aff..80742faa252 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * The method, or one of the methods it calls, might release locks that were held prior to the diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java index 2fe65e1fac0..a9932ca8ef9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java @@ -1,5 +1,10 @@ package org.checkerframework.checker.lock.qual; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.LiteralKind; import org.checkerframework.framework.qual.QualifierForLiterals; @@ -7,12 +12,6 @@ import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - /** * A type that represents a newly-constructed object. It can be treated as having any * {@code @}{@link GuardedBy} type. Typically, it is only written on factory method return types. @@ -23,10 +22,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({ - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.CONSTRUCTOR_RESULT, - TypeUseLocation.RETURN + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.CONSTRUCTOR_RESULT, + TypeUseLocation.RETURN }) @SubtypeOf({GuardedBy.class, GuardSatisfied.class}) @DefaultFor(TypeUseLocation.CONSTRUCTOR_RESULT) diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java index f595ff969b6..dc43239f906 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.lock.qual; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * The method maintains a strictly nondecreasing lock held count on the current thread for any locks diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java index b1c71880a61..be464575c30 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.mustcall.qual; -import org.checkerframework.checker.calledmethods.qual.CalledMethods; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.calledmethods.qual.CalledMethods; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; /** * Indicates that the method resets the expression's must-call type to its declared type. This @@ -75,39 +74,39 @@ @Repeatable(CreatesMustCallFor.List.class) public @interface CreatesMustCallFor { - /** - * Returns the expression whose must-call type is reset after a call to a method with this - * annotation. The expression must be visible in the scope immediately before each call site, so - * it can only refer to fields, the method's parameters (which should be referenced via the "#X" - * syntax, where "#1" is the first argument, #2 is the second, etc.), or {@code "this"}. The - * default is {@code "this"}. At call-sites, the viewpoint-adapted referent of expression must - * be owning (an owning field, a local variable tracked in a resource alias set, etc.) or a - * {@code reset.not.owning} error is issued. - * - * @return the expression to which must-call obligations are added when the annotated method is - * invoked - */ - @JavaExpression - String value() default "this"; + /** + * Returns the expression whose must-call type is reset after a call to a method with this + * annotation. The expression must be visible in the scope immediately before each call site, so + * it can only refer to fields, the method's parameters (which should be referenced via the "#X" + * syntax, where "#1" is the first argument, #2 is the second, etc.), or {@code "this"}. The + * default is {@code "this"}. At call-sites, the viewpoint-adapted referent of expression must be + * owning (an owning field, a local variable tracked in a resource alias set, etc.) or a {@code + * reset.not.owning} error is issued. + * + * @return the expression to which must-call obligations are added when the annotated method is + * invoked + */ + @JavaExpression + String value() default "this"; + /** + * A wrapper annotation that makes the {@link CreatesMustCallFor} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link CreatesMustCallFor} annotation at the same location. + * + * @checker_framework.manual #must-call-checker Must Call Checker + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link CreatesMustCallFor} annotation repeatable. - * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link CreatesMustCallFor} annotation at the same location. + * Return the repeatable annotations. * - * @checker_framework.manual #must-call-checker Must Call Checker + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD}) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - CreatesMustCallFor[] value(); - } + CreatesMustCallFor[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java index 396205c7a4a..08839790d5f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java @@ -17,10 +17,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface InheritableMustCall { - /** - * Methods that might need to be called on the expression whose type is annotated. - * - * @return methods that might need to be called - */ - public String[] value() default {}; + /** + * Methods that might need to be called on the expression whose type is annotated. + * + * @return methods that might need to be called + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java index c9357960e9f..803d1bed320 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.mustcall.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** * An expression of type {@code @MustCall({"m1", "m2"})} may be obligated to call {@code m1()} @@ -30,10 +29,10 @@ @DefaultQualifierInHierarchy @DefaultFor({TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.UPPER_BOUND}) public @interface MustCall { - /** - * Methods that might need to be called on the expression whose type is annotated. - * - * @return methods that might need to be called - */ - public String[] value() default {}; + /** + * Methods that might need to be called on the expression whose type is annotated. + * + * @return methods that might need to be called + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java index 7f234fc1fac..727a839403b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java @@ -1,11 +1,10 @@ package org.checkerframework.checker.mustcall.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The top qualifier in the Must Call type hierarchy. It represents a type that might have an diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java index 3e76f1dfb7a..ff223c3f085 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.mustcall.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * The polymorphic qualifier for the Must Call type system. The semantics of this qualifier differ diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java index 93150087869..68408f19bc0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java @@ -40,10 +40,10 @@ @Target(ElementType.METHOD) public @interface AssertNonNullIfNonNull { - /** - * Java expression(s) that are non-null after the method returns a non-null value. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * Java expression(s) that are non-null after the method returns a non-null value. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java index 84b0200fba1..71af9a7a691 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the value expressions evaluate to a value that is a key in all the given maps, if @@ -37,42 +36,42 @@ @InheritedAnnotation @Repeatable(EnsuresKeyFor.List.class) public @interface EnsuresKeyFor { - /** - * Java expressions that are keys in the given maps on successful method termination. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * Java expressions that are keys in the given maps on successful method termination. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); - /** - * Returns Java expressions whose values are maps, each of which contains each expression value - * as a key (after successful method termination). - * - * @return Java expressions whose values are maps, each of which contains each expression value - * as a key (after successful method termination) - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - @QualifierArgument("value") - String[] map(); + /** + * Returns Java expressions whose values are maps, each of which contains each expression value as + * a key (after successful method termination). + * + * @return Java expressions whose values are maps, each of which contains each expression value as + * a key (after successful method termination) + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + @QualifierArgument("value") + String[] map(); + /** + * A wrapper annotation that makes the {@link EnsuresKeyFor} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresKeyFor} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = KeyFor.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresKeyFor} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresKeyFor} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = KeyFor.class) - @InheritedAnnotation - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresKeyFor[] value(); - } + EnsuresKeyFor[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java index 017b10c554f..c98351ecc76 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the given expressions evaluate to a value that is a key in all the given maps, if @@ -37,45 +36,45 @@ @InheritedAnnotation @Repeatable(EnsuresKeyForIf.List.class) public @interface EnsuresKeyForIf { - /** The value the method must return, in order for the postcondition to hold. */ - boolean result(); + /** The value the method must return, in order for the postcondition to hold. */ + boolean result(); - /** - * Java expressions that are keys in the given maps after the method returns the given result. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Java expressions that are keys in the given maps after the method returns the given result. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns Java expressions whose values are maps, each of which contains each expression value - * as a key (after the method returns the given result). - * - * @return Java expressions whose values are maps, each of which contains each expression value - * as a key (after the method returns the given result) - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - @QualifierArgument("value") - String[] map(); + /** + * Returns Java expressions whose values are maps, each of which contains each expression value as + * a key (after the method returns the given result). + * + * @return Java expressions whose values are maps, each of which contains each expression value as + * a key (after the method returns the given result) + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + @QualifierArgument("value") + String[] map(); + /** + * A wrapper annotation that makes the {@link EnsuresKeyForIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresKeyForIf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = KeyFor.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresKeyForIf} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresKeyForIf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = KeyFor.class) - @InheritedAnnotation - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresKeyForIf[] value(); - } + EnsuresKeyForIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java index 849c8d340cf..95f194b0550 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; // TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text: Every prefix expression is // also non-null; for example, {@code @EnsuresNonNull(expression="a.b.c")} implies that both {@code @@ -47,31 +46,31 @@ @InheritedAnnotation @Repeatable(EnsuresNonNull.List.class) public @interface EnsuresNonNull { - /** - * Returns Java expressions that are {@link NonNull} after successful method termination. - * - * @return Java expressions that are {@link NonNull} after successful method termination - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * Returns Java expressions that are {@link NonNull} after successful method termination. + * + * @return Java expressions that are {@link NonNull} after successful method termination + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); + /** + * A wrapper annotation that makes the {@link EnsuresNonNull} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresNonNull} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = NonNull.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresNonNull} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresNonNull} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = NonNull.class) - @InheritedAnnotation - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresNonNull[] value(); - } + EnsuresNonNull[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java index 464c6f570ed..d74189c154c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; // TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text: Every prefix expression is // also non-null; for example, {@code @EnsuresNonNullIf(expression="a.b.c", results=true)} implies @@ -75,38 +74,38 @@ @InheritedAnnotation @Repeatable(EnsuresNonNullIf.List.class) public @interface EnsuresNonNullIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns Java expression(s) that are non-null after the method returns the given result. - * - * @return Java expression(s) that are non-null after the method returns the given result - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns Java expression(s) that are non-null after the method returns the given result. + * + * @return Java expression(s) that are non-null after the method returns the given result + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); + /** + * * A wrapper annotation that makes the {@link EnsuresNonNullIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresNonNullIf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = NonNull.class) + @InheritedAnnotation + public static @interface List { /** - * * A wrapper annotation that makes the {@link EnsuresNonNullIf} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresNonNullIf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = NonNull.class) - @InheritedAnnotation - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresNonNullIf[] value(); - } + EnsuresNonNullIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java index cf0013289f9..396f7160a43 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the value assigned to the annotated variable is a key for at least the given @@ -42,11 +41,11 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownKeyFor.class) public @interface KeyFor { - /** - * Java expression(s) that evaluate to a map for which the annotated type is a key. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - public String[] value(); + /** + * Java expression(s) that evaluate to a map for which the annotated type is a key. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java index 5ed2010f703..e4120e21048 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Map Key type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java index 7fae2732ed3..8608c15fc15 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.MonotonicQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.MonotonicQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that once the field (or variable) becomes non-null, it never becomes null again. There diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java index 1eb704f0257..b522d663d0d 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java @@ -1,5 +1,10 @@ package org.checkerframework.checker.nullness.qual; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.LiteralKind; @@ -9,12 +14,6 @@ import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.qual.UpperBoundFor; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - /** * If an expression's type is qualified by {@code @NonNull}, then the expression never evaluates to * {@code null}. (Unless the program has a bug; annotations specify intended behavior.) @@ -43,15 +42,15 @@ @QualifierForLiterals(LiteralKind.STRING) @DefaultFor(TypeUseLocation.EXCEPTION_PARAMETER) @UpperBoundFor( - typeKinds = { - TypeKind.PACKAGE, - TypeKind.INT, - TypeKind.BOOLEAN, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.LONG, - TypeKind.SHORT, - TypeKind.BYTE - }) + typeKinds = { + TypeKind.PACKAGE, + TypeKind.INT, + TypeKind.BOOLEAN, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.LONG, + TypeKind.SHORT, + TypeKind.BYTE + }) public @interface NonNull {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java index 74a3bf01236..2333f2cb8ae 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; /** * {@link Nullable} is a type annotation that makes no commitments about whether the value is {@code diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java index c5dfc57550d..6a89836561b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Map Key (@KeyFor) type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java index bf42619c94b..35b10a0b18a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the non-null type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java index 29c5dc2b7f5..9dddbd1f365 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.nullness.qual; -import org.checkerframework.framework.qual.PreconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PreconditionAnnotation; // TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text: Every prefix expression must // also be non-null; for example, {@code @RequiresNonNull(expression="a.b.c")} implies that both @@ -66,32 +65,32 @@ @Repeatable(RequiresNonNull.List.class) @PreconditionAnnotation(qualifier = NonNull.class) public @interface RequiresNonNull { - /** - * The Java expressions that need to be {@link - * org.checkerframework.checker.nullness.qual.NonNull}. - * - * @return the Java expressions that need to be {@link - * org.checkerframework.checker.nullness.qual.NonNull} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * The Java expressions that need to be {@link + * org.checkerframework.checker.nullness.qual.NonNull}. + * + * @return the Java expressions that need to be {@link + * org.checkerframework.checker.nullness.qual.NonNull} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); + /** + * A wrapper annotation that makes the {@link RequiresNonNull} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresNonNull} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PreconditionAnnotation(qualifier = NonNull.class) + public static @interface List { /** - * A wrapper annotation that makes the {@link RequiresNonNull} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresNonNull} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PreconditionAnnotation(qualifier = NonNull.class) - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - RequiresNonNull[] value(); - } + RequiresNonNull[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java index 72fbdf4f484..19e423ca910 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java @@ -1,5 +1,10 @@ package org.checkerframework.checker.nullness.qual; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; @@ -8,12 +13,6 @@ import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - /** * Used internally by the type system; should never be written by a programmer. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java index a0037970bf8..841d4135e0e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; /** * Indicates that the expression evaluates to a non-empty Optional, if the method terminates @@ -41,10 +40,10 @@ @PostconditionAnnotation(qualifier = Present.class) @InheritedAnnotation public @interface EnsuresPresent { - /** - * The expression (of Optional type) that is present, if the method returns normally. - * - * @return the expression (of Optional type) that is present, if the method returns normally - */ - String[] value(); + /** + * The expression (of Optional type) that is present, if the method returns normally. + * + * @return the expression (of Optional type) that is present, if the method returns normally + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java index 0e4fa63f846..1d094ae66c4 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Indicates that the given expressions of type Optional<T> are present, if the method returns @@ -54,38 +53,38 @@ @ConditionalPostconditionAnnotation(qualifier = Present.class) @InheritedAnnotation public @interface EnsuresPresentIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns the Java expressions of type {@code Optional} that are present after the method - * returns the given result. - * - * @return the Java expressions of type {@code Optional} that are present after the method - * returns the given result - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions of type {@code Optional} that are present after the method + * returns the given result. + * + * @return the Java expressions of type {@code Optional} that are present after the method + * returns the given result + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); + /** + * A wrapper annotation that makes the {@link EnsuresPresentIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresPresentIf} annotation at the same location. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = Present.class) + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresPresentIf} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresPresentIf} annotation at the same location. + * @return the repeatable annotations */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = Present.class) - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresPresentIf[] value(); - } + EnsuresPresentIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java index 49455dbadf0..b37ac127ffd 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * The {@link java.util.Optional Optional} container may or may not contain a value. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java index 3205701f76b..755f071ec45 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The bottom type qualifier for the Optional Checker. The only value of this type is {@code null}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java index 327773dc1bd..1f7431d6ef5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** Method annotation for methods that create an {@link java.util.Optional}. */ @Documented diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java index 1e2fc09ad7f..c030c88af02 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method annotation for methods whose receiver is an {@link java.util.Optional} and return a diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java index 9a023868629..621c8eb32d7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method annotation for methods whose receiver is an {@link java.util.Optional} and return an diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java index d159dd3a6bb..e462ed7732a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Optional type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java index 90261fe72e6..90c59e2c849 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The {@link java.util.Optional Optional} container definitely contains a (non-null) value. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java index d6173ca97a7..d8754d3beac 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.qual; -import org.checkerframework.framework.qual.PreconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PreconditionAnnotation; /** * Indicates a method precondition: the specified expressions of type Optional must be present @@ -63,30 +62,30 @@ @PreconditionAnnotation(qualifier = Present.class) public @interface RequiresPresent { - /** - * The Java expressions that that need to be {@link Present}. - * - * @return the Java expressions that need to be {@link Present} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * The Java expressions that that need to be {@link Present}. + * + * @return the Java expressions that need to be {@link Present} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); + /** + * A wrapper annotation that makes the {@link RequiresPresent} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresPresent} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PreconditionAnnotation(qualifier = Present.class) + public static @interface List { /** - * A wrapper annotation that makes the {@link RequiresPresent} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresPresent} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PreconditionAnnotation(qualifier = Present.class) - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - RequiresPresent[] value(); - } + RequiresPresent[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java index ecb47f99bcd..712fc8f4b27 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.propkey.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type can be used as key in a property file or resource bundle. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java index e45d61a8ab8..fc6d6d71167 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.propkey.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the PropertyKeyChecker (and associated checkers) qualifier hierarchy. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java index 65f9516faaf..89a55a0fb36 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.propkey.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has an unknown property key property. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java index edfd6b591e2..2519b6749a9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.regex.qual; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates a String that is not a syntactically valid regular expression. The String itself can be @@ -25,9 +24,9 @@ @SubtypeOf(org.checkerframework.checker.regex.qual.UnknownRegex.class) public @interface PartialRegex { - /** - * The String qualified by this annotation. Used to verify concatenation of partial regular - * expressions. Defaults to the empty String. - */ - String value() default ""; + /** + * The String qualified by this annotation. Used to verify concatenation of partial regular + * expressions. Defaults to the empty String. + */ + String value() default ""; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java index 2ed68f54b64..631675cecea 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.regex.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Regex type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java index d7d253e0c1a..9f7e67446d6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.regex.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * If a type is annotated as {@code @Regex(n)}, then the run-time value is a regular expression with @@ -24,6 +23,6 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownRegex.class) public @interface Regex { - /** The number of groups in the regular expression. Defaults to 0. */ - int value() default 0; + /** The number of groups in the regular expression. Defaults to 0. */ + int value() default 0; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java index 301b6b74fc4..2ebb27b3bd1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.regex.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Regex type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java index 398b04b7398..63410c3da84 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java @@ -1,16 +1,15 @@ package org.checkerframework.checker.regex.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * Represents the top of the Regex qualifier hierarchy. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java index 3ec7a61428c..880dcc449ee 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.signature.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An identifier or primitive type, followed by any number of array square brackets. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java index d3d00e20f73..c178d9371ff 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.signature.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Represents a binary name as defined in the quantity(); + /** + * Returns the base unit to use. + * + * @return the base unit to use + */ + Class quantity(); - /** - * Returns the scaling prefix. - * - * @return the scaling prefix - */ - Prefix prefix() default Prefix.one; + /** + * Returns the scaling prefix. + * + * @return the scaling prefix + */ + Prefix prefix() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java index 8b4e787f8b9..c8c0756f109 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java @@ -15,15 +15,15 @@ @Documented @Retention(RetentionPolicy.RUNTIME) public @interface UnitsRelations { - /** - * Returns the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use. - * - * @return the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use - */ - // The more precise type is Class, - // but org.checkerframework.checker.units.UnitsRelations is not in checker-qual.jar, nor can - // it be since it uses AnnotatedTypeMirrors. So this declaration uses a less precise type, and - // UnitsAnnotatedTypeFactory checks that the argument implements - // org.checkerframework.checker.units.UnitsRelations. - Class value(); + /** + * Returns the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use. + * + * @return the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use + */ + // The more precise type is Class, + // but org.checkerframework.checker.units.UnitsRelations is not in checker-qual.jar, nor can + // it be since it uses AnnotatedTypeMirrors. So this declaration uses a less precise type, and + // UnitsAnnotatedTypeFactory checks that the argument implements + // org.checkerframework.checker.units.UnitsRelations. + Class value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java index 968115b2d30..70d98c07bc9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * UnknownUnits is the top type of the type hierarchy. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java index 5d9769a3a25..a387cc24d64 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Units of volume. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java index 91bc988a4cc..3952193bebb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Candela (unit of luminance). @@ -19,5 +18,5 @@ @SubtypeOf(Luminance.class) @SuppressWarnings("checkstyle:typename") public @interface cd { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java index 55bccafee67..a4d7ae62b67 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Degrees. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java index 711f7439497..54c0da10e4f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Gram. @@ -19,5 +18,5 @@ @SubtypeOf(Mass.class) @SuppressWarnings("checkstyle:typename") public @interface g { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java index 9260815e0cf..6a568a9fd5a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Hour. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java index 2294dd067b5..fc8e99b1791 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Kilonewton. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java index ab2160812ee..714cdfb2972 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Kilogram. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java index 5f200d1f553..c2cb32d87dd 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Kilometer. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java index 2e026b26ce0..590d0fe2584 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Square kilometer. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java index a4f36d27aa9..8dc4f86e8a7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Cubic kilometer. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java index 4fd6e24ec2e..28abf4bd1c0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Kilometer per hour. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java index 688f365dfd0..0b9da5c0e65 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Meter. @@ -24,5 +23,5 @@ // @UnitsMultiple(quantity=m.class, prefix=Prefix.one) @SuppressWarnings("checkstyle:typename") public @interface m { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java index eb33c261ce7..dc4473d006d 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Square meter. @@ -19,6 +18,6 @@ @SubtypeOf(Area.class) @SuppressWarnings("checkstyle:typename") public @interface m2 { - // does this make sense? Is it multiple of (m^2)? Or (multiple of m)^2? - Prefix value() default Prefix.one; + // does this make sense? Is it multiple of (m^2)? Or (multiple of m)^2? + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java index 7c88127862b..48de6bb09ac 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Cubic meter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java index 9c2c18dded5..b626b345699 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Meter per second. @@ -19,5 +18,5 @@ @SubtypeOf(Speed.class) @SuppressWarnings("checkstyle:typename") public @interface mPERs { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java index 42cf3bba135..71f24403327 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Meter per second squared. @@ -19,5 +18,5 @@ @SubtypeOf(Acceleration.class) @SuppressWarnings("checkstyle:typename") public @interface mPERs2 { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java index f1fa0c3e229..b729aa02c23 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Minute. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java index 86f55d88976..dff99618b66 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Millimeter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java index f51200b7459..2e5569887cb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Square millimeter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java index f670cf1da81..dc84f012cb8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Cubic millimeter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java index 06870b3b7d5..a1ea94e78e3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Mole (unit of {@link Substance}). @@ -19,5 +18,5 @@ @SubtypeOf(Substance.class) @SuppressWarnings("checkstyle:typename") public @interface mol { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java index 4af13288755..1493949865f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Radians. @@ -19,5 +18,5 @@ @SubtypeOf(Angle.class) @SuppressWarnings("checkstyle:typename") public @interface radians { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java index bda43dda3ba..89b93fafab5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * A second (1/60 of a minute). @@ -19,5 +18,5 @@ @SubtypeOf(Time.class) @SuppressWarnings("checkstyle:typename") public @interface s { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java index 845dae7bba1..ca254a060a1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.units.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Metric ton. diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java index 2fa75945acb..b93160bde9c 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java @@ -1,12 +1,11 @@ package org.checkerframework.common.aliasing.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation is used on a formal parameter to indicate that the parameter may be returned, but diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java index 409382e15e3..241fdcbd9d4 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java @@ -1,15 +1,14 @@ package org.checkerframework.common.aliasing.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** * An expression with this type might have an alias. In other words, some other expression, @@ -24,6 +23,6 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy @DefaultFor( - value = {TypeUseLocation.UPPER_BOUND, TypeUseLocation.LOWER_BOUND}, - types = Void.class) + value = {TypeUseLocation.UPPER_BOUND, TypeUseLocation.LOWER_BOUND}, + types = Void.class) public @interface MaybeAliased {} diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java index 0d421fc3242..9038b6b75b0 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java @@ -1,13 +1,12 @@ package org.checkerframework.common.aliasing.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * Temporary type qualifier: diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java index 2417f2e4275..ad0b07366d5 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java @@ -1,12 +1,11 @@ package org.checkerframework.common.aliasing.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation is used on a formal parameter to indicate that the parameter is not leaked diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java index af66458ffa7..61945114567 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java @@ -1,12 +1,11 @@ package org.checkerframework.common.aliasing.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type has no aliases. In other words, no other expression, evaluated at diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java index 264e931be79..11e768f2294 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java @@ -1,15 +1,14 @@ package org.checkerframework.common.initializedfields.qual; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; /** * A method postcondition annotation indicates which fields the method definitely initializes. @@ -23,38 +22,38 @@ @InheritedAnnotation @Repeatable(EnsuresInitializedFields.List.class) public @interface EnsuresInitializedFields { - /** - * The object whose fields this method initializes. - * - * @return object whose fields are initialized - */ - public String[] value() default {"this"}; + /** + * The object whose fields this method initializes. + * + * @return object whose fields are initialized + */ + public String[] value() default {"this"}; - /** - * Fields that this method initializes. - * - * @return fields that this method initializes - */ - @QualifierArgument("value") - public String[] fields(); + /** + * Fields that this method initializes. + * + * @return fields that this method initializes + */ + @QualifierArgument("value") + public String[] fields(); + /** + * A wrapper annotation that makes the {@link EnsuresInitializedFields} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresInitializedFields} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = InitializedFields.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresInitializedFields} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresInitializedFields} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = InitializedFields.class) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresInitializedFields[] value(); - } + EnsuresInitializedFields[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java index 18ea2e2693a..d83042cc0a0 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java @@ -1,12 +1,11 @@ package org.checkerframework.common.initializedfields.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates which fields have definitely been initialized. @@ -18,10 +17,10 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy public @interface InitializedFields { - /** - * Fields that have been initialized. - * - * @return the initialized fields - */ - public String[] value() default {}; + /** + * Fields that have been initialized. + * + * @return the initialized fields + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java index 628c199eca5..9eae97f3209 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java @@ -1,13 +1,12 @@ package org.checkerframework.common.initializedfields.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type qualifier for the Initialized Fields type system. It is the type of {@code null}. diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java index 6fc0b75789e..1f25542d80a 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java @@ -1,9 +1,8 @@ package org.checkerframework.common.initializedfields.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Polymorphic qualifier for the Initialized Fields type system. diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java index 8aa0aee983f..0cae3ff79e3 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java @@ -1,12 +1,11 @@ package org.checkerframework.common.reflection.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This represents a {@code Class} object whose run-time value is equal to or a subtype of one of @@ -19,9 +18,9 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({UnknownClass.class}) public @interface ClassBound { - /** - * The binary - * name of the class or classes that upper-bound the values of this Class object. - */ - String[] value(); + /** + * The binary + * name of the class or classes that upper-bound the values of this Class object. + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java index 3fb622077f0..424efe40bb8 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java @@ -1,12 +1,11 @@ package org.checkerframework.common.reflection.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This represents a {@link java.lang.Class Class<T>} object where the set of possible values @@ -20,13 +19,13 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({UnknownClass.class}) public @interface ClassVal { - /** - * The name of the type that this Class object represents. The name is a "fully-qualified binary - * name" ({@link org.checkerframework.checker.signature.qual.FqBinaryName}): a primitive or binary - * name, possibly followed by some number of array brackets. - * - * @return the name of the type that this Class object represents - */ - String[] value(); + /** + * The name of the type that this Class object represents. The name is a "fully-qualified binary + * name" ({@link org.checkerframework.checker.signature.qual.FqBinaryName}): a primitive or binary name, + * possibly followed by some number of array brackets. + * + * @return the name of the type that this Class object represents + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java index e68c8281686..7654cc078a5 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.common.reflection.qual; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the ClassVal type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java index 1a053e6a1a4..3c7612321b0 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java @@ -1,12 +1,11 @@ package org.checkerframework.common.reflection.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * This represents a set of {@link java.lang.reflect.Method Method} or {@link @@ -24,18 +23,17 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({UnknownMethod.class}) public @interface MethodVal { - /** - * The binary - * name of the class that declares this method. - */ - String[] className(); + /** + * The binary + * name of the class that declares this method. + */ + String[] className(); - /** - * The name of the method that this Method object represents. Use {@code } for - * constructors. - */ - String[] methodName(); + /** + * The name of the method that this Method object represents. Use {@code } for constructors. + */ + String[] methodName(); - /** The number of parameters to the method. */ - int[] params(); + /** The number of parameters to the method. */ + int[] params(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java index 3e39e6b04d2..d224db54345 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.common.reflection.qual; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the MethodVal type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java index ccea4c45929..beb5e1da192 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java @@ -1,16 +1,15 @@ package org.checkerframework.common.reflection.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * Represents a Class object whose run-time value is not known at compile time. Also represents diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java index bf556909632..95dc28348a0 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java @@ -1,16 +1,15 @@ package org.checkerframework.common.reflection.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * Represents a {@link java.lang.reflect.Method Method} or {@link java.lang.reflect.Constructor diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java index a3e0f92d8bf..1099c089933 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java +++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java @@ -1,13 +1,12 @@ package org.checkerframework.common.returnsreceiver.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type for the Returns Receiver Checker's type system. Programmers should rarely write diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java index 646aee27a90..4d30a59c72a 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java +++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java @@ -1,13 +1,12 @@ package org.checkerframework.common.returnsreceiver.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * Write {@code @This} on the return type of a method that always returns its receiver ({@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java index eafa025cdfd..7ada21419f1 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java +++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java @@ -1,5 +1,9 @@ package org.checkerframework.common.returnsreceiver.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; @@ -8,11 +12,6 @@ import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - /** * The top type for the Returns Receiver Checker's type system. Values of the annotated type might * be the receiver ({@code this}) or might not. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java index 93c521d8964..2283dbd7464 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java @@ -1,12 +1,11 @@ package org.checkerframework.common.subtyping.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * A special annotation intended solely for representing the bottom type in the qualifier hierarchy. diff --git a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java index ab339ca71ea..df08edb7b9b 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java +++ b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java @@ -1,12 +1,11 @@ package org.checkerframework.common.subtyping.qual; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * A special annotation intended solely for representing an unqualified type in the qualifier diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java index 8345f9bfa2c..d0bd11cf21b 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java +++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java @@ -1,14 +1,13 @@ package org.checkerframework.common.util.report.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation intended solely for representing an unqualified type in the qualifier hierarchy for diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java index 85c2f5b4d6b..20b19a15bd6 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the length of an array or a string. If an expression's type has this @@ -24,6 +23,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface ArrayLen { - /** The possible lengths of the array. */ - int[] value(); + /** The possible lengths of the array. */ + int[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java index 1dfd0d97e74..4a1d8baccaa 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type evaluates to an array or a string whose length is in the given @@ -20,17 +19,17 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf(UnknownVal.class) public @interface ArrayLenRange { - /** - * Smallest value in the range, inclusive. - * - * @return the smallest value in the range, inclusive - */ - int from() default 0; + /** + * Smallest value in the range, inclusive. + * + * @return the smallest value in the range, inclusive + */ + int from() default 0; - /** - * Largest value in the range, inclusive. - * - * @return the largest value in the range, inclusive - */ - int to() default Integer.MAX_VALUE; + /** + * Largest value in the range, inclusive. + * + * @return the largest value in the range, inclusive + */ + int to() default Integer.MAX_VALUE; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java index adbb4d2d06f..66f5a7fcbeb 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a bool type. If an expression's type has this @@ -19,6 +18,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface BoolVal { - /** The values that the expression might evaluate to. */ - boolean[] value(); + /** The values that the expression might evaluate to. */ + boolean[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java index 26b00d63827..83da15fb1da 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java @@ -1,15 +1,14 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Constant Value type system. Programmers should rarely write this type. @@ -22,18 +21,18 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.UPPER_BOUND, TypeUseLocation.LOWER_BOUND}) @SubtypeOf({ - ArrayLen.class, - BoolVal.class, - DoubleVal.class, - IntVal.class, - StringVal.class, - MatchesRegex.class, - DoesNotMatchRegex.class, - ArrayLenRange.class, - IntRange.class, - IntRangeFromPositive.class, - IntRangeFromGTENegativeOne.class, - IntRangeFromNonNegative.class + ArrayLen.class, + BoolVal.class, + DoubleVal.class, + IntVal.class, + StringVal.class, + MatchesRegex.class, + DoesNotMatchRegex.class, + ArrayLenRange.class, + IntRange.class, + IntRangeFromPositive.class, + IntRangeFromGTENegativeOne.class, + IntRangeFromNonNegative.class }) @InvisibleQualifier public @interface BottomVal {} diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java index 8fb1c2667e1..8fde439d8e7 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a String type. The annotation's arguments are @@ -24,10 +23,10 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface DoesNotMatchRegex { - /** - * A set of Java regular expressions. - * - * @return the regular expressions - */ - String[] value(); + /** + * A set of Java regular expressions. + * + * @return the regular expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java index 612ec4da291..1bfab7a21d7 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a double or float type. If an expression's type @@ -22,6 +21,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface DoubleVal { - /** The values that the expression might evaluate to. */ - double[] value(); + /** The values that the expression might evaluate to. */ + double[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java index 298b1ba3edd..afbcd1f1e4b 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java @@ -1,15 +1,14 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the value of the given expression is a sequence containing at least the given @@ -28,48 +27,48 @@ @InheritedAnnotation @Repeatable(EnsuresMinLenIf.List.class) public @interface EnsuresMinLenIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns Java expression(s) that are a sequence with the given minimum length after the method - * returns {@link #result}. - * - * @return an array of Java expression(s), each of which is a sequence with the given minimum - * length after the method returns {@link #result} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns Java expression(s) that are a sequence with the given minimum length after the method + * returns {@link #result}. + * + * @return an array of Java expression(s), each of which is a sequence with the given minimum + * length after the method returns {@link #result} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the minimum number of elements in the sequence. - * - * @return the minimum number of elements in the sequence - */ - @QualifierArgument("value") - int targetValue() default 0; + /** + * Returns the minimum number of elements in the sequence. + * + * @return the minimum number of elements in the sequence + */ + @QualifierArgument("value") + int targetValue() default 0; + /** + * A wrapper annotation that makes the {@link EnsuresMinLenIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresMinLenIf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = MinLen.class) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresMinLenIf} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresMinLenIf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = MinLen.class) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresMinLenIf[] value(); - } + EnsuresMinLenIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java index cfd60863271..c07b66938ca 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java @@ -20,11 +20,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface EnumVal { - /** - * The simple names of the possible enum values for an expression with the annotated type. - * - * @return the simple names of the possible enum values for an expression with the annotated - * type - */ - String[] value(); + /** + * The simple names of the possible enum values for an expression with the annotated type. + * + * @return the simple names of the possible enum values for an expression with the annotated type + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java index db16428bb02..42fd4b6c2e8 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type evaluates to an integral value (byte, short, char, int, or long) in @@ -27,17 +26,17 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf(UnknownVal.class) public @interface IntRange { - /** - * Smallest value in the range, inclusive. - * - * @return the smallest value in the range, inclusive - */ - long from() default Long.MIN_VALUE; + /** + * Smallest value in the range, inclusive. + * + * @return the smallest value in the range, inclusive + */ + long from() default Long.MIN_VALUE; - /** - * Largest value in the range, inclusive. - * - * @return the largest value in the range, inclusive - */ - long to() default Long.MAX_VALUE; + /** + * Largest value in the range, inclusive. + * + * @return the largest value in the range, inclusive + */ + long to() default Long.MAX_VALUE; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java index 231f9116087..fc65c4a9f20 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java @@ -1,11 +1,10 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java index 0d39d1bf601..434ff259de3 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java @@ -1,11 +1,10 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java index 38fc9f88a18..4941505975a 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java @@ -1,11 +1,10 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java index 6ee20087e74..3ab290f1e7d 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a byte, short, char, int, or long type. If an @@ -20,6 +19,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface IntVal { - /** The values that the expression might evaluate to. */ - long[] value(); + /** The values that the expression might evaluate to. */ + long[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java index 49fa1377a88..5c10b127c35 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a String type. The annotation's arguments are @@ -24,10 +23,10 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface MatchesRegex { - /** - * A set of Java regular expressions. - * - * @return the regular expressions - */ - String[] value(); + /** + * A set of Java regular expressions. + * + * @return the regular expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java index 63a5a04367b..a05c8431bbf 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java @@ -19,6 +19,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface MinLen { - /** The minimum number of elements in this sequence. */ - int value() default 0; + /** The minimum number of elements in this sequence. */ + int value() default 0; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java index ffe7d404fd0..bf120018cd3 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java @@ -1,13 +1,12 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.FieldInvariant; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.FieldInvariant; /** * A specialization of {@link FieldInvariant} for specifying the minimum length of an array. A class @@ -21,15 +20,15 @@ @Inherited public @interface MinLenFieldInvariant { - /** - * Min length of the array. Must be greater than the min length of the array as declared in the - * superclass. - */ - int[] minLen(); + /** + * Min length of the array. Must be greater than the min length of the array as declared in the + * superclass. + */ + int[] minLen(); - /** - * The field that has an array length qualifier in the class on which the field invariant is - * written. The field must be final and declared in a superclass. - */ - String[] field(); + /** + * The field that has an array length qualifier in the class on which the field invariant is + * written. The field must be final and declared in a superclass. + */ + String[] field(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java index 99e6ddec353..8ae998b9b7f 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Constant Value Checker. diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java index 95472e9746f..6f0ccb4dd77 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java @@ -1,12 +1,11 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a String type. If an expression's type has this @@ -19,6 +18,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface StringVal { - /** The values that the expression might evaluate to. */ - String[] value(); + /** The values that the expression might evaluate to. */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java index fdbd1ff373c..1ae28c0f75b 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java @@ -1,14 +1,13 @@ package org.checkerframework.common.value.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; /** * UnknownVal is a type annotation indicating that the expression's value is not known at compile diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java index 5622d6de0f2..edc3948313a 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java @@ -38,32 +38,32 @@ @Target(ElementType.METHOD) public @interface AssertMethod { - /** - * The class of the exception thrown by this method. The default is {@link AssertionError}. - * - * @return class of the exception thrown by this method - */ - Class value() default AssertionError.class; + /** + * The class of the exception thrown by this method. The default is {@link AssertionError}. + * + * @return class of the exception thrown by this method + */ + Class value() default AssertionError.class; - /** - * The one-based index of the boolean parameter that is tested. - * - * @return the one-based index of the boolean parameter that is tested - */ - int parameter() default 1; + /** + * The one-based index of the boolean parameter that is tested. + * + * @return the one-based index of the boolean parameter that is tested + */ + int parameter() default 1; - /** - * Returns whether this method asserts that the boolean expression is false. - * - *

For example, JUnit's Assertions.assertFalse(...) - * throws an exception if the first argument is false. So it is annotated as follows: - * - *

@AssertMethod(value = AssertionFailedError.class, isAssertFalse = true)
-     * public static void assertFalse(boolean condition);
-     * 
- * - * @return the value for {@link #parameter} on which the method throws an exception - */ - boolean isAssertFalse() default false; + /** + * Returns whether this method asserts that the boolean expression is false. + * + *

For example, JUnit's Assertions.assertFalse(...) + * throws an exception if the first argument is false. So it is annotated as follows: + * + *

@AssertMethod(value = AssertionFailedError.class, isAssertFalse = true)
+   * public static void assertFalse(boolean condition);
+   * 
+ * + * @return the value for {@link #parameter} on which the method throws an exception + */ + boolean isAssertFalse() default false; } diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java index 1b57e655fd9..9f891090870 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java @@ -26,12 +26,12 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface Pure { - /** The type of purity. */ - enum Kind { - /** The method has no visible side effects. */ - SIDE_EFFECT_FREE, + /** The type of purity. */ + enum Kind { + /** The method has no visible side effects. */ + SIDE_EFFECT_FREE, - /** The method returns exactly the same value when called in the same environment. */ - DETERMINISTIC - } + /** The method returns exactly the same value when called in the same environment. */ + DETERMINISTIC + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java index 48c4f7ab47f..8c2844ee992 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java @@ -29,14 +29,14 @@ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PACKAGE}) public @interface AnnotatedFor { - /** - * Returns the type systems for which the class has been annotated. Legal arguments are any - * string that may be passed to the {@code -processor} command-line argument: the - * fully-qualified class name for the checker, or a shorthand for built-in checkers. Using the - * annotation with no arguments, as in {@code @AnnotatedFor({})}, has no effect. - * - * @return the type systems for which the class has been annotated - * @checker_framework.manual #shorthand-for-checkers Short names for built-in checkers - */ - String[] value(); + /** + * Returns the type systems for which the class has been annotated. Legal arguments are any string + * that may be passed to the {@code -processor} command-line argument: the fully-qualified class + * name for the checker, or a shorthand for built-in checkers. Using the annotation with no + * arguments, as in {@code @AnnotatedFor({})}, has no effect. + * + * @return the type systems for which the class has been annotated + * @checker_framework.manual #shorthand-for-checkers Short names for built-in checkers + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java index c6ed6140cf9..f9ef370b591 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java @@ -30,12 +30,12 @@ @Documented @Retention(RetentionPolicy.SOURCE) public @interface CFComment { - /** - * Comments about Checker Framework annotations. The text is not interpreted by the Checker - * Framework. - * - *

If you prefix each comment by the name of the type system, the comments are easier to - * understand and search for. - */ - String[] value(); + /** + * Comments about Checker Framework annotations. The text is not interpreted by the Checker + * Framework. + * + *

If you prefix each comment by the name of the type system, the comments are easier to + * understand and search for. + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java index ac65025991e..9b7feb69237 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java @@ -65,10 +65,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface ConditionalPostconditionAnnotation { - /** - * The qualifier that will be established as a postcondition. - * - *

This element is analogous to {@link EnsuresQualifierIf#qualifier()}. - */ - Class qualifier(); + /** + * The qualifier that will be established as a postcondition. + * + *

This element is analogous to {@link EnsuresQualifierIf#qualifier()}. + */ + Class qualifier(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java index ac79c2e99db..1599464f6ac 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java @@ -32,6 +32,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface Covariant { - /** The zero-based indices of the type parameters that should be treated covariantly. */ - int[] value(); + /** The zero-based indices of the type parameters that should be treated covariantly. */ + int[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java index 2b75ecf37a9..71276eab670 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java @@ -33,62 +33,62 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface DefaultFor { - /** - * Returns the locations to which the annotation should be applied. - * - * @return the locations to which the annotation should be applied - */ - TypeUseLocation[] value() default {}; + /** + * Returns the locations to which the annotation should be applied. + * + * @return the locations to which the annotation should be applied + */ + TypeUseLocation[] value() default {}; - /** - * Returns {@link TypeKind}s of types for which an annotation should be implicitly added. - * - * @return {@link TypeKind}s of types for which an annotation should be implicitly added - */ - TypeKind[] typeKinds() default {}; + /** + * Returns {@link TypeKind}s of types for which an annotation should be implicitly added. + * + * @return {@link TypeKind}s of types for which an annotation should be implicitly added + */ + TypeKind[] typeKinds() default {}; - /** - * Returns {@link Class}es for which an annotation should be applied. For example, if - * {@code @MyAnno} is meta-annotated with {@code @DefaultFor(classes=String.class)}, then every - * occurrence of {@code String} is actually {@code @MyAnno String}. - * - *

Only the given types, not their subtypes, receive the default. For instance, if the {@code - * types} element contains only {@code Iterable}, then the default does not apply to a variable - * or expression of type {@code Collection} which is a subtype of {@code Iterable}. - * - * @return {@link Class}es for which an annotation should be applied - */ - Class[] types() default {}; + /** + * Returns {@link Class}es for which an annotation should be applied. For example, if + * {@code @MyAnno} is meta-annotated with {@code @DefaultFor(classes=String.class)}, then every + * occurrence of {@code String} is actually {@code @MyAnno String}. + * + *

Only the given types, not their subtypes, receive the default. For instance, if the {@code + * types} element contains only {@code Iterable}, then the default does not apply to a variable or + * expression of type {@code Collection} which is a subtype of {@code Iterable}. + * + * @return {@link Class}es for which an annotation should be applied + */ + Class[] types() default {}; - /** - * Returns regular expressions matching names of variables, to whose types the annotation should - * be applied as a default. If a regular expression matches the name of a method, the annotation - * is applied as a default to the return type. - * - *

The regular expression must match the entire variable or method name. For example, to - * match any name that contains "foo", use ".*foo.*". - * - *

The default does not apply if the name matches any of the regexes in {@link - * #namesExceptions}. - * - *

This affects formal parameter types only if one of the following is true: - * - *

    - *
  • The method's source code is available; that is, the method is type-checked along with - * client calls. - *
  • When the method was compiled in a previous run of javac, either the processor was run - * or the {@code -g} command-line option was provided. - *
- * - * @return regular expressions matching variables to whose type an annotation should be applied - */ - String[] names() default {}; + /** + * Returns regular expressions matching names of variables, to whose types the annotation should + * be applied as a default. If a regular expression matches the name of a method, the annotation + * is applied as a default to the return type. + * + *

The regular expression must match the entire variable or method name. For example, to match + * any name that contains "foo", use ".*foo.*". + * + *

The default does not apply if the name matches any of the regexes in {@link + * #namesExceptions}. + * + *

This affects formal parameter types only if one of the following is true: + * + *

    + *
  • The method's source code is available; that is, the method is type-checked along with + * client calls. + *
  • When the method was compiled in a previous run of javac, either the processor was run or + * the {@code -g} command-line option was provided. + *
+ * + * @return regular expressions matching variables to whose type an annotation should be applied + */ + String[] names() default {}; - /** - * Returns exceptions to regular expression rules. - * - * @return exceptions to regular expression rules - * @see #names - */ - String[] namesExceptions() default {}; + /** + * Returns exceptions to regular expression rules. + * + * @return exceptions to regular expression rules + * @see #names + */ + String[] namesExceptions() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java index 1d993322abc..e08c27d7b65 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java @@ -39,63 +39,63 @@ @Documented @Retention(RetentionPolicy.SOURCE) @Target({ - ElementType.PACKAGE, - ElementType.TYPE, - ElementType.CONSTRUCTOR, - ElementType.METHOD, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.PARAMETER + ElementType.PACKAGE, + ElementType.TYPE, + ElementType.CONSTRUCTOR, + ElementType.METHOD, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.PARAMETER }) @Repeatable(DefaultQualifier.List.class) public @interface DefaultQualifier { - /** - * The Class for the default annotation. - * - *

To prevent affecting other type systems, always specify an annotation in your own type - * hierarchy. (For example, do not set {@link - * org.checkerframework.common.subtyping.qual.Unqualified} as the default.) - */ - Class value(); + /** + * The Class for the default annotation. + * + *

To prevent affecting other type systems, always specify an annotation in your own type + * hierarchy. (For example, do not set {@link + * org.checkerframework.common.subtyping.qual.Unqualified} as the default.) + */ + Class value(); - /** - * Returns the locations to which the annotation should be applied. - * - * @return the locations to which the annotation should be applied - */ - TypeUseLocation[] locations() default {TypeUseLocation.ALL}; + /** + * Returns the locations to which the annotation should be applied. + * + * @return the locations to which the annotation should be applied + */ + TypeUseLocation[] locations() default {TypeUseLocation.ALL}; - /** - * When used on a package, whether the defaults should also apply to subpackages. - * - * @return whether the default should be inherited by subpackages - */ - boolean applyToSubpackages() default true; + /** + * When used on a package, whether the defaults should also apply to subpackages. + * + * @return whether the default should be inherited by subpackages + */ + boolean applyToSubpackages() default true; + /** + * A wrapper annotation that makes the {@link DefaultQualifier} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link DefaultQualifier} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ + ElementType.PACKAGE, + ElementType.TYPE, + ElementType.CONSTRUCTOR, + ElementType.METHOD, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.PARAMETER + }) + public static @interface List { /** - * A wrapper annotation that makes the {@link DefaultQualifier} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link DefaultQualifier} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ - ElementType.PACKAGE, - ElementType.TYPE, - ElementType.CONSTRUCTOR, - ElementType.METHOD, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.PARAMETER - }) - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - DefaultQualifier[] value(); - } + DefaultQualifier[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java index 961ed0421f9..c88065e8d1a 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java @@ -15,6 +15,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DefaultQualifierForUse { - /** Qualifier to add to all unannotated uses of the type with this declaration annotation. */ - Class[] value() default {}; + /** Qualifier to add to all unannotated uses of the type with this declaration annotation. */ + Class[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java index f6d4232f977..3c897376109 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java @@ -36,39 +36,37 @@ @InheritedAnnotation @Repeatable(EnsuresQualifier.List.class) public @interface EnsuresQualifier { - /** - * Returns the Java expressions for which the qualifier holds after successful method - * termination. - * - * @return the Java expressions for which the qualifier holds after successful method - * termination - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions for which the qualifier holds after successful method termination. + * + * @return the Java expressions for which the qualifier holds after successful method termination + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the qualifier that is guaranteed to hold on successful termination of the method. - * - * @return the qualifier that is guaranteed to hold on successful termination of the method - */ - Class qualifier(); + /** + * Returns the qualifier that is guaranteed to hold on successful termination of the method. + * + * @return the qualifier that is guaranteed to hold on successful termination of the method + */ + Class qualifier(); + /** + * A wrapper annotation that makes the {@link EnsuresQualifier} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresQualifier} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresQualifier} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresQualifier} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresQualifier[] value(); - } + EnsuresQualifier[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java index 4d502b6d38d..e5e6734255d 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java @@ -39,48 +39,48 @@ @InheritedAnnotation @Repeatable(EnsuresQualifierIf.List.class) public @interface EnsuresQualifierIf { - /** - * Returns the return value of the method that needs to hold for the postcondition to hold. - * - * @return the return value of the method that needs to hold for the postcondition to hold - */ - boolean result(); + /** + * Returns the return value of the method that needs to hold for the postcondition to hold. + * + * @return the return value of the method that needs to hold for the postcondition to hold + */ + boolean result(); - /** - * Returns the Java expressions for which the qualifier holds if the method terminates with - * return value {@link #result()}. - * - * @return the Java expressions for which the qualifier holds if the method terminates with - * return value {@link #result()} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions for which the qualifier holds if the method terminates with return + * value {@link #result()}. + * + * @return the Java expressions for which the qualifier holds if the method terminates with return + * value {@link #result()} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the qualifier that is guaranteed to hold if the method terminates with return value - * {@link #result()}. - * - * @return the qualifier that is guaranteed to hold if the method terminates with return value - * {@link #result()} - */ - Class qualifier(); + /** + * Returns the qualifier that is guaranteed to hold if the method terminates with return value + * {@link #result()}. + * + * @return the qualifier that is guaranteed to hold if the method terminates with return value + * {@link #result()} + */ + Class qualifier(); + /** + * A wrapper annotation that makes the {@link EnsuresQualifierIf} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresQualifierIf} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @InheritedAnnotation + public static @interface List { /** - * A wrapper annotation that makes the {@link EnsuresQualifierIf} annotation repeatable. + * Return the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresQualifierIf} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD}) - @InheritedAnnotation - public static @interface List { - /** - * Return the repeatable annotations. - * - * @return the repeatable annotations - */ - EnsuresQualifierIf[] value(); - } + EnsuresQualifierIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java index 2f5d65e8cf5..037312637f6 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java @@ -27,15 +27,14 @@ @Inherited public @interface FieldInvariant { - /** - * The qualifier on the field. Must be a subtype of the qualifier on the declaration of the - * field. - */ - Class[] qualifier(); + /** + * The qualifier on the field. Must be a subtype of the qualifier on the declaration of the field. + */ + Class[] qualifier(); - /** - * The field that has a more precise type, in the class on which the {@code FieldInvariant} - * annotation is written. The field must be declared in a superclass and must be {@code final}. - */ - String[] field(); + /** + * The field that has a more precise type, in the class on which the {@code FieldInvariant} + * annotation is written. The field must be declared in a superclass and must be {@code final}. + */ + String[] field(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java index b04026166b8..9cc75018286 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java @@ -70,10 +70,10 @@ @Target({ElementType.TYPE, ElementType.PACKAGE}) public @interface HasQualifierParameter { - /** - * Class of the top qualifier for the hierarchy for which this class has a qualifier parameter. - * - * @return the value - */ - Class[] value(); + /** + * Class of the top qualifier for the hierarchy for which this class has a qualifier parameter. + * + * @return the value + */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java index e027ae6153f..7b3b291007b 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java @@ -13,50 +13,48 @@ */ // https://docs.oracle.com/javase/8/docs/technotes/tools/findingclasses.html#bootclass public enum LiteralKind { - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#NULL_LITERAL} trees. */ - NULL, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#INT_LITERAL} trees. */ - INT, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#LONG_LITERAL} trees. */ - LONG, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#FLOAT_LITERAL} trees. */ - FLOAT, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#DOUBLE_LITERAL} trees. */ - DOUBLE, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#BOOLEAN_LITERAL} trees. */ - BOOLEAN, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#CHAR_LITERAL} trees. */ - CHAR, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#STRING_LITERAL} trees. */ - STRING, - /** Shorthand for all other LiteralKind constants, other than PRIMITIVE. */ - ALL, - /** - * Shorthand for all primitive LiteralKind constants: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. - */ - PRIMITIVE; + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#NULL_LITERAL} trees. */ + NULL, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#INT_LITERAL} trees. */ + INT, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#LONG_LITERAL} trees. */ + LONG, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#FLOAT_LITERAL} trees. */ + FLOAT, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#DOUBLE_LITERAL} trees. */ + DOUBLE, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#BOOLEAN_LITERAL} trees. */ + BOOLEAN, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#CHAR_LITERAL} trees. */ + CHAR, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#STRING_LITERAL} trees. */ + STRING, + /** Shorthand for all other LiteralKind constants, other than PRIMITIVE. */ + ALL, + /** Shorthand for all primitive LiteralKind constants: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. */ + PRIMITIVE; - /** - * Returns all LiteralKinds except for ALL and PRIMITIVE (which are shorthands for groups of - * other LiteralKinds). - * - * @return list of LiteralKinds except for ALL and PRIMITIVE - */ - public static List allLiteralKinds() { - List list = new ArrayList<>(Arrays.asList(values())); - list.remove(ALL); - list.remove(PRIMITIVE); - return list; - } + /** + * Returns all LiteralKinds except for ALL and PRIMITIVE (which are shorthands for groups of other + * LiteralKinds). + * + * @return list of LiteralKinds except for ALL and PRIMITIVE + */ + public static List allLiteralKinds() { + List list = new ArrayList<>(Arrays.asList(values())); + list.remove(ALL); + list.remove(PRIMITIVE); + return list; + } - /** - * Returns the primitive {@code LiteralKind}s: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. This is - * all LiteralKinds except for NULL, STRING, and ones that are shorthands for groups of other - * LiteralKinds. - * - * @return list of LiteralKinds except for NULL and STRING - */ - public static List primitiveLiteralKinds() { - return Arrays.asList(INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR); - } + /** + * Returns the primitive {@code LiteralKind}s: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. This is + * all LiteralKinds except for NULL, STRING, and ones that are shorthands for groups of other + * LiteralKinds. + * + * @return list of LiteralKinds except for NULL and STRING + */ + public static List primitiveLiteralKinds() { + return Arrays.asList(INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java index 25232ebf100..73b2fde809a 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java @@ -39,5 +39,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface MonotonicQualifier { - Class value(); + Class value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java index c82472cd159..d1d1c76658d 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java @@ -18,6 +18,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface NoDefaultQualifierForUse { - /** Top qualifier in hierarchies for which no default annotation for use should be applied. */ - Class[] value() default {}; + /** Top qualifier in hierarchies for which no default annotation for use should be applied. */ + Class[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java index b61b15aa44e..4dd970b9c73 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java @@ -32,10 +32,10 @@ @Inherited public @interface NoQualifierParameter { - /** - * Class of the top qualifier for the hierarchy for which this class has no qualifier parameter. - * - * @return the value - */ - Class[] value(); + /** + * Class of the top qualifier for the hierarchy for which this class has no qualifier parameter. + * + * @return the value + */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java index c5829652b90..fc2d1278f02 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java @@ -26,15 +26,15 @@ @Target({ElementType.ANNOTATION_TYPE}) @AnnotatedFor("nullness") public @interface ParametricTypeVariableUseQualifier { - /** - * Indicates which type system this annotation refers to (optional, and usually unnecessary). - * When multiple type hierarchies are supported by a single type system, then each parametric - * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top - * qualifier from the given hierarchy. - * - * @return the top qualifier in the hierarchy of this qualifier - */ - // We use the meaningless Annotation.class as default value and - // then ensure there is a single top qualifier to use. - Class value() default Annotation.class; + /** + * Indicates which type system this annotation refers to (optional, and usually unnecessary). When + * multiple type hierarchies are supported by a single type system, then each parametric qualifier + * needs to indicate which sub-hierarchy it belongs to. Do so by passing the top qualifier from + * the given hierarchy. + * + * @return the top qualifier in the hierarchy of this qualifier + */ + // We use the meaningless Annotation.class as default value and + // then ensure there is a single top qualifier to use. + Class value() default Annotation.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java index c47a25f15d4..adcc8650019 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java @@ -21,15 +21,15 @@ @Target({ElementType.ANNOTATION_TYPE}) @AnnotatedFor("nullness") public @interface PolymorphicQualifier { - /** - * Indicates which type system this annotation refers to (optional, and usually unnecessary). - * When multiple type hierarchies are supported by a single type system, then each polymorphic - * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top - * qualifier from the given hierarchy. - * - * @return the top qualifier in the hierarchy of this qualifier - */ - // We use the meaningless Annotation.class as default value and - // then ensure there is a single top qualifier to use. - Class value() default Annotation.class; + /** + * Indicates which type system this annotation refers to (optional, and usually unnecessary). When + * multiple type hierarchies are supported by a single type system, then each polymorphic + * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top + * qualifier from the given hierarchy. + * + * @return the top qualifier in the hierarchy of this qualifier + */ + // We use the meaningless Annotation.class as default value and + // then ensure there is a single top qualifier to use. + Class value() default Annotation.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java index 1678614c67c..67bb434e848 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java @@ -60,10 +60,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface PostconditionAnnotation { - /** - * The qualifier that will be established as a postcondition. - * - *

This element is analogous to {@link EnsuresQualifier#qualifier()}. - */ - Class qualifier(); + /** + * The qualifier that will be established as a postcondition. + * + *

This element is analogous to {@link EnsuresQualifier#qualifier()}. + */ + Class qualifier(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java index 542b1fa13b4..95af23ec376 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java @@ -56,6 +56,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface PreconditionAnnotation { - /** The qualifier that must be established as a precondition. */ - Class qualifier(); + /** The qualifier that must be established as a precondition. */ + Class qualifier(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java index 0a6810569aa..e56a2800ccc 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java @@ -37,10 +37,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface QualifierArgument { - /** - * Specifies the name of the argument of the qualifier, that is passed the values held in the - * annotated element. If the value is omitted or is empty, then the name of the annotated - * element is used as the argument name. - */ - String value() default ""; + /** + * Specifies the name of the argument of the qualifier, that is passed the values held in the + * annotated element. If the value is omitted or is empty, then the name of the annotated element + * is used as the argument name. + */ + String value() default ""; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java index 6c75761f602..72cf42edf45 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java @@ -15,21 +15,21 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface QualifierForLiterals { - /** - * The kinds of literals whose types have this qualifier. For example, if {@code @MyAnno} is - * meta-annotated with {@code @QualifierForLiterals(LiteralKind.STRING)}, then a literal {@code - * String} constant such as {@code "hello world"} has type {@code @MyAnno String}, but - * occurrences of {@code String} in the source code are not affected. - * - *

For String literals, also see the {@link #stringPatterns} annotation element/field. - */ - LiteralKind[] value() default {}; + /** + * The kinds of literals whose types have this qualifier. For example, if {@code @MyAnno} is + * meta-annotated with {@code @QualifierForLiterals(LiteralKind.STRING)}, then a literal {@code + * String} constant such as {@code "hello world"} has type {@code @MyAnno String}, but occurrences + * of {@code String} in the source code are not affected. + * + *

For String literals, also see the {@link #stringPatterns} annotation element/field. + */ + LiteralKind[] value() default {}; - /** - * A string literal that matches any of these patterns has this qualifier. - * - *

If patterns for multiple qualifiers match, then the string literal is given the greatest - * lower bound of all the matches. - */ - String[] stringPatterns() default {}; + /** + * A string literal that matches any of these patterns has this qualifier. + * + *

If patterns for multiple qualifiers match, then the string literal is given the greatest + * lower bound of all the matches. + */ + String[] stringPatterns() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java index ce53d197aa5..667f408627e 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java @@ -27,17 +27,17 @@ @Target(ElementType.TYPE) @Inherited public @interface RelevantJavaTypes { - /** - * Classes where a type annotation supported by this checker may be written. - * - *

{@code Object[].class} means that the checker processes all array types. No distinction - * among array types is currently made, and no other array class should be supplied to - * {@code @RelevantJavaTypes}. - * - *

If a checker processes both primitive and boxed types, both must be specified separately, - * for example as {@code int.class} and {@code Integer.class}. - * - * @return classes where a type annotation supported by this checker may be written - */ - Class[] value(); + /** + * Classes where a type annotation supported by this checker may be written. + * + *

{@code Object[].class} means that the checker processes all array types. No distinction + * among array types is currently made, and no other array class should be supplied to + * {@code @RelevantJavaTypes}. + * + *

If a checker processes both primitive and boxed types, both must be specified separately, + * for example as {@code int.class} and {@code Integer.class}. + * + * @return classes where a type annotation supported by this checker may be written + */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java index 2092c396e1a..5b6bbab7662 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java @@ -21,36 +21,36 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Repeatable(RequiresQualifier.List.class) public @interface RequiresQualifier { - /** - * Returns the Java expressions for which the annotation need to be present. - * - * @return the Java expressions for which the annotation need to be present - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions for which the annotation need to be present. + * + * @return the Java expressions for which the annotation need to be present + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the qualifier that is required. - * - * @return the qualifier that is required - */ - Class qualifier(); + /** + * Returns the qualifier that is required. + * + * @return the qualifier that is required + */ + Class qualifier(); + /** + * A wrapper annotation that makes the {@link RequiresQualifier} annotation repeatable. + * + *

Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresQualifier} annotation at the same location. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + public static @interface List { /** - * A wrapper annotation that makes the {@link RequiresQualifier} annotation repeatable. + * Returns the repeatable annotations. * - *

Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresQualifier} annotation at the same location. + * @return the repeatable annotations */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - public static @interface List { - /** - * Returns the repeatable annotations. - * - * @return the repeatable annotations - */ - RequiresQualifier[] value(); - } + RequiresQualifier[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java index 64dbeff0700..25d74cbda4a 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java @@ -21,9 +21,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface StubFiles { - /** - * Stub file names. These are basenames: they include the extension (usually ".astub"), but no - * directory component. - */ - String[] value(); + /** + * Stub file names. These are basenames: they include the extension (usually ".astub"), but no + * directory component. + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java index 240c0a4eb73..1f6b9543ab6 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java @@ -40,6 +40,6 @@ @Target(ElementType.ANNOTATION_TYPE) @AnnotatedFor("nullness") public @interface SubtypeOf { - /** An array of the supertype qualifiers of the annotated qualifier. */ - Class[] value(); + /** An array of the supertype qualifiers of the annotated qualifier. */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java index d1f544e7017..6f2055c9a99 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java @@ -34,10 +34,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface TargetLocations { - /** - * Type uses at which the qualifier is permitted to be applied in source code. - * - * @return type-use locations declared in this meta-annotation - */ - TypeUseLocation[] value(); + /** + * Type uses at which the qualifier is permitted to be applied in source code. + * + * @return type-use locations declared in this meta-annotation + */ + TypeUseLocation[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java index e80c6fd573b..9adef287c7c 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java @@ -10,66 +10,66 @@ * annotations */ public enum TypeKind { - /** Corresponds to {@link javax.lang.model.type.TypeKind#BOOLEAN} types. */ - BOOLEAN, + /** Corresponds to {@link javax.lang.model.type.TypeKind#BOOLEAN} types. */ + BOOLEAN, - /** Corresponds to {@link javax.lang.model.type.TypeKind#BYTE} types. */ - BYTE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#BYTE} types. */ + BYTE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#SHORT} types. */ - SHORT, + /** Corresponds to {@link javax.lang.model.type.TypeKind#SHORT} types. */ + SHORT, - /** Corresponds to {@link javax.lang.model.type.TypeKind#INT} types. */ - INT, + /** Corresponds to {@link javax.lang.model.type.TypeKind#INT} types. */ + INT, - /** Corresponds to {@link javax.lang.model.type.TypeKind#LONG} types. */ - LONG, + /** Corresponds to {@link javax.lang.model.type.TypeKind#LONG} types. */ + LONG, - /** Corresponds to {@link javax.lang.model.type.TypeKind#CHAR} types. */ - CHAR, + /** Corresponds to {@link javax.lang.model.type.TypeKind#CHAR} types. */ + CHAR, - /** Corresponds to {@link javax.lang.model.type.TypeKind#FLOAT} types. */ - FLOAT, + /** Corresponds to {@link javax.lang.model.type.TypeKind#FLOAT} types. */ + FLOAT, - /** Corresponds to {@link javax.lang.model.type.TypeKind#DOUBLE} types. */ - DOUBLE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#DOUBLE} types. */ + DOUBLE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#VOID} types. */ - VOID, + /** Corresponds to {@link javax.lang.model.type.TypeKind#VOID} types. */ + VOID, - /** Corresponds to {@link javax.lang.model.type.TypeKind#NONE} types. */ - NONE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#NONE} types. */ + NONE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#NULL} types. */ - NULL, + /** Corresponds to {@link javax.lang.model.type.TypeKind#NULL} types. */ + NULL, - /** Corresponds to {@link javax.lang.model.type.TypeKind#ARRAY} types. */ - ARRAY, + /** Corresponds to {@link javax.lang.model.type.TypeKind#ARRAY} types. */ + ARRAY, - /** Corresponds to {@link javax.lang.model.type.TypeKind#DECLARED} types. */ - DECLARED, + /** Corresponds to {@link javax.lang.model.type.TypeKind#DECLARED} types. */ + DECLARED, - /** Corresponds to {@link javax.lang.model.type.TypeKind#ERROR} types. */ - ERROR, + /** Corresponds to {@link javax.lang.model.type.TypeKind#ERROR} types. */ + ERROR, - /** Corresponds to {@link javax.lang.model.type.TypeKind#TYPEVAR} types. */ - TYPEVAR, + /** Corresponds to {@link javax.lang.model.type.TypeKind#TYPEVAR} types. */ + TYPEVAR, - /** Corresponds to {@link javax.lang.model.type.TypeKind#WILDCARD} types. */ - WILDCARD, + /** Corresponds to {@link javax.lang.model.type.TypeKind#WILDCARD} types. */ + WILDCARD, - /** Corresponds to {@link javax.lang.model.type.TypeKind#PACKAGE} types. */ - PACKAGE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#PACKAGE} types. */ + PACKAGE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#EXECUTABLE} types. */ - EXECUTABLE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#EXECUTABLE} types. */ + EXECUTABLE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#OTHER} types. */ - OTHER, + /** Corresponds to {@link javax.lang.model.type.TypeKind#OTHER} types. */ + OTHER, - /** Corresponds to {@link javax.lang.model.type.TypeKind#UNION} types. */ - UNION, + /** Corresponds to {@link javax.lang.model.type.TypeKind#UNION} types. */ + UNION, - /** Corresponds to {@link javax.lang.model.type.TypeKind#INTERSECTION} types. */ - INTERSECTION; + /** Corresponds to {@link javax.lang.model.type.TypeKind#INTERSECTION} types. */ + INTERSECTION; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java index 22809919f57..96ebe745d2e 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java @@ -18,139 +18,139 @@ */ public enum TypeUseLocation { - /** Apply default annotations to unannotated top-level types of fields. */ - FIELD, - - /** - * Apply default annotations to unannotated top-level types of local variables, casts, and - * instanceof. - */ - LOCAL_VARIABLE, - - /** Apply default annotations to unannotated top-level types of resource variables. */ - RESOURCE_VARIABLE, - - /** Apply default annotations to unannotated top-level types of exception parameters. */ - EXCEPTION_PARAMETER, - - /** Apply default annotations to unannotated top-level types of receiver types. */ - RECEIVER, - - /** - * Apply default annotations to unannotated top-level types of formal parameter types, excluding - * the receiver. - */ - PARAMETER, - - /** Apply default annotations to unannotated top-level types of return types. */ - RETURN, - - /** Apply default annotations to unannotated top-level types of constructor result types. */ - CONSTRUCTOR_RESULT, - - /** - * Apply default annotations to unannotated top-level lower bounds of type parameters and - * wildcards, both explicit ones in {@code super} clauses, and implicit lower bounds when no - * explicit {@code extends} or {@code super} clause is present. - */ - LOWER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit lower bounds of wildcards: {@code - * }. Type parameters have no syntax for explicit lower bound types. - */ - EXPLICIT_LOWER_BOUND, - - /** - * Apply default annotations to unannotated implicit lower bounds of type parameters and - * wildcards: {@code } and {@code }, possibly with explicit upper bounds. - */ - // Note: no distinction between implicit lower bound when upper bound is explicit or not, in - // contrast to what we do for upper bounds. We can add that if a type system needs it. - IMPLICIT_LOWER_BOUND, - - /** - * Apply default annotations to unannotated top-level upper bounds of type parameters and - * wildcards: both explicit ones in {@code extends} clauses, and implicit upper bounds when no - * explicit {@code extends} or {@code super} clause is present. - * - *

Especially useful for parametrized classes that provide a lot of static methods with the - * same generic parameters as the class. - */ - UPPER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit type parameter and wildcard upper - * bounds: {@code } and {@code }. - */ - EXPLICIT_UPPER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit type parameter upper bounds: - * {@code }. - */ - EXPLICIT_TYPE_PARAMETER_UPPER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit wildcard upper bounds: {@code }. - */ - EXPLICIT_WILDCARD_UPPER_BOUND, - - /** - * Apply default annotations to unannotated upper bounds of type parameters and wildcards - * without explicit upper bounds: {@code }, {@code }, and {@code }. - */ - IMPLICIT_UPPER_BOUND, - - /** - * Apply default annotations to unannotated upper bounds of type parameters without explicit - * upper bounds: {@code }. - */ - IMPLICIT_TYPE_PARAMETER_UPPER_BOUND, - - /** - * Apply default annotations to unannotated upper bounds of wildcards without a super bound: - * {@code }. - */ - IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER, - - /** - * Apply default annotations to unannotated upper bounds of wildcards with a super bound: {@code - * }. - */ - IMPLICIT_WILDCARD_UPPER_BOUND_SUPER, - - /** - * Apply default annotations to unannotated upper bounds of wildcards with or without a super - * bound: {@code } or {@code }. - */ - IMPLICIT_WILDCARD_UPPER_BOUND, - - /** - * Apply default annotations to unannotated type variable uses that are not top-level local - * variables: {@code T field} or {@code List local}. - * - *

Such uses of type variables are not flow-sensitively refined and are therefore usually - * parametric. - * - *

To get parametric polymorphism: add a qualifier that is meta-annotated with {@link - * ParametricTypeVariableUseQualifier} to your type system and use it as default for {@code - * TYPE_VARIABLE_USE}, which is treated like no annotation on the type variable use. - * - *

We could name this constant {@code TYPE_VARIABLE_USE_NOT_TOP_LEVEL_LOCAL_VARIABLE} and - * introduce a separate constant {@code TYPE_VARIABLE_USE_TOP_LEVEL_LOCAL_VARIABLE}. At the - * moment we use the {@code LOCAL_VARIABLE} default for unannotated top-level type variable uses - * of local variables: {@code T local}. - */ - TYPE_VARIABLE_USE, - - /** Apply if nothing more concrete is provided. TODO: clarify relation to ALL. */ - OTHERWISE, - - /** - * Apply default annotations to all type uses other than uses of type parameters. Does not allow - * any of the other constants. Usually you want OTHERWISE. - */ - ALL; + /** Apply default annotations to unannotated top-level types of fields. */ + FIELD, + + /** + * Apply default annotations to unannotated top-level types of local variables, casts, and + * instanceof. + */ + LOCAL_VARIABLE, + + /** Apply default annotations to unannotated top-level types of resource variables. */ + RESOURCE_VARIABLE, + + /** Apply default annotations to unannotated top-level types of exception parameters. */ + EXCEPTION_PARAMETER, + + /** Apply default annotations to unannotated top-level types of receiver types. */ + RECEIVER, + + /** + * Apply default annotations to unannotated top-level types of formal parameter types, excluding + * the receiver. + */ + PARAMETER, + + /** Apply default annotations to unannotated top-level types of return types. */ + RETURN, + + /** Apply default annotations to unannotated top-level types of constructor result types. */ + CONSTRUCTOR_RESULT, + + /** + * Apply default annotations to unannotated top-level lower bounds of type parameters and + * wildcards, both explicit ones in {@code super} clauses, and implicit lower bounds when no + * explicit {@code extends} or {@code super} clause is present. + */ + LOWER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit lower bounds of wildcards: {@code + * }. Type parameters have no syntax for explicit lower bound types. + */ + EXPLICIT_LOWER_BOUND, + + /** + * Apply default annotations to unannotated implicit lower bounds of type parameters and + * wildcards: {@code } and {@code }, possibly with explicit upper bounds. + */ + // Note: no distinction between implicit lower bound when upper bound is explicit or not, in + // contrast to what we do for upper bounds. We can add that if a type system needs it. + IMPLICIT_LOWER_BOUND, + + /** + * Apply default annotations to unannotated top-level upper bounds of type parameters and + * wildcards: both explicit ones in {@code extends} clauses, and implicit upper bounds when no + * explicit {@code extends} or {@code super} clause is present. + * + *

Especially useful for parametrized classes that provide a lot of static methods with the + * same generic parameters as the class. + */ + UPPER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit type parameter and wildcard upper + * bounds: {@code } and {@code }. + */ + EXPLICIT_UPPER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit type parameter upper bounds: {@code + * }. + */ + EXPLICIT_TYPE_PARAMETER_UPPER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit wildcard upper bounds: {@code }. + */ + EXPLICIT_WILDCARD_UPPER_BOUND, + + /** + * Apply default annotations to unannotated upper bounds of type parameters and wildcards without + * explicit upper bounds: {@code }, {@code }, and {@code }. + */ + IMPLICIT_UPPER_BOUND, + + /** + * Apply default annotations to unannotated upper bounds of type parameters without explicit upper + * bounds: {@code }. + */ + IMPLICIT_TYPE_PARAMETER_UPPER_BOUND, + + /** + * Apply default annotations to unannotated upper bounds of wildcards without a super bound: + * {@code }. + */ + IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER, + + /** + * Apply default annotations to unannotated upper bounds of wildcards with a super bound: {@code + * }. + */ + IMPLICIT_WILDCARD_UPPER_BOUND_SUPER, + + /** + * Apply default annotations to unannotated upper bounds of wildcards with or without a super + * bound: {@code } or {@code }. + */ + IMPLICIT_WILDCARD_UPPER_BOUND, + + /** + * Apply default annotations to unannotated type variable uses that are not top-level local + * variables: {@code T field} or {@code List local}. + * + *

Such uses of type variables are not flow-sensitively refined and are therefore usually + * parametric. + * + *

To get parametric polymorphism: add a qualifier that is meta-annotated with {@link + * ParametricTypeVariableUseQualifier} to your type system and use it as default for {@code + * TYPE_VARIABLE_USE}, which is treated like no annotation on the type variable use. + * + *

We could name this constant {@code TYPE_VARIABLE_USE_NOT_TOP_LEVEL_LOCAL_VARIABLE} and + * introduce a separate constant {@code TYPE_VARIABLE_USE_TOP_LEVEL_LOCAL_VARIABLE}. At the moment + * we use the {@code LOCAL_VARIABLE} default for unannotated top-level type variable uses of local + * variables: {@code T local}. + */ + TYPE_VARIABLE_USE, + + /** Apply if nothing more concrete is provided. TODO: clarify relation to ALL. */ + OTHERWISE, + + /** + * Apply default annotations to all type uses other than uses of type parameters. Does not allow + * any of the other constants. Usually you want OTHERWISE. + */ + ALL; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java index f0d00d15217..e431e76dc32 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java @@ -39,9 +39,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Unused { - /** - * The field that is annotated with @Unused may not be accessed via a receiver that is annotated - * with the "when" annotation. - */ - Class when(); + /** + * The field that is annotated with @Unused may not be accessed via a receiver that is annotated + * with the "when" annotation. + */ + Class when(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java index 75d24027f27..22dbca461dd 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java @@ -31,19 +31,19 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface UpperBoundFor { - /** - * Returns {@link TypeKind}s of types that get an upper bound. The meta-annotated annotation is - * the upper bound. - * - * @return {@link TypeKind}s of types that get an upper bound - */ - TypeKind[] typeKinds() default {}; + /** + * Returns {@link TypeKind}s of types that get an upper bound. The meta-annotated annotation is + * the upper bound. + * + * @return {@link TypeKind}s of types that get an upper bound + */ + TypeKind[] typeKinds() default {}; - /** - * Returns {@link Class}es that should get an upper bound. The meta-annotated annotation is the - * upper bound. - * - * @return {@link Class}es that get an upper bound - */ - Class[] types() default {}; + /** + * Returns {@link Class}es that should get an upper bound. The meta-annotated annotation is the + * upper bound. + * + * @return {@link Class}es that get an upper bound + */ + Class[] types() default {}; } diff --git a/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java index 24d9473d16f..ee35bfd159f 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.formatter.util; -import org.checkerframework.checker.formatter.qual.ConversionCategory; -import org.checkerframework.checker.formatter.qual.ReturnsFormat; -import org.checkerframework.checker.regex.qual.Regex; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.util.ArrayList; import java.util.HashMap; import java.util.IllegalFormatConversionException; @@ -13,319 +8,316 @@ import java.util.MissingFormatArgumentException; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.formatter.qual.ConversionCategory; +import org.checkerframework.checker.formatter.qual.ReturnsFormat; +import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.framework.qual.AnnotatedFor; /** This class provides a collection of utilities to ease working with format strings. */ @AnnotatedFor("nullness") public class FormatUtil { - /** - * A representation of a format specifier, which is represented by "%..." in the format string. - * Indicates how to convert a value into a string. - */ - private static class Conversion { - /** The index in the argument list. */ - private final int index; - - /** The conversion category. */ - private final ConversionCategory cath; - - /** - * Construct a new Conversion. - * - * @param index the index in the argument list - * @param c the conversion character - */ - public Conversion(char c, int index) { - this.index = index; - this.cath = ConversionCategory.fromConversionChar(c); - } - - /** - * Returns the index in the argument list. - * - * @return the index in the argument list - */ - int index() { - return index; - } - - /** - * Returns the conversion category. - * - * @return the conversion category - */ - ConversionCategory category() { - return cath; - } - } + /** + * A representation of a format specifier, which is represented by "%..." in the format string. + * Indicates how to convert a value into a string. + */ + private static class Conversion { + /** The index in the argument list. */ + private final int index; + + /** The conversion category. */ + private final ConversionCategory cath; /** - * Returns the first argument if the format string is satisfiable, and if the format's - * parameters match the passed {@link ConversionCategory}s. Otherwise throws an exception. + * Construct a new Conversion. * - * @param format a format string - * @param cc an array of conversion categories - * @return the {@code format} argument - * @throws IllegalFormatException if the format string is incompatible with the conversion - * categories + * @param index the index in the argument list + * @param c the conversion character */ - // TODO introduce more such functions, see RegexUtil for examples - @ReturnsFormat - public static String asFormat(String format, ConversionCategory... cc) - throws IllegalFormatException { - ConversionCategory[] fcc = formatParameterCategories(format); - if (fcc.length != cc.length) { - throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length); - } - - for (int i = 0; i < cc.length; i++) { - if (cc[i] != fcc[i]) { - throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]); - } - } - - return format; + public Conversion(char c, int index) { + this.index = index; + this.cath = ConversionCategory.fromConversionChar(c); } /** - * Throws an exception if the format is not syntactically valid. + * Returns the index in the argument list. * - * @param format a format string - * @throws IllegalFormatException if the format string is invalid + * @return the index in the argument list */ - public static void tryFormatSatisfiability(String format) throws IllegalFormatException { - @SuppressWarnings({ - "unused", // called for side effect, to see if it throws an exception - "nullness:argument.type.incompatible", // it's not documented, but String.format permits - // a null array, which it treats as matching any format string (null is supplied to each - // format specifier). - "formatter:format.string.invalid", // this is a test of format string validity - }) - String unused = String.format(format, (Object[]) null); + int index() { + return index; } /** - * Returns a {@link ConversionCategory} for every conversion found in the format string. + * Returns the conversion category. * - *

Throws an exception if the format is not syntactically valid. + * @return the conversion category */ - public static ConversionCategory[] formatParameterCategories(String format) - throws IllegalFormatException { - tryFormatSatisfiability(format); - - int last = -1; // index of last argument referenced - int lasto = -1; // last ordinary index - int maxindex = -1; - - Conversion[] cs = parse(format); - Map conv = new HashMap<>(cs.length); - - for (Conversion c : cs) { - int index = c.index(); - switch (index) { - case -1: // relative index - break; - case 0: // ordinary index - lasto++; - last = lasto; - break; - default: // explicit index - last = index - 1; - break; - } - maxindex = Math.max(maxindex, last); - Integer lastKey = last; - conv.put( - last, - ConversionCategory.intersect( - conv.containsKey(lastKey) - ? conv.get(lastKey) - : ConversionCategory.UNUSED, - c.category())); - } - - ConversionCategory[] res = new ConversionCategory[maxindex + 1]; - for (int i = 0; i <= maxindex; ++i) { - Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null - res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED; - } - return res; + ConversionCategory category() { + return cath; + } + } + + /** + * Returns the first argument if the format string is satisfiable, and if the format's parameters + * match the passed {@link ConversionCategory}s. Otherwise throws an exception. + * + * @param format a format string + * @param cc an array of conversion categories + * @return the {@code format} argument + * @throws IllegalFormatException if the format string is incompatible with the conversion + * categories + */ + // TODO introduce more such functions, see RegexUtil for examples + @ReturnsFormat + public static String asFormat(String format, ConversionCategory... cc) + throws IllegalFormatException { + ConversionCategory[] fcc = formatParameterCategories(format); + if (fcc.length != cc.length) { + throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length); } - /** - * A regex that matches a format specifier. Its syntax is specified in the See {@code - * Formatter} documentation. - * - *

-     * %[argument_index$][flags][width][.precision][t]conversion
-     * group 1            2      3      4           5 6
-     * 
- * - * For dates and times, the [t] is required and precision must not be provided. For types other - * than dates and times, the [t] must not be provided. - */ - private static final @Regex(6) String formatSpecifier = - "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; + for (int i = 0; i < cc.length; i++) { + if (cc[i] != fcc[i]) { + throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]); + } + } - /** The capturing group for the optional {@code t} character. */ - private static final int formatSpecifierT = 5; + return format; + } + + /** + * Throws an exception if the format is not syntactically valid. + * + * @param format a format string + * @throws IllegalFormatException if the format string is invalid + */ + public static void tryFormatSatisfiability(String format) throws IllegalFormatException { + @SuppressWarnings({ + "unused", // called for side effect, to see if it throws an exception + "nullness:argument.type.incompatible", // it's not documented, but String.format permits + // a null array, which it treats as matching any format string (null is supplied to each + // format specifier). + "formatter:format.string.invalid", // this is a test of format string validity + }) + String unused = String.format(format, (Object[]) null); + } + + /** + * Returns a {@link ConversionCategory} for every conversion found in the format string. + * + *

Throws an exception if the format is not syntactically valid. + */ + public static ConversionCategory[] formatParameterCategories(String format) + throws IllegalFormatException { + tryFormatSatisfiability(format); + + int last = -1; // index of last argument referenced + int lasto = -1; // last ordinary index + int maxindex = -1; + + Conversion[] cs = parse(format); + Map conv = new HashMap<>(cs.length); + + for (Conversion c : cs) { + int index = c.index(); + switch (index) { + case -1: // relative index + break; + case 0: // ordinary index + lasto++; + last = lasto; + break; + default: // explicit index + last = index - 1; + break; + } + maxindex = Math.max(maxindex, last); + Integer lastKey = last; + conv.put( + last, + ConversionCategory.intersect( + conv.containsKey(lastKey) ? conv.get(lastKey) : ConversionCategory.UNUSED, + c.category())); + } - /** - * The capturing group for the last character in a format specifier, which is the conversion - * character unless the {@code t} character was given. - */ - private static final int formatSpecifierConversion = 6; + ConversionCategory[] res = new ConversionCategory[maxindex + 1]; + for (int i = 0; i <= maxindex; ++i) { + Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null + res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED; + } + return res; + } + + /** + * A regex that matches a format specifier. Its syntax is specified in the See {@code + * Formatter} documentation. + * + *

+   * %[argument_index$][flags][width][.precision][t]conversion
+   * group 1            2      3      4           5 6
+   * 
+ * + * For dates and times, the [t] is required and precision must not be provided. For types other + * than dates and times, the [t] must not be provided. + */ + private static final @Regex(6) String formatSpecifier = + "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; + + /** The capturing group for the optional {@code t} character. */ + private static final int formatSpecifierT = 5; + + /** + * The capturing group for the last character in a format specifier, which is the conversion + * character unless the {@code t} character was given. + */ + private static final int formatSpecifierConversion = 6; + + /** + * A Pattern that matches a format specifier. + * + * @see #formatSpecifier + */ + private static @Regex(6) Pattern fsPattern = Pattern.compile(formatSpecifier); + + /** + * Return the index, in the argument list, of the value that will be formatted by the matched + * format specifier. + * + * @param m a matcher that matches a format specifier + * @return the index of the argument to format + */ + private static int indexFromFormat(Matcher m) { + int index; + String s = m.group(1); + if (s != null) { // explicit index + index = Integer.parseInt(s.substring(0, s.length() - 1)); + } else { + String group2 = m.group(2); // not @Deterministic, so extract into local var + if (group2 != null && group2.contains(String.valueOf('<'))) { + index = -1; // relative index + } else { + index = 0; // ordinary index + } + } + return index; + } + + /** + * Returns the conversion character from a format specifier.. + * + * @param m a matcher that matches a format specifier + * @return the conversion character from the format specifier + */ + @SuppressWarnings( + "nullness:dereference.of.nullable") // group formatSpecifierConversion always exists + private static char conversionCharFromFormat(@Regex(6) Matcher m) { + String tGroup = m.group(formatSpecifierT); + if (tGroup != null) { + return tGroup.charAt(0); // This is the letter "t" or "T". + } else { + return m.group(formatSpecifierConversion).charAt(0); + } + } + + /** + * Return the conversion character that is in the given format specifier. + * + * @param formatSpecifier a format + * specifier + * @return the conversion character that is in the given format specifier + * @deprecated This method is public only for testing. Use private method {@code + * #conversionCharFromFormat(Matcher)}. + */ + @Deprecated // used only for testing. Use conversionCharFromFormat(Matcher). + public static char conversionCharFromFormat(String formatSpecifier) { + Matcher m = fsPattern.matcher(formatSpecifier); + assert m.find(); + return conversionCharFromFormat(m); + } + + /** + * Parse the given format string, return information about its format specifiers. + * + * @param format a format string + * @return the list of Conversions from the format specifiers in the format string + */ + private static Conversion[] parse(String format) { + ArrayList cs = new ArrayList<>(); + @Regex(7) Matcher m = fsPattern.matcher(format); + while (m.find()) { + char c = conversionCharFromFormat(m); + switch (c) { + case '%': + case 'n': + break; + default: + cs.add(new Conversion(c, indexFromFormat(m))); + } + } + return cs.toArray(new Conversion[cs.size()]); + } - /** - * A Pattern that matches a format specifier. - * - * @see #formatSpecifier - */ - private static @Regex(6) Pattern fsPattern = Pattern.compile(formatSpecifier); + public static class ExcessiveOrMissingFormatArgumentException + extends MissingFormatArgumentException { + private static final long serialVersionUID = 17000126L; + + private final int expected; + private final int found; /** - * Return the index, in the argument list, of the value that will be formatted by the matched - * format specifier. - * - * @param m a matcher that matches a format specifier - * @return the index of the argument to format + * Constructs an instance of this class with the actual argument length and the expected one. */ - private static int indexFromFormat(Matcher m) { - int index; - String s = m.group(1); - if (s != null) { // explicit index - index = Integer.parseInt(s.substring(0, s.length() - 1)); - } else { - String group2 = m.group(2); // not @Deterministic, so extract into local var - if (group2 != null && group2.contains(String.valueOf('<'))) { - index = -1; // relative index - } else { - index = 0; // ordinary index - } - } - return index; + public ExcessiveOrMissingFormatArgumentException(int expected, int found) { + super("-"); + this.expected = expected; + this.found = found; } - /** - * Returns the conversion character from a format specifier.. - * - * @param m a matcher that matches a format specifier - * @return the conversion character from the format specifier - */ - @SuppressWarnings( - "nullness:dereference.of.nullable") // group formatSpecifierConversion always exists - private static char conversionCharFromFormat(@Regex(6) Matcher m) { - String tGroup = m.group(formatSpecifierT); - if (tGroup != null) { - return tGroup.charAt(0); // This is the letter "t" or "T". - } else { - return m.group(formatSpecifierConversion).charAt(0); - } + public int getExpected() { + return expected; } - /** - * Return the conversion character that is in the given format specifier. - * - * @param formatSpecifier a format - * specifier - * @return the conversion character that is in the given format specifier - * @deprecated This method is public only for testing. Use private method {@code - * #conversionCharFromFormat(Matcher)}. - */ - @Deprecated // used only for testing. Use conversionCharFromFormat(Matcher). - public static char conversionCharFromFormat(String formatSpecifier) { - Matcher m = fsPattern.matcher(formatSpecifier); - assert m.find(); - return conversionCharFromFormat(m); + public int getFound() { + return found; } - /** - * Parse the given format string, return information about its format specifiers. - * - * @param format a format string - * @return the list of Conversions from the format specifiers in the format string - */ - private static Conversion[] parse(String format) { - ArrayList cs = new ArrayList<>(); - @Regex(7) Matcher m = fsPattern.matcher(format); - while (m.find()) { - char c = conversionCharFromFormat(m); - switch (c) { - case '%': - case 'n': - break; - default: - cs.add(new Conversion(c, indexFromFormat(m))); - } - } - return cs.toArray(new Conversion[cs.size()]); + @Override + public String getMessage() { + return String.format("Expected %d arguments but found %d.", expected, found); + } + } + + public static class IllegalFormatConversionCategoryException + extends IllegalFormatConversionException { + private static final long serialVersionUID = 17000126L; + + private final ConversionCategory expected; + private final ConversionCategory found; + + /** Constructs an instance of this class with the mismatched conversion and the expected one. */ + public IllegalFormatConversionCategoryException( + ConversionCategory expected, ConversionCategory found) { + super( + expected.chars == null || expected.chars.length() == 0 ? '-' : expected.chars.charAt(0), + found.types == null ? Object.class : found.types[0]); + this.expected = expected; + this.found = found; + } + + public ConversionCategory getExpected() { + return expected; } - public static class ExcessiveOrMissingFormatArgumentException - extends MissingFormatArgumentException { - private static final long serialVersionUID = 17000126L; - - private final int expected; - private final int found; - - /** - * Constructs an instance of this class with the actual argument length and the expected - * one. - */ - public ExcessiveOrMissingFormatArgumentException(int expected, int found) { - super("-"); - this.expected = expected; - this.found = found; - } - - public int getExpected() { - return expected; - } - - public int getFound() { - return found; - } - - @Override - public String getMessage() { - return String.format("Expected %d arguments but found %d.", expected, found); - } + public ConversionCategory getFound() { + return found; } - public static class IllegalFormatConversionCategoryException - extends IllegalFormatConversionException { - private static final long serialVersionUID = 17000126L; - - private final ConversionCategory expected; - private final ConversionCategory found; - - /** - * Constructs an instance of this class with the mismatched conversion and the expected one. - */ - public IllegalFormatConversionCategoryException( - ConversionCategory expected, ConversionCategory found) { - super( - expected.chars == null || expected.chars.length() == 0 - ? '-' - : expected.chars.charAt(0), - found.types == null ? Object.class : found.types[0]); - this.expected = expected; - this.found = found; - } - - public ConversionCategory getExpected() { - return expected; - } - - public ConversionCategory getFound() { - return found; - } - - @Override - public String getMessage() { - return String.format("Expected category %s but found %s.", expected, found); - } + @Override + public String getMessage() { + return String.format("Expected category %s but found %s.", expected, found); } + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java b/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java index 251579e6968..02302770fc5 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java @@ -1,15 +1,5 @@ package org.checkerframework.checker.i18nformatter.util; -import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat; -import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; -import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat; -import org.checkerframework.checker.interning.qual.InternedDistinct; -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.text.ChoiceFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -21,6 +11,15 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat; +import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; +import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.framework.qual.AnnotatedFor; /** * This class provides a collection of utilities to ease working with i18n format strings. @@ -30,390 +29,380 @@ @AnnotatedFor("nullness") public class I18nFormatUtil { - /** - * Throws an exception if the format is not syntactically valid. - * - * @param format the format string to parse - */ - @SuppressWarnings( - "nullness:argument.type.incompatible") // It's not documented, but passing null as the - // argument array is supported. - public static void tryFormatSatisfiability(String format) throws IllegalFormatException { - MessageFormat.format(format, (Object[]) null); + /** + * Throws an exception if the format is not syntactically valid. + * + * @param format the format string to parse + */ + @SuppressWarnings( + "nullness:argument.type.incompatible") // It's not documented, but passing null as the + // argument array is supported. + public static void tryFormatSatisfiability(String format) throws IllegalFormatException { + MessageFormat.format(format, (Object[]) null); + } + + /** + * Returns a {@link I18nConversionCategory} for every conversion found in the format string. + * + * @param format the format string to parse + * @throws IllegalFormatException if the format is not syntactically valid + */ + public static I18nConversionCategory[] formatParameterCategories(String format) + throws IllegalFormatException { + tryFormatSatisfiability(format); + I18nConversion[] cs = MessageFormatParser.parse(format); + + int maxIndex = -1; + Map conv = new HashMap<>(cs.length); + + for (I18nConversion c : cs) { + int index = c.index; + Integer indexKey = index; + conv.put( + indexKey, + I18nConversionCategory.intersect( + c.category, + conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED)); + maxIndex = Math.max(maxIndex, index); } - /** - * Returns a {@link I18nConversionCategory} for every conversion found in the format string. - * - * @param format the format string to parse - * @throws IllegalFormatException if the format is not syntactically valid - */ - public static I18nConversionCategory[] formatParameterCategories(String format) - throws IllegalFormatException { - tryFormatSatisfiability(format); - I18nConversion[] cs = MessageFormatParser.parse(format); - - int maxIndex = -1; - Map conv = new HashMap<>(cs.length); - - for (I18nConversion c : cs) { - int index = c.index; - Integer indexKey = index; - conv.put( - indexKey, - I18nConversionCategory.intersect( - c.category, - conv.containsKey(indexKey) - ? conv.get(indexKey) - : I18nConversionCategory.UNUSED)); - maxIndex = Math.max(maxIndex, index); - } + I18nConversionCategory[] res = new I18nConversionCategory[maxIndex + 1]; + for (int i = 0; i <= maxIndex; i++) { + Integer indexKey = i; + res[i] = conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED; + } + return res; + } + + /** + * Returns true if the format string is satisfiable, and if the format's parameters match the + * passed {@link I18nConversionCategory}s. Otherwise an error is thrown. + * + * @param format a format string + * @param cc a list of expected categories for the string's format specifiers + * @return true if the format string's specifiers are the given categories, in order + */ + // TODO introduce more such functions, see RegexUtil for examples + @I18nChecksFormat + public static boolean hasFormat(String format, I18nConversionCategory... cc) { + I18nConversionCategory[] fcc = formatParameterCategories(format); + if (fcc.length != cc.length) { + return false; + } - I18nConversionCategory[] res = new I18nConversionCategory[maxIndex + 1]; - for (int i = 0; i <= maxIndex; i++) { - Integer indexKey = i; - res[i] = - conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED; - } - return res; + for (int i = 0; i < cc.length; i++) { + if (!I18nConversionCategory.isSubsetOf(cc[i], fcc[i])) { + return false; + } + } + return true; + } + + @I18nValidFormat + public static boolean isFormat(String format) { + try { + formatParameterCategories(format); + } catch (Exception e) { + return false; } + return true; + } + + /** An I18n conversion directive. */ + private static class I18nConversion { + /** The index into the string. */ + public final int index; + + /** The conversion category. */ + public final I18nConversionCategory category; /** - * Returns true if the format string is satisfiable, and if the format's parameters match the - * passed {@link I18nConversionCategory}s. Otherwise an error is thrown. + * Creates a new I18nConversion. * - * @param format a format string - * @param cc a list of expected categories for the string's format specifiers - * @return true if the format string's specifiers are the given categories, in order + * @param index the index into the string + * @param category the conversion category */ - // TODO introduce more such functions, see RegexUtil for examples - @I18nChecksFormat - public static boolean hasFormat(String format, I18nConversionCategory... cc) { - I18nConversionCategory[] fcc = formatParameterCategories(format); - if (fcc.length != cc.length) { - return false; - } - - for (int i = 0; i < cc.length; i++) { - if (!I18nConversionCategory.isSubsetOf(cc[i], fcc[i])) { - return false; - } - } - return true; + public I18nConversion(int index, I18nConversionCategory category) { + this.index = index; + this.category = category; } - @I18nValidFormat - public static boolean isFormat(String format) { - try { - formatParameterCategories(format); - } catch (Exception e) { - return false; - } - return true; + @Override + public String toString() { + return category.toString() + "(index: " + index + ")"; } + } - /** An I18n conversion directive. */ - private static class I18nConversion { - /** The index into the string. */ - public final int index; - - /** The conversion category. */ - public final I18nConversionCategory category; - - /** - * Creates a new I18nConversion. - * - * @param index the index into the string - * @param category the conversion category - */ - public I18nConversion(int index, I18nConversionCategory category) { - this.index = index; - this.category = category; - } + private static class MessageFormatParser { - @Override - public String toString() { - return category.toString() + "(index: " + index + ")"; - } + public static int maxOffset; + + /** The locale to use for formatting numbers and dates. Is set in {@link #parse}. */ + private static @MonotonicNonNull Locale locale; + + /** An array of formatters, which are used to format the arguments. Is set in {@link #parse}. */ + private static @MonotonicNonNull List categories; + + /** + * The argument numbers corresponding to each formatter. (The formatters are stored in the order + * they occur in the pattern, not in the order in which the arguments are specified.) Is set in + * {@link #parse}. + */ + private static @MonotonicNonNull List argumentIndices; + + // I think this means the number of format specifiers in the format string. + /** The number of subformats. */ + private static int numFormat; + + // Indices for segments + private static final int SEG_RAW = 0; + private static final int SEG_INDEX = 1; + private static final int SEG_TYPE = 2; + private static final int SEG_MODIFIER = 3; // modifier or subformat + + // Indices for type keywords + private static final int TYPE_NULL = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_DATE = 2; + private static final int TYPE_TIME = 3; + private static final int TYPE_CHOICE = 4; + + private static final String[] TYPE_KEYWORDS = {"", "number", "date", "time", "choice"}; + + // Indices for number modifiers + private static final int MODIFIER_DEFAULT = 0; // common in number and date-time + private static final int MODIFIER_CURRENCY = 1; + private static final int MODIFIER_PERCENT = 2; + private static final int MODIFIER_INTEGER = 3; + + private static final String[] NUMBER_MODIFIER_KEYWORDS = {"", "currency", "percent", "integer"}; + + private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { + "", "short", "medium", "long", "full" + }; + + @EnsuresNonNull({"categories", "argumentIndices", "locale"}) + public static I18nConversion[] parse(String pattern) { + MessageFormatParser.categories = new ArrayList<>(); + MessageFormatParser.argumentIndices = new ArrayList<>(); + MessageFormatParser.locale = Locale.getDefault(Locale.Category.FORMAT); + applyPattern(pattern); + + I18nConversion[] ret = new I18nConversion[MessageFormatParser.numFormat]; + for (int i = 0; i < MessageFormatParser.numFormat; i++) { + ret[i] = new I18nConversion(argumentIndices.get(i), categories.get(i)); + } + return ret; } - private static class MessageFormatParser { - - public static int maxOffset; - - /** The locale to use for formatting numbers and dates. Is set in {@link #parse}. */ - private static @MonotonicNonNull Locale locale; - - /** - * An array of formatters, which are used to format the arguments. Is set in {@link #parse}. - */ - private static @MonotonicNonNull List categories; - - /** - * The argument numbers corresponding to each formatter. (The formatters are stored in the - * order they occur in the pattern, not in the order in which the arguments are specified.) - * Is set in {@link #parse}. - */ - private static @MonotonicNonNull List argumentIndices; - - // I think this means the number of format specifiers in the format string. - /** The number of subformats. */ - private static int numFormat; - - // Indices for segments - private static final int SEG_RAW = 0; - private static final int SEG_INDEX = 1; - private static final int SEG_TYPE = 2; - private static final int SEG_MODIFIER = 3; // modifier or subformat - - // Indices for type keywords - private static final int TYPE_NULL = 0; - private static final int TYPE_NUMBER = 1; - private static final int TYPE_DATE = 2; - private static final int TYPE_TIME = 3; - private static final int TYPE_CHOICE = 4; - - private static final String[] TYPE_KEYWORDS = {"", "number", "date", "time", "choice"}; - - // Indices for number modifiers - private static final int MODIFIER_DEFAULT = 0; // common in number and date-time - private static final int MODIFIER_CURRENCY = 1; - private static final int MODIFIER_PERCENT = 2; - private static final int MODIFIER_INTEGER = 3; - - private static final String[] NUMBER_MODIFIER_KEYWORDS = { - "", "currency", "percent", "integer" - }; - - private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { - "", "short", "medium", "long", "full" - }; - - @EnsuresNonNull({"categories", "argumentIndices", "locale"}) - public static I18nConversion[] parse(String pattern) { - MessageFormatParser.categories = new ArrayList<>(); - MessageFormatParser.argumentIndices = new ArrayList<>(); - MessageFormatParser.locale = Locale.getDefault(Locale.Category.FORMAT); - applyPattern(pattern); - - I18nConversion[] ret = new I18nConversion[MessageFormatParser.numFormat]; - for (int i = 0; i < MessageFormatParser.numFormat; i++) { - ret[i] = new I18nConversion(argumentIndices.get(i), categories.get(i)); + @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i] + @RequiresNonNull({"argumentIndices", "categories", "locale"}) + private static void applyPattern(String pattern) { + @Nullable StringBuilder[] segments = new StringBuilder[4]; + // Allocate only segments[SEG_RAW] here. The rest are + // allocated on demand. + segments[SEG_RAW] = new StringBuilder(); + + int part = SEG_RAW; + MessageFormatParser.numFormat = 0; + boolean inQuote = false; + int braceStack = 0; + maxOffset = -1; + for (int i = 0; i < pattern.length(); ++i) { + char ch = pattern.charAt(i); + if (part == SEG_RAW) { + if (ch == '\'') { + if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') { + segments[part].append(ch); // handle doubles + ++i; + } else { + inQuote = !inQuote; } - return ret; - } - - @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i] - @RequiresNonNull({"argumentIndices", "categories", "locale"}) - private static void applyPattern(String pattern) { - @Nullable StringBuilder[] segments = new StringBuilder[4]; - // Allocate only segments[SEG_RAW] here. The rest are - // allocated on demand. - segments[SEG_RAW] = new StringBuilder(); - - int part = SEG_RAW; - MessageFormatParser.numFormat = 0; - boolean inQuote = false; - int braceStack = 0; - maxOffset = -1; - for (int i = 0; i < pattern.length(); ++i) { - char ch = pattern.charAt(i); - if (part == SEG_RAW) { - if (ch == '\'') { - if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') { - segments[part].append(ch); // handle doubles - ++i; - } else { - inQuote = !inQuote; - } - } else if (ch == '{' && !inQuote) { - part = SEG_INDEX; - if (segments[SEG_INDEX] == null) { - segments[SEG_INDEX] = new StringBuilder(); - } - } else { - segments[part].append(ch); - } + } else if (ch == '{' && !inQuote) { + part = SEG_INDEX; + if (segments[SEG_INDEX] == null) { + segments[SEG_INDEX] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + } else { + if (inQuote) { // just copy quotes in parts + segments[part].append(ch); + if (ch == '\'') { + inQuote = false; + } + } else { + switch (ch) { + case ',': + if (part < SEG_MODIFIER) { + if (segments[++part] == null) { + segments[part] = new StringBuilder(); + } } else { - if (inQuote) { // just copy quotes in parts - segments[part].append(ch); - if (ch == '\'') { - inQuote = false; - } - } else { - switch (ch) { - case ',': - if (part < SEG_MODIFIER) { - if (segments[++part] == null) { - segments[part] = new StringBuilder(); - } - } else { - segments[part].append(ch); - } - break; - case '{': - ++braceStack; - segments[part].append(ch); - break; - case '}': - if (braceStack == 0) { - part = SEG_RAW; - makeFormat(numFormat, segments); - numFormat++; - // throw away other segments - segments[SEG_INDEX] = null; - segments[SEG_TYPE] = null; - segments[SEG_MODIFIER] = null; - } else { - --braceStack; - segments[part].append(ch); - } - break; - case ' ': - // Skip any leading space chars for SEG_TYPE. - if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { - segments[part].append(ch); - } - break; - case '\'': - inQuote = true; - segments[part].append(ch); - break; - default: - segments[part].append(ch); - break; - } - } + segments[part].append(ch); } + break; + case '{': + ++braceStack; + segments[part].append(ch); + break; + case '}': + if (braceStack == 0) { + part = SEG_RAW; + makeFormat(numFormat, segments); + numFormat++; + // throw away other segments + segments[SEG_INDEX] = null; + segments[SEG_TYPE] = null; + segments[SEG_MODIFIER] = null; + } else { + --braceStack; + segments[part].append(ch); + } + break; + case ' ': + // Skip any leading space chars for SEG_TYPE. + if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { + segments[part].append(ch); + } + break; + case '\'': + inQuote = true; + segments[part].append(ch); + break; + default: + segments[part].append(ch); + break; } - if (braceStack == 0 && part != 0) { - maxOffset = -1; - throw new IllegalArgumentException("Unmatched braces in the pattern"); - } + } } + } + if (braceStack == 0 && part != 0) { + maxOffset = -1; + throw new IllegalArgumentException("Unmatched braces in the pattern"); + } + } - /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */ - @RequiresNonNull({"argumentIndices", "categories", "locale"}) - private static void makeFormat(int offsetNumber, @Nullable StringBuilder[] textSegments) { - String[] segments = new String[textSegments.length]; - for (int i = 0; i < textSegments.length; i++) { - StringBuilder oneseg = textSegments[i]; - segments[i] = (oneseg != null) ? oneseg.toString() : ""; + /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */ + @RequiresNonNull({"argumentIndices", "categories", "locale"}) + private static void makeFormat(int offsetNumber, @Nullable StringBuilder[] textSegments) { + String[] segments = new String[textSegments.length]; + for (int i = 0; i < textSegments.length; i++) { + StringBuilder oneseg = textSegments[i]; + segments[i] = (oneseg != null) ? oneseg.toString() : ""; + } + + // get the argument number + int argumentNumber; + try { + argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always + // unlocalized! + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "can't parse argument number: " + segments[SEG_INDEX], e); + } + if (argumentNumber < 0) { + throw new IllegalArgumentException("negative argument number: " + argumentNumber); + } + + int oldMaxOffset = maxOffset; + maxOffset = offsetNumber; + argumentIndices.add(argumentNumber); + + // now get the format + final I18nConversionCategory category; + if (segments[SEG_TYPE].length() != 0) { + int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); + switch (type) { + case TYPE_NULL: + category = I18nConversionCategory.GENERAL; + break; + case TYPE_NUMBER: + switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { + case MODIFIER_DEFAULT: + case MODIFIER_CURRENCY: + case MODIFIER_PERCENT: + case MODIFIER_INTEGER: + break; + default: // DecimalFormat pattern + try { + new DecimalFormat( + segments[SEG_MODIFIER], DecimalFormatSymbols.getInstance(locale)); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + // invalid decimal subformat pattern + throw e; + } + break; } - - // get the argument number - int argumentNumber; - try { - argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always - // unlocalized! - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "can't parse argument number: " + segments[SEG_INDEX], e); + category = I18nConversionCategory.NUMBER; + break; + case TYPE_DATE: + case TYPE_TIME: + int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); + if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { + // nothing to do + } else { + // SimpleDateFormat pattern + try { + new SimpleDateFormat(segments[SEG_MODIFIER], locale); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + // invalid date subformat pattern + throw e; + } } - if (argumentNumber < 0) { - throw new IllegalArgumentException("negative argument number: " + argumentNumber); + category = I18nConversionCategory.DATE; + break; + case TYPE_CHOICE: + if (segments[SEG_MODIFIER].length() == 0) { + throw new IllegalArgumentException( + "Choice Pattern requires Subformat Pattern: " + segments[SEG_MODIFIER]); } - - int oldMaxOffset = maxOffset; - maxOffset = offsetNumber; - argumentIndices.add(argumentNumber); - - // now get the format - final I18nConversionCategory category; - if (segments[SEG_TYPE].length() != 0) { - int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); - switch (type) { - case TYPE_NULL: - category = I18nConversionCategory.GENERAL; - break; - case TYPE_NUMBER: - switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { - case MODIFIER_DEFAULT: - case MODIFIER_CURRENCY: - case MODIFIER_PERCENT: - case MODIFIER_INTEGER: - break; - default: // DecimalFormat pattern - try { - new DecimalFormat( - segments[SEG_MODIFIER], - DecimalFormatSymbols.getInstance(locale)); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - // invalid decimal subformat pattern - throw e; - } - break; - } - category = I18nConversionCategory.NUMBER; - break; - case TYPE_DATE: - case TYPE_TIME: - int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); - if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { - // nothing to do - } else { - // SimpleDateFormat pattern - try { - new SimpleDateFormat(segments[SEG_MODIFIER], locale); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - // invalid date subformat pattern - throw e; - } - } - category = I18nConversionCategory.DATE; - break; - case TYPE_CHOICE: - if (segments[SEG_MODIFIER].length() == 0) { - throw new IllegalArgumentException( - "Choice Pattern requires Subformat Pattern: " - + segments[SEG_MODIFIER]); - } - try { - // ChoiceFormat pattern - new ChoiceFormat(segments[SEG_MODIFIER]); - } catch (Exception e) { - maxOffset = oldMaxOffset; - // invalid choice subformat pattern - throw new IllegalArgumentException( - "Choice Pattern incorrect: " + segments[SEG_MODIFIER], e); - } - category = I18nConversionCategory.NUMBER; - break; - default: - maxOffset = oldMaxOffset; - throw new IllegalArgumentException( - "unknown format type: " + segments[SEG_TYPE]); - } - } else { - category = I18nConversionCategory.GENERAL; + try { + // ChoiceFormat pattern + new ChoiceFormat(segments[SEG_MODIFIER]); + } catch (Exception e) { + maxOffset = oldMaxOffset; + // invalid choice subformat pattern + throw new IllegalArgumentException( + "Choice Pattern incorrect: " + segments[SEG_MODIFIER], e); } - categories.add(category); + category = I18nConversionCategory.NUMBER; + break; + default: + maxOffset = oldMaxOffset; + throw new IllegalArgumentException("unknown format type: " + segments[SEG_TYPE]); } + } else { + category = I18nConversionCategory.GENERAL; + } + categories.add(category); + } - /** - * Return the index of s in list. If not found, return the index of - * s.trim().toLowerCase(Locale.ROOT) in list. If still not found, return -1. - */ - private static int findKeyword(String s, String[] list) { - for (int i = 0; i < list.length; ++i) { - if (s.equals(list[i])) { - return i; - } - } - - // Try trimmed lowercase. - @SuppressWarnings("interning:assignment.type.incompatible") // test if value changed - @InternedDistinct String ls = s.trim().toLowerCase(Locale.ROOT); - if (ls != s) { // Don't loop if the string trim().toLowerCase returned the same object. - for (int i = 0; i < list.length; ++i) { - if (ls.equals(list[i])) { - return i; - } - } - } - return -1; + /** + * Return the index of s in list. If not found, return the index of + * s.trim().toLowerCase(Locale.ROOT) in list. If still not found, return -1. + */ + private static int findKeyword(String s, String[] list) { + for (int i = 0; i < list.length; ++i) { + if (s.equals(list[i])) { + return i; + } + } + + // Try trimmed lowercase. + @SuppressWarnings("interning:assignment.type.incompatible") // test if value changed + @InternedDistinct String ls = s.trim().toLowerCase(Locale.ROOT); + if (ls != s) { // Don't loop if the string trim().toLowerCase returned the same object. + for (int i = 0; i < list.length; ++i) { + if (ls.equals(list[i])) { + return i; + } } + } + return -1; } + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java index 0312586d458..c7f897f601f 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java @@ -20,295 +20,294 @@ * checker-qual.jar}, along with your binaries. Or, you can copy this class into your own project. */ @SuppressWarnings({ - "nullness", // Nullness utilities are trusted regarding nullness. - "cast" // Casts look redundant if Nullness Checker is not run. + "nullness", // Nullness utilities are trusted regarding nullness. + "cast" // Casts look redundant if Nullness Checker is not run. }) @AnnotatedFor("nullness") public final class NullnessUtil { - private NullnessUtil() { - throw new AssertionError("shouldn't be instantiated"); - } + private NullnessUtil() { + throw new AssertionError("shouldn't be instantiated"); + } - /** - * A method that suppresses warnings from the Nullness Checker. - * - *

The method takes a possibly-null reference, unsafely casts it to have the @NonNull type - * qualifier, and returns it. The Nullness Checker considers both the return value, and also the - * argument, to be non-null after the method call. Therefore, the {@code castNonNull} method can - * be used either as a cast expression or as a statement. The Nullness Checker issues no - * warnings in any of the following code: - * - *


-     *   // one way to use as a cast:
-     *  {@literal @}NonNull String s = castNonNull(possiblyNull1);
-     *
-     *   // another way to use as a cast:
-     *   castNonNull(possiblyNull2).toString();
-     *
-     *   // one way to use as a statement:
-     *   castNonNull(possiblyNull3);
-     *   possiblyNull3.toString();
-     * 
- * - * The {@code castNonNull} method is intended to be used in situations where the programmer - * definitively knows that a given reference is not null, but the type system is unable to make - * this deduction. It is not intended for defensive programming, in which a programmer cannot - * prove that the value is not null but wishes to have an earlier indication if it is. See the - * Checker Framework Manual for further discussion. - * - *

The method throws {@link AssertionError} if Java assertions are enabled and the argument - * is {@code null}. If the exception is ever thrown, then that indicates that the programmer - * misused the method by using it in a circumstance where its argument can be null. - * - * @param the type of the reference - * @param ref a reference of @Nullable type, that is non-null at run time - * @return the argument, casted to have the type qualifier @NonNull - */ - @EnsuresNonNull("#1") - public static @NonNull T castNonNull(@Nullable T ref) { - assert ref != null : "Misuse of castNonNull: called with a null argument"; - return (@NonNull T) ref; - } + /** + * A method that suppresses warnings from the Nullness Checker. + * + *

The method takes a possibly-null reference, unsafely casts it to have the @NonNull type + * qualifier, and returns it. The Nullness Checker considers both the return value, and also the + * argument, to be non-null after the method call. Therefore, the {@code castNonNull} method can + * be used either as a cast expression or as a statement. The Nullness Checker issues no warnings + * in any of the following code: + * + *


+   *   // one way to use as a cast:
+   *  {@literal @}NonNull String s = castNonNull(possiblyNull1);
+   *
+   *   // another way to use as a cast:
+   *   castNonNull(possiblyNull2).toString();
+   *
+   *   // one way to use as a statement:
+   *   castNonNull(possiblyNull3);
+   *   possiblyNull3.toString();
+   * 
+ * + * The {@code castNonNull} method is intended to be used in situations where the programmer + * definitively knows that a given reference is not null, but the type system is unable to make + * this deduction. It is not intended for defensive programming, in which a programmer cannot + * prove that the value is not null but wishes to have an earlier indication if it is. See the + * Checker Framework Manual for further discussion. + * + *

The method throws {@link AssertionError} if Java assertions are enabled and the argument is + * {@code null}. If the exception is ever thrown, then that indicates that the programmer misused + * the method by using it in a circumstance where its argument can be null. + * + * @param the type of the reference + * @param ref a reference of @Nullable type, that is non-null at run time + * @return the argument, casted to have the type qualifier @NonNull + */ + @EnsuresNonNull("#1") + public static @NonNull T castNonNull(@Nullable T ref) { + assert ref != null : "Misuse of castNonNull: called with a null argument"; + return (@NonNull T) ref; + } - /** - * Suppress warnings from the Nullness Checker, with a custom error message. See {@link - * #castNonNull(Object)} for documentation. - * - * @see #castNonNull(Object) - * @param the type of the reference - * @param ref a reference of @Nullable type, that is non-null at run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull - */ - @EnsuresNonNull("#1") - public static @NonNull T castNonNull( - @Nullable T ref, String message) { - assert ref != null : "Misuse of castNonNull: called with a null argument: " + message; - return (@NonNull T) ref; - } + /** + * Suppress warnings from the Nullness Checker, with a custom error message. See {@link + * #castNonNull(Object)} for documentation. + * + * @see #castNonNull(Object) + * @param the type of the reference + * @param ref a reference of @Nullable type, that is non-null at run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull + */ + @EnsuresNonNull("#1") + public static @NonNull T castNonNull( + @Nullable T ref, String message) { + assert ref != null : "Misuse of castNonNull: called with a null argument: " + message; + return (@NonNull T) ref; + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [] castNonNullDeep( - T @Nullable [] arr) { - return (@NonNull T[]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [] castNonNullDeep( + T @Nullable [] arr) { + return (@NonNull T[]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [] castNonNullDeep( - T @Nullable [] arr, String message) { - return (@NonNull T[]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [] castNonNullDeep( + T @Nullable [] arr, String message) { + return (@NonNull T[]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][] castNonNullDeep( - T @Nullable [] @Nullable [] arr) { - return (@NonNull T[][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type of the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][] castNonNullDeep( + T @Nullable [] @Nullable [] arr) { + return (@NonNull T[][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][] castNonNullDeep( - T @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type of the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][] castNonNullDeep( + T @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (three levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type (three levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (three levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type (three levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][][]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (four levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][][][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type (four levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][][][]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (four levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][][][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type (four levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][][][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (five levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, - String message) { - return (@NonNull T[][][][][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that all + * elements at every array level are non-null. + * + * @param the component type (five levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][][][][]) castNonNullArray(arr, message); + } - /** - * The implementation of castNonNullDeep. - * - * @param the component type (five levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if there is a non-null value, or null to use uncustomized - * message - * @return the argument, casted to have the type qualifier @NonNull at all levels - */ - private static @NonNull T @NonNull [] castNonNullArray( - T @Nullable [] arr, @Nullable String message) { - assert arr != null - : "Misuse of castNonNullArray: called with a null array argument" - + ((message == null) ? "" : (": " + message)); - for (int i = 0; i < arr.length; ++i) { - assert arr[i] != null - : "Misuse of castNonNull: called with a null array element" - + ((message == null) ? "" : (": " + message)); - checkIfArray(arr[i], message); - } - return (@NonNull T[]) arr; + /** + * The implementation of castNonNullDeep. + * + * @param the component type (five levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at run + * time + * @param message text to include if there is a non-null value, or null to use uncustomized + * message + * @return the argument, casted to have the type qualifier @NonNull at all levels + */ + private static @NonNull T @NonNull [] castNonNullArray( + T @Nullable [] arr, @Nullable String message) { + assert arr != null + : "Misuse of castNonNullArray: called with a null array argument" + + ((message == null) ? "" : (": " + message)); + for (int i = 0; i < arr.length; ++i) { + assert arr[i] != null + : "Misuse of castNonNull: called with a null array element" + + ((message == null) ? "" : (": " + message)); + checkIfArray(arr[i], message); } + return (@NonNull T[]) arr; + } - /** - * If the argument is an array, requires it to be non-null at all levels. - * - * @param ref a value; if an array, all of its elements, and their elements recursively, are - * non-null at run time - * @param message text to include if there is a non-null value, or null to use uncustomized - * message - */ - private static void checkIfArray(@NonNull Object ref, @Nullable String message) { - assert ref != null - : "Misuse of checkIfArray: called with a null argument" - + ((message == null) ? "" : (": " + message)); - Class comp = ref.getClass().getComponentType(); - if (comp != null) { - // comp is non-null for arrays, otherwise null. - if (comp.isPrimitive()) { - // Nothing to do for arrays of primitive type: primitives are - // never null. - } else { - castNonNullArray((Object[]) ref, message); - } - } + /** + * If the argument is an array, requires it to be non-null at all levels. + * + * @param ref a value; if an array, all of its elements, and their elements recursively, are + * non-null at run time + * @param message text to include if there is a non-null value, or null to use uncustomized + * message + */ + private static void checkIfArray(@NonNull Object ref, @Nullable String message) { + assert ref != null + : "Misuse of checkIfArray: called with a null argument" + + ((message == null) ? "" : (": " + message)); + Class comp = ref.getClass().getComponentType(); + if (comp != null) { + // comp is non-null for arrays, otherwise null. + if (comp.isPrimitive()) { + // Nothing to do for arrays of primitive type: primitives are + // never null. + } else { + castNonNullArray((Object[]) ref, message); + } } + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java index 06631c75689..43d8a078f44 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java +++ b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java @@ -1,15 +1,14 @@ package org.checkerframework.checker.nullness.util; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.util.NoSuchElementException; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; /** * Utility class providing every method in {@link java.util.Optional}, but written for possibly-null @@ -32,115 +31,115 @@ @SuppressWarnings("NullableWildcard") // Set upper and lower bound of wildcards public final class Opt { - /** The Opt class cannot be instantiated. */ - private Opt() { - throw new AssertionError("shouldn't be instantiated"); - } + /** The Opt class cannot be instantiated. */ + private Opt() { + throw new AssertionError("shouldn't be instantiated"); + } - /** - * If primary is non-null, returns it, otherwise throws NoSuchElementException. - * - * @param the type of the argument - * @param primary a non-null value to return - * @return {@code primary} if it is non-null - * @throws NoSuchElementException if primary is null - * @see java.util.Optional#get() - */ - // `primary` is @NonNull; otherwise, the method could throw an exception. - public static T get(T primary) { - if (primary == null) { - throw new NoSuchElementException("No value present"); - } - return primary; + /** + * If primary is non-null, returns it, otherwise throws NoSuchElementException. + * + * @param the type of the argument + * @param primary a non-null value to return + * @return {@code primary} if it is non-null + * @throws NoSuchElementException if primary is null + * @see java.util.Optional#get() + */ + // `primary` is @NonNull; otherwise, the method could throw an exception. + public static T get(T primary) { + if (primary == null) { + throw new NoSuchElementException("No value present"); } + return primary; + } - /** - * Returns true if primary is non-null, false if primary is null. - * - * @see java.util.Optional#isPresent() - */ - @EnsuresNonNullIf(expression = "#1", result = true) - public static boolean isPresent(@Nullable Object primary) { - return primary != null; - } + /** + * Returns true if primary is non-null, false if primary is null. + * + * @see java.util.Optional#isPresent() + */ + @EnsuresNonNullIf(expression = "#1", result = true) + public static boolean isPresent(@Nullable Object primary) { + return primary != null; + } - /** - * If primary is non-null, invoke the specified consumer with the value, otherwise do nothing. - * - * @see java.util.Optional#ifPresent(Consumer) - */ - public static void ifPresent(T primary, Consumer<@NonNull ? super @NonNull T> consumer) { - if (primary != null) { - consumer.accept(primary); - } + /** + * If primary is non-null, invoke the specified consumer with the value, otherwise do nothing. + * + * @see java.util.Optional#ifPresent(Consumer) + */ + public static void ifPresent(T primary, Consumer<@NonNull ? super @NonNull T> consumer) { + if (primary != null) { + consumer.accept(primary); } + } - // TODO: Add ifPresentOrElse. + // TODO: Add ifPresentOrElse. - /** - * If primary is non-null, and its value matches the given predicate, return the value. If - * primary is null or its non-null value does not match the predicate, return null. - * - * @see java.util.Optional#filter(Predicate) - */ - public static @Nullable T filter( - T primary, Predicate<@NonNull ? super @NonNull T> predicate) { - if (primary == null) { - return null; - } else { - return predicate.test(primary) ? primary : null; - } + /** + * If primary is non-null, and its value matches the given predicate, return the value. If primary + * is null or its non-null value does not match the predicate, return null. + * + * @see java.util.Optional#filter(Predicate) + */ + public static @Nullable T filter( + T primary, Predicate<@NonNull ? super @NonNull T> predicate) { + if (primary == null) { + return null; + } else { + return predicate.test(primary) ? primary : null; } + } - /** - * If primary is non-null, apply the provided mapping function to it and return the result. If - * primary is null, return null. - * - * @see java.util.Optional#map(Function) - */ - public static @Nullable U map( - T primary, Function<@NonNull ? super @NonNull T, ? extends U> mapper) { - if (primary == null) { - return null; - } else { - return mapper.apply(primary); - } + /** + * If primary is non-null, apply the provided mapping function to it and return the result. If + * primary is null, return null. + * + * @see java.util.Optional#map(Function) + */ + public static @Nullable U map( + T primary, Function<@NonNull ? super @NonNull T, ? extends U> mapper) { + if (primary == null) { + return null; + } else { + return mapper.apply(primary); } + } - // flatMap would have the same signature and implementation as map + // flatMap would have the same signature and implementation as map - /** - * Return primary if it is non-null. If primary is null, return other. - * - * @see java.util.Optional#orElse(Object) - */ - public static @NonNull T orElse(T primary, @NonNull T other) { - return primary != null ? primary : other; - } + /** + * Return primary if it is non-null. If primary is null, return other. + * + * @see java.util.Optional#orElse(Object) + */ + public static @NonNull T orElse(T primary, @NonNull T other) { + return primary != null ? primary : other; + } - /** - * Return {@code primary} if it is non-null. If {@code primary} is null, invoke {@code other} - * and return the result of that invocation. - * - * @see java.util.Optional#orElseGet(Supplier) - */ - public static @NonNull T orElseGet(T primary, Supplier other) { - return primary != null ? primary : other.get(); - } + /** + * Return {@code primary} if it is non-null. If {@code primary} is null, invoke {@code other} and + * return the result of that invocation. + * + * @see java.util.Optional#orElseGet(Supplier) + */ + public static @NonNull T orElseGet(T primary, Supplier other) { + return primary != null ? primary : other.get(); + } - /** - * Return primary if it is non-null. If primary is null, throw an exception to be created by the - * provided supplier. - * - * @see java.util.Optional#orElseThrow(Supplier) - */ - // `primary` is @NonNull; otherwise, the method could throw an exception. - public static T orElseThrow( - T primary, Supplier exceptionSupplier) throws X { - if (primary != null) { - return primary; - } else { - throw exceptionSupplier.get(); - } + /** + * Return primary if it is non-null. If primary is null, throw an exception to be created by the + * provided supplier. + * + * @see java.util.Optional#orElseThrow(Supplier) + */ + // `primary` is @NonNull; otherwise, the method could throw an exception. + public static T orElseThrow( + T primary, Supplier exceptionSupplier) throws X { + if (primary != null) { + return primary; + } else { + throw exceptionSupplier.get(); } + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java b/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java index b8dcf7803b8..e6f078b9191 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional.util; +import java.util.Optional; import org.checkerframework.checker.optional.qual.EnsuresPresent; import org.checkerframework.checker.optional.qual.MaybePresent; import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.framework.qual.AnnotatedFor; -import java.util.Optional; - /** * This is a utility class for the Optional Checker. * @@ -22,55 +21,55 @@ * checker-qual.jar}, along with your binaries. Or, you can copy this class into your own project. */ @SuppressWarnings({ - "optional", // Optional utilities are trusted regarding the Optional type. - "cast" // Casts look redundant if Optional Checker is not run. + "optional", // Optional utilities are trusted regarding the Optional type. + "cast" // Casts look redundant if Optional Checker is not run. }) @AnnotatedFor("optional") public final class OptionalUtil { - /** The OptionalUtil class should not be instantiated. */ - private OptionalUtil() { - throw new AssertionError("do not instantiate"); - } + /** The OptionalUtil class should not be instantiated. */ + private OptionalUtil() { + throw new AssertionError("do not instantiate"); + } - /** - * A method that suppresses warnings from the Optional Checker. - * - *

The method takes a possibly-empty Optional reference, unsafely casts it to have - * the @Present type qualifier, and returns it. The Optional Checker considers both the return - * value, and also the argument, to be present after the method call. Therefore, the {@code - * castPresent} method can be used either as a cast expression or as a statement. - * - *


-     *   // one way to use as a cast:
-     *  {@literal @}Present String s = castPresent(possiblyEmpty1);
-     *
-     *   // another way to use as a cast:
-     *   castPresent(possiblyEmpty2).toString();
-     *
-     *   // one way to use as a statement:
-     *   castPresent(possiblyEmpty3);
-     *   possiblyEmpty3.toString();
-     * 
- * - * The {@code castPresent} method is intended to be used in situations where the programmer - * definitively knows that a given Optional reference is present, but the type system is unable - * to make this deduction. It is not intended for defensive programming, in which a programmer - * cannot prove that the value is not empty but wishes to have an earlier indication if it is. - * See the Checker Framework Manual for further discussion. - * - *

The method throws {@link AssertionError} if Java assertions are enabled and the argument - * is empty. If the exception is ever thrown, then that indicates that the programmer misused - * the method by using it in a circumstance where its argument can be empty. - * - * @param the type of content of the Optional - * @param ref an Optional reference of @MaybePresent type, that is present at run time - * @return the argument, casted to have the type qualifier @Present - */ - @EnsuresPresent("#1") - public static @Present Optional castPresent( - @MaybePresent Optional ref) { - assert ref.isPresent() : "Misuse of castPresent: called with an empty Optional"; - return (@Present Optional) ref; - } + /** + * A method that suppresses warnings from the Optional Checker. + * + *

The method takes a possibly-empty Optional reference, unsafely casts it to have the @Present + * type qualifier, and returns it. The Optional Checker considers both the return value, and also + * the argument, to be present after the method call. Therefore, the {@code castPresent} method + * can be used either as a cast expression or as a statement. + * + *


+   *   // one way to use as a cast:
+   *  {@literal @}Present String s = castPresent(possiblyEmpty1);
+   *
+   *   // another way to use as a cast:
+   *   castPresent(possiblyEmpty2).toString();
+   *
+   *   // one way to use as a statement:
+   *   castPresent(possiblyEmpty3);
+   *   possiblyEmpty3.toString();
+   * 
+ * + * The {@code castPresent} method is intended to be used in situations where the programmer + * definitively knows that a given Optional reference is present, but the type system is unable to + * make this deduction. It is not intended for defensive programming, in which a programmer cannot + * prove that the value is not empty but wishes to have an earlier indication if it is. See the + * Checker Framework Manual for further discussion. + * + *

The method throws {@link AssertionError} if Java assertions are enabled and the argument is + * empty. If the exception is ever thrown, then that indicates that the programmer misused the + * method by using it in a circumstance where its argument can be empty. + * + * @param the type of content of the Optional + * @param ref an Optional reference of @MaybePresent type, that is present at run time + * @return the argument, casted to have the type qualifier @Present + */ + @EnsuresPresent("#1") + public static @Present Optional castPresent( + @MaybePresent Optional ref) { + assert ref.isPresent() : "Misuse of castPresent: called with an empty Optional"; + return (@Present Optional) ref; + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java b/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java index 1abadef0a2f..81ad1edca4d 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java @@ -3,6 +3,13 @@ package org.checkerframework.checker.regex.util; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.checkerframework.checker.index.qual.GTENegativeOne; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.mustcall.qual.MustCallUnknown; @@ -15,14 +22,6 @@ import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.qual.EnsuresQualifierIf; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.RandomAccess; -import java.util.function.Function; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - /** * Utility methods for regular expressions, most notably for testing whether a string is a regular * expression. @@ -37,448 +36,446 @@ @AnnotatedFor("nullness") public final class RegexUtil { - /** This class is a collection of methods; it does not represent anything. */ - private RegexUtil() { - throw new Error("do not instantiate"); - } - - /** - * A checked version of {@link PatternSyntaxException}. - * - *

This exception is useful when an illegal regex is detected but the contextual information - * to report a helpful error message is not available at the current depth in the call stack. By - * using a checked PatternSyntaxException the error must be handled up the call stack where a - * better error message can be reported. - * - *

Typical usage is: - * - *

-     * void myMethod(...) throws CheckedPatternSyntaxException {
-     *   ...
-     *   if (! isRegex(myString)) {
-     *     throw new CheckedPatternSyntaxException(...);
-     *   }
-     *   ... Pattern.compile(myString) ...
-     * 
- * - * Simply calling {@code Pattern.compile} would have a similar effect, in that {@code - * PatternSyntaxException} would be thrown at run time if {@code myString} is not a regular - * expression. There are two problems with such an approach. First, a client of {@code myMethod} - * might forget to handle the exception, since {@code PatternSyntaxException} is not checked. - * Also, the Regex Checker would issue a warning about the call to {@code Pattern.compile} that - * might throw an exception. The above usage pattern avoids both problems. - * - * @see PatternSyntaxException - */ - public static class CheckedPatternSyntaxException extends Exception { - - /** Unique identifier for serialization. If you add or remove fields, change this number. */ - private static final long serialVersionUID = 6266881831979001480L; - - /** The PatternSyntaxException that this is a wrapper around. */ - private final PatternSyntaxException pse; - - /** - * Constructs a new CheckedPatternSyntaxException equivalent to the given {@link - * PatternSyntaxException}. - * - *

Consider calling this constructor with the result of {@link RegexUtil#regexError}. - * - * @param pse the PatternSyntaxException to be wrapped - */ - public CheckedPatternSyntaxException(PatternSyntaxException pse) { - this.pse = pse; - } - - /** - * Constructs a new CheckedPatternSyntaxException. - * - * @param desc a description of the error - * @param regex the erroneous pattern - * @param index the approximate index in the pattern of the error, or {@code -1} if the - * index is not known - */ - public CheckedPatternSyntaxException(String desc, String regex, @GTENegativeOne int index) { - this(new PatternSyntaxException(desc, regex, index)); - } - - /** - * Retrieves the description of the error. - * - * @return the description of the error - */ - public String getDescription() { - return pse.getDescription(); - } - - /** - * Retrieves the error index. - * - * @return the approximate index in the pattern of the error, or {@code -1} if the index is - * not known - */ - public int getIndex() { - return pse.getIndex(); - } - - /** - * Returns a multi-line string containing the description of the syntax error and its index, - * the erroneous regular-expression pattern, and a visual indication of the error index - * within the pattern. - * - * @return the full detail message - */ - @Override - @Pure - public String getMessage(@GuardSatisfied CheckedPatternSyntaxException this) { - return pse.getMessage(); - } - - /** - * Retrieves the erroneous regular-expression pattern. - * - * @return the erroneous pattern - */ - public String getPattern() { - return pse.getPattern(); - } - } + /** This class is a collection of methods; it does not represent anything. */ + private RegexUtil() { + throw new Error("do not instantiate"); + } + + /** + * A checked version of {@link PatternSyntaxException}. + * + *

This exception is useful when an illegal regex is detected but the contextual information to + * report a helpful error message is not available at the current depth in the call stack. By + * using a checked PatternSyntaxException the error must be handled up the call stack where a + * better error message can be reported. + * + *

Typical usage is: + * + *

+   * void myMethod(...) throws CheckedPatternSyntaxException {
+   *   ...
+   *   if (! isRegex(myString)) {
+   *     throw new CheckedPatternSyntaxException(...);
+   *   }
+   *   ... Pattern.compile(myString) ...
+   * 
+ * + * Simply calling {@code Pattern.compile} would have a similar effect, in that {@code + * PatternSyntaxException} would be thrown at run time if {@code myString} is not a regular + * expression. There are two problems with such an approach. First, a client of {@code myMethod} + * might forget to handle the exception, since {@code PatternSyntaxException} is not checked. + * Also, the Regex Checker would issue a warning about the call to {@code Pattern.compile} that + * might throw an exception. The above usage pattern avoids both problems. + * + * @see PatternSyntaxException + */ + public static class CheckedPatternSyntaxException extends Exception { + + /** Unique identifier for serialization. If you add or remove fields, change this number. */ + private static final long serialVersionUID = 6266881831979001480L; + + /** The PatternSyntaxException that this is a wrapper around. */ + private final PatternSyntaxException pse; /** - * Returns true if the argument is a syntactically valid regular expression. + * Constructs a new CheckedPatternSyntaxException equivalent to the given {@link + * PatternSyntaxException}. * - * @param s string to check for being a regular expression - * @return true iff s is a regular expression - */ - @Pure - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(String s) { - return isRegex(s, 0); - } - - /** - * Returns true if the argument is a syntactically valid regular expression with at least the - * given number of groups. + *

Consider calling this constructor with the result of {@link RegexUtil#regexError}. * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return true iff s is a regular expression with {@code groups} groups + * @param pse the PatternSyntaxException to be wrapped */ - @SuppressWarnings("regex") // RegexUtil - @Pure - // @EnsuresQualifierIf annotation is extraneous because this method is special-cased - // in RegexTransfer. - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(String s, int groups) { - Pattern p; - try { - p = Pattern.compile(s); - } catch (PatternSyntaxException e) { - return false; - } - return getGroupCount(p) >= groups; + public CheckedPatternSyntaxException(PatternSyntaxException pse) { + this.pse = pse; } /** - * Returns true if the argument is a syntactically valid regular expression. + * Constructs a new CheckedPatternSyntaxException. * - * @param c char to check for being a regular expression - * @return true iff c is a regular expression + * @param desc a description of the error + * @param regex the erroneous pattern + * @param index the approximate index in the pattern of the error, or {@code -1} if the index is + * not known */ - @SuppressWarnings({ - "regex", "lock" - }) // RegexUtil; temp value used in pure method is equal up to equals but not up to == - @Pure - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(char c) { - return isRegex(Character.toString(c)); + public CheckedPatternSyntaxException(String desc, String regex, @GTENegativeOne int index) { + this(new PatternSyntaxException(desc, regex, index)); } /** - * Returns the argument as a {@code @Regex String} if it is a regex, otherwise throws an error. - * The purpose of this method is to suppress Regex Checker warnings. It should be very rarely - * needed. + * Retrieves the description of the error. * - * @param s string to check for being a regular expression - * @return its argument - * @throws Error if argument is not a regex + * @return the description of the error */ - @SideEffectFree - // The return type annotation is irrelevant; this method is special-cased by - // RegexAnnotatedTypeFactory. - public static @Regex String asRegex(String s) { - return asRegex(s, 0); + public String getDescription() { + return pse.getDescription(); } /** - * Returns the argument as a {@code @Regex(groups) String} if it is a regex with at least the - * given number of groups, otherwise throws an error. The purpose of this method is to suppress - * Regex Checker warnings. It should be very rarely needed. + * Retrieves the error index. * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return its argument - * @throws Error if argument is not a regex + * @return the approximate index in the pattern of the error, or {@code -1} if the index is not + * known */ - @SuppressWarnings("regex") // RegexUtil - @SideEffectFree - // The return type annotation is irrelevant; this method is special-cased by - // RegexAnnotatedTypeFactory. - public static @Regex String asRegex(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - throw new Error(regexErrorMessage(s, groups, actualGroups)); - } - return s; - } catch (PatternSyntaxException e) { - throw new Error(e); - } + public int getIndex() { + return pse.getIndex(); } /** - * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a - * string describing why the argument is not a regex. + * Returns a multi-line string containing the description of the syntax error and its index, the + * erroneous regular-expression pattern, and a visual indication of the error index within the + * pattern. * - * @param s string to check for being a regular expression - * @return null, or a string describing why the argument is not a regex + * @return the full detail message */ - @SideEffectFree - public static @Nullable String regexError(String s) { - return regexError(s, 0); + @Override + @Pure + public String getMessage(@GuardSatisfied CheckedPatternSyntaxException this) { + return pse.getMessage(); } /** - * Returns null if the argument is a syntactically valid regular expression with at least the - * given number of groups. Otherwise returns a string describing why the argument is not a - * regex. + * Retrieves the erroneous regular-expression pattern. * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return null, or a string describing why the argument is not a regex + * @return the erroneous pattern */ - @SuppressWarnings({"regex", "not.sef"}) // RegexUtil; - @SideEffectFree - public static @Nullable String regexError(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - return regexErrorMessage(s, groups, actualGroups); - } - } catch (PatternSyntaxException e) { - return e.getMessage(); - } - return null; + public String getPattern() { + return pse.getPattern(); } - - /** - * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a - * PatternSyntaxException describing why the argument is not a regex. - * - * @param s string to check for being a regular expression - * @return null, or a PatternSyntaxException describing why the argument is not a regex - */ - @SideEffectFree - public static @Nullable PatternSyntaxException regexException(String s) { - return regexException(s, 0); + } + + /** + * Returns true if the argument is a syntactically valid regular expression. + * + * @param s string to check for being a regular expression + * @return true iff s is a regular expression + */ + @Pure + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(String s) { + return isRegex(s, 0); + } + + /** + * Returns true if the argument is a syntactically valid regular expression with at least the + * given number of groups. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return true iff s is a regular expression with {@code groups} groups + */ + @SuppressWarnings("regex") // RegexUtil + @Pure + // @EnsuresQualifierIf annotation is extraneous because this method is special-cased + // in RegexTransfer. + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(String s, int groups) { + Pattern p; + try { + p = Pattern.compile(s); + } catch (PatternSyntaxException e) { + return false; } - - /** - * Returns null if the argument is a syntactically valid regular expression with at least the - * given number of groups. Otherwise returns a PatternSyntaxException describing why the - * argument is not a regex. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return null, or a PatternSyntaxException describing why the argument is not a regex - */ - @SuppressWarnings("regex") // RegexUtil - @SideEffectFree - public static @Nullable PatternSyntaxException regexException(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - return new PatternSyntaxException( - regexErrorMessage(s, groups, actualGroups), s, -1); - } - } catch (PatternSyntaxException pse) { - return pse; - } - return null; + return getGroupCount(p) >= groups; + } + + /** + * Returns true if the argument is a syntactically valid regular expression. + * + * @param c char to check for being a regular expression + * @return true iff c is a regular expression + */ + @SuppressWarnings({ + "regex", "lock" + }) // RegexUtil; temp value used in pure method is equal up to equals but not up to == + @Pure + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(char c) { + return isRegex(Character.toString(c)); + } + + /** + * Returns the argument as a {@code @Regex String} if it is a regex, otherwise throws an error. + * The purpose of this method is to suppress Regex Checker warnings. It should be very rarely + * needed. + * + * @param s string to check for being a regular expression + * @return its argument + * @throws Error if argument is not a regex + */ + @SideEffectFree + // The return type annotation is irrelevant; this method is special-cased by + // RegexAnnotatedTypeFactory. + public static @Regex String asRegex(String s) { + return asRegex(s, 0); + } + + /** + * Returns the argument as a {@code @Regex(groups) String} if it is a regex with at least the + * given number of groups, otherwise throws an error. The purpose of this method is to suppress + * Regex Checker warnings. It should be very rarely needed. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return its argument + * @throws Error if argument is not a regex + */ + @SuppressWarnings("regex") // RegexUtil + @SideEffectFree + // The return type annotation is irrelevant; this method is special-cased by + // RegexAnnotatedTypeFactory. + public static @Regex String asRegex(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + throw new Error(regexErrorMessage(s, groups, actualGroups)); + } + return s; + } catch (PatternSyntaxException e) { + throw new Error(e); } - - /** - * Generates an error message for s when expectedGroups are needed, but s only has actualGroups. - * - * @param s string to check for being a regular expression - * @param expectedGroups the number of needed capturing groups - * @param actualGroups the number of groups that {@code s} has - * @return an error message for s when expectedGroups groups are needed, but s only has - * actualGroups groups - */ - @SideEffectFree - private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) { - return "regex \"" - + s - + "\" has " - + actualGroups - + " groups, but " - + expectedGroups - + " groups are needed."; + } + + /** + * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a + * string describing why the argument is not a regex. + * + * @param s string to check for being a regular expression + * @return null, or a string describing why the argument is not a regex + */ + @SideEffectFree + public static @Nullable String regexError(String s) { + return regexError(s, 0); + } + + /** + * Returns null if the argument is a syntactically valid regular expression with at least the + * given number of groups. Otherwise returns a string describing why the argument is not a regex. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return null, or a string describing why the argument is not a regex + */ + @SuppressWarnings({"regex", "not.sef"}) // RegexUtil; + @SideEffectFree + public static @Nullable String regexError(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + return regexErrorMessage(s, groups, actualGroups); + } + } catch (PatternSyntaxException e) { + return e.getMessage(); } - - /** - * Returns the count of groups in the argument. - * - * @param p pattern whose groups to count - * @return the count of groups in the argument - */ - @SuppressWarnings("lock") // does not depend on object identity - @Pure - private static int getGroupCount(Pattern p) { - return p.matcher("").groupCount(); + return null; + } + + /** + * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a + * PatternSyntaxException describing why the argument is not a regex. + * + * @param s string to check for being a regular expression + * @return null, or a PatternSyntaxException describing why the argument is not a regex + */ + @SideEffectFree + public static @Nullable PatternSyntaxException regexException(String s) { + return regexException(s, 0); + } + + /** + * Returns null if the argument is a syntactically valid regular expression with at least the + * given number of groups. Otherwise returns a PatternSyntaxException describing why the argument + * is not a regex. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return null, or a PatternSyntaxException describing why the argument is not a regex + */ + @SuppressWarnings("regex") // RegexUtil + @SideEffectFree + public static @Nullable PatternSyntaxException regexException(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + return new PatternSyntaxException(regexErrorMessage(s, groups, actualGroups), s, -1); + } + } catch (PatternSyntaxException pse) { + return pse; } - - /** - * Return the strings such that any one of the regexes matches it. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return the strings such that any one of the regexes matches it - */ - public static List matchesSomeRegex( - Collection strings, Collection<@Regex String> regexes) { - List patterns = mapList(Pattern::compile, regexes); - List result = new ArrayList(strings.size()); - for (String s : strings) { - for (Pattern p : patterns) { - if (p.matcher(s).matches()) { - result.add(s); - break; - } - } + return null; + } + + /** + * Generates an error message for s when expectedGroups are needed, but s only has actualGroups. + * + * @param s string to check for being a regular expression + * @param expectedGroups the number of needed capturing groups + * @param actualGroups the number of groups that {@code s} has + * @return an error message for s when expectedGroups groups are needed, but s only has + * actualGroups groups + */ + @SideEffectFree + private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) { + return "regex \"" + + s + + "\" has " + + actualGroups + + " groups, but " + + expectedGroups + + " groups are needed."; + } + + /** + * Returns the count of groups in the argument. + * + * @param p pattern whose groups to count + * @return the count of groups in the argument + */ + @SuppressWarnings("lock") // does not depend on object identity + @Pure + private static int getGroupCount(Pattern p) { + return p.matcher("").groupCount(); + } + + /** + * Return the strings such that any one of the regexes matches it. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return the strings such that any one of the regexes matches it + */ + public static List matchesSomeRegex( + Collection strings, Collection<@Regex String> regexes) { + List patterns = mapList(Pattern::compile, regexes); + List result = new ArrayList(strings.size()); + for (String s : strings) { + for (Pattern p : patterns) { + if (p.matcher(s).matches()) { + result.add(s); + break; } - return result; + } } - - /** - * Return true if every string is matched by at least one regex. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return true if every string is matched by at least one regex - */ - public static boolean everyStringMatchesSomeRegex( - Collection strings, Collection<@Regex String> regexes) { - List patterns = mapList(Pattern::compile, regexes); - outer: - for (String s : strings) { - for (Pattern p : patterns) { - if (p.matcher(s).matches()) { - continue outer; - } - } - return false; + return result; + } + + /** + * Return true if every string is matched by at least one regex. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return true if every string is matched by at least one regex + */ + public static boolean everyStringMatchesSomeRegex( + Collection strings, Collection<@Regex String> regexes) { + List patterns = mapList(Pattern::compile, regexes); + outer: + for (String s : strings) { + for (Pattern p : patterns) { + if (p.matcher(s).matches()) { + continue outer; } - return true; + } + return false; } - - /** - * Return the strings that are matched by no regex. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return the strings such that none of the regexes matches it - */ - public static List matchesNoRegex( - Collection strings, Collection<@Regex String> regexes) { - List patterns = mapList(Pattern::compile, regexes); - List result = new ArrayList(strings.size()); - outer: - for (String s : strings) { - for (Pattern p : patterns) { - if (p.matcher(s).matches()) { - continue outer; - } - } - result.add(s); + return true; + } + + /** + * Return the strings that are matched by no regex. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return the strings such that none of the regexes matches it + */ + public static List matchesNoRegex( + Collection strings, Collection<@Regex String> regexes) { + List patterns = mapList(Pattern::compile, regexes); + List result = new ArrayList(strings.size()); + outer: + for (String s : strings) { + for (Pattern p : patterns) { + if (p.matcher(s).matches()) { + continue outer; } - return result; + } + result.add(s); } - - /** - * Return true if no string is matched by any regex. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return true if no string is matched by any regex - */ - public static boolean noStringMatchesAnyRegex( - Collection strings, Collection<@Regex String> regexes) { - for (String regex : regexes) { - Pattern p = Pattern.compile(regex); - for (String s : strings) { - if (p.matcher(s).matches()) { - return false; - } - } + return result; + } + + /** + * Return true if no string is matched by any regex. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return true if no string is matched by any regex + */ + public static boolean noStringMatchesAnyRegex( + Collection strings, Collection<@Regex String> regexes) { + for (String regex : regexes) { + Pattern p = Pattern.compile(regex); + for (String s : strings) { + if (p.matcher(s).matches()) { + return false; } - return true; + } + } + return true; + } + + /// + /// Utilities + /// + + // This is from CollectionsPlume, but is here to make the file self-contained. + + /** + * Applies the function to each element of the given iterable, producing a list of the results. + * + *

The point of this method is to make mapping operations more concise. Import it with + * + *

import static org.plumelib.util.CollectionsPlume.mapList;
+ * + * This method is just like {@code transform}, but with the arguments in the other order. + * + *

To perform replacement in place, see {@code List.replaceAll}. + * + * @param the type of elements of the given iterable + * @param the type of elements of the result list + * @param f a function + * @param iterable an iterable + * @return a list of the results of applying {@code f} to the elements of {@code iterable} + */ + public static < + @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, + @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> + List mapList( + @MustCallUnknown Function<@MustCallUnknown ? super FROM, ? extends TO> f, + Iterable iterable) { + List result; + + if (iterable instanceof RandomAccess) { + // Per the Javadoc of RandomAccess, an indexed for loop is faster than a foreach loop. + List list = (List) iterable; + int size = list.size(); + result = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + result.add(f.apply(list.get(i))); + } + return result; } - /// - /// Utilities - /// - - // This is from CollectionsPlume, but is here to make the file self-contained. - - /** - * Applies the function to each element of the given iterable, producing a list of the results. - * - *

The point of this method is to make mapping operations more concise. Import it with - * - *

import static org.plumelib.util.CollectionsPlume.mapList;
- * - * This method is just like {@code transform}, but with the arguments in the other order. - * - *

To perform replacement in place, see {@code List.replaceAll}. - * - * @param the type of elements of the given iterable - * @param the type of elements of the result list - * @param f a function - * @param iterable an iterable - * @return a list of the results of applying {@code f} to the elements of {@code iterable} - */ - public static < - @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, - @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> - List mapList( - @MustCallUnknown Function<@MustCallUnknown ? super FROM, ? extends TO> f, - Iterable iterable) { - List result; - - if (iterable instanceof RandomAccess) { - // Per the Javadoc of RandomAccess, an indexed for loop is faster than a foreach loop. - List list = (List) iterable; - int size = list.size(); - result = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - result.add(f.apply(list.get(i))); - } - return result; - } - - if (iterable instanceof Collection) { - result = new ArrayList<>(((Collection) iterable).size()); - } else { - result = new ArrayList<>(); // no information about size is available - } - for (FROM elt : iterable) { - result.add(f.apply(elt)); - } - return result; + if (iterable instanceof Collection) { + result = new ArrayList<>(((Collection) iterable).size()); + } else { + result = new ArrayList<>(); // no information about size is available + } + for (FROM elt : iterable) { + result.add(f.apply(elt)); } + return result; + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java index 8f68a610b7d..21c9e75f389 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.signedness.util; -import org.checkerframework.checker.signedness.qual.Unsigned; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.io.IOException; import java.io.RandomAccessFile; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.IntBuffer; +import org.checkerframework.checker.signedness.qual.Unsigned; +import org.checkerframework.framework.qual.AnnotatedFor; /** * Provides static utility methods for unsigned values, beyond what is available in the JDK's @@ -24,519 +23,517 @@ @AnnotatedFor("nullness") public final class SignednessUtil { - private SignednessUtil() { - throw new Error("Do not instantiate"); - } - - /** - * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link - * java.nio.ByteBuffer#wrap(byte[]) wrap(byte[])}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer wrapUnsigned(@Unsigned byte[] array) { - return ByteBuffer.wrap(array); - } - - /** - * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link - * java.nio.ByteBuffer#wrap(byte[], int, int) wrap(byte[], int, int)}, but assumes that the - * input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer wrapUnsigned(@Unsigned byte[] array, int offset, int length) { - return ByteBuffer.wrap(array, offset, length); - } - - /** - * Gets an unsigned int from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#getInt() getInt()}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int getUnsignedInt(ByteBuffer b) { - return b.getInt(); - } - - /** - * Gets an unsigned short from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#getShort() getShort()}, but assumes that the result should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned short getUnsignedShort(ByteBuffer b) { - return b.getShort(); - } - - /** - * Gets an unsigned byte from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#get() get()}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned byte getUnsigned(ByteBuffer b) { - return b.get(); - } - - /** - * Gets an unsigned byte from the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#get(int) get(int)}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned byte getUnsigned(ByteBuffer b, int i) { - return b.get(i); - } - - /** - * Populates an unsigned byte array from the ByteBuffer b at i with l bytes. This method is a - * wrapper around {@link java.nio.ByteBuffer#get(byte[] bs, int, int) get(byte[], int, int)}, - * but assumes that the bytes should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer getUnsigned(ByteBuffer b, byte[] bs, int i, int l) { - return b.get(bs, i, l); - } - - /** - * Places an unsigned byte into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#put(byte) put(byte)}, but assumes that the input should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsigned(ByteBuffer b, @Unsigned byte ubyte) { - return b.put(ubyte); - } - - /** - * Places an unsigned byte into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#put(int, byte) put(int, byte)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsigned(ByteBuffer b, int i, @Unsigned byte ubyte) { - return b.put(i, ubyte); - } - - /** - * Places an unsigned int into the IntBuffer b. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int) put(int)}, but assumes that the input should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int uint) { - return b.put(uint); - } - - /** - * Places an unsigned int into the IntBuffer b at i. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int, int) put(int, int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, int i, @Unsigned int uint) { - return b.put(i, uint); - } - - /** - * Places an unsigned int array into the IntBuffer b. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int[]) put(int[])}, but assumes that the input should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints) { - return b.put(uints); - } - - /** - * Places an unsigned int array into the IntBuffer b at i with length l. This method is a - * wrapper around {@link java.nio.IntBuffer#put(int[], int, int) put(int[], int, int)}, but - * assumes that the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints, int i, int l) { - return b.put(uints, i, l); - } - - /** - * Gets an unsigned int from the IntBuffer b at i. This method is a wrapper around {@link - * java.nio.IntBuffer#get(int) get(int)}, but assumes that the output should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int getUnsigned(IntBuffer b, int i) { - return b.get(i); - } - - /** - * Places an unsigned short into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#putShort(short) putShort(short)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedShort(ByteBuffer b, @Unsigned short ushort) { - return b.putShort(ushort); - } - - /** - * Places an unsigned short into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putShort(int, short) putShort(int, short)}, but assumes that the input - * should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedShort(ByteBuffer b, int i, @Unsigned short ushort) { - return b.putShort(i, ushort); - } - - /** - * Places an unsigned int into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#putInt(int) putInt(int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedInt(ByteBuffer b, @Unsigned int uint) { - return b.putInt(uint); - } - - /** - * Places an unsigned int into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putInt(int, int) putInt(int, int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedInt(ByteBuffer b, int i, @Unsigned int uint) { - return b.putInt(i, uint); - } - - /** - * Places an unsigned long into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putLong(int, long) putLong(int, long)}, but assumes that the input should - * be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedLong(ByteBuffer b, int i, @Unsigned long ulong) { - return b.putLong(i, ulong); - } - - /** - * Reads an unsigned char from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readChar() readChar()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned char readUnsignedChar(RandomAccessFile f) throws IOException { - return f.readChar(); - } - - /** - * Reads an unsigned int from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readInt() readInt()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int readUnsignedInt(RandomAccessFile f) throws IOException { - return f.readInt(); - } - - /** - * Reads an unsigned long from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readLong() readLong()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned long readUnsignedLong(RandomAccessFile f) throws IOException { - return f.readLong(); - } - - /** - * Reads up to {@code len} bytes of data from this file into an unsigned array of bytes. This - * method is a wrapper around {@link java.io.RandomAccessFile#read(byte[], int, int) - * read(byte[], int, int)}, but assumes the output should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static int readUnsigned(RandomAccessFile f, @Unsigned byte b[], int off, int len) - throws IOException { - return f.read(b, off, len); - } - - /** - * Reads a file fully into an unsigned byte array. This method is a wrapper around {@link - * java.io.RandomAccessFile#readFully(byte[]) readFully(byte[])}, but assumes the output should - * be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void readFullyUnsigned(RandomAccessFile f, @Unsigned byte b[]) - throws IOException { - f.readFully(b); - } - - /** - * Writes len unsigned bytes to the RandomAccessFile f at offset off. This method is a wrapper - * around {@link java.io.RandomAccessFile#write(byte[], int, int) write(byte[], int, int)}, but - * assumes the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsigned(RandomAccessFile f, @Unsigned byte[] bs, int off, int len) - throws IOException { - f.write(bs, off, len); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeByte(int) writeByte(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedByte(RandomAccessFile f, @Unsigned byte b) throws IOException { - f.writeByte(Byte.toUnsignedInt(b)); - } - - /** - * Writes an unsigned char to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeChar(int) writeChar(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedChar(RandomAccessFile f, @Unsigned char c) throws IOException { - f.writeChar(toUnsignedInt(c)); - } - - /** - * Writes an unsigned short to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeShort(int) writeShort(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedShort(RandomAccessFile f, @Unsigned short s) - throws IOException { - f.writeShort(Short.toUnsignedInt(s)); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeInt(int) writeInt(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedInt(RandomAccessFile f, @Unsigned int i) throws IOException { - f.writeInt(i); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeLong(long) writeLong(long)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedLong(RandomAccessFile f, @Unsigned long l) throws IOException { - f.writeLong(l); - } - - /** - * Gets an array of unsigned bytes from the ByteBuffer b and stores them in the array bs. This - * method is a wrapper around {@link java.nio.ByteBuffer#get(byte[]) get(byte[])}, but assumes - * that the array of bytes should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void getUnsigned(ByteBuffer b, @Unsigned byte[] bs) { - b.get(bs); - } - - /** - * Compares two unsigned shorts x and y. - * - *

In Java 11 or later, use Short.compareUnsigned. - * - * @param x the first value to compare - * @param y the second value to compare - * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and - * zero iff x == y. - */ - // TODO: deprecate when we require Java 9, which defines Short.compareUnsigned() - // * @deprecated use Short.compareUnsigned - // @Deprecated // use Short.compareUnsigned - @SuppressWarnings("signedness") - public static int compareUnsigned(@Unsigned short x, @Unsigned short y) { - return Integer.compareUnsigned(Short.toUnsignedInt(x), Short.toUnsignedInt(y)); - } - - /** - * Compares two unsigned bytes x and y. - * - *

In Java 11 or later, use Byte.compareUnsigned. - * - * @param x the first value to compare - * @param y the second value to compare - * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and - * zero iff x == y. - */ - // TODO: deprecate when we require Java 9, which defines Byte.compareUnsigned() - // * @deprecated use Byte.compareUnsigned - // @Deprecated // use Byte.compareUnsigned - @SuppressWarnings("signedness") - public static int compareUnsigned(@Unsigned byte x, @Unsigned byte y) { - return Integer.compareUnsigned(Byte.toUnsignedInt(x), Byte.toUnsignedInt(y)); - } - - /** Produces a string representation of the unsigned short s. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned short s) { - return Long.toString(Short.toUnsignedLong(s)); - } - - /** Produces a string representation of the unsigned short s in base radix. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned short s, int radix) { - return Integer.toUnsignedString(Short.toUnsignedInt(s), radix); - } - - /** Produces a string representation of the unsigned byte b. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned byte b) { - return Integer.toUnsignedString(Byte.toUnsignedInt(b)); - } - - /** Produces a string representation of the unsigned byte b in base radix. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned byte b, int radix) { - return Integer.toUnsignedString(Byte.toUnsignedInt(b), radix); - } - - /* - * Creates a BigInteger representing the same value as unsigned long. - * - * This is a reimplementation of Java 8's - * {@link Long.toUnsignedBigInteger(long)}. - */ - @SuppressWarnings("signedness") - private static @Unsigned BigInteger toUnsignedBigInteger(@Unsigned long l) { - // Java 8 version: return Long.toUnsignedBigInteger(l); - if (l >= 0L) { - return BigInteger.valueOf(l); - } else { - int upper = (int) (l >>> 32); - int lower = (int) l; - - // return (upper << 32) + lower - return BigInteger.valueOf(Integer.toUnsignedLong(upper)) - .shiftLeft(32) - .add(BigInteger.valueOf(Integer.toUnsignedLong(lower))); - } - } - - /** Returns an unsigned short representing the same value as an unsigned byte. */ - public static @Unsigned short toUnsignedShort(@Unsigned byte b) { - return (short) (((int) b) & 0xff); - } - - /** Returns an unsigned long representing the same value as an unsigned char. */ - public static @Unsigned long toUnsignedLong(@Unsigned char c) { - return ((long) c) & 0xffL; - } - - /** Returns an unsigned int representing the same value as an unsigned char. */ - public static @Unsigned int toUnsignedInt(@Unsigned char c) { - return ((int) c) & 0xff; - } - - /** Returns an unsigned short representing the same value as an unsigned char. */ - public static @Unsigned short toUnsignedShort(@Unsigned char c) { - return (short) (((int) c) & 0xff); - } - - /** Returns a float representing the same value as the unsigned byte. */ - public static float toFloat(@Unsigned byte b) { - return toUnsignedBigInteger(Byte.toUnsignedLong(b)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned short. */ - public static float toFloat(@Unsigned short s) { - return toUnsignedBigInteger(Short.toUnsignedLong(s)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned int. */ - public static float toFloat(@Unsigned int i) { - return toUnsignedBigInteger(Integer.toUnsignedLong(i)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned long. */ - public static float toFloat(@Unsigned long l) { - return toUnsignedBigInteger(l).floatValue(); - } - - /** Returns a double representing the same value as the unsigned byte. */ - public static double toDouble(@Unsigned byte b) { - return toUnsignedBigInteger(Byte.toUnsignedLong(b)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned short. */ - public static double toDouble(@Unsigned short s) { - return toUnsignedBigInteger(Short.toUnsignedLong(s)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned int. */ - public static double toDouble(@Unsigned int i) { - return toUnsignedBigInteger(Integer.toUnsignedLong(i)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned long. */ - public static double toDouble(@Unsigned long l) { - return toUnsignedBigInteger(l).doubleValue(); - } - - /** Returns an unsigned byte representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned byte byteFromFloat(float f) { - assert f >= 0; - return (byte) f; - } - - /** Returns an unsigned short representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned short shortFromFloat(float f) { - assert f >= 0; - return (short) f; - } - - /** Returns an unsigned int representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned int intFromFloat(float f) { - assert f >= 0; - return (int) f; - } - - /** Returns an unsigned long representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned long longFromFloat(float f) { - assert f >= 0; - return (long) f; - } - - /** Returns an unsigned byte representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned byte byteFromDouble(double d) { - assert d >= 0; - return (byte) d; - } - - /** Returns an unsigned short representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned short shortFromDouble(double d) { - assert d >= 0; - return (short) d; - } - - /** Returns an unsigned int representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned int intFromDouble(double d) { - assert d >= 0; - return (int) d; - } - - /** Returns an unsigned long representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned long longFromDouble(double d) { - assert d >= 0; - return (long) d; - } + private SignednessUtil() { + throw new Error("Do not instantiate"); + } + + /** + * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link + * java.nio.ByteBuffer#wrap(byte[]) wrap(byte[])}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer wrapUnsigned(@Unsigned byte[] array) { + return ByteBuffer.wrap(array); + } + + /** + * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link + * java.nio.ByteBuffer#wrap(byte[], int, int) wrap(byte[], int, int)}, but assumes that the input + * should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer wrapUnsigned(@Unsigned byte[] array, int offset, int length) { + return ByteBuffer.wrap(array, offset, length); + } + + /** + * Gets an unsigned int from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#getInt() getInt()}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int getUnsignedInt(ByteBuffer b) { + return b.getInt(); + } + + /** + * Gets an unsigned short from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#getShort() getShort()}, but assumes that the result should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned short getUnsignedShort(ByteBuffer b) { + return b.getShort(); + } + + /** + * Gets an unsigned byte from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#get() get()}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned byte getUnsigned(ByteBuffer b) { + return b.get(); + } + + /** + * Gets an unsigned byte from the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#get(int) get(int)}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned byte getUnsigned(ByteBuffer b, int i) { + return b.get(i); + } + + /** + * Populates an unsigned byte array from the ByteBuffer b at i with l bytes. This method is a + * wrapper around {@link java.nio.ByteBuffer#get(byte[] bs, int, int) get(byte[], int, int)}, but + * assumes that the bytes should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer getUnsigned(ByteBuffer b, byte[] bs, int i, int l) { + return b.get(bs, i, l); + } + + /** + * Places an unsigned byte into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#put(byte) put(byte)}, but assumes that the input should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsigned(ByteBuffer b, @Unsigned byte ubyte) { + return b.put(ubyte); + } + + /** + * Places an unsigned byte into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#put(int, byte) put(int, byte)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsigned(ByteBuffer b, int i, @Unsigned byte ubyte) { + return b.put(i, ubyte); + } + + /** + * Places an unsigned int into the IntBuffer b. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int) put(int)}, but assumes that the input should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int uint) { + return b.put(uint); + } + + /** + * Places an unsigned int into the IntBuffer b at i. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int, int) put(int, int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, int i, @Unsigned int uint) { + return b.put(i, uint); + } + + /** + * Places an unsigned int array into the IntBuffer b. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int[]) put(int[])}, but assumes that the input should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints) { + return b.put(uints); + } + + /** + * Places an unsigned int array into the IntBuffer b at i with length l. This method is a wrapper + * around {@link java.nio.IntBuffer#put(int[], int, int) put(int[], int, int)}, but assumes that + * the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints, int i, int l) { + return b.put(uints, i, l); + } + + /** + * Gets an unsigned int from the IntBuffer b at i. This method is a wrapper around {@link + * java.nio.IntBuffer#get(int) get(int)}, but assumes that the output should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int getUnsigned(IntBuffer b, int i) { + return b.get(i); + } + + /** + * Places an unsigned short into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#putShort(short) putShort(short)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedShort(ByteBuffer b, @Unsigned short ushort) { + return b.putShort(ushort); + } + + /** + * Places an unsigned short into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putShort(int, short) putShort(int, short)}, but assumes that the input + * should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedShort(ByteBuffer b, int i, @Unsigned short ushort) { + return b.putShort(i, ushort); + } + + /** + * Places an unsigned int into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#putInt(int) putInt(int)}, but assumes that the input should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedInt(ByteBuffer b, @Unsigned int uint) { + return b.putInt(uint); + } + + /** + * Places an unsigned int into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putInt(int, int) putInt(int, int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedInt(ByteBuffer b, int i, @Unsigned int uint) { + return b.putInt(i, uint); + } + + /** + * Places an unsigned long into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putLong(int, long) putLong(int, long)}, but assumes that the input should + * be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedLong(ByteBuffer b, int i, @Unsigned long ulong) { + return b.putLong(i, ulong); + } + + /** + * Reads an unsigned char from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readChar() readChar()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned char readUnsignedChar(RandomAccessFile f) throws IOException { + return f.readChar(); + } + + /** + * Reads an unsigned int from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readInt() readInt()}, but assumes the output should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int readUnsignedInt(RandomAccessFile f) throws IOException { + return f.readInt(); + } + + /** + * Reads an unsigned long from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readLong() readLong()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned long readUnsignedLong(RandomAccessFile f) throws IOException { + return f.readLong(); + } + + /** + * Reads up to {@code len} bytes of data from this file into an unsigned array of bytes. This + * method is a wrapper around {@link java.io.RandomAccessFile#read(byte[], int, int) read(byte[], + * int, int)}, but assumes the output should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static int readUnsigned(RandomAccessFile f, @Unsigned byte b[], int off, int len) + throws IOException { + return f.read(b, off, len); + } + + /** + * Reads a file fully into an unsigned byte array. This method is a wrapper around {@link + * java.io.RandomAccessFile#readFully(byte[]) readFully(byte[])}, but assumes the output should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void readFullyUnsigned(RandomAccessFile f, @Unsigned byte b[]) throws IOException { + f.readFully(b); + } + + /** + * Writes len unsigned bytes to the RandomAccessFile f at offset off. This method is a wrapper + * around {@link java.io.RandomAccessFile#write(byte[], int, int) write(byte[], int, int)}, but + * assumes the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsigned(RandomAccessFile f, @Unsigned byte[] bs, int off, int len) + throws IOException { + f.write(bs, off, len); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeByte(int) writeByte(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedByte(RandomAccessFile f, @Unsigned byte b) throws IOException { + f.writeByte(Byte.toUnsignedInt(b)); + } + + /** + * Writes an unsigned char to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeChar(int) writeChar(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedChar(RandomAccessFile f, @Unsigned char c) throws IOException { + f.writeChar(toUnsignedInt(c)); + } + + /** + * Writes an unsigned short to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeShort(int) writeShort(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedShort(RandomAccessFile f, @Unsigned short s) throws IOException { + f.writeShort(Short.toUnsignedInt(s)); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeInt(int) writeInt(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedInt(RandomAccessFile f, @Unsigned int i) throws IOException { + f.writeInt(i); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeLong(long) writeLong(long)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedLong(RandomAccessFile f, @Unsigned long l) throws IOException { + f.writeLong(l); + } + + /** + * Gets an array of unsigned bytes from the ByteBuffer b and stores them in the array bs. This + * method is a wrapper around {@link java.nio.ByteBuffer#get(byte[]) get(byte[])}, but assumes + * that the array of bytes should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void getUnsigned(ByteBuffer b, @Unsigned byte[] bs) { + b.get(bs); + } + + /** + * Compares two unsigned shorts x and y. + * + *

In Java 11 or later, use Short.compareUnsigned. + * + * @param x the first value to compare + * @param y the second value to compare + * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and + * zero iff x == y. + */ + // TODO: deprecate when we require Java 9, which defines Short.compareUnsigned() + // * @deprecated use Short.compareUnsigned + // @Deprecated // use Short.compareUnsigned + @SuppressWarnings("signedness") + public static int compareUnsigned(@Unsigned short x, @Unsigned short y) { + return Integer.compareUnsigned(Short.toUnsignedInt(x), Short.toUnsignedInt(y)); + } + + /** + * Compares two unsigned bytes x and y. + * + *

In Java 11 or later, use Byte.compareUnsigned. + * + * @param x the first value to compare + * @param y the second value to compare + * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and + * zero iff x == y. + */ + // TODO: deprecate when we require Java 9, which defines Byte.compareUnsigned() + // * @deprecated use Byte.compareUnsigned + // @Deprecated // use Byte.compareUnsigned + @SuppressWarnings("signedness") + public static int compareUnsigned(@Unsigned byte x, @Unsigned byte y) { + return Integer.compareUnsigned(Byte.toUnsignedInt(x), Byte.toUnsignedInt(y)); + } + + /** Produces a string representation of the unsigned short s. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned short s) { + return Long.toString(Short.toUnsignedLong(s)); + } + + /** Produces a string representation of the unsigned short s in base radix. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned short s, int radix) { + return Integer.toUnsignedString(Short.toUnsignedInt(s), radix); + } + + /** Produces a string representation of the unsigned byte b. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned byte b) { + return Integer.toUnsignedString(Byte.toUnsignedInt(b)); + } + + /** Produces a string representation of the unsigned byte b in base radix. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned byte b, int radix) { + return Integer.toUnsignedString(Byte.toUnsignedInt(b), radix); + } + + /* + * Creates a BigInteger representing the same value as unsigned long. + * + * This is a reimplementation of Java 8's + * {@link Long.toUnsignedBigInteger(long)}. + */ + @SuppressWarnings("signedness") + private static @Unsigned BigInteger toUnsignedBigInteger(@Unsigned long l) { + // Java 8 version: return Long.toUnsignedBigInteger(l); + if (l >= 0L) { + return BigInteger.valueOf(l); + } else { + int upper = (int) (l >>> 32); + int lower = (int) l; + + // return (upper << 32) + lower + return BigInteger.valueOf(Integer.toUnsignedLong(upper)) + .shiftLeft(32) + .add(BigInteger.valueOf(Integer.toUnsignedLong(lower))); + } + } + + /** Returns an unsigned short representing the same value as an unsigned byte. */ + public static @Unsigned short toUnsignedShort(@Unsigned byte b) { + return (short) (((int) b) & 0xff); + } + + /** Returns an unsigned long representing the same value as an unsigned char. */ + public static @Unsigned long toUnsignedLong(@Unsigned char c) { + return ((long) c) & 0xffL; + } + + /** Returns an unsigned int representing the same value as an unsigned char. */ + public static @Unsigned int toUnsignedInt(@Unsigned char c) { + return ((int) c) & 0xff; + } + + /** Returns an unsigned short representing the same value as an unsigned char. */ + public static @Unsigned short toUnsignedShort(@Unsigned char c) { + return (short) (((int) c) & 0xff); + } + + /** Returns a float representing the same value as the unsigned byte. */ + public static float toFloat(@Unsigned byte b) { + return toUnsignedBigInteger(Byte.toUnsignedLong(b)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned short. */ + public static float toFloat(@Unsigned short s) { + return toUnsignedBigInteger(Short.toUnsignedLong(s)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned int. */ + public static float toFloat(@Unsigned int i) { + return toUnsignedBigInteger(Integer.toUnsignedLong(i)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned long. */ + public static float toFloat(@Unsigned long l) { + return toUnsignedBigInteger(l).floatValue(); + } + + /** Returns a double representing the same value as the unsigned byte. */ + public static double toDouble(@Unsigned byte b) { + return toUnsignedBigInteger(Byte.toUnsignedLong(b)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned short. */ + public static double toDouble(@Unsigned short s) { + return toUnsignedBigInteger(Short.toUnsignedLong(s)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned int. */ + public static double toDouble(@Unsigned int i) { + return toUnsignedBigInteger(Integer.toUnsignedLong(i)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned long. */ + public static double toDouble(@Unsigned long l) { + return toUnsignedBigInteger(l).doubleValue(); + } + + /** Returns an unsigned byte representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned byte byteFromFloat(float f) { + assert f >= 0; + return (byte) f; + } + + /** Returns an unsigned short representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned short shortFromFloat(float f) { + assert f >= 0; + return (short) f; + } + + /** Returns an unsigned int representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned int intFromFloat(float f) { + assert f >= 0; + return (int) f; + } + + /** Returns an unsigned long representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned long longFromFloat(float f) { + assert f >= 0; + return (long) f; + } + + /** Returns an unsigned byte representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned byte byteFromDouble(double d) { + assert d >= 0; + return (byte) d; + } + + /** Returns an unsigned short representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned short shortFromDouble(double d) { + assert d >= 0; + return (short) d; + } + + /** Returns an unsigned int representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned int intFromDouble(double d) { + assert d >= 0; + return (int) d; + } + + /** Returns an unsigned long representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned long longFromDouble(double d) { + assert d >= 0; + return (long) d; + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java index 215cd1351fe..719f5d05306 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java +++ b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.signedness.util; -import org.checkerframework.checker.signedness.qual.Unsigned; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.awt.Dimension; import java.awt.image.BufferedImage; +import org.checkerframework.checker.signedness.qual.Unsigned; +import org.checkerframework.framework.qual.AnnotatedFor; /** * Provides more static utility methods for unsigned values. These methods use Java packages not @@ -17,56 +16,56 @@ */ @AnnotatedFor("nullness") public class SignednessUtilExtra { - /** Do not instantiate this class. */ - private SignednessUtilExtra() { - throw new Error("Do not instantiate"); - } + /** Do not instantiate this class. */ + private SignednessUtilExtra() { + throw new Error("Do not instantiate"); + } - /** Gets the unsigned width of a {@code Dimension}. */ - @SuppressWarnings("signedness") - public static @Unsigned int dimensionUnsignedWidth(Dimension dim) { - return dim.width; - } + /** Gets the unsigned width of a {@code Dimension}. */ + @SuppressWarnings("signedness") + public static @Unsigned int dimensionUnsignedWidth(Dimension dim) { + return dim.width; + } - /** Gets the unsigned height of a {@code Dimension}. */ - @SuppressWarnings("signedness") - public static @Unsigned int dimensionUnsignedHeight(Dimension dim) { - return dim.height; - } + /** Gets the unsigned height of a {@code Dimension}. */ + @SuppressWarnings("signedness") + public static @Unsigned int dimensionUnsignedHeight(Dimension dim) { + return dim.height; + } - /** - * Sets rgb of BufferedImage b given unsigned ints. This method is a wrapper around {@link - * java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB(int, int, - * int, int, int[], int, int)}, but assumes that the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void setUnsignedRGB( - BufferedImage b, - int startX, - int startY, - int w, - int h, - @Unsigned int[] rgbArray, - int offset, - int scansize) { - b.setRGB(startX, startY, w, h, rgbArray, offset, scansize); - } + /** + * Sets rgb of BufferedImage b given unsigned ints. This method is a wrapper around {@link + * java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB(int, int, int, + * int, int[], int, int)}, but assumes that the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void setUnsignedRGB( + BufferedImage b, + int startX, + int startY, + int w, + int h, + @Unsigned int[] rgbArray, + int offset, + int scansize) { + b.setRGB(startX, startY, w, h, rgbArray, offset, scansize); + } - /** - * Gets rgb of BufferedImage b as unsigned ints. This method is a wrapper around {@link - * java.awt.image.BufferedImage#getRGB(int, int, int, int, int[], int, int) getRGB(int, int, - * int, int, int[], int, int)}, but assumes that the output should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int[] getUnsignedRGB( - BufferedImage b, - int startX, - int startY, - int w, - int h, - @Unsigned int[] rgbArray, - int offset, - int scansize) { - return b.getRGB(startX, startY, w, h, rgbArray, offset, scansize); - } + /** + * Gets rgb of BufferedImage b as unsigned ints. This method is a wrapper around {@link + * java.awt.image.BufferedImage#getRGB(int, int, int, int, int[], int, int) getRGB(int, int, int, + * int, int[], int, int)}, but assumes that the output should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int[] getUnsignedRGB( + BufferedImage b, + int startX, + int startY, + int w, + int h, + @Unsigned int[] rgbArray, + int offset, + int scansize) { + return b.getRGB(startX, startY, w, h, rgbArray, offset, scansize); + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java b/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java index 9069300a169..7b92e252246 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java +++ b/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java @@ -35,136 +35,136 @@ @SuppressWarnings({"units", "checkstyle:constantname"}) @AnnotatedFor("nullness") public class UnitsTools { - // Acceleration - public static final @mPERs2 int mPERs2 = 1; + // Acceleration + public static final @mPERs2 int mPERs2 = 1; - // Angle - public static final @radians double rad = 1; - public static final @degrees double deg = 1; + // Angle + public static final @radians double rad = 1; + public static final @degrees double deg = 1; - public static @radians double toRadians(@degrees double angdeg) { - return Math.toRadians(angdeg); - } + public static @radians double toRadians(@degrees double angdeg) { + return Math.toRadians(angdeg); + } - public static @degrees double toDegrees(@radians double angrad) { - return Math.toDegrees(angrad); - } + public static @degrees double toDegrees(@radians double angrad) { + return Math.toDegrees(angrad); + } - // Area - public static final @mm2 int mm2 = 1; - public static final @m2 int m2 = 1; - public static final @km2 int km2 = 1; - - // Volume - public static final @mm3 int mm3 = 1; - public static final @m3 int m3 = 1; - public static final @km3 int km3 = 1; + // Area + public static final @mm2 int mm2 = 1; + public static final @m2 int m2 = 1; + public static final @km2 int km2 = 1; + + // Volume + public static final @mm3 int mm3 = 1; + public static final @m3 int m3 = 1; + public static final @km3 int km3 = 1; - // Current - public static final @A int A = 1; - - // Luminance - public static final @cd int cd = 1; + // Current + public static final @A int A = 1; + + // Luminance + public static final @cd int cd = 1; - // Lengths - public static final @mm int mm = 1; - public static final @m int m = 1; - public static final @km int km = 1; - - public static @m int fromMilliMeterToMeter(@mm int mm) { - return mm / 1000; - } - - public static @mm int fromMeterToMilliMeter(@m int m) { - return m * 1000; - } - - public static @km int fromMeterToKiloMeter(@m int m) { - return m / 1000; - } - - public static @m int fromKiloMeterToMeter(@km int km) { - return km * 1000; - } - - // Mass - public static final @g int g = 1; - public static final @kg int kg = 1; - public static final @t int t = 1; - - public static @kg int fromGramToKiloGram(@g int g) { - return g / 1000; - } - - public static @g int fromKiloGramToGram(@kg int kg) { - return kg * 1000; - } - - public static @t int fromKiloGramToMetricTon(@kg int kg) { - return kg / 1000; - } - - public static @kg int fromMetricTonToKiloGram(@t int t) { - return t * 1000; - } - - // Force - public static final @N int N = 1; - public static final @kN int kN = 1; - - public static @kN int fromNewtonToKiloNewton(@N int N) { - return N / 1000; - } - - public static @N int fromKiloNewtonToNewton(@kN int kN) { - return kN * 1000; - } - - // Speed - public static final @mPERs int mPERs = 1; - public static final @kmPERh int kmPERh = 1; - - public static @kmPERh double fromMeterPerSecondToKiloMeterPerHour(@mPERs double mps) { - return mps * 3.6d; - } - - public static @mPERs double fromKiloMeterPerHourToMeterPerSecond(@kmPERh double kmph) { - return kmph / 3.6d; - } - - // Substance - public static final @mol int mol = 1; - - // Temperature - public static final @K int K = 1; - public static final @C int C = 1; - - public static @C int fromKelvinToCelsius(@K int k) { - return k - (int) 273.15; - } - - public static @K int fromCelsiusToKelvin(@C int c) { - return c + (int) 273.15; - } - - // Time - public static final @s int s = 1; - public static final @min int min = 1; - public static final @h int h = 1; - - public static @min int fromSecondToMinute(@s int s) { - return s / 60; - } - - public static @s int fromMinuteToSecond(@min int min) { - return min * 60; - } + // Lengths + public static final @mm int mm = 1; + public static final @m int m = 1; + public static final @km int km = 1; + + public static @m int fromMilliMeterToMeter(@mm int mm) { + return mm / 1000; + } + + public static @mm int fromMeterToMilliMeter(@m int m) { + return m * 1000; + } + + public static @km int fromMeterToKiloMeter(@m int m) { + return m / 1000; + } + + public static @m int fromKiloMeterToMeter(@km int km) { + return km * 1000; + } + + // Mass + public static final @g int g = 1; + public static final @kg int kg = 1; + public static final @t int t = 1; + + public static @kg int fromGramToKiloGram(@g int g) { + return g / 1000; + } + + public static @g int fromKiloGramToGram(@kg int kg) { + return kg * 1000; + } + + public static @t int fromKiloGramToMetricTon(@kg int kg) { + return kg / 1000; + } + + public static @kg int fromMetricTonToKiloGram(@t int t) { + return t * 1000; + } + + // Force + public static final @N int N = 1; + public static final @kN int kN = 1; + + public static @kN int fromNewtonToKiloNewton(@N int N) { + return N / 1000; + } + + public static @N int fromKiloNewtonToNewton(@kN int kN) { + return kN * 1000; + } + + // Speed + public static final @mPERs int mPERs = 1; + public static final @kmPERh int kmPERh = 1; + + public static @kmPERh double fromMeterPerSecondToKiloMeterPerHour(@mPERs double mps) { + return mps * 3.6d; + } + + public static @mPERs double fromKiloMeterPerHourToMeterPerSecond(@kmPERh double kmph) { + return kmph / 3.6d; + } + + // Substance + public static final @mol int mol = 1; + + // Temperature + public static final @K int K = 1; + public static final @C int C = 1; + + public static @C int fromKelvinToCelsius(@K int k) { + return k - (int) 273.15; + } + + public static @K int fromCelsiusToKelvin(@C int c) { + return c + (int) 273.15; + } + + // Time + public static final @s int s = 1; + public static final @min int min = 1; + public static final @h int h = 1; + + public static @min int fromSecondToMinute(@s int s) { + return s / 60; + } + + public static @s int fromMinuteToSecond(@min int min) { + return min * 60; + } - public static @h int fromMinuteToHour(@min int min) { - return min / 60; - } - - public static @min int fromHourToMinute(@h int h) { - return h * 60; - } + public static @h int fromMinuteToHour(@min int min) { + return min / 60; + } + + public static @min int fromHourToMinute(@h int h) { + return h * 60; + } } diff --git a/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java b/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java index 285e44fc2b6..c2d2d132ff8 100644 --- a/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java +++ b/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java @@ -1,24 +1,23 @@ package org.checkerframework.checker.optional.util; +import java.util.Optional; import org.checkerframework.checker.optional.qual.Present; import org.junit.Assert; import org.junit.Test; -import java.util.Optional; - public final class OptionalUtilTest { - @Test - public void test_castPresent() { + @Test + public void test_castPresent() { - Optional nonEmptyOpt = Optional.of("non-empty"); - Optional emptyOpt = Optional.empty(); + Optional nonEmptyOpt = Optional.of("non-empty"); + Optional emptyOpt = Optional.empty(); - Assert.assertTrue(nonEmptyOpt.isPresent()); - @Present Optional foo = OptionalUtil.castPresent(nonEmptyOpt); - Assert.assertEquals(foo.get(), "non-empty"); + Assert.assertTrue(nonEmptyOpt.isPresent()); + @Present Optional foo = OptionalUtil.castPresent(nonEmptyOpt); + Assert.assertEquals(foo.get(), "non-empty"); - Assert.assertFalse(emptyOpt.isPresent()); - Assert.assertThrows(Error.class, () -> OptionalUtil.castPresent(emptyOpt)); - } + Assert.assertFalse(emptyOpt.isPresent()); + Assert.assertThrows(Error.class, () -> OptionalUtil.castPresent(emptyOpt)); + } } diff --git a/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java b/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java index 12a10f70fed..97bb896f024 100644 --- a/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java +++ b/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java @@ -2,266 +2,265 @@ package org.checkerframework.checker.regex.util; -import org.checkerframework.checker.regex.qual.Regex; -import org.junit.Assert; -import org.junit.Test; - import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.regex.qual.Regex; +import org.junit.Assert; +import org.junit.Test; public final class RegexUtilTest { - @Test - public void test_isRegex_and_asRegex() { - - String s1 = "colo(u?)r"; - String s2 = "(brown|beige)"; - String s3 = "colou?r"; - String s4 = "1) first point"; - - Assert.assertTrue(RegexUtil.isRegex(s1)); - RegexUtil.asRegex(s1); - Assert.assertTrue(RegexUtil.isRegex(s1, 0)); - RegexUtil.asRegex(s1, 0); - Assert.assertTrue(RegexUtil.isRegex(s1, 1)); - RegexUtil.asRegex(s1, 1); - Assert.assertFalse(RegexUtil.isRegex(s1, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s1, 2)); - - Assert.assertTrue(RegexUtil.isRegex(s2)); - RegexUtil.asRegex(s2); - Assert.assertTrue(RegexUtil.isRegex(s2, 0)); - RegexUtil.asRegex(s2, 0); - Assert.assertTrue(RegexUtil.isRegex(s2, 1)); - RegexUtil.asRegex(s2, 1); - Assert.assertFalse(RegexUtil.isRegex(s2, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s2, 2)); - - Assert.assertTrue(RegexUtil.isRegex(s3)); - RegexUtil.asRegex(s3); - Assert.assertTrue(RegexUtil.isRegex(s3, 0)); - RegexUtil.asRegex(s3, 0); - Assert.assertFalse(RegexUtil.isRegex(s3, 1)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 1)); - Assert.assertFalse(RegexUtil.isRegex(s3, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 2)); - - Assert.assertFalse(RegexUtil.isRegex(s4)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4)); - Assert.assertFalse(RegexUtil.isRegex(s4, 0)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 0)); - Assert.assertFalse(RegexUtil.isRegex(s4, 1)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 1)); - Assert.assertFalse(RegexUtil.isRegex(s4, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 2)); - } - - List s1 = Arrays.asList(new String[] {"a", "b", "c"}); - List s2 = Arrays.asList(new String[] {"a", "b", "c", "d"}); - List s3 = Arrays.asList(new String[] {"aa", "bb", "cc"}); - List s4 = Arrays.asList(new String[] {"a", "aa", "b", "bb", "c"}); - List s5 = Arrays.asList(new String[] {"d", "ee", "fff"}); - List s6 = Arrays.asList(new String[] {"a", "d", "ee", "fff"}); - - List<@Regex String> r1 = Arrays.asList(new @Regex String[] {}); - List<@Regex String> r2 = Arrays.asList(new @Regex String[] {"a", "b", "c"}); - List<@Regex String> r3 = Arrays.asList(new @Regex String[] {"a+", "b+", "c"}); - List<@Regex String> r4 = Arrays.asList(new @Regex String[] {"a+", "b+", "c+"}); - List<@Regex String> r5 = Arrays.asList(new @Regex String[] {".*"}); - - List<@Regex String> r6 = Arrays.asList(new @Regex String[] {"a?b", "a*"}); - List<@Regex String> r7 = Arrays.asList(new @Regex String[] {"a?b+", "a*"}); - - List empty = Collections.emptyList(); - List onlyA = Arrays.asList(new String[] {"a"}); - List onlyAA = Arrays.asList(new String[] {"aa"}); - List onlyC = Arrays.asList(new String[] {"c"}); - List onlyCC = Arrays.asList(new String[] {"cc"}); - List onlyD = Arrays.asList(new String[] {"d"}); - List aaab = Arrays.asList(new String[] {"a", "aa", "b"}); - List ab = Arrays.asList(new String[] {"a", "b"}); - List aabb = Arrays.asList(new String[] {"aa", "bb"}); - List aacc = Arrays.asList(new String[] {"aa", "cc"}); - List bbc = Arrays.asList(new String[] {"bb", "c"}); - List bbcc = Arrays.asList(new String[] {"bb", "cc"}); - List cc = Arrays.asList(new String[] {"cc"}); - List cd = Arrays.asList(new String[] {"c", "d"}); - List eefff = Arrays.asList(new String[] {"ee", "fff"}); - - @Test - public void test_matchesSomeRegex() { - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r1), empty); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r2), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r2), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r2), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r2), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r2), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r2), onlyA); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r3), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r3), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r3), aabb); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r3), s4); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r3), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r3), onlyA); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r4), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r4), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r4), s3); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r4), s4); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r4), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r4), onlyA); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r5), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r5), s2); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r5), s3); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r5), s4); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r5), s5); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r5), s6); - - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r1)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r2)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r3)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r3)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r4)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r4)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r4)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r4)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r4)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r4)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s2, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s5, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s6, r5)); - } - - @Test - public void test_matchesNoRegex() { - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r1), s1); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r1), s2); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r1), s3); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r1), s4); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r1), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r1), s6); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r2), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r2), onlyD); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r2), s3); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r2), aabb); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r2), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r2), s5); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r3), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r3), onlyD); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r3), cc); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r3), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r3), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r3), s5); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r4), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r4), onlyD); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r4), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r4), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r4), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r4), s5); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r5), empty); - - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s1, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s2, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s4, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s6, r1)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r2)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r2)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r2)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r2)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r2)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r2)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r3)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r3)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r4)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r4)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s5, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r5)); - } - - @Test - public void test_r6() { - Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s1, r6)); - Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s2, r6)); - Assert.assertEquals(onlyAA, RegexUtil.matchesSomeRegex(s3, r6)); - Assert.assertEquals(aaab, RegexUtil.matchesSomeRegex(s4, r6)); - Assert.assertEquals(empty, RegexUtil.matchesSomeRegex(s5, r6)); - Assert.assertEquals(onlyA, RegexUtil.matchesSomeRegex(s6, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r6)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(onlyA, r7)); - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r6), onlyC); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r6), cd); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r6), bbcc); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r7), onlyCC); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r6), bbc); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r6), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r6), s5); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r6)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r6)); - } + @Test + public void test_isRegex_and_asRegex() { + + String s1 = "colo(u?)r"; + String s2 = "(brown|beige)"; + String s3 = "colou?r"; + String s4 = "1) first point"; + + Assert.assertTrue(RegexUtil.isRegex(s1)); + RegexUtil.asRegex(s1); + Assert.assertTrue(RegexUtil.isRegex(s1, 0)); + RegexUtil.asRegex(s1, 0); + Assert.assertTrue(RegexUtil.isRegex(s1, 1)); + RegexUtil.asRegex(s1, 1); + Assert.assertFalse(RegexUtil.isRegex(s1, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s1, 2)); + + Assert.assertTrue(RegexUtil.isRegex(s2)); + RegexUtil.asRegex(s2); + Assert.assertTrue(RegexUtil.isRegex(s2, 0)); + RegexUtil.asRegex(s2, 0); + Assert.assertTrue(RegexUtil.isRegex(s2, 1)); + RegexUtil.asRegex(s2, 1); + Assert.assertFalse(RegexUtil.isRegex(s2, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s2, 2)); + + Assert.assertTrue(RegexUtil.isRegex(s3)); + RegexUtil.asRegex(s3); + Assert.assertTrue(RegexUtil.isRegex(s3, 0)); + RegexUtil.asRegex(s3, 0); + Assert.assertFalse(RegexUtil.isRegex(s3, 1)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 1)); + Assert.assertFalse(RegexUtil.isRegex(s3, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 2)); + + Assert.assertFalse(RegexUtil.isRegex(s4)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4)); + Assert.assertFalse(RegexUtil.isRegex(s4, 0)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 0)); + Assert.assertFalse(RegexUtil.isRegex(s4, 1)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 1)); + Assert.assertFalse(RegexUtil.isRegex(s4, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 2)); + } + + List s1 = Arrays.asList(new String[] {"a", "b", "c"}); + List s2 = Arrays.asList(new String[] {"a", "b", "c", "d"}); + List s3 = Arrays.asList(new String[] {"aa", "bb", "cc"}); + List s4 = Arrays.asList(new String[] {"a", "aa", "b", "bb", "c"}); + List s5 = Arrays.asList(new String[] {"d", "ee", "fff"}); + List s6 = Arrays.asList(new String[] {"a", "d", "ee", "fff"}); + + List<@Regex String> r1 = Arrays.asList(new @Regex String[] {}); + List<@Regex String> r2 = Arrays.asList(new @Regex String[] {"a", "b", "c"}); + List<@Regex String> r3 = Arrays.asList(new @Regex String[] {"a+", "b+", "c"}); + List<@Regex String> r4 = Arrays.asList(new @Regex String[] {"a+", "b+", "c+"}); + List<@Regex String> r5 = Arrays.asList(new @Regex String[] {".*"}); + + List<@Regex String> r6 = Arrays.asList(new @Regex String[] {"a?b", "a*"}); + List<@Regex String> r7 = Arrays.asList(new @Regex String[] {"a?b+", "a*"}); + + List empty = Collections.emptyList(); + List onlyA = Arrays.asList(new String[] {"a"}); + List onlyAA = Arrays.asList(new String[] {"aa"}); + List onlyC = Arrays.asList(new String[] {"c"}); + List onlyCC = Arrays.asList(new String[] {"cc"}); + List onlyD = Arrays.asList(new String[] {"d"}); + List aaab = Arrays.asList(new String[] {"a", "aa", "b"}); + List ab = Arrays.asList(new String[] {"a", "b"}); + List aabb = Arrays.asList(new String[] {"aa", "bb"}); + List aacc = Arrays.asList(new String[] {"aa", "cc"}); + List bbc = Arrays.asList(new String[] {"bb", "c"}); + List bbcc = Arrays.asList(new String[] {"bb", "cc"}); + List cc = Arrays.asList(new String[] {"cc"}); + List cd = Arrays.asList(new String[] {"c", "d"}); + List eefff = Arrays.asList(new String[] {"ee", "fff"}); + + @Test + public void test_matchesSomeRegex() { + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r1), empty); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r2), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r2), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r2), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r2), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r2), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r2), onlyA); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r3), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r3), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r3), aabb); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r3), s4); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r3), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r3), onlyA); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r4), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r4), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r4), s3); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r4), s4); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r4), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r4), onlyA); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r5), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r5), s2); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r5), s3); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r5), s4); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r5), s5); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r5), s6); + + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r1)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r2)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r3)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r3)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r4)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r4)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r4)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r4)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r4)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r4)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s2, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s5, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s6, r5)); + } + + @Test + public void test_matchesNoRegex() { + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r1), s1); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r1), s2); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r1), s3); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r1), s4); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r1), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r1), s6); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r2), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r2), onlyD); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r2), s3); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r2), aabb); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r2), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r2), s5); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r3), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r3), onlyD); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r3), cc); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r3), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r3), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r3), s5); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r4), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r4), onlyD); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r4), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r4), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r4), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r4), s5); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r5), empty); + + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s1, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s2, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s4, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s6, r1)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r2)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r2)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r2)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r2)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r2)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r2)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r3)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r3)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r4)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r4)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s5, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r5)); + } + + @Test + public void test_r6() { + Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s1, r6)); + Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s2, r6)); + Assert.assertEquals(onlyAA, RegexUtil.matchesSomeRegex(s3, r6)); + Assert.assertEquals(aaab, RegexUtil.matchesSomeRegex(s4, r6)); + Assert.assertEquals(empty, RegexUtil.matchesSomeRegex(s5, r6)); + Assert.assertEquals(onlyA, RegexUtil.matchesSomeRegex(s6, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r6)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(onlyA, r7)); + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r6), onlyC); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r6), cd); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r6), bbcc); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r7), onlyCC); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r6), bbc); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r6), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r6), s5); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r6)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r6)); + } } diff --git a/checker/jtreg/SymbolNotFoundErrors.java b/checker/jtreg/SymbolNotFoundErrors.java index 8d869c33cbc..6a93e7ed40a 100644 --- a/checker/jtreg/SymbolNotFoundErrors.java +++ b/checker/jtreg/SymbolNotFoundErrors.java @@ -5,5 +5,5 @@ * @compile/fail/ref=SymbolNotFoundErrors2.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker SymbolNotFoundErrors.java */ public class SymbolNotFoundErrors { - CCC f; + CCC f; } diff --git a/checker/jtreg/index/UnneededSuppressionsTest.java b/checker/jtreg/index/UnneededSuppressionsTest.java index fe71afccaaf..5d4e6f7e568 100644 --- a/checker/jtreg/index/UnneededSuppressionsTest.java +++ b/checker/jtreg/index/UnneededSuppressionsTest.java @@ -9,36 +9,36 @@ public class UnneededSuppressionsTest { - void method(@NonNegative int i) { - @SuppressWarnings("index") - @NonNegative int x = i - 1; - } - - void method2() { - @SuppressWarnings("fallthrough") - int x = 0; - } - - @SuppressWarnings({"tainting", "lowerbound"}) - void method3() { - @SuppressWarnings("upperbound:assignment.type.incompatible") - int z = 0; - } - - void method4() { - @SuppressWarnings("lowerbound:assignment.type.incompatible") - @NonNegative int x = -1; - } - - @SuppressWarnings("purity.not.deterministic.call") - void method5() {} - - @SuppressWarnings("purity") - void method6() {} - - @SuppressWarnings("index:foo.bar.baz") - void method7() {} - - @SuppressWarnings("allcheckers:purity.not.deterministic.call") - void method8() {} + void method(@NonNegative int i) { + @SuppressWarnings("index") + @NonNegative int x = i - 1; + } + + void method2() { + @SuppressWarnings("fallthrough") + int x = 0; + } + + @SuppressWarnings({"tainting", "lowerbound"}) + void method3() { + @SuppressWarnings("upperbound:assignment.type.incompatible") + int z = 0; + } + + void method4() { + @SuppressWarnings("lowerbound:assignment.type.incompatible") + @NonNegative int x = -1; + } + + @SuppressWarnings("purity.not.deterministic.call") + void method5() {} + + @SuppressWarnings("purity") + void method6() {} + + @SuppressWarnings("index:foo.bar.baz") + void method7() {} + + @SuppressWarnings("allcheckers:purity.not.deterministic.call") + void method8() {} } diff --git a/checker/jtreg/index/valuestub/Test.java b/checker/jtreg/index/valuestub/Test.java index c06676fa9f8..6c35a2d30bc 100644 --- a/checker/jtreg/index/valuestub/Test.java +++ b/checker/jtreg/index/valuestub/Test.java @@ -4,7 +4,7 @@ @SuppressWarnings("index") public class Test { - public @LengthOf("this") int length() { - return 1; - } + public @LengthOf("this") int length() { + return 1; + } } diff --git a/checker/jtreg/index/valuestub/UseTest.java b/checker/jtreg/index/valuestub/UseTest.java index a117721fdd6..4be77b34b5f 100644 --- a/checker/jtreg/index/valuestub/UseTest.java +++ b/checker/jtreg/index/valuestub/UseTest.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class UseTest { - void test(Test t) { - @IndexOrHigh("t") int x = t.length(); - } + void test(Test t) { + @IndexOrHigh("t") int x = t.length(); + } } diff --git a/checker/jtreg/issue1133/ClassA.java b/checker/jtreg/issue1133/ClassA.java index 88d5b48d904..1c3e27f646c 100644 --- a/checker/jtreg/issue1133/ClassA.java +++ b/checker/jtreg/issue1133/ClassA.java @@ -1,5 +1,5 @@ public class ClassA { - void test(ClassB classB) { - classB.test(); - } + void test(ClassB classB) { + classB.test(); + } } diff --git a/checker/jtreg/issue1133/ClassB.java b/checker/jtreg/issue1133/ClassB.java index c545d790662..e8a1d1060d3 100644 --- a/checker/jtreg/issue1133/ClassB.java +++ b/checker/jtreg/issue1133/ClassB.java @@ -1,5 +1,5 @@ public class ClassB { - public void test() { - Object @Nullable [] o; - } + public void test() { + Object @Nullable [] o; + } } diff --git a/checker/jtreg/issue469/advancedcrash/CrashyInterface.java b/checker/jtreg/issue469/advancedcrash/CrashyInterface.java index 65e072885de..e687821a1f1 100644 --- a/checker/jtreg/issue469/advancedcrash/CrashyInterface.java +++ b/checker/jtreg/issue469/advancedcrash/CrashyInterface.java @@ -1,5 +1,5 @@ package advancedcrash; public interface CrashyInterface { - void makeItLongerAndCrash(); + void makeItLongerAndCrash(); } diff --git a/checker/jtreg/issue469/advancedcrash/LetItCrash.java b/checker/jtreg/issue469/advancedcrash/LetItCrash.java index 14cebb2e3e5..f95fdfb67c6 100644 --- a/checker/jtreg/issue469/advancedcrash/LetItCrash.java +++ b/checker/jtreg/issue469/advancedcrash/LetItCrash.java @@ -2,15 +2,15 @@ public class LetItCrash implements SomeInterface { - Integer longer = 0; + Integer longer = 0; - @Override - public void doSomethingFancy() { - System.out.print("Yay"); - } + @Override + public void doSomethingFancy() { + System.out.print("Yay"); + } - @Override - public void makeItLongerAndCrash() { - this.longer += 0; - } + @Override + public void makeItLongerAndCrash() { + this.longer += 0; + } } diff --git a/checker/jtreg/issue469/advancedcrash/SomeInterface.java b/checker/jtreg/issue469/advancedcrash/SomeInterface.java index 100621f38a8..e83a4dc9747 100644 --- a/checker/jtreg/issue469/advancedcrash/SomeInterface.java +++ b/checker/jtreg/issue469/advancedcrash/SomeInterface.java @@ -1,5 +1,5 @@ package advancedcrash; public interface SomeInterface extends CrashyInterface { - void doSomethingFancy(); + void doSomethingFancy(); } diff --git a/checker/jtreg/issue469/simplecrash/CrashyInterface.java b/checker/jtreg/issue469/simplecrash/CrashyInterface.java index 49d75c6c85f..7016050f86e 100644 --- a/checker/jtreg/issue469/simplecrash/CrashyInterface.java +++ b/checker/jtreg/issue469/simplecrash/CrashyInterface.java @@ -1,5 +1,5 @@ package simplecrash; public interface CrashyInterface { - void makeItLongerAndCrash(); + void makeItLongerAndCrash(); } diff --git a/checker/jtreg/issue469/simplecrash/LetItCrash.java b/checker/jtreg/issue469/simplecrash/LetItCrash.java index 6ef150ad5d7..519742f513c 100644 --- a/checker/jtreg/issue469/simplecrash/LetItCrash.java +++ b/checker/jtreg/issue469/simplecrash/LetItCrash.java @@ -1,10 +1,10 @@ package simplecrash; public class LetItCrash implements CrashyInterface { - private Long longer = 0L; + private Long longer = 0L; - @Override - public void makeItLongerAndCrash() { - this.longer += 0; - } + @Override + public void makeItLongerAndCrash() { + this.longer += 0; + } } diff --git a/checker/jtreg/issue469/simplecrash/SomeRandomClass.java b/checker/jtreg/issue469/simplecrash/SomeRandomClass.java index a28dbf5bbd9..f4e89aaa4d5 100644 --- a/checker/jtreg/issue469/simplecrash/SomeRandomClass.java +++ b/checker/jtreg/issue469/simplecrash/SomeRandomClass.java @@ -1,7 +1,7 @@ package simplecrash; public class SomeRandomClass { - void randomStuff(LetItCrash letItCrash) { - letItCrash.makeItLongerAndCrash(); - } + void randomStuff(LetItCrash letItCrash) { + letItCrash.makeItLongerAndCrash(); + } } diff --git a/checker/jtreg/multiplecheckers/NullnessInterning.java b/checker/jtreg/multiplecheckers/NullnessInterning.java index faf6529c90a..89b3f95b131 100644 --- a/checker/jtreg/multiplecheckers/NullnessInterning.java +++ b/checker/jtreg/multiplecheckers/NullnessInterning.java @@ -16,9 +16,9 @@ */ public class NullnessInterning { - Object f = null; + Object f = null; - void m(Object p) { - if (f == p) {} - } + void m(Object p) { + if (f == p) {} + } } diff --git a/checker/jtreg/multipleexecutions/Main.java b/checker/jtreg/multipleexecutions/Main.java index a4aa9dac6f5..ecf00de1e1a 100644 --- a/checker/jtreg/multipleexecutions/Main.java +++ b/checker/jtreg/multipleexecutions/Main.java @@ -11,52 +11,50 @@ * https://groups.google.com/d/msg/checker-framework-dev/FvWmCxB8OpE/Cgp1DsPwnWwJ */ -import org.checkerframework.checker.regex.RegexChecker; - import java.io.File; import java.util.Arrays; - import javax.tools.JavaCompiler; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; +import org.checkerframework.checker.regex.RegexChecker; public class Main { - public static void main(String[] args) { - final JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); - final StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null); - if (!doStuff(javac, fileManager)) { - return; - } - if (!doStuff(javac, fileManager)) { - return; - } - if (!doStuff(javac, fileManager)) { - return; - } + public static void main(String[] args) { + final JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); + final StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null); + if (!doStuff(javac, fileManager)) { + return; } + if (!doStuff(javac, fileManager)) { + return; + } + if (!doStuff(javac, fileManager)) { + return; + } + } - public static boolean doStuff(JavaCompiler javac, StandardJavaFileManager fileManager) { - File testfile = new File(System.getProperty("test.src", "."), "Test.java"); + public static boolean doStuff(JavaCompiler javac, StandardJavaFileManager fileManager) { + File testfile = new File(System.getProperty("test.src", "."), "Test.java"); - JavaCompiler.CompilationTask task = - javac.getTask( - null, - null, - null, - Arrays.asList( - "-classpath", - "../../dist/checker.jar", - "-proc:only", - "-AprintAllQualifiers", - "-source", - "8", - "-target", - "8", - "-Xlint:-options"), - null, - fileManager.getJavaFileObjects(testfile)); - task.setProcessors(Arrays.asList(new RegexChecker())); - return task.call(); - } + JavaCompiler.CompilationTask task = + javac.getTask( + null, + null, + null, + Arrays.asList( + "-classpath", + "../../dist/checker.jar", + "-proc:only", + "-AprintAllQualifiers", + "-source", + "8", + "-target", + "8", + "-Xlint:-options"), + null, + fileManager.getJavaFileObjects(testfile)); + task.setProcessors(Arrays.asList(new RegexChecker())); + return task.call(); + } } diff --git a/checker/jtreg/multipleexecutions/Test.java b/checker/jtreg/multipleexecutions/Test.java index 5322099b53d..4813553b092 100644 --- a/checker/jtreg/multipleexecutions/Test.java +++ b/checker/jtreg/multipleexecutions/Test.java @@ -2,9 +2,9 @@ import org.checkerframework.checker.regex.util.RegexUtil; public class Test { - void foo(String simple) { - if (RegexUtil.isRegex(simple)) { - @Regex String in = simple; - } + void foo(String simple) { + if (RegexUtil.isRegex(simple)) { + @Regex String in = simple; } + } } diff --git a/checker/jtreg/nullness/AssignmentPerformanceTest.java b/checker/jtreg/nullness/AssignmentPerformanceTest.java index 884c900456f..e71e69d5a36 100644 --- a/checker/jtreg/nullness/AssignmentPerformanceTest.java +++ b/checker/jtreg/nullness/AssignmentPerformanceTest.java @@ -5,457 +5,457 @@ * @compile/timeout=60 -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint AssignmentPerformanceTest.java */ public class AssignmentPerformanceTest { - private String s1; - private String s2; - private String s3; - private String s4; - private String s5; - private String s6; - private String s7; - private String s8; - private String s9; - private String s10; - private String s11; - private String s12; - private String s13; - private String s14; - private String s15; - private String s16; - private String s17; - private String s18; - private String s19; - private String s20; - private String s21; - private String s22; - private String s23; - private String s24; - private String s25; - private String s26; - private String s27; - private String s28; - private String s29; - private String s30; - private String s31; - private String s32; - private String s33; - private String s34; - private String s35; - private String s36; - private String s37; - private String s38; - private String s39; - private String s40; - private String s41; - private String s42; - private String s43; - private String s44; - private String s45; - private String s46; - private String s47; - private String s48; - private String s49; - private String s50; - private String s51; - private String s52; - private String s53; - private String s54; - private String s55; - private String s56; - private String s57; - private String s58; - private String s59; - private String s60; - private String s61; - private String s62; - private String s63; - private String s64; - private String s65; - private String s66; - private String s67; - private String s68; - private String s69; - private String s70; - private String s71; - private String s72; - private String s73; - private String s74; - private String s75; - private String s76; - private String s77; - private String s78; - private String s79; - private String s80; - private String s81; - private String s82; - private String s83; - private String s84; - private String s85; - private String s86; - private String s87; - private String s88; - private String s89; - private String s90; - private String s91; - private String s92; - private String s93; - private String s94; - private String s95; - private String s96; - private String s97; - private String s98; - private String s99; - private String s100; - private String s101; - private String s102; - private String s103; - private String s104; - private String s105; - private String s106; - private String s107; - private String s108; - private String s109; - private String s110; - private String s111; - private String s112; - private String s113; - private String s114; - private String s115; - private String s116; - private String s117; - private String s118; - private String s119; - private String s120; - private String s121; - private String s122; - private String s123; - private String s124; - private String s125; - private String s126; - private String s127; - private String s128; - private String s129; - private String s130; - private String s131; - private String s132; - private String s133; - private String s134; - private String s135; - private String s136; - private String s137; - private String s138; - private String s139; - private String s140; - private String s141; - private String s142; - private String s143; - private String s144; - private String s145; - private String s146; - private String s147; - private String s148; - private String s149; - private String s150; + private String s1; + private String s2; + private String s3; + private String s4; + private String s5; + private String s6; + private String s7; + private String s8; + private String s9; + private String s10; + private String s11; + private String s12; + private String s13; + private String s14; + private String s15; + private String s16; + private String s17; + private String s18; + private String s19; + private String s20; + private String s21; + private String s22; + private String s23; + private String s24; + private String s25; + private String s26; + private String s27; + private String s28; + private String s29; + private String s30; + private String s31; + private String s32; + private String s33; + private String s34; + private String s35; + private String s36; + private String s37; + private String s38; + private String s39; + private String s40; + private String s41; + private String s42; + private String s43; + private String s44; + private String s45; + private String s46; + private String s47; + private String s48; + private String s49; + private String s50; + private String s51; + private String s52; + private String s53; + private String s54; + private String s55; + private String s56; + private String s57; + private String s58; + private String s59; + private String s60; + private String s61; + private String s62; + private String s63; + private String s64; + private String s65; + private String s66; + private String s67; + private String s68; + private String s69; + private String s70; + private String s71; + private String s72; + private String s73; + private String s74; + private String s75; + private String s76; + private String s77; + private String s78; + private String s79; + private String s80; + private String s81; + private String s82; + private String s83; + private String s84; + private String s85; + private String s86; + private String s87; + private String s88; + private String s89; + private String s90; + private String s91; + private String s92; + private String s93; + private String s94; + private String s95; + private String s96; + private String s97; + private String s98; + private String s99; + private String s100; + private String s101; + private String s102; + private String s103; + private String s104; + private String s105; + private String s106; + private String s107; + private String s108; + private String s109; + private String s110; + private String s111; + private String s112; + private String s113; + private String s114; + private String s115; + private String s116; + private String s117; + private String s118; + private String s119; + private String s120; + private String s121; + private String s122; + private String s123; + private String s124; + private String s125; + private String s126; + private String s127; + private String s128; + private String s129; + private String s130; + private String s131; + private String s132; + private String s133; + private String s134; + private String s135; + private String s136; + private String s137; + private String s138; + private String s139; + private String s140; + private String s141; + private String s142; + private String s143; + private String s144; + private String s145; + private String s146; + private String s147; + private String s148; + private String s149; + private String s150; - public AssignmentPerformanceTest( - String s1, - String s2, - String s3, - String s4, - String s5, - String s6, - String s7, - String s8, - String s9, - String s10, - String s11, - String s12, - String s13, - String s14, - String s15, - String s16, - String s17, - String s18, - String s19, - String s20, - String s21, - String s22, - String s23, - String s24, - String s25, - String s26, - String s27, - String s28, - String s29, - String s30, - String s31, - String s32, - String s33, - String s34, - String s35, - String s36, - String s37, - String s38, - String s39, - String s40, - String s41, - String s42, - String s43, - String s44, - String s45, - String s46, - String s47, - String s48, - String s49, - String s50, - String s51, - String s52, - String s53, - String s54, - String s55, - String s56, - String s57, - String s58, - String s59, - String s60, - String s61, - String s62, - String s63, - String s64, - String s65, - String s66, - String s67, - String s68, - String s69, - String s70, - String s71, - String s72, - String s73, - String s74, - String s75, - String s76, - String s77, - String s78, - String s79, - String s80, - String s81, - String s82, - String s83, - String s84, - String s85, - String s86, - String s87, - String s88, - String s89, - String s90, - String s91, - String s92, - String s93, - String s94, - String s95, - String s96, - String s97, - String s98, - String s99, - String s100, - String s101, - String s102, - String s103, - String s104, - String s105, - String s106, - String s107, - String s108, - String s109, - String s110, - String s111, - String s112, - String s113, - String s114, - String s115, - String s116, - String s117, - String s118, - String s119, - String s120, - String s121, - String s122, - String s123, - String s124, - String s125, - String s126, - String s127, - String s128, - String s129, - String s130, - String s131, - String s132, - String s133, - String s134, - String s135, - String s136, - String s137, - String s138, - String s139, - String s140, - String s141, - String s142, - String s143, - String s144, - String s145, - String s146, - String s147, - String s148, - String s149, - String s150) { - this.s1 = s1; - this.s2 = s2; - this.s3 = s3; - this.s4 = s4; - this.s5 = s5; - this.s6 = s6; - this.s7 = s7; - this.s8 = s8; - this.s9 = s9; - this.s10 = s10; - this.s11 = s11; - this.s12 = s12; - this.s13 = s13; - this.s14 = s14; - this.s15 = s15; - this.s16 = s16; - this.s17 = s17; - this.s18 = s18; - this.s19 = s19; - this.s20 = s20; - this.s21 = s21; - this.s22 = s22; - this.s23 = s23; - this.s24 = s24; - this.s25 = s25; - this.s26 = s26; - this.s27 = s27; - this.s28 = s28; - this.s29 = s29; - this.s30 = s30; - this.s31 = s31; - this.s32 = s32; - this.s33 = s33; - this.s34 = s34; - this.s35 = s35; - this.s36 = s36; - this.s37 = s37; - this.s38 = s38; - this.s39 = s39; - this.s40 = s40; - this.s41 = s41; - this.s42 = s42; - this.s43 = s43; - this.s44 = s44; - this.s45 = s45; - this.s46 = s46; - this.s47 = s47; - this.s48 = s48; - this.s49 = s49; - this.s50 = s50; - this.s51 = s51; - this.s52 = s52; - this.s53 = s53; - this.s54 = s54; - this.s55 = s55; - this.s56 = s56; - this.s57 = s57; - this.s58 = s58; - this.s59 = s59; - this.s60 = s60; - this.s61 = s61; - this.s62 = s62; - this.s63 = s63; - this.s64 = s64; - this.s65 = s65; - this.s66 = s66; - this.s67 = s67; - this.s68 = s68; - this.s69 = s69; - this.s70 = s70; - this.s71 = s71; - this.s72 = s72; - this.s73 = s73; - this.s74 = s74; - this.s75 = s75; - this.s76 = s76; - this.s77 = s77; - this.s78 = s78; - this.s79 = s79; - this.s80 = s80; - this.s81 = s81; - this.s82 = s82; - this.s83 = s83; - this.s84 = s84; - this.s85 = s85; - this.s86 = s86; - this.s87 = s87; - this.s88 = s88; - this.s89 = s89; - this.s90 = s90; - this.s91 = s91; - this.s92 = s92; - this.s93 = s93; - this.s94 = s94; - this.s95 = s95; - this.s96 = s96; - this.s97 = s97; - this.s98 = s98; - this.s99 = s99; - this.s100 = s100; - this.s101 = s101; - this.s102 = s102; - this.s103 = s103; - this.s104 = s104; - this.s105 = s105; - this.s106 = s106; - this.s107 = s107; - this.s108 = s108; - this.s109 = s109; - this.s110 = s110; - this.s111 = s111; - this.s112 = s112; - this.s113 = s113; - this.s114 = s114; - this.s115 = s115; - this.s116 = s116; - this.s117 = s117; - this.s118 = s118; - this.s119 = s119; - this.s120 = s120; - this.s121 = s121; - this.s122 = s122; - this.s123 = s123; - this.s124 = s124; - this.s125 = s125; - this.s126 = s126; - this.s127 = s127; - this.s128 = s128; - this.s129 = s129; - this.s130 = s130; - this.s131 = s131; - this.s132 = s132; - this.s133 = s133; - this.s134 = s134; - this.s135 = s135; - this.s136 = s136; - this.s137 = s137; - this.s138 = s138; - this.s139 = s139; - this.s140 = s140; - this.s141 = s141; - this.s142 = s142; - this.s143 = s143; - this.s144 = s144; - this.s145 = s145; - this.s146 = s146; - this.s147 = s147; - this.s148 = s148; - this.s149 = s149; - this.s150 = s150; - } + public AssignmentPerformanceTest( + String s1, + String s2, + String s3, + String s4, + String s5, + String s6, + String s7, + String s8, + String s9, + String s10, + String s11, + String s12, + String s13, + String s14, + String s15, + String s16, + String s17, + String s18, + String s19, + String s20, + String s21, + String s22, + String s23, + String s24, + String s25, + String s26, + String s27, + String s28, + String s29, + String s30, + String s31, + String s32, + String s33, + String s34, + String s35, + String s36, + String s37, + String s38, + String s39, + String s40, + String s41, + String s42, + String s43, + String s44, + String s45, + String s46, + String s47, + String s48, + String s49, + String s50, + String s51, + String s52, + String s53, + String s54, + String s55, + String s56, + String s57, + String s58, + String s59, + String s60, + String s61, + String s62, + String s63, + String s64, + String s65, + String s66, + String s67, + String s68, + String s69, + String s70, + String s71, + String s72, + String s73, + String s74, + String s75, + String s76, + String s77, + String s78, + String s79, + String s80, + String s81, + String s82, + String s83, + String s84, + String s85, + String s86, + String s87, + String s88, + String s89, + String s90, + String s91, + String s92, + String s93, + String s94, + String s95, + String s96, + String s97, + String s98, + String s99, + String s100, + String s101, + String s102, + String s103, + String s104, + String s105, + String s106, + String s107, + String s108, + String s109, + String s110, + String s111, + String s112, + String s113, + String s114, + String s115, + String s116, + String s117, + String s118, + String s119, + String s120, + String s121, + String s122, + String s123, + String s124, + String s125, + String s126, + String s127, + String s128, + String s129, + String s130, + String s131, + String s132, + String s133, + String s134, + String s135, + String s136, + String s137, + String s138, + String s139, + String s140, + String s141, + String s142, + String s143, + String s144, + String s145, + String s146, + String s147, + String s148, + String s149, + String s150) { + this.s1 = s1; + this.s2 = s2; + this.s3 = s3; + this.s4 = s4; + this.s5 = s5; + this.s6 = s6; + this.s7 = s7; + this.s8 = s8; + this.s9 = s9; + this.s10 = s10; + this.s11 = s11; + this.s12 = s12; + this.s13 = s13; + this.s14 = s14; + this.s15 = s15; + this.s16 = s16; + this.s17 = s17; + this.s18 = s18; + this.s19 = s19; + this.s20 = s20; + this.s21 = s21; + this.s22 = s22; + this.s23 = s23; + this.s24 = s24; + this.s25 = s25; + this.s26 = s26; + this.s27 = s27; + this.s28 = s28; + this.s29 = s29; + this.s30 = s30; + this.s31 = s31; + this.s32 = s32; + this.s33 = s33; + this.s34 = s34; + this.s35 = s35; + this.s36 = s36; + this.s37 = s37; + this.s38 = s38; + this.s39 = s39; + this.s40 = s40; + this.s41 = s41; + this.s42 = s42; + this.s43 = s43; + this.s44 = s44; + this.s45 = s45; + this.s46 = s46; + this.s47 = s47; + this.s48 = s48; + this.s49 = s49; + this.s50 = s50; + this.s51 = s51; + this.s52 = s52; + this.s53 = s53; + this.s54 = s54; + this.s55 = s55; + this.s56 = s56; + this.s57 = s57; + this.s58 = s58; + this.s59 = s59; + this.s60 = s60; + this.s61 = s61; + this.s62 = s62; + this.s63 = s63; + this.s64 = s64; + this.s65 = s65; + this.s66 = s66; + this.s67 = s67; + this.s68 = s68; + this.s69 = s69; + this.s70 = s70; + this.s71 = s71; + this.s72 = s72; + this.s73 = s73; + this.s74 = s74; + this.s75 = s75; + this.s76 = s76; + this.s77 = s77; + this.s78 = s78; + this.s79 = s79; + this.s80 = s80; + this.s81 = s81; + this.s82 = s82; + this.s83 = s83; + this.s84 = s84; + this.s85 = s85; + this.s86 = s86; + this.s87 = s87; + this.s88 = s88; + this.s89 = s89; + this.s90 = s90; + this.s91 = s91; + this.s92 = s92; + this.s93 = s93; + this.s94 = s94; + this.s95 = s95; + this.s96 = s96; + this.s97 = s97; + this.s98 = s98; + this.s99 = s99; + this.s100 = s100; + this.s101 = s101; + this.s102 = s102; + this.s103 = s103; + this.s104 = s104; + this.s105 = s105; + this.s106 = s106; + this.s107 = s107; + this.s108 = s108; + this.s109 = s109; + this.s110 = s110; + this.s111 = s111; + this.s112 = s112; + this.s113 = s113; + this.s114 = s114; + this.s115 = s115; + this.s116 = s116; + this.s117 = s117; + this.s118 = s118; + this.s119 = s119; + this.s120 = s120; + this.s121 = s121; + this.s122 = s122; + this.s123 = s123; + this.s124 = s124; + this.s125 = s125; + this.s126 = s126; + this.s127 = s127; + this.s128 = s128; + this.s129 = s129; + this.s130 = s130; + this.s131 = s131; + this.s132 = s132; + this.s133 = s133; + this.s134 = s134; + this.s135 = s135; + this.s136 = s136; + this.s137 = s137; + this.s138 = s138; + this.s139 = s139; + this.s140 = s140; + this.s141 = s141; + this.s142 = s142; + this.s143 = s143; + this.s144 = s144; + this.s145 = s145; + this.s146 = s146; + this.s147 = s147; + this.s148 = s148; + this.s149 = s149; + this.s150 = s150; + } } diff --git a/checker/jtreg/nullness/DefaultNonPublicClass.java b/checker/jtreg/nullness/DefaultNonPublicClass.java index 81030cd57b3..6908779f315 100644 --- a/checker/jtreg/nullness/DefaultNonPublicClass.java +++ b/checker/jtreg/nullness/DefaultNonPublicClass.java @@ -10,14 +10,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; class Test { - Integer foo() { - return 123; - } + Integer foo() { + return 123; + } } public class DefaultNonPublicClass { - public static void main(String[] args) { - Test ti = new Test(); - @NonNull Integer ls = ti.foo(); - } + public static void main(String[] args) { + Test ti = new Test(); + @NonNull Integer ls = ti.foo(); + } } diff --git a/checker/jtreg/nullness/Issue1438.java b/checker/jtreg/nullness/Issue1438.java index f90e6af852b..7f0039f77ec 100644 --- a/checker/jtreg/nullness/Issue1438.java +++ b/checker/jtreg/nullness/Issue1438.java @@ -9,2011 +9,2011 @@ import java.util.Map; public class Issue1438 { - // Do not initialize these variables. The Nullness Checker is supposed to issue an error about - // uninitialized fields. - static Integer v0; - static Integer v1; - static Integer v2; - static Integer v3; - static Integer v4; - static Integer v5; - static Integer v6; - static Integer v7; - static Integer v8; - static Integer v9; - static Integer v10; - static Integer v11; - static Integer v12; - static Integer v13; - static Integer v14; - static Integer v15; - static Integer v16; - static Integer v17; - static Integer v18; - static Integer v19; - static Integer v20; - static Integer v21; - static Integer v22; - static Integer v23; - static Integer v24; - static Integer v25; - static Integer v26; - static Integer v27; - static Integer v28; - static Integer v29; - static Integer v30; - static Integer v31; - static Integer v32; - static Integer v33; - static Integer v34; - static Integer v35; - static Integer v36; - static Integer v37; - static Integer v38; - static Integer v39; - static Integer v40; - static Integer v41; - static Integer v42; - static Integer v43; - static Integer v44; - static Integer v45; - static Integer v46; - static Integer v47; - static Integer v48; - static Integer v49; - static Integer v50; - static Integer v51; - static Integer v52; - static Integer v53; - static Integer v54; - static Integer v55; - static Integer v56; - static Integer v57; - static Integer v58; - static Integer v59; - static Integer v60; - static Integer v61; - static Integer v62; - static Integer v63; - static Integer v64; - static Integer v65; - static Integer v66; - static Integer v67; - static Integer v68; - static Integer v69; - static Integer v70; - static Integer v71; - static Integer v72; - static Integer v73; - static Integer v74; - static Integer v75; - static Integer v76; - static Integer v77; - static Integer v78; - static Integer v79; - static Integer v80; - static Integer v81; - static Integer v82; - static Integer v83; - static Integer v84; - static Integer v85; - static Integer v86; - static Integer v87; - static Integer v88; - static Integer v89; - static Integer v90; - static Integer v91; - static Integer v92; - static Integer v93; - static Integer v94; - static Integer v95; - static Integer v96; - static Integer v97; - static Integer v98; - static Integer v99; - static Integer v100; - static Integer v101; - static Integer v102; - static Integer v103; - static Integer v104; - static Integer v105; - static Integer v106; - static Integer v107; - static Integer v108; - static Integer v109; - static Integer v110; - static Integer v111; - static Integer v112; - static Integer v113; - static Integer v114; - static Integer v115; - static Integer v116; - static Integer v117; - static Integer v118; - static Integer v119; - static Integer v120; - static Integer v121; - static Integer v122; - static Integer v123; - static Integer v124; - static Integer v125; - static Integer v126; - static Integer v127; - static Integer v128; - static Integer v129; - static Integer v130; - static Integer v131; - static Integer v132; - static Integer v133; - static Integer v134; - static Integer v135; - static Integer v136; - static Integer v137; - static Integer v138; - static Integer v139; - static Integer v140; - static Integer v141; - static Integer v142; - static Integer v143; - static Integer v144; - static Integer v145; - static Integer v146; - static Integer v147; - static Integer v148; - static Integer v149; - static Integer v150; - static Integer v151; - static Integer v152; - static Integer v153; - static Integer v154; - static Integer v155; - static Integer v156; - static Integer v157; - static Integer v158; - static Integer v159; - static Integer v160; - static Integer v161; - static Integer v162; - static Integer v163; - static Integer v164; - static Integer v165; - static Integer v166; - static Integer v167; - static Integer v168; - static Integer v169; - static Integer v170; - static Integer v171; - static Integer v172; - static Integer v173; - static Integer v174; - static Integer v175; - static Integer v176; - static Integer v177; - static Integer v178; - static Integer v179; - static Integer v180; - static Integer v181; - static Integer v182; - static Integer v183; - static Integer v184; - static Integer v185; - static Integer v186; - static Integer v187; - static Integer v188; - static Integer v189; - static Integer v190; - static Integer v191; - static Integer v192; - static Integer v193; - static Integer v194; - static Integer v195; - static Integer v196; - static Integer v197; - static Integer v198; - static Integer v199; - static Integer v200; - static Integer v201; - static Integer v202; - static Integer v203; - static Integer v204; - static Integer v205; - static Integer v206; - static Integer v207; - static Integer v208; - static Integer v209; - static Integer v210; - static Integer v211; - static Integer v212; - static Integer v213; - static Integer v214; - static Integer v215; - static Integer v216; - static Integer v217; - static Integer v218; - static Integer v219; - static Integer v220; - static Integer v221; - static Integer v222; - static Integer v223; - static Integer v224; - static Integer v225; - static Integer v226; - static Integer v227; - static Integer v228; - static Integer v229; - static Integer v230; - static Integer v231; - static Integer v232; - static Integer v233; - static Integer v234; - static Integer v235; - static Integer v236; - static Integer v237; - static Integer v238; - static Integer v239; - static Integer v240; - static Integer v241; - static Integer v242; - static Integer v243; - static Integer v244; - static Integer v245; - static Integer v246; - static Integer v247; - static Integer v248; - static Integer v249; - static Integer v250; - static Integer v251; - static Integer v252; - static Integer v253; - static Integer v254; - static Integer v255; - static Integer v256; - static Integer v257; - static Integer v258; - static Integer v259; - static Integer v260; - static Integer v261; - static Integer v262; - static Integer v263; - static Integer v264; - static Integer v265; - static Integer v266; - static Integer v267; - static Integer v268; - static Integer v269; - static Integer v270; - static Integer v271; - static Integer v272; - static Integer v273; - static Integer v274; - static Integer v275; - static Integer v276; - static Integer v277; - static Integer v278; - static Integer v279; - static Integer v280; - static Integer v281; - static Integer v282; - static Integer v283; - static Integer v284; - static Integer v285; - static Integer v286; - static Integer v287; - static Integer v288; - static Integer v289; - static Integer v290; - static Integer v291; - static Integer v292; - static Integer v293; - static Integer v294; - static Integer v295; - static Integer v296; - static Integer v297; - static Integer v298; - static Integer v299; - static Integer v300; - static Integer v301; - static Integer v302; - static Integer v303; - static Integer v304; - static Integer v305; - static Integer v306; - static Integer v307; - static Integer v308; - static Integer v309; - static Integer v310; - static Integer v311; - static Integer v312; - static Integer v313; - static Integer v314; - static Integer v315; - static Integer v316; - static Integer v317; - static Integer v318; - static Integer v319; - static Integer v320; - static Integer v321; - static Integer v322; - static Integer v323; - static Integer v324; - static Integer v325; - static Integer v326; - static Integer v327; - static Integer v328; - static Integer v329; - static Integer v330; - static Integer v331; - static Integer v332; - static Integer v333; - static Integer v334; - static Integer v335; - static Integer v336; - static Integer v337; - static Integer v338; - static Integer v339; - static Integer v340; - static Integer v341; - static Integer v342; - static Integer v343; - static Integer v344; - static Integer v345; - static Integer v346; - static Integer v347; - static Integer v348; - static Integer v349; - static Integer v350; - static Integer v351; - static Integer v352; - static Integer v353; - static Integer v354; - static Integer v355; - static Integer v356; - static Integer v357; - static Integer v358; - static Integer v359; - static Integer v360; - static Integer v361; - static Integer v362; - static Integer v363; - static Integer v364; - static Integer v365; - static Integer v366; - static Integer v367; - static Integer v368; - static Integer v369; - static Integer v370; - static Integer v371; - static Integer v372; - static Integer v373; - static Integer v374; - static Integer v375; - static Integer v376; - static Integer v377; - static Integer v378; - static Integer v379; - static Integer v380; - static Integer v381; - static Integer v382; - static Integer v383; - static Integer v384; - static Integer v385; - static Integer v386; - static Integer v387; - static Integer v388; - static Integer v389; - static Integer v390; - static Integer v391; - static Integer v392; - static Integer v393; - static Integer v394; - static Integer v395; - static Integer v396; - static Integer v397; - static Integer v398; - static Integer v399; - static Integer v400; - static Integer v401; - static Integer v402; - static Integer v403; - static Integer v404; - static Integer v405; - static Integer v406; - static Integer v407; - static Integer v408; - static Integer v409; - static Integer v410; - static Integer v411; - static Integer v412; - static Integer v413; - static Integer v414; - static Integer v415; - static Integer v416; - static Integer v417; - static Integer v418; - static Integer v419; - static Integer v420; - static Integer v421; - static Integer v422; - static Integer v423; - static Integer v424; - static Integer v425; - static Integer v426; - static Integer v427; - static Integer v428; - static Integer v429; - static Integer v430; - static Integer v431; - static Integer v432; - static Integer v433; - static Integer v434; - static Integer v435; - static Integer v436; - static Integer v437; - static Integer v438; - static Integer v439; - static Integer v440; - static Integer v441; - static Integer v442; - static Integer v443; - static Integer v444; - static Integer v445; - static Integer v446; - static Integer v447; - static Integer v448; - static Integer v449; - static Integer v450; - static Integer v451; - static Integer v452; - static Integer v453; - static Integer v454; - static Integer v455; - static Integer v456; - static Integer v457; - static Integer v458; - static Integer v459; - static Integer v460; - static Integer v461; - static Integer v462; - static Integer v463; - static Integer v464; - static Integer v465; - static Integer v466; - static Integer v467; - static Integer v468; - static Integer v469; - static Integer v470; - static Integer v471; - static Integer v472; - static Integer v473; - static Integer v474; - static Integer v475; - static Integer v476; - static Integer v477; - static Integer v478; - static Integer v479; - static Integer v480; - static Integer v481; - static Integer v482; - static Integer v483; - static Integer v484; - static Integer v485; - static Integer v486; - static Integer v487; - static Integer v488; - static Integer v489; - static Integer v490; - static Integer v491; - static Integer v492; - static Integer v493; - static Integer v494; - static Integer v495; - static Integer v496; - static Integer v497; - static Integer v498; - static Integer v499; - static Integer v500; - static Integer v501; - static Integer v502; - static Integer v503; - static Integer v504; - static Integer v505; - static Integer v506; - static Integer v507; - static Integer v508; - static Integer v509; - static Integer v510; - static Integer v511; - static Integer v512; - static Integer v513; - static Integer v514; - static Integer v515; - static Integer v516; - static Integer v517; - static Integer v518; - static Integer v519; - static Integer v520; - static Integer v521; - static Integer v522; - static Integer v523; - static Integer v524; - static Integer v525; - static Integer v526; - static Integer v527; - static Integer v528; - static Integer v529; - static Integer v530; - static Integer v531; - static Integer v532; - static Integer v533; - static Integer v534; - static Integer v535; - static Integer v536; - static Integer v537; - static Integer v538; - static Integer v539; - static Integer v540; - static Integer v541; - static Integer v542; - static Integer v543; - static Integer v544; - static Integer v545; - static Integer v546; - static Integer v547; - static Integer v548; - static Integer v549; - static Integer v550; - static Integer v551; - static Integer v552; - static Integer v553; - static Integer v554; - static Integer v555; - static Integer v556; - static Integer v557; - static Integer v558; - static Integer v559; - static Integer v560; - static Integer v561; - static Integer v562; - static Integer v563; - static Integer v564; - static Integer v565; - static Integer v566; - static Integer v567; - static Integer v568; - static Integer v569; - static Integer v570; - static Integer v571; - static Integer v572; - static Integer v573; - static Integer v574; - static Integer v575; - static Integer v576; - static Integer v577; - static Integer v578; - static Integer v579; - static Integer v580; - static Integer v581; - static Integer v582; - static Integer v583; - static Integer v584; - static Integer v585; - static Integer v586; - static Integer v587; - static Integer v588; - static Integer v589; - static Integer v590; - static Integer v591; - static Integer v592; - static Integer v593; - static Integer v594; - static Integer v595; - static Integer v596; - static Integer v597; - static Integer v598; - static Integer v599; - static Integer v600; - static Integer v601; - static Integer v602; - static Integer v603; - static Integer v604; - static Integer v605; - static Integer v606; - static Integer v607; - static Integer v608; - static Integer v609; - static Integer v610; - static Integer v611; - static Integer v612; - static Integer v613; - static Integer v614; - static Integer v615; - static Integer v616; - static Integer v617; - static Integer v618; - static Integer v619; - static Integer v620; - static Integer v621; - static Integer v622; - static Integer v623; - static Integer v624; - static Integer v625; - static Integer v626; - static Integer v627; - static Integer v628; - static Integer v629; - static Integer v630; - static Integer v631; - static Integer v632; - static Integer v633; - static Integer v634; - static Integer v635; - static Integer v636; - static Integer v637; - static Integer v638; - static Integer v639; - static Integer v640; - static Integer v641; - static Integer v642; - static Integer v643; - static Integer v644; - static Integer v645; - static Integer v646; - static Integer v647; - static Integer v648; - static Integer v649; - static Integer v650; - static Integer v651; - static Integer v652; - static Integer v653; - static Integer v654; - static Integer v655; - static Integer v656; - static Integer v657; - static Integer v658; - static Integer v659; - static Integer v660; - static Integer v661; - static Integer v662; - static Integer v663; - static Integer v664; - static Integer v665; - static Integer v666; - static Integer v667; - static Integer v668; - static Integer v669; - static Integer v670; - static Integer v671; - static Integer v672; - static Integer v673; - static Integer v674; - static Integer v675; - static Integer v676; - static Integer v677; - static Integer v678; - static Integer v679; - static Integer v680; - static Integer v681; - static Integer v682; - static Integer v683; - static Integer v684; - static Integer v685; - static Integer v686; - static Integer v687; - static Integer v688; - static Integer v689; - static Integer v690; - static Integer v691; - static Integer v692; - static Integer v693; - static Integer v694; - static Integer v695; - static Integer v696; - static Integer v697; - static Integer v698; - static Integer v699; - static Integer v700; - static Integer v701; - static Integer v702; - static Integer v703; - static Integer v704; - static Integer v705; - static Integer v706; - static Integer v707; - static Integer v708; - static Integer v709; - static Integer v710; - static Integer v711; - static Integer v712; - static Integer v713; - static Integer v714; - static Integer v715; - static Integer v716; - static Integer v717; - static Integer v718; - static Integer v719; - static Integer v720; - static Integer v721; - static Integer v722; - static Integer v723; - static Integer v724; - static Integer v725; - static Integer v726; - static Integer v727; - static Integer v728; - static Integer v729; - static Integer v730; - static Integer v731; - static Integer v732; - static Integer v733; - static Integer v734; - static Integer v735; - static Integer v736; - static Integer v737; - static Integer v738; - static Integer v739; - static Integer v740; - static Integer v741; - static Integer v742; - static Integer v743; - static Integer v744; - static Integer v745; - static Integer v746; - static Integer v747; - static Integer v748; - static Integer v749; - static Integer v750; - static Integer v751; - static Integer v752; - static Integer v753; - static Integer v754; - static Integer v755; - static Integer v756; - static Integer v757; - static Integer v758; - static Integer v759; - static Integer v760; - static Integer v761; - static Integer v762; - static Integer v763; - static Integer v764; - static Integer v765; - static Integer v766; - static Integer v767; - static Integer v768; - static Integer v769; - static Integer v770; - static Integer v771; - static Integer v772; - static Integer v773; - static Integer v774; - static Integer v775; - static Integer v776; - static Integer v777; - static Integer v778; - static Integer v779; - static Integer v780; - static Integer v781; - static Integer v782; - static Integer v783; - static Integer v784; - static Integer v785; - static Integer v786; - static Integer v787; - static Integer v788; - static Integer v789; - static Integer v790; - static Integer v791; - static Integer v792; - static Integer v793; - static Integer v794; - static Integer v795; - static Integer v796; - static Integer v797; - static Integer v798; - static Integer v799; - static Integer v800; - static Integer v801; - static Integer v802; - static Integer v803; - static Integer v804; - static Integer v805; - static Integer v806; - static Integer v807; - static Integer v808; - static Integer v809; - static Integer v810; - static Integer v811; - static Integer v812; - static Integer v813; - static Integer v814; - static Integer v815; - static Integer v816; - static Integer v817; - static Integer v818; - static Integer v819; - static Integer v820; - static Integer v821; - static Integer v822; - static Integer v823; - static Integer v824; - static Integer v825; - static Integer v826; - static Integer v827; - static Integer v828; - static Integer v829; - static Integer v830; - static Integer v831; - static Integer v832; - static Integer v833; - static Integer v834; - static Integer v835; - static Integer v836; - static Integer v837; - static Integer v838; - static Integer v839; - static Integer v840; - static Integer v841; - static Integer v842; - static Integer v843; - static Integer v844; - static Integer v845; - static Integer v846; - static Integer v847; - static Integer v848; - static Integer v849; - static Integer v850; - static Integer v851; - static Integer v852; - static Integer v853; - static Integer v854; - static Integer v855; - static Integer v856; - static Integer v857; - static Integer v858; - static Integer v859; - static Integer v860; - static Integer v861; - static Integer v862; - static Integer v863; - static Integer v864; - static Integer v865; - static Integer v866; - static Integer v867; - static Integer v868; - static Integer v869; - static Integer v870; - static Integer v871; - static Integer v872; - static Integer v873; - static Integer v874; - static Integer v875; - static Integer v876; - static Integer v877; - static Integer v878; - static Integer v879; - static Integer v880; - static Integer v881; - static Integer v882; - static Integer v883; - static Integer v884; - static Integer v885; - static Integer v886; - static Integer v887; - static Integer v888; - static Integer v889; - static Integer v890; - static Integer v891; - static Integer v892; - static Integer v893; - static Integer v894; - static Integer v895; - static Integer v896; - static Integer v897; - static Integer v898; - static Integer v899; - static Integer v900; - static Integer v901; - static Integer v902; - static Integer v903; - static Integer v904; - static Integer v905; - static Integer v906; - static Integer v907; - static Integer v908; - static Integer v909; - static Integer v910; - static Integer v911; - static Integer v912; - static Integer v913; - static Integer v914; - static Integer v915; - static Integer v916; - static Integer v917; - static Integer v918; - static Integer v919; - static Integer v920; - static Integer v921; - static Integer v922; - static Integer v923; - static Integer v924; - static Integer v925; - static Integer v926; - static Integer v927; - static Integer v928; - static Integer v929; - static Integer v930; - static Integer v931; - static Integer v932; - static Integer v933; - static Integer v934; - static Integer v935; - static Integer v936; - static Integer v937; - static Integer v938; - static Integer v939; - static Integer v940; - static Integer v941; - static Integer v942; - static Integer v943; - static Integer v944; - static Integer v945; - static Integer v946; - static Integer v947; - static Integer v948; - static Integer v949; - static Integer v950; - static Integer v951; - static Integer v952; - static Integer v953; - static Integer v954; - static Integer v955; - static Integer v956; - static Integer v957; - static Integer v958; - static Integer v959; - static Integer v960; - static Integer v961; - static Integer v962; - static Integer v963; - static Integer v964; - static Integer v965; - static Integer v966; - static Integer v967; - static Integer v968; - static Integer v969; - static Integer v970; - static Integer v971; - static Integer v972; - static Integer v973; - static Integer v974; - static Integer v975; - static Integer v976; - static Integer v977; - static Integer v978; - static Integer v979; - static Integer v980; - static Integer v981; - static Integer v982; - static Integer v983; - static Integer v984; - static Integer v985; - static Integer v986; - static Integer v987; - static Integer v988; - static Integer v989; - static Integer v990; - static Integer v991; - static Integer v992; - static Integer v993; - static Integer v994; - static Integer v995; - static Integer v996; - static Integer v997; - static Integer v998; - static Integer v999; + // Do not initialize these variables. The Nullness Checker is supposed to issue an error about + // uninitialized fields. + static Integer v0; + static Integer v1; + static Integer v2; + static Integer v3; + static Integer v4; + static Integer v5; + static Integer v6; + static Integer v7; + static Integer v8; + static Integer v9; + static Integer v10; + static Integer v11; + static Integer v12; + static Integer v13; + static Integer v14; + static Integer v15; + static Integer v16; + static Integer v17; + static Integer v18; + static Integer v19; + static Integer v20; + static Integer v21; + static Integer v22; + static Integer v23; + static Integer v24; + static Integer v25; + static Integer v26; + static Integer v27; + static Integer v28; + static Integer v29; + static Integer v30; + static Integer v31; + static Integer v32; + static Integer v33; + static Integer v34; + static Integer v35; + static Integer v36; + static Integer v37; + static Integer v38; + static Integer v39; + static Integer v40; + static Integer v41; + static Integer v42; + static Integer v43; + static Integer v44; + static Integer v45; + static Integer v46; + static Integer v47; + static Integer v48; + static Integer v49; + static Integer v50; + static Integer v51; + static Integer v52; + static Integer v53; + static Integer v54; + static Integer v55; + static Integer v56; + static Integer v57; + static Integer v58; + static Integer v59; + static Integer v60; + static Integer v61; + static Integer v62; + static Integer v63; + static Integer v64; + static Integer v65; + static Integer v66; + static Integer v67; + static Integer v68; + static Integer v69; + static Integer v70; + static Integer v71; + static Integer v72; + static Integer v73; + static Integer v74; + static Integer v75; + static Integer v76; + static Integer v77; + static Integer v78; + static Integer v79; + static Integer v80; + static Integer v81; + static Integer v82; + static Integer v83; + static Integer v84; + static Integer v85; + static Integer v86; + static Integer v87; + static Integer v88; + static Integer v89; + static Integer v90; + static Integer v91; + static Integer v92; + static Integer v93; + static Integer v94; + static Integer v95; + static Integer v96; + static Integer v97; + static Integer v98; + static Integer v99; + static Integer v100; + static Integer v101; + static Integer v102; + static Integer v103; + static Integer v104; + static Integer v105; + static Integer v106; + static Integer v107; + static Integer v108; + static Integer v109; + static Integer v110; + static Integer v111; + static Integer v112; + static Integer v113; + static Integer v114; + static Integer v115; + static Integer v116; + static Integer v117; + static Integer v118; + static Integer v119; + static Integer v120; + static Integer v121; + static Integer v122; + static Integer v123; + static Integer v124; + static Integer v125; + static Integer v126; + static Integer v127; + static Integer v128; + static Integer v129; + static Integer v130; + static Integer v131; + static Integer v132; + static Integer v133; + static Integer v134; + static Integer v135; + static Integer v136; + static Integer v137; + static Integer v138; + static Integer v139; + static Integer v140; + static Integer v141; + static Integer v142; + static Integer v143; + static Integer v144; + static Integer v145; + static Integer v146; + static Integer v147; + static Integer v148; + static Integer v149; + static Integer v150; + static Integer v151; + static Integer v152; + static Integer v153; + static Integer v154; + static Integer v155; + static Integer v156; + static Integer v157; + static Integer v158; + static Integer v159; + static Integer v160; + static Integer v161; + static Integer v162; + static Integer v163; + static Integer v164; + static Integer v165; + static Integer v166; + static Integer v167; + static Integer v168; + static Integer v169; + static Integer v170; + static Integer v171; + static Integer v172; + static Integer v173; + static Integer v174; + static Integer v175; + static Integer v176; + static Integer v177; + static Integer v178; + static Integer v179; + static Integer v180; + static Integer v181; + static Integer v182; + static Integer v183; + static Integer v184; + static Integer v185; + static Integer v186; + static Integer v187; + static Integer v188; + static Integer v189; + static Integer v190; + static Integer v191; + static Integer v192; + static Integer v193; + static Integer v194; + static Integer v195; + static Integer v196; + static Integer v197; + static Integer v198; + static Integer v199; + static Integer v200; + static Integer v201; + static Integer v202; + static Integer v203; + static Integer v204; + static Integer v205; + static Integer v206; + static Integer v207; + static Integer v208; + static Integer v209; + static Integer v210; + static Integer v211; + static Integer v212; + static Integer v213; + static Integer v214; + static Integer v215; + static Integer v216; + static Integer v217; + static Integer v218; + static Integer v219; + static Integer v220; + static Integer v221; + static Integer v222; + static Integer v223; + static Integer v224; + static Integer v225; + static Integer v226; + static Integer v227; + static Integer v228; + static Integer v229; + static Integer v230; + static Integer v231; + static Integer v232; + static Integer v233; + static Integer v234; + static Integer v235; + static Integer v236; + static Integer v237; + static Integer v238; + static Integer v239; + static Integer v240; + static Integer v241; + static Integer v242; + static Integer v243; + static Integer v244; + static Integer v245; + static Integer v246; + static Integer v247; + static Integer v248; + static Integer v249; + static Integer v250; + static Integer v251; + static Integer v252; + static Integer v253; + static Integer v254; + static Integer v255; + static Integer v256; + static Integer v257; + static Integer v258; + static Integer v259; + static Integer v260; + static Integer v261; + static Integer v262; + static Integer v263; + static Integer v264; + static Integer v265; + static Integer v266; + static Integer v267; + static Integer v268; + static Integer v269; + static Integer v270; + static Integer v271; + static Integer v272; + static Integer v273; + static Integer v274; + static Integer v275; + static Integer v276; + static Integer v277; + static Integer v278; + static Integer v279; + static Integer v280; + static Integer v281; + static Integer v282; + static Integer v283; + static Integer v284; + static Integer v285; + static Integer v286; + static Integer v287; + static Integer v288; + static Integer v289; + static Integer v290; + static Integer v291; + static Integer v292; + static Integer v293; + static Integer v294; + static Integer v295; + static Integer v296; + static Integer v297; + static Integer v298; + static Integer v299; + static Integer v300; + static Integer v301; + static Integer v302; + static Integer v303; + static Integer v304; + static Integer v305; + static Integer v306; + static Integer v307; + static Integer v308; + static Integer v309; + static Integer v310; + static Integer v311; + static Integer v312; + static Integer v313; + static Integer v314; + static Integer v315; + static Integer v316; + static Integer v317; + static Integer v318; + static Integer v319; + static Integer v320; + static Integer v321; + static Integer v322; + static Integer v323; + static Integer v324; + static Integer v325; + static Integer v326; + static Integer v327; + static Integer v328; + static Integer v329; + static Integer v330; + static Integer v331; + static Integer v332; + static Integer v333; + static Integer v334; + static Integer v335; + static Integer v336; + static Integer v337; + static Integer v338; + static Integer v339; + static Integer v340; + static Integer v341; + static Integer v342; + static Integer v343; + static Integer v344; + static Integer v345; + static Integer v346; + static Integer v347; + static Integer v348; + static Integer v349; + static Integer v350; + static Integer v351; + static Integer v352; + static Integer v353; + static Integer v354; + static Integer v355; + static Integer v356; + static Integer v357; + static Integer v358; + static Integer v359; + static Integer v360; + static Integer v361; + static Integer v362; + static Integer v363; + static Integer v364; + static Integer v365; + static Integer v366; + static Integer v367; + static Integer v368; + static Integer v369; + static Integer v370; + static Integer v371; + static Integer v372; + static Integer v373; + static Integer v374; + static Integer v375; + static Integer v376; + static Integer v377; + static Integer v378; + static Integer v379; + static Integer v380; + static Integer v381; + static Integer v382; + static Integer v383; + static Integer v384; + static Integer v385; + static Integer v386; + static Integer v387; + static Integer v388; + static Integer v389; + static Integer v390; + static Integer v391; + static Integer v392; + static Integer v393; + static Integer v394; + static Integer v395; + static Integer v396; + static Integer v397; + static Integer v398; + static Integer v399; + static Integer v400; + static Integer v401; + static Integer v402; + static Integer v403; + static Integer v404; + static Integer v405; + static Integer v406; + static Integer v407; + static Integer v408; + static Integer v409; + static Integer v410; + static Integer v411; + static Integer v412; + static Integer v413; + static Integer v414; + static Integer v415; + static Integer v416; + static Integer v417; + static Integer v418; + static Integer v419; + static Integer v420; + static Integer v421; + static Integer v422; + static Integer v423; + static Integer v424; + static Integer v425; + static Integer v426; + static Integer v427; + static Integer v428; + static Integer v429; + static Integer v430; + static Integer v431; + static Integer v432; + static Integer v433; + static Integer v434; + static Integer v435; + static Integer v436; + static Integer v437; + static Integer v438; + static Integer v439; + static Integer v440; + static Integer v441; + static Integer v442; + static Integer v443; + static Integer v444; + static Integer v445; + static Integer v446; + static Integer v447; + static Integer v448; + static Integer v449; + static Integer v450; + static Integer v451; + static Integer v452; + static Integer v453; + static Integer v454; + static Integer v455; + static Integer v456; + static Integer v457; + static Integer v458; + static Integer v459; + static Integer v460; + static Integer v461; + static Integer v462; + static Integer v463; + static Integer v464; + static Integer v465; + static Integer v466; + static Integer v467; + static Integer v468; + static Integer v469; + static Integer v470; + static Integer v471; + static Integer v472; + static Integer v473; + static Integer v474; + static Integer v475; + static Integer v476; + static Integer v477; + static Integer v478; + static Integer v479; + static Integer v480; + static Integer v481; + static Integer v482; + static Integer v483; + static Integer v484; + static Integer v485; + static Integer v486; + static Integer v487; + static Integer v488; + static Integer v489; + static Integer v490; + static Integer v491; + static Integer v492; + static Integer v493; + static Integer v494; + static Integer v495; + static Integer v496; + static Integer v497; + static Integer v498; + static Integer v499; + static Integer v500; + static Integer v501; + static Integer v502; + static Integer v503; + static Integer v504; + static Integer v505; + static Integer v506; + static Integer v507; + static Integer v508; + static Integer v509; + static Integer v510; + static Integer v511; + static Integer v512; + static Integer v513; + static Integer v514; + static Integer v515; + static Integer v516; + static Integer v517; + static Integer v518; + static Integer v519; + static Integer v520; + static Integer v521; + static Integer v522; + static Integer v523; + static Integer v524; + static Integer v525; + static Integer v526; + static Integer v527; + static Integer v528; + static Integer v529; + static Integer v530; + static Integer v531; + static Integer v532; + static Integer v533; + static Integer v534; + static Integer v535; + static Integer v536; + static Integer v537; + static Integer v538; + static Integer v539; + static Integer v540; + static Integer v541; + static Integer v542; + static Integer v543; + static Integer v544; + static Integer v545; + static Integer v546; + static Integer v547; + static Integer v548; + static Integer v549; + static Integer v550; + static Integer v551; + static Integer v552; + static Integer v553; + static Integer v554; + static Integer v555; + static Integer v556; + static Integer v557; + static Integer v558; + static Integer v559; + static Integer v560; + static Integer v561; + static Integer v562; + static Integer v563; + static Integer v564; + static Integer v565; + static Integer v566; + static Integer v567; + static Integer v568; + static Integer v569; + static Integer v570; + static Integer v571; + static Integer v572; + static Integer v573; + static Integer v574; + static Integer v575; + static Integer v576; + static Integer v577; + static Integer v578; + static Integer v579; + static Integer v580; + static Integer v581; + static Integer v582; + static Integer v583; + static Integer v584; + static Integer v585; + static Integer v586; + static Integer v587; + static Integer v588; + static Integer v589; + static Integer v590; + static Integer v591; + static Integer v592; + static Integer v593; + static Integer v594; + static Integer v595; + static Integer v596; + static Integer v597; + static Integer v598; + static Integer v599; + static Integer v600; + static Integer v601; + static Integer v602; + static Integer v603; + static Integer v604; + static Integer v605; + static Integer v606; + static Integer v607; + static Integer v608; + static Integer v609; + static Integer v610; + static Integer v611; + static Integer v612; + static Integer v613; + static Integer v614; + static Integer v615; + static Integer v616; + static Integer v617; + static Integer v618; + static Integer v619; + static Integer v620; + static Integer v621; + static Integer v622; + static Integer v623; + static Integer v624; + static Integer v625; + static Integer v626; + static Integer v627; + static Integer v628; + static Integer v629; + static Integer v630; + static Integer v631; + static Integer v632; + static Integer v633; + static Integer v634; + static Integer v635; + static Integer v636; + static Integer v637; + static Integer v638; + static Integer v639; + static Integer v640; + static Integer v641; + static Integer v642; + static Integer v643; + static Integer v644; + static Integer v645; + static Integer v646; + static Integer v647; + static Integer v648; + static Integer v649; + static Integer v650; + static Integer v651; + static Integer v652; + static Integer v653; + static Integer v654; + static Integer v655; + static Integer v656; + static Integer v657; + static Integer v658; + static Integer v659; + static Integer v660; + static Integer v661; + static Integer v662; + static Integer v663; + static Integer v664; + static Integer v665; + static Integer v666; + static Integer v667; + static Integer v668; + static Integer v669; + static Integer v670; + static Integer v671; + static Integer v672; + static Integer v673; + static Integer v674; + static Integer v675; + static Integer v676; + static Integer v677; + static Integer v678; + static Integer v679; + static Integer v680; + static Integer v681; + static Integer v682; + static Integer v683; + static Integer v684; + static Integer v685; + static Integer v686; + static Integer v687; + static Integer v688; + static Integer v689; + static Integer v690; + static Integer v691; + static Integer v692; + static Integer v693; + static Integer v694; + static Integer v695; + static Integer v696; + static Integer v697; + static Integer v698; + static Integer v699; + static Integer v700; + static Integer v701; + static Integer v702; + static Integer v703; + static Integer v704; + static Integer v705; + static Integer v706; + static Integer v707; + static Integer v708; + static Integer v709; + static Integer v710; + static Integer v711; + static Integer v712; + static Integer v713; + static Integer v714; + static Integer v715; + static Integer v716; + static Integer v717; + static Integer v718; + static Integer v719; + static Integer v720; + static Integer v721; + static Integer v722; + static Integer v723; + static Integer v724; + static Integer v725; + static Integer v726; + static Integer v727; + static Integer v728; + static Integer v729; + static Integer v730; + static Integer v731; + static Integer v732; + static Integer v733; + static Integer v734; + static Integer v735; + static Integer v736; + static Integer v737; + static Integer v738; + static Integer v739; + static Integer v740; + static Integer v741; + static Integer v742; + static Integer v743; + static Integer v744; + static Integer v745; + static Integer v746; + static Integer v747; + static Integer v748; + static Integer v749; + static Integer v750; + static Integer v751; + static Integer v752; + static Integer v753; + static Integer v754; + static Integer v755; + static Integer v756; + static Integer v757; + static Integer v758; + static Integer v759; + static Integer v760; + static Integer v761; + static Integer v762; + static Integer v763; + static Integer v764; + static Integer v765; + static Integer v766; + static Integer v767; + static Integer v768; + static Integer v769; + static Integer v770; + static Integer v771; + static Integer v772; + static Integer v773; + static Integer v774; + static Integer v775; + static Integer v776; + static Integer v777; + static Integer v778; + static Integer v779; + static Integer v780; + static Integer v781; + static Integer v782; + static Integer v783; + static Integer v784; + static Integer v785; + static Integer v786; + static Integer v787; + static Integer v788; + static Integer v789; + static Integer v790; + static Integer v791; + static Integer v792; + static Integer v793; + static Integer v794; + static Integer v795; + static Integer v796; + static Integer v797; + static Integer v798; + static Integer v799; + static Integer v800; + static Integer v801; + static Integer v802; + static Integer v803; + static Integer v804; + static Integer v805; + static Integer v806; + static Integer v807; + static Integer v808; + static Integer v809; + static Integer v810; + static Integer v811; + static Integer v812; + static Integer v813; + static Integer v814; + static Integer v815; + static Integer v816; + static Integer v817; + static Integer v818; + static Integer v819; + static Integer v820; + static Integer v821; + static Integer v822; + static Integer v823; + static Integer v824; + static Integer v825; + static Integer v826; + static Integer v827; + static Integer v828; + static Integer v829; + static Integer v830; + static Integer v831; + static Integer v832; + static Integer v833; + static Integer v834; + static Integer v835; + static Integer v836; + static Integer v837; + static Integer v838; + static Integer v839; + static Integer v840; + static Integer v841; + static Integer v842; + static Integer v843; + static Integer v844; + static Integer v845; + static Integer v846; + static Integer v847; + static Integer v848; + static Integer v849; + static Integer v850; + static Integer v851; + static Integer v852; + static Integer v853; + static Integer v854; + static Integer v855; + static Integer v856; + static Integer v857; + static Integer v858; + static Integer v859; + static Integer v860; + static Integer v861; + static Integer v862; + static Integer v863; + static Integer v864; + static Integer v865; + static Integer v866; + static Integer v867; + static Integer v868; + static Integer v869; + static Integer v870; + static Integer v871; + static Integer v872; + static Integer v873; + static Integer v874; + static Integer v875; + static Integer v876; + static Integer v877; + static Integer v878; + static Integer v879; + static Integer v880; + static Integer v881; + static Integer v882; + static Integer v883; + static Integer v884; + static Integer v885; + static Integer v886; + static Integer v887; + static Integer v888; + static Integer v889; + static Integer v890; + static Integer v891; + static Integer v892; + static Integer v893; + static Integer v894; + static Integer v895; + static Integer v896; + static Integer v897; + static Integer v898; + static Integer v899; + static Integer v900; + static Integer v901; + static Integer v902; + static Integer v903; + static Integer v904; + static Integer v905; + static Integer v906; + static Integer v907; + static Integer v908; + static Integer v909; + static Integer v910; + static Integer v911; + static Integer v912; + static Integer v913; + static Integer v914; + static Integer v915; + static Integer v916; + static Integer v917; + static Integer v918; + static Integer v919; + static Integer v920; + static Integer v921; + static Integer v922; + static Integer v923; + static Integer v924; + static Integer v925; + static Integer v926; + static Integer v927; + static Integer v928; + static Integer v929; + static Integer v930; + static Integer v931; + static Integer v932; + static Integer v933; + static Integer v934; + static Integer v935; + static Integer v936; + static Integer v937; + static Integer v938; + static Integer v939; + static Integer v940; + static Integer v941; + static Integer v942; + static Integer v943; + static Integer v944; + static Integer v945; + static Integer v946; + static Integer v947; + static Integer v948; + static Integer v949; + static Integer v950; + static Integer v951; + static Integer v952; + static Integer v953; + static Integer v954; + static Integer v955; + static Integer v956; + static Integer v957; + static Integer v958; + static Integer v959; + static Integer v960; + static Integer v961; + static Integer v962; + static Integer v963; + static Integer v964; + static Integer v965; + static Integer v966; + static Integer v967; + static Integer v968; + static Integer v969; + static Integer v970; + static Integer v971; + static Integer v972; + static Integer v973; + static Integer v974; + static Integer v975; + static Integer v976; + static Integer v977; + static Integer v978; + static Integer v979; + static Integer v980; + static Integer v981; + static Integer v982; + static Integer v983; + static Integer v984; + static Integer v985; + static Integer v986; + static Integer v987; + static Integer v988; + static Integer v989; + static Integer v990; + static Integer v991; + static Integer v992; + static Integer v993; + static Integer v994; + static Integer v995; + static Integer v996; + static Integer v997; + static Integer v998; + static Integer v999; - static Map f() { - Map map = new HashMap<>(); - map.put("k0", v0); - map.put("k1", v1); - map.put("k2", v2); - map.put("k3", v3); - map.put("k4", v4); - map.put("k5", v5); - map.put("k6", v6); - map.put("k7", v7); - map.put("k8", v8); - map.put("k9", v9); - map.put("k10", v10); - map.put("k11", v11); - map.put("k12", v12); - map.put("k13", v13); - map.put("k14", v14); - map.put("k15", v15); - map.put("k16", v16); - map.put("k17", v17); - map.put("k18", v18); - map.put("k19", v19); - map.put("k20", v20); - map.put("k21", v21); - map.put("k22", v22); - map.put("k23", v23); - map.put("k24", v24); - map.put("k25", v25); - map.put("k26", v26); - map.put("k27", v27); - map.put("k28", v28); - map.put("k29", v29); - map.put("k30", v30); - map.put("k31", v31); - map.put("k32", v32); - map.put("k33", v33); - map.put("k34", v34); - map.put("k35", v35); - map.put("k36", v36); - map.put("k37", v37); - map.put("k38", v38); - map.put("k39", v39); - map.put("k40", v40); - map.put("k41", v41); - map.put("k42", v42); - map.put("k43", v43); - map.put("k44", v44); - map.put("k45", v45); - map.put("k46", v46); - map.put("k47", v47); - map.put("k48", v48); - map.put("k49", v49); - map.put("k50", v50); - map.put("k51", v51); - map.put("k52", v52); - map.put("k53", v53); - map.put("k54", v54); - map.put("k55", v55); - map.put("k56", v56); - map.put("k57", v57); - map.put("k58", v58); - map.put("k59", v59); - map.put("k60", v60); - map.put("k61", v61); - map.put("k62", v62); - map.put("k63", v63); - map.put("k64", v64); - map.put("k65", v65); - map.put("k66", v66); - map.put("k67", v67); - map.put("k68", v68); - map.put("k69", v69); - map.put("k70", v70); - map.put("k71", v71); - map.put("k72", v72); - map.put("k73", v73); - map.put("k74", v74); - map.put("k75", v75); - map.put("k76", v76); - map.put("k77", v77); - map.put("k78", v78); - map.put("k79", v79); - map.put("k80", v80); - map.put("k81", v81); - map.put("k82", v82); - map.put("k83", v83); - map.put("k84", v84); - map.put("k85", v85); - map.put("k86", v86); - map.put("k87", v87); - map.put("k88", v88); - map.put("k89", v89); - map.put("k90", v90); - map.put("k91", v91); - map.put("k92", v92); - map.put("k93", v93); - map.put("k94", v94); - map.put("k95", v95); - map.put("k96", v96); - map.put("k97", v97); - map.put("k98", v98); - map.put("k99", v99); - map.put("k100", v100); - map.put("k101", v101); - map.put("k102", v102); - map.put("k103", v103); - map.put("k104", v104); - map.put("k105", v105); - map.put("k106", v106); - map.put("k107", v107); - map.put("k108", v108); - map.put("k109", v109); - map.put("k110", v110); - map.put("k111", v111); - map.put("k112", v112); - map.put("k113", v113); - map.put("k114", v114); - map.put("k115", v115); - map.put("k116", v116); - map.put("k117", v117); - map.put("k118", v118); - map.put("k119", v119); - map.put("k120", v120); - map.put("k121", v121); - map.put("k122", v122); - map.put("k123", v123); - map.put("k124", v124); - map.put("k125", v125); - map.put("k126", v126); - map.put("k127", v127); - map.put("k128", v128); - map.put("k129", v129); - map.put("k130", v130); - map.put("k131", v131); - map.put("k132", v132); - map.put("k133", v133); - map.put("k134", v134); - map.put("k135", v135); - map.put("k136", v136); - map.put("k137", v137); - map.put("k138", v138); - map.put("k139", v139); - map.put("k140", v140); - map.put("k141", v141); - map.put("k142", v142); - map.put("k143", v143); - map.put("k144", v144); - map.put("k145", v145); - map.put("k146", v146); - map.put("k147", v147); - map.put("k148", v148); - map.put("k149", v149); - map.put("k150", v150); - map.put("k151", v151); - map.put("k152", v152); - map.put("k153", v153); - map.put("k154", v154); - map.put("k155", v155); - map.put("k156", v156); - map.put("k157", v157); - map.put("k158", v158); - map.put("k159", v159); - map.put("k160", v160); - map.put("k161", v161); - map.put("k162", v162); - map.put("k163", v163); - map.put("k164", v164); - map.put("k165", v165); - map.put("k166", v166); - map.put("k167", v167); - map.put("k168", v168); - map.put("k169", v169); - map.put("k170", v170); - map.put("k171", v171); - map.put("k172", v172); - map.put("k173", v173); - map.put("k174", v174); - map.put("k175", v175); - map.put("k176", v176); - map.put("k177", v177); - map.put("k178", v178); - map.put("k179", v179); - map.put("k180", v180); - map.put("k181", v181); - map.put("k182", v182); - map.put("k183", v183); - map.put("k184", v184); - map.put("k185", v185); - map.put("k186", v186); - map.put("k187", v187); - map.put("k188", v188); - map.put("k189", v189); - map.put("k190", v190); - map.put("k191", v191); - map.put("k192", v192); - map.put("k193", v193); - map.put("k194", v194); - map.put("k195", v195); - map.put("k196", v196); - map.put("k197", v197); - map.put("k198", v198); - map.put("k199", v199); - map.put("k200", v200); - map.put("k201", v201); - map.put("k202", v202); - map.put("k203", v203); - map.put("k204", v204); - map.put("k205", v205); - map.put("k206", v206); - map.put("k207", v207); - map.put("k208", v208); - map.put("k209", v209); - map.put("k210", v210); - map.put("k211", v211); - map.put("k212", v212); - map.put("k213", v213); - map.put("k214", v214); - map.put("k215", v215); - map.put("k216", v216); - map.put("k217", v217); - map.put("k218", v218); - map.put("k219", v219); - map.put("k220", v220); - map.put("k221", v221); - map.put("k222", v222); - map.put("k223", v223); - map.put("k224", v224); - map.put("k225", v225); - map.put("k226", v226); - map.put("k227", v227); - map.put("k228", v228); - map.put("k229", v229); - map.put("k230", v230); - map.put("k231", v231); - map.put("k232", v232); - map.put("k233", v233); - map.put("k234", v234); - map.put("k235", v235); - map.put("k236", v236); - map.put("k237", v237); - map.put("k238", v238); - map.put("k239", v239); - map.put("k240", v240); - map.put("k241", v241); - map.put("k242", v242); - map.put("k243", v243); - map.put("k244", v244); - map.put("k245", v245); - map.put("k246", v246); - map.put("k247", v247); - map.put("k248", v248); - map.put("k249", v249); - map.put("k250", v250); - map.put("k251", v251); - map.put("k252", v252); - map.put("k253", v253); - map.put("k254", v254); - map.put("k255", v255); - map.put("k256", v256); - map.put("k257", v257); - map.put("k258", v258); - map.put("k259", v259); - map.put("k260", v260); - map.put("k261", v261); - map.put("k262", v262); - map.put("k263", v263); - map.put("k264", v264); - map.put("k265", v265); - map.put("k266", v266); - map.put("k267", v267); - map.put("k268", v268); - map.put("k269", v269); - map.put("k270", v270); - map.put("k271", v271); - map.put("k272", v272); - map.put("k273", v273); - map.put("k274", v274); - map.put("k275", v275); - map.put("k276", v276); - map.put("k277", v277); - map.put("k278", v278); - map.put("k279", v279); - map.put("k280", v280); - map.put("k281", v281); - map.put("k282", v282); - map.put("k283", v283); - map.put("k284", v284); - map.put("k285", v285); - map.put("k286", v286); - map.put("k287", v287); - map.put("k288", v288); - map.put("k289", v289); - map.put("k290", v290); - map.put("k291", v291); - map.put("k292", v292); - map.put("k293", v293); - map.put("k294", v294); - map.put("k295", v295); - map.put("k296", v296); - map.put("k297", v297); - map.put("k298", v298); - map.put("k299", v299); - map.put("k300", v300); - map.put("k301", v301); - map.put("k302", v302); - map.put("k303", v303); - map.put("k304", v304); - map.put("k305", v305); - map.put("k306", v306); - map.put("k307", v307); - map.put("k308", v308); - map.put("k309", v309); - map.put("k310", v310); - map.put("k311", v311); - map.put("k312", v312); - map.put("k313", v313); - map.put("k314", v314); - map.put("k315", v315); - map.put("k316", v316); - map.put("k317", v317); - map.put("k318", v318); - map.put("k319", v319); - map.put("k320", v320); - map.put("k321", v321); - map.put("k322", v322); - map.put("k323", v323); - map.put("k324", v324); - map.put("k325", v325); - map.put("k326", v326); - map.put("k327", v327); - map.put("k328", v328); - map.put("k329", v329); - map.put("k330", v330); - map.put("k331", v331); - map.put("k332", v332); - map.put("k333", v333); - map.put("k334", v334); - map.put("k335", v335); - map.put("k336", v336); - map.put("k337", v337); - map.put("k338", v338); - map.put("k339", v339); - map.put("k340", v340); - map.put("k341", v341); - map.put("k342", v342); - map.put("k343", v343); - map.put("k344", v344); - map.put("k345", v345); - map.put("k346", v346); - map.put("k347", v347); - map.put("k348", v348); - map.put("k349", v349); - map.put("k350", v350); - map.put("k351", v351); - map.put("k352", v352); - map.put("k353", v353); - map.put("k354", v354); - map.put("k355", v355); - map.put("k356", v356); - map.put("k357", v357); - map.put("k358", v358); - map.put("k359", v359); - map.put("k360", v360); - map.put("k361", v361); - map.put("k362", v362); - map.put("k363", v363); - map.put("k364", v364); - map.put("k365", v365); - map.put("k366", v366); - map.put("k367", v367); - map.put("k368", v368); - map.put("k369", v369); - map.put("k370", v370); - map.put("k371", v371); - map.put("k372", v372); - map.put("k373", v373); - map.put("k374", v374); - map.put("k375", v375); - map.put("k376", v376); - map.put("k377", v377); - map.put("k378", v378); - map.put("k379", v379); - map.put("k380", v380); - map.put("k381", v381); - map.put("k382", v382); - map.put("k383", v383); - map.put("k384", v384); - map.put("k385", v385); - map.put("k386", v386); - map.put("k387", v387); - map.put("k388", v388); - map.put("k389", v389); - map.put("k390", v390); - map.put("k391", v391); - map.put("k392", v392); - map.put("k393", v393); - map.put("k394", v394); - map.put("k395", v395); - map.put("k396", v396); - map.put("k397", v397); - map.put("k398", v398); - map.put("k399", v399); - map.put("k400", v400); - map.put("k401", v401); - map.put("k402", v402); - map.put("k403", v403); - map.put("k404", v404); - map.put("k405", v405); - map.put("k406", v406); - map.put("k407", v407); - map.put("k408", v408); - map.put("k409", v409); - map.put("k410", v410); - map.put("k411", v411); - map.put("k412", v412); - map.put("k413", v413); - map.put("k414", v414); - map.put("k415", v415); - map.put("k416", v416); - map.put("k417", v417); - map.put("k418", v418); - map.put("k419", v419); - map.put("k420", v420); - map.put("k421", v421); - map.put("k422", v422); - map.put("k423", v423); - map.put("k424", v424); - map.put("k425", v425); - map.put("k426", v426); - map.put("k427", v427); - map.put("k428", v428); - map.put("k429", v429); - map.put("k430", v430); - map.put("k431", v431); - map.put("k432", v432); - map.put("k433", v433); - map.put("k434", v434); - map.put("k435", v435); - map.put("k436", v436); - map.put("k437", v437); - map.put("k438", v438); - map.put("k439", v439); - map.put("k440", v440); - map.put("k441", v441); - map.put("k442", v442); - map.put("k443", v443); - map.put("k444", v444); - map.put("k445", v445); - map.put("k446", v446); - map.put("k447", v447); - map.put("k448", v448); - map.put("k449", v449); - map.put("k450", v450); - map.put("k451", v451); - map.put("k452", v452); - map.put("k453", v453); - map.put("k454", v454); - map.put("k455", v455); - map.put("k456", v456); - map.put("k457", v457); - map.put("k458", v458); - map.put("k459", v459); - map.put("k460", v460); - map.put("k461", v461); - map.put("k462", v462); - map.put("k463", v463); - map.put("k464", v464); - map.put("k465", v465); - map.put("k466", v466); - map.put("k467", v467); - map.put("k468", v468); - map.put("k469", v469); - map.put("k470", v470); - map.put("k471", v471); - map.put("k472", v472); - map.put("k473", v473); - map.put("k474", v474); - map.put("k475", v475); - map.put("k476", v476); - map.put("k477", v477); - map.put("k478", v478); - map.put("k479", v479); - map.put("k480", v480); - map.put("k481", v481); - map.put("k482", v482); - map.put("k483", v483); - map.put("k484", v484); - map.put("k485", v485); - map.put("k486", v486); - map.put("k487", v487); - map.put("k488", v488); - map.put("k489", v489); - map.put("k490", v490); - map.put("k491", v491); - map.put("k492", v492); - map.put("k493", v493); - map.put("k494", v494); - map.put("k495", v495); - map.put("k496", v496); - map.put("k497", v497); - map.put("k498", v498); - map.put("k499", v499); - map.put("k500", v500); - map.put("k501", v501); - map.put("k502", v502); - map.put("k503", v503); - map.put("k504", v504); - map.put("k505", v505); - map.put("k506", v506); - map.put("k507", v507); - map.put("k508", v508); - map.put("k509", v509); - map.put("k510", v510); - map.put("k511", v511); - map.put("k512", v512); - map.put("k513", v513); - map.put("k514", v514); - map.put("k515", v515); - map.put("k516", v516); - map.put("k517", v517); - map.put("k518", v518); - map.put("k519", v519); - map.put("k520", v520); - map.put("k521", v521); - map.put("k522", v522); - map.put("k523", v523); - map.put("k524", v524); - map.put("k525", v525); - map.put("k526", v526); - map.put("k527", v527); - map.put("k528", v528); - map.put("k529", v529); - map.put("k530", v530); - map.put("k531", v531); - map.put("k532", v532); - map.put("k533", v533); - map.put("k534", v534); - map.put("k535", v535); - map.put("k536", v536); - map.put("k537", v537); - map.put("k538", v538); - map.put("k539", v539); - map.put("k540", v540); - map.put("k541", v541); - map.put("k542", v542); - map.put("k543", v543); - map.put("k544", v544); - map.put("k545", v545); - map.put("k546", v546); - map.put("k547", v547); - map.put("k548", v548); - map.put("k549", v549); - map.put("k550", v550); - map.put("k551", v551); - map.put("k552", v552); - map.put("k553", v553); - map.put("k554", v554); - map.put("k555", v555); - map.put("k556", v556); - map.put("k557", v557); - map.put("k558", v558); - map.put("k559", v559); - map.put("k560", v560); - map.put("k561", v561); - map.put("k562", v562); - map.put("k563", v563); - map.put("k564", v564); - map.put("k565", v565); - map.put("k566", v566); - map.put("k567", v567); - map.put("k568", v568); - map.put("k569", v569); - map.put("k570", v570); - map.put("k571", v571); - map.put("k572", v572); - map.put("k573", v573); - map.put("k574", v574); - map.put("k575", v575); - map.put("k576", v576); - map.put("k577", v577); - map.put("k578", v578); - map.put("k579", v579); - map.put("k580", v580); - map.put("k581", v581); - map.put("k582", v582); - map.put("k583", v583); - map.put("k584", v584); - map.put("k585", v585); - map.put("k586", v586); - map.put("k587", v587); - map.put("k588", v588); - map.put("k589", v589); - map.put("k590", v590); - map.put("k591", v591); - map.put("k592", v592); - map.put("k593", v593); - map.put("k594", v594); - map.put("k595", v595); - map.put("k596", v596); - map.put("k597", v597); - map.put("k598", v598); - map.put("k599", v599); - map.put("k600", v600); - map.put("k601", v601); - map.put("k602", v602); - map.put("k603", v603); - map.put("k604", v604); - map.put("k605", v605); - map.put("k606", v606); - map.put("k607", v607); - map.put("k608", v608); - map.put("k609", v609); - map.put("k610", v610); - map.put("k611", v611); - map.put("k612", v612); - map.put("k613", v613); - map.put("k614", v614); - map.put("k615", v615); - map.put("k616", v616); - map.put("k617", v617); - map.put("k618", v618); - map.put("k619", v619); - map.put("k620", v620); - map.put("k621", v621); - map.put("k622", v622); - map.put("k623", v623); - map.put("k624", v624); - map.put("k625", v625); - map.put("k626", v626); - map.put("k627", v627); - map.put("k628", v628); - map.put("k629", v629); - map.put("k630", v630); - map.put("k631", v631); - map.put("k632", v632); - map.put("k633", v633); - map.put("k634", v634); - map.put("k635", v635); - map.put("k636", v636); - map.put("k637", v637); - map.put("k638", v638); - map.put("k639", v639); - map.put("k640", v640); - map.put("k641", v641); - map.put("k642", v642); - map.put("k643", v643); - map.put("k644", v644); - map.put("k645", v645); - map.put("k646", v646); - map.put("k647", v647); - map.put("k648", v648); - map.put("k649", v649); - map.put("k650", v650); - map.put("k651", v651); - map.put("k652", v652); - map.put("k653", v653); - map.put("k654", v654); - map.put("k655", v655); - map.put("k656", v656); - map.put("k657", v657); - map.put("k658", v658); - map.put("k659", v659); - map.put("k660", v660); - map.put("k661", v661); - map.put("k662", v662); - map.put("k663", v663); - map.put("k664", v664); - map.put("k665", v665); - map.put("k666", v666); - map.put("k667", v667); - map.put("k668", v668); - map.put("k669", v669); - map.put("k670", v670); - map.put("k671", v671); - map.put("k672", v672); - map.put("k673", v673); - map.put("k674", v674); - map.put("k675", v675); - map.put("k676", v676); - map.put("k677", v677); - map.put("k678", v678); - map.put("k679", v679); - map.put("k680", v680); - map.put("k681", v681); - map.put("k682", v682); - map.put("k683", v683); - map.put("k684", v684); - map.put("k685", v685); - map.put("k686", v686); - map.put("k687", v687); - map.put("k688", v688); - map.put("k689", v689); - map.put("k690", v690); - map.put("k691", v691); - map.put("k692", v692); - map.put("k693", v693); - map.put("k694", v694); - map.put("k695", v695); - map.put("k696", v696); - map.put("k697", v697); - map.put("k698", v698); - map.put("k699", v699); - map.put("k700", v700); - map.put("k701", v701); - map.put("k702", v702); - map.put("k703", v703); - map.put("k704", v704); - map.put("k705", v705); - map.put("k706", v706); - map.put("k707", v707); - map.put("k708", v708); - map.put("k709", v709); - map.put("k710", v710); - map.put("k711", v711); - map.put("k712", v712); - map.put("k713", v713); - map.put("k714", v714); - map.put("k715", v715); - map.put("k716", v716); - map.put("k717", v717); - map.put("k718", v718); - map.put("k719", v719); - map.put("k720", v720); - map.put("k721", v721); - map.put("k722", v722); - map.put("k723", v723); - map.put("k724", v724); - map.put("k725", v725); - map.put("k726", v726); - map.put("k727", v727); - map.put("k728", v728); - map.put("k729", v729); - map.put("k730", v730); - map.put("k731", v731); - map.put("k732", v732); - map.put("k733", v733); - map.put("k734", v734); - map.put("k735", v735); - map.put("k736", v736); - map.put("k737", v737); - map.put("k738", v738); - map.put("k739", v739); - map.put("k740", v740); - map.put("k741", v741); - map.put("k742", v742); - map.put("k743", v743); - map.put("k744", v744); - map.put("k745", v745); - map.put("k746", v746); - map.put("k747", v747); - map.put("k748", v748); - map.put("k749", v749); - map.put("k750", v750); - map.put("k751", v751); - map.put("k752", v752); - map.put("k753", v753); - map.put("k754", v754); - map.put("k755", v755); - map.put("k756", v756); - map.put("k757", v757); - map.put("k758", v758); - map.put("k759", v759); - map.put("k760", v760); - map.put("k761", v761); - map.put("k762", v762); - map.put("k763", v763); - map.put("k764", v764); - map.put("k765", v765); - map.put("k766", v766); - map.put("k767", v767); - map.put("k768", v768); - map.put("k769", v769); - map.put("k770", v770); - map.put("k771", v771); - map.put("k772", v772); - map.put("k773", v773); - map.put("k774", v774); - map.put("k775", v775); - map.put("k776", v776); - map.put("k777", v777); - map.put("k778", v778); - map.put("k779", v779); - map.put("k780", v780); - map.put("k781", v781); - map.put("k782", v782); - map.put("k783", v783); - map.put("k784", v784); - map.put("k785", v785); - map.put("k786", v786); - map.put("k787", v787); - map.put("k788", v788); - map.put("k789", v789); - map.put("k790", v790); - map.put("k791", v791); - map.put("k792", v792); - map.put("k793", v793); - map.put("k794", v794); - map.put("k795", v795); - map.put("k796", v796); - map.put("k797", v797); - map.put("k798", v798); - map.put("k799", v799); - map.put("k800", v800); - map.put("k801", v801); - map.put("k802", v802); - map.put("k803", v803); - map.put("k804", v804); - map.put("k805", v805); - map.put("k806", v806); - map.put("k807", v807); - map.put("k808", v808); - map.put("k809", v809); - map.put("k810", v810); - map.put("k811", v811); - map.put("k812", v812); - map.put("k813", v813); - map.put("k814", v814); - map.put("k815", v815); - map.put("k816", v816); - map.put("k817", v817); - map.put("k818", v818); - map.put("k819", v819); - map.put("k820", v820); - map.put("k821", v821); - map.put("k822", v822); - map.put("k823", v823); - map.put("k824", v824); - map.put("k825", v825); - map.put("k826", v826); - map.put("k827", v827); - map.put("k828", v828); - map.put("k829", v829); - map.put("k830", v830); - map.put("k831", v831); - map.put("k832", v832); - map.put("k833", v833); - map.put("k834", v834); - map.put("k835", v835); - map.put("k836", v836); - map.put("k837", v837); - map.put("k838", v838); - map.put("k839", v839); - map.put("k840", v840); - map.put("k841", v841); - map.put("k842", v842); - map.put("k843", v843); - map.put("k844", v844); - map.put("k845", v845); - map.put("k846", v846); - map.put("k847", v847); - map.put("k848", v848); - map.put("k849", v849); - map.put("k850", v850); - map.put("k851", v851); - map.put("k852", v852); - map.put("k853", v853); - map.put("k854", v854); - map.put("k855", v855); - map.put("k856", v856); - map.put("k857", v857); - map.put("k858", v858); - map.put("k859", v859); - map.put("k860", v860); - map.put("k861", v861); - map.put("k862", v862); - map.put("k863", v863); - map.put("k864", v864); - map.put("k865", v865); - map.put("k866", v866); - map.put("k867", v867); - map.put("k868", v868); - map.put("k869", v869); - map.put("k870", v870); - map.put("k871", v871); - map.put("k872", v872); - map.put("k873", v873); - map.put("k874", v874); - map.put("k875", v875); - map.put("k876", v876); - map.put("k877", v877); - map.put("k878", v878); - map.put("k879", v879); - map.put("k880", v880); - map.put("k881", v881); - map.put("k882", v882); - map.put("k883", v883); - map.put("k884", v884); - map.put("k885", v885); - map.put("k886", v886); - map.put("k887", v887); - map.put("k888", v888); - map.put("k889", v889); - map.put("k890", v890); - map.put("k891", v891); - map.put("k892", v892); - map.put("k893", v893); - map.put("k894", v894); - map.put("k895", v895); - map.put("k896", v896); - map.put("k897", v897); - map.put("k898", v898); - map.put("k899", v899); - map.put("k900", v900); - map.put("k901", v901); - map.put("k902", v902); - map.put("k903", v903); - map.put("k904", v904); - map.put("k905", v905); - map.put("k906", v906); - map.put("k907", v907); - map.put("k908", v908); - map.put("k909", v909); - map.put("k910", v910); - map.put("k911", v911); - map.put("k912", v912); - map.put("k913", v913); - map.put("k914", v914); - map.put("k915", v915); - map.put("k916", v916); - map.put("k917", v917); - map.put("k918", v918); - map.put("k919", v919); - map.put("k920", v920); - map.put("k921", v921); - map.put("k922", v922); - map.put("k923", v923); - map.put("k924", v924); - map.put("k925", v925); - map.put("k926", v926); - map.put("k927", v927); - map.put("k928", v928); - map.put("k929", v929); - map.put("k930", v930); - map.put("k931", v931); - map.put("k932", v932); - map.put("k933", v933); - map.put("k934", v934); - map.put("k935", v935); - map.put("k936", v936); - map.put("k937", v937); - map.put("k938", v938); - map.put("k939", v939); - map.put("k940", v940); - map.put("k941", v941); - map.put("k942", v942); - map.put("k943", v943); - map.put("k944", v944); - map.put("k945", v945); - map.put("k946", v946); - map.put("k947", v947); - map.put("k948", v948); - map.put("k949", v949); - map.put("k950", v950); - map.put("k951", v951); - map.put("k952", v952); - map.put("k953", v953); - map.put("k954", v954); - map.put("k955", v955); - map.put("k956", v956); - map.put("k957", v957); - map.put("k958", v958); - map.put("k959", v959); - map.put("k960", v960); - map.put("k961", v961); - map.put("k962", v962); - map.put("k963", v963); - map.put("k964", v964); - map.put("k965", v965); - map.put("k966", v966); - map.put("k967", v967); - map.put("k968", v968); - map.put("k969", v969); - map.put("k970", v970); - map.put("k971", v971); - map.put("k972", v972); - map.put("k973", v973); - map.put("k974", v974); - map.put("k975", v975); - map.put("k976", v976); - map.put("k977", v977); - map.put("k978", v978); - map.put("k979", v979); - map.put("k980", v980); - map.put("k981", v981); - map.put("k982", v982); - map.put("k983", v983); - map.put("k984", v984); - map.put("k985", v985); - map.put("k986", v986); - map.put("k987", v987); - map.put("k988", v988); - map.put("k989", v989); - map.put("k990", v990); - map.put("k991", v991); - map.put("k992", v992); - map.put("k993", v993); - map.put("k994", v994); - map.put("k995", v995); - map.put("k996", v996); - map.put("k997", v997); - map.put("k998", v998); - map.put("k999", v999); - return map; - } + static Map f() { + Map map = new HashMap<>(); + map.put("k0", v0); + map.put("k1", v1); + map.put("k2", v2); + map.put("k3", v3); + map.put("k4", v4); + map.put("k5", v5); + map.put("k6", v6); + map.put("k7", v7); + map.put("k8", v8); + map.put("k9", v9); + map.put("k10", v10); + map.put("k11", v11); + map.put("k12", v12); + map.put("k13", v13); + map.put("k14", v14); + map.put("k15", v15); + map.put("k16", v16); + map.put("k17", v17); + map.put("k18", v18); + map.put("k19", v19); + map.put("k20", v20); + map.put("k21", v21); + map.put("k22", v22); + map.put("k23", v23); + map.put("k24", v24); + map.put("k25", v25); + map.put("k26", v26); + map.put("k27", v27); + map.put("k28", v28); + map.put("k29", v29); + map.put("k30", v30); + map.put("k31", v31); + map.put("k32", v32); + map.put("k33", v33); + map.put("k34", v34); + map.put("k35", v35); + map.put("k36", v36); + map.put("k37", v37); + map.put("k38", v38); + map.put("k39", v39); + map.put("k40", v40); + map.put("k41", v41); + map.put("k42", v42); + map.put("k43", v43); + map.put("k44", v44); + map.put("k45", v45); + map.put("k46", v46); + map.put("k47", v47); + map.put("k48", v48); + map.put("k49", v49); + map.put("k50", v50); + map.put("k51", v51); + map.put("k52", v52); + map.put("k53", v53); + map.put("k54", v54); + map.put("k55", v55); + map.put("k56", v56); + map.put("k57", v57); + map.put("k58", v58); + map.put("k59", v59); + map.put("k60", v60); + map.put("k61", v61); + map.put("k62", v62); + map.put("k63", v63); + map.put("k64", v64); + map.put("k65", v65); + map.put("k66", v66); + map.put("k67", v67); + map.put("k68", v68); + map.put("k69", v69); + map.put("k70", v70); + map.put("k71", v71); + map.put("k72", v72); + map.put("k73", v73); + map.put("k74", v74); + map.put("k75", v75); + map.put("k76", v76); + map.put("k77", v77); + map.put("k78", v78); + map.put("k79", v79); + map.put("k80", v80); + map.put("k81", v81); + map.put("k82", v82); + map.put("k83", v83); + map.put("k84", v84); + map.put("k85", v85); + map.put("k86", v86); + map.put("k87", v87); + map.put("k88", v88); + map.put("k89", v89); + map.put("k90", v90); + map.put("k91", v91); + map.put("k92", v92); + map.put("k93", v93); + map.put("k94", v94); + map.put("k95", v95); + map.put("k96", v96); + map.put("k97", v97); + map.put("k98", v98); + map.put("k99", v99); + map.put("k100", v100); + map.put("k101", v101); + map.put("k102", v102); + map.put("k103", v103); + map.put("k104", v104); + map.put("k105", v105); + map.put("k106", v106); + map.put("k107", v107); + map.put("k108", v108); + map.put("k109", v109); + map.put("k110", v110); + map.put("k111", v111); + map.put("k112", v112); + map.put("k113", v113); + map.put("k114", v114); + map.put("k115", v115); + map.put("k116", v116); + map.put("k117", v117); + map.put("k118", v118); + map.put("k119", v119); + map.put("k120", v120); + map.put("k121", v121); + map.put("k122", v122); + map.put("k123", v123); + map.put("k124", v124); + map.put("k125", v125); + map.put("k126", v126); + map.put("k127", v127); + map.put("k128", v128); + map.put("k129", v129); + map.put("k130", v130); + map.put("k131", v131); + map.put("k132", v132); + map.put("k133", v133); + map.put("k134", v134); + map.put("k135", v135); + map.put("k136", v136); + map.put("k137", v137); + map.put("k138", v138); + map.put("k139", v139); + map.put("k140", v140); + map.put("k141", v141); + map.put("k142", v142); + map.put("k143", v143); + map.put("k144", v144); + map.put("k145", v145); + map.put("k146", v146); + map.put("k147", v147); + map.put("k148", v148); + map.put("k149", v149); + map.put("k150", v150); + map.put("k151", v151); + map.put("k152", v152); + map.put("k153", v153); + map.put("k154", v154); + map.put("k155", v155); + map.put("k156", v156); + map.put("k157", v157); + map.put("k158", v158); + map.put("k159", v159); + map.put("k160", v160); + map.put("k161", v161); + map.put("k162", v162); + map.put("k163", v163); + map.put("k164", v164); + map.put("k165", v165); + map.put("k166", v166); + map.put("k167", v167); + map.put("k168", v168); + map.put("k169", v169); + map.put("k170", v170); + map.put("k171", v171); + map.put("k172", v172); + map.put("k173", v173); + map.put("k174", v174); + map.put("k175", v175); + map.put("k176", v176); + map.put("k177", v177); + map.put("k178", v178); + map.put("k179", v179); + map.put("k180", v180); + map.put("k181", v181); + map.put("k182", v182); + map.put("k183", v183); + map.put("k184", v184); + map.put("k185", v185); + map.put("k186", v186); + map.put("k187", v187); + map.put("k188", v188); + map.put("k189", v189); + map.put("k190", v190); + map.put("k191", v191); + map.put("k192", v192); + map.put("k193", v193); + map.put("k194", v194); + map.put("k195", v195); + map.put("k196", v196); + map.put("k197", v197); + map.put("k198", v198); + map.put("k199", v199); + map.put("k200", v200); + map.put("k201", v201); + map.put("k202", v202); + map.put("k203", v203); + map.put("k204", v204); + map.put("k205", v205); + map.put("k206", v206); + map.put("k207", v207); + map.put("k208", v208); + map.put("k209", v209); + map.put("k210", v210); + map.put("k211", v211); + map.put("k212", v212); + map.put("k213", v213); + map.put("k214", v214); + map.put("k215", v215); + map.put("k216", v216); + map.put("k217", v217); + map.put("k218", v218); + map.put("k219", v219); + map.put("k220", v220); + map.put("k221", v221); + map.put("k222", v222); + map.put("k223", v223); + map.put("k224", v224); + map.put("k225", v225); + map.put("k226", v226); + map.put("k227", v227); + map.put("k228", v228); + map.put("k229", v229); + map.put("k230", v230); + map.put("k231", v231); + map.put("k232", v232); + map.put("k233", v233); + map.put("k234", v234); + map.put("k235", v235); + map.put("k236", v236); + map.put("k237", v237); + map.put("k238", v238); + map.put("k239", v239); + map.put("k240", v240); + map.put("k241", v241); + map.put("k242", v242); + map.put("k243", v243); + map.put("k244", v244); + map.put("k245", v245); + map.put("k246", v246); + map.put("k247", v247); + map.put("k248", v248); + map.put("k249", v249); + map.put("k250", v250); + map.put("k251", v251); + map.put("k252", v252); + map.put("k253", v253); + map.put("k254", v254); + map.put("k255", v255); + map.put("k256", v256); + map.put("k257", v257); + map.put("k258", v258); + map.put("k259", v259); + map.put("k260", v260); + map.put("k261", v261); + map.put("k262", v262); + map.put("k263", v263); + map.put("k264", v264); + map.put("k265", v265); + map.put("k266", v266); + map.put("k267", v267); + map.put("k268", v268); + map.put("k269", v269); + map.put("k270", v270); + map.put("k271", v271); + map.put("k272", v272); + map.put("k273", v273); + map.put("k274", v274); + map.put("k275", v275); + map.put("k276", v276); + map.put("k277", v277); + map.put("k278", v278); + map.put("k279", v279); + map.put("k280", v280); + map.put("k281", v281); + map.put("k282", v282); + map.put("k283", v283); + map.put("k284", v284); + map.put("k285", v285); + map.put("k286", v286); + map.put("k287", v287); + map.put("k288", v288); + map.put("k289", v289); + map.put("k290", v290); + map.put("k291", v291); + map.put("k292", v292); + map.put("k293", v293); + map.put("k294", v294); + map.put("k295", v295); + map.put("k296", v296); + map.put("k297", v297); + map.put("k298", v298); + map.put("k299", v299); + map.put("k300", v300); + map.put("k301", v301); + map.put("k302", v302); + map.put("k303", v303); + map.put("k304", v304); + map.put("k305", v305); + map.put("k306", v306); + map.put("k307", v307); + map.put("k308", v308); + map.put("k309", v309); + map.put("k310", v310); + map.put("k311", v311); + map.put("k312", v312); + map.put("k313", v313); + map.put("k314", v314); + map.put("k315", v315); + map.put("k316", v316); + map.put("k317", v317); + map.put("k318", v318); + map.put("k319", v319); + map.put("k320", v320); + map.put("k321", v321); + map.put("k322", v322); + map.put("k323", v323); + map.put("k324", v324); + map.put("k325", v325); + map.put("k326", v326); + map.put("k327", v327); + map.put("k328", v328); + map.put("k329", v329); + map.put("k330", v330); + map.put("k331", v331); + map.put("k332", v332); + map.put("k333", v333); + map.put("k334", v334); + map.put("k335", v335); + map.put("k336", v336); + map.put("k337", v337); + map.put("k338", v338); + map.put("k339", v339); + map.put("k340", v340); + map.put("k341", v341); + map.put("k342", v342); + map.put("k343", v343); + map.put("k344", v344); + map.put("k345", v345); + map.put("k346", v346); + map.put("k347", v347); + map.put("k348", v348); + map.put("k349", v349); + map.put("k350", v350); + map.put("k351", v351); + map.put("k352", v352); + map.put("k353", v353); + map.put("k354", v354); + map.put("k355", v355); + map.put("k356", v356); + map.put("k357", v357); + map.put("k358", v358); + map.put("k359", v359); + map.put("k360", v360); + map.put("k361", v361); + map.put("k362", v362); + map.put("k363", v363); + map.put("k364", v364); + map.put("k365", v365); + map.put("k366", v366); + map.put("k367", v367); + map.put("k368", v368); + map.put("k369", v369); + map.put("k370", v370); + map.put("k371", v371); + map.put("k372", v372); + map.put("k373", v373); + map.put("k374", v374); + map.put("k375", v375); + map.put("k376", v376); + map.put("k377", v377); + map.put("k378", v378); + map.put("k379", v379); + map.put("k380", v380); + map.put("k381", v381); + map.put("k382", v382); + map.put("k383", v383); + map.put("k384", v384); + map.put("k385", v385); + map.put("k386", v386); + map.put("k387", v387); + map.put("k388", v388); + map.put("k389", v389); + map.put("k390", v390); + map.put("k391", v391); + map.put("k392", v392); + map.put("k393", v393); + map.put("k394", v394); + map.put("k395", v395); + map.put("k396", v396); + map.put("k397", v397); + map.put("k398", v398); + map.put("k399", v399); + map.put("k400", v400); + map.put("k401", v401); + map.put("k402", v402); + map.put("k403", v403); + map.put("k404", v404); + map.put("k405", v405); + map.put("k406", v406); + map.put("k407", v407); + map.put("k408", v408); + map.put("k409", v409); + map.put("k410", v410); + map.put("k411", v411); + map.put("k412", v412); + map.put("k413", v413); + map.put("k414", v414); + map.put("k415", v415); + map.put("k416", v416); + map.put("k417", v417); + map.put("k418", v418); + map.put("k419", v419); + map.put("k420", v420); + map.put("k421", v421); + map.put("k422", v422); + map.put("k423", v423); + map.put("k424", v424); + map.put("k425", v425); + map.put("k426", v426); + map.put("k427", v427); + map.put("k428", v428); + map.put("k429", v429); + map.put("k430", v430); + map.put("k431", v431); + map.put("k432", v432); + map.put("k433", v433); + map.put("k434", v434); + map.put("k435", v435); + map.put("k436", v436); + map.put("k437", v437); + map.put("k438", v438); + map.put("k439", v439); + map.put("k440", v440); + map.put("k441", v441); + map.put("k442", v442); + map.put("k443", v443); + map.put("k444", v444); + map.put("k445", v445); + map.put("k446", v446); + map.put("k447", v447); + map.put("k448", v448); + map.put("k449", v449); + map.put("k450", v450); + map.put("k451", v451); + map.put("k452", v452); + map.put("k453", v453); + map.put("k454", v454); + map.put("k455", v455); + map.put("k456", v456); + map.put("k457", v457); + map.put("k458", v458); + map.put("k459", v459); + map.put("k460", v460); + map.put("k461", v461); + map.put("k462", v462); + map.put("k463", v463); + map.put("k464", v464); + map.put("k465", v465); + map.put("k466", v466); + map.put("k467", v467); + map.put("k468", v468); + map.put("k469", v469); + map.put("k470", v470); + map.put("k471", v471); + map.put("k472", v472); + map.put("k473", v473); + map.put("k474", v474); + map.put("k475", v475); + map.put("k476", v476); + map.put("k477", v477); + map.put("k478", v478); + map.put("k479", v479); + map.put("k480", v480); + map.put("k481", v481); + map.put("k482", v482); + map.put("k483", v483); + map.put("k484", v484); + map.put("k485", v485); + map.put("k486", v486); + map.put("k487", v487); + map.put("k488", v488); + map.put("k489", v489); + map.put("k490", v490); + map.put("k491", v491); + map.put("k492", v492); + map.put("k493", v493); + map.put("k494", v494); + map.put("k495", v495); + map.put("k496", v496); + map.put("k497", v497); + map.put("k498", v498); + map.put("k499", v499); + map.put("k500", v500); + map.put("k501", v501); + map.put("k502", v502); + map.put("k503", v503); + map.put("k504", v504); + map.put("k505", v505); + map.put("k506", v506); + map.put("k507", v507); + map.put("k508", v508); + map.put("k509", v509); + map.put("k510", v510); + map.put("k511", v511); + map.put("k512", v512); + map.put("k513", v513); + map.put("k514", v514); + map.put("k515", v515); + map.put("k516", v516); + map.put("k517", v517); + map.put("k518", v518); + map.put("k519", v519); + map.put("k520", v520); + map.put("k521", v521); + map.put("k522", v522); + map.put("k523", v523); + map.put("k524", v524); + map.put("k525", v525); + map.put("k526", v526); + map.put("k527", v527); + map.put("k528", v528); + map.put("k529", v529); + map.put("k530", v530); + map.put("k531", v531); + map.put("k532", v532); + map.put("k533", v533); + map.put("k534", v534); + map.put("k535", v535); + map.put("k536", v536); + map.put("k537", v537); + map.put("k538", v538); + map.put("k539", v539); + map.put("k540", v540); + map.put("k541", v541); + map.put("k542", v542); + map.put("k543", v543); + map.put("k544", v544); + map.put("k545", v545); + map.put("k546", v546); + map.put("k547", v547); + map.put("k548", v548); + map.put("k549", v549); + map.put("k550", v550); + map.put("k551", v551); + map.put("k552", v552); + map.put("k553", v553); + map.put("k554", v554); + map.put("k555", v555); + map.put("k556", v556); + map.put("k557", v557); + map.put("k558", v558); + map.put("k559", v559); + map.put("k560", v560); + map.put("k561", v561); + map.put("k562", v562); + map.put("k563", v563); + map.put("k564", v564); + map.put("k565", v565); + map.put("k566", v566); + map.put("k567", v567); + map.put("k568", v568); + map.put("k569", v569); + map.put("k570", v570); + map.put("k571", v571); + map.put("k572", v572); + map.put("k573", v573); + map.put("k574", v574); + map.put("k575", v575); + map.put("k576", v576); + map.put("k577", v577); + map.put("k578", v578); + map.put("k579", v579); + map.put("k580", v580); + map.put("k581", v581); + map.put("k582", v582); + map.put("k583", v583); + map.put("k584", v584); + map.put("k585", v585); + map.put("k586", v586); + map.put("k587", v587); + map.put("k588", v588); + map.put("k589", v589); + map.put("k590", v590); + map.put("k591", v591); + map.put("k592", v592); + map.put("k593", v593); + map.put("k594", v594); + map.put("k595", v595); + map.put("k596", v596); + map.put("k597", v597); + map.put("k598", v598); + map.put("k599", v599); + map.put("k600", v600); + map.put("k601", v601); + map.put("k602", v602); + map.put("k603", v603); + map.put("k604", v604); + map.put("k605", v605); + map.put("k606", v606); + map.put("k607", v607); + map.put("k608", v608); + map.put("k609", v609); + map.put("k610", v610); + map.put("k611", v611); + map.put("k612", v612); + map.put("k613", v613); + map.put("k614", v614); + map.put("k615", v615); + map.put("k616", v616); + map.put("k617", v617); + map.put("k618", v618); + map.put("k619", v619); + map.put("k620", v620); + map.put("k621", v621); + map.put("k622", v622); + map.put("k623", v623); + map.put("k624", v624); + map.put("k625", v625); + map.put("k626", v626); + map.put("k627", v627); + map.put("k628", v628); + map.put("k629", v629); + map.put("k630", v630); + map.put("k631", v631); + map.put("k632", v632); + map.put("k633", v633); + map.put("k634", v634); + map.put("k635", v635); + map.put("k636", v636); + map.put("k637", v637); + map.put("k638", v638); + map.put("k639", v639); + map.put("k640", v640); + map.put("k641", v641); + map.put("k642", v642); + map.put("k643", v643); + map.put("k644", v644); + map.put("k645", v645); + map.put("k646", v646); + map.put("k647", v647); + map.put("k648", v648); + map.put("k649", v649); + map.put("k650", v650); + map.put("k651", v651); + map.put("k652", v652); + map.put("k653", v653); + map.put("k654", v654); + map.put("k655", v655); + map.put("k656", v656); + map.put("k657", v657); + map.put("k658", v658); + map.put("k659", v659); + map.put("k660", v660); + map.put("k661", v661); + map.put("k662", v662); + map.put("k663", v663); + map.put("k664", v664); + map.put("k665", v665); + map.put("k666", v666); + map.put("k667", v667); + map.put("k668", v668); + map.put("k669", v669); + map.put("k670", v670); + map.put("k671", v671); + map.put("k672", v672); + map.put("k673", v673); + map.put("k674", v674); + map.put("k675", v675); + map.put("k676", v676); + map.put("k677", v677); + map.put("k678", v678); + map.put("k679", v679); + map.put("k680", v680); + map.put("k681", v681); + map.put("k682", v682); + map.put("k683", v683); + map.put("k684", v684); + map.put("k685", v685); + map.put("k686", v686); + map.put("k687", v687); + map.put("k688", v688); + map.put("k689", v689); + map.put("k690", v690); + map.put("k691", v691); + map.put("k692", v692); + map.put("k693", v693); + map.put("k694", v694); + map.put("k695", v695); + map.put("k696", v696); + map.put("k697", v697); + map.put("k698", v698); + map.put("k699", v699); + map.put("k700", v700); + map.put("k701", v701); + map.put("k702", v702); + map.put("k703", v703); + map.put("k704", v704); + map.put("k705", v705); + map.put("k706", v706); + map.put("k707", v707); + map.put("k708", v708); + map.put("k709", v709); + map.put("k710", v710); + map.put("k711", v711); + map.put("k712", v712); + map.put("k713", v713); + map.put("k714", v714); + map.put("k715", v715); + map.put("k716", v716); + map.put("k717", v717); + map.put("k718", v718); + map.put("k719", v719); + map.put("k720", v720); + map.put("k721", v721); + map.put("k722", v722); + map.put("k723", v723); + map.put("k724", v724); + map.put("k725", v725); + map.put("k726", v726); + map.put("k727", v727); + map.put("k728", v728); + map.put("k729", v729); + map.put("k730", v730); + map.put("k731", v731); + map.put("k732", v732); + map.put("k733", v733); + map.put("k734", v734); + map.put("k735", v735); + map.put("k736", v736); + map.put("k737", v737); + map.put("k738", v738); + map.put("k739", v739); + map.put("k740", v740); + map.put("k741", v741); + map.put("k742", v742); + map.put("k743", v743); + map.put("k744", v744); + map.put("k745", v745); + map.put("k746", v746); + map.put("k747", v747); + map.put("k748", v748); + map.put("k749", v749); + map.put("k750", v750); + map.put("k751", v751); + map.put("k752", v752); + map.put("k753", v753); + map.put("k754", v754); + map.put("k755", v755); + map.put("k756", v756); + map.put("k757", v757); + map.put("k758", v758); + map.put("k759", v759); + map.put("k760", v760); + map.put("k761", v761); + map.put("k762", v762); + map.put("k763", v763); + map.put("k764", v764); + map.put("k765", v765); + map.put("k766", v766); + map.put("k767", v767); + map.put("k768", v768); + map.put("k769", v769); + map.put("k770", v770); + map.put("k771", v771); + map.put("k772", v772); + map.put("k773", v773); + map.put("k774", v774); + map.put("k775", v775); + map.put("k776", v776); + map.put("k777", v777); + map.put("k778", v778); + map.put("k779", v779); + map.put("k780", v780); + map.put("k781", v781); + map.put("k782", v782); + map.put("k783", v783); + map.put("k784", v784); + map.put("k785", v785); + map.put("k786", v786); + map.put("k787", v787); + map.put("k788", v788); + map.put("k789", v789); + map.put("k790", v790); + map.put("k791", v791); + map.put("k792", v792); + map.put("k793", v793); + map.put("k794", v794); + map.put("k795", v795); + map.put("k796", v796); + map.put("k797", v797); + map.put("k798", v798); + map.put("k799", v799); + map.put("k800", v800); + map.put("k801", v801); + map.put("k802", v802); + map.put("k803", v803); + map.put("k804", v804); + map.put("k805", v805); + map.put("k806", v806); + map.put("k807", v807); + map.put("k808", v808); + map.put("k809", v809); + map.put("k810", v810); + map.put("k811", v811); + map.put("k812", v812); + map.put("k813", v813); + map.put("k814", v814); + map.put("k815", v815); + map.put("k816", v816); + map.put("k817", v817); + map.put("k818", v818); + map.put("k819", v819); + map.put("k820", v820); + map.put("k821", v821); + map.put("k822", v822); + map.put("k823", v823); + map.put("k824", v824); + map.put("k825", v825); + map.put("k826", v826); + map.put("k827", v827); + map.put("k828", v828); + map.put("k829", v829); + map.put("k830", v830); + map.put("k831", v831); + map.put("k832", v832); + map.put("k833", v833); + map.put("k834", v834); + map.put("k835", v835); + map.put("k836", v836); + map.put("k837", v837); + map.put("k838", v838); + map.put("k839", v839); + map.put("k840", v840); + map.put("k841", v841); + map.put("k842", v842); + map.put("k843", v843); + map.put("k844", v844); + map.put("k845", v845); + map.put("k846", v846); + map.put("k847", v847); + map.put("k848", v848); + map.put("k849", v849); + map.put("k850", v850); + map.put("k851", v851); + map.put("k852", v852); + map.put("k853", v853); + map.put("k854", v854); + map.put("k855", v855); + map.put("k856", v856); + map.put("k857", v857); + map.put("k858", v858); + map.put("k859", v859); + map.put("k860", v860); + map.put("k861", v861); + map.put("k862", v862); + map.put("k863", v863); + map.put("k864", v864); + map.put("k865", v865); + map.put("k866", v866); + map.put("k867", v867); + map.put("k868", v868); + map.put("k869", v869); + map.put("k870", v870); + map.put("k871", v871); + map.put("k872", v872); + map.put("k873", v873); + map.put("k874", v874); + map.put("k875", v875); + map.put("k876", v876); + map.put("k877", v877); + map.put("k878", v878); + map.put("k879", v879); + map.put("k880", v880); + map.put("k881", v881); + map.put("k882", v882); + map.put("k883", v883); + map.put("k884", v884); + map.put("k885", v885); + map.put("k886", v886); + map.put("k887", v887); + map.put("k888", v888); + map.put("k889", v889); + map.put("k890", v890); + map.put("k891", v891); + map.put("k892", v892); + map.put("k893", v893); + map.put("k894", v894); + map.put("k895", v895); + map.put("k896", v896); + map.put("k897", v897); + map.put("k898", v898); + map.put("k899", v899); + map.put("k900", v900); + map.put("k901", v901); + map.put("k902", v902); + map.put("k903", v903); + map.put("k904", v904); + map.put("k905", v905); + map.put("k906", v906); + map.put("k907", v907); + map.put("k908", v908); + map.put("k909", v909); + map.put("k910", v910); + map.put("k911", v911); + map.put("k912", v912); + map.put("k913", v913); + map.put("k914", v914); + map.put("k915", v915); + map.put("k916", v916); + map.put("k917", v917); + map.put("k918", v918); + map.put("k919", v919); + map.put("k920", v920); + map.put("k921", v921); + map.put("k922", v922); + map.put("k923", v923); + map.put("k924", v924); + map.put("k925", v925); + map.put("k926", v926); + map.put("k927", v927); + map.put("k928", v928); + map.put("k929", v929); + map.put("k930", v930); + map.put("k931", v931); + map.put("k932", v932); + map.put("k933", v933); + map.put("k934", v934); + map.put("k935", v935); + map.put("k936", v936); + map.put("k937", v937); + map.put("k938", v938); + map.put("k939", v939); + map.put("k940", v940); + map.put("k941", v941); + map.put("k942", v942); + map.put("k943", v943); + map.put("k944", v944); + map.put("k945", v945); + map.put("k946", v946); + map.put("k947", v947); + map.put("k948", v948); + map.put("k949", v949); + map.put("k950", v950); + map.put("k951", v951); + map.put("k952", v952); + map.put("k953", v953); + map.put("k954", v954); + map.put("k955", v955); + map.put("k956", v956); + map.put("k957", v957); + map.put("k958", v958); + map.put("k959", v959); + map.put("k960", v960); + map.put("k961", v961); + map.put("k962", v962); + map.put("k963", v963); + map.put("k964", v964); + map.put("k965", v965); + map.put("k966", v966); + map.put("k967", v967); + map.put("k968", v968); + map.put("k969", v969); + map.put("k970", v970); + map.put("k971", v971); + map.put("k972", v972); + map.put("k973", v973); + map.put("k974", v974); + map.put("k975", v975); + map.put("k976", v976); + map.put("k977", v977); + map.put("k978", v978); + map.put("k979", v979); + map.put("k980", v980); + map.put("k981", v981); + map.put("k982", v982); + map.put("k983", v983); + map.put("k984", v984); + map.put("k985", v985); + map.put("k986", v986); + map.put("k987", v987); + map.put("k988", v988); + map.put("k989", v989); + map.put("k990", v990); + map.put("k991", v991); + map.put("k992", v992); + map.put("k993", v993); + map.put("k994", v994); + map.put("k995", v995); + map.put("k996", v996); + map.put("k997", v997); + map.put("k998", v998); + map.put("k999", v999); + return map; + } } diff --git a/checker/jtreg/nullness/Issue1438b.java b/checker/jtreg/nullness/Issue1438b.java index bd778cefe81..5c8fa0ea84b 100644 --- a/checker/jtreg/nullness/Issue1438b.java +++ b/checker/jtreg/nullness/Issue1438b.java @@ -9,2010 +9,2010 @@ import java.util.Map; public class Issue1438b { - // Do not initialize these variables. The Nullness Checker is supposed to issue an error about - // uninitialized fields. - Integer v0; - Integer v1; - Integer v2; - Integer v3; - Integer v4; - Integer v5; - Integer v6; - Integer v7; - Integer v8; - Integer v9; - Integer v10; - Integer v11; - Integer v12; - Integer v13; - Integer v14; - Integer v15; - Integer v16; - Integer v17; - Integer v18; - Integer v19; - Integer v20; - Integer v21; - Integer v22; - Integer v23; - Integer v24; - Integer v25; - Integer v26; - Integer v27; - Integer v28; - Integer v29; - Integer v30; - Integer v31; - Integer v32; - Integer v33; - Integer v34; - Integer v35; - Integer v36; - Integer v37; - Integer v38; - Integer v39; - Integer v40; - Integer v41; - Integer v42; - Integer v43; - Integer v44; - Integer v45; - Integer v46; - Integer v47; - Integer v48; - Integer v49; - Integer v50; - Integer v51; - Integer v52; - Integer v53; - Integer v54; - Integer v55; - Integer v56; - Integer v57; - Integer v58; - Integer v59; - Integer v60; - Integer v61; - Integer v62; - Integer v63; - Integer v64; - Integer v65; - Integer v66; - Integer v67; - Integer v68; - Integer v69; - Integer v70; - Integer v71; - Integer v72; - Integer v73; - Integer v74; - Integer v75; - Integer v76; - Integer v77; - Integer v78; - Integer v79; - Integer v80; - Integer v81; - Integer v82; - Integer v83; - Integer v84; - Integer v85; - Integer v86; - Integer v87; - Integer v88; - Integer v89; - Integer v90; - Integer v91; - Integer v92; - Integer v93; - Integer v94; - Integer v95; - Integer v96; - Integer v97; - Integer v98; - Integer v99; - Integer v100; - Integer v101; - Integer v102; - Integer v103; - Integer v104; - Integer v105; - Integer v106; - Integer v107; - Integer v108; - Integer v109; - Integer v110; - Integer v111; - Integer v112; - Integer v113; - Integer v114; - Integer v115; - Integer v116; - Integer v117; - Integer v118; - Integer v119; - Integer v120; - Integer v121; - Integer v122; - Integer v123; - Integer v124; - Integer v125; - Integer v126; - Integer v127; - Integer v128; - Integer v129; - Integer v130; - Integer v131; - Integer v132; - Integer v133; - Integer v134; - Integer v135; - Integer v136; - Integer v137; - Integer v138; - Integer v139; - Integer v140; - Integer v141; - Integer v142; - Integer v143; - Integer v144; - Integer v145; - Integer v146; - Integer v147; - Integer v148; - Integer v149; - Integer v150; - Integer v151; - Integer v152; - Integer v153; - Integer v154; - Integer v155; - Integer v156; - Integer v157; - Integer v158; - Integer v159; - Integer v160; - Integer v161; - Integer v162; - Integer v163; - Integer v164; - Integer v165; - Integer v166; - Integer v167; - Integer v168; - Integer v169; - Integer v170; - Integer v171; - Integer v172; - Integer v173; - Integer v174; - Integer v175; - Integer v176; - Integer v177; - Integer v178; - Integer v179; - Integer v180; - Integer v181; - Integer v182; - Integer v183; - Integer v184; - Integer v185; - Integer v186; - Integer v187; - Integer v188; - Integer v189; - Integer v190; - Integer v191; - Integer v192; - Integer v193; - Integer v194; - Integer v195; - Integer v196; - Integer v197; - Integer v198; - Integer v199; - Integer v200; - Integer v201; - Integer v202; - Integer v203; - Integer v204; - Integer v205; - Integer v206; - Integer v207; - Integer v208; - Integer v209; - Integer v210; - Integer v211; - Integer v212; - Integer v213; - Integer v214; - Integer v215; - Integer v216; - Integer v217; - Integer v218; - Integer v219; - Integer v220; - Integer v221; - Integer v222; - Integer v223; - Integer v224; - Integer v225; - Integer v226; - Integer v227; - Integer v228; - Integer v229; - Integer v230; - Integer v231; - Integer v232; - Integer v233; - Integer v234; - Integer v235; - Integer v236; - Integer v237; - Integer v238; - Integer v239; - Integer v240; - Integer v241; - Integer v242; - Integer v243; - Integer v244; - Integer v245; - Integer v246; - Integer v247; - Integer v248; - Integer v249; - Integer v250; - Integer v251; - Integer v252; - Integer v253; - Integer v254; - Integer v255; - Integer v256; - Integer v257; - Integer v258; - Integer v259; - Integer v260; - Integer v261; - Integer v262; - Integer v263; - Integer v264; - Integer v265; - Integer v266; - Integer v267; - Integer v268; - Integer v269; - Integer v270; - Integer v271; - Integer v272; - Integer v273; - Integer v274; - Integer v275; - Integer v276; - Integer v277; - Integer v278; - Integer v279; - Integer v280; - Integer v281; - Integer v282; - Integer v283; - Integer v284; - Integer v285; - Integer v286; - Integer v287; - Integer v288; - Integer v289; - Integer v290; - Integer v291; - Integer v292; - Integer v293; - Integer v294; - Integer v295; - Integer v296; - Integer v297; - Integer v298; - Integer v299; - Integer v300; - Integer v301; - Integer v302; - Integer v303; - Integer v304; - Integer v305; - Integer v306; - Integer v307; - Integer v308; - Integer v309; - Integer v310; - Integer v311; - Integer v312; - Integer v313; - Integer v314; - Integer v315; - Integer v316; - Integer v317; - Integer v318; - Integer v319; - Integer v320; - Integer v321; - Integer v322; - Integer v323; - Integer v324; - Integer v325; - Integer v326; - Integer v327; - Integer v328; - Integer v329; - Integer v330; - Integer v331; - Integer v332; - Integer v333; - Integer v334; - Integer v335; - Integer v336; - Integer v337; - Integer v338; - Integer v339; - Integer v340; - Integer v341; - Integer v342; - Integer v343; - Integer v344; - Integer v345; - Integer v346; - Integer v347; - Integer v348; - Integer v349; - Integer v350; - Integer v351; - Integer v352; - Integer v353; - Integer v354; - Integer v355; - Integer v356; - Integer v357; - Integer v358; - Integer v359; - Integer v360; - Integer v361; - Integer v362; - Integer v363; - Integer v364; - Integer v365; - Integer v366; - Integer v367; - Integer v368; - Integer v369; - Integer v370; - Integer v371; - Integer v372; - Integer v373; - Integer v374; - Integer v375; - Integer v376; - Integer v377; - Integer v378; - Integer v379; - Integer v380; - Integer v381; - Integer v382; - Integer v383; - Integer v384; - Integer v385; - Integer v386; - Integer v387; - Integer v388; - Integer v389; - Integer v390; - Integer v391; - Integer v392; - Integer v393; - Integer v394; - Integer v395; - Integer v396; - Integer v397; - Integer v398; - Integer v399; - Integer v400; - Integer v401; - Integer v402; - Integer v403; - Integer v404; - Integer v405; - Integer v406; - Integer v407; - Integer v408; - Integer v409; - Integer v410; - Integer v411; - Integer v412; - Integer v413; - Integer v414; - Integer v415; - Integer v416; - Integer v417; - Integer v418; - Integer v419; - Integer v420; - Integer v421; - Integer v422; - Integer v423; - Integer v424; - Integer v425; - Integer v426; - Integer v427; - Integer v428; - Integer v429; - Integer v430; - Integer v431; - Integer v432; - Integer v433; - Integer v434; - Integer v435; - Integer v436; - Integer v437; - Integer v438; - Integer v439; - Integer v440; - Integer v441; - Integer v442; - Integer v443; - Integer v444; - Integer v445; - Integer v446; - Integer v447; - Integer v448; - Integer v449; - Integer v450; - Integer v451; - Integer v452; - Integer v453; - Integer v454; - Integer v455; - Integer v456; - Integer v457; - Integer v458; - Integer v459; - Integer v460; - Integer v461; - Integer v462; - Integer v463; - Integer v464; - Integer v465; - Integer v466; - Integer v467; - Integer v468; - Integer v469; - Integer v470; - Integer v471; - Integer v472; - Integer v473; - Integer v474; - Integer v475; - Integer v476; - Integer v477; - Integer v478; - Integer v479; - Integer v480; - Integer v481; - Integer v482; - Integer v483; - Integer v484; - Integer v485; - Integer v486; - Integer v487; - Integer v488; - Integer v489; - Integer v490; - Integer v491; - Integer v492; - Integer v493; - Integer v494; - Integer v495; - Integer v496; - Integer v497; - Integer v498; - Integer v499; - Integer v500; - Integer v501; - Integer v502; - Integer v503; - Integer v504; - Integer v505; - Integer v506; - Integer v507; - Integer v508; - Integer v509; - Integer v510; - Integer v511; - Integer v512; - Integer v513; - Integer v514; - Integer v515; - Integer v516; - Integer v517; - Integer v518; - Integer v519; - Integer v520; - Integer v521; - Integer v522; - Integer v523; - Integer v524; - Integer v525; - Integer v526; - Integer v527; - Integer v528; - Integer v529; - Integer v530; - Integer v531; - Integer v532; - Integer v533; - Integer v534; - Integer v535; - Integer v536; - Integer v537; - Integer v538; - Integer v539; - Integer v540; - Integer v541; - Integer v542; - Integer v543; - Integer v544; - Integer v545; - Integer v546; - Integer v547; - Integer v548; - Integer v549; - Integer v550; - Integer v551; - Integer v552; - Integer v553; - Integer v554; - Integer v555; - Integer v556; - Integer v557; - Integer v558; - Integer v559; - Integer v560; - Integer v561; - Integer v562; - Integer v563; - Integer v564; - Integer v565; - Integer v566; - Integer v567; - Integer v568; - Integer v569; - Integer v570; - Integer v571; - Integer v572; - Integer v573; - Integer v574; - Integer v575; - Integer v576; - Integer v577; - Integer v578; - Integer v579; - Integer v580; - Integer v581; - Integer v582; - Integer v583; - Integer v584; - Integer v585; - Integer v586; - Integer v587; - Integer v588; - Integer v589; - Integer v590; - Integer v591; - Integer v592; - Integer v593; - Integer v594; - Integer v595; - Integer v596; - Integer v597; - Integer v598; - Integer v599; - Integer v600; - Integer v601; - Integer v602; - Integer v603; - Integer v604; - Integer v605; - Integer v606; - Integer v607; - Integer v608; - Integer v609; - Integer v610; - Integer v611; - Integer v612; - Integer v613; - Integer v614; - Integer v615; - Integer v616; - Integer v617; - Integer v618; - Integer v619; - Integer v620; - Integer v621; - Integer v622; - Integer v623; - Integer v624; - Integer v625; - Integer v626; - Integer v627; - Integer v628; - Integer v629; - Integer v630; - Integer v631; - Integer v632; - Integer v633; - Integer v634; - Integer v635; - Integer v636; - Integer v637; - Integer v638; - Integer v639; - Integer v640; - Integer v641; - Integer v642; - Integer v643; - Integer v644; - Integer v645; - Integer v646; - Integer v647; - Integer v648; - Integer v649; - Integer v650; - Integer v651; - Integer v652; - Integer v653; - Integer v654; - Integer v655; - Integer v656; - Integer v657; - Integer v658; - Integer v659; - Integer v660; - Integer v661; - Integer v662; - Integer v663; - Integer v664; - Integer v665; - Integer v666; - Integer v667; - Integer v668; - Integer v669; - Integer v670; - Integer v671; - Integer v672; - Integer v673; - Integer v674; - Integer v675; - Integer v676; - Integer v677; - Integer v678; - Integer v679; - Integer v680; - Integer v681; - Integer v682; - Integer v683; - Integer v684; - Integer v685; - Integer v686; - Integer v687; - Integer v688; - Integer v689; - Integer v690; - Integer v691; - Integer v692; - Integer v693; - Integer v694; - Integer v695; - Integer v696; - Integer v697; - Integer v698; - Integer v699; - Integer v700; - Integer v701; - Integer v702; - Integer v703; - Integer v704; - Integer v705; - Integer v706; - Integer v707; - Integer v708; - Integer v709; - Integer v710; - Integer v711; - Integer v712; - Integer v713; - Integer v714; - Integer v715; - Integer v716; - Integer v717; - Integer v718; - Integer v719; - Integer v720; - Integer v721; - Integer v722; - Integer v723; - Integer v724; - Integer v725; - Integer v726; - Integer v727; - Integer v728; - Integer v729; - Integer v730; - Integer v731; - Integer v732; - Integer v733; - Integer v734; - Integer v735; - Integer v736; - Integer v737; - Integer v738; - Integer v739; - Integer v740; - Integer v741; - Integer v742; - Integer v743; - Integer v744; - Integer v745; - Integer v746; - Integer v747; - Integer v748; - Integer v749; - Integer v750; - Integer v751; - Integer v752; - Integer v753; - Integer v754; - Integer v755; - Integer v756; - Integer v757; - Integer v758; - Integer v759; - Integer v760; - Integer v761; - Integer v762; - Integer v763; - Integer v764; - Integer v765; - Integer v766; - Integer v767; - Integer v768; - Integer v769; - Integer v770; - Integer v771; - Integer v772; - Integer v773; - Integer v774; - Integer v775; - Integer v776; - Integer v777; - Integer v778; - Integer v779; - Integer v780; - Integer v781; - Integer v782; - Integer v783; - Integer v784; - Integer v785; - Integer v786; - Integer v787; - Integer v788; - Integer v789; - Integer v790; - Integer v791; - Integer v792; - Integer v793; - Integer v794; - Integer v795; - Integer v796; - Integer v797; - Integer v798; - Integer v799; - Integer v800; - Integer v801; - Integer v802; - Integer v803; - Integer v804; - Integer v805; - Integer v806; - Integer v807; - Integer v808; - Integer v809; - Integer v810; - Integer v811; - Integer v812; - Integer v813; - Integer v814; - Integer v815; - Integer v816; - Integer v817; - Integer v818; - Integer v819; - Integer v820; - Integer v821; - Integer v822; - Integer v823; - Integer v824; - Integer v825; - Integer v826; - Integer v827; - Integer v828; - Integer v829; - Integer v830; - Integer v831; - Integer v832; - Integer v833; - Integer v834; - Integer v835; - Integer v836; - Integer v837; - Integer v838; - Integer v839; - Integer v840; - Integer v841; - Integer v842; - Integer v843; - Integer v844; - Integer v845; - Integer v846; - Integer v847; - Integer v848; - Integer v849; - Integer v850; - Integer v851; - Integer v852; - Integer v853; - Integer v854; - Integer v855; - Integer v856; - Integer v857; - Integer v858; - Integer v859; - Integer v860; - Integer v861; - Integer v862; - Integer v863; - Integer v864; - Integer v865; - Integer v866; - Integer v867; - Integer v868; - Integer v869; - Integer v870; - Integer v871; - Integer v872; - Integer v873; - Integer v874; - Integer v875; - Integer v876; - Integer v877; - Integer v878; - Integer v879; - Integer v880; - Integer v881; - Integer v882; - Integer v883; - Integer v884; - Integer v885; - Integer v886; - Integer v887; - Integer v888; - Integer v889; - Integer v890; - Integer v891; - Integer v892; - Integer v893; - Integer v894; - Integer v895; - Integer v896; - Integer v897; - Integer v898; - Integer v899; - Integer v900; - Integer v901; - Integer v902; - Integer v903; - Integer v904; - Integer v905; - Integer v906; - Integer v907; - Integer v908; - Integer v909; - Integer v910; - Integer v911; - Integer v912; - Integer v913; - Integer v914; - Integer v915; - Integer v916; - Integer v917; - Integer v918; - Integer v919; - Integer v920; - Integer v921; - Integer v922; - Integer v923; - Integer v924; - Integer v925; - Integer v926; - Integer v927; - Integer v928; - Integer v929; - Integer v930; - Integer v931; - Integer v932; - Integer v933; - Integer v934; - Integer v935; - Integer v936; - Integer v937; - Integer v938; - Integer v939; - Integer v940; - Integer v941; - Integer v942; - Integer v943; - Integer v944; - Integer v945; - Integer v946; - Integer v947; - Integer v948; - Integer v949; - Integer v950; - Integer v951; - Integer v952; - Integer v953; - Integer v954; - Integer v955; - Integer v956; - Integer v957; - Integer v958; - Integer v959; - Integer v960; - Integer v961; - Integer v962; - Integer v963; - Integer v964; - Integer v965; - Integer v966; - Integer v967; - Integer v968; - Integer v969; - Integer v970; - Integer v971; - Integer v972; - Integer v973; - Integer v974; - Integer v975; - Integer v976; - Integer v977; - Integer v978; - Integer v979; - Integer v980; - Integer v981; - Integer v982; - Integer v983; - Integer v984; - Integer v985; - Integer v986; - Integer v987; - Integer v988; - Integer v989; - Integer v990; - Integer v991; - Integer v992; - Integer v993; - Integer v994; - Integer v995; - Integer v996; - Integer v997; - Integer v998; - Integer v999; + // Do not initialize these variables. The Nullness Checker is supposed to issue an error about + // uninitialized fields. + Integer v0; + Integer v1; + Integer v2; + Integer v3; + Integer v4; + Integer v5; + Integer v6; + Integer v7; + Integer v8; + Integer v9; + Integer v10; + Integer v11; + Integer v12; + Integer v13; + Integer v14; + Integer v15; + Integer v16; + Integer v17; + Integer v18; + Integer v19; + Integer v20; + Integer v21; + Integer v22; + Integer v23; + Integer v24; + Integer v25; + Integer v26; + Integer v27; + Integer v28; + Integer v29; + Integer v30; + Integer v31; + Integer v32; + Integer v33; + Integer v34; + Integer v35; + Integer v36; + Integer v37; + Integer v38; + Integer v39; + Integer v40; + Integer v41; + Integer v42; + Integer v43; + Integer v44; + Integer v45; + Integer v46; + Integer v47; + Integer v48; + Integer v49; + Integer v50; + Integer v51; + Integer v52; + Integer v53; + Integer v54; + Integer v55; + Integer v56; + Integer v57; + Integer v58; + Integer v59; + Integer v60; + Integer v61; + Integer v62; + Integer v63; + Integer v64; + Integer v65; + Integer v66; + Integer v67; + Integer v68; + Integer v69; + Integer v70; + Integer v71; + Integer v72; + Integer v73; + Integer v74; + Integer v75; + Integer v76; + Integer v77; + Integer v78; + Integer v79; + Integer v80; + Integer v81; + Integer v82; + Integer v83; + Integer v84; + Integer v85; + Integer v86; + Integer v87; + Integer v88; + Integer v89; + Integer v90; + Integer v91; + Integer v92; + Integer v93; + Integer v94; + Integer v95; + Integer v96; + Integer v97; + Integer v98; + Integer v99; + Integer v100; + Integer v101; + Integer v102; + Integer v103; + Integer v104; + Integer v105; + Integer v106; + Integer v107; + Integer v108; + Integer v109; + Integer v110; + Integer v111; + Integer v112; + Integer v113; + Integer v114; + Integer v115; + Integer v116; + Integer v117; + Integer v118; + Integer v119; + Integer v120; + Integer v121; + Integer v122; + Integer v123; + Integer v124; + Integer v125; + Integer v126; + Integer v127; + Integer v128; + Integer v129; + Integer v130; + Integer v131; + Integer v132; + Integer v133; + Integer v134; + Integer v135; + Integer v136; + Integer v137; + Integer v138; + Integer v139; + Integer v140; + Integer v141; + Integer v142; + Integer v143; + Integer v144; + Integer v145; + Integer v146; + Integer v147; + Integer v148; + Integer v149; + Integer v150; + Integer v151; + Integer v152; + Integer v153; + Integer v154; + Integer v155; + Integer v156; + Integer v157; + Integer v158; + Integer v159; + Integer v160; + Integer v161; + Integer v162; + Integer v163; + Integer v164; + Integer v165; + Integer v166; + Integer v167; + Integer v168; + Integer v169; + Integer v170; + Integer v171; + Integer v172; + Integer v173; + Integer v174; + Integer v175; + Integer v176; + Integer v177; + Integer v178; + Integer v179; + Integer v180; + Integer v181; + Integer v182; + Integer v183; + Integer v184; + Integer v185; + Integer v186; + Integer v187; + Integer v188; + Integer v189; + Integer v190; + Integer v191; + Integer v192; + Integer v193; + Integer v194; + Integer v195; + Integer v196; + Integer v197; + Integer v198; + Integer v199; + Integer v200; + Integer v201; + Integer v202; + Integer v203; + Integer v204; + Integer v205; + Integer v206; + Integer v207; + Integer v208; + Integer v209; + Integer v210; + Integer v211; + Integer v212; + Integer v213; + Integer v214; + Integer v215; + Integer v216; + Integer v217; + Integer v218; + Integer v219; + Integer v220; + Integer v221; + Integer v222; + Integer v223; + Integer v224; + Integer v225; + Integer v226; + Integer v227; + Integer v228; + Integer v229; + Integer v230; + Integer v231; + Integer v232; + Integer v233; + Integer v234; + Integer v235; + Integer v236; + Integer v237; + Integer v238; + Integer v239; + Integer v240; + Integer v241; + Integer v242; + Integer v243; + Integer v244; + Integer v245; + Integer v246; + Integer v247; + Integer v248; + Integer v249; + Integer v250; + Integer v251; + Integer v252; + Integer v253; + Integer v254; + Integer v255; + Integer v256; + Integer v257; + Integer v258; + Integer v259; + Integer v260; + Integer v261; + Integer v262; + Integer v263; + Integer v264; + Integer v265; + Integer v266; + Integer v267; + Integer v268; + Integer v269; + Integer v270; + Integer v271; + Integer v272; + Integer v273; + Integer v274; + Integer v275; + Integer v276; + Integer v277; + Integer v278; + Integer v279; + Integer v280; + Integer v281; + Integer v282; + Integer v283; + Integer v284; + Integer v285; + Integer v286; + Integer v287; + Integer v288; + Integer v289; + Integer v290; + Integer v291; + Integer v292; + Integer v293; + Integer v294; + Integer v295; + Integer v296; + Integer v297; + Integer v298; + Integer v299; + Integer v300; + Integer v301; + Integer v302; + Integer v303; + Integer v304; + Integer v305; + Integer v306; + Integer v307; + Integer v308; + Integer v309; + Integer v310; + Integer v311; + Integer v312; + Integer v313; + Integer v314; + Integer v315; + Integer v316; + Integer v317; + Integer v318; + Integer v319; + Integer v320; + Integer v321; + Integer v322; + Integer v323; + Integer v324; + Integer v325; + Integer v326; + Integer v327; + Integer v328; + Integer v329; + Integer v330; + Integer v331; + Integer v332; + Integer v333; + Integer v334; + Integer v335; + Integer v336; + Integer v337; + Integer v338; + Integer v339; + Integer v340; + Integer v341; + Integer v342; + Integer v343; + Integer v344; + Integer v345; + Integer v346; + Integer v347; + Integer v348; + Integer v349; + Integer v350; + Integer v351; + Integer v352; + Integer v353; + Integer v354; + Integer v355; + Integer v356; + Integer v357; + Integer v358; + Integer v359; + Integer v360; + Integer v361; + Integer v362; + Integer v363; + Integer v364; + Integer v365; + Integer v366; + Integer v367; + Integer v368; + Integer v369; + Integer v370; + Integer v371; + Integer v372; + Integer v373; + Integer v374; + Integer v375; + Integer v376; + Integer v377; + Integer v378; + Integer v379; + Integer v380; + Integer v381; + Integer v382; + Integer v383; + Integer v384; + Integer v385; + Integer v386; + Integer v387; + Integer v388; + Integer v389; + Integer v390; + Integer v391; + Integer v392; + Integer v393; + Integer v394; + Integer v395; + Integer v396; + Integer v397; + Integer v398; + Integer v399; + Integer v400; + Integer v401; + Integer v402; + Integer v403; + Integer v404; + Integer v405; + Integer v406; + Integer v407; + Integer v408; + Integer v409; + Integer v410; + Integer v411; + Integer v412; + Integer v413; + Integer v414; + Integer v415; + Integer v416; + Integer v417; + Integer v418; + Integer v419; + Integer v420; + Integer v421; + Integer v422; + Integer v423; + Integer v424; + Integer v425; + Integer v426; + Integer v427; + Integer v428; + Integer v429; + Integer v430; + Integer v431; + Integer v432; + Integer v433; + Integer v434; + Integer v435; + Integer v436; + Integer v437; + Integer v438; + Integer v439; + Integer v440; + Integer v441; + Integer v442; + Integer v443; + Integer v444; + Integer v445; + Integer v446; + Integer v447; + Integer v448; + Integer v449; + Integer v450; + Integer v451; + Integer v452; + Integer v453; + Integer v454; + Integer v455; + Integer v456; + Integer v457; + Integer v458; + Integer v459; + Integer v460; + Integer v461; + Integer v462; + Integer v463; + Integer v464; + Integer v465; + Integer v466; + Integer v467; + Integer v468; + Integer v469; + Integer v470; + Integer v471; + Integer v472; + Integer v473; + Integer v474; + Integer v475; + Integer v476; + Integer v477; + Integer v478; + Integer v479; + Integer v480; + Integer v481; + Integer v482; + Integer v483; + Integer v484; + Integer v485; + Integer v486; + Integer v487; + Integer v488; + Integer v489; + Integer v490; + Integer v491; + Integer v492; + Integer v493; + Integer v494; + Integer v495; + Integer v496; + Integer v497; + Integer v498; + Integer v499; + Integer v500; + Integer v501; + Integer v502; + Integer v503; + Integer v504; + Integer v505; + Integer v506; + Integer v507; + Integer v508; + Integer v509; + Integer v510; + Integer v511; + Integer v512; + Integer v513; + Integer v514; + Integer v515; + Integer v516; + Integer v517; + Integer v518; + Integer v519; + Integer v520; + Integer v521; + Integer v522; + Integer v523; + Integer v524; + Integer v525; + Integer v526; + Integer v527; + Integer v528; + Integer v529; + Integer v530; + Integer v531; + Integer v532; + Integer v533; + Integer v534; + Integer v535; + Integer v536; + Integer v537; + Integer v538; + Integer v539; + Integer v540; + Integer v541; + Integer v542; + Integer v543; + Integer v544; + Integer v545; + Integer v546; + Integer v547; + Integer v548; + Integer v549; + Integer v550; + Integer v551; + Integer v552; + Integer v553; + Integer v554; + Integer v555; + Integer v556; + Integer v557; + Integer v558; + Integer v559; + Integer v560; + Integer v561; + Integer v562; + Integer v563; + Integer v564; + Integer v565; + Integer v566; + Integer v567; + Integer v568; + Integer v569; + Integer v570; + Integer v571; + Integer v572; + Integer v573; + Integer v574; + Integer v575; + Integer v576; + Integer v577; + Integer v578; + Integer v579; + Integer v580; + Integer v581; + Integer v582; + Integer v583; + Integer v584; + Integer v585; + Integer v586; + Integer v587; + Integer v588; + Integer v589; + Integer v590; + Integer v591; + Integer v592; + Integer v593; + Integer v594; + Integer v595; + Integer v596; + Integer v597; + Integer v598; + Integer v599; + Integer v600; + Integer v601; + Integer v602; + Integer v603; + Integer v604; + Integer v605; + Integer v606; + Integer v607; + Integer v608; + Integer v609; + Integer v610; + Integer v611; + Integer v612; + Integer v613; + Integer v614; + Integer v615; + Integer v616; + Integer v617; + Integer v618; + Integer v619; + Integer v620; + Integer v621; + Integer v622; + Integer v623; + Integer v624; + Integer v625; + Integer v626; + Integer v627; + Integer v628; + Integer v629; + Integer v630; + Integer v631; + Integer v632; + Integer v633; + Integer v634; + Integer v635; + Integer v636; + Integer v637; + Integer v638; + Integer v639; + Integer v640; + Integer v641; + Integer v642; + Integer v643; + Integer v644; + Integer v645; + Integer v646; + Integer v647; + Integer v648; + Integer v649; + Integer v650; + Integer v651; + Integer v652; + Integer v653; + Integer v654; + Integer v655; + Integer v656; + Integer v657; + Integer v658; + Integer v659; + Integer v660; + Integer v661; + Integer v662; + Integer v663; + Integer v664; + Integer v665; + Integer v666; + Integer v667; + Integer v668; + Integer v669; + Integer v670; + Integer v671; + Integer v672; + Integer v673; + Integer v674; + Integer v675; + Integer v676; + Integer v677; + Integer v678; + Integer v679; + Integer v680; + Integer v681; + Integer v682; + Integer v683; + Integer v684; + Integer v685; + Integer v686; + Integer v687; + Integer v688; + Integer v689; + Integer v690; + Integer v691; + Integer v692; + Integer v693; + Integer v694; + Integer v695; + Integer v696; + Integer v697; + Integer v698; + Integer v699; + Integer v700; + Integer v701; + Integer v702; + Integer v703; + Integer v704; + Integer v705; + Integer v706; + Integer v707; + Integer v708; + Integer v709; + Integer v710; + Integer v711; + Integer v712; + Integer v713; + Integer v714; + Integer v715; + Integer v716; + Integer v717; + Integer v718; + Integer v719; + Integer v720; + Integer v721; + Integer v722; + Integer v723; + Integer v724; + Integer v725; + Integer v726; + Integer v727; + Integer v728; + Integer v729; + Integer v730; + Integer v731; + Integer v732; + Integer v733; + Integer v734; + Integer v735; + Integer v736; + Integer v737; + Integer v738; + Integer v739; + Integer v740; + Integer v741; + Integer v742; + Integer v743; + Integer v744; + Integer v745; + Integer v746; + Integer v747; + Integer v748; + Integer v749; + Integer v750; + Integer v751; + Integer v752; + Integer v753; + Integer v754; + Integer v755; + Integer v756; + Integer v757; + Integer v758; + Integer v759; + Integer v760; + Integer v761; + Integer v762; + Integer v763; + Integer v764; + Integer v765; + Integer v766; + Integer v767; + Integer v768; + Integer v769; + Integer v770; + Integer v771; + Integer v772; + Integer v773; + Integer v774; + Integer v775; + Integer v776; + Integer v777; + Integer v778; + Integer v779; + Integer v780; + Integer v781; + Integer v782; + Integer v783; + Integer v784; + Integer v785; + Integer v786; + Integer v787; + Integer v788; + Integer v789; + Integer v790; + Integer v791; + Integer v792; + Integer v793; + Integer v794; + Integer v795; + Integer v796; + Integer v797; + Integer v798; + Integer v799; + Integer v800; + Integer v801; + Integer v802; + Integer v803; + Integer v804; + Integer v805; + Integer v806; + Integer v807; + Integer v808; + Integer v809; + Integer v810; + Integer v811; + Integer v812; + Integer v813; + Integer v814; + Integer v815; + Integer v816; + Integer v817; + Integer v818; + Integer v819; + Integer v820; + Integer v821; + Integer v822; + Integer v823; + Integer v824; + Integer v825; + Integer v826; + Integer v827; + Integer v828; + Integer v829; + Integer v830; + Integer v831; + Integer v832; + Integer v833; + Integer v834; + Integer v835; + Integer v836; + Integer v837; + Integer v838; + Integer v839; + Integer v840; + Integer v841; + Integer v842; + Integer v843; + Integer v844; + Integer v845; + Integer v846; + Integer v847; + Integer v848; + Integer v849; + Integer v850; + Integer v851; + Integer v852; + Integer v853; + Integer v854; + Integer v855; + Integer v856; + Integer v857; + Integer v858; + Integer v859; + Integer v860; + Integer v861; + Integer v862; + Integer v863; + Integer v864; + Integer v865; + Integer v866; + Integer v867; + Integer v868; + Integer v869; + Integer v870; + Integer v871; + Integer v872; + Integer v873; + Integer v874; + Integer v875; + Integer v876; + Integer v877; + Integer v878; + Integer v879; + Integer v880; + Integer v881; + Integer v882; + Integer v883; + Integer v884; + Integer v885; + Integer v886; + Integer v887; + Integer v888; + Integer v889; + Integer v890; + Integer v891; + Integer v892; + Integer v893; + Integer v894; + Integer v895; + Integer v896; + Integer v897; + Integer v898; + Integer v899; + Integer v900; + Integer v901; + Integer v902; + Integer v903; + Integer v904; + Integer v905; + Integer v906; + Integer v907; + Integer v908; + Integer v909; + Integer v910; + Integer v911; + Integer v912; + Integer v913; + Integer v914; + Integer v915; + Integer v916; + Integer v917; + Integer v918; + Integer v919; + Integer v920; + Integer v921; + Integer v922; + Integer v923; + Integer v924; + Integer v925; + Integer v926; + Integer v927; + Integer v928; + Integer v929; + Integer v930; + Integer v931; + Integer v932; + Integer v933; + Integer v934; + Integer v935; + Integer v936; + Integer v937; + Integer v938; + Integer v939; + Integer v940; + Integer v941; + Integer v942; + Integer v943; + Integer v944; + Integer v945; + Integer v946; + Integer v947; + Integer v948; + Integer v949; + Integer v950; + Integer v951; + Integer v952; + Integer v953; + Integer v954; + Integer v955; + Integer v956; + Integer v957; + Integer v958; + Integer v959; + Integer v960; + Integer v961; + Integer v962; + Integer v963; + Integer v964; + Integer v965; + Integer v966; + Integer v967; + Integer v968; + Integer v969; + Integer v970; + Integer v971; + Integer v972; + Integer v973; + Integer v974; + Integer v975; + Integer v976; + Integer v977; + Integer v978; + Integer v979; + Integer v980; + Integer v981; + Integer v982; + Integer v983; + Integer v984; + Integer v985; + Integer v986; + Integer v987; + Integer v988; + Integer v989; + Integer v990; + Integer v991; + Integer v992; + Integer v993; + Integer v994; + Integer v995; + Integer v996; + Integer v997; + Integer v998; + Integer v999; - Issue1438b() { - Map map = new HashMap<>(); - map.put("k0", v0); - map.put("k1", v1); - map.put("k2", v2); - map.put("k3", v3); - map.put("k4", v4); - map.put("k5", v5); - map.put("k6", v6); - map.put("k7", v7); - map.put("k8", v8); - map.put("k9", v9); - map.put("k10", v10); - map.put("k11", v11); - map.put("k12", v12); - map.put("k13", v13); - map.put("k14", v14); - map.put("k15", v15); - map.put("k16", v16); - map.put("k17", v17); - map.put("k18", v18); - map.put("k19", v19); - map.put("k20", v20); - map.put("k21", v21); - map.put("k22", v22); - map.put("k23", v23); - map.put("k24", v24); - map.put("k25", v25); - map.put("k26", v26); - map.put("k27", v27); - map.put("k28", v28); - map.put("k29", v29); - map.put("k30", v30); - map.put("k31", v31); - map.put("k32", v32); - map.put("k33", v33); - map.put("k34", v34); - map.put("k35", v35); - map.put("k36", v36); - map.put("k37", v37); - map.put("k38", v38); - map.put("k39", v39); - map.put("k40", v40); - map.put("k41", v41); - map.put("k42", v42); - map.put("k43", v43); - map.put("k44", v44); - map.put("k45", v45); - map.put("k46", v46); - map.put("k47", v47); - map.put("k48", v48); - map.put("k49", v49); - map.put("k50", v50); - map.put("k51", v51); - map.put("k52", v52); - map.put("k53", v53); - map.put("k54", v54); - map.put("k55", v55); - map.put("k56", v56); - map.put("k57", v57); - map.put("k58", v58); - map.put("k59", v59); - map.put("k60", v60); - map.put("k61", v61); - map.put("k62", v62); - map.put("k63", v63); - map.put("k64", v64); - map.put("k65", v65); - map.put("k66", v66); - map.put("k67", v67); - map.put("k68", v68); - map.put("k69", v69); - map.put("k70", v70); - map.put("k71", v71); - map.put("k72", v72); - map.put("k73", v73); - map.put("k74", v74); - map.put("k75", v75); - map.put("k76", v76); - map.put("k77", v77); - map.put("k78", v78); - map.put("k79", v79); - map.put("k80", v80); - map.put("k81", v81); - map.put("k82", v82); - map.put("k83", v83); - map.put("k84", v84); - map.put("k85", v85); - map.put("k86", v86); - map.put("k87", v87); - map.put("k88", v88); - map.put("k89", v89); - map.put("k90", v90); - map.put("k91", v91); - map.put("k92", v92); - map.put("k93", v93); - map.put("k94", v94); - map.put("k95", v95); - map.put("k96", v96); - map.put("k97", v97); - map.put("k98", v98); - map.put("k99", v99); - map.put("k100", v100); - map.put("k101", v101); - map.put("k102", v102); - map.put("k103", v103); - map.put("k104", v104); - map.put("k105", v105); - map.put("k106", v106); - map.put("k107", v107); - map.put("k108", v108); - map.put("k109", v109); - map.put("k110", v110); - map.put("k111", v111); - map.put("k112", v112); - map.put("k113", v113); - map.put("k114", v114); - map.put("k115", v115); - map.put("k116", v116); - map.put("k117", v117); - map.put("k118", v118); - map.put("k119", v119); - map.put("k120", v120); - map.put("k121", v121); - map.put("k122", v122); - map.put("k123", v123); - map.put("k124", v124); - map.put("k125", v125); - map.put("k126", v126); - map.put("k127", v127); - map.put("k128", v128); - map.put("k129", v129); - map.put("k130", v130); - map.put("k131", v131); - map.put("k132", v132); - map.put("k133", v133); - map.put("k134", v134); - map.put("k135", v135); - map.put("k136", v136); - map.put("k137", v137); - map.put("k138", v138); - map.put("k139", v139); - map.put("k140", v140); - map.put("k141", v141); - map.put("k142", v142); - map.put("k143", v143); - map.put("k144", v144); - map.put("k145", v145); - map.put("k146", v146); - map.put("k147", v147); - map.put("k148", v148); - map.put("k149", v149); - map.put("k150", v150); - map.put("k151", v151); - map.put("k152", v152); - map.put("k153", v153); - map.put("k154", v154); - map.put("k155", v155); - map.put("k156", v156); - map.put("k157", v157); - map.put("k158", v158); - map.put("k159", v159); - map.put("k160", v160); - map.put("k161", v161); - map.put("k162", v162); - map.put("k163", v163); - map.put("k164", v164); - map.put("k165", v165); - map.put("k166", v166); - map.put("k167", v167); - map.put("k168", v168); - map.put("k169", v169); - map.put("k170", v170); - map.put("k171", v171); - map.put("k172", v172); - map.put("k173", v173); - map.put("k174", v174); - map.put("k175", v175); - map.put("k176", v176); - map.put("k177", v177); - map.put("k178", v178); - map.put("k179", v179); - map.put("k180", v180); - map.put("k181", v181); - map.put("k182", v182); - map.put("k183", v183); - map.put("k184", v184); - map.put("k185", v185); - map.put("k186", v186); - map.put("k187", v187); - map.put("k188", v188); - map.put("k189", v189); - map.put("k190", v190); - map.put("k191", v191); - map.put("k192", v192); - map.put("k193", v193); - map.put("k194", v194); - map.put("k195", v195); - map.put("k196", v196); - map.put("k197", v197); - map.put("k198", v198); - map.put("k199", v199); - map.put("k200", v200); - map.put("k201", v201); - map.put("k202", v202); - map.put("k203", v203); - map.put("k204", v204); - map.put("k205", v205); - map.put("k206", v206); - map.put("k207", v207); - map.put("k208", v208); - map.put("k209", v209); - map.put("k210", v210); - map.put("k211", v211); - map.put("k212", v212); - map.put("k213", v213); - map.put("k214", v214); - map.put("k215", v215); - map.put("k216", v216); - map.put("k217", v217); - map.put("k218", v218); - map.put("k219", v219); - map.put("k220", v220); - map.put("k221", v221); - map.put("k222", v222); - map.put("k223", v223); - map.put("k224", v224); - map.put("k225", v225); - map.put("k226", v226); - map.put("k227", v227); - map.put("k228", v228); - map.put("k229", v229); - map.put("k230", v230); - map.put("k231", v231); - map.put("k232", v232); - map.put("k233", v233); - map.put("k234", v234); - map.put("k235", v235); - map.put("k236", v236); - map.put("k237", v237); - map.put("k238", v238); - map.put("k239", v239); - map.put("k240", v240); - map.put("k241", v241); - map.put("k242", v242); - map.put("k243", v243); - map.put("k244", v244); - map.put("k245", v245); - map.put("k246", v246); - map.put("k247", v247); - map.put("k248", v248); - map.put("k249", v249); - map.put("k250", v250); - map.put("k251", v251); - map.put("k252", v252); - map.put("k253", v253); - map.put("k254", v254); - map.put("k255", v255); - map.put("k256", v256); - map.put("k257", v257); - map.put("k258", v258); - map.put("k259", v259); - map.put("k260", v260); - map.put("k261", v261); - map.put("k262", v262); - map.put("k263", v263); - map.put("k264", v264); - map.put("k265", v265); - map.put("k266", v266); - map.put("k267", v267); - map.put("k268", v268); - map.put("k269", v269); - map.put("k270", v270); - map.put("k271", v271); - map.put("k272", v272); - map.put("k273", v273); - map.put("k274", v274); - map.put("k275", v275); - map.put("k276", v276); - map.put("k277", v277); - map.put("k278", v278); - map.put("k279", v279); - map.put("k280", v280); - map.put("k281", v281); - map.put("k282", v282); - map.put("k283", v283); - map.put("k284", v284); - map.put("k285", v285); - map.put("k286", v286); - map.put("k287", v287); - map.put("k288", v288); - map.put("k289", v289); - map.put("k290", v290); - map.put("k291", v291); - map.put("k292", v292); - map.put("k293", v293); - map.put("k294", v294); - map.put("k295", v295); - map.put("k296", v296); - map.put("k297", v297); - map.put("k298", v298); - map.put("k299", v299); - map.put("k300", v300); - map.put("k301", v301); - map.put("k302", v302); - map.put("k303", v303); - map.put("k304", v304); - map.put("k305", v305); - map.put("k306", v306); - map.put("k307", v307); - map.put("k308", v308); - map.put("k309", v309); - map.put("k310", v310); - map.put("k311", v311); - map.put("k312", v312); - map.put("k313", v313); - map.put("k314", v314); - map.put("k315", v315); - map.put("k316", v316); - map.put("k317", v317); - map.put("k318", v318); - map.put("k319", v319); - map.put("k320", v320); - map.put("k321", v321); - map.put("k322", v322); - map.put("k323", v323); - map.put("k324", v324); - map.put("k325", v325); - map.put("k326", v326); - map.put("k327", v327); - map.put("k328", v328); - map.put("k329", v329); - map.put("k330", v330); - map.put("k331", v331); - map.put("k332", v332); - map.put("k333", v333); - map.put("k334", v334); - map.put("k335", v335); - map.put("k336", v336); - map.put("k337", v337); - map.put("k338", v338); - map.put("k339", v339); - map.put("k340", v340); - map.put("k341", v341); - map.put("k342", v342); - map.put("k343", v343); - map.put("k344", v344); - map.put("k345", v345); - map.put("k346", v346); - map.put("k347", v347); - map.put("k348", v348); - map.put("k349", v349); - map.put("k350", v350); - map.put("k351", v351); - map.put("k352", v352); - map.put("k353", v353); - map.put("k354", v354); - map.put("k355", v355); - map.put("k356", v356); - map.put("k357", v357); - map.put("k358", v358); - map.put("k359", v359); - map.put("k360", v360); - map.put("k361", v361); - map.put("k362", v362); - map.put("k363", v363); - map.put("k364", v364); - map.put("k365", v365); - map.put("k366", v366); - map.put("k367", v367); - map.put("k368", v368); - map.put("k369", v369); - map.put("k370", v370); - map.put("k371", v371); - map.put("k372", v372); - map.put("k373", v373); - map.put("k374", v374); - map.put("k375", v375); - map.put("k376", v376); - map.put("k377", v377); - map.put("k378", v378); - map.put("k379", v379); - map.put("k380", v380); - map.put("k381", v381); - map.put("k382", v382); - map.put("k383", v383); - map.put("k384", v384); - map.put("k385", v385); - map.put("k386", v386); - map.put("k387", v387); - map.put("k388", v388); - map.put("k389", v389); - map.put("k390", v390); - map.put("k391", v391); - map.put("k392", v392); - map.put("k393", v393); - map.put("k394", v394); - map.put("k395", v395); - map.put("k396", v396); - map.put("k397", v397); - map.put("k398", v398); - map.put("k399", v399); - map.put("k400", v400); - map.put("k401", v401); - map.put("k402", v402); - map.put("k403", v403); - map.put("k404", v404); - map.put("k405", v405); - map.put("k406", v406); - map.put("k407", v407); - map.put("k408", v408); - map.put("k409", v409); - map.put("k410", v410); - map.put("k411", v411); - map.put("k412", v412); - map.put("k413", v413); - map.put("k414", v414); - map.put("k415", v415); - map.put("k416", v416); - map.put("k417", v417); - map.put("k418", v418); - map.put("k419", v419); - map.put("k420", v420); - map.put("k421", v421); - map.put("k422", v422); - map.put("k423", v423); - map.put("k424", v424); - map.put("k425", v425); - map.put("k426", v426); - map.put("k427", v427); - map.put("k428", v428); - map.put("k429", v429); - map.put("k430", v430); - map.put("k431", v431); - map.put("k432", v432); - map.put("k433", v433); - map.put("k434", v434); - map.put("k435", v435); - map.put("k436", v436); - map.put("k437", v437); - map.put("k438", v438); - map.put("k439", v439); - map.put("k440", v440); - map.put("k441", v441); - map.put("k442", v442); - map.put("k443", v443); - map.put("k444", v444); - map.put("k445", v445); - map.put("k446", v446); - map.put("k447", v447); - map.put("k448", v448); - map.put("k449", v449); - map.put("k450", v450); - map.put("k451", v451); - map.put("k452", v452); - map.put("k453", v453); - map.put("k454", v454); - map.put("k455", v455); - map.put("k456", v456); - map.put("k457", v457); - map.put("k458", v458); - map.put("k459", v459); - map.put("k460", v460); - map.put("k461", v461); - map.put("k462", v462); - map.put("k463", v463); - map.put("k464", v464); - map.put("k465", v465); - map.put("k466", v466); - map.put("k467", v467); - map.put("k468", v468); - map.put("k469", v469); - map.put("k470", v470); - map.put("k471", v471); - map.put("k472", v472); - map.put("k473", v473); - map.put("k474", v474); - map.put("k475", v475); - map.put("k476", v476); - map.put("k477", v477); - map.put("k478", v478); - map.put("k479", v479); - map.put("k480", v480); - map.put("k481", v481); - map.put("k482", v482); - map.put("k483", v483); - map.put("k484", v484); - map.put("k485", v485); - map.put("k486", v486); - map.put("k487", v487); - map.put("k488", v488); - map.put("k489", v489); - map.put("k490", v490); - map.put("k491", v491); - map.put("k492", v492); - map.put("k493", v493); - map.put("k494", v494); - map.put("k495", v495); - map.put("k496", v496); - map.put("k497", v497); - map.put("k498", v498); - map.put("k499", v499); - map.put("k500", v500); - map.put("k501", v501); - map.put("k502", v502); - map.put("k503", v503); - map.put("k504", v504); - map.put("k505", v505); - map.put("k506", v506); - map.put("k507", v507); - map.put("k508", v508); - map.put("k509", v509); - map.put("k510", v510); - map.put("k511", v511); - map.put("k512", v512); - map.put("k513", v513); - map.put("k514", v514); - map.put("k515", v515); - map.put("k516", v516); - map.put("k517", v517); - map.put("k518", v518); - map.put("k519", v519); - map.put("k520", v520); - map.put("k521", v521); - map.put("k522", v522); - map.put("k523", v523); - map.put("k524", v524); - map.put("k525", v525); - map.put("k526", v526); - map.put("k527", v527); - map.put("k528", v528); - map.put("k529", v529); - map.put("k530", v530); - map.put("k531", v531); - map.put("k532", v532); - map.put("k533", v533); - map.put("k534", v534); - map.put("k535", v535); - map.put("k536", v536); - map.put("k537", v537); - map.put("k538", v538); - map.put("k539", v539); - map.put("k540", v540); - map.put("k541", v541); - map.put("k542", v542); - map.put("k543", v543); - map.put("k544", v544); - map.put("k545", v545); - map.put("k546", v546); - map.put("k547", v547); - map.put("k548", v548); - map.put("k549", v549); - map.put("k550", v550); - map.put("k551", v551); - map.put("k552", v552); - map.put("k553", v553); - map.put("k554", v554); - map.put("k555", v555); - map.put("k556", v556); - map.put("k557", v557); - map.put("k558", v558); - map.put("k559", v559); - map.put("k560", v560); - map.put("k561", v561); - map.put("k562", v562); - map.put("k563", v563); - map.put("k564", v564); - map.put("k565", v565); - map.put("k566", v566); - map.put("k567", v567); - map.put("k568", v568); - map.put("k569", v569); - map.put("k570", v570); - map.put("k571", v571); - map.put("k572", v572); - map.put("k573", v573); - map.put("k574", v574); - map.put("k575", v575); - map.put("k576", v576); - map.put("k577", v577); - map.put("k578", v578); - map.put("k579", v579); - map.put("k580", v580); - map.put("k581", v581); - map.put("k582", v582); - map.put("k583", v583); - map.put("k584", v584); - map.put("k585", v585); - map.put("k586", v586); - map.put("k587", v587); - map.put("k588", v588); - map.put("k589", v589); - map.put("k590", v590); - map.put("k591", v591); - map.put("k592", v592); - map.put("k593", v593); - map.put("k594", v594); - map.put("k595", v595); - map.put("k596", v596); - map.put("k597", v597); - map.put("k598", v598); - map.put("k599", v599); - map.put("k600", v600); - map.put("k601", v601); - map.put("k602", v602); - map.put("k603", v603); - map.put("k604", v604); - map.put("k605", v605); - map.put("k606", v606); - map.put("k607", v607); - map.put("k608", v608); - map.put("k609", v609); - map.put("k610", v610); - map.put("k611", v611); - map.put("k612", v612); - map.put("k613", v613); - map.put("k614", v614); - map.put("k615", v615); - map.put("k616", v616); - map.put("k617", v617); - map.put("k618", v618); - map.put("k619", v619); - map.put("k620", v620); - map.put("k621", v621); - map.put("k622", v622); - map.put("k623", v623); - map.put("k624", v624); - map.put("k625", v625); - map.put("k626", v626); - map.put("k627", v627); - map.put("k628", v628); - map.put("k629", v629); - map.put("k630", v630); - map.put("k631", v631); - map.put("k632", v632); - map.put("k633", v633); - map.put("k634", v634); - map.put("k635", v635); - map.put("k636", v636); - map.put("k637", v637); - map.put("k638", v638); - map.put("k639", v639); - map.put("k640", v640); - map.put("k641", v641); - map.put("k642", v642); - map.put("k643", v643); - map.put("k644", v644); - map.put("k645", v645); - map.put("k646", v646); - map.put("k647", v647); - map.put("k648", v648); - map.put("k649", v649); - map.put("k650", v650); - map.put("k651", v651); - map.put("k652", v652); - map.put("k653", v653); - map.put("k654", v654); - map.put("k655", v655); - map.put("k656", v656); - map.put("k657", v657); - map.put("k658", v658); - map.put("k659", v659); - map.put("k660", v660); - map.put("k661", v661); - map.put("k662", v662); - map.put("k663", v663); - map.put("k664", v664); - map.put("k665", v665); - map.put("k666", v666); - map.put("k667", v667); - map.put("k668", v668); - map.put("k669", v669); - map.put("k670", v670); - map.put("k671", v671); - map.put("k672", v672); - map.put("k673", v673); - map.put("k674", v674); - map.put("k675", v675); - map.put("k676", v676); - map.put("k677", v677); - map.put("k678", v678); - map.put("k679", v679); - map.put("k680", v680); - map.put("k681", v681); - map.put("k682", v682); - map.put("k683", v683); - map.put("k684", v684); - map.put("k685", v685); - map.put("k686", v686); - map.put("k687", v687); - map.put("k688", v688); - map.put("k689", v689); - map.put("k690", v690); - map.put("k691", v691); - map.put("k692", v692); - map.put("k693", v693); - map.put("k694", v694); - map.put("k695", v695); - map.put("k696", v696); - map.put("k697", v697); - map.put("k698", v698); - map.put("k699", v699); - map.put("k700", v700); - map.put("k701", v701); - map.put("k702", v702); - map.put("k703", v703); - map.put("k704", v704); - map.put("k705", v705); - map.put("k706", v706); - map.put("k707", v707); - map.put("k708", v708); - map.put("k709", v709); - map.put("k710", v710); - map.put("k711", v711); - map.put("k712", v712); - map.put("k713", v713); - map.put("k714", v714); - map.put("k715", v715); - map.put("k716", v716); - map.put("k717", v717); - map.put("k718", v718); - map.put("k719", v719); - map.put("k720", v720); - map.put("k721", v721); - map.put("k722", v722); - map.put("k723", v723); - map.put("k724", v724); - map.put("k725", v725); - map.put("k726", v726); - map.put("k727", v727); - map.put("k728", v728); - map.put("k729", v729); - map.put("k730", v730); - map.put("k731", v731); - map.put("k732", v732); - map.put("k733", v733); - map.put("k734", v734); - map.put("k735", v735); - map.put("k736", v736); - map.put("k737", v737); - map.put("k738", v738); - map.put("k739", v739); - map.put("k740", v740); - map.put("k741", v741); - map.put("k742", v742); - map.put("k743", v743); - map.put("k744", v744); - map.put("k745", v745); - map.put("k746", v746); - map.put("k747", v747); - map.put("k748", v748); - map.put("k749", v749); - map.put("k750", v750); - map.put("k751", v751); - map.put("k752", v752); - map.put("k753", v753); - map.put("k754", v754); - map.put("k755", v755); - map.put("k756", v756); - map.put("k757", v757); - map.put("k758", v758); - map.put("k759", v759); - map.put("k760", v760); - map.put("k761", v761); - map.put("k762", v762); - map.put("k763", v763); - map.put("k764", v764); - map.put("k765", v765); - map.put("k766", v766); - map.put("k767", v767); - map.put("k768", v768); - map.put("k769", v769); - map.put("k770", v770); - map.put("k771", v771); - map.put("k772", v772); - map.put("k773", v773); - map.put("k774", v774); - map.put("k775", v775); - map.put("k776", v776); - map.put("k777", v777); - map.put("k778", v778); - map.put("k779", v779); - map.put("k780", v780); - map.put("k781", v781); - map.put("k782", v782); - map.put("k783", v783); - map.put("k784", v784); - map.put("k785", v785); - map.put("k786", v786); - map.put("k787", v787); - map.put("k788", v788); - map.put("k789", v789); - map.put("k790", v790); - map.put("k791", v791); - map.put("k792", v792); - map.put("k793", v793); - map.put("k794", v794); - map.put("k795", v795); - map.put("k796", v796); - map.put("k797", v797); - map.put("k798", v798); - map.put("k799", v799); - map.put("k800", v800); - map.put("k801", v801); - map.put("k802", v802); - map.put("k803", v803); - map.put("k804", v804); - map.put("k805", v805); - map.put("k806", v806); - map.put("k807", v807); - map.put("k808", v808); - map.put("k809", v809); - map.put("k810", v810); - map.put("k811", v811); - map.put("k812", v812); - map.put("k813", v813); - map.put("k814", v814); - map.put("k815", v815); - map.put("k816", v816); - map.put("k817", v817); - map.put("k818", v818); - map.put("k819", v819); - map.put("k820", v820); - map.put("k821", v821); - map.put("k822", v822); - map.put("k823", v823); - map.put("k824", v824); - map.put("k825", v825); - map.put("k826", v826); - map.put("k827", v827); - map.put("k828", v828); - map.put("k829", v829); - map.put("k830", v830); - map.put("k831", v831); - map.put("k832", v832); - map.put("k833", v833); - map.put("k834", v834); - map.put("k835", v835); - map.put("k836", v836); - map.put("k837", v837); - map.put("k838", v838); - map.put("k839", v839); - map.put("k840", v840); - map.put("k841", v841); - map.put("k842", v842); - map.put("k843", v843); - map.put("k844", v844); - map.put("k845", v845); - map.put("k846", v846); - map.put("k847", v847); - map.put("k848", v848); - map.put("k849", v849); - map.put("k850", v850); - map.put("k851", v851); - map.put("k852", v852); - map.put("k853", v853); - map.put("k854", v854); - map.put("k855", v855); - map.put("k856", v856); - map.put("k857", v857); - map.put("k858", v858); - map.put("k859", v859); - map.put("k860", v860); - map.put("k861", v861); - map.put("k862", v862); - map.put("k863", v863); - map.put("k864", v864); - map.put("k865", v865); - map.put("k866", v866); - map.put("k867", v867); - map.put("k868", v868); - map.put("k869", v869); - map.put("k870", v870); - map.put("k871", v871); - map.put("k872", v872); - map.put("k873", v873); - map.put("k874", v874); - map.put("k875", v875); - map.put("k876", v876); - map.put("k877", v877); - map.put("k878", v878); - map.put("k879", v879); - map.put("k880", v880); - map.put("k881", v881); - map.put("k882", v882); - map.put("k883", v883); - map.put("k884", v884); - map.put("k885", v885); - map.put("k886", v886); - map.put("k887", v887); - map.put("k888", v888); - map.put("k889", v889); - map.put("k890", v890); - map.put("k891", v891); - map.put("k892", v892); - map.put("k893", v893); - map.put("k894", v894); - map.put("k895", v895); - map.put("k896", v896); - map.put("k897", v897); - map.put("k898", v898); - map.put("k899", v899); - map.put("k900", v900); - map.put("k901", v901); - map.put("k902", v902); - map.put("k903", v903); - map.put("k904", v904); - map.put("k905", v905); - map.put("k906", v906); - map.put("k907", v907); - map.put("k908", v908); - map.put("k909", v909); - map.put("k910", v910); - map.put("k911", v911); - map.put("k912", v912); - map.put("k913", v913); - map.put("k914", v914); - map.put("k915", v915); - map.put("k916", v916); - map.put("k917", v917); - map.put("k918", v918); - map.put("k919", v919); - map.put("k920", v920); - map.put("k921", v921); - map.put("k922", v922); - map.put("k923", v923); - map.put("k924", v924); - map.put("k925", v925); - map.put("k926", v926); - map.put("k927", v927); - map.put("k928", v928); - map.put("k929", v929); - map.put("k930", v930); - map.put("k931", v931); - map.put("k932", v932); - map.put("k933", v933); - map.put("k934", v934); - map.put("k935", v935); - map.put("k936", v936); - map.put("k937", v937); - map.put("k938", v938); - map.put("k939", v939); - map.put("k940", v940); - map.put("k941", v941); - map.put("k942", v942); - map.put("k943", v943); - map.put("k944", v944); - map.put("k945", v945); - map.put("k946", v946); - map.put("k947", v947); - map.put("k948", v948); - map.put("k949", v949); - map.put("k950", v950); - map.put("k951", v951); - map.put("k952", v952); - map.put("k953", v953); - map.put("k954", v954); - map.put("k955", v955); - map.put("k956", v956); - map.put("k957", v957); - map.put("k958", v958); - map.put("k959", v959); - map.put("k960", v960); - map.put("k961", v961); - map.put("k962", v962); - map.put("k963", v963); - map.put("k964", v964); - map.put("k965", v965); - map.put("k966", v966); - map.put("k967", v967); - map.put("k968", v968); - map.put("k969", v969); - map.put("k970", v970); - map.put("k971", v971); - map.put("k972", v972); - map.put("k973", v973); - map.put("k974", v974); - map.put("k975", v975); - map.put("k976", v976); - map.put("k977", v977); - map.put("k978", v978); - map.put("k979", v979); - map.put("k980", v980); - map.put("k981", v981); - map.put("k982", v982); - map.put("k983", v983); - map.put("k984", v984); - map.put("k985", v985); - map.put("k986", v986); - map.put("k987", v987); - map.put("k988", v988); - map.put("k989", v989); - map.put("k990", v990); - map.put("k991", v991); - map.put("k992", v992); - map.put("k993", v993); - map.put("k994", v994); - map.put("k995", v995); - map.put("k996", v996); - map.put("k997", v997); - map.put("k998", v998); - map.put("k999", v999); - } + Issue1438b() { + Map map = new HashMap<>(); + map.put("k0", v0); + map.put("k1", v1); + map.put("k2", v2); + map.put("k3", v3); + map.put("k4", v4); + map.put("k5", v5); + map.put("k6", v6); + map.put("k7", v7); + map.put("k8", v8); + map.put("k9", v9); + map.put("k10", v10); + map.put("k11", v11); + map.put("k12", v12); + map.put("k13", v13); + map.put("k14", v14); + map.put("k15", v15); + map.put("k16", v16); + map.put("k17", v17); + map.put("k18", v18); + map.put("k19", v19); + map.put("k20", v20); + map.put("k21", v21); + map.put("k22", v22); + map.put("k23", v23); + map.put("k24", v24); + map.put("k25", v25); + map.put("k26", v26); + map.put("k27", v27); + map.put("k28", v28); + map.put("k29", v29); + map.put("k30", v30); + map.put("k31", v31); + map.put("k32", v32); + map.put("k33", v33); + map.put("k34", v34); + map.put("k35", v35); + map.put("k36", v36); + map.put("k37", v37); + map.put("k38", v38); + map.put("k39", v39); + map.put("k40", v40); + map.put("k41", v41); + map.put("k42", v42); + map.put("k43", v43); + map.put("k44", v44); + map.put("k45", v45); + map.put("k46", v46); + map.put("k47", v47); + map.put("k48", v48); + map.put("k49", v49); + map.put("k50", v50); + map.put("k51", v51); + map.put("k52", v52); + map.put("k53", v53); + map.put("k54", v54); + map.put("k55", v55); + map.put("k56", v56); + map.put("k57", v57); + map.put("k58", v58); + map.put("k59", v59); + map.put("k60", v60); + map.put("k61", v61); + map.put("k62", v62); + map.put("k63", v63); + map.put("k64", v64); + map.put("k65", v65); + map.put("k66", v66); + map.put("k67", v67); + map.put("k68", v68); + map.put("k69", v69); + map.put("k70", v70); + map.put("k71", v71); + map.put("k72", v72); + map.put("k73", v73); + map.put("k74", v74); + map.put("k75", v75); + map.put("k76", v76); + map.put("k77", v77); + map.put("k78", v78); + map.put("k79", v79); + map.put("k80", v80); + map.put("k81", v81); + map.put("k82", v82); + map.put("k83", v83); + map.put("k84", v84); + map.put("k85", v85); + map.put("k86", v86); + map.put("k87", v87); + map.put("k88", v88); + map.put("k89", v89); + map.put("k90", v90); + map.put("k91", v91); + map.put("k92", v92); + map.put("k93", v93); + map.put("k94", v94); + map.put("k95", v95); + map.put("k96", v96); + map.put("k97", v97); + map.put("k98", v98); + map.put("k99", v99); + map.put("k100", v100); + map.put("k101", v101); + map.put("k102", v102); + map.put("k103", v103); + map.put("k104", v104); + map.put("k105", v105); + map.put("k106", v106); + map.put("k107", v107); + map.put("k108", v108); + map.put("k109", v109); + map.put("k110", v110); + map.put("k111", v111); + map.put("k112", v112); + map.put("k113", v113); + map.put("k114", v114); + map.put("k115", v115); + map.put("k116", v116); + map.put("k117", v117); + map.put("k118", v118); + map.put("k119", v119); + map.put("k120", v120); + map.put("k121", v121); + map.put("k122", v122); + map.put("k123", v123); + map.put("k124", v124); + map.put("k125", v125); + map.put("k126", v126); + map.put("k127", v127); + map.put("k128", v128); + map.put("k129", v129); + map.put("k130", v130); + map.put("k131", v131); + map.put("k132", v132); + map.put("k133", v133); + map.put("k134", v134); + map.put("k135", v135); + map.put("k136", v136); + map.put("k137", v137); + map.put("k138", v138); + map.put("k139", v139); + map.put("k140", v140); + map.put("k141", v141); + map.put("k142", v142); + map.put("k143", v143); + map.put("k144", v144); + map.put("k145", v145); + map.put("k146", v146); + map.put("k147", v147); + map.put("k148", v148); + map.put("k149", v149); + map.put("k150", v150); + map.put("k151", v151); + map.put("k152", v152); + map.put("k153", v153); + map.put("k154", v154); + map.put("k155", v155); + map.put("k156", v156); + map.put("k157", v157); + map.put("k158", v158); + map.put("k159", v159); + map.put("k160", v160); + map.put("k161", v161); + map.put("k162", v162); + map.put("k163", v163); + map.put("k164", v164); + map.put("k165", v165); + map.put("k166", v166); + map.put("k167", v167); + map.put("k168", v168); + map.put("k169", v169); + map.put("k170", v170); + map.put("k171", v171); + map.put("k172", v172); + map.put("k173", v173); + map.put("k174", v174); + map.put("k175", v175); + map.put("k176", v176); + map.put("k177", v177); + map.put("k178", v178); + map.put("k179", v179); + map.put("k180", v180); + map.put("k181", v181); + map.put("k182", v182); + map.put("k183", v183); + map.put("k184", v184); + map.put("k185", v185); + map.put("k186", v186); + map.put("k187", v187); + map.put("k188", v188); + map.put("k189", v189); + map.put("k190", v190); + map.put("k191", v191); + map.put("k192", v192); + map.put("k193", v193); + map.put("k194", v194); + map.put("k195", v195); + map.put("k196", v196); + map.put("k197", v197); + map.put("k198", v198); + map.put("k199", v199); + map.put("k200", v200); + map.put("k201", v201); + map.put("k202", v202); + map.put("k203", v203); + map.put("k204", v204); + map.put("k205", v205); + map.put("k206", v206); + map.put("k207", v207); + map.put("k208", v208); + map.put("k209", v209); + map.put("k210", v210); + map.put("k211", v211); + map.put("k212", v212); + map.put("k213", v213); + map.put("k214", v214); + map.put("k215", v215); + map.put("k216", v216); + map.put("k217", v217); + map.put("k218", v218); + map.put("k219", v219); + map.put("k220", v220); + map.put("k221", v221); + map.put("k222", v222); + map.put("k223", v223); + map.put("k224", v224); + map.put("k225", v225); + map.put("k226", v226); + map.put("k227", v227); + map.put("k228", v228); + map.put("k229", v229); + map.put("k230", v230); + map.put("k231", v231); + map.put("k232", v232); + map.put("k233", v233); + map.put("k234", v234); + map.put("k235", v235); + map.put("k236", v236); + map.put("k237", v237); + map.put("k238", v238); + map.put("k239", v239); + map.put("k240", v240); + map.put("k241", v241); + map.put("k242", v242); + map.put("k243", v243); + map.put("k244", v244); + map.put("k245", v245); + map.put("k246", v246); + map.put("k247", v247); + map.put("k248", v248); + map.put("k249", v249); + map.put("k250", v250); + map.put("k251", v251); + map.put("k252", v252); + map.put("k253", v253); + map.put("k254", v254); + map.put("k255", v255); + map.put("k256", v256); + map.put("k257", v257); + map.put("k258", v258); + map.put("k259", v259); + map.put("k260", v260); + map.put("k261", v261); + map.put("k262", v262); + map.put("k263", v263); + map.put("k264", v264); + map.put("k265", v265); + map.put("k266", v266); + map.put("k267", v267); + map.put("k268", v268); + map.put("k269", v269); + map.put("k270", v270); + map.put("k271", v271); + map.put("k272", v272); + map.put("k273", v273); + map.put("k274", v274); + map.put("k275", v275); + map.put("k276", v276); + map.put("k277", v277); + map.put("k278", v278); + map.put("k279", v279); + map.put("k280", v280); + map.put("k281", v281); + map.put("k282", v282); + map.put("k283", v283); + map.put("k284", v284); + map.put("k285", v285); + map.put("k286", v286); + map.put("k287", v287); + map.put("k288", v288); + map.put("k289", v289); + map.put("k290", v290); + map.put("k291", v291); + map.put("k292", v292); + map.put("k293", v293); + map.put("k294", v294); + map.put("k295", v295); + map.put("k296", v296); + map.put("k297", v297); + map.put("k298", v298); + map.put("k299", v299); + map.put("k300", v300); + map.put("k301", v301); + map.put("k302", v302); + map.put("k303", v303); + map.put("k304", v304); + map.put("k305", v305); + map.put("k306", v306); + map.put("k307", v307); + map.put("k308", v308); + map.put("k309", v309); + map.put("k310", v310); + map.put("k311", v311); + map.put("k312", v312); + map.put("k313", v313); + map.put("k314", v314); + map.put("k315", v315); + map.put("k316", v316); + map.put("k317", v317); + map.put("k318", v318); + map.put("k319", v319); + map.put("k320", v320); + map.put("k321", v321); + map.put("k322", v322); + map.put("k323", v323); + map.put("k324", v324); + map.put("k325", v325); + map.put("k326", v326); + map.put("k327", v327); + map.put("k328", v328); + map.put("k329", v329); + map.put("k330", v330); + map.put("k331", v331); + map.put("k332", v332); + map.put("k333", v333); + map.put("k334", v334); + map.put("k335", v335); + map.put("k336", v336); + map.put("k337", v337); + map.put("k338", v338); + map.put("k339", v339); + map.put("k340", v340); + map.put("k341", v341); + map.put("k342", v342); + map.put("k343", v343); + map.put("k344", v344); + map.put("k345", v345); + map.put("k346", v346); + map.put("k347", v347); + map.put("k348", v348); + map.put("k349", v349); + map.put("k350", v350); + map.put("k351", v351); + map.put("k352", v352); + map.put("k353", v353); + map.put("k354", v354); + map.put("k355", v355); + map.put("k356", v356); + map.put("k357", v357); + map.put("k358", v358); + map.put("k359", v359); + map.put("k360", v360); + map.put("k361", v361); + map.put("k362", v362); + map.put("k363", v363); + map.put("k364", v364); + map.put("k365", v365); + map.put("k366", v366); + map.put("k367", v367); + map.put("k368", v368); + map.put("k369", v369); + map.put("k370", v370); + map.put("k371", v371); + map.put("k372", v372); + map.put("k373", v373); + map.put("k374", v374); + map.put("k375", v375); + map.put("k376", v376); + map.put("k377", v377); + map.put("k378", v378); + map.put("k379", v379); + map.put("k380", v380); + map.put("k381", v381); + map.put("k382", v382); + map.put("k383", v383); + map.put("k384", v384); + map.put("k385", v385); + map.put("k386", v386); + map.put("k387", v387); + map.put("k388", v388); + map.put("k389", v389); + map.put("k390", v390); + map.put("k391", v391); + map.put("k392", v392); + map.put("k393", v393); + map.put("k394", v394); + map.put("k395", v395); + map.put("k396", v396); + map.put("k397", v397); + map.put("k398", v398); + map.put("k399", v399); + map.put("k400", v400); + map.put("k401", v401); + map.put("k402", v402); + map.put("k403", v403); + map.put("k404", v404); + map.put("k405", v405); + map.put("k406", v406); + map.put("k407", v407); + map.put("k408", v408); + map.put("k409", v409); + map.put("k410", v410); + map.put("k411", v411); + map.put("k412", v412); + map.put("k413", v413); + map.put("k414", v414); + map.put("k415", v415); + map.put("k416", v416); + map.put("k417", v417); + map.put("k418", v418); + map.put("k419", v419); + map.put("k420", v420); + map.put("k421", v421); + map.put("k422", v422); + map.put("k423", v423); + map.put("k424", v424); + map.put("k425", v425); + map.put("k426", v426); + map.put("k427", v427); + map.put("k428", v428); + map.put("k429", v429); + map.put("k430", v430); + map.put("k431", v431); + map.put("k432", v432); + map.put("k433", v433); + map.put("k434", v434); + map.put("k435", v435); + map.put("k436", v436); + map.put("k437", v437); + map.put("k438", v438); + map.put("k439", v439); + map.put("k440", v440); + map.put("k441", v441); + map.put("k442", v442); + map.put("k443", v443); + map.put("k444", v444); + map.put("k445", v445); + map.put("k446", v446); + map.put("k447", v447); + map.put("k448", v448); + map.put("k449", v449); + map.put("k450", v450); + map.put("k451", v451); + map.put("k452", v452); + map.put("k453", v453); + map.put("k454", v454); + map.put("k455", v455); + map.put("k456", v456); + map.put("k457", v457); + map.put("k458", v458); + map.put("k459", v459); + map.put("k460", v460); + map.put("k461", v461); + map.put("k462", v462); + map.put("k463", v463); + map.put("k464", v464); + map.put("k465", v465); + map.put("k466", v466); + map.put("k467", v467); + map.put("k468", v468); + map.put("k469", v469); + map.put("k470", v470); + map.put("k471", v471); + map.put("k472", v472); + map.put("k473", v473); + map.put("k474", v474); + map.put("k475", v475); + map.put("k476", v476); + map.put("k477", v477); + map.put("k478", v478); + map.put("k479", v479); + map.put("k480", v480); + map.put("k481", v481); + map.put("k482", v482); + map.put("k483", v483); + map.put("k484", v484); + map.put("k485", v485); + map.put("k486", v486); + map.put("k487", v487); + map.put("k488", v488); + map.put("k489", v489); + map.put("k490", v490); + map.put("k491", v491); + map.put("k492", v492); + map.put("k493", v493); + map.put("k494", v494); + map.put("k495", v495); + map.put("k496", v496); + map.put("k497", v497); + map.put("k498", v498); + map.put("k499", v499); + map.put("k500", v500); + map.put("k501", v501); + map.put("k502", v502); + map.put("k503", v503); + map.put("k504", v504); + map.put("k505", v505); + map.put("k506", v506); + map.put("k507", v507); + map.put("k508", v508); + map.put("k509", v509); + map.put("k510", v510); + map.put("k511", v511); + map.put("k512", v512); + map.put("k513", v513); + map.put("k514", v514); + map.put("k515", v515); + map.put("k516", v516); + map.put("k517", v517); + map.put("k518", v518); + map.put("k519", v519); + map.put("k520", v520); + map.put("k521", v521); + map.put("k522", v522); + map.put("k523", v523); + map.put("k524", v524); + map.put("k525", v525); + map.put("k526", v526); + map.put("k527", v527); + map.put("k528", v528); + map.put("k529", v529); + map.put("k530", v530); + map.put("k531", v531); + map.put("k532", v532); + map.put("k533", v533); + map.put("k534", v534); + map.put("k535", v535); + map.put("k536", v536); + map.put("k537", v537); + map.put("k538", v538); + map.put("k539", v539); + map.put("k540", v540); + map.put("k541", v541); + map.put("k542", v542); + map.put("k543", v543); + map.put("k544", v544); + map.put("k545", v545); + map.put("k546", v546); + map.put("k547", v547); + map.put("k548", v548); + map.put("k549", v549); + map.put("k550", v550); + map.put("k551", v551); + map.put("k552", v552); + map.put("k553", v553); + map.put("k554", v554); + map.put("k555", v555); + map.put("k556", v556); + map.put("k557", v557); + map.put("k558", v558); + map.put("k559", v559); + map.put("k560", v560); + map.put("k561", v561); + map.put("k562", v562); + map.put("k563", v563); + map.put("k564", v564); + map.put("k565", v565); + map.put("k566", v566); + map.put("k567", v567); + map.put("k568", v568); + map.put("k569", v569); + map.put("k570", v570); + map.put("k571", v571); + map.put("k572", v572); + map.put("k573", v573); + map.put("k574", v574); + map.put("k575", v575); + map.put("k576", v576); + map.put("k577", v577); + map.put("k578", v578); + map.put("k579", v579); + map.put("k580", v580); + map.put("k581", v581); + map.put("k582", v582); + map.put("k583", v583); + map.put("k584", v584); + map.put("k585", v585); + map.put("k586", v586); + map.put("k587", v587); + map.put("k588", v588); + map.put("k589", v589); + map.put("k590", v590); + map.put("k591", v591); + map.put("k592", v592); + map.put("k593", v593); + map.put("k594", v594); + map.put("k595", v595); + map.put("k596", v596); + map.put("k597", v597); + map.put("k598", v598); + map.put("k599", v599); + map.put("k600", v600); + map.put("k601", v601); + map.put("k602", v602); + map.put("k603", v603); + map.put("k604", v604); + map.put("k605", v605); + map.put("k606", v606); + map.put("k607", v607); + map.put("k608", v608); + map.put("k609", v609); + map.put("k610", v610); + map.put("k611", v611); + map.put("k612", v612); + map.put("k613", v613); + map.put("k614", v614); + map.put("k615", v615); + map.put("k616", v616); + map.put("k617", v617); + map.put("k618", v618); + map.put("k619", v619); + map.put("k620", v620); + map.put("k621", v621); + map.put("k622", v622); + map.put("k623", v623); + map.put("k624", v624); + map.put("k625", v625); + map.put("k626", v626); + map.put("k627", v627); + map.put("k628", v628); + map.put("k629", v629); + map.put("k630", v630); + map.put("k631", v631); + map.put("k632", v632); + map.put("k633", v633); + map.put("k634", v634); + map.put("k635", v635); + map.put("k636", v636); + map.put("k637", v637); + map.put("k638", v638); + map.put("k639", v639); + map.put("k640", v640); + map.put("k641", v641); + map.put("k642", v642); + map.put("k643", v643); + map.put("k644", v644); + map.put("k645", v645); + map.put("k646", v646); + map.put("k647", v647); + map.put("k648", v648); + map.put("k649", v649); + map.put("k650", v650); + map.put("k651", v651); + map.put("k652", v652); + map.put("k653", v653); + map.put("k654", v654); + map.put("k655", v655); + map.put("k656", v656); + map.put("k657", v657); + map.put("k658", v658); + map.put("k659", v659); + map.put("k660", v660); + map.put("k661", v661); + map.put("k662", v662); + map.put("k663", v663); + map.put("k664", v664); + map.put("k665", v665); + map.put("k666", v666); + map.put("k667", v667); + map.put("k668", v668); + map.put("k669", v669); + map.put("k670", v670); + map.put("k671", v671); + map.put("k672", v672); + map.put("k673", v673); + map.put("k674", v674); + map.put("k675", v675); + map.put("k676", v676); + map.put("k677", v677); + map.put("k678", v678); + map.put("k679", v679); + map.put("k680", v680); + map.put("k681", v681); + map.put("k682", v682); + map.put("k683", v683); + map.put("k684", v684); + map.put("k685", v685); + map.put("k686", v686); + map.put("k687", v687); + map.put("k688", v688); + map.put("k689", v689); + map.put("k690", v690); + map.put("k691", v691); + map.put("k692", v692); + map.put("k693", v693); + map.put("k694", v694); + map.put("k695", v695); + map.put("k696", v696); + map.put("k697", v697); + map.put("k698", v698); + map.put("k699", v699); + map.put("k700", v700); + map.put("k701", v701); + map.put("k702", v702); + map.put("k703", v703); + map.put("k704", v704); + map.put("k705", v705); + map.put("k706", v706); + map.put("k707", v707); + map.put("k708", v708); + map.put("k709", v709); + map.put("k710", v710); + map.put("k711", v711); + map.put("k712", v712); + map.put("k713", v713); + map.put("k714", v714); + map.put("k715", v715); + map.put("k716", v716); + map.put("k717", v717); + map.put("k718", v718); + map.put("k719", v719); + map.put("k720", v720); + map.put("k721", v721); + map.put("k722", v722); + map.put("k723", v723); + map.put("k724", v724); + map.put("k725", v725); + map.put("k726", v726); + map.put("k727", v727); + map.put("k728", v728); + map.put("k729", v729); + map.put("k730", v730); + map.put("k731", v731); + map.put("k732", v732); + map.put("k733", v733); + map.put("k734", v734); + map.put("k735", v735); + map.put("k736", v736); + map.put("k737", v737); + map.put("k738", v738); + map.put("k739", v739); + map.put("k740", v740); + map.put("k741", v741); + map.put("k742", v742); + map.put("k743", v743); + map.put("k744", v744); + map.put("k745", v745); + map.put("k746", v746); + map.put("k747", v747); + map.put("k748", v748); + map.put("k749", v749); + map.put("k750", v750); + map.put("k751", v751); + map.put("k752", v752); + map.put("k753", v753); + map.put("k754", v754); + map.put("k755", v755); + map.put("k756", v756); + map.put("k757", v757); + map.put("k758", v758); + map.put("k759", v759); + map.put("k760", v760); + map.put("k761", v761); + map.put("k762", v762); + map.put("k763", v763); + map.put("k764", v764); + map.put("k765", v765); + map.put("k766", v766); + map.put("k767", v767); + map.put("k768", v768); + map.put("k769", v769); + map.put("k770", v770); + map.put("k771", v771); + map.put("k772", v772); + map.put("k773", v773); + map.put("k774", v774); + map.put("k775", v775); + map.put("k776", v776); + map.put("k777", v777); + map.put("k778", v778); + map.put("k779", v779); + map.put("k780", v780); + map.put("k781", v781); + map.put("k782", v782); + map.put("k783", v783); + map.put("k784", v784); + map.put("k785", v785); + map.put("k786", v786); + map.put("k787", v787); + map.put("k788", v788); + map.put("k789", v789); + map.put("k790", v790); + map.put("k791", v791); + map.put("k792", v792); + map.put("k793", v793); + map.put("k794", v794); + map.put("k795", v795); + map.put("k796", v796); + map.put("k797", v797); + map.put("k798", v798); + map.put("k799", v799); + map.put("k800", v800); + map.put("k801", v801); + map.put("k802", v802); + map.put("k803", v803); + map.put("k804", v804); + map.put("k805", v805); + map.put("k806", v806); + map.put("k807", v807); + map.put("k808", v808); + map.put("k809", v809); + map.put("k810", v810); + map.put("k811", v811); + map.put("k812", v812); + map.put("k813", v813); + map.put("k814", v814); + map.put("k815", v815); + map.put("k816", v816); + map.put("k817", v817); + map.put("k818", v818); + map.put("k819", v819); + map.put("k820", v820); + map.put("k821", v821); + map.put("k822", v822); + map.put("k823", v823); + map.put("k824", v824); + map.put("k825", v825); + map.put("k826", v826); + map.put("k827", v827); + map.put("k828", v828); + map.put("k829", v829); + map.put("k830", v830); + map.put("k831", v831); + map.put("k832", v832); + map.put("k833", v833); + map.put("k834", v834); + map.put("k835", v835); + map.put("k836", v836); + map.put("k837", v837); + map.put("k838", v838); + map.put("k839", v839); + map.put("k840", v840); + map.put("k841", v841); + map.put("k842", v842); + map.put("k843", v843); + map.put("k844", v844); + map.put("k845", v845); + map.put("k846", v846); + map.put("k847", v847); + map.put("k848", v848); + map.put("k849", v849); + map.put("k850", v850); + map.put("k851", v851); + map.put("k852", v852); + map.put("k853", v853); + map.put("k854", v854); + map.put("k855", v855); + map.put("k856", v856); + map.put("k857", v857); + map.put("k858", v858); + map.put("k859", v859); + map.put("k860", v860); + map.put("k861", v861); + map.put("k862", v862); + map.put("k863", v863); + map.put("k864", v864); + map.put("k865", v865); + map.put("k866", v866); + map.put("k867", v867); + map.put("k868", v868); + map.put("k869", v869); + map.put("k870", v870); + map.put("k871", v871); + map.put("k872", v872); + map.put("k873", v873); + map.put("k874", v874); + map.put("k875", v875); + map.put("k876", v876); + map.put("k877", v877); + map.put("k878", v878); + map.put("k879", v879); + map.put("k880", v880); + map.put("k881", v881); + map.put("k882", v882); + map.put("k883", v883); + map.put("k884", v884); + map.put("k885", v885); + map.put("k886", v886); + map.put("k887", v887); + map.put("k888", v888); + map.put("k889", v889); + map.put("k890", v890); + map.put("k891", v891); + map.put("k892", v892); + map.put("k893", v893); + map.put("k894", v894); + map.put("k895", v895); + map.put("k896", v896); + map.put("k897", v897); + map.put("k898", v898); + map.put("k899", v899); + map.put("k900", v900); + map.put("k901", v901); + map.put("k902", v902); + map.put("k903", v903); + map.put("k904", v904); + map.put("k905", v905); + map.put("k906", v906); + map.put("k907", v907); + map.put("k908", v908); + map.put("k909", v909); + map.put("k910", v910); + map.put("k911", v911); + map.put("k912", v912); + map.put("k913", v913); + map.put("k914", v914); + map.put("k915", v915); + map.put("k916", v916); + map.put("k917", v917); + map.put("k918", v918); + map.put("k919", v919); + map.put("k920", v920); + map.put("k921", v921); + map.put("k922", v922); + map.put("k923", v923); + map.put("k924", v924); + map.put("k925", v925); + map.put("k926", v926); + map.put("k927", v927); + map.put("k928", v928); + map.put("k929", v929); + map.put("k930", v930); + map.put("k931", v931); + map.put("k932", v932); + map.put("k933", v933); + map.put("k934", v934); + map.put("k935", v935); + map.put("k936", v936); + map.put("k937", v937); + map.put("k938", v938); + map.put("k939", v939); + map.put("k940", v940); + map.put("k941", v941); + map.put("k942", v942); + map.put("k943", v943); + map.put("k944", v944); + map.put("k945", v945); + map.put("k946", v946); + map.put("k947", v947); + map.put("k948", v948); + map.put("k949", v949); + map.put("k950", v950); + map.put("k951", v951); + map.put("k952", v952); + map.put("k953", v953); + map.put("k954", v954); + map.put("k955", v955); + map.put("k956", v956); + map.put("k957", v957); + map.put("k958", v958); + map.put("k959", v959); + map.put("k960", v960); + map.put("k961", v961); + map.put("k962", v962); + map.put("k963", v963); + map.put("k964", v964); + map.put("k965", v965); + map.put("k966", v966); + map.put("k967", v967); + map.put("k968", v968); + map.put("k969", v969); + map.put("k970", v970); + map.put("k971", v971); + map.put("k972", v972); + map.put("k973", v973); + map.put("k974", v974); + map.put("k975", v975); + map.put("k976", v976); + map.put("k977", v977); + map.put("k978", v978); + map.put("k979", v979); + map.put("k980", v980); + map.put("k981", v981); + map.put("k982", v982); + map.put("k983", v983); + map.put("k984", v984); + map.put("k985", v985); + map.put("k986", v986); + map.put("k987", v987); + map.put("k988", v988); + map.put("k989", v989); + map.put("k990", v990); + map.put("k991", v991); + map.put("k992", v992); + map.put("k993", v993); + map.put("k994", v994); + map.put("k995", v995); + map.put("k996", v996); + map.put("k997", v997); + map.put("k998", v998); + map.put("k999", v999); + } } diff --git a/checker/jtreg/nullness/Issue1438c.java b/checker/jtreg/nullness/Issue1438c.java index 07b379d3198..8ac5ad793fb 100644 --- a/checker/jtreg/nullness/Issue1438c.java +++ b/checker/jtreg/nullness/Issue1438c.java @@ -9,2010 +9,2010 @@ import java.util.Map; public class Issue1438c { - // Do not initialize these variables. The Nullness Checker is supposed to issue an error about - // uninitialized fields. - Integer v0; - Integer v1; - Integer v2; - Integer v3; - Integer v4; - Integer v5; - Integer v6; - Integer v7; - Integer v8; - Integer v9; - Integer v10; - Integer v11; - Integer v12; - Integer v13; - Integer v14; - Integer v15; - Integer v16; - Integer v17; - Integer v18; - Integer v19; - Integer v20; - Integer v21; - Integer v22; - Integer v23; - Integer v24; - Integer v25; - Integer v26; - Integer v27; - Integer v28; - Integer v29; - Integer v30; - Integer v31; - Integer v32; - Integer v33; - Integer v34; - Integer v35; - Integer v36; - Integer v37; - Integer v38; - Integer v39; - Integer v40; - Integer v41; - Integer v42; - Integer v43; - Integer v44; - Integer v45; - Integer v46; - Integer v47; - Integer v48; - Integer v49; - Integer v50; - Integer v51; - Integer v52; - Integer v53; - Integer v54; - Integer v55; - Integer v56; - Integer v57; - Integer v58; - Integer v59; - Integer v60; - Integer v61; - Integer v62; - Integer v63; - Integer v64; - Integer v65; - Integer v66; - Integer v67; - Integer v68; - Integer v69; - Integer v70; - Integer v71; - Integer v72; - Integer v73; - Integer v74; - Integer v75; - Integer v76; - Integer v77; - Integer v78; - Integer v79; - Integer v80; - Integer v81; - Integer v82; - Integer v83; - Integer v84; - Integer v85; - Integer v86; - Integer v87; - Integer v88; - Integer v89; - Integer v90; - Integer v91; - Integer v92; - Integer v93; - Integer v94; - Integer v95; - Integer v96; - Integer v97; - Integer v98; - Integer v99; - Integer v100; - Integer v101; - Integer v102; - Integer v103; - Integer v104; - Integer v105; - Integer v106; - Integer v107; - Integer v108; - Integer v109; - Integer v110; - Integer v111; - Integer v112; - Integer v113; - Integer v114; - Integer v115; - Integer v116; - Integer v117; - Integer v118; - Integer v119; - Integer v120; - Integer v121; - Integer v122; - Integer v123; - Integer v124; - Integer v125; - Integer v126; - Integer v127; - Integer v128; - Integer v129; - Integer v130; - Integer v131; - Integer v132; - Integer v133; - Integer v134; - Integer v135; - Integer v136; - Integer v137; - Integer v138; - Integer v139; - Integer v140; - Integer v141; - Integer v142; - Integer v143; - Integer v144; - Integer v145; - Integer v146; - Integer v147; - Integer v148; - Integer v149; - Integer v150; - Integer v151; - Integer v152; - Integer v153; - Integer v154; - Integer v155; - Integer v156; - Integer v157; - Integer v158; - Integer v159; - Integer v160; - Integer v161; - Integer v162; - Integer v163; - Integer v164; - Integer v165; - Integer v166; - Integer v167; - Integer v168; - Integer v169; - Integer v170; - Integer v171; - Integer v172; - Integer v173; - Integer v174; - Integer v175; - Integer v176; - Integer v177; - Integer v178; - Integer v179; - Integer v180; - Integer v181; - Integer v182; - Integer v183; - Integer v184; - Integer v185; - Integer v186; - Integer v187; - Integer v188; - Integer v189; - Integer v190; - Integer v191; - Integer v192; - Integer v193; - Integer v194; - Integer v195; - Integer v196; - Integer v197; - Integer v198; - Integer v199; - Integer v200; - Integer v201; - Integer v202; - Integer v203; - Integer v204; - Integer v205; - Integer v206; - Integer v207; - Integer v208; - Integer v209; - Integer v210; - Integer v211; - Integer v212; - Integer v213; - Integer v214; - Integer v215; - Integer v216; - Integer v217; - Integer v218; - Integer v219; - Integer v220; - Integer v221; - Integer v222; - Integer v223; - Integer v224; - Integer v225; - Integer v226; - Integer v227; - Integer v228; - Integer v229; - Integer v230; - Integer v231; - Integer v232; - Integer v233; - Integer v234; - Integer v235; - Integer v236; - Integer v237; - Integer v238; - Integer v239; - Integer v240; - Integer v241; - Integer v242; - Integer v243; - Integer v244; - Integer v245; - Integer v246; - Integer v247; - Integer v248; - Integer v249; - Integer v250; - Integer v251; - Integer v252; - Integer v253; - Integer v254; - Integer v255; - Integer v256; - Integer v257; - Integer v258; - Integer v259; - Integer v260; - Integer v261; - Integer v262; - Integer v263; - Integer v264; - Integer v265; - Integer v266; - Integer v267; - Integer v268; - Integer v269; - Integer v270; - Integer v271; - Integer v272; - Integer v273; - Integer v274; - Integer v275; - Integer v276; - Integer v277; - Integer v278; - Integer v279; - Integer v280; - Integer v281; - Integer v282; - Integer v283; - Integer v284; - Integer v285; - Integer v286; - Integer v287; - Integer v288; - Integer v289; - Integer v290; - Integer v291; - Integer v292; - Integer v293; - Integer v294; - Integer v295; - Integer v296; - Integer v297; - Integer v298; - Integer v299; - Integer v300; - Integer v301; - Integer v302; - Integer v303; - Integer v304; - Integer v305; - Integer v306; - Integer v307; - Integer v308; - Integer v309; - Integer v310; - Integer v311; - Integer v312; - Integer v313; - Integer v314; - Integer v315; - Integer v316; - Integer v317; - Integer v318; - Integer v319; - Integer v320; - Integer v321; - Integer v322; - Integer v323; - Integer v324; - Integer v325; - Integer v326; - Integer v327; - Integer v328; - Integer v329; - Integer v330; - Integer v331; - Integer v332; - Integer v333; - Integer v334; - Integer v335; - Integer v336; - Integer v337; - Integer v338; - Integer v339; - Integer v340; - Integer v341; - Integer v342; - Integer v343; - Integer v344; - Integer v345; - Integer v346; - Integer v347; - Integer v348; - Integer v349; - Integer v350; - Integer v351; - Integer v352; - Integer v353; - Integer v354; - Integer v355; - Integer v356; - Integer v357; - Integer v358; - Integer v359; - Integer v360; - Integer v361; - Integer v362; - Integer v363; - Integer v364; - Integer v365; - Integer v366; - Integer v367; - Integer v368; - Integer v369; - Integer v370; - Integer v371; - Integer v372; - Integer v373; - Integer v374; - Integer v375; - Integer v376; - Integer v377; - Integer v378; - Integer v379; - Integer v380; - Integer v381; - Integer v382; - Integer v383; - Integer v384; - Integer v385; - Integer v386; - Integer v387; - Integer v388; - Integer v389; - Integer v390; - Integer v391; - Integer v392; - Integer v393; - Integer v394; - Integer v395; - Integer v396; - Integer v397; - Integer v398; - Integer v399; - Integer v400; - Integer v401; - Integer v402; - Integer v403; - Integer v404; - Integer v405; - Integer v406; - Integer v407; - Integer v408; - Integer v409; - Integer v410; - Integer v411; - Integer v412; - Integer v413; - Integer v414; - Integer v415; - Integer v416; - Integer v417; - Integer v418; - Integer v419; - Integer v420; - Integer v421; - Integer v422; - Integer v423; - Integer v424; - Integer v425; - Integer v426; - Integer v427; - Integer v428; - Integer v429; - Integer v430; - Integer v431; - Integer v432; - Integer v433; - Integer v434; - Integer v435; - Integer v436; - Integer v437; - Integer v438; - Integer v439; - Integer v440; - Integer v441; - Integer v442; - Integer v443; - Integer v444; - Integer v445; - Integer v446; - Integer v447; - Integer v448; - Integer v449; - Integer v450; - Integer v451; - Integer v452; - Integer v453; - Integer v454; - Integer v455; - Integer v456; - Integer v457; - Integer v458; - Integer v459; - Integer v460; - Integer v461; - Integer v462; - Integer v463; - Integer v464; - Integer v465; - Integer v466; - Integer v467; - Integer v468; - Integer v469; - Integer v470; - Integer v471; - Integer v472; - Integer v473; - Integer v474; - Integer v475; - Integer v476; - Integer v477; - Integer v478; - Integer v479; - Integer v480; - Integer v481; - Integer v482; - Integer v483; - Integer v484; - Integer v485; - Integer v486; - Integer v487; - Integer v488; - Integer v489; - Integer v490; - Integer v491; - Integer v492; - Integer v493; - Integer v494; - Integer v495; - Integer v496; - Integer v497; - Integer v498; - Integer v499; - Integer v500; - Integer v501; - Integer v502; - Integer v503; - Integer v504; - Integer v505; - Integer v506; - Integer v507; - Integer v508; - Integer v509; - Integer v510; - Integer v511; - Integer v512; - Integer v513; - Integer v514; - Integer v515; - Integer v516; - Integer v517; - Integer v518; - Integer v519; - Integer v520; - Integer v521; - Integer v522; - Integer v523; - Integer v524; - Integer v525; - Integer v526; - Integer v527; - Integer v528; - Integer v529; - Integer v530; - Integer v531; - Integer v532; - Integer v533; - Integer v534; - Integer v535; - Integer v536; - Integer v537; - Integer v538; - Integer v539; - Integer v540; - Integer v541; - Integer v542; - Integer v543; - Integer v544; - Integer v545; - Integer v546; - Integer v547; - Integer v548; - Integer v549; - Integer v550; - Integer v551; - Integer v552; - Integer v553; - Integer v554; - Integer v555; - Integer v556; - Integer v557; - Integer v558; - Integer v559; - Integer v560; - Integer v561; - Integer v562; - Integer v563; - Integer v564; - Integer v565; - Integer v566; - Integer v567; - Integer v568; - Integer v569; - Integer v570; - Integer v571; - Integer v572; - Integer v573; - Integer v574; - Integer v575; - Integer v576; - Integer v577; - Integer v578; - Integer v579; - Integer v580; - Integer v581; - Integer v582; - Integer v583; - Integer v584; - Integer v585; - Integer v586; - Integer v587; - Integer v588; - Integer v589; - Integer v590; - Integer v591; - Integer v592; - Integer v593; - Integer v594; - Integer v595; - Integer v596; - Integer v597; - Integer v598; - Integer v599; - Integer v600; - Integer v601; - Integer v602; - Integer v603; - Integer v604; - Integer v605; - Integer v606; - Integer v607; - Integer v608; - Integer v609; - Integer v610; - Integer v611; - Integer v612; - Integer v613; - Integer v614; - Integer v615; - Integer v616; - Integer v617; - Integer v618; - Integer v619; - Integer v620; - Integer v621; - Integer v622; - Integer v623; - Integer v624; - Integer v625; - Integer v626; - Integer v627; - Integer v628; - Integer v629; - Integer v630; - Integer v631; - Integer v632; - Integer v633; - Integer v634; - Integer v635; - Integer v636; - Integer v637; - Integer v638; - Integer v639; - Integer v640; - Integer v641; - Integer v642; - Integer v643; - Integer v644; - Integer v645; - Integer v646; - Integer v647; - Integer v648; - Integer v649; - Integer v650; - Integer v651; - Integer v652; - Integer v653; - Integer v654; - Integer v655; - Integer v656; - Integer v657; - Integer v658; - Integer v659; - Integer v660; - Integer v661; - Integer v662; - Integer v663; - Integer v664; - Integer v665; - Integer v666; - Integer v667; - Integer v668; - Integer v669; - Integer v670; - Integer v671; - Integer v672; - Integer v673; - Integer v674; - Integer v675; - Integer v676; - Integer v677; - Integer v678; - Integer v679; - Integer v680; - Integer v681; - Integer v682; - Integer v683; - Integer v684; - Integer v685; - Integer v686; - Integer v687; - Integer v688; - Integer v689; - Integer v690; - Integer v691; - Integer v692; - Integer v693; - Integer v694; - Integer v695; - Integer v696; - Integer v697; - Integer v698; - Integer v699; - Integer v700; - Integer v701; - Integer v702; - Integer v703; - Integer v704; - Integer v705; - Integer v706; - Integer v707; - Integer v708; - Integer v709; - Integer v710; - Integer v711; - Integer v712; - Integer v713; - Integer v714; - Integer v715; - Integer v716; - Integer v717; - Integer v718; - Integer v719; - Integer v720; - Integer v721; - Integer v722; - Integer v723; - Integer v724; - Integer v725; - Integer v726; - Integer v727; - Integer v728; - Integer v729; - Integer v730; - Integer v731; - Integer v732; - Integer v733; - Integer v734; - Integer v735; - Integer v736; - Integer v737; - Integer v738; - Integer v739; - Integer v740; - Integer v741; - Integer v742; - Integer v743; - Integer v744; - Integer v745; - Integer v746; - Integer v747; - Integer v748; - Integer v749; - Integer v750; - Integer v751; - Integer v752; - Integer v753; - Integer v754; - Integer v755; - Integer v756; - Integer v757; - Integer v758; - Integer v759; - Integer v760; - Integer v761; - Integer v762; - Integer v763; - Integer v764; - Integer v765; - Integer v766; - Integer v767; - Integer v768; - Integer v769; - Integer v770; - Integer v771; - Integer v772; - Integer v773; - Integer v774; - Integer v775; - Integer v776; - Integer v777; - Integer v778; - Integer v779; - Integer v780; - Integer v781; - Integer v782; - Integer v783; - Integer v784; - Integer v785; - Integer v786; - Integer v787; - Integer v788; - Integer v789; - Integer v790; - Integer v791; - Integer v792; - Integer v793; - Integer v794; - Integer v795; - Integer v796; - Integer v797; - Integer v798; - Integer v799; - Integer v800; - Integer v801; - Integer v802; - Integer v803; - Integer v804; - Integer v805; - Integer v806; - Integer v807; - Integer v808; - Integer v809; - Integer v810; - Integer v811; - Integer v812; - Integer v813; - Integer v814; - Integer v815; - Integer v816; - Integer v817; - Integer v818; - Integer v819; - Integer v820; - Integer v821; - Integer v822; - Integer v823; - Integer v824; - Integer v825; - Integer v826; - Integer v827; - Integer v828; - Integer v829; - Integer v830; - Integer v831; - Integer v832; - Integer v833; - Integer v834; - Integer v835; - Integer v836; - Integer v837; - Integer v838; - Integer v839; - Integer v840; - Integer v841; - Integer v842; - Integer v843; - Integer v844; - Integer v845; - Integer v846; - Integer v847; - Integer v848; - Integer v849; - Integer v850; - Integer v851; - Integer v852; - Integer v853; - Integer v854; - Integer v855; - Integer v856; - Integer v857; - Integer v858; - Integer v859; - Integer v860; - Integer v861; - Integer v862; - Integer v863; - Integer v864; - Integer v865; - Integer v866; - Integer v867; - Integer v868; - Integer v869; - Integer v870; - Integer v871; - Integer v872; - Integer v873; - Integer v874; - Integer v875; - Integer v876; - Integer v877; - Integer v878; - Integer v879; - Integer v880; - Integer v881; - Integer v882; - Integer v883; - Integer v884; - Integer v885; - Integer v886; - Integer v887; - Integer v888; - Integer v889; - Integer v890; - Integer v891; - Integer v892; - Integer v893; - Integer v894; - Integer v895; - Integer v896; - Integer v897; - Integer v898; - Integer v899; - Integer v900; - Integer v901; - Integer v902; - Integer v903; - Integer v904; - Integer v905; - Integer v906; - Integer v907; - Integer v908; - Integer v909; - Integer v910; - Integer v911; - Integer v912; - Integer v913; - Integer v914; - Integer v915; - Integer v916; - Integer v917; - Integer v918; - Integer v919; - Integer v920; - Integer v921; - Integer v922; - Integer v923; - Integer v924; - Integer v925; - Integer v926; - Integer v927; - Integer v928; - Integer v929; - Integer v930; - Integer v931; - Integer v932; - Integer v933; - Integer v934; - Integer v935; - Integer v936; - Integer v937; - Integer v938; - Integer v939; - Integer v940; - Integer v941; - Integer v942; - Integer v943; - Integer v944; - Integer v945; - Integer v946; - Integer v947; - Integer v948; - Integer v949; - Integer v950; - Integer v951; - Integer v952; - Integer v953; - Integer v954; - Integer v955; - Integer v956; - Integer v957; - Integer v958; - Integer v959; - Integer v960; - Integer v961; - Integer v962; - Integer v963; - Integer v964; - Integer v965; - Integer v966; - Integer v967; - Integer v968; - Integer v969; - Integer v970; - Integer v971; - Integer v972; - Integer v973; - Integer v974; - Integer v975; - Integer v976; - Integer v977; - Integer v978; - Integer v979; - Integer v980; - Integer v981; - Integer v982; - Integer v983; - Integer v984; - Integer v985; - Integer v986; - Integer v987; - Integer v988; - Integer v989; - Integer v990; - Integer v991; - Integer v992; - Integer v993; - Integer v994; - Integer v995; - Integer v996; - Integer v997; - Integer v998; - Integer v999; + // Do not initialize these variables. The Nullness Checker is supposed to issue an error about + // uninitialized fields. + Integer v0; + Integer v1; + Integer v2; + Integer v3; + Integer v4; + Integer v5; + Integer v6; + Integer v7; + Integer v8; + Integer v9; + Integer v10; + Integer v11; + Integer v12; + Integer v13; + Integer v14; + Integer v15; + Integer v16; + Integer v17; + Integer v18; + Integer v19; + Integer v20; + Integer v21; + Integer v22; + Integer v23; + Integer v24; + Integer v25; + Integer v26; + Integer v27; + Integer v28; + Integer v29; + Integer v30; + Integer v31; + Integer v32; + Integer v33; + Integer v34; + Integer v35; + Integer v36; + Integer v37; + Integer v38; + Integer v39; + Integer v40; + Integer v41; + Integer v42; + Integer v43; + Integer v44; + Integer v45; + Integer v46; + Integer v47; + Integer v48; + Integer v49; + Integer v50; + Integer v51; + Integer v52; + Integer v53; + Integer v54; + Integer v55; + Integer v56; + Integer v57; + Integer v58; + Integer v59; + Integer v60; + Integer v61; + Integer v62; + Integer v63; + Integer v64; + Integer v65; + Integer v66; + Integer v67; + Integer v68; + Integer v69; + Integer v70; + Integer v71; + Integer v72; + Integer v73; + Integer v74; + Integer v75; + Integer v76; + Integer v77; + Integer v78; + Integer v79; + Integer v80; + Integer v81; + Integer v82; + Integer v83; + Integer v84; + Integer v85; + Integer v86; + Integer v87; + Integer v88; + Integer v89; + Integer v90; + Integer v91; + Integer v92; + Integer v93; + Integer v94; + Integer v95; + Integer v96; + Integer v97; + Integer v98; + Integer v99; + Integer v100; + Integer v101; + Integer v102; + Integer v103; + Integer v104; + Integer v105; + Integer v106; + Integer v107; + Integer v108; + Integer v109; + Integer v110; + Integer v111; + Integer v112; + Integer v113; + Integer v114; + Integer v115; + Integer v116; + Integer v117; + Integer v118; + Integer v119; + Integer v120; + Integer v121; + Integer v122; + Integer v123; + Integer v124; + Integer v125; + Integer v126; + Integer v127; + Integer v128; + Integer v129; + Integer v130; + Integer v131; + Integer v132; + Integer v133; + Integer v134; + Integer v135; + Integer v136; + Integer v137; + Integer v138; + Integer v139; + Integer v140; + Integer v141; + Integer v142; + Integer v143; + Integer v144; + Integer v145; + Integer v146; + Integer v147; + Integer v148; + Integer v149; + Integer v150; + Integer v151; + Integer v152; + Integer v153; + Integer v154; + Integer v155; + Integer v156; + Integer v157; + Integer v158; + Integer v159; + Integer v160; + Integer v161; + Integer v162; + Integer v163; + Integer v164; + Integer v165; + Integer v166; + Integer v167; + Integer v168; + Integer v169; + Integer v170; + Integer v171; + Integer v172; + Integer v173; + Integer v174; + Integer v175; + Integer v176; + Integer v177; + Integer v178; + Integer v179; + Integer v180; + Integer v181; + Integer v182; + Integer v183; + Integer v184; + Integer v185; + Integer v186; + Integer v187; + Integer v188; + Integer v189; + Integer v190; + Integer v191; + Integer v192; + Integer v193; + Integer v194; + Integer v195; + Integer v196; + Integer v197; + Integer v198; + Integer v199; + Integer v200; + Integer v201; + Integer v202; + Integer v203; + Integer v204; + Integer v205; + Integer v206; + Integer v207; + Integer v208; + Integer v209; + Integer v210; + Integer v211; + Integer v212; + Integer v213; + Integer v214; + Integer v215; + Integer v216; + Integer v217; + Integer v218; + Integer v219; + Integer v220; + Integer v221; + Integer v222; + Integer v223; + Integer v224; + Integer v225; + Integer v226; + Integer v227; + Integer v228; + Integer v229; + Integer v230; + Integer v231; + Integer v232; + Integer v233; + Integer v234; + Integer v235; + Integer v236; + Integer v237; + Integer v238; + Integer v239; + Integer v240; + Integer v241; + Integer v242; + Integer v243; + Integer v244; + Integer v245; + Integer v246; + Integer v247; + Integer v248; + Integer v249; + Integer v250; + Integer v251; + Integer v252; + Integer v253; + Integer v254; + Integer v255; + Integer v256; + Integer v257; + Integer v258; + Integer v259; + Integer v260; + Integer v261; + Integer v262; + Integer v263; + Integer v264; + Integer v265; + Integer v266; + Integer v267; + Integer v268; + Integer v269; + Integer v270; + Integer v271; + Integer v272; + Integer v273; + Integer v274; + Integer v275; + Integer v276; + Integer v277; + Integer v278; + Integer v279; + Integer v280; + Integer v281; + Integer v282; + Integer v283; + Integer v284; + Integer v285; + Integer v286; + Integer v287; + Integer v288; + Integer v289; + Integer v290; + Integer v291; + Integer v292; + Integer v293; + Integer v294; + Integer v295; + Integer v296; + Integer v297; + Integer v298; + Integer v299; + Integer v300; + Integer v301; + Integer v302; + Integer v303; + Integer v304; + Integer v305; + Integer v306; + Integer v307; + Integer v308; + Integer v309; + Integer v310; + Integer v311; + Integer v312; + Integer v313; + Integer v314; + Integer v315; + Integer v316; + Integer v317; + Integer v318; + Integer v319; + Integer v320; + Integer v321; + Integer v322; + Integer v323; + Integer v324; + Integer v325; + Integer v326; + Integer v327; + Integer v328; + Integer v329; + Integer v330; + Integer v331; + Integer v332; + Integer v333; + Integer v334; + Integer v335; + Integer v336; + Integer v337; + Integer v338; + Integer v339; + Integer v340; + Integer v341; + Integer v342; + Integer v343; + Integer v344; + Integer v345; + Integer v346; + Integer v347; + Integer v348; + Integer v349; + Integer v350; + Integer v351; + Integer v352; + Integer v353; + Integer v354; + Integer v355; + Integer v356; + Integer v357; + Integer v358; + Integer v359; + Integer v360; + Integer v361; + Integer v362; + Integer v363; + Integer v364; + Integer v365; + Integer v366; + Integer v367; + Integer v368; + Integer v369; + Integer v370; + Integer v371; + Integer v372; + Integer v373; + Integer v374; + Integer v375; + Integer v376; + Integer v377; + Integer v378; + Integer v379; + Integer v380; + Integer v381; + Integer v382; + Integer v383; + Integer v384; + Integer v385; + Integer v386; + Integer v387; + Integer v388; + Integer v389; + Integer v390; + Integer v391; + Integer v392; + Integer v393; + Integer v394; + Integer v395; + Integer v396; + Integer v397; + Integer v398; + Integer v399; + Integer v400; + Integer v401; + Integer v402; + Integer v403; + Integer v404; + Integer v405; + Integer v406; + Integer v407; + Integer v408; + Integer v409; + Integer v410; + Integer v411; + Integer v412; + Integer v413; + Integer v414; + Integer v415; + Integer v416; + Integer v417; + Integer v418; + Integer v419; + Integer v420; + Integer v421; + Integer v422; + Integer v423; + Integer v424; + Integer v425; + Integer v426; + Integer v427; + Integer v428; + Integer v429; + Integer v430; + Integer v431; + Integer v432; + Integer v433; + Integer v434; + Integer v435; + Integer v436; + Integer v437; + Integer v438; + Integer v439; + Integer v440; + Integer v441; + Integer v442; + Integer v443; + Integer v444; + Integer v445; + Integer v446; + Integer v447; + Integer v448; + Integer v449; + Integer v450; + Integer v451; + Integer v452; + Integer v453; + Integer v454; + Integer v455; + Integer v456; + Integer v457; + Integer v458; + Integer v459; + Integer v460; + Integer v461; + Integer v462; + Integer v463; + Integer v464; + Integer v465; + Integer v466; + Integer v467; + Integer v468; + Integer v469; + Integer v470; + Integer v471; + Integer v472; + Integer v473; + Integer v474; + Integer v475; + Integer v476; + Integer v477; + Integer v478; + Integer v479; + Integer v480; + Integer v481; + Integer v482; + Integer v483; + Integer v484; + Integer v485; + Integer v486; + Integer v487; + Integer v488; + Integer v489; + Integer v490; + Integer v491; + Integer v492; + Integer v493; + Integer v494; + Integer v495; + Integer v496; + Integer v497; + Integer v498; + Integer v499; + Integer v500; + Integer v501; + Integer v502; + Integer v503; + Integer v504; + Integer v505; + Integer v506; + Integer v507; + Integer v508; + Integer v509; + Integer v510; + Integer v511; + Integer v512; + Integer v513; + Integer v514; + Integer v515; + Integer v516; + Integer v517; + Integer v518; + Integer v519; + Integer v520; + Integer v521; + Integer v522; + Integer v523; + Integer v524; + Integer v525; + Integer v526; + Integer v527; + Integer v528; + Integer v529; + Integer v530; + Integer v531; + Integer v532; + Integer v533; + Integer v534; + Integer v535; + Integer v536; + Integer v537; + Integer v538; + Integer v539; + Integer v540; + Integer v541; + Integer v542; + Integer v543; + Integer v544; + Integer v545; + Integer v546; + Integer v547; + Integer v548; + Integer v549; + Integer v550; + Integer v551; + Integer v552; + Integer v553; + Integer v554; + Integer v555; + Integer v556; + Integer v557; + Integer v558; + Integer v559; + Integer v560; + Integer v561; + Integer v562; + Integer v563; + Integer v564; + Integer v565; + Integer v566; + Integer v567; + Integer v568; + Integer v569; + Integer v570; + Integer v571; + Integer v572; + Integer v573; + Integer v574; + Integer v575; + Integer v576; + Integer v577; + Integer v578; + Integer v579; + Integer v580; + Integer v581; + Integer v582; + Integer v583; + Integer v584; + Integer v585; + Integer v586; + Integer v587; + Integer v588; + Integer v589; + Integer v590; + Integer v591; + Integer v592; + Integer v593; + Integer v594; + Integer v595; + Integer v596; + Integer v597; + Integer v598; + Integer v599; + Integer v600; + Integer v601; + Integer v602; + Integer v603; + Integer v604; + Integer v605; + Integer v606; + Integer v607; + Integer v608; + Integer v609; + Integer v610; + Integer v611; + Integer v612; + Integer v613; + Integer v614; + Integer v615; + Integer v616; + Integer v617; + Integer v618; + Integer v619; + Integer v620; + Integer v621; + Integer v622; + Integer v623; + Integer v624; + Integer v625; + Integer v626; + Integer v627; + Integer v628; + Integer v629; + Integer v630; + Integer v631; + Integer v632; + Integer v633; + Integer v634; + Integer v635; + Integer v636; + Integer v637; + Integer v638; + Integer v639; + Integer v640; + Integer v641; + Integer v642; + Integer v643; + Integer v644; + Integer v645; + Integer v646; + Integer v647; + Integer v648; + Integer v649; + Integer v650; + Integer v651; + Integer v652; + Integer v653; + Integer v654; + Integer v655; + Integer v656; + Integer v657; + Integer v658; + Integer v659; + Integer v660; + Integer v661; + Integer v662; + Integer v663; + Integer v664; + Integer v665; + Integer v666; + Integer v667; + Integer v668; + Integer v669; + Integer v670; + Integer v671; + Integer v672; + Integer v673; + Integer v674; + Integer v675; + Integer v676; + Integer v677; + Integer v678; + Integer v679; + Integer v680; + Integer v681; + Integer v682; + Integer v683; + Integer v684; + Integer v685; + Integer v686; + Integer v687; + Integer v688; + Integer v689; + Integer v690; + Integer v691; + Integer v692; + Integer v693; + Integer v694; + Integer v695; + Integer v696; + Integer v697; + Integer v698; + Integer v699; + Integer v700; + Integer v701; + Integer v702; + Integer v703; + Integer v704; + Integer v705; + Integer v706; + Integer v707; + Integer v708; + Integer v709; + Integer v710; + Integer v711; + Integer v712; + Integer v713; + Integer v714; + Integer v715; + Integer v716; + Integer v717; + Integer v718; + Integer v719; + Integer v720; + Integer v721; + Integer v722; + Integer v723; + Integer v724; + Integer v725; + Integer v726; + Integer v727; + Integer v728; + Integer v729; + Integer v730; + Integer v731; + Integer v732; + Integer v733; + Integer v734; + Integer v735; + Integer v736; + Integer v737; + Integer v738; + Integer v739; + Integer v740; + Integer v741; + Integer v742; + Integer v743; + Integer v744; + Integer v745; + Integer v746; + Integer v747; + Integer v748; + Integer v749; + Integer v750; + Integer v751; + Integer v752; + Integer v753; + Integer v754; + Integer v755; + Integer v756; + Integer v757; + Integer v758; + Integer v759; + Integer v760; + Integer v761; + Integer v762; + Integer v763; + Integer v764; + Integer v765; + Integer v766; + Integer v767; + Integer v768; + Integer v769; + Integer v770; + Integer v771; + Integer v772; + Integer v773; + Integer v774; + Integer v775; + Integer v776; + Integer v777; + Integer v778; + Integer v779; + Integer v780; + Integer v781; + Integer v782; + Integer v783; + Integer v784; + Integer v785; + Integer v786; + Integer v787; + Integer v788; + Integer v789; + Integer v790; + Integer v791; + Integer v792; + Integer v793; + Integer v794; + Integer v795; + Integer v796; + Integer v797; + Integer v798; + Integer v799; + Integer v800; + Integer v801; + Integer v802; + Integer v803; + Integer v804; + Integer v805; + Integer v806; + Integer v807; + Integer v808; + Integer v809; + Integer v810; + Integer v811; + Integer v812; + Integer v813; + Integer v814; + Integer v815; + Integer v816; + Integer v817; + Integer v818; + Integer v819; + Integer v820; + Integer v821; + Integer v822; + Integer v823; + Integer v824; + Integer v825; + Integer v826; + Integer v827; + Integer v828; + Integer v829; + Integer v830; + Integer v831; + Integer v832; + Integer v833; + Integer v834; + Integer v835; + Integer v836; + Integer v837; + Integer v838; + Integer v839; + Integer v840; + Integer v841; + Integer v842; + Integer v843; + Integer v844; + Integer v845; + Integer v846; + Integer v847; + Integer v848; + Integer v849; + Integer v850; + Integer v851; + Integer v852; + Integer v853; + Integer v854; + Integer v855; + Integer v856; + Integer v857; + Integer v858; + Integer v859; + Integer v860; + Integer v861; + Integer v862; + Integer v863; + Integer v864; + Integer v865; + Integer v866; + Integer v867; + Integer v868; + Integer v869; + Integer v870; + Integer v871; + Integer v872; + Integer v873; + Integer v874; + Integer v875; + Integer v876; + Integer v877; + Integer v878; + Integer v879; + Integer v880; + Integer v881; + Integer v882; + Integer v883; + Integer v884; + Integer v885; + Integer v886; + Integer v887; + Integer v888; + Integer v889; + Integer v890; + Integer v891; + Integer v892; + Integer v893; + Integer v894; + Integer v895; + Integer v896; + Integer v897; + Integer v898; + Integer v899; + Integer v900; + Integer v901; + Integer v902; + Integer v903; + Integer v904; + Integer v905; + Integer v906; + Integer v907; + Integer v908; + Integer v909; + Integer v910; + Integer v911; + Integer v912; + Integer v913; + Integer v914; + Integer v915; + Integer v916; + Integer v917; + Integer v918; + Integer v919; + Integer v920; + Integer v921; + Integer v922; + Integer v923; + Integer v924; + Integer v925; + Integer v926; + Integer v927; + Integer v928; + Integer v929; + Integer v930; + Integer v931; + Integer v932; + Integer v933; + Integer v934; + Integer v935; + Integer v936; + Integer v937; + Integer v938; + Integer v939; + Integer v940; + Integer v941; + Integer v942; + Integer v943; + Integer v944; + Integer v945; + Integer v946; + Integer v947; + Integer v948; + Integer v949; + Integer v950; + Integer v951; + Integer v952; + Integer v953; + Integer v954; + Integer v955; + Integer v956; + Integer v957; + Integer v958; + Integer v959; + Integer v960; + Integer v961; + Integer v962; + Integer v963; + Integer v964; + Integer v965; + Integer v966; + Integer v967; + Integer v968; + Integer v969; + Integer v970; + Integer v971; + Integer v972; + Integer v973; + Integer v974; + Integer v975; + Integer v976; + Integer v977; + Integer v978; + Integer v979; + Integer v980; + Integer v981; + Integer v982; + Integer v983; + Integer v984; + Integer v985; + Integer v986; + Integer v987; + Integer v988; + Integer v989; + Integer v990; + Integer v991; + Integer v992; + Integer v993; + Integer v994; + Integer v995; + Integer v996; + Integer v997; + Integer v998; + Integer v999; - void foo() { - Map map = new HashMap<>(); - map.put("k0", v0); - map.put("k1", v1); - map.put("k2", v2); - map.put("k3", v3); - map.put("k4", v4); - map.put("k5", v5); - map.put("k6", v6); - map.put("k7", v7); - map.put("k8", v8); - map.put("k9", v9); - map.put("k10", v10); - map.put("k11", v11); - map.put("k12", v12); - map.put("k13", v13); - map.put("k14", v14); - map.put("k15", v15); - map.put("k16", v16); - map.put("k17", v17); - map.put("k18", v18); - map.put("k19", v19); - map.put("k20", v20); - map.put("k21", v21); - map.put("k22", v22); - map.put("k23", v23); - map.put("k24", v24); - map.put("k25", v25); - map.put("k26", v26); - map.put("k27", v27); - map.put("k28", v28); - map.put("k29", v29); - map.put("k30", v30); - map.put("k31", v31); - map.put("k32", v32); - map.put("k33", v33); - map.put("k34", v34); - map.put("k35", v35); - map.put("k36", v36); - map.put("k37", v37); - map.put("k38", v38); - map.put("k39", v39); - map.put("k40", v40); - map.put("k41", v41); - map.put("k42", v42); - map.put("k43", v43); - map.put("k44", v44); - map.put("k45", v45); - map.put("k46", v46); - map.put("k47", v47); - map.put("k48", v48); - map.put("k49", v49); - map.put("k50", v50); - map.put("k51", v51); - map.put("k52", v52); - map.put("k53", v53); - map.put("k54", v54); - map.put("k55", v55); - map.put("k56", v56); - map.put("k57", v57); - map.put("k58", v58); - map.put("k59", v59); - map.put("k60", v60); - map.put("k61", v61); - map.put("k62", v62); - map.put("k63", v63); - map.put("k64", v64); - map.put("k65", v65); - map.put("k66", v66); - map.put("k67", v67); - map.put("k68", v68); - map.put("k69", v69); - map.put("k70", v70); - map.put("k71", v71); - map.put("k72", v72); - map.put("k73", v73); - map.put("k74", v74); - map.put("k75", v75); - map.put("k76", v76); - map.put("k77", v77); - map.put("k78", v78); - map.put("k79", v79); - map.put("k80", v80); - map.put("k81", v81); - map.put("k82", v82); - map.put("k83", v83); - map.put("k84", v84); - map.put("k85", v85); - map.put("k86", v86); - map.put("k87", v87); - map.put("k88", v88); - map.put("k89", v89); - map.put("k90", v90); - map.put("k91", v91); - map.put("k92", v92); - map.put("k93", v93); - map.put("k94", v94); - map.put("k95", v95); - map.put("k96", v96); - map.put("k97", v97); - map.put("k98", v98); - map.put("k99", v99); - map.put("k100", v100); - map.put("k101", v101); - map.put("k102", v102); - map.put("k103", v103); - map.put("k104", v104); - map.put("k105", v105); - map.put("k106", v106); - map.put("k107", v107); - map.put("k108", v108); - map.put("k109", v109); - map.put("k110", v110); - map.put("k111", v111); - map.put("k112", v112); - map.put("k113", v113); - map.put("k114", v114); - map.put("k115", v115); - map.put("k116", v116); - map.put("k117", v117); - map.put("k118", v118); - map.put("k119", v119); - map.put("k120", v120); - map.put("k121", v121); - map.put("k122", v122); - map.put("k123", v123); - map.put("k124", v124); - map.put("k125", v125); - map.put("k126", v126); - map.put("k127", v127); - map.put("k128", v128); - map.put("k129", v129); - map.put("k130", v130); - map.put("k131", v131); - map.put("k132", v132); - map.put("k133", v133); - map.put("k134", v134); - map.put("k135", v135); - map.put("k136", v136); - map.put("k137", v137); - map.put("k138", v138); - map.put("k139", v139); - map.put("k140", v140); - map.put("k141", v141); - map.put("k142", v142); - map.put("k143", v143); - map.put("k144", v144); - map.put("k145", v145); - map.put("k146", v146); - map.put("k147", v147); - map.put("k148", v148); - map.put("k149", v149); - map.put("k150", v150); - map.put("k151", v151); - map.put("k152", v152); - map.put("k153", v153); - map.put("k154", v154); - map.put("k155", v155); - map.put("k156", v156); - map.put("k157", v157); - map.put("k158", v158); - map.put("k159", v159); - map.put("k160", v160); - map.put("k161", v161); - map.put("k162", v162); - map.put("k163", v163); - map.put("k164", v164); - map.put("k165", v165); - map.put("k166", v166); - map.put("k167", v167); - map.put("k168", v168); - map.put("k169", v169); - map.put("k170", v170); - map.put("k171", v171); - map.put("k172", v172); - map.put("k173", v173); - map.put("k174", v174); - map.put("k175", v175); - map.put("k176", v176); - map.put("k177", v177); - map.put("k178", v178); - map.put("k179", v179); - map.put("k180", v180); - map.put("k181", v181); - map.put("k182", v182); - map.put("k183", v183); - map.put("k184", v184); - map.put("k185", v185); - map.put("k186", v186); - map.put("k187", v187); - map.put("k188", v188); - map.put("k189", v189); - map.put("k190", v190); - map.put("k191", v191); - map.put("k192", v192); - map.put("k193", v193); - map.put("k194", v194); - map.put("k195", v195); - map.put("k196", v196); - map.put("k197", v197); - map.put("k198", v198); - map.put("k199", v199); - map.put("k200", v200); - map.put("k201", v201); - map.put("k202", v202); - map.put("k203", v203); - map.put("k204", v204); - map.put("k205", v205); - map.put("k206", v206); - map.put("k207", v207); - map.put("k208", v208); - map.put("k209", v209); - map.put("k210", v210); - map.put("k211", v211); - map.put("k212", v212); - map.put("k213", v213); - map.put("k214", v214); - map.put("k215", v215); - map.put("k216", v216); - map.put("k217", v217); - map.put("k218", v218); - map.put("k219", v219); - map.put("k220", v220); - map.put("k221", v221); - map.put("k222", v222); - map.put("k223", v223); - map.put("k224", v224); - map.put("k225", v225); - map.put("k226", v226); - map.put("k227", v227); - map.put("k228", v228); - map.put("k229", v229); - map.put("k230", v230); - map.put("k231", v231); - map.put("k232", v232); - map.put("k233", v233); - map.put("k234", v234); - map.put("k235", v235); - map.put("k236", v236); - map.put("k237", v237); - map.put("k238", v238); - map.put("k239", v239); - map.put("k240", v240); - map.put("k241", v241); - map.put("k242", v242); - map.put("k243", v243); - map.put("k244", v244); - map.put("k245", v245); - map.put("k246", v246); - map.put("k247", v247); - map.put("k248", v248); - map.put("k249", v249); - map.put("k250", v250); - map.put("k251", v251); - map.put("k252", v252); - map.put("k253", v253); - map.put("k254", v254); - map.put("k255", v255); - map.put("k256", v256); - map.put("k257", v257); - map.put("k258", v258); - map.put("k259", v259); - map.put("k260", v260); - map.put("k261", v261); - map.put("k262", v262); - map.put("k263", v263); - map.put("k264", v264); - map.put("k265", v265); - map.put("k266", v266); - map.put("k267", v267); - map.put("k268", v268); - map.put("k269", v269); - map.put("k270", v270); - map.put("k271", v271); - map.put("k272", v272); - map.put("k273", v273); - map.put("k274", v274); - map.put("k275", v275); - map.put("k276", v276); - map.put("k277", v277); - map.put("k278", v278); - map.put("k279", v279); - map.put("k280", v280); - map.put("k281", v281); - map.put("k282", v282); - map.put("k283", v283); - map.put("k284", v284); - map.put("k285", v285); - map.put("k286", v286); - map.put("k287", v287); - map.put("k288", v288); - map.put("k289", v289); - map.put("k290", v290); - map.put("k291", v291); - map.put("k292", v292); - map.put("k293", v293); - map.put("k294", v294); - map.put("k295", v295); - map.put("k296", v296); - map.put("k297", v297); - map.put("k298", v298); - map.put("k299", v299); - map.put("k300", v300); - map.put("k301", v301); - map.put("k302", v302); - map.put("k303", v303); - map.put("k304", v304); - map.put("k305", v305); - map.put("k306", v306); - map.put("k307", v307); - map.put("k308", v308); - map.put("k309", v309); - map.put("k310", v310); - map.put("k311", v311); - map.put("k312", v312); - map.put("k313", v313); - map.put("k314", v314); - map.put("k315", v315); - map.put("k316", v316); - map.put("k317", v317); - map.put("k318", v318); - map.put("k319", v319); - map.put("k320", v320); - map.put("k321", v321); - map.put("k322", v322); - map.put("k323", v323); - map.put("k324", v324); - map.put("k325", v325); - map.put("k326", v326); - map.put("k327", v327); - map.put("k328", v328); - map.put("k329", v329); - map.put("k330", v330); - map.put("k331", v331); - map.put("k332", v332); - map.put("k333", v333); - map.put("k334", v334); - map.put("k335", v335); - map.put("k336", v336); - map.put("k337", v337); - map.put("k338", v338); - map.put("k339", v339); - map.put("k340", v340); - map.put("k341", v341); - map.put("k342", v342); - map.put("k343", v343); - map.put("k344", v344); - map.put("k345", v345); - map.put("k346", v346); - map.put("k347", v347); - map.put("k348", v348); - map.put("k349", v349); - map.put("k350", v350); - map.put("k351", v351); - map.put("k352", v352); - map.put("k353", v353); - map.put("k354", v354); - map.put("k355", v355); - map.put("k356", v356); - map.put("k357", v357); - map.put("k358", v358); - map.put("k359", v359); - map.put("k360", v360); - map.put("k361", v361); - map.put("k362", v362); - map.put("k363", v363); - map.put("k364", v364); - map.put("k365", v365); - map.put("k366", v366); - map.put("k367", v367); - map.put("k368", v368); - map.put("k369", v369); - map.put("k370", v370); - map.put("k371", v371); - map.put("k372", v372); - map.put("k373", v373); - map.put("k374", v374); - map.put("k375", v375); - map.put("k376", v376); - map.put("k377", v377); - map.put("k378", v378); - map.put("k379", v379); - map.put("k380", v380); - map.put("k381", v381); - map.put("k382", v382); - map.put("k383", v383); - map.put("k384", v384); - map.put("k385", v385); - map.put("k386", v386); - map.put("k387", v387); - map.put("k388", v388); - map.put("k389", v389); - map.put("k390", v390); - map.put("k391", v391); - map.put("k392", v392); - map.put("k393", v393); - map.put("k394", v394); - map.put("k395", v395); - map.put("k396", v396); - map.put("k397", v397); - map.put("k398", v398); - map.put("k399", v399); - map.put("k400", v400); - map.put("k401", v401); - map.put("k402", v402); - map.put("k403", v403); - map.put("k404", v404); - map.put("k405", v405); - map.put("k406", v406); - map.put("k407", v407); - map.put("k408", v408); - map.put("k409", v409); - map.put("k410", v410); - map.put("k411", v411); - map.put("k412", v412); - map.put("k413", v413); - map.put("k414", v414); - map.put("k415", v415); - map.put("k416", v416); - map.put("k417", v417); - map.put("k418", v418); - map.put("k419", v419); - map.put("k420", v420); - map.put("k421", v421); - map.put("k422", v422); - map.put("k423", v423); - map.put("k424", v424); - map.put("k425", v425); - map.put("k426", v426); - map.put("k427", v427); - map.put("k428", v428); - map.put("k429", v429); - map.put("k430", v430); - map.put("k431", v431); - map.put("k432", v432); - map.put("k433", v433); - map.put("k434", v434); - map.put("k435", v435); - map.put("k436", v436); - map.put("k437", v437); - map.put("k438", v438); - map.put("k439", v439); - map.put("k440", v440); - map.put("k441", v441); - map.put("k442", v442); - map.put("k443", v443); - map.put("k444", v444); - map.put("k445", v445); - map.put("k446", v446); - map.put("k447", v447); - map.put("k448", v448); - map.put("k449", v449); - map.put("k450", v450); - map.put("k451", v451); - map.put("k452", v452); - map.put("k453", v453); - map.put("k454", v454); - map.put("k455", v455); - map.put("k456", v456); - map.put("k457", v457); - map.put("k458", v458); - map.put("k459", v459); - map.put("k460", v460); - map.put("k461", v461); - map.put("k462", v462); - map.put("k463", v463); - map.put("k464", v464); - map.put("k465", v465); - map.put("k466", v466); - map.put("k467", v467); - map.put("k468", v468); - map.put("k469", v469); - map.put("k470", v470); - map.put("k471", v471); - map.put("k472", v472); - map.put("k473", v473); - map.put("k474", v474); - map.put("k475", v475); - map.put("k476", v476); - map.put("k477", v477); - map.put("k478", v478); - map.put("k479", v479); - map.put("k480", v480); - map.put("k481", v481); - map.put("k482", v482); - map.put("k483", v483); - map.put("k484", v484); - map.put("k485", v485); - map.put("k486", v486); - map.put("k487", v487); - map.put("k488", v488); - map.put("k489", v489); - map.put("k490", v490); - map.put("k491", v491); - map.put("k492", v492); - map.put("k493", v493); - map.put("k494", v494); - map.put("k495", v495); - map.put("k496", v496); - map.put("k497", v497); - map.put("k498", v498); - map.put("k499", v499); - map.put("k500", v500); - map.put("k501", v501); - map.put("k502", v502); - map.put("k503", v503); - map.put("k504", v504); - map.put("k505", v505); - map.put("k506", v506); - map.put("k507", v507); - map.put("k508", v508); - map.put("k509", v509); - map.put("k510", v510); - map.put("k511", v511); - map.put("k512", v512); - map.put("k513", v513); - map.put("k514", v514); - map.put("k515", v515); - map.put("k516", v516); - map.put("k517", v517); - map.put("k518", v518); - map.put("k519", v519); - map.put("k520", v520); - map.put("k521", v521); - map.put("k522", v522); - map.put("k523", v523); - map.put("k524", v524); - map.put("k525", v525); - map.put("k526", v526); - map.put("k527", v527); - map.put("k528", v528); - map.put("k529", v529); - map.put("k530", v530); - map.put("k531", v531); - map.put("k532", v532); - map.put("k533", v533); - map.put("k534", v534); - map.put("k535", v535); - map.put("k536", v536); - map.put("k537", v537); - map.put("k538", v538); - map.put("k539", v539); - map.put("k540", v540); - map.put("k541", v541); - map.put("k542", v542); - map.put("k543", v543); - map.put("k544", v544); - map.put("k545", v545); - map.put("k546", v546); - map.put("k547", v547); - map.put("k548", v548); - map.put("k549", v549); - map.put("k550", v550); - map.put("k551", v551); - map.put("k552", v552); - map.put("k553", v553); - map.put("k554", v554); - map.put("k555", v555); - map.put("k556", v556); - map.put("k557", v557); - map.put("k558", v558); - map.put("k559", v559); - map.put("k560", v560); - map.put("k561", v561); - map.put("k562", v562); - map.put("k563", v563); - map.put("k564", v564); - map.put("k565", v565); - map.put("k566", v566); - map.put("k567", v567); - map.put("k568", v568); - map.put("k569", v569); - map.put("k570", v570); - map.put("k571", v571); - map.put("k572", v572); - map.put("k573", v573); - map.put("k574", v574); - map.put("k575", v575); - map.put("k576", v576); - map.put("k577", v577); - map.put("k578", v578); - map.put("k579", v579); - map.put("k580", v580); - map.put("k581", v581); - map.put("k582", v582); - map.put("k583", v583); - map.put("k584", v584); - map.put("k585", v585); - map.put("k586", v586); - map.put("k587", v587); - map.put("k588", v588); - map.put("k589", v589); - map.put("k590", v590); - map.put("k591", v591); - map.put("k592", v592); - map.put("k593", v593); - map.put("k594", v594); - map.put("k595", v595); - map.put("k596", v596); - map.put("k597", v597); - map.put("k598", v598); - map.put("k599", v599); - map.put("k600", v600); - map.put("k601", v601); - map.put("k602", v602); - map.put("k603", v603); - map.put("k604", v604); - map.put("k605", v605); - map.put("k606", v606); - map.put("k607", v607); - map.put("k608", v608); - map.put("k609", v609); - map.put("k610", v610); - map.put("k611", v611); - map.put("k612", v612); - map.put("k613", v613); - map.put("k614", v614); - map.put("k615", v615); - map.put("k616", v616); - map.put("k617", v617); - map.put("k618", v618); - map.put("k619", v619); - map.put("k620", v620); - map.put("k621", v621); - map.put("k622", v622); - map.put("k623", v623); - map.put("k624", v624); - map.put("k625", v625); - map.put("k626", v626); - map.put("k627", v627); - map.put("k628", v628); - map.put("k629", v629); - map.put("k630", v630); - map.put("k631", v631); - map.put("k632", v632); - map.put("k633", v633); - map.put("k634", v634); - map.put("k635", v635); - map.put("k636", v636); - map.put("k637", v637); - map.put("k638", v638); - map.put("k639", v639); - map.put("k640", v640); - map.put("k641", v641); - map.put("k642", v642); - map.put("k643", v643); - map.put("k644", v644); - map.put("k645", v645); - map.put("k646", v646); - map.put("k647", v647); - map.put("k648", v648); - map.put("k649", v649); - map.put("k650", v650); - map.put("k651", v651); - map.put("k652", v652); - map.put("k653", v653); - map.put("k654", v654); - map.put("k655", v655); - map.put("k656", v656); - map.put("k657", v657); - map.put("k658", v658); - map.put("k659", v659); - map.put("k660", v660); - map.put("k661", v661); - map.put("k662", v662); - map.put("k663", v663); - map.put("k664", v664); - map.put("k665", v665); - map.put("k666", v666); - map.put("k667", v667); - map.put("k668", v668); - map.put("k669", v669); - map.put("k670", v670); - map.put("k671", v671); - map.put("k672", v672); - map.put("k673", v673); - map.put("k674", v674); - map.put("k675", v675); - map.put("k676", v676); - map.put("k677", v677); - map.put("k678", v678); - map.put("k679", v679); - map.put("k680", v680); - map.put("k681", v681); - map.put("k682", v682); - map.put("k683", v683); - map.put("k684", v684); - map.put("k685", v685); - map.put("k686", v686); - map.put("k687", v687); - map.put("k688", v688); - map.put("k689", v689); - map.put("k690", v690); - map.put("k691", v691); - map.put("k692", v692); - map.put("k693", v693); - map.put("k694", v694); - map.put("k695", v695); - map.put("k696", v696); - map.put("k697", v697); - map.put("k698", v698); - map.put("k699", v699); - map.put("k700", v700); - map.put("k701", v701); - map.put("k702", v702); - map.put("k703", v703); - map.put("k704", v704); - map.put("k705", v705); - map.put("k706", v706); - map.put("k707", v707); - map.put("k708", v708); - map.put("k709", v709); - map.put("k710", v710); - map.put("k711", v711); - map.put("k712", v712); - map.put("k713", v713); - map.put("k714", v714); - map.put("k715", v715); - map.put("k716", v716); - map.put("k717", v717); - map.put("k718", v718); - map.put("k719", v719); - map.put("k720", v720); - map.put("k721", v721); - map.put("k722", v722); - map.put("k723", v723); - map.put("k724", v724); - map.put("k725", v725); - map.put("k726", v726); - map.put("k727", v727); - map.put("k728", v728); - map.put("k729", v729); - map.put("k730", v730); - map.put("k731", v731); - map.put("k732", v732); - map.put("k733", v733); - map.put("k734", v734); - map.put("k735", v735); - map.put("k736", v736); - map.put("k737", v737); - map.put("k738", v738); - map.put("k739", v739); - map.put("k740", v740); - map.put("k741", v741); - map.put("k742", v742); - map.put("k743", v743); - map.put("k744", v744); - map.put("k745", v745); - map.put("k746", v746); - map.put("k747", v747); - map.put("k748", v748); - map.put("k749", v749); - map.put("k750", v750); - map.put("k751", v751); - map.put("k752", v752); - map.put("k753", v753); - map.put("k754", v754); - map.put("k755", v755); - map.put("k756", v756); - map.put("k757", v757); - map.put("k758", v758); - map.put("k759", v759); - map.put("k760", v760); - map.put("k761", v761); - map.put("k762", v762); - map.put("k763", v763); - map.put("k764", v764); - map.put("k765", v765); - map.put("k766", v766); - map.put("k767", v767); - map.put("k768", v768); - map.put("k769", v769); - map.put("k770", v770); - map.put("k771", v771); - map.put("k772", v772); - map.put("k773", v773); - map.put("k774", v774); - map.put("k775", v775); - map.put("k776", v776); - map.put("k777", v777); - map.put("k778", v778); - map.put("k779", v779); - map.put("k780", v780); - map.put("k781", v781); - map.put("k782", v782); - map.put("k783", v783); - map.put("k784", v784); - map.put("k785", v785); - map.put("k786", v786); - map.put("k787", v787); - map.put("k788", v788); - map.put("k789", v789); - map.put("k790", v790); - map.put("k791", v791); - map.put("k792", v792); - map.put("k793", v793); - map.put("k794", v794); - map.put("k795", v795); - map.put("k796", v796); - map.put("k797", v797); - map.put("k798", v798); - map.put("k799", v799); - map.put("k800", v800); - map.put("k801", v801); - map.put("k802", v802); - map.put("k803", v803); - map.put("k804", v804); - map.put("k805", v805); - map.put("k806", v806); - map.put("k807", v807); - map.put("k808", v808); - map.put("k809", v809); - map.put("k810", v810); - map.put("k811", v811); - map.put("k812", v812); - map.put("k813", v813); - map.put("k814", v814); - map.put("k815", v815); - map.put("k816", v816); - map.put("k817", v817); - map.put("k818", v818); - map.put("k819", v819); - map.put("k820", v820); - map.put("k821", v821); - map.put("k822", v822); - map.put("k823", v823); - map.put("k824", v824); - map.put("k825", v825); - map.put("k826", v826); - map.put("k827", v827); - map.put("k828", v828); - map.put("k829", v829); - map.put("k830", v830); - map.put("k831", v831); - map.put("k832", v832); - map.put("k833", v833); - map.put("k834", v834); - map.put("k835", v835); - map.put("k836", v836); - map.put("k837", v837); - map.put("k838", v838); - map.put("k839", v839); - map.put("k840", v840); - map.put("k841", v841); - map.put("k842", v842); - map.put("k843", v843); - map.put("k844", v844); - map.put("k845", v845); - map.put("k846", v846); - map.put("k847", v847); - map.put("k848", v848); - map.put("k849", v849); - map.put("k850", v850); - map.put("k851", v851); - map.put("k852", v852); - map.put("k853", v853); - map.put("k854", v854); - map.put("k855", v855); - map.put("k856", v856); - map.put("k857", v857); - map.put("k858", v858); - map.put("k859", v859); - map.put("k860", v860); - map.put("k861", v861); - map.put("k862", v862); - map.put("k863", v863); - map.put("k864", v864); - map.put("k865", v865); - map.put("k866", v866); - map.put("k867", v867); - map.put("k868", v868); - map.put("k869", v869); - map.put("k870", v870); - map.put("k871", v871); - map.put("k872", v872); - map.put("k873", v873); - map.put("k874", v874); - map.put("k875", v875); - map.put("k876", v876); - map.put("k877", v877); - map.put("k878", v878); - map.put("k879", v879); - map.put("k880", v880); - map.put("k881", v881); - map.put("k882", v882); - map.put("k883", v883); - map.put("k884", v884); - map.put("k885", v885); - map.put("k886", v886); - map.put("k887", v887); - map.put("k888", v888); - map.put("k889", v889); - map.put("k890", v890); - map.put("k891", v891); - map.put("k892", v892); - map.put("k893", v893); - map.put("k894", v894); - map.put("k895", v895); - map.put("k896", v896); - map.put("k897", v897); - map.put("k898", v898); - map.put("k899", v899); - map.put("k900", v900); - map.put("k901", v901); - map.put("k902", v902); - map.put("k903", v903); - map.put("k904", v904); - map.put("k905", v905); - map.put("k906", v906); - map.put("k907", v907); - map.put("k908", v908); - map.put("k909", v909); - map.put("k910", v910); - map.put("k911", v911); - map.put("k912", v912); - map.put("k913", v913); - map.put("k914", v914); - map.put("k915", v915); - map.put("k916", v916); - map.put("k917", v917); - map.put("k918", v918); - map.put("k919", v919); - map.put("k920", v920); - map.put("k921", v921); - map.put("k922", v922); - map.put("k923", v923); - map.put("k924", v924); - map.put("k925", v925); - map.put("k926", v926); - map.put("k927", v927); - map.put("k928", v928); - map.put("k929", v929); - map.put("k930", v930); - map.put("k931", v931); - map.put("k932", v932); - map.put("k933", v933); - map.put("k934", v934); - map.put("k935", v935); - map.put("k936", v936); - map.put("k937", v937); - map.put("k938", v938); - map.put("k939", v939); - map.put("k940", v940); - map.put("k941", v941); - map.put("k942", v942); - map.put("k943", v943); - map.put("k944", v944); - map.put("k945", v945); - map.put("k946", v946); - map.put("k947", v947); - map.put("k948", v948); - map.put("k949", v949); - map.put("k950", v950); - map.put("k951", v951); - map.put("k952", v952); - map.put("k953", v953); - map.put("k954", v954); - map.put("k955", v955); - map.put("k956", v956); - map.put("k957", v957); - map.put("k958", v958); - map.put("k959", v959); - map.put("k960", v960); - map.put("k961", v961); - map.put("k962", v962); - map.put("k963", v963); - map.put("k964", v964); - map.put("k965", v965); - map.put("k966", v966); - map.put("k967", v967); - map.put("k968", v968); - map.put("k969", v969); - map.put("k970", v970); - map.put("k971", v971); - map.put("k972", v972); - map.put("k973", v973); - map.put("k974", v974); - map.put("k975", v975); - map.put("k976", v976); - map.put("k977", v977); - map.put("k978", v978); - map.put("k979", v979); - map.put("k980", v980); - map.put("k981", v981); - map.put("k982", v982); - map.put("k983", v983); - map.put("k984", v984); - map.put("k985", v985); - map.put("k986", v986); - map.put("k987", v987); - map.put("k988", v988); - map.put("k989", v989); - map.put("k990", v990); - map.put("k991", v991); - map.put("k992", v992); - map.put("k993", v993); - map.put("k994", v994); - map.put("k995", v995); - map.put("k996", v996); - map.put("k997", v997); - map.put("k998", v998); - map.put("k999", v999); - } + void foo() { + Map map = new HashMap<>(); + map.put("k0", v0); + map.put("k1", v1); + map.put("k2", v2); + map.put("k3", v3); + map.put("k4", v4); + map.put("k5", v5); + map.put("k6", v6); + map.put("k7", v7); + map.put("k8", v8); + map.put("k9", v9); + map.put("k10", v10); + map.put("k11", v11); + map.put("k12", v12); + map.put("k13", v13); + map.put("k14", v14); + map.put("k15", v15); + map.put("k16", v16); + map.put("k17", v17); + map.put("k18", v18); + map.put("k19", v19); + map.put("k20", v20); + map.put("k21", v21); + map.put("k22", v22); + map.put("k23", v23); + map.put("k24", v24); + map.put("k25", v25); + map.put("k26", v26); + map.put("k27", v27); + map.put("k28", v28); + map.put("k29", v29); + map.put("k30", v30); + map.put("k31", v31); + map.put("k32", v32); + map.put("k33", v33); + map.put("k34", v34); + map.put("k35", v35); + map.put("k36", v36); + map.put("k37", v37); + map.put("k38", v38); + map.put("k39", v39); + map.put("k40", v40); + map.put("k41", v41); + map.put("k42", v42); + map.put("k43", v43); + map.put("k44", v44); + map.put("k45", v45); + map.put("k46", v46); + map.put("k47", v47); + map.put("k48", v48); + map.put("k49", v49); + map.put("k50", v50); + map.put("k51", v51); + map.put("k52", v52); + map.put("k53", v53); + map.put("k54", v54); + map.put("k55", v55); + map.put("k56", v56); + map.put("k57", v57); + map.put("k58", v58); + map.put("k59", v59); + map.put("k60", v60); + map.put("k61", v61); + map.put("k62", v62); + map.put("k63", v63); + map.put("k64", v64); + map.put("k65", v65); + map.put("k66", v66); + map.put("k67", v67); + map.put("k68", v68); + map.put("k69", v69); + map.put("k70", v70); + map.put("k71", v71); + map.put("k72", v72); + map.put("k73", v73); + map.put("k74", v74); + map.put("k75", v75); + map.put("k76", v76); + map.put("k77", v77); + map.put("k78", v78); + map.put("k79", v79); + map.put("k80", v80); + map.put("k81", v81); + map.put("k82", v82); + map.put("k83", v83); + map.put("k84", v84); + map.put("k85", v85); + map.put("k86", v86); + map.put("k87", v87); + map.put("k88", v88); + map.put("k89", v89); + map.put("k90", v90); + map.put("k91", v91); + map.put("k92", v92); + map.put("k93", v93); + map.put("k94", v94); + map.put("k95", v95); + map.put("k96", v96); + map.put("k97", v97); + map.put("k98", v98); + map.put("k99", v99); + map.put("k100", v100); + map.put("k101", v101); + map.put("k102", v102); + map.put("k103", v103); + map.put("k104", v104); + map.put("k105", v105); + map.put("k106", v106); + map.put("k107", v107); + map.put("k108", v108); + map.put("k109", v109); + map.put("k110", v110); + map.put("k111", v111); + map.put("k112", v112); + map.put("k113", v113); + map.put("k114", v114); + map.put("k115", v115); + map.put("k116", v116); + map.put("k117", v117); + map.put("k118", v118); + map.put("k119", v119); + map.put("k120", v120); + map.put("k121", v121); + map.put("k122", v122); + map.put("k123", v123); + map.put("k124", v124); + map.put("k125", v125); + map.put("k126", v126); + map.put("k127", v127); + map.put("k128", v128); + map.put("k129", v129); + map.put("k130", v130); + map.put("k131", v131); + map.put("k132", v132); + map.put("k133", v133); + map.put("k134", v134); + map.put("k135", v135); + map.put("k136", v136); + map.put("k137", v137); + map.put("k138", v138); + map.put("k139", v139); + map.put("k140", v140); + map.put("k141", v141); + map.put("k142", v142); + map.put("k143", v143); + map.put("k144", v144); + map.put("k145", v145); + map.put("k146", v146); + map.put("k147", v147); + map.put("k148", v148); + map.put("k149", v149); + map.put("k150", v150); + map.put("k151", v151); + map.put("k152", v152); + map.put("k153", v153); + map.put("k154", v154); + map.put("k155", v155); + map.put("k156", v156); + map.put("k157", v157); + map.put("k158", v158); + map.put("k159", v159); + map.put("k160", v160); + map.put("k161", v161); + map.put("k162", v162); + map.put("k163", v163); + map.put("k164", v164); + map.put("k165", v165); + map.put("k166", v166); + map.put("k167", v167); + map.put("k168", v168); + map.put("k169", v169); + map.put("k170", v170); + map.put("k171", v171); + map.put("k172", v172); + map.put("k173", v173); + map.put("k174", v174); + map.put("k175", v175); + map.put("k176", v176); + map.put("k177", v177); + map.put("k178", v178); + map.put("k179", v179); + map.put("k180", v180); + map.put("k181", v181); + map.put("k182", v182); + map.put("k183", v183); + map.put("k184", v184); + map.put("k185", v185); + map.put("k186", v186); + map.put("k187", v187); + map.put("k188", v188); + map.put("k189", v189); + map.put("k190", v190); + map.put("k191", v191); + map.put("k192", v192); + map.put("k193", v193); + map.put("k194", v194); + map.put("k195", v195); + map.put("k196", v196); + map.put("k197", v197); + map.put("k198", v198); + map.put("k199", v199); + map.put("k200", v200); + map.put("k201", v201); + map.put("k202", v202); + map.put("k203", v203); + map.put("k204", v204); + map.put("k205", v205); + map.put("k206", v206); + map.put("k207", v207); + map.put("k208", v208); + map.put("k209", v209); + map.put("k210", v210); + map.put("k211", v211); + map.put("k212", v212); + map.put("k213", v213); + map.put("k214", v214); + map.put("k215", v215); + map.put("k216", v216); + map.put("k217", v217); + map.put("k218", v218); + map.put("k219", v219); + map.put("k220", v220); + map.put("k221", v221); + map.put("k222", v222); + map.put("k223", v223); + map.put("k224", v224); + map.put("k225", v225); + map.put("k226", v226); + map.put("k227", v227); + map.put("k228", v228); + map.put("k229", v229); + map.put("k230", v230); + map.put("k231", v231); + map.put("k232", v232); + map.put("k233", v233); + map.put("k234", v234); + map.put("k235", v235); + map.put("k236", v236); + map.put("k237", v237); + map.put("k238", v238); + map.put("k239", v239); + map.put("k240", v240); + map.put("k241", v241); + map.put("k242", v242); + map.put("k243", v243); + map.put("k244", v244); + map.put("k245", v245); + map.put("k246", v246); + map.put("k247", v247); + map.put("k248", v248); + map.put("k249", v249); + map.put("k250", v250); + map.put("k251", v251); + map.put("k252", v252); + map.put("k253", v253); + map.put("k254", v254); + map.put("k255", v255); + map.put("k256", v256); + map.put("k257", v257); + map.put("k258", v258); + map.put("k259", v259); + map.put("k260", v260); + map.put("k261", v261); + map.put("k262", v262); + map.put("k263", v263); + map.put("k264", v264); + map.put("k265", v265); + map.put("k266", v266); + map.put("k267", v267); + map.put("k268", v268); + map.put("k269", v269); + map.put("k270", v270); + map.put("k271", v271); + map.put("k272", v272); + map.put("k273", v273); + map.put("k274", v274); + map.put("k275", v275); + map.put("k276", v276); + map.put("k277", v277); + map.put("k278", v278); + map.put("k279", v279); + map.put("k280", v280); + map.put("k281", v281); + map.put("k282", v282); + map.put("k283", v283); + map.put("k284", v284); + map.put("k285", v285); + map.put("k286", v286); + map.put("k287", v287); + map.put("k288", v288); + map.put("k289", v289); + map.put("k290", v290); + map.put("k291", v291); + map.put("k292", v292); + map.put("k293", v293); + map.put("k294", v294); + map.put("k295", v295); + map.put("k296", v296); + map.put("k297", v297); + map.put("k298", v298); + map.put("k299", v299); + map.put("k300", v300); + map.put("k301", v301); + map.put("k302", v302); + map.put("k303", v303); + map.put("k304", v304); + map.put("k305", v305); + map.put("k306", v306); + map.put("k307", v307); + map.put("k308", v308); + map.put("k309", v309); + map.put("k310", v310); + map.put("k311", v311); + map.put("k312", v312); + map.put("k313", v313); + map.put("k314", v314); + map.put("k315", v315); + map.put("k316", v316); + map.put("k317", v317); + map.put("k318", v318); + map.put("k319", v319); + map.put("k320", v320); + map.put("k321", v321); + map.put("k322", v322); + map.put("k323", v323); + map.put("k324", v324); + map.put("k325", v325); + map.put("k326", v326); + map.put("k327", v327); + map.put("k328", v328); + map.put("k329", v329); + map.put("k330", v330); + map.put("k331", v331); + map.put("k332", v332); + map.put("k333", v333); + map.put("k334", v334); + map.put("k335", v335); + map.put("k336", v336); + map.put("k337", v337); + map.put("k338", v338); + map.put("k339", v339); + map.put("k340", v340); + map.put("k341", v341); + map.put("k342", v342); + map.put("k343", v343); + map.put("k344", v344); + map.put("k345", v345); + map.put("k346", v346); + map.put("k347", v347); + map.put("k348", v348); + map.put("k349", v349); + map.put("k350", v350); + map.put("k351", v351); + map.put("k352", v352); + map.put("k353", v353); + map.put("k354", v354); + map.put("k355", v355); + map.put("k356", v356); + map.put("k357", v357); + map.put("k358", v358); + map.put("k359", v359); + map.put("k360", v360); + map.put("k361", v361); + map.put("k362", v362); + map.put("k363", v363); + map.put("k364", v364); + map.put("k365", v365); + map.put("k366", v366); + map.put("k367", v367); + map.put("k368", v368); + map.put("k369", v369); + map.put("k370", v370); + map.put("k371", v371); + map.put("k372", v372); + map.put("k373", v373); + map.put("k374", v374); + map.put("k375", v375); + map.put("k376", v376); + map.put("k377", v377); + map.put("k378", v378); + map.put("k379", v379); + map.put("k380", v380); + map.put("k381", v381); + map.put("k382", v382); + map.put("k383", v383); + map.put("k384", v384); + map.put("k385", v385); + map.put("k386", v386); + map.put("k387", v387); + map.put("k388", v388); + map.put("k389", v389); + map.put("k390", v390); + map.put("k391", v391); + map.put("k392", v392); + map.put("k393", v393); + map.put("k394", v394); + map.put("k395", v395); + map.put("k396", v396); + map.put("k397", v397); + map.put("k398", v398); + map.put("k399", v399); + map.put("k400", v400); + map.put("k401", v401); + map.put("k402", v402); + map.put("k403", v403); + map.put("k404", v404); + map.put("k405", v405); + map.put("k406", v406); + map.put("k407", v407); + map.put("k408", v408); + map.put("k409", v409); + map.put("k410", v410); + map.put("k411", v411); + map.put("k412", v412); + map.put("k413", v413); + map.put("k414", v414); + map.put("k415", v415); + map.put("k416", v416); + map.put("k417", v417); + map.put("k418", v418); + map.put("k419", v419); + map.put("k420", v420); + map.put("k421", v421); + map.put("k422", v422); + map.put("k423", v423); + map.put("k424", v424); + map.put("k425", v425); + map.put("k426", v426); + map.put("k427", v427); + map.put("k428", v428); + map.put("k429", v429); + map.put("k430", v430); + map.put("k431", v431); + map.put("k432", v432); + map.put("k433", v433); + map.put("k434", v434); + map.put("k435", v435); + map.put("k436", v436); + map.put("k437", v437); + map.put("k438", v438); + map.put("k439", v439); + map.put("k440", v440); + map.put("k441", v441); + map.put("k442", v442); + map.put("k443", v443); + map.put("k444", v444); + map.put("k445", v445); + map.put("k446", v446); + map.put("k447", v447); + map.put("k448", v448); + map.put("k449", v449); + map.put("k450", v450); + map.put("k451", v451); + map.put("k452", v452); + map.put("k453", v453); + map.put("k454", v454); + map.put("k455", v455); + map.put("k456", v456); + map.put("k457", v457); + map.put("k458", v458); + map.put("k459", v459); + map.put("k460", v460); + map.put("k461", v461); + map.put("k462", v462); + map.put("k463", v463); + map.put("k464", v464); + map.put("k465", v465); + map.put("k466", v466); + map.put("k467", v467); + map.put("k468", v468); + map.put("k469", v469); + map.put("k470", v470); + map.put("k471", v471); + map.put("k472", v472); + map.put("k473", v473); + map.put("k474", v474); + map.put("k475", v475); + map.put("k476", v476); + map.put("k477", v477); + map.put("k478", v478); + map.put("k479", v479); + map.put("k480", v480); + map.put("k481", v481); + map.put("k482", v482); + map.put("k483", v483); + map.put("k484", v484); + map.put("k485", v485); + map.put("k486", v486); + map.put("k487", v487); + map.put("k488", v488); + map.put("k489", v489); + map.put("k490", v490); + map.put("k491", v491); + map.put("k492", v492); + map.put("k493", v493); + map.put("k494", v494); + map.put("k495", v495); + map.put("k496", v496); + map.put("k497", v497); + map.put("k498", v498); + map.put("k499", v499); + map.put("k500", v500); + map.put("k501", v501); + map.put("k502", v502); + map.put("k503", v503); + map.put("k504", v504); + map.put("k505", v505); + map.put("k506", v506); + map.put("k507", v507); + map.put("k508", v508); + map.put("k509", v509); + map.put("k510", v510); + map.put("k511", v511); + map.put("k512", v512); + map.put("k513", v513); + map.put("k514", v514); + map.put("k515", v515); + map.put("k516", v516); + map.put("k517", v517); + map.put("k518", v518); + map.put("k519", v519); + map.put("k520", v520); + map.put("k521", v521); + map.put("k522", v522); + map.put("k523", v523); + map.put("k524", v524); + map.put("k525", v525); + map.put("k526", v526); + map.put("k527", v527); + map.put("k528", v528); + map.put("k529", v529); + map.put("k530", v530); + map.put("k531", v531); + map.put("k532", v532); + map.put("k533", v533); + map.put("k534", v534); + map.put("k535", v535); + map.put("k536", v536); + map.put("k537", v537); + map.put("k538", v538); + map.put("k539", v539); + map.put("k540", v540); + map.put("k541", v541); + map.put("k542", v542); + map.put("k543", v543); + map.put("k544", v544); + map.put("k545", v545); + map.put("k546", v546); + map.put("k547", v547); + map.put("k548", v548); + map.put("k549", v549); + map.put("k550", v550); + map.put("k551", v551); + map.put("k552", v552); + map.put("k553", v553); + map.put("k554", v554); + map.put("k555", v555); + map.put("k556", v556); + map.put("k557", v557); + map.put("k558", v558); + map.put("k559", v559); + map.put("k560", v560); + map.put("k561", v561); + map.put("k562", v562); + map.put("k563", v563); + map.put("k564", v564); + map.put("k565", v565); + map.put("k566", v566); + map.put("k567", v567); + map.put("k568", v568); + map.put("k569", v569); + map.put("k570", v570); + map.put("k571", v571); + map.put("k572", v572); + map.put("k573", v573); + map.put("k574", v574); + map.put("k575", v575); + map.put("k576", v576); + map.put("k577", v577); + map.put("k578", v578); + map.put("k579", v579); + map.put("k580", v580); + map.put("k581", v581); + map.put("k582", v582); + map.put("k583", v583); + map.put("k584", v584); + map.put("k585", v585); + map.put("k586", v586); + map.put("k587", v587); + map.put("k588", v588); + map.put("k589", v589); + map.put("k590", v590); + map.put("k591", v591); + map.put("k592", v592); + map.put("k593", v593); + map.put("k594", v594); + map.put("k595", v595); + map.put("k596", v596); + map.put("k597", v597); + map.put("k598", v598); + map.put("k599", v599); + map.put("k600", v600); + map.put("k601", v601); + map.put("k602", v602); + map.put("k603", v603); + map.put("k604", v604); + map.put("k605", v605); + map.put("k606", v606); + map.put("k607", v607); + map.put("k608", v608); + map.put("k609", v609); + map.put("k610", v610); + map.put("k611", v611); + map.put("k612", v612); + map.put("k613", v613); + map.put("k614", v614); + map.put("k615", v615); + map.put("k616", v616); + map.put("k617", v617); + map.put("k618", v618); + map.put("k619", v619); + map.put("k620", v620); + map.put("k621", v621); + map.put("k622", v622); + map.put("k623", v623); + map.put("k624", v624); + map.put("k625", v625); + map.put("k626", v626); + map.put("k627", v627); + map.put("k628", v628); + map.put("k629", v629); + map.put("k630", v630); + map.put("k631", v631); + map.put("k632", v632); + map.put("k633", v633); + map.put("k634", v634); + map.put("k635", v635); + map.put("k636", v636); + map.put("k637", v637); + map.put("k638", v638); + map.put("k639", v639); + map.put("k640", v640); + map.put("k641", v641); + map.put("k642", v642); + map.put("k643", v643); + map.put("k644", v644); + map.put("k645", v645); + map.put("k646", v646); + map.put("k647", v647); + map.put("k648", v648); + map.put("k649", v649); + map.put("k650", v650); + map.put("k651", v651); + map.put("k652", v652); + map.put("k653", v653); + map.put("k654", v654); + map.put("k655", v655); + map.put("k656", v656); + map.put("k657", v657); + map.put("k658", v658); + map.put("k659", v659); + map.put("k660", v660); + map.put("k661", v661); + map.put("k662", v662); + map.put("k663", v663); + map.put("k664", v664); + map.put("k665", v665); + map.put("k666", v666); + map.put("k667", v667); + map.put("k668", v668); + map.put("k669", v669); + map.put("k670", v670); + map.put("k671", v671); + map.put("k672", v672); + map.put("k673", v673); + map.put("k674", v674); + map.put("k675", v675); + map.put("k676", v676); + map.put("k677", v677); + map.put("k678", v678); + map.put("k679", v679); + map.put("k680", v680); + map.put("k681", v681); + map.put("k682", v682); + map.put("k683", v683); + map.put("k684", v684); + map.put("k685", v685); + map.put("k686", v686); + map.put("k687", v687); + map.put("k688", v688); + map.put("k689", v689); + map.put("k690", v690); + map.put("k691", v691); + map.put("k692", v692); + map.put("k693", v693); + map.put("k694", v694); + map.put("k695", v695); + map.put("k696", v696); + map.put("k697", v697); + map.put("k698", v698); + map.put("k699", v699); + map.put("k700", v700); + map.put("k701", v701); + map.put("k702", v702); + map.put("k703", v703); + map.put("k704", v704); + map.put("k705", v705); + map.put("k706", v706); + map.put("k707", v707); + map.put("k708", v708); + map.put("k709", v709); + map.put("k710", v710); + map.put("k711", v711); + map.put("k712", v712); + map.put("k713", v713); + map.put("k714", v714); + map.put("k715", v715); + map.put("k716", v716); + map.put("k717", v717); + map.put("k718", v718); + map.put("k719", v719); + map.put("k720", v720); + map.put("k721", v721); + map.put("k722", v722); + map.put("k723", v723); + map.put("k724", v724); + map.put("k725", v725); + map.put("k726", v726); + map.put("k727", v727); + map.put("k728", v728); + map.put("k729", v729); + map.put("k730", v730); + map.put("k731", v731); + map.put("k732", v732); + map.put("k733", v733); + map.put("k734", v734); + map.put("k735", v735); + map.put("k736", v736); + map.put("k737", v737); + map.put("k738", v738); + map.put("k739", v739); + map.put("k740", v740); + map.put("k741", v741); + map.put("k742", v742); + map.put("k743", v743); + map.put("k744", v744); + map.put("k745", v745); + map.put("k746", v746); + map.put("k747", v747); + map.put("k748", v748); + map.put("k749", v749); + map.put("k750", v750); + map.put("k751", v751); + map.put("k752", v752); + map.put("k753", v753); + map.put("k754", v754); + map.put("k755", v755); + map.put("k756", v756); + map.put("k757", v757); + map.put("k758", v758); + map.put("k759", v759); + map.put("k760", v760); + map.put("k761", v761); + map.put("k762", v762); + map.put("k763", v763); + map.put("k764", v764); + map.put("k765", v765); + map.put("k766", v766); + map.put("k767", v767); + map.put("k768", v768); + map.put("k769", v769); + map.put("k770", v770); + map.put("k771", v771); + map.put("k772", v772); + map.put("k773", v773); + map.put("k774", v774); + map.put("k775", v775); + map.put("k776", v776); + map.put("k777", v777); + map.put("k778", v778); + map.put("k779", v779); + map.put("k780", v780); + map.put("k781", v781); + map.put("k782", v782); + map.put("k783", v783); + map.put("k784", v784); + map.put("k785", v785); + map.put("k786", v786); + map.put("k787", v787); + map.put("k788", v788); + map.put("k789", v789); + map.put("k790", v790); + map.put("k791", v791); + map.put("k792", v792); + map.put("k793", v793); + map.put("k794", v794); + map.put("k795", v795); + map.put("k796", v796); + map.put("k797", v797); + map.put("k798", v798); + map.put("k799", v799); + map.put("k800", v800); + map.put("k801", v801); + map.put("k802", v802); + map.put("k803", v803); + map.put("k804", v804); + map.put("k805", v805); + map.put("k806", v806); + map.put("k807", v807); + map.put("k808", v808); + map.put("k809", v809); + map.put("k810", v810); + map.put("k811", v811); + map.put("k812", v812); + map.put("k813", v813); + map.put("k814", v814); + map.put("k815", v815); + map.put("k816", v816); + map.put("k817", v817); + map.put("k818", v818); + map.put("k819", v819); + map.put("k820", v820); + map.put("k821", v821); + map.put("k822", v822); + map.put("k823", v823); + map.put("k824", v824); + map.put("k825", v825); + map.put("k826", v826); + map.put("k827", v827); + map.put("k828", v828); + map.put("k829", v829); + map.put("k830", v830); + map.put("k831", v831); + map.put("k832", v832); + map.put("k833", v833); + map.put("k834", v834); + map.put("k835", v835); + map.put("k836", v836); + map.put("k837", v837); + map.put("k838", v838); + map.put("k839", v839); + map.put("k840", v840); + map.put("k841", v841); + map.put("k842", v842); + map.put("k843", v843); + map.put("k844", v844); + map.put("k845", v845); + map.put("k846", v846); + map.put("k847", v847); + map.put("k848", v848); + map.put("k849", v849); + map.put("k850", v850); + map.put("k851", v851); + map.put("k852", v852); + map.put("k853", v853); + map.put("k854", v854); + map.put("k855", v855); + map.put("k856", v856); + map.put("k857", v857); + map.put("k858", v858); + map.put("k859", v859); + map.put("k860", v860); + map.put("k861", v861); + map.put("k862", v862); + map.put("k863", v863); + map.put("k864", v864); + map.put("k865", v865); + map.put("k866", v866); + map.put("k867", v867); + map.put("k868", v868); + map.put("k869", v869); + map.put("k870", v870); + map.put("k871", v871); + map.put("k872", v872); + map.put("k873", v873); + map.put("k874", v874); + map.put("k875", v875); + map.put("k876", v876); + map.put("k877", v877); + map.put("k878", v878); + map.put("k879", v879); + map.put("k880", v880); + map.put("k881", v881); + map.put("k882", v882); + map.put("k883", v883); + map.put("k884", v884); + map.put("k885", v885); + map.put("k886", v886); + map.put("k887", v887); + map.put("k888", v888); + map.put("k889", v889); + map.put("k890", v890); + map.put("k891", v891); + map.put("k892", v892); + map.put("k893", v893); + map.put("k894", v894); + map.put("k895", v895); + map.put("k896", v896); + map.put("k897", v897); + map.put("k898", v898); + map.put("k899", v899); + map.put("k900", v900); + map.put("k901", v901); + map.put("k902", v902); + map.put("k903", v903); + map.put("k904", v904); + map.put("k905", v905); + map.put("k906", v906); + map.put("k907", v907); + map.put("k908", v908); + map.put("k909", v909); + map.put("k910", v910); + map.put("k911", v911); + map.put("k912", v912); + map.put("k913", v913); + map.put("k914", v914); + map.put("k915", v915); + map.put("k916", v916); + map.put("k917", v917); + map.put("k918", v918); + map.put("k919", v919); + map.put("k920", v920); + map.put("k921", v921); + map.put("k922", v922); + map.put("k923", v923); + map.put("k924", v924); + map.put("k925", v925); + map.put("k926", v926); + map.put("k927", v927); + map.put("k928", v928); + map.put("k929", v929); + map.put("k930", v930); + map.put("k931", v931); + map.put("k932", v932); + map.put("k933", v933); + map.put("k934", v934); + map.put("k935", v935); + map.put("k936", v936); + map.put("k937", v937); + map.put("k938", v938); + map.put("k939", v939); + map.put("k940", v940); + map.put("k941", v941); + map.put("k942", v942); + map.put("k943", v943); + map.put("k944", v944); + map.put("k945", v945); + map.put("k946", v946); + map.put("k947", v947); + map.put("k948", v948); + map.put("k949", v949); + map.put("k950", v950); + map.put("k951", v951); + map.put("k952", v952); + map.put("k953", v953); + map.put("k954", v954); + map.put("k955", v955); + map.put("k956", v956); + map.put("k957", v957); + map.put("k958", v958); + map.put("k959", v959); + map.put("k960", v960); + map.put("k961", v961); + map.put("k962", v962); + map.put("k963", v963); + map.put("k964", v964); + map.put("k965", v965); + map.put("k966", v966); + map.put("k967", v967); + map.put("k968", v968); + map.put("k969", v969); + map.put("k970", v970); + map.put("k971", v971); + map.put("k972", v972); + map.put("k973", v973); + map.put("k974", v974); + map.put("k975", v975); + map.put("k976", v976); + map.put("k977", v977); + map.put("k978", v978); + map.put("k979", v979); + map.put("k980", v980); + map.put("k981", v981); + map.put("k982", v982); + map.put("k983", v983); + map.put("k984", v984); + map.put("k985", v985); + map.put("k986", v986); + map.put("k987", v987); + map.put("k988", v988); + map.put("k989", v989); + map.put("k990", v990); + map.put("k991", v991); + map.put("k992", v992); + map.put("k993", v993); + map.put("k994", v994); + map.put("k995", v995); + map.put("k996", v996); + map.put("k997", v997); + map.put("k998", v998); + map.put("k999", v999); + } } diff --git a/checker/jtreg/nullness/Issue1809.java b/checker/jtreg/nullness/Issue1809.java index 9e7e8115924..94d03d624d6 100644 --- a/checker/jtreg/nullness/Issue1809.java +++ b/checker/jtreg/nullness/Issue1809.java @@ -16,25 +16,25 @@ @SuppressWarnings("unchecked") abstract class Issue1809 { - abstract Stream concat(Stream... streams); + abstract Stream concat(Stream... streams); - abstract Optional f(); + abstract Optional f(); - private static class A {} + private static class A {} - interface B { - List g(); - } + interface B { + List g(); + } - interface C { - List h(); - } + interface C { + List h(); + } - interface S {} + interface S {} - private Stream xrefsFor(B b) { - return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) - .filter(Optional::isPresent) - .map(Optional::get); - } + private Stream xrefsFor(B b) { + return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) + .filter(Optional::isPresent) + .map(Optional::get); + } } diff --git a/checker/jtreg/nullness/Issue2853.java b/checker/jtreg/nullness/Issue2853.java index f80d9591a60..ba037ecc5ac 100644 --- a/checker/jtreg/nullness/Issue2853.java +++ b/checker/jtreg/nullness/Issue2853.java @@ -6,114 +6,114 @@ */ public class Issue2853 { - abstract static class A { + abstract static class A { - abstract B getB(); + abstract B getB(); - public abstract C getC(); + public abstract C getC(); - public abstract Object getD(); + public abstract Object getD(); - public abstract Object getE(); + public abstract Object getE(); - public abstract Object getF(); + public abstract Object getF(); - public abstract Object getG(); + public abstract Object getG(); - public abstract H getH(); - } + public abstract H getH(); + } - abstract static class B {} + abstract static class B {} - abstract static class C { + abstract static class C { - abstract Object getI(); - } + abstract Object getI(); + } - static class I { + static class I { - static class J {} - } + static class J {} + } - abstract static class H { + abstract static class H { - abstract Object getK(); + abstract Object getK(); - abstract Object getL(); + abstract Object getL(); - abstract Object getM(); + abstract Object getM(); - abstract Object getN(); - } + abstract Object getN(); + } - abstract static class O {} + abstract static class O {} - abstract static class J { + abstract static class J { - static O f(B b) { - throw new AssertionError(); - } + static O f(B b) { + throw new AssertionError(); } + } - abstract static class K { + abstract static class K { - abstract Object getL(); - } + abstract Object getL(); + } - abstract static class M { + abstract static class M { - abstract N getN(); - } + abstract N getN(); + } - abstract static class N { + abstract static class N { - abstract Object f(); + abstract Object f(); + } + + static class Test { + + static final ImmutableSet

C = + new ImmutableSet.Builder

() + .add(R.c((A x) -> J.f(x.getB()))) + .add(R.c((A x) -> x.getC().getI())) + .add(R.c((M x) -> x.getN().f())) + .add(R.c((A x) -> x.getH().getK())) + .add(R.c((A x) -> x.getH().getM())) + .add(R.c((A x) -> x.getH().getL())) + .add(R.c((A x) -> x.getH().getN())) + .add(R.c((A x) -> x.getD())) + .add(R.c((A x) -> x.getE())) + .add(R.c((A x) -> x.getE())) + .add(R.c((A x) -> x.getG())) + .add(R.c((K x) -> x.getL())) + .build(); + + interface P {} + + interface Q { + + V get(U message); } - static class Test { - - static final ImmutableSet

C = - new ImmutableSet.Builder

() - .add(R.c((A x) -> J.f(x.getB()))) - .add(R.c((A x) -> x.getC().getI())) - .add(R.c((M x) -> x.getN().f())) - .add(R.c((A x) -> x.getH().getK())) - .add(R.c((A x) -> x.getH().getM())) - .add(R.c((A x) -> x.getH().getL())) - .add(R.c((A x) -> x.getH().getN())) - .add(R.c((A x) -> x.getD())) - .add(R.c((A x) -> x.getE())) - .add(R.c((A x) -> x.getE())) - .add(R.c((A x) -> x.getG())) - .add(R.c((K x) -> x.getL())) - .build(); - - interface P {} - - interface Q { - - V get(U message); - } - - private static class R implements P { - - static R c(Q x) { - throw new AssertionError(); - } - } + private static class R implements P { + + static R c(Q x) { + throw new AssertionError(); + } } + } - abstract static class ImmutableSet { + abstract static class ImmutableSet { - static class Builder { + static class Builder { - public Builder add(E... elements) { - throw new AssertionError(); - } + public Builder add(E... elements) { + throw new AssertionError(); + } - public ImmutableSet build() { - throw new AssertionError(); - } - } + public ImmutableSet build() { + throw new AssertionError(); + } } + } } diff --git a/checker/jtreg/nullness/Issue347.java b/checker/jtreg/nullness/Issue347.java index 29692279f03..0ebc27dba7b 100644 --- a/checker/jtreg/nullness/Issue347.java +++ b/checker/jtreg/nullness/Issue347.java @@ -11,24 +11,24 @@ public class Issue347 { - @MonotonicNonNull String mono; + @MonotonicNonNull String mono; - @Nullable String nble; + @Nullable String nble; - void testMono() { - if (mono == null) { - return; - } - // The object referenced by mono might change, but it can't become null again, even in - // concurrent semantics. - mono.toString(); + void testMono() { + if (mono == null) { + return; } + // The object referenced by mono might change, but it can't become null again, even in + // concurrent semantics. + mono.toString(); + } - void testNble() { - if (nble == null) { - return; - } - // error expected in concurrent semantics only - nble.toString(); + void testNble() { + if (nble == null) { + return; } + // error expected in concurrent semantics only + nble.toString(); + } } diff --git a/checker/jtreg/nullness/Issue373.java b/checker/jtreg/nullness/Issue373.java index a11a6697dea..b0e19bf5598 100644 --- a/checker/jtreg/nullness/Issue373.java +++ b/checker/jtreg/nullness/Issue373.java @@ -12,8 +12,8 @@ import java.util.Set; public class Issue373 extends AbstractMap { - @Override - public Set> entrySet() { - return Collections.emptySet(); - } + @Override + public Set> entrySet() { + return Collections.emptySet(); + } } diff --git a/checker/jtreg/nullness/Issue4948.java b/checker/jtreg/nullness/Issue4948.java index 4c33e056843..798918a9082 100644 --- a/checker/jtreg/nullness/Issue4948.java +++ b/checker/jtreg/nullness/Issue4948.java @@ -4,1222 +4,1221 @@ * * @compile/timeout=30 -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint Issue4948.java */ +import java.util.stream.Stream; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; -import java.util.stream.Stream; - @SuppressWarnings("nullness") // Check for timeout. public class Issue4948 { - static class MyClass { + static class MyClass { - // No type argumment inference. - static MyClass of2(@Nullable Object[]... p) { - throw new RuntimeException(); - } - - // Poly qualifiers - static MyClass<@PolyNull Object[]> of3(@PolyNull Object[]... p) { - throw new RuntimeException(); - } + // No type argumment inference. + static MyClass of2(@Nullable Object[]... p) { + throw new RuntimeException(); } - static Stream parseTest() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[1], - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); + // Poly qualifiers + static MyClass<@PolyNull Object[]> of3(@PolyNull Object[]... p) { + throw new RuntimeException(); } + } - static Stream parseTest2() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTest() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[1], + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTest4() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTest2() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static MyClass parseTest11() { - return MyClass.of2( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTest4() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static MyClass parseTestB4() { - return MyClass.of3( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static MyClass parseTest11() { + return MyClass.of2( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTestB9() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static MyClass parseTestB4() { + return MyClass.of3( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTestB10() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTestB9() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTestB11() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTestB10() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } + + static Stream parseTestB11() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } } diff --git a/checker/jtreg/nullness/Issue6373.java b/checker/jtreg/nullness/Issue6373.java index 1c17141251a..2da6966fd6a 100644 --- a/checker/jtreg/nullness/Issue6373.java +++ b/checker/jtreg/nullness/Issue6373.java @@ -7,133 +7,133 @@ */ public class Issue6373 { - abstract static class C1< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5> - extends C6 {} - - static class C6 {} - - abstract static class C2< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - RT extends C5> - implements C7 {} - - abstract static class C3< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - R extends C5> {} - - abstract static class C4< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - R extends C5> { - interface I {} - } - - abstract static class C5> implements C7 {} - - interface C7 {} - - abstract static class C8< + abstract static class C1< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5> + extends C6 {} + + static class C6 {} + + abstract static class C2< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + RT extends C5> + implements C7 {} + + abstract static class C3< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> {} + + abstract static class C4< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> { + interface I {} + } + + abstract static class C5> implements C7 {} + + interface C7 {} + + abstract static class C8< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> { + + public static < C extends C1, Q extends C2, B extends C3, D extends C4, CR extends C5, - RpT extends C5> { - - public static < - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5, - RpT extends C5> - Builder n(Q q) { - throw new AssertionError(); - } - - abstract static class Builder< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5, - RpT extends C5> { - - public abstract Builder f(C9 p); - - public C8 b() { - throw new AssertionError(); - } - } + RpT extends C5> + Builder n(Q q) { + throw new AssertionError(); } - abstract static class C9> {} + abstract static class Builder< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> { - static final class C10 extends C9 {} + public abstract Builder f(C9 p); - abstract static class C11, B extends C11> {} + public C8 b() { + throw new AssertionError(); + } + } + } - static final class C12 extends C11 { + abstract static class C9> {} - public C10 b() { - throw new AssertionError(); - } - } + static final class C10 extends C9 {} - static class C13 { + abstract static class C11, B extends C11> {} - public static final C12 n() { - return new C12(); - } + static final class C12 extends C11 { - static final class C14 extends C1 {} + public C10 b() { + throw new AssertionError(); + } + } - static final class C15 extends C2 {} + static class C13 { - static final class C18 extends C5 {} + public static final C12 n() { + return new C12(); + } - static class C17 extends C4 implements C4.I, C19 {} + static final class C14 extends C1 {} - static final class C16 extends C3 { + static final class C15 extends C2 {} - public C15 b() { - throw new AssertionError(); - } - } + static final class C18 extends C5 {} - static final C16 q() { - throw new AssertionError(); - } - } + static class C17 extends C4 implements C4.I, C19 {} - interface C19 {} + static final class C16 extends C3 { - void f() { - var x = C8.n(C13.q().b()).f(C13.n().b()).b(); - var y = x; - x = y; - x = y; - x = y; + public C15 b() { + throw new AssertionError(); + } } - void g() { - Object x = C8.n(C13.q().b()).f(C13.n().b()).b(); - Object y = x; - x = y; - x = y; - x = y; + static final C16 q() { + throw new AssertionError(); } + } + + interface C19 {} + + void f() { + var x = C8.n(C13.q().b()).f(C13.n().b()).b(); + var y = x; + x = y; + x = y; + x = y; + } + + void g() { + Object x = C8.n(C13.q().b()).f(C13.n().b()).b(); + Object y = x; + x = y; + x = y; + x = y; + } } diff --git a/checker/jtreg/nullness/PersistUtil.java b/checker/jtreg/nullness/PersistUtil.java index 1ce20637f24..db88815ebf1 100644 --- a/checker/jtreg/nullness/PersistUtil.java +++ b/checker/jtreg/nullness/PersistUtil.java @@ -3,7 +3,6 @@ // TODO: add a @Processor method-annotation to parameterize import com.sun.tools.classfile.ClassFile; - import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -25,98 +24,97 @@ */ public class PersistUtil { - public static String testClassOf(Method m) { - TestClass tc = m.getAnnotation(TestClass.class); - if (tc != null) { - return tc.value(); - } else { - return "Test"; - } + public static String testClassOf(Method m) { + TestClass tc = m.getAnnotation(TestClass.class); + if (tc != null) { + return tc.value(); + } else { + return "Test"; } - - public static ClassFile compileAndReturn(String fullFile, String testClass) throws Exception { - File source = writeTestFile(fullFile); - File clazzFile = compileTestFile(source, testClass); - return ClassFile.read(clazzFile); + } + + public static ClassFile compileAndReturn(String fullFile, String testClass) throws Exception { + File source = writeTestFile(fullFile); + File clazzFile = compileTestFile(source, testClass); + return ClassFile.read(clazzFile); + } + + public static File writeTestFile(String fullFile) throws IOException { + File f = new File("Test.java"); + try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)))) { + out.println(fullFile); } - - public static File writeTestFile(String fullFile) throws IOException { - File f = new File("Test.java"); - try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)))) { - out.println(fullFile); - } - return f; + return f; + } + + public static File compileTestFile(File f, String testClass) { + int rc = + com.sun.tools.javac.Main.compile( + new String[] { + "-AnoJreVersionCheck", + "-g", + "-processor", + "org.checkerframework.checker.nullness.NullnessChecker", + f.getPath() + }); + if (rc != 0) { + throw new Error("compilation failed. rc=" + rc); } - public static File compileTestFile(File f, String testClass) { - int rc = - com.sun.tools.javac.Main.compile( - new String[] { - "-AnoJreVersionCheck", - "-g", - "-processor", - "org.checkerframework.checker.nullness.NullnessChecker", - f.getPath() - }); - if (rc != 0) { - throw new Error("compilation failed. rc=" + rc); - } - - File result = new File(f.getParent(), testClass + ".class"); - - // This diagnostic code preserves temporary files and prints the paths where they are - // preserved. - if (false) { - try { - File tempDir = new File(System.getProperty("java.io.tmpdir")); - File fCopy = File.createTempFile("FCopy", ".java", tempDir); - File resultCopy = File.createTempFile("FCopy", ".class", tempDir); - // REPLACE_EXISTING is essential in the `Files.copy()` calls because createTempFile - // actually creates a file in addition to returning its name. - Files.copy(f.toPath(), fCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); - Files.copy( - result.toPath(), resultCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); - System.out.printf("comileTestFile: copied to %s %s%n", fCopy, resultCopy); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - return result; + File result = new File(f.getParent(), testClass + ".class"); + + // This diagnostic code preserves temporary files and prints the paths where they are + // preserved. + if (false) { + try { + File tempDir = new File(System.getProperty("java.io.tmpdir")); + File fCopy = File.createTempFile("FCopy", ".java", tempDir); + File resultCopy = File.createTempFile("FCopy", ".class", tempDir); + // REPLACE_EXISTING is essential in the `Files.copy()` calls because createTempFile + // actually creates a file in addition to returning its name. + Files.copy(f.toPath(), fCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(result.toPath(), resultCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); + System.out.printf("comileTestFile: copied to %s %s%n", fCopy, resultCopy); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - public static String wrap(String compact) { - StringJoiner sj = new StringJoiner(System.lineSeparator()); + return result; + } - // Automatically import java.util - sj.add(""); - sj.add("import java.util.*;"); - sj.add("import java.lang.annotation.*;"); + public static String wrap(String compact) { + StringJoiner sj = new StringJoiner(System.lineSeparator()); - // And the Nullness qualifiers - sj.add("import org.checkerframework.framework.qual.DefaultQualifier;"); - sj.add("import org.checkerframework.checker.nullness.qual.*;"); - sj.add("import org.checkerframework.dataflow.qual.*;"); + // Automatically import java.util + sj.add(""); + sj.add("import java.util.*;"); + sj.add("import java.lang.annotation.*;"); - sj.add(""); - boolean isSnippet = - !(compact.startsWith("class") || compact.contains(" class")) - && !compact.contains("interface") - && !compact.contains("enum"); + // And the Nullness qualifiers + sj.add("import org.checkerframework.framework.qual.DefaultQualifier;"); + sj.add("import org.checkerframework.checker.nullness.qual.*;"); + sj.add("import org.checkerframework.dataflow.qual.*;"); - if (isSnippet) { - sj.add("class Test {"); - } + sj.add(""); + boolean isSnippet = + !(compact.startsWith("class") || compact.contains(" class")) + && !compact.contains("interface") + && !compact.contains("enum"); - sj.add(compact); + if (isSnippet) { + sj.add("class Test {"); + } - if (isSnippet) { - sj.add("}"); - sj.add(""); - } + sj.add(compact); - return sj.toString(); + if (isSnippet) { + sj.add("}"); + sj.add(""); } + + return sj.toString(); + } } /** @@ -126,5 +124,5 @@ public static String wrap(String compact) { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TestClass { - String value() default "Test"; + String value() default "Test"; } diff --git a/checker/jtreg/nullness/UncheckedWarning.java b/checker/jtreg/nullness/UncheckedWarning.java index 64d11743e19..92d5c98a55a 100644 --- a/checker/jtreg/nullness/UncheckedWarning.java +++ b/checker/jtreg/nullness/UncheckedWarning.java @@ -10,17 +10,17 @@ import java.util.List; class Test { - List foo() { - List ret = new ArrayList<>(); - ret.add("Hi there!"); - return (List) ret; - } + List foo() { + List ret = new ArrayList<>(); + ret.add("Hi there!"); + return (List) ret; + } } public class UncheckedWarning { - public static void main(String[] args) { - Test ti = new Test<>(); - List ls = ti.foo(); - Integer i = ls.get(0); - } + public static void main(String[] args) { + Test ti = new Test<>(); + List ls = ti.foo(); + Integer i = ls.get(0); + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java index bce171b1e3c..786b89d0c15 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java @@ -8,8 +8,8 @@ @SuppressWarnings("nullness:unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java index c3c23a99d98..9b654510aa7 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java @@ -8,8 +8,8 @@ @SuppressWarnings("nullness:unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java index 4aa9fc66512..45549b05b9d 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java @@ -8,8 +8,8 @@ @SuppressWarnings("unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return.type.incompatible") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return.type.incompatible") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java index 9365586d354..465c6ec1e9e 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java @@ -8,8 +8,8 @@ @SuppressWarnings("unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return.type.incompatible") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return.type.incompatible") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsTest.java b/checker/jtreg/nullness/UnneededSuppressionsTest.java index 81202e12796..e2b5c602b61 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsTest.java +++ b/checker/jtreg/nullness/UnneededSuppressionsTest.java @@ -7,28 +7,28 @@ class UnneededSuppressionsTest { - @SuppressWarnings({"nullness:return.type.incompatible"}) - public String getClassAndUid1() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible"}) + public String getClassAndUid1() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) - public String getClassAndUid2() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) + public String getClassAndUid2() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) - public String getClassAndUid3() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) + public String getClassAndUid3() { + return "hello"; + } - @SuppressWarnings({"unneeded.suppression", "nullness:return.type.incompatible"}) - public String getClassAndUid5() { - return "hello"; - } + @SuppressWarnings({"unneeded.suppression", "nullness:return.type.incompatible"}) + public String getClassAndUid5() { + return "hello"; + } - @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) - public String getClassAndUid6() { - return "hello"; - } + @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) + public String getClassAndUid6() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java b/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java index a526a029f25..f822b77ed9c 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java +++ b/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java @@ -7,31 +7,28 @@ class UnneededSuppressionsTest { - @SuppressWarnings({"nullness:return.type.incompatible"}) - public String getClassAndUid1() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible"}) + public String getClassAndUid1() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) - public String getClassAndUid2() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) + public String getClassAndUid2() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) - public String getClassAndUid3() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) + public String getClassAndUid3() { + return "hello"; + } - @SuppressWarnings({ - "unneeded.suppression.type.incompatible", - "nullness:return.type.incompatible" - }) - public String getClassAndUid5() { - return "hello"; - } + @SuppressWarnings({"unneeded.suppression.type.incompatible", "nullness:return.type.incompatible"}) + public String getClassAndUid5() { + return "hello"; + } - @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) - public String getClassAndUid6() { - return "hello"; - } + @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) + public String getClassAndUid6() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/annotationsOnExtends/Other.java b/checker/jtreg/nullness/annotationsOnExtends/Other.java index f0cc1b66948..67a8736a7f8 100644 --- a/checker/jtreg/nullness/annotationsOnExtends/Other.java +++ b/checker/jtreg/nullness/annotationsOnExtends/Other.java @@ -7,8 +7,8 @@ */ public class Other { - void foo() { - Test other = null; - Test2 other2 = null; - } + void foo() { + Test other = null; + Test2 other2 = null; + } } diff --git a/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java index 431446ff681..ffb839371cc 100644 --- a/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java +++ b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java @@ -7,10 +7,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public class DefaultConstructor { - Object nullObject; - @MonotonicNonNull Object lazyField; + Object nullObject; + @MonotonicNonNull Object lazyField; - public Object getNull() { - return nullObject; - } + public Object getNull() { + return nullObject; + } } diff --git a/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java index 43023cae6b3..01addfea9a6 100644 --- a/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java +++ b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java @@ -7,29 +7,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public class NonDefaultConstructor { - Object nonNull = 4; - Object nullObject; - @MonotonicNonNull Object lazyField; + Object nonNull = 4; + Object nullObject; + @MonotonicNonNull Object lazyField; - // error doesn't initialize nullObject - public NonDefaultConstructor() {} + // error doesn't initialize nullObject + public NonDefaultConstructor() {} - // error doesn't initialize nullObject - public NonDefaultConstructor(int i) { - lazyField = "m"; - } + // error doesn't initialize nullObject + public NonDefaultConstructor(int i) { + lazyField = "m"; + } - // OK, lazyField is lazy! - public NonDefaultConstructor(double a) { - nullObject = "n"; - } + // OK, lazyField is lazy! + public NonDefaultConstructor(double a) { + nullObject = "n"; + } - public NonDefaultConstructor(String s) { - nullObject = "a"; - lazyField = "m"; - } + public NonDefaultConstructor(String s) { + nullObject = "a"; + lazyField = "m"; + } - public Object getNull() { - return nullObject; - } + public Object getNull() { + return nullObject; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Classes.java b/checker/jtreg/nullness/defaultsPersist/Classes.java index 3064a45e285..49c0903ca6f 100644 --- a/checker/jtreg/nullness/defaultsPersist/Classes.java +++ b/checker/jtreg/nullness/defaultsPersist/Classes.java @@ -11,234 +11,234 @@ public class Classes { - /* TODO: store extends/implements in TypesIntoElements. - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), - }) - public String extendsDefault1() { - return "class Test {}"; - } + /* TODO: store extends/implements in TypesIntoElements. + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), + }) + public String extendsDefault1() { + return "class Test {}"; + } - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), - }) - public String extendsDefault2() { - return "class Test extends Object {}"; - } + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), + }) + public String extendsDefault2() { + return "class Test extends Object {}"; + } - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=0), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=0), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=0), - }) - public String extendsDefault3() { - return "class Test implements java.io.Serializable {}"; - } - */ + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=0), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=0), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=0), + }) + public String extendsDefault3() { + return "class Test implements java.io.Serializable {}"; + } + */ - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams1() { - return "class Test {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams1() { + return "class Test {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams2() { - return "class Test {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams2() { + return "class Test {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams3() { - return "class Test> {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams3() { + return "class Test> {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 1, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 1, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 1, - boundIndex = 1), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams4() { - return "class Test> {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 1, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 1, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 1, + boundIndex = 1), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams4() { + return "class Test> {}"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Constructors.java b/checker/jtreg/nullness/defaultsPersist/Constructors.java index fdc5c147ca6..a5389af6d59 100644 --- a/checker/jtreg/nullness/defaultsPersist/Constructors.java +++ b/checker/jtreg/nullness/defaultsPersist/Constructors.java @@ -14,197 +14,197 @@ public class Constructors { - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - }) - public String paramDefault1() { - return "Test(Object o) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + }) + public String paramDefault1() { + return "Test(Object o) {}"; + } - @TADescriptions({ - // Should there be defaults? - // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription(annotation = - // "org/checkerframework/checker/initialization/qual/Initialized", type = METHOD_RETURN), - // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String retDefault1() { - return "Test() {}"; - } + @TADescriptions({ + // Should there be defaults? + // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription(annotation = + // "org/checkerframework/checker/initialization/qual/Initialized", type = METHOD_RETURN), + // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String retDefault1() { + return "Test() {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), - }) - public String throwsDefault1() { - return "Test() throws Throwable {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), + }) + public String throwsDefault1() { + return "Test() throws Throwable {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 1), - }) - public String throwsDefault2() { - return "Test() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 1), + }) + public String throwsDefault2() { + return "Test() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_RECEIVER), - }) - @TestClass("Outer$Inner") - public String recvDefault1() { - return "class Outer {" + " class Inner {" + " Inner(Outer Outer.this) {}" + " }" + "}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_RECEIVER), + }) + @TestClass("Outer$Inner") + public String recvDefault1() { + return "class Outer {" + " class Inner {" + " Inner(Outer Outer.this) {}" + " }" + "}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams1() { - return " Test(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams1() { + return " Test(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams2() { - return " Test(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams2() { + return " Test(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - }) - public String typeParams3() { - return "> Test(M2 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + }) + public String typeParams3() { + return "> Test(M2 p) {}"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Driver.java b/checker/jtreg/nullness/defaultsPersist/Driver.java index cd45901ec97..18de0aeb16d 100644 --- a/checker/jtreg/nullness/defaultsPersist/Driver.java +++ b/checker/jtreg/nullness/defaultsPersist/Driver.java @@ -8,7 +8,6 @@ import com.sun.tools.classfile.ClassFile; import com.sun.tools.classfile.TypeAnnotation; import com.sun.tools.classfile.TypeAnnotation.TargetType; - import java.io.PrintStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -20,201 +19,198 @@ public class Driver { - private static final PrintStream out = System.out; - - // The argument is in the format expected by Class.forName(). - public static void main(String[] args) throws Exception { - if (args.length != 1) { - throw new IllegalArgumentException("Usage: java Driver "); - } - String name = args[0]; - Class clazz = Class.forName(name); - new Driver().runDriver(clazz.newInstance()); - } - - protected void runDriver(Object object) throws Exception { - int passed = 0, failed = 0; - Class clazz = object.getClass(); - out.println("Tests for " + clazz.getName()); - - // Find methods - for (Method method : clazz.getMethods()) { - List expected = expectedOf(method); - if (expected == null) { - continue; - } - if (method.getReturnType() != String.class) { - throw new IllegalArgumentException( - "Test method needs to return a string: " + method); - } - String testClass = PersistUtil.testClassOf(method); - - try { - String compact = (String) method.invoke(object); - String fullFile = PersistUtil.wrap(compact); - ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); - boolean ignoreConstructors = !clazz.getName().equals("Constructors"); - List actual = - ReferenceInfoUtil.extendedAnnotationsOf(cf, ignoreConstructors); - String diagnostic = - String.join( - "; ", - "Tests for " + clazz.getName(), - "compact=" + compact, - "fullFile=" + fullFile, - "testClass=" + testClass); - ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); - out.println("PASSED: " + method.getName()); - ++passed; - } catch (Throwable e) { - out.println("FAILED: " + method.getName()); - out.println(" " + e); - ++failed; - } - } - - out.println(); - int total = passed + failed; - out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); - - out.flush(); - - if (failed != 0) { - throw new RuntimeException(failed + " tests failed"); - } - } - - private List expectedOf(Method m) { - TADescription ta = m.getAnnotation(TADescription.class); - TADescriptions tas = m.getAnnotation(TADescriptions.class); - - if (ta == null && tas == null) { - return null; - } - - List result = new ArrayList<>(); - - if (ta != null) { - result.add(expectedOf(ta)); - } - - if (tas != null) { - for (TADescription a : tas.value()) { - result.add(expectedOf(a)); - } - } - - return result; - } - - private AnnoPosPair expectedOf(TADescription d) { - String annoName = d.annotation(); - - TypeAnnotation.Position p = new TypeAnnotation.Position(); - p.type = d.type(); - if (d.offset() != NOT_SET) { - p.offset = d.offset(); - } - if (d.lvarOffset().length != 0) { - p.lvarOffset = d.lvarOffset(); - } - if (d.lvarLength().length != 0) { - p.lvarLength = d.lvarLength(); - } - if (d.lvarIndex().length != 0) { - p.lvarIndex = d.lvarIndex(); - } - if (d.boundIndex() != NOT_SET) { - p.bound_index = d.boundIndex(); - } - if (d.paramIndex() != NOT_SET) { - p.parameter_index = d.paramIndex(); - } - if (d.typeIndex() != NOT_SET) { - p.type_index = d.typeIndex(); - } - if (d.exceptionIndex() != NOT_SET) { - p.exception_index = d.exceptionIndex(); - } - if (d.genericLocation().length != 0) { - p.location = - TypeAnnotation.Position.getTypePathFromBinary( - wrapIntArray(d.genericLocation())); - } - - return AnnoPosPair.of(annoName, p); - } - - private List wrapIntArray(int[] ints) { - List list = new ArrayList<>(ints.length); - for (int i : ints) { - list.add(i); - } - return list; - } - - public static final int NOT_SET = -888; + private static final PrintStream out = System.out; + + // The argument is in the format expected by Class.forName(). + public static void main(String[] args) throws Exception { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: java Driver "); + } + String name = args[0]; + Class clazz = Class.forName(name); + new Driver().runDriver(clazz.newInstance()); + } + + protected void runDriver(Object object) throws Exception { + int passed = 0, failed = 0; + Class clazz = object.getClass(); + out.println("Tests for " + clazz.getName()); + + // Find methods + for (Method method : clazz.getMethods()) { + List expected = expectedOf(method); + if (expected == null) { + continue; + } + if (method.getReturnType() != String.class) { + throw new IllegalArgumentException("Test method needs to return a string: " + method); + } + String testClass = PersistUtil.testClassOf(method); + + try { + String compact = (String) method.invoke(object); + String fullFile = PersistUtil.wrap(compact); + ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); + boolean ignoreConstructors = !clazz.getName().equals("Constructors"); + List actual = + ReferenceInfoUtil.extendedAnnotationsOf(cf, ignoreConstructors); + String diagnostic = + String.join( + "; ", + "Tests for " + clazz.getName(), + "compact=" + compact, + "fullFile=" + fullFile, + "testClass=" + testClass); + ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); + out.println("PASSED: " + method.getName()); + ++passed; + } catch (Throwable e) { + out.println("FAILED: " + method.getName()); + out.println(" " + e); + ++failed; + } + } + + out.println(); + int total = passed + failed; + out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); + + out.flush(); + + if (failed != 0) { + throw new RuntimeException(failed + " tests failed"); + } + } + + private List expectedOf(Method m) { + TADescription ta = m.getAnnotation(TADescription.class); + TADescriptions tas = m.getAnnotation(TADescriptions.class); + + if (ta == null && tas == null) { + return null; + } + + List result = new ArrayList<>(); + + if (ta != null) { + result.add(expectedOf(ta)); + } + + if (tas != null) { + for (TADescription a : tas.value()) { + result.add(expectedOf(a)); + } + } + + return result; + } + + private AnnoPosPair expectedOf(TADescription d) { + String annoName = d.annotation(); + + TypeAnnotation.Position p = new TypeAnnotation.Position(); + p.type = d.type(); + if (d.offset() != NOT_SET) { + p.offset = d.offset(); + } + if (d.lvarOffset().length != 0) { + p.lvarOffset = d.lvarOffset(); + } + if (d.lvarLength().length != 0) { + p.lvarLength = d.lvarLength(); + } + if (d.lvarIndex().length != 0) { + p.lvarIndex = d.lvarIndex(); + } + if (d.boundIndex() != NOT_SET) { + p.bound_index = d.boundIndex(); + } + if (d.paramIndex() != NOT_SET) { + p.parameter_index = d.paramIndex(); + } + if (d.typeIndex() != NOT_SET) { + p.type_index = d.typeIndex(); + } + if (d.exceptionIndex() != NOT_SET) { + p.exception_index = d.exceptionIndex(); + } + if (d.genericLocation().length != 0) { + p.location = TypeAnnotation.Position.getTypePathFromBinary(wrapIntArray(d.genericLocation())); + } + + return AnnoPosPair.of(annoName, p); + } + + private List wrapIntArray(int[] ints) { + List list = new ArrayList<>(ints.length); + for (int i : ints) { + list.add(i); + } + return list; + } + + public static final int NOT_SET = -888; } /** A pair of an annotation name and a position. */ class AnnoPosPair { - /** The first element of the pair. */ - public final String first; - - /** The second element of the pair. */ - public final TypeAnnotation.Position second; - - /** - * Creates a new immutable pair. Clients should use {@link #of}. - * - * @param first the first element of the pair - * @param second the second element of the pair - */ - private AnnoPosPair(String first, TypeAnnotation.Position second) { - this.first = first; - this.second = second; - } - - /** - * Creates a new immutable pair. - * - * @param first first argument - * @param second second argument - * @return a pair of the values (first, second) - */ - public static AnnoPosPair of(String first, TypeAnnotation.Position second) { - return new AnnoPosPair(first, second); - } + /** The first element of the pair. */ + public final String first; + + /** The second element of the pair. */ + public final TypeAnnotation.Position second; + + /** + * Creates a new immutable pair. Clients should use {@link #of}. + * + * @param first the first element of the pair + * @param second the second element of the pair + */ + private AnnoPosPair(String first, TypeAnnotation.Position second) { + this.first = first; + this.second = second; + } + + /** + * Creates a new immutable pair. + * + * @param first first argument + * @param second second argument + * @return a pair of the values (first, second) + */ + public static AnnoPosPair of(String first, TypeAnnotation.Position second) { + return new AnnoPosPair(first, second); + } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TADescription { - String annotation(); + String annotation(); - TargetType type(); + TargetType type(); - int offset() default Driver.NOT_SET; + int offset() default Driver.NOT_SET; - int[] lvarOffset() default {}; + int[] lvarOffset() default {}; - int[] lvarLength() default {}; + int[] lvarLength() default {}; - int[] lvarIndex() default {}; + int[] lvarIndex() default {}; - int boundIndex() default Driver.NOT_SET; + int boundIndex() default Driver.NOT_SET; - int paramIndex() default Driver.NOT_SET; + int paramIndex() default Driver.NOT_SET; - int typeIndex() default Driver.NOT_SET; + int typeIndex() default Driver.NOT_SET; - int exceptionIndex() default Driver.NOT_SET; + int exceptionIndex() default Driver.NOT_SET; - int[] genericLocation() default {}; + int[] genericLocation() default {}; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TADescriptions { - TADescription[] value() default {}; + TADescription[] value() default {}; } diff --git a/checker/jtreg/nullness/defaultsPersist/Fields.java b/checker/jtreg/nullness/defaultsPersist/Fields.java index 95ecc80b88b..ad4b02c9e79 100644 --- a/checker/jtreg/nullness/defaultsPersist/Fields.java +++ b/checker/jtreg/nullness/defaultsPersist/Fields.java @@ -11,239 +11,227 @@ public class Fields { - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - }) - public String fieldDefault() { - return "Object f = new Object();"; - } + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + }) + public String fieldDefault() { + return "Object f = new Object();"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - }) - public String fieldDefaultOneExplicit() { - return "@NonNull Object f = new Object();"; - } + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + }) + public String fieldDefaultOneExplicit() { + return "@NonNull Object f = new Object();"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - }) - public String fieldWithDefaultQualifier() { - return "@DefaultQualifier(Nullable.class)" + System.lineSeparator() + " Object f;"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + }) + public String fieldWithDefaultQualifier() { + return "@DefaultQualifier(Nullable.class)" + System.lineSeparator() + " Object f;"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0}), - }) - public String fieldArray1() { - return "String[] sa = new String[1];"; - } + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0}), + }) + public String fieldArray1() { + return "String[] sa = new String[1];"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {0, 0, 0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0, 0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0, 0, 0, 0, 0}), - }) - public String fieldArray2() { - return "String[] @Nullable [][] saaa = new String[1][][];"; - } + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {0, 0, 0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0, 0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0, 0, 0, 0, 0}), + }) + public String fieldArray2() { + return "String[] @Nullable [][] saaa = new String[1][][];"; + } - @TADescriptions({ - // in front of the java.util.List - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), + @TADescriptions({ + // in front of the java.util.List + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), - // in front of Object //TODO: NEXT ANNO CHANGE TO NULLABLE WHEN WE GET JDK WORKING WITH THIS - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0}), + // in front of Object //TODO: NEXT ANNO CHANGE TO NULLABLE WHEN WE GET JDK WORKING WITH THIS + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0}), - // in front of the wildcard (?) - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0}), - }) - public String wildcards1() { - return "java.util.List f = new java.util.ArrayList<>();"; - } + // in front of the wildcard (?) + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0}), + }) + public String wildcards1() { + return "java.util.List f = new java.util.ArrayList<>();"; + } - @TADescriptions({ - // in front of the first java.util.List - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), + @TADescriptions({ + // in front of the first java.util.List + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), - // in front of the wildcard (?) - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0}), + // in front of the wildcard (?) + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0}), - // in front of the second java.util.List - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), - }) - public String wildcards2() { - return "java.util.List> f = new" - + " java.util.ArrayList<>();"; - } + // in front of the second java.util.List + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), + }) + public String wildcards2() { + return "java.util.List> f = new" + + " java.util.ArrayList<>();"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Methods.java b/checker/jtreg/nullness/defaultsPersist/Methods.java index 8aa50c2d270..8b2bf2ffd1f 100644 --- a/checker/jtreg/nullness/defaultsPersist/Methods.java +++ b/checker/jtreg/nullness/defaultsPersist/Methods.java @@ -15,198 +15,198 @@ public class Methods { - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - }) - public String paramDefault1() { - return "void pm1(Object o) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + }) + public String paramDefault1() { + return "void pm1(Object o) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_RETURN), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_RETURN), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_RETURN), - }) - public String retDefault1() { - return "Object rm1() { return new Object(); }"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_RETURN), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_RETURN), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_RETURN), + }) + public String retDefault1() { + return "Object rm1() { return new Object(); }"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), - }) - public String throwsDefault1() { - return "void tm1() throws Throwable {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), + }) + public String throwsDefault1() { + return "void tm1() throws Throwable {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), // from KeyFor - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 1), - }) - public String throwsDefault2() { - return "void tm2() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), // from KeyFor + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 1), + }) + public String throwsDefault2() { + return "void tm2() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_RECEIVER), - }) - public String recvDefault1() { - return "void rd1(Test this) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_RECEIVER), + }) + public String recvDefault1() { + return "void rd1(Test this) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams1() { - return " void foo(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams1() { + return " void foo(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams2() { - return " void foo(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams2() { + return " void foo(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - }) - public String typeParams3() { - return "> void bar(M2 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + }) + public String typeParams3() { + return "> void bar(M2 p) {}"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java index d7044639bb0..4f03292b6fb 100644 --- a/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java +++ b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java @@ -11,264 +11,254 @@ import com.sun.tools.classfile.Method; import com.sun.tools.classfile.RuntimeTypeAnnotations_attribute; import com.sun.tools.classfile.TypeAnnotation; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ReferenceInfoUtil { - public static final int IGNORE_VALUE = -321; - - /** If true, don't collect annotations on constructors. */ - boolean ignoreConstructors; - - /** - * Creates a new ReferenceInfoUtil. - * - * @param ignoreConstructors if true, don't collect annotations on constructor - */ - public ReferenceInfoUtil(boolean ignoreConstructors) { - this.ignoreConstructors = ignoreConstructors; + public static final int IGNORE_VALUE = -321; + + /** If true, don't collect annotations on constructors. */ + boolean ignoreConstructors; + + /** + * Creates a new ReferenceInfoUtil. + * + * @param ignoreConstructors if true, don't collect annotations on constructor + */ + public ReferenceInfoUtil(boolean ignoreConstructors) { + this.ignoreConstructors = ignoreConstructors; + } + + public static List extendedAnnotationsOf( + ClassFile cf, boolean ignoreConstructors) { + ReferenceInfoUtil riu = new ReferenceInfoUtil(ignoreConstructors); + List annos = new ArrayList<>(); + riu.findAnnotations(cf, annos); + return annos; + } + + /////////////////// Extract type annotations ////////////////// + private void findAnnotations(ClassFile cf, List annos) { + findAnnotations(cf, Attribute.RuntimeVisibleTypeAnnotations, annos); + findAnnotations(cf, Attribute.RuntimeInvisibleTypeAnnotations, annos); + + for (Field f : cf.fields) { + findAnnotations(cf, f, annos); } - - public static List extendedAnnotationsOf( - ClassFile cf, boolean ignoreConstructors) { - ReferenceInfoUtil riu = new ReferenceInfoUtil(ignoreConstructors); - List annos = new ArrayList<>(); - riu.findAnnotations(cf, annos); - return annos; + for (Method m : cf.methods) { + String methodName; + try { + methodName = m.getName(cf.constant_pool); + } catch (Exception e) { + throw new Error(e); + } + // This method, `findAnnotations`, aims to extract annotations from one method. + // In JDK 17, constructors are included in ClassFile.methods(); in JDK 11, they are not. + // Therefore, this if statement is required in JDK 17, and has no effect in JDK 11. + if (ignoreConstructors && methodName.equals("")) { + continue; + } + + findAnnotations(cf, m, annos); } - - /////////////////// Extract type annotations ////////////////// - private void findAnnotations(ClassFile cf, List annos) { - findAnnotations(cf, Attribute.RuntimeVisibleTypeAnnotations, annos); - findAnnotations(cf, Attribute.RuntimeInvisibleTypeAnnotations, annos); - - for (Field f : cf.fields) { - findAnnotations(cf, f, annos); - } - for (Method m : cf.methods) { - String methodName; - try { - methodName = m.getName(cf.constant_pool); - } catch (Exception e) { - throw new Error(e); - } - // This method, `findAnnotations`, aims to extract annotations from one method. - // In JDK 17, constructors are included in ClassFile.methods(); in JDK 11, they are not. - // Therefore, this if statement is required in JDK 17, and has no effect in JDK 11. - if (ignoreConstructors && methodName.equals("")) { - continue; - } - - findAnnotations(cf, m, annos); - } + } + + private static void findAnnotations(ClassFile cf, Method m, List annos) { + findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); + findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); + } + + private static void findAnnotations(ClassFile cf, Field m, List annos) { + findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); + findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); + } + + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's name. + */ + private static void findAnnotations(ClassFile cf, String name, List annos) { + int index = cf.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = cf.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); } - - private static void findAnnotations(ClassFile cf, Method m, List annos) { - findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); - findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); + } + + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's name. + */ + private static void findAnnotations( + ClassFile cf, Method m, String name, List annos) { + int index = m.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = m.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); } - private static void findAnnotations(ClassFile cf, Field m, List annos) { - findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); - findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); + int cindex = m.attributes.getIndex(cf.constant_pool, Attribute.Code); + if (cindex != -1) { + Attribute cattr = m.attributes.get(cindex); + assert cattr instanceof Code_attribute; + Code_attribute cAttr = (Code_attribute) cattr; + index = cAttr.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = cAttr.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); + } } - - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's - * name. - */ - private static void findAnnotations(ClassFile cf, String name, List annos) { - int index = cf.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = cf.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); - } + } + + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's name. + */ + private static void findAnnotations( + ClassFile cf, Field m, String name, List annos) { + int index = m.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = m.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); } + } - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's - * name. - */ - private static void findAnnotations( - ClassFile cf, Method m, String name, List annos) { - int index = m.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = m.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); - } - - int cindex = m.attributes.getIndex(cf.constant_pool, Attribute.Code); - if (cindex != -1) { - Attribute cattr = m.attributes.get(cindex); - assert cattr instanceof Code_attribute; - Code_attribute cAttr = (Code_attribute) cattr; - index = cAttr.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = cAttr.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); - } - } - } + /////////////////////// Equality testing ///////////////////// + private static boolean areEquals(int a, int b) { + return a == b || a == IGNORE_VALUE || b == IGNORE_VALUE; + } - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's - * name. - */ - private static void findAnnotations( - ClassFile cf, Field m, String name, List annos) { - int index = m.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = m.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); - } + private static boolean areEquals(int[] a, int[] a2) { + if (a == a2) { + return true; } - - /////////////////////// Equality testing ///////////////////// - private static boolean areEquals(int a, int b) { - return a == b || a == IGNORE_VALUE || b == IGNORE_VALUE; + if (a == null || a2 == null) { + return false; } - private static boolean areEquals(int[] a, int[] a2) { - if (a == a2) { - return true; - } - if (a == null || a2 == null) { - return false; - } - - int length = a.length; - if (a2.length != length) { - return false; - } - - for (int i = 0; i < length; i++) { - if (areEquals(a[i], a2[i])) { - return false; - } - } - - return true; + int length = a.length; + if (a2.length != length) { + return false; } - public static boolean areEquals(TypeAnnotation.Position p1, TypeAnnotation.Position p2) { - if (p1 == p2) { - return true; - } - if (p1 == null || p2 == null) { - return false; - } - - boolean result = - ((p1.type == p2.type) - && (p1.location.equals(p2.location)) - && areEquals(p1.offset, p2.offset) - && areEquals(p1.lvarOffset, p2.lvarOffset) - && areEquals(p1.lvarLength, p2.lvarLength) - && areEquals(p1.lvarIndex, p2.lvarIndex) - && areEquals(p1.bound_index, p2.bound_index) - && areEquals(p1.parameter_index, p2.parameter_index) - && areEquals(p1.type_index, p2.type_index) - && areEquals(p1.exception_index, p2.exception_index)); - return result; + for (int i = 0; i < length; i++) { + if (areEquals(a[i], a2[i])) { + return false; + } } - public static String positionCompareStr( - TypeAnnotation.Position p1, TypeAnnotation.Position p2) { - return String.join( - System.lineSeparator(), - "type = " + p1.type + ", " + p2.type, - "offset = " + p1.offset + ", " + p2.offset, - "lvarOffset = " + p1.lvarOffset + ", " + p2.lvarOffset, - "lvarLength = " + p1.lvarLength + ", " + p2.lvarLength, - "lvarIndex = " + p1.lvarIndex + ", " + p2.lvarIndex, - "bound_index = " + p1.bound_index + ", " + p2.bound_index, - "parameter_index = " + p1.parameter_index + ", " + p2.parameter_index, - "type_index = " + p1.type_index + ", " + p2.type_index, - "exception_index = " + p1.exception_index + ", " + p2.exception_index, - ""); + return true; + } + + public static boolean areEquals(TypeAnnotation.Position p1, TypeAnnotation.Position p2) { + if (p1 == p2) { + return true; + } + if (p1 == null || p2 == null) { + return false; } - private static TypeAnnotation findAnnotation( - String name, - TypeAnnotation.Position expected, - List annotations, - ClassFile cf) - throws InvalidIndex, UnexpectedEntry { - String properName = "L" + name + ";"; - for (TypeAnnotation anno : annotations) { - String actualName = cf.constant_pool.getUTF8Value(anno.annotation.type_index); - - if (properName.equals(actualName)) { - System.out.println("For Anno: " + actualName); - } - - if (properName.equals(actualName) && areEquals(expected, anno.position)) { - return anno; - } - } - return null; + boolean result = + ((p1.type == p2.type) + && (p1.location.equals(p2.location)) + && areEquals(p1.offset, p2.offset) + && areEquals(p1.lvarOffset, p2.lvarOffset) + && areEquals(p1.lvarLength, p2.lvarLength) + && areEquals(p1.lvarIndex, p2.lvarIndex) + && areEquals(p1.bound_index, p2.bound_index) + && areEquals(p1.parameter_index, p2.parameter_index) + && areEquals(p1.type_index, p2.type_index) + && areEquals(p1.exception_index, p2.exception_index)); + return result; + } + + public static String positionCompareStr(TypeAnnotation.Position p1, TypeAnnotation.Position p2) { + return String.join( + System.lineSeparator(), + "type = " + p1.type + ", " + p2.type, + "offset = " + p1.offset + ", " + p2.offset, + "lvarOffset = " + p1.lvarOffset + ", " + p2.lvarOffset, + "lvarLength = " + p1.lvarLength + ", " + p2.lvarLength, + "lvarIndex = " + p1.lvarIndex + ", " + p2.lvarIndex, + "bound_index = " + p1.bound_index + ", " + p2.bound_index, + "parameter_index = " + p1.parameter_index + ", " + p2.parameter_index, + "type_index = " + p1.type_index + ", " + p2.type_index, + "exception_index = " + p1.exception_index + ", " + p2.exception_index, + ""); + } + + private static TypeAnnotation findAnnotation( + String name, TypeAnnotation.Position expected, List annotations, ClassFile cf) + throws InvalidIndex, UnexpectedEntry { + String properName = "L" + name + ";"; + for (TypeAnnotation anno : annotations) { + String actualName = cf.constant_pool.getUTF8Value(anno.annotation.type_index); + + if (properName.equals(actualName)) { + System.out.println("For Anno: " + actualName); + } + + if (properName.equals(actualName) && areEquals(expected, anno.position)) { + return anno; + } + } + return null; + } + + public static boolean compare( + List expectedAnnos, + List actualAnnos, + ClassFile cf, + String diagnostic) + throws InvalidIndex, UnexpectedEntry { + if (actualAnnos.size() != expectedAnnos.size()) { + throw new ComparisonException( + "Wrong number of annotations in " + cf + "; " + diagnostic, expectedAnnos, actualAnnos); } - public static boolean compare( - List expectedAnnos, - List actualAnnos, - ClassFile cf, - String diagnostic) - throws InvalidIndex, UnexpectedEntry { - if (actualAnnos.size() != expectedAnnos.size()) { - throw new ComparisonException( - "Wrong number of annotations in " + cf + "; " + diagnostic, - expectedAnnos, - actualAnnos); - } - - for (AnnoPosPair e : expectedAnnos) { - String aName = e.first; - TypeAnnotation.Position expected = e.second; - TypeAnnotation actual = findAnnotation(aName, expected, actualAnnos, cf); - if (actual == null) { - throw new ComparisonException( - "Expected annotation not found: " - + aName - + " position: " - + expected - + "; " - + diagnostic, - expectedAnnos, - actualAnnos); - } - } - return true; + for (AnnoPosPair e : expectedAnnos) { + String aName = e.first; + TypeAnnotation.Position expected = e.second; + TypeAnnotation actual = findAnnotation(aName, expected, actualAnnos, cf); + if (actual == null) { + throw new ComparisonException( + "Expected annotation not found: " + + aName + + " position: " + + expected + + "; " + + diagnostic, + expectedAnnos, + actualAnnos); + } } + return true; + } } class ComparisonException extends RuntimeException { - private static final long serialVersionUID = -3930499712333815821L; - - public final List expected; - public final List found; - - public ComparisonException( - String message, List expected, List found) { - super(message); - this.expected = expected; - this.found = found; - } - - public String toString() { - return String.format( - "%s%n Expected (%d): %s%s Found (%d): %s", - super.toString(), expected.size(), expected, found.size(), found); - } + private static final long serialVersionUID = -3930499712333815821L; + + public final List expected; + public final List found; + + public ComparisonException( + String message, List expected, List found) { + super(message); + this.expected = expected; + this.found = found; + } + + public String toString() { + return String.format( + "%s%n Expected (%d): %s%s Found (%d): %s", + super.toString(), expected.size(), expected, found.size(), found); + } } diff --git a/checker/jtreg/nullness/eisop673/Lib.java b/checker/jtreg/nullness/eisop673/Lib.java index 42810b179d8..07801379a9d 100644 --- a/checker/jtreg/nullness/eisop673/Lib.java +++ b/checker/jtreg/nullness/eisop673/Lib.java @@ -1,5 +1,5 @@ import org.jetbrains.annotations.Nullable; interface Lib { - @Nullable String[] get(); + @Nullable String[] get(); } diff --git a/checker/jtreg/nullness/eisop673/User.java b/checker/jtreg/nullness/eisop673/User.java index 20e4b80c61e..f13fb42746c 100644 --- a/checker/jtreg/nullness/eisop673/User.java +++ b/checker/jtreg/nullness/eisop673/User.java @@ -9,8 +9,8 @@ * @compile/fail/ref=User.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker User.java */ class User { - void go(Lib lib, Lib7 lib7) { - String[] b = lib.get(); - String[] b7 = lib7.get(); - } + void go(Lib lib, Lib7 lib7) { + String[] b = lib.get(); + String[] b7 = lib7.get(); + } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java index 17f272214a7..a9d0388709e 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java @@ -2,10 +2,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; public abstract class AbstractClass { - @Nullable Object f; + @Nullable Object f; - @EnsuresNonNull("f") - public abstract void setf(); + @EnsuresNonNull("f") + public abstract void setf(); - public abstract void setg(); + public abstract void setg(); } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java index 604a470ac7e..28e60064bad 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java @@ -3,7 +3,6 @@ import com.sun.tools.classfile.Annotation; import com.sun.tools.classfile.ClassFile; - import java.io.PrintStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -15,104 +14,103 @@ public class Driver { - private static final PrintStream out = System.out; + private static final PrintStream out = System.out; - // The argument is in the format expected by Class.forName(). - public static void main(String[] args) throws Exception { - if (args.length != 1) { - throw new IllegalArgumentException("Usage: java Driver "); - } - String name = args[0]; - Class clazz = Class.forName(name); - new Driver().runDriver(clazz.newInstance()); + // The argument is in the format expected by Class.forName(). + public static void main(String[] args) throws Exception { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: java Driver "); } - - protected void runDriver(Object object) throws Exception { - int passed = 0, failed = 0; - Class clazz = object.getClass(); - out.println("Tests for " + clazz.getName()); - - // Find methods - for (Method method : clazz.getMethods()) { - List expected = expectedOf(method); - if (expected == null) { - continue; - } - if (method.getReturnType() != String.class) { - throw new IllegalArgumentException( - "Test method needs to return a string: " + method); - } - String testClass = PersistUtil.testClassOf(method); - - try { - String compact = (String) method.invoke(object); - String fullFile = PersistUtil.wrap(compact); - ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); - List actual = ReferenceInfoUtil.extendedAnnotationsOf(cf); - String diagnostic = - String.join( - "; ", - "Tests for " + clazz.getName(), - "compact=" + compact, - "fullFile=" + fullFile, - "testClass=" + testClass); - ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); - out.println("PASSED: " + method.getName()); - ++passed; - } catch (Throwable e) { - out.println("FAILED: " + method.getName()); - out.println(" " + e); - ++failed; - } - } - - out.println(); - int total = passed + failed; - out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); - - out.flush(); - - if (failed != 0) { - throw new RuntimeException(failed + " tests failed"); - } + String name = args[0]; + Class clazz = Class.forName(name); + new Driver().runDriver(clazz.newInstance()); + } + + protected void runDriver(Object object) throws Exception { + int passed = 0, failed = 0; + Class clazz = object.getClass(); + out.println("Tests for " + clazz.getName()); + + // Find methods + for (Method method : clazz.getMethods()) { + List expected = expectedOf(method); + if (expected == null) { + continue; + } + if (method.getReturnType() != String.class) { + throw new IllegalArgumentException("Test method needs to return a string: " + method); + } + String testClass = PersistUtil.testClassOf(method); + + try { + String compact = (String) method.invoke(object); + String fullFile = PersistUtil.wrap(compact); + ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); + List actual = ReferenceInfoUtil.extendedAnnotationsOf(cf); + String diagnostic = + String.join( + "; ", + "Tests for " + clazz.getName(), + "compact=" + compact, + "fullFile=" + fullFile, + "testClass=" + testClass); + ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); + out.println("PASSED: " + method.getName()); + ++passed; + } catch (Throwable e) { + out.println("FAILED: " + method.getName()); + out.println(" " + e); + ++failed; + } } - private List expectedOf(Method m) { - ADescription ta = m.getAnnotation(ADescription.class); - ADescriptions tas = m.getAnnotation(ADescriptions.class); + out.println(); + int total = passed + failed; + out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); - if (ta == null && tas == null) { - return null; - } + out.flush(); - List result = new ArrayList<>(); + if (failed != 0) { + throw new RuntimeException(failed + " tests failed"); + } + } - if (ta != null) { - result.add(expectedOf(ta)); - } + private List expectedOf(Method m) { + ADescription ta = m.getAnnotation(ADescription.class); + ADescriptions tas = m.getAnnotation(ADescriptions.class); - if (tas != null) { - for (ADescription a : tas.value()) { - result.add(expectedOf(a)); - } - } + if (ta == null && tas == null) { + return null; + } - return result; + List result = new ArrayList<>(); + + if (ta != null) { + result.add(expectedOf(ta)); } - private String expectedOf(ADescription d) { - return d.annotation(); + if (tas != null) { + for (ADescription a : tas.value()) { + result.add(expectedOf(a)); + } } + + return result; + } + + private String expectedOf(ADescription d) { + return d.annotation(); + } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ADescription { - String annotation(); + String annotation(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ADescriptions { - ADescription[] value() default {}; + ADescription[] value() default {}; } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java index 224feb2b133..1cf830d1017 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java @@ -8,36 +8,36 @@ public class Extends { - @ADescriptions({ - @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") - }) - public String m1() { - StringBuilder sb = new StringBuilder(); - return TestWrapper.wrap("@Override void setf() { f = new Object(); }"); - } + @ADescriptions({ + @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") + }) + public String m1() { + StringBuilder sb = new StringBuilder(); + return TestWrapper.wrap("@Override void setf() { f = new Object(); }"); + } - @ADescriptions({}) - public String m2() { - return TestWrapper.wrap("@Override void setg() {}"); - } + @ADescriptions({}) + public String m2() { + return TestWrapper.wrap("@Override void setg() {}"); + } - // Issue 342 - // We do not want that behavior with related annotations. @Pure should override @SideEffectFree. - @ADescriptions({ - @ADescription(annotation = "org/checkerframework/dataflow/qual/Pure"), - @ADescription(annotation = "org/checkerframework/dataflow/qual/SideEffectFree") - }) - public String m3() { - return TestWrapper.wrap("@Pure @Override void seth() {}"); - } + // Issue 342 + // We do not want that behavior with related annotations. @Pure should override @SideEffectFree. + @ADescriptions({ + @ADescription(annotation = "org/checkerframework/dataflow/qual/Pure"), + @ADescription(annotation = "org/checkerframework/dataflow/qual/SideEffectFree") + }) + public String m3() { + return TestWrapper.wrap("@Pure @Override void seth() {}"); + } } class TestWrapper { - public static String wrap(String... method) { - return "class Test extends Super {" - + System.lineSeparator() - + String.join(System.lineSeparator(), method) - + System.lineSeparator() - + "}"; - } + public static String wrap(String... method) { + return "class Test extends Super {" + + System.lineSeparator() + + String.join(System.lineSeparator(), method) + + System.lineSeparator() + + "}"; + } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java index 096f49b3223..31f43b7388d 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java @@ -8,23 +8,23 @@ public class Implements { - @ADescriptions({ - @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") - }) - public String m1() { - return TestWrapper.wrap( - "public Test() { f = new Object(); }", - "@Override public void setf() { f = new Object(); }", - "@Override public void setg() {}"); - } + @ADescriptions({ + @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") + }) + public String m1() { + return TestWrapper.wrap( + "public Test() { f = new Object(); }", + "@Override public void setf() { f = new Object(); }", + "@Override public void setg() {}"); + } } class TestWrapper { - public static String wrap(String... method) { - return String.join( - System.lineSeparator(), - "class Test extends AbstractClass {", - String.join(System.lineSeparator(), method), - "}"); - } + public static String wrap(String... method) { + return String.join( + System.lineSeparator(), + "class Test extends AbstractClass {", + String.join(System.lineSeparator(), method), + "}"); + } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java index 4dac1fbbf44..697d995bf15 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java @@ -9,132 +9,125 @@ import com.sun.tools.classfile.ConstantPool.UnexpectedEntry; import com.sun.tools.classfile.Method; import com.sun.tools.classfile.RuntimeAnnotations_attribute; - import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; public class ReferenceInfoUtil { - public static final int IGNORE_VALUE = -321; + public static final int IGNORE_VALUE = -321; - public static List extendedAnnotationsOf(ClassFile cf) { - List annos = new ArrayList<>(); - findAnnotations(cf, annos); - return annos; - } + public static List extendedAnnotationsOf(ClassFile cf) { + List annos = new ArrayList<>(); + findAnnotations(cf, annos); + return annos; + } - /////////////////// Extract annotations ////////////////// - private static void findAnnotations(ClassFile cf, List annos) { - for (Method m : cf.methods) { - findAnnotations(cf, m, Attribute.RuntimeVisibleAnnotations, annos); - } + /////////////////// Extract annotations ////////////////// + private static void findAnnotations(ClassFile cf, List annos) { + for (Method m : cf.methods) { + findAnnotations(cf, m, Attribute.RuntimeVisibleAnnotations, annos); } + } - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's - * name. - */ - private static void findAnnotations( - ClassFile cf, Method m, String name, List annos) { - int index = m.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = m.attributes.get(index); - assert attr instanceof RuntimeAnnotations_attribute; - RuntimeAnnotations_attribute tAttr = (RuntimeAnnotations_attribute) attr; - for (Annotation an : tAttr.annotations) { - if (!containsName(annos, an, cf)) { - annos.add(an); - } - } + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's name. + */ + private static void findAnnotations(ClassFile cf, Method m, String name, List annos) { + int index = m.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = m.attributes.get(index); + assert attr instanceof RuntimeAnnotations_attribute; + RuntimeAnnotations_attribute tAttr = (RuntimeAnnotations_attribute) attr; + for (Annotation an : tAttr.annotations) { + if (!containsName(annos, an, cf)) { + annos.add(an); } + } } + } - private static Annotation findAnnotation( - String name, List annotations, ClassFile cf) - throws InvalidIndex, UnexpectedEntry { - String properName = "L" + name + ";"; - for (Annotation anno : annotations) { - String actualName = cf.constant_pool.getUTF8Value(anno.type_index); - if (properName.equals(actualName)) { - return anno; - } - } - return null; + private static Annotation findAnnotation(String name, List annotations, ClassFile cf) + throws InvalidIndex, UnexpectedEntry { + String properName = "L" + name + ";"; + for (Annotation anno : annotations) { + String actualName = cf.constant_pool.getUTF8Value(anno.type_index); + if (properName.equals(actualName)) { + return anno; + } } + return null; + } - public static boolean compare( - List expectedAnnos, - List actualAnnos, - ClassFile cf, - String diagnostic) - throws InvalidIndex, UnexpectedEntry { - if (actualAnnos.size() != expectedAnnos.size()) { - throw new ComparisonException( - "Wrong number of annotations; " + diagnostic, expectedAnnos, actualAnnos, cf); - } - for (String annoName : expectedAnnos) { - Annotation anno = findAnnotation(annoName, actualAnnos, cf); - if (anno == null) { - throw new ComparisonException( - "Expected annotation not found: " + annoName + "; " + diagnostic, - expectedAnnos, - actualAnnos, - cf); - } - } - return true; + public static boolean compare( + List expectedAnnos, List actualAnnos, ClassFile cf, String diagnostic) + throws InvalidIndex, UnexpectedEntry { + if (actualAnnos.size() != expectedAnnos.size()) { + throw new ComparisonException( + "Wrong number of annotations; " + diagnostic, expectedAnnos, actualAnnos, cf); + } + for (String annoName : expectedAnnos) { + Annotation anno = findAnnotation(annoName, actualAnnos, cf); + if (anno == null) { + throw new ComparisonException( + "Expected annotation not found: " + annoName + "; " + diagnostic, + expectedAnnos, + actualAnnos, + cf); + } } + return true; + } - private static boolean containsName(List annos, Annotation anno, ClassFile cf) { - try { - for (Annotation an : annos) { - if (cf.constant_pool - .getUTF8Value(an.type_index) - .equals(cf.constant_pool.getUTF8Value(anno.type_index))) { - return true; - } - } - } catch (Exception e) { - throw new RuntimeException(); + private static boolean containsName(List annos, Annotation anno, ClassFile cf) { + try { + for (Annotation an : annos) { + if (cf.constant_pool + .getUTF8Value(an.type_index) + .equals(cf.constant_pool.getUTF8Value(anno.type_index))) { + return true; } - return false; + } + } catch (Exception e) { + throw new RuntimeException(); } + return false; + } } class ComparisonException extends RuntimeException { - private static final long serialVersionUID = -3930499712333815821L; + private static final long serialVersionUID = -3930499712333815821L; - public final List expected; - public final List found; - public final ClassFile cf; + public final List expected; + public final List found; + public final ClassFile cf; - public ComparisonException( - String message, List expected, List found, ClassFile cf) { - super(message); - this.expected = expected; - this.found = found; - this.cf = cf; - } + public ComparisonException( + String message, List expected, List found, ClassFile cf) { + super(message); + this.expected = expected; + this.found = found; + this.cf = cf; + } - public String toString() { - StringJoiner foundString = new StringJoiner(","); - for (Annotation anno : found) { - try { - foundString.add(cf.constant_pool.getUTF8Value(anno.type_index)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - return String.join( - System.lineSeparator(), - super.toString(), - "\tExpected: " - + expected.size() - + " annotations; but found: " - + found.size() - + " annotations", - " Expected: " + expected, - " Found: " + foundString); + public String toString() { + StringJoiner foundString = new StringJoiner(","); + for (Annotation anno : found) { + try { + foundString.add(cf.constant_pool.getUTF8Value(anno.type_index)); + } catch (Exception e) { + throw new RuntimeException(e); + } } + return String.join( + System.lineSeparator(), + super.toString(), + "\tExpected: " + + expected.size() + + " annotations; but found: " + + found.size() + + " annotations", + " Expected: " + expected, + " Found: " + foundString); + } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java index 2ab7a324c59..085cf32341a 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java @@ -2,21 +2,21 @@ import org.checkerframework.dataflow.qual.SideEffectFree; public class Super { - Object f; - Object g; - Object h; + Object f; + Object g; + Object h; - @EnsuresNonNull("f") - void setf() { - f = new Object(); - } + @EnsuresNonNull("f") + void setf() { + f = new Object(); + } - void setg() { - g = null; - } + void setg() { + g = null; + } - @SideEffectFree - void seth() { - h = null; - } + @SideEffectFree + void seth() { + h = null; + } } diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTest.java b/checker/jtreg/nullness/issue12/BinaryDefaultTest.java index 42408204f7c..8323dc2b858 100644 --- a/checker/jtreg/nullness/issue12/BinaryDefaultTest.java +++ b/checker/jtreg/nullness/issue12/BinaryDefaultTest.java @@ -11,9 +11,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class BinaryDefaultTest { - void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { - @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); - @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); - @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); - } + void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { + @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); + @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); + @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); + } } diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java b/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java index 1101b6c46b5..35b333aea3e 100644 --- a/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java +++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java @@ -1,5 +1,5 @@ public class BinaryDefaultTestBinary { - public static BinaryDefaultTestBinary foo(BinaryDefaultTestInterface foo) { - return new BinaryDefaultTestBinary(); - } + public static BinaryDefaultTestBinary foo(BinaryDefaultTestInterface foo) { + return new BinaryDefaultTestBinary(); + } } diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java index 9bf213bc4d6..17e85a394d0 100644 --- a/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java +++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java @@ -13,9 +13,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class BinaryDefaultTestWithStub { - void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { - @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); - @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); - @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); - } + void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { + @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); + @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); + @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); + } } diff --git a/checker/jtreg/nullness/issue141/CharStreams.java b/checker/jtreg/nullness/issue141/CharStreams.java index 75a7c761efa..b40120e8f59 100644 --- a/checker/jtreg/nullness/issue141/CharStreams.java +++ b/checker/jtreg/nullness/issue141/CharStreams.java @@ -1,4 +1,4 @@ public class CharStreams { - static void copy( - InputSupplier from, OutputSupplier to) {} + static void copy( + InputSupplier from, OutputSupplier to) {} } diff --git a/checker/jtreg/nullness/issue141/Files.java b/checker/jtreg/nullness/issue141/Files.java index 138fae1e7cc..e3f6c9e349e 100644 --- a/checker/jtreg/nullness/issue141/Files.java +++ b/checker/jtreg/nullness/issue141/Files.java @@ -1,13 +1,13 @@ abstract class Files { - public void copy(InputSupplier from) { - CharStreams.copy(from, newWriterSupplier()); - } + public void copy(InputSupplier from) { + CharStreams.copy(from, newWriterSupplier()); + } - public void copy(OutputSupplier to) { - CharStreams.copy(newReaderSupplier(), to); - } + public void copy(OutputSupplier to) { + CharStreams.copy(newReaderSupplier(), to); + } - abstract OutputSupplier newWriterSupplier(); + abstract OutputSupplier newWriterSupplier(); - abstract InputSupplier newReaderSupplier(); + abstract InputSupplier newReaderSupplier(); } diff --git a/checker/jtreg/nullness/issue1582/foo/Foo.java b/checker/jtreg/nullness/issue1582/foo/Foo.java index cfff2d56fbc..c7aa9ce2eb4 100644 --- a/checker/jtreg/nullness/issue1582/foo/Foo.java +++ b/checker/jtreg/nullness/issue1582/foo/Foo.java @@ -6,18 +6,18 @@ public class Foo { - Foo(@Nullable Object theObject) {} + Foo(@Nullable Object theObject) {} - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - @EnsuresNonNullIf( - expression = {"theObject", "getTheObject()"}, - result = true) - public boolean hasTheObject() { - return false; - } + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + @EnsuresNonNullIf( + expression = {"theObject", "getTheObject()"}, + result = true) + public boolean hasTheObject() { + return false; + } - @Pure - public @Nullable Object getTheObject() { - return null; - } + @Pure + public @Nullable Object getTheObject() { + return null; + } } diff --git a/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java b/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java index 700386c0107..f8641bb8024 100644 --- a/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java +++ b/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java @@ -4,9 +4,9 @@ public class JavaExpressionParseError { - public void printAThing(Foo foo) { - if (foo.hasTheObject()) { - System.out.println("Print false: " + foo.getTheObject().equals(new Object())); - } + public void printAThing(Foo foo) { + if (foo.hasTheObject()) { + System.out.println("Print false: " + foo.getTheObject().equals(new Object())); } + } } diff --git a/checker/jtreg/nullness/issue1929/Issue1929.java b/checker/jtreg/nullness/issue1929/Issue1929.java index 6482d71e7a5..73e873daf6e 100644 --- a/checker/jtreg/nullness/issue1929/Issue1929.java +++ b/checker/jtreg/nullness/issue1929/Issue1929.java @@ -6,27 +6,26 @@ * @compile/fail/ref=Issue1929-trust.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint=trustArrayLenZero Issue1929.java */ -import org.checkerframework.common.value.qual.ArrayLen; - import java.util.Collection; +import org.checkerframework.common.value.qual.ArrayLen; public class Issue1929 { - String[] works1(Collection c) { - return c.toArray(new String[0]); - } + String[] works1(Collection c) { + return c.toArray(new String[0]); + } - private static final String @ArrayLen(0) [] EMPTY_STRING_ARRAY_2 = new String[0]; + private static final String @ArrayLen(0) [] EMPTY_STRING_ARRAY_2 = new String[0]; - String[] fails2(Collection c) { - return c.toArray(EMPTY_STRING_ARRAY_2); - } + String[] fails2(Collection c) { + return c.toArray(EMPTY_STRING_ARRAY_2); + } - private static final String[] EMPTY_STRING_ARRAY_3 = new String[0]; + private static final String[] EMPTY_STRING_ARRAY_3 = new String[0]; - String[] fails3(Collection c) { - // We don't determine field types from initialization expressions. - // :: error: (return.type.incompatible) - return c.toArray(EMPTY_STRING_ARRAY_3); - } + String[] fails3(Collection c) { + // We don't determine field types from initialization expressions. + // :: error: (return.type.incompatible) + return c.toArray(EMPTY_STRING_ARRAY_3); + } } diff --git a/checker/jtreg/nullness/issue1958/NPE2Test.java b/checker/jtreg/nullness/issue1958/NPE2Test.java index 7d16d44386e..8d292792fa4 100644 --- a/checker/jtreg/nullness/issue1958/NPE2Test.java +++ b/checker/jtreg/nullness/issue1958/NPE2Test.java @@ -1,19 +1,19 @@ public class NPE2Test { - public void testNPE() { - SupplierDefs.Supplier s = new SupplierDefs.NullSupplier(); - boolean b = s.get().equals(""); - } + public void testNPE() { + SupplierDefs.Supplier s = new SupplierDefs.NullSupplier(); + boolean b = s.get().equals(""); + } - public void testNPE2() { - SupplierDefs.MyInterface s = new SupplierDefs.NullInterface(); - boolean b = s.getT().equals(""); - } + public void testNPE2() { + SupplierDefs.MyInterface s = new SupplierDefs.NullInterface(); + boolean b = s.getT().equals(""); + } - public void testNPE3() { - SupplierDefs.MyInterface s = new SupplierDefs.NullSupplierMyInterface(); - boolean b = s.getT().equals(""); + public void testNPE3() { + SupplierDefs.MyInterface s = new SupplierDefs.NullSupplierMyInterface(); + boolean b = s.getT().equals(""); - SupplierDefs.Supplier s2 = new SupplierDefs.NullSupplierMyInterface(); - boolean b2 = s2.get().equals(""); - } + SupplierDefs.Supplier s2 = new SupplierDefs.NullSupplierMyInterface(); + boolean b2 = s2.get().equals(""); + } } diff --git a/checker/jtreg/nullness/issue1958/SupplierDefs.java b/checker/jtreg/nullness/issue1958/SupplierDefs.java index 4a15a8e5421..9a8f054251e 100644 --- a/checker/jtreg/nullness/issue1958/SupplierDefs.java +++ b/checker/jtreg/nullness/issue1958/SupplierDefs.java @@ -6,43 +6,42 @@ * @compile/fail/ref=NPE2Test.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker NPE2Test.java -Anomsgtext */ -import org.checkerframework.checker.nullness.qual.*; - import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.*; public class SupplierDefs { - public abstract static class Supplier { - public abstract R get(); + public abstract static class Supplier { + public abstract R get(); + } + + public static class NullSupplier extends Supplier<@Nullable String> { + @Override + public @Nullable String get() { + return null; } + } - public static class NullSupplier extends Supplier<@Nullable String> { - @Override - public @Nullable String get() { - return null; - } + public static class NullInterface implements MyInterface<@Nullable String> { + @Override + public @Nullable String getT() { + return null; } + } - public static class NullInterface implements MyInterface<@Nullable String> { - @Override - public @Nullable String getT() { - return null; - } + public static class NullSupplierMyInterface extends Supplier<@Nullable String> + implements MyInterface<@Nullable String> { + @Override + public @Nullable String get() { + return null; } - public static class NullSupplierMyInterface extends Supplier<@Nullable String> - implements MyInterface<@Nullable String> { - @Override - public @Nullable String get() { - return null; - } - - @Override - public @Nullable String getT() { - return null; - } + @Override + public @Nullable String getT() { + return null; } + } - public interface MyInterface { - T getT(); - } + public interface MyInterface { + T getT(); + } } diff --git a/checker/jtreg/nullness/issue2173/View.java b/checker/jtreg/nullness/issue2173/View.java index 2c97d94b6eb..f46ea99a6e5 100644 --- a/checker/jtreg/nullness/issue2173/View.java +++ b/checker/jtreg/nullness/issue2173/View.java @@ -8,7 +8,7 @@ */ public class View { - private static void createTable() { - ImporterManager.chooseAndImportFile(""); - } + private static void createTable() { + ImporterManager.chooseAndImportFile(""); + } } diff --git a/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java index 3d8b6e6fe22..f32a4c77ce7 100644 --- a/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java +++ b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java @@ -11,21 +11,21 @@ public class RequireCheckerPrefix { - void method(@Nullable Object o) { - @SuppressWarnings("nullness:assignment.type.incompatible") - @NonNull Object s = o; - // "all" is not a valid prefix, so the warning is never suppressed. - @SuppressWarnings("all:assignment.type.incompatible") - @NonNull Object t = o; - @SuppressWarnings("allcheckers:assignment.type.incompatible") - @NonNull Object u = o; + void method(@Nullable Object o) { + @SuppressWarnings("nullness:assignment.type.incompatible") + @NonNull Object s = o; + // "all" is not a valid prefix, so the warning is never suppressed. + @SuppressWarnings("all:assignment.type.incompatible") + @NonNull Object t = o; + @SuppressWarnings("allcheckers:assignment.type.incompatible") + @NonNull Object u = o; - @SuppressWarnings("assignment.type.incompatible") - @NonNull Object p = o; - // Suppresses the warning if -ArequirePrefixInWarningSuppressions isn't used. - @SuppressWarnings("all") - @NonNull Object q = o; - @SuppressWarnings("allcheckers") - @NonNull Object w = o; - } + @SuppressWarnings("assignment.type.incompatible") + @NonNull Object p = o; + // Suppresses the warning if -ArequirePrefixInWarningSuppressions isn't used. + @SuppressWarnings("all") + @NonNull Object q = o; + @SuppressWarnings("allcheckers") + @NonNull Object w = o; + } } diff --git a/checker/jtreg/nullness/issue257/ClientBuilder.java b/checker/jtreg/nullness/issue257/ClientBuilder.java index 829d324cebc..f42da3f7acf 100644 --- a/checker/jtreg/nullness/issue257/ClientBuilder.java +++ b/checker/jtreg/nullness/issue257/ClientBuilder.java @@ -2,14 +2,14 @@ public class ClientBuilder> { - static @NonNull ClientBuilder newBuilder() { - return new BuilderImpl(); - } + static @NonNull ClientBuilder newBuilder() { + return new BuilderImpl(); + } - // Dummy class to get the recursive Builder typing right. - static class BuilderImpl extends ClientBuilder {} + // Dummy class to get the recursive Builder typing right. + static class BuilderImpl extends ClientBuilder {} - T setThing() { - return (T) this; - } + T setThing() { + return (T) this; + } } diff --git a/checker/jtreg/nullness/issue257/Module.java b/checker/jtreg/nullness/issue257/Module.java index 400c94390a5..785dacc4c99 100644 --- a/checker/jtreg/nullness/issue257/Module.java +++ b/checker/jtreg/nullness/issue257/Module.java @@ -9,12 +9,12 @@ * @compile -XDrawDiagnostics ClientBuilder.java */ public class Module { - void buildClient() { - ClientBuilder builder = ClientBuilder.newBuilder().setThing().setThing(); - } + void buildClient() { + ClientBuilder builder = ClientBuilder.newBuilder().setThing().setThing(); + } - void smaller() { - ClientBuilder>>> - builder = ClientBuilder.newBuilder(); - } + void smaller() { + ClientBuilder>>> + builder = ClientBuilder.newBuilder(); + } } diff --git a/checker/jtreg/nullness/issue257/Small.java b/checker/jtreg/nullness/issue257/Small.java index cd4b6bea487..6209a3e6da9 100644 --- a/checker/jtreg/nullness/issue257/Small.java +++ b/checker/jtreg/nullness/issue257/Small.java @@ -2,13 +2,13 @@ class Gen> { - static @Nullable Gen newBuilder() { - return null; - } + static @Nullable Gen newBuilder() { + return null; + } } public class Small { - void buildGen() { - Gen> builder = Gen.newBuilder(); - } + void buildGen() { + Gen> builder = Gen.newBuilder(); + } } diff --git a/checker/jtreg/nullness/issue3700/Client.java b/checker/jtreg/nullness/issue3700/Client.java index 0dd05d60d5a..e9657c70ee0 100644 --- a/checker/jtreg/nullness/issue3700/Client.java +++ b/checker/jtreg/nullness/issue3700/Client.java @@ -1,4 +1,4 @@ public class Client { - TimeUnitRange t = TimeUnitRange.YEAR; + TimeUnitRange t = TimeUnitRange.YEAR; } diff --git a/checker/jtreg/nullness/issue3700/TimeUnitRange.java b/checker/jtreg/nullness/issue3700/TimeUnitRange.java index cc4e5f0f9af..c11ced477e2 100644 --- a/checker/jtreg/nullness/issue3700/TimeUnitRange.java +++ b/checker/jtreg/nullness/issue3700/TimeUnitRange.java @@ -6,11 +6,11 @@ */ public enum TimeUnitRange { - YEAR, - YEAR_TO_MONTH, - MONTH; + YEAR, + YEAR_TO_MONTH, + MONTH; - public static TimeUnitRange of(Object endUnit) { - throw new Error("body is irrelevant"); - } + public static TimeUnitRange of(Object endUnit) { + throw new Error("body is irrelevant"); + } } diff --git a/checker/jtreg/nullness/issue380/DA.java b/checker/jtreg/nullness/issue380/DA.java index 8676c10dbca..553eb49262f 100644 --- a/checker/jtreg/nullness/issue380/DA.java +++ b/checker/jtreg/nullness/issue380/DA.java @@ -1,4 +1,4 @@ public class DA { - @Decl(flag = true) - void foo() {} + @Decl(flag = true) + void foo() {} } diff --git a/checker/jtreg/nullness/issue380/DB.java b/checker/jtreg/nullness/issue380/DB.java index 98b982598b0..b9981120237 100644 --- a/checker/jtreg/nullness/issue380/DB.java +++ b/checker/jtreg/nullness/issue380/DB.java @@ -1,3 +1,3 @@ public class DB extends DA { - void foo() {} + void foo() {} } diff --git a/checker/jtreg/nullness/issue380/Decl.java b/checker/jtreg/nullness/issue380/Decl.java index 150e0ab6e27..d262028269f 100644 --- a/checker/jtreg/nullness/issue380/Decl.java +++ b/checker/jtreg/nullness/issue380/Decl.java @@ -2,5 +2,5 @@ @InheritedAnnotation public @interface Decl { - boolean flag(); + boolean flag(); } diff --git a/checker/jtreg/nullness/issue6053/Main.java b/checker/jtreg/nullness/issue6053/Main.java index 5ea27ddacb9..6939c6d15bb 100644 --- a/checker/jtreg/nullness/issue6053/Main.java +++ b/checker/jtreg/nullness/issue6053/Main.java @@ -5,5 +5,5 @@ * @compile/ref=Main.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=stubs.jar Main.java */ public class Main { - public static void main(String[] args) {} + public static void main(String[] args) {} } diff --git a/checker/jtreg/nullness/issue6374/Lib.java b/checker/jtreg/nullness/issue6374/Lib.java index dea871dac3a..0997350896d 100644 --- a/checker/jtreg/nullness/issue6374/Lib.java +++ b/checker/jtreg/nullness/issue6374/Lib.java @@ -3,15 +3,15 @@ @SuppressWarnings("unchecked") // ignore heap pollution class Lib { - // element type inferred, array non-null - static void none(T... o) {} + // element type inferred, array non-null + static void none(T... o) {} - // element type inferred, array non-null - static void decl(@NonNull T... o) {} + // element type inferred, array non-null + static void decl(@NonNull T... o) {} - // element type nullable, array non-null - static void type(@Nullable T... o) {} + // element type nullable, array non-null + static void type(@Nullable T... o) {} - // element type nullable, array nullable - static void typenn(@Nullable T @Nullable ... o) {} + // element type nullable, array nullable + static void typenn(@Nullable T @Nullable ... o) {} } diff --git a/checker/jtreg/nullness/issue6374/User.java b/checker/jtreg/nullness/issue6374/User.java index ca0de77a7c2..bfe3171cdec 100644 --- a/checker/jtreg/nullness/issue6374/User.java +++ b/checker/jtreg/nullness/issue6374/User.java @@ -7,14 +7,14 @@ */ // Also see checker/tests/nullness/generics/Issue6374.java class User { - void go() { - Lib.decl("", null); - // :: error: (argument.type.incompatible) - Lib.decl((Object[]) null); - Lib.type("", null); - // :: error: (argument.type.incompatible) - Lib.type((Object[]) null); - Lib.typenn("", null); - Lib.typenn((Object[]) null); - } + void go() { + Lib.decl("", null); + // :: error: (argument.type.incompatible) + Lib.decl((Object[]) null); + Lib.type("", null); + // :: error: (argument.type.incompatible) + Lib.type((Object[]) null); + Lib.typenn("", null); + Lib.typenn((Object[]) null); + } } diff --git a/checker/jtreg/nullness/issue767/Class1.java b/checker/jtreg/nullness/issue767/Class1.java index 40aea26c250..fc2c20bf3e7 100644 --- a/checker/jtreg/nullness/issue767/Class1.java +++ b/checker/jtreg/nullness/issue767/Class1.java @@ -11,26 +11,26 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class1 { - public static @Nullable Object field = null; - public @Nullable Object instanceField = null; + public static @Nullable Object field = null; + public @Nullable Object instanceField = null; - @EnsuresNonNull("instanceField") - public void instanceMethod() { - instanceField = new Object(); - } + @EnsuresNonNull("instanceField") + public void instanceMethod() { + instanceField = new Object(); + } - @EnsuresNonNull("Class1.field") - public static void method() { - field = new Object(); - } + @EnsuresNonNull("Class1.field") + public static void method() { + field = new Object(); + } - @EnsuresNonNull("Class2.field") - public static void method2() { - Class2.field = new Object(); - } + @EnsuresNonNull("Class2.field") + public static void method2() { + Class2.field = new Object(); + } - @EnsuresNonNull("#1.instanceField") - public static void method3(Class2 class2) { - class2.instanceField = new Object(); - } + @EnsuresNonNull("#1.instanceField") + public static void method3(Class2 class2) { + class2.instanceField = new Object(); + } } diff --git a/checker/jtreg/nullness/issue767/Class2.java b/checker/jtreg/nullness/issue767/Class2.java index a7e561cc440..5d508a91bf6 100644 --- a/checker/jtreg/nullness/issue767/Class2.java +++ b/checker/jtreg/nullness/issue767/Class2.java @@ -2,21 +2,21 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class2 { - public static @Nullable Object field; - public @Nullable Object instanceField; + public static @Nullable Object field; + public @Nullable Object instanceField; - public static void method(Class1 class1) { - Class1.method(); - @NonNull Object o = Class1.field; - Class1.method2(); - @NonNull Object o2 = field; + public static void method(Class1 class1) { + Class1.method(); + @NonNull Object o = Class1.field; + Class1.method2(); + @NonNull Object o2 = field; - class1.instanceMethod(); - @NonNull Object o3 = class1.instanceField; - } + class1.instanceMethod(); + @NonNull Object o3 = class1.instanceField; + } - void test() { - Class1.method3(this); - @NonNull Object o = instanceField; - } + void test() { + Class1.method3(this); + @NonNull Object o = instanceField; + } } diff --git a/checker/jtreg/nullness/issue790/Class1.java b/checker/jtreg/nullness/issue790/Class1.java index 10ac7ec761c..9ff0193d76b 100644 --- a/checker/jtreg/nullness/issue790/Class1.java +++ b/checker/jtreg/nullness/issue790/Class1.java @@ -8,7 +8,7 @@ * */ public class Class1 { - Class1() { - super(this); - } + Class1() { + super(this); + } } diff --git a/checker/jtreg/nullness/issue820/AnonymousClass.java b/checker/jtreg/nullness/issue820/AnonymousClass.java index cafe4c2bb59..b20c622f96a 100644 --- a/checker/jtreg/nullness/issue820/AnonymousClass.java +++ b/checker/jtreg/nullness/issue820/AnonymousClass.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class AnonymousClass { - @NonNull Object error = null; + @NonNull Object error = null; - void method() { - Object effectivelyFinalLocal = new Object(); - Object a = - new Object() { - void foo() { - @NonNull Object nonNull = effectivelyFinalLocal; - } - }; - } + void method() { + Object effectivelyFinalLocal = new Object(); + Object a = + new Object() { + void foo() { + @NonNull Object nonNull = effectivelyFinalLocal; + } + }; + } } diff --git a/checker/jtreg/nullness/issue820/Class1.java b/checker/jtreg/nullness/issue820/Class1.java index 09fc29986be..4634344b6ce 100644 --- a/checker/jtreg/nullness/issue820/Class1.java +++ b/checker/jtreg/nullness/issue820/Class1.java @@ -12,27 +12,27 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Class1 { - public static @Nullable Object field = null; - public @Nullable Object instanceField = null; + public static @Nullable Object field = null; + public @Nullable Object instanceField = null; - @EnsuresNonNull("#1.instanceField") - public static void method3(Class2 class2) { - class2.instanceField = new Object(); - } + @EnsuresNonNull("#1.instanceField") + public static void method3(Class2 class2) { + class2.instanceField = new Object(); + } - @EnsuresNonNull("#1.instanceField") - public static void method4(Class2 class2) { - class2.instanceField = new Object(); - } + @EnsuresNonNull("#1.instanceField") + public static void method4(Class2 class2) { + class2.instanceField = new Object(); + } - @EnsuresNonNull("#1.instanceField") - public static void method5(Class2 class2) {} + @EnsuresNonNull("#1.instanceField") + public static void method5(Class2 class2) {} - @EnsuresNonNull("#1") - public static void method6(Class2 class2) {} + @EnsuresNonNull("#1") + public static void method6(Class2 class2) {} - @RequiresNonNull("#1.instanceField") - public static void method3R(Class2 class2) { - class2.instanceField.toString(); - } + @RequiresNonNull("#1.instanceField") + public static void method3R(Class2 class2) { + class2.instanceField.toString(); + } } diff --git a/checker/jtreg/nullness/issue820/Class1Min.java b/checker/jtreg/nullness/issue820/Class1Min.java index 5bc809672bc..4e899730e55 100644 --- a/checker/jtreg/nullness/issue820/Class1Min.java +++ b/checker/jtreg/nullness/issue820/Class1Min.java @@ -10,6 +10,6 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class Class1Min { - @EnsuresNonNull("#1") - public void methodInstance(Class2Min class2) {} + @EnsuresNonNull("#1") + public void methodInstance(Class2Min class2) {} } diff --git a/checker/jtreg/nullness/issue820/Class2.java b/checker/jtreg/nullness/issue820/Class2.java index 494521a35ea..a7b28dcff7e 100644 --- a/checker/jtreg/nullness/issue820/Class2.java +++ b/checker/jtreg/nullness/issue820/Class2.java @@ -2,16 +2,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class2 { - public static @Nullable Object field; - public @Nullable Object instanceField; + public static @Nullable Object field; + public @Nullable Object instanceField; - void test() { - Class1.method3(this); - @NonNull Object o = instanceField; - } + void test() { + Class1.method3(this); + @NonNull Object o = instanceField; + } - void test2() { - // instanceField = new Object(); Can't reproduce with assignment - Class1.method3R(this); - } + void test2() { + // instanceField = new Object(); Can't reproduce with assignment + Class1.method3R(this); + } } diff --git a/checker/jtreg/nullness/issue820/Class2Min.java b/checker/jtreg/nullness/issue820/Class2Min.java index 32ffbc66d24..086fc7c2fe7 100644 --- a/checker/jtreg/nullness/issue820/Class2Min.java +++ b/checker/jtreg/nullness/issue820/Class2Min.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class Class2Min { - void test(Class1Min class1) { - // Any error must be issued, not suppressed, for this to reproduce - @NonNull Object o = null; - class1.methodInstance(this); - } + void test(Class1Min class1) { + // Any error must be issued, not suppressed, for this to reproduce + @NonNull Object o = null; + class1.methodInstance(this); + } } diff --git a/checker/jtreg/nullness/issue820/Error.java b/checker/jtreg/nullness/issue820/Error.java index 05c981269a1..c3c97aafe68 100644 --- a/checker/jtreg/nullness/issue820/Error.java +++ b/checker/jtreg/nullness/issue820/Error.java @@ -10,5 +10,5 @@ */ public class Error { - @NonNull Object o = null; + @NonNull Object o = null; } diff --git a/checker/jtreg/nullness/issue824/Class2.java b/checker/jtreg/nullness/issue824/Class2.java index f29ad11bf89..7891cc4cd6b 100644 --- a/checker/jtreg/nullness/issue824/Class2.java +++ b/checker/jtreg/nullness/issue824/Class2.java @@ -12,16 +12,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class2 extends Class1 { - void call(Class1<@Nullable X> class1, Gen<@Nullable X> gen) { - class1.methodTypeParam(null); - class1.classTypeParam(null); + void call(Class1<@Nullable X> class1, Gen<@Nullable X> gen) { + class1.methodTypeParam(null); + class1.classTypeParam(null); - class1.wildcardExtends(gen); - class1.wildcardSuper(gen); - } + class1.wildcardExtends(gen); + class1.wildcardSuper(gen); + } - @Override - public T methodTypeParam(T t) { - return super.methodTypeParam(t); - } + @Override + public T methodTypeParam(T t) { + return super.methodTypeParam(t); + } } diff --git a/checker/jtreg/nullness/issue824/NoStubFirst.java b/checker/jtreg/nullness/issue824/NoStubFirst.java index 847fb20f9da..27e8a027469 100644 --- a/checker/jtreg/nullness/issue824/NoStubFirst.java +++ b/checker/jtreg/nullness/issue824/NoStubFirst.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class NoStubFirst { - public static void method( - Supplier supplier, Callable callable) {} + public static void method( + Supplier supplier, Callable callable) {} - public interface Supplier {} + public interface Supplier {} - public interface Callable {} + public interface Callable {} } diff --git a/checker/jtreg/nullness/issue824/NoStubSecond.java b/checker/jtreg/nullness/issue824/NoStubSecond.java index 9e48421710c..ab32bcd6af7 100644 --- a/checker/jtreg/nullness/issue824/NoStubSecond.java +++ b/checker/jtreg/nullness/issue824/NoStubSecond.java @@ -1,14 +1,13 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class NoStubSecond { - public static void one( - NoStubFirst.Supplier supplier, - NoStubFirst.Callable<@Nullable Object> callable) { - NoStubFirst.method(supplier, callable); - } + public static void one( + NoStubFirst.Supplier supplier, NoStubFirst.Callable<@Nullable Object> callable) { + NoStubFirst.method(supplier, callable); + } - public static void two( - NoStubFirst.Supplier supplier, NoStubFirst.Callable callable) { - NoStubFirst.method(supplier, callable); - } + public static void two( + NoStubFirst.Supplier supplier, NoStubFirst.Callable callable) { + NoStubFirst.method(supplier, callable); + } } diff --git a/checker/jtreg/nullness/issue824/Second.java b/checker/jtreg/nullness/issue824/Second.java index 566f3252035..495e94383b8 100644 --- a/checker/jtreg/nullness/issue824/Second.java +++ b/checker/jtreg/nullness/issue824/Second.java @@ -9,12 +9,12 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Second { - public static void one( - First.Supplier supplier, First.Callable<@Nullable Object> callable) { - First.method(supplier, callable); - } + public static void one( + First.Supplier supplier, First.Callable<@Nullable Object> callable) { + First.method(supplier, callable); + } - public static void two(First.Supplier supplier, First.Callable callable) { - First.method(supplier, callable); - } + public static void two(First.Supplier supplier, First.Callable callable) { + First.method(supplier, callable); + } } diff --git a/checker/jtreg/nullness/issue824lib/Class1.java b/checker/jtreg/nullness/issue824lib/Class1.java index bd4ce089af7..a358e8262bc 100644 --- a/checker/jtreg/nullness/issue824lib/Class1.java +++ b/checker/jtreg/nullness/issue824lib/Class1.java @@ -1,13 +1,13 @@ public class Class1 { - class Gen {} + class Gen {} - public T methodTypeParam(T t) { - return t; - } + public T methodTypeParam(T t) { + return t; + } - public void classTypeParam(Q e) {} + public void classTypeParam(Q e) {} - public void wildcardExtends(Gen class1) {} + public void wildcardExtends(Gen class1) {} - public void wildcardSuper(Gen class1) {} + public void wildcardSuper(Gen class1) {} } diff --git a/checker/jtreg/nullness/issue824lib/First.java b/checker/jtreg/nullness/issue824lib/First.java index c92b9a24133..f8cda22c01d 100644 --- a/checker/jtreg/nullness/issue824lib/First.java +++ b/checker/jtreg/nullness/issue824lib/First.java @@ -1,7 +1,7 @@ public class First { - public interface Supplier {} + public interface Supplier {} - public interface Callable {} + public interface Callable {} - public static void method(Supplier supplier, Callable callable) {} + public static void method(Supplier supplier, Callable callable) {} } diff --git a/checker/jtreg/nullness/preciseErrorMsg/Class1.java b/checker/jtreg/nullness/preciseErrorMsg/Class1.java index 5210560daaf..cbfd5cce103 100644 --- a/checker/jtreg/nullness/preciseErrorMsg/Class1.java +++ b/checker/jtreg/nullness/preciseErrorMsg/Class1.java @@ -11,6 +11,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Class1 { - @RequiresNonNull("instanceField") - public static void foo() {} + @RequiresNonNull("instanceField") + public static void foo() {} } diff --git a/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java b/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java index 5860c6dc2fe..42ca5de847c 100644 --- a/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java +++ b/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java @@ -8,15 +8,14 @@ * */ -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; import java.util.function.Consumer; +import org.checkerframework.checker.nullness.qual.*; public class LocateArtificialTree { - @NonNull class A {} + @NonNull class A {} - void foo() { - Consumer> c = a -> {}; - } + void foo() { + Consumer> c = a -> {}; + } } diff --git a/checker/jtreg/nullness/stub-arg/EisopIssue608.java b/checker/jtreg/nullness/stub-arg/EisopIssue608.java index 9e26a807659..16421c1519d 100644 --- a/checker/jtreg/nullness/stub-arg/EisopIssue608.java +++ b/checker/jtreg/nullness/stub-arg/EisopIssue608.java @@ -9,7 +9,7 @@ import java.util.List; public class EisopIssue608 { - boolean go(List l) { - return l.contains(null); - } + boolean go(List l) { + return l.contains(null); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/Box.java b/checker/jtreg/nullness/stub-typeparams/Box.java index cd8ebb7cbdd..4c3f729e8d3 100644 --- a/checker/jtreg/nullness/stub-typeparams/Box.java +++ b/checker/jtreg/nullness/stub-typeparams/Box.java @@ -1,10 +1,10 @@ // This class is not compiled with the Nullness Checker, // so that no annotations are stored in bytecode. public class Box { - static Box of(S in) { - // Implementation doesn't matter. - return null; - } + static Box of(S in) { + // Implementation doesn't matter. + return null; + } - static void consume(Box producer) {} + static void consume(Box producer) {} } diff --git a/checker/jtreg/nullness/stub-typeparams/NullableBox.java b/checker/jtreg/nullness/stub-typeparams/NullableBox.java index b2f8d3b06cf..38647cd95bb 100644 --- a/checker/jtreg/nullness/stub-typeparams/NullableBox.java +++ b/checker/jtreg/nullness/stub-typeparams/NullableBox.java @@ -4,12 +4,12 @@ // This class is not compiled with the Nullness Checker, // so that only explicit annotations are stored in bytecode. public class NullableBox { - static NullableBox of(S in) { - // Implementation doesn't matter. - return null; - } + static NullableBox of(S in) { + // Implementation doesn't matter. + return null; + } - static void consume(NullableBox producer) {} + static void consume(NullableBox producer) {} - static void nonnull(NullableBox producer) {} + static void nonnull(NullableBox producer) {} } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java index 13b8d046341..4d3fec61055 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java @@ -11,5 +11,5 @@ * @compile/fail/ref=StubTypeParamsClass.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=box-nonnull.astub -Werror StubTypeParamsClass.java */ public class StubTypeParamsClass { - @Nullable Box<@Nullable Object> f; + @Nullable Box<@Nullable Object> f; } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java index 8b0b88b1e55..13e396d3103 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java @@ -11,5 +11,5 @@ * @compile/fail/ref=StubTypeParamsClassNbl.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=nullablebox-nonnull.astub -Werror StubTypeParamsClassNbl.java */ public class StubTypeParamsClassNbl { - @Nullable NullableBox<@Nullable Object> f; + @Nullable NullableBox<@Nullable Object> f; } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java index 0bd86b94d98..ea50a93203a 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java @@ -9,7 +9,7 @@ * @compile/fail/ref=StubTypeParamsMeth.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=box-nonnull.astub -Werror StubTypeParamsMeth.java */ public class StubTypeParamsMeth { - void use() { - Box.of(null); - } + void use() { + Box.of(null); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java index 62605bc5e6d..ef16f72620c 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java @@ -9,7 +9,7 @@ * @compile/fail/ref=StubTypeParamsMethNbl.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=nullablebox-nonnull.astub -Werror StubTypeParamsMethNbl.java */ public class StubTypeParamsMethNbl { - void use() { - NullableBox.of(null); - } + void use() { + NullableBox.of(null); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubWildcards.java b/checker/jtreg/nullness/stub-typeparams/StubWildcards.java index 591c18e9c96..4d968f6da07 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubWildcards.java +++ b/checker/jtreg/nullness/stub-typeparams/StubWildcards.java @@ -11,7 +11,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class StubWildcards { - void use(Box<@Nullable String> bs) { - Box.consume(bs); - } + void use(Box<@Nullable String> bs) { + Box.consume(bs); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java index ea3810028a1..ccac3821527 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java +++ b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java @@ -11,7 +11,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class StubWildcardsNbl { - void use(NullableBox<@Nullable String> bs) { - NullableBox.consume(bs); - } + void use(NullableBox<@Nullable String> bs) { + NullableBox.consume(bs); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java index 304c3241dcb..0cfb78076d2 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java +++ b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java @@ -11,7 +11,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class StubWildcardsNon { - void use(NullableBox<@Nullable String> bs) { - NullableBox.nonnull(bs); - } + void use(NullableBox<@Nullable String> bs) { + NullableBox.nonnull(bs); + } } diff --git a/checker/jtreg/nullness/stub-warnings/Binary.java b/checker/jtreg/nullness/stub-warnings/Binary.java index 3e5037c12ce..3b98cd365b1 100644 --- a/checker/jtreg/nullness/stub-warnings/Binary.java +++ b/checker/jtreg/nullness/stub-warnings/Binary.java @@ -1,24 +1,23 @@ // This class is not compiled with the Nullness Checker, // so that only explicit annotations are stored in bytecode. -import org.checkerframework.checker.nullness.qual.NonNull; - import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.NonNull; public class Binary { - @Nullable Object foo() { - return null; - } + @Nullable Object foo() { + return null; + } - Object bar(Object p) { - return null; - } + Object bar(Object p) { + return null; + } - int baz(Object @NonNull [] p) { - return 1; - } + int baz(Object @NonNull [] p) { + return 1; + } - int baz2(Object[] p) { - return 1; - } + int baz2(Object[] p) { + return 1; + } } diff --git a/checker/jtreg/rawtypes/RawTypeFail.java b/checker/jtreg/rawtypes/RawTypeFail.java index ba9aecc53cd..402bf5f7cfd 100644 --- a/checker/jtreg/rawtypes/RawTypeFail.java +++ b/checker/jtreg/rawtypes/RawTypeFail.java @@ -11,7 +11,7 @@ import java.util.Map; public class RawTypeFail { - Map mr = new HashMap(); - Map mc = mr; - Map mc2 = new HashMap(); + Map mr = new HashMap(); + Map mc = mr; + Map mc2 = new HashMap(); } diff --git a/checker/jtreg/showPrefix/ShowPrefixTest.java b/checker/jtreg/showPrefix/ShowPrefixTest.java index b7a2eeed968..6774bba3e67 100644 --- a/checker/jtreg/showPrefix/ShowPrefixTest.java +++ b/checker/jtreg/showPrefix/ShowPrefixTest.java @@ -8,7 +8,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ShowPrefixTest { - @NonNull Object foo(@Nullable Object nble) { - return nble; - } + @NonNull Object foo(@Nullable Object nble) { + return nble; + } } diff --git a/checker/jtreg/sortwarnings/ErrorOrders.java b/checker/jtreg/sortwarnings/ErrorOrders.java index f2f8dec12f0..3368651b05d 100644 --- a/checker/jtreg/sortwarnings/ErrorOrders.java +++ b/checker/jtreg/sortwarnings/ErrorOrders.java @@ -9,59 +9,59 @@ /** This class tests that errors are issued in order of postion. */ public class ErrorOrders { - void test2(int i, int[] a) { - a[i] = 2; - } + void test2(int i, int[] a) { + a[i] = 2; + } - // Ignore the test suite's usage of qualifiers in illegal locations. - @SuppressWarnings("type.invalid.annotations.on.location") - int test4( - @GTENegativeOne @UpperBoundBottom int p1, - @UpperBoundBottom @GTENegativeOne int p2, - int @BottomVal [] p3, - int @SameLenBottom [] p4, - @BottomVal int p5) { + // Ignore the test suite's usage of qualifiers in illegal locations. + @SuppressWarnings("type.invalid.annotations.on.location") + int test4( + @GTENegativeOne @UpperBoundBottom int p1, + @UpperBoundBottom @GTENegativeOne int p2, + int @BottomVal [] p3, + int @SameLenBottom [] p4, + @BottomVal int p5) { - @IndexFor("p2") int z = 0; - @IndexFor("This isn't an expression") int x = z; - return x; - } + @IndexFor("p2") int z = 0; + @IndexFor("This isn't an expression") int x = z; + return x; + } - void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { - test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); - } + void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { + test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); + } - class InnerClass { - @IndexFor("This isn't an expression") int x = 0; + class InnerClass { + @IndexFor("This isn't an expression") int x = 0; - void test2(int i, int[] a) { - a[i] = 2; - } + void test2(int i, int[] a) { + a[i] = 2; } + } } class InSameCompilationUnit { - @IndexFor("This isn't an expression") int x = 0; + @IndexFor("This isn't an expression") int x = 0; - void test2(int i, int[] a) { - a[i] = 2; - } + void test2(int i, int[] a) { + a[i] = 2; + } - // Ignore the test suite's usage of qualifiers in illegal locations. - @SuppressWarnings("type.invalid.annotations.on.location") - int test4( - @GTENegativeOne @UpperBoundBottom int p1, - @UpperBoundBottom @GTENegativeOne int p2, - int @BottomVal [] p3, - int @SameLenBottom [] p4, - @BottomVal int p5) { + // Ignore the test suite's usage of qualifiers in illegal locations. + @SuppressWarnings("type.invalid.annotations.on.location") + int test4( + @GTENegativeOne @UpperBoundBottom int p1, + @UpperBoundBottom @GTENegativeOne int p2, + int @BottomVal [] p3, + int @SameLenBottom [] p4, + @BottomVal int p5) { - @IndexFor("p2") int z = 0; - @IndexFor("This isn't an expression") int x = z; - return x; - } + @IndexFor("p2") int z = 0; + @IndexFor("This isn't an expression") int x = z; + return x; + } - void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { - test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); - } + void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { + test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); + } } diff --git a/checker/jtreg/sortwarnings/OrderOfCheckers.java b/checker/jtreg/sortwarnings/OrderOfCheckers.java index b1c2df2104d..2b6a7f912bf 100644 --- a/checker/jtreg/sortwarnings/OrderOfCheckers.java +++ b/checker/jtreg/sortwarnings/OrderOfCheckers.java @@ -8,9 +8,9 @@ /** This class tests that errors issued on the same tree are sorted by checker. */ public class OrderOfCheckers { - // Ignore the test suite's usage of qualifiers in illegal locations. - @SuppressWarnings("type.invalid.annotations.on.location") - void test(int[] y) { - @GTENegativeOne @UpperBoundBottom @SearchIndexBottom int @BottomVal @SameLenBottom [] x = y; - } + // Ignore the test suite's usage of qualifiers in illegal locations. + @SuppressWarnings("type.invalid.annotations.on.location") + void test(int[] y) { + @GTENegativeOne @UpperBoundBottom @SearchIndexBottom int @BottomVal @SameLenBottom [] x = y; + } } diff --git a/checker/jtreg/stubs/annotatedFor/UseTest.java b/checker/jtreg/stubs/annotatedFor/UseTest.java index a01dba5b82c..ac1ed62f226 100644 --- a/checker/jtreg/stubs/annotatedFor/UseTest.java +++ b/checker/jtreg/stubs/annotatedFor/UseTest.java @@ -10,14 +10,13 @@ package annotatedfor; -import org.checkerframework.checker.nullness.qual.NonNull; - import annotatedforlib.Test; +import org.checkerframework.checker.nullness.qual.NonNull; public class UseTest { - void test(Test test) { - test.method1(null); - test.method2(null); - @NonNull Object o = test.method3(); - } + void test(Test test) { + test.method1(null); + test.method2(null); + @NonNull Object o = test.method3(); + } } diff --git a/checker/jtreg/stubs/annotatedForLib/Test.java b/checker/jtreg/stubs/annotatedForLib/Test.java index 931ab715f48..0e99c4991de 100644 --- a/checker/jtreg/stubs/annotatedForLib/Test.java +++ b/checker/jtreg/stubs/annotatedForLib/Test.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Test { - public void method1(T t) {} + public void method1(T t) {} - public void method2(@Nullable T t) {} + public void method2(@Nullable T t) {} - public Object method3() { - return ""; - } + public Object method3() { + return ""; + } } diff --git a/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java b/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java index 56ccb10f9c4..e6081d96b3f 100644 --- a/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java +++ b/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java @@ -1,3 +1,3 @@ public abstract class List { - abstract void retainAll(List other); + abstract void retainAll(List other); } diff --git a/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java b/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java index a4a6b8377bd..e9892d0b924 100644 --- a/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java +++ b/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java @@ -1,4 +1,4 @@ public abstract class MutableList extends List { - @Override - abstract void retainAll(List other); + @Override + abstract void retainAll(List other); } diff --git a/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java b/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java index 6b3689ef26f..42300b4dc2a 100644 --- a/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java +++ b/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java @@ -10,13 +10,13 @@ */ public class Test { - // l1 has type List - // mutableList has type MutableList - void foo(List l1, MutableList mutableList) { - // retainAll only accepts List with non-null elements - l1.retainAll(mutableList); + // l1 has type List + // mutableList has type MutableList + void foo(List l1, MutableList mutableList) { + // retainAll only accepts List with non-null elements + l1.retainAll(mutableList); - // l2 has type List - List l2 = mutableList; - } + // l2 has type List + List l2 = mutableList; + } } diff --git a/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java b/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java index 32e338c728a..ff9be0203a4 100644 --- a/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java +++ b/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java @@ -1,11 +1,11 @@ package pck; public class Defaults { - Object o; + Object o; - void test() { - // The astub file changes o to @Nullable - // and therefore the assignment is allowed. - o = null; - } + void test() { + // The astub file changes o to @Nullable + // and therefore the assignment is allowed. + o = null; + } } diff --git a/checker/jtreg/stubs/fakeoverrides/DefineClasses.java b/checker/jtreg/stubs/fakeoverrides/DefineClasses.java index 8894a01278c..a77f8765269 100644 --- a/checker/jtreg/stubs/fakeoverrides/DefineClasses.java +++ b/checker/jtreg/stubs/fakeoverrides/DefineClasses.java @@ -3,17 +3,17 @@ public class DefineClasses {} interface SuperInterface { - default int m() { - return 0; - } + default int m() { + return 0; + } } class SuperClass implements SuperInterface { - // fake override: - // @Untainted int m(); + // fake override: + // @Untainted int m(); } interface SubInterface extends SuperInterface { - // fake override: - // int m(); + // fake override: + // int m(); } diff --git a/checker/jtreg/stubs/fakeoverrides/Use.java b/checker/jtreg/stubs/fakeoverrides/Use.java index cf1f8bba478..bb0e7461971 100644 --- a/checker/jtreg/stubs/fakeoverrides/Use.java +++ b/checker/jtreg/stubs/fakeoverrides/Use.java @@ -12,8 +12,8 @@ // TODO: Issue error SuperClass and SubInterface have conflicting fake overrides // See https://github.com/typetools/checker-framework/issues/2724 public class Use extends SuperClass implements SubInterface { - void use(Use d) { - // Ok, because the fake override in SuperClasses is taken over the one in SubInterface. - @Untainted int i = d.m(); - } + void use(Use d) { + // Ok, because the fake override in SuperClasses is taken over the one in SubInterface. + @Untainted int i = d.m(); + } } diff --git a/checker/jtreg/stubs/general/Driver.java b/checker/jtreg/stubs/general/Driver.java index 44b018fdba8..cf36598054f 100644 --- a/checker/jtreg/stubs/general/Driver.java +++ b/checker/jtreg/stubs/general/Driver.java @@ -6,8 +6,8 @@ */ public class Driver { - void test() { - Object o = null; - String v = String.valueOf(o); - } + void test() { + Object o = null; + String v = String.valueOf(o); + } } diff --git a/checker/jtreg/stubs/general/Driver2.java b/checker/jtreg/stubs/general/Driver2.java index 4ca1a6d98e0..4aebab0e333 100644 --- a/checker/jtreg/stubs/general/Driver2.java +++ b/checker/jtreg/stubs/general/Driver2.java @@ -5,5 +5,5 @@ */ public class Driver2 { - void test() {} + void test() {} } diff --git a/checker/jtreg/stubs/issue1356/mypackage/MyClass.java b/checker/jtreg/stubs/issue1356/mypackage/MyClass.java index 4167328861d..9cdb2e476f6 100644 --- a/checker/jtreg/stubs/issue1356/mypackage/MyClass.java +++ b/checker/jtreg/stubs/issue1356/mypackage/MyClass.java @@ -1,15 +1,15 @@ package mypackage; public class MyClass { - static String toString(char c) { - return c + ""; - } + static String toString(char c) { + return c + ""; + } - void method1(Object[] param) {} + void method1(Object[] param) {} - void method2(Object[][] param) {} + void method2(Object[][] param) {} - void method3(Object[][][] param) {} + void method3(Object[][][] param) {} - void method4(Object[][][][] param) {} + void method4(Object[][][][] param) {} } diff --git a/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java b/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java index 19855ea2deb..ddf224eabf3 100644 --- a/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java +++ b/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java @@ -1,7 +1,7 @@ package mypackage; public class UseMyClass { - void test() { - String s = MyClass.toString('a'); - } + void test() { + String s = MyClass.toString('a'); + } } diff --git a/checker/jtreg/stubs/issue1456/Main.java b/checker/jtreg/stubs/issue1456/Main.java index 8712dbd13b3..7cac3b2ab78 100644 --- a/checker/jtreg/stubs/issue1456/Main.java +++ b/checker/jtreg/stubs/issue1456/Main.java @@ -7,23 +7,22 @@ */ package issue1456; -import org.checkerframework.checker.tainting.qual.Untainted; - import issue1456lib.Lib; +import org.checkerframework.checker.tainting.qual.Untainted; public class Main { - void test(Lib lib) { - @Untainted Object o = lib.object; - @Untainted byte @Untainted [] b = lib.byteArray; - @Untainted Object o1 = lib.object1; - @Untainted Object o2 = lib.object2; - @Untainted byte b2 = lib.byte1; - @Untainted byte @Untainted [] b3 = lib.byteArray2; - byte @Untainted [] @Untainted [] b4 = lib.byteArray3; - } + void test(Lib lib) { + @Untainted Object o = lib.object; + @Untainted byte @Untainted [] b = lib.byteArray; + @Untainted Object o1 = lib.object1; + @Untainted Object o2 = lib.object2; + @Untainted byte b2 = lib.byte1; + @Untainted byte @Untainted [] b3 = lib.byteArray2; + byte @Untainted [] @Untainted [] b4 = lib.byteArray3; + } - void test2(Lib l) { - Lib f = new Lib(l); - } + void test2(Lib l) { + Lib f = new Lib(l); + } } diff --git a/checker/jtreg/stubs/issue1456lib/Lib.java b/checker/jtreg/stubs/issue1456lib/Lib.java index 5e6cdd7cc87..12817ffadcf 100644 --- a/checker/jtreg/stubs/issue1456lib/Lib.java +++ b/checker/jtreg/stubs/issue1456lib/Lib.java @@ -1,11 +1,11 @@ package issue1456lib; public class Lib { - public Lib(T p) {} + public Lib(T p) {} - public Object object1, object2; - public Object object; - public byte[] byteArray; - public byte byte1, byteArray2[]; - public byte[][] byteArray3; + public Object object1, object2; + public Object object; + public byte[] byteArray; + public byte byte1, byteArray2[]; + public byte[][] byteArray3; } diff --git a/checker/jtreg/stubs/issue1542/ExampleAnno.java b/checker/jtreg/stubs/issue1542/ExampleAnno.java index a409caf077f..ffa3740553a 100644 --- a/checker/jtreg/stubs/issue1542/ExampleAnno.java +++ b/checker/jtreg/stubs/issue1542/ExampleAnno.java @@ -1,81 +1,81 @@ package issue1542; public class ExampleAnno { - public enum MyEnum { - A, - B, - C; - } - - @interface DoubleExample { - double value(); - } - - @interface FloatExample { - float value(); - } - - @interface ShortExample { - short value(); - } - - @interface IntExample { - int value(); - } - - @interface LongExample { - long value(); - } - - @interface CharExample { - char value(); - } - - @interface StringExample { - String value(); - } - - @interface ClassExample { - Class value(); - } - - @interface MyEnumExample { - MyEnum value(); - } - - @interface DoubleArrayExample { - double[] value(); - } - - @interface FloatArrayExample { - float[] value(); - } - - @interface ShortArrayExample { - short[] value(); - } - - @interface IntArrayExample { - int[] value(); - } - - @interface LongArrayExample { - long[] value(); - } - - @interface CharArrayExample { - char[] value(); - } - - @interface StringArrayExample { - String[] value(); - } - - @interface ClassArrayExample { - Class[] value(); - } - - @interface MyEnumArrayExample { - MyEnum[] value(); - } + public enum MyEnum { + A, + B, + C; + } + + @interface DoubleExample { + double value(); + } + + @interface FloatExample { + float value(); + } + + @interface ShortExample { + short value(); + } + + @interface IntExample { + int value(); + } + + @interface LongExample { + long value(); + } + + @interface CharExample { + char value(); + } + + @interface StringExample { + String value(); + } + + @interface ClassExample { + Class value(); + } + + @interface MyEnumExample { + MyEnum value(); + } + + @interface DoubleArrayExample { + double[] value(); + } + + @interface FloatArrayExample { + float[] value(); + } + + @interface ShortArrayExample { + short[] value(); + } + + @interface IntArrayExample { + int[] value(); + } + + @interface LongArrayExample { + long[] value(); + } + + @interface CharArrayExample { + char[] value(); + } + + @interface StringArrayExample { + String[] value(); + } + + @interface ClassArrayExample { + Class[] value(); + } + + @interface MyEnumArrayExample { + MyEnum[] value(); + } } diff --git a/checker/jtreg/stubs/issue1542/NeedsIntRange.java b/checker/jtreg/stubs/issue1542/NeedsIntRange.java index ff039acd6fc..9473085d26d 100644 --- a/checker/jtreg/stubs/issue1542/NeedsIntRange.java +++ b/checker/jtreg/stubs/issue1542/NeedsIntRange.java @@ -1,11 +1,11 @@ package issue1542; public class NeedsIntRange { - public static int range(boolean big) { - if (big) { - return 20000; - } else { - return 3; - } + public static int range(boolean big) { + if (big) { + return 20000; + } else { + return 3; } + } } diff --git a/checker/jtreg/stubs/issue1542/Stub.java b/checker/jtreg/stubs/issue1542/Stub.java index d6ffcde2884..f1da1ae9201 100644 --- a/checker/jtreg/stubs/issue1542/Stub.java +++ b/checker/jtreg/stubs/issue1542/Stub.java @@ -1,34 +1,34 @@ package issue1542; public class Stub { - int x1; - int x2; - int x3; - int x4; - int x5; - int x6; - int x7; - int x8; - int x9; - int x10; - int x11; - int x12; - int x13; - int x14; - int x15; - int x16; - int x17; - int x18; - int x19; - int x20; + int x1; + int x2; + int x3; + int x4; + int x5; + int x6; + int x7; + int x8; + int x9; + int x10; + int x11; + int x12; + int x13; + int x14; + int x15; + int x16; + int x17; + int x18; + int x19; + int x20; - public class InnerStub { - int x20; + public class InnerStub { + int x20; - class InnerInnerStub { - int x20; - } + class InnerInnerStub { + int x20; } + } - class InnerStub2 {} + class InnerStub2 {} } diff --git a/checker/jtreg/stubs/issue1542/UsesIntRange.java b/checker/jtreg/stubs/issue1542/UsesIntRange.java index d5bfaa01525..0a0571805ec 100644 --- a/checker/jtreg/stubs/issue1542/UsesIntRange.java +++ b/checker/jtreg/stubs/issue1542/UsesIntRange.java @@ -3,8 +3,8 @@ import org.checkerframework.common.value.qual.IntRange; public class UsesIntRange { - void do_things() { - @IntRange(from = 3, to = 20000) int x = NeedsIntRange.range(true); - @IntRange(from = 3L, to = 20000) int y = NeedsIntRange.range(true); - } + void do_things() { + @IntRange(from = 3, to = 20000) int x = NeedsIntRange.range(true); + @IntRange(from = 3L, to = 20000) int y = NeedsIntRange.range(true); + } } diff --git a/checker/jtreg/stubs/issue1565/MyListGeneric.java b/checker/jtreg/stubs/issue1565/MyListGeneric.java index d4fe937c69a..719f64c91ea 100644 --- a/checker/jtreg/stubs/issue1565/MyListGeneric.java +++ b/checker/jtreg/stubs/issue1565/MyListGeneric.java @@ -8,7 +8,7 @@ import java.util.AbstractList; public abstract class MyListGeneric extends AbstractList { - public MyListGeneric() { - clear(); - } + public MyListGeneric() { + clear(); + } } diff --git a/checker/jtreg/stubs/issue2147/EnumStubTest.java b/checker/jtreg/stubs/issue2147/EnumStubTest.java index 79c6974beb4..05de3b58088 100644 --- a/checker/jtreg/stubs/issue2147/EnumStubTest.java +++ b/checker/jtreg/stubs/issue2147/EnumStubTest.java @@ -11,10 +11,10 @@ import org.checkerframework.checker.tainting.qual.*; public class EnumStubTest { - void test() { - requireEnum(SampleEnum.FIRST); - requireEnum(SampleEnum.SECOND); - } + void test() { + requireEnum(SampleEnum.FIRST); + requireEnum(SampleEnum.SECOND); + } - void requireEnum(@Untainted SampleEnum sEnum) {} + void requireEnum(@Untainted SampleEnum sEnum) {} } diff --git a/checker/jtreg/stubs/issue2147/SampleEnum.java b/checker/jtreg/stubs/issue2147/SampleEnum.java index 912149c2fc4..ef1bb6a85f6 100644 --- a/checker/jtreg/stubs/issue2147/SampleEnum.java +++ b/checker/jtreg/stubs/issue2147/SampleEnum.java @@ -1,6 +1,6 @@ import org.checkerframework.checker.tainting.qual.*; public enum SampleEnum { - FIRST, - SECOND; + FIRST, + SECOND; } diff --git a/checker/jtreg/stubs/sample/Sample.java b/checker/jtreg/stubs/sample/Sample.java index 9b15e7eb156..d2c2501be6b 100644 --- a/checker/jtreg/stubs/sample/Sample.java +++ b/checker/jtreg/stubs/sample/Sample.java @@ -6,8 +6,8 @@ */ public class Sample { - void test() { - Object o = null; - String v = String.valueOf(o); - } + void test() { + Object o = null; + String v = String.valueOf(o); + } } diff --git a/checker/jtreg/stubs/stub-over-jdk/Issue321.java b/checker/jtreg/stubs/stub-over-jdk/Issue321.java index ad8fac0360f..d0e1ae09e8d 100644 --- a/checker/jtreg/stubs/stub-over-jdk/Issue321.java +++ b/checker/jtreg/stubs/stub-over-jdk/Issue321.java @@ -5,10 +5,9 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=Issue321.astub Issue321.java */ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; interface Issue321 { - @Nullable Optional o(); + @Nullable Optional o(); } diff --git a/checker/jtreg/stubs/wildcards/Wildcards.java b/checker/jtreg/stubs/wildcards/Wildcards.java index 79caa4414aa..ae6d6f15aab 100644 --- a/checker/jtreg/stubs/wildcards/Wildcards.java +++ b/checker/jtreg/stubs/wildcards/Wildcards.java @@ -7,9 +7,9 @@ */ public class Wildcards { - NonN f = new NonN(); + NonN f = new NonN(); - class LocalNonN {} + class LocalNonN {} - LocalNonN g = new LocalNonN(); + LocalNonN g = new LocalNonN(); } diff --git a/checker/jtreg/tainting/NewClass.java b/checker/jtreg/tainting/NewClass.java index 810f09b3130..67c2662856a 100644 --- a/checker/jtreg/tainting/NewClass.java +++ b/checker/jtreg/tainting/NewClass.java @@ -8,13 +8,13 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class NewClass { - public NewClass(Object param) {} + public NewClass(Object param) {} - Object get(@Untainted Object o) { - return o; - } + Object get(@Untainted Object o) { + return o; + } - void test() { - NewClass newClass = new NewClass(get(get(""))); - } + void test() { + NewClass newClass = new NewClass(get(get(""))); + } } diff --git a/checker/jtreg/tainting/classes/Issue919.java b/checker/jtreg/tainting/classes/Issue919.java index 84ffb923cfd..900178464fe 100644 --- a/checker/jtreg/tainting/classes/Issue919.java +++ b/checker/jtreg/tainting/classes/Issue919.java @@ -1,11 +1,10 @@ package classes; -import java.util.Set; - import classes.Issue919B.InnerClass; +import java.util.Set; public class Issue919 { - private static void method(Set innerClassSet2) throws Exception { - Issue919B.otherMethod(innerClassSet2); - } + private static void method(Set innerClassSet2) throws Exception { + Issue919B.otherMethod(innerClassSet2); + } } diff --git a/checker/jtreg/tainting/classes/Issue919B.java b/checker/jtreg/tainting/classes/Issue919B.java index 558fefa9559..6c906a369de 100644 --- a/checker/jtreg/tainting/classes/Issue919B.java +++ b/checker/jtreg/tainting/classes/Issue919B.java @@ -5,20 +5,20 @@ public class Issue919B { - @SuppressWarnings("nullness:return.type.incompatible") - public static Map otherMethod(Set innerClassSet) { - return null; - } + @SuppressWarnings("nullness:return.type.incompatible") + public static Map otherMethod(Set innerClassSet) { + return null; + } - public static class InnerClass { + public static class InnerClass { - InnerClass() {} + InnerClass() {} - InnerClass method(String a) { - return new InnerClass(); - } + InnerClass method(String a) { + return new InnerClass(); } + } - // This class is required in order to reproduce the error. - public static class AnotherInnerClass {} + // This class is required in order to reproduce the error. + public static class AnotherInnerClass {} } diff --git a/checker/jtreg/unboundedWildcards/issue1275/Crash.java b/checker/jtreg/unboundedWildcards/issue1275/Crash.java index 2f3fddf281f..46ecb23c8d5 100644 --- a/checker/jtreg/unboundedWildcards/issue1275/Crash.java +++ b/checker/jtreg/unboundedWildcards/issue1275/Crash.java @@ -7,8 +7,8 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Crash.java */ public class Crash { - void crash(Sub o) { - Sub.SubInner x = o.a().b().b(); - o.a().b().b().c(); - } + void crash(Sub o) { + Sub.SubInner x = o.a().b().b(); + o.a().b().b().c(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1275/Lib.java b/checker/jtreg/unboundedWildcards/issue1275/Lib.java index 3701d382939..769ae602059 100644 --- a/checker/jtreg/unboundedWildcards/issue1275/Lib.java +++ b/checker/jtreg/unboundedWildcards/issue1275/Lib.java @@ -1,22 +1,22 @@ abstract class Super { - abstract SuperInner a(); + abstract SuperInner a(); - abstract static class SuperInner> { - abstract T b(); + abstract static class SuperInner> { + abstract T b(); - abstract Super c(); - } + abstract Super c(); + } } abstract class Sub extends Super { - // It is significant that this method specializes the - // return type. If this returns SuperInner, no crash happens. - @Override - abstract SubInner a(); + // It is significant that this method specializes the + // return type. If this returns SuperInner, no crash happens. + @Override + abstract SubInner a(); - abstract static class SubInner> extends Super.SuperInner { - // Crashes with this overridden method and passes without it. - @Override - abstract Super c(); - } + abstract static class SubInner> extends Super.SuperInner { + // Crashes with this overridden method and passes without it. + @Override + abstract Super c(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1420/Crash8.java b/checker/jtreg/unboundedWildcards/issue1420/Crash8.java index 6eb55f8104f..554a114671a 100644 --- a/checker/jtreg/unboundedWildcards/issue1420/Crash8.java +++ b/checker/jtreg/unboundedWildcards/issue1420/Crash8.java @@ -7,8 +7,8 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker Crash8.java */ abstract class Crash8> { - void test(Crash8Lib.Main main, boolean b, Class cls) { - Crash8Lib.MyIterable x = main.foo(cls).get1().getIterable2(); - x = b ? x : main.foo(cls).get1().getIterable2(); - } + void test(Crash8Lib.Main main, boolean b, Class cls) { + Crash8Lib.MyIterable x = main.foo(cls).get1().getIterable2(); + x = b ? x : main.foo(cls).get1().getIterable2(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java b/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java index 4cdb6cf77c2..386ea3b455d 100644 --- a/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java +++ b/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java @@ -1,19 +1,19 @@ interface Crash8Lib { - interface MyIterable extends Iterable {} + interface MyIterable extends Iterable {} - interface Box> {} + interface Box> {} - interface Root, D> { - C get1(); + interface Root, D> { + C get1(); - MyIterable getIterable2(); - } + MyIterable getIterable2(); + } - interface Sub, G, H extends Box> extends Root {} + interface Sub, G, H extends Box> extends Root {} - interface Leaf, J extends Box> extends Sub {} + interface Leaf, J extends Box> extends Sub {} - interface Main { - > Leaf foo(Class b); - } + interface Main { + > Leaf foo(Class b); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1427/B.java b/checker/jtreg/unboundedWildcards/issue1427/B.java index 2ac65df83c3..ec411f85a4a 100644 --- a/checker/jtreg/unboundedWildcards/issue1427/B.java +++ b/checker/jtreg/unboundedWildcards/issue1427/B.java @@ -1,28 +1,28 @@ class One { - abstract static class B, C extends One> { - abstract C build(); + abstract static class B, C extends One> { + abstract C build(); - A f() { - throw new AssertionError(); - } + A f() { + throw new AssertionError(); } + } } class Two extends One { - static class B> extends One.B { - @Override - Two build() { - throw new AssertionError(); - } + static class B> extends One.B { + @Override + Two build() { + throw new AssertionError(); } + } } class Three> extends Two.B { - static Three c() { - throw new AssertionError(); - } + static Three c() { + throw new AssertionError(); + } - E g() { - throw new AssertionError(); - } + E g() { + throw new AssertionError(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1427/T.java b/checker/jtreg/unboundedWildcards/issue1427/T.java index 5fcad3ecdc0..632bf45d238 100644 --- a/checker/jtreg/unboundedWildcards/issue1427/T.java +++ b/checker/jtreg/unboundedWildcards/issue1427/T.java @@ -7,7 +7,7 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker T.java */ public class T { - { - Three.c().g().f().build(); - } + { + Three.c().g().f().build(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1428/B.java b/checker/jtreg/unboundedWildcards/issue1428/B.java index d2fd0035002..181d6da11d8 100644 --- a/checker/jtreg/unboundedWildcards/issue1428/B.java +++ b/checker/jtreg/unboundedWildcards/issue1428/B.java @@ -1,7 +1,7 @@ import java.util.List; interface B { - List> getItems(); + List> getItems(); } interface V {} diff --git a/checker/jtreg/unboundedWildcards/issue1428/T.java b/checker/jtreg/unboundedWildcards/issue1428/T.java index e85b0e55fbd..fe27125b5c9 100644 --- a/checker/jtreg/unboundedWildcards/issue1428/T.java +++ b/checker/jtreg/unboundedWildcards/issue1428/T.java @@ -11,7 +11,7 @@ import java.util.List; public class T { - public List> f(B b) { - return true ? Collections.>emptyList() : b.getItems(); - } + public List> f(B b) { + return true ? Collections.>emptyList() : b.getItems(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java index a1085a57284..607067af8ed 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java @@ -2,57 +2,54 @@ import com.google.common.collect.ImmutableSet; import com.sun.tools.javac.code.Type; - +import java.util.Set; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.common.accumulation.AccumulationAnalysis; import org.checkerframework.common.basetype.BaseTypeChecker; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; - /** * The analysis for the Called Methods Checker. The analysis is specialized to ignore certain * exception types; see {@link #isIgnoredExceptionType(TypeMirror)}. */ public class CalledMethodsAnalysis extends AccumulationAnalysis { - /** - * The fully-qualified names of the exception types that are ignored by this checker when - * computing dataflow stores. - */ - protected static final Set<@CanonicalName String> ignoredExceptionTypes = - ImmutableSet.of( - // Use the Nullness Checker instead. - NullPointerException.class.getCanonicalName(), - // Ignore run-time errors, which cannot be predicted statically. Doing - // so is unsound in the sense that they could still occur - e.g., the - // program could run out of memory - but if they did, the checker's - // results would be useless anyway. - Error.class.getCanonicalName(), - RuntimeException.class.getCanonicalName()); - - /** - * Creates a new {@code CalledMethodsAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - protected CalledMethodsAnalysis( - BaseTypeChecker checker, CalledMethodsAnnotatedTypeFactory factory) { - super(checker, factory); - } - - /** - * Ignore exceptional control flow due to ignored exception types. - * - * @param exceptionType exception type - * @return {@code true} if {@code exceptionType} is a member of {@link #ignoredExceptionTypes}, - * {@code false} otherwise - */ - @Override - protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { - return ignoredExceptionTypes.contains( - ((Type) exceptionType).tsym.getQualifiedName().toString()); - } + /** + * The fully-qualified names of the exception types that are ignored by this checker when + * computing dataflow stores. + */ + protected static final Set<@CanonicalName String> ignoredExceptionTypes = + ImmutableSet.of( + // Use the Nullness Checker instead. + NullPointerException.class.getCanonicalName(), + // Ignore run-time errors, which cannot be predicted statically. Doing + // so is unsound in the sense that they could still occur - e.g., the + // program could run out of memory - but if they did, the checker's + // results would be useless anyway. + Error.class.getCanonicalName(), + RuntimeException.class.getCanonicalName()); + + /** + * Creates a new {@code CalledMethodsAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + protected CalledMethodsAnalysis( + BaseTypeChecker checker, CalledMethodsAnnotatedTypeFactory factory) { + super(checker, factory); + } + + /** + * Ignore exceptional control flow due to ignored exception types. + * + * @param exceptionType exception type + * @return {@code true} if {@code exceptionType} is a member of {@link #ignoredExceptionTypes}, + * {@code false} otherwise + */ + @Override + protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { + return ignoredExceptionTypes.contains( + ((Type) exceptionType).tsym.getQualifiedName().toString()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java index 8a310f5a7df..a0c98b5c785 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java @@ -4,7 +4,19 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; - +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.calledmethods.builder.AutoValueSupport; import org.checkerframework.checker.calledmethods.builder.BuilderFrameworkSupport; import org.checkerframework.checker.calledmethods.builder.LombokSupport; @@ -35,547 +47,509 @@ import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.UserError; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** The annotated type factory for the Called Methods Checker. */ public class CalledMethodsAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory { - /** - * The builder frameworks (such as Lombok and AutoValue) supported by this instance of the - * Called Methods Checker. - */ - private final Collection builderFrameworkSupports; - - /** - * Whether to use the Value Checker as a subchecker to reduce false positives when analyzing - * calls to the AWS SDK. Defaults to false. Controlled by the command-line option {@code - * -AuseValueChecker}. - */ - private final boolean useValueChecker; - - /** - * The {@link java.util.Collections#singletonList} method. It is treated specially by {@link - * #adjustMethodNameUsingValueChecker}. - */ - private final ExecutableElement collectionsSingletonList = - TreeUtils.getMethod("java.util.Collections", "singletonList", 1, getProcessingEnv()); - - /** The {@link CalledMethods#value} element/argument. */ - /*package-private*/ final ExecutableElement calledMethodsValueElement = - TreeUtils.getMethod(CalledMethods.class, "value", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsVarArgs#value} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsVarArgsValueElement = - TreeUtils.getMethod(EnsuresCalledMethodsVarArgs.class, "value", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsOnException#value} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionValueElement = - TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "value", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsOnException#methods} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionMethodsElement = - TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "methods", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsOnException.List#value} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionListValueElement = - TreeUtils.getMethod( - EnsuresCalledMethodsOnException.List.class, "value", 0, processingEnv); - - /** - * Create a new CalledMethodsAnnotatedTypeFactory. - * - * @param checker the checker - */ - public CalledMethodsAnnotatedTypeFactory(BaseTypeChecker checker) { - super( - checker, - CalledMethods.class, - CalledMethodsBottom.class, - CalledMethodsPredicate.class); - - this.builderFrameworkSupports = new ArrayList<>(2); - List disabledFrameworks = - checker.getStringsOption( - CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, ','); - enableFrameworks(disabledFrameworks); - - this.useValueChecker = checker.hasOption(CalledMethodsChecker.USE_VALUE_CHECKER); - - // Lombok generates @CalledMethods annotations using an old package name, - // so we maintain it as an alias. - addAliasedTypeAnnotation( - "org.checkerframework.checker.builder.qual.CalledMethods", - CalledMethods.class, - true); - // Lombok also generates an @NotCalledMethods annotation, which we have no support for. We - // therefore treat it as top. - addAliasedTypeAnnotation( - "org.checkerframework.checker.builder.qual.NotCalledMethods", this.top); - - // Don't call postInit() for subclasses. - if (this.getClass() == CalledMethodsAnnotatedTypeFactory.class) { - this.postInit(); - } + /** + * The builder frameworks (such as Lombok and AutoValue) supported by this instance of the Called + * Methods Checker. + */ + private final Collection builderFrameworkSupports; + + /** + * Whether to use the Value Checker as a subchecker to reduce false positives when analyzing calls + * to the AWS SDK. Defaults to false. Controlled by the command-line option {@code + * -AuseValueChecker}. + */ + private final boolean useValueChecker; + + /** + * The {@link java.util.Collections#singletonList} method. It is treated specially by {@link + * #adjustMethodNameUsingValueChecker}. + */ + private final ExecutableElement collectionsSingletonList = + TreeUtils.getMethod("java.util.Collections", "singletonList", 1, getProcessingEnv()); + + /** The {@link CalledMethods#value} element/argument. */ + /*package-private*/ final ExecutableElement calledMethodsValueElement = + TreeUtils.getMethod(CalledMethods.class, "value", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsVarArgs#value} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsVarArgsValueElement = + TreeUtils.getMethod(EnsuresCalledMethodsVarArgs.class, "value", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsOnException#value} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionValueElement = + TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "value", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsOnException#methods} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionMethodsElement = + TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "methods", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsOnException.List#value} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionListValueElement = + TreeUtils.getMethod(EnsuresCalledMethodsOnException.List.class, "value", 0, processingEnv); + + /** + * Create a new CalledMethodsAnnotatedTypeFactory. + * + * @param checker the checker + */ + public CalledMethodsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class); + + this.builderFrameworkSupports = new ArrayList<>(2); + List disabledFrameworks = + checker.getStringsOption(CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, ','); + enableFrameworks(disabledFrameworks); + + this.useValueChecker = checker.hasOption(CalledMethodsChecker.USE_VALUE_CHECKER); + + // Lombok generates @CalledMethods annotations using an old package name, + // so we maintain it as an alias. + addAliasedTypeAnnotation( + "org.checkerframework.checker.builder.qual.CalledMethods", CalledMethods.class, true); + // Lombok also generates an @NotCalledMethods annotation, which we have no support for. We + // therefore treat it as top. + addAliasedTypeAnnotation( + "org.checkerframework.checker.builder.qual.NotCalledMethods", this.top); + + // Don't call postInit() for subclasses. + if (this.getClass() == CalledMethodsAnnotatedTypeFactory.class) { + this.postInit(); } - - /** - * Enables support for the default builder-generation frameworks, except those listed in the - * disabled builder frameworks parsed from the -AdisableBuilderFrameworkSupport option's - * arguments. Throws a UserError if the user included an unsupported framework in the list of - * frameworks to be disabled. - * - * @param disabledFrameworks the disabled builder frameworks - */ - private void enableFrameworks(List disabledFrameworks) { - boolean enableAutoValueSupport = true; - boolean enableLombokSupport = true; - for (String framework : disabledFrameworks) { - switch (framework) { - case "autovalue": - enableAutoValueSupport = false; - break; - case "lombok": - enableLombokSupport = false; - break; - default: - throw new UserError( - "Unsupported builder framework in -AdisableBuilderFrameworkSupports: " - + framework); - } - } - if (enableAutoValueSupport) { - builderFrameworkSupports.add(new AutoValueSupport(this)); - } - if (enableLombokSupport) { - builderFrameworkSupports.add(new LombokSupport(this)); - } + } + + /** + * Enables support for the default builder-generation frameworks, except those listed in the + * disabled builder frameworks parsed from the -AdisableBuilderFrameworkSupport option's + * arguments. Throws a UserError if the user included an unsupported framework in the list of + * frameworks to be disabled. + * + * @param disabledFrameworks the disabled builder frameworks + */ + private void enableFrameworks(List disabledFrameworks) { + boolean enableAutoValueSupport = true; + boolean enableLombokSupport = true; + for (String framework : disabledFrameworks) { + switch (framework) { + case "autovalue": + enableAutoValueSupport = false; + break; + case "lombok": + enableLombokSupport = false; + break; + default: + throw new UserError( + "Unsupported builder framework in -AdisableBuilderFrameworkSupports: " + framework); + } } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new CalledMethodsTreeAnnotator(this)); + if (enableAutoValueSupport) { + builderFrameworkSupports.add(new AutoValueSupport(this)); } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator( - super.createTypeAnnotator(), new CalledMethodsTypeAnnotator(this)); + if (enableLombokSupport) { + builderFrameworkSupports.add(new LombokSupport(this)); } - - @Override - public boolean returnsThis(MethodInvocationTree tree) { - return super.returnsThis(tree) - // Continue to trust but not check the old {@link - // org.checkerframework.checker.builder.qual.ReturnsReceiver} annotation, for - // backwards compatibility. - || this.getDeclAnnotation( - TreeUtils.elementFromUse(tree), - org.checkerframework.checker.builder.qual.ReturnsReceiver.class) - != null; + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new CalledMethodsTreeAnnotator(this)); + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(super.createTypeAnnotator(), new CalledMethodsTypeAnnotator(this)); + } + + @Override + public boolean returnsThis(MethodInvocationTree tree) { + return super.returnsThis(tree) + // Continue to trust but not check the old {@link + // org.checkerframework.checker.builder.qual.ReturnsReceiver} annotation, for + // backwards compatibility. + || this.getDeclAnnotation( + TreeUtils.elementFromUse(tree), + org.checkerframework.checker.builder.qual.ReturnsReceiver.class) + != null; + } + + /** + * Given a tree, returns the name of the method that the tree should be considered as calling. + * Returns "withOwners" if the call sets an "owner", "owner-alias", or "owner-id" filter. Returns + * "withImageIds" if the call sets an "image-ids" filter. + * + *

Package-private to permit calls from {@link CalledMethodsTransfer}. + * + * @param methodName the name of the method being explicitly called + * @param tree the invocation of the method + * @return "withOwners" or "withImageIds" if the tree is an equivalent filter addition. Otherwise, + * return the first argument. + */ + // This cannot return a Name because filterTreeToMethodName cannot. + public String adjustMethodNameUsingValueChecker(String methodName, MethodInvocationTree tree) { + if (!useValueChecker) { + return methodName; } - /** - * Given a tree, returns the name of the method that the tree should be considered as calling. - * Returns "withOwners" if the call sets an "owner", "owner-alias", or "owner-id" filter. - * Returns "withImageIds" if the call sets an "image-ids" filter. - * - *

Package-private to permit calls from {@link CalledMethodsTransfer}. - * - * @param methodName the name of the method being explicitly called - * @param tree the invocation of the method - * @return "withOwners" or "withImageIds" if the tree is an equivalent filter addition. - * Otherwise, return the first argument. - */ - // This cannot return a Name because filterTreeToMethodName cannot. - public String adjustMethodNameUsingValueChecker(String methodName, MethodInvocationTree tree) { - if (!useValueChecker) { - return methodName; - } - - ExecutableElement invokedMethod = TreeUtils.elementFromUse(tree); - if (!ElementUtils.enclosingTypeElement(invokedMethod) - .getQualifiedName() - .contentEquals("com.amazonaws.services.ec2.model.DescribeImagesRequest")) { - return methodName; - } - - if (methodName.equals("withFilters") || methodName.equals("setFilters")) { - ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); - for (Tree filterTree : tree.getArguments()) { - if (TreeUtils.isMethodInvocation( - filterTree, collectionsSingletonList, getProcessingEnv())) { - // Descend into a call to Collections.singletonList() - filterTree = ((MethodInvocationTree) filterTree).getArguments().get(0); - } - String adjustedMethodName = filterTreeToMethodName(filterTree, valueATF); - if (adjustedMethodName != null) { - return adjustedMethodName; - } - } - } - return methodName; + ExecutableElement invokedMethod = TreeUtils.elementFromUse(tree); + if (!ElementUtils.enclosingTypeElement(invokedMethod) + .getQualifiedName() + .contentEquals("com.amazonaws.services.ec2.model.DescribeImagesRequest")) { + return methodName; } - /** - * Determine the name of the method in DescribeImagesRequest that is equivalent to the Filter in - * the given tree. - * - *

Returns null unless the argument is one of the following: - * - *

    - *
  • a constructor invocation of the Filter constructor whose first argument is the name, - * such as {@code new Filter("owner").*}, or - *
  • a call to the withName method, such as {@code new Filter().*.withName("owner").*}. - *
- * - * In those cases, it returns either the argument to the constructor or the argument to the last - * invocation of withName ("owner" in both of the above examples). - * - * @param filterTree the tree that represents the filter (an argument to the withFilters or - * setFilters method) - * @param valueATF the type factory from the Value Checker - * @return the adjusted method name, or null if the method name should not be adjusted - */ - // This cannot return a Name because filterKindToMethodName cannot. - private @Nullable String filterTreeToMethodName( - Tree filterTree, ValueAnnotatedTypeFactory valueATF) { - while (filterTree != null && filterTree.getKind() == Tree.Kind.METHOD_INVOCATION) { - - MethodInvocationTree filterTreeAsMethodInvocation = (MethodInvocationTree) filterTree; - String filterMethodName = TreeUtils.methodName(filterTreeAsMethodInvocation).toString(); - if (filterMethodName.contentEquals("withName") - && filterTreeAsMethodInvocation.getArguments().size() >= 1) { - Tree withNameArgTree = filterTreeAsMethodInvocation.getArguments().get(0); - String withNameArg = - ValueCheckerUtils.getExactStringValue(withNameArgTree, valueATF); - return filterKindToMethodName(withNameArg); - } - // Proceed leftward (toward the receiver) in a fluent call sequence. - filterTree = TreeUtils.getReceiverTree(filterTreeAsMethodInvocation.getMethodSelect()); - } - // The loop has reached the beginning of a fluent sequence of method calls. If the ultimate - // receiver at the beginning of that fluent sequence is a call to the Filter() constructor, - // then use the first argument to the Filter constructor, which is the name of the filter. - if (filterTree == null) { - return null; + if (methodName.equals("withFilters") || methodName.equals("setFilters")) { + ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); + for (Tree filterTree : tree.getArguments()) { + if (TreeUtils.isMethodInvocation( + filterTree, collectionsSingletonList, getProcessingEnv())) { + // Descend into a call to Collections.singletonList() + filterTree = ((MethodInvocationTree) filterTree).getArguments().get(0); } - if (filterTree.getKind() == Tree.Kind.NEW_CLASS) { - ExpressionTree constructorArg = ((NewClassTree) filterTree).getArguments().get(0); - String filterKindName = ValueCheckerUtils.getExactStringValue(constructorArg, valueATF); - if (filterKindName != null) { - return filterKindToMethodName(filterKindName); - } - } - return null; - } - - /** - * Converts from a kind of filter to the name of the corresponding method on a - * DescribeImagesRequest object. - * - * @param filterKind the kind of filter - * @return "withOwners" if filterKind is "owner", "owner-alias", or "owner-id"; "withImageIds" - * if filterKind is "image-id"; null otherwise - */ - private static @Nullable String filterKindToMethodName(String filterKind) { - switch (filterKind) { - case "owner": - case "owner-alias": - case "owner-id": - return "withOwners"; - case "image-id": - return "withImageIds"; - default: - return null; + String adjustedMethodName = filterTreeToMethodName(filterTree, valueATF); + if (adjustedMethodName != null) { + return adjustedMethodName; } + } } - - /** - * At a fluent method call (which returns {@code this}), add the method to the type of the - * return value. - */ - private class CalledMethodsTreeAnnotator extends AccumulationTreeAnnotator { - /** - * Creates an instance of this tree annotator for the given type factory. - * - * @param factory the type factory - */ - public CalledMethodsTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { - super(factory); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - // Accumulate a method call, by adding the method being invoked to the return type. - if (returnsThis(tree)) { - TypeMirror typeMirror = type.getUnderlyingType(); - String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); - methodName = adjustMethodNameUsingValueChecker(methodName, tree); - AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); - AnnotationMirror newAnno = - qualHierarchy.greatestLowerBoundShallow( - oldAnno, - typeMirror, - createAccumulatorAnnotation(methodName), - typeMirror); - type.replaceAnnotation(newAnno); - } - - // Also do the standard accumulation analysis behavior: copy any accumulation - // annotations from the receiver to the return type. - return super.visitMethodInvocation(tree, type); - } - - @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { - builderFrameworkSupport.handleConstructor(tree, type); - } - return super.visitNewClass(tree, type); - } + return methodName; + } + + /** + * Determine the name of the method in DescribeImagesRequest that is equivalent to the Filter in + * the given tree. + * + *

Returns null unless the argument is one of the following: + * + *

    + *
  • a constructor invocation of the Filter constructor whose first argument is the name, such + * as {@code new Filter("owner").*}, or + *
  • a call to the withName method, such as {@code new Filter().*.withName("owner").*}. + *
+ * + * In those cases, it returns either the argument to the constructor or the argument to the last + * invocation of withName ("owner" in both of the above examples). + * + * @param filterTree the tree that represents the filter (an argument to the withFilters or + * setFilters method) + * @param valueATF the type factory from the Value Checker + * @return the adjusted method name, or null if the method name should not be adjusted + */ + // This cannot return a Name because filterKindToMethodName cannot. + private @Nullable String filterTreeToMethodName( + Tree filterTree, ValueAnnotatedTypeFactory valueATF) { + while (filterTree != null && filterTree.getKind() == Tree.Kind.METHOD_INVOCATION) { + + MethodInvocationTree filterTreeAsMethodInvocation = (MethodInvocationTree) filterTree; + String filterMethodName = TreeUtils.methodName(filterTreeAsMethodInvocation).toString(); + if (filterMethodName.contentEquals("withName") + && filterTreeAsMethodInvocation.getArguments().size() >= 1) { + Tree withNameArgTree = filterTreeAsMethodInvocation.getArguments().get(0); + String withNameArg = ValueCheckerUtils.getExactStringValue(withNameArgTree, valueATF); + return filterKindToMethodName(withNameArg); + } + // Proceed leftward (toward the receiver) in a fluent call sequence. + filterTree = TreeUtils.getReceiverTree(filterTreeAsMethodInvocation.getMethodSelect()); } - - /** - * Adds @CalledMethod annotations for build() methods of AutoValue and Lombok Builders to ensure - * required properties have been set. - */ - private class CalledMethodsTypeAnnotator extends TypeAnnotator { - - /** - * Constructor matching super. - * - * @param atypeFactory the type factory - */ - public CalledMethodsTypeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { - ExecutableElement element = t.getElement(); - - TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); - - for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { - if (builderFrameworkSupport.isToBuilderMethod(element)) { - builderFrameworkSupport.handleToBuilderMethod(t); - } - } - - Element nextEnclosingElement = enclosingElement.getEnclosingElement(); - if (nextEnclosingElement.getKind().isClass()) { - for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { - if (builderFrameworkSupport.isBuilderBuildMethod(element)) { - builderFrameworkSupport.handleBuilderBuildMethod(t); - } - } - } - - return super.visitExecutable(t, p); - } + // The loop has reached the beginning of a fluent sequence of method calls. If the ultimate + // receiver at the beginning of that fluent sequence is a call to the Filter() constructor, + // then use the first argument to the Filter constructor, which is the name of the filter. + if (filterTree == null) { + return null; } - - @Override - protected CalledMethodsAnalysis createFlowAnalysis() { - return new CalledMethodsAnalysis(checker, this); + if (filterTree.getKind() == Tree.Kind.NEW_CLASS) { + ExpressionTree constructorArg = ((NewClassTree) filterTree).getArguments().get(0); + String filterKindName = ValueCheckerUtils.getExactStringValue(constructorArg, valueATF); + if (filterKindName != null) { + return filterKindToMethodName(filterKindName); + } } - - /** - * Returns the annotation type mirror for the type of {@code expressionTree} with default - * annotations applied. As types relevant to Called Methods checking are rarely used inside - * generics, this is typically the best choice for type inference. - */ - @Override - public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { - TypeMirror type = TreeUtils.typeOf(expressionTree); - if (type.getKind() != TypeKind.VOID) { - AnnotatedTypeMirror atm = type(expressionTree); - addDefaultAnnotations(atm); - return atm; - } + return null; + } + + /** + * Converts from a kind of filter to the name of the corresponding method on a + * DescribeImagesRequest object. + * + * @param filterKind the kind of filter + * @return "withOwners" if filterKind is "owner", "owner-alias", or "owner-id"; "withImageIds" if + * filterKind is "image-id"; null otherwise + */ + private static @Nullable String filterKindToMethodName(String filterKind) { + switch (filterKind) { + case "owner": + case "owner-alias": + case "owner-id": + return "withOwners"; + case "image-id": + return "withImageIds"; + default: return null; } + } + /** + * At a fluent method call (which returns {@code this}), add the method to the type of the return + * value. + */ + private class CalledMethodsTreeAnnotator extends AccumulationTreeAnnotator { /** - * Fetch the supported builder frameworks that are enabled. + * Creates an instance of this tree annotator for the given type factory. * - * @return a collection of builder frameworks that are enabled in this run of the checker + * @param factory the type factory */ - /*package-private*/ Collection getBuilderFrameworkSupports() { - return builderFrameworkSupports; + public CalledMethodsTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { + super(factory); } - /** - * Get the called methods specified by the given {@link CalledMethods} annotation. - * - * @param calledMethodsAnnotation the annotation - * @return the called methods - */ - protected List getCalledMethods(AnnotationMirror calledMethodsAnnotation) { - return AnnotationUtils.getElementValueArray( - calledMethodsAnnotation, - calledMethodsValueElement, - String.class, - Collections.emptyList()); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + // Accumulate a method call, by adding the method being invoked to the return type. + if (returnsThis(tree)) { + TypeMirror typeMirror = type.getUnderlyingType(); + String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); + methodName = adjustMethodNameUsingValueChecker(methodName, tree); + AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); + AnnotationMirror newAnno = + qualHierarchy.greatestLowerBoundShallow( + oldAnno, typeMirror, createAccumulatorAnnotation(methodName), typeMirror); + type.replaceAnnotation(newAnno); + } + + // Also do the standard accumulation analysis behavior: copy any accumulation + // annotations from the receiver to the return type. + return super.visitMethodInvocation(tree, type); } @Override - protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( - String expression, - AnnotationMirror qualifier, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - if (preOrPost == BeforeOrAfter.AFTER && isAccumulatorAnnotation(qualifier)) { - List calledMethods = getCalledMethods(qualifier); - if (!calledMethods.isEmpty()) { - return ensuresCMAnno(expression, calledMethods); - } - } - return super.createRequiresOrEnsuresQualifier( - expression, qualifier, declaredType, preOrPost, preconds); + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { + builderFrameworkSupport.handleConstructor(tree, type); + } + return super.visitNewClass(tree, type); } + } - /** - * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expression. - * - * @param expression the expression to put in the value field of the EnsuresCalledMethods - * annotation - * @param calledMethods the methods that were definitely called on the expression - * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression - */ - private AnnotationMirror ensuresCMAnno(String expression, List calledMethods) { - return ensuresCMAnno(new String[] {expression}, calledMethods); - } + /** + * Adds @CalledMethod annotations for build() methods of AutoValue and Lombok Builders to ensure + * required properties have been set. + */ + private class CalledMethodsTypeAnnotator extends TypeAnnotator { /** - * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expressions. + * Constructor matching super. * - * @param expressions the expressions to put in the value field of the EnsuresCalledMethods - * annotation - * @param calledMethods the methods that were definitely called on the expression - * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression + * @param atypeFactory the type factory */ - private AnnotationMirror ensuresCMAnno(String[] expressions, List calledMethods) { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, EnsuresCalledMethods.class); - builder.setValue("value", expressions); - builder.setValue("methods", calledMethods.toArray(new String[calledMethods.size()])); - AnnotationMirror am = builder.build(); - return am; + public CalledMethodsTypeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - /** - * Returns true if the checker should ignore exceptional control flow due to the given exception - * type. - * - * @param exceptionType exception type - * @return {@code true} if {@code exceptionType} is a member of {@link - * CalledMethodsAnalysis#ignoredExceptionTypes}, {@code false} otherwise - */ @Override - public boolean isIgnoredExceptionType(TypeMirror exceptionType) { - if (exceptionType.getKind() == TypeKind.DECLARED) { - return CalledMethodsAnalysis.ignoredExceptionTypes.contains( - TypesUtils.getQualifiedName((DeclaredType) exceptionType)); - } - return false; - } + public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { + ExecutableElement element = t.getElement(); - /** - * Get the exceptional postconditions for the given method from the {@link - * EnsuresCalledMethodsOnException} annotations on it. - * - * @param methodOrConstructor the method to examine - * @return the exceptional postconditions on the given method; the return value is - * newly-allocated and can be freely modified by callers - */ - public Set getExceptionalPostconditions( - ExecutableElement methodOrConstructor) { - Set result = new LinkedHashSet<>(); + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); - parseEnsuresCalledMethodOnExceptionListAnnotation( - getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.List.class), - result); - - parseEnsuresCalledMethodOnExceptionAnnotation( - getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.class), - result); + for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { + if (builderFrameworkSupport.isToBuilderMethod(element)) { + builderFrameworkSupport.handleToBuilderMethod(t); + } + } + + Element nextEnclosingElement = enclosingElement.getEnclosingElement(); + if (nextEnclosingElement.getKind().isClass()) { + for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { + if (builderFrameworkSupport.isBuilderBuildMethod(element)) { + builderFrameworkSupport.handleBuilderBuildMethod(t); + } + } + } - return result; + return super.visitExecutable(t, p); + } + } + + @Override + protected CalledMethodsAnalysis createFlowAnalysis() { + return new CalledMethodsAnalysis(checker, this); + } + + /** + * Returns the annotation type mirror for the type of {@code expressionTree} with default + * annotations applied. As types relevant to Called Methods checking are rarely used inside + * generics, this is typically the best choice for type inference. + */ + @Override + public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { + TypeMirror type = TreeUtils.typeOf(expressionTree); + if (type.getKind() != TypeKind.VOID) { + AnnotatedTypeMirror atm = type(expressionTree); + addDefaultAnnotations(atm); + return atm; + } + return null; + } + + /** + * Fetch the supported builder frameworks that are enabled. + * + * @return a collection of builder frameworks that are enabled in this run of the checker + */ + /*package-private*/ Collection getBuilderFrameworkSupports() { + return builderFrameworkSupports; + } + + /** + * Get the called methods specified by the given {@link CalledMethods} annotation. + * + * @param calledMethodsAnnotation the annotation + * @return the called methods + */ + protected List getCalledMethods(AnnotationMirror calledMethodsAnnotation) { + return AnnotationUtils.getElementValueArray( + calledMethodsAnnotation, calledMethodsValueElement, String.class, Collections.emptyList()); + } + + @Override + protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( + String expression, + AnnotationMirror qualifier, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + if (preOrPost == BeforeOrAfter.AFTER && isAccumulatorAnnotation(qualifier)) { + List calledMethods = getCalledMethods(qualifier); + if (!calledMethods.isEmpty()) { + return ensuresCMAnno(expression, calledMethods); + } + } + return super.createRequiresOrEnsuresQualifier( + expression, qualifier, declaredType, preOrPost, preconds); + } + + /** + * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expression. + * + * @param expression the expression to put in the value field of the EnsuresCalledMethods + * annotation + * @param calledMethods the methods that were definitely called on the expression + * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression + */ + private AnnotationMirror ensuresCMAnno(String expression, List calledMethods) { + return ensuresCMAnno(new String[] {expression}, calledMethods); + } + + /** + * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expressions. + * + * @param expressions the expressions to put in the value field of the EnsuresCalledMethods + * annotation + * @param calledMethods the methods that were definitely called on the expression + * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression + */ + private AnnotationMirror ensuresCMAnno(String[] expressions, List calledMethods) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresCalledMethods.class); + builder.setValue("value", expressions); + builder.setValue("methods", calledMethods.toArray(new String[calledMethods.size()])); + AnnotationMirror am = builder.build(); + return am; + } + + /** + * Returns true if the checker should ignore exceptional control flow due to the given exception + * type. + * + * @param exceptionType exception type + * @return {@code true} if {@code exceptionType} is a member of {@link + * CalledMethodsAnalysis#ignoredExceptionTypes}, {@code false} otherwise + */ + @Override + public boolean isIgnoredExceptionType(TypeMirror exceptionType) { + if (exceptionType.getKind() == TypeKind.DECLARED) { + return CalledMethodsAnalysis.ignoredExceptionTypes.contains( + TypesUtils.getQualifiedName((DeclaredType) exceptionType)); + } + return false; + } + + /** + * Get the exceptional postconditions for the given method from the {@link + * EnsuresCalledMethodsOnException} annotations on it. + * + * @param methodOrConstructor the method to examine + * @return the exceptional postconditions on the given method; the return value is newly-allocated + * and can be freely modified by callers + */ + public Set getExceptionalPostconditions( + ExecutableElement methodOrConstructor) { + Set result = new LinkedHashSet<>(); + + parseEnsuresCalledMethodOnExceptionListAnnotation( + getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.List.class), result); + + parseEnsuresCalledMethodOnExceptionAnnotation( + getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.class), result); + + return result; + } + + /** + * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link + * EnsuresCalledMethodsOnException.List} annotation and stores the results in out. + * + * @param annotation the annotation + * @param out the output collection + */ + private void parseEnsuresCalledMethodOnExceptionListAnnotation( + @Nullable AnnotationMirror annotation, Set out) { + if (annotation == null) { + return; } - /** - * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link - * EnsuresCalledMethodsOnException.List} annotation and stores the results in out. - * - * @param annotation the annotation - * @param out the output collection - */ - private void parseEnsuresCalledMethodOnExceptionListAnnotation( - @Nullable AnnotationMirror annotation, - Set out) { - if (annotation == null) { - return; - } - - List annotations = - AnnotationUtils.getElementValueArray( - annotation, - ensuresCalledMethodsOnExceptionListValueElement, - AnnotationMirror.class, - Collections.emptyList()); + List annotations = + AnnotationUtils.getElementValueArray( + annotation, + ensuresCalledMethodsOnExceptionListValueElement, + AnnotationMirror.class, + Collections.emptyList()); - for (AnnotationMirror a : annotations) { - parseEnsuresCalledMethodOnExceptionAnnotation(a, out); - } + for (AnnotationMirror a : annotations) { + parseEnsuresCalledMethodOnExceptionAnnotation(a, out); + } + } + + /** + * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link + * EnsuresCalledMethodsOnException} annotation and stores the results in out. + * + * @param annotation the annotation + * @param out the output collection + */ + private void parseEnsuresCalledMethodOnExceptionAnnotation( + @Nullable AnnotationMirror annotation, Set out) { + if (annotation == null) { + return; } - /** - * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link - * EnsuresCalledMethodsOnException} annotation and stores the results in out. - * - * @param annotation the annotation - * @param out the output collection - */ - private void parseEnsuresCalledMethodOnExceptionAnnotation( - @Nullable AnnotationMirror annotation, - Set out) { - if (annotation == null) { - return; - } - - List expressions = - AnnotationUtils.getElementValueArray( - annotation, - ensuresCalledMethodsOnExceptionValueElement, - String.class, - Collections.emptyList()); - List methods = - AnnotationUtils.getElementValueArray( - annotation, - ensuresCalledMethodsOnExceptionMethodsElement, - String.class, - Collections.emptyList()); - - for (String expr : expressions) { - for (String method : methods) { - out.add(new EnsuresCalledMethodOnExceptionContract(expr, method)); - } - } + List expressions = + AnnotationUtils.getElementValueArray( + annotation, + ensuresCalledMethodsOnExceptionValueElement, + String.class, + Collections.emptyList()); + List methods = + AnnotationUtils.getElementValueArray( + annotation, + ensuresCalledMethodsOnExceptionMethodsElement, + String.class, + Collections.emptyList()); + + for (String expr : expressions) { + for (String method : methods) { + out.add(new EnsuresCalledMethodOnExceptionContract(expr, method)); + } } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java index a91368f6f12..e70175c5b81 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.calledmethods; +import java.util.Set; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.common.accumulation.AccumulationChecker; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -9,114 +10,111 @@ import org.checkerframework.framework.source.SupportedOptions; import org.checkerframework.framework.source.SuppressWarningsPrefix; -import java.util.Set; - /** * The Called Methods Checker tracks the methods that have definitely been called on an object. One * common use case for the Called Methods Checker is to specify safe combinations of options to * builder or builder-like interfaces, preventing objects from being instantiated incompletely. */ @SuppressWarningsPrefix({ - // Preferred checkername. - "calledmethods", - // Deprecated checkernames, supported for backward compatibility. - "builder", - "object.construction", - "objectconstruction" + // Preferred checkername. + "calledmethods", + // Deprecated checkernames, supported for backward compatibility. + "builder", + "object.construction", + "objectconstruction" }) @SupportedOptions({ - CalledMethodsChecker.USE_VALUE_CHECKER, - CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS, - CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, - CalledMethodsChecker.DISABLE_RETURNS_RECEIVER + CalledMethodsChecker.USE_VALUE_CHECKER, + CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS, + CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, + CalledMethodsChecker.DISABLE_RETURNS_RECEIVER }) @StubFiles({"DescribeImages.astub", "GenerateDataKey.astub"}) public class CalledMethodsChecker extends AccumulationChecker { - /** - * If this option is supplied, count the number of analyzed calls to build() in supported - * builder frameworks and print it when analysis is complete. Useful for collecting metrics. - */ - public static final String COUNT_FRAMEWORK_BUILD_CALLS = "countFrameworkBuildCalls"; + /** + * If this option is supplied, count the number of analyzed calls to build() in supported builder + * frameworks and print it when analysis is complete. Useful for collecting metrics. + */ + public static final String COUNT_FRAMEWORK_BUILD_CALLS = "countFrameworkBuildCalls"; - /** - * This option disables the support for (and therefore the automated checking of) code that uses - * the given builder frameworks. Useful when a user **only** wants to enforce specifications on - * custom builder objects (such as the AWS SDK examples). - */ - public static final String DISABLE_BUILDER_FRAMEWORK_SUPPORTS = - "disableBuilderFrameworkSupports"; + /** + * This option disables the support for (and therefore the automated checking of) code that uses + * the given builder frameworks. Useful when a user **only** wants to enforce specifications on + * custom builder objects (such as the AWS SDK examples). + */ + public static final String DISABLE_BUILDER_FRAMEWORK_SUPPORTS = "disableBuilderFrameworkSupports"; - /** - * If this option is supplied, use the Value Checker to reduce false positives when analyzing - * calls to the AWS SDK. - */ - public static final String USE_VALUE_CHECKER = "useValueChecker"; + /** + * If this option is supplied, use the Value Checker to reduce false positives when analyzing + * calls to the AWS SDK. + */ + public static final String USE_VALUE_CHECKER = "useValueChecker"; - /** - * Some use cases for the Called Methods Checker do not involve checking fluent APIs, and in - * those cases disabling the Returns Receiver Checker using this flag will make the Called - * Methods Checker run much faster. - */ - public static final String DISABLE_RETURNS_RECEIVER = "disableReturnsReceiver"; + /** + * Some use cases for the Called Methods Checker do not involve checking fluent APIs, and in those + * cases disabling the Returns Receiver Checker using this flag will make the Called Methods + * Checker run much faster. + */ + public static final String DISABLE_RETURNS_RECEIVER = "disableReturnsReceiver"; - /** - * The number of calls to build frameworks supported by this invocation. Incremented only if the - * {@link #COUNT_FRAMEWORK_BUILD_CALLS} command-line option was supplied. - */ - int numBuildCalls = 0; + /** + * The number of calls to build frameworks supported by this invocation. Incremented only if the + * {@link #COUNT_FRAMEWORK_BUILD_CALLS} command-line option was supplied. + */ + int numBuildCalls = 0; - /** Never access this boolean directly. Call {@link #isReturnsReceiverDisabled()} instead. */ - private @MonotonicNonNull Boolean returnsReceiverDisabled = null; + /** Never access this boolean directly. Call {@link #isReturnsReceiverDisabled()} instead. */ + private @MonotonicNonNull Boolean returnsReceiverDisabled = null; - /** - * Was the Returns Receiver Checker disabled on the command line? - * - * @return whether the -AdisableReturnsReceiver option was specified on the command line - */ - private boolean isReturnsReceiverDisabled() { - if (returnsReceiverDisabled == null) { - returnsReceiverDisabled = hasOptionNoSubcheckers(DISABLE_RETURNS_RECEIVER); - } - return returnsReceiverDisabled; + /** + * Was the Returns Receiver Checker disabled on the command line? + * + * @return whether the -AdisableReturnsReceiver option was specified on the command line + */ + private boolean isReturnsReceiverDisabled() { + if (returnsReceiverDisabled == null) { + returnsReceiverDisabled = hasOptionNoSubcheckers(DISABLE_RETURNS_RECEIVER); } + return returnsReceiverDisabled; + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - if (!isReturnsReceiverDisabled()) { - checkers.add(ReturnsReceiverChecker.class); - } - // BaseTypeChecker#hasOption calls this method (so that all subcheckers' options are - // considered), so the processingEnvironment must be checked for options directly. - if (this.processingEnv.getOptions().containsKey(USE_VALUE_CHECKER) - || this.processingEnv - .getOptions() - .containsKey(this.getClass().getSimpleName() + "_" + USE_VALUE_CHECKER)) { - checkers.add(ValueChecker.class); - } - return checkers; + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + if (!isReturnsReceiverDisabled()) { + checkers.add(ReturnsReceiverChecker.class); + } + // BaseTypeChecker#hasOption calls this method (so that all subcheckers' options are + // considered), so the processingEnvironment must be checked for options directly. + if (this.processingEnv.getOptions().containsKey(USE_VALUE_CHECKER) + || this.processingEnv + .getOptions() + .containsKey(this.getClass().getSimpleName() + "_" + USE_VALUE_CHECKER)) { + checkers.add(ValueChecker.class); } + return checkers; + } - /** - * Check whether the given alias analysis is enabled by this particular accumulation checker. - * - * @param aliasAnalysis the analysis to check - * @return true iff the analysis is enabled - */ - @Override - public boolean isEnabled(AliasAnalysis aliasAnalysis) { - if (aliasAnalysis == AliasAnalysis.RETURNS_RECEIVER) { - return !isReturnsReceiverDisabled(); - } - return super.isEnabled(aliasAnalysis); + /** + * Check whether the given alias analysis is enabled by this particular accumulation checker. + * + * @param aliasAnalysis the analysis to check + * @return true iff the analysis is enabled + */ + @Override + public boolean isEnabled(AliasAnalysis aliasAnalysis) { + if (aliasAnalysis == AliasAnalysis.RETURNS_RECEIVER) { + return !isReturnsReceiverDisabled(); } + return super.isEnabled(aliasAnalysis); + } - @Override - public void typeProcessingOver() { - if (getBooleanOption(COUNT_FRAMEWORK_BUILD_CALLS)) { - System.out.printf("Found %d build() method calls.%n", numBuildCalls); - } - super.typeProcessingOver(); + @Override + public void typeProcessingOver() { + if (getBooleanOption(COUNT_FRAMEWORK_BUILD_CALLS)) { + System.out.printf("Found %d build() method calls.%n", numBuildCalls); } + super.typeProcessingOver(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java index d3f2926c6d5..c60408ccd01 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java @@ -1,5 +1,16 @@ package org.checkerframework.checker.calledmethods; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.accumulation.AccumulationStore; @@ -23,342 +34,317 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; - /** A transfer function that accumulates the names of methods called. */ public class CalledMethodsTransfer extends AccumulationTransfer { - /** - * {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} requires a TransferInput, - * but the actual exceptional stores need to be modified in {@link #accumulate(Node, - * TransferResult, String...)}, which only has access to a TransferResult. So this field is set - * to non-null in {@link #visitMethodInvocation(MethodInvocationNode, TransferInput)} via a call - * to {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} (which reads the - * CFStores from the TransferInput) before the call to accumulate(); accumulate() can then use - * this field to read the CFStores; and then finally this field is then reset to null afterwards - * to prevent it from being used somewhere it shouldn't be. - */ - private @Nullable Map exceptionalStores; + /** + * {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} requires a TransferInput, + * but the actual exceptional stores need to be modified in {@link #accumulate(Node, + * TransferResult, String...)}, which only has access to a TransferResult. So this field is set to + * non-null in {@link #visitMethodInvocation(MethodInvocationNode, TransferInput)} via a call to + * {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} (which reads the CFStores + * from the TransferInput) before the call to accumulate(); accumulate() can then use this field + * to read the CFStores; and then finally this field is then reset to null afterwards to prevent + * it from being used somewhere it shouldn't be. + */ + private @Nullable Map exceptionalStores; - /** - * The element for the CalledMethods annotation's value element. Stored in a field in this class - * to prevent the need to cast to CalledMethods ATF every time it's used. - */ - private final ExecutableElement calledMethodsValueElement; + /** + * The element for the CalledMethods annotation's value element. Stored in a field in this class + * to prevent the need to cast to CalledMethods ATF every time it's used. + */ + private final ExecutableElement calledMethodsValueElement; - /** The type mirror for {@link Exception}. */ - protected final TypeMirror javaLangExceptionType; + /** The type mirror for {@link Exception}. */ + protected final TypeMirror javaLangExceptionType; - /* NO-AFU - * True if -AenableWpiForRlc was passed on the command line. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - private final boolean enableWpiForRlc; - */ + /* NO-AFU + * True if -AenableWpiForRlc was passed on the command line. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + private final boolean enableWpiForRlc; + */ - /** - * Create a new CalledMethodsTransfer. - * - * @param analysis the analysis - */ - public CalledMethodsTransfer(CalledMethodsAnalysis analysis) { - super(analysis); - calledMethodsValueElement = - ((CalledMethodsAnnotatedTypeFactory) atypeFactory).calledMethodsValueElement; - // enableWpiForRlc = - // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + /** + * Create a new CalledMethodsTransfer. + * + * @param analysis the analysis + */ + public CalledMethodsTransfer(CalledMethodsAnalysis analysis) { + super(analysis); + calledMethodsValueElement = + ((CalledMethodsAnnotatedTypeFactory) atypeFactory).calledMethodsValueElement; + // enableWpiForRlc = + // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); - ProcessingEnvironment env = atypeFactory.getProcessingEnv(); - javaLangExceptionType = - env.getTypeUtils() - .getDeclaredType(ElementUtils.getTypeElement(env, Exception.class)); - } + ProcessingEnvironment env = atypeFactory.getProcessingEnv(); + javaLangExceptionType = + env.getTypeUtils().getDeclaredType(ElementUtils.getTypeElement(env, Exception.class)); + } - /* NO-AFU - * @param tree a tree - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) is not passed - * as a command line argument, otherwise returns the result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree tree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; - } - return super.shouldPerformWholeProgramInference(tree); + /* NO-AFU + * @param tree a tree + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) is not passed + * as a command line argument, otherwise returns the result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree tree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; } - */ + return super.shouldPerformWholeProgramInference(tree); + } + */ - /* NO-AFU - * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @param expressionTree a tree - * @param lhsTree its element - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the - * result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; - } - return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); + /* NO-AFU + * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @param expressionTree a tree + * @param lhsTree its element + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the + * result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; } - */ + return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); + } + */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput input) { - exceptionalStores = makeExceptionalStores(node, input); - TransferResult superResult = - super.visitMethodInvocation(node, input); + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput input) { + exceptionalStores = makeExceptionalStores(node, input); + TransferResult superResult = + super.visitMethodInvocation(node, input); - ExecutableElement method = TreeUtils.elementFromUse(node.getTree()); - handleEnsuresCalledMethodsVarArgs(node, method, superResult); - handleEnsuresCalledMethodsOnException(node, method, exceptionalStores); + ExecutableElement method = TreeUtils.elementFromUse(node.getTree()); + handleEnsuresCalledMethodsVarArgs(node, method, superResult); + handleEnsuresCalledMethodsOnException(node, method, exceptionalStores); - Node receiver = node.getTarget().getReceiver(); - if (receiver != null) { - String methodName = node.getTarget().getMethod().getSimpleName().toString(); - methodName = - ((CalledMethodsAnnotatedTypeFactory) atypeFactory) - .adjustMethodNameUsingValueChecker(methodName, node.getTree()); - accumulate(receiver, superResult, methodName); - } - TransferResult finalResult = - new ConditionalTransferResult<>( - superResult.getResultValue(), - superResult.getThenStore(), - superResult.getElseStore(), - exceptionalStores); - exceptionalStores = null; - return finalResult; + Node receiver = node.getTarget().getReceiver(); + if (receiver != null) { + String methodName = node.getTarget().getMethod().getSimpleName().toString(); + methodName = + ((CalledMethodsAnnotatedTypeFactory) atypeFactory) + .adjustMethodNameUsingValueChecker(methodName, node.getTree()); + accumulate(receiver, superResult, methodName); } + TransferResult finalResult = + new ConditionalTransferResult<>( + superResult.getResultValue(), + superResult.getThenStore(), + superResult.getElseStore(), + exceptionalStores); + exceptionalStores = null; + return finalResult; + } - @Override - public void accumulate( - Node node, - TransferResult result, - String... values) { - super.accumulate(node, result, values); - if (exceptionalStores == null) { - return; - } + @Override + public void accumulate( + Node node, TransferResult result, String... values) { + super.accumulate(node, result, values); + if (exceptionalStores == null) { + return; + } - List valuesAsList = Arrays.asList(values); - JavaExpression target = JavaExpression.fromNode(node); - if (CFAbstractStore.canInsertJavaExpression(target)) { - AccumulationValue flowValue = result.getRegularStore().getValue(target); - if (flowValue != null) { - // Dataflow has already recorded information about the target. Integrate it into - // the list of values in the new annotation. - AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); - assert flowAnnos.size() <= 1; - for (AnnotationMirror anno : flowAnnos) { - if (atypeFactory.isAccumulatorAnnotation(anno)) { - List oldFlowValues = - AnnotationUtils.getElementValueArray( - anno, calledMethodsValueElement, String.class); - // valuesAsList cannot have its length changed -- it is backed by an - // array. getElementValueArray returns a new, modifiable list. - oldFlowValues.addAll(valuesAsList); - valuesAsList = oldFlowValues; - } - } - } - AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); - exceptionalStores.forEach( - (tm, s) -> - s.replaceValue( - target, - analysis.createSingleAnnotationValue( - newAnno, target.getType()))); + List valuesAsList = Arrays.asList(values); + JavaExpression target = JavaExpression.fromNode(node); + if (CFAbstractStore.canInsertJavaExpression(target)) { + AccumulationValue flowValue = result.getRegularStore().getValue(target); + if (flowValue != null) { + // Dataflow has already recorded information about the target. Integrate it into + // the list of values in the new annotation. + AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); + assert flowAnnos.size() <= 1; + for (AnnotationMirror anno : flowAnnos) { + if (atypeFactory.isAccumulatorAnnotation(anno)) { + List oldFlowValues = + AnnotationUtils.getElementValueArray(anno, calledMethodsValueElement, String.class); + // valuesAsList cannot have its length changed -- it is backed by an + // array. getElementValueArray returns a new, modifiable list. + oldFlowValues.addAll(valuesAsList); + valuesAsList = oldFlowValues; + } } + } + AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); + exceptionalStores.forEach( + (tm, s) -> + s.replaceValue( + target, analysis.createSingleAnnotationValue(newAnno, target.getType()))); } + } - /** - * Create a set of stores for the exceptional paths out of the block containing {@code node}. - * This allows propagation, along those paths, of the fact that the method being invoked in - * {@code node} was definitely called. - * - * @param node a method invocation - * @param input the transfer input associated with the method invocation - * @return a map from types to stores. The keys are the same keys used by {@link - * ExceptionBlock#getExceptionalSuccessors()}. The values are copies of the regular store - * from {@code input}. - */ - private Map makeExceptionalStores( - MethodInvocationNode node, TransferInput input) { - if (!(node.getBlock() instanceof ExceptionBlock)) { - // This can happen in some weird (buggy?) cases: - // see https://github.com/typetools/checker-framework/issues/3585 - return Collections.emptyMap(); - } - ExceptionBlock block = (ExceptionBlock) node.getBlock(); - Map result = new LinkedHashMap<>(); - block.getExceptionalSuccessors() - .forEach((tm, b) -> result.put(tm, input.getRegularStore().copy())); - return result; + /** + * Create a set of stores for the exceptional paths out of the block containing {@code node}. This + * allows propagation, along those paths, of the fact that the method being invoked in {@code + * node} was definitely called. + * + * @param node a method invocation + * @param input the transfer input associated with the method invocation + * @return a map from types to stores. The keys are the same keys used by {@link + * ExceptionBlock#getExceptionalSuccessors()}. The values are copies of the regular store from + * {@code input}. + */ + private Map makeExceptionalStores( + MethodInvocationNode node, TransferInput input) { + if (!(node.getBlock() instanceof ExceptionBlock)) { + // This can happen in some weird (buggy?) cases: + // see https://github.com/typetools/checker-framework/issues/3585 + return Collections.emptyMap(); } + ExceptionBlock block = (ExceptionBlock) node.getBlock(); + Map result = new LinkedHashMap<>(); + block + .getExceptionalSuccessors() + .forEach((tm, b) -> result.put(tm, input.getRegularStore().copy())); + return result; + } - /** - * Update the types of varargs parameters passed to a method with an {@link - * EnsuresCalledMethodsVarArgs} annotation. This method is a no-op if no such annotation is - * present. - * - * @param node the method invocation node - * @param elt the method being invoked - * @param result the current result - */ - private void handleEnsuresCalledMethodsVarArgs( - MethodInvocationNode node, - ExecutableElement elt, - TransferResult result) { - AnnotationMirror annot = - atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); - if (annot == null) { - return; + /** + * Update the types of varargs parameters passed to a method with an {@link + * EnsuresCalledMethodsVarArgs} annotation. This method is a no-op if no such annotation is + * present. + * + * @param node the method invocation node + * @param elt the method being invoked + * @param result the current result + */ + private void handleEnsuresCalledMethodsVarArgs( + MethodInvocationNode node, + ExecutableElement elt, + TransferResult result) { + AnnotationMirror annot = atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); + if (annot == null) { + return; + } + List ensuredMethodNames = + AnnotationUtils.getElementValueArray( + annot, + ((CalledMethodsAnnotatedTypeFactory) atypeFactory) + .ensuresCalledMethodsVarArgsValueElement, + String.class); + List parameters = elt.getParameters(); + int varArgsPos = parameters.size() - 1; + Node varArgActual = node.getArguments().get(varArgsPos); + // In the CFG, explicit passing of multiple arguments in the varargs position is represented + // via an ArrayCreationNode. This is the only case we handle for now. + if (varArgActual instanceof ArrayCreationNode) { + ArrayCreationNode arrayCreationNode = (ArrayCreationNode) varArgActual; + // add in the called method to all the vararg arguments + AccumulationStore thenStore = result.getThenStore(); + AccumulationStore elseStore = result.getElseStore(); + for (Node arg : arrayCreationNode.getInitializers()) { + AnnotatedTypeMirror currentType = atypeFactory.getAnnotatedType(arg.getTree()); + AnnotationMirror newType = getUpdatedCalledMethodsType(currentType, ensuredMethodNames); + if (newType == null) { + continue; } - List ensuredMethodNames = - AnnotationUtils.getElementValueArray( - annot, - ((CalledMethodsAnnotatedTypeFactory) atypeFactory) - .ensuresCalledMethodsVarArgsValueElement, - String.class); - List parameters = elt.getParameters(); - int varArgsPos = parameters.size() - 1; - Node varArgActual = node.getArguments().get(varArgsPos); - // In the CFG, explicit passing of multiple arguments in the varargs position is represented - // via an ArrayCreationNode. This is the only case we handle for now. - if (varArgActual instanceof ArrayCreationNode) { - ArrayCreationNode arrayCreationNode = (ArrayCreationNode) varArgActual; - // add in the called method to all the vararg arguments - AccumulationStore thenStore = result.getThenStore(); - AccumulationStore elseStore = result.getElseStore(); - for (Node arg : arrayCreationNode.getInitializers()) { - AnnotatedTypeMirror currentType = atypeFactory.getAnnotatedType(arg.getTree()); - AnnotationMirror newType = - getUpdatedCalledMethodsType(currentType, ensuredMethodNames); - if (newType == null) { - continue; - } - JavaExpression receiverReceiver = JavaExpression.fromNode(arg); - thenStore.insertValue(receiverReceiver, newType); - elseStore.insertValue(receiverReceiver, newType); - } - } + JavaExpression receiverReceiver = JavaExpression.fromNode(arg); + thenStore.insertValue(receiverReceiver, newType); + elseStore.insertValue(receiverReceiver, newType); + } } + } - /** - * Update the given exceptionalStores for the {@link - * org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} annotations - * written on the given method. - * - * @param node a method invocation - * @param method the method being invoked - * @param exceptionalStores the stores to update - */ - private void handleEnsuresCalledMethodsOnException( - MethodInvocationNode node, - ExecutableElement method, - Map exceptionalStores) { - Types types = atypeFactory.getProcessingEnv().getTypeUtils(); - for (EnsuresCalledMethodOnExceptionContract postcond : - ((CalledMethodsAnnotatedTypeFactory) atypeFactory) - .getExceptionalPostconditions(method)) { - JavaExpression e; - try { - e = - StringToJavaExpression.atMethodInvocation( - postcond.getExpression(), - node.getTree(), - atypeFactory.getChecker()); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - // This parse error will be reported later. For now, we'll skip this malformed - // postcondition and move on to the others. - continue; - } + /** + * Update the given exceptionalStores for the {@link + * org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} annotations + * written on the given method. + * + * @param node a method invocation + * @param method the method being invoked + * @param exceptionalStores the stores to update + */ + private void handleEnsuresCalledMethodsOnException( + MethodInvocationNode node, + ExecutableElement method, + Map exceptionalStores) { + Types types = atypeFactory.getProcessingEnv().getTypeUtils(); + for (EnsuresCalledMethodOnExceptionContract postcond : + ((CalledMethodsAnnotatedTypeFactory) atypeFactory).getExceptionalPostconditions(method)) { + JavaExpression e; + try { + e = + StringToJavaExpression.atMethodInvocation( + postcond.getExpression(), node.getTree(), atypeFactory.getChecker()); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + // This parse error will be reported later. For now, we'll skip this malformed + // postcondition and move on to the others. + continue; + } - // NOTE: this code is a little inefficient; it creates a single-method annotation and - // calls `insertOrRefine` in a loop. Even worse, this code appears within a loop. For - // now we aren't too worried about it, since the number of - // EnsuresCalledMethodsOnException annotations should be small. - AnnotationMirror calledMethod = - atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); - for (Map.Entry successor : - exceptionalStores.entrySet()) { - TypeMirror caughtException = successor.getKey(); - if (types.isSubtype(caughtException, javaLangExceptionType)) { - AccumulationStore resultStore = successor.getValue(); - resultStore.insertOrRefine(e, calledMethod); - } - } + // NOTE: this code is a little inefficient; it creates a single-method annotation and + // calls `insertOrRefine` in a loop. Even worse, this code appears within a loop. For + // now we aren't too worried about it, since the number of + // EnsuresCalledMethodsOnException annotations should be small. + AnnotationMirror calledMethod = + atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); + for (Map.Entry successor : exceptionalStores.entrySet()) { + TypeMirror caughtException = successor.getKey(); + if (types.isSubtype(caughtException, javaLangExceptionType)) { + AccumulationStore resultStore = successor.getValue(); + resultStore.insertOrRefine(e, calledMethod); } + } } + } - /** - * Extract the current called-methods type from {@code currentType}, and then add each element - * of {@code methodNames} to it, and return the result. This method is similar to GLB, but - * should be used when the new methods come from a source other than an {@code CalledMethods} - * annotation. - * - * @param currentType the current type in the called-methods hierarchy - * @param methodNames the names of the new methods to add to the type - * @return the new annotation to be added to the type, or null if the current type cannot be - * converted to an accumulator annotation - */ - private @Nullable AnnotationMirror getUpdatedCalledMethodsType( - AnnotatedTypeMirror currentType, List methodNames) { - AnnotationMirror type; - if (currentType == null || !currentType.hasAnnotationInHierarchy(atypeFactory.top)) { - type = atypeFactory.top; - } else { - type = currentType.getAnnotationInHierarchy(atypeFactory.top); - } + /** + * Extract the current called-methods type from {@code currentType}, and then add each element of + * {@code methodNames} to it, and return the result. This method is similar to GLB, but should be + * used when the new methods come from a source other than an {@code CalledMethods} annotation. + * + * @param currentType the current type in the called-methods hierarchy + * @param methodNames the names of the new methods to add to the type + * @return the new annotation to be added to the type, or null if the current type cannot be + * converted to an accumulator annotation + */ + private @Nullable AnnotationMirror getUpdatedCalledMethodsType( + AnnotatedTypeMirror currentType, List methodNames) { + AnnotationMirror type; + if (currentType == null || !currentType.hasAnnotationInHierarchy(atypeFactory.top)) { + type = atypeFactory.top; + } else { + type = currentType.getAnnotationInHierarchy(atypeFactory.top); + } - // Don't attempt to strengthen @CalledMethodsPredicate annotations, because that would - // require reasoning about the predicate itself. Instead, start over from top. - if (AnnotationUtils.areSameByName( - type, "org.checkerframework.checker.calledmethods.qual.CalledMethodsPredicate")) { - type = atypeFactory.top; - } + // Don't attempt to strengthen @CalledMethodsPredicate annotations, because that would + // require reasoning about the predicate itself. Instead, start over from top. + if (AnnotationUtils.areSameByName( + type, "org.checkerframework.checker.calledmethods.qual.CalledMethodsPredicate")) { + type = atypeFactory.top; + } - if (AnnotationUtils.areSame(type, atypeFactory.bottom)) { - return null; - } + if (AnnotationUtils.areSame(type, atypeFactory.bottom)) { + return null; + } - List currentMethods = - AnnotationUtils.getElementValueArray(type, calledMethodsValueElement, String.class); - List newList = CollectionsPlume.concatenate(currentMethods, methodNames); + List currentMethods = + AnnotationUtils.getElementValueArray(type, calledMethodsValueElement, String.class); + List newList = CollectionsPlume.concatenate(currentMethods, methodNames); - return atypeFactory.createAccumulatorAnnotation(newList); - } + return atypeFactory.createAccumulatorAnnotation(newList); + } - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; - } - */ + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; + } + */ } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java index 2a7b83ac74e..5efb711bc64 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java @@ -3,7 +3,14 @@ import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; - +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.tools.Diagnostic; import org.checkerframework.checker.calledmethods.builder.BuilderFrameworkSupport; import org.checkerframework.checker.calledmethods.qual.CalledMethods; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs; @@ -20,148 +27,131 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.tools.Diagnostic; - /** * This visitor implements the custom error message "finalizer.invocation.invalid". It also supports * counting the number of framework build calls. */ public class CalledMethodsVisitor extends AccumulationVisitor { - /** - * Creates a new CalledMethodsVisitor. - * - * @param checker the type-checker associated with this visitor - */ - public CalledMethodsVisitor(BaseTypeChecker checker) { - super(checker); + /** + * Creates a new CalledMethodsVisitor. + * + * @param checker the type-checker associated with this visitor + */ + public CalledMethodsVisitor(BaseTypeChecker checker) { + super(checker); + } + + /** + * Issue an error at every EnsuresCalledMethodsVarArgs annotation, because using it is unsound. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + if (AnnotationUtils.areSameByName( + anno, "org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs")) { + // We can't verify these yet. Emit an error (which will have to be suppressed) for now. + checker.report(tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.unverified")); } - - /** - * Issue an error at every EnsuresCalledMethodsVarArgs annotation, because using it is unsound. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - if (AnnotationUtils.areSameByName( - anno, - "org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs")) { - // We can't verify these yet. Emit an error (which will have to be suppressed) for now. - checker.report( - tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.unverified")); - } - return super.visitAnnotation(tree, p); + return super.visitAnnotation(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); + AnnotationMirror ecmva = atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); + if (ecmva != null) { + if (!elt.isVarArgs()) { + checker.report(tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.invalid")); + } } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); - AnnotationMirror ecmva = - atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); - if (ecmva != null) { - if (!elt.isVarArgs()) { - checker.report( - tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.invalid")); - } - } - for (EnsuresCalledMethodOnExceptionContract postcond : - ((CalledMethodsAnnotatedTypeFactory) atypeFactory) - .getExceptionalPostconditions(elt)) { - checkExceptionalPostcondition(postcond, tree); - } - return super.visitMethod(tree, p); + for (EnsuresCalledMethodOnExceptionContract postcond : + ((CalledMethodsAnnotatedTypeFactory) atypeFactory).getExceptionalPostconditions(elt)) { + checkExceptionalPostcondition(postcond, tree); + } + return super.visitMethod(tree, p); + } + + /** + * Check if the given postcondition is really ensured by the body of the given method. + * + * @param postcond the postcondition to check + * @param tree the method + */ + protected void checkExceptionalPostcondition( + EnsuresCalledMethodOnExceptionContract postcond, MethodTree tree) { + CFAbstractStore exitStore = atypeFactory.getExceptionalExitStore(tree); + if (exitStore == null) { + // If there is no exceptional exitStore, then the method cannot throw exceptions and + // there is no need to check anything. + return; } - /** - * Check if the given postcondition is really ensured by the body of the given method. - * - * @param postcond the postcondition to check - * @param tree the method - */ - protected void checkExceptionalPostcondition( - EnsuresCalledMethodOnExceptionContract postcond, MethodTree tree) { - CFAbstractStore exitStore = atypeFactory.getExceptionalExitStore(tree); - if (exitStore == null) { - // If there is no exceptional exitStore, then the method cannot throw exceptions and - // there is no need to check anything. - return; - } - - JavaExpression e; - try { - e = StringToJavaExpression.atMethodBody(postcond.getExpression(), tree, checker); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(tree, ex.getDiagMessage()); - return; - } - - AnnotationMirror requiredAnno = - atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); + JavaExpression e; + try { + e = StringToJavaExpression.atMethodBody(postcond.getExpression(), tree, checker); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(tree, ex.getDiagMessage()); + return; + } - CFAbstractValue value = exitStore.getValue(e); - AnnotationMirror inferredAnno = null; - if (value != null) { - AnnotationMirrorSet annos = value.getAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, requiredAnno); - } + AnnotationMirror requiredAnno = atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); - if (!checkContract(e, requiredAnno, inferredAnno, exitStore)) { - checker.reportError( - tree, - "contracts.exceptional.postcondition.not.satisfied", - tree.getName(), - contractExpressionAndType(postcond.getExpression(), inferredAnno), - contractExpressionAndType(postcond.getExpression(), requiredAnno)); - } + CFAbstractValue value = exitStore.getValue(e); + AnnotationMirror inferredAnno = null; + if (value != null) { + AnnotationMirrorSet annos = value.getAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, requiredAnno); } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (checker.getBooleanOption(CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS)) { - ExecutableElement element = TreeUtils.elementFromUse(tree); - for (BuilderFrameworkSupport builderFrameworkSupport : - ((CalledMethodsAnnotatedTypeFactory) getTypeFactory()) - .getBuilderFrameworkSupports()) { - if (builderFrameworkSupport.isBuilderBuildMethod(element)) { - ((CalledMethodsChecker) checker).numBuildCalls++; - break; - } - } + if (!checkContract(e, requiredAnno, inferredAnno, exitStore)) { + checker.reportError( + tree, + "contracts.exceptional.postcondition.not.satisfied", + tree.getName(), + contractExpressionAndType(postcond.getExpression(), inferredAnno), + contractExpressionAndType(postcond.getExpression(), requiredAnno)); + } + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (checker.getBooleanOption(CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS)) { + ExecutableElement element = TreeUtils.elementFromUse(tree); + for (BuilderFrameworkSupport builderFrameworkSupport : + ((CalledMethodsAnnotatedTypeFactory) getTypeFactory()).getBuilderFrameworkSupports()) { + if (builderFrameworkSupport.isBuilderBuildMethod(element)) { + ((CalledMethodsChecker) checker).numBuildCalls++; + break; } - return super.visitMethodInvocation(tree, p); + } } - - /** Turns some "method.invocation.invalid" errors into "finalizer.invocation.invalid" errors. */ - @Override - protected void reportMethodInvocabilityError( - MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { - - AnnotationMirror expectedCM = expected.getAnnotation(CalledMethods.class); - if (expectedCM != null) { - AnnotationMirror foundCM = found.getAnnotation(CalledMethods.class); - Set foundMethods = - foundCM == null - ? Collections.emptySet() - : new HashSet<>(atypeFactory.getAccumulatedValues(foundCM)); - List expectedMethods = atypeFactory.getAccumulatedValues(expectedCM); - StringJoiner missingMethods = new StringJoiner(" "); - for (String expectedMethod : expectedMethods) { - if (!foundMethods.contains(expectedMethod)) { - missingMethods.add(expectedMethod + "()"); - } - } - - checker.reportError(tree, "finalizer.invocation.invalid", missingMethods.toString()); - } else { - super.reportMethodInvocabilityError(tree, found, expected); + return super.visitMethodInvocation(tree, p); + } + + /** Turns some "method.invocation.invalid" errors into "finalizer.invocation.invalid" errors. */ + @Override + protected void reportMethodInvocabilityError( + MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { + + AnnotationMirror expectedCM = expected.getAnnotation(CalledMethods.class); + if (expectedCM != null) { + AnnotationMirror foundCM = found.getAnnotation(CalledMethods.class); + Set foundMethods = + foundCM == null + ? Collections.emptySet() + : new HashSet<>(atypeFactory.getAccumulatedValues(foundCM)); + List expectedMethods = atypeFactory.getAccumulatedValues(expectedCM); + StringJoiner missingMethods = new StringJoiner(" "); + for (String expectedMethod : expectedMethods) { + if (!foundMethods.contains(expectedMethod)) { + missingMethods.add(expectedMethod + "()"); } + } + + checker.reportError(tree, "finalizer.invocation.invalid", missingMethods.toString()); + } else { + super.reportMethodInvocabilityError(tree, found, expected); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java index ab3605ea631..bdc6fd10601 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java @@ -13,54 +13,53 @@ // TODO: In the future, this class should be a record. public class EnsuresCalledMethodOnExceptionContract { - /** The expression described by this postcondition. */ - private final String expression; + /** The expression described by this postcondition. */ + private final String expression; - /** The method this postcondition promises to call. */ - private final String method; + /** The method this postcondition promises to call. */ + private final String method; - /** - * Create a new EnsuredCalledMethodOnException. Usually this should be constructed - * from a {@link - * org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} appearing in - * the source code. - * - * @param expression the expression described by this postcondition - * @param method the method this postcondition promises to call - */ - public EnsuresCalledMethodOnExceptionContract(String expression, String method) { - this.expression = expression; - this.method = method; - } + /** + * Create a new EnsuredCalledMethodOnException. Usually this should be constructed + * from a {@link org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} + * appearing in the source code. + * + * @param expression the expression described by this postcondition + * @param method the method this postcondition promises to call + */ + public EnsuresCalledMethodOnExceptionContract(String expression, String method) { + this.expression = expression; + this.method = method; + } - /** - * The expression described by this postcondition. - * - * @return the expression described by this postcondition - */ - public String getExpression() { - return expression; - } + /** + * The expression described by this postcondition. + * + * @return the expression described by this postcondition + */ + public String getExpression() { + return expression; + } - /** - * The method this postcondition promises to call. - * - * @return the method this postcondition promises to call - */ - public String getMethod() { - return method; - } + /** + * The method this postcondition promises to call. + * + * @return the method this postcondition promises to call + */ + public String getMethod() { + return method; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof EnsuresCalledMethodOnExceptionContract)) return false; - EnsuresCalledMethodOnExceptionContract that = (EnsuresCalledMethodOnExceptionContract) o; - return expression.equals(that.expression) && method.equals(that.method); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EnsuresCalledMethodOnExceptionContract)) return false; + EnsuresCalledMethodOnExceptionContract that = (EnsuresCalledMethodOnExceptionContract) o; + return expression.equals(that.expression) && method.equals(that.method); + } - @Override - public int hashCode() { - return Objects.hash(expression, method); - } + @Override + public int hashCode() { + return Objects.hash(expression, method); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java index f7c52f5f30d..cf5ebb7f4c0 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java @@ -1,20 +1,6 @@ package org.checkerframework.checker.calledmethods.builder; import com.sun.source.tree.NewClassTree; - -import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; -import org.checkerframework.checker.calledmethods.qual.CalledMethods; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.checkerframework.javacutil.UserError; -import org.plumelib.util.ArraysPlume; - import java.beans.Introspector; import java.util.ArrayList; import java.util.Arrays; @@ -24,7 +10,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -34,6 +19,18 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; +import org.checkerframework.checker.calledmethods.qual.CalledMethods; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.checkerframework.javacutil.UserError; +import org.plumelib.util.ArraysPlume; /** * AutoValue support for the Called Methods Checker. This class adds {@code @}{@link CalledMethods} @@ -41,411 +38,399 @@ */ public class AutoValueSupport implements BuilderFrameworkSupport { - /** The type factory. */ - private final CalledMethodsAnnotatedTypeFactory atypeFactory; - - /** - * Create a new AutoValueSupport. - * - * @param atypeFactory the typechecker's type factory - */ - public AutoValueSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** The type factory. */ + private final CalledMethodsAnnotatedTypeFactory atypeFactory; - /** - * This method modifies the type of a copy constructor generated by AutoValue to match the type - * of the AutoValue toBuilder method, and has no effect if {@code tree} is a call to any other - * constructor. - * - * @param tree an AST for a constructor call - * @param type type of the call expression - */ - @Override - public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { - ExecutableElement element = TreeUtils.elementFromUse(tree); - TypeMirror superclass = ((TypeElement) element.getEnclosingElement()).getSuperclass(); + /** + * Create a new AutoValueSupport. + * + * @param atypeFactory the typechecker's type factory + */ + public AutoValueSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - if (superclass.getKind() != TypeKind.NONE - && ElementUtils.hasAnnotation( - TypesUtils.getTypeElement(superclass), - getAutoValuePackageName() + ".AutoValue.Builder") - && element.getParameters().size() > 0) { - handleToBuilderType( - type, - superclass, - (TypeElement) TypesUtils.getTypeElement(superclass).getEnclosingElement()); - } - } + /** + * This method modifies the type of a copy constructor generated by AutoValue to match the type of + * the AutoValue toBuilder method, and has no effect if {@code tree} is a call to any other + * constructor. + * + * @param tree an AST for a constructor call + * @param type type of the call expression + */ + @Override + public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { + ExecutableElement element = TreeUtils.elementFromUse(tree); + TypeMirror superclass = ((TypeElement) element.getEnclosingElement()).getSuperclass(); - @Override - public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { - TypeElement builderElement = (TypeElement) candidateBuildElement.getEnclosingElement(); - if (ElementUtils.hasAnnotation( - builderElement, getAutoValuePackageName() + ".AutoValue.Builder")) { - Element classContainingBuilderElement = builderElement.getEnclosingElement(); - if (!ElementUtils.hasAnnotation( - classContainingBuilderElement, getAutoValuePackageName() + ".AutoValue")) { - throw new UserError( - "class " - + classContainingBuilderElement.getSimpleName() - + " is missing @AutoValue annotation"); - } - // it is a build method if it returns the type with the @AutoValue annotation - if (TypesUtils.getTypeElement(candidateBuildElement.getReturnType()) - .equals(classContainingBuilderElement)) { - return true; - } - } - return false; + if (superclass.getKind() != TypeKind.NONE + && ElementUtils.hasAnnotation( + TypesUtils.getTypeElement(superclass), getAutoValuePackageName() + ".AutoValue.Builder") + && element.getParameters().size() > 0) { + handleToBuilderType( + type, + superclass, + (TypeElement) TypesUtils.getTypeElement(superclass).getEnclosingElement()); } + } - @Override - public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { - - ExecutableElement element = builderBuildType.getElement(); - TypeElement builderElement = (TypeElement) element.getEnclosingElement(); - TypeElement autoValueClassElement = (TypeElement) builderElement.getEnclosingElement(); - AnnotationMirror newCalledMethodsAnno = - createCalledMethodsForAutoValueClass(builderElement, autoValueClassElement); - // Only add the new @CalledMethods annotation if there is not already a @CalledMethods - // annotation present. - builderBuildType.getReceiverType().addMissingAnnotation(newCalledMethodsAnno); + @Override + public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { + TypeElement builderElement = (TypeElement) candidateBuildElement.getEnclosingElement(); + if (ElementUtils.hasAnnotation( + builderElement, getAutoValuePackageName() + ".AutoValue.Builder")) { + Element classContainingBuilderElement = builderElement.getEnclosingElement(); + if (!ElementUtils.hasAnnotation( + classContainingBuilderElement, getAutoValuePackageName() + ".AutoValue")) { + throw new UserError( + "class " + + classContainingBuilderElement.getSimpleName() + + " is missing @AutoValue annotation"); + } + // it is a build method if it returns the type with the @AutoValue annotation + if (TypesUtils.getTypeElement(candidateBuildElement.getReturnType()) + .equals(classContainingBuilderElement)) { + return true; + } } + return false; + } - @Override - public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { - if (!"toBuilder".equals(candidateToBuilderElement.getSimpleName().toString())) { - return false; - } + @Override + public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { - TypeElement candidateClassContainingToBuilder = - (TypeElement) candidateToBuilderElement.getEnclosingElement(); - boolean isAbstractAV = - isAutoValueGenerated(candidateClassContainingToBuilder) - && candidateToBuilderElement.getModifiers().contains(Modifier.ABSTRACT); - TypeMirror superclassOfClassContainingToBuilder = - candidateClassContainingToBuilder.getSuperclass(); - boolean superIsAV = false; - if (superclassOfClassContainingToBuilder.getKind() != TypeKind.NONE) { - superIsAV = - isAutoValueGenerated( - TypesUtils.getTypeElement(superclassOfClassContainingToBuilder)); - } - return superIsAV || isAbstractAV; - } + ExecutableElement element = builderBuildType.getElement(); + TypeElement builderElement = (TypeElement) element.getEnclosingElement(); + TypeElement autoValueClassElement = (TypeElement) builderElement.getEnclosingElement(); + AnnotationMirror newCalledMethodsAnno = + createCalledMethodsForAutoValueClass(builderElement, autoValueClassElement); + // Only add the new @CalledMethods annotation if there is not already a @CalledMethods + // annotation present. + builderBuildType.getReceiverType().addMissingAnnotation(newCalledMethodsAnno); + } - @Override - public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { - AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); - ExecutableElement toBuilderElement = toBuilderType.getElement(); - TypeElement classContainingToBuilder = (TypeElement) toBuilderElement.getEnclosingElement(); - // Because of the way that the check in #isToBuilderMethod works, if the code reaches this - // point and this condition is false, the other condition MUST be true (otherwise, - // isToBuilderMethod would have returned false). - if (isAutoValueGenerated(classContainingToBuilder) - && toBuilderElement.getModifiers().contains(Modifier.ABSTRACT)) { - handleToBuilderType( - returnType, returnType.getUnderlyingType(), classContainingToBuilder); - } else { - TypeElement superElement = - TypesUtils.getTypeElement(classContainingToBuilder.getSuperclass()); - handleToBuilderType(returnType, returnType.getUnderlyingType(), superElement); - } + @Override + public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { + if (!"toBuilder".equals(candidateToBuilderElement.getSimpleName().toString())) { + return false; } - /** - * Was the given element generated by AutoValue? - * - * @param element the element to check - * @return true if the element was generated by AutoValue - */ - private boolean isAutoValueGenerated(Element element) { - return ElementUtils.hasAnnotation(element, getAutoValuePackageName() + ".AutoValue"); + TypeElement candidateClassContainingToBuilder = + (TypeElement) candidateToBuilderElement.getEnclosingElement(); + boolean isAbstractAV = + isAutoValueGenerated(candidateClassContainingToBuilder) + && candidateToBuilderElement.getModifiers().contains(Modifier.ABSTRACT); + TypeMirror superclassOfClassContainingToBuilder = + candidateClassContainingToBuilder.getSuperclass(); + boolean superIsAV = false; + if (superclassOfClassContainingToBuilder.getKind() != TypeKind.NONE) { + superIsAV = + isAutoValueGenerated(TypesUtils.getTypeElement(superclassOfClassContainingToBuilder)); } + return superIsAV || isAbstractAV; + } - /** - * Add, to {@code type}, a CalledMethods annotation with all required methods called. The type - * can be the return type of toBuilder or of the corresponding generated "copy" constructor. - * - * @param type type to update - * @param builderType type of abstract @AutoValue.Builder class - * @param classElement an AutoValue class corresponding to {@code type} - */ - private void handleToBuilderType( - AnnotatedTypeMirror type, TypeMirror builderType, TypeElement classElement) { - TypeElement builderElement = TypesUtils.getTypeElement(builderType); - AnnotationMirror calledMethodsAnno = - createCalledMethodsForAutoValueClass(builderElement, classElement); - type.replaceAnnotation(calledMethodsAnno); + @Override + public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { + AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); + ExecutableElement toBuilderElement = toBuilderType.getElement(); + TypeElement classContainingToBuilder = (TypeElement) toBuilderElement.getEnclosingElement(); + // Because of the way that the check in #isToBuilderMethod works, if the code reaches this + // point and this condition is false, the other condition MUST be true (otherwise, + // isToBuilderMethod would have returned false). + if (isAutoValueGenerated(classContainingToBuilder) + && toBuilderElement.getModifiers().contains(Modifier.ABSTRACT)) { + handleToBuilderType(returnType, returnType.getUnderlyingType(), classContainingToBuilder); + } else { + TypeElement superElement = + TypesUtils.getTypeElement(classContainingToBuilder.getSuperclass()); + handleToBuilderType(returnType, returnType.getUnderlyingType(), superElement); } + } - /** - * Create an @CalledMethods annotation for the given AutoValue class and builder. The returned - * annotation contains all the required setters. - * - * @param builderElement the element for the Builder class - * @param classElement the element for the AutoValue class (i.e. the class that is built by the - * builder) - * @return an @CalledMethods annotation representing that all the required setters have been - * called - */ - private AnnotationMirror createCalledMethodsForAutoValueClass( - TypeElement builderElement, TypeElement classElement) { - Set avBuilderSetterNames = getAutoValueBuilderSetterMethodNames(builderElement); - List requiredProperties = - getAutoValueRequiredProperties(classElement, avBuilderSetterNames); - return createCalledMethodsForAutoValueProperties(requiredProperties, avBuilderSetterNames); - } + /** + * Was the given element generated by AutoValue? + * + * @param element the element to check + * @return true if the element was generated by AutoValue + */ + private boolean isAutoValueGenerated(Element element) { + return ElementUtils.hasAnnotation(element, getAutoValuePackageName() + ".AutoValue"); + } - /** - * Creates a @CalledMethods annotation for the given property names, converting the names to the - * corresponding setter method name in the Builder. - * - * @param propertyNames the property names - * @param avBuilderSetterNames names of all setters in the builder class - * @return a @CalledMethods annotation that indicates all the given properties have been set - */ - private AnnotationMirror createCalledMethodsForAutoValueProperties( - List propertyNames, Set avBuilderSetterNames) { - List calledMethodNames = - propertyNames.stream() - .map(prop -> autoValuePropToBuilderSetterName(prop, avBuilderSetterNames)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - return atypeFactory.createAccumulatorAnnotation(calledMethodNames); - } + /** + * Add, to {@code type}, a CalledMethods annotation with all required methods called. The type can + * be the return type of toBuilder or of the corresponding generated "copy" constructor. + * + * @param type type to update + * @param builderType type of abstract @AutoValue.Builder class + * @param classElement an AutoValue class corresponding to {@code type} + */ + private void handleToBuilderType( + AnnotatedTypeMirror type, TypeMirror builderType, TypeElement classElement) { + TypeElement builderElement = TypesUtils.getTypeElement(builderType); + AnnotationMirror calledMethodsAnno = + createCalledMethodsForAutoValueClass(builderElement, classElement); + type.replaceAnnotation(calledMethodsAnno); + } - /** - * Converts the name of a property (i.e., a field) into the name of its setter. - * - * @param prop the property (i.e., field) name - * @param builderSetterNames names of all methods in the builder class - * @return the name of the setter for prop, or null if it cannot be found - */ - private static @Nullable String autoValuePropToBuilderSetterName( - String prop, Set builderSetterNames) { - String[] possiblePropNames; - if (prop.startsWith("get") && prop.length() > 3 && Character.isUpperCase(prop.charAt(3))) { - possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(3))}; - } else if (prop.startsWith("is") - && prop.length() > 2 - && Character.isUpperCase(prop.charAt(2))) { - possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(2))}; - } else { - possiblePropNames = new String[] {prop}; - } + /** + * Create an @CalledMethods annotation for the given AutoValue class and builder. The returned + * annotation contains all the required setters. + * + * @param builderElement the element for the Builder class + * @param classElement the element for the AutoValue class (i.e. the class that is built by the + * builder) + * @return an @CalledMethods annotation representing that all the required setters have been + * called + */ + private AnnotationMirror createCalledMethodsForAutoValueClass( + TypeElement builderElement, TypeElement classElement) { + Set avBuilderSetterNames = getAutoValueBuilderSetterMethodNames(builderElement); + List requiredProperties = + getAutoValueRequiredProperties(classElement, avBuilderSetterNames); + return createCalledMethodsForAutoValueProperties(requiredProperties, avBuilderSetterNames); + } - for (String propName : possiblePropNames) { - // The setter may be the property name itself, or prefixed by 'set'. - if (builderSetterNames.contains(propName)) { - return propName; - } - String setterName = "set" + BuilderFrameworkSupportUtils.capitalize(propName); - if (builderSetterNames.contains(setterName)) { - return setterName; - } - } + /** + * Creates a @CalledMethods annotation for the given property names, converting the names to the + * corresponding setter method name in the Builder. + * + * @param propertyNames the property names + * @param avBuilderSetterNames names of all setters in the builder class + * @return a @CalledMethods annotation that indicates all the given properties have been set + */ + private AnnotationMirror createCalledMethodsForAutoValueProperties( + List propertyNames, Set avBuilderSetterNames) { + List calledMethodNames = + propertyNames.stream() + .map(prop -> autoValuePropToBuilderSetterName(prop, avBuilderSetterNames)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return atypeFactory.createAccumulatorAnnotation(calledMethodNames); + } - // Could not find a corresponding setter. This is likely because an AutoValue Extension is - // in use. See https://github.com/kelloggm/object-construction-checker/issues/110 . - // For now we return null, but once that bug is fixed, this should be changed to an - // assertion failure. - return null; + /** + * Converts the name of a property (i.e., a field) into the name of its setter. + * + * @param prop the property (i.e., field) name + * @param builderSetterNames names of all methods in the builder class + * @return the name of the setter for prop, or null if it cannot be found + */ + private static @Nullable String autoValuePropToBuilderSetterName( + String prop, Set builderSetterNames) { + String[] possiblePropNames; + if (prop.startsWith("get") && prop.length() > 3 && Character.isUpperCase(prop.charAt(3))) { + possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(3))}; + } else if (prop.startsWith("is") + && prop.length() > 2 + && Character.isUpperCase(prop.charAt(2))) { + possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(2))}; + } else { + possiblePropNames = new String[] {prop}; } - /** - * Computes the required properties of an @AutoValue class. - * - * @param autoValueClassElement the @AutoValue class - * @param avBuilderSetterNames names of all setters in the corresponding AutoValue builder class - * @return a list of required property names - */ - private List getAutoValueRequiredProperties( - TypeElement autoValueClassElement, Set avBuilderSetterNames) { - return getAllAbstractMethods(autoValueClassElement).stream() - .filter(member -> isAutoValueRequiredProperty(member, avBuilderSetterNames)) - .map(e -> e.getSimpleName().toString()) - .collect(Collectors.toList()); + for (String propName : possiblePropNames) { + // The setter may be the property name itself, or prefixed by 'set'. + if (builderSetterNames.contains(propName)) { + return propName; + } + String setterName = "set" + BuilderFrameworkSupportUtils.capitalize(propName); + if (builderSetterNames.contains(setterName)) { + return setterName; + } } - /** Method names for {@link #isAutoValueRequiredProperty} to ignore. */ - private final Set isAutoValueRequiredPropertyIgnored = - new HashSet<>(Arrays.asList("equals", "hashCode", "toString", "", "toBuilder")); + // Could not find a corresponding setter. This is likely because an AutoValue Extension is + // in use. See https://github.com/kelloggm/object-construction-checker/issues/110 . + // For now we return null, but once that bug is fixed, this should be changed to an + // assertion failure. + return null; + } - /** - * Does member represent a required property of an AutoValue class? - * - * @param member a member of an AutoValue class or superclass - * @param avBuilderSetterNames names of all setters in corresponding AutoValue builder class - * @return true if {@code member} is required - */ - private boolean isAutoValueRequiredProperty( - ExecutableElement member, Set avBuilderSetterNames) { - String name = member.getSimpleName().toString(); - // Ignore java.lang.Object overrides, constructors, and toBuilder methods in AutoValue - // classes. - // Strictly speaking, this code should check return types, etc. to handle strange - // overloads and other corner cases. They seem unlikely enough that we are skipping for now. - if (isAutoValueRequiredPropertyIgnored.contains(name)) { - return false; - } - TypeMirror returnType = member.getReturnType(); - if (returnType.getKind() == TypeKind.VOID) { - return false; - } - // shouldn't have a nullable return - boolean hasNullable = - Stream.concat( - atypeFactory - .getElementUtils() - .getAllAnnotationMirrors(member) - .stream(), - returnType.getAnnotationMirrors().stream()) - .anyMatch(anm -> AnnotationUtils.annotationName(anm).endsWith(".Nullable")); - if (hasNullable) { - return false; - } - // if return type of foo() is a Guava Immutable type, not required if there is a - // builder method fooBuilder() - if (BuilderFrameworkSupportUtils.isGuavaImmutableType(returnType) - && avBuilderSetterNames.contains(name + "Builder")) { - return false; - } - // if it's an Optional, the Builder will automatically initialize it - if (isOptional(returnType)) { - return false; - } - // it's required! - return true; - } + /** + * Computes the required properties of an @AutoValue class. + * + * @param autoValueClassElement the @AutoValue class + * @param avBuilderSetterNames names of all setters in the corresponding AutoValue builder class + * @return a list of required property names + */ + private List getAutoValueRequiredProperties( + TypeElement autoValueClassElement, Set avBuilderSetterNames) { + return getAllAbstractMethods(autoValueClassElement).stream() + .filter(member -> isAutoValueRequiredProperty(member, avBuilderSetterNames)) + .map(e -> e.getSimpleName().toString()) + .collect(Collectors.toList()); + } - /** - * Classes that AutoValue considers "optional". This list comes from AutoValue's source code. - */ - private static final String[] optionalClassNames = - new String[] { - // Use concatenation to avoid ShadowJar relocate - // "com.google.common.base.Optional", - "com.go".toString() + "ogle.common.base.Optional", - "java.util.Optional", - "java.util.OptionalDouble", - "java.util.OptionalInt", - "java.util.OptionalLong" - }; + /** Method names for {@link #isAutoValueRequiredProperty} to ignore. */ + private final Set isAutoValueRequiredPropertyIgnored = + new HashSet<>(Arrays.asList("equals", "hashCode", "toString", "", "toBuilder")); - /** - * Returns whether AutoValue considers a type to be "optional". Optional types do not need to be - * set before build is called on a builder. Adapted from AutoValue source code. - * - * @param type some type - * @return true if type is an Optional type - */ - private static boolean isOptional(TypeMirror type) { - if (type.getKind() != TypeKind.DECLARED) { - return false; - } - DeclaredType declaredType = (DeclaredType) type; - TypeElement typeElement = (TypeElement) declaredType.asElement(); - return typeElement.getTypeParameters().size() == declaredType.getTypeArguments().size() - && ArraysPlume.indexOf( - optionalClassNames, typeElement.getQualifiedName().toString()) - != -1; + /** + * Does member represent a required property of an AutoValue class? + * + * @param member a member of an AutoValue class or superclass + * @param avBuilderSetterNames names of all setters in corresponding AutoValue builder class + * @return true if {@code member} is required + */ + private boolean isAutoValueRequiredProperty( + ExecutableElement member, Set avBuilderSetterNames) { + String name = member.getSimpleName().toString(); + // Ignore java.lang.Object overrides, constructors, and toBuilder methods in AutoValue + // classes. + // Strictly speaking, this code should check return types, etc. to handle strange + // overloads and other corner cases. They seem unlikely enough that we are skipping for now. + if (isAutoValueRequiredPropertyIgnored.contains(name)) { + return false; + } + TypeMirror returnType = member.getReturnType(); + if (returnType.getKind() == TypeKind.VOID) { + return false; + } + // shouldn't have a nullable return + boolean hasNullable = + Stream.concat( + atypeFactory.getElementUtils().getAllAnnotationMirrors(member).stream(), + returnType.getAnnotationMirrors().stream()) + .anyMatch(anm -> AnnotationUtils.annotationName(anm).endsWith(".Nullable")); + if (hasNullable) { + return false; + } + // if return type of foo() is a Guava Immutable type, not required if there is a + // builder method fooBuilder() + if (BuilderFrameworkSupportUtils.isGuavaImmutableType(returnType) + && avBuilderSetterNames.contains(name + "Builder")) { + return false; + } + // if it's an Optional, the Builder will automatically initialize it + if (isOptional(returnType)) { + return false; } + // it's required! + return true; + } - /** - * Returns names of all setter methods. - * - * @see #isAutoValueBuilderSetter - * @param builderElement the element representing an AutoValue builder - * @return the names of setter methods for the AutoValue builder - */ - private Set getAutoValueBuilderSetterMethodNames(TypeElement builderElement) { - return getAllAbstractMethods(builderElement).stream() - .filter(e -> isAutoValueBuilderSetter(e, builderElement)) - .map(e -> e.getSimpleName().toString()) - .collect(Collectors.toSet()); + /** Classes that AutoValue considers "optional". This list comes from AutoValue's source code. */ + private static final String[] optionalClassNames = + new String[] { + // Use concatenation to avoid ShadowJar relocate + // "com.google.common.base.Optional", + "com.go".toString() + "ogle.common.base.Optional", + "java.util.Optional", + "java.util.OptionalDouble", + "java.util.OptionalInt", + "java.util.OptionalLong" + }; + + /** + * Returns whether AutoValue considers a type to be "optional". Optional types do not need to be + * set before build is called on a builder. Adapted from AutoValue source code. + * + * @param type some type + * @return true if type is an Optional type + */ + private static boolean isOptional(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; } + DeclaredType declaredType = (DeclaredType) type; + TypeElement typeElement = (TypeElement) declaredType.asElement(); + return typeElement.getTypeParameters().size() == declaredType.getTypeArguments().size() + && ArraysPlume.indexOf(optionalClassNames, typeElement.getQualifiedName().toString()) != -1; + } - /** - * Return true if the given method is a setter for an AutoValue builder; that is, its return - * type is the builder itself or a Guava Immutable type. - * - * @param method a method of a builder or one of its supertypes - * @param builderElement element for the AutoValue builder - * @return true if {@code method} is a setter for the builder - */ - private boolean isAutoValueBuilderSetter(ExecutableElement method, TypeElement builderElement) { - TypeMirror retType = method.getReturnType(); - if (retType.getKind() == TypeKind.TYPEVAR) { - // instantiate the type variable for the Builder class - retType = - AnnotatedTypes.asMemberOf( - atypeFactory.getChecker().getTypeUtils(), - atypeFactory, - atypeFactory.getAnnotatedType(builderElement), - method) - .getReturnType() - .getUnderlyingType(); - } - // Either the return type should be the builder itself, or it should be a Guava immutable - // type. - return BuilderFrameworkSupportUtils.isGuavaImmutableType(retType) - || builderElement.equals(TypesUtils.getTypeElement(retType)); + /** + * Returns names of all setter methods. + * + * @see #isAutoValueBuilderSetter + * @param builderElement the element representing an AutoValue builder + * @return the names of setter methods for the AutoValue builder + */ + private Set getAutoValueBuilderSetterMethodNames(TypeElement builderElement) { + return getAllAbstractMethods(builderElement).stream() + .filter(e -> isAutoValueBuilderSetter(e, builderElement)) + .map(e -> e.getSimpleName().toString()) + .collect(Collectors.toSet()); + } + + /** + * Return true if the given method is a setter for an AutoValue builder; that is, its return type + * is the builder itself or a Guava Immutable type. + * + * @param method a method of a builder or one of its supertypes + * @param builderElement element for the AutoValue builder + * @return true if {@code method} is a setter for the builder + */ + private boolean isAutoValueBuilderSetter(ExecutableElement method, TypeElement builderElement) { + TypeMirror retType = method.getReturnType(); + if (retType.getKind() == TypeKind.TYPEVAR) { + // instantiate the type variable for the Builder class + retType = + AnnotatedTypes.asMemberOf( + atypeFactory.getChecker().getTypeUtils(), + atypeFactory, + atypeFactory.getAnnotatedType(builderElement), + method) + .getReturnType() + .getUnderlyingType(); } + // Either the return type should be the builder itself, or it should be a Guava immutable + // type. + return BuilderFrameworkSupportUtils.isGuavaImmutableType(retType) + || builderElement.equals(TypesUtils.getTypeElement(retType)); + } - /** - * Get all the abstract methods for a class. This includes inherited abstract methods that are - * not overridden by the class or a superclass. There is no guarantee that this method will work - * as intended on code that implements an interface (which AutoValue classes are not supposed to - * do: https://github.com/google/auto/blob/master/value/userguide/howto.md#inherit). - * - * @param classElement the class - * @return list of all abstract methods - */ - public List getAllAbstractMethods(TypeElement classElement) { - List supertypes = - ElementUtils.getAllSupertypes(classElement, atypeFactory.getProcessingEnv()); - List abstractMethods = new ArrayList<>(); - Set overriddenMethods = new HashSet<>(); - for (Element t : supertypes) { - for (Element member : t.getEnclosedElements()) { - if (member.getKind() != ElementKind.METHOD) { - continue; - } - Set modifiers = member.getModifiers(); - if (modifiers.contains(Modifier.STATIC)) { - continue; - } - if (modifiers.contains(Modifier.ABSTRACT)) { - // Make sure it's not overridden. This only works because ElementUtils#closure - // returns results in a particular order. - if (!overriddenMethods.contains(member)) { - abstractMethods.add((ExecutableElement) member); - } - } else { - // Exclude any methods that this overrides. - overriddenMethods.addAll( - AnnotatedTypes.overriddenMethods( - atypeFactory.getElementUtils(), - atypeFactory, - (ExecutableElement) member) - .values()); - } - } + /** + * Get all the abstract methods for a class. This includes inherited abstract methods that are not + * overridden by the class or a superclass. There is no guarantee that this method will work as + * intended on code that implements an interface (which AutoValue classes are not supposed to do: + * https://github.com/google/auto/blob/master/value/userguide/howto.md#inherit). + * + * @param classElement the class + * @return list of all abstract methods + */ + public List getAllAbstractMethods(TypeElement classElement) { + List supertypes = + ElementUtils.getAllSupertypes(classElement, atypeFactory.getProcessingEnv()); + List abstractMethods = new ArrayList<>(); + Set overriddenMethods = new HashSet<>(); + for (Element t : supertypes) { + for (Element member : t.getEnclosedElements()) { + if (member.getKind() != ElementKind.METHOD) { + continue; + } + Set modifiers = member.getModifiers(); + if (modifiers.contains(Modifier.STATIC)) { + continue; } - return abstractMethods; + if (modifiers.contains(Modifier.ABSTRACT)) { + // Make sure it's not overridden. This only works because ElementUtils#closure + // returns results in a particular order. + if (!overriddenMethods.contains(member)) { + abstractMethods.add((ExecutableElement) member); + } + } else { + // Exclude any methods that this overrides. + overriddenMethods.addAll( + AnnotatedTypes.overriddenMethods( + atypeFactory.getElementUtils(), atypeFactory, (ExecutableElement) member) + .values()); + } + } } + return abstractMethods; + } - /** - * Get the qualified name of the package containing AutoValue annotations. This method - * constructs the String dynamically, to ensure it does not get rewritten due to relocation of - * the {@code "com.google"} package during the build process. - * - * @return {@code "com.google.auto.value"} - */ - private String getAutoValuePackageName() { - String com = "com"; - return com + "." + "google.auto.value"; - } + /** + * Get the qualified name of the package containing AutoValue annotations. This method constructs + * the String dynamically, to ensure it does not get rewritten due to relocation of the {@code + * "com.google"} package during the build process. + * + * @return {@code "com.google.auto.value"} + */ + private String getAutoValuePackageName() { + String com = "com"; + return com + "." + "google.auto.value"; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java index 40aa4a06202..cbbf9951ef0 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java @@ -1,12 +1,10 @@ package org.checkerframework.checker.calledmethods.builder; import com.sun.source.tree.NewClassTree; - +import javax.lang.model.element.ExecutableElement; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import javax.lang.model.element.ExecutableElement; - /** * Provides hooks to add CalledMethods annotations to code generated by a builder framework like * Lombok or AutoValue. If you are adding support to the Called Methods Checker for a new builder @@ -19,58 +17,57 @@ */ public interface BuilderFrameworkSupport { - /** - * Returns true if a method is a {@code toBuilder} method on a type generated by the builder - * framework. - * - * @param candidateToBuilderElement a method - * @return {@code true} if {@code candidateToBuilderElement} is a {@code toBuilder} method on a - * type generated by the builder framework - */ - boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement); + /** + * Returns true if a method is a {@code toBuilder} method on a type generated by the builder + * framework. + * + * @param candidateToBuilderElement a method + * @return {@code true} if {@code candidateToBuilderElement} is a {@code toBuilder} method on a + * type generated by the builder framework + */ + boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement); - /** - * Hook for supporting a builder framework's {@code toBuilder} routine. Typically, the returned - * Builder has had all of its required setters invoked. So, implementations of this method - * should add a {@link org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation - * capturing this fact. - * - * @param toBuilderType the type of a method that is the {@code toBuilder} method (as determined - * by {@link #isToBuilderMethod(ExecutableElement)}) for a type that has an associated - * builder - */ - void handleToBuilderMethod(AnnotatedExecutableType toBuilderType); + /** + * Hook for supporting a builder framework's {@code toBuilder} routine. Typically, the returned + * Builder has had all of its required setters invoked. So, implementations of this method should + * add a {@link org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation + * capturing this fact. + * + * @param toBuilderType the type of a method that is the {@code toBuilder} method (as determined + * by {@link #isToBuilderMethod(ExecutableElement)}) for a type that has an associated builder + */ + void handleToBuilderMethod(AnnotatedExecutableType toBuilderType); - /** - * Returns true if a method is a {@code build} method on a {@code Builder} type for the builder - * framework. - * - * @param candidateBuildElement a method - * @return {@code true} if {@code candidateBuildElement} is a {@code build} method on a {@code - * Builder} type for the builder framework - */ - boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement); + /** + * Returns true if a method is a {@code build} method on a {@code Builder} type for the builder + * framework. + * + * @param candidateBuildElement a method + * @return {@code true} if {@code candidateBuildElement} is a {@code build} method on a {@code + * Builder} type for the builder framework + */ + boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement); - /** - * Hook for adding annotations to a build() method (i.e. a finalizer) generated by a builder - * framework. - * - *

For {@code build} methods on {@code Builder} types, implementations of this method should - * determine the required properties and add a corresponding {@link - * org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation to the type of the - * receiver parameter. - * - * @param builderBuildType the type of a method that is the {@code build} method (as determined - * by {@link #isBuilderBuildMethod(ExecutableElement)}) for a builder - */ - void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType); + /** + * Hook for adding annotations to a build() method (i.e. a finalizer) generated by a builder + * framework. + * + *

For {@code build} methods on {@code Builder} types, implementations of this method should + * determine the required properties and add a corresponding {@link + * org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation to the type of the + * receiver parameter. + * + * @param builderBuildType the type of a method that is the {@code build} method (as determined by + * {@link #isBuilderBuildMethod(ExecutableElement)}) for a builder + */ + void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType); - /** - * Hook for adding annotations (e.g., {@code @}{@link - * org.checkerframework.checker.calledmethods.qual.CalledMethods}) to a constructor call. - * - * @param tree a constructor call - * @param type type of the call expression - */ - void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type); + /** + * Hook for adding annotations (e.g., {@code @}{@link + * org.checkerframework.checker.calledmethods.qual.CalledMethods}) to a constructor call. + * + * @param tree a constructor call + * @param type type of the call expression + */ + void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type); } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java index 3d98fa883b1..679c63cc5e6 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java @@ -4,31 +4,31 @@ /** A utility class of static methods used in supporting builder-generation frameworks. */ public class BuilderFrameworkSupportUtils { - /** This class is non-instantiable. */ - private BuilderFrameworkSupportUtils() { - throw new Error("Do not instantiate"); - } + /** This class is non-instantiable. */ + private BuilderFrameworkSupportUtils() { + throw new Error("Do not instantiate"); + } - /** - * Returns true if the given type is one of the immutable collections defined in - * com.google.common.collect. - * - * @param type a type - * @return true if the type is a Guava immutable collection - */ - public static boolean isGuavaImmutableType(TypeMirror type) { - // Use concatenation to avoid ShadowJar relocate - // "com.google.common.collect.Immutable" - return type.toString().startsWith("com.go".toString() + "ogle.common.collect.Immutable"); - } + /** + * Returns true if the given type is one of the immutable collections defined in + * com.google.common.collect. + * + * @param type a type + * @return true if the type is a Guava immutable collection + */ + public static boolean isGuavaImmutableType(TypeMirror type) { + // Use concatenation to avoid ShadowJar relocate + // "com.google.common.collect.Immutable" + return type.toString().startsWith("com.go".toString() + "ogle.common.collect.Immutable"); + } - /** - * Capitalizes the first letter of the given string. - * - * @param prop a String - * @return the same String, but with the first character capitalized - */ - public static String capitalize(String prop) { - return Character.toUpperCase(prop.charAt(0)) + prop.substring(1); - } + /** + * Capitalizes the first letter of the given string. + * + * @param prop a String + * @return the same String, but with the first character capitalized + */ + public static String capitalize(String prop) { + return Character.toUpperCase(prop.charAt(0)) + prop.substring(1); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java index 9807af5357f..753a271ff81 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java @@ -2,13 +2,6 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.VariableTree; - -import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.ElementUtils; - import java.beans.Introspector; import java.util.ArrayList; import java.util.Arrays; @@ -16,13 +9,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; +import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; /** * Lombok support for the Called Methods Checker. This class adds CalledMethods annotations to the @@ -30,186 +27,180 @@ */ public class LombokSupport implements BuilderFrameworkSupport { - /** The type factory. */ - private final CalledMethodsAnnotatedTypeFactory atypeFactory; - - /** - * Create a new LombokSupport. - * - * @param atypeFactory the typechecker's type factory - */ - public LombokSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; + /** The type factory. */ + private final CalledMethodsAnnotatedTypeFactory atypeFactory; + + /** + * Create a new LombokSupport. + * + * @param atypeFactory the typechecker's type factory + */ + public LombokSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } + + // The list is copied from lombok.core.handlers.HandlerUtil. The list cannot be used from that + // class directly because Lombok does not provide class files for its own implementation, to + // prevent itself from being accidentally added to clients' compile classpaths. This design + // decision means that it is impossible to depend directly on Lombok internals. + /** The list of annotations that Lombok treats as non-null. */ + public static final List NONNULL_ANNOTATIONS = + Collections.unmodifiableList( + Arrays.asList( + "android.annotation.NonNull", + "android.support.annotation.NonNull", + "com.sun.istack.internal.NotNull", + "edu.umd.cs.findbugs.annotations.NonNull", + "javax.annotation.Nonnull", + // "javax.validation.constraints.NotNull", // The field might contain a + // null value until it is persisted. + "lombok.NonNull", + "org.checkerframework.checker.nullness.qual.NonNull", + "org.eclipse.jdt.annotation.NonNull", + "org.eclipse.jgit.annotations.NonNull", + "org.jetbrains.annotations.NotNull", + "org.jmlspecs.annotation.NonNull", + "org.netbeans.api.annotations.common.NonNull", + "org.springframework.lang.NonNull")); + + /** + * A map from elements that have a lombok.Builder.Default annotation to the simple property name + * that should be treated as defaulted. + * + *

This cache is kept because the usual method for checking that an element has been defaulted + * (calling declarationFromElement and examining the resulting VariableTree) only works if a + * corresponding Tree is available (for code that is only available as bytecode, no such Tree is + * available and that method returns null). See the code in {@link + * #getLombokRequiredProperties(Element)} that handles fields. + */ + private final Map defaultedElements = new HashMap<>(2); + + @Override + public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { + TypeElement candidateGeneratedBuilderElement = + (TypeElement) candidateBuildElement.getEnclosingElement(); + + if ((ElementUtils.hasAnnotation(candidateGeneratedBuilderElement, "lombok.Generated") + || ElementUtils.hasAnnotation(candidateBuildElement, "lombok.Generated")) + && candidateGeneratedBuilderElement.getSimpleName().toString().endsWith("Builder")) { + return candidateBuildElement.getSimpleName().contentEquals("build"); } - - // The list is copied from lombok.core.handlers.HandlerUtil. The list cannot be used from that - // class directly because Lombok does not provide class files for its own implementation, to - // prevent itself from being accidentally added to clients' compile classpaths. This design - // decision means that it is impossible to depend directly on Lombok internals. - /** The list of annotations that Lombok treats as non-null. */ - public static final List NONNULL_ANNOTATIONS = - Collections.unmodifiableList( - Arrays.asList( - "android.annotation.NonNull", - "android.support.annotation.NonNull", - "com.sun.istack.internal.NotNull", - "edu.umd.cs.findbugs.annotations.NonNull", - "javax.annotation.Nonnull", - // "javax.validation.constraints.NotNull", // The field might contain a - // null value until it is persisted. - "lombok.NonNull", - "org.checkerframework.checker.nullness.qual.NonNull", - "org.eclipse.jdt.annotation.NonNull", - "org.eclipse.jgit.annotations.NonNull", - "org.jetbrains.annotations.NotNull", - "org.jmlspecs.annotation.NonNull", - "org.netbeans.api.annotations.common.NonNull", - "org.springframework.lang.NonNull")); - - /** - * A map from elements that have a lombok.Builder.Default annotation to the simple property name - * that should be treated as defaulted. - * - *

This cache is kept because the usual method for checking that an element has been - * defaulted (calling declarationFromElement and examining the resulting VariableTree) only - * works if a corresponding Tree is available (for code that is only available as bytecode, no - * such Tree is available and that method returns null). See the code in {@link - * #getLombokRequiredProperties(Element)} that handles fields. - */ - private final Map defaultedElements = new HashMap<>(2); - - @Override - public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { - TypeElement candidateGeneratedBuilderElement = - (TypeElement) candidateBuildElement.getEnclosingElement(); - - if ((ElementUtils.hasAnnotation(candidateGeneratedBuilderElement, "lombok.Generated") - || ElementUtils.hasAnnotation(candidateBuildElement, "lombok.Generated")) - && candidateGeneratedBuilderElement - .getSimpleName() - .toString() - .endsWith("Builder")) { - return candidateBuildElement.getSimpleName().contentEquals("build"); + return false; + } + + @Override + public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { + ExecutableElement buildElement = builderBuildType.getElement(); + + TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); + // The class with the @lombok.Builder annotation... + Element annotatedWithBuilderElement = generatedBuilderElement.getEnclosingElement(); + + List requiredProperties = getLombokRequiredProperties(annotatedWithBuilderElement); + AnnotationMirror newCalledMethodsAnno = + atypeFactory.createAccumulatorAnnotation(requiredProperties); + builderBuildType.getReceiverType().addAnnotation(newCalledMethodsAnno); + } + + @Override + public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { + return candidateToBuilderElement.getSimpleName().contentEquals("toBuilder") + && (ElementUtils.hasAnnotation(candidateToBuilderElement, "lombok.Generated") + || ElementUtils.hasAnnotation( + candidateToBuilderElement.getEnclosingElement(), "lombok.Generated")); + } + + @Override + public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { + AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); + ExecutableElement buildElement = toBuilderType.getElement(); + TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); + handleToBuilderType(returnType, generatedBuilderElement); + } + + /** + * Add, to a type, a CalledMethods annotation that states that all required setters have been + * called. The type can be the return type of toBuilder or of the corresponding generated "copy" + * constructor. + * + * @param type type to update + * @param classElement corresponding AutoValue class + */ + private void handleToBuilderType(AnnotatedTypeMirror type, Element classElement) { + List requiredProperties = getLombokRequiredProperties(classElement); + AnnotationMirror calledMethodsAnno = + atypeFactory.createAccumulatorAnnotation(requiredProperties); + type.replaceAnnotation(calledMethodsAnno); + } + + /** + * Computes the required properties of a @lombok.Builder class, i.e., the names of the fields + * with @lombok.NonNull annotations. + * + * @param lombokClassElement the class with the @lombok.Builder annotation + * @return a list of required property names + */ + private List getLombokRequiredProperties(Element lombokClassElement) { + List requiredPropertyNames = new ArrayList<>(); + List defaultedPropertyNames = new ArrayList<>(); + for (Element member : lombokClassElement.getEnclosedElements()) { + if (member.getKind() == ElementKind.FIELD) { + // Lombok never generates non-null fields with initializers in builders, unless the + // field is annotated with @Default or @Singular, which are handled elsewhere. So, + // this code doesn't need to consider whether the field has or does not have + // initializers. + for (AnnotationMirror anm : + atypeFactory.getElementUtils().getAllAnnotationMirrors(member)) { + if (NONNULL_ANNOTATIONS.contains(AnnotationUtils.annotationName(anm))) { + requiredPropertyNames.add(member.getSimpleName().toString()); + } } - return false; - } - - @Override - public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { - ExecutableElement buildElement = builderBuildType.getElement(); - - TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); - // The class with the @lombok.Builder annotation... - Element annotatedWithBuilderElement = generatedBuilderElement.getEnclosingElement(); - - List requiredProperties = getLombokRequiredProperties(annotatedWithBuilderElement); - AnnotationMirror newCalledMethodsAnno = - atypeFactory.createAccumulatorAnnotation(requiredProperties); - builderBuildType.getReceiverType().addAnnotation(newCalledMethodsAnno); - } - - @Override - public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { - return candidateToBuilderElement.getSimpleName().contentEquals("toBuilder") - && (ElementUtils.hasAnnotation(candidateToBuilderElement, "lombok.Generated") - || ElementUtils.hasAnnotation( - candidateToBuilderElement.getEnclosingElement(), - "lombok.Generated")); - } - - @Override - public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { - AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); - ExecutableElement buildElement = toBuilderType.getElement(); - TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); - handleToBuilderType(returnType, generatedBuilderElement); - } - - /** - * Add, to a type, a CalledMethods annotation that states that all required setters have been - * called. The type can be the return type of toBuilder or of the corresponding generated "copy" - * constructor. - * - * @param type type to update - * @param classElement corresponding AutoValue class - */ - private void handleToBuilderType(AnnotatedTypeMirror type, Element classElement) { - List requiredProperties = getLombokRequiredProperties(classElement); - AnnotationMirror calledMethodsAnno = - atypeFactory.createAccumulatorAnnotation(requiredProperties); - type.replaceAnnotation(calledMethodsAnno); - } - - /** - * Computes the required properties of a @lombok.Builder class, i.e., the names of the fields - * with @lombok.NonNull annotations. - * - * @param lombokClassElement the class with the @lombok.Builder annotation - * @return a list of required property names - */ - private List getLombokRequiredProperties(Element lombokClassElement) { - List requiredPropertyNames = new ArrayList<>(); - List defaultedPropertyNames = new ArrayList<>(); - for (Element member : lombokClassElement.getEnclosedElements()) { - if (member.getKind() == ElementKind.FIELD) { - // Lombok never generates non-null fields with initializers in builders, unless the - // field is annotated with @Default or @Singular, which are handled elsewhere. So, - // this code doesn't need to consider whether the field has or does not have - // initializers. - for (AnnotationMirror anm : - atypeFactory.getElementUtils().getAllAnnotationMirrors(member)) { - if (NONNULL_ANNOTATIONS.contains(AnnotationUtils.annotationName(anm))) { - requiredPropertyNames.add(member.getSimpleName().toString()); - } - } - } else if (member.getKind() == ElementKind.METHOD - && ElementUtils.hasAnnotation(member, "lombok.Generated")) { - String methodName = member.getSimpleName().toString(); - // If a field foo has an @Builder.Default annotation, Lombok always generates a - // method called $default$foo. - if (methodName.startsWith("$default$")) { - String propName = methodName.substring(9); // $default$ has 9 characters - defaultedPropertyNames.add(propName); - } - } else if (member.getKind().isClass() && member.toString().endsWith("Builder")) { - // Note that the test above will fail to catch builders generated by Lombok that - // have custom names using the builderClassName attribute. TODO: find a way to - // handle such builders too. - - // If a field bar has an @Singular annotation, Lombok always generates a method - // called clearBar in the builder class itself. Therefore, search the builder for - // such a method, and extract the appropriate property name to treat as defaulted. - for (Element builderMember : member.getEnclosedElements()) { - if (builderMember.getKind() == ElementKind.METHOD - && ElementUtils.hasAnnotation(builderMember, "lombok.Generated")) { - String methodName = builderMember.getSimpleName().toString(); - if (methodName.startsWith("clear")) { - String propName = - Introspector.decapitalize( - methodName.substring(5)); // clear has 5 characters - defaultedPropertyNames.add(propName); - } - } else if (builderMember.getKind() == ElementKind.FIELD) { - VariableTree variableTree = - (VariableTree) atypeFactory.declarationFromElement(builderMember); - if (variableTree != null && variableTree.getInitializer() != null) { - Name propName = variableTree.getName(); - defaultedPropertyNames.add(propName.toString()); - defaultedElements.put(builderMember, propName); - } else if (defaultedElements.containsKey(builderMember)) { - defaultedPropertyNames.add( - defaultedElements.get(builderMember).toString()); - } - } - } + } else if (member.getKind() == ElementKind.METHOD + && ElementUtils.hasAnnotation(member, "lombok.Generated")) { + String methodName = member.getSimpleName().toString(); + // If a field foo has an @Builder.Default annotation, Lombok always generates a + // method called $default$foo. + if (methodName.startsWith("$default$")) { + String propName = methodName.substring(9); // $default$ has 9 characters + defaultedPropertyNames.add(propName); + } + } else if (member.getKind().isClass() && member.toString().endsWith("Builder")) { + // Note that the test above will fail to catch builders generated by Lombok that + // have custom names using the builderClassName attribute. TODO: find a way to + // handle such builders too. + + // If a field bar has an @Singular annotation, Lombok always generates a method + // called clearBar in the builder class itself. Therefore, search the builder for + // such a method, and extract the appropriate property name to treat as defaulted. + for (Element builderMember : member.getEnclosedElements()) { + if (builderMember.getKind() == ElementKind.METHOD + && ElementUtils.hasAnnotation(builderMember, "lombok.Generated")) { + String methodName = builderMember.getSimpleName().toString(); + if (methodName.startsWith("clear")) { + String propName = + Introspector.decapitalize(methodName.substring(5)); // clear has 5 characters + defaultedPropertyNames.add(propName); } + } else if (builderMember.getKind() == ElementKind.FIELD) { + VariableTree variableTree = + (VariableTree) atypeFactory.declarationFromElement(builderMember); + if (variableTree != null && variableTree.getInitializer() != null) { + Name propName = variableTree.getName(); + defaultedPropertyNames.add(propName.toString()); + defaultedElements.put(builderMember, propName); + } else if (defaultedElements.containsKey(builderMember)) { + defaultedPropertyNames.add(defaultedElements.get(builderMember).toString()); + } + } } - requiredPropertyNames.removeAll(defaultedPropertyNames); - return requiredPropertyNames; - } - - @Override - public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { - // do nothing + } } + requiredPropertyNames.removeAll(defaultedPropertyNames); + return requiredPropertyNames; + } + + @Override + public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { + // do nothing + } } diff --git a/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java index a3fb2cf67ee..c324d3cd835 100644 --- a/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java @@ -9,18 +9,18 @@ /** A PropertyKeyATF that uses CompilerMessageKey to annotate the keys. */ public class CompilerMessagesAnnotatedTypeFactory extends PropertyKeyAnnotatedTypeFactory { - public CompilerMessagesAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Does not call postInit() because its superclass does. - // If we ever add code to this constructor, it needs to: - // * call a superclass constructor that does not call postInit(), and - // * call postInit() itself. - } + public CompilerMessagesAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Does not call postInit() because its superclass does. + // If we ever add code to this constructor, it needs to: + // * call a superclass constructor that does not call postInit(), and + // * call postInit() itself. + } - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createBasicTreeAnnotator(), - new KeyLookupTreeAnnotator(this, CompilerMessageKey.class)); - } + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createBasicTreeAnnotator(), + new KeyLookupTreeAnnotator(this, CompilerMessageKey.class)); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java index aa84913b0c2..13162b0581f 100644 --- a/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java @@ -1,5 +1,10 @@ package org.checkerframework.checker.fenum; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.fenum.qual.Fenum; import org.checkerframework.checker.fenum.qual.FenumBottom; import org.checkerframework.checker.fenum.qual.FenumTop; @@ -19,158 +24,149 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** The type factory for the Fenum Checker. */ public class FenumAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** AnnotationMirror for {@link FenumUnqualified}. */ - protected final AnnotationMirror FENUM_UNQUALIFIED; + /** AnnotationMirror for {@link FenumUnqualified}. */ + protected final AnnotationMirror FENUM_UNQUALIFIED; + + /** AnnotationMirror for {@link FenumBottom}. */ + protected final AnnotationMirror FENUM_BOTTOM; + + /** AnnotationMirror for {@link FenumTop}. */ + protected final AnnotationMirror FENUM_TOP; + + /** + * Create a FenumAnnotatedTypeFactory. + * + * @param checker checker + */ + public FenumAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + FENUM_BOTTOM = AnnotationBuilder.fromClass(elements, FenumBottom.class); + FENUM_UNQUALIFIED = AnnotationBuilder.fromClass(elements, FenumUnqualified.class); + FENUM_TOP = AnnotationBuilder.fromClass(elements, FenumTop.class); + + this.postInit(); + } + + /** + * Copied from SubtypingChecker. Instead of returning an empty set if no "quals" option is given, + * we return Fenum as the only qualifier. + * + * @return the supported type qualifiers + */ + @Override + protected Set> createSupportedTypeQualifiers() { + // Load everything in qual directory, and top, bottom, unqualified, and fake enum + Set> qualSet = + getBundledTypeQualifiers( + FenumTop.class, + Fenum.class, + FenumUnqualified.class, + FenumBottom.class, + PolyFenum.class); + + // Load externally defined quals given in the -Aquals and/or -AqualDirs options + + // load individually named qualifiers + for (String qualName : checker.getStringsOption("quals", ',')) { + if (!Signatures.isBinaryName(qualName)) { + throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); + } + Class annoClass = loader.loadExternalAnnotationClass(qualName); + if (annoClass == null) { + throw new UserError("Cannot load qualifier \"%s\" in -Aquals", qualName); + } + qualSet.add(annoClass); + } - /** AnnotationMirror for {@link FenumBottom}. */ - protected final AnnotationMirror FENUM_BOTTOM; + // load directories of qualifiers + for (String dirName : checker.getStringsOption("qualDirs", ',')) { + qualSet.addAll(loader.loadExternalAnnotationClassesFromDirectory(dirName)); + } - /** AnnotationMirror for {@link FenumTop}. */ - protected final AnnotationMirror FENUM_TOP; + // TODO: warn if no qualifiers given? + // Just Fenum("..") is still valid, though... + return qualSet; + } - /** - * Create a FenumAnnotatedTypeFactory. - * - * @param checker checker - */ - public FenumAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new FenumQualifierHierarchy(getSupportedTypeQualifiers(), elements); + } - FENUM_BOTTOM = AnnotationBuilder.fromClass(elements, FenumBottom.class); - FENUM_UNQUALIFIED = AnnotationBuilder.fromClass(elements, FenumUnqualified.class); - FENUM_TOP = AnnotationBuilder.fromClass(elements, FenumTop.class); + /** Fenum qualifier hierarchy. */ + protected class FenumQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - this.postInit(); - } + /** QualifierKind for {@link Fenum} qualifier. */ + private final QualifierKind FENUM_KIND; /** - * Copied from SubtypingChecker. Instead of returning an empty set if no "quals" option is - * given, we return Fenum as the only qualifier. + * Creates FenumQualifierHierarchy. * - * @return the supported type qualifiers + * @param qualifierClasses qualifier classes + * @param elements element utils */ - @Override - protected Set> createSupportedTypeQualifiers() { - // Load everything in qual directory, and top, bottom, unqualified, and fake enum - Set> qualSet = - getBundledTypeQualifiers( - FenumTop.class, - Fenum.class, - FenumUnqualified.class, - FenumBottom.class, - PolyFenum.class); - - // Load externally defined quals given in the -Aquals and/or -AqualDirs options - - // load individually named qualifiers - for (String qualName : checker.getStringsOption("quals", ',')) { - if (!Signatures.isBinaryName(qualName)) { - throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); - } - Class annoClass = loader.loadExternalAnnotationClass(qualName); - if (annoClass == null) { - throw new UserError("Cannot load qualifier \"%s\" in -Aquals", qualName); - } - qualSet.add(annoClass); - } - - // load directories of qualifiers - for (String dirName : checker.getStringsOption("qualDirs", ',')) { - qualSet.addAll(loader.loadExternalAnnotationClassesFromDirectory(dirName)); - } - - // TODO: warn if no qualifiers given? - // Just Fenum("..") is still valid, though... - return qualSet; + public FenumQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, FenumAnnotatedTypeFactory.this); + this.FENUM_KIND = + this.qualifierKindHierarchy.getQualifierKind(Fenum.class.getCanonicalName()); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new FenumQualifierHierarchy(getSupportedTypeQualifiers(), elements); + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization FenumQualifierHierarchy this, + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses, FenumBottom.class); } - /** Fenum qualifier hierarchy. */ - protected class FenumQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** QualifierKind for {@link Fenum} qualifier. */ - private final QualifierKind FENUM_KIND; - - /** - * Creates FenumQualifierHierarchy. - * - * @param qualifierClasses qualifier classes - * @param elements element utils - */ - public FenumQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, FenumAnnotatedTypeFactory.this); - this.FENUM_KIND = - this.qualifierKindHierarchy.getQualifierKind(Fenum.class.getCanonicalName()); - } - - @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization FenumQualifierHierarchy this, - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses, FenumBottom.class); - } - - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - return AnnotationUtils.areSame(subAnno, superAnno); - } + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + return AnnotationUtils.areSame(subAnno, superAnno); + } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } - return FENUM_TOP; - } else if (qualifierKind1 == FENUM_KIND) { - return a1; - } else if (qualifierKind2 == FENUM_KIND) { - return a2; - } - throw new TypeSystemError( - "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; } + return FENUM_TOP; + } else if (qualifierKind1 == FENUM_KIND) { + return a1; + } else if (qualifierKind2 == FENUM_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); + } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { - return FENUM_BOTTOM; - } else if (qualifierKind1 == FENUM_KIND) { - return a2; - } else if (qualifierKind2 == FENUM_KIND) { - return a1; - } - throw new TypeSystemError( - "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); - } + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { + return FENUM_BOTTOM; + } else if (qualifierKind1 == FENUM_KIND) { + return a2; + } else if (qualifierKind2 == FENUM_KIND) { + return a1; + } + throw new TypeSystemError("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java index 7835b788c73..fc38b9269c0 100644 --- a/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java @@ -1,13 +1,11 @@ package org.checkerframework.checker.fenum; +import java.util.NavigableSet; +import javax.annotation.processing.SupportedOptions; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.SubtypingChecker; import org.checkerframework.framework.qual.StubFiles; -import java.util.NavigableSet; - -import javax.annotation.processing.SupportedOptions; - /** * The main checker class for the Fake Enum Checker. * @@ -27,9 +25,9 @@ @SupportedOptions({"quals", "qualDirs"}) public class FenumChecker extends BaseTypeChecker { - @Override - public NavigableSet getSuppressWarningsPrefixes() { - return SubtypingChecker.getSuppressWarningsPrefixes( - this.visitor, super.getSuppressWarningsPrefixes()); - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + return SubtypingChecker.getSuppressWarningsPrefixes( + this.visitor, super.getSuppressWarningsPrefixes()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java index 5b915fb934c..c8b51644dee 100644 --- a/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java @@ -6,7 +6,8 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.SwitchTree; import com.sun.source.tree.Tree; - +import java.util.List; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -16,83 +17,78 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; -import java.util.List; - -import javax.lang.model.element.ExecutableElement; - /** The visitor for Fenum Checker. */ public class FenumVisitor extends BaseTypeVisitor { - /** - * Creates a Fenum Visitor - * - * @param checker the checker - */ - public FenumVisitor(BaseTypeChecker checker) { - super(checker); - } - - @Override - public Void visitBinary(BinaryTree tree, Void p) { - if (!TreeUtils.isStringConcatenation(tree)) { - // The Fenum Checker is only concerned with primitive types, so just check that - // the primary annotations are equivalent. - AnnotatedTypeMirror lhs = atypeFactory.getAnnotatedType(tree.getLeftOperand()); - AnnotatedTypeMirror rhs = atypeFactory.getAnnotatedType(tree.getRightOperand()); - - if (!(typeHierarchy.isSubtypeShallowEffective(lhs, rhs) - || typeHierarchy.isSubtypeShallowEffective(rhs, lhs))) { - checker.reportError(tree, "binary.type.incompatible", lhs, rhs); - } - } - return super.visitBinary(tree, p); - } - - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - ExpressionTree expr = tree.getExpression(); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); - - for (CaseTree caseExpr : tree.getCases()) { - List realCaseExprs = CaseUtils.getExpressions(caseExpr); - // Check all the case options against the switch expression type: - for (ExpressionTree realCaseExpr : realCaseExprs) { - AnnotatedTypeMirror caseType = atypeFactory.getAnnotatedType(realCaseExpr); - - // There is currently no "switch.type.incompatible" message key, so it is treated - // identically to "type.incompatible". - this.commonAssignmentCheck( - exprType, caseType, caseExpr, "switch.type.incompatible"); - } - } - return super.visitSwitch(tree, p); + /** + * Creates a Fenum Visitor + * + * @param checker the checker + */ + public FenumVisitor(BaseTypeChecker checker) { + super(checker); + } + + @Override + public Void visitBinary(BinaryTree tree, Void p) { + if (!TreeUtils.isStringConcatenation(tree)) { + // The Fenum Checker is only concerned with primitive types, so just check that + // the primary annotations are equivalent. + AnnotatedTypeMirror lhs = atypeFactory.getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rhs = atypeFactory.getAnnotatedType(tree.getRightOperand()); + + if (!(typeHierarchy.isSubtypeShallowEffective(lhs, rhs) + || typeHierarchy.isSubtypeShallowEffective(rhs, lhs))) { + checker.reportError(tree, "binary.type.incompatible", lhs, rhs); + } } - - @Override - protected void checkConstructorInvocation( - AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { - // Ignore the default annotation on the constructor - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Skip this check - } - - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(atypeFactory.FENUM_UNQUALIFIED); - } - - // TODO: should we require a match between switch expression and cases? - - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // The checker calls this method to compare the annotation used in a type to the modifier it - // adds to the class declaration. As our default modifier is FenumBottom, this results in an - // error when a non-subtype is used. Can we use FenumTop as default instead? - return true; + return super.visitBinary(tree, p); + } + + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + ExpressionTree expr = tree.getExpression(); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); + + for (CaseTree caseExpr : tree.getCases()) { + List realCaseExprs = CaseUtils.getExpressions(caseExpr); + // Check all the case options against the switch expression type: + for (ExpressionTree realCaseExpr : realCaseExprs) { + AnnotatedTypeMirror caseType = atypeFactory.getAnnotatedType(realCaseExpr); + + // There is currently no "switch.type.incompatible" message key, so it is treated + // identically to "type.incompatible". + this.commonAssignmentCheck(exprType, caseType, caseExpr, "switch.type.incompatible"); + } } + return super.visitSwitch(tree, p); + } + + @Override + protected void checkConstructorInvocation( + AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { + // Ignore the default annotation on the constructor + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Skip this check + } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(atypeFactory.FENUM_UNQUALIFIED); + } + + // TODO: should we require a match between switch expression and cases? + + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // The checker calls this method to compare the annotation used in a type to the modifier it + // adds to the class declaration. As our default modifier is FenumBottom, this results in an + // error when a non-subtype is used. Can we use FenumTop as default instead? + return true; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java index ee429458c92..533dd146882 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java @@ -2,7 +2,9 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.IllegalFormatException; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; import org.checkerframework.checker.formatter.qual.FormatBottom; @@ -24,11 +26,6 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.IllegalFormatException; - -import javax.lang.model.element.AnnotationMirror; - /** * Adds {@link Format} to the type of tree, if it is a {@code String} or {@code char} literal that * represents a satisfiable format. The annotation's value is set to be a list of appropriate {@link @@ -38,348 +35,337 @@ */ public class FormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link UnknownFormat} annotation. */ - protected final AnnotationMirror UNKNOWNFORMAT = - AnnotationBuilder.fromClass(elements, UnknownFormat.class); - - /** The @{@link FormatBottom} annotation. */ - protected final AnnotationMirror FORMATBOTTOM = - AnnotationBuilder.fromClass(elements, FormatBottom.class); - - /** The @{@link FormatMethod} annotation. */ - protected final AnnotationMirror FORMATMETHOD = - AnnotationBuilder.fromClass(elements, FormatMethod.class); - - /** The fully-qualified name of the {@link Format} qualifier. */ - protected static final @CanonicalName String FORMAT_NAME = Format.class.getCanonicalName(); - - /** The fully-qualified name of the {@link InvalidFormat} qualifier. */ - protected static final @CanonicalName String INVALIDFORMAT_NAME = - InvalidFormat.class.getCanonicalName(); - - /** Syntax tree utilities. */ - protected final FormatterTreeUtil treeUtil = new FormatterTreeUtil(checker); - - /** Creates a FormatterAnnotatedTypeFactory. */ - public FormatterAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - try { - // Use concatenation to avoid ShadowJar relocate - // "com.google.errorprone.annotations.FormatMethod" - @SuppressWarnings({ - "unchecked", // Class must be an annotation type - "signature:argument.type.incompatible" // Class name intentionally obfuscated - }) - Class cgFormatMethod = - (Class) - Class.forName( - "com.go".toString() - + "ogle.errorprone.annotations.FormatMethod"); - - addAliasedDeclAnnotation(cgFormatMethod, FormatMethod.class, FORMATMETHOD); - } catch (ClassNotFoundException cnfe) { - // Ignore if com.google.errorprone.annotations.FormatMethod cannot be found. - } - - this.postInit(); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new FormatterQualifierHierarchy(); + /** The @{@link UnknownFormat} annotation. */ + protected final AnnotationMirror UNKNOWNFORMAT = + AnnotationBuilder.fromClass(elements, UnknownFormat.class); + + /** The @{@link FormatBottom} annotation. */ + protected final AnnotationMirror FORMATBOTTOM = + AnnotationBuilder.fromClass(elements, FormatBottom.class); + + /** The @{@link FormatMethod} annotation. */ + protected final AnnotationMirror FORMATMETHOD = + AnnotationBuilder.fromClass(elements, FormatMethod.class); + + /** The fully-qualified name of the {@link Format} qualifier. */ + protected static final @CanonicalName String FORMAT_NAME = Format.class.getCanonicalName(); + + /** The fully-qualified name of the {@link InvalidFormat} qualifier. */ + protected static final @CanonicalName String INVALIDFORMAT_NAME = + InvalidFormat.class.getCanonicalName(); + + /** Syntax tree utilities. */ + protected final FormatterTreeUtil treeUtil = new FormatterTreeUtil(checker); + + /** Creates a FormatterAnnotatedTypeFactory. */ + public FormatterAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + try { + // Use concatenation to avoid ShadowJar relocate + // "com.google.errorprone.annotations.FormatMethod" + @SuppressWarnings({ + "unchecked", // Class must be an annotation type + "signature:argument.type.incompatible" // Class name intentionally obfuscated + }) + Class cgFormatMethod = + (Class) + Class.forName("com.go".toString() + "ogle.errorprone.annotations.FormatMethod"); + + addAliasedDeclAnnotation(cgFormatMethod, FormatMethod.class, FORMATMETHOD); + } catch (ClassNotFoundException cnfe) { + // Ignore if com.google.errorprone.annotations.FormatMethod cannot be found. } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new FormatterTreeAnnotator(this)); + this.postInit(); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new FormatterQualifierHierarchy(); + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new FormatterTreeAnnotator(this)); + } + + /* NO-AFU + * {@inheritDoc} + * + *

If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation + * from its first argument. + */ + /* NO-AFU + @Override + public void wpiPrepareMethodForWriting(AMethod method) { + super.wpiPrepareMethodForWriting(method); + if (hasFormatMethodAnno(method)) { + AField param = method.parameters.get(0); + if (param != null) { + Set paramTypeAnnos = param.type.tlAnnotationsHere; + paramTypeAnnos.removeIf( + a -> a.def.name.equals("org.checkerframework.checker.formatter.qual.Format")); + } + } + */ + + /* NO-AFU + * {@inheritDoc} + * + *

If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation + * from its first argument. + */ + /* NO-AFU + @Override + public void wpiPrepareMethodForWriting( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, + Collection inSupertypes, + Collection inSubtypes) { + super.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); + if (hasFormatMethodAnno(methodAnnos)) { + AnnotatedTypeMirror atm = methodAnnos.getParameterType(0); + atm.removeAnnotationByClass(org.checkerframework.checker.formatter.qual.Format.class); } - - /* NO-AFU - * {@inheritDoc} + } + */ + + /* NO-AFU + * Returns true if the method has a {@code @FormatMethod} annotation. + * + * @param methodAnnos method annotations + * @return true if the method has a {@code @FormatMethod} annotation + */ + /* NO-AFU + private boolean hasFormatMethodAnno(AMethod methodAnnos) { + for (Annotation anno : methodAnnos.tlAnnotationsHere) { + String annoName = anno.def.name; + if (annoName.equals("org.checkerframework.checker.formatter.qual.FormatMethod") + || anno.def.name.equals("com.google.errorprone.annotations.FormatMethod")) { + return true; + } + } + */ + + /* NO-AFU + * Returns true if the method has a {@code @FormatMethod} annotation. + * + * @param methodAnnos method annotations + * @return true if the method has a {@code @FormatMethod} annotation + */ + /* NO-AFU + private boolean hasFormatMethodAnno( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); + return AnnotationUtils.containsSameByClass( + declarationAnnos, org.checkerframework.checker.formatter.qual.FormatMethod.class) + || AnnotationUtils.containsSameByName( + declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); + } + */ + + /* NO-AFU + * Returns true if the method has a {@code @FormatMethod} annotation. + * + * @param methodAnnos method annotations + * @return true if the method has a {@code @FormatMethod} annotation + */ + /* NO-AFU + private boolean hasFormatMethodAnno( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); + return AnnotationUtils.containsSameByClass( + declarationAnnos, + org.checkerframework.checker.formatter.qual.FormatMethod.class) + || AnnotationUtils.containsSameByName( + // TODO: avoid com.google relocate + declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); + } + */ + + /** The tree annotator for the Format String Checker. */ + private class FormatterTreeAnnotator extends TreeAnnotator { + /** + * Create the tree annotator for the Format String Checker. * - *

If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation - * from its first argument. + * @param atypeFactory the Format String Checker type factory */ - /* NO-AFU - @Override - public void wpiPrepareMethodForWriting(AMethod method) { - super.wpiPrepareMethodForWriting(method); - if (hasFormatMethodAnno(method)) { - AField param = method.parameters.get(0); - if (param != null) { - Set paramTypeAnnos = param.type.tlAnnotationsHere; - paramTypeAnnos.removeIf( - a -> a.def.name.equals("org.checkerframework.checker.formatter.qual.Format")); - } + public FormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - */ - /* NO-AFU - * {@inheritDoc} - * - *

If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation - * from its first argument. - */ - /* NO-AFU @Override - public void wpiPrepareMethodForWriting( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, - Collection inSupertypes, - Collection inSubtypes) { - super.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); - if (hasFormatMethodAnno(methodAnnos)) { - AnnotatedTypeMirror atm = methodAnnos.getParameterType(0); - atm.removeAnnotationByClass(org.checkerframework.checker.formatter.qual.Format.class); + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(UNKNOWNFORMAT)) { + String format = null; + if (tree.getKind() == Tree.Kind.STRING_LITERAL) { + format = (String) tree.getValue(); + } + if (format != null) { + AnnotationMirror anno; + try { + ConversionCategory[] cs = FormatUtil.formatParameterCategories(format); + anno = FormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs); + } catch (IllegalFormatException e) { + anno = + FormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation(e); + } + type.addAnnotation(anno); + } } + return super.visitLiteral(tree, type); } - */ + } - /* NO-AFU - * Returns true if the method has a {@code @FormatMethod} annotation. - * - * @param methodAnnos method annotations - * @return true if the method has a {@code @FormatMethod} annotation - */ - /* NO-AFU - private boolean hasFormatMethodAnno(AMethod methodAnnos) { - for (Annotation anno : methodAnnos.tlAnnotationsHere) { - String annoName = anno.def.name; - if (annoName.equals("org.checkerframework.checker.formatter.qual.FormatMethod") - || anno.def.name.equals("com.google.errorprone.annotations.FormatMethod")) { - return true; - } - } - */ + /** Qualifier hierarchy for the Formatter Checker. */ + class FormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /* NO-AFU - * Returns true if the method has a {@code @FormatMethod} annotation. - * - * @param methodAnnos method annotations - * @return true if the method has a {@code @FormatMethod} annotation - */ - /* NO-AFU - private boolean hasFormatMethodAnno( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); - return AnnotationUtils.containsSameByClass( - declarationAnnos, org.checkerframework.checker.formatter.qual.FormatMethod.class) - || AnnotationUtils.containsSameByName( - declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); - } - */ + /** Qualifier kind for the @{@link Format} annotation. */ + private final QualifierKind FORMAT_KIND; - /* NO-AFU - * Returns true if the method has a {@code @FormatMethod} annotation. - * - * @param methodAnnos method annotations - * @return true if the method has a {@code @FormatMethod} annotation - */ - /* NO-AFU - private boolean hasFormatMethodAnno( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); - return AnnotationUtils.containsSameByClass( - declarationAnnos, - org.checkerframework.checker.formatter.qual.FormatMethod.class) - || AnnotationUtils.containsSameByName( - // TODO: avoid com.google relocate - declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); + /** Qualifier kind for the @{@link InvalidFormat} annotation. */ + private final QualifierKind INVALIDFORMAT_KIND; + + /** Creates a {@link FormatterQualifierHierarchy}. */ + public FormatterQualifierHierarchy() { + super( + FormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + FormatterAnnotatedTypeFactory.this); + FORMAT_KIND = getQualifierKind(FORMAT_NAME); + INVALIDFORMAT_KIND = getQualifierKind(INVALIDFORMAT_NAME); } - */ - - /** The tree annotator for the Format String Checker. */ - private class FormatterTreeAnnotator extends TreeAnnotator { - /** - * Create the tree annotator for the Format String Checker. - * - * @param atypeFactory the Format String Checker type factory - */ - public FormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == FORMAT_KIND && superKind == FORMAT_KIND) { + ConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno); + ConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno); + + if (rhsArgTypes.length > lhsArgTypes.length) { + return false; } - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(UNKNOWNFORMAT)) { - String format = null; - if (tree.getKind() == Tree.Kind.STRING_LITERAL) { - format = (String) tree.getValue(); - } - if (format != null) { - AnnotationMirror anno; - try { - ConversionCategory[] cs = FormatUtil.formatParameterCategories(format); - anno = - FormatterAnnotatedTypeFactory.this.treeUtil - .categoriesToFormatAnnotation(cs); - } catch (IllegalFormatException e) { - anno = - FormatterAnnotatedTypeFactory.this.treeUtil - .exceptionToInvalidFormatAnnotation(e); - } - type.addAnnotation(anno); - } - } - return super.visitLiteral(tree, type); + for (int i = 0; i < rhsArgTypes.length; ++i) { + if (!ConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { + return false; + } } + return true; + } else if (subKind == INVALIDFORMAT_KIND && superKind == INVALIDFORMAT_KIND) { + return true; + } + throw new TypeSystemError("Unexpected kinds: %s %s", subKind, superKind); } - /** Qualifier hierarchy for the Formatter Checker. */ - class FormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.isBottom()) { + return anno2; + } else if (qualifierKind2.isBottom()) { + return anno1; + } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { + ConversionCategory[] shorterArgTypesList = treeUtil.formatAnnotationToCategories(anno1); + ConversionCategory[] longerArgTypesList = treeUtil.formatAnnotationToCategories(anno2); + if (shorterArgTypesList.length > longerArgTypesList.length) { + ConversionCategory[] temp = longerArgTypesList; + longerArgTypesList = shorterArgTypesList; + shorterArgTypesList = temp; + } - /** Qualifier kind for the @{@link Format} annotation. */ - private final QualifierKind FORMAT_KIND; + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. - /** Qualifier kind for the @{@link InvalidFormat} annotation. */ - private final QualifierKind INVALIDFORMAT_KIND; + ConversionCategory[] resultArgTypes = new ConversionCategory[longerArgTypesList.length]; - /** Creates a {@link FormatterQualifierHierarchy}. */ - public FormatterQualifierHierarchy() { - super( - FormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - FormatterAnnotatedTypeFactory.this); - FORMAT_KIND = getQualifierKind(FORMAT_NAME); - INVALIDFORMAT_KIND = getQualifierKind(INVALIDFORMAT_NAME); + for (int i = 0; i < shorterArgTypesList.length; ++i) { + resultArgTypes[i] = + ConversionCategory.intersect(shorterArgTypesList[i], longerArgTypesList[i]); } + System.arraycopy( + longerArgTypesList, + shorterArgTypesList.length, + resultArgTypes, + shorterArgTypesList.length, + longerArgTypesList.length - shorterArgTypesList.length); + return treeUtil.categoriesToFormatAnnotation(resultArgTypes); + } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) { + + assert !anno1.getElementValues().isEmpty(); + assert !anno2.getElementValues().isEmpty(); + + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; + } + + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " or " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } + + return UNKNOWNFORMAT; + } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == FORMAT_KIND && superKind == FORMAT_KIND) { - ConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno); - ConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno); - - if (rhsArgTypes.length > lhsArgTypes.length) { - return false; - } - - for (int i = 0; i < rhsArgTypes.length; ++i) { - if (!ConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { - return false; - } - } - return true; - } else if (subKind == INVALIDFORMAT_KIND && superKind == INVALIDFORMAT_KIND) { - return true; - } - throw new TypeSystemError("Unexpected kinds: %s %s", subKind, superKind); + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1.isTop()) { + return anno2; + } else if (qualifierKind2.isTop()) { + return anno1; + } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { + ConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1); + ConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2); + + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + int length = anno1ArgTypes.length; + if (anno2ArgTypes.length < length) { + length = anno2ArgTypes.length; } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.isBottom()) { - return anno2; - } else if (qualifierKind2.isBottom()) { - return anno1; - } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { - ConversionCategory[] shorterArgTypesList = - treeUtil.formatAnnotationToCategories(anno1); - ConversionCategory[] longerArgTypesList = - treeUtil.formatAnnotationToCategories(anno2); - if (shorterArgTypesList.length > longerArgTypesList.length) { - ConversionCategory[] temp = longerArgTypesList; - longerArgTypesList = shorterArgTypesList; - shorterArgTypesList = temp; - } - - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - - ConversionCategory[] resultArgTypes = - new ConversionCategory[longerArgTypesList.length]; - - for (int i = 0; i < shorterArgTypesList.length; ++i) { - resultArgTypes[i] = - ConversionCategory.intersect( - shorterArgTypesList[i], longerArgTypesList[i]); - } - System.arraycopy( - longerArgTypesList, - shorterArgTypesList.length, - resultArgTypes, - shorterArgTypesList.length, - longerArgTypesList.length - shorterArgTypesList.length); - return treeUtil.categoriesToFormatAnnotation(resultArgTypes); - } else if (qualifierKind1 == INVALIDFORMAT_KIND - && qualifierKind2 == INVALIDFORMAT_KIND) { - - assert !anno1.getElementValues().isEmpty(); - assert !anno2.getElementValues().isEmpty(); - - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; - } - - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " or " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } - - return UNKNOWNFORMAT; + ConversionCategory[] anno3ArgTypes = new ConversionCategory[length]; + + for (int i = 0; i < length; ++i) { + anno3ArgTypes[i] = ConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); } + return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); + } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) { + + assert !anno1.getElementValues().isEmpty(); + assert !anno2.getElementValues().isEmpty(); - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1.isTop()) { - return anno2; - } else if (qualifierKind2.isTop()) { - return anno1; - } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { - ConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1); - ConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2); - - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - int length = anno1ArgTypes.length; - if (anno2ArgTypes.length < length) { - length = anno2ArgTypes.length; - } - - ConversionCategory[] anno3ArgTypes = new ConversionCategory[length]; - - for (int i = 0; i < length; ++i) { - anno3ArgTypes[i] = ConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); - } - return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); - } else if (qualifierKind1 == INVALIDFORMAT_KIND - && qualifierKind2 == INVALIDFORMAT_KIND) { - - assert !anno1.getElementValues().isEmpty(); - assert !anno2.getElementValues().isEmpty(); - - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; - } - - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " and " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } - - return FORMATBOTTOM; + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; } + + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " and " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } + + return FORMATBOTTOM; } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java index e6b4c410c0a..3b235f94f4a 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.formatter; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.util.FormatUtil; @@ -12,39 +13,35 @@ import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; -import javax.lang.model.element.AnnotationMirror; - public class FormatterTransfer extends CFTransfer { - public FormatterTransfer(CFAnalysis analysis) { - super(analysis); - } - - /** - * Makes it so that the {@link FormatUtil#asFormat} method returns a correctly annotated String. - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput in) { - FormatterAnnotatedTypeFactory atypeFactory = - (FormatterAnnotatedTypeFactory) analysis.getTypeFactory(); - TransferResult result = super.visitMethodInvocation(node, in); - FormatterTreeUtil tu = atypeFactory.treeUtil; + public FormatterTransfer(CFAnalysis analysis) { + super(analysis); + } - if (tu.isAsFormatCall(node, atypeFactory)) { - Result cats = tu.asFormatCallCategories(node); - if (cats.value() == null) { - tu.failure(cats, "format.asformat.indirect.arguments"); - } else { - AnnotationMirror anno = - atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); - CFValue newResultValue = - analysis.createSingleAnnotationValue( - anno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - } + /** + * Makes it so that the {@link FormatUtil#asFormat} method returns a correctly annotated String. + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput in) { + FormatterAnnotatedTypeFactory atypeFactory = + (FormatterAnnotatedTypeFactory) analysis.getTypeFactory(); + TransferResult result = super.visitMethodInvocation(node, in); + FormatterTreeUtil tu = atypeFactory.treeUtil; - return result; + if (tu.isAsFormatCall(node, atypeFactory)) { + Result cats = tu.asFormatCallCategories(node); + if (cats.value() == null) { + tu.failure(cats, "format.asformat.indirect.arguments"); + } else { + AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); + CFValue newResultValue = + analysis.createSingleAnnotationValue(anno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } } + + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java index 4a7cc5cd977..c8e721ee101 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java @@ -5,7 +5,16 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.SimpleTreeVisitor; - +import java.util.IllegalFormatException; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleTypeVisitor8; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; @@ -25,493 +34,469 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.IllegalFormatException; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.NullType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor8; - /** * This class provides a collection of utilities to ease working with syntax trees that have * something to do with Formatters. */ public class FormatterTreeUtil { - /** The checker. */ - public final BaseTypeChecker checker; - - /** The processing environment. */ - public final ProcessingEnvironment processingEnv; - - /** The value() element/field of an @Format annotation. */ - protected final ExecutableElement formatValueElement; - - /** The value() element/field of an @InvalidFormat annotation. */ - protected final ExecutableElement invalidFormatValueElement; - - // private final ExecutableElement formatArgTypesElement; - - public FormatterTreeUtil(BaseTypeChecker checker) { - this.checker = checker; - this.processingEnv = checker.getProcessingEnvironment(); - formatValueElement = TreeUtils.getMethod(Format.class, "value", 0, processingEnv); - invalidFormatValueElement = - TreeUtils.getMethod(InvalidFormat.class, "value", 0, processingEnv); - /* - this.formatArgTypesElement = - TreeUtils.getMethod( - Format.class, - "value", - 0, - processingEnv); - */ - } - - /** Describes the ways a format method may be invoked. */ - public enum InvocationType { - /** - * The parameters are passed as varargs. For example: - * - *

- * - *
-         * String.format("%s %d", "Example", 7);
-         * 
- * - *
- */ - VARARG, - - /** - * The parameters are passed as array. For example: - * - *
- * - *
-         * Object[] a = new Object[]{"Example",7};
-         * String.format("%s %d", a);
-         * 
- * - *
- */ - ARRAY, - - /** - * A null array is passed to the format method. This happens seldomly. - * - *
- * - *
-         * String.format("%s %d", (Object[])null);
-         * 
- * - *
- */ - NULLARRAY; - } - - /** A wrapper around a value of type E, plus an ExpressionTree location. */ - public static class Result { - private final E value; - public final ExpressionTree location; - - public Result(E value, ExpressionTree location) { - this.value = value; - this.location = location; - } - - public E value() { - return value; - } - } + /** The checker. */ + public final BaseTypeChecker checker; + + /** The processing environment. */ + public final ProcessingEnvironment processingEnv; + + /** The value() element/field of an @Format annotation. */ + protected final ExecutableElement formatValueElement; + + /** The value() element/field of an @InvalidFormat annotation. */ + protected final ExecutableElement invalidFormatValueElement; + + // private final ExecutableElement formatArgTypesElement; + + public FormatterTreeUtil(BaseTypeChecker checker) { + this.checker = checker; + this.processingEnv = checker.getProcessingEnvironment(); + formatValueElement = TreeUtils.getMethod(Format.class, "value", 0, processingEnv); + invalidFormatValueElement = TreeUtils.getMethod(InvalidFormat.class, "value", 0, processingEnv); + /* + this.formatArgTypesElement = + TreeUtils.getMethod( + Format.class, + "value", + 0, + processingEnv); + */ + } + /** Describes the ways a format method may be invoked. */ + public enum InvocationType { /** - * Returns true if the call is to a method with the @ReturnsFormat annotation. An example of - * such a method is FormatUtil.asFormat. + * The parameters are passed as varargs. For example: + * + *
+ * + *
+     * String.format("%s %d", "Example", 7);
+     * 
+ * + *
*/ - public boolean isAsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, ReturnsFormat.class); - return anno != null; - } - - private ConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( - MethodInvocationNode node) { - Node vararg = node.getArgument(1); - if (!(vararg instanceof ArrayCreationNode)) { - return null; - } - List convs = ((ArrayCreationNode) vararg).getInitializers(); - ConversionCategory[] res = new ConversionCategory[convs.size()]; - for (int i = 0; i < convs.size(); ++i) { - Node conv = convs.get(i); - if (conv instanceof FieldAccessNode) { - Class clazz = - TypesUtils.getClassFromType(((FieldAccessNode) conv).getType()); - if (clazz == ConversionCategory.class) { - res[i] = ConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName()); - continue; /* avoid returning null */ - } - } - return null; - } - return res; - } - - public Result asFormatCallCategories(MethodInvocationNode node) { - // TODO make sure the method signature looks good - return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); - } + VARARG, /** - * Returns true if {@code tree} is a call to a method annotated with {@code @FormatMethod}. + * The parameters are passed as array. For example: + * + *
* - * @param tree a method call - * @param atypeFactory a type factory - * @return true if {@code tree} is a call to a method annotated with {@code @FormatMethod} + *
+     * Object[] a = new Object[]{"Example",7};
+     * String.format("%s %d", a);
+     * 
+ * + *
*/ - public boolean isFormatMethodCall( - MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, FormatMethod.class); - return anno != null; - } + ARRAY, /** - * Creates a new FormatCall, or returns null. + * A null array is passed to the format method. This happens seldomly. * - * @param invocationTree a method invocation, where the method is annotated @FormatMethod - * @param atypeFactory the type factory - * @return a new FormatCall, or null if the invocation is of a method that is improperly - * annotated @FormatMethod + *
+ * + *
+     * String.format("%s %d", (Object[])null);
+     * 
+ * + *
*/ - public @Nullable FormatCall create( - MethodInvocationTree invocationTree, AnnotatedTypeFactory atypeFactory) { - FormatterTreeUtil ftu = ((FormatterAnnotatedTypeFactory) atypeFactory).treeUtil; - if (!ftu.isFormatMethodCall(invocationTree, atypeFactory)) { - return null; - } + NULLARRAY; + } - ExecutableElement methodElement = TreeUtils.elementFromUse(invocationTree); - int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); - if (formatStringIndex == -1) { - // Reporting the error is redundant if the method was declared in source code, because - // the visitor will have reported it; but it is necessary if the method was declared in - // byte code. - atypeFactory - .getChecker() - .reportError( - invocationTree, "format.method.invalid", methodElement.getSimpleName()); - return null; - } - ExpressionTree formatStringTree = invocationTree.getArguments().get(formatStringIndex); - AnnotatedTypeMirror formatStringType = atypeFactory.getAnnotatedType(formatStringTree); - List allArgs = invocationTree.getArguments(); - List args = - allArgs.subList(formatStringIndex + 1, allArgs.size()); - - return new FormatCall( - invocationTree, formatStringTree, formatStringType, args, atypeFactory); + /** A wrapper around a value of type E, plus an ExpressionTree location. */ + public static class Result { + private final E value; + public final ExpressionTree location; + + public Result(E value, ExpressionTree location) { + this.value = value; + this.location = location; } - /** Represents a format method invocation in the syntax tree. */ - public class FormatCall { - /** The call itself. */ - /*package-private*/ final MethodInvocationTree invocationTree; - - /** The format string argument. */ - private final ExpressionTree formatStringTree; - - /** The type of the format string argument. */ - private final AnnotatedTypeMirror formatStringType; - - /** The arguments that follow the format string argument. */ - private final List args; - - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * Create a new FormatCall object. - * - * @param invocationTree the call itself - * @param formatStringTree the format string argument - * @param formatStringType the type of the format string argument - * @param args the arguments that follow the format string argument - * @param atypeFactory the type factory - */ - private FormatCall( - MethodInvocationTree invocationTree, - ExpressionTree formatStringTree, - AnnotatedTypeMirror formatStringType, - List args, - AnnotatedTypeFactory atypeFactory) { - this.invocationTree = invocationTree; - this.formatStringTree = formatStringTree; - this.formatStringType = formatStringType; - this.args = args; - this.atypeFactory = atypeFactory; + public E value() { + return value; + } + } + + /** + * Returns true if the call is to a method with the @ReturnsFormat annotation. An example of such + * a method is FormatUtil.asFormat. + */ + public boolean isAsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, ReturnsFormat.class); + return anno != null; + } + + private ConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( + MethodInvocationNode node) { + Node vararg = node.getArgument(1); + if (!(vararg instanceof ArrayCreationNode)) { + return null; + } + List convs = ((ArrayCreationNode) vararg).getInitializers(); + ConversionCategory[] res = new ConversionCategory[convs.size()]; + for (int i = 0; i < convs.size(); ++i) { + Node conv = convs.get(i); + if (conv instanceof FieldAccessNode) { + Class clazz = + TypesUtils.getClassFromType(((FieldAccessNode) conv).getType()); + if (clazz == ConversionCategory.class) { + res[i] = ConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName()); + continue; /* avoid returning null */ } + } + return null; + } + return res; + } + + public Result asFormatCallCategories(MethodInvocationNode node) { + // TODO make sure the method signature looks good + return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); + } + + /** + * Returns true if {@code tree} is a call to a method annotated with {@code @FormatMethod}. + * + * @param tree a method call + * @param atypeFactory a type factory + * @return true if {@code tree} is a call to a method annotated with {@code @FormatMethod} + */ + public boolean isFormatMethodCall(MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, FormatMethod.class); + return anno != null; + } + + /** + * Creates a new FormatCall, or returns null. + * + * @param invocationTree a method invocation, where the method is annotated @FormatMethod + * @param atypeFactory the type factory + * @return a new FormatCall, or null if the invocation is of a method that is improperly + * annotated @FormatMethod + */ + public @Nullable FormatCall create( + MethodInvocationTree invocationTree, AnnotatedTypeFactory atypeFactory) { + FormatterTreeUtil ftu = ((FormatterAnnotatedTypeFactory) atypeFactory).treeUtil; + if (!ftu.isFormatMethodCall(invocationTree, atypeFactory)) { + return null; + } - /** - * Returns an error description if the format-string argument's type is not - * annotated as {@code @Format}. Returns null if it is annotated. - * - * @return an error description if the format string is not annotated as {@code @Format}, or - * null if it is - */ - public final @Nullable Result errMissingFormatAnnotation() { - if (!formatStringType.hasAnnotation(Format.class)) { - String msg = "(is a @Format annotation missing?)"; - AnnotationMirror inv = formatStringType.getAnnotation(InvalidFormat.class); - if (inv != null) { - msg = invalidFormatAnnotationToErrorMessage(inv); - } - return new Result<>(msg, formatStringTree); - } - return null; - } + ExecutableElement methodElement = TreeUtils.elementFromUse(invocationTree); + int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); + if (formatStringIndex == -1) { + // Reporting the error is redundant if the method was declared in source code, because + // the visitor will have reported it; but it is necessary if the method was declared in + // byte code. + atypeFactory + .getChecker() + .reportError(invocationTree, "format.method.invalid", methodElement.getSimpleName()); + return null; + } + ExpressionTree formatStringTree = invocationTree.getArguments().get(formatStringIndex); + AnnotatedTypeMirror formatStringType = atypeFactory.getAnnotatedType(formatStringTree); + List allArgs = invocationTree.getArguments(); + List args = allArgs.subList(formatStringIndex + 1, allArgs.size()); - /** - * Returns the type of method invocation. - * - * @see InvocationType - */ - public final Result getInvocationType() { - InvocationType type = InvocationType.VARARG; - - if (args.size() == 1) { - ExpressionTree first = args.get(0); - TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); - // figure out if argType is an array - type = - argType.accept( - new SimpleTypeVisitor8>() { - @Override - protected InvocationType defaultAction( - TypeMirror e, Class p) { - // not an array - return InvocationType.VARARG; - } - - @Override - public InvocationType visitArray(ArrayType t, Class p) { - // it's an array, now figure out if it's a (Object[])null - // array - return first.accept( - new SimpleTreeVisitor< - InvocationType, Class>() { - @Override - protected InvocationType defaultAction( - Tree tree, Class p) { - // just a normal array - return InvocationType.ARRAY; - } - - @Override - public InvocationType visitTypeCast( - TypeCastTree tree, Class p) { - // it's a (Object[])null - return atypeFactory - .getAnnotatedType( - tree - .getExpression()) - .getUnderlyingType() - .getKind() - == TypeKind.NULL - ? InvocationType.NULLARRAY - : InvocationType.ARRAY; - } - }, - p); - } - - @Override - public InvocationType visitNull(NullType t, Class p) { - return InvocationType.NULLARRAY; - } - }, - Void.TYPE); - } + return new FormatCall(invocationTree, formatStringTree, formatStringType, args, atypeFactory); + } - ExpressionTree loc = invocationTree.getMethodSelect(); - if (type != InvocationType.VARARG && !args.isEmpty()) { - loc = args.get(0); - } - return new Result<>(type, loc); - } + /** Represents a format method invocation in the syntax tree. */ + public class FormatCall { + /** The call itself. */ + /*package-private*/ final MethodInvocationTree invocationTree; - /** - * Returns the conversion category for every parameter. - * - * @return the conversion categories of all the parameters - * @see ConversionCategory - */ - public final ConversionCategory[] getFormatCategories() { - AnnotationMirror anno = formatStringType.getAnnotation(Format.class); - return formatAnnotationToCategories(anno); - } + /** The format string argument. */ + private final ExpressionTree formatStringTree; - /** - * Returns the types of the arguments to the call. Use {@link #isValidArgument} and {@link - * #isArgumentNull} to work with the result. - * - * @return the types of the arguments to the call - */ - public final Result[] getArgTypes() { - // One to suppress warning in javac, the other to suppress warning in Eclipse... - @SuppressWarnings({"rawtypes", "unchecked"}) - Result[] res = new Result[args.size()]; - for (int i = 0; i < res.length; ++i) { - ExpressionTree arg = args.get(i); - TypeMirror argType; - if (TreeUtils.isNullExpression(arg)) { - argType = atypeFactory.getProcessingEnv().getTypeUtils().getNullType(); - } else { - argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); - } - res[i] = new Result<>(argType, arg); - } - return res; - } + /** The type of the format string argument. */ + private final AnnotatedTypeMirror formatStringType; - /** - * Checks if the type of an argument returned from {@link #getArgTypes()} is valid for the - * passed ConversionCategory. - * - * @param formatCat a format specifier - * @param argType an argument type - * @return true if the argument can be passed to the format specifier - */ - public final boolean isValidArgument(ConversionCategory formatCat, TypeMirror argType) { - if (argType.getKind() == TypeKind.NULL || isArgumentNull(argType)) { - return true; - } - Class type = TypesUtils.getClassFromType(argType); - return formatCat.isAssignableFrom(type); - } + /** The arguments that follow the format string argument. */ + private final List args; - /** - * Checks if the argument returned from {@link #getArgTypes()} is a {@code null} expression. - * - * @param type a type - * @return true if the argument is a {@code null} expression - */ - public final boolean isArgumentNull(TypeMirror type) { - // TODO: Just check whether it is the VOID TypeMirror. - - // is it the null literal - return type.accept( - new SimpleTypeVisitor8>() { - @Override - protected Boolean defaultAction(TypeMirror e, Class p) { - // it's not the null literal - return false; - } - - @Override - public Boolean visitNull(NullType t, Class p) { - // it's the null literal - return true; - } - }, - Void.TYPE); - } - } + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; - // The failure() method is required so that FormatterTransfer, which has no access to the - // FormatterChecker, can report errors. /** - * Reports an error. + * Create a new FormatCall object. * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message + * @param invocationTree the call itself + * @param formatStringTree the format string argument + * @param formatStringType the type of the format string argument + * @param args the arguments that follow the format string argument + * @param atypeFactory the type factory */ - public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportError(res.location, msgKey, args); + private FormatCall( + MethodInvocationTree invocationTree, + ExpressionTree formatStringTree, + AnnotatedTypeMirror formatStringType, + List args, + AnnotatedTypeFactory atypeFactory) { + this.invocationTree = invocationTree; + this.formatStringTree = formatStringTree; + this.formatStringType = formatStringType; + this.args = args; + this.atypeFactory = atypeFactory; } /** - * Reports a warning. + * Returns an error description if the format-string argument's type is not annotated + * as {@code @Format}. Returns null if it is annotated. * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message - */ - public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportWarning(res.location, msgKey, args); - } - - /** - * Takes an exception that describes an invalid formatter string and, returns a syntax trees - * element that represents a {@link InvalidFormat} annotation with the exception's error message - * as value. + * @return an error description if the format string is not annotated as {@code @Format}, or + * null if it is */ - public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalFormatException ex) { - return stringToInvalidFormatAnnotation(ex.getMessage()); + public final @Nullable Result errMissingFormatAnnotation() { + if (!formatStringType.hasAnnotation(Format.class)) { + String msg = "(is a @Format annotation missing?)"; + AnnotationMirror inv = formatStringType.getAnnotation(InvalidFormat.class); + if (inv != null) { + msg = invalidFormatAnnotationToErrorMessage(inv); + } + return new Result<>(msg, formatStringTree); + } + return null; } /** - * Creates an {@link InvalidFormat} annotation with the given string as its value. + * Returns the type of method invocation. * - * @param invalidFormatString an invalid formatter string - * @return an {@link InvalidFormat} annotation with the given string as its value + * @see InvocationType */ - /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation( - String invalidFormatString) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", invalidFormatString); - return builder.build(); + public final Result getInvocationType() { + InvocationType type = InvocationType.VARARG; + + if (args.size() == 1) { + ExpressionTree first = args.get(0); + TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); + // figure out if argType is an array + type = + argType.accept( + new SimpleTypeVisitor8>() { + @Override + protected InvocationType defaultAction(TypeMirror e, Class p) { + // not an array + return InvocationType.VARARG; + } + + @Override + public InvocationType visitArray(ArrayType t, Class p) { + // it's an array, now figure out if it's a (Object[])null + // array + return first.accept( + new SimpleTreeVisitor>() { + @Override + protected InvocationType defaultAction(Tree tree, Class p) { + // just a normal array + return InvocationType.ARRAY; + } + + @Override + public InvocationType visitTypeCast(TypeCastTree tree, Class p) { + // it's a (Object[])null + return atypeFactory + .getAnnotatedType(tree.getExpression()) + .getUnderlyingType() + .getKind() + == TypeKind.NULL + ? InvocationType.NULLARRAY + : InvocationType.ARRAY; + } + }, + p); + } + + @Override + public InvocationType visitNull(NullType t, Class p) { + return InvocationType.NULLARRAY; + } + }, + Void.TYPE); + } + + ExpressionTree loc = invocationTree.getMethodSelect(); + if (type != InvocationType.VARARG && !args.isEmpty()) { + loc = args.get(0); + } + return new Result<>(type, loc); } /** - * Gets the value() element/field out of an InvalidFormat annotation. + * Returns the conversion category for every parameter. * - * @param anno an InvalidFormat annotation - * @return its value() element/field + * @return the conversion categories of all the parameters + * @see ConversionCategory */ - private String getInvalidFormatValue(AnnotationMirror anno) { - return (String) anno.getElementValues().get(invalidFormatValueElement).getValue(); + public final ConversionCategory[] getFormatCategories() { + AnnotationMirror anno = formatStringType.getAnnotation(Format.class); + return formatAnnotationToCategories(anno); } /** - * Takes a syntax tree element that represents a {@link InvalidFormat} annotation, and returns - * its value. + * Returns the types of the arguments to the call. Use {@link #isValidArgument} and {@link + * #isArgumentNull} to work with the result. * - * @param anno an InvalidFormat annotation - * @return its value() element/field + * @return the types of the arguments to the call */ - public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { - return "\"" + getInvalidFormatValue(anno) + "\""; + public final Result[] getArgTypes() { + // One to suppress warning in javac, the other to suppress warning in Eclipse... + @SuppressWarnings({"rawtypes", "unchecked"}) + Result[] res = new Result[args.size()]; + for (int i = 0; i < res.length; ++i) { + ExpressionTree arg = args.get(i); + TypeMirror argType; + if (TreeUtils.isNullExpression(arg)) { + argType = atypeFactory.getProcessingEnv().getTypeUtils().getNullType(); + } else { + argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); + } + res[i] = new Result<>(argType, arg); + } + return res; } /** - * Creates a {@code @}{@link Format} annotation with the given list as its value. + * Checks if the type of an argument returned from {@link #getArgTypes()} is valid for the + * passed ConversionCategory. * - * @param args conversion categories for the {@code @Format} annotation - * @return a {@code @}{@link Format} annotation with the given list as its value + * @param formatCat a format specifier + * @param argType an argument type + * @return true if the argument can be passed to the format specifier */ - public AnnotationMirror categoriesToFormatAnnotation(ConversionCategory[] args) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Format.class); - builder.setValue("value", args); - return builder.build(); + public final boolean isValidArgument(ConversionCategory formatCat, TypeMirror argType) { + if (argType.getKind() == TypeKind.NULL || isArgumentNull(argType)) { + return true; + } + Class type = TypesUtils.getClassFromType(argType); + return formatCat.isAssignableFrom(type); } /** - * Returns the value of a {@code @}{@link Format} annotation. + * Checks if the argument returned from {@link #getArgTypes()} is a {@code null} expression. * - * @param anno a {@code @}{@link Format} annotation - * @return the annotation's {@code value} element + * @param type a type + * @return true if the argument is a {@code null} expression */ - @SuppressWarnings("GetClassOnEnum") - public ConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { - return AnnotationUtils.getElementValueEnumArray( - anno, formatValueElement, ConversionCategory.class); + public final boolean isArgumentNull(TypeMirror type) { + // TODO: Just check whether it is the VOID TypeMirror. + + // is it the null literal + return type.accept( + new SimpleTypeVisitor8>() { + @Override + protected Boolean defaultAction(TypeMirror e, Class p) { + // it's not the null literal + return false; + } + + @Override + public Boolean visitNull(NullType t, Class p) { + // it's the null literal + return true; + } + }, + Void.TYPE); } + } + + // The failure() method is required so that FormatterTransfer, which has no access to the + // FormatterChecker, can report errors. + /** + * Reports an error. + * + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message + */ + public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportError(res.location, msgKey, args); + } + + /** + * Reports a warning. + * + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message + */ + public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportWarning(res.location, msgKey, args); + } + + /** + * Takes an exception that describes an invalid formatter string and, returns a syntax trees + * element that represents a {@link InvalidFormat} annotation with the exception's error message + * as value. + */ + public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalFormatException ex) { + return stringToInvalidFormatAnnotation(ex.getMessage()); + } + + /** + * Creates an {@link InvalidFormat} annotation with the given string as its value. + * + * @param invalidFormatString an invalid formatter string + * @return an {@link InvalidFormat} annotation with the given string as its value + */ + /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation(String invalidFormatString) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", invalidFormatString); + return builder.build(); + } + + /** + * Gets the value() element/field out of an InvalidFormat annotation. + * + * @param anno an InvalidFormat annotation + * @return its value() element/field + */ + private String getInvalidFormatValue(AnnotationMirror anno) { + return (String) anno.getElementValues().get(invalidFormatValueElement).getValue(); + } + + /** + * Takes a syntax tree element that represents a {@link InvalidFormat} annotation, and returns its + * value. + * + * @param anno an InvalidFormat annotation + * @return its value() element/field + */ + public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { + return "\"" + getInvalidFormatValue(anno) + "\""; + } + + /** + * Creates a {@code @}{@link Format} annotation with the given list as its value. + * + * @param args conversion categories for the {@code @Format} annotation + * @return a {@code @}{@link Format} annotation with the given list as its value + */ + public AnnotationMirror categoriesToFormatAnnotation(ConversionCategory[] args) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Format.class); + builder.setValue("value", args); + return builder.build(); + } + + /** + * Returns the value of a {@code @}{@link Format} annotation. + * + * @param anno a {@code @}{@link Format} annotation + * @return the annotation's {@code value} element + */ + @SuppressWarnings("GetClassOnEnum") + public ConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { + return AnnotationUtils.getElementValueEnumArray( + anno, formatValueElement, ConversionCategory.class); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java index 241954912e4..15ccc76c09a 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java @@ -6,7 +6,12 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.FormatterTreeUtil.FormatCall; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; @@ -24,14 +29,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.UserError; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /* NO-AFU import org.checkerframework.common.wholeprograminference.WholeProgramInference; */ @@ -43,276 +40,263 @@ * @checker_framework.manual #formatter-guarantees Format String Checker */ public class FormatterVisitor extends BaseTypeVisitor { - public FormatterVisitor(BaseTypeChecker checker) { - super(checker); - } + public FormatterVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); - if (atypeFactory.getDeclAnnotation(methodElement, FormatMethod.class) != null) { - int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); - if (formatStringIndex == -1) { - checker.reportError(tree, "format.method.invalid", methodElement.getSimpleName()); - } - } - return super.visitMethod(tree, p); + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); + if (atypeFactory.getDeclAnnotation(methodElement, FormatMethod.class) != null) { + int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); + if (formatStringIndex == -1) { + checker.reportError(tree, "format.method.invalid", methodElement.getSimpleName()); + } } + return super.visitMethod(tree, p); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - FormatterTreeUtil ftu = atypeFactory.treeUtil; - FormatCall fc = ftu.create(tree, atypeFactory); - if (fc != null) { - MethodTree enclosingMethod = - TreePathUtil.enclosingMethod(atypeFactory.getPath(fc.invocationTree)); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + FormatterTreeUtil ftu = atypeFactory.treeUtil; + FormatCall fc = ftu.create(tree, atypeFactory); + if (fc != null) { + MethodTree enclosingMethod = + TreePathUtil.enclosingMethod(atypeFactory.getPath(fc.invocationTree)); - Result errMissingFormat = fc.errMissingFormatAnnotation(); - if (errMissingFormat != null) { - // The string's type has no @Format annotation. - if (isWrappedFormatCall(fc, enclosingMethod)) { - // Nothing to do, because call is legal. - } else { - // I.1 - ftu.failure( - errMissingFormat, "format.string.invalid", errMissingFormat.value()); - } + Result errMissingFormat = fc.errMissingFormatAnnotation(); + if (errMissingFormat != null) { + // The string's type has no @Format annotation. + if (isWrappedFormatCall(fc, enclosingMethod)) { + // Nothing to do, because call is legal. + } else { + // I.1 + ftu.failure(errMissingFormat, "format.string.invalid", errMissingFormat.value()); + } + } else { + // The string has a @Format annotation. + Result invc = fc.getInvocationType(); + ConversionCategory[] formatCats = fc.getFormatCategories(); + switch (invc.value()) { + case VARARG: + Result[] argTypes = fc.getArgTypes(); + int argl = argTypes.length; + int formatl = formatCats.length; + if (argl < formatl) { + // For assignments, format.missing.arguments is issued from + // commonAssignmentCheck(). + // II.1 + ftu.failure(invc, "format.missing.arguments", formatl, argl); } else { - // The string has a @Format annotation. - Result invc = fc.getInvocationType(); - ConversionCategory[] formatCats = fc.getFormatCategories(); - switch (invc.value()) { - case VARARG: - Result[] argTypes = fc.getArgTypes(); - int argl = argTypes.length; - int formatl = formatCats.length; - if (argl < formatl) { - // For assignments, format.missing.arguments is issued from - // commonAssignmentCheck(). - // II.1 - ftu.failure(invc, "format.missing.arguments", formatl, argl); - } else { - if (argl > formatl) { - // II.2 - ftu.warning(invc, "format.excess.arguments", formatl, argl); - } - for (int i = 0; i < formatl; ++i) { - ConversionCategory formatCat = formatCats[i]; - Result arg = argTypes[i]; - TypeMirror argType = arg.value(); + if (argl > formatl) { + // II.2 + ftu.warning(invc, "format.excess.arguments", formatl, argl); + } + for (int i = 0; i < formatl; ++i) { + ConversionCategory formatCat = formatCats[i]; + Result arg = argTypes[i]; + TypeMirror argType = arg.value(); - switch (formatCat) { - case UNUSED: - // I.2 - ftu.warning(arg, "format.argument.unused", " " + (1 + i)); - break; - case NULL: - // I.3 - if (argType.getKind() == TypeKind.NULL) { - ftu.warning( - arg, "format.specifier.null", " " + (1 + i)); - } else { - ftu.failure( - arg, "format.specifier.null", " " + (1 + i)); - } - break; - case GENERAL: - break; - default: - if (!fc.isValidArgument(formatCat, argType)) { - // II.3 - ExecutableElement method = - TreeUtils.elementFromUse(tree); - CharSequence methodName = - ElementUtils.getSimpleDescription(method); - ftu.failure( - arg, - "argument.type.incompatible", - "in varargs position", - methodName, - argType, - formatCat); - } - break; - } - } - } - break; - case ARRAY: - // III - if (!isWrappedFormatCall(fc, enclosingMethod)) { - ftu.warning(invc, "format.indirect.arguments"); - } - // TODO: If it is explict array construction, such as "new Object[] { - // ... }", then we could treat it like the VARARGS case, analyzing each - // argument. "new array" is probably rare, in the varargs position. - // fall through - case NULLARRAY: - for (ConversionCategory cat : formatCats) { - if (cat == ConversionCategory.NULL) { - // I.3 - if (invc.value() == FormatterTreeUtil.InvocationType.NULLARRAY) { - ftu.warning(invc, "format.specifier.null", ""); - } else { - ftu.failure(invc, "format.specifier.null", ""); - } - } - if (cat == ConversionCategory.UNUSED) { - // I.2 - ftu.warning(invc, "format.argument.unused", ""); - } - } - break; + switch (formatCat) { + case UNUSED: + // I.2 + ftu.warning(arg, "format.argument.unused", " " + (1 + i)); + break; + case NULL: + // I.3 + if (argType.getKind() == TypeKind.NULL) { + ftu.warning(arg, "format.specifier.null", " " + (1 + i)); + } else { + ftu.failure(arg, "format.specifier.null", " " + (1 + i)); + } + break; + case GENERAL: + break; + default: + if (!fc.isValidArgument(formatCat, argType)) { + // II.3 + ExecutableElement method = TreeUtils.elementFromUse(tree); + CharSequence methodName = ElementUtils.getSimpleDescription(method); + ftu.failure( + arg, + "argument.type.incompatible", + "in varargs position", + methodName, + argType, + formatCat); + } + break; } + } } - - /* NO-AFU - // Support -Ainfer command-line argument. - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - if (wpi != null && forwardsArguments(tree, enclosingMethod)) { - wpi.addMethodDeclarationAnnotation( - TreeUtils.elementFromDeclaration(enclosingMethod), - atypeFactory.FORMATMETHOD); - } - */ + break; + case ARRAY: + // III + if (!isWrappedFormatCall(fc, enclosingMethod)) { + ftu.warning(invc, "format.indirect.arguments"); + } + // TODO: If it is explict array construction, such as "new Object[] { + // ... }", then we could treat it like the VARARGS case, analyzing each + // argument. "new array" is probably rare, in the varargs position. + // fall through + case NULLARRAY: + for (ConversionCategory cat : formatCats) { + if (cat == ConversionCategory.NULL) { + // I.3 + if (invc.value() == FormatterTreeUtil.InvocationType.NULLARRAY) { + ftu.warning(invc, "format.specifier.null", ""); + } else { + ftu.failure(invc, "format.specifier.null", ""); + } + } + if (cat == ConversionCategory.UNUSED) { + // I.2 + ftu.warning(invc, "format.argument.unused", ""); + } + } + break; } - return super.visitMethodInvocation(tree, p); + } + + /* NO-AFU + // Support -Ainfer command-line argument. + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + if (wpi != null && forwardsArguments(tree, enclosingMethod)) { + wpi.addMethodDeclarationAnnotation( + TreeUtils.elementFromDeclaration(enclosingMethod), + atypeFactory.FORMATMETHOD); + } + */ } + return super.visitMethodInvocation(tree, p); + } - /** - * Returns true if {@code fc} is within a method m annotated as {@code @FormatMethod}, and fc's - * arguments are m's formal parameters. In other words, fc forwards m's arguments to another - * format method. - * - * @param fc an invocation of a format method - * @param enclosingMethod the method that contains the call - * @return true if {@code fc} is a call to a format method that forwards its containing method's - * arguments - */ - private boolean isWrappedFormatCall(FormatCall fc, @Nullable MethodTree enclosingMethod) { - if (enclosingMethod == null) { - return false; - } - ExecutableElement enclosingMethodElement = - TreeUtils.elementFromDeclaration(enclosingMethod); - boolean withinFormatMethod = - (atypeFactory.getDeclAnnotation(enclosingMethodElement, FormatMethod.class) - != null); - return withinFormatMethod && forwardsArguments(fc.invocationTree, enclosingMethod); + /** + * Returns true if {@code fc} is within a method m annotated as {@code @FormatMethod}, and fc's + * arguments are m's formal parameters. In other words, fc forwards m's arguments to another + * format method. + * + * @param fc an invocation of a format method + * @param enclosingMethod the method that contains the call + * @return true if {@code fc} is a call to a format method that forwards its containing method's + * arguments + */ + private boolean isWrappedFormatCall(FormatCall fc, @Nullable MethodTree enclosingMethod) { + if (enclosingMethod == null) { + return false; } + ExecutableElement enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); + boolean withinFormatMethod = + (atypeFactory.getDeclAnnotation(enclosingMethodElement, FormatMethod.class) != null); + return withinFormatMethod && forwardsArguments(fc.invocationTree, enclosingMethod); + } - /** - * Returns true if {@code invocationTree}'s arguments are {@code enclosingMethod}'s formal - * parameters. In other words, {@code invocationTree} forwards {@code enclosingMethod}'s - * arguments. - * - *

Only arguments from the first String formal parameter onward count. Returns false if there - * is no String formal parameter. - * - * @param invocationTree an invocation of a method - * @param enclosingMethod the method that contains the call - * @return true if {@code invocationTree} is a call to a method that forwards its containing - * method's arguments - */ - private boolean forwardsArguments( - MethodInvocationTree invocationTree, @Nullable MethodTree enclosingMethod) { + /** + * Returns true if {@code invocationTree}'s arguments are {@code enclosingMethod}'s formal + * parameters. In other words, {@code invocationTree} forwards {@code enclosingMethod}'s + * arguments. + * + *

Only arguments from the first String formal parameter onward count. Returns false if there + * is no String formal parameter. + * + * @param invocationTree an invocation of a method + * @param enclosingMethod the method that contains the call + * @return true if {@code invocationTree} is a call to a method that forwards its containing + * method's arguments + */ + private boolean forwardsArguments( + MethodInvocationTree invocationTree, @Nullable MethodTree enclosingMethod) { - if (enclosingMethod == null) { - return false; - } - - ExecutableElement enclosingMethodElement = - TreeUtils.elementFromDeclaration(enclosingMethod); - int paramIndex = formatStringIndex(enclosingMethodElement); - if (paramIndex == -1) { - return false; - } + if (enclosingMethod == null) { + return false; + } - ExecutableElement calledMethodElement = TreeUtils.elementFromUse(invocationTree); - int callIndex = formatStringIndex(calledMethodElement); - if (callIndex == -1) { - throw new UserError( - "Method " - + calledMethodElement - + " is annotated @FormatMethod but has no String formal parameter"); - } + ExecutableElement enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); + int paramIndex = formatStringIndex(enclosingMethodElement); + if (paramIndex == -1) { + return false; + } - List args = invocationTree.getArguments(); - List params = enclosingMethod.getParameters(); + ExecutableElement calledMethodElement = TreeUtils.elementFromUse(invocationTree); + int callIndex = formatStringIndex(calledMethodElement); + if (callIndex == -1) { + throw new UserError( + "Method " + + calledMethodElement + + " is annotated @FormatMethod but has no String formal parameter"); + } - if (params.size() - paramIndex != args.size() - callIndex) { - return false; - } - while (paramIndex < params.size()) { - ExpressionTree argTree = args.get(callIndex); - if (argTree.getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - VariableTree param = params.get(paramIndex); - if (param.getName() != ((IdentifierTree) argTree).getName()) { - return false; - } - paramIndex++; - callIndex++; - } + List args = invocationTree.getArguments(); + List params = enclosingMethod.getParameters(); - return true; + if (params.size() - paramIndex != args.size() - callIndex) { + return false; + } + while (paramIndex < params.size()) { + ExpressionTree argTree = args.get(callIndex); + if (argTree.getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + VariableTree param = params.get(paramIndex); + if (param.getName() != ((IdentifierTree) argTree).getName()) { + return false; + } + paramIndex++; + callIndex++; } - // TODO: Should this be the last String argument? That would require that every method - // annotated with @FormatMethod uses varargs syntax. - /** - * Returns the index of the format string of a method: the first formal parameter with declared - * type String. - * - * @param m a method - * @return the index of the last String formal parameter, or -1 if none - */ - public static int formatStringIndex(ExecutableElement m) { - List params = m.getParameters(); - for (int i = 0; i < params.size(); i++) { - if (TypesUtils.isString(params.get(i).asType())) { - return i; - } - } - return -1; + return true; + } + + // TODO: Should this be the last String argument? That would require that every method + // annotated with @FormatMethod uses varargs syntax. + /** + * Returns the index of the format string of a method: the first formal parameter with declared + * type String. + * + * @param m a method + * @return the index of the last String formal parameter, or -1 if none + */ + public static int formatStringIndex(ExecutableElement m) { + List params = m.getParameters(); + for (int i = 0; i < params.size(); i++) { + if (TypesUtils.isString(params.get(i).asType())) { + return i; + } } + return -1; + } - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); - AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); + AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); + AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); - // From the manual: "It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued." - // The format.missing.arguments warning is issued here for assignments. - // For method calls, it is issued in visitMethodInvocation. - if (rhs != null - && lhs != null - && AnnotationUtils.areSameByName(rhs, FormatterAnnotatedTypeFactory.FORMAT_NAME) - && AnnotationUtils.areSameByName(lhs, FormatterAnnotatedTypeFactory.FORMAT_NAME)) { - ConversionCategory[] rhsArgTypes = - atypeFactory.treeUtil.formatAnnotationToCategories(rhs); - ConversionCategory[] lhsArgTypes = - atypeFactory.treeUtil.formatAnnotationToCategories(lhs); + // From the manual: "It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued." + // The format.missing.arguments warning is issued here for assignments. + // For method calls, it is issued in visitMethodInvocation. + if (rhs != null + && lhs != null + && AnnotationUtils.areSameByName(rhs, FormatterAnnotatedTypeFactory.FORMAT_NAME) + && AnnotationUtils.areSameByName(lhs, FormatterAnnotatedTypeFactory.FORMAT_NAME)) { + ConversionCategory[] rhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(rhs); + ConversionCategory[] lhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(lhs); - if (rhsArgTypes.length < lhsArgTypes.length) { - checker.reportWarning( - valueTree, - "format.missing.arguments", - varType.toString(), - valueType.toString()); - result = false; - } - } - return result; + if (rhsArgTypes.length < lhsArgTypes.length) { + checker.reportWarning( + valueTree, "format.missing.arguments", varType.toString(), valueType.toString()); + result = false; + } } + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java b/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java index dd07607b67a..907f511a116 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.guieffect; +import java.lang.annotation.Annotation; import org.checkerframework.checker.guieffect.qual.PolyUIEffect; import org.checkerframework.checker.guieffect.qual.SafeEffect; import org.checkerframework.checker.guieffect.qual.UIEffect; @@ -7,120 +8,118 @@ import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; -import java.lang.annotation.Annotation; - /** An effect -- either UIEffect, PolyUIEffect, or SafeEffect. */ public final class Effect { - // Colin hates Java's comparable interface, so he's not using it - - private final Class annotClass; - - /** - * Create a new Effect object. - * - * @param cls one of UIEffect.class, PolyUIEffect.class, or SafeEffect.class - */ - public Effect(Class cls) { - assert cls == UIEffect.class || cls == PolyUIEffect.class || cls == SafeEffect.class; - annotClass = cls; + // Colin hates Java's comparable interface, so he's not using it + + private final Class annotClass; + + /** + * Create a new Effect object. + * + * @param cls one of UIEffect.class, PolyUIEffect.class, or SafeEffect.class + */ + public Effect(Class cls) { + assert cls == UIEffect.class || cls == PolyUIEffect.class || cls == SafeEffect.class; + annotClass = cls; + } + + /** + * Return true iff {@code left} is less than or equal to {@code right}. + * + * @param left the first effect to compare + * @param right the first effect to compare + * @return true iff {@code left} is less than or equal to {@code right} + */ + public static boolean lessThanOrEqualTo(Effect left, Effect right) { + assert (left != null && right != null); + boolean leftBottom = left.annotClass == SafeEffect.class; + boolean rightTop = right.annotClass == UIEffect.class; + return leftBottom || rightTop || left.annotClass == right.annotClass; + } + + public static Effect min(Effect l, Effect r) { + if (lessThanOrEqualTo(l, r)) { + return l; + } else { + return r; } + } - /** - * Return true iff {@code left} is less than or equal to {@code right}. - * - * @param left the first effect to compare - * @param right the first effect to compare - * @return true iff {@code left} is less than or equal to {@code right} - */ - public static boolean lessThanOrEqualTo(Effect left, Effect right) { - assert (left != null && right != null); - boolean leftBottom = left.annotClass == SafeEffect.class; - boolean rightTop = right.annotClass == UIEffect.class; - return leftBottom || rightTop || left.annotClass == right.annotClass; - } + public static final class EffectRange { + public final Effect min; + public final Effect max; - public static Effect min(Effect l, Effect r) { - if (lessThanOrEqualTo(l, r)) { - return l; - } else { - return r; - } + public EffectRange(@Nullable Effect min, @Nullable Effect max) { + assert (min != null || max != null); + // If one is null, fill in with the other + this.min = (min != null ? min : max); + this.max = (max != null ? max : min); } - - public static final class EffectRange { - public final Effect min; - public final Effect max; - - public EffectRange(@Nullable Effect min, @Nullable Effect max) { - assert (min != null || max != null); - // If one is null, fill in with the other - this.min = (min != null ? min : max); - this.max = (max != null ? max : min); - } + } + + /** + * Return true if this is SafeEffect. + * + * @return true if this is SafeEffect + */ + public boolean isSafe() { + return annotClass == SafeEffect.class; + } + + /** + * Return true if this is UIEffect. + * + * @return true if this is UIEffect + */ + public boolean isUI() { + return annotClass == UIEffect.class; + } + + /** + * Return true if this is PolyUIEffect. + * + * @return true if this is PolyUIEffect + */ + @Pure + public boolean isPoly() { + return annotClass == PolyUIEffect.class; + } + + public Class getAnnot() { + return annotClass; + } + + @SideEffectFree + @Override + public String toString() { + return annotClass.getSimpleName(); + } + + /** + * Return true if this equals the given effect. + * + * @param e the effect to compare this to + * @return true if this equals the given effect + */ + @SuppressWarnings("NonOverridingEquals") // TODO: clean this up! + public boolean equals(Effect e) { + return annotClass == e.annotClass; + } + + @Override + @SuppressWarnings("interning:not.interned") // equality + public boolean equals(@Nullable Object o) { + if (o instanceof Effect) { + return this.equals((Effect) o); + } else { + return this == o; } + } - /** - * Return true if this is SafeEffect. - * - * @return true if this is SafeEffect - */ - public boolean isSafe() { - return annotClass == SafeEffect.class; - } - - /** - * Return true if this is UIEffect. - * - * @return true if this is UIEffect - */ - public boolean isUI() { - return annotClass == UIEffect.class; - } - - /** - * Return true if this is PolyUIEffect. - * - * @return true if this is PolyUIEffect - */ - @Pure - public boolean isPoly() { - return annotClass == PolyUIEffect.class; - } - - public Class getAnnot() { - return annotClass; - } - - @SideEffectFree - @Override - public String toString() { - return annotClass.getSimpleName(); - } - - /** - * Return true if this equals the given effect. - * - * @param e the effect to compare this to - * @return true if this equals the given effect - */ - @SuppressWarnings("NonOverridingEquals") // TODO: clean this up! - public boolean equals(Effect e) { - return annotClass == e.annotClass; - } - - @Override - @SuppressWarnings("interning:not.interned") // equality - public boolean equals(@Nullable Object o) { - if (o instanceof Effect) { - return this.equals((Effect) o); - } else { - return this == o; - } - } - - @Pure - @Override - public int hashCode() { - return 31 + annotClass.hashCode(); - } + @Pure + @Override + public int hashCode() { + return 31 + annotClass.hashCode(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java index 4e1ed86028f..433175f42c6 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java @@ -8,7 +8,16 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.ParenthesizedTree; import com.sun.source.tree.Tree; - +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.guieffect.Effect.EffectRange; import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.PolyUI; @@ -34,610 +43,592 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** Annotated type factory for the GUI Effect Checker. */ public class GuiEffectTypeFactory extends BaseAnnotatedTypeFactory { - protected final boolean debugSpew; - - /** - * Keeps track of all lambda expressions with inferred UIEffect. - * - *

{@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} adds lambda - * expressions to this set, and is called from GuiEffectVisitor whenever a lambda expression - * calls a @UIEffect method. Afterwards {@link - * #getInferedEffectForLambdaExpression(LambdaExpressionTree) - * getInferedEffectForLambdaExpression} uses this set and the type annotations of the functional - * interface of the lambda to figure out if it can affect the UI or not. - */ - protected final Set uiLambdas = new HashSet<>(); - - /** - * Keeps track of all anonymous inner classes with inferred UIEffect. - * - *

{@link #constrainAnonymousClassToUI(TypeElement) constrainAnonymousClassToUI} adds - * anonymous inner classes to this set, and is called from GuiEffectVisitor whenever an - * anonymous inner class calls a @UIEffect method. Afterwards {@link #isUIType(TypeElement) - * isUIType} and {@link #getAnnotatedType(Tree) getAnnotatedType} will treat this inner class as - * if it had been annotated with @UI. - */ - protected final Set uiAnonClasses = new HashSet<>(); - - /** The @{@link AlwaysSafe} annotation. */ - protected final AnnotationMirror ALWAYSSAFE = - AnnotationBuilder.fromClass(elements, AlwaysSafe.class); - - /** The @{@link PolyUI} annotation. */ - protected final AnnotationMirror POLYUI = AnnotationBuilder.fromClass(elements, PolyUI.class); - - /** The @{@link UI} annotation. */ - protected final AnnotationMirror UI = AnnotationBuilder.fromClass(elements, UI.class); - - public GuiEffectTypeFactory(BaseTypeChecker checker, boolean spew) { - // use true to enable flow inference, false to disable it - super(checker, false); - - debugSpew = spew; - this.postInit(); + protected final boolean debugSpew; + + /** + * Keeps track of all lambda expressions with inferred UIEffect. + * + *

{@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} adds lambda + * expressions to this set, and is called from GuiEffectVisitor whenever a lambda expression calls + * a @UIEffect method. Afterwards {@link + * #getInferedEffectForLambdaExpression(LambdaExpressionTree) getInferedEffectForLambdaExpression} + * uses this set and the type annotations of the functional interface of the lambda to figure out + * if it can affect the UI or not. + */ + protected final Set uiLambdas = new HashSet<>(); + + /** + * Keeps track of all anonymous inner classes with inferred UIEffect. + * + *

{@link #constrainAnonymousClassToUI(TypeElement) constrainAnonymousClassToUI} adds anonymous + * inner classes to this set, and is called from GuiEffectVisitor whenever an anonymous inner + * class calls a @UIEffect method. Afterwards {@link #isUIType(TypeElement) isUIType} and {@link + * #getAnnotatedType(Tree) getAnnotatedType} will treat this inner class as if it had been + * annotated with @UI. + */ + protected final Set uiAnonClasses = new HashSet<>(); + + /** The @{@link AlwaysSafe} annotation. */ + protected final AnnotationMirror ALWAYSSAFE = + AnnotationBuilder.fromClass(elements, AlwaysSafe.class); + + /** The @{@link PolyUI} annotation. */ + protected final AnnotationMirror POLYUI = AnnotationBuilder.fromClass(elements, PolyUI.class); + + /** The @{@link UI} annotation. */ + protected final AnnotationMirror UI = AnnotationBuilder.fromClass(elements, UI.class); + + public GuiEffectTypeFactory(BaseTypeChecker checker, boolean spew) { + // use true to enable flow inference, false to disable it + super(checker, false); + + debugSpew = spew; + this.postInit(); + } + + /** + * Returns true if the given type is polymorphic. + * + * @param cls the type to test + * @return true if the given type is polymorphic + */ + public boolean isPolymorphicType(TypeElement cls) { + assert (cls != null); + return getDeclAnnotation(cls, PolyUIType.class) != null + || fromElement(cls).hasAnnotation(PolyUI.class); + } + + public boolean isUIType(TypeElement cls) { + if (debugSpew) { + System.err.println(" isUIType(" + cls + ")"); } + boolean targetClassUIP = fromElement(cls).hasAnnotation(UI.class); + AnnotationMirror targetClassUITypeP = getDeclAnnotation(cls, UIType.class); + AnnotationMirror targetClassSafeTypeP = getDeclAnnotation(cls, SafeType.class); - /** - * Returns true if the given type is polymorphic. - * - * @param cls the type to test - * @return true if the given type is polymorphic - */ - public boolean isPolymorphicType(TypeElement cls) { - assert (cls != null); - return getDeclAnnotation(cls, PolyUIType.class) != null - || fromElement(cls).hasAnnotation(PolyUI.class); + if (targetClassSafeTypeP != null) { + return false; // explicitly marked not a UI type } - public boolean isUIType(TypeElement cls) { - if (debugSpew) { - System.err.println(" isUIType(" + cls + ")"); - } - boolean targetClassUIP = fromElement(cls).hasAnnotation(UI.class); - AnnotationMirror targetClassUITypeP = getDeclAnnotation(cls, UIType.class); - AnnotationMirror targetClassSafeTypeP = getDeclAnnotation(cls, SafeType.class); - - if (targetClassSafeTypeP != null) { - return false; // explicitly marked not a UI type - } - - boolean hasUITypeDirectly = (targetClassUIP || targetClassUITypeP != null); - - if (hasUITypeDirectly) { - return true; - } - - // Anon inner classes should not inherit the package annotation, since - // they're so often used for closures to run async on background threads. - if (isAnonymousType(cls)) { - // However, we need to look into Anonymous class effect inference - if (uiAnonClasses.contains(cls)) { - return true; - } - return false; - } + boolean hasUITypeDirectly = (targetClassUIP || targetClassUITypeP != null); - // We don't check polymorphic annos so we can make a couple methods of - // an @UIType polymorphic explicitly - // AnnotationMirror targetClassPolyP = getDeclAnnotation(cls, PolyUI.class); - // AnnotationMirror targetClassPolyTypeP = getDeclAnnotation(cls, PolyUIType.class); - boolean targetClassSafeP = fromElement(cls).hasAnnotation(AlwaysSafe.class); - if (targetClassSafeP) { - return false; // explicitly annotated otherwise - } - - // Look for the package - Element packageP = ElementUtils.enclosingPackage(cls); - - if (packageP != null) { - if (debugSpew) { - System.err.println("Found package " + packageP); - } - if (getDeclAnnotation(packageP, UIPackage.class) != null) { - if (debugSpew) { - System.err.println("Package " + packageP + " is annotated @UIPackage"); - } - return true; - } - } + if (hasUITypeDirectly) { + return true; + } - return false; + // Anon inner classes should not inherit the package annotation, since + // they're so often used for closures to run async on background threads. + if (isAnonymousType(cls)) { + // However, we need to look into Anonymous class effect inference + if (uiAnonClasses.contains(cls)) { + return true; + } + return false; } - // TODO: is there a framework method for this? - private static boolean isAnonymousType(TypeElement elem) { - return elem.getSimpleName().length() == 0; + // We don't check polymorphic annos so we can make a couple methods of + // an @UIType polymorphic explicitly + // AnnotationMirror targetClassPolyP = getDeclAnnotation(cls, PolyUI.class); + // AnnotationMirror targetClassPolyTypeP = getDeclAnnotation(cls, PolyUIType.class); + boolean targetClassSafeP = fromElement(cls).hasAnnotation(AlwaysSafe.class); + if (targetClassSafeP) { + return false; // explicitly annotated otherwise } - /** - * Calling context annotations. - * - *

To make anon-inner-classes work, I need to climb the inheritance DAG, until I: - * - *

    - *
  • find the class/interface that declares this calling method (an anon inner class is a - * separate class that implements an interface) - *
  • check whether *that* declaration specifies @UI on either the type or method - *
- * - * A method has the UI effect when: - * - *
    - *
  1. A method is UI if annotated @UIEffect - *
  2. A method is UI if the enclosing class is annotated @UI or @UIType and the method is not - * annotated @AlwaysSafe - *
  3. A method is UI if the corresponding method in the super-class/interface is UI, and this - * method is not annotated @AlwaysSafe, and this method resides in an anonymous inner - * class (named classes still require a package/class/method annotation to make it UI, - * only anon inner classes have this inheritance-by-default) - *
      - *
    • A method must be *annotated* UI if the method it overrides is *annotated* UI - *
    • A method must be *annotated* UI if it overrides a UI method and the enclosing - * class is not UI - *
    - *
  4. It is an error if a method is UI but the same method in a super-type is not UI - *
  5. It is an error if two super-types specify the same method, where one type says it's UI - * and one says it's not (it's possible to simply enforce the weaker (safe) effect, but - * this seems more principled, it's easier --- backwards-compatible --- to change our - * minds about this later) - *
- */ - public Effect getDeclaredEffect(ExecutableElement methodElt) { - if (debugSpew) { - System.err.println("begin mayHaveUIEffect(" + methodElt + ")"); - } - AnnotationMirror targetUIP = getDeclAnnotation(methodElt, UIEffect.class); - AnnotationMirror targetSafeP = getDeclAnnotation(methodElt, SafeEffect.class); - AnnotationMirror targetPolyP = getDeclAnnotation(methodElt, PolyUIEffect.class); - TypeElement targetClassElt = (TypeElement) methodElt.getEnclosingElement(); - boolean hasMainThreadAnnot = - getDeclAnnotations(methodElt) - .toString() - .contains("@android.support.annotation.MainThread"); + // Look for the package + Element packageP = ElementUtils.enclosingPackage(cls); + if (packageP != null) { + if (debugSpew) { + System.err.println("Found package " + packageP); + } + if (getDeclAnnotation(packageP, UIPackage.class) != null) { if (debugSpew) { - System.err.println("targetClassElt found"); - } - - // Short-circuit if the method is explicitly annotated - if (targetSafeP != null) { - if (debugSpew) { - System.err.println("Method marked @SafeEffect"); - } - return new Effect(SafeEffect.class); - } else if (targetUIP != null || hasMainThreadAnnot) { - if (debugSpew) { - System.err.println("Method marked @UIEffect"); - } - return new Effect(UIEffect.class); - } else if (targetPolyP != null) { - if (debugSpew) { - System.err.println("Method marked @PolyUIEffect"); - } - return new Effect(PolyUIEffect.class); + System.err.println("Package " + packageP + " is annotated @UIPackage"); } + return true; + } + } - // The method is not explicitly annotated, so check class and package annotations, - // and supertype effects if in an anonymous inner class + return false; + } + + // TODO: is there a framework method for this? + private static boolean isAnonymousType(TypeElement elem) { + return elem.getSimpleName().length() == 0; + } + + /** + * Calling context annotations. + * + *

To make anon-inner-classes work, I need to climb the inheritance DAG, until I: + * + *

    + *
  • find the class/interface that declares this calling method (an anon inner class is a + * separate class that implements an interface) + *
  • check whether *that* declaration specifies @UI on either the type or method + *
+ * + * A method has the UI effect when: + * + *
    + *
  1. A method is UI if annotated @UIEffect + *
  2. A method is UI if the enclosing class is annotated @UI or @UIType and the method is not + * annotated @AlwaysSafe + *
  3. A method is UI if the corresponding method in the super-class/interface is UI, and this + * method is not annotated @AlwaysSafe, and this method resides in an anonymous inner class + * (named classes still require a package/class/method annotation to make it UI, only anon + * inner classes have this inheritance-by-default) + *
      + *
    • A method must be *annotated* UI if the method it overrides is *annotated* UI + *
    • A method must be *annotated* UI if it overrides a UI method and the enclosing class + * is not UI + *
    + *
  4. It is an error if a method is UI but the same method in a super-type is not UI + *
  5. It is an error if two super-types specify the same method, where one type says it's UI + * and one says it's not (it's possible to simply enforce the weaker (safe) effect, but this + * seems more principled, it's easier --- backwards-compatible --- to change our minds about + * this later) + *
+ */ + public Effect getDeclaredEffect(ExecutableElement methodElt) { + if (debugSpew) { + System.err.println("begin mayHaveUIEffect(" + methodElt + ")"); + } + AnnotationMirror targetUIP = getDeclAnnotation(methodElt, UIEffect.class); + AnnotationMirror targetSafeP = getDeclAnnotation(methodElt, SafeEffect.class); + AnnotationMirror targetPolyP = getDeclAnnotation(methodElt, PolyUIEffect.class); + TypeElement targetClassElt = (TypeElement) methodElt.getEnclosingElement(); + boolean hasMainThreadAnnot = + getDeclAnnotations(methodElt).toString().contains("@android.support.annotation.MainThread"); + + if (debugSpew) { + System.err.println("targetClassElt found"); + } - if (isUIType(targetClassElt)) { - // Already checked, no explicit @SafeEffect annotation - return new Effect(UIEffect.class); - } + // Short-circuit if the method is explicitly annotated + if (targetSafeP != null) { + if (debugSpew) { + System.err.println("Method marked @SafeEffect"); + } + return new Effect(SafeEffect.class); + } else if (targetUIP != null || hasMainThreadAnnot) { + if (debugSpew) { + System.err.println("Method marked @UIEffect"); + } + return new Effect(UIEffect.class); + } else if (targetPolyP != null) { + if (debugSpew) { + System.err.println("Method marked @PolyUIEffect"); + } + return new Effect(PolyUIEffect.class); + } - // Anonymous inner types should just get the effect of the parent by default, rather than - // annotating every instance. Unless it's implementing a polymorphic supertype, in which - // case we still want the developer to be explicit. - if (isAnonymousType(targetClassElt)) { - boolean canInheritParentEffects = true; // Refine this for polymorphic parents - DeclaredType directSuper = (DeclaredType) targetClassElt.getSuperclass(); - TypeElement superElt = (TypeElement) directSuper.asElement(); - // Anonymous subtypes of polymorphic classes other than object can't inherit - if (getDeclAnnotation(superElt, PolyUIType.class) != null - && !TypesUtils.isObject(directSuper)) { - canInheritParentEffects = false; - } else { - for (TypeMirror ifaceM : targetClassElt.getInterfaces()) { - DeclaredType iface = (DeclaredType) ifaceM; - TypeElement ifaceElt = (TypeElement) iface.asElement(); - if (getDeclAnnotation(ifaceElt, PolyUIType.class) != null) { - canInheritParentEffects = false; - } - } - } - - if (canInheritParentEffects) { - EffectRange r = findInheritedEffectRange(targetClassElt, methodElt); - return (r != null ? Effect.min(r.min, r.max) : new Effect(SafeEffect.class)); - } - } + // The method is not explicitly annotated, so check class and package annotations, + // and supertype effects if in an anonymous inner class - return new Effect(SafeEffect.class); + if (isUIType(targetClassElt)) { + // Already checked, no explicit @SafeEffect annotation + return new Effect(UIEffect.class); } - /** - * Get the effect of a method call at its callsite, acknowledging polymorphic instantiation - * using type use annotations. - * - * @param tree the method invocation as an AST node - * @param callerReceiver the type of the receiver object if available. Used to resolve direct - * calls like "super()" - * @param methodElt the element of the callee method - * @return the computed effect (SafeEffect or UIEffect) for the method call - */ - public Effect getComputedEffectAtCallsite( - MethodInvocationTree tree, - AnnotatedTypeMirror.AnnotatedDeclaredType callerReceiver, - ExecutableElement methodElt) { - Effect targetEffect = getDeclaredEffect(methodElt); - if (targetEffect.isPoly()) { - AnnotatedTypeMirror srcType = null; - if (tree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { - ExpressionTree src = ((MemberSelectTree) tree.getMethodSelect()).getExpression(); - srcType = getAnnotatedType(src); - } else if (tree.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { - // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()" - if (callerReceiver == null) { - // Not enought information provided to instantiate this type-polymorphic effects - return targetEffect; - } - srcType = callerReceiver; - } else { - throw new TypeSystemError("Unexpected getMethodSelect() kind at callsite " + tree); - } - - // Instantiate type-polymorphic effects - if (srcType.hasAnnotation(AlwaysSafe.class)) { - targetEffect = new Effect(SafeEffect.class); - } else if (srcType.hasAnnotation(UI.class)) { - targetEffect = new Effect(UIEffect.class); - } - // Poly substitution would be a noop. + // Anonymous inner types should just get the effect of the parent by default, rather than + // annotating every instance. Unless it's implementing a polymorphic supertype, in which + // case we still want the developer to be explicit. + if (isAnonymousType(targetClassElt)) { + boolean canInheritParentEffects = true; // Refine this for polymorphic parents + DeclaredType directSuper = (DeclaredType) targetClassElt.getSuperclass(); + TypeElement superElt = (TypeElement) directSuper.asElement(); + // Anonymous subtypes of polymorphic classes other than object can't inherit + if (getDeclAnnotation(superElt, PolyUIType.class) != null + && !TypesUtils.isObject(directSuper)) { + canInheritParentEffects = false; + } else { + for (TypeMirror ifaceM : targetClassElt.getInterfaces()) { + DeclaredType iface = (DeclaredType) ifaceM; + TypeElement ifaceElt = (TypeElement) iface.asElement(); + if (getDeclAnnotation(ifaceElt, PolyUIType.class) != null) { + canInheritParentEffects = false; + } } - return targetEffect; - } + } - /** - * Get the inferred effect of a lambda expression based on the type annotations of its - * functional interface and the effects of the calls in its body. - * - *

This relies on GuiEffectVisitor to perform the actual inference step and mark lambdas - * with @PolyUIEffect functional interfaces as being explicitly UI-affecting using the {@link - * #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} method. - * - * @param lambdaTree a lambda expression's AST node - * @return the inferred effect of the lambda - */ - public Effect getInferedEffectForLambdaExpression(LambdaExpressionTree lambdaTree) { - // @UI type if annotated on the lambda expression explicitly - if (uiLambdas.contains(lambdaTree)) { - return new Effect(UIEffect.class); - } - ExecutableElement functionalInterfaceMethodElt = - TreeUtils.findFunction(lambdaTree, checker.getProcessingEnvironment()); - if (debugSpew) { - System.err.println("functionalInterfaceMethodElt found for lambda"); - } - return getDeclaredEffect(functionalInterfaceMethodElt); + if (canInheritParentEffects) { + EffectRange r = findInheritedEffectRange(targetClassElt, methodElt); + return (r != null ? Effect.min(r.min, r.max) : new Effect(SafeEffect.class)); + } } - /** - * Test if this tree corresponds to a lambda expression or new class marked as UI affecting by - * either {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI}} or {@link - * #constrainAnonymousClassToUI(TypeElement)}. Only explicit markings due to inference are - * considered here, for the properly computed type of the expression, use {@link - * #getAnnotatedType(Tree)} instead. - * - * @param tree the tree to check - * @return whether it is a lambda expression or new class marked as UI by inference - */ - public boolean isDirectlyMarkedUIThroughInference(Tree tree) { - if (tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - return uiLambdas.contains((LambdaExpressionTree) tree); - } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { - AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); - if (typeMirror.getKind() == TypeKind.DECLARED) { - return uiAnonClasses.contains( - ((DeclaredType) typeMirror.getUnderlyingType()).asElement()); - } + return new Effect(SafeEffect.class); + } + + /** + * Get the effect of a method call at its callsite, acknowledging polymorphic instantiation using + * type use annotations. + * + * @param tree the method invocation as an AST node + * @param callerReceiver the type of the receiver object if available. Used to resolve direct + * calls like "super()" + * @param methodElt the element of the callee method + * @return the computed effect (SafeEffect or UIEffect) for the method call + */ + public Effect getComputedEffectAtCallsite( + MethodInvocationTree tree, + AnnotatedTypeMirror.AnnotatedDeclaredType callerReceiver, + ExecutableElement methodElt) { + Effect targetEffect = getDeclaredEffect(methodElt); + if (targetEffect.isPoly()) { + AnnotatedTypeMirror srcType = null; + if (tree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { + ExpressionTree src = ((MemberSelectTree) tree.getMethodSelect()).getExpression(); + srcType = getAnnotatedType(src); + } else if (tree.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { + // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()" + if (callerReceiver == null) { + // Not enought information provided to instantiate this type-polymorphic effects + return targetEffect; } - return false; + srcType = callerReceiver; + } else { + throw new TypeSystemError("Unexpected getMethodSelect() kind at callsite " + tree); + } + + // Instantiate type-polymorphic effects + if (srcType.hasAnnotation(AlwaysSafe.class)) { + targetEffect = new Effect(SafeEffect.class); + } else if (srcType.hasAnnotation(UI.class)) { + targetEffect = new Effect(UIEffect.class); + } + // Poly substitution would be a noop. } - - @Override - public AnnotatedTypeMirror getAnnotatedType(Tree tree) { - AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); - if (typeMirror.hasAnnotation(UI.class)) { - return typeMirror; - } - // Check if this an @UI anonymous class or lambda due to inference, or an expression - // containing such class/lambda - if (isDirectlyMarkedUIThroughInference(tree)) { - typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); - } else if (tree.getKind() == Tree.Kind.PARENTHESIZED) { - ParenthesizedTree parenthesizedTree = (ParenthesizedTree) tree; - return this.getAnnotatedType(parenthesizedTree.getExpression()); - } else if (tree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { - ConditionalExpressionTree cet = (ConditionalExpressionTree) tree; - boolean isTrueOperandUI = - (cet.getTrueExpression() != null - && this.getAnnotatedType(cet.getTrueExpression()) - .hasAnnotation(UI.class)); - boolean isFalseOperandUI = - (cet.getFalseExpression() != null - && this.getAnnotatedType(cet.getFalseExpression()) - .hasAnnotation(UI.class)); - if (isTrueOperandUI || isFalseOperandUI) { - typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); - } - } - // TODO: Do we need to support other expression here? - // (i.e. are there any other operators that take new or lambda expressions as operands) - return typeMirror; + return targetEffect; + } + + /** + * Get the inferred effect of a lambda expression based on the type annotations of its functional + * interface and the effects of the calls in its body. + * + *

This relies on GuiEffectVisitor to perform the actual inference step and mark lambdas + * with @PolyUIEffect functional interfaces as being explicitly UI-affecting using the {@link + * #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} method. + * + * @param lambdaTree a lambda expression's AST node + * @return the inferred effect of the lambda + */ + public Effect getInferedEffectForLambdaExpression(LambdaExpressionTree lambdaTree) { + // @UI type if annotated on the lambda expression explicitly + if (uiLambdas.contains(lambdaTree)) { + return new Effect(UIEffect.class); } - - // Only the visitMethod call should pass true for warnings - public EffectRange findInheritedEffectRange( - TypeElement declaringType, ExecutableElement overridingMethod) { - return findInheritedEffectRange(declaringType, overridingMethod, false, null); + ExecutableElement functionalInterfaceMethodElt = + TreeUtils.findFunction(lambdaTree, checker.getProcessingEnvironment()); + if (debugSpew) { + System.err.println("functionalInterfaceMethodElt found for lambda"); } - - /** - * Find the greatest and least effects of methods the specified definition overrides. This - * method is used for two reasons: - * - *

1. {@link GuiEffectVisitor#visitMethod(MethodTree,Void) GuiEffectVisitor.visitMethod} - * calls this to perform an effect override check (that a method's effect is less than or equal - * to the effect of any method it overrides). This use passes {@code true} for the {@code - * issueConflictWarning} in order to trigger warning messages. - * - *

2. {@link #getDeclaredEffect(ExecutableElement) getDeclaredEffect} in this class uses this - * to infer the default effect of methods in anonymous inner classes. This use passes {@code - * false} for {@code issueConflictWarning}, because it only needs the return value. - * - * @param declaringType the type declaring the override - * @param overridingMethod the method override itself - * @param issueConflictWarning whether or not to issue warnings - * @param errorTree the method declaration AST node; used for reporting errors - * @return the min and max inherited effects, or null if none were discovered - */ - public @Nullable EffectRange findInheritedEffectRange( - TypeElement declaringType, - ExecutableElement overridingMethod, - boolean issueConflictWarning, - Tree errorTree) { - assert (declaringType != null); - ExecutableElement uiOverridden = null; - ExecutableElement safeOverridden = null; - ExecutableElement polyOverridden = null; - - // We must account for explicit annotation, type declaration annotations, and package - // annotations. - boolean isUI = - (getDeclAnnotation(overridingMethod, UIEffect.class) != null - || isUIType(declaringType)) - && getDeclAnnotation(overridingMethod, SafeEffect.class) == null; - boolean isPolyUI = getDeclAnnotation(overridingMethod, PolyUIEffect.class) != null; - - // Check for invalid overrides. - // AnnotatedTypes.overriddenMethods retrieves all transitive definitions overridden by this - // declaration. - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, this, overridingMethod); - - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); - AnnotatedTypeMirror.AnnotatedExecutableType overriddenMethod = - AnnotatedTypes.asMemberOf(types, this, overriddenType, pair.getValue()); - ExecutableElement overriddenMethodElt = pair.getValue(); - if (debugSpew) { - System.err.println( - "Found " - + declaringType - + "::" - + overridingMethod - + " overrides " - + overriddenType - + "::" - + overriddenMethod); - } - Effect eff = getDeclaredEffect(overriddenMethodElt); - if (eff.isSafe()) { - safeOverridden = overriddenMethodElt; - if (isUI) { - checker.reportError( - errorTree, - "override.effect.invalid", - declaringType, - overridingMethod, - overriddenType, - safeOverridden); - } else if (isPolyUI) { - checker.reportError( - errorTree, - "override.effect.invalid.polymorphic", - declaringType, - overridingMethod, - overriddenType, - safeOverridden); - } - } else if (eff.isUI()) { - uiOverridden = overriddenMethodElt; - } else { - assert eff.isPoly(); - polyOverridden = overriddenMethodElt; - if (isUI) { - // Need to special case an anonymous class with @UI on the decl, because - // "new @UI Runnable {...}" - // parses as @UI on an anon class decl extending Runnable - boolean isAnonInstantiation = - isAnonymousType(declaringType) - && (fromElement(declaringType).hasAnnotation(UI.class) - || uiAnonClasses.contains(declaringType)); - if (!isAnonInstantiation && !overriddenType.hasAnnotation(UI.class)) { - checker.reportError( - errorTree, - "override.effect.invalid.nonui", - declaringType, - overridingMethod, - overriddenType, - polyOverridden); - } - } - } - } - - // We don't need to issue warnings for overriding both poly and a concrete effect. - if (uiOverridden != null && safeOverridden != null && issueConflictWarning) { - // There may be more than two parent methods, but for now it's - // enough to know there are at least 2 in conflict. - checker.reportWarning( - errorTree, - "override.effect.warning.inheritance", - declaringType, - overridingMethod, - uiOverridden.getEnclosingElement().asType(), - uiOverridden, - safeOverridden.getEnclosingElement().asType(), - safeOverridden); - } - - Effect min = - (safeOverridden != null - ? new Effect(SafeEffect.class) - : (polyOverridden != null - ? new Effect(PolyUIEffect.class) - : (uiOverridden != null ? new Effect(UIEffect.class) : null))); - Effect max = - (uiOverridden != null - ? new Effect(UIEffect.class) - : (polyOverridden != null - ? new Effect(PolyUIEffect.class) - : (safeOverridden != null ? new Effect(SafeEffect.class) : null))); - if (debugSpew) { - System.err.println( - "Found " - + declaringType - + "." - + overridingMethod - + " to have inheritance pair (" - + min - + "," - + max - + ")"); + return getDeclaredEffect(functionalInterfaceMethodElt); + } + + /** + * Test if this tree corresponds to a lambda expression or new class marked as UI affecting by + * either {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI}} or {@link + * #constrainAnonymousClassToUI(TypeElement)}. Only explicit markings due to inference are + * considered here, for the properly computed type of the expression, use {@link + * #getAnnotatedType(Tree)} instead. + * + * @param tree the tree to check + * @return whether it is a lambda expression or new class marked as UI by inference + */ + public boolean isDirectlyMarkedUIThroughInference(Tree tree) { + if (tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + return uiLambdas.contains((LambdaExpressionTree) tree); + } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { + AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); + if (typeMirror.getKind() == TypeKind.DECLARED) { + return uiAnonClasses.contains(((DeclaredType) typeMirror.getUnderlyingType()).asElement()); + } + } + return false; + } + + @Override + public AnnotatedTypeMirror getAnnotatedType(Tree tree) { + AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); + if (typeMirror.hasAnnotation(UI.class)) { + return typeMirror; + } + // Check if this an @UI anonymous class or lambda due to inference, or an expression + // containing such class/lambda + if (isDirectlyMarkedUIThroughInference(tree)) { + typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); + } else if (tree.getKind() == Tree.Kind.PARENTHESIZED) { + ParenthesizedTree parenthesizedTree = (ParenthesizedTree) tree; + return this.getAnnotatedType(parenthesizedTree.getExpression()); + } else if (tree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { + ConditionalExpressionTree cet = (ConditionalExpressionTree) tree; + boolean isTrueOperandUI = + (cet.getTrueExpression() != null + && this.getAnnotatedType(cet.getTrueExpression()).hasAnnotation(UI.class)); + boolean isFalseOperandUI = + (cet.getFalseExpression() != null + && this.getAnnotatedType(cet.getFalseExpression()).hasAnnotation(UI.class)); + if (isTrueOperandUI || isFalseOperandUI) { + typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); + } + } + // TODO: Do we need to support other expression here? + // (i.e. are there any other operators that take new or lambda expressions as operands) + return typeMirror; + } + + // Only the visitMethod call should pass true for warnings + public EffectRange findInheritedEffectRange( + TypeElement declaringType, ExecutableElement overridingMethod) { + return findInheritedEffectRange(declaringType, overridingMethod, false, null); + } + + /** + * Find the greatest and least effects of methods the specified definition overrides. This method + * is used for two reasons: + * + *

1. {@link GuiEffectVisitor#visitMethod(MethodTree,Void) GuiEffectVisitor.visitMethod} calls + * this to perform an effect override check (that a method's effect is less than or equal to the + * effect of any method it overrides). This use passes {@code true} for the {@code + * issueConflictWarning} in order to trigger warning messages. + * + *

2. {@link #getDeclaredEffect(ExecutableElement) getDeclaredEffect} in this class uses this + * to infer the default effect of methods in anonymous inner classes. This use passes {@code + * false} for {@code issueConflictWarning}, because it only needs the return value. + * + * @param declaringType the type declaring the override + * @param overridingMethod the method override itself + * @param issueConflictWarning whether or not to issue warnings + * @param errorTree the method declaration AST node; used for reporting errors + * @return the min and max inherited effects, or null if none were discovered + */ + public @Nullable EffectRange findInheritedEffectRange( + TypeElement declaringType, + ExecutableElement overridingMethod, + boolean issueConflictWarning, + Tree errorTree) { + assert (declaringType != null); + ExecutableElement uiOverridden = null; + ExecutableElement safeOverridden = null; + ExecutableElement polyOverridden = null; + + // We must account for explicit annotation, type declaration annotations, and package + // annotations. + boolean isUI = + (getDeclAnnotation(overridingMethod, UIEffect.class) != null || isUIType(declaringType)) + && getDeclAnnotation(overridingMethod, SafeEffect.class) == null; + boolean isPolyUI = getDeclAnnotation(overridingMethod, PolyUIEffect.class) != null; + + // Check for invalid overrides. + // AnnotatedTypes.overriddenMethods retrieves all transitive definitions overridden by this + // declaration. + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, this, overridingMethod); + + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); + AnnotatedTypeMirror.AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf(types, this, overriddenType, pair.getValue()); + ExecutableElement overriddenMethodElt = pair.getValue(); + if (debugSpew) { + System.err.println( + "Found " + + declaringType + + "::" + + overridingMethod + + " overrides " + + overriddenType + + "::" + + overriddenMethod); + } + Effect eff = getDeclaredEffect(overriddenMethodElt); + if (eff.isSafe()) { + safeOverridden = overriddenMethodElt; + if (isUI) { + checker.reportError( + errorTree, + "override.effect.invalid", + declaringType, + overridingMethod, + overriddenType, + safeOverridden); + } else if (isPolyUI) { + checker.reportError( + errorTree, + "override.effect.invalid.polymorphic", + declaringType, + overridingMethod, + overriddenType, + safeOverridden); } - - if (min == null && max == null) { - return null; - } else { - return new EffectRange(min, max); + } else if (eff.isUI()) { + uiOverridden = overriddenMethodElt; + } else { + assert eff.isPoly(); + polyOverridden = overriddenMethodElt; + if (isUI) { + // Need to special case an anonymous class with @UI on the decl, because + // "new @UI Runnable {...}" + // parses as @UI on an anon class decl extending Runnable + boolean isAnonInstantiation = + isAnonymousType(declaringType) + && (fromElement(declaringType).hasAnnotation(UI.class) + || uiAnonClasses.contains(declaringType)); + if (!isAnonInstantiation && !overriddenType.hasAnnotation(UI.class)) { + checker.reportError( + errorTree, + "override.effect.invalid.nonui", + declaringType, + overridingMethod, + overriddenType, + polyOverridden); + } } + } } - @Override - protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { - return qualHierarchy.getBottomAnnotations(); + // We don't need to issue warnings for overriding both poly and a concrete effect. + if (uiOverridden != null && safeOverridden != null && issueConflictWarning) { + // There may be more than two parent methods, but for now it's + // enough to know there are at least 2 in conflict. + checker.reportWarning( + errorTree, + "override.effect.warning.inheritance", + declaringType, + overridingMethod, + uiOverridden.getEnclosingElement().asType(), + uiOverridden, + safeOverridden.getEnclosingElement().asType(), + safeOverridden); } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new GuiEffectTreeAnnotator()); + Effect min = + (safeOverridden != null + ? new Effect(SafeEffect.class) + : (polyOverridden != null + ? new Effect(PolyUIEffect.class) + : (uiOverridden != null ? new Effect(UIEffect.class) : null))); + Effect max = + (uiOverridden != null + ? new Effect(UIEffect.class) + : (polyOverridden != null + ? new Effect(PolyUIEffect.class) + : (safeOverridden != null ? new Effect(SafeEffect.class) : null))); + if (debugSpew) { + System.err.println( + "Found " + + declaringType + + "." + + overridingMethod + + " to have inheritance pair (" + + min + + "," + + max + + ")"); } - /** - * Force the given lambda expression to have UIEffect. - * - *

Used by GuiEffectVisitor to mark as UIEffect all lambdas that perform UIEffect calls - * inside their bodies. - * - * @param lambdaExpressionTree a lambda expression's AST node - */ - public void constrainLambdaToUI(LambdaExpressionTree lambdaExpressionTree) { - uiLambdas.add(lambdaExpressionTree); + if (min == null && max == null) { + return null; + } else { + return new EffectRange(min, max); } - - /** - * Force the given anonymous inner class to be an @UI instantiation of its base class. - * - *

Used by GuiEffectVisitor to mark as @UI all anonymous inner classes which: inherit from a - * PolyUIType annotated superclass, override a PolyUIEffect method from said superclass, and - * perform UIEffect calls inside the body of this method. - * - * @param classElt the TypeElement corresponding to the anonymous inner class to mark as an @UI - * instantiation of an UI-polymorphic superclass. - */ - public void constrainAnonymousClassToUI(TypeElement classElt) { - assert TypesUtils.isAnonymous(classElt.asType()); - uiAnonClasses.add(classElt); + } + + @Override + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + return qualHierarchy.getBottomAnnotations(); + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new GuiEffectTreeAnnotator()); + } + + /** + * Force the given lambda expression to have UIEffect. + * + *

Used by GuiEffectVisitor to mark as UIEffect all lambdas that perform UIEffect calls inside + * their bodies. + * + * @param lambdaExpressionTree a lambda expression's AST node + */ + public void constrainLambdaToUI(LambdaExpressionTree lambdaExpressionTree) { + uiLambdas.add(lambdaExpressionTree); + } + + /** + * Force the given anonymous inner class to be an @UI instantiation of its base class. + * + *

Used by GuiEffectVisitor to mark as @UI all anonymous inner classes which: inherit from a + * PolyUIType annotated superclass, override a PolyUIEffect method from said superclass, and + * perform UIEffect calls inside the body of this method. + * + * @param classElt the TypeElement corresponding to the anonymous inner class to mark as an @UI + * instantiation of an UI-polymorphic superclass. + */ + public void constrainAnonymousClassToUI(TypeElement classElt) { + assert TypesUtils.isAnonymous(classElt.asType()); + uiAnonClasses.add(classElt); + } + + /** A class for adding annotations based on tree. */ + private class GuiEffectTreeAnnotator extends TreeAnnotator { + + GuiEffectTreeAnnotator() { + super(GuiEffectTypeFactory.this); } - /** A class for adding annotations based on tree. */ - private class GuiEffectTreeAnnotator extends TreeAnnotator { - - GuiEffectTreeAnnotator() { - super(GuiEffectTypeFactory.this); - } + /* + public boolean hasExplicitUIEffect(ExecutableElement methElt) { + return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, UIEffect.class) != null; + } - /* - public boolean hasExplicitUIEffect(ExecutableElement methElt) { - return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, UIEffect.class) != null; - } + public boolean hasExplicitSafeEffect(ExecutableElement methElt) { + return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, SafeEffect.class) != null; + } - public boolean hasExplicitSafeEffect(ExecutableElement methElt) { - return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, SafeEffect.class) != null; - } + public boolean hasExplicitPolyUIEffect(ExecutableElement methElt) { + return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, PolyUIEffect.class) != null; + } - public boolean hasExplicitPolyUIEffect(ExecutableElement methElt) { - return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, PolyUIEffect.class) != null; - } + public boolean hasExplicitEffect(ExecutableElement methElt) { + return hasExplicitUIEffect(methElt) + || hasExplicitSafeEffect(methElt) + || hasExplicitPolyUIEffect(methElt); + } + */ - public boolean hasExplicitEffect(ExecutableElement methElt) { - return hasExplicitUIEffect(methElt) - || hasExplicitSafeEffect(methElt) - || hasExplicitPolyUIEffect(methElt); - } - */ - - @Override - public Void visitMethod(MethodTree tree, AnnotatedTypeMirror type) { - AnnotatedTypeMirror.AnnotatedExecutableType methType = - (AnnotatedTypeMirror.AnnotatedExecutableType) type; - // Effect e = getDeclaredEffect(methType.getElement()); - TypeElement cls = (TypeElement) methType.getElement().getEnclosingElement(); - - // STEP 1: Get the method effect annotation - // if (!hasExplicitEffect(methType.getElement())) { - // TODO: This line does nothing! - // AnnotatedTypeMirror.addAnnotation silently ignores non-qualifier annotations! - // We should be digging up the /declaration/ of the method, and annotating that. - // methType.addAnnotation(e.getAnnot()); - // } - - // STEP 2: Fix up the method receiver annotation - AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = methType.getReceiverType(); - if (receiverType != null && !receiverType.hasAnnotationInHierarchy(UI)) { - receiverType.addAnnotation( - isPolymorphicType(cls) - ? POLYUI - : fromElement(cls).hasAnnotation(UI.class) ? UI : ALWAYSSAFE); - } - return super.visitMethod(tree, type); - } + @Override + public Void visitMethod(MethodTree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror.AnnotatedExecutableType methType = + (AnnotatedTypeMirror.AnnotatedExecutableType) type; + // Effect e = getDeclaredEffect(methType.getElement()); + TypeElement cls = (TypeElement) methType.getElement().getEnclosingElement(); + + // STEP 1: Get the method effect annotation + // if (!hasExplicitEffect(methType.getElement())) { + // TODO: This line does nothing! + // AnnotatedTypeMirror.addAnnotation silently ignores non-qualifier annotations! + // We should be digging up the /declaration/ of the method, and annotating that. + // methType.addAnnotation(e.getAnnot()); + // } + + // STEP 2: Fix up the method receiver annotation + AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = methType.getReceiverType(); + if (receiverType != null && !receiverType.hasAnnotationInHierarchy(UI)) { + receiverType.addAnnotation( + isPolymorphicType(cls) + ? POLYUI + : fromElement(cls).hasAnnotation(UI.class) ? UI : ALWAYSSAFE); + } + return super.visitMethod(tree, type); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java index 36566bfd896..5a295a5ffe3 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java @@ -11,7 +11,14 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.ArrayDeque; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.PolyUI; import org.checkerframework.checker.guieffect.qual.PolyUIEffect; @@ -34,583 +41,561 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.ArrayDeque; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; - /** Require that only UI code invokes code with the UI effect. */ public class GuiEffectVisitor extends BaseTypeVisitor { - /** The type of the class currently being visited. */ - private @Nullable AnnotatedDeclaredType classType = null; - - /** The receiver type of the enclosing method tree. */ - private @Nullable AnnotatedDeclaredType receiverType = null; + /** The type of the class currently being visited. */ + private @Nullable AnnotatedDeclaredType classType = null; - /** Whether or not to display debugging information. */ - protected final boolean debugSpew; + /** The receiver type of the enclosing method tree. */ + private @Nullable AnnotatedDeclaredType receiverType = null; - // effStack and currentMethods should always be the same size. - protected final ArrayDeque effStack; - protected final ArrayDeque currentMethods; + /** Whether or not to display debugging information. */ + protected final boolean debugSpew; - public GuiEffectVisitor(BaseTypeChecker checker) { - super(checker); - debugSpew = checker.getLintOption("debugSpew", false); - if (debugSpew) { - System.err.println("Running GuiEffectVisitor"); - } - effStack = new ArrayDeque<>(); - currentMethods = new ArrayDeque<>(); - } + // effStack and currentMethods should always be the same size. + protected final ArrayDeque effStack; + protected final ArrayDeque currentMethods; - @Override - protected GuiEffectTypeFactory createTypeFactory() { - return new GuiEffectTypeFactory(checker, debugSpew); + public GuiEffectVisitor(BaseTypeChecker checker) { + super(checker); + debugSpew = checker.getLintOption("debugSpew", false); + if (debugSpew) { + System.err.println("Running GuiEffectVisitor"); } - - // The issue is that the receiver implicitly receives an @AlwaysSafe anno, so calls on @UI - // references fail because the framework doesn't implicitly upcast the receiver (which in - // general wouldn't be sound). - // TODO: Fix method receiver defaults: method-polymorphic for any polymorphic method, UI - // for any UI instantiations, safe otherwise + effStack = new ArrayDeque<>(); + currentMethods = new ArrayDeque<>(); + } + + @Override + protected GuiEffectTypeFactory createTypeFactory() { + return new GuiEffectTypeFactory(checker, debugSpew); + } + + // The issue is that the receiver implicitly receives an @AlwaysSafe anno, so calls on @UI + // references fail because the framework doesn't implicitly upcast the receiver (which in + // general wouldn't be sound). + // TODO: Fix method receiver defaults: method-polymorphic for any polymorphic method, UI + // for any UI instantiations, safe otherwise + @Override + protected void checkMethodInvocability( + AnnotatedExecutableType method, MethodInvocationTree tree) { + // The inherited version of this complains about invoking methods of @UI instantiations of + // classes, which by default are annotated @AlwaysSafe, which for data type qualifiers is + // reasonable, but it not what we want, since we want . + // TODO: Undo this hack! + } + + protected class GuiEffectOverrideChecker extends OverrideChecker { + /** + * Extend the receiver part of the method override check. We extend the standard check, to + * additionally permit narrowing the receiver's permission to {@code @AlwaysSafe} in a safe + * instantiation of a {@code @PolyUIType}. Returns true if the override is permitted. + */ @Override - protected void checkMethodInvocability( - AnnotatedExecutableType method, MethodInvocationTree tree) { - // The inherited version of this complains about invoking methods of @UI instantiations of - // classes, which by default are annotated @AlwaysSafe, which for data type qualifiers is - // reasonable, but it not what we want, since we want . - // TODO: Undo this hack! - } - - protected class GuiEffectOverrideChecker extends OverrideChecker { - /** - * Extend the receiver part of the method override check. We extend the standard check, to - * additionally permit narrowing the receiver's permission to {@code @AlwaysSafe} in a safe - * instantiation of a {@code @PolyUIType}. Returns true if the override is permitted. - */ - @Override - protected boolean checkReceiverOverride() { - // We cannot reuse the inherited method because it directly issues the failure, but we - // want a more permissive check. So this is copied down and modified from - // BaseTypeVisitor.OverrideChecker.checkReceiverOverride. - // isSubtype() requires its arguments to be actual subtypes with - // respect to JLS, but the overrider receiver is not a subtype of the - // overridden receiver. Hence copying the annotations. - // TODO: Does this need to be improved for generic receivers? I.e., do we need to - // add extra checking to reject the case of also changing qualifiers in type parameters? - // Such as overriding a {@code @PolyUI C<@UI T>} by {@code @AlwaysSafe C<@AlwaysSafe - // T>}? The change to the receiver permission is acceptable, while the change to the - // parameter should be rejected. - AnnotatedTypeMirror overriddenReceiver = - overrider.getReceiverType().getErased().shallowCopy(false); - overriddenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations()); - if (!atypeFactory - .getTypeHierarchy() - .isSubtype(overriddenReceiver, overrider.getReceiverType().getErased())) { - // This is the point at which the default check would issue an error. - // We additionally permit overrides to move from @PolyUI receivers to @AlwaysSafe - // receivers, if it's in a @AlwaysSafe specialization of a @PolyUIType - boolean safeParent = overriddenType.getAnnotation(AlwaysSafe.class) != null; - boolean polyParentDecl = - atypeFactory.getDeclAnnotation( - overriddenType.getUnderlyingType().asElement(), - PolyUIType.class) - != null; - // TODO: How much validation do I need here? Do I need to check that the overridden - // receiver was really @PolyUI and the method is really an @PolyUIEffect? I don't - // think so - we know it's a polymorphic parent type, so all receivers would be - // @PolyUI. - // Java would already reject before running type annotation processors if the Java - // types were wrong. - // The *only* extra leeway we want to permit is overriding @PolyUI receiver to - // @AlwaysSafe. But with generics, the tentative check below is inadequate. - boolean safeReceiverOverride = - overrider.getReceiverType().getAnnotation(AlwaysSafe.class) != null; - if (safeParent && polyParentDecl && safeReceiverOverride) { - return true; - } - checker.reportError( - overriderTree, - "override.receiver.invalid", - overrider.getReceiverType(), - overridden.getReceiverType(), - overriderType, - overrider, - overriddenType, - overridden); - return false; - } - return true; - } - - /** - * Create a GuiEffectOverrideChecker. - * - * @param overriderTree the AST node of the overriding method or method reference - * @param overrider the type of the overriding method - * @param overridingType the type enclosing the overrider method, usually an - * AnnotatedDeclaredType; for Method References may be something else - * @param overridingReturnType the return type of the overriding method - * @param overridden the type of the overridden method - * @param overriddenType the declared type enclosing the overridden method - * @param overriddenReturnType the return type of the overridden method - */ - public GuiEffectOverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overrider, - AnnotatedTypeMirror overridingType, - AnnotatedTypeMirror overridingReturnType, - AnnotatedExecutableType overridden, - AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - super( - overriderTree, - overrider, - overridingType, - overridingReturnType, - overridden, - overriddenType, - overriddenReturnType); + protected boolean checkReceiverOverride() { + // We cannot reuse the inherited method because it directly issues the failure, but we + // want a more permissive check. So this is copied down and modified from + // BaseTypeVisitor.OverrideChecker.checkReceiverOverride. + // isSubtype() requires its arguments to be actual subtypes with + // respect to JLS, but the overrider receiver is not a subtype of the + // overridden receiver. Hence copying the annotations. + // TODO: Does this need to be improved for generic receivers? I.e., do we need to + // add extra checking to reject the case of also changing qualifiers in type parameters? + // Such as overriding a {@code @PolyUI C<@UI T>} by {@code @AlwaysSafe C<@AlwaysSafe + // T>}? The change to the receiver permission is acceptable, while the change to the + // parameter should be rejected. + AnnotatedTypeMirror overriddenReceiver = + overrider.getReceiverType().getErased().shallowCopy(false); + overriddenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations()); + if (!atypeFactory + .getTypeHierarchy() + .isSubtype(overriddenReceiver, overrider.getReceiverType().getErased())) { + // This is the point at which the default check would issue an error. + // We additionally permit overrides to move from @PolyUI receivers to @AlwaysSafe + // receivers, if it's in a @AlwaysSafe specialization of a @PolyUIType + boolean safeParent = overriddenType.getAnnotation(AlwaysSafe.class) != null; + boolean polyParentDecl = + atypeFactory.getDeclAnnotation( + overriddenType.getUnderlyingType().asElement(), PolyUIType.class) + != null; + // TODO: How much validation do I need here? Do I need to check that the overridden + // receiver was really @PolyUI and the method is really an @PolyUIEffect? I don't + // think so - we know it's a polymorphic parent type, so all receivers would be + // @PolyUI. + // Java would already reject before running type annotation processors if the Java + // types were wrong. + // The *only* extra leeway we want to permit is overriding @PolyUI receiver to + // @AlwaysSafe. But with generics, the tentative check below is inadequate. + boolean safeReceiverOverride = + overrider.getReceiverType().getAnnotation(AlwaysSafe.class) != null; + if (safeParent && polyParentDecl && safeReceiverOverride) { + return true; } + checker.reportError( + overriderTree, + "override.receiver.invalid", + overrider.getReceiverType(), + overridden.getReceiverType(), + overriderType, + overrider, + overriddenType, + overridden); + return false; + } + return true; } - @Override - protected OverrideChecker createOverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overrider, - AnnotatedTypeMirror overridingType, - AnnotatedTypeMirror overridingReturnType, - AnnotatedExecutableType overridden, - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - return new GuiEffectOverrideChecker( - overriderTree, - overrider, - overridingType, - overridingReturnType, - overridden, - overriddenType, - overriddenReturnType); + /** + * Create a GuiEffectOverrideChecker. + * + * @param overriderTree the AST node of the overriding method or method reference + * @param overrider the type of the overriding method + * @param overridingType the type enclosing the overrider method, usually an + * AnnotatedDeclaredType; for Method References may be something else + * @param overridingReturnType the return type of the overriding method + * @param overridden the type of the overridden method + * @param overriddenType the declared type enclosing the overridden method + * @param overriddenReturnType the return type of the overridden method + */ + public GuiEffectOverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overrider, + AnnotatedTypeMirror overridingType, + AnnotatedTypeMirror overridingReturnType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + super( + overriderTree, + overrider, + overridingType, + overridingReturnType, + overridden, + overriddenType, + overriddenReturnType); } - - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(AnnotationBuilder.fromClass(elements, AlwaysSafe.class)); + } + + @Override + protected OverrideChecker createOverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overrider, + AnnotatedTypeMirror overridingType, + AnnotatedTypeMirror overridingReturnType, + AnnotatedExecutableType overridden, + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + return new GuiEffectOverrideChecker( + overriderTree, + overrider, + overridingType, + overridingReturnType, + overridden, + overriddenType, + overriddenReturnType); + } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(AnnotationBuilder.fromClass(elements, AlwaysSafe.class)); + } + + @Override + public boolean isValidUse( + AnnotatedTypeMirror.AnnotatedDeclaredType declarationType, + AnnotatedTypeMirror.AnnotatedDeclaredType useType, + Tree tree) { + boolean ret = + useType.hasAnnotation(AlwaysSafe.class) + || useType.hasAnnotation(PolyUI.class) + || atypeFactory.isPolymorphicType( + (TypeElement) declarationType.getUnderlyingType().asElement()) + || (useType.hasAnnotation(UI.class) && declarationType.hasAnnotation(UI.class)); + if (debugSpew && !ret) { + System.err.println("use: " + useType); + System.err.println("use safe: " + useType.hasAnnotation(AlwaysSafe.class)); + System.err.println("use poly: " + useType.hasAnnotation(PolyUI.class)); + System.err.println("use ui: " + useType.hasAnnotation(UI.class)); + System.err.println("declaration safe: " + declarationType.hasAnnotation(AlwaysSafe.class)); + System.err.println( + "declaration poly: " + + atypeFactory.isPolymorphicType( + (TypeElement) declarationType.getUnderlyingType().asElement())); + System.err.println("declaration ui: " + declarationType.hasAnnotation(UI.class)); + System.err.println("declaration: " + declarationType); } - - @Override - public boolean isValidUse( - AnnotatedTypeMirror.AnnotatedDeclaredType declarationType, - AnnotatedTypeMirror.AnnotatedDeclaredType useType, - Tree tree) { - boolean ret = - useType.hasAnnotation(AlwaysSafe.class) - || useType.hasAnnotation(PolyUI.class) - || atypeFactory.isPolymorphicType( - (TypeElement) declarationType.getUnderlyingType().asElement()) - || (useType.hasAnnotation(UI.class) - && declarationType.hasAnnotation(UI.class)); - if (debugSpew && !ret) { - System.err.println("use: " + useType); - System.err.println("use safe: " + useType.hasAnnotation(AlwaysSafe.class)); - System.err.println("use poly: " + useType.hasAnnotation(PolyUI.class)); - System.err.println("use ui: " + useType.hasAnnotation(UI.class)); - System.err.println( - "declaration safe: " + declarationType.hasAnnotation(AlwaysSafe.class)); - System.err.println( - "declaration poly: " - + atypeFactory.isPolymorphicType( - (TypeElement) declarationType.getUnderlyingType().asElement())); - System.err.println("declaration ui: " + declarationType.hasAnnotation(UI.class)); - System.err.println("declaration: " + declarationType); - } - return ret; + return ret; + } + + @Override + @SuppressWarnings("interning:not.interned") // comparing AST nodes + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + Void v = super.visitLambdaExpression(tree, p); + // If this is a lambda inferred to be @UI, scan up the path and re-check any assignments + // involving it. + if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { + // Backtrack path to the lambda expression itself + TreePath path = getCurrentPath(); + while (path.getLeaf() != tree) { + assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; + path = path.getParentPath(); + } + scanUp(path); } - - @Override - @SuppressWarnings("interning:not.interned") // comparing AST nodes - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - Void v = super.visitLambdaExpression(tree, p); - // If this is a lambda inferred to be @UI, scan up the path and re-check any assignments - // involving it. - if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { - // Backtrack path to the lambda expression itself - TreePath path = getCurrentPath(); - while (path.getLeaf() != tree) { - assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; - path = path.getParentPath(); - } - scanUp(path); - } - return v; + return v; + } + + @Override + protected void checkExtendsAndImplements(ClassTree classTree) { + // Skip this check + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Skip this check. + } + + @Override + protected void checkForPolymorphicQualifiers(ClassTree classTree) { + // Polymorphic qualifiers are legal on classes, so skip this check. + } + + // Check that the invoked effect is <= permitted effect (effStack.peek()) + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (debugSpew) { + System.err.println("For invocation " + tree + " in " + currentMethods.peek().getName()); } - @Override - protected void checkExtendsAndImplements(ClassTree classTree) { - // Skip this check + // Target method annotations + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + if (debugSpew) { + System.err.println("methodElt found"); } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Skip this check. + Tree callerTree = TreePathUtil.enclosingMethodOrLambda(getCurrentPath()); + if (callerTree == null) { + // Static initializer; let's assume this is safe to have the UI effect + if (debugSpew) { + System.err.println("No enclosing method: likely static initializer"); + } + return super.visitMethodInvocation(tree, p); } - - @Override - protected void checkForPolymorphicQualifiers(ClassTree classTree) { - // Polymorphic qualifiers are legal on classes, so skip this check. + if (debugSpew) { + System.err.println("callerTree found: " + callerTree.getKind()); } - // Check that the invoked effect is <= permitted effect (effStack.peek()) - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (debugSpew) { - System.err.println("For invocation " + tree + " in " + currentMethods.peek().getName()); - } - - // Target method annotations - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - if (debugSpew) { - System.err.println("methodElt found"); - } - - Tree callerTree = TreePathUtil.enclosingMethodOrLambda(getCurrentPath()); - if (callerTree == null) { - // Static initializer; let's assume this is safe to have the UI effect - if (debugSpew) { - System.err.println("No enclosing method: likely static initializer"); - } - return super.visitMethodInvocation(tree, p); + Effect targetEffect = atypeFactory.getComputedEffectAtCallsite(tree, receiverType, methodElt); + + Effect callerEffect = null; + if (callerTree.getKind() == Tree.Kind.METHOD) { + ExecutableElement callerElt = TreeUtils.elementFromDeclaration((MethodTree) callerTree); + if (debugSpew) { + System.err.println("callerElt found"); + } + + callerEffect = atypeFactory.getDeclaredEffect(callerElt); + DeclaredType callerReceiverType = classType.getUnderlyingType(); + assert callerReceiverType != null; + TypeElement callerReceiverElt = (TypeElement) callerReceiverType.asElement(); + // Note: All these checks should be fast in the common case, but happen for every method + // call inside the anonymous class. Consider a cache here if profiling surfaces this as + // taking too long. + if (TypesUtils.isAnonymous(callerReceiverType) + // Skip if already inferred @UI + && !effStack.peek().isUI() + // Ignore if explicitly annotated + && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(AlwaysSafe.class) + && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(UI.class)) { + boolean overridesPolymorphic = false; + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, callerElt); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); + AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, pair.getValue()); + if (atypeFactory.getDeclAnnotation(overriddenMethod.getElement(), PolyUIEffect.class) + != null + && atypeFactory.getDeclAnnotation( + overriddenType.getUnderlyingType().asElement(), PolyUIType.class) + != null) { + overridesPolymorphic = true; + break; + } } - if (debugSpew) { - System.err.println("callerTree found: " + callerTree.getKind()); + // Perform anonymous class polymorphic effect inference: + // method overrides @PolyUIEffect method of @PolyUIClass class, calls @UIEffect => + // @UI anon class + if (overridesPolymorphic && targetEffect.isUI()) { + // Mark the anonymous class as @UI + atypeFactory.constrainAnonymousClassToUI(callerReceiverElt); + // Then re-calculate this method's effect (it might still not be an + // @PolyUIEffect method). + callerEffect = atypeFactory.getDeclaredEffect(callerElt); + effStack.pop(); + effStack.push(callerEffect); } - - Effect targetEffect = - atypeFactory.getComputedEffectAtCallsite(tree, receiverType, methodElt); - - Effect callerEffect = null; - if (callerTree.getKind() == Tree.Kind.METHOD) { - ExecutableElement callerElt = TreeUtils.elementFromDeclaration((MethodTree) callerTree); - if (debugSpew) { - System.err.println("callerElt found"); - } - - callerEffect = atypeFactory.getDeclaredEffect(callerElt); - DeclaredType callerReceiverType = classType.getUnderlyingType(); - assert callerReceiverType != null; - TypeElement callerReceiverElt = (TypeElement) callerReceiverType.asElement(); - // Note: All these checks should be fast in the common case, but happen for every method - // call inside the anonymous class. Consider a cache here if profiling surfaces this as - // taking too long. - if (TypesUtils.isAnonymous(callerReceiverType) - // Skip if already inferred @UI - && !effStack.peek().isUI() - // Ignore if explicitly annotated - && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(AlwaysSafe.class) - && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(UI.class)) { - boolean overridesPolymorphic = false; - Map - overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, callerElt); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); - AnnotatedExecutableType overriddenMethod = - AnnotatedTypes.asMemberOf( - types, atypeFactory, overriddenType, pair.getValue()); - if (atypeFactory.getDeclAnnotation( - overriddenMethod.getElement(), PolyUIEffect.class) - != null - && atypeFactory.getDeclAnnotation( - overriddenType.getUnderlyingType().asElement(), - PolyUIType.class) - != null) { - overridesPolymorphic = true; - break; - } - } - // Perform anonymous class polymorphic effect inference: - // method overrides @PolyUIEffect method of @PolyUIClass class, calls @UIEffect => - // @UI anon class - if (overridesPolymorphic && targetEffect.isUI()) { - // Mark the anonymous class as @UI - atypeFactory.constrainAnonymousClassToUI(callerReceiverElt); - // Then re-calculate this method's effect (it might still not be an - // @PolyUIEffect method). - callerEffect = atypeFactory.getDeclaredEffect(callerElt); - effStack.pop(); - effStack.push(callerEffect); - } - } - - // Field initializers inside anonymous inner classes show up with a null current-method - // --- the traversal goes straight from the class to the initializer. - assert (currentMethods.peek() == null || callerEffect.equals(effStack.peek())); - } else if (callerTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - callerEffect = - atypeFactory.getInferedEffectForLambdaExpression( - (LambdaExpressionTree) callerTree); - // Perform lambda polymorphic effect inference: @PolyUI lambda, calling @UIEffect => @UI - // lambda - if (targetEffect.isUI() && callerEffect.isPoly()) { - atypeFactory.constrainLambdaToUI((LambdaExpressionTree) callerTree); - callerEffect = new Effect(UIEffect.class); - } - } - assert callerEffect != null; - - if (!Effect.lessThanOrEqualTo(targetEffect, callerEffect)) { - checker.reportError(tree, "call.invalid.ui", targetEffect, callerEffect); - if (debugSpew) { - System.err.println("Issuing error for tree: " + tree); - } - } - if (debugSpew) { - System.err.println( - "Successfully finished main non-recursive checkinv of invocation " + tree); - } - return super.visitMethodInvocation(tree, p); + } + + // Field initializers inside anonymous inner classes show up with a null current-method + // --- the traversal goes straight from the class to the initializer. + assert (currentMethods.peek() == null || callerEffect.equals(effStack.peek())); + } else if (callerTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + callerEffect = + atypeFactory.getInferedEffectForLambdaExpression((LambdaExpressionTree) callerTree); + // Perform lambda polymorphic effect inference: @PolyUI lambda, calling @UIEffect => @UI + // lambda + if (targetEffect.isUI() && callerEffect.isPoly()) { + atypeFactory.constrainLambdaToUI((LambdaExpressionTree) callerTree); + callerEffect = new Effect(UIEffect.class); + } } + assert callerEffect != null; - @Override - public Void visitMethod(MethodTree tree, Void p) { - AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); - AnnotatedDeclaredType previousReceiverType = receiverType; - receiverType = methodType.getReceiverType(); - - // TODO: If the type we're in is a polymorphic (over effect qualifiers) type, the receiver - // must be @PolyUI. Otherwise a "non-polymorphic" method of a polymorphic type could be - // called on a UI instance, which then gets a Safe reference to itself (unsound!) that it - // can then pass off elsewhere (dangerous!). So all receivers in methods of a @PolyUIType - // must be @PolyUI. - - // TODO: What do we do then about classes that inherit from a concrete instantiation? If it - // subclasses a Safe instantiation, all is well. If it subclasses a UI instantiation, then - // the receivers should probably be @UI in both new and override methods, so calls to - // polymorphic methods of the parent class will work correctly. In which case for proving - // anything, the qualifier on sublasses of UI instantiations would always have to be @UI... - // Need to write down |- t for this system! And the judgments for method overrides and - // inheritance! Those are actually the hardest part of the system. - - ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); - if (debugSpew) { - System.err.println( - "Visiting method " + methElt + " of " + methElt.getEnclosingElement()); - } - - // Check for conflicting (multiple) annotations - // TypeMirror scratch = methElt.getReturnType(); - AnnotationMirror targetUIP = atypeFactory.getDeclAnnotation(methElt, UIEffect.class); - AnnotationMirror targetSafeP = atypeFactory.getDeclAnnotation(methElt, SafeEffect.class); - AnnotationMirror targetPolyP = atypeFactory.getDeclAnnotation(methElt, PolyUIEffect.class); - TypeElement targetClassElt = (TypeElement) methElt.getEnclosingElement(); - - if ((targetUIP != null && (targetSafeP != null || targetPolyP != null)) - || (targetSafeP != null && targetPolyP != null)) { - checker.reportError(tree, "annotations.conflicts"); - } - if (targetPolyP != null && !atypeFactory.isPolymorphicType(targetClassElt)) { - checker.reportError(tree, "polymorphism.invalid"); - } - if (targetUIP != null && atypeFactory.isUIType(targetClassElt)) { - checker.reportWarning(tree, "effects.redundant.uitype"); - } + if (!Effect.lessThanOrEqualTo(targetEffect, callerEffect)) { + checker.reportError(tree, "call.invalid.ui", targetEffect, callerEffect); + if (debugSpew) { + System.err.println("Issuing error for tree: " + tree); + } + } + if (debugSpew) { + System.err.println("Successfully finished main non-recursive checkinv of invocation " + tree); + } + return super.visitMethodInvocation(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); + AnnotatedDeclaredType previousReceiverType = receiverType; + receiverType = methodType.getReceiverType(); + + // TODO: If the type we're in is a polymorphic (over effect qualifiers) type, the receiver + // must be @PolyUI. Otherwise a "non-polymorphic" method of a polymorphic type could be + // called on a UI instance, which then gets a Safe reference to itself (unsound!) that it + // can then pass off elsewhere (dangerous!). So all receivers in methods of a @PolyUIType + // must be @PolyUI. + + // TODO: What do we do then about classes that inherit from a concrete instantiation? If it + // subclasses a Safe instantiation, all is well. If it subclasses a UI instantiation, then + // the receivers should probably be @UI in both new and override methods, so calls to + // polymorphic methods of the parent class will work correctly. In which case for proving + // anything, the qualifier on sublasses of UI instantiations would always have to be @UI... + // Need to write down |- t for this system! And the judgments for method overrides and + // inheritance! Those are actually the hardest part of the system. + + ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); + if (debugSpew) { + System.err.println("Visiting method " + methElt + " of " + methElt.getEnclosingElement()); + } - // TODO: Report an error for polymorphic method bodies??? Until we fix the receiver - // defaults, it won't really be correct - @SuppressWarnings("unused") // call has side effects - Effect.EffectRange range = - atypeFactory.findInheritedEffectRange( - ((TypeElement) methElt.getEnclosingElement()), methElt, true, tree); - // if (targetUIP == null && targetSafeP == null && targetPolyP == null) { - // implicitly annotate this method with the LUB of the effects of the methods it overrides - // atypeFactory.fromElement(methElt).addAnnotation(range != null ? range.min.getAnnot() - // : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? UI.class : - // AlwaysSafe.class)); - // TODO: This line does nothing! AnnotatedTypeMirror.addAnnotation - // silently ignores non-qualifier annotations! - // System.err.println("ERROR: TREE ANNOTATOR SHOULD HAVE ADDED EXPLICIT ANNOTATION! (" - // +tree.getName()+")"); - // atypeFactory - // .fromElement(methElt) - // .addAnnotation(atypeFactory.getDeclaredEffect(methElt).getAnnot()); - // } - - // We hang onto the current method here for ease. We back up the old - // current method because this code is reentrant when we traverse methods of an inner class - currentMethods.addFirst(tree); - // effStack.push(targetSafeP != null ? new Effect(AlwaysSafe.class) : - // (targetPolyP != null ? new Effect(PolyUI.class) : - // (targetUIP != null ? new Effect(UI.class) : - // (range != null ? range.min : - // (isUIType(((TypeElement)methElt.getEnclosingElement())) ? new Effect(UI.class) : new - // Effect(AlwaysSafe.class)))))); - effStack.addFirst(atypeFactory.getDeclaredEffect(methElt)); - if (debugSpew) { - System.err.println( - "Pushing " + effStack.peek() + " onto the stack when checking " + methElt); - } + // Check for conflicting (multiple) annotations + // TypeMirror scratch = methElt.getReturnType(); + AnnotationMirror targetUIP = atypeFactory.getDeclAnnotation(methElt, UIEffect.class); + AnnotationMirror targetSafeP = atypeFactory.getDeclAnnotation(methElt, SafeEffect.class); + AnnotationMirror targetPolyP = atypeFactory.getDeclAnnotation(methElt, PolyUIEffect.class); + TypeElement targetClassElt = (TypeElement) methElt.getEnclosingElement(); - Void ret = super.visitMethod(tree, p); - currentMethods.removeFirst(); - effStack.removeFirst(); - receiverType = previousReceiverType; - return ret; + if ((targetUIP != null && (targetSafeP != null || targetPolyP != null)) + || (targetSafeP != null && targetPolyP != null)) { + checker.reportError(tree, "annotations.conflicts"); } - - @Override - @SuppressWarnings("interning:not.interned") // comparing AST nodes - public Void visitNewClass(NewClassTree tree, Void p) { - Void v = super.visitNewClass(tree, p); - // If this is an anonymous inner class inferred to be @UI, scan up the path and re-check any - // assignments involving it. - if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { - // Backtrack path to the new class expression itself - TreePath path = getCurrentPath(); - while (path.getLeaf() != tree) { - assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; - path = path.getParentPath(); - } - scanUp(getCurrentPath().getParentPath()); - } - return v; + if (targetPolyP != null && !atypeFactory.isPolymorphicType(targetClassElt)) { + checker.reportError(tree, "polymorphism.invalid"); } - - /** - * This method is called to traverse the path back up from any anonymous inner class or lambda - * which has been inferred to be UI affecting and re-run {@code commonAssignmentCheck()} as - * needed on places where the class declaration or lambda expression are being assigned to a - * variable, passed as a parameter or returned from a method. This is necessary because the - * normal visitor traversal only checks assignments on the way down the AST, before inference - * has had a chance to run. - * - * @param path the path to traverse up from a UI-affecting class - */ - private void scanUp(TreePath path) { - Tree tree = path.getLeaf(); - switch (tree.getKind()) { - case ASSIGNMENT: - AssignmentTree assignmentTree = (AssignmentTree) tree; - commonAssignmentCheck( - atypeFactory.getAnnotatedType(assignmentTree.getVariable()), - atypeFactory.getAnnotatedType(assignmentTree.getExpression()), - assignmentTree.getExpression(), - "assignment.type.incompatible"); - break; - case VARIABLE: - VariableTree variableTree = (VariableTree) tree; - commonAssignmentCheck( - atypeFactory.getAnnotatedType(variableTree), - atypeFactory.getAnnotatedType(variableTree.getInitializer()), - variableTree.getInitializer(), - "assignment.type.incompatible"); - break; - case METHOD_INVOCATION: - MethodInvocationTree invocationTree = (MethodInvocationTree) tree; - List args = invocationTree.getArguments(); - ParameterizedExecutableType mType = atypeFactory.methodFromUse(invocationTree); - AnnotatedExecutableType invokedMethod = mType.executableType; - ExecutableElement method = invokedMethod.getElement(); - CharSequence methodName = ElementUtils.getSimpleDescription(method); - List methodParams = method.getParameters(); - List paramTypes = invokedMethod.getParameterTypes(); - for (int i = 0; i < args.size(); ++i) { - if (args.get(i).getKind() == Tree.Kind.NEW_CLASS - || args.get(i).getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - commonAssignmentCheck( - paramTypes.get(i), - atypeFactory.getAnnotatedType(args.get(i)), - args.get(i), - "argument.type.incompatible", - methodParams.get(i), - methodName); - } - } - break; - case RETURN: - ReturnTree returnTree = (ReturnTree) tree; - if (returnTree.getExpression().getKind() == Tree.Kind.NEW_CLASS - || returnTree.getExpression().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - Tree enclosing = TreePathUtil.enclosingMethodOrLambda(path); - AnnotatedTypeMirror ret = null; - if (enclosing.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = (MethodTree) enclosing; - boolean valid = validateTypeOf(enclosing); - if (valid) { - ret = atypeFactory.getMethodReturnType(enclosingMethod, returnTree); - } - } else { - ret = - atypeFactory - .getFunctionTypeFromTree((LambdaExpressionTree) enclosing) - .getReturnType(); - } - - if (ret != null) { - commonAssignmentCheck( - ret, - atypeFactory.getAnnotatedType(returnTree.getExpression()), - returnTree.getExpression(), - "return.type.incompatible"); - } - } - break; - case METHOD: - // Stop scanning at method boundaries, since the expression can't escape the method - // without either being assigned to a field or returned. - return; - case CLASS: - // Can't ever happen, because we stop scanning at either method or field initializer - // boundaries - assert false; - return; - default: - scanUp(path.getParentPath()); - } + if (targetUIP != null && atypeFactory.isUIType(targetClassElt)) { + checker.reportWarning(tree, "effects.redundant.uitype"); } - // @Override - // public Void visitMemberSelect(MemberSelectTree tree, Void p) { - // TODO: Same effect checks as for methods - // return super.visitMemberSelect(tree, p); + // TODO: Report an error for polymorphic method bodies??? Until we fix the receiver + // defaults, it won't really be correct + @SuppressWarnings("unused") // call has side effects + Effect.EffectRange range = + atypeFactory.findInheritedEffectRange( + ((TypeElement) methElt.getEnclosingElement()), methElt, true, tree); + // if (targetUIP == null && targetSafeP == null && targetPolyP == null) { + // implicitly annotate this method with the LUB of the effects of the methods it overrides + // atypeFactory.fromElement(methElt).addAnnotation(range != null ? range.min.getAnnot() + // : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? UI.class : + // AlwaysSafe.class)); + // TODO: This line does nothing! AnnotatedTypeMirror.addAnnotation + // silently ignores non-qualifier annotations! + // System.err.println("ERROR: TREE ANNOTATOR SHOULD HAVE ADDED EXPLICIT ANNOTATION! (" + // +tree.getName()+")"); + // atypeFactory + // .fromElement(methElt) + // .addAnnotation(atypeFactory.getDeclaredEffect(methElt).getAnnot()); // } - // @Override - // public void processClassTree(ClassTree tree) { - // TODO: Check constraints on this class decl vs. parent class decl., and interfaces - // TODO: This has to wait for now: maybe this will be easier with the isValidUse on the - // TypeFactory. - // AnnotatedTypeMirror.AnnotatedDeclaredType atype = atypeFactory.fromClass(tree); - - // Push a null method and UI effect onto the stack for static field initialization - // TODO: Figure out if this is safe! For static data, almost certainly, - // but for statically initialized instance fields, I'm assuming those - // are implicitly moved into each constructor, which must then be @UI. - // currentMethods.addFirst(null); - // effStack.addFirst(new Effect(UIEffect.class)); - // super.processClassTree(tree); - // currentMethods.removeFirst(); - // effStack.removeFirst(); - // } + // We hang onto the current method here for ease. We back up the old + // current method because this code is reentrant when we traverse methods of an inner class + currentMethods.addFirst(tree); + // effStack.push(targetSafeP != null ? new Effect(AlwaysSafe.class) : + // (targetPolyP != null ? new Effect(PolyUI.class) : + // (targetUIP != null ? new Effect(UI.class) : + // (range != null ? range.min : + // (isUIType(((TypeElement)methElt.getEnclosingElement())) ? new Effect(UI.class) : new + // Effect(AlwaysSafe.class)))))); + effStack.addFirst(atypeFactory.getDeclaredEffect(methElt)); + if (debugSpew) { + System.err.println("Pushing " + effStack.peek() + " onto the stack when checking " + methElt); + } - @Override - public void processClassTree(ClassTree classTree) { - AnnotatedDeclaredType previousClassType = classType; - AnnotatedDeclaredType previousReceiverType = receiverType; - receiverType = null; - classType = atypeFactory.getAnnotatedType(TreeUtils.elementFromDeclaration(classTree)); - try { - super.processClassTree(classTree); - } finally { - classType = previousClassType; - receiverType = previousReceiverType; + Void ret = super.visitMethod(tree, p); + currentMethods.removeFirst(); + effStack.removeFirst(); + receiverType = previousReceiverType; + return ret; + } + + @Override + @SuppressWarnings("interning:not.interned") // comparing AST nodes + public Void visitNewClass(NewClassTree tree, Void p) { + Void v = super.visitNewClass(tree, p); + // If this is an anonymous inner class inferred to be @UI, scan up the path and re-check any + // assignments involving it. + if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { + // Backtrack path to the new class expression itself + TreePath path = getCurrentPath(); + while (path.getLeaf() != tree) { + assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; + path = path.getParentPath(); + } + scanUp(getCurrentPath().getParentPath()); + } + return v; + } + + /** + * This method is called to traverse the path back up from any anonymous inner class or lambda + * which has been inferred to be UI affecting and re-run {@code commonAssignmentCheck()} as needed + * on places where the class declaration or lambda expression are being assigned to a variable, + * passed as a parameter or returned from a method. This is necessary because the normal visitor + * traversal only checks assignments on the way down the AST, before inference has had a chance to + * run. + * + * @param path the path to traverse up from a UI-affecting class + */ + private void scanUp(TreePath path) { + Tree tree = path.getLeaf(); + switch (tree.getKind()) { + case ASSIGNMENT: + AssignmentTree assignmentTree = (AssignmentTree) tree; + commonAssignmentCheck( + atypeFactory.getAnnotatedType(assignmentTree.getVariable()), + atypeFactory.getAnnotatedType(assignmentTree.getExpression()), + assignmentTree.getExpression(), + "assignment.type.incompatible"); + break; + case VARIABLE: + VariableTree variableTree = (VariableTree) tree; + commonAssignmentCheck( + atypeFactory.getAnnotatedType(variableTree), + atypeFactory.getAnnotatedType(variableTree.getInitializer()), + variableTree.getInitializer(), + "assignment.type.incompatible"); + break; + case METHOD_INVOCATION: + MethodInvocationTree invocationTree = (MethodInvocationTree) tree; + List args = invocationTree.getArguments(); + ParameterizedExecutableType mType = atypeFactory.methodFromUse(invocationTree); + AnnotatedExecutableType invokedMethod = mType.executableType; + ExecutableElement method = invokedMethod.getElement(); + CharSequence methodName = ElementUtils.getSimpleDescription(method); + List methodParams = method.getParameters(); + List paramTypes = invokedMethod.getParameterTypes(); + for (int i = 0; i < args.size(); ++i) { + if (args.get(i).getKind() == Tree.Kind.NEW_CLASS + || args.get(i).getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + commonAssignmentCheck( + paramTypes.get(i), + atypeFactory.getAnnotatedType(args.get(i)), + args.get(i), + "argument.type.incompatible", + methodParams.get(i), + methodName); + } } + break; + case RETURN: + ReturnTree returnTree = (ReturnTree) tree; + if (returnTree.getExpression().getKind() == Tree.Kind.NEW_CLASS + || returnTree.getExpression().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + Tree enclosing = TreePathUtil.enclosingMethodOrLambda(path); + AnnotatedTypeMirror ret = null; + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = (MethodTree) enclosing; + boolean valid = validateTypeOf(enclosing); + if (valid) { + ret = atypeFactory.getMethodReturnType(enclosingMethod, returnTree); + } + } else { + ret = + atypeFactory + .getFunctionTypeFromTree((LambdaExpressionTree) enclosing) + .getReturnType(); + } + + if (ret != null) { + commonAssignmentCheck( + ret, + atypeFactory.getAnnotatedType(returnTree.getExpression()), + returnTree.getExpression(), + "return.type.incompatible"); + } + } + break; + case METHOD: + // Stop scanning at method boundaries, since the expression can't escape the method + // without either being assigned to a field or returned. + return; + case CLASS: + // Can't ever happen, because we stop scanning at either method or field initializer + // boundaries + assert false; + return; + default: + scanUp(path.getParentPath()); + } + } + + // @Override + // public Void visitMemberSelect(MemberSelectTree tree, Void p) { + // TODO: Same effect checks as for methods + // return super.visitMemberSelect(tree, p); + // } + + // @Override + // public void processClassTree(ClassTree tree) { + // TODO: Check constraints on this class decl vs. parent class decl., and interfaces + // TODO: This has to wait for now: maybe this will be easier with the isValidUse on the + // TypeFactory. + // AnnotatedTypeMirror.AnnotatedDeclaredType atype = atypeFactory.fromClass(tree); + + // Push a null method and UI effect onto the stack for static field initialization + // TODO: Figure out if this is safe! For static data, almost certainly, + // but for statically initialized instance fields, I'm assuming those + // are implicitly moved into each constructor, which must then be @UI. + // currentMethods.addFirst(null); + // effStack.addFirst(new Effect(UIEffect.class)); + // super.processClassTree(tree); + // currentMethods.removeFirst(); + // effStack.removeFirst(); + // } + + @Override + public void processClassTree(ClassTree classTree) { + AnnotatedDeclaredType previousClassType = classType; + AnnotatedDeclaredType previousReceiverType = receiverType; + receiverType = null; + classType = atypeFactory.getAnnotatedType(TreeUtils.elementFromDeclaration(classTree)); + try { + super.processClassTree(classTree); + } finally { + classType = previousClassType; + receiverType = previousReceiverType; } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java index 5ff26f76fa5..6f18ec73303 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java @@ -4,7 +4,11 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.i18n.qual.Localized; import org.checkerframework.checker.i18n.qual.UnknownLocalized; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; @@ -15,60 +19,53 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationBuilder; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; +public class I18nAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { -import javax.lang.model.element.AnnotationMirror; + public I18nAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } -public class I18nAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>(Arrays.asList(Localized.class, UnknownLocalized.class)); + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nTreeAnnotator(this)); + } + + /** Do not propagate types through binary/compound operations. */ + private class I18nTreeAnnotator extends TreeAnnotator { + /** The @{@link Localized} annotation. */ + private final AnnotationMirror LOCALIZED = + AnnotationBuilder.fromClass(elements, Localized.class); - public I18nAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); + public I18nTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>(Arrays.asList(Localized.class, UnknownLocalized.class)); + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(LOCALIZED); + return null; } @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nTreeAnnotator(this)); + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(LOCALIZED); + return null; } - /** Do not propagate types through binary/compound operations. */ - private class I18nTreeAnnotator extends TreeAnnotator { - /** The @{@link Localized} annotation. */ - private final AnnotationMirror LOCALIZED = - AnnotationBuilder.fromClass(elements, Localized.class); - - public I18nTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(LOCALIZED); - return null; - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(LOCALIZED); - return null; - } - - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(LOCALIZED)) { - if (tree.getKind() == Tree.Kind.STRING_LITERAL && tree.getValue().equals("")) { - type.addAnnotation(LOCALIZED); - } - } - return super.visitLiteral(tree, type); + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(LOCALIZED)) { + if (tree.getKind() == Tree.Kind.STRING_LITERAL && tree.getValue().equals("")) { + type.addAnnotation(LOCALIZED); } + } + return super.visitLiteral(tree, type); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java b/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java index 47217ffc53a..d08f2b13cc2 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.i18n; -import org.checkerframework.framework.source.AggregateChecker; -import org.checkerframework.framework.source.SourceChecker; - import java.util.ArrayList; import java.util.Collection; +import org.checkerframework.framework.source.AggregateChecker; +import org.checkerframework.framework.source.SourceChecker; /** * A type-checker that enforces (and finds the violations of) two properties: @@ -21,11 +20,11 @@ */ public class I18nChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - Collection> checkers = new ArrayList<>(2); - checkers.add(I18nSubchecker.class); - checkers.add(LocalizableKeyChecker.class); - return checkers; - } + @Override + protected Collection> getSupportedCheckers() { + Collection> checkers = new ArrayList<>(2); + checkers.add(I18nSubchecker.class); + checkers.add(LocalizableKeyChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java index 1cf79a45706..5141db26f48 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java @@ -1,5 +1,9 @@ package org.checkerframework.checker.i18n; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; import org.checkerframework.checker.i18n.qual.LocalizableKey; import org.checkerframework.checker.i18n.qual.LocalizableKeyBottom; import org.checkerframework.checker.i18n.qual.UnknownLocalizableKey; @@ -8,35 +12,27 @@ import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; - /** A PropertyKeyATF that uses LocalizableKey to annotate the keys. */ public class LocalizableKeyAnnotatedTypeFactory extends PropertyKeyAnnotatedTypeFactory { - public LocalizableKeyAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Does not call postInit() because its superclass does. - // If we ever add code to this constructor, it needs to: - // * call a superclass constructor that does not call postInit(), and - // * call postInit() itself. - } + public LocalizableKeyAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Does not call postInit() because its superclass does. + // If we ever add code to this constructor, it needs to: + // * call a superclass constructor that does not call postInit(), and + // * call postInit() itself. + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - LocalizableKey.class, - LocalizableKeyBottom.class, - UnknownLocalizableKey.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + LocalizableKey.class, LocalizableKeyBottom.class, UnknownLocalizableKey.class)); + } - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createBasicTreeAnnotator(), - new KeyLookupTreeAnnotator(this, LocalizableKey.class)); - } + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createBasicTreeAnnotator(), new KeyLookupTreeAnnotator(this, LocalizableKey.class)); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java index 9099c7a124d..64fc642f6e9 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java @@ -1,11 +1,9 @@ package org.checkerframework.checker.i18n; -import org.checkerframework.checker.propkey.PropertyKeyChecker; - import java.util.Locale; import java.util.ResourceBundle; - import javax.annotation.processing.SupportedOptions; +import org.checkerframework.checker.propkey.PropertyKeyChecker; /** * A type-checker that checks that only valid localizable keys are used when using localizing diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java index 72ff676c2d7..175ea40d10e 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java @@ -2,7 +2,18 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.ResourceBundle; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.qual.I18nFormat; import org.checkerframework.checker.i18nformatter.qual.I18nFormatBottom; @@ -25,20 +36,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.reflection.Signatures; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; -import java.util.ResourceBundle; - -import javax.lang.model.element.AnnotationMirror; - /** * Adds {@link I18nFormat} to the type of tree, if it is a {@code String} or {@code char} literal * that represents a satisfiable format. The annotation's value is set to be a list of appropriate @@ -52,337 +49,321 @@ */ public class I18nFormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link I18nUnknownFormat} annotation. */ - protected final AnnotationMirror I18NUNKNOWNFORMAT = - AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); - - /** The @{@link I18nFormatBottom} annotation. */ - protected final AnnotationMirror I18NFORMATBOTTOM = - AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); - - /** The fully-qualified name of {@link I18nFormat}. */ - protected static final @CanonicalName String I18NFORMAT_NAME = - I18nFormat.class.getCanonicalName(); + /** The @{@link I18nUnknownFormat} annotation. */ + protected final AnnotationMirror I18NUNKNOWNFORMAT = + AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); + + /** The @{@link I18nFormatBottom} annotation. */ + protected final AnnotationMirror I18NFORMATBOTTOM = + AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); + + /** The fully-qualified name of {@link I18nFormat}. */ + protected static final @CanonicalName String I18NFORMAT_NAME = + I18nFormat.class.getCanonicalName(); + + /** The fully-qualified name of {@link I18nInvalidFormat}. */ + protected static final @CanonicalName String I18NINVALIDFORMAT_NAME = + I18nInvalidFormat.class.getCanonicalName(); + + /** The fully-qualified name of {@link I18nFormatFor}. */ + protected static final @CanonicalName String I18NFORMATFOR_NAME = + I18nFormatFor.class.getCanonicalName(); + + /** Map from a translation file key to its value in the file. */ + public final Map translations = Collections.unmodifiableMap(buildLookup()); + + /** Syntax tree utilities. */ + protected final I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(checker); + + /** Create a new I18nFormatterAnnotatedTypeFactory. */ + public I18nFormatterAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); + } + + /** + * Builds a map from a translation file key to its value in the file. Builds the map for all files + * in the "-Apropfiles" command-line argument. + * + *

Called only once, during initialization. + * + * @return a map from a translation file key to its value in the file + */ + private Map buildLookup() { + Map result = new HashMap<>(); + + if (checker.hasOption("propfiles")) { + for (String propfile : checker.getStringsOption("propfiles", File.pathSeparator)) { + Properties prop = new Properties(); + ClassLoader cl = this.getClass().getClassLoader(); + if (cl == null) { + // The class loader is null if the system class loader was used. + cl = ClassLoader.getSystemClassLoader(); + } + try (InputStream in = cl.getResourceAsStream(propfile)) { + if (in != null) { + prop.load(in); + } else { + // If the classloader didn't manage to load the file, try whether a + // FileInputStream works. For absolute paths this might help. + try (InputStream fis = new FileInputStream(propfile)) { + prop.load(fis); + } catch (FileNotFoundException e) { + System.err.println("Couldn't find the properties file: " + propfile); + // report(null, "propertykeychecker.filenotfound", propfile); + // return Collections.emptySet(); + continue; + } + } + + for (String key : prop.stringPropertyNames()) { + result.put(key, prop.getProperty(key)); + } + } catch (Exception e) { + // TODO: is there a nicer way to report messages, that are not connected to + // an AST node? One cannot use `report`, because it needs a node. + System.err.println( + "Exception in PropertyKeyChecker.keysOfPropertyFile while processing " + + propfile + + ": " + + e); + e.printStackTrace(); + } + } + } - /** The fully-qualified name of {@link I18nInvalidFormat}. */ - protected static final @CanonicalName String I18NINVALIDFORMAT_NAME = - I18nInvalidFormat.class.getCanonicalName(); + if (checker.hasOption("bundlenames")) { + for (String bundleName : checker.getStringsOption("bundlenames", ':')) { + if (!Signatures.isBinaryName(bundleName)) { + System.err.println( + "Malformed resource bundle: <" + bundleName + "> should be a binary name."); + continue; + } + ResourceBundle bundle = ResourceBundle.getBundle(bundleName); + if (bundle == null) { + System.err.println( + "Couldn't find the resource bundle: <" + + bundleName + + "> for locale <" + + Locale.getDefault() + + ">."); + continue; + } - /** The fully-qualified name of {@link I18nFormatFor}. */ - protected static final @CanonicalName String I18NFORMATFOR_NAME = - I18nFormatFor.class.getCanonicalName(); + for (String key : bundle.keySet()) { + result.put(key, bundle.getString(key)); + } + } + } - /** Map from a translation file key to its value in the file. */ - public final Map translations = Collections.unmodifiableMap(buildLookup()); + return result; + } - /** Syntax tree utilities. */ - protected final I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(checker); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new I18nFormatterQualifierHierarchy(); + } - /** Create a new I18nFormatterAnnotatedTypeFactory. */ - public I18nFormatterAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nFormatterTreeAnnotator(this)); + } - this.postInit(); + private class I18nFormatterTreeAnnotator extends TreeAnnotator { + public I18nFormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - /** - * Builds a map from a translation file key to its value in the file. Builds the map for all - * files in the "-Apropfiles" command-line argument. - * - *

Called only once, during initialization. - * - * @return a map from a translation file key to its value in the file - */ - private Map buildLookup() { - Map result = new HashMap<>(); - - if (checker.hasOption("propfiles")) { - for (String propfile : checker.getStringsOption("propfiles", File.pathSeparator)) { - Properties prop = new Properties(); - ClassLoader cl = this.getClass().getClassLoader(); - if (cl == null) { - // The class loader is null if the system class loader was used. - cl = ClassLoader.getSystemClassLoader(); - } - try (InputStream in = cl.getResourceAsStream(propfile)) { - if (in != null) { - prop.load(in); - } else { - // If the classloader didn't manage to load the file, try whether a - // FileInputStream works. For absolute paths this might help. - try (InputStream fis = new FileInputStream(propfile)) { - prop.load(fis); - } catch (FileNotFoundException e) { - System.err.println("Couldn't find the properties file: " + propfile); - // report(null, "propertykeychecker.filenotfound", propfile); - // return Collections.emptySet(); - continue; - } - } - - for (String key : prop.stringPropertyNames()) { - result.put(key, prop.getProperty(key)); - } - } catch (Exception e) { - // TODO: is there a nicer way to report messages, that are not connected to - // an AST node? One cannot use `report`, because it needs a node. - System.err.println( - "Exception in PropertyKeyChecker.keysOfPropertyFile while processing " - + propfile - + ": " - + e); - e.printStackTrace(); - } - } + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(I18NUNKNOWNFORMAT)) { + String format = null; + if (tree.getKind() == Tree.Kind.STRING_LITERAL) { + format = (String) tree.getValue(); } - - if (checker.hasOption("bundlenames")) { - for (String bundleName : checker.getStringsOption("bundlenames", ':')) { - if (!Signatures.isBinaryName(bundleName)) { - System.err.println( - "Malformed resource bundle: <" - + bundleName - + "> should be a binary name."); - continue; - } - ResourceBundle bundle = ResourceBundle.getBundle(bundleName); - if (bundle == null) { - System.err.println( - "Couldn't find the resource bundle: <" - + bundleName - + "> for locale <" - + Locale.getDefault() - + ">."); - continue; - } - - for (String key : bundle.keySet()) { - result.put(key, bundle.getString(key)); - } - } + if (format != null) { + AnnotationMirror anno; + try { + I18nConversionCategory[] cs = I18nFormatUtil.formatParameterCategories(format); + anno = I18nFormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs); + } catch (IllegalArgumentException e) { + anno = + I18nFormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation( + e); + } + type.addAnnotation(anno); } + } - return result; + return super.visitLiteral(tree, type); } + } - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new I18nFormatterQualifierHierarchy(); - } + /** I18nFormatterQualifierHierarchy. */ + class I18nFormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new I18nFormatterTreeAnnotator(this)); + /** Qualifier kind for the @{@link I18nFormat} annotation. */ + private final QualifierKind I18NFORMAT_KIND; + + /** Qualifier kind for the @{@link I18nFormatFor} annotation. */ + private final QualifierKind I18NFORMATFOR_KIND; + + /** Qualifier kind for the @{@link I18nInvalidFormat} annotation. */ + private final QualifierKind I18NINVALIDFORMAT_KIND; + + /** Creates I18nFormatterQualifierHierarchy. */ + public I18nFormatterQualifierHierarchy() { + super( + I18nFormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + I18nFormatterAnnotatedTypeFactory.this); + this.I18NFORMAT_KIND = this.getQualifierKind(I18NFORMAT_NAME); + this.I18NFORMATFOR_KIND = this.getQualifierKind(I18NFORMATFOR_NAME); + this.I18NINVALIDFORMAT_KIND = this.getQualifierKind(I18NINVALIDFORMAT_NAME); } - private class I18nFormatterTreeAnnotator extends TreeAnnotator { - public I18nFormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == I18NFORMAT_KIND && superKind == I18NFORMAT_KIND) { + + I18nConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno); + I18nConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno); + + if (rhsArgTypes.length > lhsArgTypes.length) { + return false; } - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(I18NUNKNOWNFORMAT)) { - String format = null; - if (tree.getKind() == Tree.Kind.STRING_LITERAL) { - format = (String) tree.getValue(); - } - if (format != null) { - AnnotationMirror anno; - try { - I18nConversionCategory[] cs = - I18nFormatUtil.formatParameterCategories(format); - anno = - I18nFormatterAnnotatedTypeFactory.this.treeUtil - .categoriesToFormatAnnotation(cs); - } catch (IllegalArgumentException e) { - anno = - I18nFormatterAnnotatedTypeFactory.this.treeUtil - .exceptionToInvalidFormatAnnotation(e); - } - type.addAnnotation(anno); - } - } - - return super.visitLiteral(tree, type); + for (int i = 0; i < rhsArgTypes.length; ++i) { + if (!I18nConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { + return false; + } } + return true; + } else if ((subKind == I18NINVALIDFORMAT_KIND && superKind == I18NINVALIDFORMAT_KIND) + || (subKind == I18NFORMATFOR_KIND && superKind == I18NFORMATFOR_KIND)) { + return Objects.equals( + treeUtil.getI18nInvalidFormatValue(subAnno), + treeUtil.getI18nInvalidFormatValue(superAnno)); + } + throw new TypeSystemError("Unexpected QualifierKinds: %s %s", subKind, superKind); } - /** I18nFormatterQualifierHierarchy. */ - class I18nFormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** Qualifier kind for the @{@link I18nFormat} annotation. */ - private final QualifierKind I18NFORMAT_KIND; + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.isBottom()) { + return anno2; + } else if (qualifierKind2.isBottom()) { + return anno1; + } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { + I18nConversionCategory[] shorterArgTypesList = treeUtil.formatAnnotationToCategories(anno1); + I18nConversionCategory[] longerArgTypesList = treeUtil.formatAnnotationToCategories(anno2); + if (shorterArgTypesList.length > longerArgTypesList.length) { + I18nConversionCategory[] temp = longerArgTypesList; + longerArgTypesList = shorterArgTypesList; + shorterArgTypesList = temp; + } - /** Qualifier kind for the @{@link I18nFormatFor} annotation. */ - private final QualifierKind I18NFORMATFOR_KIND; + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. - /** Qualifier kind for the @{@link I18nInvalidFormat} annotation. */ - private final QualifierKind I18NINVALIDFORMAT_KIND; + I18nConversionCategory[] resultArgTypes = + new I18nConversionCategory[longerArgTypesList.length]; - /** Creates I18nFormatterQualifierHierarchy. */ - public I18nFormatterQualifierHierarchy() { - super( - I18nFormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - I18nFormatterAnnotatedTypeFactory.this); - this.I18NFORMAT_KIND = this.getQualifierKind(I18NFORMAT_NAME); - this.I18NFORMATFOR_KIND = this.getQualifierKind(I18NFORMATFOR_NAME); - this.I18NINVALIDFORMAT_KIND = this.getQualifierKind(I18NINVALIDFORMAT_NAME); + for (int i = 0; i < shorterArgTypesList.length; ++i) { + resultArgTypes[i] = + I18nConversionCategory.intersect(shorterArgTypesList[i], longerArgTypesList[i]); + } + System.arraycopy( + longerArgTypesList, + shorterArgTypesList.length, + resultArgTypes, + shorterArgTypesList.length, + longerArgTypesList.length - shorterArgTypesList.length); + return treeUtil.categoriesToFormatAnnotation(resultArgTypes); + } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND + && qualifierKind2 == I18NINVALIDFORMAT_KIND) { + assert !anno1.getElementValues().isEmpty(); + assert !anno1.getElementValues().isEmpty(); + + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == I18NFORMAT_KIND && superKind == I18NFORMAT_KIND) { - - I18nConversionCategory[] rhsArgTypes = - treeUtil.formatAnnotationToCategories(subAnno); - I18nConversionCategory[] lhsArgTypes = - treeUtil.formatAnnotationToCategories(superAnno); - - if (rhsArgTypes.length > lhsArgTypes.length) { - return false; - } - - for (int i = 0; i < rhsArgTypes.length; ++i) { - if (!I18nConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { - return false; - } - } - return true; - } else if ((subKind == I18NINVALIDFORMAT_KIND && superKind == I18NINVALIDFORMAT_KIND) - || (subKind == I18NFORMATFOR_KIND && superKind == I18NFORMATFOR_KIND)) { - return Objects.equals( - treeUtil.getI18nInvalidFormatValue(subAnno), - treeUtil.getI18nInvalidFormatValue(superAnno)); - } - throw new TypeSystemError("Unexpected QualifierKinds: %s %s", subKind, superKind); + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " or " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } else if (qualifierKind1 == I18NFORMATFOR_KIND && AnnotationUtils.areSame(anno1, anno2)) { + // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. + return anno1; + } + + return I18NUNKNOWNFORMAT; + } + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1.isTop()) { + return anno2; + } else if (qualifierKind2.isTop()) { + return anno1; + } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { + I18nConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1); + I18nConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2); + + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + int length = anno1ArgTypes.length; + if (anno2ArgTypes.length < length) { + length = anno2ArgTypes.length; } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.isBottom()) { - return anno2; - } else if (qualifierKind2.isBottom()) { - return anno1; - } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { - I18nConversionCategory[] shorterArgTypesList = - treeUtil.formatAnnotationToCategories(anno1); - I18nConversionCategory[] longerArgTypesList = - treeUtil.formatAnnotationToCategories(anno2); - if (shorterArgTypesList.length > longerArgTypesList.length) { - I18nConversionCategory[] temp = longerArgTypesList; - longerArgTypesList = shorterArgTypesList; - shorterArgTypesList = temp; - } - - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - - I18nConversionCategory[] resultArgTypes = - new I18nConversionCategory[longerArgTypesList.length]; - - for (int i = 0; i < shorterArgTypesList.length; ++i) { - resultArgTypes[i] = - I18nConversionCategory.intersect( - shorterArgTypesList[i], longerArgTypesList[i]); - } - System.arraycopy( - longerArgTypesList, - shorterArgTypesList.length, - resultArgTypes, - shorterArgTypesList.length, - longerArgTypesList.length - shorterArgTypesList.length); - return treeUtil.categoriesToFormatAnnotation(resultArgTypes); - } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND - && qualifierKind2 == I18NINVALIDFORMAT_KIND) { - assert !anno1.getElementValues().isEmpty(); - assert !anno1.getElementValues().isEmpty(); - - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; - } - - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " or " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } else if (qualifierKind1 == I18NFORMATFOR_KIND - && AnnotationUtils.areSame(anno1, anno2)) { - // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. - return anno1; - } + I18nConversionCategory[] anno3ArgTypes = new I18nConversionCategory[length]; - return I18NUNKNOWNFORMAT; + for (int i = 0; i < length; ++i) { + anno3ArgTypes[i] = I18nConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); } + return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); + } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND + && qualifierKind2 == I18NINVALIDFORMAT_KIND) { - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1.isTop()) { - return anno2; - } else if (qualifierKind2.isTop()) { - return anno1; - } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { - I18nConversionCategory[] anno1ArgTypes = - treeUtil.formatAnnotationToCategories(anno1); - I18nConversionCategory[] anno2ArgTypes = - treeUtil.formatAnnotationToCategories(anno2); - - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - int length = anno1ArgTypes.length; - if (anno2ArgTypes.length < length) { - length = anno2ArgTypes.length; - } - - I18nConversionCategory[] anno3ArgTypes = new I18nConversionCategory[length]; - - for (int i = 0; i < length; ++i) { - anno3ArgTypes[i] = - I18nConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); - } - return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); - } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND - && qualifierKind2 == I18NINVALIDFORMAT_KIND) { - - assert !anno2.getElementValues().isEmpty(); - - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; - } - - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " and " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } else if (qualifierKind1 == I18NFORMATFOR_KIND - && AnnotationUtils.areSame(anno1, anno2)) { - // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. - return anno1; - } + assert !anno2.getElementValues().isEmpty(); - return I18NFORMATBOTTOM; + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; } + + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " and " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } else if (qualifierKind1 == I18NFORMATFOR_KIND && AnnotationUtils.areSame(anno1, anno2)) { + // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. + return anno1; + } + + return I18NFORMATBOTTOM; } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java index 01749e614ec..af8910612f3 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.i18nformatter; +import javax.annotation.processing.SupportedOptions; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; -import javax.annotation.processing.SupportedOptions; - /** * A type-checker plug-in for the qualifier that finds syntactically invalid i18n-formatter calls * (MessageFormat.format()). diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java index 5dd05f7058a..a366029cd7c 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.i18nformatter; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat; @@ -15,8 +16,6 @@ import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationBuilder; -import javax.lang.model.element.AnnotationMirror; - /** * The transfer function for the Internationalization Format String Checker. * @@ -24,69 +23,65 @@ */ public class I18nFormatterTransfer extends CFTransfer { - public I18nFormatterTransfer(CFAnalysis analysis) { - super(analysis); - } - - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput in) { - I18nFormatterAnnotatedTypeFactory atypeFactory = - (I18nFormatterAnnotatedTypeFactory) analysis.getTypeFactory(); - TransferResult result = super.visitMethodInvocation(node, in); - I18nFormatterTreeUtil tu = atypeFactory.treeUtil; + public I18nFormatterTransfer(CFAnalysis analysis) { + super(analysis); + } - // If hasFormat is called, make sure that the format string is annotated correctly - if (tu.isHasFormatCall(node, atypeFactory)) { - CFStore thenStore = result.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - Result cats = tu.getHasFormatCallCategories(node); - if (cats.value() == null) { - tu.failure(cats, "i18nformat.indirect.arguments"); - } else { - JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); - AnnotationMirror anno = - atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); - thenStore.insertValue(firstParam, anno); - } - return newResult; - } + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput in) { + I18nFormatterAnnotatedTypeFactory atypeFactory = + (I18nFormatterAnnotatedTypeFactory) analysis.getTypeFactory(); + TransferResult result = super.visitMethodInvocation(node, in); + I18nFormatterTreeUtil tu = atypeFactory.treeUtil; - // If isFormat is called, annotate the format string with I18nInvalidFormat - if (tu.isIsFormatCall(node, atypeFactory)) { - CFStore thenStore = result.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); - AnnotationBuilder builder = - new AnnotationBuilder(tu.processingEnv, I18nInvalidFormat.class); - // No need to set a value of @I18nInvalidFormat - builder.setValue("value", ""); - elseStore.insertValue(firstParam, builder.build()); - return newResult; - } + // If hasFormat is called, make sure that the format string is annotated correctly + if (tu.isHasFormatCall(node, atypeFactory)) { + CFStore thenStore = result.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + Result cats = tu.getHasFormatCallCategories(node); + if (cats.value() == null) { + tu.failure(cats, "i18nformat.indirect.arguments"); + } else { + JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); + AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); + thenStore.insertValue(firstParam, anno); + } + return newResult; + } - // @I18nMakeFormat that will be used to annotate ResourceBundle.getString() so that when the - // getString() method is called, this will check if the given key exist in the translation - // file and annotate the result string with the correct format annotation according to the - // corresponding key's value. - if (tu.isMakeFormatCall(node, atypeFactory)) { - Result cats = tu.makeFormatCallCategories(node, atypeFactory); - if (cats.value() == null) { - tu.failure(cats, "i18nformat.key.not.found"); - } else { - AnnotationMirror anno = - atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); - CFValue newResultValue = - analysis.createSingleAnnotationValue( - anno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - } + // If isFormat is called, annotate the format string with I18nInvalidFormat + if (tu.isIsFormatCall(node, atypeFactory)) { + CFStore thenStore = result.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); + AnnotationBuilder builder = new AnnotationBuilder(tu.processingEnv, I18nInvalidFormat.class); + // No need to set a value of @I18nInvalidFormat + builder.setValue("value", ""); + elseStore.insertValue(firstParam, builder.build()); + return newResult; + } - return result; + // @I18nMakeFormat that will be used to annotate ResourceBundle.getString() so that when the + // getString() method is called, this will check if the given key exist in the translation + // file and annotate the result string with the correct format annotation according to the + // corresponding key's value. + if (tu.isMakeFormatCall(node, atypeFactory)) { + Result cats = tu.makeFormatCallCategories(node, atypeFactory); + if (cats.value() == null) { + tu.failure(cats, "i18nformat.key.not.found"); + } else { + AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); + CFValue newResultValue = + analysis.createSingleAnnotationValue(anno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } } + + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java index fb3f4f9f52a..1077510e08c 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java @@ -5,7 +5,21 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.SimpleTreeVisitor; - +import java.util.List; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; @@ -34,23 +48,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; -import java.util.List; -import java.util.Map; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.NullType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor8; -import javax.lang.model.util.SimpleTypeVisitor8; - /** * This class provides a collection of utilities to ease working with syntax trees that have * something to do with I18nFormatters. @@ -58,545 +55,530 @@ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker */ public class I18nFormatterTreeUtil { - /** The checker. */ - public final BaseTypeChecker checker; + /** The checker. */ + public final BaseTypeChecker checker; + + /** The processing environment. */ + public final ProcessingEnvironment processingEnv; + + /** The value() element/field of an @I18nFormat annotation. */ + protected final ExecutableElement i18nFormatValueElement; + + /** The value() element/field of an @I18nFormatFor annotation. */ + protected final ExecutableElement i18nFormatForValueElement; + + /** The value() element/field of an @I18nInvalidFormat annotation. */ + protected final ExecutableElement i18nInvalidFormatValueElement; + + /** + * Creates a new I18nFormatterTreeUtil. + * + * @param checker the checker + */ + public I18nFormatterTreeUtil(BaseTypeChecker checker) { + this.checker = checker; + this.processingEnv = checker.getProcessingEnvironment(); + i18nFormatValueElement = TreeUtils.getMethod(I18nFormat.class, "value", 0, processingEnv); + i18nFormatForValueElement = TreeUtils.getMethod(I18nFormatFor.class, "value", 0, processingEnv); + i18nInvalidFormatValueElement = + TreeUtils.getMethod(I18nInvalidFormat.class, "value", 0, processingEnv); + } + + /** Describe the format annotation type. */ + public enum FormatType { + I18NINVALID, + I18NFORMAT, + I18NFORMATFOR + } + + /** + * Takes an exception that describes an invalid formatter string and returns a syntax trees + * element that represents a {@link I18nInvalidFormat} annotation with the exception's error + * message as value. + */ + public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalArgumentException ex) { + return stringToInvalidFormatAnnotation(ex.getMessage()); + } + + /** + * Creates an {@link I18nInvalidFormat} annotation with the given string as its value. + * + * @param invalidFormatString an invalid formatter string + * @return an {@link I18nInvalidFormat} annotation with the given string as its value + */ + /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation(String invalidFormatString) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", invalidFormatString); + return builder.build(); + } + + /** + * Gets the value() element/field out of an I18nInvalidFormat annotation. + * + * @param anno an I18nInvalidFormat annotation + * @return its value() element/field, or null if it does not have one + */ + /*package-private*/ @Nullable String getI18nInvalidFormatValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValue(anno, i18nInvalidFormatValueElement, String.class, null); + } + + /** + * Gets the value() element/field out of an I18NFormatFor annotation. + * + * @param anno an I18NFormatFor annotation + * @return its value() element/field + */ + /*package-private*/ String getI18nFormatForValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValue(anno, i18nFormatForValueElement, String.class); + } + + /** + * Takes a syntax tree element that represents a {@link I18nInvalidFormat} annotation, and returns + * its value. + * + * @param anno an I18nInvalidFormat annotation + * @return its value() element/field, within double-quotes + */ + public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { + return "\"" + getI18nInvalidFormatValue(anno) + "\""; + } + + /** + * Creates a {@code @}{@link I18nFormat} annotation with the given list as its value. + * + * @param args conversion categories for the {@code @Format} annotation + * @return a {@code @}{@link I18nFormat} annotation with the given list as its value + */ + public AnnotationMirror categoriesToFormatAnnotation(I18nConversionCategory[] args) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nFormat.class); + builder.setValue("value", args); + return builder.build(); + } + + /** + * Takes an {@code @}{@link I18nFormat} annotation, and returns its {@code value} element + * + * @param anno an {@code @}{@link I18nFormat} annotation + * @return the {@code @}{@link I18nFormat} annotation's {@code value} element + */ + public I18nConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { + return AnnotationUtils.getElementValueEnumArray( + anno, i18nFormatValueElement, I18nConversionCategory.class); + } + + /** + * Returns true if the call is to a method with the @I18nChecksFormat annotation. An example of + * such a method is I18nFormatUtil.hasFormat. + */ + public boolean isHasFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nChecksFormat.class); + return anno != null; + } + + /** + * Returns true if the call is to a method with the @I18nValidFormat annotation. An example of + * such a method is I18nFormatUtil.isFormat. + */ + public boolean isIsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nValidFormat.class); + return anno != null; + } + + /** + * Returns true if the call is to a method with the @I18nMakeFormat annotation. An example of such + * a method is ResourceBundle.getString. + */ + public boolean isMakeFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nMakeFormat.class); + return anno != null; + } + + /** + * Reports an error. + * + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message + */ + public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportError(res.location, msgKey, args); + } + + /** + * Reports a warning. + * + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message + */ + public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportWarning(res.location, msgKey, args); + } + + private I18nConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( + MethodInvocationNode node) { + Node vararg = node.getArgument(1); + if (vararg instanceof ArrayCreationNode) { + List convs = ((ArrayCreationNode) vararg).getInitializers(); + I18nConversionCategory[] res = new I18nConversionCategory[convs.size()]; + for (int i = 0; i < convs.size(); i++) { + Node conv = convs.get(i); + if (conv instanceof FieldAccessNode) { + if (typeMirrorToClass(((FieldAccessNode) conv).getType()) + == I18nConversionCategory.class) { + res[i] = I18nConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName()); + continue; /* avoid returning null */ + } + } + return null; + } + return res; + } + return null; + } + + public Result getHasFormatCallCategories(MethodInvocationNode node) { + return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); + } + + public Result makeFormatCallCategories( + MethodInvocationNode node, I18nFormatterAnnotatedTypeFactory atypeFactory) { + Map translations = atypeFactory.translations; + Node firstParam = node.getArgument(0); + Result ret = new Result<>(null, node.getTree()); + + // Now only work with a literal string + if (firstParam instanceof StringLiteralNode) { + String s = ((StringLiteralNode) firstParam).getValue(); + if (translations.containsKey(s)) { + String value = translations.get(s); + ret = new Result<>(I18nFormatUtil.formatParameterCategories(value), node.getTree()); + } + } + return ret; + } + + /** + * Returns an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. + * Otherwise, returns null. + * + * @param tree method invocation tree + * @param atypeFactory type factory + * @return an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. + * Otherwise, returns null. + */ + public @Nullable I18nFormatCall createFormatForCall( + MethodInvocationTree tree, I18nFormatterAnnotatedTypeFactory atypeFactory) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); + for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { + // find @FormatFor + if (paramType.getAnnotation(I18nFormatFor.class) != null) { + return atypeFactory.treeUtil.new I18nFormatCall(tree, atypeFactory); + } + } + return null; + } - /** The processing environment. */ - public final ProcessingEnvironment processingEnv; + /** + * Represents a format method invocation in the syntax tree. + * + *

An I18nFormatCall instance can only be instantiated by the createFormatForCall method. + */ + public class I18nFormatCall { - /** The value() element/field of an @I18nFormat annotation. */ - protected final ExecutableElement i18nFormatValueElement; + /** The AST node for the call. */ + private final MethodInvocationTree tree; - /** The value() element/field of an @I18nFormatFor annotation. */ - protected final ExecutableElement i18nFormatForValueElement; + /** The format string argument. */ + private ExpressionTree formatArg; - /** The value() element/field of an @I18nInvalidFormat annotation. */ - protected final ExecutableElement i18nInvalidFormatValueElement; + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; - /** - * Creates a new I18nFormatterTreeUtil. - * - * @param checker the checker - */ - public I18nFormatterTreeUtil(BaseTypeChecker checker) { - this.checker = checker; - this.processingEnv = checker.getProcessingEnvironment(); - i18nFormatValueElement = TreeUtils.getMethod(I18nFormat.class, "value", 0, processingEnv); - i18nFormatForValueElement = - TreeUtils.getMethod(I18nFormatFor.class, "value", 0, processingEnv); - i18nInvalidFormatValueElement = - TreeUtils.getMethod(I18nInvalidFormat.class, "value", 0, processingEnv); - } + /** The arguments to the format string. */ + private List args; - /** Describe the format annotation type. */ - public enum FormatType { - I18NINVALID, - I18NFORMAT, - I18NFORMATFOR - } + /** Extra description for error messages. */ + private String invalidMessage; - /** - * Takes an exception that describes an invalid formatter string and returns a syntax trees - * element that represents a {@link I18nInvalidFormat} annotation with the exception's error - * message as value. - */ - public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalArgumentException ex) { - return stringToInvalidFormatAnnotation(ex.getMessage()); - } + /** The type of the format string formal parameter. */ + private AnnotatedTypeMirror formatAnno; /** - * Creates an {@link I18nInvalidFormat} annotation with the given string as its value. + * Creates an {@code I18nFormatCall} for the given method invocation tree. * - * @param invalidFormatString an invalid formatter string - * @return an {@link I18nInvalidFormat} annotation with the given string as its value + * @param tree method invocation tree + * @param atypeFactory type factory */ - /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation( - String invalidFormatString) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", invalidFormatString); - return builder.build(); + @SuppressWarnings("nullness:initialization.fields.uninitialized") + public I18nFormatCall(MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { + this.tree = tree; + this.atypeFactory = atypeFactory; + List theargs = tree.getArguments(); + this.args = null; + ExecutableElement method = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); + initialCheck(theargs, method, methodAnno); } /** - * Gets the value() element/field out of an I18nInvalidFormat annotation. + * Returns the AST node for the call. * - * @param anno an I18nInvalidFormat annotation - * @return its value() element/field, or null if it does not have one + * @return the AST node for the call */ - /*package-private*/ @Nullable String getI18nInvalidFormatValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValue( - anno, i18nInvalidFormatValueElement, String.class, null); + public MethodInvocationTree getTree() { + return tree; } - /** - * Gets the value() element/field out of an I18NFormatFor annotation. - * - * @param anno an I18NFormatFor annotation - * @return its value() element/field - */ - /*package-private*/ String getI18nFormatForValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValue(anno, i18nFormatForValueElement, String.class); + @Override + public String toString() { + return this.tree.toString(); } /** - * Takes a syntax tree element that represents a {@link I18nInvalidFormat} annotation, and - * returns its value. + * This method checks the validity of the FormatFor. If it is valid, this.args will be set to + * the correct parameter arguments. Otherwise, it will be still null. * - * @param anno an I18nInvalidFormat annotation - * @return its value() element/field, within double-quotes + * @param theargs arguments to the format method call + * @param method the ExecutableElement of the format method + * @param methodAnno annotated type of {@code method} */ - public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { - return "\"" + getI18nInvalidFormatValue(anno) + "\""; + private void initialCheck( + List theargs, + ExecutableElement method, + AnnotatedExecutableType methodAnno) { + // paramIndex is a 0-based index + int paramIndex = -1; + int i = 0; + for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { + if (paramType.getAnnotation(I18nFormatFor.class) != null) { + this.formatArg = theargs.get(i); + this.formatAnno = atypeFactory.getAnnotatedType(formatArg); + + if (typeMirrorToClass(paramType.getUnderlyingType()) != String.class) { + // Invalid FormatFor invocation + return; + } + + String formatforArg = getI18nFormatForValue(paramType.getAnnotation(I18nFormatFor.class)); + + paramIndex = JavaExpressionParseUtil.parameterIndex(formatforArg); + if (paramIndex == -1) { + // report errors here + checker.reportError(tree, "i18nformat.invalid.formatfor"); + } else { + paramIndex--; + } + break; + } + i++; + } + + if (paramIndex != -1) { + VariableElement param = method.getParameters().get(paramIndex); + if (param.asType().getKind() == TypeKind.ARRAY) { + this.args = theargs.subList(paramIndex, theargs.size()); + } else { + this.args = theargs.subList(paramIndex, paramIndex + 1); + } + } } - /** - * Creates a {@code @}{@link I18nFormat} annotation with the given list as its value. - * - * @param args conversion categories for the {@code @Format} annotation - * @return a {@code @}{@link I18nFormat} annotation with the given list as its value - */ - public AnnotationMirror categoriesToFormatAnnotation(I18nConversionCategory[] args) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nFormat.class); - builder.setValue("value", args); - return builder.build(); + public Result getFormatType() { + FormatType type; + if (isValidFormatForInvocation()) { + if (formatAnno.hasAnnotation(I18nFormat.class)) { + type = FormatType.I18NFORMAT; + } else if (formatAnno.hasAnnotation(I18nFormatFor.class)) { + type = FormatType.I18NFORMATFOR; + } else { + type = FormatType.I18NINVALID; + AnnotationMirror inv = formatAnno.getAnnotation(I18nInvalidFormat.class); + if (inv != null) { + invalidMessage = getI18nInvalidFormatValue(inv); + } else { + invalidMessage = "(is a @I18nFormat annotation missing?)"; + } + } + } else { + // If the FormatFor is invalid, it's still I18nFormatFor type but invalid, + // and we can't do anything else + type = FormatType.I18NFORMATFOR; + } + return new Result<>(type, formatArg); } - /** - * Takes an {@code @}{@link I18nFormat} annotation, and returns its {@code value} element - * - * @param anno an {@code @}{@link I18nFormat} annotation - * @return the {@code @}{@link I18nFormat} annotation's {@code value} element - */ - public I18nConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { - return AnnotationUtils.getElementValueEnumArray( - anno, i18nFormatValueElement, I18nConversionCategory.class); + public final String getInvalidError() { + return invalidMessage; } - /** - * Returns true if the call is to a method with the @I18nChecksFormat annotation. An example of - * such a method is I18nFormatUtil.hasFormat. - */ - public boolean isHasFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nChecksFormat.class); - return anno != null; + public boolean isValidFormatForInvocation() { + return this.args != null; } /** - * Returns true if the call is to a method with the @I18nValidFormat annotation. An example of - * such a method is I18nFormatUtil.isFormat. - */ - public boolean isIsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nValidFormat.class); - return anno != null; - } - - /** - * Returns true if the call is to a method with the @I18nMakeFormat annotation. An example of - * such a method is ResourceBundle.getString. + * Returns the type of method invocation. + * + * @see InvocationType */ - public boolean isMakeFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nMakeFormat.class); - return anno != null; + public final Result getInvocationType() { + InvocationType type = InvocationType.VARARG; + + if (args.size() == 1) { + ExpressionTree first = args.get(0); + TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); + // figure out if argType is an array + type = + argType.accept( + new SimpleTypeVisitor8>() { + @Override + protected InvocationType defaultAction(TypeMirror e, Class p) { + // not an array + return InvocationType.VARARG; + } + + @Override + public InvocationType visitArray(ArrayType t, Class p) { + // it's an array, now figure out if it's a + // (Object[])null array + return first.accept( + new SimpleTreeVisitor>() { + @Override + protected InvocationType defaultAction(Tree tree, Class p) { + // just a normal array + return InvocationType.ARRAY; + } + + @Override + public InvocationType visitTypeCast(TypeCastTree tree, Class p) { + // it's a (Object[])null + return atypeFactory + .getAnnotatedType(tree.getExpression()) + .getUnderlyingType() + .getKind() + == TypeKind.NULL + ? InvocationType.NULLARRAY + : InvocationType.ARRAY; + } + }, + p); + } + + @Override + public InvocationType visitNull(NullType t, Class p) { + return InvocationType.NULLARRAY; + } + }, + Void.TYPE); + } + + ExpressionTree loc; + loc = tree.getMethodSelect(); + if (type != InvocationType.VARARG && !args.isEmpty()) { + loc = args.get(0); + } + return new Result<>(type, loc); } - /** - * Reports an error. - * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message - */ - public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportError(res.location, msgKey, args); + public Result getInvalidInvocationType() { + return new Result<>(FormatType.I18NFORMATFOR, formatArg); } /** - * Reports a warning. + * Returns the conversion category for every parameter. * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message + * @see I18nConversionCategory */ - public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportWarning(res.location, msgKey, args); + public final I18nConversionCategory[] getFormatCategories() { + AnnotationMirror anno = formatAnno.getAnnotation(I18nFormat.class); + return formatAnnotationToCategories(anno); } - private I18nConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( - MethodInvocationNode node) { - Node vararg = node.getArgument(1); - if (vararg instanceof ArrayCreationNode) { - List convs = ((ArrayCreationNode) vararg).getInitializers(); - I18nConversionCategory[] res = new I18nConversionCategory[convs.size()]; - for (int i = 0; i < convs.size(); i++) { - Node conv = convs.get(i); - if (conv instanceof FieldAccessNode) { - if (typeMirrorToClass(((FieldAccessNode) conv).getType()) - == I18nConversionCategory.class) { - res[i] = - I18nConversionCategory.valueOf( - ((FieldAccessNode) conv).getFieldName()); - continue; /* avoid returning null */ - } - } - return null; - } - return res; - } - return null; + public final Result[] getParamTypes() { + // One to suppress warning in javac, the other to suppress warning in Eclipse... + @SuppressWarnings({"rawtypes", "unchecked"}) + Result[] res = new Result[args.size()]; + for (int i = 0; i < res.length; ++i) { + ExpressionTree arg = args.get(i); + TypeMirror argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); + res[i] = new Result<>(argType, arg); + } + return res; } - public Result getHasFormatCallCategories(MethodInvocationNode node) { - return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); + public boolean isValidParameter(I18nConversionCategory formatCat, TypeMirror paramType) { + Class type = typeMirrorToClass(paramType); + if (type == null) { + // we did not recognize the parameter type + return false; + } + return formatCat.isAssignableFrom(type); } - - public Result makeFormatCallCategories( - MethodInvocationNode node, I18nFormatterAnnotatedTypeFactory atypeFactory) { - Map translations = atypeFactory.translations; - Node firstParam = node.getArgument(0); - Result ret = new Result<>(null, node.getTree()); - - // Now only work with a literal string - if (firstParam instanceof StringLiteralNode) { - String s = ((StringLiteralNode) firstParam).getValue(); - if (translations.containsKey(s)) { - String value = translations.get(s); - ret = new Result<>(I18nFormatUtil.formatParameterCategories(value), node.getTree()); - } - } - return ret; + } + + /** Converts a TypeMirror to a Class. */ + private static class TypeMirrorToClassVisitor + extends SimpleTypeVisitor8, Class> { + @Override + public Class visitPrimitive(PrimitiveType t, Class v) { + switch (t.getKind()) { + case BOOLEAN: + return Boolean.class; + case BYTE: + return Byte.class; + case CHAR: + return Character.class; + case SHORT: + return Short.class; + case INT: + return Integer.class; + case LONG: + return Long.class; + case FLOAT: + return Float.class; + case DOUBLE: + return Double.class; + default: + throw new BugInCF("unknown primitive type " + t); + } } - /** - * Returns an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. - * Otherwise, returns null. - * - * @param tree method invocation tree - * @param atypeFactory type factory - * @return an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. - * Otherwise, returns null. - */ - public @Nullable I18nFormatCall createFormatForCall( - MethodInvocationTree tree, I18nFormatterAnnotatedTypeFactory atypeFactory) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); - for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { - // find @FormatFor - if (paramType.getAnnotation(I18nFormatFor.class) != null) { - return atypeFactory.treeUtil.new I18nFormatCall(tree, atypeFactory); - } - } - return null; - } - - /** - * Represents a format method invocation in the syntax tree. - * - *

An I18nFormatCall instance can only be instantiated by the createFormatForCall method. - */ - public class I18nFormatCall { - - /** The AST node for the call. */ - private final MethodInvocationTree tree; - - /** The format string argument. */ - private ExpressionTree formatArg; - - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** The arguments to the format string. */ - private List args; - - /** Extra description for error messages. */ - private String invalidMessage; - - /** The type of the format string formal parameter. */ - private AnnotatedTypeMirror formatAnno; - - /** - * Creates an {@code I18nFormatCall} for the given method invocation tree. - * - * @param tree method invocation tree - * @param atypeFactory type factory - */ - @SuppressWarnings("nullness:initialization.fields.uninitialized") - public I18nFormatCall(MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { - this.tree = tree; - this.atypeFactory = atypeFactory; - List theargs = tree.getArguments(); - this.args = null; - ExecutableElement method = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); - initialCheck(theargs, method, methodAnno); - } - - /** - * Returns the AST node for the call. - * - * @return the AST node for the call - */ - public MethodInvocationTree getTree() { - return tree; - } - - @Override - public String toString() { - return this.tree.toString(); - } - - /** - * This method checks the validity of the FormatFor. If it is valid, this.args will be set - * to the correct parameter arguments. Otherwise, it will be still null. - * - * @param theargs arguments to the format method call - * @param method the ExecutableElement of the format method - * @param methodAnno annotated type of {@code method} - */ - private void initialCheck( - List theargs, - ExecutableElement method, - AnnotatedExecutableType methodAnno) { - // paramIndex is a 0-based index - int paramIndex = -1; - int i = 0; - for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { - if (paramType.getAnnotation(I18nFormatFor.class) != null) { - this.formatArg = theargs.get(i); - this.formatAnno = atypeFactory.getAnnotatedType(formatArg); - - if (typeMirrorToClass(paramType.getUnderlyingType()) != String.class) { - // Invalid FormatFor invocation - return; - } - - String formatforArg = - getI18nFormatForValue(paramType.getAnnotation(I18nFormatFor.class)); - - paramIndex = JavaExpressionParseUtil.parameterIndex(formatforArg); - if (paramIndex == -1) { - // report errors here - checker.reportError(tree, "i18nformat.invalid.formatfor"); - } else { - paramIndex--; - } - break; + @Override + public Class visitDeclared(DeclaredType dt, Class v) { + return dt.asElement() + .accept( + new SimpleElementVisitor8, Class>() { + @Override + public Class visitType(TypeElement te, Class v) { + try { + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658: + // Name.toString should be @PolySignature + @BinaryName String cname = te.getQualifiedName().toString(); + return Class.forName(cname); + } catch (ClassNotFoundException e) { + // The lookup should work for all the classes we care about. + throw new Error(e); + } } - i++; - } - - if (paramIndex != -1) { - VariableElement param = method.getParameters().get(paramIndex); - if (param.asType().getKind() == TypeKind.ARRAY) { - this.args = theargs.subList(paramIndex, theargs.size()); - } else { - this.args = theargs.subList(paramIndex, paramIndex + 1); - } - } - } - - public Result getFormatType() { - FormatType type; - if (isValidFormatForInvocation()) { - if (formatAnno.hasAnnotation(I18nFormat.class)) { - type = FormatType.I18NFORMAT; - } else if (formatAnno.hasAnnotation(I18nFormatFor.class)) { - type = FormatType.I18NFORMATFOR; - } else { - type = FormatType.I18NINVALID; - AnnotationMirror inv = formatAnno.getAnnotation(I18nInvalidFormat.class); - if (inv != null) { - invalidMessage = getI18nInvalidFormatValue(inv); - } else { - invalidMessage = "(is a @I18nFormat annotation missing?)"; - } - } - } else { - // If the FormatFor is invalid, it's still I18nFormatFor type but invalid, - // and we can't do anything else - type = FormatType.I18NFORMATFOR; - } - return new Result<>(type, formatArg); - } - - public final String getInvalidError() { - return invalidMessage; - } - - public boolean isValidFormatForInvocation() { - return this.args != null; - } - - /** - * Returns the type of method invocation. - * - * @see InvocationType - */ - public final Result getInvocationType() { - InvocationType type = InvocationType.VARARG; - - if (args.size() == 1) { - ExpressionTree first = args.get(0); - TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); - // figure out if argType is an array - type = - argType.accept( - new SimpleTypeVisitor8>() { - @Override - protected InvocationType defaultAction( - TypeMirror e, Class p) { - // not an array - return InvocationType.VARARG; - } - - @Override - public InvocationType visitArray(ArrayType t, Class p) { - // it's an array, now figure out if it's a - // (Object[])null array - return first.accept( - new SimpleTreeVisitor< - InvocationType, Class>() { - @Override - protected InvocationType defaultAction( - Tree tree, Class p) { - // just a normal array - return InvocationType.ARRAY; - } - - @Override - public InvocationType visitTypeCast( - TypeCastTree tree, Class p) { - // it's a (Object[])null - return atypeFactory - .getAnnotatedType( - tree - .getExpression()) - .getUnderlyingType() - .getKind() - == TypeKind.NULL - ? InvocationType.NULLARRAY - : InvocationType.ARRAY; - } - }, - p); - } - - @Override - public InvocationType visitNull(NullType t, Class p) { - return InvocationType.NULLARRAY; - } - }, - Void.TYPE); - } - - ExpressionTree loc; - loc = tree.getMethodSelect(); - if (type != InvocationType.VARARG && !args.isEmpty()) { - loc = args.get(0); - } - return new Result<>(type, loc); - } - - public Result getInvalidInvocationType() { - return new Result<>(FormatType.I18NFORMATFOR, formatArg); - } - - /** - * Returns the conversion category for every parameter. - * - * @see I18nConversionCategory - */ - public final I18nConversionCategory[] getFormatCategories() { - AnnotationMirror anno = formatAnno.getAnnotation(I18nFormat.class); - return formatAnnotationToCategories(anno); - } - - public final Result[] getParamTypes() { - // One to suppress warning in javac, the other to suppress warning in Eclipse... - @SuppressWarnings({"rawtypes", "unchecked"}) - Result[] res = new Result[args.size()]; - for (int i = 0; i < res.length; ++i) { - ExpressionTree arg = args.get(i); - TypeMirror argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); - res[i] = new Result<>(argType, arg); - } - return res; - } - - public boolean isValidParameter(I18nConversionCategory formatCat, TypeMirror paramType) { - Class type = typeMirrorToClass(paramType); - if (type == null) { - // we did not recognize the parameter type - return false; - } - return formatCat.isAssignableFrom(type); - } - } - - /** Converts a TypeMirror to a Class. */ - private static class TypeMirrorToClassVisitor - extends SimpleTypeVisitor8, Class> { - @Override - public Class visitPrimitive(PrimitiveType t, Class v) { - switch (t.getKind()) { - case BOOLEAN: - return Boolean.class; - case BYTE: - return Byte.class; - case CHAR: - return Character.class; - case SHORT: - return Short.class; - case INT: - return Integer.class; - case LONG: - return Long.class; - case FLOAT: - return Float.class; - case DOUBLE: - return Double.class; - default: - throw new BugInCF("unknown primitive type " + t); - } - } - - @Override - public Class visitDeclared(DeclaredType dt, Class v) { - return dt.asElement() - .accept( - new SimpleElementVisitor8, Class>() { - @Override - public Class visitType( - TypeElement te, Class v) { - try { - @SuppressWarnings( - "signature") // https://tinyurl.com/cfissue/658: - // Name.toString should be @PolySignature - @BinaryName String cname = te.getQualifiedName().toString(); - return Class.forName(cname); - } catch (ClassNotFoundException e) { - // The lookup should work for all the classes we care about. - throw new Error(e); - } - } - }, - Void.TYPE); - } - } - - /** The singleton instance of TypeMirrorToClassVisitor. */ - private static TypeMirrorToClassVisitor typeMirrorToClassVisitor = - new TypeMirrorToClassVisitor(); - - /** - * Converts a TypeMirror to a Class. - * - * @param type a TypeMirror - * @return the class corresponding to the argument - */ - private static Class typeMirrorToClass(TypeMirror type) { - return type.accept(typeMirrorToClassVisitor, Void.TYPE); + }, + Void.TYPE); } + } + + /** The singleton instance of TypeMirrorToClassVisitor. */ + private static TypeMirrorToClassVisitor typeMirrorToClassVisitor = new TypeMirrorToClassVisitor(); + + /** + * Converts a TypeMirror to a Class. + * + * @param type a TypeMirror + * @return the class corresponding to the argument + */ + private static Class typeMirrorToClass(TypeMirror type) { + return type.accept(typeMirrorToClassVisitor, Void.TYPE); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java index 3b1c3af7bd6..f6556fafa58 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java @@ -2,7 +2,9 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; @@ -17,10 +19,6 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - /** * Whenever a method with {@link I18nFormatFor} annotation is invoked, it will perform the format * string verification. @@ -29,157 +27,146 @@ */ public class I18nFormatterVisitor extends BaseTypeVisitor { - public I18nFormatterVisitor(BaseTypeChecker checker) { - super(checker); - } + public I18nFormatterVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - I18nFormatterTreeUtil tu = atypeFactory.treeUtil; - I18nFormatCall fc = tu.createFormatForCall(tree, atypeFactory); - if (fc != null) { - checkInvocationFormatFor(fc); - return p; - } else { - return super.visitMethodInvocation(tree, p); - } + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + I18nFormatterTreeUtil tu = atypeFactory.treeUtil; + I18nFormatCall fc = tu.createFormatForCall(tree, atypeFactory); + if (fc != null) { + checkInvocationFormatFor(fc); + return p; + } else { + return super.visitMethodInvocation(tree, p); } + } - private void checkInvocationFormatFor(I18nFormatCall fc) { - I18nFormatterTreeUtil tu = atypeFactory.treeUtil; - Result type = fc.getFormatType(); + private void checkInvocationFormatFor(I18nFormatCall fc) { + I18nFormatterTreeUtil tu = atypeFactory.treeUtil; + Result type = fc.getFormatType(); - switch (type.value()) { - case I18NINVALID: - tu.failure(type, "i18nformat.string.invalid", fc.getInvalidError()); - break; - case I18NFORMATFOR: - if (!fc.isValidFormatForInvocation()) { - Result failureType = fc.getInvalidInvocationType(); - tu.failure(failureType, "i18nformat.invalid.formatfor"); - } - break; - case I18NFORMAT: - Result invc = fc.getInvocationType(); - I18nConversionCategory[] formatCats = fc.getFormatCategories(); - switch (invc.value()) { - case VARARG: - Result[] paramTypes = fc.getParamTypes(); - int paraml = paramTypes.length; - int formatl = formatCats.length; + switch (type.value()) { + case I18NINVALID: + tu.failure(type, "i18nformat.string.invalid", fc.getInvalidError()); + break; + case I18NFORMATFOR: + if (!fc.isValidFormatForInvocation()) { + Result failureType = fc.getInvalidInvocationType(); + tu.failure(failureType, "i18nformat.invalid.formatfor"); + } + break; + case I18NFORMAT: + Result invc = fc.getInvocationType(); + I18nConversionCategory[] formatCats = fc.getFormatCategories(); + switch (invc.value()) { + case VARARG: + Result[] paramTypes = fc.getParamTypes(); + int paraml = paramTypes.length; + int formatl = formatCats.length; - // For assignments, "i18nformat.missing.arguments" and - // "i18nformat.excess.arguments" are - // issued from commonAssignmentCheck(). - if (paraml < formatl) { - tu.warning(invc, "i18nformat.missing.arguments", formatl, paraml); - } - if (paraml > formatl) { - tu.warning(invc, "i18nformat.excess.arguments", formatl, paraml); - } - for (int i = 0; i < formatl && i < paraml; ++i) { - I18nConversionCategory formatCat = formatCats[i]; - Result param = paramTypes[i]; - TypeMirror paramType = param.value(); - switch (formatCat) { - case UNUSED: - tu.warning(param, "i18nformat.argument.unused", " " + (1 + i)); - break; - case GENERAL: - break; - default: - if (!fc.isValidParameter(formatCat, paramType)) { - ExecutableElement method = - TreeUtils.elementFromUse(fc.getTree()); - CharSequence methodName = - ElementUtils.getSimpleDescription(method); - tu.failure( - param, - "argument.type.incompatible", - "", // parameter name is not useful - methodName, - paramType, - formatCat); - } - } - } - break; - case NULLARRAY: - // fall-through - case ARRAY: - for (I18nConversionCategory cat : formatCats) { - if (cat == I18nConversionCategory.UNUSED) { - tu.warning(invc, "i18nformat.argument.unused", ""); - } - } - tu.warning(invc, "i18nformat.indirect.arguments"); - break; - default: - break; - } - break; - default: - break; + // For assignments, "i18nformat.missing.arguments" and + // "i18nformat.excess.arguments" are + // issued from commonAssignmentCheck(). + if (paraml < formatl) { + tu.warning(invc, "i18nformat.missing.arguments", formatl, paraml); + } + if (paraml > formatl) { + tu.warning(invc, "i18nformat.excess.arguments", formatl, paraml); + } + for (int i = 0; i < formatl && i < paraml; ++i) { + I18nConversionCategory formatCat = formatCats[i]; + Result param = paramTypes[i]; + TypeMirror paramType = param.value(); + switch (formatCat) { + case UNUSED: + tu.warning(param, "i18nformat.argument.unused", " " + (1 + i)); + break; + case GENERAL: + break; + default: + if (!fc.isValidParameter(formatCat, paramType)) { + ExecutableElement method = TreeUtils.elementFromUse(fc.getTree()); + CharSequence methodName = ElementUtils.getSimpleDescription(method); + tu.failure( + param, + "argument.type.incompatible", + "", // parameter name is not useful + methodName, + paramType, + formatCat); + } + } + } + break; + case NULLARRAY: + // fall-through + case ARRAY: + for (I18nConversionCategory cat : formatCats) { + if (cat == I18nConversionCategory.UNUSED) { + tu.warning(invc, "i18nformat.argument.unused", ""); + } + } + tu.warning(invc, "i18nformat.indirect.arguments"); + break; + default: + break; } + break; + default: + break; } + } - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = true; + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = true; - AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); - AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); + AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); + AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); - // "i18nformat.missing.arguments" and "i18nformat.excess.arguments" are issued here for - // assignments. - // For method calls, they are issued in checkInvocationFormatFor(). - if (rhs != null - && lhs != null - && AnnotationUtils.areSameByName( - rhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME) - && AnnotationUtils.areSameByName( - lhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME)) { - I18nConversionCategory[] rhsArgTypes = - atypeFactory.treeUtil.formatAnnotationToCategories(rhs); - I18nConversionCategory[] lhsArgTypes = - atypeFactory.treeUtil.formatAnnotationToCategories(lhs); + // "i18nformat.missing.arguments" and "i18nformat.excess.arguments" are issued here for + // assignments. + // For method calls, they are issued in checkInvocationFormatFor(). + if (rhs != null + && lhs != null + && AnnotationUtils.areSameByName(rhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME) + && AnnotationUtils.areSameByName(lhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME)) { + I18nConversionCategory[] rhsArgTypes = + atypeFactory.treeUtil.formatAnnotationToCategories(rhs); + I18nConversionCategory[] lhsArgTypes = + atypeFactory.treeUtil.formatAnnotationToCategories(lhs); - if (rhsArgTypes.length < lhsArgTypes.length) { - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - checker.reportWarning( - valueTree, - "i18nformat.missing.arguments", - varType.toString(), - valueType.toString()); - } else if (rhsArgTypes.length > lhsArgTypes.length) { - // Since it is known that too many conversion categories were provided, issue a more - // specific error message to that effect than "assignment.type.incompatible". - checker.reportError( - valueTree, - "i18nformat.excess.arguments", - varType.toString(), - valueType.toString()); - result = false; - } - } - - // TODO: What does "take precedence over" mean? Both are issued, but the - // "i18nformat.excess.arguments" appears first in the output. Is this meant to not call - // super.commonAssignmentCheck() if `result` is already false? - // By calling super.commonAssignmentCheck() last, any "i18nformat.excess.arguments" - // message issued for a given line of code will take precedence over the - // "assignment.type.incompatible" - // issued by super.commonAssignmentCheck(). - result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) - && result; - return result; + if (rhsArgTypes.length < lhsArgTypes.length) { + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + checker.reportWarning( + valueTree, "i18nformat.missing.arguments", varType.toString(), valueType.toString()); + } else if (rhsArgTypes.length > lhsArgTypes.length) { + // Since it is known that too many conversion categories were provided, issue a more + // specific error message to that effect than "assignment.type.incompatible". + checker.reportError( + valueTree, "i18nformat.excess.arguments", varType.toString(), valueType.toString()); + result = false; + } } + + // TODO: What does "take precedence over" mean? Both are issued, but the + // "i18nformat.excess.arguments" appears first in the output. Is this meant to not call + // super.commonAssignmentCheck() if `result` is already false? + // By calling super.commonAssignmentCheck() last, any "i18nformat.excess.arguments" + // message issued for a given line of code will take precedence over the + // "assignment.type.incompatible" + // issued by super.commonAssignmentCheck(). + result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) && result; + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java index ab390781238..2c9d016536c 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java @@ -1,77 +1,73 @@ package org.checkerframework.checker.index; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.index.qual.HasSubsequence; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; - /** * A class for functionality common to multiple type-checkers that are used by the Index Checker. */ public abstract class BaseAnnotatedTypeFactoryForIndexChecker extends BaseAnnotatedTypeFactory { - /** The from() element/field of a @HasSubsequence annotation. */ - protected final ExecutableElement hasSubsequenceFromElement = - TreeUtils.getMethod(HasSubsequence.class, "from", 0, processingEnv); + /** The from() element/field of a @HasSubsequence annotation. */ + protected final ExecutableElement hasSubsequenceFromElement = + TreeUtils.getMethod(HasSubsequence.class, "from", 0, processingEnv); - /** The to() element/field of a @HasSubsequence annotation. */ - protected final ExecutableElement hasSubsequenceToElement = - TreeUtils.getMethod(HasSubsequence.class, "to", 0, processingEnv); + /** The to() element/field of a @HasSubsequence annotation. */ + protected final ExecutableElement hasSubsequenceToElement = + TreeUtils.getMethod(HasSubsequence.class, "to", 0, processingEnv); - /** The subsequence() element/field of a @HasSubsequence annotation. */ - protected final ExecutableElement hasSubsequenceSubsequenceElement = - TreeUtils.getMethod(HasSubsequence.class, "subsequence", 0, processingEnv); + /** The subsequence() element/field of a @HasSubsequence annotation. */ + protected final ExecutableElement hasSubsequenceSubsequenceElement = + TreeUtils.getMethod(HasSubsequence.class, "subsequence", 0, processingEnv); - /** - * Creates a new BaseAnnotatedTypeFactoryForIndexChecker. - * - * @param checker the checker - */ - public BaseAnnotatedTypeFactoryForIndexChecker(BaseTypeChecker checker) { - super(checker); - } + /** + * Creates a new BaseAnnotatedTypeFactoryForIndexChecker. + * + * @param checker the checker + */ + public BaseAnnotatedTypeFactoryForIndexChecker(BaseTypeChecker checker) { + super(checker); + } - /** - * Gets the from() element/field out of a HasSubsequence annotation. - * - * @param anno a HasSubsequence annotation - * @return its from() element/field - */ - public String hasSubsequenceFromValue(AnnotationMirror anno) { - String result = - AnnotationUtils.getElementValue(anno, hasSubsequenceFromElement, String.class); - assert result != null : "@AssumeAssertion(nullness)"; - return result; - } + /** + * Gets the from() element/field out of a HasSubsequence annotation. + * + * @param anno a HasSubsequence annotation + * @return its from() element/field + */ + public String hasSubsequenceFromValue(AnnotationMirror anno) { + String result = AnnotationUtils.getElementValue(anno, hasSubsequenceFromElement, String.class); + assert result != null : "@AssumeAssertion(nullness)"; + return result; + } - /** - * Gets the to() element/field out of a HasSubsequence annotation. - * - * @param anno a HasSubsequence annotation - * @return its to() element/field - */ - public String hasSubsequenceToValue(AnnotationMirror anno) { - String result = - AnnotationUtils.getElementValue(anno, hasSubsequenceToElement, String.class); - assert result != null : "@AssumeAssertion(nullness)"; - return result; - } + /** + * Gets the to() element/field out of a HasSubsequence annotation. + * + * @param anno a HasSubsequence annotation + * @return its to() element/field + */ + public String hasSubsequenceToValue(AnnotationMirror anno) { + String result = AnnotationUtils.getElementValue(anno, hasSubsequenceToElement, String.class); + assert result != null : "@AssumeAssertion(nullness)"; + return result; + } - /** - * Gets the subsequence() element/field out of a HasSubsequence annotation. - * - * @param anno a HasSubsequence annotation - * @return its subsequence() element/field - */ - public String hasSubsequenceSubsequenceValue(AnnotationMirror anno) { - String result = - AnnotationUtils.getElementValue( - anno, hasSubsequenceSubsequenceElement, String.class); - assert result != null : "@AssumeAssertion(nullness)"; - return result; - } + /** + * Gets the subsequence() element/field out of a HasSubsequence annotation. + * + * @param anno a HasSubsequence annotation + * @return its subsequence() element/field + */ + public String hasSubsequenceSubsequenceValue(AnnotationMirror anno) { + String result = + AnnotationUtils.getElementValue(anno, hasSubsequenceSubsequenceElement, String.class); + assert result != null : "@AssumeAssertion(nullness)"; + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java index 2ce0f5a0403..5e1eda17297 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.index; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.GreaterThanNode; @@ -12,8 +13,6 @@ import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; -import javax.lang.model.element.AnnotationMirror; - /** * This class provides methods shared by the Index Checker's internal checkers in their transfer * functions. In particular, it provides a common framework for visiting comparison operators. @@ -21,96 +20,96 @@ @SuppressWarnings("ArgumentSelectionDefectChecker") // TODO: apply suggested error-prone fixes public abstract class IndexAbstractTransfer extends CFTransfer { - protected IndexAbstractTransfer(CFAnalysis analysis) { - super(analysis); - } - - @Override - public TransferResult visitGreaterThan( - GreaterThanNode node, TransferInput in) { - TransferResult result = super.visitGreaterThan(node, in); - - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } - // Refine the then branch. - refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); + protected IndexAbstractTransfer(CFAnalysis analysis) { + super(analysis); + } - // Refine the else branch, which is the inverse of the then branch. - refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); + @Override + public TransferResult visitGreaterThan( + GreaterThanNode node, TransferInput in) { + TransferResult result = super.visitGreaterThan(node, in); - return rfi.newResult; + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; } + // Refine the then branch. + refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); - @Override - public TransferResult visitGreaterThanOrEqual( - GreaterThanOrEqualNode node, TransferInput in) { - TransferResult result = super.visitGreaterThanOrEqual(node, in); + // Refine the else branch, which is the inverse of the then branch. + refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } + return rfi.newResult; + } - // Refine the then branch. - refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); + @Override + public TransferResult visitGreaterThanOrEqual( + GreaterThanOrEqualNode node, TransferInput in) { + TransferResult result = super.visitGreaterThanOrEqual(node, in); - // Refine the else branch. - refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); - - return rfi.newResult; + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; } - @Override - public TransferResult visitLessThanOrEqual( - LessThanOrEqualNode node, TransferInput in) { - TransferResult result = super.visitLessThanOrEqual(node, in); + // Refine the then branch. + refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); + + // Refine the else branch. + refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } + return rfi.newResult; + } - // Refine the then branch. A <= is just a flipped >=. - refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); + @Override + public TransferResult visitLessThanOrEqual( + LessThanOrEqualNode node, TransferInput in) { + TransferResult result = super.visitLessThanOrEqual(node, in); - // Refine the else branch. - refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); - return rfi.newResult; + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; } - @Override - public TransferResult visitLessThan( - LessThanNode node, TransferInput in) { - TransferResult result = super.visitLessThan(node, in); + // Refine the then branch. A <= is just a flipped >=. + refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } + // Refine the else branch. + refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); + return rfi.newResult; + } - // Refine the then branch. A < is just a flipped >. - refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); + @Override + public TransferResult visitLessThan( + LessThanNode node, TransferInput in) { + TransferResult result = super.visitLessThan(node, in); - // Refine the else branch. - refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); - return rfi.newResult; + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; } - protected abstract void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in); - - protected abstract void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in); + // Refine the then branch. A < is just a flipped >. + refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); + + // Refine the else branch. + refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); + return rfi.newResult; + } + + protected abstract void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in); + + protected abstract void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in); } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java index 5dd34ae41b5..c1152466fd7 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java @@ -79,6 +79,6 @@ // @RelevantJavaTypes annotations appear on other checkers. public class IndexChecker extends UpperBoundChecker { - /** Creates the Index Checker. */ - public IndexChecker() {} + /** Creates the Index Checker. */ + public IndexChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java b/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java index 75d232b2db6..58844e54b7a 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java @@ -2,7 +2,12 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.index.qual.LengthOf; import org.checkerframework.dataflow.cfg.node.MethodAccessNode; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; @@ -11,179 +16,165 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; - /** * Given a Tree or other construct, this class has methods to query whether it is a particular * method call. */ public class IndexMethodIdentifier { - /** The {@code java.lang.Math#random()} method. */ - private final ExecutableElement mathRandom; - - /** The {@code java.util.Random#nextDouble()} method. */ - private final ExecutableElement randomNextDouble; - - /** The {@code java.util.Random#nextInt()} method. */ - private final ExecutableElement randomNextInt; - - /** The {@code java.lang.String#length()} method. */ - private final ExecutableElement stringLength; - - /** The {@code java.lang.Math#min()} methods. */ - private final List mathMinMethods; - - /** The {@code java.lang.Math#max()} methods. */ - private final List mathMaxMethods; - - /** The LengthOf.value argument/element. */ - private final ExecutableElement lengthOfValueElement; - - /** - * The {@code java.lang.String#indexOf} and {@code #lastIndexOf} methods that take a string as a - * non-receiver parameter. - */ - private final List indexOfStringMethods; - - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - public IndexMethodIdentifier(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - mathRandom = TreeUtils.getMethod("java.lang.Math", "random", 0, processingEnv); - randomNextDouble = TreeUtils.getMethod("java.util.Random", "nextDouble", 0, processingEnv); - randomNextInt = TreeUtils.getMethod("java.util.Random", "nextInt", 1, processingEnv); - - stringLength = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); - - mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); - mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); - - indexOfStringMethods = new ArrayList<>(4); - indexOfStringMethods.add( - TreeUtils.getMethod( - "java.lang.String", "indexOf", processingEnv, "java.lang.String")); - indexOfStringMethods.add( - TreeUtils.getMethod( - "java.lang.String", "indexOf", processingEnv, "java.lang.String", "int")); - indexOfStringMethods.add( - TreeUtils.getMethod( - "java.lang.String", "lastIndexOf", processingEnv, "java.lang.String")); - indexOfStringMethods.add( - TreeUtils.getMethod( - "java.lang.String", - "lastIndexOf", - processingEnv, - "java.lang.String", - "int")); - - lengthOfValueElement = TreeUtils.getMethod(LengthOf.class, "value", 0, processingEnv); + /** The {@code java.lang.Math#random()} method. */ + private final ExecutableElement mathRandom; + + /** The {@code java.util.Random#nextDouble()} method. */ + private final ExecutableElement randomNextDouble; + + /** The {@code java.util.Random#nextInt()} method. */ + private final ExecutableElement randomNextInt; + + /** The {@code java.lang.String#length()} method. */ + private final ExecutableElement stringLength; + + /** The {@code java.lang.Math#min()} methods. */ + private final List mathMinMethods; + + /** The {@code java.lang.Math#max()} methods. */ + private final List mathMaxMethods; + + /** The LengthOf.value argument/element. */ + private final ExecutableElement lengthOfValueElement; + + /** + * The {@code java.lang.String#indexOf} and {@code #lastIndexOf} methods that take a string as a + * non-receiver parameter. + */ + private final List indexOfStringMethods; + + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + public IndexMethodIdentifier(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + mathRandom = TreeUtils.getMethod("java.lang.Math", "random", 0, processingEnv); + randomNextDouble = TreeUtils.getMethod("java.util.Random", "nextDouble", 0, processingEnv); + randomNextInt = TreeUtils.getMethod("java.util.Random", "nextInt", 1, processingEnv); + + stringLength = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); + + mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); + mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); + + indexOfStringMethods = new ArrayList<>(4); + indexOfStringMethods.add( + TreeUtils.getMethod("java.lang.String", "indexOf", processingEnv, "java.lang.String")); + indexOfStringMethods.add( + TreeUtils.getMethod( + "java.lang.String", "indexOf", processingEnv, "java.lang.String", "int")); + indexOfStringMethods.add( + TreeUtils.getMethod("java.lang.String", "lastIndexOf", processingEnv, "java.lang.String")); + indexOfStringMethods.add( + TreeUtils.getMethod( + "java.lang.String", "lastIndexOf", processingEnv, "java.lang.String", "int")); + + lengthOfValueElement = TreeUtils.getMethod(LengthOf.class, "value", 0, processingEnv); + } + + /** + * Returns true iff the argument is an invocation of String#indexOf or String#lastIndexOf that + * takes a string parameter. + * + * @param methodTree the method invocation tree to be tested + * @return true iff the argument is an invocation of one of String's indexOf or lastIndexOf + * methods that takes another string as a parameter + */ + public boolean isIndexOfString(Tree methodTree) { + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + return TreeUtils.isMethodInvocation(methodTree, indexOfStringMethods, processingEnv); + } + + /** + * Returns true iff the argument is an invocation of Math.min. + * + * @param methodTree the method invocation tree to be tested + * @return true iff the argument is an invocation of Math.min() + */ + public boolean isMathMin(Tree methodTree) { + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); + } + + /** Returns true iff the argument is an invocation of Math.max. */ + public boolean isMathMax(Tree methodTree) { + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); + } + + /** Returns true iff the argument is an invocation of Math.random(). */ + public boolean isMathRandom(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, mathRandom, processingEnv); + } + + /** Returns true iff the argument is an invocation of Random.nextDouble(). */ + public boolean isRandomNextDouble(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, randomNextDouble, processingEnv); + } + + /** Returns true iff the argument is an invocation of Random.nextInt(). */ + public boolean isRandomNextInt(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, randomNextInt, processingEnv); + } + + /** + * Returns true if {@code tree} is an invocation of a method that returns the length of "this" + * + * @param tree a tree + * @return true if {@code tree} is an invocation of a method that returns the length of {@code + * this} + */ + public boolean isLengthOfMethodInvocation(Tree tree) { + if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { + return false; } - - /** - * Returns true iff the argument is an invocation of String#indexOf or String#lastIndexOf that - * takes a string parameter. - * - * @param methodTree the method invocation tree to be tested - * @return true iff the argument is an invocation of one of String's indexOf or lastIndexOf - * methods that takes another string as a parameter - */ - public boolean isIndexOfString(Tree methodTree) { - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - return TreeUtils.isMethodInvocation(methodTree, indexOfStringMethods, processingEnv); - } - - /** - * Returns true iff the argument is an invocation of Math.min. - * - * @param methodTree the method invocation tree to be tested - * @return true iff the argument is an invocation of Math.min() - */ - public boolean isMathMin(Tree methodTree) { - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); - } - - /** Returns true iff the argument is an invocation of Math.max. */ - public boolean isMathMax(Tree methodTree) { - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); - } - - /** Returns true iff the argument is an invocation of Math.random(). */ - public boolean isMathRandom(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, mathRandom, processingEnv); + return isLengthOfMethodInvocation(TreeUtils.elementFromUse((MethodInvocationTree) tree)); + } + + /** + * Returns true if {@code tree} evaluates to the length of "this". This might be a call to + * String,length, or a method annotated with @LengthOf. + * + * @return true if {@code tree} evaluates to the length of "this" + */ + public boolean isLengthOfMethodInvocation(ExecutableElement ele) { + if (stringLength.equals(ele)) { + // TODO: Why not just annotate String.length with @LengthOf and thus eliminate the + // special case in this method's implementation? + return true; } - /** Returns true iff the argument is an invocation of Random.nextDouble(). */ - public boolean isRandomNextDouble(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, randomNextDouble, processingEnv); + AnnotationMirror lengthOfAnno = atypeFactory.getDeclAnnotation(ele, LengthOf.class); + if (lengthOfAnno == null) { + return false; } - - /** Returns true iff the argument is an invocation of Random.nextInt(). */ - public boolean isRandomNextInt(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, randomNextInt, processingEnv); - } - - /** - * Returns true if {@code tree} is an invocation of a method that returns the length of "this" - * - * @param tree a tree - * @return true if {@code tree} is an invocation of a method that returns the length of {@code - * this} - */ - public boolean isLengthOfMethodInvocation(Tree tree) { - if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { - return false; - } - return isLengthOfMethodInvocation(TreeUtils.elementFromUse((MethodInvocationTree) tree)); - } - - /** - * Returns true if {@code tree} evaluates to the length of "this". This might be a call to - * String,length, or a method annotated with @LengthOf. - * - * @return true if {@code tree} evaluates to the length of "this" - */ - public boolean isLengthOfMethodInvocation(ExecutableElement ele) { - if (stringLength.equals(ele)) { - // TODO: Why not just annotate String.length with @LengthOf and thus eliminate the - // special case in this method's implementation? - return true; - } - - AnnotationMirror lengthOfAnno = atypeFactory.getDeclAnnotation(ele, LengthOf.class); - if (lengthOfAnno == null) { - return false; - } - AnnotationValue lengthOfValue = lengthOfAnno.getElementValues().get(lengthOfValueElement); - return AnnotationUtils.annotationValueContains(lengthOfValue, "this"); - } - - /** - * Returns true if {@code node} is an invocation of a method that returns the length of {@code - * this} - * - * @param node a node - * @return true if {@code node} is an invocation of a method that returns the length of {@code - * this} - */ - public boolean isLengthOfMethodInvocation(Node node) { - if (node instanceof MethodInvocationNode) { - MethodInvocationNode methodInvocationNode = (MethodInvocationNode) node; - MethodAccessNode methodAccessNode = methodInvocationNode.getTarget(); - ExecutableElement ele = methodAccessNode.getMethod(); - - return isLengthOfMethodInvocation(ele); - } - return false; + AnnotationValue lengthOfValue = lengthOfAnno.getElementValues().get(lengthOfValueElement); + return AnnotationUtils.annotationValueContains(lengthOfValue, "this"); + } + + /** + * Returns true if {@code node} is an invocation of a method that returns the length of {@code + * this} + * + * @param node a node + * @return true if {@code node} is an invocation of a method that returns the length of {@code + * this} + */ + public boolean isLengthOfMethodInvocation(Node node) { + if (node instanceof MethodInvocationNode) { + MethodInvocationNode methodInvocationNode = (MethodInvocationNode) node; + MethodAccessNode methodAccessNode = methodInvocationNode.getTarget(); + ExecutableElement ele = methodAccessNode.getMethod(); + + return isLengthOfMethodInvocation(ele); } + return false; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java b/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java index 31e4c5d2854..233773cf79d 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.index; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.TransferResult; @@ -12,8 +13,6 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.TypeSystemError; -import javax.lang.model.element.AnnotationMirror; - /** * This struct contains all of the information that the refinement functions need. It's called by * each node function (i.e. greater than node, less than node, etc.) and then the results are passed @@ -22,87 +21,85 @@ */ public class IndexRefinementInfo { - /** The left operand. */ - public final Node left; + /** The left operand. */ + public final Node left; - /** The right operand. */ - public final Node right; + /** The right operand. */ + public final Node right; - /** - * Annotation for left expressions. Might be null if dataflow doesn't have a value for the - * expression. - */ - public final @Nullable AnnotationMirror leftAnno; + /** + * Annotation for left expressions. Might be null if dataflow doesn't have a value for the + * expression. + */ + public final @Nullable AnnotationMirror leftAnno; - /** - * Annotation for right expressions. Might be null if dataflow doesn't have a value for the - * expression. - */ - public final @Nullable AnnotationMirror rightAnno; + /** + * Annotation for right expressions. Might be null if dataflow doesn't have a value for the + * expression. + */ + public final @Nullable AnnotationMirror rightAnno; - /** The then store. */ - public final CFStore thenStore; + /** The then store. */ + public final CFStore thenStore; - /** The else store. */ - public final CFStore elseStore; + /** The else store. */ + public final CFStore elseStore; - /** The new result, after refinement. */ - public final ConditionalTransferResult newResult; + /** The new result, after refinement. */ + public final ConditionalTransferResult newResult; - /** - * Creates a new IndexRefinementInfo. - * - * @param left the left operand - * @param right the right operand - * @param result the new result, after refinement - * @param analysis the CFAbstractAnalysis - */ - public IndexRefinementInfo( - TransferResult result, - CFAbstractAnalysis analysis, - Node right, - Node left) { - this.right = right; - this.left = left; + /** + * Creates a new IndexRefinementInfo. + * + * @param left the left operand + * @param right the right operand + * @param result the new result, after refinement + * @param analysis the CFAbstractAnalysis + */ + public IndexRefinementInfo( + TransferResult result, + CFAbstractAnalysis analysis, + Node right, + Node left) { + this.right = right; + this.left = left; - thenStore = result.getThenStore(); - elseStore = result.getElseStore(); + thenStore = result.getThenStore(); + elseStore = result.getElseStore(); - if (analysis.getValue(right) == null || analysis.getValue(left) == null) { - leftAnno = null; - rightAnno = null; - newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - } else { - QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - rightAnno = getAnno(analysis.getValue(right).getAnnotations(), hierarchy); - leftAnno = getAnno(analysis.getValue(left).getAnnotations(), hierarchy); - newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - } + if (analysis.getValue(right) == null || analysis.getValue(left) == null) { + leftAnno = null; + rightAnno = null; + newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + } else { + QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + rightAnno = getAnno(analysis.getValue(right).getAnnotations(), hierarchy); + leftAnno = getAnno(analysis.getValue(left).getAnnotations(), hierarchy); + newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); } + } - public IndexRefinementInfo( - TransferResult result, - CFAbstractAnalysis analysis, - BinaryOperationNode node) { - this(result, analysis, node.getRightOperand(), node.getLeftOperand()); - } + public IndexRefinementInfo( + TransferResult result, + CFAbstractAnalysis analysis, + BinaryOperationNode node) { + this(result, analysis, node.getRightOperand(), node.getLeftOperand()); + } - /** - * Returns the annotation (from the given set) in the given hierarchy. - * - * @param set a set of annotations - * @param hierarchy a qualifier hierarchy - * @return the annotation (from {@code set}) in the given hierarchy - */ - private static AnnotationMirror getAnno(AnnotationMirrorSet set, QualifierHierarchy hierarchy) { - AnnotationMirrorSet tops = hierarchy.getTopAnnotations(); - if (tops.size() != 1) { - throw new TypeSystemError( - "%s: Found %d tops, but expected one.%nFound: %s", - IndexRefinementInfo.class, tops.size(), tops); - } - return hierarchy.findAnnotationInHierarchy(set, tops.iterator().next()); + /** + * Returns the annotation (from the given set) in the given hierarchy. + * + * @param set a set of annotations + * @param hierarchy a qualifier hierarchy + * @return the annotation (from {@code set}) in the given hierarchy + */ + private static AnnotationMirror getAnno(AnnotationMirrorSet set, QualifierHierarchy hierarchy) { + AnnotationMirrorSet tops = hierarchy.getTopAnnotations(); + if (tops.size() != 1) { + throw new TypeSystemError( + "%s: Found %d tops, but expected one.%nFound: %s", + IndexRefinementInfo.class, tops.size(), tops); } + return hierarchy.findAnnotationInHierarchy(set, tops.iterator().next()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java b/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java index 9f86692a4e4..5cc17ff13ca 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java @@ -4,42 +4,40 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; /** A collection of utility functions used by several Index Checker subcheckers. */ public class IndexUtil { - /** Do not instantiate IndexUtil. */ - private IndexUtil() { - throw new Error("Do not instantiate IndexUtil."); - } - - /** - * Returns true if the type is a sequence supported by this checker. - * - * @param type a type - * @return true if the type is a sequence supported by this checker - */ - public static boolean isSequenceType(TypeMirror type) { - return type.getKind() == TypeKind.ARRAY || TypesUtils.isString(type); + /** Do not instantiate IndexUtil. */ + private IndexUtil() { + throw new Error("Do not instantiate IndexUtil."); + } + + /** + * Returns true if the type is a sequence supported by this checker. + * + * @param type a type + * @return true if the type is a sequence supported by this checker + */ + public static boolean isSequenceType(TypeMirror type) { + return type.getKind() == TypeKind.ARRAY || TypesUtils.isString(type); + } + + /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ + public static @Nullable ExpressionTree getLengthSequenceTree( + Tree lengthTree, IndexMethodIdentifier imf, ProcessingEnvironment processingEnv) { + if (TreeUtils.isArrayLengthAccess(lengthTree)) { + return ((MemberSelectTree) lengthTree).getExpression(); + } else if (imf.isLengthOfMethodInvocation(lengthTree)) { + return TreeUtils.getReceiverTree((MethodInvocationTree) lengthTree); } - /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ - public static @Nullable ExpressionTree getLengthSequenceTree( - Tree lengthTree, IndexMethodIdentifier imf, ProcessingEnvironment processingEnv) { - if (TreeUtils.isArrayLengthAccess(lengthTree)) { - return ((MemberSelectTree) lengthTree).getExpression(); - } else if (imf.isLengthOfMethodInvocation(lengthTree)) { - return TreeUtils.getReceiverTree((MethodInvocationTree) lengthTree); - } - - return null; - } + return null; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java b/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java index 316ac4f43f2..202f82913fe 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java +++ b/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java @@ -1,7 +1,6 @@ package org.checkerframework.checker.index; import com.sun.source.tree.MemberSelectTree; - import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.ValueCheckerUtils; import org.checkerframework.dataflow.expression.JavaExpression; @@ -17,28 +16,28 @@ * addition or subtraction of several Java expressions. For example, {@code array.length - 1}. */ public class OffsetDependentTypesHelper extends DependentTypesHelper { - public OffsetDependentTypesHelper(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + public OffsetDependentTypesHelper(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - @Override - protected @Nullable JavaExpression transform(JavaExpression javaExpr) { - return ValueCheckerUtils.optimize(javaExpr, atypeFactory); - } + @Override + protected @Nullable JavaExpression transform(JavaExpression javaExpr) { + return ValueCheckerUtils.optimize(javaExpr, atypeFactory); + } - @Override - public TreeAnnotator createDependentTypesTreeAnnotator() { - return new DependentTypesTreeAnnotator(atypeFactory, this) { - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - // UpperBoundTreeAnnotator changes the type of array.length to @LTEL("array"). - // If the DependentTypesTreeAnnotator tries to viewpoint-adapt it based on the - // declaration of length, it will fail. - if (TreeUtils.isArrayLengthAccess(tree)) { - return null; - } - return super.visitMemberSelect(tree, type); - } - }; - } + @Override + public TreeAnnotator createDependentTypesTreeAnnotator() { + return new DependentTypesTreeAnnotator(atypeFactory, this) { + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + // UpperBoundTreeAnnotator changes the type of array.length to @LTEL("array"). + // If the DependentTypesTreeAnnotator tries to viewpoint-adapt it based on the + // declaration of length, it will fail. + if (TreeUtils.isArrayLengthAccess(tree)) { + return null; + } + return super.visitMemberSelect(tree, type); + } + }; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java b/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java index fca1e8aa2ce..126eac408e9 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java +++ b/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java @@ -1,7 +1,9 @@ package org.checkerframework.checker.index; import com.sun.source.tree.Tree; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.index.qual.HasSubsequence; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.expression.FieldAccess; @@ -12,158 +14,154 @@ import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; - /** Holds information from {@link HasSubsequence} annotations. */ public class Subsequence { - /** Name of the Subsequence. */ - public final String array; - - /** First index of the subsequence in the backing sequence. */ - public final String from; - - /** Last index of the subsequence in the backing sequence. */ - public final String to; - - private Subsequence(String array, String from, String to) { - this.array = array; - this.from = from; - this.to = to; + /** Name of the Subsequence. */ + public final String array; + + /** First index of the subsequence in the backing sequence. */ + public final String from; + + /** Last index of the subsequence in the backing sequence. */ + public final String to; + + private Subsequence(String array, String from, String to) { + this.array = array; + this.from = from; + this.to = to; + } + + /** + * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration of + * {@code varTree} or null if there is not such annotation. + * + *

Note that this method does not standardize or viewpoint adapt the arguments to the + * annotation, unlike getSubsequenceFromReceiver. + * + * @param varTree some tree + * @param factory an AnnotatedTypeFactory + * @return null or a new Subsequence from the declaration of {@code varTree} + */ + public static @Nullable Subsequence getSubsequenceFromTree( + Tree varTree, BaseAnnotatedTypeFactoryForIndexChecker factory) { + + if (!(varTree.getKind() == Tree.Kind.IDENTIFIER + || varTree.getKind() == Tree.Kind.MEMBER_SELECT + || varTree.getKind() == Tree.Kind.VARIABLE)) { + return null; } - /** - * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration - * of {@code varTree} or null if there is not such annotation. - * - *

Note that this method does not standardize or viewpoint adapt the arguments to the - * annotation, unlike getSubsequenceFromReceiver. - * - * @param varTree some tree - * @param factory an AnnotatedTypeFactory - * @return null or a new Subsequence from the declaration of {@code varTree} - */ - public static @Nullable Subsequence getSubsequenceFromTree( - Tree varTree, BaseAnnotatedTypeFactoryForIndexChecker factory) { - - if (!(varTree.getKind() == Tree.Kind.IDENTIFIER - || varTree.getKind() == Tree.Kind.MEMBER_SELECT - || varTree.getKind() == Tree.Kind.VARIABLE)) { - return null; - } - - Element element = TreeUtils.elementFromTree(varTree); - AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); - return createSubsequence(hasSub, factory); + Element element = TreeUtils.elementFromTree(varTree); + AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); + return createSubsequence(hasSub, factory); + } + + /** + * Factory method to create a representation of a subsequence. + * + * @param hasSub {@link HasSubsequence} annotation or null + * @param factory the type factory + * @return a new Subsequence object representing {@code hasSub} or null + */ + private static @Nullable Subsequence createSubsequence( + AnnotationMirror hasSub, BaseAnnotatedTypeFactoryForIndexChecker factory) { + if (hasSub == null) { + return null; } - - /** - * Factory method to create a representation of a subsequence. - * - * @param hasSub {@link HasSubsequence} annotation or null - * @param factory the type factory - * @return a new Subsequence object representing {@code hasSub} or null - */ - private static @Nullable Subsequence createSubsequence( - AnnotationMirror hasSub, BaseAnnotatedTypeFactoryForIndexChecker factory) { - if (hasSub == null) { - return null; - } - String from = factory.hasSubsequenceFromValue(hasSub); - String to = factory.hasSubsequenceToValue(hasSub); - String array = factory.hasSubsequenceSubsequenceValue(hasSub); - - return new Subsequence(array, from, to); + String from = factory.hasSubsequenceFromValue(hasSub); + String to = factory.hasSubsequenceToValue(hasSub); + String array = factory.hasSubsequenceSubsequenceValue(hasSub); + + return new Subsequence(array, from, to); + } + + /** + * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration of + * {@code rec} or null if there is not such annotation. + * + * @param expr some tree + * @param factory an AnnotatedTypeFactory + * @return null or a new Subsequence from the declaration of {@code varTree} + */ + public static @Nullable Subsequence getSubsequenceFromReceiver( + JavaExpression expr, BaseAnnotatedTypeFactoryForIndexChecker factory) { + if (!(expr instanceof FieldAccess)) { + return null; } - /** - * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration - * of {@code rec} or null if there is not such annotation. - * - * @param expr some tree - * @param factory an AnnotatedTypeFactory - * @return null or a new Subsequence from the declaration of {@code varTree} - */ - public static @Nullable Subsequence getSubsequenceFromReceiver( - JavaExpression expr, BaseAnnotatedTypeFactoryForIndexChecker factory) { - if (!(expr instanceof FieldAccess)) { - return null; - } - - FieldAccess fa = (FieldAccess) expr; - VariableElement element = fa.getField(); - AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); - if (hasSub == null) { - return null; - } - - String array = - standardizeAndViewpointAdapt( - factory.hasSubsequenceSubsequenceValue(hasSub), fa, factory.getChecker()); - String from = - standardizeAndViewpointAdapt( - factory.hasSubsequenceFromValue(hasSub), fa, factory.getChecker()); - String to = - standardizeAndViewpointAdapt( - factory.hasSubsequenceToValue(hasSub), fa, factory.getChecker()); - - return new Subsequence(array, from, to); + FieldAccess fa = (FieldAccess) expr; + VariableElement element = fa.getField(); + AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); + if (hasSub == null) { + return null; } - /** - * Helper function to standardize and viewpoint-adapt a string at a given field access. Wraps - * {@link JavaExpressionParseUtil#parse}. If a parse exception is encountered, this returns its - * argument. - * - * @param s a Java expression string - * @param fieldAccess the field access - * @param checker used to parse the expression - * @return the argument, standardized and viewpoint-adapted - */ - private static String standardizeAndViewpointAdapt( - String s, FieldAccess fieldAccess, SourceChecker checker) { - JavaExpression parseResult; - try { - parseResult = StringToJavaExpression.atFieldDecl(s, fieldAccess.getField(), checker); - } catch (JavaExpressionParseException e) { - return s; - } - - return parseResult.atFieldAccess(fieldAccess.getReceiver()).toString(); + String array = + standardizeAndViewpointAdapt( + factory.hasSubsequenceSubsequenceValue(hasSub), fa, factory.getChecker()); + String from = + standardizeAndViewpointAdapt( + factory.hasSubsequenceFromValue(hasSub), fa, factory.getChecker()); + String to = + standardizeAndViewpointAdapt( + factory.hasSubsequenceToValue(hasSub), fa, factory.getChecker()); + + return new Subsequence(array, from, to); + } + + /** + * Helper function to standardize and viewpoint-adapt a string at a given field access. Wraps + * {@link JavaExpressionParseUtil#parse}. If a parse exception is encountered, this returns its + * argument. + * + * @param s a Java expression string + * @param fieldAccess the field access + * @param checker used to parse the expression + * @return the argument, standardized and viewpoint-adapted + */ + private static String standardizeAndViewpointAdapt( + String s, FieldAccess fieldAccess, SourceChecker checker) { + JavaExpression parseResult; + try { + parseResult = StringToJavaExpression.atFieldDecl(s, fieldAccess.getField(), checker); + } catch (JavaExpressionParseException e) { + return s; } - /** - * Returns the additive inverse of the given String. That is, if the result of this method is - * some String s', then s + s' == 0 will evaluate to true. Note that this relies on the fact - * that the JavaExpression parser cannot parse multiplication, so it naively just changes '-' to - * '+' and vice-versa. - * - * @param s a Java expression string - * @return the negated string - */ - public static String negateString(String s) { - String original = s; - String result = ""; - if (!original.startsWith("-")) { - result += '-'; - } - for (int i = 0; i < original.length(); i++) { - char c = original.charAt(i); - if (c == '-') { - result += '+'; - } else if (c == '+') { - result += '-'; - } else { - result += c; - } - } - return result; + return parseResult.atFieldAccess(fieldAccess.getReceiver()).toString(); + } + + /** + * Returns the additive inverse of the given String. That is, if the result of this method is some + * String s', then s + s' == 0 will evaluate to true. Note that this relies on the fact that the + * JavaExpression parser cannot parse multiplication, so it naively just changes '-' to '+' and + * vice-versa. + * + * @param s a Java expression string + * @return the negated string + */ + public static String negateString(String s) { + String original = s; + String result = ""; + if (!original.startsWith("-")) { + result += '-'; } - - @Override - public String toString() { - return "Subsequence(array=" + this.array + ", from=" + this.from + ", to=" + this.to + ")"; + for (int i = 0; i < original.length(); i++) { + char c = original.charAt(i); + if (c == '-') { + result += '+'; + } else if (c == '+') { + result += '-'; + } else { + result += c; + } } + return result; + } + + @Override + public String toString() { + return "Subsequence(array=" + this.array + ", from=" + this.from + ", to=" + this.to + ")"; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java index 641527d5231..0ca964eedec 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java @@ -3,7 +3,16 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker; import org.checkerframework.checker.index.OffsetDependentTypesHelper; import org.checkerframework.checker.index.qual.LessThan; @@ -31,335 +40,316 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; - /** The type factory for the Less Than Checker. */ public class LessThanAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker { - /** The @LessThanBottom annotation. */ - private final AnnotationMirror LESS_THAN_BOTTOM = - AnnotationBuilder.fromClass(elements, LessThanBottom.class); - - /** The @LessThanUnknown annotation. */ - public final AnnotationMirror LESS_THAN_UNKNOWN = - AnnotationBuilder.fromClass(elements, LessThanUnknown.class); - - /** The LessThan.value argument/element. */ - private final ExecutableElement lessThanValueElement = - TreeUtils.getMethod(LessThan.class, "value", 0, processingEnv); + /** The @LessThanBottom annotation. */ + private final AnnotationMirror LESS_THAN_BOTTOM = + AnnotationBuilder.fromClass(elements, LessThanBottom.class); + + /** The @LessThanUnknown annotation. */ + public final AnnotationMirror LESS_THAN_UNKNOWN = + AnnotationBuilder.fromClass(elements, LessThanUnknown.class); + + /** The LessThan.value argument/element. */ + private final ExecutableElement lessThanValueElement = + TreeUtils.getMethod(LessThan.class, "value", 0, processingEnv); + + /** + * Creates a new LessThanAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this type factory + */ + public LessThanAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } + + /** + * Returns the Value Checker's annotated type factory. + * + * @return the Value Checker's annotated type factory + */ + public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList(LessThan.class, LessThanUnknown.class, LessThanBottom.class)); + } + + @Override + protected DependentTypesHelper createDependentTypesHelper() { + // Allows + or - in a @LessThan. + return new OffsetDependentTypesHelper(this); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new LessThanQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** LessThanQualifierHierarchy. */ + class LessThanQualifierHierarchy extends ElementQualifierHierarchy { /** - * Creates a new LessThanAnnotatedTypeFactory. + * Creates a LessThanQualifierHierarchy from the given classes. * - * @param checker the type-checker associated with this type factory + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils */ - public LessThanAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - /** - * Returns the Value Checker's annotated type factory. - * - * @return the Value Checker's annotated type factory - */ - public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); + public LessThanQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, LessThanAnnotatedTypeFactory.this); } @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList(LessThan.class, LessThanUnknown.class, LessThanBottom.class)); + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + List subList = getLessThanExpressions(subAnno); + if (subList == null) { + return true; + } + List superList = getLessThanExpressions(superAnno); + if (superList == null) { + return false; + } + + return subList.containsAll(superList); } @Override - protected DependentTypesHelper createDependentTypesHelper() { - // Allows + or - in a @LessThan. - return new OffsetDependentTypesHelper(this); + public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } + + List a1List = getLessThanExpressions(a1); + List a2List = getLessThanExpressions(a2); + a1List.retainAll(a2List); // intersection + return createLessThanQualifier(a1List); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new LessThanQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } + + List a1List = getLessThanExpressions(a1); + List a2List = getLessThanExpressions(a2); + CollectionsPlume.adjoinAll(a1List, a2List); // union + return createLessThanQualifier(a1List); } - - /** LessThanQualifierHierarchy. */ - class LessThanQualifierHierarchy extends ElementQualifierHierarchy { - - /** - * Creates a LessThanQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - public LessThanQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, LessThanAnnotatedTypeFactory.this); - } - - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - List subList = getLessThanExpressions(subAnno); - if (subList == null) { - return true; - } - List superList = getLessThanExpressions(superAnno); - if (superList == null) { - return false; - } - - return subList.containsAll(superList); - } - - @Override - public AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } - - List a1List = getLessThanExpressions(a1); - List a2List = getLessThanExpressions(a2); - a1List.retainAll(a2List); // intersection - return createLessThanQualifier(a1List); - } - - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } - - List a1List = getLessThanExpressions(a1); - List a2List = getLessThanExpressions(a2); - CollectionsPlume.adjoinAll(a1List, a2List); // union - return createLessThanQualifier(a1List); - } + } + + /** + * Returns true if {@code left} is less than {@code right}. + * + * @param left the first tree to compare + * @param right the second tree to compare + * @return is left less than right? + */ + public boolean isLessThan(Tree left, String right) { + AnnotatedTypeMirror leftATM = getAnnotatedType(left); + return isLessThan(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); + } + + /** + * Returns true if {@code left} is less than {@code right}. + * + * @param left the first value to compare (an annotation) + * @param right the second value to compare (an expression) + * @return is left less than right? + */ + public boolean isLessThan(AnnotationMirror left, String right) { + List expressions = getLessThanExpressions(left); + if (expressions == null) { + // `left` is @LessThanBottom + return true; } - - /** - * Returns true if {@code left} is less than {@code right}. - * - * @param left the first tree to compare - * @param right the second tree to compare - * @return is left less than right? - */ - public boolean isLessThan(Tree left, String right) { - AnnotatedTypeMirror leftATM = getAnnotatedType(left); - return isLessThan(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); + return expressions.contains(right); + } + + /** + * Returns true if {@code smaller < bigger}. + * + * @param smaller the first value to compare + * @param bigger the second value to compare + * @param path used to parse expressions strings + * @return {@code smaller < bigger}, using information from the Value Checker + */ + public boolean isLessThanByValue(Tree smaller, String bigger, TreePath path) { + Long smallerValue = ValueCheckerUtils.getMaxValue(smaller, getValueAnnotatedTypeFactory()); + if (smallerValue == null) { + return false; } - /** - * Returns true if {@code left} is less than {@code right}. - * - * @param left the first value to compare (an annotation) - * @param right the second value to compare (an expression) - * @return is left less than right? - */ - public boolean isLessThan(AnnotationMirror left, String right) { - List expressions = getLessThanExpressions(left); - if (expressions == null) { - // `left` is @LessThanBottom - return true; - } - return expressions.contains(right); + OffsetEquation offsetEquation = OffsetEquation.createOffsetFromJavaExpression(bigger); + if (offsetEquation.isInt()) { + // bigger is an int literal + return smallerValue < offsetEquation.getInt(); } - - /** - * Returns true if {@code smaller < bigger}. - * - * @param smaller the first value to compare - * @param bigger the second value to compare - * @param path used to parse expressions strings - * @return {@code smaller < bigger}, using information from the Value Checker - */ - public boolean isLessThanByValue(Tree smaller, String bigger, TreePath path) { - Long smallerValue = ValueCheckerUtils.getMaxValue(smaller, getValueAnnotatedTypeFactory()); - if (smallerValue == null) { - return false; - } - - OffsetEquation offsetEquation = OffsetEquation.createOffsetFromJavaExpression(bigger); - if (offsetEquation.isInt()) { - // bigger is an int literal - return smallerValue < offsetEquation.getInt(); - } - // If bigger is "expression + literal", then smaller < expression + literal - // can be reduced to smaller - literal < expression + literal - literal - smallerValue = smallerValue - offsetEquation.getInt(); - offsetEquation = - offsetEquation.copyAdd( - '-', OffsetEquation.createOffsetForInt(offsetEquation.getInt())); - - long minValueOfBigger = getMinValueFromString(offsetEquation.toString(), smaller, path); - return smallerValue < minValueOfBigger; + // If bigger is "expression + literal", then smaller < expression + literal + // can be reduced to smaller - literal < expression + literal - literal + smallerValue = smallerValue - offsetEquation.getInt(); + offsetEquation = + offsetEquation.copyAdd('-', OffsetEquation.createOffsetForInt(offsetEquation.getInt())); + + long minValueOfBigger = getMinValueFromString(offsetEquation.toString(), smaller, path); + return smallerValue < minValueOfBigger; + } + + /** + * Returns the minimum value of {@code expression} at {@code tree}. + * + * @param expression the expression whose minimum value to retrieve + * @param tree where to determine the value + * @param path the path to {@code tree} + * @return the minimum value of {@code expression} at {@code tree} + */ + private long getMinValueFromString(String expression, Tree tree, TreePath path) { + ValueAnnotatedTypeFactory valueAtypeFactory = getValueAnnotatedTypeFactory(); + JavaExpression expressionJe; + try { + expressionJe = valueAtypeFactory.parseJavaExpressionString(expression, path); + } catch (JavaExpressionParseException e) { + return Long.MIN_VALUE; } - /** - * Returns the minimum value of {@code expression} at {@code tree}. - * - * @param expression the expression whose minimum value to retrieve - * @param tree where to determine the value - * @param path the path to {@code tree} - * @return the minimum value of {@code expression} at {@code tree} - */ - private long getMinValueFromString(String expression, Tree tree, TreePath path) { - ValueAnnotatedTypeFactory valueAtypeFactory = getValueAnnotatedTypeFactory(); - JavaExpression expressionJe; - try { - expressionJe = valueAtypeFactory.parseJavaExpressionString(expression, path); - } catch (JavaExpressionParseException e) { - return Long.MIN_VALUE; - } - - AnnotationMirror intRange = - valueAtypeFactory.getAnnotationFromJavaExpression( - expressionJe, tree, IntRange.class); - if (intRange != null) { - return valueAtypeFactory.getRange(intRange).from; - } - AnnotationMirror intValue = - valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntVal.class); - if (intValue != null) { - List possibleValues = valueAtypeFactory.getIntValues(intValue); - return Collections.min(possibleValues); - } - - if (expressionJe instanceof FieldAccess) { - FieldAccess fieldAccess = ((FieldAccess) expressionJe); - if (fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY) { - // array.length might not be in the store, so check for the length of the array. - AnnotationMirror arrayRange = - valueAtypeFactory.getAnnotationFromJavaExpression( - fieldAccess.getReceiver(), tree, ArrayLenRange.class); - if (arrayRange != null) { - return valueAtypeFactory.getRange(arrayRange).from; - } - AnnotationMirror arrayLen = - valueAtypeFactory.getAnnotationFromJavaExpression( - expressionJe, tree, ArrayLen.class); - if (arrayLen != null) { - List possibleValues = valueAtypeFactory.getArrayLength(arrayLen); - return Collections.min(possibleValues); - } - // Even arrays that we know nothing about must have at least zero length. - return 0; - } - } - - return Long.MIN_VALUE; + AnnotationMirror intRange = + valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntRange.class); + if (intRange != null) { + return valueAtypeFactory.getRange(intRange).from; } - - /** - * Returns true if left is less than or equal to right. - * - * @param left the first value to compare - * @param right the second value to compare - * @return is left less than or equal to right? - */ - public boolean isLessThanOrEqual(Tree left, String right) { - AnnotatedTypeMirror leftATM = getAnnotatedType(left); - return isLessThanOrEqual(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); + AnnotationMirror intValue = + valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntVal.class); + if (intValue != null) { + List possibleValues = valueAtypeFactory.getIntValues(intValue); + return Collections.min(possibleValues); } - /** - * Returns true if left is less than or equal to right. - * - * @param left the first value to compare - * @param right the second value to compare - * @return is left less than or equal to right? - */ - public boolean isLessThanOrEqual(AnnotationMirror left, String right) { - List expressions = getLessThanExpressions(left); - if (expressions == null) { - // left is bottom so it is always less than right. - return true; - } - if (expressions.contains(right)) { - return true; + if (expressionJe instanceof FieldAccess) { + FieldAccess fieldAccess = ((FieldAccess) expressionJe); + if (fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY) { + // array.length might not be in the store, so check for the length of the array. + AnnotationMirror arrayRange = + valueAtypeFactory.getAnnotationFromJavaExpression( + fieldAccess.getReceiver(), tree, ArrayLenRange.class); + if (arrayRange != null) { + return valueAtypeFactory.getRange(arrayRange).from; } - for (String expression : expressions) { - if (expression.endsWith(" + 1") - && expression.substring(0, expression.length() - 4).equals(right)) { - return true; - } + AnnotationMirror arrayLen = + valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, ArrayLen.class); + if (arrayLen != null) { + List possibleValues = valueAtypeFactory.getArrayLength(arrayLen); + return Collections.min(possibleValues); } - return false; + // Even arrays that we know nothing about must have at least zero length. + return 0; + } } - /** - * Returns a sorted, modifiable list of expressions that {@code expression} is less than. If the - * {@code expression} is annotated with {@link LessThanBottom}, null is returned. - * - * @param expression an expression - * @return expressions that {@code expression} is less than - */ - public @Nullable List getLessThanExpressions(ExpressionTree expression) { - AnnotatedTypeMirror annotatedTypeMirror = getAnnotatedType(expression); - return getLessThanExpressions( - annotatedTypeMirror.getAnnotationInHierarchy(LESS_THAN_UNKNOWN)); + return Long.MIN_VALUE; + } + + /** + * Returns true if left is less than or equal to right. + * + * @param left the first value to compare + * @param right the second value to compare + * @return is left less than or equal to right? + */ + public boolean isLessThanOrEqual(Tree left, String right) { + AnnotatedTypeMirror leftATM = getAnnotatedType(left); + return isLessThanOrEqual(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); + } + + /** + * Returns true if left is less than or equal to right. + * + * @param left the first value to compare + * @param right the second value to compare + * @return is left less than or equal to right? + */ + public boolean isLessThanOrEqual(AnnotationMirror left, String right) { + List expressions = getLessThanExpressions(left); + if (expressions == null) { + // left is bottom so it is always less than right. + return true; } - - /** - * Creates a less than qualifier given the expressions. - * - *

If expressions is null, {@link LessThanBottom} is returned. If expressions is empty, - * {@link LessThanUnknown} is returned. Otherwise, {@code @LessThan(expressions)} is returned. - * - * @param expressions a list of expressions - * @return a @LessThan qualifier with the given arguments - */ - public AnnotationMirror createLessThanQualifier(@Nullable List expressions) { - if (expressions == null) { - return LESS_THAN_BOTTOM; - } else if (expressions.isEmpty()) { - return LESS_THAN_UNKNOWN; - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, LessThan.class); - builder.setValue("value", expressions); - return builder.build(); - } + if (expressions.contains(right)) { + return true; } - - /** Returns {@code @LessThan(expression)}. */ - public AnnotationMirror createLessThanQualifier(String expression) { - return createLessThanQualifier(Collections.singletonList(expression)); + for (String expression : expressions) { + if (expression.endsWith(" + 1") + && expression.substring(0, expression.length() - 4).equals(right)) { + return true; + } } - - /** - * If the annotation is LessThan, returns a list of expressions in the annotation. If the - * annotation is {@link LessThanBottom}, returns null. If the annotation is {@link - * LessThanUnknown}, returns the empty list. - * - * @param annotation an annotation from the same hierarchy as LessThan - * @return the list of expressions in the annotation - */ - public @Nullable List getLessThanExpressions(AnnotationMirror annotation) { - if (AnnotationUtils.areSameByName( - annotation, "org.checkerframework.checker.index.qual.LessThanBottom")) { - return null; - } else if (AnnotationUtils.areSameByName( - annotation, "org.checkerframework.checker.index.qual.LessThanUnknown")) { - return Collections.emptyList(); - } else { - // The annotation is @LessThan. - return AnnotationUtils.getElementValueArray( - annotation, lessThanValueElement, String.class); - } + return false; + } + + /** + * Returns a sorted, modifiable list of expressions that {@code expression} is less than. If the + * {@code expression} is annotated with {@link LessThanBottom}, null is returned. + * + * @param expression an expression + * @return expressions that {@code expression} is less than + */ + public @Nullable List getLessThanExpressions(ExpressionTree expression) { + AnnotatedTypeMirror annotatedTypeMirror = getAnnotatedType(expression); + return getLessThanExpressions(annotatedTypeMirror.getAnnotationInHierarchy(LESS_THAN_UNKNOWN)); + } + + /** + * Creates a less than qualifier given the expressions. + * + *

If expressions is null, {@link LessThanBottom} is returned. If expressions is empty, {@link + * LessThanUnknown} is returned. Otherwise, {@code @LessThan(expressions)} is returned. + * + * @param expressions a list of expressions + * @return a @LessThan qualifier with the given arguments + */ + public AnnotationMirror createLessThanQualifier(@Nullable List expressions) { + if (expressions == null) { + return LESS_THAN_BOTTOM; + } else if (expressions.isEmpty()) { + return LESS_THAN_UNKNOWN; + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, LessThan.class); + builder.setValue("value", expressions); + return builder.build(); + } + } + + /** Returns {@code @LessThan(expression)}. */ + public AnnotationMirror createLessThanQualifier(String expression) { + return createLessThanQualifier(Collections.singletonList(expression)); + } + + /** + * If the annotation is LessThan, returns a list of expressions in the annotation. If the + * annotation is {@link LessThanBottom}, returns null. If the annotation is {@link + * LessThanUnknown}, returns the empty list. + * + * @param annotation an annotation from the same hierarchy as LessThan + * @return the list of expressions in the annotation + */ + public @Nullable List getLessThanExpressions(AnnotationMirror annotation) { + if (AnnotationUtils.areSameByName( + annotation, "org.checkerframework.checker.index.qual.LessThanBottom")) { + return null; + } else if (AnnotationUtils.areSameByName( + annotation, "org.checkerframework.checker.index.qual.LessThanUnknown")) { + return Collections.emptyList(); + } else { + // The annotation is @LessThan. + return AnnotationUtils.getElementValueArray(annotation, lessThanValueElement, String.class); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java index 43cafb61bf3..2b601b0e131 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.inequality; +import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SuppressWarningsPrefix; -import java.util.Set; - /** * An internal checker that estimates which expression's values are less than other expressions' * values. @@ -15,22 +14,22 @@ */ @SuppressWarningsPrefix({"index", "lessthan"}) @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) public class LessThanChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java index b3552e7b31c..bc297041ad9 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java @@ -1,5 +1,9 @@ package org.checkerframework.checker.index.inequality; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.common.value.ValueAnnotatedTypeFactory; import org.checkerframework.common.value.ValueCheckerUtils; @@ -17,12 +21,6 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.plumelib.util.CollectionsPlume; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; - /** * Implements 3 refinement rules: * @@ -36,158 +34,156 @@ */ public class LessThanTransfer extends IndexAbstractTransfer { - public LessThanTransfer(CFAnalysis analysis) { - super(analysis); - } + public LessThanTransfer(CFAnalysis analysis) { + super(analysis); + } - /** Case 1. */ - @Override - protected void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - // left > right so right < left - // Refine right to @LessThan("left") - JavaExpression leftJe = JavaExpression.fromNode(left); - if (leftJe != null && leftJe.isUnassignableByOtherCode()) { - if (isDoubleOrFloatLiteral(leftJe)) { - return; - } - LessThanAnnotatedTypeFactory factory = - (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - List lessThanExpressions = factory.getLessThanExpressions(rightAnno); - if (lessThanExpressions == null) { - // right is already bottom, nothing to refine. - return; - } - String leftString = leftJe.toString(); - if (!lessThanExpressions.contains(leftString)) { - lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftString); - JavaExpression rightJe = JavaExpression.fromNode(right); - store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); - } - } + /** Case 1. */ + @Override + protected void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + // left > right so right < left + // Refine right to @LessThan("left") + JavaExpression leftJe = JavaExpression.fromNode(left); + if (leftJe != null && leftJe.isUnassignableByOtherCode()) { + if (isDoubleOrFloatLiteral(leftJe)) { + return; + } + LessThanAnnotatedTypeFactory factory = + (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + List lessThanExpressions = factory.getLessThanExpressions(rightAnno); + if (lessThanExpressions == null) { + // right is already bottom, nothing to refine. + return; + } + String leftString = leftJe.toString(); + if (!lessThanExpressions.contains(leftString)) { + lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftString); + JavaExpression rightJe = JavaExpression.fromNode(right); + store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); + } } + } - /** Case 2. */ - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - // left >= right so right is less than left - // Refine right to @LessThan("left + 1") + /** Case 2. */ + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + // left >= right so right is less than left + // Refine right to @LessThan("left + 1") - // left > right so right is less than left - // Refine right to @LessThan("left") - JavaExpression leftJe = JavaExpression.fromNode(left); - if (leftJe != null && leftJe.isUnassignableByOtherCode()) { - if (isDoubleOrFloatLiteral(leftJe)) { - return; - } - LessThanAnnotatedTypeFactory factory = - (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - List lessThanExpressions = factory.getLessThanExpressions(rightAnno); - if (lessThanExpressions == null) { - // right is already bottom, nothing to refine. - return; - } - String leftIncremented = incrementedExpression(leftJe); - if (!lessThanExpressions.contains(leftIncremented)) { - lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftIncremented); - JavaExpression rightJe = JavaExpression.fromNode(right); - store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); - } - } + // left > right so right is less than left + // Refine right to @LessThan("left") + JavaExpression leftJe = JavaExpression.fromNode(left); + if (leftJe != null && leftJe.isUnassignableByOtherCode()) { + if (isDoubleOrFloatLiteral(leftJe)) { + return; + } + LessThanAnnotatedTypeFactory factory = + (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + List lessThanExpressions = factory.getLessThanExpressions(rightAnno); + if (lessThanExpressions == null) { + // right is already bottom, nothing to refine. + return; + } + String leftIncremented = incrementedExpression(leftJe); + if (!lessThanExpressions.contains(leftIncremented)) { + lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftIncremented); + JavaExpression rightJe = JavaExpression.fromNode(right); + store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); + } } + } - /** Case 3. */ - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput in) { - LessThanAnnotatedTypeFactory factory = - (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - JavaExpression leftJe = JavaExpression.fromNode(n.getLeftOperand()); - if (leftJe != null && leftJe.isUnassignableByOtherCode()) { - ValueAnnotatedTypeFactory valueFactory = factory.getValueAnnotatedTypeFactory(); - Long right = ValueCheckerUtils.getMinValue(n.getRightOperand().getTree(), valueFactory); - if (right != null && 0 < right) { - // left - right < left iff 0 < right - List expressions = getLessThanExpressions(n.getLeftOperand()); - if (!isDoubleOrFloatLiteral(leftJe)) { - if (expressions == null) { - expressions = Collections.singletonList(leftJe.toString()); - } else { - expressions = CollectionsPlume.append(expressions, leftJe.toString()); - } - } - AnnotationMirror refine = factory.createLessThanQualifier(expressions); - CFValue value = analysis.createSingleAnnotationValue(refine, n.getType()); - CFStore info = in.getRegularStore(); - return new RegularTransferResult<>(finishValue(value, info), info); - } + /** Case 3. */ + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput in) { + LessThanAnnotatedTypeFactory factory = (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + JavaExpression leftJe = JavaExpression.fromNode(n.getLeftOperand()); + if (leftJe != null && leftJe.isUnassignableByOtherCode()) { + ValueAnnotatedTypeFactory valueFactory = factory.getValueAnnotatedTypeFactory(); + Long right = ValueCheckerUtils.getMinValue(n.getRightOperand().getTree(), valueFactory); + if (right != null && 0 < right) { + // left - right < left iff 0 < right + List expressions = getLessThanExpressions(n.getLeftOperand()); + if (!isDoubleOrFloatLiteral(leftJe)) { + if (expressions == null) { + expressions = Collections.singletonList(leftJe.toString()); + } else { + expressions = CollectionsPlume.append(expressions, leftJe.toString()); + } } - return super.visitNumericalSubtraction(n, in); + AnnotationMirror refine = factory.createLessThanQualifier(expressions); + CFValue value = analysis.createSingleAnnotationValue(refine, n.getType()); + CFStore info = in.getRegularStore(); + return new RegularTransferResult<>(finishValue(value, info), info); + } } + return super.visitNumericalSubtraction(n, in); + } - /** - * Return the expressions that {@code node} is less than. - * - * @param node a CFG node - * @return the expressions that {@code node} is less than - */ - private List getLessThanExpressions(Node node) { - AnnotationMirrorSet s = analysis.getValue(node).getAnnotations(); - if (s != null && !s.isEmpty()) { - LessThanAnnotatedTypeFactory factory = - (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - return factory.getLessThanExpressions( - factory.getQualifierHierarchy() - .findAnnotationInHierarchy(s, factory.LESS_THAN_UNKNOWN)); - } else { - return Collections.emptyList(); - } + /** + * Return the expressions that {@code node} is less than. + * + * @param node a CFG node + * @return the expressions that {@code node} is less than + */ + private List getLessThanExpressions(Node node) { + AnnotationMirrorSet s = analysis.getValue(node).getAnnotations(); + if (s != null && !s.isEmpty()) { + LessThanAnnotatedTypeFactory factory = + (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + return factory.getLessThanExpressions( + factory.getQualifierHierarchy().findAnnotationInHierarchy(s, factory.LESS_THAN_UNKNOWN)); + } else { + return Collections.emptyList(); } + } - /** - * Return true if {@code expr} is a double or float literal, which can't be parsed by {@link - * JavaExpressionParseUtil}. - */ - private boolean isDoubleOrFloatLiteral(JavaExpression expr) { - if (expr instanceof ValueLiteral) { - return expr.getType().getKind() == TypeKind.DOUBLE - || expr.getType().getKind() == TypeKind.FLOAT; - } else { - return false; - } + /** + * Return true if {@code expr} is a double or float literal, which can't be parsed by {@link + * JavaExpressionParseUtil}. + */ + private boolean isDoubleOrFloatLiteral(JavaExpression expr) { + if (expr instanceof ValueLiteral) { + return expr.getType().getKind() == TypeKind.DOUBLE + || expr.getType().getKind() == TypeKind.FLOAT; + } else { + return false; } + } - /** - * Return the string representation of {@code expr + 1}. - * - * @param expr a JavaExpression - * @return the string representation of {@code expr + 1} - */ - private String incrementedExpression(JavaExpression expr) { - expr = ValueCheckerUtils.optimize(expr, analysis.getTypeFactory()); - if (expr instanceof ValueLiteral) { - ValueLiteral literal = (ValueLiteral) expr; - if (literal.getValue() instanceof Number) { - long longLiteral = ((Number) literal.getValue()).longValue(); - if (longLiteral != Long.MAX_VALUE) { - return (longLiteral + 1) + "L"; - } - } + /** + * Return the string representation of {@code expr + 1}. + * + * @param expr a JavaExpression + * @return the string representation of {@code expr + 1} + */ + private String incrementedExpression(JavaExpression expr) { + expr = ValueCheckerUtils.optimize(expr, analysis.getTypeFactory()); + if (expr instanceof ValueLiteral) { + ValueLiteral literal = (ValueLiteral) expr; + if (literal.getValue() instanceof Number) { + long longLiteral = ((Number) literal.getValue()).longValue(); + if (longLiteral != Long.MAX_VALUE) { + return (longLiteral + 1) + "L"; } - - // Could do more optimization to merge with a literal at end of `exprString`. Is that - // needed? - return expr + " + 1"; + } } + + // Could do more optimization to merge with a literal at end of `exprString`. Is that + // needed? + return expr + " + 1"; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java index 85a5f6fafed..0f2b145ef59 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java @@ -3,7 +3,8 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.Tree; - +import java.util.List; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.Subsequence; import org.checkerframework.checker.index.upperbound.OffsetEquation; @@ -13,132 +14,123 @@ import org.checkerframework.framework.util.JavaExpressionParseUtil; import org.plumelib.util.CollectionsPlume; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; - /** The visitor for the Less Than Checker. */ public class LessThanVisitor extends BaseTypeVisitor { - private static final @CompilerMessageKey String FROM_GT_TO = "from.gt.to"; - - public LessThanVisitor(BaseTypeChecker checker) { - super(checker); + private static final @CompilerMessageKey String FROM_GT_TO = "from.gt.to"; + + public LessThanVisitor(BaseTypeChecker checker) { + super(checker); + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + boolean result = true; + + // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) + // occurs, from <= to. + + Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); + if (subSeq != null) { + AnnotationMirror anm; + try { + anm = + atypeFactory.getAnnotationMirrorFromJavaExpressionString( + subSeq.from, varTree, getCurrentPath()); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + anm = null; + } + + LessThanAnnotatedTypeFactory factory = getTypeFactory(); + + if (anm == null || !factory.isLessThanOrEqual(anm, subSeq.to)) { + // issue an error + checker.reportError( + valueTree, + FROM_GT_TO, + subSeq.from, + subSeq.to, + anm == null ? "@LessThanUnknown" : anm, + subSeq.to, + subSeq.to); + result = false; + } } - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - boolean result = true; - - // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) - // occurs, from <= to. - - Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); - if (subSeq != null) { - AnnotationMirror anm; - try { - anm = - atypeFactory.getAnnotationMirrorFromJavaExpressionString( - subSeq.from, varTree, getCurrentPath()); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - anm = null; - } - - LessThanAnnotatedTypeFactory factory = getTypeFactory(); - - if (anm == null || !factory.isLessThanOrEqual(anm, subSeq.to)) { - // issue an error - checker.reportError( - valueTree, - FROM_GT_TO, - subSeq.from, - subSeq.to, - anm == null ? "@LessThanUnknown" : anm, - subSeq.to, - subSeq.to); - result = false; - } + result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; + return result; + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // If value is less than all expressions in the annotation in varType, + // using the Value Checker, then skip the common assignment check. + // Also skip the check if the only expression is "a + 1" and the valueTree is "a". + List expressions = + getTypeFactory() + .getLessThanExpressions( + varType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN)); + if (expressions != null) { + boolean isLessThan = true; + for (String expression : expressions) { + if (!atypeFactory.isLessThanByValue(valueTree, expression, getCurrentPath())) { + isLessThan = false; } - - result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; - return result; - } - - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // If value is less than all expressions in the annotation in varType, - // using the Value Checker, then skip the common assignment check. - // Also skip the check if the only expression is "a + 1" and the valueTree is "a". - List expressions = - getTypeFactory() - .getLessThanExpressions( - varType.getEffectiveAnnotationInHierarchy( - atypeFactory.LESS_THAN_UNKNOWN)); - if (expressions != null) { - boolean isLessThan = true; - for (String expression : expressions) { - if (!atypeFactory.isLessThanByValue(valueTree, expression, getCurrentPath())) { - isLessThan = false; - } - } - if (!isLessThan && expressions.size() == 1) { - String expression = expressions.get(0); - if (expression.endsWith(" + 1")) { - String value = expression.substring(0, expression.length() - 4); - if (valueTree.getKind() == Tree.Kind.IDENTIFIER) { - String id = ((IdentifierTree) valueTree).getName().toString(); - if (id.equals(value)) { - isLessThan = true; - } - } - } - } - - if (isLessThan) { - // Print the messages because super isn't called. - commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); - commonAssignmentCheckEndDiagnostic( - true, "isLessThan", varType, valueType, valueTree); - // skip call to super, everything is OK. - return true; + } + if (!isLessThan && expressions.size() == 1) { + String expression = expressions.get(0); + if (expression.endsWith(" + 1")) { + String value = expression.substring(0, expression.length() - 4); + if (valueTree.getKind() == Tree.Kind.IDENTIFIER) { + String id = ((IdentifierTree) valueTree).getName().toString(); + if (id.equals(value)) { + isLessThan = true; } + } } - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } + + if (isLessThan) { + // Print the messages because super isn't called. + commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); + commonAssignmentCheckEndDiagnostic(true, "isLessThan", varType, valueType, valueTree); + // skip call to super, everything is OK. + return true; + } } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - AnnotationMirror exprLTAnno = - exprType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN); + AnnotationMirror exprLTAnno = + exprType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN); - if (exprLTAnno != null) { - LessThanAnnotatedTypeFactory factory = getTypeFactory(); - List initialAnnotations = factory.getLessThanExpressions(exprLTAnno); + if (exprLTAnno != null) { + LessThanAnnotatedTypeFactory factory = getTypeFactory(); + List initialAnnotations = factory.getLessThanExpressions(exprLTAnno); - if (initialAnnotations != null) { - List updatedAnnotations = - CollectionsPlume.mapList( - annotation -> - OffsetEquation.createOffsetFromJavaExpression(annotation) - .toString(), - initialAnnotations); + if (initialAnnotations != null) { + List updatedAnnotations = + CollectionsPlume.mapList( + annotation -> OffsetEquation.createOffsetFromJavaExpression(annotation).toString(), + initialAnnotations); - exprType.replaceAnnotation( - atypeFactory.createLessThanQualifier(updatedAnnotations)); - } - } - - return super.isTypeCastSafe(castType, exprType); + exprType.replaceAnnotation(atypeFactory.createLessThanQualifier(updatedAnnotations)); + } } + + return super.isTypeCastSafe(castType, exprType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java index b799608322d..05c6be9f793 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java @@ -7,7 +7,12 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker; import org.checkerframework.checker.index.IndexMethodIdentifier; import org.checkerframework.checker.index.inequality.LessThanAnnotatedTypeFactory; @@ -44,14 +49,6 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.TreeUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; - /** * Implements the introduction rules for the Lower Bound Checker. * @@ -93,388 +90,378 @@ */ public class LowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker { - /** The canonical @{@link GTENegativeOne} annotation. */ - public final AnnotationMirror GTEN1 = - AnnotationBuilder.fromClass(elements, GTENegativeOne.class); - - /** The canonical @{@link NonNegative} annotation. */ - public final AnnotationMirror NN = AnnotationBuilder.fromClass(elements, NonNegative.class); - - /** The canonical @{@link Positive} annotation. */ - public final AnnotationMirror POS = AnnotationBuilder.fromClass(elements, Positive.class); - - /** The bottom annotation. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, LowerBoundBottom.class); - - /** The canonical @{@link LowerBoundUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, LowerBoundUnknown.class); - - /** The canonical @{@link PolyLowerBound} annotation. */ - public final AnnotationMirror POLY = - AnnotationBuilder.fromClass(elements, PolyLowerBound.class); - - /** Predicates about method calls. */ - private final IndexMethodIdentifier imf; - - /** - * Create a new LowerBoundAnnotatedTypeFactory. - * - * @param checker the type-checker - */ - public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Any annotations that are aliased to @NonNegative, @Positive, or @GTENegativeOne must also - // be aliased in the constructor of ValueAnnotatedTypeFactory to the appropriate - // @IntRangeFrom* annotation. - addAliasedTypeAnnotation(IndexFor.class, NN); - addAliasedTypeAnnotation(IndexOrLow.class, GTEN1); - addAliasedTypeAnnotation(IndexOrHigh.class, NN); - addAliasedTypeAnnotation(LengthOf.class, NN); - addAliasedTypeAnnotation(PolyIndex.class, POLY); - addAliasedTypeAnnotation(SubstringIndexFor.class, GTEN1); - - addAliasedTypeAnnotation(SignedPositive.class, NN); - addAliasedTypeAnnotation(SignednessGlb.class, NN); - - imf = new IndexMethodIdentifier(this); - - this.postInit(); + /** The canonical @{@link GTENegativeOne} annotation. */ + public final AnnotationMirror GTEN1 = AnnotationBuilder.fromClass(elements, GTENegativeOne.class); + + /** The canonical @{@link NonNegative} annotation. */ + public final AnnotationMirror NN = AnnotationBuilder.fromClass(elements, NonNegative.class); + + /** The canonical @{@link Positive} annotation. */ + public final AnnotationMirror POS = AnnotationBuilder.fromClass(elements, Positive.class); + + /** The bottom annotation. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, LowerBoundBottom.class); + + /** The canonical @{@link LowerBoundUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, LowerBoundUnknown.class); + + /** The canonical @{@link PolyLowerBound} annotation. */ + public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyLowerBound.class); + + /** Predicates about method calls. */ + private final IndexMethodIdentifier imf; + + /** + * Create a new LowerBoundAnnotatedTypeFactory. + * + * @param checker the type-checker + */ + public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Any annotations that are aliased to @NonNegative, @Positive, or @GTENegativeOne must also + // be aliased in the constructor of ValueAnnotatedTypeFactory to the appropriate + // @IntRangeFrom* annotation. + addAliasedTypeAnnotation(IndexFor.class, NN); + addAliasedTypeAnnotation(IndexOrLow.class, GTEN1); + addAliasedTypeAnnotation(IndexOrHigh.class, NN); + addAliasedTypeAnnotation(LengthOf.class, NN); + addAliasedTypeAnnotation(PolyIndex.class, POLY); + addAliasedTypeAnnotation(SubstringIndexFor.class, GTEN1); + + addAliasedTypeAnnotation(SignedPositive.class, NN); + addAliasedTypeAnnotation(SignednessGlb.class, NN); + + imf = new IndexMethodIdentifier(this); + + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList( + Positive.class, + NonNegative.class, + GTENegativeOne.class, + LowerBoundUnknown.class, + PolyLowerBound.class, + LowerBoundBottom.class)); + } + + /** + * Takes a value type (only interesting if it's an IntVal), and converts it to a lower bound type. + * If the new lower bound type is more specific than type, convert type to that type. + * + * @param valueType the Value Checker type + * @param type the current lower bound type of the expression being evaluated + */ + private void addLowerBoundTypeFromValueType( + AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { + AnnotationMirror anm = getLowerBoundAnnotationFromValueType(valueType); + if (!type.hasAnnotationInHierarchy(UNKNOWN)) { + if (!areSameByClass(anm, LowerBoundUnknown.class)) { + type.addAnnotation(anm); + } + return; } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - Positive.class, - NonNegative.class, - GTENegativeOne.class, - LowerBoundUnknown.class, - PolyLowerBound.class, - LowerBoundBottom.class)); + if (typeHierarchy.isSubtypeShallowEffective(anm, type)) { + type.replaceAnnotation(anm); } - - /** - * Takes a value type (only interesting if it's an IntVal), and converts it to a lower bound - * type. If the new lower bound type is more specific than type, convert type to that type. - * - * @param valueType the Value Checker type - * @param type the current lower bound type of the expression being evaluated - */ - private void addLowerBoundTypeFromValueType( - AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { - AnnotationMirror anm = getLowerBoundAnnotationFromValueType(valueType); - if (!type.hasAnnotationInHierarchy(UNKNOWN)) { - if (!areSameByClass(anm, LowerBoundUnknown.class)) { - type.addAnnotation(anm); - } - return; - } - if (typeHierarchy.isSubtypeShallowEffective(anm, type)) { - type.replaceAnnotation(anm); - } + } + + /** Handles cases 1, 2, and 3. */ + @Override + public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(element, type); + if (element != null) { + AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element); + addLowerBoundTypeFromValueType(valueType, type); } - - /** Handles cases 1, 2, and 3. */ - @Override - public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(element, type); - if (element != null) { - AnnotatedTypeMirror valueType = - getValueAnnotatedTypeFactory().getAnnotatedType(element); - addLowerBoundTypeFromValueType(valueType, type); - } + } + + /** Handles cases 1, 2, and 3. */ + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); + // If dataflow shouldn't be used to compute this type, then do not use the result from + // the Value Checker, because dataflow is used to compute that type. (Without this, + // "int i = 1; --i;" fails.) + if (tree != null + // Necessary to check that an ajava file isn't being parsed, because the call + // to the Value Checker's getAnnotatedType() method can fail during parsing: + // the check in GenericAnnotatedTypeFactory#addComputedTypeAnnotations only + // checks if the **current** type factory is parsing, not whether the parent + // checker's type factory is parsing. + && !ajavaTypes.isParsing() + && TreeUtils.isExpressionTree(tree) + && (getUseFlow() || tree instanceof LiteralTree)) { + AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); + addLowerBoundTypeFromValueType(valueType, type); } - - /** Handles cases 1, 2, and 3. */ - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(tree, type); - // If dataflow shouldn't be used to compute this type, then do not use the result from - // the Value Checker, because dataflow is used to compute that type. (Without this, - // "int i = 1; --i;" fails.) - if (tree != null - // Necessary to check that an ajava file isn't being parsed, because the call - // to the Value Checker's getAnnotatedType() method can fail during parsing: - // the check in GenericAnnotatedTypeFactory#addComputedTypeAnnotations only - // checks if the **current** type factory is parsing, not whether the parent - // checker's type factory is parsing. - && !ajavaTypes.isParsing() - && TreeUtils.isExpressionTree(tree) - && (getUseFlow() || tree instanceof LiteralTree)) { - AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); - addLowerBoundTypeFromValueType(valueType, type); - } + } + + /** Returns the Value Checker's annotated type factory. */ + public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); + } + + /** Returns the SearchIndexFor Checker's annotated type factory. */ + public SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SearchIndexChecker.class); + } + + /** Returns the LessThan Checker's annotated type factory. */ + public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(LessThanChecker.class); + } + + /** Returns the type in the lower bound hierarchy that a Value Checker type corresponds to. */ + private AnnotationMirror getLowerBoundAnnotationFromValueType(AnnotatedTypeMirror valueType) { + Range possibleValues = + ValueCheckerUtils.getPossibleValues(valueType, getValueAnnotatedTypeFactory()); + // possibleValues is null if the Value Checker does not have any estimate. + if (possibleValues == null) { + // possibleValues is null if there is no IntVal annotation on the type - such as + // when there is a BottomVal annotation. In that case, give this the LBC's bottom type. + if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { + return BOTTOM; + } + return UNKNOWN; } - - /** Returns the Value Checker's annotated type factory. */ - public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); + // The annotation of the whole list is the min of the list. + long lvalMin = possibleValues.from; + // Turn it into an integer. + int valMin = (int) Math.max(Math.min(Integer.MAX_VALUE, lvalMin), Integer.MIN_VALUE); + return anmFromVal(valMin); + } + + /** Determine the annotation that should be associated with a literal. */ + /*package-private*/ AnnotationMirror anmFromVal(long val) { + if (val >= 1) { + return POS; + } else if (val >= 0) { + return NN; + } else if (val >= -1) { + return GTEN1; + } else { + return UNKNOWN; } + } - /** Returns the SearchIndexFor Checker's annotated type factory. */ - public SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SearchIndexChecker.class); - } + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new LowerBoundTreeAnnotator(this), super.createTreeAnnotator()); + } - /** Returns the LessThan Checker's annotated type factory. */ - public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(LessThanChecker.class); + private class LowerBoundTreeAnnotator extends TreeAnnotator { + public LowerBoundTreeAnnotator(AnnotatedTypeFactory annotatedTypeFactory) { + super(annotatedTypeFactory); } - /** Returns the type in the lower bound hierarchy that a Value Checker type corresponds to. */ - private AnnotationMirror getLowerBoundAnnotationFromValueType(AnnotatedTypeMirror valueType) { - Range possibleValues = - ValueCheckerUtils.getPossibleValues(valueType, getValueAnnotatedTypeFactory()); - // possibleValues is null if the Value Checker does not have any estimate. - if (possibleValues == null) { - // possibleValues is null if there is no IntVal annotation on the type - such as - // when there is a BottomVal annotation. In that case, give this the LBC's bottom type. - if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { - return BOTTOM; - } - return UNKNOWN; - } - // The annotation of the whole list is the min of the list. - long lvalMin = possibleValues.from; - // Turn it into an integer. - int valMin = (int) Math.max(Math.min(Integer.MAX_VALUE, lvalMin), Integer.MIN_VALUE); - return anmFromVal(valMin); + /** + * Sets typeDst to the immediate supertype of typeSrc, unless typeSrc is already Positive. + * Implements the following transitions: + * + *

+     *      pos → pos
+     *      nn → pos
+     *      gte-1 → nn
+     *      lbu → lbu
+     *  
+ */ + private void promoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { + if (typeSrc.hasAnnotation(POS)) { + typeDst.replaceAnnotation(POS); + } else if (typeSrc.hasAnnotation(NN)) { + typeDst.replaceAnnotation(POS); + } else if (typeSrc.hasAnnotation(GTEN1)) { + typeDst.replaceAnnotation(NN); + } else { // Only unknown is left. + typeDst.replaceAnnotation(UNKNOWN); + } } - /** Determine the annotation that should be associated with a literal. */ - /*package-private*/ AnnotationMirror anmFromVal(long val) { - if (val >= 1) { - return POS; - } else if (val >= 0) { - return NN; - } else if (val >= -1) { - return GTEN1; - } else { - return UNKNOWN; - } + /** + * Sets typeDst to the immediate subtype of typeSrc, unless typeSrc is already + * LowerBoundUnknown. Implements the following transitions: + * + *
+     *       pos → nn
+     *       nn → gte-1
+     *       gte-1, lbu → lbu
+     *  
+ */ + private void demoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { + if (typeSrc.hasAnnotation(POS)) { + typeDst.replaceAnnotation(NN); + } else if (typeSrc.hasAnnotation(NN)) { + typeDst.replaceAnnotation(GTEN1); + } else { // GTEN1 and UNKNOWN both become UNKNOWN. + typeDst.replaceAnnotation(UNKNOWN); + } } + /** Call increment and decrement helper functions. Handles cases 4, 5 and 6. */ @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - new LowerBoundTreeAnnotator(this), super.createTreeAnnotator()); - } - - private class LowerBoundTreeAnnotator extends TreeAnnotator { - public LowerBoundTreeAnnotator(AnnotatedTypeFactory annotatedTypeFactory) { - super(annotatedTypeFactory); - } - - /** - * Sets typeDst to the immediate supertype of typeSrc, unless typeSrc is already Positive. - * Implements the following transitions: - * - *
-         *      pos → pos
-         *      nn → pos
-         *      gte-1 → nn
-         *      lbu → lbu
-         *  
- */ - private void promoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { - if (typeSrc.hasAnnotation(POS)) { - typeDst.replaceAnnotation(POS); - } else if (typeSrc.hasAnnotation(NN)) { - typeDst.replaceAnnotation(POS); - } else if (typeSrc.hasAnnotation(GTEN1)) { - typeDst.replaceAnnotation(NN); - } else { // Only unknown is left. - typeDst.replaceAnnotation(UNKNOWN); - } - } - - /** - * Sets typeDst to the immediate subtype of typeSrc, unless typeSrc is already - * LowerBoundUnknown. Implements the following transitions: - * - *
-         *       pos → nn
-         *       nn → gte-1
-         *       gte-1, lbu → lbu
-         *  
- */ - private void demoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { - if (typeSrc.hasAnnotation(POS)) { - typeDst.replaceAnnotation(NN); - } else if (typeSrc.hasAnnotation(NN)) { - typeDst.replaceAnnotation(GTEN1); - } else { // GTEN1 and UNKNOWN both become UNKNOWN. - typeDst.replaceAnnotation(UNKNOWN); - } - } - - /** Call increment and decrement helper functions. Handles cases 4, 5 and 6. */ - @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror typeDst) { - AnnotatedTypeMirror typeSrc = getAnnotatedType(tree.getExpression()); - switch (tree.getKind()) { - case PREFIX_INCREMENT: - promoteType(typeSrc, typeDst); - break; - case PREFIX_DECREMENT: - demoteType(typeSrc, typeDst); - break; - case POSTFIX_INCREMENT: - case POSTFIX_DECREMENT: - // Do nothing. The CF should take care of these itself. - break; - case BITWISE_COMPLEMENT: - handleBitWiseComplement( - getSearchIndexAnnotatedTypeFactory() - .getAnnotatedType(tree.getExpression()), - typeDst); - break; - default: - break; - } - return super.visitUnary(tree, typeDst); - } - - /** - * Bitwise complement converts between {@code @NegativeIndexFor} and {@code @IndexOrHigh}. - * This handles the lowerbound part of that type, so the result is converted to - * {@code @NonNegative}. - * - * @param searchIndexType the type of an expression in a bitwise complement. For instance, - * in {@code ~x}, this is the type of {@code x}. - * @param typeDst the type of the entire bitwise complement expression. It is modified by - * this method. - */ - private void handleBitWiseComplement( - AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { - if (containsSameByClass(searchIndexType.getAnnotations(), NegativeIndexFor.class)) { - typeDst.addAnnotation(NN); - } - } - - /** Special handling for Math.max. The return is the GLB of the arguments. Case 7. */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (imf.isMathMax(tree)) { - ExpressionTree left = tree.getArguments().get(0); - ExpressionTree right = tree.getArguments().get(1); - AnnotatedTypeMirror leftType = getAnnotatedType(left); - AnnotatedTypeMirror rightType = getAnnotatedType(right); - type.replaceAnnotation( - qualHierarchy.greatestLowerBoundShallow( - leftType.getAnnotationInHierarchy(POS), - leftType.getUnderlyingType(), - rightType.getAnnotationInHierarchy(POS), - rightType.getUnderlyingType())); - } - return super.visitMethodInvocation(tree, type); - } - - /** - * For dealing with array length expressions. Looks for array length accesses specifically, - * then dispatches to the MinLen checker to determine the length of the relevant array. If - * it's found, use it to give the expression a type. Case 8. - */ - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - Integer minLen = getMinLenFromMemberSelectTree(tree); - if (minLen != null) { - type.replaceAnnotation(anmFromVal(minLen)); - } - return super.visitMemberSelect(tree, type); - } - - /** - * Does not dispatch to binary operator helper methods. The Lower Bound Checker handles - * binary operations via its transfer function. - */ - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.addAnnotation(UNKNOWN); - return super.visitBinary(tree, type); - } + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror typeDst) { + AnnotatedTypeMirror typeSrc = getAnnotatedType(tree.getExpression()); + switch (tree.getKind()) { + case PREFIX_INCREMENT: + promoteType(typeSrc, typeDst); + break; + case PREFIX_DECREMENT: + demoteType(typeSrc, typeDst); + break; + case POSTFIX_INCREMENT: + case POSTFIX_DECREMENT: + // Do nothing. The CF should take care of these itself. + break; + case BITWISE_COMPLEMENT: + handleBitWiseComplement( + getSearchIndexAnnotatedTypeFactory().getAnnotatedType(tree.getExpression()), typeDst); + break; + default: + break; + } + return super.visitUnary(tree, typeDst); } /** - * Looks up the minlen of a member select tree. Returns null if the tree doesn't represent an - * array's length field. + * Bitwise complement converts between {@code @NegativeIndexFor} and {@code @IndexOrHigh}. This + * handles the lowerbound part of that type, so the result is converted to {@code @NonNegative}. + * + * @param searchIndexType the type of an expression in a bitwise complement. For instance, in + * {@code ~x}, this is the type of {@code x}. + * @param typeDst the type of the entire bitwise complement expression. It is modified by this + * method. */ - /*package-private*/ @Nullable Integer getMinLenFromMemberSelectTree(MemberSelectTree tree) { - if (TreeUtils.isArrayLengthAccess(tree)) { - return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); - } - return null; + private void handleBitWiseComplement( + AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { + if (containsSameByClass(searchIndexType.getAnnotations(), NegativeIndexFor.class)) { + typeDst.addAnnotation(NN); + } } - /** - * Looks up the minlen of a method invocation tree. Returns null if the tree doesn't represent - * an string length method. - */ - /*package-private*/ @Nullable Integer getMinLenFromMethodInvocationTree( - MethodInvocationTree tree) { - if (imf.isLengthOfMethodInvocation(tree)) { - return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); - } - return null; + /** Special handling for Math.max. The return is the GLB of the arguments. Case 7. */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (imf.isMathMax(tree)) { + ExpressionTree left = tree.getArguments().get(0); + ExpressionTree right = tree.getArguments().get(1); + AnnotatedTypeMirror leftType = getAnnotatedType(left); + AnnotatedTypeMirror rightType = getAnnotatedType(right); + type.replaceAnnotation( + qualHierarchy.greatestLowerBoundShallow( + leftType.getAnnotationInHierarchy(POS), leftType.getUnderlyingType(), + rightType.getAnnotationInHierarchy(POS), rightType.getUnderlyingType())); + } + return super.visitMethodInvocation(tree, type); } /** - * Given a multiplication, return its type if the LBC special-cases it, or null otherwise. - * - *

The LBC special-cases {@code Math.random() * array.length} and {@code Random.nextDouble() - * * array.length}. - * - * @param node a multiplication node that may need special casing - * @return an AnnotationMirror representing the result if the special case is valid, or null if - * not + * For dealing with array length expressions. Looks for array length accesses specifically, then + * dispatches to the MinLen checker to determine the length of the relevant array. If it's + * found, use it to give the expression a type. Case 8. */ - /*package-private*/ @Nullable AnnotationMirror checkForMathRandomSpecialCase( - NumericalMultiplicationNode node) { - AnnotationMirror forwardRes = - checkForMathRandomSpecialCase( - node.getLeftOperand().getTree(), node.getRightOperand().getTree()); - if (forwardRes != null) { - return forwardRes; - } - AnnotationMirror backwardsRes = - checkForMathRandomSpecialCase( - node.getRightOperand().getTree(), node.getLeftOperand().getTree()); - if (backwardsRes != null) { - return backwardsRes; - } - return null; + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + Integer minLen = getMinLenFromMemberSelectTree(tree); + if (minLen != null) { + type.replaceAnnotation(anmFromVal(minLen)); + } + return super.visitMemberSelect(tree, type); } /** - * Return a non-null value if randTree is a call to Math.random() or Random.nextDouble(), and - * arrLenTree is someArray.length. + * Does not dispatch to binary operator helper methods. The Lower Bound Checker handles binary + * operations via its transfer function. */ - private @Nullable AnnotationMirror checkForMathRandomSpecialCase( - Tree randTree, Tree arrLenTree) { - if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION - && TreeUtils.isArrayLengthAccess(arrLenTree)) { - MethodInvocationTree miTree = (MethodInvocationTree) randTree; - - if (imf.isMathRandom(miTree, processingEnv)) { - // This is Math.random() * array.length, which must be NonNegative - return NN; - } - - if (imf.isRandomNextDouble(miTree, processingEnv)) { - // This is Random.nextDouble() * array.length, which must be NonNegative - return NN; - } - } - return null; + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.addAnnotation(UNKNOWN); + return super.visitBinary(tree, type); } - - /** Checks if the expression is non-negative, i.e. it has Positive on NonNegative annotation. */ - public boolean isNonNegative(Tree tree) { - // TODO: consolidate with the isNonNegative method in LowerBoundTransfer - AnnotatedTypeMirror treeType = getAnnotatedType(tree); - return treeType.hasAnnotation(NonNegative.class) || treeType.hasAnnotation(Positive.class); + } + + /** + * Looks up the minlen of a member select tree. Returns null if the tree doesn't represent an + * array's length field. + */ + /*package-private*/ @Nullable Integer getMinLenFromMemberSelectTree(MemberSelectTree tree) { + if (TreeUtils.isArrayLengthAccess(tree)) { + return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); + } + return null; + } + + /** + * Looks up the minlen of a method invocation tree. Returns null if the tree doesn't represent an + * string length method. + */ + /*package-private*/ @Nullable Integer getMinLenFromMethodInvocationTree( + MethodInvocationTree tree) { + if (imf.isLengthOfMethodInvocation(tree)) { + return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); + } + return null; + } + + /** + * Given a multiplication, return its type if the LBC special-cases it, or null otherwise. + * + *

The LBC special-cases {@code Math.random() * array.length} and {@code Random.nextDouble() * + * array.length}. + * + * @param node a multiplication node that may need special casing + * @return an AnnotationMirror representing the result if the special case is valid, or null if + * not + */ + /*package-private*/ @Nullable AnnotationMirror checkForMathRandomSpecialCase( + NumericalMultiplicationNode node) { + AnnotationMirror forwardRes = + checkForMathRandomSpecialCase( + node.getLeftOperand().getTree(), node.getRightOperand().getTree()); + if (forwardRes != null) { + return forwardRes; + } + AnnotationMirror backwardsRes = + checkForMathRandomSpecialCase( + node.getRightOperand().getTree(), node.getLeftOperand().getTree()); + if (backwardsRes != null) { + return backwardsRes; + } + return null; + } + + /** + * Return a non-null value if randTree is a call to Math.random() or Random.nextDouble(), and + * arrLenTree is someArray.length. + */ + private @Nullable AnnotationMirror checkForMathRandomSpecialCase(Tree randTree, Tree arrLenTree) { + if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION + && TreeUtils.isArrayLengthAccess(arrLenTree)) { + MethodInvocationTree miTree = (MethodInvocationTree) randTree; + + if (imf.isMathRandom(miTree, processingEnv)) { + // This is Math.random() * array.length, which must be NonNegative + return NN; + } + + if (imf.isRandomNextDouble(miTree, processingEnv)) { + // This is Random.nextDouble() * array.length, which must be NonNegative + return NN; + } } + return null; + } + + /** Checks if the expression is non-negative, i.e. it has Positive on NonNegative annotation. */ + public boolean isNonNegative(Tree tree) { + // TODO: consolidate with the isNonNegative method in LowerBoundTransfer + AnnotatedTypeMirror treeType = getAnnotatedType(tree); + return treeType.hasAnnotation(NonNegative.class) || treeType.hasAnnotation(Positive.class); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java index adaa12abd55..13370f9d74f 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java @@ -1,5 +1,7 @@ package org.checkerframework.checker.index.lowerbound; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.checker.index.inequality.LessThanChecker; import org.checkerframework.checker.index.searchindex.SearchIndexChecker; import org.checkerframework.checker.signature.qual.FullyQualifiedName; @@ -8,9 +10,6 @@ import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SuppressWarningsPrefix; -import java.util.HashSet; -import java.util.Set; - /** * A type-checker for preventing fixed-length sequences such as arrays or strings from being * accessed with values that are too low. Normally bundled as part of the Index Checker. @@ -19,51 +18,51 @@ */ @SuppressWarningsPrefix({"index", "lowerbound"}) @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) public class LowerBoundChecker extends BaseTypeChecker { - /** - * These collection classes have some subtypes whose length can change and some subtypes whose - * length cannot change. Lower bound checker warnings are skipped at uses of them. - */ - private final HashSet collectionBaseTypeNames; + /** + * These collection classes have some subtypes whose length can change and some subtypes whose + * length cannot change. Lower bound checker warnings are skipped at uses of them. + */ + private final HashSet collectionBaseTypeNames; - /** - * A type-checker for preventing fixed-length sequences such as arrays or strings from being - * accessed with values that are too low. Normally bundled as part of the Index Checker. - */ - public LowerBoundChecker() { - Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; - collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); - for (Class collectionBaseClass : collectionBaseClasses) { - collectionBaseTypeNames.add(collectionBaseClass.getName()); - } + /** + * A type-checker for preventing fixed-length sequences such as arrays or strings from being + * accessed with values that are too low. Normally bundled as part of the Index Checker. + */ + public LowerBoundChecker() { + Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; + collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); + for (Class collectionBaseClass : collectionBaseClasses) { + collectionBaseTypeNames.add(collectionBaseClass.getName()); } + } - @Override - public boolean shouldSkipUses(@FullyQualifiedName String typeName) { - if (collectionBaseTypeNames.contains(typeName)) { - return true; - } - return super.shouldSkipUses(typeName); + @Override + public boolean shouldSkipUses(@FullyQualifiedName String typeName) { + if (collectionBaseTypeNames.contains(typeName)) { + return true; } + return super.shouldSkipUses(typeName); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - checkers.add(LessThanChecker.class); - checkers.add(SearchIndexChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + checkers.add(LessThanChecker.class); + checkers.add(SearchIndexChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java index fdd566a4816..c4e626939b7 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java @@ -5,7 +5,10 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.checker.index.IndexRefinementInfo; import org.checkerframework.checker.index.qual.GTENegativeOne; @@ -37,12 +40,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; - /** * Implements dataflow refinement rules based on tests: <, >, ==, and their derivatives. * @@ -163,777 +160,765 @@ */ public class LowerBoundTransfer extends IndexAbstractTransfer { - /** The canonical {@link GTENegativeOne} annotation. */ - public final AnnotationMirror GTEN1; - - /** The canonical {@link NonNegative} annotation. */ - public final AnnotationMirror NN; - - /** The canonical {@link Positive} annotation. */ - public final AnnotationMirror POS; - - /** The canonical {@link LowerBoundUnknown} annotation. */ - public final AnnotationMirror UNKNOWN; - - /** The annotated type factory. */ - private final LowerBoundAnnotatedTypeFactory atypeFactory; - - /** - * Create a new LowerBoundTransfer. - * - * @param analysis the CFAnalysis - */ - public LowerBoundTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (LowerBoundAnnotatedTypeFactory) analysis.getTypeFactory(); - // Initialize qualifiers. - GTEN1 = atypeFactory.GTEN1; - NN = atypeFactory.NN; - POS = atypeFactory.POS; - UNKNOWN = atypeFactory.UNKNOWN; - } - - /** - * Refines GTEN1 to NN if it is not equal to -1, and NN to Pos if it is not equal to 0. - * Implements case 7. - * - * @param mLiteral a potential literal - * @param otherNode the node on the other side of the ==/!= - * @param otherAnno the annotation of the other side of the ==/!= - */ - private void notEqualToValue( - Node mLiteral, Node otherNode, AnnotationMirror otherAnno, CFStore store) { - - Long integerLiteral = - ValueCheckerUtils.getExactValue( - mLiteral.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - - if (integerLiteral == null) { - return; - } - long intLiteral = integerLiteral.longValue(); - - if (intLiteral == 0) { - if (atypeFactory.areSameByClass(otherAnno, NonNegative.class)) { - List internals = splitAssignments(otherNode); - for (Node internal : internals) { - JavaExpression je = JavaExpression.fromNode(internal); - store.insertValue(je, POS); - } - } - } else if (intLiteral == -1) { - if (atypeFactory.areSameByClass(otherAnno, GTENegativeOne.class)) { - List internals = splitAssignments(otherNode); - for (Node internal : internals) { - JavaExpression je = JavaExpression.fromNode(internal); - store.insertValue(je, NN); - } - } - } - } - - /** - * Implements the transfer rules for both equal nodes and not-equals nodes (i.e. cases 5, 6, - * 32). - */ - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult result, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - result = - super.strengthenAnnotationOfEqualTo( - result, firstNode, secondNode, firstValue, secondValue, notEqualTo); - - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, secondNode, firstNode); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } - - // There is also special processing to look for literals on one side of the equals and a - // GTEN1 or NN on the other, so that those types can be promoted in the branch where their - // values are not equal to certain literals. - CFStore notEqualsStore = notEqualTo ? rfi.thenStore : rfi.elseStore; - notEqualToValue(rfi.left, rfi.right, rfi.rightAnno, notEqualsStore); - notEqualToValue(rfi.right, rfi.left, rfi.leftAnno, notEqualsStore); - - notEqualsLessThan(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, notEqualsStore); - notEqualsLessThan(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, notEqualsStore); - - return rfi.newResult; - } - - /** Implements case 32. */ - private void notEqualsLessThan( - Node leftNode, - AnnotationMirror leftAnno, - Node otherNode, - AnnotationMirror otherAnno, - CFStore store) { - if (!isNonNegative(leftAnno) || !isNonNegative(otherAnno)) { - return; - } - JavaExpression otherJe = JavaExpression.fromNode(otherNode); - if (atypeFactory - .getLessThanAnnotatedTypeFactory() - .isLessThanOrEqual(leftNode.getTree(), otherJe.toString())) { - store.insertValue(otherJe, POS); - } - } - - /** - * The implementation of the algorithm for refining a > test. Changes the type of left (the - * greater one) to one closer to bottom than the type of right. Can't call the promote function - * from the ATF directly because a new expression isn't introduced here - the modifications have - * to be made to an existing one. - * - *

This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described - * in the Javadoc of this class. - */ - @Override - protected void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - - if (rightAnno == null || leftAnno == null) { - return; - } - - JavaExpression leftJe = JavaExpression.fromNode(left); - - if (AnnotationUtils.areSame(rightAnno, GTEN1)) { - store.insertValue(leftJe, NN); - return; - } - if (AnnotationUtils.areSame(rightAnno, NN)) { - store.insertValue(leftJe, POS); - return; - } - if (AnnotationUtils.areSame(rightAnno, POS)) { - store.insertValue(leftJe, POS); - return; - } - } - - /** - * Refines left to exactly the level of right, since in the worst case they're equal. Modifies - * an existing type in the store, but has to be careful not to overwrite a more precise existing - * type. - * - *

This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described - * in this class's Javadoc. - */ - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - - if (rightAnno == null || leftAnno == null) { - return; - } - - JavaExpression leftJe = JavaExpression.fromNode(left); - - AnnotationMirror newLBType = - atypeFactory - .getQualifierHierarchy() - .greatestLowerBoundShallow( - rightAnno, right.getType(), leftAnno, left.getType()); - - store.insertValue(leftJe, newLBType); - } - - /** - * Returns an annotation mirror representing the result of subtracting one from {@code oldAnm}. + /** The canonical {@link GTENegativeOne} annotation. */ + public final AnnotationMirror GTEN1; + + /** The canonical {@link NonNegative} annotation. */ + public final AnnotationMirror NN; + + /** The canonical {@link Positive} annotation. */ + public final AnnotationMirror POS; + + /** The canonical {@link LowerBoundUnknown} annotation. */ + public final AnnotationMirror UNKNOWN; + + /** The annotated type factory. */ + private final LowerBoundAnnotatedTypeFactory atypeFactory; + + /** + * Create a new LowerBoundTransfer. + * + * @param analysis the CFAnalysis + */ + public LowerBoundTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (LowerBoundAnnotatedTypeFactory) analysis.getTypeFactory(); + // Initialize qualifiers. + GTEN1 = atypeFactory.GTEN1; + NN = atypeFactory.NN; + POS = atypeFactory.POS; + UNKNOWN = atypeFactory.UNKNOWN; + } + + /** + * Refines GTEN1 to NN if it is not equal to -1, and NN to Pos if it is not equal to 0. Implements + * case 7. + * + * @param mLiteral a potential literal + * @param otherNode the node on the other side of the ==/!= + * @param otherAnno the annotation of the other side of the ==/!= + */ + private void notEqualToValue( + Node mLiteral, Node otherNode, AnnotationMirror otherAnno, CFStore store) { + + Long integerLiteral = + ValueCheckerUtils.getExactValue( + mLiteral.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + + if (integerLiteral == null) { + return; + } + long intLiteral = integerLiteral.longValue(); + + if (intLiteral == 0) { + if (atypeFactory.areSameByClass(otherAnno, NonNegative.class)) { + List internals = splitAssignments(otherNode); + for (Node internal : internals) { + JavaExpression je = JavaExpression.fromNode(internal); + store.insertValue(je, POS); + } + } + } else if (intLiteral == -1) { + if (atypeFactory.areSameByClass(otherAnno, GTENegativeOne.class)) { + List internals = splitAssignments(otherNode); + for (Node internal : internals) { + JavaExpression je = JavaExpression.fromNode(internal); + store.insertValue(je, NN); + } + } + } + } + + /** + * Implements the transfer rules for both equal nodes and not-equals nodes (i.e. cases 5, 6, 32). + */ + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult result, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + result = + super.strengthenAnnotationOfEqualTo( + result, firstNode, secondNode, firstValue, secondValue, notEqualTo); + + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, secondNode, firstNode); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; + } + + // There is also special processing to look for literals on one side of the equals and a + // GTEN1 or NN on the other, so that those types can be promoted in the branch where their + // values are not equal to certain literals. + CFStore notEqualsStore = notEqualTo ? rfi.thenStore : rfi.elseStore; + notEqualToValue(rfi.left, rfi.right, rfi.rightAnno, notEqualsStore); + notEqualToValue(rfi.right, rfi.left, rfi.leftAnno, notEqualsStore); + + notEqualsLessThan(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, notEqualsStore); + notEqualsLessThan(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, notEqualsStore); + + return rfi.newResult; + } + + /** Implements case 32. */ + private void notEqualsLessThan( + Node leftNode, + AnnotationMirror leftAnno, + Node otherNode, + AnnotationMirror otherAnno, + CFStore store) { + if (!isNonNegative(leftAnno) || !isNonNegative(otherAnno)) { + return; + } + JavaExpression otherJe = JavaExpression.fromNode(otherNode); + if (atypeFactory + .getLessThanAnnotatedTypeFactory() + .isLessThanOrEqual(leftNode.getTree(), otherJe.toString())) { + store.insertValue(otherJe, POS); + } + } + + /** + * The implementation of the algorithm for refining a > test. Changes the type of left (the + * greater one) to one closer to bottom than the type of right. Can't call the promote function + * from the ATF directly because a new expression isn't introduced here - the modifications have + * to be made to an existing one. + * + *

This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described in + * the Javadoc of this class. + */ + @Override + protected void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + + if (rightAnno == null || leftAnno == null) { + return; + } + + JavaExpression leftJe = JavaExpression.fromNode(left); + + if (AnnotationUtils.areSame(rightAnno, GTEN1)) { + store.insertValue(leftJe, NN); + return; + } + if (AnnotationUtils.areSame(rightAnno, NN)) { + store.insertValue(leftJe, POS); + return; + } + if (AnnotationUtils.areSame(rightAnno, POS)) { + store.insertValue(leftJe, POS); + return; + } + } + + /** + * Refines left to exactly the level of right, since in the worst case they're equal. Modifies an + * existing type in the store, but has to be careful not to overwrite a more precise existing + * type. + * + *

This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described in + * this class's Javadoc. + */ + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + + if (rightAnno == null || leftAnno == null) { + return; + } + + JavaExpression leftJe = JavaExpression.fromNode(left); + + AnnotationMirror newLBType = + atypeFactory + .getQualifierHierarchy() + .greatestLowerBoundShallow(rightAnno, right.getType(), leftAnno, left.getType()); + + store.insertValue(leftJe, newLBType); + } + + /** + * Returns an annotation mirror representing the result of subtracting one from {@code oldAnm}. + */ + private AnnotationMirror anmAfterSubtractingOne(AnnotationMirror oldAnm) { + if (isPositive(oldAnm)) { + return NN; + } else if (isNonNegative(oldAnm)) { + return GTEN1; + } else { + return UNKNOWN; + } + } + + /** Returns an annotation mirror representing the result of adding one to {@code oldAnm}. */ + private AnnotationMirror anmAfterAddingOne(AnnotationMirror oldAnm) { + if (isNonNegative(oldAnm)) { + return POS; + } else if (isGTEN1(oldAnm)) { + return NN; + } else { + return UNKNOWN; + } + } + + /** + * Helper method for getAnnotationForPlus. Handles addition of constants (cases 8 and 9). + * + * @param val the integer value of the constant + * @param nonLiteralType the type of the side of the expression that isn't a constant + */ + private AnnotationMirror getAnnotationForLiteralPlus(int val, AnnotationMirror nonLiteralType) { + if (val == -2) { + if (isPositive(nonLiteralType)) { + return GTEN1; + } + } else if (val == -1) { + return anmAfterSubtractingOne(nonLiteralType); + } else if (val == 0) { + return nonLiteralType; + } else if (val == 1) { + return anmAfterAddingOne(nonLiteralType); + } else if (val >= 2) { + if (isGTEN1(nonLiteralType)) { + // 2 + a positive, or a non-negative, or a non-negative-1 is a positive + return POS; + } + } + return UNKNOWN; + } + + /** + * getAnnotationForPlus handles the following cases (cases 10-12 above): + * + *

+   *      8. lit -2 + pos → gte-1
+   *      lit -1 + * → call demote
+   *      lit 0 + * → *
+   *      lit 1 + * → call promote
+   *      9. lit ≥ 2 + {gte-1, nn, or pos} → pos
+   *      let all other lits, including sets, fall through:
+   *      10. pos + pos → pos
+   *      11. nn + * → *
+   *      12. pos + gte-1 → nn
+   *      * + * → lbu
+   *  
+ */ + private AnnotationMirror getAnnotationForPlus( + BinaryOperationNode binaryOpNode, TransferInput p) { + + Node leftExprNode = binaryOpNode.getLeftOperand(); + Node rightExprNode = binaryOpNode.getRightOperand(); + + AnnotationMirror leftAnno = getLowerBoundAnnotation(leftExprNode, p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + rightExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return getAnnotationForLiteralPlus(valRight.intValue(), leftAnno); + } + + AnnotationMirror rightAnno = getLowerBoundAnnotation(rightExprNode, p); + + // Check if the left side's value is known at compile time. + Long valLeft = + ValueCheckerUtils.getExactValue( + leftExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valLeft != null) { + return getAnnotationForLiteralPlus(valLeft.intValue(), rightAnno); + } + + /* This section is handling the generic cases: + * pos + pos -> pos + * nn + * -> * + * pos + gte-1 -> nn */ - private AnnotationMirror anmAfterSubtractingOne(AnnotationMirror oldAnm) { - if (isPositive(oldAnm)) { - return NN; - } else if (isNonNegative(oldAnm)) { - return GTEN1; - } else { - return UNKNOWN; - } - } - - /** Returns an annotation mirror representing the result of adding one to {@code oldAnm}. */ - private AnnotationMirror anmAfterAddingOne(AnnotationMirror oldAnm) { - if (isNonNegative(oldAnm)) { - return POS; - } else if (isGTEN1(oldAnm)) { - return NN; - } else { - return UNKNOWN; - } - } - - /** - * Helper method for getAnnotationForPlus. Handles addition of constants (cases 8 and 9). - * - * @param val the integer value of the constant - * @param nonLiteralType the type of the side of the expression that isn't a constant + if (atypeFactory.areSameByClass(leftAnno, Positive.class) + && atypeFactory.areSameByClass(rightAnno, Positive.class)) { + return POS; + } + + if (atypeFactory.areSameByClass(leftAnno, NonNegative.class)) { + return rightAnno; + } + + if (atypeFactory.areSameByClass(rightAnno, NonNegative.class)) { + return leftAnno; + } + + if ((isPositive(leftAnno) && isGTEN1(rightAnno)) + || (isGTEN1(leftAnno) && isPositive(rightAnno))) { + return NN; + } + return UNKNOWN; + } + + /** + * getAnnotationForMinus handles the following cases: + * + *
+   *      * - lit → call plus(*, -1 * the value of the lit)
+   *      * - * → lbu
+   *      13. if the LessThan type checker can establish that the left side of the expression is > the right side,
+   *      returns POS.
+   *      14. if the LessThan type checker can establish that the left side of the expression is ≥ the right side,
+   *      returns NN.
+   *      15. special handling for when the left side is the length of an array or String that's stored as a field,
+   *      and the right side is a compile time constant. Do we need this?
+   *  
+ */ + private AnnotationMirror getAnnotationForMinus( + BinaryOperationNode minusNode, TransferInput p) { + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + minusNode.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + AnnotationMirror leftAnno = getLowerBoundAnnotation(minusNode.getLeftOperand(), p); + // Instead of a separate method for subtraction, add the negative of a constant. + AnnotationMirror result = getAnnotationForLiteralPlus(-1 * valRight.intValue(), leftAnno); + + Tree leftExpr = minusNode.getLeftOperand().getTree(); + Integer minLen = null; + // Check if the left side is a field access of an array's length, or invocation of + // String.length. If so, try to look up the MinLen of the array, and potentially keep + // this either NN or POS instead of GTEN1 or LBU. + if (leftExpr.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree mstree = (MemberSelectTree) leftExpr; + minLen = atypeFactory.getMinLenFromMemberSelectTree(mstree); + } else if (leftExpr.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree mitree = (MethodInvocationTree) leftExpr; + minLen = atypeFactory.getMinLenFromMethodInvocationTree(mitree); + } + + if (minLen != null) { + result = atypeFactory.anmFromVal(minLen - valRight); + } + return result; + } + + OffsetEquation leftExpression = + OffsetEquation.createOffsetFromNode(minusNode.getLeftOperand(), atypeFactory, '+'); + if (leftExpression != null) { + if (atypeFactory + .getLessThanAnnotatedTypeFactory() + .isLessThan(minusNode.getRightOperand().getTree(), leftExpression.toString())) { + return POS; + } + + if (atypeFactory + .getLessThanAnnotatedTypeFactory() + .isLessThanOrEqual(minusNode.getRightOperand().getTree(), leftExpression.toString())) { + return NN; + } + } + + // The checker can't reason about arbitrary (i.e. non-literal) + // things that are being subtracted, so it gives up. + return UNKNOWN; + } + + /** + * Helper function for getAnnotationForMultiply. Handles compile-time known constants. + * + * @param val the integer value of the constant + * @param nonLiteralType the type of the side of the expression that isn't a constant + */ + private AnnotationMirror getAnnotationForLiteralMultiply( + int val, AnnotationMirror nonLiteralType) { + if (val == 0) { + return NN; + } else if (val == 1) { + return nonLiteralType; + } else if (val > 1) { + if (isNonNegative(nonLiteralType)) { + return nonLiteralType; + } + } + return UNKNOWN; + } + + /** + * getAnnotationForMultiply handles the following cases: + * + *
+   *        * * lit 0 → nn (=0)
+   *        16. * * lit 1 → *
+   *        17. pos * pos → pos
+   *        18. pos * nn → nn
+   *        19. nn * nn → nn
+   *        * * * → lbu
+   *  
+ * + * Also handles a special case involving Math.random (case 20). + */ + private AnnotationMirror getAnnotationForMultiply( + NumericalMultiplicationNode node, TransferInput p) { + + // Special handling for multiplying an array length by a Math.random(). + AnnotationMirror randomSpecialCaseResult = atypeFactory.checkForMathRandomSpecialCase(node); + if (randomSpecialCaseResult != null) { + return randomSpecialCaseResult; + } + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + node.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return getAnnotationForLiteralMultiply(valRight.intValue(), leftAnno); + } + + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + // Check if the left side's value is known at compile time. + Long valLeft = + ValueCheckerUtils.getExactValue( + node.getLeftOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valLeft != null) { + return getAnnotationForLiteralMultiply(valLeft.intValue(), rightAnno); + } + + /* This section handles generic annotations: + * pos * pos -> pos + * nn * pos -> nn (elided, since positives are also non-negative) + * nn * nn -> nn */ - private AnnotationMirror getAnnotationForLiteralPlus(int val, AnnotationMirror nonLiteralType) { - if (val == -2) { - if (isPositive(nonLiteralType)) { - return GTEN1; - } - } else if (val == -1) { - return anmAfterSubtractingOne(nonLiteralType); - } else if (val == 0) { - return nonLiteralType; - } else if (val == 1) { - return anmAfterAddingOne(nonLiteralType); - } else if (val >= 2) { - if (isGTEN1(nonLiteralType)) { - // 2 + a positive, or a non-negative, or a non-negative-1 is a positive - return POS; - } - } - return UNKNOWN; - } - - /** - * getAnnotationForPlus handles the following cases (cases 10-12 above): - * - *
-     *      8. lit -2 + pos → gte-1
-     *      lit -1 + * → call demote
-     *      lit 0 + * → *
-     *      lit 1 + * → call promote
-     *      9. lit ≥ 2 + {gte-1, nn, or pos} → pos
-     *      let all other lits, including sets, fall through:
-     *      10. pos + pos → pos
-     *      11. nn + * → *
-     *      12. pos + gte-1 → nn
-     *      * + * → lbu
-     *  
+ if (isPositive(leftAnno) && isPositive(rightAnno)) { + return POS; + } + if (isNonNegative(leftAnno) && isNonNegative(rightAnno)) { + return NN; + } + return UNKNOWN; + } + + /** When the value on the left is known at compile time. */ + private AnnotationMirror addAnnotationForLiteralDivideLeft(int val, AnnotationMirror rightAnno) { + if (val == 0) { + return NN; + } else if (val == 1) { + if (isNonNegative(rightAnno)) { + return NN; + } else { + // (1 / x) can't be outside the range [-1, 1] when x is an integer. + return GTEN1; + } + } + return UNKNOWN; + } + + /** When the value on the right is known at compile time. */ + private AnnotationMirror addAnnotationForLiteralDivideRight(int val, AnnotationMirror leftAnno) { + if (val == 0) { + // Reaching this indicates a divide by zero error. If the value is zero, then this is + // division by zero. Division by zero is treated as bottom so that users aren't warned + // about dead code that's dividing by zero. This code assumes that non-dead code won't + // include literal divide by zeros... + return atypeFactory.BOTTOM; + } else if (val == 1) { + return leftAnno; + } else if (val >= 2) { + if (isNonNegative(leftAnno)) { + return NN; + } + } + return UNKNOWN; + } + + /** + * getAnnotationForDivide handles the following cases (21-26). + * + *
+   *      lit 0 / * → nn (=0)
+   *      * / lit 0 → pos
+   *      lit 1 / {pos, nn} → nn
+   *      lit 1 / * → gten1
+   *      * / lit 1 → *
+   *      {pos, nn} / lit >1 → nn
+   *      pos / {pos, nn} → nn (can round to zero)
+   *      * / {pos, nn} → *
+   *      * / * → lbu
+   *  
+ */ + private AnnotationMirror getAnnotationForDivide( + IntegerDivisionNode node, TransferInput p) { + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + node.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return addAnnotationForLiteralDivideRight(valRight.intValue(), leftAnno); + } + + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + + // Check if the left side's value is known at compile time. + Long valLeft = + ValueCheckerUtils.getExactValue( + node.getLeftOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valLeft != null) { + return addAnnotationForLiteralDivideLeft(valLeft.intValue(), leftAnno); + } + + /* This section handles generic annotations: + * pos / {pos, nn} -> nn (can round to zero) + * * / {pos, nn} -> * */ - private AnnotationMirror getAnnotationForPlus( - BinaryOperationNode binaryOpNode, TransferInput p) { - - Node leftExprNode = binaryOpNode.getLeftOperand(); - Node rightExprNode = binaryOpNode.getRightOperand(); - - AnnotationMirror leftAnno = getLowerBoundAnnotation(leftExprNode, p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - rightExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return getAnnotationForLiteralPlus(valRight.intValue(), leftAnno); - } - - AnnotationMirror rightAnno = getLowerBoundAnnotation(rightExprNode, p); - - // Check if the left side's value is known at compile time. - Long valLeft = - ValueCheckerUtils.getExactValue( - leftExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valLeft != null) { - return getAnnotationForLiteralPlus(valLeft.intValue(), rightAnno); - } - - /* This section is handling the generic cases: - * pos + pos -> pos - * nn + * -> * - * pos + gte-1 -> nn - */ - if (atypeFactory.areSameByClass(leftAnno, Positive.class) - && atypeFactory.areSameByClass(rightAnno, Positive.class)) { - return POS; - } - - if (atypeFactory.areSameByClass(leftAnno, NonNegative.class)) { - return rightAnno; - } - - if (atypeFactory.areSameByClass(rightAnno, NonNegative.class)) { - return leftAnno; - } - - if ((isPositive(leftAnno) && isGTEN1(rightAnno)) - || (isGTEN1(leftAnno) && isPositive(rightAnno))) { - return NN; - } - return UNKNOWN; - } - - /** - * getAnnotationForMinus handles the following cases: - * - *
-     *      * - lit → call plus(*, -1 * the value of the lit)
-     *      * - * → lbu
-     *      13. if the LessThan type checker can establish that the left side of the expression is > the right side,
-     *      returns POS.
-     *      14. if the LessThan type checker can establish that the left side of the expression is ≥ the right side,
-     *      returns NN.
-     *      15. special handling for when the left side is the length of an array or String that's stored as a field,
-     *      and the right side is a compile time constant. Do we need this?
-     *  
- */ - private AnnotationMirror getAnnotationForMinus( - BinaryOperationNode minusNode, TransferInput p) { - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - minusNode.getRightOperand().getTree(), - atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - AnnotationMirror leftAnno = getLowerBoundAnnotation(minusNode.getLeftOperand(), p); - // Instead of a separate method for subtraction, add the negative of a constant. - AnnotationMirror result = - getAnnotationForLiteralPlus(-1 * valRight.intValue(), leftAnno); - - Tree leftExpr = minusNode.getLeftOperand().getTree(); - Integer minLen = null; - // Check if the left side is a field access of an array's length, or invocation of - // String.length. If so, try to look up the MinLen of the array, and potentially keep - // this either NN or POS instead of GTEN1 or LBU. - if (leftExpr.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree mstree = (MemberSelectTree) leftExpr; - minLen = atypeFactory.getMinLenFromMemberSelectTree(mstree); - } else if (leftExpr.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree mitree = (MethodInvocationTree) leftExpr; - minLen = atypeFactory.getMinLenFromMethodInvocationTree(mitree); - } - - if (minLen != null) { - result = atypeFactory.anmFromVal(minLen - valRight); - } - return result; - } - - OffsetEquation leftExpression = - OffsetEquation.createOffsetFromNode(minusNode.getLeftOperand(), atypeFactory, '+'); - if (leftExpression != null) { - if (atypeFactory - .getLessThanAnnotatedTypeFactory() - .isLessThan(minusNode.getRightOperand().getTree(), leftExpression.toString())) { - return POS; - } - - if (atypeFactory - .getLessThanAnnotatedTypeFactory() - .isLessThanOrEqual( - minusNode.getRightOperand().getTree(), leftExpression.toString())) { - return NN; - } - } - - // The checker can't reason about arbitrary (i.e. non-literal) - // things that are being subtracted, so it gives up. - return UNKNOWN; - } - - /** - * Helper function for getAnnotationForMultiply. Handles compile-time known constants. - * - * @param val the integer value of the constant - * @param nonLiteralType the type of the side of the expression that isn't a constant - */ - private AnnotationMirror getAnnotationForLiteralMultiply( - int val, AnnotationMirror nonLiteralType) { - if (val == 0) { - return NN; - } else if (val == 1) { - return nonLiteralType; - } else if (val > 1) { - if (isNonNegative(nonLiteralType)) { - return nonLiteralType; - } - } - return UNKNOWN; - } - - /** - * getAnnotationForMultiply handles the following cases: - * - *
-     *        * * lit 0 → nn (=0)
-     *        16. * * lit 1 → *
-     *        17. pos * pos → pos
-     *        18. pos * nn → nn
-     *        19. nn * nn → nn
-     *        * * * → lbu
-     *  
- * - * Also handles a special case involving Math.random (case 20). - */ - private AnnotationMirror getAnnotationForMultiply( - NumericalMultiplicationNode node, TransferInput p) { - - // Special handling for multiplying an array length by a Math.random(). - AnnotationMirror randomSpecialCaseResult = atypeFactory.checkForMathRandomSpecialCase(node); - if (randomSpecialCaseResult != null) { - return randomSpecialCaseResult; - } - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - node.getRightOperand().getTree(), - atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return getAnnotationForLiteralMultiply(valRight.intValue(), leftAnno); - } - - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - // Check if the left side's value is known at compile time. - Long valLeft = - ValueCheckerUtils.getExactValue( - node.getLeftOperand().getTree(), - atypeFactory.getValueAnnotatedTypeFactory()); - if (valLeft != null) { - return getAnnotationForLiteralMultiply(valLeft.intValue(), rightAnno); - } - - /* This section handles generic annotations: - * pos * pos -> pos - * nn * pos -> nn (elided, since positives are also non-negative) - * nn * nn -> nn - */ - if (isPositive(leftAnno) && isPositive(rightAnno)) { - return POS; - } - if (isNonNegative(leftAnno) && isNonNegative(rightAnno)) { - return NN; - } - return UNKNOWN; - } - - /** When the value on the left is known at compile time. */ - private AnnotationMirror addAnnotationForLiteralDivideLeft( - int val, AnnotationMirror rightAnno) { - if (val == 0) { - return NN; - } else if (val == 1) { - if (isNonNegative(rightAnno)) { - return NN; - } else { - // (1 / x) can't be outside the range [-1, 1] when x is an integer. - return GTEN1; - } - } - return UNKNOWN; - } - - /** When the value on the right is known at compile time. */ - private AnnotationMirror addAnnotationForLiteralDivideRight( - int val, AnnotationMirror leftAnno) { - if (val == 0) { - // Reaching this indicates a divide by zero error. If the value is zero, then this is - // division by zero. Division by zero is treated as bottom so that users aren't warned - // about dead code that's dividing by zero. This code assumes that non-dead code won't - // include literal divide by zeros... - return atypeFactory.BOTTOM; - } else if (val == 1) { - return leftAnno; - } else if (val >= 2) { - if (isNonNegative(leftAnno)) { - return NN; - } - } - return UNKNOWN; - } - - /** - * getAnnotationForDivide handles the following cases (21-26). - * - *
-     *      lit 0 / * → nn (=0)
-     *      * / lit 0 → pos
-     *      lit 1 / {pos, nn} → nn
-     *      lit 1 / * → gten1
-     *      * / lit 1 → *
-     *      {pos, nn} / lit >1 → nn
-     *      pos / {pos, nn} → nn (can round to zero)
-     *      * / {pos, nn} → *
-     *      * / * → lbu
-     *  
- */ - private AnnotationMirror getAnnotationForDivide( - IntegerDivisionNode node, TransferInput p) { - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - node.getRightOperand().getTree(), - atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return addAnnotationForLiteralDivideRight(valRight.intValue(), leftAnno); - } - - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - - // Check if the left side's value is known at compile time. - Long valLeft = - ValueCheckerUtils.getExactValue( - node.getLeftOperand().getTree(), - atypeFactory.getValueAnnotatedTypeFactory()); - if (valLeft != null) { - return addAnnotationForLiteralDivideLeft(valLeft.intValue(), leftAnno); - } - - /* This section handles generic annotations: - * pos / {pos, nn} -> nn (can round to zero) - * * / {pos, nn} -> * - */ - if (isPositive(leftAnno) && isNonNegative(rightAnno)) { - return NN; - } - if (isNonNegative(rightAnno)) { - return leftAnno; - } - // Everything else is unknown. - return UNKNOWN; - } - - /** A remainder with 1 or -1 as the divisor always results in zero. */ - private AnnotationMirror addAnnotationForLiteralRemainder(int val) { - if (val == 1 || val == -1) { - return NN; - } - return UNKNOWN; - } - - /** Adds a default NonNegative annotation to every character. Implements case 33. */ - @Override - protected void addInformationFromPreconditions( - CFStore info, - AnnotatedTypeFactory factory, - UnderlyingAST.CFGMethod method, - MethodTree methodTree, - ExecutableElement methodElement) { - super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); - - List paramTrees = methodTree.getParameters(); - - for (VariableTree variableTree : paramTrees) { - if (TreeUtils.typeOf(variableTree).getKind() == TypeKind.CHAR) { - JavaExpression je = JavaExpression.fromVariableTree(variableTree); - info.insertValuePermitNondeterministic(je, atypeFactory.NN); - } - } - } - - /** - * getAnnotationForRemainder handles these cases: - * - *
-     *      27. * % 1/-1 → nn
-     *      28. pos/nn % * → nn
-     *      29. gten1 % * → gten1
-     *      * % * → lbu
-     * 
- */ - public AnnotationMirror getAnnotationForRemainder( - IntegerRemainderNode node, TransferInput p) { - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - node.getRightOperand().getTree(), - atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return addAnnotationForLiteralRemainder(valRight.intValue()); - } - - /* This section handles generic annotations: - pos/nn % * -> nn - gten1 % * -> gten1 - */ - if (isNonNegative(leftAnno)) { - return NN; - } - if (isGTEN1(leftAnno)) { - return GTEN1; - } - - // Everything else is unknown. - return UNKNOWN; - } - - /** Handles shifts (case 30). * >> NonNegative → NonNegative */ - private AnnotationMirror getAnnotationForRightShift( - BinaryOperationNode node, TransferInput p) { - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - - if (isNonNegative(leftAnno)) { - if (isNonNegative(rightAnno)) { - return NN; - } - } - return UNKNOWN; - } - - /** - * Handles masking (case 31). Particularly, handles the following cases: * & NonNegative - * → NonNegative - */ - private AnnotationMirror getAnnotationForAnd( - BitwiseAndNode node, TransferInput p) { - - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - if (isNonNegative(rightAnno)) { - return NN; - } - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - if (isNonNegative(leftAnno)) { - return NN; - } - return UNKNOWN; - } - - /** - * Returns true if the argument is the @Positive type annotation. - * - * @param anm the annotation to test - * @return true if the argument is the @Positive type annotation - */ - private boolean isPositive(AnnotationMirror anm) { - return atypeFactory.areSameByClass(anm, Positive.class); - } - - /** - * Returns true if the argument is the @NonNegative type annotation (or a stronger one). - * - * @param anm the annotation to test - * @return true if the argument is the @NonNegative type annotation - */ - private boolean isNonNegative(AnnotationMirror anm) { - return atypeFactory.areSameByClass(anm, NonNegative.class) || isPositive(anm); - } - - /** - * Returns true if the argument is the @GTENegativeOne type annotation (or a stronger one). - * - * @param anm the annotation to test - * @return true if the argument is the @GTENegativeOne type annotation - */ - private boolean isGTEN1(AnnotationMirror anm) { - return atypeFactory.areSameByClass(anm, GTENegativeOne.class) || isNonNegative(anm); - } - - private AnnotationMirror getLowerBoundAnnotation( - Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - if (value == null) { - throw new BugInCF("value==null for getLowerBoundAnnotation(%s, %s)%n", subNode, p); - } - return getLowerBoundAnnotation(value); - } - - /** - * Returns the lower bound annotation for the given value. - * - * @param cfValue a value - * @return the lower bound annotation for the given value - */ - private AnnotationMirror getLowerBoundAnnotation(CFValue cfValue) { - return atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(cfValue.getAnnotations(), atypeFactory.UNKNOWN); - } - - @Override - public TransferResult visitNumericalAddition( - NumericalAdditionNode n, TransferInput p) { - TransferResult result = super.visitNumericalAddition(n, p); - AnnotationMirror newAnno = getAnnotationForPlus(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput p) { - TransferResult result = super.visitNumericalSubtraction(n, p); - AnnotationMirror newAnno = getAnnotationForMinus(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitNumericalMultiplication( - NumericalMultiplicationNode n, TransferInput p) { - TransferResult result = super.visitNumericalMultiplication(n, p); - AnnotationMirror newAnno = getAnnotationForMultiply(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitIntegerDivision( - IntegerDivisionNode n, TransferInput p) { - TransferResult result = super.visitIntegerDivision(n, p); - AnnotationMirror newAnno = getAnnotationForDivide(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitIntegerRemainder( - IntegerRemainderNode n, TransferInput p) { - TransferResult transferResult = super.visitIntegerRemainder(n, p); - AnnotationMirror resultAnno = getAnnotationForRemainder(n, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitSignedRightShift( - SignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitSignedRightShift(n, p); - AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitUnsignedRightShift( - UnsignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitUnsignedRightShift(n, p); - AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseAnd( - BitwiseAndNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseAnd(n, p); - AnnotationMirror resultAnno = getAnnotationForAnd(n, p); - return createNewResult(transferResult, resultAnno); - } - - /** - * Create a new transfer result based on the original result and the new annotation. - * - * @param result the original result - * @param resultAnno the new annotation - * @return the new transfer result - */ - private TransferResult createNewResult( - TransferResult result, AnnotationMirror resultAnno) { - CFValue newResultValue = - analysis.createSingleAnnotationValue( - resultAnno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } + if (isPositive(leftAnno) && isNonNegative(rightAnno)) { + return NN; + } + if (isNonNegative(rightAnno)) { + return leftAnno; + } + // Everything else is unknown. + return UNKNOWN; + } + + /** A remainder with 1 or -1 as the divisor always results in zero. */ + private AnnotationMirror addAnnotationForLiteralRemainder(int val) { + if (val == 1 || val == -1) { + return NN; + } + return UNKNOWN; + } + + /** Adds a default NonNegative annotation to every character. Implements case 33. */ + @Override + protected void addInformationFromPreconditions( + CFStore info, + AnnotatedTypeFactory factory, + UnderlyingAST.CFGMethod method, + MethodTree methodTree, + ExecutableElement methodElement) { + super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); + + List paramTrees = methodTree.getParameters(); + + for (VariableTree variableTree : paramTrees) { + if (TreeUtils.typeOf(variableTree).getKind() == TypeKind.CHAR) { + JavaExpression je = JavaExpression.fromVariableTree(variableTree); + info.insertValuePermitNondeterministic(je, atypeFactory.NN); + } + } + } + + /** + * getAnnotationForRemainder handles these cases: + * + *
+   *      27. * % 1/-1 → nn
+   *      28. pos/nn % * → nn
+   *      29. gten1 % * → gten1
+   *      * % * → lbu
+   * 
+ */ + public AnnotationMirror getAnnotationForRemainder( + IntegerRemainderNode node, TransferInput p) { + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + node.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return addAnnotationForLiteralRemainder(valRight.intValue()); + } + + /* This section handles generic annotations: + pos/nn % * -> nn + gten1 % * -> gten1 + */ + if (isNonNegative(leftAnno)) { + return NN; + } + if (isGTEN1(leftAnno)) { + return GTEN1; + } + + // Everything else is unknown. + return UNKNOWN; + } + + /** Handles shifts (case 30). * >> NonNegative → NonNegative */ + private AnnotationMirror getAnnotationForRightShift( + BinaryOperationNode node, TransferInput p) { + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + + if (isNonNegative(leftAnno)) { + if (isNonNegative(rightAnno)) { + return NN; + } + } + return UNKNOWN; + } + + /** + * Handles masking (case 31). Particularly, handles the following cases: * & NonNegative + * → NonNegative + */ + private AnnotationMirror getAnnotationForAnd( + BitwiseAndNode node, TransferInput p) { + + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + if (isNonNegative(rightAnno)) { + return NN; + } + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + if (isNonNegative(leftAnno)) { + return NN; + } + return UNKNOWN; + } + + /** + * Returns true if the argument is the @Positive type annotation. + * + * @param anm the annotation to test + * @return true if the argument is the @Positive type annotation + */ + private boolean isPositive(AnnotationMirror anm) { + return atypeFactory.areSameByClass(anm, Positive.class); + } + + /** + * Returns true if the argument is the @NonNegative type annotation (or a stronger one). + * + * @param anm the annotation to test + * @return true if the argument is the @NonNegative type annotation + */ + private boolean isNonNegative(AnnotationMirror anm) { + return atypeFactory.areSameByClass(anm, NonNegative.class) || isPositive(anm); + } + + /** + * Returns true if the argument is the @GTENegativeOne type annotation (or a stronger one). + * + * @param anm the annotation to test + * @return true if the argument is the @GTENegativeOne type annotation + */ + private boolean isGTEN1(AnnotationMirror anm) { + return atypeFactory.areSameByClass(anm, GTENegativeOne.class) || isNonNegative(anm); + } + + private AnnotationMirror getLowerBoundAnnotation( + Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + if (value == null) { + throw new BugInCF("value==null for getLowerBoundAnnotation(%s, %s)%n", subNode, p); + } + return getLowerBoundAnnotation(value); + } + + /** + * Returns the lower bound annotation for the given value. + * + * @param cfValue a value + * @return the lower bound annotation for the given value + */ + private AnnotationMirror getLowerBoundAnnotation(CFValue cfValue) { + return atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(cfValue.getAnnotations(), atypeFactory.UNKNOWN); + } + + @Override + public TransferResult visitNumericalAddition( + NumericalAdditionNode n, TransferInput p) { + TransferResult result = super.visitNumericalAddition(n, p); + AnnotationMirror newAnno = getAnnotationForPlus(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput p) { + TransferResult result = super.visitNumericalSubtraction(n, p); + AnnotationMirror newAnno = getAnnotationForMinus(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitNumericalMultiplication( + NumericalMultiplicationNode n, TransferInput p) { + TransferResult result = super.visitNumericalMultiplication(n, p); + AnnotationMirror newAnno = getAnnotationForMultiply(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitIntegerDivision( + IntegerDivisionNode n, TransferInput p) { + TransferResult result = super.visitIntegerDivision(n, p); + AnnotationMirror newAnno = getAnnotationForDivide(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitIntegerRemainder( + IntegerRemainderNode n, TransferInput p) { + TransferResult transferResult = super.visitIntegerRemainder(n, p); + AnnotationMirror resultAnno = getAnnotationForRemainder(n, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitSignedRightShift( + SignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitSignedRightShift(n, p); + AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitUnsignedRightShift( + UnsignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitUnsignedRightShift(n, p); + AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseAnd( + BitwiseAndNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseAnd(n, p); + AnnotationMirror resultAnno = getAnnotationForAnd(n, p); + return createNewResult(transferResult, resultAnno); + } + + /** + * Create a new transfer result based on the original result and the new annotation. + * + * @param result the original result + * @param resultAnno the new annotation + * @return the new transfer result + */ + private TransferResult createNewResult( + TransferResult result, AnnotationMirror resultAnno) { + CFValue newResultValue = + analysis.createSingleAnnotationValue( + resultAnno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java index dfeef36f9b0..ee67e1087e4 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java @@ -4,7 +4,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; - +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.Subsequence; import org.checkerframework.checker.index.qual.NonNegative; @@ -14,8 +14,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.util.JavaExpressionParseUtil; -import javax.lang.model.element.AnnotationMirror; - /** * Implements the actual checks to make sure that array accesses aren't too low. Will issue a * warning if a variable that can't be proved to be either "NonNegative" (i.e. ≥ 0) or "Positive" @@ -23,80 +21,75 @@ */ public class LowerBoundVisitor extends BaseTypeVisitor { - /* This is a key into the messages.properties file in the same - * directory, which includes the actual text of the warning. - */ - private static final @CompilerMessageKey String LOWER_BOUND = "array.access.unsafe.low"; - private static final @CompilerMessageKey String NEGATIVE_ARRAY = "array.length.negative"; - private static final @CompilerMessageKey String FROM_NOT_NN = "from.not.nonnegative"; + /* This is a key into the messages.properties file in the same + * directory, which includes the actual text of the warning. + */ + private static final @CompilerMessageKey String LOWER_BOUND = "array.access.unsafe.low"; + private static final @CompilerMessageKey String NEGATIVE_ARRAY = "array.length.negative"; + private static final @CompilerMessageKey String FROM_NOT_NN = "from.not.nonnegative"; - public LowerBoundVisitor(BaseTypeChecker checker) { - super(checker); - } + public LowerBoundVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void type) { - ExpressionTree index = tree.getIndex(); - String arrName = tree.getExpression().toString(); - AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(index); - if (!(indexType.hasAnnotation(NonNegative.class) - || indexType.hasAnnotation(Positive.class))) { - checker.reportError(index, LOWER_BOUND, indexType.toString(), arrName); - } - - return super.visitArrayAccess(tree, type); + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void type) { + ExpressionTree index = tree.getIndex(); + String arrName = tree.getExpression().toString(); + AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(index); + if (!(indexType.hasAnnotation(NonNegative.class) || indexType.hasAnnotation(Positive.class))) { + checker.reportError(index, LOWER_BOUND, indexType.toString(), arrName); } - @Override - public Void visitNewArray(NewArrayTree tree, Void type) { - if (!tree.getDimensions().isEmpty()) { - for (ExpressionTree dim : tree.getDimensions()) { - AnnotatedTypeMirror dimType = atypeFactory.getAnnotatedType(dim); - if (!(dimType.hasAnnotation(NonNegative.class) - || dimType.hasAnnotation(Positive.class))) { - checker.reportError(dim, NEGATIVE_ARRAY, dimType.toString()); - } - } - } + return super.visitArrayAccess(tree, type); + } - return super.visitNewArray(tree, type); + @Override + public Void visitNewArray(NewArrayTree tree, Void type) { + if (!tree.getDimensions().isEmpty()) { + for (ExpressionTree dim : tree.getDimensions()) { + AnnotatedTypeMirror dimType = atypeFactory.getAnnotatedType(dim); + if (!(dimType.hasAnnotation(NonNegative.class) || dimType.hasAnnotation(Positive.class))) { + checker.reportError(dim, NEGATIVE_ARRAY, dimType.toString()); + } + } } - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { + return super.visitNewArray(tree, type); + } - // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) - // occurs, from is non-negative. + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { - boolean result = true; + // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) + // occurs, from is non-negative. - Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); - if (subSeq != null) { - AnnotationMirror anm; - try { - anm = - atypeFactory.getAnnotationMirrorFromJavaExpressionString( - subSeq.from, varTree, getCurrentPath()); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - anm = null; - } - if (anm == null - || !(atypeFactory.areSameByClass(anm, NonNegative.class) - || atypeFactory.areSameByClass(anm, Positive.class))) { - checker.reportError( - valueTree, - FROM_NOT_NN, - subSeq.from, - anm == null ? "@LowerBoundUnknown" : anm); - result = false; - } - } + boolean result = true; - result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; - return result; + Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); + if (subSeq != null) { + AnnotationMirror anm; + try { + anm = + atypeFactory.getAnnotationMirrorFromJavaExpressionString( + subSeq.from, varTree, getCurrentPath()); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + anm = null; + } + if (anm == null + || !(atypeFactory.areSameByClass(anm, NonNegative.class) + || atypeFactory.areSameByClass(anm, Positive.class))) { + checker.reportError( + valueTree, FROM_NOT_NN, subSeq.from, anm == null ? "@LowerBoundUnknown" : anm); + result = false; + } } + + result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java index d713d95fad3..65bfc497218 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java @@ -5,7 +5,17 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.index.IndexMethodIdentifier; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.PolyLength; @@ -30,19 +40,6 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** * The SameLen Checker is used to determine whether there are multiple fixed-length sequences (such * as arrays or strings) in a program that share the same length. It is part of the Index Checker, @@ -73,351 +70,339 @@ */ public class SameLenAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link SameLenUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, SameLenUnknown.class); - - /** The @{@link SameLenBottom} annotation. */ - private final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, SameLenBottom.class); - - /** The @{@link PolySameLen} annotation. */ - private final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolySameLen.class); - - /** The SameLen.value field/element. */ - final ExecutableElement sameLenValueElement = - TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); - - /** Predicates about method calls. */ - private final IndexMethodIdentifier imf = new IndexMethodIdentifier(this); - - /** Create a new SameLenAnnotatedTypeFactory. */ - public SameLenAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - addAliasedTypeAnnotation(PolyLength.class, POLY); - - this.postInit(); - } - - /** Gets a helper object that holds references to methods with special handling. */ - IndexMethodIdentifier getMethodIdentifier() { - return imf; + /** The @{@link SameLenUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, SameLenUnknown.class); + + /** The @{@link SameLenBottom} annotation. */ + private final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, SameLenBottom.class); + + /** The @{@link PolySameLen} annotation. */ + private final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolySameLen.class); + + /** The SameLen.value field/element. */ + final ExecutableElement sameLenValueElement = + TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); + + /** Predicates about method calls. */ + private final IndexMethodIdentifier imf = new IndexMethodIdentifier(this); + + /** Create a new SameLenAnnotatedTypeFactory. */ + public SameLenAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + addAliasedTypeAnnotation(PolyLength.class, POLY); + + this.postInit(); + } + + /** Gets a helper object that holds references to methods with special handling. */ + IndexMethodIdentifier getMethodIdentifier() { + return imf; + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList(SameLen.class, SameLenBottom.class, SameLenUnknown.class, PolySameLen.class)); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new SameLenQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + // Handles case "user-written SameLen" + @Override + public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree tree) { + AnnotatedTypeMirror atm = super.getAnnotatedTypeLhs(tree); + + if (tree.getKind() == Tree.Kind.VARIABLE) { + AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); + if (sameLenAnno != null) { + JavaExpression je = JavaExpression.fromVariableTree((VariableTree) tree); + String varName = je.toString(); + + List exprs = + AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class); + exprs.remove(varName); + if (exprs.isEmpty()) { + atm.replaceAnnotation(UNKNOWN); + } else { + atm.replaceAnnotation(createSameLen(exprs)); + } + } } - @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - SameLen.class, - SameLenBottom.class, - SameLenUnknown.class, - PolySameLen.class)); - } + return atm; + } + + /** Returns true if the given expression may appear in a @SameLen annotation. */ + public static boolean mayAppearInSameLen(JavaExpression expr) { + return !expr.containsUnknown() + && !(expr instanceof ArrayCreation) + && !(expr instanceof ClassName) + // avoid SameLen expressions with e.g. literal String constants + && !(expr instanceof ValueLiteral) + // Big expressions cause a stack overflow in JavaExpressionParseUtil. + // So limit them to an arbitrary length of 999. + && expr.toString().length() < 1000; + } + + /** + * The qualifier hierarchy for the SameLen type system. Most types are distinct and at the same + * level: for instance @SameLen("a") and @SameLen("b) have nothing in common. However, if one type + * includes even one overlapping name, then the types have to be the same: + * so @SameLen({"a","b","c"} and @SameLen({"c","f","g"} are actually the same type -- both should + * usually be replaced by a SameLen with the union of the lists of names. + */ + private final class SameLenQualifierHierarchy extends ElementQualifierHierarchy { - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SameLenQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + /** + * Creates a SameLenQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + public SameLenQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, SameLenAnnotatedTypeFactory.this); } - // Handles case "user-written SameLen" @Override - public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree tree) { - AnnotatedTypeMirror atm = super.getAnnotatedTypeLhs(tree); - - if (tree.getKind() == Tree.Kind.VARIABLE) { - AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); - if (sameLenAnno != null) { - JavaExpression je = JavaExpression.fromVariableTree((VariableTree) tree); - String varName = je.toString(); - - List exprs = - AnnotationUtils.getElementValueArray( - sameLenAnno, sameLenValueElement, String.class); - exprs.remove(varName); - if (exprs.isEmpty()) { - atm.replaceAnnotation(UNKNOWN); - } else { - atm.replaceAnnotation(createSameLen(exprs)); - } - } - } - - return atm; - } - - /** Returns true if the given expression may appear in a @SameLen annotation. */ - public static boolean mayAppearInSameLen(JavaExpression expr) { - return !expr.containsUnknown() - && !(expr instanceof ArrayCreation) - && !(expr instanceof ClassName) - // avoid SameLen expressions with e.g. literal String constants - && !(expr instanceof ValueLiteral) - // Big expressions cause a stack overflow in JavaExpressionParseUtil. - // So limit them to an arbitrary length of 999. - && expr.toString().length() < 1000; + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + return UNKNOWN; } /** - * The qualifier hierarchy for the SameLen type system. Most types are distinct and at the same - * level: for instance @SameLen("a") and @SameLen("b) have nothing in common. However, if one - * type includes even one overlapping name, then the types have to be the same: - * so @SameLen({"a","b","c"} and @SameLen({"c","f","g"} are actually the same type -- both - * should usually be replaced by a SameLen with the union of the lists of names. + * If the collections are both non-empty and disjoint, returns null. Otherwise, returns their + * union. The collections must not contain duplicates. + * + * @param c1 a collection of Strings (intended to be the value argument of a SameLen annotation) + * @param c2 another collection of Strings + * @return if the two inputs are disjoint (i.e., have no elements in common) and both are + * non-empty, returns null. Otherwise, returns the union of the two collections (which, if + * one collection is empty, is just the other collection). The result is sorted. */ - private final class SameLenQualifierHierarchy extends ElementQualifierHierarchy { - - /** - * Creates a SameLenQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - public SameLenQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, SameLenAnnotatedTypeFactory.this); - } - - @Override - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - return UNKNOWN; - } - - /** - * If the collections are both non-empty and disjoint, returns null. Otherwise, returns - * their union. The collections must not contain duplicates. - * - * @param c1 a collection of Strings (intended to be the value argument of a SameLen - * annotation) - * @param c2 another collection of Strings - * @return if the two inputs are disjoint (i.e., have no elements in common) and both are - * non-empty, returns null. Otherwise, returns the union of the two collections (which, - * if one collection is empty, is just the other collection). The result is sorted. - */ - private @Nullable Collection unionIfNotDisjoint( - Collection c1, Collection c2) { - if (c1.isEmpty()) { - return c2; - } else if (c2.isEmpty()) { - return c1; - } - Set result = new TreeSet<>(c1); - boolean disjoint = true; - for (String s : c2) { - if (!result.add(s)) { - disjoint = false; - } - } - if (!disjoint) { - return result; - } else { - return null; - } - } - - // The GLB of two SameLen annotations is the union of the two sets of arrays, or is bottom - // if the sets do not intersect. - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { - List a1Val = - AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); - List a2Val = - AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); - - Collection exprs = unionIfNotDisjoint(a1Val, a2Val); - if (exprs == null) { - return BOTTOM; - } else { - return createSameLen(exprs); - } - } else { - // If one of the annotations is top, the glb is the other annotation; otherwise - // bottom. - if (areSameByClass(a1, SameLenUnknown.class)) { - return a2; - } else if (areSameByClass(a2, SameLenUnknown.class)) { - return a1; - } else { - return BOTTOM; - } - } + private @Nullable Collection unionIfNotDisjoint( + Collection c1, Collection c2) { + if (c1.isEmpty()) { + return c2; + } else if (c2.isEmpty()) { + return c1; + } + Set result = new TreeSet<>(c1); + boolean disjoint = true; + for (String s : c2) { + if (!result.add(s)) { + disjoint = false; } + } + if (!disjoint) { + return result; + } else { + return null; + } + } - // The LUB of two SameLen annotations is the intersection of the two sets of arrays, or is - // top if they do not intersect. - @Override - public AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { - List a1Val = - AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); - List a2Val = - AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); - - if (!Collections.disjoint(a1Val, a2Val)) { - a1Val.retainAll(a2Val); - return createSameLen(a1Val); - } else { - return UNKNOWN; - } - - } else { - // the lub is either one of the annotations (if the other is bottom), or top. - if (areSameByClass(a1, SameLenBottom.class)) { - return a2; - } else if (areSameByClass(a2, SameLenBottom.class)) { - return a1; - } else if (areSameByClass(a1, PolySameLen.class) - && areSameByClass(a2, PolySameLen.class)) { - return a1; - } else { - return UNKNOWN; - } - } + // The GLB of two SameLen annotations is the union of the two sets of arrays, or is bottom + // if the sets do not intersect. + @Override + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { + List a1Val = + AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); + List a2Val = + AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); + + Collection exprs = unionIfNotDisjoint(a1Val, a2Val); + if (exprs == null) { + return BOTTOM; + } else { + return createSameLen(exprs); } - - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (areSameByClass(subAnno, SameLenBottom.class)) { - return true; - } else if (areSameByClass(superAnno, SameLenUnknown.class)) { - return true; - } else if (areSameByClass(subAnno, PolySameLen.class)) { - return areSameByClass(superAnno, PolySameLen.class); - } else if (areSameByClass(subAnno, SameLen.class) - && areSameByClass(superAnno, SameLen.class)) { - List subArrays = - AnnotationUtils.getElementValueArray( - subAnno, sameLenValueElement, String.class); - List superArrays = - AnnotationUtils.getElementValueArray( - superAnno, sameLenValueElement, String.class); - - if (subArrays.containsAll(superArrays)) { - return true; - } - } - return false; + } else { + // If one of the annotations is top, the glb is the other annotation; otherwise + // bottom. + if (areSameByClass(a1, SameLenUnknown.class)) { + return a2; + } else if (areSameByClass(a2, SameLenUnknown.class)) { + return a1; + } else { + return BOTTOM; } + } } + // The LUB of two SameLen annotations is the intersection of the two sets of arrays, or is + // top if they do not intersect. @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new SameLenTreeAnnotator(this)); - } - - /** - * SameLen needs a tree annotator in order to properly type the right side of assignments of new - * arrays that are initialized with the length of another array. - */ - protected class SameLenTreeAnnotator extends TreeAnnotator { - - public SameLenTreeAnnotator(SameLenAnnotatedTypeFactory factory) { - super(factory); + public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { + List a1Val = + AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); + List a2Val = + AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); + + if (!Collections.disjoint(a1Val, a2Val)) { + a1Val.retainAll(a2Val); + return createSameLen(a1Val); + } else { + return UNKNOWN; } - // Case "new array" for "new T[a.length]" - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - if (tree.getDimensions().size() == 1) { - Tree dimensionTree = tree.getDimensions().get(0); - ExpressionTree sequenceTree = - IndexUtil.getLengthSequenceTree(dimensionTree, imf, processingEnv); - if (sequenceTree != null) { - AnnotationMirror sequenceAnno = - getAnnotatedType(sequenceTree).getAnnotationInHierarchy(UNKNOWN); - - JavaExpression sequenceExpr = JavaExpression.fromTree(sequenceTree); - if (mayAppearInSameLen(sequenceExpr)) { - String recString = sequenceExpr.toString(); - if (areSameByClass(sequenceAnno, SameLenUnknown.class)) { - sequenceAnno = createSameLen(Collections.singletonList(recString)); - } else if (areSameByClass(sequenceAnno, SameLen.class)) { - // Add the sequence whose length is being used to the annotation. - List exprs = - AnnotationUtils.getElementValueArray( - sequenceAnno, sameLenValueElement, String.class); - int index = Collections.binarySearch(exprs, recString); - if (index < 0) { - exprs.add(-index - 1, recString); - sequenceAnno = createSameLen(exprs); - } - } - } - type.addAnnotation(sequenceAnno); - } - } - return null; + } else { + // the lub is either one of the annotations (if the other is bottom), or top. + if (areSameByClass(a1, SameLenBottom.class)) { + return a2; + } else if (areSameByClass(a2, SameLenBottom.class)) { + return a1; + } else if (areSameByClass(a1, PolySameLen.class) && areSameByClass(a2, PolySameLen.class)) { + return a1; + } else { + return UNKNOWN; } + } } - /** - * Find all the sequences that are members of the SameLen annotation associated with the - * sequence named in sequenceExpression along the current path. - */ - public List getSameLensFromString( - String sequenceExpression, Tree tree, TreePath currentPath) { - AnnotationMirror sameLenAnno; - try { - sameLenAnno = - getAnnotationFromJavaExpressionString( - sequenceExpression, tree, currentPath, SameLen.class); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - // ignore parse errors - return Collections.emptyList(); - } - if (sameLenAnno == null) { - return Collections.emptyList(); + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (areSameByClass(subAnno, SameLenBottom.class)) { + return true; + } else if (areSameByClass(superAnno, SameLenUnknown.class)) { + return true; + } else if (areSameByClass(subAnno, PolySameLen.class)) { + return areSameByClass(superAnno, PolySameLen.class); + } else if (areSameByClass(subAnno, SameLen.class) + && areSameByClass(superAnno, SameLen.class)) { + List subArrays = + AnnotationUtils.getElementValueArray(subAnno, sameLenValueElement, String.class); + List superArrays = + AnnotationUtils.getElementValueArray(superAnno, sameLenValueElement, String.class); + + if (subArrays.containsAll(superArrays)) { + return true; } - return AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class); + } + return false; } + } - /// - /// Creating @SameLen annotations - /// + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new SameLenTreeAnnotator(this)); + } - /** - * Creates a @SameLen annotation whose values are the given strings. - * - * @param exprs the values for the @SameLen annotation. This must be an ordered - * collection such as a list or TreeSet in which the strings are in alphabetical order. - * @return a @SameLen annotation whose values are the given strings - */ - public AnnotationMirror createSameLen(Collection exprs) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class); - String[] exprArray = exprs.toArray(new String[exprs.size()]); - builder.setValue("value", exprArray); - return builder.build(); - } + /** + * SameLen needs a tree annotator in order to properly type the right side of assignments of new + * arrays that are initialized with the length of another array. + */ + protected class SameLenTreeAnnotator extends TreeAnnotator { - /** - * Generates a SameLen that includes each expression, as well as everything in the annotations2, - * if they are SameLen annotations. - * - * @param exprs a list of expressions representing arrays to be included in the combined - * annotation - * @param annos a list of annotations - * @return a combined SameLen annotation - */ - public AnnotationMirror createCombinedSameLen( - List exprs, List annos) { + public SameLenTreeAnnotator(SameLenAnnotatedTypeFactory factory) { + super(factory); + } - Set strings = new TreeSet<>(); - for (JavaExpression expr : exprs) { - if (mayAppearInSameLen(expr)) { - strings.add(expr.toString()); - } - } - for (AnnotationMirror anno : annos) { - if (areSameByClass(anno, SameLen.class)) { - strings.addAll( - AnnotationUtils.getElementValueArray( - anno, sameLenValueElement, String.class)); + // Case "new array" for "new T[a.length]" + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + if (tree.getDimensions().size() == 1) { + Tree dimensionTree = tree.getDimensions().get(0); + ExpressionTree sequenceTree = + IndexUtil.getLengthSequenceTree(dimensionTree, imf, processingEnv); + if (sequenceTree != null) { + AnnotationMirror sequenceAnno = + getAnnotatedType(sequenceTree).getAnnotationInHierarchy(UNKNOWN); + + JavaExpression sequenceExpr = JavaExpression.fromTree(sequenceTree); + if (mayAppearInSameLen(sequenceExpr)) { + String recString = sequenceExpr.toString(); + if (areSameByClass(sequenceAnno, SameLenUnknown.class)) { + sequenceAnno = createSameLen(Collections.singletonList(recString)); + } else if (areSameByClass(sequenceAnno, SameLen.class)) { + // Add the sequence whose length is being used to the annotation. + List exprs = + AnnotationUtils.getElementValueArray( + sequenceAnno, sameLenValueElement, String.class); + int index = Collections.binarySearch(exprs, recString); + if (index < 0) { + exprs.add(-index - 1, recString); + sequenceAnno = createSameLen(exprs); + } } + } + type.addAnnotation(sequenceAnno); } - return createSameLen(strings); + } + return null; + } + } + + /** + * Find all the sequences that are members of the SameLen annotation associated with the sequence + * named in sequenceExpression along the current path. + */ + public List getSameLensFromString( + String sequenceExpression, Tree tree, TreePath currentPath) { + AnnotationMirror sameLenAnno; + try { + sameLenAnno = + getAnnotationFromJavaExpressionString( + sequenceExpression, tree, currentPath, SameLen.class); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + // ignore parse errors + return Collections.emptyList(); + } + if (sameLenAnno == null) { + return Collections.emptyList(); + } + return AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class); + } + + /// + /// Creating @SameLen annotations + /// + + /** + * Creates a @SameLen annotation whose values are the given strings. + * + * @param exprs the values for the @SameLen annotation. This must be an ordered + * collection such as a list or TreeSet in which the strings are in alphabetical order. + * @return a @SameLen annotation whose values are the given strings + */ + public AnnotationMirror createSameLen(Collection exprs) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class); + String[] exprArray = exprs.toArray(new String[exprs.size()]); + builder.setValue("value", exprArray); + return builder.build(); + } + + /** + * Generates a SameLen that includes each expression, as well as everything in the annotations2, + * if they are SameLen annotations. + * + * @param exprs a list of expressions representing arrays to be included in the combined + * annotation + * @param annos a list of annotations + * @return a combined SameLen annotation + */ + public AnnotationMirror createCombinedSameLen( + List exprs, List annos) { + + Set strings = new TreeSet<>(); + for (JavaExpression expr : exprs) { + if (mayAppearInSameLen(expr)) { + strings.add(expr.toString()); + } + } + for (AnnotationMirror anno : annos) { + if (areSameByClass(anno, SameLen.class)) { + strings.addAll( + AnnotationUtils.getElementValueArray(anno, sameLenValueElement, String.class)); + } } + return createSameLen(strings); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java index 1b33b608da3..97394dd83e5 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java @@ -14,6 +14,6 @@ // @RelevantJavaTypes({CharSequence.class, Object[].class, Object.class}) @SuppressWarningsPrefix({"index", "samelen"}) public class SameLenChecker extends BaseTypeChecker { - /** Create a new SameLenChecker. */ - public SameLenChecker() {} + /** Create a new SameLenChecker. */ + public SameLenChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java index 69db0830c8b..17606d22572 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java @@ -3,7 +3,13 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.SameLen; import org.checkerframework.checker.nullness.qual.Nullable; @@ -26,15 +32,6 @@ import org.checkerframework.framework.util.JavaExpressionParseUtil; import org.checkerframework.javacutil.AnnotationUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; - /** * The transfer function for the SameLen checker. Contains three cases: * @@ -47,273 +44,270 @@ */ public class SameLenTransfer extends CFTransfer { - /** The annotated type factory. */ - private final SameLenAnnotatedTypeFactory atypeFactory; - - /** Shorthand for atypeFactory.UNKNOWN. */ - private final AnnotationMirror UNKNOWN; - - /** - * Create a new SameLenTransfer. - * - * @param analysis the CFAnalysis - */ - public SameLenTransfer(CFAnalysis analysis) { - super(analysis); - this.atypeFactory = (SameLenAnnotatedTypeFactory) analysis.getTypeFactory(); - this.UNKNOWN = atypeFactory.UNKNOWN; + /** The annotated type factory. */ + private final SameLenAnnotatedTypeFactory atypeFactory; + + /** Shorthand for atypeFactory.UNKNOWN. */ + private final AnnotationMirror UNKNOWN; + + /** + * Create a new SameLenTransfer. + * + * @param analysis the CFAnalysis + */ + public SameLenTransfer(CFAnalysis analysis) { + super(analysis); + this.atypeFactory = (SameLenAnnotatedTypeFactory) analysis.getTypeFactory(); + this.UNKNOWN = atypeFactory.UNKNOWN; + } + + /** + * Gets the receiver sequence of a length access node, or null if {@code lengthNode} is not a + * length access. + */ + private @Nullable Node getLengthReceiver(Node lengthNode) { + if (isArrayLengthAccess(lengthNode)) { + // lengthNode is a.length + FieldAccessNode lengthFieldAccessNode = (FieldAccessNode) lengthNode; + return lengthFieldAccessNode.getReceiver(); + } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthNode)) { + // lengthNode is s.length() + MethodInvocationNode lengthMethodInvocationNode = (MethodInvocationNode) lengthNode; + return lengthMethodInvocationNode.getTarget().getReceiver(); + } else { + return null; } - - /** - * Gets the receiver sequence of a length access node, or null if {@code lengthNode} is not a - * length access. - */ - private @Nullable Node getLengthReceiver(Node lengthNode) { - if (isArrayLengthAccess(lengthNode)) { - // lengthNode is a.length - FieldAccessNode lengthFieldAccessNode = (FieldAccessNode) lengthNode; - return lengthFieldAccessNode.getReceiver(); - } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthNode)) { - // lengthNode is s.length() - MethodInvocationNode lengthMethodInvocationNode = (MethodInvocationNode) lengthNode; - return lengthMethodInvocationNode.getTarget().getReceiver(); - } else { - return null; + } + + @Override + public TransferResult visitAssignment( + AssignmentNode node, TransferInput in) { + TransferResult result = super.visitAssignment(node, in); + + // Handle b = new T[a.length] + if (node.getExpression() instanceof ArrayCreationNode) { + ArrayCreationNode acNode = (ArrayCreationNode) node.getExpression(); + if (acNode.getDimensions().size() == 1) { + + Node lengthNode = acNode.getDimension(0); + Node lengthNodeReceiver = getLengthReceiver(lengthNode); + + if (lengthNodeReceiver != null) { + // "new T[a.length]" or "new T[s.length()]" is the right hand side of the + // assignment. + // lengthNode is known to be "lengthNodeReceiver.length" or + // "lengthNodeReceiver.length()" + + // targetRec is the receiver for the left hand side of the assignment. + JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); + JavaExpression otherRec = JavaExpression.fromNode(lengthNodeReceiver); + + AnnotationMirror lengthNodeAnnotation = + atypeFactory + .getAnnotatedType(lengthNodeReceiver.getTree()) + .getAnnotationInHierarchy(UNKNOWN); + + AnnotationMirror combinedSameLen = + atypeFactory.createCombinedSameLen( + Arrays.asList(targetRec, otherRec), Arrays.asList(UNKNOWN, lengthNodeAnnotation)); + + propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); + return result; } + } } - @Override - public TransferResult visitAssignment( - AssignmentNode node, TransferInput in) { - TransferResult result = super.visitAssignment(node, in); - - // Handle b = new T[a.length] - if (node.getExpression() instanceof ArrayCreationNode) { - ArrayCreationNode acNode = (ArrayCreationNode) node.getExpression(); - if (acNode.getDimensions().size() == 1) { - - Node lengthNode = acNode.getDimension(0); - Node lengthNodeReceiver = getLengthReceiver(lengthNode); - - if (lengthNodeReceiver != null) { - // "new T[a.length]" or "new T[s.length()]" is the right hand side of the - // assignment. - // lengthNode is known to be "lengthNodeReceiver.length" or - // "lengthNodeReceiver.length()" - - // targetRec is the receiver for the left hand side of the assignment. - JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); - JavaExpression otherRec = JavaExpression.fromNode(lengthNodeReceiver); - - AnnotationMirror lengthNodeAnnotation = - atypeFactory - .getAnnotatedType(lengthNodeReceiver.getTree()) - .getAnnotationInHierarchy(UNKNOWN); - - AnnotationMirror combinedSameLen = - atypeFactory.createCombinedSameLen( - Arrays.asList(targetRec, otherRec), - Arrays.asList(UNKNOWN, lengthNodeAnnotation)); - - propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); - return result; - } - } - } - - AnnotationMirror rightAnno = - atypeFactory - .getAnnotatedType(node.getExpression().getTree()) - .getAnnotationInHierarchy(UNKNOWN); - - // If the left side of the assignment is an array or a string, then have both the right and - // left side be SameLen of each other. + AnnotationMirror rightAnno = + atypeFactory + .getAnnotatedType(node.getExpression().getTree()) + .getAnnotationInHierarchy(UNKNOWN); - JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); + // If the left side of the assignment is an array or a string, then have both the right and + // left side be SameLen of each other. - JavaExpression exprRec = JavaExpression.fromNode(node.getExpression()); + JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); - if (IndexUtil.isSequenceType(node.getTarget().getType()) - || (rightAnno != null && atypeFactory.areSameByClass(rightAnno, SameLen.class))) { + JavaExpression exprRec = JavaExpression.fromNode(node.getExpression()); - AnnotationMirror rightAnnoOrUnknown = rightAnno == null ? UNKNOWN : rightAnno; + if (IndexUtil.isSequenceType(node.getTarget().getType()) + || (rightAnno != null && atypeFactory.areSameByClass(rightAnno, SameLen.class))) { - AnnotationMirror combinedSameLen = - atypeFactory.createCombinedSameLen( - Arrays.asList(targetRec, exprRec), - Arrays.asList(UNKNOWN, rightAnnoOrUnknown)); + AnnotationMirror rightAnnoOrUnknown = rightAnno == null ? UNKNOWN : rightAnno; - propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); - } + AnnotationMirror combinedSameLen = + atypeFactory.createCombinedSameLen( + Arrays.asList(targetRec, exprRec), Arrays.asList(UNKNOWN, rightAnnoOrUnknown)); - return result; + propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); } - /** - * Insert a @SameLen annotation into the store as the SameLen type of each array listed in it. - * - * @param sameLenAnno a {@code @SameLen} annotation. Not just an annotation in the SameLen - * hierarchy; this annotation must be {@code @SameLen(...)}. - * @param node the node in the tree where the combination is happening. Used for context. - * @param store the store to modify - */ - private void propagateCombinedSameLen(AnnotationMirror sameLenAnno, Node node, CFStore store) { - TreePath currentPath = atypeFactory.getPath(node.getTree()); - if (currentPath == null) { - return; - } - for (String exprString : - AnnotationUtils.getElementValueArray( - sameLenAnno, atypeFactory.sameLenValueElement, String.class)) { - JavaExpression je; - try { - je = atypeFactory.parseJavaExpressionString(exprString, currentPath); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - continue; - } - store.clearValue(je); - store.insertValue(je, sameLenAnno); - } + return result; + } + + /** + * Insert a @SameLen annotation into the store as the SameLen type of each array listed in it. + * + * @param sameLenAnno a {@code @SameLen} annotation. Not just an annotation in the SameLen + * hierarchy; this annotation must be {@code @SameLen(...)}. + * @param node the node in the tree where the combination is happening. Used for context. + * @param store the store to modify + */ + private void propagateCombinedSameLen(AnnotationMirror sameLenAnno, Node node, CFStore store) { + TreePath currentPath = atypeFactory.getPath(node.getTree()); + if (currentPath == null) { + return; } - - /** Returns true if node is of the form "someArray.length". */ - private boolean isArrayLengthAccess(Node node) { - return (node instanceof FieldAccessNode - && ((FieldAccessNode) node).getFieldName().equals("length") - && ((FieldAccessNode) node).getReceiver().getType().getKind() == TypeKind.ARRAY); + for (String exprString : + AnnotationUtils.getElementValueArray( + sameLenAnno, atypeFactory.sameLenValueElement, String.class)) { + JavaExpression je; + try { + je = atypeFactory.parseJavaExpressionString(exprString, currentPath); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + continue; + } + store.clearValue(je); + store.insertValue(je, sameLenAnno); } - - /** - * Handles refinement of equality comparisons. Assumes "a == b" or "a.length == b.length" - * evaluates to true. The method gives a and b SameLen of each other in the store. - * - * @param left the first argument to the equality operator - * @param right the second argument to the equality operator - * @param store the store in which to perform refinement - */ - private void refineEq(Node left, Node right, CFStore store) { - List exprs = new ArrayList<>(2); - List annos = new ArrayList<>(2); - for (Node internal : splitAssignments(left)) { - exprs.add(JavaExpression.fromNode(internal)); - annos.add(getAnno(internal)); - } - for (Node internal : splitAssignments(right)) { - exprs.add(JavaExpression.fromNode(internal)); - annos.add(getAnno(internal)); - } - - AnnotationMirror combinedSameLen = atypeFactory.createCombinedSameLen(exprs, annos); - - propagateCombinedSameLen(combinedSameLen, left, store); + } + + /** Returns true if node is of the form "someArray.length". */ + private boolean isArrayLengthAccess(Node node) { + return (node instanceof FieldAccessNode + && ((FieldAccessNode) node).getFieldName().equals("length") + && ((FieldAccessNode) node).getReceiver().getType().getKind() == TypeKind.ARRAY); + } + + /** + * Handles refinement of equality comparisons. Assumes "a == b" or "a.length == b.length" + * evaluates to true. The method gives a and b SameLen of each other in the store. + * + * @param left the first argument to the equality operator + * @param right the second argument to the equality operator + * @param store the store in which to perform refinement + */ + private void refineEq(Node left, Node right, CFStore store) { + List exprs = new ArrayList<>(2); + List annos = new ArrayList<>(2); + for (Node internal : splitAssignments(left)) { + exprs.add(JavaExpression.fromNode(internal)); + annos.add(getAnno(internal)); } - - /** - * Returns {@code n}'s annotation from the SameLen hierarchy. - * - *

analysis.getValue fails if called on an lvalue. However, this method needs to always - * succeed, even when n is an lvalue. Consider this code: - * - *

{@code if ((a = b) == c) {...}}
- * - * where a, b, and c are all arrays, and a has type {@code @SameLen("d")}. Afterwards, all three - * should have the type {@code @SameLen({"a", "b", "c", "d"})}, but in order to accomplish this, - * this method must return the type of a, which is an lvalue. - * - * @param n a node whose SameLen annotation to return - * @return {@code n}'s annotation from the SameLen hierarchy - */ - AnnotationMirror getAnno(Node n) { - if (n.isLValue()) { - return atypeFactory.getAnnotatedType(n.getTree()).getAnnotationInHierarchy(UNKNOWN); - } - CFValue cfValue = analysis.getValue(n); - if (cfValue == null) { - return UNKNOWN; - } - return atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(cfValue.getAnnotations(), UNKNOWN); + for (Node internal : splitAssignments(right)) { + exprs.add(JavaExpression.fromNode(internal)); + annos.add(getAnno(internal)); } - /** Implements the transfer rules for both equal nodes and not-equals nodes. */ - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult result, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - // If result is a Regular transfer, then the elseStore is a copy of the then store, that is - // created when getElseStore is called. So do that before refining any values. - CFStore elseStore = result.getElseStore(); - CFStore thenStore = result.getThenStore(); - CFStore equalStore = notEqualTo ? elseStore : thenStore; - - Node firstLengthReceiver = getLengthReceiver(firstNode); - Node secondLengthReceiver = getLengthReceiver(secondNode); - - if (firstLengthReceiver != null && secondLengthReceiver != null) { - // Refinement in the else store if this is a.length == b.length (or length() in case of - // strings). - refineEq(firstLengthReceiver, secondLengthReceiver, equalStore); - } else if (IndexUtil.isSequenceType(firstNode.getType()) - || IndexUtil.isSequenceType(secondNode.getType())) { - refineEq(firstNode, secondNode, equalStore); - } + AnnotationMirror combinedSameLen = atypeFactory.createCombinedSameLen(exprs, annos); + + propagateCombinedSameLen(combinedSameLen, left, store); + } + + /** + * Returns {@code n}'s annotation from the SameLen hierarchy. + * + *

analysis.getValue fails if called on an lvalue. However, this method needs to always + * succeed, even when n is an lvalue. Consider this code: + * + *

{@code if ((a = b) == c) {...}}
+ * + * where a, b, and c are all arrays, and a has type {@code @SameLen("d")}. Afterwards, all three + * should have the type {@code @SameLen({"a", "b", "c", "d"})}, but in order to accomplish this, + * this method must return the type of a, which is an lvalue. + * + * @param n a node whose SameLen annotation to return + * @return {@code n}'s annotation from the SameLen hierarchy + */ + AnnotationMirror getAnno(Node n) { + if (n.isLValue()) { + return atypeFactory.getAnnotatedType(n.getTree()).getAnnotationInHierarchy(UNKNOWN); + } + CFValue cfValue = analysis.getValue(n); + if (cfValue == null) { + return UNKNOWN; + } + return atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(cfValue.getAnnotations(), UNKNOWN); + } + + /** Implements the transfer rules for both equal nodes and not-equals nodes. */ + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult result, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + // If result is a Regular transfer, then the elseStore is a copy of the then store, that is + // created when getElseStore is called. So do that before refining any values. + CFStore elseStore = result.getElseStore(); + CFStore thenStore = result.getThenStore(); + CFStore equalStore = notEqualTo ? elseStore : thenStore; + + Node firstLengthReceiver = getLengthReceiver(firstNode); + Node secondLengthReceiver = getLengthReceiver(secondNode); + + if (firstLengthReceiver != null && secondLengthReceiver != null) { + // Refinement in the else store if this is a.length == b.length (or length() in case of + // strings). + refineEq(firstLengthReceiver, secondLengthReceiver, equalStore); + } else if (IndexUtil.isSequenceType(firstNode.getType()) + || IndexUtil.isSequenceType(secondNode.getType())) { + refineEq(firstNode, secondNode, equalStore); + } - return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + } + + /** Overridden to ensure that SameLen annotations on method parameters are symmetric. */ + @Override + protected void addInformationFromPreconditions( + CFStore info, + AnnotatedTypeFactory factory, + UnderlyingAST.CFGMethod method, + MethodTree methodTree, + ExecutableElement methodElement) { + super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); + + List paramTrees = methodTree.getParameters(); + int numParams = paramTrees.size(); + List paramNames = new ArrayList<>(numParams); + List params = new ArrayList<>(numParams); + + for (VariableTree tree : paramTrees) { + paramNames.add(tree.getName().toString()); + params.add(atypeFactory.getAnnotatedType(tree)); } - /** Overridden to ensure that SameLen annotations on method parameters are symmetric. */ - @Override - protected void addInformationFromPreconditions( - CFStore info, - AnnotatedTypeFactory factory, - UnderlyingAST.CFGMethod method, - MethodTree methodTree, - ExecutableElement methodElement) { - super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); - - List paramTrees = methodTree.getParameters(); - int numParams = paramTrees.size(); - List paramNames = new ArrayList<>(numParams); - List params = new ArrayList<>(numParams); - - for (VariableTree tree : paramTrees) { - paramNames.add(tree.getName().toString()); - params.add(atypeFactory.getAnnotatedType(tree)); + for (int index = 0; index < numParams; index++) { + + // If the parameter has a samelen annotation, then look for other parameters in that + // annotation and propagate default the other annotation so that it is symmetric. + AnnotatedTypeMirror atm = params.get(index); + AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); + if (sameLenAnno == null) { + continue; + } + + List values = + AnnotationUtils.getElementValueArray( + sameLenAnno, atypeFactory.sameLenValueElement, String.class); + for (String value : values) { + int otherParamIndex = paramNames.indexOf(value); + if (otherParamIndex == -1) { + continue; } - for (int index = 0; index < numParams; index++) { - - // If the parameter has a samelen annotation, then look for other parameters in that - // annotation and propagate default the other annotation so that it is symmetric. - AnnotatedTypeMirror atm = params.get(index); - AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); - if (sameLenAnno == null) { - continue; - } - - List values = - AnnotationUtils.getElementValueArray( - sameLenAnno, atypeFactory.sameLenValueElement, String.class); - for (String value : values) { - int otherParamIndex = paramNames.indexOf(value); - if (otherParamIndex == -1) { - continue; - } - - // the SameLen value is in the list of params, so modify the type of - // that param in the store - AnnotationMirror newSameLen = - atypeFactory.createSameLen( - Collections.singletonList(paramNames.get(index))); - JavaExpression otherParamRec = - JavaExpression.fromVariableTree(paramTrees.get(otherParamIndex)); - info.insertValuePermitNondeterministic(otherParamRec, newSameLen); - } - } + // the SameLen value is in the list of params, so modify the type of + // that param in the store + AnnotationMirror newSameLen = + atypeFactory.createSameLen(Collections.singletonList(paramNames.get(index))); + JavaExpression otherParamRec = + JavaExpression.fromVariableTree(paramTrees.get(otherParamIndex)); + info.insertValuePermitNondeterministic(otherParamRec, newSameLen); + } } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java index 4244f77ad0c..a28a076d347 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java @@ -2,7 +2,10 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; - +import java.util.Collection; +import java.util.Collections; +import java.util.TreeSet; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.PolySameLen; @@ -14,55 +17,47 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collection; -import java.util.Collections; -import java.util.TreeSet; - -import javax.lang.model.element.AnnotationMirror; - public class SameLenVisitor extends BaseTypeVisitor { - public SameLenVisitor(BaseTypeChecker checker) { - super(checker); - } + public SameLenVisitor(BaseTypeChecker checker) { + super(checker); + } - /** - * Merges SameLen annotations, then calls super. - * - *

{@inheritDoc} - */ - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - if (IndexUtil.isSequenceType(valueType.getUnderlyingType()) - && TreeUtils.isExpressionTree(valueTree) - // if both annotations are @PolySameLen, there is nothing to do - && !(valueType.hasAnnotation(PolySameLen.class) - && varType.hasAnnotation(PolySameLen.class))) { + /** + * Merges SameLen annotations, then calls super. + * + *

{@inheritDoc} + */ + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + if (IndexUtil.isSequenceType(valueType.getUnderlyingType()) + && TreeUtils.isExpressionTree(valueTree) + // if both annotations are @PolySameLen, there is nothing to do + && !(valueType.hasAnnotation(PolySameLen.class) + && varType.hasAnnotation(PolySameLen.class))) { - JavaExpression rhs = JavaExpression.fromTree((ExpressionTree) valueTree); - if (rhs != null && SameLenAnnotatedTypeFactory.mayAppearInSameLen(rhs)) { - String rhsExpr = rhs.toString(); - AnnotationMirror sameLenAnno = valueType.getAnnotation(SameLen.class); - Collection exprs; - if (sameLenAnno == null) { - exprs = Collections.singletonList(rhsExpr); - } else { - exprs = - new TreeSet<>( - AnnotationUtils.getElementValueArray( - sameLenAnno, - atypeFactory.sameLenValueElement, - String.class)); - exprs.add(rhsExpr); - } - AnnotationMirror newSameLen = atypeFactory.createSameLen(exprs); - valueType.replaceAnnotation(newSameLen); - } + JavaExpression rhs = JavaExpression.fromTree((ExpressionTree) valueTree); + if (rhs != null && SameLenAnnotatedTypeFactory.mayAppearInSameLen(rhs)) { + String rhsExpr = rhs.toString(); + AnnotationMirror sameLenAnno = valueType.getAnnotation(SameLen.class); + Collection exprs; + if (sameLenAnno == null) { + exprs = Collections.singletonList(rhsExpr); + } else { + exprs = + new TreeSet<>( + AnnotationUtils.getElementValueArray( + sameLenAnno, atypeFactory.sameLenValueElement, String.class)); + exprs.add(rhsExpr); } - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + AnnotationMirror newSameLen = atypeFactory.createSameLen(exprs); + valueType.replaceAnnotation(newSameLen); + } } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java index 2613f403ead..868e1d96678 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java @@ -1,5 +1,16 @@ package org.checkerframework.checker.index.searchindex; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.index.qual.NegativeIndexFor; import org.checkerframework.checker.index.qual.SearchIndexBottom; import org.checkerframework.checker.index.qual.SearchIndexFor; @@ -15,238 +26,220 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** * The Search Index Checker is used to help type the results of calls to the JDK's binary search * methods. It is part of the Index Checker. */ public class SearchIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link SearchIndexUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, SearchIndexUnknown.class); - - /** The @{@link SearchIndexBottom} annotation. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, SearchIndexBottom.class); - - /** The NegativeIndexFor.value field/element. */ - protected final ExecutableElement negativeIndexForValueElement = - TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); + /** The @{@link SearchIndexUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, SearchIndexUnknown.class); + + /** The @{@link SearchIndexBottom} annotation. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, SearchIndexBottom.class); + + /** The NegativeIndexFor.value field/element. */ + protected final ExecutableElement negativeIndexForValueElement = + TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); + + /** The SearchIndexFor.value field/element. */ + protected final ExecutableElement searchIndexForValueElement = + TreeUtils.getMethod(SearchIndexFor.class, "value", 0, processingEnv); + + /** + * Create a new SearchIndexAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this + */ + public SearchIndexAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); + } + + /** + * Provides a way to query the Constant Value Checker, which computes the values of expressions + * known at compile time (constant propagation and folding). + */ + ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + SearchIndexFor.class, + SearchIndexBottom.class, + SearchIndexUnknown.class, + NegativeIndexFor.class)); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new SearchIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** + * Returns the {@code value} field/element of the given annotation. + * + * @param am a @NegativeIndexFor or @SearchIndexFor annotation + * @return the {@code value} field/element of the given annotation + */ + private List getValueElement(AnnotationMirror am) { + if (areSameByClass(am, NegativeIndexFor.class)) { + return AnnotationUtils.getElementValueArray(am, negativeIndexForValueElement, String.class); + } else if (areSameByClass(am, SearchIndexFor.class)) { + return AnnotationUtils.getElementValueArray(am, searchIndexForValueElement, String.class); + } else { + throw new TypeSystemError("indexForValue(%s)", am); + } + } - /** The SearchIndexFor.value field/element. */ - protected final ExecutableElement searchIndexForValueElement = - TreeUtils.getMethod(SearchIndexFor.class, "value", 0, processingEnv); + /** SearchIndexQualifierHierarchy. */ + private final class SearchIndexQualifierHierarchy extends ElementQualifierHierarchy { /** - * Create a new SearchIndexAnnotatedTypeFactory. + * Creates a SearchIndexQualifierHierarchy from the given classes. * - * @param checker the type-checker associated with this + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils */ - public SearchIndexAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - this.postInit(); - } - - /** - * Provides a way to query the Constant Value Checker, which computes the values of expressions - * known at compile time (constant propagation and folding). - */ - ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); + public SearchIndexQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, SearchIndexAnnotatedTypeFactory.this); } @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - SearchIndexFor.class, - SearchIndexBottom.class, - SearchIndexUnknown.class, - NegativeIndexFor.class)); + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a1; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a2; + } + if (isSubtypeQualifiers(a1, a2)) { + return a1; + } + if (isSubtypeQualifiers(a2, a1)) { + return a2; + } + // If neither is a subtype of the other, then create an + // annotation that combines their values. + + // Each annotation is either NegativeIndexFor or SearchIndexFor. + Set combinedSet = new HashSet<>(getValueElement(a1)); + combinedSet.addAll(getValueElement(a2)); + // The list is backed by the given array. + List combinedList = + Arrays.asList(combinedSet.toArray(new String[combinedSet.size()])); + + // NegativeIndexFor <: SearchIndexFor. + if (areSameByClass(a1, NegativeIndexFor.class) + || areSameByClass(a2, NegativeIndexFor.class)) { + return createNegativeIndexFor(combinedList); + } else { + return createSearchIndexFor(combinedList); + } } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SearchIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a2; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a1; + } + if (isSubtypeQualifiers(a1, a2)) { + return a2; + } + if (isSubtypeQualifiers(a2, a1)) { + return a1; + } + // If neither is a subtype of the other, then create an + // annotation that includes only their overlapping values. + + // Each annotation is either NegativeIndexFor or SearchIndexFor. + List arrayIntersection = getValueElement(a1); + arrayIntersection.retainAll(getValueElement(a2)); // intersection + + if (arrayIntersection.isEmpty()) { + return UNKNOWN; + } + + if (areSameByClass(a1, SearchIndexFor.class) || areSameByClass(a2, SearchIndexFor.class)) { + return createSearchIndexFor(arrayIntersection); + } else { + return createNegativeIndexFor(arrayIntersection); + } } - /** - * Returns the {@code value} field/element of the given annotation. - * - * @param am a @NegativeIndexFor or @SearchIndexFor annotation - * @return the {@code value} field/element of the given annotation - */ - private List getValueElement(AnnotationMirror am) { - if (areSameByClass(am, NegativeIndexFor.class)) { - return AnnotationUtils.getElementValueArray( - am, negativeIndexForValueElement, String.class); - } else if (areSameByClass(am, SearchIndexFor.class)) { - return AnnotationUtils.getElementValueArray( - am, searchIndexForValueElement, String.class); - } else { - throw new TypeSystemError("indexForValue(%s)", am); - } + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (areSameByClass(superAnno, SearchIndexUnknown.class)) { + return true; + } + if (areSameByClass(subAnno, SearchIndexBottom.class)) { + return true; + } + if (areSameByClass(subAnno, SearchIndexUnknown.class)) { + return false; + } + if (areSameByClass(superAnno, SearchIndexBottom.class)) { + return false; + } + + // Each annotation is either NegativeIndexFor or SearchIndexFor. + List superArrays = getValueElement(superAnno); + List subArrays = getValueElement(subAnno); + + // Subtyping requires: + // * subtype is NegativeIndexFor or supertype is SearchIndexFor + // * subtype's arrays are a superset of supertype's arrays + return ((areSameByClass(subAnno, NegativeIndexFor.class) + || areSameByClass(superAnno, SearchIndexFor.class)) + && subArrays.containsAll(superArrays)); } + } - /** SearchIndexQualifierHierarchy. */ - private final class SearchIndexQualifierHierarchy extends ElementQualifierHierarchy { - - /** - * Creates a SearchIndexQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - public SearchIndexQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, SearchIndexAnnotatedTypeFactory.this); - } - - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a1; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a2; - } - if (isSubtypeQualifiers(a1, a2)) { - return a1; - } - if (isSubtypeQualifiers(a2, a1)) { - return a2; - } - // If neither is a subtype of the other, then create an - // annotation that combines their values. - - // Each annotation is either NegativeIndexFor or SearchIndexFor. - Set combinedSet = new HashSet<>(getValueElement(a1)); - combinedSet.addAll(getValueElement(a2)); - // The list is backed by the given array. - List combinedList = - Arrays.asList(combinedSet.toArray(new String[combinedSet.size()])); - - // NegativeIndexFor <: SearchIndexFor. - if (areSameByClass(a1, NegativeIndexFor.class) - || areSameByClass(a2, NegativeIndexFor.class)) { - return createNegativeIndexFor(combinedList); - } else { - return createSearchIndexFor(combinedList); - } - } - - @Override - public AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a2; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a1; - } - if (isSubtypeQualifiers(a1, a2)) { - return a2; - } - if (isSubtypeQualifiers(a2, a1)) { - return a1; - } - // If neither is a subtype of the other, then create an - // annotation that includes only their overlapping values. - - // Each annotation is either NegativeIndexFor or SearchIndexFor. - List arrayIntersection = getValueElement(a1); - arrayIntersection.retainAll(getValueElement(a2)); // intersection - - if (arrayIntersection.isEmpty()) { - return UNKNOWN; - } - - if (areSameByClass(a1, SearchIndexFor.class) - || areSameByClass(a2, SearchIndexFor.class)) { - return createSearchIndexFor(arrayIntersection); - } else { - return createNegativeIndexFor(arrayIntersection); - } - } - - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (areSameByClass(superAnno, SearchIndexUnknown.class)) { - return true; - } - if (areSameByClass(subAnno, SearchIndexBottom.class)) { - return true; - } - if (areSameByClass(subAnno, SearchIndexUnknown.class)) { - return false; - } - if (areSameByClass(superAnno, SearchIndexBottom.class)) { - return false; - } - - // Each annotation is either NegativeIndexFor or SearchIndexFor. - List superArrays = getValueElement(superAnno); - List subArrays = getValueElement(subAnno); - - // Subtyping requires: - // * subtype is NegativeIndexFor or supertype is SearchIndexFor - // * subtype's arrays are a superset of supertype's arrays - return ((areSameByClass(subAnno, NegativeIndexFor.class) - || areSameByClass(superAnno, SearchIndexFor.class)) - && subArrays.containsAll(superArrays)); - } + /** Create a new {@code @NegativeIndexFor} annotation with the given arrays as its arguments. */ + AnnotationMirror createNegativeIndexFor(List arrays) { + if (arrays.isEmpty()) { + return UNKNOWN; } - /** Create a new {@code @NegativeIndexFor} annotation with the given arrays as its arguments. */ - AnnotationMirror createNegativeIndexFor(List arrays) { - if (arrays.isEmpty()) { - return UNKNOWN; - } + arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort - arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, NegativeIndexFor.class); + builder.setValue("value", arrays); + return builder.build(); + } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, NegativeIndexFor.class); - builder.setValue("value", arrays); - return builder.build(); + /** Create a new {@code @SearchIndexFor} annotation with the given arrays as its arguments. */ + AnnotationMirror createSearchIndexFor(List arrays) { + if (arrays.isEmpty()) { + return UNKNOWN; } - /** Create a new {@code @SearchIndexFor} annotation with the given arrays as its arguments. */ - AnnotationMirror createSearchIndexFor(List arrays) { - if (arrays.isEmpty()) { - return UNKNOWN; - } + arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort - arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort - - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SearchIndexFor.class); - builder.setValue("value", arrays); - return builder.build(); - } + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SearchIndexFor.class); + builder.setValue("value", arrays); + return builder.build(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java index fad7e9e235c..7f929a949e1 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.index.searchindex; +import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SuppressWarningsPrefix; -import java.util.Set; - /** * An internal checker that assists the Index Checker in typing the results of calls to the JDK's * {@link java.util.Arrays#binarySearch(Object[],Object) Arrays.binarySearch} routine. @@ -15,23 +14,23 @@ */ @SuppressWarningsPrefix({"index", "searchindex"}) @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) public class SearchIndexChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java index 060428fa680..b4a2b6be3c5 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java @@ -1,5 +1,7 @@ package org.checkerframework.checker.index.searchindex; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.checker.index.qual.NegativeIndexFor; import org.checkerframework.checker.index.qual.SearchIndexFor; @@ -12,10 +14,6 @@ import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationUtils; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; - /** * The transfer function for the SearchIndexFor checker. Allows {@link SearchIndexFor} to be refined * to {@link NegativeIndexFor}. @@ -24,80 +22,79 @@ */ public class SearchIndexTransfer extends IndexAbstractTransfer { - /** The annotated type factory. */ - private final SearchIndexAnnotatedTypeFactory atypeFactory; + /** The annotated type factory. */ + private final SearchIndexAnnotatedTypeFactory atypeFactory; - /** - * Create a new SearchIndexTransfer. - * - * @param analysis the CFAnalysis - */ - public SearchIndexTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (SearchIndexAnnotatedTypeFactory) analysis.getTypeFactory(); - } + /** + * Create a new SearchIndexTransfer. + * + * @param analysis the CFAnalysis + */ + public SearchIndexTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (SearchIndexAnnotatedTypeFactory) analysis.getTypeFactory(); + } - /** - * If a {@link SearchIndexFor} value is negative, then refine it to {@link NegativeIndexFor}. - * This method is called by refinement rules for binary comparison operators. - * - *

If the left value (in a greater-than or greater-than-or-equal binary comparison) is - * exactly the value of {@code valueToCompareTo}, and the right side has type {@link - * SearchIndexFor}, then the right side's new value in the store should become {@link - * NegativeIndexFor}. - * - *

For example, this allows the following code to typecheck: - * - *

-     * 
-     * {@literal @}SearchIndexFor("a") int index = Arrays.binarySearch(a, y);
-     *  if (index < 0) {
-     *    {@literal @}NegativeIndexFor("a") int negInsertionPoint = index;
-     *  }
-     * 
-     * 
- * - * @param valueToCompareTo this value must be 0 (for greater than or less than) or -1 (for - * greater than or equal or less than or equal) - */ - private void refineSearchIndexToNegativeIndexFor( - Node left, Node right, CFStore store, int valueToCompareTo) { - assert valueToCompareTo == 0 || valueToCompareTo == -1; - Long leftValue = - ValueCheckerUtils.getExactValue( - left.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (leftValue != null && leftValue == valueToCompareTo) { - AnnotationMirror rightSIF = - atypeFactory.getAnnotationMirror(right.getTree(), SearchIndexFor.class); - if (rightSIF != null) { - List arrays = - AnnotationUtils.getElementValueArray( - rightSIF, atypeFactory.searchIndexForValueElement, String.class); - AnnotationMirror nif = atypeFactory.createNegativeIndexFor(arrays); - store.insertValue(JavaExpression.fromNode(right), nif); - } - } + /** + * If a {@link SearchIndexFor} value is negative, then refine it to {@link NegativeIndexFor}. This + * method is called by refinement rules for binary comparison operators. + * + *

If the left value (in a greater-than or greater-than-or-equal binary comparison) is exactly + * the value of {@code valueToCompareTo}, and the right side has type {@link SearchIndexFor}, then + * the right side's new value in the store should become {@link NegativeIndexFor}. + * + *

For example, this allows the following code to typecheck: + * + *

+   * 
+   * {@literal @}SearchIndexFor("a") int index = Arrays.binarySearch(a, y);
+   *  if (index < 0) {
+   *    {@literal @}NegativeIndexFor("a") int negInsertionPoint = index;
+   *  }
+   * 
+   * 
+ * + * @param valueToCompareTo this value must be 0 (for greater than or less than) or -1 (for greater + * than or equal or less than or equal) + */ + private void refineSearchIndexToNegativeIndexFor( + Node left, Node right, CFStore store, int valueToCompareTo) { + assert valueToCompareTo == 0 || valueToCompareTo == -1; + Long leftValue = + ValueCheckerUtils.getExactValue( + left.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (leftValue != null && leftValue == valueToCompareTo) { + AnnotationMirror rightSIF = + atypeFactory.getAnnotationMirror(right.getTree(), SearchIndexFor.class); + if (rightSIF != null) { + List arrays = + AnnotationUtils.getElementValueArray( + rightSIF, atypeFactory.searchIndexForValueElement, String.class); + AnnotationMirror nif = atypeFactory.createNegativeIndexFor(arrays); + store.insertValue(JavaExpression.fromNode(right), nif); + } } + } - @Override - protected void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - refineSearchIndexToNegativeIndexFor(left, right, store, 0); - } + @Override + protected void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + refineSearchIndexToNegativeIndexFor(left, right, store, 0); + } - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - refineSearchIndexToNegativeIndexFor(left, right, store, -1); - } + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + refineSearchIndexToNegativeIndexFor(left, right, store, -1); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java index 55f91d5e1c8..8d6711c911e 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java @@ -1,5 +1,11 @@ package org.checkerframework.checker.index.substringindex; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.checker.index.OffsetDependentTypesHelper; import org.checkerframework.checker.index.qual.SubstringIndexBottom; @@ -15,178 +21,159 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** * Builds types with annotations from the Substring Index checker hierarchy, which contains * the @{@link SubstringIndexFor} annotation. */ public class SubstringIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The top qualifier of the Substring Index hierarchy. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, SubstringIndexUnknown.class); - - /** The bottom qualifier of the Substring Index hierarchy. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, SubstringIndexBottom.class); + /** The top qualifier of the Substring Index hierarchy. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, SubstringIndexUnknown.class); + + /** The bottom qualifier of the Substring Index hierarchy. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, SubstringIndexBottom.class); + + /** + * Create a new SubstringIndexAnnotatedTypeFactory. + * + * @param checker the associated checker + */ + public SubstringIndexAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); + } + + /** + * Returns a mutable set of annotation classes that are supported by the Substring Index Checker. + * + * @return mutable set containing annotation classes from the Substring Index qualifier hierarchy + */ + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + SubstringIndexUnknown.class, SubstringIndexFor.class, SubstringIndexBottom.class)); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new SubstringIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** + * Creates an {@link DependentTypesHelper} that allows use of addition and subtraction in the + * Substring Index Checker annotations. + */ + @Override + protected DependentTypesHelper createDependentTypesHelper() { + return new OffsetDependentTypesHelper(this); + } + + /** + * The Substring Index qualifier hierarchy. The hierarchy consists of a top element {@link + * #UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link #BOTTOM} of type {@link + * SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the subtyping + * relation of {@link UBQualifier}. + */ + private final class SubstringIndexQualifierHierarchy extends ElementQualifierHierarchy { /** - * Create a new SubstringIndexAnnotatedTypeFactory. + * Creates a SubstringIndexQualifierHierarchy from the given classes. * - * @param checker the associated checker + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils */ - public SubstringIndexAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - this.postInit(); + public SubstringIndexQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, SubstringIndexAnnotatedTypeFactory.this); } - /** - * Returns a mutable set of annotation classes that are supported by the Substring Index - * Checker. - * - * @return mutable set containing annotation classes from the Substring Index qualifier - * hierarchy - */ @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - SubstringIndexUnknown.class, - SubstringIndexFor.class, - SubstringIndexBottom.class)); + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a1; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a2; + } + UBQualifier ubq1 = + UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier ubq2 = + UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier glb = ubq1.glb(ubq2); + return convertUBQualifierToAnnotation(glb); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SubstringIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a2; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a1; + } + UBQualifier ubq1 = + UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier ubq2 = + UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier lub = ubq1.lub(ubq2); + return convertUBQualifierToAnnotation(lub); } - /** - * Creates an {@link DependentTypesHelper} that allows use of addition and subtraction in the - * Substring Index Checker annotations. - */ @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new OffsetDependentTypesHelper(this); + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (areSameByClass(superAnno, SubstringIndexUnknown.class)) { + return true; + } + if (areSameByClass(subAnno, SubstringIndexBottom.class)) { + return true; + } + if (areSameByClass(subAnno, SubstringIndexUnknown.class)) { + return false; + } + if (areSameByClass(superAnno, SubstringIndexBottom.class)) { + return false; + } + + UBQualifier subtype = + UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier supertype = + UBQualifier.createUBQualifier( + superAnno, (IndexChecker) checker.getUltimateParentChecker()); + return subtype.isSubtype(supertype); } - - /** - * The Substring Index qualifier hierarchy. The hierarchy consists of a top element {@link - * #UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link #BOTTOM} of type - * {@link SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the - * subtyping relation of {@link UBQualifier}. - */ - private final class SubstringIndexQualifierHierarchy extends ElementQualifierHierarchy { - - /** - * Creates a SubstringIndexQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - public SubstringIndexQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, SubstringIndexAnnotatedTypeFactory.this); - } - - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a1; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a2; - } - UBQualifier ubq1 = - UBQualifier.createUBQualifier( - a1, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier ubq2 = - UBQualifier.createUBQualifier( - a2, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier glb = ubq1.glb(ubq2); - return convertUBQualifierToAnnotation(glb); - } - - @Override - public AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a2; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a1; - } - UBQualifier ubq1 = - UBQualifier.createUBQualifier( - a1, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier ubq2 = - UBQualifier.createUBQualifier( - a2, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier lub = ubq1.lub(ubq2); - return convertUBQualifierToAnnotation(lub); - } - - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (areSameByClass(superAnno, SubstringIndexUnknown.class)) { - return true; - } - if (areSameByClass(subAnno, SubstringIndexBottom.class)) { - return true; - } - if (areSameByClass(subAnno, SubstringIndexUnknown.class)) { - return false; - } - if (areSameByClass(superAnno, SubstringIndexBottom.class)) { - return false; - } - - UBQualifier subtype = - UBQualifier.createUBQualifier( - subAnno, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier supertype = - UBQualifier.createUBQualifier( - superAnno, (IndexChecker) checker.getUltimateParentChecker()); - return subtype.isSubtype(supertype); - } + } + + /** + * Converts an instance of {@link UBQualifier} to an annotation from the Substring Index + * hierarchy. + * + * @param qualifier the {@link UBQualifier} to be converted + * @return an annotation from the Substring Index hierarchy, representing {@code qualifier} + */ + public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { + if (qualifier.isUnknown()) { + return UNKNOWN; + } else if (qualifier.isBottom()) { + return BOTTOM; } - /** - * Converts an instance of {@link UBQualifier} to an annotation from the Substring Index - * hierarchy. - * - * @param qualifier the {@link UBQualifier} to be converted - * @return an annotation from the Substring Index hierarchy, representing {@code qualifier} - */ - public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { - if (qualifier.isUnknown()) { - return UNKNOWN; - } else if (qualifier.isBottom()) { - return BOTTOM; - } - - LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; - return ltlQualifier.convertToSubstringIndexAnnotation(processingEnv); - } + LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; + return ltlQualifier.convertToSubstringIndexAnnotation(processingEnv); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java index 20e44ef50e4..160dc0592cb 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java @@ -15,6 +15,6 @@ // int.class is for @SubstringIndexFor @RelevantJavaTypes({CharSequence.class, Object[].class, int.class}) public class SubstringIndexChecker extends BaseTypeChecker { - /** Creates a SubstringIndexChecker. */ - public SubstringIndexChecker() {} + /** Creates a SubstringIndexChecker. */ + public SubstringIndexChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java index 3a323570523..577f7ce38cd 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java @@ -1,5 +1,11 @@ package org.checkerframework.checker.index.upperbound; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.ValueAnnotatedTypeFactory; import org.checkerframework.common.value.ValueCheckerUtils; @@ -12,13 +18,6 @@ import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.TreeUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Pattern; - /** * An offset equation is 2 sets of Java expression strings, one set of added terms and one set of * subtracted terms, and a single integer constant. The Java expression strings have been @@ -27,472 +26,469 @@ *

An OffsetEquation is mutable. */ public class OffsetEquation { - /** The equation for 0 (zero). */ - public static final OffsetEquation ZERO = createOffsetForInt(0); - - /** The equation for -1. */ - public static final OffsetEquation NEG_1 = createOffsetForInt(-1); - - /** The equation for 1. */ - public static final OffsetEquation ONE = createOffsetForInt(1); - - /** Mutable list of terms that have been added to this. */ - private final List addedTerms; - - /** Mutable list of terms that have been subtracted from this. */ - private final List subtractedTerms; - - /** The integer offset. */ - private int intValue; - - /** Non-null if an error has occurred. */ - private @Nullable String error; - - /** Create a new OffsetEquation. */ - private OffsetEquation() { - addedTerms = new ArrayList<>(1); - subtractedTerms = new ArrayList<>(1); - this.intValue = 0; - this.error = null; + /** The equation for 0 (zero). */ + public static final OffsetEquation ZERO = createOffsetForInt(0); + + /** The equation for -1. */ + public static final OffsetEquation NEG_1 = createOffsetForInt(-1); + + /** The equation for 1. */ + public static final OffsetEquation ONE = createOffsetForInt(1); + + /** Mutable list of terms that have been added to this. */ + private final List addedTerms; + + /** Mutable list of terms that have been subtracted from this. */ + private final List subtractedTerms; + + /** The integer offset. */ + private int intValue; + + /** Non-null if an error has occurred. */ + private @Nullable String error; + + /** Create a new OffsetEquation. */ + private OffsetEquation() { + addedTerms = new ArrayList<>(1); + subtractedTerms = new ArrayList<>(1); + this.intValue = 0; + this.error = null; + } + + /** + * Create a new OffsetEquation that is a copy of the given one. + * + * @param other the OffsetEquation to copy + */ + protected OffsetEquation(OffsetEquation other) { + this.addedTerms = new ArrayList<>(other.addedTerms); + this.subtractedTerms = new ArrayList<>(other.subtractedTerms); + this.intValue = other.intValue; + this.error = other.error; + } + + public boolean hasError() { + return error != null; + } + + public @Nullable String getError() { + return error; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; } - - /** - * Create a new OffsetEquation that is a copy of the given one. - * - * @param other the OffsetEquation to copy - */ - protected OffsetEquation(OffsetEquation other) { - this.addedTerms = new ArrayList<>(other.addedTerms); - this.subtractedTerms = new ArrayList<>(other.subtractedTerms); - this.intValue = other.intValue; - this.error = other.error; + if (o == null || getClass() != o.getClass()) { + return false; } - public boolean hasError() { - return error != null; - } + OffsetEquation that = (OffsetEquation) o; - public @Nullable String getError() { - return error; + if (intValue != that.intValue) { + return false; } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - OffsetEquation that = (OffsetEquation) o; - - if (intValue != that.intValue) { - return false; - } - if (addedTerms.size() != that.addedTerms.size() - || !addedTerms.containsAll(that.addedTerms) - || !that.addedTerms.containsAll(addedTerms)) { - return false; - } - if (subtractedTerms.size() != that.subtractedTerms.size() - || !subtractedTerms.containsAll(that.subtractedTerms) - || !that.subtractedTerms.containsAll(subtractedTerms)) { - return false; - } - return error != null ? error.equals(that.error) : that.error == null; + if (addedTerms.size() != that.addedTerms.size() + || !addedTerms.containsAll(that.addedTerms) + || !that.addedTerms.containsAll(addedTerms)) { + return false; } - - @Override - public int hashCode() { - return Objects.hash(addedTerms, subtractedTerms, intValue, error); + if (subtractedTerms.size() != that.subtractedTerms.size() + || !subtractedTerms.containsAll(that.subtractedTerms) + || !that.subtractedTerms.containsAll(subtractedTerms)) { + return false; } - - @Override - public String toString() { - if (addedTerms.isEmpty() && subtractedTerms.isEmpty()) { - return String.valueOf(intValue); - } - List sortedAdds = new ArrayList<>(addedTerms); - Collections.sort(sortedAdds); - - List sortedSubs = new ArrayList<>(subtractedTerms); - Collections.sort(sortedSubs); - - String adds = String.join(" + ", sortedAdds); - String minus = String.join(" - ", sortedSubs); - if (sortedSubs.size() == 1 && sortedAdds.isEmpty()) { - minus = "-" + minus; - } else if (!sortedSubs.isEmpty()) { - minus = " - " + minus; - } - String terms = (adds + minus).trim(); - if (intValue != 0) { - char op = intValue > 0 ? '+' : '-'; - if (terms.isEmpty()) { - terms += intValue; - } else { - terms += " " + op + " " + Math.abs(intValue); - } - } - return terms; + return error != null ? error.equals(that.error) : that.error == null; + } + + @Override + public int hashCode() { + return Objects.hash(addedTerms, subtractedTerms, intValue, error); + } + + @Override + public String toString() { + if (addedTerms.isEmpty() && subtractedTerms.isEmpty()) { + return String.valueOf(intValue); } - - /** - * Makes a copy of this offset and removes any added terms that are accesses to the length of - * the listed sequences. If any terms were removed, then the copy is returned. Otherwise, null - * is returned. - * - * @param sequences list of sequences (arrays or strings) - * @return a copy of this equation with array.length and string.length() removed or null if no - * array.lengths or string.length() could be removed - */ - public @Nullable OffsetEquation removeSequenceLengths(List sequences) { - OffsetEquation copy = new OffsetEquation(this); - boolean simplified = false; - for (String sequence : sequences) { - String arrayLen = sequence + ".length"; - if (addedTerms.contains(arrayLen)) { - copy.addedTerms.remove(arrayLen); - simplified = true; - } - String stringLen = sequence + ".length()"; - if (addedTerms.contains(stringLen)) { - copy.addedTerms.remove(stringLen); - simplified = true; - } - } - return simplified ? copy : null; + List sortedAdds = new ArrayList<>(addedTerms); + Collections.sort(sortedAdds); + + List sortedSubs = new ArrayList<>(subtractedTerms); + Collections.sort(sortedSubs); + + String adds = String.join(" + ", sortedAdds); + String minus = String.join(" - ", sortedSubs); + if (sortedSubs.size() == 1 && sortedAdds.isEmpty()) { + minus = "-" + minus; + } else if (!sortedSubs.isEmpty()) { + minus = " - " + minus; } - - /** - * Adds or subtracts the other equation to a copy of this one. - * - *

If subtraction is specified, then every term in other is subtracted. - * - * @param op '-' for subtraction or '+' for addition - * @param other equation to add or subtract - * @return a copy of this equation +/- other - */ - public OffsetEquation copyAdd(char op, OffsetEquation other) { - assert op == '-' || op == '+'; - OffsetEquation copy = new OffsetEquation(this); - if (op == '+') { - copy.plus(other); - } else { - copy.minus(other); - } - return copy; + String terms = (adds + minus).trim(); + if (intValue != 0) { + char op = intValue > 0 ? '+' : '-'; + if (terms.isEmpty()) { + terms += intValue; + } else { + terms += " " + op + " " + Math.abs(intValue); + } } - - private void plus(OffsetEquation eq) { - addInt(eq.intValue); - for (String term : eq.addedTerms) { - addTerm('+', term); - } - for (String term : eq.subtractedTerms) { - addTerm('-', term); - } + return terms; + } + + /** + * Makes a copy of this offset and removes any added terms that are accesses to the length of the + * listed sequences. If any terms were removed, then the copy is returned. Otherwise, null is + * returned. + * + * @param sequences list of sequences (arrays or strings) + * @return a copy of this equation with array.length and string.length() removed or null if no + * array.lengths or string.length() could be removed + */ + public @Nullable OffsetEquation removeSequenceLengths(List sequences) { + OffsetEquation copy = new OffsetEquation(this); + boolean simplified = false; + for (String sequence : sequences) { + String arrayLen = sequence + ".length"; + if (addedTerms.contains(arrayLen)) { + copy.addedTerms.remove(arrayLen); + simplified = true; + } + String stringLen = sequence + ".length()"; + if (addedTerms.contains(stringLen)) { + copy.addedTerms.remove(stringLen); + simplified = true; + } } - - private void minus(OffsetEquation eq) { - addInt(-1 * eq.intValue); - for (String term : eq.addedTerms) { - addTerm('-', term); - } - for (String term : eq.subtractedTerms) { - addTerm('+', term); - } + return simplified ? copy : null; + } + + /** + * Adds or subtracts the other equation to a copy of this one. + * + *

If subtraction is specified, then every term in other is subtracted. + * + * @param op '-' for subtraction or '+' for addition + * @param other equation to add or subtract + * @return a copy of this equation +/- other + */ + public OffsetEquation copyAdd(char op, OffsetEquation other) { + assert op == '-' || op == '+'; + OffsetEquation copy = new OffsetEquation(this); + if (op == '+') { + copy.plus(other); + } else { + copy.minus(other); } + return copy; + } - /** - * Returns whether or not this equation is known to be less than or equal to the other equation. - * - * @param other equation - * @return whether or not this equation is known to be less than or equal to the other equation - */ - public boolean lessThanOrEqual(OffsetEquation other) { - return (isInt() && other.isInt() && intValue <= other.getInt()) || this.equals(other); + private void plus(OffsetEquation eq) { + addInt(eq.intValue); + for (String term : eq.addedTerms) { + addTerm('+', term); } - - /** - * Returns true if this equation is a single int value. - * - * @return true if this equation is a single int value - */ - public boolean isInt() { - return addedTerms.isEmpty() && subtractedTerms.isEmpty(); + for (String term : eq.subtractedTerms) { + addTerm('-', term); } + } - /** - * Returns the int value associated with this equation. - * - *

The equation may or may not have other terms. Use {@link #isInt()} to determine if the - * equation is only this int value. - * - * @return the int value associated with this equation - */ - public int getInt() { - return intValue; + private void minus(OffsetEquation eq) { + addInt(-1 * eq.intValue); + for (String term : eq.addedTerms) { + addTerm('-', term); } - - /** - * Returns true if this equation is exactly -1. - * - * @return true if this equation is exactly -1 - */ - public boolean isNegOne() { - return isInt() && getInt() == -1; + for (String term : eq.subtractedTerms) { + addTerm('+', term); } - - /** - * Returns true if this equation non-negative. - * - * @return true if this equation non-negative - */ - public boolean isNonNegative() { - return isInt() && getInt() >= 0; + } + + /** + * Returns whether or not this equation is known to be less than or equal to the other equation. + * + * @param other equation + * @return whether or not this equation is known to be less than or equal to the other equation + */ + public boolean lessThanOrEqual(OffsetEquation other) { + return (isInt() && other.isInt() && intValue <= other.getInt()) || this.equals(other); + } + + /** + * Returns true if this equation is a single int value. + * + * @return true if this equation is a single int value + */ + public boolean isInt() { + return addedTerms.isEmpty() && subtractedTerms.isEmpty(); + } + + /** + * Returns the int value associated with this equation. + * + *

The equation may or may not have other terms. Use {@link #isInt()} to determine if the + * equation is only this int value. + * + * @return the int value associated with this equation + */ + public int getInt() { + return intValue; + } + + /** + * Returns true if this equation is exactly -1. + * + * @return true if this equation is exactly -1 + */ + public boolean isNegOne() { + return isInt() && getInt() == -1; + } + + /** + * Returns true if this equation non-negative. + * + * @return true if this equation non-negative + */ + public boolean isNonNegative() { + return isInt() && getInt() >= 0; + } + + /** + * Returns true if this equation is negative or zero. + * + * @return true if this equation is negative or zero + */ + public boolean isNegativeOrZero() { + return isInt() && getInt() <= 0; + } + + /** + * Adds the term to this equation. If string is an integer, then it is added or subtracted, + * depending on operator, from the int value of this equation. Otherwise, the term is placed in + * the added or subtracted terms set, depending on operator. + * + * @param operator '+' or '-' + * @param term an int value or Java expression to add to this equation + */ + private void addTerm(char operator, String term) { + term = term.trim(); + if (operator == '-' && term.equals("2147483648")) { + addInt(-2147483648); + return; } - - /** - * Returns true if this equation is negative or zero. - * - * @return true if this equation is negative or zero - */ - public boolean isNegativeOrZero() { - return isInt() && getInt() <= 0; + if (isInt(term)) { + int literal = parseInt(term); + addInt(operator == '-' ? -1 * literal : literal); + return; } - - /** - * Adds the term to this equation. If string is an integer, then it is added or subtracted, - * depending on operator, from the int value of this equation. Otherwise, the term is placed in - * the added or subtracted terms set, depending on operator. - * - * @param operator '+' or '-' - * @param term an int value or Java expression to add to this equation - */ - private void addTerm(char operator, String term) { - term = term.trim(); - if (operator == '-' && term.equals("2147483648")) { - addInt(-2147483648); - return; - } - if (isInt(term)) { - int literal = parseInt(term); - addInt(operator == '-' ? -1 * literal : literal); - return; - } - if (operator == '-') { - if (addedTerms.contains(term)) { - addedTerms.remove(term); - } else { - subtractedTerms.add(term); - } - } else if (operator == '+') { - if (subtractedTerms.contains(term)) { - subtractedTerms.remove(term); - } else { - addedTerms.add(term); - } - } else { - assert false; - } + if (operator == '-') { + if (addedTerms.contains(term)) { + addedTerms.remove(term); + } else { + subtractedTerms.add(term); + } + } else if (operator == '+') { + if (subtractedTerms.contains(term)) { + subtractedTerms.remove(term); + } else { + addedTerms.add(term); + } + } else { + assert false; } - - private void addInt(int value) { - intValue += value; + } + + private void addInt(int value) { + intValue += value; + } + + /** + * Returns the offset equation that is an int value or null if there isn't one. + * + * @param equationSet a set of offset equations + * @return the offset equation that is an int value or null if there isn't one + */ + public static @Nullable OffsetEquation getIntOffsetEquation(Set equationSet) { + for (OffsetEquation eq : equationSet) { + if (eq.isInt()) { + return eq; + } } - - /** - * Returns the offset equation that is an int value or null if there isn't one. - * - * @param equationSet a set of offset equations - * @return the offset equation that is an int value or null if there isn't one - */ - public static @Nullable OffsetEquation getIntOffsetEquation(Set equationSet) { - for (OffsetEquation eq : equationSet) { - if (eq.isInt()) { - return eq; - } - } - return null; + return null; + } + + /** + * Creates an offset equation that is only the int value specified. + * + * @param value int value of the equation + * @return an offset equation that is only the int value specified + */ + public static OffsetEquation createOffsetForInt(int value) { + OffsetEquation equation = new OffsetEquation(); + equation.intValue = value; + return equation; + } + + /** + * Creates an offset equation from the expressionEquation. The expressionEquation may be several + * Java expressions added or subtracted from each other. The expressionEquation may also start + * with + or -. If the expressionEquation is the empty string, then the offset equation returned + * is zero. + * + * @param expressionEquation a Java expression made up of sums and differences + * @return an offset equation created from expressionEquation + */ + public static OffsetEquation createOffsetFromJavaExpression(String expressionEquation) { + expressionEquation = expressionEquation.trim(); + OffsetEquation equation = new OffsetEquation(); + if (expressionEquation.isEmpty()) { + equation.addTerm('+', "0"); + return equation; } - /** - * Creates an offset equation that is only the int value specified. - * - * @param value int value of the equation - * @return an offset equation that is only the int value specified - */ - public static OffsetEquation createOffsetForInt(int value) { - OffsetEquation equation = new OffsetEquation(); - equation.intValue = value; - return equation; + if (DependentTypesError.isExpressionError(expressionEquation)) { + equation.error = expressionEquation; + return equation; } - - /** - * Creates an offset equation from the expressionEquation. The expressionEquation may be several - * Java expressions added or subtracted from each other. The expressionEquation may also start - * with + or -. If the expressionEquation is the empty string, then the offset equation returned - * is zero. - * - * @param expressionEquation a Java expression made up of sums and differences - * @return an offset equation created from expressionEquation - */ - public static OffsetEquation createOffsetFromJavaExpression(String expressionEquation) { - expressionEquation = expressionEquation.trim(); - OffsetEquation equation = new OffsetEquation(); - if (expressionEquation.isEmpty()) { - equation.addTerm('+', "0"); - return equation; - } - - if (DependentTypesError.isExpressionError(expressionEquation)) { - equation.error = expressionEquation; - return equation; - } - if (indexOf(expressionEquation, '-', '+', 0) == -1) { - equation.addTerm('+', expressionEquation); - return equation; - } - - int index = 0; - while (index < expressionEquation.length()) { - char operator = expressionEquation.charAt(index); - if (operator == '-' || operator == '+') { - index++; - } else { - operator = '+'; - } - - int endIndex = indexOf(expressionEquation, '-', '+', index); - String subexpression; - if (endIndex == -1) { - endIndex = expressionEquation.length(); - subexpression = expressionEquation.substring(index); - } else { - subexpression = expressionEquation.substring(index, endIndex); - } - - equation.addTerm(operator, subexpression); - index = endIndex; - } - return equation; + if (indexOf(expressionEquation, '-', '+', 0) == -1) { + equation.addTerm('+', expressionEquation); + return equation; } - /** A regular expression that matches an integer literal. */ - private static Pattern intPattern = Pattern.compile("[-+]?[0-9]+"); - - /** - * Returns true if the given string is an integer literal - * - * @param string a string - * @return true if the given string is an integer literal - */ - private static boolean isInt(String string) { - return intPattern.matcher(string).matches(); + int index = 0; + while (index < expressionEquation.length()) { + char operator = expressionEquation.charAt(index); + if (operator == '-' || operator == '+') { + index++; + } else { + operator = '+'; + } + + int endIndex = indexOf(expressionEquation, '-', '+', index); + String subexpression; + if (endIndex == -1) { + endIndex = expressionEquation.length(); + subexpression = expressionEquation.substring(index); + } else { + subexpression = expressionEquation.substring(index, endIndex); + } + + equation.addTerm(operator, subexpression); + index = endIndex; } - - private static int parseInt(String intLiteral) { - if (intLiteral.isEmpty()) { - return 0; - } - return Integer.valueOf(intLiteral); + return equation; + } + + /** A regular expression that matches an integer literal. */ + private static Pattern intPattern = Pattern.compile("[-+]?[0-9]+"); + + /** + * Returns true if the given string is an integer literal + * + * @param string a string + * @return true if the given string is an integer literal + */ + private static boolean isInt(String string) { + return intPattern.matcher(string).matches(); + } + + private static int parseInt(String intLiteral) { + if (intLiteral.isEmpty()) { + return 0; } - - /** Returns the first index of a or b in string, or -1 if neither char is in string. */ - private static int indexOf(String string, char a, char b, int index) { - int aIndex = string.indexOf(a, index); - int bIndex = string.indexOf(b, index); - if (aIndex == -1) { - return bIndex; - } else if (bIndex == -1) { - return aIndex; - } else { - return Math.min(aIndex, bIndex); - } + return Integer.valueOf(intLiteral); + } + + /** Returns the first index of a or b in string, or -1 if neither char is in string. */ + private static int indexOf(String string, char a, char b, int index) { + int aIndex = string.indexOf(a, index); + int bIndex = string.indexOf(b, index); + if (aIndex == -1) { + return bIndex; + } else if (bIndex == -1) { + return aIndex; + } else { + return Math.min(aIndex, bIndex); } - - /** - * If node is an int value known at compile time, then the returned equation is just the int - * value or if op is '-', the return equation is the negation of the int value. - * - *

Otherwise, null is returned. - * - * @param node the Node from which to create an offset equation - * @param factory an AnnotationTypeFactory - * @param op '+' or '-' - * @return an offset equation from value of known or null if the value isn't known - */ - public static @Nullable OffsetEquation createOffsetFromNodesValue( - Node node, ValueAnnotatedTypeFactory factory, char op) { - assert op == '+' || op == '-'; - if (node.getTree() != null && TreeUtils.isExpressionTree(node.getTree())) { - Long i = ValueCheckerUtils.getExactValue(node.getTree(), factory); - if (i != null) { - if (op == '-') { - i = -i; - } - OffsetEquation eq = new OffsetEquation(); - eq.addInt(i.intValue()); - return eq; - } + } + + /** + * If node is an int value known at compile time, then the returned equation is just the int value + * or if op is '-', the return equation is the negation of the int value. + * + *

Otherwise, null is returned. + * + * @param node the Node from which to create an offset equation + * @param factory an AnnotationTypeFactory + * @param op '+' or '-' + * @return an offset equation from value of known or null if the value isn't known + */ + public static @Nullable OffsetEquation createOffsetFromNodesValue( + Node node, ValueAnnotatedTypeFactory factory, char op) { + assert op == '+' || op == '-'; + if (node.getTree() != null && TreeUtils.isExpressionTree(node.getTree())) { + Long i = ValueCheckerUtils.getExactValue(node.getTree(), factory); + if (i != null) { + if (op == '-') { + i = -i; } - return null; - } - - /** - * Creates an offset equation from the Node. - * - *

If node is an addition or subtracted node, then this method is called recursively on the - * left and right hand nodes and the two equations are added/subtracted to each other depending - * on the value of op. - * - *

Otherwise the return equation is created by converting the node to a {@link - * org.checkerframework.dataflow.expression.JavaExpression} and then added as a term to the - * returned equation. If op is '-' then it is a subtracted term. - * - * @param node the Node from which to create an offset equation - * @param factory an AnnotationTypeFactory - * @param op '+' or '-' - * @return an offset equation from the Node - */ - public static OffsetEquation createOffsetFromNode( - Node node, AnnotationProvider factory, char op) { - assert op == '+' || op == '-'; OffsetEquation eq = new OffsetEquation(); - createOffsetFromNode(node, factory, eq, op); + eq.addInt(i.intValue()); return eq; + } } - - /** - * Updates an offset equation from a Node. - * - * @param node the Node from which to create an offset equation - * @param factory an AnnotationTypeFactory - * @param eq an OffsetEquation to update - * @param op '+' or '-' - */ - private static void createOffsetFromNode( - Node node, AnnotationProvider factory, OffsetEquation eq, char op) { - JavaExpression je = JavaExpression.fromNode(node); - if (je instanceof Unknown || je == null) { - if (node instanceof NumericalAdditionNode) { - createOffsetFromNode( - ((NumericalAdditionNode) node).getLeftOperand(), factory, eq, op); - createOffsetFromNode( - ((NumericalAdditionNode) node).getRightOperand(), factory, eq, op); - } else if (node instanceof NumericalSubtractionNode) { - createOffsetFromNode( - ((NumericalSubtractionNode) node).getLeftOperand(), factory, eq, op); - char other = op == '+' ? '-' : '+'; - createOffsetFromNode( - ((NumericalSubtractionNode) node).getRightOperand(), factory, eq, other); - } else { - eq.error = node.toString(); - } - } else { - eq.addTerm(op, je.toString()); - } + return null; + } + + /** + * Creates an offset equation from the Node. + * + *

If node is an addition or subtracted node, then this method is called recursively on the + * left and right hand nodes and the two equations are added/subtracted to each other depending on + * the value of op. + * + *

Otherwise the return equation is created by converting the node to a {@link + * org.checkerframework.dataflow.expression.JavaExpression} and then added as a term to the + * returned equation. If op is '-' then it is a subtracted term. + * + * @param node the Node from which to create an offset equation + * @param factory an AnnotationTypeFactory + * @param op '+' or '-' + * @return an offset equation from the Node + */ + public static OffsetEquation createOffsetFromNode( + Node node, AnnotationProvider factory, char op) { + assert op == '+' || op == '-'; + OffsetEquation eq = new OffsetEquation(); + createOffsetFromNode(node, factory, eq, op); + return eq; + } + + /** + * Updates an offset equation from a Node. + * + * @param node the Node from which to create an offset equation + * @param factory an AnnotationTypeFactory + * @param eq an OffsetEquation to update + * @param op '+' or '-' + */ + private static void createOffsetFromNode( + Node node, AnnotationProvider factory, OffsetEquation eq, char op) { + JavaExpression je = JavaExpression.fromNode(node); + if (je instanceof Unknown || je == null) { + if (node instanceof NumericalAdditionNode) { + createOffsetFromNode(((NumericalAdditionNode) node).getLeftOperand(), factory, eq, op); + createOffsetFromNode(((NumericalAdditionNode) node).getRightOperand(), factory, eq, op); + } else if (node instanceof NumericalSubtractionNode) { + createOffsetFromNode(((NumericalSubtractionNode) node).getLeftOperand(), factory, eq, op); + char other = op == '+' ? '-' : '+'; + createOffsetFromNode( + ((NumericalSubtractionNode) node).getRightOperand(), factory, eq, other); + } else { + eq.error = node.toString(); + } + } else { + eq.addTerm(op, je.toString()); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java index 2e99a826fc2..b7dc890e584 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java @@ -1,5 +1,15 @@ package org.checkerframework.checker.index.upperbound; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.index.qual.LTEqLengthOf; import org.checkerframework.checker.index.qual.LTLengthOf; import org.checkerframework.checker.index.qual.LTOMLengthOf; @@ -15,18 +25,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; - /** * Abstraction for Upper Bound annotations. This abstract class has 4 subclasses, each of which is a * nested class: {@link LessThanLengthOf}, {@link UpperBoundUnknownQualifier}, {@code @@ -38,1483 +36,1463 @@ */ public abstract class UBQualifier { + /** + * Create a UBQualifier from the given annotation. + * + * @param am the annotation to turn into a UBQualifier + * @param ubChecker used to obtain the fields of {@code am} + * @return a UBQualifier that represents the same information as the given annotation + */ + public static UBQualifier createUBQualifier(AnnotationMirror am, UpperBoundChecker ubChecker) { + return createUBQualifier(am, null, ubChecker); + } + + /** + * Create a UBQualifier from the given annotation, with an extra offset. + * + * @param am the annotation to turn into a UBQualifier + * @param offset the extra offset; may be null + * @param ubChecker used to obtain the fields of {@code am} + * @return a UBQualifier that represents the same information as the given annotation (plus an + * optional offset) + */ + public static UBQualifier createUBQualifier( + AnnotationMirror am, @Nullable String offset, UpperBoundChecker ubChecker) { + switch (AnnotationUtils.annotationName(am)) { + case "org.checkerframework.checker.index.qual.UpperBoundUnknown": + return UpperBoundUnknownQualifier.UNKNOWN; + case "org.checkerframework.checker.index.qual.UpperBoundBottom": + return UpperBoundBottomQualifier.BOTTOM; + case "org.checkerframework.checker.index.qual.UpperBoundLiteral": + int intValue = + AnnotationUtils.getElementValueInt(am, ubChecker.upperBoundLiteralValueElement); + return UpperBoundLiteralQualifier.create(intValue); + case "org.checkerframework.checker.index.qual.LTLengthOf": + return parseLTLengthOf(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.SubstringIndexFor": + return parseSubstringIndexFor(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.LTEqLengthOf": + return parseLTEqLengthOf(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.LTOMLengthOf": + return parseLTOMLengthOf(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.PolyUpperBound": + // TODO: Ignores offset. Should we check that offset is not set? + return PolyQualifier.POLY; + default: + throw new TypeSystemError("createUBQualifier(%s, %s, ...)", am, offset); + } + } + + /** A cache for the {@link #nCopiesEmptyStringCache} method. */ + private static final List> nCopiesEmptyStringCache = new ArrayList<>(10); + + static { + nCopiesEmptyStringCache.add(Collections.emptyList()); + nCopiesEmptyStringCache.add(Collections.singletonList("")); + nCopiesEmptyStringCache.add(Collections.nCopies(2, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(3, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(4, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(5, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(6, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(7, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(8, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(9, "")); + } + + /** + * Equivalent to {@code Collections.nCopies(n, "")}. + * + * @param n the length of the list + * @return an immutable list of {@code n} copies of {@code ""} + */ + private static List nCopiesEmptyString(int n) { + if (n < 10) { + return nCopiesEmptyStringCache.get(n); + } else { + return Collections.nCopies(n, ""); + } + } + + /** + * Create a UBQualifier from a @LTLengthOf annotation. + * + * @param ltLengthOfAnno a @LTLengthOf annotation + * @param extraOffset the extra offset + * @param ubChecker used to obtain the fields of {@code am} + * @return a UBQualifier created from the @LTLengthOf annotation + */ + private static UBQualifier parseLTLengthOf( + AnnotationMirror ltLengthOfAnno, String extraOffset, UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray( + ltLengthOfAnno, ubChecker.ltLengthOfValueElement, String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of an LTLengthOf annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; + } + List offsets = + AnnotationUtils.getElementValueArray( + ltLengthOfAnno, + ubChecker.ltLengthOfOffsetElement, + String.class, + nCopiesEmptyString(sequences.size())); + return createUBQualifier(sequences, offsets, extraOffset); + } + + /** + * Create a UBQualifier from a @SubstringIndexFor annotation. + * + * @param substringIndexForAnno a @SubstringIndexFor annotation + * @param extraOffset the extra offset + * @param ubChecker used for obtaining arguments/elements from {@code substringIndexForAnno} + * @return a UBQualifier created from the @SubstringIndexFor annotation + */ + private static UBQualifier parseSubstringIndexFor( + AnnotationMirror substringIndexForAnno, String extraOffset, UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray( + substringIndexForAnno, ubChecker.substringIndexForValueElement, String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of a SubstringIndexFor annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; + } + List offsets = + AnnotationUtils.getElementValueArray( + substringIndexForAnno, ubChecker.substringIndexForOffsetElement, String.class); + if (offsets.isEmpty()) { + offsets = nCopiesEmptyString(sequences.size()); + } + return createUBQualifier(sequences, offsets, extraOffset); + } + + /** + * Create a UBQualifier from a @LTEqLengthOf annotation. + * + * @param am a @LTEqLengthOf annotation + * @param extraOffset the extra offset + * @param ubChecker used for obtaining fields from {@code am} + * @return a UBQualifier created from the @LTEqLengthOf annotation + */ + private static UBQualifier parseLTEqLengthOf( + AnnotationMirror am, String extraOffset, UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray(am, ubChecker.ltEqLengthOfValueElement, String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of an LTEqLengthOf annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; + } + List offset = Collections.nCopies(sequences.size(), "-1"); + return createUBQualifier(sequences, offset, extraOffset); + } + + /** + * Create a UBQualifier from a @LTOMLengthOf annotation. + * + * @param am a @LTOMLengthOf annotation + * @param extraOffset offset to add to each element of offsets; may be null + * @param ubChecker used for obtaining fields from {@code am} + * @return a UBQualifier created from the @LTOMLengthOf annotation + */ + private static UBQualifier parseLTOMLengthOf( + AnnotationMirror am, @Nullable String extraOffset, UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray(am, ubChecker.ltOMLengthOfValueElement, String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of an LTOMLengthOf annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; + } + List offset = Collections.nCopies(sequences.size(), "1"); + return createUBQualifier(sequences, offset, extraOffset); + } + + public static UBQualifier createUBQualifier(String sequence, String offset) { + return createUBQualifier( + Collections.singletonList(sequence), Collections.singletonList(offset)); + } + + /** + * Create an upper bound qualifier. + * + * @param type the type from which to obtain an annotation + * @param top the top annotation in a hierarchy; the annotation in this hierarchy will be used + * @param ubChecker used to obtain the fields of {@code am} + * @return a new upper bound qualifier + */ + public static UBQualifier createUBQualifier( + AnnotatedTypeMirror type, AnnotationMirror top, UpperBoundChecker ubChecker) { + return createUBQualifier(type.getEffectiveAnnotationInHierarchy(top), ubChecker); + } + + /** + * Creates an {@link UBQualifier} from the given sequences and offsets. The list of sequences may + * not be empty. If the offsets list is empty, then an offset of 0 is used for each sequence. If + * the offsets list is not empty, then it must be the same size as sequence. + * + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @return an {@link UBQualifier} for the sequences with the given offsets + */ + public static UBQualifier createUBQualifier(List sequences, List offsets) { + return createUBQualifier(sequences, offsets, null); + } + + /** + * Creates an {@link UBQualifier} from the given sequences and offsets, with the given additional + * offset. The list of sequences may not be empty. If the offsets list is empty, then an offset of + * 0 is used for each sequence. If the offsets list is not empty, then it must be the same size as + * sequence. + * + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @param extraOffset offset to add to each element of offsets; may be null + * @return an {@link UBQualifier} for the sequences with the given offsets + */ + public static UBQualifier createUBQualifier( + List sequences, List offsets, @Nullable String extraOffset) { + assert !sequences.isEmpty(); + + OffsetEquation extraEq; + if (extraOffset == null) { + extraEq = OffsetEquation.ZERO; + } else { + extraEq = OffsetEquation.createOffsetFromJavaExpression(extraOffset); + if (extraEq.hasError()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + } + + return new LessThanLengthOf(sequences, offsets, extraEq); + } + + /** + * Add the node as an offset to a copy of this qualifier. If this qualifier is UNKNOWN or BOTTOM, + * then UNKNOWN is returned. Otherwise, see {@link LessThanLengthOf#plusOffset(int)} for an + * explanation of how node is added as an offset. + * + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @return a copy of this qualifier with node added as an offset + */ + public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + + public UBQualifier plusOffset(int value) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + + public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + + public UBQualifier minusOffset(int value) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + + public boolean isLessThanLengthQualifier() { + return false; + } + + /** + * Returns true if this UBQualifier represents a literal integer. + * + * @return true if this UBQualifier represents a literal integer + */ + public boolean isLiteral() { + return false; + } + + /** + * Returns true if this UBQualifier is the top type. + * + * @return true if this UBQualifier is the top type + */ + public boolean isUnknown() { + return false; + } + + /** + * Returns true if this UBQualifier is the bottom type. + * + * @return true if this UBQualifier is the bottom type + */ + public boolean isBottom() { + return false; + } + + /** + * Return true if this is UBQualifier.PolyQualifier. + * + * @return true if this is UBQualifier.PolyQualifier + */ + @Pure + public boolean isPoly() { + return false; + } + + public abstract boolean isSubtype(UBQualifier superType); + + public abstract UBQualifier lub(UBQualifier other); + + public UBQualifier widenUpperBound(UBQualifier obj) { + return lub(obj); + } + + public abstract UBQualifier glb(UBQualifier other); + + /** + * Is the value with this qualifier less than the length of sequence? + * + * @param sequence a String sequence + * @return whether or not the value with this qualifier is less than the length of sequence + */ + public boolean isLessThanLengthOf(String sequence) { + return false; + } + + /** + * Is the value with this qualifier less than the length of any of the sequences? + * + * @param sequences list of sequences + * @return whether or not the value with this qualifier is less than the length of any of the + * sequences + */ + public boolean isLessThanLengthOfAny(List sequences) { + return false; + } + + /** + * Returns whether or not this qualifier has sequence with the specified offset. + * + * @param sequence sequence expression + * @param offset the offset being looked for + * @return whether or not this qualifier has sequence with the specified offset + */ + public boolean hasSequenceWithOffset(String sequence, int offset) { + return false; + } + + /** + * Returns whether or not this qualifier has sequence with the specified offset. + * + * @param sequence sequence expression + * @param offset the offset being looked for + * @return whether or not this qualifier has sequence with the specified offset + */ + public boolean hasSequenceWithOffset(String sequence, String offset) { + return false; + } + + /** + * Is the value with this qualifier less than or equal to the length of sequence? + * + * @param sequence a String sequence + * @return whether or not the value with this qualifier is less than or equal to the length of + * sequence + */ + public boolean isLessThanOrEqualTo(String sequence) { + return false; + } + + /** The less-than-length-of qualifier (@LTLengthOf). */ + public static class LessThanLengthOf extends UBQualifier { + + // There are two representations for sequences and offsets. + // In source code, they are represented by two parallel arrays, as in + // @LTLengthOf(value = {"a", "b", "a", "c"}, offset = {"-1", "x", "y", "0"}). + // In this implementation, they are represented by a single map; the above would be + // { "a" : {"-1", "y"}, "b" : {"x"}, "c" : {"0"} } + // Code in this class transforms from one representation to the other. + + /** Maps from sequence name to offset. */ + private final Map> map; + /** - * Create a UBQualifier from the given annotation. + * Returns a copy of the map. * - * @param am the annotation to turn into a UBQualifier - * @param ubChecker used to obtain the fields of {@code am} - * @return a UBQualifier that represents the same information as the given annotation + * @return a copy of the map */ - public static UBQualifier createUBQualifier(AnnotationMirror am, UpperBoundChecker ubChecker) { - return createUBQualifier(am, null, ubChecker); + private Map> copyMap() { + Map> result = new HashMap<>(CollectionsPlume.mapCapacity(map)); + for (String sequenceName : map.keySet()) { + Set oldEquations = map.get(sequenceName); + Set newEquations = + new HashSet<>(CollectionsPlume.mapCapacity(oldEquations)); + for (OffsetEquation offsetEquation : oldEquations) { + newEquations.add(new OffsetEquation(offsetEquation)); + } + result.put(sequenceName, newEquations); + } + return result; } /** - * Create a UBQualifier from the given annotation, with an extra offset. + * Returns true if the given integer literal is a subtype of this. The literal is a subtype of + * this if, for every offset expression, {@code literal + offset <= -1}. * - * @param am the annotation to turn into a UBQualifier - * @param offset the extra offset; may be null - * @param ubChecker used to obtain the fields of {@code am} - * @return a UBQualifier that represents the same information as the given annotation (plus an - * optional offset) + * @param i an integer + * @return true if the given integer literal is a subtype of this */ - public static UBQualifier createUBQualifier( - AnnotationMirror am, @Nullable String offset, UpperBoundChecker ubChecker) { - switch (AnnotationUtils.annotationName(am)) { - case "org.checkerframework.checker.index.qual.UpperBoundUnknown": - return UpperBoundUnknownQualifier.UNKNOWN; - case "org.checkerframework.checker.index.qual.UpperBoundBottom": - return UpperBoundBottomQualifier.BOTTOM; - case "org.checkerframework.checker.index.qual.UpperBoundLiteral": - int intValue = - AnnotationUtils.getElementValueInt( - am, ubChecker.upperBoundLiteralValueElement); - return UpperBoundLiteralQualifier.create(intValue); - case "org.checkerframework.checker.index.qual.LTLengthOf": - return parseLTLengthOf(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.SubstringIndexFor": - return parseSubstringIndexFor(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.LTEqLengthOf": - return parseLTEqLengthOf(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.LTOMLengthOf": - return parseLTOMLengthOf(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.PolyUpperBound": - // TODO: Ignores offset. Should we check that offset is not set? - return PolyQualifier.POLY; - default: - throw new TypeSystemError("createUBQualifier(%s, %s, ...)", am, offset); + /*package-private*/ boolean literalIsSubtype(int i) { + for (Map.Entry> entry : map.entrySet()) { + for (OffsetEquation equation : entry.getValue()) { + if (!equation.isInt()) { + return false; + } + int offset = equation.getInt(); + if (i + offset > -1) { + return false; + } } + } + return true; } - /** A cache for the {@link #nCopiesEmptyStringCache} method. */ - private static final List> nCopiesEmptyStringCache = new ArrayList<>(10); + /** + * Convert the parallel array representation to the map representation. + * + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @param extraEq offset to add to each element of offsets; may be null + * @return the map representation of a {@link UBQualifier}, or null if there is an error + */ + private static @Nullable Map> sequencesAndOffsetsToMap( + List sequences, List offsets, @Nullable OffsetEquation extraEq) { + + Map> map = new HashMap<>(CollectionsPlume.mapCapacity(sequences)); + if (offsets.isEmpty()) { + for (String sequence : sequences) { + // Not `Collections.singleton(extraEq)` because the values get modified + Set thisSet = new HashSet<>(1); + thisSet.add(extraEq); + map.put(sequence, thisSet); + } + } else { + assert sequences.size() == offsets.size(); + for (int i = 0; i < sequences.size(); i++) { + String sequence = sequences.get(i); + String offset = offsets.get(i); + Set set = map.computeIfAbsent(sequence, __ -> new HashSet<>()); + OffsetEquation eq = OffsetEquation.createOffsetFromJavaExpression(offset); + if (eq.hasError()) { + return null; + } + eq = eq.copyAdd('+', extraEq); + set.add(eq); + } + } + return map; + } - static { - nCopiesEmptyStringCache.add(Collections.emptyList()); - nCopiesEmptyStringCache.add(Collections.singletonList("")); - nCopiesEmptyStringCache.add(Collections.nCopies(2, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(3, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(4, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(5, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(6, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(7, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(8, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(9, "")); + /** A triple that is the return type of {@link #mapToSequencesAndOffsets}. */ + private static class SequencesOffsetsAndClass { + /** List of sequences. */ + public final List sequences; + + /** List of offsets. */ + public final List offsets; + + /** The class of the annotation to be built. */ + public final Class annoClass; + + /** + * Creates a new SequencesOffsetsAndClass. + * + * @param sequences list of sequences + * @param offsets list of offsets + * @param annoClass the class of the annotation to be built + */ + public SequencesOffsetsAndClass( + List sequences, List offsets, Class annoClass) { + + this.sequences = sequences; + this.offsets = offsets; + this.annoClass = annoClass; + } } /** - * Equivalent to {@code Collections.nCopies(n, "")}. + * Given the map representation, returns parallel-arrays representation. * - * @param n the length of the list - * @return an immutable list of {@code n} copies of {@code ""} + * @param map the internal representation of LessThanLengthOf + * @param buildSubstringIndexAnnotation if true, the annoClass in the result is + * ubstringIndexFor.class + * @return the external representation */ - private static List nCopiesEmptyString(int n) { - if (n < 10) { - return nCopiesEmptyStringCache.get(n); - } else { - return Collections.nCopies(n, ""); - } + private static SequencesOffsetsAndClass mapToSequencesAndOffsets( + Map> map, boolean buildSubstringIndexAnnotation) { + List<@KeyFor("map") String> sortedSequences = new ArrayList<>(map.keySet()); + Collections.sort(sortedSequences); + List sequences = new ArrayList<>(); + List offsets = new ArrayList<>(); + boolean isLTEq = true; + boolean isLTOM = true; + for (String sequence : sortedSequences) { + // The offsets for this sequence. + List thisOffsets = new ArrayList<>(); + for (OffsetEquation eq : map.get(sequence)) { + isLTEq = isLTEq && eq.equals(OffsetEquation.NEG_1); + isLTOM = isLTOM && eq.equals(OffsetEquation.ONE); + thisOffsets.add(eq.toString()); + } + Collections.sort(thisOffsets); + for (String offset : thisOffsets) { + sequences.add(sequence); + offsets.add(offset); + } + } + Class annoClass; + if (buildSubstringIndexAnnotation) { + annoClass = SubstringIndexFor.class; + } else if (isLTEq) { + annoClass = LTEqLengthOf.class; + } else if (isLTOM) { + annoClass = LTOMLengthOf.class; + } else { + annoClass = LTLengthOf.class; + } + return new SequencesOffsetsAndClass(sequences, offsets, annoClass); } + // End of code for manipulating the representation + /** - * Create a UBQualifier from a @LTLengthOf annotation. + * Create a new LessThanLengthOf, from the internal representation. * - * @param ltLengthOfAnno a @LTLengthOf annotation - * @param extraOffset the extra offset - * @param ubChecker used to obtain the fields of {@code am} - * @return a UBQualifier created from the @LTLengthOf annotation + * @param map a map from sequence name to offse */ - private static UBQualifier parseLTLengthOf( - AnnotationMirror ltLengthOfAnno, String extraOffset, UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray( - ltLengthOfAnno, ubChecker.ltLengthOfValueElement, String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of an LTLengthOf annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; - } - List offsets = - AnnotationUtils.getElementValueArray( - ltLengthOfAnno, - ubChecker.ltLengthOfOffsetElement, - String.class, - nCopiesEmptyString(sequences.size())); - return createUBQualifier(sequences, offsets, extraOffset); + private LessThanLengthOf(Map> map) { + assert !map.isEmpty(); + this.map = map; } /** - * Create a UBQualifier from a @SubstringIndexFor annotation. + * Create a new LessThanLengthOf from the parallel array representation. * - * @param substringIndexForAnno a @SubstringIndexFor annotation - * @param extraOffset the extra offset - * @param ubChecker used for obtaining arguments/elements from {@code substringIndexForAnno} - * @return a UBQualifier created from the @SubstringIndexFor annotation + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @param extraEq offset to add to each element of offsets; may be null */ - private static UBQualifier parseSubstringIndexFor( - AnnotationMirror substringIndexForAnno, - String extraOffset, - UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray( - substringIndexForAnno, - ubChecker.substringIndexForValueElement, - String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of a SubstringIndexFor annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; - } - List offsets = - AnnotationUtils.getElementValueArray( - substringIndexForAnno, - ubChecker.substringIndexForOffsetElement, - String.class); - if (offsets.isEmpty()) { - offsets = nCopiesEmptyString(sequences.size()); - } - return createUBQualifier(sequences, offsets, extraOffset); + private LessThanLengthOf( + List sequences, List offsets, @Nullable OffsetEquation extraEq) { + this(sequencesAndOffsetsToMap(sequences, offsets, extraEq)); + } + + @Override + public boolean hasSequenceWithOffset(String sequence, int offset) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + return offsets.contains(OffsetEquation.createOffsetForInt(offset)); + } + + @Override + public boolean hasSequenceWithOffset(String sequence, String offset) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + OffsetEquation target = OffsetEquation.createOffsetFromJavaExpression(offset); + return offsets.contains(target); } /** - * Create a UBQualifier from a @LTEqLengthOf annotation. + * Is a value with this type less than or equal to the length of sequence? * - * @param am a @LTEqLengthOf annotation - * @param extraOffset the extra offset - * @param ubChecker used for obtaining fields from {@code am} - * @return a UBQualifier created from the @LTEqLengthOf annotation + * @param sequence a String sequence + * @return true if a value with this type is less than or equal to the length of sequence */ - private static UBQualifier parseLTEqLengthOf( - AnnotationMirror am, String extraOffset, UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray( - am, ubChecker.ltEqLengthOfValueElement, String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of an LTEqLengthOf annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; - } - List offset = Collections.nCopies(sequences.size(), "-1"); - return createUBQualifier(sequences, offset, extraOffset); + @Override + public boolean isLessThanOrEqualTo(String sequence) { + return isLessThanLengthOf(sequence) || hasSequenceWithOffset(sequence, -1); } /** - * Create a UBQualifier from a @LTOMLengthOf annotation. + * Is a value with this type less than the length of any of the sequences? * - * @param am a @LTOMLengthOf annotation - * @param extraOffset offset to add to each element of offsets; may be null - * @param ubChecker used for obtaining fields from {@code am} - * @return a UBQualifier created from the @LTOMLengthOf annotation + * @param sequences list of sequences + * @return true if a value with this type is less than the length of any of the sequences */ - private static UBQualifier parseLTOMLengthOf( - AnnotationMirror am, @Nullable String extraOffset, UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray( - am, ubChecker.ltOMLengthOfValueElement, String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of an LTOMLengthOf annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; + @Override + public boolean isLessThanLengthOfAny(List sequences) { + for (String sequence : sequences) { + if (isLessThanLengthOf(sequence)) { + return true; } - List offset = Collections.nCopies(sequences.size(), "1"); - return createUBQualifier(sequences, offset, extraOffset); - } - - public static UBQualifier createUBQualifier(String sequence, String offset) { - return createUBQualifier( - Collections.singletonList(sequence), Collections.singletonList(offset)); + } + return false; } /** - * Create an upper bound qualifier. + * Is a value with this type less than the length of the sequence? * - * @param type the type from which to obtain an annotation - * @param top the top annotation in a hierarchy; the annotation in this hierarchy will be used - * @param ubChecker used to obtain the fields of {@code am} - * @return a new upper bound qualifier + * @param sequence a String sequence + * @return true if a value with this type is less than the length of the sequence */ - public static UBQualifier createUBQualifier( - AnnotatedTypeMirror type, AnnotationMirror top, UpperBoundChecker ubChecker) { - return createUBQualifier(type.getEffectiveAnnotationInHierarchy(top), ubChecker); + @Override + public boolean isLessThanLengthOf(String sequence) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + if (offsets.isEmpty()) { + return true; + } + for (OffsetEquation offset : offsets) { + if (offset.isNonNegative()) { + return true; + } + } + return false; } /** - * Creates an {@link UBQualifier} from the given sequences and offsets. The list of sequences - * may not be empty. If the offsets list is empty, then an offset of 0 is used for each - * sequence. If the offsets list is not empty, then it must be the same size as sequence. + * Returns the AnnotationMirror that represents this qualifier. If possible, AnnotationMirrors + * using @{@link LTEqLengthOf} or @{@link LTOMLengthOf} are returned. Otherwise, @{@link + * LTLengthOf} is used. * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @return an {@link UBQualifier} for the sequences with the given offsets + *

The returned annotation is canonicalized by sorting its arguments by sequence and then + * offset. This is so that {@link AnnotationUtils#areSame(AnnotationMirror, AnnotationMirror)} + * returns true for equivalent annotations. + * + * @param env a processing environment used to build the returned annotation + * @return the AnnotationMirror that represents this qualifier */ - public static UBQualifier createUBQualifier(List sequences, List offsets) { - return createUBQualifier(sequences, offsets, null); + public AnnotationMirror convertToAnnotation(ProcessingEnvironment env) { + return convertToAnnotation(env, false); } /** - * Creates an {@link UBQualifier} from the given sequences and offsets, with the given - * additional offset. The list of sequences may not be empty. If the offsets list is empty, then - * an offset of 0 is used for each sequence. If the offsets list is not empty, then it must be - * the same size as sequence. + * Returns the @{@link SubstringIndexFor} AnnotationMirror from the Substring Index hierarchy + * that imposes the same upper bounds on the annotated expression as this qualifier. However, + * the upper bounds represented by this qualifier do not apply to the value -1 which is always + * allowed by the returned annotation. * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @param extraOffset offset to add to each element of offsets; may be null - * @return an {@link UBQualifier} for the sequences with the given offsets + * @param env a processing environment used to build the returned annotation + * @return the AnnotationMirror from the Substring Index hierarchy that represents the same + * upper bounds as this qualifier */ - public static UBQualifier createUBQualifier( - List sequences, List offsets, @Nullable String extraOffset) { - assert !sequences.isEmpty(); - - OffsetEquation extraEq; - if (extraOffset == null) { - extraEq = OffsetEquation.ZERO; - } else { - extraEq = OffsetEquation.createOffsetFromJavaExpression(extraOffset); - if (extraEq.hasError()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - } - - return new LessThanLengthOf(sequences, offsets, extraEq); + public AnnotationMirror convertToSubstringIndexAnnotation(ProcessingEnvironment env) { + return convertToAnnotation(env, true); } /** - * Add the node as an offset to a copy of this qualifier. If this qualifier is UNKNOWN or - * BOTTOM, then UNKNOWN is returned. Otherwise, see {@link LessThanLengthOf#plusOffset(int)} for - * an explanation of how node is added as an offset. + * Helper method called by {@link #convertToAnnotation} and {@link + * convertToSubstringIndexAnnotation} that does the real work. * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @return a copy of this qualifier with node added as an offset + * @param env a processing environment used to build the returned annotation + * @param buildSubstringIndexAnnotation if true, act like {@link + * #convertToSubstringIndexAnnotation} and return a @{@link SubstringIndexFor} annotation; + * if false, act like {@link #convertToAnnotation} + * @return the AnnotationMirror that represents the same upper bounds as this qualifier */ - public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return UpperBoundUnknownQualifier.UNKNOWN; + private AnnotationMirror convertToAnnotation( + ProcessingEnvironment env, boolean buildSubstringIndexAnnotation) { + SequencesOffsetsAndClass soc = mapToSequencesAndOffsets(map, buildSubstringIndexAnnotation); + List sequences = soc.sequences; + List offsets = soc.offsets; + Class annoClass = soc.annoClass; + + AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); + if (annoClass == SubstringIndexFor.class) { + builder.setValue("value", sequences); + builder.setValue("offset", offsets); + } else if (annoClass == LTEqLengthOf.class) { + builder.setValue("value", sequences); + } else if (annoClass == LTOMLengthOf.class) { + builder.setValue("value", sequences); + } else if (annoClass == LTLengthOf.class) { + builder.setValue("value", sequences); + builder.setValue("offset", offsets); + } else { + throw new TypeSystemError("What annoClass? " + annoClass); + } + return builder.build(); } - public UBQualifier plusOffset(int value) { - return UpperBoundUnknownQualifier.UNKNOWN; + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LessThanLengthOf qualifier = (LessThanLengthOf) o; + if (containsSame(map.keySet(), qualifier.map.keySet())) { + for (Map.Entry> entry : map.entrySet()) { + Set otherOffset = qualifier.map.get(entry.getKey()); + Set thisOffset = entry.getValue(); + if (!containsSame(otherOffset, thisOffset)) { + return false; + } + } + return true; + } + return false; } - public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return UpperBoundUnknownQualifier.UNKNOWN; + private static boolean containsSame(Set set1, Set set2) { + return set1.containsAll(set2) && set2.containsAll(set1); } - public UBQualifier minusOffset(int value) { - return UpperBoundUnknownQualifier.UNKNOWN; + @Override + public int hashCode() { + return map.hashCode(); } + @Override public boolean isLessThanLengthQualifier() { - return false; + return true; } /** - * Returns true if this UBQualifier represents a literal integer. + * If superType is Unknown, return true. If superType is Bottom, return false. + * + *

Otherwise, return true if this qualifier contains all the sequences in superType, AND for + * each of the offsets for each sequence in superType, there is an offset in this qualifier for + * the sequence that is greater than or equal to the super offset. * - * @return true if this UBQualifier represents a literal integer + * @param superType other qualifier + * @return whether this qualifier is a subtype of superType */ - public boolean isLiteral() { + @Override + public boolean isSubtype(UBQualifier superType) { + if (superType.isUnknown()) { + return true; + } else if (superType.isBottom()) { return false; - } + } else if (superType.isLiteral()) { + return false; + } - /** - * Returns true if this UBQualifier is the top type. - * - * @return true if this UBQualifier is the top type - */ - public boolean isUnknown() { + LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; + + if (!map.keySet().containsAll(superTypeLTL.map.keySet())) { return false; + } + for (Map.Entry> entry : superTypeLTL.map.entrySet()) { + String sequence = entry.getKey(); + Set superOffsets = entry.getValue(); + Set subOffsets = map.get(sequence); + + if (!isSubtypeOffset(subOffsets, superOffsets)) { + return false; + } + } + + return true; } /** - * Returns true if this UBQualifier is the bottom type. - * - * @return true if this UBQualifier is the bottom type + * One set of offsets is a subtype of another if for every superOffsets, at least one suboffset + * is greater than or equal to the superOffset. */ - public boolean isBottom() { - return false; + private boolean isSubtypeOffset( + Set subOffsets, Set superOffsets) { + for (OffsetEquation superOffset : superOffsets) { + boolean oneIsSubtype = false; + for (OffsetEquation subOffset : subOffsets) { + if (superOffset.lessThanOrEqual(subOffset)) { + oneIsSubtype = true; + break; + } + } + if (!oneIsSubtype) { + return false; + } + } + return true; } /** - * Return true if this is UBQualifier.PolyQualifier. + * If other is Unknown, return Unknown. If other is Bottom, return this. * - * @return true if this is UBQualifier.PolyQualifier + *

Otherwise lub is computed as follows: + * + *

1. Create the intersection of the sets of arrays for this and other. + * + *

2. For each sequence in the intersection, get the offsets for this and other. If any + * offset in this is a less than or equal to an offset in other, then that offset is an offset + * for the sequence in lub. If any offset in other is a less than or equal to an offset in this, + * then that offset is an offset for the sequence in lub. + * + * @param other to lub with this + * @return the lub */ - @Pure - public boolean isPoly() { - return false; + @Override + public UBQualifier lub(UBQualifier other) { + if (other.isUnknown()) { + return other; + } else if (other.isBottom()) { + return this; + } else if (other.isLiteral()) { + return other.lub(this); + } + LessThanLengthOf otherLtl = (LessThanLengthOf) other; + + Set sequences = new HashSet<>(map.keySet()); + sequences.retainAll(otherLtl.map.keySet()); + + Map> lubMap = + new HashMap<>(CollectionsPlume.mapCapacity(sequences)); + for (String sequence : sequences) { + Set offsets1 = map.get(sequence); + Set offsets2 = otherLtl.map.get(sequence); + Set lub = new HashSet<>(offsets1.size() + offsets2.size()); + for (OffsetEquation offset1 : offsets1) { + for (OffsetEquation offset2 : offsets2) { + if (offset2.lessThanOrEqual(offset1)) { + lub.add(offset2); + } else if (offset1.lessThanOrEqual(offset2)) { + lub.add(offset1); + } + } + } + if (!lub.isEmpty()) { + lubMap.put(sequence, lub); + } + } + if (lubMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return new LessThanLengthOf(lubMap); } - public abstract boolean isSubtype(UBQualifier superType); - - public abstract UBQualifier lub(UBQualifier other); - + @Override public UBQualifier widenUpperBound(UBQualifier obj) { - return lub(obj); + UBQualifier lub = lub(obj); + if (!lub.isLessThanLengthQualifier() || !obj.isLessThanLengthQualifier()) { + return lub; + } + Map> lubMap = ((LessThanLengthOf) lub).map; + widenLub((LessThanLengthOf) obj, lubMap); + if (lubMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return new LessThanLengthOf(lubMap); } - public abstract UBQualifier glb(UBQualifier other); - /** - * Is the value with this qualifier less than the length of sequence? * - * @param sequence a String sequence - * @return whether or not the value with this qualifier is less than the length of sequence + * + *

@LTLengthOf("a") int i = ...;
+     * while (expr) {
+     *   i++;
+     * }
+ * + *

Dataflow never stops analyzing the above loop, because the type of i always changes after + * each analysis of the loop: + * + *

1. @LTLengthOf(value="a', offset="-1") + * + *

2. @LTLengthOf(value="a', offset="-2") + * + *

3. @LTLengthOf(value="a', offset="-3") + * + *

In order to prevent this, if both types passed to lub include all the same sequences with + * the same non-constant value offsets and if the constant value offsets are different then + * remove that sequence-offset pair from lub. + * + *

For example: + * + *

LUB @LTLengthOf(value={"a", "b"}, offset={"0", "0") and @LTLengthOf(value={"a", "b"}, + * offset={"-20", "0") is @LTLengthOf("b") + * + *

This widened lub should only be used in order to break dataflow analysis loops. */ - public boolean isLessThanLengthOf(String sequence) { - return false; + private void widenLub(LessThanLengthOf other, Map> lubMap) { + if (!containsSame(this.map.keySet(), lubMap.keySet()) + || !containsSame(other.map.keySet(), lubMap.keySet())) { + return; + } + List> remove = new ArrayList<>(); + for (Map.Entry> entry : lubMap.entrySet()) { + String sequence = entry.getKey(); + Set lubOffsets = entry.getValue(); + Set thisOffsets = this.map.get(sequence); + Set otherOffsets = other.map.get(sequence); + if (lubOffsets.size() != thisOffsets.size() || lubOffsets.size() != otherOffsets.size()) { + return; + } + for (OffsetEquation lubEq : lubOffsets) { + if (lubEq.isInt()) { + int thisInt = OffsetEquation.getIntOffsetEquation(thisOffsets).getInt(); + int otherInt = OffsetEquation.getIntOffsetEquation(otherOffsets).getInt(); + if (thisInt != otherInt) { + remove.add(IPair.of(sequence, lubEq)); + } + } else if (thisOffsets.contains(lubEq) && otherOffsets.contains(lubEq)) { + // continue; + } else { + return; + } + } + } + for (IPair pair : remove) { + String sequence = pair.first; + Set offsets = lubMap.get(sequence); + offsets.remove(pair.second); + if (offsets.isEmpty()) { + lubMap.remove(sequence); + } + } + } + + @Override + public UBQualifier glb(UBQualifier other) { + if (other.isUnknown()) { + return this; + } else if (other.isBottom()) { + return other; + } else if (other.isLiteral()) { + return other.glb(this); + } + LessThanLengthOf otherLtl = (LessThanLengthOf) other; + + Set sequences = new HashSet<>(map.keySet()); + sequences.addAll(otherLtl.map.keySet()); + + Map> glbMap = new HashMap<>(sequences.size()); + for (String sequence : sequences) { + Set glb = map.get(sequence); + Set otherglb = otherLtl.map.get(sequence); + if (glb == null) { + glb = otherglb; + } else if (otherglb != null) { + glb.addAll(otherglb); + } + glbMap.put(sequence, removeSmallerInts(glb)); + } + return new LessThanLengthOf(glbMap); } /** - * Is the value with this qualifier less than the length of any of the sequences? + * Returns a copy of the argument, but it contains just one offset equation that is an int value + * -- the largest one in the argument. Any non-int offset equations appear in the result. Does + * not side effect its argument. * - * @param sequences list of sequences - * @return whether or not the value with this qualifier is less than the length of any of the - * sequences + * @param offsets a set of offset equations + * @return a copy of the argument with just one int value (the largest in the input) and + * arbitrarily many non-ints */ - public boolean isLessThanLengthOfAny(List sequences) { - return false; + private Set removeSmallerInts(Set offsets) { + Set newOff = new HashSet<>(offsets.size()); + OffsetEquation literal = null; + for (OffsetEquation eq : offsets) { + if (eq.isInt()) { + if (literal == null) { + literal = eq; + } else { + literal = literal.lessThanOrEqual(eq) ? eq : literal; + } + } else { + newOff.add(eq); + } + } + if (literal != null) { + newOff.add(literal); + } + return newOff; } /** - * Returns whether or not this qualifier has sequence with the specified offset. + * Adds node as an offset to a copy of this qualifier. This is done by creating an offset + * equation for node and then adding that equation to every offset equation in a copy of this + * object. * - * @param sequence sequence expression - * @param offset the offset being looked for - * @return whether or not this qualifier has sequence with the specified offset + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @return a copy of this qualifier with node add as an offset */ - public boolean hasSequenceWithOffset(String sequence, int offset) { - return false; + @Override + public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return plusOrMinusOffset(node, factory, '+'); } /** - * Returns whether or not this qualifier has sequence with the specified offset. + * Adds node as a negative offset to a copy of this qualifier. This is done by creating a + * negative offset equation for node and then adding that equation to every offset equation in a + * copy of this object. * - * @param sequence sequence expression - * @param offset the offset being looked for - * @return whether or not this qualifier has sequence with the specified offset + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @return a copy of this qualifier with node add as an offset */ - public boolean hasSequenceWithOffset(String sequence, String offset) { - return false; + @Override + public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return plusOrMinusOffset(node, factory, '-'); } /** - * Is the value with this qualifier less than or equal to the length of sequence? + * Adds node as a positive or negative offset to a copy of this qualifier. This is done by + * creating an offset equation for node and then adding or subtracting that equation to every + * offset equation in a copy of this object. * - * @param sequence a String sequence - * @return whether or not the value with this qualifier is less than or equal to the length of - * sequence + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @param op either '-' or '+' + * @return a copy of this qualifier with node add as an offset */ - public boolean isLessThanOrEqualTo(String sequence) { - return false; - } - - /** The less-than-length-of qualifier (@LTLengthOf). */ - public static class LessThanLengthOf extends UBQualifier { - - // There are two representations for sequences and offsets. - // In source code, they are represented by two parallel arrays, as in - // @LTLengthOf(value = {"a", "b", "a", "c"}, offset = {"-1", "x", "y", "0"}). - // In this implementation, they are represented by a single map; the above would be - // { "a" : {"-1", "y"}, "b" : {"x"}, "c" : {"0"} } - // Code in this class transforms from one representation to the other. - - /** Maps from sequence name to offset. */ - private final Map> map; - - /** - * Returns a copy of the map. - * - * @return a copy of the map - */ - private Map> copyMap() { - Map> result = - new HashMap<>(CollectionsPlume.mapCapacity(map)); - for (String sequenceName : map.keySet()) { - Set oldEquations = map.get(sequenceName); - Set newEquations = - new HashSet<>(CollectionsPlume.mapCapacity(oldEquations)); - for (OffsetEquation offsetEquation : oldEquations) { - newEquations.add(new OffsetEquation(offsetEquation)); - } - result.put(sequenceName, newEquations); - } - return result; - } - - /** - * Returns true if the given integer literal is a subtype of this. The literal is a subtype - * of this if, for every offset expression, {@code literal + offset <= -1}. - * - * @param i an integer - * @return true if the given integer literal is a subtype of this - */ - /*package-private*/ boolean literalIsSubtype(int i) { - for (Map.Entry> entry : map.entrySet()) { - for (OffsetEquation equation : entry.getValue()) { - if (!equation.isInt()) { - return false; - } - int offset = equation.getInt(); - if (i + offset > -1) { - return false; - } - } - } - return true; - } - - /** - * Convert the parallel array representation to the map representation. - * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @param extraEq offset to add to each element of offsets; may be null - * @return the map representation of a {@link UBQualifier}, or null if there is an error - */ - private static @Nullable Map> sequencesAndOffsetsToMap( - List sequences, List offsets, @Nullable OffsetEquation extraEq) { - - Map> map = - new HashMap<>(CollectionsPlume.mapCapacity(sequences)); - if (offsets.isEmpty()) { - for (String sequence : sequences) { - // Not `Collections.singleton(extraEq)` because the values get modified - Set thisSet = new HashSet<>(1); - thisSet.add(extraEq); - map.put(sequence, thisSet); - } - } else { - assert sequences.size() == offsets.size(); - for (int i = 0; i < sequences.size(); i++) { - String sequence = sequences.get(i); - String offset = offsets.get(i); - Set set = map.computeIfAbsent(sequence, __ -> new HashSet<>()); - OffsetEquation eq = OffsetEquation.createOffsetFromJavaExpression(offset); - if (eq.hasError()) { - return null; - } - eq = eq.copyAdd('+', extraEq); - set.add(eq); - } - } - return map; - } - - /** A triple that is the return type of {@link #mapToSequencesAndOffsets}. */ - private static class SequencesOffsetsAndClass { - /** List of sequences. */ - public final List sequences; - - /** List of offsets. */ - public final List offsets; - - /** The class of the annotation to be built. */ - public final Class annoClass; - - /** - * Creates a new SequencesOffsetsAndClass. - * - * @param sequences list of sequences - * @param offsets list of offsets - * @param annoClass the class of the annotation to be built - */ - public SequencesOffsetsAndClass( - List sequences, - List offsets, - Class annoClass) { - - this.sequences = sequences; - this.offsets = offsets; - this.annoClass = annoClass; - } - } - - /** - * Given the map representation, returns parallel-arrays representation. - * - * @param map the internal representation of LessThanLengthOf - * @param buildSubstringIndexAnnotation if true, the annoClass in the result is - * ubstringIndexFor.class - * @return the external representation - */ - private static SequencesOffsetsAndClass mapToSequencesAndOffsets( - Map> map, boolean buildSubstringIndexAnnotation) { - List<@KeyFor("map") String> sortedSequences = new ArrayList<>(map.keySet()); - Collections.sort(sortedSequences); - List sequences = new ArrayList<>(); - List offsets = new ArrayList<>(); - boolean isLTEq = true; - boolean isLTOM = true; - for (String sequence : sortedSequences) { - // The offsets for this sequence. - List thisOffsets = new ArrayList<>(); - for (OffsetEquation eq : map.get(sequence)) { - isLTEq = isLTEq && eq.equals(OffsetEquation.NEG_1); - isLTOM = isLTOM && eq.equals(OffsetEquation.ONE); - thisOffsets.add(eq.toString()); - } - Collections.sort(thisOffsets); - for (String offset : thisOffsets) { - sequences.add(sequence); - offsets.add(offset); - } - } - Class annoClass; - if (buildSubstringIndexAnnotation) { - annoClass = SubstringIndexFor.class; - } else if (isLTEq) { - annoClass = LTEqLengthOf.class; - } else if (isLTOM) { - annoClass = LTOMLengthOf.class; - } else { - annoClass = LTLengthOf.class; - } - return new SequencesOffsetsAndClass(sequences, offsets, annoClass); - } - - // End of code for manipulating the representation - - /** - * Create a new LessThanLengthOf, from the internal representation. - * - * @param map a map from sequence name to offse - */ - private LessThanLengthOf(Map> map) { - assert !map.isEmpty(); - this.map = map; - } - - /** - * Create a new LessThanLengthOf from the parallel array representation. - * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @param extraEq offset to add to each element of offsets; may be null - */ - private LessThanLengthOf( - List sequences, List offsets, @Nullable OffsetEquation extraEq) { - this(sequencesAndOffsetsToMap(sequences, offsets, extraEq)); - } - - @Override - public boolean hasSequenceWithOffset(String sequence, int offset) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - return offsets.contains(OffsetEquation.createOffsetForInt(offset)); - } - - @Override - public boolean hasSequenceWithOffset(String sequence, String offset) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - OffsetEquation target = OffsetEquation.createOffsetFromJavaExpression(offset); - return offsets.contains(target); - } - - /** - * Is a value with this type less than or equal to the length of sequence? - * - * @param sequence a String sequence - * @return true if a value with this type is less than or equal to the length of sequence - */ - @Override - public boolean isLessThanOrEqualTo(String sequence) { - return isLessThanLengthOf(sequence) || hasSequenceWithOffset(sequence, -1); - } - - /** - * Is a value with this type less than the length of any of the sequences? - * - * @param sequences list of sequences - * @return true if a value with this type is less than the length of any of the sequences - */ - @Override - public boolean isLessThanLengthOfAny(List sequences) { - for (String sequence : sequences) { - if (isLessThanLengthOf(sequence)) { - return true; - } - } - return false; - } - - /** - * Is a value with this type less than the length of the sequence? - * - * @param sequence a String sequence - * @return true if a value with this type is less than the length of the sequence - */ - @Override - public boolean isLessThanLengthOf(String sequence) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - if (offsets.isEmpty()) { - return true; - } - for (OffsetEquation offset : offsets) { - if (offset.isNonNegative()) { - return true; - } - } - return false; - } - - /** - * Returns the AnnotationMirror that represents this qualifier. If possible, - * AnnotationMirrors using @{@link LTEqLengthOf} or @{@link LTOMLengthOf} are returned. - * Otherwise, @{@link LTLengthOf} is used. - * - *

The returned annotation is canonicalized by sorting its arguments by sequence and then - * offset. This is so that {@link AnnotationUtils#areSame(AnnotationMirror, - * AnnotationMirror)} returns true for equivalent annotations. - * - * @param env a processing environment used to build the returned annotation - * @return the AnnotationMirror that represents this qualifier - */ - public AnnotationMirror convertToAnnotation(ProcessingEnvironment env) { - return convertToAnnotation(env, false); - } - - /** - * Returns the @{@link SubstringIndexFor} AnnotationMirror from the Substring Index - * hierarchy that imposes the same upper bounds on the annotated expression as this - * qualifier. However, the upper bounds represented by this qualifier do not apply to the - * value -1 which is always allowed by the returned annotation. - * - * @param env a processing environment used to build the returned annotation - * @return the AnnotationMirror from the Substring Index hierarchy that represents the same - * upper bounds as this qualifier - */ - public AnnotationMirror convertToSubstringIndexAnnotation(ProcessingEnvironment env) { - return convertToAnnotation(env, true); - } - - /** - * Helper method called by {@link #convertToAnnotation} and {@link - * convertToSubstringIndexAnnotation} that does the real work. - * - * @param env a processing environment used to build the returned annotation - * @param buildSubstringIndexAnnotation if true, act like {@link - * #convertToSubstringIndexAnnotation} and return a @{@link SubstringIndexFor} - * annotation; if false, act like {@link #convertToAnnotation} - * @return the AnnotationMirror that represents the same upper bounds as this qualifier - */ - private AnnotationMirror convertToAnnotation( - ProcessingEnvironment env, boolean buildSubstringIndexAnnotation) { - SequencesOffsetsAndClass soc = - mapToSequencesAndOffsets(map, buildSubstringIndexAnnotation); - List sequences = soc.sequences; - List offsets = soc.offsets; - Class annoClass = soc.annoClass; - - AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); - if (annoClass == SubstringIndexFor.class) { - builder.setValue("value", sequences); - builder.setValue("offset", offsets); - } else if (annoClass == LTEqLengthOf.class) { - builder.setValue("value", sequences); - } else if (annoClass == LTOMLengthOf.class) { - builder.setValue("value", sequences); - } else if (annoClass == LTLengthOf.class) { - builder.setValue("value", sequences); - builder.setValue("offset", offsets); - } else { - throw new TypeSystemError("What annoClass? " + annoClass); - } - return builder.build(); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - LessThanLengthOf qualifier = (LessThanLengthOf) o; - if (containsSame(map.keySet(), qualifier.map.keySet())) { - for (Map.Entry> entry : map.entrySet()) { - Set otherOffset = qualifier.map.get(entry.getKey()); - Set thisOffset = entry.getValue(); - if (!containsSame(otherOffset, thisOffset)) { - return false; - } - } - return true; - } - return false; - } - - private static boolean containsSame(Set set1, Set set2) { - return set1.containsAll(set2) && set2.containsAll(set1); - } - - @Override - public int hashCode() { - return map.hashCode(); - } - - @Override - public boolean isLessThanLengthQualifier() { - return true; - } - - /** - * If superType is Unknown, return true. If superType is Bottom, return false. - * - *

Otherwise, return true if this qualifier contains all the sequences in superType, AND - * for each of the offsets for each sequence in superType, there is an offset in this - * qualifier for the sequence that is greater than or equal to the super offset. - * - * @param superType other qualifier - * @return whether this qualifier is a subtype of superType - */ - @Override - public boolean isSubtype(UBQualifier superType) { - if (superType.isUnknown()) { - return true; - } else if (superType.isBottom()) { - return false; - } else if (superType.isLiteral()) { - return false; - } - - LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; - - if (!map.keySet().containsAll(superTypeLTL.map.keySet())) { - return false; - } - for (Map.Entry> entry : superTypeLTL.map.entrySet()) { - String sequence = entry.getKey(); - Set superOffsets = entry.getValue(); - Set subOffsets = map.get(sequence); - - if (!isSubtypeOffset(subOffsets, superOffsets)) { - return false; - } - } - - return true; - } - - /** - * One set of offsets is a subtype of another if for every superOffsets, at least one - * suboffset is greater than or equal to the superOffset. - */ - private boolean isSubtypeOffset( - Set subOffsets, Set superOffsets) { - for (OffsetEquation superOffset : superOffsets) { - boolean oneIsSubtype = false; - for (OffsetEquation subOffset : subOffsets) { - if (superOffset.lessThanOrEqual(subOffset)) { - oneIsSubtype = true; - break; - } - } - if (!oneIsSubtype) { - return false; - } - } - return true; - } - - /** - * If other is Unknown, return Unknown. If other is Bottom, return this. - * - *

Otherwise lub is computed as follows: - * - *

1. Create the intersection of the sets of arrays for this and other. - * - *

2. For each sequence in the intersection, get the offsets for this and other. If any - * offset in this is a less than or equal to an offset in other, then that offset is an - * offset for the sequence in lub. If any offset in other is a less than or equal to an - * offset in this, then that offset is an offset for the sequence in lub. - * - * @param other to lub with this - * @return the lub - */ - @Override - public UBQualifier lub(UBQualifier other) { - if (other.isUnknown()) { - return other; - } else if (other.isBottom()) { - return this; - } else if (other.isLiteral()) { - return other.lub(this); - } - LessThanLengthOf otherLtl = (LessThanLengthOf) other; - - Set sequences = new HashSet<>(map.keySet()); - sequences.retainAll(otherLtl.map.keySet()); - - Map> lubMap = - new HashMap<>(CollectionsPlume.mapCapacity(sequences)); - for (String sequence : sequences) { - Set offsets1 = map.get(sequence); - Set offsets2 = otherLtl.map.get(sequence); - Set lub = new HashSet<>(offsets1.size() + offsets2.size()); - for (OffsetEquation offset1 : offsets1) { - for (OffsetEquation offset2 : offsets2) { - if (offset2.lessThanOrEqual(offset1)) { - lub.add(offset2); - } else if (offset1.lessThanOrEqual(offset2)) { - lub.add(offset1); - } - } - } - if (!lub.isEmpty()) { - lubMap.put(sequence, lub); - } - } - if (lubMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - return new LessThanLengthOf(lubMap); - } - - @Override - public UBQualifier widenUpperBound(UBQualifier obj) { - UBQualifier lub = lub(obj); - if (!lub.isLessThanLengthQualifier() || !obj.isLessThanLengthQualifier()) { - return lub; - } - Map> lubMap = ((LessThanLengthOf) lub).map; - widenLub((LessThanLengthOf) obj, lubMap); - if (lubMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - return new LessThanLengthOf(lubMap); - } - - /** - * - * - *

@LTLengthOf("a") int i = ...;
-         * while (expr) {
-         *   i++;
-         * }
- * - *

Dataflow never stops analyzing the above loop, because the type of i always changes - * after each analysis of the loop: - * - *

1. @LTLengthOf(value="a', offset="-1") - * - *

2. @LTLengthOf(value="a', offset="-2") - * - *

3. @LTLengthOf(value="a', offset="-3") - * - *

In order to prevent this, if both types passed to lub include all the same sequences - * with the same non-constant value offsets and if the constant value offsets are different - * then remove that sequence-offset pair from lub. - * - *

For example: - * - *

LUB @LTLengthOf(value={"a", "b"}, offset={"0", "0") and @LTLengthOf(value={"a", "b"}, - * offset={"-20", "0") is @LTLengthOf("b") - * - *

This widened lub should only be used in order to break dataflow analysis loops. - */ - private void widenLub(LessThanLengthOf other, Map> lubMap) { - if (!containsSame(this.map.keySet(), lubMap.keySet()) - || !containsSame(other.map.keySet(), lubMap.keySet())) { - return; - } - List> remove = new ArrayList<>(); - for (Map.Entry> entry : lubMap.entrySet()) { - String sequence = entry.getKey(); - Set lubOffsets = entry.getValue(); - Set thisOffsets = this.map.get(sequence); - Set otherOffsets = other.map.get(sequence); - if (lubOffsets.size() != thisOffsets.size() - || lubOffsets.size() != otherOffsets.size()) { - return; - } - for (OffsetEquation lubEq : lubOffsets) { - if (lubEq.isInt()) { - int thisInt = OffsetEquation.getIntOffsetEquation(thisOffsets).getInt(); - int otherInt = OffsetEquation.getIntOffsetEquation(otherOffsets).getInt(); - if (thisInt != otherInt) { - remove.add(IPair.of(sequence, lubEq)); - } - } else if (thisOffsets.contains(lubEq) && otherOffsets.contains(lubEq)) { - // continue; - } else { - return; - } - } - } - for (IPair pair : remove) { - String sequence = pair.first; - Set offsets = lubMap.get(sequence); - offsets.remove(pair.second); - if (offsets.isEmpty()) { - lubMap.remove(sequence); - } - } - } - - @Override - public UBQualifier glb(UBQualifier other) { - if (other.isUnknown()) { - return this; - } else if (other.isBottom()) { - return other; - } else if (other.isLiteral()) { - return other.glb(this); - } - LessThanLengthOf otherLtl = (LessThanLengthOf) other; - - Set sequences = new HashSet<>(map.keySet()); - sequences.addAll(otherLtl.map.keySet()); - - Map> glbMap = new HashMap<>(sequences.size()); - for (String sequence : sequences) { - Set glb = map.get(sequence); - Set otherglb = otherLtl.map.get(sequence); - if (glb == null) { - glb = otherglb; - } else if (otherglb != null) { - glb.addAll(otherglb); - } - glbMap.put(sequence, removeSmallerInts(glb)); - } - return new LessThanLengthOf(glbMap); - } - - /** - * Returns a copy of the argument, but it contains just one offset equation that is an int - * value -- the largest one in the argument. Any non-int offset equations appear in the - * result. Does not side effect its argument. - * - * @param offsets a set of offset equations - * @return a copy of the argument with just one int value (the largest in the input) and - * arbitrarily many non-ints - */ - private Set removeSmallerInts(Set offsets) { - Set newOff = new HashSet<>(offsets.size()); - OffsetEquation literal = null; - for (OffsetEquation eq : offsets) { - if (eq.isInt()) { - if (literal == null) { - literal = eq; - } else { - literal = literal.lessThanOrEqual(eq) ? eq : literal; - } - } else { - newOff.add(eq); - } - } - if (literal != null) { - newOff.add(literal); - } - return newOff; - } - - /** - * Adds node as an offset to a copy of this qualifier. This is done by creating an offset - * equation for node and then adding that equation to every offset equation in a copy of - * this object. - * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @return a copy of this qualifier with node add as an offset - */ - @Override - public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return plusOrMinusOffset(node, factory, '+'); + private UBQualifier plusOrMinusOffset( + Node node, UpperBoundAnnotatedTypeFactory factory, char op) { + assert op == '-' || op == '+'; + + // Try treating the offset as both an OffsetEquation and as a value. + // Use whichever is not null, or glb the two. + + OffsetEquation newOffset = OffsetEquation.createOffsetFromNode(node, factory, op); + LessThanLengthOf nodeOffsetQualifier = null; + if (!newOffset.hasError()) { + UBQualifier nodeOffsetQualifierMaybe = addOffset(newOffset); + if (!(nodeOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { + nodeOffsetQualifier = (LessThanLengthOf) nodeOffsetQualifierMaybe; + } + } + + OffsetEquation valueOffset = + OffsetEquation.createOffsetFromNodesValue( + node, factory.getValueAnnotatedTypeFactory(), op); + LessThanLengthOf valueOffsetQualifier = null; + if (valueOffset != null && !valueOffset.hasError()) { + UBQualifier valueOffsetQualifierMaybe = addOffset(valueOffset); + if (!(valueOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { + valueOffsetQualifier = (LessThanLengthOf) valueOffsetQualifierMaybe; + } + } + + if (valueOffsetQualifier == null) { + if (nodeOffsetQualifier == null) { + return UpperBoundUnknownQualifier.UNKNOWN; + } else { + return nodeOffsetQualifier; } - - /** - * Adds node as a negative offset to a copy of this qualifier. This is done by creating a - * negative offset equation for node and then adding that equation to every offset equation - * in a copy of this object. - * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @return a copy of this qualifier with node add as an offset - */ - @Override - public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return plusOrMinusOffset(node, factory, '-'); + } else { + if (nodeOffsetQualifier == null) { + return valueOffsetQualifier; + } else { + return nodeOffsetQualifier.glb(valueOffsetQualifier); } + } + } - /** - * Adds node as a positive or negative offset to a copy of this qualifier. This is done by - * creating an offset equation for node and then adding or subtracting that equation to - * every offset equation in a copy of this object. - * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @param op either '-' or '+' - * @return a copy of this qualifier with node add as an offset - */ - private UBQualifier plusOrMinusOffset( - Node node, UpperBoundAnnotatedTypeFactory factory, char op) { - assert op == '-' || op == '+'; - - // Try treating the offset as both an OffsetEquation and as a value. - // Use whichever is not null, or glb the two. - - OffsetEquation newOffset = OffsetEquation.createOffsetFromNode(node, factory, op); - LessThanLengthOf nodeOffsetQualifier = null; - if (!newOffset.hasError()) { - UBQualifier nodeOffsetQualifierMaybe = addOffset(newOffset); - if (!(nodeOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { - nodeOffsetQualifier = (LessThanLengthOf) nodeOffsetQualifierMaybe; - } - } - - OffsetEquation valueOffset = - OffsetEquation.createOffsetFromNodesValue( - node, factory.getValueAnnotatedTypeFactory(), op); - LessThanLengthOf valueOffsetQualifier = null; - if (valueOffset != null && !valueOffset.hasError()) { - UBQualifier valueOffsetQualifierMaybe = addOffset(valueOffset); - if (!(valueOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { - valueOffsetQualifier = (LessThanLengthOf) valueOffsetQualifierMaybe; - } - } - - if (valueOffsetQualifier == null) { - if (nodeOffsetQualifier == null) { - return UpperBoundUnknownQualifier.UNKNOWN; - } else { - return nodeOffsetQualifier; - } - } else { - if (nodeOffsetQualifier == null) { - return valueOffsetQualifier; - } else { - return nodeOffsetQualifier.glb(valueOffsetQualifier); - } - } - } + /** + * Adds value as an offset to a copy of this qualifier. This is done by adding value to every + * offset equation in a copy of this object. + * + * @param value int value to add + * @return a copy of this qualifier with value add as an offset + */ + @Override + public UBQualifier plusOffset(int value) { + OffsetEquation newOffset = OffsetEquation.createOffsetForInt(value); + return addOffset(newOffset); + } - /** - * Adds value as an offset to a copy of this qualifier. This is done by adding value to - * every offset equation in a copy of this object. - * - * @param value int value to add - * @return a copy of this qualifier with value add as an offset - */ - @Override - public UBQualifier plusOffset(int value) { - OffsetEquation newOffset = OffsetEquation.createOffsetForInt(value); - return addOffset(newOffset); - } + /** + * Adds the negation of value as an offset to a copy of this qualifier. This is done by adding + * the negation of {@code value} to every offset equation in a copy of this object. + * + * @param value int value to add + * @return a copy of this qualifier with value add as an offset + */ + @Override + public UBQualifier minusOffset(int value) { + OffsetEquation newOffset = OffsetEquation.createOffsetForInt(-value); + return addOffset(newOffset); + } - /** - * Adds the negation of value as an offset to a copy of this qualifier. This is done by - * adding the negation of {@code value} to every offset equation in a copy of this object. - * - * @param value int value to add - * @return a copy of this qualifier with value add as an offset - */ - @Override - public UBQualifier minusOffset(int value) { - OffsetEquation newOffset = OffsetEquation.createOffsetForInt(-value); - return addOffset(newOffset); - } + /** + * Returns a copy of this qualifier with sequence-offset pairs where in the original the offset + * contains an access of an sequence length in {@code sequences}. The sequence length access has + * been removed from the offset. If the original qualifier has no sequence length offsets, then + * UNKNOWN is returned. + * + * @param sequences access of the length of these sequences are removed + * @return a copy of this qualifier with some offsets removed + */ + public UBQualifier removeSequenceLengthAccess(List sequences) { + if (sequences.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + OffsetEquationFunction removeSequenceLengthsFunc = eq -> eq.removeSequenceLengths(sequences); + return computeNewOffsets(removeSequenceLengthsFunc); + } - /** - * Returns a copy of this qualifier with sequence-offset pairs where in the original the - * offset contains an access of an sequence length in {@code sequences}. The sequence length - * access has been removed from the offset. If the original qualifier has no sequence length - * offsets, then UNKNOWN is returned. - * - * @param sequences access of the length of these sequences are removed - * @return a copy of this qualifier with some offsets removed - */ - public UBQualifier removeSequenceLengthAccess(List sequences) { - if (sequences.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; + /** + * Returns a copy of this qualifier with sequence-offset pairs where in the original the offset + * contains an access of an sequence length in {@code sequences}. The sequence length access has + * been removed from the offset. If the offset also has -1 then -1 is also removed. + * + * @param sequences access of the length of these sequences are removed + * @return a copy of this qualifier with some offsets removed + */ + public UBQualifier removeSequenceLengthAccessAndNeg1(List sequences) { + if (sequences.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + OffsetEquationFunction removeSequenceLenFunc = + eq -> { + OffsetEquation newEq = eq.removeSequenceLengths(sequences); + if (newEq == null) { + return null; } - OffsetEquationFunction removeSequenceLengthsFunc = - eq -> eq.removeSequenceLengths(sequences); - return computeNewOffsets(removeSequenceLengthsFunc); - } - - /** - * Returns a copy of this qualifier with sequence-offset pairs where in the original the - * offset contains an access of an sequence length in {@code sequences}. The sequence length - * access has been removed from the offset. If the offset also has -1 then -1 is also - * removed. - * - * @param sequences access of the length of these sequences are removed - * @return a copy of this qualifier with some offsets removed - */ - public UBQualifier removeSequenceLengthAccessAndNeg1(List sequences) { - if (sequences.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; + if (newEq.getInt() == -1) { + return newEq.copyAdd('+', OffsetEquation.ONE); } - OffsetEquationFunction removeSequenceLenFunc = - eq -> { - OffsetEquation newEq = eq.removeSequenceLengths(sequences); - if (newEq == null) { - return null; - } - if (newEq.getInt() == -1) { - return newEq.copyAdd('+', OffsetEquation.ONE); - } - return newEq; - }; - return computeNewOffsets(removeSequenceLenFunc); - } - - /** - * Returns a new qualifier, which is this qualifier plus the given offset. - * - * @param newOffset the offset to add to this - * @return a new qualifier, which is this qualifier plus the given offset - */ - private UBQualifier addOffset(OffsetEquation newOffset) { - OffsetEquationFunction addOffsetFunc = eq -> eq.copyAdd('+', newOffset); - return computeNewOffsets(addOffsetFunc); - } + return newEq; + }; + return computeNewOffsets(removeSequenceLenFunc); + } - /** - * If divisor == 1, return this object. - * - *

If divisor greater than 1, then return a copy of this object keeping only sequences - * and offsets where the offset is less than or equal to zero. - * - *

Otherwise, return UNKNOWN. - * - * @param divisor number to divide by - * @return the result of dividing a value with this qualifier by divisor - */ - public UBQualifier divide(int divisor) { - if (divisor == 1) { - return this; - } else if (divisor > 1) { - OffsetEquationFunction divideFunc = eq -> (eq.isNegativeOrZero() ? eq : null); - return computeNewOffsets(divideFunc); - } - return UpperBoundUnknownQualifier.UNKNOWN; - } + /** + * Returns a new qualifier, which is this qualifier plus the given offset. + * + * @param newOffset the offset to add to this + * @return a new qualifier, which is this qualifier plus the given offset + */ + private UBQualifier addOffset(OffsetEquation newOffset) { + OffsetEquationFunction addOffsetFunc = eq -> eq.copyAdd('+', newOffset); + return computeNewOffsets(addOffsetFunc); + } - public boolean isValuePlusOffsetLessThanMinLen(String sequence, long value, int minlen) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - for (OffsetEquation offset : offsets) { - if (offset.isInt()) { - // This expression must not overflow - return (long) minlen - offset.getInt() > value; - } - } - return false; - } + /** + * If divisor == 1, return this object. + * + *

If divisor greater than 1, then return a copy of this object keeping only sequences and + * offsets where the offset is less than or equal to zero. + * + *

Otherwise, return UNKNOWN. + * + * @param divisor number to divide by + * @return the result of dividing a value with this qualifier by divisor + */ + public UBQualifier divide(int divisor) { + if (divisor == 1) { + return this; + } else if (divisor > 1) { + OffsetEquationFunction divideFunc = eq -> (eq.isNegativeOrZero() ? eq : null); + return computeNewOffsets(divideFunc); + } + return UpperBoundUnknownQualifier.UNKNOWN; + } - /** - * Checks whether replacing sequence with replacementSequence in this qualifier creates - * replacementSequence entry in other. - */ - public boolean isValidReplacement( - String sequence, String replacementSequence, LessThanLengthOf other) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - Set otherOffsets = other.map.get(replacementSequence); - if (otherOffsets == null) { - return false; - } - return containsSame(offsets, otherOffsets); - } + public boolean isValuePlusOffsetLessThanMinLen(String sequence, long value, int minlen) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + for (OffsetEquation offset : offsets) { + if (offset.isInt()) { + // This expression must not overflow + return (long) minlen - offset.getInt() > value; + } + } + return false; + } - @Override - public String toString() { - return "LessThanLengthOf{" + "map=" + map + '}'; - } + /** + * Checks whether replacing sequence with replacementSequence in this qualifier creates + * replacementSequence entry in other. + */ + public boolean isValidReplacement( + String sequence, String replacementSequence, LessThanLengthOf other) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + Set otherOffsets = other.map.get(replacementSequence); + if (otherOffsets == null) { + return false; + } + return containsSame(offsets, otherOffsets); + } - public Iterable getSequences() { - return map.keySet(); - } + @Override + public String toString() { + return "LessThanLengthOf{" + "map=" + map + '}'; + } - /** - * Generates a new UBQualifer without the given (sequence, offset) pair. Other occurrences - * of the sequence and the offset may remain in the result, but not together. - * - * @param sequence a Java expression representing a string - * @param offset an integral offset - * @return a new UBQualifer without the given sequence and offset - */ - public UBQualifier removeOffset(String sequence, int offset) { - OffsetEquation offsetEq = OffsetEquation.createOffsetForInt(offset); - Map> newMap = copyMap(); - Set equations = newMap.get(sequence); - if (equations != null) { - equations.remove(offsetEq); - if (equations.isEmpty()) { - newMap.remove(sequence); - } - } + public Iterable getSequences() { + return map.keySet(); + } - if (newMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } else { - return new LessThanLengthOf(newMap); - } - } + /** + * Generates a new UBQualifer without the given (sequence, offset) pair. Other occurrences of + * the sequence and the offset may remain in the result, but not together. + * + * @param sequence a Java expression representing a string + * @param offset an integral offset + * @return a new UBQualifer without the given sequence and offset + */ + public UBQualifier removeOffset(String sequence, int offset) { + OffsetEquation offsetEq = OffsetEquation.createOffsetForInt(offset); + Map> newMap = copyMap(); + Set equations = newMap.get(sequence); + if (equations != null) { + equations.remove(offsetEq); + if (equations.isEmpty()) { + newMap.remove(sequence); + } + } + + if (newMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } else { + return new LessThanLengthOf(newMap); + } + } - /** Functional interface that operates on {@link OffsetEquation}s. */ - private interface OffsetEquationFunction { - /** - * Returns the result of the computation or null if the passed equation should be - * removed. - * - * @param eq current offset equation - * @return the result of the computation or null if the passed equation should be - * removed - */ - @Nullable OffsetEquation compute(OffsetEquation eq); - } + /** Functional interface that operates on {@link OffsetEquation}s. */ + private interface OffsetEquationFunction { + /** + * Returns the result of the computation or null if the passed equation should be removed. + * + * @param eq current offset equation + * @return the result of the computation or null if the passed equation should be removed + */ + @Nullable OffsetEquation compute(OffsetEquation eq); + } - /** - * Returns a new qualifier that is a copy of this qualifier with the OffsetEquationFunction - * applied to each offset. - * - *

If the {@link OffsetEquationFunction} returns null, it's not added as an offset. If - * after all functions have been applied, an sequence has no offsets, then that sequence is - * not added to the returned qualifier. If no sequences are added to the returned qualifier, - * then UNKNOWN is returned. - * - * @param f function to apply - * @return a new qualifier that is a copy of this qualifier with the OffsetEquationFunction - * applied to each offset - */ - private UBQualifier computeNewOffsets(OffsetEquationFunction f) { - Map> newMap = new HashMap<>(map.size()); - for (Map.Entry> entry : map.entrySet()) { - Set offsets = new HashSet<>(entry.getValue().size()); - for (OffsetEquation eq : entry.getValue()) { - OffsetEquation newEq = f.compute(eq); - if (newEq != null) { - offsets.add(newEq); - } - } - if (!offsets.isEmpty()) { - newMap.put(entry.getKey(), offsets); - } - } - if (newMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - return new LessThanLengthOf(newMap); - } + /** + * Returns a new qualifier that is a copy of this qualifier with the OffsetEquationFunction + * applied to each offset. + * + *

If the {@link OffsetEquationFunction} returns null, it's not added as an offset. If after + * all functions have been applied, an sequence has no offsets, then that sequence is not added + * to the returned qualifier. If no sequences are added to the returned qualifier, then UNKNOWN + * is returned. + * + * @param f function to apply + * @return a new qualifier that is a copy of this qualifier with the OffsetEquationFunction + * applied to each offset + */ + private UBQualifier computeNewOffsets(OffsetEquationFunction f) { + Map> newMap = new HashMap<>(map.size()); + for (Map.Entry> entry : map.entrySet()) { + Set offsets = new HashSet<>(entry.getValue().size()); + for (OffsetEquation eq : entry.getValue()) { + OffsetEquation newEq = f.compute(eq); + if (newEq != null) { + offsets.add(newEq); + } + } + if (!offsets.isEmpty()) { + newMap.put(entry.getKey(), offsets); + } + } + if (newMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return new LessThanLengthOf(newMap); } + } - /** Represents an integer value that is known at compile time. */ - public static class UpperBoundLiteralQualifier extends UBQualifier { + /** Represents an integer value that is known at compile time. */ + public static class UpperBoundLiteralQualifier extends UBQualifier { - /** Represents the value -1. */ - public static final UpperBoundLiteralQualifier NEGATIVEONE = - new UpperBoundLiteralQualifier(-1); + /** Represents the value -1. */ + public static final UpperBoundLiteralQualifier NEGATIVEONE = new UpperBoundLiteralQualifier(-1); - /** Represents the value 0. */ - public static final UpperBoundLiteralQualifier ZERO = new UpperBoundLiteralQualifier(0); + /** Represents the value 0. */ + public static final UpperBoundLiteralQualifier ZERO = new UpperBoundLiteralQualifier(0); - /** Represents the value 1. */ - public static final UpperBoundLiteralQualifier ONE = new UpperBoundLiteralQualifier(1); + /** Represents the value 1. */ + public static final UpperBoundLiteralQualifier ONE = new UpperBoundLiteralQualifier(1); - /** - * Creates a new UpperBoundLiteralQualifier, without using cached values. - * - * @param value the integer value - */ - private UpperBoundLiteralQualifier(int value) { - this.value = value; - } + /** + * Creates a new UpperBoundLiteralQualifier, without using cached values. + * + * @param value the integer value + */ + private UpperBoundLiteralQualifier(int value) { + this.value = value; + } - /** - * Creates an UpperBoundLiteralQualifier. - * - * @param value the integer value - * @return an UpperBoundLiteralQualifier - */ - public static UpperBoundLiteralQualifier create(int value) { - switch (value) { - case -1: - return NEGATIVEONE; - case 0: - return ZERO; - case 1: - return ONE; - default: - return new UpperBoundLiteralQualifier(value); - } - } + /** + * Creates an UpperBoundLiteralQualifier. + * + * @param value the integer value + * @return an UpperBoundLiteralQualifier + */ + public static UpperBoundLiteralQualifier create(int value) { + switch (value) { + case -1: + return NEGATIVEONE; + case 0: + return ZERO; + case 1: + return ONE; + default: + return new UpperBoundLiteralQualifier(value); + } + } - /** The integer value. */ - private final int value; + /** The integer value. */ + private final int value; - /** - * Returns the integer value. - * - * @return the integer value - */ - public int getValue() { - return value; - } + /** + * Returns the integer value. + * + * @return the integer value + */ + public int getValue() { + return value; + } - @Override - public boolean isLiteral() { - return true; - } + @Override + public boolean isLiteral() { + return true; + } - @Override - public boolean isSubtype(UBQualifier superType) { - if (superType.isUnknown()) { - return true; - } else if (superType.isBottom()) { - return false; - } else if (superType.isPoly()) { - return false; - } else if (superType.isLiteral()) { - int otherValue = ((UpperBoundLiteralQualifier) superType).value; - return value == otherValue; - } + @Override + public boolean isSubtype(UBQualifier superType) { + if (superType.isUnknown()) { + return true; + } else if (superType.isBottom()) { + return false; + } else if (superType.isPoly()) { + return false; + } else if (superType.isLiteral()) { + int otherValue = ((UpperBoundLiteralQualifier) superType).value; + return value == otherValue; + } - LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; - return superTypeLTL.literalIsSubtype(value); - } + LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; + return superTypeLTL.literalIsSubtype(value); + } - @Override - public UBQualifier lub(UBQualifier other) { - if (isSubtype(other)) { - return other; - } else { - return UpperBoundUnknownQualifier.UNKNOWN; - } - } + @Override + public UBQualifier lub(UBQualifier other) { + if (isSubtype(other)) { + return other; + } else { + return UpperBoundUnknownQualifier.UNKNOWN; + } + } - @Override - public UBQualifier glb(UBQualifier other) { - if (isSubtype(other)) { - return this; - } else { - return UpperBoundBottomQualifier.BOTTOM; - } - } + @Override + public UBQualifier glb(UBQualifier other) { + if (isSubtype(other)) { + return this; + } else { + return UpperBoundBottomQualifier.BOTTOM; + } + } - @Override - public String toString() { - return "Literal(" + value + ")"; - } + @Override + public String toString() { + return "Literal(" + value + ")"; } + } - /** The top type qualifier. */ - public static class UpperBoundUnknownQualifier extends UBQualifier { - /** The canonical representative. */ - public static final UBQualifier UNKNOWN = new UpperBoundUnknownQualifier(); + /** The top type qualifier. */ + public static class UpperBoundUnknownQualifier extends UBQualifier { + /** The canonical representative. */ + public static final UBQualifier UNKNOWN = new UpperBoundUnknownQualifier(); - /** This class is a singleton. */ - private UpperBoundUnknownQualifier() {} + /** This class is a singleton. */ + private UpperBoundUnknownQualifier() {} - @Override - public boolean isSubtype(UBQualifier superType) { - return superType.isUnknown(); - } + @Override + public boolean isSubtype(UBQualifier superType) { + return superType.isUnknown(); + } - @Override - public boolean isUnknown() { - return true; - } + @Override + public boolean isUnknown() { + return true; + } - @Override - public UBQualifier lub(UBQualifier other) { - return this; - } + @Override + public UBQualifier lub(UBQualifier other) { + return this; + } - @Override - public UBQualifier glb(UBQualifier other) { - return other; - } + @Override + public UBQualifier glb(UBQualifier other) { + return other; + } - @Override - public String toString() { - return "UNKNOWN"; - } + @Override + public String toString() { + return "UNKNOWN"; } + } - /** The bottom qualifier for the upperbound type system. */ - private static class UpperBoundBottomQualifier extends UBQualifier { - /** The canonical bottom qualifier for the upperbound type system. */ - public static final UBQualifier BOTTOM = new UpperBoundBottomQualifier(); + /** The bottom qualifier for the upperbound type system. */ + private static class UpperBoundBottomQualifier extends UBQualifier { + /** The canonical bottom qualifier for the upperbound type system. */ + public static final UBQualifier BOTTOM = new UpperBoundBottomQualifier(); - /** This class is a singleton. */ - private UpperBoundBottomQualifier() {} + /** This class is a singleton. */ + private UpperBoundBottomQualifier() {} - @Override - public boolean isBottom() { - return true; - } + @Override + public boolean isBottom() { + return true; + } - @Override - public boolean isSubtype(UBQualifier superType) { - return true; - } + @Override + public boolean isSubtype(UBQualifier superType) { + return true; + } - @Override - public UBQualifier lub(UBQualifier other) { - return other; - } + @Override + public UBQualifier lub(UBQualifier other) { + return other; + } - @Override - public UBQualifier glb(UBQualifier other) { - return this; - } + @Override + public UBQualifier glb(UBQualifier other) { + return this; + } - @Override - public String toString() { - return "BOTTOM"; - } + @Override + public String toString() { + return "BOTTOM"; } + } - /** The polymorphic qualifier. */ - private static class PolyQualifier extends UBQualifier { - /** The canonical representative. */ - public static final UBQualifier POLY = new PolyQualifier(); + /** The polymorphic qualifier. */ + private static class PolyQualifier extends UBQualifier { + /** The canonical representative. */ + public static final UBQualifier POLY = new PolyQualifier(); - /** This class is a singleton. */ - private PolyQualifier() {} + /** This class is a singleton. */ + private PolyQualifier() {} - @Override - @Pure - public boolean isPoly() { - return true; - } + @Override + @Pure + public boolean isPoly() { + return true; + } - @Override - public boolean isSubtype(UBQualifier superType) { - return superType.isUnknown() || superType.isPoly(); - } + @Override + public boolean isSubtype(UBQualifier superType) { + return superType.isUnknown() || superType.isPoly(); + } - @Override - public UBQualifier lub(UBQualifier other) { - if (other.isPoly() || other.isBottom()) { - return this; - } - return UpperBoundUnknownQualifier.UNKNOWN; - } + @Override + public UBQualifier lub(UBQualifier other) { + if (other.isPoly() || other.isBottom()) { + return this; + } + return UpperBoundUnknownQualifier.UNKNOWN; + } - @Override - public UBQualifier glb(UBQualifier other) { - if (other.isPoly() || other.isUnknown()) { - return this; - } - return UpperBoundBottomQualifier.BOTTOM; - } + @Override + public UBQualifier glb(UBQualifier other) { + if (other.isPoly() || other.isUnknown()) { + return this; + } + return UpperBoundBottomQualifier.BOTTOM; } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java index 5eaba43cee8..65415bb9b28 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java @@ -8,7 +8,18 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; import com.sun.source.util.TreePath; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.checker.index.IndexMethodIdentifier; @@ -71,20 +82,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.IPair; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** * Implements the introduction rules for the Upper Bound Checker. * @@ -112,925 +109,889 @@ */ public class UpperBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker { - /** The @{@link UpperBoundUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, UpperBoundUnknown.class); - - /** The @{@link UpperBoundBottom} annotation. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, UpperBoundBottom.class); - - /** The @{@link PolyUpperBound} annotation. */ - public final AnnotationMirror POLY = - AnnotationBuilder.fromClass(elements, PolyUpperBound.class); - - /** The @{@link UpperBoundLiteral}(-1) annotation. */ - public final AnnotationMirror NEGATIVEONE = - new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", -1) - .build(); - - /** The @{@link UpperBoundLiteral}(0) annotation. */ - public final AnnotationMirror ZERO = - new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", 0) - .build(); - - /** The @{@link UpperBoundLiteral}(1) annotation. */ - public final AnnotationMirror ONE = - new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", 1) - .build(); - - /** The NegativeIndexFor.value element/field. */ - public final ExecutableElement negativeIndexForValueElement = - TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); - - /** The SameLen.value element/field. */ - public final ExecutableElement sameLenValueElement = - TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); - - /** The LTLengthOf.value element/field. */ - public final ExecutableElement ltLengthOfValueElement = - TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); - - /** The LTLengthOf.offset element/field. */ - public final ExecutableElement ltLengthOfOffsetElement = - TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); - - /** Predicates about what method an invocation is calling. */ - private final IndexMethodIdentifier imf; - - /** Create a new UpperBoundAnnotatedTypeFactory. */ - public UpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - addAliasedTypeAnnotation(IndexFor.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(IndexOrLow.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(IndexOrHigh.class, LTEqLengthOf.class, true); - addAliasedTypeAnnotation(SearchIndexFor.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(NegativeIndexFor.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(LengthOf.class, LTEqLengthOf.class, true); - addAliasedTypeAnnotation(PolyIndex.class, POLY); - - imf = new IndexMethodIdentifier(this); - - this.postInit(); + /** The @{@link UpperBoundUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, UpperBoundUnknown.class); + + /** The @{@link UpperBoundBottom} annotation. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, UpperBoundBottom.class); + + /** The @{@link PolyUpperBound} annotation. */ + public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyUpperBound.class); + + /** The @{@link UpperBoundLiteral}(-1) annotation. */ + public final AnnotationMirror NEGATIVEONE = + new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", -1) + .build(); + + /** The @{@link UpperBoundLiteral}(0) annotation. */ + public final AnnotationMirror ZERO = + new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", 0) + .build(); + + /** The @{@link UpperBoundLiteral}(1) annotation. */ + public final AnnotationMirror ONE = + new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", 1) + .build(); + + /** The NegativeIndexFor.value element/field. */ + public final ExecutableElement negativeIndexForValueElement = + TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); + + /** The SameLen.value element/field. */ + public final ExecutableElement sameLenValueElement = + TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); + + /** The LTLengthOf.value element/field. */ + public final ExecutableElement ltLengthOfValueElement = + TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); + + /** The LTLengthOf.offset element/field. */ + public final ExecutableElement ltLengthOfOffsetElement = + TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); + + /** Predicates about what method an invocation is calling. */ + private final IndexMethodIdentifier imf; + + /** Create a new UpperBoundAnnotatedTypeFactory. */ + public UpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + addAliasedTypeAnnotation(IndexFor.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(IndexOrLow.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(IndexOrHigh.class, LTEqLengthOf.class, true); + addAliasedTypeAnnotation(SearchIndexFor.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(NegativeIndexFor.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(LengthOf.class, LTEqLengthOf.class, true); + addAliasedTypeAnnotation(PolyIndex.class, POLY); + + imf = new IndexMethodIdentifier(this); + + this.postInit(); + } + + /** Gets a helper object that holds references to methods with special handling. */ + IndexMethodIdentifier getMethodIdentifier() { + return imf; + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList( + UpperBoundUnknown.class, + LTEqLengthOf.class, + LTLengthOf.class, + LTOMLengthOf.class, + UpperBoundLiteral.class, + UpperBoundBottom.class, + PolyUpperBound.class)); + } + + /** + * Provides a way to query the Constant Value Checker, which computes the values of expressions + * known at compile time (constant propagation and folding). + */ + ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); + } + + /** + * Provides a way to query the Search Index Checker, which helps the Index Checker type the + * results of calling the JDK's binary search methods correctly. + */ + private SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SearchIndexChecker.class); + } + + /** + * Gets the annotated type factory of the Substring Index Checker running along with the Upper + * Bound checker, allowing it to refine the upper bounds of expressions annotated by Substring + * Index Checker annotations. + */ + SubstringIndexAnnotatedTypeFactory getSubstringIndexAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SubstringIndexChecker.class); + } + + /** + * Provides a way to query the SameLen (same length) Checker, which determines the relationships + * among the lengths of arrays. + */ + SameLenAnnotatedTypeFactory getSameLenAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SameLenChecker.class); + } + + /** + * Provides a way to query the Lower Bound Checker, which determines whether each integer in the + * program is non-negative or not, and checks that no possibly negative integers are used to + * access arrays. + */ + LowerBoundAnnotatedTypeFactory getLowerBoundAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(LowerBoundChecker.class); + } + + /** Returns the LessThan Checker's annotated type factory. */ + public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(LessThanChecker.class); + } + + @Override + public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(element, type); + if (element != null && !ajavaTypes.isParsing()) { + AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element); + addUpperBoundTypeFromValueType(valueType, type); + } + } + + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); + // If dataflow shouldn't be used to compute this type, then do not use the result from + // the Value Checker, because dataflow is used to compute that type. (Without this, + // "int i = 1; --i;" fails.) + if (getUseFlow() + && tree != null + && !ajavaTypes.isParsing() + && TreeUtils.isExpressionTree(tree)) { + AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); + addUpperBoundTypeFromValueType(valueType, type); } + } + + /** + * Checks if valueType contains a {@link org.checkerframework.common.value.qual.BottomVal} + * annotation. If so, adds an {@link UpperBoundBottom} annotation to type. + * + * @param valueType annotated type from the {@link ValueAnnotatedTypeFactory} + * @param type annotated type from this factory that is side effected + */ + private void addUpperBoundTypeFromValueType( + AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { + if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { + type.replaceAnnotation(BOTTOM); + } + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(new UpperBoundTypeAnnotator(this), super.createTypeAnnotator()); + } + + /** + * Performs pre-processing on annotations written by users, replacing illegal annotations by legal + * ones. + */ + private class UpperBoundTypeAnnotator extends TypeAnnotator { - /** Gets a helper object that holds references to methods with special handling. */ - IndexMethodIdentifier getMethodIdentifier() { - return imf; + private UpperBoundTypeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - UpperBoundUnknown.class, - LTEqLengthOf.class, - LTLengthOf.class, - LTOMLengthOf.class, - UpperBoundLiteral.class, - UpperBoundBottom.class, - PolyUpperBound.class)); + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + // If there is an LTLengthOf annotation whose argument lengths don't match, replace it + // with bottom. + AnnotationMirror anm = type.getAnnotation(LTLengthOf.class); + if (anm != null) { + List sequences = + AnnotationUtils.getElementValueArray(anm, ltLengthOfValueElement, String.class); + List offsets = + AnnotationUtils.getElementValueArray( + anm, ltLengthOfOffsetElement, String.class, Collections.emptyList()); + if (sequences != null + && offsets != null + && sequences.size() != offsets.size() + && !offsets.isEmpty()) { + // Cannot use type.replaceAnnotation because it will call isSubtype, which will + // try to process the annotation and throw an error. + type.clearAnnotations(); + type.addAnnotation(BOTTOM); + } + } + return super.scan(type, aVoid); } - + } + + @Override + protected DependentTypesHelper createDependentTypesHelper() { + return new OffsetDependentTypesHelper(this); + } + + /** + * Queries the SameLen Checker to return the type that the SameLen Checker associates with the + * given tree. + */ + public @Nullable AnnotationMirror sameLenAnnotationFromTree(Tree tree) { + AnnotatedTypeMirror sameLenType = getSameLenAnnotatedTypeFactory().getAnnotatedType(tree); + return sameLenType.getAnnotation(SameLen.class); + } + + // Wrapper methods for accessing the IndexMethodIdentifier. + + public boolean isMathMin(Tree methodTree) { + return imf.isMathMin(methodTree); + } + + /** + * Returns true if the tree is for {@code Random.nextInt(int)}. + * + * @param methodTree a tree to check + * @return true iff the tree is for {@code Random.nextInt(int)} + */ + public boolean isRandomNextInt(Tree methodTree) { + return imf.isRandomNextInt(methodTree, processingEnv); + } + + /** + * Creates a new @LTLengthOf annotation. + * + * @param names the arguments to @LTLengthOf + * @return a new @LTLengthOf annotation with the given arguments + */ + AnnotationMirror createLTLengthOfAnnotation(String... names) { + if (names == null || names.length == 0) { + throw new TypeSystemError( + "createLTLengthOfAnnotation: bad argument %s", Arrays.toString(names)); + } + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTLengthOf.class); + builder.setValue("value", names); + return builder.build(); + } + + /** + * Creates a new @LTEqLengthOf annotation. + * + * @param names the arguments to @LTEqLengthOf + * @return a new @LTEqLengthOf annotation with the given arguments + */ + AnnotationMirror createLTEqLengthOfAnnotation(String... names) { + if (names == null || names.length == 0) { + throw new TypeSystemError( + "createLTEqLengthOfAnnotation: bad argument %s", Arrays.toString(names)); + } + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTEqLengthOf.class); + builder.setValue("value", names); + return builder.build(); + } + + /** + * Returns true iff the given node has the passed Lower Bound qualifier according to the LBC. The + * last argument should be Positive.class, NonNegative.class, or GTENegativeOne.class. + * + * @param node the given node + * @param classOfType one of Positive.class, NonNegative.class, or GTENegativeOne.class + * @return true iff the given node has the passed Lower Bound qualifier according to the LBC + */ + public boolean hasLowerBoundTypeByClass(Node node, Class classOfType) { + return areSameByClass( + getLowerBoundAnnotatedTypeFactory() + .getAnnotatedType(node.getTree()) + .getAnnotationInHierarchy(getLowerBoundAnnotatedTypeFactory().UNKNOWN), + classOfType); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new UpperBoundQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** The qualifier hierarchy for the upperbound type system. */ + protected final class UpperBoundQualifierHierarchy extends ElementQualifierHierarchy { /** - * Provides a way to query the Constant Value Checker, which computes the values of expressions - * known at compile time (constant propagation and folding). + * Creates an UpperBoundQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils */ - ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); + UpperBoundQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, UpperBoundAnnotatedTypeFactory.this); } - /** - * Provides a way to query the Search Index Checker, which helps the Index Checker type the - * results of calling the JDK's binary search methods correctly. - */ - private SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SearchIndexChecker.class); + @Override + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); + UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); + UBQualifier glb = a1Obj.glb(a2Obj); + return convertUBQualifierToAnnotation(glb); } /** - * Gets the annotated type factory of the Substring Index Checker running along with the Upper - * Bound checker, allowing it to refine the upper bounds of expressions annotated by Substring - * Index Checker annotations. + * Determines the least upper bound of a1 and a2. If a1 and a2 are both the same type of Value + * annotation, then the LUB is the result of taking the intersection of values from both a1 and + * a2. + * + * @return the least upper bound of a1 and a2 */ - SubstringIndexAnnotatedTypeFactory getSubstringIndexAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SubstringIndexChecker.class); + @Override + public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); + UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); + UBQualifier lub = a1Obj.lub(a2Obj); + return convertUBQualifierToAnnotation(lub); } - /** - * Provides a way to query the SameLen (same length) Checker, which determines the relationships - * among the lengths of arrays. - */ - SameLenAnnotatedTypeFactory getSameLenAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SameLenChecker.class); + @Override + public AnnotationMirror widenedUpperBound( + AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { + UBQualifier a1Obj = UBQualifier.createUBQualifier(newQualifier, (IndexChecker) checker); + UBQualifier a2Obj = UBQualifier.createUBQualifier(previousQualifier, (IndexChecker) checker); + UBQualifier lub = a1Obj.widenUpperBound(a2Obj); + return convertUBQualifierToAnnotation(lub); + } + + @Override + public int numberOfIterationsBeforeWidening() { + return 10; } /** - * Provides a way to query the Lower Bound Checker, which determines whether each integer in the - * program is non-negative or not, and checks that no possibly negative integers are used to - * access arrays. + * {@inheritDoc} + * + *

Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both + * annotations have the same class. In this case, rhs is a subtype of lhs iff rhs contains every + * element of lhs. */ - LowerBoundAnnotatedTypeFactory getLowerBoundAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(LowerBoundChecker.class); + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + UBQualifier subtypeQual = UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker); + UBQualifier supertypeQual = UBQualifier.createUBQualifier(superAnno, (IndexChecker) checker); + return subtypeQual.isSubtype(supertypeQual); } + } - /** Returns the LessThan Checker's annotated type factory. */ - public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(LessThanChecker.class); - } + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new UpperBoundTreeAnnotator(this), super.createTreeAnnotator()); + } - @Override - public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(element, type); - if (element != null && !ajavaTypes.isParsing()) { - AnnotatedTypeMirror valueType = - getValueAnnotatedTypeFactory().getAnnotatedType(element); - addUpperBoundTypeFromValueType(valueType, type); - } + protected class UpperBoundTreeAnnotator extends TreeAnnotator { + + public UpperBoundTreeAnnotator(UpperBoundAnnotatedTypeFactory factory) { + super(factory); } + /** + * This exists for Math.min and Random.nextInt, which must be special-cased. + * + *

    + *
  • Math.min has unusual semantics that combines annotations for the UBC. + *
  • The return type of Random.nextInt depends on the argument, but is not equal to it, so a + * polymorphic qualifier is insufficient. + *
+ * + * Other methods should not be special-cased here unless there is a compelling reason to do so. + */ @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(tree, type); - // If dataflow shouldn't be used to compute this type, then do not use the result from - // the Value Checker, because dataflow is used to compute that type. (Without this, - // "int i = 1; --i;" fails.) - if (getUseFlow() - && tree != null - && !ajavaTypes.isParsing() - && TreeUtils.isExpressionTree(tree)) { - AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); - addUpperBoundTypeFromValueType(valueType, type); - } + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (isMathMin(tree)) { + AnnotatedTypeMirror leftType = getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror rightType = getAnnotatedType(tree.getArguments().get(1)); + + type.replaceAnnotation( + qualHierarchy.greatestLowerBoundShallow( + leftType.getAnnotationInHierarchy(UNKNOWN), + leftType.getUnderlyingType(), + rightType.getAnnotationInHierarchy(UNKNOWN), + rightType.getUnderlyingType())); + } + if (isRandomNextInt(tree)) { + AnnotatedTypeMirror argType = getAnnotatedType(tree.getArguments().get(0)); + AnnotationMirror anno = argType.getAnnotationInHierarchy(UNKNOWN); + UBQualifier qualifier = UBQualifier.createUBQualifier(anno, (IndexChecker) checker); + qualifier = qualifier.plusOffset(1); + type.replaceAnnotation(convertUBQualifierToAnnotation(qualifier)); + } + if (imf.isIndexOfString(tree)) { + // String#indexOf(String) and its variants that also take a String technically + // return (and are annotated as) @LTEqLengthOf the receiver. However, the result is + // always @LTLengthOf the receiver unless both the receiver and the target string + // are the empty string: "".indexOf("") returns 0, which isn't an index into "". So, + // this special case modifies the return type of these methods if either the + // parameter or the receiver is known (by the Value Checker) to not be the empty + // string. There are three ways the Value Checker might have that information: + // either string could have a @StringVal annotation whose value doesn't include "", + // either could have an @ArrayLen annotation whose value doesn't contain zero, or + // either could have an @ArrayLenRange annotation whose from value is any positive + // integer. + ValueAnnotatedTypeFactory vatf = + ((UpperBoundAnnotatedTypeFactory) this.atypeFactory).getValueAnnotatedTypeFactory(); + AnnotatedTypeMirror argType = vatf.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror receiverType = vatf.getReceiverType(tree); + if (definitelyIsNotTheEmptyString(argType, vatf) + || definitelyIsNotTheEmptyString(receiverType, vatf)) { + String receiverName = JavaExpression.getReceiver(tree).toString(); + UBQualifier ltLengthOfReceiver = UBQualifier.createUBQualifier(receiverName, "0"); + AnnotationMirror currentReturnAnno = type.getAnnotationInHierarchy(UNKNOWN); + UBQualifier currentUBQualifier = + UBQualifier.createUBQualifier(currentReturnAnno, (IndexChecker) checker); + UBQualifier result = currentUBQualifier.glb(ltLengthOfReceiver); + type.replaceAnnotation(convertUBQualifierToAnnotation(result)); + } + } + return super.visitMethodInvocation(tree, type); } /** - * Checks if valueType contains a {@link org.checkerframework.common.value.qual.BottomVal} - * annotation. If so, adds an {@link UpperBoundBottom} annotation to type. + * Returns true if the given Value Checker annotations guarantee that the annotated element is + * not the empty string. * - * @param valueType annotated type from the {@link ValueAnnotatedTypeFactory} - * @param type annotated type from this factory that is side effected + * @param atm an annotated type from the Value Checker + * @param vatf the Value Annotated Type Factory + * @return true iff atm contains a {@code StringVal} annotation whose value doesn't contain "", + * an {@code ArrayLen} annotation whose value doesn't contain 0, or an {@code ArrayLenRange} + * annotation whose from value is greater than 0 */ - private void addUpperBoundTypeFromValueType( - AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { - if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { - type.replaceAnnotation(BOTTOM); + private boolean definitelyIsNotTheEmptyString( + AnnotatedTypeMirror atm, ValueAnnotatedTypeFactory vatf) { + AnnotationMirrorSet annos = atm.getAnnotations(); + for (AnnotationMirror anno : annos) { + switch (AnnotationUtils.annotationName(anno)) { + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + List strings = vatf.getStringValues(anno); + if (strings != null && !strings.contains("")) { + return true; + } + break; + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + List lengths = vatf.getArrayLength(anno); + if (lengths != null && !lengths.contains(0)) { + return true; + } + break; + default: + Range range = vatf.getRange(anno); + if (range != null && range.from > 0) { + return true; + } + break; } + } + return false; } @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator( - new UpperBoundTypeAnnotator(this), super.createTypeAnnotator()); + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + // Could also handle long literals, but array indexes are always ints. + if (tree.getKind() == Tree.Kind.INT_LITERAL) { + type.addAnnotation(createLiteral(((Integer) tree.getValue()).intValue())); + } + return super.visitLiteral(tree, type); + } + + /* Handles case 3. */ + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + // Dataflow refines this type if possible + if (tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT) { + addAnnotationForBitwiseComplement( + getSearchIndexAnnotatedTypeFactory().getAnnotatedType(tree.getExpression()), type); + } else { + type.addAnnotation(UNKNOWN); + } + return super.visitUnary(tree, type); } /** - * Performs pre-processing on annotations written by users, replacing illegal annotations by - * legal ones. + * If a type returned by an {@link SearchIndexAnnotatedTypeFactory} has a {@link + * NegativeIndexFor} annotation, then refine the result to be {@link LTEqLengthOf}. This handles + * this case: + * + *
{@code
+     * int i = Arrays.binarySearch(a, x);
+     * if (i >= 0) {
+     *     // do something
+     * } else {
+     *     i = ~i;
+     *     // i is now @LTEqLengthOf("a"), because the bitwise complement of a NegativeIndexFor is an LTL.
+     *     for (int j = 0; j < i; j++) {
+     *          // j is now a valid index for "a"
+     *     }
+     * }
+     * }
+ * + * @param searchIndexType the type of an expression in a bitwise complement. For instance, in + * {@code ~x}, this is the type of {@code x}. + * @param typeDst the type of the entire bitwise complement expression. It is modified by this + * method. */ - private class UpperBoundTypeAnnotator extends TypeAnnotator { - - private UpperBoundTypeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + private void addAnnotationForBitwiseComplement( + AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { + AnnotationMirror nif = searchIndexType.getAnnotation(NegativeIndexFor.class); + if (nif != null) { + List arrays = + AnnotationUtils.getElementValueArray(nif, negativeIndexForValueElement, String.class); + List negativeOnes = Collections.nCopies(arrays.size(), "-1"); + UBQualifier qual = UBQualifier.createUBQualifier(arrays, negativeOnes); + typeDst.addAnnotation(convertUBQualifierToAnnotation(qual)); + } else { + typeDst.addAnnotation(UNKNOWN); + } + } - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - // If there is an LTLengthOf annotation whose argument lengths don't match, replace it - // with bottom. - AnnotationMirror anm = type.getAnnotation(LTLengthOf.class); - if (anm != null) { - List sequences = - AnnotationUtils.getElementValueArray( - anm, ltLengthOfValueElement, String.class); - List offsets = - AnnotationUtils.getElementValueArray( - anm, - ltLengthOfOffsetElement, - String.class, - Collections.emptyList()); - if (sequences != null - && offsets != null - && sequences.size() != offsets.size() - && !offsets.isEmpty()) { - // Cannot use type.replaceAnnotation because it will call isSubtype, which will - // try to process the annotation and throw an error. - type.clearAnnotations(); - type.addAnnotation(BOTTOM); - } - } - return super.scan(type, aVoid); - } + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + // Dataflow refines this type if possible + type.addAnnotation(UNKNOWN); + return super.visitCompoundAssignment(tree, type); } @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new OffsetDependentTypesHelper(this); + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + // A few small rules for addition/subtraction by 0/1, etc. + if (TreeUtils.isStringConcatenation(tree)) { + type.addAnnotation(UNKNOWN); + return super.visitBinary(tree, type); + } + + ExpressionTree left = tree.getLeftOperand(); + ExpressionTree right = tree.getRightOperand(); + switch (tree.getKind()) { + case PLUS: + case MINUS: + // Dataflow refines this type if possible + type.addAnnotation(UNKNOWN); + break; + case MULTIPLY: + addAnnotationForMultiply(left, right, type); + break; + case DIVIDE: + addAnnotationForDivide(left, right, type); + break; + case REMAINDER: + addAnnotationForRemainder(left, right, type); + break; + case AND: + addAnnotationForAnd(left, right, type); + break; + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + addAnnotationForRightShift(left, right, type); + break; + default: + break; + } + return super.visitBinary(tree, type); } /** - * Queries the SameLen Checker to return the type that the SameLen Checker associates with the - * given tree. + * Infers upper-bound annotation for {@code left >> right} and {@code left >>> right} (case 4). */ - public @Nullable AnnotationMirror sameLenAnnotationFromTree(Tree tree) { - AnnotatedTypeMirror sameLenType = getSameLenAnnotatedTypeFactory().getAnnotatedType(tree); - return sameLenType.getAnnotation(SameLen.class); + private void addAnnotationForRightShift( + ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { + LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); + if (lowerBoundATF.isNonNegative(left)) { + AnnotationMirror annotation = getAnnotatedType(left).getAnnotationInHierarchy(UNKNOWN); + // For non-negative numbers, right shift is equivalent to division by a power of + // two. + // The range of the shift amount is limited to 0..30 to avoid overflows and int/long + // differences. + Long shiftAmount = ValueCheckerUtils.getExactValue(right, getValueAnnotatedTypeFactory()); + if (shiftAmount != null && shiftAmount >= 0 && shiftAmount < Integer.SIZE - 1) { + int divisor = 1 << shiftAmount; + // Support average by shift just like for division + UBQualifier plusDivQualifier = plusTreeDivideByVal(divisor, left); + if (!plusDivQualifier.isUnknown()) { + UBQualifier qualifier = + UBQualifier.createUBQualifier(annotation, (IndexChecker) checker); + qualifier = qualifier.glb(plusDivQualifier); + annotation = convertUBQualifierToAnnotation(qualifier); + } + } + type.addAnnotation(annotation); + } } - // Wrapper methods for accessing the IndexMethodIdentifier. + /** + * If either argument is non-negative, the and expression retains that argument's upperbound + * type. If both are non-negative, the result of the expression is the GLB of the two (case 5). + */ + private void addAnnotationForAnd( + ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { + LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); + AnnotatedTypeMirror leftType = getAnnotatedType(left); + AnnotationMirror leftResultAnno = UNKNOWN; + if (lowerBoundATF.isNonNegative(left)) { + leftResultAnno = leftType.getAnnotationInHierarchy(UNKNOWN); + } + + AnnotatedTypeMirror rightType = getAnnotatedType(right); + AnnotationMirror rightResultAnno = UNKNOWN; + if (lowerBoundATF.isNonNegative(right)) { + rightResultAnno = rightType.getAnnotationInHierarchy(UNKNOWN); + } + + type.addAnnotation( + qualHierarchy.greatestLowerBoundShallow( + leftResultAnno, + leftType.getUnderlyingType(), + rightResultAnno, + rightType.getUnderlyingType())); + } - public boolean isMathMin(Tree methodTree) { - return imf.isMathMin(methodTree); + /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ + private @Nullable ExpressionTree getLengthSequenceTree(ExpressionTree lengthTree) { + return IndexUtil.getLengthSequenceTree(lengthTree, imf, processingEnv); } /** - * Returns true if the tree is for {@code Random.nextInt(int)}. + * Infers upper-bound annotation for {@code numerator % divisor}. There are two cases where an + * upperbound type is inferred: * - * @param methodTree a tree to check - * @return true iff the tree is for {@code Random.nextInt(int)} + *
    + *
  • 6. if numerator ≥ 0, then numerator % divisor ≤ numerator + *
  • 7. if divisor ≥ 0, then numerator % divisor < divisor + *
*/ - public boolean isRandomNextInt(Tree methodTree) { - return imf.isRandomNextInt(methodTree, processingEnv); + private void addAnnotationForRemainder( + ExpressionTree numeratorTree, ExpressionTree divisorTree, AnnotatedTypeMirror resultType) { + LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); + UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; + // if numerator >= 0, then numerator%divisor <= numerator + if (lowerBoundATF.isNonNegative(numeratorTree)) { + result = + UBQualifier.createUBQualifier( + getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); + } + // if divisor >= 0, then numerator%divisor < divisor + if (lowerBoundATF.isNonNegative(divisorTree)) { + UBQualifier divisor = + UBQualifier.createUBQualifier( + getAnnotatedType(divisorTree), UNKNOWN, (IndexChecker) checker); + result = result.glb(divisor.plusOffset(1)); + } + resultType.addAnnotation(convertUBQualifierToAnnotation(result)); } /** - * Creates a new @LTLengthOf annotation. + * Implements two cases (8 and 9): * - * @param names the arguments to @LTLengthOf - * @return a new @LTLengthOf annotation with the given arguments + *
    + *
  • 8. If the numerator is an array length access of an array with non-zero length, and the + * divisor is greater than one, glb the result with an LTL of the array. + *
  • 9. if numeratorTree is a + b and divisor greater than 1, and a and b are less than the + * length of some sequence, then (a + b) / divisor is less than the length of that + * sequence. + *
*/ - AnnotationMirror createLTLengthOfAnnotation(String... names) { - if (names == null || names.length == 0) { - throw new TypeSystemError( - "createLTLengthOfAnnotation: bad argument %s", Arrays.toString(names)); - } - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTLengthOf.class); - builder.setValue("value", names); - return builder.build(); + private void addAnnotationForDivide( + ExpressionTree numeratorTree, ExpressionTree divisorTree, AnnotatedTypeMirror resultType) { + + Long divisor = ValueCheckerUtils.getExactValue(divisorTree, getValueAnnotatedTypeFactory()); + if (divisor == null) { + resultType.addAnnotation(UNKNOWN); + return; + } + + UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; + UBQualifier numerator = + UBQualifier.createUBQualifier( + getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); + if (numerator.isLessThanLengthQualifier()) { + result = ((LessThanLengthOf) numerator).divide(divisor.intValue()); + } + result = result.glb(plusTreeDivideByVal(divisor.intValue(), numeratorTree)); + + ExpressionTree numeratorSequenceTree = getLengthSequenceTree(numeratorTree); + // If the numerator is an array length access of an array with non-zero length, and the + // divisor is greater than one, glb the result with an LTL of the array. + if (numeratorSequenceTree != null && divisor > 1) { + String arrayName = numeratorSequenceTree.toString(); + int minlen = + getValueAnnotatedTypeFactory() + .getMinLenFromString(arrayName, numeratorTree, getPath(numeratorTree)); + if (minlen > 0) { + result = result.glb(UBQualifier.createUBQualifier(arrayName, "0")); + } + } + + resultType.addAnnotation(convertUBQualifierToAnnotation(result)); } /** - * Creates a new @LTEqLengthOf annotation. + * If {@code numeratorTree} is "a + b" and {@code divisor} is greater than 1, and a and b are + * less than the length of some sequence, then "(a + b) / divisor" is less than the length of + * that sequence. * - * @param names the arguments to @LTEqLengthOf - * @return a new @LTEqLengthOf annotation with the given arguments + * @param divisor the divisor + * @param numeratorTree an addition tree that is divided by {@code divisor} + * @return a qualifier for the division */ - AnnotationMirror createLTEqLengthOfAnnotation(String... names) { - if (names == null || names.length == 0) { - throw new TypeSystemError( - "createLTEqLengthOfAnnotation: bad argument %s", Arrays.toString(names)); - } - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTEqLengthOf.class); - builder.setValue("value", names); - return builder.build(); + private UBQualifier plusTreeDivideByVal(int divisor, ExpressionTree numeratorTree) { + numeratorTree = TreeUtils.withoutParens(numeratorTree); + if (divisor < 2 || numeratorTree.getKind() != Tree.Kind.PLUS) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + BinaryTree plusTree = (BinaryTree) numeratorTree; + UBQualifier left = + UBQualifier.createUBQualifier( + getAnnotatedType(plusTree.getLeftOperand()), UNKNOWN, (IndexChecker) checker); + UBQualifier right = + UBQualifier.createUBQualifier( + getAnnotatedType(plusTree.getRightOperand()), UNKNOWN, (IndexChecker) checker); + if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { + LessThanLengthOf leftLTL = (LessThanLengthOf) left; + LessThanLengthOf rightLTL = (LessThanLengthOf) right; + List sequences = new ArrayList<>(); + for (String sequence : leftLTL.getSequences()) { + if (rightLTL.isLessThanLengthOf(sequence) && leftLTL.isLessThanLengthOf(sequence)) { + sequences.add(sequence); + } + } + if (!sequences.isEmpty()) { + return UBQualifier.createUBQualifier(sequences, Collections.emptyList()); + } + } + + return UpperBoundUnknownQualifier.UNKNOWN; } /** - * Returns true iff the given node has the passed Lower Bound qualifier according to the LBC. - * The last argument should be Positive.class, NonNegative.class, or GTENegativeOne.class. - * - * @param node the given node - * @param classOfType one of Positive.class, NonNegative.class, or GTENegativeOne.class - * @return true iff the given node has the passed Lower Bound qualifier according to the LBC + * Special handling for Math.random: Math.random() * array.length is LTL array. Same for + * Random.nextDouble. Case 10. */ - public boolean hasLowerBoundTypeByClass(Node node, Class classOfType) { - return areSameByClass( - getLowerBoundAnnotatedTypeFactory() - .getAnnotatedType(node.getTree()) - .getAnnotationInHierarchy(getLowerBoundAnnotatedTypeFactory().UNKNOWN), - classOfType); - } + private boolean checkForMathRandomSpecialCase( + ExpressionTree randTree, ExpressionTree seqLenTree, AnnotatedTypeMirror type) { - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new UpperBoundQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** The qualifier hierarchy for the upperbound type system. */ - protected final class UpperBoundQualifierHierarchy extends ElementQualifierHierarchy { - /** - * Creates an UpperBoundQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - UpperBoundQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, UpperBoundAnnotatedTypeFactory.this); - } + ExpressionTree seqTree = getLengthSequenceTree(seqLenTree); - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); - UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); - UBQualifier glb = a1Obj.glb(a2Obj); - return convertUBQualifierToAnnotation(glb); - } + if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION && seqTree != null) { - /** - * Determines the least upper bound of a1 and a2. If a1 and a2 are both the same type of - * Value annotation, then the LUB is the result of taking the intersection of values from - * both a1 and a2. - * - * @return the least upper bound of a1 and a2 - */ - @Override - public AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); - UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); - UBQualifier lub = a1Obj.lub(a2Obj); - return convertUBQualifierToAnnotation(lub); - } + MethodInvocationTree mitree = (MethodInvocationTree) randTree; - @Override - public AnnotationMirror widenedUpperBound( - AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { - UBQualifier a1Obj = UBQualifier.createUBQualifier(newQualifier, (IndexChecker) checker); - UBQualifier a2Obj = - UBQualifier.createUBQualifier(previousQualifier, (IndexChecker) checker); - UBQualifier lub = a1Obj.widenUpperBound(a2Obj); - return convertUBQualifierToAnnotation(lub); + if (imf.isMathRandom(mitree, processingEnv)) { + // Okay, so this is Math.random() * array.length, which must be NonNegative + type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); + return true; } - @Override - public int numberOfIterationsBeforeWidening() { - return 10; + if (imf.isRandomNextDouble(mitree, processingEnv)) { + // Okay, so this is Random.nextDouble() * array.length, which must be + // NonNegative + type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); + return true; } + } - /** - * {@inheritDoc} - * - *

Computes subtyping as per the subtyping in the qualifier hierarchy structure unless - * both annotations have the same class. In this case, rhs is a subtype of lhs iff rhs - * contains every element of lhs. - */ - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - UBQualifier subtypeQual = - UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker); - UBQualifier supertypeQual = - UBQualifier.createUBQualifier(superAnno, (IndexChecker) checker); - return subtypeQual.isSubtype(supertypeQual); - } + return false; } - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - new UpperBoundTreeAnnotator(this), super.createTreeAnnotator()); + private void addAnnotationForMultiply( + ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { + // Special handling for multiplying an array length by a random variable. + if (checkForMathRandomSpecialCase(rightExpr, leftExpr, type) + || checkForMathRandomSpecialCase(leftExpr, rightExpr, type)) { + return; + } + type.addAnnotation(UNKNOWN); } - - protected class UpperBoundTreeAnnotator extends TreeAnnotator { - - public UpperBoundTreeAnnotator(UpperBoundAnnotatedTypeFactory factory) { - super(factory); - } - - /** - * This exists for Math.min and Random.nextInt, which must be special-cased. - * - *

    - *
  • Math.min has unusual semantics that combines annotations for the UBC. - *
  • The return type of Random.nextInt depends on the argument, but is not equal to it, - * so a polymorphic qualifier is insufficient. - *
- * - * Other methods should not be special-cased here unless there is a compelling reason to do - * so. - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (isMathMin(tree)) { - AnnotatedTypeMirror leftType = getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror rightType = getAnnotatedType(tree.getArguments().get(1)); - - type.replaceAnnotation( - qualHierarchy.greatestLowerBoundShallow( - leftType.getAnnotationInHierarchy(UNKNOWN), - leftType.getUnderlyingType(), - rightType.getAnnotationInHierarchy(UNKNOWN), - rightType.getUnderlyingType())); - } - if (isRandomNextInt(tree)) { - AnnotatedTypeMirror argType = getAnnotatedType(tree.getArguments().get(0)); - AnnotationMirror anno = argType.getAnnotationInHierarchy(UNKNOWN); - UBQualifier qualifier = UBQualifier.createUBQualifier(anno, (IndexChecker) checker); - qualifier = qualifier.plusOffset(1); - type.replaceAnnotation(convertUBQualifierToAnnotation(qualifier)); - } - if (imf.isIndexOfString(tree)) { - // String#indexOf(String) and its variants that also take a String technically - // return (and are annotated as) @LTEqLengthOf the receiver. However, the result is - // always @LTLengthOf the receiver unless both the receiver and the target string - // are the empty string: "".indexOf("") returns 0, which isn't an index into "". So, - // this special case modifies the return type of these methods if either the - // parameter or the receiver is known (by the Value Checker) to not be the empty - // string. There are three ways the Value Checker might have that information: - // either string could have a @StringVal annotation whose value doesn't include "", - // either could have an @ArrayLen annotation whose value doesn't contain zero, or - // either could have an @ArrayLenRange annotation whose from value is any positive - // integer. - ValueAnnotatedTypeFactory vatf = - ((UpperBoundAnnotatedTypeFactory) this.atypeFactory) - .getValueAnnotatedTypeFactory(); - AnnotatedTypeMirror argType = vatf.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror receiverType = vatf.getReceiverType(tree); - if (definitelyIsNotTheEmptyString(argType, vatf) - || definitelyIsNotTheEmptyString(receiverType, vatf)) { - String receiverName = JavaExpression.getReceiver(tree).toString(); - UBQualifier ltLengthOfReceiver = - UBQualifier.createUBQualifier(receiverName, "0"); - AnnotationMirror currentReturnAnno = type.getAnnotationInHierarchy(UNKNOWN); - UBQualifier currentUBQualifier = - UBQualifier.createUBQualifier( - currentReturnAnno, (IndexChecker) checker); - UBQualifier result = currentUBQualifier.glb(ltLengthOfReceiver); - type.replaceAnnotation(convertUBQualifierToAnnotation(result)); - } - } - return super.visitMethodInvocation(tree, type); - } - - /** - * Returns true if the given Value Checker annotations guarantee that the annotated element - * is not the empty string. - * - * @param atm an annotated type from the Value Checker - * @param vatf the Value Annotated Type Factory - * @return true iff atm contains a {@code StringVal} annotation whose value doesn't contain - * "", an {@code ArrayLen} annotation whose value doesn't contain 0, or an {@code - * ArrayLenRange} annotation whose from value is greater than 0 - */ - private boolean definitelyIsNotTheEmptyString( - AnnotatedTypeMirror atm, ValueAnnotatedTypeFactory vatf) { - AnnotationMirrorSet annos = atm.getAnnotations(); - for (AnnotationMirror anno : annos) { - switch (AnnotationUtils.annotationName(anno)) { - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - List strings = vatf.getStringValues(anno); - if (strings != null && !strings.contains("")) { - return true; - } - break; - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - List lengths = vatf.getArrayLength(anno); - if (lengths != null && !lengths.contains(0)) { - return true; - } - break; - default: - Range range = vatf.getRange(anno); - if (range != null && range.from > 0) { - return true; - } - break; - } - } - return false; - } - - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - // Could also handle long literals, but array indexes are always ints. - if (tree.getKind() == Tree.Kind.INT_LITERAL) { - type.addAnnotation(createLiteral(((Integer) tree.getValue()).intValue())); - } - return super.visitLiteral(tree, type); - } - - /* Handles case 3. */ - @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - // Dataflow refines this type if possible - if (tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT) { - addAnnotationForBitwiseComplement( - getSearchIndexAnnotatedTypeFactory().getAnnotatedType(tree.getExpression()), - type); - } else { - type.addAnnotation(UNKNOWN); - } - return super.visitUnary(tree, type); - } - - /** - * If a type returned by an {@link SearchIndexAnnotatedTypeFactory} has a {@link - * NegativeIndexFor} annotation, then refine the result to be {@link LTEqLengthOf}. This - * handles this case: - * - *
{@code
-         * int i = Arrays.binarySearch(a, x);
-         * if (i >= 0) {
-         *     // do something
-         * } else {
-         *     i = ~i;
-         *     // i is now @LTEqLengthOf("a"), because the bitwise complement of a NegativeIndexFor is an LTL.
-         *     for (int j = 0; j < i; j++) {
-         *          // j is now a valid index for "a"
-         *     }
-         * }
-         * }
- * - * @param searchIndexType the type of an expression in a bitwise complement. For instance, - * in {@code ~x}, this is the type of {@code x}. - * @param typeDst the type of the entire bitwise complement expression. It is modified by - * this method. - */ - private void addAnnotationForBitwiseComplement( - AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { - AnnotationMirror nif = searchIndexType.getAnnotation(NegativeIndexFor.class); - if (nif != null) { - List arrays = - AnnotationUtils.getElementValueArray( - nif, negativeIndexForValueElement, String.class); - List negativeOnes = Collections.nCopies(arrays.size(), "-1"); - UBQualifier qual = UBQualifier.createUBQualifier(arrays, negativeOnes); - typeDst.addAnnotation(convertUBQualifierToAnnotation(qual)); - } else { - typeDst.addAnnotation(UNKNOWN); - } - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - // Dataflow refines this type if possible - type.addAnnotation(UNKNOWN); - return super.visitCompoundAssignment(tree, type); - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - // A few small rules for addition/subtraction by 0/1, etc. - if (TreeUtils.isStringConcatenation(tree)) { - type.addAnnotation(UNKNOWN); - return super.visitBinary(tree, type); - } - - ExpressionTree left = tree.getLeftOperand(); - ExpressionTree right = tree.getRightOperand(); - switch (tree.getKind()) { - case PLUS: - case MINUS: - // Dataflow refines this type if possible - type.addAnnotation(UNKNOWN); - break; - case MULTIPLY: - addAnnotationForMultiply(left, right, type); - break; - case DIVIDE: - addAnnotationForDivide(left, right, type); - break; - case REMAINDER: - addAnnotationForRemainder(left, right, type); - break; - case AND: - addAnnotationForAnd(left, right, type); - break; - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - addAnnotationForRightShift(left, right, type); - break; - default: - break; - } - return super.visitBinary(tree, type); - } - - /** - * Infers upper-bound annotation for {@code left >> right} and {@code left >>> right} (case - * 4). - */ - private void addAnnotationForRightShift( - ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { - LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); - if (lowerBoundATF.isNonNegative(left)) { - AnnotationMirror annotation = - getAnnotatedType(left).getAnnotationInHierarchy(UNKNOWN); - // For non-negative numbers, right shift is equivalent to division by a power of - // two. - // The range of the shift amount is limited to 0..30 to avoid overflows and int/long - // differences. - Long shiftAmount = - ValueCheckerUtils.getExactValue(right, getValueAnnotatedTypeFactory()); - if (shiftAmount != null && shiftAmount >= 0 && shiftAmount < Integer.SIZE - 1) { - int divisor = 1 << shiftAmount; - // Support average by shift just like for division - UBQualifier plusDivQualifier = plusTreeDivideByVal(divisor, left); - if (!plusDivQualifier.isUnknown()) { - UBQualifier qualifier = - UBQualifier.createUBQualifier(annotation, (IndexChecker) checker); - qualifier = qualifier.glb(plusDivQualifier); - annotation = convertUBQualifierToAnnotation(qualifier); - } - } - type.addAnnotation(annotation); - } - } - - /** - * If either argument is non-negative, the and expression retains that argument's upperbound - * type. If both are non-negative, the result of the expression is the GLB of the two (case - * 5). - */ - private void addAnnotationForAnd( - ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { - LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); - AnnotatedTypeMirror leftType = getAnnotatedType(left); - AnnotationMirror leftResultAnno = UNKNOWN; - if (lowerBoundATF.isNonNegative(left)) { - leftResultAnno = leftType.getAnnotationInHierarchy(UNKNOWN); - } - - AnnotatedTypeMirror rightType = getAnnotatedType(right); - AnnotationMirror rightResultAnno = UNKNOWN; - if (lowerBoundATF.isNonNegative(right)) { - rightResultAnno = rightType.getAnnotationInHierarchy(UNKNOWN); - } - - type.addAnnotation( - qualHierarchy.greatestLowerBoundShallow( - leftResultAnno, - leftType.getUnderlyingType(), - rightResultAnno, - rightType.getUnderlyingType())); - } - - /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ - private @Nullable ExpressionTree getLengthSequenceTree(ExpressionTree lengthTree) { - return IndexUtil.getLengthSequenceTree(lengthTree, imf, processingEnv); - } - - /** - * Infers upper-bound annotation for {@code numerator % divisor}. There are two cases where - * an upperbound type is inferred: - * - *
    - *
  • 6. if numerator ≥ 0, then numerator % divisor ≤ numerator - *
  • 7. if divisor ≥ 0, then numerator % divisor < divisor - *
- */ - private void addAnnotationForRemainder( - ExpressionTree numeratorTree, - ExpressionTree divisorTree, - AnnotatedTypeMirror resultType) { - LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); - UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; - // if numerator >= 0, then numerator%divisor <= numerator - if (lowerBoundATF.isNonNegative(numeratorTree)) { - result = - UBQualifier.createUBQualifier( - getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); - } - // if divisor >= 0, then numerator%divisor < divisor - if (lowerBoundATF.isNonNegative(divisorTree)) { - UBQualifier divisor = - UBQualifier.createUBQualifier( - getAnnotatedType(divisorTree), UNKNOWN, (IndexChecker) checker); - result = result.glb(divisor.plusOffset(1)); - } - resultType.addAnnotation(convertUBQualifierToAnnotation(result)); - } - - /** - * Implements two cases (8 and 9): - * - *
    - *
  • 8. If the numerator is an array length access of an array with non-zero length, and - * the divisor is greater than one, glb the result with an LTL of the array. - *
  • 9. if numeratorTree is a + b and divisor greater than 1, and a and b are less than - * the length of some sequence, then (a + b) / divisor is less than the length of that - * sequence. - *
- */ - private void addAnnotationForDivide( - ExpressionTree numeratorTree, - ExpressionTree divisorTree, - AnnotatedTypeMirror resultType) { - - Long divisor = - ValueCheckerUtils.getExactValue(divisorTree, getValueAnnotatedTypeFactory()); - if (divisor == null) { - resultType.addAnnotation(UNKNOWN); - return; - } - - UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; - UBQualifier numerator = - UBQualifier.createUBQualifier( - getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); - if (numerator.isLessThanLengthQualifier()) { - result = ((LessThanLengthOf) numerator).divide(divisor.intValue()); - } - result = result.glb(plusTreeDivideByVal(divisor.intValue(), numeratorTree)); - - ExpressionTree numeratorSequenceTree = getLengthSequenceTree(numeratorTree); - // If the numerator is an array length access of an array with non-zero length, and the - // divisor is greater than one, glb the result with an LTL of the array. - if (numeratorSequenceTree != null && divisor > 1) { - String arrayName = numeratorSequenceTree.toString(); - int minlen = - getValueAnnotatedTypeFactory() - .getMinLenFromString( - arrayName, numeratorTree, getPath(numeratorTree)); - if (minlen > 0) { - result = result.glb(UBQualifier.createUBQualifier(arrayName, "0")); - } - } - - resultType.addAnnotation(convertUBQualifierToAnnotation(result)); - } - - /** - * If {@code numeratorTree} is "a + b" and {@code divisor} is greater than 1, and a and b - * are less than the length of some sequence, then "(a + b) / divisor" is less than the - * length of that sequence. - * - * @param divisor the divisor - * @param numeratorTree an addition tree that is divided by {@code divisor} - * @return a qualifier for the division - */ - private UBQualifier plusTreeDivideByVal(int divisor, ExpressionTree numeratorTree) { - numeratorTree = TreeUtils.withoutParens(numeratorTree); - if (divisor < 2 || numeratorTree.getKind() != Tree.Kind.PLUS) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - BinaryTree plusTree = (BinaryTree) numeratorTree; - UBQualifier left = - UBQualifier.createUBQualifier( - getAnnotatedType(plusTree.getLeftOperand()), - UNKNOWN, - (IndexChecker) checker); - UBQualifier right = - UBQualifier.createUBQualifier( - getAnnotatedType(plusTree.getRightOperand()), - UNKNOWN, - (IndexChecker) checker); - if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { - LessThanLengthOf leftLTL = (LessThanLengthOf) left; - LessThanLengthOf rightLTL = (LessThanLengthOf) right; - List sequences = new ArrayList<>(); - for (String sequence : leftLTL.getSequences()) { - if (rightLTL.isLessThanLengthOf(sequence) - && leftLTL.isLessThanLengthOf(sequence)) { - sequences.add(sequence); - } - } - if (!sequences.isEmpty()) { - return UBQualifier.createUBQualifier(sequences, Collections.emptyList()); - } - } - - return UpperBoundUnknownQualifier.UNKNOWN; - } - - /** - * Special handling for Math.random: Math.random() * array.length is LTL array. Same for - * Random.nextDouble. Case 10. - */ - private boolean checkForMathRandomSpecialCase( - ExpressionTree randTree, ExpressionTree seqLenTree, AnnotatedTypeMirror type) { - - ExpressionTree seqTree = getLengthSequenceTree(seqLenTree); - - if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION && seqTree != null) { - - MethodInvocationTree mitree = (MethodInvocationTree) randTree; - - if (imf.isMathRandom(mitree, processingEnv)) { - // Okay, so this is Math.random() * array.length, which must be NonNegative - type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); - return true; - } - - if (imf.isRandomNextDouble(mitree, processingEnv)) { - // Okay, so this is Random.nextDouble() * array.length, which must be - // NonNegative - type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); - return true; - } - } - - return false; - } - - private void addAnnotationForMultiply( - ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { - // Special handling for multiplying an array length by a random variable. - if (checkForMathRandomSpecialCase(rightExpr, leftExpr, type) - || checkForMathRandomSpecialCase(leftExpr, rightExpr, type)) { - return; - } - type.addAnnotation(UNKNOWN); - } + } + + /** + * Creates a @{@link UpperBoundLiteral} annotation. + * + * @param i the integer + * @return a @{@link UpperBoundLiteral} annotation + */ + public AnnotationMirror createLiteral(int i) { + switch (i) { + case -1: + return NEGATIVEONE; + case 0: + return ZERO; + case 1: + return ONE; + default: + return new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", i) + .build(); } - - /** - * Creates a @{@link UpperBoundLiteral} annotation. - * - * @param i the integer - * @return a @{@link UpperBoundLiteral} annotation - */ - public AnnotationMirror createLiteral(int i) { - switch (i) { - case -1: - return NEGATIVEONE; - case 0: - return ZERO; - case 1: - return ONE; - default: - return new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", i) - .build(); - } + } + + /** + * Convert the internal representation to an annotation. + * + * @param qualifier a UBQualifier + * @return an annotation corresponding to the given qualifier + */ + public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { + if (qualifier.isUnknown()) { + return UNKNOWN; + } else if (qualifier.isBottom()) { + return BOTTOM; + } else if (qualifier.isPoly()) { + return POLY; + } else if (qualifier.isLiteral()) { + return createLiteral(((UpperBoundLiteralQualifier) qualifier).getValue()); } - /** - * Convert the internal representation to an annotation. - * - * @param qualifier a UBQualifier - * @return an annotation corresponding to the given qualifier - */ - public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { - if (qualifier.isUnknown()) { - return UNKNOWN; - } else if (qualifier.isBottom()) { - return BOTTOM; - } else if (qualifier.isPoly()) { - return POLY; - } else if (qualifier.isLiteral()) { - return createLiteral(((UpperBoundLiteralQualifier) qualifier).getValue()); - } + LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; + return ltlQualifier.convertToAnnotation(processingEnv); + } - LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; - return ltlQualifier.convertToAnnotation(processingEnv); + @Nullable UBQualifier fromLessThan(ExpressionTree tree, TreePath treePath) { + List lessThanExpressions = + getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); + if (lessThanExpressions == null) { + return null; } - - @Nullable UBQualifier fromLessThan(ExpressionTree tree, TreePath treePath) { - List lessThanExpressions = - getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); - if (lessThanExpressions == null) { - return null; - } - UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); - if (ubQualifier != null) { - return ubQualifier.plusOffset(1); - } - return null; + UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); + if (ubQualifier != null) { + return ubQualifier.plusOffset(1); } - - @Nullable UBQualifier fromLessThanOrEqual(ExpressionTree tree, TreePath treePath) { - List lessThanExpressions = - getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); - if (lessThanExpressions == null) { - return null; - } - UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); - return ubQualifier; + return null; + } + + @Nullable UBQualifier fromLessThanOrEqual(ExpressionTree tree, TreePath treePath) { + List lessThanExpressions = + getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); + if (lessThanExpressions == null) { + return null; } - - private @Nullable UBQualifier fromLessThanOrEqual( - Tree tree, TreePath treePath, List lessThanExpressions) { - UBQualifier ubQualifier = null; - for (String expression : lessThanExpressions) { - IPair exprAndOffset; - try { - exprAndOffset = - getExpressionAndOffsetFromJavaExpressionString(expression, treePath); - } catch (JavaExpressionParseException e) { - exprAndOffset = null; - } - if (exprAndOffset == null) { - continue; - } - JavaExpression je = exprAndOffset.first; - String offset = exprAndOffset.second; - - if (!CFAbstractStore.canInsertJavaExpression(je)) { - continue; - } - CFStore store = getStoreBefore(tree); - CFValue value = store.getValue(je); - if (value != null && value.getAnnotations().size() == 1) { - UBQualifier newUBQ = - UBQualifier.createUBQualifier( - qualHierarchy.findAnnotationInHierarchy( - value.getAnnotations(), UNKNOWN), - AnnotatedTypeFactory.negateConstant(offset), - (IndexChecker) checker); - if (ubQualifier == null) { - ubQualifier = newUBQ; - } else { - ubQualifier = ubQualifier.glb(newUBQ); - } - } - } - return ubQualifier; + UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); + return ubQualifier; + } + + private @Nullable UBQualifier fromLessThanOrEqual( + Tree tree, TreePath treePath, List lessThanExpressions) { + UBQualifier ubQualifier = null; + for (String expression : lessThanExpressions) { + IPair exprAndOffset; + try { + exprAndOffset = getExpressionAndOffsetFromJavaExpressionString(expression, treePath); + } catch (JavaExpressionParseException e) { + exprAndOffset = null; + } + if (exprAndOffset == null) { + continue; + } + JavaExpression je = exprAndOffset.first; + String offset = exprAndOffset.second; + + if (!CFAbstractStore.canInsertJavaExpression(je)) { + continue; + } + CFStore store = getStoreBefore(tree); + CFValue value = store.getValue(je); + if (value != null && value.getAnnotations().size() == 1) { + UBQualifier newUBQ = + UBQualifier.createUBQualifier( + qualHierarchy.findAnnotationInHierarchy(value.getAnnotations(), UNKNOWN), + AnnotatedTypeFactory.negateConstant(offset), + (IndexChecker) checker); + if (ubQualifier == null) { + ubQualifier = newUBQ; + } else { + ubQualifier = ubQualifier.glb(newUBQ); + } + } } + return ubQualifier; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java index 2e4fbf840d3..515c9b6fb4b 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java @@ -1,5 +1,8 @@ package org.checkerframework.checker.index.upperbound; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.index.inequality.LessThanChecker; import org.checkerframework.checker.index.lowerbound.LowerBoundChecker; import org.checkerframework.checker.index.qual.LTEqLengthOf; @@ -18,103 +21,96 @@ import org.checkerframework.framework.source.SuppressWarningsPrefix; import org.checkerframework.javacutil.TreeUtils; -import java.util.HashSet; -import java.util.Set; - -import javax.lang.model.element.ExecutableElement; - /** * A type-checker for preventing arrays from being accessed with values that are too high. * * @checker_framework.manual #index-checker Index Checker */ @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) @SuppressWarningsPrefix({"index", "upperbound"}) public class UpperBoundChecker extends BaseTypeChecker { - /** The SubstringIndexFor.value argument/element. */ - public @MonotonicNonNull ExecutableElement substringIndexForValueElement; + /** The SubstringIndexFor.value argument/element. */ + public @MonotonicNonNull ExecutableElement substringIndexForValueElement; - /** The SubstringIndexFor.offset argument/element. */ - public @MonotonicNonNull ExecutableElement substringIndexForOffsetElement; + /** The SubstringIndexFor.offset argument/element. */ + public @MonotonicNonNull ExecutableElement substringIndexForOffsetElement; - /** The LTLengthOf.value argument/element. */ - public @MonotonicNonNull ExecutableElement ltLengthOfValueElement; + /** The LTLengthOf.value argument/element. */ + public @MonotonicNonNull ExecutableElement ltLengthOfValueElement; - /** The LTLengthOf.offset argument/element. */ - public @MonotonicNonNull ExecutableElement ltLengthOfOffsetElement; + /** The LTLengthOf.offset argument/element. */ + public @MonotonicNonNull ExecutableElement ltLengthOfOffsetElement; - /** The LTEqLengthOf.value argument/element. */ - public @MonotonicNonNull ExecutableElement ltEqLengthOfValueElement; + /** The LTEqLengthOf.value argument/element. */ + public @MonotonicNonNull ExecutableElement ltEqLengthOfValueElement; - /** The LTOMLengthOf.value argument/element. */ - public @MonotonicNonNull ExecutableElement ltOMLengthOfValueElement; + /** The LTOMLengthOf.value argument/element. */ + public @MonotonicNonNull ExecutableElement ltOMLengthOfValueElement; - /** The UpperBoundLiteral.value element/field. */ - public @MonotonicNonNull ExecutableElement upperBoundLiteralValueElement; + /** The UpperBoundLiteral.value element/field. */ + public @MonotonicNonNull ExecutableElement upperBoundLiteralValueElement; - /** - * These collection classes have some subtypes whose length can change and some subtypes whose - * length cannot change. Warnings are skipped at uses of them. - */ - private final HashSet collectionBaseTypeNames; + /** + * These collection classes have some subtypes whose length can change and some subtypes whose + * length cannot change. Warnings are skipped at uses of them. + */ + private final HashSet collectionBaseTypeNames; - /** Create a new UpperBoundChecker. */ - public UpperBoundChecker() { - // These classes are bases for both mutable and immutable sequence collections, which - // contain methods that change the length. - // Upper bound checker warnings are skipped at uses of them. - Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; - collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); - for (Class collectionBaseClass : collectionBaseClasses) { - collectionBaseTypeNames.add(collectionBaseClass.getName()); - } + /** Create a new UpperBoundChecker. */ + public UpperBoundChecker() { + // These classes are bases for both mutable and immutable sequence collections, which + // contain methods that change the length. + // Upper bound checker warnings are skipped at uses of them. + Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; + collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); + for (Class collectionBaseClass : collectionBaseClasses) { + collectionBaseTypeNames.add(collectionBaseClass.getName()); } + } - @Override - public void initChecker() { - super.initChecker(); - substringIndexForValueElement = - TreeUtils.getMethod(SubstringIndexFor.class, "value", 0, processingEnv); - substringIndexForOffsetElement = - TreeUtils.getMethod(SubstringIndexFor.class, "offset", 0, processingEnv); - ltLengthOfValueElement = TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); - ltLengthOfOffsetElement = TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); - ltEqLengthOfValueElement = - TreeUtils.getMethod(LTEqLengthOf.class, "value", 0, processingEnv); - ltOMLengthOfValueElement = - TreeUtils.getMethod(LTOMLengthOf.class, "value", 0, processingEnv); - upperBoundLiteralValueElement = - TreeUtils.getMethod(UpperBoundLiteral.class, "value", 0, processingEnv); - } + @Override + public void initChecker() { + super.initChecker(); + substringIndexForValueElement = + TreeUtils.getMethod(SubstringIndexFor.class, "value", 0, processingEnv); + substringIndexForOffsetElement = + TreeUtils.getMethod(SubstringIndexFor.class, "offset", 0, processingEnv); + ltLengthOfValueElement = TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); + ltLengthOfOffsetElement = TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); + ltEqLengthOfValueElement = TreeUtils.getMethod(LTEqLengthOf.class, "value", 0, processingEnv); + ltOMLengthOfValueElement = TreeUtils.getMethod(LTOMLengthOf.class, "value", 0, processingEnv); + upperBoundLiteralValueElement = + TreeUtils.getMethod(UpperBoundLiteral.class, "value", 0, processingEnv); + } - @Override - public boolean shouldSkipUses(@FullyQualifiedName String typeName) { - if (collectionBaseTypeNames.contains(typeName)) { - return true; - } - return super.shouldSkipUses(typeName); + @Override + public boolean shouldSkipUses(@FullyQualifiedName String typeName) { + if (collectionBaseTypeNames.contains(typeName)) { + return true; } + return super.shouldSkipUses(typeName); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(SubstringIndexChecker.class); - checkers.add(SearchIndexChecker.class); - checkers.add(SameLenChecker.class); - checkers.add(LowerBoundChecker.class); - checkers.add(ValueChecker.class); - checkers.add(LessThanChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(SubstringIndexChecker.class); + checkers.add(SearchIndexChecker.class); + checkers.add(SameLenChecker.class); + checkers.add(LowerBoundChecker.class); + checkers.add(ValueChecker.class); + checkers.add(LessThanChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java index 37fe1b85bab..2919c1d43a5 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java @@ -2,7 +2,12 @@ import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.checker.index.IndexRefinementInfo; import org.checkerframework.checker.index.Subsequence; @@ -42,14 +47,6 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * Contains the transfer functions for the upper bound type system, a part of the Index Checker. * This class implements the following refinement rules: @@ -110,789 +107,759 @@ */ public class UpperBoundTransfer extends IndexAbstractTransfer { - /** The type factory associated with this transfer function. */ - private final UpperBoundAnnotatedTypeFactory atypeFactory; - - /** The int TypeMirror. */ - private final TypeMirror intTM; - - /** - * Creates a new UpperBoundTransfer. - * - * @param analysis the analysis for this transfer function - */ - public UpperBoundTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (UpperBoundAnnotatedTypeFactory) analysis.getTypeFactory(); - intTM = atypeFactory.types.getPrimitiveType(TypeKind.INT); - } - - /** - * Case 1: Refine the type of expressions used as an array dimension to be less than length of - * the array to which the new array is assigned. For example, in "int[] array = new int[expr];", - * the type of expr is @LTEqLength("array"). - */ - @Override - public TransferResult visitAssignment( - AssignmentNode node, TransferInput in) { - TransferResult result = super.visitAssignment(node, in); - - Node expNode = node.getExpression(); - - // strip off typecast if any - Node expNodeSansCast = - (expNode instanceof TypeCastNode) ? ((TypeCastNode) expNode).getOperand() : expNode; - // null if right-hand-side is not an array creation expression - ArrayCreationNode acNode = - (expNodeSansCast instanceof ArrayCreationNode) - ? acNode = (ArrayCreationNode) expNodeSansCast - : null; - - if (acNode != null) { - // Right-hand side of assignment is an array creation expression - List nodeList = acNode.getDimensions(); - if (nodeList.size() < 1) { - return result; - } - Node dim = acNode.getDimension(0); - - UBQualifier previousQualifier = getUBQualifier(dim, in); - JavaExpression arrayExpr = JavaExpression.fromNode(node.getTarget()); - String arrayString = arrayExpr.toString(); - LessThanLengthOf newInfo = - (LessThanLengthOf) UBQualifier.createUBQualifier(arrayString, "-1"); - UBQualifier combined = previousQualifier.glb(newInfo); - AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(combined); - - JavaExpression dimExpr = JavaExpression.fromNode(dim); - result.getRegularStore().insertValue(dimExpr, newAnno); - propagateToOperands(newInfo, dim, in, result.getRegularStore()); - } + /** The type factory associated with this transfer function. */ + private final UpperBoundAnnotatedTypeFactory atypeFactory; + + /** The int TypeMirror. */ + private final TypeMirror intTM; + + /** + * Creates a new UpperBoundTransfer. + * + * @param analysis the analysis for this transfer function + */ + public UpperBoundTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (UpperBoundAnnotatedTypeFactory) analysis.getTypeFactory(); + intTM = atypeFactory.types.getPrimitiveType(TypeKind.INT); + } + + /** + * Case 1: Refine the type of expressions used as an array dimension to be less than length of the + * array to which the new array is assigned. For example, in "int[] array = new int[expr];", the + * type of expr is @LTEqLength("array"). + */ + @Override + public TransferResult visitAssignment( + AssignmentNode node, TransferInput in) { + TransferResult result = super.visitAssignment(node, in); + + Node expNode = node.getExpression(); + + // strip off typecast if any + Node expNodeSansCast = + (expNode instanceof TypeCastNode) ? ((TypeCastNode) expNode).getOperand() : expNode; + // null if right-hand-side is not an array creation expression + ArrayCreationNode acNode = + (expNodeSansCast instanceof ArrayCreationNode) + ? acNode = (ArrayCreationNode) expNodeSansCast + : null; + + if (acNode != null) { + // Right-hand side of assignment is an array creation expression + List nodeList = acNode.getDimensions(); + if (nodeList.size() < 1) { return result; + } + Node dim = acNode.getDimension(0); + + UBQualifier previousQualifier = getUBQualifier(dim, in); + JavaExpression arrayExpr = JavaExpression.fromNode(node.getTarget()); + String arrayString = arrayExpr.toString(); + LessThanLengthOf newInfo = + (LessThanLengthOf) UBQualifier.createUBQualifier(arrayString, "-1"); + UBQualifier combined = previousQualifier.glb(newInfo); + AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(combined); + + JavaExpression dimExpr = JavaExpression.fromNode(dim); + result.getRegularStore().insertValue(dimExpr, newAnno); + propagateToOperands(newInfo, dim, in, result.getRegularStore()); } - - /** - * {@code node} is known to be {@code typeOfNode}. If the node is a plus or a minus then the - * types of the left and right operands can be refined to include offsets. If the node is a - * multiplication, its operands can also be refined. See {@link - * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, - * CFStore)}, {@link #propagateToSubtractionOperands(UBQualifier.LessThanLengthOf, - * NumericalSubtractionNode, TransferInput, CFStore)}, and {@link - * #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, - * CFStore)} for details. - * - * @param typeOfNode type of node - * @param node the node - * @param in the TransferInput before propagate to this operand - * @param store location to store the refined type - */ - private void propagateToOperands( - LessThanLengthOf typeOfNode, - Node node, - TransferInput in, - CFStore store) { - if (node instanceof NumericalAdditionNode) { - Node right = ((NumericalAdditionNode) node).getRightOperand(); - Node left = ((NumericalAdditionNode) node).getLeftOperand(); - propagateToAdditionOperand(typeOfNode, left, right, in, store); - propagateToAdditionOperand(typeOfNode, right, left, in, store); - } else if (node instanceof NumericalSubtractionNode) { - propagateToSubtractionOperands(typeOfNode, (NumericalSubtractionNode) node, in, store); - } else if (node instanceof NumericalMultiplicationNode) { - if (atypeFactory.hasLowerBoundTypeByClass(node, Positive.class)) { - Node right = ((NumericalMultiplicationNode) node).getRightOperand(); - Node left = ((NumericalMultiplicationNode) node).getLeftOperand(); - propagateToMultiplicationOperand(typeOfNode, left, right, in, store); - propagateToMultiplicationOperand(typeOfNode, right, left, in, store); - } - } + return result; + } + + /** + * {@code node} is known to be {@code typeOfNode}. If the node is a plus or a minus then the types + * of the left and right operands can be refined to include offsets. If the node is a + * multiplication, its operands can also be refined. See {@link + * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, CFStore)}, + * {@link #propagateToSubtractionOperands(UBQualifier.LessThanLengthOf, NumericalSubtractionNode, + * TransferInput, CFStore)}, and {@link + * #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, + * CFStore)} for details. + * + * @param typeOfNode type of node + * @param node the node + * @param in the TransferInput before propagate to this operand + * @param store location to store the refined type + */ + private void propagateToOperands( + LessThanLengthOf typeOfNode, Node node, TransferInput in, CFStore store) { + if (node instanceof NumericalAdditionNode) { + Node right = ((NumericalAdditionNode) node).getRightOperand(); + Node left = ((NumericalAdditionNode) node).getLeftOperand(); + propagateToAdditionOperand(typeOfNode, left, right, in, store); + propagateToAdditionOperand(typeOfNode, right, left, in, store); + } else if (node instanceof NumericalSubtractionNode) { + propagateToSubtractionOperands(typeOfNode, (NumericalSubtractionNode) node, in, store); + } else if (node instanceof NumericalMultiplicationNode) { + if (atypeFactory.hasLowerBoundTypeByClass(node, Positive.class)) { + Node right = ((NumericalMultiplicationNode) node).getRightOperand(); + Node left = ((NumericalMultiplicationNode) node).getLeftOperand(); + propagateToMultiplicationOperand(typeOfNode, left, right, in, store); + propagateToMultiplicationOperand(typeOfNode, right, left, in, store); + } } - - /** - * {@code other} times {@code node} is known to be {@code typeOfMultiplication}. - * - *

This implies that if {@code other} is positive, then {@code node} is {@code - * typeOfMultiplication}. If {@code other} is greater than 1, then {@code node} is {@code - * typeOfMultiplication} plus 1. These are cases 2 and 3, respectively. - */ - private void propagateToMultiplicationOperand( - LessThanLengthOf typeOfMultiplication, - Node node, - Node other, - TransferInput in, - CFStore store) { - if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { - Long minValue = - ValueCheckerUtils.getMinValue( - other.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (minValue != null && minValue > 1) { - typeOfMultiplication = (LessThanLengthOf) typeOfMultiplication.plusOffset(1); - } - UBQualifier qual = getUBQualifier(node, in); - UBQualifier newQual = qual.glb(typeOfMultiplication); - JavaExpression je = JavaExpression.fromNode(node); - store.insertValue(je, atypeFactory.convertUBQualifierToAnnotation(newQual)); - } + } + + /** + * {@code other} times {@code node} is known to be {@code typeOfMultiplication}. + * + *

This implies that if {@code other} is positive, then {@code node} is {@code + * typeOfMultiplication}. If {@code other} is greater than 1, then {@code node} is {@code + * typeOfMultiplication} plus 1. These are cases 2 and 3, respectively. + */ + private void propagateToMultiplicationOperand( + LessThanLengthOf typeOfMultiplication, + Node node, + Node other, + TransferInput in, + CFStore store) { + if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { + Long minValue = + ValueCheckerUtils.getMinValue( + other.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (minValue != null && minValue > 1) { + typeOfMultiplication = (LessThanLengthOf) typeOfMultiplication.plusOffset(1); + } + UBQualifier qual = getUBQualifier(node, in); + UBQualifier newQual = qual.glb(typeOfMultiplication); + JavaExpression je = JavaExpression.fromNode(node); + store.insertValue(je, atypeFactory.convertUBQualifierToAnnotation(newQual)); } - - /** - * The subtraction node, {@code node}, is known to be {@code typeOfSubtraction}. - * - *

This means that the left node is less than or equal to the length of the array when the - * right node is subtracted from the left node. Note that unlike {@link - * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, - * CFStore)} and {@link #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, - * Node, TransferInput, CFStore)}, this method takes the NumericalSubtractionNode instead of the - * two operand nodes. This implements case 4. - * - * @param typeOfSubtraction type of node - * @param node subtraction node that has typeOfSubtraction - * @param in a TransferInput - * @param store location to store the refined type - */ - private void propagateToSubtractionOperands( - LessThanLengthOf typeOfSubtraction, - NumericalSubtractionNode node, - TransferInput in, - CFStore store) { - UBQualifier left = getUBQualifier(node.getLeftOperand(), in); - UBQualifier newInfo = typeOfSubtraction.minusOffset(node.getRightOperand(), atypeFactory); - - UBQualifier newLeft = left.glb(newInfo); - JavaExpression leftJe = JavaExpression.fromNode(node.getLeftOperand()); - store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(newLeft)); + } + + /** + * The subtraction node, {@code node}, is known to be {@code typeOfSubtraction}. + * + *

This means that the left node is less than or equal to the length of the array when the + * right node is subtracted from the left node. Note that unlike {@link + * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, CFStore)} + * and {@link #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, Node, + * TransferInput, CFStore)}, this method takes the NumericalSubtractionNode instead of the two + * operand nodes. This implements case 4. + * + * @param typeOfSubtraction type of node + * @param node subtraction node that has typeOfSubtraction + * @param in a TransferInput + * @param store location to store the refined type + */ + private void propagateToSubtractionOperands( + LessThanLengthOf typeOfSubtraction, + NumericalSubtractionNode node, + TransferInput in, + CFStore store) { + UBQualifier left = getUBQualifier(node.getLeftOperand(), in); + UBQualifier newInfo = typeOfSubtraction.minusOffset(node.getRightOperand(), atypeFactory); + + UBQualifier newLeft = left.glb(newInfo); + JavaExpression leftJe = JavaExpression.fromNode(node.getLeftOperand()); + store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(newLeft)); + } + + /** + * Refines the type of {@code operand} to {@code typeOfAddition} plus {@code other}. If {@code + * other} is non-negative, then {@code operand} also less than the length of the arrays in {@code + * typeOfAddition}. If {@code other} is positive, then {@code operand} also less than the length + * of the arrays in {@code typeOfAddition} plus 1. These are cases 5, 6, and 7. + * + * @param typeOfAddition type of {@code operand + other} + * @param operand the Node to refine + * @param other the Node added to {@code operand} + * @param in a TransferInput + * @param store location to store the refined types + */ + private void propagateToAdditionOperand( + LessThanLengthOf typeOfAddition, + Node operand, + Node other, + TransferInput in, + CFStore store) { + UBQualifier operandQual = getUBQualifier(operand, in); + UBQualifier newQual = operandQual.glb(typeOfAddition.plusOffset(other, atypeFactory)); + + // If the node is NonNegative, add an LTEL to the qual. If Positive, add an LTL. + if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { + newQual = newQual.glb(typeOfAddition.plusOffset(1)); + } else if (atypeFactory.hasLowerBoundTypeByClass(other, NonNegative.class)) { + newQual = newQual.glb(typeOfAddition); } - - /** - * Refines the type of {@code operand} to {@code typeOfAddition} plus {@code other}. If {@code - * other} is non-negative, then {@code operand} also less than the length of the arrays in - * {@code typeOfAddition}. If {@code other} is positive, then {@code operand} also less than the - * length of the arrays in {@code typeOfAddition} plus 1. These are cases 5, 6, and 7. - * - * @param typeOfAddition type of {@code operand + other} - * @param operand the Node to refine - * @param other the Node added to {@code operand} - * @param in a TransferInput - * @param store location to store the refined types - */ - private void propagateToAdditionOperand( - LessThanLengthOf typeOfAddition, - Node operand, - Node other, - TransferInput in, - CFStore store) { - UBQualifier operandQual = getUBQualifier(operand, in); - UBQualifier newQual = operandQual.glb(typeOfAddition.plusOffset(other, atypeFactory)); - - // If the node is NonNegative, add an LTEL to the qual. If Positive, add an LTL. - if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { - newQual = newQual.glb(typeOfAddition.plusOffset(1)); - } else if (atypeFactory.hasLowerBoundTypeByClass(other, NonNegative.class)) { - newQual = newQual.glb(typeOfAddition); - } - JavaExpression operandJe = JavaExpression.fromNode(operand); - store.insertValue(operandJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); + JavaExpression operandJe = JavaExpression.fromNode(operand); + store.insertValue(operandJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); + } + + /** + * Case 8: if x < y, and y has a type that is related to the length of an array, then x has the + * same type, with an offset that is one less. + */ + @Override + protected void refineGT( + Node larger, + AnnotationMirror largerAnno, + Node smaller, + AnnotationMirror smallerAnno, + CFStore store, + TransferInput in) { + // larger > smaller + UBQualifier largerQual = + UBQualifier.createUBQualifier(largerAnno, (UpperBoundChecker) atypeFactory.getChecker()); + // larger + 1 >= smaller + UBQualifier largerQualPlus1 = largerQual.plusOffset(1); + UBQualifier rightQualifier = + UBQualifier.createUBQualifier(smallerAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier refinedRight = rightQualifier.glb(largerQualPlus1); + + if (largerQualPlus1.isLessThanLengthQualifier()) { + propagateToOperands((LessThanLengthOf) largerQualPlus1, smaller, in, store); } - /** - * Case 8: if x < y, and y has a type that is related to the length of an array, then x has - * the same type, with an offset that is one less. - */ - @Override - protected void refineGT( - Node larger, - AnnotationMirror largerAnno, - Node smaller, - AnnotationMirror smallerAnno, - CFStore store, - TransferInput in) { - // larger > smaller - UBQualifier largerQual = - UBQualifier.createUBQualifier( - largerAnno, (UpperBoundChecker) atypeFactory.getChecker()); - // larger + 1 >= smaller - UBQualifier largerQualPlus1 = largerQual.plusOffset(1); - UBQualifier rightQualifier = - UBQualifier.createUBQualifier( - smallerAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier refinedRight = rightQualifier.glb(largerQualPlus1); - - if (largerQualPlus1.isLessThanLengthQualifier()) { - propagateToOperands((LessThanLengthOf) largerQualPlus1, smaller, in, store); - } - - refineSubtrahendWithOffset(larger, smaller, true, in, store); - - JavaExpression rightJe = JavaExpression.fromNode(smaller); - store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); + refineSubtrahendWithOffset(larger, smaller, true, in, store); + + JavaExpression rightJe = JavaExpression.fromNode(smaller); + store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); + } + + /** + * Case 9: if x ≤ y, and y has a type that is related to the length of an array, then x has the + * same type. + */ + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + UBQualifier leftQualifier = + UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier rightQualifier = + UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier refinedRight = rightQualifier.glb(leftQualifier); + + if (leftQualifier.isLessThanLengthQualifier()) { + propagateToOperands((LessThanLengthOf) leftQualifier, right, in, store); } - /** - * Case 9: if x ≤ y, and y has a type that is related to the length of an array, then x has - * the same type. - */ - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - UBQualifier leftQualifier = - UBQualifier.createUBQualifier( - leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier rightQualifier = - UBQualifier.createUBQualifier( - rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier refinedRight = rightQualifier.glb(leftQualifier); - - if (leftQualifier.isLessThanLengthQualifier()) { - propagateToOperands((LessThanLengthOf) leftQualifier, right, in, store); - } - - refineSubtrahendWithOffset(left, right, false, in, store); - - JavaExpression rightJe = JavaExpression.fromNode(right); - store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); + refineSubtrahendWithOffset(left, right, false, in, store); + + JavaExpression rightJe = JavaExpression.fromNode(right); + store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); + } + + /** + * Refines the subtrahend in a subtraction which is greater than or equal to a certain offset. The + * type of the subtrahend is refined to the type of the minuend with the offset added. This is + * case 10. + * + *

This is based on the fact that if {@code (minuend - subtrahend) >= offset}, and {@code + * minuend + o < l}, then {@code subtrahend + o + offset < l}. + * + *

If {@code gtNode} is not a {@link NumericalSubtractionNode}, the method does nothing. + * + * @param gtNode the node that is greater or equal to the offset + * @param offsetNode a node part of the offset + * @param offsetAddOne whether to add one to the offset + * @param in input of the transfer function + * @param store location to store the refined types + */ + private void refineSubtrahendWithOffset( + Node gtNode, + Node offsetNode, + boolean offsetAddOne, + TransferInput in, + CFStore store) { + if (gtNode instanceof NumericalSubtractionNode) { + NumericalSubtractionNode subtractionNode = (NumericalSubtractionNode) gtNode; + + Node minuend = subtractionNode.getLeftOperand(); + UBQualifier minuendQual = getUBQualifier(minuend, in); + Node subtrahend = subtractionNode.getRightOperand(); + UBQualifier subtrahendQual = getUBQualifier(subtrahend, in); + + UBQualifier newQual = + subtrahendQual.glb( + minuendQual.plusOffset(offsetNode, atypeFactory).plusOffset(offsetAddOne ? 1 : 0)); + JavaExpression subtrahendJe = JavaExpression.fromNode(subtrahend); + store.insertValue(subtrahendJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); } - - /** - * Refines the subtrahend in a subtraction which is greater than or equal to a certain offset. - * The type of the subtrahend is refined to the type of the minuend with the offset added. This - * is case 10. - * - *

This is based on the fact that if {@code (minuend - subtrahend) >= offset}, and {@code - * minuend + o < l}, then {@code subtrahend + o + offset < l}. - * - *

If {@code gtNode} is not a {@link NumericalSubtractionNode}, the method does nothing. - * - * @param gtNode the node that is greater or equal to the offset - * @param offsetNode a node part of the offset - * @param offsetAddOne whether to add one to the offset - * @param in input of the transfer function - * @param store location to store the refined types - */ - private void refineSubtrahendWithOffset( - Node gtNode, - Node offsetNode, - boolean offsetAddOne, - TransferInput in, - CFStore store) { - if (gtNode instanceof NumericalSubtractionNode) { - NumericalSubtractionNode subtractionNode = (NumericalSubtractionNode) gtNode; - - Node minuend = subtractionNode.getLeftOperand(); - UBQualifier minuendQual = getUBQualifier(minuend, in); - Node subtrahend = subtractionNode.getRightOperand(); - UBQualifier subtrahendQual = getUBQualifier(subtrahend, in); - - UBQualifier newQual = - subtrahendQual.glb( - minuendQual - .plusOffset(offsetNode, atypeFactory) - .plusOffset(offsetAddOne ? 1 : 0)); - JavaExpression subtrahendJe = JavaExpression.fromNode(subtrahend); - store.insertValue(subtrahendJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); - } + } + + /** Implements case 11. */ + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult res, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + TransferResult result = + super.strengthenAnnotationOfEqualTo( + res, firstNode, secondNode, firstValue, secondValue, notEqualTo); + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, firstNode, secondNode); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; } - /** Implements case 11. */ - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult res, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - TransferResult result = - super.strengthenAnnotationOfEqualTo( - res, firstNode, secondNode, firstValue, secondValue, notEqualTo); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, firstNode, secondNode); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } - - CFStore equalsStore = notEqualTo ? rfi.elseStore : rfi.thenStore; - CFStore notEqualStore = notEqualTo ? rfi.thenStore : rfi.elseStore; - - refineEq(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, equalsStore); - - refineNeqSequenceLength(rfi.left, rfi.right, rfi.rightAnno, notEqualStore); - refineNeqSequenceLength(rfi.right, rfi.left, rfi.leftAnno, notEqualStore); - return rfi.newResult; + CFStore equalsStore = notEqualTo ? rfi.elseStore : rfi.thenStore; + CFStore notEqualStore = notEqualTo ? rfi.thenStore : rfi.elseStore; + + refineEq(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, equalsStore); + + refineNeqSequenceLength(rfi.left, rfi.right, rfi.rightAnno, notEqualStore); + refineNeqSequenceLength(rfi.right, rfi.left, rfi.leftAnno, notEqualStore); + return rfi.newResult; + } + + /** Refines the type of the left and right node to glb of the left and right annotation. */ + private void refineEq( + Node left, AnnotationMirror leftAnno, Node right, AnnotationMirror rightAnno, CFStore store) { + UBQualifier leftQualifier = + UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier rightQualifier = + UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier glb = rightQualifier.glb(leftQualifier); + AnnotationMirror glbAnno = atypeFactory.convertUBQualifierToAnnotation(glb); + + List internalsRight = splitAssignments(right); + for (Node internal : internalsRight) { + JavaExpression rightJe = JavaExpression.fromNode(internal); + store.insertValue(rightJe, glbAnno); } - /** Refines the type of the left and right node to glb of the left and right annotation. */ - private void refineEq( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store) { - UBQualifier leftQualifier = - UBQualifier.createUBQualifier( - leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier rightQualifier = - UBQualifier.createUBQualifier( - rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier glb = rightQualifier.glb(leftQualifier); - AnnotationMirror glbAnno = atypeFactory.convertUBQualifierToAnnotation(glb); - - List internalsRight = splitAssignments(right); - for (Node internal : internalsRight) { - JavaExpression rightJe = JavaExpression.fromNode(internal); - store.insertValue(rightJe, glbAnno); - } - - List internalsLeft = splitAssignments(left); - for (Node internal : internalsLeft) { - JavaExpression leftJe = JavaExpression.fromNode(internal); - store.insertValue(leftJe, glbAnno); - } + List internalsLeft = splitAssignments(left); + for (Node internal : internalsLeft) { + JavaExpression leftJe = JavaExpression.fromNode(internal); + store.insertValue(leftJe, glbAnno); } - - /** - * If lengthAccess node is an sequence length field or method access (optionally with a constant - * offset subtracted) and the other node is less than or equal to that sequence length (minus - * the offset), then refine the other node's type to less than the sequence length (minus the - * offset). This is case 12. - */ - private void refineNeqSequenceLength( - Node lengthAccess, Node otherNode, AnnotationMirror otherNodeAnno, CFStore store) { - - // If lengthAccess is "receiver.length - c" where c is an integer constant, - // then lengthOffset is "c". - int lengthOffset = 0; - if (lengthAccess instanceof NumericalSubtractionNode) { - NumericalSubtractionNode subtraction = (NumericalSubtractionNode) lengthAccess; - Node offsetNode = subtraction.getRightOperand(); - Long offsetValue = - ValueCheckerUtils.getExactValue( - offsetNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (offsetValue != null - && offsetValue > Integer.MIN_VALUE - && offsetValue <= Integer.MAX_VALUE) { - lengthOffset = offsetValue.intValue(); - lengthAccess = subtraction.getLeftOperand(); - } else { - // Subtraction of non-constant expressions is not supported - return; - } - } - - JavaExpression receiver = null; - if (NodeUtils.isArrayLengthFieldAccess(lengthAccess)) { - FieldAccess fa = - (FieldAccess) - JavaExpression.fromNodeFieldAccess((FieldAccessNode) lengthAccess); - receiver = fa.getReceiver(); - - } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthAccess)) { - JavaExpression ma = JavaExpression.fromNode(lengthAccess); - if (ma instanceof MethodCall) { - receiver = ((MethodCall) ma).getReceiver(); - } - } - - if (receiver != null && !receiver.containsUnknown()) { - UBQualifier otherQualifier = - UBQualifier.createUBQualifier( - otherNodeAnno, (UpperBoundChecker) atypeFactory.getChecker()); - String sequence = receiver.toString(); - // Check if otherNode + c - 1 < receiver.length - if (otherQualifier.hasSequenceWithOffset(sequence, lengthOffset - 1)) { - // Add otherNode + c < receiver.length - UBQualifier newQualifier = - UBQualifier.createUBQualifier(sequence, Integer.toString(lengthOffset)); - otherQualifier = otherQualifier.glb(newQualifier); - for (Node internal : splitAssignments(otherNode)) { - JavaExpression leftJe = JavaExpression.fromNode(internal); - store.insertValue( - leftJe, atypeFactory.convertUBQualifierToAnnotation(otherQualifier)); - } - } - } + } + + /** + * If lengthAccess node is an sequence length field or method access (optionally with a constant + * offset subtracted) and the other node is less than or equal to that sequence length (minus the + * offset), then refine the other node's type to less than the sequence length (minus the offset). + * This is case 12. + */ + private void refineNeqSequenceLength( + Node lengthAccess, Node otherNode, AnnotationMirror otherNodeAnno, CFStore store) { + + // If lengthAccess is "receiver.length - c" where c is an integer constant, + // then lengthOffset is "c". + int lengthOffset = 0; + if (lengthAccess instanceof NumericalSubtractionNode) { + NumericalSubtractionNode subtraction = (NumericalSubtractionNode) lengthAccess; + Node offsetNode = subtraction.getRightOperand(); + Long offsetValue = + ValueCheckerUtils.getExactValue( + offsetNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (offsetValue != null + && offsetValue > Integer.MIN_VALUE + && offsetValue <= Integer.MAX_VALUE) { + lengthOffset = offsetValue.intValue(); + lengthAccess = subtraction.getLeftOperand(); + } else { + // Subtraction of non-constant expressions is not supported + return; + } } - /** - * If some Node a is known to be less than the length of some array, x, then, the type of a + b, - * is @LTLengthOf(value="x", offset="-b"). If b is known to be less than the length of some - * other array, y, then the type of a + b is @LTLengthOf(value={"x", "y"}, offset={"-b", "-a"}). - * - *

Alternatively, if a is known to be less than the length of x when some offset, o, is added - * to a (the type of a is @LTLengthOf(value="x", offset="o")), then the type of a + b - * is @LTLengthOf(value="x",offset="o - b"). (Note, if "o - b" can be computed, then it is and - * the result is used in the annotation.) - * - *

In addition, If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int - * and expression j is less than or equal to the length of f1, then the type of i + j is - * .@LTLengthOf("f2"). - * - *

These three cases correspond to cases 13-15. - */ - @Override - public TransferResult visitNumericalAddition( - NumericalAdditionNode n, TransferInput in) { - // type of leftNode + rightNode is glb(t, s) where - // t = minusOffset(type(leftNode), rightNode) and - // s = minusOffset(type(rightNode), leftNode) - - UBQualifier left = getUBQualifierForAddition(n.getLeftOperand(), in); - UBQualifier t = left.minusOffset(n.getRightOperand(), atypeFactory); - - UBQualifier right = getUBQualifierForAddition(n.getRightOperand(), in); - UBQualifier s = right.minusOffset(n.getLeftOperand(), atypeFactory); - - UBQualifier glb = t.glb(s); - if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { - // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and - // expression j is less than or equal to the length of f1, then the type of i + j is - // @LTLengthOf("f2"). - UBQualifier r = - removeSequenceLengths((LessThanLengthOf) left, (LessThanLengthOf) right); - glb = glb.glb(r); - UBQualifier l = - removeSequenceLengths((LessThanLengthOf) right, (LessThanLengthOf) left); - glb = glb.glb(l); - } - - return createTransferResult(n, in, glb); + JavaExpression receiver = null; + if (NodeUtils.isArrayLengthFieldAccess(lengthAccess)) { + FieldAccess fa = + (FieldAccess) JavaExpression.fromNodeFieldAccess((FieldAccessNode) lengthAccess); + receiver = fa.getReceiver(); + + } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthAccess)) { + JavaExpression ma = JavaExpression.fromNode(lengthAccess); + if (ma instanceof MethodCall) { + receiver = ((MethodCall) ma).getReceiver(); + } } - /** - * Return the result of adding i to j. - * - *

When expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length") int} and - * expression j is less than or equal to the length of f1, then the type of i + j - * is @LTLengthOf("f2"). - * - *

When expression i has type {@code @LTLengthOf (value = "f2", offset = "f1.length - 1") - * int} and expression j is less than the length of f1, then the type of i + j - * is @LTLengthOf("f2"). - * - * @param i the type of the expression added to j - * @param j the type of the expression added to i - * @return the type of i + j - */ - private UBQualifier removeSequenceLengths(LessThanLengthOf i, LessThanLengthOf j) { - List lessThan = new ArrayList<>(); - List lessThanOrEqual = new ArrayList<>(); - for (String sequence : i.getSequences()) { - if (i.isLessThanLengthOf(sequence)) { - lessThan.add(sequence); - } else if (i.hasSequenceWithOffset(sequence, -1)) { - lessThanOrEqual.add(sequence); - } + if (receiver != null && !receiver.containsUnknown()) { + UBQualifier otherQualifier = + UBQualifier.createUBQualifier( + otherNodeAnno, (UpperBoundChecker) atypeFactory.getChecker()); + String sequence = receiver.toString(); + // Check if otherNode + c - 1 < receiver.length + if (otherQualifier.hasSequenceWithOffset(sequence, lengthOffset - 1)) { + // Add otherNode + c < receiver.length + UBQualifier newQualifier = + UBQualifier.createUBQualifier(sequence, Integer.toString(lengthOffset)); + otherQualifier = otherQualifier.glb(newQualifier); + for (Node internal : splitAssignments(otherNode)) { + JavaExpression leftJe = JavaExpression.fromNode(internal); + store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(otherQualifier)); } - // Creates a qualifier that is the same a j with the array.length offsets removed. If - // an offset doesn't have an array.length, then the offset/array pair is removed. If - // there are no such pairs, Unknown is returned. - UBQualifier lessThanEqQ = j.removeSequenceLengthAccess(lessThanOrEqual); - // Creates a qualifier that is the same a j with the array.length - 1 offsets removed. If - // an offset doesn't have an array.length, then the offset/array pair is removed. If - // there are no such pairs, Unknown is returned. - UBQualifier lessThanQ = j.removeSequenceLengthAccessAndNeg1(lessThan); - - return lessThanEqQ.glb(lessThanQ); + } + } + } + + /** + * If some Node a is known to be less than the length of some array, x, then, the type of a + b, + * is @LTLengthOf(value="x", offset="-b"). If b is known to be less than the length of some other + * array, y, then the type of a + b is @LTLengthOf(value={"x", "y"}, offset={"-b", "-a"}). + * + *

Alternatively, if a is known to be less than the length of x when some offset, o, is added + * to a (the type of a is @LTLengthOf(value="x", offset="o")), then the type of a + b + * is @LTLengthOf(value="x",offset="o - b"). (Note, if "o - b" can be computed, then it is and the + * result is used in the annotation.) + * + *

In addition, If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int + * and expression j is less than or equal to the length of f1, then the type of i + j is + * .@LTLengthOf("f2"). + * + *

These three cases correspond to cases 13-15. + */ + @Override + public TransferResult visitNumericalAddition( + NumericalAdditionNode n, TransferInput in) { + // type of leftNode + rightNode is glb(t, s) where + // t = minusOffset(type(leftNode), rightNode) and + // s = minusOffset(type(rightNode), leftNode) + + UBQualifier left = getUBQualifierForAddition(n.getLeftOperand(), in); + UBQualifier t = left.minusOffset(n.getRightOperand(), atypeFactory); + + UBQualifier right = getUBQualifierForAddition(n.getRightOperand(), in); + UBQualifier s = right.minusOffset(n.getLeftOperand(), atypeFactory); + + UBQualifier glb = t.glb(s); + if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { + // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and + // expression j is less than or equal to the length of f1, then the type of i + j is + // @LTLengthOf("f2"). + UBQualifier r = removeSequenceLengths((LessThanLengthOf) left, (LessThanLengthOf) right); + glb = glb.glb(r); + UBQualifier l = removeSequenceLengths((LessThanLengthOf) right, (LessThanLengthOf) left); + glb = glb.glb(l); } - /** - * If some Node a is known to be less than the length of some sequence x, then the type of a - b - * is @LTLengthOf(value="x", offset="b"). If b is known to be less than the length of some other - * sequence, this doesn't add any information about the type of a - b. But, if b is non-negative - * or positive, then a - b should keep the types of a. This corresponds to cases 16 and 17. - */ - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput in) { - UBQualifier left = getUBQualifier(n.getLeftOperand(), in); - UBQualifier leftWithOffset = left.plusOffset(n.getRightOperand(), atypeFactory); - if (atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), NonNegative.class) - || atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), Positive.class)) { - // If the right side of the expression is NN or POS, then all the left side's - // annotations should be kept. - if (left.isLessThanLengthQualifier()) { - leftWithOffset = left.glb(leftWithOffset); - } - } - - // If the result of a numerical subtraction would be LTEL(b) or LTL(b), and b is HSS(a, - // from, to), and the subtraction node itself is i - from where i is LTEL(b), then the - // result is LTEL(a). If i is LTL(b) instead, the result is LTL(a). - - if (leftWithOffset.isLessThanLengthQualifier()) { - - LessThanLengthOf subtractionResult = (LessThanLengthOf) leftWithOffset; - - for (String b : subtractionResult.getSequences()) { - if (subtractionResult.hasSequenceWithOffset(b, -1) - || subtractionResult.hasSequenceWithOffset(b, 0)) { - - TreePath currentPath = this.atypeFactory.getPath(n.getTree()); - JavaExpression je; - try { - je = - UpperBoundVisitor.parseJavaExpressionString( - b, atypeFactory, currentPath); - } catch (NullPointerException npe) { - // I have no idea why this seems to happen only on a few JDK classes. It - // appears to only happen during the preprocessing step - the NPE is thrown - // while trying to find the enclosing class of a class tree, which is null. - // I can't find a reproducible test case that's smaller than the size of - // DualPivotQuicksort. Since this refinement is optional, but useful - // elsewhere, catching this NPE here and returning is always safe. - return createTransferResult(n, in, leftWithOffset); - } - - Subsequence subsequence = - Subsequence.getSubsequenceFromReceiver(je, atypeFactory); - - if (subsequence != null) { - String from = subsequence.from; - String to = subsequence.to; - String a = subsequence.array; - - JavaExpression leftOp = JavaExpression.fromNode(n.getLeftOperand()); - JavaExpression rightOp = JavaExpression.fromNode(n.getRightOperand()); - - if (rightOp.toString().equals(from)) { - LessThanAnnotatedTypeFactory lessThanAtypeFactory = - atypeFactory.getLessThanAnnotatedTypeFactory(); - AnnotationMirror lessThanType = - lessThanAtypeFactory - .getAnnotatedType(n.getLeftOperand().getTree()) - .getAnnotation(LessThan.class); - - if (lessThanType != null - && lessThanAtypeFactory.isLessThan(lessThanType, to)) { - UBQualifier ltlA = UBQualifier.createUBQualifier(a, "0"); - leftWithOffset = leftWithOffset.glb(ltlA); - } else if (leftOp.toString().equals(to) - || (lessThanType != null - && lessThanAtypeFactory.isLessThanOrEqual( - lessThanType, to))) { - // It's necessary to check if leftOp == to because LessThan doesn't - // infer that things are less than or equal to themselves. - UBQualifier ltelA = UBQualifier.createUBQualifier(a, "-1"); - leftWithOffset = leftWithOffset.glb(ltelA); - } - } - } - } - } - } - return createTransferResult(n, in, leftWithOffset); + return createTransferResult(n, in, glb); + } + + /** + * Return the result of adding i to j. + * + *

When expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length") int} and + * expression j is less than or equal to the length of f1, then the type of i + j + * is @LTLengthOf("f2"). + * + *

When expression i has type {@code @LTLengthOf (value = "f2", offset = "f1.length - 1") int} + * and expression j is less than the length of f1, then the type of i + j is @LTLengthOf("f2"). + * + * @param i the type of the expression added to j + * @param j the type of the expression added to i + * @return the type of i + j + */ + private UBQualifier removeSequenceLengths(LessThanLengthOf i, LessThanLengthOf j) { + List lessThan = new ArrayList<>(); + List lessThanOrEqual = new ArrayList<>(); + for (String sequence : i.getSequences()) { + if (i.isLessThanLengthOf(sequence)) { + lessThan.add(sequence); + } else if (i.hasSequenceWithOffset(sequence, -1)) { + lessThanOrEqual.add(sequence); + } + } + // Creates a qualifier that is the same a j with the array.length offsets removed. If + // an offset doesn't have an array.length, then the offset/array pair is removed. If + // there are no such pairs, Unknown is returned. + UBQualifier lessThanEqQ = j.removeSequenceLengthAccess(lessThanOrEqual); + // Creates a qualifier that is the same a j with the array.length - 1 offsets removed. If + // an offset doesn't have an array.length, then the offset/array pair is removed. If + // there are no such pairs, Unknown is returned. + UBQualifier lessThanQ = j.removeSequenceLengthAccessAndNeg1(lessThan); + + return lessThanEqQ.glb(lessThanQ); + } + + /** + * If some Node a is known to be less than the length of some sequence x, then the type of a - b + * is @LTLengthOf(value="x", offset="b"). If b is known to be less than the length of some other + * sequence, this doesn't add any information about the type of a - b. But, if b is non-negative + * or positive, then a - b should keep the types of a. This corresponds to cases 16 and 17. + */ + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput in) { + UBQualifier left = getUBQualifier(n.getLeftOperand(), in); + UBQualifier leftWithOffset = left.plusOffset(n.getRightOperand(), atypeFactory); + if (atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), NonNegative.class) + || atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), Positive.class)) { + // If the right side of the expression is NN or POS, then all the left side's + // annotations should be kept. + if (left.isLessThanLengthQualifier()) { + leftWithOffset = left.glb(leftWithOffset); + } } - /** - * Computes a type of a sequence length access. This is case 18. - * - * @param n sequence length access node - */ - private @Nullable TransferResult visitLengthAccess( - Node n, - TransferInput in, - JavaExpression sequenceJe, - Tree sequenceTree) { - if (sequenceTree == null) { - return null; - } - // Look up the SameLen type of the sequence. - AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(sequenceTree); - List sameLenSequences; - if (sameLenAnno == null) { - sameLenSequences = Collections.singletonList(sequenceJe.toString()); - } else { - sameLenSequences = - AnnotationUtils.getElementValueArray( - sameLenAnno, atypeFactory.sameLenValueElement, String.class); - String sequenceString = sequenceJe.toString(); - if (!sameLenSequences.contains(sequenceString)) { - sameLenSequences.add(sequenceString); + // If the result of a numerical subtraction would be LTEL(b) or LTL(b), and b is HSS(a, + // from, to), and the subtraction node itself is i - from where i is LTEL(b), then the + // result is LTEL(a). If i is LTL(b) instead, the result is LTL(a). + + if (leftWithOffset.isLessThanLengthQualifier()) { + + LessThanLengthOf subtractionResult = (LessThanLengthOf) leftWithOffset; + + for (String b : subtractionResult.getSequences()) { + if (subtractionResult.hasSequenceWithOffset(b, -1) + || subtractionResult.hasSequenceWithOffset(b, 0)) { + + TreePath currentPath = this.atypeFactory.getPath(n.getTree()); + JavaExpression je; + try { + je = UpperBoundVisitor.parseJavaExpressionString(b, atypeFactory, currentPath); + } catch (NullPointerException npe) { + // I have no idea why this seems to happen only on a few JDK classes. It + // appears to only happen during the preprocessing step - the NPE is thrown + // while trying to find the enclosing class of a class tree, which is null. + // I can't find a reproducible test case that's smaller than the size of + // DualPivotQuicksort. Since this refinement is optional, but useful + // elsewhere, catching this NPE here and returning is always safe. + return createTransferResult(n, in, leftWithOffset); + } + + Subsequence subsequence = Subsequence.getSubsequenceFromReceiver(je, atypeFactory); + + if (subsequence != null) { + String from = subsequence.from; + String to = subsequence.to; + String a = subsequence.array; + + JavaExpression leftOp = JavaExpression.fromNode(n.getLeftOperand()); + JavaExpression rightOp = JavaExpression.fromNode(n.getRightOperand()); + + if (rightOp.toString().equals(from)) { + LessThanAnnotatedTypeFactory lessThanAtypeFactory = + atypeFactory.getLessThanAnnotatedTypeFactory(); + AnnotationMirror lessThanType = + lessThanAtypeFactory + .getAnnotatedType(n.getLeftOperand().getTree()) + .getAnnotation(LessThan.class); + + if (lessThanType != null && lessThanAtypeFactory.isLessThan(lessThanType, to)) { + UBQualifier ltlA = UBQualifier.createUBQualifier(a, "0"); + leftWithOffset = leftWithOffset.glb(ltlA); + } else if (leftOp.toString().equals(to) + || (lessThanType != null + && lessThanAtypeFactory.isLessThanOrEqual(lessThanType, to))) { + // It's necessary to check if leftOp == to because LessThan doesn't + // infer that things are less than or equal to themselves. + UBQualifier ltelA = UBQualifier.createUBQualifier(a, "-1"); + leftWithOffset = leftWithOffset.glb(ltelA); + } } + } } + } + } + return createTransferResult(n, in, leftWithOffset); + } + + /** + * Computes a type of a sequence length access. This is case 18. + * + * @param n sequence length access node + */ + private @Nullable TransferResult visitLengthAccess( + Node n, TransferInput in, JavaExpression sequenceJe, Tree sequenceTree) { + if (sequenceTree == null) { + return null; + } + // Look up the SameLen type of the sequence. + AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(sequenceTree); + List sameLenSequences; + if (sameLenAnno == null) { + sameLenSequences = Collections.singletonList(sequenceJe.toString()); + } else { + sameLenSequences = + AnnotationUtils.getElementValueArray( + sameLenAnno, atypeFactory.sameLenValueElement, String.class); + String sequenceString = sequenceJe.toString(); + if (!sameLenSequences.contains(sequenceString)) { + sameLenSequences.add(sequenceString); + } + } - List offsets = Collections.nCopies(sameLenSequences.size(), "-1"); - - if (CFAbstractStore.canInsertJavaExpression(sequenceJe)) { - UBQualifier qualifier = UBQualifier.createUBQualifier(sameLenSequences, offsets); - UBQualifier previous = getUBQualifier(n, in); - return createTransferResult(n, in, qualifier.glb(previous)); - } + List offsets = Collections.nCopies(sameLenSequences.size(), "-1"); - return null; + if (CFAbstractStore.canInsertJavaExpression(sequenceJe)) { + UBQualifier qualifier = UBQualifier.createUBQualifier(sameLenSequences, offsets); + UBQualifier previous = getUBQualifier(n, in); + return createTransferResult(n, in, qualifier.glb(previous)); } - /** - * If n is an array length field access, then the type of a.length is the glb - * of @LTEqLengthOf("a") and the value of a.length in the store. This is case 19. - */ - @Override - public TransferResult visitFieldAccess( - FieldAccessNode n, TransferInput in) { - if (NodeUtils.isArrayLengthFieldAccess(n)) { - FieldAccess arrayLength = (FieldAccess) JavaExpression.fromNodeFieldAccess(n); - JavaExpression arrayJe = arrayLength.getReceiver(); - Tree arrayTree = n.getReceiver().getTree(); - TransferResult result = visitLengthAccess(n, in, arrayJe, arrayTree); - if (result != null) { - return result; - } - } - return super.visitFieldAccess(n, in); + return null; + } + + /** + * If n is an array length field access, then the type of a.length is the glb + * of @LTEqLengthOf("a") and the value of a.length in the store. This is case 19. + */ + @Override + public TransferResult visitFieldAccess( + FieldAccessNode n, TransferInput in) { + if (NodeUtils.isArrayLengthFieldAccess(n)) { + FieldAccess arrayLength = (FieldAccess) JavaExpression.fromNodeFieldAccess(n); + JavaExpression arrayJe = arrayLength.getReceiver(); + Tree arrayTree = n.getReceiver().getTree(); + TransferResult result = visitLengthAccess(n, in, arrayJe, arrayTree); + if (result != null) { + return result; + } } - - /** - * If n is a String.length() method invocation, then the type of s.length() is the glb - * of @LTEqLengthOf("s") and the value of s.length() in the store. This is case 20. - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - - if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(n)) { - JavaExpression stringLength = JavaExpression.fromNode(n); - if (stringLength instanceof MethodCall) { - JavaExpression receiverJe = ((MethodCall) stringLength).getReceiver(); - Tree receiverTree = n.getTarget().getReceiver().getTree(); - // receiverTree is null when the receiver is implicit "this". - if (receiverTree != null) { - TransferResult result = - visitLengthAccess(n, in, receiverJe, receiverTree); - if (result != null) { - return result; - } - } - } + return super.visitFieldAccess(n, in); + } + + /** + * If n is a String.length() method invocation, then the type of s.length() is the glb + * of @LTEqLengthOf("s") and the value of s.length() in the store. This is case 20. + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + + if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(n)) { + JavaExpression stringLength = JavaExpression.fromNode(n); + if (stringLength instanceof MethodCall) { + JavaExpression receiverJe = ((MethodCall) stringLength).getReceiver(); + Tree receiverTree = n.getTarget().getReceiver().getTree(); + // receiverTree is null when the receiver is implicit "this". + if (receiverTree != null) { + TransferResult result = + visitLengthAccess(n, in, receiverJe, receiverTree); + if (result != null) { + return result; + } } - return super.visitMethodInvocation(n, in); + } } - - /** - * Returns the UBQualifier for a node, with additional refinement useful specifically for - * integer addition, based on the information from subcheckers of the Index Checker. - * - * @param n the node - * @param in dataflow analysis transfer input - * @return the UBQualifier for {@code node} - */ - private UBQualifier getUBQualifierForAddition(Node n, TransferInput in) { - - // The method takes the greatest lower bound of the qualifier returned by - // getUBQualifier and a qualifier created from a SubstringIndexFor annotation, if such - // annotation is present and the index is known to be non-negative. - - UBQualifier ubQualifier = getUBQualifier(n, in); - Tree nodeTree = n.getTree(); - // Annotation from the Substring Index hierarchy - AnnotatedTypeMirror substringIndexType = - atypeFactory.getSubstringIndexAnnotatedTypeFactory().getAnnotatedType(nodeTree); - AnnotationMirror substringIndexAnno = - substringIndexType.getAnnotation(SubstringIndexFor.class); - // Annotation from the Lower bound hierarchy - AnnotatedTypeMirror lowerBoundType = - atypeFactory.getLowerBoundAnnotatedTypeFactory().getAnnotatedType(nodeTree); - // If the index has an SubstringIndexFor annotation and at the same time is non-negative, - // convert the SubstringIndexFor annotation to a upper bound qualifier. - if (substringIndexAnno != null - && (lowerBoundType.hasAnnotation(NonNegative.class) - || lowerBoundType.hasAnnotation(Positive.class))) { - UBQualifier substringIndexQualifier = - UBQualifier.createUBQualifier( - substringIndexAnno, (UpperBoundChecker) atypeFactory.getChecker()); - ubQualifier = ubQualifier.glb(substringIndexQualifier); - } - return ubQualifier; + return super.visitMethodInvocation(n, in); + } + + /** + * Returns the UBQualifier for a node, with additional refinement useful specifically for integer + * addition, based on the information from subcheckers of the Index Checker. + * + * @param n the node + * @param in dataflow analysis transfer input + * @return the UBQualifier for {@code node} + */ + private UBQualifier getUBQualifierForAddition(Node n, TransferInput in) { + + // The method takes the greatest lower bound of the qualifier returned by + // getUBQualifier and a qualifier created from a SubstringIndexFor annotation, if such + // annotation is present and the index is known to be non-negative. + + UBQualifier ubQualifier = getUBQualifier(n, in); + Tree nodeTree = n.getTree(); + // Annotation from the Substring Index hierarchy + AnnotatedTypeMirror substringIndexType = + atypeFactory.getSubstringIndexAnnotatedTypeFactory().getAnnotatedType(nodeTree); + AnnotationMirror substringIndexAnno = substringIndexType.getAnnotation(SubstringIndexFor.class); + // Annotation from the Lower bound hierarchy + AnnotatedTypeMirror lowerBoundType = + atypeFactory.getLowerBoundAnnotatedTypeFactory().getAnnotatedType(nodeTree); + // If the index has an SubstringIndexFor annotation and at the same time is non-negative, + // convert the SubstringIndexFor annotation to a upper bound qualifier. + if (substringIndexAnno != null + && (lowerBoundType.hasAnnotation(NonNegative.class) + || lowerBoundType.hasAnnotation(Positive.class))) { + UBQualifier substringIndexQualifier = + UBQualifier.createUBQualifier( + substringIndexAnno, (UpperBoundChecker) atypeFactory.getChecker()); + ubQualifier = ubQualifier.glb(substringIndexQualifier); } - - /** - * Returns the UBQualifier for node. It does this by finding a {@link CFValue} for node. First - * it checks the store in the transfer input. If one isn't there, the analysis is checked. If - * the UNKNOWN qualifier is returned, then the AnnotatedTypeMirror from the type factory is - * used. - * - * @param n node - * @param in transfer input - * @return the UBQualifier for node - */ - private UBQualifier getUBQualifier(Node n, TransferInput in) { - QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - JavaExpression je = JavaExpression.fromNode(n); - CFValue value = null; - if (CFAbstractStore.canInsertJavaExpression(je)) { - value = in.getRegularStore().getValue(je); - } - if (value == null) { - value = analysis.getValue(n); - } - UBQualifier qualifier = getUBQualifier(hierarchy, value); - if (qualifier.isUnknown()) { - // The qualifier from the store or analysis might be UNKNOWN if there was some error. - // For example, - // @LTLength("a") int i = 4; // error - // The type of i in the store is @UpperBoundUnknown, but the type of i as computed by - // the type factory is @LTLength("a"), so use that type. - CFValue valueFromFactory = getValueFromFactory(n.getTree(), n); - return getUBQualifier(hierarchy, valueFromFactory); - } - return qualifier; + return ubQualifier; + } + + /** + * Returns the UBQualifier for node. It does this by finding a {@link CFValue} for node. First it + * checks the store in the transfer input. If one isn't there, the analysis is checked. If the + * UNKNOWN qualifier is returned, then the AnnotatedTypeMirror from the type factory is used. + * + * @param n node + * @param in transfer input + * @return the UBQualifier for node + */ + private UBQualifier getUBQualifier(Node n, TransferInput in) { + QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + JavaExpression je = JavaExpression.fromNode(n); + CFValue value = null; + if (CFAbstractStore.canInsertJavaExpression(je)) { + value = in.getRegularStore().getValue(je); } - - private UBQualifier getUBQualifier(QualifierHierarchy hierarchy, CFValue value) { - if (value == null) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - AnnotationMirrorSet set = value.getAnnotations(); - AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(set, atypeFactory.UNKNOWN); - if (anno == null) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - return UBQualifier.createUBQualifier(anno, (UpperBoundChecker) atypeFactory.getChecker()); + if (value == null) { + value = analysis.getValue(n); } - - private TransferResult createTransferResult( - Node n, TransferInput in, UBQualifier qualifier) { - AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(qualifier); - CFValue value = analysis.createSingleAnnotationValue(newAnno, n.getType()); - return createTransferResult(value, in); + UBQualifier qualifier = getUBQualifier(hierarchy, value); + if (qualifier.isUnknown()) { + // The qualifier from the store or analysis might be UNKNOWN if there was some error. + // For example, + // @LTLength("a") int i = 4; // error + // The type of i in the store is @UpperBoundUnknown, but the type of i as computed by + // the type factory is @LTLength("a"), so use that type. + CFValue valueFromFactory = getValueFromFactory(n.getTree(), n); + return getUBQualifier(hierarchy, valueFromFactory); } + return qualifier; + } - @Override - public TransferResult visitCase( - CaseNode n, TransferInput in) { - TransferResult result = super.visitCase(n, in); - // Refines subtrahend in the switch expression - // TODO: This cannot be done in strengthenAnnotationOfEqualTo, because that does not provide - // transfer input. - List caseNodes = n.getCaseOperands(); - AssignmentNode assign = n.getSwitchOperand(); - Node switchNode = assign.getExpression(); - for (Node caseNode : caseNodes) { - refineSubtrahendWithOffset(switchNode, caseNode, false, in, result.getThenStore()); - } - return result; + private UBQualifier getUBQualifier(QualifierHierarchy hierarchy, CFValue value) { + if (value == null) { + return UpperBoundUnknownQualifier.UNKNOWN; } - - @Override - public TransferResult visitIntegerLiteral( - IntegerLiteralNode n, TransferInput pi) { - TransferResult result = super.visitIntegerLiteral(n, pi); - - int intValue = n.getValue(); - AnnotationMirror newAnno; - switch (intValue) { - case 0: - newAnno = atypeFactory.ZERO; - break; - case -1: - newAnno = atypeFactory.NEGATIVEONE; - break; - default: - return result; - } - CFValue c = new CFValue(analysis, AnnotationMirrorSet.singleton(newAnno), intTM); - return new RegularTransferResult<>(c, result.getRegularStore()); + AnnotationMirrorSet set = value.getAnnotations(); + AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(set, atypeFactory.UNKNOWN); + if (anno == null) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return UBQualifier.createUBQualifier(anno, (UpperBoundChecker) atypeFactory.getChecker()); + } + + private TransferResult createTransferResult( + Node n, TransferInput in, UBQualifier qualifier) { + AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(qualifier); + CFValue value = analysis.createSingleAnnotationValue(newAnno, n.getType()); + return createTransferResult(value, in); + } + + @Override + public TransferResult visitCase( + CaseNode n, TransferInput in) { + TransferResult result = super.visitCase(n, in); + // Refines subtrahend in the switch expression + // TODO: This cannot be done in strengthenAnnotationOfEqualTo, because that does not provide + // transfer input. + List caseNodes = n.getCaseOperands(); + AssignmentNode assign = n.getSwitchOperand(); + Node switchNode = assign.getExpression(); + for (Node caseNode : caseNodes) { + refineSubtrahendWithOffset(switchNode, caseNode, false, in, result.getThenStore()); + } + return result; + } + + @Override + public TransferResult visitIntegerLiteral( + IntegerLiteralNode n, TransferInput pi) { + TransferResult result = super.visitIntegerLiteral(n, pi); + + int intValue = n.getValue(); + AnnotationMirror newAnno; + switch (intValue) { + case 0: + newAnno = atypeFactory.ZERO; + break; + case -1: + newAnno = atypeFactory.NEGATIVEONE; + break; + default: + return result; } + CFValue c = new CFValue(analysis, AnnotationMirrorSet.singleton(newAnno), intTM); + return new RegularTransferResult<>(c, result.getRegularStore()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java index c04714bbc46..98a7aaf4b84 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java @@ -7,7 +7,11 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; - +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.Subsequence; import org.checkerframework.checker.index.qual.HasSubsequence; @@ -35,515 +39,485 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; - /** Warns about array accesses that could be too high. */ public class UpperBoundVisitor extends BaseTypeVisitor { - private static final @CompilerMessageKey String UPPER_BOUND = "array.access.unsafe.high"; - private static final @CompilerMessageKey String UPPER_BOUND_CONST = - "array.access.unsafe.high.constant"; - private static final @CompilerMessageKey String UPPER_BOUND_RANGE = - "array.access.unsafe.high.range"; - private static final @CompilerMessageKey String TO_NOT_LTEL = "to.not.ltel"; - private static final @CompilerMessageKey String NOT_FINAL = "not.final"; - private static final @CompilerMessageKey String HSS = "which.subsequence"; - - public UpperBoundVisitor(BaseTypeChecker checker) { - super(checker); + private static final @CompilerMessageKey String UPPER_BOUND = "array.access.unsafe.high"; + private static final @CompilerMessageKey String UPPER_BOUND_CONST = + "array.access.unsafe.high.constant"; + private static final @CompilerMessageKey String UPPER_BOUND_RANGE = + "array.access.unsafe.high.range"; + private static final @CompilerMessageKey String TO_NOT_LTEL = "to.not.ltel"; + private static final @CompilerMessageKey String NOT_FINAL = "not.final"; + private static final @CompilerMessageKey String HSS = "which.subsequence"; + + public UpperBoundVisitor(BaseTypeChecker checker) { + super(checker); + } + + /** + * When the visitor reaches an array access, it needs to check a couple of things. First, it + * checks if the index has been assigned a reasonable UpperBound type: only an index with type + * LTLengthOf(arr) is safe to access arr. If that fails, it checks if the access is still safe. To + * do so, it checks if the Value Checker knows the minimum length of arr by querying the Value + * Annotated Type Factory. If the minimum length of the array is known, the visitor can check if + * the index is less than that minimum length. If so, then the access is still safe. Otherwise, + * report a potential unsafe access. + */ + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void type) { + ExpressionTree indexTree = tree.getIndex(); + ExpressionTree arrTree = tree.getExpression(); + visitAccess(indexTree, arrTree); + return super.visitArrayAccess(tree, type); + } + + /** Warns about LTLengthOf annotations with arguments whose lengths do not match. */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + if (atypeFactory.areSameByClass(anno, LTLengthOf.class)) { + List args = tree.getArguments(); + if (args.size() == 2) { + // If offsets are provided, there must be the same number of them as there are + // arrays. + List sequences = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.ltLengthOfValueElement, String.class); + List offsets = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.ltLengthOfOffsetElement, String.class, Collections.emptyList()); + if (sequences.size() != offsets.size() && !offsets.isEmpty()) { + checker.reportError( + tree, "different.length.sequences.offsets", sequences.size(), offsets.size()); + return null; + } + } + } else if (atypeFactory.areSameByClass(anno, HasSubsequence.class)) { + // Check that the arguments to a HasSubsequence annotation are valid JavaExpressions, + // and issue an error if one of them is not. + + String seq = atypeFactory.hasSubsequenceSubsequenceValue(anno); + String from = atypeFactory.hasSubsequenceFromValue(anno); + String to = atypeFactory.hasSubsequenceToValue(anno); + + // check that each expression is parsable at the declaration of this class + ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); + checkEffectivelyFinalAndParsable(seq, enclosingClass, tree); + checkEffectivelyFinalAndParsable(from, enclosingClass, tree); + checkEffectivelyFinalAndParsable(to, enclosingClass, tree); } - - /** - * When the visitor reaches an array access, it needs to check a couple of things. First, it - * checks if the index has been assigned a reasonable UpperBound type: only an index with type - * LTLengthOf(arr) is safe to access arr. If that fails, it checks if the access is still safe. - * To do so, it checks if the Value Checker knows the minimum length of arr by querying the - * Value Annotated Type Factory. If the minimum length of the array is known, the visitor can - * check if the index is less than that minimum length. If so, then the access is still safe. - * Otherwise, report a potential unsafe access. - */ - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void type) { - ExpressionTree indexTree = tree.getIndex(); - ExpressionTree arrTree = tree.getExpression(); - visitAccess(indexTree, arrTree); - return super.visitArrayAccess(tree, type); + return super.visitAnnotation(tree, p); + } + + /** + * Reports an error if the Java expression named by s is not effectively final when parsed at the + * declaration of the given class. + * + * @param s a Java expression + * @param classTree the expression is parsed with respect to this class + * @param whereToReportError the tree at which to possibly report an error + */ + private void checkEffectivelyFinalAndParsable( + String s, ClassTree classTree, Tree whereToReportError) { + JavaExpression je; + try { + je = + StringToJavaExpression.atTypeDecl( + s, TreeUtils.elementFromDeclaration(classTree), checker); + } catch (JavaExpressionParseException e) { + checker.report(whereToReportError, e.getDiagMessage()); + return; } - - /** Warns about LTLengthOf annotations with arguments whose lengths do not match. */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - if (atypeFactory.areSameByClass(anno, LTLengthOf.class)) { - List args = tree.getArguments(); - if (args.size() == 2) { - // If offsets are provided, there must be the same number of them as there are - // arrays. - List sequences = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.ltLengthOfValueElement, String.class); - List offsets = - AnnotationUtils.getElementValueArray( - anno, - atypeFactory.ltLengthOfOffsetElement, - String.class, - Collections.emptyList()); - if (sequences.size() != offsets.size() && !offsets.isEmpty()) { - checker.reportError( - tree, - "different.length.sequences.offsets", - sequences.size(), - offsets.size()); - return null; - } - } - } else if (atypeFactory.areSameByClass(anno, HasSubsequence.class)) { - // Check that the arguments to a HasSubsequence annotation are valid JavaExpressions, - // and issue an error if one of them is not. - - String seq = atypeFactory.hasSubsequenceSubsequenceValue(anno); - String from = atypeFactory.hasSubsequenceFromValue(anno); - String to = atypeFactory.hasSubsequenceToValue(anno); - - // check that each expression is parsable at the declaration of this class - ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); - checkEffectivelyFinalAndParsable(seq, enclosingClass, tree); - checkEffectivelyFinalAndParsable(from, enclosingClass, tree); - checkEffectivelyFinalAndParsable(to, enclosingClass, tree); - } - return super.visitAnnotation(tree, p); + Element element = null; + if (je instanceof LocalVariable) { + element = ((LocalVariable) je).getElement(); + } else if (je instanceof FieldAccess) { + element = ((FieldAccess) je).getField(); + } else if (je instanceof ThisReference || je instanceof ValueLiteral) { + return; } - - /** - * Reports an error if the Java expression named by s is not effectively final when parsed at - * the declaration of the given class. - * - * @param s a Java expression - * @param classTree the expression is parsed with respect to this class - * @param whereToReportError the tree at which to possibly report an error - */ - private void checkEffectivelyFinalAndParsable( - String s, ClassTree classTree, Tree whereToReportError) { - JavaExpression je; - try { - je = - StringToJavaExpression.atTypeDecl( - s, TreeUtils.elementFromDeclaration(classTree), checker); - } catch (JavaExpressionParseException e) { - checker.report(whereToReportError, e.getDiagMessage()); - return; - } - Element element = null; - if (je instanceof LocalVariable) { - element = ((LocalVariable) je).getElement(); - } else if (je instanceof FieldAccess) { - element = ((FieldAccess) je).getField(); - } else if (je instanceof ThisReference || je instanceof ValueLiteral) { - return; - } - if (element == null || !ElementUtils.isEffectivelyFinal(element)) { - checker.reportError(whereToReportError, NOT_FINAL, je); - } + if (element == null || !ElementUtils.isEffectivelyFinal(element)) { + checker.reportError(whereToReportError, NOT_FINAL, je); } - - /** - * Checks if this array access is legal. Uses the common assignment check and a simple MinLen - * check of its own. The MinLen check is needed because the common assignment check always - * returns false when the upper bound qualifier is @UpperBoundUnknown. - * - * @param indexTree the array index - * @param arrTree the array - */ - private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) { - - String arrName = JavaExpression.fromTree(arrTree).toString(); - LessThanLengthOf lhsQual = (LessThanLengthOf) UBQualifier.createUBQualifier(arrName, "0"); - if (relaxedCommonAssignmentCheck(lhsQual, indexTree) || checkMinLen(indexTree, arrTree)) { - return; - } // else issue errors. - - // We can issue three different errors: - // 1. If the index is a compile-time constant, issue an error that describes the array type. - // 2. If the index is a compile-time range and has no upperbound qualifier, - // issue an error that names the upperbound of the range and the array's type. - // 3. If neither of the above, issue an error that names the upper bound type. - - AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(indexTree); - UBQualifier qualifier = - UBQualifier.createUBQualifier( - indexType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - ValueAnnotatedTypeFactory valueFactory = atypeFactory.getValueAnnotatedTypeFactory(); - Long valMax = ValueCheckerUtils.getMaxValue(indexTree, valueFactory); - - if (ValueCheckerUtils.getExactValue(indexTree, valueFactory) != null) { - // Note that valMax is equal to the exact value in this case. - checker.reportError( - indexTree, - UPPER_BOUND_CONST, - valMax, - valueFactory.getAnnotatedType(arrTree).toString(), - valMax + 1, - valMax + 1); - } else if (valMax != null && qualifier.isUnknown() && valMax != Integer.MAX_VALUE) { - - checker.reportError( - indexTree, - UPPER_BOUND_RANGE, - valueFactory.getAnnotatedType(indexTree).toString(), - valueFactory.getAnnotatedType(arrTree).toString(), - arrName, - arrName, - valMax + 1); - } else { - checker.reportError( - indexTree, UPPER_BOUND, indexType.toString(), arrName, arrName, arrName); - } + } + + /** + * Checks if this array access is legal. Uses the common assignment check and a simple MinLen + * check of its own. The MinLen check is needed because the common assignment check always returns + * false when the upper bound qualifier is @UpperBoundUnknown. + * + * @param indexTree the array index + * @param arrTree the array + */ + private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) { + + String arrName = JavaExpression.fromTree(arrTree).toString(); + LessThanLengthOf lhsQual = (LessThanLengthOf) UBQualifier.createUBQualifier(arrName, "0"); + if (relaxedCommonAssignmentCheck(lhsQual, indexTree) || checkMinLen(indexTree, arrTree)) { + return; + } // else issue errors. + + // We can issue three different errors: + // 1. If the index is a compile-time constant, issue an error that describes the array type. + // 2. If the index is a compile-time range and has no upperbound qualifier, + // issue an error that names the upperbound of the range and the array's type. + // 3. If neither of the above, issue an error that names the upper bound type. + + AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(indexTree); + UBQualifier qualifier = + UBQualifier.createUBQualifier(indexType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + ValueAnnotatedTypeFactory valueFactory = atypeFactory.getValueAnnotatedTypeFactory(); + Long valMax = ValueCheckerUtils.getMaxValue(indexTree, valueFactory); + + if (ValueCheckerUtils.getExactValue(indexTree, valueFactory) != null) { + // Note that valMax is equal to the exact value in this case. + checker.reportError( + indexTree, + UPPER_BOUND_CONST, + valMax, + valueFactory.getAnnotatedType(arrTree).toString(), + valMax + 1, + valMax + 1); + } else if (valMax != null && qualifier.isUnknown() && valMax != Integer.MAX_VALUE) { + + checker.reportError( + indexTree, + UPPER_BOUND_RANGE, + valueFactory.getAnnotatedType(indexTree).toString(), + valueFactory.getAnnotatedType(arrTree).toString(), + arrName, + arrName, + valMax + 1); + } else { + checker.reportError(indexTree, UPPER_BOUND, indexType.toString(), arrName, arrName, arrName); } - - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - boolean result = true; - - // check that when an assignment to a variable b declared as @HasSubsequence(a, from, to) - // occurs, to <= a.length, i.e. to is @LTEqLengthOf(a). - - Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); - if (subSeq != null) { - AnnotationMirror anm; - try { - anm = - atypeFactory.getAnnotationMirrorFromJavaExpressionString( - subSeq.to, varTree, getCurrentPath()); - } catch (JavaExpressionParseException e) { - anm = null; - } - - boolean ltelCheckFailed = true; - if (anm != null) { - UBQualifier qual = UBQualifier.createUBQualifier(anm, (UpperBoundChecker) checker); - ltelCheckFailed = !qual.isLessThanOrEqualTo(subSeq.array); - } - - if (ltelCheckFailed) { - // issue an error - checker.reportError( - valueTree, - TO_NOT_LTEL, - subSeq.to, - subSeq.array, - anm == null ? "@UpperBoundUnknown" : anm, - subSeq.array, - subSeq.array, - subSeq.array); - result = false; - } else { - checker.reportWarning( - valueTree, - HSS, - subSeq.array, - subSeq.from, - subSeq.from, - subSeq.to, - subSeq.to, - subSeq.array, - subSeq.array); - } - } - - result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; - return result; + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + boolean result = true; + + // check that when an assignment to a variable b declared as @HasSubsequence(a, from, to) + // occurs, to <= a.length, i.e. to is @LTEqLengthOf(a). + + Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); + if (subSeq != null) { + AnnotationMirror anm; + try { + anm = + atypeFactory.getAnnotationMirrorFromJavaExpressionString( + subSeq.to, varTree, getCurrentPath()); + } catch (JavaExpressionParseException e) { + anm = null; + } + + boolean ltelCheckFailed = true; + if (anm != null) { + UBQualifier qual = UBQualifier.createUBQualifier(anm, (UpperBoundChecker) checker); + ltelCheckFailed = !qual.isLessThanOrEqualTo(subSeq.array); + } + + if (ltelCheckFailed) { + // issue an error + checker.reportError( + valueTree, + TO_NOT_LTEL, + subSeq.to, + subSeq.array, + anm == null ? "@UpperBoundUnknown" : anm, + subSeq.array, + subSeq.array, + subSeq.array); + result = false; + } else { + checker.reportWarning( + valueTree, + HSS, + subSeq.array, + subSeq.from, + subSeq.from, + subSeq.to, + subSeq.to, + subSeq.array, + subSeq.array); + } } - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree); - commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); - boolean result = true; - String diagnosticMessage = ""; - if (!relaxedCommonAssignment(varType, valueTree)) { - commonAssignmentCheckEndDiagnostic( - "relaxedCommonAssignment did not succeed, now must call super", - varType, - valueType, - valueTree); - result = super.commonAssignmentCheck(varType, valueTree, errorKey, extraArgs); - if (!result && showchecks) { - diagnosticMessage = "relaxedCommonAssignment()=>false and super()=>false"; - } - } - commonAssignmentCheckEndDiagnostic( - result, diagnosticMessage, varType, valueType, valueTree); - return result; + result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; + return result; + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree); + commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); + boolean result = true; + String diagnosticMessage = ""; + if (!relaxedCommonAssignment(varType, valueTree)) { + commonAssignmentCheckEndDiagnostic( + "relaxedCommonAssignment did not succeed, now must call super", + varType, + valueType, + valueTree); + result = super.commonAssignmentCheck(varType, valueTree, errorKey, extraArgs); + if (!result && showchecks) { + diagnosticMessage = "relaxedCommonAssignment()=>false and super()=>false"; + } } - - /** - * Returns whether the assignment is legal based on the relaxed assignment rules. - * - *

The relaxed assignment rules are the following: Assuming the varType (left-hand side) is - * less than the length of some array given some offset - * - *

1. If both the offset and the value expression (rhs) are ints known at compile time, and - * if the min length of the array is greater than offset + value, then the assignment is legal. - * (This method returns true.) - * - *

2. If the value expression (rhs) is less than the length of an array that is the same - * length as the array in the varType, and if the offsets are equal, then the assignment is - * legal. (This method returns true.) - * - *

3. Otherwise the assignment is only legal if the usual assignment rules are true, so this - * method returns false. - * - *

If the varType is less than the length of multiple arrays, then this method only returns - * true if the relaxed rules above apply for each array. - * - *

If the varType is an array type and the value expression is an array initializer, then the - * above rules are applied for expression in the initializer where the varType is the component - * type of the array. - * - * @param varType the type of the left-hand side (the variable in the assignment) - * @param valueExp the right-hand side (the expression in the assignment) - * @return true if the assignment is legal based on special Upper Bound rules - */ - private boolean relaxedCommonAssignment(AnnotatedTypeMirror varType, ExpressionTree valueExp) { - if (valueExp.getKind() == Tree.Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) { - List expressions = - ((NewArrayTree) valueExp).getInitializers(); - if (expressions == null || expressions.isEmpty()) { - return false; - } - // The qualifier we need for an array is in the component type, not varType. - AnnotatedTypeMirror componentType = ((AnnotatedArrayType) varType).getComponentType(); - UBQualifier qualifier = - UBQualifier.createUBQualifier( - componentType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - if (!qualifier.isLessThanLengthQualifier()) { - return false; - } - for (ExpressionTree expressionTree : expressions) { - if (!relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, expressionTree)) { - return false; - } - } - return true; + commonAssignmentCheckEndDiagnostic(result, diagnosticMessage, varType, valueType, valueTree); + return result; + } + + /** + * Returns whether the assignment is legal based on the relaxed assignment rules. + * + *

The relaxed assignment rules are the following: Assuming the varType (left-hand side) is + * less than the length of some array given some offset + * + *

1. If both the offset and the value expression (rhs) are ints known at compile time, and if + * the min length of the array is greater than offset + value, then the assignment is legal. (This + * method returns true.) + * + *

2. If the value expression (rhs) is less than the length of an array that is the same length + * as the array in the varType, and if the offsets are equal, then the assignment is legal. (This + * method returns true.) + * + *

3. Otherwise the assignment is only legal if the usual assignment rules are true, so this + * method returns false. + * + *

If the varType is less than the length of multiple arrays, then this method only returns + * true if the relaxed rules above apply for each array. + * + *

If the varType is an array type and the value expression is an array initializer, then the + * above rules are applied for expression in the initializer where the varType is the component + * type of the array. + * + * @param varType the type of the left-hand side (the variable in the assignment) + * @param valueExp the right-hand side (the expression in the assignment) + * @return true if the assignment is legal based on special Upper Bound rules + */ + private boolean relaxedCommonAssignment(AnnotatedTypeMirror varType, ExpressionTree valueExp) { + if (valueExp.getKind() == Tree.Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) { + List expressions = ((NewArrayTree) valueExp).getInitializers(); + if (expressions == null || expressions.isEmpty()) { + return false; + } + // The qualifier we need for an array is in the component type, not varType. + AnnotatedTypeMirror componentType = ((AnnotatedArrayType) varType).getComponentType(); + UBQualifier qualifier = + UBQualifier.createUBQualifier( + componentType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + if (!qualifier.isLessThanLengthQualifier()) { + return false; + } + for (ExpressionTree expressionTree : expressions) { + if (!relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, expressionTree)) { + return false; } - - UBQualifier qualifier = - UBQualifier.createUBQualifier( - varType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - return qualifier.isLessThanLengthQualifier() - && relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, valueExp); + } + return true; } - /** - * Fetches a receiver and an offset from a String using the passed type factory. Returns null if - * there is a parse exception. This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. - * - *

This is useful for expressions like "n+1", for which {@link #parseJavaExpressionString} - * returns null because the whole expression is not a receiver. - */ - static @Nullable IPair getExpressionAndOffsetFromJavaExpressionString( - String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { - - IPair p = AnnotatedTypeFactory.getExpressionAndOffset(s); - - JavaExpression je = parseJavaExpressionString(p.first, atypeFactory, currentPath); - if (je == null) { - return null; - } - return IPair.of(je, p.second); + UBQualifier qualifier = + UBQualifier.createUBQualifier(varType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + return qualifier.isLessThanLengthQualifier() + && relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, valueExp); + } + + /** + * Fetches a receiver and an offset from a String using the passed type factory. Returns null if + * there is a parse exception. This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. + * + *

This is useful for expressions like "n+1", for which {@link #parseJavaExpressionString} + * returns null because the whole expression is not a receiver. + */ + static @Nullable IPair getExpressionAndOffsetFromJavaExpressionString( + String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { + + IPair p = AnnotatedTypeFactory.getExpressionAndOffset(s); + + JavaExpression je = parseJavaExpressionString(p.first, atypeFactory, currentPath); + if (je == null) { + return null; } - - /** - * Fetches a receiver from a String using the passed type factory. Returns null if there is a - * parse exception -- that is, if the string does not represent an expression for a - * JavaExpression. For example, the expression "n+1" does not represent a JavaExpression. - * - *

This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. - */ - static @Nullable JavaExpression parseJavaExpressionString( - String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { - JavaExpression result; - try { - result = atypeFactory.parseJavaExpressionString(s, currentPath); - } catch (JavaExpressionParseException e) { - result = null; - } - return result; + return IPair.of(je, p.second); + } + + /** + * Fetches a receiver from a String using the passed type factory. Returns null if there is a + * parse exception -- that is, if the string does not represent an expression for a + * JavaExpression. For example, the expression "n+1" does not represent a JavaExpression. + * + *

This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. + */ + static @Nullable JavaExpression parseJavaExpressionString( + String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { + JavaExpression result; + try { + result = atypeFactory.parseJavaExpressionString(s, currentPath); + } catch (JavaExpressionParseException e) { + result = null; } - - /* - * Queries the Value Checker to determine if the maximum possible value of indexTree - * is less than the minimum possible length of arrTree, and returns true if so. - */ - private boolean checkMinLen(ExpressionTree indexTree, ExpressionTree arrTree) { - int minLen = - ValueCheckerUtils.getMinLen(arrTree, atypeFactory.getValueAnnotatedTypeFactory()); - Long valMax = - ValueCheckerUtils.getMaxValue( - indexTree, atypeFactory.getValueAnnotatedTypeFactory()); - return valMax != null && valMax < minLen; + return result; + } + + /* + * Queries the Value Checker to determine if the maximum possible value of indexTree + * is less than the minimum possible length of arrTree, and returns true if so. + */ + private boolean checkMinLen(ExpressionTree indexTree, ExpressionTree arrTree) { + int minLen = ValueCheckerUtils.getMinLen(arrTree, atypeFactory.getValueAnnotatedTypeFactory()); + Long valMax = + ValueCheckerUtils.getMaxValue(indexTree, atypeFactory.getValueAnnotatedTypeFactory()); + return valMax != null && valMax < minLen; + } + + /** + * Implements the actual check for the relaxed common assignment check. For what is permitted, see + * {@link #relaxedCommonAssignment}. + * + * @param varLtlQual the variable qualifier (the left-hand side of the assignment) + * @param valueExp the expression (the right-hand side of the assignment) + * @return true if the assignment is legal: varLtlQual is a supertype of the type of valueExp + */ + private boolean relaxedCommonAssignmentCheck( + LessThanLengthOf varLtlQual, ExpressionTree valueExp) { + + AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(valueExp); + UBQualifier expQual = + UBQualifier.createUBQualifier(expType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + + UBQualifier lessThanQual = atypeFactory.fromLessThan(valueExp, getCurrentPath()); + if (lessThanQual != null) { + expQual = expQual.glb(lessThanQual); } - /** - * Implements the actual check for the relaxed common assignment check. For what is permitted, - * see {@link #relaxedCommonAssignment}. - * - * @param varLtlQual the variable qualifier (the left-hand side of the assignment) - * @param valueExp the expression (the right-hand side of the assignment) - * @return true if the assignment is legal: varLtlQual is a supertype of the type of valueExp - */ - private boolean relaxedCommonAssignmentCheck( - LessThanLengthOf varLtlQual, ExpressionTree valueExp) { - - AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(valueExp); - UBQualifier expQual = - UBQualifier.createUBQualifier( - expType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - - UBQualifier lessThanQual = atypeFactory.fromLessThan(valueExp, getCurrentPath()); - if (lessThanQual != null) { - expQual = expQual.glb(lessThanQual); - } - - UBQualifier lessThanOrEqualQual = - atypeFactory.fromLessThanOrEqual(valueExp, getCurrentPath()); - if (lessThanOrEqualQual != null) { - expQual = expQual.glb(lessThanOrEqualQual); - } - if (expQual.isSubtype(varLtlQual)) { - return true; - } + UBQualifier lessThanOrEqualQual = atypeFactory.fromLessThanOrEqual(valueExp, getCurrentPath()); + if (lessThanOrEqualQual != null) { + expQual = expQual.glb(lessThanOrEqualQual); + } + if (expQual.isSubtype(varLtlQual)) { + return true; + } - // Take advantage of information available on a HasSubsequence(a, from, to) annotation - // on the lhs qualifier (varLtlQual): - // this allows us to show that iff varLtlQual includes LTL(b), b has HSS, and expQual - // includes LTL(a, -from), then the LTL(b) can be removed from varLtlQual. + // Take advantage of information available on a HasSubsequence(a, from, to) annotation + // on the lhs qualifier (varLtlQual): + // this allows us to show that iff varLtlQual includes LTL(b), b has HSS, and expQual + // includes LTL(a, -from), then the LTL(b) can be removed from varLtlQual. - UBQualifier newLHS = processSubsequenceForLHS(varLtlQual, expQual); - if (newLHS.isUnknown()) { - return true; - } else { - varLtlQual = (LessThanLengthOf) newLHS; - } + UBQualifier newLHS = processSubsequenceForLHS(varLtlQual, expQual); + if (newLHS.isUnknown()) { + return true; + } else { + varLtlQual = (LessThanLengthOf) newLHS; + } - Long value = - ValueCheckerUtils.getMaxValue( - valueExp, atypeFactory.getValueAnnotatedTypeFactory()); + Long value = + ValueCheckerUtils.getMaxValue(valueExp, atypeFactory.getValueAnnotatedTypeFactory()); - if (value == null && !expQual.isLessThanLengthQualifier()) { - return false; - } + if (value == null && !expQual.isLessThanLengthQualifier()) { + return false; + } - SameLenAnnotatedTypeFactory sameLenFactory = atypeFactory.getSameLenAnnotatedTypeFactory(); - ValueAnnotatedTypeFactory valueAnnotatedTypeFactory = - atypeFactory.getValueAnnotatedTypeFactory(); - checkloop: - for (String sequenceName : varLtlQual.getSequences()) { - - List sameLenSequences = - sameLenFactory.getSameLensFromString(sequenceName, valueExp, getCurrentPath()); - if (testSameLen(expQual, varLtlQual, sameLenSequences, sequenceName)) { - continue; - } - - int minlen = - valueAnnotatedTypeFactory.getMinLenFromString( - sequenceName, valueExp, getCurrentPath()); - if (testMinLen(value, minlen, sequenceName, varLtlQual)) { - continue; - } - for (String sequence : sameLenSequences) { - int minlenSL = - valueAnnotatedTypeFactory.getMinLenFromString( - sequence, valueExp, getCurrentPath()); - if (testMinLen(value, minlenSL, sequenceName, varLtlQual)) { - continue checkloop; - } - } - - return false; + SameLenAnnotatedTypeFactory sameLenFactory = atypeFactory.getSameLenAnnotatedTypeFactory(); + ValueAnnotatedTypeFactory valueAnnotatedTypeFactory = + atypeFactory.getValueAnnotatedTypeFactory(); + checkloop: + for (String sequenceName : varLtlQual.getSequences()) { + + List sameLenSequences = + sameLenFactory.getSameLensFromString(sequenceName, valueExp, getCurrentPath()); + if (testSameLen(expQual, varLtlQual, sameLenSequences, sequenceName)) { + continue; + } + + int minlen = + valueAnnotatedTypeFactory.getMinLenFromString(sequenceName, valueExp, getCurrentPath()); + if (testMinLen(value, minlen, sequenceName, varLtlQual)) { + continue; + } + for (String sequence : sameLenSequences) { + int minlenSL = + valueAnnotatedTypeFactory.getMinLenFromString(sequence, valueExp, getCurrentPath()); + if (testMinLen(value, minlenSL, sequenceName, varLtlQual)) { + continue checkloop; } + } - return true; + return false; } - /* Returns the new value of the left hand side after processing the arrays named in the lhs. - * Iff varLtlQual includes LTL(lhsSeq), - * lhsSeq has HSS, and expQual includes LTL(a, -from), then the LTL(lhsSeq) will be removed from varLtlQual - */ - private UBQualifier processSubsequenceForLHS(LessThanLengthOf varLtlQual, UBQualifier expQual) { - UBQualifier newLHS = varLtlQual; - for (String lhsSeq : varLtlQual.getSequences()) { - // check is lhsSeq is an actual LTL - if (varLtlQual.hasSequenceWithOffset(lhsSeq, 0)) { - - JavaExpression lhsSeqExpr = - parseJavaExpressionString(lhsSeq, atypeFactory, getCurrentPath()); - Subsequence subSeq = - Subsequence.getSubsequenceFromReceiver(lhsSeqExpr, atypeFactory); - - if (subSeq != null) { - String from = subSeq.from; - String a = subSeq.array; - if (expQual.hasSequenceWithOffset(a, Subsequence.negateString(from))) { - // This cast is safe because LTLs cannot contain duplicates. - // Note that this updates newLHS on each iteration from its old value, - // so even if there are multiple HSS arrays the result will be correct. - newLHS = ((LessThanLengthOf) newLHS).removeOffset(lhsSeq, 0); - } - } - } - } - return newLHS; - } + return true; + } - /** - * Tests whether replacing any of the arrays in sameLenArrays with arrayName makes expQual - * equivalent to varQual. - */ - private boolean testSameLen( - UBQualifier expQual, - LessThanLengthOf varQual, - List sameLenArrays, - String arrayName) { - - if (!expQual.isLessThanLengthQualifier()) { - return false; - } + /* Returns the new value of the left hand side after processing the arrays named in the lhs. + * Iff varLtlQual includes LTL(lhsSeq), + * lhsSeq has HSS, and expQual includes LTL(a, -from), then the LTL(lhsSeq) will be removed from varLtlQual + */ + private UBQualifier processSubsequenceForLHS(LessThanLengthOf varLtlQual, UBQualifier expQual) { + UBQualifier newLHS = varLtlQual; + for (String lhsSeq : varLtlQual.getSequences()) { + // check is lhsSeq is an actual LTL + if (varLtlQual.hasSequenceWithOffset(lhsSeq, 0)) { - for (String sameLenArrayName : sameLenArrays) { - // Check whether replacing the value for any of the current type's offset results - // in the type we're trying to match. - if (varQual.isValidReplacement( - arrayName, sameLenArrayName, (LessThanLengthOf) expQual)) { - return true; - } + JavaExpression lhsSeqExpr = + parseJavaExpressionString(lhsSeq, atypeFactory, getCurrentPath()); + Subsequence subSeq = Subsequence.getSubsequenceFromReceiver(lhsSeqExpr, atypeFactory); + + if (subSeq != null) { + String from = subSeq.from; + String a = subSeq.array; + if (expQual.hasSequenceWithOffset(a, Subsequence.negateString(from))) { + // This cast is safe because LTLs cannot contain duplicates. + // Note that this updates newLHS on each iteration from its old value, + // so even if there are multiple HSS arrays the result will be correct. + newLHS = ((LessThanLengthOf) newLHS).removeOffset(lhsSeq, 0); + } } - return false; + } + } + return newLHS; + } + + /** + * Tests whether replacing any of the arrays in sameLenArrays with arrayName makes expQual + * equivalent to varQual. + */ + private boolean testSameLen( + UBQualifier expQual, LessThanLengthOf varQual, List sameLenArrays, String arrayName) { + + if (!expQual.isLessThanLengthQualifier()) { + return false; } - /** - * Tests a constant value (value) against the minlen (minlens) of an array (arrayName) with a - * qualifier (varQual). - */ - private boolean testMinLen(Long value, int minLen, String arrayName, LessThanLengthOf varQual) { - if (value == null) { - return false; - } - return varQual.isValuePlusOffsetLessThanMinLen(arrayName, value, minLen); + for (String sameLenArrayName : sameLenArrays) { + // Check whether replacing the value for any of the current type's offset results + // in the type we're trying to match. + if (varQual.isValidReplacement(arrayName, sameLenArrayName, (LessThanLengthOf) expQual)) { + return true; + } + } + return false; + } + + /** + * Tests a constant value (value) against the minlen (minlens) of an array (arrayName) with a + * qualifier (varQual). + */ + private boolean testMinLen(Long value, int minLen, String arrayName, LessThanLengthOf varQual) { + if (value == null) { + return false; } + return varQual.isValuePlusOffsetLessThanMinLen(arrayName, value, minLen); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java index 1c87a27f488..de54b499895 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java @@ -1,49 +1,48 @@ package org.checkerframework.checker.initialization; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.type.TypeMirror; - /** * The analysis class for the initialization type system (serves as factory for the transfer * function, stores, and abstract values. */ public class InitializationAnalysis - extends CFAbstractAnalysis { - - /** - * Creates a new {@code InitializationAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - protected InitializationAnalysis( - BaseTypeChecker checker, InitializationParentAnnotatedTypeFactory factory) { - super(checker, factory); - } - - @Override - public InitializationStore createEmptyStore(boolean sequentialSemantics) { - return new InitializationStore(this, sequentialSemantics); - } - - @Override - public InitializationStore createCopiedStore(InitializationStore s) { - return new InitializationStore(s); - } - - @Override - public @Nullable CFValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - return defaultCreateAbstractValue(this, annotations, underlyingType); - } - - @Override - public InitializationParentAnnotatedTypeFactory getTypeFactory() { - return (InitializationParentAnnotatedTypeFactory) super.getTypeFactory(); - } + extends CFAbstractAnalysis { + + /** + * Creates a new {@code InitializationAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + protected InitializationAnalysis( + BaseTypeChecker checker, InitializationParentAnnotatedTypeFactory factory) { + super(checker, factory); + } + + @Override + public InitializationStore createEmptyStore(boolean sequentialSemantics) { + return new InitializationStore(this, sequentialSemantics); + } + + @Override + public InitializationStore createCopiedStore(InitializationStore s) { + return new InitializationStore(s); + } + + @Override + public @Nullable CFValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + return defaultCreateAbstractValue(this, annotations, underlyingType); + } + + @Override + public InitializationParentAnnotatedTypeFactory getTypeFactory() { + return (InitializationParentAnnotatedTypeFactory) super.getTypeFactory(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java index 13b1c96ee37..2d2dbe2e9ce 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java @@ -7,7 +7,13 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; - +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -33,15 +39,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.VariableElement; - /** * The annotated type factory for the freedom-before-commitment type system. When using the * freedom-before-commitment type system as a subchecker, you must ensure that the parent checker @@ -49,250 +46,244 @@ */ public class InitializationAnnotatedTypeFactory extends InitializationParentAnnotatedTypeFactory { - /** - * Create a new InitializationAnnotatedTypeFactory. - * - * @param checker the checker to which the new type factory belongs - */ - public InitializationAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + /** + * Create a new InitializationAnnotatedTypeFactory. + * + * @param checker the checker to which the new type factory belongs + */ + public InitializationAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - public InitializationChecker getChecker() { - return (InitializationChecker) super.getChecker(); - } + @Override + public InitializationChecker getChecker() { + return (InitializationChecker) super.getChecker(); + } - /** - * Gets the factory of the {@link InitializationFieldAccessSubchecker}, whose flow-analysis - * results we reuse to avoid performing the same flow analysis twice. - * - *

If type checking has not yet started, the subcheckers are uninitialized, and this returns - * {@code null}. More concretely, this method only returns a non-null value after {@link - * SourceChecker#initChecker()} has been called on all subcheckers. Since the flow analysis is - * initialized in {@link AnnotatedTypeFactory#postInit()}, and the type factory is created after - * all subcheckers have been initialized, this method will always return a non-null value unless - * a subclass attempts to use it for some purpose other than accessing the flow analysis. - * - * @return the factory of the {@link InitializationFieldAccessSubchecker}, or {@code null} if - * not yet initialized - * @see #createFlowAnalysis() - * @see #performFlowAnalysis(ClassTree) - * @see #getRegularExitStore(Tree) - * @see #getExceptionalExitStore(Tree) - * @see #getReturnStatementStores(MethodTree) - */ - protected @Nullable InitializationFieldAccessAnnotatedTypeFactory getFieldAccessFactory() { - InitializationChecker checker = getChecker(); - BaseTypeChecker targetChecker = checker.getSubchecker(checker.getTargetCheckerClass()); - return targetChecker.getTypeFactoryOfSubcheckerOrNull( - InitializationFieldAccessSubchecker.class); - } + /** + * Gets the factory of the {@link InitializationFieldAccessSubchecker}, whose flow-analysis + * results we reuse to avoid performing the same flow analysis twice. + * + *

If type checking has not yet started, the subcheckers are uninitialized, and this returns + * {@code null}. More concretely, this method only returns a non-null value after {@link + * SourceChecker#initChecker()} has been called on all subcheckers. Since the flow analysis is + * initialized in {@link AnnotatedTypeFactory#postInit()}, and the type factory is created after + * all subcheckers have been initialized, this method will always return a non-null value unless a + * subclass attempts to use it for some purpose other than accessing the flow analysis. + * + * @return the factory of the {@link InitializationFieldAccessSubchecker}, or {@code null} if not + * yet initialized + * @see #createFlowAnalysis() + * @see #performFlowAnalysis(ClassTree) + * @see #getRegularExitStore(Tree) + * @see #getExceptionalExitStore(Tree) + * @see #getReturnStatementStores(MethodTree) + */ + protected @Nullable InitializationFieldAccessAnnotatedTypeFactory getFieldAccessFactory() { + InitializationChecker checker = getChecker(); + BaseTypeChecker targetChecker = checker.getSubchecker(checker.getTargetCheckerClass()); + return targetChecker.getTypeFactoryOfSubcheckerOrNull( + InitializationFieldAccessSubchecker.class); + } - @Override - protected InitializationAnalysis createFlowAnalysis() { - return getFieldAccessFactory().getAnalysis(); - } + @Override + protected InitializationAnalysis createFlowAnalysis() { + return getFieldAccessFactory().getAnalysis(); + } - @Override - protected void performFlowAnalysis(ClassTree classTree) { - flowResult = getFieldAccessFactory().getFlowResult(); - } + @Override + protected void performFlowAnalysis(ClassTree classTree) { + flowResult = getFieldAccessFactory().getFlowResult(); + } - @Override - public InitializationStore getRegularExitStore(Tree tree) { - return getFieldAccessFactory().getRegularExitStore(tree); - } + @Override + public InitializationStore getRegularExitStore(Tree tree) { + return getFieldAccessFactory().getRegularExitStore(tree); + } - @Override - public InitializationStore getExceptionalExitStore(Tree tree) { - return getFieldAccessFactory().getExceptionalExitStore(tree); - } + @Override + public InitializationStore getExceptionalExitStore(Tree tree) { + return getFieldAccessFactory().getExceptionalExitStore(tree); + } - @Override - public List>> - getReturnStatementStores(MethodTree methodTree) { - return getFieldAccessFactory().getReturnStatementStores(methodTree); - } + @Override + public List>> + getReturnStatementStores(MethodTree methodTree) { + return getFieldAccessFactory().getReturnStatementStores(methodTree); + } - /** - * {@inheritDoc} - * - *

This implementaiton also takes the target checker into account. - * - * @see #getUninitializedFields(InitializationStore, CFAbstractStore, TreePath, boolean, - * Collection) - */ - @Override - protected void setSelfTypeInInitializationCode( - Tree tree, AnnotatedTypeMirror.AnnotatedDeclaredType selfType, TreePath path) { - ClassTree enclosingClass = TreePathUtil.enclosingClass(path); - Type classType = ((JCTree) enclosingClass).type; - AnnotationMirror annotation; + /** + * {@inheritDoc} + * + *

This implementaiton also takes the target checker into account. + * + * @see #getUninitializedFields(InitializationStore, CFAbstractStore, TreePath, boolean, + * Collection) + */ + @Override + protected void setSelfTypeInInitializationCode( + Tree tree, AnnotatedTypeMirror.AnnotatedDeclaredType selfType, TreePath path) { + ClassTree enclosingClass = TreePathUtil.enclosingClass(path); + Type classType = ((JCTree) enclosingClass).type; + AnnotationMirror annotation; - // If all fields are initialized-only, and they are all initialized, - // then: - // - if the class is final, this is @Initialized - // - otherwise, this is @UnderInitialization(CurrentClass) as - // there might still be subclasses that need initialization. - if (areAllFieldsInitializedOnly(enclosingClass)) { - GenericAnnotatedTypeFactory targetFactory = - checker.getTypeFactoryOfSubcheckerOrNull( - ((InitializationChecker) checker).getTargetCheckerClass()); - InitializationStore initStore = getStoreBefore(tree); - CFAbstractStore targetStore = targetFactory.getStoreBefore(tree); - if (initStore != null - && targetStore != null - && getUninitializedFields( - initStore, targetStore, path, false, Collections.emptyList()) - .isEmpty()) { - if (classType.isFinal()) { - annotation = INITIALIZED; - } else { - annotation = createUnderInitializationAnnotation(classType); - } - } else if (initStore != null - && getUninitializedFields(initStore, path, false, Collections.emptyList()) - .isEmpty()) { - if (classType.isFinal()) { - annotation = INITIALIZED; - } else { - annotation = createUnderInitializationAnnotation(classType); - } - } else { - annotation = null; - } + // If all fields are initialized-only, and they are all initialized, + // then: + // - if the class is final, this is @Initialized + // - otherwise, this is @UnderInitialization(CurrentClass) as + // there might still be subclasses that need initialization. + if (areAllFieldsInitializedOnly(enclosingClass)) { + GenericAnnotatedTypeFactory targetFactory = + checker.getTypeFactoryOfSubcheckerOrNull( + ((InitializationChecker) checker).getTargetCheckerClass()); + InitializationStore initStore = getStoreBefore(tree); + CFAbstractStore targetStore = targetFactory.getStoreBefore(tree); + if (initStore != null + && targetStore != null + && getUninitializedFields(initStore, targetStore, path, false, Collections.emptyList()) + .isEmpty()) { + if (classType.isFinal()) { + annotation = INITIALIZED; } else { - annotation = null; + annotation = createUnderInitializationAnnotation(classType); } - - if (annotation == null) { - annotation = getUnderInitializationAnnotationOfSuperType(classType); + } else if (initStore != null + && getUninitializedFields(initStore, path, false, Collections.emptyList()).isEmpty()) { + if (classType.isFinal()) { + annotation = INITIALIZED; + } else { + annotation = createUnderInitializationAnnotation(classType); } - selfType.replaceAnnotation(annotation); + } else { + annotation = null; + } + } else { + annotation = null; } - /** - * Returns the fields that are not yet initialized in a given store, taking into account the - * target checker. - * - *

A field f is initialized if - * - *

    - *
  • f is initialized in the initialization store, i.e., it has been assigned; - *
  • the value of f in the target store has a non-top qualifier that does not have the - * meta-annotation {@link HoldsForDefaultValue}; or - *
  • the declared qualifier of f in the target hierarchy either has the meta-annotation - * {@link HoldsForDefaultValue} or is a top qualifier. - *
- * - *

See {@link #getUninitializedFields(InitializationStore, TreePath, boolean, Collection)} - * for a method that does not require the target checker. - * - * @param initStore a store for the initialization checker - * @param targetStore a store for the target checker corresponding to initStore - * @param path the current path, used to determine the current class - * @param isStatic whether to report static fields or instance fields - * @param receiverAnnotations the annotations on the receiver - * @return the fields that are not yet initialized in a given store - */ - public List getUninitializedFields( - InitializationStore initStore, - CFAbstractStore targetStore, - TreePath path, - boolean isStatic, - Collection receiverAnnotations) { - List uninitializedFields = - super.getUninitializedFields(initStore, path, isStatic, receiverAnnotations); - - GenericAnnotatedTypeFactory factory = - checker.getTypeFactoryOfSubcheckerOrNull( - ((InitializationChecker) checker).getTargetCheckerClass()); + if (annotation == null) { + annotation = getUnderInitializationAnnotationOfSuperType(classType); + } + selfType.replaceAnnotation(annotation); + } - if (factory == null) { - throw new BugInCF( - "Did not find target type factory for checker " - + ((InitializationChecker) checker).getTargetCheckerClass()); - } + /** + * Returns the fields that are not yet initialized in a given store, taking into account the + * target checker. + * + *

A field f is initialized if + * + *

    + *
  • f is initialized in the initialization store, i.e., it has been assigned; + *
  • the value of f in the target store has a non-top qualifier that does not have the + * meta-annotation {@link HoldsForDefaultValue}; or + *
  • the declared qualifier of f in the target hierarchy either has the meta-annotation {@link + * HoldsForDefaultValue} or is a top qualifier. + *
+ * + *

See {@link #getUninitializedFields(InitializationStore, TreePath, boolean, Collection)} for + * a method that does not require the target checker. + * + * @param initStore a store for the initialization checker + * @param targetStore a store for the target checker corresponding to initStore + * @param path the current path, used to determine the current class + * @param isStatic whether to report static fields or instance fields + * @param receiverAnnotations the annotations on the receiver + * @return the fields that are not yet initialized in a given store + */ + public List getUninitializedFields( + InitializationStore initStore, + CFAbstractStore targetStore, + TreePath path, + boolean isStatic, + Collection receiverAnnotations) { + List uninitializedFields = + super.getUninitializedFields(initStore, path, isStatic, receiverAnnotations); - // Remove primitives - if (!((InitializationChecker) checker).checkPrimitives()) { - uninitializedFields.removeIf(var -> getAnnotatedType(var).getKind().isPrimitive()); - } + GenericAnnotatedTypeFactory factory = + checker.getTypeFactoryOfSubcheckerOrNull( + ((InitializationChecker) checker).getTargetCheckerClass()); - // Filter out fields which are initialized according to subchecker - uninitializedFields.removeIf( - var -> { - ClassTree enclosingClass = TreePathUtil.enclosingClass(getPath(var)); - Node receiver; - if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration(var))) { - receiver = new ClassNameNode(enclosingClass); - } else { - receiver = - new ImplicitThisNode( - TreeUtils.elementFromDeclaration(enclosingClass).asType()); - } - VariableElement varElement = TreeUtils.elementFromDeclaration(var); - FieldAccessNode fa = new FieldAccessNode(var, varElement, receiver); - CFAbstractValue value = targetStore.getValue(fa); - return isInitialized(factory, value, varElement); - }); + if (factory == null) { + throw new BugInCF( + "Did not find target type factory for checker " + + ((InitializationChecker) checker).getTargetCheckerClass()); + } - return uninitializedFields; + // Remove primitives + if (!((InitializationChecker) checker).checkPrimitives()) { + uninitializedFields.removeIf(var -> getAnnotatedType(var).getKind().isPrimitive()); } - /** - * Determines whether the specified variable's current value is initialized. - * - *

Returns {@code true} iff the variable's current value is initialized. This holds for - * variables whose value has a non-top qualifier that does not have the meta-annotation {@link - * HoldsForDefaultValue} (e.g., variables with a {@code NonNull} value), as well as variables - * whose declaration has a qualifier that has the meta-annotation {@link HoldsForDefaultValue} - * (e.g., variables whose declared type is {@code Nullable}). - * - * @param factory the parent checker's factory - * @param value the variable's current value - * @param var the variable to check - * @return whether the specified variable is yet to be initialized - */ - public static boolean isInitialized( - GenericAnnotatedTypeFactory factory, - CFAbstractValue value, - VariableElement var) { - AnnotatedTypeMirror declType = factory.getAnnotatedType(var); + // Filter out fields which are initialized according to subchecker + uninitializedFields.removeIf( + var -> { + ClassTree enclosingClass = TreePathUtil.enclosingClass(getPath(var)); + Node receiver; + if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration(var))) { + receiver = new ClassNameNode(enclosingClass); + } else { + receiver = + new ImplicitThisNode(TreeUtils.elementFromDeclaration(enclosingClass).asType()); + } + VariableElement varElement = TreeUtils.elementFromDeclaration(var); + FieldAccessNode fa = new FieldAccessNode(var, varElement, receiver); + CFAbstractValue value = targetStore.getValue(fa); + return isInitialized(factory, value, varElement); + }); - Set topAnnotations = - factory.getQualifierHierarchy().getTopAnnotations(); + return uninitializedFields; + } - for (Class invariant : factory.getSupportedTypeQualifiers()) { - // Skip default-value, monotonic, polymorphic, and top qualifiers - if (invariant.getAnnotation(HoldsForDefaultValue.class) != null - || invariant.getAnnotation(MonotonicQualifier.class) != null - || invariant.getAnnotation(PolymorphicQualifier.class) != null - || topAnnotations.stream() - .anyMatch( - annotation -> factory.areSameByClass(annotation, invariant))) { - continue; - } + /** + * Determines whether the specified variable's current value is initialized. + * + *

Returns {@code true} iff the variable's current value is initialized. This holds for + * variables whose value has a non-top qualifier that does not have the meta-annotation {@link + * HoldsForDefaultValue} (e.g., variables with a {@code NonNull} value), as well as variables + * whose declaration has a qualifier that has the meta-annotation {@link HoldsForDefaultValue} + * (e.g., variables whose declared type is {@code Nullable}). + * + * @param factory the parent checker's factory + * @param value the variable's current value + * @param var the variable to check + * @return whether the specified variable is yet to be initialized + */ + public static boolean isInitialized( + GenericAnnotatedTypeFactory factory, + CFAbstractValue value, + VariableElement var) { + AnnotatedTypeMirror declType = factory.getAnnotatedType(var); - boolean hasInvariantInStore = - value != null - && value.getAnnotations().stream() - .anyMatch( - annotation -> - factory.areSameByClass(annotation, invariant)); - boolean hasInvariantAtDeclaration = - AnnotatedTypes.findEffectiveLowerBoundAnnotations( - factory.getQualifierHierarchy(), declType) - .stream() - .anyMatch(annotation -> factory.areSameByClass(annotation, invariant)); + Set topAnnotations = + factory.getQualifierHierarchy().getTopAnnotations(); - if (hasInvariantAtDeclaration && !hasInvariantInStore) { - return false; - } - } + for (Class invariant : factory.getSupportedTypeQualifiers()) { + // Skip default-value, monotonic, polymorphic, and top qualifiers + if (invariant.getAnnotation(HoldsForDefaultValue.class) != null + || invariant.getAnnotation(MonotonicQualifier.class) != null + || invariant.getAnnotation(PolymorphicQualifier.class) != null + || topAnnotations.stream() + .anyMatch(annotation -> factory.areSameByClass(annotation, invariant))) { + continue; + } - return true; + boolean hasInvariantInStore = + value != null + && value.getAnnotations().stream() + .anyMatch(annotation -> factory.areSameByClass(annotation, invariant)); + boolean hasInvariantAtDeclaration = + AnnotatedTypes.findEffectiveLowerBoundAnnotations( + factory.getQualifierHierarchy(), declType) + .stream() + .anyMatch(annotation -> factory.areSameByClass(annotation, invariant)); + + if (hasInvariantAtDeclaration && !hasInvariantInStore) { + return false; + } } + + return true; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java index 8aa8cdd3db3..c0ec95770a8 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java @@ -3,7 +3,10 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; +import java.util.Set; import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; import org.checkerframework.checker.initialization.qual.Initialized; import org.checkerframework.checker.nullness.NullnessChecker; @@ -15,11 +18,6 @@ import org.checkerframework.checker.signature.qual.FullyQualifiedName; import org.checkerframework.common.basetype.BaseTypeChecker; -import java.util.ArrayList; -import java.util.List; -import java.util.NavigableSet; -import java.util.Set; - /** * Tracks whether a value is initialized (all its fields are set), and checks that values are * initialized before being used. Implements the freedom-before-commitment scheme for @@ -71,89 +69,89 @@ */ public abstract class InitializationChecker extends BaseTypeChecker { - /** Default constructor for InitializationChecker. */ - public InitializationChecker() {} + /** Default constructor for InitializationChecker. */ + public InitializationChecker() {} - /** - * Whether to check primitives for initialization. - * - * @return whether to check primitives for initialization - */ - public abstract boolean checkPrimitives(); + /** + * Whether to check primitives for initialization. + * + * @return whether to check primitives for initialization + */ + public abstract boolean checkPrimitives(); - /** - * The checker for the target type system for which to check initialization. - * - * @return the checker for the target type system. - */ - public abstract Class getTargetCheckerClass(); + /** + * The checker for the target type system for which to check initialization. + * + * @return the checker for the target type system. + */ + public abstract Class getTargetCheckerClass(); - /** - * Also handle {@code AnnotatedFor} annotations for this checker. See {@link - * InitializationFieldAccessSubchecker#getUpstreamCheckerNames()} and the two implementations - * should be kept in sync. - */ - @Override - public List<@FullyQualifiedName String> getUpstreamCheckerNames() { - if (upstreamCheckerNames == null) { - super.getUpstreamCheckerNames(); - upstreamCheckerNames.add(InitializationChecker.class.getName()); - } - return upstreamCheckerNames; + /** + * Also handle {@code AnnotatedFor} annotations for this checker. See {@link + * InitializationFieldAccessSubchecker#getUpstreamCheckerNames()} and the two implementations + * should be kept in sync. + */ + @Override + public List<@FullyQualifiedName String> getUpstreamCheckerNames() { + if (upstreamCheckerNames == null) { + super.getUpstreamCheckerNames(); + upstreamCheckerNames.add(InitializationChecker.class.getName()); } + return upstreamCheckerNames; + } - @Override - public NavigableSet getSuppressWarningsPrefixes() { - NavigableSet result = super.getSuppressWarningsPrefixes(); - // "fbc" is for backward compatibility only; you should use - // "initialization" instead. - result.add("fbc"); - // The default prefix "initialization" must be added manually because this checker class - // is abstract and its subclasses are not named "InitializationChecker". - result.add("initialization"); - return result; - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + NavigableSet result = super.getSuppressWarningsPrefixes(); + // "fbc" is for backward compatibility only; you should use + // "initialization" instead. + result.add("fbc"); + // The default prefix "initialization" must be added manually because this checker class + // is abstract and its subclasses are not named "InitializationChecker". + result.add("initialization"); + return result; + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(getTargetCheckerClass()); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(getTargetCheckerClass()); + return checkers; + } - /** - * Returns a list of all fields of the given class. - * - * @param clazz the class - * @return a list of all fields of {@code clazz} - */ - public static List getAllFields(ClassTree clazz) { - List fields = new ArrayList<>(); - for (Tree t : clazz.getMembers()) { - if (t.getKind() == Tree.Kind.VARIABLE) { - VariableTree vt = (VariableTree) t; - fields.add(vt); - } - } - return fields; + /** + * Returns a list of all fields of the given class. + * + * @param clazz the class + * @return a list of all fields of {@code clazz} + */ + public static List getAllFields(ClassTree clazz) { + List fields = new ArrayList<>(); + for (Tree t : clazz.getMembers()) { + if (t.getKind() == Tree.Kind.VARIABLE) { + VariableTree vt = (VariableTree) t; + fields.add(vt); + } } + return fields; + } - @Override - public InitializationAnnotatedTypeFactory getTypeFactory() { - return (InitializationAnnotatedTypeFactory) super.getTypeFactory(); - } + @Override + public InitializationAnnotatedTypeFactory getTypeFactory() { + return (InitializationAnnotatedTypeFactory) super.getTypeFactory(); + } - @Override - protected InitializationVisitor createSourceVisitor() { - return new InitializationVisitor(this); - } + @Override + protected InitializationVisitor createSourceVisitor() { + return new InitializationVisitor(this); + } - @Override - protected boolean messageKeyMatches( - String messageKey, String messageKeyInSuppressWarningsString) { - // Also support the shorter keys used by typetools - return super.messageKeyMatches(messageKey, messageKeyInSuppressWarningsString) - || super.messageKeyMatches( - messageKey.replace(".invalid", ""), messageKeyInSuppressWarningsString); - } + @Override + protected boolean messageKeyMatches( + String messageKey, String messageKeyInSuppressWarningsString) { + // Also support the shorter keys used by typetools + return super.messageKeyMatches(messageKey, messageKeyInSuppressWarningsString) + || super.messageKeyMatches( + messageKey.replace(".invalid", ""), messageKeyInSuppressWarningsString); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java index 4f04ab3340b..926d61d12b3 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java @@ -1,62 +1,61 @@ package org.checkerframework.checker.initialization; import com.sun.source.tree.ClassTree; - import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.analysis.AnalysisResult; import org.checkerframework.framework.flow.CFValue; /** The type factory for the {@link InitializationFieldAccessSubchecker}. */ public class InitializationFieldAccessAnnotatedTypeFactory - extends InitializationParentAnnotatedTypeFactory { - - /** - * Create a new InitializationFieldAccessAnnotatedTypeFactory. - * - * @param checker the checker to which the new type factory belongs - */ - public InitializationFieldAccessAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - @Override - protected InitializationAnalysis createFlowAnalysis() { - return new InitializationAnalysis(checker, this); - } - - @Override - protected void performFlowAnalysis(ClassTree classTree) { - // Only perform the analysis if initialization checking is turned on. - if (!assumeInitialized) { - super.performFlowAnalysis(classTree); - } - } - - /** - * Returns the flow analysis. - * - * @return the flow analysis - * @see #getFlowResult() - */ - /*package-private*/ InitializationAnalysis getAnalysis() { - return analysis; - } - - /** - * Returns the result of the flow analysis. Invariant: - * - *

-     *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
-     * 
- * - * Note that flowResult contains analysis results for Trees from multiple classes which are - * produced by multiple calls to performFlowAnalysis. - * - * @return the result of the flow analysis - * @see #getAnalysis() - */ - /*package-private*/ AnalysisResult getFlowResult() { - return flowResult; + extends InitializationParentAnnotatedTypeFactory { + + /** + * Create a new InitializationFieldAccessAnnotatedTypeFactory. + * + * @param checker the checker to which the new type factory belongs + */ + public InitializationFieldAccessAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } + + @Override + protected InitializationAnalysis createFlowAnalysis() { + return new InitializationAnalysis(checker, this); + } + + @Override + protected void performFlowAnalysis(ClassTree classTree) { + // Only perform the analysis if initialization checking is turned on. + if (!assumeInitialized) { + super.performFlowAnalysis(classTree); } + } + + /** + * Returns the flow analysis. + * + * @return the flow analysis + * @see #getFlowResult() + */ + /*package-private*/ InitializationAnalysis getAnalysis() { + return analysis; + } + + /** + * Returns the result of the flow analysis. Invariant: + * + *
+   *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
+   * 
+ * + * Note that flowResult contains analysis results for Trees from multiple classes which are + * produced by multiple calls to performFlowAnalysis. + * + * @return the result of the flow analysis + * @see #getAnalysis() + */ + /*package-private*/ AnalysisResult getFlowResult() { + return flowResult; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java index 0d3f5799020..0e041af00dc 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java @@ -1,11 +1,10 @@ package org.checkerframework.checker.initialization; +import java.util.List; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.signature.qual.FullyQualifiedName; import org.checkerframework.common.basetype.BaseTypeChecker; -import java.util.List; - /** * Part of the freedom-before-commitment type system. * @@ -22,33 +21,32 @@ */ public class InitializationFieldAccessSubchecker extends BaseTypeChecker { - /** Default constructor for InitializationFieldAccessSubchecker. */ - public InitializationFieldAccessSubchecker() {} - - /** - * Also handle {@code AnnotatedFor} annotations for the {@link InitializationChecker}. See - * {@link InitializationChecker#getUpstreamCheckerNames()} and the two implementations should be - * kept in sync. - */ - @Override - public List<@FullyQualifiedName String> getUpstreamCheckerNames() { - if (upstreamCheckerNames == null) { - super.getUpstreamCheckerNames(); - upstreamCheckerNames.add(InitializationChecker.class.getName()); - } - return upstreamCheckerNames; + /** Default constructor for InitializationFieldAccessSubchecker. */ + public InitializationFieldAccessSubchecker() {} + + /** + * Also handle {@code AnnotatedFor} annotations for the {@link InitializationChecker}. See {@link + * InitializationChecker#getUpstreamCheckerNames()} and the two implementations should be kept in + * sync. + */ + @Override + public List<@FullyQualifiedName String> getUpstreamCheckerNames() { + if (upstreamCheckerNames == null) { + super.getUpstreamCheckerNames(); + upstreamCheckerNames.add(InitializationChecker.class.getName()); } + return upstreamCheckerNames; + } - // Suppress all errors and warnings, since they are also reported by the InitializationChecker + // Suppress all errors and warnings, since they are also reported by the InitializationChecker - @Override - public void reportError(Object source, @CompilerMessageKey String messageKey, Object... args) { - // do nothing - } + @Override + public void reportError(Object source, @CompilerMessageKey String messageKey, Object... args) { + // do nothing + } - @Override - public void reportWarning( - Object source, @CompilerMessageKey String messageKey, Object... args) { - // do nothing - } + @Override + public void reportWarning(Object source, @CompilerMessageKey String messageKey, Object... args) { + // do nothing + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java index e2ddd5f094c..9b6de183bfa 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java @@ -4,7 +4,8 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; - +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; @@ -12,9 +13,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeMirror; - /** * Part of the freedom-before-commitment type system. * @@ -26,130 +24,128 @@ */ public class InitializationFieldAccessTreeAnnotator extends TreeAnnotator { - /** The value of the assumeInitialized option. */ - protected final boolean assumeInitialized; - - /** - * Creates a new CommitmentFieldAccessTreeAnnotator. - * - * @param atypeFactory the type factory belonging to the init checker's parent - */ - public InitializationFieldAccessTreeAnnotator( - GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - assumeInitialized = atypeFactory.getChecker().hasOption("assumeInitialized"); + /** The value of the assumeInitialized option. */ + protected final boolean assumeInitialized; + + /** + * Creates a new CommitmentFieldAccessTreeAnnotator. + * + * @param atypeFactory the type factory belonging to the init checker's parent + */ + public InitializationFieldAccessTreeAnnotator( + GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + assumeInitialized = atypeFactory.getChecker().hasOption("assumeInitialized"); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror p) { + super.visitIdentifier(tree, p); + computeFieldAccessType(tree, p); + return null; + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror p) { + super.visitMemberSelect(tree, p); + computeFieldAccessType(tree, p); + return null; + } + + /** + * Adapts the type in the target checker hierarchy of a field access depending on the field's + * declared type and the receiver's initialization type. + * + * @param tree the field access + * @param type the field access's unadapted type + */ + private void computeFieldAccessType(ExpressionTree tree, AnnotatedTypeMirror type) { + GenericAnnotatedTypeFactory factory = + (GenericAnnotatedTypeFactory) atypeFactory; + + // Don't adapt anything if initialization checking is turned off. + if (assumeInitialized) { + return; + } + + // Don't adapt anything if "tree" is not actually a field access. + + // Don't adapt uses of the identifiers "this" or "super" that are not field accesses + // (e.g., constructor calls or uses of an outer this). + if (tree instanceof IdentifierTree) { + IdentifierTree identTree = (IdentifierTree) tree; + if (identTree.getName().contentEquals("this") || identTree.getName().contentEquals("super")) { + return; + } + } + + // Don't adapt method accesses. + if (type instanceof AnnotatedTypeMirror.AnnotatedExecutableType) { + return; + } + + // Don't adapt trees that do not have a (explicit or implicit) receiver (e.g., local + // variables). + InitializationFieldAccessAnnotatedTypeFactory initFactory = + atypeFactory + .getChecker() + .getTypeFactoryOfSubcheckerOrNull(InitializationFieldAccessSubchecker.class); + if (initFactory == null) { + throw new BugInCF("Did not find InitializationFieldAccessSubchecker!"); + } + AnnotatedTypeMirror receiver = initFactory.getReceiverType(tree); + if (receiver == null) { + return; } - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror p) { - super.visitIdentifier(tree, p); - computeFieldAccessType(tree, p); - return null; + // Don't adapt trees whose receiver is initialized. + if (!initFactory.isUnknownInitialization(receiver) + && !initFactory.isUnderInitialization(receiver)) { + return; } - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror p) { - super.visitMemberSelect(tree, p); - computeFieldAccessType(tree, p); - return null; + // Don't adapt trees with an explicit UnknownInitialization annotation on the field + Element element = TreeUtils.elementFromUse(tree); + AnnotatedTypeMirror fieldAnnotations = factory.getAnnotatedType(element); + if (AnnotationUtils.containsSameByName( + fieldAnnotations.getAnnotations(), initFactory.UNKNOWN_INITIALIZATION)) { + return; } - /** - * Adapts the type in the target checker hierarchy of a field access depending on the field's - * declared type and the receiver's initialization type. - * - * @param tree the field access - * @param type the field access's unadapted type - */ - private void computeFieldAccessType(ExpressionTree tree, AnnotatedTypeMirror type) { - GenericAnnotatedTypeFactory factory = - (GenericAnnotatedTypeFactory) atypeFactory; - - // Don't adapt anything if initialization checking is turned off. - if (assumeInitialized) { - return; - } - - // Don't adapt anything if "tree" is not actually a field access. - - // Don't adapt uses of the identifiers "this" or "super" that are not field accesses - // (e.g., constructor calls or uses of an outer this). - if (tree instanceof IdentifierTree) { - IdentifierTree identTree = (IdentifierTree) tree; - if (identTree.getName().contentEquals("this") - || identTree.getName().contentEquals("super")) { - return; - } - } - - // Don't adapt method accesses. - if (type instanceof AnnotatedTypeMirror.AnnotatedExecutableType) { - return; - } - - // Don't adapt trees that do not have a (explicit or implicit) receiver (e.g., local - // variables). - InitializationFieldAccessAnnotatedTypeFactory initFactory = - atypeFactory - .getChecker() - .getTypeFactoryOfSubcheckerOrNull( - InitializationFieldAccessSubchecker.class); - if (initFactory == null) { - throw new BugInCF("Did not find InitializationFieldAccessSubchecker!"); - } - AnnotatedTypeMirror receiver = initFactory.getReceiverType(tree); - if (receiver == null) { - return; - } - - // Don't adapt trees whose receiver is initialized. - if (!initFactory.isUnknownInitialization(receiver) - && !initFactory.isUnderInitialization(receiver)) { - return; - } - - // Don't adapt trees with an explicit UnknownInitialization annotation on the field - Element element = TreeUtils.elementFromUse(tree); - AnnotatedTypeMirror fieldAnnotations = factory.getAnnotatedType(element); - if (AnnotationUtils.containsSameByName( - fieldAnnotations.getAnnotations(), initFactory.UNKNOWN_INITIALIZATION)) { - return; - } - - TypeMirror fieldOwnerType = element.getEnclosingElement().asType(); - boolean isReceiverInitToOwner = initFactory.isInitializedForFrame(receiver, fieldOwnerType); - - // If the field has been initialized, don't clear annotations. - // This is ok even if the field was initialized with a non-invariant - // value because in that case, there must have been an error before. - // E.g.: - // { f1 = f2; - // f2 = f1; } - // Here, we will get an error for the first assignment, but we won't get another - // error for the second assignment. - // See the AssignmentDuringInitialization test case. - Tree fieldDeclarationTree = initFactory.declarationFromElement(element); - InitializationStore store = initFactory.getStoreBefore(tree); - // If the field declaration is null (because the field is declared in bytecode), - // or the store is null (because flow-sensitive refinement is turned off), - // the field is considered uninitialized. - // Fields of objects other than this are not tracked and thus also considered uninitialized. - // Otherwise, check if the field is initialized in the given store. - boolean isFieldInitialized = - fieldDeclarationTree != null - && store != null - && TreeUtils.isSelfAccess(tree) - && initFactory - .getInitializedFields(store, initFactory.getPath(tree)) - .contains(fieldDeclarationTree); - if (!isReceiverInitToOwner - && !isFieldInitialized - && !factory.isComputingAnnotatedTypeMirrorOfLhs()) { - // The receiver is not initialized for this frame and the type being computed is - // not a LHS. - // Replace all annotations with the top annotation for that hierarchy. - type.clearAnnotations(); - type.addAnnotations(factory.getQualifierHierarchy().getTopAnnotations()); - } + TypeMirror fieldOwnerType = element.getEnclosingElement().asType(); + boolean isReceiverInitToOwner = initFactory.isInitializedForFrame(receiver, fieldOwnerType); + + // If the field has been initialized, don't clear annotations. + // This is ok even if the field was initialized with a non-invariant + // value because in that case, there must have been an error before. + // E.g.: + // { f1 = f2; + // f2 = f1; } + // Here, we will get an error for the first assignment, but we won't get another + // error for the second assignment. + // See the AssignmentDuringInitialization test case. + Tree fieldDeclarationTree = initFactory.declarationFromElement(element); + InitializationStore store = initFactory.getStoreBefore(tree); + // If the field declaration is null (because the field is declared in bytecode), + // or the store is null (because flow-sensitive refinement is turned off), + // the field is considered uninitialized. + // Fields of objects other than this are not tracked and thus also considered uninitialized. + // Otherwise, check if the field is initialized in the given store. + boolean isFieldInitialized = + fieldDeclarationTree != null + && store != null + && TreeUtils.isSelfAccess(tree) + && initFactory + .getInitializedFields(store, initFactory.getPath(tree)) + .contains(fieldDeclarationTree); + if (!isReceiverInitToOwner + && !isFieldInitialized + && !factory.isComputingAnnotatedTypeMirrorOfLhs()) { + // The receiver is not initialized for this frame and the type being computed is + // not a LHS. + // Replace all annotations with the top annotation for that hierarchy. + type.clearAnnotations(); + type.addAnnotations(factory.getQualifierHierarchy().getTopAnnotations()); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java index 65b6ba51ab8..9223cc0051d 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java @@ -1,36 +1,35 @@ package org.checkerframework.checker.initialization; import com.sun.source.tree.ClassTree; - import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; /** The visitor for the {@link InitializationFieldAccessSubchecker}. */ public class InitializationFieldAccessVisitor - extends BaseTypeVisitor { + extends BaseTypeVisitor { - /** The value of the assumeInitialized option. */ - private final boolean assumeInitialized; + /** The value of the assumeInitialized option. */ + private final boolean assumeInitialized; - /** - * Create an InitializationFieldAccessVisitor. - * - * @param checker the initialization field-access checker - */ - public InitializationFieldAccessVisitor(BaseTypeChecker checker) { - super(checker); - assumeInitialized = checker.hasOption("assumeInitialized"); - } + /** + * Create an InitializationFieldAccessVisitor. + * + * @param checker the initialization field-access checker + */ + public InitializationFieldAccessVisitor(BaseTypeChecker checker) { + super(checker); + assumeInitialized = checker.hasOption("assumeInitialized"); + } - @Override - public void processClassTree(ClassTree classTree) { - // As stated in the documentation for the InitializationFieldAccessChecker - // and InitializationChecker, this checker performs the flow analysis - // (which is handled in the BaseTypeVisitor), but does not perform - // any type checking. - // Thus, this method does nothing but scan through the members. - if (!assumeInitialized) { - scan(classTree.getMembers(), null); - } + @Override + public void processClassTree(ClassTree classTree) { + // As stated in the documentation for the InitializationFieldAccessChecker + // and InitializationChecker, this checker performs the flow analysis + // (which is handled in the BaseTypeVisitor), but does not perform + // any type checking. + // Thus, this method does nothing but scan through the members. + if (!assumeInitialized) { + scan(classTree.getMembers(), null); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java index 6fb6ae3d6ec..649dee78940 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java @@ -14,7 +14,25 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; import org.checkerframework.checker.initialization.qual.FBCBottom; import org.checkerframework.checker.initialization.qual.Initialized; import org.checkerframework.checker.initialization.qual.NotOnlyInitialized; @@ -48,913 +66,862 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; - /** * Superclass for {@link InitializationFieldAccessAnnotatedTypeFactory} and {@link * InitializationAnnotatedTypeFactory} to contain common functionality. */ public abstract class InitializationParentAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory< - CFValue, InitializationStore, InitializationTransfer, InitializationAnalysis> { - - /** {@link UnknownInitialization}. */ - protected final AnnotationMirror UNKNOWN_INITIALIZATION; - - /** {@link Initialized}. */ - protected final AnnotationMirror INITIALIZED; - - /** {@link UnderInitialization} or null. */ - protected final AnnotationMirror UNDER_INITALIZATION; - - /** {@link NotOnlyInitialized} or null. */ - protected final AnnotationMirror NOT_ONLY_INITIALIZED; - - /** {@link PolyInitialized}. */ - protected final AnnotationMirror POLY_INITIALIZED; - - /** {@link FBCBottom}. */ - protected final AnnotationMirror FBCBOTTOM; - - /** The java.lang.Object type. */ - protected final TypeMirror objectTypeMirror; - - /** The Unused.when field/element. */ - protected final ExecutableElement unusedWhenElement; - - /** The UnderInitialization.value field/element. */ - protected final ExecutableElement underInitializationValueElement; - - /** The UnknownInitialization.value field/element. */ - protected final ExecutableElement unknownInitializationValueElement; - - /** The value of the assumeInitialized option. */ - protected final boolean assumeInitialized; + extends GenericAnnotatedTypeFactory< + CFValue, InitializationStore, InitializationTransfer, InitializationAnalysis> { + + /** {@link UnknownInitialization}. */ + protected final AnnotationMirror UNKNOWN_INITIALIZATION; + + /** {@link Initialized}. */ + protected final AnnotationMirror INITIALIZED; + + /** {@link UnderInitialization} or null. */ + protected final AnnotationMirror UNDER_INITALIZATION; + + /** {@link NotOnlyInitialized} or null. */ + protected final AnnotationMirror NOT_ONLY_INITIALIZED; + + /** {@link PolyInitialized}. */ + protected final AnnotationMirror POLY_INITIALIZED; + + /** {@link FBCBottom}. */ + protected final AnnotationMirror FBCBOTTOM; + + /** The java.lang.Object type. */ + protected final TypeMirror objectTypeMirror; + + /** The Unused.when field/element. */ + protected final ExecutableElement unusedWhenElement; + + /** The UnderInitialization.value field/element. */ + protected final ExecutableElement underInitializationValueElement; + + /** The UnknownInitialization.value field/element. */ + protected final ExecutableElement unknownInitializationValueElement; + + /** The value of the assumeInitialized option. */ + protected final boolean assumeInitialized; + + /** + * Create a new InitializationParentAnnotatedTypeFactory. + * + *

Don't forget to call {@link #postInit()} in the concrete subclass. + * + * @param checker the checker to which the new type factory belongs + */ + public InitializationParentAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + + UNKNOWN_INITIALIZATION = AnnotationBuilder.fromClass(elements, UnknownInitialization.class); + INITIALIZED = AnnotationBuilder.fromClass(elements, Initialized.class); + UNDER_INITALIZATION = AnnotationBuilder.fromClass(elements, UnderInitialization.class); + NOT_ONLY_INITIALIZED = AnnotationBuilder.fromClass(elements, NotOnlyInitialized.class); + POLY_INITIALIZED = AnnotationBuilder.fromClass(elements, PolyInitialized.class); + FBCBOTTOM = AnnotationBuilder.fromClass(elements, FBCBottom.class); + + objectTypeMirror = processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType(); + unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, processingEnv); + underInitializationValueElement = + TreeUtils.getMethod(UnderInitialization.class, "value", 0, processingEnv); + unknownInitializationValueElement = + TreeUtils.getMethod(UnknownInitialization.class, "value", 0, processingEnv); + + assumeInitialized = checker.hasOption("assumeInitialized"); + } + + @Override + public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { + super.postAsMemberOf(type, owner, element); + + if (element.getKind().isField()) { + Collection declaredFieldAnnotations = getDeclAnnotations(element); + AnnotatedTypeMirror fieldAnnotations = getAnnotatedType(element); + computeFieldAccessInitializationType(type, declaredFieldAnnotations, owner, fieldAnnotations); + } + } + + /** + * Adapts the initialization type of a field access (implicit or explicit) based on the receiver + * type and the declared annotations for the field. + * + *

To adapt the type in the target checker's hierarchy, see the {@link + * InitializationFieldAccessTreeAnnotator} instead. + * + * @param type type of the field access expression + * @param declaredFieldAnnotations declared annotations on the field + * @param receiverType inferred annotations of the receiver + * @param fieldType inferred annotations of the field + */ + private void computeFieldAccessInitializationType( + AnnotatedTypeMirror type, + Collection declaredFieldAnnotations, + AnnotatedTypeMirror receiverType, + AnnotatedTypeMirror fieldType) { + // Primitive values have no fields and are thus always @Initialized. + if (TypesUtils.isPrimitive(type.getUnderlyingType())) { + return; + } + // not necessary if there is an explicit UnknownInitialization + // annotation on the field + if (AnnotationUtils.containsSameByName(fieldType.getAnnotations(), UNKNOWN_INITIALIZATION)) { + return; + } + + if (isUnknownInitialization(receiverType) || isUnderInitialization(receiverType)) { + if (AnnotationUtils.containsSame(declaredFieldAnnotations, NOT_ONLY_INITIALIZED)) { + // A field declared @NotOnlyInitialized with an uninitialized receiver has + // @UnknownInitialization + type.replaceAnnotation(UNKNOWN_INITIALIZATION); + } else { + // A field declared @NotOnlyInitialized with an initialized receiver is + // @Initialized + type.replaceAnnotation(INITIALIZED); + } + } + } + + @Override + protected Set> createSupportedTypeQualifiers() { + Set> result = new HashSet<>(); + result.add(UnknownInitialization.class); + result.add(UnderInitialization.class); + result.add(Initialized.class); + result.add(FBCBottom.class); + result.add(PolyInitialized.class); + return result; + } + + @Override + public InitializationTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new InitializationTransfer((InitializationAnalysis) analysis); + } + + /** + * Returns {@code true}. Initialization cannot be undone, i.e., an @Initialized object always + * stays @Initialized, an @UnderInitialization(A) object always stays @UnderInitialization(A) + * (though it may additionally become @Initialized), etc. + */ + @Override + public boolean isImmutable(TypeMirror type) { + return true; + } + + /** + * Creates a {@link UnderInitialization} annotation with the given type as its type frame + * argument. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnderInitialization} annotation with the given argument + */ + public AnnotationMirror createUnderInitializationAnnotation(TypeMirror typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); + } + + @Override + public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { + AnnotatedDeclaredType selfType = super.getSelfType(tree); + + if (assumeInitialized) { + return selfType; + } + + TreePath path = getPath(tree); + AnnotatedDeclaredType enclosing = selfType; + while (path != null && enclosing != null) { + TreePath topLevelMemberPath = findTopLevelClassMemberForTree(path); + if (topLevelMemberPath != null && topLevelMemberPath.getLeaf() != null) { + Tree topLevelMember = topLevelMemberPath.getLeaf(); + if (topLevelMember.getKind() != Tree.Kind.METHOD + || TreeUtils.isConstructor((MethodTree) topLevelMember)) { + setSelfTypeInInitializationCode(tree, enclosing, topLevelMemberPath); + } + path = topLevelMemberPath.getParentPath(); + enclosing = enclosing.getEnclosingType(); + } else { + break; + } + } + + return selfType; + } + + /** + * In the first enclosing class, find the path to the top-level member that contains {@code path}. + * + * @param path the path whose leaf is the target + * @return path to a top-level member containing the leaf of {@code path} + */ + @SuppressWarnings("interning:not.interned") // AST node comparison + private @Nullable TreePath findTopLevelClassMemberForTree(TreePath path) { + if (TreeUtils.isClassTree(path.getLeaf())) { + path = path.getParentPath(); + if (path == null) { + return null; + } + } + ClassTree enclosingClass = TreePathUtil.enclosingClass(path); + if (enclosingClass != null) { + List classMembers = enclosingClass.getMembers(); + TreePath searchPath = path; + while (searchPath.getParentPath() != null + && searchPath.getParentPath().getLeaf() != enclosingClass) { + searchPath = searchPath.getParentPath(); + if (classMembers.contains(searchPath.getLeaf())) { + return searchPath; + } + } + } + return null; + } + + /** + * Side-effects argument {@code selfType} to make it @Initialized or @UnderInitialization, + * depending on whether all fields have been set. + * + * @param tree a tree + * @param selfType the type to side-effect + * @param path a path + */ + protected void setSelfTypeInInitializationCode( + Tree tree, AnnotatedDeclaredType selfType, TreePath path) { + ClassTree enclosingClass = TreePathUtil.enclosingClass(path); + Type classType = ((JCTree) enclosingClass).type; + AnnotationMirror annotation = null; + + // If all fields are initialized-only, and they are all initialized, + // then: + // - if the class is final, this is @Initialized + // - otherwise, this is @UnderInitialization(CurrentClass) as + // there might still be subclasses that need initialization. + if (areAllFieldsInitializedOnly(enclosingClass)) { + InitializationStore store = getStoreBefore(tree); + if (store != null + && getUninitializedFields(store, path, false, Collections.emptyList()).isEmpty()) { + if (classType.isFinal()) { + annotation = INITIALIZED; + } else { + annotation = createUnderInitializationAnnotation(classType); + } + } + } + + if (annotation == null) { + annotation = getUnderInitializationAnnotationOfSuperType(classType); + } + selfType.replaceAnnotation(annotation); + } + + /** + * Returns an {@link UnderInitialization} annotation that has the superclass of {@code type} as + * type frame. + * + * @param type a type + * @return true an {@link UnderInitialization} for the supertype of {@code type} + */ + protected AnnotationMirror getUnderInitializationAnnotationOfSuperType(TypeMirror type) { + // Find supertype if possible. + AnnotationMirror annotation; + List superTypes = types.directSupertypes(type); + TypeMirror superClass = null; + for (TypeMirror superType : superTypes) { + ElementKind kind = types.asElement(superType).getKind(); + if (kind == ElementKind.CLASS) { + superClass = superType; + break; + } + } + // Create annotation. + if (superClass != null) { + annotation = createUnderInitializationAnnotation(superClass); + } else { + // Use Object as a valid super-class. + annotation = createUnderInitializationAnnotation(Object.class); + } + return annotation; + } + + /** + * Returns whether the specified field is unused, given the specified annotations on the receiver. + * + * @param field the field to check + * @param receiverAnnos the annotations on the receiver + * @return whether {@code field} is unused given {@code receiverAnnos} + */ + protected boolean isUnused( + VariableTree field, Collection receiverAnnos) { + if (receiverAnnos.isEmpty()) { + return false; + } + + AnnotationMirror unused = + getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class); + if (unused == null) { + return false; + } + + Name when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement); + for (AnnotationMirror anno : receiverAnnos) { + Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName(); + if (annoName.contentEquals(when)) { + return true; + } + } + + return false; + } + + /** + * Creates a {@link UnderInitialization} annotation with the given type frame. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnderInitialization} annotation with the given argument + */ + public AnnotationMirror createUnderInitializationAnnotation(Class typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); + } + + /** + * Are all fields initialized-only? + * + * @param classTree the class to query + * @return true if all fields are initialized-only + */ + protected boolean areAllFieldsInitializedOnly(ClassTree classTree) { + for (Tree member : classTree.getMembers()) { + if (member.getKind() != Tree.Kind.VARIABLE) { + continue; + } + VariableTree var = (VariableTree) member; + VariableElement varElt = TreeUtils.elementFromDeclaration(var); + // var is not initialized-only + if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) { + // var is not static -- need a check of initializer blocks, + // not of constructor which is where this is used + if (!varElt.getModifiers().contains(Modifier.STATIC)) { + return false; + } + } + } + return true; + } + + /** + * Returns the fields that are possibly uninitialized in a given store, without taking into + * account the target checker. + * + *

I.e., this method returns all fields that have not been assigned, without considering fields + * that may be considered initialized by the target checker even though they have not been + * explicitly assigned. See {@link InitializationAnnotatedTypeFactory#getUninitializedFields( + * InitializationStore, CFAbstractStore, TreePath, boolean, Collection)} for a method that does + * take the target checker into account. + * + * @param store a store + * @param path the current path, used to determine the current class + * @param isStatic whether to report static fields or instance fields + * @param receiverAnnotations the annotations on the receiver + * @return the fields that are not yet initialized in a given store + */ + public List getUninitializedFields( + InitializationStore store, + TreePath path, + boolean isStatic, + Collection receiverAnnotations) { + ClassTree currentClass = TreePathUtil.enclosingClass(path); + List fields = InitializationChecker.getAllFields(currentClass); + List uninit = new ArrayList<>(); + for (VariableTree field : fields) { + if (isUnused(field, receiverAnnotations)) { + continue; // don't consider unused fields + } + VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); + if (ElementUtils.isStatic(fieldElem) == isStatic) { + if (!store.isFieldInitialized(fieldElem)) { + uninit.add(field); + } + } + } + return uninit; + } + + /** + * Returns the fields that are initialized in the given store. + * + * @param store a store + * @param path the current path; used to compute the current class + * @return the fields that are initialized in the given store + */ + public List getInitializedFields(InitializationStore store, TreePath path) { + // TODO: Instead of passing the TreePath around, can we use + // getCurrentClassTree? + ClassTree currentClass = TreePathUtil.enclosingClass(path); + List fields = InitializationChecker.getAllFields(currentClass); + List initializedFields = new ArrayList<>(); + for (VariableTree field : fields) { + VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); + if (!ElementUtils.isStatic(fieldElem)) { + if (store.isFieldInitialized(fieldElem)) { + initializedFields.add(field); + } + } + } + return initializedFields; + } + + @Override + public boolean isNotFullyInitializedReceiver(MethodTree methodTree) { + if (super.isNotFullyInitializedReceiver(methodTree)) { + return true; + } + AnnotatedDeclaredType receiverType = getAnnotatedType(methodTree).getReceiverType(); + if (receiverType != null) { + return isUnknownInitialization(receiverType) || isUnderInitialization(receiverType); + } else { + // There is no receiver e.g. in static methods. + return false; + } + } + + /** + * Creates a {@link UnknownInitialization} annotation with a given type frame. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnknownInitialization} annotation with the given argument + */ + public AnnotationMirror createUnknownInitializationAnnotation(Class typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); + } + + /** + * Creates an {@link UnknownInitialization} annotation with a given type frame. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnknownInitialization} annotation with the given argument + */ + public AnnotationMirror createUnknownInitializationAnnotation(TypeMirror typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); + } + + /** + * Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link UnderInitialization} + */ + public boolean isUnderInitialization(AnnotationMirror anno) { + return areSameByClass(anno, UnderInitialization.class); + } + + /** + * Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link UnknownInitialization} + */ + public boolean isUnknownInitialization(AnnotationMirror anno) { + return areSameByClass(anno, UnknownInitialization.class); + } + + /** + * Is {@code anno} the bottom annotation? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link FBCBottom} + */ + public boolean isFbcBottom(AnnotationMirror anno) { + return AnnotationUtils.areSame(anno, FBCBOTTOM); + } + + /** + * Is {@code anno} the {@link Initialized} annotation? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link Initialized} + */ + public boolean isInitialized(AnnotationMirror anno) { + return AnnotationUtils.areSame(anno, INITIALIZED); + } + + /** + * Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link UnderInitialization} + */ + public boolean isUnderInitialization(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(UnderInitialization.class); + } + + /** + * Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link UnknownInitialization} + */ + public boolean isUnknownInitialization(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(UnknownInitialization.class); + } + + /** + * Does {@code anno} have the bottom annotation? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link FBCBottom} + */ + public boolean isFbcBottom(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(FBCBottom.class); + } + + /** + * Does {@code anno} have the annotation {@link Initialized}? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link Initialized} + */ + public boolean isInitialized(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(Initialized.class); + } + + /** + * Return true if the type is initialized with respect to the given frame -- that is, all of the + * fields of the frame are initialized. + * + * @param type the type whose initialization type qualifiers to check + * @param frame a class in {@code type}'s class hierarchy + * @return true if the type is initialized for the given frame + */ + public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) { + if (isInitialized(type)) { + return true; + } + + AnnotationMirror initializationAnno = + type.getEffectiveAnnotationInHierarchy(UNKNOWN_INITIALIZATION); + if (initializationAnno == null) { + initializationAnno = type.getEffectiveAnnotationInHierarchy(UNDER_INITALIZATION); + } + + TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno); + Types types = processingEnv.getTypeUtils(); + return types.isSubtype(typeFrame, types.erasure(frame)); + } + + /** + * Returns the type frame (that is, the argument) of a given initialization annotation. + * + * @param annotation a {@link UnderInitialization} or {@link UnknownInitialization} annotation + * @return the annotation's argument + */ + public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) { + if (AnnotationUtils.areSameByName( + annotation, "org.checkerframework.checker.initialization.qual.UnderInitialization")) { + return AnnotationUtils.getElementValue( + annotation, underInitializationValueElement, TypeMirror.class, objectTypeMirror); + } else { + return AnnotationUtils.getElementValue( + annotation, unknownInitializationValueElement, TypeMirror.class, objectTypeMirror); + } + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new InitializationQualifierHierarchy(); + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(super.createTypeAnnotator(), new CommitmentTypeAnnotator(this)); + } + + /** + * Returns {@code false}. Redundancy in only the initialization hierarchy is ok and may even be + * caused by implicit default annotations. The parent checker should determine whether to warn + * about redundancy. + */ + @Override + public boolean shouldWarnIfStubRedundantWithBytecode() { + return false; + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because we want our CommitmentTreeAnnotator + // instead of the default PropagationTreeAnnotator + List treeAnnotators = new ArrayList<>(2); + treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); + if (dependentTypesHelper.hasDependentAnnotations()) { + treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); + } + treeAnnotators.add(new CommitmentTreeAnnotator(this)); + return new ListTreeAnnotator(treeAnnotators); + } + + /** This type annotator adds the correct UnderInitialization annotation to super constructors. */ + protected class CommitmentTypeAnnotator extends TypeAnnotator { /** - * Create a new InitializationParentAnnotatedTypeFactory. - * - *

Don't forget to call {@link #postInit()} in the concrete subclass. + * Creates a new CommitmentTypeAnnotator. * - * @param checker the checker to which the new type factory belongs + * @param atypeFactory this factory */ - public InitializationParentAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - - UNKNOWN_INITIALIZATION = AnnotationBuilder.fromClass(elements, UnknownInitialization.class); - INITIALIZED = AnnotationBuilder.fromClass(elements, Initialized.class); - UNDER_INITALIZATION = AnnotationBuilder.fromClass(elements, UnderInitialization.class); - NOT_ONLY_INITIALIZED = AnnotationBuilder.fromClass(elements, NotOnlyInitialized.class); - POLY_INITIALIZED = AnnotationBuilder.fromClass(elements, PolyInitialized.class); - FBCBOTTOM = AnnotationBuilder.fromClass(elements, FBCBottom.class); - - objectTypeMirror = - processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType(); - unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, processingEnv); - underInitializationValueElement = - TreeUtils.getMethod(UnderInitialization.class, "value", 0, processingEnv); - unknownInitializationValueElement = - TreeUtils.getMethod(UnknownInitialization.class, "value", 0, processingEnv); - - assumeInitialized = checker.hasOption("assumeInitialized"); + public CommitmentTypeAnnotator(InitializationParentAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - public void postAsMemberOf( - AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { - super.postAsMemberOf(type, owner, element); - - if (element.getKind().isField()) { - Collection declaredFieldAnnotations = - getDeclAnnotations(element); - AnnotatedTypeMirror fieldAnnotations = getAnnotatedType(element); - computeFieldAccessInitializationType( - type, declaredFieldAnnotations, owner, fieldAnnotations); - } - } + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + Void result = super.visitExecutable(t, p); + Element elem = t.getElement(); + if (elem.getKind() == ElementKind.CONSTRUCTOR) { + AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); + DeclaredType underlyingType = returnType.getUnderlyingType(); + // TODO: Make sure we check explicit annotations somewhere. + returnType.replaceAnnotation(getUnderInitializationAnnotationOfSuperType(underlyingType)); + } + return result; + } + } + + /** + * This tree annotator modifies the propagation tree annotator to add propagation rules for the + * freedom-before-commitment system. + */ + protected class CommitmentTreeAnnotator extends PropagationTreeAnnotator { /** - * Adapts the initialization type of a field access (implicit or explicit) based on the receiver - * type and the declared annotations for the field. - * - *

To adapt the type in the target checker's hierarchy, see the {@link - * InitializationFieldAccessTreeAnnotator} instead. + * Creates a new CommitmentTreeAnnotator. * - * @param type type of the field access expression - * @param declaredFieldAnnotations declared annotations on the field - * @param receiverType inferred annotations of the receiver - * @param fieldType inferred annotations of the field + * @param initializationAnnotatedTypeFactory this factory */ - private void computeFieldAccessInitializationType( - AnnotatedTypeMirror type, - Collection declaredFieldAnnotations, - AnnotatedTypeMirror receiverType, - AnnotatedTypeMirror fieldType) { - // Primitive values have no fields and are thus always @Initialized. - if (TypesUtils.isPrimitive(type.getUnderlyingType())) { - return; - } - // not necessary if there is an explicit UnknownInitialization - // annotation on the field - if (AnnotationUtils.containsSameByName( - fieldType.getAnnotations(), UNKNOWN_INITIALIZATION)) { - return; - } - - if (isUnknownInitialization(receiverType) || isUnderInitialization(receiverType)) { - if (AnnotationUtils.containsSame(declaredFieldAnnotations, NOT_ONLY_INITIALIZED)) { - // A field declared @NotOnlyInitialized with an uninitialized receiver has - // @UnknownInitialization - type.replaceAnnotation(UNKNOWN_INITIALIZATION); - } else { - // A field declared @NotOnlyInitialized with an initialized receiver is - // @Initialized - type.replaceAnnotation(INITIALIZED); - } - } + public CommitmentTreeAnnotator( + InitializationParentAnnotatedTypeFactory initializationAnnotatedTypeFactory) { + super(initializationAnnotatedTypeFactory); } @Override - protected Set> createSupportedTypeQualifiers() { - Set> result = new HashSet<>(); - result.add(UnknownInitialization.class); - result.add(UnderInitialization.class); - result.add(Initialized.class); - result.add(FBCBottom.class); - result.add(PolyInitialized.class); - return result; + public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { + Void result = super.visitMethod(tree, p); + if (TreeUtils.isConstructor(tree)) { + assert p instanceof AnnotatedExecutableType; + AnnotatedExecutableType exeType = (AnnotatedExecutableType) p; + DeclaredType underlyingType = (DeclaredType) exeType.getReturnType().getUnderlyingType(); + AnnotationMirror a = getUnderInitializationAnnotationOfSuperType(underlyingType); + exeType.getReturnType().replaceAnnotation(a); + } + return result; } @Override - public InitializationTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new InitializationTransfer((InitializationAnalysis) analysis); + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror p) { + super.visitNewClass(tree, p); + boolean allInitialized = true; + Type type = ((JCTree) tree).type; + for (ExpressionTree a : tree.getArguments()) { + AnnotatedTypeMirror t = getAnnotatedType(a); + allInitialized &= (isInitialized(t) || isFbcBottom(t)); + } + if (!allInitialized) { + p.replaceAnnotation(createUnderInitializationAnnotation(type)); + return null; + } + p.replaceAnnotation(INITIALIZED); + return null; } - /** - * Returns {@code true}. Initialization cannot be undone, i.e., an @Initialized object always - * stays @Initialized, an @UnderInitialization(A) object always stays @UnderInitialization(A) - * (though it may additionally become @Initialized), etc. - */ @Override - public boolean isImmutable(TypeMirror type) { - return true; - } - - /** - * Creates a {@link UnderInitialization} annotation with the given type as its type frame - * argument. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnderInitialization} annotation with the given argument - */ - public AnnotationMirror createUnderInitializationAnnotation(TypeMirror typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (tree.getKind() != Tree.Kind.NULL_LITERAL) { + type.addAnnotation(INITIALIZED); + } + return super.visitLiteral(tree, type); } @Override - public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { - AnnotatedDeclaredType selfType = super.getSelfType(tree); - - if (assumeInitialized) { - return selfType; - } - - TreePath path = getPath(tree); - AnnotatedDeclaredType enclosing = selfType; - while (path != null && enclosing != null) { - TreePath topLevelMemberPath = findTopLevelClassMemberForTree(path); - if (topLevelMemberPath != null && topLevelMemberPath.getLeaf() != null) { - Tree topLevelMember = topLevelMemberPath.getLeaf(); - if (topLevelMember.getKind() != Tree.Kind.METHOD - || TreeUtils.isConstructor((MethodTree) topLevelMember)) { - setSelfTypeInInitializationCode(tree, enclosing, topLevelMemberPath); - } - path = topLevelMemberPath.getParentPath(); - enclosing = enclosing.getEnclosingType(); - } else { - break; - } - } - - return selfType; - } - - /** - * In the first enclosing class, find the path to the top-level member that contains {@code - * path}. - * - * @param path the path whose leaf is the target - * @return path to a top-level member containing the leaf of {@code path} - */ - @SuppressWarnings("interning:not.interned") // AST node comparison - private @Nullable TreePath findTopLevelClassMemberForTree(TreePath path) { - if (TreeUtils.isClassTree(path.getLeaf())) { - path = path.getParentPath(); - if (path == null) { - return null; - } - } - ClassTree enclosingClass = TreePathUtil.enclosingClass(path); - if (enclosingClass != null) { - List classMembers = enclosingClass.getMembers(); - TreePath searchPath = path; - while (searchPath.getParentPath() != null - && searchPath.getParentPath().getLeaf() != enclosingClass) { - searchPath = searchPath.getParentPath(); - if (classMembers.contains(searchPath.getLeaf())) { - return searchPath; - } - } - } - return null; - } - - /** - * Side-effects argument {@code selfType} to make it @Initialized or @UnderInitialization, - * depending on whether all fields have been set. - * - * @param tree a tree - * @param selfType the type to side-effect - * @param path a path - */ - protected void setSelfTypeInInitializationCode( - Tree tree, AnnotatedDeclaredType selfType, TreePath path) { - ClassTree enclosingClass = TreePathUtil.enclosingClass(path); - Type classType = ((JCTree) enclosingClass).type; - AnnotationMirror annotation = null; - - // If all fields are initialized-only, and they are all initialized, - // then: - // - if the class is final, this is @Initialized - // - otherwise, this is @UnderInitialization(CurrentClass) as - // there might still be subclasses that need initialization. - if (areAllFieldsInitializedOnly(enclosingClass)) { - InitializationStore store = getStoreBefore(tree); - if (store != null - && getUninitializedFields(store, path, false, Collections.emptyList()) - .isEmpty()) { - if (classType.isFinal()) { - annotation = INITIALIZED; - } else { - annotation = createUnderInitializationAnnotation(classType); - } - } - } - - if (annotation == null) { - annotation = getUnderInitializationAnnotationOfSuperType(classType); - } - selfType.replaceAnnotation(annotation); - } - - /** - * Returns an {@link UnderInitialization} annotation that has the superclass of {@code type} as - * type frame. - * - * @param type a type - * @return true an {@link UnderInitialization} for the supertype of {@code type} - */ - protected AnnotationMirror getUnderInitializationAnnotationOfSuperType(TypeMirror type) { - // Find supertype if possible. - AnnotationMirror annotation; - List superTypes = types.directSupertypes(type); - TypeMirror superClass = null; - for (TypeMirror superType : superTypes) { - ElementKind kind = types.asElement(superType).getKind(); - if (kind == ElementKind.CLASS) { - superClass = superType; - break; - } - } - // Create annotation. - if (superClass != null) { - annotation = createUnderInitializationAnnotation(superClass); - } else { - // Use Object as a valid super-class. - annotation = createUnderInitializationAnnotation(Object.class); - } - return annotation; - } - - /** - * Returns whether the specified field is unused, given the specified annotations on the - * receiver. - * - * @param field the field to check - * @param receiverAnnos the annotations on the receiver - * @return whether {@code field} is unused given {@code receiverAnnos} - */ - protected boolean isUnused( - VariableTree field, Collection receiverAnnos) { - if (receiverAnnos.isEmpty()) { - return false; - } - - AnnotationMirror unused = - getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class); - if (unused == null) { - return false; - } - - Name when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement); - for (AnnotationMirror anno : receiverAnnos) { - Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName(); - if (annoName.contentEquals(when)) { - return true; - } - } - - return false; - } - - /** - * Creates a {@link UnderInitialization} annotation with the given type frame. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnderInitialization} annotation with the given argument - */ - public AnnotationMirror createUnderInitializationAnnotation(Class typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); - } - - /** - * Are all fields initialized-only? - * - * @param classTree the class to query - * @return true if all fields are initialized-only - */ - protected boolean areAllFieldsInitializedOnly(ClassTree classTree) { - for (Tree member : classTree.getMembers()) { - if (member.getKind() != Tree.Kind.VARIABLE) { - continue; - } - VariableTree var = (VariableTree) member; - VariableElement varElt = TreeUtils.elementFromDeclaration(var); - // var is not initialized-only - if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) { - // var is not static -- need a check of initializer blocks, - // not of constructor which is where this is used - if (!varElt.getModifiers().contains(Modifier.STATIC)) { - return false; - } - } - } - return true; - } - - /** - * Returns the fields that are possibly uninitialized in a given store, without taking into - * account the target checker. - * - *

I.e., this method returns all fields that have not been assigned, without considering - * fields that may be considered initialized by the target checker even though they have not - * been explicitly assigned. See {@link - * InitializationAnnotatedTypeFactory#getUninitializedFields( InitializationStore, - * CFAbstractStore, TreePath, boolean, Collection)} for a method that does take the target - * checker into account. - * - * @param store a store - * @param path the current path, used to determine the current class - * @param isStatic whether to report static fields or instance fields - * @param receiverAnnotations the annotations on the receiver - * @return the fields that are not yet initialized in a given store - */ - public List getUninitializedFields( - InitializationStore store, - TreePath path, - boolean isStatic, - Collection receiverAnnotations) { - ClassTree currentClass = TreePathUtil.enclosingClass(path); - List fields = InitializationChecker.getAllFields(currentClass); - List uninit = new ArrayList<>(); - for (VariableTree field : fields) { - if (isUnused(field, receiverAnnotations)) { - continue; // don't consider unused fields - } - VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); - if (ElementUtils.isStatic(fieldElem) == isStatic) { - if (!store.isFieldInitialized(fieldElem)) { - uninit.add(field); - } - } - } - return uninit; - } - - /** - * Returns the fields that are initialized in the given store. - * - * @param store a store - * @param path the current path; used to compute the current class - * @return the fields that are initialized in the given store - */ - public List getInitializedFields(InitializationStore store, TreePath path) { - // TODO: Instead of passing the TreePath around, can we use - // getCurrentClassTree? - ClassTree currentClass = TreePathUtil.enclosingClass(path); - List fields = InitializationChecker.getAllFields(currentClass); - List initializedFields = new ArrayList<>(); - for (VariableTree field : fields) { - VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); - if (!ElementUtils.isStatic(fieldElem)) { - if (store.isFieldInitialized(fieldElem)) { - initializedFields.add(field); - } - } - } - return initializedFields; + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + // The most precise element type for `new Object[] {null}` is @FBCBottom, but + // the most useful element type is @Initialized (which is also accurate). + AnnotatedArrayType arrayType = (AnnotatedArrayType) type; + AnnotatedTypeMirror componentType = arrayType.getComponentType(); + if (componentType.hasEffectiveAnnotation(FBCBOTTOM)) { + componentType.replaceAnnotation(INITIALIZED); + } + return null; } @Override - public boolean isNotFullyInitializedReceiver(MethodTree methodTree) { - if (super.isNotFullyInitializedReceiver(methodTree)) { - return true; - } - AnnotatedDeclaredType receiverType = getAnnotatedType(methodTree).getReceiverType(); - if (receiverType != null) { - return isUnknownInitialization(receiverType) || isUnderInitialization(receiverType); - } else { - // There is no receiver e.g. in static methods. - return false; - } - } - - /** - * Creates a {@link UnknownInitialization} annotation with a given type frame. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnknownInitialization} annotation with the given argument - */ - public AnnotationMirror createUnknownInitializationAnnotation(Class typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, UnknownInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); - } - - /** - * Creates an {@link UnknownInitialization} annotation with a given type frame. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnknownInitialization} annotation with the given argument - */ - public AnnotationMirror createUnknownInitializationAnnotation(TypeMirror typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, UnknownInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); - } - - /** - * Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link UnderInitialization} - */ - public boolean isUnderInitialization(AnnotationMirror anno) { - return areSameByClass(anno, UnderInitialization.class); - } - - /** - * Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link UnknownInitialization} - */ - public boolean isUnknownInitialization(AnnotationMirror anno) { - return areSameByClass(anno, UnknownInitialization.class); + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + if (TreeUtils.isArrayLengthAccess(tree)) { + annotatedTypeMirror.replaceAnnotation(INITIALIZED); + } + return super.visitMemberSelect(tree, annotatedTypeMirror); } - /** - * Is {@code anno} the bottom annotation? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link FBCBottom} - */ - public boolean isFbcBottom(AnnotationMirror anno) { - return AnnotationUtils.areSame(anno, FBCBOTTOM); - } + /* The result of a binary or unary operator is either primitive or a String. + * Primitives have no fields and are thus always @Initialized. + * Since all String constructors return @Initialized strings, Strings + * are also always @Initialized. */ - /** - * Is {@code anno} the {@link Initialized} annotation? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link Initialized} - */ - public boolean isInitialized(AnnotationMirror anno) { - return AnnotationUtils.areSame(anno, INITIALIZED); - } - - /** - * Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link UnderInitialization} - */ - public boolean isUnderInitialization(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(UnderInitialization.class); - } - - /** - * Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link UnknownInitialization} - */ - public boolean isUnknownInitialization(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(UnknownInitialization.class); - } - - /** - * Does {@code anno} have the bottom annotation? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link FBCBottom} - */ - public boolean isFbcBottom(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(FBCBottom.class); + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + return null; } - /** - * Does {@code anno} have the annotation {@link Initialized}? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link Initialized} - */ - public boolean isInitialized(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(Initialized.class); + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + return null; } + } - /** - * Return true if the type is initialized with respect to the given frame -- that is, all of the - * fields of the frame are initialized. - * - * @param type the type whose initialization type qualifiers to check - * @param frame a class in {@code type}'s class hierarchy - * @return true if the type is initialized for the given frame - */ - public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) { - if (isInitialized(type)) { - return true; - } + /** The {@link QualifierHierarchy} for the initialization type system. */ + protected class InitializationQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - AnnotationMirror initializationAnno = - type.getEffectiveAnnotationInHierarchy(UNKNOWN_INITIALIZATION); - if (initializationAnno == null) { - initializationAnno = type.getEffectiveAnnotationInHierarchy(UNDER_INITALIZATION); - } + /** Qualifier kind for the @{@link UnknownInitialization} annotation. */ + private final QualifierKind UNKNOWN_INIT; - TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno); - Types types = processingEnv.getTypeUtils(); - return types.isSubtype(typeFrame, types.erasure(frame)); - } + /** Qualifier kind for the @{@link UnderInitialization} annotation. */ + private final QualifierKind UNDER_INIT; - /** - * Returns the type frame (that is, the argument) of a given initialization annotation. - * - * @param annotation a {@link UnderInitialization} or {@link UnknownInitialization} annotation - * @return the annotation's argument - */ - public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) { - if (AnnotationUtils.areSameByName( - annotation, - "org.checkerframework.checker.initialization.qual.UnderInitialization")) { - return AnnotationUtils.getElementValue( - annotation, - underInitializationValueElement, - TypeMirror.class, - objectTypeMirror); - } else { - return AnnotationUtils.getElementValue( - annotation, - unknownInitializationValueElement, - TypeMirror.class, - objectTypeMirror); - } + /** Create an InitializationQualifierHierarchy. */ + protected InitializationQualifierHierarchy() { + super( + InitializationParentAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + InitializationParentAnnotatedTypeFactory.this); + UNKNOWN_INIT = getQualifierKind(UNKNOWN_INITIALIZATION); + UNDER_INIT = getQualifierKind(UNDER_INITALIZATION); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new InitializationQualifierHierarchy(); - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator( - super.createTypeAnnotator(), new CommitmentTypeAnnotator(this)); - } - - /** - * Returns {@code false}. Redundancy in only the initialization hierarchy is ok and may even be - * caused by implicit default annotations. The parent checker should determine whether to warn - * about redundancy. - */ - @Override - public boolean shouldWarnIfStubRedundantWithBytecode() { + public boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (!subKind.isSubtypeOf(superKind)) { return false; + } else if ((subKind == UNDER_INIT && superKind == UNDER_INIT) + || (subKind == UNDER_INIT && superKind == UNKNOWN_INIT) + || (subKind == UNKNOWN_INIT && superKind == UNKNOWN_INIT)) { + // Thus, we only need to look at the type frame. + TypeMirror frame1 = getTypeFrameFromAnnotation(subAnno); + TypeMirror frame2 = getTypeFrameFromAnnotation(superAnno); + return types.isSubtype(frame1, frame2); + } else { + return true; + } } @Override - protected TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because we want our CommitmentTreeAnnotator - // instead of the default PropagationTreeAnnotator - List treeAnnotators = new ArrayList<>(2); - treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); - if (dependentTypesHelper.hasDependentAnnotations()) { - treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); - } - treeAnnotators.add(new CommitmentTreeAnnotator(this)); - return new ListTreeAnnotator(treeAnnotators); - } - - /** - * This type annotator adds the correct UnderInitialization annotation to super constructors. - */ - protected class CommitmentTypeAnnotator extends TypeAnnotator { - - /** - * Creates a new CommitmentTypeAnnotator. - * - * @param atypeFactory this factory - */ - public CommitmentTypeAnnotator(InitializationParentAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitExecutable(AnnotatedExecutableType t, Void p) { - Void result = super.visitExecutable(t, p); - Element elem = t.getElement(); - if (elem.getKind() == ElementKind.CONSTRUCTOR) { - AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); - DeclaredType underlyingType = returnType.getUnderlyingType(); - // TODO: Make sure we check explicit annotations somewhere. - returnType.replaceAnnotation( - getUnderInitializationAnnotationOfSuperType(underlyingType)); - } - return result; - } + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror anno1, + QualifierKind qual1, + AnnotationMirror anno2, + QualifierKind qual2, + QualifierKind lubKind) { + // Handle the case where one is a subtype of the other. + if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { + return anno2; + } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { + return anno1; + } + boolean unknowninit1 = isUnknownInitialization(anno1); + boolean unknowninit2 = isUnknownInitialization(anno2); + boolean underinit1 = isUnderInitialization(anno1); + boolean underinit2 = isUnderInitialization(anno2); + + // Handle @Initialized. + if (isInitialized(anno1)) { + assert underinit2; + return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno2)); + } else if (isInitialized(anno2)) { + assert underinit1; + return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno1)); + } + + if (underinit1 && underinit2) { + return createUnderInitializationAnnotation( + lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2))); + } + + assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); + return createUnknownInitializationAnnotation( + lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2))); } /** - * This tree annotator modifies the propagation tree annotator to add propagation rules for the - * freedom-before-commitment system. + * Returns the least upper bound of two Java basetypes (without annotations). + * + * @param a the first argument + * @param b the second argument + * @return the lub of the two arguments */ - protected class CommitmentTreeAnnotator extends PropagationTreeAnnotator { - - /** - * Creates a new CommitmentTreeAnnotator. - * - * @param initializationAnnotatedTypeFactory this factory - */ - public CommitmentTreeAnnotator( - InitializationParentAnnotatedTypeFactory initializationAnnotatedTypeFactory) { - super(initializationAnnotatedTypeFactory); - } - - @Override - public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { - Void result = super.visitMethod(tree, p); - if (TreeUtils.isConstructor(tree)) { - assert p instanceof AnnotatedExecutableType; - AnnotatedExecutableType exeType = (AnnotatedExecutableType) p; - DeclaredType underlyingType = - (DeclaredType) exeType.getReturnType().getUnderlyingType(); - AnnotationMirror a = getUnderInitializationAnnotationOfSuperType(underlyingType); - exeType.getReturnType().replaceAnnotation(a); - } - return result; - } - - @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror p) { - super.visitNewClass(tree, p); - boolean allInitialized = true; - Type type = ((JCTree) tree).type; - for (ExpressionTree a : tree.getArguments()) { - AnnotatedTypeMirror t = getAnnotatedType(a); - allInitialized &= (isInitialized(t) || isFbcBottom(t)); - } - if (!allInitialized) { - p.replaceAnnotation(createUnderInitializationAnnotation(type)); - return null; - } - p.replaceAnnotation(INITIALIZED); - return null; - } - - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (tree.getKind() != Tree.Kind.NULL_LITERAL) { - type.addAnnotation(INITIALIZED); - } - return super.visitLiteral(tree, type); - } - - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - // The most precise element type for `new Object[] {null}` is @FBCBottom, but - // the most useful element type is @Initialized (which is also accurate). - AnnotatedArrayType arrayType = (AnnotatedArrayType) type; - AnnotatedTypeMirror componentType = arrayType.getComponentType(); - if (componentType.hasEffectiveAnnotation(FBCBOTTOM)) { - componentType.replaceAnnotation(INITIALIZED); - } - return null; - } - - @Override - public Void visitMemberSelect( - MemberSelectTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - if (TreeUtils.isArrayLengthAccess(tree)) { - annotatedTypeMirror.replaceAnnotation(INITIALIZED); - } - return super.visitMemberSelect(tree, annotatedTypeMirror); - } - - /* The result of a binary or unary operator is either primitive or a String. - * Primitives have no fields and are thus always @Initialized. - * Since all String constructors return @Initialized strings, Strings - * are also always @Initialized. */ - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - return null; - } + protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) { + if (types.isSubtype(a, b)) { + return b; + } else if (types.isSubtype(b, a)) { + return a; + } - @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - return null; - } + return TypesUtils.leastUpperBound(a, b, processingEnv); } - /** The {@link QualifierHierarchy} for the initialization type system. */ - protected class InitializationQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** Qualifier kind for the @{@link UnknownInitialization} annotation. */ - private final QualifierKind UNKNOWN_INIT; - - /** Qualifier kind for the @{@link UnderInitialization} annotation. */ - private final QualifierKind UNDER_INIT; - - /** Create an InitializationQualifierHierarchy. */ - protected InitializationQualifierHierarchy() { - super( - InitializationParentAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - InitializationParentAnnotatedTypeFactory.this); - UNKNOWN_INIT = getQualifierKind(UNKNOWN_INITIALIZATION); - UNDER_INIT = getQualifierKind(UNDER_INITALIZATION); - } - - @Override - public boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (!subKind.isSubtypeOf(superKind)) { - return false; - } else if ((subKind == UNDER_INIT && superKind == UNDER_INIT) - || (subKind == UNDER_INIT && superKind == UNKNOWN_INIT) - || (subKind == UNKNOWN_INIT && superKind == UNKNOWN_INIT)) { - // Thus, we only need to look at the type frame. - TypeMirror frame1 = getTypeFrameFromAnnotation(subAnno); - TypeMirror frame2 = getTypeFrameFromAnnotation(superAnno); - return types.isSubtype(frame1, frame2); - } else { - return true; - } - } - - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror anno1, - QualifierKind qual1, - AnnotationMirror anno2, - QualifierKind qual2, - QualifierKind lubKind) { - // Handle the case where one is a subtype of the other. - if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { - return anno2; - } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { - return anno1; - } - boolean unknowninit1 = isUnknownInitialization(anno1); - boolean unknowninit2 = isUnknownInitialization(anno2); - boolean underinit1 = isUnderInitialization(anno1); - boolean underinit2 = isUnderInitialization(anno2); - - // Handle @Initialized. - if (isInitialized(anno1)) { - assert underinit2; - return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno2)); - } else if (isInitialized(anno2)) { - assert underinit1; - return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno1)); - } - - if (underinit1 && underinit2) { - return createUnderInitializationAnnotation( - lubTypeFrame( - getTypeFrameFromAnnotation(anno1), - getTypeFrameFromAnnotation(anno2))); - } - - assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); - return createUnknownInitializationAnnotation( - lubTypeFrame( - getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2))); - } - - /** - * Returns the least upper bound of two Java basetypes (without annotations). - * - * @param a the first argument - * @param b the second argument - * @return the lub of the two arguments - */ - protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) { - if (types.isSubtype(a, b)) { - return b; - } else if (types.isSubtype(b, a)) { - return a; - } - - return TypesUtils.leastUpperBound(a, b, processingEnv); - } - - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror anno1, - QualifierKind qual1, - AnnotationMirror anno2, - QualifierKind qual2, - QualifierKind glbKind) { - // Handle the case where one is a subtype of the other. - if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { - return anno1; - } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { - return anno2; - } - boolean unknowninit1 = isUnknownInitialization(anno1); - boolean unknowninit2 = isUnknownInitialization(anno2); - boolean underinit1 = isUnderInitialization(anno1); - boolean underinit2 = isUnderInitialization(anno2); - - // Handle @Initialized. - if (isInitialized(anno1)) { - assert underinit2; - return FBCBOTTOM; - } else if (isInitialized(anno2)) { - assert underinit1; - return FBCBOTTOM; - } - - TypeMirror typeFrame = - TypesUtils.greatestLowerBound( - getTypeFrameFromAnnotation(anno1), - getTypeFrameFromAnnotation(anno2), - processingEnv); - if (typeFrame.getKind() == TypeKind.ERROR - || typeFrame.getKind() == TypeKind.INTERSECTION) { - return FBCBOTTOM; - } - - if (underinit1 && underinit2) { - return createUnderInitializationAnnotation(typeFrame); - } - - assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); - return createUnderInitializationAnnotation(typeFrame); - } - } + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror anno1, + QualifierKind qual1, + AnnotationMirror anno2, + QualifierKind qual2, + QualifierKind glbKind) { + // Handle the case where one is a subtype of the other. + if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { + return anno1; + } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { + return anno2; + } + boolean unknowninit1 = isUnknownInitialization(anno1); + boolean unknowninit2 = isUnknownInitialization(anno2); + boolean underinit1 = isUnderInitialization(anno1); + boolean underinit2 = isUnderInitialization(anno2); + + // Handle @Initialized. + if (isInitialized(anno1)) { + assert underinit2; + return FBCBOTTOM; + } else if (isInitialized(anno2)) { + assert underinit1; + return FBCBOTTOM; + } + + TypeMirror typeFrame = + TypesUtils.greatestLowerBound( + getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2), processingEnv); + if (typeFrame.getKind() == TypeKind.ERROR || typeFrame.getKind() == TypeKind.INTERSECTION) { + return FBCBOTTOM; + } + + if (underinit1 && underinit2) { + return createUnderInitializationAnnotation(typeFrame); + } + + assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); + return createUnderInitializationAnnotation(typeFrame); + } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java index 09a38400b19..4efdb783383 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java @@ -1,5 +1,9 @@ package org.checkerframework.checker.initialization; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer; import org.checkerframework.dataflow.expression.ClassName; import org.checkerframework.dataflow.expression.FieldAccess; @@ -9,12 +13,6 @@ import org.checkerframework.framework.flow.CFValue; import org.plumelib.util.ToStringComparator; -import java.util.HashSet; -import java.util.Set; - -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; - /** * A store that extends {@code CFAbstractStore} and additionally tracks which fields of the 'self' * reference have been initialized. @@ -23,195 +21,194 @@ */ public class InitializationStore extends CFAbstractStore { - /** The set of fields that are initialized. */ - protected final Set initializedFields; - - /** - * Creates a new InitializationStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics? - */ - public InitializationStore(InitializationAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - // The initialCapacity for the two maps is set to 4, an arbitrary, small value. - initializedFields = new HashSet<>(4); - } - - /** - * {@inheritDoc} - * - *

If the receiver is a field, and has an invariant annotation, then it can be considered - * initialized. - */ - @Override - public void insertValue(JavaExpression je, CFValue value, boolean permitNondeterministic) { - if (!shouldInsert(je, value, permitNondeterministic)) { - return; - } - - super.insertValue(je, value, permitNondeterministic); - - if (je instanceof FieldAccess) { - FieldAccess fa = (FieldAccess) je; - if (fa.getReceiver() instanceof ThisReference - || fa.getReceiver() instanceof ClassName) { - addInitializedField(fa.getField()); - } - } - } - - /** - * A copy constructor. - * - * @param other the store to copy - */ - public InitializationStore(InitializationStore other) { - super(other); - initializedFields = new HashSet<>(other.initializedFields); + /** The set of fields that are initialized. */ + protected final Set initializedFields; + + /** + * Creates a new InitializationStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics? + */ + public InitializationStore(InitializationAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + // The initialCapacity for the two maps is set to 4, an arbitrary, small value. + initializedFields = new HashSet<>(4); + } + + /** + * {@inheritDoc} + * + *

If the receiver is a field, and has an invariant annotation, then it can be considered + * initialized. + */ + @Override + public void insertValue(JavaExpression je, CFValue value, boolean permitNondeterministic) { + if (!shouldInsert(je, value, permitNondeterministic)) { + return; } - /** - * Mark the field identified by the element {@code field} as initialized if it belongs to the - * current class, or is static (in which case there is no aliasing issue and we can just add all - * static fields). - * - * @param field a field that is initialized - */ - public void addInitializedField(FieldAccess field) { - boolean fieldOnThisReference = field.getReceiver() instanceof ThisReference; - boolean staticField = field.isStatic(); - if (fieldOnThisReference || staticField) { - initializedFields.add(field.getField()); - } - } + super.insertValue(je, value, permitNondeterministic); - /** - * Mark the field identified by the element {@code f} as initialized (the caller needs to ensure - * that the field belongs to the current class, or is a static field). - * - * @param f a field that is initialized - */ - public void addInitializedField(VariableElement f) { - initializedFields.add(f); + if (je instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) je; + if (fa.getReceiver() instanceof ThisReference || fa.getReceiver() instanceof ClassName) { + addInitializedField(fa.getField()); + } } - - /** Is the field identified by the element {@code f} initialized? */ - public boolean isFieldInitialized(Element f) { - return initializedFields.contains(f); + } + + /** + * A copy constructor. + * + * @param other the store to copy + */ + public InitializationStore(InitializationStore other) { + super(other); + initializedFields = new HashSet<>(other.initializedFields); + } + + /** + * Mark the field identified by the element {@code field} as initialized if it belongs to the + * current class, or is static (in which case there is no aliasing issue and we can just add all + * static fields). + * + * @param field a field that is initialized + */ + public void addInitializedField(FieldAccess field) { + boolean fieldOnThisReference = field.getReceiver() instanceof ThisReference; + boolean staticField = field.isStatic(); + if (fieldOnThisReference || staticField) { + initializedFields.add(field.getField()); } - - @Override - protected boolean supersetOf(CFAbstractStore o) { - if (!(o instanceof InitializationStore)) { - return false; - } - InitializationStore other = (InitializationStore) o; - - for (Element field : other.initializedFields) { - if (!initializedFields.contains(field)) { - return false; - } - } - - return super.supersetOf(other); - } - - @Override - public InitializationStore leastUpperBound(InitializationStore other) { - InitializationStore result = super.leastUpperBound(other); - - result.initializedFields.addAll(other.initializedFields); - result.initializedFields.retainAll(initializedFields); - - return result; + } + + /** + * Mark the field identified by the element {@code f} as initialized (the caller needs to ensure + * that the field belongs to the current class, or is a static field). + * + * @param f a field that is initialized + */ + public void addInitializedField(VariableElement f) { + initializedFields.add(f); + } + + /** Is the field identified by the element {@code f} initialized? */ + public boolean isFieldInitialized(Element f) { + return initializedFields.contains(f); + } + + @Override + protected boolean supersetOf(CFAbstractStore o) { + if (!(o instanceof InitializationStore)) { + return false; } + InitializationStore other = (InitializationStore) o; - /* - * TODO: implement a meaningful `isDeclaredInitialized`. - * - @Override - protected CFValue newFieldValueAfterMethodCall( - FieldAccess fieldAccess, - GenericAnnotatedTypeFactory atypeFactory, - CFValue value) { - if (isDeclaredInitialized(fieldAccess)) { - return value; - } - - return super.newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); - } - */ - - /* - * Determine whether the given field is declared as {@link Initialized} (taking into account - * viewpoint adaption for {@link NotOnlyInitialized}). - * - * @param fieldAccess the field to check - * @return whether the given field is declared as {@link Initialized} (taking into account - * viewpoint adaption for {@link NotOnlyInitialized}) - * - protected boolean isDeclaredInitialized(FieldAccess fieldAccess) { - // Returning false is the conservative answer, but not much faster than asking the ATF. + for (Element field : other.initializedFields) { + if (!initializedFields.contains(field)) { return false; - /* - InitializationParentAnnotatedTypeFactory atypeFactory = - (InitializationParentAnnotatedTypeFactory) analysis.getTypeFactory(); - AnnotatedTypeMirror declField = atypeFactory.getAnnotatedType(fieldAccess.getField()); - if (!declField.hasAnnotation(atypeFactory.INITIALIZED)) { - return false; - } - - AnnotatedTypeMirror receiverType; - if (thisValue != null - && thisValue.getUnderlyingType().getKind() != TypeKind.ERROR - && thisValue.getUnderlyingType().getKind() != TypeKind.NULL) { - receiverType = - AnnotatedTypeMirror.createType( - thisValue.getUnderlyingType(), atypeFactory, false); - for (AnnotationMirror anno : thisValue.getAnnotations()) { - receiverType.replaceAnnotation(anno); - } - } else if (!fieldAccess.isStatic()) { - receiverType = - AnnotatedTypeMirror.createType( - fieldAccess.getReceiver().getType(), atypeFactory, false) - .getErased(); - receiverType.addAnnotations(atypeFactory.getQualifierHierarchy().getTopAnnotations()); - } else { - receiverType = null; - } - - if (receiverType != null) { - return receiverType.hasAnnotation(atypeFactory.INITIALIZED); - } else { - // The field is static and INITIALIZED, so there is nothing else to check. - return true; - } - } - */ - - @Override - protected String internalVisualize(CFGVisualizer viz) { - String superVisualize = super.internalVisualize(viz); - - String initializedVisualize = - viz.visualizeStoreKeyVal( - "initialized fields", ToStringComparator.sorted(initializedFields)); - - if (superVisualize.isEmpty()) { - return String.join(viz.getSeparator(), initializedVisualize); - } else { - return String.join(viz.getSeparator(), superVisualize, initializedVisualize); - } + } } - /** - * Returns the analysis associated with this store. - * - * @return the analysis associated with this store - */ - public InitializationAnalysis getAnalysis() { - return (InitializationAnalysis) analysis; + return super.supersetOf(other); + } + + @Override + public InitializationStore leastUpperBound(InitializationStore other) { + InitializationStore result = super.leastUpperBound(other); + + result.initializedFields.addAll(other.initializedFields); + result.initializedFields.retainAll(initializedFields); + + return result; + } + + /* + * TODO: implement a meaningful `isDeclaredInitialized`. + * + @Override + protected CFValue newFieldValueAfterMethodCall( + FieldAccess fieldAccess, + GenericAnnotatedTypeFactory atypeFactory, + CFValue value) { + if (isDeclaredInitialized(fieldAccess)) { + return value; + } + + return super.newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + } + */ + + /* + * Determine whether the given field is declared as {@link Initialized} (taking into account + * viewpoint adaption for {@link NotOnlyInitialized}). + * + * @param fieldAccess the field to check + * @return whether the given field is declared as {@link Initialized} (taking into account + * viewpoint adaption for {@link NotOnlyInitialized}) + * + protected boolean isDeclaredInitialized(FieldAccess fieldAccess) { + // Returning false is the conservative answer, but not much faster than asking the ATF. + return false; + /* + InitializationParentAnnotatedTypeFactory atypeFactory = + (InitializationParentAnnotatedTypeFactory) analysis.getTypeFactory(); + AnnotatedTypeMirror declField = atypeFactory.getAnnotatedType(fieldAccess.getField()); + if (!declField.hasAnnotation(atypeFactory.INITIALIZED)) { + return false; + } + + AnnotatedTypeMirror receiverType; + if (thisValue != null + && thisValue.getUnderlyingType().getKind() != TypeKind.ERROR + && thisValue.getUnderlyingType().getKind() != TypeKind.NULL) { + receiverType = + AnnotatedTypeMirror.createType( + thisValue.getUnderlyingType(), atypeFactory, false); + for (AnnotationMirror anno : thisValue.getAnnotations()) { + receiverType.replaceAnnotation(anno); + } + } else if (!fieldAccess.isStatic()) { + receiverType = + AnnotatedTypeMirror.createType( + fieldAccess.getReceiver().getType(), atypeFactory, false) + .getErased(); + receiverType.addAnnotations(atypeFactory.getQualifierHierarchy().getTopAnnotations()); + } else { + receiverType = null; + } + + if (receiverType != null) { + return receiverType.hasAnnotation(atypeFactory.INITIALIZED); + } else { + // The field is static and INITIALIZED, so there is nothing else to check. + return true; + } + } + */ + + @Override + protected String internalVisualize(CFGVisualizer viz) { + String superVisualize = super.internalVisualize(viz); + + String initializedVisualize = + viz.visualizeStoreKeyVal( + "initialized fields", ToStringComparator.sorted(initializedFields)); + + if (superVisualize.isEmpty()) { + return String.join(viz.getSeparator(), initializedVisualize); + } else { + return String.join(viz.getSeparator(), superVisualize, initializedVisualize); } + } + + /** + * Returns the analysis associated with this store. + * + * @return the analysis associated with this store + */ + public InitializationAnalysis getAnalysis() { + return (InitializationAnalysis) analysis; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java index b18487a80aa..ba1c068b2a3 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java @@ -3,7 +3,14 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.code.Symbol; - +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; @@ -18,16 +25,6 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; -import java.util.ArrayList; -import java.util.List; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; - /** * A transfer function that extends {@link CFAbstractTransfer} and tracks {@link * InitializationStore}s. In addition to the features of {@link CFAbstractTransfer}, this transfer @@ -43,113 +40,113 @@ * */ public class InitializationTransfer - extends CFAbstractTransfer { - - /** The initialization type factory */ - protected final InitializationParentAnnotatedTypeFactory atypeFactory; - - /** - * Create a new InitializationTransfer for the given analysis. - * - * @param analysis init analysi.s - */ - public InitializationTransfer(InitializationAnalysis analysis) { - super(analysis); - this.atypeFactory = analysis.getTypeFactory(); + extends CFAbstractTransfer { + + /** The initialization type factory */ + protected final InitializationParentAnnotatedTypeFactory atypeFactory; + + /** + * Create a new InitializationTransfer for the given analysis. + * + * @param analysis init analysi.s + */ + public InitializationTransfer(InitializationAnalysis analysis) { + super(analysis); + this.atypeFactory = analysis.getTypeFactory(); + } + + /** + * Returns the fields that can safely be considered initialized after the method call {@code + * node}. + * + * @param node a method call + * @return the fields that are initialized after the method call + */ + protected List initializedFieldsAfterCall(MethodInvocationNode node) { + List result = new ArrayList<>(); + MethodInvocationTree tree = node.getTree(); + ExecutableElement method = TreeUtils.elementFromUse(tree); + boolean isConstructor = method.getSimpleName().contentEquals(""); + Node receiver = node.getTarget().getReceiver(); + String methodString = tree.getMethodSelect().toString(); + + // Case 1: After a call to the constructor of the same class, all + // fields are guaranteed to be initialized. + if (isConstructor && receiver instanceof ThisNode && methodString.equals("this")) { + ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); + TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); + markFieldsAsInitialized(result, clazzElem); } - /** - * Returns the fields that can safely be considered initialized after the method call {@code - * node}. - * - * @param node a method call - * @return the fields that are initialized after the method call - */ - protected List initializedFieldsAfterCall(MethodInvocationNode node) { - List result = new ArrayList<>(); - MethodInvocationTree tree = node.getTree(); - ExecutableElement method = TreeUtils.elementFromUse(tree); - boolean isConstructor = method.getSimpleName().contentEquals(""); - Node receiver = node.getTarget().getReceiver(); - String methodString = tree.getMethodSelect().toString(); - - // Case 1: After a call to the constructor of the same class, all - // fields are guaranteed to be initialized. - if (isConstructor && receiver instanceof ThisNode && methodString.equals("this")) { - ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); - TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); - markFieldsAsInitialized(result, clazzElem); - } - - // Case 4: After a call to the constructor of the super class, all - // fields of any super class are guaranteed to be initialized. - if (isConstructor && receiver instanceof ThisNode && methodString.equals("super")) { - ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); - TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); - TypeMirror superClass = clazzElem.getSuperclass(); - - while (superClass != null && superClass.getKind() != TypeKind.NONE) { - clazzElem = (TypeElement) analysis.getTypes().asElement(superClass); - superClass = clazzElem.getSuperclass(); - markFieldsAsInitialized(result, clazzElem); - } - } - - return result; + // Case 4: After a call to the constructor of the super class, all + // fields of any super class are guaranteed to be initialized. + if (isConstructor && receiver instanceof ThisNode && methodString.equals("super")) { + ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); + TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); + TypeMirror superClass = clazzElem.getSuperclass(); + + while (superClass != null && superClass.getKind() != TypeKind.NONE) { + clazzElem = (TypeElement) analysis.getTypes().asElement(superClass); + superClass = clazzElem.getSuperclass(); + markFieldsAsInitialized(result, clazzElem); + } } - /** - * Adds all the fields of the class {@code clazzElem} to the list of initialized fields {@code - * result}. - * - * @param result the list of initialized fields - * @param clazzElem the class whose fields to add - */ - protected void markFieldsAsInitialized(List result, TypeElement clazzElem) { - List fields = ElementFilter.fieldsIn(clazzElem.getEnclosedElements()); - for (VariableElement field : fields) { - if (((Symbol) field).type.tsym.completer != Symbol.Completer.NULL_COMPLETER - || ((Symbol) field).type.getKind() == TypeKind.ERROR) { - // If the type is not completed yet, we might run into trouble. Skip the field. - // TODO: is there a nicer solution? - // This was raised by Issue #244. - continue; - } - result.add(field); - } + return result; + } + + /** + * Adds all the fields of the class {@code clazzElem} to the list of initialized fields {@code + * result}. + * + * @param result the list of initialized fields + * @param clazzElem the class whose fields to add + */ + protected void markFieldsAsInitialized(List result, TypeElement clazzElem) { + List fields = ElementFilter.fieldsIn(clazzElem.getEnclosedElements()); + for (VariableElement field : fields) { + if (((Symbol) field).type.tsym.completer != Symbol.Completer.NULL_COMPLETER + || ((Symbol) field).type.getKind() == TypeKind.ERROR) { + // If the type is not completed yet, we might run into trouble. Skip the field. + // TODO: is there a nicer solution? + // This was raised by Issue #244. + continue; + } + result.add(field); } - - @Override - public TransferResult visitAssignment( - AssignmentNode n, TransferInput in) { - TransferResult result = super.visitAssignment(n, in); - JavaExpression lhs = JavaExpression.fromNode(n.getTarget()); - - // If this is an assignment to a field of 'this', then mark the field as initialized. - if (!lhs.containsUnknown() && lhs instanceof FieldAccess) { - FieldAccess fa = (FieldAccess) lhs; - // Only a ternary expression may cause a conditional transfer result, e.g. - // condExpr#num0 = (obj instanceof List) - // In such cases, the LHS is never a FieldAccess, so we can assert that result - // is a regular transfer result. This is important because otherwise the - // addInitializedField would be called on a temporary, merged store. - assert result instanceof RegularTransferResult; - result.getRegularStore().addInitializedField(fa); - } - return result; + } + + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput in) { + TransferResult result = super.visitAssignment(n, in); + JavaExpression lhs = JavaExpression.fromNode(n.getTarget()); + + // If this is an assignment to a field of 'this', then mark the field as initialized. + if (!lhs.containsUnknown() && lhs instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) lhs; + // Only a ternary expression may cause a conditional transfer result, e.g. + // condExpr#num0 = (obj instanceof List) + // In such cases, the LHS is never a FieldAccess, so we can assert that result + // is a regular transfer result. This is important because otherwise the + // addInitializedField would be called on a temporary, merged store. + assert result instanceof RegularTransferResult; + result.getRegularStore().addInitializedField(fa); } - - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = super.visitMethodInvocation(n, in); - List newlyInitializedFields = initializedFieldsAfterCall(n); - if (!newlyInitializedFields.isEmpty()) { - for (VariableElement f : newlyInitializedFields) { - result.getThenStore().addInitializedField(f); - result.getElseStore().addInitializedField(f); - } - } - return result; + return result; + } + + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = super.visitMethodInvocation(n, in); + List newlyInitializedFields = initializedFieldsAfterCall(n); + if (!newlyInitializedFields.isEmpty()) { + for (VariableElement f : newlyInitializedFields) { + result.getThenStore().addInitializedField(f); + result.getElseStore().addInitializedField(f); + } } + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java index 9cb5828fe09..c1c691ad972 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java @@ -12,7 +12,15 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -29,17 +37,6 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.StringJoiner; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; - /* NO-AFU import org.checkerframework.common.wholeprograminference.WholeProgramInference; */ @@ -51,357 +48,351 @@ */ public class InitializationVisitor extends BaseTypeVisitor { - // Error message keys - private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_TYPE = - "initialization.invalid.field.type"; - private static final @CompilerMessageKey String COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE = - "initialization.invalid.constructor.return.type"; - private static final @CompilerMessageKey String - COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION = - "initialization.invalid.field.write.unknown"; - private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED = - "initialization.invalid.field.write.initialized"; - - /** List of fields in the current compilation unit that have been initialized. */ - protected final List initializedFields; - - /** The value of the assumeInitialized option. */ - protected final boolean assumeInitialized; - - /** - * Create an InitializationVisitor. - * - * @param checker the initialization checker - */ - public InitializationVisitor(BaseTypeChecker checker) { - super(checker); - initializedFields = new ArrayList<>(); - assumeInitialized = checker.hasOption("assumeInitialized"); + // Error message keys + private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_TYPE = + "initialization.invalid.field.type"; + private static final @CompilerMessageKey String COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE = + "initialization.invalid.constructor.return.type"; + private static final @CompilerMessageKey String + COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION = + "initialization.invalid.field.write.unknown"; + private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED = + "initialization.invalid.field.write.initialized"; + + /** List of fields in the current compilation unit that have been initialized. */ + protected final List initializedFields; + + /** The value of the assumeInitialized option. */ + protected final boolean assumeInitialized; + + /** + * Create an InitializationVisitor. + * + * @param checker the initialization checker + */ + public InitializationVisitor(BaseTypeChecker checker) { + super(checker); + initializedFields = new ArrayList<>(); + assumeInitialized = checker.hasOption("assumeInitialized"); + } + + @Override + protected InitializationAnnotatedTypeFactory createTypeFactory() { + return new InitializationAnnotatedTypeFactory(checker); + } + + @Override + public void visit(TreePath path) { + // This visitor does nothing if init checking is turned off. + if (!assumeInitialized) { + super.visit(path); } - - @Override - protected InitializationAnnotatedTypeFactory createTypeFactory() { - return new InitializationAnnotatedTypeFactory(checker); - } - - @Override - public void visit(TreePath path) { - // This visitor does nothing if init checking is turned off. - if (!assumeInitialized) { - super.visit(path); + } + + @Override + public void setRoot(CompilationUnitTree root) { + // Clean up the cache of initialized fields once per compilation unit. + // Alternatively, but harder to determine, this could be done once per top-level class. + initializedFields.clear(); + super.setRoot(root); + } + + @Override + protected void checkConstructorInvocation( + AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { + // Receiver annotations for constructors are forbidden, therefore no check is necessary. + // TODO: nested constructors can have receivers! + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Nothing to check + } + + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + // Nothing to check + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // field write of the form x.f = y + if (TreeUtils.isFieldAccess(varTree)) { + // cast is safe: a field access can only be an IdentifierTree or MemberSelectTree + ExpressionTree lhs = (ExpressionTree) varTree; + VariableElement el = TreeUtils.variableElementFromUse(lhs); + AnnotatedTypeMirror lhsReceiverType = atypeFactory.getReceiverType(lhs); + AnnotatedTypeMirror valueExpType = atypeFactory.getAnnotatedType(valueExpTree); + // the special FBC rules do not apply if there is an explicit + // UnknownInitialization annotation + AnnotationMirrorSet fieldAnnotations = atypeFactory.getAnnotatedType(el).getAnnotations(); + if (!AnnotationUtils.containsSameByName( + fieldAnnotations, atypeFactory.UNKNOWN_INITIALIZATION)) { + if (!ElementUtils.isStatic(el) + && !(atypeFactory.isInitialized(valueExpType) + || atypeFactory.isUnderInitialization(lhsReceiverType) + || atypeFactory.isFbcBottom(valueExpType))) { + @CompilerMessageKey String err; + if (atypeFactory.isInitialized(lhsReceiverType)) { + err = COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED; + } else { + err = COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION; + } + checker.reportError(varTree, err, varTree); + return false; // prevent issuing another error about subtyping } + } } - - @Override - public void setRoot(CompilationUnitTree root) { - // Clean up the cache of initialized fields once per compilation unit. - // Alternatively, but harder to determine, this could be done once per top-level class. - initializedFields.clear(); - super.setRoot(root); - } - - @Override - protected void checkConstructorInvocation( - AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { - // Receiver annotations for constructors are forbidden, therefore no check is necessary. - // TODO: nested constructors can have receivers! - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Nothing to check - } - - @Override - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { - // Nothing to check + return super.commonAssignmentCheck(varTree, valueExpTree, errorKey, extraArgs); + } + + @Override + protected void checkExceptionParameter(CatchTree node) { + // TODO Issue 363 + // https://github.com/eisop/checker-framework/issues/363 + } + + @Override + public void processClassTree(ClassTree tree) { + // go through all members and look for initializers. + // save all fields that are initialized and do not report errors about + // them later when checking constructors. + for (Tree member : tree.getMembers()) { + if (member.getKind() == Tree.Kind.BLOCK && !((BlockTree) member).isStatic()) { + BlockTree block = (BlockTree) member; + InitializationStore store = atypeFactory.getRegularExitStore(block); + + // Add field values for fields with an initializer. + for (FieldInitialValue fieldInitialValue : + store.getAnalysis().getFieldInitialValues()) { + if (fieldInitialValue.initializer != null) { + store.addInitializedField(fieldInitialValue.fieldDecl.getField()); + } + } + List init = atypeFactory.getInitializedFields(store, getCurrentPath()); + initializedFields.addAll(init); + } } - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // field write of the form x.f = y - if (TreeUtils.isFieldAccess(varTree)) { - // cast is safe: a field access can only be an IdentifierTree or MemberSelectTree - ExpressionTree lhs = (ExpressionTree) varTree; - VariableElement el = TreeUtils.variableElementFromUse(lhs); - AnnotatedTypeMirror lhsReceiverType = atypeFactory.getReceiverType(lhs); - AnnotatedTypeMirror valueExpType = atypeFactory.getAnnotatedType(valueExpTree); - // the special FBC rules do not apply if there is an explicit - // UnknownInitialization annotation - AnnotationMirrorSet fieldAnnotations = - atypeFactory.getAnnotatedType(el).getAnnotations(); - if (!AnnotationUtils.containsSameByName( - fieldAnnotations, atypeFactory.UNKNOWN_INITIALIZATION)) { - if (!ElementUtils.isStatic(el) - && !(atypeFactory.isInitialized(valueExpType) - || atypeFactory.isUnderInitialization(lhsReceiverType) - || atypeFactory.isFbcBottom(valueExpType))) { - @CompilerMessageKey String err; - if (atypeFactory.isInitialized(lhsReceiverType)) { - err = COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED; - } else { - err = COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION; - } - checker.reportError(varTree, err, varTree); - return false; // prevent issuing another error about subtyping - } - } + super.processClassTree(tree); + + // Warn about uninitialized static fields. + Tree.Kind nodeKind = tree.getKind(); + // Skip interfaces (and annotations, which are interfaces). In an interface, every static + // field must be initialized. Java forbids uninitialized variables and static initializer + // blocks. + if (nodeKind != Tree.Kind.INTERFACE && nodeKind != Tree.Kind.ANNOTATION_TYPE) { + // See GenericAnnotatedTypeFactory.performFlowAnalysis for why we use + // the regular exit store of the class here. + InitializationStore store = atypeFactory.getRegularExitStore(tree); + if (store != null) { + // Add field values for fields with an initializer. + for (FieldInitialValue fieldInitialValue : + store.getAnalysis().getFieldInitialValues()) { + if (fieldInitialValue.initializer != null) { + store.addInitializedField(fieldInitialValue.fieldDecl.getField()); + } } - return super.commonAssignmentCheck(varTree, valueExpTree, errorKey, extraArgs); - } + } - @Override - protected void checkExceptionParameter(CatchTree node) { - // TODO Issue 363 - // https://github.com/eisop/checker-framework/issues/363 + List receiverAnnotations = Collections.emptyList(); + checkFieldsInitialized(tree, true, store, receiverAnnotations); } - - @Override - public void processClassTree(ClassTree tree) { - // go through all members and look for initializers. - // save all fields that are initialized and do not report errors about - // them later when checking constructors. - for (Tree member : tree.getMembers()) { - if (member.getKind() == Tree.Kind.BLOCK && !((BlockTree) member).isStatic()) { - BlockTree block = (BlockTree) member; - InitializationStore store = atypeFactory.getRegularExitStore(block); - - // Add field values for fields with an initializer. - for (FieldInitialValue fieldInitialValue : - store.getAnalysis().getFieldInitialValues()) { - if (fieldInitialValue.initializer != null) { - store.addInitializedField(fieldInitialValue.fieldDecl.getField()); - } - } - List init = - atypeFactory.getInitializedFields(store, getCurrentPath()); - initializedFields.addAll(init); - } + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + if (TreeUtils.isConstructor(tree)) { + Collection returnTypeAnnotations = + AnnotationUtils.getExplicitAnnotationsOnConstructorResult(tree); + // check for invalid constructor return type + for (Class c : atypeFactory.getSupportedTypeQualifiers()) { + for (AnnotationMirror a : returnTypeAnnotations) { + if (atypeFactory.areSameByClass(a, c)) { + checker.reportError(tree, COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE, tree); + break; + } } + } - super.processClassTree(tree); - - // Warn about uninitialized static fields. - Tree.Kind nodeKind = tree.getKind(); - // Skip interfaces (and annotations, which are interfaces). In an interface, every static - // field must be initialized. Java forbids uninitialized variables and static initializer - // blocks. - if (nodeKind != Tree.Kind.INTERFACE && nodeKind != Tree.Kind.ANNOTATION_TYPE) { - // See GenericAnnotatedTypeFactory.performFlowAnalysis for why we use - // the regular exit store of the class here. - InitializationStore store = atypeFactory.getRegularExitStore(tree); - if (store != null) { - // Add field values for fields with an initializer. - for (FieldInitialValue fieldInitialValue : - store.getAnalysis().getFieldInitialValues()) { - if (fieldInitialValue.initializer != null) { - store.addInitializedField(fieldInitialValue.fieldDecl.getField()); - } - } - } - - List receiverAnnotations = Collections.emptyList(); - checkFieldsInitialized(tree, true, store, receiverAnnotations); - } - } + // Check that all fields have been initialized at the end of the constructor. + boolean isStatic = false; - @Override - public Void visitMethod(MethodTree tree, Void p) { - if (TreeUtils.isConstructor(tree)) { - Collection returnTypeAnnotations = - AnnotationUtils.getExplicitAnnotationsOnConstructorResult(tree); - // check for invalid constructor return type - for (Class c : atypeFactory.getSupportedTypeQualifiers()) { - for (AnnotationMirror a : returnTypeAnnotations) { - if (atypeFactory.areSameByClass(a, c)) { - checker.reportError(tree, COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE, tree); - break; - } - } - } - - // Check that all fields have been initialized at the end of the constructor. - boolean isStatic = false; - - InitializationStore store = atypeFactory.getRegularExitStore(tree); - List receiverAnnotations = getAllReceiverAnnotations(tree); - checkFieldsInitialized(tree, isStatic, store, receiverAnnotations); - } - return super.visitMethod(tree, p); + InitializationStore store = atypeFactory.getRegularExitStore(tree); + List receiverAnnotations = getAllReceiverAnnotations(tree); + checkFieldsInitialized(tree, isStatic, store, receiverAnnotations); } - - /** - * The assignment/variable/method invocation tree currently being checked. - * - *

In the case that the right-hand side is an object, this is used by {@link - * #reportCommonAssignmentError(AnnotatedTypeMirror, AnnotatedTypeMirror, Tree, String, - * Object...)} to get the correct store value for the right-hand side's fields and check whether - * they are initialized according to the target checker. - */ - protected Tree commonAssignmentTree; - - @Override - public Void visitVariable(VariableTree tree, Void p) { - Tree oldCommonAssignmentTree = commonAssignmentTree; - commonAssignmentTree = tree; - // is this a field (and not a local variable)? - if (TreeUtils.elementFromDeclaration(tree).getKind().isField()) { - AnnotationMirrorSet annotationMirrors = - atypeFactory.getAnnotatedType(tree).getExplicitAnnotations(); - // Fields cannot have commitment annotations. - for (Class c : atypeFactory.getSupportedTypeQualifiers()) { - for (AnnotationMirror a : annotationMirrors) { - if (atypeFactory.isUnknownInitialization(a)) { - continue; // unknown initialization is allowed - } - if (atypeFactory.areSameByClass(a, c)) { - checker.reportError(tree, COMMITMENT_INVALID_FIELD_TYPE, tree); - break; - } - } - } + return super.visitMethod(tree, p); + } + + /** + * The assignment/variable/method invocation tree currently being checked. + * + *

In the case that the right-hand side is an object, this is used by {@link + * #reportCommonAssignmentError(AnnotatedTypeMirror, AnnotatedTypeMirror, Tree, String, + * Object...)} to get the correct store value for the right-hand side's fields and check whether + * they are initialized according to the target checker. + */ + protected Tree commonAssignmentTree; + + @Override + public Void visitVariable(VariableTree tree, Void p) { + Tree oldCommonAssignmentTree = commonAssignmentTree; + commonAssignmentTree = tree; + // is this a field (and not a local variable)? + if (TreeUtils.elementFromDeclaration(tree).getKind().isField()) { + AnnotationMirrorSet annotationMirrors = + atypeFactory.getAnnotatedType(tree).getExplicitAnnotations(); + // Fields cannot have commitment annotations. + for (Class c : atypeFactory.getSupportedTypeQualifiers()) { + for (AnnotationMirror a : annotationMirrors) { + if (atypeFactory.isUnknownInitialization(a)) { + continue; // unknown initialization is allowed + } + if (atypeFactory.areSameByClass(a, c)) { + checker.reportError(tree, COMMITMENT_INVALID_FIELD_TYPE, tree); + break; + } } - super.visitVariable(tree, p); - commonAssignmentTree = oldCommonAssignmentTree; - return null; + } } - - @Override - public Void visitAssignment(AssignmentTree node, Void p) { - Tree oldCommonAssignmentTree = commonAssignmentTree; - commonAssignmentTree = node; - super.visitAssignment(node, p); - commonAssignmentTree = oldCommonAssignmentTree; - return null; + super.visitVariable(tree, p); + commonAssignmentTree = oldCommonAssignmentTree; + return null; + } + + @Override + public Void visitAssignment(AssignmentTree node, Void p) { + Tree oldCommonAssignmentTree = commonAssignmentTree; + commonAssignmentTree = node; + super.visitAssignment(node, p); + commonAssignmentTree = oldCommonAssignmentTree; + return null; + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void p) { + Tree oldCommonAssignmentTree = commonAssignmentTree; + commonAssignmentTree = node; + super.visitMethodInvocation(node, p); + commonAssignmentTree = oldCommonAssignmentTree; + return null; + } + + /** + * Returns the full list of annotations on the receiver. + * + * @param tree a method declaration + * @return all the annotations on the method's receiver + */ + private List getAllReceiverAnnotations(MethodTree tree) { + // TODO: get access to a Types instance and use it to get receiver type + // Or, extend ExecutableElement with such a method. + // Note that we cannot use the receiver type from AnnotatedExecutableType, because that + // would only have the nullness annotations; here we want to see all annotations on the + // receiver. + if (TreeUtils.isConstructor(tree)) { + com.sun.tools.javac.code.Symbol meth = + (com.sun.tools.javac.code.Symbol) TreeUtils.elementFromDeclaration(tree); + return meth.getRawTypeAttributes(); } - - @Override - public Void visitMethodInvocation(MethodInvocationTree node, Void p) { - Tree oldCommonAssignmentTree = commonAssignmentTree; - commonAssignmentTree = node; - super.visitMethodInvocation(node, p); - commonAssignmentTree = oldCommonAssignmentTree; - return null; + return Collections.emptyList(); + } + + /** + * Checks that all fields (all static fields if {@code staticFields} is true) are initialized at + * the end of a given constructor or static class initializer. + * + * @param tree a {@link ClassTree} if {@code staticFields} is true; a {@link MethodTree} for a + * constructor if {@code staticFields} is false. This is where errors are reported, if they + * are not reported at the fields themselves + * @param staticFields whether to check static fields or instance fields + * @param initExitStore the initialization exit store for the constructor or static initializer + * @param receiverAnnotations the annotations on the receiver + */ + protected void checkFieldsInitialized( + Tree tree, + boolean staticFields, + InitializationStore initExitStore, + List receiverAnnotations) { + // If the store is null, then the constructor cannot terminate successfully + if (initExitStore == null) { + return; } - /** - * Returns the full list of annotations on the receiver. - * - * @param tree a method declaration - * @return all the annotations on the method's receiver - */ - private List getAllReceiverAnnotations(MethodTree tree) { - // TODO: get access to a Types instance and use it to get receiver type - // Or, extend ExecutableElement with such a method. - // Note that we cannot use the receiver type from AnnotatedExecutableType, because that - // would only have the nullness annotations; here we want to see all annotations on the - // receiver. - if (TreeUtils.isConstructor(tree)) { - com.sun.tools.javac.code.Symbol meth = - (com.sun.tools.javac.code.Symbol) TreeUtils.elementFromDeclaration(tree); - return meth.getRawTypeAttributes(); - } - return Collections.emptyList(); + // Compact canonical record constructors do not generate visible assignments in the source, + // but by definition they assign to all the record's fields so we don't need to + // check for uninitialized fields in them: + if (tree.getKind() == Tree.Kind.METHOD + && TreeUtils.isCompactCanonicalRecordConstructor((MethodTree) tree)) { + return; } - /** - * Checks that all fields (all static fields if {@code staticFields} is true) are initialized at - * the end of a given constructor or static class initializer. - * - * @param tree a {@link ClassTree} if {@code staticFields} is true; a {@link MethodTree} for a - * constructor if {@code staticFields} is false. This is where errors are reported, if they - * are not reported at the fields themselves - * @param staticFields whether to check static fields or instance fields - * @param initExitStore the initialization exit store for the constructor or static initializer - * @param receiverAnnotations the annotations on the receiver - */ - protected void checkFieldsInitialized( - Tree tree, - boolean staticFields, - InitializationStore initExitStore, - List receiverAnnotations) { - // If the store is null, then the constructor cannot terminate successfully - if (initExitStore == null) { - return; + GenericAnnotatedTypeFactory targetFactory = + checker.getTypeFactoryOfSubcheckerOrNull( + ((InitializationChecker) checker).getTargetCheckerClass()); + // The target checker's store corresponding to initExitStore + CFAbstractStore targetExitStore = targetFactory.getRegularExitStore(tree); + List uninitializedFields = + atypeFactory.getUninitializedFields( + initExitStore, targetExitStore, getCurrentPath(), staticFields, receiverAnnotations); + uninitializedFields.removeAll(initializedFields); + + // If we are checking initialization of a class's static fields or of a default constructor, + // we issue an error for every uninitialized field at the respective field declaration. + // If we are checking a non-default constructor, we issue a single error at the constructor + // declaration. + boolean errorAtField = staticFields || TreeUtils.isSynthetic((MethodTree) tree); + + String errorMsg = + (staticFields + ? "initialization.static.field.uninitialized" + : errorAtField + ? "initialization.field.uninitialized" + : "initialization.fields.uninitialized"); + + // Remove fields with a relevant @SuppressWarnings annotation + uninitializedFields.removeIf( + f -> checker.shouldSuppressWarnings(TreeUtils.elementFromDeclaration(f), errorMsg)); + + if (!uninitializedFields.isEmpty()) { + if (errorAtField) { + // Issue each error at the relevant field + for (VariableTree f : uninitializedFields) { + checker.reportError(f, errorMsg, f.getName()); } - - // Compact canonical record constructors do not generate visible assignments in the source, - // but by definition they assign to all the record's fields so we don't need to - // check for uninitialized fields in them: - if (tree.getKind() == Tree.Kind.METHOD - && TreeUtils.isCompactCanonicalRecordConstructor((MethodTree) tree)) { - return; - } - - GenericAnnotatedTypeFactory targetFactory = - checker.getTypeFactoryOfSubcheckerOrNull( - ((InitializationChecker) checker).getTargetCheckerClass()); - // The target checker's store corresponding to initExitStore - CFAbstractStore targetExitStore = targetFactory.getRegularExitStore(tree); - List uninitializedFields = - atypeFactory.getUninitializedFields( - initExitStore, - targetExitStore, - getCurrentPath(), - staticFields, - receiverAnnotations); - uninitializedFields.removeAll(initializedFields); - - // If we are checking initialization of a class's static fields or of a default constructor, - // we issue an error for every uninitialized field at the respective field declaration. - // If we are checking a non-default constructor, we issue a single error at the constructor - // declaration. - boolean errorAtField = staticFields || TreeUtils.isSynthetic((MethodTree) tree); - - String errorMsg = - (staticFields - ? "initialization.static.field.uninitialized" - : errorAtField - ? "initialization.field.uninitialized" - : "initialization.fields.uninitialized"); - - // Remove fields with a relevant @SuppressWarnings annotation - uninitializedFields.removeIf( - f -> checker.shouldSuppressWarnings(TreeUtils.elementFromDeclaration(f), errorMsg)); - - if (!uninitializedFields.isEmpty()) { - if (errorAtField) { - // Issue each error at the relevant field - for (VariableTree f : uninitializedFields) { - checker.reportError(f, errorMsg, f.getName()); - } - } else { - // Issue all the errors at the relevant constructor - StringJoiner fieldsString = new StringJoiner(", "); - for (VariableTree f : uninitializedFields) { - fieldsString.add(f.getName()); - } - checker.reportError(tree, errorMsg, fieldsString); - } + } else { + // Issue all the errors at the relevant constructor + StringJoiner fieldsString = new StringJoiner(", "); + for (VariableTree f : uninitializedFields) { + fieldsString.add(f.getName()); } + checker.reportError(tree, errorMsg, fieldsString); + } + } - /* NO-AFU - // Support -Ainfer command-line argument. - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - if (wpi != null) { - // For each uninitialized field, treat it as if the default value is assigned to it. - List uninitFields = new ArrayList<>(violatingFields); - uninitFields.addAll(nonviolatingFields); - for (VariableTree fieldTree : uninitFields) { - Element elt = TreeUtils.elementFromDeclaration(fieldTree); - wpi.updateFieldFromType( - fieldTree, - elt, - fieldTree.getName().toString(), - atypeFactory.getDefaultValueAnnotatedType(elt.asType())); - } - } - */ + /* NO-AFU + // Support -Ainfer command-line argument. + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + if (wpi != null) { + // For each uninitialized field, treat it as if the default value is assigned to it. + List uninitFields = new ArrayList<>(violatingFields); + uninitFields.addAll(nonviolatingFields); + for (VariableTree fieldTree : uninitFields) { + Element elt = TreeUtils.elementFromDeclaration(fieldTree); + wpi.updateFieldFromType( + fieldTree, + elt, + fieldTree.getName().toString(), + atypeFactory.getDefaultValueAnnotatedType(elt.asType())); + } } + */ + } } diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java index 06da92f8c2e..015ccee3765 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java @@ -6,7 +6,12 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.tools.javac.code.Symbol.MethodSymbol; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternMethod; import org.checkerframework.checker.interning.qual.Interned; @@ -31,13 +36,6 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * An {@link AnnotatedTypeFactory} that accounts for the properties of the Interned type system. * This type factory will add the {@link Interned} annotation to a type if the input: @@ -66,176 +64,175 @@ */ public class InterningAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@link UnknownInterned} annotation. */ - final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownInterned.class); + /** The {@link UnknownInterned} annotation. */ + final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownInterned.class); - /** The {@link Interned} annotation. */ - final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); + /** The {@link Interned} annotation. */ + final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); - /** The {@link InternedDistinct} annotation. */ - final AnnotationMirror INTERNED_DISTINCT = - AnnotationBuilder.fromClass(elements, InternedDistinct.class); + /** The {@link InternedDistinct} annotation. */ + final AnnotationMirror INTERNED_DISTINCT = + AnnotationBuilder.fromClass(elements, InternedDistinct.class); - /** A set containing just {@link #INTERNED}. */ - final AnnotationMirrorSet INTERNED_SET = AnnotationMirrorSet.singleton(INTERNED); + /** A set containing just {@link #INTERNED}. */ + final AnnotationMirrorSet INTERNED_SET = AnnotationMirrorSet.singleton(INTERNED); - /** - * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST. - * - * @param checker the checker to use - */ - public InterningAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + /** + * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST. + * + * @param checker the checker to use + */ + public InterningAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + // If you update the following, also update ../../../../../docs/manual/interning-checker.tex + addAliasedTypeAnnotation("com.sun.istack.internal.Interned", INTERNED); + + this.postInit(); + } - // If you update the following, also update ../../../../../docs/manual/interning-checker.tex - addAliasedTypeAnnotation("com.sun.istack.internal.Interned", INTERNED); + @Override + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new InterningDefaultQualifierForUseTypeAnnotator(this); + } - this.postInit(); + /** + * Does not add defaults for type uses on constructor results. Constructor results should be + * {@code @UnknownInterned} by default. + */ + static class InterningDefaultQualifierForUseTypeAnnotator + extends DefaultQualifierForUseTypeAnnotator { + + public InterningDefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); } @Override - protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { - return new InterningDefaultQualifierForUseTypeAnnotator(this); + public Void visitExecutable(AnnotatedExecutableType type, Void p) { + MethodSymbol methodElt = (MethodSymbol) type.getElement(); + + if (methodElt == null || methodElt.getKind() != ElementKind.CONSTRUCTOR) { + // Annotate method returns, not constructors. + scan(type.getReturnType(), p); + } + AnnotatedTypeMirror receiverType = type.getReceiverType(); + if (receiverType != null + // Intern method may be called on UnknownInterned object, so its receiver should + // not be annotated as @Interned. + && atypeFactory.getDeclAnnotation(methodElt, InternMethod.class) == null) { + scanAndReduce(receiverType, p, null); + } + scanAndReduce(type.getParameterTypes(), p, null); + scanAndReduce(type.getThrownTypes(), p, null); + scanAndReduce(type.getTypeVariables(), p, null); + return null; } + } - /** - * Does not add defaults for type uses on constructor results. Constructor results should be - * {@code @UnknownInterned} by default. - */ - static class InterningDefaultQualifierForUseTypeAnnotator - extends DefaultQualifierForUseTypeAnnotator { - - public InterningDefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - } - - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void p) { - MethodSymbol methodElt = (MethodSymbol) type.getElement(); - - if (methodElt == null || methodElt.getKind() != ElementKind.CONSTRUCTOR) { - // Annotate method returns, not constructors. - scan(type.getReturnType(), p); - } - AnnotatedTypeMirror receiverType = type.getReceiverType(); - if (receiverType != null - // Intern method may be called on UnknownInterned object, so its receiver should - // not be annotated as @Interned. - && atypeFactory.getDeclAnnotation(methodElt, InternMethod.class) == null) { - scanAndReduce(receiverType, p, null); - } - scanAndReduce(type.getParameterTypes(), p, null); - scanAndReduce(type.getThrownTypes(), p, null); - scanAndReduce(type.getTypeVariables(), p, null); - return null; - } + @Override + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror typeMirror) { + if (typeMirror.getKind() == TypeKind.DECLARED + && ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.ENUM) { + return INTERNED_SET; + } + return super.getTypeDeclarationBounds(typeMirror); + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new InterningTreeAnnotator(this)); + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(new InterningTypeAnnotator(this), super.createTypeAnnotator()); + } + + @Override + public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(INTERNED) && ElementUtils.isCompileTimeConstant(element)) { + type.addAnnotation(INTERNED); } + super.addComputedTypeAnnotations(element, type); + } - @Override - public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror typeMirror) { - if (typeMirror.getKind() == TypeKind.DECLARED - && ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.ENUM) { - return INTERNED_SET; - } - return super.getTypeDeclarationBounds(typeMirror); + /** A class for adding annotations based on tree. */ + private class InterningTreeAnnotator extends TreeAnnotator { + + InterningTreeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new InterningTreeAnnotator(this)); + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isCompileTimeString(tree)) { + type.replaceAnnotation(INTERNED); + } else if (TreeUtils.isStringConcatenation(tree)) { + type.replaceAnnotation(TOP); + } else if (type.getKind().isPrimitive() + || tree.getKind() == Tree.Kind.EQUAL_TO + || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { + type.replaceAnnotation(INTERNED); + } else { + type.replaceAnnotation(TOP); + } + return super.visitBinary(tree, type); } + /* Compound assignments never result in an interned result. + */ @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(new InterningTypeAnnotator(this), super.createTypeAnnotator()); + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(TOP); + return super.visitCompoundAssignment(tree, type); } @Override - public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(INTERNED) - && ElementUtils.isCompileTimeConstant(element)) { - type.addAnnotation(INTERNED); - } - super.addComputedTypeAnnotations(element, type); + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.typeOf(tree.getType()).getKind().isPrimitive()) { + type.replaceAnnotation(INTERNED); + } + return super.visitTypeCast(tree, type); } - /** A class for adding annotations based on tree. */ - private class InterningTreeAnnotator extends TreeAnnotator { - - InterningTreeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isCompileTimeString(tree)) { - type.replaceAnnotation(INTERNED); - } else if (TreeUtils.isStringConcatenation(tree)) { - type.replaceAnnotation(TOP); - } else if (type.getKind().isPrimitive() - || tree.getKind() == Tree.Kind.EQUAL_TO - || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { - type.replaceAnnotation(INTERNED); - } else { - type.replaceAnnotation(TOP); - } - return super.visitBinary(tree, type); - } - - /* Compound assignments never result in an interned result. - */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(TOP); - return super.visitCompoundAssignment(tree, type); - } - - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.typeOf(tree.getType()).getKind().isPrimitive()) { - type.replaceAnnotation(INTERNED); - } - return super.visitTypeCast(tree, type); - } - - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - Element e = TreeUtils.elementFromUse(tree); - if (atypeFactory.getDeclAnnotation(e, FindDistinct.class) != null) { - // TODO: See note above about this being a poor implementation. - type.replaceAnnotation(INTERNED_DISTINCT); - } - return super.visitIdentifier(tree, type); - } + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + Element e = TreeUtils.elementFromUse(tree); + if (atypeFactory.getDeclAnnotation(e, FindDistinct.class) != null) { + // TODO: See note above about this being a poor implementation. + type.replaceAnnotation(INTERNED_DISTINCT); + } + return super.visitIdentifier(tree, type); } + } + + /** Adds @Interned to enum types. */ + private class InterningTypeAnnotator extends TypeAnnotator { - /** Adds @Interned to enum types. */ - private class InterningTypeAnnotator extends TypeAnnotator { - - InterningTypeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType t, Void p) { - // case 3: Enum types, and the Enum class itself, are interned - Element elt = t.getUnderlyingType().asElement(); - assert elt != null; - if (elt.getKind() == ElementKind.ENUM) { - t.replaceAnnotation(INTERNED); - } - return super.visitDeclared(t, p); - } + InterningTypeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - /** - * Unbox type and replace any interning type annotations with @Interned since all primitives can - * safely use ==. See case 4 in the class comments. - */ @Override - public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) { - AnnotatedPrimitiveType primitive = super.getUnboxedType(type); - primitive.replaceAnnotation(INTERNED); - return primitive; + public Void visitDeclared(AnnotatedDeclaredType t, Void p) { + // case 3: Enum types, and the Enum class itself, are interned + Element elt = t.getUnderlyingType().asElement(); + assert elt != null; + if (elt.getKind() == ElementKind.ENUM) { + t.replaceAnnotation(INTERNED); + } + return super.visitDeclared(t, p); } + } + + /** + * Unbox type and replace any interning type annotations with @Interned since all primitives can + * safely use ==. See case 4 in the class comments. + */ + @Override + public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) { + AnnotatedPrimitiveType primitive = super.getUnboxedType(type); + primitive.replaceAnnotation(INTERNED); + return primitive; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java index 428fed8e423..063200c6a5f 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.interning; +import javax.annotation.processing.SupportedOptions; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedLintOptions; -import javax.annotation.processing.SupportedOptions; - /** * A type-checker plug-in for the {@link Interned} qualifier that finds (and verifies the absence * of) equality-testing and interning errors. diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java index eff70f42a86..a28327326cf 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java @@ -17,7 +17,19 @@ import com.sun.source.tree.StatementTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; - +import java.util.Comparator; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.tools.Diagnostic; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.interning.qual.CompareToMethod; import org.checkerframework.checker.interning.qual.EqualsMethod; @@ -39,21 +51,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.Comparator; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; -import javax.tools.Diagnostic; - /** * Typechecks source code for interning violations. A type is considered interned if its primary * annotation is {@link Interned} or {@link InternedDistinct}. This visitor reports errors or @@ -71,923 +68,901 @@ */ public final class InterningVisitor extends BaseTypeVisitor { - /** The @Interned annotation. */ - private final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); - - /** The @InternedDistinct annotation. */ - private final AnnotationMirror INTERNED_DISTINCT = - AnnotationBuilder.fromClass(elements, InternedDistinct.class); - - /** - * The declared type of which the equality tests should be tested, if the user explicitly passed - * one. The user can pass the class name via the {@code -Acheckclass=...} option. Null if no - * class is specified, or the class specified isn't in the classpath. - */ - private final @Nullable DeclaredType typeToCheck = typeToCheck(); - - /** The Comparable.compareTo method. */ - private final ExecutableElement comparableCompareTo = - TreeUtils.getMethod( - "java.lang.Comparable", "compareTo", 1, checker.getProcessingEnvironment()); - - /** Create an InterningVisitor. */ - public InterningVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * Returns true if interning should be verified for the input expression. By default, all - * classes are checked for interning unless {@code -Acheckclass} is specified. - * - * @return true if interning should be verified for the input expression - * @see What the Interning - * Checker checks - */ - private boolean shouldCheckExpression(ExpressionTree tree) { - if (typeToCheck == null) { - return true; - } - - TypeMirror type = TreeUtils.typeOf(tree); - return types.isSubtype(type, typeToCheck) || types.isSubtype(typeToCheck, type); + /** The @Interned annotation. */ + private final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); + + /** The @InternedDistinct annotation. */ + private final AnnotationMirror INTERNED_DISTINCT = + AnnotationBuilder.fromClass(elements, InternedDistinct.class); + + /** + * The declared type of which the equality tests should be tested, if the user explicitly passed + * one. The user can pass the class name via the {@code -Acheckclass=...} option. Null if no class + * is specified, or the class specified isn't in the classpath. + */ + private final @Nullable DeclaredType typeToCheck = typeToCheck(); + + /** The Comparable.compareTo method. */ + private final ExecutableElement comparableCompareTo = + TreeUtils.getMethod( + "java.lang.Comparable", "compareTo", 1, checker.getProcessingEnvironment()); + + /** Create an InterningVisitor. */ + public InterningVisitor(BaseTypeChecker checker) { + super(checker); + } + + /** + * Returns true if interning should be verified for the input expression. By default, all classes + * are checked for interning unless {@code -Acheckclass} is specified. + * + * @return true if interning should be verified for the input expression + * @see What the Interning Checker + * checks + */ + private boolean shouldCheckExpression(ExpressionTree tree) { + if (typeToCheck == null) { + return true; } - /** Checks comparison operators, == and !=, for INTERNING violations. */ - @Override - public Void visitBinary(BinaryTree tree, Void p) { + TypeMirror type = TreeUtils.typeOf(tree); + return types.isSubtype(type, typeToCheck) || types.isSubtype(typeToCheck, type); + } - // No checking unless the operator is "==" or "!=". - if (!(tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { - return super.visitBinary(tree, p); - } + /** Checks comparison operators, == and !=, for INTERNING violations. */ + @Override + public Void visitBinary(BinaryTree tree, Void p) { - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); - - // Check passes if either arg is null. - if (leftOp.getKind() == Tree.Kind.NULL_LITERAL - || rightOp.getKind() == Tree.Kind.NULL_LITERAL) { - return super.visitBinary(tree, p); - } + // No checking unless the operator is "==" or "!=". + if (!(tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { + return super.visitBinary(tree, p); + } - AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); - AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); - // If either argument is a primitive, check passes due to auto-unboxing - if (left.getKind().isPrimitive() || right.getKind().isPrimitive()) { - return super.visitBinary(tree, p); - } + // Check passes if either arg is null. + if (leftOp.getKind() == Tree.Kind.NULL_LITERAL || rightOp.getKind() == Tree.Kind.NULL_LITERAL) { + return super.visitBinary(tree, p); + } - if (left.hasEffectiveAnnotation(INTERNED_DISTINCT) - || right.hasEffectiveAnnotation(INTERNED_DISTINCT)) { - return super.visitBinary(tree, p); - } + AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); + AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); - // If shouldCheckExpression returns true for either the LHS or RHS, - // this method proceeds with the interning check. - - // Justification: Consider the following scenario: - - // interface I { ... } - // class A { ... } - // class B extends A implements I { ... } - // ... - // I i; - // A a; - // ... - // if (a == i) { ... } - - // The Java compiler does not issue a compilation error for the (a == i) comparison because, - // even though A does not implement I, 'a' could be assigned an instance of B, and B does - // implement I (note that the compiler does not need to know about the existence of B - // in order to assume this). - - // Now suppose the user passes -AcheckClass=A on the command-line. - // I is not a subtype or supertype of A, so shouldCheckExpression will not return true for - // I. - // But the interning check must be performed, given the argument above. Therefore if - // shouldCheckExpression returns true for either the LHS or the RHS, this method proceeds - // with the interning check. - - if (!shouldCheckExpression(leftOp) && !shouldCheckExpression(rightOp)) { - return super.visitBinary(tree, p); - } + // If either argument is a primitive, check passes due to auto-unboxing + if (left.getKind().isPrimitive() || right.getKind().isPrimitive()) { + return super.visitBinary(tree, p); + } - // Syntactic checks for legal uses of == - if (suppressInsideComparison(tree)) { - return super.visitBinary(tree, p); - } - if (suppressEarlyEquals(tree)) { - return super.visitBinary(tree, p); - } - if (suppressEarlyCompareTo(tree)) { - return super.visitBinary(tree, p); - } + if (left.hasEffectiveAnnotation(INTERNED_DISTINCT) + || right.hasEffectiveAnnotation(INTERNED_DISTINCT)) { + return super.visitBinary(tree, p); + } - if (suppressEqualsIfClassIsAnnotated(left, right)) { - return super.visitBinary(tree, p); - } + // If shouldCheckExpression returns true for either the LHS or RHS, + // this method proceeds with the interning check. + + // Justification: Consider the following scenario: + + // interface I { ... } + // class A { ... } + // class B extends A implements I { ... } + // ... + // I i; + // A a; + // ... + // if (a == i) { ... } + + // The Java compiler does not issue a compilation error for the (a == i) comparison because, + // even though A does not implement I, 'a' could be assigned an instance of B, and B does + // implement I (note that the compiler does not need to know about the existence of B + // in order to assume this). + + // Now suppose the user passes -AcheckClass=A on the command-line. + // I is not a subtype or supertype of A, so shouldCheckExpression will not return true for + // I. + // But the interning check must be performed, given the argument above. Therefore if + // shouldCheckExpression returns true for either the LHS or the RHS, this method proceeds + // with the interning check. + + if (!shouldCheckExpression(leftOp) && !shouldCheckExpression(rightOp)) { + return super.visitBinary(tree, p); + } - Element leftElt = TypesUtils.getTypeElement(left.getUnderlyingType()); - // If neither @Interned or @UsesObjectEquals, report error. - if (!(left.hasEffectiveAnnotation(INTERNED) - || (leftElt != null - && atypeFactory.getDeclAnnotation(leftElt, UsesObjectEquals.class) - != null))) { - checker.reportError(leftOp, "not.interned"); - } + // Syntactic checks for legal uses of == + if (suppressInsideComparison(tree)) { + return super.visitBinary(tree, p); + } + if (suppressEarlyEquals(tree)) { + return super.visitBinary(tree, p); + } + if (suppressEarlyCompareTo(tree)) { + return super.visitBinary(tree, p); + } - Element rightElt = TypesUtils.getTypeElement(right.getUnderlyingType()); - if (!(right.hasEffectiveAnnotation(INTERNED) - || (rightElt != null - && atypeFactory.getDeclAnnotation(rightElt, UsesObjectEquals.class) - != null))) { - checker.reportError(rightOp, "not.interned"); - } - return super.visitBinary(tree, p); - } - - /** - * If lint option "dotequals" is specified, warn if the .equals method is used where reference - * equality is safe. - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (isInvocationOfEquals(tree)) { - AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); - assert receiverType != null : "@AssumeAssertion(nullness)"; - AnnotatedTypeMirror comp = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - - if (this.checker.getLintOption("dotequals", true) - && receiverType.hasEffectiveAnnotation(INTERNED) - && comp.hasEffectiveAnnotation(INTERNED)) { - checker.reportWarning(tree, "unnecessary.equals"); - } - } + if (suppressEqualsIfClassIsAnnotated(left, right)) { + return super.visitBinary(tree, p); + } - return super.visitMethodInvocation(tree, p); - } - - // Ensure that method annotations are not written on methods they don't apply to. - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); - boolean hasCompareToMethodAnno = - atypeFactory.getDeclAnnotation(methElt, CompareToMethod.class) != null; - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; - boolean hasInternMethodAnno = - atypeFactory.getDeclAnnotation(methElt, InternMethod.class) != null; - int params = methElt.getParameters().size(); - if (hasCompareToMethodAnno && !(params == 1 || params == 2)) { - checker.reportError( - tree, - "invalid.method.annotation", - "@CompareToMethod", - "1 or 2", - methElt, - params); - } else if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { - checker.reportError( - tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); - } else if (hasInternMethodAnno && !(params == 0)) { - checker.reportError( - tree, "invalid.method.annotation", "@InternMethod", "0", methElt, params); - } + Element leftElt = TypesUtils.getTypeElement(left.getUnderlyingType()); + // If neither @Interned or @UsesObjectEquals, report error. + if (!(left.hasEffectiveAnnotation(INTERNED) + || (leftElt != null + && atypeFactory.getDeclAnnotation(leftElt, UsesObjectEquals.class) != null))) { + checker.reportError(leftOp, "not.interned"); + } - return super.visitMethod(tree, p); - } - - /** - * Method to implement the @UsesObjectEquals functionality. If a class is annotated - * with @UsesObjectEquals, it must: - * - *

    - *
  • not override .equals(Object) and be a subclass of a class annotated - * with @UsesObjectEquals, or - *
  • override equals(Object) with body "this == arg" - *
- * - * If a class is not annotated with @UsesObjectEquals, it must: - * - *
    - *
  • not have a superclass annotated with @UsesObjectEquals - *
- * - * @see - * org.checkerframework.common.basetype.BaseTypeVisitor#visitClass(com.sun.source.tree.ClassTree, - * java.lang.Object) - */ - @Override - public void processClassTree(ClassTree classTree) { - TypeElement elt = TreeUtils.elementFromDeclaration(classTree); - AnnotationMirror annotation = atypeFactory.getDeclAnnotation(elt, UsesObjectEquals.class); - - // If @UsesObjectEquals is present, check to make sure the class does not override equals - // and its supertype is Object or is annotated with @UsesObjectEquals. - if (annotation != null) { - MethodTree equalsMethod = equalsImplementation(classTree); - if (equalsMethod != null) { - if (!isReferenceEqualityImplementation(equalsMethod)) { - checker.reportError(classTree, "overrides.equals"); - } - } else { - // Does not override equals() - TypeMirror superClass = elt.getSuperclass(); - if (superClass != null - // The super class of an interface is "none" rather than null. - && superClass.getKind() == TypeKind.DECLARED) { - TypeElement superClassElement = TypesUtils.getTypeElement(superClass); - if (superClassElement != null - && !ElementUtils.isObject(superClassElement) - && atypeFactory.getDeclAnnotation( - superClassElement, UsesObjectEquals.class) - == null) { - checker.reportError(classTree, "superclass.notannotated"); - } - } - } - } + Element rightElt = TypesUtils.getTypeElement(right.getUnderlyingType()); + if (!(right.hasEffectiveAnnotation(INTERNED) + || (rightElt != null + && atypeFactory.getDeclAnnotation(rightElt, UsesObjectEquals.class) != null))) { + checker.reportError(rightOp, "not.interned"); + } + return super.visitBinary(tree, p); + } + + /** + * If lint option "dotequals" is specified, warn if the .equals method is used where reference + * equality is safe. + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (isInvocationOfEquals(tree)) { + AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); + assert receiverType != null : "@AssumeAssertion(nullness)"; + AnnotatedTypeMirror comp = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + + if (this.checker.getLintOption("dotequals", true) + && receiverType.hasEffectiveAnnotation(INTERNED) + && comp.hasEffectiveAnnotation(INTERNED)) { + checker.reportWarning(tree, "unnecessary.equals"); + } + } - super.processClassTree(classTree); - } - - /** - * Returns true if the given equals() method implements reference equality. - * - * @param equalsMethod an overriding implementation of Object.equals() - * @return true if the given equals() method implements reference equality - */ - private boolean isReferenceEqualityImplementation(MethodTree equalsMethod) { - BlockTree body = equalsMethod.getBody(); - List bodyStatements = body.getStatements(); - if (bodyStatements.size() == 1) { - StatementTree bodyStatement = bodyStatements.get(0); - if (bodyStatement.getKind() == Tree.Kind.RETURN) { - ExpressionTree returnExpr = - TreeUtils.withoutParens(((ReturnTree) bodyStatement).getExpression()); - if (returnExpr.getKind() == Tree.Kind.EQUAL_TO) { - BinaryTree bt = (BinaryTree) returnExpr; - ExpressionTree lhsTree = bt.getLeftOperand(); - ExpressionTree rhsTree = bt.getRightOperand(); - if (lhsTree.getKind() == Tree.Kind.IDENTIFIER - && rhsTree.getKind() == Tree.Kind.IDENTIFIER) { - Name leftName = ((IdentifierTree) lhsTree).getName(); - Name rightName = ((IdentifierTree) rhsTree).getName(); - Name paramName = equalsMethod.getParameters().get(0).getName(); - if ((leftName.contentEquals("this") && rightName == paramName) - || (leftName == paramName && rightName.contentEquals("this"))) { - return true; - } - } - } - } - } - return false; + return super.visitMethodInvocation(tree, p); + } + + // Ensure that method annotations are not written on methods they don't apply to. + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); + boolean hasCompareToMethodAnno = + atypeFactory.getDeclAnnotation(methElt, CompareToMethod.class) != null; + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; + boolean hasInternMethodAnno = + atypeFactory.getDeclAnnotation(methElt, InternMethod.class) != null; + int params = methElt.getParameters().size(); + if (hasCompareToMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + tree, "invalid.method.annotation", "@CompareToMethod", "1 or 2", methElt, params); + } else if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); + } else if (hasInternMethodAnno && !(params == 0)) { + checker.reportError(tree, "invalid.method.annotation", "@InternMethod", "0", methElt, params); } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - if (constructorElement.getEnclosingElement().getKind() == ElementKind.ENUM) { - // Enums constructor are only called once per enum constant. - return; - } - super.checkConstructorResult(constructorType, constructorElement); + return super.visitMethod(tree, p); + } + + /** + * Method to implement the @UsesObjectEquals functionality. If a class is annotated + * with @UsesObjectEquals, it must: + * + *
    + *
  • not override .equals(Object) and be a subclass of a class annotated + * with @UsesObjectEquals, or + *
  • override equals(Object) with body "this == arg" + *
+ * + * If a class is not annotated with @UsesObjectEquals, it must: + * + *
    + *
  • not have a superclass annotated with @UsesObjectEquals + *
+ * + * @see + * org.checkerframework.common.basetype.BaseTypeVisitor#visitClass(com.sun.source.tree.ClassTree, + * java.lang.Object) + */ + @Override + public void processClassTree(ClassTree classTree) { + TypeElement elt = TreeUtils.elementFromDeclaration(classTree); + AnnotationMirror annotation = atypeFactory.getDeclAnnotation(elt, UsesObjectEquals.class); + + // If @UsesObjectEquals is present, check to make sure the class does not override equals + // and its supertype is Object or is annotated with @UsesObjectEquals. + if (annotation != null) { + MethodTree equalsMethod = equalsImplementation(classTree); + if (equalsMethod != null) { + if (!isReferenceEqualityImplementation(equalsMethod)) { + checker.reportError(classTree, "overrides.equals"); + } + } else { + // Does not override equals() + TypeMirror superClass = elt.getSuperclass(); + if (superClass != null + // The super class of an interface is "none" rather than null. + && superClass.getKind() == TypeKind.DECLARED) { + TypeElement superClassElement = TypesUtils.getTypeElement(superClass); + if (superClassElement != null + && !ElementUtils.isObject(superClassElement) + && atypeFactory.getDeclAnnotation(superClassElement, UsesObjectEquals.class) + == null) { + checker.reportError(classTree, "superclass.notannotated"); + } + } + } } - @Override - public boolean validateTypeOf(Tree tree) { - // Don't check the result type of a constructor, because it must be @UnknownInterned, even - // if the type on the class declaration is @Interned. - if (tree.getKind() == Tree.Kind.METHOD && TreeUtils.isConstructor((MethodTree) tree)) { - return true; - } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { - NewClassTree newClassTree = (NewClassTree) tree; - TypeMirror typeMirror = TreeUtils.typeOf(newClassTree); - AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(typeMirror); - // Don't issue an invalid type warning for creations of objects of interned classes; - // instead, issue an interned.object.creation if required. - if (atypeFactory.containsSameByClass(bounds, Interned.class)) { - ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(newClassTree); - AnnotatedExecutableType constructor = fromUse.executableType; - if (!checkCreationOfInternedObject(newClassTree, constructor)) { - return false; - } + super.processClassTree(classTree); + } + + /** + * Returns true if the given equals() method implements reference equality. + * + * @param equalsMethod an overriding implementation of Object.equals() + * @return true if the given equals() method implements reference equality + */ + private boolean isReferenceEqualityImplementation(MethodTree equalsMethod) { + BlockTree body = equalsMethod.getBody(); + List bodyStatements = body.getStatements(); + if (bodyStatements.size() == 1) { + StatementTree bodyStatement = bodyStatements.get(0); + if (bodyStatement.getKind() == Tree.Kind.RETURN) { + ExpressionTree returnExpr = + TreeUtils.withoutParens(((ReturnTree) bodyStatement).getExpression()); + if (returnExpr.getKind() == Tree.Kind.EQUAL_TO) { + BinaryTree bt = (BinaryTree) returnExpr; + ExpressionTree lhsTree = bt.getLeftOperand(); + ExpressionTree rhsTree = bt.getRightOperand(); + if (lhsTree.getKind() == Tree.Kind.IDENTIFIER + && rhsTree.getKind() == Tree.Kind.IDENTIFIER) { + Name leftName = ((IdentifierTree) lhsTree).getName(); + Name rightName = ((IdentifierTree) rhsTree).getName(); + Name paramName = equalsMethod.getParameters().get(0).getName(); + if ((leftName.contentEquals("this") && rightName == paramName) + || (leftName == paramName && rightName.contentEquals("this"))) { + return true; } + } } - return super.validateTypeOf(tree); - } - - /** - * Issue an error if {@code newInternedObject} is not immediately interned. - * - * @param newInternedObject call to a constructor of an interned class - * @param constructor declared type of the constructor - * @return false unless {@code newInternedObject} is immediately interned - */ - private boolean checkCreationOfInternedObject( - NewClassTree newInternedObject, AnnotatedExecutableType constructor) { - if (constructor.getReturnType().hasAnnotation(Interned.class)) { + } + } + return false; + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + if (constructorElement.getEnclosingElement().getKind() == ElementKind.ENUM) { + // Enums constructor are only called once per enum constant. + return; + } + super.checkConstructorResult(constructorType, constructorElement); + } + + @Override + public boolean validateTypeOf(Tree tree) { + // Don't check the result type of a constructor, because it must be @UnknownInterned, even + // if the type on the class declaration is @Interned. + if (tree.getKind() == Tree.Kind.METHOD && TreeUtils.isConstructor((MethodTree) tree)) { + return true; + } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { + NewClassTree newClassTree = (NewClassTree) tree; + TypeMirror typeMirror = TreeUtils.typeOf(newClassTree); + AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(typeMirror); + // Don't issue an invalid type warning for creations of objects of interned classes; + // instead, issue an interned.object.creation if required. + if (atypeFactory.containsSameByClass(bounds, Interned.class)) { + ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(newClassTree); + AnnotatedExecutableType constructor = fromUse.executableType; + if (!checkCreationOfInternedObject(newClassTree, constructor)) { + return false; + } + } + } + return super.validateTypeOf(tree); + } + + /** + * Issue an error if {@code newInternedObject} is not immediately interned. + * + * @param newInternedObject call to a constructor of an interned class + * @param constructor declared type of the constructor + * @return false unless {@code newInternedObject} is immediately interned + */ + private boolean checkCreationOfInternedObject( + NewClassTree newInternedObject, AnnotatedExecutableType constructor) { + if (constructor.getReturnType().hasAnnotation(Interned.class)) { + return true; + } + TreePath path = getCurrentPath(); + if (path != null) { + TreePath parentPath = path.getParentPath(); + while (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.PARENTHESIZED) { + parentPath = parentPath.getParentPath(); + } + if (parentPath != null && parentPath.getParentPath() != null) { + Tree parent = parentPath.getParentPath().getLeaf(); + if (parent.getKind() == Tree.Kind.METHOD_INVOCATION) { + // Allow new MyInternType().intern(), where "intern" is any method marked + // @InternMethod. + ExecutableElement elt = TreeUtils.elementFromUse((MethodInvocationTree) parent); + if (atypeFactory.getDeclAnnotation(elt, InternMethod.class) != null) { return true; + } } - TreePath path = getCurrentPath(); - if (path != null) { - TreePath parentPath = path.getParentPath(); - while (parentPath != null - && parentPath.getLeaf().getKind() == Tree.Kind.PARENTHESIZED) { - parentPath = parentPath.getParentPath(); - } - if (parentPath != null && parentPath.getParentPath() != null) { - Tree parent = parentPath.getParentPath().getLeaf(); - if (parent.getKind() == Tree.Kind.METHOD_INVOCATION) { - // Allow new MyInternType().intern(), where "intern" is any method marked - // @InternMethod. - ExecutableElement elt = TreeUtils.elementFromUse((MethodInvocationTree) parent); - if (atypeFactory.getDeclAnnotation(elt, InternMethod.class) != null) { - return true; - } - } - } - } + } + } + + checker.reportError(newInternedObject, "interned.object.creation"); + return false; + } + + // ********************************************************************** + // Helper methods + // ********************************************************************** + + /** + * Returns the method that overrides Object.equals, or null. + * + * @param tree a class + * @return the class's implementation of equals, or null + */ + private @Nullable MethodTree equalsImplementation(ClassTree tree) { + List members = tree.getMembers(); + for (Tree member : members) { + if (member instanceof MethodTree) { + MethodTree mTree = (MethodTree) member; + ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree); + if (overrides(enclosing, Object.class, "equals")) { + return mTree; + } + } + } + return null; + } + + /** + * Tests whether a method invocation is an invocation of {@link #equals} with one argument. + * + *

Returns true even if a method overloads {@link Object#equals(Object)}, because of the common + * idiom of writing an equals method with a non-Object parameter, in addition to the equals method + * that overrides {@link Object#equals(Object)}. + * + * @param tree a method invocation tree + * @return true iff {@code tree} is a invocation of {@code equals()} + */ + public static boolean isInvocationOfEquals(MethodInvocationTree tree) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + return (method.getParameters().size() == 1 + && method.getReturnType().getKind() == TypeKind.BOOLEAN + // method symbols only have simple names + && method.getSimpleName().contentEquals("equals")); + } + + /** + * Pattern matches particular comparisons to avoid common false positives in the {@link + * Comparable#compareTo(Object)} and {@link Object#equals(Object)}. + * + *

Specifically, this method tests if: the comparison is a == comparison, it is the test of an + * if statement that's the first statement in the method, and one of the following is true: + * + *

    + *
  1. the method overrides {@link Comparator#compare}, the "then" branch of the if statement + * returns zero, and the comparison tests equality of the method's two parameters + *
  2. the method overrides {@link Object#equals(Object)} and the comparison tests "this" + * against the method's parameter + *
  3. the method overrides {@link Comparable#compareTo(Object)}, the "then" branch of the if + * statement returns zero, and the comparison tests "this" against the method's parameter + *
+ * + * @param binaryTree the comparison to check + * @return true if one of the supported heuristics is matched, false otherwise + */ + // TODO: handle != comparisons too! + // TODO: handle more methods, such as early return from addAll when this == arg + private boolean suppressInsideComparison(BinaryTree binaryTree) { + // Only handle == binary trees + if (binaryTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } + + ExpressionTree left = binaryTree.getLeftOperand(); + ExpressionTree right = binaryTree.getRightOperand(); + + // Only valid if we're comparing identifiers. + if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { + return false; + } + + TreePath path = getCurrentPath(); + TreePath parentPath = path.getParentPath(); + Tree parent = parentPath.getLeaf(); - checker.reportError(newInternedObject, "interned.object.creation"); + // Ensure the == is in a return or in an if, and that enclosing statement is the first + // statement in the method. + if (parent.getKind() == Tree.Kind.RETURN) { + // ensure the return statement is the first statement in the method + if (parentPath.getParentPath().getParentPath().getLeaf().getKind() != Tree.Kind.METHOD) { return false; + } + + // maybe set some variables?? + } else if (Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) { + // Ensure the if statement is the first statement in the method + + // Retrieve the enclosing if statement tree and method tree + Tree ifStatementTree = null; + MethodTree methodTree = null; + // Set ifStatementTree and methodTree + { + TreePath ppath = parentPath; + Tree candidateTree; + while ((candidateTree = ppath.getLeaf()) != null) { + if (candidateTree.getKind() == Tree.Kind.IF) { + ifStatementTree = candidateTree; + } else if (candidateTree.getKind() == Tree.Kind.METHOD) { + methodTree = (MethodTree) candidateTree; + break; + } + ppath = ppath.getParentPath(); + } + } + assert ifStatementTree != null; + assert methodTree != null; + StatementTree firstStmnt = methodTree.getBody().getStatements().get(0); + assert firstStmnt != null; + @SuppressWarnings("interning:not.interned") // comparing AST nodes + boolean notSameNode = firstStmnt != ifStatementTree; + if (notSameNode) { + return false; // The if statement is not the first statement in the method. + } + } else { + return false; } - // ********************************************************************** - // Helper methods - // ********************************************************************** - - /** - * Returns the method that overrides Object.equals, or null. - * - * @param tree a class - * @return the class's implementation of equals, or null - */ - private @Nullable MethodTree equalsImplementation(ClassTree tree) { - List members = tree.getMembers(); - for (Tree member : members) { - if (member instanceof MethodTree) { - MethodTree mTree = (MethodTree) member; - ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree); - if (overrides(enclosing, Object.class, "equals")) { - return mTree; - } + ExecutableElement enclosingMethod = TreeUtils.elementFromDeclaration(methodTree); + + Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); + Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); + + // Matcher to check for if statement that returns zero + Heuristics.Matcher matcherIfReturnsZero = + new Heuristics.Matcher() { + + @Override + public Boolean visitIf(IfTree tree, Void p) { + return visit(tree.getThenStatement(), p); + } + + @Override + public Boolean visitBlock(BlockTree tree, Void p) { + if (tree.getStatements().isEmpty()) { + return false; } - } - return null; - } - - /** - * Tests whether a method invocation is an invocation of {@link #equals} with one argument. - * - *

Returns true even if a method overloads {@link Object#equals(Object)}, because of the - * common idiom of writing an equals method with a non-Object parameter, in addition to the - * equals method that overrides {@link Object#equals(Object)}. - * - * @param tree a method invocation tree - * @return true iff {@code tree} is a invocation of {@code equals()} - */ - public static boolean isInvocationOfEquals(MethodInvocationTree tree) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - return (method.getParameters().size() == 1 - && method.getReturnType().getKind() == TypeKind.BOOLEAN - // method symbols only have simple names - && method.getSimpleName().contentEquals("equals")); - } - - /** - * Pattern matches particular comparisons to avoid common false positives in the {@link - * Comparable#compareTo(Object)} and {@link Object#equals(Object)}. - * - *

Specifically, this method tests if: the comparison is a == comparison, it is the test of - * an if statement that's the first statement in the method, and one of the following is true: - * - *

    - *
  1. the method overrides {@link Comparator#compare}, the "then" branch of the if statement - * returns zero, and the comparison tests equality of the method's two parameters - *
  2. the method overrides {@link Object#equals(Object)} and the comparison tests "this" - * against the method's parameter - *
  3. the method overrides {@link Comparable#compareTo(Object)}, the "then" branch of the if - * statement returns zero, and the comparison tests "this" against the method's parameter - *
- * - * @param binaryTree the comparison to check - * @return true if one of the supported heuristics is matched, false otherwise - */ - // TODO: handle != comparisons too! - // TODO: handle more methods, such as early return from addAll when this == arg - private boolean suppressInsideComparison(BinaryTree binaryTree) { - // Only handle == binary trees - if (binaryTree.getKind() != Tree.Kind.EQUAL_TO) { - return false; - } + return visit(tree.getStatements().get(0), p); + } + + @Override + public Boolean visitReturn(ReturnTree tree, Void p) { + ExpressionTree expr = tree.getExpression(); + return (expr != null + && expr.getKind() == Tree.Kind.INT_LITERAL + && ((LiteralTree) expr).getValue().equals(0)); + } + }; + + boolean hasCompareToMethodAnno = + atypeFactory.getDeclAnnotation(enclosingMethod, CompareToMethod.class) != null; + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(enclosingMethod, EqualsMethod.class) != null; + int params = enclosingMethod.getParameters().size(); + + // Determine whether or not the "then" statement of the if has a single + // "return 0" statement (for the Comparator.compare heuristic). + if (overrides(enclosingMethod, Comparator.class, "compare") + || (hasCompareToMethodAnno && params == 2)) { + boolean returnsZero = + new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) + .match(getCurrentPath()); + + if (!returnsZero) { + return false; + } + + assert params == 2; + Element p1 = enclosingMethod.getParameters().get(0); + Element p2 = enclosingMethod.getParameters().get(1); + return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); + + } else if (overrides(enclosingMethod, Object.class, "equals") + || (hasEqualsMethodAnno && params == 1)) { + assert params == 1; + Element param = enclosingMethod.getParameters().get(0); + Element thisElt = getThis(trees.getScope(getCurrentPath())); + assert thisElt != null; + return (thisElt.equals(lhs) && param.equals(rhs)) + || (thisElt.equals(rhs) && param.equals(lhs)); + + } else if (hasEqualsMethodAnno && params == 2) { + Element p1 = enclosingMethod.getParameters().get(0); + Element p2 = enclosingMethod.getParameters().get(1); + return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); + + } else if (overrides(enclosingMethod, Comparable.class, "compareTo") + || (hasCompareToMethodAnno && params == 1)) { + + boolean returnsZero = + new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) + .match(getCurrentPath()); + + if (!returnsZero) { + return false; + } + + assert params == 1; + Element param = enclosingMethod.getParameters().get(0); + Element thisElt = getThis(trees.getScope(getCurrentPath())); + assert thisElt != null; + return (thisElt.equals(lhs) && param.equals(rhs)) + || (thisElt.equals(rhs) && param.equals(lhs)); + } - ExpressionTree left = binaryTree.getLeftOperand(); - ExpressionTree right = binaryTree.getRightOperand(); + return false; + } + + /** + * Pattern matches to prevent false positives of the forms: + * + *
{@code
+   * (a == b) || a.equals(b)
+   * (a == b) || (a != null ? a.equals(b) : false)
+   * (a == b) || (a != null && a.equals(b))
+   * }
+ * + * Returns true iff the given tree fits this pattern. + * + * @param topBinaryTree the binary operation to check + * @return true iff the tree fits a pattern such as (a == b || a.equals(b)) + */ + private boolean suppressEarlyEquals(BinaryTree topBinaryTree) { + // Only handle == binary trees + if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } - // Only valid if we're comparing identifiers. - if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { - return false; - } + // should strip parens + ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); + ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); - TreePath path = getCurrentPath(); - TreePath parentPath = path.getParentPath(); - Tree parent = parentPath.getLeaf(); + // looking for ((a == b || a.equals(b)) + Heuristics.Matcher matcherEqOrEquals = + new Heuristics.Matcher() { - // Ensure the == is in a return or in an if, and that enclosing statement is the first - // statement in the method. - if (parent.getKind() == Tree.Kind.RETURN) { - // ensure the return statement is the first statement in the method - if (parentPath.getParentPath().getParentPath().getLeaf().getKind() - != Tree.Kind.METHOD) { + /** Returns true if e is either "e1 != null" or "e2 != null". */ + private boolean isNeqNull(ExpressionTree e, ExpressionTree e1, ExpressionTree e2) { + e = TreeUtils.withoutParens(e); + if (e.getKind() != Tree.Kind.NOT_EQUAL_TO) { + return false; + } + ExpressionTree neqLeft = ((BinaryTree) e).getLeftOperand(); + ExpressionTree neqRight = ((BinaryTree) e).getRightOperand(); + return (((TreeUtils.sameTree(neqLeft, e1) || TreeUtils.sameTree(neqLeft, e2)) + && neqRight.getKind() == Tree.Kind.NULL_LITERAL) + // also check for "null != e1" and "null != e2" + || ((TreeUtils.sameTree(neqRight, e1) || TreeUtils.sameTree(neqRight, e2)) + && neqLeft.getKind() == Tree.Kind.NULL_LITERAL)); + } + + @Override + public Boolean visitBinary(BinaryTree tree, Void p) { + ExpressionTree leftTree = tree.getLeftOperand(); + ExpressionTree rightTree = tree.getRightOperand(); + + if (tree.getKind() == Tree.Kind.CONDITIONAL_OR) { + if (TreeUtils.sameTree(leftTree, topBinaryTree)) { + // left is "a==b" + // check right, which should be a.equals(b) or b.equals(a) or + // similar + return visit(rightTree, p); + } else { return false; + } } - // maybe set some variables?? - } else if (Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) { - // Ensure the if statement is the first statement in the method - - // Retrieve the enclosing if statement tree and method tree - Tree ifStatementTree = null; - MethodTree methodTree = null; - // Set ifStatementTree and methodTree - { - TreePath ppath = parentPath; - Tree candidateTree; - while ((candidateTree = ppath.getLeaf()) != null) { - if (candidateTree.getKind() == Tree.Kind.IF) { - ifStatementTree = candidateTree; - } else if (candidateTree.getKind() == Tree.Kind.METHOD) { - methodTree = (MethodTree) candidateTree; - break; - } - ppath = ppath.getParentPath(); - } + if (tree.getKind() == Tree.Kind.CONDITIONAL_AND) { + // looking for: (a != null && a.equals(b))) + if (isNeqNull(leftTree, left, right)) { + return visit(rightTree, p); + } + return false; } - assert ifStatementTree != null; - assert methodTree != null; - StatementTree firstStmnt = methodTree.getBody().getStatements().get(0); - assert firstStmnt != null; - @SuppressWarnings("interning:not.interned") // comparing AST nodes - boolean notSameNode = firstStmnt != ifStatementTree; - if (notSameNode) { - return false; // The if statement is not the first statement in the method. + + return false; + } + + @Override + public Boolean visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + // looking for: (a != null ? a.equals(b) : false) + ExpressionTree cond = tree.getCondition(); + ExpressionTree trueExp = tree.getTrueExpression(); + ExpressionTree falseExp = tree.getFalseExpression(); + if (isNeqNull(cond, left, right) + && (falseExp.getKind() == Tree.Kind.BOOLEAN_LITERAL) + && ((LiteralTree) falseExp).getValue().equals(false)) { + return visit(trueExp, p); } - } else { return false; - } + } - ExecutableElement enclosingMethod = TreeUtils.elementFromDeclaration(methodTree); - - Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); - Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); - - // Matcher to check for if statement that returns zero - Heuristics.Matcher matcherIfReturnsZero = - new Heuristics.Matcher() { - - @Override - public Boolean visitIf(IfTree tree, Void p) { - return visit(tree.getThenStatement(), p); - } - - @Override - public Boolean visitBlock(BlockTree tree, Void p) { - if (tree.getStatements().isEmpty()) { - return false; - } - return visit(tree.getStatements().get(0), p); - } - - @Override - public Boolean visitReturn(ReturnTree tree, Void p) { - ExpressionTree expr = tree.getExpression(); - return (expr != null - && expr.getKind() == Tree.Kind.INT_LITERAL - && ((LiteralTree) expr).getValue().equals(0)); - } - }; - - boolean hasCompareToMethodAnno = - atypeFactory.getDeclAnnotation(enclosingMethod, CompareToMethod.class) != null; - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(enclosingMethod, EqualsMethod.class) != null; - int params = enclosingMethod.getParameters().size(); - - // Determine whether or not the "then" statement of the if has a single - // "return 0" statement (for the Comparator.compare heuristic). - if (overrides(enclosingMethod, Comparator.class, "compare") - || (hasCompareToMethodAnno && params == 2)) { - boolean returnsZero = - new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) - .match(getCurrentPath()); - - if (!returnsZero) { - return false; + @Override + public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (!isInvocationOfEquals(tree)) { + return false; } - assert params == 2; - Element p1 = enclosingMethod.getParameters().get(0); - Element p2 = enclosingMethod.getParameters().get(1); - return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); - - } else if (overrides(enclosingMethod, Object.class, "equals") - || (hasEqualsMethodAnno && params == 1)) { - assert params == 1; - Element param = enclosingMethod.getParameters().get(0); - Element thisElt = getThis(trees.getScope(getCurrentPath())); - assert thisElt != null; - return (thisElt.equals(lhs) && param.equals(rhs)) - || (thisElt.equals(rhs) && param.equals(lhs)); - - } else if (hasEqualsMethodAnno && params == 2) { - Element p1 = enclosingMethod.getParameters().get(0); - Element p2 = enclosingMethod.getParameters().get(1); - return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); - - } else if (overrides(enclosingMethod, Comparable.class, "compareTo") - || (hasCompareToMethodAnno && params == 1)) { - - boolean returnsZero = - new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) - .match(getCurrentPath()); - - if (!returnsZero) { - return false; + List args = tree.getArguments(); + if (args.size() != 1) { + return false; + } + ExpressionTree arg = args.get(0); + // if (arg.getKind() != Tree.Kind.IDENTIFIER) { + // return false; + // } + // Element argElt = TreeUtils.elementFromUse((IdentifierTree) arg); + + ExpressionTree exp = tree.getMethodSelect(); + if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; } + MemberSelectTree member = (MemberSelectTree) exp; + ExpressionTree receiver = member.getExpression(); + // Element refElt = TreeUtils.elementFromUse(receiver); - assert params == 1; - Element param = enclosingMethod.getParameters().get(0); - Element thisElt = getThis(trees.getScope(getCurrentPath())); - assert thisElt != null; - return (thisElt.equals(lhs) && param.equals(rhs)) - || (thisElt.equals(rhs) && param.equals(lhs)); - } + // if (!((refElt.equals(lhs) && argElt.equals(rhs)) || + // ((refElt.equals(rhs) && argElt.equals(lhs))))) { + // return false; + // } - return false; - } + if (TreeUtils.sameTree(receiver, left) && TreeUtils.sameTree(arg, right)) { + return true; + } + if (TreeUtils.sameTree(receiver, right) && TreeUtils.sameTree(arg, left)) { + return true; + } - /** - * Pattern matches to prevent false positives of the forms: - * - *
{@code
-     * (a == b) || a.equals(b)
-     * (a == b) || (a != null ? a.equals(b) : false)
-     * (a == b) || (a != null && a.equals(b))
-     * }
- * - * Returns true iff the given tree fits this pattern. - * - * @param topBinaryTree the binary operation to check - * @return true iff the tree fits a pattern such as (a == b || a.equals(b)) - */ - private boolean suppressEarlyEquals(BinaryTree topBinaryTree) { - // Only handle == binary trees - if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { return false; - } + } + }; + + boolean okay = + new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrEquals)) + .match(getCurrentPath()); + return okay; + } + + /** + * Pattern matches to prevent false positives of the form {@code (a == b || a.compareTo(b) == 0)}. + * Returns true iff the given tree fits this pattern. + * + * @param topBinaryTree the binary operation to check + * @return true iff the tree fits the pattern (a == b || a.compareTo(b) == 0) + */ + private boolean suppressEarlyCompareTo(BinaryTree topBinaryTree) { + // Only handle == binary trees + if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } - // should strip parens - ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); - ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); - - // looking for ((a == b || a.equals(b)) - Heuristics.Matcher matcherEqOrEquals = - new Heuristics.Matcher() { - - /** Returns true if e is either "e1 != null" or "e2 != null". */ - private boolean isNeqNull( - ExpressionTree e, ExpressionTree e1, ExpressionTree e2) { - e = TreeUtils.withoutParens(e); - if (e.getKind() != Tree.Kind.NOT_EQUAL_TO) { - return false; - } - ExpressionTree neqLeft = ((BinaryTree) e).getLeftOperand(); - ExpressionTree neqRight = ((BinaryTree) e).getRightOperand(); - return (((TreeUtils.sameTree(neqLeft, e1) - || TreeUtils.sameTree(neqLeft, e2)) - && neqRight.getKind() == Tree.Kind.NULL_LITERAL) - // also check for "null != e1" and "null != e2" - || ((TreeUtils.sameTree(neqRight, e1) - || TreeUtils.sameTree(neqRight, e2)) - && neqLeft.getKind() == Tree.Kind.NULL_LITERAL)); - } - - @Override - public Boolean visitBinary(BinaryTree tree, Void p) { - ExpressionTree leftTree = tree.getLeftOperand(); - ExpressionTree rightTree = tree.getRightOperand(); - - if (tree.getKind() == Tree.Kind.CONDITIONAL_OR) { - if (TreeUtils.sameTree(leftTree, topBinaryTree)) { - // left is "a==b" - // check right, which should be a.equals(b) or b.equals(a) or - // similar - return visit(rightTree, p); - } else { - return false; - } - } - - if (tree.getKind() == Tree.Kind.CONDITIONAL_AND) { - // looking for: (a != null && a.equals(b))) - if (isNeqNull(leftTree, left, right)) { - return visit(rightTree, p); - } - return false; - } - - return false; - } - - @Override - public Boolean visitConditionalExpression( - ConditionalExpressionTree tree, Void p) { - // looking for: (a != null ? a.equals(b) : false) - ExpressionTree cond = tree.getCondition(); - ExpressionTree trueExp = tree.getTrueExpression(); - ExpressionTree falseExp = tree.getFalseExpression(); - if (isNeqNull(cond, left, right) - && (falseExp.getKind() == Tree.Kind.BOOLEAN_LITERAL) - && ((LiteralTree) falseExp).getValue().equals(false)) { - return visit(trueExp, p); - } - return false; - } - - @Override - public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (!isInvocationOfEquals(tree)) { - return false; - } - - List args = tree.getArguments(); - if (args.size() != 1) { - return false; - } - ExpressionTree arg = args.get(0); - // if (arg.getKind() != Tree.Kind.IDENTIFIER) { - // return false; - // } - // Element argElt = TreeUtils.elementFromUse((IdentifierTree) arg); - - ExpressionTree exp = tree.getMethodSelect(); - if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; - } - MemberSelectTree member = (MemberSelectTree) exp; - ExpressionTree receiver = member.getExpression(); - // Element refElt = TreeUtils.elementFromUse(receiver); - - // if (!((refElt.equals(lhs) && argElt.equals(rhs)) || - // ((refElt.equals(rhs) && argElt.equals(lhs))))) { - // return false; - // } - - if (TreeUtils.sameTree(receiver, left) && TreeUtils.sameTree(arg, right)) { - return true; - } - if (TreeUtils.sameTree(receiver, right) && TreeUtils.sameTree(arg, left)) { - return true; - } - - return false; - } - }; - - boolean okay = - new Heuristics.Within( - new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrEquals)) - .match(getCurrentPath()); - return okay; - } - - /** - * Pattern matches to prevent false positives of the form {@code (a == b || a.compareTo(b) == - * 0)}. Returns true iff the given tree fits this pattern. - * - * @param topBinaryTree the binary operation to check - * @return true iff the tree fits the pattern (a == b || a.compareTo(b) == 0) - */ - private boolean suppressEarlyCompareTo(BinaryTree topBinaryTree) { - // Only handle == binary trees - if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { - return false; - } + ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); + ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); - ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); - ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); + // Only valid if we're comparing identifiers. + if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { + return false; + } - // Only valid if we're comparing identifiers. - if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { - return false; - } + Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); + Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); - Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); - Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); - - // looking for ((a == b || a.compareTo(b) == 0) - Heuristics.Matcher matcherEqOrCompareTo = - new Heuristics.Matcher() { - - @Override - public Boolean visitBinary(BinaryTree tree, Void p) { - if (tree.getKind() == Tree.Kind.EQUAL_TO) { // a.compareTo(b) == 0 - ExpressionTree leftTree = - tree.getLeftOperand(); // looking for a.compareTo(b) or - // b.compareTo(a) - ExpressionTree rightTree = tree.getRightOperand(); // looking for 0 - - if (rightTree.getKind() != Tree.Kind.INT_LITERAL) { - return false; - } - LiteralTree rightLiteral = (LiteralTree) rightTree; - if (!rightLiteral.getValue().equals(0)) { - return false; - } - - return visit(leftTree, p); - } else { - // a == b || a.compareTo(b) == 0 - @SuppressWarnings( - "interning:assignment.type.incompatible" // AST node comparisons - ) - @InternedDistinct ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b - ExpressionTree rightTree = - tree.getRightOperand(); // looking for a.compareTo(b) == 0 - // or b.compareTo(a) == 0 - if (leftTree != topBinaryTree) { - return false; - } - if (rightTree.getKind() != Tree.Kind.EQUAL_TO) { - return false; - } - return visit(rightTree, p); - } - } - - @Override - public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (!TreeUtils.isMethodInvocation( - tree, comparableCompareTo, checker.getProcessingEnvironment())) { - return false; - } - - List args = tree.getArguments(); - if (args.size() != 1) { - return false; - } - ExpressionTree arg = args.get(0); - if (arg.getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - Element argElt = TreeUtils.elementFromUse(arg); - - ExpressionTree exp = tree.getMethodSelect(); - if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; - } - MemberSelectTree member = (MemberSelectTree) exp; - if (member.getExpression().getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - - Element refElt = TreeUtils.elementFromUse(member.getExpression()); - - if (!((refElt.equals(lhs) && argElt.equals(rhs)) - || (refElt.equals(rhs) && argElt.equals(lhs)))) { - return false; - } - return true; - } - }; - - boolean okay = - new Heuristics.Within( - new Heuristics.OfKind( - Tree.Kind.CONDITIONAL_OR, matcherEqOrCompareTo)) - .match(getCurrentPath()); - return okay; - } - - /** - * Given {@code a == b}, where a has type A and b has type B, don't issue a warning when either - * the declaration of A or that of B is annotated with @Interned because {@code a == b} will be - * true only if a's run-time type is B (or lower), in which case a is actually interned. - */ - private boolean suppressEqualsIfClassIsAnnotated( - AnnotatedTypeMirror left, AnnotatedTypeMirror right) { - // It would be better to just test their greatest lower bound. - // That could permit some comparisons that this forbids. - return classIsAnnotated(left) || classIsAnnotated(right); - } - - /** Returns true if the type's declaration has an @Interned annotation. */ - private boolean classIsAnnotated(AnnotatedTypeMirror type) { - - TypeMirror tm = type.getUnderlyingType(); - if (tm == null) { - // Maybe a type variable or wildcard had no upper bound - return false; - } + // looking for ((a == b || a.compareTo(b) == 0) + Heuristics.Matcher matcherEqOrCompareTo = + new Heuristics.Matcher() { - tm = TypesUtils.findConcreteUpperBound(tm); - if (tm == null || tm.getKind() == TypeKind.ARRAY) { - // Bound of a wildcard might be null - return false; - } + @Override + public Boolean visitBinary(BinaryTree tree, Void p) { + if (tree.getKind() == Tree.Kind.EQUAL_TO) { // a.compareTo(b) == 0 + ExpressionTree leftTree = tree.getLeftOperand(); // looking for a.compareTo(b) or + // b.compareTo(a) + ExpressionTree rightTree = tree.getRightOperand(); // looking for 0 - if (tm.getKind() != TypeKind.DECLARED) { - checker.message( - Diagnostic.Kind.WARNING, - "InterningVisitor.classIsAnnotated: tm = %s (%s)", - tm, - tm.getClass()); - } - Element classElt = ((DeclaredType) tm).asElement(); - if (classElt == null) { - checker.message( - Diagnostic.Kind.WARNING, - "InterningVisitor.classIsAnnotated: classElt = null for tm = %s (%s)", - tm, - tm.getClass()); - } - if (classElt != null) { - AnnotationMirrorSet bound = atypeFactory.getTypeDeclarationBounds(tm); - return atypeFactory.containsSameByClass(bound, Interned.class); - } - return false; - } + if (rightTree.getKind() != Tree.Kind.INT_LITERAL) { + return false; + } + LiteralTree rightLiteral = (LiteralTree) rightTree; + if (!rightLiteral.getValue().equals(0)) { + return false; + } - /** - * Determines the element corresponding to "this" inside a scope. Returns null within static - * methods. - * - * @param scope the scope to search for the element corresponding to "this" in - * @return the element corresponding to "this" in the given scope, or null if not found - */ - private @Nullable Element getThis(Scope scope) { - for (Element e : scope.getLocalElements()) { - if (e.getSimpleName().contentEquals("this")) { - return e; + return visit(leftTree, p); + } else { + // a == b || a.compareTo(b) == 0 + @SuppressWarnings("interning:assignment.type.incompatible" // AST node comparisons + ) + @InternedDistinct ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b + ExpressionTree rightTree = tree.getRightOperand(); // looking for a.compareTo(b) == 0 + // or b.compareTo(a) == 0 + if (leftTree != topBinaryTree) { + return false; + } + if (rightTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } + return visit(rightTree, p); } - } - return null; - } - - /** - * Returns true if the given element overrides the named method in the named class. - * - * @param e an element for a method - * @param clazz the class - * @param method the name of a method - * @return true if the method given by {@code e} overrides the named method in the named class; - * false otherwise - */ - private boolean overrides(ExecutableElement e, Class clazz, String method) { - - // Get the element named by "clazz". - TypeElement clazzElt = elements.getTypeElement(clazz.getCanonicalName()); - assert clazzElt != null; - - // Check all of the methods in the class for name matches and overriding. - for (ExecutableElement elt : ElementFilter.methodsIn(clazzElt.getEnclosedElements())) { - if (elt.getSimpleName().contentEquals(method) && elements.overrides(e, elt, clazzElt)) { - return true; + } + + @Override + public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (!TreeUtils.isMethodInvocation( + tree, comparableCompareTo, checker.getProcessingEnvironment())) { + return false; } - } - return false; + List args = tree.getArguments(); + if (args.size() != 1) { + return false; + } + ExpressionTree arg = args.get(0); + if (arg.getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + Element argElt = TreeUtils.elementFromUse(arg); + + ExpressionTree exp = tree.getMethodSelect(); + if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + MemberSelectTree member = (MemberSelectTree) exp; + if (member.getExpression().getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + + Element refElt = TreeUtils.elementFromUse(member.getExpression()); + + if (!((refElt.equals(lhs) && argElt.equals(rhs)) + || (refElt.equals(rhs) && argElt.equals(lhs)))) { + return false; + } + return true; + } + }; + + boolean okay = + new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrCompareTo)) + .match(getCurrentPath()); + return okay; + } + + /** + * Given {@code a == b}, where a has type A and b has type B, don't issue a warning when either + * the declaration of A or that of B is annotated with @Interned because {@code a == b} will be + * true only if a's run-time type is B (or lower), in which case a is actually interned. + */ + private boolean suppressEqualsIfClassIsAnnotated( + AnnotatedTypeMirror left, AnnotatedTypeMirror right) { + // It would be better to just test their greatest lower bound. + // That could permit some comparisons that this forbids. + return classIsAnnotated(left) || classIsAnnotated(right); + } + + /** Returns true if the type's declaration has an @Interned annotation. */ + private boolean classIsAnnotated(AnnotatedTypeMirror type) { + + TypeMirror tm = type.getUnderlyingType(); + if (tm == null) { + // Maybe a type variable or wildcard had no upper bound + return false; } - /** - * Returns the type to check. - * - * @return the type to check - */ - private @Nullable DeclaredType typeToCheck( - @UnknownInitialization(BaseTypeVisitor.class) InterningVisitor this) { - @SuppressWarnings("signature:assignment.type.incompatible") // user input - @CanonicalName String className = checker.getOption("checkclass"); - if (className == null) { - return null; - } + tm = TypesUtils.findConcreteUpperBound(tm); + if (tm == null || tm.getKind() == TypeKind.ARRAY) { + // Bound of a wildcard might be null + return false; + } - TypeElement classElt = elements.getTypeElement(className); - if (classElt == null) { - return null; - } + if (tm.getKind() != TypeKind.DECLARED) { + checker.message( + Diagnostic.Kind.WARNING, + "InterningVisitor.classIsAnnotated: tm = %s (%s)", + tm, + tm.getClass()); + } + Element classElt = ((DeclaredType) tm).asElement(); + if (classElt == null) { + checker.message( + Diagnostic.Kind.WARNING, + "InterningVisitor.classIsAnnotated: classElt = null for tm = %s (%s)", + tm, + tm.getClass()); + } + if (classElt != null) { + AnnotationMirrorSet bound = atypeFactory.getTypeDeclarationBounds(tm); + return atypeFactory.containsSameByClass(bound, Interned.class); + } + return false; + } + + /** + * Determines the element corresponding to "this" inside a scope. Returns null within static + * methods. + * + * @param scope the scope to search for the element corresponding to "this" in + * @return the element corresponding to "this" in the given scope, or null if not found + */ + private @Nullable Element getThis(Scope scope) { + for (Element e : scope.getLocalElements()) { + if (e.getSimpleName().contentEquals("this")) { + return e; + } + } + return null; + } + + /** + * Returns true if the given element overrides the named method in the named class. + * + * @param e an element for a method + * @param clazz the class + * @param method the name of a method + * @return true if the method given by {@code e} overrides the named method in the named class; + * false otherwise + */ + private boolean overrides(ExecutableElement e, Class clazz, String method) { + + // Get the element named by "clazz". + TypeElement clazzElt = elements.getTypeElement(clazz.getCanonicalName()); + assert clazzElt != null; + + // Check all of the methods in the class for name matches and overriding. + for (ExecutableElement elt : ElementFilter.methodsIn(clazzElt.getEnclosedElements())) { + if (elt.getSimpleName().contentEquals(method) && elements.overrides(e, elt, clazzElt)) { + return true; + } + } - return types.getDeclaredType(classElt); + return false; + } + + /** + * Returns the type to check. + * + * @return the type to check + */ + private @Nullable DeclaredType typeToCheck( + @UnknownInitialization(BaseTypeVisitor.class) InterningVisitor this) { + @SuppressWarnings("signature:assignment.type.incompatible") // user input + @CanonicalName String className = checker.getOption("checkclass"); + if (className == null) { + return null; } - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - if (castType.getKind().isPrimitive()) { - return true; - } - return super.isTypeCastSafe(castType, exprType); + TypeElement classElt = elements.getTypeElement(className); + if (classElt == null) { + return null; + } + + return types.getDeclaredType(classElt); + } + + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + if (castType.getKind().isPrimitive()) { + return true; } + return super.isTypeCastSafe(castType, exprType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java index 91bc9ac9cfc..76256f1b706 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.lock; +import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFStore; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.type.TypeMirror; - /** * The analysis class for the lock type system. * @@ -16,33 +15,33 @@ */ public class LockAnalysis extends CFAbstractAnalysis { - /** - * Creates a new {@link LockAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public LockAnalysis(BaseTypeChecker checker, LockAnnotatedTypeFactory factory) { - super(checker, factory); - } - - @Override - public LockTransfer createTransferFunction() { - return new LockTransfer(this, (LockChecker) checker); - } - - @Override - public LockStore createEmptyStore(boolean sequentialSemantics) { - return new LockStore(this, sequentialSemantics); - } - - @Override - public LockStore createCopiedStore(LockStore s) { - return new LockStore(this, s); - } - - @Override - public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { - return defaultCreateAbstractValue(this, annotations, underlyingType); - } + /** + * Creates a new {@link LockAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public LockAnalysis(BaseTypeChecker checker, LockAnnotatedTypeFactory factory) { + super(checker, factory); + } + + @Override + public LockTransfer createTransferFunction() { + return new LockTransfer(this, (LockChecker) checker); + } + + @Override + public LockStore createEmptyStore(boolean sequentialSemantics) { + return new LockStore(this, sequentialSemantics); + } + + @Override + public LockStore createCopiedStore(LockStore s) { + return new LockStore(this, s); + } + + @Override + public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { + return defaultCreateAbstractValue(this, annotations, underlyingType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java index 43c8882a3ed..043f2ff907a 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java @@ -4,7 +4,22 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.lock.qual.EnsuresLockHeld; import org.checkerframework.checker.lock.qual.EnsuresLockHeldIf; import org.checkerframework.checker.lock.qual.GuardSatisfied; @@ -49,24 +64,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** * LockAnnotatedTypeFactory builds types with @LockHeld and @LockPossiblyHeld annotations. LockHeld * identifies that an object is being used as a lock and is being held when a given tree is @@ -80,715 +77,702 @@ * @checker_framework.manual #lock-checker Lock Checker */ public class LockAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory { + extends GenericAnnotatedTypeFactory { - /** dependent type annotation error message for when the expression is not effectively final. */ - public static final String NOT_EFFECTIVELY_FINAL = "lock expression is not effectively final"; + /** dependent type annotation error message for when the expression is not effectively final. */ + public static final String NOT_EFFECTIVELY_FINAL = "lock expression is not effectively final"; - /** The @{@link LockHeld} annotation. */ - protected final AnnotationMirror LOCKHELD = - AnnotationBuilder.fromClass(elements, LockHeld.class); + /** The @{@link LockHeld} annotation. */ + protected final AnnotationMirror LOCKHELD = AnnotationBuilder.fromClass(elements, LockHeld.class); - /** The @{@link LockPossiblyHeld} annotation. */ - protected final AnnotationMirror LOCKPOSSIBLYHELD = - AnnotationBuilder.fromClass(elements, LockPossiblyHeld.class); + /** The @{@link LockPossiblyHeld} annotation. */ + protected final AnnotationMirror LOCKPOSSIBLYHELD = + AnnotationBuilder.fromClass(elements, LockPossiblyHeld.class); - /** The @{@link SideEffectFree} annotation. */ - protected final AnnotationMirror SIDEEFFECTFREE = - AnnotationBuilder.fromClass(elements, SideEffectFree.class); + /** The @{@link SideEffectFree} annotation. */ + protected final AnnotationMirror SIDEEFFECTFREE = + AnnotationBuilder.fromClass(elements, SideEffectFree.class); - /** The @{@link GuardedByUnknown} annotation. */ - protected final AnnotationMirror GUARDEDBYUNKNOWN = - AnnotationBuilder.fromClass(elements, GuardedByUnknown.class); + /** The @{@link GuardedByUnknown} annotation. */ + protected final AnnotationMirror GUARDEDBYUNKNOWN = + AnnotationBuilder.fromClass(elements, GuardedByUnknown.class); - /** The @{@link GuardedBy} annotation. */ - protected final AnnotationMirror GUARDEDBY = - createGuardedByAnnotationMirror(new ArrayList()); + /** The @{@link GuardedBy} annotation. */ + protected final AnnotationMirror GUARDEDBY = + createGuardedByAnnotationMirror(new ArrayList()); - /** The @{@link NewObject} annotation. */ - protected final AnnotationMirror NEWOBJECT = - AnnotationBuilder.fromClass(elements, NewObject.class); + /** The @{@link NewObject} annotation. */ + protected final AnnotationMirror NEWOBJECT = + AnnotationBuilder.fromClass(elements, NewObject.class); - /** The @{@link GuardedByBottom} annotation. */ - protected final AnnotationMirror GUARDEDBYBOTTOM = - AnnotationBuilder.fromClass(elements, GuardedByBottom.class); + /** The @{@link GuardedByBottom} annotation. */ + protected final AnnotationMirror GUARDEDBYBOTTOM = + AnnotationBuilder.fromClass(elements, GuardedByBottom.class); - /** The @{@link GuardSatisfied} annotation. */ - protected final AnnotationMirror GUARDSATISFIED = - AnnotationBuilder.fromClass(elements, GuardSatisfied.class); + /** The @{@link GuardSatisfied} annotation. */ + protected final AnnotationMirror GUARDSATISFIED = + AnnotationBuilder.fromClass(elements, GuardSatisfied.class); - /** The value() element/field of a @GuardedBy annotation. */ - protected final ExecutableElement guardedByValueElement = - TreeUtils.getMethod(GuardedBy.class, "value", 0, processingEnv); + /** The value() element/field of a @GuardedBy annotation. */ + protected final ExecutableElement guardedByValueElement = + TreeUtils.getMethod(GuardedBy.class, "value", 0, processingEnv); - /** The value() element/field of a @GuardSatisfied annotation. */ - protected final ExecutableElement guardSatisfiedValueElement = - TreeUtils.getMethod(GuardSatisfied.class, "value", 0, processingEnv); + /** The value() element/field of a @GuardSatisfied annotation. */ + protected final ExecutableElement guardSatisfiedValueElement = + TreeUtils.getMethod(GuardSatisfied.class, "value", 0, processingEnv); - /** The EnsuresLockHeld.value element/field. */ - protected final ExecutableElement ensuresLockHeldValueElement = - TreeUtils.getMethod(EnsuresLockHeld.class, "value", 0, processingEnv); + /** The EnsuresLockHeld.value element/field. */ + protected final ExecutableElement ensuresLockHeldValueElement = + TreeUtils.getMethod(EnsuresLockHeld.class, "value", 0, processingEnv); - /** The EnsuresLockHeldIf.expression element/field. */ - protected final ExecutableElement ensuresLockHeldIfExpressionElement = - TreeUtils.getMethod(EnsuresLockHeldIf.class, "expression", 0, processingEnv); + /** The EnsuresLockHeldIf.expression element/field. */ + protected final ExecutableElement ensuresLockHeldIfExpressionElement = + TreeUtils.getMethod(EnsuresLockHeldIf.class, "expression", 0, processingEnv); - /** The net.jcip.annotations.GuardedBy annotation, or null if not on the classpath. */ - protected final @Nullable Class jcipGuardedBy; + /** The net.jcip.annotations.GuardedBy annotation, or null if not on the classpath. */ + protected final @Nullable Class jcipGuardedBy; - /** The javax.annotation.concurrent.GuardedBy annotation, or null if not on the classpath. */ - protected final @Nullable Class javaxGuardedBy; + /** The javax.annotation.concurrent.GuardedBy annotation, or null if not on the classpath. */ + protected final @Nullable Class javaxGuardedBy; - /** Create a new LockAnnotatedTypeFactory. */ - public LockAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); + /** Create a new LockAnnotatedTypeFactory. */ + public LockAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); - jcipGuardedBy = classForNameOrNull("net.jcip.annotations.GuardedBy"); + jcipGuardedBy = classForNameOrNull("net.jcip.annotations.GuardedBy"); - javaxGuardedBy = classForNameOrNull("javax.annotation.concurrent.GuardedBy"); + javaxGuardedBy = classForNameOrNull("javax.annotation.concurrent.GuardedBy"); - postInit(); - } + postInit(); + } - /** - * Returns the value of Class.forName, or null if Class.forName would throw an exception. - * - * @param annotationClassName an annotation's name, in ClassGetName format - * @return an annotation class or null - */ - @SuppressWarnings("unchecked") // cast to generic type - private @Nullable Class classForNameOrNull( - @ClassGetName String annotationClassName) { - try { - return (Class) Class.forName(annotationClassName); - } catch (Exception e) { - return null; - } + /** + * Returns the value of Class.forName, or null if Class.forName would throw an exception. + * + * @param annotationClassName an annotation's name, in ClassGetName format + * @return an annotation class or null + */ + @SuppressWarnings("unchecked") // cast to generic type + private @Nullable Class classForNameOrNull( + @ClassGetName String annotationClassName) { + try { + return (Class) Class.forName(annotationClassName); + } catch (Exception e) { + return null; } + } + + @Override + protected DependentTypesHelper createDependentTypesHelper() { + return new DependentTypesHelper(this) { + @Override + protected void reportErrors(Tree errorTree, List errors) { + // If the error message is NOT_EFFECTIVELY_FINAL, then report + // "lock.expression.not.final" instead of "expression.unparsable.type.invalid". + List superErrors = new ArrayList<>(errors.size()); + for (DependentTypesError error : errors) { + if (error.error.equals(NOT_EFFECTIVELY_FINAL)) { + checker.reportError(errorTree, "lock.expression.not.final", error.expression); + } else { + superErrors.add(error); + } + } + super.reportErrors(errorTree, superErrors); + } + + @Override + protected boolean shouldPassThroughExpression(String expression) { + // There is no expression to use to replace here, so just pass the expression + // along. + return super.shouldPassThroughExpression(expression) + || LockVisitor.SELF_RECEIVER_PATTERN.matcher(expression).matches(); + } + + @Override + protected @Nullable JavaExpression transform(JavaExpression javaExpr) { + if (javaExpr instanceof Unknown || isExpressionEffectivelyFinal(javaExpr)) { + return javaExpr; + } - @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new DependentTypesHelper(this) { - @Override - protected void reportErrors(Tree errorTree, List errors) { - // If the error message is NOT_EFFECTIVELY_FINAL, then report - // "lock.expression.not.final" instead of "expression.unparsable.type.invalid". - List superErrors = new ArrayList<>(errors.size()); - for (DependentTypesError error : errors) { - if (error.error.equals(NOT_EFFECTIVELY_FINAL)) { - checker.reportError( - errorTree, "lock.expression.not.final", error.expression); - } else { - superErrors.add(error); - } - } - super.reportErrors(errorTree, superErrors); - } - - @Override - protected boolean shouldPassThroughExpression(String expression) { - // There is no expression to use to replace here, so just pass the expression - // along. - return super.shouldPassThroughExpression(expression) - || LockVisitor.SELF_RECEIVER_PATTERN.matcher(expression).matches(); - } - - @Override - protected @Nullable JavaExpression transform(JavaExpression javaExpr) { - if (javaExpr instanceof Unknown || isExpressionEffectivelyFinal(javaExpr)) { - return javaExpr; - } - - // If the expression isn't effectively final, then return the NOT_EFFECTIVELY_FINAL - // error string. - return createError(javaExpr.toString(), NOT_EFFECTIVELY_FINAL); - } - }; + // If the expression isn't effectively final, then return the NOT_EFFECTIVELY_FINAL + // error string. + return createError(javaExpr.toString(), NOT_EFFECTIVELY_FINAL); + } + }; + } + + /** + * Returns whether or not the expression is effectively final. + * + *

This method returns true in the following cases when expr is: + * + *

1. a field access and the field is final and the field access expression is effectively + * final as specified by this method. + * + *

2. an effectively final local variable. + * + *

3. a deterministic method call whose arguments and receiver expression are effectively final + * as specified by this method. + * + *

4. a this reference or a class literal + * + * @param expr expression + * @return whether or not the expression is effectively final + */ + boolean isExpressionEffectivelyFinal(JavaExpression expr) { + if (expr instanceof FieldAccess) { + FieldAccess fieldAccess = (FieldAccess) expr; + JavaExpression receiver = fieldAccess.getReceiver(); + // Don't call fieldAccess + return fieldAccess.isFinal() && isExpressionEffectivelyFinal(receiver); + } else if (expr instanceof LocalVariable) { + return ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement()); + } else if (expr instanceof MethodCall) { + MethodCall methodCall = (MethodCall) expr; + for (JavaExpression arg : methodCall.getArguments()) { + if (!isExpressionEffectivelyFinal(arg)) { + return false; + } + } + return PurityUtils.isDeterministic(this, methodCall.getElement()) + && isExpressionEffectivelyFinal(methodCall.getReceiver()); + } else if (expr instanceof ThisReference || expr instanceof ClassName) { + // this is always final. "ClassName" is actually a class literal (String.class), it's + // final too. + return true; + } else { // type of 'expr' is not supported in @GuardedBy(...) lock expressions + return false; } + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + LockHeld.class, + LockPossiblyHeld.class, + GuardedBy.class, + GuardedByUnknown.class, + GuardSatisfied.class, + NewObject.class, + GuardedByBottom.class)); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new LockQualifierHierarchy(getSupportedTypeQualifiers(), elements); + } + + @Override + protected LockAnalysis createFlowAnalysis() { + return new LockAnalysis(checker, this); + } + + @Override + public LockTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new LockTransfer((LockAnalysis) analysis, (LockChecker) this.checker); + } + + /** LockQualifierHierarchy. */ + class LockQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + /** Qualifier kind for the @{@link GuardedByUnknown} annotation. */ + private final QualifierKind GUARDEDBYUNKNOWN_KIND; + + /** Qualifier kind for the @{@link GuardedBy} annotation. */ + private final QualifierKind GUARDEDBY_KIND; + + /** Qualifier kind for the @{@link GuardSatisfied} annotation. */ + private final QualifierKind GUARDSATISFIED_KIND; + + /** Qualifier kind for the @{@link NewObject} annotation. */ + private final QualifierKind NEWOBJECT_KIND; + + /** Qualifier kind for the @{@link GuardedByBottom} annotation. */ + private final QualifierKind GUARDEDBYBOTTOM_KIND; /** - * Returns whether or not the expression is effectively final. - * - *

This method returns true in the following cases when expr is: + * Creates a LockQualifierHierarchy. * - *

1. a field access and the field is final and the field access expression is effectively - * final as specified by this method. - * - *

2. an effectively final local variable. - * - *

3. a deterministic method call whose arguments and receiver expression are effectively - * final as specified by this method. - * - *

4. a this reference or a class literal - * - * @param expr expression - * @return whether or not the expression is effectively final + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils */ - boolean isExpressionEffectivelyFinal(JavaExpression expr) { - if (expr instanceof FieldAccess) { - FieldAccess fieldAccess = (FieldAccess) expr; - JavaExpression receiver = fieldAccess.getReceiver(); - // Don't call fieldAccess - return fieldAccess.isFinal() && isExpressionEffectivelyFinal(receiver); - } else if (expr instanceof LocalVariable) { - return ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement()); - } else if (expr instanceof MethodCall) { - MethodCall methodCall = (MethodCall) expr; - for (JavaExpression arg : methodCall.getArguments()) { - if (!isExpressionEffectivelyFinal(arg)) { - return false; - } - } - return PurityUtils.isDeterministic(this, methodCall.getElement()) - && isExpressionEffectivelyFinal(methodCall.getReceiver()); - } else if (expr instanceof ThisReference || expr instanceof ClassName) { - // this is always final. "ClassName" is actually a class literal (String.class), it's - // final too. - return true; - } else { // type of 'expr' is not supported in @GuardedBy(...) lock expressions - return false; - } - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - LockHeld.class, - LockPossiblyHeld.class, - GuardedBy.class, - GuardedByUnknown.class, - GuardSatisfied.class, - NewObject.class, - GuardedByBottom.class)); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new LockQualifierHierarchy(getSupportedTypeQualifiers(), elements); + public LockQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, LockAnnotatedTypeFactory.this); + GUARDEDBYUNKNOWN_KIND = getQualifierKind(GUARDEDBYUNKNOWN); + GUARDEDBY_KIND = getQualifierKind(GUARDEDBY); + GUARDSATISFIED_KIND = getQualifierKind(GUARDSATISFIED); + NEWOBJECT_KIND = getQualifierKind(NEWOBJECT); + GUARDEDBYBOTTOM_KIND = getQualifierKind(GUARDEDBYBOTTOM); } @Override - protected LockAnalysis createFlowAnalysis() { - return new LockAnalysis(checker, this); + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == GUARDEDBY_KIND && superKind == GUARDEDBY_KIND) { + List subLocks = + AnnotationUtils.getElementValueArray( + superAnno, guardedByValueElement, String.class, Collections.emptyList()); + List superLocks = + AnnotationUtils.getElementValueArray( + subAnno, guardedByValueElement, String.class, Collections.emptyList()); + return subLocks.containsAll(superLocks) && superLocks.containsAll(subLocks); + } else if (subKind == GUARDSATISFIED_KIND && superKind == GUARDSATISFIED_KIND) { + return AnnotationUtils.areSame(superAnno, subAnno); + } + throw new RuntimeException("Unexpected"); } @Override - public LockTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new LockTransfer((LockAnalysis) analysis, (LockChecker) this.checker); - } - - /** LockQualifierHierarchy. */ - class LockQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** Qualifier kind for the @{@link GuardedByUnknown} annotation. */ - private final QualifierKind GUARDEDBYUNKNOWN_KIND; - - /** Qualifier kind for the @{@link GuardedBy} annotation. */ - private final QualifierKind GUARDEDBY_KIND; - - /** Qualifier kind for the @{@link GuardSatisfied} annotation. */ - private final QualifierKind GUARDSATISFIED_KIND; - - /** Qualifier kind for the @{@link NewObject} annotation. */ - private final QualifierKind NEWOBJECT_KIND; - - /** Qualifier kind for the @{@link GuardedByBottom} annotation. */ - private final QualifierKind GUARDEDBYBOTTOM_KIND; - - /** - * Creates a LockQualifierHierarchy. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - public LockQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, LockAnnotatedTypeFactory.this); - GUARDEDBYUNKNOWN_KIND = getQualifierKind(GUARDEDBYUNKNOWN); - GUARDEDBY_KIND = getQualifierKind(GUARDEDBY); - GUARDSATISFIED_KIND = getQualifierKind(GUARDSATISFIED); - NEWOBJECT_KIND = getQualifierKind(NEWOBJECT); - GUARDEDBYBOTTOM_KIND = getQualifierKind(GUARDEDBYBOTTOM); - } - - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == GUARDEDBY_KIND && superKind == GUARDEDBY_KIND) { - List subLocks = - AnnotationUtils.getElementValueArray( - superAnno, - guardedByValueElement, - String.class, - Collections.emptyList()); - List superLocks = - AnnotationUtils.getElementValueArray( - subAnno, - guardedByValueElement, - String.class, - Collections.emptyList()); - return subLocks.containsAll(superLocks) && superLocks.containsAll(subLocks); - } else if (subKind == GUARDSATISFIED_KIND && superKind == GUARDSATISFIED_KIND) { - return AnnotationUtils.areSame(superAnno, subAnno); - } - throw new RuntimeException("Unexpected"); - } - - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { - List locks1 = - AnnotationUtils.getElementValueArray( - a1, guardedByValueElement, String.class, Collections.emptyList()); - List locks2 = - AnnotationUtils.getElementValueArray( - a2, guardedByValueElement, String.class, Collections.emptyList()); - if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { - return a1; - } else { - return GUARDEDBYUNKNOWN; - } - } else if (qualifierKind1 == GUARDSATISFIED_KIND - && qualifierKind2 == GUARDSATISFIED_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return GUARDEDBYUNKNOWN; - } - } else if (qualifierKind1 == GUARDEDBYBOTTOM_KIND) { - return a2; - } else if (qualifierKind2 == GUARDEDBYBOTTOM_KIND) { - return a1; - } else if (qualifierKind1 == NEWOBJECT_KIND) { - return a2; - } else if (qualifierKind2 == NEWOBJECT_KIND) { - return a1; - } - throw new TypeSystemError( - "leastUpperBoundWithElements(%s, %s, %s, %s, %s)", - a1, qualifierKind1, a2, qualifierKind2, lubKind); + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { + List locks1 = + AnnotationUtils.getElementValueArray( + a1, guardedByValueElement, String.class, Collections.emptyList()); + List locks2 = + AnnotationUtils.getElementValueArray( + a2, guardedByValueElement, String.class, Collections.emptyList()); + if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { + return a1; + } else { + return GUARDEDBYUNKNOWN; } - - // GLB never returns @NewObject unless one of the argumetns is @NewObject; it returns - // @GuardedByBottom instead, to prevent showing users the unexpected @NewObject type. - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { - List locks1 = - AnnotationUtils.getElementValueArray( - a1, guardedByValueElement, String.class, Collections.emptyList()); - List locks2 = - AnnotationUtils.getElementValueArray( - a2, guardedByValueElement, String.class, Collections.emptyList()); - if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { - return a1; - } else { - return GUARDEDBYBOTTOM; - } - } else if (qualifierKind1 == GUARDSATISFIED_KIND - && qualifierKind2 == GUARDSATISFIED_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return GUARDEDBYBOTTOM; - } - } else if (qualifierKind1 == GUARDEDBYUNKNOWN_KIND) { - return a2; - } else if (qualifierKind2 == GUARDEDBYUNKNOWN_KIND) { - return a1; - } - throw new TypeSystemError( - "greatestLowerBoundWithElements(%s, %s, %s, %s, %s)", - a1, qualifierKind1, a2, qualifierKind2, glbKind); + } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return GUARDEDBYUNKNOWN; } + } else if (qualifierKind1 == GUARDEDBYBOTTOM_KIND) { + return a2; + } else if (qualifierKind2 == GUARDEDBYBOTTOM_KIND) { + return a1; + } else if (qualifierKind1 == NEWOBJECT_KIND) { + return a2; + } else if (qualifierKind2 == NEWOBJECT_KIND) { + return a1; + } + throw new TypeSystemError( + "leastUpperBoundWithElements(%s, %s, %s, %s, %s)", + a1, qualifierKind1, a2, qualifierKind2, lubKind); } - // The side effect annotations processed by the Lock Checker. - enum SideEffectAnnotation { - MAYRELEASELOCKS("@MayReleaseLocks", MayReleaseLocks.class), - RELEASESNOLOCKS("@ReleasesNoLocks", ReleasesNoLocks.class), - LOCKINGFREE("@LockingFree", LockingFree.class), - SIDEEFFECTFREE("@SideEffectFree", SideEffectFree.class), - PURE("@Pure", Pure.class); - final String annotation; - final Class annotationClass; - - SideEffectAnnotation(String annotation, Class annotationClass) { - this.annotation = annotation; - this.annotationClass = annotationClass; - } - - public String getNameOfSideEffectAnnotation() { - return annotation; - } - - public Class getAnnotationClass() { - return annotationClass; - } - - /** - * Returns true if the receiver side effect annotation is weaker than side effect annotation - * 'other'. - */ - boolean isWeakerThan(SideEffectAnnotation other) { - boolean weaker = false; - - switch (other) { - case MAYRELEASELOCKS: - break; - case RELEASESNOLOCKS: - if (this == SideEffectAnnotation.MAYRELEASELOCKS) { - weaker = true; - } - break; - case LOCKINGFREE: - switch (this) { - case MAYRELEASELOCKS: - case RELEASESNOLOCKS: - weaker = true; - break; - default: - } - break; - case SIDEEFFECTFREE: - switch (this) { - case MAYRELEASELOCKS: - case RELEASESNOLOCKS: - case LOCKINGFREE: - weaker = true; - break; - default: - } - break; - case PURE: - switch (this) { - case MAYRELEASELOCKS: - case RELEASESNOLOCKS: - case LOCKINGFREE: - case SIDEEFFECTFREE: - weaker = true; - break; - default: - } - break; - } - - return weaker; + // GLB never returns @NewObject unless one of the argumetns is @NewObject; it returns + // @GuardedByBottom instead, to prevent showing users the unexpected @NewObject type. + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { + List locks1 = + AnnotationUtils.getElementValueArray( + a1, guardedByValueElement, String.class, Collections.emptyList()); + List locks2 = + AnnotationUtils.getElementValueArray( + a2, guardedByValueElement, String.class, Collections.emptyList()); + if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { + return a1; + } else { + return GUARDEDBYBOTTOM; } - - static SideEffectAnnotation weakest = null; - - public static SideEffectAnnotation weakest() { - if (weakest == null) { - for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { - if (weakest == null) { - weakest = sea; - } - if (sea.isWeakerThan(weakest)) { - weakest = sea; - } - } - } - return weakest; + } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return GUARDEDBYBOTTOM; } + } else if (qualifierKind1 == GUARDEDBYUNKNOWN_KIND) { + return a2; + } else if (qualifierKind2 == GUARDEDBYUNKNOWN_KIND) { + return a1; + } + throw new TypeSystemError( + "greatestLowerBoundWithElements(%s, %s, %s, %s, %s)", + a1, qualifierKind1, a2, qualifierKind2, glbKind); + } + } + + // The side effect annotations processed by the Lock Checker. + enum SideEffectAnnotation { + MAYRELEASELOCKS("@MayReleaseLocks", MayReleaseLocks.class), + RELEASESNOLOCKS("@ReleasesNoLocks", ReleasesNoLocks.class), + LOCKINGFREE("@LockingFree", LockingFree.class), + SIDEEFFECTFREE("@SideEffectFree", SideEffectFree.class), + PURE("@Pure", Pure.class); + final String annotation; + final Class annotationClass; + + SideEffectAnnotation(String annotation, Class annotationClass) { + this.annotation = annotation; + this.annotationClass = annotationClass; } - /** - * Indicates which side effect annotation is present on the given method. If more than one - * annotation is present, this method issues an error (if issueErrorIfMoreThanOnePresent is - * true) and returns the annotation providing the weakest guarantee. Only call with - * issueErrorIfMoreThanOnePresent == true when visiting a method definition. This prevents - * multiple errors being issued for the same method (as would occur if - * issueErrorIfMoreThanOnePresent were set to true when visiting method invocations). If no - * annotation is present, return RELEASESNOLOCKS as the default, and MAYRELEASELOCKS as the - * conservative default. - * - * @param methodElement the method element - * @param issueErrorIfMoreThanOnePresent whether to issue an error if more than one side effect - * annotation is present on the method - * @return the side effect annotation that is present on the given method - */ - /*package-private*/ @Nullable SideEffectAnnotation methodSideEffectAnnotation( - ExecutableElement methodElement, boolean issueErrorIfMoreThanOnePresent) { - if (methodElement == null) { - // When there is not enough information to determine the correct side effect annotation, - // return the weakest one. - return SideEffectAnnotation.weakest(); - } - - Set sideEffectAnnotationPresent = - EnumSet.noneOf(SideEffectAnnotation.class); - for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { - if (getDeclAnnotationNoAliases(methodElement, sea.getAnnotationClass()) != null) { - sideEffectAnnotationPresent.add(sea); - } - } - - int count = sideEffectAnnotationPresent.size(); - - if (count == 0) { - return defaults.applyConservativeDefaults(methodElement) - ? SideEffectAnnotation.MAYRELEASELOCKS - : SideEffectAnnotation.RELEASESNOLOCKS; - } - - if (count > 1 && issueErrorIfMoreThanOnePresent) { - // TODO: Turn on after figuring out how this interacts with inherited annotations. - // checker.reportError(methodElement, "multiple.sideeffect.annotations"); - } - - SideEffectAnnotation weakest = null; - // At least one side effect annotation was found. Return the weakest. - for (SideEffectAnnotation sea : sideEffectAnnotationPresent) { - if (weakest == null || sea.isWeakerThan(weakest)) { - weakest = sea; - } - } - return weakest; + public String getNameOfSideEffectAnnotation() { + return annotation; } - /** - * Returns the index (that is, the {@code value} element) on the {@code @}{@link GuardSatisfied} - * annotation in the given AnnotatedTypeMirror. - * - * @param atm an AnnotatedTypeMirror containing a {@link GuardSatisfied} annotation - * @return the index on the {@link GuardSatisfied} annotation - */ - /*package-private*/ int getGuardSatisfiedIndex(AnnotatedTypeMirror atm) { - return getGuardSatisfiedIndex(atm.getAnnotation(GuardSatisfied.class)); + public Class getAnnotationClass() { + return annotationClass; } /** - * Returns the index (that is, the {@code value} element) on the given {@code @}{@link - * GuardSatisfied} annotation. - * - * @param am an AnnotationMirror for a {@link GuardSatisfied} annotation - * @return the index on the {@link GuardSatisfied} annotation + * Returns true if the receiver side effect annotation is weaker than side effect annotation + * 'other'. */ - /*package-private*/ int getGuardSatisfiedIndex(AnnotationMirror am) { - return AnnotationUtils.getElementValueInt(am, guardSatisfiedValueElement, -1); + boolean isWeakerThan(SideEffectAnnotation other) { + boolean weaker = false; + + switch (other) { + case MAYRELEASELOCKS: + break; + case RELEASESNOLOCKS: + if (this == SideEffectAnnotation.MAYRELEASELOCKS) { + weaker = true; + } + break; + case LOCKINGFREE: + switch (this) { + case MAYRELEASELOCKS: + case RELEASESNOLOCKS: + weaker = true; + break; + default: + } + break; + case SIDEEFFECTFREE: + switch (this) { + case MAYRELEASELOCKS: + case RELEASESNOLOCKS: + case LOCKINGFREE: + weaker = true; + break; + default: + } + break; + case PURE: + switch (this) { + case MAYRELEASELOCKS: + case RELEASESNOLOCKS: + case LOCKINGFREE: + case SIDEEFFECTFREE: + weaker = true; + break; + default: + } + break; + } + + return weaker; } - @Override - public ParameterizedExecutableType methodFromUse( - ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - ParameterizedExecutableType mType = super.methodFromUse(tree, methodElt, receiverType); + static SideEffectAnnotation weakest = null; - if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { - return mType; + public static SideEffectAnnotation weakest() { + if (weakest == null) { + for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { + if (weakest == null) { + weakest = sea; + } + if (sea.isWeakerThan(weakest)) { + weakest = sea; + } } + } + return weakest; + } + } + + /** + * Indicates which side effect annotation is present on the given method. If more than one + * annotation is present, this method issues an error (if issueErrorIfMoreThanOnePresent is true) + * and returns the annotation providing the weakest guarantee. Only call with + * issueErrorIfMoreThanOnePresent == true when visiting a method definition. This prevents + * multiple errors being issued for the same method (as would occur if + * issueErrorIfMoreThanOnePresent were set to true when visiting method invocations). If no + * annotation is present, return RELEASESNOLOCKS as the default, and MAYRELEASELOCKS as the + * conservative default. + * + * @param methodElement the method element + * @param issueErrorIfMoreThanOnePresent whether to issue an error if more than one side effect + * annotation is present on the method + * @return the side effect annotation that is present on the given method + */ + /*package-private*/ @Nullable SideEffectAnnotation methodSideEffectAnnotation( + ExecutableElement methodElement, boolean issueErrorIfMoreThanOnePresent) { + if (methodElement == null) { + // When there is not enough information to determine the correct side effect annotation, + // return the weakest one. + return SideEffectAnnotation.weakest(); + } - // If a method's formal return type is annotated with @GuardSatisfied(index), look for the - // first instance of @GuardSatisfied(index) in the method definition's receiver type or - // formal parameters, retrieve the corresponding type of the actual parameter / receiver at - // the call site (e.g. @GuardedBy("someLock") and replace the return type at the call site - // with this type. - - AnnotatedExecutableType invokedMethod = mType.executableType; + Set sideEffectAnnotationPresent = + EnumSet.noneOf(SideEffectAnnotation.class); + for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { + if (getDeclAnnotationNoAliases(methodElement, sea.getAnnotationClass()) != null) { + sideEffectAnnotationPresent.add(sea); + } + } - if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) { - return mType; - } + int count = sideEffectAnnotationPresent.size(); - AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType(); + if (count == 0) { + return defaults.applyConservativeDefaults(methodElement) + ? SideEffectAnnotation.MAYRELEASELOCKS + : SideEffectAnnotation.RELEASESNOLOCKS; + } - if (methodDefinitionReturn == null - || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) { - return mType; - } + if (count > 1 && issueErrorIfMoreThanOnePresent) { + // TODO: Turn on after figuring out how this interacts with inherited annotations. + // checker.reportError(methodElement, "multiple.sideeffect.annotations"); + } - int returnGuardSatisfiedIndex = getGuardSatisfiedIndex(methodDefinitionReturn); + SideEffectAnnotation weakest = null; + // At least one side effect annotation was found. Return the weakest. + for (SideEffectAnnotation sea : sideEffectAnnotationPresent) { + if (weakest == null || sea.isWeakerThan(weakest)) { + weakest = sea; + } + } + return weakest; + } + + /** + * Returns the index (that is, the {@code value} element) on the {@code @}{@link GuardSatisfied} + * annotation in the given AnnotatedTypeMirror. + * + * @param atm an AnnotatedTypeMirror containing a {@link GuardSatisfied} annotation + * @return the index on the {@link GuardSatisfied} annotation + */ + /*package-private*/ int getGuardSatisfiedIndex(AnnotatedTypeMirror atm) { + return getGuardSatisfiedIndex(atm.getAnnotation(GuardSatisfied.class)); + } + + /** + * Returns the index (that is, the {@code value} element) on the given {@code @}{@link + * GuardSatisfied} annotation. + * + * @param am an AnnotationMirror for a {@link GuardSatisfied} annotation + * @return the index on the {@link GuardSatisfied} annotation + */ + /*package-private*/ int getGuardSatisfiedIndex(AnnotationMirror am) { + return AnnotationUtils.getElementValueInt(am, guardSatisfiedValueElement, -1); + } + + @Override + public ParameterizedExecutableType methodFromUse( + ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { + ParameterizedExecutableType mType = super.methodFromUse(tree, methodElt, receiverType); + + if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { + return mType; + } - // @GuardSatisfied with no index defaults to index -1. Ignore instances of @GuardSatisfied - // with no index. If a method is defined with a return type of @GuardSatisfied with no - // index, an error is reported by LockVisitor.visitMethod. + // If a method's formal return type is annotated with @GuardSatisfied(index), look for the + // first instance of @GuardSatisfied(index) in the method definition's receiver type or + // formal parameters, retrieve the corresponding type of the actual parameter / receiver at + // the call site (e.g. @GuardedBy("someLock") and replace the return type at the call site + // with this type. - if (returnGuardSatisfiedIndex == -1) { - return mType; - } + AnnotatedExecutableType invokedMethod = mType.executableType; - // Find the receiver or first parameter whose @GS index matches that of the return type. - // Ensuring that the type annotations on distinct @GS parameters with the same index - // match at the call site is handled in LockVisitor.visitMethodInvocation - - if (!ElementUtils.isStatic(invokedMethod.getElement()) - && replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( - methodDefinitionReturn, - invokedMethod.getReceiverType() /* the method definition receiver*/, - returnGuardSatisfiedIndex, - receiverType.getAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { - return mType; - } + if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) { + return mType; + } - List methodInvocationTreeArguments = - ((MethodInvocationTree) tree).getArguments(); - List paramTypes = invokedMethod.getParameterTypes(); - - for (int i = 0; i < paramTypes.size(); i++) { - if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( - methodDefinitionReturn, - paramTypes.get(i), - returnGuardSatisfiedIndex, - getAnnotatedType(methodInvocationTreeArguments.get(i)) - .getEffectiveAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { - return mType; - } - } + AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType(); - return mType; + if (methodDefinitionReturn == null + || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) { + return mType; } - /** - * If {@code atm} is not null and contains a {@code @GuardSatisfied} annotation, and if the - * index of this {@code @GuardSatisfied} annotation matches {@code matchingGuardSatisfiedIndex}, - * then {@code methodReturnAtm} will have its annotation in the {@code @GuardedBy} hierarchy - * replaced with that in {@code annotationInGuardedByHierarchy}. - * - * @param methodReturnAtm the AnnotatedTypeMirror for the return type of a method that will - * potentially have its annotation in the {@code @GuardedBy} hierarchy replaced. - * @param atm an AnnotatedTypeMirror that may contain a {@code @GuardSatisfied} annotation. May - * be null. - * @param matchingGuardSatisfiedIndex the {code @GuardSatisfied} index that the - * {@code @GuardSatisfied} annotation in {@code atm} must have in order for the replacement - * to occur. - * @param annotationInGuardedByHierarchy if the replacement occurs, the annotation in the - * {@code @GuardedBy} hierarchy in this parameter will be used for the replacement. - * @return true if the replacement occurred, false otherwise - */ - private boolean replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( - AnnotatedTypeMirror methodReturnAtm, - @Nullable AnnotatedTypeMirror atm, - int matchingGuardSatisfiedIndex, - AnnotationMirror annotationInGuardedByHierarchy) { - if (atm == null - || !atm.hasAnnotation(GuardSatisfied.class) - || getGuardSatisfiedIndex(atm) != matchingGuardSatisfiedIndex) { - return false; - } + int returnGuardSatisfiedIndex = getGuardSatisfiedIndex(methodDefinitionReturn); - methodReturnAtm.replaceAnnotation(annotationInGuardedByHierarchy); + // @GuardSatisfied with no index defaults to index -1. Ignore instances of @GuardSatisfied + // with no index. If a method is defined with a return type of @GuardSatisfied with no + // index, an error is reported by LockVisitor.visitMethod. - return true; + if (returnGuardSatisfiedIndex == -1) { + return mType; } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new LockTreeAnnotator(this), super.createTreeAnnotator()); + // Find the receiver or first parameter whose @GS index matches that of the return type. + // Ensuring that the type annotations on distinct @GS parameters with the same index + // match at the call site is handled in LockVisitor.visitMethodInvocation + + if (!ElementUtils.isStatic(invokedMethod.getElement()) + && replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( + methodDefinitionReturn, + invokedMethod.getReceiverType() /* the method definition receiver*/, + returnGuardSatisfiedIndex, + receiverType.getAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { + return mType; } - @Override - public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - translateJcipAndJavaxAnnotations(elt, type); + List methodInvocationTreeArguments = + ((MethodInvocationTree) tree).getArguments(); + List paramTypes = invokedMethod.getParameterTypes(); + + for (int i = 0; i < paramTypes.size(); i++) { + if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( + methodDefinitionReturn, + paramTypes.get(i), + returnGuardSatisfiedIndex, + getAnnotatedType(methodInvocationTreeArguments.get(i)) + .getEffectiveAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { + return mType; + } + } - super.addComputedTypeAnnotations(elt, type); + return mType; + } + + /** + * If {@code atm} is not null and contains a {@code @GuardSatisfied} annotation, and if the index + * of this {@code @GuardSatisfied} annotation matches {@code matchingGuardSatisfiedIndex}, then + * {@code methodReturnAtm} will have its annotation in the {@code @GuardedBy} hierarchy replaced + * with that in {@code annotationInGuardedByHierarchy}. + * + * @param methodReturnAtm the AnnotatedTypeMirror for the return type of a method that will + * potentially have its annotation in the {@code @GuardedBy} hierarchy replaced. + * @param atm an AnnotatedTypeMirror that may contain a {@code @GuardSatisfied} annotation. May be + * null. + * @param matchingGuardSatisfiedIndex the {code @GuardSatisfied} index that the + * {@code @GuardSatisfied} annotation in {@code atm} must have in order for the replacement to + * occur. + * @param annotationInGuardedByHierarchy if the replacement occurs, the annotation in the + * {@code @GuardedBy} hierarchy in this parameter will be used for the replacement. + * @return true if the replacement occurred, false otherwise + */ + private boolean replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( + AnnotatedTypeMirror methodReturnAtm, + @Nullable AnnotatedTypeMirror atm, + int matchingGuardSatisfiedIndex, + AnnotationMirror annotationInGuardedByHierarchy) { + if (atm == null + || !atm.hasAnnotation(GuardSatisfied.class) + || getGuardSatisfiedIndex(atm) != matchingGuardSatisfiedIndex) { + return false; } - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - if (tree.getKind() == Tree.Kind.VARIABLE) { - translateJcipAndJavaxAnnotations( - TreeUtils.elementFromDeclaration((VariableTree) tree), type); - } + methodReturnAtm.replaceAnnotation(annotationInGuardedByHierarchy); - super.addComputedTypeAnnotations(tree, type); - } + return true; + } - /** - * Given a field declaration with a {@code @net.jcip.annotations.GuardedBy} or {@code - * javax.annotation.concurrent.GuardedBy} annotation and an AnnotatedTypeMirror for that field, - * inserts the corresponding {@code @org.checkerframework.checker.lock.qual.GuardedBy} type - * qualifier into that AnnotatedTypeMirror. - * - * @param element any Element (this method does nothing if the Element is not for a field - * declaration) - * @param atm the AnnotatedTypeMirror for element - the {@code @GuardedBy} type qualifier will - * be inserted here - */ - private void translateJcipAndJavaxAnnotations(Element element, AnnotatedTypeMirror atm) { - if (!element.getKind().isField()) { - return; - } + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new LockTreeAnnotator(this), super.createTreeAnnotator()); + } - AnnotationMirror anno = null; + @Override + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + translateJcipAndJavaxAnnotations(elt, type); - if (jcipGuardedBy != null) { - anno = getDeclAnnotation(element, jcipGuardedBy); - } + super.addComputedTypeAnnotations(elt, type); + } - if (anno == null && javaxGuardedBy != null) { - anno = getDeclAnnotation(element, javaxGuardedBy); - } + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + if (tree.getKind() == Tree.Kind.VARIABLE) { + translateJcipAndJavaxAnnotations(TreeUtils.elementFromDeclaration((VariableTree) tree), type); + } - if (anno == null) { - return; - } + super.addComputedTypeAnnotations(tree, type); + } + + /** + * Given a field declaration with a {@code @net.jcip.annotations.GuardedBy} or {@code + * javax.annotation.concurrent.GuardedBy} annotation and an AnnotatedTypeMirror for that field, + * inserts the corresponding {@code @org.checkerframework.checker.lock.qual.GuardedBy} type + * qualifier into that AnnotatedTypeMirror. + * + * @param element any Element (this method does nothing if the Element is not for a field + * declaration) + * @param atm the AnnotatedTypeMirror for element - the {@code @GuardedBy} type qualifier will be + * inserted here + */ + private void translateJcipAndJavaxAnnotations(Element element, AnnotatedTypeMirror atm) { + if (!element.getKind().isField()) { + return; + } - // The version of javax.annotation.concurrent.GuardedBy included with the Checker Framework - // declares the type of value as an array of Strings, whereas the one defined in JCIP and - // included with FindBugs declares it as a String. So, the code below figures out which type - // should be used. - Map valmap = - anno.getElementValues(); - Object value = null; - for (ExecutableElement elem : valmap.keySet()) { - if (elem.getSimpleName().contentEquals("value")) { - value = valmap.get(elem).getValue(); - break; - } - } - List lockExpressions; - if (value instanceof List) { - @SuppressWarnings("unchecked") - List la = (List) value; - lockExpressions = - CollectionsPlume.mapList((AnnotationValue a) -> (String) a.getValue(), la); - } else if (value instanceof String) { - lockExpressions = Collections.singletonList((String) value); - } else { - return; - } + AnnotationMirror anno = null; - if (lockExpressions.isEmpty()) { - atm.addAnnotation(GUARDEDBY); - } else { - atm.addAnnotation(createGuardedByAnnotationMirror(lockExpressions)); - } + if (jcipGuardedBy != null) { + anno = getDeclAnnotation(element, jcipGuardedBy); } - /** - * Returns an AnnotationMirror corresponding to @GuardedBy(values). - * - * @param values a list of lock expressions - * @return an AnnotationMirror corresponding to @GuardedBy(values) - */ - private AnnotationMirror createGuardedByAnnotationMirror(List values) { - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), GuardedBy.class); - builder.setValue("value", values.toArray()); + if (anno == null && javaxGuardedBy != null) { + anno = getDeclAnnotation(element, javaxGuardedBy); + } - // Return the resulting AnnotationMirror - return builder.build(); + if (anno == null) { + return; } - @Override - public boolean isSideEffectFree(ExecutableElement method) { - SideEffectAnnotation seAnno = methodSideEffectAnnotation(method, false); - return seAnno == SideEffectAnnotation.RELEASESNOLOCKS - || seAnno == SideEffectAnnotation.LOCKINGFREE - || super.isSideEffectFree(method); + // The version of javax.annotation.concurrent.GuardedBy included with the Checker Framework + // declares the type of value as an array of Strings, whereas the one defined in JCIP and + // included with FindBugs declares it as a String. So, the code below figures out which type + // should be used. + Map valmap = anno.getElementValues(); + Object value = null; + for (ExecutableElement elem : valmap.keySet()) { + if (elem.getSimpleName().contentEquals("value")) { + value = valmap.get(elem).getValue(); + break; + } + } + List lockExpressions; + if (value instanceof List) { + @SuppressWarnings("unchecked") + List la = (List) value; + lockExpressions = CollectionsPlume.mapList((AnnotationValue a) -> (String) a.getValue(), la); + } else if (value instanceof String) { + lockExpressions = Collections.singletonList((String) value); + } else { + return; + } + + if (lockExpressions.isEmpty()) { + atm.addAnnotation(GUARDEDBY); + } else { + atm.addAnnotation(createGuardedByAnnotationMirror(lockExpressions)); } + } + + /** + * Returns an AnnotationMirror corresponding to @GuardedBy(values). + * + * @param values a list of lock expressions + * @return an AnnotationMirror corresponding to @GuardedBy(values) + */ + private AnnotationMirror createGuardedByAnnotationMirror(List values) { + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), GuardedBy.class); + builder.setValue("value", values.toArray()); + + // Return the resulting AnnotationMirror + return builder.build(); + } + + @Override + public boolean isSideEffectFree(ExecutableElement method) { + SideEffectAnnotation seAnno = methodSideEffectAnnotation(method, false); + return seAnno == SideEffectAnnotation.RELEASESNOLOCKS + || seAnno == SideEffectAnnotation.LOCKINGFREE + || super.isSideEffectFree(method); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java index cbf58ce5c44..fe902901a75 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java @@ -1,5 +1,8 @@ package org.checkerframework.checker.lock; +import java.util.ArrayList; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.lock.qual.LockHeld; import org.checkerframework.checker.lock.qual.LockPossiblyHeld; import org.checkerframework.checker.nullness.qual.Nullable; @@ -19,11 +22,6 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; -import java.util.ArrayList; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; - /** * The Lock Store behaves like CFAbstractStore but requires the ability to insert exact annotations. * This is because we want to be able to insert @LockPossiblyHeld to replace @LockHeld, which @@ -31,242 +29,242 @@ */ public class LockStore extends CFAbstractStore { - /** - * If true, indicates that the store refers to a point in the code inside a constructor or - * initializer. This is useful because constructors and initializers are special with regard to - * the set of locks that is considered to be held. For example, 'this' is considered to be held - * inside a constructor. - */ - protected boolean inConstructorOrInitializer = false; + /** + * If true, indicates that the store refers to a point in the code inside a constructor or + * initializer. This is useful because constructors and initializers are special with regard to + * the set of locks that is considered to be held. For example, 'this' is considered to be held + * inside a constructor. + */ + protected boolean inConstructorOrInitializer = false; - /** The type factory to use. */ - private final LockAnnotatedTypeFactory atypeFactory; + /** The type factory to use. */ + private final LockAnnotatedTypeFactory atypeFactory; - /** - * Create a LockStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume - * that only one thread is running at all times)? - */ - public LockStore(LockAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); - } + /** + * Create a LockStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume that + * only one thread is running at all times)? + */ + public LockStore(LockAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); + } - /** Copy constructor. */ - public LockStore(LockAnalysis analysis, CFAbstractStore other) { - super(other); - this.inConstructorOrInitializer = ((LockStore) other).inConstructorOrInitializer; - this.atypeFactory = ((LockStore) other).atypeFactory; - } + /** Copy constructor. */ + public LockStore(LockAnalysis analysis, CFAbstractStore other) { + super(other); + this.inConstructorOrInitializer = ((LockStore) other).inConstructorOrInitializer; + this.atypeFactory = ((LockStore) other).atypeFactory; + } - @Override - public LockStore leastUpperBound(LockStore other) { - LockStore newStore = super.leastUpperBound(other); + @Override + public LockStore leastUpperBound(LockStore other) { + LockStore newStore = super.leastUpperBound(other); - // Least upper bound of a boolean - newStore.inConstructorOrInitializer = - this.inConstructorOrInitializer && other.inConstructorOrInitializer; + // Least upper bound of a boolean + newStore.inConstructorOrInitializer = + this.inConstructorOrInitializer && other.inConstructorOrInitializer; - return newStore; - } + return newStore; + } - /* - * Insert an annotation exactly, without regard to whether an annotation was already present. - * This is only done for @LockPossiblyHeld. This is not sound for other type qualifiers. - */ - public void insertLockPossiblyHeld(JavaExpression je) { - if (je.containsUnknown()) { - // Expressions containing unknown expressions are not stored. - return; - } - if (je instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) je; - CFValue current = localVariableValues.get(localVar); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - localVariableValues.put(localVar, value); - } - } else if (je instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) je; - CFValue current = fieldValues.get(fieldAcc); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - fieldValues.put(fieldAcc, value); - } - } else if (je instanceof MethodCall) { - MethodCall method = (MethodCall) je; - CFValue current = methodValues.get(method); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - methodValues.put(method, value); - } - } else if (je instanceof ArrayAccess) { - ArrayAccess arrayAccess = (ArrayAccess) je; - CFValue current = arrayValues.get(arrayAccess); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - arrayValues.put(arrayAccess, value); - } - } else if (je instanceof ThisReference) { - thisValue = changeLockAnnoToTop(je, thisValue); - } else if (je instanceof ClassName) { - ClassName className = (ClassName) je; - CFValue current = classValues.get(className); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - classValues.put(className, value); - } - } else { - // No other types of expressions need to be stored. - } + /* + * Insert an annotation exactly, without regard to whether an annotation was already present. + * This is only done for @LockPossiblyHeld. This is not sound for other type qualifiers. + */ + public void insertLockPossiblyHeld(JavaExpression je) { + if (je.containsUnknown()) { + // Expressions containing unknown expressions are not stored. + return; } + if (je instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) je; + CFValue current = localVariableValues.get(localVar); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + localVariableValues.put(localVar, value); + } + } else if (je instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) je; + CFValue current = fieldValues.get(fieldAcc); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + fieldValues.put(fieldAcc, value); + } + } else if (je instanceof MethodCall) { + MethodCall method = (MethodCall) je; + CFValue current = methodValues.get(method); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + methodValues.put(method, value); + } + } else if (je instanceof ArrayAccess) { + ArrayAccess arrayAccess = (ArrayAccess) je; + CFValue current = arrayValues.get(arrayAccess); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + arrayValues.put(arrayAccess, value); + } + } else if (je instanceof ThisReference) { + thisValue = changeLockAnnoToTop(je, thisValue); + } else if (je instanceof ClassName) { + ClassName className = (ClassName) je; + CFValue current = classValues.get(className); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + classValues.put(className, value); + } + } else { + // No other types of expressions need to be stored. + } + } - /** - * Makes a new CFValue with the same annotations as currentValue except that the annotation in - * the LockPossiblyHeld hierarchy is set to LockPossiblyHeld. If currentValue is null, then a - * new value is created where the annotation set is LockPossiblyHeld and GuardedByUnknown - */ - private CFValue changeLockAnnoToTop(JavaExpression je, @Nullable CFValue currentValue) { - if (currentValue == null) { - AnnotationMirrorSet set = new AnnotationMirrorSet(); - set.add(atypeFactory.GUARDEDBYUNKNOWN); - set.add(atypeFactory.LOCKPOSSIBLYHELD); - return analysis.createAbstractValue(set, je.getType()); - } - - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - AnnotationMirrorSet currentSet = currentValue.getAnnotations(); - AnnotationMirror gb = - qualHierarchy.findAnnotationInHierarchy(currentSet, atypeFactory.GUARDEDBYUNKNOWN); - AnnotationMirrorSet newSet = new AnnotationMirrorSet(); - newSet.add(atypeFactory.LOCKPOSSIBLYHELD); - if (gb != null) { - newSet.add(gb); - } - return analysis.createAbstractValue(newSet, currentValue.getUnderlyingType()); + /** + * Makes a new CFValue with the same annotations as currentValue except that the annotation in the + * LockPossiblyHeld hierarchy is set to LockPossiblyHeld. If currentValue is null, then a new + * value is created where the annotation set is LockPossiblyHeld and GuardedByUnknown + */ + private CFValue changeLockAnnoToTop(JavaExpression je, @Nullable CFValue currentValue) { + if (currentValue == null) { + AnnotationMirrorSet set = new AnnotationMirrorSet(); + set.add(atypeFactory.GUARDEDBYUNKNOWN); + set.add(atypeFactory.LOCKPOSSIBLYHELD); + return analysis.createAbstractValue(set, je.getType()); } - public void setInConstructorOrInitializer() { - inConstructorOrInitializer = true; + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + AnnotationMirrorSet currentSet = currentValue.getAnnotations(); + AnnotationMirror gb = + qualHierarchy.findAnnotationInHierarchy(currentSet, atypeFactory.GUARDEDBYUNKNOWN); + AnnotationMirrorSet newSet = new AnnotationMirrorSet(); + newSet.add(atypeFactory.LOCKPOSSIBLYHELD); + if (gb != null) { + newSet.add(gb); } + return analysis.createAbstractValue(newSet, currentValue.getUnderlyingType()); + } - @Override - public @Nullable CFValue getValue(JavaExpression expr) { + public void setInConstructorOrInitializer() { + inConstructorOrInitializer = true; + } - if (inConstructorOrInitializer) { - // 'this' is automatically considered as being held in a constructor or initializer. - // The class name, however, is not. - if (expr instanceof ThisReference) { - initializeThisValue(atypeFactory.LOCKHELD, expr.getType()); - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - if (!fieldAcc.isStatic() && fieldAcc.getReceiver() instanceof ThisReference) { - insertValue(fieldAcc.getReceiver(), atypeFactory.LOCKHELD); - } - } - } + @Override + public @Nullable CFValue getValue(JavaExpression expr) { - return super.getValue(expr); + if (inConstructorOrInitializer) { + // 'this' is automatically considered as being held in a constructor or initializer. + // The class name, however, is not. + if (expr instanceof ThisReference) { + initializeThisValue(atypeFactory.LOCKHELD, expr.getType()); + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + if (!fieldAcc.isStatic() && fieldAcc.getReceiver() instanceof ThisReference) { + insertValue(fieldAcc.getReceiver(), atypeFactory.LOCKHELD); + } + } } - @Override - protected String internalVisualize(CFGVisualizer viz) { - return viz.visualizeStoreKeyVal("inConstructorOrInitializer", inConstructorOrInitializer) - + viz.getSeparator() - + super.internalVisualize(viz); - } + return super.getValue(expr); + } - @Override - public void updateForMethodCall( - MethodInvocationNode n, - GenericAnnotatedTypeFactory atypeFactory, - CFValue val) { - super.updateForMethodCall(n, atypeFactory, val); - ExecutableElement method = n.getTarget().getMethod(); - // The following behavior is similar to setting the sideEffectsUnrefineAliases field of - // Lockannotatedtypefactory, but it affects only the LockPosssiblyHeld type hierarchy (not - // the @GuardedBy hierarchy), so it cannot use that logic. - if (!atypeFactory.isSideEffectFree(method)) { - // After the call to super.updateForMethodCall, only final fields are left in - // fieldValues (if the method called is side-effecting). For the LockPossiblyHeld - // hierarchy, even a final field might be locked or unlocked by a side-effecting method. - // So, final fields must be set to @LockPossiblyHeld, but the annotation in the - // GuardedBy hierarchy should not be changed. - for (FieldAccess field : new ArrayList<>(fieldValues.keySet())) { - CFValue newValue = changeLockAnnoToTop(field, fieldValues.get(field)); - if (newValue != null) { - fieldValues.put(field, newValue); - } else { - fieldValues.remove(field); - } - } + @Override + protected String internalVisualize(CFGVisualizer viz) { + return viz.visualizeStoreKeyVal("inConstructorOrInitializer", inConstructorOrInitializer) + + viz.getSeparator() + + super.internalVisualize(viz); + } - // Local variables could also be unlocked via an alias - for (LocalVariable var : new ArrayList<>(localVariableValues.keySet())) { - CFValue newValue = changeLockAnnoToTop(var, localVariableValues.get(var)); - if (newValue != null) { - localVariableValues.put(var, newValue); - } - } + @Override + public void updateForMethodCall( + MethodInvocationNode n, + GenericAnnotatedTypeFactory atypeFactory, + CFValue val) { + super.updateForMethodCall(n, atypeFactory, val); + ExecutableElement method = n.getTarget().getMethod(); + // The following behavior is similar to setting the sideEffectsUnrefineAliases field of + // Lockannotatedtypefactory, but it affects only the LockPosssiblyHeld type hierarchy (not + // the @GuardedBy hierarchy), so it cannot use that logic. + if (!atypeFactory.isSideEffectFree(method)) { + // After the call to super.updateForMethodCall, only final fields are left in + // fieldValues (if the method called is side-effecting). For the LockPossiblyHeld + // hierarchy, even a final field might be locked or unlocked by a side-effecting method. + // So, final fields must be set to @LockPossiblyHeld, but the annotation in the + // GuardedBy hierarchy should not be changed. + for (FieldAccess field : new ArrayList<>(fieldValues.keySet())) { + CFValue newValue = changeLockAnnoToTop(field, fieldValues.get(field)); + if (newValue != null) { + fieldValues.put(field, newValue); + } else { + fieldValues.remove(field); + } + } - if (thisValue != null) { - thisValue = changeLockAnnoToTop(null, thisValue); - } + // Local variables could also be unlocked via an alias + for (LocalVariable var : new ArrayList<>(localVariableValues.keySet())) { + CFValue newValue = changeLockAnnoToTop(var, localVariableValues.get(var)); + if (newValue != null) { + localVariableValues.put(var, newValue); } - } + } - /** - * Whether the specified value has the {@link LockHeld} annotation. - * - * @param value the value to check. - * @return whether the {@code value} has the {@link LockHeld} annotation - */ - boolean hasLockHeld(CFValue value) { - return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKHELD); + if (thisValue != null) { + thisValue = changeLockAnnoToTop(null, thisValue); + } } + } + + /** + * Whether the specified value has the {@link LockHeld} annotation. + * + * @param value the value to check. + * @return whether the {@code value} has the {@link LockHeld} annotation + */ + boolean hasLockHeld(CFValue value) { + return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKHELD); + } + + /** + * Whether the specified value has the {@link LockPossiblyHeld} annotation. + * + * @param value the value to check. + * @return whether the {@code value} has the {@link LockPossiblyHeld} annotation + */ + boolean hasLockPossiblyHeld(CFValue value) { + return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKPOSSIBLYHELD); + } - /** - * Whether the specified value has the {@link LockPossiblyHeld} annotation. - * - * @param value the value to check. - * @return whether the {@code value} has the {@link LockPossiblyHeld} annotation - */ - boolean hasLockPossiblyHeld(CFValue value) { - return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKPOSSIBLYHELD); + @Override + public void insertValue( + JavaExpression je, @Nullable CFValue value, boolean permitNondeterministic) { + if (!shouldInsert(je, value, permitNondeterministic)) { + return; } - @Override - public void insertValue( - JavaExpression je, @Nullable CFValue value, boolean permitNondeterministic) { - if (!shouldInsert(je, value, permitNondeterministic)) { - return; + // Even with concurrent semantics enabled, a @LockHeld value must always be + // stored for fields and @Pure method calls. This is sound because: + // * Another thread can never release the lock on the current thread, and + // * Locks are assumed to be effectively final, hence another thread will not + // side effect the lock expression that has value @LockHeld. + if (hasLockHeld(value)) { + if (je instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) je; + CFValue oldValue = fieldValues.get(fieldAcc); + CFValue newValue = value.mostSpecific(oldValue, null); + if (newValue != null) { + fieldValues.put(fieldAcc, newValue); } - - // Even with concurrent semantics enabled, a @LockHeld value must always be - // stored for fields and @Pure method calls. This is sound because: - // * Another thread can never release the lock on the current thread, and - // * Locks are assumed to be effectively final, hence another thread will not - // side effect the lock expression that has value @LockHeld. - if (hasLockHeld(value)) { - if (je instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) je; - CFValue oldValue = fieldValues.get(fieldAcc); - CFValue newValue = value.mostSpecific(oldValue, null); - if (newValue != null) { - fieldValues.put(fieldAcc, newValue); - } - } else if (je instanceof MethodCall) { - MethodCall method = (MethodCall) je; - CFValue oldValue = methodValues.get(method); - CFValue newValue = value.mostSpecific(oldValue, null); - if (newValue != null) { - methodValues.put(method, newValue); - } - } + } else if (je instanceof MethodCall) { + MethodCall method = (MethodCall) je; + CFValue oldValue = methodValues.get(method); + CFValue newValue = value.mostSpecific(oldValue, null); + if (newValue != null) { + methodValues.put(method, newValue); } - - super.insertValue(je, value, permitNondeterministic); + } } + + super.insertValue(je, value, permitNondeterministic); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java b/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java index 740c0ba594c..2745c36871b 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java @@ -2,7 +2,11 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; - +import java.util.List; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.UnderlyingAST; @@ -17,142 +21,134 @@ import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.TreeUtils; -import java.util.List; - -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.type.TypeMirror; - /** * LockTransfer handles constructors, initializers, synchronized methods, and synchronized blocks. */ public class LockTransfer extends CFAbstractTransfer { - /** The type factory associated with this transfer function. */ - private final LockAnnotatedTypeFactory atypeFactory; - - /** - * Create a transfer function for the Lock Checker. - * - * @param analysis the analysis this transfer function belongs to - * @param checker the type-checker this transfer function belongs to - */ - public LockTransfer(LockAnalysis analysis, LockChecker checker) { - // Always run the Lock Checker with -AconcurrentSemantics turned on. - super(analysis, /* useConcurrentSemantics= */ true); - this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); + /** The type factory associated with this transfer function. */ + private final LockAnnotatedTypeFactory atypeFactory; + + /** + * Create a transfer function for the Lock Checker. + * + * @param analysis the analysis this transfer function belongs to + * @param checker the type-checker this transfer function belongs to + */ + public LockTransfer(LockAnalysis analysis, LockChecker checker) { + // Always run the Lock Checker with -AconcurrentSemantics turned on. + super(analysis, /* useConcurrentSemantics= */ true); + this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); + } + + /** + * Sets a given {@link Node} to @LockHeld in the given {@code store}. + * + * @param store the store to update + * @param node the node that should be @LockHeld + */ + protected void makeLockHeld(LockStore store, Node node) { + JavaExpression internalRepr = JavaExpression.fromNode(node); + store.insertValue(internalRepr, atypeFactory.LOCKHELD); + } + + /** + * Sets a given {@link Node} to @LockPossiblyHeld in the given {@code store}. + * + * @param store the store to update + * @param node the node that should be @LockPossiblyHeld + */ + protected void makeLockPossiblyHeld(LockStore store, Node node) { + JavaExpression internalRepr = JavaExpression.fromNode(node); + + // insertValue cannot change an annotation to a less specific type (e.g. LockHeld to + // LockPossiblyHeld), so insertLockPossiblyHeld is called. + store.insertLockPossiblyHeld(internalRepr); + } + + /** Sets a given {@link Node} {@code node} to LockHeld in the given {@link TransferResult}. */ + protected void makeLockHeld(TransferResult result, Node node) { + if (result.containsTwoStores()) { + makeLockHeld(result.getThenStore(), node); + makeLockHeld(result.getElseStore(), node); + } else { + makeLockHeld(result.getRegularStore(), node); } - - /** - * Sets a given {@link Node} to @LockHeld in the given {@code store}. - * - * @param store the store to update - * @param node the node that should be @LockHeld - */ - protected void makeLockHeld(LockStore store, Node node) { - JavaExpression internalRepr = JavaExpression.fromNode(node); - store.insertValue(internalRepr, atypeFactory.LOCKHELD); + } + + /** + * Sets a given {@link Node} {@code node} to LockPossiblyHeld in the given {@link TransferResult}. + */ + protected void makeLockPossiblyHeld(TransferResult result, Node node) { + if (result.containsTwoStores()) { + makeLockPossiblyHeld(result.getThenStore(), node); + makeLockPossiblyHeld(result.getElseStore(), node); + } else { + makeLockPossiblyHeld(result.getRegularStore(), node); } + } - /** - * Sets a given {@link Node} to @LockPossiblyHeld in the given {@code store}. - * - * @param store the store to update - * @param node the node that should be @LockPossiblyHeld - */ - protected void makeLockPossiblyHeld(LockStore store, Node node) { - JavaExpression internalRepr = JavaExpression.fromNode(node); - - // insertValue cannot change an annotation to a less specific type (e.g. LockHeld to - // LockPossiblyHeld), so insertLockPossiblyHeld is called. - store.insertLockPossiblyHeld(internalRepr); - } + @Override + public LockStore initialStore(UnderlyingAST underlyingAST, List parameters) { - /** Sets a given {@link Node} {@code node} to LockHeld in the given {@link TransferResult}. */ - protected void makeLockHeld(TransferResult result, Node node) { - if (result.containsTwoStores()) { - makeLockHeld(result.getThenStore(), node); - makeLockHeld(result.getElseStore(), node); - } else { - makeLockHeld(result.getRegularStore(), node); - } - } - - /** - * Sets a given {@link Node} {@code node} to LockPossiblyHeld in the given {@link - * TransferResult}. - */ - protected void makeLockPossiblyHeld(TransferResult result, Node node) { - if (result.containsTwoStores()) { - makeLockPossiblyHeld(result.getThenStore(), node); - makeLockPossiblyHeld(result.getElseStore(), node); - } else { - makeLockPossiblyHeld(result.getRegularStore(), node); - } - } + LockStore store = super.initialStore(underlyingAST, parameters); - @Override - public LockStore initialStore(UnderlyingAST underlyingAST, List parameters) { + Kind astKind = underlyingAST.getKind(); - LockStore store = super.initialStore(underlyingAST, parameters); + // Methods with the 'synchronized' modifier are holding the 'this' lock. - Kind astKind = underlyingAST.getKind(); + // There is a subtle difference between synchronized methods and constructors/initializers. + // A synchronized method is only taking the intrinsic lock of the current object. It says + // nothing about any fields of the current object. - // Methods with the 'synchronized' modifier are holding the 'this' lock. + // Furthermore, since the current object already exists, other objects may be guarded by the + // current object. So a synchronized method can affect the locking behavior of other + // objects. - // There is a subtle difference between synchronized methods and constructors/initializers. - // A synchronized method is only taking the intrinsic lock of the current object. It says - // nothing about any fields of the current object. + // A constructor/initializer behaves as if the current object and all its non-static fields + // were held as locks. But in reality no locks are held. - // Furthermore, since the current object already exists, other objects may be guarded by the - // current object. So a synchronized method can affect the locking behavior of other - // objects. + // Furthermore, since the current object is being constructed, no other object can be + // guarded by it or any of its non-static fields. - // A constructor/initializer behaves as if the current object and all its non-static fields - // were held as locks. But in reality no locks are held. + // Handle synchronized methods and constructors. + if (astKind == UnderlyingAST.Kind.METHOD) { + CFGMethod method = (CFGMethod) underlyingAST; + MethodTree methodTree = method.getMethod(); - // Furthermore, since the current object is being constructed, no other object can be - // guarded by it or any of its non-static fields. + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); - // Handle synchronized methods and constructors. - if (astKind == UnderlyingAST.Kind.METHOD) { - CFGMethod method = (CFGMethod) underlyingAST; - MethodTree methodTree = method.getMethod(); + if (methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { + ClassTree classTree = method.getClassTree(); + TypeMirror classType = TreeUtils.typeOf(classTree); - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); - - if (methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { - ClassTree classTree = method.getClassTree(); - TypeMirror classType = TreeUtils.typeOf(classTree); - - if (methodElement.getModifiers().contains(Modifier.STATIC)) { - store.insertValue(new ClassName(classType), atypeFactory.LOCKHELD); - } else { - store.insertThisValue(atypeFactory.LOCKHELD, classType); - } - } else if (methodElement.getKind() == ElementKind.CONSTRUCTOR) { - store.setInConstructorOrInitializer(); - } - } else if (astKind == Kind.ARBITRARY_CODE) { // Handle initializers - store.setInConstructorOrInitializer(); + if (methodElement.getModifiers().contains(Modifier.STATIC)) { + store.insertValue(new ClassName(classType), atypeFactory.LOCKHELD); + } else { + store.insertThisValue(atypeFactory.LOCKHELD, classType); } - - return store; + } else if (methodElement.getKind() == ElementKind.CONSTRUCTOR) { + store.setInConstructorOrInitializer(); + } + } else if (astKind == Kind.ARBITRARY_CODE) { // Handle initializers + store.setInConstructorOrInitializer(); } - @Override - public TransferResult visitSynchronized( - SynchronizedNode n, TransferInput p) { + return store; + } - TransferResult result = super.visitSynchronized(n, p); + @Override + public TransferResult visitSynchronized( + SynchronizedNode n, TransferInput p) { - // Handle the entering and leaving of the synchronized block - if (n.getIsStartOfBlock()) { - makeLockHeld(result, n.getExpression()); - } else { - makeLockPossiblyHeld(result, n.getExpression()); - } + TransferResult result = super.visitSynchronized(n, p); - return result; + // Handle the entering and leaving of the synchronized block + if (n.getIsStartOfBlock()) { + makeLockHeld(result, n.getExpression()); + } else { + makeLockPossiblyHeld(result, n.getExpression()); } + + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java index 03ab5dbc12c..5030b5f20e1 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java @@ -3,7 +3,6 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.NewArrayTree; - import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; @@ -16,45 +15,45 @@ */ public class LockTreeAnnotator extends TreeAnnotator { - public LockTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - // For any binary operation whose LHS or RHS can be a non-boolean type, and whose resulting - // type is necessarily boolean, the resulting annotation on the boolean type must be - // @GuardedBy({}). + public LockTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - // There is no need to enforce that the annotation on the result of &&, ||, etc. is - // @GuardedBy({}) since for such operators, both operands are of type @GuardedBy({}) boolean - // to begin with. + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + // For any binary operation whose LHS or RHS can be a non-boolean type, and whose resulting + // type is necessarily boolean, the resulting annotation on the boolean type must be + // @GuardedBy({}). - if (TreeUtils.isBinaryComparison(tree) || TypesUtils.isString(type.getUnderlyingType())) { - // A boolean or String is always @GuardedBy({}). LockVisitor determines whether - // the LHS and RHS of this operation can be legally dereferenced. - type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); + // There is no need to enforce that the annotation on the result of &&, ||, etc. is + // @GuardedBy({}) since for such operators, both operands are of type @GuardedBy({}) boolean + // to begin with. - return null; - } + if (TreeUtils.isBinaryComparison(tree) || TypesUtils.isString(type.getUnderlyingType())) { + // A boolean or String is always @GuardedBy({}). LockVisitor determines whether + // the LHS and RHS of this operation can be legally dereferenced. + type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); - return super.visitBinary(tree, type); + return null; } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TypesUtils.isString(type.getUnderlyingType())) { - type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); - } + return super.visitBinary(tree, type); + } - return super.visitCompoundAssignment(tree, type); + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TypesUtils.isString(type.getUnderlyingType())) { + type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); } - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT)) { - type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT); - } - return super.visitNewArray(tree, type); + return super.visitCompoundAssignment(tree, type); + } + + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT)) { + type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT); } + return super.visitNewArray(tree, type); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java index 6c8f71dcf9c..a046bc81b14 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java @@ -14,7 +14,21 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation; import org.checkerframework.checker.lock.qual.EnsuresLockHeld; @@ -49,23 +63,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * The LockVisitor enforces the special type-checking rules described in the Lock Checker manual * chapter. @@ -73,1284 +70,1219 @@ * @checker_framework.manual #lock-checker Lock Checker */ public class LockVisitor extends BaseTypeVisitor { - /** The class of GuardedBy */ - private static final Class checkerGuardedByClass = GuardedBy.class; - - /** The class of GuardSatisfied */ - private static final Class checkerGuardSatisfiedClass = - GuardSatisfied.class; - - /** A pattern for spotting self receiver */ - protected static final Pattern SELF_RECEIVER_PATTERN = Pattern.compile("^(\\.(.*))?$"); - - /** - * Constructs a {@link LockVisitor}. - * - * @param checker the type checker to use - */ - public LockVisitor(BaseTypeChecker checker) { - super(checker); + /** The class of GuardedBy */ + private static final Class checkerGuardedByClass = GuardedBy.class; + + /** The class of GuardSatisfied */ + private static final Class checkerGuardSatisfiedClass = + GuardSatisfied.class; + + /** A pattern for spotting self receiver */ + protected static final Pattern SELF_RECEIVER_PATTERN = Pattern.compile("^(\\.(.*))?$"); + + /** + * Constructs a {@link LockVisitor}. + * + * @param checker the type checker to use + */ + public LockVisitor(BaseTypeChecker checker) { + super(checker); + } + + @Override + public Void visitVariable(VariableTree tree, Void p) { // visit a variable declaration + // A user may not annotate a primitive type, a boxed primitive type or a String + // with any qualifier from the @GuardedBy hierarchy. + // They are immutable, so there is no need to guard them. + + TypeMirror tm = TreeUtils.typeOf(tree); + + if (TypesUtils.isBoxedPrimitive(tm) || TypesUtils.isPrimitive(tm) || TypesUtils.isString(tm)) { + AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(tree); + if (atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDSATISFIED) + || atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDEDBY) + || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYUNKNOWN) + || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYBOTTOM)) { + checker.reportError(tree, "immutable.type.guardedby"); + } } - @Override - public Void visitVariable(VariableTree tree, Void p) { // visit a variable declaration - // A user may not annotate a primitive type, a boxed primitive type or a String - // with any qualifier from the @GuardedBy hierarchy. - // They are immutable, so there is no need to guard them. - - TypeMirror tm = TreeUtils.typeOf(tree); - - if (TypesUtils.isBoxedPrimitive(tm) - || TypesUtils.isPrimitive(tm) - || TypesUtils.isString(tm)) { - AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(tree); - if (atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDSATISFIED) - || atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDEDBY) - || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYUNKNOWN) - || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYBOTTOM)) { - checker.reportError(tree, "immutable.type.guardedby"); - } + issueErrorIfMoreThanOneGuardedByAnnotationPresent(tree); + + return super.visitVariable(tree, p); + } + + /** + * Issues an error if two or more of the following annotations are present on a variable + * declaration. + * + *

    + *
  • {@code @org.checkerframework.checker.lock.qual.GuardedBy} + *
  • {@code @net.jcip.annotations.GuardedBy} + *
  • {@code @javax.annotation.concurrent.GuardedBy} + *
+ * + * @param variableTree the VariableTree for the variable declaration used to determine if + * multiple @GuardedBy annotations are present and to report the error + */ + private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) { + int guardedByAnnotationCount = 0; + + List annos = + TreeUtils.annotationsFromTypeAnnotationTrees(variableTree.getModifiers().getAnnotations()); + for (AnnotationMirror anno : annos) { + if (atypeFactory.areSameByClass(anno, GuardedBy.class) + || AnnotationUtils.areSameByName(anno, "net.jcip.annotations.GuardedBy") + || AnnotationUtils.areSameByName(anno, "javax.annotation.concurrent.GuardedBy")) { + guardedByAnnotationCount++; + if (guardedByAnnotationCount > 1) { + checker.reportError(variableTree, "multiple.guardedby.annotations"); + return; } - - issueErrorIfMoreThanOneGuardedByAnnotationPresent(tree); - - return super.visitVariable(tree, p); + } } - - /** - * Issues an error if two or more of the following annotations are present on a variable - * declaration. - * - *
    - *
  • {@code @org.checkerframework.checker.lock.qual.GuardedBy} - *
  • {@code @net.jcip.annotations.GuardedBy} - *
  • {@code @javax.annotation.concurrent.GuardedBy} - *
- * - * @param variableTree the VariableTree for the variable declaration used to determine if - * multiple @GuardedBy annotations are present and to report the error - */ - private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) { - int guardedByAnnotationCount = 0; - - List annos = - TreeUtils.annotationsFromTypeAnnotationTrees( - variableTree.getModifiers().getAnnotations()); - for (AnnotationMirror anno : annos) { - if (atypeFactory.areSameByClass(anno, GuardedBy.class) - || AnnotationUtils.areSameByName(anno, "net.jcip.annotations.GuardedBy") - || AnnotationUtils.areSameByName( - anno, "javax.annotation.concurrent.GuardedBy")) { - guardedByAnnotationCount++; - if (guardedByAnnotationCount > 1) { - checker.reportError(variableTree, "multiple.guardedby.annotations"); - return; - } - } - } - } - - @Override - public LockAnnotatedTypeFactory createTypeFactory() { - return new LockAnnotatedTypeFactory(checker); - } - - /** - * Issues an error if a method (explicitly or implicitly) annotated with @MayReleaseLocks has a - * formal parameter or receiver (explicitly or implicitly) annotated with @GuardSatisfied. Also - * issues an error if a synchronized method has a @LockingFree, @SideEffectFree, or @Pure - * annotation. - * - * @param tree the MethodTree of the method definition to visit - */ - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); - - issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, tree); - - SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true); - - if (sea == SideEffectAnnotation.MAYRELEASELOCKS) { - boolean issueGSwithMRLWarning = false; - - VariableTree receiver = tree.getReceiverParameter(); - if (receiver != null) { - if (atypeFactory - .getAnnotatedType(receiver) - .hasAnnotation(checkerGuardSatisfiedClass)) { - issueGSwithMRLWarning = true; - } - } - - if (!issueGSwithMRLWarning) { // Skip loop if we already decided to issue the warning. - for (VariableTree vt : tree.getParameters()) { - if (atypeFactory - .getAnnotatedType(vt) - .hasAnnotation(checkerGuardSatisfiedClass)) { - issueGSwithMRLWarning = true; - break; - } - } - } - - if (issueGSwithMRLWarning) { - checker.reportError(tree, "guardsatisfied.with.mayreleaselocks"); - } + } + + @Override + public LockAnnotatedTypeFactory createTypeFactory() { + return new LockAnnotatedTypeFactory(checker); + } + + /** + * Issues an error if a method (explicitly or implicitly) annotated with @MayReleaseLocks has a + * formal parameter or receiver (explicitly or implicitly) annotated with @GuardSatisfied. Also + * issues an error if a synchronized method has a @LockingFree, @SideEffectFree, or @Pure + * annotation. + * + * @param tree the MethodTree of the method definition to visit + */ + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); + + issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, tree); + + SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true); + + if (sea == SideEffectAnnotation.MAYRELEASELOCKS) { + boolean issueGSwithMRLWarning = false; + + VariableTree receiver = tree.getReceiverParameter(); + if (receiver != null) { + if (atypeFactory.getAnnotatedType(receiver).hasAnnotation(checkerGuardSatisfiedClass)) { + issueGSwithMRLWarning = true; } - - // Issue an error if a non-constructor method definition has a return type of - // @GuardSatisfied without an index. - if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) { - AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(tree).getReturnType(); - - if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) { - int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM); - - if (returnGuardSatisfiedIndex == -1) { - checker.reportError(tree, "guardsatisfied.return.must.have.index"); - } - } - } - - if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE) - && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { - checker.reportError(tree, "lockingfree.synchronized.method", sea); + } + + if (!issueGSwithMRLWarning) { // Skip loop if we already decided to issue the warning. + for (VariableTree vt : tree.getParameters()) { + if (atypeFactory.getAnnotatedType(vt).hasAnnotation(checkerGuardSatisfiedClass)) { + issueGSwithMRLWarning = true; + break; + } } + } - return super.visitMethod(tree, p); + if (issueGSwithMRLWarning) { + checker.reportError(tree, "guardsatisfied.with.mayreleaselocks"); + } } - /** - * Issues an error if two or more of the following annotations are present on a method. - * - *
    - *
  • {@code @Holding} - *
  • {@code @net.jcip.annotations.GuardedBy} - *
  • {@code @javax.annotation.concurrent.GuardedBy} - *
- * - * @param methodElement the ExecutableElement for the method call referred to by {@code tree} - * @param treeForErrorReporting the MethodTree used to report the error - */ - private void issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent( - ExecutableElement methodElement, MethodTree treeForErrorReporting) { - int lockPreconditionAnnotationCount = 0; - - if (atypeFactory.getDeclAnnotation(methodElement, Holding.class) != null) { - lockPreconditionAnnotationCount++; - } - - try { - if (atypeFactory.jcipGuardedBy != null - && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.jcipGuardedBy) - != null) { - lockPreconditionAnnotationCount++; - } + // Issue an error if a non-constructor method definition has a return type of + // @GuardSatisfied without an index. + if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) { + AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(tree).getReturnType(); - if (lockPreconditionAnnotationCount < 2 - && atypeFactory.javaxGuardedBy != null - && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.javaxGuardedBy) - != null) { - lockPreconditionAnnotationCount++; - } - } catch (Exception e) { - // Ignore exceptions from Class.forName - } + if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) { + int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM); - if (lockPreconditionAnnotationCount > 1) { - checker.reportError(treeForErrorReporting, "multiple.lock.precondition.annotations"); + if (returnGuardSatisfiedIndex == -1) { + checker.reportError(tree, "guardsatisfied.return.must.have.index"); } + } } - /** - * When visiting a method call, if the receiver formal parameter has type @GuardSatisfied and - * the receiver actual parameter has type @GuardedBy(...), this method verifies that the guard - * is satisfied, and it returns true, indicating that the receiver subtype check should be - * skipped. If the receiver actual parameter has type @GuardSatisfied, this method simply - * returns true without performing any other actions. The method returns false otherwise. - * - * @param methodInvocationTree the MethodInvocationTree of the method being called - * @param methodDefinitionReceiver the ATM of the formal receiver parameter of the method being - * called - * @param methodCallReceiver the ATM of the receiver argument of the method call - * @return whether the caller can skip the receiver subtype check - */ - @Override - protected boolean skipReceiverSubtypeCheck( - MethodInvocationTree methodInvocationTree, - AnnotatedTypeMirror methodDefinitionReceiver, - AnnotatedTypeMirror methodCallReceiver) { - - AnnotationMirror primaryGb = - methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - AnnotationMirror effectiveGb = - methodCallReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - - // If the receiver actual parameter has type @GuardSatisfied, skip the subtype check. - // Consider only a @GuardSatisfied primary annotation - hence use primaryGb instead of - // effectiveGb. - if (primaryGb != null - && atypeFactory.areSameByClass(primaryGb, checkerGuardSatisfiedClass)) { - AnnotationMirror primaryGbOnMethodDefinition = - methodDefinitionReceiver.getAnnotationInHierarchy( - atypeFactory.GUARDEDBYUNKNOWN); - if (primaryGbOnMethodDefinition != null - && atypeFactory.areSameByClass( - primaryGbOnMethodDefinition, checkerGuardSatisfiedClass)) { - return true; - } - } - - if (atypeFactory.areSameByClass(effectiveGb, checkerGuardedByClass)) { - AnnotationMirrorSet annos = methodDefinitionReceiver.getAnnotations(); - AnnotationMirror guardSatisfied = - atypeFactory.getAnnotationByClass(annos, checkerGuardSatisfiedClass); - if (guardSatisfied != null) { - ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); - if (receiverTree == null) { - checkLockOfImplicitThis(methodInvocationTree, effectiveGb); - } else { - checkLock(receiverTree, effectiveGb); - } - return true; - } - } - - return false; + if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE) + && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { + checker.reportError(tree, "lockingfree.synchronized.method", sea); } - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); - for (AnnotationMirror anno : tops) { - if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN)) { - annotationSet.add(atypeFactory.GUARDEDBY); - } else { - annotationSet.add(anno); - } - } - return annotationSet; + return super.visitMethod(tree, p); + } + + /** + * Issues an error if two or more of the following annotations are present on a method. + * + *
    + *
  • {@code @Holding} + *
  • {@code @net.jcip.annotations.GuardedBy} + *
  • {@code @javax.annotation.concurrent.GuardedBy} + *
+ * + * @param methodElement the ExecutableElement for the method call referred to by {@code tree} + * @param treeForErrorReporting the MethodTree used to report the error + */ + private void issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent( + ExecutableElement methodElement, MethodTree treeForErrorReporting) { + int lockPreconditionAnnotationCount = 0; + + if (atypeFactory.getDeclAnnotation(methodElement, Holding.class) != null) { + lockPreconditionAnnotationCount++; } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Newly created objects are guarded by nothing, so allow @GuardedBy({}) on constructor - // results. - AnnotationMirror anno = - constructorType - .getReturnType() - .getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN) - || AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYBOTTOM)) { - checker.reportWarning(constructorElement, "inconsistent.constructor.type", anno, null); - } + try { + if (atypeFactory.jcipGuardedBy != null + && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.jcipGuardedBy) != null) { + lockPreconditionAnnotationCount++; + } + + if (lockPreconditionAnnotationCount < 2 + && atypeFactory.javaxGuardedBy != null + && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.javaxGuardedBy) != null) { + lockPreconditionAnnotationCount++; + } + } catch (Exception e) { + // Ignore exceptions from Class.forName } - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - // In cases where assigning a value with a @GuardedBy annotation to a variable with a - // @GuardSatisfied annotation is legal, this is our last chance to check that the - // appropriate locks are held before the information in the @GuardedBy annotation is lost in - // the assignment to the variable annotated with @GuardSatisfied. See the discussion of - // @GuardSatisfied in the "Type-checking rules" section of the Lock Checker manual chapter - // for more details. - - boolean result = true; - if (varType.hasAnnotation(GuardSatisfied.class)) { - if (valueType.hasAnnotation(GuardedBy.class)) { - return checkLock(valueTree, valueType.getAnnotation(GuardedBy.class)); - } else if (valueType.hasAnnotation(GuardSatisfied.class)) { - // TODO: Find a cleaner, non-abstraction-breaking way to know whether method actual - // parameters are being assigned to formal parameters. - - if (!errorKey.equals("argument.type.incompatible")) { - // If both @GuardSatisfied have no index, the assignment is not allowed because - // the LHS and RHS expressions may be guarded by different lock expressions. - // The assignment is allowed when matching a formal parameter to an actual - // parameter (see the if block above). - - int varTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(varType); - int valueTypeGuardSatisfiedIndex = - atypeFactory.getGuardSatisfiedIndex(valueType); - - if (varTypeGuardSatisfiedIndex == -1 && valueTypeGuardSatisfiedIndex == -1) { - checker.reportError( - valueTree, - "guardsatisfied.assignment.disallowed", - varType, - valueType); - result = false; - } - } else { - // The RHS can be @GuardSatisfied with a different index when matching method - // formal parameters to actual parameters. - // The actual matching is done in LockVisitor.visitMethodInvocation and a - // guardsatisfied.parameters.must.match error - // is issued if the parameters do not match exactly. - // Do nothing here, since there is no precondition to be checked on a - // @GuardSatisfied parameter. - // Note: this matching of a @GS(index) to a @GS(differentIndex) is *only* - // allowed when matching method formal parameters to actual parameters. - - return true; - } - } else if (!atypeFactory.getTypeHierarchy().isSubtype(valueType, varType)) { - // Special case: replace the @GuardSatisfied primary annotation on the LHS with - // @GuardedBy({}) and see if it type checks. - - AnnotatedTypeMirror varType2 = - varType.deepCopy(); // TODO: Would shallowCopy be sufficient? - varType2.replaceAnnotation(atypeFactory.GUARDEDBY); - if (atypeFactory.getTypeHierarchy().isSubtype(valueType, varType2)) { - return true; - } - } - } - - result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) - && result; - return result; + if (lockPreconditionAnnotationCount > 1) { + checker.reportError(treeForErrorReporting, "multiple.lock.precondition.annotations"); } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - if (TreeUtils.isFieldAccess(tree)) { - AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); - // The atmOfReceiver for "void.class" is TypeKind.VOID, which isn't annotated so avoid - // it. - if (atmOfReceiver.getKind() != TypeKind.VOID) { - AnnotationMirror gb = - atmOfReceiver.getEffectiveAnnotationInHierarchy( - atypeFactory.GUARDEDBYUNKNOWN); - checkLock(tree.getExpression(), gb); - } - } - - return super.visitMemberSelect(tree, p); + } + + /** + * When visiting a method call, if the receiver formal parameter has type @GuardSatisfied and the + * receiver actual parameter has type @GuardedBy(...), this method verifies that the guard is + * satisfied, and it returns true, indicating that the receiver subtype check should be skipped. + * If the receiver actual parameter has type @GuardSatisfied, this method simply returns true + * without performing any other actions. The method returns false otherwise. + * + * @param methodInvocationTree the MethodInvocationTree of the method being called + * @param methodDefinitionReceiver the ATM of the formal receiver parameter of the method being + * called + * @param methodCallReceiver the ATM of the receiver argument of the method call + * @return whether the caller can skip the receiver subtype check + */ + @Override + protected boolean skipReceiverSubtypeCheck( + MethodInvocationTree methodInvocationTree, + AnnotatedTypeMirror methodDefinitionReceiver, + AnnotatedTypeMirror methodCallReceiver) { + + AnnotationMirror primaryGb = + methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + AnnotationMirror effectiveGb = + methodCallReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + + // If the receiver actual parameter has type @GuardSatisfied, skip the subtype check. + // Consider only a @GuardSatisfied primary annotation - hence use primaryGb instead of + // effectiveGb. + if (primaryGb != null && atypeFactory.areSameByClass(primaryGb, checkerGuardSatisfiedClass)) { + AnnotationMirror primaryGbOnMethodDefinition = + methodDefinitionReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + if (primaryGbOnMethodDefinition != null + && atypeFactory.areSameByClass(primaryGbOnMethodDefinition, checkerGuardSatisfiedClass)) { + return true; + } } - private void reportFailure( - @CompilerMessageKey String messageKey, - MethodTree overriderTree, - AnnotatedDeclaredType enclosingType, - AnnotatedExecutableType overridden, - AnnotatedDeclaredType overriddenType, - List overriderLocks, - List overriddenLocks) { - // Get the type of the overriding method. - AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree); - - if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) { - overridden = overridden.getErased(); + if (atypeFactory.areSameByClass(effectiveGb, checkerGuardedByClass)) { + AnnotationMirrorSet annos = methodDefinitionReceiver.getAnnotations(); + AnnotationMirror guardSatisfied = + atypeFactory.getAnnotationByClass(annos, checkerGuardSatisfiedClass); + if (guardSatisfied != null) { + ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); + if (receiverTree == null) { + checkLockOfImplicitThis(methodInvocationTree, effectiveGb); + } else { + checkLock(receiverTree, effectiveGb); } - String overriderMeth = overrider.toString(); - String overriderTyp = enclosingType.getUnderlyingType().asElement().toString(); - String overriddenMeth = overridden.toString(); - String overriddenTyp = overriddenType.getUnderlyingType().asElement().toString(); + return true; + } + } - if (overriderLocks == null || overriddenLocks == null) { + return false; + } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); + for (AnnotationMirror anno : tops) { + if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN)) { + annotationSet.add(atypeFactory.GUARDEDBY); + } else { + annotationSet.add(anno); + } + } + return annotationSet; + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Newly created objects are guarded by nothing, so allow @GuardedBy({}) on constructor + // results. + AnnotationMirror anno = + constructorType.getReturnType().getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN) + || AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYBOTTOM)) { + checker.reportWarning(constructorElement, "inconsistent.constructor.type", anno, null); + } + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + // In cases where assigning a value with a @GuardedBy annotation to a variable with a + // @GuardSatisfied annotation is legal, this is our last chance to check that the + // appropriate locks are held before the information in the @GuardedBy annotation is lost in + // the assignment to the variable annotated with @GuardSatisfied. See the discussion of + // @GuardSatisfied in the "Type-checking rules" section of the Lock Checker manual chapter + // for more details. + + boolean result = true; + if (varType.hasAnnotation(GuardSatisfied.class)) { + if (valueType.hasAnnotation(GuardedBy.class)) { + return checkLock(valueTree, valueType.getAnnotation(GuardedBy.class)); + } else if (valueType.hasAnnotation(GuardSatisfied.class)) { + // TODO: Find a cleaner, non-abstraction-breaking way to know whether method actual + // parameters are being assigned to formal parameters. + + if (!errorKey.equals("argument.type.incompatible")) { + // If both @GuardSatisfied have no index, the assignment is not allowed because + // the LHS and RHS expressions may be guarded by different lock expressions. + // The assignment is allowed when matching a formal parameter to an actual + // parameter (see the if block above). + + int varTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(varType); + int valueTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(valueType); + + if (varTypeGuardSatisfiedIndex == -1 && valueTypeGuardSatisfiedIndex == -1) { checker.reportError( - overriderTree, - messageKey, - overriderTyp, - overriderMeth, - overriddenTyp, - overriddenMeth); + valueTree, "guardsatisfied.assignment.disallowed", varType, valueType); + result = false; + } } else { - checker.reportError( - overriderTree, - messageKey, - overriderTyp, - overriderMeth, - overriddenTyp, - overriddenMeth, - overriderLocks, - overriddenLocks); + // The RHS can be @GuardSatisfied with a different index when matching method + // formal parameters to actual parameters. + // The actual matching is done in LockVisitor.visitMethodInvocation and a + // guardsatisfied.parameters.must.match error + // is issued if the parameters do not match exactly. + // Do nothing here, since there is no precondition to be checked on a + // @GuardSatisfied parameter. + // Note: this matching of a @GS(index) to a @GS(differentIndex) is *only* + // allowed when matching method formal parameters to actual parameters. + + return true; } - } - - /** - * Ensures that subclass methods are annotated with a stronger or equally strong side effect - * annotation than the parent class method. - */ - @Override - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedDeclaredType enclosingType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType) { - - boolean isValid = true; - - SideEffectAnnotation seaOfOverriderMethod = - atypeFactory.methodSideEffectAnnotation( - TreeUtils.elementFromDeclaration(overriderTree), false); - SideEffectAnnotation seaOfOverriddenMethod = - atypeFactory.methodSideEffectAnnotation(overriddenMethodType.getElement(), false); - - if (seaOfOverriderMethod.isWeakerThan(seaOfOverriddenMethod)) { - isValid = false; - reportFailure( - "override.sideeffect.invalid", - overriderTree, - enclosingType, - overriddenMethodType, - overriddenType, - null, - null); + } else if (!atypeFactory.getTypeHierarchy().isSubtype(valueType, varType)) { + // Special case: replace the @GuardSatisfied primary annotation on the LHS with + // @GuardedBy({}) and see if it type checks. + + AnnotatedTypeMirror varType2 = varType.deepCopy(); // TODO: Would shallowCopy be sufficient? + varType2.replaceAnnotation(atypeFactory.GUARDEDBY); + if (atypeFactory.getTypeHierarchy().isSubtype(valueType, varType2)) { + return true; } - - return super.checkOverride( - overriderTree, enclosingType, overriddenMethodType, overriddenType) - && isValid; + } } - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); + result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) && result; + return result; + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + if (TreeUtils.isFieldAccess(tree)) { + AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); + // The atmOfReceiver for "void.class" is TypeKind.VOID, which isn't annotated so avoid + // it. + if (atmOfReceiver.getKind() != TypeKind.VOID) { AnnotationMirror gb = - atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); checkLock(tree.getExpression(), gb); - return super.visitArrayAccess(tree, p); + } } - /** - * Skips the call to super and returns true. - * - *

{@code GuardedBy({})} is the default type on class declarations, which is a subtype of the - * top annotation {@code @GuardedByUnknown}. However, it is valid to declare an instance of a - * class with any annotation from the {@code @GuardedBy} hierarchy. Hence, this method returns - * true for annotations in the {@code @GuardedBy} hierarchy. - * - *

Also returns true for annotations in the {@code @LockPossiblyHeld} hierarchy since the - * default for that hierarchy is the top type and annotations from that hierarchy cannot be - * explicitly written in code. - */ - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - return true; + return super.visitMemberSelect(tree, p); + } + + private void reportFailure( + @CompilerMessageKey String messageKey, + MethodTree overriderTree, + AnnotatedDeclaredType enclosingType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType, + List overriderLocks, + List overriddenLocks) { + // Get the type of the overriding method. + AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree); + + if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) { + overridden = overridden.getErased(); + } + String overriderMeth = overrider.toString(); + String overriderTyp = enclosingType.getUnderlyingType().asElement().toString(); + String overriddenMeth = overridden.toString(); + String overriddenTyp = overriddenType.getUnderlyingType().asElement().toString(); + + if (overriderLocks == null || overriddenLocks == null) { + checker.reportError( + overriderTree, messageKey, overriderTyp, overriderMeth, overriddenTyp, overriddenMeth); + } else { + checker.reportError( + overriderTree, + messageKey, + overriderTyp, + overriderMeth, + overriddenTyp, + overriddenMeth, + overriderLocks, + overriddenLocks); + } + } + + /** + * Ensures that subclass methods are annotated with a stronger or equally strong side effect + * annotation than the parent class method. + */ + @Override + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedDeclaredType enclosingType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType) { + + boolean isValid = true; + + SideEffectAnnotation seaOfOverriderMethod = + atypeFactory.methodSideEffectAnnotation( + TreeUtils.elementFromDeclaration(overriderTree), false); + SideEffectAnnotation seaOfOverriddenMethod = + atypeFactory.methodSideEffectAnnotation(overriddenMethodType.getElement(), false); + + if (seaOfOverriderMethod.isWeakerThan(seaOfOverriddenMethod)) { + isValid = false; + reportFailure( + "override.sideeffect.invalid", + overriderTree, + enclosingType, + overriddenMethodType, + overriddenType, + null, + null); } - /** - * When visiting a method invocation, issue an error if the side effect annotation on the called - * method causes the side effect guarantee of the enclosing method to be violated. For example, - * a method annotated with @ReleasesNoLocks may not call a method annotated - * with @MayReleaseLocks. Also check that matching @GuardSatisfied(index) on a method's formal - * receiver/parameters matches those in corresponding locations on the method call site. - * - * @param methodInvocationTree the MethodInvocationTree of the method call being visited - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree methodInvocationTree, Void p) { - ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); - - SideEffectAnnotation seaOfInvokedMethod = - atypeFactory.methodSideEffectAnnotation(methodElement, false); - - MethodTree enclosingMethod = - TreePathUtil.enclosingMethod(atypeFactory.getPath(methodInvocationTree)); - - ExecutableElement enclosingMethodElement = null; - if (enclosingMethod != null) { - enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); - } + return super.checkOverride(overriderTree, enclosingType, overriddenMethodType, overriddenType) + && isValid; + } + + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); + AnnotationMirror gb = + atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + checkLock(tree.getExpression(), gb); + return super.visitArrayAccess(tree, p); + } + + /** + * Skips the call to super and returns true. + * + *

{@code GuardedBy({})} is the default type on class declarations, which is a subtype of the + * top annotation {@code @GuardedByUnknown}. However, it is valid to declare an instance of a + * class with any annotation from the {@code @GuardedBy} hierarchy. Hence, this method returns + * true for annotations in the {@code @GuardedBy} hierarchy. + * + *

Also returns true for annotations in the {@code @LockPossiblyHeld} hierarchy since the + * default for that hierarchy is the top type and annotations from that hierarchy cannot be + * explicitly written in code. + */ + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + return true; + } + + /** + * When visiting a method invocation, issue an error if the side effect annotation on the called + * method causes the side effect guarantee of the enclosing method to be violated. For example, a + * method annotated with @ReleasesNoLocks may not call a method annotated with @MayReleaseLocks. + * Also check that matching @GuardSatisfied(index) on a method's formal receiver/parameters + * matches those in corresponding locations on the method call site. + * + * @param methodInvocationTree the MethodInvocationTree of the method call being visited + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree methodInvocationTree, Void p) { + ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); + + SideEffectAnnotation seaOfInvokedMethod = + atypeFactory.methodSideEffectAnnotation(methodElement, false); + + MethodTree enclosingMethod = + TreePathUtil.enclosingMethod(atypeFactory.getPath(methodInvocationTree)); + + ExecutableElement enclosingMethodElement = null; + if (enclosingMethod != null) { + enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); + } - if (enclosingMethodElement != null) { - SideEffectAnnotation seaOfContainingMethod = - atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false); + if (enclosingMethodElement != null) { + SideEffectAnnotation seaOfContainingMethod = + atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false); + + if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) { + checker.reportError( + methodInvocationTree, + "method.guarantee.violated", + seaOfContainingMethod.getNameOfSideEffectAnnotation(), + enclosingMethodElement.getSimpleName(), + methodElement.getSimpleName(), + seaOfInvokedMethod.getNameOfSideEffectAnnotation()); + } + } - if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) { - checker.reportError( - methodInvocationTree, - "method.guarantee.violated", - seaOfContainingMethod.getNameOfSideEffectAnnotation(), - enclosingMethodElement.getSimpleName(), - methodElement.getSimpleName(), - seaOfInvokedMethod.getNameOfSideEffectAnnotation()); - } + if (methodElement != null) { + // Handle releasing of explicit locks. Verify that the lock expression is effectively + // final. + ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); + + ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(methodElement, receiverTree); + + // Handle acquiring of explicit locks. Verify that the lock expression is effectively + // final. + + // If the method causes expression "this" or "#1" to be locked, verify that those + // expressions are effectively final. TODO: generalize to any expression. This is + // currently designed only to support methods in ReentrantLock and + // ReentrantReadWriteLock (which use the "this" expression), as well as Thread.holdsLock + // (which uses the "#1" expression). + + AnnotationMirror ensuresLockHeldAnno = + atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class); + + List expressions = new ArrayList<>(); + if (ensuresLockHeldAnno != null) { + expressions.addAll( + AnnotationUtils.getElementValueArray( + ensuresLockHeldAnno, atypeFactory.ensuresLockHeldValueElement, String.class)); + } + + AnnotationMirror ensuresLockHeldIfAnno = + atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class); + + if (ensuresLockHeldIfAnno != null) { + expressions.addAll( + AnnotationUtils.getElementValueArray( + ensuresLockHeldIfAnno, + atypeFactory.ensuresLockHeldIfExpressionElement, + String.class)); + } + + for (String expr : expressions) { + if (expr.equals("this")) { + // receiverTree will be null for implicit this, or class name receivers. But + // they are also final. So nothing to be checked for them. + if (receiverTree != null) { + ensureExpressionIsEffectivelyFinal(receiverTree); + } + } else if (expr.equals("#1")) { + ExpressionTree firstParameter = methodInvocationTree.getArguments().get(0); + if (firstParameter != null) { + ensureExpressionIsEffectivelyFinal(firstParameter); + } } + } + } - if (methodElement != null) { - // Handle releasing of explicit locks. Verify that the lock expression is effectively - // final. - ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); + // Check that matching @GuardSatisfied(index) on a method's formal receiver/parameters + // matches those in corresponding locations on the method call site. - ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(methodElement, receiverTree); + ParameterizedExecutableType mType = atypeFactory.methodFromUse(methodInvocationTree); + AnnotatedExecutableType invokedMethod = mType.executableType; - // Handle acquiring of explicit locks. Verify that the lock expression is effectively - // final. + List paramTypes = invokedMethod.getParameterTypes(); - // If the method causes expression "this" or "#1" to be locked, verify that those - // expressions are effectively final. TODO: generalize to any expression. This is - // currently designed only to support methods in ReentrantLock and - // ReentrantReadWriteLock (which use the "this" expression), as well as Thread.holdsLock - // (which uses the "#1" expression). + // Index on @GuardSatisfied at each location. -1 when no @GuardSatisfied annotation was + // present. + // Note that @GuardSatisfied with no index is normally represented as having index -1. + // We would like to ignore a @GuardSatisfied with no index for these purposes, so if it is + // encountered we leave its index as -1. + // The first element of the array is reserved for the receiver. + int guardSatisfiedIndex[] = + new int[paramTypes.size() + 1]; // + 1 for the receiver parameter type - AnnotationMirror ensuresLockHeldAnno = - atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class); + // Retrieve receiver types from method definition and method call - List expressions = new ArrayList<>(); - if (ensuresLockHeldAnno != null) { - expressions.addAll( - AnnotationUtils.getElementValueArray( - ensuresLockHeldAnno, - atypeFactory.ensuresLockHeldValueElement, - String.class)); - } + guardSatisfiedIndex[0] = -1; - AnnotationMirror ensuresLockHeldIfAnno = - atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class); + AnnotatedTypeMirror methodDefinitionReceiver = null; + AnnotatedTypeMirror methodCallReceiver = null; - if (ensuresLockHeldIfAnno != null) { - expressions.addAll( - AnnotationUtils.getElementValueArray( - ensuresLockHeldIfAnno, - atypeFactory.ensuresLockHeldIfExpressionElement, - String.class)); - } - - for (String expr : expressions) { - if (expr.equals("this")) { - // receiverTree will be null for implicit this, or class name receivers. But - // they are also final. So nothing to be checked for them. - if (receiverTree != null) { - ensureExpressionIsEffectivelyFinal(receiverTree); - } - } else if (expr.equals("#1")) { - ExpressionTree firstParameter = methodInvocationTree.getArguments().get(0); - if (firstParameter != null) { - ensureExpressionIsEffectivelyFinal(firstParameter); - } - } - } - } - - // Check that matching @GuardSatisfied(index) on a method's formal receiver/parameters - // matches those in corresponding locations on the method call site. - - ParameterizedExecutableType mType = atypeFactory.methodFromUse(methodInvocationTree); - AnnotatedExecutableType invokedMethod = mType.executableType; - - List paramTypes = invokedMethod.getParameterTypes(); + ExecutableElement invokedMethodElement = invokedMethod.getElement(); + if (!ElementUtils.isStatic(invokedMethodElement) + && invokedMethod.getElement().getKind() != ElementKind.CONSTRUCTOR) { + methodDefinitionReceiver = invokedMethod.getReceiverType(); + if (methodDefinitionReceiver != null + && methodDefinitionReceiver.hasAnnotation(checkerGuardSatisfiedClass)) { + guardSatisfiedIndex[0] = atypeFactory.getGuardSatisfiedIndex(methodDefinitionReceiver); + methodCallReceiver = atypeFactory.getReceiverType(methodInvocationTree); + } + } - // Index on @GuardSatisfied at each location. -1 when no @GuardSatisfied annotation was - // present. - // Note that @GuardSatisfied with no index is normally represented as having index -1. - // We would like to ignore a @GuardSatisfied with no index for these purposes, so if it is - // encountered we leave its index as -1. - // The first element of the array is reserved for the receiver. - int guardSatisfiedIndex[] = - new int[paramTypes.size() + 1]; // + 1 for the receiver parameter type + // Retrieve formal parameter types from the method definition. - // Retrieve receiver types from method definition and method call + for (int i = 0; i < paramTypes.size(); i++) { + guardSatisfiedIndex[i + 1] = -1; - guardSatisfiedIndex[0] = -1; + AnnotatedTypeMirror paramType = paramTypes.get(i); - AnnotatedTypeMirror methodDefinitionReceiver = null; - AnnotatedTypeMirror methodCallReceiver = null; + if (paramType.hasAnnotation(checkerGuardSatisfiedClass)) { + guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(paramType); + } + } - ExecutableElement invokedMethodElement = invokedMethod.getElement(); - if (!ElementUtils.isStatic(invokedMethodElement) - && invokedMethod.getElement().getKind() != ElementKind.CONSTRUCTOR) { - methodDefinitionReceiver = invokedMethod.getReceiverType(); - if (methodDefinitionReceiver != null - && methodDefinitionReceiver.hasAnnotation(checkerGuardSatisfiedClass)) { - guardSatisfiedIndex[0] = - atypeFactory.getGuardSatisfiedIndex(methodDefinitionReceiver); - methodCallReceiver = atypeFactory.getReceiverType(methodInvocationTree); - } - } + // Combine all of the actual parameters into one list of AnnotationMirrors. - // Retrieve formal parameter types from the method definition. + ArrayList passedArgTypes = new ArrayList<>(guardSatisfiedIndex.length); + passedArgTypes.add(methodCallReceiver); + for (ExpressionTree argTree : methodInvocationTree.getArguments()) { + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argTree); + passedArgTypes.add(argType); + } + ArrayList passedArgAnnotations = new ArrayList<>(guardSatisfiedIndex.length); + for (AnnotatedTypeMirror atm : passedArgTypes) { + if (atm != null) { + passedArgAnnotations.add(atm.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN)); + } else { + passedArgAnnotations.add(null); + } + } - for (int i = 0; i < paramTypes.size(); i++) { - guardSatisfiedIndex[i + 1] = -1; + // Perform the validity check and issue an error if not valid. + + for (int i = 0; i < guardSatisfiedIndex.length; i++) { + if (guardSatisfiedIndex[i] != -1) { + for (int j = i + 1; j < guardSatisfiedIndex.length; j++) { + if (guardSatisfiedIndex[i] == guardSatisfiedIndex[j]) { + // The @GuardedBy/@GuardSatisfied/@GuardedByUnknown/@GuardedByBottom + // annotations must be identical on the corresponding actual parameters. + AnnotationMirror arg1Anno = passedArgAnnotations.get(i); + AnnotationMirror arg2Anno = passedArgAnnotations.get(j); + if (arg1Anno != null && arg2Anno != null) { + boolean bothAreGSwithNoIndex = false; + + if (atypeFactory.areSameByClass(arg1Anno, checkerGuardSatisfiedClass) + && atypeFactory.areSameByClass(arg2Anno, checkerGuardSatisfiedClass)) { + if (atypeFactory.getGuardSatisfiedIndex(arg1Anno) == -1 + && atypeFactory.getGuardSatisfiedIndex(arg2Anno) == -1) { + // Generally speaking, two @GuardSatisfied annotations with no + // index are incomparable. + // TODO: If they come from the same variable, they are + // comparable. Fix and add a test case. + bothAreGSwithNoIndex = true; + } + } - AnnotatedTypeMirror paramType = paramTypes.get(i); + TypeMirror arg1TM = passedArgTypes.get(i).getUnderlyingType(); + TypeMirror arg2TM = passedArgTypes.get(j).getUnderlyingType(); - if (paramType.hasAnnotation(checkerGuardSatisfiedClass)) { - guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(paramType); - } - } + if (bothAreGSwithNoIndex + || !(qualHierarchy.isSubtypeShallow(arg1Anno, arg1TM, arg2Anno, arg2TM) + || qualHierarchy.isSubtypeShallow(arg2Anno, arg2TM, arg1Anno, arg1TM))) { - // Combine all of the actual parameters into one list of AnnotationMirrors. + String formalParam1; + if (i == 0) { + formalParam1 = "The receiver type"; + } else { + formalParam1 = "Parameter #" + i; // i, not i-1, so the index is 1-based + } - ArrayList passedArgTypes = new ArrayList<>(guardSatisfiedIndex.length); - passedArgTypes.add(methodCallReceiver); - for (ExpressionTree argTree : methodInvocationTree.getArguments()) { - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argTree); - passedArgTypes.add(argType); - } - ArrayList passedArgAnnotations = - new ArrayList<>(guardSatisfiedIndex.length); - for (AnnotatedTypeMirror atm : passedArgTypes) { - if (atm != null) { - passedArgAnnotations.add( - atm.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN)); - } else { - passedArgAnnotations.add(null); - } - } + String formalParam2 = "parameter #" + j; // j, not j-1, so the index is 1-based - // Perform the validity check and issue an error if not valid. - - for (int i = 0; i < guardSatisfiedIndex.length; i++) { - if (guardSatisfiedIndex[i] != -1) { - for (int j = i + 1; j < guardSatisfiedIndex.length; j++) { - if (guardSatisfiedIndex[i] == guardSatisfiedIndex[j]) { - // The @GuardedBy/@GuardSatisfied/@GuardedByUnknown/@GuardedByBottom - // annotations must be identical on the corresponding actual parameters. - AnnotationMirror arg1Anno = passedArgAnnotations.get(i); - AnnotationMirror arg2Anno = passedArgAnnotations.get(j); - if (arg1Anno != null && arg2Anno != null) { - boolean bothAreGSwithNoIndex = false; - - if (atypeFactory.areSameByClass(arg1Anno, checkerGuardSatisfiedClass) - && atypeFactory.areSameByClass( - arg2Anno, checkerGuardSatisfiedClass)) { - if (atypeFactory.getGuardSatisfiedIndex(arg1Anno) == -1 - && atypeFactory.getGuardSatisfiedIndex(arg2Anno) == -1) { - // Generally speaking, two @GuardSatisfied annotations with no - // index are incomparable. - // TODO: If they come from the same variable, they are - // comparable. Fix and add a test case. - bothAreGSwithNoIndex = true; - } - } - - TypeMirror arg1TM = passedArgTypes.get(i).getUnderlyingType(); - TypeMirror arg2TM = passedArgTypes.get(j).getUnderlyingType(); - - if (bothAreGSwithNoIndex - || !(qualHierarchy.isSubtypeShallow( - arg1Anno, arg1TM, arg2Anno, arg2TM) - || qualHierarchy.isSubtypeShallow( - arg2Anno, arg2TM, arg1Anno, arg1TM))) { - - String formalParam1; - if (i == 0) { - formalParam1 = "The receiver type"; - } else { - formalParam1 = - "Parameter #" - + i; // i, not i-1, so the index is 1-based - } - - String formalParam2 = - "parameter #" + j; // j, not j-1, so the index is 1-based - - checker.reportError( - methodInvocationTree, - "guardsatisfied.parameters.must.match", - formalParam1, - formalParam2, - invokedMethod.toString(), - guardSatisfiedIndex[i], - arg1Anno, - arg2Anno); - } - } - } - } + checker.reportError( + methodInvocationTree, + "guardsatisfied.parameters.must.match", + formalParam1, + formalParam2, + invokedMethod.toString(), + guardSatisfiedIndex[i], + arg1Anno, + arg2Anno); + } } + } } - - return super.visitMethodInvocation(methodInvocationTree, p); + } } - /** - * Issues an error if the receiver of an unlock() call is not effectively final. - * - * @param methodElement the ExecutableElement for a method call to unlock() - * @param lockExpression the receiver tree for the method call to unlock(). Can be null. - */ - private void ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal( - ExecutableElement methodElement, @Nullable ExpressionTree lockExpression) { - if (lockExpression == null) { - // Implicit this, or class name receivers, are null. But they are also final. So nothing - // to be checked for them. - return; - } + return super.visitMethodInvocation(methodInvocationTree, p); + } + + /** + * Issues an error if the receiver of an unlock() call is not effectively final. + * + * @param methodElement the ExecutableElement for a method call to unlock() + * @param lockExpression the receiver tree for the method call to unlock(). Can be null. + */ + private void ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal( + ExecutableElement methodElement, @Nullable ExpressionTree lockExpression) { + if (lockExpression == null) { + // Implicit this, or class name receivers, are null. But they are also final. So nothing + // to be checked for them. + return; + } - if (!methodElement.getSimpleName().contentEquals("unlock")) { - return; - } + if (!methodElement.getSimpleName().contentEquals("unlock")) { + return; + } - TypeMirror lockExpressionType = TreeUtils.typeOf(lockExpression); + TypeMirror lockExpressionType = TreeUtils.typeOf(lockExpression); - ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); + ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); - javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); + javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); - // TODO: make a type declaration annotation for this rather than looking for the - // Lock.unlock() method explicitly. - TypeMirror lockInterfaceTypeMirror = - TypesUtils.typeFromClass( - Lock.class, types, processingEnvironment.getElementUtils()); + // TODO: make a type declaration annotation for this rather than looking for the + // Lock.unlock() method explicitly. + TypeMirror lockInterfaceTypeMirror = + TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils()); - if (types.isSubtype(types.erasure(lockExpressionType), lockInterfaceTypeMirror)) { - ensureExpressionIsEffectivelyFinal(lockExpression); - } + if (types.isSubtype(types.erasure(lockExpressionType), lockInterfaceTypeMirror)) { + ensureExpressionIsEffectivelyFinal(lockExpression); + } + } + + /** + * When visiting a synchronized block, issue an error if the expression has a type that implements + * the java.util.concurrent.locks.Lock interface. This prevents explicit locks from being + * accidentally used as built-in (monitor) locks. This is important because the Lock Checker does + * not have a mechanism to separately keep track of the explicit lock and the monitor lock of an + * expression that implements the Lock interface (i.e. there is a @LockHeld annotation used in + * dataflow, but there are not distinct @MonitorLockHeld and @ExplicitLockHeld annotations). It is + * assumed that both kinds of locks will never be held for any expression that implements Lock. + * + *

Additionally, a synchronized block may not be present in a method that has a @LockingFree + * guarantee or stronger. An error is issued in this case. + * + * @param tree the SynchronizedTree for the synchronized block being visited + */ + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); + + javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); + + // TODO: make a type declaration annotation for this rather than looking for Lock.class + // explicitly. + TypeMirror lockInterfaceTypeMirror = + TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils()); + + ExpressionTree synchronizedExpression = tree.getExpression(); + + ensureExpressionIsEffectivelyFinal(synchronizedExpression); + + TypeMirror expressionType = + types.erasure(atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType()); + + if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) { + checker.reportError(tree, "explicit.lock.synchronized"); } - /** - * When visiting a synchronized block, issue an error if the expression has a type that - * implements the java.util.concurrent.locks.Lock interface. This prevents explicit locks from - * being accidentally used as built-in (monitor) locks. This is important because the Lock - * Checker does not have a mechanism to separately keep track of the explicit lock and the - * monitor lock of an expression that implements the Lock interface (i.e. there is a @LockHeld - * annotation used in dataflow, but there are not distinct @MonitorLockHeld - * and @ExplicitLockHeld annotations). It is assumed that both kinds of locks will never be held - * for any expression that implements Lock. - * - *

Additionally, a synchronized block may not be present in a method that has a @LockingFree - * guarantee or stronger. An error is issued in this case. - * - * @param tree the SynchronizedTree for the synchronized block being visited - */ - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); - - javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); - - // TODO: make a type declaration annotation for this rather than looking for Lock.class - // explicitly. - TypeMirror lockInterfaceTypeMirror = - TypesUtils.typeFromClass( - Lock.class, types, processingEnvironment.getElementUtils()); - - ExpressionTree synchronizedExpression = tree.getExpression(); - - ensureExpressionIsEffectivelyFinal(synchronizedExpression); - - TypeMirror expressionType = - types.erasure( - atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType()); - - if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) { - checker.reportError(tree, "explicit.lock.synchronized"); - } + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(tree)); - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(tree)); + ExecutableElement methodElement = null; + if (enclosingMethod != null) { + methodElement = TreeUtils.elementFromDeclaration(enclosingMethod); - ExecutableElement methodElement = null; - if (enclosingMethod != null) { - methodElement = TreeUtils.elementFromDeclaration(enclosingMethod); + SideEffectAnnotation seaOfContainingMethod = + atypeFactory.methodSideEffectAnnotation(methodElement, false); - SideEffectAnnotation seaOfContainingMethod = - atypeFactory.methodSideEffectAnnotation(methodElement, false); + if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) { + checker.reportError( + tree, "synchronized.block.in.lockingfree.method", seaOfContainingMethod); + } + } - if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) { - checker.reportError( - tree, "synchronized.block.in.lockingfree.method", seaOfContainingMethod); - } - } + return super.visitSynchronized(tree, p); + } + + /** + * Ensures that each variable accessed in an expression is final or effectively final and that + * each called method in the expression is @Deterministic. Issues an error otherwise. Recursively + * performs this check on method arguments. Only intended to be used on the expression of a + * synchronized block. + * + *

Example: given the expression var1.field1.method1(var2.method2()).field2, var1, var2, field1 + * and field2 are enforced to be final or effectively final, and method1 and method2 are enforced + * to be @Deterministic. + * + * @param lockExpressionTree the expression tree of a synchronized block + * @return true if the check succeeds, false if an error message was issued + */ + private boolean ensureExpressionIsEffectivelyFinal(ExpressionTree lockExpressionTree) { + // This functionality could be implemented using a visitor instead, however with this + // design, it is easier to be certain that an error will always be issued if a tree kind is + // not recognized. + // Only the most common tree kinds for synchronized expressions are supported. + + // Traverse the expression using 'tree', as 'lockExpressionTree' is used for error + // reporting. + ExpressionTree tree = lockExpressionTree; + + boolean result = true; + while (true) { + tree = TreeUtils.withoutParens(tree); + + switch (tree.getKind()) { + case MEMBER_SELECT: + if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { + checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); + return false; + } + tree = ((MemberSelectTree) tree).getExpression(); + break; + case IDENTIFIER: + if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { + checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); + return false; + } + return result; + case METHOD_INVOCATION: + Element elem = TreeUtils.elementFromUse(tree); + if (atypeFactory.getDeclAnnotationNoAliases(elem, Deterministic.class) == null + && atypeFactory.getDeclAnnotationNoAliases(elem, Pure.class) == null) { + checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); + return false; + } - return super.visitSynchronized(tree, p); - } + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; - /** - * Ensures that each variable accessed in an expression is final or effectively final and that - * each called method in the expression is @Deterministic. Issues an error otherwise. - * Recursively performs this check on method arguments. Only intended to be used on the - * expression of a synchronized block. - * - *

Example: given the expression var1.field1.method1(var2.method2()).field2, var1, var2, - * field1 and field2 are enforced to be final or effectively final, and method1 and method2 are - * enforced to be @Deterministic. - * - * @param lockExpressionTree the expression tree of a synchronized block - * @return true if the check succeeds, false if an error message was issued - */ - private boolean ensureExpressionIsEffectivelyFinal(ExpressionTree lockExpressionTree) { - // This functionality could be implemented using a visitor instead, however with this - // design, it is easier to be certain that an error will always be issued if a tree kind is - // not recognized. - // Only the most common tree kinds for synchronized expressions are supported. - - // Traverse the expression using 'tree', as 'lockExpressionTree' is used for error - // reporting. - ExpressionTree tree = lockExpressionTree; - - boolean result = true; - while (true) { - tree = TreeUtils.withoutParens(tree); - - switch (tree.getKind()) { - case MEMBER_SELECT: - if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { - checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); - return false; - } - tree = ((MemberSelectTree) tree).getExpression(); - break; - case IDENTIFIER: - if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { - checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); - return false; - } - return result; - case METHOD_INVOCATION: - Element elem = TreeUtils.elementFromUse(tree); - if (atypeFactory.getDeclAnnotationNoAliases(elem, Deterministic.class) == null - && atypeFactory.getDeclAnnotationNoAliases(elem, Pure.class) == null) { - checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); - return false; - } - - MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; - - for (ExpressionTree argTree : methodInvocationTree.getArguments()) { - result = ensureExpressionIsEffectivelyFinal(argTree) && result; - } - - tree = methodInvocationTree.getMethodSelect(); - break; - default: - checker.reportError( - tree, "lock.expression.possibly.not.final", lockExpressionTree); - return false; - } - } - } + for (ExpressionTree argTree : methodInvocationTree.getArguments()) { + result = ensureExpressionIsEffectivelyFinal(argTree) && result; + } - /** - * Issues an error if the given expression is not effectively final. Returns true if the - * expression is effectively final, false if an error was issued. - * - * @param lockExpr an expression that might be effectively final - * @param expressionForErrorReporting how to print the expression in an error message - * @param treeForErrorReporting where to report the error - * @return true if the expression is effectively final, false if an error was issued - */ - private boolean ensureExpressionIsEffectivelyFinal( - JavaExpression lockExpr, - String expressionForErrorReporting, - Tree treeForErrorReporting) { - boolean result = atypeFactory.isExpressionEffectivelyFinal(lockExpr); - if (!result) { - checker.reportError( - treeForErrorReporting, - "lock.expression.not.final", - expressionForErrorReporting); - } - return result; + tree = methodInvocationTree.getMethodSelect(); + break; + default: + checker.reportError(tree, "lock.expression.possibly.not.final", lockExpressionTree); + return false; + } } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - ArrayList annotationTreeList = new ArrayList<>(1); - annotationTreeList.add(tree); - List amList = - TreeUtils.annotationsFromTypeAnnotationTrees(annotationTreeList); - - if (amList != null) { - for (AnnotationMirror annotationMirror : amList) { - if (atypeFactory.areSameByClass(annotationMirror, checkerGuardSatisfiedClass)) { - issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(tree); - } - } + } + + /** + * Issues an error if the given expression is not effectively final. Returns true if the + * expression is effectively final, false if an error was issued. + * + * @param lockExpr an expression that might be effectively final + * @param expressionForErrorReporting how to print the expression in an error message + * @param treeForErrorReporting where to report the error + * @return true if the expression is effectively final, false if an error was issued + */ + private boolean ensureExpressionIsEffectivelyFinal( + JavaExpression lockExpr, String expressionForErrorReporting, Tree treeForErrorReporting) { + boolean result = atypeFactory.isExpressionEffectivelyFinal(lockExpr); + if (!result) { + checker.reportError( + treeForErrorReporting, "lock.expression.not.final", expressionForErrorReporting); + } + return result; + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + ArrayList annotationTreeList = new ArrayList<>(1); + annotationTreeList.add(tree); + List amList = + TreeUtils.annotationsFromTypeAnnotationTrees(annotationTreeList); + + if (amList != null) { + for (AnnotationMirror annotationMirror : amList) { + if (atypeFactory.areSameByClass(annotationMirror, checkerGuardSatisfiedClass)) { + issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(tree); } - - return super.visitAnnotation(tree, p); + } } - /** - * Issues an error if a GuardSatisfied annotation is found in a location other than a method - * return type or parameter (including the receiver). - * - * @param annotationTree an AnnotationTree used for error reporting and to help determine that - * an array parameter has no GuardSatisfied annotations except on the array type - */ - // TODO: Remove this method once @TargetLocations are enforced (i.e. once - // issue https://github.com/typetools/checker-framework/issues/1919 is closed). - private void issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation( - AnnotationTree annotationTree) { - TreePath currentPath = getCurrentPath(); - TreePath path = getPathForLocalVariableRetrieval(currentPath); - if (path != null) { - Tree tree = path.getLeaf(); - Tree.Kind kind = tree.getKind(); - - if (kind == Tree.Kind.METHOD) { - // The @GuardSatisfied annotation is on the return type. + return super.visitAnnotation(tree, p); + } + + /** + * Issues an error if a GuardSatisfied annotation is found in a location other than a method + * return type or parameter (including the receiver). + * + * @param annotationTree an AnnotationTree used for error reporting and to help determine that an + * array parameter has no GuardSatisfied annotations except on the array type + */ + // TODO: Remove this method once @TargetLocations are enforced (i.e. once + // issue https://github.com/typetools/checker-framework/issues/1919 is closed). + private void issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation( + AnnotationTree annotationTree) { + TreePath currentPath = getCurrentPath(); + TreePath path = getPathForLocalVariableRetrieval(currentPath); + if (path != null) { + Tree tree = path.getLeaf(); + Tree.Kind kind = tree.getKind(); + + if (kind == Tree.Kind.METHOD) { + // The @GuardSatisfied annotation is on the return type. + return; + } else if (kind == Tree.Kind.VARIABLE) { + VariableTree varTree = (VariableTree) tree; + Tree varTypeTree = varTree.getType(); + if (varTypeTree != null) { + TreePath parentPath = path.getParentPath(); + if (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) { + Tree.Kind varTypeTreeKind = varTypeTree.getKind(); + if (varTypeTreeKind == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) varTypeTree; + + if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE + || annotatedTypeTree.getAnnotations().contains(annotationTree)) { + // Method parameter return; - } else if (kind == Tree.Kind.VARIABLE) { - VariableTree varTree = (VariableTree) tree; - Tree varTypeTree = varTree.getType(); - if (varTypeTree != null) { - TreePath parentPath = path.getParentPath(); - if (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) { - Tree.Kind varTypeTreeKind = varTypeTree.getKind(); - if (varTypeTreeKind == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) varTypeTree; - - if (annotatedTypeTree.getUnderlyingType().getKind() - != Tree.Kind.ARRAY_TYPE - || annotatedTypeTree - .getAnnotations() - .contains(annotationTree)) { - // Method parameter - return; - } - } else if (varTypeTreeKind != Tree.Kind.ARRAY_TYPE) { - // Method parameter or receiver - return; - } - } - } + } + } else if (varTypeTreeKind != Tree.Kind.ARRAY_TYPE) { + // Method parameter or receiver + return; } + } } - - checker.reportError(annotationTree, "guardsatisfied.location.disallowed"); + } } - /** - * The JavaExpression parser requires a path for retrieving the scope that will be used to - * resolve local variables. One would expect that simply providing the path to an AnnotationTree - * would work, since the compiler (as called by the org.checkerframework.javacutil.Resolver - * class) could walk up the path from the AnnotationTree to determine the scope. Unfortunately - * this is not how the compiler works. One must provide the path at the right level (not so deep - * that it results in a symbol not being found, but not so high up that it is out of the scope - * at hand). This is a problem when trying to retrieve local variables, since one could silently - * miss a local variable in scope and accidentally retrieve a field with the same name. This - * method returns the correct path for this purpose, given a path to an AnnotationTree. - * - *

Note: this is definitely necessary for local variable retrieval. It has not been tested - * whether this is strictly necessary for fields or other identifiers. - * - *

Only call this method from visitAnnotation. - * - * @param path the TreePath whose leaf is an AnnotationTree - * @return a TreePath that can be passed to methods in the Resolver class to locate local - * variables - */ - private @Nullable TreePath getPathForLocalVariableRetrieval(TreePath path) { - assert path.getLeaf() instanceof AnnotationTree; - - // TODO: handle annotations in trees of kind NEW_CLASS (and add test coverage for this - // scenario). - // Currently an annotation in such a tree, such as "new @GuardedBy("foo") Object()", - // results in a "constructor.invocation.invalid" error. This must be fixed first. - - path = path.getParentPath(); - - if (path == null) { - return null; - } + checker.reportError(annotationTree, "guardsatisfied.location.disallowed"); + } + + /** + * The JavaExpression parser requires a path for retrieving the scope that will be used to resolve + * local variables. One would expect that simply providing the path to an AnnotationTree would + * work, since the compiler (as called by the org.checkerframework.javacutil.Resolver class) could + * walk up the path from the AnnotationTree to determine the scope. Unfortunately this is not how + * the compiler works. One must provide the path at the right level (not so deep that it results + * in a symbol not being found, but not so high up that it is out of the scope at hand). This is a + * problem when trying to retrieve local variables, since one could silently miss a local variable + * in scope and accidentally retrieve a field with the same name. This method returns the correct + * path for this purpose, given a path to an AnnotationTree. + * + *

Note: this is definitely necessary for local variable retrieval. It has not been tested + * whether this is strictly necessary for fields or other identifiers. + * + *

Only call this method from visitAnnotation. + * + * @param path the TreePath whose leaf is an AnnotationTree + * @return a TreePath that can be passed to methods in the Resolver class to locate local + * variables + */ + private @Nullable TreePath getPathForLocalVariableRetrieval(TreePath path) { + assert path.getLeaf() instanceof AnnotationTree; + + // TODO: handle annotations in trees of kind NEW_CLASS (and add test coverage for this + // scenario). + // Currently an annotation in such a tree, such as "new @GuardedBy("foo") Object()", + // results in a "constructor.invocation.invalid" error. This must be fixed first. + + path = path.getParentPath(); + + if (path == null) { + return null; + } - // A MODIFIERS tree for a VARIABLE or METHOD parent tree would be available at this level, - // but it is not directly handled. Instead, its parent tree (one level higher) is handled. - // Other tree kinds are also handled one level higher. + // A MODIFIERS tree for a VARIABLE or METHOD parent tree would be available at this level, + // but it is not directly handled. Instead, its parent tree (one level higher) is handled. + // Other tree kinds are also handled one level higher. - path = path.getParentPath(); + path = path.getParentPath(); - if (path == null) { - return null; - } - - Tree tree = path.getLeaf(); - Tree.Kind kind = tree.getKind(); - - switch (kind) { - case ARRAY_TYPE: - case VARIABLE: - case TYPE_CAST: - case INSTANCE_OF: - case METHOD: - case NEW_ARRAY: - case TYPE_PARAMETER: - // TODO: visitAnnotation does not currently visit annotations on wildcard bounds. - // Address this for the Lock Checker somehow and enable these, as well as the - // corresponding test cases in ChapterExamples.java - // case EXTENDS_WILDCARD: - // case SUPER_WILDCARD: - return path; - default: - return null; - } + if (path == null) { + return null; } - /** - * Returns true if the symbol for the given tree is final or effectively final. Package, class - * and method symbols are unmodifiable and therefore considered final. - * - * @param tree the tree to test - * @return true if the symbol for the given tree is final or effectively final - */ - private boolean isTreeSymbolEffectivelyFinalOrUnmodifiable(Tree tree) { - Element elem = TreeUtils.elementFromTree(tree); - ElementKind ek = elem.getKind(); - return ek == ElementKind.PACKAGE - || ek == ElementKind.CLASS - || ek == ElementKind.METHOD - || ElementUtils.isEffectivelyFinal(elem); + Tree tree = path.getLeaf(); + Tree.Kind kind = tree.getKind(); + + switch (kind) { + case ARRAY_TYPE: + case VARIABLE: + case TYPE_CAST: + case INSTANCE_OF: + case METHOD: + case NEW_ARRAY: + case TYPE_PARAMETER: + // TODO: visitAnnotation does not currently visit annotations on wildcard bounds. + // Address this for the Lock Checker somehow and enable these, as well as the + // corresponding test cases in ChapterExamples.java + // case EXTENDS_WILDCARD: + // case SUPER_WILDCARD: + return path; + default: + return null; } - - @Override - @SuppressWarnings("interning:not.interned") // AST node comparison - public Void visitIdentifier(IdentifierTree tree, Void p) { - // If the identifier is a field accessed via an implicit this, then check the lock of this. - // (All other field accesses are checked in visitMemberSelect.) - if (TreeUtils.isFieldAccess(tree)) { - Tree parent = getCurrentPath().getParentPath().getLeaf(); - // If the parent is not a member select, or if it is and the field is the expression, - // then the field is accessed via an implicit this. - if ((parent.getKind() != Tree.Kind.MEMBER_SELECT - || ((MemberSelectTree) parent).getExpression() == tree) - && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { - AnnotationMirror guardedBy = - atypeFactory - .getSelfType(tree) - .getAnnotationInHierarchy(atypeFactory.GUARDEDBY); - checkLockOfImplicitThis(tree, guardedBy); - } - } - return super.visitIdentifier(tree, p); + } + + /** + * Returns true if the symbol for the given tree is final or effectively final. Package, class and + * method symbols are unmodifiable and therefore considered final. + * + * @param tree the tree to test + * @return true if the symbol for the given tree is final or effectively final + */ + private boolean isTreeSymbolEffectivelyFinalOrUnmodifiable(Tree tree) { + Element elem = TreeUtils.elementFromTree(tree); + ElementKind ek = elem.getKind(); + return ek == ElementKind.PACKAGE + || ek == ElementKind.CLASS + || ek == ElementKind.METHOD + || ElementUtils.isEffectivelyFinal(elem); + } + + @Override + @SuppressWarnings("interning:not.interned") // AST node comparison + public Void visitIdentifier(IdentifierTree tree, Void p) { + // If the identifier is a field accessed via an implicit this, then check the lock of this. + // (All other field accesses are checked in visitMemberSelect.) + if (TreeUtils.isFieldAccess(tree)) { + Tree parent = getCurrentPath().getParentPath().getLeaf(); + // If the parent is not a member select, or if it is and the field is the expression, + // then the field is accessed via an implicit this. + if ((parent.getKind() != Tree.Kind.MEMBER_SELECT + || ((MemberSelectTree) parent).getExpression() == tree) + && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { + AnnotationMirror guardedBy = + atypeFactory.getSelfType(tree).getAnnotationInHierarchy(atypeFactory.GUARDEDBY); + checkLockOfImplicitThis(tree, guardedBy); + } } - - @Override - public Void visitBinary(BinaryTree binaryTree, Void p) { - if (TreeUtils.isStringConcatenation(binaryTree)) { - ExpressionTree leftTree = binaryTree.getLeftOperand(); - ExpressionTree rightTree = binaryTree.getRightOperand(); - - boolean lhsIsString = TypesUtils.isString(TreeUtils.typeOf(leftTree)); - boolean rhsIsString = TypesUtils.isString(TreeUtils.typeOf(rightTree)); - if (!lhsIsString) { - checkPreconditionsForImplicitToStringCall(leftTree); - } else if (!rhsIsString) { - checkPreconditionsForImplicitToStringCall(rightTree); - } - } - - return super.visitBinary(binaryTree, p); + return super.visitIdentifier(tree, p); + } + + @Override + public Void visitBinary(BinaryTree binaryTree, Void p) { + if (TreeUtils.isStringConcatenation(binaryTree)) { + ExpressionTree leftTree = binaryTree.getLeftOperand(); + ExpressionTree rightTree = binaryTree.getRightOperand(); + + boolean lhsIsString = TypesUtils.isString(TreeUtils.typeOf(leftTree)); + boolean rhsIsString = TypesUtils.isString(TreeUtils.typeOf(rightTree)); + if (!lhsIsString) { + checkPreconditionsForImplicitToStringCall(leftTree); + } else if (!rhsIsString) { + checkPreconditionsForImplicitToStringCall(rightTree); + } } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - ExpressionTree rightTree = tree.getExpression(); - if (!TypesUtils.isString(TreeUtils.typeOf(rightTree))) { - checkPreconditionsForImplicitToStringCall(rightTree); - } - } + return super.visitBinary(binaryTree, p); + } - return super.visitCompoundAssignment(tree, p); + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + ExpressionTree rightTree = tree.getExpression(); + if (!TypesUtils.isString(TreeUtils.typeOf(rightTree))) { + checkPreconditionsForImplicitToStringCall(rightTree); + } } - /** - * Checks precondition for {@code tree} that is known to be the receiver of an implicit - * toString() call. The receiver of toString() is defined in the annotated JDK to - * be @GuardSatisfied. Therefore if the expression is guarded by a set of locks, the locks must - * be held prior to this implicit call to toString(). - * - *

Only call this method from visitBinary and visitCompoundAssignment. - * - * @param tree the Tree corresponding to the expression that is known to be the receiver of an - * implicit toString() call - */ - // TODO: If and when the de-sugared .toString() tree is accessible from BaseTypeVisitor, - // the toString() method call should be visited instead of doing this. This would result - // in "contracts.precondition.not.satisfied" errors being issued instead of - // "contracts.precondition.not.satisfied.field", so it would be clear that - // the error refers to an implicit method call, not a dereference (field access). - private void checkPreconditionsForImplicitToStringCall(ExpressionTree tree) { - AnnotationMirror gbAnno = - atypeFactory - .getAnnotatedType(tree) - .getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBY); - checkLock(tree, gbAnno); + return super.visitCompoundAssignment(tree, p); + } + + /** + * Checks precondition for {@code tree} that is known to be the receiver of an implicit toString() + * call. The receiver of toString() is defined in the annotated JDK to be @GuardSatisfied. + * Therefore if the expression is guarded by a set of locks, the locks must be held prior to this + * implicit call to toString(). + * + *

Only call this method from visitBinary and visitCompoundAssignment. + * + * @param tree the Tree corresponding to the expression that is known to be the receiver of an + * implicit toString() call + */ + // TODO: If and when the de-sugared .toString() tree is accessible from BaseTypeVisitor, + // the toString() method call should be visited instead of doing this. This would result + // in "contracts.precondition.not.satisfied" errors being issued instead of + // "contracts.precondition.not.satisfied.field", so it would be clear that + // the error refers to an implicit method call, not a dereference (field access). + private void checkPreconditionsForImplicitToStringCall(ExpressionTree tree) { + AnnotationMirror gbAnno = + atypeFactory + .getAnnotatedType(tree) + .getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBY); + checkLock(tree, gbAnno); + } + + private void checkLockOfImplicitThis(Tree tree, AnnotationMirror gbAnno) { + checkLockOfThisOrTree(tree, true, gbAnno); + } + + /** + * Checks the lock of the given tree. + * + * @param tree a tree whose lock to check + * @param gbAnno a {@code @GuardedBy} annotation + * @return true if the check succeeds, false if an error message was issued + */ + private boolean checkLock(Tree tree, AnnotationMirror gbAnno) { + return checkLockOfThisOrTree(tree, false, gbAnno); + } + + /** + * Helper method that checks the lock of either the implicit {@code this} or the given tree. + * + * @param tree a tree whose lock to check + * @param implicitThis true if checking the lock of the implicit {@code this} + * @param gbAnno a {@code @GuardedBy} annotation + * @return true if the check succeeds, false if an error message was issued + */ + private boolean checkLockOfThisOrTree(Tree tree, boolean implicitThis, AnnotationMirror gbAnno) { + if (gbAnno == null) { + throw new TypeSystemError("LockVisitor.checkLock: gbAnno cannot be null"); } - - private void checkLockOfImplicitThis(Tree tree, AnnotationMirror gbAnno) { - checkLockOfThisOrTree(tree, true, gbAnno); + if (atypeFactory.areSameByClass(gbAnno, GuardedByUnknown.class) + || atypeFactory.areSameByClass(gbAnno, GuardedByBottom.class)) { + checker.reportError(tree, "lock.not.held", "unknown lock " + gbAnno); + return false; + } else if (atypeFactory.areSameByClass(gbAnno, GuardSatisfied.class)) { + return true; } - /** - * Checks the lock of the given tree. - * - * @param tree a tree whose lock to check - * @param gbAnno a {@code @GuardedBy} annotation - * @return true if the check succeeds, false if an error message was issued - */ - private boolean checkLock(Tree tree, AnnotationMirror gbAnno) { - return checkLockOfThisOrTree(tree, false, gbAnno); + List expressions = getLockExpressions(implicitThis, gbAnno, tree); + if (expressions.isEmpty()) { + return true; } - /** - * Helper method that checks the lock of either the implicit {@code this} or the given tree. - * - * @param tree a tree whose lock to check - * @param implicitThis true if checking the lock of the implicit {@code this} - * @param gbAnno a {@code @GuardedBy} annotation - * @return true if the check succeeds, false if an error message was issued - */ - private boolean checkLockOfThisOrTree( - Tree tree, boolean implicitThis, AnnotationMirror gbAnno) { - if (gbAnno == null) { - throw new TypeSystemError("LockVisitor.checkLock: gbAnno cannot be null"); - } - if (atypeFactory.areSameByClass(gbAnno, GuardedByUnknown.class) - || atypeFactory.areSameByClass(gbAnno, GuardedByBottom.class)) { - checker.reportError(tree, "lock.not.held", "unknown lock " + gbAnno); - return false; - } else if (atypeFactory.areSameByClass(gbAnno, GuardSatisfied.class)) { - return true; - } - - List expressions = getLockExpressions(implicitThis, gbAnno, tree); - if (expressions.isEmpty()) { - return true; - } - - boolean result = true; - LockStore store = atypeFactory.getStoreBefore(tree); - for (LockExpression expression : expressions) { - if (expression.error != null) { - checker.reportError( - tree, "expression.unparsable.type.invalid", expression.error.toString()); - result = false; - } else if (expression.lockExpression == null) { - checker.reportError( - tree, "expression.unparsable.type.invalid", expression.expressionString); - result = false; - } else if (!isLockHeld(expression.lockExpression, store)) { - checker.reportError(tree, "lock.not.held", expression.lockExpression.toString()); - result = false; - } - - if (expression.error != null && expression.lockExpression != null) { - result = - ensureExpressionIsEffectivelyFinal( - expression.lockExpression, - expression.expressionString, - tree) - && result; - } - } - return result; + boolean result = true; + LockStore store = atypeFactory.getStoreBefore(tree); + for (LockExpression expression : expressions) { + if (expression.error != null) { + checker.reportError( + tree, "expression.unparsable.type.invalid", expression.error.toString()); + result = false; + } else if (expression.lockExpression == null) { + checker.reportError( + tree, "expression.unparsable.type.invalid", expression.expressionString); + result = false; + } else if (!isLockHeld(expression.lockExpression, store)) { + checker.reportError(tree, "lock.not.held", expression.lockExpression.toString()); + result = false; + } + + if (expression.error != null && expression.lockExpression != null) { + result = + ensureExpressionIsEffectivelyFinal( + expression.lockExpression, expression.expressionString, tree) + && result; + } } + return result; + } - private boolean isLockHeld(JavaExpression lockExpr, LockStore store) { - if (store == null) { - return false; - } - CFAbstractValue value = store.getValue(lockExpr); - if (value == null) { - return false; - } - AnnotationMirrorSet annos = value.getAnnotations(); - AnnotationMirror lockAnno = - qualHierarchy.findAnnotationInSameHierarchy(annos, atypeFactory.LOCKHELD); - return lockAnno != null && atypeFactory.areSameByClass(lockAnno, LockHeld.class); + private boolean isLockHeld(JavaExpression lockExpr, LockStore store) { + if (store == null) { + return false; } + CFAbstractValue value = store.getValue(lockExpr); + if (value == null) { + return false; + } + AnnotationMirrorSet annos = value.getAnnotations(); + AnnotationMirror lockAnno = + qualHierarchy.findAnnotationInSameHierarchy(annos, atypeFactory.LOCKHELD); + return lockAnno != null && atypeFactory.areSameByClass(lockAnno, LockHeld.class); + } - private List getLockExpressions( - boolean implicitThis, AnnotationMirror gbAnno, Tree tree) { - - List expressions = - AnnotationUtils.getElementValueArray( - gbAnno, - atypeFactory.guardedByValueElement, - String.class, - Collections.emptyList()); + private List getLockExpressions( + boolean implicitThis, AnnotationMirror gbAnno, Tree tree) { - if (expressions.isEmpty()) { - return Collections.emptyList(); - } + List expressions = + AnnotationUtils.getElementValueArray( + gbAnno, atypeFactory.guardedByValueElement, String.class, Collections.emptyList()); - TreePath currentPath = getCurrentPath(); + if (expressions.isEmpty()) { + return Collections.emptyList(); + } - TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(currentPath)); - JavaExpression pseudoReceiver = - JavaExpression.getPseudoReceiver(currentPath, enclosingType); + TreePath currentPath = getCurrentPath(); - JavaExpression self; - if (implicitThis) { - self = pseudoReceiver; - } else if (TreeUtils.isExpressionTree(tree)) { - self = JavaExpression.fromTree((ExpressionTree) tree); - } else { - self = new Unknown(tree); - } + TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(currentPath)); + JavaExpression pseudoReceiver = JavaExpression.getPseudoReceiver(currentPath, enclosingType); - return CollectionsPlume.mapList( - expression -> parseExpressionString(expression, currentPath, self), expressions); + JavaExpression self; + if (implicitThis) { + self = pseudoReceiver; + } else if (TreeUtils.isExpressionTree(tree)) { + self = JavaExpression.fromTree((ExpressionTree) tree); + } else { + self = new Unknown(tree); } - /** - * Parse a Java expression. - * - * @param expression the Java expression - * @param path the path to the expression - * @param itself the self expression - * @return the parsed expression - */ - private LockExpression parseExpressionString( - String expression, TreePath path, JavaExpression itself) { - - LockExpression lockExpression = new LockExpression(expression); - if (DependentTypesError.isExpressionError(expression)) { - lockExpression.error = DependentTypesError.unparse(expression); - return lockExpression; - } + return CollectionsPlume.mapList( + expression -> parseExpressionString(expression, currentPath, self), expressions); + } + + /** + * Parse a Java expression. + * + * @param expression the Java expression + * @param path the path to the expression + * @param itself the self expression + * @return the parsed expression + */ + private LockExpression parseExpressionString( + String expression, TreePath path, JavaExpression itself) { + + LockExpression lockExpression = new LockExpression(expression); + if (DependentTypesError.isExpressionError(expression)) { + lockExpression.error = DependentTypesError.unparse(expression); + return lockExpression; + } - Matcher selfReceiverMatcher = SELF_RECEIVER_PATTERN.matcher(expression); - try { - if (selfReceiverMatcher.matches()) { - String remainingExpression = selfReceiverMatcher.group(2); - if (remainingExpression == null || remainingExpression.isEmpty()) { - lockExpression.lockExpression = itself; - if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { - checker.reportError( - path.getLeaf(), - "lock.expression.not.final", - lockExpression.lockExpression); - } - return lockExpression; - } else { - lockExpression.lockExpression = - StringToJavaExpression.atPath( - itself.toString() + "." + remainingExpression, path, checker); - if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { - checker.reportError( - path.getLeaf(), - "lock.expression.not.final", - lockExpression.lockExpression); - } - return lockExpression; - } - } else { - lockExpression.lockExpression = - StringToJavaExpression.atPath(expression, path, checker); - return lockExpression; - } - } catch (JavaExpressionParseException ex) { - lockExpression.error = new DependentTypesError(expression, ex); - return lockExpression; + Matcher selfReceiverMatcher = SELF_RECEIVER_PATTERN.matcher(expression); + try { + if (selfReceiverMatcher.matches()) { + String remainingExpression = selfReceiverMatcher.group(2); + if (remainingExpression == null || remainingExpression.isEmpty()) { + lockExpression.lockExpression = itself; + if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { + checker.reportError( + path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression); + } + return lockExpression; + } else { + lockExpression.lockExpression = + StringToJavaExpression.atPath( + itself.toString() + "." + remainingExpression, path, checker); + if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { + checker.reportError( + path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression); + } + return lockExpression; } + } else { + lockExpression.lockExpression = StringToJavaExpression.atPath(expression, path, checker); + return lockExpression; + } + } catch (JavaExpressionParseException ex) { + lockExpression.error = new DependentTypesError(expression, ex); + return lockExpression; } + } - private static class LockExpression { - final String expressionString; - JavaExpression lockExpression = null; - DependentTypesError error = null; + private static class LockExpression { + final String expressionString; + JavaExpression lockExpression = null; + DependentTypesError error = null; - LockExpression(String expression) { - this.expressionString = expression; - } + LockExpression(String expression) { + this.expressionString = expression; } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java index a055574c6c7..7c949e9b8b8 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java @@ -1,8 +1,7 @@ package org.checkerframework.checker.mustcall; -import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; - import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; /** * This interface should be implemented by all type factories that can provide an {@link @@ -14,17 +13,17 @@ */ public interface CreatesMustCallForElementSupplier { - /** - * Returns the CreatesMustCallFor.value field/element. - * - * @return the CreatesMustCallFor.value field/element - */ - ExecutableElement getCreatesMustCallForValueElement(); + /** + * Returns the CreatesMustCallFor.value field/element. + * + * @return the CreatesMustCallFor.value field/element + */ + ExecutableElement getCreatesMustCallForValueElement(); - /** - * Returns the CreatesMustCallFor.List.value field/element. - * - * @return the CreatesMustCallFor.List.value field/element - */ - ExecutableElement getCreatesMustCallForListValueElement(); + /** + * Returns the CreatesMustCallFor.List.value field/element. + * + * @return the CreatesMustCallFor.List.value field/element + */ + ExecutableElement getCreatesMustCallForListValueElement(); } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java index 66eaeaf0d26..746fe2ef310 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java @@ -2,7 +2,11 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; - +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; @@ -14,197 +18,182 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.ArrayList; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; - /** * Utility methods to convert targets of {@code @CreatesMustCallFor} annotations to {@link * org.checkerframework.dataflow.expression.JavaExpression}s. */ public class CreatesMustCallForToJavaExpression { - /** static utility methods only; don't create instances */ - private CreatesMustCallForToJavaExpression() {} + /** static utility methods only; don't create instances */ + private CreatesMustCallForToJavaExpression() {} - /** - * Returns the elements of the @CreatesMustCallFor annotations on the invoked method, as - * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor - * annotation. - * - *

If any expression is unparseable, this method reports an error and returns the empty set. - * - * @param n a method invocation - * @param atypeFactory the type factory to report errors and parse the expression string - * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor - * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory - * again. The arguments are different so that the given type factory's adherence to both - * protocols are checked by the type system. - * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list - */ - public static List getCreatesMustCallForExpressionsAtInvocation( - MethodInvocationNode n, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier) { - List results = new ArrayList<>(1); - List createsMustCallForAnnos = - getCreatesMustCallForAnnos(n.getTarget().getMethod(), atypeFactory, supplier); - for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { - JavaExpression expr = - getCreatesMustCallForExpression( - createsMustCallFor, - n.getTree(), - n.getTarget().getMethod().getSimpleName(), - atypeFactory, - supplier, - (s) -> - StringToJavaExpression.atMethodInvocation( - s, n, atypeFactory.getChecker())); - if (expr != null && !results.contains(expr)) { - results.add(expr); - } - } - return results; + /** + * Returns the elements of the @CreatesMustCallFor annotations on the invoked method, as + * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor + * annotation. + * + *

If any expression is unparseable, this method reports an error and returns the empty set. + * + * @param n a method invocation + * @param atypeFactory the type factory to report errors and parse the expression string + * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor + * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory + * again. The arguments are different so that the given type factory's adherence to both + * protocols are checked by the type system. + * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list + */ + public static List getCreatesMustCallForExpressionsAtInvocation( + MethodInvocationNode n, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier) { + List results = new ArrayList<>(1); + List createsMustCallForAnnos = + getCreatesMustCallForAnnos(n.getTarget().getMethod(), atypeFactory, supplier); + for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { + JavaExpression expr = + getCreatesMustCallForExpression( + createsMustCallFor, + n.getTree(), + n.getTarget().getMethod().getSimpleName(), + atypeFactory, + supplier, + (s) -> StringToJavaExpression.atMethodInvocation(s, n, atypeFactory.getChecker())); + if (expr != null && !results.contains(expr)) { + results.add(expr); + } } + return results; + } - /** - * Returns the elements of the @CreatesMustCallFor annotations on the method declaration, as - * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor - * annotation. - * - *

If any expression is unparseable, this method reports an error and returns the empty set. - * - * @param tree a method declaration - * @param atypeFactory the type factory to report errors and parse the expression string - * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor - * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory - * again. The arguments are different so that the given type factory's adherence to both - * protocols are checked by the type system. - * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list - */ - public static List getCreatesMustCallForExpressionsAtMethodDeclaration( - MethodTree tree, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier) { - List results = new ArrayList<>(1); - ExecutableElement method = TreeUtils.elementFromDeclaration(tree); - List createsMustCallForAnnos = - getCreatesMustCallForAnnos(method, atypeFactory, supplier); - for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { - JavaExpression expr = - getCreatesMustCallForExpression( - createsMustCallFor, - tree, - method.getSimpleName(), - atypeFactory, - supplier, - (s) -> - StringToJavaExpression.atMethodBody( - s, tree, atypeFactory.getChecker())); - if (expr != null && !results.contains(expr)) { - results.add(expr); - } - } - return results; + /** + * Returns the elements of the @CreatesMustCallFor annotations on the method declaration, as + * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor + * annotation. + * + *

If any expression is unparseable, this method reports an error and returns the empty set. + * + * @param tree a method declaration + * @param atypeFactory the type factory to report errors and parse the expression string + * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor + * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory + * again. The arguments are different so that the given type factory's adherence to both + * protocols are checked by the type system. + * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list + */ + public static List getCreatesMustCallForExpressionsAtMethodDeclaration( + MethodTree tree, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier) { + List results = new ArrayList<>(1); + ExecutableElement method = TreeUtils.elementFromDeclaration(tree); + List createsMustCallForAnnos = + getCreatesMustCallForAnnos(method, atypeFactory, supplier); + for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { + JavaExpression expr = + getCreatesMustCallForExpression( + createsMustCallFor, + tree, + method.getSimpleName(), + atypeFactory, + supplier, + (s) -> StringToJavaExpression.atMethodBody(s, tree, atypeFactory.getChecker())); + if (expr != null && !results.contains(expr)) { + results.add(expr); + } } + return results; + } - /** - * Returns the {@code CreatesMustCallFor} annotations on a method - * - * @param method the method - * @param atypeFactory the type factory to use for looking up annotations - * @param supplier supplier to use to get elements - * @return the {@code CreatesMustCallFor} annotations - */ - private static List getCreatesMustCallForAnnos( - ExecutableElement method, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier) { - AnnotationMirror createsMustCallForList = - atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.List.class); - List result = new ArrayList<>(); - if (createsMustCallForList != null) { - // Handle a set of CreatesMustCallFor annotations. - result.addAll( - AnnotationUtils.getElementValueArray( - createsMustCallForList, - supplier.getCreatesMustCallForListValueElement(), - AnnotationMirror.class)); - } - AnnotationMirror createsMustCallFor = - atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.class); - if (createsMustCallFor != null) { - result.add(createsMustCallFor); - } - return result; + /** + * Returns the {@code CreatesMustCallFor} annotations on a method + * + * @param method the method + * @param atypeFactory the type factory to use for looking up annotations + * @param supplier supplier to use to get elements + * @return the {@code CreatesMustCallFor} annotations + */ + private static List getCreatesMustCallForAnnos( + ExecutableElement method, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier) { + AnnotationMirror createsMustCallForList = + atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.List.class); + List result = new ArrayList<>(); + if (createsMustCallForList != null) { + // Handle a set of CreatesMustCallFor annotations. + result.addAll( + AnnotationUtils.getElementValueArray( + createsMustCallForList, + supplier.getCreatesMustCallForListValueElement(), + AnnotationMirror.class)); } - - /** - * Parses a single CreatesMustCallFor annotation. If the target expression cannot be parsed, - * issues a checker error. - * - * @param createsMustCallFor a @CreatesMustCallFor annotation - * @param tree the tree on which to report an error if annotation cannot be parsed - * @param methodName name to use in error message if annotation cannot be parsed - * @param atypeFactory the type factory - * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor - * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory - * again. The arguments are different so that the given type factory's adherence to both - * protocols are checked by the type system. - * @param converter function to be used to create JavaExpression - * @return the Java expression representing the target, or null if the target is unparseable - */ - private static @Nullable JavaExpression getCreatesMustCallForExpression( - AnnotationMirror createsMustCallFor, - Tree tree, - Name methodName, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier, - StringToJavaExpression converter) { - // Unfortunately, there is no way to avoid passing the default string "this" here. The - // default must be hard-coded into the client, such as here. That is the price for the - // efficiency of not having to query the annotation definition (such queries are expensive). - String targetStrWithoutAdaptation = - AnnotationUtils.getElementValue( - createsMustCallFor, - supplier.getCreatesMustCallForValueElement(), - String.class, - "this"); - // TODO: find a way to also check if the target is a known tempvar, and if so return that. - // That should improve the quality of the error messages we give. - JavaExpression targetExpr; - try { - targetExpr = converter.toJavaExpression(targetStrWithoutAdaptation); - if (targetExpr instanceof Unknown) { - issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); - return null; - } - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); - return null; - } - return targetExpr; + AnnotationMirror createsMustCallFor = + atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.class); + if (createsMustCallFor != null) { + result.add(createsMustCallFor); } + return result; + } - /** - * Issues a createsmustcallfor.target.unparseable error. - * - * @param tree the tree on which to report the error - * @param methodName method name to use in error message - * @param atypeFactory the type factory to use to issue the error - * @param unparseable the unparseable string - */ - private static void issueUnparseableError( - Tree tree, - Name methodName, - GenericAnnotatedTypeFactory atypeFactory, - String unparseable) { - atypeFactory - .getChecker() - .reportError( - tree, "createsmustcallfor.target.unparseable", methodName, unparseable); + /** + * Parses a single CreatesMustCallFor annotation. If the target expression cannot be parsed, + * issues a checker error. + * + * @param createsMustCallFor a @CreatesMustCallFor annotation + * @param tree the tree on which to report an error if annotation cannot be parsed + * @param methodName name to use in error message if annotation cannot be parsed + * @param atypeFactory the type factory + * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor + * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory + * again. The arguments are different so that the given type factory's adherence to both + * protocols are checked by the type system. + * @param converter function to be used to create JavaExpression + * @return the Java expression representing the target, or null if the target is unparseable + */ + private static @Nullable JavaExpression getCreatesMustCallForExpression( + AnnotationMirror createsMustCallFor, + Tree tree, + Name methodName, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier, + StringToJavaExpression converter) { + // Unfortunately, there is no way to avoid passing the default string "this" here. The + // default must be hard-coded into the client, such as here. That is the price for the + // efficiency of not having to query the annotation definition (such queries are expensive). + String targetStrWithoutAdaptation = + AnnotationUtils.getElementValue( + createsMustCallFor, supplier.getCreatesMustCallForValueElement(), String.class, "this"); + // TODO: find a way to also check if the target is a known tempvar, and if so return that. + // That should improve the quality of the error messages we give. + JavaExpression targetExpr; + try { + targetExpr = converter.toJavaExpression(targetStrWithoutAdaptation); + if (targetExpr instanceof Unknown) { + issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); + return null; + } + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); + return null; } + return targetExpr; + } + + /** + * Issues a createsmustcallfor.target.unparseable error. + * + * @param tree the tree on which to report the error + * @param methodName method name to use in error message + * @param atypeFactory the type factory to use to issue the error + * @param unparseable the unparseable string + */ + private static void issueUnparseableError( + Tree tree, + Name methodName, + GenericAnnotatedTypeFactory atypeFactory, + String unparseable) { + atypeFactory + .getChecker() + .reportError(tree, "createsmustcallfor.target.unparseable", methodName, unparseable); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java index 904b933d58b..f3ad4bb5eed 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java @@ -7,7 +7,23 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; @@ -46,573 +62,541 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; - /** * The annotated type factory for the Must Call Checker. Primarily responsible for the subtyping * rules between @MustCall annotations. */ public class MustCallAnnotatedTypeFactory extends BaseAnnotatedTypeFactory - implements CreatesMustCallForElementSupplier { - - /** The {@code @}{@link MustCallUnknown} annotation. */ - public final AnnotationMirror TOP; - - /** - * The {@code @}{@link MustCall}{@code ()} annotation. It is the default in unannotated code. - */ - public final AnnotationMirror BOTTOM; - - /** The {@code @}{@link PolyMustCall} annotation. */ - public final AnnotationMirror POLY; - - /** - * Map from trees representing expressions to the temporary variables that represent them in the - * store. - * - *

Consider the following code, adapted from Apache Zookeeper: - * - *

-     *   sock = SocketChannel.open();
-     *   sock.socket().setSoLinger(false, -1);
-     * 
- * - * This code is safe from resource leaks: sock is an unconnected socket and therefore has no - * must-call obligation. The expression sock.socket() similarly has no must-call obligation - * because it is a resource alias, but without a temporary variable that represents that - * expression in the store, the resource leak checker wouldn't be able to determine that. - * - *

These temporary variables are only created once---here---but are used by all three parts - * of the resource leak checker by calling {@link #getTempVar(Node)}. The temporary variables - * are shared in the same way that subcheckers share CFG structure; see {@link - * #getSharedCFGForTree(Tree)}. - */ - /*package-private*/ final IdentityHashMap tempVars = - new IdentityHashMap<>(100); - - /** The MustCall.value field/element. */ - private final ExecutableElement mustCallValueElement = - TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); - - /** The InheritableMustCall.value field/element. */ - /*package-private*/ final ExecutableElement inheritableMustCallValueElement = - TreeUtils.getMethod(InheritableMustCall.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.List.value field/element. */ - private final ExecutableElement createsMustCallForListValueElement = - TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.value field/element. */ - private final ExecutableElement createsMustCallForValueElement = - TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); - - /** True if -AnoLightweightOwnership was passed on the command line. */ - private final boolean noLightweightOwnership; - - /* NO-AFU - * True if -AenableWpiForRlc (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) was passed on - * the command line. - * - private final boolean enableWpiForRlc; - */ - - /** - * Creates a MustCallAnnotatedTypeFactory. - * - * @param checker the checker associated with this type factory - */ - public MustCallAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - TOP = AnnotationBuilder.fromClass(elements, MustCallUnknown.class); - BOTTOM = createMustCall(Collections.emptyList()); - POLY = AnnotationBuilder.fromClass(elements, PolyMustCall.class); - addAliasedTypeAnnotation(InheritableMustCall.class, MustCall.class, true); - if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) { - // In NO_RESOURCE_ALIASES mode, all @MustCallAlias annotations are ignored. - addAliasedTypeAnnotation(MustCallAlias.class, POLY); - } - noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); - // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); - this.postInit(); + implements CreatesMustCallForElementSupplier { + + /** The {@code @}{@link MustCallUnknown} annotation. */ + public final AnnotationMirror TOP; + + /** The {@code @}{@link MustCall}{@code ()} annotation. It is the default in unannotated code. */ + public final AnnotationMirror BOTTOM; + + /** The {@code @}{@link PolyMustCall} annotation. */ + public final AnnotationMirror POLY; + + /** + * Map from trees representing expressions to the temporary variables that represent them in the + * store. + * + *

Consider the following code, adapted from Apache Zookeeper: + * + *

+   *   sock = SocketChannel.open();
+   *   sock.socket().setSoLinger(false, -1);
+   * 
+ * + * This code is safe from resource leaks: sock is an unconnected socket and therefore has no + * must-call obligation. The expression sock.socket() similarly has no must-call obligation + * because it is a resource alias, but without a temporary variable that represents that + * expression in the store, the resource leak checker wouldn't be able to determine that. + * + *

These temporary variables are only created once---here---but are used by all three parts of + * the resource leak checker by calling {@link #getTempVar(Node)}. The temporary variables are + * shared in the same way that subcheckers share CFG structure; see {@link + * #getSharedCFGForTree(Tree)}. + */ + /*package-private*/ final IdentityHashMap tempVars = + new IdentityHashMap<>(100); + + /** The MustCall.value field/element. */ + private final ExecutableElement mustCallValueElement = + TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); + + /** The InheritableMustCall.value field/element. */ + /*package-private*/ final ExecutableElement inheritableMustCallValueElement = + TreeUtils.getMethod(InheritableMustCall.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.List.value field/element. */ + private final ExecutableElement createsMustCallForListValueElement = + TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.value field/element. */ + private final ExecutableElement createsMustCallForValueElement = + TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); + + /** True if -AnoLightweightOwnership was passed on the command line. */ + private final boolean noLightweightOwnership; + + /* NO-AFU + * True if -AenableWpiForRlc (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) was passed on + * the command line. + * + private final boolean enableWpiForRlc; + */ + + /** + * Creates a MustCallAnnotatedTypeFactory. + * + * @param checker the checker associated with this type factory + */ + public MustCallAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + TOP = AnnotationBuilder.fromClass(elements, MustCallUnknown.class); + BOTTOM = createMustCall(Collections.emptyList()); + POLY = AnnotationBuilder.fromClass(elements, PolyMustCall.class); + addAliasedTypeAnnotation(InheritableMustCall.class, MustCall.class, true); + if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) { + // In NO_RESOURCE_ALIASES mode, all @MustCallAlias annotations are ignored. + addAliasedTypeAnnotation(MustCallAlias.class, POLY); } - - @Override - public void setRoot(@Nullable CompilationUnitTree root) { - super.setRoot(root); - // TODO: This should probably be guarded by isSafeToClearSharedCFG from - // GenericAnnotatedTypeFactory, but this works here because we know the Must Call Checker is - // always the first subchecker that's sharing tempvars. - tempVars.clear(); + noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); + // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + this.postInit(); + } + + @Override + public void setRoot(@Nullable CompilationUnitTree root) { + super.setRoot(root); + // TODO: This should probably be guarded by isSafeToClearSharedCFG from + // GenericAnnotatedTypeFactory, but this works here because we know the Must Call Checker is + // always the first subchecker that's sharing tempvars. + tempVars.clear(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Explicitly name the qualifiers, in order to exclude @MustCallAlias. + return new LinkedHashSet<>( + Arrays.asList(MustCall.class, MustCallUnknown.class, PolyMustCall.class)); + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new MustCallTreeAnnotator(this)); + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(super.createTypeAnnotator(), new MustCallTypeAnnotator(this)); + } + + /** + * Returns a {@literal @}MustCall annotation that is like the input, but it does not have "close". + * Returns the argument annotation mirror (not a new one) if the argument doesn't have "close" as + * one of its elements. + * + *

If the argument is null, returns bottom. + * + * @param anno a MustCall annotation + * @return a MustCall annotation that does not have "close" as one of its values, but is otherwise + * identical to anno + */ + public AnnotationMirror withoutClose(@Nullable AnnotationMirror anno) { + if (anno == null || AnnotationUtils.areSame(anno, BOTTOM)) { + return BOTTOM; + } else if (!AnnotationUtils.areSameByName( + anno, "org.checkerframework.checker.mustcall.qual.MustCall")) { + return anno; } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Explicitly name the qualifiers, in order to exclude @MustCallAlias. - return new LinkedHashSet<>( - Arrays.asList(MustCall.class, MustCallUnknown.class, PolyMustCall.class)); + List values = + AnnotationUtils.getElementValueArray(anno, mustCallValueElement, String.class); + // Use `removeAll` because `remove` only removes the first occurrence. + if (values.removeAll(Collections.singletonList("close"))) { + return createMustCall(values); + } else { + return anno; } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new MustCallTreeAnnotator(this)); + } + + /** Treat non-owning method parameters as @MustCallUnknown (top) when the method is called. */ + @Override + public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { + ExecutableElement declaration; + if (tree instanceof MethodInvocationTree) { + declaration = TreeUtils.elementFromUse((MethodInvocationTree) tree); + } else if (tree instanceof MemberReferenceTree) { + declaration = (ExecutableElement) TreeUtils.elementFromUse(tree); + } else { + throw new TypeSystemError("unexpected type of method tree: " + tree.getKind()); } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(super.createTypeAnnotator(), new MustCallTypeAnnotator(this)); - } - + changeNonOwningParameterTypesToTop(declaration, type); + super.methodFromUsePreSubstitution(tree, type); + } + + @Override + protected void constructorFromUsePreSubstitution( + NewClassTree tree, AnnotatedExecutableType type) { + ExecutableElement declaration = TreeUtils.elementFromUse(tree); + changeNonOwningParameterTypesToTop(declaration, type); + super.constructorFromUsePreSubstitution(tree, type); + } + + /** + * Class to implement the customized semantics of {@link MustCallAlias} (and {@link PolyMustCall}) + * annotations; see the {@link MustCallAlias} documentation for details. + */ + private class MustCallQualifierPolymorphism extends DefaultQualifierPolymorphism { /** - * Returns a {@literal @}MustCall annotation that is like the input, but it does not have - * "close". Returns the argument annotation mirror (not a new one) if the argument doesn't have - * "close" as one of its elements. - * - *

If the argument is null, returns bottom. + * Creates a {@link MustCallQualifierPolymorphism}. * - * @param anno a MustCall annotation - * @return a MustCall annotation that does not have "close" as one of its values, but is - * otherwise identical to anno + * @param env the processing environment + * @param factory the factory for the current checker */ - public AnnotationMirror withoutClose(@Nullable AnnotationMirror anno) { - if (anno == null || AnnotationUtils.areSame(anno, BOTTOM)) { - return BOTTOM; - } else if (!AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.mustcall.qual.MustCall")) { - return anno; - } - List values = - AnnotationUtils.getElementValueArray(anno, mustCallValueElement, String.class); - // Use `removeAll` because `remove` only removes the first occurrence. - if (values.removeAll(Collections.singletonList("close"))) { - return createMustCall(values); - } else { - return anno; - } - } - - /** Treat non-owning method parameters as @MustCallUnknown (top) when the method is called. */ - @Override - public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { - ExecutableElement declaration; - if (tree instanceof MethodInvocationTree) { - declaration = TreeUtils.elementFromUse((MethodInvocationTree) tree); - } else if (tree instanceof MemberReferenceTree) { - declaration = (ExecutableElement) TreeUtils.elementFromUse(tree); - } else { - throw new TypeSystemError("unexpected type of method tree: " + tree.getKind()); - } - changeNonOwningParameterTypesToTop(declaration, type); - super.methodFromUsePreSubstitution(tree, type); + public MustCallQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { + super(env, factory); } @Override - protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type) { - ExecutableElement declaration = TreeUtils.elementFromUse(tree); - changeNonOwningParameterTypesToTop(declaration, type); - super.constructorFromUsePreSubstitution(tree, type); - } - - /** - * Class to implement the customized semantics of {@link MustCallAlias} (and {@link - * PolyMustCall}) annotations; see the {@link MustCallAlias} documentation for details. - */ - private class MustCallQualifierPolymorphism extends DefaultQualifierPolymorphism { - /** - * Creates a {@link MustCallQualifierPolymorphism}. - * - * @param env the processing environment - * @param factory the factory for the current checker - */ - public MustCallQualifierPolymorphism( - ProcessingEnvironment env, AnnotatedTypeFactory factory) { - super(env, factory); - } - - @Override - protected void replace( - AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { - AnnotationMirrorMap realReplacements = replacements; - AnnotationMirror extantPolyAnnoReplacement = null; - TypeElement typeElement = TypesUtils.getTypeElement(type.getUnderlyingType()); - // only customize replacement for type elements - if (typeElement != null) { - assert replacements.size() == 1 && replacements.containsKey(POLY); - extantPolyAnnoReplacement = replacements.get(POLY); - if (AnnotationUtils.areSameByName( - extantPolyAnnoReplacement, MustCall.class.getCanonicalName())) { - List extentReplacementVals = - AnnotationUtils.getElementValueArray( - extantPolyAnnoReplacement, - getMustCallValueElement(), - String.class, - Collections.emptyList()); - // replacement only customized when parameter type has a non-empty must-call - // obligation - if (!extentReplacementVals.isEmpty()) { - AnnotationMirror inheritableMustCall = - getDeclAnnotation(typeElement, InheritableMustCall.class); - if (inheritableMustCall != null) { - List inheritableMustCallVals = - AnnotationUtils.getElementValueArray( - inheritableMustCall, - inheritableMustCallValueElement, - String.class, - Collections.emptyList()); - if (!inheritableMustCallVals.equals(extentReplacementVals)) { - // Use the must call values from the @InheritableMustCall annotation - // instead. - // This allows for wrapper types to have a must-call method with a - // different name than the must-call method for the wrapped type - AnnotationMirror mustCall = createMustCall(inheritableMustCallVals); - realReplacements = new AnnotationMirrorMap<>(); - realReplacements.put(POLY, mustCall); - } - } - } - } + protected void replace( + AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { + AnnotationMirrorMap realReplacements = replacements; + AnnotationMirror extantPolyAnnoReplacement = null; + TypeElement typeElement = TypesUtils.getTypeElement(type.getUnderlyingType()); + // only customize replacement for type elements + if (typeElement != null) { + assert replacements.size() == 1 && replacements.containsKey(POLY); + extantPolyAnnoReplacement = replacements.get(POLY); + if (AnnotationUtils.areSameByName( + extantPolyAnnoReplacement, MustCall.class.getCanonicalName())) { + List extentReplacementVals = + AnnotationUtils.getElementValueArray( + extantPolyAnnoReplacement, + getMustCallValueElement(), + String.class, + Collections.emptyList()); + // replacement only customized when parameter type has a non-empty must-call + // obligation + if (!extentReplacementVals.isEmpty()) { + AnnotationMirror inheritableMustCall = + getDeclAnnotation(typeElement, InheritableMustCall.class); + if (inheritableMustCall != null) { + List inheritableMustCallVals = + AnnotationUtils.getElementValueArray( + inheritableMustCall, + inheritableMustCallValueElement, + String.class, + Collections.emptyList()); + if (!inheritableMustCallVals.equals(extentReplacementVals)) { + // Use the must call values from the @InheritableMustCall annotation + // instead. + // This allows for wrapper types to have a must-call method with a + // different name than the must-call method for the wrapped type + AnnotationMirror mustCall = createMustCall(inheritableMustCallVals); + realReplacements = new AnnotationMirrorMap<>(); + realReplacements.put(POLY, mustCall); + } } - super.replace(type, realReplacements); + } } + } + super.replace(type, realReplacements); } - - @Override - protected QualifierPolymorphism createQualifierPolymorphism() { - return new MustCallQualifierPolymorphism(processingEnv, this); + } + + @Override + protected QualifierPolymorphism createQualifierPolymorphism() { + return new MustCallQualifierPolymorphism(processingEnv, this); + } + + /** + * Changes the type of each parameter not annotated as @Owning to @MustCallUnknown (top). Also + * replaces the component type of the varargs array, if applicable. + * + *

This method is not responsible for handling receivers, which can never be owning. + * + * @param declaration a method or constructor declaration + * @param type the method or constructor's type + */ + private void changeNonOwningParameterTypesToTop( + ExecutableElement declaration, AnnotatedExecutableType type) { + // Formal parameters without a declared owning annotation are disregarded by the RLC + // _analysis_, as their @MustCall obligation is set to Top in this method. However, this + // computation is not desirable for RLC _inference_ in unannotated programs, where a goal is + // to infer and add @Owning annotations to formal parameters. + /* NO-AFU + if (getWholeProgramInference() != null && !isWpiEnabledForRLC()) { + return; } - - /** - * Changes the type of each parameter not annotated as @Owning to @MustCallUnknown (top). Also - * replaces the component type of the varargs array, if applicable. - * - *

This method is not responsible for handling receivers, which can never be owning. - * - * @param declaration a method or constructor declaration - * @param type the method or constructor's type - */ - private void changeNonOwningParameterTypesToTop( - ExecutableElement declaration, AnnotatedExecutableType type) { - // Formal parameters without a declared owning annotation are disregarded by the RLC - // _analysis_, as their @MustCall obligation is set to Top in this method. However, this - // computation is not desirable for RLC _inference_ in unannotated programs, where a goal is - // to infer and add @Owning annotations to formal parameters. - /* NO-AFU - if (getWholeProgramInference() != null && !isWpiEnabledForRLC()) { - return; - } - */ - List parameterTypes = type.getParameterTypes(); - for (int i = 0; i < parameterTypes.size(); i++) { - Element paramDecl = declaration.getParameters().get(i); - if (noLightweightOwnership || getDeclAnnotation(paramDecl, Owning.class) == null) { - AnnotatedTypeMirror paramType = parameterTypes.get(i); - if (!paramType.hasAnnotation(POLY)) { - paramType.replaceAnnotation(TOP); - } - } - } - if (declaration.isVarArgs()) { - // also modify the component type of a varargs array - AnnotatedTypeMirror varargsType = - ((AnnotatedArrayType) parameterTypes.get(parameterTypes.size() - 1)) - .getComponentType(); - if (!varargsType.hasAnnotation(POLY)) { - varargsType.replaceAnnotation(TOP); - } + */ + List parameterTypes = type.getParameterTypes(); + for (int i = 0; i < parameterTypes.size(); i++) { + Element paramDecl = declaration.getParameters().get(i); + if (noLightweightOwnership || getDeclAnnotation(paramDecl, Owning.class) == null) { + AnnotatedTypeMirror paramType = parameterTypes.get(i); + if (!paramType.hasAnnotation(POLY)) { + paramType.replaceAnnotation(TOP); } + } } - - @Override - protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { - return new MustCallDefaultQualifierForUseTypeAnnotator(); - } - - /** - * Returns the {@link MustCall#value} element. For use with {@link - * AnnotationUtils#getElementValueArray}. - * - * @return the {@link MustCall#value} element - */ - public ExecutableElement getMustCallValueElement() { - return mustCallValueElement; + if (declaration.isVarArgs()) { + // also modify the component type of a varargs array + AnnotatedTypeMirror varargsType = + ((AnnotatedArrayType) parameterTypes.get(parameterTypes.size() - 1)).getComponentType(); + if (!varargsType.hasAnnotation(POLY)) { + varargsType.replaceAnnotation(TOP); + } } - - /** Support @InheritableMustCall meaning @MustCall on all subtype elements. */ - private class MustCallDefaultQualifierForUseTypeAnnotator - extends DefaultQualifierForUseTypeAnnotator { - - /** Creates a {@code MustCallDefaultQualifierForUseTypeAnnotator}. */ - public MustCallDefaultQualifierForUseTypeAnnotator() { - super(MustCallAnnotatedTypeFactory.this); - } - - @Override - protected AnnotationMirrorSet getExplicitAnnos(Element element) { - AnnotationMirrorSet explict = super.getExplicitAnnos(element); - if (explict.isEmpty() && ElementUtils.isTypeElement(element)) { - AnnotationMirror inheritableMustCall = - getDeclAnnotation(element, InheritableMustCall.class); - if (inheritableMustCall != null) { - List mustCallVal = - AnnotationUtils.getElementValueArray( - inheritableMustCall, - inheritableMustCallValueElement, - String.class); - return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); - } - } - return explict; - } + } + + @Override + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new MustCallDefaultQualifierForUseTypeAnnotator(); + } + + /** + * Returns the {@link MustCall#value} element. For use with {@link + * AnnotationUtils#getElementValueArray}. + * + * @return the {@link MustCall#value} element + */ + public ExecutableElement getMustCallValueElement() { + return mustCallValueElement; + } + + /** Support @InheritableMustCall meaning @MustCall on all subtype elements. */ + private class MustCallDefaultQualifierForUseTypeAnnotator + extends DefaultQualifierForUseTypeAnnotator { + + /** Creates a {@code MustCallDefaultQualifierForUseTypeAnnotator}. */ + public MustCallDefaultQualifierForUseTypeAnnotator() { + super(MustCallAnnotatedTypeFactory.this); } @Override - protected QualifierUpperBounds createQualifierUpperBounds() { - return new MustCallQualifierUpperBounds(); - } - - /** Support @InheritableMustCall meaning @MustCall on all subtypes. */ - private class MustCallQualifierUpperBounds extends QualifierUpperBounds { - - /** - * Creates a {@link QualifierUpperBounds} from the MustCall Checker the annotations that are - * in the type hierarchy. - */ - public MustCallQualifierUpperBounds() { - super(MustCallAnnotatedTypeFactory.this); - } - - @Override - protected AnnotationMirrorSet getAnnotationFromElement(Element element) { - AnnotationMirrorSet explict = super.getAnnotationFromElement(element); - if (!explict.isEmpty()) { - return explict; - } - AnnotationMirror inheritableMustCall = - getDeclAnnotation(element, InheritableMustCall.class); - if (inheritableMustCall != null) { - List mustCallVal = - AnnotationUtils.getElementValueArray( - inheritableMustCall, inheritableMustCallValueElement, String.class); - return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); - } - return AnnotationMirrorSet.emptySet(); + protected AnnotationMirrorSet getExplicitAnnos(Element element) { + AnnotationMirrorSet explict = super.getExplicitAnnos(element); + if (explict.isEmpty() && ElementUtils.isTypeElement(element)) { + AnnotationMirror inheritableMustCall = + getDeclAnnotation(element, InheritableMustCall.class); + if (inheritableMustCall != null) { + List mustCallVal = + AnnotationUtils.getElementValueArray( + inheritableMustCall, inheritableMustCallValueElement, String.class); + return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); } + } + return explict; } + } - /** - * Cache of the MustCall annotations that have actually been created. Most programs require few - * distinct MustCall annotations (e.g. MustCall() and MustCall("close")). - */ - private final Map, AnnotationMirror> mustCallAnnotations = new HashMap<>(10); + @Override + protected QualifierUpperBounds createQualifierUpperBounds() { + return new MustCallQualifierUpperBounds(); + } - /** - * Creates a {@link MustCall} annotation whose values are the given strings. - * - * @param val the methods that should be called - * @return an annotation indicating that the given methods should be called - */ - public AnnotationMirror createMustCall(List val) { - return mustCallAnnotations.computeIfAbsent(val, this::createMustCallImpl); - } + /** Support @InheritableMustCall meaning @MustCall on all subtypes. */ + private class MustCallQualifierUpperBounds extends QualifierUpperBounds { /** - * Creates a {@link MustCall} annotation whose values are the given strings. - * - *

This internal version bypasses the cache, and is only used for new annotations. - * - * @param methodList the methods that should be called - * @return an annotation indicating that the given methods should be called + * Creates a {@link QualifierUpperBounds} from the MustCall Checker the annotations that are in + * the type hierarchy. */ - private AnnotationMirror createMustCallImpl(List methodList) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MustCall.class); - String[] methodArray = methodList.toArray(new String[methodList.size()]); - Arrays.sort(methodArray); - builder.setValue("value", methodArray); - return builder.build(); + public MustCallQualifierUpperBounds() { + super(MustCallAnnotatedTypeFactory.this); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new MustCallQualifierHierarchy( - this.getSupportedTypeQualifiers(), this.getProcessingEnv(), this); + protected AnnotationMirrorSet getAnnotationFromElement(Element element) { + AnnotationMirrorSet explict = super.getAnnotationFromElement(element); + if (!explict.isEmpty()) { + return explict; + } + AnnotationMirror inheritableMustCall = getDeclAnnotation(element, InheritableMustCall.class); + if (inheritableMustCall != null) { + List mustCallVal = + AnnotationUtils.getElementValueArray( + inheritableMustCall, inheritableMustCallValueElement, String.class); + return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); + } + return AnnotationMirrorSet.emptySet(); } - + } + + /** + * Cache of the MustCall annotations that have actually been created. Most programs require few + * distinct MustCall annotations (e.g. MustCall() and MustCall("close")). + */ + private final Map, AnnotationMirror> mustCallAnnotations = new HashMap<>(10); + + /** + * Creates a {@link MustCall} annotation whose values are the given strings. + * + * @param val the methods that should be called + * @return an annotation indicating that the given methods should be called + */ + public AnnotationMirror createMustCall(List val) { + return mustCallAnnotations.computeIfAbsent(val, this::createMustCallImpl); + } + + /** + * Creates a {@link MustCall} annotation whose values are the given strings. + * + *

This internal version bypasses the cache, and is only used for new annotations. + * + * @param methodList the methods that should be called + * @return an annotation indicating that the given methods should be called + */ + private AnnotationMirror createMustCallImpl(List methodList) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MustCall.class); + String[] methodArray = methodList.toArray(new String[methodList.size()]); + Arrays.sort(methodArray); + builder.setValue("value", methodArray); + return builder.build(); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new MustCallQualifierHierarchy( + this.getSupportedTypeQualifiers(), this.getProcessingEnv(), this); + } + + /** + * Fetches the store from the results of dataflow for {@code first}. If {@code afterFirstStore} is + * true, then the store after {@code first} is returned; if {@code afterFirstStore} is false, the + * store before {@code succ} is returned. + * + * @param afterFirstStore whether to use the store after the first block or the store before its + * successor, succ + * @param first a block + * @param succ first's successor + * @return the appropriate CFStore, populated with MustCall annotations, from the results of + * running dataflow + */ + public CFStore getStoreForBlock(boolean afterFirstStore, Block first, Block succ) { + return afterFirstStore ? flowResult.getStoreAfter(first) : flowResult.getStoreBefore(succ); + } + + /** + * Returns the CreatesMustCallFor.value field/element. + * + * @return the CreatesMustCallFor.value field/element + */ + @Override + public ExecutableElement getCreatesMustCallForValueElement() { + return createsMustCallForValueElement; + } + + /** + * Returns the CreatesMustCallFor.List.value field/element. + * + * @return the CreatesMustCallFor.List.value field/element + */ + @Override + public ExecutableElement getCreatesMustCallForListValueElement() { + return createsMustCallForListValueElement; + } + + /** + * The TreeAnnotator for the MustCall type system. + * + *

This tree annotator treats non-owning method parameters as bottom, regardless of their + * declared type, when they appear in the body of the method. Doing so is safe because being + * non-owning means, by definition, that their must-call obligations are only relevant in the + * callee. (This behavior is disabled if the {@code -AnoLightweightOwnership} option is passed to + * the checker.) + * + *

The tree annotator also changes the type of resource variables to remove "close" from their + * must-call types, because the try-with-resources statement guarantees that close() is called on + * all such variables. + */ + private class MustCallTreeAnnotator extends TreeAnnotator { /** - * Fetches the store from the results of dataflow for {@code first}. If {@code afterFirstStore} - * is true, then the store after {@code first} is returned; if {@code afterFirstStore} is false, - * the store before {@code succ} is returned. + * Create a MustCallTreeAnnotator. * - * @param afterFirstStore whether to use the store after the first block or the store before its - * successor, succ - * @param first a block - * @param succ first's successor - * @return the appropriate CFStore, populated with MustCall annotations, from the results of - * running dataflow + * @param mustCallAnnotatedTypeFactory the type factory */ - public CFStore getStoreForBlock(boolean afterFirstStore, Block first, Block succ) { - return afterFirstStore ? flowResult.getStoreAfter(first) : flowResult.getStoreBefore(succ); + public MustCallTreeAnnotator(MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory) { + super(mustCallAnnotatedTypeFactory); } - /** - * Returns the CreatesMustCallFor.value field/element. - * - * @return the CreatesMustCallFor.value field/element - */ @Override - public ExecutableElement getCreatesMustCallForValueElement() { - return createsMustCallForValueElement; - } - - /** - * Returns the CreatesMustCallFor.List.value field/element. - * - * @return the CreatesMustCallFor.List.value field/element - */ - @Override - public ExecutableElement getCreatesMustCallForListValueElement() { - return createsMustCallForListValueElement; - } - - /** - * The TreeAnnotator for the MustCall type system. - * - *

This tree annotator treats non-owning method parameters as bottom, regardless of their - * declared type, when they appear in the body of the method. Doing so is safe because being - * non-owning means, by definition, that their must-call obligations are only relevant in the - * callee. (This behavior is disabled if the {@code -AnoLightweightOwnership} option is passed - * to the checker.) - * - *

The tree annotator also changes the type of resource variables to remove "close" from - * their must-call types, because the try-with-resources statement guarantees that close() is - * called on all such variables. - */ - private class MustCallTreeAnnotator extends TreeAnnotator { - /** - * Create a MustCallTreeAnnotator. - * - * @param mustCallAnnotatedTypeFactory the type factory - */ - public MustCallTreeAnnotator(MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory) { - super(mustCallAnnotatedTypeFactory); - } - - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - Element elt = TreeUtils.elementFromUse(tree); - // The following changes are not desired for RLC _inference_ in unannotated programs, - // where a goal is to infer and add @Owning annotations to formal parameters. Therefore, - // if WPI is enabled, they should not be executed. - if ( // NO-AFU getWholeProgramInference() == null - elt.getKind() == ElementKind.PARAMETER - && (noLightweightOwnership || getDeclAnnotation(elt, Owning.class) == null)) { - if (!type.hasAnnotation(POLY)) { - // Parameters that are not annotated with @Owning should be treated as bottom - // (to suppress warnings about them). An exception is polymorphic parameters, - // which might be @MustCallAlias (and so wouldn't be annotated with @Owning): - // these are not modified, to support verification of @MustCallAlias - // annotations. - type.replaceAnnotation(BOTTOM); - } - } - return super.visitIdentifier(tree, type); + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + Element elt = TreeUtils.elementFromUse(tree); + // The following changes are not desired for RLC _inference_ in unannotated programs, + // where a goal is to infer and add @Owning annotations to formal parameters. Therefore, + // if WPI is enabled, they should not be executed. + if ( // NO-AFU getWholeProgramInference() == null + elt.getKind() == ElementKind.PARAMETER + && (noLightweightOwnership || getDeclAnnotation(elt, Owning.class) == null)) { + if (!type.hasAnnotation(POLY)) { + // Parameters that are not annotated with @Owning should be treated as bottom + // (to suppress warnings about them). An exception is polymorphic parameters, + // which might be @MustCallAlias (and so wouldn't be annotated with @Owning): + // these are not modified, to support verification of @MustCallAlias + // annotations. + type.replaceAnnotation(BOTTOM); } + } + return super.visitIdentifier(tree, type); } + } + + /** + * Return the temporary variable for node, if it exists. See {@code #tempVars}. + * + * @param node a CFG node + * @return the corresponding temporary variable, or null if there is not one + */ + public @Nullable LocalVariableNode getTempVar(Node node) { + return tempVars.get(node.getTree()); + } + + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; + } + */ + + /** + * Returns true if the given type should never have a must-call obligation. + * + * @param type the type to check + * @return true if the given type should never have a must-call obligation + */ + public boolean shouldHaveNoMustCallObligation(TypeMirror type) { + return type.getKind().isPrimitive() || TypesUtils.isClass(type) || TypesUtils.isString(type); + } + + /** Qualifier hierarchy for the Must Call Checker. */ + private class MustCallQualifierHierarchy extends SubtypeIsSubsetQualifierHierarchy { /** - * Return the temporary variable for node, if it exists. See {@code #tempVars}. + * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. * - * @param node a CFG node - * @return the corresponding temporary variable, or null if there is not one + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param processingEnv processing environment + * @param atypeFactory the associated type factory */ - public @Nullable LocalVariableNode getTempVar(Node node) { - return tempVars.get(node.getTree()); + public MustCallQualifierHierarchy( + Collection> qualifierClasses, + ProcessingEnvironment processingEnv, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, processingEnv, atypeFactory); } - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; - } - */ - - /** - * Returns true if the given type should never have a must-call obligation. - * - * @param type the type to check - * @return true if the given type should never have a must-call obligation - */ - public boolean shouldHaveNoMustCallObligation(TypeMirror type) { - return type.getKind().isPrimitive() - || TypesUtils.isClass(type) - || TypesUtils.isString(type); + @Override + public boolean isSubtypeShallow( + AnnotationMirror subQualifier, + TypeMirror subType, + AnnotationMirror superQualifier, + TypeMirror superType) { + if (shouldHaveNoMustCallObligation(subType) || shouldHaveNoMustCallObligation(superType)) { + return true; + } + return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); } - /** Qualifier hierarchy for the Must Call Checker. */ - private class MustCallQualifierHierarchy extends SubtypeIsSubsetQualifierHierarchy { - - /** - * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param processingEnv processing environment - * @param atypeFactory the associated type factory - */ - public MustCallQualifierHierarchy( - Collection> qualifierClasses, - ProcessingEnvironment processingEnv, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, processingEnv, atypeFactory); - } - - @Override - public boolean isSubtypeShallow( - AnnotationMirror subQualifier, - TypeMirror subType, - AnnotationMirror superQualifier, - TypeMirror superType) { - if (shouldHaveNoMustCallObligation(subType) - || shouldHaveNoMustCallObligation(superType)) { - return true; - } - return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); - } - - @Override - public @Nullable AnnotationMirror leastUpperBoundShallow( - AnnotationMirror qualifier1, - TypeMirror tm1, - AnnotationMirror qualifier2, - TypeMirror tm2) { - boolean tm1NoMustCall = shouldHaveNoMustCallObligation(tm1); - boolean tm2NoMustCall = shouldHaveNoMustCallObligation(tm2); - if (tm1NoMustCall == tm2NoMustCall) { - return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); - } else if (tm1NoMustCall) { - return qualifier1; - } else { // if (tm2NoMustCall) { - return qualifier2; - } - } + @Override + public @Nullable AnnotationMirror leastUpperBoundShallow( + AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { + boolean tm1NoMustCall = shouldHaveNoMustCallObligation(tm1); + boolean tm2NoMustCall = shouldHaveNoMustCallObligation(tm2); + if (tm1NoMustCall == tm2NoMustCall) { + return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); + } else if (tm1NoMustCall) { + return qualifier1; + } else { // if (tm2NoMustCall) { + return qualifier2; + } } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java index 85265b53d55..617fe571db6 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java @@ -10,33 +10,33 @@ * another. The Resource Leak Checker verifies that the given methods are actually called. */ @StubFiles({ - "IOUtils.astub", - "JavaEE.astub", - "JdkCompiler.astub", - "Reflection.astub", - "SocketCreatesMustCallFor.astub", + "IOUtils.astub", + "JavaEE.astub", + "JdkCompiler.astub", + "Reflection.astub", + "SocketCreatesMustCallFor.astub", }) @SupportedOptions({ - MustCallChecker.NO_CREATES_MUSTCALLFOR, - MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, - MustCallChecker.NO_RESOURCE_ALIASES + MustCallChecker.NO_CREATES_MUSTCALLFOR, + MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, + MustCallChecker.NO_RESOURCE_ALIASES }) public class MustCallChecker extends BaseTypeChecker { - /** - * Disables @CreatesMustCallFor support. Not of interest to most users. Not documented in the - * manual. - */ - public static final String NO_CREATES_MUSTCALLFOR = "noCreatesMustCallFor"; + /** + * Disables @CreatesMustCallFor support. Not of interest to most users. Not documented in the + * manual. + */ + public static final String NO_CREATES_MUSTCALLFOR = "noCreatesMustCallFor"; - /** - * Disables @Owning/@NotOwning support. Not of interest to most users. Not documented in the - * manual. - */ - public static final String NO_LIGHTWEIGHT_OWNERSHIP = "noLightweightOwnership"; + /** + * Disables @Owning/@NotOwning support. Not of interest to most users. Not documented in the + * manual. + */ + public static final String NO_LIGHTWEIGHT_OWNERSHIP = "noLightweightOwnership"; - /** - * Disables @MustCallAlias support. Not of interest to most users. Not documented in the manual. - */ - public static final String NO_RESOURCE_ALIASES = "noResourceAliases"; + /** + * Disables @MustCallAlias support. Not of interest to most users. Not documented in the manual. + */ + public static final String NO_RESOURCE_ALIASES = "noResourceAliases"; } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java index 53b04aade45..6affabc60f1 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java @@ -11,15 +11,15 @@ *

The only difference is the contents of the @StubFiles annotation. */ @StubFiles({ - "JavaEE.astub", - "Reflection.astub", + "JavaEE.astub", + "Reflection.astub", }) @SuppressWarningsPrefix({ - // Preferred checkername, so that warnings are suppressed regardless of the option passed. - "mustcall", - // Also supported, but will only suppress warnings from this checker (and not from the regular - // Must Call Checker). - "mustcallnocreatesmustcallfor" + // Preferred checkername, so that warnings are suppressed regardless of the option passed. + "mustcall", + // Also supported, but will only suppress warnings from this checker (and not from the regular + // Must Call Checker). + "mustcallnocreatesmustcallfor" }) @SupportedOptions({MustCallChecker.NO_CREATES_MUSTCALLFOR}) public class MustCallNoCreatesMustCallForChecker extends MustCallChecker {} diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java index cc74d571f91..baea3862d71 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java @@ -5,7 +5,12 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.TransferInput; @@ -27,14 +32,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.trees.TreeBuilder; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeMirror; - /** * Transfer function for the must-call type system. Its primary purposes are (1) to create temporary * variables for expressions (which allow those expressions to have refined information in the @@ -43,299 +40,297 @@ */ public class MustCallTransfer extends CFTransfer { - /** For building new AST nodes. */ - private final TreeBuilder treeBuilder; + /** For building new AST nodes. */ + private final TreeBuilder treeBuilder; - /** The type factory. */ - private final MustCallAnnotatedTypeFactory atypeFactory; + /** The type factory. */ + private final MustCallAnnotatedTypeFactory atypeFactory; - /** - * A cache for the default type for java.lang.String, to avoid needing to look it up for every - * implicit string conversion. See {@link #getDefaultStringType(StringConversionNode)}. - */ - private @MonotonicNonNull AnnotationMirror defaultStringType; + /** + * A cache for the default type for java.lang.String, to avoid needing to look it up for every + * implicit string conversion. See {@link #getDefaultStringType(StringConversionNode)}. + */ + private @MonotonicNonNull AnnotationMirror defaultStringType; - /** True if -AnoCreatesMustCallFor was passed on the command line. */ - private final boolean noCreatesMustCallFor; + /** True if -AnoCreatesMustCallFor was passed on the command line. */ + private final boolean noCreatesMustCallFor; - /* NO-AFU - * True if -AenableWpiForRlc was passed on the command line. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - private final boolean enableWpiForRlc; - */ + /* NO-AFU + * True if -AenableWpiForRlc was passed on the command line. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + private final boolean enableWpiForRlc; + */ - /** - * Create a MustCallTransfer. - * - * @param analysis the analysis - */ - public MustCallTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (MustCallAnnotatedTypeFactory) analysis.getTypeFactory(); - noCreatesMustCallFor = - atypeFactory.getChecker().hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); - // enableWpiForRlc = - // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); - ProcessingEnvironment env = atypeFactory.getChecker().getProcessingEnvironment(); - treeBuilder = new TreeBuilder(env); - } + /** + * Create a MustCallTransfer. + * + * @param analysis the analysis + */ + public MustCallTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (MustCallAnnotatedTypeFactory) analysis.getTypeFactory(); + noCreatesMustCallFor = + atypeFactory.getChecker().hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); + // enableWpiForRlc = + // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + ProcessingEnvironment env = atypeFactory.getChecker().getProcessingEnvironment(); + treeBuilder = new TreeBuilder(env); + } - @Override - public TransferResult visitStringConversion( - StringConversionNode n, TransferInput p) { - // Implicit String conversions should assume that the String's type is - // whatever the default for String is, not that the conversion is polymorphic. - TransferResult result = super.visitStringConversion(n, p); - LocalVariableNode temp = getOrCreateTempVar(n); - if (temp != null) { - AnnotationMirror defaultStringType = getDefaultStringType(n); - JavaExpression localExp = JavaExpression.fromNode(temp); - insertIntoStores(result, localExp, defaultStringType); - } - return result; + @Override + public TransferResult visitStringConversion( + StringConversionNode n, TransferInput p) { + // Implicit String conversions should assume that the String's type is + // whatever the default for String is, not that the conversion is polymorphic. + TransferResult result = super.visitStringConversion(n, p); + LocalVariableNode temp = getOrCreateTempVar(n); + if (temp != null) { + AnnotationMirror defaultStringType = getDefaultStringType(n); + JavaExpression localExp = JavaExpression.fromNode(temp); + insertIntoStores(result, localExp, defaultStringType); } + return result; + } - /** - * Returns the default type for java.lang.String, which is cached in this class to avoid - * recomputing it. If the cache is currently unset, this method sets it. - * - * @param n a string conversion node, from which the type is computed if it is required - * @return the type of java.lang.String - */ - private AnnotationMirror getDefaultStringType(StringConversionNode n) { - if (this.defaultStringType == null) { - this.defaultStringType = - atypeFactory - .getAnnotatedType(TypesUtils.getTypeElement(n.getType())) - .getAnnotationInHierarchy(atypeFactory.TOP); - assert this.defaultStringType != null : "@AssumeAssertion(nullness): same hierarchy"; - } - return this.defaultStringType; + /** + * Returns the default type for java.lang.String, which is cached in this class to avoid + * recomputing it. If the cache is currently unset, this method sets it. + * + * @param n a string conversion node, from which the type is computed if it is required + * @return the type of java.lang.String + */ + private AnnotationMirror getDefaultStringType(StringConversionNode n) { + if (this.defaultStringType == null) { + this.defaultStringType = + atypeFactory + .getAnnotatedType(TypesUtils.getTypeElement(n.getType())) + .getAnnotationInHierarchy(atypeFactory.TOP); + assert this.defaultStringType != null : "@AssumeAssertion(nullness): same hierarchy"; } + return this.defaultStringType; + } - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = super.visitMethodInvocation(n, in); + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = super.visitMethodInvocation(n, in); - updateStoreWithTempVar(result, n); - if (!noCreatesMustCallFor) { - List targetExprs = - CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( - n, atypeFactory, atypeFactory); - for (JavaExpression targetExpr : targetExprs) { - AnnotationMirror defaultType = - atypeFactory - .getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) - .getAnnotationInHierarchy(atypeFactory.TOP); + updateStoreWithTempVar(result, n); + if (!noCreatesMustCallFor) { + List targetExprs = + CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( + n, atypeFactory, atypeFactory); + for (JavaExpression targetExpr : targetExprs) { + AnnotationMirror defaultType = + atypeFactory + .getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) + .getAnnotationInHierarchy(atypeFactory.TOP); - if (result.containsTwoStores()) { - CFStore thenStore = result.getThenStore(); - lubWithStoreValue(thenStore, targetExpr, defaultType); + if (result.containsTwoStores()) { + CFStore thenStore = result.getThenStore(); + lubWithStoreValue(thenStore, targetExpr, defaultType); - CFStore elseStore = result.getElseStore(); - lubWithStoreValue(elseStore, targetExpr, defaultType); - } else { - CFStore store = result.getRegularStore(); - lubWithStoreValue(store, targetExpr, defaultType); - } - } + CFStore elseStore = result.getElseStore(); + lubWithStoreValue(elseStore, targetExpr, defaultType); + } else { + CFStore store = result.getRegularStore(); + lubWithStoreValue(store, targetExpr, defaultType); } - return result; + } } + return result; + } - /** - * Computes the LUB of the current value in the store for expr, if it exists, and defaultType. - * Inserts that LUB into the store as the new value for expr. - * - * @param store a CFStore - * @param expr an expression that might be in the store - * @param defaultType the default type of the expression's static type - */ - private void lubWithStoreValue( - CFStore store, JavaExpression expr, AnnotationMirror defaultType) { - CFValue value = store.getValue(expr); - CFValue defaultTypeAsCFValue = - analysis.createSingleAnnotationValue(defaultType, expr.getType()); - CFValue newValue = defaultTypeAsCFValue.leastUpperBound(value); - store.clearValue(expr); - store.insertValue(expr, newValue); - } + /** + * Computes the LUB of the current value in the store for expr, if it exists, and defaultType. + * Inserts that LUB into the store as the new value for expr. + * + * @param store a CFStore + * @param expr an expression that might be in the store + * @param defaultType the default type of the expression's static type + */ + private void lubWithStoreValue(CFStore store, JavaExpression expr, AnnotationMirror defaultType) { + CFValue value = store.getValue(expr); + CFValue defaultTypeAsCFValue = + analysis.createSingleAnnotationValue(defaultType, expr.getType()); + CFValue newValue = defaultTypeAsCFValue.leastUpperBound(value); + store.clearValue(expr); + store.insertValue(expr, newValue); + } - /* NO-AFU - * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @param tree a tree - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the - * result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree tree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; - } - return super.shouldPerformWholeProgramInference(tree); + /* NO-AFU + * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @param tree a tree + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the + * result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree tree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; } - /* + return super.shouldPerformWholeProgramInference(tree); + } + /* - /* NO-AFU - * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @param expressionTree a tree - * @param lhsTree its element - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the - * result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; - } - return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); + /* NO-AFU + * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @param expressionTree a tree + * @param lhsTree its element + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the + * result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; } - */ + return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); + } + */ - @Override - public TransferResult visitObjectCreation( - ObjectCreationNode node, TransferInput input) { - TransferResult result = super.visitObjectCreation(node, input); - updateStoreWithTempVar(result, node); - return result; - } + @Override + public TransferResult visitObjectCreation( + ObjectCreationNode node, TransferInput input) { + TransferResult result = super.visitObjectCreation(node, input); + updateStoreWithTempVar(result, node); + return result; + } - @Override - public TransferResult visitTernaryExpression( - TernaryExpressionNode node, TransferInput input) { - TransferResult result = super.visitTernaryExpression(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - atypeFactory.tempVars.put(node.getTree(), node.getTernaryExpressionVar()); - } - return result; + @Override + public TransferResult visitTernaryExpression( + TernaryExpressionNode node, TransferInput input) { + TransferResult result = super.visitTernaryExpression(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + atypeFactory.tempVars.put(node.getTree(), node.getTernaryExpressionVar()); } + return result; + } - @Override - public TransferResult visitSwitchExpressionNode( - SwitchExpressionNode node, TransferInput input) { - TransferResult result = super.visitSwitchExpressionNode(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - atypeFactory.tempVars.put(node.getTree(), node.getSwitchExpressionVar()); - } - return result; + @Override + public TransferResult visitSwitchExpressionNode( + SwitchExpressionNode node, TransferInput input) { + TransferResult result = super.visitSwitchExpressionNode(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + atypeFactory.tempVars.put(node.getTree(), node.getSwitchExpressionVar()); } + return result; + } - /** - * This method either creates or looks up the temp var t for node, and then updates the store to - * give t the same type as {@code node}. - * - * @param node the node to be assigned to a temporary variable - * @param result the transfer result containing the store to be modified - */ - public void updateStoreWithTempVar(TransferResult result, Node node) { - // Must-call obligations on primitives are not supported. - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - LocalVariableNode temp = getOrCreateTempVar(node); - if (temp != null) { - JavaExpression localExp = JavaExpression.fromNode(temp); - AnnotationMirror anm = - atypeFactory - .getAnnotatedType(node.getTree()) - .getAnnotationInHierarchy(atypeFactory.TOP); - insertIntoStores(result, localExp, anm == null ? atypeFactory.TOP : anm); - } - } + /** + * This method either creates or looks up the temp var t for node, and then updates the store to + * give t the same type as {@code node}. + * + * @param node the node to be assigned to a temporary variable + * @param result the transfer result containing the store to be modified + */ + public void updateStoreWithTempVar(TransferResult result, Node node) { + // Must-call obligations on primitives are not supported. + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + LocalVariableNode temp = getOrCreateTempVar(node); + if (temp != null) { + JavaExpression localExp = JavaExpression.fromNode(temp); + AnnotationMirror anm = + atypeFactory + .getAnnotatedType(node.getTree()) + .getAnnotationInHierarchy(atypeFactory.TOP); + insertIntoStores(result, localExp, anm == null ? atypeFactory.TOP : anm); + } } + } - /** - * Either returns the temporary variable associated with node, or creates one if one does not - * exist. - * - * @param node a node, which must be an expression (not a statement) - * @return a temporary variable node representing {@code node} that can be placed into a store - */ - private @Nullable LocalVariableNode getOrCreateTempVar(Node node) { - LocalVariableNode localVariableNode = atypeFactory.tempVars.get(node.getTree()); - if (localVariableNode == null) { - VariableTree temp = createTemporaryVar(node); - if (temp != null) { - IdentifierTree identifierTree = treeBuilder.buildVariableUse(temp); - localVariableNode = new LocalVariableNode(identifierTree); - localVariableNode.setInSource(true); - atypeFactory.tempVars.put(node.getTree(), localVariableNode); - } - } - return localVariableNode; + /** + * Either returns the temporary variable associated with node, or creates one if one does not + * exist. + * + * @param node a node, which must be an expression (not a statement) + * @return a temporary variable node representing {@code node} that can be placed into a store + */ + private @Nullable LocalVariableNode getOrCreateTempVar(Node node) { + LocalVariableNode localVariableNode = atypeFactory.tempVars.get(node.getTree()); + if (localVariableNode == null) { + VariableTree temp = createTemporaryVar(node); + if (temp != null) { + IdentifierTree identifierTree = treeBuilder.buildVariableUse(temp); + localVariableNode = new LocalVariableNode(identifierTree); + localVariableNode.setInSource(true); + atypeFactory.tempVars.put(node.getTree(), localVariableNode); + } } + return localVariableNode; + } - /** - * Creates a variable declaration for the given expression node, if possible. - * - *

Note that error reporting code assumes that the names of temporary variables are not legal - * Java identifiers (see JLS 3.8). - * The temporary variable names generated here include an {@code '-'} character to make the - * names invalid. - * - * @param node an expression node - * @return a variable tree for the node, or null if an appropriate containing element cannot be - * located - */ - protected @Nullable VariableTree createTemporaryVar(Node node) { - ExpressionTree tree = (ExpressionTree) node.getTree(); - TypeMirror treeType = TreeUtils.typeOf(tree); - Element enclosingElement; - TreePath path = atypeFactory.getPath(tree); - if (path == null) { - enclosingElement = TreeUtils.elementFromUse(tree).getEnclosingElement(); - } else { - ClassTree classTree = TreePathUtil.enclosingClass(path); - enclosingElement = TreeUtils.elementFromDeclaration(classTree); - } - if (enclosingElement == null) { - return null; - } - // Declare and initialize a new, unique variable - VariableTree tmpVarTree = - treeBuilder.buildVariableDecl( - treeType, uniqueName("temp-var"), enclosingElement, tree); - return tmpVarTree; + /** + * Creates a variable declaration for the given expression node, if possible. + * + *

Note that error reporting code assumes that the names of temporary variables are not legal + * Java identifiers (see JLS 3.8). The + * temporary variable names generated here include an {@code '-'} character to make the names + * invalid. + * + * @param node an expression node + * @return a variable tree for the node, or null if an appropriate containing element cannot be + * located + */ + protected @Nullable VariableTree createTemporaryVar(Node node) { + ExpressionTree tree = (ExpressionTree) node.getTree(); + TypeMirror treeType = TreeUtils.typeOf(tree); + Element enclosingElement; + TreePath path = atypeFactory.getPath(tree); + if (path == null) { + enclosingElement = TreeUtils.elementFromUse(tree).getEnclosingElement(); + } else { + ClassTree classTree = TreePathUtil.enclosingClass(path); + enclosingElement = TreeUtils.elementFromDeclaration(classTree); + } + if (enclosingElement == null) { + return null; } + // Declare and initialize a new, unique variable + VariableTree tmpVarTree = + treeBuilder.buildVariableDecl(treeType, uniqueName("temp-var"), enclosingElement, tree); + return tmpVarTree; + } - /** A unique identifier counter for node names. */ - private static AtomicLong uid = new AtomicLong(); + /** A unique identifier counter for node names. */ + private static AtomicLong uid = new AtomicLong(); - /** - * Creates a unique, arbitrary string that can be used as a name for a temporary variable, using - * the given prefix. Can be used up to Long.MAX_VALUE times. - * - *

Note that the correctness of the Resource Leak Checker depends on these names actually - * being unique, because {@code LocalVariableNode}s derived from them are used as keys in a map. - * - * @param prefix the prefix for the name - * @return a unique name that starts with the prefix - */ - protected String uniqueName(String prefix) { - return prefix + "-" + uid.getAndIncrement(); - } + /** + * Creates a unique, arbitrary string that can be used as a name for a temporary variable, using + * the given prefix. Can be used up to Long.MAX_VALUE times. + * + *

Note that the correctness of the Resource Leak Checker depends on these names actually being + * unique, because {@code LocalVariableNode}s derived from them are used as keys in a map. + * + * @param prefix the prefix for the name + * @return a unique name that starts with the prefix + */ + protected String uniqueName(String prefix) { + return prefix + "-" + uid.getAndIncrement(); + } - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; - } - */ + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; + } + */ } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java index a60ac7db446..fb5cc66ec10 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java @@ -6,18 +6,18 @@ /** Primitive types always have no must-call obligations. */ public class MustCallTypeAnnotator extends TypeAnnotator { - /** - * Create a MustCallTypeAnnotator. - * - * @param typeFactory the type factory - */ - protected MustCallTypeAnnotator(MustCallAnnotatedTypeFactory typeFactory) { - super(typeFactory); - } + /** + * Create a MustCallTypeAnnotator. + * + * @param typeFactory the type factory + */ + protected MustCallTypeAnnotator(MustCallAnnotatedTypeFactory typeFactory) { + super(typeFactory); + } - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - type.replaceAnnotation(((MustCallAnnotatedTypeFactory) atypeFactory).BOTTOM); - return super.visitPrimitive(type, aVoid); - } + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { + type.replaceAnnotation(((MustCallAnnotatedTypeFactory) atypeFactory).BOTTOM); + return super.visitPrimitive(type, aVoid); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java index c0f96db13ca..95a06e28630 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java @@ -8,7 +8,15 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.ReturnTree; import com.sun.source.tree.Tree; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.MustCallAlias; @@ -26,17 +34,6 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; - /** * The visitor for the Must Call Checker. This visitor is similar to BaseTypeVisitor, but overrides * methods that don't work well with the MustCall type hierarchy because it doesn't use the top type @@ -44,309 +41,289 @@ */ public class MustCallVisitor extends BaseTypeVisitor { - /** True if -AnoLightweightOwnership was passed on the command line. */ - private final boolean noLightweightOwnership; + /** True if -AnoLightweightOwnership was passed on the command line. */ + private final boolean noLightweightOwnership; - /** - * Creates a new MustCallVisitor. - * - * @param checker the type-checker associated with this visitor - */ - public MustCallVisitor(BaseTypeChecker checker) { - super(checker); - noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); - } + /** + * Creates a new MustCallVisitor. + * + * @param checker the type-checker associated with this visitor + */ + public MustCallVisitor(BaseTypeChecker checker) { + super(checker); + noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); + } - @Override - public Void visitReturn(ReturnTree tree, Void p) { - // Only check return types if ownership is being transferred. - if (!noLightweightOwnership) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(this.getCurrentPath()); - // enclosingMethod is null if this return site is inside a lambda. TODO: handle lambdas - // more precisely? - if (enclosingMethod != null) { - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(enclosingMethod); - AnnotationMirror notOwningAnno = - atypeFactory.getDeclAnnotation(methodElt, NotOwning.class); - if (notOwningAnno != null) { - // Skip return type subtyping check, because not-owning pointer means Object - // Construction Checker won't check anyway. - return null; - } - } + @Override + public Void visitReturn(ReturnTree tree, Void p) { + // Only check return types if ownership is being transferred. + if (!noLightweightOwnership) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(this.getCurrentPath()); + // enclosingMethod is null if this return site is inside a lambda. TODO: handle lambdas + // more precisely? + if (enclosingMethod != null) { + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(enclosingMethod); + AnnotationMirror notOwningAnno = atypeFactory.getDeclAnnotation(methodElt, NotOwning.class); + if (notOwningAnno != null) { + // Skip return type subtyping check, because not-owning pointer means Object + // Construction Checker won't check anyway. + return null; } - return super.visitReturn(tree, p); + } } + return super.visitReturn(tree, p); + } - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - // This code implements the following rule: - // * It is always safe to assign a MustCallAlias parameter of a constructor - // to an owning field of the containing class. - // It is necessary to special case this because MustCallAlias is translated - // into @PolyMustCall, so the common assignment check will fail when assigning - // an @MustCallAlias parameter to an owning field: the parameter is polymorphic, - // but the field is not. - ExpressionTree lhs = tree.getVariable(); - ExpressionTree rhs = tree.getExpression(); - Element lhsElt = TreeUtils.elementFromTree(lhs); - Element rhsElt = TreeUtils.elementFromTree(rhs); - if (lhsElt != null && rhsElt != null) { - // Note that it is not necessary to check that the assignment is to a field of this, - // because that is implied by the other conditions: - // * if the field is final, then the only place it can be assigned to is in the - // constructor of the proper object (enforced by javac). - // * if the field is not final, then it cannot be assigned to in a constructor at all: - // the @CreatesMustCallFor annotation cannot be written on a constructor (it has - // @Target({ElementType.METHOD})), so this code relies on the standard rules for - // non-final owning field reassignment, which prevent it without an - // @CreatesMustCallFor annotation except in the constructor of the object containing - // the field. - boolean lhsIsOwningField = - lhs.getKind() == Tree.Kind.MEMBER_SELECT - && atypeFactory.getDeclAnnotation(lhsElt, Owning.class) != null; - boolean rhsIsMCA = - AnnotationUtils.containsSameByClass( - rhsElt.getAnnotationMirrors(), MustCallAlias.class); - boolean rhsIsConstructorParam = - rhsElt.getKind() == ElementKind.PARAMETER - && rhsElt.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR; - if (lhsIsOwningField && rhsIsMCA && rhsIsConstructorParam) { - // Do not execute common assignment check. - return null; - } - } - - return super.visitAssignment(tree, p); + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + // This code implements the following rule: + // * It is always safe to assign a MustCallAlias parameter of a constructor + // to an owning field of the containing class. + // It is necessary to special case this because MustCallAlias is translated + // into @PolyMustCall, so the common assignment check will fail when assigning + // an @MustCallAlias parameter to an owning field: the parameter is polymorphic, + // but the field is not. + ExpressionTree lhs = tree.getVariable(); + ExpressionTree rhs = tree.getExpression(); + Element lhsElt = TreeUtils.elementFromTree(lhs); + Element rhsElt = TreeUtils.elementFromTree(rhs); + if (lhsElt != null && rhsElt != null) { + // Note that it is not necessary to check that the assignment is to a field of this, + // because that is implied by the other conditions: + // * if the field is final, then the only place it can be assigned to is in the + // constructor of the proper object (enforced by javac). + // * if the field is not final, then it cannot be assigned to in a constructor at all: + // the @CreatesMustCallFor annotation cannot be written on a constructor (it has + // @Target({ElementType.METHOD})), so this code relies on the standard rules for + // non-final owning field reassignment, which prevent it without an + // @CreatesMustCallFor annotation except in the constructor of the object containing + // the field. + boolean lhsIsOwningField = + lhs.getKind() == Tree.Kind.MEMBER_SELECT + && atypeFactory.getDeclAnnotation(lhsElt, Owning.class) != null; + boolean rhsIsMCA = + AnnotationUtils.containsSameByClass(rhsElt.getAnnotationMirrors(), MustCallAlias.class); + boolean rhsIsConstructorParam = + rhsElt.getKind() == ElementKind.PARAMETER + && rhsElt.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR; + if (lhsIsOwningField && rhsIsMCA && rhsIsConstructorParam) { + // Do not execute common assignment check. + return null; + } } - /** An empty string list. */ - private static final List emptyStringList = Collections.emptyList(); + return super.visitAssignment(tree, p); + } - @Override - protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isClassTree(tree)) { - TypeElement classEle = TreeUtils.elementFromDeclaration((ClassTree) tree); - // If no @InheritableMustCall annotation is written here, `getDeclAnnotation()` gets one - // from stub files and supertypes. - AnnotationMirror anyInheritableMustCall = - atypeFactory.getDeclAnnotation(classEle, InheritableMustCall.class); - // An @InheritableMustCall annotation that is directly present. - AnnotationMirror directInheritableMustCall = - AnnotationUtils.getAnnotationByClass( - classEle.getAnnotationMirrors(), InheritableMustCall.class); - if (anyInheritableMustCall == null) { - if (!ElementUtils.isFinal(classEle)) { - // There is no @InheritableMustCall annotation on this or any superclass and - // this is a non-final class. - // If an explicit @MustCall annotation is present, issue a warning suggesting - // that @InheritableMustCall is probably what the programmer means, for - // usability. - if (atypeFactory.getDeclAnnotation(classEle, MustCall.class) != null) { - checker.reportWarning( - tree, - "mustcall.not.inheritable", - ElementUtils.getQualifiedName(classEle)); - } - } - } else { - // There is an @InheritableMustCall annotation on this, on a superclass, or in an - // annotation file. - // There are two possible problems: - // 1. There is an inconsistent @MustCall on this. - // 2. There is an explicit @InheritableMustCall here, and it is inconsistent with - // an @InheritableMustCall annotation on a supertype. + /** An empty string list. */ + private static final List emptyStringList = Collections.emptyList(); - // Check for problem 1. - AnnotationMirror explicitMustCall = - atypeFactory - .fromElement(classEle) - .getAnnotationInHierarchy(atypeFactory.TOP); - if (explicitMustCall != null) { - // There is a @MustCall annotation here. + @Override + protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isClassTree(tree)) { + TypeElement classEle = TreeUtils.elementFromDeclaration((ClassTree) tree); + // If no @InheritableMustCall annotation is written here, `getDeclAnnotation()` gets one + // from stub files and supertypes. + AnnotationMirror anyInheritableMustCall = + atypeFactory.getDeclAnnotation(classEle, InheritableMustCall.class); + // An @InheritableMustCall annotation that is directly present. + AnnotationMirror directInheritableMustCall = + AnnotationUtils.getAnnotationByClass( + classEle.getAnnotationMirrors(), InheritableMustCall.class); + if (anyInheritableMustCall == null) { + if (!ElementUtils.isFinal(classEle)) { + // There is no @InheritableMustCall annotation on this or any superclass and + // this is a non-final class. + // If an explicit @MustCall annotation is present, issue a warning suggesting + // that @InheritableMustCall is probably what the programmer means, for + // usability. + if (atypeFactory.getDeclAnnotation(classEle, MustCall.class) != null) { + checker.reportWarning( + tree, "mustcall.not.inheritable", ElementUtils.getQualifiedName(classEle)); + } + } + } else { + // There is an @InheritableMustCall annotation on this, on a superclass, or in an + // annotation file. + // There are two possible problems: + // 1. There is an inconsistent @MustCall on this. + // 2. There is an explicit @InheritableMustCall here, and it is inconsistent with + // an @InheritableMustCall annotation on a supertype. - List inheritableMustCallVal = - AnnotationUtils.getElementValueArray( - anyInheritableMustCall, - atypeFactory.inheritableMustCallValueElement, - String.class, - emptyStringList); - AnnotationMirror inheritedMCAnno = - atypeFactory.createMustCall(inheritableMustCallVal); + // Check for problem 1. + AnnotationMirror explicitMustCall = + atypeFactory.fromElement(classEle).getAnnotationInHierarchy(atypeFactory.TOP); + if (explicitMustCall != null) { + // There is a @MustCall annotation here. - // Issue an error if there is an inconsistent, user-written @MustCall annotation - // here. - AnnotationMirror effectiveMCAnno = - type.getAnnotationInHierarchy(atypeFactory.TOP); - TypeMirror tm = type.getUnderlyingType(); - if (effectiveMCAnno != null - && !qualHierarchy.isSubtypeShallow( - inheritedMCAnno, effectiveMCAnno, tm)) { + List inheritableMustCallVal = + AnnotationUtils.getElementValueArray( + anyInheritableMustCall, + atypeFactory.inheritableMustCallValueElement, + String.class, + emptyStringList); + AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(inheritableMustCallVal); - checker.reportError( - tree, - "inconsistent.mustcall.subtype", - ElementUtils.getQualifiedName(classEle), - effectiveMCAnno, - anyInheritableMustCall); - return false; - } - } + // Issue an error if there is an inconsistent, user-written @MustCall annotation + // here. + AnnotationMirror effectiveMCAnno = type.getAnnotationInHierarchy(atypeFactory.TOP); + TypeMirror tm = type.getUnderlyingType(); + if (effectiveMCAnno != null + && !qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) { - // Check for problem 2. - if (directInheritableMustCall != null) { + checker.reportError( + tree, + "inconsistent.mustcall.subtype", + ElementUtils.getQualifiedName(classEle), + effectiveMCAnno, + anyInheritableMustCall); + return false; + } + } - // `inheritedImcs` is inherited @InheritableMustCall annotations. - List inheritedImcs = new ArrayList<>(); - for (TypeElement elt : - ElementUtils.getDirectSuperTypeElements(classEle, elements)) { - AnnotationMirror imc = - atypeFactory.getDeclAnnotation(elt, InheritableMustCall.class); - if (imc != null) { - inheritedImcs.add(imc); - } - } - if (!inheritedImcs.isEmpty()) { - // There is an inherited @InheritableMustCall annotation, in addition to the - // one written explicitly here. - List inheritedMustCallVal = new ArrayList<>(); - for (AnnotationMirror inheritedImc : inheritedImcs) { - inheritedMustCallVal.addAll( - AnnotationUtils.getElementValueArray( - inheritedImc, - atypeFactory.inheritableMustCallValueElement, - String.class)); - } - AnnotationMirror inheritedMCAnno = - atypeFactory.createMustCall(inheritedMustCallVal); + // Check for problem 2. + if (directInheritableMustCall != null) { - AnnotationMirror effectiveMCAnno = - type.getAnnotationInHierarchy(atypeFactory.TOP); + // `inheritedImcs` is inherited @InheritableMustCall annotations. + List inheritedImcs = new ArrayList<>(); + for (TypeElement elt : ElementUtils.getDirectSuperTypeElements(classEle, elements)) { + AnnotationMirror imc = atypeFactory.getDeclAnnotation(elt, InheritableMustCall.class); + if (imc != null) { + inheritedImcs.add(imc); + } + } + if (!inheritedImcs.isEmpty()) { + // There is an inherited @InheritableMustCall annotation, in addition to the + // one written explicitly here. + List inheritedMustCallVal = new ArrayList<>(); + for (AnnotationMirror inheritedImc : inheritedImcs) { + inheritedMustCallVal.addAll( + AnnotationUtils.getElementValueArray( + inheritedImc, atypeFactory.inheritableMustCallValueElement, String.class)); + } + AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(inheritedMustCallVal); - TypeMirror tm = type.getUnderlyingType(); + AnnotationMirror effectiveMCAnno = type.getAnnotationInHierarchy(atypeFactory.TOP); - if (!qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) { + TypeMirror tm = type.getUnderlyingType(); - checker.reportError( - tree, - "inconsistent.mustcall.subtype", - ElementUtils.getQualifiedName(classEle), - effectiveMCAnno, - inheritedMCAnno); - return false; - } - } - } - } - } - return super.validateType(tree, type); - } + if (!qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) { - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // MustCallAlias annotations are always permitted on type uses, despite not technically - // being a part of the type hierarchy. It's necessary to get the annotation from the element - // because MustCallAlias is aliased to PolyMustCall, which is what useType would contain. - // Note that isValidUse does not need to consider component types, on which it should be - // called separately. - Element elt = TreeUtils.elementFromTree(tree); - if (elt != null) { - if (AnnotationUtils.containsSameByClass( - elt.getAnnotationMirrors(), MustCallAlias.class)) { - return true; - } - // Need to check the type mirror for ajava-derived annotations and the element itself - // for human-written annotations from the source code. Getting to the ajava file - // directly at this point is impossible, so we approximate "the ajava file has an - // @MustCallAlias annotation" with "there is an @PolyMustCall annotation on the use - // type, but not in the source code". This only works because none of our inference - // techniques infer @PolyMustCall, so if @PolyMustCall is present but wasn't in the - // source, it must have been derived from an @MustCallAlias annotation (which we do - // infer). - boolean ajavaFileHasMustCallAlias = - useType.hasAnnotation(PolyMustCall.class) - && !AnnotationUtils.containsSameByClass( - elt.getAnnotationMirrors(), PolyMustCall.class); - if (ajavaFileHasMustCallAlias) { - return true; + checker.reportError( + tree, + "inconsistent.mustcall.subtype", + ElementUtils.getQualifiedName(classEle), + effectiveMCAnno, + inheritedMCAnno); + return false; } + } } - return super.isValidUse(declarationType, useType, tree); + } } + return super.validateType(tree, type); + } - @Override - protected boolean skipReceiverSubtypeCheck( - MethodInvocationTree tree, - AnnotatedTypeMirror methodDefinitionReceiver, - AnnotatedTypeMirror methodCallReceiver) { - // It does not make sense for receivers to have must-call obligations. If the receiver of a - // method were to have a non-empty must-call obligation, then actually this method should - // be part of the must-call annotation on the class declaration! So skipping this check is - // always sound. + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // MustCallAlias annotations are always permitted on type uses, despite not technically + // being a part of the type hierarchy. It's necessary to get the annotation from the element + // because MustCallAlias is aliased to PolyMustCall, which is what useType would contain. + // Note that isValidUse does not need to consider component types, on which it should be + // called separately. + Element elt = TreeUtils.elementFromTree(tree); + if (elt != null) { + if (AnnotationUtils.containsSameByClass(elt.getAnnotationMirrors(), MustCallAlias.class)) { return true; + } + // Need to check the type mirror for ajava-derived annotations and the element itself + // for human-written annotations from the source code. Getting to the ajava file + // directly at this point is impossible, so we approximate "the ajava file has an + // @MustCallAlias annotation" with "there is an @PolyMustCall annotation on the use + // type, but not in the source code". This only works because none of our inference + // techniques infer @PolyMustCall, so if @PolyMustCall is present but wasn't in the + // source, it must have been derived from an @MustCallAlias annotation (which we do + // infer). + boolean ajavaFileHasMustCallAlias = + useType.hasAnnotation(PolyMustCall.class) + && !AnnotationUtils.containsSameByClass( + elt.getAnnotationMirrors(), PolyMustCall.class); + if (ajavaFileHasMustCallAlias) { + return true; + } } + return super.isValidUse(declarationType, useType, tree); + } - /** - * This method typically issues a warning if the result type of the constructor is not top, - * because in top-default type systems that indicates a potential problem. The Must Call Checker - * does not need this warning, because it expects the type of all constructors to be {@code - * MustCall({})} (by default) or some other {@code MustCall} type, not the top type. - * - *

Instead, this method checks that the result type of a constructor is a supertype of the - * declared type on the class, if one exists. - * - * @param constructorType an AnnotatedExecutableType for the constructor - * @param constructorElement element that declares the constructor - */ - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - AnnotatedTypeMirror defaultType = - atypeFactory.getAnnotatedType( - ElementUtils.enclosingTypeElement(constructorElement)); - AnnotationMirror defaultAnno = defaultType.getAnnotationInHierarchy(atypeFactory.TOP); - AnnotatedTypeMirror resultType = constructorType.getReturnType(); - AnnotationMirror resultAnno = resultType.getAnnotationInHierarchy(atypeFactory.TOP); - if (!qualHierarchy.isSubtypeShallow( - defaultAnno, - defaultType.getUnderlyingType(), - resultAnno, - resultType.getUnderlyingType())) { - checker.reportError( - constructorElement, "inconsistent.constructor.type", resultAnno, defaultAnno); - } - } + @Override + protected boolean skipReceiverSubtypeCheck( + MethodInvocationTree tree, + AnnotatedTypeMirror methodDefinitionReceiver, + AnnotatedTypeMirror methodCallReceiver) { + // It does not make sense for receivers to have must-call obligations. If the receiver of a + // method were to have a non-empty must-call obligation, then actually this method should + // be part of the must-call annotation on the class declaration! So skipping this check is + // always sound. + return true; + } - /** - * Change the default for exception parameter lower bounds to bottom (the default), to prevent - * false positives. This is unsound; see the discussion on - * https://github.com/typetools/checker-framework/issues/3839. - * - *

TODO: change checking of throws clauses to require that the thrown exception - * is @MustCall({}). This would probably eliminate most of the same false positives, without - * adding undue false positives. - * - * @return a set containing only the @MustCall({}) annotation - */ - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(atypeFactory.BOTTOM); + /** + * This method typically issues a warning if the result type of the constructor is not top, + * because in top-default type systems that indicates a potential problem. The Must Call Checker + * does not need this warning, because it expects the type of all constructors to be {@code + * MustCall({})} (by default) or some other {@code MustCall} type, not the top type. + * + *

Instead, this method checks that the result type of a constructor is a supertype of the + * declared type on the class, if one exists. + * + * @param constructorType an AnnotatedExecutableType for the constructor + * @param constructorElement element that declares the constructor + */ + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + AnnotatedTypeMirror defaultType = + atypeFactory.getAnnotatedType(ElementUtils.enclosingTypeElement(constructorElement)); + AnnotationMirror defaultAnno = defaultType.getAnnotationInHierarchy(atypeFactory.TOP); + AnnotatedTypeMirror resultType = constructorType.getReturnType(); + AnnotationMirror resultAnno = resultType.getAnnotationInHierarchy(atypeFactory.TOP); + if (!qualHierarchy.isSubtypeShallow( + defaultAnno, defaultType.getUnderlyingType(), resultAnno, resultType.getUnderlyingType())) { + checker.reportError( + constructorElement, "inconsistent.constructor.type", resultAnno, defaultAnno); } + } - /** - * Does not issue any warnings. - * - *

This implementation prevents recursing into annotation arguments. Annotation arguments are - * literals, which don't have must-call obligations. - * - *

Annotation arguments are treated as return locations for the purposes of defaulting, - * rather than parameter locations. This causes them to default incorrectly when the annotation - * is defined in bytecode. See https://github.com/typetools/checker-framework/issues/3178 for an - * explanation of why this is necessary to avoid false positives. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - return null; - } + /** + * Change the default for exception parameter lower bounds to bottom (the default), to prevent + * false positives. This is unsound; see the discussion on + * https://github.com/typetools/checker-framework/issues/3839. + * + *

TODO: change checking of throws clauses to require that the thrown exception + * is @MustCall({}). This would probably eliminate most of the same false positives, without + * adding undue false positives. + * + * @return a set containing only the @MustCall({}) annotation + */ + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(atypeFactory.BOTTOM); + } + + /** + * Does not issue any warnings. + * + *

This implementation prevents recursing into annotation arguments. Annotation arguments are + * literals, which don't have must-call obligations. + * + *

Annotation arguments are treated as return locations for the purposes of defaulting, rather + * than parameter locations. This causes them to default incorrectly when the annotation is + * defined in bytecode. See https://github.com/typetools/checker-framework/issues/3178 for an + * explanation of why this is necessary to avoid false positives. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + return null; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java b/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java index a7108d5eec0..7872adc79a1 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java @@ -5,7 +5,14 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; - +import java.util.Collection; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -17,16 +24,6 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collection; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * Determines the nullness type of calls to {@link java.util.Collection#toArray()}. * @@ -36,202 +33,195 @@ */ public class CollectionToArrayHeuristics { - /** The processing environment. */ - private final ProcessingEnvironment processingEnv; - - /** The checker, used for issuing diagnostic messages. */ - private final BaseTypeChecker checker; - - /** The type factory. */ - private final NullnessNoInitAnnotatedTypeFactory atypeFactory; - - /** Whether to trust {@code @ArrayLen(0)} annotations. */ - private final boolean trustArrayLenZero; - - /** The Collection type. */ - private final AnnotatedDeclaredType collectionType; - - /** The Collection.toArray(T[]) method. */ - private final ExecutableElement collectionToArrayE; - - /** The Collection.size() method. */ - private final ExecutableElement size; - - /** The ArrayLen.value field/element. */ - private final ExecutableElement arrayLenValueElement; - - /** - * Create a CollectionToArrayHeuristics. - * - * @param checker the checker, used for issuing diagnostic messages - * @param factory the type factory - */ - public CollectionToArrayHeuristics( - BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { - this.processingEnv = checker.getProcessingEnvironment(); - this.checker = checker; - this.atypeFactory = factory; - - this.collectionType = - factory.fromElement(ElementUtils.getTypeElement(processingEnv, Collection.class)); - this.collectionToArrayE = - TreeUtils.getMethod("java.util.Collection", "toArray", processingEnv, "T[]"); - this.size = TreeUtils.getMethod("java.util.Collection", "size", 0, processingEnv); - this.arrayLenValueElement = TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); - - this.trustArrayLenZero = - checker.getLintOption( - NullnessChecker.LINT_TRUSTARRAYLENZERO, - NullnessChecker.LINT_DEFAULT_TRUSTARRAYLENZERO); - } - - /** - * If the method invocation is a call to {@code toArray}, then it manipulates the returned type - * of {@code method} arg to contain the appropriate nullness. Otherwise, it does nothing. - * - * @param tree method invocation tree - * @param method invoked method type - */ - public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { - if (TreeUtils.isMethodInvocation(tree, collectionToArrayE, processingEnv)) { - assert !tree.getArguments().isEmpty() : tree; - ExpressionTree argument = tree.getArguments().get(0); - boolean receiverIsNonNull = receiverIsCollectionOfNonNullElements(tree); - boolean argIsHandled = - isHandledArrayCreation(argument, receiverName(tree.getMethodSelect())) - || (trustArrayLenZero && isArrayLenZeroFieldAccess(argument)); - setComponentNullness(receiverIsNonNull && argIsHandled, method.getReturnType()); - - // TODO: We need a mechanism to prevent nullable collections - // from inserting null elements into a nonnull arrays. - if (!receiverIsNonNull) { - setComponentNullness(false, method.getParameterTypes().get(0)); - } - - if (receiverIsNonNull && !argIsHandled) { - if (argument.getKind() != Tree.Kind.NEW_ARRAY) { - checker.reportWarning(tree, "toarray.nullable.elements.not.newarray"); - } else { - checker.reportWarning(tree, "toarray.nullable.elements.mismatched.size"); - } - } + /** The processing environment. */ + private final ProcessingEnvironment processingEnv; + + /** The checker, used for issuing diagnostic messages. */ + private final BaseTypeChecker checker; + + /** The type factory. */ + private final NullnessNoInitAnnotatedTypeFactory atypeFactory; + + /** Whether to trust {@code @ArrayLen(0)} annotations. */ + private final boolean trustArrayLenZero; + + /** The Collection type. */ + private final AnnotatedDeclaredType collectionType; + + /** The Collection.toArray(T[]) method. */ + private final ExecutableElement collectionToArrayE; + + /** The Collection.size() method. */ + private final ExecutableElement size; + + /** The ArrayLen.value field/element. */ + private final ExecutableElement arrayLenValueElement; + + /** + * Create a CollectionToArrayHeuristics. + * + * @param checker the checker, used for issuing diagnostic messages + * @param factory the type factory + */ + public CollectionToArrayHeuristics( + BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { + this.processingEnv = checker.getProcessingEnvironment(); + this.checker = checker; + this.atypeFactory = factory; + + this.collectionType = + factory.fromElement(ElementUtils.getTypeElement(processingEnv, Collection.class)); + this.collectionToArrayE = + TreeUtils.getMethod("java.util.Collection", "toArray", processingEnv, "T[]"); + this.size = TreeUtils.getMethod("java.util.Collection", "size", 0, processingEnv); + this.arrayLenValueElement = TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); + + this.trustArrayLenZero = + checker.getLintOption( + NullnessChecker.LINT_TRUSTARRAYLENZERO, NullnessChecker.LINT_DEFAULT_TRUSTARRAYLENZERO); + } + + /** + * If the method invocation is a call to {@code toArray}, then it manipulates the returned type of + * {@code method} arg to contain the appropriate nullness. Otherwise, it does nothing. + * + * @param tree method invocation tree + * @param method invoked method type + */ + public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { + if (TreeUtils.isMethodInvocation(tree, collectionToArrayE, processingEnv)) { + assert !tree.getArguments().isEmpty() : tree; + ExpressionTree argument = tree.getArguments().get(0); + boolean receiverIsNonNull = receiverIsCollectionOfNonNullElements(tree); + boolean argIsHandled = + isHandledArrayCreation(argument, receiverName(tree.getMethodSelect())) + || (trustArrayLenZero && isArrayLenZeroFieldAccess(argument)); + setComponentNullness(receiverIsNonNull && argIsHandled, method.getReturnType()); + + // TODO: We need a mechanism to prevent nullable collections + // from inserting null elements into a nonnull arrays. + if (!receiverIsNonNull) { + setComponentNullness(false, method.getParameterTypes().get(0)); + } + + if (receiverIsNonNull && !argIsHandled) { + if (argument.getKind() != Tree.Kind.NEW_ARRAY) { + checker.reportWarning(tree, "toarray.nullable.elements.not.newarray"); + } else { + checker.reportWarning(tree, "toarray.nullable.elements.mismatched.size"); } + } } - - /** - * Sets the nullness of the component of the array type. - * - * @param isNonNull indicates which annotation ({@code NonNull} or {@code Nullable}) should be - * inserted - * @param type the array type - */ - private void setComponentNullness(boolean isNonNull, AnnotatedTypeMirror type) { - assert type.getKind() == TypeKind.ARRAY; - AnnotatedTypeMirror compType = ((AnnotatedArrayType) type).getComponentType(); - compType.replaceAnnotation(isNonNull ? atypeFactory.NONNULL : atypeFactory.NULLABLE); + } + + /** + * Sets the nullness of the component of the array type. + * + * @param isNonNull indicates which annotation ({@code NonNull} or {@code Nullable}) should be + * inserted + * @param type the array type + */ + private void setComponentNullness(boolean isNonNull, AnnotatedTypeMirror type) { + assert type.getKind() == TypeKind.ARRAY; + AnnotatedTypeMirror compType = ((AnnotatedArrayType) type).getComponentType(); + compType.replaceAnnotation(isNonNull ? atypeFactory.NONNULL : atypeFactory.NULLABLE); + } + + /** + * Returns true if {@code argument} is one of the array creation trees that the heuristic handles. + * + * @param argument the tree passed to {@link Collection#toArray(Object[]) Collection.toArray(T[])} + * @param receiver the expression for the receiver collection + * @return true if the argument is handled and assume to return nonnull elements + */ + private boolean isHandledArrayCreation(Tree argument, String receiver) { + if (argument.getKind() != Tree.Kind.NEW_ARRAY) { + return false; } + NewArrayTree newArr = (NewArrayTree) argument; - /** - * Returns true if {@code argument} is one of the array creation trees that the heuristic - * handles. - * - * @param argument the tree passed to {@link Collection#toArray(Object[]) - * Collection.toArray(T[])} - * @param receiver the expression for the receiver collection - * @return true if the argument is handled and assume to return nonnull elements - */ - private boolean isHandledArrayCreation(Tree argument, String receiver) { - if (argument.getKind() != Tree.Kind.NEW_ARRAY) { - return false; - } - NewArrayTree newArr = (NewArrayTree) argument; - - // empty array initializer - if (newArr.getInitializers() != null) { - return newArr.getInitializers().isEmpty(); - } - - assert !newArr.getDimensions().isEmpty(); - Tree dimension = newArr.getDimensions().get(newArr.getDimensions().size() - 1); + // empty array initializer + if (newArr.getInitializers() != null) { + return newArr.getInitializers().isEmpty(); + } - // 0-length array creation - if (dimension.toString().equals("0")) { - return true; - } + assert !newArr.getDimensions().isEmpty(); + Tree dimension = newArr.getDimensions().get(newArr.getDimensions().size() - 1); - // size()-length array creation - if (TreeUtils.isMethodInvocation(dimension, size, processingEnv)) { - MethodInvocationTree invok = (MethodInvocationTree) dimension; - String invokReceiver = receiverName(invok.getMethodSelect()); - return invokReceiver.equals(receiver); - } + // 0-length array creation + if (dimension.toString().equals("0")) { + return true; + } - return false; + // size()-length array creation + if (TreeUtils.isMethodInvocation(dimension, size, processingEnv)) { + MethodInvocationTree invok = (MethodInvocationTree) dimension; + String invokReceiver = receiverName(invok.getMethodSelect()); + return invokReceiver.equals(receiver); } - /** - * Returns true if the argument is a field access expression, where the field has declared type - * {@code @ArrayLen(0)}. - * - * @param argument an expression tree - * @return true if the argument is a field access expression, where the field has declared type - * {@code @ArrayLen(0)} - */ - private boolean isArrayLenZeroFieldAccess(ExpressionTree argument) { - Element el = TreeUtils.elementFromUse(argument); - if (el != null && el.getKind().isField()) { - TypeMirror t = ElementUtils.getType(el); - if (t.getKind() == TypeKind.ARRAY) { - List ams = t.getAnnotationMirrors(); - for (AnnotationMirror am : ams) { - if (atypeFactory.areSameByClass(am, ArrayLen.class)) { - List lens = - AnnotationUtils.getElementValueArray( - am, arrayLenValueElement, Integer.class); - if (lens.size() == 1 && lens.get(0) == 0) { - return true; - } - } - } + return false; + } + + /** + * Returns true if the argument is a field access expression, where the field has declared type + * {@code @ArrayLen(0)}. + * + * @param argument an expression tree + * @return true if the argument is a field access expression, where the field has declared type + * {@code @ArrayLen(0)} + */ + private boolean isArrayLenZeroFieldAccess(ExpressionTree argument) { + Element el = TreeUtils.elementFromUse(argument); + if (el != null && el.getKind().isField()) { + TypeMirror t = ElementUtils.getType(el); + if (t.getKind() == TypeKind.ARRAY) { + List ams = t.getAnnotationMirrors(); + for (AnnotationMirror am : ams) { + if (atypeFactory.areSameByClass(am, ArrayLen.class)) { + List lens = + AnnotationUtils.getElementValueArray(am, arrayLenValueElement, Integer.class); + if (lens.size() == 1 && lens.get(0) == 0) { + return true; } + } } - return false; + } } - - /** - * Returns {@code true} if the method invocation tree receiver is collection that contains - * non-null elements (i.e. its type argument is {@code @NonNull}. - * - * @param tree a method invocation - * @return true if the receiver is a collection of non-null elements - */ - private boolean receiverIsCollectionOfNonNullElements(MethodInvocationTree tree) { - // check receiver - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - AnnotatedDeclaredType collection = - AnnotatedTypes.asSuper(atypeFactory, receiver, collectionType); - - if (collection.getTypeArguments().isEmpty() // raw type - || !collection - .getTypeArguments() - .get(0) - .hasEffectiveAnnotation(atypeFactory.NONNULL)) { - return false; - } - return true; + return false; + } + + /** + * Returns {@code true} if the method invocation tree receiver is collection that contains + * non-null elements (i.e. its type argument is {@code @NonNull}. + * + * @param tree a method invocation + * @return true if the receiver is a collection of non-null elements + */ + private boolean receiverIsCollectionOfNonNullElements(MethodInvocationTree tree) { + // check receiver + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + AnnotatedDeclaredType collection = + AnnotatedTypes.asSuper(atypeFactory, receiver, collectionType); + + if (collection.getTypeArguments().isEmpty() // raw type + || !collection.getTypeArguments().get(0).hasEffectiveAnnotation(atypeFactory.NONNULL)) { + return false; } - - /** - * The name of the receiver object of the tree. - * - * @param tree either an identifier tree or a member select tree - */ - // This method is quite sloppy, but works most of the time - private String receiverName(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - return ((MemberSelectTree) tree).getExpression().toString(); - } else { - return "this"; - } + return true; + } + + /** + * The name of the receiver object of the tree. + * + * @param tree either an identifier tree or a member select tree + */ + // This method is quite sloppy, but works most of the time + private String receiverName(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + return ((MemberSelectTree) tree).getExpression().toString(); + } else { + return "this"; } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java index a6f18ffccb2..301bd382cc0 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java @@ -1,43 +1,42 @@ package org.checkerframework.checker.nullness; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.type.TypeMirror; - /** Boilerplate code to glue together all the parts the KeyFor dataflow classes. */ public class KeyForAnalysis extends CFAbstractAnalysis { - /** - * Creates a new {@code KeyForAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public KeyForAnalysis(BaseTypeChecker checker, KeyForAnnotatedTypeFactory factory) { - super(checker, factory); - } - - @Override - public KeyForStore createEmptyStore(boolean sequentialSemantics) { - return new KeyForStore(this, sequentialSemantics); - } - - @Override - public KeyForStore createCopiedStore(KeyForStore store) { - return new KeyForStore(store); - } - - @Override - public @Nullable KeyForValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; - } - return new KeyForValue(this, annotations, underlyingType); + /** + * Creates a new {@code KeyForAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public KeyForAnalysis(BaseTypeChecker checker, KeyForAnnotatedTypeFactory factory) { + super(checker, factory); + } + + @Override + public KeyForStore createEmptyStore(boolean sequentialSemantics) { + return new KeyForStore(this, sequentialSemantics); + } + + @Override + public KeyForStore createCopiedStore(KeyForStore store) { + return new KeyForStore(store); + } + + @Override + public @Nullable KeyForValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; } + return new KeyForValue(this, annotations, underlyingType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java index f358b435bc2..f90b51b077e 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java @@ -3,7 +3,14 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.PolyKeyFor; @@ -23,209 +30,194 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; - public class KeyForAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory< - KeyForValue, KeyForStore, KeyForTransfer, KeyForAnalysis> { - - /** The @{@link UnknownKeyFor} annotation. */ - protected final AnnotationMirror UNKNOWNKEYFOR = - AnnotationBuilder.fromClass(elements, UnknownKeyFor.class); - - /** The @{@link KeyForBottom} annotation. */ - protected final AnnotationMirror KEYFORBOTTOM = - AnnotationBuilder.fromClass(elements, KeyForBottom.class); - - /** The canonical name of the KeyFor class. */ - protected static final @CanonicalName String KEYFOR_NAME = KeyFor.class.getCanonicalName(); - - /** The Map.containsKey method. */ - private final ExecutableElement mapContainsKey = - TreeUtils.getMethod("java.util.Map", "containsKey", 1, processingEnv); - - /** The Map.get method. */ - private final ExecutableElement mapGet = - TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); - - /** The Map.put method. */ - private final ExecutableElement mapPut = - TreeUtils.getMethod("java.util.Map", "put", 2, processingEnv); - - /** The KeyFor.value field/element. */ - protected final ExecutableElement keyForValueElement = - TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); - - /** Moves annotations from one side of a pseudo-assignment to the other. */ - private final KeyForPropagator keyForPropagator = new KeyForPropagator(UNKNOWNKEYFOR); - - /** - * If true, assume the argument to Map.get is always a key for the receiver map. This is set by - * the `-AassumeKeyFor` command-line argument. However, if the Nullness Checker is being run, - * then `-AassumeKeyFor` disables the Map Key Checker. - */ - private final boolean assumeKeyFor; - - /** - * Creates a new KeyForAnnotatedTypeFactory. - * - * @param checker the associated checker - */ - public KeyForAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - - assumeKeyFor = checker.hasOption("assumeKeyFor"); - - // Add compatibility annotations: - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.KeyForDecl", KeyFor.class, true); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.KeyForType", KeyFor.class, true); - - // While strictly required for soundness, this leads to too many false positives. Printing - // a key or putting it in a map erases all knowledge of what maps it was a key for. - // TODO: Revisit when side effect annotations are more precise. - // sideEffectsUnrefineAliases = true; - - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - KeyFor.class, UnknownKeyFor.class, KeyForBottom.class, PolyKeyFor.class)); - } - - @Override - public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { - ParameterizedExecutableType result = super.constructorFromUse(tree); - keyForPropagator.propagateNewClassTree(tree, result.executableType.getReturnType(), this); - return result; - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), - new KeyForPropagationTreeAnnotator(this, keyForPropagator)); - } - - @Override - protected KeyForAnalysis createFlowAnalysis() { - // Explicitly call the constructor instead of using reflection. - return new KeyForAnalysis(checker, this); - } - - @Override - public KeyForTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - // Explicitly call the constructor instead of using reflection. - return new KeyForTransfer((KeyForAnalysis) analysis); - } - - /** - * Given a string array 'values', returns an AnnotationMirror corresponding to @KeyFor(values) - * - * @param values the values for the {@code @KeyFor} annotation - * @return a {@code @KeyFor} annotation with the given values - */ - public AnnotationMirror createKeyForAnnotationMirrorWithValue(Set values) { - // Create an AnnotationBuilder with the ArrayList - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), KeyFor.class); - builder.setValue("value", values.toArray()); - - // Return the resulting AnnotationMirror - return builder.build(); - } - - /** - * Given a string 'value', returns an AnnotationMirror corresponding to @KeyFor(value) - * - * @param value the argument to {@code @KeyFor} - * @return a {@code @KeyFor} annotation with the given value - */ - public AnnotationMirror createKeyForAnnotationMirrorWithValue(String value) { - return createKeyForAnnotationMirrorWithValue(Collections.singleton(value)); - } - - /** - * Returns true if the expression tree is a key for the map. - * - * @param mapExpression expression that has type Map - * @param tree expression that might be a key for the map - * @return whether or not the expression is a key for the map - */ - public boolean isKeyForMap(String mapExpression, ExpressionTree tree) { - // This test only has an effect if the Map Key Checker is being run on its own. If the - // Nullness Checker is being run, then -AassumeKeyFor disables the Map Key Checker. - if (assumeKeyFor) { - return true; - } - Collection maps = null; - AnnotatedTypeMirror type = getAnnotatedType(tree); - AnnotationMirror keyForAnno = type.getEffectiveAnnotation(KeyFor.class); - if (keyForAnno != null) { - maps = - AnnotationUtils.getElementValueArray( - keyForAnno, keyForValueElement, String.class); - } else { - KeyForValue value = getInferredValueFor(tree); - if (value != null) { - maps = value.getKeyForMaps(); - } - } - - return maps != null && maps.contains(mapExpression); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SubtypeIsSupersetQualifierHierarchy( - getSupportedTypeQualifiers(), processingEnv, KeyForAnnotatedTypeFactory.this); - } - - /** Returns true if the node is an invocation of Map.containsKey. */ - boolean isMapContainsKey(Tree tree) { - return TreeUtils.isMethodInvocation(tree, mapContainsKey, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.get. */ - boolean isMapGet(Tree tree) { - return TreeUtils.isMethodInvocation(tree, mapGet, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.put. */ - boolean isMapPut(Tree tree) { - return TreeUtils.isMethodInvocation(tree, mapPut, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.containsKey. */ - boolean isMapContainsKey(Node node) { - return NodeUtils.isMethodInvocation(node, mapContainsKey, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.get. */ - boolean isMapGet(Node node) { - return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.put. */ - boolean isMapPut(Node node) { - return NodeUtils.isMethodInvocation(node, mapPut, getProcessingEnv()); - } - - /** Returns false. Redundancy in the KeyFor hierarchy is not worth warning about. */ - @Override - public boolean shouldWarnIfStubRedundantWithBytecode() { - return false; - } + extends GenericAnnotatedTypeFactory { + + /** The @{@link UnknownKeyFor} annotation. */ + protected final AnnotationMirror UNKNOWNKEYFOR = + AnnotationBuilder.fromClass(elements, UnknownKeyFor.class); + + /** The @{@link KeyForBottom} annotation. */ + protected final AnnotationMirror KEYFORBOTTOM = + AnnotationBuilder.fromClass(elements, KeyForBottom.class); + + /** The canonical name of the KeyFor class. */ + protected static final @CanonicalName String KEYFOR_NAME = KeyFor.class.getCanonicalName(); + + /** The Map.containsKey method. */ + private final ExecutableElement mapContainsKey = + TreeUtils.getMethod("java.util.Map", "containsKey", 1, processingEnv); + + /** The Map.get method. */ + private final ExecutableElement mapGet = + TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); + + /** The Map.put method. */ + private final ExecutableElement mapPut = + TreeUtils.getMethod("java.util.Map", "put", 2, processingEnv); + + /** The KeyFor.value field/element. */ + protected final ExecutableElement keyForValueElement = + TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); + + /** Moves annotations from one side of a pseudo-assignment to the other. */ + private final KeyForPropagator keyForPropagator = new KeyForPropagator(UNKNOWNKEYFOR); + + /** + * If true, assume the argument to Map.get is always a key for the receiver map. This is set by + * the `-AassumeKeyFor` command-line argument. However, if the Nullness Checker is being run, then + * `-AassumeKeyFor` disables the Map Key Checker. + */ + private final boolean assumeKeyFor; + + /** + * Creates a new KeyForAnnotatedTypeFactory. + * + * @param checker the associated checker + */ + public KeyForAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + + assumeKeyFor = checker.hasOption("assumeKeyFor"); + + // Add compatibility annotations: + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.KeyForDecl", KeyFor.class, true); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.KeyForType", KeyFor.class, true); + + // While strictly required for soundness, this leads to too many false positives. Printing + // a key or putting it in a map erases all knowledge of what maps it was a key for. + // TODO: Revisit when side effect annotations are more precise. + // sideEffectsUnrefineAliases = true; + + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList(KeyFor.class, UnknownKeyFor.class, KeyForBottom.class, PolyKeyFor.class)); + } + + @Override + public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + ParameterizedExecutableType result = super.constructorFromUse(tree); + keyForPropagator.propagateNewClassTree(tree, result.executableType.getReturnType(), this); + return result; + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new KeyForPropagationTreeAnnotator(this, keyForPropagator)); + } + + @Override + protected KeyForAnalysis createFlowAnalysis() { + // Explicitly call the constructor instead of using reflection. + return new KeyForAnalysis(checker, this); + } + + @Override + public KeyForTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + // Explicitly call the constructor instead of using reflection. + return new KeyForTransfer((KeyForAnalysis) analysis); + } + + /** + * Given a string array 'values', returns an AnnotationMirror corresponding to @KeyFor(values) + * + * @param values the values for the {@code @KeyFor} annotation + * @return a {@code @KeyFor} annotation with the given values + */ + public AnnotationMirror createKeyForAnnotationMirrorWithValue(Set values) { + // Create an AnnotationBuilder with the ArrayList + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), KeyFor.class); + builder.setValue("value", values.toArray()); + + // Return the resulting AnnotationMirror + return builder.build(); + } + + /** + * Given a string 'value', returns an AnnotationMirror corresponding to @KeyFor(value) + * + * @param value the argument to {@code @KeyFor} + * @return a {@code @KeyFor} annotation with the given value + */ + public AnnotationMirror createKeyForAnnotationMirrorWithValue(String value) { + return createKeyForAnnotationMirrorWithValue(Collections.singleton(value)); + } + + /** + * Returns true if the expression tree is a key for the map. + * + * @param mapExpression expression that has type Map + * @param tree expression that might be a key for the map + * @return whether or not the expression is a key for the map + */ + public boolean isKeyForMap(String mapExpression, ExpressionTree tree) { + // This test only has an effect if the Map Key Checker is being run on its own. If the + // Nullness Checker is being run, then -AassumeKeyFor disables the Map Key Checker. + if (assumeKeyFor) { + return true; + } + Collection maps = null; + AnnotatedTypeMirror type = getAnnotatedType(tree); + AnnotationMirror keyForAnno = type.getEffectiveAnnotation(KeyFor.class); + if (keyForAnno != null) { + maps = AnnotationUtils.getElementValueArray(keyForAnno, keyForValueElement, String.class); + } else { + KeyForValue value = getInferredValueFor(tree); + if (value != null) { + maps = value.getKeyForMaps(); + } + } + + return maps != null && maps.contains(mapExpression); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new SubtypeIsSupersetQualifierHierarchy( + getSupportedTypeQualifiers(), processingEnv, KeyForAnnotatedTypeFactory.this); + } + + /** Returns true if the node is an invocation of Map.containsKey. */ + boolean isMapContainsKey(Tree tree) { + return TreeUtils.isMethodInvocation(tree, mapContainsKey, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.get. */ + boolean isMapGet(Tree tree) { + return TreeUtils.isMethodInvocation(tree, mapGet, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.put. */ + boolean isMapPut(Tree tree) { + return TreeUtils.isMethodInvocation(tree, mapPut, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.containsKey. */ + boolean isMapContainsKey(Node node) { + return NodeUtils.isMethodInvocation(node, mapContainsKey, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.get. */ + boolean isMapGet(Node node) { + return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.put. */ + boolean isMapPut(Node node) { + return NodeUtils.isMethodInvocation(node, mapPut, getProcessingEnv()); + } + + /** Returns false. Redundancy in the KeyFor hierarchy is not worth warning about. */ + @Override + public boolean shouldWarnIfStubRedundantWithBytecode() { + return false; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java index abacdbcbbfe..a51bf035eb6 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java @@ -3,7 +3,8 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.VariableTree; - +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.nullness.KeyForPropagator.PropagationDirection; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -11,9 +12,6 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; - /** * For the following initializations we wish to propagate the annotations from the left-hand side to * the right-hand side or vice versa: @@ -47,63 +45,61 @@ * AnnotatedDeclaredType then this class does nothing. */ public class KeyForPropagationTreeAnnotator extends TreeAnnotator { - private final KeyForPropagator keyForPropagator; - private final ExecutableElement keySetMethod; + private final KeyForPropagator keyForPropagator; + private final ExecutableElement keySetMethod; - public KeyForPropagationTreeAnnotator( - AnnotatedTypeFactory atypeFactory, KeyForPropagator propagationTreeAnnotator) { - super(atypeFactory); - this.keyForPropagator = propagationTreeAnnotator; - keySetMethod = - TreeUtils.getMethod("java.util.Map", "keySet", 0, atypeFactory.getProcessingEnv()); - } + public KeyForPropagationTreeAnnotator( + AnnotatedTypeFactory atypeFactory, KeyForPropagator propagationTreeAnnotator) { + super(atypeFactory); + this.keyForPropagator = propagationTreeAnnotator; + keySetMethod = + TreeUtils.getMethod("java.util.Map", "keySet", 0, atypeFactory.getProcessingEnv()); + } - /** - * Returns true iff expression is a call to java.util.Map.KeySet. - * - * @return true iff expression is a call to java.util.Map.KeySet - */ - public boolean isCallToKeyset(ExpressionTree expression) { - return TreeUtils.isMethodInvocation( - expression, keySetMethod, atypeFactory.getProcessingEnv()); - } + /** + * Returns true iff expression is a call to java.util.Map.KeySet. + * + * @return true iff expression is a call to java.util.Map.KeySet + */ + public boolean isCallToKeyset(ExpressionTree expression) { + return TreeUtils.isMethodInvocation(expression, keySetMethod, atypeFactory.getProcessingEnv()); + } - /** - * Transfers annotations on type arguments from the initializer to the variableTree, if the - * initializer is a call to java.util.Map.keySet. - */ - @Override - public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { - super.visitVariable(variableTree, type); + /** + * Transfers annotations on type arguments from the initializer to the variableTree, if the + * initializer is a call to java.util.Map.keySet. + */ + @Override + public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { + super.visitVariable(variableTree, type); - // This should only happen on Map.keySet(); - if (type.getKind() == TypeKind.DECLARED) { - ExpressionTree initializer = variableTree.getInitializer(); + // This should only happen on Map.keySet(); + if (type.getKind() == TypeKind.DECLARED) { + ExpressionTree initializer = variableTree.getInitializer(); - if (isCallToKeyset(initializer)) { - AnnotatedDeclaredType variableType = (AnnotatedDeclaredType) type; - AnnotatedTypeMirror initializerType = atypeFactory.getAnnotatedType(initializer); + if (isCallToKeyset(initializer)) { + AnnotatedDeclaredType variableType = (AnnotatedDeclaredType) type; + AnnotatedTypeMirror initializerType = atypeFactory.getAnnotatedType(initializer); - // Propagate just for declared (class) types, not for array types, boxed primitives, - // etc. - if (variableType.getKind() == TypeKind.DECLARED) { - keyForPropagator.propagate( - (AnnotatedDeclaredType) initializerType, - variableType, - PropagationDirection.TO_SUPERTYPE, - atypeFactory); - } - } + // Propagate just for declared (class) types, not for array types, boxed primitives, + // etc. + if (variableType.getKind() == TypeKind.DECLARED) { + keyForPropagator.propagate( + (AnnotatedDeclaredType) initializerType, + variableType, + PropagationDirection.TO_SUPERTYPE, + atypeFactory); } - - return null; + } } - /** Transfers annotations to type if the left hand side is a variable declaration. */ - @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - keyForPropagator.propagateNewClassTree( - tree, type, (KeyForAnnotatedTypeFactory) atypeFactory); - return super.visitNewClass(tree, type); - } + return null; + } + + /** Transfers annotations to type if the left hand side is a variable declaration. */ + @Override + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + keyForPropagator.propagateNewClassTree(tree, type, (KeyForAnnotatedTypeFactory) atypeFactory); + return super.visitNewClass(tree, type); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java index 0cee0aee4ab..32e73394207 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java @@ -4,7 +4,12 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.UnknownKeyFor; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -15,14 +20,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Types; - /** * KeyForPropagator is used to move nested KeyFor annotations in type arguments from one side of a * pseudo-assignment to the other. The KeyForPropagationTreeAnnotator details the locations in which @@ -31,180 +28,180 @@ * @see org.checkerframework.checker.nullness.KeyForPropagationTreeAnnotator */ public class KeyForPropagator { - public enum PropagationDirection { - // transfer FROM the super type to the subtype - TO_SUBTYPE, - - // transfer FROM the subtype to the supertype - TO_SUPERTYPE, - - // first execute TO_SUBTYPE then TO_SUPERTYPE, if TO_SUBTYPE actually transfers - // an annotation for a particular type T then T will not be affected by the - // TO_SUPERTYPE transfer because it will already have a KeyFor annotation - BOTH - } - - /** - * The top type of the KeyFor hierarchy. - * - *

This class will replace @UnknownKeyFor annotations. It will also add annotations when they - * are missing for types that require primary annotation (i.e. not TypeVars, Wildcards, - * Intersections, or Unions). - */ - private final AnnotationMirror UNKNOWN_KEYFOR; - - /** Instance of {@link KeyForPropagationReplacer}. */ - private final KeyForPropagationReplacer replacer = new KeyForPropagationReplacer(); - - /** - * Creates a KeyForPropagator - * - * @param unknownKeyfor an {@link UnknownKeyFor} annotation - */ - public KeyForPropagator(AnnotationMirror unknownKeyfor) { - this.UNKNOWN_KEYFOR = unknownKeyfor; + public enum PropagationDirection { + // transfer FROM the super type to the subtype + TO_SUBTYPE, + + // transfer FROM the subtype to the supertype + TO_SUPERTYPE, + + // first execute TO_SUBTYPE then TO_SUPERTYPE, if TO_SUBTYPE actually transfers + // an annotation for a particular type T then T will not be affected by the + // TO_SUPERTYPE transfer because it will already have a KeyFor annotation + BOTH + } + + /** + * The top type of the KeyFor hierarchy. + * + *

This class will replace @UnknownKeyFor annotations. It will also add annotations when they + * are missing for types that require primary annotation (i.e. not TypeVars, Wildcards, + * Intersections, or Unions). + */ + private final AnnotationMirror UNKNOWN_KEYFOR; + + /** Instance of {@link KeyForPropagationReplacer}. */ + private final KeyForPropagationReplacer replacer = new KeyForPropagationReplacer(); + + /** + * Creates a KeyForPropagator + * + * @param unknownKeyfor an {@link UnknownKeyFor} annotation + */ + public KeyForPropagator(AnnotationMirror unknownKeyfor) { + this.UNKNOWN_KEYFOR = unknownKeyfor; + } + + /** + * Propagate annotations from the type arguments of one type to another. Which type is the source + * and destination of the annotations depends on the direction parameter. Only @KeyFor annotations + * are propagated and only if the type to which it would be propagated contains an @UnknownKeyFor + * or contains no key for annotations of any kind. If any of the type arguments are wildcards than + * they are ignored. + * + *

Note the primary annotations of subtype/supertype are not used. + * + *

Simple Example: + * + *

{@code
+   * typeOf(subtype) = ArrayList<@KeyFor("a") String>
+   * typeOf(supertype) = List<@UnknownKeyFor String>
+   * direction = TO_SUPERTYPE
+   * }
+ * + * The type of supertype after propagate would be: {@code List<@KeyFor("a") String>} + * + *

A more complex example would be: + * + *

{@code
+   * typeOf(subtype) = HashMap<@UnknownKeyFor String, @KeyFor("b") List<@KeyFor("c") String>>
+   * typeOf(supertype) = Map<@KeyFor("a") String, @KeyFor("b") List<@KeyFor("c") String>>
+   * direction = TO_SUBTYPE
+   * }
+ * + * The type of subtype after propagate would be: {@code HashMap<@KeyFor("a") String, @KeyFor("b") + * List<@KeyFor("c") String>>} + */ + public void propagate( + AnnotatedDeclaredType subtype, + AnnotatedDeclaredType supertype, + PropagationDirection direction, + AnnotatedTypeFactory typeFactory) { + TypeElement subtypeElement = (TypeElement) subtype.getUnderlyingType().asElement(); + TypeElement supertypeElement = (TypeElement) supertype.getUnderlyingType().asElement(); + Types types = typeFactory.getProcessingEnv().getTypeUtils(); + + // Note: The right hand side of this or expression will cover raw types + if (subtype.getTypeArguments().isEmpty()) { + return; + } // else + + // this can happen for two reasons: + // 1) the subclass introduced NEW type arguments when the superclass had none + // 2) the supertype was RAW. + // In either case, there is no reason to propagate + if (supertype.getTypeArguments().isEmpty()) { + return; } - /** - * Propagate annotations from the type arguments of one type to another. Which type is the - * source and destination of the annotations depends on the direction parameter. Only @KeyFor - * annotations are propagated and only if the type to which it would be propagated contains - * an @UnknownKeyFor or contains no key for annotations of any kind. If any of the type - * arguments are wildcards than they are ignored. - * - *

Note the primary annotations of subtype/supertype are not used. - * - *

Simple Example: - * - *

{@code
-     * typeOf(subtype) = ArrayList<@KeyFor("a") String>
-     * typeOf(supertype) = List<@UnknownKeyFor String>
-     * direction = TO_SUPERTYPE
-     * }
- * - * The type of supertype after propagate would be: {@code List<@KeyFor("a") String>} - * - *

A more complex example would be: - * - *

{@code
-     * typeOf(subtype) = HashMap<@UnknownKeyFor String, @KeyFor("b") List<@KeyFor("c") String>>
-     * typeOf(supertype) = Map<@KeyFor("a") String, @KeyFor("b") List<@KeyFor("c") String>>
-     * direction = TO_SUBTYPE
-     * }
- * - * The type of subtype after propagate would be: {@code HashMap<@KeyFor("a") - * String, @KeyFor("b") List<@KeyFor("c") String>>} - */ - public void propagate( - AnnotatedDeclaredType subtype, - AnnotatedDeclaredType supertype, - PropagationDirection direction, - AnnotatedTypeFactory typeFactory) { - TypeElement subtypeElement = (TypeElement) subtype.getUnderlyingType().asElement(); - TypeElement supertypeElement = (TypeElement) supertype.getUnderlyingType().asElement(); - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - - // Note: The right hand side of this or expression will cover raw types - if (subtype.getTypeArguments().isEmpty()) { - return; - } // else - - // this can happen for two reasons: - // 1) the subclass introduced NEW type arguments when the superclass had none - // 2) the supertype was RAW. - // In either case, there is no reason to propagate - if (supertype.getTypeArguments().isEmpty()) { - return; - } - - Set> typeParamMappings = - TypeArgumentMapper.mapTypeArgumentIndices(subtypeElement, supertypeElement, types); + Set> typeParamMappings = + TypeArgumentMapper.mapTypeArgumentIndices(subtypeElement, supertypeElement, types); - List subtypeArgs = subtype.getTypeArguments(); - List supertypeArgs = supertype.getTypeArguments(); + List subtypeArgs = subtype.getTypeArguments(); + List supertypeArgs = supertype.getTypeArguments(); - for (IPair path : typeParamMappings) { - AnnotatedTypeMirror subtypeArg = subtypeArgs.get(path.first); - AnnotatedTypeMirror supertypeArg = supertypeArgs.get(path.second); + for (IPair path : typeParamMappings) { + AnnotatedTypeMirror subtypeArg = subtypeArgs.get(path.first); + AnnotatedTypeMirror supertypeArg = supertypeArgs.get(path.second); - if (subtypeArg.getKind() == TypeKind.WILDCARD - || supertypeArg.getKind() == TypeKind.WILDCARD) { - continue; - } + if (subtypeArg.getKind() == TypeKind.WILDCARD + || supertypeArg.getKind() == TypeKind.WILDCARD) { + continue; + } - switch (direction) { - case TO_SUBTYPE: - replacer.visit(supertypeArg, subtypeArg); - break; + switch (direction) { + case TO_SUBTYPE: + replacer.visit(supertypeArg, subtypeArg); + break; - case TO_SUPERTYPE: - replacer.visit(subtypeArg, supertypeArg); - break; + case TO_SUPERTYPE: + replacer.visit(subtypeArg, supertypeArg); + break; - case BOTH: - // note if they both have an annotation nothing will happen - replacer.visit(subtypeArg, supertypeArg); - replacer.visit(supertypeArg, subtypeArg); - break; - } - } + case BOTH: + // note if they both have an annotation nothing will happen + replacer.visit(subtypeArg, supertypeArg); + replacer.visit(supertypeArg, subtypeArg); + break; + } } - - /** - * Propagate annotations from the type arguments of {@code type} to the assignment context of - * {@code newClassTree} if one exists. - * - * @param newClassTree new class tree - * @param type annotated type of {@code newClassTree} - * @param atypeFactory factory - */ - public void propagateNewClassTree( - NewClassTree newClassTree, - AnnotatedTypeMirror type, - KeyForAnnotatedTypeFactory atypeFactory) { - if (type.getKind() != TypeKind.DECLARED || TreeUtils.isDiamondTree(newClassTree)) { - return; - } - TreePath path = atypeFactory.getPath(newClassTree); - if (path == null) { - return; - } - Tree assignmentContext = TreePathUtil.getAssignmentContext(path); - AnnotatedTypeMirror assignedTo; - if (assignmentContext instanceof VariableTree) { - if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) assignmentContext)) { - return; - } - assignedTo = atypeFactory.getAnnotatedTypeLhs(assignmentContext); - } else { - return; - } - - // array types and boxed primitives etc don't require propagation - if (assignedTo.getKind() == TypeKind.DECLARED) { - propagate( - (AnnotatedDeclaredType) type, - (AnnotatedDeclaredType) assignedTo, - PropagationDirection.TO_SUBTYPE, - atypeFactory); - } + } + + /** + * Propagate annotations from the type arguments of {@code type} to the assignment context of + * {@code newClassTree} if one exists. + * + * @param newClassTree new class tree + * @param type annotated type of {@code newClassTree} + * @param atypeFactory factory + */ + public void propagateNewClassTree( + NewClassTree newClassTree, + AnnotatedTypeMirror type, + KeyForAnnotatedTypeFactory atypeFactory) { + if (type.getKind() != TypeKind.DECLARED || TreeUtils.isDiamondTree(newClassTree)) { + return; + } + TreePath path = atypeFactory.getPath(newClassTree); + if (path == null) { + return; + } + Tree assignmentContext = TreePathUtil.getAssignmentContext(path); + AnnotatedTypeMirror assignedTo; + if (assignmentContext instanceof VariableTree) { + if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) assignmentContext)) { + return; + } + assignedTo = atypeFactory.getAnnotatedTypeLhs(assignmentContext); + } else { + return; } - /** - * An {@link AnnotatedTypeReplacer} that copies the annotation in KeyFor hierarchy from the - * first types to the second type, if the second type is annotated with @UnknownKeyFor or has no - * annotation in the KeyFor hierarchy. - */ - private class KeyForPropagationReplacer extends AnnotatedTypeReplacer { - @Override - protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - AnnotationMirror fromKeyFor = from.getAnnotationInHierarchy(UNKNOWN_KEYFOR); - if (fromKeyFor != null) { - if (to.hasAnnotation(UNKNOWN_KEYFOR) - || to.getAnnotationInHierarchy(UNKNOWN_KEYFOR) == null) { - to.replaceAnnotation(fromKeyFor); - } - } + // array types and boxed primitives etc don't require propagation + if (assignedTo.getKind() == TypeKind.DECLARED) { + propagate( + (AnnotatedDeclaredType) type, + (AnnotatedDeclaredType) assignedTo, + PropagationDirection.TO_SUBTYPE, + atypeFactory); + } + } + + /** + * An {@link AnnotatedTypeReplacer} that copies the annotation in KeyFor hierarchy from the first + * types to the second type, if the second type is annotated with @UnknownKeyFor or has no + * annotation in the KeyFor hierarchy. + */ + private class KeyForPropagationReplacer extends AnnotatedTypeReplacer { + @Override + protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + AnnotationMirror fromKeyFor = from.getAnnotationInHierarchy(UNKNOWN_KEYFOR); + if (fromKeyFor != null) { + if (to.hasAnnotation(UNKNOWN_KEYFOR) + || to.getAnnotationInHierarchy(UNKNOWN_KEYFOR) == null) { + to.replaceAnnotation(fromKeyFor); } + } } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java index 75f09cb88db..f59c9ef4614 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java @@ -4,12 +4,12 @@ import org.checkerframework.framework.flow.CFAbstractStore; public class KeyForStore extends CFAbstractStore { - public KeyForStore( - CFAbstractAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - } + public KeyForStore( + CFAbstractAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } - protected KeyForStore(CFAbstractStore other) { - super(other); - } + protected KeyForStore(CFAbstractStore other) { + super(other); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java index 1aa71bcf0c2..bce1270fce6 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java @@ -1,8 +1,7 @@ package org.checkerframework.checker.nullness; -import org.checkerframework.common.basetype.BaseTypeChecker; - import javax.annotation.processing.SupportedOptions; +import org.checkerframework.common.basetype.BaseTypeChecker; /** * A type-checker for determining which values are keys for which maps. Typically used as part of diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java index 3061400b306..4898845185b 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java @@ -1,5 +1,11 @@ package org.checkerframework.checker.nullness; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; @@ -10,95 +16,87 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; - /** * KeyForTransfer ensures that java.util.Map.put and containsKey cause the appropriate @KeyFor * annotation to be added to the key. */ public class KeyForTransfer extends CFAbstractTransfer { - /** The KeyFor.value element/field. */ - protected final ExecutableElement keyForValueElement; - - /** - * Creates a new KeyForTransfer. - * - * @param analysis the analysis - */ - public KeyForTransfer(KeyForAnalysis analysis) { - super(analysis); - - ProcessingEnvironment processingEnv = - ((KeyForAnnotatedTypeFactory) analysis.getTypeFactory()).getProcessingEnv(); - keyForValueElement = TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); - } - - /* - * Provided that m is of a type that implements interface java.util.Map: - *
    - *
  • Given a call m.containsKey(k), ensures that k is @KeyFor("m") in the thenStore of the transfer result. - *
  • Given a call m.put(k, ...), ensures that k is @KeyFor("m") in the thenStore and elseStore of the transfer result. - *
- */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput in) { - - TransferResult result = super.visitMethodInvocation(node, in); - KeyForAnnotatedTypeFactory factory = (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); - if (factory.isMapContainsKey(node) || factory.isMapPut(node)) { - - Node receiver = node.getTarget().getReceiver(); - JavaExpression receiverJe = JavaExpression.fromNode(receiver); - String mapName = receiverJe.toString(); - JavaExpression keyExpr = JavaExpression.fromNode(node.getArgument(0)); - - LinkedHashSet keyForMaps = new LinkedHashSet<>(); - keyForMaps.add(mapName); - - KeyForValue previousKeyValue = in.getValueOfSubNode(node.getArgument(0)); - if (previousKeyValue != null) { - for (AnnotationMirror prevAm : previousKeyValue.getAnnotations()) { - if (prevAm != null && factory.areSameByClass(prevAm, KeyFor.class)) { - keyForMaps.addAll(getKeys(prevAm)); - } - } - } - - AnnotationMirror am = factory.createKeyForAnnotationMirrorWithValue(keyForMaps); - - if (factory.isMapContainsKey(node)) { - // method is Map.containsKey - result.getThenStore().insertValue(keyExpr, am); - } else { - // method is Map.put - result.getThenStore().insertValue(keyExpr, am); - result.getElseStore().insertValue(keyExpr, am); - } + /** The KeyFor.value element/field. */ + protected final ExecutableElement keyForValueElement; + + /** + * Creates a new KeyForTransfer. + * + * @param analysis the analysis + */ + public KeyForTransfer(KeyForAnalysis analysis) { + super(analysis); + + ProcessingEnvironment processingEnv = + ((KeyForAnnotatedTypeFactory) analysis.getTypeFactory()).getProcessingEnv(); + keyForValueElement = TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); + } + + /* + * Provided that m is of a type that implements interface java.util.Map: + *
    + *
  • Given a call m.containsKey(k), ensures that k is @KeyFor("m") in the thenStore of the transfer result. + *
  • Given a call m.put(k, ...), ensures that k is @KeyFor("m") in the thenStore and elseStore of the transfer result. + *
+ */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput in) { + + TransferResult result = super.visitMethodInvocation(node, in); + KeyForAnnotatedTypeFactory factory = (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); + if (factory.isMapContainsKey(node) || factory.isMapPut(node)) { + + Node receiver = node.getTarget().getReceiver(); + JavaExpression receiverJe = JavaExpression.fromNode(receiver); + String mapName = receiverJe.toString(); + JavaExpression keyExpr = JavaExpression.fromNode(node.getArgument(0)); + + LinkedHashSet keyForMaps = new LinkedHashSet<>(); + keyForMaps.add(mapName); + + KeyForValue previousKeyValue = in.getValueOfSubNode(node.getArgument(0)); + if (previousKeyValue != null) { + for (AnnotationMirror prevAm : previousKeyValue.getAnnotations()) { + if (prevAm != null && factory.areSameByClass(prevAm, KeyFor.class)) { + keyForMaps.addAll(getKeys(prevAm)); + } } - - return result; + } + + AnnotationMirror am = factory.createKeyForAnnotationMirrorWithValue(keyForMaps); + + if (factory.isMapContainsKey(node)) { + // method is Map.containsKey + result.getThenStore().insertValue(keyExpr, am); + } else { + // method is Map.put + result.getThenStore().insertValue(keyExpr, am); + result.getElseStore().insertValue(keyExpr, am); + } } - /** - * Returns the elements/arguments of a {@code @KeyFor} annotation. - * - * @param keyFor a {@code @KeyFor} annotation - * @return the elements/arguments of a {@code @KeyFor} annotation - */ - private Set getKeys(AnnotationMirror keyFor) { - if (keyFor.getElementValues().isEmpty()) { - return Collections.emptySet(); - } - - return new LinkedHashSet<>( - AnnotationUtils.getElementValueArray(keyFor, keyForValueElement, String.class)); + return result; + } + + /** + * Returns the elements/arguments of a {@code @KeyFor} annotation. + * + * @param keyFor a {@code @KeyFor} annotation + * @return the elements/arguments of a {@code @KeyFor} annotation + */ + private Set getKeys(AnnotationMirror keyFor) { + if (keyFor.getElementValues().isEmpty()) { + return Collections.emptySet(); } + + return new LinkedHashSet<>( + AnnotationUtils.getElementValueArray(keyFor, keyForValueElement, String.class)); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java index 3f6c231f503..9c04223489d 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java @@ -1,7 +1,12 @@ package org.checkerframework.checker.nullness; import com.sun.source.tree.ExpressionTree; - +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.flow.CFAbstractAnalysis; @@ -10,14 +15,6 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.plumelib.util.CollectionsPlume; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * KeyForValue holds additional information about which maps this value is a key for. This extra * information is required when adding the @KeyFor qualifier to the type is not a refinement of the @@ -43,103 +40,103 @@ * is used in {@link KeyForAnnotatedTypeFactory#isKeyForMap(String, ExpressionTree)}. */ public class KeyForValue extends CFAbstractValue { - /** - * If the underlying type is a type variable or a wildcard, then this is a set of maps for which - * this value is a key. Otherwise, it's null. - */ - // Cannot be final because lub re-assigns; add a new constructor to do this cleanly? - private @Nullable Set keyForMaps; + /** + * If the underlying type is a type variable or a wildcard, then this is a set of maps for which + * this value is a key. Otherwise, it's null. + */ + // Cannot be final because lub re-assigns; add a new constructor to do this cleanly? + private @Nullable Set keyForMaps; - /** - * Create a KeyForValue. - * - * @param analysis the analysis - * @param annotations the annotations - * @param underlyingType the underlying type - */ - public KeyForValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - KeyForAnnotatedTypeFactory atypeFactory = - (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); - AnnotationMirror keyfor = atypeFactory.getAnnotationByClass(annotations, KeyFor.class); - if (keyfor != null - && (underlyingType.getKind() == TypeKind.TYPEVAR - || underlyingType.getKind() == TypeKind.WILDCARD)) { - List list = - AnnotationUtils.getElementValueArray( - keyfor, atypeFactory.keyForValueElement, String.class); - keyForMaps = new LinkedHashSet<>(list.size()); - keyForMaps.addAll(list); - } else { - keyForMaps = null; - } + /** + * Create a KeyForValue. + * + * @param analysis the analysis + * @param annotations the annotations + * @param underlyingType the underlying type + */ + public KeyForValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + KeyForAnnotatedTypeFactory atypeFactory = + (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); + AnnotationMirror keyfor = atypeFactory.getAnnotationByClass(annotations, KeyFor.class); + if (keyfor != null + && (underlyingType.getKind() == TypeKind.TYPEVAR + || underlyingType.getKind() == TypeKind.WILDCARD)) { + List list = + AnnotationUtils.getElementValueArray( + keyfor, atypeFactory.keyForValueElement, String.class); + keyForMaps = new LinkedHashSet<>(list.size()); + keyForMaps.addAll(list); + } else { + keyForMaps = null; } + } - /** - * If the underlying type is a type variable or a wildcard, then this is a set of maps for which - * this value is a key. Otherwise, it's null. - */ - public @Nullable Set getKeyForMaps() { - return keyForMaps; - } + /** + * If the underlying type is a type variable or a wildcard, then this is a set of maps for which + * this value is a key. Otherwise, it's null. + */ + public @Nullable Set getKeyForMaps() { + return keyForMaps; + } - @Override - protected KeyForValue upperBound( - @Nullable KeyForValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { - KeyForValue upperBound = super.upperBound(other, upperBoundTypeMirror, shouldWiden); + @Override + protected KeyForValue upperBound( + @Nullable KeyForValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { + KeyForValue upperBound = super.upperBound(other, upperBoundTypeMirror, shouldWiden); - if (other == null || other.keyForMaps == null || this.keyForMaps == null) { - return upperBound; - } - // Lub the keyForMaps by intersecting the sets. - upperBound.keyForMaps = new LinkedHashSet<>(this.keyForMaps.size()); - upperBound.keyForMaps.addAll(this.keyForMaps); - upperBound.keyForMaps.retainAll(other.keyForMaps); - if (upperBound.keyForMaps.isEmpty()) { - upperBound.keyForMaps = null; - } - return upperBound; + if (other == null || other.keyForMaps == null || this.keyForMaps == null) { + return upperBound; + } + // Lub the keyForMaps by intersecting the sets. + upperBound.keyForMaps = new LinkedHashSet<>(this.keyForMaps.size()); + upperBound.keyForMaps.addAll(this.keyForMaps); + upperBound.keyForMaps.retainAll(other.keyForMaps); + if (upperBound.keyForMaps.isEmpty()) { + upperBound.keyForMaps = null; } + return upperBound; + } - @Override - public @Nullable KeyForValue mostSpecific( - @Nullable KeyForValue other, @Nullable KeyForValue backup) { - KeyForValue mostSpecific = super.mostSpecific(other, backup); - if (mostSpecific == null) { - if (other == null) { - return this; - } - // mostSpecific is null if the two types are not comparable. This is normally - // because one of this or other is a type variable and annotations is empty, but the - // other annotations are not empty. In this case, copy the keyForMaps and to the - // value with the no annotations and return it as most specific. - if (other.getAnnotations().isEmpty()) { - other.addKeyFor(this.keyForMaps); - return other; - } else if (this.getAnnotations().isEmpty()) { - this.addKeyFor(other.keyForMaps); - return this; - } - return null; - } + @Override + public @Nullable KeyForValue mostSpecific( + @Nullable KeyForValue other, @Nullable KeyForValue backup) { + KeyForValue mostSpecific = super.mostSpecific(other, backup); + if (mostSpecific == null) { + if (other == null) { + return this; + } + // mostSpecific is null if the two types are not comparable. This is normally + // because one of this or other is a type variable and annotations is empty, but the + // other annotations are not empty. In this case, copy the keyForMaps and to the + // value with the no annotations and return it as most specific. + if (other.getAnnotations().isEmpty()) { + other.addKeyFor(this.keyForMaps); + return other; + } else if (this.getAnnotations().isEmpty()) { + this.addKeyFor(other.keyForMaps); + return this; + } + return null; + } - mostSpecific.addKeyFor(this.keyForMaps); - if (other != null) { - mostSpecific.addKeyFor(other.keyForMaps); - } - return mostSpecific; + mostSpecific.addKeyFor(this.keyForMaps); + if (other != null) { + mostSpecific.addKeyFor(other.keyForMaps); } + return mostSpecific; + } - private void addKeyFor(Set newKeyForMaps) { - if (newKeyForMaps == null || newKeyForMaps.isEmpty()) { - return; - } - if (keyForMaps == null) { - keyForMaps = new LinkedHashSet<>(CollectionsPlume.mapCapacity(newKeyForMaps.size())); - } - keyForMaps.addAll(newKeyForMaps); + private void addKeyFor(Set newKeyForMaps) { + if (newKeyForMaps == null || newKeyForMaps.isEmpty()) { + return; + } + if (keyForMaps == null) { + keyForMaps = new LinkedHashSet<>(CollectionsPlume.mapCapacity(newKeyForMaps.size())); } + keyForMaps.addAll(newKeyForMaps); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java index 6783774d122..9ec35cdece4 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.nullness; +import javax.annotation.processing.SupportedOptions; import org.checkerframework.checker.initialization.InitializationChecker; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.source.SupportedLintOptions; -import javax.annotation.processing.SupportedOptions; - /** * An implementation of the nullness type-system, parameterized by an initialization type-system for * safe initialization. It uses freedom-before-commitment, augmented by type frames (which are @@ -32,72 +31,72 @@ * @checker_framework.manual #nullness-checker Nullness Checker */ @SupportedLintOptions({ - NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, - NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, - // Temporary option to forbid non-null array component types, which is allowed by default. - // Forbidding is sound and will eventually be the default. - // Allowing is unsound, as described in Section 3.3.4, "Nullness and arrays": - // https://eisop.github.io/cf/manual/#nullness-arrays - // It is the default temporarily, until we improve the analysis to reduce false positives or we - // learn what advice to give programmers about avoid false positive warnings. - // See issue #986: https://github.com/typetools/checker-framework/issues/986 - "soundArrayCreationNullness", - // Old name for soundArrayCreationNullness, for backward compatibility; remove in January 2021. - "forbidnonnullarraycomponents", - NullnessChecker.LINT_TRUSTARRAYLENZERO, - NullnessChecker.LINT_PERMITCLEARPROPERTY, + NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, + // Temporary option to forbid non-null array component types, which is allowed by default. + // Forbidding is sound and will eventually be the default. + // Allowing is unsound, as described in Section 3.3.4, "Nullness and arrays": + // https://eisop.github.io/cf/manual/#nullness-arrays + // It is the default temporarily, until we improve the analysis to reduce false positives or we + // learn what advice to give programmers about avoid false positive warnings. + // See issue #986: https://github.com/typetools/checker-framework/issues/986 + "soundArrayCreationNullness", + // Old name for soundArrayCreationNullness, for backward compatibility; remove in January 2021. + "forbidnonnullarraycomponents", + NullnessChecker.LINT_TRUSTARRAYLENZERO, + NullnessChecker.LINT_PERMITCLEARPROPERTY, }) @SupportedOptions({ - "assumeKeyFor", - "assumeInitialized", - "jspecifyNullMarkedAlias", - "conservativeArgumentNullnessAfterInvocation" + "assumeKeyFor", + "assumeInitialized", + "jspecifyNullMarkedAlias", + "conservativeArgumentNullnessAfterInvocation" }) public class NullnessChecker extends InitializationChecker { - /** Should we be strict about initialization of {@link MonotonicNonNull} variables? */ - public static final String LINT_NOINITFORMONOTONICNONNULL = "noInitForMonotonicNonNull"; + /** Should we be strict about initialization of {@link MonotonicNonNull} variables? */ + public static final String LINT_NOINITFORMONOTONICNONNULL = "noInitForMonotonicNonNull"; - /** Default for {@link #LINT_NOINITFORMONOTONICNONNULL}. */ - public static final boolean LINT_DEFAULT_NOINITFORMONOTONICNONNULL = false; + /** Default for {@link #LINT_NOINITFORMONOTONICNONNULL}. */ + public static final boolean LINT_DEFAULT_NOINITFORMONOTONICNONNULL = false; - /** - * Warn about redundant comparisons of an expression with {@code null}, if the expression is - * known to be non-null. - */ - public static final String LINT_REDUNDANTNULLCOMPARISON = "redundantNullComparison"; + /** + * Warn about redundant comparisons of an expression with {@code null}, if the expression is known + * to be non-null. + */ + public static final String LINT_REDUNDANTNULLCOMPARISON = "redundantNullComparison"; - /** Default for {@link #LINT_REDUNDANTNULLCOMPARISON}. */ - public static final boolean LINT_DEFAULT_REDUNDANTNULLCOMPARISON = false; + /** Default for {@link #LINT_REDUNDANTNULLCOMPARISON}. */ + public static final boolean LINT_DEFAULT_REDUNDANTNULLCOMPARISON = false; - /** - * Should the Nullness Checker unsoundly trust {@code @ArrayLen(0)} annotations to improve - * handling of {@link java.util.Collection#toArray()} by {@link CollectionToArrayHeuristics}? - */ - public static final String LINT_TRUSTARRAYLENZERO = "trustArrayLenZero"; + /** + * Should the Nullness Checker unsoundly trust {@code @ArrayLen(0)} annotations to improve + * handling of {@link java.util.Collection#toArray()} by {@link CollectionToArrayHeuristics}? + */ + public static final String LINT_TRUSTARRAYLENZERO = "trustArrayLenZero"; - /** Default for {@link #LINT_TRUSTARRAYLENZERO}. */ - public static final boolean LINT_DEFAULT_TRUSTARRAYLENZERO = false; + /** Default for {@link #LINT_TRUSTARRAYLENZERO}. */ + public static final boolean LINT_DEFAULT_TRUSTARRAYLENZERO = false; - /** - * If true, client code may clear system properties. If false (the default), some calls to - * {@code System.getProperty} are refined to return @NonNull. - */ - public static final String LINT_PERMITCLEARPROPERTY = "permitClearProperty"; + /** + * If true, client code may clear system properties. If false (the default), some calls to {@code + * System.getProperty} are refined to return @NonNull. + */ + public static final String LINT_PERMITCLEARPROPERTY = "permitClearProperty"; - /** Default for {@link #LINT_PERMITCLEARPROPERTY}. */ - public static final boolean LINT_DEFAULT_PERMITCLEARPROPERTY = false; + /** Default for {@link #LINT_PERMITCLEARPROPERTY}. */ + public static final boolean LINT_DEFAULT_PERMITCLEARPROPERTY = false; - /** Default constructor for NullnessChecker. */ - public NullnessChecker() {} + /** Default constructor for NullnessChecker. */ + public NullnessChecker() {} - @Override - public boolean checkPrimitives() { - return false; - } + @Override + public boolean checkPrimitives() { + return false; + } - @Override - public Class getTargetCheckerClass() { - return NullnessNoInitSubchecker.class; - } + @Override + public Class getTargetCheckerClass() { + return NullnessNoInitSubchecker.class; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java index f1ff4ca3c0a..9ef53ddadb6 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java @@ -1,48 +1,46 @@ package org.checkerframework.checker.nullness; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.type.TypeMirror; - /** * The analysis class for the non-null type system (serves as factory for the transfer function, * stores and abstract values. */ public class NullnessNoInitAnalysis - extends CFAbstractAnalysis< - NullnessNoInitValue, NullnessNoInitStore, NullnessNoInitTransfer> { + extends CFAbstractAnalysis { - /** - * Creates a new {@code NullnessAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public NullnessNoInitAnalysis( - BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { - super(checker, factory); - } + /** + * Creates a new {@code NullnessAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public NullnessNoInitAnalysis( + BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { + super(checker, factory); + } - @Override - public NullnessNoInitStore createEmptyStore(boolean sequentialSemantics) { - return new NullnessNoInitStore(this, sequentialSemantics); - } + @Override + public NullnessNoInitStore createEmptyStore(boolean sequentialSemantics) { + return new NullnessNoInitStore(this, sequentialSemantics); + } - @Override - public NullnessNoInitStore createCopiedStore(NullnessNoInitStore s) { - return new NullnessNoInitStore(s); - } + @Override + public NullnessNoInitStore createCopiedStore(NullnessNoInitStore s) { + return new NullnessNoInitStore(s); + } - @Override - public @Nullable NullnessNoInitValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; - } - return new NullnessNoInitValue(this, annotations, underlyingType); + @Override + public @Nullable NullnessNoInitValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; } + return new NullnessNoInitValue(this, annotations, underlyingType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java index ea521789880..8cf8b6c16b6 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java @@ -15,7 +15,18 @@ import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.initialization.InitializationFieldAccessAnnotatedTypeFactory; import org.checkerframework.checker.initialization.InitializationFieldAccessSubchecker; import org.checkerframework.checker.initialization.InitializationFieldAccessTreeAnnotator; @@ -58,1020 +69,986 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - /** The annotated type factory for the nullness type-system. */ public class NullnessNoInitAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory< - NullnessNoInitValue, - NullnessNoInitStore, - NullnessNoInitTransfer, - NullnessNoInitAnalysis> { - - /** The @{@link NonNull} annotation. */ - protected final AnnotationMirror NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); - - /** The @{@link Nullable} annotation. */ - protected final AnnotationMirror NULLABLE = - AnnotationBuilder.fromClass(elements, Nullable.class); - - /** The @{@link PolyNull} annotation. */ - protected final AnnotationMirror POLYNULL = - AnnotationBuilder.fromClass(elements, PolyNull.class); - - /** The @{@link MonotonicNonNull} annotation. */ - protected final AnnotationMirror MONOTONIC_NONNULL = - AnnotationBuilder.fromClass(elements, MonotonicNonNull.class); - - /** Handles invocations of {@link java.lang.System#getProperty(String)}. */ - protected final SystemGetPropertyHandler systemGetPropertyHandler; - - /** Determines the nullness type of calls to {@link java.util.Collection#toArray()}. */ - protected final CollectionToArrayHeuristics collectionToArrayHeuristics; - - /** The Class.getCanonicalName() method. */ - protected final ExecutableElement classGetCanonicalName; - - /** The Arrays.copyOf() methods that operate on arrays of references. */ - private final List copyOfMethods; - - /** Cache for the nullness annotations. */ - protected final Set> nullnessAnnos; - - /** The Map.get method. */ - private final ExecutableElement mapGet = - TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); - - // List is in alphabetical order. If you update it, also update - // ../../../../../../../../docs/manual/nullness-checker.tex - // and make a pull request for variables NONNULL_ANNOTATIONS and BASE_COPYABLE_ANNOTATIONS in - // https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/core/handlers/HandlerUtil.java . - // Avoid changes to the string constants by ShadowJar relocate by using "start".toString() + - // "rest". - // Keep the original string constant in a comment to allow searching for it. - /** Aliases for {@code @Nonnull}. */ - @SuppressWarnings( - "signature:assignment.type.incompatible") // Class names intentionally obfuscated - private static final List<@FullyQualifiedName String> NONNULL_ALIASES = - Arrays.asList( - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java - // https://developer.android.com/reference/androidx/annotation/NonNull - "android.annotation.NonNull", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/NonNull.java - // https://developer.android.com/reference/android/support/annotation/NonNull - "android.support.annotation.NonNull", - // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java - "android.support.annotation.RecentlyNonNull", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/NonNull.java - "androidx.annotation.NonNull", - // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java - "androidx.annotation.RecentlyNonNull", - // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/NonNull.java - "com.android.annotations.NonNull", - // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/NotNull.java - // "com.google.firebase.database.annotations.NotNull", - "com.go".toString() + "ogle.firebase.database.annotations.NotNull", - // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/NonNull.java - // "com.google.firebase.internal.NonNull", - "com.go".toString() + "ogle.firebase.internal.NonNull", - // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/NonNull.java - "com.mongodb.lang.NonNull", - // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/NotNull.java - "com.sun.istack.NotNull", - // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/NotNull.java - "com.sun.istack.internal.NotNull", - // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/NotNull.java - "com.unboundid.util.NotNull", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/NonNull.html - "edu.umd.cs.findbugs.annotations.NonNull", - // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/NonNull.java - "io.micrometer.core.lang.NonNull", - // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/NonNull.java - "io.micronaut.core.annotation.NonNull", - // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/NonNull.java - "io.reactivex.annotations.NonNull", - // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/NonNull.java - "io.reactivex.rxjava3.annotations.NonNull", - // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nonnull.java - "jakarta.annotation.Nonnull", - // https://jcp.org/en/jsr/detail?id=305; no documentation at - // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nonnull.html - "javax.annotation.Nonnull", - // https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/NotNull.html - "javax.validation.constraints.NotNull", - // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/NonNull.java - "libcore.util.NonNull", - // https://github.com/projectlombok/lombok/blob/master/src/core/lombok/NonNull.java - "lombok.NonNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java - "net.bytebuddy.agent.utility.nullability.NeverNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/NeverNull.java - "net.bytebuddy.utility.nullability.NeverNull", - // Removed in ANTLR 4.6. - // https://github.com/antlr/antlr4/blob/master/runtime/Java/src/org/antlr/v4/runtime/misc/NotNull.java - "org.antlr.v4.runtime.misc.NotNull", - // https://search.maven.org/artifact/org.checkerframework/checker-compat-qual/2.5.5/jar - "org.checkerframework.checker.nullness.compatqual.NonNullDecl", - "org.checkerframework.checker.nullness.compatqual.NonNullType", - // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/NotNull.html - "org.codehaus.commons.nullanalysis.NotNull", - // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/NonNull.html - // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/NonNull.java - "org.eclipse.jdt.annotation.NonNull", - // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java - "org.eclipse.jgit.annotations.NonNull", - // https://github.com/eclipse/lsp4j/blob/main/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java - "org.eclipse.lsp4j.jsonrpc.validation.NonNull", - // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/NotNull.java - // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html - "org.jetbrains.annotations.NotNull", - // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/NonNull.java - "org.jmlspecs.annotation.NonNull", - // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/NonNull.java - "org.jspecify.annotations.NonNull", - // 2022-11-17: Deprecated old package location, remove after some grace period - // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness - "org.jspecify.nullness.NonNull", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NonNull.html - "org.netbeans.api.annotations.common.NonNull", - // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/NonNull.java - "org.springframework.lang.NonNull", - // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/NonNull.java - "reactor.util.annotation.NonNull"); - - // List is in alphabetical order. If you update it, also update - // ../../../../../../../../docs/manual/nullness-checker.tex . - // See more comments with NONNULL_ALIASES above. - /** Aliases for {@code @Nullable}. */ - @SuppressWarnings( - "signature:assignment.type.incompatible") // Class names intentionally obfuscated - private static final List<@FullyQualifiedName String> NULLABLE_ALIASES = - Arrays.asList( - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java - // https://developer.android.com/reference/androidx/annotation/Nullable - "android.annotation.Nullable", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/Nullable.java - // https://developer.android.com/reference/android/support/annotation/Nullable - "android.support.annotation.Nullable", - // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java - "android.support.annotation.RecentlyNullable", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/Nullable.java - "androidx.annotation.Nullable", - // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java - "androidx.annotation.RecentlyNullable", - // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/Nullable.java - "com.android.annotations.Nullable", - // https://github.com/lpantano/java_seqbuster/blob/master/AdRec/src/adrec/com/beust/jcommander/internal/Nullable.java - "com.beust.jcommander.internal.Nullable", - // https://github.com/cloudendpoints/endpoints-java/blob/master/endpoints-framework/src/main/java/com/google/api/server/spi/config/Nullable.java - // "com.google.api.server.spi.config.Nullable", - "com.go".toString() + "ogle.api.server.spi.config.Nullable", - // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/Nullable.java - // "com.google.firebase.database.annotations.Nullable", - "com.go".toString() + "ogle.firebase.database.annotations.Nullable", - // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/Nullable.java - // "com.google.firebase.internal.Nullable", - "com.go".toString() + "ogle.firebase.internal.Nullable", - // https://gerrit.googlesource.com/gerrit/+/refs/heads/master/java/com/google/gerrit/common/Nullable.java - // "com.google.gerrit.common.Nullable", - "com.go".toString() + "ogle.gerrit.common.Nullable", - // - // "com.google.protobuf.Internal.ProtoMethodAcceptsNullParameter", - "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodAcceptsNullParameter", - // - // "com.google.protobuf.Internal.ProtoMethodMayReturnNull", - "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodMayReturnNull", - // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/Nullable.java - "com.mongodb.lang.Nullable", - // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/Nullable.java - "com.sun.istack.Nullable", - // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/Nullable.java - "com.sun.istack.internal.Nullable", - // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/Nullable.java - "com.unboundid.util.Nullable", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/CheckForNull.html - "edu.umd.cs.findbugs.annotations.CheckForNull", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html - "edu.umd.cs.findbugs.annotations.Nullable", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/PossiblyNull.html - "edu.umd.cs.findbugs.annotations.PossiblyNull", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/UnknownNullness.html - "edu.umd.cs.findbugs.annotations.UnknownNullness", - // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/Nullable.java - "io.micrometer.core.lang.Nullable", - // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/Nullable.java - "io.micronaut.core.annotation.Nullable", - // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Nullable.java - "io.reactivex.annotations.Nullable", - // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/Nullable.java - "io.reactivex.rxjava3.annotations.Nullable", - // https://github.com/eclipse-vertx/vertx-codegen/blob/master/src/main/java/io/vertx/codegen/annotations/Nullable.java - "io.vertx.codegen.annotations.Nullable", - // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nullable.java - "jakarta.annotation.Nullable", - // https://jcp.org/en/jsr/detail?id=305; no documentation at - // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nullable.html - "javax.annotation.CheckForNull", - "javax.annotation.Nullable", - // https://github.com/Pragmatists/JUnitParams/blob/master/src/main/java/junitparams/converters/Nullable.java - "junitparams.converters.Nullable", - // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/Nullable.java - "libcore.util.Nullable", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java - "net.bytebuddy.agent.utility.nullability.AlwaysNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java - "net.bytebuddy.agent.utility.nullability.MaybeNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java - "net.bytebuddy.agent.utility.nullability.UnknownNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/AlwaysNull.java - "net.bytebuddy.utility.nullability.AlwaysNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/MaybeNull.java - "net.bytebuddy.utility.nullability.MaybeNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/UnknownNull.java - "net.bytebuddy.utility.nullability.UnknownNull", - // https://github.com/apache/avro/blob/master/lang/java/avro/src/main/java/org/apache/avro/reflect/Nullable.java - // "org.apache.avro.reflect.Nullable", - "org.apa".toString() + "che.avro.reflect.Nullable", - // https://github.com/apache/cxf/blob/master/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/Nullable.java - // "org.apache.cxf.jaxrs.ext.Nullable", - "org.apa".toString() + "che.cxf.jaxrs.ext.Nullable", - // https://github.com/gatein/gatein-shindig/blob/master/java/common/src/main/java/org/apache/shindig/common/Nullable.java - // "org.apache.shindig.common.Nullable", - "org.apa".toString() + "che.shindig.common.Nullable", - // https://search.maven.org/search?q=a:checker-compat-qual - "org.checkerframework.checker.nullness.compatqual.NullableDecl", - "org.checkerframework.checker.nullness.compatqual.NullableType", - // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/Nullable.html - "org.codehaus.commons.nullanalysis.Nullable", - // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/Nullable.html - // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Nullable.java - "org.eclipse.jdt.annotation.Nullable", - // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java - "org.eclipse.jgit.annotations.Nullable", - // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/Nullable.java - // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html - "org.jetbrains.annotations.Nullable", - // https://github.com/JetBrains/java-annotations/blob/master/java8/src/main/java/org/jetbrains/annotations/UnknownNullability.java - "org.jetbrains.annotations.UnknownNullability", - // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/Nullable.java - "org.jmlspecs.annotation.Nullable", - // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/Nullable.java - "org.jspecify.annotations.Nullable", - // 2022-11-17: Deprecated old package location, remove after some grace period - // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness - "org.jspecify.nullness.Nullable", - "org.jspecify.nullness.NullnessUnspecified", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/CheckForNull.html - "org.netbeans.api.annotations.common.CheckForNull", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullAllowed.html - "org.netbeans.api.annotations.common.NullAllowed", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullUnknown.html - "org.netbeans.api.annotations.common.NullUnknown", - // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/Nullable.java - "org.springframework.lang.Nullable", - // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/Nullable.java - "reactor.util.annotation.Nullable"); - - // List is in alphabetical order. If you update it, also update - // ../../../../../../../../docs/manual/nullness-checker.tex . - // See more comments with NONNULL_ALIASES above. - /** Aliases for {@code @PolyNull}. */ - @SuppressWarnings( - "signature:assignment.type.incompatible") // Class names intentionally obfuscated - private static final List<@FullyQualifiedName String> POLYNULL_ALIASES = - Arrays.asList( - // "com.google.protobuf.Internal.ProtoPassThroughNullness", - "com.go".toString() + "ogle.protobuf.Internal.ProtoPassThroughNullness"); + extends GenericAnnotatedTypeFactory< + NullnessNoInitValue, NullnessNoInitStore, NullnessNoInitTransfer, NullnessNoInitAnalysis> { + + /** The @{@link NonNull} annotation. */ + protected final AnnotationMirror NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); + + /** The @{@link Nullable} annotation. */ + protected final AnnotationMirror NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class); + + /** The @{@link PolyNull} annotation. */ + protected final AnnotationMirror POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class); + + /** The @{@link MonotonicNonNull} annotation. */ + protected final AnnotationMirror MONOTONIC_NONNULL = + AnnotationBuilder.fromClass(elements, MonotonicNonNull.class); + + /** Handles invocations of {@link java.lang.System#getProperty(String)}. */ + protected final SystemGetPropertyHandler systemGetPropertyHandler; + + /** Determines the nullness type of calls to {@link java.util.Collection#toArray()}. */ + protected final CollectionToArrayHeuristics collectionToArrayHeuristics; + + /** The Class.getCanonicalName() method. */ + protected final ExecutableElement classGetCanonicalName; + + /** The Arrays.copyOf() methods that operate on arrays of references. */ + private final List copyOfMethods; + + /** Cache for the nullness annotations. */ + protected final Set> nullnessAnnos; + + /** The Map.get method. */ + private final ExecutableElement mapGet = + TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); + + // List is in alphabetical order. If you update it, also update + // ../../../../../../../../docs/manual/nullness-checker.tex + // and make a pull request for variables NONNULL_ANNOTATIONS and BASE_COPYABLE_ANNOTATIONS in + // https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/core/handlers/HandlerUtil.java . + // Avoid changes to the string constants by ShadowJar relocate by using "start".toString() + + // "rest". + // Keep the original string constant in a comment to allow searching for it. + /** Aliases for {@code @Nonnull}. */ + @SuppressWarnings( + "signature:assignment.type.incompatible") // Class names intentionally obfuscated + private static final List<@FullyQualifiedName String> NONNULL_ALIASES = + Arrays.asList( + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java + // https://developer.android.com/reference/androidx/annotation/NonNull + "android.annotation.NonNull", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/NonNull.java + // https://developer.android.com/reference/android/support/annotation/NonNull + "android.support.annotation.NonNull", + // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java + "android.support.annotation.RecentlyNonNull", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/NonNull.java + "androidx.annotation.NonNull", + // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java + "androidx.annotation.RecentlyNonNull", + // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/NonNull.java + "com.android.annotations.NonNull", + // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/NotNull.java + // "com.google.firebase.database.annotations.NotNull", + "com.go".toString() + "ogle.firebase.database.annotations.NotNull", + // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/NonNull.java + // "com.google.firebase.internal.NonNull", + "com.go".toString() + "ogle.firebase.internal.NonNull", + // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/NonNull.java + "com.mongodb.lang.NonNull", + // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/NotNull.java + "com.sun.istack.NotNull", + // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/NotNull.java + "com.sun.istack.internal.NotNull", + // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/NotNull.java + "com.unboundid.util.NotNull", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/NonNull.html + "edu.umd.cs.findbugs.annotations.NonNull", + // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/NonNull.java + "io.micrometer.core.lang.NonNull", + // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/NonNull.java + "io.micronaut.core.annotation.NonNull", + // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/NonNull.java + "io.reactivex.annotations.NonNull", + // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/NonNull.java + "io.reactivex.rxjava3.annotations.NonNull", + // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nonnull.java + "jakarta.annotation.Nonnull", + // https://jcp.org/en/jsr/detail?id=305; no documentation at + // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nonnull.html + "javax.annotation.Nonnull", + // https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/NotNull.html + "javax.validation.constraints.NotNull", + // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/NonNull.java + "libcore.util.NonNull", + // https://github.com/projectlombok/lombok/blob/master/src/core/lombok/NonNull.java + "lombok.NonNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java + "net.bytebuddy.agent.utility.nullability.NeverNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/NeverNull.java + "net.bytebuddy.utility.nullability.NeverNull", + // Removed in ANTLR 4.6. + // https://github.com/antlr/antlr4/blob/master/runtime/Java/src/org/antlr/v4/runtime/misc/NotNull.java + "org.antlr.v4.runtime.misc.NotNull", + // https://search.maven.org/artifact/org.checkerframework/checker-compat-qual/2.5.5/jar + "org.checkerframework.checker.nullness.compatqual.NonNullDecl", + "org.checkerframework.checker.nullness.compatqual.NonNullType", + // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/NotNull.html + "org.codehaus.commons.nullanalysis.NotNull", + // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/NonNull.html + // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/NonNull.java + "org.eclipse.jdt.annotation.NonNull", + // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java + "org.eclipse.jgit.annotations.NonNull", + // https://github.com/eclipse/lsp4j/blob/main/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java + "org.eclipse.lsp4j.jsonrpc.validation.NonNull", + // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/NotNull.java + // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html + "org.jetbrains.annotations.NotNull", + // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/NonNull.java + "org.jmlspecs.annotation.NonNull", + // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/NonNull.java + "org.jspecify.annotations.NonNull", + // 2022-11-17: Deprecated old package location, remove after some grace period + // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness + "org.jspecify.nullness.NonNull", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NonNull.html + "org.netbeans.api.annotations.common.NonNull", + // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/NonNull.java + "org.springframework.lang.NonNull", + // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/NonNull.java + "reactor.util.annotation.NonNull"); + + // List is in alphabetical order. If you update it, also update + // ../../../../../../../../docs/manual/nullness-checker.tex . + // See more comments with NONNULL_ALIASES above. + /** Aliases for {@code @Nullable}. */ + @SuppressWarnings( + "signature:assignment.type.incompatible") // Class names intentionally obfuscated + private static final List<@FullyQualifiedName String> NULLABLE_ALIASES = + Arrays.asList( + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java + // https://developer.android.com/reference/androidx/annotation/Nullable + "android.annotation.Nullable", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/Nullable.java + // https://developer.android.com/reference/android/support/annotation/Nullable + "android.support.annotation.Nullable", + // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java + "android.support.annotation.RecentlyNullable", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/Nullable.java + "androidx.annotation.Nullable", + // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java + "androidx.annotation.RecentlyNullable", + // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/Nullable.java + "com.android.annotations.Nullable", + // https://github.com/lpantano/java_seqbuster/blob/master/AdRec/src/adrec/com/beust/jcommander/internal/Nullable.java + "com.beust.jcommander.internal.Nullable", + // https://github.com/cloudendpoints/endpoints-java/blob/master/endpoints-framework/src/main/java/com/google/api/server/spi/config/Nullable.java + // "com.google.api.server.spi.config.Nullable", + "com.go".toString() + "ogle.api.server.spi.config.Nullable", + // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/Nullable.java + // "com.google.firebase.database.annotations.Nullable", + "com.go".toString() + "ogle.firebase.database.annotations.Nullable", + // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/Nullable.java + // "com.google.firebase.internal.Nullable", + "com.go".toString() + "ogle.firebase.internal.Nullable", + // https://gerrit.googlesource.com/gerrit/+/refs/heads/master/java/com/google/gerrit/common/Nullable.java + // "com.google.gerrit.common.Nullable", + "com.go".toString() + "ogle.gerrit.common.Nullable", + // + // "com.google.protobuf.Internal.ProtoMethodAcceptsNullParameter", + "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodAcceptsNullParameter", + // + // "com.google.protobuf.Internal.ProtoMethodMayReturnNull", + "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodMayReturnNull", + // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/Nullable.java + "com.mongodb.lang.Nullable", + // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/Nullable.java + "com.sun.istack.Nullable", + // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/Nullable.java + "com.sun.istack.internal.Nullable", + // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/Nullable.java + "com.unboundid.util.Nullable", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/CheckForNull.html + "edu.umd.cs.findbugs.annotations.CheckForNull", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html + "edu.umd.cs.findbugs.annotations.Nullable", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/PossiblyNull.html + "edu.umd.cs.findbugs.annotations.PossiblyNull", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/UnknownNullness.html + "edu.umd.cs.findbugs.annotations.UnknownNullness", + // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/Nullable.java + "io.micrometer.core.lang.Nullable", + // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/Nullable.java + "io.micronaut.core.annotation.Nullable", + // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Nullable.java + "io.reactivex.annotations.Nullable", + // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/Nullable.java + "io.reactivex.rxjava3.annotations.Nullable", + // https://github.com/eclipse-vertx/vertx-codegen/blob/master/src/main/java/io/vertx/codegen/annotations/Nullable.java + "io.vertx.codegen.annotations.Nullable", + // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nullable.java + "jakarta.annotation.Nullable", + // https://jcp.org/en/jsr/detail?id=305; no documentation at + // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nullable.html + "javax.annotation.CheckForNull", + "javax.annotation.Nullable", + // https://github.com/Pragmatists/JUnitParams/blob/master/src/main/java/junitparams/converters/Nullable.java + "junitparams.converters.Nullable", + // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/Nullable.java + "libcore.util.Nullable", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java + "net.bytebuddy.agent.utility.nullability.AlwaysNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java + "net.bytebuddy.agent.utility.nullability.MaybeNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java + "net.bytebuddy.agent.utility.nullability.UnknownNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/AlwaysNull.java + "net.bytebuddy.utility.nullability.AlwaysNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/MaybeNull.java + "net.bytebuddy.utility.nullability.MaybeNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/UnknownNull.java + "net.bytebuddy.utility.nullability.UnknownNull", + // https://github.com/apache/avro/blob/master/lang/java/avro/src/main/java/org/apache/avro/reflect/Nullable.java + // "org.apache.avro.reflect.Nullable", + "org.apa".toString() + "che.avro.reflect.Nullable", + // https://github.com/apache/cxf/blob/master/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/Nullable.java + // "org.apache.cxf.jaxrs.ext.Nullable", + "org.apa".toString() + "che.cxf.jaxrs.ext.Nullable", + // https://github.com/gatein/gatein-shindig/blob/master/java/common/src/main/java/org/apache/shindig/common/Nullable.java + // "org.apache.shindig.common.Nullable", + "org.apa".toString() + "che.shindig.common.Nullable", + // https://search.maven.org/search?q=a:checker-compat-qual + "org.checkerframework.checker.nullness.compatqual.NullableDecl", + "org.checkerframework.checker.nullness.compatqual.NullableType", + // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/Nullable.html + "org.codehaus.commons.nullanalysis.Nullable", + // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/Nullable.html + // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Nullable.java + "org.eclipse.jdt.annotation.Nullable", + // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java + "org.eclipse.jgit.annotations.Nullable", + // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/Nullable.java + // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html + "org.jetbrains.annotations.Nullable", + // https://github.com/JetBrains/java-annotations/blob/master/java8/src/main/java/org/jetbrains/annotations/UnknownNullability.java + "org.jetbrains.annotations.UnknownNullability", + // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/Nullable.java + "org.jmlspecs.annotation.Nullable", + // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/Nullable.java + "org.jspecify.annotations.Nullable", + // 2022-11-17: Deprecated old package location, remove after some grace period + // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness + "org.jspecify.nullness.Nullable", + "org.jspecify.nullness.NullnessUnspecified", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/CheckForNull.html + "org.netbeans.api.annotations.common.CheckForNull", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullAllowed.html + "org.netbeans.api.annotations.common.NullAllowed", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullUnknown.html + "org.netbeans.api.annotations.common.NullUnknown", + // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/Nullable.java + "org.springframework.lang.Nullable", + // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/Nullable.java + "reactor.util.annotation.Nullable"); + + // List is in alphabetical order. If you update it, also update + // ../../../../../../../../docs/manual/nullness-checker.tex . + // See more comments with NONNULL_ALIASES above. + /** Aliases for {@code @PolyNull}. */ + @SuppressWarnings( + "signature:assignment.type.incompatible") // Class names intentionally obfuscated + private static final List<@FullyQualifiedName String> POLYNULL_ALIASES = + Arrays.asList( + // "com.google.protobuf.Internal.ProtoPassThroughNullness", + "com.go".toString() + "ogle.protobuf.Internal.ProtoPassThroughNullness"); + + /** + * Creates a NullnessAnnotatedTypeFactory. + * + * @param checker the associated {@link NullnessNoInitSubchecker} + */ + public NullnessNoInitAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + Set> tempNullnessAnnos = new LinkedHashSet<>(4); + tempNullnessAnnos.add(NonNull.class); + tempNullnessAnnos.add(MonotonicNonNull.class); + tempNullnessAnnos.add(Nullable.class); + tempNullnessAnnos.add(PolyNull.class); + nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos); + + NONNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NONNULL)); + NULLABLE_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NULLABLE)); + POLYNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, POLYNULL)); + + // Add compatibility annotations: + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.PolyNullDecl", POLYNULL); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl", MONOTONIC_NONNULL); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.PolyNullType", POLYNULL); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType", MONOTONIC_NONNULL); + + if (checker.getUltimateParentChecker().getBooleanOption("jspecifyNullMarkedAlias", true)) { + AnnotationMirror nullMarkedDefaultQual = + new AnnotationBuilder(processingEnv, DefaultQualifier.class) + .setValue("value", NonNull.class) + .setValue("locations", new TypeUseLocation[] {TypeUseLocation.UPPER_BOUND}) + .setValue("applyToSubpackages", false) + .build(); + addAliasedDeclAnnotation( + "org.jspecify.annotations.NullMarked", + DefaultQualifier.class.getCanonicalName(), + nullMarkedDefaultQual); + + // 2022-11-17: Deprecated old package location, remove after some grace period + addAliasedDeclAnnotation( + "org.jspecify.nullness.NullMarked", + DefaultQualifier.class.getCanonicalName(), + nullMarkedDefaultQual); + } - /** - * Creates a NullnessAnnotatedTypeFactory. - * - * @param checker the associated {@link NullnessNoInitSubchecker} - */ - public NullnessNoInitAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - Set> tempNullnessAnnos = new LinkedHashSet<>(4); - tempNullnessAnnos.add(NonNull.class); - tempNullnessAnnos.add(MonotonicNonNull.class); - tempNullnessAnnos.add(Nullable.class); - tempNullnessAnnos.add(PolyNull.class); - nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos); - - NONNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NONNULL)); - NULLABLE_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NULLABLE)); - POLYNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, POLYNULL)); - - // Add compatibility annotations: - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.PolyNullDecl", POLYNULL); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl", - MONOTONIC_NONNULL); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.PolyNullType", POLYNULL); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType", - MONOTONIC_NONNULL); - - if (checker.getUltimateParentChecker().getBooleanOption("jspecifyNullMarkedAlias", true)) { - AnnotationMirror nullMarkedDefaultQual = - new AnnotationBuilder(processingEnv, DefaultQualifier.class) - .setValue("value", NonNull.class) - .setValue( - "locations", - new TypeUseLocation[] {TypeUseLocation.UPPER_BOUND}) - .setValue("applyToSubpackages", false) - .build(); - addAliasedDeclAnnotation( - "org.jspecify.annotations.NullMarked", - DefaultQualifier.class.getCanonicalName(), - nullMarkedDefaultQual); - - // 2022-11-17: Deprecated old package location, remove after some grace period - addAliasedDeclAnnotation( - "org.jspecify.nullness.NullMarked", - DefaultQualifier.class.getCanonicalName(), - nullMarkedDefaultQual); + boolean permitClearProperty = + checker.getLintOption( + NullnessChecker.LINT_PERMITCLEARPROPERTY, + NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); + systemGetPropertyHandler = + new SystemGetPropertyHandler(processingEnv, this, permitClearProperty); + + classGetCanonicalName = + TreeUtils.getMethod("java.lang.Class", "getCanonicalName", 0, processingEnv); + copyOfMethods = + Arrays.asList( + TreeUtils.getMethod("java.util.Arrays", "copyOf", processingEnv, "T[]", "int"), + TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); + + postInit(); + + // do this last, as it might use the factory again. + this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(checker, this); + } + + @Override + public NullnessNoInitSubchecker getChecker() { + return (NullnessNoInitSubchecker) checker; + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList(Nullable.class, MonotonicNonNull.class, NonNull.class, PolyNull.class)); + } + + /** + * For types of left-hand side of an assignment, this method replaces {@link PolyNull} with {@link + * Nullable} (or with {@link NonNull} if the org.checkerframework.dataflow analysis has determined + * that this is allowed soundly. For example: + * + *
 @PolyNull String foo(@PolyNull String param) {
+   *    if (param == null) {
+   *        //  @PolyNull is really @Nullable, so change
+   *        // the type of param to @Nullable.
+   *        param = null;
+   *    }
+   *    return param;
+   * }
+   * 
+ * + * @param lhsType type to replace whose polymorphic qualifier will be replaced + * @param context tree used to get dataflow value + */ + protected void replacePolyQualifier(AnnotatedTypeMirror lhsType, Tree context) { + if (lhsType.hasAnnotation(PolyNull.class)) { + NullnessNoInitValue inferred = getInferredValueFor(context); + if (inferred != null) { + if (inferred.isPolyNullNonNull) { + lhsType.replaceAnnotation(NONNULL); + } else if (inferred.isPolyNullNull) { + lhsType.replaceAnnotation(NULLABLE); } + } + } + } + + @Override + protected NullnessNoInitAnalysis createFlowAnalysis() { + return new NullnessNoInitAnalysis(checker, this); + } + + @Override + public NullnessNoInitTransfer createFlowTransferFunction( + CFAbstractAnalysis + analysis) { + return new NullnessNoInitTransfer((NullnessNoInitAnalysis) analysis); + } + + /** + * Returns an AnnotatedTypeFormatter that does not print the qualifiers on null literals. + * + * @return an AnnotatedTypeFormatter that does not print the qualifiers on null literals + */ + @Override + protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { + boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); + return new NullnessNoInitAnnotatedTypeFormatter( + printVerboseGenerics, + // -AprintVerboseGenerics implies -AprintAllQualifiers + printVerboseGenerics || checker.hasOption("printAllQualifiers")); + } + + @Override + public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { + ParameterizedExecutableType mType = super.methodFromUse(tree); + AnnotatedExecutableType method = mType.executableType; + + // Special cases for method invocations with specific arguments. + systemGetPropertyHandler.handle(tree, method); + collectionToArrayHeuristics.handle(tree, method); + // `MyClass.class.getCanonicalName()` is non-null. + if (TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv)) { + ExpressionTree receiver = ((MemberSelectTree) tree.getMethodSelect()).getExpression(); + if (TreeUtils.isClassLiteral(receiver)) { + AnnotatedTypeMirror type = method.getReturnType(); + type.replaceAnnotation(NONNULL); + } + } - boolean permitClearProperty = - checker.getLintOption( - NullnessChecker.LINT_PERMITCLEARPROPERTY, - NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); - systemGetPropertyHandler = - new SystemGetPropertyHandler(processingEnv, this, permitClearProperty); - - classGetCanonicalName = - TreeUtils.getMethod("java.lang.Class", "getCanonicalName", 0, processingEnv); - copyOfMethods = - Arrays.asList( - TreeUtils.getMethod( - "java.util.Arrays", "copyOf", processingEnv, "T[]", "int"), - TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); - - postInit(); - - // do this last, as it might use the factory again. - this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(checker, this); + return mType; + } + + @Override + public void adaptGetClassReturnTypeToReceiver( + AnnotatedExecutableType getClassType, AnnotatedTypeMirror receiverType, ExpressionTree tree) { + + super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); + + // Make the captured wildcard always @NonNull, regardless of the declared type. + + AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); + List typeArgs = returnAdt.getTypeArguments(); + AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); + classWildcardArg.getUpperBound().replaceAnnotation(NONNULL); + } + + @Override + public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { + AnnotatedTypeMirror result = super.getMethodReturnType(m, r); + replacePolyQualifier(result, r); + return result; + } + + @Override + public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { + InitializationFieldAccessAnnotatedTypeFactory initFactory = + getChecker().getTypeFactoryOfSubcheckerOrNull(InitializationFieldAccessSubchecker.class); + if (initFactory == null) { + // init checker is deactivated. + return super.isNotFullyInitializedReceiver(methodDeclTree); + } + return initFactory.isNotFullyInitializedReceiver(methodDeclTree); + } + + @Override + public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { + InitializationFieldAccessAnnotatedTypeFactory initFactory = + getChecker().getTypeFactoryOfSubcheckerOrNull(InitializationFieldAccessSubchecker.class); + if (initFactory == null) { + // init checker is deactivated. + return super.getAnnotatedTypeBefore(expr, tree); } + if (expr instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) expr; + JavaExpression receiver = fa.getReceiver(); + TypeMirror declaringClass = fa.getField().getEnclosingElement().asType(); + AnnotatedTypeMirror receiverType; + + if (receiver instanceof LocalVariable) { + Element receiverElem = ((LocalVariable) receiver).getElement(); + receiverType = initFactory.getAnnotatedType(receiverElem); + } else if (receiver instanceof ThisReference) { + receiverType = initFactory.getSelfType(tree); + } else { + return super.getAnnotatedTypeBefore(expr, tree); + } - @Override - public NullnessNoInitSubchecker getChecker() { - return (NullnessNoInitSubchecker) checker; + if (initFactory.isInitializedForFrame(receiverType, declaringClass)) { + AnnotatedTypeMirror declared = getAnnotatedType(fa.getField()); + AnnotatedTypeMirror refined = super.getAnnotatedTypeBefore(expr, tree); + AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(fa.getType(), this, false); + // If the expression is initialized, then by definition, it has at least its + // declared annotation. + // Assuming the correctness of the Nullness Checker's type refinement, + // it also has its refined annotation. + // We thus use the GLB of those two annotations. + res.addAnnotations( + qualHierarchy.greatestLowerBoundsShallow( + declared.getAnnotations(), + declared.getUnderlyingType(), + refined.getAnnotations(), + refined.getUnderlyingType())); + return res; + } } - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - Nullable.class, MonotonicNonNull.class, NonNull.class, PolyNull.class)); + // Is there anything better we could do? + // Ideally, we would turn the expression string into a Tree or Element + // instead of a JavaExpression, so we could use + // atypeFactory.getAnnotatedType on the whole expression, + // but that doesn't seem possible. + return super.getAnnotatedTypeBefore(expr, tree); + } + + @Override + protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { + DefaultForTypeAnnotator defaultForTypeAnnotator = new DefaultForTypeAnnotator(this); + defaultForTypeAnnotator.addAtmClass(AnnotatedNoType.class, NONNULL); + defaultForTypeAnnotator.addAtmClass(AnnotatedPrimitiveType.class, NONNULL); + return defaultForTypeAnnotator; + } + + @Override + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + if (element != null + && element.getKind() == ElementKind.LOCAL_VARIABLE + && type.getKind().isPrimitive()) { + // Always apply the DefaultQualifierForUse for primitives. + super.addAnnotationsFromDefaultForType(null, type); + } else { + super.addAnnotationsFromDefaultForType(element, type); } + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because the default tree annotators are incorrect + // for the Nullness Checker. + List annotators = new ArrayList<>(3); + // annotators.add(new DebugListTreeAnnotator(new Tree.Kind[] + // {Tree.Kind.CONDITIONAL_EXPRESSION})); + annotators.add(new InitializationFieldAccessTreeAnnotator(this)); + annotators.add(new NullnessPropagationTreeAnnotator(this)); + annotators.add(new LiteralTreeAnnotator(this)); + return new ListTreeAnnotator(annotators); + } + + /** Adds nullness-specific propagation rules */ + protected class NullnessPropagationTreeAnnotator extends PropagationTreeAnnotator { /** - * For types of left-hand side of an assignment, this method replaces {@link PolyNull} with - * {@link Nullable} (or with {@link NonNull} if the org.checkerframework.dataflow analysis has - * determined that this is allowed soundly. For example: + * Creates a NullnessPropagationTreeAnnotator. * - *
 @PolyNull String foo(@PolyNull String param) {
-     *    if (param == null) {
-     *        //  @PolyNull is really @Nullable, so change
-     *        // the type of param to @Nullable.
-     *        param = null;
-     *    }
-     *    return param;
-     * }
-     * 
- * - * @param lhsType type to replace whose polymorphic qualifier will be replaced - * @param context tree used to get dataflow value + * @param atypeFactory this factory */ - protected void replacePolyQualifier(AnnotatedTypeMirror lhsType, Tree context) { - if (lhsType.hasAnnotation(PolyNull.class)) { - NullnessNoInitValue inferred = getInferredValueFor(context); - if (inferred != null) { - if (inferred.isPolyNullNonNull) { - lhsType.replaceAnnotation(NONNULL); - } else if (inferred.isPolyNullNull) { - lhsType.replaceAnnotation(NULLABLE); - } - } - } + public NullnessPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - protected NullnessNoInitAnalysis createFlowAnalysis() { - return new NullnessNoInitAnalysis(checker, this); + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + if (type.getKind().isPrimitive()) { + AnnotationMirror NONNULL = ((NullnessNoInitAnnotatedTypeFactory) atypeFactory).NONNULL; + // If a @Nullable expression is cast to a primitive, then an unboxing.of.nullable + // error is issued. Treat the cast as if it were annotated as @NonNull to avoid an + // "type.invalid.annotations.on.use" error. + type.addMissingAnnotation(NONNULL); + } + return super.visitTypeCast(tree, type); } @Override - public NullnessNoInitTransfer createFlowTransferFunction( - CFAbstractAnalysis - analysis) { - return new NullnessNoInitTransfer((NullnessNoInitAnalysis) analysis); - } + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + Element elt = TreeUtils.elementFromUse(tree); + assert elt != null; + + // Make primitive variable @NonNull in case the Initialization Checker + // considers it uninitialized. + if (TypesUtils.isPrimitive(type.getUnderlyingType())) { + type.replaceAnnotation(NONNULL); + } - /** - * Returns an AnnotatedTypeFormatter that does not print the qualifiers on null literals. - * - * @return an AnnotatedTypeFormatter that does not print the qualifiers on null literals - */ - @Override - protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { - boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); - return new NullnessNoInitAnnotatedTypeFormatter( - printVerboseGenerics, - // -AprintVerboseGenerics implies -AprintAllQualifiers - printVerboseGenerics || checker.hasOption("printAllQualifiers")); + return null; } @Override - public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { - ParameterizedExecutableType mType = super.methodFromUse(tree); - AnnotatedExecutableType method = mType.executableType; - - // Special cases for method invocations with specific arguments. - systemGetPropertyHandler.handle(tree, method); - collectionToArrayHeuristics.handle(tree, method); - // `MyClass.class.getCanonicalName()` is non-null. - if (TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv)) { - ExpressionTree receiver = ((MemberSelectTree) tree.getMethodSelect()).getExpression(); - if (TreeUtils.isClassLiteral(receiver)) { - AnnotatedTypeMirror type = method.getReturnType(); - type.replaceAnnotation(NONNULL); - } - } - - return mType; + public Void visitVariable(VariableTree tree, AnnotatedTypeMirror type) { + Element elt = TreeUtils.elementFromDeclaration(tree); + if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { + // case 9. exception parameter + type.addMissingAnnotation(NONNULL); + } + return null; } @Override - public void adaptGetClassReturnTypeToReceiver( - AnnotatedExecutableType getClassType, - AnnotatedTypeMirror receiverType, - ExpressionTree tree) { + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); + Element elt = TreeUtils.elementFromUse(tree); + assert elt != null; - // Make the captured wildcard always @NonNull, regardless of the declared type. + if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { + // TODO: It's surprising that we have to do this in both visitVariable and + // visitIdentifier. This should already be handled by applying the defaults anyway. + // case 9. exception parameter + type.replaceAnnotation(NONNULL); + } - AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); - List typeArgs = returnAdt.getTypeArguments(); - AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); - classWildcardArg.getUpperBound().replaceAnnotation(NONNULL); - } + // Make primitive variable @NonNull in case the Initialization Checker + // considers it uninitialized. + if (TypesUtils.isPrimitive(type.getUnderlyingType())) { + type.replaceAnnotation(NONNULL); + } - @Override - public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { - AnnotatedTypeMirror result = super.getMethodReturnType(m, r); - replacePolyQualifier(result, r); - return result; + return null; } + // The result of a binary operation is always non-null. @Override - public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { - InitializationFieldAccessAnnotatedTypeFactory initFactory = - getChecker() - .getTypeFactoryOfSubcheckerOrNull( - InitializationFieldAccessSubchecker.class); - if (initFactory == null) { - // init checker is deactivated. - return super.isNotFullyInitializedReceiver(methodDeclTree); - } - return initFactory.isNotFullyInitializedReceiver(methodDeclTree); + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(NONNULL); + return null; } + // The result of a compound operation is always non-null. @Override - public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { - InitializationFieldAccessAnnotatedTypeFactory initFactory = - getChecker() - .getTypeFactoryOfSubcheckerOrNull( - InitializationFieldAccessSubchecker.class); - if (initFactory == null) { - // init checker is deactivated. - return super.getAnnotatedTypeBefore(expr, tree); - } - if (expr instanceof FieldAccess) { - FieldAccess fa = (FieldAccess) expr; - JavaExpression receiver = fa.getReceiver(); - TypeMirror declaringClass = fa.getField().getEnclosingElement().asType(); - AnnotatedTypeMirror receiverType; - - if (receiver instanceof LocalVariable) { - Element receiverElem = ((LocalVariable) receiver).getElement(); - receiverType = initFactory.getAnnotatedType(receiverElem); - } else if (receiver instanceof ThisReference) { - receiverType = initFactory.getSelfType(tree); - } else { - return super.getAnnotatedTypeBefore(expr, tree); - } - - if (initFactory.isInitializedForFrame(receiverType, declaringClass)) { - AnnotatedTypeMirror declared = getAnnotatedType(fa.getField()); - AnnotatedTypeMirror refined = super.getAnnotatedTypeBefore(expr, tree); - AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(fa.getType(), this, false); - // If the expression is initialized, then by definition, it has at least its - // declared annotation. - // Assuming the correctness of the Nullness Checker's type refinement, - // it also has its refined annotation. - // We thus use the GLB of those two annotations. - res.addAnnotations( - qualHierarchy.greatestLowerBoundsShallow( - declared.getAnnotations(), - declared.getUnderlyingType(), - refined.getAnnotations(), - refined.getUnderlyingType())); - return res; - } - } - - // Is there anything better we could do? - // Ideally, we would turn the expression string into a Tree or Element - // instead of a JavaExpression, so we could use - // atypeFactory.getAnnotatedType on the whole expression, - // but that doesn't seem possible. - return super.getAnnotatedTypeBefore(expr, tree); + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + super.visitCompoundAssignment(tree, type); + type.replaceAnnotation(NONNULL); + return null; } + // The result of a unary operation is always non-null. @Override - protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { - DefaultForTypeAnnotator defaultForTypeAnnotator = new DefaultForTypeAnnotator(this); - defaultForTypeAnnotator.addAtmClass(AnnotatedNoType.class, NONNULL); - defaultForTypeAnnotator.addAtmClass(AnnotatedPrimitiveType.class, NONNULL); - return defaultForTypeAnnotator; + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(NONNULL); + return null; } + // The result of newly allocated structures is always non-null, + // explicit nullable annotations are left intact for the visitor to inspect. @Override - protected void addAnnotationsFromDefaultForType( - @Nullable Element element, AnnotatedTypeMirror type) { - if (element != null - && element.getKind() == ElementKind.LOCAL_VARIABLE - && type.getKind().isPrimitive()) { - // Always apply the DefaultQualifierForUse for primitives. - super.addAnnotationsFromDefaultForType(null, type); - } else { - super.addAnnotationsFromDefaultForType(element, type); - } + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + // The constructor return type should already be NONNULL, so in most cases this will do + // nothing. + type.addMissingAnnotation(NONNULL); + return null; } + // The result of newly allocated structures is always non-null, + // explicit nullable annotations are left intact for the visitor to inspect. @Override - protected TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because the default tree annotators are incorrect - // for the Nullness Checker. - List annotators = new ArrayList<>(3); - // annotators.add(new DebugListTreeAnnotator(new Tree.Kind[] - // {Tree.Kind.CONDITIONAL_EXPRESSION})); - annotators.add(new InitializationFieldAccessTreeAnnotator(this)); - annotators.add(new NullnessPropagationTreeAnnotator(this)); - annotators.add(new LiteralTreeAnnotator(this)); - return new ListTreeAnnotator(annotators); - } - - /** Adds nullness-specific propagation rules */ - protected class NullnessPropagationTreeAnnotator extends PropagationTreeAnnotator { - - /** - * Creates a NullnessPropagationTreeAnnotator. - * - * @param atypeFactory this factory - */ - public NullnessPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - if (type.getKind().isPrimitive()) { - AnnotationMirror NONNULL = - ((NullnessNoInitAnnotatedTypeFactory) atypeFactory).NONNULL; - // If a @Nullable expression is cast to a primitive, then an unboxing.of.nullable - // error is issued. Treat the cast as if it were annotated as @NonNull to avoid an - // "type.invalid.annotations.on.use" error. - type.addMissingAnnotation(NONNULL); - } - return super.visitTypeCast(tree, type); - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - Element elt = TreeUtils.elementFromUse(tree); - assert elt != null; - - // Make primitive variable @NonNull in case the Initialization Checker - // considers it uninitialized. - if (TypesUtils.isPrimitive(type.getUnderlyingType())) { - type.replaceAnnotation(NONNULL); - } - - return null; - } - - @Override - public Void visitVariable(VariableTree tree, AnnotatedTypeMirror type) { - Element elt = TreeUtils.elementFromDeclaration(tree); - if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { - // case 9. exception parameter - type.addMissingAnnotation(NONNULL); - } - return null; - } - - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - - Element elt = TreeUtils.elementFromUse(tree); - assert elt != null; - - if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { - // TODO: It's surprising that we have to do this in both visitVariable and - // visitIdentifier. This should already be handled by applying the defaults anyway. - // case 9. exception parameter - type.replaceAnnotation(NONNULL); - } - - // Make primitive variable @NonNull in case the Initialization Checker - // considers it uninitialized. - if (TypesUtils.isPrimitive(type.getUnderlyingType())) { - type.replaceAnnotation(NONNULL); - } - - return null; - } - - // The result of a binary operation is always non-null. - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(NONNULL); - return null; - } - - // The result of a compound operation is always non-null. - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - super.visitCompoundAssignment(tree, type); - type.replaceAnnotation(NONNULL); - return null; - } - - // The result of a unary operation is always non-null. - @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(NONNULL); - return null; - } - - // The result of newly allocated structures is always non-null, - // explicit nullable annotations are left intact for the visitor to inspect. - @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - // The constructor return type should already be NONNULL, so in most cases this will do - // nothing. - type.addMissingAnnotation(NONNULL); - return null; - } - - // The result of newly allocated structures is always non-null, - // explicit nullable annotations are left intact for the visitor to inspect. - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - super.visitNewArray(tree, type); - type.addMissingAnnotation(NONNULL); - return null; - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv)) { - List args = tree.getArguments(); - ExpressionTree lengthArg = args.get(1); - if (TreeUtils.isArrayLengthAccess(lengthArg)) { - // TODO: This syntactic test may not be not correct if the array expression has - // a side effect that affects the array length. This code could require that - // the expression has no method calls, assignments, etc. - ExpressionTree arrayArg = args.get(0); - if (TreeUtils.sameTree( - arrayArg, ((MemberSelectTree) lengthArg).getExpression())) { - AnnotatedArrayType arrayArgType = - (AnnotatedArrayType) getAnnotatedType(arrayArg); - AnnotatedTypeMirror arrayArgComponentType = arrayArgType.getComponentType(); - // Maybe this call is only necessary if argNullness is @NonNull. - ((AnnotatedArrayType) type) - .getComponentType() - .replaceAnnotations(arrayArgComponentType.getAnnotations()); - } - } - } - return super.visitMethodInvocation(tree, type); - } + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + super.visitNewArray(tree, type); + type.addMissingAnnotation(NONNULL); + return null; } @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(super.createTypeAnnotator(), new NullnessTypeAnnotator(this)); + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv)) { + List args = tree.getArguments(); + ExpressionTree lengthArg = args.get(1); + if (TreeUtils.isArrayLengthAccess(lengthArg)) { + // TODO: This syntactic test may not be not correct if the array expression has + // a side effect that affects the array length. This code could require that + // the expression has no method calls, assignments, etc. + ExpressionTree arrayArg = args.get(0); + if (TreeUtils.sameTree(arrayArg, ((MemberSelectTree) lengthArg).getExpression())) { + AnnotatedArrayType arrayArgType = (AnnotatedArrayType) getAnnotatedType(arrayArg); + AnnotatedTypeMirror arrayArgComponentType = arrayArgType.getComponentType(); + // Maybe this call is only necessary if argNullness is @NonNull. + ((AnnotatedArrayType) type) + .getComponentType() + .replaceAnnotations(arrayArgComponentType.getAnnotations()); + } + } + } + return super.visitMethodInvocation(tree, type); } + } - /** - * This type annotator ensures that constructor return types are NONNULL, unless there is an - * explicit different annotation. - */ - protected class NullnessTypeAnnotator extends TypeAnnotator { - - /** - * Creates a new NullnessTypeAnnotator. - * - * @param atypeFactory this factory - */ - public NullnessTypeAnnotator(NullnessNoInitAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(super.createTypeAnnotator(), new NullnessTypeAnnotator(this)); + } - @Override - public Void visitExecutable(AnnotatedExecutableType t, Void p) { - Void result = super.visitExecutable(t, p); - Element elem = t.getElement(); - if (elem.getKind() == ElementKind.CONSTRUCTOR) { - AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); - returnType.addMissingAnnotation(NONNULL); - } - return result; - } - } + /** + * This type annotator ensures that constructor return types are NONNULL, unless there is an + * explicit different annotation. + */ + protected class NullnessTypeAnnotator extends TypeAnnotator { /** - * Returns the list of annotations of the non-null type system. + * Creates a new NullnessTypeAnnotator. * - * @return the list of annotations of the non-null type system + * @param atypeFactory this factory */ - public Set> getNullnessAnnotations() { - return nullnessAnnos; + public NullnessTypeAnnotator(NullnessNoInitAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this); + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + Void result = super.visitExecutable(t, p); + Element elem = t.getElement(); + if (elem.getKind() == ElementKind.CONSTRUCTOR) { + AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); + returnType.addMissingAnnotation(NONNULL); + } + return result; } - - /** - * Returns true if some annotation on the given type, or in the given list, is a nullness - * annotation such as @NonNull, @Nullable, @MonotonicNonNull, etc. - * - *

This method ignores aliases of nullness annotations that are declaration annotations, - * because they may apply to inner types. - * - * @param annoTrees a list of annotations that the Java parser attached to the variable/method - * declaration; null if this type is not from such a location. This is a list of extra - * annotations to check, in addition to those on the type. - * @param typeTree the type whose annotations to test - * @return true if some annotation is a nullness annotation - */ - protected boolean containsNullnessAnnotation( - @Nullable List annoTrees, Tree typeTree) { - List annos = - TreeUtils.getExplicitAnnotationTrees(annoTrees, typeTree); - return containsNullnessAnnotation(annos); + } + + /** + * Returns the list of annotations of the non-null type system. + * + * @return the list of annotations of the non-null type system + */ + public Set> getNullnessAnnotations() { + return nullnessAnnos; + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this); + } + + /** + * Returns true if some annotation on the given type, or in the given list, is a nullness + * annotation such as @NonNull, @Nullable, @MonotonicNonNull, etc. + * + *

This method ignores aliases of nullness annotations that are declaration annotations, + * because they may apply to inner types. + * + * @param annoTrees a list of annotations that the Java parser attached to the variable/method + * declaration; null if this type is not from such a location. This is a list of extra + * annotations to check, in addition to those on the type. + * @param typeTree the type whose annotations to test + * @return true if some annotation is a nullness annotation + */ + protected boolean containsNullnessAnnotation( + @Nullable List annoTrees, Tree typeTree) { + List annos = + TreeUtils.getExplicitAnnotationTrees(annoTrees, typeTree); + return containsNullnessAnnotation(annos); + } + + /** + * Returns true if some annotation in the given list is a nullness annotation such + * as @NonNull, @Nullable, @MonotonicNonNull, etc. + * + *

This method ignores aliases of nullness annotations that are declaration annotations, + * because they may apply to inner types. + * + *

Clients that are processing a field or variable definition, or a method return type, should + * call {@link #containsNullnessAnnotation(List, Tree)} instead. + * + * @param annoTrees a list of annotations to check + * @return true if some annotation is a nullness annotation + * @see #containsNullnessAnnotation(List, Tree) + */ + protected boolean containsNullnessAnnotation(List annoTrees) { + for (AnnotationTree annoTree : annoTrees) { + AnnotationMirror am = TreeUtils.annotationFromAnnotationTree(annoTree); + if (isNullnessAnnotation(am) && AnnotationUtils.isTypeUseAnnotation(am)) { + return true; + } } - - /** - * Returns true if some annotation in the given list is a nullness annotation such - * as @NonNull, @Nullable, @MonotonicNonNull, etc. - * - *

This method ignores aliases of nullness annotations that are declaration annotations, - * because they may apply to inner types. - * - *

Clients that are processing a field or variable definition, or a method return type, - * should call {@link #containsNullnessAnnotation(List, Tree)} instead. - * - * @param annoTrees a list of annotations to check - * @return true if some annotation is a nullness annotation - * @see #containsNullnessAnnotation(List, Tree) - */ - protected boolean containsNullnessAnnotation(List annoTrees) { - for (AnnotationTree annoTree : annoTrees) { - AnnotationMirror am = TreeUtils.annotationFromAnnotationTree(annoTree); - if (isNullnessAnnotation(am) && AnnotationUtils.isTypeUseAnnotation(am)) { - return true; - } - } - return false; + return false; + } + + /** + * Returns true if the given annotation is a nullness annotation such as {@code @NonNull}, + * {@code @Nullable}, {@code @MonotonicNonNull}, {@code @PolyNull}, or an alias thereof. + * + * @param am an annotation + * @return true if the given annotation is a nullness annotation + */ + protected boolean isNullnessAnnotation(AnnotationMirror am) { + return isNonNullOrAlias(am) + || isNullableOrAlias(am) + || AnnotationUtils.areSameByName(am, MONOTONIC_NONNULL) + || isPolyNullOrAlias(am); + } + + /** + * Returns true if the given annotation is {@code @NonNull} or an alias for it. + * + * @param am an annotation + * @return true if the given annotation is {@code @NonNull} or an alias for it + */ + protected boolean isNonNullOrAlias(AnnotationMirror am) { + AnnotationMirror canonical = canonicalAnnotation(am); + if (canonical != null) { + am = canonical; } - - /** - * Returns true if the given annotation is a nullness annotation such as {@code @NonNull}, - * {@code @Nullable}, {@code @MonotonicNonNull}, {@code @PolyNull}, or an alias thereof. - * - * @param am an annotation - * @return true if the given annotation is a nullness annotation - */ - protected boolean isNullnessAnnotation(AnnotationMirror am) { - return isNonNullOrAlias(am) - || isNullableOrAlias(am) - || AnnotationUtils.areSameByName(am, MONOTONIC_NONNULL) - || isPolyNullOrAlias(am); + return AnnotationUtils.areSameByName(am, NONNULL); + } + + /** + * Returns true if the given annotation is {@code @Nullable} or an alias for it. + * + * @param am an annotation + * @return true if the given annotation is {@code @Nullable} or an alias for it + */ + protected boolean isNullableOrAlias(AnnotationMirror am) { + AnnotationMirror canonical = canonicalAnnotation(am); + if (canonical != null) { + am = canonical; } - - /** - * Returns true if the given annotation is {@code @NonNull} or an alias for it. - * - * @param am an annotation - * @return true if the given annotation is {@code @NonNull} or an alias for it - */ - protected boolean isNonNullOrAlias(AnnotationMirror am) { - AnnotationMirror canonical = canonicalAnnotation(am); - if (canonical != null) { - am = canonical; - } - return AnnotationUtils.areSameByName(am, NONNULL); + return AnnotationUtils.areSameByName(am, NULLABLE); + } + + /** + * Returns true if the given annotation is {@code @PolyNull} or an alias for it. + * + * @param am an annotation + * @return true if the given annotation is {@code @PolyNull} or an alias for it + */ + protected boolean isPolyNullOrAlias(AnnotationMirror am) { + AnnotationMirror canonical = canonicalAnnotation(am); + if (canonical != null) { + am = canonical; } - - /** - * Returns true if the given annotation is {@code @Nullable} or an alias for it. - * - * @param am an annotation - * @return true if the given annotation is {@code @Nullable} or an alias for it - */ - protected boolean isNullableOrAlias(AnnotationMirror am) { - AnnotationMirror canonical = canonicalAnnotation(am); - if (canonical != null) { - am = canonical; - } - return AnnotationUtils.areSameByName(am, NULLABLE); + return AnnotationUtils.areSameByName(am, POLYNULL); + } + + // If a reference field has no initializer, then its default value is null. Treat that as + // @MonotonicNonNull rather than as @Nullable. + @Override + public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { + AnnotatedTypeMirror result = super.getDefaultValueAnnotatedType(typeMirror); + if (getAnnotationByClass(result.getAnnotations(), Nullable.class) != null) { + result.replaceAnnotation(MONOTONIC_NONNULL); } - - /** - * Returns true if the given annotation is {@code @PolyNull} or an alias for it. - * - * @param am an annotation - * @return true if the given annotation is {@code @PolyNull} or an alias for it - */ - protected boolean isPolyNullOrAlias(AnnotationMirror am) { - AnnotationMirror canonical = canonicalAnnotation(am); - if (canonical != null) { - am = canonical; - } - return AnnotationUtils.areSameByName(am, POLYNULL); + return result; + } + + /** A non-null reference to an object stays non-null under mutation. */ + @Override + public boolean isImmutable(TypeMirror type) { + return true; + } + + /* NO-AFU + // If + // 1. rhs is @Nullable + // 2. lhs is a field of this + // 3. in a constructor, initializer block, or field initializer + // then change rhs to @MonotonicNonNull. + @Override + public void wpiAdjustForUpdateField( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { + // Synthetic variable names contain "#". Ignore them. + if (!rhsATM.hasAnnotation(Nullable.class) || fieldName.contains("#")) { + return; } - - // If a reference field has no initializer, then its default value is null. Treat that as - // @MonotonicNonNull rather than as @Nullable. - @Override - public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { - AnnotatedTypeMirror result = super.getDefaultValueAnnotatedType(typeMirror); - if (getAnnotationByClass(result.getAnnotations(), Nullable.class) != null) { - result.replaceAnnotation(MONOTONIC_NONNULL); - } - return result; + TreePath lhsPath = getPath(lhsTree); + TypeElement enclosingClassOfLhs = + TreeUtils.elementFromDeclaration(TreePathUtil.enclosingClass(lhsPath)); + ClassSymbol enclosingClassOfField = ((VarSymbol) element).enclClass(); + if (enclosingClassOfLhs.equals(enclosingClassOfField) && TreePathUtil.inConstructor(lhsPath)) { + rhsATM.replaceAnnotation(MONOTONIC_NONNULL); } - - /** A non-null reference to an object stays non-null under mutation. */ - @Override - public boolean isImmutable(TypeMirror type) { - return true; - } - - /* NO-AFU - // If - // 1. rhs is @Nullable - // 2. lhs is a field of this - // 3. in a constructor, initializer block, or field initializer - // then change rhs to @MonotonicNonNull. - @Override - public void wpiAdjustForUpdateField( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { - // Synthetic variable names contain "#". Ignore them. - if (!rhsATM.hasAnnotation(Nullable.class) || fieldName.contains("#")) { - return; - } - TreePath lhsPath = getPath(lhsTree); - TypeElement enclosingClassOfLhs = - TreeUtils.elementFromDeclaration(TreePathUtil.enclosingClass(lhsPath)); - ClassSymbol enclosingClassOfField = ((VarSymbol) element).enclClass(); - if (enclosingClassOfLhs.equals(enclosingClassOfField) && TreePathUtil.inConstructor(lhsPath)) { - rhsATM.replaceAnnotation(MONOTONIC_NONNULL); - } + } + + // If + // 1. rhs is @MonotonicNonNull + // then change rhs to @Nullable + @Override + public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) { + if (rhsATM.hasAnnotation(MonotonicNonNull.class)) { + rhsATM.replaceAnnotation(NULLABLE); } - - // If - // 1. rhs is @MonotonicNonNull - // then change rhs to @Nullable - @Override - public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) { - if (rhsATM.hasAnnotation(MonotonicNonNull.class)) { - rhsATM.replaceAnnotation(NULLABLE); - } + } + + @Override + public boolean wpiShouldInferTypesForReceivers() { + // All receivers must be non-null, or the dereference involved in + // the method call would fail (and cause an NPE). So, WPI should not + // infer non-null or nullable annotations on method receiver parameters. + return false; + } + + // This implementation overrides the superclass implementation to: + // * check for @MonotonicNonNull + // * output @RequiresNonNull rather than @RequiresQualifier. + @Override + protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( + String expression, + AnnotationMirror qualifier, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + // TODO: This does not handle the possibility that the user set a different default + // annotation. + if (!(declaredType.hasAnnotation(NULLABLE) + || declaredType.hasAnnotation(POLYNULL) + || declaredType.hasAnnotation(MONOTONIC_NONNULL))) { + return null; } - @Override - public boolean wpiShouldInferTypesForReceivers() { - // All receivers must be non-null, or the dereference involved in - // the method call would fail (and cause an NPE). So, WPI should not - // infer non-null or nullable annotations on method receiver parameters. - return false; + if (preOrPost == BeforeOrAfter.AFTER + && declaredType.hasAnnotation(MONOTONIC_NONNULL) + && preconds.contains(requiresNonNullAnno(expression))) { + // The postcondition is implied by the precondition and the field being + // @MonotonicNonNull. + return null; } - // This implementation overrides the superclass implementation to: - // * check for @MonotonicNonNull - // * output @RequiresNonNull rather than @RequiresQualifier. - @Override - protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( - String expression, - AnnotationMirror qualifier, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - // TODO: This does not handle the possibility that the user set a different default - // annotation. - if (!(declaredType.hasAnnotation(NULLABLE) - || declaredType.hasAnnotation(POLYNULL) - || declaredType.hasAnnotation(MONOTONIC_NONNULL))) { - return null; + if (AnnotationUtils.areSameByName( + qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { + if (preOrPost == BeforeOrAfter.BEFORE) { + return requiresNonNullAnno(expression); + } else { + return ensuresNonNullAnno(expression); } if (preOrPost == BeforeOrAfter.AFTER - && declaredType.hasAnnotation(MONOTONIC_NONNULL) - && preconds.contains(requiresNonNullAnno(expression))) { - // The postcondition is implied by the precondition and the field being - // @MonotonicNonNull. - return null; + && declaredType.hasAnnotation(MONOTONIC_NONNULL) + && preconds.contains(requiresNonNullAnno(expression))) { + // The postcondition is implied by the precondition and the field being + // @MonotonicNonNull. + return null; } if (AnnotationUtils.areSameByName( - qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { - if (preOrPost == BeforeOrAfter.BEFORE) { - return requiresNonNullAnno(expression); - } else { - return ensuresNonNullAnno(expression); - } - - if (preOrPost == BeforeOrAfter.AFTER - && declaredType.hasAnnotation(MONOTONIC_NONNULL) - && preconds.contains(requiresNonNullAnno(expression))) { - // The postcondition is implied by the precondition and the field being - // @MonotonicNonNull. - return null; - } - - if (AnnotationUtils.areSameByName( - qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { - if (preOrPost == BeforeOrAfter.BEFORE) { - return requiresNonNullAnno(expression); - } else { - return ensuresNonNullAnno(expression); - } - } - return super.createRequiresOrEnsuresQualifier( - expression, qualifier, declaredType, preOrPost, preconds); - } - */ - - /* NO-AFU - * Returns a {@code RequiresNonNull("...")} annotation for the given expression. - * - * @param expression an expression - * @return a {@code RequiresNonNull("...")} annotation for the given expression - */ - /* NO-AFU - private AnnotationMirror requiresNonNullAnno(String expression) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, RequiresNonNull.class); - builder.setValue("value", new String[] {expression}); - AnnotationMirror am = builder.build(); - return am; - } - */ - - /* NO-AFU - * Returns a {@code EnsuresNonNull("...")} annotation for the given expression. - * - * @param expression an expression - * @return a {@code EnsuresNonNull("...")} annotation for the given expression - */ - /* NO-AFU - private AnnotationMirror ensuresNonNullAnno(String expression) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresNonNull.class); - builder.setValue("value", new String[] {expression}); - AnnotationMirror am = builder.build(); - return am; - } - */ - - /** - * Returns true if {@code node} is an invocation of Map.get. - * - * @param node a CFG node - * @return true if {@code node} is an invocation of Map.get - */ - public boolean isMapGet(Node node) { - return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); - } + qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { + if (preOrPost == BeforeOrAfter.BEFORE) { + return requiresNonNullAnno(expression); + } else { + return ensuresNonNullAnno(expression); + } + } + return super.createRequiresOrEnsuresQualifier( + expression, qualifier, declaredType, preOrPost, preconds); + } + */ + + /* NO-AFU + * Returns a {@code RequiresNonNull("...")} annotation for the given expression. + * + * @param expression an expression + * @return a {@code RequiresNonNull("...")} annotation for the given expression + */ + /* NO-AFU + private AnnotationMirror requiresNonNullAnno(String expression) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, RequiresNonNull.class); + builder.setValue("value", new String[] {expression}); + AnnotationMirror am = builder.build(); + return am; + } + */ + + /* NO-AFU + * Returns a {@code EnsuresNonNull("...")} annotation for the given expression. + * + * @param expression an expression + * @return a {@code EnsuresNonNull("...")} annotation for the given expression + */ + /* NO-AFU + private AnnotationMirror ensuresNonNullAnno(String expression) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresNonNull.class); + builder.setValue("value", new String[] {expression}); + AnnotationMirror am = builder.build(); + return am; + } + */ + + /** + * Returns true if {@code node} is an invocation of Map.get. + * + * @param node a CFG node + * @return true if {@code node} is an invocation of Map.get + */ + public boolean isMapGet(Node node) { + return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java index ee85f8ada9e..b68697f8dd5 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.nullness; +import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; @@ -7,58 +8,54 @@ import org.checkerframework.framework.util.AnnotationFormatter; import org.checkerframework.framework.util.DefaultAnnotationFormatter; -import java.util.Set; - /** A DefaultAnnotatedTypeFormatter that prints null literals without their annotations. */ public class NullnessNoInitAnnotatedTypeFormatter extends DefaultAnnotatedTypeFormatter { + /** + * Create a new NullnessNoInitAnnotatedTypeFormatter + * + * @param printVerboseGenerics whether to print type variables in a less ambiguous manner using + * {@code []} to delimit bounds + * @param printInvisibleQualifiers whether or not to print invisible qualifiers + */ + public NullnessNoInitAnnotatedTypeFormatter( + boolean printVerboseGenerics, boolean printInvisibleQualifiers) { + super( + new NullnessFormattingVisitor( + new DefaultAnnotationFormatter(), printVerboseGenerics, printInvisibleQualifiers)); + } + + /** The visitor used by the {@code NullnessNoInitAnnotatedTypeFormatter}. */ + protected static class NullnessFormattingVisitor extends FormattingVisitor { + /** - * Create a new NullnessNoInitAnnotatedTypeFormatter + * Create a new NullnessFormattingVisitor. * + * @param annoFormatter the formatter to use * @param printVerboseGenerics whether to print type variables in a less ambiguous manner using * {@code []} to delimit bounds - * @param printInvisibleQualifiers whether or not to print invisible qualifiers + * @param defaultInvisiblesSetting whether or not to print invisible qualifiers */ - public NullnessNoInitAnnotatedTypeFormatter( - boolean printVerboseGenerics, boolean printInvisibleQualifiers) { - super( - new NullnessFormattingVisitor( - new DefaultAnnotationFormatter(), - printVerboseGenerics, - printInvisibleQualifiers)); + public NullnessFormattingVisitor( + AnnotationFormatter annoFormatter, + boolean printVerboseGenerics, + boolean defaultInvisiblesSetting) { + super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); } - /** The visitor used by the {@code NullnessNoInitAnnotatedTypeFormatter}. */ - protected static class NullnessFormattingVisitor extends FormattingVisitor { - - /** - * Create a new NullnessFormattingVisitor. - * - * @param annoFormatter the formatter to use - * @param printVerboseGenerics whether to print type variables in a less ambiguous manner - * using {@code []} to delimit bounds - * @param defaultInvisiblesSetting whether or not to print invisible qualifiers - */ - public NullnessFormattingVisitor( - AnnotationFormatter annoFormatter, - boolean printVerboseGenerics, - boolean defaultInvisiblesSetting) { - super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); + @Override + public String visitNull(AnnotatedNullType type, Set visiting) { + if (type.getAnnotation(Nullable.class) != null) { + // The null type will be understood as nullable by readers (I hope), therefore omit + // the annotations if they are @Nullable. + // Note: The visitTypeVariable will still print lower bounds with Null kind as + // "Void" + if (!currentPrintInvisibleSetting) { + return "null (NullType)"; } + } - @Override - public String visitNull(AnnotatedNullType type, Set visiting) { - if (type.getAnnotation(Nullable.class) != null) { - // The null type will be understood as nullable by readers (I hope), therefore omit - // the annotations if they are @Nullable. - // Note: The visitTypeVariable will still print lower bounds with Null kind as - // "Void" - if (!currentPrintInvisibleSetting) { - return "null (NullType)"; - } - } - - return super.visitNull(type, visiting); - } + return super.visitNull(type, visiting); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java index 78942759eaf..78edc3ebcbb 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java @@ -1,5 +1,7 @@ package org.checkerframework.checker.nullness; +import java.util.HashMap; +import java.util.Map; import org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -11,174 +13,168 @@ import org.checkerframework.framework.qual.MonotonicQualifier; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import java.util.HashMap; -import java.util.Map; - /** * In addition to the base class behavior, tracks whether {@link PolyNull} is known to be {@link * NonNull} or {@link Nullable} (or not known to be either). */ public class NullnessNoInitStore extends CFAbstractStore { - /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ - protected boolean isPolyNullNonNull; - - /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ - protected boolean isPolyNullNull; - - /** - * Initialized fields and their values. - * - *

This is used by {@link #newFieldValueAfterMethodCall(FieldAccess, - * GenericAnnotatedTypeFactory, NullnessNoInitValue)} as cache to avoid performance issue in - * #1438. - * - * @see - * InitializationAnnotatedTypeFactory#isInitialized(org.checkerframework.framework.type.GenericAnnotatedTypeFactory, - * org.checkerframework.framework.flow.CFAbstractValue, - * javax.lang.model.element.VariableElement) - */ - protected Map initializedFields; - - /** - * Create a NullnessStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume - * that only one thread is running at all times)? - */ - public NullnessNoInitStore( - CFAbstractAnalysis analysis, - boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - isPolyNullNonNull = false; - isPolyNullNull = false; - } - - /** - * Create a NullnessStore (copy constructor). - * - * @param s a store to copy - */ - public NullnessNoInitStore(NullnessNoInitStore s) { - super(s); - isPolyNullNonNull = s.isPolyNullNonNull; - isPolyNullNull = s.isPolyNullNull; - if (s.initializedFields != null) { - initializedFields = s.initializedFields; - } + /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ + protected boolean isPolyNullNonNull; + + /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ + protected boolean isPolyNullNull; + + /** + * Initialized fields and their values. + * + *

This is used by {@link #newFieldValueAfterMethodCall(FieldAccess, + * GenericAnnotatedTypeFactory, NullnessNoInitValue)} as cache to avoid performance issue in + * #1438. + * + * @see + * InitializationAnnotatedTypeFactory#isInitialized(org.checkerframework.framework.type.GenericAnnotatedTypeFactory, + * org.checkerframework.framework.flow.CFAbstractValue, + * javax.lang.model.element.VariableElement) + */ + protected Map initializedFields; + + /** + * Create a NullnessStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume that + * only one thread is running at all times)? + */ + public NullnessNoInitStore( + CFAbstractAnalysis analysis, + boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + isPolyNullNonNull = false; + isPolyNullNull = false; + } + + /** + * Create a NullnessStore (copy constructor). + * + * @param s a store to copy + */ + public NullnessNoInitStore(NullnessNoInitStore s) { + super(s); + isPolyNullNonNull = s.isPolyNullNonNull; + isPolyNullNull = s.isPolyNullNull; + if (s.initializedFields != null) { + initializedFields = s.initializedFields; } - - @Override - protected NullnessNoInitValue newFieldValueAfterMethodCall( - FieldAccess fieldAccess, - GenericAnnotatedTypeFactory - atypeFactory, - NullnessNoInitValue value) { - // If the field is unassignable, it cannot change; thus we keep - // its current value. - // Unassignable fields must be handled before initialized fields - // because in the case of a field that is both unassignable and - // initialized, the initializedFields cache may contain an older, - // less refined value. - if (fieldAccess.isUnassignableByOtherCode()) { - return value; - } - - if (initializedFields == null) { - initializedFields = new HashMap<>(4); - } - - // If the field is initialized, it can change, but cannot be uninitialized. - // We thus keep a new value based on its declared type. - if (initializedFields.containsKey(fieldAccess)) { - return initializedFields.get(fieldAccess); - } else if (InitializationAnnotatedTypeFactory.isInitialized( - atypeFactory, value, fieldAccess.getField()) - && atypeFactory - .getAnnotationWithMetaAnnotation( - fieldAccess.getField(), MonotonicQualifier.class) - .isEmpty()) { - - NullnessNoInitValue newValue = - analysis.createAbstractValue( - atypeFactory.getAnnotatedType(fieldAccess.getField()).getAnnotations(), - value.getUnderlyingType()); - initializedFields.put(fieldAccess, newValue); - return newValue; - } - - // If the field has a monotonic annotation, we use the superclass's - // handling of monotonic annotations. - return super.newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + } + + @Override + protected NullnessNoInitValue newFieldValueAfterMethodCall( + FieldAccess fieldAccess, + GenericAnnotatedTypeFactory atypeFactory, + NullnessNoInitValue value) { + // If the field is unassignable, it cannot change; thus we keep + // its current value. + // Unassignable fields must be handled before initialized fields + // because in the case of a field that is both unassignable and + // initialized, the initializedFields cache may contain an older, + // less refined value. + if (fieldAccess.isUnassignableByOtherCode()) { + return value; } - @Override - public NullnessNoInitStore leastUpperBound(NullnessNoInitStore other) { - NullnessNoInitStore lub = super.leastUpperBound(other); - lub.isPolyNullNonNull = isPolyNullNonNull && other.isPolyNullNonNull; - lub.isPolyNullNull = isPolyNullNull && other.isPolyNullNull; - return lub; + if (initializedFields == null) { + initializedFields = new HashMap<>(4); } - @Override - protected boolean supersetOf(CFAbstractStore o) { - if (!(o instanceof NullnessNoInitStore)) { - return false; - } - NullnessNoInitStore other = (NullnessNoInitStore) o; - if ((other.isPolyNullNonNull != isPolyNullNonNull) - || (other.isPolyNullNull != isPolyNullNull)) { - return false; - } - return super.supersetOf(other); + // If the field is initialized, it can change, but cannot be uninitialized. + // We thus keep a new value based on its declared type. + if (initializedFields.containsKey(fieldAccess)) { + return initializedFields.get(fieldAccess); + } else if (InitializationAnnotatedTypeFactory.isInitialized( + atypeFactory, value, fieldAccess.getField()) + && atypeFactory + .getAnnotationWithMetaAnnotation(fieldAccess.getField(), MonotonicQualifier.class) + .isEmpty()) { + + NullnessNoInitValue newValue = + analysis.createAbstractValue( + atypeFactory.getAnnotatedType(fieldAccess.getField()).getAnnotations(), + value.getUnderlyingType()); + initializedFields.put(fieldAccess, newValue); + return newValue; } - @Override - protected String internalVisualize( - CFGVisualizer viz) { - return super.internalVisualize(viz) - + viz.getSeparator() - + viz.visualizeStoreKeyVal("isPolyNullNonNull", isPolyNullNonNull) - + viz.getSeparator() - + viz.visualizeStoreKeyVal("isPolyNullNull", isPolyNullNull); + // If the field has a monotonic annotation, we use the superclass's + // handling of monotonic annotations. + return super.newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + } + + @Override + public NullnessNoInitStore leastUpperBound(NullnessNoInitStore other) { + NullnessNoInitStore lub = super.leastUpperBound(other); + lub.isPolyNullNonNull = isPolyNullNonNull && other.isPolyNullNonNull; + lub.isPolyNullNull = isPolyNullNull && other.isPolyNullNull; + return lub; + } + + @Override + protected boolean supersetOf(CFAbstractStore o) { + if (!(o instanceof NullnessNoInitStore)) { + return false; } - - /** - * Returns true if, at this point, {@link PolyNull} is known to be {@link NonNull}. - * - * @return true if, at this point, {@link PolyNull} is known to be {@link NonNull} - */ - public boolean isPolyNullNonNull() { - return isPolyNullNonNull; - } - - /** - * Set the value of whether, at this point, {@link PolyNull} is known to be {@link NonNull}. - * - * @param isPolyNullNonNull whether, at this point, {@link PolyNull} is known to be {@link - * NonNull} - */ - public void setPolyNullNonNull(boolean isPolyNullNonNull) { - this.isPolyNullNonNull = isPolyNullNonNull; - } - - /** - * Returns true if, at this point, {@link PolyNull} is known to be {@link Nullable}. - * - * @return true if, at this point, {@link PolyNull} is known to be {@link Nullable} - */ - public boolean isPolyNullNull() { - return isPolyNullNull; - } - - /** - * Set the value of whether, at this point, {@link PolyNull} is known to be {@link Nullable}. - * - * @param isPolyNullNull whether, at this point, {@link PolyNull} is known to be {@link - * Nullable} - */ - public void setPolyNullNull(boolean isPolyNullNull) { - this.isPolyNullNull = isPolyNullNull; + NullnessNoInitStore other = (NullnessNoInitStore) o; + if ((other.isPolyNullNonNull != isPolyNullNonNull) + || (other.isPolyNullNull != isPolyNullNull)) { + return false; } + return super.supersetOf(other); + } + + @Override + protected String internalVisualize( + CFGVisualizer viz) { + return super.internalVisualize(viz) + + viz.getSeparator() + + viz.visualizeStoreKeyVal("isPolyNullNonNull", isPolyNullNonNull) + + viz.getSeparator() + + viz.visualizeStoreKeyVal("isPolyNullNull", isPolyNullNull); + } + + /** + * Returns true if, at this point, {@link PolyNull} is known to be {@link NonNull}. + * + * @return true if, at this point, {@link PolyNull} is known to be {@link NonNull} + */ + public boolean isPolyNullNonNull() { + return isPolyNullNonNull; + } + + /** + * Set the value of whether, at this point, {@link PolyNull} is known to be {@link NonNull}. + * + * @param isPolyNullNonNull whether, at this point, {@link PolyNull} is known to be {@link + * NonNull} + */ + public void setPolyNullNonNull(boolean isPolyNullNonNull) { + this.isPolyNullNonNull = isPolyNullNonNull; + } + + /** + * Returns true if, at this point, {@link PolyNull} is known to be {@link Nullable}. + * + * @return true if, at this point, {@link PolyNull} is known to be {@link Nullable} + */ + public boolean isPolyNullNull() { + return isPolyNullNull; + } + + /** + * Set the value of whether, at this point, {@link PolyNull} is known to be {@link Nullable}. + * + * @param isPolyNullNull whether, at this point, {@link PolyNull} is known to be {@link Nullable} + */ + public void setPolyNullNull(boolean isPolyNullNull) { + this.isPolyNullNull = isPolyNullNull; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java index 0536a3ccb7f..28968b51033 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java @@ -2,7 +2,8 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; - +import java.util.NavigableSet; +import java.util.Set; import org.checkerframework.checker.initialization.InitializationChecker; import org.checkerframework.checker.initialization.InitializationFieldAccessSubchecker; import org.checkerframework.checker.nullness.qual.NonNull; @@ -10,9 +11,6 @@ import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.qual.StubFiles; -import java.util.NavigableSet; -import java.util.Set; - /** * The subchecker of the {@link NullnessChecker} which actually checks {@link NonNull} and related * qualifiers. @@ -23,50 +21,50 @@ @StubFiles({"junit-assertions.astub"}) public class NullnessNoInitSubchecker extends BaseTypeChecker { - /** Default constructor for NonNullChecker. */ - public NullnessNoInitSubchecker() {} + /** Default constructor for NonNullChecker. */ + public NullnessNoInitSubchecker() {} - @Override - public NullnessNoInitAnnotatedTypeFactory getTypeFactory() { - return (NullnessNoInitAnnotatedTypeFactory) super.getTypeFactory(); - } + @Override + public NullnessNoInitAnnotatedTypeFactory getTypeFactory() { + return (NullnessNoInitAnnotatedTypeFactory) super.getTypeFactory(); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - if (!hasOption("assumeKeyFor")) { - checkers.add(KeyForSubchecker.class); - } - checkers.add(InitializationFieldAccessSubchecker.class); - return checkers; + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + if (!hasOption("assumeKeyFor")) { + checkers.add(KeyForSubchecker.class); } + checkers.add(InitializationFieldAccessSubchecker.class); + return checkers; + } - @Override - public NavigableSet getSuppressWarningsPrefixes() { - NavigableSet result = super.getSuppressWarningsPrefixes(); - result.add("nullness"); - return result; - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + NavigableSet result = super.getSuppressWarningsPrefixes(); + result.add("nullness"); + return result; + } - @Override - protected String getWarningMessagePrefix() { - return "nullness"; - } + @Override + protected String getWarningMessagePrefix() { + return "nullness"; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new NullnessNoInitVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new NullnessNoInitVisitor(this); + } - // The NullnessNoInitChecker should also skip defs skipped by the NullnessChecker + // The NullnessNoInitChecker should also skip defs skipped by the NullnessChecker - @Override - public boolean shouldSkipDefs(ClassTree tree) { - return super.shouldSkipDefs(tree) || parentChecker.shouldSkipDefs(tree); - } + @Override + public boolean shouldSkipDefs(ClassTree tree) { + return super.shouldSkipDefs(tree) || parentChecker.shouldSkipDefs(tree); + } - @Override - public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { - return super.shouldSkipDefs(cls, meth) || parentChecker.shouldSkipDefs(cls, meth); - } + @Override + public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { + return super.shouldSkipDefs(cls, meth) || parentChecker.shouldSkipDefs(cls, meth); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java index 7c951135d8c..34e4f3064ee 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java @@ -3,7 +3,14 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; - +import java.util.List; +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; @@ -37,16 +44,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; - /** * Transfer function for the non-null type system. Performs the following refinements: * @@ -61,453 +58,443 @@ * */ public class NullnessNoInitTransfer - extends CFAbstractTransfer< - NullnessNoInitValue, NullnessNoInitStore, NullnessNoInitTransfer> { - - /** The @{@link NonNull} annotation. */ - protected final AnnotationMirror NONNULL; - - /** The @{@link Nullable} annotation. */ - protected final AnnotationMirror NULLABLE; - - /** The @{@link PolyNull} annotation. */ - protected final AnnotationMirror POLYNULL; - - /** - * Java's Map interface. - * - *

The qualifiers in this type don't matter -- it is not used as a fully-annotated - * AnnotatedDeclaredType, but just passed to asSuper(). - */ - protected final AnnotatedDeclaredType MAP_TYPE; - - /** The type factory for the nullness analysis that was passed to the constructor. */ - protected final NullnessNoInitAnnotatedTypeFactory nullnessTypeFactory; - - /** - * The type factory for the map key analysis, or null if the Map Key Checker should not be run. - */ - protected final @Nullable KeyForAnnotatedTypeFactory keyForTypeFactory; - - /** True if -AassumeKeyFor was provided on the command line. */ - private final boolean assumeKeyFor; - - /** - * True if conservativeArgumentNullnessAfterInvocation flag is turned off, meaning that after a - * method call or constructor invocation, arguments of the invocation (including the receiver) - * are assumed to be non-null. - */ - private final boolean nonNullAssumptionAfterInvocation; - - /** - * Create a new NullnessTransfer for the given analysis. - * - * @param analysis nullness analysis - */ - public NullnessNoInitTransfer(NullnessNoInitAnalysis analysis) { - super(analysis); - this.nullnessTypeFactory = (NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory(); - Elements elements = nullnessTypeFactory.getElementUtils(); - BaseTypeChecker checker = nullnessTypeFactory.getChecker(); - assumeKeyFor = checker.hasOption("assumeKeyFor"); - if (assumeKeyFor) { - this.keyForTypeFactory = null; - } else { - // It is error-prone to put a type factory in a field. It is OK here because - // keyForTypeFactory is used only to call methods isMapGet() and isKeyForMap(). - this.keyForTypeFactory = - nullnessTypeFactory.getTypeFactoryOfSubchecker(KeyForSubchecker.class); - } - - NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); - NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class); - POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class); - - MAP_TYPE = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType( - TypesUtils.typeFromClass(Map.class, analysis.getTypes(), elements), - nullnessTypeFactory, - false); - - nonNullAssumptionAfterInvocation = - !analysis.getTypeFactory() - .getChecker() - .getUltimateParentChecker() - .getBooleanOption("conservativeArgumentNullnessAfterInvocation", false); - } - - /** - * Sets a given {@link Node} to non-null in the given {@code store}. Calls to this method - * implement case 2. - * - * @param store the store to update - * @param node the node that should be non-null - */ - protected void makeNonNull(NullnessNoInitStore store, Node node) { - JavaExpression internalRepr = JavaExpression.fromNode(node); - store.insertValue(internalRepr, NONNULL); + extends CFAbstractTransfer { + + /** The @{@link NonNull} annotation. */ + protected final AnnotationMirror NONNULL; + + /** The @{@link Nullable} annotation. */ + protected final AnnotationMirror NULLABLE; + + /** The @{@link PolyNull} annotation. */ + protected final AnnotationMirror POLYNULL; + + /** + * Java's Map interface. + * + *

The qualifiers in this type don't matter -- it is not used as a fully-annotated + * AnnotatedDeclaredType, but just passed to asSuper(). + */ + protected final AnnotatedDeclaredType MAP_TYPE; + + /** The type factory for the nullness analysis that was passed to the constructor. */ + protected final NullnessNoInitAnnotatedTypeFactory nullnessTypeFactory; + + /** + * The type factory for the map key analysis, or null if the Map Key Checker should not be run. + */ + protected final @Nullable KeyForAnnotatedTypeFactory keyForTypeFactory; + + /** True if -AassumeKeyFor was provided on the command line. */ + private final boolean assumeKeyFor; + + /** + * True if conservativeArgumentNullnessAfterInvocation flag is turned off, meaning that after a + * method call or constructor invocation, arguments of the invocation (including the receiver) are + * assumed to be non-null. + */ + private final boolean nonNullAssumptionAfterInvocation; + + /** + * Create a new NullnessTransfer for the given analysis. + * + * @param analysis nullness analysis + */ + public NullnessNoInitTransfer(NullnessNoInitAnalysis analysis) { + super(analysis); + this.nullnessTypeFactory = (NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory(); + Elements elements = nullnessTypeFactory.getElementUtils(); + BaseTypeChecker checker = nullnessTypeFactory.getChecker(); + assumeKeyFor = checker.hasOption("assumeKeyFor"); + if (assumeKeyFor) { + this.keyForTypeFactory = null; + } else { + // It is error-prone to put a type factory in a field. It is OK here because + // keyForTypeFactory is used only to call methods isMapGet() and isKeyForMap(). + this.keyForTypeFactory = + nullnessTypeFactory.getTypeFactoryOfSubchecker(KeyForSubchecker.class); } - /** - * Sets a given node to non-null in the given transfer result. - * - * @param result the transfer result - * @param node the node to make non-null - */ - protected void makeNonNull( - TransferResult result, Node node) { - if (result.containsTwoStores()) { - makeNonNull(result.getThenStore(), node); - makeNonNull(result.getElseStore(), node); - } else { - makeNonNull(result.getRegularStore(), node); - } + NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); + NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class); + POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class); + + MAP_TYPE = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + TypesUtils.typeFromClass(Map.class, analysis.getTypes(), elements), + nullnessTypeFactory, + false); + + nonNullAssumptionAfterInvocation = + !analysis + .getTypeFactory() + .getChecker() + .getUltimateParentChecker() + .getBooleanOption("conservativeArgumentNullnessAfterInvocation", false); + } + + /** + * Sets a given {@link Node} to non-null in the given {@code store}. Calls to this method + * implement case 2. + * + * @param store the store to update + * @param node the node that should be non-null + */ + protected void makeNonNull(NullnessNoInitStore store, Node node) { + JavaExpression internalRepr = JavaExpression.fromNode(node); + store.insertValue(internalRepr, NONNULL); + } + + /** + * Sets a given node to non-null in the given transfer result. + * + * @param result the transfer result + * @param node the node to make non-null + */ + protected void makeNonNull( + TransferResult result, Node node) { + if (result.containsTwoStores()) { + makeNonNull(result.getThenStore(), node); + makeNonNull(result.getElseStore(), node); + } else { + makeNonNull(result.getRegularStore(), node); } - - /** - * Refine the given result to @NonNull. - * - * @param result the result to refine - */ - protected void refineToNonNull( - TransferResult result) { - NullnessNoInitValue oldResultValue = result.getResultValue(); - NullnessNoInitValue refinedResultValue = - analysis.createSingleAnnotationValue(NONNULL, oldResultValue.getUnderlyingType()); - NullnessNoInitValue newResultValue = refinedResultValue.mostSpecific(oldResultValue, null); - result.setResultValue(newResultValue); + } + + /** + * Refine the given result to @NonNull. + * + * @param result the result to refine + */ + protected void refineToNonNull(TransferResult result) { + NullnessNoInitValue oldResultValue = result.getResultValue(); + NullnessNoInitValue refinedResultValue = + analysis.createSingleAnnotationValue(NONNULL, oldResultValue.getUnderlyingType()); + NullnessNoInitValue newResultValue = refinedResultValue.mostSpecific(oldResultValue, null); + result.setResultValue(newResultValue); + } + + @Override + protected @Nullable NullnessNoInitValue finishValue( + @Nullable NullnessNoInitValue value, NullnessNoInitStore store) { + value = super.finishValue(value, store); + if (value != null) { + value.isPolyNullNonNull = store.isPolyNullNonNull(); + value.isPolyNullNull = store.isPolyNullNull(); } - - @Override - protected @Nullable NullnessNoInitValue finishValue( - @Nullable NullnessNoInitValue value, NullnessNoInitStore store) { - value = super.finishValue(value, store); - if (value != null) { - value.isPolyNullNonNull = store.isPolyNullNonNull(); - value.isPolyNullNull = store.isPolyNullNull(); - } - return value; + return value; + } + + @Override + protected @Nullable NullnessNoInitValue finishValue( + @Nullable NullnessNoInitValue value, + NullnessNoInitStore thenStore, + NullnessNoInitStore elseStore) { + value = super.finishValue(value, thenStore, elseStore); + if (value != null) { + value.isPolyNullNonNull = thenStore.isPolyNullNonNull() && elseStore.isPolyNullNonNull(); + value.isPolyNullNull = thenStore.isPolyNullNull() && elseStore.isPolyNullNull(); } - - @Override - protected @Nullable NullnessNoInitValue finishValue( - @Nullable NullnessNoInitValue value, - NullnessNoInitStore thenStore, - NullnessNoInitStore elseStore) { - value = super.finishValue(value, thenStore, elseStore); - if (value != null) { - value.isPolyNullNonNull = - thenStore.isPolyNullNonNull() && elseStore.isPolyNullNonNull(); - value.isPolyNullNull = thenStore.isPolyNullNull() && elseStore.isPolyNullNull(); + return value; + } + + /** + * {@inheritDoc} + * + *

Furthermore, this method refines the type to {@code NonNull} for the appropriate branch if + * an expression is compared to the {@code null} literal (listed as case 1 in the class + * description). + */ + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult res, + Node firstNode, + Node secondNode, + NullnessNoInitValue firstValue, + NullnessNoInitValue secondValue, + boolean notEqualTo) { + res = + super.strengthenAnnotationOfEqualTo( + res, firstNode, secondNode, firstValue, secondValue, notEqualTo); + if (firstNode instanceof NullLiteralNode) { + NullnessNoInitStore thenStore = res.getThenStore(); + NullnessNoInitStore elseStore = res.getElseStore(); + + List secondParts = splitAssignments(secondNode); + for (Node secondPart : secondParts) { + JavaExpression secondInternal = JavaExpression.fromNode(secondPart); + if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { + thenStore = thenStore == null ? res.getThenStore() : thenStore; + elseStore = elseStore == null ? res.getElseStore() : elseStore; + if (notEqualTo) { + thenStore.insertValue(secondInternal, NONNULL); + } else { + elseStore.insertValue(secondInternal, NONNULL); + } } - return value; - } - - /** - * {@inheritDoc} - * - *

Furthermore, this method refines the type to {@code NonNull} for the appropriate branch if - * an expression is compared to the {@code null} literal (listed as case 1 in the class - * description). - */ - @Override - protected TransferResult - strengthenAnnotationOfEqualTo( - TransferResult res, - Node firstNode, - Node secondNode, - NullnessNoInitValue firstValue, - NullnessNoInitValue secondValue, - boolean notEqualTo) { - res = - super.strengthenAnnotationOfEqualTo( - res, firstNode, secondNode, firstValue, secondValue, notEqualTo); - if (firstNode instanceof NullLiteralNode) { - NullnessNoInitStore thenStore = res.getThenStore(); - NullnessNoInitStore elseStore = res.getElseStore(); - - List secondParts = splitAssignments(secondNode); - for (Node secondPart : secondParts) { - JavaExpression secondInternal = JavaExpression.fromNode(secondPart); - if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { - thenStore = thenStore == null ? res.getThenStore() : thenStore; - elseStore = elseStore == null ? res.getElseStore() : elseStore; - if (notEqualTo) { - thenStore.insertValue(secondInternal, NONNULL); - } else { - elseStore.insertValue(secondInternal, NONNULL); - } - } - } - - AnnotationMirrorSet secondAnnos = - secondValue != null ? secondValue.getAnnotations() : new AnnotationMirrorSet(); - if (nullnessTypeFactory.containsSameByClass(secondAnnos, PolyNull.class)) { - thenStore = thenStore == null ? res.getThenStore() : thenStore; - elseStore = elseStore == null ? res.getElseStore() : elseStore; - // TODO: methodTree is null for lambdas. Handle that case. See Issue3850.java. - MethodTree methodTree = analysis.getContainingMethod(secondNode.getTree()); - ExecutableElement methodElem = - methodTree == null ? null : TreeUtils.elementFromDeclaration(methodTree); - if (notEqualTo) { - elseStore.setPolyNullNull(true); - if (methodElem != null && polyNullIsNonNull(methodElem, thenStore)) { - thenStore.setPolyNullNonNull(true); - } - } else { - thenStore.setPolyNullNull(true); - if (methodElem != null && polyNullIsNonNull(methodElem, elseStore)) { - elseStore.setPolyNullNonNull(true); - } - } - } - - if (thenStore != null) { - return new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); - } + } + + AnnotationMirrorSet secondAnnos = + secondValue != null ? secondValue.getAnnotations() : new AnnotationMirrorSet(); + if (nullnessTypeFactory.containsSameByClass(secondAnnos, PolyNull.class)) { + thenStore = thenStore == null ? res.getThenStore() : thenStore; + elseStore = elseStore == null ? res.getElseStore() : elseStore; + // TODO: methodTree is null for lambdas. Handle that case. See Issue3850.java. + MethodTree methodTree = analysis.getContainingMethod(secondNode.getTree()); + ExecutableElement methodElem = + methodTree == null ? null : TreeUtils.elementFromDeclaration(methodTree); + if (notEqualTo) { + elseStore.setPolyNullNull(true); + if (methodElem != null && polyNullIsNonNull(methodElem, thenStore)) { + thenStore.setPolyNullNonNull(true); + } + } else { + thenStore.setPolyNullNull(true); + if (methodElem != null && polyNullIsNonNull(methodElem, elseStore)) { + elseStore.setPolyNullNonNull(true); + } } - return res; - } + } - /** - * Returns true if every formal parameter that is declared as @PolyNull is currently known to be - * non-null. - * - * @param method a method - * @param s a store - * @return true if every formal parameter declared as @PolyNull is non-null - */ - private boolean polyNullIsNonNull(ExecutableElement method, NullnessNoInitStore s) { - // No need to check the receiver, which is always non-null. - for (VariableElement var : method.getParameters()) { - AnnotatedTypeMirror varType = nullnessTypeFactory.fromElement(var); - - if (containsPolyNullNotAtTopLevel(varType)) { - return false; - } - - if (varType.hasAnnotation(POLYNULL)) { - NullnessNoInitValue v = s.getValue(new LocalVariable(var)); - if (!AnnotationUtils.containsSameByName(v.getAnnotations(), NONNULL)) { - return false; - } - } - } - return true; + if (thenStore != null) { + return new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); + } } - - /** - * A scanner that returns true if there is an occurrence of @PolyNull that is not at the top - * level. - */ - // Not static so it can access field POLYNULL. - private class ContainsPolyNullNotAtTopLevelScanner - extends SimpleAnnotatedTypeScanner { - /** - * True if the top-level type has not yet been processed (by the first call to - * defaultAction). - */ - private boolean isTopLevel = true; - - /** Create a ContainsPolyNullNotAtTopLevelScanner. */ - ContainsPolyNullNotAtTopLevelScanner() { - super(Boolean::logicalOr, false); - } - - @Override - protected Boolean defaultAction(AnnotatedTypeMirror type, Void p) { - if (isTopLevel) { - isTopLevel = false; - return false; - } else { - return type.hasAnnotation(POLYNULL); - } + return res; + } + + /** + * Returns true if every formal parameter that is declared as @PolyNull is currently known to be + * non-null. + * + * @param method a method + * @param s a store + * @return true if every formal parameter declared as @PolyNull is non-null + */ + private boolean polyNullIsNonNull(ExecutableElement method, NullnessNoInitStore s) { + // No need to check the receiver, which is always non-null. + for (VariableElement var : method.getParameters()) { + AnnotatedTypeMirror varType = nullnessTypeFactory.fromElement(var); + + if (containsPolyNullNotAtTopLevel(varType)) { + return false; + } + + if (varType.hasAnnotation(POLYNULL)) { + NullnessNoInitValue v = s.getValue(new LocalVariable(var)); + if (!AnnotationUtils.containsSameByName(v.getAnnotations(), NONNULL)) { + return false; } + } } - + return true; + } + + /** + * A scanner that returns true if there is an occurrence of @PolyNull that is not at the top + * level. + */ + // Not static so it can access field POLYNULL. + private class ContainsPolyNullNotAtTopLevelScanner + extends SimpleAnnotatedTypeScanner { /** - * Returns true if there is an occurrence of @PolyNull that is not at the top level. - * - * @param t a type - * @return true if there is an occurrence of @PolyNull that is not at the top level + * True if the top-level type has not yet been processed (by the first call to defaultAction). */ - private boolean containsPolyNullNotAtTopLevel(AnnotatedTypeMirror t) { - return new ContainsPolyNullNotAtTopLevelScanner().visit(t); - } + private boolean isTopLevel = true; - @Override - public TransferResult visitArrayAccess( - ArrayAccessNode n, TransferInput p) { - TransferResult result = - super.visitArrayAccess(n, p); - makeNonNull(result, n.getArray()); - return result; + /** Create a ContainsPolyNullNotAtTopLevelScanner. */ + ContainsPolyNullNotAtTopLevelScanner() { + super(Boolean::logicalOr, false); } @Override - public TransferResult visitInstanceOf( - InstanceOfNode n, TransferInput p) { - TransferResult result = - super.visitInstanceOf(n, p); - NullnessNoInitStore thenStore = result.getThenStore(); - NullnessNoInitStore elseStore = result.getElseStore(); - makeNonNull(thenStore, n.getOperand()); - return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + protected Boolean defaultAction(AnnotatedTypeMirror type, Void p) { + if (isTopLevel) { + isTopLevel = false; + return false; + } else { + return type.hasAnnotation(POLYNULL); + } } - - @Override - public TransferResult visitMethodAccess( - MethodAccessNode n, TransferInput p) { - TransferResult result = - super.visitMethodAccess(n, p); - // The receiver of an instance method access is non-null. A static method access does not - // ensure that the receiver is non-null. - if (!n.isStatic()) { - makeNonNull(result, n.getReceiver()); - } - return result; + } + + /** + * Returns true if there is an occurrence of @PolyNull that is not at the top level. + * + * @param t a type + * @return true if there is an occurrence of @PolyNull that is not at the top level + */ + private boolean containsPolyNullNotAtTopLevel(AnnotatedTypeMirror t) { + return new ContainsPolyNullNotAtTopLevelScanner().visit(t); + } + + @Override + public TransferResult visitArrayAccess( + ArrayAccessNode n, TransferInput p) { + TransferResult result = super.visitArrayAccess(n, p); + makeNonNull(result, n.getArray()); + return result; + } + + @Override + public TransferResult visitInstanceOf( + InstanceOfNode n, TransferInput p) { + TransferResult result = super.visitInstanceOf(n, p); + NullnessNoInitStore thenStore = result.getThenStore(); + NullnessNoInitStore elseStore = result.getElseStore(); + makeNonNull(thenStore, n.getOperand()); + return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + } + + @Override + public TransferResult visitMethodAccess( + MethodAccessNode n, TransferInput p) { + TransferResult result = super.visitMethodAccess(n, p); + // The receiver of an instance method access is non-null. A static method access does not + // ensure that the receiver is non-null. + if (!n.isStatic()) { + makeNonNull(result, n.getReceiver()); } - - @Override - public TransferResult visitFieldAccess( - FieldAccessNode n, TransferInput p) { - TransferResult result = - super.visitFieldAccess(n, p); - // The receiver of an instance field access is non-null. A static field access does not - // ensure that the receiver is non-null. - if (!n.isStatic()) { - makeNonNull(result, n.getReceiver()); - } - return result; + return result; + } + + @Override + public TransferResult visitFieldAccess( + FieldAccessNode n, TransferInput p) { + TransferResult result = super.visitFieldAccess(n, p); + // The receiver of an instance field access is non-null. A static field access does not + // ensure that the receiver is non-null. + if (!n.isStatic()) { + makeNonNull(result, n.getReceiver()); } - - @Override - public TransferResult visitThrow( - ThrowNode n, TransferInput p) { - TransferResult result = super.visitThrow(n, p); - makeNonNull(result, n.getExpression()); - return result; + return result; + } + + @Override + public TransferResult visitThrow( + ThrowNode n, TransferInput p) { + TransferResult result = super.visitThrow(n, p); + makeNonNull(result, n.getExpression()); + return result; + } + + /** + * {@inheritDoc} + * + *

When the conservativeArgumentNullnessAfterInvocation flag is turned off, the receiver and + * arguments that are passed to non-null parameters in a method call or constructor invocation are + * unsoundly assumed to be non-null after the invocation. + * + *

When the flag is turned on, the analysis is more conservative by checking the method is + * SideEffectFree or the receiver is unassignable. Only if either one of the two is true, is the + * receiver made non-null. Similar logic is applied to the arguments of the invocation. + * + *

Provided that m is of a type that implements interface java.util.Map: + * + *

    + *
  • Given a call m.get(k), if k is @KeyFor("m") and m's value type is @NonNull, then the + * result is @NonNull in the thenStore and elseStore of the transfer result. + *
+ */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = + super.visitMethodInvocation(n, in); + + MethodInvocationTree tree = n.getTree(); + ExecutableElement method = TreeUtils.elementFromUse(tree); + + boolean isMethodSideEffectFree = + nullnessTypeFactory.isSideEffectFree(method) + || PurityUtils.isSideEffectFree(nullnessTypeFactory, method); + Node receiver = n.getTarget().getReceiver(); + if (nonNullAssumptionAfterInvocation + || isMethodSideEffectFree + || JavaExpression.fromNode(receiver).isUnassignableByOtherCode()) { + // Make receiver non-null. + makeNonNull(result, receiver); } - /** - * {@inheritDoc} - * - *

When the conservativeArgumentNullnessAfterInvocation flag is turned off, the receiver and - * arguments that are passed to non-null parameters in a method call or constructor invocation - * are unsoundly assumed to be non-null after the invocation. - * - *

When the flag is turned on, the analysis is more conservative by checking the method is - * SideEffectFree or the receiver is unassignable. Only if either one of the two is true, is the - * receiver made non-null. Similar logic is applied to the arguments of the invocation. - * - *

Provided that m is of a type that implements interface java.util.Map: - * - *

    - *
  • Given a call m.get(k), if k is @KeyFor("m") and m's value type is @NonNull, then the - * result is @NonNull in the thenStore and elseStore of the transfer result. - *
- */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = - super.visitMethodInvocation(n, in); - - MethodInvocationTree tree = n.getTree(); - ExecutableElement method = TreeUtils.elementFromUse(tree); - - boolean isMethodSideEffectFree = - nullnessTypeFactory.isSideEffectFree(method) - || PurityUtils.isSideEffectFree(nullnessTypeFactory, method); - Node receiver = n.getTarget().getReceiver(); - if (nonNullAssumptionAfterInvocation - || isMethodSideEffectFree - || JavaExpression.fromNode(receiver).isUnassignableByOtherCode()) { - // Make receiver non-null. - makeNonNull(result, receiver); - } - - // For all formal parameters with a non-null annotation, make the actual argument non-null. - // The point of this is to prevent cascaded errors -- the Nullness Checker will issue a - // warning for the method invocation, but not for subsequent uses of the argument. See test - // case FlowNullness.java. - AnnotatedExecutableType methodType = nullnessTypeFactory.getAnnotatedType(method); - List methodParams = methodType.getParameterTypes(); - List methodArgs = tree.getArguments(); - for (int i = 0; i < methodParams.size() && i < methodArgs.size(); ++i) { - if (methodParams.get(i).hasAnnotation(NONNULL) - && (nonNullAssumptionAfterInvocation - || isMethodSideEffectFree - || JavaExpression.fromTree(methodArgs.get(i)) - .isUnassignableByOtherCode())) { - makeNonNull(result, n.getArgument(i)); - } - } - - // Refine result to @NonNull if n is an invocation of Map.get, the argument is a key for - // the map, and the map's value type is not @Nullable. - if (nullnessTypeFactory.isMapGet(n)) { - boolean isKeyFor; - if (keyForTypeFactory != null) { - String mapName = JavaExpression.fromNode(receiver).toString(); - isKeyFor = keyForTypeFactory.isKeyForMap(mapName, methodArgs.get(0)); - } else { - isKeyFor = assumeKeyFor; - } - if (isKeyFor) { - AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree()); - if (!hasNullableValueType(receiverType)) { - makeNonNull(result, n); - refineToNonNull(result); - } - } - } - - return result; + // For all formal parameters with a non-null annotation, make the actual argument non-null. + // The point of this is to prevent cascaded errors -- the Nullness Checker will issue a + // warning for the method invocation, but not for subsequent uses of the argument. See test + // case FlowNullness.java. + AnnotatedExecutableType methodType = nullnessTypeFactory.getAnnotatedType(method); + List methodParams = methodType.getParameterTypes(); + List methodArgs = tree.getArguments(); + for (int i = 0; i < methodParams.size() && i < methodArgs.size(); ++i) { + if (methodParams.get(i).hasAnnotation(NONNULL) + && (nonNullAssumptionAfterInvocation + || isMethodSideEffectFree + || JavaExpression.fromTree(methodArgs.get(i)).isUnassignableByOtherCode())) { + makeNonNull(result, n.getArgument(i)); + } } - /** - * Returns true if mapType's value type (the V type argument to Map) is @Nullable. - * - * @param mapOrSubtype the Map type, or a subtype - * @return true if mapType's value type is @Nullable - */ - private boolean hasNullableValueType(AnnotatedTypeMirror mapOrSubtype) { - AnnotatedDeclaredType mapType = - AnnotatedTypes.asSuper(nullnessTypeFactory, mapOrSubtype, MAP_TYPE); - int numTypeArguments = mapType.getTypeArguments().size(); - if (numTypeArguments != 2) { - throw new TypeSystemError( - "Wrong number %d of type arguments: %s", numTypeArguments, mapType); + // Refine result to @NonNull if n is an invocation of Map.get, the argument is a key for + // the map, and the map's value type is not @Nullable. + if (nullnessTypeFactory.isMapGet(n)) { + boolean isKeyFor; + if (keyForTypeFactory != null) { + String mapName = JavaExpression.fromNode(receiver).toString(); + isKeyFor = keyForTypeFactory.isKeyForMap(mapName, methodArgs.get(0)); + } else { + isKeyFor = assumeKeyFor; + } + if (isKeyFor) { + AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree()); + if (!hasNullableValueType(receiverType)) { + makeNonNull(result, n); + refineToNonNull(result); } - AnnotatedTypeMirror valueType = mapType.getTypeArguments().get(1); - return valueType.hasAnnotation(NULLABLE); + } } - @Override - public TransferResult visitReturn( - ReturnNode n, TransferInput in) { - TransferResult result = super.visitReturn(n, in); - - if (result.getResultValue() == null) { - // Make sure there is a value for return statements, to record (at this return - // statement) the values of isPolyNullNotNull and isPolyNullNull. - return recreateTransferResult(createDummyValue(), result); - } else { - return result; - } + return result; + } + + /** + * Returns true if mapType's value type (the V type argument to Map) is @Nullable. + * + * @param mapOrSubtype the Map type, or a subtype + * @return true if mapType's value type is @Nullable + */ + private boolean hasNullableValueType(AnnotatedTypeMirror mapOrSubtype) { + AnnotatedDeclaredType mapType = + AnnotatedTypes.asSuper(nullnessTypeFactory, mapOrSubtype, MAP_TYPE); + int numTypeArguments = mapType.getTypeArguments().size(); + if (numTypeArguments != 2) { + throw new TypeSystemError("Wrong number %d of type arguments: %s", numTypeArguments, mapType); } - - /** - * Creates a dummy abstract value (whose type is not supposed to be looked at). - * - * @return a dummy abstract value - */ - private NullnessNoInitValue createDummyValue() { - TypeMirror dummy = analysis.getEnv().getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN); - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - annos.addAll(nullnessTypeFactory.getQualifierHierarchy().getBottomAnnotations()); - return new NullnessNoInitValue(analysis, annos, dummy); + AnnotatedTypeMirror valueType = mapType.getTypeArguments().get(1); + return valueType.hasAnnotation(NULLABLE); + } + + @Override + public TransferResult visitReturn( + ReturnNode n, TransferInput in) { + TransferResult result = super.visitReturn(n, in); + + if (result.getResultValue() == null) { + // Make sure there is a value for return statements, to record (at this return + // statement) the values of isPolyNullNotNull and isPolyNullNull. + return recreateTransferResult(createDummyValue(), result); + } else { + return result; } + } + + /** + * Creates a dummy abstract value (whose type is not supposed to be looked at). + * + * @return a dummy abstract value + */ + private NullnessNoInitValue createDummyValue() { + TypeMirror dummy = analysis.getEnv().getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN); + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + annos.addAll(nullnessTypeFactory.getQualifierHierarchy().getBottomAnnotations()); + return new NullnessNoInitValue(analysis, annos, dummy); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java index 8a3bb327d80..104ce05cd2d 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java @@ -1,5 +1,7 @@ package org.checkerframework.checker.nullness; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; @@ -12,95 +14,90 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TypesUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; - /** * Behaves just like {@link CFValue}, but additionally tracks whether at this point {@link PolyNull} * is known to be {@link NonNull} or {@link Nullable} (or not known to be either) */ public class NullnessNoInitValue extends CFAbstractValue { - /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ - protected boolean isPolyNullNonNull; + /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ + protected boolean isPolyNullNonNull; - /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ - protected boolean isPolyNullNull; + /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ + protected boolean isPolyNullNull; - /** - * Creates a new NullnessValue. - * - * @param analysis the analysis - * @param annotations the annotations - * @param underlyingType the underlying type - */ - public NullnessNoInitValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - } + /** + * Creates a new NullnessValue. + * + * @param analysis the analysis + * @param annotations the annotations + * @param underlyingType the underlying type + */ + public NullnessNoInitValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + } - @Override - protected NullnessNoInitValue upperBound( - @Nullable NullnessNoInitValue other, - TypeMirror upperBoundTypeMirror, - boolean shouldWiden) { - NullnessNoInitValue result = super.upperBound(other, upperBoundTypeMirror, shouldWiden); + @Override + protected NullnessNoInitValue upperBound( + @Nullable NullnessNoInitValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { + NullnessNoInitValue result = super.upperBound(other, upperBoundTypeMirror, shouldWiden); - AnnotationMirror resultNullableAnno = - analysis.getTypeFactory().getAnnotationByClass(result.annotations, Nullable.class); + AnnotationMirror resultNullableAnno = + analysis.getTypeFactory().getAnnotationByClass(result.annotations, Nullable.class); - if (resultNullableAnno != null && other != null) { - if ((this.isPolyNullNonNull - && this.containsNonNullOrPolyNull() - && other.isPolyNullNull - && other.containsNullableOrPolyNull()) - || (other.isPolyNullNonNull - && other.containsNonNullOrPolyNull() - && this.isPolyNullNull - && this.containsNullableOrPolyNull())) { - result.annotations.remove(resultNullableAnno); - result.annotations.add( - ((NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory()).POLYNULL); - } - } - return result; + if (resultNullableAnno != null && other != null) { + if ((this.isPolyNullNonNull + && this.containsNonNullOrPolyNull() + && other.isPolyNullNull + && other.containsNullableOrPolyNull()) + || (other.isPolyNullNonNull + && other.containsNonNullOrPolyNull() + && this.isPolyNullNull + && this.containsNullableOrPolyNull())) { + result.annotations.remove(resultNullableAnno); + result.annotations.add( + ((NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory()).POLYNULL); + } } + return result; + } - /** - * Returns true if this value contains {@code @NonNull} or {@code @PolyNull}. - * - * @return true if this value contains {@code @NonNull} or {@code @PolyNull} - */ - @Pure - private boolean containsNonNullOrPolyNull() { - return analysis.getTypeFactory().containsSameByClass(annotations, NonNull.class) - || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); - } + /** + * Returns true if this value contains {@code @NonNull} or {@code @PolyNull}. + * + * @return true if this value contains {@code @NonNull} or {@code @PolyNull} + */ + @Pure + private boolean containsNonNullOrPolyNull() { + return analysis.getTypeFactory().containsSameByClass(annotations, NonNull.class) + || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); + } - /** - * Returns true if this value contans {@code @Nullable} or {@code @PolyNull}. - * - * @return true if this value contans {@code @Nullable} or {@code @PolyNull} - */ - @Pure - private boolean containsNullableOrPolyNull() { - return analysis.getTypeFactory().containsSameByClass(annotations, Nullable.class) - || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); - } + /** + * Returns true if this value contans {@code @Nullable} or {@code @PolyNull}. + * + * @return true if this value contans {@code @Nullable} or {@code @PolyNull} + */ + @Pure + private boolean containsNullableOrPolyNull() { + return analysis.getTypeFactory().containsSameByClass(annotations, Nullable.class) + || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); + } - @SideEffectFree - @Override - public String toStringSimple() { - return "NV{" - + AnnotationUtils.toStringSimple(annotations) - + ", " - + TypesUtils.simpleTypeName(underlyingType) - + ", poly nn/n=" - + (isPolyNullNonNull ? 't' : 'f') - + '/' - + (isPolyNullNull ? 't' : 'f') - + '}'; - } + @SideEffectFree + @Override + public String toStringSimple() { + return "NV{" + + AnnotationUtils.toStringSimple(annotations) + + ", " + + TypesUtils.simpleTypeName(underlyingType) + + ", poly nn/n=" + + (isPolyNullNonNull ? 't' : 'f') + + '/' + + (isPolyNullNull ? 't' : 'f') + + '}'; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java index 2b85cf0efc9..208f5d9060f 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java @@ -34,7 +34,13 @@ import com.sun.source.tree.VariableTree; import com.sun.source.tree.WhileLoopTree; import com.sun.source.util.TreePath; - +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.nullness.qual.NonNull; @@ -59,938 +65,918 @@ import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - /** The visitor for the nullness type-system. */ public class NullnessNoInitVisitor extends BaseTypeVisitor { - // Error message keys - // private static final @CompilerMessageKey String ASSIGNMENT_TYPE_INCOMPATIBLE = - // "assignment.type.incompatible"; - /** Error message key. */ - private static final @CompilerMessageKey String UNBOXING_OF_NULLABLE = "unboxing.of.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String LOCKING_NULLABLE = "locking.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String THROWING_NULLABLE = "throwing.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String ACCESSING_NULLABLE = "accessing.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String CONDITION_NULLABLE = "condition.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String ITERATING_NULLABLE = "iterating.over.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String SWITCHING_NULLABLE = "switching.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String DEREFERENCE_OF_NULLABLE = - "dereference.of.nullable"; - - /** Annotation mirrors for nullness annotations. */ - private final AnnotationMirror NONNULL, NULLABLE, MONOTONIC_NONNULL, POLYNULL; - - /** TypeMirror for java.lang.String. */ - private final TypeMirror stringType; - - /** The element for java.util.Collection.size(). */ - private final ExecutableElement collectionSize; - - /** The element for java.util.Collection.toArray(T). */ - private final ExecutableElement collectionToArray; - - /** The System.clearProperty(String) method. */ - private final ExecutableElement systemClearProperty; - - /** The System.setProperties(String) method. */ - private final ExecutableElement systemSetProperties; - - /** True if checked code may clear system properties. */ - private final boolean permitClearProperty; - - /** True if -AassumeAssertionsAreEnabled was passed on the command line. */ - private final boolean assumeAssertionsAreEnabled; - - /** True if -AassumeAssertionsAreDisabled was passed on the command line. */ - private final boolean assumeAssertionsAreDisabled; - - /** True if -Alint=redundantNullComparison was passed on the command line. */ - private final boolean redundantNullComparison; - - /** - * Create a new NullnessVisitor. - * - * @param checker the checker to which this visitor belongs - */ - public NullnessNoInitVisitor(BaseTypeChecker checker) { - super(checker); - - NONNULL = atypeFactory.NONNULL; - NULLABLE = atypeFactory.NULLABLE; - MONOTONIC_NONNULL = atypeFactory.MONOTONIC_NONNULL; - POLYNULL = atypeFactory.POLYNULL; - stringType = elements.getTypeElement(String.class.getCanonicalName()).asType(); - - ProcessingEnvironment env = checker.getProcessingEnvironment(); - this.collectionSize = TreeUtils.getMethod("java.util.Collection", "size", 0, env); - this.collectionToArray = TreeUtils.getMethod("java.util.Collection", "toArray", env, "T[]"); - systemClearProperty = TreeUtils.getMethod("java.lang.System", "clearProperty", 1, env); - systemSetProperties = TreeUtils.getMethod("java.lang.System", "setProperties", 1, env); - - this.permitClearProperty = - checker.getLintOption( - NullnessChecker.LINT_PERMITCLEARPROPERTY, - NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); - assumeAssertionsAreEnabled = checker.hasOption("assumeAssertionsAreEnabled"); - assumeAssertionsAreDisabled = checker.hasOption("assumeAssertionsAreDisabled"); - redundantNullComparison = - checker.getLintOption( - NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, - NullnessChecker.LINT_DEFAULT_REDUNDANTNULLCOMPARISON); - } - - @Override - public NullnessNoInitAnnotatedTypeFactory createTypeFactory() { - return new NullnessNoInitAnnotatedTypeFactory(checker); - } - - @Override - public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { - // The Nullness Checker issues a more comprehensible "nullness.on.primitive" error rather - // than the "type.invalid.annotations.on.use" error this method would issue. - return true; - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Constructor results are always @NonNull. Other annotations are forbidden by - // #visitMethod. - // Nothing to check. - } - - @Override - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { - // Constructor results are always @NonNull, so the result type of a this/super call is - // always equal to the result type of the current constructor. - // Nothing to check. - } - - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // Allow a MonotonicNonNull field to be initialized to null at its declaration, in a - // constructor, or in an initializer block. (The latter two are, strictly speaking, unsound - // because the constructor or initializer block might have previously set the field to a - // non-null value. Maybe add an option to disable that behavior.) - Element elem = initializedElement(varTree); - if (elem != null - && atypeFactory.fromElement(elem).hasEffectiveAnnotation(MONOTONIC_NONNULL) - && !checker.getLintOption( - NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, - NullnessChecker.LINT_DEFAULT_NOINITFORMONOTONICNONNULL)) { - return true; - } - return super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); - } - - /** - * Returns the variable element, if the argument is an initialization; otherwise returns null. - * - * @param varTree an assignment LHS - * @return the initialized element, or null - */ - @SuppressWarnings("UnusedMethod") - private @Nullable Element initializedElement(Tree varTree) { - switch (varTree.getKind()) { - case VARIABLE: - // It's a variable declaration. - return TreeUtils.elementFromDeclaration((VariableTree) varTree); - - case MEMBER_SELECT: - MemberSelectTree mst = (MemberSelectTree) varTree; - ExpressionTree receiver = mst.getExpression(); - // This recognizes "this.fieldname = ..." but not "MyClass.fieldname = ..." or - // "MyClass.this.fieldname = ...". The latter forms are probably rare in a - // constructor. - // Note that this method should return non-null only for fields of this class, not - // fields of any other class, including outer classes. - if (receiver.getKind() != Tree.Kind.IDENTIFIER - || !((IdentifierTree) receiver).getName().contentEquals("this")) { - return null; - } - // fallthrough - case IDENTIFIER: - TreePath path = getCurrentPath(); - if (TreePathUtil.inConstructor(path)) { - return TreeUtils.elementFromUse((ExpressionTree) varTree); - } else { - return null; - } - - default: - return null; - } - } - - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // Use the valueExp as the context because data flow will have a value for that tree. It - // might not have a value for the var tree. This is sound because if data flow has - // determined @PolyNull is @Nullable at the RHS, then it is also @Nullable for the LHS. - // TODO: A visitor should not change types, so this call to replacePolyQualifier is a hack. - // To work around this hack here, NullnessVisitor#visitConditionalExpression needs a further - // hack. Both should be undone, by properly resolving polymorphic qualifiers in the - // ATF/transfer. - atypeFactory.replacePolyQualifier(varType, valueExp); - return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); - } - - @Override - @FormatMethod - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - if (TypesUtils.isPrimitive(varType.getUnderlyingType()) - && !TypesUtils.isPrimitive(valueType.getUnderlyingType())) { - boolean succeed = checkForNullability(valueType, valueTree, UNBOXING_OF_NULLABLE); - if (!succeed) { - // Only issue the unboxing of nullable error. - return false; - } - } - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - } - - /** Case 1: Check for null dereferencing. */ - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - // if (atypeFactory.isUnreachable(tree)) { - // return super.visitMemberSelect(tree, p); - // } - Element e = TreeUtils.elementFromUse(tree); - if (e.getKind() == ElementKind.CLASS) { - if (atypeFactory.containsNullnessAnnotation(null, tree.getExpression())) { - checker.reportError(tree, "nullness.on.outer"); - } - } else if (!(TreeUtils.isSelfAccess(tree) - || tree.getExpression().getKind() == Tree.Kind.PARAMETERIZED_TYPE - // case 8. static member access - || ElementUtils.isStatic(e))) { - checkForNullability(tree.getExpression(), DEREFERENCE_OF_NULLABLE); - } - - return super.visitMemberSelect(tree, p); - } - - /** Case 2: Check for implicit {@code .iterator} call. */ - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - checkForNullability(tree.getExpression(), ITERATING_NULLABLE); - return super.visitEnhancedForLoop(tree, p); - } - - /** Case 3: Check for array dereferencing. */ - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - checkForNullability(tree.getExpression(), ACCESSING_NULLABLE); - return super.visitArrayAccess(tree, p); - } - - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - AnnotatedArrayType type = atypeFactory.getAnnotatedType(tree); - AnnotatedTypeMirror componentType = type.getComponentType(); - if (componentType.hasEffectiveAnnotation(NONNULL) - && !isNewArrayAllZeroDims(tree) - && !isNewArrayInToArray(tree) - && !TypesUtils.isPrimitive(componentType.getUnderlyingType()) - && (checker.getLintOption("soundArrayCreationNullness", false) - // temporary, for backward compatibility - || checker.getLintOption("forbidnonnullarraycomponents", false))) { - checker.reportError( - tree, - "new.array.type.invalid", - componentType.getAnnotations(), - type.toString()); - } - - if (type.hasEffectiveAnnotation(NULLABLE) - || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) - || type.hasEffectiveAnnotation(POLYNULL)) { - checker.reportError(tree, "nullness.on.new.array"); - } - - return super.visitNewArray(tree, p); - } - - /** - * Determine whether all dimensions given in a new array expression have zero as length. For - * example "new Object[0][0];". Also true for empty dimensions, as in "new Object[] {...}". - * - * @param tree the constructor invocation to check - * @return true if every array dimension has a size of zero - */ - private static boolean isNewArrayAllZeroDims(NewArrayTree tree) { - boolean isAllZeros = true; - for (ExpressionTree dim : tree.getDimensions()) { - if (dim instanceof LiteralTree) { - Object val = ((LiteralTree) dim).getValue(); - if (!(val instanceof Number) || !Integer.valueOf(0).equals(val)) { - isAllZeros = false; - break; - } - } else { - isAllZeros = false; - break; - } - } - return isAllZeros; - } - - /** - * Return true if the given tree is "new X[]", in the context "toArray(new X[])". - * - * @param tree a tree to test - * @return true if the tree is a new array within a call to toArray() - */ - private boolean isNewArrayInToArray(NewArrayTree tree) { - if (tree.getDimensions().size() != 1) { - return false; - } - - ExpressionTree dim = tree.getDimensions().get(0); - ProcessingEnvironment env = checker.getProcessingEnvironment(); - - if (!TreeUtils.isMethodInvocation(dim, collectionSize, env)) { - return false; - } - - ExpressionTree rcvsize = ((MethodInvocationTree) dim).getMethodSelect(); - if (!(rcvsize instanceof MemberSelectTree)) { - return false; - } - rcvsize = ((MemberSelectTree) rcvsize).getExpression(); - if (!(rcvsize instanceof IdentifierTree)) { - return false; - } - - Tree encl = getCurrentPath().getParentPath().getLeaf(); - - if (!TreeUtils.isMethodInvocation(encl, collectionToArray, env)) { - return false; - } - - ExpressionTree rcvtoarray = ((MethodInvocationTree) encl).getMethodSelect(); - if (!(rcvtoarray instanceof MemberSelectTree)) { - return false; - } - rcvtoarray = ((MemberSelectTree) rcvtoarray).getExpression(); - if (!(rcvtoarray instanceof IdentifierTree)) { - return false; - } - - return ((IdentifierTree) rcvsize).getName() == ((IdentifierTree) rcvtoarray).getName(); - } - - /** Case 4: Check for thrown exception nullness. */ - @Override - protected void checkThrownExpression(ThrowTree tree) { - checkForNullability(tree.getExpression(), THROWING_NULLABLE); - } - - /** Case 5: Check for synchronizing locks. */ - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - checkForNullability(tree.getExpression(), LOCKING_NULLABLE); - return super.visitSynchronized(tree, p); - } - - @Override - public Void visitAssert(AssertTree tree, Void p) { - // See also - // org.checkerframework.dataflow.cfg.builder.CFGBuilder.CFGTranslationPhaseOne.visitAssert - - // In cases where neither assumeAssertionsAreEnabled nor assumeAssertionsAreDisabled are - // turned on and @AssumeAssertions is not used, checkForNullability is still called since - // the CFGBuilder will have generated one branch for which asserts are assumed to be - // enabled. - - boolean doVisitAssert; - if (assumeAssertionsAreEnabled - || CFCFGBuilder.assumeAssertionsActivatedForAssertTree(checker, tree)) { - doVisitAssert = true; - } else if (assumeAssertionsAreDisabled) { - doVisitAssert = false; + // Error message keys + // private static final @CompilerMessageKey String ASSIGNMENT_TYPE_INCOMPATIBLE = + // "assignment.type.incompatible"; + /** Error message key. */ + private static final @CompilerMessageKey String UNBOXING_OF_NULLABLE = "unboxing.of.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String LOCKING_NULLABLE = "locking.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String THROWING_NULLABLE = "throwing.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String ACCESSING_NULLABLE = "accessing.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String CONDITION_NULLABLE = "condition.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String ITERATING_NULLABLE = "iterating.over.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String SWITCHING_NULLABLE = "switching.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String DEREFERENCE_OF_NULLABLE = + "dereference.of.nullable"; + + /** Annotation mirrors for nullness annotations. */ + private final AnnotationMirror NONNULL, NULLABLE, MONOTONIC_NONNULL, POLYNULL; + + /** TypeMirror for java.lang.String. */ + private final TypeMirror stringType; + + /** The element for java.util.Collection.size(). */ + private final ExecutableElement collectionSize; + + /** The element for java.util.Collection.toArray(T). */ + private final ExecutableElement collectionToArray; + + /** The System.clearProperty(String) method. */ + private final ExecutableElement systemClearProperty; + + /** The System.setProperties(String) method. */ + private final ExecutableElement systemSetProperties; + + /** True if checked code may clear system properties. */ + private final boolean permitClearProperty; + + /** True if -AassumeAssertionsAreEnabled was passed on the command line. */ + private final boolean assumeAssertionsAreEnabled; + + /** True if -AassumeAssertionsAreDisabled was passed on the command line. */ + private final boolean assumeAssertionsAreDisabled; + + /** True if -Alint=redundantNullComparison was passed on the command line. */ + private final boolean redundantNullComparison; + + /** + * Create a new NullnessVisitor. + * + * @param checker the checker to which this visitor belongs + */ + public NullnessNoInitVisitor(BaseTypeChecker checker) { + super(checker); + + NONNULL = atypeFactory.NONNULL; + NULLABLE = atypeFactory.NULLABLE; + MONOTONIC_NONNULL = atypeFactory.MONOTONIC_NONNULL; + POLYNULL = atypeFactory.POLYNULL; + stringType = elements.getTypeElement(String.class.getCanonicalName()).asType(); + + ProcessingEnvironment env = checker.getProcessingEnvironment(); + this.collectionSize = TreeUtils.getMethod("java.util.Collection", "size", 0, env); + this.collectionToArray = TreeUtils.getMethod("java.util.Collection", "toArray", env, "T[]"); + systemClearProperty = TreeUtils.getMethod("java.lang.System", "clearProperty", 1, env); + systemSetProperties = TreeUtils.getMethod("java.lang.System", "setProperties", 1, env); + + this.permitClearProperty = + checker.getLintOption( + NullnessChecker.LINT_PERMITCLEARPROPERTY, + NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); + assumeAssertionsAreEnabled = checker.hasOption("assumeAssertionsAreEnabled"); + assumeAssertionsAreDisabled = checker.hasOption("assumeAssertionsAreDisabled"); + redundantNullComparison = + checker.getLintOption( + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, + NullnessChecker.LINT_DEFAULT_REDUNDANTNULLCOMPARISON); + } + + @Override + public NullnessNoInitAnnotatedTypeFactory createTypeFactory() { + return new NullnessNoInitAnnotatedTypeFactory(checker); + } + + @Override + public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { + // The Nullness Checker issues a more comprehensible "nullness.on.primitive" error rather + // than the "type.invalid.annotations.on.use" error this method would issue. + return true; + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Constructor results are always @NonNull. Other annotations are forbidden by + // #visitMethod. + // Nothing to check. + } + + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + // Constructor results are always @NonNull, so the result type of a this/super call is + // always equal to the result type of the current constructor. + // Nothing to check. + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // Allow a MonotonicNonNull field to be initialized to null at its declaration, in a + // constructor, or in an initializer block. (The latter two are, strictly speaking, unsound + // because the constructor or initializer block might have previously set the field to a + // non-null value. Maybe add an option to disable that behavior.) + Element elem = initializedElement(varTree); + if (elem != null + && atypeFactory.fromElement(elem).hasEffectiveAnnotation(MONOTONIC_NONNULL) + && !checker.getLintOption( + NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, + NullnessChecker.LINT_DEFAULT_NOINITFORMONOTONICNONNULL)) { + return true; + } + return super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); + } + + /** + * Returns the variable element, if the argument is an initialization; otherwise returns null. + * + * @param varTree an assignment LHS + * @return the initialized element, or null + */ + @SuppressWarnings("UnusedMethod") + private @Nullable Element initializedElement(Tree varTree) { + switch (varTree.getKind()) { + case VARIABLE: + // It's a variable declaration. + return TreeUtils.elementFromDeclaration((VariableTree) varTree); + + case MEMBER_SELECT: + MemberSelectTree mst = (MemberSelectTree) varTree; + ExpressionTree receiver = mst.getExpression(); + // This recognizes "this.fieldname = ..." but not "MyClass.fieldname = ..." or + // "MyClass.this.fieldname = ...". The latter forms are probably rare in a + // constructor. + // Note that this method should return non-null only for fields of this class, not + // fields of any other class, including outer classes. + if (receiver.getKind() != Tree.Kind.IDENTIFIER + || !((IdentifierTree) receiver).getName().contentEquals("this")) { + return null; + } + // fallthrough + case IDENTIFIER: + TreePath path = getCurrentPath(); + if (TreePathUtil.inConstructor(path)) { + return TreeUtils.elementFromUse((ExpressionTree) varTree); } else { - // no option given -> visit - doVisitAssert = true; - } - - if (doVisitAssert) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitAssert(tree, p); + return null; } + default: return null; } - - @Override - public Void visitIf(IfTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitIf(tree, p); - } - - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - // The "reference type" is the type after "instanceof". - Tree refTypeTree = tree.getType(); - if (refTypeTree == null) { - // TODO: the type is null for deconstructor patterns. - // Handle them properly. - return null; - } - - List annotations = null; - if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - annotations = TreeUtils.annotationsFromTree((AnnotatedTypeTree) refTypeTree); - } else { - Tree patternTree = TreeUtilsAfterJava11.InstanceOfUtils.getPattern(tree); - if (patternTree != null && TreeUtils.isBindingPatternTree(patternTree)) { - VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); - if (variableTree.getModifiers() != null) { - List annotationTree = - variableTree.getModifiers().getAnnotations(); - annotations = TreeUtils.annotationsFromTypeAnnotationTrees(annotationTree); - } - } - } - - if (annotations != null) { - if (AnnotationUtils.containsSame(annotations, NULLABLE)) { - checker.reportError(tree, "instanceof.nullable"); - } - if (AnnotationUtils.containsSame(annotations, NONNULL)) { - checker.reportWarning(tree, "instanceof.nonnull.redundant"); - } - } - - // Don't call super because it will issue an incorrect instanceof.unsafe warning. + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // Use the valueExp as the context because data flow will have a value for that tree. It + // might not have a value for the var tree. This is sound because if data flow has + // determined @PolyNull is @Nullable at the RHS, then it is also @Nullable for the LHS. + // TODO: A visitor should not change types, so this call to replacePolyQualifier is a hack. + // To work around this hack here, NullnessVisitor#visitConditionalExpression needs a further + // hack. Both should be undone, by properly resolving polymorphic qualifiers in the + // ATF/transfer. + atypeFactory.replacePolyQualifier(varType, valueExp); + return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); + } + + @Override + @FormatMethod + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + if (TypesUtils.isPrimitive(varType.getUnderlyingType()) + && !TypesUtils.isPrimitive(valueType.getUnderlyingType())) { + boolean succeed = checkForNullability(valueType, valueTree, UNBOXING_OF_NULLABLE); + if (!succeed) { + // Only issue the unboxing of nullable error. + return false; + } + } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } + + /** Case 1: Check for null dereferencing. */ + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + // if (atypeFactory.isUnreachable(tree)) { + // return super.visitMemberSelect(tree, p); + // } + Element e = TreeUtils.elementFromUse(tree); + if (e.getKind() == ElementKind.CLASS) { + if (atypeFactory.containsNullnessAnnotation(null, tree.getExpression())) { + checker.reportError(tree, "nullness.on.outer"); + } + } else if (!(TreeUtils.isSelfAccess(tree) + || tree.getExpression().getKind() == Tree.Kind.PARAMETERIZED_TYPE + // case 8. static member access + || ElementUtils.isStatic(e))) { + checkForNullability(tree.getExpression(), DEREFERENCE_OF_NULLABLE); + } + + return super.visitMemberSelect(tree, p); + } + + /** Case 2: Check for implicit {@code .iterator} call. */ + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + checkForNullability(tree.getExpression(), ITERATING_NULLABLE); + return super.visitEnhancedForLoop(tree, p); + } + + /** Case 3: Check for array dereferencing. */ + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + checkForNullability(tree.getExpression(), ACCESSING_NULLABLE); + return super.visitArrayAccess(tree, p); + } + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + AnnotatedArrayType type = atypeFactory.getAnnotatedType(tree); + AnnotatedTypeMirror componentType = type.getComponentType(); + if (componentType.hasEffectiveAnnotation(NONNULL) + && !isNewArrayAllZeroDims(tree) + && !isNewArrayInToArray(tree) + && !TypesUtils.isPrimitive(componentType.getUnderlyingType()) + && (checker.getLintOption("soundArrayCreationNullness", false) + // temporary, for backward compatibility + || checker.getLintOption("forbidnonnullarraycomponents", false))) { + checker.reportError( + tree, "new.array.type.invalid", componentType.getAnnotations(), type.toString()); + } + + if (type.hasEffectiveAnnotation(NULLABLE) + || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) + || type.hasEffectiveAnnotation(POLYNULL)) { + checker.reportError(tree, "nullness.on.new.array"); + } + + return super.visitNewArray(tree, p); + } + + /** + * Determine whether all dimensions given in a new array expression have zero as length. For + * example "new Object[0][0];". Also true for empty dimensions, as in "new Object[] {...}". + * + * @param tree the constructor invocation to check + * @return true if every array dimension has a size of zero + */ + private static boolean isNewArrayAllZeroDims(NewArrayTree tree) { + boolean isAllZeros = true; + for (ExpressionTree dim : tree.getDimensions()) { + if (dim instanceof LiteralTree) { + Object val = ((LiteralTree) dim).getValue(); + if (!(val instanceof Number) || !Integer.valueOf(0).equals(val)) { + isAllZeros = false; + break; + } + } else { + isAllZeros = false; + break; + } + } + return isAllZeros; + } + + /** + * Return true if the given tree is "new X[]", in the context "toArray(new X[])". + * + * @param tree a tree to test + * @return true if the tree is a new array within a call to toArray() + */ + private boolean isNewArrayInToArray(NewArrayTree tree) { + if (tree.getDimensions().size() != 1) { + return false; + } + + ExpressionTree dim = tree.getDimensions().get(0); + ProcessingEnvironment env = checker.getProcessingEnvironment(); + + if (!TreeUtils.isMethodInvocation(dim, collectionSize, env)) { + return false; + } + + ExpressionTree rcvsize = ((MethodInvocationTree) dim).getMethodSelect(); + if (!(rcvsize instanceof MemberSelectTree)) { + return false; + } + rcvsize = ((MemberSelectTree) rcvsize).getExpression(); + if (!(rcvsize instanceof IdentifierTree)) { + return false; + } + + Tree encl = getCurrentPath().getParentPath().getLeaf(); + + if (!TreeUtils.isMethodInvocation(encl, collectionToArray, env)) { + return false; + } + + ExpressionTree rcvtoarray = ((MethodInvocationTree) encl).getMethodSelect(); + if (!(rcvtoarray instanceof MemberSelectTree)) { + return false; + } + rcvtoarray = ((MemberSelectTree) rcvtoarray).getExpression(); + if (!(rcvtoarray instanceof IdentifierTree)) { + return false; + } + + return ((IdentifierTree) rcvsize).getName() == ((IdentifierTree) rcvtoarray).getName(); + } + + /** Case 4: Check for thrown exception nullness. */ + @Override + protected void checkThrownExpression(ThrowTree tree) { + checkForNullability(tree.getExpression(), THROWING_NULLABLE); + } + + /** Case 5: Check for synchronizing locks. */ + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + checkForNullability(tree.getExpression(), LOCKING_NULLABLE); + return super.visitSynchronized(tree, p); + } + + @Override + public Void visitAssert(AssertTree tree, Void p) { + // See also + // org.checkerframework.dataflow.cfg.builder.CFGBuilder.CFGTranslationPhaseOne.visitAssert + + // In cases where neither assumeAssertionsAreEnabled nor assumeAssertionsAreDisabled are + // turned on and @AssumeAssertions is not used, checkForNullability is still called since + // the CFGBuilder will have generated one branch for which asserts are assumed to be + // enabled. + + boolean doVisitAssert; + if (assumeAssertionsAreEnabled + || CFCFGBuilder.assumeAssertionsActivatedForAssertTree(checker, tree)) { + doVisitAssert = true; + } else if (assumeAssertionsAreDisabled) { + doVisitAssert = false; + } else { + // no option given -> visit + doVisitAssert = true; + } + + if (doVisitAssert) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitAssert(tree, p); + } + + return null; + } + + @Override + public Void visitIf(IfTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitIf(tree, p); + } + + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + // The "reference type" is the type after "instanceof". + Tree refTypeTree = tree.getType(); + if (refTypeTree == null) { + // TODO: the type is null for deconstructor patterns. + // Handle them properly. + return null; + } + + List annotations = null; + if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + annotations = TreeUtils.annotationsFromTree((AnnotatedTypeTree) refTypeTree); + } else { + Tree patternTree = TreeUtilsAfterJava11.InstanceOfUtils.getPattern(tree); + if (patternTree != null && TreeUtils.isBindingPatternTree(patternTree)) { + VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); + if (variableTree.getModifiers() != null) { + List annotationTree = + variableTree.getModifiers().getAnnotations(); + annotations = TreeUtils.annotationsFromTypeAnnotationTrees(annotationTree); + } + } + } + + if (annotations != null) { + if (AnnotationUtils.containsSame(annotations, NULLABLE)) { + checker.reportError(tree, "instanceof.nullable"); + } + if (AnnotationUtils.containsSame(annotations, NONNULL)) { + checker.reportWarning(tree, "instanceof.nonnull.redundant"); + } + } + + // Don't call super because it will issue an incorrect instanceof.unsafe warning. + return null; + } + + /** + * Reports an error if a comparison of a @NonNull expression with the null literal is performed. + * Does nothing unless {@code -Alint=redundantNullComparison} is passed on the command line. + * + * @param tree a tree that might be a comparison of a @NonNull expression with the null literal + */ + protected void checkForRedundantTests(BinaryTree tree) { + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); + + // respect command-line option + if (!redundantNullComparison) { + return; + } + + // equality tests + if ((tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { + AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); + AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); + if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && right.hasEffectiveAnnotation(NONNULL)) { + checker.reportWarning(tree, "nulltest.redundant", rightOp.toString()); + } else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL + && left.hasEffectiveAnnotation(NONNULL)) { + checker.reportWarning(tree, "nulltest.redundant", leftOp.toString()); + } + } + } + + /** Case 6: Check for redundant nullness tests Case 7: unboxing case: primitive operations. */ + @Override + public Void visitBinary(BinaryTree tree, Void p) { + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); + + if (isUnboxingOperation(tree)) { + checkForNullability(leftOp, UNBOXING_OF_NULLABLE); + checkForNullability(rightOp, UNBOXING_OF_NULLABLE); + } + + checkForRedundantTests(tree); + + return super.visitBinary(tree, p); + } + + /** Case 7: unboxing case: primitive operation. */ + @Override + public Void visitUnary(UnaryTree tree, Void p) { + checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); + return super.visitUnary(tree, p); + } + + /** Case 7: unboxing case: primitive operation. */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + // ignore String concatenation + if (!isString(tree)) { + checkForNullability(tree.getVariable(), UNBOXING_OF_NULLABLE); + checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); + } + return super.visitCompoundAssignment(tree, p); + } + + /** Case 7: unboxing case: casting to a primitive. */ + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + if (isPrimitive(tree) && !isPrimitive(tree.getExpression())) { + if (!checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE)) { + // If unboxing of nullable is issued, don't issue any other errors. return null; - } - - /** - * Reports an error if a comparison of a @NonNull expression with the null literal is performed. - * Does nothing unless {@code -Alint=redundantNullComparison} is passed on the command line. - * - * @param tree a tree that might be a comparison of a @NonNull expression with the null literal - */ - protected void checkForRedundantTests(BinaryTree tree) { - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); - - // respect command-line option - if (!redundantNullComparison) { - return; - } - - // equality tests - if ((tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { - AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); - AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); - if (leftOp.getKind() == Tree.Kind.NULL_LITERAL - && right.hasEffectiveAnnotation(NONNULL)) { - checker.reportWarning(tree, "nulltest.redundant", rightOp.toString()); - } else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL - && left.hasEffectiveAnnotation(NONNULL)) { - checker.reportWarning(tree, "nulltest.redundant", leftOp.toString()); - } - } - } - - /** Case 6: Check for redundant nullness tests Case 7: unboxing case: primitive operations. */ - @Override - public Void visitBinary(BinaryTree tree, Void p) { - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); - - if (isUnboxingOperation(tree)) { - checkForNullability(leftOp, UNBOXING_OF_NULLABLE); - checkForNullability(rightOp, UNBOXING_OF_NULLABLE); - } - - checkForRedundantTests(tree); - - return super.visitBinary(tree, p); - } - - /** Case 7: unboxing case: primitive operation. */ - @Override - public Void visitUnary(UnaryTree tree, Void p) { - checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); - return super.visitUnary(tree, p); - } - - /** Case 7: unboxing case: primitive operation. */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - // ignore String concatenation - if (!isString(tree)) { - checkForNullability(tree.getVariable(), UNBOXING_OF_NULLABLE); - checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); - } - return super.visitCompoundAssignment(tree, p); - } - - /** Case 7: unboxing case: casting to a primitive. */ - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - if (isPrimitive(tree) && !isPrimitive(tree.getExpression())) { - if (!checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE)) { - // If unboxing of nullable is issued, don't issue any other errors. - return null; - } - } - return super.visitTypeCast(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - VariableTree receiver = tree.getReceiverParameter(); - - if (TreeUtils.isConstructor(tree)) { - // Constructor results are always @NonNull. Any annotations are forbidden. - List annoTrees = tree.getModifiers().getAnnotations(); - if (atypeFactory.containsNullnessAnnotation(annoTrees)) { - checker.reportError(tree, "nullness.on.constructor"); - } - } - - if (receiver != null) { - List annoTrees = receiver.getModifiers().getAnnotations(); - Tree type = receiver.getType(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { - checker.reportError(tree, "nullness.on.receiver"); + } + } + return super.visitTypeCast(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + VariableTree receiver = tree.getReceiverParameter(); + + if (TreeUtils.isConstructor(tree)) { + // Constructor results are always @NonNull. Any annotations are forbidden. + List annoTrees = tree.getModifiers().getAnnotations(); + if (atypeFactory.containsNullnessAnnotation(annoTrees)) { + checker.reportError(tree, "nullness.on.constructor"); + } + } + + if (receiver != null) { + List annoTrees = receiver.getModifiers().getAnnotations(); + Tree type = receiver.getType(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { + checker.reportError(tree, "nullness.on.receiver"); + } + } + + return super.visitMethod(tree, p); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (!permitClearProperty) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + if (TreeUtils.isMethodInvocation(tree, systemClearProperty, env)) { + String literal = literalFirstArgument(tree); + if (literal == null + || SystemGetPropertyHandler.predefinedSystemProperties.contains(literal)) { + checker.reportError(tree, "clear.system.property"); + } + } + if (TreeUtils.isMethodInvocation(tree, systemSetProperties, env)) { + checker.reportError(tree, "clear.system.property"); + } + } + return super.visitMethodInvocation(tree, p); + } + + /** + * If the first argument of a method call is a literal, return it; otherwise return null. + * + * @param tree a method invocation whose first formal parameter is of String type + * @return the first argument if it is a literal, otherwise null + */ + /*package-private*/ static @Nullable String literalFirstArgument(MethodInvocationTree tree) { + List args = tree.getArguments(); + assert args.size() > 0; + ExpressionTree arg = args.get(0); + if (arg.getKind() == Tree.Kind.STRING_LITERAL) { + String literal = (String) ((LiteralTree) arg).getValue(); + return literal; + } + return null; + } + + @Override + public void processClassTree(ClassTree classTree) { + Tree extendsClause = classTree.getExtendsClause(); + if (extendsClause != null) { + reportErrorIfSupertypeContainsNullnessAnnotation(extendsClause); + } + for (Tree implementsClause : classTree.getImplementsClause()) { + reportErrorIfSupertypeContainsNullnessAnnotation(implementsClause); + } + + if (classTree.getKind() == Tree.Kind.ENUM) { + for (Tree member : classTree.getMembers()) { + if (member.getKind() == Tree.Kind.VARIABLE + && TreeUtils.elementFromDeclaration((VariableTree) member).getKind() + == ElementKind.ENUM_CONSTANT) { + VariableTree varDecl = (VariableTree) member; + List annoTrees = varDecl.getModifiers().getAnnotations(); + Tree type = varDecl.getType(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { + checker.reportError(member, "nullness.on.enum"); + } + } + } + } + + super.processClassTree(classTree); + } + + /** + * Report "nullness.on.supertype" error if a supertype has a nullness annotation. + * + * @param typeTree a supertype tree, from an {@code extends} or {@code implements} clause + */ + private void reportErrorIfSupertypeContainsNullnessAnnotation(Tree typeTree) { + if (typeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + List annoTrees = ((AnnotatedTypeTree) typeTree).getAnnotations(); + if (atypeFactory.containsNullnessAnnotation(annoTrees)) { + checker.reportError(typeTree, "nullness.on.supertype"); + } + } + } + + // ///////////// Utility methods ////////////////////////////// + + /** + * Issues the error message if the type of the tree is not of a {@link NonNull} type. + * + * @param tree the tree where the error is to reported + * @param errMsg the error message (must be {@link CompilerMessageKey}) + * @return whether or not the check succeeded + */ + private boolean checkForNullability(ExpressionTree tree, @CompilerMessageKey String errMsg) { + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + return checkForNullability(type, tree, errMsg); + } + + /** + * Issues the error message if an expression with this type may be null. + * + * @param type annotated type + * @param tree the tree where the error is to reported + * @param errMsg the error message (must be {@link CompilerMessageKey}) + * @return whether or not the check succeeded + */ + private boolean checkForNullability( + AnnotatedTypeMirror type, Tree tree, @CompilerMessageKey String errMsg) { + if (!type.hasEffectiveAnnotation(NONNULL)) { + checker.reportError(tree, errMsg, tree); + return false; + } + return true; + } + + @Override + protected void checkMethodInvocability( + AnnotatedExecutableType method, MethodInvocationTree tree) { + if (method.getReceiverType() == null) { + // Static methods don't have a receiver to check. + return; + } + + if (!TreeUtils.isSelfAccess(tree) + && + // Static methods don't have a receiver + method.getReceiverType() != null) { + // TODO: should all or some constructors be excluded? + // method.getElement().getKind() != ElementKind.CONSTRUCTOR) { + AnnotationMirrorSet receiverAnnos = atypeFactory.getReceiverType(tree).getAnnotations(); + AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); + AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); + AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); + treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); + // If receiver is Nullable, then we don't want to issue a warning about method + // invocability (we'd rather have only the "dereference.of.nullable" message). + if (treeReceiver.hasAnnotation(NULLABLE) + || receiverAnnos.contains(MONOTONIC_NONNULL) + || treeReceiver.hasAnnotation(POLYNULL)) { + return; + } + } + super.checkMethodInvocability(method, tree); + } + + /** + * Returns true if the binary operation could cause an unboxing operation. + * + * @param tree a binary operation + * @return true if the binary operation could cause an unboxing operation + */ + private boolean isUnboxingOperation(BinaryTree tree) { + if (tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { + // it is valid to check equality between two reference types, even + // if one (or both) of them is null + return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand()); + } else { + // All BinaryTree's are of type String, a primitive type or the reference type + // equivalent of a primitive type. Furthermore, Strings don't have a primitive type, and + // therefore only BinaryTrees that aren't String can cause unboxing. + return !isString(tree); + } + } + + /** + * Returns true if the type of the tree is a super of String. + * + * @param tree a tree + * @return true if the type of the tree is a super of String + */ + private boolean isString(ExpressionTree tree) { + TypeMirror type = TreeUtils.typeOf(tree); + return types.isAssignable(stringType, type); + } + + /** + * Returns true if the type of the tree is a primitive. + * + * @param tree a tree + * @return true if the type of the tree is a primitive + */ + private static boolean isPrimitive(ExpressionTree tree) { + return TreeUtils.typeOf(tree).getKind().isPrimitive(); + } + + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + if (!TreeUtils.hasNullCaseLabel(tree)) { + checkForNullability(tree.getExpression(), SWITCHING_NULLABLE); + } + if (redundantNullComparison && TreeUtils.isEnhancedSwitchStatement(tree)) { + ExpressionTree expression = tree.getExpression(); + List cases = tree.getCases(); + checkSwitchNullRedundant(expression, cases); + } + return super.visitSwitch(tree, p); + } + + @Override + public void visitSwitchExpression17(Tree switchExprTree) { + if (!TreeUtils.hasNullCaseLabel(switchExprTree)) { + checkForNullability(SwitchExpressionUtils.getExpression(switchExprTree), SWITCHING_NULLABLE); + } + if (redundantNullComparison) { + ExpressionTree expression = SwitchExpressionUtils.getExpression(switchExprTree); + List cases = SwitchExpressionUtils.getCases(switchExprTree); + checkSwitchNullRedundant(expression, cases); + } + super.visitSwitchExpression17(switchExprTree); + } + + /** + * Reports a warning if the expression of the switch statement or expression is {@code @NonNull} + * and one of the case labels in the case trees is the null literal. + * + * @param expression the expression of the switch statement or expression + * @param cases the cases of the switch statement or expression + */ + private void checkSwitchNullRedundant(ExpressionTree expression, List cases) { + AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(expression); + if (!switchType.hasEffectiveAnnotation(NONNULL)) { + return; + } + outer: + for (CaseTree caseTree : cases) { + List caseLabels = TreeUtilsAfterJava11.CaseUtils.getLabels(caseTree); + for (Tree caseLabel : caseLabels) { + if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL) { + checker.reportWarning(caseLabel, "nulltest.redundant", expression.toString()); + break outer; + } + } + } + } + + @Override + public Void visitForLoop(ForLoopTree tree, Void p) { + if (tree.getCondition() != null) { + // Condition is null e.g. in "for (;;) {...}" + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + } + return super.visitForLoop(tree, p); + } + + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + ExpressionTree enclosingExpr = tree.getEnclosingExpression(); + if (enclosingExpr != null) { + checkForNullability(enclosingExpr, DEREFERENCE_OF_NULLABLE); + } + + AnnotatedTypeMirror.AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); + if (type.hasEffectiveAnnotation(NULLABLE) + || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) + || type.hasEffectiveAnnotation(POLYNULL)) { + checker.reportError(tree, "nullness.on.new.object"); + } + return super.visitNewClass(tree, p); + } + + @Override + public Void visitWhileLoop(WhileLoopTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitWhileLoop(tree, p); + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitDoWhileLoop(tree, p); + } + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + // Note: Because of the hack in NullnessVisitor#commonAssignmentCheck, this code needs to + // use a copy of the types for the two invocations of #commonAssignmentCheck. These hacks + // should be undone, by properly resolving polymorphic qualifiers in the ATF/transfer. As + // this code is mostly duplicated from the super method, this code needs to be kept in sync. + AnnotatedTypeMirror condThen = atypeFactory.getAnnotatedType(tree); + AnnotatedTypeMirror condElse = condThen.deepCopy(); + this.commonAssignmentCheck(condThen, tree.getTrueExpression(), "conditional.type.incompatible"); + this.commonAssignmentCheck( + condElse, tree.getFalseExpression(), "conditional.type.incompatible"); + // Avoid calling super, as the super method does not handle conditional branches correctly. + // Instead, manually implement the logic from TreeScanner#visitConditionalExpression to + // traverse the subtree. + Void r = scan(tree.getCondition(), p); + r = reduce(scan(tree.getTrueExpression(), p), r); + r = reduce(scan(tree.getFalseExpression(), p), r); + return r; + } + + @Override + protected void checkExceptionParameter(CatchTree tree) { + VariableTree param = tree.getParameter(); + List annoTrees = param.getModifiers().getAnnotations(); + Tree paramType = param.getType(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, paramType)) { + // This is a warning rather than an error because writing `@Nullable` could make sense + // if the catch block re-assigns the variable to null. (That would be bad style.) + checker.reportWarning(param, "nullness.on.exception.parameter"); + } + + // Don't call super. + // BasetypeVisitor forces annotations on exception parameters to be top, but because + // exceptions can never be null, the Nullness Checker does not require this check. + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + // All annotation arguments are non-null and initialized, so no need to check them. + return null; + } + + @Override + public void visitAnnotatedType( + @Nullable List annoTrees, Tree typeTree) { + // Look for a MEMBER_SELECT or PRIMITIVE within the type. + Tree t = typeTree; + while (t != null) { + switch (t.getKind()) { + case MEMBER_SELECT: + Tree expr = ((MemberSelectTree) t).getExpression(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, expr)) { + checker.reportError(expr, "nullness.on.outer"); + } + t = null; + break; + case PRIMITIVE_TYPE: + if (atypeFactory.containsNullnessAnnotation(annoTrees, t)) { + checker.reportError(t, "nullness.on.primitive"); + } + t = null; + break; + case ANNOTATED_TYPE: + AnnotatedTypeTree at = ((AnnotatedTypeTree) t); + Tree underlying = at.getUnderlyingType(); + if (underlying.getKind() == Tree.Kind.PRIMITIVE_TYPE) { + if (atypeFactory.containsNullnessAnnotation(null, at)) { + checker.reportError(t, "nullness.on.primitive"); } - } - - return super.visitMethod(tree, p); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (!permitClearProperty) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - if (TreeUtils.isMethodInvocation(tree, systemClearProperty, env)) { - String literal = literalFirstArgument(tree); - if (literal == null - || SystemGetPropertyHandler.predefinedSystemProperties.contains(literal)) { - checker.reportError(tree, "clear.system.property"); - } - } - if (TreeUtils.isMethodInvocation(tree, systemSetProperties, env)) { - checker.reportError(tree, "clear.system.property"); - } - } - return super.visitMethodInvocation(tree, p); - } + t = null; + } else { + t = underlying; + } + break; + case ARRAY_TYPE: + t = ((ArrayTypeTree) t).getType(); + break; + case PARAMETERIZED_TYPE: + t = ((ParameterizedTypeTree) t).getType(); + break; + default: + t = null; + break; + } + } + + super.visitAnnotatedType(annoTrees, typeTree); + } + + @Override + protected TypeValidator createTypeValidator() { + return new NullnessValidator(checker, this, atypeFactory); + } + + /** + * Check that primitive types are annotated with {@code @NonNull} even if they are the type of a + * local variable. + */ + private static class NullnessValidator extends BaseTypeValidator { /** - * If the first argument of a method call is a literal, return it; otherwise return null. + * Create NullnessValidator. * - * @param tree a method invocation whose first formal parameter is of String type - * @return the first argument if it is a literal, otherwise null + * @param checker checker + * @param visitor visitor + * @param atypeFactory factory */ - /*package-private*/ static @Nullable String literalFirstArgument(MethodInvocationTree tree) { - List args = tree.getArguments(); - assert args.size() > 0; - ExpressionTree arg = args.get(0); - if (arg.getKind() == Tree.Kind.STRING_LITERAL) { - String literal = (String) ((LiteralTree) arg).getValue(); - return literal; - } - return null; + public NullnessValidator( + BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); } @Override - public void processClassTree(ClassTree classTree) { - Tree extendsClause = classTree.getExtendsClause(); - if (extendsClause != null) { - reportErrorIfSupertypeContainsNullnessAnnotation(extendsClause); - } - for (Tree implementsClause : classTree.getImplementsClause()) { - reportErrorIfSupertypeContainsNullnessAnnotation(implementsClause); - } - - if (classTree.getKind() == Tree.Kind.ENUM) { - for (Tree member : classTree.getMembers()) { - if (member.getKind() == Tree.Kind.VARIABLE - && TreeUtils.elementFromDeclaration((VariableTree) member).getKind() - == ElementKind.ENUM_CONSTANT) { - VariableTree varDecl = (VariableTree) member; - List annoTrees = - varDecl.getModifiers().getAnnotations(); - Tree type = varDecl.getType(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { - checker.reportError(member, "nullness.on.enum"); - } - } - } - } - - super.processClassTree(classTree); - } - - /** - * Report "nullness.on.supertype" error if a supertype has a nullness annotation. - * - * @param typeTree a supertype tree, from an {@code extends} or {@code implements} clause - */ - private void reportErrorIfSupertypeContainsNullnessAnnotation(Tree typeTree) { - if (typeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - List annoTrees = - ((AnnotatedTypeTree) typeTree).getAnnotations(); - if (atypeFactory.containsNullnessAnnotation(annoTrees)) { - checker.reportError(typeTree, "nullness.on.supertype"); - } - } - } - - // ///////////// Utility methods ////////////////////////////// - - /** - * Issues the error message if the type of the tree is not of a {@link NonNull} type. - * - * @param tree the tree where the error is to reported - * @param errMsg the error message (must be {@link CompilerMessageKey}) - * @return whether or not the check succeeded - */ - private boolean checkForNullability(ExpressionTree tree, @CompilerMessageKey String errMsg) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - return checkForNullability(type, tree, errMsg); - } - - /** - * Issues the error message if an expression with this type may be null. - * - * @param type annotated type - * @param tree the tree where the error is to reported - * @param errMsg the error message (must be {@link CompilerMessageKey}) - * @return whether or not the check succeeded - */ - private boolean checkForNullability( - AnnotatedTypeMirror type, Tree tree, @CompilerMessageKey String errMsg) { - if (!type.hasEffectiveAnnotation(NONNULL)) { - checker.reportError(tree, errMsg, tree); - return false; - } + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( + AnnotatedTypeMirror type, Tree tree) { + if (type.getKind().isPrimitive()) { return true; + } + return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); } - - @Override - protected void checkMethodInvocability( - AnnotatedExecutableType method, MethodInvocationTree tree) { - if (method.getReceiverType() == null) { - // Static methods don't have a receiver to check. - return; - } - - if (!TreeUtils.isSelfAccess(tree) - && - // Static methods don't have a receiver - method.getReceiverType() != null) { - // TODO: should all or some constructors be excluded? - // method.getElement().getKind() != ElementKind.CONSTRUCTOR) { - AnnotationMirrorSet receiverAnnos = atypeFactory.getReceiverType(tree).getAnnotations(); - AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); - AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); - AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); - treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); - // If receiver is Nullable, then we don't want to issue a warning about method - // invocability (we'd rather have only the "dereference.of.nullable" message). - if (treeReceiver.hasAnnotation(NULLABLE) - || receiverAnnos.contains(MONOTONIC_NONNULL) - || treeReceiver.hasAnnotation(POLYNULL)) { - return; - } - } - super.checkMethodInvocability(method, tree); - } - - /** - * Returns true if the binary operation could cause an unboxing operation. - * - * @param tree a binary operation - * @return true if the binary operation could cause an unboxing operation - */ - private boolean isUnboxingOperation(BinaryTree tree) { - if (tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { - // it is valid to check equality between two reference types, even - // if one (or both) of them is null - return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand()); - } else { - // All BinaryTree's are of type String, a primitive type or the reference type - // equivalent of a primitive type. Furthermore, Strings don't have a primitive type, and - // therefore only BinaryTrees that aren't String can cause unboxing. - return !isString(tree); - } - } - - /** - * Returns true if the type of the tree is a super of String. - * - * @param tree a tree - * @return true if the type of the tree is a super of String - */ - private boolean isString(ExpressionTree tree) { - TypeMirror type = TreeUtils.typeOf(tree); - return types.isAssignable(stringType, type); - } - - /** - * Returns true if the type of the tree is a primitive. - * - * @param tree a tree - * @return true if the type of the tree is a primitive - */ - private static boolean isPrimitive(ExpressionTree tree) { - return TreeUtils.typeOf(tree).getKind().isPrimitive(); - } - - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - if (!TreeUtils.hasNullCaseLabel(tree)) { - checkForNullability(tree.getExpression(), SWITCHING_NULLABLE); - } - if (redundantNullComparison && TreeUtils.isEnhancedSwitchStatement(tree)) { - ExpressionTree expression = tree.getExpression(); - List cases = tree.getCases(); - checkSwitchNullRedundant(expression, cases); - } - return super.visitSwitch(tree, p); - } - - @Override - public void visitSwitchExpression17(Tree switchExprTree) { - if (!TreeUtils.hasNullCaseLabel(switchExprTree)) { - checkForNullability( - SwitchExpressionUtils.getExpression(switchExprTree), SWITCHING_NULLABLE); - } - if (redundantNullComparison) { - ExpressionTree expression = SwitchExpressionUtils.getExpression(switchExprTree); - List cases = SwitchExpressionUtils.getCases(switchExprTree); - checkSwitchNullRedundant(expression, cases); - } - super.visitSwitchExpression17(switchExprTree); - } - - /** - * Reports a warning if the expression of the switch statement or expression is {@code @NonNull} - * and one of the case labels in the case trees is the null literal. - * - * @param expression the expression of the switch statement or expression - * @param cases the cases of the switch statement or expression - */ - private void checkSwitchNullRedundant( - ExpressionTree expression, List cases) { - AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(expression); - if (!switchType.hasEffectiveAnnotation(NONNULL)) { - return; - } - outer: - for (CaseTree caseTree : cases) { - List caseLabels = TreeUtilsAfterJava11.CaseUtils.getLabels(caseTree); - for (Tree caseLabel : caseLabels) { - if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL) { - checker.reportWarning(caseLabel, "nulltest.redundant", expression.toString()); - break outer; - } - } - } - } - - @Override - public Void visitForLoop(ForLoopTree tree, Void p) { - if (tree.getCondition() != null) { - // Condition is null e.g. in "for (;;) {...}" - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - } - return super.visitForLoop(tree, p); - } - - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - ExpressionTree enclosingExpr = tree.getEnclosingExpression(); - if (enclosingExpr != null) { - checkForNullability(enclosingExpr, DEREFERENCE_OF_NULLABLE); - } - - AnnotatedTypeMirror.AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); - if (type.hasEffectiveAnnotation(NULLABLE) - || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) - || type.hasEffectiveAnnotation(POLYNULL)) { - checker.reportError(tree, "nullness.on.new.object"); - } - return super.visitNewClass(tree, p); - } - - @Override - public Void visitWhileLoop(WhileLoopTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitWhileLoop(tree, p); - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitDoWhileLoop(tree, p); - } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - // Note: Because of the hack in NullnessVisitor#commonAssignmentCheck, this code needs to - // use a copy of the types for the two invocations of #commonAssignmentCheck. These hacks - // should be undone, by properly resolving polymorphic qualifiers in the ATF/transfer. As - // this code is mostly duplicated from the super method, this code needs to be kept in sync. - AnnotatedTypeMirror condThen = atypeFactory.getAnnotatedType(tree); - AnnotatedTypeMirror condElse = condThen.deepCopy(); - this.commonAssignmentCheck( - condThen, tree.getTrueExpression(), "conditional.type.incompatible"); - this.commonAssignmentCheck( - condElse, tree.getFalseExpression(), "conditional.type.incompatible"); - // Avoid calling super, as the super method does not handle conditional branches correctly. - // Instead, manually implement the logic from TreeScanner#visitConditionalExpression to - // traverse the subtree. - Void r = scan(tree.getCondition(), p); - r = reduce(scan(tree.getTrueExpression(), p), r); - r = reduce(scan(tree.getFalseExpression(), p), r); - return r; - } - - @Override - protected void checkExceptionParameter(CatchTree tree) { - VariableTree param = tree.getParameter(); - List annoTrees = param.getModifiers().getAnnotations(); - Tree paramType = param.getType(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, paramType)) { - // This is a warning rather than an error because writing `@Nullable` could make sense - // if the catch block re-assigns the variable to null. (That would be bad style.) - checker.reportWarning(param, "nullness.on.exception.parameter"); - } - - // Don't call super. - // BasetypeVisitor forces annotations on exception parameters to be top, but because - // exceptions can never be null, the Nullness Checker does not require this check. - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - // All annotation arguments are non-null and initialized, so no need to check them. - return null; - } - - @Override - public void visitAnnotatedType( - @Nullable List annoTrees, Tree typeTree) { - // Look for a MEMBER_SELECT or PRIMITIVE within the type. - Tree t = typeTree; - while (t != null) { - switch (t.getKind()) { - case MEMBER_SELECT: - Tree expr = ((MemberSelectTree) t).getExpression(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, expr)) { - checker.reportError(expr, "nullness.on.outer"); - } - t = null; - break; - case PRIMITIVE_TYPE: - if (atypeFactory.containsNullnessAnnotation(annoTrees, t)) { - checker.reportError(t, "nullness.on.primitive"); - } - t = null; - break; - case ANNOTATED_TYPE: - AnnotatedTypeTree at = ((AnnotatedTypeTree) t); - Tree underlying = at.getUnderlyingType(); - if (underlying.getKind() == Tree.Kind.PRIMITIVE_TYPE) { - if (atypeFactory.containsNullnessAnnotation(null, at)) { - checker.reportError(t, "nullness.on.primitive"); - } - t = null; - } else { - t = underlying; - } - break; - case ARRAY_TYPE: - t = ((ArrayTypeTree) t).getType(); - break; - case PARAMETERIZED_TYPE: - t = ((ParameterizedTypeTree) t).getType(); - break; - default: - t = null; - break; - } - } - - super.visitAnnotatedType(annoTrees, typeTree); - } - - @Override - protected TypeValidator createTypeValidator() { - return new NullnessValidator(checker, this, atypeFactory); - } - - /** - * Check that primitive types are annotated with {@code @NonNull} even if they are the type of a - * local variable. - */ - private static class NullnessValidator extends BaseTypeValidator { - - /** - * Create NullnessValidator. - * - * @param checker checker - * @param visitor visitor - * @param atypeFactory factory - */ - public NullnessValidator( - BaseTypeChecker checker, - BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } - - @Override - protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( - AnnotatedTypeMirror type, Tree tree) { - if (type.getKind().isPrimitive()) { - return true; - } - return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); - } - } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java b/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java index 346e008e896..bfa4276dd8e 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java @@ -1,17 +1,14 @@ package org.checkerframework.checker.nullness; import com.sun.source.tree.MethodInvocationTree; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Arrays; import java.util.Collection; import java.util.HashSet; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.TreeUtils; /** * Utility class for handling {@link java.lang.System#getProperty(String)} and related invocations. @@ -22,108 +19,108 @@ */ public class SystemGetPropertyHandler { - /** - * If true, client code may clear system properties, and this class (SystemGetPropertyHandler) - * has no effect. - */ - private final boolean permitClearProperty; + /** + * If true, client code may clear system properties, and this class (SystemGetPropertyHandler) has + * no effect. + */ + private final boolean permitClearProperty; - /** The processing environment. */ - private final ProcessingEnvironment env; + /** The processing environment. */ + private final ProcessingEnvironment env; - /** The factory for constructing and looking up types. */ - private final NullnessNoInitAnnotatedTypeFactory factory; + /** The factory for constructing and looking up types. */ + private final NullnessNoInitAnnotatedTypeFactory factory; - /** The System.getProperty(String) method. */ - private final ExecutableElement systemGetProperty; + /** The System.getProperty(String) method. */ + private final ExecutableElement systemGetProperty; - /** The System.setProperty(String) method. */ - private final ExecutableElement systemSetProperty; + /** The System.setProperty(String) method. */ + private final ExecutableElement systemSetProperty; - /** - * System properties that are defined at startup on every JVM. - * - *

This list is from the Javadoc of System.getProperties, for Java 17. - */ - public static final Collection predefinedSystemProperties = - new HashSet<>( - Arrays.asList( - "java.version", - "java.version.date", - "java.vendor", - "java.vendor.url", - "java.vendor.version", - "java.home", - "java.vm.specification.version", - "java.vm.specification.vendor", - "java.vm.specification.name", - "java.vm.version", - "java.vm.vendor", - "java.vm.name", - "java.specification.version", - "java.specification.maintenance.version", - "java.specification.vendor", - "java.specification.name", - "java.class.version", - "java.class.path", - "java.library.path", - "java.io.tmpdir", - "java.compiler", - "os.name", - "os.arch", - "os.version", - "file.separator", - "path.separator", - "line.separator", - "user.name", - "user.home", - "user.dir", - "native.encoding", - "stdout.encoding", - "stderr.encoding", - "jdk.module.path", - "jdk.module.upgrade.path", - "jdk.module.main", - "jdk.module.main.class", - "file.encoding")); + /** + * System properties that are defined at startup on every JVM. + * + *

This list is from the Javadoc of System.getProperties, for Java 17. + */ + public static final Collection predefinedSystemProperties = + new HashSet<>( + Arrays.asList( + "java.version", + "java.version.date", + "java.vendor", + "java.vendor.url", + "java.vendor.version", + "java.home", + "java.vm.specification.version", + "java.vm.specification.vendor", + "java.vm.specification.name", + "java.vm.version", + "java.vm.vendor", + "java.vm.name", + "java.specification.version", + "java.specification.maintenance.version", + "java.specification.vendor", + "java.specification.name", + "java.class.version", + "java.class.path", + "java.library.path", + "java.io.tmpdir", + "java.compiler", + "os.name", + "os.arch", + "os.version", + "file.separator", + "path.separator", + "line.separator", + "user.name", + "user.home", + "user.dir", + "native.encoding", + "stdout.encoding", + "stderr.encoding", + "jdk.module.path", + "jdk.module.upgrade.path", + "jdk.module.main", + "jdk.module.main.class", + "file.encoding")); - /** - * Creates a SystemGetPropertyHandler. - * - * @param env the processing environment - * @param factory the factory for constructing and looking up types - * @param permitClearProperty if true, client code may clear system properties, and this object - * does nothing - */ - public SystemGetPropertyHandler( - ProcessingEnvironment env, - NullnessNoInitAnnotatedTypeFactory factory, - boolean permitClearProperty) { - this.env = env; - this.factory = factory; - this.permitClearProperty = permitClearProperty; + /** + * Creates a SystemGetPropertyHandler. + * + * @param env the processing environment + * @param factory the factory for constructing and looking up types + * @param permitClearProperty if true, client code may clear system properties, and this object + * does nothing + */ + public SystemGetPropertyHandler( + ProcessingEnvironment env, + NullnessNoInitAnnotatedTypeFactory factory, + boolean permitClearProperty) { + this.env = env; + this.factory = factory; + this.permitClearProperty = permitClearProperty; - systemGetProperty = TreeUtils.getMethod("java.lang.System", "getProperty", 1, env); - systemSetProperty = TreeUtils.getMethod("java.lang.System", "setProperty", 2, env); - } + systemGetProperty = TreeUtils.getMethod("java.lang.System", "getProperty", 1, env); + systemSetProperty = TreeUtils.getMethod("java.lang.System", "setProperty", 2, env); + } - /** - * Apply rules regarding System.getProperty and related methods. - * - * @param tree a method invocation - * @param method the method being invoked - */ - public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { - if (permitClearProperty) { - return; - } - if (TreeUtils.isMethodInvocation(tree, systemGetProperty, env) - || TreeUtils.isMethodInvocation(tree, systemSetProperty, env)) { - String literal = NullnessNoInitVisitor.literalFirstArgument(tree); - if (literal != null && predefinedSystemProperties.contains(literal)) { - AnnotatedTypeMirror type = method.getReturnType(); - type.replaceAnnotation(factory.NONNULL); - } - } + /** + * Apply rules regarding System.getProperty and related methods. + * + * @param tree a method invocation + * @param method the method being invoked + */ + public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { + if (permitClearProperty) { + return; + } + if (TreeUtils.isMethodInvocation(tree, systemGetProperty, env) + || TreeUtils.isMethodInvocation(tree, systemSetProperty, env)) { + String literal = NullnessNoInitVisitor.literalFirstArgument(tree); + if (literal != null && predefinedSystemProperties.contains(literal)) { + AnnotatedTypeMirror type = method.getReturnType(); + type.replaceAnnotation(factory.NONNULL); + } } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java index 52142d042dd..951d4f4551a 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java @@ -5,7 +5,11 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; - +import java.util.Collection; +import java.util.function.Function; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -17,122 +21,114 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collection; -import java.util.function.Function; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; - /** OptionalAnnotatedTypeFactory for the Optional Checker. */ public class OptionalAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The element for java.util.Optional.map(). */ - private final ExecutableElement optionalMap; + /** The element for java.util.Optional.map(). */ + private final ExecutableElement optionalMap; - /** The @{@link Present} annotation. */ - protected final AnnotationMirror PRESENT = AnnotationBuilder.fromClass(elements, Present.class); + /** The @{@link Present} annotation. */ + protected final AnnotationMirror PRESENT = AnnotationBuilder.fromClass(elements, Present.class); - /** - * Creates an OptionalAnnotatedTypeFactory. - * - * @param checker the Optional Checker associated with this type factory - */ - public OptionalAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - optionalMap = TreeUtils.getMethodOrNull("java.util.Optional", "map", 1, getProcessingEnv()); - postInit(); - } + /** + * Creates an OptionalAnnotatedTypeFactory. + * + * @param checker the Optional Checker associated with this type factory + */ + public OptionalAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + optionalMap = TreeUtils.getMethodOrNull("java.util.Optional", "map", 1, getProcessingEnv()); + postInit(); + } - @Override - public AnnotatedTypeMirror getAnnotatedType(Tree tree) { - AnnotatedTypeMirror result = super.getAnnotatedType(tree); - optionalMapNonNull(tree, result); - return result; - } + @Override + public AnnotatedTypeMirror getAnnotatedType(Tree tree) { + AnnotatedTypeMirror result = super.getAnnotatedType(tree); + optionalMapNonNull(tree, result); + return result; + } - /** - * If {@code tree} is a call to {@link java.util.Optional#map(Function)} whose argument is a - * method reference, then this method adds {@code @Present} to {@code type} if the following is - * true: - * - *

    - *
  • The type of the receiver to {@link java.util.Optional#map(Function)} is - * {@code @Present}, and - *
  • {@link #returnHasNullable(MemberReferenceTree)} returns false. - *
- * - * @param tree a tree - * @param type the type of the tree, which may be side-effected by this method - */ - private void optionalMapNonNull(Tree tree, AnnotatedTypeMirror type) { - if (!TreeUtils.isMethodInvocation(tree, optionalMap, processingEnv)) { - return; - } - MethodInvocationTree mapTree = (MethodInvocationTree) tree; - ExpressionTree argTree = mapTree.getArguments().get(0); - if (argTree.getKind() == Kind.MEMBER_REFERENCE) { - MemberReferenceTree memberReferenceTree = (MemberReferenceTree) argTree; - AnnotatedTypeMirror optType = getReceiverType(mapTree); - if (optType == null || !optType.hasEffectiveAnnotation(Present.class)) { - return; - } - if (!returnHasNullable(memberReferenceTree)) { - // The method still could have a @PolyNull on the return and might return null. - // If @PolyNull is the primary annotation on the parameter and not on any type - // arguments or array elements, then it is still safe to mark the optional type as - // present. - // TODO: Add the check for poly null on arguments. - type.replaceAnnotation(PRESENT); - } - } + /** + * If {@code tree} is a call to {@link java.util.Optional#map(Function)} whose argument is a + * method reference, then this method adds {@code @Present} to {@code type} if the following is + * true: + * + *
    + *
  • The type of the receiver to {@link java.util.Optional#map(Function)} is {@code @Present}, + * and + *
  • {@link #returnHasNullable(MemberReferenceTree)} returns false. + *
+ * + * @param tree a tree + * @param type the type of the tree, which may be side-effected by this method + */ + private void optionalMapNonNull(Tree tree, AnnotatedTypeMirror type) { + if (!TreeUtils.isMethodInvocation(tree, optionalMap, processingEnv)) { + return; } + MethodInvocationTree mapTree = (MethodInvocationTree) tree; + ExpressionTree argTree = mapTree.getArguments().get(0); + if (argTree.getKind() == Kind.MEMBER_REFERENCE) { + MemberReferenceTree memberReferenceTree = (MemberReferenceTree) argTree; + AnnotatedTypeMirror optType = getReceiverType(mapTree); + if (optType == null || !optType.hasEffectiveAnnotation(Present.class)) { + return; + } + if (!returnHasNullable(memberReferenceTree)) { + // The method still could have a @PolyNull on the return and might return null. + // If @PolyNull is the primary annotation on the parameter and not on any type + // arguments or array elements, then it is still safe to mark the optional type as + // present. + // TODO: Add the check for poly null on arguments. + type.replaceAnnotation(PRESENT); + } + } + } - /** - * Returns true if the return type of the function type of {@code memberReferenceTree} is - * annotated with {@code @Nullable}. - * - * @param memberReferenceTree a member reference - * @return true if the return type of the function type of {@code memberReferenceTree} is - * annotated with {@code @Nullable} - */ - private boolean returnHasNullable(MemberReferenceTree memberReferenceTree) { - if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree) - .isConstructorReference()) { - return false; - } - ExecutableElement memberReferenceFuncType = TreeUtils.elementFromUse(memberReferenceTree); - if (memberReferenceFuncType.getEnclosingElement().getKind() - == ElementKind.ANNOTATION_TYPE) { - // Annotation element accessor are always non-null; - return false; - } - - if (!checker.hasOption("optionalMapAssumeNonNull")) { - return true; - } - return containsNullable(memberReferenceFuncType.getAnnotationMirrors()) - || containsNullable(memberReferenceFuncType.getReturnType().getAnnotationMirrors()); + /** + * Returns true if the return type of the function type of {@code memberReferenceTree} is + * annotated with {@code @Nullable}. + * + * @param memberReferenceTree a member reference + * @return true if the return type of the function type of {@code memberReferenceTree} is + * annotated with {@code @Nullable} + */ + private boolean returnHasNullable(MemberReferenceTree memberReferenceTree) { + if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree) + .isConstructorReference()) { + return false; + } + ExecutableElement memberReferenceFuncType = TreeUtils.elementFromUse(memberReferenceTree); + if (memberReferenceFuncType.getEnclosingElement().getKind() == ElementKind.ANNOTATION_TYPE) { + // Annotation element accessor are always non-null; + return false; } - /** - * Returns true if {@code annos} contains a nullable annotation. - * - * @param annos a collection of annotations - * @return true if {@code annos} contains a nullable annotation - */ - private boolean containsNullable(Collection annos) { - for (AnnotationMirror anno : annos) { - if (anno.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) { - return true; - } - } - return false; + if (!checker.hasOption("optionalMapAssumeNonNull")) { + return true; } + return containsNullable(memberReferenceFuncType.getAnnotationMirrors()) + || containsNullable(memberReferenceFuncType.getReturnType().getAnnotationMirrors()); + } - @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new OptionalTransfer(analysis); + /** + * Returns true if {@code annos} contains a nullable annotation. + * + * @param annos a collection of annotations + * @return true if {@code annos} contains a nullable annotation + */ + private boolean containsNullable(Collection annos) { + for (AnnotationMirror anno : annos) { + if (anno.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) { + return true; + } } + return false; + } + + @Override + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new OptionalTransfer(analysis); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java index 4ef8be6e4fb..1c6db443bef 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.optional; +import java.util.Optional; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedOptions; -import java.util.Optional; - /** * A type-checker that prevents misuse of the {@link java.util.Optional} class. * @@ -18,6 +17,6 @@ @StubFiles({"javaparser.astub"}) @SupportedOptions("optionalMapAssumeNonNull") public class OptionalChecker extends BaseTypeChecker { - /** Create an OptionalChecker. */ - public OptionalChecker() {} + /** Create an OptionalChecker. */ + public OptionalChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java index ceb8574c7ff..de521118adc 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java @@ -7,7 +7,11 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.dataflow.cfg.UnderlyingAST; @@ -22,87 +26,77 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.TreeUtils; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** The transfer function for the Optional Checker. */ public class OptionalTransfer extends CFTransfer { - /** The @{@link Present} annotation. */ - private final AnnotationMirror PRESENT; + /** The @{@link Present} annotation. */ + private final AnnotationMirror PRESENT; - /** The element for java.util.Optional.ifPresent(). */ - private final ExecutableElement optionalIfPresent; + /** The element for java.util.Optional.ifPresent(). */ + private final ExecutableElement optionalIfPresent; - /** The element for java.util.Optional.ifPresentOrElse(), or null. */ - private final @Nullable ExecutableElement optionalIfPresentOrElse; + /** The element for java.util.Optional.ifPresentOrElse(), or null. */ + private final @Nullable ExecutableElement optionalIfPresentOrElse; - /** The type factory associated with this transfer function. */ - private final AnnotatedTypeFactory atypeFactory; + /** The type factory associated with this transfer function. */ + private final AnnotatedTypeFactory atypeFactory; - /** - * Create an OptionalTransfer. - * - * @param analysis the Optional Checker instance - */ - public OptionalTransfer(CFAbstractAnalysis analysis) { - super(analysis); - atypeFactory = analysis.getTypeFactory(); - Elements elements = atypeFactory.getElementUtils(); - PRESENT = AnnotationBuilder.fromClass(elements, Present.class); - ProcessingEnvironment env = atypeFactory.getProcessingEnv(); - optionalIfPresent = TreeUtils.getMethod("java.util.Optional", "ifPresent", 1, env); - optionalIfPresentOrElse = - TreeUtils.getMethodOrNull("java.util.Optional", "ifPresentOrElse", 2, env); - } + /** + * Create an OptionalTransfer. + * + * @param analysis the Optional Checker instance + */ + public OptionalTransfer(CFAbstractAnalysis analysis) { + super(analysis); + atypeFactory = analysis.getTypeFactory(); + Elements elements = atypeFactory.getElementUtils(); + PRESENT = AnnotationBuilder.fromClass(elements, Present.class); + ProcessingEnvironment env = atypeFactory.getProcessingEnv(); + optionalIfPresent = TreeUtils.getMethod("java.util.Optional", "ifPresent", 1, env); + optionalIfPresentOrElse = + TreeUtils.getMethodOrNull("java.util.Optional", "ifPresentOrElse", 2, env); + } - @Override - public CFStore initialStore(UnderlyingAST underlyingAST, List parameters) { + @Override + public CFStore initialStore(UnderlyingAST underlyingAST, List parameters) { - CFStore result = super.initialStore(underlyingAST, parameters); + CFStore result = super.initialStore(underlyingAST, parameters); - if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { - // Check whether this lambda is an argument to `Optional.ifPresent()` or - // `Optional.ifPresentOrElse()`. If so, then within the lambda, the receiver of the - // `ifPresent*` method is @Present. - CFGLambda cfgLambda = (CFGLambda) underlyingAST; - LambdaExpressionTree lambdaTree = cfgLambda.getLambdaTree(); - List lambdaParams = lambdaTree.getParameters(); - if (lambdaParams.size() == 1) { - TreePath lambdaPath = atypeFactory.getPath(lambdaTree); - Tree lambdaParent = lambdaPath.getParentPath().getLeaf(); - if (lambdaParent.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree invok = (MethodInvocationTree) lambdaParent; - ExecutableElement methodElt = TreeUtils.elementFromUse(invok); - if (methodElt.equals(optionalIfPresent) - || methodElt.equals(optionalIfPresentOrElse)) { - // `underlyingAST` is an invocation of `Optional.ifPresent()` or - // `Optional.ifPresentOrElse()`. In the lambda, the receiver is @Present. - ExpressionTree methodSelectTree = - TreeUtils.withoutParens(invok.getMethodSelect()); - ExpressionTree receiverTree = - ((MemberSelectTree) methodSelectTree).getExpression(); - JavaExpression receiverJe = JavaExpression.fromTree(receiverTree); - result.insertValue(receiverJe, PRESENT); - } - } - } + if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { + // Check whether this lambda is an argument to `Optional.ifPresent()` or + // `Optional.ifPresentOrElse()`. If so, then within the lambda, the receiver of the + // `ifPresent*` method is @Present. + CFGLambda cfgLambda = (CFGLambda) underlyingAST; + LambdaExpressionTree lambdaTree = cfgLambda.getLambdaTree(); + List lambdaParams = lambdaTree.getParameters(); + if (lambdaParams.size() == 1) { + TreePath lambdaPath = atypeFactory.getPath(lambdaTree); + Tree lambdaParent = lambdaPath.getParentPath().getLeaf(); + if (lambdaParent.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree invok = (MethodInvocationTree) lambdaParent; + ExecutableElement methodElt = TreeUtils.elementFromUse(invok); + if (methodElt.equals(optionalIfPresent) || methodElt.equals(optionalIfPresentOrElse)) { + // `underlyingAST` is an invocation of `Optional.ifPresent()` or + // `Optional.ifPresentOrElse()`. In the lambda, the receiver is @Present. + ExpressionTree methodSelectTree = TreeUtils.withoutParens(invok.getMethodSelect()); + ExpressionTree receiverTree = ((MemberSelectTree) methodSelectTree).getExpression(); + JavaExpression receiverJe = JavaExpression.fromTree(receiverTree); + result.insertValue(receiverJe, PRESENT); + } } + } + } - // TODO: Similar logic to the above can be applied in the Nullness Checker. - // Some methods take a function as an argument, guaranteeing that, if the function is - // called: - // * the value passed to the function is non-null - // * some other argument to the method is non-null - // Examples: - // * Jodd's `StringUtil.ifNotNull()` - // * `Opt.ifPresent()` - // * `Opt.map()` + // TODO: Similar logic to the above can be applied in the Nullness Checker. + // Some methods take a function as an argument, guaranteeing that, if the function is + // called: + // * the value passed to the function is non-null + // * some other argument to the method is non-null + // Examples: + // * Jodd's `StringUtil.ifNotNull()` + // * `Opt.ifPresent()` + // * `Opt.map()` - return result; - } + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java index 5a9a0827419..575375071ee 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java @@ -15,7 +15,18 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.optional.qual.OptionalCreator; @@ -33,20 +44,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.IPair; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * The OptionalVisitor enforces the Optional Checker rules. These rules are described in the Checker * Framework Manual. @@ -54,446 +51,467 @@ * @checker_framework.manual #optional-checker Optional Checker */ public class OptionalVisitor - extends BaseTypeVisitor { - - /** The Collection type. */ - private final TypeMirror collectionType; - - /** The element for java.util.Optional.get(). */ - private final ExecutableElement optionalGet; - - /** The element for java.util.Optional.isPresent(). */ - private final ExecutableElement optionalIsPresent; - - /** The element for java.util.Optional.isEmpty(), or null if running under JDK 8. */ - private final @Nullable ExecutableElement optionalIsEmpty; - - /** The element for java.util.stream.Stream.filter(). */ - private final ExecutableElement streamFilter; - - /** The element for java.util.stream.Stream.map(). */ - private final ExecutableElement streamMap; - - /** - * Create an OptionalVisitor. - * - * @param checker the associated OptionalChecker - */ - public OptionalVisitor(BaseTypeChecker checker) { - super(checker); - collectionType = types.erasure(TypesUtils.typeFromClass(Collection.class, types, elements)); - - ProcessingEnvironment env = checker.getProcessingEnvironment(); - optionalGet = TreeUtils.getMethod("java.util.Optional", "get", 0, env); - optionalIsPresent = TreeUtils.getMethod("java.util.Optional", "isPresent", 0, env); - optionalIsEmpty = TreeUtils.getMethodOrNull("java.util.Optional", "isEmpty", 0, env); - - streamFilter = TreeUtils.getMethod("java.util.stream.Stream", "filter", 1, env); - streamMap = TreeUtils.getMethod("java.util.stream.Stream", "map", 1, env); + extends BaseTypeVisitor { + + /** The Collection type. */ + private final TypeMirror collectionType; + + /** The element for java.util.Optional.get(). */ + private final ExecutableElement optionalGet; + + /** The element for java.util.Optional.isPresent(). */ + private final ExecutableElement optionalIsPresent; + + /** The element for java.util.Optional.isEmpty(), or null if running under JDK 8. */ + private final @Nullable ExecutableElement optionalIsEmpty; + + /** The element for java.util.stream.Stream.filter(). */ + private final ExecutableElement streamFilter; + + /** The element for java.util.stream.Stream.map(). */ + private final ExecutableElement streamMap; + + /** + * Create an OptionalVisitor. + * + * @param checker the associated OptionalChecker + */ + public OptionalVisitor(BaseTypeChecker checker) { + super(checker); + collectionType = types.erasure(TypesUtils.typeFromClass(Collection.class, types, elements)); + + ProcessingEnvironment env = checker.getProcessingEnvironment(); + optionalGet = TreeUtils.getMethod("java.util.Optional", "get", 0, env); + optionalIsPresent = TreeUtils.getMethod("java.util.Optional", "isPresent", 0, env); + optionalIsEmpty = TreeUtils.getMethodOrNull("java.util.Optional", "isEmpty", 0, env); + + streamFilter = TreeUtils.getMethod("java.util.stream.Stream", "filter", 1, env); + streamMap = TreeUtils.getMethod("java.util.stream.Stream", "map", 1, env); + } + + @Override + protected BaseTypeValidator createTypeValidator() { + return new OptionalTypeValidator(checker, this, atypeFactory); + } + + /** + * Returns true iff {@code expression} is a call to java.util.Optional.get. + * + * @param expression an expression + * @return true iff {@code expression} is a call to java.util.Optional.get + */ + private boolean isCallToGet(ExpressionTree expression) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + return TreeUtils.isMethodInvocation(expression, optionalGet, env); + } + + /** + * Is the expression a call to {@code isPresent} or {@code isEmpty}? If not, returns null. If so, + * returns a pair of (boolean, receiver expression). The boolean is true if the given expression + * is a call to {@code isPresent} and is false if the given expression is a call to {@code + * isEmpty}. + * + * @param expression an expression + * @return a pair of a boolean (indicating whether the expression is a call to {@code + * Optional.isPresent} or to {@code Optional.isEmpty}) and its receiver; or null if not a call + * to either of the methods + */ + private @Nullable IPair isCallToIsPresent( + ExpressionTree expression) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + boolean negate = false; + while (true) { + switch (expression.getKind()) { + case PARENTHESIZED: + expression = ((ParenthesizedTree) expression).getExpression(); + break; + case LOGICAL_COMPLEMENT: + expression = ((UnaryTree) expression).getExpression(); + negate = !negate; + break; + case METHOD_INVOCATION: + if (TreeUtils.isMethodInvocation(expression, optionalIsPresent, env)) { + return IPair.of(!negate, TreeUtils.getReceiverTree(expression)); + } else if (optionalIsEmpty != null + && TreeUtils.isMethodInvocation(expression, optionalIsEmpty, env)) { + return IPair.of(negate, TreeUtils.getReceiverTree(expression)); + } else { + return null; + } + default: + return null; + } } - - @Override - protected BaseTypeValidator createTypeValidator() { - return new OptionalTypeValidator(checker, this, atypeFactory); + } + + /** + * Returns true iff the method being called is Optional creation: empty, of, ofNullable. + * + * @param methInvok a method invocation + * @return true iff the method being called is Optional creation: empty, of, ofNullable + */ + private boolean isOptionalCreation(MethodInvocationTree methInvok) { + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalCreator.class) != null; + } + + /** + * Returns true iff the method being called is Optional propagation: filter, flatMap, map, or. + * + * @param methInvok a method invocation + * @return true true iff the method being called is Optional propagation: filter, flatMap, map, or + */ + private boolean isOptionalPropagation(MethodInvocationTree methInvok) { + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalPropagator.class) != null; + } + + /** + * Returns true iff the method being called is Optional elimination: get, orElse, orElseGet, + * orElseThrow. + * + * @param methInvok a method invocation + * @return true iff the method being called is Optional elimination: get, orElse, orElseGet, + * orElseThrow + */ + private boolean isOptionalElimination(MethodInvocationTree methInvok) { + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalEliminator.class) != null; + } + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + handleTernaryIsPresentGet(tree); + return super.visitConditionalExpression(tree, p); + } + + /** + * Part of rule #3. + * + *

Pattern match for: {@code VAR.isPresent() ? VAR.get().METHOD() : VALUE} + * + *

Prefer: {@code VAR.map(METHOD).orElse(VALUE);} + * + * @param tree a conditional expression that can perhaps be simplified + */ + // TODO: Should handle this via a transfer function, instead of pattern-matching. + public void handleTernaryIsPresentGet(ConditionalExpressionTree tree) { + + ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); + IPair isPresentCall = isCallToIsPresent(condExpr); + if (isPresentCall == null) { + return; } - - /** - * Returns true iff {@code expression} is a call to java.util.Optional.get. - * - * @param expression an expression - * @return true iff {@code expression} is a call to java.util.Optional.get - */ - private boolean isCallToGet(ExpressionTree expression) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - return TreeUtils.isMethodInvocation(expression, optionalGet, env); + ExpressionTree trueExpr = TreeUtils.withoutParens(tree.getTrueExpression()); + ExpressionTree falseExpr = TreeUtils.withoutParens(tree.getFalseExpression()); + if (!isPresentCall.first) { + ExpressionTree tmp = trueExpr; + trueExpr = falseExpr; + falseExpr = tmp; } - /** - * Is the expression a call to {@code isPresent} or {@code isEmpty}? If not, returns null. If - * so, returns a pair of (boolean, receiver expression). The boolean is true if the given - * expression is a call to {@code isPresent} and is false if the given expression is a call to - * {@code isEmpty}. - * - * @param expression an expression - * @return a pair of a boolean (indicating whether the expression is a call to {@code - * Optional.isPresent} or to {@code Optional.isEmpty}) and its receiver; or null if not a - * call to either of the methods - */ - private @Nullable IPair isCallToIsPresent( - ExpressionTree expression) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - boolean negate = false; - while (true) { - switch (expression.getKind()) { - case PARENTHESIZED: - expression = ((ParenthesizedTree) expression).getExpression(); - break; - case LOGICAL_COMPLEMENT: - expression = ((UnaryTree) expression).getExpression(); - negate = !negate; - break; - case METHOD_INVOCATION: - if (TreeUtils.isMethodInvocation(expression, optionalIsPresent, env)) { - return IPair.of(!negate, TreeUtils.getReceiverTree(expression)); - } else if (optionalIsEmpty != null - && TreeUtils.isMethodInvocation(expression, optionalIsEmpty, env)) { - return IPair.of(negate, TreeUtils.getReceiverTree(expression)); - } else { - return null; - } - default: - return null; - } - } + if (trueExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { + return; } - - /** - * Returns true iff the method being called is Optional creation: empty, of, ofNullable. - * - * @param methInvok a method invocation - * @return true iff the method being called is Optional creation: empty, of, ofNullable - */ - private boolean isOptionalCreation(MethodInvocationTree methInvok) { - ExecutableElement method = TreeUtils.elementFromUse(methInvok); - return atypeFactory.getDeclAnnotation(method, OptionalCreator.class) != null; + ExpressionTree trueReceiver = TreeUtils.getReceiverTree(trueExpr); + if (!isCallToGet(trueReceiver)) { + return; } - - /** - * Returns true iff the method being called is Optional propagation: filter, flatMap, map, or. - * - * @param methInvok a method invocation - * @return true true iff the method being called is Optional propagation: filter, flatMap, map, - * or - */ - private boolean isOptionalPropagation(MethodInvocationTree methInvok) { - ExecutableElement method = TreeUtils.elementFromUse(methInvok); - return atypeFactory.getDeclAnnotation(method, OptionalPropagator.class) != null; + ExpressionTree getReceiver = TreeUtils.getReceiverTree(trueReceiver); + + // What is a better way to do this than string comparison? + // Use transfer functions and Store entries. + ExpressionTree receiver = isPresentCall.second; + if (sameExpression(receiver, getReceiver)) { + ExecutableElement ele = TreeUtils.elementFromUse((MethodInvocationTree) trueExpr); + + checker.reportWarning( + tree, + "prefer.map.and.orelse", + receiver, + // The literal "CONTAININGCLASS::" is gross. + // TODO: add this to the error message. + // ElementUtils.getQualifiedClassName(ele); + ele.getSimpleName(), + falseExpr); } - - /** - * Returns true iff the method being called is Optional elimination: get, orElse, orElseGet, - * orElseThrow. - * - * @param methInvok a method invocation - * @return true iff the method being called is Optional elimination: get, orElse, orElseGet, - * orElseThrow - */ - private boolean isOptionalElimination(MethodInvocationTree methInvok) { - ExecutableElement method = TreeUtils.elementFromUse(methInvok); - return atypeFactory.getDeclAnnotation(method, OptionalEliminator.class) != null; + } + + /** + * Returns true if the two trees represent the same expression. + * + * @param tree1 the first tree + * @param tree2 the second tree + * @return true if the two trees represent the same expression + */ + private boolean sameExpression(ExpressionTree tree1, ExpressionTree tree2) { + JavaExpression r1 = JavaExpression.fromTree(tree1); + JavaExpression r2 = JavaExpression.fromTree(tree2); + if (r1 != null && !r1.containsUnknown() && r2 != null && !r2.containsUnknown()) { + return r1.equals(r2); + } else { + return tree1.toString().equals(tree2.toString()); } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - handleTernaryIsPresentGet(tree); - return super.visitConditionalExpression(tree, p); + } + + @Override + public Void visitIf(IfTree tree, Void p) { + handleConditionalStatementIsPresentGet(tree); + return super.visitIf(tree, p); + } + + /** + * Part of rule #3. + * + *

Pattern match for: {@code if (VAR.isPresent()) { METHOD(VAR.get()); }} + * + *

Prefer: {@code VAR.ifPresent(METHOD);} + * + * @param tree an if statement that can perhaps be simplified + */ + public void handleConditionalStatementIsPresentGet(IfTree tree) { + + ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); + IPair isPresentCall = isCallToIsPresent(condExpr); + if (isPresentCall == null) { + return; } - /** - * Part of rule #3. - * - *

Pattern match for: {@code VAR.isPresent() ? VAR.get().METHOD() : VALUE} - * - *

Prefer: {@code VAR.map(METHOD).orElse(VALUE);} - * - * @param tree a conditional expression that can perhaps be simplified - */ - // TODO: Should handle this via a transfer function, instead of pattern-matching. - public void handleTernaryIsPresentGet(ConditionalExpressionTree tree) { - - ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); - IPair isPresentCall = isCallToIsPresent(condExpr); - if (isPresentCall == null) { - return; - } - ExpressionTree trueExpr = TreeUtils.withoutParens(tree.getTrueExpression()); - ExpressionTree falseExpr = TreeUtils.withoutParens(tree.getFalseExpression()); - if (!isPresentCall.first) { - ExpressionTree tmp = trueExpr; - trueExpr = falseExpr; - falseExpr = tmp; - } - - if (trueExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { - return; - } - ExpressionTree trueReceiver = TreeUtils.getReceiverTree(trueExpr); - if (!isCallToGet(trueReceiver)) { - return; - } - ExpressionTree getReceiver = TreeUtils.getReceiverTree(trueReceiver); - - // What is a better way to do this than string comparison? - // Use transfer functions and Store entries. - ExpressionTree receiver = isPresentCall.second; - if (sameExpression(receiver, getReceiver)) { - ExecutableElement ele = TreeUtils.elementFromUse((MethodInvocationTree) trueExpr); - - checker.reportWarning( - tree, - "prefer.map.and.orelse", - receiver, - // The literal "CONTAININGCLASS::" is gross. - // TODO: add this to the error message. - // ElementUtils.getQualifiedClassName(ele); - ele.getSimpleName(), - falseExpr); - } + StatementTree thenStmt = skipBlocks(tree.getThenStatement()); + StatementTree elseStmt = skipBlocks(tree.getElseStatement()); + if (!isPresentCall.first) { + StatementTree tmp = thenStmt; + thenStmt = elseStmt; + elseStmt = tmp; } - /** - * Returns true if the two trees represent the same expression. - * - * @param tree1 the first tree - * @param tree2 the second tree - * @return true if the two trees represent the same expression - */ - private boolean sameExpression(ExpressionTree tree1, ExpressionTree tree2) { - JavaExpression r1 = JavaExpression.fromTree(tree1); - JavaExpression r2 = JavaExpression.fromTree(tree2); - if (r1 != null && !r1.containsUnknown() && r2 != null && !r2.containsUnknown()) { - return r1.equals(r2); - } else { - return tree1.toString().equals(tree2.toString()); - } + if (!(elseStmt == null + || (elseStmt.getKind() == Tree.Kind.BLOCK + && ((BlockTree) elseStmt).getStatements().isEmpty()))) { + // else block is missing or is an empty block: "{}" + return; } - @Override - public Void visitIf(IfTree tree, Void p) { - handleConditionalStatementIsPresentGet(tree); - return super.visitIf(tree, p); + if (thenStmt.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { + return; } - - /** - * Part of rule #3. - * - *

Pattern match for: {@code if (VAR.isPresent()) { METHOD(VAR.get()); }} - * - *

Prefer: {@code VAR.ifPresent(METHOD);} - * - * @param tree an if statement that can perhaps be simplified - */ - public void handleConditionalStatementIsPresentGet(IfTree tree) { - - ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); - IPair isPresentCall = isCallToIsPresent(condExpr); - if (isPresentCall == null) { - return; - } - - StatementTree thenStmt = skipBlocks(tree.getThenStatement()); - StatementTree elseStmt = skipBlocks(tree.getElseStatement()); - if (!isPresentCall.first) { - StatementTree tmp = thenStmt; - thenStmt = elseStmt; - elseStmt = tmp; - } - - if (!(elseStmt == null - || (elseStmt.getKind() == Tree.Kind.BLOCK - && ((BlockTree) elseStmt).getStatements().isEmpty()))) { - // else block is missing or is an empty block: "{}" - return; - } - - if (thenStmt.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { - return; - } - ExpressionTree thenExpr = ((ExpressionStatementTree) thenStmt).getExpression(); - if (thenExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { - return; - } - MethodInvocationTree invok = (MethodInvocationTree) thenExpr; - List args = invok.getArguments(); - if (args.size() != 1) { - return; - } - ExpressionTree arg = TreeUtils.withoutParens(args.get(0)); - if (!isCallToGet(arg)) { - return; - } - ExpressionTree receiver = isPresentCall.second; - ExpressionTree getReceiver = TreeUtils.getReceiverTree(arg); - if (!receiver.toString().equals(getReceiver.toString())) { - return; - } - ExpressionTree method = invok.getMethodSelect(); - - String methodString = method.toString(); - int dotPos = methodString.lastIndexOf("."); - if (dotPos != -1) { - methodString = - methodString.substring(0, dotPos) + "::" + methodString.substring(dotPos + 1); - } - - checker.reportWarning(tree, "prefer.ifpresent", receiver, methodString); + ExpressionTree thenExpr = ((ExpressionStatementTree) thenStmt).getExpression(); + if (thenExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { + return; } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - handleCreationElimination(tree); - handleNestedOptionalCreation(tree); - return super.visitMethodInvocation(tree, p); + MethodInvocationTree invok = (MethodInvocationTree) thenExpr; + List args = invok.getArguments(); + if (args.size() != 1) { + return; } - - @Override - public Void visitBinary(BinaryTree tree, Void p) { - handleCompareToNull(tree); - return super.visitBinary(tree, p); + ExpressionTree arg = TreeUtils.withoutParens(args.get(0)); + if (!isCallToGet(arg)) { + return; } - - /** - * Partially enforces Rule #1. - * - *

If an Optional value is compared with the null literal, it indicates that the programmer - * expects it might have been assigned a null value (or no value at all) somewhere in the code. - * - * @param tree a binary tree representing a binary operation. - */ - private void handleCompareToNull(BinaryTree tree) { - if (!isEqualityOperation(tree)) { - return; - } - ExpressionTree leftOp = TreeUtils.withoutParens(tree.getLeftOperand()); - ExpressionTree rightOp = TreeUtils.withoutParens(tree.getRightOperand()); - TypeMirror leftOpType = TreeUtils.typeOf(leftOp); - TypeMirror rightOpType = TreeUtils.typeOf(rightOp); - - if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(rightOpType)) { - checker.reportWarning(tree, "optional.null.comparison"); - } - if (rightOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(leftOpType)) { - checker.reportWarning(tree, "optional.null.comparison"); - } + ExpressionTree receiver = isPresentCall.second; + ExpressionTree getReceiver = TreeUtils.getReceiverTree(arg); + if (!receiver.toString().equals(getReceiver.toString())) { + return; } + ExpressionTree method = invok.getMethodSelect(); - /** - * Returns true if the binary operation is {@code ==} or {@code !=}. - * - * @param tree a binary operation - * @return true if the binary operation is {@code ==} or {@code !=} - */ - private boolean isEqualityOperation(BinaryTree tree) { - return tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO; + String methodString = method.toString(); + int dotPos = methodString.lastIndexOf("."); + if (dotPos != -1) { + methodString = methodString.substring(0, dotPos) + "::" + methodString.substring(dotPos + 1); } - // Partially enforces Rule #1. (Only handles the literal `null`, not all nullable expressions.) - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = super.commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); - ExpressionTree valueWithoutParens = TreeUtils.withoutParens(valueExpTree); - if (valueWithoutParens.getKind() == Kind.NULL_LITERAL - && isOptionalType(varType.getUnderlyingType())) { - checker.reportWarning(valueWithoutParens, "optional.null.assignment"); - return false; - } - return result; + checker.reportWarning(tree, "prefer.ifpresent", receiver, methodString); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + handleCreationElimination(tree); + handleNestedOptionalCreation(tree); + return super.visitMethodInvocation(tree, p); + } + + @Override + public Void visitBinary(BinaryTree tree, Void p) { + handleCompareToNull(tree); + return super.visitBinary(tree, p); + } + + /** + * Partially enforces Rule #1. + * + *

If an Optional value is compared with the null literal, it indicates that the programmer + * expects it might have been assigned a null value (or no value at all) somewhere in the code. + * + * @param tree a binary tree representing a binary operation. + */ + private void handleCompareToNull(BinaryTree tree) { + if (!isEqualityOperation(tree)) { + return; } + ExpressionTree leftOp = TreeUtils.withoutParens(tree.getLeftOperand()); + ExpressionTree rightOp = TreeUtils.withoutParens(tree.getRightOperand()); + TypeMirror leftOpType = TreeUtils.typeOf(leftOp); + TypeMirror rightOpType = TreeUtils.typeOf(rightOp); - /** - * Rule #4. - * - *

Pattern match for: {@code CREATION().PROPAGATION()*.ELIMINATION()} - * - *

Prefer: {@code VAR.ifPresent(METHOD);} - * - * @param tree a method invocation that can perhaps be simplified - */ - public void handleCreationElimination(MethodInvocationTree tree) { - if (!isOptionalElimination(tree)) { - return; - } - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - while (true) { - if (receiver == null) { - // The receiver can be null if the receiver is the implicit "this.". - return; - } - if (receiver.getKind() != Tree.Kind.METHOD_INVOCATION) { - return; - } - MethodInvocationTree methodCall = (MethodInvocationTree) receiver; - if (isOptionalPropagation(methodCall)) { - receiver = TreeUtils.getReceiverTree(methodCall); - // Continue with new receiver. - } else if (isOptionalCreation(methodCall)) { - checker.reportWarning(tree, "introduce.eliminate"); - return; - } else { - return; - } - } + if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(rightOpType)) { + checker.reportWarning(tree, "optional.null.comparison"); } - - /** - * Partial support for Rule #5 and Rule #7. - * - *

Rule #5: Avoid nested Optional chains, or operations that have an intermediate Optional - * value. - * - *

Rule #7: Don't use Optional to wrap any collection type. - * - *

Certain types are illegal, such as {@code Optional}. The type validator may see - * a supertype of the most precise run-time type; for example, it may see the type as {@code - * Optional}, and it would not flag any problem with such a type. This method - * checks at {@code Optional} creation sites. - * - *

TODO: This finds only some {@code Optional}: those that consist of {@code - * Optional.of(optionalExpr)} or {@code Optional.ofNullable(optionalExpr)}, where {@code - * optionalExpr} has type {@code Optional}. There are other ways that {@code Optional} - * can be created, such as {@code optionalExpr.map(Optional::of)}. - * - *

TODO: Also check at collection creation sites, but there are so many of them, and there - * often are not values of the element type at the collection creation site. - * - * @param tree a method invocation that might create an Optional of an illegal type - */ - public void handleNestedOptionalCreation(MethodInvocationTree tree) { - if (!isOptionalCreation(tree)) { - return; - } - if (tree.getArguments().isEmpty()) { - // This is a call to Optional.empty(), which takes no argument. - return; - } - ExpressionTree arg = tree.getArguments().get(0); - AnnotatedTypeMirror argAtm = atypeFactory.getAnnotatedType(arg); - TypeMirror argType = argAtm.getUnderlyingType(); - if (isOptionalType(argType)) { - checker.reportWarning(tree, "optional.nesting"); - } else if (isCollectionType(argType)) { - checker.reportWarning(tree, "optional.collection"); - } + if (rightOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(leftOpType)) { + checker.reportWarning(tree, "optional.null.comparison"); } - - /** - * Rule #6 (partial). - * - *

Don't use Optional in fields and method parameters. - */ - @Override - public Void visitVariable(VariableTree tree, Void p) { - VariableElement ve = TreeUtils.elementFromDeclaration(tree); - TypeMirror tm = ve.asType(); - if (isOptionalType(tm)) { - ElementKind ekind = TreeUtils.elementFromDeclaration(tree).getKind(); - if (ekind.isField()) { - checker.reportWarning(tree, "optional.field"); - } else if (ekind == ElementKind.PARAMETER) { - TreePath paramPath = getCurrentPath(); - Tree parent = paramPath.getParentPath().getLeaf(); - if (parent.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - // Exception to rule: lambda parameters can have type Optional. - } else { - checker.reportWarning(tree, "optional.parameter"); - } - } + } + + /** + * Returns true if the binary operation is {@code ==} or {@code !=}. + * + * @param tree a binary operation + * @return true if the binary operation is {@code ==} or {@code !=} + */ + private boolean isEqualityOperation(BinaryTree tree) { + return tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO; + } + + // Partially enforces Rule #1. (Only handles the literal `null`, not all nullable expressions.) + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = super.commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); + ExpressionTree valueWithoutParens = TreeUtils.withoutParens(valueExpTree); + if (valueWithoutParens.getKind() == Kind.NULL_LITERAL + && isOptionalType(varType.getUnderlyingType())) { + checker.reportWarning(valueWithoutParens, "optional.null.assignment"); + return false; + } + return result; + } + + /** + * Rule #4. + * + *

Pattern match for: {@code CREATION().PROPAGATION()*.ELIMINATION()} + * + *

Prefer: {@code VAR.ifPresent(METHOD);} + * + * @param tree a method invocation that can perhaps be simplified + */ + public void handleCreationElimination(MethodInvocationTree tree) { + if (!isOptionalElimination(tree)) { + return; + } + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + while (true) { + if (receiver == null) { + // The receiver can be null if the receiver is the implicit "this.". + return; + } + if (receiver.getKind() != Tree.Kind.METHOD_INVOCATION) { + return; + } + MethodInvocationTree methodCall = (MethodInvocationTree) receiver; + if (isOptionalPropagation(methodCall)) { + receiver = TreeUtils.getReceiverTree(methodCall); + // Continue with new receiver. + } else if (isOptionalCreation(methodCall)) { + checker.reportWarning(tree, "introduce.eliminate"); + return; + } else { + return; + } + } + } + + /** + * Partial support for Rule #5 and Rule #7. + * + *

Rule #5: Avoid nested Optional chains, or operations that have an intermediate Optional + * value. + * + *

Rule #7: Don't use Optional to wrap any collection type. + * + *

Certain types are illegal, such as {@code Optional}. The type validator may see a + * supertype of the most precise run-time type; for example, it may see the type as {@code + * Optional}, and it would not flag any problem with such a type. This method + * checks at {@code Optional} creation sites. + * + *

TODO: This finds only some {@code Optional}: those that consist of {@code + * Optional.of(optionalExpr)} or {@code Optional.ofNullable(optionalExpr)}, where {@code + * optionalExpr} has type {@code Optional}. There are other ways that {@code Optional} + * can be created, such as {@code optionalExpr.map(Optional::of)}. + * + *

TODO: Also check at collection creation sites, but there are so many of them, and there + * often are not values of the element type at the collection creation site. + * + * @param tree a method invocation that might create an Optional of an illegal type + */ + public void handleNestedOptionalCreation(MethodInvocationTree tree) { + if (!isOptionalCreation(tree)) { + return; + } + if (tree.getArguments().isEmpty()) { + // This is a call to Optional.empty(), which takes no argument. + return; + } + ExpressionTree arg = tree.getArguments().get(0); + AnnotatedTypeMirror argAtm = atypeFactory.getAnnotatedType(arg); + TypeMirror argType = argAtm.getUnderlyingType(); + if (isOptionalType(argType)) { + checker.reportWarning(tree, "optional.nesting"); + } else if (isCollectionType(argType)) { + checker.reportWarning(tree, "optional.collection"); + } + } + + /** + * Rule #6 (partial). + * + *

Don't use Optional in fields and method parameters. + */ + @Override + public Void visitVariable(VariableTree tree, Void p) { + VariableElement ve = TreeUtils.elementFromDeclaration(tree); + TypeMirror tm = ve.asType(); + if (isOptionalType(tm)) { + ElementKind ekind = TreeUtils.elementFromDeclaration(tree).getKind(); + if (ekind.isField()) { + checker.reportWarning(tree, "optional.field"); + } else if (ekind == ElementKind.PARAMETER) { + TreePath paramPath = getCurrentPath(); + Tree parent = paramPath.getParentPath().getLeaf(); + if (parent.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + // Exception to rule: lambda parameters can have type Optional. + } else { + checker.reportWarning(tree, "optional.parameter"); } - return super.visitVariable(tree, p); + } + } + return super.visitVariable(tree, p); + } + + /** + * Handles Rule #5, part of Rule #6, and also Rule #7. + * + *

Rule #5: Avoid nested Optional chains, or operations that have an intermediate Optional + * value. + * + *

Rule #6: Don't use Optional in fields, parameters, and collections. + * + *

Rule #7: Don't use Optional to wrap any collection type. + * + *

The validator is called on the type of every expression, such as on the right-hand side of + * {@code x = Optional.of(Optional.of("baz"));}. However, the type of the right-hand side is + * {@code Optional}, not {@code Optional>}. Therefore, to fully + * check for improper types, it is necessary to examine, in the type checker, the argument to + * construction of an Optional. Method {@link #handleNestedOptionalCreation} does so. + */ + private final class OptionalTypeValidator extends BaseTypeValidator { + + public OptionalTypeValidator( + BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); } /** @@ -505,172 +523,144 @@ public Void visitVariable(VariableTree tree, Void p) { *

Rule #6: Don't use Optional in fields, parameters, and collections. * *

Rule #7: Don't use Optional to wrap any collection type. - * - *

The validator is called on the type of every expression, such as on the right-hand side of - * {@code x = Optional.of(Optional.of("baz"));}. However, the type of the right-hand side is - * {@code Optional}, not {@code Optional>}. Therefore, to - * fully check for improper types, it is necessary to examine, in the type checker, the argument - * to construction of an Optional. Method {@link #handleNestedOptionalCreation} does so. */ - private final class OptionalTypeValidator extends BaseTypeValidator { - - public OptionalTypeValidator( - BaseTypeChecker checker, - BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } - - /** - * Handles Rule #5, part of Rule #6, and also Rule #7. - * - *

Rule #5: Avoid nested Optional chains, or operations that have an intermediate - * Optional value. - * - *

Rule #6: Don't use Optional in fields, parameters, and collections. - * - *

Rule #7: Don't use Optional to wrap any collection type. - */ - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - TypeMirror tm = type.getUnderlyingType(); - if (isCollectionType(tm)) { - List typeArgs = ((DeclaredType) tm).getTypeArguments(); - if (typeArgs.size() == 1) { - // TODO: handle collections that have more than one type parameter - TypeMirror typeArg = typeArgs.get(0); - if (isOptionalType(typeArg)) { - checker.reportWarning(tree, "optional.as.element.type"); - } - } - } else if (isOptionalType(tm)) { - List typeArgs = ((DeclaredType) tm).getTypeArguments(); - // If typeArgs.size()==0, then the user wrote a raw type `Optional`. - if (typeArgs.size() == 1) { - TypeMirror typeArg = typeArgs.get(0); - if (isCollectionType(typeArg)) { - checker.reportWarning(tree, "optional.collection"); - } - if (isOptionalType(typeArg)) { - checker.reportWarning(tree, "optional.nesting"); - } - } - } - return super.visitDeclared(type, tree); + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + TypeMirror tm = type.getUnderlyingType(); + if (isCollectionType(tm)) { + List typeArgs = ((DeclaredType) tm).getTypeArguments(); + if (typeArgs.size() == 1) { + // TODO: handle collections that have more than one type parameter + TypeMirror typeArg = typeArgs.get(0); + if (isOptionalType(typeArg)) { + checker.reportWarning(tree, "optional.as.element.type"); + } + } + } else if (isOptionalType(tm)) { + List typeArgs = ((DeclaredType) tm).getTypeArguments(); + // If typeArgs.size()==0, then the user wrote a raw type `Optional`. + if (typeArgs.size() == 1) { + TypeMirror typeArg = typeArgs.get(0); + if (isCollectionType(typeArg)) { + checker.reportWarning(tree, "optional.collection"); + } + if (isOptionalType(typeArg)) { + checker.reportWarning(tree, "optional.nesting"); + } } + } + return super.visitDeclared(type, tree); } - - /** - * Return true if tm is a subtype of Collection (other than the Null type). - * - * @param tm a type - * @return true if the given type is a subtype of Collection - */ - private boolean isCollectionType(TypeMirror tm) { - return tm.getKind() == TypeKind.DECLARED && types.isSubtype(tm, collectionType); - } - - /** The fully-qualified names of the 4 optional classes in java.util. */ - private static final Set fqOptionalTypes = - new HashSet<>( - Arrays.asList( - "java.util.Optional", - "java.util.OptionalDouble", - "java.util.OptionalInt", - "java.util.OptionalLong")); - - /** - * Return true if tm is class Optional, OptionalDouble, OptionalInt, or OptionalLong in - * java.util. - * - * @param tm a type - * @return true if the given type is Optional, OptionalDouble, OptionalInt, or OptionalLong - */ - private boolean isOptionalType(TypeMirror tm) { - return TypesUtils.isDeclaredOfName(tm, fqOptionalTypes); + } + + /** + * Return true if tm is a subtype of Collection (other than the Null type). + * + * @param tm a type + * @return true if the given type is a subtype of Collection + */ + private boolean isCollectionType(TypeMirror tm) { + return tm.getKind() == TypeKind.DECLARED && types.isSubtype(tm, collectionType); + } + + /** The fully-qualified names of the 4 optional classes in java.util. */ + private static final Set fqOptionalTypes = + new HashSet<>( + Arrays.asList( + "java.util.Optional", + "java.util.OptionalDouble", + "java.util.OptionalInt", + "java.util.OptionalLong")); + + /** + * Return true if tm is class Optional, OptionalDouble, OptionalInt, or OptionalLong in java.util. + * + * @param tm a type + * @return true if the given type is Optional, OptionalDouble, OptionalInt, or OptionalLong + */ + private boolean isOptionalType(TypeMirror tm) { + return TypesUtils.isDeclaredOfName(tm, fqOptionalTypes); + } + + /** + * If the given tree is a block tree with a single element, return the enclosed non-block + * statement. Otherwise, return the same tree. + * + * @param tree a statement tree + * @return the single enclosed statement, if it exists; otherwise, the same tree + */ + // TODO: The Optional Checker should work over the CFG, then it would not need this any longer. + public static StatementTree skipBlocks(StatementTree tree) { + if (tree == null) { + return tree; } - - /** - * If the given tree is a block tree with a single element, return the enclosed non-block - * statement. Otherwise, return the same tree. - * - * @param tree a statement tree - * @return the single enclosed statement, if it exists; otherwise, the same tree - */ - // TODO: The Optional Checker should work over the CFG, then it would not need this any longer. - public static StatementTree skipBlocks(StatementTree tree) { - if (tree == null) { - return tree; - } - StatementTree s = tree; - while (s.getKind() == Tree.Kind.BLOCK) { - List stmts = ((BlockTree) s).getStatements(); - if (stmts.size() == 1) { - s = stmts.get(0); - } else { - return s; - } - } + StatementTree s = tree; + while (s.getKind() == Tree.Kind.BLOCK) { + List stmts = ((BlockTree) s).getStatements(); + if (stmts.size() == 1) { + s = stmts.get(0); + } else { return s; + } } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void p) { - if (isFilterIsPresentMapGet(tree)) { - // TODO: This is a (sound) workaround until - // https://github.com/typetools/checker-framework/issues/1345 - // is fixed. - return null; - } - return super.visitMemberReference(tree, p); + return s; + } + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void p) { + if (isFilterIsPresentMapGet(tree)) { + // TODO: This is a (sound) workaround until + // https://github.com/typetools/checker-framework/issues/1345 + // is fixed. + return null; } - - /** - * Returns true if {@code memberRefTree} is the {@code Optional::get} in {@code - * Stream.filter(Optional::isPresent).map(Optional::get)}. - * - * @param memberRefTree a member reference tree - * @return true if {@code memberRefTree} the {@code Optional::get} in {@code - * Stream.filter(Optional::isPresent).map(Optional::get)} - */ - private boolean isFilterIsPresentMapGet(MemberReferenceTree memberRefTree) { - if (!TreeUtils.elementFromUse(memberRefTree).equals(optionalGet)) { - // The method reference is not Optional::get - return false; - } - // "getPath" means "the path to the node `Optional::get`". - TreePath getPath = getCurrentPath(); - TreePath getParentPath = getPath.getParentPath(); - // "getParent" means "the parent of the node `Optional::get`". - Tree getParent = getParentPath.getLeaf(); - if (getParent.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree hasGetAsArgumentTree = (MethodInvocationTree) getParent; - ExecutableElement hasGetAsArgumentElement = - TreeUtils.elementFromUse(hasGetAsArgumentTree); - if (!hasGetAsArgumentElement.equals(streamMap)) { - // Optional::get is not an argument to stream#map - return false; - } - // hasGetAsArgumentTree is an invocation of Stream#map(...). - Tree mapReceiverTree = TreeUtils.getReceiverTree(hasGetAsArgumentTree); - // Will check whether mapParent is the call `Stream.filter(Optional::isPresent)`. - if (mapReceiverTree != null - && mapReceiverTree.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree fluentToMapTree = (MethodInvocationTree) mapReceiverTree; - ExecutableElement fluentToMapElement = TreeUtils.elementFromUse(fluentToMapTree); - if (!fluentToMapElement.equals(streamFilter)) { - // The receiver of map(Optional::get) is not Stream#filter - return false; - } - MethodInvocationTree filterInvocationTree = fluentToMapTree; - ExpressionTree filterArgTree = filterInvocationTree.getArguments().get(0); - if (filterArgTree.getKind() == Tree.Kind.MEMBER_REFERENCE) { - ExecutableElement filterArgElement = - TreeUtils.elementFromUse((MemberReferenceTree) filterArgTree); - return filterArgElement.equals(optionalIsPresent); - } - } - } + return super.visitMemberReference(tree, p); + } + + /** + * Returns true if {@code memberRefTree} is the {@code Optional::get} in {@code + * Stream.filter(Optional::isPresent).map(Optional::get)}. + * + * @param memberRefTree a member reference tree + * @return true if {@code memberRefTree} the {@code Optional::get} in {@code + * Stream.filter(Optional::isPresent).map(Optional::get)} + */ + private boolean isFilterIsPresentMapGet(MemberReferenceTree memberRefTree) { + if (!TreeUtils.elementFromUse(memberRefTree).equals(optionalGet)) { + // The method reference is not Optional::get + return false; + } + // "getPath" means "the path to the node `Optional::get`". + TreePath getPath = getCurrentPath(); + TreePath getParentPath = getPath.getParentPath(); + // "getParent" means "the parent of the node `Optional::get`". + Tree getParent = getParentPath.getLeaf(); + if (getParent.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree hasGetAsArgumentTree = (MethodInvocationTree) getParent; + ExecutableElement hasGetAsArgumentElement = TreeUtils.elementFromUse(hasGetAsArgumentTree); + if (!hasGetAsArgumentElement.equals(streamMap)) { + // Optional::get is not an argument to stream#map return false; + } + // hasGetAsArgumentTree is an invocation of Stream#map(...). + Tree mapReceiverTree = TreeUtils.getReceiverTree(hasGetAsArgumentTree); + // Will check whether mapParent is the call `Stream.filter(Optional::isPresent)`. + if (mapReceiverTree != null && mapReceiverTree.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree fluentToMapTree = (MethodInvocationTree) mapReceiverTree; + ExecutableElement fluentToMapElement = TreeUtils.elementFromUse(fluentToMapTree); + if (!fluentToMapElement.equals(streamFilter)) { + // The receiver of map(Optional::get) is not Stream#filter + return false; + } + MethodInvocationTree filterInvocationTree = fluentToMapTree; + ExpressionTree filterArgTree = filterInvocationTree.getArguments().get(0); + if (filterArgTree.getKind() == Tree.Kind.MEMBER_REFERENCE) { + ExecutableElement filterArgElement = + TreeUtils.elementFromUse((MemberReferenceTree) filterArgTree); + return filterArgElement.equals(optionalIsPresent); + } + } } + return false; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java index 142f51f9829..eead5ddda91 100644 --- a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java @@ -4,17 +4,6 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.propkey.qual.PropertyKey; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; -import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.plumelib.reflection.Signatures; -import org.plumelib.util.CollectionsPlume; - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -27,9 +16,17 @@ import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; - import javax.lang.model.element.AnnotationMirror; import javax.tools.Diagnostic; +import org.checkerframework.checker.propkey.qual.PropertyKey; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.plumelib.reflection.Signatures; +import org.plumelib.util.CollectionsPlume; /** * This AnnotatedTypeFactory adds PropertyKey annotations to String literals that contain values @@ -37,206 +34,202 @@ */ public class PropertyKeyAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private final Set lookupKeys; - - public PropertyKeyAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.lookupKeys = Collections.unmodifiableSet(buildLookupKeys()); - - this.postInit(); + private final Set lookupKeys; + + public PropertyKeyAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.lookupKeys = Collections.unmodifiableSet(buildLookupKeys()); + + this.postInit(); + } + + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this, PropertyKey.class)); + } + + // To allow subclasses access to createTreeAnnotator from the BATF. + protected TreeAnnotator createBasicTreeAnnotator() { + return super.createTreeAnnotator(); + } + + /** + * This TreeAnnotator checks for every String literal whether it is included in the lookup keys. + * If it is, the given annotation is added to the literal; otherwise, nothing happens. Subclasses + * of this AnnotatedTypeFactory can directly reuse this class and use a different annotation as + * parameter. + */ + protected class KeyLookupTreeAnnotator extends TreeAnnotator { + AnnotationMirror theAnnot; + + public KeyLookupTreeAnnotator(BaseAnnotatedTypeFactory atf, Class annot) { + super(atf); + theAnnot = AnnotationBuilder.fromClass(elements, annot); } @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this, PropertyKey.class)); + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(theAnnot) + && tree.getKind() == Tree.Kind.STRING_LITERAL + && strContains(lookupKeys, tree.getValue().toString())) { + type.addAnnotation(theAnnot); + } + // A possible extension is to record all the keys that have been used and + // in the end output a list of keys that were not used in the program, + // possibly pointing to the opposite problem, keys that were supposed to + // be used somewhere, but have not been, maybe because of copy-and-paste errors. + return super.visitLiteral(tree, type); } - // To allow subclasses access to createTreeAnnotator from the BATF. - protected TreeAnnotator createBasicTreeAnnotator() { - return super.createTreeAnnotator(); + // Result of binary op might not be a property key. + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(theAnnot); + return null; // super.visitBinary(tree, type); } - /** - * This TreeAnnotator checks for every String literal whether it is included in the lookup keys. - * If it is, the given annotation is added to the literal; otherwise, nothing happens. - * Subclasses of this AnnotatedTypeFactory can directly reuse this class and use a different - * annotation as parameter. - */ - protected class KeyLookupTreeAnnotator extends TreeAnnotator { - AnnotationMirror theAnnot; - - public KeyLookupTreeAnnotator( - BaseAnnotatedTypeFactory atf, Class annot) { - super(atf); - theAnnot = AnnotationBuilder.fromClass(elements, annot); - } - - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(theAnnot) - && tree.getKind() == Tree.Kind.STRING_LITERAL - && strContains(lookupKeys, tree.getValue().toString())) { - type.addAnnotation(theAnnot); - } - // A possible extension is to record all the keys that have been used and - // in the end output a list of keys that were not used in the program, - // possibly pointing to the opposite problem, keys that were supposed to - // be used somewhere, but have not been, maybe because of copy-and-paste errors. - return super.visitLiteral(tree, type); - } - - // Result of binary op might not be a property key. - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(theAnnot); - return null; // super.visitBinary(tree, type); - } - - // Result of unary op might not be a property key. - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(theAnnot); - return null; // super.visitCompoundAssignment(tree, type); - } + // Result of unary op might not be a property key. + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(theAnnot); + return null; // super.visitCompoundAssignment(tree, type); } - - /** - * Instead of a precise comparison, we incrementally remove leading dot-separated strings until - * we find a match. For example if messages contains "y.z" and we look for "x.y.z" we find a - * match after removing the first "x.". - * - *

Compare to SourceChecker.fullMessageOf. - */ - private static boolean strContains(Set messages, String messageKey) { - String key = messageKey; - - do { - if (messages.contains(key)) { - return true; - } - - int dot = key.indexOf('.'); - if (dot < 0) { - return false; - } - key = key.substring(dot + 1); - } while (true); + } + + /** + * Instead of a precise comparison, we incrementally remove leading dot-separated strings until we + * find a match. For example if messages contains "y.z" and we look for "x.y.z" we find a match + * after removing the first "x.". + * + *

Compare to SourceChecker.fullMessageOf. + */ + private static boolean strContains(Set messages, String messageKey) { + String key = messageKey; + + do { + if (messages.contains(key)) { + return true; + } + + int dot = key.indexOf('.'); + if (dot < 0) { + return false; + } + key = key.substring(dot + 1); + } while (true); + } + + /** + * Returns a set of the valid keys that can be used. + * + * @return the valid keys that can be used + */ + public Set getLookupKeys() { + return this.lookupKeys; + } + + private Set buildLookupKeys() { + Set result = new HashSet<>(); + + if (checker.hasOption("propfiles")) { + result.addAll(keysOfPropertyFiles(checker.getStringsOption("propfiles", File.pathSeparator))); } - - /** - * Returns a set of the valid keys that can be used. - * - * @return the valid keys that can be used - */ - public Set getLookupKeys() { - return this.lookupKeys; + if (checker.hasOption("bundlenames")) { + result.addAll(keysOfResourceBundle(checker.getStringsOption("bundlenames", ':'))); } - private Set buildLookupKeys() { - Set result = new HashSet<>(); + return result; + } + + /** + * Obtains the keys from all the property files. + * + * @param propfiles a list of property file names + * @return a set of all the keys found in all the property files + */ + private Set keysOfPropertyFiles(List propfiles) { + if (propfiles.isEmpty()) { + return Collections.emptySet(); + } - if (checker.hasOption("propfiles")) { - result.addAll( - keysOfPropertyFiles(checker.getStringsOption("propfiles", File.pathSeparator))); - } - if (checker.hasOption("bundlenames")) { - result.addAll(keysOfResourceBundle(checker.getStringsOption("bundlenames", ':'))); - } + Set result = new HashSet<>(CollectionsPlume.mapCapacity(propfiles)); - return result; - } + for (String propfile : propfiles) { + try { + Properties prop = new Properties(); - /** - * Obtains the keys from all the property files. - * - * @param propfiles a list of property file names - * @return a set of all the keys found in all the property files - */ - private Set keysOfPropertyFiles(List propfiles) { - if (propfiles.isEmpty()) { - return Collections.emptySet(); + ClassLoader cl = this.getClass().getClassLoader(); + if (cl == null) { + // The class loader is null if the system class loader was used. + cl = ClassLoader.getSystemClassLoader(); } - Set result = new HashSet<>(CollectionsPlume.mapCapacity(propfiles)); - - for (String propfile : propfiles) { - try { - Properties prop = new Properties(); - - ClassLoader cl = this.getClass().getClassLoader(); - if (cl == null) { - // The class loader is null if the system class loader was used. - cl = ClassLoader.getSystemClassLoader(); - } - - try (InputStream in = cl.getResourceAsStream(propfile)) { - if (in != null) { - prop.load(in); - } else { - // If the classloader didn't manage to load the file, try whether a - // FileInputStream works. For absolute paths this might help. - try (InputStream fis = new FileInputStream(propfile)) { - prop.load(fis); - } catch (FileNotFoundException e) { - checker.message( - Diagnostic.Kind.WARNING, - "Couldn't find the properties file: " + propfile); - // report(null, "propertykeychecker.filenotfound", propfile); - // return Collections.emptySet(); - continue; - } - } - } - - result.addAll(prop.stringPropertyNames()); - } catch (Exception e) { - // TODO: is there a nicer way to report messages, that are not connected to an AST - // node? - // One cannot use `report`, because it needs a node. - checker.message( - Diagnostic.Kind.WARNING, - "Exception in PropertyKeyChecker.keysOfPropertyFile: " + e); - e.printStackTrace(); + try (InputStream in = cl.getResourceAsStream(propfile)) { + if (in != null) { + prop.load(in); + } else { + // If the classloader didn't manage to load the file, try whether a + // FileInputStream works. For absolute paths this might help. + try (InputStream fis = new FileInputStream(propfile)) { + prop.load(fis); + } catch (FileNotFoundException e) { + checker.message( + Diagnostic.Kind.WARNING, "Couldn't find the properties file: " + propfile); + // report(null, "propertykeychecker.filenotfound", propfile); + // return Collections.emptySet(); + continue; } + } } - return result; + result.addAll(prop.stringPropertyNames()); + } catch (Exception e) { + // TODO: is there a nicer way to report messages, that are not connected to an AST + // node? + // One cannot use `report`, because it needs a node. + checker.message( + Diagnostic.Kind.WARNING, "Exception in PropertyKeyChecker.keysOfPropertyFile: " + e); + e.printStackTrace(); + } } - /** - * Returns the keys for the given resource bundles. - * - * @param bundleNames names of resource bundles - * @return the keys for the given resource bundles - */ - private Set keysOfResourceBundle(List bundleNames) { - if (bundleNames.isEmpty()) { - return Collections.emptySet(); - } - - Set result = new HashSet<>(CollectionsPlume.mapCapacity(bundleNames)); - - for (String bundleName : bundleNames) { - if (!Signatures.isBinaryName(bundleName)) { - System.err.println( - "Malformed resource bundle: <" + bundleName + "> should be a binary name."); - continue; - } - ResourceBundle bundle = ResourceBundle.getBundle(bundleName); - if (bundle == null) { - checker.message( - Diagnostic.Kind.WARNING, - "Couldn't find the resource bundle: <" - + bundleName - + "> for locale <" - + Locale.getDefault() - + ">"); - continue; - } + return result; + } + + /** + * Returns the keys for the given resource bundles. + * + * @param bundleNames names of resource bundles + * @return the keys for the given resource bundles + */ + private Set keysOfResourceBundle(List bundleNames) { + if (bundleNames.isEmpty()) { + return Collections.emptySet(); + } - result.addAll(bundle.keySet()); - } - return result; + Set result = new HashSet<>(CollectionsPlume.mapCapacity(bundleNames)); + + for (String bundleName : bundleNames) { + if (!Signatures.isBinaryName(bundleName)) { + System.err.println( + "Malformed resource bundle: <" + bundleName + "> should be a binary name."); + continue; + } + ResourceBundle bundle = ResourceBundle.getBundle(bundleName); + if (bundle == null) { + checker.message( + Diagnostic.Kind.WARNING, + "Couldn't find the resource bundle: <" + + bundleName + + "> for locale <" + + Locale.getDefault() + + ">"); + continue; + } + + result.addAll(bundle.keySet()); } + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java index 0b251add1b0..4a3f02c8321 100644 --- a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.propkey; +import java.util.Locale; +import java.util.ResourceBundle; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SupportedOptions; -import java.util.Locale; -import java.util.ResourceBundle; - /** * A type-checker that checks that only valid keys are used to access property files and resource * bundles. Subclasses can specialize this class for the different uses of property files, for diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java index b500fc36e4d..96849822346 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java @@ -6,7 +6,14 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Set; +import java.util.regex.Pattern; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.regex.qual.PartialRegex; import org.checkerframework.checker.regex.qual.PolyRegex; @@ -39,16 +46,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Set; -import java.util.regex.Pattern; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; - /** * Adds {@link Regex} to the type of tree, in the following cases: * @@ -80,488 +77,482 @@ */ public class RegexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link Regex} annotation. */ - protected final AnnotationMirror REGEX = AnnotationBuilder.fromClass(elements, Regex.class); - - /** The @{@link RegexBottom} annotation. */ - protected final AnnotationMirror REGEXBOTTOM = - AnnotationBuilder.fromClass(elements, RegexBottom.class); - - /** The @{@link PartialRegex} annotation. */ - protected final AnnotationMirror PARTIALREGEX = - AnnotationBuilder.fromClass(elements, PartialRegex.class); - - /** The @{@link PolyRegex} annotation. */ - protected final AnnotationMirror POLYREGEX = - AnnotationBuilder.fromClass(elements, PolyRegex.class); - - /** The @{@link UnknownRegex} annotation. */ - protected final AnnotationMirror UNKNOWNREGEX = - AnnotationBuilder.fromClass(elements, UnknownRegex.class); - - /** A set containing just {@link #UNKNOWNREGEX}. */ - protected final AnnotationMirrorSet UNKNOWNREGEX_SET = - AnnotationMirrorSet.singleton(UNKNOWNREGEX); + /** The @{@link Regex} annotation. */ + protected final AnnotationMirror REGEX = AnnotationBuilder.fromClass(elements, Regex.class); + + /** The @{@link RegexBottom} annotation. */ + protected final AnnotationMirror REGEXBOTTOM = + AnnotationBuilder.fromClass(elements, RegexBottom.class); + + /** The @{@link PartialRegex} annotation. */ + protected final AnnotationMirror PARTIALREGEX = + AnnotationBuilder.fromClass(elements, PartialRegex.class); + + /** The @{@link PolyRegex} annotation. */ + protected final AnnotationMirror POLYREGEX = + AnnotationBuilder.fromClass(elements, PolyRegex.class); + + /** The @{@link UnknownRegex} annotation. */ + protected final AnnotationMirror UNKNOWNREGEX = + AnnotationBuilder.fromClass(elements, UnknownRegex.class); + + /** A set containing just {@link #UNKNOWNREGEX}. */ + protected final AnnotationMirrorSet UNKNOWNREGEX_SET = + AnnotationMirrorSet.singleton(UNKNOWNREGEX); + + /** The method that returns the value element of a {@code @Regex} annotation. */ + protected final ExecutableElement regexValueElement = + TreeUtils.getMethod( + "org.checkerframework.checker.regex.qual.Regex", "value", 0, processingEnv); + + /** + * The value method of the PartialRegex qualifier. + * + * @see org.checkerframework.checker.regex.qual.PartialRegex + */ + private final ExecutableElement partialRegexValueElement = + TreeUtils.getMethod(PartialRegex.class, "value", 0, processingEnv); + + /** + * The Pattern.compile method. + * + * @see java.util.regex.Pattern#compile(String) + */ + private final ExecutableElement patternCompile = + TreeUtils.getMethod("java.util.regex.Pattern", "compile", 1, processingEnv); + + /** + * The Pattern.compile method that takes two formal parameters (second one is flags). + * + * @see java.util.regex.Pattern#compile(String, int) + */ + private final ExecutableElement patternCompile2 = + TreeUtils.getMethod("java.util.regex.Pattern", "compile", 2, processingEnv); + + /** + * Create a new RegexAnnotatedTypeFactory. + * + * @param checker the checker + */ + public RegexAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + Regex.class, PartialRegex.class, + RegexBottom.class, UnknownRegex.class); + } + + @Override + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new RegexTransfer((CFAnalysis) analysis); + } + + /** Returns a new Regex annotation with the given group count. */ + /*package-private*/ AnnotationMirror createRegexAnnotation(int groupCount) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Regex.class); + if (groupCount > 0) { + builder.setValue("value", groupCount); + } + return builder.build(); + } - /** The method that returns the value element of a {@code @Regex} annotation. */ - protected final ExecutableElement regexValueElement = - TreeUtils.getMethod( - "org.checkerframework.checker.regex.qual.Regex", "value", 0, processingEnv); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new RegexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } - /** - * The value method of the PartialRegex qualifier. - * - * @see org.checkerframework.checker.regex.qual.PartialRegex - */ - private final ExecutableElement partialRegexValueElement = - TreeUtils.getMethod(PartialRegex.class, "value", 0, processingEnv); + /** + * A custom qualifier hierarchy for the Regex Checker. This makes a regex annotation a subtype of + * all regex annotations with lower group count values. For example, {@code @Regex(3)} is a + * subtype of {@code @Regex(1)}. All regex annotations are subtypes of {@code @Regex}, which has a + * default value of 0. + */ + private final class RegexQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /** - * The Pattern.compile method. - * - * @see java.util.regex.Pattern#compile(String) - */ - private final ExecutableElement patternCompile = - TreeUtils.getMethod("java.util.regex.Pattern", "compile", 1, processingEnv); + /** Qualifier kind for the @{@link Regex} annotation. */ + private final QualifierKind REGEX_KIND; - /** - * The Pattern.compile method that takes two formal parameters (second one is flags). - * - * @see java.util.regex.Pattern#compile(String, int) - */ - private final ExecutableElement patternCompile2 = - TreeUtils.getMethod("java.util.regex.Pattern", "compile", 2, processingEnv); + /** Qualifier kind for the @{@link PartialRegex} annotation. */ + private final QualifierKind PARTIALREGEX_KIND; /** - * Create a new RegexAnnotatedTypeFactory. + * Creates a RegexQualifierHierarchy from the given classes. * - * @param checker the checker + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils */ - public RegexAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - this.postInit(); + private RegexQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, RegexAnnotatedTypeFactory.this); + REGEX_KIND = getQualifierKind(REGEX); + PARTIALREGEX_KIND = getQualifierKind(PARTIALREGEX); } @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - Regex.class, PartialRegex.class, - RegexBottom.class, UnknownRegex.class); + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == REGEX_KIND && superKind == REGEX_KIND) { + int rhsValue = getRegexValue(subAnno); + int lhsValue = getRegexValue(superAnno); + return lhsValue <= rhsValue; + } else if (subKind == PARTIALREGEX_KIND && superKind == PARTIALREGEX_KIND) { + return AnnotationUtils.areSame(subAnno, superAnno); + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); } @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new RegexTransfer((CFAnalysis) analysis); - } - - /** Returns a new Regex annotation with the given group count. */ - /*package-private*/ AnnotationMirror createRegexAnnotation(int groupCount) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Regex.class); - if (groupCount > 0) { - builder.setValue("value", groupCount); + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { + int value1 = getRegexValue(a1); + int value2 = getRegexValue(a2); + if (value1 < value2) { + return a1; + } else { + return a2; + } + } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return UNKNOWNREGEX; } - return builder.build(); + } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { + return a1; + } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new RegexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** - * A custom qualifier hierarchy for the Regex Checker. This makes a regex annotation a subtype - * of all regex annotations with lower group count values. For example, {@code @Regex(3)} is a - * subtype of {@code @Regex(1)}. All regex annotations are subtypes of {@code @Regex}, which has - * a default value of 0. - */ - private final class RegexQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** Qualifier kind for the @{@link Regex} annotation. */ - private final QualifierKind REGEX_KIND; - - /** Qualifier kind for the @{@link PartialRegex} annotation. */ - private final QualifierKind PARTIALREGEX_KIND; - - /** - * Creates a RegexQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - private RegexQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, RegexAnnotatedTypeFactory.this); - REGEX_KIND = getQualifierKind(REGEX); - PARTIALREGEX_KIND = getQualifierKind(PARTIALREGEX); + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { + int value1 = getRegexValue(a1); + int value2 = getRegexValue(a2); + if (value1 > value2) { + return a1; + } else { + return a2; } - - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == REGEX_KIND && superKind == REGEX_KIND) { - int rhsValue = getRegexValue(subAnno); - int lhsValue = getRegexValue(superAnno); - return lhsValue <= rhsValue; - } else if (subKind == PARTIALREGEX_KIND && superKind == PARTIALREGEX_KIND) { - return AnnotationUtils.areSame(subAnno, superAnno); - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); - } - - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { - int value1 = getRegexValue(a1); - int value2 = getRegexValue(a2); - if (value1 < value2) { - return a1; - } else { - return a2; - } - } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return UNKNOWNREGEX; - } - } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { - return a1; - } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); - } - - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { - int value1 = getRegexValue(a1); - int value2 = getRegexValue(a2); - if (value1 > value2) { - return a1; - } else { - return a2; - } - } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return REGEXBOTTOM; - } - } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { - return a1; - } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); - } - - /** - * Gets the value out of a regex annotation. - * - * @param anno a @Regex annotation - * @return the {@code value} element of the annotation - */ - private int getRegexValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); + } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return REGEXBOTTOM; } + } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { + return a1; + } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); } /** - * Returns the group count value of the given annotation or 0 if there's a problem getting the - * group count value. + * Gets the value out of a regex annotation. * * @param anno a @Regex annotation * @return the {@code value} element of the annotation */ - public int getGroupCount(AnnotationMirror anno) { - if (anno == null) { - return 0; - } - return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); + private int getRegexValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); } - - /** Returns the number of groups in the given regex String. */ - public static int getGroupCount(@Regex String regexp) { - return Pattern.compile(regexp).matcher("").groupCount(); + } + + /** + * Returns the group count value of the given annotation or 0 if there's a problem getting the + * group count value. + * + * @param anno a @Regex annotation + * @return the {@code value} element of the annotation + */ + public int getGroupCount(AnnotationMirror anno) { + if (anno == null) { + return 0; } - - @Override - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - return UNKNOWNREGEX_SET; + return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); + } + + /** Returns the number of groups in the given regex String. */ + public static int getGroupCount(@Regex String regexp) { + return Pattern.compile(regexp).matcher("").groupCount(); + } + + @Override + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + return UNKNOWNREGEX_SET; + } + + @Override + public TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because the PropagationTreeAnnotator types binary + // expressions as lub. + return new ListTreeAnnotator( + new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), + new RegexTreeAnnotator(this), + new RegexPropagationTreeAnnotator(this)); + } + + private static class RegexPropagationTreeAnnotator extends PropagationTreeAnnotator { + + public RegexPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - public TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because the PropagationTreeAnnotator types binary - // expressions as lub. - return new ListTreeAnnotator( - new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), - new RegexTreeAnnotator(this), - new RegexPropagationTreeAnnotator(this)); + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + // Don't call super method which will try to create a LUB + // Even when it is not yet valid: i.e. between a @PolyRegex and a @Regex + return null; } + } - private static class RegexPropagationTreeAnnotator extends PropagationTreeAnnotator { - - public RegexPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + private class RegexTreeAnnotator extends TreeAnnotator { - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - // Don't call super method which will try to create a LUB - // Even when it is not yet valid: i.e. between a @PolyRegex and a @Regex - return null; - } + public RegexTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - private class RegexTreeAnnotator extends TreeAnnotator { - - public RegexTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** + * Case 1: valid regular expression String or char literal. Adds PartialRegex annotation to + * String literals that are not valid regular expressions. + */ + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(REGEX)) { + String regex = null; + if (tree.getKind() == Tree.Kind.STRING_LITERAL) { + regex = (String) tree.getValue(); + } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) { + regex = Character.toString((Character) tree.getValue()); } - - /** - * Case 1: valid regular expression String or char literal. Adds PartialRegex annotation to - * String literals that are not valid regular expressions. - */ - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(REGEX)) { - String regex = null; - if (tree.getKind() == Tree.Kind.STRING_LITERAL) { - regex = (String) tree.getValue(); - } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) { - regex = Character.toString((Character) tree.getValue()); - } - if (regex != null) { - if (RegexUtil.isRegex(regex)) { - int groupCount = getGroupCount(regex); - type.addAnnotation(createRegexAnnotation(groupCount)); - } else { - type.addAnnotation(createPartialRegexAnnotation(regex)); - } - } - } - return super.visitLiteral(tree, type); + if (regex != null) { + if (RegexUtil.isRegex(regex)) { + int groupCount = getGroupCount(regex); + type.addAnnotation(createRegexAnnotation(groupCount)); + } else { + type.addAnnotation(createPartialRegexAnnotation(regex)); + } } + } + return super.visitLiteral(tree, type); + } - /** - * Case 2: concatenation of Regex or PolyRegex String/char literals. Also handles - * concatenation of partial regular expressions. - */ - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(REGEX) && TreeUtils.isStringConcatenation(tree)) { - AnnotatedTypeMirror lExpr = getAnnotatedType(tree.getLeftOperand()); - AnnotatedTypeMirror rExpr = getAnnotatedType(tree.getRightOperand()); - - Integer lGroupCount = getMinimumRegexCount(lExpr); - Integer rGroupCount = getMinimumRegexCount(rExpr); - boolean lExprRE = lGroupCount != null; - boolean rExprRE = rGroupCount != null; - boolean lExprPart = lExpr.hasAnnotation(PartialRegex.class); - boolean rExprPart = rExpr.hasAnnotation(PartialRegex.class); - boolean lExprPoly = lExpr.hasAnnotation(PolyRegex.class); - boolean rExprPoly = rExpr.hasAnnotation(PolyRegex.class); - - if (lExprRE && rExprRE) { - // Remove current @Regex annotation and add a new one with the correct group - // count value. - type.replaceAnnotation(createRegexAnnotation(lGroupCount + rGroupCount)); - } else if ((lExprPoly && rExprPoly) - || (lExprPoly && rExprRE) - || (lExprRE && rExprPoly)) { - type.addAnnotation(POLYREGEX); - } else if (lExprPart && rExprPart) { - String lRegex = getPartialRegexValue(lExpr); - String rRegex = getPartialRegexValue(rExpr); - String concat = lRegex + rRegex; - if (RegexUtil.isRegex(concat)) { - int groupCount = getGroupCount(concat); - type.addAnnotation(createRegexAnnotation(groupCount)); - } else { - type.addAnnotation(createPartialRegexAnnotation(concat)); - } - } else if (lExprRE && rExprPart) { - String rRegex = getPartialRegexValue(rExpr); - String concat = "e" + rRegex; - type.addAnnotation(createPartialRegexAnnotation(concat)); - } else if (lExprPart && rExprRE) { - String lRegex = getPartialRegexValue(lExpr); - String concat = lRegex + "e"; - type.addAnnotation(createPartialRegexAnnotation(concat)); - } - } - return null; // super.visitBinary(tree, type); + /** + * Case 2: concatenation of Regex or PolyRegex String/char literals. Also handles concatenation + * of partial regular expressions. + */ + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(REGEX) && TreeUtils.isStringConcatenation(tree)) { + AnnotatedTypeMirror lExpr = getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rExpr = getAnnotatedType(tree.getRightOperand()); + + Integer lGroupCount = getMinimumRegexCount(lExpr); + Integer rGroupCount = getMinimumRegexCount(rExpr); + boolean lExprRE = lGroupCount != null; + boolean rExprRE = rGroupCount != null; + boolean lExprPart = lExpr.hasAnnotation(PartialRegex.class); + boolean rExprPart = rExpr.hasAnnotation(PartialRegex.class); + boolean lExprPoly = lExpr.hasAnnotation(PolyRegex.class); + boolean rExprPoly = rExpr.hasAnnotation(PolyRegex.class); + + if (lExprRE && rExprRE) { + // Remove current @Regex annotation and add a new one with the correct group + // count value. + type.replaceAnnotation(createRegexAnnotation(lGroupCount + rGroupCount)); + } else if ((lExprPoly && rExprPoly) || (lExprPoly && rExprRE) || (lExprRE && rExprPoly)) { + type.addAnnotation(POLYREGEX); + } else if (lExprPart && rExprPart) { + String lRegex = getPartialRegexValue(lExpr); + String rRegex = getPartialRegexValue(rExpr); + String concat = lRegex + rRegex; + if (RegexUtil.isRegex(concat)) { + int groupCount = getGroupCount(concat); + type.addAnnotation(createRegexAnnotation(groupCount)); + } else { + type.addAnnotation(createPartialRegexAnnotation(concat)); + } + } else if (lExprRE && rExprPart) { + String rRegex = getPartialRegexValue(rExpr); + String concat = "e" + rRegex; + type.addAnnotation(createPartialRegexAnnotation(concat)); + } else if (lExprPart && rExprRE) { + String lRegex = getPartialRegexValue(lExpr); + String concat = lRegex + "e"; + type.addAnnotation(createPartialRegexAnnotation(concat)); } + } + return null; // super.visitBinary(tree, type); + } - /** Case 2: Also handle compound String concatenation. */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - AnnotatedTypeMirror rhs = getAnnotatedType(tree.getExpression()); - AnnotatedTypeMirror lhs = getAnnotatedType(tree.getVariable()); - - Integer lhsRegexCount = getMinimumRegexCount(lhs); - Integer rhsRegexCount = getMinimumRegexCount(rhs); - - if (lhsRegexCount != null && rhsRegexCount != null) { - int lCount = getGroupCount(lhs.getAnnotation(Regex.class)); - int rCount = getGroupCount(rhs.getAnnotation(Regex.class)); - type.removeAnnotationInHierarchy(REGEX); - type.addAnnotation(createRegexAnnotation(lCount + rCount)); - } - } - return null; // super.visitCompoundAssignment(tree, type); + /** Case 2: Also handle compound String concatenation. */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + AnnotatedTypeMirror rhs = getAnnotatedType(tree.getExpression()); + AnnotatedTypeMirror lhs = getAnnotatedType(tree.getVariable()); + + Integer lhsRegexCount = getMinimumRegexCount(lhs); + Integer rhsRegexCount = getMinimumRegexCount(rhs); + + if (lhsRegexCount != null && rhsRegexCount != null) { + int lCount = getGroupCount(lhs.getAnnotation(Regex.class)); + int rCount = getGroupCount(rhs.getAnnotation(Regex.class)); + type.removeAnnotationInHierarchy(REGEX); + type.addAnnotation(createRegexAnnotation(lCount + rCount)); } + } + return null; // super.visitCompoundAssignment(tree, type); + } - /** - * Case 3: For a call to Pattern.compile, add an annotation to the return type that has the - * same group count value as the parameter. For calls to {@code asRegex(String, int)} change - * the return type to have the same group count as the value of the second argument. - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isMethodInvocation(tree, patternCompile, processingEnv) - || TreeUtils.isMethodInvocation(tree, patternCompile2, processingEnv)) { - ExpressionTree arg0 = tree.getArguments().get(0); - - AnnotatedTypeMirror argType = getAnnotatedType(arg0); - Integer regexCount = getMinimumRegexCount(argType); - AnnotationMirror bottomAnno = - getAnnotatedType(arg0).getAnnotation(RegexBottom.class); - - if (regexCount != null) { - // Remove current @Regex annotation... - // ...and add a new one with the correct group count value. - type.replaceAnnotation(createRegexAnnotation(regexCount)); - } else if (bottomAnno != null) { - type.replaceAnnotation( - AnnotationBuilder.fromClass(elements, RegexBottom.class)); - } - } - return super.visitMethodInvocation(tree, type); + /** + * Case 3: For a call to Pattern.compile, add an annotation to the return type that has the same + * group count value as the parameter. For calls to {@code asRegex(String, int)} change the + * return type to have the same group count as the value of the second argument. + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isMethodInvocation(tree, patternCompile, processingEnv) + || TreeUtils.isMethodInvocation(tree, patternCompile2, processingEnv)) { + ExpressionTree arg0 = tree.getArguments().get(0); + + AnnotatedTypeMirror argType = getAnnotatedType(arg0); + Integer regexCount = getMinimumRegexCount(argType); + AnnotationMirror bottomAnno = getAnnotatedType(arg0).getAnnotation(RegexBottom.class); + + if (regexCount != null) { + // Remove current @Regex annotation... + // ...and add a new one with the correct group count value. + type.replaceAnnotation(createRegexAnnotation(regexCount)); + } else if (bottomAnno != null) { + type.replaceAnnotation(AnnotationBuilder.fromClass(elements, RegexBottom.class)); } + } + return super.visitMethodInvocation(tree, type); + } - /** Returns a new PartialRegex annotation with the given partial regular expression. */ - private AnnotationMirror createPartialRegexAnnotation(String partial) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, PartialRegex.class); - builder.setValue("value", partial); - return builder.build(); - } + /** Returns a new PartialRegex annotation with the given partial regular expression. */ + private AnnotationMirror createPartialRegexAnnotation(String partial) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, PartialRegex.class); + builder.setValue("value", partial); + return builder.build(); + } - /** - * Returns the {@code value} element of a {@code @PartialRegex} annotation, if there is one - * in {@code type}. - * - * @param type a type - * @return the {@code value} element of a {@code @PartialRegex} annotation, or "" if none - */ - private String getPartialRegexValue(AnnotatedTypeMirror type) { - AnnotationMirror partialRegexAnno = type.getAnnotation(PartialRegex.class); - if (partialRegexAnno == null) { - return ""; - } - return AnnotationUtils.getElementValue( - partialRegexAnno, partialRegexValueElement, String.class, ""); - } + /** + * Returns the {@code value} element of a {@code @PartialRegex} annotation, if there is one in + * {@code type}. + * + * @param type a type + * @return the {@code value} element of a {@code @PartialRegex} annotation, or "" if none + */ + private String getPartialRegexValue(AnnotatedTypeMirror type) { + AnnotationMirror partialRegexAnno = type.getAnnotation(PartialRegex.class); + if (partialRegexAnno == null) { + return ""; + } + return AnnotationUtils.getElementValue( + partialRegexAnno, partialRegexValueElement, String.class, ""); + } - /** - * Returns the value of the Regex annotation on the given type or NULL if there is no Regex - * annotation. If type is a TYPEVAR, WILDCARD, or INTERSECTION type, visit first their - * primary annotation then visit their upper bounds to get the Regex annotation. The method - * gets "minimum" regex count because, depending on the bounds of a typevar or wildcard, the - * actual type may have more than the upper bound's count. - * - * @param type type that may carry a Regex annotation - * @return the Integer value of the Regex annotation (0 if no value exists) - */ - private @Nullable Integer getMinimumRegexCount(AnnotatedTypeMirror type) { - AnnotationMirror primaryRegexAnno = type.getAnnotation(Regex.class); - if (primaryRegexAnno == null) { - switch (type.getKind()) { - case TYPEVAR: - return getMinimumRegexCount(((AnnotatedTypeVariable) type).getUpperBound()); - - case WILDCARD: - return getMinimumRegexCount( - ((AnnotatedWildcardType) type).getExtendsBound()); - - case INTERSECTION: - Integer maxBound = null; - for (AnnotatedTypeMirror bound : - ((AnnotatedIntersectionType) type).getBounds()) { - Integer boundRegexNum = getMinimumRegexCount(bound); - if (boundRegexNum != null) { - if (maxBound == null || boundRegexNum > maxBound) { - maxBound = boundRegexNum; - } - } - } - return maxBound; - default: - // Nothing to do for other cases. + /** + * Returns the value of the Regex annotation on the given type or NULL if there is no Regex + * annotation. If type is a TYPEVAR, WILDCARD, or INTERSECTION type, visit first their primary + * annotation then visit their upper bounds to get the Regex annotation. The method gets + * "minimum" regex count because, depending on the bounds of a typevar or wildcard, the actual + * type may have more than the upper bound's count. + * + * @param type type that may carry a Regex annotation + * @return the Integer value of the Regex annotation (0 if no value exists) + */ + private @Nullable Integer getMinimumRegexCount(AnnotatedTypeMirror type) { + AnnotationMirror primaryRegexAnno = type.getAnnotation(Regex.class); + if (primaryRegexAnno == null) { + switch (type.getKind()) { + case TYPEVAR: + return getMinimumRegexCount(((AnnotatedTypeVariable) type).getUpperBound()); + + case WILDCARD: + return getMinimumRegexCount(((AnnotatedWildcardType) type).getExtendsBound()); + + case INTERSECTION: + Integer maxBound = null; + for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) type).getBounds()) { + Integer boundRegexNum = getMinimumRegexCount(bound); + if (boundRegexNum != null) { + if (maxBound == null || boundRegexNum > maxBound) { + maxBound = boundRegexNum; } - - return null; + } } - - return getGroupCount(primaryRegexAnno); + return maxBound; + default: + // Nothing to do for other cases. } - // This won't work correctly until flow sensitivity is supported by the - // the Regex Checker. For example: - // - // char @Regex [] arr = {'r', 'e'}; - // arr[0] = '('; // type is still "char @Regex []", but this is no longer correct - // - // There are associated tests in tests/regex/Simple.java:testCharArrays - // that can be uncommented when this is uncommented. - // /** - // * Case 4: a char array that as a String is a valid regular expression. - // */ - // @Override - // public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - // boolean isCharArray = ((ArrayType) type.getUnderlyingType()) - // .getComponentType().getKind() == TypeKind.CHAR; - // if (isCharArray && tree.getInitializers() != null) { - // List initializers = tree.getInitializers(); - // StringBuilder charArray = new StringBuilder(); - // boolean allLiterals = true; - // for (int i = 0; allLiterals && i < initializers.size(); i++) { - // ExpressionTree e = initializers.get(i); - // if (e.getKind() == Tree.Kind.CHAR_LITERAL) { - // charArray.append(((LiteralTree) e).getValue()); - // } else if (getAnnotatedType(e).hasAnnotation(Regex.class)) { - // // if there's an @Regex char in the array then substitute - // // it with a . - // charArray.append('.'); - // } else { - // allLiterals = false; - // } - // } - // if (allLiterals && RegexUtil.isRegex(charArray.toString())) { - // type.addAnnotation(Regex.class); - // } - // } - // return super.visitNewArray(tree, type); - // } + return null; + } + + return getGroupCount(primaryRegexAnno); } + + // This won't work correctly until flow sensitivity is supported by the + // the Regex Checker. For example: + // + // char @Regex [] arr = {'r', 'e'}; + // arr[0] = '('; // type is still "char @Regex []", but this is no longer correct + // + // There are associated tests in tests/regex/Simple.java:testCharArrays + // that can be uncommented when this is uncommented. + // /** + // * Case 4: a char array that as a String is a valid regular expression. + // */ + // @Override + // public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + // boolean isCharArray = ((ArrayType) type.getUnderlyingType()) + // .getComponentType().getKind() == TypeKind.CHAR; + // if (isCharArray && tree.getInitializers() != null) { + // List initializers = tree.getInitializers(); + // StringBuilder charArray = new StringBuilder(); + // boolean allLiterals = true; + // for (int i = 0; allLiterals && i < initializers.size(); i++) { + // ExpressionTree e = initializers.get(i); + // if (e.getKind() == Tree.Kind.CHAR_LITERAL) { + // charArray.append(((LiteralTree) e).getValue()); + // } else if (getAnnotatedType(e).hasAnnotation(Regex.class)) { + // // if there's an @Regex char in the array then substitute + // // it with a . + // charArray.append('.'); + // } else { + // allLiterals = false; + // } + // } + // if (allLiterals && RegexUtil.isRegex(charArray.toString())) { + // type.addAnnotation(Regex.class); + // } + // } + // return super.visitNewArray(tree, type); + // } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java index 822b687e5fe..2b1f72579d8 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java @@ -1,14 +1,13 @@ package org.checkerframework.checker.regex; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.qual.StubFiles; -import java.util.regex.MatchResult; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * A type-checker plug-in for the {@link Regex} qualifier that finds syntactically invalid regular * expressions. @@ -17,12 +16,12 @@ */ @StubFiles("apache-xerces.astub") @RelevantJavaTypes({ - CharSequence.class, - // javax.swing.text.Segment.class - char.class, - Character.class, - Pattern.class, - Matcher.class, - MatchResult.class + CharSequence.class, + // javax.swing.text.Segment.class + char.class, + Character.class, + Pattern.class, + Matcher.class, + MatchResult.class }) public class RegexChecker extends BaseTypeChecker {} diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java index 4cf6e3cd40e..ce9d1dd72e9 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java @@ -1,5 +1,7 @@ package org.checkerframework.checker.regex; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -22,209 +24,201 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; - /** The transfer function for the Regex Checker. */ public class RegexTransfer extends CFTransfer { - // isRegex and asRegex are tested as signatures (string name plus formal parameters), not - // ExecutableElement, because they exist in two packages: - // org.checkerframework.checker.regex.util.RegexUtil.isRegex(String,int) - // org.plumelib.util.RegexUtil.isRegex(String,int) - // and org.plumelib.util might not be on the classpath. - private static final String IS_REGEX_METHOD_NAME = "isRegex"; - private static final String AS_REGEX_METHOD_NAME = "asRegex"; - - /** The MatchResult.groupCount() method. */ - private final ExecutableElement matchResultgroupCount; - - /** Create the transfer function for the Regex Checker. */ - public RegexTransfer(CFAbstractAnalysis analysis) { - super(analysis); - this.matchResultgroupCount = - TreeUtils.getMethod( - "java.util.regex.MatchResult", - "groupCount", - 0, - analysis.getTypeFactory().getProcessingEnv()); + // isRegex and asRegex are tested as signatures (string name plus formal parameters), not + // ExecutableElement, because they exist in two packages: + // org.checkerframework.checker.regex.util.RegexUtil.isRegex(String,int) + // org.plumelib.util.RegexUtil.isRegex(String,int) + // and org.plumelib.util might not be on the classpath. + private static final String IS_REGEX_METHOD_NAME = "isRegex"; + private static final String AS_REGEX_METHOD_NAME = "asRegex"; + + /** The MatchResult.groupCount() method. */ + private final ExecutableElement matchResultgroupCount; + + /** Create the transfer function for the Regex Checker. */ + public RegexTransfer(CFAbstractAnalysis analysis) { + super(analysis); + this.matchResultgroupCount = + TreeUtils.getMethod( + "java.util.regex.MatchResult", + "groupCount", + 0, + analysis.getTypeFactory().getProcessingEnv()); + } + + // TODO: These are special cases for isRegex(String, int) and asRegex(String, int). They should + // be replaced by adding an @EnsuresQualifierIf annotation that supports specifying attributes. + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = super.visitMethodInvocation(n, in); + + // refine result for some helper methods + MethodAccessNode target = n.getTarget(); + ExecutableElement method = target.getMethod(); + Node receiver = target.getReceiver(); + if (receiver instanceof ClassNameNode) { + ClassNameNode cnn = (ClassNameNode) receiver; + String receiverName = cnn.getElement().toString(); + if (isRegexUtil(receiverName)) { + result = handleRegexUtil(n, method, result); + } } - - // TODO: These are special cases for isRegex(String, int) and asRegex(String, int). They should - // be replaced by adding an @EnsuresQualifierIf annotation that supports specifying attributes. - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = super.visitMethodInvocation(n, in); - - // refine result for some helper methods - MethodAccessNode target = n.getTarget(); - ExecutableElement method = target.getMethod(); - Node receiver = target.getReceiver(); - if (receiver instanceof ClassNameNode) { - ClassNameNode cnn = (ClassNameNode) receiver; - String receiverName = cnn.getElement().toString(); - if (isRegexUtil(receiverName)) { - result = handleRegexUtil(n, method, result); - } - } - return result; + return result; + } + + private TransferResult handleRegexUtil( + MethodInvocationNode n, ExecutableElement method, TransferResult result) { + RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); + if (ElementUtils.matchesElement(method, IS_REGEX_METHOD_NAME, String.class, int.class)) { + // RegexUtil.isRegex(s, groups) method + // (No special case is needed for isRegex(String) because of + // the annotation on that method's definition.) + + CFStore thenStore = result.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + JavaExpression firstParam = JavaExpression.fromNode(n.getArgument(0)); + + // add annotation with correct group count (if possible, + // regex annotation without count otherwise) + Node count = n.getArgument(1); + int groupCount; + if (count instanceof IntegerLiteralNode) { + IntegerLiteralNode iln = (IntegerLiteralNode) count; + groupCount = iln.getValue(); + } else { + groupCount = 0; + } + AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); + thenStore.insertValue(firstParam, regexAnnotation); + return newResult; + } else if (ElementUtils.matchesElement(method, AS_REGEX_METHOD_NAME, String.class, int.class)) { + // RegexUtil.asRegex(s, groups) method + // (No special case is needed for asRegex(String) because of + // the annotation on that method's definition.) + + // add annotation with correct group count (if possible, + // regex annotation without count otherwise) + AnnotationMirror regexAnnotation; + Node count = n.getArgument(1); + int groupCount; + if (count instanceof IntegerLiteralNode) { + IntegerLiteralNode iln = (IntegerLiteralNode) count; + groupCount = iln.getValue(); + } else { + groupCount = 0; + } + regexAnnotation = factory.createRegexAnnotation(groupCount); + + CFValue newResultValue = + analysis.createSingleAnnotationValue( + regexAnnotation, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); } - - private TransferResult handleRegexUtil( - MethodInvocationNode n, - ExecutableElement method, - TransferResult result) { - RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); - if (ElementUtils.matchesElement(method, IS_REGEX_METHOD_NAME, String.class, int.class)) { - // RegexUtil.isRegex(s, groups) method - // (No special case is needed for isRegex(String) because of - // the annotation on that method's definition.) - - CFStore thenStore = result.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - JavaExpression firstParam = JavaExpression.fromNode(n.getArgument(0)); - - // add annotation with correct group count (if possible, - // regex annotation without count otherwise) - Node count = n.getArgument(1); - int groupCount; - if (count instanceof IntegerLiteralNode) { - IntegerLiteralNode iln = (IntegerLiteralNode) count; - groupCount = iln.getValue(); - } else { - groupCount = 0; - } - AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); - thenStore.insertValue(firstParam, regexAnnotation); - return newResult; - } else if (ElementUtils.matchesElement( - method, AS_REGEX_METHOD_NAME, String.class, int.class)) { - // RegexUtil.asRegex(s, groups) method - // (No special case is needed for asRegex(String) because of - // the annotation on that method's definition.) - - // add annotation with correct group count (if possible, - // regex annotation without count otherwise) - AnnotationMirror regexAnnotation; - Node count = n.getArgument(1); - int groupCount; - if (count instanceof IntegerLiteralNode) { - IntegerLiteralNode iln = (IntegerLiteralNode) count; - groupCount = iln.getValue(); - } else { - groupCount = 0; - } - regexAnnotation = factory.createRegexAnnotation(groupCount); - - CFValue newResultValue = - analysis.createSingleAnnotationValue( - regexAnnotation, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - return result; + return result; + } + + @Override + public TransferResult visitLessThan( + LessThanNode n, TransferInput in) { + // Look for: constant < mat.groupCount() + // Make mat be @Regex(constant + 1) + TransferResult res = super.visitLessThan(n, in); + return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), false, res); + } + + @Override + public TransferResult visitLessThanOrEqual( + LessThanOrEqualNode n, TransferInput in) { + // Look for: constant <= mat.groupCount() + // Make mat be @Regex(constant) + TransferResult res = super.visitLessThanOrEqual(n, in); + return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), true, res); + } + + @Override + public TransferResult visitGreaterThan( + GreaterThanNode n, TransferInput in) { + + TransferResult res = super.visitGreaterThan(n, in); + return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), false, res); + } + + @Override + public TransferResult visitGreaterThanOrEqual( + GreaterThanOrEqualNode n, TransferInput in) { + // Look for: mat.groupCount() >= constant + // Make mat be @Regex(constant) + TransferResult res = super.visitGreaterThanOrEqual(n, in); + return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), true, res); + } + + /** + * See whether possibleMatcher is a call of groupCount on a Matcher and possibleConstant is a + * constant. If so, annotate the matcher as constant + 1 if !isAlsoEqual constant if isAlsoEqual + * + * @param possibleMatcher the Node that might be a call of Matcher.groupCount() + * @param possibleConstant the Node that might be a constant + * @param isAlsoEqual whether the comparison operation is strict or reflexive + * @param resultIn the TransferResult + * @return the possibly refined output TransferResult + */ + private TransferResult handleMatcherGroupCount( + Node possibleMatcher, + Node possibleConstant, + boolean isAlsoEqual, + TransferResult resultIn) { + if (!(possibleMatcher instanceof MethodInvocationNode)) { + return resultIn; } - - @Override - public TransferResult visitLessThan( - LessThanNode n, TransferInput in) { - // Look for: constant < mat.groupCount() - // Make mat be @Regex(constant + 1) - TransferResult res = super.visitLessThan(n, in); - return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), false, res); + if (!(possibleConstant instanceof IntegerLiteralNode)) { + return resultIn; } - @Override - public TransferResult visitLessThanOrEqual( - LessThanOrEqualNode n, TransferInput in) { - // Look for: constant <= mat.groupCount() - // Make mat be @Regex(constant) - TransferResult res = super.visitLessThanOrEqual(n, in); - return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), true, res); + if (!NodeUtils.isMethodInvocation( + possibleMatcher, matchResultgroupCount, analysis.getTypeFactory().getProcessingEnv())) { + return resultIn; } - @Override - public TransferResult visitGreaterThan( - GreaterThanNode n, TransferInput in) { - - TransferResult res = super.visitGreaterThan(n, in); - return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), false, res); - } + MethodAccessNode methodAccessNode = ((MethodInvocationNode) possibleMatcher).getTarget(); + Node receiver = methodAccessNode.getReceiver(); - @Override - public TransferResult visitGreaterThanOrEqual( - GreaterThanOrEqualNode n, TransferInput in) { - // Look for: mat.groupCount() >= constant - // Make mat be @Regex(constant) - TransferResult res = super.visitGreaterThanOrEqual(n, in); - return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), true, res); - } + JavaExpression matcherReceiver = JavaExpression.fromNode(receiver); - /** - * See whether possibleMatcher is a call of groupCount on a Matcher and possibleConstant is a - * constant. If so, annotate the matcher as constant + 1 if !isAlsoEqual constant if isAlsoEqual - * - * @param possibleMatcher the Node that might be a call of Matcher.groupCount() - * @param possibleConstant the Node that might be a constant - * @param isAlsoEqual whether the comparison operation is strict or reflexive - * @param resultIn the TransferResult - * @return the possibly refined output TransferResult - */ - private TransferResult handleMatcherGroupCount( - Node possibleMatcher, - Node possibleConstant, - boolean isAlsoEqual, - TransferResult resultIn) { - if (!(possibleMatcher instanceof MethodInvocationNode)) { - return resultIn; - } - if (!(possibleConstant instanceof IntegerLiteralNode)) { - return resultIn; - } - - if (!NodeUtils.isMethodInvocation( - possibleMatcher, - matchResultgroupCount, - analysis.getTypeFactory().getProcessingEnv())) { - return resultIn; - } - - MethodAccessNode methodAccessNode = ((MethodInvocationNode) possibleMatcher).getTarget(); - Node receiver = methodAccessNode.getReceiver(); - - JavaExpression matcherReceiver = JavaExpression.fromNode(receiver); - - IntegerLiteralNode iln = (IntegerLiteralNode) possibleConstant; - int groupCount; - if (isAlsoEqual) { - groupCount = iln.getValue(); - } else { - groupCount = iln.getValue() + 1; - } - - CFStore thenStore = resultIn.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(resultIn.getResultValue(), thenStore, elseStore); - RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); - - AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); - thenStore.insertValue(matcherReceiver, regexAnnotation); - - return newResult; + IntegerLiteralNode iln = (IntegerLiteralNode) possibleConstant; + int groupCount; + if (isAlsoEqual) { + groupCount = iln.getValue(); + } else { + groupCount = iln.getValue() + 1; } - /** - * Returns true if the given receiver is a class named "RegexUtil". Examples of such classes are - * org.checkerframework.checker.regex.util.RegexUtil and org.plumelib.util.RegexUtil, and the - * user might copy one into their own project. - * - * @param receiver some string - * @return true if the given receiver is a class named "RegexUtil" - */ - private boolean isRegexUtil(String receiver) { - return receiver.equals("RegexUtil") || receiver.endsWith(".RegexUtil"); - } + CFStore thenStore = resultIn.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(resultIn.getResultValue(), thenStore, elseStore); + RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); + + AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); + thenStore.insertValue(matcherReceiver, regexAnnotation); + + return newResult; + } + + /** + * Returns true if the given receiver is a class named "RegexUtil". Examples of such classes are + * org.checkerframework.checker.regex.util.RegexUtil and org.plumelib.util.RegexUtil, and the user + * might copy one into their own project. + * + * @param receiver some string + * @return true if the given receiver is a class named "RegexUtil" + */ + private boolean isRegexUtil(String receiver) { + return receiver.equals("RegexUtil") || receiver.endsWith(".RegexUtil"); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java index cbe60cddeca..23e9f55cda6 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java @@ -5,17 +5,15 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.TreeUtils; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; - /** * A type-checking visitor for the Regex type system. * @@ -32,96 +30,92 @@ */ public class RegexVisitor extends BaseTypeVisitor { - /** The method java.util.regex.MatchResult.end(int). */ - private final ExecutableElement matchResultEndInt; + /** The method java.util.regex.MatchResult.end(int). */ + private final ExecutableElement matchResultEndInt; - /** The method java.util.regex.MatchResult.group(int). */ - private final ExecutableElement matchResultGroupInt; + /** The method java.util.regex.MatchResult.group(int). */ + private final ExecutableElement matchResultGroupInt; - /** The method java.util.regex.MatchResult.start(int). */ - private final ExecutableElement matchResultStartInt; + /** The method java.util.regex.MatchResult.start(int). */ + private final ExecutableElement matchResultStartInt; - /** The method java.util.regex.Pattern.compile. */ - private final ExecutableElement patternCompile; + /** The method java.util.regex.Pattern.compile. */ + private final ExecutableElement patternCompile; - /** The field java.util.regex.Pattern.LITERAL. */ - private final VariableElement patternLiteral; + /** The field java.util.regex.Pattern.LITERAL. */ + private final VariableElement patternLiteral; - /** - * Create a RegexVisitor. - * - * @param checker the associated RegexChecker - */ - public RegexVisitor(BaseTypeChecker checker) { - super(checker); - ProcessingEnvironment env = checker.getProcessingEnvironment(); - this.matchResultEndInt = - TreeUtils.getMethod("java.util.regex.MatchResult", "end", env, "int"); - this.matchResultGroupInt = - TreeUtils.getMethod("java.util.regex.MatchResult", "group", env, "int"); - this.matchResultStartInt = - TreeUtils.getMethod("java.util.regex.MatchResult", "start", env, "int"); - this.patternCompile = - TreeUtils.getMethod( - "java.util.regex.Pattern", "compile", env, "java.lang.String", "int"); - this.patternLiteral = TreeUtils.getField("java.util.regex.Pattern", "LITERAL", env); - } + /** + * Create a RegexVisitor. + * + * @param checker the associated RegexChecker + */ + public RegexVisitor(BaseTypeChecker checker) { + super(checker); + ProcessingEnvironment env = checker.getProcessingEnvironment(); + this.matchResultEndInt = TreeUtils.getMethod("java.util.regex.MatchResult", "end", env, "int"); + this.matchResultGroupInt = + TreeUtils.getMethod("java.util.regex.MatchResult", "group", env, "int"); + this.matchResultStartInt = + TreeUtils.getMethod("java.util.regex.MatchResult", "start", env, "int"); + this.patternCompile = + TreeUtils.getMethod("java.util.regex.Pattern", "compile", env, "java.lang.String", "int"); + this.patternLiteral = TreeUtils.getField("java.util.regex.Pattern", "LITERAL", env); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - if (TreeUtils.isMethodInvocation(tree, patternCompile, env)) { - /* - * Case 1: Don't require a Regex annotation on the String argument to Pattern.compile if the - * Pattern.LITERAL flag is passed. - */ - ExpressionTree flagParam = tree.getArguments().get(1); - if (flagParam.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree memSelect = (MemberSelectTree) flagParam; - if (TreeUtils.isSpecificFieldAccess(memSelect, patternLiteral)) { - // This is a call to Pattern.compile with the Pattern.LITERAL flag so the first - // parameter doesn't need to be a @Regex String. Don't call the super method to - // skip checking if the first parameter is a @Regex String, but make sure to - // still recurse on all of the different parts of the method call. - Void r = scan(tree.getTypeArguments(), p); - r = reduce(scan(tree.getMethodSelect(), p), r); - r = reduce(scan(tree.getArguments(), p), r); - return r; - } - } - } else if (TreeUtils.isMethodInvocation(tree, matchResultEndInt, env) - || TreeUtils.isMethodInvocation(tree, matchResultGroupInt, env) - || TreeUtils.isMethodInvocation(tree, matchResultStartInt, env)) { - // Case 2: Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code - // MatchResult.group} to ensure that a valid group number is passed. - ExpressionTree group = tree.getArguments().get(0); - if (group.getKind() == Tree.Kind.INT_LITERAL) { - LiteralTree literal = (LiteralTree) group; - int paramGroups = (Integer) literal.getValue(); - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - if (receiver == null) { - // When checking implementations of java.util.regex.MatcherResult, calls to - // group (and other methods) don't have a receiver tree. So, just do the - // regular checking. - // Verifying an implementation of a subclass of MatcherResult is out of the - // scope of this checker. - return super.visitMethodInvocation(tree, p); - } - int annoGroups = 0; - AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(receiver); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + if (TreeUtils.isMethodInvocation(tree, patternCompile, env)) { + /* + * Case 1: Don't require a Regex annotation on the String argument to Pattern.compile if the + * Pattern.LITERAL flag is passed. + */ + ExpressionTree flagParam = tree.getArguments().get(1); + if (flagParam.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree memSelect = (MemberSelectTree) flagParam; + if (TreeUtils.isSpecificFieldAccess(memSelect, patternLiteral)) { + // This is a call to Pattern.compile with the Pattern.LITERAL flag so the first + // parameter doesn't need to be a @Regex String. Don't call the super method to + // skip checking if the first parameter is a @Regex String, but make sure to + // still recurse on all of the different parts of the method call. + Void r = scan(tree.getTypeArguments(), p); + r = reduce(scan(tree.getMethodSelect(), p), r); + r = reduce(scan(tree.getArguments(), p), r); + return r; + } + } + } else if (TreeUtils.isMethodInvocation(tree, matchResultEndInt, env) + || TreeUtils.isMethodInvocation(tree, matchResultGroupInt, env) + || TreeUtils.isMethodInvocation(tree, matchResultStartInt, env)) { + // Case 2: Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code + // MatchResult.group} to ensure that a valid group number is passed. + ExpressionTree group = tree.getArguments().get(0); + if (group.getKind() == Tree.Kind.INT_LITERAL) { + LiteralTree literal = (LiteralTree) group; + int paramGroups = (Integer) literal.getValue(); + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + if (receiver == null) { + // When checking implementations of java.util.regex.MatcherResult, calls to + // group (and other methods) don't have a receiver tree. So, just do the + // regular checking. + // Verifying an implementation of a subclass of MatcherResult is out of the + // scope of this checker. + return super.visitMethodInvocation(tree, p); + } + int annoGroups = 0; + AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(receiver); - if (receiverType != null && receiverType.hasAnnotation(Regex.class)) { - annoGroups = - atypeFactory.getGroupCount(receiverType.getAnnotation(Regex.class)); - } - if (paramGroups > annoGroups) { - checker.reportError( - group, "group.count.invalid", paramGroups, annoGroups, receiver); - } - } else { - checker.reportWarning(group, "group.count.unknown"); - } + if (receiverType != null && receiverType.hasAnnotation(Regex.class)) { + annoGroups = atypeFactory.getGroupCount(receiverType.getAnnotation(Regex.class)); + } + if (paramGroups > annoGroups) { + checker.reportError(group, "group.count.invalid", paramGroups, annoGroups, receiver); } - return super.visitMethodInvocation(tree, p); + } else { + checker.reportWarning(group, "group.count.unknown"); + } } + return super.visitMethodInvocation(tree, p); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java index 02830312a73..9a792884250 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java @@ -10,7 +10,33 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.calledmethods.qual.CalledMethods; import org.checkerframework.checker.mustcall.CreatesMustCallForToJavaExpression; import org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory; @@ -58,35 +84,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.IPair; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * An analyzer that checks consistency of {@link MustCall} and {@link CalledMethods} types, thereby * detecting resource leaks. For any expression e the analyzer ensures that when e @@ -145,2507 +142,2442 @@ /*package-private*/ class MustCallConsistencyAnalyzer { - /** True if errors related to static owning fields should be suppressed. */ - private final boolean permitStaticOwning; - - /** True if errors related to field initialization should be suppressed. */ - private final boolean permitInitializationLeak; - - /** - * Aliases about which the checker has already reported about a resource leak, to avoid - * duplicate reports. - */ - private final Set reportedErrorAliases = new HashSet<>(); - - /** - * The type factory for the Resource Leak Checker, which is used to get called methods types and - * to access the Must Call Checker. - */ - private final ResourceLeakAnnotatedTypeFactory typeFactory; + /** True if errors related to static owning fields should be suppressed. */ + private final boolean permitStaticOwning; + + /** True if errors related to field initialization should be suppressed. */ + private final boolean permitInitializationLeak; + + /** + * Aliases about which the checker has already reported about a resource leak, to avoid duplicate + * reports. + */ + private final Set reportedErrorAliases = new HashSet<>(); + + /** + * The type factory for the Resource Leak Checker, which is used to get called methods types and + * to access the Must Call Checker. + */ + private final ResourceLeakAnnotatedTypeFactory typeFactory; + + /** + * A cache for the result of calling {@code ResourceLeakAnnotatedTypeFactory.getStoreAfter()} on a + * node. The cache prevents repeatedly computing least upper bounds on stores + */ + private final IdentityHashMap cmStoreAfter = new IdentityHashMap<>(); + + /** + * A cache for the result of calling {@code MustCallAnnotatedTypeFactory.getStoreAfter()} on a + * node. The cache prevents repeatedly computing least upper bounds on stores + */ + private final IdentityHashMap mcStoreAfter = new IdentityHashMap<>(); + + /** The Resource Leak Checker, used to issue errors. */ + private final ResourceLeakChecker checker; + + /** The analysis from the Resource Leak Checker, used to get input stores based on CFG blocks. */ + private final ResourceLeakAnalysis analysis; + + /** True if -AnoLightweightOwnership was passed on the command line. */ + private final boolean noLightweightOwnership; + + /** True if -AcountMustCall was passed on the command line. */ + private final boolean countMustCall; + + /** A description for how a method might exit. */ + /*package-private*/ enum MethodExitKind { + + /** The method exits normally by returning. */ + NORMAL_RETURN, + + /** The method exits by throwing an exception. */ + EXCEPTIONAL_EXIT; + + /** An immutable set containing all possible ways for a method to exit. */ + public static final Set ALL = + ImmutableSet.copyOf(EnumSet.allOf(MethodExitKind.class)); + } + + /** + * An Obligation is a dataflow fact: a set of resource aliases and when those resources need to be + * cleaned up. Abstractly, each Obligation represents a resource for which the analyzed program + * might have a must-call obligation. Each Obligation is a pair of a set of resource aliases and + * their must-call obligation. Must-call obligations are tracked by the {@link MustCallChecker} + * and are accessed by looking up the type(s) in its type system of the resource aliases contained + * in each {@code Obligation} using {@link #getMustCallMethods(ResourceLeakAnnotatedTypeFactory, + * CFStore)}. + * + *

An Obligation might not matter on all paths out of a method. For instance, after a + * constructor assigns a resource to an {@link Owning} field, the resource only needs to be closed + * if the constructor throws an exception. If the constructor exits normally then the obligation + * is satisfied because the field is now responsible for its must-call obligations. See {@link + * #whenToEnforce}, which defines when the Obligation needs to be enforced. + * + *

There is no guarantee that a given Obligation represents a resource with a real must-call + * obligation. When the analysis can conclude that a given Obligation certainly does not represent + * a real resource with a real must-call obligation (such as if the only resource alias is + * certainly a null pointer, or if the must-call obligation is the empty set), the analysis can + * discard the Obligation. + */ + /*package-private*/ static class Obligation { /** - * A cache for the result of calling {@code ResourceLeakAnnotatedTypeFactory.getStoreAfter()} on - * a node. The cache prevents repeatedly computing least upper bounds on stores + * The set of resource aliases through which a must-call obligation can be satisfied. Calling + * the required method(s) in the must-call obligation through any of them satisfies the + * must-call obligation: that is, if the called-methods type of any alias contains the required + * method(s), then the must-call obligation is satisfied. See {@link #getMustCallMethods}. + * + *

{@code Obligation} is deeply immutable. If some code were to accidentally mutate a {@code + * resourceAliases} set it could be really nasty to debug, so this set is always immutable. */ - private final IdentityHashMap cmStoreAfter = new IdentityHashMap<>(); + public final ImmutableSet resourceAliases; /** - * A cache for the result of calling {@code MustCallAnnotatedTypeFactory.getStoreAfter()} on a - * node. The cache prevents repeatedly computing least upper bounds on stores + * The ways a method can exit along which this Obligation has to be enforced. For example, this + * will usually be {@link MethodExitKind#ALL}, indicating that this Obligation has to be + * enforced no matter how the method exits. It may also be a smaller set indicating that the + * Obligation only has to be enforced on certain exit conditions. + * + *

If this set is empty then the Obligation can be dropped as it never needs to be enforced. */ - private final IdentityHashMap mcStoreAfter = new IdentityHashMap<>(); - - /** The Resource Leak Checker, used to issue errors. */ - private final ResourceLeakChecker checker; + public final ImmutableSet whenToEnforce; /** - * The analysis from the Resource Leak Checker, used to get input stores based on CFG blocks. + * Create an Obligation from a set of resource aliases. + * + * @param resourceAliases a set of resource aliases + * @param whenToEnforce when this Obligation should be enforced */ - private final ResourceLeakAnalysis analysis; - - /** True if -AnoLightweightOwnership was passed on the command line. */ - private final boolean noLightweightOwnership; - - /** True if -AcountMustCall was passed on the command line. */ - private final boolean countMustCall; - - /** A description for how a method might exit. */ - /*package-private*/ enum MethodExitKind { - - /** The method exits normally by returning. */ - NORMAL_RETURN, - - /** The method exits by throwing an exception. */ - EXCEPTIONAL_EXIT; - - /** An immutable set containing all possible ways for a method to exit. */ - public static final Set ALL = - ImmutableSet.copyOf(EnumSet.allOf(MethodExitKind.class)); + public Obligation(Set resourceAliases, Set whenToEnforce) { + this.resourceAliases = ImmutableSet.copyOf(resourceAliases); + this.whenToEnforce = ImmutableSet.copyOf(whenToEnforce); } /** - * An Obligation is a dataflow fact: a set of resource aliases and when those resources need to - * be cleaned up. Abstractly, each Obligation represents a resource for which the analyzed - * program might have a must-call obligation. Each Obligation is a pair of a set of resource - * aliases and their must-call obligation. Must-call obligations are tracked by the {@link - * MustCallChecker} and are accessed by looking up the type(s) in its type system of the - * resource aliases contained in each {@code Obligation} using {@link - * #getMustCallMethods(ResourceLeakAnnotatedTypeFactory, CFStore)}. + * Returns the resource alias in this Obligation's resource alias set corresponding to {@code + * localVariableNode} if one is present. Otherwise, returns null. * - *

An Obligation might not matter on all paths out of a method. For instance, after a - * constructor assigns a resource to an {@link Owning} field, the resource only needs to be - * closed if the constructor throws an exception. If the constructor exits normally then the - * obligation is satisfied because the field is now responsible for its must-call obligations. - * See {@link #whenToEnforce}, which defines when the Obligation needs to be enforced. - * - *

There is no guarantee that a given Obligation represents a resource with a real must-call - * obligation. When the analysis can conclude that a given Obligation certainly does not - * represent a real resource with a real must-call obligation (such as if the only resource - * alias is certainly a null pointer, or if the must-call obligation is the empty set), the - * analysis can discard the Obligation. + * @param localVariableNode a local variable + * @return the resource alias corresponding to {@code localVariableNode} if one is present; + * otherwise, null */ - /*package-private*/ static class Obligation { - - /** - * The set of resource aliases through which a must-call obligation can be satisfied. - * Calling the required method(s) in the must-call obligation through any of them satisfies - * the must-call obligation: that is, if the called-methods type of any alias contains the - * required method(s), then the must-call obligation is satisfied. See {@link - * #getMustCallMethods}. - * - *

{@code Obligation} is deeply immutable. If some code were to accidentally mutate a - * {@code resourceAliases} set it could be really nasty to debug, so this set is always - * immutable. - */ - public final ImmutableSet resourceAliases; - - /** - * The ways a method can exit along which this Obligation has to be enforced. For example, - * this will usually be {@link MethodExitKind#ALL}, indicating that this Obligation has to - * be enforced no matter how the method exits. It may also be a smaller set indicating that - * the Obligation only has to be enforced on certain exit conditions. - * - *

If this set is empty then the Obligation can be dropped as it never needs to be - * enforced. - */ - public final ImmutableSet whenToEnforce; - - /** - * Create an Obligation from a set of resource aliases. - * - * @param resourceAliases a set of resource aliases - * @param whenToEnforce when this Obligation should be enforced - */ - public Obligation(Set resourceAliases, Set whenToEnforce) { - this.resourceAliases = ImmutableSet.copyOf(resourceAliases); - this.whenToEnforce = ImmutableSet.copyOf(whenToEnforce); - } - - /** - * Returns the resource alias in this Obligation's resource alias set corresponding to - * {@code localVariableNode} if one is present. Otherwise, returns null. - * - * @param localVariableNode a local variable - * @return the resource alias corresponding to {@code localVariableNode} if one is present; - * otherwise, null - */ - private @Nullable ResourceAlias getResourceAlias(LocalVariableNode localVariableNode) { - Element element = localVariableNode.getElement(); - for (ResourceAlias alias : resourceAliases) { - if (alias.reference instanceof LocalVariable && alias.element.equals(element)) { - return alias; - } - } - return null; - } - - /** - * Returns the resource alias in this Obligation's resource alias set corresponding to - * {@code expression} if one is present. Otherwise, returns null. - * - * @param expression a Java expression - * @return the resource alias corresponding to {@code expression} if one is present; - * otherwise, null - */ - private @Nullable ResourceAlias getResourceAlias(JavaExpression expression) { - for (ResourceAlias alias : resourceAliases) { - if (alias.reference.equals(expression)) { - return alias; - } - } - return null; - } - - /** - * Returns true if this contains a resource alias corresponding to {@code - * localVariableNode}, meaning that calling the required methods on {@code - * localVariableNode} is sufficient to satisfy the must-call obligation this object - * represents. - * - * @param localVariableNode a local variable node - * @return true if a resource alias corresponding to {@code localVariableNode} is present - */ - private boolean canBeSatisfiedThrough(LocalVariableNode localVariableNode) { - return getResourceAlias(localVariableNode) != null; - } - - /** - * Does this Obligation contain any resource aliases that were derived from {@link - * MustCallAlias} parameters? - * - * @return the logical or of the {@link ResourceAlias#derivedFromMustCallAliasParam} fields - * of this Obligation's resource aliases - */ - public boolean derivedFromMustCallAlias() { - for (ResourceAlias ra : resourceAliases) { - if (ra.derivedFromMustCallAliasParam) { - return true; - } - } - return false; - } - - /** - * Gets the must-call methods (i.e. the list of methods that must be called to satisfy the - * must-call obligation) of each resource alias represented by this Obligation. - * - * @param rlAtf a Resource Leak Annotated Type Factory - * @param mcStore a CFStore produced by the MustCall checker's dataflow analysis. If this is - * null, then the default MustCall type of each variable's class will be used. - * @return a map from each resource alias of this to a list of its must-call method names, - * or null if the must-call obligations are unsatisfiable (i.e. the value of some - * tracked resource alias of this in the Must Call store is MustCallUnknown) - */ - public @Nullable Map> getMustCallMethods( - ResourceLeakAnnotatedTypeFactory rlAtf, @Nullable CFStore mcStore) { - Map> result = new HashMap<>(this.resourceAliases.size()); - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - rlAtf.getTypeFactoryOfSubchecker(MustCallChecker.class); - - for (ResourceAlias alias : this.resourceAliases) { - AnnotationMirror mcAnno = - getMustCallValue(alias, mcStore, mustCallAnnotatedTypeFactory); - if (!AnnotationUtils.areSameByName(mcAnno, MustCall.class.getCanonicalName())) { - // MustCallUnknown; cannot be satisfied - return null; - } - List annoVals = rlAtf.getMustCallValues(mcAnno); - // Really, annoVals should never be empty here; we should not have created the - // obligation in the first place. - // TODO: add an assertion that annoVals is non-empty and address any failures - result.put(alias, annoVals); - } - return result; - } - - /** - * Gets the must-call type associated with the given resource alias, falling on back on the - * declared type if there is no refined type for the alias in the store. - * - * @param alias a resource alias - * @param mcStore the must-call checker's store - * @param mcAtf the must-call checker's annotated type factory - * @return the annotation from the must-call type hierarchy associated with {@code alias} - */ - private static AnnotationMirror getMustCallValue( - ResourceAlias alias, - @Nullable CFStore mcStore, - MustCallAnnotatedTypeFactory mcAtf) { - JavaExpression reference = alias.reference; - CFValue value = mcStore == null ? null : mcStore.getValue(reference); - if (value != null) { - AnnotationMirror result = - AnnotationUtils.getAnnotationByClass( - value.getAnnotations(), MustCall.class); - if (result != null) { - return result; - } - } - - AnnotationMirror result = - mcAtf.getAnnotatedType(alias.element) - .getEffectiveAnnotationInHierarchy(mcAtf.TOP); - if (result != null && !AnnotationUtils.areSame(result, mcAtf.TOP)) { - return result; - } - // There wasn't an @MustCall annotation for it in the store and the type factory has no - // information, so fall back to the default must-call type for the class. - // TODO: we currently end up in this case when checking a call to the return type - // of a returns-receiver method on something with a MustCall type; for example, - // see tests/socket/ZookeeperReport6.java. We should instead use a poly type if we can. - TypeElement typeElt = TypesUtils.getTypeElement(reference.getType()); - if (typeElt == null) { - // typeElt is null if reference.getType() was not a class, interface, annotation - // type, or enum -- that is, was not an annotatable type. - // That happens rarely, such as when it is a wildcard type. In these cases, fall - // back on a safe default: top. - return mcAtf.TOP; - } - if (typeElt.asType().getKind() == TypeKind.VOID) { - // Void types can't have methods called on them, so returning bottom is safe. - return mcAtf.BOTTOM; - } - - return mcAtf.getAnnotatedType(typeElt).getAnnotationInHierarchy(mcAtf.TOP); - } - - @Override - public String toString() { - return "Obligation: resourceAliases=" - + Iterables.toString(resourceAliases) - + ", whenToEnforce=" - + whenToEnforce; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - Obligation that = (Obligation) obj; - return this.resourceAliases.equals(that.resourceAliases) - && this.whenToEnforce.equals(that.whenToEnforce); - } - - @Override - public int hashCode() { - return Objects.hash(resourceAliases, whenToEnforce); - } + private @Nullable ResourceAlias getResourceAlias(LocalVariableNode localVariableNode) { + Element element = localVariableNode.getElement(); + for (ResourceAlias alias : resourceAliases) { + if (alias.reference instanceof LocalVariable && alias.element.equals(element)) { + return alias; + } + } + return null; } - // Is there a different Obligation on every line of the program, or is Obligation mutable? - // (Or maybe Obligation is abstractly mutable when you consider the @MustCall types that are not - // recorded in Obligation's representation.) Could you clarify? I found the first paragraph - // confusing, including "correspond to". /** - * A resource alias is a reference through which a must-call obligation can be satisfied. Any - * must-call obligation might be satisfiable through one or more resource aliases. An {@link - * Obligation} tracks one set of resource aliases that correspond to one must-call obligation in - * the program. + * Returns the resource alias in this Obligation's resource alias set corresponding to {@code + * expression} if one is present. Otherwise, returns null. * - *

A resource alias is always owning; non-owning aliases are, by definition, not tracked. - * - *

Internally, a resource alias is represented by a pair of a {@link JavaExpression} (the - * "reference" through which the must-call obligations for the alias set to which it belongs can - * be satisfied) and a tree that "assigns" the reference. + * @param expression a Java expression + * @return the resource alias corresponding to {@code expression} if one is present; otherwise, + * null */ - /*package-private*/ static class ResourceAlias { - - /** An expression from the source code or a temporary variable for an expression. */ - public final JavaExpression reference; - - /** The element for {@link #reference}. */ - public final Element element; - - /** The tree at which {@code reference} was assigned, for the purpose of error reporting. */ - public final Tree tree; - - /** - * Was this ResourceAlias derived from a parameter to a method that was annotated as {@link - * MustCallAlias}? If so, the obligation containing this resource alias must be discharged - * only in one of the following ways: - * - *

    - *
  • it is passed to another method or constructor in an @MustCallAlias position, and - * then the containing method returns that method’s result, or the call is a super() - * constructor call annotated with {@link MustCallAlias}, or - *
  • it is stored in an owning field of the class under analysis - *
- */ - public final boolean derivedFromMustCallAliasParam; - - /** - * Create a new resource alias. This constructor should only be used if the resource alias - * was not derived from a method parameter annotated as {@link MustCallAlias}. - * - * @param reference the local variable - * @param tree the tree - */ - public ResourceAlias(LocalVariable reference, Tree tree) { - this(reference, reference.getElement(), tree); - } - - /** - * Create a new resource alias. This constructor should only be used if the resource alias - * was not derived from a method parameter annotated as {@link MustCallAlias}. - * - * @param reference the reference - * @param element the element for the given reference - * @param tree the tree - */ - public ResourceAlias(JavaExpression reference, Element element, Tree tree) { - this(reference, element, tree, false); - } - - /** - * Create a new resource alias. - * - * @param reference the local variable - * @param element the element for the reference - * @param tree the tree - * @param derivedFromMustCallAliasParam true iff this resource alias was created because of - * an {@link MustCallAlias} parameter - */ - public ResourceAlias( - JavaExpression reference, - Element element, - Tree tree, - boolean derivedFromMustCallAliasParam) { - this.reference = reference; - this.element = element; - this.tree = tree; - this.derivedFromMustCallAliasParam = derivedFromMustCallAliasParam; - } - - @Override - public String toString() { - return "(ResourceAlias: reference: " + reference + " |||| tree: " + tree + ")"; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ResourceAlias that = (ResourceAlias) o; - return reference.equals(that.reference) && tree.equals(that.tree); - } - - @Override - public int hashCode() { - return Objects.hash(reference, tree); - } - - /** - * Returns an appropriate String for representing this in an error message. In particular, - * if {@link #reference} is a temporary variable, we return the String representation of - * {@link #tree}, to avoid exposing the temporary name (which has no meaning for the user) - * in the error message - * - * @return an appropriate String for representing this in an error message - */ - public String stringForErrorMessage() { - String referenceStr = reference.toString(); - // We assume that any temporary variable name will not be a syntactically-valid - // identifier or keyword. - return !SourceVersion.isIdentifier(referenceStr) ? tree.toString() : referenceStr; + private @Nullable ResourceAlias getResourceAlias(JavaExpression expression) { + for (ResourceAlias alias : resourceAliases) { + if (alias.reference.equals(expression)) { + return alias; } + } + return null; } /** - * Creates a consistency analyzer. Typically, the type factory's postAnalyze method would - * instantiate a new consistency analyzer using this constructor and then call {@link - * #analyze(ControlFlowGraph)}. + * Returns true if this contains a resource alias corresponding to {@code localVariableNode}, + * meaning that calling the required methods on {@code localVariableNode} is sufficient to + * satisfy the must-call obligation this object represents. * - * @param typeFactory the type factory - * @param analysis the analysis from the type factory. Usually this would have protected access, - * so this constructor cannot get it directly. + * @param localVariableNode a local variable node + * @return true if a resource alias corresponding to {@code localVariableNode} is present */ - /*package-private*/ MustCallConsistencyAnalyzer( - ResourceLeakAnnotatedTypeFactory typeFactory, ResourceLeakAnalysis analysis) { - this.typeFactory = typeFactory; - this.checker = (ResourceLeakChecker) typeFactory.getChecker(); - this.analysis = analysis; - this.permitStaticOwning = checker.hasOption("permitStaticOwning"); - this.permitInitializationLeak = checker.hasOption("permitInitializationLeak"); - this.noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); - this.countMustCall = checker.hasOption(ResourceLeakChecker.COUNT_MUST_CALL); + private boolean canBeSatisfiedThrough(LocalVariableNode localVariableNode) { + return getResourceAlias(localVariableNode) != null; } /** - * The main function of the consistency dataflow analysis. The analysis tracks dataflow facts - * ("Obligations") of type {@link Obligation}, each representing a set of owning resource - * aliases for some value with a non-empty {@code @MustCall} obligation. The set of tracked - * Obligations is guaranteed to include at least one Obligation for each actual resource in the - * program, but might include other, spurious Obligations, too (that is, it is a conservative - * over-approximation of the true Obligation set). - * - *

The analysis improves its precision by removing Obligations from tracking when it can - * prove that they do not represent real resources. For example, it is not necessary to track - * expressions with empty {@code @MustCall} obligations, because they are trivially fulfilled. - * Nor is tracking non-owning aliases necessary, because by definition they cannot be used to - * fulfill must-call obligations. + * Does this Obligation contain any resource aliases that were derived from {@link + * MustCallAlias} parameters? * - * @param cfg the control flow graph of the method to check + * @return the logical or of the {@link ResourceAlias#derivedFromMustCallAliasParam} fields of + * this Obligation's resource aliases */ - // TODO: This analysis is currently implemented directly using a worklist; in the future, it - // should be rewritten to use the dataflow framework of the Checker Framework. - /*package-private*/ void analyze(ControlFlowGraph cfg) { - // The `visited` set contains everything that has been added to the worklist, even if it has - // not yet been removed and analyzed. - Set visited = new HashSet<>(); - Deque worklist = new ArrayDeque<>(); - - // Add any owning parameters to the initial set of variables to track. - BlockWithObligations entry = - new BlockWithObligations(cfg.getEntryBlock(), computeOwningParameters(cfg)); - worklist.add(entry); - visited.add(entry); - - while (!worklist.isEmpty()) { - BlockWithObligations current = worklist.remove(); - propagateObligationsToSuccessorBlocks( - cfg, current.obligations, current.block, visited, worklist); + public boolean derivedFromMustCallAlias() { + for (ResourceAlias ra : resourceAliases) { + if (ra.derivedFromMustCallAliasParam) { + return true; } + } + return false; } /** - * Update a set of Obligations to account for a method or constructor invocation. - * - * @param obligations the Obligations to update - * @param node the method or constructor invocation - * @param exceptionType a description of the outgoing CFG edge from the node: null - * to indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given - * throwable class was thrown + * Gets the must-call methods (i.e. the list of methods that must be called to satisfy the + * must-call obligation) of each resource alias represented by this Obligation. + * + * @param rlAtf a Resource Leak Annotated Type Factory + * @param mcStore a CFStore produced by the MustCall checker's dataflow analysis. If this is + * null, then the default MustCall type of each variable's class will be used. + * @return a map from each resource alias of this to a list of its must-call method names, or + * null if the must-call obligations are unsatisfiable (i.e. the value of some tracked + * resource alias of this in the Must Call store is MustCallUnknown) */ - private void updateObligationsForInvocation( - Set obligations, Node node, @Nullable TypeMirror exceptionType) { - removeObligationsAtOwnershipTransferToParameters(obligations, node, exceptionType); - if (node instanceof MethodInvocationNode - && typeFactory.canCreateObligations() - && typeFactory.hasCreatesMustCallFor((MethodInvocationNode) node)) { - checkCreatesMustCallForInvocation(obligations, (MethodInvocationNode) node); - // Count calls to @CreatesMustCallFor methods as creating new resources. Doing so could - // result in slightly over-counting, because @CreatesMustCallFor doesn't guarantee that - // a new resource is created: it just means that a new resource might have been created. - incrementNumMustCall(node); - } - - if (!shouldTrackInvocationResult(obligations, node)) { - return; - } - - if (typeFactory.declaredTypeHasMustCall(node.getTree())) { - // The incrementNumMustCall call above increments the count for the target of the - // @CreatesMustCallFor annotation. By contrast, this call increments the count for the - // return value of the method (which can't be the target of the annotation, because our - // syntax doesn't support that). - incrementNumMustCall(node); - } - updateObligationsWithInvocationResult(obligations, node); + public @Nullable Map> getMustCallMethods( + ResourceLeakAnnotatedTypeFactory rlAtf, @Nullable CFStore mcStore) { + Map> result = new HashMap<>(this.resourceAliases.size()); + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + rlAtf.getTypeFactoryOfSubchecker(MustCallChecker.class); + + for (ResourceAlias alias : this.resourceAliases) { + AnnotationMirror mcAnno = getMustCallValue(alias, mcStore, mustCallAnnotatedTypeFactory); + if (!AnnotationUtils.areSameByName(mcAnno, MustCall.class.getCanonicalName())) { + // MustCallUnknown; cannot be satisfied + return null; + } + List annoVals = rlAtf.getMustCallValues(mcAnno); + // Really, annoVals should never be empty here; we should not have created the + // obligation in the first place. + // TODO: add an assertion that annoVals is non-empty and address any failures + result.put(alias, annoVals); + } + return result; } /** - * Checks that an invocation of a CreatesMustCallFor method is valid. + * Gets the must-call type associated with the given resource alias, falling on back on the + * declared type if there is no refined type for the alias in the store. * - *

Such an invocation is valid if any of the conditions in {@link - * #isValidCreatesMustCallForExpression(Set, JavaExpression, TreePath)} is true for each - * expression in the argument to the CreatesMustCallFor annotation. As a special case, the - * invocation of a CreatesMustCallFor method with "this" as its expression is permitted in the - * constructor of the relevant class (invoking a constructor already creates an obligation). If - * none of these conditions are true for any of the expressions, this method issues a - * reset.not.owning error. - * - *

For soundness, this method also guarantees that if any of the expressions in the - * CreatesMustCallFor annotation has a tracked Obligation, any tracked resource aliases of it - * will be removed (lest the analysis conclude that it is already closed because one of these - * aliases was closed before the method was invoked). Aliases created after the - * CreatesMustCallFor method is invoked are still permitted. - * - * @param obligations the currently-tracked Obligations; this value is side-effected if there is - * an Obligation in it which tracks any expression from the CreatesMustCallFor annotation as - * one of its resource aliases - * @param node a method invocation node, invoking a method with a CreatesMustCallFor annotation + * @param alias a resource alias + * @param mcStore the must-call checker's store + * @param mcAtf the must-call checker's annotated type factory + * @return the annotation from the must-call type hierarchy associated with {@code alias} */ - private void checkCreatesMustCallForInvocation( - Set obligations, MethodInvocationNode node) { - - TreePath currentPath = typeFactory.getPath(node.getTree()); - List cmcfExpressions = - CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( - node, typeFactory, typeFactory); - List missing = new ArrayList<>(0); - for (JavaExpression expression : cmcfExpressions) { - if (!isValidCreatesMustCallForExpression(obligations, expression, currentPath)) { - missing.add(expression); - } - } + private static AnnotationMirror getMustCallValue( + ResourceAlias alias, @Nullable CFStore mcStore, MustCallAnnotatedTypeFactory mcAtf) { + JavaExpression reference = alias.reference; + CFValue value = mcStore == null ? null : mcStore.getValue(reference); + if (value != null) { + AnnotationMirror result = + AnnotationUtils.getAnnotationByClass(value.getAnnotations(), MustCall.class); + if (result != null) { + return result; + } + } + + AnnotationMirror result = + mcAtf.getAnnotatedType(alias.element).getEffectiveAnnotationInHierarchy(mcAtf.TOP); + if (result != null && !AnnotationUtils.areSame(result, mcAtf.TOP)) { + return result; + } + // There wasn't an @MustCall annotation for it in the store and the type factory has no + // information, so fall back to the default must-call type for the class. + // TODO: we currently end up in this case when checking a call to the return type + // of a returns-receiver method on something with a MustCall type; for example, + // see tests/socket/ZookeeperReport6.java. We should instead use a poly type if we can. + TypeElement typeElt = TypesUtils.getTypeElement(reference.getType()); + if (typeElt == null) { + // typeElt is null if reference.getType() was not a class, interface, annotation + // type, or enum -- that is, was not an annotatable type. + // That happens rarely, such as when it is a wildcard type. In these cases, fall + // back on a safe default: top. + return mcAtf.TOP; + } + if (typeElt.asType().getKind() == TypeKind.VOID) { + // Void types can't have methods called on them, so returning bottom is safe. + return mcAtf.BOTTOM; + } + + return mcAtf.getAnnotatedType(typeElt).getAnnotationInHierarchy(mcAtf.TOP); + } - if (missing.isEmpty()) { - // All expressions matched one of the rules, so the invocation is valid. - return; - } + @Override + public String toString() { + return "Obligation: resourceAliases=" + + Iterables.toString(resourceAliases) + + ", whenToEnforce=" + + whenToEnforce; + } - // Special case for invocations of CreatesMustCallFor("this") methods in the constructor. - if (missing.size() == 1) { - JavaExpression expression = missing.get(0); - if (expression instanceof ThisReference && TreePathUtil.inConstructor(currentPath)) { - return; - } - } + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Obligation that = (Obligation) obj; + return this.resourceAliases.equals(that.resourceAliases) + && this.whenToEnforce.equals(that.whenToEnforce); + } - StringJoiner missingStrs = new StringJoiner(","); - for (JavaExpression m : missing) { - String s = m.toString(); - missingStrs.add(s.equals("this") ? s + " of type " + m.getType() : s); - } - checker.reportError( - node.getTree(), - "reset.not.owning", - node.getTarget().getMethod().getSimpleName().toString(), - missingStrs.toString()); + @Override + public int hashCode() { + return Objects.hash(resourceAliases, whenToEnforce); } + } + + // Is there a different Obligation on every line of the program, or is Obligation mutable? + // (Or maybe Obligation is abstractly mutable when you consider the @MustCall types that are not + // recorded in Obligation's representation.) Could you clarify? I found the first paragraph + // confusing, including "correspond to". + /** + * A resource alias is a reference through which a must-call obligation can be satisfied. Any + * must-call obligation might be satisfiable through one or more resource aliases. An {@link + * Obligation} tracks one set of resource aliases that correspond to one must-call obligation in + * the program. + * + *

A resource alias is always owning; non-owning aliases are, by definition, not tracked. + * + *

Internally, a resource alias is represented by a pair of a {@link JavaExpression} (the + * "reference" through which the must-call obligations for the alias set to which it belongs can + * be satisfied) and a tree that "assigns" the reference. + */ + /*package-private*/ static class ResourceAlias { + + /** An expression from the source code or a temporary variable for an expression. */ + public final JavaExpression reference; + + /** The element for {@link #reference}. */ + public final Element element; + + /** The tree at which {@code reference} was assigned, for the purpose of error reporting. */ + public final Tree tree; /** - * Checks the validity of the given expression from an invoked method's {@link - * org.checkerframework.checker.mustcall.qual.CreatesMustCallFor} annotation. Helper method for - * {@link #checkCreatesMustCallForInvocation(Set, MethodInvocationNode)}. - * - *

An expression is valid if one of the following conditions is true: + * Was this ResourceAlias derived from a parameter to a method that was annotated as {@link + * MustCallAlias}? If so, the obligation containing this resource alias must be discharged only + * in one of the following ways: * *

    - *
  • 1) the expression is an owning pointer, - *
  • 2) the expression already has a tracked Obligation (i.e. there is already a resource - * alias in some Obligation's resource alias set that refers to the expression), or - *
  • 3) the method in which the invocation occurs also has an @CreatesMustCallFor - * annotation, with the same expression. + *
  • it is passed to another method or constructor in an @MustCallAlias position, and then + * the containing method returns that method’s result, or the call is a super() + * constructor call annotated with {@link MustCallAlias}, or + *
  • it is stored in an owning field of the class under analysis *
- * - * @param obligations the currently-tracked Obligations; this value is side-effected if there is - * an Obligation in it which tracks {@code expression} as one of its resource aliases - * @param expression an element of a method's @CreatesMustCallFor annotation - * @param invocationPath the path to the invocation of the method from whose @CreateMustCallFor - * annotation {@code expression} came - * @return true iff the expression is valid, as defined above */ - private boolean isValidCreatesMustCallForExpression( - Set obligations, JavaExpression expression, TreePath invocationPath) { - if (expression instanceof FieldAccess) { - Element elt = ((FieldAccess) expression).getField(); - if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { - // The expression is an Owning field. This satisfies case 1. - return true; - } - } else if (expression instanceof LocalVariable) { - Element elt = ((LocalVariable) expression).getElement(); - if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { - // The expression is an Owning formal parameter. Note that this cannot actually - // be a local variable (despite expressions's type being LocalVariable) because - // the @Owning annotation can only be written on methods, parameters, and fields; - // formal parameters are also represented by LocalVariable in the bodies of methods. - // This satisfies case 1. - return true; - } else { - Obligation toRemove = null; - Obligation toAdd = null; - for (Obligation obligation : obligations) { - ResourceAlias alias = obligation.getResourceAlias(expression); - if (alias != null) { - // This satisfies case 2 above. Remove all its aliases, then return below. - if (toRemove != null) { - throw new TypeSystemError( - "tried to remove multiple sets containing a reset expression at" - + " once"); - } - toRemove = obligation; - toAdd = new Obligation(ImmutableSet.of(alias), obligation.whenToEnforce); - } - } - - if (toRemove != null) { - obligations.remove(toRemove); - obligations.add(toAdd); - // This satisfies case 2. - return true; - } - } - } - - // TODO: Getting this every time is inefficient if a method has many @CreatesMustCallFor - // annotations, but that should be rare. - MethodTree callerMethodTree = TreePathUtil.enclosingMethod(invocationPath); - if (callerMethodTree == null) { - return false; - } - ExecutableElement callerMethodElt = TreeUtils.elementFromDeclaration(callerMethodTree); - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - List callerCmcfValues = - ResourceLeakVisitor.getCreatesMustCallForValues( - callerMethodElt, mcAtf, typeFactory); - if (callerCmcfValues.isEmpty()) { - return false; - } - for (String callerCmcfValue : callerCmcfValues) { - JavaExpression callerTarget; - try { - callerTarget = - StringToJavaExpression.atMethodBody( - callerCmcfValue, callerMethodTree, checker); - } catch (JavaExpressionParseException e) { - // Do not issue an error here, because it would be a duplicate. - // The error will be issued by the Transfer class of the checker, - // via the CreatesMustCallForElementSupplier interface. - callerTarget = null; - } - - if (areSame(expression, callerTarget)) { - // This satisfies case 3. - return true; - } - } - return false; - } + public final boolean derivedFromMustCallAliasParam; /** - * Checks whether the two JavaExpressions are the same. This is identical to calling equals() on - * one of them, with two exceptions: the second expression can be null, and {@code this} - * references are compared using their underlying type. (ThisReference#equals always returns - * true, which is probably a bug and isn't accurate in the case of nested classes.) + * Create a new resource alias. This constructor should only be used if the resource alias was + * not derived from a method parameter annotated as {@link MustCallAlias}. * - * @param target a JavaExpression - * @param enclosingTarget another, possibly null, JavaExpression - * @return true iff they represent the same program element + * @param reference the local variable + * @param tree the tree */ - private boolean areSame(JavaExpression target, @Nullable JavaExpression enclosingTarget) { - if (enclosingTarget == null) { - return false; - } - if (enclosingTarget instanceof ThisReference && target instanceof ThisReference) { - return enclosingTarget.getType().toString().equals(target.getType().toString()); - } else { - return enclosingTarget.equals(target); - } + public ResourceAlias(LocalVariable reference, Tree tree) { + this(reference, reference.getElement(), tree); } /** - * Given a node representing a method or constructor call, updates the set of Obligations to - * account for the result, which is treated as a new resource alias. Adds the new resource alias - * to the set of an Obligation in {@code obligations}: either an existing Obligation if the - * result is definitely resource-aliased with it, or a new Obligation if not. + * Create a new resource alias. This constructor should only be used if the resource alias was + * not derived from a method parameter annotated as {@link MustCallAlias}. * - * @param obligations the currently-tracked Obligations. This is always side-effected: either a - * new resource alias is added to the resource alias set of an existing Obligation, or a new - * Obligation with a single-element resource alias set is created and added. - * @param node the invocation node whose result is to be tracked; must be {@link - * MethodInvocationNode} or {@link ObjectCreationNode} + * @param reference the reference + * @param element the element for the given reference + * @param tree the tree */ - /*package-private*/ void updateObligationsWithInvocationResult( - Set obligations, Node node) { - Tree tree = node.getTree(); - // Only track the result of the call if there is a temporary variable for the call node - // (because if there is no temporary, then the invocation must produce an untrackable value, - // such as a primitive type). - LocalVariableNode tmpVar = typeFactory.getTempVarForNode(node); - if (tmpVar == null) { - return; - } - - // `mustCallAliases` is a (possibly-empty) list of arguments passed in a MustCallAlias - // position. - List mustCallAliases = getMustCallAliasArgumentNodes(node); - // If call returns @This, add the receiver to mustCallAliases. - if (node instanceof MethodInvocationNode - && typeFactory.returnsThis((MethodInvocationTree) tree)) { - mustCallAliases.add( - removeCastsAndGetTmpVarIfPresent( - ((MethodInvocationNode) node).getTarget().getReceiver())); - } - - if (mustCallAliases.isEmpty()) { - // If mustCallAliases is an empty List, add tmpVarAsResourceAlias to a new set. - ResourceAlias tmpVarAsResourceAlias = - new ResourceAlias(new LocalVariable(tmpVar), tree); - obligations.add( - new Obligation(ImmutableSet.of(tmpVarAsResourceAlias), MethodExitKind.ALL)); - } else { - for (Node mustCallAlias : mustCallAliases) { - if (mustCallAlias instanceof FieldAccessNode) { - // Do not track the call result if the MustCallAlias argument is a field. - // Handling of @Owning fields is a completely separate check, and there is never - // a need to track an alias of a non-@Owning field, as by definition such a - // field does not have must-call obligations! - } else if (mustCallAlias instanceof LocalVariableNode) { - // If mustCallAlias is a local variable already being tracked, add - // tmpVarAsResourceAlias to the set containing mustCallAlias. - Obligation obligationContainingMustCallAlias = - getObligationForVar(obligations, (LocalVariableNode) mustCallAlias); - if (obligationContainingMustCallAlias != null) { - ResourceAlias tmpVarAsResourceAlias = - new ResourceAlias( - new LocalVariable(tmpVar), - tmpVar.getElement(), - tree, - obligationContainingMustCallAlias - .derivedFromMustCallAlias()); - Set newResourceAliasSet = - FluentIterable.from( - obligationContainingMustCallAlias.resourceAliases) - .append(tmpVarAsResourceAlias) - .toSet(); - obligations.remove(obligationContainingMustCallAlias); - obligations.add( - new Obligation( - newResourceAliasSet, - obligationContainingMustCallAlias.whenToEnforce)); - // It is not an error if there is no Obligation containing the must-call - // alias. In that case, what has usually happened is that no Obligation was - // created in the first place. - // For example, when checking the invocation of a "wrapper stream" - // constructor, if the argument in the must-call alias position is some - // stream with no must-call obligations like a ByteArrayInputStream, then no - // Obligation object will have been created for it and therefore - // obligationContainingMustCallAlias will be null. - } - } - } - } + public ResourceAlias(JavaExpression reference, Element element, Tree tree) { + this(reference, element, tree, false); } /** - * Returns true if the result of the given method or constructor invocation node should be - * tracked in {@code obligations}. In some cases, there is no need to track the result because - * the must-call obligations are already satisfied in some other way or there cannot possibly be - * must-call obligations because of the structure of the code. - * - *

Specifically, an invocation result does NOT need to be tracked if any of the following is - * true: - * - *

    - *
  • The invocation is a call to a {@code this()} or {@code super()} constructor. - *
  • The method's return type is annotated with MustCallAlias and the argument passed in - * this invocation in the corresponding position is an owning field. - *
  • The method's return type is non-owning, which can either be because the method has no - * return type or because the return type is annotated with {@link NotOwning}. - *
- * - *

This method can also side-effect {@code obligations}, if node is a super or this - * constructor call with MustCallAlias annotations, by removing that Obligation. + * Create a new resource alias. * - * @param obligations the current set of Obligations, which may be side-effected - * @param node the invocation node to check; must be {@link MethodInvocationNode} or {@link - * ObjectCreationNode} - * @return true iff the result of {@code node} should be tracked in {@code obligations} + * @param reference the local variable + * @param element the element for the reference + * @param tree the tree + * @param derivedFromMustCallAliasParam true iff this resource alias was created because of an + * {@link MustCallAlias} parameter */ - private boolean shouldTrackInvocationResult(Set obligations, Node node) { - Tree callTree = node.getTree(); - if (callTree.getKind() == Tree.Kind.NEW_CLASS) { - // Constructor results from new expressions are tracked as long as the declared type has - // a non-empty @MustCall annotation. - NewClassTree newClassTree = (NewClassTree) callTree; - ExecutableElement executableElement = TreeUtils.elementFromUse(newClassTree); - TypeElement typeElt = - TypesUtils.getTypeElement(ElementUtils.getType(executableElement)); - return typeElt == null - || !typeFactory.hasEmptyMustCallValue(typeElt) - || !typeFactory.hasEmptyMustCallValue(newClassTree); - } - - // Now callTree.getKind() == Tree.Kind.METHOD_INVOCATION. - MethodInvocationTree methodInvokeTree = (MethodInvocationTree) callTree; - - if (TreeUtils.isSuperConstructorCall(methodInvokeTree) - || TreeUtils.isThisConstructorCall(methodInvokeTree)) { - List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); - // If there is a MustCallAlias argument that is also in the set of Obligations, then - // remove it; its must-call obligation has been fulfilled by being passed on to the - // MustCallAlias constructor (because a this/super constructor call can only occur in - // the body of another constructor). - for (Node mustCallAliasArgument : mustCallAliasArguments) { - if (mustCallAliasArgument instanceof LocalVariableNode) { - removeObligationsContainingVar( - obligations, (LocalVariableNode) mustCallAliasArgument); - } - } - return false; - } - return !returnTypeIsMustCallAliasWithUntrackable((MethodInvocationNode) node) - && shouldTrackReturnType((MethodInvocationNode) node); + public ResourceAlias( + JavaExpression reference, + Element element, + Tree tree, + boolean derivedFromMustCallAliasParam) { + this.reference = reference; + this.element = element; + this.tree = tree; + this.derivedFromMustCallAliasParam = derivedFromMustCallAliasParam; } - /** - * Returns true if this node represents a method invocation of a must-call-alias method, where - * the argument in the must-call-alias position is untrackable: an owning field or a pointer - * that is guaranteed to be non-owning, such as {@code "this"} or a non-owning field. Owning - * fields are handled by the rest of the checker, not by this algorithm, so they are - * "untrackable". Non-owning fields and this nodes are guaranteed to be non-owning, and are - * therefore also "untrackable". Because both owning and non-owning fields are untrackable (and - * there are no other kinds of fields), this method returns true for all field accesses. - * - * @param node a method invocation node - * @return true if this is the invocation of a method whose return type is MCA with an owning - * field or a definitely non-owning pointer - */ - private boolean returnTypeIsMustCallAliasWithUntrackable(MethodInvocationNode node) { - List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); - for (Node mustCallAliasArg : mustCallAliasArguments) { - if (!(mustCallAliasArg instanceof FieldAccessNode - || mustCallAliasArg instanceof ThisNode)) { - return false; - } - } - return !mustCallAliasArguments.isEmpty(); + @Override + public String toString() { + return "(ResourceAlias: reference: " + reference + " |||| tree: " + tree + ")"; } - /** - * Checks if {@code node} is either directly enclosed by a {@link TypeCastNode}, by looking at - * the successor block in the CFG. In this case the enclosing operator is a "no-op" that - * evaluates to the same value as {@code node}. This method is only used within {@link - * #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, Deque)} to ensure - * Obligations are propagated to cast nodes properly. It relies on the assumption that a {@link - * TypeCastNode} will only appear in a CFG as the first node in a block. - * - * @param node the CFG node - * @return {@code true} if {@code node} is in a {@link SingleSuccessorBlock} {@code b}, the - * first {@link Node} in {@code b}'s successor block is a {@link TypeCastNode}, and {@code - * node} is an operand of the successor node; {@code false} otherwise - */ - private boolean inCast(Node node) { - if (!(node.getBlock() instanceof SingleSuccessorBlock)) { - return false; - } - Block successorBlock = ((SingleSuccessorBlock) node.getBlock()).getSuccessor(); - if (successorBlock != null) { - List succNodes = successorBlock.getNodes(); - if (succNodes.size() > 0) { - Node succNode = succNodes.get(0); - if (succNode instanceof TypeCastNode) { - return ((TypeCastNode) succNode).getOperand().equals(node); - } - } - } + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { return false; + } + ResourceAlias that = (ResourceAlias) o; + return reference.equals(that.reference) && tree.equals(that.tree); } - /** - * Transfer ownership of any locals passed as arguments to {@code @Owning} parameters at a - * method or constructor call by removing the Obligations corresponding to those locals. - * - * @param obligations the current set of Obligations, which is side-effected to remove - * Obligations for locals that are passed as owning parameters to the method or constructor - * @param node a method or constructor invocation node - * @param exceptionType a description of the outgoing CFG edge from the node: null - * to indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given - * throwable class was thrown - */ - private void removeObligationsAtOwnershipTransferToParameters( - Set obligations, Node node, @Nullable TypeMirror exceptionType) { - - if (exceptionType != null) { - // Do not transfer ownership if the called method throws an exception. - return; - } - - if (noLightweightOwnership) { - // Never transfer ownership to parameters, matching the default in the analysis built - // into Eclipse. - return; - } - - List arguments = getArgumentsOfInvocation(node); - List parameters = getParametersOfInvocation(node); - - if (arguments.size() != parameters.size()) { - // This could happen, e.g., with varargs, or with strange cases like generated Enum - // constructors. In the varargs case (i.e. if the varargs parameter is owning), - // only the first of the varargs arguments will actually get transferred: the second - // and later varargs arguments will continue to be tracked at the call-site. - // For now, just skip this case - the worst that will happen is a false positive in - // cases like the varargs one described above. - // TODO allow for ownership transfer here if needed in future - return; - } - for (int i = 0; i < arguments.size(); i++) { - Node n = removeCastsAndGetTmpVarIfPresent(arguments.get(i)); - if (n instanceof LocalVariableNode) { - LocalVariableNode local = (LocalVariableNode) n; - if (varTrackedInObligations(obligations, local)) { - - // check if parameter has an @Owning annotation - VariableElement parameter = parameters.get(i); - if (typeFactory.hasOwning(parameter)) { - Obligation localObligation = getObligationForVar(obligations, local); - // Passing to an owning parameter is not sufficient to resolve the - // obligation created from a MustCallAlias parameter, because the - // containing method must actually return the value. - if (!localObligation.derivedFromMustCallAlias()) { - // Transfer ownership! - obligations.remove(localObligation); - } - } - } - } - } + @Override + public int hashCode() { + return Objects.hash(reference, tree); } /** - * If the return type of the enclosing method is {@code @Owning}, treat the must-call - * obligations of the return expression as satisfied by removing all references to them from - * {@code obligations}. + * Returns an appropriate String for representing this in an error message. In particular, if + * {@link #reference} is a temporary variable, we return the String representation of {@link + * #tree}, to avoid exposing the temporary name (which has no meaning for the user) in the error + * message * - * @param obligations the current set of tracked Obligations. If ownership is transferred, it is - * side-effected to remove any Obligations that are resource-aliased to the return node. - * @param cfg the CFG of the enclosing method - * @param node a return node + * @return an appropriate String for representing this in an error message */ - private void updateObligationsForOwningReturn( - Set obligations, ControlFlowGraph cfg, ReturnNode node) { - if (isTransferOwnershipAtReturn(cfg)) { - Node returnExpr = node.getResult(); - returnExpr = getTempVarOrNode(returnExpr); - if (returnExpr instanceof LocalVariableNode) { - removeObligationsContainingVar(obligations, (LocalVariableNode) returnExpr); - } - } + public String stringForErrorMessage() { + String referenceStr = reference.toString(); + // We assume that any temporary variable name will not be a syntactically-valid + // identifier or keyword. + return !SourceVersion.isIdentifier(referenceStr) ? tree.toString() : referenceStr; } - - /** - * Helper method that gets the temporary node corresponding to {@code node}, if one exists. If - * not, this method returns its input. - * - * @param node a node - * @return the temporary for node, or node if no temporary exists - */ - /*package-private*/ Node getTempVarOrNode(Node node) { - Node temp = typeFactory.getTempVarForNode(node); - if (temp != null) { - return temp; - } - return node; + } + + /** + * Creates a consistency analyzer. Typically, the type factory's postAnalyze method would + * instantiate a new consistency analyzer using this constructor and then call {@link + * #analyze(ControlFlowGraph)}. + * + * @param typeFactory the type factory + * @param analysis the analysis from the type factory. Usually this would have protected access, + * so this constructor cannot get it directly. + */ + /*package-private*/ MustCallConsistencyAnalyzer( + ResourceLeakAnnotatedTypeFactory typeFactory, ResourceLeakAnalysis analysis) { + this.typeFactory = typeFactory; + this.checker = (ResourceLeakChecker) typeFactory.getChecker(); + this.analysis = analysis; + this.permitStaticOwning = checker.hasOption("permitStaticOwning"); + this.permitInitializationLeak = checker.hasOption("permitInitializationLeak"); + this.noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); + this.countMustCall = checker.hasOption(ResourceLeakChecker.COUNT_MUST_CALL); + } + + /** + * The main function of the consistency dataflow analysis. The analysis tracks dataflow facts + * ("Obligations") of type {@link Obligation}, each representing a set of owning resource aliases + * for some value with a non-empty {@code @MustCall} obligation. The set of tracked Obligations is + * guaranteed to include at least one Obligation for each actual resource in the program, but + * might include other, spurious Obligations, too (that is, it is a conservative + * over-approximation of the true Obligation set). + * + *

The analysis improves its precision by removing Obligations from tracking when it can prove + * that they do not represent real resources. For example, it is not necessary to track + * expressions with empty {@code @MustCall} obligations, because they are trivially fulfilled. Nor + * is tracking non-owning aliases necessary, because by definition they cannot be used to fulfill + * must-call obligations. + * + * @param cfg the control flow graph of the method to check + */ + // TODO: This analysis is currently implemented directly using a worklist; in the future, it + // should be rewritten to use the dataflow framework of the Checker Framework. + /*package-private*/ void analyze(ControlFlowGraph cfg) { + // The `visited` set contains everything that has been added to the worklist, even if it has + // not yet been removed and analyzed. + Set visited = new HashSet<>(); + Deque worklist = new ArrayDeque<>(); + + // Add any owning parameters to the initial set of variables to track. + BlockWithObligations entry = + new BlockWithObligations(cfg.getEntryBlock(), computeOwningParameters(cfg)); + worklist.add(entry); + visited.add(entry); + + while (!worklist.isEmpty()) { + BlockWithObligations current = worklist.remove(); + propagateObligationsToSuccessorBlocks( + cfg, current.obligations, current.block, visited, worklist); + } + } + + /** + * Update a set of Obligations to account for a method or constructor invocation. + * + * @param obligations the Obligations to update + * @param node the method or constructor invocation + * @param exceptionType a description of the outgoing CFG edge from the node: null to + * indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given + * throwable class was thrown + */ + private void updateObligationsForInvocation( + Set obligations, Node node, @Nullable TypeMirror exceptionType) { + removeObligationsAtOwnershipTransferToParameters(obligations, node, exceptionType); + if (node instanceof MethodInvocationNode + && typeFactory.canCreateObligations() + && typeFactory.hasCreatesMustCallFor((MethodInvocationNode) node)) { + checkCreatesMustCallForInvocation(obligations, (MethodInvocationNode) node); + // Count calls to @CreatesMustCallFor methods as creating new resources. Doing so could + // result in slightly over-counting, because @CreatesMustCallFor doesn't guarantee that + // a new resource is created: it just means that a new resource might have been created. + incrementNumMustCall(node); } - /** - * Should ownership be transferred to the return type of the method corresponding to a CFG? - * Returns true when there is no {@link NotOwning} annotation on the return type. - * - * @param cfg the CFG of the method - * @return true iff ownership should be transferred to the return type of the method - * corresponding to a CFG - */ - private boolean isTransferOwnershipAtReturn(ControlFlowGraph cfg) { - if (noLightweightOwnership) { - // If not using LO, default to always transfer at return, just like Eclipse does. - return true; - } + if (!shouldTrackInvocationResult(obligations, node)) { + return; + } - UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); - if (underlyingAST instanceof UnderlyingAST.CFGMethod) { - // TODO: lambdas? In that case false is returned below, which means that ownership will - // not be transferred. - MethodTree method = ((UnderlyingAST.CFGMethod) underlyingAST).getMethod(); - ExecutableElement executableElement = TreeUtils.elementFromDeclaration(method); - return !typeFactory.hasNotOwning(executableElement); - } - return false; + if (typeFactory.declaredTypeHasMustCall(node.getTree())) { + // The incrementNumMustCall call above increments the count for the target of the + // @CreatesMustCallFor annotation. By contrast, this call increments the count for the + // return value of the method (which can't be the target of the annotation, because our + // syntax doesn't support that). + incrementNumMustCall(node); + } + updateObligationsWithInvocationResult(obligations, node); + } + + /** + * Checks that an invocation of a CreatesMustCallFor method is valid. + * + *

Such an invocation is valid if any of the conditions in {@link + * #isValidCreatesMustCallForExpression(Set, JavaExpression, TreePath)} is true for each + * expression in the argument to the CreatesMustCallFor annotation. As a special case, the + * invocation of a CreatesMustCallFor method with "this" as its expression is permitted in the + * constructor of the relevant class (invoking a constructor already creates an obligation). If + * none of these conditions are true for any of the expressions, this method issues a + * reset.not.owning error. + * + *

For soundness, this method also guarantees that if any of the expressions in the + * CreatesMustCallFor annotation has a tracked Obligation, any tracked resource aliases of it will + * be removed (lest the analysis conclude that it is already closed because one of these aliases + * was closed before the method was invoked). Aliases created after the CreatesMustCallFor method + * is invoked are still permitted. + * + * @param obligations the currently-tracked Obligations; this value is side-effected if there is + * an Obligation in it which tracks any expression from the CreatesMustCallFor annotation as + * one of its resource aliases + * @param node a method invocation node, invoking a method with a CreatesMustCallFor annotation + */ + private void checkCreatesMustCallForInvocation( + Set obligations, MethodInvocationNode node) { + + TreePath currentPath = typeFactory.getPath(node.getTree()); + List cmcfExpressions = + CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( + node, typeFactory, typeFactory); + List missing = new ArrayList<>(0); + for (JavaExpression expression : cmcfExpressions) { + if (!isValidCreatesMustCallForExpression(obligations, expression, currentPath)) { + missing.add(expression); + } } - /** - * Updates a set of Obligations to account for an assignment. Assigning to an owning field might - * remove Obligations, assigning to a resource variable might remove obligations, assigning to a - * new local variable might modify an Obligation (by increasing the size of its resource alias - * set), etc. - * - * @param obligations the set of Obligations to update - * @param cfg the control flow graph that contains {@code assignmentNode} - * @param assignmentNode the assignment - */ - private void updateObligationsForAssignment( - Set obligations, ControlFlowGraph cfg, AssignmentNode assignmentNode) { - Node lhs = assignmentNode.getTarget(); - Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); - if (lhsElement == null) { - return; - } - // Use the temporary variable for the rhs if it exists. - Node rhs = NodeUtils.removeCasts(assignmentNode.getExpression()); - rhs = getTempVarOrNode(rhs); - - // Ownership transfer to @Owning field. - if (lhsElement.getKind() == ElementKind.FIELD) { - boolean isOwningField = !noLightweightOwnership && typeFactory.hasOwning(lhsElement); - // Check that the must-call obligations of the lhs have been satisfied, if the field is - // non-final and owning. - if (isOwningField - && typeFactory.canCreateObligations() - && !ElementUtils.isFinal(lhsElement)) { - checkReassignmentToField(obligations, assignmentNode); - } + if (missing.isEmpty()) { + // All expressions matched one of the rules, so the invocation is valid. + return; + } - // Remove Obligations from local variables, now that the owning field is responsible. - // (When obligation creation is turned off, non-final fields cannot take ownership.) - if (isOwningField - && rhs instanceof LocalVariableNode - && (typeFactory.canCreateObligations() || ElementUtils.isFinal(lhsElement))) { - - LocalVariableNode rhsVar = (LocalVariableNode) rhs; - - MethodTree containingMethod = cfg.getContainingMethod(assignmentNode.getTree()); - boolean inConstructor = - containingMethod != null && TreeUtils.isConstructor(containingMethod); - - // Determine which obligations this field assignment can clear. In a constructor, - // assignments to `this.field` only clears obligations on normal return, since - // on exception `this` becomes inaccessible. - Set toClear; - if (inConstructor - && lhs instanceof FieldAccessNode - && ((FieldAccessNode) lhs).getReceiver() instanceof ThisNode) { - toClear = Collections.singleton(MethodExitKind.NORMAL_RETURN); - } else { - toClear = MethodExitKind.ALL; - } - - @Nullable Element enclosingElem = lhsElement.getEnclosingElement(); - @Nullable TypeElement enclosingType = - enclosingElem != null - ? ElementUtils.enclosingTypeElement(enclosingElem) - : null; - - // Assigning to an owning field is sufficient to clear a must-call alias obligation - // in a constructor, if the enclosing class has at most one @Owning field. If the - // class had multiple owning fields, then a soundness bug would occur: the must call - // alias relationship would allow the whole class' obligation to be fulfilled by - // closing only one of the parameters passed to the constructor (but the other - // owning fields might not actually have had their obligations fulfilled). See test - // case checker/tests/resourceleak/TwoOwningMCATest.java for an example. - if (hasAtMostOneOwningField(enclosingType)) { - removeObligationsContainingVar( - obligations, - rhsVar, - MustCallAliasHandling.NO_SPECIAL_HANDLING, - toClear); - } else { - removeObligationsContainingVar( - obligations, - rhsVar, - MustCallAliasHandling - .RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, - toClear); - } - - // Finally, if any obligations containing this var remain, then closing the field - // will satisfy them. Here we are overly cautious and only track final fields. In - // the future we could perhaps relax this guard with careful handling for field - // reassignments. - if (ElementUtils.isFinal(lhsElement)) { - addAliasToObligationsContainingVar( - obligations, - rhsVar, - new ResourceAlias( - JavaExpression.fromNode(lhs), lhsElement, lhs.getTree())); - } - } - } else if (lhs instanceof LocalVariableNode) { - LocalVariableNode lhsVar = (LocalVariableNode) lhs; - updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); - } + // Special case for invocations of CreatesMustCallFor("this") methods in the constructor. + if (missing.size() == 1) { + JavaExpression expression = missing.get(0); + if (expression instanceof ThisReference && TreePathUtil.inConstructor(currentPath)) { + return; + } } - /** - * Returns true iff the given type element has 0 or 1 @Owning fields. - * - * @param element an element for a class - * @return true iff element has no more than 1 owning field - */ - private boolean hasAtMostOneOwningField(TypeElement element) { - List fields = - ElementUtils.getAllFieldsIn(element, typeFactory.getElementUtils()); - // Has an owning field already been encountered? - boolean hasOwningField = false; - for (VariableElement field : fields) { - if (typeFactory.hasOwning(field)) { - if (hasOwningField) { - return false; - } else { - hasOwningField = true; - } + StringJoiner missingStrs = new StringJoiner(","); + for (JavaExpression m : missing) { + String s = m.toString(); + missingStrs.add(s.equals("this") ? s + " of type " + m.getType() : s); + } + checker.reportError( + node.getTree(), + "reset.not.owning", + node.getTarget().getMethod().getSimpleName().toString(), + missingStrs.toString()); + } + + /** + * Checks the validity of the given expression from an invoked method's {@link + * org.checkerframework.checker.mustcall.qual.CreatesMustCallFor} annotation. Helper method for + * {@link #checkCreatesMustCallForInvocation(Set, MethodInvocationNode)}. + * + *

An expression is valid if one of the following conditions is true: + * + *

    + *
  • 1) the expression is an owning pointer, + *
  • 2) the expression already has a tracked Obligation (i.e. there is already a resource + * alias in some Obligation's resource alias set that refers to the expression), or + *
  • 3) the method in which the invocation occurs also has an @CreatesMustCallFor annotation, + * with the same expression. + *
+ * + * @param obligations the currently-tracked Obligations; this value is side-effected if there is + * an Obligation in it which tracks {@code expression} as one of its resource aliases + * @param expression an element of a method's @CreatesMustCallFor annotation + * @param invocationPath the path to the invocation of the method from whose @CreateMustCallFor + * annotation {@code expression} came + * @return true iff the expression is valid, as defined above + */ + private boolean isValidCreatesMustCallForExpression( + Set obligations, JavaExpression expression, TreePath invocationPath) { + if (expression instanceof FieldAccess) { + Element elt = ((FieldAccess) expression).getField(); + if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { + // The expression is an Owning field. This satisfies case 1. + return true; + } + } else if (expression instanceof LocalVariable) { + Element elt = ((LocalVariable) expression).getElement(); + if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { + // The expression is an Owning formal parameter. Note that this cannot actually + // be a local variable (despite expressions's type being LocalVariable) because + // the @Owning annotation can only be written on methods, parameters, and fields; + // formal parameters are also represented by LocalVariable in the bodies of methods. + // This satisfies case 1. + return true; + } else { + Obligation toRemove = null; + Obligation toAdd = null; + for (Obligation obligation : obligations) { + ResourceAlias alias = obligation.getResourceAlias(expression); + if (alias != null) { + // This satisfies case 2 above. Remove all its aliases, then return below. + if (toRemove != null) { + throw new TypeSystemError( + "tried to remove multiple sets containing a reset expression at" + " once"); } + toRemove = obligation; + toAdd = new Obligation(ImmutableSet.of(alias), obligation.whenToEnforce); + } } - // We haven't seen two owning fields, so there must be 1 or 0. - return true; - } - - /** - * Add a new alias to all Obligations that have {@code var} in their resource-alias set. This - * method should be used when {@code var} and {@code newAlias} definitively point to the same - * object in memory. - * - * @param obligations the set of Obligations to modify - * @param var a variable - * @param newAlias a new {@link ResourceAlias} to add - */ - private void addAliasToObligationsContainingVar( - Set obligations, LocalVariableNode var, ResourceAlias newAlias) { - Iterator it = obligations.iterator(); - List newObligations = new ArrayList<>(); - - while (it.hasNext()) { - Obligation obligation = it.next(); - if (obligation.canBeSatisfiedThrough(var)) { - it.remove(); - - Set newAliases = new LinkedHashSet<>(obligation.resourceAliases); - newAliases.add(newAlias); - newObligations.add(new Obligation(newAliases, obligation.whenToEnforce)); - } + if (toRemove != null) { + obligations.remove(toRemove); + obligations.add(toAdd); + // This satisfies case 2. + return true; } + } + } - obligations.addAll(newObligations); + // TODO: Getting this every time is inefficient if a method has many @CreatesMustCallFor + // annotations, but that should be rare. + MethodTree callerMethodTree = TreePathUtil.enclosingMethod(invocationPath); + if (callerMethodTree == null) { + return false; + } + ExecutableElement callerMethodElt = TreeUtils.elementFromDeclaration(callerMethodTree); + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + List callerCmcfValues = + ResourceLeakVisitor.getCreatesMustCallForValues(callerMethodElt, mcAtf, typeFactory); + if (callerCmcfValues.isEmpty()) { + return false; + } + for (String callerCmcfValue : callerCmcfValues) { + JavaExpression callerTarget; + try { + callerTarget = + StringToJavaExpression.atMethodBody(callerCmcfValue, callerMethodTree, checker); + } catch (JavaExpressionParseException e) { + // Do not issue an error here, because it would be a duplicate. + // The error will be issued by the Transfer class of the checker, + // via the CreatesMustCallForElementSupplier interface. + callerTarget = null; + } + + if (areSame(expression, callerTarget)) { + // This satisfies case 3. + return true; + } + } + return false; + } + + /** + * Checks whether the two JavaExpressions are the same. This is identical to calling equals() on + * one of them, with two exceptions: the second expression can be null, and {@code this} + * references are compared using their underlying type. (ThisReference#equals always returns true, + * which is probably a bug and isn't accurate in the case of nested classes.) + * + * @param target a JavaExpression + * @param enclosingTarget another, possibly null, JavaExpression + * @return true iff they represent the same program element + */ + private boolean areSame(JavaExpression target, @Nullable JavaExpression enclosingTarget) { + if (enclosingTarget == null) { + return false; + } + if (enclosingTarget instanceof ThisReference && target instanceof ThisReference) { + return enclosingTarget.getType().toString().equals(target.getType().toString()); + } else { + return enclosingTarget.equals(target); + } + } + + /** + * Given a node representing a method or constructor call, updates the set of Obligations to + * account for the result, which is treated as a new resource alias. Adds the new resource alias + * to the set of an Obligation in {@code obligations}: either an existing Obligation if the result + * is definitely resource-aliased with it, or a new Obligation if not. + * + * @param obligations the currently-tracked Obligations. This is always side-effected: either a + * new resource alias is added to the resource alias set of an existing Obligation, or a new + * Obligation with a single-element resource alias set is created and added. + * @param node the invocation node whose result is to be tracked; must be {@link + * MethodInvocationNode} or {@link ObjectCreationNode} + */ + /*package-private*/ void updateObligationsWithInvocationResult( + Set obligations, Node node) { + Tree tree = node.getTree(); + // Only track the result of the call if there is a temporary variable for the call node + // (because if there is no temporary, then the invocation must produce an untrackable value, + // such as a primitive type). + LocalVariableNode tmpVar = typeFactory.getTempVarForNode(node); + if (tmpVar == null) { + return; } - /** - * Remove any Obligations that contain {@code var} in their resource-alias set. - * - * @param obligations the set of Obligations to modify - * @param var a variable - */ - /*package-private*/ void removeObligationsContainingVar( - Set obligations, LocalVariableNode var) { - removeObligationsContainingVar( - obligations, var, MustCallAliasHandling.NO_SPECIAL_HANDLING, MethodExitKind.ALL); + // `mustCallAliases` is a (possibly-empty) list of arguments passed in a MustCallAlias + // position. + List mustCallAliases = getMustCallAliasArgumentNodes(node); + // If call returns @This, add the receiver to mustCallAliases. + if (node instanceof MethodInvocationNode + && typeFactory.returnsThis((MethodInvocationTree) tree)) { + mustCallAliases.add( + removeCastsAndGetTmpVarIfPresent( + ((MethodInvocationNode) node).getTarget().getReceiver())); } - /** - * Helper type for {@link #removeObligationsContainingVar(Set, LocalVariableNode, - * MustCallAliasHandling, Set)} - */ - private enum MustCallAliasHandling { - /** - * Obligations derived from {@link MustCallAlias} parameters do not require special - * handling, and they should be removed like any other obligation. - */ - NO_SPECIAL_HANDLING, - - /** - * Obligations derived from {@link MustCallAlias} parameters are not satisfied and should be - * retained. - */ - RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, + if (mustCallAliases.isEmpty()) { + // If mustCallAliases is an empty List, add tmpVarAsResourceAlias to a new set. + ResourceAlias tmpVarAsResourceAlias = new ResourceAlias(new LocalVariable(tmpVar), tree); + obligations.add(new Obligation(ImmutableSet.of(tmpVarAsResourceAlias), MethodExitKind.ALL)); + } else { + for (Node mustCallAlias : mustCallAliases) { + if (mustCallAlias instanceof FieldAccessNode) { + // Do not track the call result if the MustCallAlias argument is a field. + // Handling of @Owning fields is a completely separate check, and there is never + // a need to track an alias of a non-@Owning field, as by definition such a + // field does not have must-call obligations! + } else if (mustCallAlias instanceof LocalVariableNode) { + // If mustCallAlias is a local variable already being tracked, add + // tmpVarAsResourceAlias to the set containing mustCallAlias. + Obligation obligationContainingMustCallAlias = + getObligationForVar(obligations, (LocalVariableNode) mustCallAlias); + if (obligationContainingMustCallAlias != null) { + ResourceAlias tmpVarAsResourceAlias = + new ResourceAlias( + new LocalVariable(tmpVar), + tmpVar.getElement(), + tree, + obligationContainingMustCallAlias.derivedFromMustCallAlias()); + Set newResourceAliasSet = + FluentIterable.from(obligationContainingMustCallAlias.resourceAliases) + .append(tmpVarAsResourceAlias) + .toSet(); + obligations.remove(obligationContainingMustCallAlias); + obligations.add( + new Obligation( + newResourceAliasSet, obligationContainingMustCallAlias.whenToEnforce)); + // It is not an error if there is no Obligation containing the must-call + // alias. In that case, what has usually happened is that no Obligation was + // created in the first place. + // For example, when checking the invocation of a "wrapper stream" + // constructor, if the argument in the must-call alias position is some + // stream with no must-call obligations like a ByteArrayInputStream, then no + // Obligation object will have been created for it and therefore + // obligationContainingMustCallAlias will be null. + } + } + } } - - /** - * Remove Obligations that contain {@code var} in their resource-alias set. - * - *

Some operations do not satisfy all Obligations. For instance, assigning to a field in a - * constructor only satisfies Obligations when the constructor exits normally (i.e. without - * throwing an exception). The last two arguments to this method can be used to retain some - * Obligations in special circumstances. - * - * @param obligations the set of Obligations to modify - * @param var a variable - * @param mustCallAliasHandling how to treat Obligations derived from {@link MustCallAlias} - * parameters - * @param whatToClear the kind of Obligations to remove - */ - private void removeObligationsContainingVar( - Set obligations, - LocalVariableNode var, - MustCallAliasHandling mustCallAliasHandling, - Set whatToClear) { - List newObligations = new ArrayList<>(); - - Iterator it = obligations.iterator(); - while (it.hasNext()) { - Obligation obligation = it.next(); - - if (obligation.canBeSatisfiedThrough(var) - && (mustCallAliasHandling == MustCallAliasHandling.NO_SPECIAL_HANDLING - || !obligation.derivedFromMustCallAlias())) { - it.remove(); - - Set whenToEnforce = new HashSet<>(obligation.whenToEnforce); - whenToEnforce.removeAll(whatToClear); - - if (!whenToEnforce.isEmpty()) { - newObligations.add(new Obligation(obligation.resourceAliases, whenToEnforce)); - } - } - } - - obligations.addAll(newObligations); + } + + /** + * Returns true if the result of the given method or constructor invocation node should be tracked + * in {@code obligations}. In some cases, there is no need to track the result because the + * must-call obligations are already satisfied in some other way or there cannot possibly be + * must-call obligations because of the structure of the code. + * + *

Specifically, an invocation result does NOT need to be tracked if any of the following is + * true: + * + *

    + *
  • The invocation is a call to a {@code this()} or {@code super()} constructor. + *
  • The method's return type is annotated with MustCallAlias and the argument passed in this + * invocation in the corresponding position is an owning field. + *
  • The method's return type is non-owning, which can either be because the method has no + * return type or because the return type is annotated with {@link NotOwning}. + *
+ * + *

This method can also side-effect {@code obligations}, if node is a super or this constructor + * call with MustCallAlias annotations, by removing that Obligation. + * + * @param obligations the current set of Obligations, which may be side-effected + * @param node the invocation node to check; must be {@link MethodInvocationNode} or {@link + * ObjectCreationNode} + * @return true iff the result of {@code node} should be tracked in {@code obligations} + */ + private boolean shouldTrackInvocationResult(Set obligations, Node node) { + Tree callTree = node.getTree(); + if (callTree.getKind() == Tree.Kind.NEW_CLASS) { + // Constructor results from new expressions are tracked as long as the declared type has + // a non-empty @MustCall annotation. + NewClassTree newClassTree = (NewClassTree) callTree; + ExecutableElement executableElement = TreeUtils.elementFromUse(newClassTree); + TypeElement typeElt = TypesUtils.getTypeElement(ElementUtils.getType(executableElement)); + return typeElt == null + || !typeFactory.hasEmptyMustCallValue(typeElt) + || !typeFactory.hasEmptyMustCallValue(newClassTree); } - /** - * Update a set of tracked Obligations to account for a (pseudo-)assignment to some variable, as - * in a gen-kill dataflow analysis problem. That is, add ("gen") and remove ("kill") resource - * aliases from Obligations in the {@code obligations} set as appropriate based on the - * (pseudo-)assignment performed by {@code node}. This method may also remove an Obligation - * entirely if the analysis concludes that its resource alias set is empty because the last - * tracked alias to it has been overwritten (including checking that the must-call obligations - * were satisfied before the assignment). - * - *

Pseudo-assignments may include operations that "assign" to a temporary variable, exposing - * the possible value flow into the variable. E.g., for a ternary expression {@code b ? x : y} - * whose temporary variable is {@code t}, this method may process "assignments" {@code t = x} - * and {@code t = y}, thereby capturing the two possible values of {@code t}. - * - * @param obligations the tracked Obligations, which will be side-effected - * @param node the node performing the pseudo-assignment; it is not necessarily an assignment - * node - * @param lhsVar the left-hand side variable for the pseudo-assignment - * @param rhs the right-hand side for the pseudo-assignment, which must have been converted to a - * temporary variable (via a call to {@link - * ResourceLeakAnnotatedTypeFactory#getTempVarForNode}) - */ - /*package-private*/ void updateObligationsForPseudoAssignment( - Set obligations, Node node, LocalVariableNode lhsVar, Node rhs) { - // Replacements to eventually perform in Obligations. This map is kept to avoid a - // ConcurrentModificationException in the loop below. - Map replacements = new LinkedHashMap<>(); - // Cache to re-use on subsequent iterations. - ResourceAlias aliasForAssignment = null; - for (Obligation obligation : obligations) { - // This is a non-null value iff the resource alias set for obligation needs to - // change because of the pseudo-assignment. The value of this variable is the new - // alias set for `obligation` if it is non-null. - Set newResourceAliasesForObligation = null; - - // Always kill the lhs var if it is present in the resource alias set for this - // Obligation by removing it from the resource alias set. - ResourceAlias aliasForLhs = obligation.getResourceAlias(lhsVar); - if (aliasForLhs != null) { - newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases); - newResourceAliasesForObligation.remove(aliasForLhs); - } - // If rhs is a variable tracked in the Obligation's resource alias set, gen the lhs - // by adding it to the resource alias set. - if (rhs instanceof LocalVariableNode - && obligation.canBeSatisfiedThrough((LocalVariableNode) rhs)) { - LocalVariableNode rhsVar = (LocalVariableNode) rhs; - if (newResourceAliasesForObligation == null) { - newResourceAliasesForObligation = - new LinkedHashSet<>(obligation.resourceAliases); - } - if (aliasForAssignment == null) { - // It is possible to observe assignments to temporary variables, e.g., - // synthetic assignments to ternary expression variables in the CFG. For such - // cases, use the tree associated with the temp var for the resource alias, - // as that is the tree where errors should be reported. - Tree treeForAlias = - typeFactory.isTempVar(lhsVar) - ? typeFactory.getTreeForTempVar(lhsVar) - : node.getTree(); - aliasForAssignment = new ResourceAlias(new LocalVariable(lhsVar), treeForAlias); - } - newResourceAliasesForObligation.add(aliasForAssignment); - // Remove temp vars from tracking once they are assigned to another location. - if (typeFactory.isTempVar(rhsVar)) { - ResourceAlias aliasForRhs = obligation.getResourceAlias(rhsVar); - if (aliasForRhs != null) { - newResourceAliasesForObligation.remove(aliasForRhs); - } - } - } + // Now callTree.getKind() == Tree.Kind.METHOD_INVOCATION. + MethodInvocationTree methodInvokeTree = (MethodInvocationTree) callTree; + + if (TreeUtils.isSuperConstructorCall(methodInvokeTree) + || TreeUtils.isThisConstructorCall(methodInvokeTree)) { + List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); + // If there is a MustCallAlias argument that is also in the set of Obligations, then + // remove it; its must-call obligation has been fulfilled by being passed on to the + // MustCallAlias constructor (because a this/super constructor call can only occur in + // the body of another constructor). + for (Node mustCallAliasArgument : mustCallAliasArguments) { + if (mustCallAliasArgument instanceof LocalVariableNode) { + removeObligationsContainingVar(obligations, (LocalVariableNode) mustCallAliasArgument); + } + } + return false; + } + return !returnTypeIsMustCallAliasWithUntrackable((MethodInvocationNode) node) + && shouldTrackReturnType((MethodInvocationNode) node); + } + + /** + * Returns true if this node represents a method invocation of a must-call-alias method, where the + * argument in the must-call-alias position is untrackable: an owning field or a pointer that is + * guaranteed to be non-owning, such as {@code "this"} or a non-owning field. Owning fields are + * handled by the rest of the checker, not by this algorithm, so they are "untrackable". + * Non-owning fields and this nodes are guaranteed to be non-owning, and are therefore also + * "untrackable". Because both owning and non-owning fields are untrackable (and there are no + * other kinds of fields), this method returns true for all field accesses. + * + * @param node a method invocation node + * @return true if this is the invocation of a method whose return type is MCA with an owning + * field or a definitely non-owning pointer + */ + private boolean returnTypeIsMustCallAliasWithUntrackable(MethodInvocationNode node) { + List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); + for (Node mustCallAliasArg : mustCallAliasArguments) { + if (!(mustCallAliasArg instanceof FieldAccessNode || mustCallAliasArg instanceof ThisNode)) { + return false; + } + } + return !mustCallAliasArguments.isEmpty(); + } + + /** + * Checks if {@code node} is either directly enclosed by a {@link TypeCastNode}, by looking at the + * successor block in the CFG. In this case the enclosing operator is a "no-op" that evaluates to + * the same value as {@code node}. This method is only used within {@link + * #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, Deque)} to ensure + * Obligations are propagated to cast nodes properly. It relies on the assumption that a {@link + * TypeCastNode} will only appear in a CFG as the first node in a block. + * + * @param node the CFG node + * @return {@code true} if {@code node} is in a {@link SingleSuccessorBlock} {@code b}, the first + * {@link Node} in {@code b}'s successor block is a {@link TypeCastNode}, and {@code node} is + * an operand of the successor node; {@code false} otherwise + */ + private boolean inCast(Node node) { + if (!(node.getBlock() instanceof SingleSuccessorBlock)) { + return false; + } + Block successorBlock = ((SingleSuccessorBlock) node.getBlock()).getSuccessor(); + if (successorBlock != null) { + List succNodes = successorBlock.getNodes(); + if (succNodes.size() > 0) { + Node succNode = succNodes.get(0); + if (succNode instanceof TypeCastNode) { + return ((TypeCastNode) succNode).getOperand().equals(node); + } + } + } + return false; + } + + /** + * Transfer ownership of any locals passed as arguments to {@code @Owning} parameters at a method + * or constructor call by removing the Obligations corresponding to those locals. + * + * @param obligations the current set of Obligations, which is side-effected to remove Obligations + * for locals that are passed as owning parameters to the method or constructor + * @param node a method or constructor invocation node + * @param exceptionType a description of the outgoing CFG edge from the node: null to + * indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given + * throwable class was thrown + */ + private void removeObligationsAtOwnershipTransferToParameters( + Set obligations, Node node, @Nullable TypeMirror exceptionType) { + + if (exceptionType != null) { + // Do not transfer ownership if the called method throws an exception. + return; + } - // If no changes were made to the resource alias set, there is no need to update the - // Obligation. - if (newResourceAliasesForObligation == null) { - continue; - } + if (noLightweightOwnership) { + // Never transfer ownership to parameters, matching the default in the analysis built + // into Eclipse. + return; + } - if (newResourceAliasesForObligation.isEmpty()) { - // Because the last reference to the resource has been overwritten, check the - // must-call obligation. - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - checkMustCall( - obligation, - typeFactory.getStoreBefore(node), - mcAtf.getStoreBefore(node), - "variable overwritten by assignment " + node.getTree()); - replacements.put(obligation, null); - } else { - replacements.put( - obligation, - new Obligation(newResourceAliasesForObligation, obligation.whenToEnforce)); + List arguments = getArgumentsOfInvocation(node); + List parameters = getParametersOfInvocation(node); + + if (arguments.size() != parameters.size()) { + // This could happen, e.g., with varargs, or with strange cases like generated Enum + // constructors. In the varargs case (i.e. if the varargs parameter is owning), + // only the first of the varargs arguments will actually get transferred: the second + // and later varargs arguments will continue to be tracked at the call-site. + // For now, just skip this case - the worst that will happen is a false positive in + // cases like the varargs one described above. + // TODO allow for ownership transfer here if needed in future + return; + } + for (int i = 0; i < arguments.size(); i++) { + Node n = removeCastsAndGetTmpVarIfPresent(arguments.get(i)); + if (n instanceof LocalVariableNode) { + LocalVariableNode local = (LocalVariableNode) n; + if (varTrackedInObligations(obligations, local)) { + + // check if parameter has an @Owning annotation + VariableElement parameter = parameters.get(i); + if (typeFactory.hasOwning(parameter)) { + Obligation localObligation = getObligationForVar(obligations, local); + // Passing to an owning parameter is not sufficient to resolve the + // obligation created from a MustCallAlias parameter, because the + // containing method must actually return the value. + if (!localObligation.derivedFromMustCallAlias()) { + // Transfer ownership! + obligations.remove(localObligation); } + } } + } + } + } + + /** + * If the return type of the enclosing method is {@code @Owning}, treat the must-call obligations + * of the return expression as satisfied by removing all references to them from {@code + * obligations}. + * + * @param obligations the current set of tracked Obligations. If ownership is transferred, it is + * side-effected to remove any Obligations that are resource-aliased to the return node. + * @param cfg the CFG of the enclosing method + * @param node a return node + */ + private void updateObligationsForOwningReturn( + Set obligations, ControlFlowGraph cfg, ReturnNode node) { + if (isTransferOwnershipAtReturn(cfg)) { + Node returnExpr = node.getResult(); + returnExpr = getTempVarOrNode(returnExpr); + if (returnExpr instanceof LocalVariableNode) { + removeObligationsContainingVar(obligations, (LocalVariableNode) returnExpr); + } + } + } + + /** + * Helper method that gets the temporary node corresponding to {@code node}, if one exists. If + * not, this method returns its input. + * + * @param node a node + * @return the temporary for node, or node if no temporary exists + */ + /*package-private*/ Node getTempVarOrNode(Node node) { + Node temp = typeFactory.getTempVarForNode(node); + if (temp != null) { + return temp; + } + return node; + } + + /** + * Should ownership be transferred to the return type of the method corresponding to a CFG? + * Returns true when there is no {@link NotOwning} annotation on the return type. + * + * @param cfg the CFG of the method + * @return true iff ownership should be transferred to the return type of the method corresponding + * to a CFG + */ + private boolean isTransferOwnershipAtReturn(ControlFlowGraph cfg) { + if (noLightweightOwnership) { + // If not using LO, default to always transfer at return, just like Eclipse does. + return true; + } - // Finally, update the set of Obligations according to the replacements. - for (Map.Entry entry : replacements.entrySet()) { - obligations.remove(entry.getKey()); - if (entry.getValue() != null && !entry.getValue().resourceAliases.isEmpty()) { - obligations.add(entry.getValue()); - } + UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); + if (underlyingAST instanceof UnderlyingAST.CFGMethod) { + // TODO: lambdas? In that case false is returned below, which means that ownership will + // not be transferred. + MethodTree method = ((UnderlyingAST.CFGMethod) underlyingAST).getMethod(); + ExecutableElement executableElement = TreeUtils.elementFromDeclaration(method); + return !typeFactory.hasNotOwning(executableElement); + } + return false; + } + + /** + * Updates a set of Obligations to account for an assignment. Assigning to an owning field might + * remove Obligations, assigning to a resource variable might remove obligations, assigning to a + * new local variable might modify an Obligation (by increasing the size of its resource alias + * set), etc. + * + * @param obligations the set of Obligations to update + * @param cfg the control flow graph that contains {@code assignmentNode} + * @param assignmentNode the assignment + */ + private void updateObligationsForAssignment( + Set obligations, ControlFlowGraph cfg, AssignmentNode assignmentNode) { + Node lhs = assignmentNode.getTarget(); + Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); + if (lhsElement == null) { + return; + } + // Use the temporary variable for the rhs if it exists. + Node rhs = NodeUtils.removeCasts(assignmentNode.getExpression()); + rhs = getTempVarOrNode(rhs); + + // Ownership transfer to @Owning field. + if (lhsElement.getKind() == ElementKind.FIELD) { + boolean isOwningField = !noLightweightOwnership && typeFactory.hasOwning(lhsElement); + // Check that the must-call obligations of the lhs have been satisfied, if the field is + // non-final and owning. + if (isOwningField + && typeFactory.canCreateObligations() + && !ElementUtils.isFinal(lhsElement)) { + checkReassignmentToField(obligations, assignmentNode); + } + + // Remove Obligations from local variables, now that the owning field is responsible. + // (When obligation creation is turned off, non-final fields cannot take ownership.) + if (isOwningField + && rhs instanceof LocalVariableNode + && (typeFactory.canCreateObligations() || ElementUtils.isFinal(lhsElement))) { + + LocalVariableNode rhsVar = (LocalVariableNode) rhs; + + MethodTree containingMethod = cfg.getContainingMethod(assignmentNode.getTree()); + boolean inConstructor = + containingMethod != null && TreeUtils.isConstructor(containingMethod); + + // Determine which obligations this field assignment can clear. In a constructor, + // assignments to `this.field` only clears obligations on normal return, since + // on exception `this` becomes inaccessible. + Set toClear; + if (inConstructor + && lhs instanceof FieldAccessNode + && ((FieldAccessNode) lhs).getReceiver() instanceof ThisNode) { + toClear = Collections.singleton(MethodExitKind.NORMAL_RETURN); + } else { + toClear = MethodExitKind.ALL; + } + + @Nullable Element enclosingElem = lhsElement.getEnclosingElement(); + @Nullable TypeElement enclosingType = + enclosingElem != null ? ElementUtils.enclosingTypeElement(enclosingElem) : null; + + // Assigning to an owning field is sufficient to clear a must-call alias obligation + // in a constructor, if the enclosing class has at most one @Owning field. If the + // class had multiple owning fields, then a soundness bug would occur: the must call + // alias relationship would allow the whole class' obligation to be fulfilled by + // closing only one of the parameters passed to the constructor (but the other + // owning fields might not actually have had their obligations fulfilled). See test + // case checker/tests/resourceleak/TwoOwningMCATest.java for an example. + if (hasAtMostOneOwningField(enclosingType)) { + removeObligationsContainingVar( + obligations, rhsVar, MustCallAliasHandling.NO_SPECIAL_HANDLING, toClear); + } else { + removeObligationsContainingVar( + obligations, + rhsVar, + MustCallAliasHandling.RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, + toClear); + } + + // Finally, if any obligations containing this var remain, then closing the field + // will satisfy them. Here we are overly cautious and only track final fields. In + // the future we could perhaps relax this guard with careful handling for field + // reassignments. + if (ElementUtils.isFinal(lhsElement)) { + addAliasToObligationsContainingVar( + obligations, + rhsVar, + new ResourceAlias(JavaExpression.fromNode(lhs), lhsElement, lhs.getTree())); + } + } + } else if (lhs instanceof LocalVariableNode) { + LocalVariableNode lhsVar = (LocalVariableNode) lhs; + updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); + } + } + + /** + * Returns true iff the given type element has 0 or 1 @Owning fields. + * + * @param element an element for a class + * @return true iff element has no more than 1 owning field + */ + private boolean hasAtMostOneOwningField(TypeElement element) { + List fields = + ElementUtils.getAllFieldsIn(element, typeFactory.getElementUtils()); + // Has an owning field already been encountered? + boolean hasOwningField = false; + for (VariableElement field : fields) { + if (typeFactory.hasOwning(field)) { + if (hasOwningField) { + return false; + } else { + hasOwningField = true; } + } + } + // We haven't seen two owning fields, so there must be 1 or 0. + return true; + } + + /** + * Add a new alias to all Obligations that have {@code var} in their resource-alias set. This + * method should be used when {@code var} and {@code newAlias} definitively point to the same + * object in memory. + * + * @param obligations the set of Obligations to modify + * @param var a variable + * @param newAlias a new {@link ResourceAlias} to add + */ + private void addAliasToObligationsContainingVar( + Set obligations, LocalVariableNode var, ResourceAlias newAlias) { + Iterator it = obligations.iterator(); + List newObligations = new ArrayList<>(); + + while (it.hasNext()) { + Obligation obligation = it.next(); + if (obligation.canBeSatisfiedThrough(var)) { + it.remove(); + + Set newAliases = new LinkedHashSet<>(obligation.resourceAliases); + newAliases.add(newAlias); + + newObligations.add(new Obligation(newAliases, obligation.whenToEnforce)); + } } + obligations.addAll(newObligations); + } + + /** + * Remove any Obligations that contain {@code var} in their resource-alias set. + * + * @param obligations the set of Obligations to modify + * @param var a variable + */ + /*package-private*/ void removeObligationsContainingVar( + Set obligations, LocalVariableNode var) { + removeObligationsContainingVar( + obligations, var, MustCallAliasHandling.NO_SPECIAL_HANDLING, MethodExitKind.ALL); + } + + /** + * Helper type for {@link #removeObligationsContainingVar(Set, LocalVariableNode, + * MustCallAliasHandling, Set)} + */ + private enum MustCallAliasHandling { /** - * Issues an error if the given re-assignment to a non-final, owning field is not valid. A - * re-assignment is valid if the called methods type of the lhs before the assignment satisfies - * the must-call obligations of the field. - * - *

Despite the name of this method, the argument {@code node} might be the first and only - * assignment to a field. - * - * @param obligations current tracked Obligations - * @param node an assignment to a non-final, owning field + * Obligations derived from {@link MustCallAlias} parameters do not require special handling, + * and they should be removed like any other obligation. */ - private void checkReassignmentToField(Set obligations, AssignmentNode node) { - - Node lhsNode = node.getTarget(); + NO_SPECIAL_HANDLING, - if (!(lhsNode instanceof FieldAccessNode)) { - throw new TypeSystemError( - "checkReassignmentToField: non-field node " - + node - + " of class " - + node.getClass()); - } - - FieldAccessNode lhs = (FieldAccessNode) lhsNode; - Node receiver = lhs.getReceiver(); + /** + * Obligations derived from {@link MustCallAlias} parameters are not satisfied and should be + * retained. + */ + RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, + } + + /** + * Remove Obligations that contain {@code var} in their resource-alias set. + * + *

Some operations do not satisfy all Obligations. For instance, assigning to a field in a + * constructor only satisfies Obligations when the constructor exits normally (i.e. without + * throwing an exception). The last two arguments to this method can be used to retain some + * Obligations in special circumstances. + * + * @param obligations the set of Obligations to modify + * @param var a variable + * @param mustCallAliasHandling how to treat Obligations derived from {@link MustCallAlias} + * parameters + * @param whatToClear the kind of Obligations to remove + */ + private void removeObligationsContainingVar( + Set obligations, + LocalVariableNode var, + MustCallAliasHandling mustCallAliasHandling, + Set whatToClear) { + List newObligations = new ArrayList<>(); + + Iterator it = obligations.iterator(); + while (it.hasNext()) { + Obligation obligation = it.next(); + + if (obligation.canBeSatisfiedThrough(var) + && (mustCallAliasHandling == MustCallAliasHandling.NO_SPECIAL_HANDLING + || !obligation.derivedFromMustCallAlias())) { + it.remove(); + + Set whenToEnforce = new HashSet<>(obligation.whenToEnforce); + whenToEnforce.removeAll(whatToClear); + + if (!whenToEnforce.isEmpty()) { + newObligations.add(new Obligation(obligation.resourceAliases, whenToEnforce)); + } + } + } - if (permitStaticOwning && receiver instanceof ClassNameNode) { - return; - } + obligations.addAll(newObligations); + } + + /** + * Update a set of tracked Obligations to account for a (pseudo-)assignment to some variable, as + * in a gen-kill dataflow analysis problem. That is, add ("gen") and remove ("kill") resource + * aliases from Obligations in the {@code obligations} set as appropriate based on the + * (pseudo-)assignment performed by {@code node}. This method may also remove an Obligation + * entirely if the analysis concludes that its resource alias set is empty because the last + * tracked alias to it has been overwritten (including checking that the must-call obligations + * were satisfied before the assignment). + * + *

Pseudo-assignments may include operations that "assign" to a temporary variable, exposing + * the possible value flow into the variable. E.g., for a ternary expression {@code b ? x : y} + * whose temporary variable is {@code t}, this method may process "assignments" {@code t = x} and + * {@code t = y}, thereby capturing the two possible values of {@code t}. + * + * @param obligations the tracked Obligations, which will be side-effected + * @param node the node performing the pseudo-assignment; it is not necessarily an assignment node + * @param lhsVar the left-hand side variable for the pseudo-assignment + * @param rhs the right-hand side for the pseudo-assignment, which must have been converted to a + * temporary variable (via a call to {@link + * ResourceLeakAnnotatedTypeFactory#getTempVarForNode}) + */ + /*package-private*/ void updateObligationsForPseudoAssignment( + Set obligations, Node node, LocalVariableNode lhsVar, Node rhs) { + // Replacements to eventually perform in Obligations. This map is kept to avoid a + // ConcurrentModificationException in the loop below. + Map replacements = new LinkedHashMap<>(); + // Cache to re-use on subsequent iterations. + ResourceAlias aliasForAssignment = null; + for (Obligation obligation : obligations) { + // This is a non-null value iff the resource alias set for obligation needs to + // change because of the pseudo-assignment. The value of this variable is the new + // alias set for `obligation` if it is non-null. + Set newResourceAliasesForObligation = null; + + // Always kill the lhs var if it is present in the resource alias set for this + // Obligation by removing it from the resource alias set. + ResourceAlias aliasForLhs = obligation.getResourceAlias(lhsVar); + if (aliasForLhs != null) { + newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases); + newResourceAliasesForObligation.remove(aliasForLhs); + } + // If rhs is a variable tracked in the Obligation's resource alias set, gen the lhs + // by adding it to the resource alias set. + if (rhs instanceof LocalVariableNode + && obligation.canBeSatisfiedThrough((LocalVariableNode) rhs)) { + LocalVariableNode rhsVar = (LocalVariableNode) rhs; + if (newResourceAliasesForObligation == null) { + newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases); + } + if (aliasForAssignment == null) { + // It is possible to observe assignments to temporary variables, e.g., + // synthetic assignments to ternary expression variables in the CFG. For such + // cases, use the tree associated with the temp var for the resource alias, + // as that is the tree where errors should be reported. + Tree treeForAlias = + typeFactory.isTempVar(lhsVar) + ? typeFactory.getTreeForTempVar(lhsVar) + : node.getTree(); + aliasForAssignment = new ResourceAlias(new LocalVariable(lhsVar), treeForAlias); + } + newResourceAliasesForObligation.add(aliasForAssignment); + // Remove temp vars from tracking once they are assigned to another location. + if (typeFactory.isTempVar(rhsVar)) { + ResourceAlias aliasForRhs = obligation.getResourceAlias(rhsVar); + if (aliasForRhs != null) { + newResourceAliasesForObligation.remove(aliasForRhs); + } + } + } + + // If no changes were made to the resource alias set, there is no need to update the + // Obligation. + if (newResourceAliasesForObligation == null) { + continue; + } + + if (newResourceAliasesForObligation.isEmpty()) { + // Because the last reference to the resource has been overwritten, check the + // must-call obligation. + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + checkMustCall( + obligation, + typeFactory.getStoreBefore(node), + mcAtf.getStoreBefore(node), + "variable overwritten by assignment " + node.getTree()); + replacements.put(obligation, null); + } else { + replacements.put( + obligation, new Obligation(newResourceAliasesForObligation, obligation.whenToEnforce)); + } + } - // TODO: it would be better to defer getting the path until after checking - // for a CreatesMustCallFor annotation, because getting the path can be expensive. - // It might be possible to exploit the CFG structure to find the containing - // method (rather than using the path, as below), because if a method is being - // analyzed then it should be the root of the CFG (I think). - TreePath currentPath = typeFactory.getPath(node.getTree()); - MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(currentPath); - - if (enclosingMethodTree == null) { - // The assignment is taking place outside of a method: in a variable declaration's - // initializer or in an initializer block. - // The Resource Leak Checker issues no error if the assignment is a field initializer. - if (node.getTree().getKind() == Tree.Kind.VARIABLE) { - // An assignment to a field that is also a declaration must be a field initializer - // (VARIABLE Trees are only used for declarations). Assignment in a field - // initializer is always permitted. - return; - } else if (permitInitializationLeak - && TreePathUtil.isTopLevelAssignmentInInitializerBlock(currentPath)) { - // This is likely not reassignment; if reassignment, the number of assignments that - // were not warned about is limited to other initializations (is not unbounded). - // This behavior is unsound; see InstanceInitializer.java test case. - return; - } else { - // Issue an error if the field has a non-empty must-call type. - MustCallAnnotatedTypeFactory mcTypeFactory = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotationMirror mcAnno = - mcTypeFactory - .getAnnotatedType(lhs.getElement()) - .getAnnotation(MustCall.class); - List mcValues = - AnnotationUtils.getElementValueArray( - mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); - if (mcValues.isEmpty()) { - return; - } - VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); - checker.reportError( - node.getTree(), - "required.method.not.called", - formatMissingMustCallMethods(mcValues), - "field " + lhsElement.getSimpleName().toString(), - lhsElement.asType().toString(), - "Field assignment outside method or declaration might overwrite field's" - + " current value"); - return; - } - } else if (permitInitializationLeak && TreeUtils.isConstructor(enclosingMethodTree)) { - Element enclosingClassElement = - TreeUtils.elementFromDeclaration(enclosingMethodTree).getEnclosingElement(); - if (ElementUtils.isTypeElement(enclosingClassElement)) { - Element receiverElement = TypesUtils.getTypeElement(receiver.getType()); - if (Objects.equals(enclosingClassElement, receiverElement)) { - return; - } - } - } + // Finally, update the set of Obligations according to the replacements. + for (Map.Entry entry : replacements.entrySet()) { + obligations.remove(entry.getKey()); + if (entry.getValue() != null && !entry.getValue().resourceAliases.isEmpty()) { + obligations.add(entry.getValue()); + } + } + } + + /** + * Issues an error if the given re-assignment to a non-final, owning field is not valid. A + * re-assignment is valid if the called methods type of the lhs before the assignment satisfies + * the must-call obligations of the field. + * + *

Despite the name of this method, the argument {@code node} might be the first and only + * assignment to a field. + * + * @param obligations current tracked Obligations + * @param node an assignment to a non-final, owning field + */ + private void checkReassignmentToField(Set obligations, AssignmentNode node) { + + Node lhsNode = node.getTarget(); + + if (!(lhsNode instanceof FieldAccessNode)) { + throw new TypeSystemError( + "checkReassignmentToField: non-field node " + node + " of class " + node.getClass()); + } - // Check that there is a corresponding CreatesMustCallFor annotation, unless this is - // 1) an assignment to a field of a newly-declared local variable whose scope does not - // extend beyond the method's body (and which therefore could not be targeted by an - // annotation on the method declaration), or 2) the rhs is a null literal (so there's - // nothing to reset). - if (!(receiver instanceof LocalVariableNode - && varTrackedInObligations(obligations, (LocalVariableNode) receiver)) - && !(node.getExpression() instanceof NullLiteralNode)) { - checkEnclosingMethodIsCreatesMustCallFor(node, enclosingMethodTree); - } + FieldAccessNode lhs = (FieldAccessNode) lhsNode; + Node receiver = lhs.getReceiver(); - // The following code handles a special case where the field being assigned is itself - // getting passed in an owning position to another method on the RHS of the assignment. - // For example, if the field's type is a class whose constructor takes another instance - // of itself (such as a node in a linked list) in an owning position, re-assigning the field - // to a new instance that takes the field's value as an owning parameter is safe (the new - // value has taken responsibility for closing the old value). In such a case, it is not - // required that the must-call obligation of the field be satisfied via method calls before - // the assignment, since the invoked method will take ownership of the object previously - // referenced by the field and handle the obligation. This fixes the false positive in - // https://github.com/typetools/checker-framework/issues/5971. - Node rhs = node.getExpression(); - if (!noLightweightOwnership - && (rhs instanceof ObjectCreationNode || rhs instanceof MethodInvocationNode)) { - - List arguments = getArgumentsOfInvocation(rhs); - List parameters = getParametersOfInvocation(rhs); - - if (arguments.size() == parameters.size()) { - for (int i = 0; i < arguments.size(); i++) { - VariableElement param = parameters.get(i); - if (typeFactory.hasOwning(param)) { - Node argument = arguments.get(i); - if (argument.equals(lhs)) { - return; - } - } - } - } else { - // This could happen, e.g., with varargs, or with strange cases like generated Enum - // constructors. In the varargs case (i.e. if the varargs parameter is owning), - // only the first of the varargs arguments will actually get transferred: the second - // and later varargs arguments will continue to be tracked at the call-site. - // For now, just skip this case - the worst that will happen is a false positive in - // cases like the varargs one described above. - // TODO allow for ownership transfer here if needed in future, but for now do - // nothing - } - } + if (permitStaticOwning && receiver instanceof ClassNameNode) { + return; + } + // TODO: it would be better to defer getting the path until after checking + // for a CreatesMustCallFor annotation, because getting the path can be expensive. + // It might be possible to exploit the CFG structure to find the containing + // method (rather than using the path, as below), because if a method is being + // analyzed then it should be the root of the CFG (I think). + TreePath currentPath = typeFactory.getPath(node.getTree()); + MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(currentPath); + + if (enclosingMethodTree == null) { + // The assignment is taking place outside of a method: in a variable declaration's + // initializer or in an initializer block. + // The Resource Leak Checker issues no error if the assignment is a field initializer. + if (node.getTree().getKind() == Tree.Kind.VARIABLE) { + // An assignment to a field that is also a declaration must be a field initializer + // (VARIABLE Trees are only used for declarations). Assignment in a field + // initializer is always permitted. + return; + } else if (permitInitializationLeak + && TreePathUtil.isTopLevelAssignmentInInitializerBlock(currentPath)) { + // This is likely not reassignment; if reassignment, the number of assignments that + // were not warned about is limited to other initializations (is not unbounded). + // This behavior is unsound; see InstanceInitializer.java test case. + return; + } else { + // Issue an error if the field has a non-empty must-call type. MustCallAnnotatedTypeFactory mcTypeFactory = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - - // Get the Must Call type for the field. If there's info about this field in the store, use - // that. Otherwise, use the declared type of the field - CFStore mcStore = mcTypeFactory.getStoreBefore(lhs); - CFValue mcValue = mcStore.getValue(lhs); - AnnotationMirror mcAnno = null; - if (mcValue != null) { - mcAnno = AnnotationUtils.getAnnotationByClass(mcValue.getAnnotations(), MustCall.class); - } - if (mcAnno == null) { - // No stored value (or the stored value is Poly/top), so use the declared type. - mcAnno = mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class); - } - // if mcAnno is still null, then the declared type must be something other than - // @MustCall (probably @MustCallUnknown). Do nothing in this case: a warning - // about the field will be issued elsewhere (it will be impossible to satisfy its - // obligations!). - if (mcAnno == null) { - return; - } + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotationMirror mcAnno = + mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class); List mcValues = - AnnotationUtils.getElementValueArray( - mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); - + AnnotationUtils.getElementValueArray( + mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); if (mcValues.isEmpty()) { - return; - } - - // Get the store before the RHS rather than the assignment node, because the CFG always has - // the RHS first. If the RHS has side-effects, then the assignment node's store will have - // had its inferred types erased. - AccumulationStore cmStoreBefore = typeFactory.getStoreBefore(rhs); - AccumulationValue cmValue = cmStoreBefore == null ? null : cmStoreBefore.getValue(lhs); - AnnotationMirror cmAnno = null; - if (cmValue != null) { // When store contains the lhs - Set accumulatedValues = cmValue.getAccumulatedValues(); - if (accumulatedValues != null) { // type variable or wildcard type - cmAnno = typeFactory.createCalledMethods(accumulatedValues.toArray(new String[0])); - } else { - for (AnnotationMirror anno : cmValue.getAnnotations()) { - if (AnnotationUtils.areSameByName( - anno, - "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { - cmAnno = anno; - } - } - } - } - if (cmAnno == null) { - cmAnno = typeFactory.top; - } - if (!calledMethodsSatisfyMustCall(mcValues, cmAnno)) { - VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); - if (!checker.shouldSkipUses(lhsElement)) { - checker.reportError( - node.getTree(), - "required.method.not.called", - formatMissingMustCallMethods(mcValues), - "field " + lhsElement.getSimpleName().toString(), - lhsElement.asType().toString(), - " Non-final owning field might be overwritten"); - } - } - } - - /** - * Checks that the method that encloses an assignment is marked with @CreatesMustCallFor - * annotation whose target is the object whose field is being re-assigned. - * - * @param node an assignment node whose lhs is a non-final, owning field - * @param enclosingMethod the MethodTree in which the re-assignment takes place - */ - private void checkEnclosingMethodIsCreatesMustCallFor( - AssignmentNode node, MethodTree enclosingMethod) { - Node lhs = node.getTarget(); - if (!(lhs instanceof FieldAccessNode)) { - return; - } - if (permitStaticOwning && ((FieldAccessNode) lhs).getReceiver() instanceof ClassNameNode) { - return; - } - - String receiverString = receiverAsString((FieldAccessNode) lhs); - if ("this".equals(receiverString) && TreeUtils.isConstructor(enclosingMethod)) { - // Constructors always create must-call obligations, so there is no need for them to - // be annotated. - return; - } - ExecutableElement enclosingMethodElt = TreeUtils.elementFromDeclaration(enclosingMethod); - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - - List cmcfValues = - ResourceLeakVisitor.getCreatesMustCallForValues( - enclosingMethodElt, mcAtf, typeFactory); - - if (cmcfValues.isEmpty()) { - checker.reportError( - enclosingMethod, - "missing.creates.mustcall.for", - enclosingMethodElt.getSimpleName().toString(), - receiverString, - ((FieldAccessNode) lhs).getFieldName()); - return; - } - - List checked = new ArrayList<>(); - for (String targetStrWithoutAdaptation : cmcfValues) { - String targetStr; - try { - targetStr = - StringToJavaExpression.atMethodBody( - targetStrWithoutAdaptation, enclosingMethod, checker) - .toString(); - } catch (JavaExpressionParseException e) { - targetStr = targetStrWithoutAdaptation; - } - if (targetStr.equals(receiverString)) { - // This @CreatesMustCallFor annotation matches. - return; - } - checked.add(targetStr); + return; } + VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); checker.reportError( - enclosingMethod, - "incompatible.creates.mustcall.for", - enclosingMethodElt.getSimpleName().toString(), - receiverString, - ((FieldAccessNode) lhs).getFieldName(), - String.join(", ", checked)); + node.getTree(), + "required.method.not.called", + formatMissingMustCallMethods(mcValues), + "field " + lhsElement.getSimpleName().toString(), + lhsElement.asType().toString(), + "Field assignment outside method or declaration might overwrite field's" + + " current value"); + return; + } + } else if (permitInitializationLeak && TreeUtils.isConstructor(enclosingMethodTree)) { + Element enclosingClassElement = + TreeUtils.elementFromDeclaration(enclosingMethodTree).getEnclosingElement(); + if (ElementUtils.isTypeElement(enclosingClassElement)) { + Element receiverElement = TypesUtils.getTypeElement(receiver.getType()); + if (Objects.equals(enclosingClassElement, receiverElement)) { + return; + } + } } - /** - * Gets a standardized name for an object whose field is being re-assigned. - * - * @param fieldAccessNode a field access node - * @return the name of the object whose field is being accessed (the receiver), as a string - */ - private String receiverAsString(FieldAccessNode fieldAccessNode) { - Node receiver = fieldAccessNode.getReceiver(); - if (receiver instanceof ThisNode) { - return "this"; - } - if (receiver instanceof LocalVariableNode) { - return ((LocalVariableNode) receiver).getName(); - } - if (receiver instanceof ClassNameNode) { - return ((ClassNameNode) receiver).getElement().toString(); - } - if (receiver instanceof SuperNode) { - return "super"; - } - throw new TypeSystemError( - "unexpected receiver of field assignment: " - + receiver - + " of type " - + receiver.getClass()); + // Check that there is a corresponding CreatesMustCallFor annotation, unless this is + // 1) an assignment to a field of a newly-declared local variable whose scope does not + // extend beyond the method's body (and which therefore could not be targeted by an + // annotation on the method declaration), or 2) the rhs is a null literal (so there's + // nothing to reset). + if (!(receiver instanceof LocalVariableNode + && varTrackedInObligations(obligations, (LocalVariableNode) receiver)) + && !(node.getExpression() instanceof NullLiteralNode)) { + checkEnclosingMethodIsCreatesMustCallFor(node, enclosingMethodTree); } - /** - * Finds the arguments passed in the {@code @MustCallAlias} positions for a call. - * - * @param callNode callNode representing the call; must be {@link MethodInvocationNode} or - * {@link ObjectCreationNode} - * @return if {@code callNode} invokes a method with a {@code @MustCallAlias} annotation on some - * formal parameter(s) (or the receiver), returns the result of calling {@link - * #removeCastsAndGetTmpVarIfPresent(Node)} on the argument(s) passed in corresponding - * position(s). Otherwise, returns an empty List. - */ - private List getMustCallAliasArgumentNodes(Node callNode) { - Preconditions.checkArgument( - callNode instanceof MethodInvocationNode || callNode instanceof ObjectCreationNode); - List result = new ArrayList<>(); - if (!typeFactory.hasMustCallAlias(callNode.getTree())) { - return result; - } - - List args = getArgumentsOfInvocation(callNode); - List parameters = getParametersOfInvocation(callNode); - for (int i = 0; i < args.size(); i++) { - if (typeFactory.hasMustCallAlias(parameters.get(i))) { - result.add(removeCastsAndGetTmpVarIfPresent(args.get(i))); + // The following code handles a special case where the field being assigned is itself + // getting passed in an owning position to another method on the RHS of the assignment. + // For example, if the field's type is a class whose constructor takes another instance + // of itself (such as a node in a linked list) in an owning position, re-assigning the field + // to a new instance that takes the field's value as an owning parameter is safe (the new + // value has taken responsibility for closing the old value). In such a case, it is not + // required that the must-call obligation of the field be satisfied via method calls before + // the assignment, since the invoked method will take ownership of the object previously + // referenced by the field and handle the obligation. This fixes the false positive in + // https://github.com/typetools/checker-framework/issues/5971. + Node rhs = node.getExpression(); + if (!noLightweightOwnership + && (rhs instanceof ObjectCreationNode || rhs instanceof MethodInvocationNode)) { + + List arguments = getArgumentsOfInvocation(rhs); + List parameters = getParametersOfInvocation(rhs); + + if (arguments.size() == parameters.size()) { + for (int i = 0; i < arguments.size(); i++) { + VariableElement param = parameters.get(i); + if (typeFactory.hasOwning(param)) { + Node argument = arguments.get(i); + if (argument.equals(lhs)) { + return; } - } + } + } + } else { + // This could happen, e.g., with varargs, or with strange cases like generated Enum + // constructors. In the varargs case (i.e. if the varargs parameter is owning), + // only the first of the varargs arguments will actually get transferred: the second + // and later varargs arguments will continue to be tracked at the call-site. + // For now, just skip this case - the worst that will happen is a false positive in + // cases like the varargs one described above. + // TODO allow for ownership transfer here if needed in future, but for now do + // nothing + } + } - // If none of the parameters were @MustCallAlias, it must be the receiver - if (result.isEmpty() && callNode instanceof MethodInvocationNode) { - result.add( - removeCastsAndGetTmpVarIfPresent( - ((MethodInvocationNode) callNode).getTarget().getReceiver())); - } + MustCallAnnotatedTypeFactory mcTypeFactory = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - return result; + // Get the Must Call type for the field. If there's info about this field in the store, use + // that. Otherwise, use the declared type of the field + CFStore mcStore = mcTypeFactory.getStoreBefore(lhs); + CFValue mcValue = mcStore.getValue(lhs); + AnnotationMirror mcAnno = null; + if (mcValue != null) { + mcAnno = AnnotationUtils.getAnnotationByClass(mcValue.getAnnotations(), MustCall.class); } - - /** - * If a temporary variable exists for node after typecasts have been removed, return it. - * Otherwise, return node. - * - * @param node a node - * @return either a tempvar for node's content sans typecasts, or node - */ - /*package-private*/ Node removeCastsAndGetTmpVarIfPresent(Node node) { - // TODO: Create temp vars for TypeCastNodes as well, so there is no need to explicitly - // remove casts here. - node = NodeUtils.removeCasts(node); - return getTempVarOrNode(node); + if (mcAnno == null) { + // No stored value (or the stored value is Poly/top), so use the declared type. + mcAnno = mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class); + } + // if mcAnno is still null, then the declared type must be something other than + // @MustCall (probably @MustCallUnknown). Do nothing in this case: a warning + // about the field will be issued elsewhere (it will be impossible to satisfy its + // obligations!). + if (mcAnno == null) { + return; } + List mcValues = + AnnotationUtils.getElementValueArray( + mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); - /** - * Get the nodes representing the arguments of a method or constructor invocation from the - * invocation node. - * - * @param node a MethodInvocation or ObjectCreation node - * @return the arguments, in order - */ - /*package-private*/ List getArgumentsOfInvocation(Node node) { - if (node instanceof MethodInvocationNode) { - MethodInvocationNode invocationNode = (MethodInvocationNode) node; - return invocationNode.getArguments(); - } else if (node instanceof ObjectCreationNode) { - return ((ObjectCreationNode) node).getArguments(); - } else { - throw new TypeSystemError("unexpected node type " + node.getClass()); - } + if (mcValues.isEmpty()) { + return; } - /** - * Get the elements representing the formal parameters of a method or constructor, from an - * invocation of that method or constructor. - * - * @param node a method invocation or object creation node - * @return a list of the declarations of the formal parameters of the method or constructor - * being invoked - */ - /*package-private*/ List getParametersOfInvocation(Node node) { - ExecutableElement executableElement; - if (node instanceof MethodInvocationNode) { - MethodInvocationNode invocationNode = (MethodInvocationNode) node; - executableElement = TreeUtils.elementFromUse(invocationNode.getTree()); - } else if (node instanceof ObjectCreationNode) { - executableElement = TreeUtils.elementFromUse(((ObjectCreationNode) node).getTree()); - } else { - throw new TypeSystemError("unexpected node type " + node.getClass()); - } + // Get the store before the RHS rather than the assignment node, because the CFG always has + // the RHS first. If the RHS has side-effects, then the assignment node's store will have + // had its inferred types erased. + AccumulationStore cmStoreBefore = typeFactory.getStoreBefore(rhs); + AccumulationValue cmValue = cmStoreBefore == null ? null : cmStoreBefore.getValue(lhs); + AnnotationMirror cmAnno = null; + if (cmValue != null) { // When store contains the lhs + Set accumulatedValues = cmValue.getAccumulatedValues(); + if (accumulatedValues != null) { // type variable or wildcard type + cmAnno = typeFactory.createCalledMethods(accumulatedValues.toArray(new String[0])); + } else { + for (AnnotationMirror anno : cmValue.getAnnotations()) { + if (AnnotationUtils.areSameByName( + anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { + cmAnno = anno; + } + } + } + } + if (cmAnno == null) { + cmAnno = typeFactory.top; + } + if (!calledMethodsSatisfyMustCall(mcValues, cmAnno)) { + VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); + if (!checker.shouldSkipUses(lhsElement)) { + checker.reportError( + node.getTree(), + "required.method.not.called", + formatMissingMustCallMethods(mcValues), + "field " + lhsElement.getSimpleName().toString(), + lhsElement.asType().toString(), + " Non-final owning field might be overwritten"); + } + } + } + + /** + * Checks that the method that encloses an assignment is marked with @CreatesMustCallFor + * annotation whose target is the object whose field is being re-assigned. + * + * @param node an assignment node whose lhs is a non-final, owning field + * @param enclosingMethod the MethodTree in which the re-assignment takes place + */ + private void checkEnclosingMethodIsCreatesMustCallFor( + AssignmentNode node, MethodTree enclosingMethod) { + Node lhs = node.getTarget(); + if (!(lhs instanceof FieldAccessNode)) { + return; + } + if (permitStaticOwning && ((FieldAccessNode) lhs).getReceiver() instanceof ClassNameNode) { + return; + } - return executableElement.getParameters(); + String receiverString = receiverAsString((FieldAccessNode) lhs); + if ("this".equals(receiverString) && TreeUtils.isConstructor(enclosingMethod)) { + // Constructors always create must-call obligations, so there is no need for them to + // be annotated. + return; + } + ExecutableElement enclosingMethodElt = TreeUtils.elementFromDeclaration(enclosingMethod); + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + + List cmcfValues = + ResourceLeakVisitor.getCreatesMustCallForValues(enclosingMethodElt, mcAtf, typeFactory); + + if (cmcfValues.isEmpty()) { + checker.reportError( + enclosingMethod, + "missing.creates.mustcall.for", + enclosingMethodElt.getSimpleName().toString(), + receiverString, + ((FieldAccessNode) lhs).getFieldName()); + return; } - /** - * Is the return type of the invoked method one that should be tracked? - * - * @param node a method invocation - * @return true iff the checker is in no-lightweight-ownership mode, or the method has a - * {@code @MustCallAlias} annotation, or (1) the method has a return type that needs to be - * tracked (i.e., it has a non-empty {@code @MustCall} obligation and (2) the method - * declaration does not have a {@code @NotOwning} annotation - */ - private boolean shouldTrackReturnType(MethodInvocationNode node) { - if (noLightweightOwnership) { - // Default to always transferring at return if not using LO, just like Eclipse does. - return true; - } - MethodInvocationTree methodInvocationTree = node.getTree(); - ExecutableElement executableElement = TreeUtils.elementFromUse(methodInvocationTree); - if (typeFactory.hasMustCallAlias(executableElement)) { - // assume tracking is required - return true; - } - TypeMirror type = ElementUtils.getType(executableElement); - // void or primitive-returning methods are "not owning" by construction - if (type.getKind() == TypeKind.VOID || type.getKind().isPrimitive()) { - return false; - } - TypeElement typeElt = TypesUtils.getTypeElement(type); - // no need to track if type has no possible @MustCall obligation - if (typeElt != null - && typeFactory.hasEmptyMustCallValue(typeElt) - && typeFactory.hasEmptyMustCallValue(methodInvocationTree)) { - return false; - } - // check for absence of @NotOwning annotation - return !typeFactory.hasNotOwning(executableElement); + List checked = new ArrayList<>(); + for (String targetStrWithoutAdaptation : cmcfValues) { + String targetStr; + try { + targetStr = + StringToJavaExpression.atMethodBody( + targetStrWithoutAdaptation, enclosingMethod, checker) + .toString(); + } catch (JavaExpressionParseException e) { + targetStr = targetStrWithoutAdaptation; + } + if (targetStr.equals(receiverString)) { + // This @CreatesMustCallFor annotation matches. + return; + } + checked.add(targetStr); + } + checker.reportError( + enclosingMethod, + "incompatible.creates.mustcall.for", + enclosingMethodElt.getSimpleName().toString(), + receiverString, + ((FieldAccessNode) lhs).getFieldName(), + String.join(", ", checked)); + } + + /** + * Gets a standardized name for an object whose field is being re-assigned. + * + * @param fieldAccessNode a field access node + * @return the name of the object whose field is being accessed (the receiver), as a string + */ + private String receiverAsString(FieldAccessNode fieldAccessNode) { + Node receiver = fieldAccessNode.getReceiver(); + if (receiver instanceof ThisNode) { + return "this"; + } + if (receiver instanceof LocalVariableNode) { + return ((LocalVariableNode) receiver).getName(); + } + if (receiver instanceof ClassNameNode) { + return ((ClassNameNode) receiver).getElement().toString(); + } + if (receiver instanceof SuperNode) { + return "super"; + } + throw new TypeSystemError( + "unexpected receiver of field assignment: " + receiver + " of type " + receiver.getClass()); + } + + /** + * Finds the arguments passed in the {@code @MustCallAlias} positions for a call. + * + * @param callNode callNode representing the call; must be {@link MethodInvocationNode} or {@link + * ObjectCreationNode} + * @return if {@code callNode} invokes a method with a {@code @MustCallAlias} annotation on some + * formal parameter(s) (or the receiver), returns the result of calling {@link + * #removeCastsAndGetTmpVarIfPresent(Node)} on the argument(s) passed in corresponding + * position(s). Otherwise, returns an empty List. + */ + private List getMustCallAliasArgumentNodes(Node callNode) { + Preconditions.checkArgument( + callNode instanceof MethodInvocationNode || callNode instanceof ObjectCreationNode); + List result = new ArrayList<>(); + if (!typeFactory.hasMustCallAlias(callNode.getTree())) { + return result; } - /** - * Get all successor blocks for some block, except for those corresponding to ignored exception - * types. See {@link ResourceLeakAnalysis#isIgnoredExceptionType(TypeMirror)}. Each exceptional - * successor is paired with the type of exception that leads to it, for use in error messages. - * - * @param block input block - * @return set of pairs (b, t), where b is a successor block, and t is the type of exception for - * the CFG edge from block to b, or {@code null} if b is a non-exceptional successor - */ - private Set> getSuccessorsExceptIgnoredExceptions( - Block block) { - if (block.getType() == Block.BlockType.EXCEPTION_BLOCK) { - ExceptionBlock excBlock = (ExceptionBlock) block; - Set> result = new LinkedHashSet<>(); - // regular successor - Block regularSucc = excBlock.getSuccessor(); - if (regularSucc != null) { - result.add(IPair.of(regularSucc, null)); - } - // non-ignored exception successors - Map> exceptionalSuccessors = excBlock.getExceptionalSuccessors(); - for (Map.Entry> entry : exceptionalSuccessors.entrySet()) { - TypeMirror exceptionType = entry.getKey(); - if (!analysis.isIgnoredExceptionType(exceptionType)) { - for (Block exSucc : entry.getValue()) { - result.add(IPair.of(exSucc, exceptionType)); - } - } - } - return result; - } else { - Set> result = new LinkedHashSet<>(); - for (Block b : block.getSuccessors()) { - result.add(IPair.of(b, null)); - } - return result; - } + List args = getArgumentsOfInvocation(callNode); + List parameters = getParametersOfInvocation(callNode); + for (int i = 0; i < args.size(); i++) { + if (typeFactory.hasMustCallAlias(parameters.get(i))) { + result.add(removeCastsAndGetTmpVarIfPresent(args.get(i))); + } } - /** - * Propagates a set of Obligations to successors, and performs consistency checks when variables - * are going out of scope. - * - *

The basic algorithm loops over the successor blocks of the current block. For each - * successor, two things happen: - * - *

First, it constructs an updated set of Obligations using {@code incomingObligations}, the - * nodes in {@code currentBlock}, and the nature of the edge from {@code currentBlock} to the - * successor. The edge can either be normal control flow or an exception. See - * - *

    - *
  • {@link #updateObligationsForAssignment(Set, ControlFlowGraph, AssignmentNode)} - *
  • {@link #updateObligationsForOwningReturn(Set, ControlFlowGraph, ReturnNode)} - *
  • {@link #updateObligationsForInvocation(Set, Node, TypeMirror)} - *
- * - *

Second, it checks every Obligation in obligations. If the successor is an exit block or - * all of an Obligation's resource aliases might be going out of scope, then a consistency check - * occurs (with two exceptions, both related to temporary variables that don't actually get - * assigned; see code comments for details) and an error is issued if it fails. If the successor - * is any other kind of block and there is information about at least one of the Obligation's - * aliases in the successor store (i.e. the resource itself definitely does not go out of - * scope), then the Obligation is passed forward to the successor ("propagated") with any - * definitely out-of-scope aliases removed from its resource alias set. - * - * @param cfg the control flow graph - * @param incomingObligations the Obligations for the current block - * @param currentBlock the current block - * @param visited block-Obligations pairs already analyzed or already on the worklist - * @param worklist current worklist - */ - private void propagateObligationsToSuccessorBlocks( - ControlFlowGraph cfg, - Set incomingObligations, - Block currentBlock, - Set visited, - Deque worklist) { - // For each successor block that isn't caused by an ignored exception type, this loop - // computes the set of Obligations that should be propagated to it and then adds it to the - // worklist if any of its resource aliases are still in scope in the successor block. If - // none are, then the loop performs a consistency check for that Obligation. - for (IPair successorAndExceptionType : - getSuccessorsExceptIgnoredExceptions(currentBlock)) { - - // A *mutable* set that eventually holds the set of dataflow facts to be propagated to - // successor blocks. The set is initialized to the current dataflow facts and updated by - // the methods invoked in the for loop below. - Set obligations = new LinkedHashSet<>(incomingObligations); - - // PERFORMANCE NOTE: The computed changes to `obligations` are mostly the same for each - // successor block, but can vary slightly depending on the exception type. There might - // be some opportunities for optimization in this mostly-redundant work. - for (Node node : currentBlock.getNodes()) { - if (node instanceof AssignmentNode) { - updateObligationsForAssignment(obligations, cfg, (AssignmentNode) node); - } else if (node instanceof ReturnNode) { - updateObligationsForOwningReturn(obligations, cfg, (ReturnNode) node); - } else if (node instanceof MethodInvocationNode - || node instanceof ObjectCreationNode) { - updateObligationsForInvocation( - obligations, node, successorAndExceptionType.second); - } - // All other types of nodes are ignored. This is safe, because other kinds of - // nodes cannot create or modify the resource-alias sets that the algorithm is - // tracking. - } + // If none of the parameters were @MustCallAlias, it must be the receiver + if (result.isEmpty() && callNode instanceof MethodInvocationNode) { + result.add( + removeCastsAndGetTmpVarIfPresent( + ((MethodInvocationNode) callNode).getTarget().getReceiver())); + } - propagateObligationsToSuccessorBlock( - obligations, - currentBlock, - successorAndExceptionType.first, - successorAndExceptionType.second, - visited, - worklist); - } + return result; + } + + /** + * If a temporary variable exists for node after typecasts have been removed, return it. + * Otherwise, return node. + * + * @param node a node + * @return either a tempvar for node's content sans typecasts, or node + */ + /*package-private*/ Node removeCastsAndGetTmpVarIfPresent(Node node) { + // TODO: Create temp vars for TypeCastNodes as well, so there is no need to explicitly + // remove casts here. + node = NodeUtils.removeCasts(node); + return getTempVarOrNode(node); + } + + /** + * Get the nodes representing the arguments of a method or constructor invocation from the + * invocation node. + * + * @param node a MethodInvocation or ObjectCreation node + * @return the arguments, in order + */ + /*package-private*/ List getArgumentsOfInvocation(Node node) { + if (node instanceof MethodInvocationNode) { + MethodInvocationNode invocationNode = (MethodInvocationNode) node; + return invocationNode.getArguments(); + } else if (node instanceof ObjectCreationNode) { + return ((ObjectCreationNode) node).getArguments(); + } else { + throw new TypeSystemError("unexpected node type " + node.getClass()); + } + } + + /** + * Get the elements representing the formal parameters of a method or constructor, from an + * invocation of that method or constructor. + * + * @param node a method invocation or object creation node + * @return a list of the declarations of the formal parameters of the method or constructor being + * invoked + */ + /*package-private*/ List getParametersOfInvocation(Node node) { + ExecutableElement executableElement; + if (node instanceof MethodInvocationNode) { + MethodInvocationNode invocationNode = (MethodInvocationNode) node; + executableElement = TreeUtils.elementFromUse(invocationNode.getTree()); + } else if (node instanceof ObjectCreationNode) { + executableElement = TreeUtils.elementFromUse(((ObjectCreationNode) node).getTree()); + } else { + throw new TypeSystemError("unexpected node type " + node.getClass()); } - /** - * Helper for {@link #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, - * Deque)} that propagates obligations along a single edge. - * - * @param obligations the Obligations for the current block - * @param currentBlock the current block - * @param successor a successor of the current block - * @param exceptionType the type of edge from currentBlock to successor - * : null for normal control flow, or a throwable type for exceptional - * control flow - * @param visited block-Obligations pairs already analyzed or already on the worklist - * @param worklist current worklist - */ - private void propagateObligationsToSuccessorBlock( - Set obligations, - Block currentBlock, - Block successor, - @Nullable TypeMirror exceptionType, - Set visited, - Deque worklist) { - List currentBlockNodes = currentBlock.getNodes(); - // successorObligations eventually contains the Obligations to propagate to successor. - // The loop below mutates it. - Set successorObligations = new LinkedHashSet<>(); - // A detailed reason to give in the case that the last resource alias of an Obligation - // goes out of scope without a called-methods type that satisfies the corresponding - // must-call obligation along the current control-flow edge. Computed here for - // efficiency; used in the loop over the Obligations, below. - String exitReasonForErrorMessage = - exceptionType == null - ? - // Technically the variable may be going out of scope before the method - // exit, but that doesn't seem to provide additional helpful - // information. - "regular method exit" - : "possible exceptional exit due to " - + ((ExceptionBlock) currentBlock).getNode().getTree() - + " with exception type " - + exceptionType; - // Computed outside the Obligation loop for efficiency. - AccumulationStore regularStoreOfSuccessor = analysis.getInput(successor).getRegularStore(); - for (Obligation obligation : obligations) { - // This boolean is true if there is no evidence that the Obligation does not go out - // of scope - that is, if there is definitely a resource alias that is in scope in - // the successor. - boolean obligationGoesOutOfScopeBeforeSuccessor = true; - for (ResourceAlias resourceAlias : obligation.resourceAliases) { - if (aliasInScopeInSuccessor(regularStoreOfSuccessor, resourceAlias)) { - obligationGoesOutOfScopeBeforeSuccessor = false; - break; - } - } - // This check is to determine if this Obligation's resource aliases are definitely - // going out of scope: if this is an exit block or there is no information about any - // of them in the successor store, all aliases must be going out of scope and a - // consistency check should occur. - if (successor.getType() == BlockType.SPECIAL_BLOCK /* special blocks are exit blocks */ - || obligationGoesOutOfScopeBeforeSuccessor) { - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - - // If successor is an exceptional successor, and Obligation represents the - // temporary variable for currentBlock's node, do not propagate or do a - // consistency check, as in the exceptional case the "assignment" to the - // temporary variable does not succeed. - // - // Note that this test cannot be "successor.getType() == - // BlockType.EXCEPTIONAL_BLOCK", because not every exceptional successor is an - // exceptional block. For example, the successor might be a regular block - // (containing a catch clause, for example), or a special block indicating an - // exceptional exit. Nor can this test be "currentBlock.getType() == - // BlockType.EXCEPTIONAL_BLOCK", because some exception types are ignored. - // Whether exceptionType is null captures the logic of both of these cases. - if (exceptionType != null) { - Node exceptionalNode = - NodeUtils.removeCasts(((ExceptionBlock) currentBlock).getNode()); - LocalVariableNode tmpVarForExcNode = - typeFactory.getTempVarForNode(exceptionalNode); - if (tmpVarForExcNode != null - && obligation.resourceAliases.size() == 1 - && obligation.canBeSatisfiedThrough(tmpVarForExcNode)) { - continue; - } - } - - // Always propagate the Obligation to the successor if current block represents - // code nested in a cast. Without this logic, the analysis may report a false - // positive when the Obligation represents a temporary variable for a nested - // expression, as the temporary may not appear in the successor store and hence - // seems to be going out of scope. The temporary will be handled with special - // logic; casts are unwrapped at various points in the analysis. - if (currentBlockNodes.size() == 1 && inCast(currentBlockNodes.get(0))) { - successorObligations.add(obligation); - continue; - } - - // At this point, a consistency check will definitely occur, unless the - // obligation was derived from a MustCallAlias parameter. If it was, an error is - // immediately issued, because such a parameter should not go out of scope - // without its obligation being resolved some other way. - if (obligation.derivedFromMustCallAlias()) { - // MustCallAlias annotations only have meaning if the method returns - // normally, so issue an error if and only if this exit is happening on a - // normal exit path. - if (exceptionType == null - && obligation.whenToEnforce.contains(MethodExitKind.NORMAL_RETURN)) { - checker.reportError( - obligation.resourceAliases.asList().get(0).tree, - "mustcallalias.out.of.scope", - exitReasonForErrorMessage); - } - // Whether or not an error is issued, the check is now complete - there is - // no further checking to do on a must-call-alias-derived obligation along - // an exceptional path. - continue; - } - - // Which stores from the called-methods and must-call checkers are used in - // the consistency check varies depending on the context. The rules are: - // 1. if the current block has no nodes (and therefore the store must come from - // a block - // rather than a node): - // 1a. if there is information about any alias in the resource alias set - // in the successor store, use the successor's CM and MC stores, which - // contain whatever information is true after this block finishes. - // 1b. if there is not any information about any alias in the resource alias - // set in the successor store, use the current blocks' CM and MC stores, - // which contain whatever information is true before this (empty) block. - // 2. if the current block has one or more nodes, always use the CM store after - // the last node. To decide which MC store to use: - // 2a. if the last node in the block is the invocation of an - // @CreatesMustCallFor method that might throw an exception, and the - // consistency check is for an exceptional path, use the MC store - // immediately before the method invocation, because the method threw an - // exception rather than finishing and therefore did not actually create - // any must-call obligation, so the MC store after might contain - // must-call obligations that do not need to be fulfilled along this - // path. - // 2b. in all other cases, use the MC store from after the last node in - // the block. - CFStore mcStore; - AccumulationStore cmStore; - if (currentBlockNodes.size() == 0 /* currentBlock is special or conditional */) { - cmStore = - obligationGoesOutOfScopeBeforeSuccessor - ? analysis.getInput(currentBlock).getRegularStore() // 1a. (CM) - : regularStoreOfSuccessor; // 1b. (CM) - mcStore = - mcAtf.getStoreForBlock( - obligationGoesOutOfScopeBeforeSuccessor, - currentBlock, // 1a. (MC) - successor); // 1b. (MC) - } else { - // In this case, current block has at least one node. - // Use the called-methods store immediately after the last node in - // currentBlock. - Node last = currentBlockNodes.get(currentBlockNodes.size() - 1); // 2. (CM) - - if (cmStoreAfter.containsKey(last)) { - cmStore = cmStoreAfter.get(last); - } else { - cmStore = typeFactory.getStoreAfter(last); - cmStoreAfter.put(last, cmStore); - } - // If this is an exceptional block, check the MC store beforehand to avoid - // issuing an error about a call to a CreatesMustCallFor method that might - // throw an exception. Otherwise, use the store after. - if (exceptionType != null && isInvocationOfCreatesMustCallForMethod(last)) { - mcStore = mcAtf.getStoreBefore(last); // 2a. (MC) - } else { - if (mcStoreAfter.containsKey(last)) { - mcStore = mcStoreAfter.get(last); - } else { - mcStore = mcAtf.getStoreAfter(last); // 2b. (MC) - mcStoreAfter.put(last, mcStore); - } - } - } - - MethodExitKind exitKind = - exceptionType == null - ? MethodExitKind.NORMAL_RETURN - : MethodExitKind.EXCEPTIONAL_EXIT; - if (obligation.whenToEnforce.contains(exitKind)) { - checkMustCall(obligation, cmStore, mcStore, exitReasonForErrorMessage); - } + return executableElement.getParameters(); + } + + /** + * Is the return type of the invoked method one that should be tracked? + * + * @param node a method invocation + * @return true iff the checker is in no-lightweight-ownership mode, or the method has a + * {@code @MustCallAlias} annotation, or (1) the method has a return type that needs to be + * tracked (i.e., it has a non-empty {@code @MustCall} obligation and (2) the method + * declaration does not have a {@code @NotOwning} annotation + */ + private boolean shouldTrackReturnType(MethodInvocationNode node) { + if (noLightweightOwnership) { + // Default to always transferring at return if not using LO, just like Eclipse does. + return true; + } + MethodInvocationTree methodInvocationTree = node.getTree(); + ExecutableElement executableElement = TreeUtils.elementFromUse(methodInvocationTree); + if (typeFactory.hasMustCallAlias(executableElement)) { + // assume tracking is required + return true; + } + TypeMirror type = ElementUtils.getType(executableElement); + // void or primitive-returning methods are "not owning" by construction + if (type.getKind() == TypeKind.VOID || type.getKind().isPrimitive()) { + return false; + } + TypeElement typeElt = TypesUtils.getTypeElement(type); + // no need to track if type has no possible @MustCall obligation + if (typeElt != null + && typeFactory.hasEmptyMustCallValue(typeElt) + && typeFactory.hasEmptyMustCallValue(methodInvocationTree)) { + return false; + } + // check for absence of @NotOwning annotation + return !typeFactory.hasNotOwning(executableElement); + } + + /** + * Get all successor blocks for some block, except for those corresponding to ignored exception + * types. See {@link ResourceLeakAnalysis#isIgnoredExceptionType(TypeMirror)}. Each exceptional + * successor is paired with the type of exception that leads to it, for use in error messages. + * + * @param block input block + * @return set of pairs (b, t), where b is a successor block, and t is the type of exception for + * the CFG edge from block to b, or {@code null} if b is a non-exceptional successor + */ + private Set> getSuccessorsExceptIgnoredExceptions( + Block block) { + if (block.getType() == Block.BlockType.EXCEPTION_BLOCK) { + ExceptionBlock excBlock = (ExceptionBlock) block; + Set> result = new LinkedHashSet<>(); + // regular successor + Block regularSucc = excBlock.getSuccessor(); + if (regularSucc != null) { + result.add(IPair.of(regularSucc, null)); + } + // non-ignored exception successors + Map> exceptionalSuccessors = excBlock.getExceptionalSuccessors(); + for (Map.Entry> entry : exceptionalSuccessors.entrySet()) { + TypeMirror exceptionType = entry.getKey(); + if (!analysis.isIgnoredExceptionType(exceptionType)) { + for (Block exSucc : entry.getValue()) { + result.add(IPair.of(exSucc, exceptionType)); + } + } + } + return result; + } else { + Set> result = new LinkedHashSet<>(); + for (Block b : block.getSuccessors()) { + result.add(IPair.of(b, null)); + } + return result; + } + } + + /** + * Propagates a set of Obligations to successors, and performs consistency checks when variables + * are going out of scope. + * + *

The basic algorithm loops over the successor blocks of the current block. For each + * successor, two things happen: + * + *

First, it constructs an updated set of Obligations using {@code incomingObligations}, the + * nodes in {@code currentBlock}, and the nature of the edge from {@code currentBlock} to the + * successor. The edge can either be normal control flow or an exception. See + * + *

    + *
  • {@link #updateObligationsForAssignment(Set, ControlFlowGraph, AssignmentNode)} + *
  • {@link #updateObligationsForOwningReturn(Set, ControlFlowGraph, ReturnNode)} + *
  • {@link #updateObligationsForInvocation(Set, Node, TypeMirror)} + *
+ * + *

Second, it checks every Obligation in obligations. If the successor is an exit block or all + * of an Obligation's resource aliases might be going out of scope, then a consistency check + * occurs (with two exceptions, both related to temporary variables that don't actually get + * assigned; see code comments for details) and an error is issued if it fails. If the successor + * is any other kind of block and there is information about at least one of the Obligation's + * aliases in the successor store (i.e. the resource itself definitely does not go out of scope), + * then the Obligation is passed forward to the successor ("propagated") with any definitely + * out-of-scope aliases removed from its resource alias set. + * + * @param cfg the control flow graph + * @param incomingObligations the Obligations for the current block + * @param currentBlock the current block + * @param visited block-Obligations pairs already analyzed or already on the worklist + * @param worklist current worklist + */ + private void propagateObligationsToSuccessorBlocks( + ControlFlowGraph cfg, + Set incomingObligations, + Block currentBlock, + Set visited, + Deque worklist) { + // For each successor block that isn't caused by an ignored exception type, this loop + // computes the set of Obligations that should be propagated to it and then adds it to the + // worklist if any of its resource aliases are still in scope in the successor block. If + // none are, then the loop performs a consistency check for that Obligation. + for (IPair successorAndExceptionType : + getSuccessorsExceptIgnoredExceptions(currentBlock)) { + + // A *mutable* set that eventually holds the set of dataflow facts to be propagated to + // successor blocks. The set is initialized to the current dataflow facts and updated by + // the methods invoked in the for loop below. + Set obligations = new LinkedHashSet<>(incomingObligations); + + // PERFORMANCE NOTE: The computed changes to `obligations` are mostly the same for each + // successor block, but can vary slightly depending on the exception type. There might + // be some opportunities for optimization in this mostly-redundant work. + for (Node node : currentBlock.getNodes()) { + if (node instanceof AssignmentNode) { + updateObligationsForAssignment(obligations, cfg, (AssignmentNode) node); + } else if (node instanceof ReturnNode) { + updateObligationsForOwningReturn(obligations, cfg, (ReturnNode) node); + } else if (node instanceof MethodInvocationNode || node instanceof ObjectCreationNode) { + updateObligationsForInvocation(obligations, node, successorAndExceptionType.second); + } + // All other types of nodes are ignored. This is safe, because other kinds of + // nodes cannot create or modify the resource-alias sets that the algorithm is + // tracking. + } + + propagateObligationsToSuccessorBlock( + obligations, + currentBlock, + successorAndExceptionType.first, + successorAndExceptionType.second, + visited, + worklist); + } + } + + /** + * Helper for {@link #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, + * Deque)} that propagates obligations along a single edge. + * + * @param obligations the Obligations for the current block + * @param currentBlock the current block + * @param successor a successor of the current block + * @param exceptionType the type of edge from currentBlock to successor + * : null for normal control flow, or a throwable type for exceptional + * control flow + * @param visited block-Obligations pairs already analyzed or already on the worklist + * @param worklist current worklist + */ + private void propagateObligationsToSuccessorBlock( + Set obligations, + Block currentBlock, + Block successor, + @Nullable TypeMirror exceptionType, + Set visited, + Deque worklist) { + List currentBlockNodes = currentBlock.getNodes(); + // successorObligations eventually contains the Obligations to propagate to successor. + // The loop below mutates it. + Set successorObligations = new LinkedHashSet<>(); + // A detailed reason to give in the case that the last resource alias of an Obligation + // goes out of scope without a called-methods type that satisfies the corresponding + // must-call obligation along the current control-flow edge. Computed here for + // efficiency; used in the loop over the Obligations, below. + String exitReasonForErrorMessage = + exceptionType == null + ? + // Technically the variable may be going out of scope before the method + // exit, but that doesn't seem to provide additional helpful + // information. + "regular method exit" + : "possible exceptional exit due to " + + ((ExceptionBlock) currentBlock).getNode().getTree() + + " with exception type " + + exceptionType; + // Computed outside the Obligation loop for efficiency. + AccumulationStore regularStoreOfSuccessor = analysis.getInput(successor).getRegularStore(); + for (Obligation obligation : obligations) { + // This boolean is true if there is no evidence that the Obligation does not go out + // of scope - that is, if there is definitely a resource alias that is in scope in + // the successor. + boolean obligationGoesOutOfScopeBeforeSuccessor = true; + for (ResourceAlias resourceAlias : obligation.resourceAliases) { + if (aliasInScopeInSuccessor(regularStoreOfSuccessor, resourceAlias)) { + obligationGoesOutOfScopeBeforeSuccessor = false; + break; + } + } + // This check is to determine if this Obligation's resource aliases are definitely + // going out of scope: if this is an exit block or there is no information about any + // of them in the successor store, all aliases must be going out of scope and a + // consistency check should occur. + if (successor.getType() == BlockType.SPECIAL_BLOCK /* special blocks are exit blocks */ + || obligationGoesOutOfScopeBeforeSuccessor) { + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + + // If successor is an exceptional successor, and Obligation represents the + // temporary variable for currentBlock's node, do not propagate or do a + // consistency check, as in the exceptional case the "assignment" to the + // temporary variable does not succeed. + // + // Note that this test cannot be "successor.getType() == + // BlockType.EXCEPTIONAL_BLOCK", because not every exceptional successor is an + // exceptional block. For example, the successor might be a regular block + // (containing a catch clause, for example), or a special block indicating an + // exceptional exit. Nor can this test be "currentBlock.getType() == + // BlockType.EXCEPTIONAL_BLOCK", because some exception types are ignored. + // Whether exceptionType is null captures the logic of both of these cases. + if (exceptionType != null) { + Node exceptionalNode = NodeUtils.removeCasts(((ExceptionBlock) currentBlock).getNode()); + LocalVariableNode tmpVarForExcNode = typeFactory.getTempVarForNode(exceptionalNode); + if (tmpVarForExcNode != null + && obligation.resourceAliases.size() == 1 + && obligation.canBeSatisfiedThrough(tmpVarForExcNode)) { + continue; + } + } + + // Always propagate the Obligation to the successor if current block represents + // code nested in a cast. Without this logic, the analysis may report a false + // positive when the Obligation represents a temporary variable for a nested + // expression, as the temporary may not appear in the successor store and hence + // seems to be going out of scope. The temporary will be handled with special + // logic; casts are unwrapped at various points in the analysis. + if (currentBlockNodes.size() == 1 && inCast(currentBlockNodes.get(0))) { + successorObligations.add(obligation); + continue; + } + + // At this point, a consistency check will definitely occur, unless the + // obligation was derived from a MustCallAlias parameter. If it was, an error is + // immediately issued, because such a parameter should not go out of scope + // without its obligation being resolved some other way. + if (obligation.derivedFromMustCallAlias()) { + // MustCallAlias annotations only have meaning if the method returns + // normally, so issue an error if and only if this exit is happening on a + // normal exit path. + if (exceptionType == null + && obligation.whenToEnforce.contains(MethodExitKind.NORMAL_RETURN)) { + checker.reportError( + obligation.resourceAliases.asList().get(0).tree, + "mustcallalias.out.of.scope", + exitReasonForErrorMessage); + } + // Whether or not an error is issued, the check is now complete - there is + // no further checking to do on a must-call-alias-derived obligation along + // an exceptional path. + continue; + } + + // Which stores from the called-methods and must-call checkers are used in + // the consistency check varies depending on the context. The rules are: + // 1. if the current block has no nodes (and therefore the store must come from + // a block + // rather than a node): + // 1a. if there is information about any alias in the resource alias set + // in the successor store, use the successor's CM and MC stores, which + // contain whatever information is true after this block finishes. + // 1b. if there is not any information about any alias in the resource alias + // set in the successor store, use the current blocks' CM and MC stores, + // which contain whatever information is true before this (empty) block. + // 2. if the current block has one or more nodes, always use the CM store after + // the last node. To decide which MC store to use: + // 2a. if the last node in the block is the invocation of an + // @CreatesMustCallFor method that might throw an exception, and the + // consistency check is for an exceptional path, use the MC store + // immediately before the method invocation, because the method threw an + // exception rather than finishing and therefore did not actually create + // any must-call obligation, so the MC store after might contain + // must-call obligations that do not need to be fulfilled along this + // path. + // 2b. in all other cases, use the MC store from after the last node in + // the block. + CFStore mcStore; + AccumulationStore cmStore; + if (currentBlockNodes.size() == 0 /* currentBlock is special or conditional */) { + cmStore = + obligationGoesOutOfScopeBeforeSuccessor + ? analysis.getInput(currentBlock).getRegularStore() // 1a. (CM) + : regularStoreOfSuccessor; // 1b. (CM) + mcStore = + mcAtf.getStoreForBlock( + obligationGoesOutOfScopeBeforeSuccessor, + currentBlock, // 1a. (MC) + successor); // 1b. (MC) + } else { + // In this case, current block has at least one node. + // Use the called-methods store immediately after the last node in + // currentBlock. + Node last = currentBlockNodes.get(currentBlockNodes.size() - 1); // 2. (CM) + + if (cmStoreAfter.containsKey(last)) { + cmStore = cmStoreAfter.get(last); + } else { + cmStore = typeFactory.getStoreAfter(last); + cmStoreAfter.put(last, cmStore); + } + // If this is an exceptional block, check the MC store beforehand to avoid + // issuing an error about a call to a CreatesMustCallFor method that might + // throw an exception. Otherwise, use the store after. + if (exceptionType != null && isInvocationOfCreatesMustCallForMethod(last)) { + mcStore = mcAtf.getStoreBefore(last); // 2a. (MC) + } else { + if (mcStoreAfter.containsKey(last)) { + mcStore = mcStoreAfter.get(last); } else { - // In this case, there is info in the successor store about some alias in the - // Obligation. - // Handles the possibility that some resource in the Obligation may go out of - // scope. - Set copyOfResourceAliases = - new LinkedHashSet<>(obligation.resourceAliases); - copyOfResourceAliases.removeIf( - alias -> !aliasInScopeInSuccessor(regularStoreOfSuccessor, alias)); - successorObligations.add( - new Obligation(copyOfResourceAliases, obligation.whenToEnforce)); + mcStore = mcAtf.getStoreAfter(last); // 2b. (MC) + mcStoreAfter.put(last, mcStore); } - } - - propagate(new BlockWithObligations(successor, successorObligations), visited, worklist); + } + } + + MethodExitKind exitKind = + exceptionType == null ? MethodExitKind.NORMAL_RETURN : MethodExitKind.EXCEPTIONAL_EXIT; + if (obligation.whenToEnforce.contains(exitKind)) { + checkMustCall(obligation, cmStore, mcStore, exitReasonForErrorMessage); + } + } else { + // In this case, there is info in the successor store about some alias in the + // Obligation. + // Handles the possibility that some resource in the Obligation may go out of + // scope. + Set copyOfResourceAliases = new LinkedHashSet<>(obligation.resourceAliases); + copyOfResourceAliases.removeIf( + alias -> !aliasInScopeInSuccessor(regularStoreOfSuccessor, alias)); + successorObligations.add(new Obligation(copyOfResourceAliases, obligation.whenToEnforce)); + } } - /** - * Returns true if {@code alias.reference} is definitely in-scope in the successor store: that - * is, there is a value for it in {@code successorStore}. - * - * @param successorStore the regular store of the successor block - * @param alias the resource alias to check - * @return true if the variable is definitely in scope for the purposes of the consistency - * checking algorithm in the successor block from which the store came - */ - private boolean aliasInScopeInSuccessor(AccumulationStore successorStore, ResourceAlias alias) { - return successorStore.getValue(alias.reference) != null; + propagate(new BlockWithObligations(successor, successorObligations), visited, worklist); + } + + /** + * Returns true if {@code alias.reference} is definitely in-scope in the successor store: that is, + * there is a value for it in {@code successorStore}. + * + * @param successorStore the regular store of the successor block + * @param alias the resource alias to check + * @return true if the variable is definitely in scope for the purposes of the consistency + * checking algorithm in the successor block from which the store came + */ + private boolean aliasInScopeInSuccessor(AccumulationStore successorStore, ResourceAlias alias) { + return successorStore.getValue(alias.reference) != null; + } + + /** + * Returns true if node is a MethodInvocationNode of a method with a CreatesMustCallFor + * annotation. + * + * @param node a node + * @return true if node is a MethodInvocationNode of a method with a CreatesMustCallFor annotation + */ + private boolean isInvocationOfCreatesMustCallForMethod(Node node) { + if (!(node instanceof MethodInvocationNode)) { + return false; } - - /** - * Returns true if node is a MethodInvocationNode of a method with a CreatesMustCallFor - * annotation. - * - * @param node a node - * @return true if node is a MethodInvocationNode of a method with a CreatesMustCallFor - * annotation - */ - private boolean isInvocationOfCreatesMustCallForMethod(Node node) { - if (!(node instanceof MethodInvocationNode)) { - return false; - } - MethodInvocationNode miNode = (MethodInvocationNode) node; - return typeFactory.hasCreatesMustCallFor(miNode); + MethodInvocationNode miNode = (MethodInvocationNode) node; + return typeFactory.hasCreatesMustCallFor(miNode); + } + + /** + * Finds {@link Owning} formal parameters for the method corresponding to a CFG. + * + * @param cfg the CFG + * @return the owning formal parameters of the method that corresponds to the given cfg, or an + * empty set if the given CFG doesn't correspond to a method body + */ + private Set computeOwningParameters(ControlFlowGraph cfg) { + // TODO what about lambdas? + if (cfg.getUnderlyingAST().getKind() == Kind.METHOD) { + MethodTree method = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); + Set result = new LinkedHashSet<>(1); + for (VariableTree param : method.getParameters()) { + VariableElement paramElement = TreeUtils.elementFromDeclaration(param); + boolean hasMustCallAlias = typeFactory.hasMustCallAlias(paramElement); + if (hasMustCallAlias + || (typeFactory.declaredTypeHasMustCall(param) + && !noLightweightOwnership + && paramElement.getAnnotation(Owning.class) != null)) { + result.add( + new Obligation( + ImmutableSet.of( + new ResourceAlias( + new LocalVariable(paramElement), paramElement, param, hasMustCallAlias)), + Collections.singleton(MethodExitKind.NORMAL_RETURN))); + // Increment numMustCall for each @Owning parameter tracked by the enclosing + // method. + incrementNumMustCall(paramElement); + } + } + return result; } - - /** - * Finds {@link Owning} formal parameters for the method corresponding to a CFG. - * - * @param cfg the CFG - * @return the owning formal parameters of the method that corresponds to the given cfg, or an - * empty set if the given CFG doesn't correspond to a method body - */ - private Set computeOwningParameters(ControlFlowGraph cfg) { - // TODO what about lambdas? - if (cfg.getUnderlyingAST().getKind() == Kind.METHOD) { - MethodTree method = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); - Set result = new LinkedHashSet<>(1); - for (VariableTree param : method.getParameters()) { - VariableElement paramElement = TreeUtils.elementFromDeclaration(param); - boolean hasMustCallAlias = typeFactory.hasMustCallAlias(paramElement); - if (hasMustCallAlias - || (typeFactory.declaredTypeHasMustCall(param) - && !noLightweightOwnership - && paramElement.getAnnotation(Owning.class) != null)) { - result.add( - new Obligation( - ImmutableSet.of( - new ResourceAlias( - new LocalVariable(paramElement), - paramElement, - param, - hasMustCallAlias)), - Collections.singleton(MethodExitKind.NORMAL_RETURN))); - // Increment numMustCall for each @Owning parameter tracked by the enclosing - // method. - incrementNumMustCall(paramElement); - } - } - return result; - } - return Collections.emptySet(); + return Collections.emptySet(); + } + + /** + * Checks whether there is some resource alias set R in {@code obligations} such that + * R contains a {@link ResourceAlias} whose local variable is {@code node}. + * + * @param obligations the set of Obligations to search + * @param var the local variable to look for + * @return true iff there is a resource alias set in {@code obligations} that contains node + */ + private static boolean varTrackedInObligations( + Set obligations, LocalVariableNode var) { + for (Obligation obligation : obligations) { + if (obligation.canBeSatisfiedThrough(var)) { + return true; + } } - - /** - * Checks whether there is some resource alias set R in {@code obligations} such that - * R contains a {@link ResourceAlias} whose local variable is {@code node}. - * - * @param obligations the set of Obligations to search - * @param var the local variable to look for - * @return true iff there is a resource alias set in {@code obligations} that contains node - */ - private static boolean varTrackedInObligations( - Set obligations, LocalVariableNode var) { - for (Obligation obligation : obligations) { - if (obligation.canBeSatisfiedThrough(var)) { - return true; - } - } - return false; + return false; + } + + /** + * Gets the Obligation whose resource aliase set contains the given local variable, if one exists + * in {@code obligations}. + * + * @param obligations set of Obligations + * @param node variable of interest + * @return the Obligation in {@code obligations} whose resource alias set contains {@code node}, + * or {@code null} if there is no such Obligation + */ + /*package-private*/ static @Nullable Obligation getObligationForVar( + Set obligations, LocalVariableNode node) { + for (Obligation obligation : obligations) { + if (obligation.canBeSatisfiedThrough(node)) { + return obligation; + } } - - /** - * Gets the Obligation whose resource aliase set contains the given local variable, if one - * exists in {@code obligations}. - * - * @param obligations set of Obligations - * @param node variable of interest - * @return the Obligation in {@code obligations} whose resource alias set contains {@code node}, - * or {@code null} if there is no such Obligation - */ - /*package-private*/ static @Nullable Obligation getObligationForVar( - Set obligations, LocalVariableNode node) { - for (Obligation obligation : obligations) { - if (obligation.canBeSatisfiedThrough(node)) { - return obligation; - } - } - return null; + return null; + } + + /** + * For the given Obligation, checks that at least one of its variables has its {@code @MustCall} + * obligation satisfied, based on {@code @CalledMethods} and {@code @MustCall} types in the given + * stores. + * + * @param obligation the Obligation + * @param cmStore the called-methods store + * @param mcStore the must-call store + * @param outOfScopeReason if the {@code @MustCall} obligation is not satisfied, a useful + * explanation to include in the error message + */ + private void checkMustCall( + Obligation obligation, AccumulationStore cmStore, CFStore mcStore, String outOfScopeReason) { + + Map> mustCallValues = + obligation.getMustCallMethods(typeFactory, mcStore); + + // optimization: if mustCallValues is null, always issue a warning (there is no way to + // satisfy the check). A null mustCallValue occurs when the type is top (@MustCallUnknown). + if (mustCallValues == null) { + // Report the error at the first alias' definition. This choice is arbitrary but + // consistent. + ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); + if (!reportedErrorAliases.contains(firstAlias)) { + if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { + reportedErrorAliases.add(firstAlias); + checker.reportError( + firstAlias.tree, + "required.method.not.known", + firstAlias.stringForErrorMessage(), + firstAlias.reference.getType().toString(), + outOfScopeReason); + } + } + return; + } + if (mustCallValues.isEmpty()) { + throw new TypeSystemError("unexpected empty must-call values for obligation " + obligation); } - /** - * For the given Obligation, checks that at least one of its variables has its {@code @MustCall} - * obligation satisfied, based on {@code @CalledMethods} and {@code @MustCall} types in the - * given stores. - * - * @param obligation the Obligation - * @param cmStore the called-methods store - * @param mcStore the must-call store - * @param outOfScopeReason if the {@code @MustCall} obligation is not satisfied, a useful - * explanation to include in the error message - */ - private void checkMustCall( - Obligation obligation, - AccumulationStore cmStore, - CFStore mcStore, - String outOfScopeReason) { - - Map> mustCallValues = - obligation.getMustCallMethods(typeFactory, mcStore); - - // optimization: if mustCallValues is null, always issue a warning (there is no way to - // satisfy the check). A null mustCallValue occurs when the type is top (@MustCallUnknown). - if (mustCallValues == null) { - // Report the error at the first alias' definition. This choice is arbitrary but - // consistent. - ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); - if (!reportedErrorAliases.contains(firstAlias)) { - if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { - reportedErrorAliases.add(firstAlias); - checker.reportError( - firstAlias.tree, - "required.method.not.known", - firstAlias.stringForErrorMessage(), - firstAlias.reference.getType().toString(), - outOfScopeReason); - } - } - return; - } - if (mustCallValues.isEmpty()) { - throw new TypeSystemError( - "unexpected empty must-call values for obligation " + obligation); - } - - boolean mustCallSatisfied = false; - for (ResourceAlias alias : obligation.resourceAliases) { - - List mustCallValuesForAlias = mustCallValues.get(alias); - // optimization when there are no methods to call - if (mustCallValuesForAlias.isEmpty()) { - mustCallSatisfied = true; - break; - } - - // sometimes the store is null! this looks like a bug in checker dataflow. - // TODO track down and report the root-cause bug - AccumulationValue cmValue = cmStore != null ? cmStore.getValue(alias.reference) : null; - AnnotationMirror cmAnno = null; - - if (cmValue != null) { // When store contains the lhs - Set accumulatedValues = cmValue.getAccumulatedValues(); - if (accumulatedValues != null) { // type variable or wildcard type - cmAnno = - typeFactory.createCalledMethods( - accumulatedValues.toArray(new String[0])); - } else { - for (AnnotationMirror anno : cmValue.getAnnotations()) { - if (AnnotationUtils.areSameByName( - anno, - "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { - cmAnno = anno; - } - } - } - } - if (cmAnno == null) { - cmAnno = - typeFactory - .getAnnotatedType(alias.element) - .getEffectiveAnnotationInHierarchy(typeFactory.top); - } - - if (calledMethodsSatisfyMustCall(mustCallValuesForAlias, cmAnno)) { - mustCallSatisfied = true; - break; - } - } - - if (!mustCallSatisfied) { - // Report the error at the first alias' definition. This choice is arbitrary but - // consistent. - ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); - if (!reportedErrorAliases.contains(firstAlias)) { - if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { - reportedErrorAliases.add(firstAlias); - checker.reportError( - firstAlias.tree, - "required.method.not.called", - formatMissingMustCallMethods(mustCallValues.get(firstAlias)), - firstAlias.stringForErrorMessage(), - firstAlias.reference.getType().toString(), - outOfScopeReason); - } + boolean mustCallSatisfied = false; + for (ResourceAlias alias : obligation.resourceAliases) { + + List mustCallValuesForAlias = mustCallValues.get(alias); + // optimization when there are no methods to call + if (mustCallValuesForAlias.isEmpty()) { + mustCallSatisfied = true; + break; + } + + // sometimes the store is null! this looks like a bug in checker dataflow. + // TODO track down and report the root-cause bug + AccumulationValue cmValue = cmStore != null ? cmStore.getValue(alias.reference) : null; + AnnotationMirror cmAnno = null; + + if (cmValue != null) { // When store contains the lhs + Set accumulatedValues = cmValue.getAccumulatedValues(); + if (accumulatedValues != null) { // type variable or wildcard type + cmAnno = typeFactory.createCalledMethods(accumulatedValues.toArray(new String[0])); + } else { + for (AnnotationMirror anno : cmValue.getAnnotations()) { + if (AnnotationUtils.areSameByName( + anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { + cmAnno = anno; } - } + } + } + } + if (cmAnno == null) { + cmAnno = + typeFactory + .getAnnotatedType(alias.element) + .getEffectiveAnnotationInHierarchy(typeFactory.top); + } + + if (calledMethodsSatisfyMustCall(mustCallValuesForAlias, cmAnno)) { + mustCallSatisfied = true; + break; + } } - /** - * Increment the -AcountMustCall counter. - * - * @param node the node being counted, to extract the type - */ - private void incrementNumMustCall(Node node) { - if (countMustCall) { - TypeMirror type = node.getType(); - incrementMustCallImpl(type); - } + if (!mustCallSatisfied) { + // Report the error at the first alias' definition. This choice is arbitrary but + // consistent. + ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); + if (!reportedErrorAliases.contains(firstAlias)) { + if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { + reportedErrorAliases.add(firstAlias); + checker.reportError( + firstAlias.tree, + "required.method.not.called", + formatMissingMustCallMethods(mustCallValues.get(firstAlias)), + firstAlias.stringForErrorMessage(), + firstAlias.reference.getType().toString(), + outOfScopeReason); + } + } } - - /** - * Increment the -AcountMustCall counter. - * - * @param elt the elt being counted, to extract the type - */ - private void incrementNumMustCall(Element elt) { - if (countMustCall) { - TypeMirror type = elt.asType(); - incrementMustCallImpl(type); - } + } + + /** + * Increment the -AcountMustCall counter. + * + * @param node the node being counted, to extract the type + */ + private void incrementNumMustCall(Node node) { + if (countMustCall) { + TypeMirror type = node.getType(); + incrementMustCallImpl(type); } - - /** - * Shared implementation for the two version of countMustCall. Don't call this directly. - * - * @param type the type of the object that has a must call obligation - */ - private void incrementMustCallImpl(TypeMirror type) { - // only count uses of JDK classes, since that's what the paper reported - if (!isJdkClass(TypesUtils.getTypeElement(type).getQualifiedName().toString())) { - return; - } - checker.numMustCall++; + } + + /** + * Increment the -AcountMustCall counter. + * + * @param elt the elt being counted, to extract the type + */ + private void incrementNumMustCall(Element elt) { + if (countMustCall) { + TypeMirror type = elt.asType(); + incrementMustCallImpl(type); } - - /** - * Is the given class a java* class? This is a heuristic for whether the class was defined in - * the JDK. - * - * @param qualifiedName a fully qualified name of a class - * @return true iff the type's fully-qualified name starts with "java", indicating that it is - * from a java.* or javax.* package (probably) - */ - /*package-private*/ static boolean isJdkClass(String qualifiedName) { - return qualifiedName.startsWith("java"); + } + + /** + * Shared implementation for the two version of countMustCall. Don't call this directly. + * + * @param type the type of the object that has a must call obligation + */ + private void incrementMustCallImpl(TypeMirror type) { + // only count uses of JDK classes, since that's what the paper reported + if (!isJdkClass(TypesUtils.getTypeElement(type).getQualifiedName().toString())) { + return; } - - /** - * Do the called methods represented by the {@link CalledMethods} type {@code cmAnno} include - * all the methods in {@code mustCallValues}? - * - * @param mustCallValues the strings representing the must-call obligations - * @param cmAnno an annotation from the called-methods type hierarchy - * @return true iff cmAnno is a subtype of a called-methods annotation with the same values as - * mustCallValues - */ - /*package-private*/ boolean calledMethodsSatisfyMustCall( - List mustCallValues, AnnotationMirror cmAnno) { - // Create this annotation and use a subtype test because there's no guarantee that - // cmAnno is actually an instance of CalledMethods: it could be CMBottom or CMPredicate. - AnnotationMirror cmAnnoForMustCallMethods = - typeFactory.createCalledMethods( - mustCallValues.toArray(new String[mustCallValues.size()])); - return typeFactory - .getQualifierHierarchy() - .isSubtypeQualifiersOnly(cmAnno, cmAnnoForMustCallMethods); + checker.numMustCall++; + } + + /** + * Is the given class a java* class? This is a heuristic for whether the class was defined in the + * JDK. + * + * @param qualifiedName a fully qualified name of a class + * @return true iff the type's fully-qualified name starts with "java", indicating that it is from + * a java.* or javax.* package (probably) + */ + /*package-private*/ static boolean isJdkClass(String qualifiedName) { + return qualifiedName.startsWith("java"); + } + + /** + * Do the called methods represented by the {@link CalledMethods} type {@code cmAnno} include all + * the methods in {@code mustCallValues}? + * + * @param mustCallValues the strings representing the must-call obligations + * @param cmAnno an annotation from the called-methods type hierarchy + * @return true iff cmAnno is a subtype of a called-methods annotation with the same values as + * mustCallValues + */ + /*package-private*/ boolean calledMethodsSatisfyMustCall( + List mustCallValues, AnnotationMirror cmAnno) { + // Create this annotation and use a subtype test because there's no guarantee that + // cmAnno is actually an instance of CalledMethods: it could be CMBottom or CMPredicate. + AnnotationMirror cmAnnoForMustCallMethods = + typeFactory.createCalledMethods(mustCallValues.toArray(new String[mustCallValues.size()])); + return typeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly(cmAnno, cmAnnoForMustCallMethods); + } + + /** + * If the input {@code state} has not been visited yet, add it to {@code visited} and {@code + * worklist}. + * + * @param state the current state + * @param visited the states that have been analyzed or are already on the worklist + * @param worklist the states that will be analyzed + */ + private static void propagate( + BlockWithObligations state, + Set visited, + Deque worklist) { + + if (visited.add(state)) { + worklist.add(state); } + } + + /** + * Formats a list of must-call method names to be printed in an error message. + * + * @param mustCallVal the list of must-call strings + * @return a formatted string + */ + /*package-private*/ static String formatMissingMustCallMethods(List mustCallVal) { + int size = mustCallVal.size(); + if (size == 0) { + throw new TypeSystemError("empty mustCallVal " + mustCallVal); + } else if (size == 1) { + return "method " + mustCallVal.get(0); + } else { + return "methods " + String.join(", ", mustCallVal); + } + } - /** - * If the input {@code state} has not been visited yet, add it to {@code visited} and {@code - * worklist}. - * - * @param state the current state - * @param visited the states that have been analyzed or are already on the worklist - * @param worklist the states that will be analyzed - */ - private static void propagate( - BlockWithObligations state, - Set visited, - Deque worklist) { + /** + * A pair of a {@link Block} and a set of dataflow facts on entry to the block. Each dataflow fact + * represents a set of resource aliases for some tracked resource. The analyzer's worklist + * consists of BlockWithObligations objects, each representing the need to handle the set of + * dataflow facts reaching the block during analysis. + */ + /*package-private*/ static class BlockWithObligations { - if (visited.add(state)) { - worklist.add(state); - } - } + /** The block. */ + public final Block block; + + /** The dataflow facts. */ + public final ImmutableSet obligations; /** - * Formats a list of must-call method names to be printed in an error message. + * Create a new BlockWithObligations from a block and a set of dataflow facts. * - * @param mustCallVal the list of must-call strings - * @return a formatted string + * @param b the block + * @param obligations the set of incoming Obligations at the start of the block (may be the + * empty set) */ - /*package-private*/ static String formatMissingMustCallMethods(List mustCallVal) { - int size = mustCallVal.size(); - if (size == 0) { - throw new TypeSystemError("empty mustCallVal " + mustCallVal); - } else if (size == 1) { - return "method " + mustCallVal.get(0); - } else { - return "methods " + String.join(", ", mustCallVal); - } + public BlockWithObligations(Block b, Set obligations) { + this.block = b; + this.obligations = ImmutableSet.copyOf(obligations); } - /** - * A pair of a {@link Block} and a set of dataflow facts on entry to the block. Each dataflow - * fact represents a set of resource aliases for some tracked resource. The analyzer's worklist - * consists of BlockWithObligations objects, each representing the need to handle the set of - * dataflow facts reaching the block during analysis. - */ - /*package-private*/ static class BlockWithObligations { - - /** The block. */ - public final Block block; - - /** The dataflow facts. */ - public final ImmutableSet obligations; - - /** - * Create a new BlockWithObligations from a block and a set of dataflow facts. - * - * @param b the block - * @param obligations the set of incoming Obligations at the start of the block (may be the - * empty set) - */ - public BlockWithObligations(Block b, Set obligations) { - this.block = b; - this.obligations = ImmutableSet.copyOf(obligations); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - BlockWithObligations that = (BlockWithObligations) o; - return block.equals(that.block) && obligations.equals(that.obligations); - } - - @Override - public int hashCode() { - return Objects.hash(block, obligations); - } + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BlockWithObligations that = (BlockWithObligations) o; + return block.equals(that.block) && obligations.equals(that.obligations); + } - @Override - public String toString() { - return String.format( - "BWO{%s %d, %d obligations %d}", - block.getType(), block.getUid(), obligations.size(), obligations.hashCode()); - } + @Override + public int hashCode() { + return Objects.hash(block, obligations); + } - /** - * Returns a printed representation of a collection of BlockWithObligations. If a - * BlockWithObligations appears multiple times in the collection, it is printed more - * succinctly after the first time. - * - * @param bwos a collection of BlockWithObligations, to format - * @return a printed representation of a collection of BlockWithObligations - */ - public static String collectionToString(Collection bwos) { - List blocksWithDuplicates = new ArrayList<>(); - for (BlockWithObligations bwo : bwos) { - blocksWithDuplicates.add(bwo.block); - } - List duplicateBlocks = duplicates(blocksWithDuplicates); - StringJoiner result = new StringJoiner(", ", "BWOs[", "]"); - for (BlockWithObligations bwo : bwos) { - ImmutableSet obligations = bwo.obligations; - if (duplicateBlocks.contains(bwo.block)) { - result.add( - String.format( - "BWO{%s %d, %d obligations %s}", - bwo.block.getType(), - bwo.block.getUid(), - obligations.size(), - obligations)); - } else { - result.add( - String.format( - "BWO{%s %d, %d obligations}", - bwo.block.getType(), bwo.block.getUid(), obligations.size())); - } - } - return result.toString(); - } + @Override + public String toString() { + return String.format( + "BWO{%s %d, %d obligations %d}", + block.getType(), block.getUid(), obligations.size(), obligations.hashCode()); } - // TODO: Use from plume-lib's CollectionsPlume once version 1.9.0 is released. /** - * Returns the elements (once each) that appear more than once in the given collection. + * Returns a printed representation of a collection of BlockWithObligations. If a + * BlockWithObligations appears multiple times in the collection, it is printed more succinctly + * after the first time. * - * @param the type of elements - * @param c a collection - * @return the elements (once each) that appear more than once in the given collection + * @param bwos a collection of BlockWithObligations, to format + * @return a printed representation of a collection of BlockWithObligations */ - public static List duplicates(Collection c) { - // Inefficient (because of streams) but simple implementation. - Set withoutDuplicates = new HashSet<>(); - return c.stream().filter(n -> !withoutDuplicates.add(n)).collect(Collectors.toList()); + public static String collectionToString(Collection bwos) { + List blocksWithDuplicates = new ArrayList<>(); + for (BlockWithObligations bwo : bwos) { + blocksWithDuplicates.add(bwo.block); + } + List duplicateBlocks = duplicates(blocksWithDuplicates); + StringJoiner result = new StringJoiner(", ", "BWOs[", "]"); + for (BlockWithObligations bwo : bwos) { + ImmutableSet obligations = bwo.obligations; + if (duplicateBlocks.contains(bwo.block)) { + result.add( + String.format( + "BWO{%s %d, %d obligations %s}", + bwo.block.getType(), bwo.block.getUid(), obligations.size(), obligations)); + } else { + result.add( + String.format( + "BWO{%s %d, %d obligations}", + bwo.block.getType(), bwo.block.getUid(), obligations.size())); + } + } + return result.toString(); } + } + + // TODO: Use from plume-lib's CollectionsPlume once version 1.9.0 is released. + /** + * Returns the elements (once each) that appear more than once in the given collection. + * + * @param the type of elements + * @param c a collection + * @return the elements (once each) that appear more than once in the given collection + */ + public static List duplicates(Collection c) { + // Inefficient (because of streams) but simple implementation. + Set withoutDuplicates = new HashSet<>(); + return c.stream().filter(n -> !withoutDuplicates.add(n)).collect(Collectors.toList()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java index af7c31ca27b..c37dc4d3b86 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java @@ -4,7 +4,25 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; - +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCallAlias; @@ -42,27 +60,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - /** * This class implements the annotation inference algorithm for the Resource Leak Checker. It infers * annotations such as {@code @}{@link Owning} on owning fields and parameters, {@code @}{@link @@ -95,884 +92,856 @@ */ public class MustCallInference { - /** - * This map keeps the fields that have been inferred to be disposed within the current method. - * Keys represent inferred owning fields, and values contain the must-call method names (Note: - * currently this code assumes that the must-call set only contains one element). When inference - * finishes, all of the fields in this map will be given an @Owning annotation. Note that this - * map is not monotonically-increasing: fields may be added to this map and then removed during - * inference. For example, if a field's must-call method is called, it is added to this map. If, - * in a later statement in the same method, the same field is re-assigned, it will be removed - * from this map (since the field assignment invalidated the previously-inferred disposing of - * the obligation). - */ - private final Map disposedFields = new LinkedHashMap<>(); - - /** - * The owned fields. This includes: - * - *

    - *
  • fields with written {@code @Owning} annotations, and - *
  • the inferred owning fields in this analysis. - *
- * - * This set is a superset of the key set in the {@code disposedFields} map. - */ - private final Set owningFields = new HashSet<>(); - - /** - * The type factory for the Resource Leak Checker, which is used to access the Must Call - * Checker. - */ - private final ResourceLeakAnnotatedTypeFactory resourceLeakAtf; - - /** The MustCallConsistencyAnalyzer. */ - private final MustCallConsistencyAnalyzer mcca; - - /** The {@link Owning} annotation. */ - protected final AnnotationMirror OWNING; - - /** The {@link NotOwning} annotation. */ - protected final AnnotationMirror NOTOWNING; - - /** The {@link MustCallAlias} annotation. */ - protected final AnnotationMirror MUSTCALLALIAS; - - /** - * The control flow graph of the current method. There is a separate MustCallInference for each - * method. - */ - private final ControlFlowGraph cfg; - - /** The MethodTree of the current method. */ - private final MethodTree methodTree; - - /** The element for the current method. */ - private final ExecutableElement methodElt; - - /** The tree for the enclosing class of the current method. */ - private final ClassTree classTree; - - /** - * The element for the enclosing class of the current method. It can be null for certain kinds - * of anonymous classes, such as PolyCollectorTypeVar.java in the all-systems test suite. - */ - private final @Nullable TypeElement classElt; - - /** - * This map is used to track must-alias relationships between the obligations that are - * resource-aliased to the return nodes and method parameters. The keys are the obligation of - * return nodes, and the values are the index of current method formal parameter (1-based) that - * is aliased with the return node. This map will be used to infer the {@link MustCallAlias} - * annotation for method parameters. - */ - private final Map returnObligationToParameter = new HashMap<>(); - - /** - * Creates a MustCallInference instance. - * - * @param resourceLeakAtf the type factory - * @param cfg the control flow graph of the method to check - * @param mcca the MustCallConsistencyAnalyzer - */ - /*package-private*/ MustCallInference( - ResourceLeakAnnotatedTypeFactory resourceLeakAtf, - ControlFlowGraph cfg, - MustCallConsistencyAnalyzer mcca) { - this.resourceLeakAtf = resourceLeakAtf; - this.mcca = mcca; - this.cfg = cfg; - this.OWNING = - AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), Owning.class); - this.NOTOWNING = - AnnotationBuilder.fromClass( - this.resourceLeakAtf.getElementUtils(), NotOwning.class); - this.MUSTCALLALIAS = - AnnotationBuilder.fromClass( - this.resourceLeakAtf.getElementUtils(), MustCallAlias.class); - this.methodTree = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); - this.methodElt = TreeUtils.elementFromDeclaration(methodTree); - this.classTree = TreePathUtil.enclosingClass(resourceLeakAtf.getPath(methodTree)); - this.classElt = TreeUtils.elementFromDeclaration(classTree); - if (classElt != null) { - for (Element memberElt : classElt.getEnclosedElements()) { - if (memberElt.getKind().isField() && resourceLeakAtf.hasOwning(memberElt)) { - owningFields.add((VariableElement) memberElt); - } - } - } + /** + * This map keeps the fields that have been inferred to be disposed within the current method. + * Keys represent inferred owning fields, and values contain the must-call method names (Note: + * currently this code assumes that the must-call set only contains one element). When inference + * finishes, all of the fields in this map will be given an @Owning annotation. Note that this map + * is not monotonically-increasing: fields may be added to this map and then removed during + * inference. For example, if a field's must-call method is called, it is added to this map. If, + * in a later statement in the same method, the same field is re-assigned, it will be removed from + * this map (since the field assignment invalidated the previously-inferred disposing of the + * obligation). + */ + private final Map disposedFields = new LinkedHashMap<>(); + + /** + * The owned fields. This includes: + * + *
    + *
  • fields with written {@code @Owning} annotations, and + *
  • the inferred owning fields in this analysis. + *
+ * + * This set is a superset of the key set in the {@code disposedFields} map. + */ + private final Set owningFields = new HashSet<>(); + + /** + * The type factory for the Resource Leak Checker, which is used to access the Must Call Checker. + */ + private final ResourceLeakAnnotatedTypeFactory resourceLeakAtf; + + /** The MustCallConsistencyAnalyzer. */ + private final MustCallConsistencyAnalyzer mcca; + + /** The {@link Owning} annotation. */ + protected final AnnotationMirror OWNING; + + /** The {@link NotOwning} annotation. */ + protected final AnnotationMirror NOTOWNING; + + /** The {@link MustCallAlias} annotation. */ + protected final AnnotationMirror MUSTCALLALIAS; + + /** + * The control flow graph of the current method. There is a separate MustCallInference for each + * method. + */ + private final ControlFlowGraph cfg; + + /** The MethodTree of the current method. */ + private final MethodTree methodTree; + + /** The element for the current method. */ + private final ExecutableElement methodElt; + + /** The tree for the enclosing class of the current method. */ + private final ClassTree classTree; + + /** + * The element for the enclosing class of the current method. It can be null for certain kinds of + * anonymous classes, such as PolyCollectorTypeVar.java in the all-systems test suite. + */ + private final @Nullable TypeElement classElt; + + /** + * This map is used to track must-alias relationships between the obligations that are + * resource-aliased to the return nodes and method parameters. The keys are the obligation of + * return nodes, and the values are the index of current method formal parameter (1-based) that is + * aliased with the return node. This map will be used to infer the {@link MustCallAlias} + * annotation for method parameters. + */ + private final Map returnObligationToParameter = new HashMap<>(); + + /** + * Creates a MustCallInference instance. + * + * @param resourceLeakAtf the type factory + * @param cfg the control flow graph of the method to check + * @param mcca the MustCallConsistencyAnalyzer + */ + /*package-private*/ MustCallInference( + ResourceLeakAnnotatedTypeFactory resourceLeakAtf, + ControlFlowGraph cfg, + MustCallConsistencyAnalyzer mcca) { + this.resourceLeakAtf = resourceLeakAtf; + this.mcca = mcca; + this.cfg = cfg; + this.OWNING = AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), Owning.class); + this.NOTOWNING = + AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), NotOwning.class); + this.MUSTCALLALIAS = + AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), MustCallAlias.class); + this.methodTree = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); + this.methodElt = TreeUtils.elementFromDeclaration(methodTree); + this.classTree = TreePathUtil.enclosingClass(resourceLeakAtf.getPath(methodTree)); + this.classElt = TreeUtils.elementFromDeclaration(classTree); + if (classElt != null) { + for (Element memberElt : classElt.getEnclosedElements()) { + if (memberElt.getKind().isField() && resourceLeakAtf.hasOwning(memberElt)) { + owningFields.add((VariableElement) memberElt); + } + } } - - /** - * Creates a MustCallInference instance and runs the inference algorithm. This method is called - * by the {@link ResourceLeakAnnotatedTypeFactory#postAnalyze} method if Whole Program Inference - * is enabled. - * - * @param resourceLeakAtf the type factory - * @param cfg the control flow graph of the method to check - * @param mcca the MustCallConsistencyAnalyzer - */ - /*package-private*/ static void runMustCallInference( - ResourceLeakAnnotatedTypeFactory resourceLeakAtf, - ControlFlowGraph cfg, - MustCallConsistencyAnalyzer mcca) { - MustCallInference mustCallInferenceLogic = - new MustCallInference(resourceLeakAtf, cfg, mcca); - mustCallInferenceLogic.runInference(); - } - - /** - * Runs the inference algorithm on the current method (the {@link #cfg} field). It discovers - * annotations such as {@code @}{@link Owning} on owning fields and parameters, {@code @}{@link - * NotOwning} on return types, {@code @}{@link EnsuresCalledMethods} on methods, {@code @}{@link - * MustCallAlias} on parameters and return types, and {@code @}{@link InheritableMustCall} on - * class declarations. - * - *

Operationally, it checks method invocations for fields and parameters (with - * non-empty @MustCall obligations) along all paths to the regular exit point. - */ - private void runInference() { - - Set visited = new HashSet<>(); - Deque worklist = new ArrayDeque<>(); - - BlockWithObligations entry = - new BlockWithObligations(cfg.getEntryBlock(), getNonEmptyMCParams(cfg)); - worklist.add(entry); - visited.add(entry); - - while (!worklist.isEmpty()) { - BlockWithObligations current = worklist.remove(); - - // Use a LinkedHashSet for determinism. - Set obligations = new LinkedHashSet<>(current.obligations); - - for (Node node : current.block.getNodes()) { - // The obligation set calculated for RLC differs from the Inference process. In the - // Inference process, it exclusively tracks parameters with non-empty must-call - // types, whether they have the @Owning annotation or not. However, there are some - // shared computations, such as updateObligationsWithInvocationResult, which is used - // during inference and could potentially affect the RLC result if it were called - // before the checking phase. However, calling - // updateObligationsWithInvocationResult() will not have any side effects on the - // outcome of the Resource Leak Checker. This is because the inference occurs within - // the postAnalyze method of the ResourceLeakAnnotatedTypeFactory, once the - // consistency analyzer has completed its process. - if (node instanceof MethodInvocationNode || node instanceof ObjectCreationNode) { - mcca.updateObligationsWithInvocationResult(obligations, node); - inferOwningFromInvocation(obligations, node); - } else if (node instanceof AssignmentNode) { - analyzeAssignmentNode(obligations, (AssignmentNode) node); - } else if (node instanceof ReturnNode) { - analyzeReturnNode(obligations, (ReturnNode) node); - } - } - - addNonExceptionalSuccessorsToWorklist(obligations, current.block, visited, worklist); - } - - addMemberAndClassAnnotations(); - } - - /** - * Analyzes a return statement and performs two computations. Does nothing if the return - * expression's type has an empty must-call obligation. - * - *

    - *
  • If the returned expression is a field with non-empty must-call obligations, adds a - * {@link NotOwning} annotation to the return type of the current method. Note the - * implication: if a method returns a field of this class at any return site, the - * return type is inferred to be non-owning. - *
  • Compute the index of the parameter that is an alias of the return node and add it the - * {@link #returnObligationToParameter} map. - *
- * - * @param obligations the current set of tracked Obligations - * @param node the return node - */ - private void analyzeReturnNode(Set obligations, ReturnNode node) { - Node returnNode = node.getResult(); - returnNode = mcca.removeCastsAndGetTmpVarIfPresent(returnNode); - if (resourceLeakAtf.hasEmptyMustCallValue(returnNode.getTree())) { - return; - } - - if (returnNode instanceof FieldAccessNode) { - addNotOwningToMethodDecl(); - } else if (returnNode instanceof LocalVariableNode) { - Obligation returnNodeObligation = - MustCallConsistencyAnalyzer.getObligationForVar( - obligations, (LocalVariableNode) returnNode); - if (returnNodeObligation != null) { - returnObligationToParameter.put( - returnNodeObligation, getIndexOfParam(returnNodeObligation)); - } - } + } + + /** + * Creates a MustCallInference instance and runs the inference algorithm. This method is called by + * the {@link ResourceLeakAnnotatedTypeFactory#postAnalyze} method if Whole Program Inference is + * enabled. + * + * @param resourceLeakAtf the type factory + * @param cfg the control flow graph of the method to check + * @param mcca the MustCallConsistencyAnalyzer + */ + /*package-private*/ static void runMustCallInference( + ResourceLeakAnnotatedTypeFactory resourceLeakAtf, + ControlFlowGraph cfg, + MustCallConsistencyAnalyzer mcca) { + MustCallInference mustCallInferenceLogic = new MustCallInference(resourceLeakAtf, cfg, mcca); + mustCallInferenceLogic.runInference(); + } + + /** + * Runs the inference algorithm on the current method (the {@link #cfg} field). It discovers + * annotations such as {@code @}{@link Owning} on owning fields and parameters, {@code @}{@link + * NotOwning} on return types, {@code @}{@link EnsuresCalledMethods} on methods, {@code @}{@link + * MustCallAlias} on parameters and return types, and {@code @}{@link InheritableMustCall} on + * class declarations. + * + *

Operationally, it checks method invocations for fields and parameters (with + * non-empty @MustCall obligations) along all paths to the regular exit point. + */ + private void runInference() { + + Set visited = new HashSet<>(); + Deque worklist = new ArrayDeque<>(); + + BlockWithObligations entry = + new BlockWithObligations(cfg.getEntryBlock(), getNonEmptyMCParams(cfg)); + worklist.add(entry); + visited.add(entry); + + while (!worklist.isEmpty()) { + BlockWithObligations current = worklist.remove(); + + // Use a LinkedHashSet for determinism. + Set obligations = new LinkedHashSet<>(current.obligations); + + for (Node node : current.block.getNodes()) { + // The obligation set calculated for RLC differs from the Inference process. In the + // Inference process, it exclusively tracks parameters with non-empty must-call + // types, whether they have the @Owning annotation or not. However, there are some + // shared computations, such as updateObligationsWithInvocationResult, which is used + // during inference and could potentially affect the RLC result if it were called + // before the checking phase. However, calling + // updateObligationsWithInvocationResult() will not have any side effects on the + // outcome of the Resource Leak Checker. This is because the inference occurs within + // the postAnalyze method of the ResourceLeakAnnotatedTypeFactory, once the + // consistency analyzer has completed its process. + if (node instanceof MethodInvocationNode || node instanceof ObjectCreationNode) { + mcca.updateObligationsWithInvocationResult(obligations, node); + inferOwningFromInvocation(obligations, node); + } else if (node instanceof AssignmentNode) { + analyzeAssignmentNode(obligations, (AssignmentNode) node); + } else if (node instanceof ReturnNode) { + analyzeReturnNode(obligations, (ReturnNode) node); + } + } + + addNonExceptionalSuccessorsToWorklist(obligations, current.block, visited, worklist); } - /** - * Adds inferred {@literal @Owning} annotations to fields, {@literal @EnsuresCalledMethods} - * annotations to the current method, {@literal @MustCallAlias} annotations to the parameter, - * and {@literal @InheritableMustCall} annotations to the enclosing class. - */ - private void addMemberAndClassAnnotations() { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - assert wpi != null : "MustCallInference is running without WPI."; - for (VariableElement fieldElt : getOwningFields()) { - wpi.addFieldDeclarationAnnotation(fieldElt, OWNING); - } - if (!disposedFields.isEmpty()) { - addEnsuresCalledMethodsForDisposedFields(); - } - - // If all return statements alias the same parameter index, then add the @MustCallAlias - // annotation to that parameter and the return type. - if (!returnObligationToParameter.isEmpty()) { - if (returnObligationToParameter.values().stream().distinct().count() == 1) { - int indexOfParam = returnObligationToParameter.values().iterator().next(); - if (indexOfParam > 0) { - addMustCallAliasToFormalParameter(indexOfParam); - } - } - } - - addInheritableMustCallToClass(); - } - - /** - * Returns a set of obligations representing the formal parameters of the current method that - * have non-empty MustCall annotations. Returns an empty set if the given CFG doesn't correspond - * to a method body. - * - * @param cfg the control flow graph of the method to check - * @return a set of obligations representing the parameters with non-empty MustCall obligations - */ - private Set getNonEmptyMCParams(ControlFlowGraph cfg) { - // TODO what about lambdas? - if (cfg.getUnderlyingAST().getKind() != UnderlyingAST.Kind.METHOD) { - return Collections.emptySet(); - } - Set result = null; - for (VariableTree param : methodTree.getParameters()) { - if (resourceLeakAtf.declaredTypeHasMustCall(param)) { - VariableElement paramElement = TreeUtils.elementFromDeclaration(param); - if (result == null) { - result = new HashSet<>(2); - } - result.add( - new Obligation( - ImmutableSet.of( - new ResourceAlias( - new LocalVariable(paramElement), - paramElement, - param)), - Collections.singleton(MethodExitKind.NORMAL_RETURN))); - } - } - return result != null ? result : Collections.emptySet(); - } - - /** - * Retrieves the owning fields, including fields inferred as owning from the current iteration. - * - * @return the owning fields - */ - private Set getOwningFields() { - return owningFields; - } - - /** - * Adds an owning annotation to the formal parameter at the given index. - * - * @param index the index of a formal parameter of the current method (1-based) - */ - private void addOwningToParam(int index) { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, OWNING); - } - - /** - * Adds the node to the disposedFields map and the owningFields set if it is a field and its - * must-call obligation is satisfied by the given method call. If so, it will be given - * an @Owning annotation later. - * - * @param node possibly an owning field - * @param invocation method invoked on the possible owning field - */ - private void inferOwningField(Node node, MethodInvocationNode invocation) { - Element nodeElt = TreeUtils.elementFromTree(node.getTree()); - if (nodeElt == null || !nodeElt.getKind().isField()) { - return; - } - if (resourceLeakAtf.isFieldWithNonemptyMustCallValue(nodeElt)) { - node = NodeUtils.removeCasts(node); - JavaExpression nodeJe = JavaExpression.fromNode(node); - AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, nodeJe); - List mustCallValues = resourceLeakAtf.getMustCallValues(nodeElt); - if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { - assert !mustCallValues.isEmpty() - : "Must-call obligation of owning field " + nodeElt + " is empty."; - // TODO: generalize this to MustCall annotations with more than one element. - // Currently, this code assumes that the must-call set has only one element. - assert mustCallValues.size() == 1 - : "The must-call set of " - + nodeElt - + "should be a singleton: " - + mustCallValues; - disposedFields.put((VariableElement) nodeElt, mustCallValues.get(0)); - owningFields.add((VariableElement) nodeElt); - } - } + addMemberAndClassAnnotations(); + } + + /** + * Analyzes a return statement and performs two computations. Does nothing if the return + * expression's type has an empty must-call obligation. + * + *

    + *
  • If the returned expression is a field with non-empty must-call obligations, adds a {@link + * NotOwning} annotation to the return type of the current method. Note the implication: if + * a method returns a field of this class at any return site, the return type is + * inferred to be non-owning. + *
  • Compute the index of the parameter that is an alias of the return node and add it the + * {@link #returnObligationToParameter} map. + *
+ * + * @param obligations the current set of tracked Obligations + * @param node the return node + */ + private void analyzeReturnNode(Set obligations, ReturnNode node) { + Node returnNode = node.getResult(); + returnNode = mcca.removeCastsAndGetTmpVarIfPresent(returnNode); + if (resourceLeakAtf.hasEmptyMustCallValue(returnNode.getTree())) { + return; } - /** - * Analyzes an assignment statement and performs the following computations. Does nothing if the - * rhs is not a local variable or parameter, or if the rhs has no must-call obligation. - * - *
    - *
  • 1) If the current method under analysis is a constructor, the left-hand side of the - * assignment is the only owning field of the enclosing class, and the rhs is an alias of - * a formal parameter, it adds an {@code @MustCallAlias} annotation to the formal - * parameter and the result type of the constructor. - *
  • 2) If the left-hand side of the assignment is an owning field, and the rhs is an alias - * of a formal parameter, it adds an {@code @Owning} annotation to the formal parameter. - *
  • 3) Otherwise, updates the set of tracked obligations to account for the - * (pseudo-)assignment, as in a gen-kill dataflow analysis problem. - *
- * - * @param obligations the set of obligations to update - * @param assignmentNode the assignment statement - */ - private void analyzeAssignmentNode(Set obligations, AssignmentNode assignmentNode) { - Node lhs = assignmentNode.getTarget(); - Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); - // Use the temporary variable for the rhs if it exists. - Node rhs = mcca.removeCastsAndGetTmpVarIfPresent(assignmentNode.getExpression()); - - if (!(rhs instanceof LocalVariableNode)) { - return; - } - Obligation rhsObligation = - MustCallConsistencyAnalyzer.getObligationForVar( - obligations, (LocalVariableNode) rhs); - if (rhsObligation == null) { - return; - } - - if (lhsElement.getKind() == ElementKind.FIELD) { - if (!getOwningFields().contains(lhsElement)) { - return; - } - - // If the owning field is present in the disposedFields map and there is an assignment - // to the field, it must be removed from the set. This is essential since the - // disposedFields map is used for adding @EnsuresCalledMethods annotations to the - // current method later. Note that this removal doesn't affect the owning annotation we - // inferred for the field, as the owningField set is updated with the inferred owning - // field in the 'inferOwningField' method. - if (!TreeUtils.isConstructor(methodTree)) { - disposedFields.remove((VariableElement) lhsElement); - } - - int paramIndex = getIndexOfParam(rhsObligation); - if (paramIndex == -1) { - // We are only tracking formal parameter aliases. If the rhsObligation is not an - // alias of any of the formal parameters, it won't be present in the obligations - // set. Thus, skipping the rest of this method is fine. - return; - } - - if (TreeUtils.isConstructor(methodTree) && getOwningFields().size() == 1) { - // case 1 is satisfied. - addMustCallAliasToFormalParameter(paramIndex); - mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); - } else { - // case 2 is satisfied. - addOwningToParam(paramIndex); - mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); - } - - } else if (lhs instanceof LocalVariableNode) { - // Updates the set of tracked obligations. (case 4) - LocalVariableNode lhsVar = (LocalVariableNode) lhs; - mcca.updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); - } + if (returnNode instanceof FieldAccessNode) { + addNotOwningToMethodDecl(); + } else if (returnNode instanceof LocalVariableNode) { + Obligation returnNodeObligation = + MustCallConsistencyAnalyzer.getObligationForVar( + obligations, (LocalVariableNode) returnNode); + if (returnNodeObligation != null) { + returnObligationToParameter.put( + returnNodeObligation, getIndexOfParam(returnNodeObligation)); + } } - - /** - * Return the (1-based) index of the method parameter that exist in the set of aliases of the - * given {@code obligation}, if one exists; otherwise, return -1. - * - * @param obligation the obligation - * @return the index of the current method parameter that exist in the set of aliases of the - * given obligation, if one exists; otherwise, return -1. - */ - private int getIndexOfParam(Obligation obligation) { - Set resourceAliases = obligation.resourceAliases; - List paramElts = - CollectionsPlume.mapList( - TreeUtils::elementFromDeclaration, methodTree.getParameters()); - for (ResourceAlias resourceAlias : resourceAliases) { - int paramIndex = paramElts.indexOf(resourceAlias.element); - if (paramIndex != -1) { - return paramIndex + 1; - } - } - - return -1; - } - - /** Adds a {@link NotOwning} annotation to the current method. */ - private void addNotOwningToMethodDecl() { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addMethodDeclarationAnnotation(methodElt, NOTOWNING); - } - - /** - * Adds a {@link MustCallAlias} annotation to the formal parameter at the given index. - * - * @param index the index of a formal parameter of the current method (1-based) - */ - private void addMustCallAliasToFormalParameter(int index) { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addMethodDeclarationAnnotation(methodElt, MUSTCALLALIAS); - wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, MUSTCALLALIAS); - } - - /** - * Adds an {@link EnsuresCalledMethods} annotation to the current method for any owning field - * whose must-call obligation is satisfied within the current method, i.e., the fields in {@link - * #disposedFields}. - */ - private void addEnsuresCalledMethodsForDisposedFields() { - // The keys are the must-call method names, and the values are the set of fields on which - // those methods are called. This map is used to create a @EnsuresCalledMethods annotation - // for each set of fields that share the same must-call obligation. - Map> methodToFields = new LinkedHashMap<>(); - for (VariableElement disposedField : disposedFields.keySet()) { - String mustCallValue = disposedFields.get(disposedField); - String fieldName = "this." + disposedField.getSimpleName().toString(); - methodToFields - .computeIfAbsent(mustCallValue, k -> new LinkedHashSet<>()) - .add(fieldName); - } - - for (String mustCallValue : methodToFields.keySet()) { - Set fields = methodToFields.get(mustCallValue); - AnnotationMirror am = - createEnsuresCalledMethods( - fields.toArray(new String[fields.size()]), - new String[] {mustCallValue}); - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addMethodDeclarationAnnotation(methodElt, am); - } + } + + /** + * Adds inferred {@literal @Owning} annotations to fields, {@literal @EnsuresCalledMethods} + * annotations to the current method, {@literal @MustCallAlias} annotations to the parameter, and + * {@literal @InheritableMustCall} annotations to the enclosing class. + */ + private void addMemberAndClassAnnotations() { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + assert wpi != null : "MustCallInference is running without WPI."; + for (VariableElement fieldElt : getOwningFields()) { + wpi.addFieldDeclarationAnnotation(fieldElt, OWNING); + } + if (!disposedFields.isEmpty()) { + addEnsuresCalledMethodsForDisposedFields(); } - /** - * Possibly adds an InheritableMustCall annotation on the enclosing class. - * - *

Let the enclosing class be C. If C already has a non-empty MustCall type (that is written - * or inherited from one of its superclasses), this method preserves the exising must-call type - * to avoid infinite iteration. Otherwise, if the current method is not private and satisfies - * the must-call obligations of all the owning fields in C, it adds an InheritableMustCall - * annotation to C. - */ - private void addInheritableMustCallToClass() { - if (classElt == null) { - return; - } - - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - - List currentMustCallValues = resourceLeakAtf.getMustCallValues(classElt); - if (!currentMustCallValues.isEmpty()) { - // The class already has a MustCall annotation. - - // If it is inherited from a superclass, do nothing. - if (classElt.getSuperclass() != null) { - TypeMirror superType = classElt.getSuperclass(); - TypeElement superClassElt = TypesUtils.getTypeElement(superType); - if (superClassElt != null - && !resourceLeakAtf.getMustCallValues(superClassElt).isEmpty()) { - return; - } - } - - // If the enclosing class already has a non-empty @MustCall type, either added by - // programmers or inferred in previous iterations (not-inherited), we do not change it - // in the current analysis round to prevent potential inconsistencies and guarantee the - // termination of the inference algorithm. This becomes particularly important when - // multiple methods could satisfy the must-call obligation of the enclosing class. To - // ensure the existing @MustCall annotation is included in the inference result for this - // iteration, we re-add it. - assert currentMustCallValues.size() == 1 : "TODO: Handle multiple must-call values"; - AnnotationMirror am = - createInheritableMustCall(new String[] {currentMustCallValues.get(0)}); - wpi.addClassDeclarationAnnotation(classElt, am); - return; + // If all return statements alias the same parameter index, then add the @MustCallAlias + // annotation to that parameter and the return type. + if (!returnObligationToParameter.isEmpty()) { + if (returnObligationToParameter.values().stream().distinct().count() == 1) { + int indexOfParam = returnObligationToParameter.values().iterator().next(); + if (indexOfParam > 0) { + addMustCallAliasToFormalParameter(indexOfParam); } + } + } - // If the current method is not private and satisfies the must-call obligation of all owning - // fields, then add (to the class) an InheritableMustCall annotation with the name of this - // method. - if (!methodTree.getModifiers().getFlags().contains(Modifier.PRIVATE)) { - // Since the result of getOwningFields() is a superset of the key set in disposedFields - // map, it is sufficient to check the equality of their sizes to determine if both sets - // are equal. - if (!disposedFields.isEmpty() && disposedFields.size() == getOwningFields().size()) { - AnnotationMirror am = - createInheritableMustCall(new String[] {methodTree.getName().toString()}); - wpi.addClassDeclarationAnnotation(classElt, am); - } - } + addInheritableMustCallToClass(); + } + + /** + * Returns a set of obligations representing the formal parameters of the current method that have + * non-empty MustCall annotations. Returns an empty set if the given CFG doesn't correspond to a + * method body. + * + * @param cfg the control flow graph of the method to check + * @return a set of obligations representing the parameters with non-empty MustCall obligations + */ + private Set getNonEmptyMCParams(ControlFlowGraph cfg) { + // TODO what about lambdas? + if (cfg.getUnderlyingAST().getKind() != UnderlyingAST.Kind.METHOD) { + return Collections.emptySet(); + } + Set result = null; + for (VariableTree param : methodTree.getParameters()) { + if (resourceLeakAtf.declaredTypeHasMustCall(param)) { + VariableElement paramElement = TreeUtils.elementFromDeclaration(param); + if (result == null) { + result = new HashSet<>(2); + } + result.add( + new Obligation( + ImmutableSet.of( + new ResourceAlias(new LocalVariable(paramElement), paramElement, param)), + Collections.singleton(MethodExitKind.NORMAL_RETURN))); + } + } + return result != null ? result : Collections.emptySet(); + } + + /** + * Retrieves the owning fields, including fields inferred as owning from the current iteration. + * + * @return the owning fields + */ + private Set getOwningFields() { + return owningFields; + } + + /** + * Adds an owning annotation to the formal parameter at the given index. + * + * @param index the index of a formal parameter of the current method (1-based) + */ + private void addOwningToParam(int index) { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, OWNING); + } + + /** + * Adds the node to the disposedFields map and the owningFields set if it is a field and its + * must-call obligation is satisfied by the given method call. If so, it will be given an @Owning + * annotation later. + * + * @param node possibly an owning field + * @param invocation method invoked on the possible owning field + */ + private void inferOwningField(Node node, MethodInvocationNode invocation) { + Element nodeElt = TreeUtils.elementFromTree(node.getTree()); + if (nodeElt == null || !nodeElt.getKind().isField()) { + return; + } + if (resourceLeakAtf.isFieldWithNonemptyMustCallValue(nodeElt)) { + node = NodeUtils.removeCasts(node); + JavaExpression nodeJe = JavaExpression.fromNode(node); + AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, nodeJe); + List mustCallValues = resourceLeakAtf.getMustCallValues(nodeElt); + if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { + assert !mustCallValues.isEmpty() + : "Must-call obligation of owning field " + nodeElt + " is empty."; + // TODO: generalize this to MustCall annotations with more than one element. + // Currently, this code assumes that the must-call set has only one element. + assert mustCallValues.size() == 1 + : "The must-call set of " + nodeElt + "should be a singleton: " + mustCallValues; + disposedFields.put((VariableElement) nodeElt, mustCallValues.get(0)); + owningFields.add((VariableElement) nodeElt); + } + } + } + + /** + * Analyzes an assignment statement and performs the following computations. Does nothing if the + * rhs is not a local variable or parameter, or if the rhs has no must-call obligation. + * + *

    + *
  • 1) If the current method under analysis is a constructor, the left-hand side of the + * assignment is the only owning field of the enclosing class, and the rhs is an alias of a + * formal parameter, it adds an {@code @MustCallAlias} annotation to the formal parameter + * and the result type of the constructor. + *
  • 2) If the left-hand side of the assignment is an owning field, and the rhs is an alias of + * a formal parameter, it adds an {@code @Owning} annotation to the formal parameter. + *
  • 3) Otherwise, updates the set of tracked obligations to account for the + * (pseudo-)assignment, as in a gen-kill dataflow analysis problem. + *
+ * + * @param obligations the set of obligations to update + * @param assignmentNode the assignment statement + */ + private void analyzeAssignmentNode(Set obligations, AssignmentNode assignmentNode) { + Node lhs = assignmentNode.getTarget(); + Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); + // Use the temporary variable for the rhs if it exists. + Node rhs = mcca.removeCastsAndGetTmpVarIfPresent(assignmentNode.getExpression()); + + if (!(rhs instanceof LocalVariableNode)) { + return; + } + Obligation rhsObligation = + MustCallConsistencyAnalyzer.getObligationForVar(obligations, (LocalVariableNode) rhs); + if (rhsObligation == null) { + return; } - /** - * Infers ownership transfer at the method call to infer @owning annotations for formal - * parameters of the current method, if the parameter is passed into the call and the - * corresponding formal parameter of the callee is @owning. - * - * @param obligations the current set of tracked Obligations - * @param invocation the method or constructor invocation - */ - private void inferOwningParamsViaOwnershipTransfer( - Set obligations, Node invocation) { - List paramsOfCurrentMethod = methodTree.getParameters(); - if (paramsOfCurrentMethod.isEmpty()) { - return; - } - List calleeParams = mcca.getParametersOfInvocation(invocation); - if (calleeParams.isEmpty()) { - return; - } - List arguments = mcca.getArgumentsOfInvocation(invocation); - - for (int i = 0; i < arguments.size(); i++) { - if (!resourceLeakAtf.hasOwning(calleeParams.get(i))) { - continue; - } - for (int j = 0; j < paramsOfCurrentMethod.size(); j++) { - VariableTree paramOfCurrMethod = paramsOfCurrentMethod.get(j); - if (resourceLeakAtf.hasEmptyMustCallValue(paramOfCurrMethod)) { - continue; - } - - Node arg = NodeUtils.removeCasts(arguments.get(i)); - VariableElement paramElt = TreeUtils.elementFromDeclaration(paramOfCurrMethod); - if (nodeAndElementResourceAliased(obligations, arg, paramElt)) { - addOwningToParam(j + 1); - break; - } - } - } + if (lhsElement.getKind() == ElementKind.FIELD) { + if (!getOwningFields().contains(lhsElement)) { + return; + } + + // If the owning field is present in the disposedFields map and there is an assignment + // to the field, it must be removed from the set. This is essential since the + // disposedFields map is used for adding @EnsuresCalledMethods annotations to the + // current method later. Note that this removal doesn't affect the owning annotation we + // inferred for the field, as the owningField set is updated with the inferred owning + // field in the 'inferOwningField' method. + if (!TreeUtils.isConstructor(methodTree)) { + disposedFields.remove((VariableElement) lhsElement); + } + + int paramIndex = getIndexOfParam(rhsObligation); + if (paramIndex == -1) { + // We are only tracking formal parameter aliases. If the rhsObligation is not an + // alias of any of the formal parameters, it won't be present in the obligations + // set. Thus, skipping the rest of this method is fine. + return; + } + + if (TreeUtils.isConstructor(methodTree) && getOwningFields().size() == 1) { + // case 1 is satisfied. + addMustCallAliasToFormalParameter(paramIndex); + mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); + } else { + // case 2 is satisfied. + addOwningToParam(paramIndex); + mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); + } + + } else if (lhs instanceof LocalVariableNode) { + // Updates the set of tracked obligations. (case 4) + LocalVariableNode lhsVar = (LocalVariableNode) lhs; + mcca.updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); + } + } + + /** + * Return the (1-based) index of the method parameter that exist in the set of aliases of the + * given {@code obligation}, if one exists; otherwise, return -1. + * + * @param obligation the obligation + * @return the index of the current method parameter that exist in the set of aliases of the given + * obligation, if one exists; otherwise, return -1. + */ + private int getIndexOfParam(Obligation obligation) { + Set resourceAliases = obligation.resourceAliases; + List paramElts = + CollectionsPlume.mapList(TreeUtils::elementFromDeclaration, methodTree.getParameters()); + for (ResourceAlias resourceAlias : resourceAliases) { + int paramIndex = paramElts.indexOf(resourceAlias.element); + if (paramIndex != -1) { + return paramIndex + 1; + } } - /** - * Checks whether the given element is a resource alias of the given node in the provided set of - * obligations. - * - * @param obligations the current set of tracked Obligations - * @param node the node - * @param element the element - * @return true if {@code element} is a resource alias of {@code node} - */ - private boolean nodeAndElementResourceAliased( - Set obligations, Node node, VariableElement element) { - Set nodeAliases = getResourceAliasOfNode(obligations, node); - for (ResourceAlias nodeAlias : nodeAliases) { - Element nodeAliasElt = nodeAlias.element; - if (nodeAliasElt.equals(element)) { - return true; - } - } - return false; - } - - /** - * Infers @Owning annotations for fields or the parameters of the current method that are passed - * in the receiver or arguments position of a call if their must-call obligation is satisfied - * via the {@code invocation}. - * - * @param obligations the current set of tracked Obligations - * @param invocation a method invocation node to check - */ - private void inferOwningForRecieverOrFormalParamPassedToCall( - Set obligations, MethodInvocationNode invocation) { - Node receiver = invocation.getTarget().getReceiver(); - receiver = NodeUtils.removeCasts(receiver); - if (receiver.getTree() != null) { - inferOwningForParamOrField(obligations, invocation, receiver); - } + return -1; + } + + /** Adds a {@link NotOwning} annotation to the current method. */ + private void addNotOwningToMethodDecl() { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addMethodDeclarationAnnotation(methodElt, NOTOWNING); + } + + /** + * Adds a {@link MustCallAlias} annotation to the formal parameter at the given index. + * + * @param index the index of a formal parameter of the current method (1-based) + */ + private void addMustCallAliasToFormalParameter(int index) { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addMethodDeclarationAnnotation(methodElt, MUSTCALLALIAS); + wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, MUSTCALLALIAS); + } + + /** + * Adds an {@link EnsuresCalledMethods} annotation to the current method for any owning field + * whose must-call obligation is satisfied within the current method, i.e., the fields in {@link + * #disposedFields}. + */ + private void addEnsuresCalledMethodsForDisposedFields() { + // The keys are the must-call method names, and the values are the set of fields on which + // those methods are called. This map is used to create a @EnsuresCalledMethods annotation + // for each set of fields that share the same must-call obligation. + Map> methodToFields = new LinkedHashMap<>(); + for (VariableElement disposedField : disposedFields.keySet()) { + String mustCallValue = disposedFields.get(disposedField); + String fieldName = "this." + disposedField.getSimpleName().toString(); + methodToFields.computeIfAbsent(mustCallValue, k -> new LinkedHashSet<>()).add(fieldName); + } - for (Node argument : mcca.getArgumentsOfInvocation(invocation)) { - Node arg = mcca.removeCastsAndGetTmpVarIfPresent(argument); - // In the CFG, explicit passing of multiple arguments in the varargs position is - // represented via an ArrayCreationNode. In this case, it checks the called methods set - // of each argument passed in this position. - if (arg instanceof ArrayCreationNode) { - ArrayCreationNode varArgsNode = (ArrayCreationNode) arg; - for (Node varArgNode : varArgsNode.getInitializers()) { - inferOwningForParamOrField(obligations, invocation, varArgNode); - } - } else { - inferOwningForParamOrField(obligations, invocation, arg); - } - } + for (String mustCallValue : methodToFields.keySet()) { + Set fields = methodToFields.get(mustCallValue); + AnnotationMirror am = + createEnsuresCalledMethods( + fields.toArray(new String[fields.size()]), new String[] {mustCallValue}); + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addMethodDeclarationAnnotation(methodElt, am); + } + } + + /** + * Possibly adds an InheritableMustCall annotation on the enclosing class. + * + *

Let the enclosing class be C. If C already has a non-empty MustCall type (that is written or + * inherited from one of its superclasses), this method preserves the exising must-call type to + * avoid infinite iteration. Otherwise, if the current method is not private and satisfies the + * must-call obligations of all the owning fields in C, it adds an InheritableMustCall annotation + * to C. + */ + private void addInheritableMustCallToClass() { + if (classElt == null) { + return; } - /** - * Infers an @Owning annotation for the {@code arg} that can be a receiver or an argument passed - * into a method call if the must-call obligation of the {@code arg} is satisfied via the {@code - * invocation}. - * - * @param obligations the current set of tracked Obligations - * @param invocation the method invocation node to check - * @param arg a receiver or an argument passed to the method call - */ - private void inferOwningForParamOrField( - Set obligations, MethodInvocationNode invocation, Node arg) { - Element argElt = TreeUtils.elementFromTree(arg.getTree()); - // The must-call obligation of a field can be satisfied either through a call where it - // serves as a receiver or within the callee method when it is passed as an argument. - if (argElt != null && argElt.getKind().isField()) { - inferOwningField(arg, invocation); - return; - } + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + + List currentMustCallValues = resourceLeakAtf.getMustCallValues(classElt); + if (!currentMustCallValues.isEmpty()) { + // The class already has a MustCall annotation. + + // If it is inherited from a superclass, do nothing. + if (classElt.getSuperclass() != null) { + TypeMirror superType = classElt.getSuperclass(); + TypeElement superClassElt = TypesUtils.getTypeElement(superType); + if (superClassElt != null && !resourceLeakAtf.getMustCallValues(superClassElt).isEmpty()) { + return; + } + } + + // If the enclosing class already has a non-empty @MustCall type, either added by + // programmers or inferred in previous iterations (not-inherited), we do not change it + // in the current analysis round to prevent potential inconsistencies and guarantee the + // termination of the inference algorithm. This becomes particularly important when + // multiple methods could satisfy the must-call obligation of the enclosing class. To + // ensure the existing @MustCall annotation is included in the inference result for this + // iteration, we re-add it. + assert currentMustCallValues.size() == 1 : "TODO: Handle multiple must-call values"; + AnnotationMirror am = createInheritableMustCall(new String[] {currentMustCallValues.get(0)}); + wpi.addClassDeclarationAnnotation(classElt, am); + return; + } - List paramsOfCurrentMethod = methodTree.getParameters(); - outerLoop: - for (int i = 0; i < paramsOfCurrentMethod.size(); i++) { - VariableTree currentMethodParamTree = paramsOfCurrentMethod.get(i); - if (resourceLeakAtf.hasEmptyMustCallValue(currentMethodParamTree)) { - continue; - } - - VariableElement paramElt = TreeUtils.elementFromDeclaration(currentMethodParamTree); - if (!nodeAndElementResourceAliased(obligations, arg, paramElt)) { - continue; - } - - List mustCallValues = resourceLeakAtf.getMustCallValues(paramElt); - assert mustCallValues.size() <= 1 : "TODO: Handle larger must-call values sets"; - Set nodeAliases = getResourceAliasOfNode(obligations, arg); - for (ResourceAlias resourceAlias : nodeAliases) { - AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, resourceAlias.reference); - if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { - addOwningToParam(i + 1); - break outerLoop; - } - } - } + // If the current method is not private and satisfies the must-call obligation of all owning + // fields, then add (to the class) an InheritableMustCall annotation with the name of this + // method. + if (!methodTree.getModifiers().getFlags().contains(Modifier.PRIVATE)) { + // Since the result of getOwningFields() is a superset of the key set in disposedFields + // map, it is sufficient to check the equality of their sizes to determine if both sets + // are equal. + if (!disposedFields.isEmpty() && disposedFields.size() == getOwningFields().size()) { + AnnotationMirror am = + createInheritableMustCall(new String[] {methodTree.getName().toString()}); + wpi.addClassDeclarationAnnotation(classElt, am); + } } + } + + /** + * Infers ownership transfer at the method call to infer @owning annotations for formal parameters + * of the current method, if the parameter is passed into the call and the corresponding formal + * parameter of the callee is @owning. + * + * @param obligations the current set of tracked Obligations + * @param invocation the method or constructor invocation + */ + private void inferOwningParamsViaOwnershipTransfer(Set obligations, Node invocation) { + List paramsOfCurrentMethod = methodTree.getParameters(); + if (paramsOfCurrentMethod.isEmpty()) { + return; + } + List calleeParams = mcca.getParametersOfInvocation(invocation); + if (calleeParams.isEmpty()) { + return; + } + List arguments = mcca.getArgumentsOfInvocation(invocation); - /** - * Returns the set of resource aliases associated with the given node, by looking up the - * corresponding obligation in the given set of obligations. - * - * @param obligations the set of obligations to search in - * @param node the node whose resource aliases are to be returned - * @return the resource aliases associated with the given node, or an empty set if the node has - * none - */ - private Set getResourceAliasOfNode(Set obligations, Node node) { - Node tempVar = mcca.getTempVarOrNode(node); - if (!(tempVar instanceof LocalVariableNode)) { - return Collections.emptySet(); + for (int i = 0; i < arguments.size(); i++) { + if (!resourceLeakAtf.hasOwning(calleeParams.get(i))) { + continue; + } + for (int j = 0; j < paramsOfCurrentMethod.size(); j++) { + VariableTree paramOfCurrMethod = paramsOfCurrentMethod.get(j); + if (resourceLeakAtf.hasEmptyMustCallValue(paramOfCurrMethod)) { + continue; } - Obligation argumentObligation = - MustCallConsistencyAnalyzer.getObligationForVar( - obligations, (LocalVariableNode) tempVar); - if (argumentObligation == null) { - return Collections.emptySet(); - } - return argumentObligation.resourceAliases; - } - - /** - * Infers @Owning or @MustCallAlias annotations for formal parameters of the enclosing method - * and @Owning annotations for fields of the enclosing class, as follows: - * - *

    - *
  • If a formal parameter is passed as an owning parameter, add an @Owning annotation to - * that formal parameter (see {@link #inferOwningParamsViaOwnershipTransfer}). - *
  • It calls {@link #inferOwningForRecieverOrFormalParamPassedToCall} to infer @Owning - * annotations for the receiver or arguments of a call by analyzing the called-methods set - * after the call. - *
  • It calls {@link #inferMustCallAliasFromThisOrSuperCall} to infer @MustCallAlias - * annotation for formal parameters and the result of the constructor. - *
- * - * @param obligations the set of obligations to search in - * @param invocation the method or constructor invocation - */ - private void inferOwningFromInvocation(Set obligations, Node invocation) { - if (invocation instanceof ObjectCreationNode) { - // If the invocation corresponds to an object creation node, only ownership transfer - // checking is required, as constructor parameters may have an @Owning annotation. We - // do not handle @EnsuresCalledMethods annotations on constructors as we have not - // observed them in practice. - inferOwningParamsViaOwnershipTransfer(obligations, invocation); - } else if (invocation instanceof MethodInvocationNode) { - inferMustCallAliasFromThisOrSuperCall(obligations, (MethodInvocationNode) invocation); - inferOwningParamsViaOwnershipTransfer(obligations, invocation); - inferOwningForRecieverOrFormalParamPassedToCall( - obligations, (MethodInvocationNode) invocation); + Node arg = NodeUtils.removeCasts(arguments.get(i)); + VariableElement paramElt = TreeUtils.elementFromDeclaration(paramOfCurrMethod); + if (nodeAndElementResourceAliased(obligations, arg, paramElt)) { + addOwningToParam(j + 1); + break; } + } + } + } + + /** + * Checks whether the given element is a resource alias of the given node in the provided set of + * obligations. + * + * @param obligations the current set of tracked Obligations + * @param node the node + * @param element the element + * @return true if {@code element} is a resource alias of {@code node} + */ + private boolean nodeAndElementResourceAliased( + Set obligations, Node node, VariableElement element) { + Set nodeAliases = getResourceAliasOfNode(obligations, node); + for (ResourceAlias nodeAlias : nodeAliases) { + Element nodeAliasElt = nodeAlias.element; + if (nodeAliasElt.equals(element)) { + return true; + } + } + return false; + } + + /** + * Infers @Owning annotations for fields or the parameters of the current method that are passed + * in the receiver or arguments position of a call if their must-call obligation is satisfied via + * the {@code invocation}. + * + * @param obligations the current set of tracked Obligations + * @param invocation a method invocation node to check + */ + private void inferOwningForRecieverOrFormalParamPassedToCall( + Set obligations, MethodInvocationNode invocation) { + Node receiver = invocation.getTarget().getReceiver(); + receiver = NodeUtils.removeCasts(receiver); + if (receiver.getTree() != null) { + inferOwningForParamOrField(obligations, invocation, receiver); } - /** - * Adds the @MustCallAlias annotation to a method parameter when it is passed in - * a @MustCallAlias position during a constructor call using {@literal this} or {@literal - * super}. - * - * @param obligations the current set of tracked Obligations - * @param node a method invocation node - */ - private void inferMustCallAliasFromThisOrSuperCall( - Set obligations, MethodInvocationNode node) { - if (!TreeUtils.isSuperConstructorCall(node.getTree()) - && !TreeUtils.isThisConstructorCall(node.getTree())) { - return; - } - List calleeParams = mcca.getParametersOfInvocation(node); - List arguments = mcca.getArgumentsOfInvocation(node); - for (int i = 0; i < arguments.size(); i++) { - if (!resourceLeakAtf.hasMustCallAlias(calleeParams.get(i))) { - continue; - } - - Node arg = mcca.removeCastsAndGetTmpVarIfPresent(arguments.get(i)); - Obligation argObligation = - MustCallConsistencyAnalyzer.getObligationForVar( - obligations, (LocalVariableNode) arg); - if (argObligation == null) { - return; - } - int index = getIndexOfParam(argObligation); - if (index != -1) { - addMustCallAliasToFormalParameter(index); - break; - } - } + for (Node argument : mcca.getArgumentsOfInvocation(invocation)) { + Node arg = mcca.removeCastsAndGetTmpVarIfPresent(argument); + // In the CFG, explicit passing of multiple arguments in the varargs position is + // represented via an ArrayCreationNode. In this case, it checks the called methods set + // of each argument passed in this position. + if (arg instanceof ArrayCreationNode) { + ArrayCreationNode varArgsNode = (ArrayCreationNode) arg; + for (Node varArgNode : varArgsNode.getInitializers()) { + inferOwningForParamOrField(obligations, invocation, varArgNode); + } + } else { + inferOwningForParamOrField(obligations, invocation, arg); + } + } + } + + /** + * Infers an @Owning annotation for the {@code arg} that can be a receiver or an argument passed + * into a method call if the must-call obligation of the {@code arg} is satisfied via the {@code + * invocation}. + * + * @param obligations the current set of tracked Obligations + * @param invocation the method invocation node to check + * @param arg a receiver or an argument passed to the method call + */ + private void inferOwningForParamOrField( + Set obligations, MethodInvocationNode invocation, Node arg) { + Element argElt = TreeUtils.elementFromTree(arg.getTree()); + // The must-call obligation of a field can be satisfied either through a call where it + // serves as a receiver or within the callee method when it is passed as an argument. + if (argElt != null && argElt.getKind().isField()) { + inferOwningField(arg, invocation); + return; } - /** - * Returns the called methods annotation for the given Java expression after the invocation - * node. - * - * @param invocation the MethodInvocationNode - * @param varJe a Java expression - * @return the called methods annotation for the {@code varJe} after the {@code invocation} node - */ - private AnnotationMirror getCalledMethodsAnno( - MethodInvocationNode invocation, JavaExpression varJe) { - AccumulationStore cmStoreAfter = resourceLeakAtf.getStoreAfter(invocation); - AccumulationValue cmValue = cmStoreAfter == null ? null : cmStoreAfter.getValue(varJe); - - AnnotationMirror cmAnno = null; - - if (cmValue != null) { - // The store contains the lhs. - Set accumulatedValues = cmValue.getAccumulatedValues(); - if (accumulatedValues != null) { // type variable or wildcard type - cmAnno = - resourceLeakAtf.createCalledMethods( - accumulatedValues.toArray(new String[0])); - } else { - for (AnnotationMirror anno : cmValue.getAnnotations()) { - if (AnnotationUtils.areSameByName( - anno, - "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { - cmAnno = anno; - } - } - } - } + List paramsOfCurrentMethod = methodTree.getParameters(); + outerLoop: + for (int i = 0; i < paramsOfCurrentMethod.size(); i++) { + VariableTree currentMethodParamTree = paramsOfCurrentMethod.get(i); + if (resourceLeakAtf.hasEmptyMustCallValue(currentMethodParamTree)) { + continue; + } + + VariableElement paramElt = TreeUtils.elementFromDeclaration(currentMethodParamTree); + if (!nodeAndElementResourceAliased(obligations, arg, paramElt)) { + continue; + } + + List mustCallValues = resourceLeakAtf.getMustCallValues(paramElt); + assert mustCallValues.size() <= 1 : "TODO: Handle larger must-call values sets"; + Set nodeAliases = getResourceAliasOfNode(obligations, arg); + for (ResourceAlias resourceAlias : nodeAliases) { + AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, resourceAlias.reference); + if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { + addOwningToParam(i + 1); + break outerLoop; + } + } + } + } + + /** + * Returns the set of resource aliases associated with the given node, by looking up the + * corresponding obligation in the given set of obligations. + * + * @param obligations the set of obligations to search in + * @param node the node whose resource aliases are to be returned + * @return the resource aliases associated with the given node, or an empty set if the node has + * none + */ + private Set getResourceAliasOfNode(Set obligations, Node node) { + Node tempVar = mcca.getTempVarOrNode(node); + if (!(tempVar instanceof LocalVariableNode)) { + return Collections.emptySet(); + } - if (cmAnno == null) { - cmAnno = resourceLeakAtf.top; - } + Obligation argumentObligation = + MustCallConsistencyAnalyzer.getObligationForVar(obligations, (LocalVariableNode) tempVar); + if (argumentObligation == null) { + return Collections.emptySet(); + } + return argumentObligation.resourceAliases; + } + + /** + * Infers @Owning or @MustCallAlias annotations for formal parameters of the enclosing method + * and @Owning annotations for fields of the enclosing class, as follows: + * + *
    + *
  • If a formal parameter is passed as an owning parameter, add an @Owning annotation to that + * formal parameter (see {@link #inferOwningParamsViaOwnershipTransfer}). + *
  • It calls {@link #inferOwningForRecieverOrFormalParamPassedToCall} to infer @Owning + * annotations for the receiver or arguments of a call by analyzing the called-methods set + * after the call. + *
  • It calls {@link #inferMustCallAliasFromThisOrSuperCall} to infer @MustCallAlias + * annotation for formal parameters and the result of the constructor. + *
+ * + * @param obligations the set of obligations to search in + * @param invocation the method or constructor invocation + */ + private void inferOwningFromInvocation(Set obligations, Node invocation) { + if (invocation instanceof ObjectCreationNode) { + // If the invocation corresponds to an object creation node, only ownership transfer + // checking is required, as constructor parameters may have an @Owning annotation. We + // do not handle @EnsuresCalledMethods annotations on constructors as we have not + // observed them in practice. + inferOwningParamsViaOwnershipTransfer(obligations, invocation); + } else if (invocation instanceof MethodInvocationNode) { + inferMustCallAliasFromThisOrSuperCall(obligations, (MethodInvocationNode) invocation); + inferOwningParamsViaOwnershipTransfer(obligations, invocation); + inferOwningForRecieverOrFormalParamPassedToCall( + obligations, (MethodInvocationNode) invocation); + } + } + + /** + * Adds the @MustCallAlias annotation to a method parameter when it is passed in a @MustCallAlias + * position during a constructor call using {@literal this} or {@literal super}. + * + * @param obligations the current set of tracked Obligations + * @param node a method invocation node + */ + private void inferMustCallAliasFromThisOrSuperCall( + Set obligations, MethodInvocationNode node) { + if (!TreeUtils.isSuperConstructorCall(node.getTree()) + && !TreeUtils.isThisConstructorCall(node.getTree())) { + return; + } + List calleeParams = mcca.getParametersOfInvocation(node); + List arguments = mcca.getArgumentsOfInvocation(node); + for (int i = 0; i < arguments.size(); i++) { + if (!resourceLeakAtf.hasMustCallAlias(calleeParams.get(i))) { + continue; + } + + Node arg = mcca.removeCastsAndGetTmpVarIfPresent(arguments.get(i)); + Obligation argObligation = + MustCallConsistencyAnalyzer.getObligationForVar(obligations, (LocalVariableNode) arg); + if (argObligation == null) { + return; + } + int index = getIndexOfParam(argObligation); + if (index != -1) { + addMustCallAliasToFormalParameter(index); + break; + } + } + } + + /** + * Returns the called methods annotation for the given Java expression after the invocation node. + * + * @param invocation the MethodInvocationNode + * @param varJe a Java expression + * @return the called methods annotation for the {@code varJe} after the {@code invocation} node + */ + private AnnotationMirror getCalledMethodsAnno( + MethodInvocationNode invocation, JavaExpression varJe) { + AccumulationStore cmStoreAfter = resourceLeakAtf.getStoreAfter(invocation); + AccumulationValue cmValue = cmStoreAfter == null ? null : cmStoreAfter.getValue(varJe); + + AnnotationMirror cmAnno = null; + + if (cmValue != null) { + // The store contains the lhs. + Set accumulatedValues = cmValue.getAccumulatedValues(); + if (accumulatedValues != null) { // type variable or wildcard type + cmAnno = resourceLeakAtf.createCalledMethods(accumulatedValues.toArray(new String[0])); + } else { + for (AnnotationMirror anno : cmValue.getAnnotations()) { + if (AnnotationUtils.areSameByName( + anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { + cmAnno = anno; + } + } + } + } - return cmAnno; - } - - /** - * Adds all non-exceptional successors to {@code worklist}. - * - * @param obligations the current set of tracked Obligations - * @param curBlock the block whose successors to add to the worklist - * @param visited block-Obligations pairs already analyzed or already on the worklist - * @param worklist the worklist, which is side-effected by this method - */ - private void addNonExceptionalSuccessorsToWorklist( - Set obligations, - Block curBlock, - Set visited, - Deque worklist) { - - for (Block successor : getNonExceptionalSuccessors(curBlock)) { - // If successor is a special block, it must be the regular exit. - if (successor.getType() != Block.BlockType.SPECIAL_BLOCK) { - BlockWithObligations state = new BlockWithObligations(successor, obligations); - if (visited.add(state)) { - worklist.add(state); - } - } - } + if (cmAnno == null) { + cmAnno = resourceLeakAtf.top; } - /** - * Returns the non-exceptional successors of a block. - * - * @param cur a block - * @return the successors of the given block - */ - private List getNonExceptionalSuccessors(Block cur) { - if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { - ConditionalBlock ccur = (ConditionalBlock) cur; - return Arrays.asList(ccur.getThenSuccessor(), ccur.getElseSuccessor()); - } - if (!(cur instanceof SingleSuccessorBlock)) { - throw new BugInCF("Not a conditional block nor a SingleSuccessorBlock: " + cur); - } + return cmAnno; + } + + /** + * Adds all non-exceptional successors to {@code worklist}. + * + * @param obligations the current set of tracked Obligations + * @param curBlock the block whose successors to add to the worklist + * @param visited block-Obligations pairs already analyzed or already on the worklist + * @param worklist the worklist, which is side-effected by this method + */ + private void addNonExceptionalSuccessorsToWorklist( + Set obligations, + Block curBlock, + Set visited, + Deque worklist) { + + for (Block successor : getNonExceptionalSuccessors(curBlock)) { + // If successor is a special block, it must be the regular exit. + if (successor.getType() != Block.BlockType.SPECIAL_BLOCK) { + BlockWithObligations state = new BlockWithObligations(successor, obligations); + if (visited.add(state)) { + worklist.add(state); + } + } + } + } + + /** + * Returns the non-exceptional successors of a block. + * + * @param cur a block + * @return the successors of the given block + */ + private List getNonExceptionalSuccessors(Block cur) { + if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { + ConditionalBlock ccur = (ConditionalBlock) cur; + return Arrays.asList(ccur.getThenSuccessor(), ccur.getElseSuccessor()); + } + if (!(cur instanceof SingleSuccessorBlock)) { + throw new BugInCF("Not a conditional block nor a SingleSuccessorBlock: " + cur); + } - Block successor = ((SingleSuccessorBlock) cur).getSuccessor(); - if (successor != null) { - return Collections.singletonList(successor); - } - return Collections.emptyList(); - } - - /** - * Creates an {@code @EnsuresCalledMethods} annotation with the given arguments. - * - * @param value the expressions that will have methods called on them - * @param methods the methods guaranteed to be invoked on the expressions - * @return an {@code @EnsuresCalledMethods} annotation with the given arguments - */ - private AnnotationMirror createEnsuresCalledMethods(String[] value, String[] methods) { - AnnotationBuilder builder = - new AnnotationBuilder( - resourceLeakAtf.getProcessingEnv(), EnsuresCalledMethods.class); - builder.setValue("value", value); - builder.setValue("methods", methods); - AnnotationMirror am = builder.build(); - return am; - } - - /** - * Creates an {@code @InheritableMustCall} annotation with the given arguments. - * - * @param methods methods that might need to be called on the expression whose type is annotated - * @return an {@code @InheritableMustCall} annotation with the given arguments - */ - private AnnotationMirror createInheritableMustCall(String[] methods) { - AnnotationBuilder builder = - new AnnotationBuilder( - resourceLeakAtf.getProcessingEnv(), InheritableMustCall.class); - Arrays.sort(methods); - builder.setValue("value", methods); - return builder.build(); + Block successor = ((SingleSuccessorBlock) cur).getSuccessor(); + if (successor != null) { + return Collections.singletonList(successor); } + return Collections.emptyList(); + } + + /** + * Creates an {@code @EnsuresCalledMethods} annotation with the given arguments. + * + * @param value the expressions that will have methods called on them + * @param methods the methods guaranteed to be invoked on the expressions + * @return an {@code @EnsuresCalledMethods} annotation with the given arguments + */ + private AnnotationMirror createEnsuresCalledMethods(String[] value, String[] methods) { + AnnotationBuilder builder = + new AnnotationBuilder(resourceLeakAtf.getProcessingEnv(), EnsuresCalledMethods.class); + builder.setValue("value", value); + builder.setValue("methods", methods); + AnnotationMirror am = builder.build(); + return am; + } + + /** + * Creates an {@code @InheritableMustCall} annotation with the given arguments. + * + * @param methods methods that might need to be called on the expression whose type is annotated + * @return an {@code @InheritableMustCall} annotation with the given arguments + */ + private AnnotationMirror createInheritableMustCall(String[] methods) { + AnnotationBuilder builder = + new AnnotationBuilder(resourceLeakAtf.getProcessingEnv(), InheritableMustCall.class); + Arrays.sort(methods); + builder.setValue("value", methods); + return builder.build(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java index 0b68646c1c0..1dab92a1da8 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.resourceleak; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.calledmethods.CalledMethodsAnalysis; import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; -import javax.lang.model.type.TypeMirror; - /** * This variant of CFAnalysis extends the set of ignored exception types. * @@ -12,26 +11,26 @@ */ public class ResourceLeakAnalysis extends CalledMethodsAnalysis { - /** - * The set of exceptions to ignore, cached from {@link - * ResourceLeakChecker#getIgnoredExceptions()}. - */ - private final SetOfTypes ignoredExceptions; + /** + * The set of exceptions to ignore, cached from {@link + * ResourceLeakChecker#getIgnoredExceptions()}. + */ + private final SetOfTypes ignoredExceptions; - /** - * Creates a new {@code CalledMethodsAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - protected ResourceLeakAnalysis( - ResourceLeakChecker checker, CalledMethodsAnnotatedTypeFactory factory) { - super(checker, factory); - this.ignoredExceptions = checker.getIgnoredExceptions(); - } + /** + * Creates a new {@code CalledMethodsAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + protected ResourceLeakAnalysis( + ResourceLeakChecker checker, CalledMethodsAnnotatedTypeFactory factory) { + super(checker, factory); + this.ignoredExceptions = checker.getIgnoredExceptions(); + } - @Override - public boolean isIgnoredExceptionType(TypeMirror exceptionType) { - return ignoredExceptions.contains(getTypes(), exceptionType); - } + @Override + public boolean isIgnoredExceptionType(TypeMirror exceptionType) { + return ignoredExceptions.contains(getTypes(), exceptionType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java index be3c4da7403..a0befd944f2 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java @@ -3,7 +3,15 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; import org.checkerframework.checker.calledmethods.EnsuresCalledMethodOnExceptionContract; import org.checkerframework.checker.calledmethods.qual.CalledMethods; @@ -34,489 +42,473 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; - /** * The type factory for the Resource Leak Checker. The main difference between this and the Called * Methods type factory from which it is derived is that this version's {@link * #postAnalyze(ControlFlowGraph)} method checks that must-call obligations are fulfilled. */ public class ResourceLeakAnnotatedTypeFactory extends CalledMethodsAnnotatedTypeFactory - implements CreatesMustCallForElementSupplier { - - /** The MustCall.value element/field. */ - private final ExecutableElement mustCallValueElement = - TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); - - /** The EnsuresCalledMethods.value element/field. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsValueElement = - TreeUtils.getMethod(EnsuresCalledMethods.class, "value", 0, processingEnv); - - /** The EnsuresCalledMethods.methods element/field. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsMethodsElement = - TreeUtils.getMethod(EnsuresCalledMethods.class, "methods", 0, processingEnv); - - /** The EnsuresCalledMethods.List.value element/field. */ - private final ExecutableElement ensuresCalledMethodsListValueElement = - TreeUtils.getMethod(EnsuresCalledMethods.List.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.List.value element/field. */ - private final ExecutableElement createsMustCallForListValueElement = - TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.value element/field. */ - private final ExecutableElement createsMustCallForValueElement = - TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); - - /** True if -AnoResourceAliases was passed on the command line. */ - private final boolean noResourceAliases; - - /** - * Bidirectional map to store temporary variables created for expressions with - * non-empty @MustCall obligations and the corresponding trees. Keys are the artificial local - * variable nodes created as temporary variables; values are the corresponding trees. - * - *

Note that in an ideal world, this would be an {@code IdentityBiMap}: that is, a BiMap - * using {@link java.util.IdentityHashMap} as both of the backing maps. However, Guava doesn't - * have such a map AND their implementation is incompatible with IdentityHashMap as a backing - * map, because even their {@code AbstractBiMap} class uses {@code equals} calls in its - * implementation (and its documentation calls out that it and all its derived BiMaps are - * incompatible with IdentityHashMap as a backing map for this reason). Therefore, we use a - * regular BiMap. Doing so is safe iff 1) the LocalVariableNode keys all have different names, - * and 2) a standard Tree implementation that uses reference equality for equality (e.g., JCTree - * in javac) is used. - */ - private final BiMap tempVarToTree = HashBiMap.create(); - - /** - * Creates a new ResourceLeakAnnotatedTypeFactory. - * - * @param checker the checker associated with this type factory - */ - public ResourceLeakAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.noResourceAliases = checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES); - this.postInit(); - } - - /** - * Is the given element a candidate to be an owning field? A candidate owning field must have a - * non-empty must-call obligation. - * - * @param element a element - * @return true iff the given element is a field with non-empty @MustCall obligation - */ - /*package-private*/ boolean isFieldWithNonemptyMustCallValue(Element element) { - return element.getKind().isField() && !hasEmptyMustCallValue(element); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class); - } - - /** - * Creates a @CalledMethods annotation whose values are the given strings. - * - * @param val the methods that have been called - * @return an annotation indicating that the given methods have been called - */ - public AnnotationMirror createCalledMethods(String... val) { - return createAccumulatorAnnotation(Arrays.asList(val)); - } - - @Override - public void postAnalyze(ControlFlowGraph cfg) { - MustCallConsistencyAnalyzer mustCallConsistencyAnalyzer = - new MustCallConsistencyAnalyzer(this, (ResourceLeakAnalysis) this.analysis); - mustCallConsistencyAnalyzer.analyze(cfg); - - // Inferring owning annotations for @Owning fields/parameters, @EnsuresCalledMethods for - // finalizer methods and @InheritableMustCall annotations for the class declarations. - /* NO-AFU - if (getWholeProgramInference() != null) { - if (cfg.getUnderlyingAST().getKind() == UnderlyingAST.Kind.METHOD) { - MustCallInference.runMustCallInference(this, cfg, mustCallConsistencyAnalyzer); - } - } - */ - - super.postAnalyze(cfg); - tempVarToTree.clear(); - } - - @Override - protected ResourceLeakAnalysis createFlowAnalysis() { - return new ResourceLeakAnalysis((ResourceLeakChecker) checker, this); - } - - /** - * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on - * the type of {@code tree} is definitely empty. - * - *

This method only considers the declared type: it does not consider flow-sensitive - * refinement. - * - * @param tree a tree - * @return true if the Must Call type is non-empty or top - */ - /*package-private*/ boolean hasEmptyMustCallValue(Tree tree) { - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotatedTypeMirror mustCallAnnotatedType = - mustCallAnnotatedTypeFactory.getAnnotatedType(tree); - AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); - if (mustCallAnnotation != null) { - return getMustCallValues(mustCallAnnotation).isEmpty(); - } else { - // Indicates @MustCallUnknown, which should be treated (conservatively) as if it - // contains - // some must call values. - return false; - } - } - - /** - * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on - * the type of {@code element} is definitely empty. - * - *

This method only considers the declared type: it does not consider flow-sensitive - * refinement. - * - * @param element an element - * @return true if the Must Call type is non-empty or top - */ - /*package-private*/ boolean hasEmptyMustCallValue(Element element) { - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotatedTypeMirror mustCallAnnotatedType = - mustCallAnnotatedTypeFactory.getAnnotatedType(element); - AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); - if (mustCallAnnotation != null) { - return getMustCallValues(mustCallAnnotation).isEmpty(); - } else { - // Indicates @MustCallUnknown, which should be treated (conservatively) as if it - // contains - // some must call values. - return false; - } + implements CreatesMustCallForElementSupplier { + + /** The MustCall.value element/field. */ + private final ExecutableElement mustCallValueElement = + TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); + + /** The EnsuresCalledMethods.value element/field. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsValueElement = + TreeUtils.getMethod(EnsuresCalledMethods.class, "value", 0, processingEnv); + + /** The EnsuresCalledMethods.methods element/field. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsMethodsElement = + TreeUtils.getMethod(EnsuresCalledMethods.class, "methods", 0, processingEnv); + + /** The EnsuresCalledMethods.List.value element/field. */ + private final ExecutableElement ensuresCalledMethodsListValueElement = + TreeUtils.getMethod(EnsuresCalledMethods.List.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.List.value element/field. */ + private final ExecutableElement createsMustCallForListValueElement = + TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.value element/field. */ + private final ExecutableElement createsMustCallForValueElement = + TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); + + /** True if -AnoResourceAliases was passed on the command line. */ + private final boolean noResourceAliases; + + /** + * Bidirectional map to store temporary variables created for expressions with non-empty @MustCall + * obligations and the corresponding trees. Keys are the artificial local variable nodes created + * as temporary variables; values are the corresponding trees. + * + *

Note that in an ideal world, this would be an {@code IdentityBiMap}: that is, a BiMap using + * {@link java.util.IdentityHashMap} as both of the backing maps. However, Guava doesn't have such + * a map AND their implementation is incompatible with IdentityHashMap as a backing map, because + * even their {@code AbstractBiMap} class uses {@code equals} calls in its implementation (and its + * documentation calls out that it and all its derived BiMaps are incompatible with + * IdentityHashMap as a backing map for this reason). Therefore, we use a regular BiMap. Doing so + * is safe iff 1) the LocalVariableNode keys all have different names, and 2) a standard Tree + * implementation that uses reference equality for equality (e.g., JCTree in javac) is used. + */ + private final BiMap tempVarToTree = HashBiMap.create(); + + /** + * Creates a new ResourceLeakAnnotatedTypeFactory. + * + * @param checker the checker associated with this type factory + */ + public ResourceLeakAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.noResourceAliases = checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES); + this.postInit(); + } + + /** + * Is the given element a candidate to be an owning field? A candidate owning field must have a + * non-empty must-call obligation. + * + * @param element a element + * @return true iff the given element is a field with non-empty @MustCall obligation + */ + /*package-private*/ boolean isFieldWithNonemptyMustCallValue(Element element) { + return element.getKind().isField() && !hasEmptyMustCallValue(element); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class); + } + + /** + * Creates a @CalledMethods annotation whose values are the given strings. + * + * @param val the methods that have been called + * @return an annotation indicating that the given methods have been called + */ + public AnnotationMirror createCalledMethods(String... val) { + return createAccumulatorAnnotation(Arrays.asList(val)); + } + + @Override + public void postAnalyze(ControlFlowGraph cfg) { + MustCallConsistencyAnalyzer mustCallConsistencyAnalyzer = + new MustCallConsistencyAnalyzer(this, (ResourceLeakAnalysis) this.analysis); + mustCallConsistencyAnalyzer.analyze(cfg); + + // Inferring owning annotations for @Owning fields/parameters, @EnsuresCalledMethods for + // finalizer methods and @InheritableMustCall annotations for the class declarations. + /* NO-AFU + if (getWholeProgramInference() != null) { + if (cfg.getUnderlyingAST().getKind() == UnderlyingAST.Kind.METHOD) { + MustCallInference.runMustCallInference(this, cfg, mustCallConsistencyAnalyzer); + } } - - /** - * Returns the {@link MustCall#value} element/argument of the @MustCall annotation on the class - * type of {@code element}. If there is no such annotation, returns the empty list. - * - *

Do not use this method to get the MustCall values of an {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, - * use {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, - * CFStore)}. - * - *

Do not call {@link List#isEmpty()} on the result of this method: prefer to call {@link - * #hasEmptyMustCallValue(Element)}, which correctly accounts for @MustCallUnknown, instead. - * - * @param element an element - * @return the strings in its must-call type - */ - /*package-private*/ List getMustCallValues(Element element) { - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotatedTypeMirror mustCallAnnotatedType = - mustCallAnnotatedTypeFactory.getAnnotatedType(element); - AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); - return getMustCallValues(mustCallAnnotation); + */ + + super.postAnalyze(cfg); + tempVarToTree.clear(); + } + + @Override + protected ResourceLeakAnalysis createFlowAnalysis() { + return new ResourceLeakAnalysis((ResourceLeakChecker) checker, this); + } + + /** + * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on the + * type of {@code tree} is definitely empty. + * + *

This method only considers the declared type: it does not consider flow-sensitive + * refinement. + * + * @param tree a tree + * @return true if the Must Call type is non-empty or top + */ + /*package-private*/ boolean hasEmptyMustCallValue(Tree tree) { + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotatedTypeMirror mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType(tree); + AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); + if (mustCallAnnotation != null) { + return getMustCallValues(mustCallAnnotation).isEmpty(); + } else { + // Indicates @MustCallUnknown, which should be treated (conservatively) as if it + // contains + // some must call values. + return false; } - - /** - * Helper method for getting the must-call values from a must-call annotation. - * - * @param mustCallAnnotation a {@link MustCall} annotation, or null - * @return the strings in mustCallAnnotation's value element, or the empty list if - * mustCallAnnotation is null - */ - /*package-private*/ List getMustCallValues( - @Nullable AnnotationMirror mustCallAnnotation) { - if (mustCallAnnotation == null) { - return Collections.emptyList(); - } - return AnnotationUtils.getElementValueArray( - mustCallAnnotation, mustCallValueElement, String.class, Collections.emptyList()); + } + + /** + * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on the + * type of {@code element} is definitely empty. + * + *

This method only considers the declared type: it does not consider flow-sensitive + * refinement. + * + * @param element an element + * @return true if the Must Call type is non-empty or top + */ + /*package-private*/ boolean hasEmptyMustCallValue(Element element) { + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotatedTypeMirror mustCallAnnotatedType = + mustCallAnnotatedTypeFactory.getAnnotatedType(element); + AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); + if (mustCallAnnotation != null) { + return getMustCallValues(mustCallAnnotation).isEmpty(); + } else { + // Indicates @MustCallUnknown, which should be treated (conservatively) as if it + // contains + // some must call values. + return false; } - - /** - * Helper method to get the temporary variable that represents the given node, if one exists. - * - * @param node a node - * @return the tempvar for node's expression, or null if one does not exist - */ - /*package-private*/ @Nullable LocalVariableNode getTempVarForNode(Node node) { - return tempVarToTree.inverse().get(node.getTree()); + } + + /** + * Returns the {@link MustCall#value} element/argument of the @MustCall annotation on the class + * type of {@code element}. If there is no such annotation, returns the empty list. + * + *

Do not use this method to get the MustCall values of an {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, use + * {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, + * CFStore)}. + * + *

Do not call {@link List#isEmpty()} on the result of this method: prefer to call {@link + * #hasEmptyMustCallValue(Element)}, which correctly accounts for @MustCallUnknown, instead. + * + * @param element an element + * @return the strings in its must-call type + */ + /*package-private*/ List getMustCallValues(Element element) { + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotatedTypeMirror mustCallAnnotatedType = + mustCallAnnotatedTypeFactory.getAnnotatedType(element); + AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); + return getMustCallValues(mustCallAnnotation); + } + + /** + * Helper method for getting the must-call values from a must-call annotation. + * + * @param mustCallAnnotation a {@link MustCall} annotation, or null + * @return the strings in mustCallAnnotation's value element, or the empty list if + * mustCallAnnotation is null + */ + /*package-private*/ List getMustCallValues( + @Nullable AnnotationMirror mustCallAnnotation) { + if (mustCallAnnotation == null) { + return Collections.emptyList(); } - - /** - * Is the given node a temporary variable? - * - * @param node a node - * @return true iff the given node is a temporary variable - */ - /*package-private*/ boolean isTempVar(Node node) { - return tempVarToTree.containsKey(node); + return AnnotationUtils.getElementValueArray( + mustCallAnnotation, mustCallValueElement, String.class, Collections.emptyList()); + } + + /** + * Helper method to get the temporary variable that represents the given node, if one exists. + * + * @param node a node + * @return the tempvar for node's expression, or null if one does not exist + */ + /*package-private*/ @Nullable LocalVariableNode getTempVarForNode(Node node) { + return tempVarToTree.inverse().get(node.getTree()); + } + + /** + * Is the given node a temporary variable? + * + * @param node a node + * @return true iff the given node is a temporary variable + */ + /*package-private*/ boolean isTempVar(Node node) { + return tempVarToTree.containsKey(node); + } + + /** + * Gets the tree for a temporary variable + * + * @param node a node for a temporary variable + * @return the tree for {@code node} + */ + /*package-private*/ Tree getTreeForTempVar(Node node) { + if (!tempVarToTree.containsKey(node)) { + throw new TypeSystemError(node + " must be a temporary variable"); } - - /** - * Gets the tree for a temporary variable - * - * @param node a node for a temporary variable - * @return the tree for {@code node} - */ - /*package-private*/ Tree getTreeForTempVar(Node node) { - if (!tempVarToTree.containsKey(node)) { - throw new TypeSystemError(node + " must be a temporary variable"); - } - return tempVarToTree.get(node); + return tempVarToTree.get(node); + } + + /** + * Registers a temporary variable by adding it to this type factory's tempvar map. + * + * @param tmpVar a temporary variable + * @param tree the tree of the expression the tempvar represents + */ + /*package-private*/ void addTempVar(LocalVariableNode tmpVar, Tree tree) { + if (!tempVarToTree.containsValue(tree)) { + tempVarToTree.put(tmpVar, tree); } - - /** - * Registers a temporary variable by adding it to this type factory's tempvar map. - * - * @param tmpVar a temporary variable - * @param tree the tree of the expression the tempvar represents - */ - /*package-private*/ void addTempVar(LocalVariableNode tmpVar, Tree tree) { - if (!tempVarToTree.containsValue(tree)) { - tempVarToTree.put(tmpVar, tree); - } + } + + /** + * Returns true if the type of the tree includes a must-call annotation. Note that this method may + * not consider dataflow, and is only safe to use when you need the declared, rather than + * inferred, type of the tree. + * + *

Do not use this method if you are trying to get the must-call obligations of the resource + * aliases of an {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, use + * {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, + * CFStore)}. + * + * @param tree a tree + * @return whether the tree has declared must-call obligations + */ + /*package-private*/ boolean declaredTypeHasMustCall(Tree tree) { + assert tree.getKind() == Tree.Kind.METHOD + || tree.getKind() == Tree.Kind.VARIABLE + || tree.getKind() == Tree.Kind.NEW_CLASS + || tree.getKind() == Tree.Kind.METHOD_INVOCATION + : "unexpected declaration tree kind: " + tree.getKind(); + return !hasEmptyMustCallValue(tree); + } + + /** + * Returns true if the given tree has an {@link MustCallAlias} annotation and resource-alias + * tracking is not disabled. + * + * @param tree a tree + * @return true if the given tree has an {@link MustCallAlias} annotation + */ + /*package-private*/ boolean hasMustCallAlias(Tree tree) { + Element elt = TreeUtils.elementFromTree(tree); + return hasMustCallAlias(elt); + } + + /** + * Returns true if the given element has an {@link MustCallAlias} annotation and resource-alias + * tracking is not disabled. + * + * @param elt an element + * @return true if the given element has an {@link MustCallAlias} annotation + */ + /*package-private*/ boolean hasMustCallAlias(Element elt) { + if (noResourceAliases) { + return false; } - - /** - * Returns true if the type of the tree includes a must-call annotation. Note that this method - * may not consider dataflow, and is only safe to use when you need the declared, rather than - * inferred, type of the tree. - * - *

Do not use this method if you are trying to get the must-call obligations of the resource - * aliases of an {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, - * use {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, - * CFStore)}. - * - * @param tree a tree - * @return whether the tree has declared must-call obligations - */ - /*package-private*/ boolean declaredTypeHasMustCall(Tree tree) { - assert tree.getKind() == Tree.Kind.METHOD - || tree.getKind() == Tree.Kind.VARIABLE - || tree.getKind() == Tree.Kind.NEW_CLASS - || tree.getKind() == Tree.Kind.METHOD_INVOCATION - : "unexpected declaration tree kind: " + tree.getKind(); - return !hasEmptyMustCallValue(tree); + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + return mustCallAnnotatedTypeFactory.getDeclAnnotationNoAliases(elt, MustCallAlias.class) + != null; + } + + /** + * Returns true if the declaration of the method being invoked has one or more {@link + * CreatesMustCallFor} annotations. + * + * @param node a method invocation node + * @return true iff there is one or more @CreatesMustCallFor annotations on the declaration of the + * invoked method + */ + public boolean hasCreatesMustCallFor(MethodInvocationNode node) { + ExecutableElement decl = TreeUtils.elementFromUse(node.getTree()); + return getDeclAnnotation(decl, CreatesMustCallFor.class) != null + || getDeclAnnotation(decl, CreatesMustCallFor.List.class) != null; + } + + /** + * Does this type factory support {@link CreatesMustCallFor}? + * + * @return true iff the -AnoCreatesMustCallFor command-line argument was not supplied to the + * checker + */ + public boolean canCreateObligations() { + // Precomputing this call to `hasOption` causes a NullPointerException, so leave it as is. + return !checker.hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); + } + + @Override + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public > + @Nullable T getTypeFactoryOfSubcheckerOrNull( + Class subCheckerClass) { + if (subCheckerClass == MustCallChecker.class) { + if (!canCreateObligations()) { + return super.getTypeFactoryOfSubcheckerOrNull(MustCallNoCreatesMustCallForChecker.class); + } } - - /** - * Returns true if the given tree has an {@link MustCallAlias} annotation and resource-alias - * tracking is not disabled. - * - * @param tree a tree - * @return true if the given tree has an {@link MustCallAlias} annotation - */ - /*package-private*/ boolean hasMustCallAlias(Tree tree) { - Element elt = TreeUtils.elementFromTree(tree); - return hasMustCallAlias(elt); - } - - /** - * Returns true if the given element has an {@link MustCallAlias} annotation and resource-alias - * tracking is not disabled. - * - * @param elt an element - * @return true if the given element has an {@link MustCallAlias} annotation - */ - /*package-private*/ boolean hasMustCallAlias(Element elt) { - if (noResourceAliases) { - return false; + return super.getTypeFactoryOfSubcheckerOrNull(subCheckerClass); + } + + /** + * Returns the {@link EnsuresCalledMethods.List#value} element. + * + * @return the {@link EnsuresCalledMethods.List#value} element + */ + public ExecutableElement getEnsuresCalledMethodsListValueElement() { + return ensuresCalledMethodsListValueElement; + } + + /** + * Returns the {@link CreatesMustCallFor#value} element. + * + * @return the {@link CreatesMustCallFor#value} element + */ + @Override + public ExecutableElement getCreatesMustCallForValueElement() { + return createsMustCallForValueElement; + } + + /** + * Returns the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} + * element. + * + * @return the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} + * element + */ + @Override + public ExecutableElement getCreatesMustCallForListValueElement() { + return createsMustCallForListValueElement; + } + + /** + * Does the given element have an {@code @NotOwning} annotation (including in stub files)? + * + *

Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type factory + * directly, which won't find this annotation in stub files (it only considers stub files loaded + * by this checker, not subcheckers). + * + * @param elt an element + * @return whether there is a NotOwning annotation on the given element + */ + public boolean hasNotOwning(Element elt) { + MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); + return mcatf.getDeclAnnotation(elt, NotOwning.class) != null; + } + + /** + * Does the given element have an {@code @Owning} annotation (including in stub files)? + * + *

Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type factory + * directly, which won't find this annotation in stub files (it only considers stub files loaded + * by this checker, not subcheckers). + * + * @param elt an element + * @return whether there is an Owning annotation on the given element + */ + public boolean hasOwning(Element elt) { + MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); + return mcatf.getDeclAnnotation(elt, Owning.class) != null; + } + + @Override + public Set getExceptionalPostconditions( + ExecutableElement methodOrConstructor) { + Set result = + super.getExceptionalPostconditions(methodOrConstructor); + + // This override is a sneaky way to satisfy a few subtle design constraints: + // 1. The RLC requires destructors to close the class's @Owning fields even on exception + // (see ResourceLeakVisitor.checkOwningField). + // 2. In versions 3.39.0 and earlier, the RLC did not have the annotation + // @EnsuresCalledMethodsOnException, meaning that for destructors it had to treat + // a simple @EnsuresCalledMethods annotation as serving both purposes. + // + // As a result, there is a lot of code that is missing the "correct" + // @EnsuresCalledMethodsOnException annotations on its destructors. + // + // This override treats the @EnsuresCalledMethods annotations on destructors as if they + // were also @EnsuresCalledMethodsOnException for backwards compatibility. By overriding + // this method we get both directions of checking: destructor implementations have to + // satisfy these implicit contracts, and destructor callers get to benefit from them. + // + // It should be possible to remove this override entirely without sacrificing any soundness. + // However, that is undesirable at this point because it would be a breaking change. + // + // TODO: gradually remove this override. + // 1. When this override adds an implicit annotation, the Checker Framework should issue + // a warning along with a suggestion to add the right annotations. + // 2. After a few months we should remove this override and require proper annotations on + // all destructors. + + if (isMustCallMethod(methodOrConstructor)) { + Set normalPostconditions = + getContractsFromMethod().getPostconditions(methodOrConstructor); + for (Contract.Postcondition normalPostcondition : normalPostconditions) { + for (String method : getCalledMethods(normalPostcondition.annotation)) { + result.add( + new EnsuresCalledMethodOnExceptionContract( + normalPostcondition.expressionString, method)); } - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - return mustCallAnnotatedTypeFactory.getDeclAnnotationNoAliases(elt, MustCallAlias.class) - != null; - } - - /** - * Returns true if the declaration of the method being invoked has one or more {@link - * CreatesMustCallFor} annotations. - * - * @param node a method invocation node - * @return true iff there is one or more @CreatesMustCallFor annotations on the declaration of - * the invoked method - */ - public boolean hasCreatesMustCallFor(MethodInvocationNode node) { - ExecutableElement decl = TreeUtils.elementFromUse(node.getTree()); - return getDeclAnnotation(decl, CreatesMustCallFor.class) != null - || getDeclAnnotation(decl, CreatesMustCallFor.List.class) != null; - } - - /** - * Does this type factory support {@link CreatesMustCallFor}? - * - * @return true iff the -AnoCreatesMustCallFor command-line argument was not supplied to the - * checker - */ - public boolean canCreateObligations() { - // Precomputing this call to `hasOption` causes a NullPointerException, so leave it as is. - return !checker.hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); + } } - @Override - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public > - @Nullable T getTypeFactoryOfSubcheckerOrNull( - Class subCheckerClass) { - if (subCheckerClass == MustCallChecker.class) { - if (!canCreateObligations()) { - return super.getTypeFactoryOfSubcheckerOrNull( - MustCallNoCreatesMustCallForChecker.class); - } - } - return super.getTypeFactoryOfSubcheckerOrNull(subCheckerClass); - } - - /** - * Returns the {@link EnsuresCalledMethods.List#value} element. - * - * @return the {@link EnsuresCalledMethods.List#value} element - */ - public ExecutableElement getEnsuresCalledMethodsListValueElement() { - return ensuresCalledMethodsListValueElement; - } - - /** - * Returns the {@link CreatesMustCallFor#value} element. - * - * @return the {@link CreatesMustCallFor#value} element - */ - @Override - public ExecutableElement getCreatesMustCallForValueElement() { - return createsMustCallForValueElement; - } - - /** - * Returns the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} - * element. - * - * @return the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} - * element - */ - @Override - public ExecutableElement getCreatesMustCallForListValueElement() { - return createsMustCallForListValueElement; - } - - /** - * Does the given element have an {@code @NotOwning} annotation (including in stub files)? - * - *

Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type - * factory directly, which won't find this annotation in stub files (it only considers stub - * files loaded by this checker, not subcheckers). - * - * @param elt an element - * @return whether there is a NotOwning annotation on the given element - */ - public boolean hasNotOwning(Element elt) { - MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); - return mcatf.getDeclAnnotation(elt, NotOwning.class) != null; - } - - /** - * Does the given element have an {@code @Owning} annotation (including in stub files)? - * - *

Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type - * factory directly, which won't find this annotation in stub files (it only considers stub - * files loaded by this checker, not subcheckers). - * - * @param elt an element - * @return whether there is an Owning annotation on the given element - */ - public boolean hasOwning(Element elt) { - MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); - return mcatf.getDeclAnnotation(elt, Owning.class) != null; - } - - @Override - public Set getExceptionalPostconditions( - ExecutableElement methodOrConstructor) { - Set result = - super.getExceptionalPostconditions(methodOrConstructor); - - // This override is a sneaky way to satisfy a few subtle design constraints: - // 1. The RLC requires destructors to close the class's @Owning fields even on exception - // (see ResourceLeakVisitor.checkOwningField). - // 2. In versions 3.39.0 and earlier, the RLC did not have the annotation - // @EnsuresCalledMethodsOnException, meaning that for destructors it had to treat - // a simple @EnsuresCalledMethods annotation as serving both purposes. - // - // As a result, there is a lot of code that is missing the "correct" - // @EnsuresCalledMethodsOnException annotations on its destructors. - // - // This override treats the @EnsuresCalledMethods annotations on destructors as if they - // were also @EnsuresCalledMethodsOnException for backwards compatibility. By overriding - // this method we get both directions of checking: destructor implementations have to - // satisfy these implicit contracts, and destructor callers get to benefit from them. - // - // It should be possible to remove this override entirely without sacrificing any soundness. - // However, that is undesirable at this point because it would be a breaking change. - // - // TODO: gradually remove this override. - // 1. When this override adds an implicit annotation, the Checker Framework should issue - // a warning along with a suggestion to add the right annotations. - // 2. After a few months we should remove this override and require proper annotations on - // all destructors. - - if (isMustCallMethod(methodOrConstructor)) { - Set normalPostconditions = - getContractsFromMethod().getPostconditions(methodOrConstructor); - for (Contract.Postcondition normalPostcondition : normalPostconditions) { - for (String method : getCalledMethods(normalPostcondition.annotation)) { - result.add( - new EnsuresCalledMethodOnExceptionContract( - normalPostcondition.expressionString, method)); - } - } - } - - return result; - } - - /** - * Returns true iff the {@code MustCall} annotation of the class that encloses the methodTree - * names this method. - * - * @param elt a method - * @return whether that method is one of the must-call methods for its enclosing class - */ - private boolean isMustCallMethod(ExecutableElement elt) { - TypeElement containingClass = ElementUtils.enclosingTypeElement(elt); - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotationMirror mcAnno = - mustCallAnnotatedTypeFactory - .getAnnotatedType(containingClass) - .getAnnotationInHierarchy(mustCallAnnotatedTypeFactory.TOP); - List mcValues = - AnnotationUtils.getElementValueArray( - mcAnno, - mustCallAnnotatedTypeFactory.getMustCallValueElement(), - String.class); - String methodName = elt.getSimpleName().toString(); - return mcValues.contains(methodName); - } + return result; + } + + /** + * Returns true iff the {@code MustCall} annotation of the class that encloses the methodTree + * names this method. + * + * @param elt a method + * @return whether that method is one of the must-call methods for its enclosing class + */ + private boolean isMustCallMethod(ExecutableElement elt) { + TypeElement containingClass = ElementUtils.enclosingTypeElement(elt); + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotationMirror mcAnno = + mustCallAnnotatedTypeFactory + .getAnnotatedType(containingClass) + .getAnnotationInHierarchy(mustCallAnnotatedTypeFactory.TOP); + List mcValues = + AnnotationUtils.getElementValueArray( + mcAnno, mustCallAnnotatedTypeFactory.getMustCallValueElement(), String.class); + String methodName = elt.getSimpleName().toString(); + return mcValues.contains(methodName); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java index fdfd3435e3b..241a2fd06e8 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java @@ -1,7 +1,15 @@ package org.checkerframework.checker.resourceleak; import com.google.common.collect.ImmutableSet; - +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.mustcall.MustCallChecker; @@ -13,295 +21,273 @@ import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedOptions; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic; - /** * The entry point for the Resource Leak Checker. This checker is a modifed {@link * CalledMethodsChecker} that checks that the must-call obligations of each expression (as computed * via the {@link org.checkerframework.checker.mustcall.MustCallChecker} have been fulfilled. */ @SupportedOptions({ - "permitStaticOwning", - "permitInitializationLeak", - ResourceLeakChecker.COUNT_MUST_CALL, - ResourceLeakChecker.IGNORED_EXCEPTIONS, - MustCallChecker.NO_CREATES_MUSTCALLFOR, - MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, - MustCallChecker.NO_RESOURCE_ALIASES, - // NO-AFU ResourceLeakChecker.ENABLE_WPI_FOR_RLC, + "permitStaticOwning", + "permitInitializationLeak", + ResourceLeakChecker.COUNT_MUST_CALL, + ResourceLeakChecker.IGNORED_EXCEPTIONS, + MustCallChecker.NO_CREATES_MUSTCALLFOR, + MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, + MustCallChecker.NO_RESOURCE_ALIASES, + // NO-AFU ResourceLeakChecker.ENABLE_WPI_FOR_RLC, }) @StubFiles("IOUtils.astub") public class ResourceLeakChecker extends CalledMethodsChecker { - /** Creates a ResourceLeakChecker. */ - public ResourceLeakChecker() {} + /** Creates a ResourceLeakChecker. */ + public ResourceLeakChecker() {} - /** - * Command-line option for counting how many must-call obligations were checked by the Resource - * Leak Checker, and emitting the number after processing all files. Used for generating tables - * for a research paper. Not of interest to most users. - */ - public static final String COUNT_MUST_CALL = "countMustCall"; + /** + * Command-line option for counting how many must-call obligations were checked by the Resource + * Leak Checker, and emitting the number after processing all files. Used for generating tables + * for a research paper. Not of interest to most users. + */ + public static final String COUNT_MUST_CALL = "countMustCall"; - /** - * The exception types in this set are ignored in the CFG when determining if a resource leaks - * along an exceptional path. These kinds of errors fall into a few categories: runtime errors, - * errors that the JVM can issue on any statement, and errors that can be prevented by running - * some other CF checker. - */ - private static final SetOfTypes DEFAULT_IGNORED_EXCEPTIONS = - SetOfTypes.anyOfTheseNames( - ImmutableSet.of( - // Any method call has a CFG edge for Throwable/RuntimeException/Error - // to represent run-time misbehavior. Ignore it. - Throwable.class.getCanonicalName(), - Error.class.getCanonicalName(), - RuntimeException.class.getCanonicalName(), - // Use the Nullness Checker to prove this won't happen. - NullPointerException.class.getCanonicalName(), - // These errors can't be predicted statically, so ignore them and assume - // they won't happen. - ClassCircularityError.class.getCanonicalName(), - ClassFormatError.class.getCanonicalName(), - NoClassDefFoundError.class.getCanonicalName(), - OutOfMemoryError.class.getCanonicalName(), - // It's not our problem if the Java type system is wrong. - ClassCastException.class.getCanonicalName(), - // It's not our problem if the code is going to divide by zero. - ArithmeticException.class.getCanonicalName(), - // Use the Index Checker to prevent these errors. - ArrayIndexOutOfBoundsException.class.getCanonicalName(), - NegativeArraySizeException.class.getCanonicalName(), - // Most of the time, this exception is infeasible, as the charset used - // is guaranteed to be present by the Java spec (e.g., "UTF-8"). - // Eventually, this exclusion could be refined by looking at the charset - // being requested. - UnsupportedEncodingException.class.getCanonicalName())); + /** + * The exception types in this set are ignored in the CFG when determining if a resource leaks + * along an exceptional path. These kinds of errors fall into a few categories: runtime errors, + * errors that the JVM can issue on any statement, and errors that can be prevented by running + * some other CF checker. + */ + private static final SetOfTypes DEFAULT_IGNORED_EXCEPTIONS = + SetOfTypes.anyOfTheseNames( + ImmutableSet.of( + // Any method call has a CFG edge for Throwable/RuntimeException/Error + // to represent run-time misbehavior. Ignore it. + Throwable.class.getCanonicalName(), + Error.class.getCanonicalName(), + RuntimeException.class.getCanonicalName(), + // Use the Nullness Checker to prove this won't happen. + NullPointerException.class.getCanonicalName(), + // These errors can't be predicted statically, so ignore them and assume + // they won't happen. + ClassCircularityError.class.getCanonicalName(), + ClassFormatError.class.getCanonicalName(), + NoClassDefFoundError.class.getCanonicalName(), + OutOfMemoryError.class.getCanonicalName(), + // It's not our problem if the Java type system is wrong. + ClassCastException.class.getCanonicalName(), + // It's not our problem if the code is going to divide by zero. + ArithmeticException.class.getCanonicalName(), + // Use the Index Checker to prevent these errors. + ArrayIndexOutOfBoundsException.class.getCanonicalName(), + NegativeArraySizeException.class.getCanonicalName(), + // Most of the time, this exception is infeasible, as the charset used + // is guaranteed to be present by the Java spec (e.g., "UTF-8"). + // Eventually, this exclusion could be refined by looking at the charset + // being requested. + UnsupportedEncodingException.class.getCanonicalName())); - /** - * Command-line option for controlling which exceptions are ignored. - * - * @see #DEFAULT_IGNORED_EXCEPTIONS - * @see #getIgnoredExceptions() - */ - public static final String IGNORED_EXCEPTIONS = "resourceLeakIgnoredExceptions"; + /** + * Command-line option for controlling which exceptions are ignored. + * + * @see #DEFAULT_IGNORED_EXCEPTIONS + * @see #getIgnoredExceptions() + */ + public static final String IGNORED_EXCEPTIONS = "resourceLeakIgnoredExceptions"; - /** - * A pattern that matches one or more consecutive commas, optionally preceded and followed by - * whitespace. - */ - private static final Pattern COMMAS = - Pattern.compile("\\s*(?:" + Pattern.quote(",") + "\\s*)+"); + /** + * A pattern that matches one or more consecutive commas, optionally preceded and followed by + * whitespace. + */ + private static final Pattern COMMAS = Pattern.compile("\\s*(?:" + Pattern.quote(",") + "\\s*)+"); - /** - * A pattern that matches an exception specifier for the {@link #IGNORED_EXCEPTIONS} option: an - * optional "=" followed by a qualified name. The whole thing can be padded with whitespace. - */ - private static final Pattern EXCEPTION_SPECIFIER = - Pattern.compile( - "^\\s*" - + "(" - + Pattern.quote("=") - + "\\s*" - + ")?" - + "(\\w+(?:\\.\\w+)*)" - + "\\s*$"); + /** + * A pattern that matches an exception specifier for the {@link #IGNORED_EXCEPTIONS} option: an + * optional "=" followed by a qualified name. The whole thing can be padded with whitespace. + */ + private static final Pattern EXCEPTION_SPECIFIER = + Pattern.compile( + "^\\s*" + "(" + Pattern.quote("=") + "\\s*" + ")?" + "(\\w+(?:\\.\\w+)*)" + "\\s*$"); - /* NO-AFU - * Ordinarily, when the -Ainfer flag is used, whole-program inference is run for every checker - * and sub-checker. However, the Resource Leak Checker is different. The -Ainfer flag enables - * the RLC's own (non-WPI) inference mechanism ({@link MustCallInference}). To use WPI in - * addition to this mechanism for its sub-checkers, use the -AenableWpiForRlc flag, which is - * intended only for testing and experiments. - * - public static final String ENABLE_WPI_FOR_RLC = "enableWpiForRlc"; - */ + /* NO-AFU + * Ordinarily, when the -Ainfer flag is used, whole-program inference is run for every checker + * and sub-checker. However, the Resource Leak Checker is different. The -Ainfer flag enables + * the RLC's own (non-WPI) inference mechanism ({@link MustCallInference}). To use WPI in + * addition to this mechanism for its sub-checkers, use the -AenableWpiForRlc flag, which is + * intended only for testing and experiments. + * + public static final String ENABLE_WPI_FOR_RLC = "enableWpiForRlc"; + */ - /** - * The number of expressions with must-call obligations that were checked. Incremented only if - * the {@link #COUNT_MUST_CALL} command-line option was supplied. - */ - /*package-private*/ int numMustCall = 0; + /** + * The number of expressions with must-call obligations that were checked. Incremented only if the + * {@link #COUNT_MUST_CALL} command-line option was supplied. + */ + /*package-private*/ int numMustCall = 0; - /** - * The number of must-call-related errors issued. The count of verified must-call expressions is - * the difference between this and {@link #numMustCall}. - */ - private int numMustCallFailed = 0; + /** + * The number of must-call-related errors issued. The count of verified must-call expressions is + * the difference between this and {@link #numMustCall}. + */ + private int numMustCallFailed = 0; - /** - * The cached set of ignored exceptions parsed from {@link #IGNORED_EXCEPTIONS}. Caching this - * field prevents the checker from issuing duplicate warnings about missing exception types. - * - * @see #getIgnoredExceptions() - */ - private @MonotonicNonNull SetOfTypes ignoredExceptions = null; + /** + * The cached set of ignored exceptions parsed from {@link #IGNORED_EXCEPTIONS}. Caching this + * field prevents the checker from issuing duplicate warnings about missing exception types. + * + * @see #getIgnoredExceptions() + */ + private @MonotonicNonNull SetOfTypes ignoredExceptions = null; - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); - if (this.processingEnv.getOptions().containsKey(MustCallChecker.NO_CREATES_MUSTCALLFOR)) { - checkers.add(MustCallNoCreatesMustCallForChecker.class); - } else { - checkers.add(MustCallChecker.class); - } - - return checkers; + if (this.processingEnv.getOptions().containsKey(MustCallChecker.NO_CREATES_MUSTCALLFOR)) { + checkers.add(MustCallNoCreatesMustCallForChecker.class); + } else { + checkers.add(MustCallChecker.class); } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ResourceLeakVisitor(this); - } + return checkers; + } + + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ResourceLeakVisitor(this); + } - @Override - public void reportError( - @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { - if (messageKey.equals("required.method.not.called")) { - // This is safe because of the message key. - String qualifiedTypeName = (String) args[1]; - // Only count classes in the JDK, not user-defined classes. - if (MustCallConsistencyAnalyzer.isJdkClass(qualifiedTypeName)) { - numMustCallFailed++; - } - } - super.reportError(source, messageKey, args); + @Override + public void reportError( + @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { + if (messageKey.equals("required.method.not.called")) { + // This is safe because of the message key. + String qualifiedTypeName = (String) args[1]; + // Only count classes in the JDK, not user-defined classes. + if (MustCallConsistencyAnalyzer.isJdkClass(qualifiedTypeName)) { + numMustCallFailed++; + } } + super.reportError(source, messageKey, args); + } - @Override - public void typeProcessingOver() { - if (hasOption(COUNT_MUST_CALL)) { - message(Diagnostic.Kind.WARNING, "Found %d must call obligation(s).%n", numMustCall); - message( - Diagnostic.Kind.WARNING, - "Successfully verified %d must call obligation(s).%n", - numMustCall - numMustCallFailed); - } - super.typeProcessingOver(); + @Override + public void typeProcessingOver() { + if (hasOption(COUNT_MUST_CALL)) { + message(Diagnostic.Kind.WARNING, "Found %d must call obligation(s).%n", numMustCall); + message( + Diagnostic.Kind.WARNING, + "Successfully verified %d must call obligation(s).%n", + numMustCall - numMustCallFailed); } + super.typeProcessingOver(); + } - /** - * Get the set of exceptions that should be ignored. This set comes from the {@link - * #IGNORED_EXCEPTIONS} option if it was provided, or {@link #DEFAULT_IGNORED_EXCEPTIONS} if - * not. - * - * @return the set of exceptions to ignore - */ - public SetOfTypes getIgnoredExceptions() { - SetOfTypes result = ignoredExceptions; - if (result == null) { - String ignoredExceptionsOptionValue = getOption(IGNORED_EXCEPTIONS); - result = - ignoredExceptionsOptionValue == null - ? DEFAULT_IGNORED_EXCEPTIONS - : parseIgnoredExceptions(ignoredExceptionsOptionValue); - ignoredExceptions = result; - } - return result; + /** + * Get the set of exceptions that should be ignored. This set comes from the {@link + * #IGNORED_EXCEPTIONS} option if it was provided, or {@link #DEFAULT_IGNORED_EXCEPTIONS} if not. + * + * @return the set of exceptions to ignore + */ + public SetOfTypes getIgnoredExceptions() { + SetOfTypes result = ignoredExceptions; + if (result == null) { + String ignoredExceptionsOptionValue = getOption(IGNORED_EXCEPTIONS); + result = + ignoredExceptionsOptionValue == null + ? DEFAULT_IGNORED_EXCEPTIONS + : parseIgnoredExceptions(ignoredExceptionsOptionValue); + ignoredExceptions = result; } + return result; + } - /** - * Parse the argument given for the {@link #IGNORED_EXCEPTIONS} option. Warnings will be issued - * for any problems in the argument, for instance if any of the named exceptions cannot be - * found. - * - * @param ignoredExceptionsOptionValue the value given for {@link #IGNORED_EXCEPTIONS} - * @return the set of ignored exceptions - */ - protected SetOfTypes parseIgnoredExceptions(String ignoredExceptionsOptionValue) { - String[] exceptions = COMMAS.split(ignoredExceptionsOptionValue); - List sets = new ArrayList<>(); - for (String e : exceptions) { - SetOfTypes set = parseExceptionSpecifier(e, ignoredExceptionsOptionValue); - if (set != null) { - sets.add(set); - } - } - return SetOfTypes.union(sets.toArray(new SetOfTypes[0])); + /** + * Parse the argument given for the {@link #IGNORED_EXCEPTIONS} option. Warnings will be issued + * for any problems in the argument, for instance if any of the named exceptions cannot be found. + * + * @param ignoredExceptionsOptionValue the value given for {@link #IGNORED_EXCEPTIONS} + * @return the set of ignored exceptions + */ + protected SetOfTypes parseIgnoredExceptions(String ignoredExceptionsOptionValue) { + String[] exceptions = COMMAS.split(ignoredExceptionsOptionValue); + List sets = new ArrayList<>(); + for (String e : exceptions) { + SetOfTypes set = parseExceptionSpecifier(e, ignoredExceptionsOptionValue); + if (set != null) { + sets.add(set); + } } + return SetOfTypes.union(sets.toArray(new SetOfTypes[0])); + } - /** - * Parse a single exception specifier from the {@link #IGNORED_EXCEPTIONS} option and issue - * warnings if it does not parse. See {@link #EXCEPTION_SPECIFIER} for a description of the - * syntax. - * - * @param exceptionSpecifier the exception specifier to parse - * @param ignoredExceptionsOptionValue the whole value of the {@link #IGNORED_EXCEPTIONS} - * option; only used for error reporting - * @return the parsed set of types, or null if the value does not parse - */ - @SuppressWarnings({ - // user input might not be a legal @CanonicalName, but it should be safe to pass to - // `SetOfTypes.anyOfTheseNames` - "signature:argument", - }) - protected @Nullable SetOfTypes parseExceptionSpecifier( - String exceptionSpecifier, String ignoredExceptionsOptionValue) { - Matcher m = EXCEPTION_SPECIFIER.matcher(exceptionSpecifier); - if (m.matches()) { - @Nullable String equalsSign = m.group(1); - String qualifiedName = m.group(2); + /** + * Parse a single exception specifier from the {@link #IGNORED_EXCEPTIONS} option and issue + * warnings if it does not parse. See {@link #EXCEPTION_SPECIFIER} for a description of the + * syntax. + * + * @param exceptionSpecifier the exception specifier to parse + * @param ignoredExceptionsOptionValue the whole value of the {@link #IGNORED_EXCEPTIONS} option; + * only used for error reporting + * @return the parsed set of types, or null if the value does not parse + */ + @SuppressWarnings({ + // user input might not be a legal @CanonicalName, but it should be safe to pass to + // `SetOfTypes.anyOfTheseNames` + "signature:argument", + }) + protected @Nullable SetOfTypes parseExceptionSpecifier( + String exceptionSpecifier, String ignoredExceptionsOptionValue) { + Matcher m = EXCEPTION_SPECIFIER.matcher(exceptionSpecifier); + if (m.matches()) { + @Nullable String equalsSign = m.group(1); + String qualifiedName = m.group(2); - if (qualifiedName.equalsIgnoreCase("default")) { - return DEFAULT_IGNORED_EXCEPTIONS; - } - TypeMirror type = checkCanonicalName(qualifiedName); - if (type == null) { - // There is a chance that the user named a real type, but the class is not - // accessible for some reason. We'll issue a warning (in case this was a typo) but - // add the type as ignored anyway (in case it's just an inaccessible type). - // - // Note that if the user asked to ignore subtypes of this exception, this code won't - // do it because we can't know what those subtypes are. We have to treat this as if - // it were "=qualifiedName" even if no equals sign was provided. - message( - Diagnostic.Kind.WARNING, - "The exception '%s' appears in the -A%s=%s option, but it does not seem to exist", - exceptionSpecifier, - IGNORED_EXCEPTIONS, - ignoredExceptionsOptionValue); - return SetOfTypes.anyOfTheseNames(ImmutableSet.of(qualifiedName)); - } else { - return equalsSign == null - ? SetOfTypes.allSubtypes(type) - : SetOfTypes.singleton(type); - } - } else if (!exceptionSpecifier.trim().isEmpty()) { - message( - Diagnostic.Kind.WARNING, - "The string '%s' appears in the -A%s=%s option, but it is not a legal exception specifier", - exceptionSpecifier, - IGNORED_EXCEPTIONS, - ignoredExceptionsOptionValue); - } - return null; + if (qualifiedName.equalsIgnoreCase("default")) { + return DEFAULT_IGNORED_EXCEPTIONS; + } + TypeMirror type = checkCanonicalName(qualifiedName); + if (type == null) { + // There is a chance that the user named a real type, but the class is not + // accessible for some reason. We'll issue a warning (in case this was a typo) but + // add the type as ignored anyway (in case it's just an inaccessible type). + // + // Note that if the user asked to ignore subtypes of this exception, this code won't + // do it because we can't know what those subtypes are. We have to treat this as if + // it were "=qualifiedName" even if no equals sign was provided. + message( + Diagnostic.Kind.WARNING, + "The exception '%s' appears in the -A%s=%s option, but it does not seem to exist", + exceptionSpecifier, + IGNORED_EXCEPTIONS, + ignoredExceptionsOptionValue); + return SetOfTypes.anyOfTheseNames(ImmutableSet.of(qualifiedName)); + } else { + return equalsSign == null ? SetOfTypes.allSubtypes(type) : SetOfTypes.singleton(type); + } + } else if (!exceptionSpecifier.trim().isEmpty()) { + message( + Diagnostic.Kind.WARNING, + "The string '%s' appears in the -A%s=%s option, but it is not a legal exception specifier", + exceptionSpecifier, + IGNORED_EXCEPTIONS, + ignoredExceptionsOptionValue); } + return null; + } - /** - * Check if the given String refers to an actual type. - * - * @param s any string - * @return the referenced type, or null if it does not exist - */ - @SuppressWarnings({ - "signature:argument", // `s` is not a qualified name, but we pass it to getTypeElement - // anyway - }) - protected @Nullable TypeMirror checkCanonicalName(String s) { - TypeElement elem = getProcessingEnvironment().getElementUtils().getTypeElement(s); - if (elem == null) { - return null; - } - return types.getDeclaredType(elem); + /** + * Check if the given String refers to an actual type. + * + * @param s any string + * @return the referenced type, or null if it does not exist + */ + @SuppressWarnings({ + "signature:argument", // `s` is not a qualified name, but we pass it to getTypeElement + // anyway + }) + protected @Nullable TypeMirror checkCanonicalName(String s) { + TypeElement elem = getProcessingEnvironment().getElementUtils().getTypeElement(s); + if (elem == null) { + return null; } + return types.getDeclaredType(elem); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java index 91672e523f4..4817965da02 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java @@ -1,5 +1,7 @@ package org.checkerframework.checker.resourceleak; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.calledmethods.CalledMethodsTransfer; import org.checkerframework.checker.mustcall.CreatesMustCallForToJavaExpression; import org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory; @@ -17,152 +19,147 @@ import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.javacutil.TypesUtils; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; - /** The transfer function for the resource-leak extension to the called-methods type system. */ public class ResourceLeakTransfer extends CalledMethodsTransfer { - /** - * Shadowed because we must dispatch to the Resource Leak Checker's version of - * getTypefactoryOfSubchecker to get the correct MustCallAnnotatedTypeFactory. - */ - private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; - - /** - * Create a new resource leak transfer function. - * - * @param analysis the analysis. Its type factory must be a {@link - * ResourceLeakAnnotatedTypeFactory}. - */ - public ResourceLeakTransfer(ResourceLeakAnalysis analysis) { - super(analysis); - this.rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) analysis.getTypeFactory(); + /** + * Shadowed because we must dispatch to the Resource Leak Checker's version of + * getTypefactoryOfSubchecker to get the correct MustCallAnnotatedTypeFactory. + */ + private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; + + /** + * Create a new resource leak transfer function. + * + * @param analysis the analysis. Its type factory must be a {@link + * ResourceLeakAnnotatedTypeFactory}. + */ + public ResourceLeakTransfer(ResourceLeakAnalysis analysis) { + super(analysis); + this.rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) analysis.getTypeFactory(); + } + + @Override + public TransferResult visitTernaryExpression( + TernaryExpressionNode node, TransferInput input) { + TransferResult result = + super.visitTernaryExpression(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + rlTypeFactory.addTempVar(node.getTernaryExpressionVar(), node.getTree()); } - - @Override - public TransferResult visitTernaryExpression( - TernaryExpressionNode node, TransferInput input) { - TransferResult result = - super.visitTernaryExpression(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - rlTypeFactory.addTempVar(node.getTernaryExpressionVar(), node.getTree()); - } - return result; + return result; + } + + @Override + public TransferResult visitSwitchExpressionNode( + SwitchExpressionNode node, TransferInput input) { + TransferResult result = + super.visitSwitchExpressionNode(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + rlTypeFactory.addTempVar(node.getSwitchExpressionVar(), node.getTree()); } - - @Override - public TransferResult visitSwitchExpressionNode( - SwitchExpressionNode node, TransferInput input) { - TransferResult result = - super.visitSwitchExpressionNode(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - rlTypeFactory.addTempVar(node.getSwitchExpressionVar(), node.getTree()); - } - return result; + return result; + } + + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput input) { + + TransferResult result = + super.visitMethodInvocation(node, input); + + handleCreatesMustCallFor(node, result); + updateStoreWithTempVar(result, node); + + // If there is a temporary variable for the receiver, update its type. + Node receiver = node.getTarget().getReceiver(); + MustCallAnnotatedTypeFactory mcAtf = + rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + Node accumulationTarget = mcAtf.getTempVar(receiver); + if (accumulationTarget != null) { + String methodName = node.getTarget().getMethod().getSimpleName().toString(); + methodName = rlTypeFactory.adjustMethodNameUsingValueChecker(methodName, node.getTree()); + accumulate(accumulationTarget, result, methodName); } - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput input) { - - TransferResult result = - super.visitMethodInvocation(node, input); - - handleCreatesMustCallFor(node, result); - updateStoreWithTempVar(result, node); - - // If there is a temporary variable for the receiver, update its type. - Node receiver = node.getTarget().getReceiver(); - MustCallAnnotatedTypeFactory mcAtf = - rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - Node accumulationTarget = mcAtf.getTempVar(receiver); - if (accumulationTarget != null) { - String methodName = node.getTarget().getMethod().getSimpleName().toString(); - methodName = - rlTypeFactory.adjustMethodNameUsingValueChecker(methodName, node.getTree()); - accumulate(accumulationTarget, result, methodName); - } - - return result; + return result; + } + + /** + * Clears the called-methods store of all information about the target if an @CreatesMustCallFor + * method is invoked and the type factory can create obligations. Otherwise, does nothing. + * + * @param n a method invocation + * @param result the transfer result whose stores should be cleared of information + */ + private void handleCreatesMustCallFor( + MethodInvocationNode n, TransferResult result) { + if (!rlTypeFactory.canCreateObligations()) { + return; } - /** - * Clears the called-methods store of all information about the target if an @CreatesMustCallFor - * method is invoked and the type factory can create obligations. Otherwise, does nothing. - * - * @param n a method invocation - * @param result the transfer result whose stores should be cleared of information - */ - private void handleCreatesMustCallFor( - MethodInvocationNode n, TransferResult result) { - if (!rlTypeFactory.canCreateObligations()) { - return; - } - - List targetExprs = - CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( - n, rlTypeFactory, rlTypeFactory); - AnnotationMirror defaultType = rlTypeFactory.top; - for (JavaExpression targetExpr : targetExprs) { - AccumulationValue defaultTypeValue = - analysis.createSingleAnnotationValue(defaultType, targetExpr.getType()); - if (result.containsTwoStores()) { - result.getThenStore().replaceValue(targetExpr, defaultTypeValue); - result.getElseStore().replaceValue(targetExpr, defaultTypeValue); - } else { - result.getRegularStore().replaceValue(targetExpr, defaultTypeValue); - } - } - } - - @Override - public TransferResult visitObjectCreation( - ObjectCreationNode node, TransferInput input) { - TransferResult result = - super.visitObjectCreation(node, input); - updateStoreWithTempVar(result, node); - return result; + List targetExprs = + CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( + n, rlTypeFactory, rlTypeFactory); + AnnotationMirror defaultType = rlTypeFactory.top; + for (JavaExpression targetExpr : targetExprs) { + AccumulationValue defaultTypeValue = + analysis.createSingleAnnotationValue(defaultType, targetExpr.getType()); + if (result.containsTwoStores()) { + result.getThenStore().replaceValue(targetExpr, defaultTypeValue); + result.getElseStore().replaceValue(targetExpr, defaultTypeValue); + } else { + result.getRegularStore().replaceValue(targetExpr, defaultTypeValue); + } } - - /** - * This method either creates or looks up the temp var t for node, and then updates the store to - * give t the same type as node. Temporary variables are supported for expressions throughout - * this checker (and the Must Call Checker) to enable refinement of their types. See the - * documentation of {@link MustCallConsistencyAnalyzer} for more details. - * - * @param node the node to be assigned to a temporary variable - * @param result the transfer result containing the store to be modified - */ - public void updateStoreWithTempVar( - TransferResult result, Node node) { - // Must-call obligations on primitives are not supported. - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - MustCallAnnotatedTypeFactory mcAtf = - rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - LocalVariableNode temp = mcAtf.getTempVar(node); - if (temp != null) { - rlTypeFactory.addTempVar(temp, node.getTree()); - JavaExpression localExp = JavaExpression.fromNode(temp); - AnnotationMirror anm = - rlTypeFactory - .getAnnotatedType(node.getTree()) - .getAnnotationInHierarchy(rlTypeFactory.top); - if (anm == null) { - anm = rlTypeFactory.top; - } - if (result.containsTwoStores()) { - result.getThenStore().insertValue(localExp, anm); - result.getElseStore().insertValue(localExp, anm); - } else { - result.getRegularStore().insertValue(localExp, anm); - } - } + } + + @Override + public TransferResult visitObjectCreation( + ObjectCreationNode node, TransferInput input) { + TransferResult result = + super.visitObjectCreation(node, input); + updateStoreWithTempVar(result, node); + return result; + } + + /** + * This method either creates or looks up the temp var t for node, and then updates the store to + * give t the same type as node. Temporary variables are supported for expressions throughout this + * checker (and the Must Call Checker) to enable refinement of their types. See the documentation + * of {@link MustCallConsistencyAnalyzer} for more details. + * + * @param node the node to be assigned to a temporary variable + * @param result the transfer result containing the store to be modified + */ + public void updateStoreWithTempVar( + TransferResult result, Node node) { + // Must-call obligations on primitives are not supported. + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + MustCallAnnotatedTypeFactory mcAtf = + rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + LocalVariableNode temp = mcAtf.getTempVar(node); + if (temp != null) { + rlTypeFactory.addTempVar(temp, node.getTree()); + JavaExpression localExp = JavaExpression.fromNode(temp); + AnnotationMirror anm = + rlTypeFactory + .getAnnotatedType(node.getTree()) + .getAnnotationInHierarchy(rlTypeFactory.top); + if (anm == null) { + anm = rlTypeFactory.top; + } + if (result.containsTwoStores()) { + result.getThenStore().insertValue(localExp, anm); + result.getElseStore().insertValue(localExp, anm); + } else { + result.getRegularStore().insertValue(localExp, anm); } + } } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java index 8a2211d4455..5decce0ce9e 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java @@ -2,7 +2,18 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; - +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.calledmethods.CalledMethodsVisitor; import org.checkerframework.checker.calledmethods.EnsuresCalledMethodOnExceptionContract; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; @@ -25,20 +36,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.VariableElement; - /** * The visitor for the Resource Leak Checker. Responsible for checking that the rules for {@link * Owning} fields are satisfied, and for checking that {@link CreatesMustCallFor} overrides are @@ -46,522 +43,510 @@ */ public class ResourceLeakVisitor extends CalledMethodsVisitor { - /** True if errors related to static owning fields should be suppressed. */ - private final boolean permitStaticOwning; - - /** - * Because CalledMethodsVisitor doesn't have a type parameter, we need a reference to the type - * factory that has this static type to access the features that - * ResourceLeakAnnotatedTypeFactory implements but CalledMethodsAnnotatedTypeFactory does not. - */ - private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; - - /** True if -AnoLightweightOwnership was supplied on the command line. */ - private final boolean noLightweightOwnership; - - /* NO-AFU - * True if -AenableWpiForRlc was passed on the command line. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - private final boolean enableWpiForRlc; - */ - - /** - * Create the visitor. - * - * @param checker the type-checker associated with this visitor - */ - public ResourceLeakVisitor(BaseTypeChecker checker) { - super(checker); - rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) atypeFactory; - permitStaticOwning = checker.hasOption("permitStaticOwning"); - noLightweightOwnership = checker.hasOption("noLightweightOwnership"); - // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + /** True if errors related to static owning fields should be suppressed. */ + private final boolean permitStaticOwning; + + /** + * Because CalledMethodsVisitor doesn't have a type parameter, we need a reference to the type + * factory that has this static type to access the features that ResourceLeakAnnotatedTypeFactory + * implements but CalledMethodsAnnotatedTypeFactory does not. + */ + private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; + + /** True if -AnoLightweightOwnership was supplied on the command line. */ + private final boolean noLightweightOwnership; + + /* NO-AFU + * True if -AenableWpiForRlc was passed on the command line. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + private final boolean enableWpiForRlc; + */ + + /** + * Create the visitor. + * + * @param checker the type-checker associated with this visitor + */ + public ResourceLeakVisitor(BaseTypeChecker checker) { + super(checker); + rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) atypeFactory; + permitStaticOwning = checker.hasOption("permitStaticOwning"); + noLightweightOwnership = checker.hasOption("noLightweightOwnership"); + // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + } + + @Override + protected ResourceLeakAnnotatedTypeFactory createTypeFactory() { + return new ResourceLeakAnnotatedTypeFactory(checker); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); + MustCallAnnotatedTypeFactory mcAtf = + rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + List cmcfValues = getCreatesMustCallForValues(elt, mcAtf, rlTypeFactory); + if (!cmcfValues.isEmpty()) { + checkCreatesMustCallForOverrides(tree, elt, mcAtf, cmcfValues); + checkCreatesMustCallForTargetsHaveNonEmptyMustCall(tree, mcAtf); } - - @Override - protected ResourceLeakAnnotatedTypeFactory createTypeFactory() { - return new ResourceLeakAnnotatedTypeFactory(checker); + checkOwningOverrides(tree, elt, mcAtf); + return super.visitMethod(tree, p); + } + + /** + * checks that any created must-call obligation has a declared type with a non-empty + * {@code @MustCall} obligation + * + * @param tree the method + * @param mcAtf the type factory + */ + private void checkCreatesMustCallForTargetsHaveNonEmptyMustCall( + MethodTree tree, MustCallAnnotatedTypeFactory mcAtf) { + // Get all the JavaExpressions for all CreatesMustCallFor annotations + List createsMustCallExprs = + CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtMethodDeclaration( + tree, mcAtf, mcAtf); + for (JavaExpression targetExpr : createsMustCallExprs) { + AnnotationMirror mustCallAnno = + mcAtf + .getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) + .getAnnotationInHierarchy(mcAtf.TOP); + if (rlTypeFactory.getMustCallValues(mustCallAnno).isEmpty()) { + checker.reportError( + tree, + "creates.mustcall.for.invalid.target", + targetExpr.toString(), + targetExpr.getType().toString()); + } } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); - MustCallAnnotatedTypeFactory mcAtf = - rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - List cmcfValues = getCreatesMustCallForValues(elt, mcAtf, rlTypeFactory); - if (!cmcfValues.isEmpty()) { - checkCreatesMustCallForOverrides(tree, elt, mcAtf, cmcfValues); - checkCreatesMustCallForTargetsHaveNonEmptyMustCall(tree, mcAtf); - } - checkOwningOverrides(tree, elt, mcAtf); - return super.visitMethod(tree, p); + } + + /** + * Check that an overriding method does not reduce the number of created must-call obligations + * + * @param tree overriding method + * @param elt element for overriding method + * @param mcAtf the type factory + * @param cmcfValues must call values created by overriding method + */ + private void checkCreatesMustCallForOverrides( + MethodTree tree, + ExecutableElement elt, + MustCallAnnotatedTypeFactory mcAtf, + List cmcfValues) { + // If this method overrides another method, it must create at least as many + // obligations. Without this check, dynamic dispatch might allow e.g. a field to be + // overwritten by a CMCF method, but the CMCF effect wouldn't occur. + for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(elt, this.types)) { + List overriddenCmcfValues = + getCreatesMustCallForValues(overridden, mcAtf, rlTypeFactory); + if (!overriddenCmcfValues.containsAll(cmcfValues)) { + String foundCmcfValueString = String.join(", ", cmcfValues); + String neededCmcfValueString = String.join(", ", overriddenCmcfValues); + String actualClassname = ElementUtils.getEnclosingClassName(elt); + String overriddenClassname = ElementUtils.getEnclosingClassName(overridden); + checker.reportError( + tree, + "creates.mustcall.for.override.invalid", + actualClassname + "#" + elt, + overriddenClassname + "#" + overridden, + foundCmcfValueString, + neededCmcfValueString); + } } - - /** - * checks that any created must-call obligation has a declared type with a non-empty - * {@code @MustCall} obligation - * - * @param tree the method - * @param mcAtf the type factory - */ - private void checkCreatesMustCallForTargetsHaveNonEmptyMustCall( - MethodTree tree, MustCallAnnotatedTypeFactory mcAtf) { - // Get all the JavaExpressions for all CreatesMustCallFor annotations - List createsMustCallExprs = - CreatesMustCallForToJavaExpression - .getCreatesMustCallForExpressionsAtMethodDeclaration(tree, mcAtf, mcAtf); - for (JavaExpression targetExpr : createsMustCallExprs) { - AnnotationMirror mustCallAnno = - mcAtf.getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) - .getAnnotationInHierarchy(mcAtf.TOP); - if (rlTypeFactory.getMustCallValues(mustCallAnno).isEmpty()) { - checker.reportError( - tree, - "creates.mustcall.for.invalid.target", - targetExpr.toString(), - targetExpr.getType().toString()); - } + } + + /** + * Checks that overrides respect behavioral subtyping for @Owning and @NotOwning annotations. In + * particular, checks that 1) if an overridden method has an @Owning parameter, then that + * parameter is @Owning in the overrider, and 2) if an overridden method has an @NotOwning return, + * then the overrider also has an @NotOwning return. + * + * @param tree overriding method, for error reporting + * @param overrider element for overriding method + * @param mcAtf the type factory + */ + private void checkOwningOverrides( + MethodTree tree, ExecutableElement overrider, MustCallAnnotatedTypeFactory mcAtf) { + for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(overrider, this.types)) { + // Check for @Owning parameters. Must use an explicitly-indexed for loop so that the + // same parameter index can be accessed in the overrider's parameter list, which is the + // same length. + for (int i = 0; i < overridden.getParameters().size(); i++) { + if (mcAtf.getDeclAnnotation(overridden.getParameters().get(i), Owning.class) != null) { + if (mcAtf.getDeclAnnotation(overrider.getParameters().get(i), Owning.class) == null) { + checker.reportError( + tree, + "owning.override.param", + overrider.getParameters().get(i).getSimpleName().toString(), + overrider.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overrider), + overridden.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overridden)); + } } + } + // Check for @NotOwning returns. + if (mcAtf.getDeclAnnotation(overridden, NotOwning.class) != null + && mcAtf.getDeclAnnotation(overrider, NotOwning.class) == null) { + checker.reportError( + tree, + "owning.override.return", + overrider.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overrider), + overridden.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overridden)); + } } - - /** - * Check that an overriding method does not reduce the number of created must-call obligations - * - * @param tree overriding method - * @param elt element for overriding method - * @param mcAtf the type factory - * @param cmcfValues must call values created by overriding method - */ - private void checkCreatesMustCallForOverrides( - MethodTree tree, - ExecutableElement elt, - MustCallAnnotatedTypeFactory mcAtf, - List cmcfValues) { - // If this method overrides another method, it must create at least as many - // obligations. Without this check, dynamic dispatch might allow e.g. a field to be - // overwritten by a CMCF method, but the CMCF effect wouldn't occur. - for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(elt, this.types)) { - List overriddenCmcfValues = - getCreatesMustCallForValues(overridden, mcAtf, rlTypeFactory); - if (!overriddenCmcfValues.containsAll(cmcfValues)) { - String foundCmcfValueString = String.join(", ", cmcfValues); - String neededCmcfValueString = String.join(", ", overriddenCmcfValues); - String actualClassname = ElementUtils.getEnclosingClassName(elt); - String overriddenClassname = ElementUtils.getEnclosingClassName(overridden); - checker.reportError( - tree, - "creates.mustcall.for.override.invalid", - actualClassname + "#" + elt, - overriddenClassname + "#" + overridden, - foundCmcfValueString, - neededCmcfValueString); - } - } + } + + /* NO-AFU + @Override + protected boolean shouldPerformContractInference() { + return atypeFactory.getWholeProgramInference() != null && isWpiEnabledForRLC(); + } + */ + + /** + * Returns the {@link CreatesMustCallFor#value} element/argument of the given @CreatesMustCallFor + * annotation, or "this" if there is none. + * + *

Does not vipewpoint-adaptation. + * + * @param createsMustCallFor an @CreatesMustCallFor annotation + * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element + * @return the string value + */ + private static String getCreatesMustCallForValue( + AnnotationMirror createsMustCallFor, MustCallAnnotatedTypeFactory mcAtf) { + return AnnotationUtils.getElementValue( + createsMustCallFor, mcAtf.getCreatesMustCallForValueElement(), String.class, "this"); + } + + /** + * Returns all the {@link CreatesMustCallFor#value} elements/arguments of all @CreatesMustCallFor + * annotations on the given element. + * + *

Does no viewpoint-adaptation, unlike {@link + * CreatesMustCallForToJavaExpression#getCreatesMustCallForExpressionsAtInvocation} which does. + * + * @param elt an executable element + * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element + * @param atypeFactory a ResourceLeakAnnotatedTypeFactory + * @return the literal strings present in the @CreatesMustCallFor annotation(s) of that element, + * substituting the default "this" for empty annotations. This method returns the empty list + * iff there are no @CreatesMustCallFor annotations on elt. The returned list is always + * modifiable if it is non-empty. + */ + /*package-private*/ static List getCreatesMustCallForValues( + ExecutableElement elt, + MustCallAnnotatedTypeFactory mcAtf, + ResourceLeakAnnotatedTypeFactory atypeFactory) { + AnnotationMirror createsMustCallForList = + atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.List.class); + List result = new ArrayList<>(4); + if (createsMustCallForList != null) { + List createsMustCallFors = + AnnotationUtils.getElementValueArray( + createsMustCallForList, + mcAtf.getCreatesMustCallForListValueElement(), + AnnotationMirror.class); + for (AnnotationMirror cmcf : createsMustCallFors) { + result.add(getCreatesMustCallForValue(cmcf, mcAtf)); + } } - - /** - * Checks that overrides respect behavioral subtyping for @Owning and @NotOwning annotations. In - * particular, checks that 1) if an overridden method has an @Owning parameter, then that - * parameter is @Owning in the overrider, and 2) if an overridden method has an @NotOwning - * return, then the overrider also has an @NotOwning return. - * - * @param tree overriding method, for error reporting - * @param overrider element for overriding method - * @param mcAtf the type factory - */ - private void checkOwningOverrides( - MethodTree tree, ExecutableElement overrider, MustCallAnnotatedTypeFactory mcAtf) { - for (ExecutableElement overridden : - ElementUtils.getOverriddenMethods(overrider, this.types)) { - // Check for @Owning parameters. Must use an explicitly-indexed for loop so that the - // same parameter index can be accessed in the overrider's parameter list, which is the - // same length. - for (int i = 0; i < overridden.getParameters().size(); i++) { - if (mcAtf.getDeclAnnotation(overridden.getParameters().get(i), Owning.class) - != null) { - if (mcAtf.getDeclAnnotation(overrider.getParameters().get(i), Owning.class) - == null) { - checker.reportError( - tree, - "owning.override.param", - overrider.getParameters().get(i).getSimpleName().toString(), - overrider.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overrider), - overridden.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overridden)); - } - } - } - // Check for @NotOwning returns. - if (mcAtf.getDeclAnnotation(overridden, NotOwning.class) != null - && mcAtf.getDeclAnnotation(overrider, NotOwning.class) == null) { - checker.reportError( - tree, - "owning.override.return", - overrider.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overrider), - overridden.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overridden)); - } - } + AnnotationMirror createsMustCallFor = + atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.class); + if (createsMustCallFor != null) { + result.add(getCreatesMustCallForValue(createsMustCallFor, mcAtf)); } - - /* NO-AFU - @Override - protected boolean shouldPerformContractInference() { - return atypeFactory.getWholeProgramInference() != null && isWpiEnabledForRLC(); + return result; + } + + /** + * Get all {@link EnsuresCalledMethods} annotations on an element. + * + * @param elt an executable element that might have {@link EnsuresCalledMethods} annotations + * @param atypeFactory a ResourceLeakAnnotatedTypeFactory + * @return a set of {@link EnsuresCalledMethods} annotations + */ + @Pure + private static AnnotationMirrorSet getEnsuresCalledMethodsAnnotations( + ExecutableElement elt, ResourceLeakAnnotatedTypeFactory atypeFactory) { + AnnotationMirror ensuresCalledMethodsAnnos = + atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.List.class); + AnnotationMirrorSet result = new AnnotationMirrorSet(); + if (ensuresCalledMethodsAnnos != null) { + result.addAll( + AnnotationUtils.getElementValueArray( + ensuresCalledMethodsAnnos, + atypeFactory.getEnsuresCalledMethodsListValueElement(), + AnnotationMirror.class)); } - */ - - /** - * Returns the {@link CreatesMustCallFor#value} element/argument of the - * given @CreatesMustCallFor annotation, or "this" if there is none. - * - *

Does not vipewpoint-adaptation. - * - * @param createsMustCallFor an @CreatesMustCallFor annotation - * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element - * @return the string value - */ - private static String getCreatesMustCallForValue( - AnnotationMirror createsMustCallFor, MustCallAnnotatedTypeFactory mcAtf) { - return AnnotationUtils.getElementValue( - createsMustCallFor, - mcAtf.getCreatesMustCallForValueElement(), - String.class, - "this"); + AnnotationMirror ensuresCalledMethod = + atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.class); + if (ensuresCalledMethod != null) { + result.add(ensuresCalledMethod); } + return result; + } - /** - * Returns all the {@link CreatesMustCallFor#value} elements/arguments of - * all @CreatesMustCallFor annotations on the given element. - * - *

Does no viewpoint-adaptation, unlike {@link - * CreatesMustCallForToJavaExpression#getCreatesMustCallForExpressionsAtInvocation} which does. - * - * @param elt an executable element - * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element - * @param atypeFactory a ResourceLeakAnnotatedTypeFactory - * @return the literal strings present in the @CreatesMustCallFor annotation(s) of that element, - * substituting the default "this" for empty annotations. This method returns the empty list - * iff there are no @CreatesMustCallFor annotations on elt. The returned list is always - * modifiable if it is non-empty. - */ - /*package-private*/ static List getCreatesMustCallForValues( - ExecutableElement elt, - MustCallAnnotatedTypeFactory mcAtf, - ResourceLeakAnnotatedTypeFactory atypeFactory) { - AnnotationMirror createsMustCallForList = - atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.List.class); - List result = new ArrayList<>(4); - if (createsMustCallForList != null) { - List createsMustCallFors = - AnnotationUtils.getElementValueArray( - createsMustCallForList, - mcAtf.getCreatesMustCallForListValueElement(), - AnnotationMirror.class); - for (AnnotationMirror cmcf : createsMustCallFors) { - result.add(getCreatesMustCallForValue(cmcf, mcAtf)); - } - } - AnnotationMirror createsMustCallFor = - atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.class); - if (createsMustCallFor != null) { - result.add(getCreatesMustCallForValue(createsMustCallFor, mcAtf)); - } - return result; - } + @Override + public Void visitVariable(VariableTree tree, Void p) { + VariableElement varElement = TreeUtils.elementFromDeclaration(tree); - /** - * Get all {@link EnsuresCalledMethods} annotations on an element. - * - * @param elt an executable element that might have {@link EnsuresCalledMethods} annotations - * @param atypeFactory a ResourceLeakAnnotatedTypeFactory - * @return a set of {@link EnsuresCalledMethods} annotations - */ - @Pure - private static AnnotationMirrorSet getEnsuresCalledMethodsAnnotations( - ExecutableElement elt, ResourceLeakAnnotatedTypeFactory atypeFactory) { - AnnotationMirror ensuresCalledMethodsAnnos = - atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.List.class); - AnnotationMirrorSet result = new AnnotationMirrorSet(); - if (ensuresCalledMethodsAnnos != null) { - result.addAll( - AnnotationUtils.getElementValueArray( - ensuresCalledMethodsAnnos, - atypeFactory.getEnsuresCalledMethodsListValueElement(), - AnnotationMirror.class)); - } - AnnotationMirror ensuresCalledMethod = - atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.class); - if (ensuresCalledMethod != null) { - result.add(ensuresCalledMethod); - } - return result; + if (varElement.getKind().isField() + && !noLightweightOwnership + && rlTypeFactory.getDeclAnnotation(varElement, Owning.class) != null) { + checkOwningField(varElement); } - @Override - public Void visitVariable(VariableTree tree, Void p) { - VariableElement varElement = TreeUtils.elementFromDeclaration(tree); + return super.visitVariable(tree, p); + } - if (varElement.getKind().isField() - && !noLightweightOwnership - && rlTypeFactory.getDeclAnnotation(varElement, Owning.class) != null) { - checkOwningField(varElement); - } + /** + * An obligation that must be satisfied by a destructor. Helper type for {@link + * #checkOwningField(VariableElement)}. + */ + // TODO: In the future, this class should be a record. + private static final class DestructorObligation { + /** The method that must be called on the field. */ + final String mustCallMethod; - return super.visitVariable(tree, p); - } + /** When the method must be called. */ + final MustCallConsistencyAnalyzer.MethodExitKind exitKind; /** - * An obligation that must be satisfied by a destructor. Helper type for {@link - * #checkOwningField(VariableElement)}. + * Create a new obligation. + * + * @param mustCallMethod the method that must be called + * @param exitKind when the method must be called */ - // TODO: In the future, this class should be a record. - private static final class DestructorObligation { - /** The method that must be called on the field. */ - final String mustCallMethod; - - /** When the method must be called. */ - final MustCallConsistencyAnalyzer.MethodExitKind exitKind; - - /** - * Create a new obligation. - * - * @param mustCallMethod the method that must be called - * @param exitKind when the method must be called - */ - public DestructorObligation( - String mustCallMethod, MustCallConsistencyAnalyzer.MethodExitKind exitKind) { - this.mustCallMethod = mustCallMethod; - this.exitKind = exitKind; - } + public DestructorObligation( + String mustCallMethod, MustCallConsistencyAnalyzer.MethodExitKind exitKind) { + this.mustCallMethod = mustCallMethod; + this.exitKind = exitKind; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DestructorObligation that = (DestructorObligation) o; - return mustCallMethod.equals(that.mustCallMethod) && exitKind == that.exitKind; - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DestructorObligation that = (DestructorObligation) o; + return mustCallMethod.equals(that.mustCallMethod) && exitKind == that.exitKind; + } - @Override - public int hashCode() { - return Objects.hash(mustCallMethod, exitKind); - } + @Override + public int hashCode() { + return Objects.hash(mustCallMethod, exitKind); + } + } + + /** + * Checks validity of a field {@code field} with an {@code @}{@link Owning} annotation. Say the + * type of {@code field} is {@code @MustCall("m"}}. This method checks that the enclosing class of + * {@code field} has a type {@code @MustCall("m2")} for some method {@code m2}, and that {@code + * m2} has an annotation {@code @EnsuresCalledMethods(value = "this.field", methods = "m")}, + * guaranteeing that the {@code @MustCall} obligation of the field will be satisfied. + * + * @param field the declaration of the field to check + */ + private void checkOwningField(VariableElement field) { + + if (checker.shouldSkipUses(field)) { + return; } - /** - * Checks validity of a field {@code field} with an {@code @}{@link Owning} annotation. Say the - * type of {@code field} is {@code @MustCall("m"}}. This method checks that the enclosing class - * of {@code field} has a type {@code @MustCall("m2")} for some method {@code m2}, and that - * {@code m2} has an annotation {@code @EnsuresCalledMethods(value = "this.field", methods = - * "m")}, guaranteeing that the {@code @MustCall} obligation of the field will be satisfied. - * - * @param field the declaration of the field to check - */ - private void checkOwningField(VariableElement field) { + Set modifiers = field.getModifiers(); + if (modifiers.contains(Modifier.STATIC)) { + if (permitStaticOwning) { + return; + } + if (modifiers.contains(Modifier.FINAL)) { + return; + } + } - if (checker.shouldSkipUses(field)) { - return; - } + List mustCallObligationsOfOwningField = rlTypeFactory.getMustCallValues(field); - Set modifiers = field.getModifiers(); - if (modifiers.contains(Modifier.STATIC)) { - if (permitStaticOwning) { - return; - } - if (modifiers.contains(Modifier.FINAL)) { - return; - } - } - - List mustCallObligationsOfOwningField = rlTypeFactory.getMustCallValues(field); + if (mustCallObligationsOfOwningField.isEmpty()) { + return; + } - if (mustCallObligationsOfOwningField.isEmpty()) { - return; - } + // This value is side-effected. + Set unsatisfiedMustCallObligationsOfOwningField = new LinkedHashSet<>(); + for (String mustCallMethod : mustCallObligationsOfOwningField) { + for (MustCallConsistencyAnalyzer.MethodExitKind exitKind : + MustCallConsistencyAnalyzer.MethodExitKind.values()) { + unsatisfiedMustCallObligationsOfOwningField.add( + new DestructorObligation(mustCallMethod, exitKind)); + } + } - // This value is side-effected. - Set unsatisfiedMustCallObligationsOfOwningField = - new LinkedHashSet<>(); - for (String mustCallMethod : mustCallObligationsOfOwningField) { - for (MustCallConsistencyAnalyzer.MethodExitKind exitKind : - MustCallConsistencyAnalyzer.MethodExitKind.values()) { - unsatisfiedMustCallObligationsOfOwningField.add( - new DestructorObligation(mustCallMethod, exitKind)); + String error; + Element enclosingElement = field.getEnclosingElement(); + List enclosingMustCallValues = rlTypeFactory.getMustCallValues(enclosingElement); + + if (enclosingMustCallValues == null) { + error = + " The enclosing element " + + ElementUtils.getQualifiedName(enclosingElement) + + " doesn't have a @MustCall annotation"; + } else if (enclosingMustCallValues.isEmpty()) { + error = + " The enclosing element " + + ElementUtils.getQualifiedName(enclosingElement) + + " has an empty @MustCall annotation"; + } else { + error = " [[checkOwningField() did not find a reason!]]"; // should be reassigned + List siblingsOfOwningField = enclosingElement.getEnclosedElements(); + for (Element siblingElement : siblingsOfOwningField) { + if (siblingElement.getKind() == ElementKind.METHOD + && enclosingMustCallValues.contains(siblingElement.getSimpleName().toString())) { + + ExecutableElement siblingMethod = (ExecutableElement) siblingElement; + + AnnotationMirrorSet allEnsuresCalledMethodsAnnos = + getEnsuresCalledMethodsAnnotations(siblingMethod, rlTypeFactory); + for (AnnotationMirror ensuresCalledMethodsAnno : allEnsuresCalledMethodsAnnos) { + List values = + AnnotationUtils.getElementValueArray( + ensuresCalledMethodsAnno, + rlTypeFactory.ensuresCalledMethodsValueElement, + String.class); + for (String value : values) { + if (expressionEqualsField(value, field)) { + List methods = + AnnotationUtils.getElementValueArray( + ensuresCalledMethodsAnno, + rlTypeFactory.ensuresCalledMethodsMethodsElement, + String.class); + for (String method : methods) { + unsatisfiedMustCallObligationsOfOwningField.remove( + new DestructorObligation( + method, MustCallConsistencyAnalyzer.MethodExitKind.NORMAL_RETURN)); + } + } } - } - String error; - Element enclosingElement = field.getEnclosingElement(); - List enclosingMustCallValues = rlTypeFactory.getMustCallValues(enclosingElement); - - if (enclosingMustCallValues == null) { - error = - " The enclosing element " - + ElementUtils.getQualifiedName(enclosingElement) - + " doesn't have a @MustCall annotation"; - } else if (enclosingMustCallValues.isEmpty()) { - error = - " The enclosing element " - + ElementUtils.getQualifiedName(enclosingElement) - + " has an empty @MustCall annotation"; - } else { - error = " [[checkOwningField() did not find a reason!]]"; // should be reassigned - List siblingsOfOwningField = enclosingElement.getEnclosedElements(); - for (Element siblingElement : siblingsOfOwningField) { - if (siblingElement.getKind() == ElementKind.METHOD - && enclosingMustCallValues.contains( - siblingElement.getSimpleName().toString())) { - - ExecutableElement siblingMethod = (ExecutableElement) siblingElement; - - AnnotationMirrorSet allEnsuresCalledMethodsAnnos = - getEnsuresCalledMethodsAnnotations(siblingMethod, rlTypeFactory); - for (AnnotationMirror ensuresCalledMethodsAnno : allEnsuresCalledMethodsAnnos) { - List values = - AnnotationUtils.getElementValueArray( - ensuresCalledMethodsAnno, - rlTypeFactory.ensuresCalledMethodsValueElement, - String.class); - for (String value : values) { - if (expressionEqualsField(value, field)) { - List methods = - AnnotationUtils.getElementValueArray( - ensuresCalledMethodsAnno, - rlTypeFactory.ensuresCalledMethodsMethodsElement, - String.class); - for (String method : methods) { - unsatisfiedMustCallObligationsOfOwningField.remove( - new DestructorObligation( - method, - MustCallConsistencyAnalyzer.MethodExitKind - .NORMAL_RETURN)); - } - } - } - - Set exceptionalPostconds = - rlTypeFactory.getExceptionalPostconditions(siblingMethod); - for (EnsuresCalledMethodOnExceptionContract postcond : - exceptionalPostconds) { - if (expressionEqualsField(postcond.getExpression(), field)) { - unsatisfiedMustCallObligationsOfOwningField.remove( - new DestructorObligation( - postcond.getMethod(), - MustCallConsistencyAnalyzer.MethodExitKind - .EXCEPTIONAL_EXIT)); - } - } - - // Optimization: stop early as soon as we've exhausted the list of - // obligations - if (unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { - return; - } - } - - if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { - // This variable could be set immediately before reporting the error, but - // IMO it is more clear to set it here. - error = - "Postconditions written on MustCall methods are missing: " - + formatMissingMustCallMethodPostconditions( - field, unsatisfiedMustCallObligationsOfOwningField); - } - } + Set exceptionalPostconds = + rlTypeFactory.getExceptionalPostconditions(siblingMethod); + for (EnsuresCalledMethodOnExceptionContract postcond : exceptionalPostconds) { + if (expressionEqualsField(postcond.getExpression(), field)) { + unsatisfiedMustCallObligationsOfOwningField.remove( + new DestructorObligation( + postcond.getMethod(), + MustCallConsistencyAnalyzer.MethodExitKind.EXCEPTIONAL_EXIT)); + } } - } - if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { - Set missingMethods = new LinkedHashSet<>(); - for (DestructorObligation obligation : unsatisfiedMustCallObligationsOfOwningField) { - missingMethods.add(obligation.mustCallMethod); + // Optimization: stop early as soon as we've exhausted the list of + // obligations + if (unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { + return; } + } - checker.reportError( - field, - "required.method.not.called", - MustCallConsistencyAnalyzer.formatMissingMustCallMethods( - new ArrayList<>(missingMethods)), - "field " + field.getSimpleName().toString(), - field.asType().toString(), - error); + if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { + // This variable could be set immediately before reporting the error, but + // IMO it is more clear to set it here. + error = + "Postconditions written on MustCall methods are missing: " + + formatMissingMustCallMethodPostconditions( + field, unsatisfiedMustCallObligationsOfOwningField); + } } + } } - /** - * Determine if the given expression e refers to this.field. - * - * @param e the expression - * @param field the field - * @return true if e refers to this.field - */ - private boolean expressionEqualsField(String e, VariableElement field) { - try { - JavaExpression je = StringToJavaExpression.atFieldDecl(e, field, this.checker); - return je instanceof FieldAccess && ((FieldAccess) je).getField().equals(field); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - // The parsing error will be reported elsewhere, assuming e was derived from an - // annotation. - return false; - } + if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { + Set missingMethods = new LinkedHashSet<>(); + for (DestructorObligation obligation : unsatisfiedMustCallObligationsOfOwningField) { + missingMethods.add(obligation.mustCallMethod); + } + + checker.reportError( + field, + "required.method.not.called", + MustCallConsistencyAnalyzer.formatMissingMustCallMethods(new ArrayList<>(missingMethods)), + "field " + field.getSimpleName().toString(), + field.asType().toString(), + error); } - - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; + } + + /** + * Determine if the given expression e refers to this.field. + * + * @param e the expression + * @param field the field + * @return true if e refers to this.field + */ + private boolean expressionEqualsField(String e, VariableElement field) { + try { + JavaExpression je = StringToJavaExpression.atFieldDecl(e, field, this.checker); + return je instanceof FieldAccess && ((FieldAccess) je).getField().equals(field); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + // The parsing error will be reported elsewhere, assuming e was derived from an + // annotation. + return false; } - */ - - /** - * Formats a list of must-call method post-conditions to be printed in an error message. - * - * @param field the value whose methods must be called - * @param mustCallVal the list of must-call strings - * @return a formatted string - */ - /*package-private*/ static String formatMissingMustCallMethodPostconditions( - Element field, Set mustCallVal) { - int size = mustCallVal.size(); - if (size == 0) { - throw new TypeSystemError("empty mustCallVal " + mustCallVal); - } - String fieldName = field.getSimpleName().toString(); - return mustCallVal.stream() - .map( - o -> - postconditionAnnotationFor(o.exitKind) - + "(value = \"" - + fieldName - + "\", methods = \"" - + o.mustCallMethod - + "\")") - .collect(Collectors.joining(", ")); + } + + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; + } + */ + + /** + * Formats a list of must-call method post-conditions to be printed in an error message. + * + * @param field the value whose methods must be called + * @param mustCallVal the list of must-call strings + * @return a formatted string + */ + /*package-private*/ static String formatMissingMustCallMethodPostconditions( + Element field, Set mustCallVal) { + int size = mustCallVal.size(); + if (size == 0) { + throw new TypeSystemError("empty mustCallVal " + mustCallVal); } - - /** - * Format a must-call post-condition to be printed in an error message. - * - * @param exitKind the kind of method exit - * @return the name of the annotation - */ - private static String postconditionAnnotationFor( - MustCallConsistencyAnalyzer.MethodExitKind exitKind) { - switch (exitKind) { - case NORMAL_RETURN: - return "@EnsuresCalledMethods"; - case EXCEPTIONAL_EXIT: - return "@EnsuresCalledMethodsOnException"; - default: - throw new UnsupportedOperationException(exitKind.toString()); - } + String fieldName = field.getSimpleName().toString(); + return mustCallVal.stream() + .map( + o -> + postconditionAnnotationFor(o.exitKind) + + "(value = \"" + + fieldName + + "\", methods = \"" + + o.mustCallMethod + + "\")") + .collect(Collectors.joining(", ")); + } + + /** + * Format a must-call post-condition to be printed in an error message. + * + * @param exitKind the kind of method exit + * @return the name of the annotation + */ + private static String postconditionAnnotationFor( + MustCallConsistencyAnalyzer.MethodExitKind exitKind) { + switch (exitKind) { + case NORMAL_RETURN: + return "@EnsuresCalledMethods"; + case EXCEPTIONAL_EXIT: + return "@EnsuresCalledMethodsOnException"; + default: + throw new UnsupportedOperationException(exitKind.toString()); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java index 185bbbf70e9..4187adfc506 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java @@ -2,12 +2,10 @@ import com.google.common.collect.ImmutableSet; import com.sun.tools.javac.code.Type; - -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.dataflow.qual.Pure; - import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.dataflow.qual.Pure; /** * A set of types. @@ -25,75 +23,75 @@ */ public interface SetOfTypes { - /** - * Test whether this set contains the given type. - * - * @param typeUtils a {@code Types} object for computing the relationships between types - * @param type the type in question - * @return true if this set contains {@code type}, or false otherwise - */ - @Pure - boolean contains(Types typeUtils, TypeMirror type); + /** + * Test whether this set contains the given type. + * + * @param typeUtils a {@code Types} object for computing the relationships between types + * @param type the type in question + * @return true if this set contains {@code type}, or false otherwise + */ + @Pure + boolean contains(Types typeUtils, TypeMirror type); - /** An empty set of types. */ - SetOfTypes EMPTY = (typeUtils, type) -> false; + /** An empty set of types. */ + SetOfTypes EMPTY = (typeUtils, type) -> false; - /** - * Create a set containing exactly the given type, but not its subtypes. - * - * @param t the type - * @return a set containing only {@code t} - */ - @Pure - static SetOfTypes singleton(TypeMirror t) { - return (typeUtils, u) -> typeUtils.isSameType(t, u); - } + /** + * Create a set containing exactly the given type, but not its subtypes. + * + * @param t the type + * @return a set containing only {@code t} + */ + @Pure + static SetOfTypes singleton(TypeMirror t) { + return (typeUtils, u) -> typeUtils.isSameType(t, u); + } - /** - * Create a set containing the given type and all of its subtypes. - * - * @param t the type - * @return a set containing {@code t} and its subtypes - */ - @Pure - static SetOfTypes allSubtypes(TypeMirror t) { - return (typeUtils, u) -> typeUtils.isSubtype(u, t); - } + /** + * Create a set containing the given type and all of its subtypes. + * + * @param t the type + * @return a set containing {@code t} and its subtypes + */ + @Pure + static SetOfTypes allSubtypes(TypeMirror t) { + return (typeUtils, u) -> typeUtils.isSubtype(u, t); + } - /** - * Create a set containing exactly the types with the given names, but not their subtypes. - * - * @param names the type names - * @return a set containing only the named types - */ - @Pure - static SetOfTypes anyOfTheseNames(ImmutableSet<@CanonicalName String> names) { - return (typeUtils, u) -> - u instanceof Type && names.contains(((Type) u).tsym.getQualifiedName().toString()); - } + /** + * Create a set containing exactly the types with the given names, but not their subtypes. + * + * @param names the type names + * @return a set containing only the named types + */ + @Pure + static SetOfTypes anyOfTheseNames(ImmutableSet<@CanonicalName String> names) { + return (typeUtils, u) -> + u instanceof Type && names.contains(((Type) u).tsym.getQualifiedName().toString()); + } - /** - * Create a set representing the union of all the given sets. - * - * @param typeSets an array of sets - * @return the union of the given sets - */ - @Pure - static SetOfTypes union(SetOfTypes... typeSets) { - switch (typeSets.length) { - case 0: - return EMPTY; - case 1: - return typeSets[0]; - default: - return (typeUtils, type) -> { - for (SetOfTypes set : typeSets) { - if (set.contains(typeUtils, type)) { - return true; - } - } - return false; - }; - } + /** + * Create a set representing the union of all the given sets. + * + * @param typeSets an array of sets + * @return the union of the given sets + */ + @Pure + static SetOfTypes union(SetOfTypes... typeSets) { + switch (typeSets.length) { + case 0: + return EMPTY; + case 1: + return typeSets[0]; + default: + return (typeUtils, type) -> { + for (SetOfTypes set : typeSets) { + if (set.contains(typeUtils, type)) { + return true; + } + } + return false; + }; } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java index be385c7881b..6b31fd81869 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java @@ -8,7 +8,15 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.PrimitiveTypeTree; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.signature.qual.ArrayWithoutPackage; import org.checkerframework.checker.signature.qual.BinaryName; import org.checkerframework.checker.signature.qual.BinaryNameOrPrimitiveType; @@ -42,283 +50,263 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.reflection.SignatureRegexes; -import java.lang.annotation.Annotation; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - // TODO: Does not yet handle method signature annotations, such as // @MethodDescriptor. /** Accounts for the effects of certain calls to String.replace. */ public class SignatureAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@literal @}{@link SignatureUnknown} annotation. */ - protected final AnnotationMirror SIGNATURE_UNKNOWN = - AnnotationBuilder.fromClass(elements, SignatureUnknown.class); + /** The {@literal @}{@link SignatureUnknown} annotation. */ + protected final AnnotationMirror SIGNATURE_UNKNOWN = + AnnotationBuilder.fromClass(elements, SignatureUnknown.class); - /** The {@literal @}{@link BinaryName} annotation. */ - protected final AnnotationMirror BINARY_NAME = - AnnotationBuilder.fromClass(elements, BinaryName.class); + /** The {@literal @}{@link BinaryName} annotation. */ + protected final AnnotationMirror BINARY_NAME = + AnnotationBuilder.fromClass(elements, BinaryName.class); - /** The {@literal @}{@link InternalForm} annotation. */ - protected final AnnotationMirror INTERNAL_FORM = - AnnotationBuilder.fromClass(elements, InternalForm.class); + /** The {@literal @}{@link InternalForm} annotation. */ + protected final AnnotationMirror INTERNAL_FORM = + AnnotationBuilder.fromClass(elements, InternalForm.class); - /** The {@literal @}{@link DotSeparatedIdentifiers} annotation. */ - protected final AnnotationMirror DOT_SEPARATED_IDENTIFIERS = - AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class); + /** The {@literal @}{@link DotSeparatedIdentifiers} annotation. */ + protected final AnnotationMirror DOT_SEPARATED_IDENTIFIERS = + AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class); - /** The {@literal @}{@link CanonicalName} annotation. */ - protected final AnnotationMirror CANONICAL_NAME = - AnnotationBuilder.fromClass(elements, CanonicalName.class); + /** The {@literal @}{@link CanonicalName} annotation. */ + protected final AnnotationMirror CANONICAL_NAME = + AnnotationBuilder.fromClass(elements, CanonicalName.class); - /** The {@literal @}{@link CanonicalNameAndBinaryName} annotation. */ - protected final AnnotationMirror CANONICAL_NAME_AND_BINARY_NAME = - AnnotationBuilder.fromClass(elements, CanonicalNameAndBinaryName.class); + /** The {@literal @}{@link CanonicalNameAndBinaryName} annotation. */ + protected final AnnotationMirror CANONICAL_NAME_AND_BINARY_NAME = + AnnotationBuilder.fromClass(elements, CanonicalNameAndBinaryName.class); - /** The {@literal @}{@link PrimitiveType} annotation. */ - protected final AnnotationMirror PRIMITIVE_TYPE = - AnnotationBuilder.fromClass(elements, PrimitiveType.class); + /** The {@literal @}{@link PrimitiveType} annotation. */ + protected final AnnotationMirror PRIMITIVE_TYPE = + AnnotationBuilder.fromClass(elements, PrimitiveType.class); - /** The {@link String#replace(char, char)} method. */ - private final ExecutableElement replaceCharChar = - TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char"); + /** The {@link String#replace(char, char)} method. */ + private final ExecutableElement replaceCharChar = + TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char"); - /** The {@link String#replace(CharSequence, CharSequence)} method. */ - private final ExecutableElement replaceCharSequenceCharSequence = - TreeUtils.getMethod( - "java.lang.String", - "replace", - processingEnv, - "java.lang.CharSequence", - "java.lang.CharSequence"); + /** The {@link String#replace(CharSequence, CharSequence)} method. */ + private final ExecutableElement replaceCharSequenceCharSequence = + TreeUtils.getMethod( + "java.lang.String", + "replace", + processingEnv, + "java.lang.CharSequence", + "java.lang.CharSequence"); - /** The {@link Class#getName()} method. */ - private final ExecutableElement classGetName = - TreeUtils.getMethod("java.lang.Class", "getName", processingEnv); + /** The {@link Class#getName()} method. */ + private final ExecutableElement classGetName = + TreeUtils.getMethod("java.lang.Class", "getName", processingEnv); - /** The {@link Class#getCanonicalName()} method. */ - private final ExecutableElement classGetCanonicalName = - TreeUtils.getMethod(java.lang.Class.class, "getCanonicalName", processingEnv); + /** The {@link Class#getCanonicalName()} method. */ + private final ExecutableElement classGetCanonicalName = + TreeUtils.getMethod(java.lang.Class.class, "getCanonicalName", processingEnv); - /** - * Creates a SignatureAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this type factory - */ - public SignatureAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + /** + * Creates a SignatureAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this type factory + */ + public SignatureAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(SignatureUnknown.class, SignatureBottom.class); + } + + @Override + public TreeAnnotator createTreeAnnotator() { + // It is slightly inefficient that super also adds a LiteralTreeAnnotator, but it seems + // better than hard-coding the behavior of super here. + return new ListTreeAnnotator( + signatureLiteralTreeAnnotator(this), + new SignatureTreeAnnotator(this), + super.createTreeAnnotator()); + } + + /** + * Create a LiteralTreeAnnotator for the Signature Checker. + * + * @param atypeFactory the type factory + * @return a LiteralTreeAnnotator for the Signature Checker + */ + private LiteralTreeAnnotator signatureLiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + LiteralTreeAnnotator result = new LiteralTreeAnnotator(atypeFactory); + result.addStandardLiteralQualifiers(); + + // The below code achieves the same effect as writing a meta-annotation + // @QualifierForLiterals(stringPatterns = "...") + // on each type qualifier definition. Annotation elements cannot be computations (not even + // string concatenations of literal strings) and cannot be not references to compile-time + // constants such as effectively-final fields. So every `stringPatterns = "..."` would have + // to be a literal string, which would be verbose ard hard to maintain. + result.addStringPattern( + SignatureRegexes.ArrayWithoutPackageRegex, + AnnotationBuilder.fromClass(elements, ArrayWithoutPackage.class)); + result.addStringPattern( + SignatureRegexes.BinaryNameRegex, AnnotationBuilder.fromClass(elements, BinaryName.class)); + result.addStringPattern( + SignatureRegexes.BinaryNameOrPrimitiveTypeRegex, + AnnotationBuilder.fromClass(elements, BinaryNameOrPrimitiveType.class)); + result.addStringPattern( + SignatureRegexes.BinaryNameWithoutPackageRegex, + AnnotationBuilder.fromClass(elements, BinaryNameWithoutPackage.class)); + result.addStringPattern( + SignatureRegexes.ClassGetNameRegex, + AnnotationBuilder.fromClass(elements, ClassGetName.class)); + result.addStringPattern( + SignatureRegexes.ClassGetSimpleNameRegex, + AnnotationBuilder.fromClass(elements, ClassGetSimpleName.class)); + result.addStringPattern( + SignatureRegexes.DotSeparatedIdentifiersRegex, + AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class)); + result.addStringPattern( + SignatureRegexes.DotSeparatedIdentifiersOrPrimitiveTypeRegex, + AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiersOrPrimitiveType.class)); + result.addStringPattern( + SignatureRegexes.FieldDescriptorRegex, + AnnotationBuilder.fromClass(elements, FieldDescriptor.class)); + result.addStringPattern( + SignatureRegexes.FieldDescriptorForPrimitiveRegex, + AnnotationBuilder.fromClass(elements, FieldDescriptorForPrimitive.class)); + result.addStringPattern( + SignatureRegexes.FieldDescriptorWithoutPackageRegex, + AnnotationBuilder.fromClass(elements, FieldDescriptorWithoutPackage.class)); + result.addStringPattern( + SignatureRegexes.FqBinaryNameRegex, + AnnotationBuilder.fromClass(elements, FqBinaryName.class)); + result.addStringPattern( + SignatureRegexes.FullyQualifiedNameRegex, + AnnotationBuilder.fromClass(elements, FullyQualifiedName.class)); + result.addStringPattern( + SignatureRegexes.IdentifierRegex, AnnotationBuilder.fromClass(elements, Identifier.class)); + result.addStringPattern( + SignatureRegexes.IdentifierOrPrimitiveTypeRegex, + AnnotationBuilder.fromClass(elements, IdentifierOrPrimitiveType.class)); + result.addStringPattern( + SignatureRegexes.InternalFormRegex, + AnnotationBuilder.fromClass(elements, InternalForm.class)); + result.addStringPattern( + SignatureRegexes.PrimitiveTypeRegex, + AnnotationBuilder.fromClass(elements, PrimitiveType.class)); + return result; + } - this.postInit(); + private class SignatureTreeAnnotator extends TreeAnnotator { + + public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(SignatureUnknown.class, SignatureBottom.class); + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringConcatenation(tree)) { + // This could be made more precise. + type.replaceAnnotation(SIGNATURE_UNKNOWN); + } + return null; // super.visitBinary(tree, type); } @Override - public TreeAnnotator createTreeAnnotator() { - // It is slightly inefficient that super also adds a LiteralTreeAnnotator, but it seems - // better than hard-coding the behavior of super here. - return new ListTreeAnnotator( - signatureLiteralTreeAnnotator(this), - new SignatureTreeAnnotator(this), - super.createTreeAnnotator()); + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + // This could be made more precise. + type.replaceAnnotation(SIGNATURE_UNKNOWN); + } + return null; // super.visitCompoundAssignment(tree, type); } /** - * Create a LiteralTreeAnnotator for the Signature Checker. + * String.replace, when called with specific constant arguments, converts between internal form + * and binary name: + * + *


+     * {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
+     * {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
+     * 
+ * + * Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return a + * {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they + * return a {@link BinaryName}: * - * @param atypeFactory the type factory - * @return a LiteralTreeAnnotator for the Signature Checker + *

+     * {@literal @}BinaryName String binaryName = MyClass.class.getName();
+     * 
*/ - private LiteralTreeAnnotator signatureLiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - LiteralTreeAnnotator result = new LiteralTreeAnnotator(atypeFactory); - result.addStandardLiteralQualifiers(); - - // The below code achieves the same effect as writing a meta-annotation - // @QualifierForLiterals(stringPatterns = "...") - // on each type qualifier definition. Annotation elements cannot be computations (not even - // string concatenations of literal strings) and cannot be not references to compile-time - // constants such as effectively-final fields. So every `stringPatterns = "..."` would have - // to be a literal string, which would be verbose ard hard to maintain. - result.addStringPattern( - SignatureRegexes.ArrayWithoutPackageRegex, - AnnotationBuilder.fromClass(elements, ArrayWithoutPackage.class)); - result.addStringPattern( - SignatureRegexes.BinaryNameRegex, - AnnotationBuilder.fromClass(elements, BinaryName.class)); - result.addStringPattern( - SignatureRegexes.BinaryNameOrPrimitiveTypeRegex, - AnnotationBuilder.fromClass(elements, BinaryNameOrPrimitiveType.class)); - result.addStringPattern( - SignatureRegexes.BinaryNameWithoutPackageRegex, - AnnotationBuilder.fromClass(elements, BinaryNameWithoutPackage.class)); - result.addStringPattern( - SignatureRegexes.ClassGetNameRegex, - AnnotationBuilder.fromClass(elements, ClassGetName.class)); - result.addStringPattern( - SignatureRegexes.ClassGetSimpleNameRegex, - AnnotationBuilder.fromClass(elements, ClassGetSimpleName.class)); - result.addStringPattern( - SignatureRegexes.DotSeparatedIdentifiersRegex, - AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class)); - result.addStringPattern( - SignatureRegexes.DotSeparatedIdentifiersOrPrimitiveTypeRegex, - AnnotationBuilder.fromClass( - elements, DotSeparatedIdentifiersOrPrimitiveType.class)); - result.addStringPattern( - SignatureRegexes.FieldDescriptorRegex, - AnnotationBuilder.fromClass(elements, FieldDescriptor.class)); - result.addStringPattern( - SignatureRegexes.FieldDescriptorForPrimitiveRegex, - AnnotationBuilder.fromClass(elements, FieldDescriptorForPrimitive.class)); - result.addStringPattern( - SignatureRegexes.FieldDescriptorWithoutPackageRegex, - AnnotationBuilder.fromClass(elements, FieldDescriptorWithoutPackage.class)); - result.addStringPattern( - SignatureRegexes.FqBinaryNameRegex, - AnnotationBuilder.fromClass(elements, FqBinaryName.class)); - result.addStringPattern( - SignatureRegexes.FullyQualifiedNameRegex, - AnnotationBuilder.fromClass(elements, FullyQualifiedName.class)); - result.addStringPattern( - SignatureRegexes.IdentifierRegex, - AnnotationBuilder.fromClass(elements, Identifier.class)); - result.addStringPattern( - SignatureRegexes.IdentifierOrPrimitiveTypeRegex, - AnnotationBuilder.fromClass(elements, IdentifierOrPrimitiveType.class)); - result.addStringPattern( - SignatureRegexes.InternalFormRegex, - AnnotationBuilder.fromClass(elements, InternalForm.class)); - result.addStringPattern( - SignatureRegexes.PrimitiveTypeRegex, - AnnotationBuilder.fromClass(elements, PrimitiveType.class)); - return result; - } - - private class SignatureTreeAnnotator extends TreeAnnotator { - - public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringConcatenation(tree)) { - // This could be made more precise. - type.replaceAnnotation(SIGNATURE_UNKNOWN); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv) + || TreeUtils.isMethodInvocation(tree, replaceCharSequenceCharSequence, processingEnv)) { + char oldChar = ' '; // initial dummy value + char newChar = ' '; // initial dummy value + if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)) { + ExpressionTree arg0 = tree.getArguments().get(0); + ExpressionTree arg1 = tree.getArguments().get(1); + if (arg0.getKind() == Tree.Kind.CHAR_LITERAL + && arg1.getKind() == Tree.Kind.CHAR_LITERAL) { + oldChar = (char) ((LiteralTree) arg0).getValue(); + newChar = (char) ((LiteralTree) arg1).getValue(); + } + } else { + ExpressionTree arg0 = tree.getArguments().get(0); + ExpressionTree arg1 = tree.getArguments().get(1); + if (arg0.getKind() == Tree.Kind.STRING_LITERAL + && arg1.getKind() == Tree.Kind.STRING_LITERAL) { + String const0 = (String) ((LiteralTree) arg0).getValue(); + String const1 = (String) ((LiteralTree) arg1).getValue(); + if (const0.length() == 1 && const1.length() == 1) { + oldChar = const0.charAt(0); + newChar = const1.charAt(0); } - return null; // super.visitBinary(tree, type); + } } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - // This could be made more precise. - type.replaceAnnotation(SIGNATURE_UNKNOWN); - } - return null; // super.visitCompoundAssignment(tree, type); + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + AnnotatedTypeMirror receiverType = getAnnotatedType(receiver); + if ((oldChar == '.' && newChar == '/') + && receiverType.getAnnotation(BinaryName.class) != null) { + type.replaceAnnotation(INTERNAL_FORM); + } else if ((oldChar == '/' && newChar == '.') + && receiverType.getAnnotation(InternalForm.class) != null) { + type.replaceAnnotation(BINARY_NAME); } - - /** - * String.replace, when called with specific constant arguments, converts between internal - * form and binary name: - * - *

-         * {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
-         * {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
-         * 
- * - * Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return - * a {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they - * return a {@link BinaryName}: - * - *

-         * {@literal @}BinaryName String binaryName = MyClass.class.getName();
-         * 
- */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv) - || TreeUtils.isMethodInvocation( - tree, replaceCharSequenceCharSequence, processingEnv)) { - char oldChar = ' '; // initial dummy value - char newChar = ' '; // initial dummy value - if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)) { - ExpressionTree arg0 = tree.getArguments().get(0); - ExpressionTree arg1 = tree.getArguments().get(1); - if (arg0.getKind() == Tree.Kind.CHAR_LITERAL - && arg1.getKind() == Tree.Kind.CHAR_LITERAL) { - oldChar = (char) ((LiteralTree) arg0).getValue(); - newChar = (char) ((LiteralTree) arg1).getValue(); - } - } else { - ExpressionTree arg0 = tree.getArguments().get(0); - ExpressionTree arg1 = tree.getArguments().get(1); - if (arg0.getKind() == Tree.Kind.STRING_LITERAL - && arg1.getKind() == Tree.Kind.STRING_LITERAL) { - String const0 = (String) ((LiteralTree) arg0).getValue(); - String const1 = (String) ((LiteralTree) arg1).getValue(); - if (const0.length() == 1 && const1.length() == 1) { - oldChar = const0.charAt(0); - newChar = const1.charAt(0); - } - } - } - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - AnnotatedTypeMirror receiverType = getAnnotatedType(receiver); - if ((oldChar == '.' && newChar == '/') - && receiverType.getAnnotation(BinaryName.class) != null) { - type.replaceAnnotation(INTERNAL_FORM); - } else if ((oldChar == '/' && newChar == '.') - && receiverType.getAnnotation(InternalForm.class) != null) { - type.replaceAnnotation(BINARY_NAME); - } + } else { + boolean isClassGetName = TreeUtils.isMethodInvocation(tree, classGetName, processingEnv); + boolean isClassGetCanonicalName = + TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv); + if (isClassGetName || isClassGetCanonicalName) { + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + if (TreeUtils.isClassLiteral(receiver)) { + ExpressionTree classExpr = ((MemberSelectTree) receiver).getExpression(); + if (classExpr.getKind() == Tree.Kind.PRIMITIVE_TYPE) { + if (((PrimitiveTypeTree) classExpr).getPrimitiveTypeKind() == TypeKind.VOID) { + // do nothing + } else { + type.replaceAnnotation(PRIMITIVE_TYPE); + } } else { - boolean isClassGetName = - TreeUtils.isMethodInvocation(tree, classGetName, processingEnv); - boolean isClassGetCanonicalName = - TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv); - if (isClassGetName || isClassGetCanonicalName) { - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - if (TreeUtils.isClassLiteral(receiver)) { - ExpressionTree classExpr = ((MemberSelectTree) receiver).getExpression(); - if (classExpr.getKind() == Tree.Kind.PRIMITIVE_TYPE) { - if (((PrimitiveTypeTree) classExpr).getPrimitiveTypeKind() - == TypeKind.VOID) { - // do nothing - } else { - type.replaceAnnotation(PRIMITIVE_TYPE); - } - } else { - // Binary name if non-array, non-primitive, non-nested. - TypeMirror literalType = TreeUtils.typeOf(classExpr); - if (literalType.getKind() == TypeKind.DECLARED) { - TypeElement typeElt = TypesUtils.getTypeElement(literalType); - Element enclosing = typeElt.getEnclosingElement(); - if (enclosing == null - || enclosing.getKind() == ElementKind.PACKAGE) { - type.replaceAnnotation( - isClassGetName - ? DOT_SEPARATED_IDENTIFIERS - : CANONICAL_NAME_AND_BINARY_NAME); - } - } - } - } + // Binary name if non-array, non-primitive, non-nested. + TypeMirror literalType = TreeUtils.typeOf(classExpr); + if (literalType.getKind() == TypeKind.DECLARED) { + TypeElement typeElt = TypesUtils.getTypeElement(literalType); + Element enclosing = typeElt.getEnclosingElement(); + if (enclosing == null || enclosing.getKind() == ElementKind.PACKAGE) { + type.replaceAnnotation( + isClassGetName ? DOT_SEPARATED_IDENTIFIERS : CANONICAL_NAME_AND_BINARY_NAME); } + } } - - return super.visitMethodInvocation(tree, type); + } } + } + + return super.visitMethodInvocation(tree, type); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java index 6b3bcfa4dfc..770db52ca15 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java @@ -1,7 +1,6 @@ package org.checkerframework.checker.signature; import com.sun.source.tree.CompilationUnitTree; - import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.StubFiles; @@ -16,9 +15,9 @@ @StubFiles({"javac.astub", "javaparser.astub"}) public final class SignatureChecker extends BaseTypeChecker { - // This method is needed only under MacOS, perhaps as a result of the - // broken Apple Java distribution. - public SignatureAnnotatedTypeFactory createFactory(CompilationUnitTree root) { - return new SignatureAnnotatedTypeFactory(this); - } + // This method is needed only under MacOS, perhaps as a result of the + // broken Apple Java distribution. + public SignatureAnnotatedTypeFactory createFactory(CompilationUnitTree root) { + return new SignatureAnnotatedTypeFactory(this); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java index d9caf2cac54..fcefc408603 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.checker.signature; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -16,50 +17,46 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; -import javax.lang.model.element.ExecutableElement; - /** The transfer function for the Signature Checker. */ public class SignatureTransfer extends CFTransfer { - /** The annotated type factory for this transfer function. */ - private final SignatureAnnotatedTypeFactory atypeFactory; - - /** - * Create a new SignatureTransfer. - * - * @param analysis the analysis - */ - public SignatureTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (SignatureAnnotatedTypeFactory) analysis.getTypeFactory(); - } - - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult superResult = super.visitMethodInvocation(n, in); - - MethodAccessNode target = n.getTarget(); - ExecutableElement method = target.getMethod(); - Node receiver = target.getReceiver(); - if (TypesUtils.isString(receiver.getType()) - && ElementUtils.matchesElement(method, "isEmpty")) { - - AnnotatedTypeMirror receiverAtm = atypeFactory.getAnnotatedType(receiver.getTree()); - if (receiverAtm.hasAnnotation(CanonicalNameOrEmpty.class)) { - - CFStore thenStore = superResult.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult result = - new ConditionalTransferResult<>( - superResult.getResultValue(), thenStore, elseStore); - // The refined expression is the receiver of the method call. - JavaExpression refinedExpr = JavaExpression.fromNode(receiver); - - elseStore.insertValue(refinedExpr, atypeFactory.CANONICAL_NAME); - return result; - } - } - return superResult; + /** The annotated type factory for this transfer function. */ + private final SignatureAnnotatedTypeFactory atypeFactory; + + /** + * Create a new SignatureTransfer. + * + * @param analysis the analysis + */ + public SignatureTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (SignatureAnnotatedTypeFactory) analysis.getTypeFactory(); + } + + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult superResult = super.visitMethodInvocation(n, in); + + MethodAccessNode target = n.getTarget(); + ExecutableElement method = target.getMethod(); + Node receiver = target.getReceiver(); + if (TypesUtils.isString(receiver.getType()) && ElementUtils.matchesElement(method, "isEmpty")) { + + AnnotatedTypeMirror receiverAtm = atypeFactory.getAnnotatedType(receiver.getTree()); + if (receiverAtm.hasAnnotation(CanonicalNameOrEmpty.class)) { + + CFStore thenStore = superResult.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult result = + new ConditionalTransferResult<>(superResult.getResultValue(), thenStore, elseStore); + // The refined expression is the receiver of the method call. + JavaExpression refinedExpr = JavaExpression.fromNode(receiver); + + elseStore.insertValue(refinedExpr, atypeFactory.CANONICAL_NAME); + return result; + } } + return superResult; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java index b3b189cc238..6a0c99cf025 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java @@ -7,7 +7,13 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.TreePath; - +import java.io.Serializable; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signedness.qual.PolySigned; import org.checkerframework.checker.signedness.qual.Signed; @@ -40,15 +46,6 @@ import org.checkerframework.javacutil.TypeKindUtils; import org.checkerframework.javacutil.TypesUtils; -import java.io.Serializable; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * The type factory for the Signedness Checker. * @@ -56,392 +53,386 @@ */ public class SignednessAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @Signed annotation. */ - protected final AnnotationMirror SIGNED = AnnotationBuilder.fromClass(elements, Signed.class); - - /** The @Unsigned annotation. */ - protected final AnnotationMirror UNSIGNED = - AnnotationBuilder.fromClass(elements, Unsigned.class); - - /** The @SignednessGlb annotation. Do not use @SignedPositive; use this instead. */ - protected final AnnotationMirror SIGNEDNESS_GLB = - AnnotationBuilder.fromClass(elements, SignednessGlb.class); - - /** The @SignedPositive annotation. */ - protected final AnnotationMirror SIGNED_POSITIVE = - AnnotationBuilder.fromClass(elements, SignedPositive.class); - - /** The @SignednessBottom annotation. */ - protected final AnnotationMirror SIGNEDNESS_BOTTOM = - AnnotationBuilder.fromClass(elements, SignednessBottom.class); - - /** The @PolySigned annotation. */ - protected final AnnotationMirror POLY_SIGNED = - AnnotationBuilder.fromClass(elements, PolySigned.class); - - /** The @NonNegative annotation of the Index Checker, as represented by the Value Checker. */ - private final AnnotationMirror INT_RANGE_FROM_NON_NEGATIVE = - AnnotationBuilder.fromClass(elements, IntRangeFromNonNegative.class); - - /** The @Positive annotation of the Index Checker, as represented by the Value Checker. */ - private final AnnotationMirror INT_RANGE_FROM_POSITIVE = - AnnotationBuilder.fromClass(elements, IntRangeFromPositive.class); + /** The @Signed annotation. */ + protected final AnnotationMirror SIGNED = AnnotationBuilder.fromClass(elements, Signed.class); + + /** The @Unsigned annotation. */ + protected final AnnotationMirror UNSIGNED = AnnotationBuilder.fromClass(elements, Unsigned.class); + + /** The @SignednessGlb annotation. Do not use @SignedPositive; use this instead. */ + protected final AnnotationMirror SIGNEDNESS_GLB = + AnnotationBuilder.fromClass(elements, SignednessGlb.class); + + /** The @SignedPositive annotation. */ + protected final AnnotationMirror SIGNED_POSITIVE = + AnnotationBuilder.fromClass(elements, SignedPositive.class); + + /** The @SignednessBottom annotation. */ + protected final AnnotationMirror SIGNEDNESS_BOTTOM = + AnnotationBuilder.fromClass(elements, SignednessBottom.class); + + /** The @PolySigned annotation. */ + protected final AnnotationMirror POLY_SIGNED = + AnnotationBuilder.fromClass(elements, PolySigned.class); + + /** The @NonNegative annotation of the Index Checker, as represented by the Value Checker. */ + private final AnnotationMirror INT_RANGE_FROM_NON_NEGATIVE = + AnnotationBuilder.fromClass(elements, IntRangeFromNonNegative.class); + + /** The @Positive annotation of the Index Checker, as represented by the Value Checker. */ + private final AnnotationMirror INT_RANGE_FROM_POSITIVE = + AnnotationBuilder.fromClass(elements, IntRangeFromPositive.class); + + /** The Serializable type mirror. */ + private final TypeMirror serializableTM = + elements.getTypeElement(Serializable.class.getCanonicalName()).asType(); + + /** The Comparable type mirror. */ + private final TypeMirror comparableTM = + elements.getTypeElement(Comparable.class.getCanonicalName()).asType(); + + /** The Number type mirror. */ + private final TypeMirror numberTM = + elements.getTypeElement(Number.class.getCanonicalName()).asType(); + + /** A set containing just {@code @Signed}. */ + private final AnnotationMirrorSet SIGNED_SINGLETON = new AnnotationMirrorSet(SIGNED); + + /** A set containing just {@code @Unsigned}. */ + private final AnnotationMirrorSet UNSIGNED_SINGLETON = new AnnotationMirrorSet(UNSIGNED); + + /** + * Create a SignednessAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this type factory + */ + public SignednessAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + addAliasedTypeAnnotation("jdk.jfr.Unsigned", UNSIGNED); + + postInit(); + } + + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + Tree.Kind treeKind = tree.getKind(); + if (treeKind == Tree.Kind.INT_LITERAL) { + int literalValue = (int) ((LiteralTree) tree).getValue(); + if (literalValue >= 0) { + type.replaceAnnotation(SIGNED_POSITIVE); + } else { + type.replaceAnnotation(SIGNEDNESS_GLB); + } + } else if (treeKind == Tree.Kind.LONG_LITERAL) { + long literalValue = (long) ((LiteralTree) tree).getValue(); + if (literalValue >= 0) { + type.replaceAnnotation(SIGNED_POSITIVE); + } else { + type.replaceAnnotation(SIGNEDNESS_GLB); + } + } else if (!isComputingAnnotatedTypeMirrorOfLhs()) { + addSignedPositiveAnnotation(tree, type); + } + super.addComputedTypeAnnotations(tree, type); + } + + /** + * Refines an integer expression to @SignedPositive if its value is within the signed positive + * range (i.e. its MSB is zero). Does not refine the type of cast expressions. + * + * @param tree an AST node, whose type may be refined + * @param type the type of the tree + */ + private void addSignedPositiveAnnotation(Tree tree, AnnotatedTypeMirror type) { + if (tree.getKind() == Tree.Kind.TYPE_CAST) { + return; + } + TypeMirror javaType = type.getUnderlyingType(); + TypeKind javaTypeKind = javaType.getKind(); + if (tree.getKind() == Tree.Kind.VARIABLE) { + return; + } + if (!(javaTypeKind == TypeKind.BYTE + || javaTypeKind == TypeKind.CHAR + || javaTypeKind == TypeKind.SHORT + || javaTypeKind == TypeKind.INT + || javaTypeKind == TypeKind.LONG)) { + return; + } + ValueAnnotatedTypeFactory valueFactory = getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotatedTypeMirror valueATM = valueFactory.getAnnotatedType(tree); + // These annotations are trusted rather than checked. Maybe have an option to + // disable using them? + if ((valueATM.hasAnnotation(INT_RANGE_FROM_NON_NEGATIVE) + || valueATM.hasAnnotation(INT_RANGE_FROM_POSITIVE)) + && type.hasAnnotation(SIGNED)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } else { + Range treeRange = ValueCheckerUtils.getPossibleValues(valueATM, valueFactory); + + if (treeRange != null) { + switch (javaType.getKind()) { + case BYTE: + case CHAR: + if (treeRange.isWithin(0, Byte.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + case SHORT: + if (treeRange.isWithin(0, Short.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + case INT: + if (treeRange.isWithin(0, Integer.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + case LONG: + if (treeRange.isWithin(0, Long.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + default: + // Nothing + } + } + } + } - /** The Serializable type mirror. */ - private final TypeMirror serializableTM = - elements.getTypeElement(Serializable.class.getCanonicalName()).asType(); + @Override + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + assert annos.size() == 1; - /** The Comparable type mirror. */ - private final TypeMirror comparableTM = - elements.getTypeElement(Comparable.class.getCanonicalName()).asType(); + AnnotationMirrorSet result = new AnnotationMirrorSet(); + if (TypeKindUtils.isFloatingPoint(widenedTypeKind)) { + result.add(SIGNED); + return result; + } + if (widenedTypeKind == TypeKind.CHAR) { + result.add(UNSIGNED); + return result; + } + if ((widenedTypeKind == TypeKind.INT || widenedTypeKind == TypeKind.LONG) + && typeKind == TypeKind.CHAR) { + result.add(SIGNED_POSITIVE); + return result; + } + return annos; + } - /** The Number type mirror. */ - private final TypeMirror numberTM = - elements.getTypeElement(Number.class.getCanonicalName()).asType(); + @Override + public AnnotationMirrorSet getNarrowedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { + assert annos.size() == 1; - /** A set containing just {@code @Signed}. */ - private final AnnotationMirrorSet SIGNED_SINGLETON = new AnnotationMirrorSet(SIGNED); + AnnotationMirrorSet result = new AnnotationMirrorSet(); - /** A set containing just {@code @Unsigned}. */ - private final AnnotationMirrorSet UNSIGNED_SINGLETON = new AnnotationMirrorSet(UNSIGNED); + if (narrowedTypeKind == TypeKind.CHAR) { + result.add(SIGNED); + return result; + } - /** - * Create a SignednessAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this type factory - */ - public SignednessAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + return annos; + } - addAliasedTypeAnnotation("jdk.jfr.Unsigned", UNSIGNED); + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new SignednessTreeAnnotator(this), super.createTreeAnnotator()); + } - postInit(); + @Override + public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { + if (TypesUtils.isCharType(tm)) { + return UNSIGNED_SINGLETON; + } else { + return SIGNED_SINGLETON; } - - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - Tree.Kind treeKind = tree.getKind(); - if (treeKind == Tree.Kind.INT_LITERAL) { - int literalValue = (int) ((LiteralTree) tree).getValue(); - if (literalValue >= 0) { - type.replaceAnnotation(SIGNED_POSITIVE); - } else { - type.replaceAnnotation(SIGNEDNESS_GLB); - } - } else if (treeKind == Tree.Kind.LONG_LITERAL) { - long literalValue = (long) ((LiteralTree) tree).getValue(); - if (literalValue >= 0) { - type.replaceAnnotation(SIGNED_POSITIVE); - } else { - type.replaceAnnotation(SIGNEDNESS_GLB); - } - } else if (!isComputingAnnotatedTypeMirrorOfLhs()) { - addSignedPositiveAnnotation(tree, type); - } - super.addComputedTypeAnnotations(tree, type); + } + + /** + * This TreeAnnotator ensures that: + * + *
    + *
  • boolean expressions are not given Unsigned or Signed annotations by {@link + * PropagationTreeAnnotator}, + *
  • shift results take on the type of their left operand, + *
  • the types of identifiers are refined based on the results of the Value Checker. + *
  • casts take types related to widening + *
+ */ + private class SignednessTreeAnnotator extends TreeAnnotator { + + public SignednessTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - /** - * Refines an integer expression to @SignedPositive if its value is within the signed positive - * range (i.e. its MSB is zero). Does not refine the type of cast expressions. - * - * @param tree an AST node, whose type may be refined - * @param type the type of the tree - */ - private void addSignedPositiveAnnotation(Tree tree, AnnotatedTypeMirror type) { - if (tree.getKind() == Tree.Kind.TYPE_CAST) { - return; - } - TypeMirror javaType = type.getUnderlyingType(); - TypeKind javaTypeKind = javaType.getKind(); - if (tree.getKind() == Tree.Kind.VARIABLE) { - return; - } - if (!(javaTypeKind == TypeKind.BYTE - || javaTypeKind == TypeKind.CHAR - || javaTypeKind == TypeKind.SHORT - || javaTypeKind == TypeKind.INT - || javaTypeKind == TypeKind.LONG)) { - return; - } - ValueAnnotatedTypeFactory valueFactory = getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotatedTypeMirror valueATM = valueFactory.getAnnotatedType(tree); - // These annotations are trusted rather than checked. Maybe have an option to - // disable using them? - if ((valueATM.hasAnnotation(INT_RANGE_FROM_NON_NEGATIVE) - || valueATM.hasAnnotation(INT_RANGE_FROM_POSITIVE)) - && type.hasAnnotation(SIGNED)) { + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + switch (tree.getKind()) { + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + TreePath path = getPath(tree); + if (path != null + && (SignednessShifts.isMaskedShiftEitherSignedness(tree, path) + || SignednessShifts.isCastedShiftEitherSignedness(tree, path))) { type.replaceAnnotation(SIGNED_POSITIVE); - } else { - Range treeRange = ValueCheckerUtils.getPossibleValues(valueATM, valueFactory); - - if (treeRange != null) { - switch (javaType.getKind()) { - case BYTE: - case CHAR: - if (treeRange.isWithin(0, Byte.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - case SHORT: - if (treeRange.isWithin(0, Short.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - case INT: - if (treeRange.isWithin(0, Integer.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - case LONG: - if (treeRange.isWithin(0, Long.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - default: - // Nothing - } - } - } + } else { + AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); + type.replaceAnnotations(lht.getAnnotations()); + } + break; + default: + // Do nothing + } + return null; } @Override - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - assert annos.size() == 1; - - AnnotationMirrorSet result = new AnnotationMirrorSet(); - if (TypeKindUtils.isFloatingPoint(widenedTypeKind)) { - result.add(SIGNED); - return result; - } - if (widenedTypeKind == TypeKind.CHAR) { - result.add(UNSIGNED); - return result; + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + if (TypesUtils.isCharType(TreeUtils.typeOf(tree.getExpression()))) { + type.replaceAnnotation(SIGNED); } - if ((widenedTypeKind == TypeKind.INT || widenedTypeKind == TypeKind.LONG) - && typeKind == TypeKind.CHAR) { - result.add(SIGNED_POSITIVE); - return result; - } - return annos; + } + return null; } @Override - public AnnotationMirrorSet getNarrowedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { - assert annos.size() == 1; - - AnnotationMirrorSet result = new AnnotationMirrorSet(); - - if (narrowedTypeKind == TypeKind.CHAR) { - result.add(SIGNED); - return result; + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + // Don't change the annotation on a cast with an explicit annotation. + if (TypesUtils.isCharType(type.getUnderlyingType())) { + type.replaceAnnotation(UNSIGNED); + } else if (type.getAnnotations().isEmpty() && !maybeIntegral(type)) { + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); + if ((type.getKind() != TypeKind.TYPEVAR || exprType.getKind() != TypeKind.TYPEVAR) + && !AnnotationUtils.containsSame(exprType.getEffectiveAnnotations(), UNSIGNED)) { + type.addAnnotation(SIGNED); } - - return annos; + } + log("SATF.visitTypeCast(%s, ...) final: %s%n", tree, type); + log("SATF: treeAnnotator=%s%n", treeAnnotator); + return null; } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - new SignednessTreeAnnotator(this), super.createTreeAnnotator()); + } + + /** + * Returns true if {@code type}'s underlying type might be integral: it is a number, char, or a + * supertype of them. + * + * @param type a type + * @return true if {@code type}'s underlying type might be integral + */ + public boolean maybeIntegral(AnnotatedTypeMirror type) { + + TypeKind kind = type.getKind(); + + switch (kind) { + case BOOLEAN: + return false; + case BYTE: + case SHORT: + case INT: + case LONG: + case CHAR: + return true; + case FLOAT: + case DOUBLE: + return false; + + case DECLARED: + case TYPEVAR: + case WILDCARD: + TypeMirror erasedType = types.erasure(type.getUnderlyingType()); + return (TypesUtils.isBoxedPrimitive(erasedType) + || TypesUtils.isObject(erasedType) + || TypesUtils.isErasedSubtype(numberTM, erasedType, types) + || TypesUtils.isErasedSubtype(serializableTM, erasedType, types) + || TypesUtils.isErasedSubtype(comparableTM, erasedType, types)); + + default: + return false; } - - @Override - public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { - if (TypesUtils.isCharType(tm)) { - return UNSIGNED_SINGLETON; - } else { - return SIGNED_SINGLETON; - } + } + + @Override + protected void adaptGetClassReturnTypeToReceiver( + AnnotatedExecutableType getClassType, AnnotatedTypeMirror receiverType, ExpressionTree tree) { + super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); + // Make the captured wildcard always @Signed, regardless of the declared type. + AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); + List typeArgs = returnAdt.getTypeArguments(); + AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); + classWildcardArg.getUpperBound().replaceAnnotation(SIGNED); + } + + @Override + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + TypeMirror underlying = type.getUnderlyingType(); + if (TypesUtils.isFloatingPrimitive(underlying) + || TypesUtils.isBoxedFloating(underlying) + || TypesUtils.isCharType(underlying)) { + // Floats are always signed and chars are always unsigned. + super.addAnnotationsFromDefaultForType(null, type); + } else { + super.addAnnotationsFromDefaultForType(element, type); } - + } + + /** + * Requires that, when two formal parameter types are annotated with {@code @PolySigned}, the two + * arguments must have the same signedness type annotation. + */ + // Not static because it references SIGNEDNESS_BOTTOM. + private class SignednessQualifierPolymorphism extends DefaultQualifierPolymorphism { /** - * This TreeAnnotator ensures that: + * Creates a {@link SignednessQualifierPolymorphism}. * - *
    - *
  • boolean expressions are not given Unsigned or Signed annotations by {@link - * PropagationTreeAnnotator}, - *
  • shift results take on the type of their left operand, - *
  • the types of identifiers are refined based on the results of the Value Checker. - *
  • casts take types related to widening - *
+ * @param env the processing environment + * @param factory the factory for the current checker */ - private class SignednessTreeAnnotator extends TreeAnnotator { - - public SignednessTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - switch (tree.getKind()) { - case LEFT_SHIFT: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - TreePath path = getPath(tree); - if (path != null - && (SignednessShifts.isMaskedShiftEitherSignedness(tree, path) - || SignednessShifts.isCastedShiftEitherSignedness( - tree, path))) { - type.replaceAnnotation(SIGNED_POSITIVE); - } else { - AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); - type.replaceAnnotations(lht.getAnnotations()); - } - break; - default: - // Do nothing - } - return null; - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - if (TypesUtils.isCharType(TreeUtils.typeOf(tree.getExpression()))) { - type.replaceAnnotation(SIGNED); - } - } - return null; - } - - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - // Don't change the annotation on a cast with an explicit annotation. - if (TypesUtils.isCharType(type.getUnderlyingType())) { - type.replaceAnnotation(UNSIGNED); - } else if (type.getAnnotations().isEmpty() && !maybeIntegral(type)) { - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); - if ((type.getKind() != TypeKind.TYPEVAR || exprType.getKind() != TypeKind.TYPEVAR) - && !AnnotationUtils.containsSame( - exprType.getEffectiveAnnotations(), UNSIGNED)) { - type.addAnnotation(SIGNED); - } - } - log("SATF.visitTypeCast(%s, ...) final: %s%n", tree, type); - log("SATF: treeAnnotator=%s%n", treeAnnotator); - return null; - } + public SignednessQualifierPolymorphism( + ProcessingEnvironment env, AnnotatedTypeFactory factory) { + super(env, factory); } /** - * Returns true if {@code type}'s underlying type might be integral: it is a number, char, or a - * supertype of them. + * Combines the two annotations. If they are comparable, return the lub. If they are + * incomparable, return @SignednessBottom. * - * @param type a type - * @return true if {@code type}'s underlying type might be integral + * @param polyQual the polymorphic qualifier + * @param a1 the first annotation to compare + * @param a2 the second annotation to compare + * @return the lub, unless the annotations are incomparable */ - public boolean maybeIntegral(AnnotatedTypeMirror type) { - - TypeKind kind = type.getKind(); - - switch (kind) { - case BOOLEAN: - return false; - case BYTE: - case SHORT: - case INT: - case LONG: - case CHAR: - return true; - case FLOAT: - case DOUBLE: - return false; - - case DECLARED: - case TYPEVAR: - case WILDCARD: - TypeMirror erasedType = types.erasure(type.getUnderlyingType()); - return (TypesUtils.isBoxedPrimitive(erasedType) - || TypesUtils.isObject(erasedType) - || TypesUtils.isErasedSubtype(numberTM, erasedType, types) - || TypesUtils.isErasedSubtype(serializableTM, erasedType, types) - || TypesUtils.isErasedSubtype(comparableTM, erasedType, types)); - - default: - return false; - } - } - @Override - protected void adaptGetClassReturnTypeToReceiver( - AnnotatedExecutableType getClassType, - AnnotatedTypeMirror receiverType, - ExpressionTree tree) { - super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); - // Make the captured wildcard always @Signed, regardless of the declared type. - AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); - List typeArgs = returnAdt.getTypeArguments(); - AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); - classWildcardArg.getUpperBound().replaceAnnotation(SIGNED); + protected AnnotationMirror combine( + AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == null) { + return a2; + } else if (a2 == null) { + return a1; + } else if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else if (qualHierarchy.isSubtypeQualifiersOnly(a1, a2)) { + return a2; + } else if (qualHierarchy.isSubtypeQualifiersOnly(a2, a1)) { + return a1; + } else + // The two annotations are incomparable + // TODO: Issue a warning at the proper code location. + // TODO: Returning bottom leads to obscure error messages. It would probably be + // better to issue a warning in this method, then return lub as usual. + return SIGNEDNESS_BOTTOM; } + } - @Override - protected void addAnnotationsFromDefaultForType( - @Nullable Element element, AnnotatedTypeMirror type) { - TypeMirror underlying = type.getUnderlyingType(); - if (TypesUtils.isFloatingPrimitive(underlying) - || TypesUtils.isBoxedFloating(underlying) - || TypesUtils.isCharType(underlying)) { - // Floats are always signed and chars are always unsigned. - super.addAnnotationsFromDefaultForType(null, type); - } else { - super.addAnnotationsFromDefaultForType(element, type); - } - } - - /** - * Requires that, when two formal parameter types are annotated with {@code @PolySigned}, the - * two arguments must have the same signedness type annotation. - */ - // Not static because it references SIGNEDNESS_BOTTOM. - private class SignednessQualifierPolymorphism extends DefaultQualifierPolymorphism { - /** - * Creates a {@link SignednessQualifierPolymorphism}. - * - * @param env the processing environment - * @param factory the factory for the current checker - */ - public SignednessQualifierPolymorphism( - ProcessingEnvironment env, AnnotatedTypeFactory factory) { - super(env, factory); - } - - /** - * Combines the two annotations. If they are comparable, return the lub. If they are - * incomparable, return @SignednessBottom. - * - * @param polyQual the polymorphic qualifier - * @param a1 the first annotation to compare - * @param a2 the second annotation to compare - * @return the lub, unless the annotations are incomparable - */ - @Override - protected AnnotationMirror combine( - AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == null) { - return a2; - } else if (a2 == null) { - return a1; - } else if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else if (qualHierarchy.isSubtypeQualifiersOnly(a1, a2)) { - return a2; - } else if (qualHierarchy.isSubtypeQualifiersOnly(a2, a1)) { - return a1; - } else - // The two annotations are incomparable - // TODO: Issue a warning at the proper code location. - // TODO: Returning bottom leads to obscure error messages. It would probably be - // better to issue a warning in this method, then return lub as usual. - return SIGNEDNESS_BOTTOM; - } - } - - @Override - protected QualifierPolymorphism createQualifierPolymorphism() { - return new SignednessQualifierPolymorphism(processingEnv, this); - } + @Override + protected QualifierPolymorphism createQualifierPolymorphism() { + return new SignednessQualifierPolymorphism(processingEnv, this); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java index a7823bd4568..8c6e21c6130 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.signedness; +import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.qual.StubFiles; -import java.util.Set; - /** * A type-checker that prevents mixing of unsigned and signed values, and prevents meaningless * operations on unsigned values. @@ -16,25 +15,25 @@ // Character and char are omitted here because they are always @Unsigned, and the user is not // allowed to write @Signed or @Unsigned on them. @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - byte.class, - short.class, - int.class, - long.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + byte.class, + short.class, + int.class, + long.class, }) @StubFiles({"junit-assertions.astub"}) public class SignednessChecker extends BaseTypeChecker { - /** Creates a new SignednessChecker. */ - public SignednessChecker() {} + /** Creates a new SignednessChecker. */ + public SignednessChecker() {} - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java index 69758906971..b26663dbbd5 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java @@ -8,7 +8,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.TreePath; - +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.TreePathUtil; @@ -16,8 +16,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.IPair; -import javax.lang.model.type.TypeKind; - /** * This file contains code to special-case shifts whose result does not depend on the MSB of the * first argument, due to subsequent masking or casts. @@ -26,279 +24,279 @@ */ public class SignednessShifts { - /** Do not instantiate SignednessShifts. */ - private SignednessShifts() { - throw new Error("Do not instantiate SignednessShifts"); + /** Do not instantiate SignednessShifts. */ + private SignednessShifts() { + throw new Error("Do not instantiate SignednessShifts"); + } + + /** + * Returns true iff the given tree node is a mask operation (& or |). + * + * @param tree a tree to test + * @return true iff node is a mask operation (& or |) + */ + private static boolean isMask(Tree tree) { + Tree.Kind kind = tree.getKind(); + + return kind == Tree.Kind.AND || kind == Tree.Kind.OR; + } + + // TODO: Return a TypeKind rather than a PrimitiveTypeTree? + /** + * Returns the type of a primitive cast, or null if the argument is not a cast to a primitive. + * + * @param tree a tree that might be a cast to a primitive + * @return type of a primitive cast, or null if not a cast to a primitive + */ + private static @Nullable PrimitiveTypeTree primitiveTypeCast(Tree tree) { + if (tree.getKind() != Tree.Kind.TYPE_CAST) { + return null; } - /** - * Returns true iff the given tree node is a mask operation (& or |). - * - * @param tree a tree to test - * @return true iff node is a mask operation (& or |) - */ - private static boolean isMask(Tree tree) { - Tree.Kind kind = tree.getKind(); + TypeCastTree cast = (TypeCastTree) tree; + Tree castType = cast.getType(); - return kind == Tree.Kind.AND || kind == Tree.Kind.OR; + Tree underlyingType; + if (castType.getKind() == Tree.Kind.ANNOTATED_TYPE) { + underlyingType = ((AnnotatedTypeTree) castType).getUnderlyingType(); + } else { + underlyingType = castType; } - // TODO: Return a TypeKind rather than a PrimitiveTypeTree? - /** - * Returns the type of a primitive cast, or null if the argument is not a cast to a primitive. - * - * @param tree a tree that might be a cast to a primitive - * @return type of a primitive cast, or null if not a cast to a primitive - */ - private static @Nullable PrimitiveTypeTree primitiveTypeCast(Tree tree) { - if (tree.getKind() != Tree.Kind.TYPE_CAST) { - return null; - } - - TypeCastTree cast = (TypeCastTree) tree; - Tree castType = cast.getType(); - - Tree underlyingType; - if (castType.getKind() == Tree.Kind.ANNOTATED_TYPE) { - underlyingType = ((AnnotatedTypeTree) castType).getUnderlyingType(); - } else { - underlyingType = castType; - } - - if (underlyingType.getKind() != Tree.Kind.PRIMITIVE_TYPE) { - return null; - } - - return (PrimitiveTypeTree) underlyingType; + if (underlyingType.getKind() != Tree.Kind.PRIMITIVE_TYPE) { + return null; } - /** - * Returns true iff the given tree is a literal. - * - * @param expr a tree to test - * @return true iff expr is a literal - */ - private static boolean isLiteral(ExpressionTree expr) { - return expr instanceof LiteralTree; + return (PrimitiveTypeTree) underlyingType; + } + + /** + * Returns true iff the given tree is a literal. + * + * @param expr a tree to test + * @return true iff expr is a literal + */ + private static boolean isLiteral(ExpressionTree expr) { + return expr instanceof LiteralTree; + } + + /** + * Returns the long value of an Integer or a Long + * + * @param obj either an Integer or a Long + * @return the long value of obj + */ + private static long getLong(Object obj) { + return ((Number) obj).longValue(); + } + + /** + * Given a masking operation of the form {@code expr & maskLit} or {@code expr | maskLit}, return + * true iff the masking operation results in the same output regardless of the value of the + * shiftAmount most significant bits of expr. This is if the shiftAmount most significant bits of + * mask are 0 for AND, and 1 for OR. For example, assuming that shiftAmount is 4, the following is + * true about AND and OR masks: + * + *

{@code expr & 0x0[anything] == 0x0[something] ;} + * + *

{@code expr | 0xF[anything] == 0xF[something] ;} + * + * @param maskKind the kind of mask (AND or OR) + * @param shiftAmountLit the LiteralTree whose value is shiftAmount + * @param maskLit the LiteralTree whose value is mask + * @param shiftedTypeKind the type of shift operation; int or long + * @return true iff the shiftAmount most significant bits of mask are 0 for AND, and 1 for OR + */ + private static boolean maskIgnoresMSB( + Tree.Kind maskKind, + LiteralTree shiftAmountLit, + LiteralTree maskLit, + TypeKind shiftedTypeKind) { + long shiftAmount = getLong(shiftAmountLit.getValue()); + + // Shift of zero is a nop + if (shiftAmount == 0) { + return true; } - /** - * Returns the long value of an Integer or a Long - * - * @param obj either an Integer or a Long - * @return the long value of obj - */ - private static long getLong(Object obj) { - return ((Number) obj).longValue(); + long mask = getLong(maskLit.getValue()); + // Shift the shiftAmount most significant bits to become the shiftAmount least significant + // bits, zeroing out the rest. + if (shiftedTypeKind == TypeKind.INT) { + mask <<= 32; + } + mask >>>= (64 - shiftAmount); + + if (maskKind == Tree.Kind.AND) { + // Check that the shiftAmount most significant bits of the mask were 0. + return mask == 0; + } else if (maskKind == Tree.Kind.OR) { + // Check that the shiftAmount most significant bits of the mask were 1. + return mask == (1 << shiftAmount) - 1; + } else { + throw new TypeSystemError("Invalid Masking Operation"); + } + } + + /** + * Given a casted right shift of the form {@code (type) (baseExpr >> shiftAmount)} or {@code + * (type) (baseExpr >>> shiftAmount)}, return true iff the expression's value is the same + * regardless of the type of right shift (signed or unsigned). This is true if the cast ignores + * the shiftAmount most significant bits of the shift result -- that is, if the cast ignores all + * the new bits that the right shift introduced on the left. + * + *

For example, the function returns true for + * + *

{@code (short) (myInt >> 16)}
+ * + * and for + * + *
{@code (short) (myInt >>> 16)}
+ * + * because these two expressions are guaranteed to have the same result. + * + * @param shiftTypeKind the kind of the type of the shift literal (BYTE, CHAR, SHORT, INT, or + * LONG) + * @param castTypeKind the kind of the cast target type (BYTE, CHAR, SHORT, INT, or LONG) + * @param shiftAmountLit the LiteralTree whose value is shiftAmount + * @return true iff introduced bits are discarded + */ + private static boolean castIgnoresMSB( + TypeKind shiftTypeKind, TypeKind castTypeKind, LiteralTree shiftAmountLit) { + + // Determine number of bits in the shift type, note shifts upcast to int. + // Also determine the shift amount as it is dependent on the shift type. + long shiftBits; + long shiftAmount; + switch (shiftTypeKind) { + case INT: + shiftBits = 32; + // When the LHS of the shift is an int, the 5 lower order bits of the RHS are used. + shiftAmount = 0x1F & getLong(shiftAmountLit.getValue()); + break; + case LONG: + shiftBits = 64; + // When the LHS of the shift is a long, the 6 lower order bits of the RHS are used. + shiftAmount = 0x3F & getLong(shiftAmountLit.getValue()); + break; + default: + throw new TypeSystemError("Invalid shift type"); } - /** - * Given a masking operation of the form {@code expr & maskLit} or {@code expr | maskLit}, - * return true iff the masking operation results in the same output regardless of the value of - * the shiftAmount most significant bits of expr. This is if the shiftAmount most significant - * bits of mask are 0 for AND, and 1 for OR. For example, assuming that shiftAmount is 4, the - * following is true about AND and OR masks: - * - *

{@code expr & 0x0[anything] == 0x0[something] ;} - * - *

{@code expr | 0xF[anything] == 0xF[something] ;} - * - * @param maskKind the kind of mask (AND or OR) - * @param shiftAmountLit the LiteralTree whose value is shiftAmount - * @param maskLit the LiteralTree whose value is mask - * @param shiftedTypeKind the type of shift operation; int or long - * @return true iff the shiftAmount most significant bits of mask are 0 for AND, and 1 for OR - */ - private static boolean maskIgnoresMSB( - Tree.Kind maskKind, - LiteralTree shiftAmountLit, - LiteralTree maskLit, - TypeKind shiftedTypeKind) { - long shiftAmount = getLong(shiftAmountLit.getValue()); - - // Shift of zero is a nop - if (shiftAmount == 0) { - return true; - } - - long mask = getLong(maskLit.getValue()); - // Shift the shiftAmount most significant bits to become the shiftAmount least significant - // bits, zeroing out the rest. - if (shiftedTypeKind == TypeKind.INT) { - mask <<= 32; - } - mask >>>= (64 - shiftAmount); - - if (maskKind == Tree.Kind.AND) { - // Check that the shiftAmount most significant bits of the mask were 0. - return mask == 0; - } else if (maskKind == Tree.Kind.OR) { - // Check that the shiftAmount most significant bits of the mask were 1. - return mask == (1 << shiftAmount) - 1; - } else { - throw new TypeSystemError("Invalid Masking Operation"); - } + // Determine number of bits in the cast type + long castBits; + switch (castTypeKind) { + case BYTE: + castBits = 8; + break; + case CHAR: + castBits = 8; + break; + case SHORT: + castBits = 16; + break; + case INT: + castBits = 32; + break; + case LONG: + castBits = 64; + break; + default: + throw new TypeSystemError("Invalid cast target"); } - /** - * Given a casted right shift of the form {@code (type) (baseExpr >> shiftAmount)} or {@code - * (type) (baseExpr >>> shiftAmount)}, return true iff the expression's value is the same - * regardless of the type of right shift (signed or unsigned). This is true if the cast ignores - * the shiftAmount most significant bits of the shift result -- that is, if the cast ignores all - * the new bits that the right shift introduced on the left. - * - *

For example, the function returns true for - * - *

{@code (short) (myInt >> 16)}
- * - * and for - * - *
{@code (short) (myInt >>> 16)}
- * - * because these two expressions are guaranteed to have the same result. - * - * @param shiftTypeKind the kind of the type of the shift literal (BYTE, CHAR, SHORT, INT, or - * LONG) - * @param castTypeKind the kind of the cast target type (BYTE, CHAR, SHORT, INT, or LONG) - * @param shiftAmountLit the LiteralTree whose value is shiftAmount - * @return true iff introduced bits are discarded - */ - private static boolean castIgnoresMSB( - TypeKind shiftTypeKind, TypeKind castTypeKind, LiteralTree shiftAmountLit) { - - // Determine number of bits in the shift type, note shifts upcast to int. - // Also determine the shift amount as it is dependent on the shift type. - long shiftBits; - long shiftAmount; - switch (shiftTypeKind) { - case INT: - shiftBits = 32; - // When the LHS of the shift is an int, the 5 lower order bits of the RHS are used. - shiftAmount = 0x1F & getLong(shiftAmountLit.getValue()); - break; - case LONG: - shiftBits = 64; - // When the LHS of the shift is a long, the 6 lower order bits of the RHS are used. - shiftAmount = 0x3F & getLong(shiftAmountLit.getValue()); - break; - default: - throw new TypeSystemError("Invalid shift type"); - } - - // Determine number of bits in the cast type - long castBits; - switch (castTypeKind) { - case BYTE: - castBits = 8; - break; - case CHAR: - castBits = 8; - break; - case SHORT: - castBits = 16; - break; - case INT: - castBits = 32; - break; - case LONG: - castBits = 64; - break; - default: - throw new TypeSystemError("Invalid cast target"); - } - - long bitsDiscarded = shiftBits - castBits; - - return shiftAmount <= bitsDiscarded || shiftAmount == 0; + long bitsDiscarded = shiftBits - castBits; + + return shiftAmount <= bitsDiscarded || shiftAmount == 0; + } + + /** + * Returns true if a right shift operation, {@code >>} or {@code >>>}, is masked with a masking + * operation of the form {@code shiftExpr & maskLit} or {@code shiftExpr | maskLit} such that the + * mask renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by destroying the bits + * duplicated into the shift result. For example, the following pairs of right shifts on {@code + * byte b} both produce the same results under any input, because of their masks: + * + *

{@code (b >> 4) & 0x0F == (b >>> 4) & 0x0F;} + * + *

{@code (b >> 4) | 0xF0 == (b >>> 4) | 0xF0;} + * + * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} + * @param path the path to {@code shiftExpr} + * @return true iff the right shift is masked such that a signed or unsigned right shift has the + * same effect + */ + /*package-private*/ static boolean isMaskedShiftEitherSignedness( + BinaryTree shiftExpr, TreePath path) { + IPair enclosingPair = TreePathUtil.enclosingNonParen(path); + // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr + Tree enclosing = enclosingPair.first; + // enclosingChild is a child of enclosing: shiftExpr or a parenthesized version of it. + @SuppressWarnings("interning:assignment.type.incompatible") // comparing AST nodes + @InternedDistinct Tree enclosingChild = enclosingPair.second; + + if (!isMask(enclosing)) { + return false; } - /** - * Returns true if a right shift operation, {@code >>} or {@code >>>}, is masked with a masking - * operation of the form {@code shiftExpr & maskLit} or {@code shiftExpr | maskLit} such that - * the mask renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by destroying - * the bits duplicated into the shift result. For example, the following pairs of right shifts - * on {@code byte b} both produce the same results under any input, because of their masks: - * - *

{@code (b >> 4) & 0x0F == (b >>> 4) & 0x0F;} - * - *

{@code (b >> 4) | 0xF0 == (b >>> 4) | 0xF0;} - * - * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} - * @param path the path to {@code shiftExpr} - * @return true iff the right shift is masked such that a signed or unsigned right shift has the - * same effect - */ - /*package-private*/ static boolean isMaskedShiftEitherSignedness( - BinaryTree shiftExpr, TreePath path) { - IPair enclosingPair = TreePathUtil.enclosingNonParen(path); - // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr - Tree enclosing = enclosingPair.first; - // enclosingChild is a child of enclosing: shiftExpr or a parenthesized version of it. - @SuppressWarnings("interning:assignment.type.incompatible") // comparing AST nodes - @InternedDistinct Tree enclosingChild = enclosingPair.second; - - if (!isMask(enclosing)) { - return false; - } - - BinaryTree maskExpr = (BinaryTree) enclosing; - ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); - - // Determine which child of maskExpr leads to shiftExpr. The other one is the mask. - ExpressionTree mask = - maskExpr.getRightOperand() == enclosingChild - ? maskExpr.getLeftOperand() - : maskExpr.getRightOperand(); - - // Strip away the parentheses from the mask if any exist - mask = TreeUtils.withoutParens(mask); - - if (!isLiteral(shiftAmountExpr) || !isLiteral(mask)) { - return false; - } - - LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; - LiteralTree maskLit = (LiteralTree) mask; - - return maskIgnoresMSB( - maskExpr.getKind(), shiftLit, maskLit, TreeUtils.typeOf(shiftExpr).getKind()); + BinaryTree maskExpr = (BinaryTree) enclosing; + ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); + + // Determine which child of maskExpr leads to shiftExpr. The other one is the mask. + ExpressionTree mask = + maskExpr.getRightOperand() == enclosingChild + ? maskExpr.getLeftOperand() + : maskExpr.getRightOperand(); + + // Strip away the parentheses from the mask if any exist + mask = TreeUtils.withoutParens(mask); + + if (!isLiteral(shiftAmountExpr) || !isLiteral(mask)) { + return false; + } + + LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; + LiteralTree maskLit = (LiteralTree) mask; + + return maskIgnoresMSB( + maskExpr.getKind(), shiftLit, maskLit, TreeUtils.typeOf(shiftExpr).getKind()); + } + + /** + * Returns true if a right shift operation, {@code >>} or {@code >>>}, is type casted such that + * the cast renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by discarding the + * bits duplicated into the shift result. For example, the following pair of right shifts on + * {@code short s} both produce the same results under any input, because of type casting: + * + *

{@code (byte)(s >> 8) == (byte)(b >>> 8);} + * + * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} + * @param path the path to {@code shiftExpr} + * @return true iff the right shift is type casted such that a signed or unsigned right shift has + * the same effect + */ + /*package-private*/ static boolean isCastedShiftEitherSignedness( + BinaryTree shiftExpr, TreePath path) { + // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr + Tree enclosing = TreePathUtil.enclosingNonParen(path).first; + + PrimitiveTypeTree castPrimitiveType = primitiveTypeCast(enclosing); + if (castPrimitiveType == null) { + return false; } + TypeKind castTypeKind = castPrimitiveType.getPrimitiveTypeKind(); + + // Determine the type of the shift result + TypeKind shiftTypeKind = TreeUtils.typeOf(shiftExpr).getKind(); - /** - * Returns true if a right shift operation, {@code >>} or {@code >>>}, is type casted such that - * the cast renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by discarding - * the bits duplicated into the shift result. For example, the following pair of right shifts on - * {@code short s} both produce the same results under any input, because of type casting: - * - *

{@code (byte)(s >> 8) == (byte)(b >>> 8);} - * - * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} - * @param path the path to {@code shiftExpr} - * @return true iff the right shift is type casted such that a signed or unsigned right shift - * has the same effect - */ - /*package-private*/ static boolean isCastedShiftEitherSignedness( - BinaryTree shiftExpr, TreePath path) { - // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr - Tree enclosing = TreePathUtil.enclosingNonParen(path).first; - - PrimitiveTypeTree castPrimitiveType = primitiveTypeCast(enclosing); - if (castPrimitiveType == null) { - return false; - } - TypeKind castTypeKind = castPrimitiveType.getPrimitiveTypeKind(); - - // Determine the type of the shift result - TypeKind shiftTypeKind = TreeUtils.typeOf(shiftExpr).getKind(); - - // Determine shift literal - ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); - if (!isLiteral(shiftAmountExpr)) { - return false; - } - LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; - - boolean result = castIgnoresMSB(shiftTypeKind, castTypeKind, shiftLit); - return result; + // Determine shift literal + ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); + if (!isLiteral(shiftAmountExpr)) { + return false; } + LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; + + boolean result = castIgnoresMSB(shiftTypeKind, castTypeKind, shiftLit); + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java index 38ce6a96736..6d61a021e16 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java @@ -6,7 +6,9 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.InterningVisitor; import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.checker.signedness.qual.PolySigned; @@ -22,10 +24,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.IPair; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - /** * The SignednessVisitor enforces the Signedness Checker rules. These rules are described in the * Checker Framework Manual. @@ -34,365 +32,341 @@ */ public class SignednessVisitor extends BaseTypeVisitor { - public SignednessVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * Returns true if an annotated type is annotated as {@link Unsigned} or {@link PolySigned} - * - * @param type the annotated type to be checked - * @return true if the annotated type is annotated as {@link Unsigned} or {@link PolySigned} - */ - private boolean hasUnsignedAnnotation(AnnotatedTypeMirror type) { - return type.hasAnnotation(Unsigned.class) || type.hasAnnotation(PolySigned.class); - } - - /** - * Returns true if an annotated type is annotated as {@link Signed} or {@link PolySigned} - * - * @param type the annotated type to be checked - * @return true if the annotated type is annotated as {@link Signed} or {@link PolySigned} - */ - private boolean hasSignedAnnotation(AnnotatedTypeMirror type) { - return type.hasAnnotation(Signed.class) || type.hasAnnotation(PolySigned.class); - } - - /** - * Enforces the following rules on binary operations involving Unsigned and Signed types: - * - *

    - *
  • Do not allow any Unsigned types or PolySigned types in {@literal {/, %}} operations. - *
  • Do not allow signed right shift {@literal {>>}} on an Unsigned type or a PolySigned - * type. - *
  • Do not allow unsigned right shift {@literal {>>>}} on a Signed type or a PolySigned - * type. - *
  • Allow any left shift {@literal {<<}}. - *
  • Do not allow non-equality comparisons {@literal {<, <=, >, >=}} on Unsigned types or - * PolySigned types. - *
  • Do not allow the mixing of Signed and Unsigned types. - *
- */ - @Override - public Void visitBinary(BinaryTree tree, Void p) { - // Used in diagnostic messages. - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); - - IPair argTypes = - atypeFactory.binaryTreeArgTypes(tree); - AnnotatedTypeMirror leftOpType = argTypes.first; - AnnotatedTypeMirror rightOpType = argTypes.second; - - Tree.Kind kind = tree.getKind(); - - switch (kind) { - case DIVIDE: - case REMAINDER: - if (hasUnsignedAnnotation(leftOpType)) { - checker.reportError( - leftOp, "operation.unsignedlhs", kind, leftOpType, rightOpType); - } else if (hasUnsignedAnnotation(rightOpType)) { - checker.reportError( - rightOp, "operation.unsignedrhs", kind, leftOpType, rightOpType); - } - break; - - case RIGHT_SHIFT: - if (hasUnsignedAnnotation(leftOpType) - && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) - && !SignednessShifts.isCastedShiftEitherSignedness( - tree, getCurrentPath())) { - checker.reportError(leftOp, "shift.signed", kind, leftOpType, rightOpType); - } - break; - - case UNSIGNED_RIGHT_SHIFT: - if (hasSignedAnnotation(leftOpType) - && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) - && !SignednessShifts.isCastedShiftEitherSignedness( - tree, getCurrentPath())) { - checker.reportError(leftOp, "shift.unsigned", kind, leftOpType, rightOpType); - } - break; - - case LEFT_SHIFT: - break; - - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case LESS_THAN: - case LESS_THAN_EQUAL: - if (hasUnsignedAnnotation(leftOpType)) { - checker.reportError(leftOp, "comparison.unsignedlhs", leftOpType, rightOpType); - } else if (hasUnsignedAnnotation(rightOpType)) { - checker.reportError(rightOp, "comparison.unsignedrhs", leftOpType, rightOpType); - } - break; - - case EQUAL_TO: - case NOT_EQUAL_TO: - if (!atypeFactory.maybeIntegral(leftOpType) - || !atypeFactory.maybeIntegral(rightOpType)) { - break; - } - if (leftOpType.hasEffectiveAnnotation(Unsigned.class) - && rightOpType.hasEffectiveAnnotation(Signed.class)) { - checker.reportError( - tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); - } else if (leftOpType.hasEffectiveAnnotation(Signed.class) - && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { - checker.reportError( - tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); - } - break; - - case PLUS: - if (TreeUtils.isStringConcatenation(tree)) { - // Note that leftOpType.getUnderlyingType() and rightOpType.getUnderlyingType() - // are always java.lang.String. Please refer to binaryTreeArgTypes for more - // details. - // Here, the original types of operands can be something different from string. - // For example, "123" + obj is a string concatenation in which the original type - // of the right operand is java.lang.Object. - TypeMirror leftOpTM = TreeUtils.typeOf(leftOp); - AnnotationMirror leftAnno = - leftOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); - TypeMirror rightOpTM = TreeUtils.typeOf(rightOp); - AnnotationMirror rightAnno = - rightOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); - if (!TypesUtils.isCharType(leftOpTM) - && !qualHierarchy.isSubtypeQualifiersOnly( - leftAnno, atypeFactory.SIGNED)) { - checker.reportError(leftOp, "unsigned.concat"); - } else if (!TypesUtils.isCharType(rightOpTM) - && !qualHierarchy.isSubtypeQualifiersOnly( - rightAnno, atypeFactory.SIGNED)) { - checker.reportError(rightOp, "unsigned.concat"); - } - break; - } - // Other plus binary trees should be handled in the default case. - // fall through - default: - if (leftOpType.hasEffectiveAnnotation(Unsigned.class) - && rightOpType.hasEffectiveAnnotation(Signed.class)) { - checker.reportError( - tree, "operation.mixed.unsignedlhs", kind, leftOpType, rightOpType); - } else if (leftOpType.hasEffectiveAnnotation(Signed.class) - && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { - checker.reportError( - tree, "operation.mixed.unsignedrhs", kind, leftOpType, rightOpType); - } - break; + public SignednessVisitor(BaseTypeChecker checker) { + super(checker); + } + + /** + * Returns true if an annotated type is annotated as {@link Unsigned} or {@link PolySigned} + * + * @param type the annotated type to be checked + * @return true if the annotated type is annotated as {@link Unsigned} or {@link PolySigned} + */ + private boolean hasUnsignedAnnotation(AnnotatedTypeMirror type) { + return type.hasAnnotation(Unsigned.class) || type.hasAnnotation(PolySigned.class); + } + + /** + * Returns true if an annotated type is annotated as {@link Signed} or {@link PolySigned} + * + * @param type the annotated type to be checked + * @return true if the annotated type is annotated as {@link Signed} or {@link PolySigned} + */ + private boolean hasSignedAnnotation(AnnotatedTypeMirror type) { + return type.hasAnnotation(Signed.class) || type.hasAnnotation(PolySigned.class); + } + + /** + * Enforces the following rules on binary operations involving Unsigned and Signed types: + * + *
    + *
  • Do not allow any Unsigned types or PolySigned types in {@literal {/, %}} operations. + *
  • Do not allow signed right shift {@literal {>>}} on an Unsigned type or a PolySigned type. + *
  • Do not allow unsigned right shift {@literal {>>>}} on a Signed type or a PolySigned type. + *
  • Allow any left shift {@literal {<<}}. + *
  • Do not allow non-equality comparisons {@literal {<, <=, >, >=}} on Unsigned types or + * PolySigned types. + *
  • Do not allow the mixing of Signed and Unsigned types. + *
+ */ + @Override + public Void visitBinary(BinaryTree tree, Void p) { + // Used in diagnostic messages. + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); + + IPair argTypes = + atypeFactory.binaryTreeArgTypes(tree); + AnnotatedTypeMirror leftOpType = argTypes.first; + AnnotatedTypeMirror rightOpType = argTypes.second; + + Tree.Kind kind = tree.getKind(); + + switch (kind) { + case DIVIDE: + case REMAINDER: + if (hasUnsignedAnnotation(leftOpType)) { + checker.reportError(leftOp, "operation.unsignedlhs", kind, leftOpType, rightOpType); + } else if (hasUnsignedAnnotation(rightOpType)) { + checker.reportError(rightOp, "operation.unsignedrhs", kind, leftOpType, rightOpType); } - return super.visitBinary(tree, p); - } + break; - // Ensure that method annotations are not written on methods they don't apply to. - // Copied from InterningVisitor - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; - int params = methElt.getParameters().size(); - if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { - checker.reportError( - tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); + case RIGHT_SHIFT: + if (hasUnsignedAnnotation(leftOpType) + && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) + && !SignednessShifts.isCastedShiftEitherSignedness(tree, getCurrentPath())) { + checker.reportError(leftOp, "shift.signed", kind, leftOpType, rightOpType); } + break; - return super.visitMethod(tree, p); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ExecutableElement methElt = TreeUtils.elementFromUse(tree); - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; - if (hasEqualsMethodAnno || InterningVisitor.isInvocationOfEquals(tree)) { - int params = methElt.getParameters().size(); - if (!(params == 1 || params == 2)) { - checker.reportError( - tree, - "invalid.method.annotation", - "@EqualsMethod", - "1 or 2", - methElt, - params); - } else { - AnnotatedTypeMirror leftOpType; - AnnotatedTypeMirror rightOpType; - if (params == 1) { - leftOpType = atypeFactory.getReceiverType(tree); - rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - } else if (params == 2) { - leftOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); - } else { - throw new BugInCF("Checked that params is 1 or 2"); - } - if (!atypeFactory.maybeIntegral(leftOpType) - || !atypeFactory.maybeIntegral(rightOpType)) { - // nothing to do - } else if (leftOpType.hasAnnotation(Unsigned.class) - && rightOpType.hasAnnotation(Signed.class)) { - checker.reportError( - tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); - } else if (leftOpType.hasAnnotation(Signed.class) - && rightOpType.hasAnnotation(Unsigned.class)) { - checker.reportError( - tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); - } - } - // Don't check against the annotated method declaration (which super would do). - return null; + case UNSIGNED_RIGHT_SHIFT: + if (hasSignedAnnotation(leftOpType) + && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) + && !SignednessShifts.isCastedShiftEitherSignedness(tree, getCurrentPath())) { + checker.reportError(leftOp, "shift.unsigned", kind, leftOpType, rightOpType); + } + break; + + case LEFT_SHIFT: + break; + + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case LESS_THAN: + case LESS_THAN_EQUAL: + if (hasUnsignedAnnotation(leftOpType)) { + checker.reportError(leftOp, "comparison.unsignedlhs", leftOpType, rightOpType); + } else if (hasUnsignedAnnotation(rightOpType)) { + checker.reportError(rightOp, "comparison.unsignedrhs", leftOpType, rightOpType); } + break; - return super.visitMethodInvocation(tree, p); + case EQUAL_TO: + case NOT_EQUAL_TO: + if (!atypeFactory.maybeIntegral(leftOpType) || !atypeFactory.maybeIntegral(rightOpType)) { + break; + } + if (leftOpType.hasEffectiveAnnotation(Unsigned.class) + && rightOpType.hasEffectiveAnnotation(Signed.class)) { + checker.reportError(tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); + } else if (leftOpType.hasEffectiveAnnotation(Signed.class) + && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { + checker.reportError(tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); + } + break; + + case PLUS: + if (TreeUtils.isStringConcatenation(tree)) { + // Note that leftOpType.getUnderlyingType() and rightOpType.getUnderlyingType() + // are always java.lang.String. Please refer to binaryTreeArgTypes for more + // details. + // Here, the original types of operands can be something different from string. + // For example, "123" + obj is a string concatenation in which the original type + // of the right operand is java.lang.Object. + TypeMirror leftOpTM = TreeUtils.typeOf(leftOp); + AnnotationMirror leftAnno = + leftOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); + TypeMirror rightOpTM = TreeUtils.typeOf(rightOp); + AnnotationMirror rightAnno = + rightOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); + if (!TypesUtils.isCharType(leftOpTM) + && !qualHierarchy.isSubtypeQualifiersOnly(leftAnno, atypeFactory.SIGNED)) { + checker.reportError(leftOp, "unsigned.concat"); + } else if (!TypesUtils.isCharType(rightOpTM) + && !qualHierarchy.isSubtypeQualifiersOnly(rightAnno, atypeFactory.SIGNED)) { + checker.reportError(rightOp, "unsigned.concat"); + } + break; + } + // Other plus binary trees should be handled in the default case. + // fall through + default: + if (leftOpType.hasEffectiveAnnotation(Unsigned.class) + && rightOpType.hasEffectiveAnnotation(Signed.class)) { + checker.reportError(tree, "operation.mixed.unsignedlhs", kind, leftOpType, rightOpType); + } else if (leftOpType.hasEffectiveAnnotation(Signed.class) + && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { + checker.reportError(tree, "operation.mixed.unsignedrhs", kind, leftOpType, rightOpType); + } + break; + } + return super.visitBinary(tree, p); + } + + // Ensure that method annotations are not written on methods they don't apply to. + // Copied from InterningVisitor + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; + int params = methElt.getParameters().size(); + if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); } - /** - * Returns a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if - * any. - * - * @param kind a tree kind - * @return a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if - * any - */ - private String kindWithoutAssignment(Tree.Kind kind) { - String result = kind.toString(); - if (result.endsWith("_ASSIGNMENT")) { - return result.substring(0, result.length() - "_ASSIGNMENT".length()); + return super.visitMethod(tree, p); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ExecutableElement methElt = TreeUtils.elementFromUse(tree); + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; + if (hasEqualsMethodAnno || InterningVisitor.isInvocationOfEquals(tree)) { + int params = methElt.getParameters().size(); + if (!(params == 1 || params == 2)) { + checker.reportError( + tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); + } else { + AnnotatedTypeMirror leftOpType; + AnnotatedTypeMirror rightOpType; + if (params == 1) { + leftOpType = atypeFactory.getReceiverType(tree); + rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + } else if (params == 2) { + leftOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); } else { - return result; + throw new BugInCF("Checked that params is 1 or 2"); } - } - - /** - * Enforces the following rules on compound assignments involving Unsigned and Signed types: - * - *
    - *
  • Do not allow any Unsigned types or PolySigned types in {@literal {/=, %=}} assignments. - *
  • Do not allow signed right shift {@literal {>>=}} to assign to an Unsigned type or a - * PolySigned type. - *
  • Do not allow unsigned right shift {@literal {>>>=}} to assign to a Signed type or a - * PolySigned type. - *
  • Allow any left shift {@literal {<<=}} assignment. - *
  • Do not allow mixing of Signed and Unsigned types. - *
- */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - - ExpressionTree var = tree.getVariable(); - ExpressionTree expr = tree.getExpression(); - - IPair argTypes = - atypeFactory.compoundAssignmentTreeArgTypes(tree); - AnnotatedTypeMirror varType = argTypes.first; - AnnotatedTypeMirror exprType = argTypes.second; - - Tree.Kind kind = tree.getKind(); - - switch (kind) { - case DIVIDE_ASSIGNMENT: - case REMAINDER_ASSIGNMENT: - if (hasUnsignedAnnotation(varType)) { - checker.reportError( - var, - "compound.assignment.unsigned.variable", - kindWithoutAssignment(kind), - varType, - exprType); - } else if (hasUnsignedAnnotation(exprType)) { - checker.reportError( - expr, - "compound.assignment.unsigned.expression", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; - - case RIGHT_SHIFT_ASSIGNMENT: - if (hasUnsignedAnnotation(varType)) { - checker.reportError( - var, - "compound.assignment.shift.signed", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; - - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - if (hasSignedAnnotation(varType)) { - checker.reportError( - var, - "compound.assignment.shift.unsigned", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; - - case LEFT_SHIFT_ASSIGNMENT: - break; - - case PLUS_ASSIGNMENT: - if (TreeUtils.isStringCompoundConcatenation(tree)) { - // Note that exprType.getUnderlyingType() is always java.lang.String. - // Please refer to compoundAssignmentTreeArgTypes for more details. - if (TypesUtils.isCharType(TreeUtils.typeOf(expr))) { - break; - } - AnnotationMirror exprAnno = - exprType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); - if (!qualHierarchy.isSubtypeQualifiersOnly(exprAnno, atypeFactory.SIGNED)) { - checker.reportError(tree.getExpression(), "unsigned.concat"); - } - break; - } - // Other plus binary trees should be handled in the default case. - // fall through - default: - if (varType.hasAnnotation(Unsigned.class) && exprType.hasAnnotation(Signed.class)) { - checker.reportError( - expr, - "compound.assignment.mixed.unsigned.variable", - kindWithoutAssignment(kind), - varType, - exprType); - } else if (varType.hasAnnotation(Signed.class) - && exprType.hasAnnotation(Unsigned.class)) { - checker.reportError( - expr, - "compound.assignment.mixed.unsigned.expression", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; + if (!atypeFactory.maybeIntegral(leftOpType) || !atypeFactory.maybeIntegral(rightOpType)) { + // nothing to do + } else if (leftOpType.hasAnnotation(Unsigned.class) + && rightOpType.hasAnnotation(Signed.class)) { + checker.reportError(tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); + } else if (leftOpType.hasAnnotation(Signed.class) + && rightOpType.hasAnnotation(Unsigned.class)) { + checker.reportError(tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); } - return super.visitCompoundAssignment(tree, p); + } + // Don't check against the annotated method declaration (which super would do). + return null; } - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - if (!atypeFactory.maybeIntegral(castType)) { - // If the cast is not a number or a char, then it is legal. - return true; + return super.visitMethodInvocation(tree, p); + } + + /** + * Returns a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if any. + * + * @param kind a tree kind + * @return a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if any + */ + private String kindWithoutAssignment(Tree.Kind kind) { + String result = kind.toString(); + if (result.endsWith("_ASSIGNMENT")) { + return result.substring(0, result.length() - "_ASSIGNMENT".length()); + } else { + return result; + } + } + + /** + * Enforces the following rules on compound assignments involving Unsigned and Signed types: + * + *
    + *
  • Do not allow any Unsigned types or PolySigned types in {@literal {/=, %=}} assignments. + *
  • Do not allow signed right shift {@literal {>>=}} to assign to an Unsigned type or a + * PolySigned type. + *
  • Do not allow unsigned right shift {@literal {>>>=}} to assign to a Signed type or a + * PolySigned type. + *
  • Allow any left shift {@literal {<<=}} assignment. + *
  • Do not allow mixing of Signed and Unsigned types. + *
+ */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + + ExpressionTree var = tree.getVariable(); + ExpressionTree expr = tree.getExpression(); + + IPair argTypes = + atypeFactory.compoundAssignmentTreeArgTypes(tree); + AnnotatedTypeMirror varType = argTypes.first; + AnnotatedTypeMirror exprType = argTypes.second; + + Tree.Kind kind = tree.getKind(); + + switch (kind) { + case DIVIDE_ASSIGNMENT: + case REMAINDER_ASSIGNMENT: + if (hasUnsignedAnnotation(varType)) { + checker.reportError( + var, + "compound.assignment.unsigned.variable", + kindWithoutAssignment(kind), + varType, + exprType); + } else if (hasUnsignedAnnotation(exprType)) { + checker.reportError( + expr, + "compound.assignment.unsigned.expression", + kindWithoutAssignment(kind), + varType, + exprType); + } + break; + + case RIGHT_SHIFT_ASSIGNMENT: + if (hasUnsignedAnnotation(varType)) { + checker.reportError( + var, + "compound.assignment.shift.signed", + kindWithoutAssignment(kind), + varType, + exprType); + } + break; + + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + if (hasSignedAnnotation(varType)) { + checker.reportError( + var, + "compound.assignment.shift.unsigned", + kindWithoutAssignment(kind), + varType, + exprType); } - return super.isTypeCastSafe(castType, exprType); + break; + + case LEFT_SHIFT_ASSIGNMENT: + break; + + case PLUS_ASSIGNMENT: + if (TreeUtils.isStringCompoundConcatenation(tree)) { + // Note that exprType.getUnderlyingType() is always java.lang.String. + // Please refer to compoundAssignmentTreeArgTypes for more details. + if (TypesUtils.isCharType(TreeUtils.typeOf(expr))) { + break; + } + AnnotationMirror exprAnno = + exprType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); + if (!qualHierarchy.isSubtypeQualifiersOnly(exprAnno, atypeFactory.SIGNED)) { + checker.reportError(tree.getExpression(), "unsigned.concat"); + } + break; + } + // Other plus binary trees should be handled in the default case. + // fall through + default: + if (varType.hasAnnotation(Unsigned.class) && exprType.hasAnnotation(Signed.class)) { + checker.reportError( + expr, + "compound.assignment.mixed.unsigned.variable", + kindWithoutAssignment(kind), + varType, + exprType); + } else if (varType.hasAnnotation(Signed.class) && exprType.hasAnnotation(Unsigned.class)) { + checker.reportError( + expr, + "compound.assignment.mixed.unsigned.expression", + kindWithoutAssignment(kind), + varType, + exprType); + } + break; } - - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(atypeFactory.SIGNED); + return super.visitCompoundAssignment(tree, p); + } + + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + if (!atypeFactory.maybeIntegral(castType)) { + // If the cast is not a number or a char, then it is legal. + return true; } + return super.isTypeCastSafe(castType, exprType); + } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(atypeFactory.SIGNED); + } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} } diff --git a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java index 93b9cc75519..a796fa0101d 100644 --- a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java @@ -1,36 +1,35 @@ package org.checkerframework.checker.tainting; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.element.AnnotationMirror; - /** Annotated type factory for the Tainting Checker. */ public class TaintingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@code @}{@link Untainted} annotation mirror. */ - private final AnnotationMirror UNTAINTED; + /** The {@code @}{@link Untainted} annotation mirror. */ + private final AnnotationMirror UNTAINTED; - /** A singleton set containing the {@code @}{@link Untainted} annotation mirror. */ - private final AnnotationMirrorSet setOfUntainted; + /** A singleton set containing the {@code @}{@link Untainted} annotation mirror. */ + private final AnnotationMirrorSet setOfUntainted; - /** - * Creates a {@link TaintingAnnotatedTypeFactory}. - * - * @param checker the tainting checker - */ - public TaintingAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.UNTAINTED = AnnotationBuilder.fromClass(getElementUtils(), Untainted.class); - this.setOfUntainted = AnnotationMirrorSet.singleton(UNTAINTED); - postInit(); - } + /** + * Creates a {@link TaintingAnnotatedTypeFactory}. + * + * @param checker the tainting checker + */ + public TaintingAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.UNTAINTED = AnnotationBuilder.fromClass(getElementUtils(), Untainted.class); + this.setOfUntainted = AnnotationMirrorSet.singleton(UNTAINTED); + postInit(); + } - @Override - protected AnnotationMirrorSet getEnumConstructorQualifiers() { - return setOfUntainted; - } + @Override + protected AnnotationMirrorSet getEnumConstructorQualifiers() { + return setOfUntainted; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java index 15f5937aca6..40f78e18ce5 100644 --- a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.tainting; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import javax.lang.model.element.ExecutableElement; - /** Visitor for the {@link TaintingChecker}. */ public class TaintingVisitor extends BaseTypeVisitor { - /** - * Creates a {@link TaintingVisitor}. - * - * @param checker the checker that uses this visitor - */ - public TaintingVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Creates a {@link TaintingVisitor}. + * + * @param checker the checker that uses this visitor + */ + public TaintingVisitor(BaseTypeChecker checker) { + super(checker); + } - /** - * Don't check that the constructor result is top. Checking that the super() or this() call is a - * subtype of the constructor result is sufficient. - */ - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} + /** + * Don't check that the constructor result is top. Checking that the super() or this() call is a + * subtype of the constructor result is sufficient. + */ + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java index c1f34a6ed2f..8784eb31be0 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java @@ -4,7 +4,19 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; - +import java.io.File; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.util.Elements; +import javax.tools.Diagnostic; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; @@ -43,21 +55,6 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; -import java.io.File; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.util.Elements; -import javax.tools.Diagnostic; - /** * Annotated type factory for the Units Checker. * @@ -69,653 +66,631 @@ * correct unit for the result. */ public class UnitsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private static final Class - unitsRelationsAnnoClass = org.checkerframework.checker.units.qual.UnitsRelations.class; - - protected final AnnotationMirror mixedUnits = - AnnotationBuilder.fromClass(elements, MixedUnits.class); - protected final AnnotationMirror TOP = - AnnotationBuilder.fromClass(elements, UnknownUnits.class); - protected final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, UnitsBottom.class); - - /** The UnitsMultiple.prefix argument/element. */ - private final ExecutableElement unitsMultiplePrefixElement = - TreeUtils.getMethod(UnitsMultiple.class, "prefix", 0, processingEnv); - - /** The UnitsMultiple.quantity argument/element. */ - private final ExecutableElement unitsMultipleQuantityElement = - TreeUtils.getMethod(UnitsMultiple.class, "quantity", 0, processingEnv); - - /** The UnitsRelations.value argument/element. */ - private final ExecutableElement unitsRelationsValueElement = - TreeUtils.getMethod( - org.checkerframework.checker.units.qual.UnitsRelations.class, - "value", - 0, - processingEnv); - - /** - * Map from canonical class name to the corresponding UnitsRelations instance. We use the string - * to prevent instantiating the UnitsRelations multiple times. - */ - private @MonotonicNonNull Map<@CanonicalName String, UnitsRelations> unitsRel; + private static final Class + unitsRelationsAnnoClass = org.checkerframework.checker.units.qual.UnitsRelations.class; + + protected final AnnotationMirror mixedUnits = + AnnotationBuilder.fromClass(elements, MixedUnits.class); + protected final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownUnits.class); + protected final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, UnitsBottom.class); + + /** The UnitsMultiple.prefix argument/element. */ + private final ExecutableElement unitsMultiplePrefixElement = + TreeUtils.getMethod(UnitsMultiple.class, "prefix", 0, processingEnv); + + /** The UnitsMultiple.quantity argument/element. */ + private final ExecutableElement unitsMultipleQuantityElement = + TreeUtils.getMethod(UnitsMultiple.class, "quantity", 0, processingEnv); + + /** The UnitsRelations.value argument/element. */ + private final ExecutableElement unitsRelationsValueElement = + TreeUtils.getMethod( + org.checkerframework.checker.units.qual.UnitsRelations.class, "value", 0, processingEnv); + + /** + * Map from canonical class name to the corresponding UnitsRelations instance. We use the string + * to prevent instantiating the UnitsRelations multiple times. + */ + private @MonotonicNonNull Map<@CanonicalName String, UnitsRelations> unitsRel; + + /** Map from canonical name of external qualifiers, to their Class. */ + private static final Map<@CanonicalName String, Class> externalQualsMap = + new HashMap<>(); + + private static final Map aliasMap = new HashMap<>(); + + public UnitsAnnotatedTypeFactory(BaseTypeChecker checker) { + // use true to enable flow inference, false to disable it + super(checker, false); + + this.postInit(); + } + + // In Units Checker, we always want to print out the Invisible Qualifiers (UnknownUnits), and to + // format the print out of qualifiers by removing Prefix.one + @Override + protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { + return new UnitsAnnotatedTypeFormatter(checker); + } + + // Converts all metric-prefixed units' alias annotations (eg @kg) into base unit annotations + // with prefix values (eg @g(Prefix.kilo)) + @Override + public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { + // Get the name of the aliased annotation + String aname = AnnotationUtils.annotationName(anno); + + // See if we already have a map from this aliased annotation to its corresponding base unit + // annotation + if (aliasMap.containsKey(aname)) { + // if so return it + return aliasMap.get(aname); + } - /** Map from canonical name of external qualifiers, to their Class. */ - private static final Map<@CanonicalName String, Class> externalQualsMap = - new HashMap<>(); + boolean built = false; + AnnotationMirror result = null; + // if not, look for the UnitsMultiple meta annotations of this aliased annotation + for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) { + // see if the meta annotation is UnitsMultiple + if (isUnitsMultiple(metaAnno)) { + // retrieve the Class of the base unit annotation + Name baseUnitAnnoClass = + AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement); + + // retrieve the SI Prefix of the aliased annotation + Prefix prefix = + AnnotationUtils.getElementValueEnum( + metaAnno, unitsMultiplePrefixElement, Prefix.class, Prefix.one); + + // Build a base unit annotation with the prefix applied + result = + UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix( + processingEnv, baseUnitAnnoClass, prefix); + + // TODO: assert that this annotation is a prefix multiple of a Unit that's in the + // supported type qualifiers list currently this breaks for externally loaded + // annotations if the order was an alias before a base annotation. + // assert isSupportedQualifier(result); + + built = true; + break; + } + } - private static final Map aliasMap = new HashMap<>(); + if (built) { + // aliases shouldn't have Prefix.one, but if it does then clean it up here + if (UnitsRelationsTools.getPrefix(result) == Prefix.one) { + result = removePrefix(result); + } - public UnitsAnnotatedTypeFactory(BaseTypeChecker checker) { - // use true to enable flow inference, false to disable it - super(checker, false); + // add this to the alias map + aliasMap.put(aname, result); + return result; + } - this.postInit(); + return super.canonicalAnnotation(anno); + } + + /** + * Returns a map from canonical class name to the corresponding UnitsRelations instance. + * + * @return a map from canonical class name to the corresponding UnitsRelations instance + */ + protected Map<@CanonicalName String, UnitsRelations> getUnitsRel() { + if (unitsRel == null) { + unitsRel = new HashMap<>(); + // Always add the default units relations, for the standard units. + // Other code adds more relations. + unitsRel.put( + UnitsRelationsDefault.class.getCanonicalName(), + new UnitsRelationsDefault().init(processingEnv)); + } + return unitsRel; + } + + @Override + protected AnnotationClassLoader createAnnotationClassLoader() { + // Use the UnitsAnnotationClassLoader instead of the default one + return new UnitsAnnotationClassLoader(checker); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Get all the loaded annotations. + Set> qualSet = getBundledTypeQualifiers(); + + // Load all the units specified on the command line. + loadAllExternalUnits(); + qualSet.addAll(externalQualsMap.values()); + + return qualSet; + } + + /** Loads all the externnal units specified on the command line. */ + private void loadAllExternalUnits() { + // load external individually named units + for (String qualName : checker.getStringsOption("units", ',')) { + if (!Signatures.isBinaryName(qualName)) { + throw new UserError("Malformed qualifier name \"%s\" in -Aunits", qualName); + } + loadExternalUnit(qualName); } - // In Units Checker, we always want to print out the Invisible Qualifiers (UnknownUnits), and to - // format the print out of qualifiers by removing Prefix.one - @Override - protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { - return new UnitsAnnotatedTypeFormatter(checker); + // load external directories of units + for (String directoryName : checker.getStringsOption("unitsDirs", ':')) { + if (!new File(directoryName).exists()) { + throw new UserError("Nonexistent directory in -AunitsDirs: " + directoryName); + } + loadExternalDirectory(directoryName); + } + } + + /** + * Loads and processes a single external units qualifier. + * + * @param annoName the name of a units qualifier + */ + private void loadExternalUnit(@BinaryName String annoName) { + // loadExternalAnnotationClass() returns null for alias units + Class loadedClass = loader.loadExternalAnnotationClass(annoName); + if (loadedClass != null) { + addUnitToExternalQualMap(loadedClass); } + } - // Converts all metric-prefixed units' alias annotations (eg @kg) into base unit annotations - // with prefix values (eg @g(Prefix.kilo)) - @Override - public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { - // Get the name of the aliased annotation - String aname = AnnotationUtils.annotationName(anno); - - // See if we already have a map from this aliased annotation to its corresponding base unit - // annotation - if (aliasMap.containsKey(aname)) { - // if so return it - return aliasMap.get(aname); - } + /** Loads and processes the units qualifiers from a single external directory. */ + private void loadExternalDirectory(String directoryName) { + Set> annoClassSet = + loader.loadExternalAnnotationClassesFromDirectory(directoryName); - boolean built = false; - AnnotationMirror result = null; - // if not, look for the UnitsMultiple meta annotations of this aliased annotation - for (AnnotationMirror metaAnno : - anno.getAnnotationType().asElement().getAnnotationMirrors()) { - // see if the meta annotation is UnitsMultiple - if (isUnitsMultiple(metaAnno)) { - // retrieve the Class of the base unit annotation - Name baseUnitAnnoClass = - AnnotationUtils.getElementValueClassName( - metaAnno, unitsMultipleQuantityElement); - - // retrieve the SI Prefix of the aliased annotation - Prefix prefix = - AnnotationUtils.getElementValueEnum( - metaAnno, unitsMultiplePrefixElement, Prefix.class, Prefix.one); - - // Build a base unit annotation with the prefix applied - result = - UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix( - processingEnv, baseUnitAnnoClass, prefix); - - // TODO: assert that this annotation is a prefix multiple of a Unit that's in the - // supported type qualifiers list currently this breaks for externally loaded - // annotations if the order was an alias before a base annotation. - // assert isSupportedQualifier(result); - - built = true; - break; - } + for (Class annoClass : annoClassSet) { + addUnitToExternalQualMap(annoClass); + } + } + + /** Adds the annotation class to the external qualifier map if it is not an alias annotation. */ + private void addUnitToExternalQualMap(Class annoClass) { + AnnotationMirror mirror = + UnitsRelationsTools.buildAnnoMirrorWithNoPrefix( + processingEnv, annoClass.getCanonicalName()); + + // if it is not an aliased annotation, add to external quals map if it isn't already in map + if (!isAliasedAnnotation(mirror)) { + String unitClassName = annoClass.getCanonicalName(); + if (!externalQualsMap.containsKey(unitClassName)) { + externalQualsMap.put(unitClassName, annoClass); + } + } + // if it is an aliased annotation + else { + // ensure it has a base unit + @CanonicalName Name baseUnitClass = getBaseUnitAnno(mirror); + if (baseUnitClass != null) { + // if the base unit isn't already added, add that first + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 + @DotSeparatedIdentifiers String baseUnitClassName = baseUnitClass.toString(); + if (!externalQualsMap.containsKey(baseUnitClassName)) { + loadExternalUnit(baseUnitClassName); } - if (built) { - // aliases shouldn't have Prefix.one, but if it does then clean it up here - if (UnitsRelationsTools.getPrefix(result) == Prefix.one) { - result = removePrefix(result); - } + // then add the aliased annotation to the alias map + // TODO: refactor so we can directly add to alias map, skipping the assert check in + // canonicalAnnotation. + canonicalAnnotation(mirror); + } else { + // error: somehow the aliased annotation has @UnitsMultiple meta annotation, but no + // base class defined in that meta annotation + // TODO: error abort + } + } - // add this to the alias map - aliasMap.put(aname, result); - return result; - } + // process the units annotation and add its corresponding units relations class + addUnitsRelations(annoClass); + } + + private boolean isAliasedAnnotation(AnnotationMirror anno) { + // loop through the meta annotations of the annotation, look for UnitsMultiple + for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) { + // see if the meta annotation is UnitsMultiple + if (isUnitsMultiple(metaAnno)) { + // TODO: does every alias have to have Prefix? + return true; + } + } - return super.canonicalAnnotation(anno); + // if we are unable to find UnitsMultiple meta annotation, then this is not an Aliased + // Annotation + return false; + } + + /** + * Return the name of the given annotation, if it is meta-annotated with UnitsMultiple; otherwise + * return null. + * + * @param anno the annotation to examine + * @return the annotation's name, if it is meta-annotated with UnitsMultiple; otherwise null + */ + private @Nullable @CanonicalName Name getBaseUnitAnno(AnnotationMirror anno) { + // loop through the meta annotations of the annotation, look for UnitsMultiple + for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) { + // see if the meta annotation is UnitsMultiple + if (isUnitsMultiple(metaAnno)) { + // TODO: does every alias have to have Prefix? + // Retrieve the base unit annotation. + Name baseUnitAnnoClass = + AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement); + return baseUnitAnnoClass; + } } + return null; + } + + /** + * Returns true if {@code metaAnno} is {@link UnitsMultiple}. + * + * @param metaAnno an annotation mirror + * @return true if {@code metaAnno} is {@link UnitsMultiple} + */ + private boolean isUnitsMultiple(AnnotationMirror metaAnno) { + return areSameByClass(metaAnno, UnitsMultiple.class); + } + + /** A class loader for looking up annotations. */ + private static final ClassLoader CLASSLOADER = + InternalUtils.getClassLoaderForClass(AnnotationUtils.class); + + /** + * Look for an @UnitsRelations annotation on the qualifier and add it to the list of + * UnitsRelations. + * + * @param qual the qualifier to investigate + */ + private void addUnitsRelations(Class qual) { + AnnotationMirror am = AnnotationBuilder.fromClass(elements, qual); + + for (AnnotationMirror ama : am.getAnnotationType().asElement().getAnnotationMirrors()) { + if (areSameByClass(ama, unitsRelationsAnnoClass)) { + String theclassname = + AnnotationUtils.getElementValueClassName(ama, unitsRelationsValueElement).toString(); + if (!Signatures.isClassGetName(theclassname)) { + throw new UserError( + "Malformed class name \"%s\" should be in ClassGetName format in" + " annotation %s", + theclassname, ama); + } + Class valueElement; + try { + valueElement = Class.forName(theclassname, true, CLASSLOADER); + } catch (ClassNotFoundException e) { + String msg = + String.format( + "Could not load class '%s' for field 'value' in annotation %s", + theclassname, ama); + throw new UserError(msg, e); + } + Class unitsRelationsClass; + try { + unitsRelationsClass = valueElement.asSubclass(UnitsRelations.class); + } catch (ClassCastException ex) { + throw new UserError( + "Invalid @UnitsRelations meta-annotation found in %s. " + + "@UnitsRelations value %s is not a subclass of " + + "org.checkerframework.checker.units.UnitsRelations.", + qual, ama); + } + String classname = unitsRelationsClass.getCanonicalName(); - /** - * Returns a map from canonical class name to the corresponding UnitsRelations instance. - * - * @return a map from canonical class name to the corresponding UnitsRelations instance - */ - protected Map<@CanonicalName String, UnitsRelations> getUnitsRel() { - if (unitsRel == null) { - unitsRel = new HashMap<>(); - // Always add the default units relations, for the standard units. - // Other code adds more relations. + if (!getUnitsRel().containsKey(classname)) { + try { unitsRel.put( - UnitsRelationsDefault.class.getCanonicalName(), - new UnitsRelationsDefault().init(processingEnv)); + classname, + unitsRelationsClass.getDeclaredConstructor().newInstance().init(processingEnv)); + } catch (Throwable e) { + throw new TypeSystemError("Throwable when instantiating UnitsRelations", e); + } } - return unitsRel; + } + } + } + + @Override + public TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because it includes PropagationTreeAnnotator which + // is incorrect. + return new ListTreeAnnotator( + new UnitsPropagationTreeAnnotator(this), + new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), + new UnitsTreeAnnotator(this)); + } + + private static class UnitsPropagationTreeAnnotator extends PropagationTreeAnnotator { + + public UnitsPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } + // Handled completely by UnitsTreeAnnotator @Override - protected AnnotationClassLoader createAnnotationClassLoader() { - // Use the UnitsAnnotationClassLoader instead of the default one - return new UnitsAnnotationClassLoader(checker); + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + return null; } + // Handled completely by UnitsTreeAnnotator @Override - protected Set> createSupportedTypeQualifiers() { - // Get all the loaded annotations. - Set> qualSet = getBundledTypeQualifiers(); - - // Load all the units specified on the command line. - loadAllExternalUnits(); - qualSet.addAll(externalQualsMap.values()); - - return qualSet; + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + return null; } + } - /** Loads all the externnal units specified on the command line. */ - private void loadAllExternalUnits() { - // load external individually named units - for (String qualName : checker.getStringsOption("units", ',')) { - if (!Signatures.isBinaryName(qualName)) { - throw new UserError("Malformed qualifier name \"%s\" in -Aunits", qualName); - } - loadExternalUnit(qualName); - } + /** A class for adding annotations based on tree. */ + private class UnitsTreeAnnotator extends TreeAnnotator { - // load external directories of units - for (String directoryName : checker.getStringsOption("unitsDirs", ':')) { - if (!new File(directoryName).exists()) { - throw new UserError("Nonexistent directory in -AunitsDirs: " + directoryName); - } - loadExternalDirectory(directoryName); - } + UnitsTreeAnnotator(UnitsAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - /** - * Loads and processes a single external units qualifier. - * - * @param annoName the name of a units qualifier - */ - private void loadExternalUnit(@BinaryName String annoName) { - // loadExternalAnnotationClass() returns null for alias units - Class loadedClass = loader.loadExternalAnnotationClass(annoName); - if (loadedClass != null) { - addUnitToExternalQualMap(loadedClass); + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rht = getAnnotatedType(tree.getRightOperand()); + Tree.Kind kind = tree.getKind(); + + // Remove Prefix.one + if (UnitsRelationsTools.getPrefix(lht) == Prefix.one) { + lht = UnitsRelationsTools.removePrefix(elements, lht); + } + if (UnitsRelationsTools.getPrefix(rht) == Prefix.one) { + rht = UnitsRelationsTools.removePrefix(elements, rht); + } + + AnnotationMirror bestres = null; + for (UnitsRelations ur : getUnitsRel().values()) { + AnnotationMirror res = useUnitsRelation(kind, ur, lht, rht); + + if (bestres != null && res != null && !bestres.equals(res)) { + checker.message( + Diagnostic.Kind.WARNING, + "UnitsRelation mismatch, taking neither! Previous: " + + bestres + + " and current: " + + res); + return null; // super.visitBinary(tree, type); } - } - /** Loads and processes the units qualifiers from a single external directory. */ - private void loadExternalDirectory(String directoryName) { - Set> annoClassSet = - loader.loadExternalAnnotationClassesFromDirectory(directoryName); - - for (Class annoClass : annoClassSet) { - addUnitToExternalQualMap(annoClass); + if (res != null) { + bestres = res; } - } - - /** Adds the annotation class to the external qualifier map if it is not an alias annotation. */ - private void addUnitToExternalQualMap(Class annoClass) { - AnnotationMirror mirror = - UnitsRelationsTools.buildAnnoMirrorWithNoPrefix( - processingEnv, annoClass.getCanonicalName()); - - // if it is not an aliased annotation, add to external quals map if it isn't already in map - if (!isAliasedAnnotation(mirror)) { - String unitClassName = annoClass.getCanonicalName(); - if (!externalQualsMap.containsKey(unitClassName)) { - externalQualsMap.put(unitClassName, annoClass); + } + + if (bestres != null) { + type.replaceAnnotation(bestres); + } else { + // If none of the units relations classes could resolve the units, then apply + // default rules. + + switch (kind) { + case MINUS: + case PLUS: + if (lht.getAnnotations().equals(rht.getAnnotations())) { + // The sum or difference has the same units as both operands. + type.replaceAnnotations(lht.getAnnotations()); + } else { + // otherwise it results in mixed + type.replaceAnnotation(mixedUnits); } - } - // if it is an aliased annotation - else { - // ensure it has a base unit - @CanonicalName Name baseUnitClass = getBaseUnitAnno(mirror); - if (baseUnitClass != null) { - // if the base unit isn't already added, add that first - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 - @DotSeparatedIdentifiers String baseUnitClassName = baseUnitClass.toString(); - if (!externalQualsMap.containsKey(baseUnitClassName)) { - loadExternalUnit(baseUnitClassName); - } - - // then add the aliased annotation to the alias map - // TODO: refactor so we can directly add to alias map, skipping the assert check in - // canonicalAnnotation. - canonicalAnnotation(mirror); + break; + case DIVIDE: + if (lht.getAnnotations().equals(rht.getAnnotations())) { + // If the units of the division match, return TOP + type.replaceAnnotation(TOP); + } else if (UnitsRelationsTools.hasNoUnits(rht)) { + // any unit divided by a scalar keeps that unit + type.replaceAnnotations(lht.getAnnotations()); } else { - // error: somehow the aliased annotation has @UnitsMultiple meta annotation, but no - // base class defined in that meta annotation - // TODO: error abort + // Either UnitsRelationsTools.hasNoUnits(lht), which is a scalar divided + // by any unit returns mixed. + // Or else it is a division of two units that have no defined relations + // from a relations class return mixed. + type.replaceAnnotation(mixedUnits); } - } - - // process the units annotation and add its corresponding units relations class - addUnitsRelations(annoClass); - } - - private boolean isAliasedAnnotation(AnnotationMirror anno) { - // loop through the meta annotations of the annotation, look for UnitsMultiple - for (AnnotationMirror metaAnno : - anno.getAnnotationType().asElement().getAnnotationMirrors()) { - // see if the meta annotation is UnitsMultiple - if (isUnitsMultiple(metaAnno)) { - // TODO: does every alias have to have Prefix? - return true; + break; + case MULTIPLY: + if (UnitsRelationsTools.hasNoUnits(lht)) { + // any unit multiplied by a scalar keeps the unit + type.replaceAnnotations(rht.getAnnotations()); + } else if (UnitsRelationsTools.hasNoUnits(rht)) { + // any scalar multiplied by a unit becomes the unit + type.replaceAnnotations(lht.getAnnotations()); + } else { + // else it is a multiplication of two units that have no defined + // relations from a relations class return mixed. + type.replaceAnnotation(mixedUnits); } + break; + case REMAINDER: + // in modulo operation, it always returns the left unit regardless of what + // it is (unknown, or some unit) + type.replaceAnnotations(lht.getAnnotations()); + break; + default: + // Placeholders for unhandled binary operations + // Do nothing } + } - // if we are unable to find UnitsMultiple meta annotation, then this is not an Aliased - // Annotation - return false; + return null; } - /** - * Return the name of the given annotation, if it is meta-annotated with UnitsMultiple; - * otherwise return null. - * - * @param anno the annotation to examine - * @return the annotation's name, if it is meta-annotated with UnitsMultiple; otherwise null - */ - private @Nullable @CanonicalName Name getBaseUnitAnno(AnnotationMirror anno) { - // loop through the meta annotations of the annotation, look for UnitsMultiple - for (AnnotationMirror metaAnno : - anno.getAnnotationType().asElement().getAnnotationMirrors()) { - // see if the meta annotation is UnitsMultiple - if (isUnitsMultiple(metaAnno)) { - // TODO: does every alias have to have Prefix? - // Retrieve the base unit annotation. - Name baseUnitAnnoClass = - AnnotationUtils.getElementValueClassName( - metaAnno, unitsMultipleQuantityElement); - return baseUnitAnnoClass; - } - } - return null; - } + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + ExpressionTree var = tree.getVariable(); + AnnotatedTypeMirror varType = getAnnotatedType(var); - /** - * Returns true if {@code metaAnno} is {@link UnitsMultiple}. - * - * @param metaAnno an annotation mirror - * @return true if {@code metaAnno} is {@link UnitsMultiple} - */ - private boolean isUnitsMultiple(AnnotationMirror metaAnno) { - return areSameByClass(metaAnno, UnitsMultiple.class); + type.replaceAnnotations(varType.getAnnotations()); + return null; } - /** A class loader for looking up annotations. */ - private static final ClassLoader CLASSLOADER = - InternalUtils.getClassLoaderForClass(AnnotationUtils.class); - - /** - * Look for an @UnitsRelations annotation on the qualifier and add it to the list of - * UnitsRelations. - * - * @param qual the qualifier to investigate - */ - private void addUnitsRelations(Class qual) { - AnnotationMirror am = AnnotationBuilder.fromClass(elements, qual); - - for (AnnotationMirror ama : am.getAnnotationType().asElement().getAnnotationMirrors()) { - if (areSameByClass(ama, unitsRelationsAnnoClass)) { - String theclassname = - AnnotationUtils.getElementValueClassName(ama, unitsRelationsValueElement) - .toString(); - if (!Signatures.isClassGetName(theclassname)) { - throw new UserError( - "Malformed class name \"%s\" should be in ClassGetName format in" - + " annotation %s", - theclassname, ama); - } - Class valueElement; - try { - valueElement = Class.forName(theclassname, true, CLASSLOADER); - } catch (ClassNotFoundException e) { - String msg = - String.format( - "Could not load class '%s' for field 'value' in annotation %s", - theclassname, ama); - throw new UserError(msg, e); - } - Class unitsRelationsClass; - try { - unitsRelationsClass = valueElement.asSubclass(UnitsRelations.class); - } catch (ClassCastException ex) { - throw new UserError( - "Invalid @UnitsRelations meta-annotation found in %s. " - + "@UnitsRelations value %s is not a subclass of " - + "org.checkerframework.checker.units.UnitsRelations.", - qual, ama); - } - String classname = unitsRelationsClass.getCanonicalName(); - - if (!getUnitsRel().containsKey(classname)) { - try { - unitsRel.put( - classname, - unitsRelationsClass - .getDeclaredConstructor() - .newInstance() - .init(processingEnv)); - } catch (Throwable e) { - throw new TypeSystemError("Throwable when instantiating UnitsRelations", e); - } - } - } + private @Nullable AnnotationMirror useUnitsRelation( + Tree.Kind kind, UnitsRelations ur, AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + + if (ur != null) { + switch (kind) { + case DIVIDE: + return ur.division(lht, rht); + case MULTIPLY: + return ur.multiplication(lht, rht); + default: + // Do nothing } + } + return null; + } + } + + /** Set the Bottom qualifier as the bottom of the hierarchy. */ + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new UnitsQualifierHierarchy(); + } + + /** Qualifier Hierarchy for the Units Checker. */ + @AnnotatedFor("nullness") + protected class UnitsQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + /** Constructor. */ + public UnitsQualifierHierarchy() { + super( + UnitsAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + UnitsAnnotatedTypeFactory.this); } @Override - public TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because it includes PropagationTreeAnnotator which - // is incorrect. - return new ListTreeAnnotator( - new UnitsPropagationTreeAnnotator(this), - new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), - new UnitsTreeAnnotator(this)); + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization UnitsQualifierHierarchy this, + Collection> qualifierClasses) { + return new UnitsQualifierKindHierarchy(qualifierClasses, elements); } - private static class UnitsPropagationTreeAnnotator extends PropagationTreeAnnotator { - - public UnitsPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - // Handled completely by UnitsTreeAnnotator - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - return null; - } - - // Handled completely by UnitsTreeAnnotator - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - return null; - } + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + return AnnotationUtils.areSame(subAnno, superAnno); } - /** A class for adding annotations based on tree. */ - private class UnitsTreeAnnotator extends TreeAnnotator { - - UnitsTreeAnnotator(UnitsAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); - AnnotatedTypeMirror rht = getAnnotatedType(tree.getRightOperand()); - Tree.Kind kind = tree.getKind(); - - // Remove Prefix.one - if (UnitsRelationsTools.getPrefix(lht) == Prefix.one) { - lht = UnitsRelationsTools.removePrefix(elements, lht); - } - if (UnitsRelationsTools.getPrefix(rht) == Prefix.one) { - rht = UnitsRelationsTools.removePrefix(elements, rht); - } - - AnnotationMirror bestres = null; - for (UnitsRelations ur : getUnitsRel().values()) { - AnnotationMirror res = useUnitsRelation(kind, ur, lht, rht); - - if (bestres != null && res != null && !bestres.equals(res)) { - checker.message( - Diagnostic.Kind.WARNING, - "UnitsRelation mismatch, taking neither! Previous: " - + bestres - + " and current: " - + res); - return null; // super.visitBinary(tree, type); - } - - if (res != null) { - bestres = res; - } - } - - if (bestres != null) { - type.replaceAnnotation(bestres); - } else { - // If none of the units relations classes could resolve the units, then apply - // default rules. - - switch (kind) { - case MINUS: - case PLUS: - if (lht.getAnnotations().equals(rht.getAnnotations())) { - // The sum or difference has the same units as both operands. - type.replaceAnnotations(lht.getAnnotations()); - } else { - // otherwise it results in mixed - type.replaceAnnotation(mixedUnits); - } - break; - case DIVIDE: - if (lht.getAnnotations().equals(rht.getAnnotations())) { - // If the units of the division match, return TOP - type.replaceAnnotation(TOP); - } else if (UnitsRelationsTools.hasNoUnits(rht)) { - // any unit divided by a scalar keeps that unit - type.replaceAnnotations(lht.getAnnotations()); - } else { - // Either UnitsRelationsTools.hasNoUnits(lht), which is a scalar divided - // by any unit returns mixed. - // Or else it is a division of two units that have no defined relations - // from a relations class return mixed. - type.replaceAnnotation(mixedUnits); - } - break; - case MULTIPLY: - if (UnitsRelationsTools.hasNoUnits(lht)) { - // any unit multiplied by a scalar keeps the unit - type.replaceAnnotations(rht.getAnnotations()); - } else if (UnitsRelationsTools.hasNoUnits(rht)) { - // any scalar multiplied by a unit becomes the unit - type.replaceAnnotations(lht.getAnnotations()); - } else { - // else it is a multiplication of two units that have no defined - // relations from a relations class return mixed. - type.replaceAnnotation(mixedUnits); - } - break; - case REMAINDER: - // in modulo operation, it always returns the left unit regardless of what - // it is (unknown, or some unit) - type.replaceAnnotations(lht.getAnnotations()); - break; - default: - // Placeholders for unhandled binary operations - // Do nothing - } - } - - return null; - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - ExpressionTree var = tree.getVariable(); - AnnotatedTypeMirror varType = getAnnotatedType(var); - - type.replaceAnnotations(varType.getAnnotations()); - return null; - } - - private @Nullable AnnotationMirror useUnitsRelation( - Tree.Kind kind, - UnitsRelations ur, - AnnotatedTypeMirror lht, - AnnotatedTypeMirror rht) { - - if (ur != null) { - switch (kind) { - case DIVIDE: - return ur.division(lht, rht); - case MULTIPLY: - return ur.multiplication(lht, rht); - default: - // Do nothing - } - } - return null; + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.isBottom()) { + return a2; + } else if (qualifierKind2.isBottom()) { + return a1; + } else if (qualifierKind1 == qualifierKind2) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + @SuppressWarnings({ + "nullness:assignment.type.incompatible" // Every qualifier kind is a + // key in directSuperQualifierMap. + }) + @NonNull AnnotationMirror lub = + ((UnitsQualifierKindHierarchy) qualifierKindHierarchy) + .directSuperQualifierMap.get(qualifierKind1); + return lub; } + } + throw new TypeSystemError("Unexpected QualifierKinds: %s %s", qualifierKind1, qualifierKind2); } - /** Set the Bottom qualifier as the bottom of the hierarchy. */ @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new UnitsQualifierHierarchy(); + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + return UnitsAnnotatedTypeFactory.this.BOTTOM; } + } - /** Qualifier Hierarchy for the Units Checker. */ - @AnnotatedFor("nullness") - protected class UnitsQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /** Constructor. */ - public UnitsQualifierHierarchy() { - super( - UnitsAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - UnitsAnnotatedTypeFactory.this); - } + /** UnitsQualifierKindHierarchy. */ + @AnnotatedFor("nullness") + protected static class UnitsQualifierKindHierarchy extends DefaultQualifierKindHierarchy { - @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization UnitsQualifierHierarchy this, - Collection> qualifierClasses) { - return new UnitsQualifierKindHierarchy(qualifierClasses, elements); - } - - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - return AnnotationUtils.areSame(subAnno, superAnno); - } - - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.isBottom()) { - return a2; - } else if (qualifierKind2.isBottom()) { - return a1; - } else if (qualifierKind1 == qualifierKind2) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - @SuppressWarnings({ - "nullness:assignment.type.incompatible" // Every qualifier kind is a - // key in directSuperQualifierMap. - }) - @NonNull AnnotationMirror lub = - ((UnitsQualifierKindHierarchy) qualifierKindHierarchy) - .directSuperQualifierMap.get(qualifierKind1); - return lub; - } - } - throw new TypeSystemError( - "Unexpected QualifierKinds: %s %s", qualifierKind1, qualifierKind2); - } + /** + * Mapping from QualifierKind to an AnnotationMirror that represents its direct super qualifier. + * Every qualifier kind maps to a nonnull AnnotationMirror. + */ + private final Map directSuperQualifierMap; - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - return UnitsAnnotatedTypeFactory.this.BOTTOM; - } + /** + * Creates a UnitsQualifierKindHierarchy. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + public UnitsQualifierKindHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, UnitsBottom.class); + directSuperQualifierMap = createDirectSuperQualifierMap(elements); } - /** UnitsQualifierKindHierarchy. */ - @AnnotatedFor("nullness") - protected static class UnitsQualifierKindHierarchy extends DefaultQualifierKindHierarchy { - - /** - * Mapping from QualifierKind to an AnnotationMirror that represents its direct super - * qualifier. Every qualifier kind maps to a nonnull AnnotationMirror. - */ - private final Map directSuperQualifierMap; - - /** - * Creates a UnitsQualifierKindHierarchy. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - public UnitsQualifierKindHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, UnitsBottom.class); - directSuperQualifierMap = createDirectSuperQualifierMap(elements); + /** + * Creates the direct super qualifier map. + * + * @param elements element utils + * @return the map + */ + @RequiresNonNull("this.qualifierKinds") + private Map createDirectSuperQualifierMap( + @UnderInitialization UnitsQualifierKindHierarchy this, Elements elements) { + Map directSuperType = new TreeMap<>(); + for (QualifierKind qualifierKind : qualifierKinds) { + QualifierKind directSuperTypeKind = getDirectSuperQualifierKind(qualifierKind); + AnnotationMirror directSuperTypeAnno; + try { + directSuperTypeAnno = AnnotationBuilder.fromName(elements, directSuperTypeKind.getName()); + } catch (BugInCF ex) { + throw new TypeSystemError("Unit annotations must have a default for all elements."); } - - /** - * Creates the direct super qualifier map. - * - * @param elements element utils - * @return the map - */ - @RequiresNonNull("this.qualifierKinds") - private Map createDirectSuperQualifierMap( - @UnderInitialization UnitsQualifierKindHierarchy this, Elements elements) { - Map directSuperType = new TreeMap<>(); - for (QualifierKind qualifierKind : qualifierKinds) { - QualifierKind directSuperTypeKind = getDirectSuperQualifierKind(qualifierKind); - AnnotationMirror directSuperTypeAnno; - try { - directSuperTypeAnno = - AnnotationBuilder.fromName(elements, directSuperTypeKind.getName()); - } catch (BugInCF ex) { - throw new TypeSystemError( - "Unit annotations must have a default for all elements."); - } - if (directSuperTypeAnno == null) { - throw new TypeSystemError( - "Could not create AnnotationMirror: %s", directSuperTypeAnno); - } - directSuperType.put(qualifierKind, directSuperTypeAnno); - } - return directSuperType; + if (directSuperTypeAnno == null) { + throw new TypeSystemError("Could not create AnnotationMirror: %s", directSuperTypeAnno); } + directSuperType.put(qualifierKind, directSuperTypeAnno); + } + return directSuperType; + } - /** - * Get the direct super qualifier for the given qualifier kind. - * - * @param qualifierKind qualifier kind - * @return direct super qualifier kind - */ - private QualifierKind getDirectSuperQualifierKind( - @UnderInitialization UnitsQualifierKindHierarchy this, - QualifierKind qualifierKind) { - if (qualifierKind.isTop()) { - return qualifierKind; - } - Set superQuals = new TreeSet<>(qualifierKind.getStrictSuperTypes()); - while (superQuals.size() > 0) { - Set lowest = findLowestQualifiers(superQuals); - if (lowest.size() == 1) { - return lowest.iterator().next(); - } - superQuals.removeAll(lowest); - } - throw new TypeSystemError("No direct super qualifier found for %s", qualifierKind); + /** + * Get the direct super qualifier for the given qualifier kind. + * + * @param qualifierKind qualifier kind + * @return direct super qualifier kind + */ + private QualifierKind getDirectSuperQualifierKind( + @UnderInitialization UnitsQualifierKindHierarchy this, QualifierKind qualifierKind) { + if (qualifierKind.isTop()) { + return qualifierKind; + } + Set superQuals = new TreeSet<>(qualifierKind.getStrictSuperTypes()); + while (superQuals.size() > 0) { + Set lowest = findLowestQualifiers(superQuals); + if (lowest.size() == 1) { + return lowest.iterator().next(); } + superQuals.removeAll(lowest); + } + throw new TypeSystemError("No direct super qualifier found for %s", qualifierKind); } + } - private AnnotationMirror removePrefix(AnnotationMirror anno) { - return UnitsRelationsTools.removePrefix(elements, anno); - } + private AnnotationMirror removePrefix(AnnotationMirror anno) { + return UnitsRelationsTools.removePrefix(elements, anno); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java index e4e017decfb..d173d5a128d 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java @@ -1,5 +1,9 @@ package org.checkerframework.checker.units; +import java.util.Collection; +import java.util.Collections; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.units.qual.Prefix; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.DefaultAnnotatedTypeFormatter; @@ -7,85 +11,79 @@ import org.checkerframework.framework.util.DefaultAnnotationFormatter; import org.checkerframework.javacutil.AnnotationMirrorSet; -import java.util.Collection; -import java.util.Collections; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** Formats units-of-measure annotations. */ public class UnitsAnnotatedTypeFormatter extends DefaultAnnotatedTypeFormatter { - /** The checker. */ - protected final BaseTypeChecker checker; + /** The checker. */ + protected final BaseTypeChecker checker; - /** Javac element utilities. */ - protected final Elements elements; + /** Javac element utilities. */ + protected final Elements elements; - /** - * Create a UnitsAnnotatedTypeFormatter. - * - * @param checker the checker - */ - public UnitsAnnotatedTypeFormatter(BaseTypeChecker checker) { - // Utilize the Default Type Formatter, but force it to print out Invisible Qualifiers. - // Keep super call in sync with implementation in DefaultAnnotatedTypeFormatter. - // Keep checker options in sync with implementation in AnnotatedTypeFactory. - super( - new UnitsFormattingVisitor( - checker, - new UnitsAnnotationFormatter(checker), - checker.hasOption("printVerboseGenerics"), - true)); + /** + * Create a UnitsAnnotatedTypeFormatter. + * + * @param checker the checker + */ + public UnitsAnnotatedTypeFormatter(BaseTypeChecker checker) { + // Utilize the Default Type Formatter, but force it to print out Invisible Qualifiers. + // Keep super call in sync with implementation in DefaultAnnotatedTypeFormatter. + // Keep checker options in sync with implementation in AnnotatedTypeFactory. + super( + new UnitsFormattingVisitor( + checker, + new UnitsAnnotationFormatter(checker), + checker.hasOption("printVerboseGenerics"), + true)); - this.checker = checker; - this.elements = checker.getElementUtils(); - } + this.checker = checker; + this.elements = checker.getElementUtils(); + } - protected static class UnitsFormattingVisitor - extends DefaultAnnotatedTypeFormatter.FormattingVisitor { - protected final BaseTypeChecker checker; - protected final Elements elements; + protected static class UnitsFormattingVisitor + extends DefaultAnnotatedTypeFormatter.FormattingVisitor { + protected final BaseTypeChecker checker; + protected final Elements elements; - public UnitsFormattingVisitor( - BaseTypeChecker checker, - AnnotationFormatter annoFormatter, - boolean printVerboseGenerics, - boolean defaultInvisiblesSetting) { + public UnitsFormattingVisitor( + BaseTypeChecker checker, + AnnotationFormatter annoFormatter, + boolean printVerboseGenerics, + boolean defaultInvisiblesSetting) { - super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); - this.checker = checker; - this.elements = checker.getElementUtils(); - } + super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); + this.checker = checker; + this.elements = checker.getElementUtils(); } + } - /** Format the error printout of any units qualifier that uses Prefix.one. */ - protected static class UnitsAnnotationFormatter extends DefaultAnnotationFormatter { - protected final BaseTypeChecker checker; - protected final Elements elements; - - public UnitsAnnotationFormatter(BaseTypeChecker checker) { - this.checker = checker; - this.elements = checker.getElementUtils(); - } + /** Format the error printout of any units qualifier that uses Prefix.one. */ + protected static class UnitsAnnotationFormatter extends DefaultAnnotationFormatter { + protected final BaseTypeChecker checker; + protected final Elements elements; - @Override - public String formatAnnotationString( - Collection annos, boolean printInvisible) { - // create an empty annotation set - AnnotationMirrorSet trimmedAnnoSet = new AnnotationMirrorSet(); + public UnitsAnnotationFormatter(BaseTypeChecker checker) { + this.checker = checker; + this.elements = checker.getElementUtils(); + } - // loop through all the annotation mirrors to see if they use Prefix.one, remove - // Prefix.one if it does - for (AnnotationMirror anno : annos) { - if (UnitsRelationsTools.getPrefix(anno) == Prefix.one) { - anno = UnitsRelationsTools.removePrefix(elements, anno); - } - // add to set - trimmedAnnoSet.add(anno); - } + @Override + public String formatAnnotationString( + Collection annos, boolean printInvisible) { + // create an empty annotation set + AnnotationMirrorSet trimmedAnnoSet = new AnnotationMirrorSet(); - return super.formatAnnotationString( - Collections.unmodifiableSet(trimmedAnnoSet), printInvisible); + // loop through all the annotation mirrors to see if they use Prefix.one, remove + // Prefix.one if it does + for (AnnotationMirror anno : annos) { + if (UnitsRelationsTools.getPrefix(anno) == Prefix.one) { + anno = UnitsRelationsTools.removePrefix(elements, anno); } + // add to set + trimmedAnnoSet.add(anno); + } + + return super.formatAnnotationString( + Collections.unmodifiableSet(trimmedAnnoSet), printInvisible); } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java index 0c36171bfb3..e62adb681d4 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java @@ -1,52 +1,50 @@ package org.checkerframework.checker.units; +import java.lang.annotation.Annotation; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.units.qual.UnitsMultiple; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.AnnotationClassLoader; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -import java.lang.annotation.Annotation; - -import javax.lang.model.element.AnnotationMirror; - public class UnitsAnnotationClassLoader extends AnnotationClassLoader { - public UnitsAnnotationClassLoader(BaseTypeChecker checker) { - super(checker); + public UnitsAnnotationClassLoader(BaseTypeChecker checker) { + super(checker); + } + + /** + * Custom filter for units annotations: + * + *

This filter will ignore (by returning false) any units annotation which is an alias of + * another base unit annotation (identified via {@link UnitsMultiple} meta-annotation). Alias + * annotations can still be used in source code; they are converted into a base annotation by + * {@link UnitsAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}. This filter simply + * makes sure that the alias annotations themselves don't become part of the type hierarchy as + * their base annotations already are in the hierarchy. + */ + @Override + protected boolean isSupportedAnnotationClass(Class annoClass) { + // build the initial annotation mirror (missing prefix) + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoClass); + AnnotationMirror initialResult = builder.build(); + + // further refine to see if the annotation is an alias of some other SI Unit annotation + for (AnnotationMirror metaAnno : + initialResult.getAnnotationType().asElement().getAnnotationMirrors()) { + // TODO : special treatment of invisible qualifiers? + + // If the annotation is a SI prefix multiple of some base unit, then return false. + // Units checker does not need to load the annotations of SI prefix multiples of base + // units. + if (AnnotationUtils.areSameByName( + metaAnno, "org.checkerframework.checker.units.qual.UnitsMultiple")) { + return false; + } } - /** - * Custom filter for units annotations: - * - *

This filter will ignore (by returning false) any units annotation which is an alias of - * another base unit annotation (identified via {@link UnitsMultiple} meta-annotation). Alias - * annotations can still be used in source code; they are converted into a base annotation by - * {@link UnitsAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}. This filter simply - * makes sure that the alias annotations themselves don't become part of the type hierarchy as - * their base annotations already are in the hierarchy. - */ - @Override - protected boolean isSupportedAnnotationClass(Class annoClass) { - // build the initial annotation mirror (missing prefix) - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoClass); - AnnotationMirror initialResult = builder.build(); - - // further refine to see if the annotation is an alias of some other SI Unit annotation - for (AnnotationMirror metaAnno : - initialResult.getAnnotationType().asElement().getAnnotationMirrors()) { - // TODO : special treatment of invisible qualifiers? - - // If the annotation is a SI prefix multiple of some base unit, then return false. - // Units checker does not need to load the annotations of SI prefix multiples of base - // units. - if (AnnotationUtils.areSameByName( - metaAnno, "org.checkerframework.checker.units.qual.UnitsMultiple")) { - return false; - } - } - - // Not an alias unit - return true; - } + // Not an alias unit + return true; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java index e084ae7c65f..b1f919299e7 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java @@ -1,11 +1,9 @@ package org.checkerframework.checker.units; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.common.subtyping.SubtypingChecker; - import java.util.NavigableSet; - import javax.annotation.processing.SupportedOptions; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.subtyping.SubtypingChecker; /** * Units Checker main class. @@ -20,9 +18,9 @@ @SupportedOptions({"units", "unitsDirs"}) public class UnitsChecker extends BaseTypeChecker { - @Override - public NavigableSet getSuppressWarningsPrefixes() { - return SubtypingChecker.getSuppressWarningsPrefixes( - this.visitor, super.getSuppressWarningsPrefixes()); - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + return SubtypingChecker.getSuppressWarningsPrefixes( + this.visitor, super.getSuppressWarningsPrefixes()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java index e71c6d58e70..10b6bdec2ff 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.units; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; /** * Interface that is used to specify the relation between units. A class that implements this @@ -12,31 +11,31 @@ * annotation. */ public interface UnitsRelations { - /** - * Initialize the object. Needs to be called before any other method. - * - * @param env the ProcessingEnvironment to use - * @return a reference to "this" - */ - UnitsRelations init(ProcessingEnvironment env); + /** + * Initialize the object. Needs to be called before any other method. + * + * @param env the ProcessingEnvironment to use + * @return a reference to "this" + */ + UnitsRelations init(ProcessingEnvironment env); - /** - * Called for the multiplication of type lht and rht. - * - * @param lht left hand side in multiplication - * @param rht right hand side in multiplication - * @return the annotation to use for the result of the multiplication or null if no special - * relation is known - */ - @Nullable AnnotationMirror multiplication(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); + /** + * Called for the multiplication of type lht and rht. + * + * @param lht left hand side in multiplication + * @param rht right hand side in multiplication + * @return the annotation to use for the result of the multiplication or null if no special + * relation is known + */ + @Nullable AnnotationMirror multiplication(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); - /** - * Called for the division of type lht and rht. - * - * @param lht left hand side in division - * @param rht right hand side in division - * @return the annotation to use for the result of the division or null if no special relation - * is known - */ - @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); + /** + * Called for the division of type lht and rht. + * + * @param lht left hand side in division + * @param rht right hand side in division + * @return the annotation to use for the result of the division or null if no special relation is + * known + */ + @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java index 7199398fc97..9c725a1a071 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java @@ -1,5 +1,8 @@ package org.checkerframework.checker.units; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.units.qual.N; import org.checkerframework.checker.units.qual.Prefix; @@ -19,252 +22,242 @@ import org.checkerframework.checker.units.qual.t; import org.checkerframework.framework.type.AnnotatedTypeMirror; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** Default relations between SI units. */ public class UnitsRelationsDefault implements UnitsRelations { - /** SI base units. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror m, km, mm, s, g, kg; + /** SI base units. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror m, km, mm, s, g, kg; - /** Derived SI units without special names */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror m2, km2, mm2, m3, km3, mm3, mPERs, mPERs2; + /** Derived SI units without special names */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror m2, km2, mm2, m3, km3, mm3, mPERs, mPERs2; - /** Derived SI units with special names */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror N, kN; + /** Derived SI units with special names */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror N, kN; - /** Non-SI units */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror h, kmPERh, t; + /** Non-SI units */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror h, kmPERh, t; - /** The Element Utilities from the Units Checker's processing environment. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected Elements elements; + /** The Element Utilities from the Units Checker's processing environment. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected Elements elements; - /** - * Constructs various AnnotationMirrors representing specific checker-framework provided Units - * involved in the rules resolved in this UnitsRelations implementation. - */ - @Override - public UnitsRelations init(ProcessingEnvironment env) { - elements = env.getElementUtils(); + /** + * Constructs various AnnotationMirrors representing specific checker-framework provided Units + * involved in the rules resolved in this UnitsRelations implementation. + */ + @Override + public UnitsRelations init(ProcessingEnvironment env) { + elements = env.getElementUtils(); - m = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, m.class); - km = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.kilo); - mm = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.milli); + m = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, m.class); + km = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.kilo); + mm = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.milli); - m2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m2.class); - km2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km2.class); - mm2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm2.class); + m2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m2.class); + km2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km2.class); + mm2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm2.class); - m3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m3.class); - km3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km3.class); - mm3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm3.class); + m3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m3.class); + km3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km3.class); + mm3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm3.class); - s = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); - h = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, h.class); + s = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); + h = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, h.class); - mPERs = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs.class); - kmPERh = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, kmPERh.class); + mPERs = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs.class); + kmPERh = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, kmPERh.class); - mPERs2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs2.class); + mPERs2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs2.class); - g = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, g.class); - kg = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, g.class, Prefix.kilo); - t = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, t.class); - N = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, N.class); - kN = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, N.class, Prefix.kilo); + g = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, g.class); + kg = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, g.class, Prefix.kilo); + t = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, t.class); + N = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, N.class); + kN = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, N.class, Prefix.kilo); - return this; - } + return this; + } - /** - * Provides rules for resolving the result Unit of the multiplication of checker-framework - * provided Units. - */ - @Override - public @Nullable AnnotationMirror multiplication( - AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - // TODO: does this handle scaling correctly? + /** + * Provides rules for resolving the result Unit of the multiplication of checker-framework + * provided Units. + */ + @Override + public @Nullable AnnotationMirror multiplication( + AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + // TODO: does this handle scaling correctly? - // length * length => area - // checking SI units only - if (UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(lht, m) - && UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(rht, m)) { - if (UnitsRelationsTools.hasNoPrefix(lht) && UnitsRelationsTools.hasNoPrefix(rht)) { - // m * m - return m2; - } + // length * length => area + // checking SI units only + if (UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(lht, m) + && UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(rht, m)) { + if (UnitsRelationsTools.hasNoPrefix(lht) && UnitsRelationsTools.hasNoPrefix(rht)) { + // m * m + return m2; + } - Prefix lhtPrefix = UnitsRelationsTools.getPrefix(lht); - Prefix rhtPrefix = UnitsRelationsTools.getPrefix(rht); + Prefix lhtPrefix = UnitsRelationsTools.getPrefix(lht); + Prefix rhtPrefix = UnitsRelationsTools.getPrefix(rht); - if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.kilo)) { - // km * km - return km2; - } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.one)) { - // m(Prefix.one) * m(Prefix.one) - return m2; - } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.milli)) { - // mm * mm - return mm2; - } else { - return null; - } - } else if (havePairOfUnitsIgnoringOrder(lht, m, rht, m2)) { - return m3; - } else if (havePairOfUnitsIgnoringOrder(lht, km, rht, km2)) { - return km3; - } else if (havePairOfUnitsIgnoringOrder(lht, mm, rht, mm2)) { - return mm3; - } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs)) { - // s * mPERs or mPERs * s => m - return m; - } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs2)) { - // s * mPERs2 or mPERs2 * s => mPERs - return mPERs; - } else if (havePairOfUnitsIgnoringOrder(lht, h, rht, kmPERh)) { - // h * kmPERh or kmPERh * h => km - return km; - } else if (havePairOfUnitsIgnoringOrder(lht, kg, rht, mPERs2)) { - // kg * mPERs2 or mPERs2 * kg = N - return N; - } else if (havePairOfUnitsIgnoringOrder(lht, t, rht, mPERs2)) { - // t * mPERs2 or mPERs2 * t = kN - return kN; - } else { - return null; - } + if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.kilo)) { + // km * km + return km2; + } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.one)) { + // m(Prefix.one) * m(Prefix.one) + return m2; + } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.milli)) { + // mm * mm + return mm2; + } else { + return null; + } + } else if (havePairOfUnitsIgnoringOrder(lht, m, rht, m2)) { + return m3; + } else if (havePairOfUnitsIgnoringOrder(lht, km, rht, km2)) { + return km3; + } else if (havePairOfUnitsIgnoringOrder(lht, mm, rht, mm2)) { + return mm3; + } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs)) { + // s * mPERs or mPERs * s => m + return m; + } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs2)) { + // s * mPERs2 or mPERs2 * s => mPERs + return mPERs; + } else if (havePairOfUnitsIgnoringOrder(lht, h, rht, kmPERh)) { + // h * kmPERh or kmPERh * h => km + return km; + } else if (havePairOfUnitsIgnoringOrder(lht, kg, rht, mPERs2)) { + // kg * mPERs2 or mPERs2 * kg = N + return N; + } else if (havePairOfUnitsIgnoringOrder(lht, t, rht, mPERs2)) { + // t * mPERs2 or mPERs2 * t = kN + return kN; + } else { + return null; } + } - /** - * Provides rules for resolving the result Unit of the division of checker-framework provided - * Units. - */ - @Override - public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - if (havePairOfUnits(lht, m, rht, s)) { - // m / s => mPERs - return mPERs; - } else if (havePairOfUnits(lht, km, rht, h)) { - // km / h => kmPERh - return kmPERh; - } else if (havePairOfUnits(lht, m2, rht, m)) { - // m2 / m => m - return m; - } else if (havePairOfUnits(lht, km2, rht, km)) { - // km2 / km => km - return km; - } else if (havePairOfUnits(lht, mm2, rht, mm)) { - // mm2 / mm => mm - return mm; - } else if (havePairOfUnits(lht, m3, rht, m)) { - // m3 / m => m2 - return m2; - } else if (havePairOfUnits(lht, km3, rht, km)) { - // km3 / km => km2 - return km2; - } else if (havePairOfUnits(lht, mm3, rht, mm)) { - // mm3 / mm => mm2 - return mm2; - } else if (havePairOfUnits(lht, m3, rht, m2)) { - // m3 / m2 => m - return m; - } else if (havePairOfUnits(lht, km3, rht, km2)) { - // km3 / km2 => km - return km; - } else if (havePairOfUnits(lht, mm3, rht, mm2)) { - // mm3 / mm2 => mm - return mm; - } else if (havePairOfUnits(lht, m, rht, mPERs)) { - // m / mPERs => s - return s; - } else if (havePairOfUnits(lht, km, rht, kmPERh)) { - // km / kmPERh => h - return h; - } else if (havePairOfUnits(lht, mPERs, rht, s)) { - // mPERs / s = mPERs2 - return mPERs2; - } else if (havePairOfUnits(lht, mPERs, rht, mPERs2)) { - // mPERs / mPERs2 => s (velocity / acceleration == time) - return s; - } else if (UnitsRelationsTools.hasSpecificUnit(lht, N)) { - if (UnitsRelationsTools.hasSpecificUnit(rht, kg)) { - // N / kg => mPERs2 - return mPERs2; - } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { - // N / mPERs2 => kg - return kg; - } - return null; - } else if (UnitsRelationsTools.hasSpecificUnit(lht, kN)) { - if (UnitsRelationsTools.hasSpecificUnit(rht, t)) { - // kN / t => mPERs2 - return mPERs2; - } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { - // kN / mPERs2 => t - return t; - } - return null; - } else { - return null; - } + /** + * Provides rules for resolving the result Unit of the division of checker-framework provided + * Units. + */ + @Override + public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + if (havePairOfUnits(lht, m, rht, s)) { + // m / s => mPERs + return mPERs; + } else if (havePairOfUnits(lht, km, rht, h)) { + // km / h => kmPERh + return kmPERh; + } else if (havePairOfUnits(lht, m2, rht, m)) { + // m2 / m => m + return m; + } else if (havePairOfUnits(lht, km2, rht, km)) { + // km2 / km => km + return km; + } else if (havePairOfUnits(lht, mm2, rht, mm)) { + // mm2 / mm => mm + return mm; + } else if (havePairOfUnits(lht, m3, rht, m)) { + // m3 / m => m2 + return m2; + } else if (havePairOfUnits(lht, km3, rht, km)) { + // km3 / km => km2 + return km2; + } else if (havePairOfUnits(lht, mm3, rht, mm)) { + // mm3 / mm => mm2 + return mm2; + } else if (havePairOfUnits(lht, m3, rht, m2)) { + // m3 / m2 => m + return m; + } else if (havePairOfUnits(lht, km3, rht, km2)) { + // km3 / km2 => km + return km; + } else if (havePairOfUnits(lht, mm3, rht, mm2)) { + // mm3 / mm2 => mm + return mm; + } else if (havePairOfUnits(lht, m, rht, mPERs)) { + // m / mPERs => s + return s; + } else if (havePairOfUnits(lht, km, rht, kmPERh)) { + // km / kmPERh => h + return h; + } else if (havePairOfUnits(lht, mPERs, rht, s)) { + // mPERs / s = mPERs2 + return mPERs2; + } else if (havePairOfUnits(lht, mPERs, rht, mPERs2)) { + // mPERs / mPERs2 => s (velocity / acceleration == time) + return s; + } else if (UnitsRelationsTools.hasSpecificUnit(lht, N)) { + if (UnitsRelationsTools.hasSpecificUnit(rht, kg)) { + // N / kg => mPERs2 + return mPERs2; + } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { + // N / mPERs2 => kg + return kg; + } + return null; + } else if (UnitsRelationsTools.hasSpecificUnit(lht, kN)) { + if (UnitsRelationsTools.hasSpecificUnit(rht, t)) { + // kN / t => mPERs2 + return mPERs2; + } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { + // kN / mPERs2 => t + return t; + } + return null; + } else { + return null; } + } - /** - * Checks to see if both lhtPrefix and rhtPrefix have the same prefix as specificPrefix. - * - * @param lhtPrefix left hand side prefix - * @param rhtPrefix right hand side prefix - * @param specificPrefix specific desired prefix to match - * @return true if all 3 Prefix are the same, false otherwise - */ - protected boolean bothHaveSpecificPrefix( - Prefix lhtPrefix, Prefix rhtPrefix, Prefix specificPrefix) { - if (lhtPrefix == null || rhtPrefix == null || specificPrefix == null) { - return false; - } - - return lhtPrefix == rhtPrefix && rhtPrefix == specificPrefix; + /** + * Checks to see if both lhtPrefix and rhtPrefix have the same prefix as specificPrefix. + * + * @param lhtPrefix left hand side prefix + * @param rhtPrefix right hand side prefix + * @param specificPrefix specific desired prefix to match + * @return true if all 3 Prefix are the same, false otherwise + */ + protected boolean bothHaveSpecificPrefix( + Prefix lhtPrefix, Prefix rhtPrefix, Prefix specificPrefix) { + if (lhtPrefix == null || rhtPrefix == null || specificPrefix == null) { + return false; } - /** - * Checks to see if lht has the unit ul and if rht has the unit ur all at the same time. - * - * @param lht left hand annotated type - * @param ul left hand unit - * @param rht right hand annotated type - * @param ur right hand unit - * @return true if lht has lu and rht has ru, false otherwise - */ - protected boolean havePairOfUnits( - AnnotatedTypeMirror lht, - AnnotationMirror ul, - AnnotatedTypeMirror rht, - AnnotationMirror ur) { - return UnitsRelationsTools.hasSpecificUnit(lht, ul) - && UnitsRelationsTools.hasSpecificUnit(rht, ur); - } + return lhtPrefix == rhtPrefix && rhtPrefix == specificPrefix; + } - /** - * Checks to see if lht and rht have the pair of units u1 and u2 regardless of order. - * - * @param lht left hand annotated type - * @param u1 unit 1 - * @param rht right hand annotated type - * @param u2 unit 2 - * @return true if lht and rht have the pair of units u1 and u2 regardless of order, false - * otherwise - */ - protected boolean havePairOfUnitsIgnoringOrder( - AnnotatedTypeMirror lht, - AnnotationMirror u1, - AnnotatedTypeMirror rht, - AnnotationMirror u2) { - return havePairOfUnits(lht, u1, rht, u2) || havePairOfUnits(lht, u2, rht, u1); - } + /** + * Checks to see if lht has the unit ul and if rht has the unit ur all at the same time. + * + * @param lht left hand annotated type + * @param ul left hand unit + * @param rht right hand annotated type + * @param ur right hand unit + * @return true if lht has lu and rht has ru, false otherwise + */ + protected boolean havePairOfUnits( + AnnotatedTypeMirror lht, AnnotationMirror ul, AnnotatedTypeMirror rht, AnnotationMirror ur) { + return UnitsRelationsTools.hasSpecificUnit(lht, ul) + && UnitsRelationsTools.hasSpecificUnit(rht, ur); + } + + /** + * Checks to see if lht and rht have the pair of units u1 and u2 regardless of order. + * + * @param lht left hand annotated type + * @param u1 unit 1 + * @param rht right hand annotated type + * @param u2 unit 2 + * @return true if lht and rht have the pair of units u1 and u2 regardless of order, false + * otherwise + */ + protected boolean havePairOfUnitsIgnoringOrder( + AnnotatedTypeMirror lht, AnnotationMirror u1, AnnotatedTypeMirror rht, AnnotationMirror u2) { + return havePairOfUnits(lht, u1, rht, u2) || havePairOfUnits(lht, u2, rht, u1); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java index 134af783d4f..5e5ae26f2e3 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java @@ -1,5 +1,12 @@ package org.checkerframework.checker.units; +import java.lang.annotation.Annotation; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.FullyQualifiedName; import org.checkerframework.checker.units.qual.Prefix; @@ -9,311 +16,297 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; -import java.lang.annotation.Annotation; -import java.util.Map; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** * A helper class for UnitsRelations, providing numerous methods which help process Annotations and * Annotated Types representing various units. */ public class UnitsRelationsTools { - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific - * Prefix p. - * - * @param env the Checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the fully-qualified name of an Annotation representing a Unit (eg m.class - * for meters) - * @param p a Prefix value - * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be - * constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( - ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass, Prefix p) { - AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); - builder.setValue("value", p); - return builder.build(); - } + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific Prefix + * p. + * + * @param env the Checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the fully-qualified name of an Annotation representing a Unit (eg m.class for + * meters) + * @param p a Prefix value + * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( + ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass, Prefix p) { + AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); + builder.setValue("value", p); + return builder.build(); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. - * - * @param env checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the getElementValueClassname of an Annotation representing a Unit (eg - * m.class for meters) - * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( - ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass) { - return AnnotationBuilder.fromName(env.getElementUtils(), annoClass); - } - - /** - * Retrieves the SI Prefix of an Annotated Type. - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return a Prefix value (including Prefix.one), or null if it has none - */ - public static @Nullable Prefix getPrefix(AnnotatedTypeMirror annoType) { - Prefix result = null; + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. + * + * @param env checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the getElementValueClassname of an Annotation representing a Unit (eg m.class + * for meters) + * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( + ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass) { + return AnnotationBuilder.fromName(env.getElementUtils(), annoClass); + } - // go through each Annotation of an Annotated Type, find the prefix and return it - for (AnnotationMirror mirror : annoType.getAnnotations()) { - // try to get a prefix - result = getPrefix(mirror); - // if it is not null, then return the retrieved prefix immediately - if (result != null) { - return result; - } - } + /** + * Retrieves the SI Prefix of an Annotated Type. + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return a Prefix value (including Prefix.one), or null if it has none + */ + public static @Nullable Prefix getPrefix(AnnotatedTypeMirror annoType) { + Prefix result = null; - // if it can't find any prefix at all, then return null + // go through each Annotation of an Annotated Type, find the prefix and return it + for (AnnotationMirror mirror : annoType.getAnnotations()) { + // try to get a prefix + result = getPrefix(mirror); + // if it is not null, then return the retrieved prefix immediately + if (result != null) { return result; + } } - /** - * Retrieves the SI Prefix of an Annotation. - * - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation - * @return a Prefix value (including Prefix.one), or null if it has none - */ - public static @Nullable Prefix getPrefix(AnnotationMirror unitsAnnotation) { - AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); + // if it can't find any prefix at all, then return null + return result; + } - // if this Annotation has no prefix, return null - if (hasNoPrefix(annotationValue)) { - return null; - } + /** + * Retrieves the SI Prefix of an Annotation. + * + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation + * @return a Prefix value (including Prefix.one), or null if it has none + */ + public static @Nullable Prefix getPrefix(AnnotationMirror unitsAnnotation) { + AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); - // if the Annotation has a value, then detect and match the string name of the prefix, and - // return the matching Prefix - String prefixString = annotationValue.getValue().toString(); - for (Prefix prefix : Prefix.values()) { - if (prefixString.equals(prefix.toString())) { - return prefix; - } - } - - // if none of the strings match, then return null - return null; + // if this Annotation has no prefix, return null + if (hasNoPrefix(annotationValue)) { + return null; } - /** - * Checks to see if an Annotated Type has no prefix. - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return true if it has no prefix, false otherwise - */ - public static boolean hasNoPrefix(AnnotatedTypeMirror annoType) { - for (AnnotationMirror mirror : annoType.getAnnotations()) { - // if any Annotation has a prefix, return false - if (!hasNoPrefix(mirror)) { - return false; - } - } - - return true; + // if the Annotation has a value, then detect and match the string name of the prefix, and + // return the matching Prefix + String prefixString = annotationValue.getValue().toString(); + for (Prefix prefix : Prefix.values()) { + if (prefixString.equals(prefix.toString())) { + return prefix; + } } - /** - * Checks to see if an Annotation has no prefix (ie, no value element). - * - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation - * @return true if it has no prefix, false otherwise - */ - public static boolean hasNoPrefix(AnnotationMirror unitsAnnotation) { - AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); - return hasNoPrefix(annotationValue); - } + // if none of the strings match, then return null + return null; + } - private static boolean hasNoPrefix(AnnotationValue annotationValue) { - // Annotation has no element value (ie no SI prefix) - if (annotationValue == null) { - return true; - } else { - return false; - } + /** + * Checks to see if an Annotated Type has no prefix. + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return true if it has no prefix, false otherwise + */ + public static boolean hasNoPrefix(AnnotatedTypeMirror annoType) { + for (AnnotationMirror mirror : annoType.getAnnotations()) { + // if any Annotation has a prefix, return false + if (!hasNoPrefix(mirror)) { + return false; + } } - /** - * Given an Annotation, returns the prefix (eg kilo) as an AnnotationValue if there is any, - * otherwise returns null. - */ - private static @Nullable AnnotationValue getAnnotationMirrorPrefix( - AnnotationMirror unitsAnnotation) { - Map elementValues = - unitsAnnotation.getElementValues(); + return true; + } - for (Map.Entry entry : - elementValues.entrySet()) { - if (entry.getKey().getSimpleName().contentEquals("value")) { - return entry.getValue(); - } - } + /** + * Checks to see if an Annotation has no prefix (ie, no value element). + * + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation + * @return true if it has no prefix, false otherwise + */ + public static boolean hasNoPrefix(AnnotationMirror unitsAnnotation) { + AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); + return hasNoPrefix(annotationValue); + } - return null; + private static boolean hasNoPrefix(AnnotationValue annotationValue) { + // Annotation has no element value (ie no SI prefix) + if (annotationValue == null) { + return true; + } else { + return false; } + } + + /** + * Given an Annotation, returns the prefix (eg kilo) as an AnnotationValue if there is any, + * otherwise returns null. + */ + private static @Nullable AnnotationValue getAnnotationMirrorPrefix( + AnnotationMirror unitsAnnotation) { + Map elementValues = + unitsAnnotation.getElementValues(); - /** - * Removes the prefix value from an Annotation, by constructing and returning a copy of its base - * SI unit's Annotation. - * - * @param elements the Element Utilities from a checker's processing environment, typically - * obtained by calling env.getElementUtils() in init() of a Units Relations implementation - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation - * @return the base SI Unit's AnnotationMirror, or null if the base SI Unit cannot be - * constructed - */ - public static AnnotationMirror removePrefix( - Elements elements, AnnotationMirror unitsAnnotation) { - if (hasNoPrefix(unitsAnnotation)) { - // Optimization, though the else case would also work. - return unitsAnnotation; - } else { - String unitsAnnoName = AnnotationUtils.annotationName(unitsAnnotation); - // In the Units Checker, the only annotation value is the prefix value. Therefore, - // fromName (which creates an annotation with no values) is acceptable. - // TODO: refine sensitivity of removal for extension units, in case extension - // Annotations have more than just Prefix in its values. - return AnnotationBuilder.fromName(elements, unitsAnnoName); - } + for (Map.Entry entry : + elementValues.entrySet()) { + if (entry.getKey().getSimpleName().contentEquals("value")) { + return entry.getValue(); + } } - /** - * Removes the Prefix value from an Annotated Type, by constructing and returning a copy of the - * Annotated Type without the prefix. - * - * @param elements the Element Utilities from a checker's processing environment, typically - * obtained by calling env.getElementUtils() in init() of a Units Relations implementation - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return a copy of the Annotated Type without the prefix - */ - public static AnnotatedTypeMirror removePrefix( - Elements elements, AnnotatedTypeMirror annoType) { - // deep copy the Annotated Type Mirror without any of the Annotations - AnnotatedTypeMirror result = annoType.deepCopy(false); + return null; + } - // get all of the original Annotations in the Annotated Type - AnnotationMirrorSet annos = annoType.getAnnotations(); + /** + * Removes the prefix value from an Annotation, by constructing and returning a copy of its base + * SI unit's Annotation. + * + * @param elements the Element Utilities from a checker's processing environment, typically + * obtained by calling env.getElementUtils() in init() of a Units Relations implementation + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation + * @return the base SI Unit's AnnotationMirror, or null if the base SI Unit cannot be constructed + */ + public static AnnotationMirror removePrefix(Elements elements, AnnotationMirror unitsAnnotation) { + if (hasNoPrefix(unitsAnnotation)) { + // Optimization, though the else case would also work. + return unitsAnnotation; + } else { + String unitsAnnoName = AnnotationUtils.annotationName(unitsAnnotation); + // In the Units Checker, the only annotation value is the prefix value. Therefore, + // fromName (which creates an annotation with no values) is acceptable. + // TODO: refine sensitivity of removal for extension units, in case extension + // Annotations have more than just Prefix in its values. + return AnnotationBuilder.fromName(elements, unitsAnnoName); + } + } - // loop through all the Annotations to see if they use Prefix.one, remove Prefix.one if it - // does - for (AnnotationMirror anno : annos) { - // try to clean the Annotation Mirror of the Prefix - AnnotationMirror cleanedMirror = removePrefix(elements, anno); - // if successful, add the cleaned annotation to the deep copy - if (cleanedMirror != null) { - result.addAnnotation(cleanedMirror); - } - // if unsuccessful, add the original annotation - else { - result.addAnnotation(anno); - } - } + /** + * Removes the Prefix value from an Annotated Type, by constructing and returning a copy of the + * Annotated Type without the prefix. + * + * @param elements the Element Utilities from a checker's processing environment, typically + * obtained by calling env.getElementUtils() in init() of a Units Relations implementation + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return a copy of the Annotated Type without the prefix + */ + public static AnnotatedTypeMirror removePrefix(Elements elements, AnnotatedTypeMirror annoType) { + // deep copy the Annotated Type Mirror without any of the Annotations + AnnotatedTypeMirror result = annoType.deepCopy(false); - return result; - } + // get all of the original Annotations in the Annotated Type + AnnotationMirrorSet annos = annoType.getAnnotations(); - /** - * Checks to see if a particular Annotated Type has no units, such as scalar constants in - * calculations. - * - *

Any number that isn't assigned a unit will automatically get the Annotation UnknownUnits. - * eg: int x = 5; // x has @UnknownUnits - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return true if the Type has no units, false otherwise - */ - public static boolean hasNoUnits(AnnotatedTypeMirror annoType) { - return (annoType.getAnnotation(UnknownUnits.class) != null); + // loop through all the Annotations to see if they use Prefix.one, remove Prefix.one if it + // does + for (AnnotationMirror anno : annos) { + // try to clean the Annotation Mirror of the Prefix + AnnotationMirror cleanedMirror = removePrefix(elements, anno); + // if successful, add the cleaned annotation to the deep copy + if (cleanedMirror != null) { + result.addAnnotation(cleanedMirror); + } + // if unsuccessful, add the original annotation + else { + result.addAnnotation(anno); + } } - /** - * Checks to see if a particular Annotated Type has a specific unit (represented by its - * Annotation). - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of a specific unit - * @return true if the Type has the specific unit, false otherwise - */ - public static boolean hasSpecificUnit( - AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { - return AnnotationUtils.containsSame(annoType.getAnnotations(), unitsAnnotation); - } + return result; + } - /** - * Checks to see if a particular Annotated Type has a particular base unit (represented by its - * Annotation). - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of the base unit - * @return true if the Type has the specific unit, false otherwise - */ - public static boolean hasSpecificUnitIgnoringPrefix( - AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { - return AnnotationUtils.containsSameByName(annoType.getAnnotations(), unitsAnnotation); - } + /** + * Checks to see if a particular Annotated Type has no units, such as scalar constants in + * calculations. + * + *

Any number that isn't assigned a unit will automatically get the Annotation UnknownUnits. + * eg: int x = 5; // x has @UnknownUnits + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return true if the Type has no units, false otherwise + */ + public static boolean hasNoUnits(AnnotatedTypeMirror annoType) { + return (annoType.getAnnotation(UnknownUnits.class) != null); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific - * Prefix p. - * - *

This interface is intended only for subclasses of UnitsRelations; other clients should use - * {@link #buildAnnoMirrorWithSpecificPrefix(ProcessingEnvironment, CharSequence, Prefix)} - * - * @param env the Checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) - * @param p a Prefix value - * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be - * constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( - ProcessingEnvironment env, Class annoClass, Prefix p) { - AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); - builder.setValue("value", p); - return builder.build(); - } + /** + * Checks to see if a particular Annotated Type has a specific unit (represented by its + * Annotation). + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of a specific unit + * @return true if the Type has the specific unit, false otherwise + */ + public static boolean hasSpecificUnit( + AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { + return AnnotationUtils.containsSame(annoType.getAnnotations(), unitsAnnotation); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with the default Prefix - * of {@code Prefix.one}. - * - *

This interface is intended only for subclasses of UnitsRelations; other clients should not - * use it. - * - * @param env the Checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) - * @return an AnnotationMirror of the Unit with Prefix.one, or null if it cannot be constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithDefaultPrefix( - ProcessingEnvironment env, Class annoClass) { - return buildAnnoMirrorWithSpecificPrefix(env, annoClass, Prefix.one); - } + /** + * Checks to see if a particular Annotated Type has a particular base unit (represented by its + * Annotation). + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of the base unit + * @return true if the Type has the specific unit, false otherwise + */ + public static boolean hasSpecificUnitIgnoringPrefix( + AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { + return AnnotationUtils.containsSameByName(annoType.getAnnotations(), unitsAnnotation); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. - * - *

This interface is intended only for subclasses of UnitsRelations; other clients should use - * {@link #buildAnnoMirrorWithNoPrefix(ProcessingEnvironment, CharSequence)}. - * - * @param env checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) - * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed - */ - static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( - ProcessingEnvironment env, Class annoClass) { - return AnnotationBuilder.fromClass(env.getElementUtils(), annoClass); - } + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific Prefix + * p. + * + *

This interface is intended only for subclasses of UnitsRelations; other clients should use + * {@link #buildAnnoMirrorWithSpecificPrefix(ProcessingEnvironment, CharSequence, Prefix)} + * + * @param env the Checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) + * @param p a Prefix value + * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( + ProcessingEnvironment env, Class annoClass, Prefix p) { + AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); + builder.setValue("value", p); + return builder.build(); + } + + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with the default Prefix + * of {@code Prefix.one}. + * + *

This interface is intended only for subclasses of UnitsRelations; other clients should not + * use it. + * + * @param env the Checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) + * @return an AnnotationMirror of the Unit with Prefix.one, or null if it cannot be constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithDefaultPrefix( + ProcessingEnvironment env, Class annoClass) { + return buildAnnoMirrorWithSpecificPrefix(env, annoClass, Prefix.one); + } + + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. + * + *

This interface is intended only for subclasses of UnitsRelations; other clients should use + * {@link #buildAnnoMirrorWithNoPrefix(ProcessingEnvironment, CharSequence)}. + * + * @param env checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) + * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed + */ + static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( + ProcessingEnvironment env, Class annoClass) { + return AnnotationBuilder.fromClass(env.getElementUtils(), annoClass); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java index 7937c850cb5..b44334b6547 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java @@ -3,7 +3,6 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.units.qual.UnknownUnits; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -15,29 +14,28 @@ *

Ensure consistent use of compound assignments. */ public class UnitsVisitor extends BaseTypeVisitor { - public UnitsVisitor(BaseTypeChecker checker) { - super(checker); - } + public UnitsVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - ExpressionTree var = tree.getVariable(); - ExpressionTree expr = tree.getExpression(); - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(var); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + ExpressionTree var = tree.getVariable(); + ExpressionTree expr = tree.getExpression(); + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(var); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); - Tree.Kind kind = tree.getKind(); + Tree.Kind kind = tree.getKind(); - if ((kind == Tree.Kind.PLUS_ASSIGNMENT || kind == Tree.Kind.MINUS_ASSIGNMENT)) { - if (!typeHierarchy.isSubtypeShallowEffective(exprType, varType)) { - checker.reportError( - tree, "compound.assignment.type.incompatible", varType, exprType); - } - } else if (!exprType.hasAnnotation(UnknownUnits.class)) { - // Only allow mul/div with unqualified units - checker.reportError(tree, "compound.assignment.type.incompatible", varType, exprType); - } - - return null; // super.visitCompoundAssignment(tree, p); + if ((kind == Tree.Kind.PLUS_ASSIGNMENT || kind == Tree.Kind.MINUS_ASSIGNMENT)) { + if (!typeHierarchy.isSubtypeShallowEffective(exprType, varType)) { + checker.reportError(tree, "compound.assignment.type.incompatible", varType, exprType); + } + } else if (!exprType.hasAnnotation(UnknownUnits.class)) { + // Only allow mul/div with unqualified units + checker.reportError(tree, "compound.assignment.type.incompatible", varType, exprType); } + + return null; // super.visitCompoundAssignment(tree, p); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java index 57513de6147..1041c10fd9a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java @@ -1,36 +1,35 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** Tests the conservative defaults for Initialization Checker and Nullness Checker. */ public class AnnotatedForNullnessTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AnnotatedForNullnessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AnnotatedForNullnessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); + } - /** - * This method returns the directories containing test code. Each directory will be type-checked - * with {@code -AuseConservativeDefaultsForUncheckedCode=source,bytecode}. - * - * @return the directories containing test code - */ - @Parameters - public static String[] getTestDirs() { - return new String[] { - "nulless-conservative-defaults/annotatedfornullness", - "nulless-conservative-defaults/packageannotatedfornullness" - }; - } + /** + * This method returns the directories containing test code. Each directory will be type-checked + * with {@code -AuseConservativeDefaultsForUncheckedCode=source,bytecode}. + * + * @return the directories containing test code + */ + @Parameters + public static String[] getTestDirs() { + return new String[] { + "nulless-conservative-defaults/annotatedfornullness", + "nulless-conservative-defaults/packageannotatedfornullness" + }; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java index 7605df2a60b..ee58c09852f 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java @@ -1,34 +1,33 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.checker.calledmethods.CalledMethodsChecker; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.calledmethods.CalledMethodsChecker; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** Test case for Called Methods Checker's AutoValue support. */ public class CalledMethodsAutoValueTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsAutoValueTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "com.google.auto.value.extension.memoized.processor.MemoizedValidator", - "com.google.auto.value.processor.AutoAnnotationProcessor", - "com.google.auto.value.processor.AutoOneOfProcessor", - "com.google.auto.value.processor.AutoValueBuilderProcessor", - "com.google.auto.value.processor.AutoValueProcessor", - CalledMethodsChecker.class.getName()), - "calledmethods-autovalue", - Collections.emptyList(), - "-nowarn"); - } + public CalledMethodsAutoValueTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "com.google.auto.value.extension.memoized.processor.MemoizedValidator", + "com.google.auto.value.processor.AutoAnnotationProcessor", + "com.google.auto.value.processor.AutoOneOfProcessor", + "com.google.auto.value.processor.AutoValueBuilderProcessor", + "com.google.auto.value.processor.AutoValueProcessor", + CalledMethodsChecker.class.getName()), + "calledmethods-autovalue", + Collections.emptyList(), + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-autovalue"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-autovalue"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java index 05c1d984183..a58762c2c1e 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java @@ -1,26 +1,25 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Basic tests for the Called Methods Checker. */ public class CalledMethodsDisableReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsDisableReturnsReceiverTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods-disablereturnsreceiver", - "-AdisableReturnsReceiver", - "-encoding", - "UTF-8"); - } + public CalledMethodsDisableReturnsReceiverTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods-disablereturnsreceiver", + "-AdisableReturnsReceiver", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-disablereturnsreceiver"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-disablereturnsreceiver"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java index da5a50cc260..3f1d6a8f1f8 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java @@ -1,37 +1,36 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.checker.calledmethods.CalledMethodsChecker; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.calledmethods.CalledMethodsChecker; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class CalledMethodsDisableframeworksTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsDisableframeworksTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "com.google.auto.value.extension.memoized.processor.MemoizedValidator", - "com.google.auto.value.processor.AutoAnnotationProcessor", - "com.google.auto.value.processor.AutoOneOfProcessor", - "com.google.auto.value.processor.AutoValueBuilderProcessor", - "com.google.auto.value.processor.AutoValueProcessor", - CalledMethodsChecker.class.getName()), - "calledmethods-disableframeworks", - Collections.emptyList(), - "-AdisableBuilderFrameworkSupports=autovalue,lombok", - // The next option is so that we can run the usevaluechecker tests under this - // configuration. - "-ACalledMethodsChecker_useValueChecker", - "-nowarn"); - } + public CalledMethodsDisableframeworksTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "com.google.auto.value.extension.memoized.processor.MemoizedValidator", + "com.google.auto.value.processor.AutoAnnotationProcessor", + "com.google.auto.value.processor.AutoOneOfProcessor", + "com.google.auto.value.processor.AutoValueBuilderProcessor", + "com.google.auto.value.processor.AutoValueProcessor", + CalledMethodsChecker.class.getName()), + "calledmethods-disableframeworks", + Collections.emptyList(), + "-AdisableBuilderFrameworkSupports=autovalue,lombok", + // The next option is so that we can run the usevaluechecker tests under this + // configuration. + "-ACalledMethodsChecker_useValueChecker", + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-disableframeworks", "calledmethods-usevaluechecker"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-disableframeworks", "calledmethods-usevaluechecker"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java index 4f73eb6a501..6717210f48f 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java @@ -1,25 +1,24 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Test that the Called Methods Checker's support for Lombok works correctly. */ public class CalledMethodsLombokTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsLombokTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods-delomboked", - "-nowarn", - "-AsuppressWarnings=type.anno.before.modifier"); - } + public CalledMethodsLombokTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods-delomboked", + "-nowarn", + "-AsuppressWarnings=type.anno.before.modifier"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-delomboked"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-delomboked"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java index 558ab47f4d0..2c316d1b912 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java @@ -1,7 +1,9 @@ package org.checkerframework.checker.test.junit; import com.google.common.collect.ImmutableList; - +import java.io.File; +import java.util.Collections; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; import org.checkerframework.framework.test.TestConfigurationBuilder; @@ -10,55 +12,49 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.Collections; -import java.util.List; - /** * This test suite exists to demonstrate and keep a record of the unsoundness that occurs when * Lombok and the Checker Framework are run in the same invocation of javac. */ public class CalledMethodsNoDelombokTest extends CheckerFrameworkPerDirectoryTest { - private static final ImmutableList ANNOTATION_PROCS = - ImmutableList.of( - "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", - "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", - org.checkerframework.checker.calledmethods.CalledMethodsChecker.class - .getName()); - - public CalledMethodsNoDelombokTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.calledmethods.CalledMethodsChecker.class, - "lombok", - "-nowarn"); - } - - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-nodelombok"}; - } - - /** - * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change - * the annotation processors to {@link #ANNOTATION_PROCS} - */ - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - classpathExtra, - ANNOTATION_PROCS, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); - TestUtilities.assertTestDidNotFail(adjustedTestResult); - } + private static final ImmutableList ANNOTATION_PROCS = + ImmutableList.of( + "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", + org.checkerframework.checker.calledmethods.CalledMethodsChecker.class.getName()); + + public CalledMethodsNoDelombokTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.calledmethods.CalledMethodsChecker.class, + "lombok", + "-nowarn"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-nodelombok"}; + } + + /** + * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change the + * annotation processors to {@link #ANNOTATION_PROCS} + */ + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + classpathExtra, + ANNOTATION_PROCS, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); + TestUtilities.assertTestDidNotFail(adjustedTestResult); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java index ac396d83db1..c3defdf6cce 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java @@ -1,28 +1,27 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Basic tests for the Called Methods Checker. */ public class CalledMethodsTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods", - "-nowarn", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-encoding", - "UTF-8"); - } + public CalledMethodsTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods", + "-nowarn", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java index 651932cc8fa..40b5bb09f2c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java @@ -1,24 +1,23 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; -import java.io.File; -import java.util.List; - public class CalledMethodsUseValueCheckerTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsUseValueCheckerTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods-usevaluechecker", - "-AuseValueChecker", - "-nowarn"); - } + public CalledMethodsUseValueCheckerTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods-usevaluechecker", + "-AuseValueChecker", + "-nowarn"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-usevaluechecker"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-usevaluechecker"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java index 6ace53a5b48..9343fd3005c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Compiler Messages Checker. Depends on the compiler.properties file. */ public class CompilerMessagesTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a CompilerMessagesTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public CompilerMessagesTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.compilermsgs.CompilerMessagesChecker.class, - "compilermsg", - "-Apropfiles=tests/compilermsg/compiler.properties"); - } + /** + * Create a CompilerMessagesTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public CompilerMessagesTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.compilermsgs.CompilerMessagesChecker.class, + "compilermsg", + "-Apropfiles=tests/compilermsg/compiler.properties"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"compilermsg", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"compilermsg", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java index caa4bdb64ad..b9bd2e5f719 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java @@ -1,32 +1,31 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the custom aliasing. */ public class CustomAliasTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a CustomAliasTest with the Nullness Checker and the Purity Checker. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public CustomAliasTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "custom-alias", - "-AaliasedTypeAnnos=org.checkerframework.checker.nullness.qual.NonNull:custom.alias.NonNull;" - + "org.checkerframework.checker.nullness.qual.Nullable:custom.alias.Nullable", - "-AaliasedDeclAnnos=org.checkerframework.dataflow.qual.Pure:custom.alias.Pure", - "-AcheckPurityAnnotations"); - } + /** + * Create a CustomAliasTest with the Nullness Checker and the Purity Checker. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public CustomAliasTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "custom-alias", + "-AaliasedTypeAnnos=org.checkerframework.checker.nullness.qual.NonNull:custom.alias.NonNull;" + + "org.checkerframework.checker.nullness.qual.Nullable:custom.alias.Nullable", + "-AaliasedDeclAnnos=org.checkerframework.dataflow.qual.Pure:custom.alias.Pure", + "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"custom-alias"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"custom-alias"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java index a14c14b9fbb..fc65d1e0ca2 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java @@ -1,35 +1,34 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.disbaruse.DisbarUseChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class DisbarUseTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a DisbarUseTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public DisbarUseTest(List testFiles) { - super( - testFiles, - DisbarUseChecker.class, - "disbaruse-records", - "-Astubs=tests/disbaruse-records", - "-AstubWarnIfNotFound"); - } + /** + * Create a DisbarUseTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public DisbarUseTest(List testFiles) { + super( + testFiles, + DisbarUseChecker.class, + "disbaruse-records", + "-Astubs=tests/disbaruse-records", + "-AstubWarnIfNotFound"); + } - @Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"disbaruse-records"}; - } else { - return new String[] {}; - } + @Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"disbaruse-records"}; + } else { + return new String[] {}; } + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java index dfc7f55fb4c..2a3e4c102b5 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java @@ -1,31 +1,30 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class FenumSwingTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FenumSwingTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FenumSwingTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.fenum.FenumChecker.class, - "fenum", - "-Aquals=org.checkerframework.checker.fenum.qual.SwingVerticalOrientation,org.checkerframework.checker.fenum.qual.SwingHorizontalOrientation,org.checkerframework.checker.fenum.qual.SwingBoxOrientation,org.checkerframework.checker.fenum.qual.SwingCompassDirection,org.checkerframework.checker.fenum.qual.SwingElementOrientation,org.checkerframework.checker.fenum.qual.SwingTextOrientation", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - // TODO: check all qualifiers - } + /** + * Create a FenumSwingTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FenumSwingTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.fenum.FenumChecker.class, + "fenum", + "-Aquals=org.checkerframework.checker.fenum.qual.SwingVerticalOrientation,org.checkerframework.checker.fenum.qual.SwingHorizontalOrientation,org.checkerframework.checker.fenum.qual.SwingBoxOrientation,org.checkerframework.checker.fenum.qual.SwingCompassDirection,org.checkerframework.checker.fenum.qual.SwingElementOrientation,org.checkerframework.checker.fenum.qual.SwingTextOrientation", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + // TODO: check all qualifiers + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"fenumswing", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"fenumswing", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java index 777086a832a..46c75c5577b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java @@ -1,24 +1,23 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class FenumTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FenumTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FenumTest(List testFiles) { - super(testFiles, org.checkerframework.checker.fenum.FenumChecker.class, "fenum"); - } + /** + * Create a FenumTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FenumTest(List testFiles) { + super(testFiles, org.checkerframework.checker.fenum.FenumChecker.class, "fenum"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"fenum", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"fenum", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java index e5d9b426753..2562011117e 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java @@ -4,26 +4,25 @@ // https://github.com/typetools/checker-framework/issues/691 // This exists to just run the FormatterLubGlbChecker. +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.lubglb.FormatterLubGlbChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class FormatterLubGlbCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FormatterLubGlbCheckerTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FormatterLubGlbCheckerTest(List testFiles) { - super(testFiles, FormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); - } + /** + * Create a FormatterLubGlbCheckerTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FormatterLubGlbCheckerTest(List testFiles) { + super(testFiles, FormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"formatter-lubglb"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"formatter-lubglb"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java index 58f88dc12d3..8da204177a4 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java @@ -1,26 +1,22 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class FormatterTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FormatterTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FormatterTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.formatter.FormatterChecker.class, - "formatter"); - } + /** + * Create a FormatterTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FormatterTest(List testFiles) { + super(testFiles, org.checkerframework.checker.formatter.FormatterChecker.class, "formatter"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"formatter", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"formatter", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java index 4096dfd7403..bf88181d555 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java @@ -1,27 +1,26 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class FormatterUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FormatterUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FormatterUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.formatter.FormatterChecker.class, - "formatter", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create a FormatterUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FormatterUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.formatter.FormatterChecker.class, + "formatter", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"formatter-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"formatter-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java index 20b67868668..fd179ea1ac1 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java @@ -6,40 +6,40 @@ public class FormatterUnitTest { - @SuppressWarnings("deprecation") // calls methods that are used only for testing - @Test - public void testConversionCharFromFormat() { - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$s")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tb")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$te")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tm")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tY")); - Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%+10.4f")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$s")); - Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%(,.2f")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("% testFiles) { - super( - testFiles, - org.checkerframework.checker.guieffect.GuiEffectChecker.class, - "guieffect"); - // , "-Alint=debugSpew"); - } + /** + * Create a GuiEffectTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public GuiEffectTest(List testFiles) { + super(testFiles, org.checkerframework.checker.guieffect.GuiEffectChecker.class, "guieffect"); + // , "-Alint=debugSpew"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"guieffect", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"guieffect", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java index 22f65cd1998..6f87e783e08 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java @@ -4,26 +4,25 @@ // https://github.com/typetools/checker-framework/issues/723 // This exists to just run the I18nFormatterLubGlbChecker. +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.lubglb.I18nFormatterLubGlbChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class I18nFormatterLubGlbCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nFormatterLubGlbCheckerTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nFormatterLubGlbCheckerTest(List testFiles) { - super(testFiles, I18nFormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); - } + /** + * Create an I18nFormatterLubGlbCheckerTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nFormatterLubGlbCheckerTest(List testFiles) { + super(testFiles, I18nFormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-formatter-lubglb"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-formatter-lubglb"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java index 003de4dff67..af3b2a2923f 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java @@ -1,27 +1,26 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class I18nFormatterTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nFormatterTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nFormatterTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, - "i18n-formatter"); - } + /** + * Create an I18nFormatterTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nFormatterTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, + "i18n-formatter"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-formatter", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-formatter", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java index 04cbbe0abcf..85ab191f97b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java @@ -1,28 +1,27 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class I18nFormatterUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nFormatterUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nFormatterUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, - "i18n-formatter", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create an I18nFormatterUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nFormatterUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, + "i18n-formatter", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-formatter-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-formatter-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java index ca85671f9dc..8f59efb1ab9 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java @@ -7,146 +7,135 @@ public class I18nFormatterUnitTest { - @Test - public void stringToI18nConversionCategoryTest() { - Assert.assertEquals( - I18nConversionCategory.NUMBER, - I18nConversionCategory.stringToI18nConversionCategory("number")); - Assert.assertEquals( - I18nConversionCategory.NUMBER, - I18nConversionCategory.stringToI18nConversionCategory("nuMber")); - Assert.assertEquals( - I18nConversionCategory.NUMBER, - I18nConversionCategory.stringToI18nConversionCategory("choice")); - Assert.assertEquals( - I18nConversionCategory.DATE, - I18nConversionCategory.stringToI18nConversionCategory("TIME")); - Assert.assertEquals( - I18nConversionCategory.DATE, - I18nConversionCategory.stringToI18nConversionCategory("DatE")); - Assert.assertEquals( - I18nConversionCategory.DATE, - I18nConversionCategory.stringToI18nConversionCategory("date")); - } + @Test + public void stringToI18nConversionCategoryTest() { + Assert.assertEquals( + I18nConversionCategory.NUMBER, + I18nConversionCategory.stringToI18nConversionCategory("number")); + Assert.assertEquals( + I18nConversionCategory.NUMBER, + I18nConversionCategory.stringToI18nConversionCategory("nuMber")); + Assert.assertEquals( + I18nConversionCategory.NUMBER, + I18nConversionCategory.stringToI18nConversionCategory("choice")); + Assert.assertEquals( + I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("TIME")); + Assert.assertEquals( + I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("DatE")); + Assert.assertEquals( + I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("date")); + } - @Test - public void isSubsetTest() { + @Test + public void isSubsetTest() { - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.UNUSED)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.GENERAL)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.UNUSED)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.GENERAL)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.UNUSED)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.GENERAL)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.UNUSED)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.GENERAL)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.UNUSED)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.GENERAL)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.UNUSED)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.GENERAL)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.UNUSED)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.GENERAL)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); - } + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.UNUSED)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.GENERAL)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); + } - @Test - public void hasFormatTest() { - Assert.assertTrue(I18nFormatUtil.hasFormat("{0}", I18nConversionCategory.GENERAL)); - Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1} {0, date}", - I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{0} and {1,number}", - I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1, number}", - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{2}", - I18nConversionCategory.UNUSED, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{3, number} {0} {1, time}", - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{0} {1, date} {2, time} {3, number} {5}", - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.DATE, - I18nConversionCategory.NUMBER, - I18nConversionCategory.UNUSED, - I18nConversionCategory.GENERAL)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1} {1, date}", - I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1, number} {1, date}", - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat("{0, date} {0, date}", I18nConversionCategory.DATE)); + @Test + public void hasFormatTest() { + Assert.assertTrue(I18nFormatUtil.hasFormat("{0}", I18nConversionCategory.GENERAL)); + Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1} {0, date}", I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{0} and {1,number}", I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1, number}", I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{2}", + I18nConversionCategory.UNUSED, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{3, number} {0} {1, time}", + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{0} {1, date} {2, time} {3, number} {5}", + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.DATE, + I18nConversionCategory.NUMBER, + I18nConversionCategory.UNUSED, + I18nConversionCategory.GENERAL)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1} {1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1, number} {1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); + Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date} {0, date}", I18nConversionCategory.DATE)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{1}", I18nConversionCategory.GENERAL)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.DATE)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.GENERAL)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.GENERAL)); - Assert.assertFalse( - I18nFormatUtil.hasFormat( - "{0, date}", I18nConversionCategory.DATE, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nFormatUtil.hasFormat("{0, date} {1, date}", I18nConversionCategory.DATE)); - } + Assert.assertFalse(I18nFormatUtil.hasFormat("{1}", I18nConversionCategory.GENERAL)); + Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.DATE)); + Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.GENERAL)); + Assert.assertFalse(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.GENERAL)); + Assert.assertFalse( + I18nFormatUtil.hasFormat( + "{0, date}", I18nConversionCategory.DATE, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nFormatUtil.hasFormat("{0, date} {1, date}", I18nConversionCategory.DATE)); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java index bd0e3687a65..277809dc5cb 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class I18nTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18n.I18nChecker.class, - "i18n", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create an I18nTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18n.I18nChecker.class, + "i18n", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java index b93127e08b3..ad2e0e9b278 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java @@ -1,28 +1,27 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class I18nUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18n.I18nChecker.class, - "i18n", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create an I18nUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18n.I18nChecker.class, + "i18n", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java index cdd19fb988a..b70832cbb56 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java @@ -1,36 +1,35 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Index Checker when running together with the InitializedFields Checker. */ public class IndexInitializedFieldsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an IndexTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public IndexInitializedFieldsTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "org.checkerframework.checker.index.IndexChecker", - "org.checkerframework.common.initializedfields.InitializedFieldsChecker"), - "index-initializedfields", - Collections.emptyList(), - "-Aajava=tests/index-initializedfields/input-annotation-files/", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create an IndexTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public IndexInitializedFieldsTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "org.checkerframework.checker.index.IndexChecker", + "org.checkerframework.common.initializedfields.InitializedFieldsChecker"), + "index-initializedfields", + Collections.emptyList(), + "-Aajava=tests/index-initializedfields/input-annotation-files/", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"index-initializedfields"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"index-initializedfields"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java index 4cae656a319..ddcc096f003 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java @@ -1,30 +1,29 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Index Checker. */ public class IndexTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an IndexTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public IndexTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.index.IndexChecker.class, - "index", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create an IndexTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public IndexTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.index.IndexChecker.class, + "index", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"index", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"index", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java index b20d0dc2a7d..94987f926c9 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java @@ -1,28 +1,24 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Interning Checker, which tests the Interned annotation. */ public class InterningTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an InterningTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InterningTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.interning.InterningChecker.class, - "interning"); - } + /** + * Create an InterningTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InterningTest(List testFiles) { + super(testFiles, org.checkerframework.checker.interning.InterningChecker.class, "interning"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"interning", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"interning", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java index 20636c13551..ff3d86e253e 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Interning checker when AwarnRedundantAnnotations is used. */ public class InterningWarnRedundantAnnotationsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a InterningWarnRedundantAnnotationsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InterningWarnRedundantAnnotationsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.interning.InterningChecker.class, - "interning-warnredundantannotations", - "-AwarnRedundantAnnotations"); - } + /** + * Create a InterningWarnRedundantAnnotationsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InterningWarnRedundantAnnotationsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.interning.InterningChecker.class, + "interning-warnredundantannotations", + "-AwarnRedundantAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"interning-warnredundantannotations"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"interning-warnredundantannotations"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java index ab7bed9d0d6..1ab3ac9bc57 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Lock checker when using safe defaults for unchecked source code. */ public class LockSafeDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a LockSafeDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public LockSafeDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.lock.LockChecker.class, - "lock", - "-AuseConservativeDefaultsForUncheckedCode=source"); - } + /** + * Create a LockSafeDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public LockSafeDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.lock.LockChecker.class, + "lock", + "-AuseConservativeDefaultsForUncheckedCode=source"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"lock-safedefaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"lock-safedefaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java index 7327a154632..c8a4bef88b2 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java @@ -1,34 +1,33 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class LockTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a LockTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public LockTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.lock.LockChecker.class, - "lock", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create a LockTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public LockTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.lock.LockChecker.class, + "lock", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"lock", "lock-records", "all-systems"}; - } else { - return new String[] {"lock", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"lock", "lock-records", "all-systems"}; + } else { + return new String[] {"lock", "all-systems"}; } + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java index 18d9f7e0519..8f072476ebf 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java @@ -1,24 +1,23 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class MustCallNoLightweightOwnershipTest extends CheckerFrameworkPerDirectoryTest { - public MustCallNoLightweightOwnershipTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.mustcall.MustCallChecker.class, - "mustcall-nolightweightownership", - "-AnoLightweightOwnership", - // "-AstubDebug"); - "-nowarn"); - } + public MustCallNoLightweightOwnershipTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.mustcall.MustCallChecker.class, + "mustcall-nolightweightownership", + "-AnoLightweightOwnership", + // "-AstubDebug"); + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"mustcall-nolightweightownership"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"mustcall-nolightweightownership"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java index 970fda0e605..b245673d793 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java @@ -1,23 +1,22 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class MustCallTest extends CheckerFrameworkPerDirectoryTest { - public MustCallTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.mustcall.MustCallChecker.class, - "mustcall", - // "-AstubDebug"); - "-nowarn"); - } + public MustCallTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.mustcall.MustCallChecker.class, + "mustcall", + // "-AstubDebug"); + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"mustcall"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"mustcall"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java index c2731b57045..d8995e3dc2b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java @@ -4,26 +4,25 @@ // https://github.com/typetools/checker-framework/issues/343 // This exists to just run the NestedAggregateChecker. +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.NestedAggregateChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class NestedAggregateCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NestedAggregateCheckerTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NestedAggregateCheckerTest(List testFiles) { - super(testFiles, NestedAggregateChecker.class, "", "-AcheckPurityAnnotations"); - } + /** + * Create a NestedAggregateCheckerTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NestedAggregateCheckerTest(List testFiles) { + super(testFiles, NestedAggregateChecker.class, "", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"aggregate", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"aggregate", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java index ee3c12300b9..b8ba91502df 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java @@ -1,37 +1,35 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** JUnit tests for the Nullness checker. */ public class NullnessAssertsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssertsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssertsTest(List testFiles) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AassumeAssertionsAreEnabled", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," - + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessAssertsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssertsTest(List testFiles) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AassumeAssertionsAreEnabled", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-asserts"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-asserts"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java index c3716e3c199..3cc7734beb6 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java @@ -1,30 +1,29 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness Checker. */ public class NullnessAssumeAssertionsAreDisabledTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssumeAssertionsAreDisabledTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssumeAssertionsAreDisabledTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AassumeAssertionsAreDisabled", - "-Xlint:deprecation"); - } + /** + * Create a NullnessAssumeAssertionsAreDisabledTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssumeAssertionsAreDisabledTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AassumeAssertionsAreDisabled", + "-Xlint:deprecation"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-assumeassertions"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-assumeassertions"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java index a5df11dcee7..1e13913ea50 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java @@ -1,38 +1,36 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** JUnit tests for the Nullness Checker without the Initialization Checker. */ public class NullnessAssumeInitializedTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssumeInitializedTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssumeInitializedTest(List testFiles) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AassumeInitialized", - "-AconservativeArgumentNullnessAfterInvocation=true", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," - + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessAssumeInitializedTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssumeInitializedTest(List testFiles) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AassumeInitialized", + "-AconservativeArgumentNullnessAfterInvocation=true", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness", "nullness-assumeinitialized", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness", "nullness-assumeinitialized", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java index 4dc3ce056d2..7d73f3b2c5c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java @@ -1,37 +1,35 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** JUnit tests for the Nullness checker. */ public class NullnessAssumeKeyForTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssumeKeyForTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssumeKeyForTest(List testFiles) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AassumeKeyFor", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," - + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessAssumeKeyForTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssumeKeyForTest(List testFiles) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AassumeKeyFor", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-assumekeyfor"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-assumekeyfor"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java index 165d35d9f31..237a94b2b90 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness checker when checkCastElementType is used. */ public class NullnessCheckCastElementTypeTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessCheckCastElementTypeTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessCheckCastElementTypeTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckCastElementType"); - } + /** + * Create a NullnessCheckCastElementTypeTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessCheckCastElementTypeTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckCastElementType"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-checkcastelementtype"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-checkcastelementtype"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java index e556a67c1ab..ad7d3ee9b18 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness checker when running with concurrent semantics. */ public class NullnessConcurrentTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessConcurrentTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessConcurrentTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AconcurrentSemantics"); - } + /** + * Create a NullnessConcurrentTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessConcurrentTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AconcurrentSemantics"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-concurrent-semantics"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-concurrent-semantics"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java index 4976c79e5b3..8220fcd8e9b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * JUnit test for the Nullness Checker with checking of enclosing expressions of inner class * instantiations enabled. */ public class NullnessEnclosingExprTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessEnclosingExprTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessEnclosingExprTest(List testFiles) { - super(testFiles, NullnessChecker.class, "nullness", "-AcheckEnclosingExpr"); - } + /** + * Create a NullnessEnclosingExprTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessEnclosingExprTest(List testFiles) { + super(testFiles, NullnessChecker.class, "nullness", "-AcheckEnclosingExpr"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-enclosingexpr"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-enclosingexpr"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java index 59963e12190..5b4905b3614 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java @@ -1,5 +1,8 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.Collections; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; @@ -9,57 +12,53 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.Collections; -import java.util.List; - /** JUnit tests for the Nullness checker for issue #511. */ public class NullnessGenericWildcardTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessGenericWildcardTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessGenericWildcardTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - // This test reads bytecode .class files created by NullnessGenericWildcardLibTest - "-cp", - TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); - } + /** + * Create a NullnessGenericWildcardTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessGenericWildcardTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + // This test reads bytecode .class files created by NullnessGenericWildcardLibTest + "-cp", + TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-genericwildcard"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-genericwildcard"}; + } - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions1 = customizeOptions(Collections.emptyList()); - TestConfiguration config1 = - TestConfigurationBuilder.buildDefaultConfiguration( - "tests/nullness-genericwildcardlib", - new File("tests/nullness-genericwildcardlib", "GwiParent.java"), - NullnessChecker.class, - customizedOptions1, - shouldEmitDebugInfo); - TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); - TestUtilities.assertTestDidNotFail(testResult1); + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions1 = customizeOptions(Collections.emptyList()); + TestConfiguration config1 = + TestConfigurationBuilder.buildDefaultConfiguration( + "tests/nullness-genericwildcardlib", + new File("tests/nullness-genericwildcardlib", "GwiParent.java"), + NullnessChecker.class, + customizedOptions1, + shouldEmitDebugInfo); + TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); + TestUtilities.assertTestDidNotFail(testResult1); - List customizedOptions2 = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config2 = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - Collections.singleton(NullnessChecker.class.getName()), - customizedOptions2, - shouldEmitDebugInfo); - TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); - TestUtilities.assertTestDidNotFail(testResult2); - } + List customizedOptions2 = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config2 = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + Collections.singleton(NullnessChecker.class.getName()), + customizedOptions2, + shouldEmitDebugInfo); + TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); + TestUtilities.assertTestDidNotFail(testResult2); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java index 15acf45cca6..77bc0f979f7 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness checker when array subtyping is invariant. */ public class NullnessInvariantArraysTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessInvariantArraysTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessInvariantArraysTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AinvariantArrays"); - } + /** + * Create a NullnessInvariantArraysTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessInvariantArraysTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AinvariantArrays"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-invariantarrays"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-invariantarrays"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java index 57886c339e2..b34ddc86056 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java @@ -1,30 +1,28 @@ package org.checkerframework.checker.test.junit; +import java.io.File; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; - /** JUnit tests for the Nullness checker that issue javac errors. */ public class NullnessJavacErrorsTest extends CheckerFrameworkPerFileTest { - public NullnessJavacErrorsTest(File testFile) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFile, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," - + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + public NullnessJavacErrorsTest(File testFile) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFile, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-javac-errors"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-javac-errors"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java index 5751d82f3ff..dbfde8a6eeb 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java @@ -1,46 +1,45 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.checkerframework.javacutil.SystemUtil; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.Collections; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.checkerframework.javacutil.SystemUtil; +import org.junit.runners.Parameterized.Parameters; /** * JUnit tests for the Nullness Checker -- testing type-checking of code that uses Javadoc classes. */ public class NullnessJavadocTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessJavadocTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - // required for JDK 8 (maybe not required for JDK 11, but it does no harm) - toolsJarList()); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessJavadocTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + // required for JDK 8 (maybe not required for JDK 11, but it does no harm) + toolsJarList()); + } - /** - * Return a list that contains the pathname to the tools.jar file, if it exists. - * - * @return a list that contains the pathname to the tools.jar file, if it exists - */ - private static List toolsJarList() { - String toolsJar = SystemUtil.getToolsJar(); - if (toolsJar == null) { - return Collections.emptyList(); - } else { - return Collections.singletonList(toolsJar); - } + /** + * Return a list that contains the pathname to the tools.jar file, if it exists. + * + * @return a list that contains the pathname to the tools.jar file, if it exists + */ + private static List toolsJarList() { + String toolsJar = SystemUtil.getToolsJar(); + if (toolsJar == null) { + return Collections.emptyList(); + } else { + return Collections.singletonList(toolsJar); } + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-javadoc"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-javadoc"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java index 7c5a0d5e95d..fbeb439e35e 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java @@ -1,7 +1,9 @@ package org.checkerframework.checker.test.junit; import com.google.common.collect.ImmutableList; - +import java.io.File; +import java.util.Collections; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; import org.checkerframework.framework.test.TestConfigurationBuilder; @@ -10,54 +12,49 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.Collections; -import java.util.List; - /** * This test suite exists to demonstrate and keep a record of the unsoundness that occurs when * Lombok and the Checker Framework are run in the same invocation of javac. */ public class NullnessNoDelombokTest extends CheckerFrameworkPerDirectoryTest { - private static final ImmutableList ANNOTATION_PROCS = - ImmutableList.of( - "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", - "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", - org.checkerframework.checker.nullness.NullnessChecker.class.getName()); - - public NullnessNoDelombokTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness-nodelombok", - "-nowarn"); - } - - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-nodelombok"}; - } - - /** - * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change - * the annotation processors to {@link #ANNOTATION_PROCS} - */ - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - classpathExtra, - ANNOTATION_PROCS, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); - TestUtilities.assertTestDidNotFail(adjustedTestResult); - } + private static final ImmutableList ANNOTATION_PROCS = + ImmutableList.of( + "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", + org.checkerframework.checker.nullness.NullnessChecker.class.getName()); + + public NullnessNoDelombokTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness-nodelombok", + "-nowarn"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-nodelombok"}; + } + + /** + * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change the + * annotation processors to {@link #ANNOTATION_PROCS} + */ + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + classpathExtra, + ANNOTATION_PROCS, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); + TestUtilities.assertTestDidNotFail(adjustedTestResult); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java index df697164a87..3cf9444ccd8 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java @@ -1,40 +1,39 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestUtilities; import org.junit.Test; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** JUnit tests for the Nullness checker. */ public class NullnessNullMarkedTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessNullMarkedTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessNullMarkedTest(List testFiles) { - super(testFiles, org.checkerframework.checker.nullness.NullnessChecker.class, "nullness"); - } + /** + * Create a NullnessNullMarkedTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessNullMarkedTest(List testFiles) { + super(testFiles, org.checkerframework.checker.nullness.NullnessChecker.class, "nullness"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-nullmarked"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-nullmarked"}; + } - @Override - @Test - public void run() { - /* - * Skip under JDK8: checker/bin-devel/build.sh doesn't build JSpecify under that version - * (since the JSpecify build requires JDK9+), so there would be no JSpecify jar, and tests - * would fail on account of the missing classes. - */ - if (TestUtilities.IS_AT_LEAST_9_JVM) { - super.run(); - } + @Override + @Test + public void run() { + /* + * Skip under JDK8: checker/bin-devel/build.sh doesn't build JSpecify under that version + * (since the JSpecify build requires JDK9+), so there would be no JSpecify jar, and tests + * would fail on account of the missing classes. + */ + if (TestUtilities.IS_AT_LEAST_9_JVM) { + super.run(); } + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java index 9cb896513ca..e003b381fa8 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** * JUnit tests for the Nullness Checker -- testing {@code -Alint=permitClearProperty} command-line @@ -12,19 +11,19 @@ */ public class NullnessPermitClearPropertyTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessPermitClearPropertyTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-Alint=permitClearProperty"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessPermitClearPropertyTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-Alint=permitClearProperty"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-permitClearProperty"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-permitClearProperty"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java index 0637242b65b..8da74b63c2b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java @@ -1,37 +1,36 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** JUnit tests for the Nullness checker with records (JDK16+ only). */ public class NullnessRecordsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessRecordsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessRecordsTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness-records", - "-AcheckPurityAnnotations", - "-Xlint:deprecation"); - } + /** + * Create a NullnessRecordsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessRecordsTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness-records", + "-AcheckPurityAnnotations", + "-Xlint:deprecation"); + } - @Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - // There is no decimal point in the JDK 17 version number. - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"nullness-records"}; - } else { - return new String[] {}; - } + @Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + // There is no decimal point in the JDK 17 version number. + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"nullness-records"}; + } else { + return new String[] {}; } + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java index 525a81bfa7d..bd817017291 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness checker when reflection resolution is enabled. */ public class NullnessReflectionTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessReflectionTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessReflectionTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AresolveReflection"); - } + /** + * Create a NullnessReflectionTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessReflectionTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AresolveReflection"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-reflection"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-reflection"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java index d5ffae2deab..212efb85395 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness checker when using safe defaults for unannotated bytecode. */ public class NullnessSafeDefaultsBytecodeTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSafeDefaultsBytecodeTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSafeDefaultsBytecodeTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AuseConservativeDefaultsForUncheckedCode=bytecode"); - } + /** + * Create a NullnessSafeDefaultsBytecodeTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSafeDefaultsBytecodeTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AuseConservativeDefaultsForUncheckedCode=bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-safedefaultsbytecode"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-safedefaultsbytecode"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java index dc8e868d617..63fec6f6530 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java @@ -1,5 +1,9 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; @@ -9,60 +13,55 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - /** JUnit tests for the Nullness checker when using safe defaults for unannotated source code. */ public class NullnessSafeDefaultsSourceCodeTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSafeDefaultsSourceCodeTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSafeDefaultsSourceCodeTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - "-AuseConservativeDefaultsForUncheckedCode=source", - "-cp", - TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); - } + /** + * Create a NullnessSafeDefaultsSourceCodeTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSafeDefaultsSourceCodeTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + "-AuseConservativeDefaultsForUncheckedCode=source", + "-cp", + TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-safedefaultssourcecode"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-safedefaultssourcecode"}; + } - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions1 = - customizeOptions( - Arrays.asList("-AuseConservativeDefaultsForUncheckedCode=source,bytecode")); - TestConfiguration config1 = - TestConfigurationBuilder.buildDefaultConfiguration( - "tests/nullness-safedefaultssourcecodelib", - new File("tests/nullness-safedefaultssourcecodelib", "Lib.java"), - NullnessChecker.class, - customizedOptions1, - shouldEmitDebugInfo); - TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); - TestUtilities.assertTestDidNotFail(testResult1); + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions1 = + customizeOptions( + Arrays.asList("-AuseConservativeDefaultsForUncheckedCode=source,bytecode")); + TestConfiguration config1 = + TestConfigurationBuilder.buildDefaultConfiguration( + "tests/nullness-safedefaultssourcecodelib", + new File("tests/nullness-safedefaultssourcecodelib", "Lib.java"), + NullnessChecker.class, + customizedOptions1, + shouldEmitDebugInfo); + TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); + TestUtilities.assertTestDidNotFail(testResult1); - List customizedOptions2 = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config2 = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - Collections.singleton(NullnessChecker.class.getName()), - customizedOptions2, - shouldEmitDebugInfo); - TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); - TestUtilities.assertTestDidNotFail(testResult2); - } + List customizedOptions2 = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config2 = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + Collections.singleton(NullnessChecker.class.getName()), + customizedOptions2, + shouldEmitDebugInfo); + TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); + TestUtilities.assertTestDidNotFail(testResult2); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java index 7865131017c..1ecb5f73ed7 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness Checker -- testing {@code -AskipDefs} command-line argument. */ public class NullnessSkipDefsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSkipDefsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSkipDefsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AskipDefs=SkipMe"); - } + /** + * Create a NullnessSkipDefsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSkipDefsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AskipDefs=SkipMe"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-skipdefs"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-skipdefs"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java index 077fc55365f..26b1e1b1117 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness Checker -- testing {@code -AskipUses} command-line argument. */ public class NullnessSkipUsesTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSkipUsesTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSkipUsesTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AskipUses=SkipMe"); - } + /** + * Create a NullnessSkipUsesTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSkipUsesTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AskipUses=SkipMe"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-skipuses"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-skipuses"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java index 99101a6033f..8081d88aba4 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java @@ -1,32 +1,31 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestUtilities; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class NullnessStubfileTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessStubfileTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessStubfileTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - TestUtilities.adapt( - "-Astubs=tests/nullness-stubfile/stubfile1.astub:" - + "tests/nullness-stubfile/stubfile2.astub:" - + "tests/nullness-stubfile/requireNonNull.astub")); - } + /** + * Create a NullnessStubfileTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessStubfileTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + TestUtilities.adapt( + "-Astubs=tests/nullness-stubfile/stubfile1.astub:" + + "tests/nullness-stubfile/stubfile2.astub:" + + "tests/nullness-stubfile/requireNonNull.astub")); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-stubfile"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-stubfile"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java index c7d54b0cb94..e2e4f6d4bee 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java @@ -1,31 +1,29 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** JUnit tests for the Nullness Checker. */ public class NullnessTempTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessTempTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessTempTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-Alint=soundArrayCreationNullness," - + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessTempTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessTempTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-temp"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-temp"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java index c4e519e96c1..d74c919da49 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * JUnit tests for the Nullness Checker with the Initialization Checker. * @@ -24,27 +23,24 @@ */ public class NullnessTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AconservativeArgumentNullnessAfterInvocation=true", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," - + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AconservativeArgumentNullnessAfterInvocation=true", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] { - "nullness", "nullness-initialization", "initialization", "all-systems" - }; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness", "nullness-initialization", "initialization", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java index 3f44fd1e469..3bb2111262c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Nullness checker when AwarnRedundantAnnotations is used. */ public class NullnessWarnRedundantAnnotationsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessWarnRedundantAnnotationsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessWarnRedundantAnnotationsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness-warnredundantannotations", - "-AwarnRedundantAnnotations"); - } + /** + * Create a NullnessWarnRedundantAnnotationsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessWarnRedundantAnnotationsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness-warnredundantannotations", + "-AwarnRedundantAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-warnredundantannotations"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-warnredundantannotations"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java index ca7d19aeacf..be95a29a0df 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Optional Checker, which has the {@code @Present} annotation. */ public class OptionalPureGettersTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an OptionalPureGettersTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public OptionalPureGettersTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.optional.OptionalChecker.class, - "optional", - "-AassumePureGetters"); - } + /** + * Create an OptionalPureGettersTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public OptionalPureGettersTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.optional.OptionalChecker.class, + "optional", + "-AassumePureGetters"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"optional-pure-getters"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"optional-pure-getters"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java index e9e36acf9a5..ee2087f2303 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Optional Checker, which has the {@code @Present} annotation. */ public class OptionalTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an OptionalTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public OptionalTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.optional.OptionalChecker.class, - "optional", - "-AoptionalMapAssumeNonNull"); - } + /** + * Create an OptionalTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public OptionalTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.optional.OptionalChecker.class, + "optional", + "-AoptionalMapAssumeNonNull"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"optional", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"optional", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java index 0694f4fafec..88dd3d0de0d 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java @@ -1,26 +1,25 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests {@code -AparseAllJdk} option. */ public class ParseAllJdkTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a ParseAllJdkTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public ParseAllJdkTest(List testFiles) { - super(testFiles, NullnessChecker.class, "parse-all-jdk", "-AparseAllJdk"); - } + /** + * Create a ParseAllJdkTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public ParseAllJdkTest(List testFiles) { + super(testFiles, NullnessChecker.class, "parse-all-jdk", "-AparseAllJdk"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"parse-all-jdk"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"parse-all-jdk"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java index 7d9f8688613..007998351ac 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java @@ -1,24 +1,23 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class RegexTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a RegexTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public RegexTest(List testFiles) { - super(testFiles, org.checkerframework.checker.regex.RegexChecker.class, "regex"); - } + /** + * Create a RegexTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public RegexTest(List testFiles) { + super(testFiles, org.checkerframework.checker.regex.RegexChecker.class, "regex"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"regex", "regex_poly", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"regex", "regex_poly", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java index bfecdb05d97..df6911b0620 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java @@ -1,26 +1,25 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; -import java.io.File; -import java.util.List; - public class ResourceLeakCustomIgnoredExceptionsTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakCustomIgnoredExceptionsTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-customignoredexceptions", - "-AresourceLeakIgnoredExceptions=java.lang.Error, =java.lang.NullPointerException", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakCustomIgnoredExceptionsTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-customignoredexceptions", + "-AresourceLeakIgnoredExceptions=java.lang.Error, =java.lang.NullPointerException", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-customignoredexceptions"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-customignoredexceptions"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java index 42addc33c03..9a69eac7c02 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java @@ -1,26 +1,25 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; -import java.io.File; -import java.util.List; - public class ResourceLeakExtraIgnoredExceptionsTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakExtraIgnoredExceptionsTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-extraignoredexceptions", - "-AresourceLeakIgnoredExceptions=default,java.lang.IllegalStateException", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakExtraIgnoredExceptionsTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-extraignoredexceptions", + "-AresourceLeakIgnoredExceptions=default,java.lang.IllegalStateException", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-extraignoredexceptions"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-extraignoredexceptions"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java index 6b73c882cb5..0243ed9ea4a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java @@ -1,27 +1,26 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the Resource Leak Checker. */ public class ResourceLeakNoCreatesMustCallForTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakNoCreatesMustCallForTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-nocreatesmustcallfor", - "-AnoCreatesMustCallFor", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakNoCreatesMustCallForTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-nocreatesmustcallfor", + "-AnoCreatesMustCallFor", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-nocreatesmustcallfor"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-nocreatesmustcallfor"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java index 3f0354b894d..629e93c5fd9 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java @@ -1,27 +1,26 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the Resource Leak Checker. */ public class ResourceLeakNoLightweightOwnershipTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakNoLightweightOwnershipTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-nolightweightownership", - "-AnoLightweightOwnership", - "-nowarn", - "-encoding", - "UTF-8"); - } + public ResourceLeakNoLightweightOwnershipTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-nolightweightownership", + "-AnoLightweightOwnership", + "-nowarn", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-nolightweightownership"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-nolightweightownership"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java index 99d77e202be..4d18f3075ee 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java @@ -1,27 +1,26 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the Resource Leak Checker. */ public class ResourceLeakNoResourceAliasesTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakNoResourceAliasesTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-noresourcealiases", - "-AnoResourceAliases", - "-nowarn", - "-encoding", - "UTF-8"); - } + public ResourceLeakNoResourceAliasesTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-noresourcealiases", + "-AnoResourceAliases", + "-nowarn", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-noresourcealiases"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-noresourcealiases"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java index 668217f1f08..1b6c605c758 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java @@ -1,27 +1,26 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the Resource Leak Checker. */ public class ResourceLeakPermitInitializationLeak extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakPermitInitializationLeak(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-permitinitializationleak", - "-ApermitInitializationLeak", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakPermitInitializationLeak(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-permitinitializationleak", + "-ApermitInitializationLeak", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-permitinitializationleak"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-permitinitializationleak"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java index 41347bd58d8..ffb496ea1e4 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java @@ -1,27 +1,26 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the Resource Leak Checker. */ public class ResourceLeakPermitStaticOwning extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakPermitStaticOwning(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-permitstaticowning", - "-ApermitStaticOwning", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakPermitStaticOwning(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-permitstaticowning", + "-ApermitStaticOwning", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-permitstaticowning"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-permitstaticowning"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java index e0868c6f63c..1329fefed04 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java @@ -1,26 +1,25 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the Resource Leak Checker. */ public class ResourceLeakTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java index c649e50fb31..ce5f97880b2 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java @@ -1,27 +1,23 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class SignatureTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignatureTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignatureTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.signature.SignatureChecker.class, - "signature"); - } + /** + * Create a SignatureTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignatureTest(List testFiles) { + super(testFiles, org.checkerframework.checker.signature.SignatureChecker.class, "signature"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signature", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signature", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java index 09ae2e1fe62..bd8f6caac2b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java @@ -1,33 +1,32 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class SignednessInitializedFieldsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignednessInitializedFieldsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignednessInitializedFieldsTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "org.checkerframework.common.initializedfields.InitializedFieldsChecker", - "org.checkerframework.checker.signedness.SignednessChecker"), - "signedness-initialized-fields", - Collections.emptyList() // classpathextra - ); - } + /** + * Create a SignednessInitializedFieldsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignednessInitializedFieldsTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "org.checkerframework.common.initializedfields.InitializedFieldsChecker", + "org.checkerframework.checker.signedness.SignednessChecker"), + "signedness-initialized-fields", + Collections.emptyList() // classpathextra + ); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signedness-initialized-fields", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signedness-initialized-fields", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java index c5f0946897b..47748c23945 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class SignednessTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignednessTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignednessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.signedness.SignednessChecker.class, - "signedness", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create a SignednessTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignednessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.signedness.SignednessChecker.class, + "signedness", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signedness", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signedness", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java index c75554f9e8b..9a6166f5069 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java @@ -1,28 +1,27 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class SignednessUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignednessUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignednessUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.signedness.SignednessChecker.class, - "signedness", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create a SignednessUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignednessUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.signedness.SignednessChecker.class, + "signedness", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signedness-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signedness-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java index f64fe68d0dd..4fd17302f70 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized; /** Tests for stub parsing. */ public class StubparserNullnessTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a StubparserNullnessTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public StubparserNullnessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "stubparser-nullness", - "-Astubs=tests/stubparser-nullness"); - } + /** + * Create a StubparserNullnessTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public StubparserNullnessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "stubparser-nullness", + "-Astubs=tests/stubparser-nullness"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"stubparser-nullness"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"stubparser-nullness"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java index 3b170c6a60a..2cd0a718f80 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java @@ -1,35 +1,34 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized; /** Tests for stub parsing with records. */ public class StubparserRecordTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a StubparserRecordTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public StubparserRecordTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "stubparser-records", - "-Astubs=tests/stubparser-records"); - } + /** + * Create a StubparserRecordTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public StubparserRecordTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "stubparser-records", + "-Astubs=tests/stubparser-records"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - // There is no decimal point in the JDK 17 version number. - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"stubparser-records"}; - } else { - return new String[] {}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + // There is no decimal point in the JDK 17 version number. + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"stubparser-records"}; + } else { + return new String[] {}; } + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java index b9e7508207c..55bac7a9cc5 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java @@ -1,30 +1,29 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized; /** Tests for stub parsing. */ public class StubparserTaintingTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a StubparserTaintingTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public StubparserTaintingTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.tainting.TaintingChecker.class, - "stubparser-tainting", - "-AmergeStubsWithSource", - "-Astubs=tests/stubparser-tainting"); - } + /** + * Create a StubparserTaintingTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public StubparserTaintingTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.tainting.TaintingChecker.class, + "stubparser-tainting", + "-AmergeStubsWithSource", + "-Astubs=tests/stubparser-tainting"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"stubparser-tainting", "all-systems"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"stubparser-tainting", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java index 1b6ddb9136a..5a2ce4ec997 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java @@ -1,25 +1,24 @@ package org.checkerframework.checker.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.checker.tainting.TaintingChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class TaintingTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a TaintingTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public TaintingTest(List testFiles) { - super(testFiles, TaintingChecker.class, "tainting"); - } + /** + * Create a TaintingTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public TaintingTest(List testFiles) { + super(testFiles, TaintingChecker.class, "tainting"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"tainting", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"tainting", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java index f2f37767005..b22d5d041dc 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java @@ -1,24 +1,23 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class UnitsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a UnitsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public UnitsTest(List testFiles) { - super(testFiles, org.checkerframework.checker.units.UnitsChecker.class, "units"); - } + /** + * Create a UnitsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public UnitsTest(List testFiles) { + super(testFiles, org.checkerframework.checker.units.UnitsChecker.class, "units"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"units", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"units", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java index edcaa5767f4..b2fc0e98432 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java @@ -1,30 +1,29 @@ package org.checkerframework.checker.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Value Checker's interactions with the Index Checker. */ public class ValueIndexInteractionTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a ValueIndexInteractionTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueIndexInteractionTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value-index-interaction", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create a ValueIndexInteractionTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueIndexInteractionTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value-index-interaction", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"value-index-interaction"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"value-index-interaction"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java index b84d0bf8cad..98f81fb75d7 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program inference with the aid of ajava files. This test is the first pass on the * test data, which generates the ajava files. This specific test suite is designed to elicit @@ -20,21 +19,21 @@ @Category(AinferIndexAjavaGenerationTest.class) public class AinferIndexAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferIndexAjavaGenerationTest(List testFiles) { - super( - testFiles, - IndexChecker.class, - "ainfer-index/non-annotated", - "-Ainfer=ajava", - // "-Aajava=tests/ainfer-index/input-annotation-files/", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferIndexAjavaGenerationTest(List testFiles) { + super( + testFiles, + IndexChecker.class, + "ainfer-index/non-annotated", + "-Ainfer=ajava", + // "-Aajava=tests/ainfer-index/input-annotation-files/", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-index/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-index/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java index 87cc53d482e..46c2f76ae3d 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program type inference with ajava files. This test is the second pass, which ensures * that with the ajava files in place, the errors that those annotations remove are no longer @@ -16,22 +15,22 @@ @Category(AinferIndexAjavaGenerationTest.class) public class AinferIndexAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferIndexAjavaValidationTest(List testFiles) { - super( - testFiles, - IndexChecker.class, - "index", - "ainfer-index/annotated", - AinferIndexAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "index"), - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferIndexAjavaValidationTest(List testFiles) { + super( + testFiles, + IndexChecker.class, + "index", + "ainfer-index/annotated", + AinferIndexAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "index"), + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-index/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-index/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java index e55b10de9cf..d61fb6a7a96 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program inference with the aid of ajava files. This test is the first pass on the * test data, which generates the ajava files. This specific test suite is designed to elicit @@ -20,21 +19,21 @@ @Category(AinferNullnessAjavaGenerationTest.class) public class AinferNullnessAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessAjavaGenerationTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "ainfer-nullness/non-annotated", - "-Ainfer=ajava", - "-Aajava=tests/ainfer-nullness/input-annotation-files/", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessAjavaGenerationTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "ainfer-nullness/non-annotated", + "-Ainfer=ajava", + "-Aajava=tests/ainfer-nullness/input-annotation-files/", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java index 334acae4f71..1288354c5a2 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program type inference with ajava files. This test is the second pass, which ensures * that with the ajava files in place, the errors that those annotations remove are no longer @@ -16,22 +15,22 @@ @Category(AinferNullnessAjavaGenerationTest.class) public class AinferNullnessAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessAjavaValidationTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - "ainfer-nullness/annotated", - AinferNullnessAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "nullness"), - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessAjavaValidationTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + "ainfer-nullness/annotated", + AinferNullnessAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "nullness"), + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java index 090023bc123..a755ab4bfc1 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Runs whole-program inference and inserts annotations into source code. * @@ -17,21 +16,21 @@ */ @Category(AinferNullnessJaifsGenerationTest.class) public class AinferNullnessJaifsGenerationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessJaifsGenerationTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - "-Ainfer=jaifs", - "-Awarns", - "-Aajava=tests/ainfer-nullness/input-annotation-files/"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessJaifsGenerationTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + "-Ainfer=jaifs", + "-Awarns", + "-Aajava=tests/ainfer-nullness/input-annotation-files/"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java index d9613d66b66..d332f78e91a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java @@ -1,39 +1,38 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program type inference with the aid of .jaif files. This test is the second pass, * which ensures that with the annotations inserted, the errors are no longer issued. */ @Category(AinferNullnessJaifsGenerationTest.class) public class AinferNullnessJaifsValidationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessJaifsValidationTest(List testFiles) { - super(testFiles, NullnessChecker.class, "nullness"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessJaifsValidationTest(List testFiles) { + super(testFiles, NullnessChecker.class, "nullness"); + } - @Override - public void run() { - // Only run if annotated files have been created. - // See ainferTest task. - if (!new File("tests/ainfer-nullness/annotated/").exists()) { - throw new RuntimeException( - AinferNullnessJaifsGenerationTest.class + " must be run before this test."); - } - super.run(); + @Override + public void run() { + // Only run if annotated files have been created. + // See ainferTest task. + if (!new File("tests/ainfer-nullness/annotated/").exists()) { + throw new RuntimeException( + AinferNullnessJaifsGenerationTest.class + " must be run before this test."); } + super.run(); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java index 19760313b7a..32a11b6c351 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests RLC-specific inference features with the aid of ajava files. This test is the first pass on * the test data, which generates the ajava files. @@ -21,21 +20,21 @@ @Category(AinferResourceLeakAjavaGenerationTest.class) public class AinferResourceLeakAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferResourceLeakAjavaGenerationTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "ainfer-resourceleak/non-annotated", - "-Ainfer=ajava", - // "-Aajava=tests/ainfer-resourceleak/input-annotation-files/", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferResourceLeakAjavaGenerationTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "ainfer-resourceleak/non-annotated", + "-Ainfer=ajava", + // "-Aajava=tests/ainfer-resourceleak/input-annotation-files/", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-resourceleak/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-resourceleak/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java index 88739d56fd6..59493b9240f 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests RLC-specific inference features with ajava files. This test is the second pass, which * ensures that with the ajava files in place, the errors that those annotations remove are no @@ -16,22 +15,22 @@ @Category(AinferResourceLeakAjavaGenerationTest.class) public class AinferResourceLeakAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferResourceLeakAjavaValidationTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak", - "ainfer-resourceleak/annotated", - AinferResourceLeakAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "resourceleak"), - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferResourceLeakAjavaValidationTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak", + "ainfer-resourceleak/annotated", + AinferResourceLeakAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "resourceleak"), + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-resourceleak/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-resourceleak/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java index 3807a530756..5d9949e1387 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program inference with the aid of ajava files. This test is the first pass on the * test data, which generates the ajava files. @@ -19,21 +18,21 @@ @Category(AinferTestCheckerAjavaGenerationTest.class) public class AinferTestCheckerAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerAjavaGenerationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "ainfer-testchecker/non-annotated", - "-Ainfer=ajava", - "-Aajava=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerAjavaGenerationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "ainfer-testchecker/non-annotated", + "-Ainfer=ajava", + "-Aajava=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java index 9cb1997fb73..09e49a0e3fd 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program type inference with ajava files. This test is the second pass, which ensures * that with the ajava files in place, the errors that those annotations remove are no longer @@ -16,23 +15,23 @@ @Category(AinferTestCheckerAjavaGenerationTest.class) public class AinferTestCheckerAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerAjavaValidationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "testchecker", - "ainfer-testchecker/annotated", - AinferTestCheckerAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "testchecker"), - "-AcheckPurityAnnotations", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerAjavaValidationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "testchecker", + "ainfer-testchecker/annotated", + AinferTestCheckerAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "testchecker"), + "-AcheckPurityAnnotations", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java index eba528c9634..bd109812699 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Runs whole-program inference and inserts annotations into source code. * @@ -17,25 +16,25 @@ */ @Category(AinferTestCheckerJaifsGenerationTest.class) public class AinferTestCheckerJaifsGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerJaifsGenerationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "ainfer-testchecker/non-annotated", - "-Ainfer=jaifs", - // Use a stub file here, even though this is a JAIF test. This test can't pass - // without an external file that specifies that a method is pure, and there is no - // way to directly pass a JAIF file (in a real WPI run, the JAIF file's annotations - // would have been inserted into the source). - "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerJaifsGenerationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "ainfer-testchecker/non-annotated", + "-Ainfer=jaifs", + // Use a stub file here, even though this is a JAIF test. This test can't pass + // without an external file that specifies that a method is pure, and there is no + // way to directly pass a JAIF file (in a real WPI run, the JAIF file's annotations + // would have been inserted into the source). + "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java index 1fe779d8fad..68fc2a17df6 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java @@ -1,35 +1,34 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program type inference with the aid of .jaif files. This test is the second pass, * which ensures that with the annotations inserted, the errors are no longer issued. */ @Category(AinferTestCheckerJaifsGenerationTest.class) public class AinferTestCheckerJaifsValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerJaifsValidationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "testchecker", - "ainfer-testchecker/non-annotated", - AinferTestCheckerJaifsGenerationTest.class, - "-Awarns", - "-AskipDefs=TestPure"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerJaifsValidationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "testchecker", + "ainfer-testchecker/non-annotated", + AinferTestCheckerJaifsGenerationTest.class, + "-Awarns", + "-AskipDefs=TestPure"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java index 23f8becb43b..e7ebb3a505f 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program inference with the aid of stub files. This test is the first pass on the test * data, which generates the stubs. @@ -19,21 +18,21 @@ @Category(AinferTestCheckerStubsGenerationTest.class) public class AinferTestCheckerStubsGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerStubsGenerationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "ainfer-testchecker/non-annotated", - "-Ainfer=stubs", - "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerStubsGenerationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "ainfer-testchecker/non-annotated", + "-Ainfer=stubs", + "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java index 778b91e3c77..138aa29e71b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.test.junit.ainferrunners; +import java.io.File; +import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests whole-program type inference with stub files. This test is the second pass, which ensures * that with the stubs in place, the errors that those annotations remove are no longer issued. @@ -15,25 +14,25 @@ @Category(AinferTestCheckerStubsGenerationTest.class) public class AinferTestCheckerStubsValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerStubsValidationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "testchecker", - "ainfer-testchecker/annotated", - AinferTestCheckerStubsGenerationTest.class, - astubsArgFromFiles(testFiles, "testchecker"), - // "-AstubDebug", - "-AmergeStubsWithSource", - "-Awarns", - "-AskipDefs=TestPure"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerStubsValidationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "testchecker", + "ainfer-testchecker/annotated", + AinferTestCheckerStubsGenerationTest.class, + astubsArgFromFiles(testFiles, "testchecker"), + // "-AstubDebug", + "-AmergeStubsWithSource", + "-Awarns", + "-AskipDefs=TestPure"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java index 567af32fc0b..87c7887dfbd 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java @@ -3,6 +3,8 @@ // Test case for Issue 343 // https://github.com/typetools/checker-framework/issues/343 +import java.util.ArrayList; +import java.util.Collection; import org.checkerframework.checker.fenum.FenumChecker; import org.checkerframework.checker.i18n.I18nChecker; import org.checkerframework.checker.nullness.NullnessChecker; @@ -10,20 +12,17 @@ import org.checkerframework.framework.source.AggregateChecker; import org.checkerframework.framework.source.SourceChecker; -import java.util.ArrayList; -import java.util.Collection; - public class NestedAggregateChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - ArrayList> list = - new ArrayList>(); + @Override + protected Collection> getSupportedCheckers() { + ArrayList> list = + new ArrayList>(); - list.add(FenumChecker.class); - list.add(I18nChecker.class); // The I18nChecker is an aggregate checker - list.add(NullnessChecker.class); - list.add(RegexChecker.class); + list.add(FenumChecker.class); + list.add(I18nChecker.class); // The I18nChecker is an aggregate checker + list.add(NullnessChecker.class); + list.add(RegexChecker.class); - return list; - } + return list; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java index 4ba8d643131..dff1fa0ab0d 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java @@ -1,5 +1,16 @@ package org.checkerframework.checker.testchecker.ainfer; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.testchecker.ainfer.qual.AinferBottom; import org.checkerframework.checker.testchecker.ainfer.qual.AinferDefaultType; import org.checkerframework.checker.testchecker.ainfer.qual.AinferImplicitAnno; @@ -27,19 +38,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** * AnnotatedTypeFactory to test whole-program inference using .jaif files. * @@ -64,238 +62,231 @@ */ public class AinferTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private final AnnotationMirror PARENT = - new AnnotationBuilder(processingEnv, AinferParent.class).build(); - private final AnnotationMirror BOTTOM = - new AnnotationBuilder(processingEnv, AinferBottom.class).build(); - private final AnnotationMirror IMPLICIT_ANNO = - new AnnotationBuilder(processingEnv, AinferImplicitAnno.class).build(); + private final AnnotationMirror PARENT = + new AnnotationBuilder(processingEnv, AinferParent.class).build(); + private final AnnotationMirror BOTTOM = + new AnnotationBuilder(processingEnv, AinferBottom.class).build(); + private final AnnotationMirror IMPLICIT_ANNO = + new AnnotationBuilder(processingEnv, AinferImplicitAnno.class).build(); - private final AnnotationMirror SIBLING1 = - new AnnotationBuilder(processingEnv, AinferSibling1.class).build(); + private final AnnotationMirror SIBLING1 = + new AnnotationBuilder(processingEnv, AinferSibling1.class).build(); - // NO-AFU - // private final AnnotationMirror TREAT_AS_SIBLING1 = - // new AnnotationBuilder(processingEnv, AinferTreatAsSibling1.class).build(); + // NO-AFU + // private final AnnotationMirror TREAT_AS_SIBLING1 = + // new AnnotationBuilder(processingEnv, AinferTreatAsSibling1.class).build(); - /** The AinferSiblingWithFields.value field/element. */ - private final ExecutableElement siblingWithFieldsValueElement = - TreeUtils.getMethod(AinferSiblingWithFields.class, "value", 0, processingEnv); + /** The AinferSiblingWithFields.value field/element. */ + private final ExecutableElement siblingWithFieldsValueElement = + TreeUtils.getMethod(AinferSiblingWithFields.class, "value", 0, processingEnv); - /** The AinferSiblingWithFields.value2 field/element. */ - private final ExecutableElement siblingWithFieldsValue2Element = - TreeUtils.getMethod(AinferSiblingWithFields.class, "value2", 0, processingEnv); + /** The AinferSiblingWithFields.value2 field/element. */ + private final ExecutableElement siblingWithFieldsValue2Element = + TreeUtils.getMethod(AinferSiblingWithFields.class, "value2", 0, processingEnv); + + /** + * Creates an AinferTestAnnotatedTypeFactory. + * + * @param checker the checker + */ + public AinferTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Support a declaration annotation that has the same meaning as @Sibling1, to test that the + // WPI feature allowing inference of declaration annotations works as intended. + addAliasedTypeAnnotation(AinferTreatAsSibling1.class, SIBLING1); + postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + AinferParent.class, + AinferDefaultType.class, + AinferTop.class, + AinferSibling1.class, + AinferSibling2.class, + AinferBottom.class, + AinferSiblingWithFields.class, + AinferImplicitAnno.class)); + } + + @Override + public TreeAnnotator createTreeAnnotator() { + LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); + literalTreeAnnotator.addLiteralKind(LiteralKind.INT, BOTTOM); + literalTreeAnnotator.addStandardLiteralQualifiers(); + + return new ListTreeAnnotator( + new PropagationTreeAnnotator(this), + literalTreeAnnotator, + new AinferTestTreeAnnotator(this)); + } + + protected static class AinferTestTreeAnnotator extends TreeAnnotator { /** - * Creates an AinferTestAnnotatedTypeFactory. + * Create a new AinferTestTreeAnnotator. * - * @param checker the checker + * @param atypeFactory the type factory */ - public AinferTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Support a declaration annotation that has the same meaning as @Sibling1, to test that the - // WPI feature allowing inference of declaration annotations works as intended. - addAliasedTypeAnnotation(AinferTreatAsSibling1.class, SIBLING1); - postInit(); + protected AinferTestTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } + /* NO-AFU @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - AinferParent.class, - AinferDefaultType.class, - AinferTop.class, - AinferSibling1.class, - AinferSibling2.class, - AinferBottom.class, - AinferSiblingWithFields.class, - AinferImplicitAnno.class)); + public Void visitClass(ClassTree classTree, AnnotatedTypeMirror type) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + TypeElement classElt = TreeUtils.elementFromDeclaration(classTree); + if (wpi != null && classElt.getSimpleName().contentEquals("IShouldBeSibling1")) { + wpi.addClassDeclarationAnnotation(classElt, SIBLING1); + } + return super.visitClass(classTree, type); } @Override - public TreeAnnotator createTreeAnnotator() { - LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); - literalTreeAnnotator.addLiteralKind(LiteralKind.INT, BOTTOM); - literalTreeAnnotator.addStandardLiteralQualifiers(); - - return new ListTreeAnnotator( - new PropagationTreeAnnotator(this), - literalTreeAnnotator, - new AinferTestTreeAnnotator(this)); + public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + VariableElement varElt = TreeUtils.elementFromDeclaration(variableTree); + if (wpi != null && varElt.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { + wpi.addFieldDeclarationAnnotation(varElt, TREAT_AS_SIBLING1); + } + return super.visitVariable(variableTree, type); } - protected static class AinferTestTreeAnnotator extends TreeAnnotator { - - /** - * Create a new AinferTestTreeAnnotator. - * - * @param atypeFactory the type factory - */ - protected AinferTestTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - /* NO-AFU - @Override - public Void visitClass(ClassTree classTree, AnnotatedTypeMirror type) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - TypeElement classElt = TreeUtils.elementFromDeclaration(classTree); - if (wpi != null && classElt.getSimpleName().contentEquals("IShouldBeSibling1")) { - wpi.addClassDeclarationAnnotation(classElt, SIBLING1); - } - return super.visitClass(classTree, type); - } - - @Override - public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - VariableElement varElt = TreeUtils.elementFromDeclaration(variableTree); - if (wpi != null && varElt.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { - wpi.addFieldDeclarationAnnotation(varElt, TREAT_AS_SIBLING1); - } - return super.visitVariable(variableTree, type); - } - - @Override - public Void visitMethod(MethodTree methodTree, AnnotatedTypeMirror type) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - if (wpi != null) { - ExecutableElement execElt = TreeUtils.elementFromDeclaration(methodTree); - int numParams = execElt.getParameters().size(); - for (int i = 0; i < numParams; ++i) { - VariableElement param = execElt.getParameters().get(i); - if (param.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { - wpi.addDeclarationAnnotationToFormalParameter(execElt, i + 1, TREAT_AS_SIBLING1); - } - } + @Override + public Void visitMethod(MethodTree methodTree, AnnotatedTypeMirror type) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + if (wpi != null) { + ExecutableElement execElt = TreeUtils.elementFromDeclaration(methodTree); + int numParams = execElt.getParameters().size(); + for (int i = 0; i < numParams; ++i) { + VariableElement param = execElt.getParameters().get(i); + if (param.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { + wpi.addDeclarationAnnotationToFormalParameter(execElt, i + 1, TREAT_AS_SIBLING1); } - return super.visitMethod(methodTree, type); } - end NO-AFU */ + } + return super.visitMethod(methodTree, type); } + end NO-AFU */ + } - @Override - public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(elt, type); - // If an element has an @AinferTreatAsSibling1 annotation, replace its type with - // @AinferSibling1. - // This should be handled by the fact that @AinferTreatAsSibling1 and @AinferSibling1 are - // aliases, but by default the CF does not look for declaration annotations - // that are aliases of type annotations in annotation files. - // TODO: is that a bug in the CF or expected behavior? - if (getDeclAnnotation(elt, AinferTreatAsSibling1.class) != null) { - type.replaceAnnotation(SIBLING1); - } + @Override + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(elt, type); + // If an element has an @AinferTreatAsSibling1 annotation, replace its type with + // @AinferSibling1. + // This should be handled by the fact that @AinferTreatAsSibling1 and @AinferSibling1 are + // aliases, but by default the CF does not look for declaration annotations + // that are aliases of type annotations in annotation files. + // TODO: is that a bug in the CF or expected behavior? + if (getDeclAnnotation(elt, AinferTreatAsSibling1.class) != null) { + type.replaceAnnotation(SIBLING1); } + } - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new AinferTestQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new AinferTestQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** + * Using a MultiGraphQualifierHierarchy to enable tests with Annotations that contain fields. + * + * @see AinferSiblingWithFields + */ + protected class AinferTestQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + private final QualifierKind SIBLING_WITH_FIELDS_KIND; /** - * Using a MultiGraphQualifierHierarchy to enable tests with Annotations that contain fields. + * Creates a AinferTestQualifierHierarchy from the given classes. * - * @see AinferSiblingWithFields + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils */ - protected class AinferTestQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - private final QualifierKind SIBLING_WITH_FIELDS_KIND; - - /** - * Creates a AinferTestQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - protected AinferTestQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, AinferTestAnnotatedTypeFactory.this); - SIBLING_WITH_FIELDS_KIND = - getQualifierKind(AinferSiblingWithFields.class.getCanonicalName()); - } + protected AinferTestQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, AinferTestAnnotatedTypeFactory.this); + SIBLING_WITH_FIELDS_KIND = getQualifierKind(AinferSiblingWithFields.class.getCanonicalName()); + } - @Override - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - return BOTTOM; - } + @Override + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + return BOTTOM; + } - @Override - public AnnotationMirrorSet getBottomAnnotations() { - return new AnnotationMirrorSet(BOTTOM); - } + @Override + public AnnotationMirrorSet getBottomAnnotations() { + return new AnnotationMirrorSet(BOTTOM); + } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { - return a1; - } else { - return IMPLICIT_ANNO; - } - } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - return a1; - } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { + return a1; + } else { + return IMPLICIT_ANNO; } + } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + return a1; + } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { - return a1; - } else { - return PARENT; - } - } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - return a1; - } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { + return a1; + } else { + return PARENT; } + } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + return a1; + } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == SIBLING_WITH_FIELDS_KIND && superKind == SIBLING_WITH_FIELDS_KIND) { - List subVal1 = - AnnotationUtils.getElementValueArray( - subAnno, - siblingWithFieldsValueElement, - String.class, - Collections.emptyList()); - List supVal1 = - AnnotationUtils.getElementValueArray( - superAnno, - siblingWithFieldsValueElement, - String.class, - Collections.emptyList()); - String subVal2 = - AnnotationUtils.getElementValue( - subAnno, siblingWithFieldsValue2Element, String.class, ""); - String supVal2 = - AnnotationUtils.getElementValue( - superAnno, siblingWithFieldsValue2Element, String.class, ""); - return subVal1.equals(supVal1) && subVal2.equals(supVal2); - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); - } + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == SIBLING_WITH_FIELDS_KIND && superKind == SIBLING_WITH_FIELDS_KIND) { + List subVal1 = + AnnotationUtils.getElementValueArray( + subAnno, siblingWithFieldsValueElement, String.class, Collections.emptyList()); + List supVal1 = + AnnotationUtils.getElementValueArray( + superAnno, siblingWithFieldsValueElement, String.class, Collections.emptyList()); + String subVal2 = + AnnotationUtils.getElementValue( + subAnno, siblingWithFieldsValue2Element, String.class, ""); + String supVal2 = + AnnotationUtils.getElementValue( + superAnno, siblingWithFieldsValue2Element, String.class, ""); + return subVal1.equals(supVal1) && subVal2.equals(supVal2); + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); } + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java index 1149f3a65c1..a767ad6b2b0 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java @@ -1,11 +1,10 @@ package org.checkerframework.checker.testchecker.ainfer; +import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; -import java.util.Set; - /** * Checker for a simple type system to test whole-program inference. Uses the Value Checker as a * subchecker to ensure that generated files contain annotations both from this checker and from the @@ -13,15 +12,15 @@ */ public class AinferTestChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new AinferTestVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new AinferTestVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java index af24c61b1f8..f5908466b92 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java @@ -3,39 +3,37 @@ import com.sun.source.tree.AnnotationTree; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeInfo; - +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.testchecker.ainfer.qual.AinferDefaultType; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; - /** Visitor for a simple type system to test whole-program inference using .jaif files. */ public class AinferTestVisitor extends BaseTypeVisitor { - public AinferTestVisitor(BaseTypeChecker checker) { - super(checker); - } + public AinferTestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected AinferTestAnnotatedTypeFactory createTypeFactory() { - return new AinferTestAnnotatedTypeFactory(checker); - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - Element anno = TreeInfo.symbol((JCTree) tree.getAnnotationType()); - if (anno.toString().equals(AinferDefaultType.class.getName())) { - checker.reportError(tree, "annotation.not.allowed.in.src", anno.toString()); - } - return super.visitAnnotation(tree, p); - } + @Override + protected AinferTestAnnotatedTypeFactory createTypeFactory() { + return new AinferTestAnnotatedTypeFactory(checker); + } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Skip this check. + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + Element anno = TreeInfo.symbol((JCTree) tree.getAnnotationType()); + if (anno.toString().equals(AinferDefaultType.class.getName())) { + checker.reportError(tree, "annotation.not.allowed.in.src", anno.toString()); } + return super.visitAnnotation(tree, p); + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Skip this check. + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java index cb325fd4f25..8634985e55c 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java @@ -1,13 +1,12 @@ package org.checkerframework.checker.testchecker.ainfer.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - /** * Toy type system for testing field inference. * diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java index ac18c134ec7..8fbdc9ed922 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * AinferDefaultType is used to test the relaxInference option. Toy type system for testing field diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java index 8e7f86a9702..51f2f7ab7d8 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.testchecker.ainfer.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.IgnoreInWholeProgramInference; import org.checkerframework.framework.qual.SubtypeOf; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - /** * Toy type system for testing field inference. * diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java index 8ba9566c2c2..fdc86cdd25f 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java @@ -1,9 +1,8 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java index 3dcf3b2ffd0..cb2785b9de7 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java @@ -1,9 +1,8 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java index 58f19ab886c..cd8bfa96c78 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java @@ -1,9 +1,8 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java index d557f6cc549..75b159d1af2 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java @@ -1,9 +1,8 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. @@ -15,7 +14,7 @@ @SubtypeOf(AinferParent.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface AinferSiblingWithFields { - String[] value() default {}; + String[] value() default {}; - String value2() default ""; + String value2() default ""; } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java index a8fadb9f548..9994c4c7f29 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java @@ -1,9 +1,8 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java index 12618d40780..fbd5b07d94a 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java @@ -1,29 +1,28 @@ package org.checkerframework.checker.testchecker.disbaruse; -import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUseBottom; -import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUseTop; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; +import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUseBottom; +import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUseTop; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; /** The type factory for forbidding use of the DisbarUse type. */ public class DisbarUseTypeFactory extends BaseAnnotatedTypeFactory { - /** - * Creates a new DisbarUseTypeFactory. - * - * @param checker the checker - */ - public DisbarUseTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + /** + * Creates a new DisbarUseTypeFactory. + * + * @param checker the checker + */ + public DisbarUseTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>(Arrays.asList(DisbarUseTop.class, DisbarUseBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>(Arrays.asList(DisbarUseTop.class, DisbarUseBottom.class)); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java index d56cd25f9f0..8c9b8506f93 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java @@ -5,69 +5,65 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; - +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUse; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; - public class DisbarUseVisitor extends BaseTypeVisitor { - public DisbarUseVisitor(BaseTypeChecker checker) { - super(checker); - } + public DisbarUseVisitor(BaseTypeChecker checker) { + super(checker); + } - protected DisbarUseVisitor(BaseTypeChecker checker, DisbarUseTypeFactory typeFactory) { - super(checker, typeFactory); - } + protected DisbarUseVisitor(BaseTypeChecker checker, DisbarUseTypeFactory typeFactory) { + super(checker, typeFactory); + } - @Override - protected DisbarUseTypeFactory createTypeFactory() { - return new DisbarUseTypeFactory(checker); - } + @Override + protected DisbarUseTypeFactory createTypeFactory() { + return new DisbarUseTypeFactory(checker); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - if (methodElt != null - && atypeFactory.getDeclAnnotation(methodElt, DisbarUse.class) != null) { - checker.reportError(tree, "disbar.use"); - } - return super.visitMethodInvocation(tree, p); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + if (methodElt != null && atypeFactory.getDeclAnnotation(methodElt, DisbarUse.class) != null) { + checker.reportError(tree, "disbar.use"); } + return super.visitMethodInvocation(tree, p); + } - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - ExecutableElement consElt = TreeUtils.elementFromUse(tree); - if (consElt != null && atypeFactory.getDeclAnnotation(consElt, DisbarUse.class) != null) { - checker.reportError(tree, "disbar.use"); - } - return super.visitNewClass(tree, p); + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + ExecutableElement consElt = TreeUtils.elementFromUse(tree); + if (consElt != null && atypeFactory.getDeclAnnotation(consElt, DisbarUse.class) != null) { + checker.reportError(tree, "disbar.use"); } + return super.visitNewClass(tree, p); + } - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - MemberSelectTree enclosingMemberSel = enclosingMemberSelect(); - ExpressionTree[] expressionTrees = - enclosingMemberSel == null - ? new ExpressionTree[] {tree} - : new ExpressionTree[] {enclosingMemberSel, tree}; + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + MemberSelectTree enclosingMemberSel = enclosingMemberSelect(); + ExpressionTree[] expressionTrees = + enclosingMemberSel == null + ? new ExpressionTree[] {tree} + : new ExpressionTree[] {enclosingMemberSel, tree}; - for (ExpressionTree memberSel : expressionTrees) { - Element elem = TreeUtils.elementFromUse(memberSel); + for (ExpressionTree memberSel : expressionTrees) { + Element elem = TreeUtils.elementFromUse(memberSel); - // We only issue errors for variables that are fields or parameters: - if (elem != null - && (elem.getKind().isField() || elem.getKind() == ElementKind.PARAMETER)) { - if (atypeFactory.getDeclAnnotation(elem, DisbarUse.class) != null) { - checker.reportError(memberSel, "disbar.use"); - } - } + // We only issue errors for variables that are fields or parameters: + if (elem != null && (elem.getKind().isField() || elem.getKind() == ElementKind.PARAMETER)) { + if (atypeFactory.getDeclAnnotation(elem, DisbarUse.class) != null) { + checker.reportError(memberSel, "disbar.use"); } - - return super.visitIdentifier(tree, p); + } } + + return super.visitIdentifier(tree, p); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java index 0bd267c7504..43d9fecf6bb 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java @@ -1,12 +1,11 @@ package org.checkerframework.checker.testchecker.disbaruse.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - @DefaultFor(TypeUseLocation.LOWER_BOUND) @SubtypeOf(DisbarUseTop.class) @Target({ElementType.TYPE_USE}) diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java index 36e8678e16c..39749401b0e 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java @@ -1,10 +1,9 @@ package org.checkerframework.checker.testchecker.disbaruse.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE}) @SubtypeOf({}) diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java index fb1a74a4cf7..d416f1db9e3 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java @@ -4,6 +4,12 @@ // https://github.com/typetools/checker-framework/issues/691 // https://github.com/typetools/checker-framework/issues/756 +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.formatter.FormatterAnnotatedTypeFactory; import org.checkerframework.checker.formatter.FormatterChecker; import org.checkerframework.checker.formatter.FormatterTreeUtil; @@ -19,14 +25,6 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** * This class tests the implementation of GLB computation in the Formatter Checker, but it does not * test for the crash described in issue 691. That is done by tests/all-systems/Issue691.java. It @@ -34,510 +32,495 @@ */ public class FormatterLubGlbChecker extends FormatterChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new FormatterVisitor(this) { - @Override - protected FormatterLubGlbAnnotatedTypeFactory createTypeFactory() { - return new FormatterLubGlbAnnotatedTypeFactory(checker); - } - }; - } - - /** FormatterLubGlbAnnotatedTypeFactory. */ - private static class FormatterLubGlbAnnotatedTypeFactory extends FormatterAnnotatedTypeFactory { - - /** - * Constructor. - * - * @param checker checker - */ - public FormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - FormatBottom.class, - Format.class, - InvalidFormat.class, - UnknownFormat.class)); - } - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new FormatterVisitor(this) { + @Override + protected FormatterLubGlbAnnotatedTypeFactory createTypeFactory() { + return new FormatterLubGlbAnnotatedTypeFactory(checker); + } + }; + } - /** - * Throws an exception if glb(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void glbAssert( - AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format( - "GLB of %s and %s should be %s, but is %s", - arg1, arg2, expected, result)); - } - } + /** FormatterLubGlbAnnotatedTypeFactory. */ + private static class FormatterLubGlbAnnotatedTypeFactory extends FormatterAnnotatedTypeFactory { /** - * Throws an exception if lub(arg1, arg2) != result. + * Constructor. * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result + * @param checker checker */ - private void lubAssert( - AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format( - "LUB of %s and %s should be %s, but is %s", - arg1, arg2, expected, result)); - } + public FormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); } - @SuppressWarnings("checkstyle:localvariablename") @Override - public void initChecker() { - super.initChecker(); - FormatterTreeUtil treeUtil = new FormatterTreeUtil(this); - - Elements elements = getElementUtils(); - AnnotationMirror UNKNOWNFORMAT = AnnotationBuilder.fromClass(elements, UnknownFormat.class); - AnnotationMirror FORMAT = - AnnotationBuilder.fromClass( - elements, - Format.class, - AnnotationBuilder.elementNamesValues("value", new ConversionCategory[0])); - AnnotationMirror INVALIDFORMAT = - AnnotationBuilder.fromClass( - elements, - InvalidFormat.class, - AnnotationBuilder.elementNamesValues("value", "dummy")); - AnnotationMirror FORMATBOTTOM = AnnotationBuilder.fromClass(elements, FormatBottom.class); - - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "Message"); - AnnotationMirror invalidFormatWithMessage = builder.build(); - - builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "Message2"); - AnnotationMirror invalidFormatWithMessage2 = builder.build(); - - builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "(\"Message\" or \"Message2\")"); - AnnotationMirror invalidFormatWithMessagesOred = builder.build(); - - builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "(\"Message\" and \"Message2\")"); - AnnotationMirror invalidFormatWithMessagesAnded = builder.build(); - - ConversionCategory[] cc = new ConversionCategory[1]; - - cc[0] = ConversionCategory.UNUSED; - AnnotationMirror formatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.GENERAL; - AnnotationMirror formatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.CHAR; - AnnotationMirror formatCharAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.INT; - AnnotationMirror formatIntAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.TIME; - AnnotationMirror formatTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.FLOAT; - AnnotationMirror formatFloatAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.CHAR_AND_INT; - AnnotationMirror formatCharAndIntAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.INT_AND_TIME; - AnnotationMirror formatIntAndTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.NULL; - AnnotationMirror formatNullAnno = treeUtil.categoriesToFormatAnnotation(cc); - - // ** GLB tests ** - - glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); - - // GLB of UNUSED and others - - glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatGeneralAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatCharAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatIntAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatTimeAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatFloatAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatCharAndIntAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatIntAndTimeAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatNullAnno, formatUnusedAnno); - - // GLB of GENERAL and others - - glbAssert(formatGeneralAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatCharAndIntAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatIntAndTimeAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatNullAnno, formatGeneralAnno); - - // GLB of CHAR and others - - glbAssert(formatCharAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatCharAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatCharAnno, formatCharAnno); - glbAssert(formatCharAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatCharAndIntAnno, formatCharAnno); - glbAssert(formatCharAnno, formatIntAndTimeAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatNullAnno, formatCharAnno); - - // GLB of INT and others - - glbAssert(formatIntAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatIntAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatIntAnno, formatIntAnno); - glbAssert(formatIntAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatCharAndIntAnno, formatIntAnno); - glbAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAnno); - glbAssert(formatIntAnno, formatNullAnno, formatIntAnno); - - // GLB of TIME and others - - glbAssert(formatTimeAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatTimeAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); - glbAssert(formatTimeAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatCharAndIntAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatIntAndTimeAnno, formatTimeAnno); - glbAssert(formatTimeAnno, formatNullAnno, formatTimeAnno); - - // GLB of FLOAT and others - - glbAssert(formatFloatAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatFloatAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); - glbAssert(formatFloatAnno, formatCharAndIntAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatIntAndTimeAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatNullAnno, formatFloatAnno); - - // GLB of CHAR_AND_INT and others - - glbAssert(formatCharAndIntAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatCharAndIntAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatCharAndIntAnno, formatCharAnno, formatCharAnno); - glbAssert(formatCharAndIntAnno, formatIntAnno, formatIntAnno); - glbAssert(formatCharAndIntAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatCharAndIntAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); - glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); - glbAssert(formatCharAndIntAnno, formatNullAnno, formatCharAndIntAnno); - - // GLB of INT_AND_TIME and others - - glbAssert(formatIntAndTimeAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatIntAndTimeAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatIntAndTimeAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAnno); - glbAssert(formatIntAndTimeAnno, formatTimeAnno, formatTimeAnno); - glbAssert(formatIntAndTimeAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatIntAnno); - glbAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - glbAssert(formatIntAndTimeAnno, formatNullAnno, formatIntAndTimeAnno); - - // GLB of NULL and others - - glbAssert(formatNullAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatNullAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatNullAnno, formatCharAnno, formatCharAnno); - glbAssert(formatNullAnno, formatIntAnno, formatIntAnno); - glbAssert(formatNullAnno, formatTimeAnno, formatTimeAnno); - glbAssert(formatNullAnno, formatFloatAnno, formatFloatAnno); - glbAssert(formatNullAnno, formatCharAndIntAnno, formatCharAndIntAnno); - glbAssert(formatNullAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - glbAssert(formatNullAnno, formatNullAnno, formatNullAnno); - - // Now test with two ConversionCategory at a time: - - ConversionCategory[] cc2 = new ConversionCategory[2]; - - cc2[0] = ConversionCategory.CHAR_AND_INT; - cc2[1] = ConversionCategory.FLOAT; - AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = ConversionCategory.INT; - cc2[1] = ConversionCategory.CHAR; - AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = ConversionCategory.INT; - cc2[1] = ConversionCategory.GENERAL; - AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); - - glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); - - // Test that the GLB of two ConversionCategory arrays of different sizes is an array of the - // smallest size of the two: - - glbAssert(formatGeneralAnno, formatTwoConvCat1, formatGeneralAnno); - glbAssert(formatTwoConvCat2, formatNullAnno, formatIntAnno); - - // GLB of @UnknownFormat and others - - glbAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - glbAssert(UNKNOWNFORMAT, FORMAT, FORMAT); - glbAssert(UNKNOWNFORMAT, formatUnusedAnno, formatUnusedAnno); - glbAssert(UNKNOWNFORMAT, INVALIDFORMAT, INVALIDFORMAT); - glbAssert(UNKNOWNFORMAT, invalidFormatWithMessage, invalidFormatWithMessage); - glbAssert(UNKNOWNFORMAT, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @Format(null) and others - - glbAssert(FORMAT, UNKNOWNFORMAT, FORMAT); - // Computing the GLB of @Format(null) and @Format(null) should never occur in practice; - // skipping this case as it causes an expected crash. - // Computing the GLB of @Format(null) and @Format with a value should never occur in - // practice; skipping this case as it causes an expected crash. - glbAssert(FORMAT, INVALIDFORMAT, FORMATBOTTOM); - glbAssert(FORMAT, invalidFormatWithMessage, FORMATBOTTOM); - glbAssert(FORMAT, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @Format(UNUSED) and others - - glbAssert(formatUnusedAnno, UNKNOWNFORMAT, formatUnusedAnno); - // Computing the GLB of @Format with a value and @Format(null) should never occur in - // practice; skipping this case as it causes an expected crash. - glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, INVALIDFORMAT, FORMATBOTTOM); - glbAssert(formatUnusedAnno, invalidFormatWithMessage, FORMATBOTTOM); - glbAssert(formatUnusedAnno, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @InvalidFormat(null) and others - - glbAssert(INVALIDFORMAT, UNKNOWNFORMAT, INVALIDFORMAT); - glbAssert(INVALIDFORMAT, FORMAT, FORMATBOTTOM); - glbAssert(INVALIDFORMAT, formatUnusedAnno, FORMATBOTTOM); - glbAssert(INVALIDFORMAT, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @InvalidFormat("Message") and others - - glbAssert(invalidFormatWithMessage, UNKNOWNFORMAT, invalidFormatWithMessage); - glbAssert(invalidFormatWithMessage, FORMAT, FORMATBOTTOM); - glbAssert(invalidFormatWithMessage, formatUnusedAnno, FORMATBOTTOM); - glbAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); - glbAssert( - invalidFormatWithMessage, - invalidFormatWithMessage2, - invalidFormatWithMessagesAnded); - glbAssert(invalidFormatWithMessage, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @FormatBottom and others - - glbAssert(FORMATBOTTOM, UNKNOWNFORMAT, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, FORMAT, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, formatUnusedAnno, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, INVALIDFORMAT, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, invalidFormatWithMessage, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); - - // ** LUB tests ** - - // LUB of UNUSED and others - - lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - lubAssert(formatUnusedAnno, formatGeneralAnno, formatGeneralAnno); - lubAssert(formatUnusedAnno, formatCharAnno, formatCharAnno); - lubAssert(formatUnusedAnno, formatIntAnno, formatIntAnno); - lubAssert(formatUnusedAnno, formatTimeAnno, formatTimeAnno); - lubAssert(formatUnusedAnno, formatFloatAnno, formatFloatAnno); - lubAssert(formatUnusedAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatUnusedAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatUnusedAnno, formatNullAnno, formatNullAnno); - - // LUB of GENERAL and others - - lubAssert(formatGeneralAnno, formatUnusedAnno, formatGeneralAnno); - lubAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); - lubAssert(formatGeneralAnno, formatCharAnno, formatCharAnno); - lubAssert(formatGeneralAnno, formatIntAnno, formatIntAnno); - lubAssert(formatGeneralAnno, formatTimeAnno, formatTimeAnno); - lubAssert(formatGeneralAnno, formatFloatAnno, formatFloatAnno); - lubAssert(formatGeneralAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatGeneralAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatGeneralAnno, formatNullAnno, formatNullAnno); - - // LUB of CHAR and others - - lubAssert(formatCharAnno, formatUnusedAnno, formatCharAnno); - lubAssert(formatCharAnno, formatGeneralAnno, formatCharAnno); - lubAssert(formatCharAnno, formatCharAnno, formatCharAnno); - lubAssert(formatCharAnno, formatIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatCharAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatCharAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatCharAnno, formatNullAnno, formatNullAnno); - - // LUB of INT and others - - lubAssert(formatIntAnno, formatUnusedAnno, formatIntAnno); - lubAssert(formatIntAnno, formatGeneralAnno, formatIntAnno); - lubAssert(formatIntAnno, formatCharAnno, formatCharAndIntAnno); - lubAssert(formatIntAnno, formatIntAnno, formatIntAnno); - lubAssert(formatIntAnno, formatTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAnno, formatNullAnno, formatNullAnno); - - // LUB of TIME and others - - lubAssert(formatTimeAnno, formatUnusedAnno, formatTimeAnno); - lubAssert(formatTimeAnno, formatGeneralAnno, formatTimeAnno); - lubAssert(formatTimeAnno, formatCharAnno, formatNullAnno); - lubAssert(formatTimeAnno, formatIntAnno, formatIntAndTimeAnno); - lubAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); - lubAssert(formatTimeAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatTimeAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatTimeAnno, formatNullAnno, formatNullAnno); - - // LUB of FLOAT and others - - lubAssert(formatFloatAnno, formatUnusedAnno, formatFloatAnno); - lubAssert(formatFloatAnno, formatGeneralAnno, formatFloatAnno); - lubAssert(formatFloatAnno, formatCharAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatIntAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); - lubAssert(formatFloatAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatNullAnno, formatNullAnno); - - // LUB of CHAR_AND_INT and others - - lubAssert(formatCharAndIntAnno, formatUnusedAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatGeneralAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatCharAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatCharAndIntAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatCharAndIntAnno, formatNullAnno, formatNullAnno); - - // LUB of INT_AND_TIME and others - - lubAssert(formatIntAndTimeAnno, formatUnusedAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatGeneralAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatCharAnno, formatNullAnno); - lubAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatNullAnno, formatNullAnno); - - // LUB of NULL and others - - lubAssert(formatNullAnno, formatUnusedAnno, formatNullAnno); - lubAssert(formatNullAnno, formatGeneralAnno, formatNullAnno); - lubAssert(formatNullAnno, formatCharAnno, formatNullAnno); - lubAssert(formatNullAnno, formatIntAnno, formatNullAnno); - lubAssert(formatNullAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatNullAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatNullAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatNullAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatNullAnno, formatNullAnno, formatNullAnno); - - // Now test with two ConversionCategory at a time: - - cc2[0] = ConversionCategory.CHAR_AND_INT; - cc2[1] = ConversionCategory.NULL; - AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = ConversionCategory.NULL; - cc2[1] = ConversionCategory.CHAR; - AnnotationMirror formatTwoConvCat5 = treeUtil.categoriesToFormatAnnotation(cc2); - - lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); - - // Test that the LUB of two ConversionCategory arrays of different sizes is an array of the - // largest size of the two: - - lubAssert(formatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); - lubAssert(formatTwoConvCat2, formatNullAnno, formatTwoConvCat5); - - // LUB of @UnknownFormat and others - - lubAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, FORMAT, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, formatUnusedAnno, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, INVALIDFORMAT, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, FORMATBOTTOM, UNKNOWNFORMAT); - - // LUB of @Format(null) and others - - lubAssert(FORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - // Computing the LUB of @Format(null) and @Format(null) should never occur in practice; - // skipping this case as it causes an expected crash. - // Computing the LUB of @Format(null) and @Format with a value should never occur in - // practice; skipping this case as it causes an expected crash. - lubAssert(FORMAT, INVALIDFORMAT, UNKNOWNFORMAT); - lubAssert(FORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); - lubAssert(FORMAT, FORMATBOTTOM, FORMAT); - - // LUB of @Format(UNUSED) and others - - lubAssert(formatUnusedAnno, UNKNOWNFORMAT, UNKNOWNFORMAT); - // Computing the LUB of @Format with a value and @Format(null) should never occur in - // practice; skipping this case as it causes an expected crash. - lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - lubAssert(formatUnusedAnno, INVALIDFORMAT, UNKNOWNFORMAT); - lubAssert(formatUnusedAnno, invalidFormatWithMessage, UNKNOWNFORMAT); - lubAssert(formatUnusedAnno, FORMATBOTTOM, formatUnusedAnno); - - // LUB of @InvalidFormat(null) and others - - lubAssert(INVALIDFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(INVALIDFORMAT, FORMAT, UNKNOWNFORMAT); - lubAssert(INVALIDFORMAT, formatUnusedAnno, UNKNOWNFORMAT); - lubAssert(INVALIDFORMAT, FORMATBOTTOM, INVALIDFORMAT); - - // LUB of @InvalidFormat("Message") and others - - lubAssert(invalidFormatWithMessage, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(invalidFormatWithMessage, FORMAT, UNKNOWNFORMAT); - lubAssert(invalidFormatWithMessage, formatUnusedAnno, UNKNOWNFORMAT); - lubAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); - lubAssert( - invalidFormatWithMessage, invalidFormatWithMessage2, invalidFormatWithMessagesOred); - lubAssert(invalidFormatWithMessage, FORMATBOTTOM, invalidFormatWithMessage); - - // LUB of @FormatBottom and others - - lubAssert(FORMATBOTTOM, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(FORMATBOTTOM, FORMAT, FORMAT); - lubAssert(FORMATBOTTOM, formatUnusedAnno, formatUnusedAnno); - lubAssert(FORMATBOTTOM, INVALIDFORMAT, INVALIDFORMAT); - lubAssert(FORMATBOTTOM, invalidFormatWithMessage, invalidFormatWithMessage); - lubAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + FormatBottom.class, Format.class, InvalidFormat.class, UnknownFormat.class)); + } + } + + /** + * Throws an exception if glb(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void glbAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format("GLB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + } + } + + /** + * Throws an exception if lub(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void lubAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format("LUB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); } + } + + @SuppressWarnings("checkstyle:localvariablename") + @Override + public void initChecker() { + super.initChecker(); + FormatterTreeUtil treeUtil = new FormatterTreeUtil(this); + + Elements elements = getElementUtils(); + AnnotationMirror UNKNOWNFORMAT = AnnotationBuilder.fromClass(elements, UnknownFormat.class); + AnnotationMirror FORMAT = + AnnotationBuilder.fromClass( + elements, + Format.class, + AnnotationBuilder.elementNamesValues("value", new ConversionCategory[0])); + AnnotationMirror INVALIDFORMAT = + AnnotationBuilder.fromClass( + elements, InvalidFormat.class, AnnotationBuilder.elementNamesValues("value", "dummy")); + AnnotationMirror FORMATBOTTOM = AnnotationBuilder.fromClass(elements, FormatBottom.class); + + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "Message"); + AnnotationMirror invalidFormatWithMessage = builder.build(); + + builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "Message2"); + AnnotationMirror invalidFormatWithMessage2 = builder.build(); + + builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "(\"Message\" or \"Message2\")"); + AnnotationMirror invalidFormatWithMessagesOred = builder.build(); + + builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "(\"Message\" and \"Message2\")"); + AnnotationMirror invalidFormatWithMessagesAnded = builder.build(); + + ConversionCategory[] cc = new ConversionCategory[1]; + + cc[0] = ConversionCategory.UNUSED; + AnnotationMirror formatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.GENERAL; + AnnotationMirror formatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.CHAR; + AnnotationMirror formatCharAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.INT; + AnnotationMirror formatIntAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.TIME; + AnnotationMirror formatTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.FLOAT; + AnnotationMirror formatFloatAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.CHAR_AND_INT; + AnnotationMirror formatCharAndIntAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.INT_AND_TIME; + AnnotationMirror formatIntAndTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.NULL; + AnnotationMirror formatNullAnno = treeUtil.categoriesToFormatAnnotation(cc); + + // ** GLB tests ** + + glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); + + // GLB of UNUSED and others + + glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatGeneralAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatCharAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatIntAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatTimeAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatFloatAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatCharAndIntAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatIntAndTimeAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatNullAnno, formatUnusedAnno); + + // GLB of GENERAL and others + + glbAssert(formatGeneralAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatCharAndIntAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatIntAndTimeAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatNullAnno, formatGeneralAnno); + + // GLB of CHAR and others + + glbAssert(formatCharAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatCharAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatCharAnno, formatCharAnno); + glbAssert(formatCharAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatCharAndIntAnno, formatCharAnno); + glbAssert(formatCharAnno, formatIntAndTimeAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatNullAnno, formatCharAnno); + + // GLB of INT and others + + glbAssert(formatIntAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatIntAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatIntAnno, formatIntAnno); + glbAssert(formatIntAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatCharAndIntAnno, formatIntAnno); + glbAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAnno); + glbAssert(formatIntAnno, formatNullAnno, formatIntAnno); + + // GLB of TIME and others + + glbAssert(formatTimeAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatTimeAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); + glbAssert(formatTimeAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatCharAndIntAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatIntAndTimeAnno, formatTimeAnno); + glbAssert(formatTimeAnno, formatNullAnno, formatTimeAnno); + + // GLB of FLOAT and others + + glbAssert(formatFloatAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatFloatAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); + glbAssert(formatFloatAnno, formatCharAndIntAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatIntAndTimeAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatNullAnno, formatFloatAnno); + + // GLB of CHAR_AND_INT and others + + glbAssert(formatCharAndIntAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatCharAndIntAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatCharAndIntAnno, formatCharAnno, formatCharAnno); + glbAssert(formatCharAndIntAnno, formatIntAnno, formatIntAnno); + glbAssert(formatCharAndIntAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatCharAndIntAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); + glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); + glbAssert(formatCharAndIntAnno, formatNullAnno, formatCharAndIntAnno); + + // GLB of INT_AND_TIME and others + + glbAssert(formatIntAndTimeAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatIntAndTimeAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatIntAndTimeAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAnno); + glbAssert(formatIntAndTimeAnno, formatTimeAnno, formatTimeAnno); + glbAssert(formatIntAndTimeAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatIntAnno); + glbAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + glbAssert(formatIntAndTimeAnno, formatNullAnno, formatIntAndTimeAnno); + + // GLB of NULL and others + + glbAssert(formatNullAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatNullAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatNullAnno, formatCharAnno, formatCharAnno); + glbAssert(formatNullAnno, formatIntAnno, formatIntAnno); + glbAssert(formatNullAnno, formatTimeAnno, formatTimeAnno); + glbAssert(formatNullAnno, formatFloatAnno, formatFloatAnno); + glbAssert(formatNullAnno, formatCharAndIntAnno, formatCharAndIntAnno); + glbAssert(formatNullAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + glbAssert(formatNullAnno, formatNullAnno, formatNullAnno); + + // Now test with two ConversionCategory at a time: + + ConversionCategory[] cc2 = new ConversionCategory[2]; + + cc2[0] = ConversionCategory.CHAR_AND_INT; + cc2[1] = ConversionCategory.FLOAT; + AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = ConversionCategory.INT; + cc2[1] = ConversionCategory.CHAR; + AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = ConversionCategory.INT; + cc2[1] = ConversionCategory.GENERAL; + AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); + + glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); + + // Test that the GLB of two ConversionCategory arrays of different sizes is an array of the + // smallest size of the two: + + glbAssert(formatGeneralAnno, formatTwoConvCat1, formatGeneralAnno); + glbAssert(formatTwoConvCat2, formatNullAnno, formatIntAnno); + + // GLB of @UnknownFormat and others + + glbAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + glbAssert(UNKNOWNFORMAT, FORMAT, FORMAT); + glbAssert(UNKNOWNFORMAT, formatUnusedAnno, formatUnusedAnno); + glbAssert(UNKNOWNFORMAT, INVALIDFORMAT, INVALIDFORMAT); + glbAssert(UNKNOWNFORMAT, invalidFormatWithMessage, invalidFormatWithMessage); + glbAssert(UNKNOWNFORMAT, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @Format(null) and others + + glbAssert(FORMAT, UNKNOWNFORMAT, FORMAT); + // Computing the GLB of @Format(null) and @Format(null) should never occur in practice; + // skipping this case as it causes an expected crash. + // Computing the GLB of @Format(null) and @Format with a value should never occur in + // practice; skipping this case as it causes an expected crash. + glbAssert(FORMAT, INVALIDFORMAT, FORMATBOTTOM); + glbAssert(FORMAT, invalidFormatWithMessage, FORMATBOTTOM); + glbAssert(FORMAT, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @Format(UNUSED) and others + + glbAssert(formatUnusedAnno, UNKNOWNFORMAT, formatUnusedAnno); + // Computing the GLB of @Format with a value and @Format(null) should never occur in + // practice; skipping this case as it causes an expected crash. + glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, INVALIDFORMAT, FORMATBOTTOM); + glbAssert(formatUnusedAnno, invalidFormatWithMessage, FORMATBOTTOM); + glbAssert(formatUnusedAnno, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @InvalidFormat(null) and others + + glbAssert(INVALIDFORMAT, UNKNOWNFORMAT, INVALIDFORMAT); + glbAssert(INVALIDFORMAT, FORMAT, FORMATBOTTOM); + glbAssert(INVALIDFORMAT, formatUnusedAnno, FORMATBOTTOM); + glbAssert(INVALIDFORMAT, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @InvalidFormat("Message") and others + + glbAssert(invalidFormatWithMessage, UNKNOWNFORMAT, invalidFormatWithMessage); + glbAssert(invalidFormatWithMessage, FORMAT, FORMATBOTTOM); + glbAssert(invalidFormatWithMessage, formatUnusedAnno, FORMATBOTTOM); + glbAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); + glbAssert(invalidFormatWithMessage, invalidFormatWithMessage2, invalidFormatWithMessagesAnded); + glbAssert(invalidFormatWithMessage, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @FormatBottom and others + + glbAssert(FORMATBOTTOM, UNKNOWNFORMAT, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, FORMAT, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, formatUnusedAnno, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, INVALIDFORMAT, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, invalidFormatWithMessage, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); + + // ** LUB tests ** + + // LUB of UNUSED and others + + lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + lubAssert(formatUnusedAnno, formatGeneralAnno, formatGeneralAnno); + lubAssert(formatUnusedAnno, formatCharAnno, formatCharAnno); + lubAssert(formatUnusedAnno, formatIntAnno, formatIntAnno); + lubAssert(formatUnusedAnno, formatTimeAnno, formatTimeAnno); + lubAssert(formatUnusedAnno, formatFloatAnno, formatFloatAnno); + lubAssert(formatUnusedAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatUnusedAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatUnusedAnno, formatNullAnno, formatNullAnno); + + // LUB of GENERAL and others + + lubAssert(formatGeneralAnno, formatUnusedAnno, formatGeneralAnno); + lubAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); + lubAssert(formatGeneralAnno, formatCharAnno, formatCharAnno); + lubAssert(formatGeneralAnno, formatIntAnno, formatIntAnno); + lubAssert(formatGeneralAnno, formatTimeAnno, formatTimeAnno); + lubAssert(formatGeneralAnno, formatFloatAnno, formatFloatAnno); + lubAssert(formatGeneralAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatGeneralAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatGeneralAnno, formatNullAnno, formatNullAnno); + + // LUB of CHAR and others + + lubAssert(formatCharAnno, formatUnusedAnno, formatCharAnno); + lubAssert(formatCharAnno, formatGeneralAnno, formatCharAnno); + lubAssert(formatCharAnno, formatCharAnno, formatCharAnno); + lubAssert(formatCharAnno, formatIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatCharAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatCharAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatCharAnno, formatNullAnno, formatNullAnno); + + // LUB of INT and others + + lubAssert(formatIntAnno, formatUnusedAnno, formatIntAnno); + lubAssert(formatIntAnno, formatGeneralAnno, formatIntAnno); + lubAssert(formatIntAnno, formatCharAnno, formatCharAndIntAnno); + lubAssert(formatIntAnno, formatIntAnno, formatIntAnno); + lubAssert(formatIntAnno, formatTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAnno, formatNullAnno, formatNullAnno); + + // LUB of TIME and others + + lubAssert(formatTimeAnno, formatUnusedAnno, formatTimeAnno); + lubAssert(formatTimeAnno, formatGeneralAnno, formatTimeAnno); + lubAssert(formatTimeAnno, formatCharAnno, formatNullAnno); + lubAssert(formatTimeAnno, formatIntAnno, formatIntAndTimeAnno); + lubAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); + lubAssert(formatTimeAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatTimeAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatTimeAnno, formatNullAnno, formatNullAnno); + + // LUB of FLOAT and others + + lubAssert(formatFloatAnno, formatUnusedAnno, formatFloatAnno); + lubAssert(formatFloatAnno, formatGeneralAnno, formatFloatAnno); + lubAssert(formatFloatAnno, formatCharAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatIntAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); + lubAssert(formatFloatAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatNullAnno, formatNullAnno); + + // LUB of CHAR_AND_INT and others + + lubAssert(formatCharAndIntAnno, formatUnusedAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatGeneralAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatCharAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatCharAndIntAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatCharAndIntAnno, formatNullAnno, formatNullAnno); + + // LUB of INT_AND_TIME and others + + lubAssert(formatIntAndTimeAnno, formatUnusedAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatGeneralAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatCharAnno, formatNullAnno); + lubAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatNullAnno, formatNullAnno); + + // LUB of NULL and others + + lubAssert(formatNullAnno, formatUnusedAnno, formatNullAnno); + lubAssert(formatNullAnno, formatGeneralAnno, formatNullAnno); + lubAssert(formatNullAnno, formatCharAnno, formatNullAnno); + lubAssert(formatNullAnno, formatIntAnno, formatNullAnno); + lubAssert(formatNullAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatNullAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatNullAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatNullAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatNullAnno, formatNullAnno, formatNullAnno); + + // Now test with two ConversionCategory at a time: + + cc2[0] = ConversionCategory.CHAR_AND_INT; + cc2[1] = ConversionCategory.NULL; + AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = ConversionCategory.NULL; + cc2[1] = ConversionCategory.CHAR; + AnnotationMirror formatTwoConvCat5 = treeUtil.categoriesToFormatAnnotation(cc2); + + lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); + + // Test that the LUB of two ConversionCategory arrays of different sizes is an array of the + // largest size of the two: + + lubAssert(formatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); + lubAssert(formatTwoConvCat2, formatNullAnno, formatTwoConvCat5); + + // LUB of @UnknownFormat and others + + lubAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, FORMAT, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, formatUnusedAnno, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, INVALIDFORMAT, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, FORMATBOTTOM, UNKNOWNFORMAT); + + // LUB of @Format(null) and others + + lubAssert(FORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + // Computing the LUB of @Format(null) and @Format(null) should never occur in practice; + // skipping this case as it causes an expected crash. + // Computing the LUB of @Format(null) and @Format with a value should never occur in + // practice; skipping this case as it causes an expected crash. + lubAssert(FORMAT, INVALIDFORMAT, UNKNOWNFORMAT); + lubAssert(FORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); + lubAssert(FORMAT, FORMATBOTTOM, FORMAT); + + // LUB of @Format(UNUSED) and others + + lubAssert(formatUnusedAnno, UNKNOWNFORMAT, UNKNOWNFORMAT); + // Computing the LUB of @Format with a value and @Format(null) should never occur in + // practice; skipping this case as it causes an expected crash. + lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + lubAssert(formatUnusedAnno, INVALIDFORMAT, UNKNOWNFORMAT); + lubAssert(formatUnusedAnno, invalidFormatWithMessage, UNKNOWNFORMAT); + lubAssert(formatUnusedAnno, FORMATBOTTOM, formatUnusedAnno); + + // LUB of @InvalidFormat(null) and others + + lubAssert(INVALIDFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(INVALIDFORMAT, FORMAT, UNKNOWNFORMAT); + lubAssert(INVALIDFORMAT, formatUnusedAnno, UNKNOWNFORMAT); + lubAssert(INVALIDFORMAT, FORMATBOTTOM, INVALIDFORMAT); + + // LUB of @InvalidFormat("Message") and others + + lubAssert(invalidFormatWithMessage, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(invalidFormatWithMessage, FORMAT, UNKNOWNFORMAT); + lubAssert(invalidFormatWithMessage, formatUnusedAnno, UNKNOWNFORMAT); + lubAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); + lubAssert(invalidFormatWithMessage, invalidFormatWithMessage2, invalidFormatWithMessagesOred); + lubAssert(invalidFormatWithMessage, FORMATBOTTOM, invalidFormatWithMessage); + + // LUB of @FormatBottom and others + + lubAssert(FORMATBOTTOM, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(FORMATBOTTOM, FORMAT, FORMAT); + lubAssert(FORMATBOTTOM, formatUnusedAnno, formatUnusedAnno); + lubAssert(FORMATBOTTOM, INVALIDFORMAT, INVALIDFORMAT); + lubAssert(FORMATBOTTOM, invalidFormatWithMessage, invalidFormatWithMessage); + lubAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java index 851e1ad3830..a444889b802 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java @@ -4,6 +4,12 @@ // https://github.com/typetools/checker-framework/issues/723 // https://github.com/typetools/checker-framework/issues/756 +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.i18nformatter.I18nFormatterAnnotatedTypeFactory; import org.checkerframework.checker.i18nformatter.I18nFormatterChecker; import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil; @@ -20,14 +26,6 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** * This class tests the implementation of GLB computation in the I18n Format String Checker (see * issue 723), but it does not test for the crash that occurs if I18nFormatterAnnotatedTypeFactory @@ -35,439 +33,426 @@ * tests the implementation of LUB computation in the I18n Format String Checker. */ public class I18nFormatterLubGlbChecker extends I18nFormatterChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new I18nFormatterVisitor(this) { - @Override - protected I18nFormatterAnnotatedTypeFactory createTypeFactory() { - return new I18nFormatterLubGlbAnnotatedTypeFactory(checker); - } - }; - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new I18nFormatterVisitor(this) { + @Override + protected I18nFormatterAnnotatedTypeFactory createTypeFactory() { + return new I18nFormatterLubGlbAnnotatedTypeFactory(checker); + } + }; + } + + /** I18nFormatterLubGlbAnnotatedTypeFactory. */ + private static class I18nFormatterLubGlbAnnotatedTypeFactory + extends I18nFormatterAnnotatedTypeFactory { - /** I18nFormatterLubGlbAnnotatedTypeFactory. */ - private static class I18nFormatterLubGlbAnnotatedTypeFactory - extends I18nFormatterAnnotatedTypeFactory { - - /** - * Constructor. - * - * @param checker checker - */ - public I18nFormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - I18nUnknownFormat.class, - I18nFormatBottom.class, - I18nFormat.class, - I18nInvalidFormat.class, - I18nFormatFor.class)); - } + /** + * Constructor. + * + * @param checker checker + */ + public I18nFormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); } - @SuppressWarnings("checkstyle:localvariablename") @Override - public void initChecker() { - super.initChecker(); - I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(this); - - Elements elements = getElementUtils(); - AnnotationMirror I18NUNKNOWNFORMAT = - AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); - AnnotationMirror I18NFORMAT = - AnnotationBuilder.fromClass( - elements, - I18nFormat.class, - AnnotationBuilder.elementNamesValues( - "value", new I18nConversionCategory[0])); - AnnotationMirror I18NINVALIDFORMAT = - AnnotationBuilder.fromClass( - elements, - I18nInvalidFormat.class, - AnnotationBuilder.elementNamesValues("value", "dummy")); - AnnotationMirror I18NFORMATFOR = - AnnotationBuilder.fromClass( - elements, - I18nFormatFor.class, - AnnotationBuilder.elementNamesValues("value", "dummy")); - AnnotationMirror I18NFORMATBOTTOM = - AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); - - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "Message"); - AnnotationMirror i18nInvalidFormatWithMessage = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "Message2"); - AnnotationMirror i18nInvalidFormatWithMessage2 = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "(\"Message\" or \"Message2\")"); - AnnotationMirror i18nInvalidFormatWithMessagesOred = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "(\"Message\" and \"Message2\")"); - AnnotationMirror i18nInvalidFormatWithMessagesAnded = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); - builder.setValue("value", "#1"); - AnnotationMirror i18nFormatForWithValue1 = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); - builder.setValue("value", "#2"); - AnnotationMirror i18nFormatForWithValue2 = builder.build(); - - I18nConversionCategory[] cc = new I18nConversionCategory[1]; - - cc[0] = I18nConversionCategory.UNUSED; - AnnotationMirror i18nFormatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = I18nConversionCategory.GENERAL; - AnnotationMirror i18nFormatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = I18nConversionCategory.DATE; - AnnotationMirror i18nFormatDateAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = I18nConversionCategory.NUMBER; - AnnotationMirror i18nFormatNumberAnno = treeUtil.categoriesToFormatAnnotation(cc); - - // ** GLB tests ** - - // GLB of UNUSED and others - - glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatUnusedAnno); - - // GLB of GENERAL and others - - glbAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatGeneralAnno); - - // GLB of DATE and others - - glbAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); - glbAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatDateAnno); - - // GLB of NUMBER and others - - glbAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatDateAnno); - glbAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // Now test with two I18nConversionCategory at a time: - - I18nConversionCategory[] cc2 = new I18nConversionCategory[2]; - - cc2[0] = I18nConversionCategory.DATE; - cc2[1] = I18nConversionCategory.DATE; - AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = I18nConversionCategory.UNUSED; - cc2[1] = I18nConversionCategory.NUMBER; - AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = I18nConversionCategory.UNUSED; - cc2[1] = I18nConversionCategory.DATE; - AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); - - glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); - - // Test that the GLB of two I18nConversionCategory arrays of different sizes is an array of - // the smallest size of the two: - - glbAssert(i18nFormatGeneralAnno, formatTwoConvCat1, i18nFormatGeneralAnno); - glbAssert(formatTwoConvCat2, i18nFormatDateAnno, i18nFormatUnusedAnno); - - // GLB of two distinct @I18nFormatFor(...) annotations is @I18nFormatBottom - - glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NFORMATBOTTOM); - - // GLB of @I18nUnknownFormat and others - - glbAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - glbAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NFORMAT); - glbAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NINVALIDFORMAT); - glbAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); - glbAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NFORMATFOR); - glbAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, i18nFormatForWithValue1); - glbAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormat(null) and others - - glbAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NFORMAT); - // Computing the GLB of @I18nFormat(null) and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - // Computing the GLB of @I18nFormat(null) and @I18nFormat with a value should never occur in - // practice. Skipping this case as it causes an expected crash. - glbAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormat(UNUSED) and others - - glbAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, i18nFormatUnusedAnno); - // Computing the GLB of @I18nFormat with a value and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nInvalidFormat(null) and others - - glbAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NINVALIDFORMAT); - glbAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nInvalidFormat("Message") and others - - glbAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage); - - glbAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert( - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage); - glbAssert( - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage2, - i18nInvalidFormatWithMessagesAnded); - glbAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormatFor(null) and others - - glbAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NFORMATFOR); - glbAssert(I18NFORMATFOR, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); - glbAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormatFor("#1") and others - - glbAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, i18nFormatForWithValue1); - glbAssert(i18nFormatForWithValue1, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); - glbAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormatBottom and others - - glbAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // ** LUB tests ** - - // LUB of UNUSED and others - - lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - lubAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - lubAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatDateAnno); - lubAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // LUB of GENERAL and others - - lubAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatGeneralAnno); - lubAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - lubAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatDateAnno); - lubAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // LUB of DATE and others - - lubAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatDateAnno); - lubAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatDateAnno); - lubAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); - lubAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // LUB of NUMBER and others - - lubAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatNumberAnno); - lubAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatNumberAnno); - lubAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatNumberAnno); - lubAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // Now test with two I18nConversionCategory at a time: - - cc2[0] = I18nConversionCategory.DATE; - cc2[1] = I18nConversionCategory.NUMBER; - AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); - - lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); - - // Test that the LUB of two I18nConversionCategory arrays of different sizes is an array of - // the largest size of the two: - - lubAssert(i18nFormatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); - lubAssert(formatTwoConvCat2, i18nFormatDateAnno, formatTwoConvCat4); - - // LUB of two distinct @I18nFormatFor(...) annotations is @I18nUnknownFormat - - lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NUNKNOWNFORMAT); - - // LUB of @I18nUnknownFormat and others - - lubAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NUNKNOWNFORMAT); - - // LUB of @I18nFormat(null) and others - - lubAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - // Computing the LUB of @I18nFormat(null) and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - // Computing the LUB of @I18nFormat(null) and @I18nFormat with a value should never occur in - // practice. Skipping this case as it causes an expected crash. - lubAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMAT); - - // LUB of @I18nFormat(UNUSED) and others - - lubAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - // Computing the LUB of @I18nFormat with a value and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - lubAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, i18nFormatUnusedAnno); - - // LUB of @I18nInvalidFormat(null) and others - - lubAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NINVALIDFORMAT); - - // LUB of @I18nInvalidFormat("Message") and others - - lubAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - - lubAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert( - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage); - lubAssert( - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage2, - i18nInvalidFormatWithMessagesOred); - lubAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, i18nInvalidFormatWithMessage); - - // LUB of @I18nFormatFor(null) and others - - lubAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); - lubAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATFOR); - - // LUB of @I18nFormatFor("#1") and others - - lubAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); - lubAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, i18nFormatForWithValue1); - - // LUB of @I18nFormatBottom and others - - lubAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMAT); - lubAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - lubAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NINVALIDFORMAT); - lubAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); - lubAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATFOR); - lubAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, i18nFormatForWithValue1); - lubAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + I18nUnknownFormat.class, + I18nFormatBottom.class, + I18nFormat.class, + I18nInvalidFormat.class, + I18nFormatFor.class)); } + } + + @SuppressWarnings("checkstyle:localvariablename") + @Override + public void initChecker() { + super.initChecker(); + I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(this); + + Elements elements = getElementUtils(); + AnnotationMirror I18NUNKNOWNFORMAT = + AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); + AnnotationMirror I18NFORMAT = + AnnotationBuilder.fromClass( + elements, + I18nFormat.class, + AnnotationBuilder.elementNamesValues("value", new I18nConversionCategory[0])); + AnnotationMirror I18NINVALIDFORMAT = + AnnotationBuilder.fromClass( + elements, + I18nInvalidFormat.class, + AnnotationBuilder.elementNamesValues("value", "dummy")); + AnnotationMirror I18NFORMATFOR = + AnnotationBuilder.fromClass( + elements, I18nFormatFor.class, AnnotationBuilder.elementNamesValues("value", "dummy")); + AnnotationMirror I18NFORMATBOTTOM = + AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); + + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "Message"); + AnnotationMirror i18nInvalidFormatWithMessage = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "Message2"); + AnnotationMirror i18nInvalidFormatWithMessage2 = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "(\"Message\" or \"Message2\")"); + AnnotationMirror i18nInvalidFormatWithMessagesOred = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "(\"Message\" and \"Message2\")"); + AnnotationMirror i18nInvalidFormatWithMessagesAnded = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); + builder.setValue("value", "#1"); + AnnotationMirror i18nFormatForWithValue1 = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); + builder.setValue("value", "#2"); + AnnotationMirror i18nFormatForWithValue2 = builder.build(); + + I18nConversionCategory[] cc = new I18nConversionCategory[1]; + + cc[0] = I18nConversionCategory.UNUSED; + AnnotationMirror i18nFormatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = I18nConversionCategory.GENERAL; + AnnotationMirror i18nFormatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = I18nConversionCategory.DATE; + AnnotationMirror i18nFormatDateAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = I18nConversionCategory.NUMBER; + AnnotationMirror i18nFormatNumberAnno = treeUtil.categoriesToFormatAnnotation(cc); + + // ** GLB tests ** + + // GLB of UNUSED and others + + glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatUnusedAnno); + + // GLB of GENERAL and others + + glbAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatGeneralAnno); + + // GLB of DATE and others + + glbAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); + glbAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatDateAnno); + + // GLB of NUMBER and others + + glbAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatDateAnno); + glbAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // Now test with two I18nConversionCategory at a time: + + I18nConversionCategory[] cc2 = new I18nConversionCategory[2]; + + cc2[0] = I18nConversionCategory.DATE; + cc2[1] = I18nConversionCategory.DATE; + AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = I18nConversionCategory.UNUSED; + cc2[1] = I18nConversionCategory.NUMBER; + AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = I18nConversionCategory.UNUSED; + cc2[1] = I18nConversionCategory.DATE; + AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); + + glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); + + // Test that the GLB of two I18nConversionCategory arrays of different sizes is an array of + // the smallest size of the two: + + glbAssert(i18nFormatGeneralAnno, formatTwoConvCat1, i18nFormatGeneralAnno); + glbAssert(formatTwoConvCat2, i18nFormatDateAnno, i18nFormatUnusedAnno); + + // GLB of two distinct @I18nFormatFor(...) annotations is @I18nFormatBottom + + glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NFORMATBOTTOM); + + // GLB of @I18nUnknownFormat and others + + glbAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + glbAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NFORMAT); + glbAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NINVALIDFORMAT); + glbAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); + glbAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NFORMATFOR); + glbAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, i18nFormatForWithValue1); + glbAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormat(null) and others + + glbAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NFORMAT); + // Computing the GLB of @I18nFormat(null) and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + // Computing the GLB of @I18nFormat(null) and @I18nFormat with a value should never occur in + // practice. Skipping this case as it causes an expected crash. + glbAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormat(UNUSED) and others + + glbAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, i18nFormatUnusedAnno); + // Computing the GLB of @I18nFormat with a value and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nInvalidFormat(null) and others + + glbAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NINVALIDFORMAT); + glbAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - /** - * Throws an exception if glb(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void glbAssert( - AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format( - "GLB of %s and %s should be %s, but is %s", - arg1, arg2, expected, result)); - } - } + // GLB of @I18nInvalidFormat("Message") and others - /** - * Throws an exception if lub(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void lubAssert( - AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format( - "LUB of %s and %s should be %s, but is %s", - arg1, arg2, expected, result)); - } + glbAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage); + + glbAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert( + i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); + glbAssert( + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage2, + i18nInvalidFormatWithMessagesAnded); + glbAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormatFor(null) and others + + glbAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NFORMATFOR); + glbAssert(I18NFORMATFOR, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); + glbAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormatFor("#1") and others + + glbAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, i18nFormatForWithValue1); + glbAssert(i18nFormatForWithValue1, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); + glbAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormatBottom and others + + glbAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // ** LUB tests ** + + // LUB of UNUSED and others + + lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + lubAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + lubAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatDateAnno); + lubAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // LUB of GENERAL and others + + lubAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatGeneralAnno); + lubAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + lubAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatDateAnno); + lubAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // LUB of DATE and others + + lubAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatDateAnno); + lubAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatDateAnno); + lubAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); + lubAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // LUB of NUMBER and others + + lubAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatNumberAnno); + lubAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatNumberAnno); + lubAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatNumberAnno); + lubAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // Now test with two I18nConversionCategory at a time: + + cc2[0] = I18nConversionCategory.DATE; + cc2[1] = I18nConversionCategory.NUMBER; + AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); + + lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); + + // Test that the LUB of two I18nConversionCategory arrays of different sizes is an array of + // the largest size of the two: + + lubAssert(i18nFormatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); + lubAssert(formatTwoConvCat2, i18nFormatDateAnno, formatTwoConvCat4); + + // LUB of two distinct @I18nFormatFor(...) annotations is @I18nUnknownFormat + + lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NUNKNOWNFORMAT); + + // LUB of @I18nUnknownFormat and others + + lubAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NUNKNOWNFORMAT); + + // LUB of @I18nFormat(null) and others + + lubAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + // Computing the LUB of @I18nFormat(null) and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + // Computing the LUB of @I18nFormat(null) and @I18nFormat with a value should never occur in + // practice. Skipping this case as it causes an expected crash. + lubAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMAT); + + // LUB of @I18nFormat(UNUSED) and others + + lubAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + // Computing the LUB of @I18nFormat with a value and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + lubAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, i18nFormatUnusedAnno); + + // LUB of @I18nInvalidFormat(null) and others + + lubAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NINVALIDFORMAT); + + // LUB of @I18nInvalidFormat("Message") and others + + lubAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + + lubAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert( + i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); + lubAssert( + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage2, + i18nInvalidFormatWithMessagesOred); + lubAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, i18nInvalidFormatWithMessage); + + // LUB of @I18nFormatFor(null) and others + + lubAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); + lubAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATFOR); + + // LUB of @I18nFormatFor("#1") and others + + lubAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); + lubAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, i18nFormatForWithValue1); + + // LUB of @I18nFormatBottom and others + + lubAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMAT); + lubAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + lubAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NINVALIDFORMAT); + lubAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); + lubAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATFOR); + lubAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, i18nFormatForWithValue1); + lubAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + } + + /** + * Throws an exception if glb(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void glbAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format("GLB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + } + } + + /** + * Throws an exception if lub(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void lubAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format("LUB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); } + } } diff --git a/checker/src/testannotations/java/android/support/annotation/NonNull.java b/checker/src/testannotations/java/android/support/annotation/NonNull.java index 24e84cfa2fc..05a67f4fd0e 100644 --- a/checker/src/testannotations/java/android/support/annotation/NonNull.java +++ b/checker/src/testannotations/java/android/support/annotation/NonNull.java @@ -12,11 +12,11 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.ANNOTATION_TYPE, - ElementType.PACKAGE + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.ANNOTATION_TYPE, + ElementType.PACKAGE }) public @interface NonNull {} diff --git a/checker/src/testannotations/java/android/support/annotation/Nullable.java b/checker/src/testannotations/java/android/support/annotation/Nullable.java index b6472495a34..0bd40d3b505 100644 --- a/checker/src/testannotations/java/android/support/annotation/Nullable.java +++ b/checker/src/testannotations/java/android/support/annotation/Nullable.java @@ -12,11 +12,11 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.ANNOTATION_TYPE, - ElementType.PACKAGE + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.ANNOTATION_TYPE, + ElementType.PACKAGE }) public @interface Nullable {} diff --git a/checker/src/testannotations/java/javax/annotation/Nonnull.java b/checker/src/testannotations/java/javax/annotation/Nonnull.java index a6f2e3bc127..5d4b646cf26 100644 --- a/checker/src/testannotations/java/javax/annotation/Nonnull.java +++ b/checker/src/testannotations/java/javax/annotation/Nonnull.java @@ -3,11 +3,10 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; - import javax.annotation.meta.When; @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Nonnull { - When when() default When.ALWAYS; + When when() default When.ALWAYS; } diff --git a/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java b/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java index a34b6f2e881..0a4770b9ac4 100644 --- a/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java +++ b/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java @@ -1,13 +1,12 @@ package javax.annotation.concurrent; -import org.checkerframework.checker.lock.qual.LockHeld; -import org.checkerframework.framework.qual.PreconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.lock.qual.LockHeld; +import org.checkerframework.framework.qual.PreconditionAnnotation; // The JCIP annotation can be used on a field (in which case it corresponds to the Lock Checker's // @GuardedBy annotation) or on a method (in which case it is a declaration annotation corresponding @@ -21,11 +20,11 @@ @Target({ElementType.METHOD, ElementType.FIELD}) @PreconditionAnnotation(qualifier = LockHeld.class) public @interface GuardedBy { - /** - * The Java expressions that need to be held. - * - * @see Syntax of - * Java expressions - */ - String[] value() default {}; + /** + * The Java expressions that need to be held. + * + * @see Syntax of Java + * expressions + */ + String[] value() default {}; } diff --git a/checker/src/testannotations/java/javax/annotation/meta/When.java b/checker/src/testannotations/java/javax/annotation/meta/When.java index 0749f6d0d2b..a1082078711 100644 --- a/checker/src/testannotations/java/javax/annotation/meta/When.java +++ b/checker/src/testannotations/java/javax/annotation/meta/When.java @@ -5,12 +5,12 @@ * annotated element. */ public enum When { - /** S is a subset of T */ - ALWAYS, - /** nothing definitive is known about the relation between S and T */ - UNKNOWN, - /** S intersection T is non empty and S - T is nonempty. */ - MAYBE, - /** S intersection T is empty. */ - NEVER; + /** S is a subset of T */ + ALWAYS, + /** nothing definitive is known about the relation between S and T */ + UNKNOWN, + /** S intersection T is non empty and S - T is nonempty. */ + MAYBE, + /** S intersection T is empty. */ + NEVER; } diff --git a/checker/src/testannotations/java/lombok/NonNull.java b/checker/src/testannotations/java/lombok/NonNull.java index fac1e99c1e9..347cfc4b038 100644 --- a/checker/src/testannotations/java/lombok/NonNull.java +++ b/checker/src/testannotations/java/lombok/NonNull.java @@ -12,10 +12,10 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.FIELD, - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.LOCAL_VARIABLE, - ElementType.TYPE_USE + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.LOCAL_VARIABLE, + ElementType.TYPE_USE }) public @interface NonNull {} diff --git a/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java b/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java index e88cc22622a..24b4402052c 100644 --- a/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java +++ b/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java @@ -3,14 +3,13 @@ package net.jcip.annotations; -import org.checkerframework.checker.lock.qual.LockHeld; -import org.checkerframework.framework.qual.PreconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.lock.qual.LockHeld; +import org.checkerframework.framework.qual.PreconditionAnnotation; // The JCIP annotation can be used on a field (in which case it corresponds to the Lock Checker's // @GuardedBy annotation) or on a method (in which case it is a declaration annotation corresponding @@ -24,11 +23,11 @@ @Target({ElementType.FIELD, ElementType.METHOD}) @PreconditionAnnotation(qualifier = LockHeld.class) public @interface GuardedBy { - /** - * The Java expressions that need to be held. - * - * @see Syntax of - * Java expressions - */ - String[] value() default {}; + /** + * The Java expressions that need to be held. + * + * @see Syntax of Java + * expressions + */ + String[] value() default {}; } diff --git a/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java b/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java index d193e4c03d8..9b78ffcbced 100644 --- a/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java +++ b/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java @@ -12,12 +12,12 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.FIELD, - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.LOCAL_VARIABLE, - ElementType.TYPE_USE + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.LOCAL_VARIABLE, + ElementType.TYPE_USE }) public @interface NotNull { - String value() default ""; + String value() default ""; } diff --git a/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java b/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java index d189bf6ce7a..1c6fc456895 100644 --- a/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java +++ b/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java @@ -12,12 +12,12 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.FIELD, - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.LOCAL_VARIABLE, - ElementType.TYPE_USE + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.LOCAL_VARIABLE, + ElementType.TYPE_USE }) public @interface Nullable { - String value() default ""; + String value() default ""; } diff --git a/checker/tests/aggregate/NullnessAndRegex.java b/checker/tests/aggregate/NullnessAndRegex.java index 43f77b96088..10c8bbfd55a 100644 --- a/checker/tests/aggregate/NullnessAndRegex.java +++ b/checker/tests/aggregate/NullnessAndRegex.java @@ -4,17 +4,17 @@ import org.checkerframework.checker.regex.qual.Regex; public class NullnessAndRegex { - // :: error: (assignment.type.incompatible) - @Regex String s1 = "De(mo"; - // :: error: (assignment.type.incompatible) - Object f = null; - // :: error: (assignment.type.incompatible) - @Regex String s2 = "De(mo"; + // :: error: (assignment.type.incompatible) + @Regex String s1 = "De(mo"; + // :: error: (assignment.type.incompatible) + Object f = null; + // :: error: (assignment.type.incompatible) + @Regex String s2 = "De(mo"; - void localized(@Localized String s) {} + void localized(@Localized String s) {} - void method() { - // :: error: (argument.type.incompatible) - localized("ldskjfldj"); // error - } + void method() { + // :: error: (argument.type.incompatible) + localized("ldskjfldj"); // error + } } diff --git a/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java index e62a641d31a..0c0caae3568 100644 --- a/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java +++ b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java @@ -7,50 +7,50 @@ public class Dataset6Crash { - public static Iterator limit( - final Iterator base, final CountingPredicate filter) { - return new Iterator() { - - private T next; - - private boolean end; - - private int index = 0; - - public boolean hasNext() { - return true; - } - - public T next() { - fetch(); - T r = next; - next = null; - return r; - } - - private void fetch() { - if (next == null && !end) { - if (base.hasNext()) { - next = base.next(); - if (!filter.apply(index++, next)) { - next = null; - end = true; - } - } else { - end = true; - } - } + public static Iterator limit( + final Iterator base, final CountingPredicate filter) { + return new Iterator() { + + private T next; + + private boolean end; + + private int index = 0; + + public boolean hasNext() { + return true; + } + + public T next() { + fetch(); + T r = next; + next = null; + return r; + } + + private void fetch() { + if (next == null && !end) { + if (base.hasNext()) { + next = base.next(); + if (!filter.apply(index++, next)) { + next = null; + end = true; } + } else { + end = true; + } + } + } - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } - private static class CountingPredicate { - public boolean apply(int i, T next) { - return false; - } + private static class CountingPredicate { + public boolean apply(int i, T next) { + return false; } + } } diff --git a/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java b/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java index a1f7a86df85..c8798e763a6 100644 --- a/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java +++ b/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java @@ -6,79 +6,79 @@ public class Dataset9Crash { - @SuppressWarnings("all") - private class NotificationList implements Collection { - @Override - public int size() { - return 0; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean contains(Object o) { - return false; - } - - @Override - public Iterator iterator() { - return null; - } - - @Override - public Object[] toArray() { - return new Object[0]; - } - - @Override - public boolean add(Object o) { - return false; - } - - @Override - public boolean remove(Object o) { - return false; - } - - @Override - public boolean addAll(Collection c) { - return false; - } - - @Override - public void clear() {} - - @Override - public boolean equals(Object o) { - return false; - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public boolean retainAll(Collection c) { - return false; - } - - @Override - public boolean removeAll(Collection c) { - return false; - } - - @Override - public boolean containsAll(Collection c) { - return false; - } - - @Override - public Object[] toArray(Object[] a) { - return new Object[0]; - } + @SuppressWarnings("all") + private class NotificationList implements Collection { + @Override + public int size() { + return 0; } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object o) { + return false; + } + + @Override + public Iterator iterator() { + return null; + } + + @Override + public Object[] toArray() { + return new Object[0]; + } + + @Override + public boolean add(Object o) { + return false; + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean addAll(Collection c) { + return false; + } + + @Override + public void clear() {} + + @Override + public boolean equals(Object o) { + return false; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean containsAll(Collection c) { + return false; + } + + @Override + public Object[] toArray(Object[] a) { + return new Object[0]; + } + } } diff --git a/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java b/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java index 5df8af294a4..da454d2d73a 100644 --- a/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java +++ b/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java @@ -6,53 +6,53 @@ public class DelocalizeAtCallsites { - void a1(int[][] a, @IndexFor("#1") int y) { - int[] z = a[y]; - } - - void a2(int y, int x) {} - - void testArrayAccess() { - int[][] arr = new int[5][5]; - int x1 = 3; - @SuppressWarnings("all") - @IndexFor(value = {"arr[x1]", "arr"}) int y1 = 2; - // test that out-of-scope indices are handled properly - a1(arr, y1); - // test that out-of-scope arrays are handled properly - a2(y1, x1); - } - - void a3(int x) {} - - void testArrayCreation() { - int x = 10; - @SuppressWarnings("all") - @IndexFor("new int[x]") int y = 0; - a3(y); - @SuppressWarnings("all") - @IndexFor("new int[x]{x, x, x, x, x, x, x, x, x, x}") int z = 0; - a3(z); - } - - void testBinary() { - int x1 = 5; - @LessThan("x1 + 1") int y = 4; - a3(y); - } - - void testUnary() { - int x1 = 5; - @LessThan("x1++") int y = 4; - a3(y); - } - - public int f; - - void testFieldAccess() { - DelocalizeAtCallsites d = new DelocalizeAtCallsites(); - d.f = 3; - @LessThan("d.f") int y = 2; - a3(y); - } + void a1(int[][] a, @IndexFor("#1") int y) { + int[] z = a[y]; + } + + void a2(int y, int x) {} + + void testArrayAccess() { + int[][] arr = new int[5][5]; + int x1 = 3; + @SuppressWarnings("all") + @IndexFor(value = {"arr[x1]", "arr"}) int y1 = 2; + // test that out-of-scope indices are handled properly + a1(arr, y1); + // test that out-of-scope arrays are handled properly + a2(y1, x1); + } + + void a3(int x) {} + + void testArrayCreation() { + int x = 10; + @SuppressWarnings("all") + @IndexFor("new int[x]") int y = 0; + a3(y); + @SuppressWarnings("all") + @IndexFor("new int[x]{x, x, x, x, x, x, x, x, x, x}") int z = 0; + a3(z); + } + + void testBinary() { + int x1 = 5; + @LessThan("x1 + 1") int y = 4; + a3(y); + } + + void testUnary() { + int x1 = 5; + @LessThan("x1++") int y = 4; + a3(y); + } + + public int f; + + void testFieldAccess() { + DelocalizeAtCallsites d = new DelocalizeAtCallsites(); + d.f = 3; + @LessThan("d.f") int y = 2; + a3(y); + } } diff --git a/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java b/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java index 2e7b84af542..80f8159f67b 100644 --- a/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java +++ b/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java @@ -6,68 +6,66 @@ public class DependentTypesViewpointAdaptationTest { - public static void run() { - String word1 = "\"Hamburg\""; - String word2 = "burg"; - // The existence of word3 here forces the inferred @SameLen - // annotation to include a local variable that isn't a parameter - // to compute(), to test that such local variables are viewpoint-adapted - // correctly. - String word3 = word1; - compute(word1, word2); - } + public static void run() { + String word1 = "\"Hamburg\""; + String word2 = "burg"; + // The existence of word3 here forces the inferred @SameLen + // annotation to include a local variable that isn't a parameter + // to compute(), to test that such local variables are viewpoint-adapted + // correctly. + String word3 = word1; + compute(word1, word2); + } - public static void compute(String word1, String otherWord) { - // content doesn't matter - } + public static void compute(String word1, String otherWord) { + // content doesn't matter + } - public static void receiverTest( - @SameLen("#2") DependentTypesViewpointAdaptationTest t1, - @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { - t1.compute2(t2); - } + public static void receiverTest( + @SameLen("#2") DependentTypesViewpointAdaptationTest t1, + @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { + t1.compute2(t2); + } - public void compute2( - DependentTypesViewpointAdaptationTest this, - DependentTypesViewpointAdaptationTest other) { - // content doesn't matter - } + public void compute2( + DependentTypesViewpointAdaptationTest this, DependentTypesViewpointAdaptationTest other) { + // content doesn't matter + } - public void thisTest(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { - compute3(this, t1); - } + public void thisTest(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { + compute3(this, t1); + } - public static void compute3( - DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { - // content doesn't matter - } + public static void compute3( + DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { + // content doesn't matter + } - public void thisTestNoUse(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { - compute4(t1, t1); - } + public void thisTestNoUse(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { + compute4(t1, t1); + } - public static void compute4( - DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { - // content doesn't matter - } + public static void compute4( + DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { + // content doesn't matter + } - public static void testThisInference( - DependentTypesViewpointAdaptationTest t1, - @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { - t1.compute5(t2); - t1.compute6(t2); - } + public static void testThisInference( + DependentTypesViewpointAdaptationTest t1, + @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { + t1.compute5(t2); + t1.compute6(t2); + } - public void compute5( - DependentTypesViewpointAdaptationTest this, - DependentTypesViewpointAdaptationTest other) { - // :: warning: (assignment.type.incompatible) - @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; - } + public void compute5( + DependentTypesViewpointAdaptationTest this, DependentTypesViewpointAdaptationTest other) { + // :: warning: (assignment.type.incompatible) + @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; + } - // Same as compute5, but without an explicit this parameter. - public void compute6(DependentTypesViewpointAdaptationTest other) { - // :: warning: (assignment.type.incompatible) - @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; - } + // Same as compute5, but without an explicit this parameter. + public void compute6(DependentTypesViewpointAdaptationTest other) { + // :: warning: (assignment.type.incompatible) + @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; + } } diff --git a/checker/tests/ainfer-index/non-annotated/InternCrash.java b/checker/tests/ainfer-index/non-annotated/InternCrash.java index f1daf6b5bb5..57d3e140f6f 100644 --- a/checker/tests/ainfer-index/non-annotated/InternCrash.java +++ b/checker/tests/ainfer-index/non-annotated/InternCrash.java @@ -4,16 +4,16 @@ public final class InternCrash { - public static String[] intern(String[] a) { - return a; - } + public static String[] intern(String[] a) { + return a; + } - public static Object intern(Object a) { - if (a instanceof String[]) { - String[] asArray = (String[]) a; - return intern(asArray); - } else { - return null; - } + public static Object intern(Object a) { + if (a instanceof String[]) { + String[] asArray = (String[]) a; + return intern(asArray); + } else { + return null; } + } } diff --git a/checker/tests/ainfer-index/non-annotated/LongMathTest.java b/checker/tests/ainfer-index/non-annotated/LongMathTest.java index ff5e2e9f058..7edffd7e6fe 100644 --- a/checker/tests/ainfer-index/non-annotated/LongMathTest.java +++ b/checker/tests/ainfer-index/non-annotated/LongMathTest.java @@ -1,5 +1,5 @@ public class LongMathTest { - public static long mod(long x, long y) { - return x % y; - } + public static long mod(long x, long y) { + return x % y; + } } diff --git a/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java b/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java index 284b04c2fba..c2835f68b03 100644 --- a/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java +++ b/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java @@ -4,18 +4,17 @@ public class RequireJavadocCrash { - public static void main(String[] args) { - RequireJavadocCrash rj = new RequireJavadocCrash(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); + public static void main(String[] args) { + RequireJavadocCrash rj = new RequireJavadocCrash(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - } + rj.setJavaFiles(remainingArgs); + } - private RequireJavadocCrash() {} + private RequireJavadocCrash() {} - private void setJavaFiles(String[] args) {} + private void setJavaFiles(String[] args) {} } diff --git a/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java b/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java index 2dfe2ba6a7d..8284319cfbc 100644 --- a/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java +++ b/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java @@ -1,7 +1,7 @@ public class TypeVarAssignment { - T t; + T t; - void foo(S s) { - t = s; - } + void foo(S s) { + t = s; + } } diff --git a/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava b/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava index 11336ca0f7e..55d54a405e7 100644 --- a/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava +++ b/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava @@ -2,12 +2,12 @@ // was only sometimes inferred. Based on an example from // https://github.com/dd482IT/cache2k-wpi/blob/0eaa156bdecd617b2aa4c745d0f8844a32609697/cache2k-api/src/main/java/org/cache2k/config/ToggleFeature.java#L73 @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.nullness.NullnessChecker") + "org.checkerframework.checker.nullness.NullnessChecker") public class TypeVarReturnAnnotated { - public static - @org.checkerframework.checker.initialization.qual.FBCBottom @org.checkerframework.checker.nullness.qual.Nullable T - extract() { - return null; - } + public static + @org.checkerframework.checker.initialization.qual.FBCBottom @org.checkerframework.checker.nullness.qual.Nullable T + extract() { + return null; + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/Bug.java b/checker/tests/ainfer-nullness/non-annotated/Bug.java index b90b483496c..aec77c8a525 100644 --- a/checker/tests/ainfer-nullness/non-annotated/Bug.java +++ b/checker/tests/ainfer-nullness/non-annotated/Bug.java @@ -2,68 +2,66 @@ import org.checkerframework.checker.nullness.qual.*; public class Bug { - /** Actions that MultiVersionControl can perform. */ - static enum Action { - /** Clone a repository. */ - CLONE, - /** Show the working tree status. */ - STATUS, - /** Pull changes from upstream. */ - PULL, - /** List the known repositories. */ - LIST - } + /** Actions that MultiVersionControl can perform. */ + static enum Action { + /** Clone a repository. */ + CLONE, + /** Show the working tree status. */ + STATUS, + /** Pull changes from upstream. */ + PULL, + /** List the known repositories. */ + LIST + } - private @MonotonicNonNull Action action; - public static String home = System.getProperty("user.home"); + private @MonotonicNonNull Action action; + public static String home = System.getProperty("user.home"); - void other() { - this.action = Action.LIST; - expandTilde(""); - } + void other() { + this.action = Action.LIST; + expandTilde(""); + } - /** - * Replace "~" by the expansion of "$HOME". - * - * @param path the input path, which might contain "~" - * @return path with "~" expanded - */ - private static String expandTilde(String path) { - return path.replaceFirst("^~", home); - } + /** + * Replace "~" by the expansion of "$HOME". + * + * @param path the input path, which might contain "~" + * @return path with "~" expanded + */ + private static String expandTilde(String path) { + return path.replaceFirst("^~", home); + } - public static void main(String[] args) { - Bug b = new Bug(); - Bug.expandTilde(""); - } + public static void main(String[] args) { + Bug b = new Bug(); + Bug.expandTilde(""); + } - Bug() { - parseArgs(new String[0]); - } + Bug() { + parseArgs(new String[0]); + } - @EnsuresNonNull("action") - public void parseArgs(@UnknownInitialization Bug this, String[] args) { - String actionString = args[0]; - if ("checkout".startsWith(actionString)) { - action = Action.CLONE; - } else if ("clone".startsWith(actionString)) { - action = Action.CLONE; - } else if ("list".startsWith(actionString)) { - action = Action.LIST; - } else if ("pull".startsWith(actionString)) { - action = Action.PULL; - } else if ("status".startsWith(actionString)) { - action = Action.STATUS; - } else if ("update".startsWith(actionString)) { - action = Action.PULL; - } else { - System.out.printf("Unrecognized action \"%s\"", actionString); - System.exit(1); - } + @EnsuresNonNull("action") + public void parseArgs(@UnknownInitialization Bug this, String[] args) { + String actionString = args[0]; + if ("checkout".startsWith(actionString)) { + action = Action.CLONE; + } else if ("clone".startsWith(actionString)) { + action = Action.CLONE; + } else if ("list".startsWith(actionString)) { + action = Action.LIST; + } else if ("pull".startsWith(actionString)) { + action = Action.PULL; + } else if ("status".startsWith(actionString)) { + action = Action.STATUS; + } else if ("update".startsWith(actionString)) { + action = Action.PULL; + } else { + System.out.printf("Unrecognized action \"%s\"", actionString); + System.exit(1); } + } - public static final @Nullable String VERSION = - Bug.class.getPackage() != null - ? Bug.class.getPackage().getImplementationVersion() - : null; + public static final @Nullable String VERSION = + Bug.class.getPackage() != null ? Bug.class.getPackage().getImplementationVersion() : null; } diff --git a/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java b/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java index f0a9b6768a8..2249bf0df89 100644 --- a/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java +++ b/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java @@ -2,70 +2,70 @@ public class MonotonicNonNullInferenceTest { - // :: warning: (initialization.static.field.uninitialized) - static String staticString1; + // :: warning: (initialization.static.field.uninitialized) + static String staticString1; - // :: warning: (assignment.type.incompatible) - static String staticString2 = null; + // :: warning: (assignment.type.incompatible) + static String staticString2 = null; - static String staticString3; + static String staticString3; - String instanceString1; + String instanceString1; - // :: warning: (assignment.type.incompatible) - String instanceString2 = null; + // :: warning: (assignment.type.incompatible) + String instanceString2 = null; - String instanceString3; + String instanceString3; - static { - // :: warning: (assignment.type.incompatible) - staticString3 = null; - } + static { + // :: warning: (assignment.type.incompatible) + staticString3 = null; + } - // :: warning: (initialization.fields.uninitialized) - MonotonicNonNullInferenceTest() { - String instanceString3 = "hello"; - } + // :: warning: (initialization.fields.uninitialized) + MonotonicNonNullInferenceTest() { + String instanceString3 = "hello"; + } - static void m1(String arg) { - staticString1 = arg; - staticString2 = arg; - staticString3 = arg; - } + static void m1(String arg) { + staticString1 = arg; + staticString2 = arg; + staticString3 = arg; + } - void m2(String arg) { - instanceString1 = arg; - instanceString2 = arg; - instanceString3 = arg; - } + void m2(String arg) { + instanceString1 = arg; + instanceString2 = arg; + instanceString3 = arg; + } - void hasSideEffect() {} + void hasSideEffect() {} - void testMonotonicNonNull() { - @NonNull String s; - if (staticString1 != null) { - hasSideEffect(); - s = staticString1; - } - if (staticString2 != null) { - hasSideEffect(); - s = staticString2; - } - if (staticString3 != null) { - hasSideEffect(); - s = staticString3; - } - if (instanceString1 != null) { - hasSideEffect(); - s = instanceString1; - } - if (instanceString2 != null) { - hasSideEffect(); - s = instanceString2; - } - if (instanceString3 != null) { - hasSideEffect(); - s = instanceString3; - } + void testMonotonicNonNull() { + @NonNull String s; + if (staticString1 != null) { + hasSideEffect(); + s = staticString1; + } + if (staticString2 != null) { + hasSideEffect(); + s = staticString2; + } + if (staticString3 != null) { + hasSideEffect(); + s = staticString3; + } + if (instanceString1 != null) { + hasSideEffect(); + s = instanceString1; + } + if (instanceString2 != null) { + hasSideEffect(); + s = instanceString2; + } + if (instanceString3 != null) { + hasSideEffect(); + s = instanceString3; } + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java b/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java index 16e6d27802d..7d6d2a17a08 100644 --- a/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java +++ b/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java @@ -8,25 +8,25 @@ public class NullTypeVarTest { - // :: warning: assignment.type.incompatible - private String indentString = null; + // :: warning: assignment.type.incompatible + private String indentString = null; - private List indentStrings; + private List indentStrings; - private final String INDENT_STR_ONE_LEVEL = " "; + private final String INDENT_STR_ONE_LEVEL = " "; - public NullTypeVarTest() { - indentStrings = new ArrayList(); - indentStrings.add(""); - } + public NullTypeVarTest() { + indentStrings = new ArrayList(); + indentStrings.add(""); + } - private String getIndentString(int indentLevel) { - if (indentString == null) { - for (int i = indentStrings.size(); i <= indentLevel; i++) { - indentStrings.add(indentStrings.get(i - 1) + INDENT_STR_ONE_LEVEL); - } - indentString = indentStrings.get(indentLevel); - } - return indentString; + private String getIndentString(int indentLevel) { + if (indentString == null) { + for (int i = indentStrings.size(); i <= indentLevel; i++) { + indentStrings.add(indentStrings.get(i - 1) + INDENT_STR_ONE_LEVEL); + } + indentString = indentStrings.get(indentLevel); } + return indentString; + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java b/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java index cf1bfb52eb7..9cd984f2818 100644 --- a/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java +++ b/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java @@ -3,14 +3,14 @@ import java.util.Set; public abstract class TwoCtorGenericAbstract implements Set { - protected T value; + protected T value; - protected TwoCtorGenericAbstract() { - // :: warning: (assignment.type.incompatible) - this.value = null; - } + protected TwoCtorGenericAbstract() { + // :: warning: (assignment.type.incompatible) + this.value = null; + } - protected TwoCtorGenericAbstract(T v) { - this.value = v; - } + protected TwoCtorGenericAbstract(T v) { + this.value = v; + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java b/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java index 133c4a1938d..afeb3f4a149 100644 --- a/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java +++ b/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TypeVarPlumeUtil { - @SuppressWarnings({ - "nullness" // only check for crashes. Also, was present in the original source file (so the - // annotations in this code were preserved by RemoveAnnotationsForInference). - }) - public V merge(@NonNull V value) { - return value; - } + @SuppressWarnings({ + "nullness" // only check for crashes. Also, was present in the original source file (so the + // annotations in this code were preserved by RemoveAnnotationsForInference). + }) + public V merge(@NonNull V value) { + return value; + } - public V mergeNullable(@Nullable V value) { - // :: warning: (return) - return value; - } + public V mergeNullable(@Nullable V value) { + // :: warning: (return) + return value; + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java b/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java index da50c30866f..42cfc3be95b 100644 --- a/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java +++ b/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java @@ -3,7 +3,7 @@ // https://github.com/dd482IT/cache2k-wpi/blob/0eaa156bdecd617b2aa4c745d0f8844a32609697/cache2k-api/src/main/java/org/cache2k/config/ToggleFeature.java#L73 public class TypeVarReturnAnnotated { - public static T extract() { - return null; - } + public static T extract() { + return null; + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java b/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java index 393e86e6ca4..83321766e00 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java @@ -6,87 +6,87 @@ class AddNotOwning { - @InheritableMustCall("a") - static class Foo { - void a() {} + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + static class NonEmptyMustCallFinalField { + final Foo f; // expect owning annotation for this field + + NonEmptyMustCallFinalField() { + // :: warning: (required.method.not.called) + f = new Foo(); + } + + Foo getField() { + return f; + } + + Foo getFieldOnSomePath() { + if (true) { + return null; + } else { + return f; + } + } + + void testNotOwningOnFinal() { + // :: warning: (required.method.not.called) + Foo f = getField(); + } + + void testNotOwningOnGetFieldOnSomePath() { + // :: warning: (required.method.not.called) + Foo f = getFieldOnSomePath(); + } + + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + void dispose() { + f.a(); + } + } + + @InheritableMustCall("dispose") + static class NonEmptyMustCallNonFinalField { + Foo f; // expect owning annotation for this field + + @SuppressWarnings("missing.creates.mustcall.for") + void initialyzeFoo() { + f.a(); + // :: warning: (required.method.not.called) + f = new Foo(); + } + + Foo getField() { + return f; + } + + Foo getFieldOnSomePath() { + if (true) { + return null; + } else { + return f; + } + } + + void testNotOwningOnNonFinal() { + // :: warning: (required.method.not.called) + Foo f = getField(); } - static class NonEmptyMustCallFinalField { - final Foo f; // expect owning annotation for this field - - NonEmptyMustCallFinalField() { - // :: warning: (required.method.not.called) - f = new Foo(); - } - - Foo getField() { - return f; - } - - Foo getFieldOnSomePath() { - if (true) { - return null; - } else { - return f; - } - } - - void testNotOwningOnFinal() { - // :: warning: (required.method.not.called) - Foo f = getField(); - } - - void testNotOwningOnGetFieldOnSomePath() { - // :: warning: (required.method.not.called) - Foo f = getFieldOnSomePath(); - } - - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - void dispose() { - f.a(); - } + void testNotOwningOnGetFieldOnSomePath() { + // :: warning: (required.method.not.called) + Foo f = getFieldOnSomePath(); } - @InheritableMustCall("dispose") - static class NonEmptyMustCallNonFinalField { - Foo f; // expect owning annotation for this field - - @SuppressWarnings("missing.creates.mustcall.for") - void initialyzeFoo() { - f.a(); - // :: warning: (required.method.not.called) - f = new Foo(); - } - - Foo getField() { - return f; - } - - Foo getFieldOnSomePath() { - if (true) { - return null; - } else { - return f; - } - } - - void testNotOwningOnNonFinal() { - // :: warning: (required.method.not.called) - Foo f = getField(); - } - - void testNotOwningOnGetFieldOnSomePath() { - // :: warning: (required.method.not.called) - Foo f = getFieldOnSomePath(); - } - - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - void dispose() { - f.a(); - } + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + void dispose() { + f.a(); } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java b/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java index e19b06ccf95..6b011244f11 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java @@ -5,36 +5,36 @@ import org.checkerframework.checker.mustcall.qual.*; class ClassWithTwoOwningFieldsTest { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @InheritableMustCall("close") - private class ClassWithTwoOwningFields { - // :: warning: (required.method.not.called) - final @Owning Foo foo1; - // :: warning: (required.method.not.called) - final @Owning Foo foo2; + @InheritableMustCall("close") + private class ClassWithTwoOwningFields { + // :: warning: (required.method.not.called) + final @Owning Foo foo1; + // :: warning: (required.method.not.called) + final @Owning Foo foo2; - public ClassWithTwoOwningFields(Foo f1, Foo f2) { - foo1 = f1; - foo2 = f2; - } + public ClassWithTwoOwningFields(Foo f1, Foo f2) { + foo1 = f1; + foo2 = f2; + } - void close() { - foo1.a(); - foo2.a(); - } + void close() { + foo1.a(); + foo2.a(); } + } - void testTwoOwning() { - // :: warning: (required.method.not.called) - Foo f1 = new Foo(); - // :: warning: (required.method.not.called) - Foo f2 = new Foo(); + void testTwoOwning() { + // :: warning: (required.method.not.called) + Foo f1 = new Foo(); + // :: warning: (required.method.not.called) + Foo f2 = new Foo(); - ClassWithTwoOwningFields ff = new ClassWithTwoOwningFields(f1, f2); - ff.close(); - } + ClassWithTwoOwningFields ff = new ClassWithTwoOwningFields(f1, f2); + ff.close(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java b/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java index 0db8cca1c22..8997c923653 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java @@ -2,15 +2,15 @@ import java.util.*; public class DatadirCleanupManager { - static class PurgeTask extends TimerTask { + static class PurgeTask extends TimerTask { - @Override - public void run() { - try { - PurgeTxnLog.purge(); - } catch (Exception e) { + @Override + public void run() { + try { + PurgeTxnLog.purge(); + } catch (Exception e) { - } - } + } } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java b/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java index 5bde4f629b4..7fecb377aa4 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java @@ -5,54 +5,54 @@ public class ECMInference { - class A1 { - void doStuff() { - toString(); - } - - void clientA1() { - doStuff(); - // :: warning: (assignment) - @CalledMethods("toString") A1 a1 = this; - } - } - - class B1 extends A1 { - @Override - void doStuff() { - toString(); - } - - void clientB1() { - doStuff(); - // :: warning: (assignment) - @CalledMethods("toString") B1 b1 = this; - } - } - - class A2 { - void doStuff() { - toString(); - } - - void clientA2() { - doStuff(); - // :: warning: (assignment) - @CalledMethods("toString") A2 a2 = this; - } - } - - class B2 extends A2 { - @Override - void doStuff() { - toString(); - hashCode(); - } - - void clientB2() { - doStuff(); - // :: warning: (assignment) - @CalledMethods({"toString", "hashCode"}) B2 b2 = this; - } + class A1 { + void doStuff() { + toString(); } + + void clientA1() { + doStuff(); + // :: warning: (assignment) + @CalledMethods("toString") A1 a1 = this; + } + } + + class B1 extends A1 { + @Override + void doStuff() { + toString(); + } + + void clientB1() { + doStuff(); + // :: warning: (assignment) + @CalledMethods("toString") B1 b1 = this; + } + } + + class A2 { + void doStuff() { + toString(); + } + + void clientA2() { + doStuff(); + // :: warning: (assignment) + @CalledMethods("toString") A2 a2 = this; + } + } + + class B2 extends A2 { + @Override + void doStuff() { + toString(); + hashCode(); + } + + void clientB2() { + doStuff(); + // :: warning: (assignment) + @CalledMethods({"toString", "hashCode"}) B2 b2 = this; + } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java index 269918e0d53..65c1fb5cf4b 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java @@ -2,28 +2,28 @@ import org.checkerframework.checker.mustcall.qual.*; class EnsuresCalledMethodsTest { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @InheritableMustCall("close") - class ECM { - // :: warning: (required.method.not.called) - @Owning Foo foo; + @InheritableMustCall("close") + class ECM { + // :: warning: (required.method.not.called) + @Owning Foo foo; - private void closePrivate() { - if (foo != null) { - foo.a(); - foo = null; - } - } + private void closePrivate() { + if (foo != null) { + foo.a(); + foo = null; + } + } - void close() { - if (foo != null) { - foo.a(); - foo = null; - } - } + void close() { + if (foo != null) { + foo.a(); + foo = null; + } } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java index 005ad4d531f..a8ebf5b0fea 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java @@ -3,58 +3,58 @@ class EnsuresCalledMethodsVarArgsTest { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - static class Utils { - @SuppressWarnings("ensuresvarargs.unverified") - @EnsuresCalledMethodsVarArgs("a") - public static void close(Foo... foos) { - for (Foo f : foos) { - if (f != null) { - f.a(); - } - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + static class Utils { + @SuppressWarnings("ensuresvarargs.unverified") + @EnsuresCalledMethodsVarArgs("a") + public static void close(Foo... foos) { + for (Foo f : foos) { + if (f != null) { + f.a(); } + } } + } - private class ECMVA { - final Foo foo; - - ECMVA() { - // :: warning: (required.method.not.called) - foo = new Foo(); - } + private class ECMVA { + final Foo foo; - void finalyzer() { - Utils.close(foo); - } + ECMVA() { + // :: warning: (required.method.not.called) + foo = new Foo(); + } - @EnsuresCalledMethods( - value = {"#1"}, - methods = {"a"}) - void closef(Foo f) { - if (f != null) { - Utils.close(f); - } - } + void finalyzer() { + Utils.close(foo); + } - void owningParam(Foo f) { - Foo foo = f; - Utils.close(foo); - } + @EnsuresCalledMethods( + value = {"#1"}, + methods = {"a"}) + void closef(Foo f) { + if (f != null) { + Utils.close(f); + } + } - void testOwningParamOnOwningParam() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - owningParam(f); - } + void owningParam(Foo f) { + Foo foo = f; + Utils.close(foo); } - void testCorrect() { - ECMVA e = new ECMVA(); - e.finalyzer(); + void testOwningParamOnOwningParam() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + owningParam(f); } + } + + void testCorrect() { + ECMVA e = new ECMVA(); + e.finalyzer(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java b/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java index 527b185c175..f617a0590ef 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java @@ -1,9 +1,9 @@ class NullReceiverTest { - public static void testReceiver(NullReceiverTest nrt) { - nrt.nullReceiver(); - } + public static void testReceiver(NullReceiverTest nrt) { + nrt.nullReceiver(); + } - public static NullReceiverTest nullReceiver() { - return new NullReceiverTest(); - } + public static NullReceiverTest nullReceiver() { + return new NullReceiverTest(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java index 20d0f9b5f8c..c3c0190b3f2 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java @@ -1,41 +1,40 @@ // @skip-test until we have support for adding annotation on the receiver parameter. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - @InheritableMustCall("close") public class MustCallAliasOnReceiver { - final @Owning InputStream is; + final @Owning InputStream is; - @MustCallAlias MustCallAliasOnReceiver(@MustCallAlias InputStream p, boolean b) { - this.is = p; - } + @MustCallAlias MustCallAliasOnReceiver(@MustCallAlias InputStream p, boolean b) { + this.is = p; + } - MustCallAliasOnReceiver returnReceiver(MustCallAliasOnReceiver this) { - return this; - } + MustCallAliasOnReceiver returnReceiver(MustCallAliasOnReceiver this) { + return this; + } - // :: warning: (required.method.not.called) - void testReceiverMCAAnnotation(@Owning InputStream inputStream) throws IOException { - MustCallAliasOnReceiver mcar = new MustCallAliasOnReceiver(is, false); - mcar.returnReceiver().close(); - } + // :: warning: (required.method.not.called) + void testReceiverMCAAnnotation(@Owning InputStream inputStream) throws IOException { + MustCallAliasOnReceiver mcar = new MustCallAliasOnReceiver(is, false); + mcar.returnReceiver().close(); + } - @EnsuresCalledMethods(value = "this.is", methods = "close") - public void close() throws IOException { - this.is.close(); - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } - public static MustCallAliasOnReceiver mcaneFactory(InputStream is) { - return new MustCallAliasOnReceiver(is, false); - } + public static MustCallAliasOnReceiver mcaneFactory(InputStream is) { + return new MustCallAliasOnReceiver(is, false); + } - // :: warning: (required.method.not.called) - public static void testUse(@Owning InputStream inputStream) throws Exception { - MustCallAliasOnReceiver mcane = mcaneFactory(inputStream); - mcane.close(); - } + // :: warning: (required.method.not.called) + public static void testUse(@Owning InputStream inputStream) throws Exception { + MustCallAliasOnReceiver mcane = mcaneFactory(inputStream); + mcane.close(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java index 2199bc04604..a5c40ba80c4 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java @@ -1,53 +1,52 @@ // This test ensures that the all-paths condition for inferring the @MustCallAlias annotation is // restricted to the examination of 'Regular' paths. +import java.io.IOException; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.IOException; - class MustCallAliasOnRegularExits { - @InheritableMustCall("a") - static class Foo { - void a() {} + @InheritableMustCall("a") + static class Foo { + void a() {} - int b() throws IOException { - return 0; - } + int b() throws IOException { + return 0; } + } - private class MCAConstructor extends Foo { - - protected final @Owning Foo f; - protected long s = 0L; - - // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a - // false positive. - @SuppressWarnings("assignment") - protected MCAConstructor(Foo foo) throws IOException { - if (foo == null) { - this.s = foo.b(); - } - this.f = foo; - } - - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - public void a() { - f.a(); - } + private class MCAConstructor extends Foo { + + protected final @Owning Foo f; + protected long s = 0L; + + // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a + // false positive. + @SuppressWarnings("assignment") + protected MCAConstructor(Foo foo) throws IOException { + if (foo == null) { + this.s = foo.b(); + } + this.f = foo; } - void testMCAOnMCAConstructor() { - Foo f = new Foo(); - try { - // :: warning: (required.method.not.called) - MCAConstructor mcaf = new MCAConstructor(f); - } catch (IOException e) { - } finally { - f.a(); - } + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + public void a() { + f.a(); + } + } + + void testMCAOnMCAConstructor() { + Foo f = new Foo(); + try { + // :: warning: (required.method.not.called) + MCAConstructor mcaf = new MCAConstructor(f); + } catch (IOException e) { + } finally { + f.a(); } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java index 295183bc20d..fbf6b29df9f 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java @@ -5,155 +5,155 @@ class MustCallAliasParams { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @InheritableMustCall("a") - private class MCAConstructor { + @InheritableMustCall("a") + private class MCAConstructor { - final @Owning Foo f; + final @Owning Foo f; - // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a - // false positive. - @SuppressWarnings("assignment") - MCAConstructor(Foo foo) { - f = foo; - } + // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a + // false positive. + @SuppressWarnings("assignment") + MCAConstructor(Foo foo) { + f = foo; + } - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - public void a() { - f.a(); - } + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + public void a() { + f.a(); + } + } + + void testMCAConstructor() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + MCAConstructor mcac = new MCAConstructor(f); + mcac.a(); + } + + @InheritableMustCall("a") + private class MCASuperClass { + int i; + final @Owning Foo f; + + // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a + // false positive. + @SuppressWarnings("assignment") + @MustCallAlias MCASuperClass(@MustCallAlias Foo foo, int i) { + f = foo; + i = i; } - void testMCAConstructor() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - MCAConstructor mcac = new MCAConstructor(f); - mcac.a(); + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + public void a() { + f.a(); + } + } + + private class MCASuperCall extends MCASuperClass { + MCASuperCall(Foo foo) { + super(foo, 1); + } + } + + void mCASuperCallTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + MCASuperCall mcaSuperCall = new MCASuperCall(f); + mcaSuperCall.a(); + } + + private class MCAThisCall extends MCASuperClass { + @MustCallAlias MCAThisCall(@MustCallAlias Foo foo) { + super(foo, 1); + } + + MCAThisCall(Foo foo, boolean b) { + this(foo); + } + } + + void mCAThisCallTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + MCAThisCall mcaThisCall = new MCAThisCall(f, true); + mcaThisCall.a(); + } + + private class MCAMethod { + Foo returnFoo(Foo foo) { + return foo; } - @InheritableMustCall("a") - private class MCASuperClass { - int i; - final @Owning Foo f; + void returnFooTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + Foo foo = returnFoo(f); + foo.a(); + } - // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a - // false positive. - @SuppressWarnings("assignment") - @MustCallAlias MCASuperClass(@MustCallAlias Foo foo, int i) { - f = foo; - i = i; - } + Foo returnAliasFoo(Foo foo) { + Foo f = foo; + return f; + } - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - public void a() { - f.a(); - } + MCASuperClass returnAliasFoo2(Foo foo, int i) { + MCASuperClass f = new MCASuperClass(foo, i); + return f; } - private class MCASuperCall extends MCASuperClass { - MCASuperCall(Foo foo) { - super(foo, 1); - } + void testReturnAliasFoo2() { + // :: warning: (required.method.not.called) + Foo foo = new Foo(); + MCASuperClass f = returnAliasFoo2(foo, 0); + f.a(); } - void mCASuperCallTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - MCASuperCall mcaSuperCall = new MCASuperCall(f); - mcaSuperCall.a(); + void returnAliasFooTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + Foo foo = returnAliasFoo(f); + foo.a(); } - private class MCAThisCall extends MCASuperClass { - @MustCallAlias MCAThisCall(@MustCallAlias Foo foo) { - super(foo, 1); - } + Foo returnFooAllPaths(Foo foo) { + if (true) { + Foo f = foo; + return f; + } else { + return foo; + } + } - MCAThisCall(Foo foo, boolean b) { - this(foo); - } + void returnFooAllPathsTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + Foo foo = returnFooAllPaths(f); + foo.a(); } - void mCAThisCallTest() { - // :: warning: (required.method.not.called) + Foo returnFooSomePath(Foo foo) { + if (true) { Foo f = new Foo(); - MCAThisCall mcaThisCall = new MCAThisCall(f, true); - mcaThisCall.a(); - } - - private class MCAMethod { - Foo returnFoo(Foo foo) { - return foo; - } - - void returnFooTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - Foo foo = returnFoo(f); - foo.a(); - } - - Foo returnAliasFoo(Foo foo) { - Foo f = foo; - return f; - } - - MCASuperClass returnAliasFoo2(Foo foo, int i) { - MCASuperClass f = new MCASuperClass(foo, i); - return f; - } - - void testReturnAliasFoo2() { - // :: warning: (required.method.not.called) - Foo foo = new Foo(); - MCASuperClass f = returnAliasFoo2(foo, 0); - f.a(); - } - - void returnAliasFooTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - Foo foo = returnAliasFoo(f); - foo.a(); - } - - Foo returnFooAllPaths(Foo foo) { - if (true) { - Foo f = foo; - return f; - } else { - return foo; - } - } - - void returnFooAllPathsTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - Foo foo = returnFooAllPaths(f); - foo.a(); - } - - Foo returnFooSomePath(Foo foo) { - if (true) { - Foo f = new Foo(); - return f; - } else { - return foo; - } - } - - void returnFooSomePathTest() { - Foo f = new Foo(); - Foo foo = returnFooSomePath(f); - f.a(); - foo.a(); - } + return f; + } else { + return foo; + } + } + + void returnFooSomePathTest() { + Foo f = new Foo(); + Foo foo = returnFooSomePath(f); + f.a(); + foo.a(); } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java b/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java index 267624cebc5..96b4382db38 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java @@ -1,29 +1,28 @@ -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; class OwnershipTransferOnConstructor { - static class Foo { - Foo(@Owning Socket s) { - try { - s.close(); - } catch (IOException e) { + static class Foo { + Foo(@Owning Socket s) { + try { + s.close(); + } catch (IOException e) { - } - } + } } + } - private class Bar { - void baz(Socket s) { - Foo f = new Foo(s); - } + private class Bar { + void baz(Socket s) { + Foo f = new Foo(s); + } - // :: warning: (required.method.not.called) - void testOwningOnBaz(@Owning Socket s) { - Socket s2 = s; - baz(s2); - } + // :: warning: (required.method.not.called) + void testOwningOnBaz(@Owning Socket s) { + Socket s2 = s; + baz(s2); } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java b/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java index 614266af4b0..02771551046 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java @@ -2,22 +2,22 @@ class OwningField { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @InheritableMustCall("dispose") - static class FinalOwningField { - final Foo f; + @InheritableMustCall("dispose") + static class FinalOwningField { + final Foo f; - FinalOwningField() { - // :: warning: (required.method.not.called) - f = new Foo(); - } + FinalOwningField() { + // :: warning: (required.method.not.called) + f = new Foo(); + } - void dispose() { - f.a(); - } + void dispose() { + f.a(); } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java b/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java index edf4117ed2d..7c2c542e9b7 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java @@ -3,35 +3,35 @@ class OwningFieldIndirectCall { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - static class Utility { - @EnsuresCalledMethods(value = "#1", methods = "a") - public static void closeStream(Foo f) { - if (f != null) { - f.a(); - } - } + static class Utility { + @EnsuresCalledMethods(value = "#1", methods = "a") + public static void closeStream(Foo f) { + if (f != null) { + f.a(); + } } + } - static class DisposeFieldUsingECM { - final Foo f; // expect owning annotation for this field - - DisposeFieldUsingECM() { - // :: warning: (required.method.not.called) - f = new Foo(); - } + static class DisposeFieldUsingECM { + final Foo f; // expect owning annotation for this field - void dispose() { - Utility.closeStream(f); - } + DisposeFieldUsingECM() { + // :: warning: (required.method.not.called) + f = new Foo(); } - void testCorrect() { - DisposeFieldUsingECM d = new DisposeFieldUsingECM(); - d.dispose(); + void dispose() { + Utility.closeStream(f); } + } + + void testCorrect() { + DisposeFieldUsingECM d = new DisposeFieldUsingECM(); + d.dispose(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java b/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java index 97f7040595f..1c1cd29eeef 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java @@ -2,59 +2,59 @@ import org.checkerframework.checker.mustcall.qual.*; class OwningParams { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - private class OwningParamsDirectCall { - void passOwnership(Foo f) { - f.a(); - } - - void passOwnershipTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - passOwnership(f); - } - } - - private class OwningParamsIndirectCall { - @EnsuresCalledMethods( - value = {"#1"}, - methods = {"a"}) - void hasECM(Foo f) { - f.a(); - } - - void owningFoo(@Owning Foo f) { - f.a(); - } - - void passOwnership(Foo f1, Foo f2) { - Foo f11 = f1; - hasECM(f11); - Foo f22 = f2; - owningFoo(f22); - } - - void checkAlias(Foo f1) { - Foo f2 = f1; - f2.a(); - } - - void checkAliasTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - checkAlias(f); - } - - void passOwnershipTest() { - // :: warning: (required.method.not.called) - Foo f1 = new Foo(); - // :: warning: (required.method.not.called) - Foo f2 = new Foo(); - passOwnership(f1, f2); - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + private class OwningParamsDirectCall { + void passOwnership(Foo f) { + f.a(); } + + void passOwnershipTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + passOwnership(f); + } + } + + private class OwningParamsIndirectCall { + @EnsuresCalledMethods( + value = {"#1"}, + methods = {"a"}) + void hasECM(Foo f) { + f.a(); + } + + void owningFoo(@Owning Foo f) { + f.a(); + } + + void passOwnership(Foo f1, Foo f2) { + Foo f11 = f1; + hasECM(f11); + Foo f22 = f2; + owningFoo(f22); + } + + void checkAlias(Foo f1) { + Foo f2 = f1; + f2.a(); + } + + void checkAliasTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + checkAlias(f); + } + + void passOwnershipTest() { + // :: warning: (required.method.not.called) + Foo f1 = new Foo(); + // :: warning: (required.method.not.called) + Foo f2 = new Foo(); + passOwnership(f1, f2); + } + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java b/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java index 13dc3762f9e..957cb0bb53f 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java @@ -3,20 +3,20 @@ public class PurgeTxnLog { - public static void purge() throws IOException { - staticMethod(); - } - - static void staticMethod() { + public static void purge() throws IOException { + staticMethod(); + } - class MyFileFilter implements FileFilter { - MyFileFilter() {} + static void staticMethod() { - public boolean accept(File f) { - return true; - } - } + class MyFileFilter implements FileFilter { + MyFileFilter() {} - MyFileFilter m = new MyFileFilter(); + public boolean accept(File f) { + return true; + } } + + MyFileFilter m = new MyFileFilter(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java b/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java index e1ce8cf4fd0..b0599501ff3 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java @@ -6,37 +6,37 @@ class ReplaceMustCallAliasAnnotation { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + @InheritableMustCall("a") + private class TwoOwningFields { + + final @Owning Foo f1; + final @Owning Foo f2; - @InheritableMustCall("a") - private class TwoOwningFields { - - final @Owning Foo f1; - final @Owning Foo f2; - - @SuppressWarnings({"assignment", "mustcallalias.out.of.scope"}) - @MustCallAlias TwoOwningFields(@MustCallAlias Foo foo1, Foo foo2) { - f1 = foo1; - f2 = foo2; - } - - @EnsuresCalledMethods( - value = {"this.f1", "this.f2"}, - methods = {"a"}) - public void a() { - f1.a(); - f2.a(); - } + @SuppressWarnings({"assignment", "mustcallalias.out.of.scope"}) + @MustCallAlias TwoOwningFields(@MustCallAlias Foo foo1, Foo foo2) { + f1 = foo1; + f2 = foo2; } - void testOwningAnnotations() { - Foo f1 = new Foo(); - // :: warning: (required.method.not.called) - Foo f2 = new Foo(); - TwoOwningFields t = new TwoOwningFields(f1, f2); - t.a(); + @EnsuresCalledMethods( + value = {"this.f1", "this.f2"}, + methods = {"a"}) + public void a() { + f1.a(); + f2.a(); } + } + + void testOwningAnnotations() { + Foo f1 = new Foo(); + // :: warning: (required.method.not.called) + Foo f2 = new Foo(); + TwoOwningFields t = new TwoOwningFields(f1, f2); + t.a(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java b/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java index adac77e685f..622b9f0d1b6 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java @@ -1,19 +1,19 @@ public class UnwantedECMInference { - class Bar { - Object field; + class Bar { + Object field; - void doStuff() { - field.toString(); - } + void doStuff() { + field.toString(); } + } - class Baz extends Bar { - @Override - void doStuff() { - // This method does not call toString(), so an @EnsuresCalledMethods("toString") - // annotation on either this method or on the overridden method is an error! - field.hashCode(); - } + class Baz extends Bar { + @Override + void doStuff() { + // This method does not call toString(), so an @EnsuresCalledMethods("toString") + // annotation on either this method or on the overridden method is an error! + field.hashCode(); } + } } diff --git a/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava b/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava index 995fe6b92e1..7f358123edd 100644 --- a/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava +++ b/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava @@ -6,31 +6,31 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; import org.checkerframework.framework.qual.EnsuresQualifierIf; @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.testchecker.ainfer.AinferTestChecker") + "org.checkerframework.checker.testchecker.ainfer.AinferTestChecker") public class ExistingPurityAnnotations { - Object obj; + Object obj; - @org.checkerframework.dataflow.qual.Pure - public Object pureMethod(Object object) { - return null; - } + @org.checkerframework.dataflow.qual.Pure + public Object pureMethod(Object object) { + return null; + } - @SuppressWarnings("ainfertest") - @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) - public boolean checkAinferSibling1(Object obj1) { - return true; - } + @SuppressWarnings("ainfertest") + @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) + public boolean checkAinferSibling1(Object obj1) { + return true; + } - public @AinferSibling1 Object usePureMethod() { + public @AinferSibling1 Object usePureMethod() { - if (checkAinferSibling1(obj)) { - // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should - // unrefine the type of obj, and an error will be issued when - // we try to return obj on the next line. - pureMethod(obj); - return obj; - } - return null; + if (checkAinferSibling1(obj)) { + // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should + // unrefine the type of obj, and an error will be issued when + // we try to return obj on the next line. + pureMethod(obj); + return obj; } + return null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java b/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java index d4d6eff2339..88d96a23c32 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java @@ -3,26 +3,26 @@ import java.io.*; class JavaSerialization { - private interface Serializer {} + private interface Serializer {} - static class JavaSerializationSerializer implements Serializer { + static class JavaSerializationSerializer implements Serializer { - private ObjectOutputStream oos; + private ObjectOutputStream oos; - // Note that it is important to reproduce the crash that the name of this parameter not - // be changed: if it is e.g., "iShouldBeTreatedAsSibling1", no crash occurs. - public JavaSerializationSerializer(OutputStream out) throws IOException { - oos = - new ObjectOutputStream(out) { - @Override - protected void writeStreamHeader() { - // no header - } - }; - } + // Note that it is important to reproduce the crash that the name of this parameter not + // be changed: if it is e.g., "iShouldBeTreatedAsSibling1", no crash occurs. + public JavaSerializationSerializer(OutputStream out) throws IOException { + oos = + new ObjectOutputStream(out) { + @Override + protected void writeStreamHeader() { + // no header + } + }; + } - public void close() throws IOException { - oos.close(); - } + public void close() throws IOException { + oos.close(); } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java b/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java index 3f7886f5a60..60a562f0cc0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java @@ -7,7 +7,7 @@ */ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface AinferSibling1 { - String value() default "Sibling1"; + String value() default "Sibling1"; - String anotherValue() default "foo"; + String anotherValue() default "foo"; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java b/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java index 72ac6ada89f..165ad14a755 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java @@ -2,49 +2,49 @@ public class AnnotationWithFieldTest { - private String fields; - - private String emptyFields; - - void testAnnotationWithFields() { - fields = getAinferSiblingWithFields(); - // :: warning: (argument.type.incompatible) - expectsAinferSiblingWithFields(fields); - } - - void testAnnotationWithEmptyFields() { - emptyFields = getAinferSiblingWithFieldsEmpty(); - // :: warning: (argument.type.incompatible) - expectsAinferSiblingWithEmptyFields(emptyFields); - } - - void expectsAinferSiblingWithFields( - @AinferSiblingWithFields( - value = {"test", "test2"}, - value2 = "test3") - String s) {} - - void expectsAinferSiblingWithEmptyFields( - @AinferSiblingWithFields( - value = {}, - value2 = "") - String s) {} - - @SuppressWarnings("cast.unsafe") - String getAinferSiblingWithFields() { - return (@AinferSiblingWithFields( - value = {"test", "test2"}, - value2 = "test3") - String) - ""; - } - - @SuppressWarnings("cast.unsafe") - String getAinferSiblingWithFieldsEmpty() { - return (@AinferSiblingWithFields( - value = {}, - value2 = "") - String) - ""; - } + private String fields; + + private String emptyFields; + + void testAnnotationWithFields() { + fields = getAinferSiblingWithFields(); + // :: warning: (argument.type.incompatible) + expectsAinferSiblingWithFields(fields); + } + + void testAnnotationWithEmptyFields() { + emptyFields = getAinferSiblingWithFieldsEmpty(); + // :: warning: (argument.type.incompatible) + expectsAinferSiblingWithEmptyFields(emptyFields); + } + + void expectsAinferSiblingWithFields( + @AinferSiblingWithFields( + value = {"test", "test2"}, + value2 = "test3") + String s) {} + + void expectsAinferSiblingWithEmptyFields( + @AinferSiblingWithFields( + value = {}, + value2 = "") + String s) {} + + @SuppressWarnings("cast.unsafe") + String getAinferSiblingWithFields() { + return (@AinferSiblingWithFields( + value = {"test", "test2"}, + value2 = "test3") + String) + ""; + } + + @SuppressWarnings("cast.unsafe") + String getAinferSiblingWithFieldsEmpty() { + return (@AinferSiblingWithFields( + value = {}, + value2 = "") + String) + ""; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java b/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java index 38518230da5..ca5e9bcf8a0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java @@ -5,33 +5,33 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; public class Anonymous { - public static int field1; // parent - public static int field2; // sib2 + public static int field1; // parent + public static int field2; // sib2 - public Anonymous() { - field1 = getAinferSibling1(); - } + public Anonymous() { + field1 = getAinferSibling1(); + } - void testPublicInference() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(field2); - // :: warning: (argument.type.incompatible) - expectsParent(field1); - // :: warning: (argument.type.incompatible) - expectsParent(field2); - } + void testPublicInference() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(field2); + // :: warning: (argument.type.incompatible) + expectsParent(field1); + // :: warning: (argument.type.incompatible) + expectsParent(field2); + } - void expectsBottom(@AinferBottom int t) {} + void expectsBottom(@AinferBottom int t) {} - void expectsAinferSibling1(@AinferSibling1 int t) {} + void expectsAinferSibling1(@AinferSibling1 int t) {} - void expectsAinferSibling2(@AinferSibling2 int t) {} + void expectsAinferSibling2(@AinferSibling2 int t) {} - void expectsAinferTop(@AinferTop int t) {} + void expectsAinferTop(@AinferTop int t) {} - void expectsParent(@AinferParent int t) {} + void expectsParent(@AinferParent int t) {} - @AinferSibling1 int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + @AinferSibling1 int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java index 030f88cbabd..56648c3962e 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java @@ -3,39 +3,39 @@ // For WPI, this test is actually useful for testing that varargs are handled properly. public class AnonymousAndInnerClass { - class MyInnerClass { - public MyInnerClass() {} + class MyInnerClass { + public MyInnerClass() {} - public MyInnerClass(String s) {} + public MyInnerClass(String s) {} - public MyInnerClass(int... i) {} - } + public MyInnerClass(int... i) {} + } - static class MyClass { - public MyClass() {} + static class MyClass { + public MyClass() {} - public MyClass(String s) {} + public MyClass(String s) {} - public MyClass(int... i) {} - } + public MyClass(int... i) {} + } - void test(AnonymousAndInnerClass outer, String tainted) { - new MyClass() {}; - new MyClass(tainted) {}; - new MyClass(1, 2, 3) {}; - new MyClass(1) {}; - new MyInnerClass() {}; - new MyInnerClass(tainted) {}; - new MyInnerClass(1) {}; - new MyInnerClass(1, 2, 3) {}; - this.new MyInnerClass() {}; - this.new MyInnerClass(tainted) {}; - this.new MyInnerClass(1) {}; - this.new MyInnerClass(1, 2, 3) {}; - outer.new MyInnerClass() {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(1) {}; - outer.new MyInnerClass(1, 2, 3) {}; - } + void test(AnonymousAndInnerClass outer, String tainted) { + new MyClass() {}; + new MyClass(tainted) {}; + new MyClass(1, 2, 3) {}; + new MyClass(1) {}; + new MyInnerClass() {}; + new MyInnerClass(tainted) {}; + new MyInnerClass(1) {}; + new MyInnerClass(1, 2, 3) {}; + this.new MyInnerClass() {}; + this.new MyInnerClass(tainted) {}; + this.new MyInnerClass(1) {}; + this.new MyInnerClass(1, 2, 3) {}; + outer.new MyInnerClass() {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(1) {}; + outer.new MyInnerClass(1, 2, 3) {}; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java index e97b9ff0d9f..74a25110e83 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java @@ -4,5 +4,5 @@ import java.util.*; public class AnonymousClassField { - public static final List foo = new ArrayList() {}; + public static final List foo = new ArrayList() {}; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java index 49aa669c4bc..f7a59e06f9b 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java @@ -3,25 +3,25 @@ public class AnonymousClassWithField { - public void scan(InterfaceTest foo) { - // do nothing - } + public void scan(InterfaceTest foo) { + // do nothing + } - public void test() { - this.scan( - new InterfaceTestExtension() { - private String s1 = InterfaceTest.getAinferSibling1(); + public void test() { + this.scan( + new InterfaceTestExtension() { + private String s1 = InterfaceTest.getAinferSibling1(); - @Override - public void testX() { - // :: warning: (argument) - requireAinferSibling1(s1); - } + @Override + public void testX() { + // :: warning: (argument) + requireAinferSibling1(s1); + } - public void testY() { - // :: warning: (argument) - requireAinferSibling1(toaster); - } - }); - } + public void testY() { + // :: warning: (argument) + requireAinferSibling1(toaster); + } + }); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java index 4426b2fc58b..4e279363a95 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java @@ -2,17 +2,17 @@ // anonymous class that overrides a method. class AnonymousOverride { - public static void main(String[] args) { - (new SpecialThread() { - @Override - public void run() { - System.out.println("starting a thread!"); - } - }) - .start(); - } + public static void main(String[] args) { + (new SpecialThread() { + @Override + public void run() { + System.out.println("starting a thread!"); + } + }) + .start(); + } - private static class SpecialThread extends Thread { - public T t; - } + private static class SpecialThread extends Thread { + public T t; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java b/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java index b7488365d56..38b59005ebf 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java @@ -3,15 +3,15 @@ class ArrayAndTypevar { - private class MyClass { - private T t; + private class MyClass { + private T t; - public MyClass(T t) { - this.t = t; - } + public MyClass(T t) { + this.t = t; } + } - public void test() { - MyClass m = new MyClass(new String[] {"foo", "bar"}); - } + public void test() { + MyClass m = new MyClass(new String[] {"foo", "bar"}); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java index 07f75d63e10..bd42985d9fc 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java @@ -2,23 +2,23 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class CompoundTypeTest { - // The default type for fields is @AinferDefaultType. - Object[] field; + // The default type for fields is @AinferDefaultType. + Object[] field; - void assign() { - field = getCompoundType(); - } + void assign() { + field = getCompoundType(); + } - void test() { - // :: warning: (argument.type.incompatible) - expectsCompoundType(field); - } + void test() { + // :: warning: (argument.type.incompatible) + expectsCompoundType(field); + } - void expectsCompoundType(@AinferSibling1 Object @AinferSibling2 [] obj) {} + void expectsCompoundType(@AinferSibling1 Object @AinferSibling2 [] obj) {} - @AinferSibling1 Object @AinferSibling2 [] getCompoundType() { - @SuppressWarnings("cast.unsafe") - @AinferSibling1 Object @AinferSibling2 [] out = (@AinferSibling1 Object @AinferSibling2 []) new Object[1]; - return out; - } + @AinferSibling1 Object @AinferSibling2 [] getCompoundType() { + @SuppressWarnings("cast.unsafe") + @AinferSibling1 Object @AinferSibling2 [] out = (@AinferSibling1 Object @AinferSibling2 []) new Object[1]; + return out; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java index 91fb7315d3c..7b51f68f45d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java @@ -1,4 +1,4 @@ public class CompoundTypeTest2 { - private static int[] foo = new int[10]; - private static String[] bar = new String[10]; + private static int[] foo = new int[10]; + private static String[] bar = new String[10]; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java b/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java index 6ff64312e7f..2214046fc7c 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java @@ -6,26 +6,26 @@ public class ConflictingAnnotationsTest { - int getWPINamespaceAinferSibling1() { - return getAinferSibling1(); - } + int getWPINamespaceAinferSibling1() { + return getAinferSibling1(); + } - // This version of AinferSibling1 is not typechecked - it doesn't belong to the checker and - // instead is - // defined in the AinferSibling1.java file in this directory. - @AinferSibling1 Object getLocalAinferSibling1(Object o) { - return o; - } + // This version of AinferSibling1 is not typechecked - it doesn't belong to the checker and + // instead is + // defined in the AinferSibling1.java file in this directory. + @AinferSibling1 Object getLocalAinferSibling1(Object o) { + return o; + } - void test() { - // :: warning: argument.type.incompatible - expectsAinferSibling1(getWPINamespaceAinferSibling1()); - } + void test() { + // :: warning: argument.type.incompatible + expectsAinferSibling1(getWPINamespaceAinferSibling1()); + } - @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int getAinferSibling1() { - return 1; - } + @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int getAinferSibling1() { + return 1; + } - void expectsAinferSibling1( - @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int i) {} + void expectsAinferSibling1( + @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int i) {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java b/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java index 8d3cdddb66b..74e34d77351 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java @@ -2,11 +2,11 @@ public class ConstructorTest { - public ConstructorTest(int top) {} + public ConstructorTest(int top) {} - void test() { - @AinferTop int top = (@AinferTop int) 0; - // :: warning: (argument.type.incompatible) - new ConstructorTest(top); - } + void test() { + @AinferTop int top = (@AinferTop int) 0; + // :: warning: (argument.type.incompatible) + new ConstructorTest(top); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java b/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java index fd9a9064b0d..492a2fd3edd 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java +++ b/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java @@ -1,20 +1,20 @@ @SuppressWarnings("all") // Check for crashes. public class CrazyEnum { - private enum MyEnum { - ENUM_CONST1 { - private final String s = method(); + private enum MyEnum { + ENUM_CONST1 { + private final String s = method(); - private String method() { - return "hello"; - } - }, + private String method() { + return "hello"; + } + }, - ENUM_CONST2 { - private final String s = this.method(); + ENUM_CONST2 { + private final String s = this.method(); - private String method() { - return "hello"; - } - } + private String method() { + return "hello"; + } } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java b/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java index c0d466cbbf8..1e863b8617e 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java @@ -6,24 +6,24 @@ // annotated version of this class (in the annotated folder) should have no explicit // @AinferDefaultType annotations. public class DefaultsTest { - String defaultField = ""; - String defaultField2; + String defaultField = ""; + String defaultField2; - void test() { - @SuppressWarnings("all") // To allow the use of the explicit @AinferDefaultType. - @AinferDefaultType String explicitDefault = ""; - defaultField2 = explicitDefault; - } + void test() { + @SuppressWarnings("all") // To allow the use of the explicit @AinferDefaultType. + @AinferDefaultType String explicitDefault = ""; + defaultField2 = explicitDefault; + } - // This method's return type should not be updated by the whole-program inference - // since it is the default. - String lubTest() { - if (Math.random() > 0.5) { - return ""; // @AinferDefaultType - } else { - @SuppressWarnings("cast.unsafe") - @AinferBottom String s = (@AinferBottom String) ""; - return s; - } + // This method's return type should not be updated by the whole-program inference + // since it is the default. + String lubTest() { + if (Math.random() > 0.5) { + return ""; // @AinferDefaultType + } else { + @SuppressWarnings("cast.unsafe") + @AinferBottom String s = (@AinferBottom String) ""; + return s; } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java b/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java index bb9743d84d6..ca9c1015a00 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java @@ -2,13 +2,13 @@ // instead of "enum". public class DeviceTypeTest { - public enum DeviceType { - TRACKER; - } + public enum DeviceType { + TRACKER; + } - private final DeviceType deviceType; + private final DeviceType deviceType; - public DeviceTypeTest() { - deviceType = DeviceType.valueOf("tracker"); - } + public DeviceTypeTest() { + deviceType = DeviceType.valueOf("tracker"); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java b/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java index 6844994e7fa..fc968b0ea10 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java +++ b/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java @@ -4,5 +4,5 @@ import java.util.Map; public class DoubleGeneric { - static Map> map10 = new HashMap>(); + static Map> map10 = new HashMap>(); } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java index 5e16ac6eaba..8816a3f5f18 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java @@ -7,8 +7,8 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; class EnsuresQualifierFieldDecl { - @AinferSibling1 Object bar; + @AinferSibling1 Object bar; - // No annotation should be inferred here. - void test() {} + // No annotation should be inferred here. + void test() {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java index 079c061bbb6..6cb3fd23091 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java @@ -6,179 +6,179 @@ class EnsuresQualifierParamsTest { - // these methods are used to infer types + // these methods are used to infer types - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferParent.class) - void becomeParent(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferParent.class) + void becomeParent(Object arg) {} - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferSibling1.class) - void becomeAinferSibling1(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferSibling1.class) + void becomeAinferSibling1(Object arg) {} - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferSibling2.class) - void becomeAinferSibling2(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferSibling2.class) + void becomeAinferSibling2(Object arg) {} - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferBottom.class) - void becomeBottom(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferBottom.class) + void becomeBottom(Object arg) {} - // these methods should have types inferred for them - - void argIsParent(Object arg) { - becomeParent(arg); - } - - void argIsParent_2(Object arg, boolean b) { - if (b) { - becomeAinferSibling1(arg); - } else { - becomeAinferSibling2(arg); - } - } - - void argIsAinferSibling2(Object arg) { - becomeAinferSibling2(arg); - } - - void argIsAinferSibling2_2(Object arg, boolean b) { - if (b) { - becomeAinferSibling2(arg); - } else { - becomeBottom(arg); - } - } - - void thisIsParent() { - becomeParent(this); - } - - void thisIsParent_2(boolean b) { - if (b) { - becomeAinferSibling1(this); - } else { - becomeAinferSibling2(this); - } - } - - void thisIsParent_2_2(boolean b) { - if (b) { - becomeAinferSibling2(this); - } else { - becomeAinferSibling1(this); - } - } - - void thisIsParent_3(boolean b) { - if (b) { - becomeAinferSibling1(this); - } else { - becomeAinferSibling2(this); - } - noEnsures(); - } - - void thisIsEmpty(boolean b) { - if (b) { - // do nothing - this.noEnsures(); - } else { - becomeAinferSibling1(this); - } - } - - void thisIsAinferSibling2() { - becomeAinferSibling2(this); - } - - void thisIsAinferSibling2_2(boolean b) { - if (b) { - becomeAinferSibling2(this); - } else { - becomeBottom(this); - } - } - - void thisIsAinferSibling2_2_2(boolean b) { - if (b) { - becomeBottom(this); - } else { - becomeAinferSibling2(this); - } - } - - void noEnsures() {} - - void client1(Object arg) { - argIsParent(arg); - // :: warning: (assignment.type.incompatible) - @AinferParent Object p = arg; - } - - void client2(Object arg) { - argIsParent_2(arg, true); - // :: warning: (assignment.type.incompatible) - @AinferParent Object p = arg; - } - - void client3(Object arg) { - argIsAinferSibling2(arg); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object x = arg; - } - - void client4(Object arg) { - argIsAinferSibling2_2(arg, true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object x = arg; - } - - void clientThis1() { - thisIsParent(); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis2() { - thisIsParent_2(true); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis2_2() { - thisIsParent_2(false); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis2_3() { - thisIsParent_3(false); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis3() { - thisIsAinferSibling2(); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = this; - } - - void clientThis4() { - thisIsAinferSibling2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = this; - } - - void clientThis5() { - thisIsAinferSibling2_2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = this; - } - - void clientThis6() { - thisIsParent_2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } + // these methods should have types inferred for them + + void argIsParent(Object arg) { + becomeParent(arg); + } + + void argIsParent_2(Object arg, boolean b) { + if (b) { + becomeAinferSibling1(arg); + } else { + becomeAinferSibling2(arg); + } + } + + void argIsAinferSibling2(Object arg) { + becomeAinferSibling2(arg); + } + + void argIsAinferSibling2_2(Object arg, boolean b) { + if (b) { + becomeAinferSibling2(arg); + } else { + becomeBottom(arg); + } + } + + void thisIsParent() { + becomeParent(this); + } + + void thisIsParent_2(boolean b) { + if (b) { + becomeAinferSibling1(this); + } else { + becomeAinferSibling2(this); + } + } + + void thisIsParent_2_2(boolean b) { + if (b) { + becomeAinferSibling2(this); + } else { + becomeAinferSibling1(this); + } + } + + void thisIsParent_3(boolean b) { + if (b) { + becomeAinferSibling1(this); + } else { + becomeAinferSibling2(this); + } + noEnsures(); + } + + void thisIsEmpty(boolean b) { + if (b) { + // do nothing + this.noEnsures(); + } else { + becomeAinferSibling1(this); + } + } + + void thisIsAinferSibling2() { + becomeAinferSibling2(this); + } + + void thisIsAinferSibling2_2(boolean b) { + if (b) { + becomeAinferSibling2(this); + } else { + becomeBottom(this); + } + } + + void thisIsAinferSibling2_2_2(boolean b) { + if (b) { + becomeBottom(this); + } else { + becomeAinferSibling2(this); + } + } + + void noEnsures() {} + + void client1(Object arg) { + argIsParent(arg); + // :: warning: (assignment.type.incompatible) + @AinferParent Object p = arg; + } + + void client2(Object arg) { + argIsParent_2(arg, true); + // :: warning: (assignment.type.incompatible) + @AinferParent Object p = arg; + } + + void client3(Object arg) { + argIsAinferSibling2(arg); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object x = arg; + } + + void client4(Object arg) { + argIsAinferSibling2_2(arg, true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object x = arg; + } + + void clientThis1() { + thisIsParent(); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis2() { + thisIsParent_2(true); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis2_2() { + thisIsParent_2(false); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis2_3() { + thisIsParent_3(false); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis3() { + thisIsAinferSibling2(); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = this; + } + + void clientThis4() { + thisIsAinferSibling2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = this; + } + + void clientThis5() { + thisIsAinferSibling2_2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = this; + } + + void clientThis6() { + thisIsParent_2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java index 03718f90639..877352e242e 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java @@ -6,77 +6,77 @@ class EnsuresQualifierTest { - @AinferTop int field1; - @AinferTop int field2; + @AinferTop int field1; + @AinferTop int field2; - @AinferTop int top; - @AinferParent int parent; - @AinferSibling1 int sibling1; - @AinferSibling2 int sibling2; - @AinferBottom int bottom; + @AinferTop int top; + @AinferParent int parent; + @AinferSibling1 int sibling1; + @AinferSibling2 int sibling2; + @AinferBottom int bottom; - void field1IsParent() { - field1 = parent; - } + void field1IsParent() { + field1 = parent; + } - void field1IsParent_2(boolean b) { - if (b) { - field1 = sibling1; - } else { - field1 = sibling2; - } + void field1IsParent_2(boolean b) { + if (b) { + field1 = sibling1; + } else { + field1 = sibling2; } + } - void field1IsAinferSibling2() { - field1 = sibling2; - } + void field1IsAinferSibling2() { + field1 = sibling2; + } - void field1IsAinferSibling2_2(boolean b) { - if (b) { - field1 = sibling2; - } else { - field1 = bottom; - } + void field1IsAinferSibling2_2(boolean b) { + if (b) { + field1 = sibling2; + } else { + field1 = bottom; } + } - void parentIsAinferSibling1() { - parent = sibling1; - } + void parentIsAinferSibling1() { + parent = sibling1; + } - // Prevent refinement of the `parent` field variable. - void parentIsParent(@AinferParent int x) { - parent = x; - } + // Prevent refinement of the `parent` field variable. + void parentIsParent(@AinferParent int x) { + parent = x; + } - void noEnsures() {} + void noEnsures() {} - void client1() { - field1IsParent(); - // :: warning: (assignment.type.incompatible) - @AinferParent int p = field1; - } + void client1() { + field1IsParent(); + // :: warning: (assignment.type.incompatible) + @AinferParent int p = field1; + } - void client2() { - field1IsParent_2(true); - // :: warning: (assignment.type.incompatible) - @AinferParent int p = field1; - } + void client2() { + field1IsParent_2(true); + // :: warning: (assignment.type.incompatible) + @AinferParent int p = field1; + } - void client3() { - field1IsAinferSibling2(); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 int x = field1; - } + void client3() { + field1IsAinferSibling2(); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 int x = field1; + } - void client4() { - field1IsAinferSibling2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 int x = field1; - } + void client4() { + field1IsAinferSibling2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 int x = field1; + } - void client5() { - parentIsAinferSibling1(); - // :: warning: (assignment.type.incompatible) - @AinferSibling1 int x = parent; - } + void client5() { + parentIsAinferSibling1(); + // :: warning: (assignment.type.incompatible) + @AinferSibling1 int x = parent; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java b/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java index d971edba23d..0a3d45d9dcc 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java @@ -7,15 +7,15 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class EnumConstants { - enum MyEnum { - ONE, - TWO; - } + enum MyEnum { + ONE, + TWO; + } - void requiresS1(@AinferSibling1 MyEnum e) {} + void requiresS1(@AinferSibling1 MyEnum e) {} - void test() { - // :: warning: argument.type.incompatible - requiresS1(MyEnum.ONE); - } + void test() { + // :: warning: argument.type.incompatible + requiresS1(MyEnum.ONE); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java b/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java index 622d6e6ee08..8c7b8295e61 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java @@ -4,23 +4,23 @@ @SuppressWarnings("all") // only check for crashes public class EnumMapCrash { - private class Holder { - public T held; + private class Holder { + public T held; - public Holder(T held) { - this.held = held; - } + public Holder(T held) { + this.held = held; + } - @Override - public String toString() { - return String.valueOf(held); - } + @Override + public String toString() { + return String.valueOf(held); } + } - private enum FSEditLogOpCodes {} + private enum FSEditLogOpCodes {} - void callHolder(FSEditLogOpCodes f, EnumMap> opCounts) { - Holder holder = opCounts.get(f); - holder.held++; - } + void callHolder(FSEditLogOpCodes f, EnumMap> opCounts) { + Holder holder = opCounts.get(f); + holder.held++; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java b/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java index 6e07594ad34..183fa6d69d1 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java @@ -1,29 +1,29 @@ public class EnumTest { - public enum MyEnum { - ONE("ONE"), - TWO("TWO"), - THREE("THREE"); + public enum MyEnum { + ONE("ONE"), + TWO("TWO"), + THREE("THREE"); - private final String value; + private final String value; - private MyEnum(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } + private MyEnum(String value) { + this.value = value; + } - public static MyEnum fromValue(String value) throws IllegalArgumentException { - for (MyEnum method : MyEnum.values()) { - String methodString = method.toString(); - if (methodString != null && methodString.equals(value)) { - return method; - } - } + @Override + public String toString() { + return value; + } - throw new IllegalArgumentException("Cannot create enum from: " + value); + public static MyEnum fromValue(String value) throws IllegalArgumentException { + for (MyEnum method : MyEnum.values()) { + String methodString = method.toString(); + if (methodString != null && methodString.equals(value)) { + return method; } + } + + throw new IllegalArgumentException("Cannot create enum from: " + value); } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java b/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java index 32a1b3ae419..facb8ab99b5 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java @@ -4,18 +4,18 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; enum EnumWithInnerClass { - CONSTANT; + CONSTANT; - private static class MyInnerClass { - int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + private static class MyInnerClass { + int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } - void requireAinferSibling1(@AinferSibling1 int x) {} + void requireAinferSibling1(@AinferSibling1 int x) {} - void test() { - // :: warning: argument.type.incompatible - requireAinferSibling1(getAinferSibling1()); - } + void test() { + // :: warning: argument.type.incompatible + requireAinferSibling1(getAinferSibling1()); } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java b/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java index 5a8e9d7453d..97fd0b72274 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java @@ -7,27 +7,27 @@ public class ExistingPurityAnnotations { - Object obj; + Object obj; - public Object pureMethod(Object object) { - return null; - } + public Object pureMethod(Object object) { + return null; + } - @SuppressWarnings("ainfertest") - @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) - public boolean checkAinferSibling1(Object obj1) { - return true; - } + @SuppressWarnings("ainfertest") + @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) + public boolean checkAinferSibling1(Object obj1) { + return true; + } - public @AinferSibling1 Object usePureMethod() { + public @AinferSibling1 Object usePureMethod() { - if (checkAinferSibling1(obj)) { - // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should - // unrefine the type of obj, and an error will be issued when - // we try to return obj on the next line. - pureMethod(obj); - return obj; - } - return null; + if (checkAinferSibling1(obj)) { + // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should + // unrefine the type of obj, and an error will be issued when + // we try to return obj on the next line. + pureMethod(obj); + return obj; } + return null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java b/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java index 9c63af4c83f..ed491538d10 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java @@ -1,3 +1,4 @@ +import java.lang.reflect.Field; import org.checkerframework.checker.testchecker.ainfer.qual.AinferBottom; import org.checkerframework.checker.testchecker.ainfer.qual.AinferParent; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; @@ -6,239 +7,237 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; import org.checkerframework.framework.qual.IgnoreInWholeProgramInference; -import java.lang.reflect.Field; - /** * This file contains expected errors that should exist even after the jaif type inference occurs. */ public class ExpectedErrors { - // Case where the declared type is a supertype of the refined type. - private @AinferTop int privateDeclaredField; - public @AinferTop int publicDeclaredField; - - // The type of both privateDeclaredField and publicDeclaredField are - // not refined to @AinferBottom. - void assignFieldsToAinferSibling1() { - privateDeclaredField = getAinferSibling1(); - publicDeclaredField = getAinferSibling1(); - } - - void testFields() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(privateDeclaredField); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(publicDeclaredField); - } - - // Case where the declared type is a subtype of the refined type. - private @AinferBottom int privateDeclaredField2; - public @AinferBottom int publicDeclaredField2; - - // The refinement cannot happen and an assignemnt type incompatible error occurs. - void assignFieldsToAinferTop() { - // :: warning: (assignment.type.incompatible) - privateDeclaredField2 = getAinferTop(); - // :: warning: (assignment.type.incompatible) - publicDeclaredField2 = getAinferTop(); - } + // Case where the declared type is a supertype of the refined type. + private @AinferTop int privateDeclaredField; + public @AinferTop int publicDeclaredField; + + // The type of both privateDeclaredField and publicDeclaredField are + // not refined to @AinferBottom. + void assignFieldsToAinferSibling1() { + privateDeclaredField = getAinferSibling1(); + publicDeclaredField = getAinferSibling1(); + } + + void testFields() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(privateDeclaredField); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(publicDeclaredField); + } + + // Case where the declared type is a subtype of the refined type. + private @AinferBottom int privateDeclaredField2; + public @AinferBottom int publicDeclaredField2; + + // The refinement cannot happen and an assignemnt type incompatible error occurs. + void assignFieldsToAinferTop() { + // :: warning: (assignment.type.incompatible) + privateDeclaredField2 = getAinferTop(); + // :: warning: (assignment.type.incompatible) + publicDeclaredField2 = getAinferTop(); + } + + // No errors should be issued below: + void assignFieldsToBot() { + privateDeclaredField2 = getBottom(); + publicDeclaredField2 = getBottom(); + } + + // Testing that the types above were not widened. + void testFields2() { + expectsBottom(privateDeclaredField2); + expectsBottom(publicDeclaredField2); + } + + // LUB TEST + // The default type for fields is @AinferTop. + private static int lubPrivateField; + public static int lubPublicField; + + void assignLubFieldsToAinferSibling1() { + lubPrivateField = getAinferSibling1(); + lubPublicField = getAinferSibling1(); + } + + static { + lubPrivateField = getAinferSibling2(); + lubPublicField = getAinferSibling2(); + } + + void testLUBFields1() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(lubPrivateField); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(lubPublicField); + } + + void testLUBFields2() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(lubPrivateField); + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(lubPublicField); + } + + private static boolean bool = false; + + public static int lubTest() { + if (bool) { + return (@AinferSibling1 int) 0; + } else { + return (@AinferSibling2 int) 0; + } + } + + public @AinferSibling1 int getAinferSibling1Wrong() { + int x = lubTest(); + // :: warning: (return.type.incompatible) + return x; + } + + public @AinferSibling2 int getAinferSibling2Wrong() { + int x = lubTest(); + // :: warning: (return.type.incompatible) + return x; + } + + void expectsAinferSibling1(@AinferSibling1 int t) {} + + void expectsAinferSibling2(@AinferSibling2 int t) {} + + void expectsBottom(@AinferBottom int t) {} + + void expectsBottom(@AinferBottom String t) {} + + void expectsAinferTop(@AinferTop int t) {} + + void expectsParent(@AinferParent int t) {} + + static @AinferSibling1 int getAinferSibling1() { + return 0; + } + + static @AinferSibling2 int getAinferSibling2() { + return 0; + } + + @AinferBottom int getBottom() { + return 0; + } + + @AinferTop int getAinferTop() { + return 0; + } + + // Method Field.setBoolean != ExpectedErrors.setBoolean. + // No refinement should happen. + void test(Field f) throws Exception { + f.setBoolean(null, false); + } + + void setBoolean(Object o, boolean b) { + // :: warning: (assignment.type.incompatible) + @AinferBottom Object bot = o; + } + + public class SuppressWarningsTest { + // Tests that whole-program inference in a @SuppressWarnings block is ignored. + private int i; + private int i2; - // No errors should be issued below: - void assignFieldsToBot() { - privateDeclaredField2 = getBottom(); - publicDeclaredField2 = getBottom(); - } - - // Testing that the types above were not widened. - void testFields2() { - expectsBottom(privateDeclaredField2); - expectsBottom(publicDeclaredField2); - } - - // LUB TEST - // The default type for fields is @AinferTop. - private static int lubPrivateField; - public static int lubPublicField; - - void assignLubFieldsToAinferSibling1() { - lubPrivateField = getAinferSibling1(); - lubPublicField = getAinferSibling1(); - } - - static { - lubPrivateField = getAinferSibling2(); - lubPublicField = getAinferSibling2(); - } - - void testLUBFields1() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(lubPrivateField); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(lubPublicField); - } - - void testLUBFields2() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(lubPrivateField); - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(lubPublicField); - } - - private static boolean bool = false; - - public static int lubTest() { - if (bool) { - return (@AinferSibling1 int) 0; - } else { - return (@AinferSibling2 int) 0; - } - } - - public @AinferSibling1 int getAinferSibling1Wrong() { - int x = lubTest(); - // :: warning: (return.type.incompatible) - return x; + @SuppressWarnings("all") + public void suppressWarningsTest() { + i = (@AinferSibling1 int) 0; + i2 = getAinferSibling1(); } - public @AinferSibling2 int getAinferSibling2Wrong() { - int x = lubTest(); - // :: warning: (return.type.incompatible) - return x; + public void suppressWarningsTest2() { + SuppressWarningsInner.i = (@AinferSibling1 int) 0; + SuppressWarningsInner.i2 = getAinferSibling1(); } - void expectsAinferSibling1(@AinferSibling1 int t) {} - - void expectsAinferSibling2(@AinferSibling2 int t) {} - - void expectsBottom(@AinferBottom int t) {} - - void expectsBottom(@AinferBottom String t) {} + public void suppressWarningsValidation() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(i); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(i2); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(SuppressWarningsInner.i); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(SuppressWarningsInner.i2); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(suppressWarningsMethodReturn()); - void expectsAinferTop(@AinferTop int t) {} - - void expectsParent(@AinferParent int t) {} - - static @AinferSibling1 int getAinferSibling1() { - return 0; + suppressWarningsMethodParams(getAinferSibling1()); } - static @AinferSibling2 int getAinferSibling2() { - return 0; + @SuppressWarnings("all") + public int suppressWarningsMethodReturn() { + return getAinferSibling1(); } - @AinferBottom int getBottom() { - return 0; - } + // It is problematic to automatically test whole-program inference for method params when + // suppressing warnings. + // Since we must use @SuppressWarnings() for the method, we won't be able to catch any error + // inside the method body. Verified manually that in the "annotated" folder param's type + // wasn't updated. + @SuppressWarnings("all") + public void suppressWarningsMethodParams(int param) {} + } - @AinferTop int getAinferTop() { - return 0; - } + @SuppressWarnings("all") + static class SuppressWarningsInner { + public static int i; + public static int i2; + } - // Method Field.setBoolean != ExpectedErrors.setBoolean. - // No refinement should happen. - void test(Field f) throws Exception { - f.setBoolean(null, false); - } + class NullTest { + // The default type for fields is @AinferDefaultType. + private String privateField; + public String publicField; - void setBoolean(Object o, boolean b) { - // :: warning: (assignment.type.incompatible) - @AinferBottom Object bot = o; + // The types of both fields are not refined to @AinferBottom, as whole-program + // inference never performs refinement in the presence of the null literal. + @SuppressWarnings("value") + void assignFieldsToBottom() { + privateField = null; + publicField = null; } - public class SuppressWarningsTest { - // Tests that whole-program inference in a @SuppressWarnings block is ignored. - private int i; - private int i2; - - @SuppressWarnings("all") - public void suppressWarningsTest() { - i = (@AinferSibling1 int) 0; - i2 = getAinferSibling1(); - } - - public void suppressWarningsTest2() { - SuppressWarningsInner.i = (@AinferSibling1 int) 0; - SuppressWarningsInner.i2 = getAinferSibling1(); - } - - public void suppressWarningsValidation() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(i); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(i2); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(SuppressWarningsInner.i); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(SuppressWarningsInner.i2); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(suppressWarningsMethodReturn()); - - suppressWarningsMethodParams(getAinferSibling1()); - } - - @SuppressWarnings("all") - public int suppressWarningsMethodReturn() { - return getAinferSibling1(); - } - - // It is problematic to automatically test whole-program inference for method params when - // suppressing warnings. - // Since we must use @SuppressWarnings() for the method, we won't be able to catch any error - // inside the method body. Verified manually that in the "annotated" folder param's type - // wasn't updated. - @SuppressWarnings("all") - public void suppressWarningsMethodParams(int param) {} + // Testing the refinement above. + void testFields() { + // :: warning: (argument.type.incompatible) + expectsBottom(privateField); + // :: warning: (argument.type.incompatible) + expectsBottom(publicField); } + } - @SuppressWarnings("all") - static class SuppressWarningsInner { - public static int i; - public static int i2; - } + class IgnoreMetaAnnotationTest2 { + @AinferToIgnore int field; + @IgnoreInWholeProgramInference int field2; - class NullTest { - // The default type for fields is @AinferDefaultType. - private String privateField; - public String publicField; - - // The types of both fields are not refined to @AinferBottom, as whole-program - // inference never performs refinement in the presence of the null literal. - @SuppressWarnings("value") - void assignFieldsToBottom() { - privateField = null; - publicField = null; - } - - // Testing the refinement above. - void testFields() { - // :: warning: (argument.type.incompatible) - expectsBottom(privateField); - // :: warning: (argument.type.incompatible) - expectsBottom(publicField); - } + void foo() { + field = getAinferSibling1(); + field2 = getAinferSibling1(); } - class IgnoreMetaAnnotationTest2 { - @AinferToIgnore int field; - @IgnoreInWholeProgramInference int field2; - - void foo() { - field = getAinferSibling1(); - field2 = getAinferSibling1(); - } - - void test() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(field); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(field2); - } + void test() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(field); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(field2); } + } - class AssignParam { - public void f(@AinferBottom Object param) { - // :: warning: assignment.type.incompatible - param = ((@AinferTop Object) null); - } + class AssignParam { + public void f(@AinferBottom Object param) { + // :: warning: assignment.type.incompatible + param = ((@AinferTop Object) null); } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java b/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java index b19093a4baf..aad961e0c6e 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java +++ b/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java @@ -1,16 +1,15 @@ -import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; - import java.util.GregorianCalendar; +import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class FieldInOtherCompilationUnit { - static @AinferSibling1 int myTime; + static @AinferSibling1 int myTime; - static void test() { - new GregorianCalendar() { - public void newMethod() { - this.time = myTime; - } - }; - } + static void test() { + new GregorianCalendar() { + public void newMethod() { + this.time = myTime; + } + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java b/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java index 894bcfa0259..b836ba11ad6 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java +++ b/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java @@ -5,46 +5,46 @@ public class FromReceiver { - public void source(@AinferSibling1 FromReceiver this) { - this.sinkNoThis(); - this.sinkExplicitThis(); - - sinkNoThis2(); - sinkExplicitThis2(); - } - - public void sinkNoThis() { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkExplicitThis(FromReceiver this) { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkNoThis2() { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkExplicitThis2(FromReceiver this) { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public static void source2(@AinferSibling1 FromReceiver f1) { - f1.sinkNoThis3(); - f1.sinkExplicitThis3(); - } - - public void sinkNoThis3() { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkExplicitThis3(FromReceiver this) { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } + public void source(@AinferSibling1 FromReceiver this) { + this.sinkNoThis(); + this.sinkExplicitThis(); + + sinkNoThis2(); + sinkExplicitThis2(); + } + + public void sinkNoThis() { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkExplicitThis(FromReceiver this) { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkNoThis2() { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkExplicitThis2(FromReceiver this) { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public static void source2(@AinferSibling1 FromReceiver f1) { + f1.sinkNoThis3(); + f1.sinkExplicitThis3(); + } + + public void sinkNoThis3() { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkExplicitThis3(FromReceiver this) { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java b/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java index 7bd45940232..0f69ff066d9 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java +++ b/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java @@ -7,8 +7,8 @@ @SuppressWarnings("super.invocation") // Intentional. public class IShouldBeSibling1 { - public static void test(IShouldBeSibling1 s1) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 IShouldBeSibling1 s = s1; - } + public static void test(IShouldBeSibling1 s1) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 IShouldBeSibling1 s = s1; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java b/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java index ae75e631512..b418a95c2ca 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java +++ b/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java @@ -3,20 +3,20 @@ // See ExpectedErrors#IgnoreMetaAnnotationTest2 public class IgnoreMetaAnnotationTest1 { - int field2; + int field2; - void foo() { - field2 = getAinferSibling1(); - } + void foo() { + field2 = getAinferSibling1(); + } - void test() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(field2); - } + void test() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(field2); + } - void expectsAinferSibling1(@AinferSibling1 int t) {} + void expectsAinferSibling1(@AinferSibling1 int t) {} - static @AinferSibling1 int getAinferSibling1() { - return 0; - } + static @AinferSibling1 int getAinferSibling1() { + return 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java b/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java index e5b02727622..a5de87704ef 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java @@ -1,7 +1,7 @@ public class ImplicitAnnosTest { - void test() { - StringBuffer sb = new StringBuffer(); - StringBuffer sb2 = sb; - } + void test() { + StringBuffer sb = new StringBuffer(); + StringBuffer sb2 = sb; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java b/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java index 05543bdf34d..4c03d0779d3 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferBottom; public class InheritanceTest { - class IParent { - int field; + class IParent { + int field; - public void expectsBotNoSignature(int t) { - // :: warning: (argument.type.incompatible) - expectsBot(t); - // :: warning: (argument.type.incompatible) - expectsBot(field); - } - - void expectsBot(@AinferBottom int t) {} + public void expectsBotNoSignature(int t) { + // :: warning: (argument.type.incompatible) + expectsBot(t); + // :: warning: (argument.type.incompatible) + expectsBot(field); } - class IChild extends IParent { - void test1() { - @AinferBottom int bot = (@AinferBottom int) 0; - expectsBotNoSignature(bot); - field = bot; - } + void expectsBot(@AinferBottom int t) {} + } + + class IChild extends IParent { + void test1() { + @AinferBottom int bot = (@AinferBottom int) 0; + expectsBotNoSignature(bot); + field = bot; } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java b/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java index c8b5cb366a7..20a1eb20dcb 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java @@ -7,18 +7,18 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTreatAsSibling1; public class InnerClassFieldDeclAnno { - static class Outer { - static class Inner {} - } + static class Outer { + static class Inner {} + } - public Outer.Inner iShouldBeTreatedAsSibling1 = new Outer.Inner(); + public Outer.Inner iShouldBeTreatedAsSibling1 = new Outer.Inner(); - @AinferTreatAsSibling1 public Outer.Inner preAnnotated = null; + @AinferTreatAsSibling1 public Outer.Inner preAnnotated = null; - public static void test(InnerClassFieldDeclAnno a) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 Object obj = a.iShouldBeTreatedAsSibling1; - // Test that the annotation works as expected. - @AinferSibling1 Object obj2 = a.preAnnotated; - } + public static void test(InnerClassFieldDeclAnno a) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 Object obj = a.iShouldBeTreatedAsSibling1; + // Test that the annotation works as expected. + @AinferSibling1 Object obj2 = a.preAnnotated; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java index b713d30dfc6..65d56a44874 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java @@ -1,17 +1,17 @@ public class InnerTypeTest { - public static String toStringQuoted(Object[] a) { - return toString(a, true); - } + public static String toStringQuoted(Object[] a) { + return toString(a, true); + } - public static String toString(Object[] a, boolean quoted) { - if (a == null) { - return "null"; - } - StringBuffer sb = new StringBuffer(); - return sb.toString(); + public static String toString(Object[] a, boolean quoted) { + if (a == null) { + return "null"; } + StringBuffer sb = new StringBuffer(); + return sb.toString(); + } - public void bar() { - assert InnerTypeTest.toStringQuoted((Object[]) null).equals("null"); - } + public void bar() { + assert InnerTypeTest.toStringQuoted((Object[]) null).equals("null"); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java index 52e8111d4ec..3c90c7d348e 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java @@ -1,10 +1,10 @@ public class InnerTypeTest2 { - public static int[] min_max(int[] a) { - if (a.length == 0) { - return null; - } - int result_min = a[0]; - int result_max = a[0]; - return new int[] {result_min, result_max}; + public static int[] min_max(int[] a) { + if (a.length == 0) { + return null; } + int result_min = a[0]; + int result_max = a[0]; + return new int[] {result_min, result_max}; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java index eb3386cc5e6..522d352a2ea 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java @@ -1,9 +1,9 @@ public class InnerTypeTest3 { - private int[] nums; + private int[] nums; - private static byte[] buffer = new byte[4096]; + private static byte[] buffer = new byte[4096]; - private static final char[] digits = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; + private static final char[] digits = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java b/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java index 6ceeb0109a5..2472f7938e8 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java @@ -4,16 +4,16 @@ @SuppressWarnings("cast.unsafe") public interface InterfaceTest { - public String toaster = getAinferSibling1(); + public String toaster = getAinferSibling1(); - public static @AinferSibling1 String getAinferSibling1() { - return (@AinferSibling1 String) "foo"; - } + public static @AinferSibling1 String getAinferSibling1() { + return (@AinferSibling1 String) "foo"; + } - default void requireAinferSibling1(@AinferSibling1 String x) {} + default void requireAinferSibling1(@AinferSibling1 String x) {} - default void testX() { - // :: warning: (argument.type.incompatible) - requireAinferSibling1(toaster); - } + default void testX() { + // :: warning: (argument.type.incompatible) + requireAinferSibling1(toaster); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java b/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java index 6d2e3c26295..5f40371a983 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java @@ -3,48 +3,48 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class LUBAssignmentTest { - // The default type for fields is @AinferDefaultType. - private static int privateField; - public static int publicField; - - void assignFieldsToAinferSibling1() { - privateField = getAinferSibling1(); - publicField = getAinferSibling1(); - } - - static { - privateField = getAinferSibling2(); - publicField = getAinferSibling2(); - } - - // LUB between @AinferSibling1 and @AinferSibling2 is @AinferParent, therefore the assignments - // above refine the type of privateField to @AinferParent. - void testFields() { - // :: warning: (argument.type.incompatible) - expectsParent(privateField); - // :: warning: (argument.type.incompatible) - expectsParent(publicField); - } - - void expectsParent(@AinferParent int t) {} - - static @AinferSibling1 int getAinferSibling1() { - return 0; - } - - static @AinferSibling2 int getAinferSibling2() { - return 0; - } - - String lubTest2() { - if (Math.random() > 0.5) { - @SuppressWarnings("cast.unsafe") - @AinferSibling1 String s = (@AinferSibling1 String) ""; - return s; - } else { - @SuppressWarnings("cast.unsafe") - @AinferSibling2 String s = (@AinferSibling2 String) ""; - return s; - } + // The default type for fields is @AinferDefaultType. + private static int privateField; + public static int publicField; + + void assignFieldsToAinferSibling1() { + privateField = getAinferSibling1(); + publicField = getAinferSibling1(); + } + + static { + privateField = getAinferSibling2(); + publicField = getAinferSibling2(); + } + + // LUB between @AinferSibling1 and @AinferSibling2 is @AinferParent, therefore the assignments + // above refine the type of privateField to @AinferParent. + void testFields() { + // :: warning: (argument.type.incompatible) + expectsParent(privateField); + // :: warning: (argument.type.incompatible) + expectsParent(publicField); + } + + void expectsParent(@AinferParent int t) {} + + static @AinferSibling1 int getAinferSibling1() { + return 0; + } + + static @AinferSibling2 int getAinferSibling2() { + return 0; + } + + String lubTest2() { + if (Math.random() > 0.5) { + @SuppressWarnings("cast.unsafe") + @AinferSibling1 String s = (@AinferSibling1 String) ""; + return s; + } else { + @SuppressWarnings("cast.unsafe") + @AinferSibling2 String s = (@AinferSibling2 String) ""; + return s; } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java b/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java index 91497b956b2..e9e9d6a655d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java @@ -8,18 +8,18 @@ @SuppressWarnings("all") // only checking for crashes public class LambdaParamCrash { - void groupAndSend() { - addListener( - (r, e1) -> { - if (e1 == null) { - e1 = badResponse(); - } - }); - } + void groupAndSend() { + addListener( + (r, e1) -> { + if (e1 == null) { + e1 = badResponse(); + } + }); + } - private IOException badResponse() { - return new IOException(); - } + private IOException badResponse() { + return new IOException(); + } - public static void addListener(BiConsumer action) {} + public static void addListener(BiConsumer action) {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java b/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java index 7b24565b178..44300afcd5d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java @@ -4,15 +4,15 @@ import java.io.FileFilter; public class LambdaReturn { - void test() { - FileFilter docxFilter = - pathname -> { - // We only want to process *.docx files, everything else can be skipped. - if (pathname.isFile() && pathname.getName().matches(".*\\.docx")) { - return true; - } + void test() { + FileFilter docxFilter = + pathname -> { + // We only want to process *.docx files, everything else can be skipped. + if (pathname.isFile() && pathname.getName().matches(".*\\.docx")) { + return true; + } - return false; - }; - } + return false; + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java b/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java index cc1fb6d4b12..a4c1d572bc6 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class LocalClassTest { - public void method() { - class Local { - Object o = (@AinferSibling1 Object) null; - } + public void method() { + class Local { + Object o = (@AinferSibling1 Object) null; } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java b/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java index 3683d404925..ccd791d74bb 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java @@ -3,21 +3,21 @@ abstract class MethodDefinedInSupertype { - void test() { - // :: warning: argument.type.incompatible - expectsAinferSibling1(shouldReturnAinferSibling1()); - } + void test() { + // :: warning: argument.type.incompatible + expectsAinferSibling1(shouldReturnAinferSibling1()); + } - public void expectsAinferSibling1(@AinferSibling1 int t) {} + public void expectsAinferSibling1(@AinferSibling1 int t) {} - public abstract int shouldReturnAinferSibling1(); + public abstract int shouldReturnAinferSibling1(); - void testMultipleOverrides() { - // :: warning: argument.type.incompatible - expectsParent(shouldReturnParent()); - } + void testMultipleOverrides() { + // :: warning: argument.type.incompatible + expectsParent(shouldReturnParent()); + } - public void expectsParent(@AinferParent int t1) {} + public void expectsParent(@AinferParent int t1) {} - public abstract int shouldReturnParent(); + public abstract int shouldReturnParent(); } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java index 00d52190aa3..e6989d46120 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class MethodOverrideInSubtype extends MethodDefinedInSupertype { - @java.lang.Override - public int shouldReturnAinferSibling1() { - return getAinferSibling1(); - } + @java.lang.Override + public int shouldReturnAinferSibling1() { + return getAinferSibling1(); + } - private @AinferSibling1 int getAinferSibling1() { - return 0; - } + private @AinferSibling1 int getAinferSibling1() { + return 0; + } - @java.lang.Override - public int shouldReturnParent() { - return getAinferSibling1(); - } + @java.lang.Override + public int shouldReturnParent() { + return getAinferSibling1(); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java index 0ef69af68a2..1290f2dc51f 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java @@ -2,12 +2,12 @@ abstract class MethodOverrideInSubtype2 extends MethodDefinedInSupertype { - private @AinferSibling2 int getAinferSibling2() { - return 0; - } + private @AinferSibling2 int getAinferSibling2() { + return 0; + } - @java.lang.Override - public int shouldReturnParent() { - return getAinferSibling2(); - } + @java.lang.Override + public int shouldReturnParent() { + return getAinferSibling2(); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java b/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java index ff01c061fbb..b325425dbce 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java @@ -3,11 +3,11 @@ // TODO: Like this one, some tests must verify that it contains the expected // output after performing the whole-program inference. public class MethodParameterInferenceTest { - void foo(int i) { - i = getAinferSibling1(); // The type of i must be inferred to @AinferSibling1. - } + void foo(int i) { + i = getAinferSibling1(); // The type of i must be inferred to @AinferSibling1. + } - @AinferSibling1 int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + @AinferSibling1 int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java b/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java index 811d3ead6f0..d761c7a682c 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java @@ -4,49 +4,49 @@ public class MethodReturnTest { - static int getAinferSibling1NotAnnotated() { - return (@AinferSibling1 int) 0; + static int getAinferSibling1NotAnnotated() { + return (@AinferSibling1 int) 0; + } + + static @AinferSibling1 int getAinferSibling1() { + // :: warning: (return.type.incompatible) + return getAinferSibling1NotAnnotated(); + } + + public static boolean bool = false; + + public static int lubTest() { + if (bool) { + return (@AinferSibling1 int) 0; + } else { + return (@AinferSibling2 int) 0; } + } - static @AinferSibling1 int getAinferSibling1() { - // :: warning: (return.type.incompatible) - return getAinferSibling1NotAnnotated(); - } + public static @AinferParent int getParent() { + int x = lubTest(); + // :: warning: (return.type.incompatible) + return x; + } - public static boolean bool = false; + class InnerClass { + int field = 0; - public static int lubTest() { - if (bool) { - return (@AinferSibling1 int) 0; - } else { - return (@AinferSibling2 int) 0; - } + int getParent2() { + field = getParent(); + return getParent(); } - public static @AinferParent int getParent() { - int x = lubTest(); - // :: warning: (return.type.incompatible) - return x; + void receivesAinferSibling1(int i) { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(i); } - class InnerClass { - int field = 0; - - int getParent2() { - field = getParent(); - return getParent(); - } - - void receivesAinferSibling1(int i) { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(i); - } - - void expectsAinferSibling1(@AinferSibling1 int i) {} + void expectsAinferSibling1(@AinferSibling1 int i) {} - void test() { - @AinferSibling1 int sib = (@AinferSibling1 int) 0; - receivesAinferSibling1(sib); - } + void test() { + @AinferSibling1 int sib = (@AinferSibling1 int) 0; + receivesAinferSibling1(sib); } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java b/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java index de610342ef9..fc9e3b51b75 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java @@ -1,6 +1,7 @@ // This test ensures that annotations on different component types of multidimensional arrays // are printed correctly. +import java.util.List; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSiblingWithFields; @@ -8,259 +9,257 @@ import org.checkerframework.common.aliasing.qual.NonLeaked; import org.checkerframework.common.aliasing.qual.Unique; -import java.util.List; - public class MultiDimensionalArrays { - // two dimensional arrays - - void requiresS1S2(@AinferSibling1 int @AinferSibling2 [] x) {} - - int[] twoDimArray; - - void testField() { - // :: warning: argument.type.incompatible - requiresS1S2(twoDimArray); - } - - void useField(@AinferSibling1 int @AinferSibling2 [] x) { - twoDimArray = x; - } + // two dimensional arrays - void testParam(int[] x) { - // :: warning: argument.type.incompatible - requiresS1S2(x); - } + void requiresS1S2(@AinferSibling1 int @AinferSibling2 [] x) {} - void useParam(@AinferSibling1 int @AinferSibling2 [] x) { - testParam(x); - } + int[] twoDimArray; - int[] useReturn(@AinferSibling1 int @AinferSibling2 [] x) { - return x; - } + void testField() { + // :: warning: argument.type.incompatible + requiresS1S2(twoDimArray); + } - void testReturn() { - requiresS1S2( - // :: warning: argument.type.incompatible - useReturn( - // :: warning: argument.type.incompatible - twoDimArray)); - } + void useField(@AinferSibling1 int @AinferSibling2 [] x) { + twoDimArray = x; + } - // three dimensional arrays + void testParam(int[] x) { + // :: warning: argument.type.incompatible + requiresS1S2(x); + } - void requiresS1S2S1(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) {} + void useParam(@AinferSibling1 int @AinferSibling2 [] x) { + testParam(x); + } - int[][] threeDimArray; + int[] useReturn(@AinferSibling1 int @AinferSibling2 [] x) { + return x; + } - void testField2() { + void testReturn() { + requiresS1S2( // :: warning: argument.type.incompatible - requiresS1S2S1(threeDimArray); - } + useReturn( + // :: warning: argument.type.incompatible + twoDimArray)); + } - void useField2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { - threeDimArray = x; - } + // three dimensional arrays - void testParam2(int[][] x) { - // :: warning: argument.type.incompatible - requiresS1S2S1(x); - } + void requiresS1S2S1(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) {} - void useParam2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { - testParam2(x); - } + int[][] threeDimArray; - int[][] useReturn2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { - return x; - } + void testField2() { + // :: warning: argument.type.incompatible + requiresS1S2S1(threeDimArray); + } - void testReturn2() { - // :: warning: argument.type.incompatible - requiresS1S2S1(useReturn2(threeDimArray)); - } + void useField2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { + threeDimArray = x; + } - // three dimensional array with annotations only on two inner types + void testParam2(int[][] x) { + // :: warning: argument.type.incompatible + requiresS1S2S1(x); + } - void requiresS1S2N(@AinferSibling1 int @AinferSibling2 [][] x) {} + void useParam2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { + testParam2(x); + } - int[][] threeDimArray2; + int[][] useReturn2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { + return x; + } - void testField3() { - // :: warning: argument.type.incompatible - requiresS1S2N(threeDimArray2); - } + void testReturn2() { + // :: warning: argument.type.incompatible + requiresS1S2S1(useReturn2(threeDimArray)); + } - void useField3(@AinferSibling1 int @AinferSibling2 [][] x) { - threeDimArray2 = x; - } + // three dimensional array with annotations only on two inner types - void testParam3(int[][] x) { - // :: warning: argument.type.incompatible - requiresS1S2N(x); - } - - void useParam3(@AinferSibling1 int @AinferSibling2 [][] x) { - testParam3(x); - } + void requiresS1S2N(@AinferSibling1 int @AinferSibling2 [][] x) {} - int[][] useReturn3(@AinferSibling1 int @AinferSibling2 [][] x) { - return x; - } + int[][] threeDimArray2; - void testReturn3() { - // :: warning: argument.type.incompatible - requiresS1S2N(useReturn3(threeDimArray2)); - } + void testField3() { + // :: warning: argument.type.incompatible + requiresS1S2N(threeDimArray2); + } - // three dimensional array with annotations only on two array types, not innermost type + void useField3(@AinferSibling1 int @AinferSibling2 [][] x) { + threeDimArray2 = x; + } - void requiresS2S1(int @AinferSibling2 [] @AinferSibling1 [] x) {} + void testParam3(int[][] x) { + // :: warning: argument.type.incompatible + requiresS1S2N(x); + } - int[][] threeDimArray3; + void useParam3(@AinferSibling1 int @AinferSibling2 [][] x) { + testParam3(x); + } - void testField4() { - // :: warning: argument.type.incompatible - requiresS2S1(threeDimArray3); - } + int[][] useReturn3(@AinferSibling1 int @AinferSibling2 [][] x) { + return x; + } - void useField4(int @AinferSibling2 [] @AinferSibling1 [] x) { - threeDimArray3 = x; - } + void testReturn3() { + // :: warning: argument.type.incompatible + requiresS1S2N(useReturn3(threeDimArray2)); + } - void testParam4(int[][] x) { - // :: warning: argument.type.incompatible - requiresS2S1(x); - } + // three dimensional array with annotations only on two array types, not innermost type - void useParam4(int @AinferSibling2 [] @AinferSibling1 [] x) { - testParam4(x); - } + void requiresS2S1(int @AinferSibling2 [] @AinferSibling1 [] x) {} - int[][] useReturn4(int @AinferSibling2 [] @AinferSibling1 [] x) { - return x; - } + int[][] threeDimArray3; + + void testField4() { + // :: warning: argument.type.incompatible + requiresS2S1(threeDimArray3); + } + + void useField4(int @AinferSibling2 [] @AinferSibling1 [] x) { + threeDimArray3 = x; + } - void testReturn4() { - // :: warning: argument.type.incompatible - requiresS2S1(useReturn4(threeDimArray3)); - } + void testParam4(int[][] x) { + // :: warning: argument.type.incompatible + requiresS2S1(x); + } - // three-dimensional arrays with arguments in annotations + void useParam4(int @AinferSibling2 [] @AinferSibling1 [] x) { + testParam4(x); + } - void requiresSf1Sf2Sf3( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) {} + int[][] useReturn4(int @AinferSibling2 [] @AinferSibling1 [] x) { + return x; + } - int[][] threeDimArray4; + void testReturn4() { + // :: warning: argument.type.incompatible + requiresS2S1(useReturn4(threeDimArray3)); + } - void testField5() { - // :: warning: argument.type.incompatible - requiresSf1Sf2Sf3(threeDimArray4); - } - - void useField5( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) { - threeDimArray4 = x; - } - - void testParam5(int[][] x) { - // :: warning: argument.type.incompatible - requiresSf1Sf2Sf3(x); - } - - void useParam5( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) { - testParam5(x); - } - - int[][] useReturn5( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) { - return x; - } - - void testReturn5() { - // :: warning: argument.type.incompatible - requiresSf1Sf2Sf3(useReturn5(threeDimArray4)); - } + // three-dimensional arrays with arguments in annotations - // three dimensional array with annotations from other hierarchies that ought to be preserved - - int[][] threeDimArray5; - - void testField6() { - // :: warning: argument.type.incompatible - requiresS1S2S1(threeDimArray5); - } - - void useField6( - @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { - threeDimArray5 = x; - } - - void testParam6(int[][] x) { - // :: warning: argument.type.incompatible - requiresS1S2S1(x); - } - - void useParam6( - @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { - testParam6(x); - } - - int[][] useReturn6( - @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { - return x; - } - - void testReturn6() { - // :: warning: argument.type.incompatible - requiresS1S2S1(useReturn6(threeDimArray)); - } - - // Shenanigans with lists + arrays; commented out annotations can't be inferred by either - // jaif or stub based WPI for now due to limitations in generics inference. - - List[] arrayofListsOfStringArrays; - - void testField7() { - // :: warning: argument.type.incompatible - requiresS1S2L(arrayofListsOfStringArrays); - } - - void requiresS1S2L( - @AinferSibling1 List @AinferSibling2 [] la) {} - - void useField7( - @AinferSibling1 List @AinferSibling2 [] x) { - arrayofListsOfStringArrays = x; - } - - void testParam7(List[] x) { - // :: warning: argument.type.incompatible - requiresS1S2L(x); - } - - void useParam7( - @AinferSibling1 List @AinferSibling2 [] x) { - testParam7(x); - } - - List[] useReturn7( - @AinferSibling1 List @AinferSibling2 [] x) { - return x; - } - - void testReturn7() { - // :: warning: argument.type.incompatible - requiresS1S2L(useReturn7(arrayofListsOfStringArrays)); - } + void requiresSf1Sf2Sf3( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) {} + + int[][] threeDimArray4; + + void testField5() { + // :: warning: argument.type.incompatible + requiresSf1Sf2Sf3(threeDimArray4); + } + + void useField5( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) { + threeDimArray4 = x; + } + + void testParam5(int[][] x) { + // :: warning: argument.type.incompatible + requiresSf1Sf2Sf3(x); + } + + void useParam5( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) { + testParam5(x); + } + + int[][] useReturn5( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) { + return x; + } + + void testReturn5() { + // :: warning: argument.type.incompatible + requiresSf1Sf2Sf3(useReturn5(threeDimArray4)); + } + + // three dimensional array with annotations from other hierarchies that ought to be preserved + + int[][] threeDimArray5; + + void testField6() { + // :: warning: argument.type.incompatible + requiresS1S2S1(threeDimArray5); + } + + void useField6( + @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { + threeDimArray5 = x; + } + + void testParam6(int[][] x) { + // :: warning: argument.type.incompatible + requiresS1S2S1(x); + } + + void useParam6( + @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { + testParam6(x); + } + + int[][] useReturn6( + @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { + return x; + } + + void testReturn6() { + // :: warning: argument.type.incompatible + requiresS1S2S1(useReturn6(threeDimArray)); + } + + // Shenanigans with lists + arrays; commented out annotations can't be inferred by either + // jaif or stub based WPI for now due to limitations in generics inference. + + List[] arrayofListsOfStringArrays; + + void testField7() { + // :: warning: argument.type.incompatible + requiresS1S2L(arrayofListsOfStringArrays); + } + + void requiresS1S2L( + @AinferSibling1 List @AinferSibling2 [] la) {} + + void useField7( + @AinferSibling1 List @AinferSibling2 [] x) { + arrayofListsOfStringArrays = x; + } + + void testParam7(List[] x) { + // :: warning: argument.type.incompatible + requiresS1S2L(x); + } + + void useParam7( + @AinferSibling1 List @AinferSibling2 [] x) { + testParam7(x); + } + + List[] useReturn7( + @AinferSibling1 List @AinferSibling2 [] x) { + return x; + } + + void testReturn7() { + // :: warning: argument.type.incompatible + requiresS1S2L(useReturn7(arrayofListsOfStringArrays)); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java b/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java index 59fcc6cfd81..3c7a255935e 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class MultidimensionalAnnotatedArray { - boolean[][] field = getArray(); + boolean[][] field = getArray(); - public boolean[] @AinferSibling1 [] getArray() { - return null; - } + public boolean[] @AinferSibling1 [] getArray() { + return null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java b/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java index 8b2935885cb..5f60e43981b 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java +++ b/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class NamedInnerClassInAnonymous { - void test() { - Object o = - new NamedInnerClassInAnonymous() { - class NamedInner { - // The stub parser cannot parse inner classes, so stub-based WPI should - // not attempt to print a stub file for this. - public int myAinferSibling1() { - return ((@AinferSibling1 int) 0); - } - } - }; - } + void test() { + Object o = + new NamedInnerClassInAnonymous() { + class NamedInner { + // The stub parser cannot parse inner classes, so stub-based WPI should + // not attempt to print a stub file for this. + public int myAinferSibling1() { + return ((@AinferSibling1 int) 0); + } + } + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java b/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java index 14d4870e2e5..fcfbb134d7d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java @@ -12,7 +12,7 @@ @Target(ElementType.FIELD) public @interface OptionGroup { - String value(); + String value(); - boolean unpublicized() default false; + boolean unpublicized() default false; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java b/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java index 916acb526af..2388c683c13 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java @@ -5,38 +5,38 @@ public class OtherAnnotations { - void requireAinferSibling1(@AinferSibling1 int a) {} + void requireAinferSibling1(@AinferSibling1 int a) {} - @Unique int x; + @Unique int x; - void assignX(@AinferSibling1 int y) { - x = y; - } + void assignX(@AinferSibling1 int y) { + x = y; + } - void useX() { - // :: warning: argument.type.incompatible - requireAinferSibling1(x); - } + void useX() { + // :: warning: argument.type.incompatible + requireAinferSibling1(x); + } - void methodWithAnnotatedParam(@Unique int z) { - // :: warning: argument.type.incompatible - requireAinferSibling1(z); - } + void methodWithAnnotatedParam(@Unique int z) { + // :: warning: argument.type.incompatible + requireAinferSibling1(z); + } - void useMethodWithAnnotatedParam(@AinferSibling1 int w) { - methodWithAnnotatedParam(w); - } + void useMethodWithAnnotatedParam(@AinferSibling1 int w) { + methodWithAnnotatedParam(w); + } - @AinferSibling1 int getAinferSibling1() { - return 5; - } + @AinferSibling1 int getAinferSibling1() { + return 5; + } - @Unique int getIntVal5() { - return getAinferSibling1(); - } + @Unique int getIntVal5() { + return getAinferSibling1(); + } - void useGetIntVal5() { - // :: warning: argument.type.incompatible - requireAinferSibling1(getIntVal5()); - } + void useGetIntVal5() { + // :: warning: argument.type.incompatible + requireAinferSibling1(getIntVal5()); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java b/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java index d1142070bb9..05f14ad67f0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class OuterClassWithTypeParam { - public class InnerClass { - Object o = (@AinferSibling1 Object) null; - } + public class InnerClass { + Object o = (@AinferSibling1 Object) null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java b/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java index f77316e4b52..c37872f75f5 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java @@ -4,17 +4,17 @@ public class OverloadedMethodsTest { - String f; + String f; - String m1() { - return this.f; - } + String m1() { + return this.f; + } - String m1(String x) { - return getAinferSibling1(); - } + String m1(String x) { + return getAinferSibling1(); + } - @AinferSibling1 String getAinferSibling1() { - return null; - } + @AinferSibling1 String getAinferSibling1() { + return null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java index cd99468cf98..52a45306315 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java @@ -2,57 +2,56 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class OverriddenMethodsTest { - static class OverriddenMethodsTestParent { - public void foo(@AinferSibling1 Object obj, @AinferSibling2 Object obj2) {} + static class OverriddenMethodsTestParent { + public void foo(@AinferSibling1 Object obj, @AinferSibling2 Object obj2) {} - public void bar( - @AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} + public void bar(@AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} - public void barz( - @AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} + public void barz( + @AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} - public void qux(Object obj1, Object obj2) { - // :: warning: (argument.type.incompatible) - foo(obj1, obj2); - } + public void qux(Object obj1, Object obj2) { + // :: warning: (argument.type.incompatible) + foo(obj1, obj2); + } + + public void thud(Object obj1, Object obj2) { + // :: warning: (argument.type.incompatible) + foo(obj1, obj2); + } + } + + class OverriddenMethodsTestChild extends OverriddenMethodsTestParent { + @Override + public void foo(Object obj, Object obj2) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 Object o = obj; + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o2 = obj2; + } + + @Override + public void bar(Object obj) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 OverriddenMethodsTestChild child = this; + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = obj; + } + + @SuppressWarnings("all") + @Override + public void barz(Object obj) {} - public void thud(Object obj1, Object obj2) { - // :: warning: (argument.type.incompatible) - foo(obj1, obj2); - } + public void callbarz(Object obj) { + // If the @SuppressWarnings("all") on the overridden version of barz above is not + // respected, and the annotations on the receiver and parameter of barz are + // inferred, then the following call to barz will result in a method.invocation + // and an argument type checking errors. + barz(obj); } - class OverriddenMethodsTestChild extends OverriddenMethodsTestParent { - @Override - public void foo(Object obj, Object obj2) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 Object o = obj; - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o2 = obj2; - } - - @Override - public void bar(Object obj) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 OverriddenMethodsTestChild child = this; - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = obj; - } - - @SuppressWarnings("all") - @Override - public void barz(Object obj) {} - - public void callbarz(Object obj) { - // If the @SuppressWarnings("all") on the overridden version of barz above is not - // respected, and the annotations on the receiver and parameter of barz are - // inferred, then the following call to barz will result in a method.invocation - // and an argument type checking errors. - barz(obj); - } - - public void callqux(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { - qux(obj1, obj2); - } + public void callqux(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { + qux(obj1, obj2); } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java index f8265f05529..919fcadbf1d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java @@ -2,8 +2,8 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class OverriddenMethodsTestChildInAnotherCompilationUnit - extends OverriddenMethodsTest.OverriddenMethodsTestParent { - public void callthud(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { - thud(obj1, obj2); - } + extends OverriddenMethodsTest.OverriddenMethodsTestParent { + public void callthud(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { + thud(obj1, obj2); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java b/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java index 1f4fbd51f41..527118ed04b 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java @@ -6,43 +6,43 @@ public class OverrideIncompatiblePurity { - interface MyInterface { - // WPI should not infer @Pure for this unless all implementations are pure. - void method(); - } + interface MyInterface { + // WPI should not infer @Pure for this unless all implementations are pure. + void method(); + } - class MyImplementation implements MyInterface { + class MyImplementation implements MyInterface { - int field; + int field; - @java.lang.Override - public void method() { - // Side effect! - field = 5; - } + @java.lang.Override + public void method() { + // Side effect! + field = 5; } + } - class Foo { + class Foo { - // This implementation is pure, but an overriding implementation in Bar is not. - String getA(int x) { - return "A"; - } + // This implementation is pure, but an overriding implementation in Bar is not. + String getA(int x) { + return "A"; } + } - class Bar extends Foo { + class Bar extends Foo { - String y; + String y; - // This implementation is neither deterministic nor side-effect free. - @java.lang.Override - String getA(int x) { - if (new Random().nextInt(5) > x) { - return "B"; - } else { - y = "C"; - return y; - } - } + // This implementation is neither deterministic nor side-effect free. + @java.lang.Override + String getA(int x) { + if (new Random().nextInt(5) > x) { + return "B"; + } else { + y = "C"; + return y; + } } + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java b/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java index 8aeed48d668..0143c443a11 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java @@ -3,21 +3,21 @@ public class ParameterInferenceTest { - void test1() { - @AinferParent int parent = (@AinferParent int) 0; - expectsParentNoSignature(parent); - } + void test1() { + @AinferParent int parent = (@AinferParent int) 0; + expectsParentNoSignature(parent); + } - void expectsParentNoSignature(int t) { - // :: warning: (assignment.type.incompatible) - @AinferParent int parent = t; - } + void expectsParentNoSignature(int t) { + // :: warning: (assignment.type.incompatible) + @AinferParent int parent = t; + } - void test2() { - @AinferTop int top = (@AinferTop int) 0; - // :: warning: (argument.type.incompatible) - expectsAinferTopNoSignature(top); - } + void test2() { + @AinferTop int top = (@AinferTop int) 0; + // :: warning: (argument.type.incompatible) + expectsAinferTopNoSignature(top); + } - void expectsAinferTopNoSignature(int t) {} + void expectsAinferTopNoSignature(int t) {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Planet.java b/checker/tests/ainfer-testchecker/non-annotated/Planet.java index cacf7f10f50..aadb5a3d6b3 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Planet.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Planet.java @@ -3,54 +3,54 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; @SuppressWarnings( - "value" // Do not generate Value Checker annotations, because IndexFileParser cannot handle + "value" // Do not generate Value Checker annotations, because IndexFileParser cannot handle // scientific notation. ) public enum Planet { - MERCURY(3.303e+23, 2.4397e6), - VENUS(4.869e+24, 6.0518e6), - EARTH(5.976e+24, 6.37814e6), - MARS(6.421e+23, 3.3972e6), - JUPITER(1.9e+27, 7.1492e7), - SATURN(5.688e+26, 6.0268e7), - URANUS(8.686e+25, 2.5559e7), - NEPTUNE(1.024e+26, 2.4746e7); - - public int foo; - - private final double mass; // in kilograms - private final double radius; // in meters - - Planet(double mass, double radius) { - this.mass = mass; - this.radius = radius; - } - - private double mass() { - return mass; - } - - private double radius() { - return radius; - } - - // universal gravitational constant (m3 kg-1 s-2) - public static final double G = 6.67300E-11; - - double surfaceGravity() { - return G * mass / (radius * radius); - } - - double surfaceWeight(double otherMass) { - return otherMass * surfaceGravity(); - } - - void test(@AinferSibling1 int x) { - foo = x; - } - - void test2() { - // :: warning: argument.type.incompatible - test(foo); - } + MERCURY(3.303e+23, 2.4397e6), + VENUS(4.869e+24, 6.0518e6), + EARTH(5.976e+24, 6.37814e6), + MARS(6.421e+23, 3.3972e6), + JUPITER(1.9e+27, 7.1492e7), + SATURN(5.688e+26, 6.0268e7), + URANUS(8.686e+25, 2.5559e7), + NEPTUNE(1.024e+26, 2.4746e7); + + public int foo; + + private final double mass; // in kilograms + private final double radius; // in meters + + Planet(double mass, double radius) { + this.mass = mass; + this.radius = radius; + } + + private double mass() { + return mass; + } + + private double radius() { + return radius; + } + + // universal gravitational constant (m3 kg-1 s-2) + public static final double G = 6.67300E-11; + + double surfaceGravity() { + return G * mass / (radius * radius); + } + + double surfaceWeight(double otherMass) { + return otherMass * surfaceGravity(); + } + + void test(@AinferSibling1 int x) { + foo = x; + } + + void test2() { + // :: warning: argument.type.incompatible + test(foo); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java b/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java index bb221671bc5..b6db26644d9 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java @@ -5,63 +5,63 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; public class PublicFieldTest { - public static int field1; // parent - public static int field2; // sib2 + public static int field1; // parent + public static int field2; // sib2 - public PublicFieldTest() { - field1 = getAinferSibling1(); - } - - void testPublicInference() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(field2); - // :: warning: (argument.type.incompatible) - expectsParent(field1); - // :: warning: (argument.type.incompatible) - expectsParent(field2); - } + public PublicFieldTest() { + field1 = getAinferSibling1(); + } - void expectsBottom(@AinferBottom int t) {} + void testPublicInference() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(field2); + // :: warning: (argument.type.incompatible) + expectsParent(field1); + // :: warning: (argument.type.incompatible) + expectsParent(field2); + } - void expectsAinferSibling1(@AinferSibling1 int t) {} + void expectsBottom(@AinferBottom int t) {} - void expectsAinferSibling2(@AinferSibling2 int t) {} + void expectsAinferSibling1(@AinferSibling1 int t) {} - void expectsAinferTop(@AinferTop int t) {} + void expectsAinferSibling2(@AinferSibling2 int t) {} - void expectsParent(@AinferParent int t) {} + void expectsAinferTop(@AinferTop int t) {} - @AinferSibling1 int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + void expectsParent(@AinferParent int t) {} - class AnotherClass { + @AinferSibling1 int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } - int innerField; + class AnotherClass { - public AnotherClass() { - PublicFieldTest.field1 = getAinferSibling2(); - PublicFieldTest.field2 = getAinferSibling2(); - innerField = getAinferSibling2(); - } + int innerField; - void innerFieldTest() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(innerField); - } + public AnotherClass() { + PublicFieldTest.field1 = getAinferSibling2(); + PublicFieldTest.field2 = getAinferSibling2(); + innerField = getAinferSibling2(); + } - @AinferBottom int getBottom() { - return (@AinferBottom int) 0; - } + void innerFieldTest() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(innerField); + } - @AinferTop int getAinferTop() { - return (@AinferTop int) 0; - } + @AinferBottom int getBottom() { + return (@AinferBottom int) 0; + } - @AinferSibling2 int getAinferSibling2() { - return (@AinferSibling2 int) 0; - } + @AinferTop int getAinferTop() { + return (@AinferTop int) 0; + } - void expectsAinferSibling2(@AinferSibling2 int t) {} + @AinferSibling2 int getAinferSibling2() { + return (@AinferSibling2 int) 0; } + + void expectsAinferSibling2(@AinferSibling2 int t) {} + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Purity.java b/checker/tests/ainfer-testchecker/non-annotated/Purity.java index 4d6b6851f8a..f2193ce6d16 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Purity.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Purity.java @@ -6,24 +6,24 @@ import org.checkerframework.dataflow.qual.*; interface PureFunc { - @Pure - String doNothing(); + @Pure + String doNothing(); } class TestPure1 { - static String myMethod() { - return ""; - } + static String myMethod() { + return ""; + } - @Pure - static String myPureMethod() { - return ""; - } + @Pure + static String myPureMethod() { + return ""; + } - void context() { - PureFunc f1 = TestPure1::myPureMethod; - // :: warning: (purity.methodref) - PureFunc f2 = TestPure1::myMethod; - } + void context() { + PureFunc f1 = TestPure1::myPureMethod; + // :: warning: (purity.methodref) + PureFunc f2 = TestPure1::myMethod; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java b/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java index a4700f65e89..8fd44468de9 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java @@ -6,49 +6,49 @@ class RequiresQualifierTest { - @AinferTop int field1; - @AinferTop int field2; - - @AinferTop int top; - @AinferParent int parent; - @AinferSibling1 int sibling1; - @AinferSibling2 int sibling2; - @AinferBottom int bottom; - - void field1IsParent() { - // :: warning: (assignment.type.incompatible) - @AinferParent int x = field1; - } - - void field1IsAinferSibling2() { - // :: warning: (assignment.type.incompatible) - @AinferSibling2 int x = field1; - } - - void parentIsAinferSibling1() { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 int x = parent; - } - - void noRequirements() {} - - void client2(@AinferParent int p) { - field1 = p; - field1IsParent(); - } - - void client1() { - noRequirements(); - - field1 = parent; - field1IsParent(); - - field1 = sibling2; - field1IsAinferSibling2(); - field1 = bottom; - field1IsAinferSibling2(); - - parent = sibling1; - parentIsAinferSibling1(); - } + @AinferTop int field1; + @AinferTop int field2; + + @AinferTop int top; + @AinferParent int parent; + @AinferSibling1 int sibling1; + @AinferSibling2 int sibling2; + @AinferBottom int bottom; + + void field1IsParent() { + // :: warning: (assignment.type.incompatible) + @AinferParent int x = field1; + } + + void field1IsAinferSibling2() { + // :: warning: (assignment.type.incompatible) + @AinferSibling2 int x = field1; + } + + void parentIsAinferSibling1() { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 int x = parent; + } + + void noRequirements() {} + + void client2(@AinferParent int p) { + field1 = p; + field1IsParent(); + } + + void client1() { + noRequirements(); + + field1 = parent; + field1IsParent(); + + field1 = sibling2; + field1IsAinferSibling2(); + field1 = bottom; + field1IsAinferSibling2(); + + parent = sibling1; + parentIsAinferSibling1(); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java b/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java index c5e55eaa80b..87425884a85 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java @@ -2,31 +2,31 @@ public class StringConcatenationTest { - private String options_str; - private String options_str2; - private String options_str3; + private String options_str; + private String options_str2; + private String options_str3; - void foo() { - options_str = getAinferSibling1(); + void foo() { + options_str = getAinferSibling1(); - // Addition lubs the results, so these have no effect on the (default) type of the fields. - // Also, the following two lines should behave identically. - options_str2 += getAinferSibling1(); - options_str3 = options_str3 + getAinferSibling1(); - } + // Addition lubs the results, so these have no effect on the (default) type of the fields. + // Also, the following two lines should behave identically. + options_str2 += getAinferSibling1(); + options_str3 = options_str3 + getAinferSibling1(); + } - void test() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(options_str); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(options_str2); - expectsAinferSibling1(options_str3); - } + void test() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(options_str); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(options_str2); + expectsAinferSibling1(options_str3); + } - void expectsAinferSibling1(@AinferSibling1 String t) {} + void expectsAinferSibling1(@AinferSibling1 String t) {} - @SuppressWarnings("cast.unsafe") - @AinferSibling1 String getAinferSibling1() { - return (@AinferSibling1 String) " "; - } + @SuppressWarnings("cast.unsafe") + @AinferSibling1 String getAinferSibling1() { + return (@AinferSibling1 String) " "; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java b/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java index a46b67df0c0..dc0b1a4fa24 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java @@ -1,8 +1,8 @@ // test case for https://github.com/typetools/checker-framework/issues/3442 public class Tempvars { - static { - int i = 0; - i++; - } + static { + int i = 0; + i++; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java index b2991170f48..0e26718817a 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java @@ -6,8 +6,8 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class TreatAsSibling1InferenceTest { - public void test(Object iShouldBeTreatedAsSibling1) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 Object x = iShouldBeTreatedAsSibling1; - } + public void test(Object iShouldBeTreatedAsSibling1) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 Object x = iShouldBeTreatedAsSibling1; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java index eef11cac87e..e8f84538794 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java @@ -5,7 +5,7 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTreatAsSibling1; public class TreatAsSibling1Test { - public void test(@AinferTreatAsSibling1 Object y) { - @AinferSibling1 Object x = y; - } + public void test(@AinferTreatAsSibling1 Object y) { + @AinferSibling1 Object x = y; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java b/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java index 84604f2b7da..aaec92546dc 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java @@ -6,20 +6,20 @@ public class TwoMethodsSameName { - void test(int x, int y) { - // :: warning: assignment.type.incompatible - @AinferSibling1 int x1 = x; - // :: warning: assignment.type.incompatible - @AinferSibling2 int y1 = y; - } + void test(int x, int y) { + // :: warning: assignment.type.incompatible + @AinferSibling1 int x1 = x; + // :: warning: assignment.type.incompatible + @AinferSibling2 int y1 = y; + } - void test(int z) { - // :: warning: assignment.type.incompatible - @AinferSibling2 int z1 = z; - } + void test(int z) { + // :: warning: assignment.type.incompatible + @AinferSibling2 int z1 = z; + } - void uses(@AinferSibling1 int a, @AinferSibling2 int b) { - test(a, b); - test(b); - } + void uses(@AinferSibling1 int a, @AinferSibling2 int b) { + test(a, b); + test(b); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java index 041d16d9fc8..dcdd80de065 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java @@ -4,30 +4,29 @@ public class TypeVariablesTest { - // This method's parameter type should not be updated by the whole-program inference. - // Even though there is only one call to foo with argument of type @AinferBottom, - // the method has in its signature that the parameter is a subtype of @AinferParent, - // therefore no annotation should be added. - public static - TypeVariablesTest foo(A a, B b) { - return null; - } + // This method's parameter type should not be updated by the whole-program inference. + // Even though there is only one call to foo with argument of type @AinferBottom, + // the method has in its signature that the parameter is a subtype of @AinferParent, + // therefore no annotation should be added. + public static + TypeVariablesTest foo(A a, B b) { + return null; + } - public static void typeVarWithTypeVarUB( - A a, B b) {} + public static void typeVarWithTypeVarUB(A a, B b) {} - void test1() { - @SuppressWarnings("cast.unsafe") - @AinferParent String s = (@AinferParent String) ""; - foo(getAinferSibling1(), getAinferSibling2()); - typeVarWithTypeVarUB(getAinferSibling1(), getAinferSibling2()); - } + void test1() { + @SuppressWarnings("cast.unsafe") + @AinferParent String s = (@AinferParent String) ""; + foo(getAinferSibling1(), getAinferSibling2()); + typeVarWithTypeVarUB(getAinferSibling1(), getAinferSibling2()); + } - static @AinferSibling1 int getAinferSibling1() { - return 0; - } + static @AinferSibling1 int getAinferSibling1() { + return 0; + } - static @AinferSibling2 int getAinferSibling2() { - return 0; - } + static @AinferSibling2 int getAinferSibling2() { + return 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java index 4e814159d58..b9e1c00f149 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java @@ -3,9 +3,9 @@ public class TypeVariablesTest2 { - Map map = new HashMap<>(); + Map map = new HashMap<>(); - public V getValue(K key) { - return map.get(key); - } + public V getValue(K key) { + return map.get(key); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java index 3705e4fac8d..233acefde83 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java @@ -2,22 +2,22 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class TypeVariablesTest3<@AinferSibling1 T extends @AinferSibling1 Object> { - public @AinferSibling2 T sibling2; - public @AinferSibling1 T sibling1; + public @AinferSibling2 T sibling2; + public @AinferSibling1 T sibling1; - public T tField; + public T tField; - void foo(T param) { - // :: warning: (assignment.type.incompatible) - param = sibling2; - } + void foo(T param) { + // :: warning: (assignment.type.incompatible) + param = sibling2; + } - void baz(T param) { - param = sibling1; - } + void baz(T param) { + param = sibling1; + } - void bar(@AinferSibling2 T param) { - // :: warning: (assignment.type.incompatible) - tField = param; - } + void bar(@AinferSibling2 T param) { + // :: warning: (assignment.type.incompatible) + tField = param; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java b/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java index c387ad33643..13985bfd6fb 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java +++ b/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java @@ -5,10 +5,10 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class UsesAnnotationAsClass { - public static String test(@AinferSibling2 AinferSibling1 annotation) { - String value = annotation.value(); - // goal of this is to trigger inference for AinferSibling1's definition. - String anotherValue = annotation.anotherValue(); - return value + anotherValue; - } + public static String test(@AinferSibling2 AinferSibling1 annotation) { + String value = annotation.value(); + // goal of this is to trigger inference for AinferSibling1's definition. + String anotherValue = annotation.anotherValue(); + return value + anotherValue; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java b/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java index b32f587f931..4f9925e30e3 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java +++ b/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java @@ -3,35 +3,35 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; public class UsesAnonymous { - void method() { - Anonymous a = - new Anonymous() { - int innerField; + void method() { + Anonymous a = + new Anonymous() { + int innerField; - public void method2() { - Anonymous.field1 = getAinferSibling2(); - Anonymous.field2 = getAinferSibling2(); - innerField = getAinferSibling2(); - } + public void method2() { + Anonymous.field1 = getAinferSibling2(); + Anonymous.field2 = getAinferSibling2(); + innerField = getAinferSibling2(); + } - void innerFieldTest() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(innerField); - } + void innerFieldTest() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(innerField); + } - @AinferBottom int getBottom() { - return (@AinferBottom int) 0; - } + @AinferBottom int getBottom() { + return (@AinferBottom int) 0; + } - @AinferTop int getAinferTop() { - return (@AinferTop int) 0; - } + @AinferTop int getAinferTop() { + return (@AinferTop int) 0; + } - @AinferSibling2 int getAinferSibling2() { - return (@AinferSibling2 int) 0; - } + @AinferSibling2 int getAinferSibling2() { + return (@AinferSibling2 int) 0; + } - void expectsAinferSibling2(@AinferSibling2 int t) {} - }; - } + void expectsAinferSibling2(@AinferSibling2 int t) {} + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java b/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java index 0b8d48162ba..1adb9d6e2d0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java @@ -7,20 +7,20 @@ public class ValueCheck { - // return value should be @AinferSibling1 @IntVal(5) int - int getAinferSibling1withValue5() { - return ((@AinferSibling1 int) 5); - } + // return value should be @AinferSibling1 @IntVal(5) int + int getAinferSibling1withValue5() { + return ((@AinferSibling1 int) 5); + } - void requireAinferSibling1(@AinferSibling1 int x) {} + void requireAinferSibling1(@AinferSibling1 int x) {} - void requireIntVal5(@IntVal(5) int x) {} + void requireIntVal5(@IntVal(5) int x) {} - void test() { - int x = getAinferSibling1withValue5(); - // :: warning: argument.type.incompatible - requireAinferSibling1(x); - // :: warning: argument.type.incompatible - requireIntVal5(x); - } + void test() { + int x = getAinferSibling1withValue5(); + // :: warning: argument.type.incompatible + requireAinferSibling1(x); + // :: warning: argument.type.incompatible + requireIntVal5(x); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java b/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java index a16423d50f0..2ae50ad8b83 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java @@ -3,31 +3,31 @@ public class VarargsTest { - static void m1Varargs(Object... args) {} + static void m1Varargs(Object... args) {} - static void m1ArrArgs(Object[] args) {} + static void m1ArrArgs(Object[] args) {} - static void m2Varargs(Object... args) {} + static void m2Varargs(Object... args) {} - static void m2ArrArgs(Object[] args) {} + static void m2ArrArgs(Object[] args) {} - static void m3Varargs(Object... args) {} + static void m3Varargs(Object... args) {} - static void m3ArrArgs(Object[] args) {} + static void m3ArrArgs(Object[] args) {} - static @AinferSibling1 Object @AinferParent [] p_s1_array; - static @AinferSibling1 Object @AinferSibling1 [] s1_s1_array; + static @AinferSibling1 Object @AinferParent [] p_s1_array; + static @AinferSibling1 Object @AinferSibling1 [] s1_s1_array; - static void client() { - m1Varargs(s1_s1_array); - m1ArrArgs(s1_s1_array); + static void client() { + m1Varargs(s1_s1_array); + m1ArrArgs(s1_s1_array); - m2Varargs(p_s1_array); - m2ArrArgs(p_s1_array); + m2Varargs(p_s1_array); + m2ArrArgs(p_s1_array); - m3Varargs(s1_s1_array); - m3ArrArgs(s1_s1_array); - m3Varargs(p_s1_array); - m3ArrArgs(p_s1_array); - } + m3Varargs(s1_s1_array); + m3ArrArgs(s1_s1_array); + m3Varargs(p_s1_array); + m3ArrArgs(p_s1_array); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java b/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java index 122dc6e3af0..c3245e1e39d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java +++ b/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java @@ -6,13 +6,13 @@ import java.util.stream.Collectors; public class WildcardReturn { - public Set getCredentialIdsForUsername(String username) { - return getRegistrationsByUsername(username).stream() - .map(registration -> registration.toString()) - .collect(Collectors.toSet()); - } + public Set getCredentialIdsForUsername(String username) { + return getRegistrationsByUsername(username).stream() + .map(registration -> registration.toString()) + .collect(Collectors.toSet()); + } - public Collection getRegistrationsByUsername(String username) { - return null; - } + public Collection getRegistrationsByUsername(String username) { + return null; + } } diff --git a/checker/tests/calledmethods-autovalue/Animal.java b/checker/tests/calledmethods-autovalue/Animal.java index 424bcc82147..a52ebdd3445 100644 --- a/checker/tests/calledmethods-autovalue/Animal.java +++ b/checker/tests/calledmethods-autovalue/Animal.java @@ -1,88 +1,86 @@ import com.google.auto.value.AutoValue; - +import java.util.Optional; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; -import java.util.Optional; - /** * Adapted from the standard AutoValue example code: * https://github.com/google/auto/blob/master/value/userguide/builders.md */ @AutoValue abstract class Animal { - abstract String name(); + abstract String name(); - abstract @Nullable String habitat(); + abstract @Nullable String habitat(); - abstract int numberOfLegs(); + abstract int numberOfLegs(); - // does not need to be explicitly set - abstract Optional extra(); + // does not need to be explicitly set + abstract Optional extra(); - public String getStr() { - return "str"; - } + public String getStr() { + return "str"; + } - public abstract Builder toBuilder(); + public abstract Builder toBuilder(); - static Builder builder() { - return new AutoValue_Animal.Builder(); - } + static Builder builder() { + return new AutoValue_Animal.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract Builder setNumberOfLegs(int value); + abstract Builder setNumberOfLegs(int value); - abstract Builder setHabitat(String value); + abstract Builder setHabitat(String value); - abstract Builder setExtra(String value); + abstract Builder setExtra(String value); - abstract Animal build(); - } + abstract Animal build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.build(); + } - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHabitat("jungle"); - b.build(); - } + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHabitat("jungle"); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().setName("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().setName("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).build(); - } + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).build(); + } - public static void buildWithToBuilder() { - Animal a1 = builder().setName("Jim").setNumberOfLegs(7).build(); - a1.toBuilder().build(); - } + public static void buildWithToBuilder() { + Animal a1 = builder().setName("Jim").setNumberOfLegs(7).build(); + a1.toBuilder().build(); + } - public static void buildSomethingRightFluentWithLocal() { - Builder b = builder(); - b.setName("Jim").setNumberOfLegs(7); - b.build(); - } + public static void buildSomethingRightFluentWithLocal() { + Builder b = builder(); + b.setName("Jim").setNumberOfLegs(7); + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/AnimalNoSet.java b/checker/tests/calledmethods-autovalue/AnimalNoSet.java index 2e011578f77..6276ef97ca6 100644 --- a/checker/tests/calledmethods-autovalue/AnimalNoSet.java +++ b/checker/tests/calledmethods-autovalue/AnimalNoSet.java @@ -1,5 +1,4 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @@ -9,60 +8,60 @@ */ @AutoValue abstract class AnimalNoSet { - abstract String name(); + abstract String name(); - abstract @Nullable String habitat(); + abstract @Nullable String habitat(); - abstract int numberOfLegs(); + abstract int numberOfLegs(); - public String getStr() { - return "str"; - } + public String getStr() { + return "str"; + } - static Builder builder() { - return new AutoValue_AnimalNoSet.Builder(); - } + static Builder builder() { + return new AutoValue_AnimalNoSet.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder name(String value); + abstract Builder name(String value); - abstract Builder numberOfLegs(int value); + abstract Builder numberOfLegs(int value); - abstract Builder habitat(String value); + abstract Builder habitat(String value); - abstract AnimalNoSet build(); - } + abstract AnimalNoSet build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.name("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.name("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.name("Frank"); - b.numberOfLegs(4); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.name("Frank"); + b.numberOfLegs(4); + b.build(); + } - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.name("Frank"); - b.numberOfLegs(4); - b.habitat("jungle"); - b.build(); - } + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.name("Frank"); + b.numberOfLegs(4); + b.habitat("jungle"); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().name("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().name("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().name("Jim").numberOfLegs(7).build(); - } + public static void buildSomethingRightFluent() { + builder().name("Jim").numberOfLegs(7).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/BuilderGetter.java b/checker/tests/calledmethods-autovalue/BuilderGetter.java index 15b58d870fb..d4fdd97848a 100644 --- a/checker/tests/calledmethods-autovalue/BuilderGetter.java +++ b/checker/tests/calledmethods-autovalue/BuilderGetter.java @@ -1,37 +1,36 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class BuilderGetter { - public abstract String name(); + public abstract String name(); - static Builder builder() { - return new AutoValue_BuilderGetter.Builder(); - } + static Builder builder() { + return new AutoValue_BuilderGetter.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String name); + abstract Builder setName(String name); - abstract String name(); + abstract String name(); - abstract BuilderGetter build(); - } + abstract BuilderGetter build(); + } - static void correct() { - Builder b = builder(); - b.setName("Phil"); - b.build(); - } + static void correct() { + Builder b = builder(); + b.setName("Phil"); + b.build(); + } - static void wrong() { - Builder b = builder(); - b.name(); - // :: error: finalizer.invocation.invalid - b.build(); - } + static void wrong() { + Builder b = builder(); + b.name(); + // :: error: finalizer.invocation.invalid + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/CallWithinBuilder.java b/checker/tests/calledmethods-autovalue/CallWithinBuilder.java index 0760d00789d..6cc3ca4c860 100644 --- a/checker/tests/calledmethods-autovalue/CallWithinBuilder.java +++ b/checker/tests/calledmethods-autovalue/CallWithinBuilder.java @@ -1,37 +1,35 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; - +import java.util.Collection; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; -import java.util.Collection; - @AutoValue abstract class CallWithinBuilder { - public abstract ImmutableList names(); + public abstract ImmutableList names(); - static Builder builder() { - return new AutoValue_CallWithinBuilder.Builder(); - } - - @AutoValue.Builder - abstract static class Builder { + static Builder builder() { + return new AutoValue_CallWithinBuilder.Builder(); + } - abstract ImmutableList.Builder namesBuilder(); + @AutoValue.Builder + abstract static class Builder { - public Builder addName(String name) { - namesBuilder().add(name); - return this; - } + abstract ImmutableList.Builder namesBuilder(); - public Builder addNames(Collection names) { - for (String n : names) { - addName(n); - } - return this; - } + public Builder addName(String name) { + namesBuilder().add(name); + return this; + } - abstract CallWithinBuilder build(); + public Builder addNames(Collection names) { + for (String n : names) { + addName(n); + } + return this; } + + abstract CallWithinBuilder build(); + } } diff --git a/checker/tests/calledmethods-autovalue/FooParcelable.java b/checker/tests/calledmethods-autovalue/FooParcelable.java index 18b7453af74..a43d573a56a 100644 --- a/checker/tests/calledmethods-autovalue/FooParcelable.java +++ b/checker/tests/calledmethods-autovalue/FooParcelable.java @@ -1,6 +1,5 @@ -import com.google.auto.value.AutoValue; - import android.os.Parcelable; +import com.google.auto.value.AutoValue; /** * Test for support of AutoValue Parcel extension. This test currently passes, but only because we @@ -9,29 +8,29 @@ */ @AutoValue abstract class FooParcelable implements Parcelable { - abstract String name(); + abstract String name(); - static Builder builder() { - return new AutoValue_FooParcelable.Builder(); - } + static Builder builder() { + return new AutoValue_FooParcelable.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract FooParcelable build(); - } + abstract FooParcelable build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GetAndIs.java b/checker/tests/calledmethods-autovalue/GetAndIs.java index f52a4ce9b8b..6eeca1f536e 100644 --- a/checker/tests/calledmethods-autovalue/GetAndIs.java +++ b/checker/tests/calledmethods-autovalue/GetAndIs.java @@ -1,48 +1,47 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class GetAndIs { - abstract String get(); + abstract String get(); - abstract boolean is(); + abstract boolean is(); - static Builder builder() { - return new AutoValue_GetAndIs.Builder(); - } + static Builder builder() { + return new AutoValue_GetAndIs.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setGet(String value); + abstract Builder setGet(String value); - abstract Builder setIs(boolean value); + abstract Builder setIs(boolean value); - abstract GetAndIs build(); - } + abstract GetAndIs build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setGet("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setGet("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setGet("Frank"); - b.setIs(false); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setGet("Frank"); + b.setIs(false); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().setGet("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().setGet("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setGet("Jim").setIs(true).build(); - } + public static void buildSomethingRightFluent() { + builder().setGet("Jim").setIs(true).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GetAnimal.java b/checker/tests/calledmethods-autovalue/GetAnimal.java index 4b83cc4059e..c7a3cbaa881 100644 --- a/checker/tests/calledmethods-autovalue/GetAnimal.java +++ b/checker/tests/calledmethods-autovalue/GetAnimal.java @@ -1,5 +1,4 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @@ -9,62 +8,62 @@ */ @AutoValue abstract class GetAnimal { - abstract String getName(); + abstract String getName(); - abstract @Nullable String getHabitat(); + abstract @Nullable String getHabitat(); - abstract int getNumberOfLegs(); + abstract int getNumberOfLegs(); - abstract boolean isHasArms(); + abstract boolean isHasArms(); - static Builder builder() { - return new AutoValue_GetAnimal.Builder(); - } + static Builder builder() { + return new AutoValue_GetAnimal.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract Builder setNumberOfLegs(int value); + abstract Builder setNumberOfLegs(int value); - abstract Builder setHabitat(String value); + abstract Builder setHabitat(String value); - abstract Builder setHasArms(boolean b); + abstract Builder setHasArms(boolean b); - abstract GetAnimal build(); - } + abstract GetAnimal build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHasArms(true); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHasArms(true); + b.build(); + } - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHabitat("jungle"); - b.setHasArms(true); - b.build(); - } + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHabitat("jungle"); + b.setHasArms(true); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().setName("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().setName("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).setHasArms(false).build(); - } + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).setHasArms(false).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GuavaImmutable.java b/checker/tests/calledmethods-autovalue/GuavaImmutable.java index 1db3f3b23ba..b66cba4ec10 100644 --- a/checker/tests/calledmethods-autovalue/GuavaImmutable.java +++ b/checker/tests/calledmethods-autovalue/GuavaImmutable.java @@ -1,32 +1,31 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class GuavaImmutable { - public abstract ImmutableList names(); + public abstract ImmutableList names(); - static Builder builder() { - return new AutoValue_GuavaImmutable.Builder(); - } + static Builder builder() { + return new AutoValue_GuavaImmutable.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder names(ImmutableList value); + abstract Builder names(ImmutableList value); - abstract GuavaImmutable build(); - } + abstract GuavaImmutable build(); + } - public static void buildSomethingWrong() { - // :: error: finalizer.invocation.invalid - builder().build(); - } + public static void buildSomethingWrong() { + // :: error: finalizer.invocation.invalid + builder().build(); + } - public static void buildSomethingRight() { - builder().names(ImmutableList.of()).build(); - } + public static void buildSomethingRight() { + builder().names(ImmutableList.of()).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java b/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java index 88aa9f654fc..dbf258abe6b 100644 --- a/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java +++ b/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java @@ -1,28 +1,27 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class GuavaImmutablePropBuilder { - public abstract ImmutableList names(); + public abstract ImmutableList names(); - static Builder builder() { - return new AutoValue_GuavaImmutablePropBuilder.Builder(); - } + static Builder builder() { + return new AutoValue_GuavaImmutablePropBuilder.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract ImmutableList.Builder namesBuilder(); + abstract ImmutableList.Builder namesBuilder(); - abstract GuavaImmutablePropBuilder build(); - } + abstract GuavaImmutablePropBuilder build(); + } - public static void buildSomething() { - // don't need to explicitly set the names - builder().build(); - } + public static void buildSomething() { + // don't need to explicitly set the names + builder().build(); + } } diff --git a/checker/tests/calledmethods-autovalue/Inheritance.java b/checker/tests/calledmethods-autovalue/Inheritance.java index fb5643d95b7..aefa1bb4bf5 100644 --- a/checker/tests/calledmethods-autovalue/Inheritance.java +++ b/checker/tests/calledmethods-autovalue/Inheritance.java @@ -1,48 +1,47 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.common.returnsreceiver.qual.This; public class Inheritance { - static interface Props { - String name(); + static interface Props { + String name(); - String somethingElse(); + String somethingElse(); - abstract class Builder> { - public abstract @This B name(String value); - } + abstract class Builder> { + public abstract @This B name(String value); } + } - @AutoValue - abstract static class PropHolder implements Props { - abstract int size(); - - @Override - public String somethingElse() { - return "hi"; - } - - static Builder builder() { - return new AutoValue_Inheritance_PropHolder.Builder(); - } + @AutoValue + abstract static class PropHolder implements Props { + abstract int size(); - @AutoValue.Builder - public abstract static class Builder extends Props.Builder { - public abstract Builder size(int value); - - public abstract PropHolder build(); - } + @Override + public String somethingElse() { + return "hi"; } - static void correct() { - PropHolder.Builder b = PropHolder.builder(); - b.name("Manu").size(1).build(); + static Builder builder() { + return new AutoValue_Inheritance_PropHolder.Builder(); } - static void wrong() { - PropHolder.Builder b = PropHolder.builder(); - // :: error: finalizer.invocation.invalid - b.size(1).build(); + @AutoValue.Builder + public abstract static class Builder extends Props.Builder { + public abstract Builder size(int value); + + public abstract PropHolder build(); } + } + + static void correct() { + PropHolder.Builder b = PropHolder.builder(); + b.name("Manu").size(1).build(); + } + + static void wrong() { + PropHolder.Builder b = PropHolder.builder(); + // :: error: finalizer.invocation.invalid + b.size(1).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/IsPreserved.java b/checker/tests/calledmethods-autovalue/IsPreserved.java index 697e1ad72cd..f8fa2694bcf 100644 --- a/checker/tests/calledmethods-autovalue/IsPreserved.java +++ b/checker/tests/calledmethods-autovalue/IsPreserved.java @@ -1,42 +1,41 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class IsPreserved { - abstract String name(); + abstract String name(); - abstract String getAddress(); + abstract String getAddress(); - abstract boolean isPresent(); + abstract boolean isPresent(); - static Builder builder() { - return new AutoValue_IsPreserved.Builder(); - } + static Builder builder() { + return new AutoValue_IsPreserved.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder name(String val); + abstract Builder name(String val); - abstract Builder getAddress(String val); + abstract Builder getAddress(String val); - abstract Builder isPresent(boolean value); + abstract Builder isPresent(boolean value); - abstract IsPreserved build(); - } + abstract IsPreserved build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.name("Frank"); - b.getAddress("something"); - b.isPresent(true); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.name("Frank"); + b.getAddress("something"); + b.isPresent(true); + b.build(); + } - public static void buildSomethingRightFluent() { - builder().name("Bill").getAddress("something").isPresent(false).build(); - } + public static void buildSomethingRightFluent() { + builder().name("Bill").getAddress("something").isPresent(false).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/NonAVBuilder.java b/checker/tests/calledmethods-autovalue/NonAVBuilder.java index f869e11ddb1..7f9dc0313b0 100644 --- a/checker/tests/calledmethods-autovalue/NonAVBuilder.java +++ b/checker/tests/calledmethods-autovalue/NonAVBuilder.java @@ -1,19 +1,18 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class NonAVBuilder { - abstract String name(); + abstract String name(); - public Builder toBuilder() { - return new Builder(this); - } + public Builder toBuilder() { + return new Builder(this); + } - // NOT an AutoValue builder - static final class Builder { + // NOT an AutoValue builder + static final class Builder { - Builder(NonAVBuilder b) {} - } + Builder(NonAVBuilder b) {} + } } diff --git a/checker/tests/calledmethods-autovalue/NonBuildName.java b/checker/tests/calledmethods-autovalue/NonBuildName.java index 6d1dbc2cdca..1bd2a58f370 100644 --- a/checker/tests/calledmethods-autovalue/NonBuildName.java +++ b/checker/tests/calledmethods-autovalue/NonBuildName.java @@ -1,34 +1,33 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class NonBuildName { - public abstract String name(); + public abstract String name(); - static Builder builder() { - return new AutoValue_NonBuildName.Builder(); - } + static Builder builder() { + return new AutoValue_NonBuildName.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String name); + abstract Builder setName(String name); - abstract NonBuildName makeIt(); - } + abstract NonBuildName makeIt(); + } - static void correct() { - Builder b = builder(); - b.setName("Phil"); - b.makeIt(); - } + static void correct() { + Builder b = builder(); + b.setName("Phil"); + b.makeIt(); + } - static void wrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.makeIt(); - } + static void wrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.makeIt(); + } } diff --git a/checker/tests/calledmethods-autovalue/Parcel.java b/checker/tests/calledmethods-autovalue/Parcel.java index 3015789cfc2..81203608e62 100644 --- a/checker/tests/calledmethods-autovalue/Parcel.java +++ b/checker/tests/calledmethods-autovalue/Parcel.java @@ -3,9 +3,9 @@ /** stub to avoid bringing in Android dependence */ public final class Parcel { - public String readString() { - return ""; - } + public String readString() { + return ""; + } - public void writeString(String val) {} + public void writeString(String val) {} } diff --git a/checker/tests/calledmethods-autovalue/Parcelable.java b/checker/tests/calledmethods-autovalue/Parcelable.java index f01a75dd8df..7b9fb4fa227 100644 --- a/checker/tests/calledmethods-autovalue/Parcelable.java +++ b/checker/tests/calledmethods-autovalue/Parcelable.java @@ -2,14 +2,14 @@ /** stub to avoid bringing in Android dependence */ public interface Parcelable { - public interface Creator { + public interface Creator { - public T createFromParcel(Parcel source); + public T createFromParcel(Parcel source); - public T[] newArray(int size); - } + public T[] newArray(int size); + } - public int describeContents(); + public int describeContents(); - public void writeToParcel(Parcel dest, int flags); + public void writeToParcel(Parcel dest, int flags); } diff --git a/checker/tests/calledmethods-autovalue/SetInsideBuild.java b/checker/tests/calledmethods-autovalue/SetInsideBuild.java index 691df85f941..88942c25b45 100644 --- a/checker/tests/calledmethods-autovalue/SetInsideBuild.java +++ b/checker/tests/calledmethods-autovalue/SetInsideBuild.java @@ -1,41 +1,40 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class SetInsideBuild { - public abstract String name(); - - public abstract int size(); - - static Builder builder() { - return new AutoValue_SetInsideBuild.Builder(); - } + public abstract String name(); - @AutoValue.Builder - abstract static class Builder { - abstract Builder setName(String name); + public abstract int size(); - abstract Builder setSize(int value); + static Builder builder() { + return new AutoValue_SetInsideBuild.Builder(); + } - abstract SetInsideBuild autoBuild(); + @AutoValue.Builder + abstract static class Builder { + abstract Builder setName(String name); - public SetInsideBuild build(@CalledMethods({"setName"}) Builder this) { + abstract Builder setSize(int value); - return this.setSize(4).autoBuild(); - } - } + abstract SetInsideBuild autoBuild(); - public static void buildSomethingWrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); - } + public SetInsideBuild build(@CalledMethods({"setName"}) Builder this) { - public static void buildSomethingCorrect() { - Builder b = builder(); - b.setName("Frank"); - b.build(); + return this.setSize(4).autoBuild(); } + } + + public static void buildSomethingWrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); + } + + public static void buildSomethingCorrect() { + Builder b = builder(); + b.setName("Frank"); + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java b/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java index 6ed7b942674..2bdac94246e 100644 --- a/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java +++ b/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java @@ -1,41 +1,40 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class SetInsideBuildWithCM { - public abstract String name(); - - public abstract int size(); - - static Builder builder() { - return new AutoValue_SetInsideBuildWithCM.Builder(); - } + public abstract String name(); - @AutoValue.Builder - abstract static class Builder { - abstract Builder setName(String name); + public abstract int size(); - abstract Builder setSize(int value); + static Builder builder() { + return new AutoValue_SetInsideBuildWithCM.Builder(); + } - abstract SetInsideBuildWithCM autoBuild(); + @AutoValue.Builder + abstract static class Builder { + abstract Builder setName(String name); - public SetInsideBuildWithCM build() { - return this.autoBuild(); - } - } + abstract Builder setSize(int value); - public static void buildSomethingCorrect() { - Builder b = builder(); - b.setName("Frank"); - b.setSize(2); - b.build(); - } + abstract SetInsideBuildWithCM autoBuild(); - public static void buildSomethingWrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); + public SetInsideBuildWithCM build() { + return this.autoBuild(); } + } + + public static void buildSomethingCorrect() { + Builder b = builder(); + b.setName("Frank"); + b.setSize(2); + b.build(); + } + + public static void buildSomethingWrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/Validation.java b/checker/tests/calledmethods-autovalue/Validation.java index f77bcd710fa..c8f7f359315 100644 --- a/checker/tests/calledmethods-autovalue/Validation.java +++ b/checker/tests/calledmethods-autovalue/Validation.java @@ -1,42 +1,41 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class Validation { - public abstract String name(); - - static Builder builder() { - return new AutoValue_Validation.Builder(); - } - - @AutoValue.Builder - abstract static class Builder { + public abstract String name(); - abstract Builder setName(String name); + static Builder builder() { + return new AutoValue_Validation.Builder(); + } - abstract Validation autoBuild(); + @AutoValue.Builder + abstract static class Builder { - public Validation build(@CalledMethods("setName") Builder this) { - Validation v = autoBuild(); - if (v.name().length() < 5) { - throw new RuntimeException("name too short!"); - } - return v; - } - } + abstract Builder setName(String name); - static void correct() { - Builder b = builder(); - b.setName("Phil"); - b.build(); - } + abstract Validation autoBuild(); - static void wrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); + public Validation build(@CalledMethods("setName") Builder this) { + Validation v = autoBuild(); + if (v.name().length() < 5) { + throw new RuntimeException("name too short!"); + } + return v; } + } + + static void correct() { + Builder b = builder(); + b.setName("Phil"); + b.build(); + } + + static void wrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); + } } diff --git a/checker/tests/calledmethods-disableframeworks/AnimalSimple.java b/checker/tests/calledmethods-disableframeworks/AnimalSimple.java index d0f36c399f9..538b4bf79c6 100644 --- a/checker/tests/calledmethods-disableframeworks/AnimalSimple.java +++ b/checker/tests/calledmethods-disableframeworks/AnimalSimple.java @@ -6,48 +6,48 @@ */ @AutoValue abstract class AnimalSimple { - abstract String name(); + abstract String name(); - abstract int numberOfLegs(); + abstract int numberOfLegs(); - static Builder builder() { - return new AutoValue_AnimalSimple.Builder(); - } + static Builder builder() { + return new AutoValue_AnimalSimple.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract Builder setNumberOfLegs(int value); + abstract Builder setNumberOfLegs(int value); - abstract AnimalSimple build(); - } + abstract AnimalSimple build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.build(); + } - public static void buildSomethingWrongFluent() { - builder().setName("Frank").build(); - } + public static void buildSomethingWrongFluent() { + builder().setName("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).build(); - } + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).build(); + } - public static void buildSomethingRightFluentWithLocal() { - Builder b = builder(); - b.setName("Jim").setNumberOfLegs(7); - b.build(); - } + public static void buildSomethingRightFluentWithLocal() { + Builder b = builder(); + b.setName("Jim").setNumberOfLegs(7); + b.build(); + } } diff --git a/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java b/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java index 926d12ecdc8..0e6ce11bce2 100644 --- a/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java +++ b/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java @@ -1,82 +1,82 @@ // Generated by delombok at Wed Aug 14 15:57:15 PDT 2019 // A test for support for the builder() method in Lombok builders. public class LombokBuilderExample { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - builder().build(); + static void test() { + builder().build(); + } + + @java.lang.SuppressWarnings("all") + @lombok.Generated + LombokBuilderExample(@lombok.NonNull final Object foo, @lombok.NonNull final Object bar) { + if (foo == null) { + throw new java.lang.NullPointerException("foo is marked non-null but is null"); + } + if (bar == null) { + throw new java.lang.NullPointerException("bar is marked non-null but is null"); } + this.foo = foo; + this.bar = bar; + } + @java.lang.SuppressWarnings("all") + @lombok.Generated + public static class LombokBuilderExampleBuilder { @java.lang.SuppressWarnings("all") @lombok.Generated - LombokBuilderExample(@lombok.NonNull final Object foo, @lombok.NonNull final Object bar) { - if (foo == null) { - throw new java.lang.NullPointerException("foo is marked non-null but is null"); - } - if (bar == null) { - throw new java.lang.NullPointerException("bar is marked non-null but is null"); - } - this.foo = foo; - this.bar = bar; - } + private Object foo; @java.lang.SuppressWarnings("all") @lombok.Generated - public static class LombokBuilderExampleBuilder { - @java.lang.SuppressWarnings("all") - @lombok.Generated - private Object foo; - - @java.lang.SuppressWarnings("all") - @lombok.Generated - private Object bar; + private Object bar; - @java.lang.SuppressWarnings("all") - @lombok.Generated - LombokBuilderExampleBuilder() {} - - @java.lang.SuppressWarnings("all") - @lombok.Generated - public LombokBuilderExampleBuilder foo(@lombok.NonNull final Object foo) { - if (foo == null) { - throw new java.lang.NullPointerException("foo is marked non-null but is null"); - } - this.foo = foo; - return this; - } + @java.lang.SuppressWarnings("all") + @lombok.Generated + LombokBuilderExampleBuilder() {} - @java.lang.SuppressWarnings("all") - @lombok.Generated - public LombokBuilderExampleBuilder bar(@lombok.NonNull final Object bar) { - if (bar == null) { - throw new java.lang.NullPointerException("bar is marked non-null but is null"); - } - this.bar = bar; - return this; - } + @java.lang.SuppressWarnings("all") + @lombok.Generated + public LombokBuilderExampleBuilder foo(@lombok.NonNull final Object foo) { + if (foo == null) { + throw new java.lang.NullPointerException("foo is marked non-null but is null"); + } + this.foo = foo; + return this; + } - @java.lang.SuppressWarnings("all") - @lombok.Generated - public LombokBuilderExample build() { - return new LombokBuilderExample(foo, bar); - } + @java.lang.SuppressWarnings("all") + @lombok.Generated + public LombokBuilderExampleBuilder bar(@lombok.NonNull final Object bar) { + if (bar == null) { + throw new java.lang.NullPointerException("bar is marked non-null but is null"); + } + this.bar = bar; + return this; + } - @java.lang.Override - @java.lang.SuppressWarnings("all") - @lombok.Generated - public java.lang.String toString() { - return "LombokBuilderExample.LombokBuilderExampleBuilder(foo=" - + this.foo - + ", bar=" - + this.bar - + ")"; - } + @java.lang.SuppressWarnings("all") + @lombok.Generated + public LombokBuilderExample build() { + return new LombokBuilderExample(foo, bar); } + @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated - public static LombokBuilderExampleBuilder builder() { - return new LombokBuilderExampleBuilder(); + public java.lang.String toString() { + return "LombokBuilderExample.LombokBuilderExampleBuilder(foo=" + + this.foo + + ", bar=" + + this.bar + + ")"; } + } + + @java.lang.SuppressWarnings("all") + @lombok.Generated + public static LombokBuilderExampleBuilder builder() { + return new LombokBuilderExampleBuilder(); + } } diff --git a/checker/tests/calledmethods-disableframeworks/Parcel.java b/checker/tests/calledmethods-disableframeworks/Parcel.java index 3015789cfc2..81203608e62 100644 --- a/checker/tests/calledmethods-disableframeworks/Parcel.java +++ b/checker/tests/calledmethods-disableframeworks/Parcel.java @@ -3,9 +3,9 @@ /** stub to avoid bringing in Android dependence */ public final class Parcel { - public String readString() { - return ""; - } + public String readString() { + return ""; + } - public void writeString(String val) {} + public void writeString(String val) {} } diff --git a/checker/tests/calledmethods-disableframeworks/Parcelable.java b/checker/tests/calledmethods-disableframeworks/Parcelable.java index f01a75dd8df..7b9fb4fa227 100644 --- a/checker/tests/calledmethods-disableframeworks/Parcelable.java +++ b/checker/tests/calledmethods-disableframeworks/Parcelable.java @@ -2,14 +2,14 @@ /** stub to avoid bringing in Android dependence */ public interface Parcelable { - public interface Creator { + public interface Creator { - public T createFromParcel(Parcel source); + public T createFromParcel(Parcel source); - public T[] newArray(int size); - } + public T[] newArray(int size); + } - public int describeContents(); + public int describeContents(); - public void writeToParcel(Parcel dest, int flags); + public void writeToParcel(Parcel dest, int flags); } diff --git a/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java b/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java index 20fbf9605a5..61c8c7e3ce6 100644 --- a/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java +++ b/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java @@ -7,70 +7,70 @@ /* Simple inference of a fluent builder */ public class SimpleFluentInference { - SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { - return this; - } + SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { + return this; + } - SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { - return this; - } + SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { + return this; + } - @This SimpleFluentInference a() { - return this; - } + @This SimpleFluentInference a() { + return this; + } - @This SimpleFluentInference b() { - return this; - } + @This SimpleFluentInference b() { + return this; + } - // intentionally does not have an @This annotation - SimpleFluentInference c() { - return new SimpleFluentInference(); - } - - static void doStuffCorrect() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - .b() - // :: error: finalizer.invocation.invalid - .build(); - } + // intentionally does not have an @This annotation + SimpleFluentInference c() { + return new SimpleFluentInference(); + } - static void doStuffWrong() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - // :: error: finalizer.invocation.invalid - .build(); - } + static void doStuffCorrect() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + .b() + // :: error: finalizer.invocation.invalid + .build(); + } - static void doStuffRightWeak() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - // :: error: finalizer.invocation.invalid - .weakbuild(); - } + static void doStuffWrong() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + // :: error: finalizer.invocation.invalid + .build(); + } - static void noReturnsReceiverAnno() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - .b() - .c() - // :: error: finalizer.invocation.invalid - .build(); - } + static void doStuffRightWeak() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + // :: error: finalizer.invocation.invalid + .weakbuild(); + } - static void fluentLoop() { - SimpleFluentInference s = new SimpleFluentInference().a(); - int i = 10; - while (i > 0) { + static void noReturnsReceiverAnno() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + .b() + .c() // :: error: finalizer.invocation.invalid - s.b().build(); - i--; - s = new SimpleFluentInference(); - } + .build(); + } + + static void fluentLoop() { + SimpleFluentInference s = new SimpleFluentInference().a(); + int i = 10; + while (i > 0) { + // :: error: finalizer.invocation.invalid + s.b().build(); + i--; + s = new SimpleFluentInference(); } + } } diff --git a/checker/tests/calledmethods-lombok/BuilderTest.java b/checker/tests/calledmethods-lombok/BuilderTest.java index 40106a0c678..c4c0af687b0 100644 --- a/checker/tests/calledmethods-lombok/BuilderTest.java +++ b/checker/tests/calledmethods-lombok/BuilderTest.java @@ -3,25 +3,25 @@ @Builder public class BuilderTest { - private Integer x; - @NonNull private Integer y; - @Builder.Default @NonNull private Integer z = 5; + private Integer x; + @NonNull private Integer y; + @Builder.Default @NonNull private Integer z = 5; - public static void test_simplePattern() { - BuilderTest.builder().x(0).y(0).build(); // good builder - BuilderTest.builder().y(0).build(); // good builder - BuilderTest.builder().y(0).z(5).build(); // good builder - // :: error: (finalizer.invocation.invalid) - BuilderTest.builder().x(0).build(); // bad builder - } + public static void test_simplePattern() { + BuilderTest.builder().x(0).y(0).build(); // good builder + BuilderTest.builder().y(0).build(); // good builder + BuilderTest.builder().y(0).z(5).build(); // good builder + // :: error: (finalizer.invocation.invalid) + BuilderTest.builder().x(0).build(); // bad builder + } - public static void test_builderVar() { - final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); - goodBuilder.x(0); - goodBuilder.y(0); - goodBuilder.build(); - final BuilderTest.BuilderTestBuilder badBuilder = new BuilderTestBuilder(); - // :: error: (finalizer.invocation.invalid) - badBuilder.build(); - } + public static void test_builderVar() { + final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); + goodBuilder.x(0); + goodBuilder.y(0); + goodBuilder.build(); + final BuilderTest.BuilderTestBuilder badBuilder = new BuilderTestBuilder(); + // :: error: (finalizer.invocation.invalid) + badBuilder.build(); + } } diff --git a/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java b/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java index 5241db55261..e1945e48cca 100644 --- a/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java +++ b/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java @@ -3,202 +3,192 @@ public class CheckerFrameworkBuilder { - /** - * Most of this test was copied from - * https://raw.githubusercontent.com/projectlombok/lombok/master/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java - * with the exception of the following lines until the next long comment. I have made one change - * outside the scope of these comments: - I fixed the placement of the type annotations, which - * were originally on scoping constructs. I think this is a bug in the delombok pretty-printer - * used to generate this code, but I wasn't able to find the configuration options used to - * reproduce it in the public release. - * - *

This test represents exactly the code that Lombok generates with the checkerframework = - * True option in a lombok.config file, including the weird package names they use for the CF - * and the {@code @NotCalledMethods} annotation that they use even though we don't (and never - * have) supported such a thing. - * - *

The new code added in this block ensures that the Called Methods checker handles clients - * of the copied code correctly. - */ - public static void testOldCalledMethodsGood( - @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder pb) { - pb.build(); - } - - public static void testOldCalledMethodsBad( - @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y"}) CheckerFrameworkBuilderBuilder pb) { + /** + * Most of this test was copied from + * https://raw.githubusercontent.com/projectlombok/lombok/master/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java + * with the exception of the following lines until the next long comment. I have made one change + * outside the scope of these comments: - I fixed the placement of the type annotations, which + * were originally on scoping constructs. I think this is a bug in the delombok pretty-printer + * used to generate this code, but I wasn't able to find the configuration options used to + * reproduce it in the public release. + * + *

This test represents exactly the code that Lombok generates with the checkerframework = True + * option in a lombok.config file, including the weird package names they use for the CF and the + * {@code @NotCalledMethods} annotation that they use even though we don't (and never have) + * supported such a thing. + * + *

The new code added in this block ensures that the Called Methods checker handles clients of + * the copied code correctly. + */ + public static void testOldCalledMethodsGood( + @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder pb) { + pb.build(); + } + + public static void testOldCalledMethodsBad( + @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y"}) CheckerFrameworkBuilderBuilder pb) { + // :: error: finalizer.invocation.invalid + pb.build(); // pb requires y, z + } + + public static void testOldRRGood() { + CheckerFrameworkBuilder b = CheckerFrameworkBuilder.builder().y(5).z(6).build(); + } + + public static void testOldRRBad() { + CheckerFrameworkBuilder b = // :: error: finalizer.invocation.invalid - pb.build(); // pb requires y, z - } + CheckerFrameworkBuilder.builder().z(6).build(); // also needs to call y + } + + /** End new, non-copied code. */ + int x; + + int y; + int z; + List names; + + @java.lang.SuppressWarnings("all") + private static int $default$x() { + return 5; + } + + @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") + CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) { + this.x = x; + this.y = y; + this.z = z; + this.names = names; + } + + @java.lang.SuppressWarnings("all") + public static class CheckerFrameworkBuilderBuilder { + @java.lang.SuppressWarnings("all") + private boolean x$set; + + @java.lang.SuppressWarnings("all") + private int x$value; + + @java.lang.SuppressWarnings("all") + private int y; + + @java.lang.SuppressWarnings("all") + private int z; + + @java.lang.SuppressWarnings("all") + private java.util.ArrayList names; - public static void testOldRRGood() { - CheckerFrameworkBuilder b = CheckerFrameworkBuilder.builder().y(5).z(6).build(); + @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") + CheckerFrameworkBuilderBuilder() {} + + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder x( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods("x") CheckerFrameworkBuilderBuilder + this, + final int x) { + this.x$value = x; + x$set = true; + return this; } - public static void testOldRRBad() { - CheckerFrameworkBuilder b = - // :: error: finalizer.invocation.invalid - CheckerFrameworkBuilder.builder().z(6).build(); // also needs to call y + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder y( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods("y") CheckerFrameworkBuilderBuilder + this, + final int y) { + this.y = y; + return this; } - /** End new, non-copied code. */ - int x; + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder z( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods("z") CheckerFrameworkBuilderBuilder + this, + final int z) { + this.z = z; + return this; + } - int y; - int z; - List names; + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder name(final String name) { + if (this.names == null) { + this.names = new java.util.ArrayList(); + } + this.names.add(name); + return this; + } + @org.checkerframework.checker.builder.qual.ReturnsReceiver @java.lang.SuppressWarnings("all") - private static int $default$x() { - return 5; + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder names( + final java.util.Collection names) { + if (names == null) { + throw new java.lang.NullPointerException("names cannot be null"); + } + if (this.names == null) { + this.names = new java.util.ArrayList(); + } + this.names.addAll(names); + return this; } - @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") - CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) { - this.x = x; - this.y = y; - this.z = z; - this.names = names; + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder clearNames() { + if (this.names != null) { + this.names.clear(); + } + return this; } + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") - public static class CheckerFrameworkBuilderBuilder { - @java.lang.SuppressWarnings("all") - private boolean x$set; - - @java.lang.SuppressWarnings("all") - private int x$value; - - @java.lang.SuppressWarnings("all") - private int y; - - @java.lang.SuppressWarnings("all") - private int z; - - @java.lang.SuppressWarnings("all") - private java.util.ArrayList names; - - @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") - CheckerFrameworkBuilderBuilder() {} - - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder x( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods( - "x") - CheckerFrameworkBuilderBuilder - this, - final int x) { - this.x$value = x; - x$set = true; - return this; - } - - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder y( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods( - "y") - CheckerFrameworkBuilderBuilder - this, - final int y) { - this.y = y; - return this; - } - - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder z( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods( - "z") - CheckerFrameworkBuilderBuilder - this, - final int z) { - this.z = z; - return this; - } - - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder name(final String name) { - if (this.names == null) { - this.names = new java.util.ArrayList(); - } - this.names.add(name); - return this; - } - - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder names( - final java.util.Collection names) { - if (names == null) { - throw new java.lang.NullPointerException("names cannot be null"); - } - if (this.names == null) { - this.names = new java.util.ArrayList(); - } - this.names.addAll(names); - return this; - } - - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder clearNames() { - if (this.names != null) { - this.names.clear(); - } - return this; - } - - @org.checkerframework.dataflow.qual.SideEffectFree - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder build( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.CalledMethods({ - "y", "z" - }) - CheckerFrameworkBuilderBuilder - this) { - java.util.List names; - switch (this.names == null ? 0 : this.names.size()) { - case 0: - names = java.util.Collections.emptyList(); - break; - case 1: - names = java.util.Collections.singletonList(this.names.get(0)); - break; - default: - names = - java.util.Collections.unmodifiableList( - new java.util.ArrayList(this.names)); - } - int x$value = this.x$value; - if (!this.x$set) { - x$value = CheckerFrameworkBuilder.$default$x(); - } - return new CheckerFrameworkBuilder(x$value, this.y, this.z, names); - } - - @org.checkerframework.dataflow.qual.SideEffectFree - @java.lang.Override - @java.lang.SuppressWarnings("all") - public java.lang.String toString() { - return "CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(x$value=" - + this.x$value - + ", y=" - + this.y - + ", z=" - + this.z - + ", names=" - + this.names - + ")"; - } + public CheckerFrameworkBuilder build( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder + this) { + java.util.List names; + switch (this.names == null ? 0 : this.names.size()) { + case 0: + names = java.util.Collections.emptyList(); + break; + case 1: + names = java.util.Collections.singletonList(this.names.get(0)); + break; + default: + names = + java.util.Collections.unmodifiableList(new java.util.ArrayList(this.names)); + } + int x$value = this.x$value; + if (!this.x$set) { + x$value = CheckerFrameworkBuilder.$default$x(); + } + return new CheckerFrameworkBuilder(x$value, this.y, this.z, names); } @org.checkerframework.dataflow.qual.SideEffectFree + @java.lang.Override @java.lang.SuppressWarnings("all") - public static CheckerFrameworkBuilder.@org.checkerframework.common.aliasing.qual.Unique CheckerFrameworkBuilderBuilder - builder() { - return new CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(); + public java.lang.String toString() { + return "CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(x$value=" + + this.x$value + + ", y=" + + this.y + + ", z=" + + this.z + + ", names=" + + this.names + + ")"; } + } + + @org.checkerframework.dataflow.qual.SideEffectFree + @java.lang.SuppressWarnings("all") + public static CheckerFrameworkBuilder.@org.checkerframework.common.aliasing.qual.Unique CheckerFrameworkBuilderBuilder + builder() { + return new CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(); + } } diff --git a/checker/tests/calledmethods-lombok/DefaultedName.java b/checker/tests/calledmethods-lombok/DefaultedName.java index b9974aef1e2..02cb09ccc06 100644 --- a/checker/tests/calledmethods-lombok/DefaultedName.java +++ b/checker/tests/calledmethods-lombok/DefaultedName.java @@ -2,17 +2,17 @@ @Builder public class DefaultedName { - @Builder.Default @lombok.NonNull String name = "Martin"; + @Builder.Default @lombok.NonNull String name = "Martin"; - static void test1() { - builder().build(); - } + static void test1() { + builder().build(); + } - static void test2(Object foo) { - DefaultedNameBuilder b = builder(); - if (foo != null) { - b.name(foo.toString()); - } - b.build(); + static void test2(Object foo) { + DefaultedNameBuilder b = builder(); + if (foo != null) { + b.name(foo.toString()); } + b.build(); + } } diff --git a/checker/tests/calledmethods-lombok/LombokBuilderExample.java b/checker/tests/calledmethods-lombok/LombokBuilderExample.java index 9a8ca6843e7..5cf70721e06 100644 --- a/checker/tests/calledmethods-lombok/LombokBuilderExample.java +++ b/checker/tests/calledmethods-lombok/LombokBuilderExample.java @@ -4,11 +4,11 @@ @Builder public class LombokBuilderExample { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - // :: error: (finalizer.invocation.invalid) - builder().build(); - } + static void test() { + // :: error: (finalizer.invocation.invalid) + builder().build(); + } } diff --git a/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java b/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java index 2adc444fd71..df474dcaff6 100644 --- a/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java +++ b/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java @@ -1,7 +1,6 @@ import lombok.Builder; import lombok.NonNull; import lombok.Value; - import org.checkerframework.checker.calledmethods.qual.CalledMethods; import org.checkerframework.common.returnsreceiver.qual.This; @@ -9,32 +8,32 @@ @Value public class LombokBuilderSubclassExample { - @NonNull Integer attribute; + @NonNull Integer attribute; - public static LombokBuilderSubclassExampleBuilder builder() { - return new LombokBuilderSubclassExampleBuilder(); - } + public static LombokBuilderSubclassExampleBuilder builder() { + return new LombokBuilderSubclassExampleBuilder(); + } - public static class LombokBuilderSubclassExampleBuilder extends BaseBuilder { + public static class LombokBuilderSubclassExampleBuilder extends BaseBuilder { - @Override - @This public LombokBuilderSubclassExampleBuilder attribute(@NonNull Integer attribute) { - return (LombokBuilderSubclassExampleBuilder) super.attribute(attribute); - } + @Override + @This public LombokBuilderSubclassExampleBuilder attribute(@NonNull Integer attribute) { + return (LombokBuilderSubclassExampleBuilder) super.attribute(attribute); + } - @Override - public LombokBuilderSubclassExample build( - @CalledMethods("attribute") LombokBuilderSubclassExampleBuilder this) { - final LombokBuilderSubclassExample result = super.build(); - // here result.getAttribute() is guaranteed to be non null, so we do not have to check - // this - // ourselves + @Override + public LombokBuilderSubclassExample build( + @CalledMethods("attribute") LombokBuilderSubclassExampleBuilder this) { + final LombokBuilderSubclassExample result = super.build(); + // here result.getAttribute() is guaranteed to be non null, so we do not have to check + // this + // ourselves - if (result.getAttribute() < 0) { - throw new IllegalArgumentException("attribute must be >= 0"); - } + if (result.getAttribute() < 0) { + throw new IllegalArgumentException("attribute must be >= 0"); + } - return result; - } + return result; } + } } diff --git a/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java b/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java index dcba963492c..a2c164e89c7 100644 --- a/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java +++ b/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java @@ -1,16 +1,15 @@ -import lombok.Builder; - import java.util.Optional; +import lombok.Builder; @Builder public class LombokDefaultAssignments { - @lombok.NonNull Optional bar; + @lombok.NonNull Optional bar; - public static class LombokDefaultAssignmentsBuilder { - private Optional bar = Optional.empty(); - } + public static class LombokDefaultAssignmentsBuilder { + private Optional bar = Optional.empty(); + } - static void test() { - LombokDefaultAssignments.builder().build(); - } + static void test() { + LombokDefaultAssignments.builder().build(); + } } diff --git a/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java b/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java index 5ddf7275c78..0022f2674a7 100644 --- a/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java +++ b/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java @@ -1,23 +1,22 @@ -import lombok.Builder; - import java.util.List; +import lombok.Builder; @Builder public class LombokNoSingularButClearMethodExample { - @lombok.NonNull List items; + @lombok.NonNull List items; - // This one should throw an error, because the field isn't - // automatically initialized. - public static void testNoItems() { - // :: error: finalizer.invocation.invalid - LombokNoSingularButClearMethodExample.builder().build(); - } + // This one should throw an error, because the field isn't + // automatically initialized. + public static void testNoItems() { + // :: error: finalizer.invocation.invalid + LombokNoSingularButClearMethodExample.builder().build(); + } - public static void testWithList(List l) { - LombokNoSingularButClearMethodExample.builder().items(l).build(); - } + public static void testWithList(List l) { + LombokNoSingularButClearMethodExample.builder().items(l).build(); + } - public static class LombokNoSingularButClearMethodExampleBuilder { - public void clearItems() {} - } + public static class LombokNoSingularButClearMethodExampleBuilder { + public void clearItems() {} + } } diff --git a/checker/tests/calledmethods-lombok/LombokSingularExample.java b/checker/tests/calledmethods-lombok/LombokSingularExample.java index 647f629f3b1..82b171e2059 100644 --- a/checker/tests/calledmethods-lombok/LombokSingularExample.java +++ b/checker/tests/calledmethods-lombok/LombokSingularExample.java @@ -1,25 +1,24 @@ +import java.util.List; import lombok.Builder; import lombok.Singular; -import java.util.List; - @Builder public class LombokSingularExample { - @Singular @lombok.NonNull List items; + @Singular @lombok.NonNull List items; - // This one should be permitted, because @Singular will - // produce an empty list even if one is not specified directly. - public static void testNoItems() { - LombokSingularExample.builder().build(); - } + // This one should be permitted, because @Singular will + // produce an empty list even if one is not specified directly. + public static void testNoItems() { + LombokSingularExample.builder().build(); + } - // This call should also be permitted, even though items() is - // not called, because the list will be automatically initialized. - public static void testOneItem() { - LombokSingularExample.builder().item(new Object()).build(); - } + // This call should also be permitted, even though items() is + // not called, because the list will be automatically initialized. + public static void testOneItem() { + LombokSingularExample.builder().item(new Object()).build(); + } - public static void testWithList(List l) { - LombokSingularExample.builder().items(l).build(); - } + public static void testWithList(List l) { + LombokSingularExample.builder().items(l).build(); + } } diff --git a/checker/tests/calledmethods-lombok/LombokToBuilderExample.java b/checker/tests/calledmethods-lombok/LombokToBuilderExample.java index c1cbdb8e567..4c527710577 100644 --- a/checker/tests/calledmethods-lombok/LombokToBuilderExample.java +++ b/checker/tests/calledmethods-lombok/LombokToBuilderExample.java @@ -4,14 +4,14 @@ @Builder(toBuilder = true) public class LombokToBuilderExample { - @lombok.NonNull String req; + @lombok.NonNull String req; - static void test(LombokToBuilderExample foo) { - foo.toBuilder().build(); - } + static void test(LombokToBuilderExample foo) { + foo.toBuilder().build(); + } - static void ensureThatErrorIssued() { - // :: error: finalizer.invocation.invalid - LombokToBuilderExample.builder().build(); - } + static void ensureThatErrorIssued() { + // :: error: finalizer.invocation.invalid + LombokToBuilderExample.builder().build(); + } } diff --git a/checker/tests/calledmethods-lombok/OldInherited.java b/checker/tests/calledmethods-lombok/OldInherited.java index d163c1b13c6..aaa624a2562 100644 --- a/checker/tests/calledmethods-lombok/OldInherited.java +++ b/checker/tests/calledmethods-lombok/OldInherited.java @@ -5,41 +5,41 @@ import org.checkerframework.checker.calledmethods.qual.*; public class OldInherited { - @ReturnsReceiver - OldInherited getThis() { - return this; - } - - static class OldInheritedChild extends OldInherited { - @java.lang.Override - OldInherited getThis() { - return this; - } - } - - void requiresGetThis(@CalledMethods("getThis") OldInherited this) {} - - public static void testGoodParent() { - OldInherited o = new OldInherited(); - o.getThis(); - o.requiresGetThis(); - } - - public static void testGoodChild() { - OldInheritedChild o = new OldInheritedChild(); - o.getThis(); - o.requiresGetThis(); - } + @ReturnsReceiver + OldInherited getThis() { + return this; + } - public static void testBadParent() { - OldInherited o = new OldInherited(); - // :: error: finalizer.invocation.invalid - o.requiresGetThis(); - } - - public static void testBadChild() { - OldInheritedChild o = new OldInheritedChild(); - // :: error: finalizer.invocation.invalid - o.requiresGetThis(); + static class OldInheritedChild extends OldInherited { + @java.lang.Override + OldInherited getThis() { + return this; } + } + + void requiresGetThis(@CalledMethods("getThis") OldInherited this) {} + + public static void testGoodParent() { + OldInherited o = new OldInherited(); + o.getThis(); + o.requiresGetThis(); + } + + public static void testGoodChild() { + OldInheritedChild o = new OldInheritedChild(); + o.getThis(); + o.requiresGetThis(); + } + + public static void testBadParent() { + OldInherited o = new OldInherited(); + // :: error: finalizer.invocation.invalid + o.requiresGetThis(); + } + + public static void testBadChild() { + OldInheritedChild o = new OldInheritedChild(); + // :: error: finalizer.invocation.invalid + o.requiresGetThis(); + } } diff --git a/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java b/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java index e4ee4ee4006..f20b84a5f72 100644 --- a/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java +++ b/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java @@ -3,19 +3,19 @@ @lombok.Builder class UnsoundnessTest { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - // An error should be issued here, but the code has not been delombok'd. - // If the CF and Lombok are ever able to work in the same invocation of javac - // (i.e. without delomboking first), then this error should be changed back to an - // expected error by re-adding the leading "::". - // error: (finalizer.invocation) - builder().build(); - } + static void test() { + // An error should be issued here, but the code has not been delombok'd. + // If the CF and Lombok are ever able to work in the same invocation of javac + // (i.e. without delomboking first), then this error should be changed back to an + // expected error by re-adding the leading "::". + // error: (finalizer.invocation) + builder().build(); + } - static void test2() { - builder().foo(null).bar(null).build(); - } + static void test2() { + builder().foo(null).bar(null).build(); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/Cve.java b/checker/tests/calledmethods-usevaluechecker/Cve.java index 056d7e2f267..72183cf3ff6 100644 --- a/checker/tests/calledmethods-usevaluechecker/Cve.java +++ b/checker/tests/calledmethods-usevaluechecker/Cve.java @@ -7,101 +7,101 @@ // https://nvd.nist.gov/vuln/detail/CVE-2018-15869 public class Cve { - private static final String IMG_NAME = "some_linux_img"; + private static final String IMG_NAME = "some_linux_img"; - public static void onlyNames(AmazonEC2 client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + public static void onlyNames(AmazonEC2 client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); + } - public static void correct3(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withExecutableUsers("myUsers")); - } + public static void correct3(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withExecutableUsers("myUsers")); + } - // Using impl class instead of interface - public static void onlyNamesImpl(AmazonEC2Client client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + // Using impl class instead of interface + public static void onlyNamesImpl(AmazonEC2Client client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1Impl(AmazonEC2Client client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1Impl(AmazonEC2Client client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2Impl(AmazonEC2Client client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2Impl(AmazonEC2Client client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); + } - // Using async impl class - public static void onlyNamesAsync(AmazonEC2AsyncClient client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + // Using async impl class + public static void onlyNamesAsync(AmazonEC2AsyncClient client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1Async(AmazonEC2AsyncClient client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1Async(AmazonEC2AsyncClient client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2Async(AmazonEC2AsyncClient client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2Async(AmazonEC2AsyncClient client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); + } - // Using async methods - public static void onlyNamesAsync2(AmazonEC2AsyncClient client) { - // Should not be allowed unless .withOwner is also used - Object result = - client.describeImagesAsync( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + // Using async methods + public static void onlyNamesAsync2(AmazonEC2AsyncClient client) { + // Should not be allowed unless .withOwner is also used + Object result = + client.describeImagesAsync( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1Async2(AmazonEC2AsyncClient client) { - Object result = - client.describeImagesAsync( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1Async2(AmazonEC2AsyncClient client) { + Object result = + client.describeImagesAsync( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2Async2(AmazonEC2AsyncClient client) { - Object result = - client.describeImagesAsync(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2Async2(AmazonEC2AsyncClient client) { + Object result = + client.describeImagesAsync(new DescribeImagesRequest().withImageIds("myImageId")); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/Cve2.java b/checker/tests/calledmethods-usevaluechecker/Cve2.java index 844d7a82b44..e049bd73e48 100644 --- a/checker/tests/calledmethods-usevaluechecker/Cve2.java +++ b/checker/tests/calledmethods-usevaluechecker/Cve2.java @@ -2,41 +2,40 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; - import java.util.Collections; // https://nvd.nist.gov/vuln/detail/CVE-2018-15869 public class Cve2 { - private static final String IMG_NAME = "some_linux_img"; + private static final String IMG_NAME = "some_linux_img"; - public static void onlyNames(AmazonEC2 client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter("name").withValues(IMG_NAME)); + public static void onlyNames(AmazonEC2 client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter("name").withValues(IMG_NAME)); - // :: error: argument.type.incompatible - DescribeImagesResult result = client.describeImages(request); - } + // :: error: argument.type.incompatible + DescribeImagesResult result = client.describeImages(request); + } - public static void correct1(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter("name").withValues(IMG_NAME)); - request.withOwners("martin"); + public static void correct1(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter("name").withValues(IMG_NAME)); + request.withOwners("martin"); - DescribeImagesResult result = client.describeImages(request); - } + DescribeImagesResult result = client.describeImages(request); + } - public static void correct2(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withImageIds("myImageId"); + public static void correct2(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withImageIds("myImageId"); - DescribeImagesResult result = client.describeImages(request); - } + DescribeImagesResult result = client.describeImages(request); + } - public static void correct3(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.setExecutableUsers(Collections.singletonList("myUser1")); + public static void correct3(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.setExecutableUsers(Collections.singletonList("myUser1")); - DescribeImagesResult result = client.describeImages(request); - } + DescribeImagesResult result = client.describeImages(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java b/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java index eec56d9792f..92e87662112 100644 --- a/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java +++ b/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java @@ -3,111 +3,110 @@ import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.model.DataKeySpec; import com.amazonaws.services.kms.model.GenerateDataKeyRequest; - import org.checkerframework.checker.calledmethods.qual.*; public class GenerateDataKeyRequestExamples { - void correctWithKeySpec(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void correctWithNumberOfBytes(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withNumberOfBytes(32); - client.generateDataKey(request); - } - - void correctSetKeySpec(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void correctSetNumberOfBytes(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setNumberOfBytes(32); - client.generateDataKey(request); - } - - // The next four examples are "both" - void incorrect1(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setKeySpec(DataKeySpec.AES_256); - request.setNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - void incorrect2(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.setNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - void incorrect3(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setKeySpec(DataKeySpec.AES_256); - request.withNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - void incorrect4(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.withNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - // This example is "neither" - void incorrect5(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - // Calling these methods are idempotent, including between with/set versions of the same. - // TODO: Verify that these calls should be permitted. - void setTwice1(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.withKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void setTwice2(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.setKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void setTwice3(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withNumberOfBytes(32); - request.setNumberOfBytes(32); - client.generateDataKey(request); - } - - void setTwice4(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setNumberOfBytes(32); - request.setNumberOfBytes(32); - client.generateDataKey(request); - } - - /// Interprocedural - - void callee2( - AWSKMS client, - @CalledMethodsPredicate("(!withNumberOfBytes) && (!setNumberOfBytes)") GenerateDataKeyRequest request) { - request.withKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } + void correctWithKeySpec(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void correctWithNumberOfBytes(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withNumberOfBytes(32); + client.generateDataKey(request); + } + + void correctSetKeySpec(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void correctSetNumberOfBytes(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setNumberOfBytes(32); + client.generateDataKey(request); + } + + // The next four examples are "both" + void incorrect1(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setKeySpec(DataKeySpec.AES_256); + request.setNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + void incorrect2(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.setNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + void incorrect3(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setKeySpec(DataKeySpec.AES_256); + request.withNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + void incorrect4(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.withNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + // This example is "neither" + void incorrect5(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + // Calling these methods are idempotent, including between with/set versions of the same. + // TODO: Verify that these calls should be permitted. + void setTwice1(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.withKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void setTwice2(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.setKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void setTwice3(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withNumberOfBytes(32); + request.setNumberOfBytes(32); + client.generateDataKey(request); + } + + void setTwice4(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setNumberOfBytes(32); + request.setNumberOfBytes(32); + client.generateDataKey(request); + } + + /// Interprocedural + + void callee2( + AWSKMS client, + @CalledMethodsPredicate("(!withNumberOfBytes) && (!setNumberOfBytes)") GenerateDataKeyRequest request) { + request.withKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java b/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java index 1e04c13a35d..ebff3fac54d 100644 --- a/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java +++ b/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java @@ -2,69 +2,68 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; - import java.util.Arrays; import java.util.Collections; public class MorePreciseFilters { - /* TODO: handle lists - void ownerAliasList(AmazonEC2 ec2Client) { - DescribeImagesRequest imagesRequest = new DescribeImagesRequest(); - List imageFilters = new ArrayList(); - imageFilters.add(new Filter().withName("owner-alias").withValues("microsoft")); - ec2Client.describeImages(imagesRequest.withFilters(imageFilters)).getImages(); - } - */ + /* TODO: handle lists + void ownerAliasList(AmazonEC2 ec2Client) { + DescribeImagesRequest imagesRequest = new DescribeImagesRequest(); + List imageFilters = new ArrayList(); + imageFilters.add(new Filter().withName("owner-alias").withValues("microsoft")); + ec2Client.describeImages(imagesRequest.withFilters(imageFilters)).getImages(); + } + */ - void withFilterNameInList(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.setFilters( - Collections.singletonList(new Filter().withName("image-id").withValues("12345"))); + void withFilterNameInList(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.setFilters( + Collections.singletonList(new Filter().withName("image-id").withValues("12345"))); - DescribeImagesResult result = ec2Client.describeImages(request); - } + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withOwnerId(AmazonEC2 ec2) { - DescribeImagesRequest request = - new DescribeImagesRequest() - .withFilters( - new Filter("name", Arrays.asList("my_image_name")), - new Filter("owner-id", Arrays.asList("12345"))); - DescribeImagesResult result = ec2.describeImages(request); - } + void withOwnerId(AmazonEC2 ec2) { + DescribeImagesRequest request = + new DescribeImagesRequest() + .withFilters( + new Filter("name", Arrays.asList("my_image_name")), + new Filter("owner-id", Arrays.asList("12345"))); + DescribeImagesResult result = ec2.describeImages(request); + } - void withName(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter().withName("image-id").withValues("12345")); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter().withName("image-id").withValues("12345")); + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName2(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter().withName("image-id").withName("foo").withValues("12345")); - // :: error: (argument.type.incompatible) - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName2(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter().withName("image-id").withName("foo").withValues("12345")); + // :: error: (argument.type.incompatible) + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName3(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter().withName("foo").withName("image-id").withValues("12345")); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName3(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter().withName("foo").withName("image-id").withValues("12345")); + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName4(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters( - new Filter().withName("owner-id").withName("foo").withValues("12345"), - new Filter("owner-id", Arrays.asList("12345"))); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName4(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters( + new Filter().withName("owner-id").withName("foo").withValues("12345"), + new Filter("owner-id", Arrays.asList("12345"))); + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName5(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - Filter f = new Filter(); - request.withFilters(f.withName("owner-id").withValues("12345")); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName5(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + Filter f = new Filter(); + request.withFilters(f.withName("owner-id").withValues("12345")); + DescribeImagesResult result = ec2Client.describeImages(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java b/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java index 2c303a323fc..0d3b2057f44 100644 --- a/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java +++ b/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java @@ -1,16 +1,15 @@ import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; - import java.util.Collections; // Tests that just setting with/setOwners is permitted, since there are legitimate reasons to do // that. // Originally, we required with/setFilters && with/setOwners. public class OnlyOwnersFalsePositive { - void test(AmazonEC2 ec2Client) { - DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest(); - describeImagesRequest.setOwners(Collections.singleton("self")); - DescribeImagesResult describeImagesResult = ec2Client.describeImages(describeImagesRequest); - } + void test(AmazonEC2 ec2Client) { + DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest(); + describeImagesRequest.setOwners(Collections.singleton("self")); + DescribeImagesResult describeImagesResult = ec2Client.describeImages(describeImagesRequest); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java b/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java index 8396729d963..21ab8d83952 100644 --- a/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java +++ b/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java @@ -2,16 +2,14 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; - import java.util.*; // A test to ensure that requests that are created in the call to describeImages work correctly. public class RequestCreatedInCall { - void test(AmazonEC2 ec2) { - List filters = new ArrayList<>(); - filters.add(new Filter().withName("foo").withValues("bar")); - DescribeImagesResult describeImagesResult = - ec2.describeImages( - new DescribeImagesRequest().withOwners("martin").withFilters(filters)); - } + void test(AmazonEC2 ec2) { + List filters = new ArrayList<>(); + filters.add(new Filter().withName("foo").withValues("bar")); + DescribeImagesResult describeImagesResult = + ec2.describeImages(new DescribeImagesRequest().withOwners("martin").withFilters(filters)); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java b/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java index 4cea89cf842..bd3655f234a 100644 --- a/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java +++ b/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java @@ -2,21 +2,18 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; - import java.util.*; // A simple (potential) false positive case with mutliple filters. public class SimpleFalsePositive { - void test(AmazonEC2 ec2Client, String namePrefix) { - DescribeImagesRequest request = - new DescribeImagesRequest() - .withOwners("martin") - .withFilters( - Arrays.asList( - new Filter("platform", Arrays.asList("windows")), - new Filter( - "name", - Arrays.asList(String.format("%s*", namePrefix))))); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void test(AmazonEC2 ec2Client, String namePrefix) { + DescribeImagesRequest request = + new DescribeImagesRequest() + .withOwners("martin") + .withFilters( + Arrays.asList( + new Filter("platform", Arrays.asList("windows")), + new Filter("name", Arrays.asList(String.format("%s*", namePrefix))))); + DescribeImagesResult result = ec2Client.describeImages(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/SpecialNames.java b/checker/tests/calledmethods-usevaluechecker/SpecialNames.java index 74fe28ae862..da20341ff04 100644 --- a/checker/tests/calledmethods-usevaluechecker/SpecialNames.java +++ b/checker/tests/calledmethods-usevaluechecker/SpecialNames.java @@ -5,56 +5,56 @@ import org.checkerframework.common.returnsreceiver.qual.*; public class SpecialNames { - @This SpecialNames withFilters() { - return this; - } + @This SpecialNames withFilters() { + return this; + } - void setFilters() {} + void setFilters() {} - @This SpecialNames withFilters(SpecialNames f) { - return this; - } + @This SpecialNames withFilters(SpecialNames f) { + return this; + } - void setFilters(SpecialNames f) {} + void setFilters(SpecialNames f) {} - @This SpecialNames withName() { - return this; - } + @This SpecialNames withName() { + return this; + } - @This SpecialNames withName(String f) { - return this; - } + @This SpecialNames withName(String f) { + return this; + } - SpecialNames() {} + SpecialNames() {} - SpecialNames(String x) {} + SpecialNames(String x) {} - static void test(SpecialNames s) { - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); - } + static void test(SpecialNames s) { + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); + } - static void test2(SpecialNames s) { - s.setFilters(new SpecialNames("owner")); - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s; - } + static void test2(SpecialNames s) { + s.setFilters(new SpecialNames("owner")); + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s; + } - static void test3(SpecialNames s) { - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); - } + static void test3(SpecialNames s) { + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); + } - static void test4(SpecialNames s) { - s.setFilters(new SpecialNames("owner")); - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s; - } + static void test4(SpecialNames s) { + s.setFilters(new SpecialNames("owner")); + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s; + } - static void testForCrashes(SpecialNames s) { - s.setFilters(); - s.withFilters(); + static void testForCrashes(SpecialNames s) { + s.setFilters(); + s.withFilters(); - s.setFilters(new SpecialNames().withName()); - } + s.setFilters(new SpecialNames().withName()); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java b/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java index df3167eb673..0e1b274f22f 100644 --- a/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java +++ b/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java @@ -4,28 +4,27 @@ import com.amazonaws.services.ec2.model.Filter; public class WithOwnersFilter { - private static final String IMG_NAME = "some_linux_img"; + private static final String IMG_NAME = "some_linux_img"; - public static void correct1(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withFilters(new Filter("owner").withValues("my_aws_acct"))); - } + public static void correct1(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withFilters(new Filter("owner").withValues("my_aws_acct"))); + } - public static void correct2(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter("name").withValues(IMG_NAME)); - request.withFilters(new Filter("owner").withValues("my_aws_acct")); - client.describeImages(request); - } + public static void correct2(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter("name").withValues(IMG_NAME)); + request.withFilters(new Filter("owner").withValues("my_aws_acct")); + client.describeImages(request); + } - public static void correct3(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters( - new Filter("name").withValues(IMG_NAME), - new Filter("owner").withValues("my_aws_acct")); - client.describeImages(request); - } + public static void correct3(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters( + new Filter("name").withValues(IMG_NAME), new Filter("owner").withValues("my_aws_acct")); + client.describeImages(request); + } } diff --git a/checker/tests/calledmethods/CmPredicate.java b/checker/tests/calledmethods/CmPredicate.java index 52046bbdb4c..80f602b649a 100644 --- a/checker/tests/calledmethods/CmPredicate.java +++ b/checker/tests/calledmethods/CmPredicate.java @@ -2,606 +2,606 @@ public class CmPredicate { - void testOr1() { - MyClass m1 = new MyClass(); + void testOr1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.c(); - } + // :: error: method.invocation.invalid + m1.c(); + } - void testOr2() { - MyClass m1 = new MyClass(); + void testOr2() { + MyClass m1 = new MyClass(); - m1.a(); - m1.c(); - } + m1.a(); + m1.c(); + } - void testOr3() { - MyClass m1 = new MyClass(); + void testOr3() { + MyClass m1 = new MyClass(); - m1.b(); - m1.c(); - } + m1.b(); + m1.c(); + } - void testAnd1() { - MyClass m1 = new MyClass(); + void testAnd1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.d(); - } + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd2() { - MyClass m1 = new MyClass(); + void testAnd2() { + MyClass m1 = new MyClass(); - m1.a(); - // :: error: method.invocation.invalid - m1.d(); - } + m1.a(); + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd3() { - MyClass m1 = new MyClass(); + void testAnd3() { + MyClass m1 = new MyClass(); - m1.b(); - // :: error: method.invocation.invalid - m1.d(); - } + m1.b(); + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd4() { - MyClass m1 = new MyClass(); + void testAnd4() { + MyClass m1 = new MyClass(); - m1.a(); - m1.c(); - // :: error: method.invocation.invalid - m1.d(); - } + m1.a(); + m1.c(); + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd5() { - MyClass m1 = new MyClass(); + void testAnd5() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.d(); - } + m1.a(); + m1.b(); + m1.d(); + } - void testAnd6() { - MyClass m1 = new MyClass(); + void testAnd6() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.c(); - m1.d(); - } + m1.a(); + m1.b(); + m1.c(); + m1.d(); + } - void testAndOr1() { - MyClass m1 = new MyClass(); + void testAndOr1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.e(); - } + // :: error: method.invocation.invalid + m1.e(); + } - void testAndOr2() { - MyClass m1 = new MyClass(); + void testAndOr2() { + MyClass m1 = new MyClass(); - m1.a(); - m1.e(); - } + m1.a(); + m1.e(); + } - void testAndOr3() { - MyClass m1 = new MyClass(); + void testAndOr3() { + MyClass m1 = new MyClass(); - m1.b(); - // :: error: method.invocation.invalid - m1.e(); - } + m1.b(); + // :: error: method.invocation.invalid + m1.e(); + } - void testAndOr4() { - MyClass m1 = new MyClass(); + void testAndOr4() { + MyClass m1 = new MyClass(); - m1.b(); - m1.c(); - m1.e(); - } + m1.b(); + m1.c(); + m1.e(); + } - void testAndOr5() { - MyClass m1 = new MyClass(); + void testAndOr5() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.c(); - m1.d(); - m1.e(); - } + m1.a(); + m1.b(); + m1.c(); + m1.d(); + m1.e(); + } - void testPrecedence1() { - MyClass m1 = new MyClass(); + void testPrecedence1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.f(); - } + // :: error: method.invocation.invalid + m1.f(); + } - void testPrecedence2() { - MyClass m1 = new MyClass(); + void testPrecedence2() { + MyClass m1 = new MyClass(); - m1.a(); - // :: error: method.invocation.invalid - m1.f(); - } + m1.a(); + // :: error: method.invocation.invalid + m1.f(); + } - void testPrecedence3() { - MyClass m1 = new MyClass(); + void testPrecedence3() { + MyClass m1 = new MyClass(); - m1.b(); - // :: error: method.invocation.invalid - m1.f(); - } + m1.b(); + // :: error: method.invocation.invalid + m1.f(); + } - void testPrecedence4() { - MyClass m1 = new MyClass(); + void testPrecedence4() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.f(); - } + m1.a(); + m1.b(); + m1.f(); + } - void testPrecedence5() { - MyClass m1 = new MyClass(); + void testPrecedence5() { + MyClass m1 = new MyClass(); - m1.a(); - m1.c(); - m1.f(); - } + m1.a(); + m1.c(); + m1.f(); + } - void testPrecedence6() { - MyClass m1 = new MyClass(); + void testPrecedence6() { + MyClass m1 = new MyClass(); - m1.b(); - m1.c(); - m1.f(); - } + m1.b(); + m1.c(); + m1.f(); + } + + private static class MyClass { - private static class MyClass { + @CalledMethods("a") MyClass cmA; - @CalledMethods("a") MyClass cmA; + @CalledMethodsPredicate("a") MyClass cmpA; - @CalledMethodsPredicate("a") MyClass cmpA; + @CalledMethods({"a", "b"}) MyClass aB; + + @CalledMethodsPredicate("a || b") MyClass aOrB; + + @CalledMethodsPredicate("a && b") MyClass aAndB; + + @CalledMethodsPredicate("a || b && c") MyClass bAndCOrA; + + @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParens; + + @CalledMethodsPredicate("a && b || c") MyClass aAndBOrC; + + @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParens; + + @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndC; + + @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndA; + + @CalledMethodsPredicate("b && c") MyClass bAndC; + + @CalledMethodsPredicate("(b && c)") MyClass bAndCParens; + + void a() {} + + void b() {} + + void c(@CalledMethodsPredicate("a || b") MyClass this) {} + + void d(@CalledMethodsPredicate("a && b") MyClass this) {} + + void e(@CalledMethodsPredicate("a || (b && c)") MyClass this) {} + + void f(@CalledMethodsPredicate("a && b || c") MyClass this) {} + + static void testAssignability1(@CalledMethodsPredicate("a || b") MyClass cAble) { + cAble.c(); + // :: error: method.invocation.invalid + cAble.d(); + // :: error: method.invocation.invalid + cAble.e(); + // :: error: method.invocation.invalid + cAble.f(); + } + + static void testAssignability2(@CalledMethodsPredicate("a && b") MyClass dAble) { + // These calls would work if subtyping between predicates was by implication. They issue + // errors, because it is not. + // :: error: method.invocation.invalid + dAble.c(); + dAble.d(); + // :: error: method.invocation.invalid + dAble.e(); + // :: error: method.invocation.invalid + dAble.f(); + } - @CalledMethods({"a", "b"}) MyClass aB; - - @CalledMethodsPredicate("a || b") MyClass aOrB; - - @CalledMethodsPredicate("a && b") MyClass aAndB; - - @CalledMethodsPredicate("a || b && c") MyClass bAndCOrA; - - @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParens; - - @CalledMethodsPredicate("a && b || c") MyClass aAndBOrC; - - @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParens; - - @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndC; - - @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndA; - - @CalledMethodsPredicate("b && c") MyClass bAndC; - - @CalledMethodsPredicate("(b && c)") MyClass bAndCParens; - - void a() {} - - void b() {} - - void c(@CalledMethodsPredicate("a || b") MyClass this) {} - - void d(@CalledMethodsPredicate("a && b") MyClass this) {} - - void e(@CalledMethodsPredicate("a || (b && c)") MyClass this) {} - - void f(@CalledMethodsPredicate("a && b || c") MyClass this) {} - - static void testAssignability1(@CalledMethodsPredicate("a || b") MyClass cAble) { - cAble.c(); - // :: error: method.invocation.invalid - cAble.d(); - // :: error: method.invocation.invalid - cAble.e(); - // :: error: method.invocation.invalid - cAble.f(); - } - - static void testAssignability2(@CalledMethodsPredicate("a && b") MyClass dAble) { - // These calls would work if subtyping between predicates was by implication. They issue - // errors, because it is not. - // :: error: method.invocation.invalid - dAble.c(); - dAble.d(); - // :: error: method.invocation.invalid - dAble.e(); - // :: error: method.invocation.invalid - dAble.f(); - } - - void testAllAssignability() { - - @CalledMethods("a") MyClass cmALocal; - @CalledMethodsPredicate("a") MyClass cmpALocal; - @CalledMethodsPredicate("a || b") MyClass aOrBLocal; - @CalledMethods({"a", "b"}) MyClass aBLocal; - @CalledMethodsPredicate("a && b") MyClass aAndBLocal; - @CalledMethodsPredicate("a || b && c") MyClass bAndCOrALocal; - @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; - @CalledMethodsPredicate("a && b || c") MyClass aAndBOrCLocal; - @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; - @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndCLocal; - @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndALocal; - @CalledMethodsPredicate("b && c") MyClass bAndCLocal; - @CalledMethodsPredicate("(b && c)") MyClass bAndCParensLocal; - - cmALocal = cmA; - cmALocal = cmpA; - // :: error: assignment.type.incompatible - cmALocal = aOrB; - cmALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = aAndB; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmALocal = bAndC; - // :: error: assignment.type.incompatible - cmALocal = bAndCParens; - - cmpALocal = cmA; - cmpALocal = cmpA; - // :: error: assignment.type.incompatible - cmpALocal = aOrB; - cmpALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = aAndB; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmpALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmpALocal = bAndC; - // :: error: assignment.type.incompatible - cmpALocal = bAndCParens; - - aOrBLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = cmpA; - aOrBLocal = aOrB; - aOrBLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrC; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aBLocal = cmA; - // :: error: (assignment.type.incompatible) - aBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aBLocal = aOrB; - aBLocal = aB; - aBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrB; - aAndBLocal = aB; - aAndBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCParens; - - bAndCOrALocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aOrB; - bAndCOrALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aAndB; - bAndCOrALocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCParens; - - bAndCOrAParensLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aOrB; - bAndCOrAParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCOrA; - bAndCOrAParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = aOrB; - aAndBOrCLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrAParens; - aAndBOrCLocal = aAndBOrC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = aOrB; - aAndBOrCParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrAParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndBOrC; - aAndBOrCParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrCParens; - aOrBAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmpA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrB; - bOrCAndALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bOrCAndALocal = aAndB; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrBAndC; - bOrCAndALocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCLocal = bOrCAndA; - bAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCParensLocal = bAndC; - bAndCParensLocal = bAndCParens; - } + void testAllAssignability() { + + @CalledMethods("a") MyClass cmALocal; + @CalledMethodsPredicate("a") MyClass cmpALocal; + @CalledMethodsPredicate("a || b") MyClass aOrBLocal; + @CalledMethods({"a", "b"}) MyClass aBLocal; + @CalledMethodsPredicate("a && b") MyClass aAndBLocal; + @CalledMethodsPredicate("a || b && c") MyClass bAndCOrALocal; + @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; + @CalledMethodsPredicate("a && b || c") MyClass aAndBOrCLocal; + @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; + @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndCLocal; + @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndALocal; + @CalledMethodsPredicate("b && c") MyClass bAndCLocal; + @CalledMethodsPredicate("(b && c)") MyClass bAndCParensLocal; + + cmALocal = cmA; + cmALocal = cmpA; + // :: error: assignment.type.incompatible + cmALocal = aOrB; + cmALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = aAndB; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmALocal = bAndC; + // :: error: assignment.type.incompatible + cmALocal = bAndCParens; + + cmpALocal = cmA; + cmpALocal = cmpA; + // :: error: assignment.type.incompatible + cmpALocal = aOrB; + cmpALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = aAndB; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmpALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmpALocal = bAndC; + // :: error: assignment.type.incompatible + cmpALocal = bAndCParens; + + aOrBLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = cmpA; + aOrBLocal = aOrB; + aOrBLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrC; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aBLocal = cmA; + // :: error: (assignment.type.incompatible) + aBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aBLocal = aOrB; + aBLocal = aB; + aBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrB; + aAndBLocal = aB; + aAndBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCParens; + + bAndCOrALocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aOrB; + bAndCOrALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aAndB; + bAndCOrALocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCParens; + + bAndCOrAParensLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aOrB; + bAndCOrAParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCOrA; + bAndCOrAParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = aOrB; + aAndBOrCLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrAParens; + aAndBOrCLocal = aAndBOrC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = aOrB; + aAndBOrCParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrAParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndBOrC; + aAndBOrCParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrCParens; + aOrBAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmpA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrB; + bOrCAndALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bOrCAndALocal = aAndB; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrBAndC; + bOrCAndALocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCLocal = bOrCAndA; + bAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCParensLocal = bAndC; + bAndCParensLocal = bAndCParens; } + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java b/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java index a3c2e20e80b..94a2c783031 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java @@ -1,55 +1,54 @@ -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; - import java.io.Closeable; import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; public class EnsuresCalledMethodsIfRepeatable { - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") - public boolean close2MissingFirst(Closeable r1, Closeable r2) throws IOException { - r1.close(); - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public boolean close2MissingFirst(Closeable r1, Closeable r2) throws IOException { + r1.close(); + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public boolean close2MissingSecond(Closeable r1, Closeable r2) throws IOException { + r2.close(); + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { + try { + r1.close(); + } finally { + r2.close(); } + return true; + } - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") - public boolean close2MissingSecond(Closeable r1, Closeable r2) throws IOException { - r2.close(); - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public boolean close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { + return close2Correct(r1, r2); + } - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public static class SubclassWrong extends EnsuresCalledMethodsIfRepeatable { + @Override public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { - try { - r1.close(); - } finally { - r2.close(); - } - return true; + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; } + } - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") - public boolean close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { - return close2Correct(r1, r2); - } - - public static class SubclassWrong extends EnsuresCalledMethodsIfRepeatable { - @Override - public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - } - - public static class SubclassRight extends EnsuresCalledMethodsIfRepeatable { - @Override - public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { - return false; - } + public static class SubclassRight extends EnsuresCalledMethodsIfRepeatable { + @Override + public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { + return false; } + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java b/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java index 02c896ce10f..184576d194d 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java @@ -1,30 +1,29 @@ -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; - import java.io.Closeable; import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; public class EnsuresCalledMethodsIfSubclass { - public static class Parent { - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - public boolean method(Closeable x) throws IOException { - x.close(); - return true; - } + public static class Parent { + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + public boolean method(Closeable x) throws IOException { + x.close(); + return true; } + } - public static class SubclassWrong extends Parent { - @Override - public boolean method(Closeable x) throws IOException { - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + public static class SubclassWrong extends Parent { + @Override + public boolean method(Closeable x) throws IOException { + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; } + } - public static class SubclassRight extends Parent { - @Override - public boolean method(Closeable x) throws IOException { - return false; - } + public static class SubclassRight extends Parent { + @Override + public boolean method(Closeable x) throws IOException { + return false; } + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java b/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java index 7773cd5af43..2f38c3fbc9c 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java @@ -1,63 +1,62 @@ // Test case for https://github.com/typetools/checker-framework/issues/4699 +import java.io.IOException; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; -import java.io.IOException; - class EnsuresCalledMethodsIfTest { - @EnsuresCalledMethods(value = "#1", methods = "close") - // If `sock` is null, `sock.close()` will not be called, and the method will exit normally, as - // the - // NullPointerException is caught. But, the Called Methods Checker - // assumes the program is free of NullPointerExceptions, delegating verification of that - // property to the Nullness Checker. So, the postcondition is verified. - public static void closeSock(EnsuresCalledMethodsIfTest sock) throws Exception { - if (!sock.isOpen()) { - return; - } - try { - sock.close(); - } catch (Exception e) { - } + @EnsuresCalledMethods(value = "#1", methods = "close") + // If `sock` is null, `sock.close()` will not be called, and the method will exit normally, as + // the + // NullPointerException is caught. But, the Called Methods Checker + // assumes the program is free of NullPointerExceptions, delegating verification of that + // property to the Nullness Checker. So, the postcondition is verified. + public static void closeSock(EnsuresCalledMethodsIfTest sock) throws Exception { + if (!sock.isOpen()) { + return; } - - @EnsuresCalledMethods(value = "#1", methods = "close") - public static void closeSockOK(EnsuresCalledMethodsIfTest sock) throws Exception { - if (!sock.isOpen()) { - return; - } - try { - sock.close(); - } catch (IOException e) { - } + try { + sock.close(); + } catch (Exception e) { } + } - @EnsuresCalledMethods(value = "#1", methods = "close") - public static void closeSockOK1(EnsuresCalledMethodsIfTest sock) throws Exception { - if (!sock.isOpen()) { - return; - } - sock.close(); + @EnsuresCalledMethods(value = "#1", methods = "close") + public static void closeSockOK(EnsuresCalledMethodsIfTest sock) throws Exception { + if (!sock.isOpen()) { + return; } - - @EnsuresCalledMethods(value = "#1", methods = "close") - public static void closeSockOK2(EnsuresCalledMethodsIfTest sock) throws Exception { - if (sock.isOpen()) { - sock.close(); - } + try { + sock.close(); + } catch (IOException e) { } + } - void close() throws IOException {} + @EnsuresCalledMethods(value = "#1", methods = "close") + public static void closeSockOK1(EnsuresCalledMethodsIfTest sock) throws Exception { + if (!sock.isOpen()) { + return; + } + sock.close(); + } - @SuppressWarnings( - "calledmethods") // like the JDK's isOpen methods; makes this test case self-contained - @EnsuresCalledMethodsIf( - expression = "this", - result = false, - methods = {"close"}) - boolean isOpen() { - return true; + @EnsuresCalledMethods(value = "#1", methods = "close") + public static void closeSockOK2(EnsuresCalledMethodsIfTest sock) throws Exception { + if (sock.isOpen()) { + sock.close(); } + } + + void close() throws IOException {} + + @SuppressWarnings( + "calledmethods") // like the JDK's isOpen methods; makes this test case self-contained + @EnsuresCalledMethodsIf( + expression = "this", + result = false, + methods = {"close"}) + boolean isOpen() { + return true; + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java index 180856f4d1b..d1683dc64b6 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java @@ -1,46 +1,45 @@ // Test that @EnsuresCalledMethodsOnException can be repeated. -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException; - import java.io.*; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException; class EnsuresCalledMethodsOnExceptionRepeatable { - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { - r1.close(); + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { + r1.close(); + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { + r2.close(); + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") + public void close2Correct(Closeable r1, Closeable r2) throws IOException { + try { + r1.close(); + } finally { + r2.close(); } + } - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { - r2.close(); - } + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") + public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { + close2Correct(r1, r2); + } - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") + public static class Subclass extends EnsuresCalledMethodsOnExceptionRepeatable { + @Override + // ::error: (contracts.exceptional.postcondition.not.satisfied) public void close2Correct(Closeable r1, Closeable r2) throws IOException { - try { - r1.close(); - } finally { - r2.close(); - } - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") - public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { - close2Correct(r1, r2); - } - - public static class Subclass extends EnsuresCalledMethodsOnExceptionRepeatable { - @Override - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close2Correct(Closeable r1, Closeable r2) throws IOException { - throw new IOException(); - } + throw new IOException(); } + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java index e799b6f57a7..af204778861 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java @@ -1,30 +1,29 @@ // Test that @EnsuresCalledMethodsOnException is inherited by overridden methods. -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException; - import java.io.*; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException; public class EnsuresCalledMethodsOnExceptionSubclass { - public static class Parent { - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - public void method(Closeable x) throws IOException { - x.close(); - } + public static class Parent { + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + public void method(Closeable x) throws IOException { + x.close(); } + } - public static class SubclassWrong extends Parent { - @Override - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void method(Closeable x) throws IOException { - throw new IOException(); - } + public static class SubclassWrong extends Parent { + @Override + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void method(Closeable x) throws IOException { + throw new IOException(); } + } - public static class SubclassCorrect extends Parent { - @Override - public void method(Closeable x) throws IOException { - // No exception thrown ==> no contract to satisfy! - } + public static class SubclassCorrect extends Parent { + @Override + public void method(Closeable x) throws IOException { + // No exception thrown ==> no contract to satisfy! } + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java index 0defb3e4dbf..c1cd220ccbc 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java @@ -1,107 +1,106 @@ // Test that @EnsuresCalledMethodsOnException behaves as expected. -import org.checkerframework.checker.calledmethods.qual.*; - import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.*; public abstract class EnsuresCalledMethodsOnExceptionTest { - static class Resource { - void a() {} + static class Resource { + void a() {} - void b() throws IOException {} - } + void b() throws IOException {} + } - abstract boolean arbitraryChoice(); + abstract boolean arbitraryChoice(); - abstract void throwArbitraryException() throws Exception; + abstract void throwArbitraryException() throws Exception; - @EnsuresCalledMethodsOnException(value = "#1", methods = "b") - void blanketCase(Resource r) throws IOException { - // OK: r.b() counts as called even if it itself throws an exception. - r.b(); - } + @EnsuresCalledMethodsOnException(value = "#1", methods = "b") + void blanketCase(Resource r) throws IOException { + // OK: r.b() counts as called even if it itself throws an exception. + r.b(); + } - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void noCall(Resource r) { - // OK: this method does not throw exceptions. - } + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void noCall(Resource r) { + // OK: this method does not throw exceptions. + } - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - // :: error: (contracts.exceptional.postcondition.not.satisfied) - void callAfterThrow(Resource r) throws Exception { - if (arbitraryChoice()) { - // Not OK: r.a() has not been called yet - throwArbitraryException(); - } - r.a(); + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + // :: error: (contracts.exceptional.postcondition.not.satisfied) + void callAfterThrow(Resource r) throws Exception { + if (arbitraryChoice()) { + // Not OK: r.a() has not been called yet + throwArbitraryException(); } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void callInFinallyBlock(Resource r) throws Exception { - try { - if (arbitraryChoice()) { - // OK: r.a() will be called in the finally block - throwArbitraryException(); - } - } finally { - r.a(); - } + r.a(); + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void callInFinallyBlock(Resource r) throws Exception { + try { + if (arbitraryChoice()) { + // OK: r.a() will be called in the finally block + throwArbitraryException(); + } + } finally { + r.a(); } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void callInCatchBlock(Resource r) throws Exception { - try { - if (arbitraryChoice()) { - // OK: r.a() will be called in the catch block - throwArbitraryException(); - } - } catch (Exception e) { - r.a(); - throw e; - } + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void callInCatchBlock(Resource r) throws Exception { + try { + if (arbitraryChoice()) { + // OK: r.a() will be called in the catch block + throwArbitraryException(); + } + } catch (Exception e) { + r.a(); + throw e; } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - // :: error: (contracts.exceptional.postcondition.not.satisfied) - void callInSpecificCatchBlock(Resource r) throws Exception { - try { - if (arbitraryChoice()) { - // Not OK: the catch block only catches IOException - throwArbitraryException(); - } - } catch (IOException e) { - r.a(); - throw e; - } - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - abstract void callMethodOnException(Resource r) throws Exception; - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void propagateSubtypeOfException(Resource r) throws Exception { - // OK: the call satisfies our contract - callMethodOnException(r); + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + // :: error: (contracts.exceptional.postcondition.not.satisfied) + void callInSpecificCatchBlock(Resource r) throws Exception { + try { + if (arbitraryChoice()) { + // Not OK: the catch block only catches IOException + throwArbitraryException(); + } + } catch (IOException e) { + r.a(); + throw e; } - - @EnsuresCalledMethods(value = "#1", methods = "a") - void exploitCalledMethodsOnException(Resource r) throws Exception { - try { - callMethodOnException(r); - } catch (Exception e) { - // OK: the other call ensured the contract - return; - } - // OK: although r.a() was not called, this method promises nothing on exceptional return - throw new Exception("Phooey"); - } - - @EnsuresCalledMethods(value = "#1", methods = "a") - // :: error: (contracts.postcondition.not.satisfied) - void exceptionalCallsDoNotSatisfyNormalPaths(Resource r) throws Exception { - // Not OK: this call is not enough to satisfy our contract, since it only promises something - // on exceptional return. - callMethodOnException(r); + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + abstract void callMethodOnException(Resource r) throws Exception; + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void propagateSubtypeOfException(Resource r) throws Exception { + // OK: the call satisfies our contract + callMethodOnException(r); + } + + @EnsuresCalledMethods(value = "#1", methods = "a") + void exploitCalledMethodsOnException(Resource r) throws Exception { + try { + callMethodOnException(r); + } catch (Exception e) { + // OK: the other call ensured the contract + return; } + // OK: although r.a() was not called, this method promises nothing on exceptional return + throw new Exception("Phooey"); + } + + @EnsuresCalledMethods(value = "#1", methods = "a") + // :: error: (contracts.postcondition.not.satisfied) + void exceptionalCallsDoNotSatisfyNormalPaths(Resource r) throws Exception { + // Not OK: this call is not enough to satisfy our contract, since it only promises something + // on exceptional return. + callMethodOnException(r); + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java b/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java index 1b08f5f8528..21753cf25fb 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java @@ -1,54 +1,53 @@ -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; - import java.io.Closeable; import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; class EnsuresCalledMethodsRepeatable { - @EnsuresCalledMethods( - value = "#1", - methods = {"toString"}) - @EnsuresCalledMethods( - value = "#1", - methods = {"hashCode"}) - void test(Object obj) { - obj.toString(); - obj.hashCode(); + @EnsuresCalledMethods( + value = "#1", + methods = {"toString"}) + @EnsuresCalledMethods( + value = "#1", + methods = {"hashCode"}) + void test(Object obj) { + obj.toString(); + obj.hashCode(); + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") + // ::error: (contracts.postcondition.not.satisfied) + public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { + r1.close(); + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") + // ::error: (contracts.postcondition.not.satisfied) + public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { + r2.close(); + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") + public void close2Correct(Closeable r1, Closeable r2) throws IOException { + try { + r1.close(); + } finally { + r2.close(); } + } - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") - // ::error: (contracts.postcondition.not.satisfied) - public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { - r1.close(); - } + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") + public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { + close2Correct(r1, r2); + } - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") + public static class Subclass extends EnsuresCalledMethodsRepeatable { + @Override // ::error: (contracts.postcondition.not.satisfied) - public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { - r2.close(); - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") - public void close2Correct(Closeable r1, Closeable r2) throws IOException { - try { - r1.close(); - } finally { - r2.close(); - } - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") - public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { - close2Correct(r1, r2); - } - - public static class Subclass extends EnsuresCalledMethodsRepeatable { - @Override - // ::error: (contracts.postcondition.not.satisfied) - public void close2Correct(Closeable r1, Closeable r2) throws IOException {} - } + public void close2Correct(Closeable r1, Closeable r2) throws IOException {} + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java b/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java index 75652976706..6d6fb71ba0f 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java @@ -1,20 +1,19 @@ -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; - import java.io.Closeable; import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; public class EnsuresCalledMethodsSubclass { - public static class Parent { - @EnsuresCalledMethods(value = "#1", methods = "close") - public void method(Closeable x) throws IOException { - x.close(); - } + public static class Parent { + @EnsuresCalledMethods(value = "#1", methods = "close") + public void method(Closeable x) throws IOException { + x.close(); } + } - public static class Subclass extends Parent { - @Override - // ::error: (contracts.postcondition.not.satisfied) - public void method(Closeable x) throws IOException {} - } + public static class Subclass extends Parent { + @Override + // ::error: (contracts.postcondition.not.satisfied) + public void method(Closeable x) throws IOException {} + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java b/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java index 6e4f676167f..97aa817795e 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java @@ -3,41 +3,41 @@ class EnsuresCalledMethodsThisLub { - @EnsuresCalledMethods( - value = "#1", - methods = {"toString", "equals"}) - void call1(Object obj) { - obj.toString(); - obj.equals(null); - } + @EnsuresCalledMethods( + value = "#1", + methods = {"toString", "equals"}) + void call1(Object obj) { + obj.toString(); + obj.equals(null); + } - @EnsuresCalledMethods( - value = "#1", - methods = {"toString", "hashCode"}) - void call2(Object obj) { - obj.toString(); - obj.hashCode(); - } + @EnsuresCalledMethods( + value = "#1", + methods = {"toString", "hashCode"}) + void call2(Object obj) { + obj.toString(); + obj.hashCode(); + } - void test(boolean b) { - if (b) { - call1(this); - } else { - call2(this); - } - @CalledMethods("toString") Object obj1 = this; - // :: error: (assignment.type.incompatible) - @CalledMethods({"toString", "equals"}) Object obj2 = this; + void test(boolean b) { + if (b) { + call1(this); + } else { + call2(this); } + @CalledMethods("toString") Object obj1 = this; + // :: error: (assignment.type.incompatible) + @CalledMethods({"toString", "equals"}) Object obj2 = this; + } - void test_arg(Object arg, boolean b) { - if (b) { - call1(arg); - } else { - call2(arg); - } - @CalledMethods("toString") Object obj1 = arg; - // :: error: (assignment.type.incompatible) - @CalledMethods({"toString", "equals"}) Object obj2 = arg; + void test_arg(Object arg, boolean b) { + if (b) { + call1(arg); + } else { + call2(arg); } + @CalledMethods("toString") Object obj1 = arg; + // :: error: (assignment.type.incompatible) + @CalledMethods({"toString", "equals"}) Object obj2 = arg; + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java b/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java index ab82f7b0472..610446f2af6 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java @@ -1,39 +1,38 @@ // A simple test for the @EnsuresCalledMethodsVarArgs annotation. -import org.checkerframework.checker.calledmethods.qual.*; - import java.io.IOException; import java.net.Socket; import java.util.List; +import org.checkerframework.checker.calledmethods.qual.*; class EnsuresCalledMethodsVarArgsSimple { - // :: error: ensuresvarargs.unverified - @EnsuresCalledMethodsVarArgs("close") - void closeAll(Socket... sockets) { - for (Socket s : sockets) { - try { - s.close(); - } catch (IOException e) { - } - } + // :: error: ensuresvarargs.unverified + @EnsuresCalledMethodsVarArgs("close") + void closeAll(Socket... sockets) { + for (Socket s : sockets) { + try { + s.close(); + } catch (IOException e) { + } } + } - // :: error: ensuresvarargs.unverified - @EnsuresCalledMethodsVarArgs("close") - // :: error: ensuresvarargs.invalid - void closeAllNotVA(List sockets) { - for (Socket s : sockets) { - try { - s.close(); - } catch (IOException e) { - } - } + // :: error: ensuresvarargs.unverified + @EnsuresCalledMethodsVarArgs("close") + // :: error: ensuresvarargs.invalid + void closeAllNotVA(List sockets) { + for (Socket s : sockets) { + try { + s.close(); + } catch (IOException e) { + } } + } - void test(Socket s1, Socket s2) { - closeAll(s1, s2); - @CalledMethods("close") Socket s1_1 = s1; - @CalledMethods("close") Socket s2_1 = s2; - } + void test(Socket s1, Socket s2) { + closeAll(s1, s2); + @CalledMethods("close") Socket s1_1 = s1; + @CalledMethods("close") Socket s2_1 = s2; + } } diff --git a/checker/tests/calledmethods/ExceptionalPath.java b/checker/tests/calledmethods/ExceptionalPath.java index 93bb908adfc..084dd7394d7 100644 --- a/checker/tests/calledmethods/ExceptionalPath.java +++ b/checker/tests/calledmethods/ExceptionalPath.java @@ -1,18 +1,17 @@ // A test that calling a method with exceptional exit paths leads to that method being // considered "definitely called" (i.e. @CalledMethods of the method) on all paths. -import org.checkerframework.checker.calledmethods.qual.*; - import java.io.IOException; import java.net.Socket; +import org.checkerframework.checker.calledmethods.qual.*; class ExceptionalPath { - void test(Socket s) { - try { - s.close(); - @CalledMethods("close") Socket s1 = s; - } catch (IOException e) { - @CalledMethods("close") Socket s2 = s; - } + void test(Socket s) { + try { + s.close(); + @CalledMethods("close") Socket s1 = s; + } catch (IOException e) { + @CalledMethods("close") Socket s2 = s; } + } } diff --git a/checker/tests/calledmethods/ExceptionalPath2.java b/checker/tests/calledmethods/ExceptionalPath2.java index 715b729380c..fbaae26f616 100644 --- a/checker/tests/calledmethods/ExceptionalPath2.java +++ b/checker/tests/calledmethods/ExceptionalPath2.java @@ -1,38 +1,37 @@ -import org.checkerframework.checker.calledmethods.qual.*; - import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.*; class ExceptionalPath2 { - interface Resource { - void a(); + interface Resource { + void a(); - void b() throws IOException; - } + void b() throws IOException; + } - Resource r; + Resource r; - // Regression test for an obscure bug: in some cases, the called - // methods transfer function would silently fail to update the - // set of known called methods along exceptional paths. That - // would a spurious precondition error on this method. - @EnsuresCalledMethods( - value = "this.r", - methods = {"b"}) - void test() { - try { - try { - r.a(); - } finally { - r.b(); - } - } catch (IOException ignored) { - // The only way to get here is if `r.b()` started running and - // threw an IOException. We no longer know whether `r.a()` - // has been called, since `r.b()` might have overwritten `r` - // before throwing. - // ::error: (assignment.type.incompatible) - @CalledMethods({"a"}) Resource x = r; - } + // Regression test for an obscure bug: in some cases, the called + // methods transfer function would silently fail to update the + // set of known called methods along exceptional paths. That + // would a spurious precondition error on this method. + @EnsuresCalledMethods( + value = "this.r", + methods = {"b"}) + void test() { + try { + try { + r.a(); + } finally { + r.b(); + } + } catch (IOException ignored) { + // The only way to get here is if `r.b()` started running and + // threw an IOException. We no longer know whether `r.a()` + // has been called, since `r.b()` might have overwritten `r` + // before throwing. + // ::error: (assignment.type.incompatible) + @CalledMethods({"a"}) Resource x = r; } + } } diff --git a/checker/tests/calledmethods/FinallyClose.java b/checker/tests/calledmethods/FinallyClose.java index d3177bb3478..25417367031 100644 --- a/checker/tests/calledmethods/FinallyClose.java +++ b/checker/tests/calledmethods/FinallyClose.java @@ -1,66 +1,65 @@ // Test case involving some complicated try-finally control flow. -import org.checkerframework.checker.calledmethods.qual.*; - import java.io.*; +import org.checkerframework.checker.calledmethods.qual.*; abstract class FinallyClose { - abstract Closeable alloc() throws IOException; + abstract Closeable alloc() throws IOException; - abstract Closeable derive(Closeable r) throws IOException; + abstract Closeable derive(Closeable r) throws IOException; - abstract String compute(Closeable resource) throws IOException; + abstract String compute(Closeable resource) throws IOException; - abstract void makeNotes() throws IOException; + abstract void makeNotes() throws IOException; - String run1() throws IOException { - Closeable resource = null; - try { - resource = alloc(); - return compute(resource); - } finally { - try { - makeNotes(); - } finally { - closeResource(resource); - } - } + String run1() throws IOException { + Closeable resource = null; + try { + resource = alloc(); + return compute(resource); + } finally { + try { + makeNotes(); + } finally { + closeResource(resource); + } } + } - String run2() throws IOException { - Closeable resource = null; - Closeable subresource = null; + String run2() throws IOException { + Closeable resource = null; + Closeable subresource = null; + try { + resource = alloc(); + subresource = derive(resource); + return compute(subresource); + } finally { + try { + makeNotes(); + } finally { try { - resource = alloc(); - subresource = derive(resource); - return compute(subresource); + closeResource(subresource); } finally { - try { - makeNotes(); - } finally { - try { - closeResource(subresource); - } finally { - closeResource(resource); - } - } + closeResource(resource); } + } } + } - @EnsuresCalledMethods( - value = "#1", - methods = {"close"}) - @EnsuresCalledMethodsOnException( - value = "#1", - methods = {"close"}) - void closeResource(Closeable resource) throws IOException { - if (resource != null) { - try { - resource.close(); - } catch (Exception e) { - System.out.println(e); - } - } + @EnsuresCalledMethods( + value = "#1", + methods = {"close"}) + @EnsuresCalledMethodsOnException( + value = "#1", + methods = {"close"}) + void closeResource(Closeable resource) throws IOException { + if (resource != null) { + try { + resource.close(); + } catch (Exception e) { + System.out.println(e); + } } + } } diff --git a/checker/tests/calledmethods/Generics.java b/checker/tests/calledmethods/Generics.java index 3e642a7c03c..bfc75d63fec 100644 --- a/checker/tests/calledmethods/Generics.java +++ b/checker/tests/calledmethods/Generics.java @@ -1,55 +1,54 @@ -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.common.returnsreceiver.qual.*; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.common.returnsreceiver.qual.*; public class Generics { - static interface Symbol { + static interface Symbol { - boolean isStatic(); + boolean isStatic(); - void finalize(@CalledMethods("isStatic") Symbol this); - } + void finalize(@CalledMethods("isStatic") Symbol this); + } - static List<@CalledMethods("isStatic") Symbol> makeList(Symbol s) { - s.isStatic(); - ArrayList<@CalledMethods("isStatic") Symbol> l = new ArrayList<>(); - l.add(s); - return l; - } + static List<@CalledMethods("isStatic") Symbol> makeList(Symbol s) { + s.isStatic(); + ArrayList<@CalledMethods("isStatic") Symbol> l = new ArrayList<>(); + l.add(s); + return l; + } - static void useList() { - Symbol s = null; - for (Symbol t : makeList(s)) { - t.finalize(); - } + static void useList() { + Symbol s = null; + for (Symbol t : makeList(s)) { + t.finalize(); } - - // reduced from real-world code - private <@CalledMethods() T extends Symbol> T getMember(Class type, boolean b) { - if (b) { - T sym = getMember(type, !b); - if (sym != null && sym.isStatic()) { - return sym; - } - } else { - T sym = getMember(type, b); - if (sym != null) { - return sym; - } - } - return null; - } - - static Stream stringList() { - String s = "hi"; - // dummy method call - s.contains("h"); - // should infer type Stream<@CalledMethods() String> - return Arrays.asList(s).stream(); + } + + // reduced from real-world code + private <@CalledMethods() T extends Symbol> T getMember(Class type, boolean b) { + if (b) { + T sym = getMember(type, !b); + if (sym != null && sym.isStatic()) { + return sym; + } + } else { + T sym = getMember(type, b); + if (sym != null) { + return sym; + } } + return null; + } + + static Stream stringList() { + String s = "hi"; + // dummy method call + s.contains("h"); + // should infer type Stream<@CalledMethods() String> + return Arrays.asList(s).stream(); + } } diff --git a/checker/tests/calledmethods/Issue20.java b/checker/tests/calledmethods/Issue20.java index 77a95871ea2..c4b0ac213b7 100644 --- a/checker/tests/calledmethods/Issue20.java +++ b/checker/tests/calledmethods/Issue20.java @@ -4,33 +4,33 @@ public class Issue20 { - private boolean enableProtoAnnotations; - - @SuppressWarnings({"unchecked"}) - private T getProtoExtension( - E element, GeneratedExtension extension) { - // Use this method as the chokepoint for all field annotations processing, so we can - // toggle on/off annotations processing in one place. - if (!enableProtoAnnotations) { - return null; - } - return (T) element.getOptionFields().get(extension.getDescriptor()); + private boolean enableProtoAnnotations; + + @SuppressWarnings({"unchecked"}) + private T getProtoExtension( + E element, GeneratedExtension extension) { + // Use this method as the chokepoint for all field annotations processing, so we can + // toggle on/off annotations processing in one place. + if (!enableProtoAnnotations) { + return null; } + return (T) element.getOptionFields().get(extension.getDescriptor()); + } - // stubs of relevant classes - private class Message {} + // stubs of relevant classes + private class Message {} - private class ProtoElement { - public Map getOptionFields() { - return null; - } + private class ProtoElement { + public Map getOptionFields() { + return null; } + } - private class FieldDescriptor {} + private class FieldDescriptor {} - private class GeneratedExtension { - public FieldDescriptor getDescriptor() { - return null; - } + private class GeneratedExtension { + public FieldDescriptor getDescriptor() { + return null; } + } } diff --git a/checker/tests/calledmethods/Issue5402.java b/checker/tests/calledmethods/Issue5402.java index a891fd6806f..e66dd51cb90 100644 --- a/checker/tests/calledmethods/Issue5402.java +++ b/checker/tests/calledmethods/Issue5402.java @@ -9,62 +9,62 @@ public class Issue5402 {} class Issue5402_Ok1 { - public @This Issue5402_Ok1 bar() { - return this; - } + public @This Issue5402_Ok1 bar() { + return this; + } - public void baz(@CalledMethods("bar") Issue5402_Ok1 this) {} + public void baz(@CalledMethods("bar") Issue5402_Ok1 this) {} - public static void test() { - final Issue5402_Ok1 foo = new Issue5402_Ok1(); - foo.bar().baz(); // No error - } + public static void test() { + final Issue5402_Ok1 foo = new Issue5402_Ok1(); + foo.bar().baz(); // No error + } } class Issue5402_Ok2 { - public @This Issue5402_Ok2 bar() { - return this; - } + public @This Issue5402_Ok2 bar() { + return this; + } - @RequiresCalledMethods(value = "this", methods = "bar") - public void baz() {} + @RequiresCalledMethods(value = "this", methods = "bar") + public void baz() {} - public static void test() { - final Issue5402_Ok2 foo = new Issue5402_Ok2(); - final Issue5402_Ok2 foo1 = foo.bar(); - foo1.baz(); // No error - } + public static void test() { + final Issue5402_Ok2 foo = new Issue5402_Ok2(); + final Issue5402_Ok2 foo1 = foo.bar(); + foo1.baz(); // No error + } } class Issue5402_Ok3 { - @Deterministic - public @This Issue5402_Ok3 bar() { - return this; - } + @Deterministic + public @This Issue5402_Ok3 bar() { + return this; + } - @RequiresCalledMethods(value = "this", methods = "bar") - public void baz() {} + @RequiresCalledMethods(value = "this", methods = "bar") + public void baz() {} - public static void test() { - final Issue5402_Ok2 foo = new Issue5402_Ok2(); - final Issue5402_Ok2 foo1 = foo.bar(); - foo1.baz(); // No error - } + public static void test() { + final Issue5402_Ok2 foo = new Issue5402_Ok2(); + final Issue5402_Ok2 foo1 = foo.bar(); + foo1.baz(); // No error + } } class Issue5402_Bad { - public @This Issue5402_Bad bar() { - return this; - } + public @This Issue5402_Bad bar() { + return this; + } - @RequiresCalledMethods(value = "this", methods = "bar") - public void baz() {} + @RequiresCalledMethods(value = "this", methods = "bar") + public void baz() {} - public static void test() { - final Issue5402_Bad foo = new Issue5402_Bad(); - foo.bar().baz(); // Error - } + public static void test() { + final Issue5402_Bad foo = new Issue5402_Bad(); + foo.bar().baz(); // Error + } } diff --git a/checker/tests/calledmethods/Not.java b/checker/tests/calledmethods/Not.java index 9d91fbdd8fd..4cbe2c2011e 100644 --- a/checker/tests/calledmethods/Not.java +++ b/checker/tests/calledmethods/Not.java @@ -2,74 +2,74 @@ public class Not { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - void notA(@CalledMethodsPredicate("!a") Foo this) {} + void notA(@CalledMethodsPredicate("!a") Foo this) {} - void notB(@CalledMethodsPredicate("!b") Foo this) {} - } + void notB(@CalledMethodsPredicate("!b") Foo this) {} + } - void test1(Foo f) { - f.notA(); - f.notB(); - } + void test1(Foo f) { + f.notA(); + f.notB(); + } - void test2(Foo f) { - f.c(); - f.notA(); - f.notB(); - } + void test2(Foo f) { + f.c(); + f.notA(); + f.notB(); + } - void test3(Foo f) { - f.a(); - // :: error: method.invocation.invalid - f.notA(); - f.notB(); - } + void test3(Foo f) { + f.a(); + // :: error: method.invocation.invalid + f.notA(); + f.notB(); + } - void test4(Foo f) { - f.b(); - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test4(Foo f) { + f.b(); + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - // DEMONSTRATION OF UNSOUNDNESS - f.notA(); - } + void test6(Foo f) { + callA(f); + // DEMONSTRATION OF UNSOUNDNESS + f.notA(); + } - void test7(@CalledMethods("a") Foo f) { - // :: error: method.invocation.invalid - f.notA(); - } + void test7(@CalledMethods("a") Foo f) { + // :: error: method.invocation.invalid + f.notA(); + } - void test8(Foo f, boolean test) { - if (test) { - f.a(); - } else { - f.b(); - } - // DEMONSTRATION OF UNSOUNDNESS - f.notA(); + void test8(Foo f, boolean test) { + if (test) { + f.a(); + } else { + f.b(); } + // DEMONSTRATION OF UNSOUNDNESS + f.notA(); + } } diff --git a/checker/tests/calledmethods/Parens.java b/checker/tests/calledmethods/Parens.java index c86f80abec5..c8f4329b132 100644 --- a/checker/tests/calledmethods/Parens.java +++ b/checker/tests/calledmethods/Parens.java @@ -1,5 +1,5 @@ public class Parens { - public synchronized void incrementPushed(long[] pushed, int operationType) { - // ++(pushed[operationType]); - } + public synchronized void incrementPushed(long[] pushed, int operationType) { + // ++(pushed[operationType]); + } } diff --git a/checker/tests/calledmethods/Postconditions.java b/checker/tests/calledmethods/Postconditions.java index 983341aa949..39953087c36 100644 --- a/checker/tests/calledmethods/Postconditions.java +++ b/checker/tests/calledmethods/Postconditions.java @@ -2,131 +2,131 @@ /** Test for postcondition support via @EnsuresCalledMethods. */ public class Postconditions { - void build(@CalledMethods({"a", "b", "c"}) Postconditions this) {} - - void a() {} - - void b() {} - - void c() {} - - @EnsuresCalledMethods(value = "#1", methods = "b") - static void callB(Postconditions x) { - x.b(); + void build(@CalledMethods({"a", "b", "c"}) Postconditions this) {} + + void a() {} + + void b() {} + + void c() {} + + @EnsuresCalledMethods(value = "#1", methods = "b") + static void callB(Postconditions x) { + x.b(); + } + + @EnsuresCalledMethods(value = "#1", methods = "b") + // :: error: contracts.postcondition.not.satisfied + static void doesNotCallB(Postconditions x) {} + + @EnsuresCalledMethods( + value = "#1", + methods = {"b", "c"}) + static void callBAndC(Postconditions x) { + x.b(); + x.c(); + } + + static void allInOneMethod() { + Postconditions y = new Postconditions(); + y.a(); + y.b(); + y.c(); + y.build(); + } + + static void invokeCallB() { + Postconditions y = new Postconditions(); + y.a(); + callB(y); + y.c(); + y.build(); + } + + static void invokeCallBLast() { + Postconditions y = new Postconditions(); + y.a(); + y.c(); + callB(y); + y.build(); + } + + static void invokeCallBAndC() { + Postconditions y = new Postconditions(); + y.a(); + callBAndC(y); + y.build(); + } + + static void invokeCallBAndCWrong() { + Postconditions y = new Postconditions(); + callBAndC(y); + // :: error: finalizer.invocation.invalid + y.build(); + } + + @EnsuresCalledMethodsIf( + expression = "#1", + methods = {"a", "b", "c"}, + result = true) + static boolean ensuresABCIfTrue(Postconditions p, boolean b) { + if (b) { + p.a(); + p.b(); + p.c(); + return true; } - - @EnsuresCalledMethods(value = "#1", methods = "b") - // :: error: contracts.postcondition.not.satisfied - static void doesNotCallB(Postconditions x) {} - - @EnsuresCalledMethods( - value = "#1", - methods = {"b", "c"}) - static void callBAndC(Postconditions x) { - x.b(); - x.c(); + return false; + } + + static void testEnsuresCalledMethodsIf(Postconditions p, boolean b) { + if (ensuresABCIfTrue(p, b)) { + p.build(); + } else { + // :: error: finalizer.invocation.invalid + p.build(); } - - static void allInOneMethod() { - Postconditions y = new Postconditions(); - y.a(); - y.b(); - y.c(); - y.build(); + } + + @EnsuresCalledMethods(value = "#1", methods = "a") + static void callWithException(Postconditions p) { + try { + p.a(); + throw new java.io.IOException(); + } catch (java.io.IOException e) { } - - static void invokeCallB() { - Postconditions y = new Postconditions(); - y.a(); - callB(y); - y.c(); - y.build(); - } - - static void invokeCallBLast() { - Postconditions y = new Postconditions(); - y.a(); - y.c(); - callB(y); - y.build(); + } + + @EnsuresCalledMethods( + value = {"#1", "#2"}, + methods = "a") + static void callAOnBoth(Postconditions p1, Postconditions p2) { + p1.a(); + p2.a(); + } + + @EnsuresCalledMethods( + value = {"#1", "#2"}, + methods = "a") + static void callAOnBothCatchNPE(Postconditions p1, Postconditions p2) { + // postcondition is verified because the checker assumes NullPointerExceptions cannot occur + try { + p1.a(); + } catch (NullPointerException e) { } - - static void invokeCallBAndC() { - Postconditions y = new Postconditions(); - y.a(); - callBAndC(y); - y.build(); - } - - static void invokeCallBAndCWrong() { - Postconditions y = new Postconditions(); - callBAndC(y); - // :: error: finalizer.invocation.invalid - y.build(); - } - - @EnsuresCalledMethodsIf( - expression = "#1", - methods = {"a", "b", "c"}, - result = true) - static boolean ensuresABCIfTrue(Postconditions p, boolean b) { - if (b) { - p.a(); - p.b(); - p.c(); - return true; - } - return false; - } - - static void testEnsuresCalledMethodsIf(Postconditions p, boolean b) { - if (ensuresABCIfTrue(p, b)) { - p.build(); - } else { - // :: error: finalizer.invocation.invalid - p.build(); - } - } - - @EnsuresCalledMethods(value = "#1", methods = "a") - static void callWithException(Postconditions p) { - try { - p.a(); - throw new java.io.IOException(); - } catch (java.io.IOException e) { - } - } - - @EnsuresCalledMethods( - value = {"#1", "#2"}, - methods = "a") - static void callAOnBoth(Postconditions p1, Postconditions p2) { - p1.a(); - p2.a(); - } - - @EnsuresCalledMethods( - value = {"#1", "#2"}, - methods = "a") - static void callAOnBothCatchNPE(Postconditions p1, Postconditions p2) { - // postcondition is verified because the checker assumes NullPointerExceptions cannot occur - try { - p1.a(); - } catch (NullPointerException e) { - } - p2.a(); - } - - @EnsuresCalledMethods( - value = {"#1", "#2"}, - methods = "a") - static int callAOnBothFinallyNPE(Postconditions p1, Postconditions p2) { - // postcondition is verified because the checker assumes NullPointerExceptions cannot occur - try { - p1.a(); - } finally { - p2.a(); - return 0; - } + p2.a(); + } + + @EnsuresCalledMethods( + value = {"#1", "#2"}, + methods = "a") + static int callAOnBothFinallyNPE(Postconditions p1, Postconditions p2) { + // postcondition is verified because the checker assumes NullPointerExceptions cannot occur + try { + p1.a(); + } finally { + p2.a(); + return 0; } + } } diff --git a/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java b/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java index 77454b59bbc..dc9efd5aa91 100644 --- a/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java +++ b/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java @@ -1,34 +1,32 @@ -import org.checkerframework.checker.calledmethods.qual.*; - import java.io.Closeable; +import org.checkerframework.checker.calledmethods.qual.*; public class RequiresCalledMethodsRepeatable { - @RequiresCalledMethods(value = "#1", methods = "close") - @RequiresCalledMethods(value = "#2", methods = "close") - public void requires2(Closeable r1, Closeable r2) { - @CalledMethods("close") Closeable r3 = r1; - @CalledMethods("close") Closeable r4 = r2; - } + @RequiresCalledMethods(value = "#1", methods = "close") + @RequiresCalledMethods(value = "#2", methods = "close") + public void requires2(Closeable r1, Closeable r2) { + @CalledMethods("close") Closeable r3 = r1; + @CalledMethods("close") Closeable r4 = r2; + } - public void requires2Wrong(Closeable r1, Closeable r2) { - // ::error: (contracts.precondition.not.satisfied) - requires2(r1, r2); - } + public void requires2Wrong(Closeable r1, Closeable r2) { + // ::error: (contracts.precondition.not.satisfied) + requires2(r1, r2); + } - @RequiresCalledMethods(value = "#1", methods = "close") - @RequiresCalledMethods(value = "#2", methods = "close") - public void requires2Correct(Closeable r1, Closeable r2) { - requires2(r1, r2); - } + @RequiresCalledMethods(value = "#1", methods = "close") + @RequiresCalledMethods(value = "#2", methods = "close") + public void requires2Correct(Closeable r1, Closeable r2) { + requires2(r1, r2); + } - public static class Subclass extends RequiresCalledMethodsRepeatable { - @Override - public void requires2Correct(Closeable r1, Closeable r2) {} + public static class Subclass extends RequiresCalledMethodsRepeatable { + @Override + public void requires2Correct(Closeable r1, Closeable r2) {} - public void caller(Closeable r1, Closeable r2) { - requires2Correct( - r1, r2); // OK: we override requires2Correct() with a weaker precondition - } + public void caller(Closeable r1, Closeable r2) { + requires2Correct(r1, r2); // OK: we override requires2Correct() with a weaker precondition } + } } diff --git a/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java b/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java index 11a51ab0f1d..e5932f45998 100644 --- a/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java +++ b/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java @@ -1,26 +1,25 @@ -import org.checkerframework.checker.calledmethods.qual.RequiresCalledMethods; - import java.io.Closeable; import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.RequiresCalledMethods; public class RequiresCalledMethodsSubclass { - public static class Parent { - @RequiresCalledMethods(value = "#1", methods = "close") - public void method(Closeable x) throws IOException {} + public static class Parent { + @RequiresCalledMethods(value = "#1", methods = "close") + public void method(Closeable x) throws IOException {} - public void caller(Closeable x) throws IOException { - // ::error: (contracts.precondition.not.satisfied) - method(x); - } + public void caller(Closeable x) throws IOException { + // ::error: (contracts.precondition.not.satisfied) + method(x); } + } - public static class Subclass extends Parent { - @Override - public void method(Closeable x) throws IOException {} + public static class Subclass extends Parent { + @Override + public void method(Closeable x) throws IOException {} - public void caller(Closeable x) throws IOException { - method(x); // OK: we override method() with a weaker precondition - } + public void caller(Closeable x) throws IOException { + method(x); // OK: we override method() with a weaker precondition } + } } diff --git a/checker/tests/calledmethods/RequiresCalledMethodsTest.java b/checker/tests/calledmethods/RequiresCalledMethodsTest.java index 741194ae804..a83bd506cac 100644 --- a/checker/tests/calledmethods/RequiresCalledMethodsTest.java +++ b/checker/tests/calledmethods/RequiresCalledMethodsTest.java @@ -4,19 +4,19 @@ class RequiresCalledMethodsTest { - Object foo; + Object foo; - @RequiresCalledMethods(value = "this.foo", methods = "toString") - void afterFooToString() {} + @RequiresCalledMethods(value = "this.foo", methods = "toString") + void afterFooToString() {} - void test_ok() { - foo.toString(); - afterFooToString(); - } + void test_ok() { + foo.toString(); + afterFooToString(); + } - void test_bad() { - // foo.toString(); - // :: error: contracts.precondition.not.satisfied - afterFooToString(); - } + void test_bad() { + // foo.toString(); + // :: error: contracts.precondition.not.satisfied + afterFooToString(); + } } diff --git a/checker/tests/calledmethods/SimpleFluentInference.java b/checker/tests/calledmethods/SimpleFluentInference.java index ae17946aecd..7f5eb3fe5e5 100644 --- a/checker/tests/calledmethods/SimpleFluentInference.java +++ b/checker/tests/calledmethods/SimpleFluentInference.java @@ -3,61 +3,61 @@ /* Simple inference of a fluent builder */ public class SimpleFluentInference { - SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { - return this; - } - - SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { - return this; - } - - @This SimpleFluentInference a() { - return this; - } - - @This SimpleFluentInference b() { - return this; - } - - // intentionally does not have an @This annotation - SimpleFluentInference c() { - return new SimpleFluentInference(); - } - - static void doStuffCorrect() { - SimpleFluentInference s = new SimpleFluentInference().a().b().build(); - } - - static void doStuffWrong() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - // :: error: finalizer.invocation.invalid - .build(); - } - - static void doStuffRightWeak() { - SimpleFluentInference s = new SimpleFluentInference().a().weakbuild(); - } - - static void noReturnsReceiverAnno() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - .b() - .c() - // :: error: finalizer.invocation.invalid - .build(); - } - - static void fluentLoop() { - SimpleFluentInference s = new SimpleFluentInference().a(); - int i = 10; - while (i > 0) { + SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { + return this; + } + + SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { + return this; + } + + @This SimpleFluentInference a() { + return this; + } + + @This SimpleFluentInference b() { + return this; + } + + // intentionally does not have an @This annotation + SimpleFluentInference c() { + return new SimpleFluentInference(); + } + + static void doStuffCorrect() { + SimpleFluentInference s = new SimpleFluentInference().a().b().build(); + } + + static void doStuffWrong() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() // :: error: finalizer.invocation.invalid - s.b().build(); - i--; - s = new SimpleFluentInference(); - } - } + .build(); + } + + static void doStuffRightWeak() { + SimpleFluentInference s = new SimpleFluentInference().a().weakbuild(); + } + + static void noReturnsReceiverAnno() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + .b() + .c() + // :: error: finalizer.invocation.invalid + .build(); + } + + static void fluentLoop() { + SimpleFluentInference s = new SimpleFluentInference().a(); + int i = 10; + while (i > 0) { + // :: error: finalizer.invocation.invalid + s.b().build(); + i--; + s = new SimpleFluentInference(); + } + } } diff --git a/checker/tests/calledmethods/SimpleInference.java b/checker/tests/calledmethods/SimpleInference.java index 0f86b96de5b..7d8af81c903 100644 --- a/checker/tests/calledmethods/SimpleInference.java +++ b/checker/tests/calledmethods/SimpleInference.java @@ -2,19 +2,19 @@ /* The simplest inference test case Martin could think of */ public class SimpleInference { - void build(@CalledMethods({"a"}) SimpleInference this) {} + void build(@CalledMethods({"a"}) SimpleInference this) {} - void a() {} + void a() {} - static void doStuffCorrect() { - SimpleInference s = new SimpleInference(); - s.a(); - s.build(); - } + static void doStuffCorrect() { + SimpleInference s = new SimpleInference(); + s.a(); + s.build(); + } - static void doStuffWrong() { - SimpleInference s = new SimpleInference(); - // :: error: finalizer.invocation.invalid - s.build(); - } + static void doStuffWrong() { + SimpleInference s = new SimpleInference(); + // :: error: finalizer.invocation.invalid + s.build(); + } } diff --git a/checker/tests/calledmethods/SimpleInferenceMerge.java b/checker/tests/calledmethods/SimpleInferenceMerge.java index 601e34572f3..98b6d15ca41 100644 --- a/checker/tests/calledmethods/SimpleInferenceMerge.java +++ b/checker/tests/calledmethods/SimpleInferenceMerge.java @@ -2,37 +2,37 @@ /* The simplest inference test case Martin could think of */ public class SimpleInferenceMerge { - void build(@CalledMethods({"a", "b"}) SimpleInferenceMerge this) {} + void build(@CalledMethods({"a", "b"}) SimpleInferenceMerge this) {} - void a() {} + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - static void doStuffCorrectMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.a(); - s.c(); - } - s.build(); + static void doStuffCorrectMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.a(); + s.c(); } + s.build(); + } - static void doStuffWrongMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.c(); - } - // :: error: finalizer.invocation.invalid - s.build(); + static void doStuffWrongMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.c(); } + // :: error: finalizer.invocation.invalid + s.build(); + } } diff --git a/checker/tests/calledmethods/Subtyping.java b/checker/tests/calledmethods/Subtyping.java index 4d53e30f637..1364c4b33c8 100644 --- a/checker/tests/calledmethods/Subtyping.java +++ b/checker/tests/calledmethods/Subtyping.java @@ -2,73 +2,73 @@ // basic subtyping checks public class Subtyping { - @CalledMethods({}) Object top_top(@CalledMethods({}) Object o) { - return o; - } - - @CalledMethods({}) Object top_a(@CalledMethods({"a"}) Object o) { - return o; - } - - @CalledMethods({}) Object top_ab(@CalledMethods({"a", "b"}) Object o) { - return o; - } - - @CalledMethods({}) Object top_bot(@CalledMethodsBottom Object o) { - return o; - } - - @CalledMethods({"a"}) Object a_top(@CalledMethods({}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethods({"a"}) Object a_a(@CalledMethods({"a"}) Object o) { - return o; - } - - @CalledMethods({"a"}) Object a_ab(@CalledMethods({"a", "b"}) Object o) { - return o; - } - - @CalledMethods({"a"}) Object a_bot(@CalledMethodsBottom Object o) { - return o; - } - - @CalledMethods({"a", "b"}) Object ab_top(@CalledMethods({}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethods({"a", "b"}) Object ab_a(@CalledMethods({"a"}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethods({"a", "b"}) Object ab_ab(@CalledMethods({"a", "b"}) Object o) { - return o; - } - - @CalledMethods({"a", "b"}) Object ab_bot(@CalledMethodsBottom Object o) { - return o; - } - - @CalledMethodsBottom Object bot_top(@CalledMethods({}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethodsBottom Object bot_a(@CalledMethods({"a"}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethodsBottom Object bot_ab(@CalledMethods({"a", "b"}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethodsBottom Object bot_bot(@CalledMethodsBottom Object o) { - return o; - } + @CalledMethods({}) Object top_top(@CalledMethods({}) Object o) { + return o; + } + + @CalledMethods({}) Object top_a(@CalledMethods({"a"}) Object o) { + return o; + } + + @CalledMethods({}) Object top_ab(@CalledMethods({"a", "b"}) Object o) { + return o; + } + + @CalledMethods({}) Object top_bot(@CalledMethodsBottom Object o) { + return o; + } + + @CalledMethods({"a"}) Object a_top(@CalledMethods({}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethods({"a"}) Object a_a(@CalledMethods({"a"}) Object o) { + return o; + } + + @CalledMethods({"a"}) Object a_ab(@CalledMethods({"a", "b"}) Object o) { + return o; + } + + @CalledMethods({"a"}) Object a_bot(@CalledMethodsBottom Object o) { + return o; + } + + @CalledMethods({"a", "b"}) Object ab_top(@CalledMethods({}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethods({"a", "b"}) Object ab_a(@CalledMethods({"a"}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethods({"a", "b"}) Object ab_ab(@CalledMethods({"a", "b"}) Object o) { + return o; + } + + @CalledMethods({"a", "b"}) Object ab_bot(@CalledMethodsBottom Object o) { + return o; + } + + @CalledMethodsBottom Object bot_top(@CalledMethods({}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethodsBottom Object bot_a(@CalledMethods({"a"}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethodsBottom Object bot_ab(@CalledMethods({"a", "b"}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethodsBottom Object bot_bot(@CalledMethodsBottom Object o) { + return o; + } } diff --git a/checker/tests/calledmethods/UnparsablePredicate.java b/checker/tests/calledmethods/UnparsablePredicate.java index 78bf61d2bfe..c87456e0a11 100644 --- a/checker/tests/calledmethods/UnparsablePredicate.java +++ b/checker/tests/calledmethods/UnparsablePredicate.java @@ -2,49 +2,49 @@ public class UnparsablePredicate { - // :: error: predicate.invalid - void unclosedOpen(@CalledMethodsPredicate("(foo && bar") Object x) {} + // :: error: predicate.invalid + void unclosedOpen(@CalledMethodsPredicate("(foo && bar") Object x) {} - // :: error: predicate.invalid - void unopenedClose(@CalledMethodsPredicate("foo || bar)") Object x) {} + // :: error: predicate.invalid + void unopenedClose(@CalledMethodsPredicate("foo || bar)") Object x) {} - // :: error: predicate.invalid - void badKeywords1(@CalledMethodsPredicate("foo OR bar") Object x) {} + // :: error: predicate.invalid + void badKeywords1(@CalledMethodsPredicate("foo OR bar") Object x) {} - // :: error: predicate.invalid - void badKeywords2(@CalledMethodsPredicate("foo AND bar") Object x) {} + // :: error: predicate.invalid + void badKeywords2(@CalledMethodsPredicate("foo AND bar") Object x) {} - // These tests check that valid Java identifiers don't cause problems - // when evaluating predicates. Examples of identifiers from - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 + // These tests check that valid Java identifiers don't cause problems + // when evaluating predicates. Examples of identifiers from + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 - void jls0Example(@CalledMethodsPredicate("String") Object x) {} + void jls0Example(@CalledMethodsPredicate("String") Object x) {} - void callJls0Example(@CalledMethods("String") Object y) { - jls0Example(y); - } + void callJls0Example(@CalledMethods("String") Object y) { + jls0Example(y); + } - void jls1Example(@CalledMethodsPredicate("i3") Object x) {} + void jls1Example(@CalledMethodsPredicate("i3") Object x) {} - void callJls1Example(@CalledMethods("i3") Object y) { - jls1Example(y); - } + void callJls1Example(@CalledMethods("i3") Object y) { + jls1Example(y); + } - // TODO: support Unicode. SPEL, which we use to parse expressions, doesn't. - /* void jls2Example(@CalledMethodsPredicate("αρετη") Object x) { } - void callJls2Example(@CalledMethods("αρετη") Object y) { - jls2Example(y); - }*/ + // TODO: support Unicode. SPEL, which we use to parse expressions, doesn't. + /* void jls2Example(@CalledMethodsPredicate("αρετη") Object x) { } + void callJls2Example(@CalledMethods("αρετη") Object y) { + jls2Example(y); + }*/ - void jls3Example(@CalledMethodsPredicate("MAX_VALUE") Object x) {} + void jls3Example(@CalledMethodsPredicate("MAX_VALUE") Object x) {} - void callJls3Example(@CalledMethods("MAX_VALUE") Object y) { - jls3Example(y); - } + void callJls3Example(@CalledMethods("MAX_VALUE") Object y) { + jls3Example(y); + } - void jls4Example(@CalledMethodsPredicate("isLetterOrDigit") Object x) {} + void jls4Example(@CalledMethodsPredicate("isLetterOrDigit") Object x) {} - void callJls4Example(@CalledMethods("isLetterOrDigit") Object y) { - jls4Example(y); - } + void callJls4Example(@CalledMethods("isLetterOrDigit") Object y) { + jls4Example(y); + } } diff --git a/checker/tests/calledmethods/Xor.java b/checker/tests/calledmethods/Xor.java index af65d3a12f5..97e37e6d4d7 100644 --- a/checker/tests/calledmethods/Xor.java +++ b/checker/tests/calledmethods/Xor.java @@ -2,65 +2,65 @@ public class Xor { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - // SPEL doesn't support XOR directly (it represents exponentiation as ^ instead), - // so use a standard gate encoding - void aXorB(@CalledMethodsPredicate("(a || b) && !(a && b)") Foo this) {} - } + // SPEL doesn't support XOR directly (it represents exponentiation as ^ instead), + // so use a standard gate encoding + void aXorB(@CalledMethodsPredicate("(a || b) && !(a && b)") Foo this) {} + } - void test1(Foo f) { - // :: error: method.invocation.invalid - f.aXorB(); - } + void test1(Foo f) { + // :: error: method.invocation.invalid + f.aXorB(); + } - void test2(Foo f) { - f.c(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test2(Foo f) { + f.c(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void test3(Foo f) { - f.a(); - f.aXorB(); - } + void test3(Foo f) { + f.a(); + f.aXorB(); + } - void test4(Foo f) { - f.b(); - f.aXorB(); - } + void test4(Foo f) { + f.b(); + f.aXorB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - f.b(); - // DEMONSTRATION OF UNSOUNDNESS - f.aXorB(); - } + void test6(Foo f) { + callA(f); + f.b(); + // DEMONSTRATION OF UNSOUNDNESS + f.aXorB(); + } - void test7(@CalledMethods("a") Foo f) { - f.aXorB(); - } + void test7(@CalledMethods("a") Foo f) { + f.aXorB(); + } - void test8(Foo f) { - callA(f); - // THIS IS AN UNAVOIDABLE FALSE POSITIVE - // :: error: method.invocation.invalid - f.aXorB(); - } + void test8(Foo f) { + callA(f); + // THIS IS AN UNAVOIDABLE FALSE POSITIVE + // :: error: method.invocation.invalid + f.aXorB(); + } } diff --git a/checker/tests/command-line/issue618/TwoCheckers.java b/checker/tests/command-line/issue618/TwoCheckers.java index 33e446d6e77..0c188f3876c 100644 --- a/checker/tests/command-line/issue618/TwoCheckers.java +++ b/checker/tests/command-line/issue618/TwoCheckers.java @@ -23,10 +23,10 @@ public class TwoCheckers { - void client(String a) { - // :: error: (argument.type.incompatible) - requiresUntainted(a); - } + void client(String a) { + // :: error: (argument.type.incompatible) + requiresUntainted(a); + } - void requiresUntainted(@Untainted String b) {} + void requiresUntainted(@Untainted String b) {} } diff --git a/checker/tests/compilermsg/Basic.java b/checker/tests/compilermsg/Basic.java index fb503bc5a8b..e7362b99242 100644 --- a/checker/tests/compilermsg/Basic.java +++ b/checker/tests/compilermsg/Basic.java @@ -2,9 +2,9 @@ public class Basic { - void required(@CompilerMessageKey String in) {} + void required(@CompilerMessageKey String in) {} - void test() { - required("test.property"); - } + void test() { + required("test.property"); + } } diff --git a/checker/tests/compilermsg/Diamonds.java b/checker/tests/compilermsg/Diamonds.java index 836dd667d70..9da14e49194 100644 --- a/checker/tests/compilermsg/Diamonds.java +++ b/checker/tests/compilermsg/Diamonds.java @@ -1,19 +1,18 @@ -import org.checkerframework.checker.compilermsgs.qual.UnknownCompilerMessageKey; - import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import org.checkerframework.checker.compilermsgs.qual.UnknownCompilerMessageKey; public class Diamonds { - void method(List arrays) { - arrays = new ArrayList<>(new HashSet<>(arrays)); - arrays = newArrayList(new HashSet<>(arrays)); - arrays = new ArrayList<>(new HashSet<@UnknownCompilerMessageKey String>(arrays)); - } + void method(List arrays) { + arrays = new ArrayList<>(new HashSet<>(arrays)); + arrays = newArrayList(new HashSet<>(arrays)); + arrays = new ArrayList<>(new HashSet<@UnknownCompilerMessageKey String>(arrays)); + } - ArrayList newArrayList(Collection param) { - return new ArrayList<>(param); - } + ArrayList newArrayList(Collection param) { + return new ArrayList<>(param); + } } diff --git a/checker/tests/custom-alias/CustomAliasedAnnotations.java b/checker/tests/custom-alias/CustomAliasedAnnotations.java index 6ef76da0faa..dc2d92e1a00 100644 --- a/checker/tests/custom-alias/CustomAliasedAnnotations.java +++ b/checker/tests/custom-alias/CustomAliasedAnnotations.java @@ -2,28 +2,28 @@ public class CustomAliasedAnnotations { - void useNonNullAnnotations() { - // :: error: (assignment.type.incompatible) - @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; - // :: error: (assignment.type.incompatible) - @NonNull Object nn2 = null; - } + void useNonNullAnnotations() { + // :: error: (assignment.type.incompatible) + @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; + // :: error: (assignment.type.incompatible) + @NonNull Object nn2 = null; + } - void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations2(@Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations2(@Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - void setMutable1() {} + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + void setMutable1() {} - @Pure - // :: warning: (purity.deterministic.void.method) - void setMutable2() {} + @Pure + // :: warning: (purity.deterministic.void.method) + void setMutable2() {} } diff --git a/checker/tests/disbaruse-records/DisbarredClass.java b/checker/tests/disbaruse-records/DisbarredClass.java index e24cb6430d3..255084d17f6 100644 --- a/checker/tests/disbaruse-records/DisbarredClass.java +++ b/checker/tests/disbaruse-records/DisbarredClass.java @@ -2,40 +2,40 @@ class DisbarredClass { - @DisbarUse String barred; - String fine; - - DisbarredClass() {} - - @DisbarUse - DisbarredClass(String param) {} - - DisbarredClass(@DisbarUse Integer param) {} - - DisbarredClass(@DisbarUse Long param) { - // :: error: (disbar.use) - param = 7L; - // :: error: (disbar.use) - param.toString(); - } - - @DisbarUse - void disbarredMethod() {} - - void invalid() { - // :: error: (disbar.use) - disbarredMethod(); - // :: error: (disbar.use) - new DisbarredClass(""); - // :: error: (disbar.use) - barred = ""; - // :: error: (disbar.use) - int x = barred.length(); - } - - void valid() { - new DisbarredClass(); - fine = ""; - int x = fine.length(); - } + @DisbarUse String barred; + String fine; + + DisbarredClass() {} + + @DisbarUse + DisbarredClass(String param) {} + + DisbarredClass(@DisbarUse Integer param) {} + + DisbarredClass(@DisbarUse Long param) { + // :: error: (disbar.use) + param = 7L; + // :: error: (disbar.use) + param.toString(); + } + + @DisbarUse + void disbarredMethod() {} + + void invalid() { + // :: error: (disbar.use) + disbarredMethod(); + // :: error: (disbar.use) + new DisbarredClass(""); + // :: error: (disbar.use) + barred = ""; + // :: error: (disbar.use) + int x = barred.length(); + } + + void valid() { + new DisbarredClass(); + fine = ""; + int x = fine.length(); + } } diff --git a/checker/tests/disbaruse-records/DisbarredRecord.java b/checker/tests/disbaruse-records/DisbarredRecord.java index 77a890ac6a0..34c9cdae464 100644 --- a/checker/tests/disbaruse-records/DisbarredRecord.java +++ b/checker/tests/disbaruse-records/DisbarredRecord.java @@ -2,20 +2,20 @@ record DisbarredRecord(@DisbarUse String barred, String fine) { - DisbarredRecord { - // :: error: (disbar.use) - int x = barred.length(); - } + DisbarredRecord { + // :: error: (disbar.use) + int x = barred.length(); + } - void invalid() { - // :: error: (disbar.use) - barred(); - // :: error: (disbar.use) - int x = barred.length(); - } + void invalid() { + // :: error: (disbar.use) + barred(); + // :: error: (disbar.use) + int x = barred.length(); + } - void valid() { - fine(); - int x = fine.length(); - } + void valid() { + fine(); + int x = fine.length(); + } } diff --git a/checker/tests/disbaruse-records/DisbarredRecordByStubs.java b/checker/tests/disbaruse-records/DisbarredRecordByStubs.java index e31f975f08d..13730e728f1 100644 --- a/checker/tests/disbaruse-records/DisbarredRecordByStubs.java +++ b/checker/tests/disbaruse-records/DisbarredRecordByStubs.java @@ -1,19 +1,19 @@ record DisbarredRecordByStubs(String barred, String fine) { - DisbarredRecordByStubs { - // :: error: (disbar.use) - int x = barred.length(); - } + DisbarredRecordByStubs { + // :: error: (disbar.use) + int x = barred.length(); + } - void invalid() { - // :: error: (disbar.use) - barred(); - // :: error: (disbar.use) - int x = barred.length(); - } + void invalid() { + // :: error: (disbar.use) + barred(); + // :: error: (disbar.use) + int x = barred.length(); + } - void valid() { - fine(); - int x = fine.length(); - } + void valid() { + fine(); + int x = fine.length(); + } } diff --git a/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java b/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java index a3b0d9b698e..8df03d950aa 100644 --- a/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java +++ b/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java @@ -1,22 +1,22 @@ record DisbarredRecordByStubs2(String barred, String fine) { - // Annotation isn't copied to explicitly declared constructor parameters: - DisbarredRecordByStubs2(String barred, String fine) { - int x = barred.length(); - // :: error: (disbar.use) - this.barred = ""; - this.fine = fine; - } + // Annotation isn't copied to explicitly declared constructor parameters: + DisbarredRecordByStubs2(String barred, String fine) { + int x = barred.length(); + // :: error: (disbar.use) + this.barred = ""; + this.fine = fine; + } - void invalid() { - // :: error: (disbar.use) - barred(); - // :: error: (disbar.use) - int x = barred.length(); - } + void invalid() { + // :: error: (disbar.use) + barred(); + // :: error: (disbar.use) + int x = barred.length(); + } - void valid() { - fine(); - int x = fine.length(); - } + void valid() { + fine(); + int x = fine.length(); + } } diff --git a/checker/tests/fenum/CastsFenum.java b/checker/tests/fenum/CastsFenum.java index a3be27f799e..edc3c146623 100644 --- a/checker/tests/fenum/CastsFenum.java +++ b/checker/tests/fenum/CastsFenum.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.fenum.qual.Fenum; public class CastsFenum { - @Fenum("A") Object fa; + @Fenum("A") Object fa; - void m(Object p, @Fenum("A") Object a) { - fa = (Object) a; - // :: error: (assignment.type.incompatible) - fa = (Object) p; + void m(Object p, @Fenum("A") Object a) { + fa = (Object) a; + // :: error: (assignment.type.incompatible) + fa = (Object) p; - // TODO: How can we test the behavior for - // instanceof? It should be the same as for casts. - // if (p instanceof Object) { - } + // TODO: How can we test the behavior for + // instanceof? It should be the same as for casts. + // if (p instanceof Object) { + } } diff --git a/checker/tests/fenum/CatchFenumUnqualified.java b/checker/tests/fenum/CatchFenumUnqualified.java index cb54ce747c5..320770d03e5 100644 --- a/checker/tests/fenum/CatchFenumUnqualified.java +++ b/checker/tests/fenum/CatchFenumUnqualified.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.fenum.qual.Fenum; public class CatchFenumUnqualified { - void method() { - try { - } catch ( - // :: error: (exception.parameter.invalid) - @Fenum("A") RuntimeException e) { + void method() { + try { + } catch ( + // :: error: (exception.parameter.invalid) + @Fenum("A") RuntimeException e) { - } - try { - // :: error: (exception.parameter.invalid) - } catch (@Fenum("A") NullPointerException | @Fenum("A") ArrayIndexOutOfBoundsException e) { + } + try { + // :: error: (exception.parameter.invalid) + } catch (@Fenum("A") NullPointerException | @Fenum("A") ArrayIndexOutOfBoundsException e) { - } } + } } diff --git a/checker/tests/fenum/TestFlow.java b/checker/tests/fenum/TestFlow.java index 38e1ba3982a..23c849f0700 100644 --- a/checker/tests/fenum/TestFlow.java +++ b/checker/tests/fenum/TestFlow.java @@ -2,39 +2,39 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestFlow { - public final @Fenum("A") Object ACONST1 = new Object(); + public final @Fenum("A") Object ACONST1 = new Object(); - public final @Fenum("B") Object BCONST1 = new Object(); + public final @Fenum("B") Object BCONST1 = new Object(); } class FenumUser { - @Fenum("A") Object state1 = new TestFlow().ACONST1; + @Fenum("A") Object state1 = new TestFlow().ACONST1; - void foo(TestFlow t) { - // :: error: (method.invocation.invalid) - state1.hashCode(); - state1 = null; - state1.hashCode(); - m(); - // :: error: (method.invocation.invalid) - state1.hashCode(); + void foo(TestFlow t) { + // :: error: (method.invocation.invalid) + state1.hashCode(); + state1 = null; + state1.hashCode(); + m(); + // :: error: (method.invocation.invalid) + state1.hashCode(); - Object o = new Object(); - o = t.ACONST1; - methodA(o); - // :: error: (argument.type.incompatible) - methodB(o); + Object o = new Object(); + o = t.ACONST1; + methodA(o); + // :: error: (argument.type.incompatible) + methodB(o); - o = t.BCONST1; - // :: error: (argument.type.incompatible) - methodA(o); - methodB(o); - } + o = t.BCONST1; + // :: error: (argument.type.incompatible) + methodA(o); + methodB(o); + } - void m() {} + void m() {} - void methodA(@Fenum("A") Object a) {} + void methodA(@Fenum("A") Object a) {} - void methodB(@Fenum("B") Object a) {} + void methodB(@Fenum("B") Object a) {} } diff --git a/checker/tests/fenum/TestInstance.java b/checker/tests/fenum/TestInstance.java index 6f93172706e..01f4504fbd2 100644 --- a/checker/tests/fenum/TestInstance.java +++ b/checker/tests/fenum/TestInstance.java @@ -2,43 +2,43 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestInstance { - public final @Fenum("A") Object ACONST1 = new Object(); - public final @Fenum("A") Object ACONST2 = new Object(); - public final @Fenum("A") Object ACONST3 = new Object(); + public final @Fenum("A") Object ACONST1 = new Object(); + public final @Fenum("A") Object ACONST2 = new Object(); + public final @Fenum("A") Object ACONST3 = new Object(); - public final @Fenum("B") Object BCONST1 = new Object(); - public final @Fenum("B") Object BCONST2 = new Object(); - public final @Fenum("B") Object BCONST3 = new Object(); + public final @Fenum("B") Object BCONST1 = new Object(); + public final @Fenum("B") Object BCONST2 = new Object(); + public final @Fenum("B") Object BCONST3 = new Object(); } class FenumUserTestInstance { - @Fenum("A") Object state1 = new TestInstance().ACONST1; + @Fenum("A") Object state1 = new TestInstance().ACONST1; - // :: error: (assignment.type.incompatible) - @Fenum("B") Object state2 = new TestInstance().ACONST1; + // :: error: (assignment.type.incompatible) + @Fenum("B") Object state2 = new TestInstance().ACONST1; - void foo(TestInstance t) { - // :: error: (assignment.type.incompatible) - state1 = new Object(); + void foo(TestInstance t) { + // :: error: (assignment.type.incompatible) + state1 = new Object(); - state1 = t.ACONST2; - state1 = t.ACONST3; + state1 = t.ACONST2; + state1 = t.ACONST3; - // :: error: (assignment.type.incompatible) - state1 = t.BCONST1; + // :: error: (assignment.type.incompatible) + state1 = t.BCONST1; - // :: error: (method.invocation.invalid) - state1.hashCode(); - // :: error: (method.invocation.invalid) - t.ACONST1.hashCode(); + // :: error: (method.invocation.invalid) + state1.hashCode(); + // :: error: (method.invocation.invalid) + t.ACONST1.hashCode(); - // sanity check: unqualified instantiation and call work. - Object o = new Object(); - o.hashCode(); + // sanity check: unqualified instantiation and call work. + Object o = new Object(); + o.hashCode(); - if (t.ACONST1 == t.ACONST2) {} + if (t.ACONST1 == t.ACONST2) {} - // :: error: (binary.type.incompatible) - if (t.ACONST1 == t.BCONST2) {} - } + // :: error: (binary.type.incompatible) + if (t.ACONST1 == t.BCONST2) {} + } } diff --git a/checker/tests/fenum/TestPrimitive.java b/checker/tests/fenum/TestPrimitive.java index 49db9f7bdfd..40a20c348b4 100644 --- a/checker/tests/fenum/TestPrimitive.java +++ b/checker/tests/fenum/TestPrimitive.java @@ -2,45 +2,45 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestPrimitive { - public final @Fenum("A") int ACONST1 = 1; - public final @Fenum("A") int ACONST2 = 2; - public final @Fenum("A") int ACONST3 = 3; + public final @Fenum("A") int ACONST1 = 1; + public final @Fenum("A") int ACONST2 = 2; + public final @Fenum("A") int ACONST3 = 3; - public final @Fenum("B") int BCONST1 = 4; - public final @Fenum("B") int BCONST2 = 5; - public final @Fenum("B") int BCONST3 = 6; + public final @Fenum("B") int BCONST1 = 4; + public final @Fenum("B") int BCONST2 = 5; + public final @Fenum("B") int BCONST3 = 6; } class FenumUserTestPrimitive { - @Fenum("A") int state1 = new TestPrimitive().ACONST1; + @Fenum("A") int state1 = new TestPrimitive().ACONST1; - @Fenum("A") int state3 = this.state1; + @Fenum("A") int state3 = this.state1; - // :: error: (assignment.type.incompatible) - @Fenum("B") int state2 = new TestPrimitive().ACONST1; + // :: error: (assignment.type.incompatible) + @Fenum("B") int state2 = new TestPrimitive().ACONST1; - void foo(TestPrimitive t) { - // :: error: (assignment.type.incompatible) - state1 = 4; + void foo(TestPrimitive t) { + // :: error: (assignment.type.incompatible) + state1 = 4; - state1 = t.ACONST2; - state1 = t.ACONST3; + state1 = t.ACONST2; + state1 = t.ACONST3; - // :: error: (assignment.type.incompatible) - state1 = t.BCONST1; + // :: error: (assignment.type.incompatible) + state1 = t.BCONST1; - if (t.ACONST1 < t.ACONST2) { - // ok - } + if (t.ACONST1 < t.ACONST2) { + // ok + } - // :: error: (binary.type.incompatible) - if (t.ACONST1 < t.BCONST2) {} - // :: error: (binary.type.incompatible) - if (t.ACONST1 == t.BCONST2) {} + // :: error: (binary.type.incompatible) + if (t.ACONST1 < t.BCONST2) {} + // :: error: (binary.type.incompatible) + if (t.ACONST1 == t.BCONST2) {} - // :: error: (binary.type.incompatible) - if (t.ACONST1 < 5) {} - // :: error: (binary.type.incompatible) - if (t.ACONST1 == 5) {} - } + // :: error: (binary.type.incompatible) + if (t.ACONST1 < 5) {} + // :: error: (binary.type.incompatible) + if (t.ACONST1 == 5) {} + } } diff --git a/checker/tests/fenum/TestStatic.java b/checker/tests/fenum/TestStatic.java index e679856a157..19aa578eead 100644 --- a/checker/tests/fenum/TestStatic.java +++ b/checker/tests/fenum/TestStatic.java @@ -2,52 +2,52 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestStatic { - public static final @Fenum("A") int ACONST1 = 1; - public static final @Fenum("A") int ACONST2 = 2; - public static final @Fenum("A") int ACONST3 = 3; + public static final @Fenum("A") int ACONST1 = 1; + public static final @Fenum("A") int ACONST2 = 2; + public static final @Fenum("A") int ACONST3 = 3; - public static final @Fenum("B") int BCONST1 = 4; - public static final @Fenum("B") int BCONST2 = 5; - public static final @Fenum("B") int BCONST3 = 6; + public static final @Fenum("B") int BCONST1 = 4; + public static final @Fenum("B") int BCONST2 = 5; + public static final @Fenum("B") int BCONST3 = 6; } class FenumUserTestStatic { - @Fenum("A") int state1 = TestStatic.ACONST1; + @Fenum("A") int state1 = TestStatic.ACONST1; - // :: error: (assignment.type.incompatible) - @Fenum("B") int state2 = TestStatic.ACONST1; - - void bar(@Fenum("A") int p) {} - - void foo() { - // :: error: (assignment.type.incompatible) - state1 = 4; + // :: error: (assignment.type.incompatible) + @Fenum("B") int state2 = TestStatic.ACONST1; - state1 = TestStatic.ACONST2; - state1 = TestStatic.ACONST3; + void bar(@Fenum("A") int p) {} - state2 = TestStatic.BCONST3; - - // :: error: (assignment.type.incompatible) - state1 = TestStatic.BCONST1; + void foo() { + // :: error: (assignment.type.incompatible) + state1 = 4; - // :: error: (argument.type.incompatible) - bar(5); - bar(TestStatic.ACONST1); - // :: error: (argument.type.incompatible) - bar(TestStatic.BCONST1); - } + state1 = TestStatic.ACONST2; + state1 = TestStatic.ACONST3; - @SuppressWarnings("fenum") - void ignoreAll() { - state1 = 4; - bar(5); - } + state2 = TestStatic.BCONST3; - @SuppressWarnings("fenum:assignment.type.incompatible") - void ignoreOne() { - state1 = 4; - // :: error: (argument.type.incompatible) - bar(5); - } + // :: error: (assignment.type.incompatible) + state1 = TestStatic.BCONST1; + + // :: error: (argument.type.incompatible) + bar(5); + bar(TestStatic.ACONST1); + // :: error: (argument.type.incompatible) + bar(TestStatic.BCONST1); + } + + @SuppressWarnings("fenum") + void ignoreAll() { + state1 = 4; + bar(5); + } + + @SuppressWarnings("fenum:assignment.type.incompatible") + void ignoreOne() { + state1 = 4; + // :: error: (argument.type.incompatible) + bar(5); + } } diff --git a/checker/tests/fenum/TestSwitch.java b/checker/tests/fenum/TestSwitch.java index 8085c5a289b..a9a4c89ebd4 100644 --- a/checker/tests/fenum/TestSwitch.java +++ b/checker/tests/fenum/TestSwitch.java @@ -1,38 +1,38 @@ import org.checkerframework.checker.fenum.qual.Fenum; public class TestSwitch { - void m() { - @SuppressWarnings("fenum:assignment.type.incompatible") - @Fenum("TEST") final int annotated = 3; + void m() { + @SuppressWarnings("fenum:assignment.type.incompatible") + @Fenum("TEST") final int annotated = 3; - @SuppressWarnings("fenum:assignment.type.incompatible") - @Fenum("TEST") final int annotated2 = 6; + @SuppressWarnings("fenum:assignment.type.incompatible") + @Fenum("TEST") final int annotated2 = 6; - int plain = 9; // FenumUnqualified + int plain = 9; // FenumUnqualified - switch (plain) { - // :: error: (switch.type.incompatible) - case annotated: - default: - } + switch (plain) { + // :: error: (switch.type.incompatible) + case annotated: + default: + } - // un-annotated still working - switch (plain) { - case 1: - case 2: - default: - } + // un-annotated still working + switch (plain) { + case 1: + case 2: + default: + } - switch (annotated) { - // :: error: (switch.type.incompatible) - case 45: - default: - } + switch (annotated) { + // :: error: (switch.type.incompatible) + case 45: + default: + } - // annotated working - switch (annotated) { - case annotated2: - default: - } + // annotated working + switch (annotated) { + case annotated2: + default: } + } } diff --git a/checker/tests/fenum/TypeVariable.java b/checker/tests/fenum/TypeVariable.java index 75f5cc672c4..f1122830a98 100644 --- a/checker/tests/fenum/TypeVariable.java +++ b/checker/tests/fenum/TypeVariable.java @@ -2,11 +2,11 @@ * Make sure that unqualified type variables still work. */ public class TypeVariable { - X m() { - return null; - } + X m() { + return null; + } - Y bar() { - return null; - } + Y bar() { + return null; + } } diff --git a/checker/tests/fenum/UpperBoundsInByteCode.java b/checker/tests/fenum/UpperBoundsInByteCode.java index 680f61eb86b..babf913ea03 100644 --- a/checker/tests/fenum/UpperBoundsInByteCode.java +++ b/checker/tests/fenum/UpperBoundsInByteCode.java @@ -4,24 +4,24 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class UpperBoundsInByteCode { - UncheckedByteCode<@Fenum("Foo") String> foo; - UncheckedByteCode<@Fenum("Bar") Object> bar; + UncheckedByteCode<@Fenum("Foo") String> foo; + UncheckedByteCode<@Fenum("Bar") Object> bar; - void typeVarWithNonObjectUpperBound(@Fenum("A") int a) { - // :: error: (type.argument.type.incompatible) - UncheckedByteCode.methodWithTypeVarBoundedByNumber(a); - UncheckedByteCode.methodWithTypeVarBoundedByNumber(1); - } + void typeVarWithNonObjectUpperBound(@Fenum("A") int a) { + // :: error: (type.argument.type.incompatible) + UncheckedByteCode.methodWithTypeVarBoundedByNumber(a); + UncheckedByteCode.methodWithTypeVarBoundedByNumber(1); + } - void wildcardsInByteCode() { - UncheckedByteCode.unboundedWildcardParam(foo); - UncheckedByteCode.lowerboundedWildcardParam(bar); - // :: error: (argument.type.incompatible) - UncheckedByteCode.upperboundedWildcardParam(foo); - } + void wildcardsInByteCode() { + UncheckedByteCode.unboundedWildcardParam(foo); + UncheckedByteCode.lowerboundedWildcardParam(bar); + // :: error: (argument.type.incompatible) + UncheckedByteCode.upperboundedWildcardParam(foo); + } - // :: error: (type.argument.type.incompatible) - SourceCode<@Fenum("Foo") String> foo2; + // :: error: (type.argument.type.incompatible) + SourceCode<@Fenum("Foo") String> foo2; - class SourceCode {} + class SourceCode {} } diff --git a/checker/tests/fenumswing/FlowBreak.java b/checker/tests/fenumswing/FlowBreak.java index 20c51a69ca3..4dfeff49b7b 100644 --- a/checker/tests/fenumswing/FlowBreak.java +++ b/checker/tests/fenumswing/FlowBreak.java @@ -2,37 +2,37 @@ import org.checkerframework.checker.fenum.qual.SwingVerticalOrientation; public class FlowBreak { - static @SwingHorizontalOrientation Object CENTER; - static @SwingHorizontalOrientation Object LEFT; + static @SwingHorizontalOrientation Object CENTER; + static @SwingHorizontalOrientation Object LEFT; - boolean flag; + boolean flag; - @SwingHorizontalOrientation Object testInference() { - Object o; - // initially o is @FenumTop - o = null; - // o is @Bottom - while (flag) { - if (flag) { - o = CENTER; - // o is @SwingHorizontalOrientation - } else { - o = new @SwingVerticalOrientation Object(); - // o is @SwingVerticalOrientation - break; - } - // We can only come here from the then-branch, the else-branch is dead. - // Therefore, we only take the annotations at the end of the then-branch and ignore the - // results of the else-branch. - // Therefore, o is @SwingHorizontalOrientation and the following is valid: - @SwingHorizontalOrientation Object pla = o; - } - // Here we have to merge three paths: - // 1. The entry to the loop, if the condition is false [@Bottom] - // 2. The normal end of the loop body [@SwingHorizontalOrientation] - // 3. The path from the break to here [@SwingVerticalOrientation] - // Currently, the third path is ignored and we do not get this error message. - // :: error: (return.type.incompatible) - return o; + @SwingHorizontalOrientation Object testInference() { + Object o; + // initially o is @FenumTop + o = null; + // o is @Bottom + while (flag) { + if (flag) { + o = CENTER; + // o is @SwingHorizontalOrientation + } else { + o = new @SwingVerticalOrientation Object(); + // o is @SwingVerticalOrientation + break; + } + // We can only come here from the then-branch, the else-branch is dead. + // Therefore, we only take the annotations at the end of the then-branch and ignore the + // results of the else-branch. + // Therefore, o is @SwingHorizontalOrientation and the following is valid: + @SwingHorizontalOrientation Object pla = o; } + // Here we have to merge three paths: + // 1. The entry to the loop, if the condition is false [@Bottom] + // 2. The normal end of the loop body [@SwingHorizontalOrientation] + // 3. The path from the break to here [@SwingVerticalOrientation] + // Currently, the third path is ignored and we do not get this error message. + // :: error: (return.type.incompatible) + return o; + } } diff --git a/checker/tests/fenumswing/IdentityArrayListTest.java b/checker/tests/fenumswing/IdentityArrayListTest.java index 8b60b7c6c1b..3359da58102 100644 --- a/checker/tests/fenumswing/IdentityArrayListTest.java +++ b/checker/tests/fenumswing/IdentityArrayListTest.java @@ -1,37 +1,36 @@ -import org.checkerframework.checker.fenum.qual.FenumTop; - import java.util.Arrays; +import org.checkerframework.checker.fenum.qual.FenumTop; /* * This test case violates an assertion in the compiler. * It does not depend on the Fenum Checker, it breaks for any checker. */ public class IdentityArrayListTest { - // The type of the third argument to Arrays.copyOf should be: - // Class - // But the annotated JDK does not have annotations for the Fenum Checker. - @SuppressWarnings("argument.type.incompatible") - public T[] toArray(T[] a) { - // Warnings only with -Alint=cast:strict. - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - return (T[]) Arrays.copyOf(null, 0, a.getClass()); - } + // The type of the third argument to Arrays.copyOf should be: + // Class + // But the annotated JDK does not have annotations for the Fenum Checker. + @SuppressWarnings("argument.type.incompatible") + public T[] toArray(T[] a) { + // Warnings only with -Alint=cast:strict. + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + return (T[]) Arrays.copyOf(null, 0, a.getClass()); + } - public T[] toArray2(T[] a) { - wc(null, 0, new java.util.LinkedList()); - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - return (T[]) myCopyOf(null, 0, a.getClass()); - } + public T[] toArray2(T[] a) { + wc(null, 0, new java.util.LinkedList()); + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + return (T[]) myCopyOf(null, 0, a.getClass()); + } - public static T[] myCopyOf( - U[] original, int newLength, Class newType) { - return null; - } + public static T[] myCopyOf( + U[] original, int newLength, Class newType) { + return null; + } - public static T[] wc( - U[] original, int newLength, java.util.List arr) { - return null; - } + public static T[] wc( + U[] original, int newLength, java.util.List arr) { + return null; + } } diff --git a/checker/tests/fenumswing/PolyTest.java b/checker/tests/fenumswing/PolyTest.java index faa80d86f2a..eab1c7695e7 100644 --- a/checker/tests/fenumswing/PolyTest.java +++ b/checker/tests/fenumswing/PolyTest.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.fenum.qual.SwingCompassDirection; public class PolyTest { - public static boolean flag = false; + public static boolean flag = false; - @PolyFenum String merge( - @PolyFenum String a, - @PolyFenum String b, - @SwingCompassDirection String x, - @FenumBottom String bot) { - // Test lub with poly and a qualifier that isn't top or bottom. - String y = flag ? a : x; - // :: error: (assignment.type.incompatible) - @PolyFenum String y2 = flag ? a : x; + @PolyFenum String merge( + @PolyFenum String a, + @PolyFenum String b, + @SwingCompassDirection String x, + @FenumBottom String bot) { + // Test lub with poly and a qualifier that isn't top or bottom. + String y = flag ? a : x; + // :: error: (assignment.type.incompatible) + @PolyFenum String y2 = flag ? a : x; - // Test lub with poly and bottom. - // Test lub with poly and bottom. - @PolyFenum String z = flag ? a : bot; + // Test lub with poly and bottom. + // Test lub with poly and bottom. + @PolyFenum String z = flag ? a : bot; - // Test lub with two polys - return flag ? a : b; - } + // Test lub with two polys + return flag ? a : b; + } } diff --git a/checker/tests/fenumswing/SwingTest.java b/checker/tests/fenumswing/SwingTest.java index 69b808f8289..d946ac323ad 100644 --- a/checker/tests/fenumswing/SwingTest.java +++ b/checker/tests/fenumswing/SwingTest.java @@ -5,263 +5,263 @@ public class SwingTest { - static @SwingVerticalOrientation int BOTTOM; - static @SwingCompassDirection int NORTH; - - static @SwingHorizontalOrientation int CENTER; - static @SwingHorizontalOrientation int LEFT; - - static void m(@SwingVerticalOrientation int box) {} - - public static void main(String[] args) { - // ok - m(BOTTOM); - - // :: error: (argument.type.incompatible) - m(5); - - // :: error: (argument.type.incompatible) - m(NORTH); - } - - @SuppressWarnings("swingverticalorientation") - static void ignoreAll() { - m(NORTH); - - @SwingVerticalOrientation int b = 5; - } - - @SuppressWarnings("fenum:argument.type.incompatible") - static void ignoreOne() { - m(NORTH); - - // :: error: (assignment.type.incompatible) - @SwingVerticalOrientation int b = 5; - } - - void testNull() { - // This enum should only be used on ints, but I wanted to - // test how an Object enum and null interact. - @SwingVerticalOrientation Object box = null; - } - - @SwingVerticalOrientation Object testNullb() { - return null; - } - - @SwingVerticalOrientation Object testNullc() { - Object o = null; - return o; - } - - @SwingVerticalOrientation int testInference0() { - // :: error: (assignment.type.incompatible) - @SwingVerticalOrientation int boxint = 5; - int box = boxint; - return box; - } - - Object testInference1() { - Object o = new String(); - return o; - } - - @SwingVerticalOrientation int testInference2() { - int i = BOTTOM; - return i; - } - - @SwingVerticalOrientation Object testInference3() { - // :: error: (assignment.type.incompatible) - @SwingVerticalOrientation Object boxobj = new Object(); - Object obox = boxobj; - return obox; - } - - int testInference4() { - int aint = 5; - return aint; - } - - Object testInference5() { - Object o = null; - if (5 == 4) { - o = new Object(); - } - return o; - } - - Object testInference5b() { - Object o = null; - if (5 == 4) { - o = new Object(); - } else { - } - // the empty else branch actually covers a different code path! - return o; - } - - @SwingHorizontalOrientation int testInference5c() { - int o; - if (5 == 4) { - o = CENTER; - } else { - o = LEFT; - } - return o; - } - - int testInference6() { - int last = 0; - last += 1; - return last; - } - - @SwingBoxOrientation Object testInference7() { - Object o = new @SwingVerticalOrientation Object(); - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - // o = new @SwingVerticalOrientation Object(); - } - return o; - } - - @SwingBoxOrientation Object testInference7b() { - Object o; - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - } - return o; + static @SwingVerticalOrientation int BOTTOM; + static @SwingCompassDirection int NORTH; + + static @SwingHorizontalOrientation int CENTER; + static @SwingHorizontalOrientation int LEFT; + + static void m(@SwingVerticalOrientation int box) {} + + public static void main(String[] args) { + // ok + m(BOTTOM); + + // :: error: (argument.type.incompatible) + m(5); + + // :: error: (argument.type.incompatible) + m(NORTH); + } + + @SuppressWarnings("swingverticalorientation") + static void ignoreAll() { + m(NORTH); + + @SwingVerticalOrientation int b = 5; + } + + @SuppressWarnings("fenum:argument.type.incompatible") + static void ignoreOne() { + m(NORTH); + + // :: error: (assignment.type.incompatible) + @SwingVerticalOrientation int b = 5; + } + + void testNull() { + // This enum should only be used on ints, but I wanted to + // test how an Object enum and null interact. + @SwingVerticalOrientation Object box = null; + } + + @SwingVerticalOrientation Object testNullb() { + return null; + } + + @SwingVerticalOrientation Object testNullc() { + Object o = null; + return o; + } + + @SwingVerticalOrientation int testInference0() { + // :: error: (assignment.type.incompatible) + @SwingVerticalOrientation int boxint = 5; + int box = boxint; + return box; + } + + Object testInference1() { + Object o = new String(); + return o; + } + + @SwingVerticalOrientation int testInference2() { + int i = BOTTOM; + return i; + } + + @SwingVerticalOrientation Object testInference3() { + // :: error: (assignment.type.incompatible) + @SwingVerticalOrientation Object boxobj = new Object(); + Object obox = boxobj; + return obox; + } + + int testInference4() { + int aint = 5; + return aint; + } + + Object testInference5() { + Object o = null; + if (5 == 4) { + o = new Object(); } - - @SwingBoxOrientation Object testInference7c() { - Object o = null; - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - } - return o; + return o; + } + + Object testInference5b() { + Object o = null; + if (5 == 4) { + o = new Object(); + } else { } - - int s1 = 0; - int c = 3; - - void testInference8() { - int s2 = s1; - s1 = (s2 &= c); + // the empty else branch actually covers a different code path! + return o; + } + + @SwingHorizontalOrientation int testInference5c() { + int o; + if (5 == 4) { + o = CENTER; + } else { + o = LEFT; } - - void testInference8b() { - // :: error: (assignment.type.incompatible) - @SwingHorizontalOrientation int s2 = 5; - // :: error: (compound.assignment.type.incompatible) - s2 += 1; - - // :: error: (assignment.type.incompatible) - s1 = (s2 += s2); - - // :: error: (assignment.type.incompatible) - @SwingHorizontalOrientation String str = "abc"; - // yes, somebody in the Swing API really wrote this. - str += null; + return o; + } + + int testInference6() { + int last = 0; + last += 1; + return last; + } + + @SwingBoxOrientation Object testInference7() { + Object o = new @SwingVerticalOrientation Object(); + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + // o = new @SwingVerticalOrientation Object(); } - - boolean flag; - - Object testInference9() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } - } - // :: error: (return.type.incompatible) - return o; + return o; + } + + @SwingBoxOrientation Object testInference7b() { + Object o; + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); } - - @SwingBoxOrientation Object testInference9b() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } - } - return o; + return o; + } + + @SwingBoxOrientation Object testInference7c() { + Object o = null; + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); } - - @SwingHorizontalOrientation Object testInference9c() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } - } - // :: error: (return.type.incompatible) - return o; + return o; + } + + int s1 = 0; + int c = 3; + + void testInference8() { + int s2 = s1; + s1 = (s2 &= c); + } + + void testInference8b() { + // :: error: (assignment.type.incompatible) + @SwingHorizontalOrientation int s2 = 5; + // :: error: (compound.assignment.type.incompatible) + s2 += 1; + + // :: error: (assignment.type.incompatible) + s1 = (s2 += s2); + + // :: error: (assignment.type.incompatible) + @SwingHorizontalOrientation String str = "abc"; + // yes, somebody in the Swing API really wrote this. + str += null; + } + + boolean flag; + + Object testInference9() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } } - - @SwingVerticalOrientation Object testInference9d() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } - } - // :: error: (return.type.incompatible) - return o; + // :: error: (return.type.incompatible) + return o; + } + + @SwingBoxOrientation Object testInference9b() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } } - - @SwingBoxOrientation Object testInference9e() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - } - } - return o; + return o; + } + + @SwingHorizontalOrientation Object testInference9c() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } } - - /* TODO: Flow inference does not handle dead branches correctly. - * The return statement is only reachable with i being unqualified. - * However, the else-branch does not initialize the variable, leaving it - * at the @FenumTop initial value. - int testInferenceThrow() { - int i; - if ( 5==4 ) { - i = 5; - } else { - throw new RuntimeException("bla"); - } - return i; + // :: error: (return.type.incompatible) + return o; + } + + @SwingVerticalOrientation Object testInference9d() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } } - */ - - @SwingVerticalOrientation Object testDefaulting0() { - @org.checkerframework.framework.qual.DefaultQualifier(SwingVerticalOrientation.class) - // :: error: (assignment.type.incompatible) - Object o = new String(); - return o; + // :: error: (return.type.incompatible) + return o; + } + + @SwingBoxOrientation Object testInference9e() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + } } + return o; + } + + /* TODO: Flow inference does not handle dead branches correctly. + * The return statement is only reachable with i being unqualified. + * However, the else-branch does not initialize the variable, leaving it + * at the @FenumTop initial value. + int testInferenceThrow() { + int i; + if ( 5==4 ) { + i = 5; + } else { + throw new RuntimeException("bla"); + } + return i; + } + */ + + @SwingVerticalOrientation Object testDefaulting0() { + @org.checkerframework.framework.qual.DefaultQualifier(SwingVerticalOrientation.class) + // :: error: (assignment.type.incompatible) + Object o = new String(); + return o; + } } diff --git a/checker/tests/fenumswing/TypeVariable.java b/checker/tests/fenumswing/TypeVariable.java index 75f5cc672c4..f1122830a98 100644 --- a/checker/tests/fenumswing/TypeVariable.java +++ b/checker/tests/fenumswing/TypeVariable.java @@ -2,11 +2,11 @@ * Make sure that unqualified type variables still work. */ public class TypeVariable { - X m() { - return null; - } + X m() { + return null; + } - Y bar() { - return null; - } + Y bar() { + return null; + } } diff --git a/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java index 705681b6dee..42fd0131aa0 100644 --- a/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java @@ -1,18 +1,18 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - Object field; + Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - field = param.getInt(1); - field = param.getInteger(i); - field = param.identity("hello"); + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + field = param.getInt(1); + field = param.getInteger(i); + field = param.identity("hello"); - // String and Object are relevant types and must be annotated in bytecode - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - } + // String and Object are relevant types and must be annotated in bytecode + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + } } diff --git a/checker/tests/formatter/ConversionBasic.java b/checker/tests/formatter/ConversionBasic.java index d1b4413052f..5e4dc44c295 100644 --- a/checker/tests/formatter/ConversionBasic.java +++ b/checker/tests/formatter/ConversionBasic.java @@ -1,77 +1,76 @@ -import org.checkerframework.checker.formatter.qual.ConversionCategory; -import org.checkerframework.checker.formatter.qual.Format; - import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Formatter; +import org.checkerframework.checker.formatter.qual.ConversionCategory; +import org.checkerframework.checker.formatter.qual.Format; public class ConversionBasic { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - // test GENERAL, there is nothing we can do wrong - @Format({ConversionCategory.GENERAL}) String s = "%s"; - f.format("Suc-%s-ful", "cess"); - f.format("%b", 4); - f.format("%B", 7.5); - f.format("%h", new Date()); - f.format("%H", Integer.valueOf(4)); - f.format("%s", new ArrayList()); + // test GENERAL, there is nothing we can do wrong + @Format({ConversionCategory.GENERAL}) String s = "%s"; + f.format("Suc-%s-ful", "cess"); + f.format("%b", 4); + f.format("%B", 7.5); + f.format("%h", new Date()); + f.format("%H", Integer.valueOf(4)); + f.format("%s", new ArrayList()); - // test CHAR - @Format({ConversionCategory.CHAR}) String c = "%c"; - f.format("%c", 'c'); - f.format("%c", (byte) 67); - f.format("%c", (int) 67); - f.format("%c", Character.valueOf('c')); - f.format("%c", Byte.valueOf((byte) 67)); - f.format("%c", Short.valueOf((short) 67)); - f.format("%C", Integer.valueOf(67)); - // :: error: (argument.type.incompatible) - f.format("%c", 7.5); - // :: error: (argument.type.incompatible) - f.format("%C", "Value"); + // test CHAR + @Format({ConversionCategory.CHAR}) String c = "%c"; + f.format("%c", 'c'); + f.format("%c", (byte) 67); + f.format("%c", (int) 67); + f.format("%c", Character.valueOf('c')); + f.format("%c", Byte.valueOf((byte) 67)); + f.format("%c", Short.valueOf((short) 67)); + f.format("%C", Integer.valueOf(67)); + // :: error: (argument.type.incompatible) + f.format("%c", 7.5); + // :: error: (argument.type.incompatible) + f.format("%C", "Value"); - // test INT - @Format({ConversionCategory.INT}) String i = "%d"; - f.format("%d", (byte) 67); - f.format("%o", (short) 67); - f.format("%x", (int) 67); - f.format("%X", (long) 67); - f.format("%d", Long.valueOf(67)); - f.format("%d", BigInteger.ONE); - // :: error: (argument.type.incompatible) - f.format("%d", 'c'); - // :: error: (argument.type.incompatible) - f.format("%d", BigDecimal.ONE); + // test INT + @Format({ConversionCategory.INT}) String i = "%d"; + f.format("%d", (byte) 67); + f.format("%o", (short) 67); + f.format("%x", (int) 67); + f.format("%X", (long) 67); + f.format("%d", Long.valueOf(67)); + f.format("%d", BigInteger.ONE); + // :: error: (argument.type.incompatible) + f.format("%d", 'c'); + // :: error: (argument.type.incompatible) + f.format("%d", BigDecimal.ONE); - // test FLOAT - @Format({ConversionCategory.FLOAT}) String d = "%f"; - f.format("%e", (float) 67.1); - f.format("%E", (double) 67.3); - f.format("%f", Float.valueOf(42.5f)); - f.format("%g", Double.valueOf(42.5)); - f.format("%G", 67.87); - f.format("%a", BigDecimal.ONE); - // :: error: (argument.type.incompatible) - f.format("%A", 1325); - // :: error: (argument.type.incompatible) - f.format("%a", BigInteger.ONE); + // test FLOAT + @Format({ConversionCategory.FLOAT}) String d = "%f"; + f.format("%e", (float) 67.1); + f.format("%E", (double) 67.3); + f.format("%f", Float.valueOf(42.5f)); + f.format("%g", Double.valueOf(42.5)); + f.format("%G", 67.87); + f.format("%a", BigDecimal.ONE); + // :: error: (argument.type.incompatible) + f.format("%A", 1325); + // :: error: (argument.type.incompatible) + f.format("%a", BigInteger.ONE); - // test TIME - @Format({ConversionCategory.TIME}) String t = "%tT"; - f.format("%tD", new Date()); - f.format("%TM", (long) 32165456); - f.format("%TD", Calendar.getInstance()); - // :: error: (argument.type.incompatible) - f.format("%tD", 1321543512); - // :: error: (argument.type.incompatible) - f.format("%tD", new Object()); + // test TIME + @Format({ConversionCategory.TIME}) String t = "%tT"; + f.format("%tD", new Date()); + f.format("%TM", (long) 32165456); + f.format("%TD", Calendar.getInstance()); + // :: error: (argument.type.incompatible) + f.format("%tD", 1321543512); + // :: error: (argument.type.incompatible) + f.format("%tD", new Object()); - System.out.println(f.toString()); - f.close(); - } + System.out.println(f.toString()); + f.close(); + } } diff --git a/checker/tests/formatter/ConversionNull.java b/checker/tests/formatter/ConversionNull.java index 8ac77b13dde..2fe0bdcbe83 100644 --- a/checker/tests/formatter/ConversionNull.java +++ b/checker/tests/formatter/ConversionNull.java @@ -2,29 +2,29 @@ import java.util.Formatter; public class ConversionNull { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - f.format("%d %s", 0, null); - f.format("%s", (Object) null); + f.format("%d %s", 0, null); + f.format("%s", (Object) null); - f.format("%d %c", 0, null); - f.format("%c", (Character) null); - f.format("%c", (Object) null); + f.format("%d %c", 0, null); + f.format("%c", (Character) null); + f.format("%c", (Object) null); - f.format("%d %d", 0, null); - f.format("%d", (Integer) null); - f.format("%d", (Object) null); + f.format("%d %d", 0, null); + f.format("%d", (Integer) null); + f.format("%d", (Object) null); - f.format("%d %f", 0, null); - f.format("%f", (Float) null); - f.format("%f", (Object) null); + f.format("%d %f", 0, null); + f.format("%f", (Float) null); + f.format("%f", (Object) null); - f.format("%d %tD", 0, null); - f.format("%tD", (Date) null); - f.format("%tD", (Object) null); + f.format("%d %tD", 0, null); + f.format("%tD", (Date) null); + f.format("%tD", (Object) null); - System.out.println(f.toString()); - f.close(); - } + System.out.println(f.toString()); + f.close(); + } } diff --git a/checker/tests/formatter/ConversionNull2.java b/checker/tests/formatter/ConversionNull2.java index a546e0b4f6e..99d4ebf70d2 100644 --- a/checker/tests/formatter/ConversionNull2.java +++ b/checker/tests/formatter/ConversionNull2.java @@ -1,17 +1,16 @@ -import org.checkerframework.checker.formatter.qual.FormatMethod; - import java.util.Formatter; +import org.checkerframework.checker.formatter.qual.FormatMethod; public class ConversionNull2 { - void foo(Formatter f1, MyFormatter f2) { - f1.format("%d %c", 0, null); - f2.format("%d %c", 0, null); - } + void foo(Formatter f1, MyFormatter f2) { + f1.format("%d %c", 0, null); + f2.format("%d %c", 0, null); + } } class MyFormatter { - @FormatMethod - public MyFormatter format(String format, Object... args) { - return null; - } + @FormatMethod + public MyFormatter format(String format, Object... args) { + return null; + } } diff --git a/checker/tests/formatter/FlowFormatter.java b/checker/tests/formatter/FlowFormatter.java index a0350f625a4..9cf6ce0cafb 100644 --- a/checker/tests/formatter/FlowFormatter.java +++ b/checker/tests/formatter/FlowFormatter.java @@ -1,84 +1,83 @@ +import java.util.Date; +import java.util.Formatter; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; import org.checkerframework.checker.formatter.util.FormatUtil; import org.junit.Assert; -import java.util.Date; -import java.util.Formatter; - public class FlowFormatter { - public static String callUnqual(String u) { - return u; - } + public static String callUnqual(String u) { + return u; + } - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - String unqual = System.lineSeparator(); - String qual = "%s %d %f"; - String wrong = "%$s"; - callUnqual("%s"); - callUnqual(qual); - callUnqual(wrong); - callUnqual(null); - // :: error: (format.string.invalid) - f.format(null); - @Format({ConversionCategory.GENERAL}) String nullAssign = null; - // :: error: (format.string.invalid) - f.format(nullAssign, "string"); - if (false) { - nullAssign = "%s"; - } - f.format(nullAssign, "string"); - // :: error: (assignment.type.incompatible) - @Format({ConversionCategory.GENERAL}) String err0 = unqual; - // :: error: (assignment.type.incompatible) - @Format({ConversionCategory.GENERAL}) String err2 = "%$s"; - @Format({ConversionCategory.GENERAL}) String ok = "%s"; + String unqual = System.lineSeparator(); + String qual = "%s %d %f"; + String wrong = "%$s"; + callUnqual("%s"); + callUnqual(qual); + callUnqual(wrong); + callUnqual(null); + // :: error: (format.string.invalid) + f.format(null); + @Format({ConversionCategory.GENERAL}) String nullAssign = null; + // :: error: (format.string.invalid) + f.format(nullAssign, "string"); + if (false) { + nullAssign = "%s"; + } + f.format(nullAssign, "string"); + // :: error: (assignment.type.incompatible) + @Format({ConversionCategory.GENERAL}) String err0 = unqual; + // :: error: (assignment.type.incompatible) + @Format({ConversionCategory.GENERAL}) String err2 = "%$s"; + @Format({ConversionCategory.GENERAL}) String ok = "%s"; - String u = "%s" + " %" + "d"; - String v = FormatUtil.asFormat(u, ConversionCategory.GENERAL, ConversionCategory.INT); - f.format(u, "String", 1337); - // :: error: (argument.type.incompatible) - f.format(u, "String", 7.4); + String u = "%s" + " %" + "d"; + String v = FormatUtil.asFormat(u, ConversionCategory.GENERAL, ConversionCategory.INT); + f.format(u, "String", 1337); + // :: error: (argument.type.incompatible) + f.format(u, "String", 7.4); - try { - String l = FormatUtil.asFormat(u, ConversionCategory.FLOAT, ConversionCategory.INT); - Assert.fail("Expected Exception"); - } catch (Error e) { - } + try { + String l = FormatUtil.asFormat(u, ConversionCategory.FLOAT, ConversionCategory.INT); + Assert.fail("Expected Exception"); + } catch (Error e) { + } - String a = "Success: %s %d %f"; - f.format(a, "String", 1337, 7.5); + String a = "Success: %s %d %f"; + f.format(a, "String", 1337, 7.5); - String b = "Fail: %d"; - // :: error: (argument.type.incompatible) - f.format(b, "Wrong"); + String b = "Fail: %d"; + // :: error: (argument.type.incompatible) + f.format(b, "Wrong"); - @Format({ - ConversionCategory.GENERAL, - ConversionCategory.INT, - ConversionCategory.FLOAT, - ConversionCategory.CHAR - }) - String s = "Success: %s %d %f %c"; - f.format(s, "OK", 42, 3.14, 'c'); + @Format({ + ConversionCategory.GENERAL, + ConversionCategory.INT, + ConversionCategory.FLOAT, + ConversionCategory.CHAR + }) + String s = "Success: %s %d %f %c"; + f.format(s, "OK", 42, 3.14, 'c'); - @Format({ConversionCategory.GENERAL, ConversionCategory.INT, ConversionCategory.FLOAT}) String t = "Fail: %s %d %f"; - // :: error: (argument.type.incompatible) - f.format(t, "OK", "Wrong", 3.14); + @Format({ConversionCategory.GENERAL, ConversionCategory.INT, ConversionCategory.FLOAT}) String t = "Fail: %s %d %f"; + // :: error: (argument.type.incompatible) + f.format(t, "OK", "Wrong", 3.14); - call(f, "Success: %tM"); - // :: error: (argument.type.incompatible) - call(f, "Fail: %d"); + call(f, "Success: %tM"); + // :: error: (argument.type.incompatible) + call(f, "Fail: %d"); - System.out.println(f.toString()); - f.close(); - } + System.out.println(f.toString()); + f.close(); + } - public static void call(Formatter f, @Format({ConversionCategory.TIME}) String s) { - f.format(s, new Date()); - // :: error: (argument.type.incompatible) - f.format(s, "Wrong"); - } + public static void call(Formatter f, @Format({ConversionCategory.TIME}) String s) { + f.format(s, new Date()); + // :: error: (argument.type.incompatible) + f.format(s, "Wrong"); + } } diff --git a/checker/tests/formatter/FormatBasic.java b/checker/tests/formatter/FormatBasic.java index 1ff29f0ec54..97fb3e16f95 100644 --- a/checker/tests/formatter/FormatBasic.java +++ b/checker/tests/formatter/FormatBasic.java @@ -4,35 +4,33 @@ import java.util.GregorianCalendar; public class FormatBasic { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - f.format("String"); - f.format("String %20% %n"); - f.format("%% %s", "str"); - f.format("%4$2s %3$2s %2$2s %1$2s", "a", "b", "c", "d"); - f.format("e = %+10.4f", Math.E); - f.format("Amount gained or lost since last statement: $ %(,.2f", -6217.58); - f.format("Local time: %tT", Calendar.getInstance()); - f.format("Unable to open file '%1$s': %2$s", "food", "No such file or directory"); - f.format( - "Duke's Birthday: %1$tm %1$te,%1$tY", - new GregorianCalendar(1995, Calendar.MAY, 23)); - f.format("Duke's Birthday: %tm % { + static class MyClass implements Interface { - MyClass returnType; + MyClass returnType; - void method(MyClass result) { - result.returnType = Interface.method(this.returnType); - } + void method(MyClass result) { + result.returnType = Interface.method(this.returnType); } + } - interface Interface { - static > T2 method(T2 object) { - throw new RuntimeException(); - } + interface Interface { + static > T2 method(T2 object) { + throw new RuntimeException(); } + } } diff --git a/checker/tests/formatter/VarargsFormatter.java b/checker/tests/formatter/VarargsFormatter.java index cd612a25b21..25f21f603cb 100644 --- a/checker/tests/formatter/VarargsFormatter.java +++ b/checker/tests/formatter/VarargsFormatter.java @@ -2,59 +2,59 @@ import java.util.Formatter; public class VarargsFormatter { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - // vararg as parameter - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - f.format("Nothing", null); // equivalent to (Object[])null - f.format("Nothing", (Object[]) null); - f.format("%s", (Object[]) null); - f.format("%s %d %x", (Object[]) null); - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - f.format("%s %d %x", null); // equivalent to (Object[])null - // :: warning: (format.indirect.arguments) - f.format("%d", new Object[1]); - // :: warning: (format.indirect.arguments) - f.format("%s", new Object[2]); - // :: warning: (format.indirect.arguments) - f.format("%s %s", new Object[0]); - // :: warning: (format.indirect.arguments) - f.format("Empty", new Object[0]); - // :: warning: (format.indirect.arguments) - f.format("Empty", new Object[5]); - f.format("%s", new ArrayList()); - f.format("%d %s", 132, new Object[2]); - f.format("%s %d", new Object[2], 123); - // :: error: (format.missing.arguments) - f.format("%d %s %s", 132, new Object[2]); - // :: error: (argument.type.incompatible) - f.format("%d %d", new Object[2], 123); - // "error: (format.specifier.null)" could be a warning rather than an error, but that would - // require reasoning about the values in an array construction expression. - // :: error: (format.specifier.null) :: warning: (format.indirect.arguments) - f.format("%d %()); + f.format("%d %s", 132, new Object[2]); + f.format("%s %d", new Object[2], 123); + // :: error: (format.missing.arguments) + f.format("%d %s %s", 132, new Object[2]); + // :: error: (argument.type.incompatible) + f.format("%d %d", new Object[2], 123); + // "error: (format.specifier.null)" could be a warning rather than an error, but that would + // require reasoning about the values in an array construction expression. + // :: error: (format.specifier.null) :: warning: (format.indirect.arguments) + f.format("%d % { - public void executeAlwaysSafe(T arg); - } + private interface SafeFunctionalInterface { + public void executeAlwaysSafe(T arg); + } + + private interface UIFunctionalInterface { + @UIEffect + public void executeUI(T arg); + } + + @PolyUIType + private interface PolymorphicFunctionalInterface { + @PolyUIEffect + public void executePolymorphic(T arg); + } + + private static class LambdaRunner { + private final UIElement arg; - private interface UIFunctionalInterface { - @UIEffect - public void executeUI(T arg); + public LambdaRunner(UIElement arg) { + this.arg = arg; } - @PolyUIType - private interface PolymorphicFunctionalInterface { - @PolyUIEffect - public void executePolymorphic(T arg); + public void doSafe(SafeFunctionalInterface func) { + func.executeAlwaysSafe(this.arg); } - private static class LambdaRunner { - private final UIElement arg; - - public LambdaRunner(UIElement arg) { - this.arg = arg; - } - - public void doSafe(SafeFunctionalInterface func) { - func.executeAlwaysSafe(this.arg); - } - - @UIEffect - public void doUI(UIFunctionalInterface func) { - func.executeUI(this.arg); - } - - // Needs to be @UIEffect, because the functional interface method is @UIEffect - public void unsafeDoUI(UIFunctionalInterface func) { - // :: error: (call.invalid.ui) - func.executeUI(this.arg); - } - - public void doEither(@PolyUI PolymorphicFunctionalInterface func) { - // In a real program some intelligent dispatch could be done here to avoid running on UI - // thread unless needed. - arg.runOnUIThread( - new IAsyncUITask() { - final UIElement e2 = arg; - - @Override - public void doStuff() { // should inherit UI effect - func.executePolymorphic(e2); // should be okay - } - }); - } - - public void doUISafely(@UI PolymorphicFunctionalInterface func) { - // In a real program some intelligent dispatch could be done here to avoid running on UI - // thread unless needed. - arg.runOnUIThread( - new IAsyncUITask() { - final UIElement e2 = arg; - - @Override - public void doStuff() { // should inherit UI effect - func.executePolymorphic(e2); // should be okay - } - }); - } + @UIEffect + public void doUI(UIFunctionalInterface func) { + func.executeUI(this.arg); } - @PolyUIType - private static class PolymorphicLambdaRunner { - private final UIElement arg; + // Needs to be @UIEffect, because the functional interface method is @UIEffect + public void unsafeDoUI(UIFunctionalInterface func) { + // :: error: (call.invalid.ui) + func.executeUI(this.arg); + } - public PolymorphicLambdaRunner(UIElement arg) { - this.arg = arg; - } + public void doEither(@PolyUI PolymorphicFunctionalInterface func) { + // In a real program some intelligent dispatch could be done here to avoid running on UI + // thread unless needed. + arg.runOnUIThread( + new IAsyncUITask() { + final UIElement e2 = arg; - @PolyUIEffect - public void doEither(@PolyUI PolymorphicFunctionalInterface func) { - func.executePolymorphic(this.arg); - } + @Override + public void doStuff() { // should inherit UI effect + func.executePolymorphic(e2); // should be okay + } + }); } - public static void safeContextTestCases(UIElement elem) { - LambdaRunner runner = new LambdaRunner(elem); - runner.doSafe(e -> e.repaint()); - // :: error: (call.invalid.ui) - runner.doSafe(e -> e.dangerous()); // Not allowed in doSafe - // :: error: (call.invalid.ui) - runner.doUI(e -> e.repaint()); // Not allowed in safe context - // :: error: (call.invalid.ui) - runner.doUI(e -> e.dangerous()); // Not allowed in safe context - runner.doEither(e -> e.repaint()); - runner.doEither(e -> e.dangerous()); - runner.doUISafely(e -> e.dangerous()); - @AlwaysSafe PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); - safePolymorphicLambdaRunner.doEither(e -> e.repaint()); - // This next two are ok for this patch since the behavior is the same (no report) for - // lambdas as for annon classes. However, shouldn't this be (argument.type.incompatible) - // just because safePolymorphicLambdaRunner is not an @UI PolymorphicLambdaRunner ? Or, - // failing that (call.invalid.ui) since doEither is @PolyUIEffect ? - safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); - safePolymorphicLambdaRunner.doEither( - new @UI PolymorphicFunctionalInterface() { - public void executePolymorphic(UIElement arg) { - arg.dangerous(); - } - }); - @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither( - e -> e.repaint()); // Safe at runtime, but not by the type system! - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); - PolymorphicFunctionalInterface func1 = e -> e.repaint(); - // :: error: (assignment.type.incompatible) - PolymorphicFunctionalInterface func2 = e -> e.dangerous(); // Incompatible types! - PolymorphicFunctionalInterface func2p = - // :: error: (assignment.type.incompatible) - (new @UI PolymorphicFunctionalInterface() { - public void executePolymorphic(UIElement arg) { - arg.dangerous(); - } - }); - @UI PolymorphicFunctionalInterface func3 = e -> e.dangerous(); - safePolymorphicLambdaRunner.doEither(func1); - safePolymorphicLambdaRunner.doEither(func2); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(func1); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(func2); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(func3); - } + public void doUISafely(@UI PolymorphicFunctionalInterface func) { + // In a real program some intelligent dispatch could be done here to avoid running on UI + // thread unless needed. + arg.runOnUIThread( + new IAsyncUITask() { + final UIElement e2 = arg; - @UIEffect - public static void uiContextTestCases(UIElement elem) { - LambdaRunner runner = new LambdaRunner(elem); - // :: error: (call.invalid.ui) - runner.doSafe(e -> e.dangerous()); - runner.doUI(e -> e.repaint()); - runner.doUI(e -> e.dangerous()); - PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); - // No error, why? :: error: (argument.type.incompatible) - safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); - @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); - uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); + @Override + public void doStuff() { // should inherit UI effect + func.executePolymorphic(e2); // should be okay + } + }); } + } - public @UI PolymorphicFunctionalInterface returnLambdasTest1() { - return e -> e.dangerous(); - } + @PolyUIType + private static class PolymorphicLambdaRunner { + private final UIElement arg; - // This should be an error without an @UI annotation on the return type. No? - public PolymorphicFunctionalInterface returnLambdasTest2() { - // :: error: (return.type.incompatible) - return e -> { - e.dangerous(); - }; + public PolymorphicLambdaRunner(UIElement arg) { + this.arg = arg; } - // Just to check - public PolymorphicFunctionalInterface returnLambdasTest3() { - // :: error: (return.type.incompatible) - return (new @UI PolymorphicFunctionalInterface() { - public void executePolymorphic(UIElement arg) { - arg.dangerous(); - } - }); + @PolyUIEffect + public void doEither(@PolyUI PolymorphicFunctionalInterface func) { + func.executePolymorphic(this.arg); } + } + + public static void safeContextTestCases(UIElement elem) { + LambdaRunner runner = new LambdaRunner(elem); + runner.doSafe(e -> e.repaint()); + // :: error: (call.invalid.ui) + runner.doSafe(e -> e.dangerous()); // Not allowed in doSafe + // :: error: (call.invalid.ui) + runner.doUI(e -> e.repaint()); // Not allowed in safe context + // :: error: (call.invalid.ui) + runner.doUI(e -> e.dangerous()); // Not allowed in safe context + runner.doEither(e -> e.repaint()); + runner.doEither(e -> e.dangerous()); + runner.doUISafely(e -> e.dangerous()); + @AlwaysSafe PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); + safePolymorphicLambdaRunner.doEither(e -> e.repaint()); + // This next two are ok for this patch since the behavior is the same (no report) for + // lambdas as for annon classes. However, shouldn't this be (argument.type.incompatible) + // just because safePolymorphicLambdaRunner is not an @UI PolymorphicLambdaRunner ? Or, + // failing that (call.invalid.ui) since doEither is @PolyUIEffect ? + safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); + safePolymorphicLambdaRunner.doEither( + new @UI PolymorphicFunctionalInterface() { + public void executePolymorphic(UIElement arg) { + arg.dangerous(); + } + }); + @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither( + e -> e.repaint()); // Safe at runtime, but not by the type system! + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); + PolymorphicFunctionalInterface func1 = e -> e.repaint(); + // :: error: (assignment.type.incompatible) + PolymorphicFunctionalInterface func2 = e -> e.dangerous(); // Incompatible types! + PolymorphicFunctionalInterface func2p = + // :: error: (assignment.type.incompatible) + (new @UI PolymorphicFunctionalInterface() { + public void executePolymorphic(UIElement arg) { + arg.dangerous(); + } + }); + @UI PolymorphicFunctionalInterface func3 = e -> e.dangerous(); + safePolymorphicLambdaRunner.doEither(func1); + safePolymorphicLambdaRunner.doEither(func2); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(func1); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(func2); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(func3); + } + + @UIEffect + public static void uiContextTestCases(UIElement elem) { + LambdaRunner runner = new LambdaRunner(elem); + // :: error: (call.invalid.ui) + runner.doSafe(e -> e.dangerous()); + runner.doUI(e -> e.repaint()); + runner.doUI(e -> e.dangerous()); + PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); + // No error, why? :: error: (argument.type.incompatible) + safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); + @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); + uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); + } + + public @UI PolymorphicFunctionalInterface returnLambdasTest1() { + return e -> e.dangerous(); + } + + // This should be an error without an @UI annotation on the return type. No? + public PolymorphicFunctionalInterface returnLambdasTest2() { + // :: error: (return.type.incompatible) + return e -> { + e.dangerous(); + }; + } + + // Just to check + public PolymorphicFunctionalInterface returnLambdasTest3() { + // :: error: (return.type.incompatible) + return (new @UI PolymorphicFunctionalInterface() { + public void executePolymorphic(UIElement arg) { + arg.dangerous(); + } + }); + } } diff --git a/checker/tests/guieffect/MouseTest.java b/checker/tests/guieffect/MouseTest.java index 595d80012a3..9ab979cb75b 100644 --- a/checker/tests/guieffect/MouseTest.java +++ b/checker/tests/guieffect/MouseTest.java @@ -1,14 +1,13 @@ -import org.checkerframework.checker.guieffect.qual.UIType; - import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import org.checkerframework.checker.guieffect.qual.UIType; // Test the stub file handling @UIType public class MouseTest extends MouseAdapter { - @Override - public void mouseEntered(MouseEvent arg0) { - IAsyncUITask t = null; - t.doStuff(); - } + @Override + public void mouseEntered(MouseEvent arg0) { + IAsyncUITask t = null; + t.doStuff(); + } } diff --git a/checker/tests/guieffect/NoAnnotationsTest.java b/checker/tests/guieffect/NoAnnotationsTest.java index 5c6c45a32ad..1a3c8a13aee 100644 --- a/checker/tests/guieffect/NoAnnotationsTest.java +++ b/checker/tests/guieffect/NoAnnotationsTest.java @@ -1,3 +1,3 @@ public class NoAnnotationsTest { - public boolean b; + public boolean b; } diff --git a/checker/tests/guieffect/SafeParent.java b/checker/tests/guieffect/SafeParent.java index 077003e4ecc..06015b2043c 100644 --- a/checker/tests/guieffect/SafeParent.java +++ b/checker/tests/guieffect/SafeParent.java @@ -1,6 +1,6 @@ import org.checkerframework.checker.guieffect.qual.SafeEffect; public class SafeParent { - @SafeEffect - void m() {} + @SafeEffect + void m() {} } diff --git a/checker/tests/guieffect/Specialization.java b/checker/tests/guieffect/Specialization.java index bfe794e4e5e..ea68d415cd8 100644 --- a/checker/tests/guieffect/Specialization.java +++ b/checker/tests/guieffect/Specialization.java @@ -2,49 +2,49 @@ public class Specialization { - @PolyUIType - public static interface I { - @PolyUIEffect - public void m(); - } - - public static void reqSafe(@AlwaysSafe I i) {} - - @UIEffect - public static void reqUI(@UI I i) {} - - @PolyUIType - public static interface Doer { - - public void doStuff(@PolyUI Doer this, @PolyUI I i); - } - - @AlwaysSafe public static class SafeDoer implements @AlwaysSafe Doer { - // :: error: (override.param.invalid) - public void doStuff(@AlwaysSafe SafeDoer this, @AlwaysSafe I i) {} - } - - public void q(@AlwaysSafe Doer doer, @UI I i) { - doer.doStuff(i); - } - - public static void main(String[] args) { - - @AlwaysSafe Doer d = - new @AlwaysSafe Doer() { - @SafeEffect - // :: error: (override.param.invalid) - public void doStuff(@AlwaysSafe I i) { - reqSafe(i); - } - }; - @UI I ui = - new @UI I() { - public void m() { - reqUI(null); - } - }; - Specialization q = new Specialization(); - q.q(d, ui); - } + @PolyUIType + public static interface I { + @PolyUIEffect + public void m(); + } + + public static void reqSafe(@AlwaysSafe I i) {} + + @UIEffect + public static void reqUI(@UI I i) {} + + @PolyUIType + public static interface Doer { + + public void doStuff(@PolyUI Doer this, @PolyUI I i); + } + + @AlwaysSafe public static class SafeDoer implements @AlwaysSafe Doer { + // :: error: (override.param.invalid) + public void doStuff(@AlwaysSafe SafeDoer this, @AlwaysSafe I i) {} + } + + public void q(@AlwaysSafe Doer doer, @UI I i) { + doer.doStuff(i); + } + + public static void main(String[] args) { + + @AlwaysSafe Doer d = + new @AlwaysSafe Doer() { + @SafeEffect + // :: error: (override.param.invalid) + public void doStuff(@AlwaysSafe I i) { + reqSafe(i); + } + }; + @UI I ui = + new @UI I() { + public void m() { + reqUI(null); + } + }; + Specialization q = new Specialization(); + q.q(d, ui); + } } diff --git a/checker/tests/guieffect/TestProgram.java b/checker/tests/guieffect/TestProgram.java index 1604681260d..a0904d2b77f 100644 --- a/checker/tests/guieffect/TestProgram.java +++ b/checker/tests/guieffect/TestProgram.java @@ -1,81 +1,80 @@ import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.UI; import org.checkerframework.checker.guieffect.qual.UIEffect; - import packagetests.SafeByDecl; import packagetests.UIByPackageDecl; public class TestProgram { - public void nonUIStuff( - final UIElement e, - final GenericTaskUIConsumer uicons, - final GenericTaskSafeConsumer safecons) { - // :: error: (call.invalid.ui) - e.dangerous(); // should be bad - e.runOnUIThread( - new IAsyncUITask() { - final UIElement e2 = e; + public void nonUIStuff( + final UIElement e, + final GenericTaskUIConsumer uicons, + final GenericTaskSafeConsumer safecons) { + // :: error: (call.invalid.ui) + e.dangerous(); // should be bad + e.runOnUIThread( + new IAsyncUITask() { + final UIElement e2 = e; - @Override - public void doStuff() { // should inherit UI effect - e2.dangerous(); // should be okay - } - }); - uicons.runAsync( - new @UI IGenericTask() { - final UIElement e2 = e; + @Override + public void doStuff() { // should inherit UI effect + e2.dangerous(); // should be okay + } + }); + uicons.runAsync( + new @UI IGenericTask() { + final UIElement e2 = e; - @Override - public void doGenericStuff() { // Should be inst. w/ @UI eff. - e2.dangerous(); // should be okay - } - }); - safecons.runAsync( - new @AlwaysSafe IGenericTask() { - final UIElement e2 = e; + @Override + public void doGenericStuff() { // Should be inst. w/ @UI eff. + e2.dangerous(); // should be okay + } + }); + safecons.runAsync( + new @AlwaysSafe IGenericTask() { + final UIElement e2 = e; - @Override - public void doGenericStuff() { // Should be inst. w/ @AlwaysSafe - // :: error: (call.invalid.ui) - e2.dangerous(); // should be an error - safecons.runAsync(this); // Should be okay, this:@AlwaysSafe - } - }); - safecons.runAsync( - // :: error: (argument.type.incompatible) - new @UI IGenericTask() { - final UIElement e2 = e; + @Override + public void doGenericStuff() { // Should be inst. w/ @AlwaysSafe + // :: error: (call.invalid.ui) + e2.dangerous(); // should be an error + safecons.runAsync(this); // Should be okay, this:@AlwaysSafe + } + }); + safecons.runAsync( + // :: error: (argument.type.incompatible) + new @UI IGenericTask() { + final UIElement e2 = e; - @Override - public void doGenericStuff() { // Should be inst. w/ @UI - e2.dangerous(); // should be ok - // :: error: (argument.type.incompatible) - safecons.runAsync(this); // Should be error, this:@UI - } - }); - // Test that the package annotation works - // :: error: (call.invalid.ui) - UIByPackageDecl.implicitlyUI(); - // Test that @SafeType works: SafeByDecl is inside a @UIPackage - SafeByDecl.safeByTypeDespiteUIPackage(); - safecons.runAsync( - // :: error: (argument.type.incompatible) - new IGenericTask() { - @Override - public void doGenericStuff() { - // Safe here due to anonymous inner class effect inference, but will trigger - // an error above due to safecons.runAsync not taking an @UI IGenericTask. - UIByPackageDecl.implicitlyUI(); - } - }); - safecons.runAsync( - new IGenericTask() { - @Override - @UIEffect - // :: error: (override.effect.invalid.nonui) - public void doGenericStuff() { - UIByPackageDecl.implicitlyUI(); - } - }); - } + @Override + public void doGenericStuff() { // Should be inst. w/ @UI + e2.dangerous(); // should be ok + // :: error: (argument.type.incompatible) + safecons.runAsync(this); // Should be error, this:@UI + } + }); + // Test that the package annotation works + // :: error: (call.invalid.ui) + UIByPackageDecl.implicitlyUI(); + // Test that @SafeType works: SafeByDecl is inside a @UIPackage + SafeByDecl.safeByTypeDespiteUIPackage(); + safecons.runAsync( + // :: error: (argument.type.incompatible) + new IGenericTask() { + @Override + public void doGenericStuff() { + // Safe here due to anonymous inner class effect inference, but will trigger + // an error above due to safecons.runAsync not taking an @UI IGenericTask. + UIByPackageDecl.implicitlyUI(); + } + }); + safecons.runAsync( + new IGenericTask() { + @Override + @UIEffect + // :: error: (override.effect.invalid.nonui) + public void doGenericStuff() { + UIByPackageDecl.implicitlyUI(); + } + }); + } } diff --git a/checker/tests/guieffect/ThrowCatchTest.java b/checker/tests/guieffect/ThrowCatchTest.java index 06f68544099..574ffa28fba 100644 --- a/checker/tests/guieffect/ThrowCatchTest.java +++ b/checker/tests/guieffect/ThrowCatchTest.java @@ -1,121 +1,120 @@ +import java.util.List; import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.PolyUIType; import org.checkerframework.checker.guieffect.qual.UI; -import java.util.List; - public class ThrowCatchTest { - List ooo; + List ooo; - // :: error: (type.invalid.annotations.on.use) - List iii; + // :: error: (type.invalid.annotations.on.use) + List iii; - class Inner {} + class Inner {} - boolean flag = true; + boolean flag = true; - // Type var test - void throwTypeVarUI1(E ex1, @UI E ex2) throws PolyUIException { - if (flag) { - // :: error: (throw.type.invalid) - throw ex1; - } - // :: error: (throw.type.invalid) - throw ex2; + // Type var test + void throwTypeVarUI1(E ex1, @UI E ex2) throws PolyUIException { + if (flag) { + // :: error: (throw.type.invalid) + throw ex1; } - - <@UI E extends @UI PolyUIException> void throwTypeVarUI2(E ex1) throws PolyUIException { - // :: error: (throw.type.invalid) - throw ex1; + // :: error: (throw.type.invalid) + throw ex2; + } + + <@UI E extends @UI PolyUIException> void throwTypeVarUI2(E ex1) throws PolyUIException { + // :: error: (throw.type.invalid) + throw ex1; + } + + void throwTypeVarAlwaysSafe1(E ex1, @AlwaysSafe E ex2) + throws PolyUIException { + if (flag) { + throw ex1; } + throw ex2; + } - void throwTypeVarAlwaysSafe1(E ex1, @AlwaysSafe E ex2) - throws PolyUIException { - if (flag) { - throw ex1; - } - throw ex2; + <@AlwaysSafe E extends PolyUIException> void throwTypeVarAlwaysSafe2(E ex1, @AlwaysSafe E ex2) + throws PolyUIException { + if (flag) { + throw ex1; } - - <@AlwaysSafe E extends PolyUIException> void throwTypeVarAlwaysSafe2(E ex1, @AlwaysSafe E ex2) - throws PolyUIException { - if (flag) { - throw ex1; - } - throw ex2; + throw ex2; + } + + <@AlwaysSafe E extends @UI PolyUIException> void throwTypeVarMixed(E ex1, @AlwaysSafe E ex2) + throws PolyUIException { + if (flag) { + // :: error: (throw.type.invalid) + throw ex1; } - - <@AlwaysSafe E extends @UI PolyUIException> void throwTypeVarMixed(E ex1, @AlwaysSafe E ex2) - throws PolyUIException { - if (flag) { - // :: error: (throw.type.invalid) - throw ex1; - } - throw ex2; + throw ex2; + } + + // Wildcards + void throwWildcard( + List ui, + List alwaysSafe) + throws PolyUIException { + if (flag) { + throw ui.get(0); } + throw alwaysSafe.get(0); + } - // Wildcards - void throwWildcard( - List ui, - List alwaysSafe) - throws PolyUIException { - if (flag) { - throw ui.get(0); - } - throw alwaysSafe.get(0); - } - - void throwNull() { - throw null; - } + void throwNull() { + throw null; + } - // Declared - @UI PolyUIException ui = new PolyUIException(); - @AlwaysSafe PolyUIException alwaysSafe = new PolyUIException(); + // Declared + @UI PolyUIException ui = new PolyUIException(); + @AlwaysSafe PolyUIException alwaysSafe = new PolyUIException(); - void throwDeclared() { - try { - // :: error: (throw.type.invalid) - throw ui; - } catch (@UI PolyUIException UIParam) { + void throwDeclared() { + try { + // :: error: (throw.type.invalid) + throw ui; + } catch (@UI PolyUIException UIParam) { - } + } - try { - throw alwaysSafe; - } catch (@AlwaysSafe PolyUIException alwaysSafeParam) { + try { + throw alwaysSafe; + } catch (@AlwaysSafe PolyUIException alwaysSafeParam) { - } } + } - // Test Exception parameters - void unionTypes() { - try { - } catch (@AlwaysSafe NullPointerPolyUIException - | @AlwaysSafe ArrayStorePolyUIException unionParam) { + // Test Exception parameters + void unionTypes() { + try { + } catch (@AlwaysSafe NullPointerPolyUIException + | @AlwaysSafe ArrayStorePolyUIException unionParam) { - } + } - try { - } catch (@UI NullPointerPolyUIException | @UI ArrayStorePolyUIException unionParam) { + try { + } catch (@UI NullPointerPolyUIException | @UI ArrayStorePolyUIException unionParam) { - } } + } - void defaults() { - try { - throw new PolyUIException(); - } catch (PolyUIException e) { + void defaults() { + try { + throw new PolyUIException(); + } catch (PolyUIException e) { - } } + } - @PolyUIType - class PolyUIException extends Exception {} + @PolyUIType + class PolyUIException extends Exception {} - @PolyUIType - class NullPointerPolyUIException extends NullPointerException {} + @PolyUIType + class NullPointerPolyUIException extends NullPointerException {} - @PolyUIType - class ArrayStorePolyUIException extends ArrayStoreException {} + @PolyUIType + class ArrayStorePolyUIException extends ArrayStoreException {} } diff --git a/checker/tests/guieffect/TransitiveInheritance.java b/checker/tests/guieffect/TransitiveInheritance.java index ffe217505e0..e6542e19c92 100644 --- a/checker/tests/guieffect/TransitiveInheritance.java +++ b/checker/tests/guieffect/TransitiveInheritance.java @@ -2,57 +2,57 @@ public class TransitiveInheritance { - public static class TopLevel { - // Implicitly safe - public void foo() {} - } - - public static interface ITop { - public void bar(); - } - - public static interface IIndirect { - public void baz(); - } - - // Mid-level class and interface that do not redeclare or override the default safe methods - public abstract static class MidLevel extends TopLevel implements IIndirect {} - - public static interface IMid extends ITop {} - - // Issue #3287 is that if foo or bar is overridden with a @UIEffect implementation here, the - // "skip" in declarations causes the override error to not be issued - // We check both classes and interfaces; the reported issue is related only to methods whose - // nearest explicit definition lives in an interface - public static class Base extends MidLevel implements IMid { - - // Should catch when the override is for a method originating in a class two levels up (here - // TopLevel) - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void foo() {} - - // Should catch when the override is for a method originating in an interface two levels up - // (here ITop) - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void bar() {} - - // Should catch when the override is for a method originating in an interface two levels up, - // but which is implemented via class inheritance (here IIndirect, which is implemented by - // MidLevel). - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void baz() {} - } - - public static interface IBase extends IMid { - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void bar(); - } + public static class TopLevel { + // Implicitly safe + public void foo() {} + } + + public static interface ITop { + public void bar(); + } + + public static interface IIndirect { + public void baz(); + } + + // Mid-level class and interface that do not redeclare or override the default safe methods + public abstract static class MidLevel extends TopLevel implements IIndirect {} + + public static interface IMid extends ITop {} + + // Issue #3287 is that if foo or bar is overridden with a @UIEffect implementation here, the + // "skip" in declarations causes the override error to not be issued + // We check both classes and interfaces; the reported issue is related only to methods whose + // nearest explicit definition lives in an interface + public static class Base extends MidLevel implements IMid { + + // Should catch when the override is for a method originating in a class two levels up (here + // TopLevel) + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void foo() {} + + // Should catch when the override is for a method originating in an interface two levels up + // (here ITop) + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void bar() {} + + // Should catch when the override is for a method originating in an interface two levels up, + // but which is implemented via class inheritance (here IIndirect, which is implemented by + // MidLevel). + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void baz() {} + } + + public static interface IBase extends IMid { + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void bar(); + } } diff --git a/checker/tests/guieffect/UIChild.java b/checker/tests/guieffect/UIChild.java index 5dbe001853d..50106d6bba1 100644 --- a/checker/tests/guieffect/UIChild.java +++ b/checker/tests/guieffect/UIChild.java @@ -4,35 +4,35 @@ // Should not inherit @UI! public class UIChild extends UIParent { - @Override - public void doingUIStuff() { - // :: error: (call.invalid.ui) - thingy.dangerous(); - } + @Override + public void doingUIStuff() { + // :: error: (call.invalid.ui) + thingy.dangerous(); + } - // Should be an error to make this @UI - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void doingSafeStuff() {} + // Should be an error to make this @UI + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void doingSafeStuff() {} - public void shouldNotBeUI() { - // :: error: (call.invalid.ui) - thingy.dangerous(); - } + public void shouldNotBeUI() { + // :: error: (call.invalid.ui) + thingy.dangerous(); + } - @UIEffect - @SafeEffect - // :: error: (annotations.conflicts) - public void doubleAnnot1() {} + @UIEffect + @SafeEffect + // :: error: (annotations.conflicts) + public void doubleAnnot1() {} - @UIEffect - @PolyUIEffect - // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) - public void doubleAnnot2() {} + @UIEffect + @PolyUIEffect + // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) + public void doubleAnnot2() {} - @PolyUIEffect - @SafeEffect - // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) - public void doubleAnnot3() {} + @PolyUIEffect + @SafeEffect + // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) + public void doubleAnnot3() {} } diff --git a/checker/tests/guieffect/UIElement.java b/checker/tests/guieffect/UIElement.java index 00db2c6a365..ee4517ed115 100644 --- a/checker/tests/guieffect/UIElement.java +++ b/checker/tests/guieffect/UIElement.java @@ -3,11 +3,11 @@ @UIType public interface UIElement { - public void dangerous(); + public void dangerous(); - @SafeEffect - public void repaint(); + @SafeEffect + public void repaint(); - @SafeEffect - public void runOnUIThread(IAsyncUITask task); + @SafeEffect + public void runOnUIThread(IAsyncUITask task); } diff --git a/checker/tests/guieffect/UIParent.java b/checker/tests/guieffect/UIParent.java index 03cf37b6c5a..67274434c74 100644 --- a/checker/tests/guieffect/UIParent.java +++ b/checker/tests/guieffect/UIParent.java @@ -3,15 +3,15 @@ @UIType public class UIParent { - protected UIElement thingy; + protected UIElement thingy; - @SafeEffect // Making this ctor safe to allow easy safe subclasses - public UIParent() {} + @SafeEffect // Making this ctor safe to allow easy safe subclasses + public UIParent() {} - public void doingUIStuff() { - thingy.dangerous(); - } // should have UI effect + public void doingUIStuff() { + thingy.dangerous(); + } // should have UI effect - @SafeEffect - public void doingSafeStuff() {} // non-UI + @SafeEffect + public void doingSafeStuff() {} // non-UI } diff --git a/checker/tests/guieffect/WeakeningChild.java b/checker/tests/guieffect/WeakeningChild.java index 12cc60f02af..a4f815c7bbc 100644 --- a/checker/tests/guieffect/WeakeningChild.java +++ b/checker/tests/guieffect/WeakeningChild.java @@ -2,11 +2,11 @@ // Should not inherit @UI! public class WeakeningChild extends UIParent { - // Should be valid to override @UI methods with @AlwaysSafe methods - @Override - @SafeEffect - public void doingUIStuff() {} + // Should be valid to override @UI methods with @AlwaysSafe methods + @Override + @SafeEffect + public void doingUIStuff() {} - @Override - public void doingSafeStuff() {} + @Override + public void doingSafeStuff() {} } diff --git a/checker/tests/guieffect/packagetests/SafeByDecl.java b/checker/tests/guieffect/packagetests/SafeByDecl.java index b489f4b3d36..20fe6cd4d74 100644 --- a/checker/tests/guieffect/packagetests/SafeByDecl.java +++ b/checker/tests/guieffect/packagetests/SafeByDecl.java @@ -4,5 +4,5 @@ @SafeType public class SafeByDecl { - public static void safeByTypeDespiteUIPackage() {} + public static void safeByTypeDespiteUIPackage() {} } diff --git a/checker/tests/guieffect/packagetests/UIByPackageDecl.java b/checker/tests/guieffect/packagetests/UIByPackageDecl.java index 2aa0e35019b..2a519f67b26 100644 --- a/checker/tests/guieffect/packagetests/UIByPackageDecl.java +++ b/checker/tests/guieffect/packagetests/UIByPackageDecl.java @@ -1,7 +1,7 @@ package packagetests; public class UIByPackageDecl { - public static void implicitlyUI() { - // don't need to do anything here - } + public static void implicitlyUI() { + // don't need to do anything here + } } diff --git a/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java index 705681b6dee..42fd0131aa0 100644 --- a/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java @@ -1,18 +1,18 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - Object field; + Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - field = param.getInt(1); - field = param.getInteger(i); - field = param.identity("hello"); + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + field = param.getInt(1); + field = param.getInteger(i); + field = param.identity("hello"); - // String and Object are relevant types and must be annotated in bytecode - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - } + // String and Object are relevant types and must be annotated in bytecode + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + } } diff --git a/checker/tests/i18n-formatter/ConversionCategoryTest.java b/checker/tests/i18n-formatter/ConversionCategoryTest.java index 73b26e6f344..0c76aa7cfd1 100644 --- a/checker/tests/i18n-formatter/ConversionCategoryTest.java +++ b/checker/tests/i18n-formatter/ConversionCategoryTest.java @@ -3,67 +3,67 @@ public class ConversionCategoryTest { - public static void main(String[] args) { - @I18nFormat({I18nConversionCategory.GENERAL}) String s1 = "{0}"; + public static void main(String[] args) { + @I18nFormat({I18nConversionCategory.GENERAL}) String s1 = "{0}"; - @I18nFormat({I18nConversionCategory.DATE}) String s2 = "{0, date}"; - @I18nFormat({I18nConversionCategory.NUMBER}) String s3 = "{0, number}"; + @I18nFormat({I18nConversionCategory.DATE}) String s2 = "{0, date}"; + @I18nFormat({I18nConversionCategory.NUMBER}) String s3 = "{0, number}"; - @I18nFormat({I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER}) String s4 = "{1} {0, date}"; - // :: warning: (i18nformat.missing.arguments) - s4 = "{0}"; + @I18nFormat({I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER}) String s4 = "{1} {0, date}"; + // :: warning: (i18nformat.missing.arguments) + s4 = "{0}"; - @I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER}) String s5 = "{0} and {1, number}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s6 = "{1, number}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s7 = "{1, date}"; + @I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER}) String s5 = "{0} and {1, number}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s6 = "{1, number}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s7 = "{1, date}"; - @I18nFormat({ - I18nConversionCategory.UNUSED, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER - }) - String s8 = "{2}"; + @I18nFormat({ + I18nConversionCategory.UNUSED, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER + }) + String s8 = "{2}"; - @I18nFormat({ - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER - }) - String s9 = "{3, number} {0} {1, time}"; + @I18nFormat({ + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER + }) + String s9 = "{3, number} {0} {1, time}"; - @I18nFormat({ - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.DATE, - I18nConversionCategory.NUMBER, - I18nConversionCategory.UNUSED, - I18nConversionCategory.GENERAL - }) - String s10 = "{0} {1, date} {2, time} {3, number} {5}"; + @I18nFormat({ + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.DATE, + I18nConversionCategory.NUMBER, + I18nConversionCategory.UNUSED, + I18nConversionCategory.GENERAL + }) + String s10 = "{0} {1, date} {2, time} {3, number} {5}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s11 = "{1} {1, date}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s11 = "{1} {1, date}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s12 = "{1, number} {1, date}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s12 = "{1, number} {1, date}"; - @I18nFormat({I18nConversionCategory.DATE}) String s13 = "{0, date} {0, date}"; + @I18nFormat({I18nConversionCategory.DATE}) String s13 = "{0, date} {0, date}"; - // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.GENERAL}) String b1 = "{1}"; + // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.GENERAL}) String b1 = "{1}"; - // :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.DATE}) String b2 = "{0, number}"; + // :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.DATE}) String b2 = "{0, number}"; - // :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.GENERAL}) String b3 = "{0, number}"; + // :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.GENERAL}) String b3 = "{0, number}"; - // :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.GENERAL}) String b4 = "{0, date}"; + // :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.GENERAL}) String b4 = "{0, date}"; - // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.DATE}) String b5 = "{0, date} {1, date}"; + // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.DATE}) String b5 = "{0, date} {1, date}"; - // :: warning: (i18nformat.missing.arguments) - @I18nFormat({I18nConversionCategory.DATE, I18nConversionCategory.DATE}) String b6 = "{0, date}"; - } + // :: warning: (i18nformat.missing.arguments) + @I18nFormat({I18nConversionCategory.DATE, I18nConversionCategory.DATE}) String b6 = "{0, date}"; + } } diff --git a/checker/tests/i18n-formatter/HasFormat.java b/checker/tests/i18n-formatter/HasFormat.java index fce01829a38..86969c51e67 100644 --- a/checker/tests/i18n-formatter/HasFormat.java +++ b/checker/tests/i18n-formatter/HasFormat.java @@ -1,63 +1,62 @@ -import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; -import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil; - import java.text.MessageFormat; import java.util.Date; +import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; +import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil; public class HasFormat { - void test1(String format) { - if (I18nFormatUtil.hasFormat( - format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { - MessageFormat.format(format, "S", 1); - // :: warning: (i18nformat.missing.arguments) - MessageFormat.format(format, "S"); - // :: error: (argument.type.incompatible) - MessageFormat.format(format, "S", "S"); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format(format, "S", 1, 2); - } + void test1(String format) { + if (I18nFormatUtil.hasFormat( + format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { + MessageFormat.format(format, "S", 1); + // :: warning: (i18nformat.missing.arguments) + MessageFormat.format(format, "S"); + // :: error: (argument.type.incompatible) + MessageFormat.format(format, "S", "S"); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format(format, "S", 1, 2); } + } - void test2(String format) { - if (!I18nFormatUtil.hasFormat( - format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(format, "S", 1); - } + void test2(String format) { + if (!I18nFormatUtil.hasFormat( + format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(format, "S", 1); } + } - void test3(String format) { - if (I18nFormatUtil.hasFormat( - format, - I18nConversionCategory.GENERAL, - I18nConversionCategory.UNUSED, - I18nConversionCategory.GENERAL)) { - // :: warning: (i18nformat.argument.unused) - MessageFormat.format(format, "S", 1, "S"); - } + void test3(String format) { + if (I18nFormatUtil.hasFormat( + format, + I18nConversionCategory.GENERAL, + I18nConversionCategory.UNUSED, + I18nConversionCategory.GENERAL)) { + // :: warning: (i18nformat.argument.unused) + MessageFormat.format(format, "S", 1, "S"); } + } - void test4(String format) throws Exception { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(format, "S"); - if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.GENERAL)) { - MessageFormat.format(format, "S"); - MessageFormat.format(format, new Date()); - MessageFormat.format(format, 1); - } else { - throw new Exception(); - } + void test4(String format) throws Exception { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(format, "S"); + if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.GENERAL)) { + MessageFormat.format(format, "S"); + MessageFormat.format(format, new Date()); + MessageFormat.format(format, 1); + } else { + throw new Exception(); } + } - void tes5(String format) { - if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.NUMBER)) { - // :: error: (argument.type.incompatible) - MessageFormat.format(format, "S"); - MessageFormat.format(format, 1); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(format, 1); - } + void tes5(String format) { + if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.NUMBER)) { + // :: error: (argument.type.incompatible) + MessageFormat.format(format, "S"); + MessageFormat.format(format, 1); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(format, 1); } + } } diff --git a/checker/tests/i18n-formatter/I18nFormat.java b/checker/tests/i18n-formatter/I18nFormat.java index 2dc8969c388..e6b59925b74 100644 --- a/checker/tests/i18n-formatter/I18nFormat.java +++ b/checker/tests/i18n-formatter/I18nFormat.java @@ -3,50 +3,50 @@ public class I18nFormat { - void test() { + void test() { - MessageFormat.format( - "{0} {1, number} {2, time} {3, date} {4, choice, 0#zero}", - "S", 1, new Date(), new Date(), 0); - MessageFormat.format("{0, number}{1, number}", 1, 2); - MessageFormat.format("{0, number}{0}", 1); + MessageFormat.format( + "{0} {1, number} {2, time} {3, date} {4, choice, 0#zero}", + "S", 1, new Date(), new Date(), 0); + MessageFormat.format("{0, number}{1, number}", 1, 2); + MessageFormat.format("{0, number}{0}", 1); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{0, number}'", new Date(12)); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{0, number}'", new Date(12)); - // :: warning: (i18nformat.missing.arguments) - MessageFormat.format("''{0, time, short}''{1}{2, time} {33, number}{4444}'{''''", 0); + // :: warning: (i18nformat.missing.arguments) + MessageFormat.format("''{0, time, short}''{1}{2, time} {33, number}{4444}'{''''", 0); - // :: warning: (i18nformat.missing.arguments) - MessageFormat.format("{0, number}{1, number}", 1); + // :: warning: (i18nformat.missing.arguments) + MessageFormat.format("{0, number}{1, number}", 1); - // :: warning: (i18nformat.argument.unused) - MessageFormat.format("{1, number}", 1, 1); + // :: warning: (i18nformat.argument.unused) + MessageFormat.format("{1, number}", 1, 1); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("{0, number}", 1, new Date()); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("{0, number}", 1, new Date()); - // :: warning: (i18nformat.indirect.arguments) - MessageFormat.format("{0, number}", new Object[2]); + // :: warning: (i18nformat.indirect.arguments) + MessageFormat.format("{0, number}", new Object[2]); - MessageFormat.format("{0}", "S"); - MessageFormat.format("{0}", 1); - MessageFormat.format("{0}", new Date()); + MessageFormat.format("{0}", "S"); + MessageFormat.format("{0}", 1); + MessageFormat.format("{0}", new Date()); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, number}", "S"); - MessageFormat.format("{0, number}", 1); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, number}", new Date()); + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, number}", "S"); + MessageFormat.format("{0, number}", 1); + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, number}", new Date()); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, time}", "S"); - MessageFormat.format("{0, time}", 1); - MessageFormat.format("{0, time}", new Date()); + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, time}", "S"); + MessageFormat.format("{0, time}", 1); + MessageFormat.format("{0, time}", new Date()); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, date}", "S"); - MessageFormat.format("{0, date}", 1); - MessageFormat.format("{0, date}", new Date()); - } + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, date}", "S"); + MessageFormat.format("{0, date}", 1); + MessageFormat.format("{0, date}", new Date()); + } } diff --git a/checker/tests/i18n-formatter/I18nFormatForTest.java b/checker/tests/i18n-formatter/I18nFormatForTest.java index 011c37fdbc6..7a945e24042 100644 --- a/checker/tests/i18n-formatter/I18nFormatForTest.java +++ b/checker/tests/i18n-formatter/I18nFormatForTest.java @@ -1,101 +1,96 @@ -import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor; - import java.text.MessageFormat; import java.util.Date; +import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor; public class I18nFormatForTest { - static class A { - public void methodA(@I18nFormatFor("#2") String format, Object... args) {} - } - - public static void main(String[] args) { - - A a1 = new A(); - - // :: error: (i18nformat.string.invalid) - a1.methodA("{0, number", new Date(12)); - - // :: warning: (i18nformat.excess.arguments) - a1.methodA("'{0{}", 1); - a1.methodA("{0}", "A"); - - // :: error: (i18nformat.string.invalid) - a(1, 1.2, "{0, number", 1.2, new Date(12)); - a(1, 1.2, "{0, number}{1}", 1.2, 1, "A"); - // :: warning: (i18nformat.missing.arguments) - a(1, 1.2, "{0, number}{1}", 1.2, 1); - // :: warning: (i18nformat.excess.arguments) - a(1, 1.2, "{0, number}{1}", 1.2, 1, "A", 2); - b("{0, number}{1}", 1, "A"); - - // :: error: (i18nformat.string.invalid) - b("{0, number", new Date(12)); - b("{0, number}{1}", 1, "A"); - b("{0}", "a string"); - // :: error: (argument.type.incompatible) - b("{0, number}", "a string"); - - // :: error: (i18nformat.invalid.formatfor) - c("{0, number}{1}", 1, "A"); - - // :: error: (i18nformat.invalid.formatfor) - e(1, 2); - - f("{0}", 2); - - // :: error: (i18nformat.invalid.formatfor) - h("{0}", "a string"); - - // :: error: (i18nformat.invalid.formatfor) - i("{0}", "a string"); - - j("{0}"); - // :: error: (argument.type.incompatible) - j("{0, number}"); - } - - // Normal use - static void b(@I18nFormatFor("#2") String f, Object... args) { - MessageFormat.format(f, args); - } - - // @II18nFormatFor can be annotated anywhere - static void a( - int dummy1, - double dummy2, - @I18nFormatFor("#5") String f, - Object dummy3, - Object... args2) { - MessageFormat.format(f, args2); - } - - // Invalid index - static void c(@I18nFormatFor("#-1") String f, Object... args) { - MessageFormat.format("{0}", "A"); - } - - // @I18nFormatFor needs to be annotated to a string. - // :: error: (anno.on.irrelevant) - static void e(@I18nFormatFor("#2") int f, Object... args) {} - - // The parameter type is not necessary to an array of objects - static void f(@I18nFormatFor("#2") String f, int args) { - MessageFormat.format(f, args); - } - - // Invalid formatfor argument - static void h(@I18nFormatFor("2") String f, String args) { - MessageFormat.format(f, args); - } - - // We don't support this form of argument. You need to specify the parameter index. - static void i(@I18nFormatFor("arg") String f, Object... arg) { - MessageFormat.format(f, arg); - } - - // This is also a valid thing to do. - static void j(@I18nFormatFor("#1") String f) { - MessageFormat.format(f, f); - } + static class A { + public void methodA(@I18nFormatFor("#2") String format, Object... args) {} + } + + public static void main(String[] args) { + + A a1 = new A(); + + // :: error: (i18nformat.string.invalid) + a1.methodA("{0, number", new Date(12)); + + // :: warning: (i18nformat.excess.arguments) + a1.methodA("'{0{}", 1); + a1.methodA("{0}", "A"); + + // :: error: (i18nformat.string.invalid) + a(1, 1.2, "{0, number", 1.2, new Date(12)); + a(1, 1.2, "{0, number}{1}", 1.2, 1, "A"); + // :: warning: (i18nformat.missing.arguments) + a(1, 1.2, "{0, number}{1}", 1.2, 1); + // :: warning: (i18nformat.excess.arguments) + a(1, 1.2, "{0, number}{1}", 1.2, 1, "A", 2); + b("{0, number}{1}", 1, "A"); + + // :: error: (i18nformat.string.invalid) + b("{0, number", new Date(12)); + b("{0, number}{1}", 1, "A"); + b("{0}", "a string"); + // :: error: (argument.type.incompatible) + b("{0, number}", "a string"); + + // :: error: (i18nformat.invalid.formatfor) + c("{0, number}{1}", 1, "A"); + + // :: error: (i18nformat.invalid.formatfor) + e(1, 2); + + f("{0}", 2); + + // :: error: (i18nformat.invalid.formatfor) + h("{0}", "a string"); + + // :: error: (i18nformat.invalid.formatfor) + i("{0}", "a string"); + + j("{0}"); + // :: error: (argument.type.incompatible) + j("{0, number}"); + } + + // Normal use + static void b(@I18nFormatFor("#2") String f, Object... args) { + MessageFormat.format(f, args); + } + + // @II18nFormatFor can be annotated anywhere + static void a( + int dummy1, double dummy2, @I18nFormatFor("#5") String f, Object dummy3, Object... args2) { + MessageFormat.format(f, args2); + } + + // Invalid index + static void c(@I18nFormatFor("#-1") String f, Object... args) { + MessageFormat.format("{0}", "A"); + } + + // @I18nFormatFor needs to be annotated to a string. + // :: error: (anno.on.irrelevant) + static void e(@I18nFormatFor("#2") int f, Object... args) {} + + // The parameter type is not necessary to an array of objects + static void f(@I18nFormatFor("#2") String f, int args) { + MessageFormat.format(f, args); + } + + // Invalid formatfor argument + static void h(@I18nFormatFor("2") String f, String args) { + MessageFormat.format(f, args); + } + + // We don't support this form of argument. You need to specify the parameter index. + static void i(@I18nFormatFor("arg") String f, Object... arg) { + MessageFormat.format(f, arg); + } + + // This is also a valid thing to do. + static void j(@I18nFormatFor("#1") String f) { + MessageFormat.format(f, f); + } } diff --git a/checker/tests/i18n-formatter/IsFormat.java b/checker/tests/i18n-formatter/IsFormat.java index 641862e75ac..b16e6016a0d 100644 --- a/checker/tests/i18n-formatter/IsFormat.java +++ b/checker/tests/i18n-formatter/IsFormat.java @@ -1,38 +1,37 @@ +import java.text.MessageFormat; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil; -import java.text.MessageFormat; - public class IsFormat { - public static void test1(String cc) { - if (!I18nFormatUtil.isFormat(cc)) { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.GENERAL)) { - MessageFormat.format(cc, "A"); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } - } + public static void test1(String cc) { + if (!I18nFormatUtil.isFormat(cc)) { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.GENERAL)) { + MessageFormat.format(cc, "A"); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } } + } - public static void test2(String cc) { - if (!I18nFormatUtil.isFormat(cc)) { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.NUMBER)) { - MessageFormat.format(cc, 1); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } - } + public static void test2(String cc) { + if (!I18nFormatUtil.isFormat(cc)) { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.NUMBER)) { + MessageFormat.format(cc, 1); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } } + } } diff --git a/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java b/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java index 0c9df3e3768..5986b95819a 100644 --- a/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java +++ b/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java @@ -7,31 +7,31 @@ public class ManualExampleI18nFormatter { - void m(boolean flag) { - - @I18nFormat({NUMBER, DATE}) String f; - - f = "{0, number, #.#} {1, date}"; // OK - f = "{0, number} {1}"; // OK, GENERAL is weaker (less restrictive) than DATE - f = "{0} {1, date}"; // OK, GENERAL is weaker (less restrictive) than NUMBER - // :: warning: (i18nformat.missing.arguments) - f = "{0, number}"; // warning: last argument is ignored - // :: warning: (i18nformat.missing.arguments) - f = "{0}"; // warning: last argument is ignored - // :: warning: (i18nformat.missing.arguments) - f = flag ? "{0, number} {1}" : "{0, number}"; - - if (flag) { - f = "{0, number} {1}"; - } else { - // :: warning: (i18nformat.missing.arguments) - f = "{0, number}"; - } - @I18nFormat({NUMBER, DATE}) String f2 = f; - - // :: error: (assignment.type.incompatible) - f = "{0, number} {1, number}"; // error: NUMBER is stronger (more restrictive) than DATE - // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) - f = "{0} {1} {2}"; // error: too many arguments + void m(boolean flag) { + + @I18nFormat({NUMBER, DATE}) String f; + + f = "{0, number, #.#} {1, date}"; // OK + f = "{0, number} {1}"; // OK, GENERAL is weaker (less restrictive) than DATE + f = "{0} {1, date}"; // OK, GENERAL is weaker (less restrictive) than NUMBER + // :: warning: (i18nformat.missing.arguments) + f = "{0, number}"; // warning: last argument is ignored + // :: warning: (i18nformat.missing.arguments) + f = "{0}"; // warning: last argument is ignored + // :: warning: (i18nformat.missing.arguments) + f = flag ? "{0, number} {1}" : "{0, number}"; + + if (flag) { + f = "{0, number} {1}"; + } else { + // :: warning: (i18nformat.missing.arguments) + f = "{0, number}"; } + @I18nFormat({NUMBER, DATE}) String f2 = f; + + // :: error: (assignment.type.incompatible) + f = "{0, number} {1, number}"; // error: NUMBER is stronger (more restrictive) than DATE + // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) + f = "{0} {1} {2}"; // error: too many arguments + } } diff --git a/checker/tests/i18n-formatter/Syntax.java b/checker/tests/i18n-formatter/Syntax.java index 4861d2d8030..a9d5bd2f759 100644 --- a/checker/tests/i18n-formatter/Syntax.java +++ b/checker/tests/i18n-formatter/Syntax.java @@ -3,95 +3,95 @@ public class Syntax { - // Test 2.1.1: Missing '}' at end of message format (Unmatched braces in the pattern) - public static void unmatchedBraces() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, number", new Date(12)); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0}{", 1); - - // good - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{0{}", 1); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{0{}'", 1); - } - - // Test 2.1.2.1: The argument number needs to be an integer - public static void integerRequired() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{{0}}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0.2}", 1); - - // good - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{{0}}'", 1); - } - - // Test 2.1.2.2: The argument number can't be negative - public static void nonNegativeRequired() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{-1, number}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{-123}", 1); - - // good - MessageFormat.format("{0}", 1); - } - - // Test 2.1.3: Format Style required for choice format - public static void formatStyleRequired() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, choice}", 1); - - // good - MessageFormat.format("{0, choice, 0#zero}", 1); - } - - // Test 2.1.4: Wrong format Style - public static void wrongFormatStyle() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, time, number}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, number, y.m.d}", 1); - - // good - MessageFormat.format("{0, time, short}", 1); - MessageFormat.format("{0, number, currency}", 1); - } - - // Test 2.1.5: Unknown format type - public static void unknownFormatType() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, general}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, fool}", 1); - - // good - MessageFormat.format("{0}", 1); - MessageFormat.format("{0, time}", 1); - MessageFormat.format("{0, date}", 1); - MessageFormat.format("{0, number}", 1); - MessageFormat.format("{0, daTe}", 1); - MessageFormat.format("{0, NUMBER}", 1); - } - - // Test 2.1.6: Invalid Subformat Pattern - public static void invalidSubformatPattern() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, number, #.#.#}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, date, y.m.d.x}", new Date()); - - // TODO: This pattern is valid starting with JDK 23. Decide how to handle version-specific - // issues. - // TODO :: error: (i18nformat.string.invalid) - // MessageFormat.format("{0, choice, 0##zero}", 0); - - // good - MessageFormat.format("{0, number, #.#}", 1); - MessageFormat.format("{0, date, y.m.d}", new Date()); - MessageFormat.format("{0, choice, 0>zero}", 0); - } + // Test 2.1.1: Missing '}' at end of message format (Unmatched braces in the pattern) + public static void unmatchedBraces() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, number", new Date(12)); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0}{", 1); + + // good + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{0{}", 1); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{0{}'", 1); + } + + // Test 2.1.2.1: The argument number needs to be an integer + public static void integerRequired() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{{0}}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0.2}", 1); + + // good + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{{0}}'", 1); + } + + // Test 2.1.2.2: The argument number can't be negative + public static void nonNegativeRequired() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{-1, number}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{-123}", 1); + + // good + MessageFormat.format("{0}", 1); + } + + // Test 2.1.3: Format Style required for choice format + public static void formatStyleRequired() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, choice}", 1); + + // good + MessageFormat.format("{0, choice, 0#zero}", 1); + } + + // Test 2.1.4: Wrong format Style + public static void wrongFormatStyle() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, time, number}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, number, y.m.d}", 1); + + // good + MessageFormat.format("{0, time, short}", 1); + MessageFormat.format("{0, number, currency}", 1); + } + + // Test 2.1.5: Unknown format type + public static void unknownFormatType() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, general}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, fool}", 1); + + // good + MessageFormat.format("{0}", 1); + MessageFormat.format("{0, time}", 1); + MessageFormat.format("{0, date}", 1); + MessageFormat.format("{0, number}", 1); + MessageFormat.format("{0, daTe}", 1); + MessageFormat.format("{0, NUMBER}", 1); + } + + // Test 2.1.6: Invalid Subformat Pattern + public static void invalidSubformatPattern() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, number, #.#.#}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, date, y.m.d.x}", new Date()); + + // TODO: This pattern is valid starting with JDK 23. Decide how to handle version-specific + // issues. + // TODO :: error: (i18nformat.string.invalid) + // MessageFormat.format("{0, choice, 0##zero}", 0); + + // good + MessageFormat.format("{0, number, #.#}", 1); + MessageFormat.format("{0, date, y.m.d}", new Date()); + MessageFormat.format("{0, choice, 0>zero}", 0); + } } diff --git a/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java index 705681b6dee..42fd0131aa0 100644 --- a/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java @@ -1,18 +1,18 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - Object field; + Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - field = param.getInt(1); - field = param.getInteger(i); - field = param.identity("hello"); + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + field = param.getInt(1); + field = param.getInteger(i); + field = param.identity("hello"); - // String and Object are relevant types and must be annotated in bytecode - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - } + // String and Object are relevant types and must be annotated in bytecode + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + } } diff --git a/checker/tests/i18n/LocalizedMessage.java b/checker/tests/i18n/LocalizedMessage.java index bac2f4e1468..6ce89f2692f 100644 --- a/checker/tests/i18n/LocalizedMessage.java +++ b/checker/tests/i18n/LocalizedMessage.java @@ -1,59 +1,59 @@ import org.checkerframework.checker.i18n.qual.Localized; public class LocalizedMessage { - @Localized String localize(String s) { - throw new RuntimeException(); - } - - void localized(@Localized String s) {} - - void any(String s) {} - - void stringLiteral() { - // :: error: (argument.type.incompatible) - localized("ldskjfldj"); // error - any("lksjdflkjdf"); - } - - void stringRef(String ref) { - // :: error: (argument.type.incompatible) - localized(ref); // error - any(ref); - } - - void localizedRef(@Localized String ref) { - localized(ref); - any(ref); - } - - void methodRet(String ref) { - localized(localize(ref)); - localized(localize(ref)); - } - - void concatenation(@Localized String s1, String s2) { - // :: error: (argument.type.incompatible) - localized(s1 + s1); // error - // :: error: (argument.type.incompatible) :: error: (compound.assignment.type.incompatible) - localized(s1 += s1); // error - // :: error: (argument.type.incompatible) - localized(s1 + "m"); // error - // :: error: (argument.type.incompatible) - localized(s1 + s2); // error - - // :: error: (argument.type.incompatible) - localized(s2 + s1); // error - // :: error: (argument.type.incompatible) - localized(s2 + "m"); // error - // :: error: (argument.type.incompatible) - localized(s2 + s2); // error - - any(s1 + s1); - any(s1 + "m"); - any(s1 + s2); - - any(s2 + s1); - any(s2 + "m"); - any(s2 + s2); - } + @Localized String localize(String s) { + throw new RuntimeException(); + } + + void localized(@Localized String s) {} + + void any(String s) {} + + void stringLiteral() { + // :: error: (argument.type.incompatible) + localized("ldskjfldj"); // error + any("lksjdflkjdf"); + } + + void stringRef(String ref) { + // :: error: (argument.type.incompatible) + localized(ref); // error + any(ref); + } + + void localizedRef(@Localized String ref) { + localized(ref); + any(ref); + } + + void methodRet(String ref) { + localized(localize(ref)); + localized(localize(ref)); + } + + void concatenation(@Localized String s1, String s2) { + // :: error: (argument.type.incompatible) + localized(s1 + s1); // error + // :: error: (argument.type.incompatible) :: error: (compound.assignment.type.incompatible) + localized(s1 += s1); // error + // :: error: (argument.type.incompatible) + localized(s1 + "m"); // error + // :: error: (argument.type.incompatible) + localized(s1 + s2); // error + + // :: error: (argument.type.incompatible) + localized(s2 + s1); // error + // :: error: (argument.type.incompatible) + localized(s2 + "m"); // error + // :: error: (argument.type.incompatible) + localized(s2 + s2); // error + + any(s1 + s1); + any(s1 + "m"); + any(s1 + s2); + + any(s2 + s1); + any(s2 + "m"); + any(s2 + s2); + } } diff --git a/checker/tests/index-initializedfields/RequireJavadoc.java b/checker/tests/index-initializedfields/RequireJavadoc.java index 8c67a2a7c73..9718a9fa42e 100644 --- a/checker/tests/index-initializedfields/RequireJavadoc.java +++ b/checker/tests/index-initializedfields/RequireJavadoc.java @@ -34,13 +34,6 @@ import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.options.Option; -import org.plumelib.options.Options; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -58,6 +51,11 @@ import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.options.Option; +import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -67,914 +65,907 @@ */ public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - @Option("Don't check files or directories whose pathname matches the regex") - public @MonotonicNonNull Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no - * problems should be reported. - */ - @Option("Don't report problems in Java elements whose name matches the regex") - public @MonotonicNonNull Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - @Option("Don't report problems in elements with private access") - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - @Option("Don't report problems in constructors with zero formal parameters") - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

Trivial getters and setters are of the form: - * - *

{@code
-     * SomeType getFoo() {
-     *   return foo;
-     * }
-     *
-     * SomeType foo() {
-     *   return foo;
-     * }
-     *
-     * void setFoo(SomeType foo) {
-     *   this.foo = foo;
-     * }
-     *
-     * boolean hasFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean isFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean notFoo() {
-     *   return !foo;
-     * }
-     * }
- */ - @Option("Don't report problems in trivial getters and setters") - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - @Option("Don't report problems in type declarations") - public boolean dont_require_type; - - /** If true, don't check fields. */ - @Option("Don't report problems in fields") - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - @Option("Don't report problems in methods and constructors") - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - @Option("Require package-info.java file to exist") - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if - * the command-line arguments were absolute pathnames, or no command-line arguments were - * supplied. - */ - @Option("Report relative rather than absolute filenames") - public boolean relative = false; - - /** If true, output debug information. */ - @Option("Print diagnostic information") - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); - - rj.setJavaFiles(remainingArgs); - - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); + /** Matches name of file or directory where no problems should be reported. */ + @Option("Don't check files or directories whose pathname matches the regex") + public @MonotonicNonNull Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no problems + * should be reported. + */ + @Option("Don't report problems in Java elements whose name matches the regex") + public @MonotonicNonNull Pattern dont_require = null; + + /** If true, don't check elements with private access. */ + @Option("Don't report problems in elements with private access") + public boolean dont_require_private; + + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + @Option("Don't report problems in constructors with zero formal parameters") + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

Trivial getters and setters are of the form: + * + *

{@code
+   * SomeType getFoo() {
+   *   return foo;
+   * }
+   *
+   * SomeType foo() {
+   *   return foo;
+   * }
+   *
+   * void setFoo(SomeType foo) {
+   *   this.foo = foo;
+   * }
+   *
+   * boolean hasFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean isFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean notFoo() {
+   *   return !foo;
+   * }
+   * }
+ */ + @Option("Don't report problems in trivial getters and setters") + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + @Option("Don't report problems in type declarations") + public boolean dont_require_type; + + /** If true, don't check fields. */ + @Option("Don't report problems in fields") + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + @Option("Don't report problems in methods and constructors") + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + @Option("Require package-info.java file to exist") + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if the + * command-line arguments were absolute pathnames, or no command-line arguments were supplied. + */ + @Option("Report relative rather than absolute filenames") + public boolean relative = false; + + /** If true, output debug information. */ + @Option("Print diagnostic information") + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); + + rj.setJavaFiles(remainingArgs); + + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - - /** Creates a new RequireJavadoc instance. */ - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - @SuppressWarnings("lock:methodref.receiver") // no locking here - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - - FileVisitor walker = new JavaFilesVisitor(); - - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - - javaFiles.sort(Comparator.comparing(Object::toString)); - - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - @SuppressWarnings( - "nullness:assignment") // the file is not "/", so getParent() is non-null - @NonNull Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } + for (String error : rj.errors) { + System.out.println(error); + } + System.exit(rj.errors.isEmpty() ? 0 : 1); + } + + /** Creates a new RequireJavadoc instance. */ + private RequireJavadoc() {} + + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + @SuppressWarnings("lock:methodref.receiver") // no locking here + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; } - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + FileVisitor walker = new JavaFilesVisitor(); + + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + javaFiles.sort(Comparator.comparing(Object::toString)); + + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + @SuppressWarnings("nullness:assignment") // the file is not "/", so getParent() is non-null + @NonNull Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } + } - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); - } - } - return FileVisitResult.CONTINUE; - } + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); } + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); - } - return result; + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID; - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; - - /** - * Create a new PropertyKind. - * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type - */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; - } - - /** - * Returns true if this is a getter. - * - * @return true if this is a getter - */ - boolean isGetter() { - return this != SETTER; - } - - /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor - * method. - * - * @param md the method to check - * @return the PropertyKind for the given method - */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
    - *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, - * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return - * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, - * and has a body of the form {@code this.foo = foo}. - *
- * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. + * + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element + */ + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param path a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID; + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; /** - * Return true if this method declaration is a trivial getter or setter of the given kind. + * Create a new PropertyKind. * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; } /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. + * Returns true if this is a getter. * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null + * @return true if this is a getter */ - private @Nullable String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - @SuppressWarnings("index") // https://github.com/typetools/checker-framework/issues/5201 - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } + boolean isGetter() { + return this != SETTER; } /** - * Returns true if the signature of the given method is a property accessor of the given kind. + * Return the PropertyKind for the given method, or null if it isn't a property accessor method. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param md the method to check + * @return the PropertyKind for the given method */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } + + /** + * Return true if this method declaration is a trivial getter or setter. + * + *

    + *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or + * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} + * or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and + * has a body of the form {@code this.foo = foo}. + *
+ * + * @param md the method to check + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if this method declaration is a trivial getter or setter of the given kind. + * + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private @Nullable String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + @SuppressWarnings("index") // https://github.com/typetools/checker-framework/issues/5201 + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } + } + + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; + } + + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } + } + + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private @Nullable Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); + } + + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { + + /** The file being visited. Used for constructing error messages. */ + private Path filename; /** - * Returns true if the body of the given method is a property accessor of the given kind. + * Create a new RequireJavadocVisitor. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param filename the file being visited; used for diagnostic messages */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } + public RequireJavadocVisitor(Path filename) { + this.filename = filename; } /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. + * Return a string stating that documentation is missing on the given construct. * - * @param md a method declaration - * @return its sole statement, or null + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct */ - private @Nullable Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; - - /** - * Create a new RequireJavadocVisitor. - * - * @param filename the file being visited; used for diagnostic messages - */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; - } - - /** - * Return a string stating that documentation is missing on the given construct. - * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct - */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", - path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } - } - - @Override - public void visit(CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); - } - - @Override - public void visit(ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } - - @Override - public void visit(ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } + @Override + public void visit(CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - @Override - public void visit(MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf( - "skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); - } + @Override + public void visit(ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - @Override - public void visit(FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + @Override + public void visit(ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - @Override - public void visit(EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + @Override + public void visit(MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } - @Override - public void visit(EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + @Override + public void visit(FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } - @Override - public void visit(AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + @Override + public void visit(EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } - @Override - public void visit(AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); - } + @Override + public void visit(EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } - /** - * Return true if this method is annotated with {@code @Override}. - * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} - */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } + @Override + public void visit(AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); } - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; + @Override + public void visit(AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); } /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

{@code
-     * /** ... *}{@code /
-     * // text 1
-     * // text 2
-     * void m() { ... }
-     * }
- * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. + * Return true if this method is annotated with {@code @Override}. * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} */ - @SuppressWarnings({ - "JdkObsolete", // for LinkedList - "interning:not.interned", // element of a list - "ReferenceEquality", - }) - // This implementation is from Randoop's `Minimize.java` file, and before that from JavaParser's - // PrettyPrintVisitor.printOrphanCommentsBeforeThisChildNode. The JavaParser maintainers refuse - // to provide such functionality in JavaParser proper. - private static void getOrphanCommentsBeforeThisChildNode( - final Node node, List result) { - if (node instanceof Comment) { - return; - } + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } + } + + /** + * Return true if this node has a Javadoc comment. + * + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment + */ + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; + } + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
{@code
+   * /** ... *}{@code /
+   * // text 1
+   * // text 2
+   * void m() { ... }
+   * }
+ * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + @SuppressWarnings({ + "JdkObsolete", // for LinkedList + "interning:not.interned", // element of a list + "ReferenceEquality", + }) + // This implementation is from Randoop's `Minimize.java` file, and before that from JavaParser's + // PrettyPrintVisitor.printOrphanCommentsBeforeThisChildNode. The JavaParser maintainers refuse + // to provide such functionality in JavaParser proper. + private static void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); - } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); } + } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava index 5930e52e1f7..34b0a97db61 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava @@ -34,9 +34,6 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.plumelib.options.Options; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -54,6 +51,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -64,885 +62,878 @@ import java.util.regex.Pattern; @org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.index.IndexChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no - * problems should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

Trivial getters and setters are of the form: - * - *

{@code
-     * SomeType getFoo() {
-     *   return foo;
-     * }
-     *
-     * SomeType foo() {
-     *   return foo;
-     * }
-     *
-     * void setFoo(SomeType foo) {
-     *   this.foo = foo;
-     * }
-     *
-     * boolean hasFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean isFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean notFoo() {
-     *   return !foo;
-     * }
-     * }
- */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if - * the command-line arguments were absolute pathnames, or no command-line arguments were - * supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no problems + * should be reported. + */ + public Pattern dont_require = null; + + /** If true, don't check elements with private access. */ + public boolean dont_require_private; + + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

Trivial getters and setters are of the form: + * + *

{@code
+   * SomeType getFoo() {
+   *   return foo;
+   * }
+   *
+   * SomeType foo() {
+   *   return foo;
+   * }
+   *
+   * void setFoo(SomeType foo) {
+   *   this.foo = foo;
+   * }
+   *
+   * boolean hasFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean isFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean notFoo() {
+   *   return !foo;
+   * }
+   * }
+ */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if the + * command-line arguments were absolute pathnames, or no command-line arguments were supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } + for (String error : rj.errors) { + System.out.println(error); } + System.exit(rj.errors.isEmpty() ? 0 : 1); + } + + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} + + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } + } - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { - - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} - - public FileVisitResult visitFile( - JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); - } - } - return FileVisitResult.CONTINUE; - } - - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) - public FileVisitResult postVisitDirectory( - JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); } + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); - } - return result; + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) + public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; - - /** - * Create a new PropertyKind. - * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type - */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; - } - - /** - * Returns true if this is a getter. - * - * @return true if this is a getter - */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; - } - - /** - * Return the PropertyKind for the given method. - * - * @param md the method to check - * @return the PropertyKind for the given method - */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
    - *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, - * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return - * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, - * and has a body of the form {@code this.foo = foo}. - *
- * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. + * + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element + */ + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param path a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; /** - * Return true if this method declaration is a trivial getter or setter of the given kind. + * Create a new PropertyKind. * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type */ - private @org.checkerframework.checker.index.qual.UpperBoundBottom boolean - isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; } /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. + * Returns true if this is a getter. * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null + * @return true if this is a getter */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; } /** - * Returns true if the signature of the given method is a property accessor of the given kind. + * Return the PropertyKind for the given method. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param md the method to check + * @return the PropertyKind for the given method */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } + + /** + * Return true if this method declaration is a trivial getter or setter. + * + *

    + *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or + * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} + * or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and + * has a body of the form {@code this.foo = foo}. + *
+ * + * @param md the method to check + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if this method declaration is a trivial getter or setter of the given kind. + * + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter + */ + private @org.checkerframework.checker.index.qual.UpperBoundBottom boolean isTrivialGetterOrSetter( + MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); } + } + + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; + } + + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } + } + + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); + } + + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { + + /** The file being visited. Used for constructing error messages. */ + private Path filename; /** - * Returns true if the body of the given method is a property accessor of the given kind. + * Create a new RequireJavadocVisitor. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param filename the file being visited; used for diagnostic messages */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } + public RequireJavadocVisitor(Path filename) { + this.filename = filename; } /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. + * Return a string stating that documentation is missing on the given construct. * - * @param md a method declaration - * @return its sole statement, or null + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; - - /** - * Create a new RequireJavadocVisitor. - * - * @param filename the file being visited; used for diagnostic messages - */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; - } - - /** - * Return a string stating that documentation is missing on the given construct. - * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct - */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", - path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } - } - - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); - } - - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } - - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf( - "skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); - } + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } - public void visit( - RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); - } + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } - /** - * Return true if this method is annotated with {@code @Override}. - * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} - */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); } - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; + public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); } /** - * Get "orphan comments": comments before the comment before this node. For example, in + * Return true if this method is annotated with {@code @Override}. * - *

{@code
-     * /** ... *}{@code /
-     * // text 1
-     * // text 2
-     * void m() { ... }
-     * }
- * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); - } + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } + } + + /** + * Return true if this node has a Javadoc comment. + * + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment + */ + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; + } + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
{@code
+   * /** ... *}{@code /
+   * // text 1
+   * // text 2
+   * void m() { ... }
+   * }
+ * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); } + } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava index 8d2c6ce13e7..592af8c9719 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava @@ -34,9 +34,6 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.plumelib.options.Options; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -54,6 +51,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -62,888 +60,880 @@ import java.util.regex.Pattern; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.inequality.LessThanChecker") + "org.checkerframework.checker.index.inequality.LessThanChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no - * problems should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

Trivial getters and setters are of the form: - * - *

{@code
-     * SomeType getFoo() {
-     *   return foo;
-     * }
-     *
-     * SomeType foo() {
-     *   return foo;
-     * }
-     *
-     * void setFoo(SomeType foo) {
-     *   this.foo = foo;
-     * }
-     *
-     * boolean hasFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean isFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean notFoo() {
-     *   return !foo;
-     * }
-     * }
- */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if - * the command-line arguments were absolute pathnames, or no command-line arguments were - * supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no problems + * should be reported. + */ + public Pattern dont_require = null; + + /** If true, don't check elements with private access. */ + public boolean dont_require_private; + + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

Trivial getters and setters are of the form: + * + *

{@code
+   * SomeType getFoo() {
+   *   return foo;
+   * }
+   *
+   * SomeType foo() {
+   *   return foo;
+   * }
+   *
+   * void setFoo(SomeType foo) {
+   *   this.foo = foo;
+   * }
+   *
+   * boolean hasFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean isFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean notFoo() {
+   *   return !foo;
+   * }
+   * }
+ */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if the + * command-line arguments were absolute pathnames, or no command-line arguments were supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } + for (String error : rj.errors) { + System.out.println(error); } + System.exit(rj.errors.isEmpty() ? 0 : 1); + } + + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} + + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } + } - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { - - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} - - public FileVisitResult visitFile( - JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); - } - } - return FileVisitResult.CONTINUE; - } - - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) - public FileVisitResult postVisitDirectory( - JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); } + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); - } - return result; + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) + public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; - - /** - * Create a new PropertyKind. - * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type - */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; - } - - /** - * Returns true if this is a getter. - * - * @return true if this is a getter - */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; - } - - /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor - * method. - * - * @param md the method to check - * @return the PropertyKind for the given method, or null - */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
    - *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, - * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return - * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, - * and has a body of the form {@code this.foo = foo}. - *
- * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. + * + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element + */ + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param path a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; /** - * Return true if this method declaration is a trivial getter or setter of the given kind. + * Create a new PropertyKind. * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; } /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. + * Returns true if this is a getter. * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null + * @return true if this is a getter */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; } /** - * Returns true if the signature of the given method is a property accessor of the given kind. + * Return the PropertyKind for the given method, or null if it isn't a property accessor method. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param md the method to check + * @return the PropertyKind for the given method, or null */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } + + /** + * Return true if this method declaration is a trivial getter or setter. + * + *

    + *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or + * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} + * or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and + * has a body of the form {@code this.foo = foo}. + *
+ * + * @param md the method to check + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if this method declaration is a trivial getter or setter of the given kind. + * + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); } + } + + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; + } + + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } + } + + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); + } + + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { + + /** The file being visited. Used for constructing error messages. */ + private Path filename; /** - * Returns true if the body of the given method is a property accessor of the given kind. + * Create a new RequireJavadocVisitor. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param filename the file being visited; used for diagnostic messages */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } + public RequireJavadocVisitor(Path filename) { + this.filename = filename; } /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. + * Return a string stating that documentation is missing on the given construct. * - * @param md a method declaration - * @return its sole statement, or null + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; - - /** - * Create a new RequireJavadocVisitor. - * - * @param filename the file being visited; used for diagnostic messages - */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; - } - - /** - * Return a string stating that documentation is missing on the given construct. - * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct - */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", - path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } - } - - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); - } - - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } - - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf( - "skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); - } + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } - public void visit( - RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); - } + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } - /** - * Return true if this method is annotated with {@code @Override}. - * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} - */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); } - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; + public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); } /** - * Get "orphan comments": comments before the comment before this node. For example, in + * Return true if this method is annotated with {@code @Override}. * - *

{@code
-     * /** ... *}{@code /
-     * // text 1
-     * // text 2
-     * void m() { ... }
-     * }
- * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); - } + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } + } + + /** + * Return true if this node has a Javadoc comment. + * + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment + */ + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; + } + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
{@code
+   * /** ... *}{@code /
+   * // text 1
+   * // text 2
+   * void m() { ... }
+   * }
+ * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); } + } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava index 0e224203a8a..fd72c691ea9 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava @@ -34,9 +34,6 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.plumelib.options.Options; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -54,6 +51,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -62,892 +60,884 @@ import java.util.regex.Pattern; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.lowerbound.LowerBoundChecker") + "org.checkerframework.checker.index.lowerbound.LowerBoundChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no - * problems should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

Trivial getters and setters are of the form: - * - *

{@code
-     * SomeType getFoo() {
-     *   return foo;
-     * }
-     *
-     * SomeType foo() {
-     *   return foo;
-     * }
-     *
-     * void setFoo(SomeType foo) {
-     *   this.foo = foo;
-     * }
-     *
-     * boolean hasFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean isFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean notFoo() {
-     *   return !foo;
-     * }
-     * }
- */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if - * the command-line arguments were absolute pathnames, or no command-line arguments were - * supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no problems + * should be reported. + */ + public Pattern dont_require = null; + + /** If true, don't check elements with private access. */ + public boolean dont_require_private; + + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

Trivial getters and setters are of the form: + * + *

{@code
+   * SomeType getFoo() {
+   *   return foo;
+   * }
+   *
+   * SomeType foo() {
+   *   return foo;
+   * }
+   *
+   * void setFoo(SomeType foo) {
+   *   this.foo = foo;
+   * }
+   *
+   * boolean hasFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean isFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean notFoo() {
+   *   return !foo;
+   * }
+   * }
+ */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if the + * command-line arguments were absolute pathnames, or no command-line arguments were supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } + for (String error : rj.errors) { + System.out.println(error); } + System.exit(rj.errors.isEmpty() ? 0 : 1); + } + + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} + + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } + } - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { - - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} - - public FileVisitResult visitFile( - JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); - } - } - return FileVisitResult.CONTINUE; - } - - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) - public FileVisitResult postVisitDirectory( - JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); } + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); - } - return result; + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) + public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams; - - /** The return type. */ - final ReturnType returnType; - - /** - * Create a new PropertyKind. - * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type - */ - PropertyKind( - String prefix, - @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams, - ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; - } - - /** - * Returns true if this is a getter. - * - * @return true if this is a getter - */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; - } - - /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor - * method. - * - * @param md the method to check - * @return the PropertyKind for the given method, or null - */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
    - *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, - * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return - * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, - * and has a body of the form {@code this.foo = foo}. - *
- * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. + * + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element + */ + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param path a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams; + + /** The return type. */ + final ReturnType returnType; /** - * Return true if this method declaration is a trivial getter or setter of the given kind. + * Create a new PropertyKind. * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type */ - private @org.checkerframework.checker.index.qual.LowerBoundBottom boolean - isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); + PropertyKind( + String prefix, + @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams, + ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; } /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. + * Returns true if this is a getter. * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null + * @return true if this is a getter */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; } /** - * Returns true if the signature of the given method is a property accessor of the given kind. + * Return the PropertyKind for the given method, or null if it isn't a property accessor method. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param md the method to check + * @return the PropertyKind for the given method, or null */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } + + /** + * Return true if this method declaration is a trivial getter or setter. + * + *

    + *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or + * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} + * or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and + * has a body of the form {@code this.foo = foo}. + *
+ * + * @param md the method to check + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if this method declaration is a trivial getter or setter of the given kind. + * + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter + */ + private @org.checkerframework.checker.index.qual.LowerBoundBottom boolean isTrivialGetterOrSetter( + MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); } + } + + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; + } + + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } + } + + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); + } + + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { + + /** The file being visited. Used for constructing error messages. */ + private Path filename; /** - * Returns true if the body of the given method is a property accessor of the given kind. + * Create a new RequireJavadocVisitor. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param filename the file being visited; used for diagnostic messages */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } + public RequireJavadocVisitor(Path filename) { + this.filename = filename; } /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. + * Return a string stating that documentation is missing on the given construct. * - * @param md a method declaration - * @return its sole statement, or null + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; - - /** - * Create a new RequireJavadocVisitor. - * - * @param filename the file being visited; used for diagnostic messages - */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; - } - - /** - * Return a string stating that documentation is missing on the given construct. - * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct - */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", - path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } - } - - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); - } - - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } - - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf( - "skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); - } + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } - public void visit( - RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); - } + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } - /** - * Return true if this method is annotated with {@code @Override}. - * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} - */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); } - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; + public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); } /** - * Get "orphan comments": comments before the comment before this node. For example, in + * Return true if this method is annotated with {@code @Override}. * - *

{@code
-     * /** ... *}{@code /
-     * // text 1
-     * // text 2
-     * void m() { ... }
-     * }
- * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); - } + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } + } + + /** + * Return true if this node has a Javadoc comment. + * + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment + */ + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; + } + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
{@code
+   * /** ... *}{@code /
+   * // text 1
+   * // text 2
+   * void m() { ... }
+   * }
+ * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); } + } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava index 020c5fdb273..89ce44fd341 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava @@ -34,9 +34,6 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.plumelib.options.Options; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -54,6 +51,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -62,888 +60,880 @@ import java.util.regex.Pattern; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.searchindex.SearchIndexChecker") + "org.checkerframework.checker.index.searchindex.SearchIndexChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no - * problems should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

Trivial getters and setters are of the form: - * - *

{@code
-     * SomeType getFoo() {
-     *   return foo;
-     * }
-     *
-     * SomeType foo() {
-     *   return foo;
-     * }
-     *
-     * void setFoo(SomeType foo) {
-     *   this.foo = foo;
-     * }
-     *
-     * boolean hasFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean isFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean notFoo() {
-     *   return !foo;
-     * }
-     * }
- */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if - * the command-line arguments were absolute pathnames, or no command-line arguments were - * supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no problems + * should be reported. + */ + public Pattern dont_require = null; + + /** If true, don't check elements with private access. */ + public boolean dont_require_private; + + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

Trivial getters and setters are of the form: + * + *

{@code
+   * SomeType getFoo() {
+   *   return foo;
+   * }
+   *
+   * SomeType foo() {
+   *   return foo;
+   * }
+   *
+   * void setFoo(SomeType foo) {
+   *   this.foo = foo;
+   * }
+   *
+   * boolean hasFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean isFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean notFoo() {
+   *   return !foo;
+   * }
+   * }
+ */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if the + * command-line arguments were absolute pathnames, or no command-line arguments were supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } + for (String error : rj.errors) { + System.out.println(error); } + System.exit(rj.errors.isEmpty() ? 0 : 1); + } + + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} + + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } + } - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { - - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} - - public FileVisitResult visitFile( - JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); - } - } - return FileVisitResult.CONTINUE; - } - - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) - public FileVisitResult postVisitDirectory( - JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); } + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); - } - return result; + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) + public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; - - /** - * Create a new PropertyKind. - * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type - */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; - } - - /** - * Returns true if this is a getter. - * - * @return true if this is a getter - */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; - } - - /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor - * method. - * - * @param md the method to check - * @return the PropertyKind for the given method, or null - */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
    - *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, - * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return - * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, - * and has a body of the form {@code this.foo = foo}. - *
- * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. + * + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element + */ + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param path a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; /** - * Return true if this method declaration is a trivial getter or setter of the given kind. + * Create a new PropertyKind. * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; } /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. + * Returns true if this is a getter. * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null + * @return true if this is a getter */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; } /** - * Returns true if the signature of the given method is a property accessor of the given kind. + * Return the PropertyKind for the given method, or null if it isn't a property accessor method. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param md the method to check + * @return the PropertyKind for the given method, or null */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } + + /** + * Return true if this method declaration is a trivial getter or setter. + * + *

    + *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or + * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} + * or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and + * has a body of the form {@code this.foo = foo}. + *
+ * + * @param md the method to check + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if this method declaration is a trivial getter or setter of the given kind. + * + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); } + } + + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; + } + + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } + } + + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); + } + + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { + + /** The file being visited. Used for constructing error messages. */ + private Path filename; /** - * Returns true if the body of the given method is a property accessor of the given kind. + * Create a new RequireJavadocVisitor. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param filename the file being visited; used for diagnostic messages */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } + public RequireJavadocVisitor(Path filename) { + this.filename = filename; } /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. + * Return a string stating that documentation is missing on the given construct. * - * @param md a method declaration - * @return its sole statement, or null + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; - - /** - * Create a new RequireJavadocVisitor. - * - * @param filename the file being visited; used for diagnostic messages - */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; - } - - /** - * Return a string stating that documentation is missing on the given construct. - * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct - */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", - path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } - } - - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); - } - - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } - - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf( - "skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); - } + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } - public void visit( - RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); - } + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } - /** - * Return true if this method is annotated with {@code @Override}. - * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} - */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); } - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; + public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); } /** - * Get "orphan comments": comments before the comment before this node. For example, in + * Return true if this method is annotated with {@code @Override}. * - *

{@code
-     * /** ... *}{@code /
-     * // text 1
-     * // text 2
-     * void m() { ... }
-     * }
- * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); - } + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } + } + + /** + * Return true if this node has a Javadoc comment. + * + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment + */ + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; + } + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
{@code
+   * /** ... *}{@code /
+   * // text 1
+   * // text 2
+   * void m() { ... }
+   * }
+ * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); } + } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava index 5dece81dc7c..b583c6d100b 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava @@ -34,9 +34,6 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.plumelib.options.Options; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -54,6 +51,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -62,888 +60,880 @@ import java.util.regex.Pattern; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.substringindex.SubstringIndexChecker") + "org.checkerframework.checker.index.substringindex.SubstringIndexChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no - * problems should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

Trivial getters and setters are of the form: - * - *

{@code
-     * SomeType getFoo() {
-     *   return foo;
-     * }
-     *
-     * SomeType foo() {
-     *   return foo;
-     * }
-     *
-     * void setFoo(SomeType foo) {
-     *   this.foo = foo;
-     * }
-     *
-     * boolean hasFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean isFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean notFoo() {
-     *   return !foo;
-     * }
-     * }
- */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if - * the command-line arguments were absolute pathnames, or no command-line arguments were - * supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no problems + * should be reported. + */ + public Pattern dont_require = null; + + /** If true, don't check elements with private access. */ + public boolean dont_require_private; + + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

Trivial getters and setters are of the form: + * + *

{@code
+   * SomeType getFoo() {
+   *   return foo;
+   * }
+   *
+   * SomeType foo() {
+   *   return foo;
+   * }
+   *
+   * void setFoo(SomeType foo) {
+   *   this.foo = foo;
+   * }
+   *
+   * boolean hasFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean isFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean notFoo() {
+   *   return !foo;
+   * }
+   * }
+ */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if the + * command-line arguments were absolute pathnames, or no command-line arguments were supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } + for (String error : rj.errors) { + System.out.println(error); } + System.exit(rj.errors.isEmpty() ? 0 : 1); + } + + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} + + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } + } - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { - - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} - - public FileVisitResult visitFile( - JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); - } - } - return FileVisitResult.CONTINUE; - } - - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) - public FileVisitResult postVisitDirectory( - JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); } + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); - } - return result; + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) + public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; - - /** - * Create a new PropertyKind. - * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type - */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; - } - - /** - * Returns true if this is a getter. - * - * @return true if this is a getter - */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; - } - - /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor - * method. - * - * @param md the method to check - * @return the PropertyKind for the given method, or null - */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
    - *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, - * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return - * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, - * and has a body of the form {@code this.foo = foo}. - *
- * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. + * + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element + */ + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param path a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; /** - * Return true if this method declaration is a trivial getter or setter of the given kind. + * Create a new PropertyKind. * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; } /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. + * Returns true if this is a getter. * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null + * @return true if this is a getter */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; } /** - * Returns true if the signature of the given method is a property accessor of the given kind. + * Return the PropertyKind for the given method, or null if it isn't a property accessor method. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param md the method to check + * @return the PropertyKind for the given method, or null */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } + + /** + * Return true if this method declaration is a trivial getter or setter. + * + *

    + *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or + * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} + * or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and + * has a body of the form {@code this.foo = foo}. + *
+ * + * @param md the method to check + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if this method declaration is a trivial getter or setter of the given kind. + * + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); } + } + + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; + } + + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } + } + + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); + } + + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { + + /** The file being visited. Used for constructing error messages. */ + private Path filename; /** - * Returns true if the body of the given method is a property accessor of the given kind. + * Create a new RequireJavadocVisitor. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param filename the file being visited; used for diagnostic messages */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } + public RequireJavadocVisitor(Path filename) { + this.filename = filename; } /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. + * Return a string stating that documentation is missing on the given construct. * - * @param md a method declaration - * @return its sole statement, or null + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; - - /** - * Create a new RequireJavadocVisitor. - * - * @param filename the file being visited; used for diagnostic messages - */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; - } - - /** - * Return a string stating that documentation is missing on the given construct. - * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct - */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", - path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } - } - - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); - } - - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } - - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf( - "skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); - } + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } - public void visit( - RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); - } + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } - /** - * Return true if this method is annotated with {@code @Override}. - * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} - */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); } - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; + public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); } /** - * Get "orphan comments": comments before the comment before this node. For example, in + * Return true if this method is annotated with {@code @Override}. * - *

{@code
-     * /** ... *}{@code /
-     * // text 1
-     * // text 2
-     * void m() { ... }
-     * }
- * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); - } + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } + } + + /** + * Return true if this node has a Javadoc comment. + * + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment + */ + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; + } + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
{@code
+   * /** ... *}{@code /
+   * // text 1
+   * // text 2
+   * void m() { ... }
+   * }
+ * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); } + } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava index 847d60dbb5e..83889095b0b 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava @@ -34,9 +34,6 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.plumelib.options.Options; - import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -54,6 +51,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -64,945 +62,933 @@ import java.util.regex.Pattern; @org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.common.value.ValueChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no - * problems should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

Trivial getters and setters are of the form: - * - *

{@code
-     * SomeType getFoo() {
-     *   return foo;
-     * }
-     *
-     * SomeType foo() {
-     *   return foo;
-     * }
-     *
-     * void setFoo(SomeType foo) {
-     *   this.foo = foo;
-     * }
-     *
-     * boolean hasFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean isFoo() {
-     *   return foo;
-     * }
-     *
-     * boolean notFoo() {
-     *   return !foo;
-     * }
-     * }
- */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if - * the command-line arguments were absolute pathnames, or no command-line arguments were - * supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", - rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no problems + * should be reported. + */ + public Pattern dont_require = null; + + /** If true, don't check elements with private access. */ + public boolean dont_require_private; + + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

Trivial getters and setters are of the form: + * + *

{@code
+   * SomeType getFoo() {
+   *   return foo;
+   * }
+   *
+   * SomeType foo() {
+   *   return foo;
+   * }
+   *
+   * void setFoo(SomeType foo) {
+   *   this.foo = foo;
+   * }
+   *
+   * boolean hasFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean isFoo() {
+   *   return foo;
+   * }
+   *
+   * boolean notFoo() {
+   *   return !foo;
+   * }
+   * }
+ */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if the + * command-line arguments were absolute pathnames, or no command-line arguments were supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } + for (String error : rj.errors) { + System.out.println(error); + } + System.exit(rj.errors.isEmpty() ? 0 : 1); + } + + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} + + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } + } - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult - visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); - } - } - return FileVisitResult.CONTINUE; + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult + visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); } + } + return FileVisitResult.CONTINUE; + } - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE", "SKIP_SUBTREE"}) FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE", "SKIP_SUBTREE"}) FileVisitResult preVisitDirectory(JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.common.value.qual.BottomVal.class) - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult - postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.common.value.qual.BottomVal.class) + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult + postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.common.value.qual.BottomVal.class) - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult - visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.common.value.qual.BottomVal.class) + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult + visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + } + + /** + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. + * + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element + */ + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; + } + + /** + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. + * + * @param path a Java file or directory + * @return true if the file or directory should be skipped + */ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final @org.checkerframework.common.value.qual.StringVal({"set", "not", "is", "has", "", "get"}) String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams; + + /** The return type. */ + final @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType; /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. + * Create a new PropertyKind. * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; + PropertyKind( + @org.checkerframework.common.value.qual.StringVal({"set", "not", "is", "has", "", "get"}) String prefix, + @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams, + @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; } /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. + * Returns true if this is a getter. * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped + * @return true if this is a getter */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); - } - return result; + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; } /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. + * Return the PropertyKind for the given method, or null if it isn't a property accessor method. * - * @param path a Java file or directory - * @return true if the file or directory should be skipped + * @param md the method to check + * @return the PropertyKind for the given method, or null */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final @org.checkerframework.common.value.qual.StringVal({ - "set", "not", "is", "has", "", "get" - }) String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams; - - /** The return type. */ - final @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType; - - /** - * Create a new PropertyKind. - * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type - */ - PropertyKind( - @org.checkerframework.common.value.qual.StringVal({ - "set", "not", "is", "has", "", "get" - }) - String prefix, - @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams, - @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; - } - - /** - * Returns true if this is a getter. - * - * @return true if this is a getter - */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; - } - - /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor - * method. - * - * @param md the method to check - * @return the PropertyKind for the given method, or null - */ - static @org.checkerframework.common.value.qual.StringVal({ + static @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } + + /** + * Return true if this method declaration is a trivial getter or setter. + * + *
    + *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or + * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} + * or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and + * has a body of the form {@code this.foo = foo}. + *
+ * + * @param md the method to check + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + } + + /** + * Return true if this method declaration is a trivial getter or setter of the given kind. + * + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter + */ + private boolean isTrivialGetterOrSetter( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ "GETTER_NO_PREFIX", "SETTER", "GETTER_NOT", "GETTER_IS", "GETTER_HAS", "GETTER" - }) PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } + }) + PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String + propertyName( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) + PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *

    - *
  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, - * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return - * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, - * and has a body of the form {@code this.foo = foo}. - *
- * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) - PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); + } + + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean + hasCorrectSignature( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) + PropertyKind propertyKind, + @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String - propertyName( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) - PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean - hasCorrectSignature( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) - PropertyKind propertyKind, - @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); } + return true; + } + + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean hasCorrectBody( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) + PropertyKind propertyKind, + @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } + } + + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); + } + + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { + + /** The file being visited. Used for constructing error messages. */ + private Path filename; /** - * Returns true if the body of the given method is a property accessor of the given kind. + * Create a new RequireJavadocVisitor. * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor + * @param filename the file being visited; used for diagnostic messages */ - private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean hasCorrectBody( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) - PropertyKind propertyKind, - @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } + public RequireJavadocVisitor(Path filename) { + this.filename = filename; } /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. + * Return a string stating that documentation is missing on the given construct. * - * @param md a method declaration - * @return its sole statement, or null + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; - - /** - * Create a new RequireJavadocVisitor. - * - * @param filename the file being visited; used for diagnostic messages - */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; - } - - /** - * Return a string stating that documentation is missing on the given construct. - * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct - */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", - path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } - } - - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); - } - - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } - - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); - } + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf( - "skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); - } + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } - public void visit( - RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); - } + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } - /** - * Return true if this method is annotated with {@code @Override}. - * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} - */ - private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean isOverride( - MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); } - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean - hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; + public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); } /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

{@code
-     * /** ... *}{@code /
-     * // text 1
-     * // text 2
-     * void m() { ... }
-     * }
- * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. + * Return true if this method is annotated with {@code @Override}. * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); - } + private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean isOverride( + MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } + } + + /** + * Return true if this node has a Javadoc comment. + * + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment + */ + private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean hasJavadocComment( + Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; + } + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
{@code
+   * /** ... *}{@code /
+   * // text 1
+   * // text 2
+   * void m() { ... }
+   * }
+ * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); } + } } diff --git a/checker/tests/index/AndExample.java b/checker/tests/index/AndExample.java index cbef93875e4..580b3c8b0ef 100644 --- a/checker/tests/index/AndExample.java +++ b/checker/tests/index/AndExample.java @@ -3,14 +3,14 @@ public class AndExample { - @SuppressWarnings("index") // forward reference to field iYearInfoCache - private static final @IndexOrHigh("iYearInfoCache") int CACHE_SIZE = 1 << 10; + @SuppressWarnings("index") // forward reference to field iYearInfoCache + private static final @IndexOrHigh("iYearInfoCache") int CACHE_SIZE = 1 << 10; - private static final @IndexFor("iYearInfoCache") int CACHE_MASK = CACHE_SIZE - 1; + private static final @IndexFor("iYearInfoCache") int CACHE_MASK = CACHE_SIZE - 1; - private static final String[] iYearInfoCache = new String[CACHE_SIZE]; + private static final String[] iYearInfoCache = new String[CACHE_SIZE]; - private String getYearInfo(int year) { - return iYearInfoCache[year & CACHE_MASK]; - } + private String getYearInfo(int year) { + return iYearInfoCache[year & CACHE_MASK]; + } } diff --git a/checker/tests/index/AnnotatedJDKTest.java b/checker/tests/index/AnnotatedJDKTest.java index 9dd1da5a0b4..c8008a159f1 100644 --- a/checker/tests/index/AnnotatedJDKTest.java +++ b/checker/tests/index/AnnotatedJDKTest.java @@ -2,9 +2,9 @@ public class AnnotatedJDKTest { - public void printWriterWrite(PrintWriter writer) { - writer.write(-1); + public void printWriterWrite(PrintWriter writer) { + writer.write(-1); - writer.write(8); - } + writer.write(8); + } } diff --git a/checker/tests/index/ArrayAsList.java b/checker/tests/index/ArrayAsList.java index 5b143003bff..744fca8fffb 100644 --- a/checker/tests/index/ArrayAsList.java +++ b/checker/tests/index/ArrayAsList.java @@ -1,27 +1,26 @@ -import org.checkerframework.common.value.qual.MinLen; - import java.util.Arrays; import java.util.List; +import org.checkerframework.common.value.qual.MinLen; // @skip-test until we bring list support back public class ArrayAsList { - public static void toList(Integer @MinLen(10) [] arg) { - @MinLen(10) List list = Arrays.asList(arg); - System.out.println("Integer: " + list.size()); - } + public static void toList(Integer @MinLen(10) [] arg) { + @MinLen(10) List list = Arrays.asList(arg); + System.out.println("Integer: " + list.size()); + } - public static void toList2(int @MinLen(10) [] arg2) { - // :: error: (assignment.type.incompatible) - @MinLen(10) List list = Arrays.asList(arg2); - System.out.println("int: " + list.size()); + public static void toList2(int @MinLen(10) [] arg2) { + // :: error: (assignment.type.incompatible) + @MinLen(10) List list = Arrays.asList(arg2); + System.out.println("int: " + list.size()); - @MinLen(1) List list2 = Arrays.asList(arg2); - } + @MinLen(1) List list2 = Arrays.asList(arg2); + } - public static void toList3() { - @MinLen(4) List list = Arrays.asList(1, 2, 3, 6); - System.out.println("args: " + list.size()); - } + public static void toList3() { + @MinLen(4) List list = Arrays.asList(1, 2, 3, 6); + System.out.println("args: " + list.size()); + } } diff --git a/checker/tests/index/ArrayAssignmentSameLen.java b/checker/tests/index/ArrayAssignmentSameLen.java index a68a2f288ff..4e861d852fe 100644 --- a/checker/tests/index/ArrayAssignmentSameLen.java +++ b/checker/tests/index/ArrayAssignmentSameLen.java @@ -2,56 +2,56 @@ public class ArrayAssignmentSameLen { - private final int[] i_array; - private final @IndexFor("i_array") int i_index; - - ArrayAssignmentSameLen(int[] array, @IndexFor("#1") int index) { - i_array = array; - i_index = index; - } - - void test1(int[] a, int[] b, @LTEqLengthOf("#1") int index) { - int[] array = a; - @LTLengthOf( - value = {"array", "b"}, - offset = {"0", "-3"}) - // :: error: (assignment.type.incompatible) - int i = index; - } - - void test2(int[] a, int[] b, @LTLengthOf("#1") int i) { - int[] c = a; - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = {"c", "b"}) int x = i; - @LTLengthOf("c") int y = i; - } - - void test3(int[] a, @LTLengthOf("#1") int i, @NonNegative int x) { - int[] c1 = a; - // See useTest3 for an example of why this assignment should fail. - @LTLengthOf( - value = {"c1", "c1"}, - offset = {"0", "x"}) - // :: error: (assignment.type.incompatible) - int z = i; - } - - void test4( - int[] a, - @LTLengthOf( - value = {"#1", "#1"}, - offset = {"0", "#3"}) - int i, - @NonNegative int x) { - int[] c1 = a; - @LTLengthOf( - value = {"c1", "c1"}, - offset = {"0", "x"}) - int z = i; - } - - void useTest3() { - int[] a = {1, 3}; - test3(a, 0, 10); - } + private final int[] i_array; + private final @IndexFor("i_array") int i_index; + + ArrayAssignmentSameLen(int[] array, @IndexFor("#1") int index) { + i_array = array; + i_index = index; + } + + void test1(int[] a, int[] b, @LTEqLengthOf("#1") int index) { + int[] array = a; + @LTLengthOf( + value = {"array", "b"}, + offset = {"0", "-3"}) + // :: error: (assignment.type.incompatible) + int i = index; + } + + void test2(int[] a, int[] b, @LTLengthOf("#1") int i) { + int[] c = a; + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = {"c", "b"}) int x = i; + @LTLengthOf("c") int y = i; + } + + void test3(int[] a, @LTLengthOf("#1") int i, @NonNegative int x) { + int[] c1 = a; + // See useTest3 for an example of why this assignment should fail. + @LTLengthOf( + value = {"c1", "c1"}, + offset = {"0", "x"}) + // :: error: (assignment.type.incompatible) + int z = i; + } + + void test4( + int[] a, + @LTLengthOf( + value = {"#1", "#1"}, + offset = {"0", "#3"}) + int i, + @NonNegative int x) { + int[] c1 = a; + @LTLengthOf( + value = {"c1", "c1"}, + offset = {"0", "x"}) + int z = i; + } + + void useTest3() { + int[] a = {1, 3}; + test3(a, 0, 10); + } } diff --git a/checker/tests/index/ArrayAssignmentSameLenComplex.java b/checker/tests/index/ArrayAssignmentSameLenComplex.java index 5181c7561da..8f95666bae0 100644 --- a/checker/tests/index/ArrayAssignmentSameLenComplex.java +++ b/checker/tests/index/ArrayAssignmentSameLenComplex.java @@ -5,19 +5,19 @@ public class ArrayAssignmentSameLenComplex { - static class Partial { - private final int[] iValues; + static class Partial { + private final int[] iValues; - Partial(@NonNegative int n) { - iValues = new int[n]; - } + Partial(@NonNegative int n) { + iValues = new int[n]; } + } - private final Partial iBase; - private final @IndexFor("iBase.iValues") int iFieldIndex; + private final Partial iBase; + private final @IndexFor("iBase.iValues") int iFieldIndex; - ArrayAssignmentSameLenComplex(Partial partial, @IndexFor("#1.iValues") int fieldIndex) { - iBase = partial; - iFieldIndex = fieldIndex; - } + ArrayAssignmentSameLenComplex(Partial partial, @IndexFor("#1.iValues") int fieldIndex) { + iBase = partial; + iFieldIndex = fieldIndex; + } } diff --git a/checker/tests/index/ArrayConstructionPositiveLength.java b/checker/tests/index/ArrayConstructionPositiveLength.java index 2fd48604fad..c6ee6dcc6cc 100644 --- a/checker/tests/index/ArrayConstructionPositiveLength.java +++ b/checker/tests/index/ArrayConstructionPositiveLength.java @@ -6,7 +6,7 @@ public class ArrayConstructionPositiveLength { - public void makeArray(@Positive int max_values) { - String @MinLen(1) [] a = new String[max_values]; - } + public void makeArray(@Positive int max_values) { + String @MinLen(1) [] a = new String[max_values]; + } } diff --git a/checker/tests/index/ArrayCopy.java b/checker/tests/index/ArrayCopy.java index 10d18580238..b73f95ca37f 100644 --- a/checker/tests/index/ArrayCopy.java +++ b/checker/tests/index/ArrayCopy.java @@ -2,9 +2,9 @@ public class ArrayCopy { - void copy(int @MinLen(1) [] nums) { - int[] nums_copy = new int[nums.length]; - System.arraycopy(nums, 0, nums_copy, 0, nums.length); - nums = nums_copy; - } + void copy(int @MinLen(1) [] nums) { + int[] nums_copy = new int[nums.length]; + System.arraycopy(nums, 0, nums_copy, 0, nums.length); + nums = nums_copy; + } } diff --git a/checker/tests/index/ArrayCreationChecks.java b/checker/tests/index/ArrayCreationChecks.java index 452399e4073..49ae5942738 100644 --- a/checker/tests/index/ArrayCreationChecks.java +++ b/checker/tests/index/ArrayCreationChecks.java @@ -4,47 +4,47 @@ public class ArrayCreationChecks { - void test1(@Positive int x, @Positive int y) { - int[] newArray = new int[x + y]; - @IndexFor("newArray") int i = x; - @IndexFor("newArray") int j = y; - } - - void test2(@NonNegative int x, @Positive int y) { - int[] newArray = new int[x + y]; - @IndexFor("newArray") int i = x; - @IndexOrHigh("newArray") int j = y; - } - - void test3(@NonNegative int x, @NonNegative int y) { - int[] newArray = new int[x + y]; - @IndexOrHigh("newArray") int i = x; - @IndexOrHigh("newArray") int j = y; - } - - void test4(@GTENegativeOne int x, @NonNegative int y) { - // :: error: (array.length.negative) - int[] newArray = new int[x + y]; - @LTEqLengthOf("newArray") int i = x; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int j = y; - } - - void test5(@GTENegativeOne int x, @GTENegativeOne int y) { - // :: error: (array.length.negative) - int[] newArray = new int[x + y]; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int i = x; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int j = y; - } - - void test6(int x, int y) { - // :: error: (array.length.negative) - int[] newArray = new int[x + y]; - // :: error: (assignment.type.incompatible) - @IndexFor("newArray") int i = x; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int j = y; - } + void test1(@Positive int x, @Positive int y) { + int[] newArray = new int[x + y]; + @IndexFor("newArray") int i = x; + @IndexFor("newArray") int j = y; + } + + void test2(@NonNegative int x, @Positive int y) { + int[] newArray = new int[x + y]; + @IndexFor("newArray") int i = x; + @IndexOrHigh("newArray") int j = y; + } + + void test3(@NonNegative int x, @NonNegative int y) { + int[] newArray = new int[x + y]; + @IndexOrHigh("newArray") int i = x; + @IndexOrHigh("newArray") int j = y; + } + + void test4(@GTENegativeOne int x, @NonNegative int y) { + // :: error: (array.length.negative) + int[] newArray = new int[x + y]; + @LTEqLengthOf("newArray") int i = x; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int j = y; + } + + void test5(@GTENegativeOne int x, @GTENegativeOne int y) { + // :: error: (array.length.negative) + int[] newArray = new int[x + y]; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int i = x; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int j = y; + } + + void test6(int x, int y) { + // :: error: (array.length.negative) + int[] newArray = new int[x + y]; + // :: error: (assignment.type.incompatible) + @IndexFor("newArray") int i = x; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int j = y; + } } diff --git a/checker/tests/index/ArrayCreationParam.java b/checker/tests/index/ArrayCreationParam.java index 55375ecbe4f..188cc212201 100644 --- a/checker/tests/index/ArrayCreationParam.java +++ b/checker/tests/index/ArrayCreationParam.java @@ -4,14 +4,14 @@ public class ArrayCreationParam { - public static void m1() { - int n = 5; - int[] a = new int[n + 1]; - // Index Checker correctly issues no warning on the lines below - @LTLengthOf("a") int j = n; - @IndexFor("a") int k = n; - for (int i = 1; i <= n; i++) { - int x = a[i]; - } + public static void m1() { + int n = 5; + int[] a = new int[n + 1]; + // Index Checker correctly issues no warning on the lines below + @LTLengthOf("a") int j = n; + @IndexFor("a") int k = n; + for (int i = 1; i <= n; i++) { + int x = a[i]; } + } } diff --git a/checker/tests/index/ArrayCreationTest.java b/checker/tests/index/ArrayCreationTest.java index 2fa6fd9f0fd..f526c0f7acd 100644 --- a/checker/tests/index/ArrayCreationTest.java +++ b/checker/tests/index/ArrayCreationTest.java @@ -4,8 +4,8 @@ // makes both @SameLen of each other. public class ArrayCreationTest { - void test(int[] a, int[] d) { - int[] b = new int[a.length]; - int @SameLen({"a", "b"}) [] c = b; - } + void test(int[] a, int[] d) { + int[] b = new int[a.length]; + int @SameLen({"a", "b"}) [] c = b; + } } diff --git a/checker/tests/index/ArrayIntro.java b/checker/tests/index/ArrayIntro.java index 51218711aa2..48bb0f62e9c 100644 --- a/checker/tests/index/ArrayIntro.java +++ b/checker/tests/index/ArrayIntro.java @@ -2,18 +2,18 @@ @SuppressWarnings("lowerbound") public class ArrayIntro { - void test() { - int @MinLen(5) [] arr = new int[5]; - int a = 9; - a += 5; - a -= 2; - int @MinLen(12) [] arr1 = new int[a]; - int @MinLen(3) [] arr2 = {1, 2, 3}; - // :: error: (assignment.type.incompatible) - int @MinLen(4) [] arr3 = {4, 5, 6}; - // :: error: (assignment.type.incompatible) - int @MinLen(7) [] arr4 = new int[4]; - // :: error: (assignment.type.incompatible) - int @MinLen(16) [] arr5 = new int[a]; - } + void test() { + int @MinLen(5) [] arr = new int[5]; + int a = 9; + a += 5; + a -= 2; + int @MinLen(12) [] arr1 = new int[a]; + int @MinLen(3) [] arr2 = {1, 2, 3}; + // :: error: (assignment.type.incompatible) + int @MinLen(4) [] arr3 = {4, 5, 6}; + // :: error: (assignment.type.incompatible) + int @MinLen(7) [] arr4 = new int[4]; + // :: error: (assignment.type.incompatible) + int @MinLen(16) [] arr5 = new int[a]; + } } diff --git a/checker/tests/index/ArrayIntroWithCast.java b/checker/tests/index/ArrayIntroWithCast.java index e2068c29fea..b55a4bc60d6 100644 --- a/checker/tests/index/ArrayIntroWithCast.java +++ b/checker/tests/index/ArrayIntroWithCast.java @@ -2,14 +2,14 @@ public class ArrayIntroWithCast { - void test(String[] a, String[] b) { - Object result = new Object[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - } + void test(String[] a, String[] b) { + Object result = new Object[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + } - void test2(String[] a, String[] b) { - @SuppressWarnings("unchecked") - T[] result = (T[]) new Object[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - } + void test2(String[] a, String[] b) { + @SuppressWarnings("unchecked") + T[] result = (T[]) new Object[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + } } diff --git a/checker/tests/index/ArrayLenTest.java b/checker/tests/index/ArrayLenTest.java index 55eebbbba7b..932ed3364ab 100644 --- a/checker/tests/index/ArrayLenTest.java +++ b/checker/tests/index/ArrayLenTest.java @@ -1,13 +1,13 @@ import org.checkerframework.common.value.qual.*; public class ArrayLenTest { - public static String esc_quantify(String @ArrayLen({1, 2}) ... vars) { - if (vars.length == 1) { - return vars[0]; - } else { - @IntVal({2}) int i = vars.length; - String @ArrayLen({2}) [] a = vars; - return vars[0] + vars[1]; - } + public static String esc_quantify(String @ArrayLen({1, 2}) ... vars) { + if (vars.length == 1) { + return vars[0]; + } else { + @IntVal({2}) int i = vars.length; + String @ArrayLen({2}) [] a = vars; + return vars[0] + vars[1]; } + } } diff --git a/checker/tests/index/ArrayLength.java b/checker/tests/index/ArrayLength.java index b646fb2de1d..7dece22d390 100644 --- a/checker/tests/index/ArrayLength.java +++ b/checker/tests/index/ArrayLength.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.index.qual.LTEqLengthOf; public class ArrayLength { - void test() { - int[] arr = {1, 2, 3}; - @LTEqLengthOf({"arr"}) int a = arr.length; - } + void test() { + int[] arr = {1, 2, 3}; + @LTEqLengthOf({"arr"}) int a = arr.length; + } } diff --git a/checker/tests/index/ArrayLength2.java b/checker/tests/index/ArrayLength2.java index 2dd0d8240a0..5fece54fb68 100644 --- a/checker/tests/index/ArrayLength2.java +++ b/checker/tests/index/ArrayLength2.java @@ -5,9 +5,9 @@ import org.checkerframework.common.value.qual.MinLen; public class ArrayLength2 { - public static void main(String[] args) { - int N = 8; - int @MinLen(8) [] Grid = new int[N]; - @LTLengthOf("Grid") int i = 0; - } + public static void main(String[] args) { + int N = 8; + int @MinLen(8) [] Grid = new int[N]; + @LTLengthOf("Grid") int i = 0; + } } diff --git a/checker/tests/index/ArrayLength3.java b/checker/tests/index/ArrayLength3.java index a7588470b5b..4299e5ba82c 100644 --- a/checker/tests/index/ArrayLength3.java +++ b/checker/tests/index/ArrayLength3.java @@ -5,12 +5,12 @@ import org.checkerframework.common.value.qual.ArrayLen; public class ArrayLength3 { - String getFirst(String @ArrayLen(2) [] sa) { - return sa[0]; - } + String getFirst(String @ArrayLen(2) [] sa) { + return sa[0]; + } - void m() { - Integer[] a = new Integer[10]; - @LTLengthOf("a") int i = 5; - } + void m() { + Integer[] a = new Integer[10]; + @LTLengthOf("a") int i = 5; + } } diff --git a/checker/tests/index/ArrayLengthEquality.java b/checker/tests/index/ArrayLengthEquality.java index 13e09dada30..0d209389a84 100644 --- a/checker/tests/index/ArrayLengthEquality.java +++ b/checker/tests/index/ArrayLengthEquality.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.index.qual.SameLen; public class ArrayLengthEquality { - void test(int[] a, int[] b) { - if (a.length == b.length) { - int @SameLen({"a", "b"}) [] c = a; - int @SameLen({"a", "b"}) [] d = b; - } - if (a.length != b.length) { - // Do nothing. - int x = 0; - } else { - int @SameLen({"a", "b"}) [] e = a; - int @SameLen({"a", "b"}) [] f = b; - } + void test(int[] a, int[] b) { + if (a.length == b.length) { + int @SameLen({"a", "b"}) [] c = a; + int @SameLen({"a", "b"}) [] d = b; } + if (a.length != b.length) { + // Do nothing. + int x = 0; + } else { + int @SameLen({"a", "b"}) [] e = a; + int @SameLen({"a", "b"}) [] f = b; + } + } } diff --git a/checker/tests/index/ArrayLengthLBC.java b/checker/tests/index/ArrayLengthLBC.java index b326ddf93ba..84caa60b64a 100644 --- a/checker/tests/index/ArrayLengthLBC.java +++ b/checker/tests/index/ArrayLengthLBC.java @@ -2,12 +2,12 @@ public class ArrayLengthLBC { - public static Date[] add_date(Date[] dates, Date new_date) { - Date[] new_dates = new Date[dates.length + 1]; - System.arraycopy(dates, 0, new_dates, 0, dates.length); - new_dates[dates.length] = new_date; - Date[] new_dates_cast = new_dates; - return (new_dates_cast); - } + public static Date[] add_date(Date[] dates, Date new_date) { + Date[] new_dates = new Date[dates.length + 1]; + System.arraycopy(dates, 0, new_dates, 0, dates.length); + new_dates[dates.length] = new_date; + Date[] new_dates_cast = new_dates; + return (new_dates_cast); + } } // a comment diff --git a/checker/tests/index/ArrayNull.java b/checker/tests/index/ArrayNull.java index bdb6a057d9f..deb5739aa01 100644 --- a/checker/tests/index/ArrayNull.java +++ b/checker/tests/index/ArrayNull.java @@ -1,4 +1,4 @@ public class ArrayNull { - Object[][] a = new Object[][] {new Object[] {null}, null}; + Object[][] a = new Object[][] {new Object[] {null}, null}; } diff --git a/checker/tests/index/ArrayWrapper.java b/checker/tests/index/ArrayWrapper.java index 514bc450d61..88ae8fccfd6 100644 --- a/checker/tests/index/ArrayWrapper.java +++ b/checker/tests/index/ArrayWrapper.java @@ -13,53 +13,53 @@ /** ArrayWrapper is a fixed-size generic collection. */ public class ArrayWrapper { - private final Object @SameLen("this") [] delegate; + private final Object @SameLen("this") [] delegate; - @SuppressWarnings("index") // constructor creates object of size @SameLen(this) by definition - ArrayWrapper(@NonNegative int size) { - delegate = new Object[size]; - } + @SuppressWarnings("index") // constructor creates object of size @SameLen(this) by definition + ArrayWrapper(@NonNegative int size) { + delegate = new Object[size]; + } - public @LengthOf("this") int size() { - return delegate.length; - } + public @LengthOf("this") int size() { + return delegate.length; + } - public void set(@IndexFor("this") int index, T obj) { - delegate[index] = obj; - } + public void set(@IndexFor("this") int index, T obj) { + delegate[index] = obj; + } - @SuppressWarnings("unchecked") // required for normal Java compilation due to unchecked cast - public T get(@IndexFor("this") int index) { - return (T) delegate[index]; - } + @SuppressWarnings("unchecked") // required for normal Java compilation due to unchecked cast + public T get(@IndexFor("this") int index) { + return (T) delegate[index]; + } - public static void clearIndex1(ArrayWrapper a, @IndexFor("#1") int i) { - a.set(i, null); - } + public static void clearIndex1(ArrayWrapper a, @IndexFor("#1") int i) { + a.set(i, null); + } - public static void clearIndex2(ArrayWrapper a, int i) { - if (0 <= i && i < a.size()) { - a.set(i, null); - } + public static void clearIndex2(ArrayWrapper a, int i) { + if (0 <= i && i < a.size()) { + a.set(i, null); } + } - public static void clearIndex3(ArrayWrapper a, @NonNegative int i) { - if (i < a.size()) { - a.set(i, null); - } + public static void clearIndex3(ArrayWrapper a, @NonNegative int i) { + if (i < a.size()) { + a.set(i, null); } + } - // The following methods are tests that sequence annotations work correctly with - // user-defined sequence types. + // The following methods are tests that sequence annotations work correctly with + // user-defined sequence types. - public static Object testSameLen( - @SameLen("#2") ArrayWrapper a, - @SameLen("#1") ArrayWrapper b, - @IndexFor("#1") int i) { - return b.get(i); - } + public static Object testSameLen( + @SameLen("#2") ArrayWrapper a, + @SameLen("#1") ArrayWrapper b, + @IndexFor("#1") int i) { + return b.get(i); + } - public static Object testMinLen(@MinLen(3) ArrayWrapper a) { - return a.get(2); - } + public static Object testMinLen(@MinLen(3) ArrayWrapper a) { + return a.get(2); + } } diff --git a/checker/tests/index/ArraysSort.java b/checker/tests/index/ArraysSort.java index d8963d05b84..a9bdc139666 100644 --- a/checker/tests/index/ArraysSort.java +++ b/checker/tests/index/ArraysSort.java @@ -1,13 +1,12 @@ -import org.checkerframework.common.value.qual.MinLen; - import java.util.Arrays; +import org.checkerframework.common.value.qual.MinLen; public class ArraysSort { - void sortInt(int @MinLen(10) [] nums) { - // Checks the correct handling of the toIndex parameter - Arrays.sort(nums, 0, 10); - // :: error: (argument.type.incompatible) - Arrays.sort(nums, 0, 11); - } + void sortInt(int @MinLen(10) [] nums) { + // Checks the correct handling of the toIndex parameter + Arrays.sort(nums, 0, 10); + // :: error: (argument.type.incompatible) + Arrays.sort(nums, 0, 11); + } } diff --git a/checker/tests/index/BasicSubsequence.java b/checker/tests/index/BasicSubsequence.java index e021f25df13..2c34408b475 100644 --- a/checker/tests/index/BasicSubsequence.java +++ b/checker/tests/index/BasicSubsequence.java @@ -1,47 +1,47 @@ import org.checkerframework.checker.index.qual.*; public class BasicSubsequence { - // :: error: not.final - @HasSubsequence(subsequence = "this", from = "this.x", to = "this.y") - int[] b; - - int x; - int y; - - void test2(@NonNegative @LessThan("y + 1") int x1, int[] a) { - x = x1; - // :: error: to.not.ltel - b = a; - } - - void test3(@NonNegative @LessThan("y") int x1, int[] a) { - x = x1; - // :: error: to.not.ltel - b = a; - } - - void test4(@NonNegative int x1, int[] a) { - x = x1; - // :: error: from.gt.to :: error: to.not.ltel - b = a; - } - - void test5(@GTENegativeOne @LessThan("y + 1") int x1, int[] a) { - x = x1; - // :: error: from.not.nonnegative :: error: to.not.ltel - b = a; - } - - void test6(@GTENegativeOne int x1, int[] a) { - x = x1; - // :: error: from.not.nonnegative :: error: to.not.ltel :: error: from.gt.to - b = a; - } - - void test7(@IndexFor("this") @LessThan("y") int x1, @IndexOrHigh("this") int y1, int[] a) { - x = x1; - y = y1; - // :: warning: which.subsequence - b = a; - } + // :: error: not.final + @HasSubsequence(subsequence = "this", from = "this.x", to = "this.y") + int[] b; + + int x; + int y; + + void test2(@NonNegative @LessThan("y + 1") int x1, int[] a) { + x = x1; + // :: error: to.not.ltel + b = a; + } + + void test3(@NonNegative @LessThan("y") int x1, int[] a) { + x = x1; + // :: error: to.not.ltel + b = a; + } + + void test4(@NonNegative int x1, int[] a) { + x = x1; + // :: error: from.gt.to :: error: to.not.ltel + b = a; + } + + void test5(@GTENegativeOne @LessThan("y + 1") int x1, int[] a) { + x = x1; + // :: error: from.not.nonnegative :: error: to.not.ltel + b = a; + } + + void test6(@GTENegativeOne int x1, int[] a) { + x = x1; + // :: error: from.not.nonnegative :: error: to.not.ltel :: error: from.gt.to + b = a; + } + + void test7(@IndexFor("this") @LessThan("y") int x1, @IndexOrHigh("this") int y1, int[] a) { + x = x1; + y = y1; + // :: warning: which.subsequence + b = a; + } } diff --git a/checker/tests/index/BasicSubsequence2.java b/checker/tests/index/BasicSubsequence2.java index 2b572221c98..6ac9ccc08eb 100644 --- a/checker/tests/index/BasicSubsequence2.java +++ b/checker/tests/index/BasicSubsequence2.java @@ -3,34 +3,34 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class BasicSubsequence2 { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - int[] array; + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + int[] array; - @HasSubsequence(subsequence = "this", from = "start", to = "end") - int[] array2; + @HasSubsequence(subsequence = "this", from = "start", to = "end") + int[] array2; - final @IndexFor("array") int start; + final @IndexFor("array") int start; - final @IndexOrHigh("array") int end; + final @IndexOrHigh("array") int end; - private BasicSubsequence2(@IndexFor("array") int s, @IndexOrHigh("array") int e) { - start = s; - end = e; - } + private BasicSubsequence2(@IndexFor("array") int s, @IndexOrHigh("array") int e) { + start = s; + end = e; + } - void testStartIndex(@IndexFor("this") int x) { - @IndexFor("array") int y = x + start; - } + void testStartIndex(@IndexFor("this") int x) { + @IndexFor("array") int y = x + start; + } - void testViewpointAdaption(@IndexFor("this") int x) { - @IndexFor("array2") int y = x + start; - } + void testViewpointAdaption(@IndexFor("this") int x) { + @IndexFor("array2") int y = x + start; + } - void testArrayAccess(@IndexFor("this") int x) { - int y = array[x + start]; - } + void testArrayAccess(@IndexFor("this") int x) { + int y = array[x + start]; + } - void testCommutativity(@IndexFor("this") int x) { - @IndexFor("array") int y = start + x; - } + void testCommutativity(@IndexFor("this") int x) { + @IndexFor("array") int y = start + x; + } } diff --git a/checker/tests/index/BasicSubsequence3.java b/checker/tests/index/BasicSubsequence3.java index bb25d8058c1..b99257052b8 100644 --- a/checker/tests/index/BasicSubsequence3.java +++ b/checker/tests/index/BasicSubsequence3.java @@ -5,26 +5,26 @@ @SuppressWarnings("lowerbound") public class BasicSubsequence3 { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - int[] array; + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + int[] array; - @HasSubsequence(subsequence = "this", from = "start", to = "end") - int[] array2; + @HasSubsequence(subsequence = "this", from = "start", to = "end") + int[] array2; - final @IndexFor("array") int start; + final @IndexFor("array") int start; - final @IndexOrHigh("array") int end; + final @IndexOrHigh("array") int end; - private BasicSubsequence3(@IndexFor("array") int s, @IndexOrHigh("array") int e) { - start = s; - end = e; - } + private BasicSubsequence3(@IndexFor("array") int s, @IndexOrHigh("array") int e) { + start = s; + end = e; + } - void testStartIndex(@IndexFor("array") @LessThan("this.end") int x) { - @IndexFor("this") int y = x - start; - } + void testStartIndex(@IndexFor("array") @LessThan("this.end") int x) { + @IndexFor("this") int y = x - start; + } - void testViewpointAdaption(@IndexFor("array2") @LessThan("end") int x) { - @IndexFor("this") int y = x - start; - } + void testViewpointAdaption(@IndexFor("array2") @LessThan("end") int x) { + @IndexFor("this") int y = x - start; + } } diff --git a/checker/tests/index/BigBinaryExpr.java b/checker/tests/index/BigBinaryExpr.java index 318cdb9cd8f..039f7f2a8d8 100644 --- a/checker/tests/index/BigBinaryExpr.java +++ b/checker/tests/index/BigBinaryExpr.java @@ -1,111 +1,111 @@ public class BigBinaryExpr { - void test() { - int i0 = 163; - int i1 = 153; - int i2 = 75; - int i3 = -72; - int i4 = 61; - int i5 = 7; - int i6 = 83; - int i7 = -36; - int i8 = -90; - int i9 = -93; - int i10 = 187; - int i11 = -76; - int i12 = -16; - int i13 = -99; - int i14 = 113; - int i15 = 72; - int i16 = 58; - int i17 = -97; - int i18 = 115; - int i19 = -85; - int i20 = 156; - int i21 = -10; - int i22 = -85; - int i23 = 81; - int i24 = 63; - int i25 = -49; - int i26 = 158; - int i27 = 158; - int i28 = 25; - int i29 = 136; - int i30 = -90; - int i31 = 115; - int i32 = 179; - int i33 = 11; - int i34 = -100; - int i35 = 70; - int i36 = -46; - int i37 = -56; - int i38 = 108; - int i39 = -41; - int i40 = 124; - int i41 = -88; - int i42 = 54; - int i43 = 117; - int i44 = -92; - int i45 = 7; - int i46 = -94; - int i47 = 162; - int i48 = -34; - int i49 = 104; - int i50 = 111; - int i51 = -16; - int i52 = 197; - int i53 = -8; - int i54 = 101; - int i55 = 96; - int i56 = 132; - int i57 = -36; - int i58 = 148; - int i59 = 43; - int i60 = -59; - int i61 = 150; - int i62 = 48; - int i63 = 130; - int i64 = 74; - int i65 = -1; - int i66 = 79; - int i67 = 109; - int i68 = -70; - int i69 = 111; - int i70 = 78; - int i71 = 155; - int i72 = 176; - int i73 = 80; - int i74 = 181; - int i75 = 41; - int i76 = -85; - int i77 = 189; - int i78 = 97; - int i79 = 139; - int i80 = 9; - int i81 = 42; - int i82 = -50; - int i83 = 82; - int i84 = -70; - int i85 = 162; - int i86 = -20; - int i87 = 52; - int i88 = -94; - int i89 = 133; - int i90 = 136; - int i91 = 129; - int i92 = -55; - int i93 = 153; - int i94 = 6; - int i95 = -18; - int i96 = 132; - int i97 = 45; - int i98 = 120; - int i99 = 60; - // int result = i0 +i1 +i2 +i3 +i4 +i5 +i6 +i7 +i8 +i9 +i10 +i11 +i12 +i13 +i14 +i15 +i16 - // +i17 +i18 +i19 +i20 +i21 +i22 +i23 +i24 +i25 +i26 +i27 +i28 +i29 +i30 +i31 +i32 +i33 +i34 - // +i35 +i36 +i37 +i38 +i39 +i40 +i41 +i42 +i43 +i44 +i45 +i46 +i47 +i48 +i49 +i50 +i51 +i52 - // +i53 +i54 +i55 +i56 +i57 +i58 +i59 +i60 +i61 +i62 +i63 +i64 +i65 +i66 +i67 +i68 +i69 +i70 - // +i71 +i72 +i73 +i74 +i75 +i76 +i77 +i78 +i79 +i80 +i81 +i82 +i83 +i84 +i85 +i86 +i87 +i88 - // +i89 +i90 +i91 +i92 +i93 +i94 +i95 +i96 +i97 +i98 +i99; - } + void test() { + int i0 = 163; + int i1 = 153; + int i2 = 75; + int i3 = -72; + int i4 = 61; + int i5 = 7; + int i6 = 83; + int i7 = -36; + int i8 = -90; + int i9 = -93; + int i10 = 187; + int i11 = -76; + int i12 = -16; + int i13 = -99; + int i14 = 113; + int i15 = 72; + int i16 = 58; + int i17 = -97; + int i18 = 115; + int i19 = -85; + int i20 = 156; + int i21 = -10; + int i22 = -85; + int i23 = 81; + int i24 = 63; + int i25 = -49; + int i26 = 158; + int i27 = 158; + int i28 = 25; + int i29 = 136; + int i30 = -90; + int i31 = 115; + int i32 = 179; + int i33 = 11; + int i34 = -100; + int i35 = 70; + int i36 = -46; + int i37 = -56; + int i38 = 108; + int i39 = -41; + int i40 = 124; + int i41 = -88; + int i42 = 54; + int i43 = 117; + int i44 = -92; + int i45 = 7; + int i46 = -94; + int i47 = 162; + int i48 = -34; + int i49 = 104; + int i50 = 111; + int i51 = -16; + int i52 = 197; + int i53 = -8; + int i54 = 101; + int i55 = 96; + int i56 = 132; + int i57 = -36; + int i58 = 148; + int i59 = 43; + int i60 = -59; + int i61 = 150; + int i62 = 48; + int i63 = 130; + int i64 = 74; + int i65 = -1; + int i66 = 79; + int i67 = 109; + int i68 = -70; + int i69 = 111; + int i70 = 78; + int i71 = 155; + int i72 = 176; + int i73 = 80; + int i74 = 181; + int i75 = 41; + int i76 = -85; + int i77 = 189; + int i78 = 97; + int i79 = 139; + int i80 = 9; + int i81 = 42; + int i82 = -50; + int i83 = 82; + int i84 = -70; + int i85 = 162; + int i86 = -20; + int i87 = 52; + int i88 = -94; + int i89 = 133; + int i90 = 136; + int i91 = 129; + int i92 = -55; + int i93 = 153; + int i94 = 6; + int i95 = -18; + int i96 = 132; + int i97 = 45; + int i98 = 120; + int i99 = 60; + // int result = i0 +i1 +i2 +i3 +i4 +i5 +i6 +i7 +i8 +i9 +i10 +i11 +i12 +i13 +i14 +i15 +i16 + // +i17 +i18 +i19 +i20 +i21 +i22 +i23 +i24 +i25 +i26 +i27 +i28 +i29 +i30 +i31 +i32 +i33 +i34 + // +i35 +i36 +i37 +i38 +i39 +i40 +i41 +i42 +i43 +i44 +i45 +i46 +i47 +i48 +i49 +i50 +i51 +i52 + // +i53 +i54 +i55 +i56 +i57 +i58 +i59 +i60 +i61 +i62 +i63 +i64 +i65 +i66 +i67 +i68 +i69 +i70 + // +i71 +i72 +i73 +i74 +i75 +i76 +i77 +i78 +i79 +i80 +i81 +i82 +i83 +i84 +i85 +i86 +i87 +i88 + // +i89 +i90 +i91 +i92 +i93 +i94 +i95 +i96 +i97 +i98 +i99; + } } // a comment diff --git a/checker/tests/index/BinarySearchTest.java b/checker/tests/index/BinarySearchTest.java index 267901e5b60..df6ea8bab09 100644 --- a/checker/tests/index/BinarySearchTest.java +++ b/checker/tests/index/BinarySearchTest.java @@ -1,42 +1,40 @@ -import org.checkerframework.checker.index.qual.*; - import java.util.Arrays; +import org.checkerframework.checker.index.qual.*; public class BinarySearchTest { - private final long @SameLen("iNameKeys") [] iTransitions; - private final String @SameLen("iTransitions") [] iNameKeys; + private final long @SameLen("iNameKeys") [] iTransitions; + private final String @SameLen("iTransitions") [] iNameKeys; - private BinarySearchTest( - long @SameLen("iNameKeys") [] transitions, - String @SameLen("iTransitions") [] nameKeys) { - iTransitions = transitions; - iNameKeys = nameKeys; - } + private BinarySearchTest( + long @SameLen("iNameKeys") [] transitions, String @SameLen("iTransitions") [] nameKeys) { + iTransitions = transitions; + iNameKeys = nameKeys; + } - public String getNameKey(long instant) { - long[] transitions = iTransitions; - int i = Arrays.binarySearch(transitions, instant); - if (i >= 0) { - return iNameKeys[i]; - } - i = ~i; - if (i > 0) { - return iNameKeys[i - 1]; - } - return ""; + public String getNameKey(long instant) { + long[] transitions = iTransitions; + int i = Arrays.binarySearch(transitions, instant); + if (i >= 0) { + return iNameKeys[i]; + } + i = ~i; + if (i > 0) { + return iNameKeys[i - 1]; } + return ""; + } - public String getNameKey2(long instant) { - long[] transitions = iTransitions; - int i = Arrays.binarySearch(transitions, instant); - if (i >= 0) { - return iNameKeys[i]; - } - i = ~i; - if (i < iNameKeys.length) { - return iNameKeys[i]; - } - return ""; + public String getNameKey2(long instant) { + long[] transitions = iTransitions; + int i = Arrays.binarySearch(transitions, instant); + if (i >= 0) { + return iNameKeys[i]; + } + i = ~i; + if (i < iNameKeys.length) { + return iNameKeys[i]; } + return ""; + } } diff --git a/checker/tests/index/BinomialTest.java b/checker/tests/index/BinomialTest.java index 33396da60cc..dc2f72e4ef2 100644 --- a/checker/tests/index/BinomialTest.java +++ b/checker/tests/index/BinomialTest.java @@ -3,71 +3,71 @@ public class BinomialTest { - static final long @MinLen(1) [] factorials = {1L, 1L, 1L * 2}; + static final long @MinLen(1) [] factorials = {1L, 1L, 1L * 2}; - public static long binomial( - @NonNegative @LTLengthOf("BinomialTest.factorials") int n, - @NonNegative @LessThan("#1 + 1") int k) { - return factorials[k]; - } + public static long binomial( + @NonNegative @LTLengthOf("BinomialTest.factorials") int n, + @NonNegative @LessThan("#1 + 1") int k) { + return factorials[k]; + } - public static void binomial0( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { - @LTLengthOf(value = "factorials", offset = "1") int i = k; - } + public static void binomial0( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { + @LTLengthOf(value = "factorials", offset = "1") int i = k; + } - public static void binomial0Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "2") int i = k; - } + public static void binomial0Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "2") int i = k; + } - public static void binomial0Weak( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { - @LTLengthOf("factorials") int i = k; - } + public static void binomial0Weak( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { + @LTLengthOf("factorials") int i = k; + } - public static void binomial1( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { - @LTLengthOf("factorials") int i = k; - } + public static void binomial1( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { + @LTLengthOf("factorials") int i = k; + } - public static void binomial1Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "1") int i = k; - } + public static void binomial1Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "1") int i = k; + } - public static void binomial2( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { - @LTLengthOf(value = "factorials", offset = "-1") int i = k; - } + public static void binomial2( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { + @LTLengthOf(value = "factorials", offset = "-1") int i = k; + } - public static void binomial2Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "0") int i = k; - } + public static void binomial2Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "0") int i = k; + } - public static void binomial_1( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { - @LTLengthOf(value = "factorials", offset = "2") int i = k; - } + public static void binomial_1( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { + @LTLengthOf(value = "factorials", offset = "2") int i = k; + } - public static void binomial_1Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "3") int i = k; - } + public static void binomial_1Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "3") int i = k; + } - public static void binomial_2( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { - @LTLengthOf(value = "factorials", offset = "3") int i = k; - } + public static void binomial_2( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { + @LTLengthOf(value = "factorials", offset = "3") int i = k; + } - public static void binomial_2Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "4") int i = k; - } + public static void binomial_2Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "4") int i = k; + } } diff --git a/checker/tests/index/BitSetLowerBound.java b/checker/tests/index/BitSetLowerBound.java index c08e56fc58a..a95160e901a 100644 --- a/checker/tests/index/BitSetLowerBound.java +++ b/checker/tests/index/BitSetLowerBound.java @@ -1,20 +1,19 @@ // Test case for Issue 185: // https://github.com/typetools/kelloggm/issues/185 -import org.checkerframework.checker.index.qual.GTENegativeOne; - import java.util.BitSet; +import org.checkerframework.checker.index.qual.GTENegativeOne; public class BitSetLowerBound { - private void m(BitSet b) { - b.set(b.nextClearBit(0)); - // next set bit does not have to exist - // :: error: (argument.type.incompatible) - b.clear(b.nextSetBit(0)); - @GTENegativeOne int i = b.nextSetBit(0); + private void m(BitSet b) { + b.set(b.nextClearBit(0)); + // next set bit does not have to exist + // :: error: (argument.type.incompatible) + b.clear(b.nextSetBit(0)); + @GTENegativeOne int i = b.nextSetBit(0); - @GTENegativeOne int j = b.previousClearBit(-1); - @GTENegativeOne int k = b.previousSetBit(-1); - } + @GTENegativeOne int j = b.previousClearBit(-1); + @GTENegativeOne int k = b.previousSetBit(-1); + } } diff --git a/checker/tests/index/Boilerplate.java b/checker/tests/index/Boilerplate.java index 1acdaad6fcc..e4a84fc7cc9 100644 --- a/checker/tests/index/Boilerplate.java +++ b/checker/tests/index/Boilerplate.java @@ -2,9 +2,9 @@ public class Boilerplate { - void test() { - // :: error: (assignment.type.incompatible) - @Positive int a = -1; - } + void test() { + // :: error: (assignment.type.incompatible) + @Positive int a = -1; + } } // a comment diff --git a/checker/tests/index/BottomValTest.java b/checker/tests/index/BottomValTest.java index 51bbd36a9f2..213ddcef6b5 100644 --- a/checker/tests/index/BottomValTest.java +++ b/checker/tests/index/BottomValTest.java @@ -2,15 +2,15 @@ import org.checkerframework.common.value.qual.*; public class BottomValTest { - @NonNegative int foo(@BottomVal int bottom) { - return bottom; - } + @NonNegative int foo(@BottomVal int bottom) { + return bottom; + } - @Positive int bar(@BottomVal int bottom) { - return bottom; - } + @Positive int bar(@BottomVal int bottom) { + return bottom; + } - @LTLengthOf("#1") int baz(int[] a, @BottomVal int bottom) { - return bottom; - } + @LTLengthOf("#1") int baz(int[] a, @BottomVal int bottom) { + return bottom; + } } diff --git a/checker/tests/index/CastArray.java b/checker/tests/index/CastArray.java index 0ffb750b222..c6c779774ae 100644 --- a/checker/tests/index/CastArray.java +++ b/checker/tests/index/CastArray.java @@ -1,5 +1,5 @@ public class CastArray { - void test(Object a) { - int[] b = (int[]) a; - } + void test(Object a) { + int[] b = (int[]) a; + } } diff --git a/checker/tests/index/CharPrintedAsVariable.java b/checker/tests/index/CharPrintedAsVariable.java index b3eea0fd25d..a143eae338c 100644 --- a/checker/tests/index/CharPrintedAsVariable.java +++ b/checker/tests/index/CharPrintedAsVariable.java @@ -1,15 +1,15 @@ // Test case for https://github.com/typetools/checker-framework/issues/3167 . public class CharPrintedAsVariable { - void m1(char c) { - if (c <= 'A') { - int x = (int) c; - } + void m1(char c) { + if (c <= 'A') { + int x = (int) c; } + } - void m2(char c) { - if (c <= '\377') { - int x = (int) c; - } + void m2(char c) { + if (c <= '\377') { + int x = (int) c; } + } } diff --git a/checker/tests/index/CharSequenceTest.java b/checker/tests/index/CharSequenceTest.java index d741b721c13..17a5254f58d 100644 --- a/checker/tests/index/CharSequenceTest.java +++ b/checker/tests/index/CharSequenceTest.java @@ -1,82 +1,81 @@ // Tests suport for index annotations applied to CharSequence and related indices. +import java.io.IOException; +import java.io.StringWriter; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.common.value.qual.MinLen; import org.checkerframework.common.value.qual.StringVal; -import java.io.IOException; -import java.io.StringWriter; - public class CharSequenceTest { - // Tests that minlen is correctly applied to CharSequence assigned from String, but not - // StringBuilder - void minLenCharSequence() { - @MinLen(10) CharSequence str = "0123456789"; - // :: error: (assignment.type.incompatible) - @MinLen(10) CharSequence sb = new StringBuilder("0123456789"); - } + // Tests that minlen is correctly applied to CharSequence assigned from String, but not + // StringBuilder + void minLenCharSequence() { + @MinLen(10) CharSequence str = "0123456789"; + // :: error: (assignment.type.incompatible) + @MinLen(10) CharSequence sb = new StringBuilder("0123456789"); + } - // Tests the subSequence method - void testSubSequence() { - // Local variable used because of https://github.com/kelloggm/checker-framework/issues/165 - String str = "0123456789"; - str.subSequence(5, 8); - // :: error: (argument.type.incompatible) - str.subSequence(5, 13); - } + // Tests the subSequence method + void testSubSequence() { + // Local variable used because of https://github.com/kelloggm/checker-framework/issues/165 + String str = "0123456789"; + str.subSequence(5, 8); + // :: error: (argument.type.incompatible) + str.subSequence(5, 13); + } - // Dummy method that takes a CharSequence and its index - void sink(CharSequence cs, @IndexOrHigh("#1") int i) {} + // Dummy method that takes a CharSequence and its index + void sink(CharSequence cs, @IndexOrHigh("#1") int i) {} - // Tests passing sequences as CharSequence - void argumentPassing() { - String s = "0123456789"; - sink(s, 8); - StringBuilder sb = new StringBuilder("0123456789"); - // :: error: (argument.type.incompatible) - sink(sb, 8); - } + // Tests passing sequences as CharSequence + void argumentPassing() { + String s = "0123456789"; + sink(s, 8); + StringBuilder sb = new StringBuilder("0123456789"); + // :: error: (argument.type.incompatible) + sink(sb, 8); + } - // Tests forwardning sequences as CharSequence - void agumentForwarding(String s, @IndexOrHigh("#1") int i) { - sink(s, i); - } - - // Tests concatenation of CharSequence and String - void concat() { - CharSequence a = "a"; - @StringVal({"nullb", "ab"}) CharSequence ab = a + "b"; - sink(ab, 2); - } + // Tests forwardning sequences as CharSequence + void agumentForwarding(String s, @IndexOrHigh("#1") int i) { + sink(s, i); + } - // Tests that length retrieved from CharSequence can be used as an index - void getLength(CharSequence cs, int i) { - if (i >= 0 && i < cs.length()) { - cs.charAt(i); - } + // Tests concatenation of CharSequence and String + void concat() { + CharSequence a = "a"; + @StringVal({"nullb", "ab"}) CharSequence ab = a + "b"; + sink(ab, 2); + } - @IndexOrHigh("cs") int l = cs.length(); + // Tests that length retrieved from CharSequence can be used as an index + void getLength(CharSequence cs, int i) { + if (i >= 0 && i < cs.length()) { + cs.charAt(i); } - void testCharAt(CharSequence cs, int i, @IndexFor("#1") int j) { - cs.charAt(j); - cs.subSequence(j, j); - // :: error: (argument.type.incompatible) - cs.charAt(i); - // :: error: (argument.type.incompatible) - cs.subSequence(i, j); - } + @IndexOrHigh("cs") int l = cs.length(); + } - void testAppend(Appendable app, CharSequence cs, @IndexFor("#2") int i) throws IOException { - app.append(cs, i, i); - // :: error: (argument.type.incompatible) - app.append(cs, 1, 2); - } + void testCharAt(CharSequence cs, int i, @IndexFor("#1") int j) { + cs.charAt(j); + cs.subSequence(j, j); + // :: error: (argument.type.incompatible) + cs.charAt(i); + // :: error: (argument.type.incompatible) + cs.subSequence(i, j); + } - void testAppend(StringWriter app, CharSequence cs, @IndexFor("#2") int i) throws IOException { - app.append(cs, i, i); - // :: error: (argument.type.incompatible) - app.append(cs, 1, 2); - } + void testAppend(Appendable app, CharSequence cs, @IndexFor("#2") int i) throws IOException { + app.append(cs, i, i); + // :: error: (argument.type.incompatible) + app.append(cs, 1, 2); + } + + void testAppend(StringWriter app, CharSequence cs, @IndexFor("#2") int i) throws IOException { + app.append(cs, i, i); + // :: error: (argument.type.incompatible) + app.append(cs, 1, 2); + } } diff --git a/checker/tests/index/CharToIntCast.java b/checker/tests/index/CharToIntCast.java index 61359040de3..1344b43b571 100644 --- a/checker/tests/index/CharToIntCast.java +++ b/checker/tests/index/CharToIntCast.java @@ -4,15 +4,15 @@ public class CharToIntCast { - public static void charRange(char c) { - @IntRange(from = 0, to = Character.MAX_VALUE) int i = c; - } + public static void charRange(char c) { + @IntRange(from = 0, to = Character.MAX_VALUE) int i = c; + } - public static void charShift(char c) { - char c2 = (char) (c >> 4); - } + public static void charShift(char c) { + char c2 = (char) (c >> 4); + } - public static void rangeShiftOk(@IntRange(from = 0, to = Character.MAX_VALUE) int i) { - char c2 = (char) (i >> 4); - } + public static void rangeShiftOk(@IntRange(from = 0, to = Character.MAX_VALUE) int i) { + char c2 = (char) (i >> 4); + } } diff --git a/checker/tests/index/CheckAgainstNegativeOne.java b/checker/tests/index/CheckAgainstNegativeOne.java index 6c61942bfa0..211e9221546 100644 --- a/checker/tests/index/CheckAgainstNegativeOne.java +++ b/checker/tests/index/CheckAgainstNegativeOne.java @@ -2,20 +2,20 @@ public class CheckAgainstNegativeOne { - public static String replaceString(String target, String oldStr, String newStr) { - if (oldStr.equals("")) { - throw new IllegalArgumentException(); - } + public static String replaceString(String target, String oldStr, String newStr) { + if (oldStr.equals("")) { + throw new IllegalArgumentException(); + } - StringBuffer result = new StringBuffer(); - @IndexOrHigh("target") int lastend = 0; - int pos; - while ((pos = target.indexOf(oldStr, lastend)) != -1) { - result.append(target.substring(lastend, pos)); - result.append(newStr); - lastend = pos + oldStr.length(); - } - result.append(target.substring(lastend)); - return result.toString(); + StringBuffer result = new StringBuffer(); + @IndexOrHigh("target") int lastend = 0; + int pos; + while ((pos = target.indexOf(oldStr, lastend)) != -1) { + result.append(target.substring(lastend, pos)); + result.append(newStr); + lastend = pos + oldStr.length(); } + result.append(target.substring(lastend)); + return result.toString(); + } } diff --git a/checker/tests/index/CheckNotNull1.java b/checker/tests/index/CheckNotNull1.java index afd2b7c418e..dcc18547290 100644 --- a/checker/tests/index/CheckNotNull1.java +++ b/checker/tests/index/CheckNotNull1.java @@ -1,9 +1,9 @@ public class CheckNotNull1 { - T checkNotNull(T ref) { - return ref; - } + T checkNotNull(T ref) { + return ref; + } - void test(S ref) { - checkNotNull(ref); - } + void test(S ref) { + checkNotNull(ref); + } } diff --git a/checker/tests/index/CheckNotNull2.java b/checker/tests/index/CheckNotNull2.java index 8304f277582..03047e6bf69 100644 --- a/checker/tests/index/CheckNotNull2.java +++ b/checker/tests/index/CheckNotNull2.java @@ -1,9 +1,9 @@ public class CheckNotNull2 { - T checkNotNull(T ref) { - return ref; - } + T checkNotNull(T ref) { + return ref; + } - void test(T ref) { - checkNotNull(ref); - } + void test(T ref) { + checkNotNull(ref); + } } diff --git a/checker/tests/index/CombineFacts.java b/checker/tests/index/CombineFacts.java index 70a88ccc56e..817eecb4386 100644 --- a/checker/tests/index/CombineFacts.java +++ b/checker/tests/index/CombineFacts.java @@ -2,14 +2,14 @@ @SuppressWarnings("lowerbound") public class CombineFacts { - void test(int[] a1) { - @LTLengthOf("a1") int len = a1.length - 1; - int[] a2 = new int[len]; - a2[len - 1] = 1; - a1[len] = 1; + void test(int[] a1) { + @LTLengthOf("a1") int len = a1.length - 1; + int[] a2 = new int[len]; + a2[len - 1] = 1; + a1[len] = 1; - // This access should issue an error. - // :: error: (array.access.unsafe.high) - a2[len] = 1; - } + // This access should issue an error. + // :: error: (array.access.unsafe.high) + a2[len] = 1; + } } diff --git a/checker/tests/index/CompareBySubtraction.java b/checker/tests/index/CompareBySubtraction.java index 20912bad226..8ac834cccb8 100644 --- a/checker/tests/index/CompareBySubtraction.java +++ b/checker/tests/index/CompareBySubtraction.java @@ -1,20 +1,20 @@ // @skip-test until fixed. public class CompareBySubtraction { - public int compare(int[] a1, int[] a2) { - if (a1 == a2) { - return 0; - } - int tmp; - tmp = a1.length - a2.length; - if (tmp != 0) { - return tmp; - } - for (int i = 0; i < a1.length; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); - } - } - return 0; + public int compare(int[] a1, int[] a2) { + if (a1 == a2) { + return 0; } + int tmp; + tmp = a1.length - a2.length; + if (tmp != 0) { + return tmp; + } + for (int i = 0; i < a1.length; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); + } + } + return 0; + } } diff --git a/checker/tests/index/CompoundAssignmentCheck.java b/checker/tests/index/CompoundAssignmentCheck.java index 7518d9c4668..65f577b1113 100644 --- a/checker/tests/index/CompoundAssignmentCheck.java +++ b/checker/tests/index/CompoundAssignmentCheck.java @@ -1,8 +1,8 @@ public class CompoundAssignmentCheck { - void test() { - int a = 9; - a += 5; - a -= 2; - int[] arr5 = new int[a]; // LBC shouldn't warn here. - } + void test() { + int a = 9; + a += 5; + a -= 2; + int[] arr5 = new int[a]; // LBC shouldn't warn here. + } } diff --git a/checker/tests/index/ComputeConst.java b/checker/tests/index/ComputeConst.java index 851dcdc53dc..2616ddbc8da 100644 --- a/checker/tests/index/ComputeConst.java +++ b/checker/tests/index/ComputeConst.java @@ -1,11 +1,11 @@ public class ComputeConst { - public static int hash(long l) { - // If possible, use the value itself. - if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { - return (int) l; - } - - return Long.hashCode(l); + public static int hash(long l) { + // If possible, use the value itself. + if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { + return (int) l; } + + return Long.hashCode(l); + } } diff --git a/checker/tests/index/ConditionalIndex.java b/checker/tests/index/ConditionalIndex.java index cace653309a..1ee3ec1d318 100644 --- a/checker/tests/index/ConditionalIndex.java +++ b/checker/tests/index/ConditionalIndex.java @@ -1,15 +1,15 @@ // test case for issue 162: https://github.com/kelloggm/checker-framework/issues/162 public class ConditionalIndex { - public void f(boolean cond) { - int[] a = new int[10]; - int[] b = new int[1]; - if (cond) { - int[] c = a; - } else { - int[] c = b; - } - - int[] d = (cond ? a : b); + public void f(boolean cond) { + int[] a = new int[10]; + int[] b = new int[1]; + if (cond) { + int[] c = a; + } else { + int[] c = b; } + + int[] d = (cond ? a : b); + } } diff --git a/checker/tests/index/ConstantArrays.java b/checker/tests/index/ConstantArrays.java index c6a56da5ca7..b044098891f 100644 --- a/checker/tests/index/ConstantArrays.java +++ b/checker/tests/index/ConstantArrays.java @@ -1,33 +1,33 @@ import org.checkerframework.checker.index.qual.*; public class ConstantArrays { - void basic_test() { - int[] b = new int[4]; - @LTLengthOf("b") int[] a = {0, 1, 2, 3}; + void basic_test() { + int[] b = new int[4]; + @LTLengthOf("b") int[] a = {0, 1, 2, 3}; - // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) - @LTLengthOf("b") int[] a1 = {0, 1, 2, 4}; + // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) + @LTLengthOf("b") int[] a1 = {0, 1, 2, 4}; - @LTEqLengthOf("b") int[] c = {-1, 4, 3, 1}; + @LTEqLengthOf("b") int[] c = {-1, 4, 3, 1}; - // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) - @LTEqLengthOf("b") int[] c2 = {-1, 4, 5, 1}; - } + // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) + @LTEqLengthOf("b") int[] c2 = {-1, 4, 5, 1}; + } - void offset_test() { - int[] b = new int[4]; - int[] b2 = new int[10]; - @LTLengthOf( - value = {"b", "b2"}, - offset = {"-2", "5"}) - int[] a = {2, 3, 0}; + void offset_test() { + int[] b = new int[4]; + int[] b2 = new int[10]; + @LTLengthOf( + value = {"b", "b2"}, + offset = {"-2", "5"}) + int[] a = {2, 3, 0}; - @LTLengthOf( - value = {"b", "b2"}, - offset = {"-2", "5"}) - // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) - int[] a2 = {2, 3, 5}; + @LTLengthOf( + value = {"b", "b2"}, + offset = {"-2", "5"}) + // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) + int[] a2 = {2, 3, 5}; - // Non-constant offsets don't work correctly. See kelloggm#120. - } + // Non-constant offsets don't work correctly. See kelloggm#120. + } } diff --git a/checker/tests/index/ConstantOffsets.java b/checker/tests/index/ConstantOffsets.java index d821704aab0..bd5cccfb8b8 100644 --- a/checker/tests/index/ConstantOffsets.java +++ b/checker/tests/index/ConstantOffsets.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class ConstantOffsets { - void method1(int[] a, int offset, @LTLengthOf(value = "#1", offset = "-#2 - 1") int x) {} + void method1(int[] a, int offset, @LTLengthOf(value = "#1", offset = "-#2 - 1") int x) {} - void test1() { - int offset = -4; - int x = 4; - int[] f1 = new int[x - offset]; - method1(f1, offset, x); - } + void test1() { + int offset = -4; + int x = 4; + int[] f1 = new int[x - offset]; + method1(f1, offset, x); + } - void method2(int[] a, int offset, @LTLengthOf(value = "#1", offset = "#2 - 1") int x) {} + void method2(int[] a, int offset, @LTLengthOf(value = "#1", offset = "#2 - 1") int x) {} - void test2() { - int offset = 4; - int x = 4; - int[] f1 = new int[x + offset]; - method2(f1, offset, x); - } + void test2() { + int offset = 4; + int x = 4; + int[] f1 = new int[x + offset]; + method2(f1, offset, x); + } } diff --git a/checker/tests/index/ConstantsIndex.java b/checker/tests/index/ConstantsIndex.java index 672b15332b6..22871601e5c 100644 --- a/checker/tests/index/ConstantsIndex.java +++ b/checker/tests/index/ConstantsIndex.java @@ -2,10 +2,10 @@ public class ConstantsIndex { - void test() { - int @MinLen(3) [] arr = {1, 2, 3}; - int i = arr[1]; - // :: error: (array.access.unsafe.high.constant) - int j = arr[3]; - } + void test() { + int @MinLen(3) [] arr = {1, 2, 3}; + int i = arr[1]; + // :: error: (array.access.unsafe.high.constant) + int j = arr[3]; + } } diff --git a/checker/tests/index/CustomContractWithArgs.java b/checker/tests/index/CustomContractWithArgs.java index 1c12bd5b255..902f579822a 100644 --- a/checker/tests/index/CustomContractWithArgs.java +++ b/checker/tests/index/CustomContractWithArgs.java @@ -7,155 +7,155 @@ import org.checkerframework.framework.qual.QualifierArgument; public class CustomContractWithArgs { - // Postcondition for MinLen - @PostconditionAnnotation(qualifier = MinLen.class) - @interface EnsuresMinLen { - public String[] value(); - - @QualifierArgument("value") - public int targetValue(); + // Postcondition for MinLen + @PostconditionAnnotation(qualifier = MinLen.class) + @interface EnsuresMinLen { + public String[] value(); + + @QualifierArgument("value") + public int targetValue(); + } + + // Conditional postcondition for LTLengthOf + @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) + @interface EnsuresLTLIf { + public boolean result(); + + public String[] expression(); + + @JavaExpression + @QualifierArgument("value") + public String[] targetValue(); + + @JavaExpression + @QualifierArgument("offset") + public String[] targetOffset(); + } + + // Precondition for LTLengthOf + @PreconditionAnnotation(qualifier = LTLengthOf.class) + @interface RequiresLTL { + public String[] value(); + + @JavaExpression + @QualifierArgument("value") + public String[] targetValue(); + + @JavaExpression + @QualifierArgument("offset") + public String[] targetOffset(); + } + + class Base { + @EnsuresMinLen(value = "#1", targetValue = 10) + void minLenContract(int[] a) { + if (a.length < 10) throw new RuntimeException(); } - // Conditional postcondition for LTLengthOf - @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) - @interface EnsuresLTLIf { - public boolean result(); + @EnsuresMinLen(value = "#1", targetValue = 10) + // :: error: (contracts.postcondition.not.satisfied) + void minLenWrong(int[] a) { + if (a.length < 9) throw new RuntimeException(); + } - public String[] expression(); + void minLenUse(int[] b) { + minLenContract(b); + int @MinLen(10) [] c = b; + } - @JavaExpression - @QualifierArgument("value") - public String[] targetValue(); + public int b, y; + + @EnsuresLTLIf( + expression = "b", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "10"}, + result = true) + boolean ltlPost(int[] a, int c) { + if (b < a.length - c - 1 && b < a.length - 10) { + return true; + } else { + return false; + } + } - @JavaExpression - @QualifierArgument("offset") - public String[] targetOffset(); + @EnsuresLTLIf(expression = "b", targetValue = "#1", targetOffset = "#3", result = true) + // :: error: (flowexpr.parse.error) + boolean ltlPostInvalid(int[] a, int c) { + return false; } - // Precondition for LTLengthOf - @PreconditionAnnotation(qualifier = LTLengthOf.class) - @interface RequiresLTL { - public String[] value(); + @RequiresLTL( + value = "b", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "-10"}) + void ltlPre(int[] a, int c) { + @LTLengthOf(value = "a", offset = "c+1") int i = b; + } - @JavaExpression - @QualifierArgument("value") - public String[] targetValue(); + void ltlUse(int[] a, int c) { + if (ltlPost(a, c)) { + @LTLengthOf(value = "a", offset = "c+1") int i = b; - @JavaExpression - @QualifierArgument("offset") - public String[] targetOffset(); + ltlPre(a, c); + } + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "a", offset = "c+1") int j = b; } - - class Base { - @EnsuresMinLen(value = "#1", targetValue = 10) - void minLenContract(int[] a) { - if (a.length < 10) throw new RuntimeException(); - } - - @EnsuresMinLen(value = "#1", targetValue = 10) - // :: error: (contracts.postcondition.not.satisfied) - void minLenWrong(int[] a) { - if (a.length < 9) throw new RuntimeException(); - } - - void minLenUse(int[] b) { - minLenContract(b); - int @MinLen(10) [] c = b; - } - - public int b, y; - - @EnsuresLTLIf( - expression = "b", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "10"}, - result = true) - boolean ltlPost(int[] a, int c) { - if (b < a.length - c - 1 && b < a.length - 10) { - return true; - } else { - return false; - } - } - - @EnsuresLTLIf(expression = "b", targetValue = "#1", targetOffset = "#3", result = true) - // :: error: (flowexpr.parse.error) - boolean ltlPostInvalid(int[] a, int c) { - return false; - } - - @RequiresLTL( - value = "b", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "-10"}) - void ltlPre(int[] a, int c) { - @LTLengthOf(value = "a", offset = "c+1") int i = b; - } - - void ltlUse(int[] a, int c) { - if (ltlPost(a, c)) { - @LTLengthOf(value = "a", offset = "c+1") int i = b; - - ltlPre(a, c); - } - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "a", offset = "c+1") int j = b; - } + } + + class Derived extends Base { + public int x; + + @Override + @EnsuresLTLIf( + expression = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "11"}, + result = true) + boolean ltlPost(int[] a, int d) { + return false; } - class Derived extends Base { - public int x; - - @Override - @EnsuresLTLIf( - expression = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "11"}, - result = true) - boolean ltlPost(int[] a, int d) { - return false; - } - - @Override - @RequiresLTL( - value = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "-11"}) - void ltlPre(int[] a, int d) { - @LTLengthOf( - value = {"a", "a"}, - offset = {"d+1", "-10"}) - // :: error: (assignment.type.incompatible) - int i = b; - } + @Override + @RequiresLTL( + value = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "-11"}) + void ltlPre(int[] a, int d) { + @LTLengthOf( + value = {"a", "a"}, + offset = {"d+1", "-10"}) + // :: error: (assignment.type.incompatible) + int i = b; + } + } + + class DerivedInvalid extends Base { + public int x; + + @Override + @EnsuresLTLIf( + expression = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "9"}, + result = true) + // :: error: (contracts.conditional.postcondition.true.override.invalid) + boolean ltlPost(int[] a, int c) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; } - class DerivedInvalid extends Base { - public int x; - - @Override - @EnsuresLTLIf( - expression = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "9"}, - result = true) - // :: error: (contracts.conditional.postcondition.true.override.invalid) - boolean ltlPost(int[] a, int c) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @Override - @RequiresLTL( - value = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "-9"}) - // :: error: (contracts.precondition.override.invalid) - void ltlPre(int[] a, int d) { - @LTLengthOf( - value = {"a", "a"}, - offset = {"d+1", "-10"}) - int i = b; - } + @Override + @RequiresLTL( + value = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "-9"}) + // :: error: (contracts.precondition.override.invalid) + void ltlPre(int[] a, int d) { + @LTLengthOf( + value = {"a", "a"}, + offset = {"d+1", "-10"}) + int i = b; } + } } diff --git a/checker/tests/index/DaikonCrash.java b/checker/tests/index/DaikonCrash.java index 45bf79754ea..4a4beb8acf2 100644 --- a/checker/tests/index/DaikonCrash.java +++ b/checker/tests/index/DaikonCrash.java @@ -1,15 +1,14 @@ -import org.checkerframework.dataflow.qual.Pure; - import java.util.Arrays; +import org.checkerframework.dataflow.qual.Pure; public class DaikonCrash { - void method(Object[] a1) { - int[] u = union(new int[] {}, new int[] {}); - Arrays.sort(u); - } + void method(Object[] a1) { + int[] u = union(new int[] {}, new int[] {}); + Arrays.sort(u); + } - @Pure - private int[] union(int[] ints, int[] ints1) { - throw new RuntimeException(); - } + @Pure + private int[] union(int[] ints, int[] ints1) { + throw new RuntimeException(); + } } diff --git a/checker/tests/index/DefaultingForEach.java b/checker/tests/index/DefaultingForEach.java index 5cbe49fddc3..64f348ed8c2 100644 --- a/checker/tests/index/DefaultingForEach.java +++ b/checker/tests/index/DefaultingForEach.java @@ -7,16 +7,16 @@ class DefaultForEach { - @DefaultQualifier(NonNegative.class) - static int[] foo() { - throw new RuntimeException(); - } + @DefaultQualifier(NonNegative.class) + static int[] foo() { + throw new RuntimeException(); + } - void bar() { - for (Integer p : foo()) { - // :: error: assignment.type.incompatible - @Positive int x = p; - @NonNegative int y = p; - } + void bar() { + for (Integer p : foo()) { + // :: error: assignment.type.incompatible + @Positive int x = p; + @NonNegative int y = p; } + } } diff --git a/checker/tests/index/Dimension.java b/checker/tests/index/Dimension.java index 256da19eb11..b2ab086e61e 100644 --- a/checker/tests/index/Dimension.java +++ b/checker/tests/index/Dimension.java @@ -1,18 +1,18 @@ @SuppressWarnings("lowerbound") public class Dimension { - void test(int expr) { - int[] array = new int[expr]; - // :: error: (array.access.unsafe.high) - array[expr] = 0; - array[expr - 1] = 0; - } + void test(int expr) { + int[] array = new int[expr]; + // :: error: (array.access.unsafe.high) + array[expr] = 0; + array[expr - 1] = 0; + } - String[] arrayField = new String[1]; + String[] arrayField = new String[1]; - void test2(int expr) { - arrayField = new String[expr]; - // :: error: (array.access.unsafe.high) - this.arrayField[expr] = ""; - this.arrayField[expr - 1] = ""; - } + void test2(int expr) { + arrayField = new String[expr]; + // :: error: (array.access.unsafe.high) + this.arrayField[expr] = ""; + this.arrayField[expr - 1] = ""; + } } diff --git a/checker/tests/index/DivisionTest.java b/checker/tests/index/DivisionTest.java index 3407f4eef93..26003be29c7 100644 --- a/checker/tests/index/DivisionTest.java +++ b/checker/tests/index/DivisionTest.java @@ -2,7 +2,7 @@ public class DivisionTest { - public static void division() { - System.out.println(1 / (2.0)); - } + public static void division() { + System.out.println(1 / (2.0)); + } } diff --git a/checker/tests/index/EndsWith.java b/checker/tests/index/EndsWith.java index cf5c180430b..a41ff8bcc19 100644 --- a/checker/tests/index/EndsWith.java +++ b/checker/tests/index/EndsWith.java @@ -5,9 +5,9 @@ public class EndsWith { - void testEndsWith(String arg) { - if (arg.endsWith("[]")) { - @MinLen(2) String arg2 = arg; - } + void testEndsWith(String arg) { + if (arg.endsWith("[]")) { + @MinLen(2) String arg2 = arg; } + } } diff --git a/checker/tests/index/EndsWith2.java b/checker/tests/index/EndsWith2.java index 54a5c06afaa..5034b5930d3 100644 --- a/checker/tests/index/EndsWith2.java +++ b/checker/tests/index/EndsWith2.java @@ -2,16 +2,16 @@ public class EndsWith2 { - public static String invertBrackets(String classname) { + public static String invertBrackets(String classname) { - // Get the array depth (if any) - int array_depth = 0; - String brackets = ""; - while (classname.endsWith("[]")) { - brackets = brackets + classname.substring(classname.length() - 2); - classname = classname.substring(0, classname.length() - 2); - array_depth++; - } - return brackets + classname; + // Get the array depth (if any) + int array_depth = 0; + String brackets = ""; + while (classname.endsWith("[]")) { + brackets = brackets + classname.substring(classname.length() - 2); + classname = classname.substring(0, classname.length() - 2); + array_depth++; } + return brackets + classname; + } } diff --git a/checker/tests/index/EnumValues.java b/checker/tests/index/EnumValues.java index 49452b3fae8..7a23d1f2616 100644 --- a/checker/tests/index/EnumValues.java +++ b/checker/tests/index/EnumValues.java @@ -2,21 +2,21 @@ public class EnumValues { - public static enum Direction { - NORTH, - SOUTH, - EAST, - WEST - }; + public static enum Direction { + NORTH, + SOUTH, + EAST, + WEST + }; - public static void enumValues() { - Direction @ArrayLen(4) [] arr4 = Direction.values(); - Direction[] arr = Direction.values(); - Direction a = arr[0]; - Direction b = arr[1]; - Direction c = arr[2]; - Direction d = arr[3]; - // :: error: (array.access.unsafe.high.constant) - Direction e = arr[4]; - } + public static void enumValues() { + Direction @ArrayLen(4) [] arr4 = Direction.values(); + Direction[] arr = Direction.values(); + Direction a = arr[0]; + Direction b = arr[1]; + Direction c = arr[2]; + Direction d = arr[3]; + // :: error: (array.access.unsafe.high.constant) + Direction e = arr[4]; + } } diff --git a/checker/tests/index/EqualToIndex.java b/checker/tests/index/EqualToIndex.java index 33aae4b82b2..b9cd7f50300 100644 --- a/checker/tests/index/EqualToIndex.java +++ b/checker/tests/index/EqualToIndex.java @@ -2,11 +2,11 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class EqualToIndex { - static int[] a = {0}; + static int[] a = {0}; - public static void equalToUpper(@LTLengthOf("a") int m, @LTEqLengthOf("a") int r) { - if (r == m) { - @LTLengthOf("a") int j = r; - } + public static void equalToUpper(@LTLengthOf("a") int m, @LTEqLengthOf("a") int r) { + if (r == m) { + @LTLengthOf("a") int j = r; } + } } diff --git a/checker/tests/index/EqualToTransfer.java b/checker/tests/index/EqualToTransfer.java index 54d622b3219..37c642dd2fc 100644 --- a/checker/tests/index/EqualToTransfer.java +++ b/checker/tests/index/EqualToTransfer.java @@ -1,19 +1,19 @@ import org.checkerframework.common.value.qual.MinLen; public class EqualToTransfer { - void eq_check(int[] a) { - if (1 == a.length) { - int @MinLen(1) [] b = a; - } - if (a.length == 1) { - int @MinLen(1) [] b = a; - } + void eq_check(int[] a) { + if (1 == a.length) { + int @MinLen(1) [] b = a; } + if (a.length == 1) { + int @MinLen(1) [] b = a; + } + } - void eq_bad_check(int[] a) { - if (1 == a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void eq_bad_check(int[] a) { + if (1 == a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/checker/tests/index/ErrorMessageCheck.java b/checker/tests/index/ErrorMessageCheck.java index c6a8a3d325d..9c225942e49 100644 --- a/checker/tests/index/ErrorMessageCheck.java +++ b/checker/tests/index/ErrorMessageCheck.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.index.qual.NonNegative; public class ErrorMessageCheck { - @NonNegative int size; - int[] vDown = new int[size]; + @NonNegative int size; + int[] vDown = new int[size]; - void method3(@NonNegative int size, @NonNegative int value) { - this.size = size; - this.vDown = new int[this.size]; - // :: error: (array.access.unsafe.high) - vDown[1 + value] = 10; - } + void method3(@NonNegative int size, @NonNegative int value) { + this.size = size; + this.vDown = new int[this.size]; + // :: error: (array.access.unsafe.high) + vDown[1 + value] = 10; + } } diff --git a/checker/tests/index/Errors.java b/checker/tests/index/Errors.java index 0ad9b55e402..d68201cd9a9 100644 --- a/checker/tests/index/Errors.java +++ b/checker/tests/index/Errors.java @@ -5,25 +5,25 @@ public class Errors { - void test() { - int[] arr = new int[5]; + void test() { + int[] arr = new int[5]; - // unsafe - @GTENegativeOne int n1p = -1; - @LowerBoundUnknown int u = -10; + // unsafe + @GTENegativeOne int n1p = -1; + @LowerBoundUnknown int u = -10; - // safe - @NonNegative int nn = 0; - @Positive int p = 1; + // safe + @NonNegative int nn = 0; + @Positive int p = 1; - // :: error: (array.access.unsafe.low) - int a = arr[n1p]; + // :: error: (array.access.unsafe.low) + int a = arr[n1p]; - // :: error: (array.access.unsafe.low) - int b = arr[u]; + // :: error: (array.access.unsafe.low) + int b = arr[u]; - int c = arr[nn]; - int d = arr[p]; - } + int c = arr[nn]; + int d = arr[p]; + } } // a comment diff --git a/checker/tests/index/ExampleUsage.java b/checker/tests/index/ExampleUsage.java index 1f959249925..005c1475fad 100644 --- a/checker/tests/index/ExampleUsage.java +++ b/checker/tests/index/ExampleUsage.java @@ -1,33 +1,33 @@ public class ExampleUsage { - /** - * this class contains a set of test methods that are supposed to show how the lowerbound - * checker should work in practice. They contain no annotations - the only test is whether or - * not it alarms on particular code constructs that are or are not safe - */ - void safe_loop_const() { - int[] arr = new int[5]; - int k; - for (int i = 0; i < 5; i++) { - k = arr[i]; - } + /** + * this class contains a set of test methods that are supposed to show how the lowerbound checker + * should work in practice. They contain no annotations - the only test is whether or not it + * alarms on particular code constructs that are or are not safe + */ + void safe_loop_const() { + int[] arr = new int[5]; + int k; + for (int i = 0; i < 5; i++) { + k = arr[i]; } + } - void safe_loop_spooky() { - int[] arr = new int[5]; - int k; - for (int i = -1; i < 4; ) { - i++; - k = arr[i]; - } + void safe_loop_spooky() { + int[] arr = new int[5]; + int k; + for (int i = -1; i < 4; ) { + i++; + k = arr[i]; } + } - void obviously_unsafe_loop() { - int[] arr = new int[5]; - int k; - for (int i = -1; i < 5; i++) { - // :: error: (array.access.unsafe.low) - k = arr[i]; - } + void obviously_unsafe_loop() { + int[] arr = new int[5]; + int k; + for (int i = -1; i < 5; i++) { + // :: error: (array.access.unsafe.low) + k = arr[i]; } + } } // a comment diff --git a/checker/tests/index/GenericAssignment.java b/checker/tests/index/GenericAssignment.java index bfb406b3d37..7d4f361387f 100644 --- a/checker/tests/index/GenericAssignment.java +++ b/checker/tests/index/GenericAssignment.java @@ -1,29 +1,28 @@ +import java.util.List; import org.checkerframework.checker.index.qual.GTENegativeOne; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.value.qual.IntRange; -import java.util.List; - public class GenericAssignment { - public void assignNonNegativeList(List<@NonNegative Integer> l) { - List<@NonNegative Integer> i = l; // line 10 - } + public void assignNonNegativeList(List<@NonNegative Integer> l) { + List<@NonNegative Integer> i = l; // line 10 + } - public void assignPositiveList(List<@Positive Integer> l) { - List<@Positive Integer> i = l; // line 13 - } + public void assignPositiveList(List<@Positive Integer> l) { + List<@Positive Integer> i = l; // line 13 + } - public void assignGTENOList(List<@GTENegativeOne Integer> l) { - List<@GTENegativeOne Integer> i = l; // line 16 - } + public void assignGTENOList(List<@GTENegativeOne Integer> l) { + List<@GTENegativeOne Integer> i = l; // line 16 + } - // Similar examples that work - public void assignNonNegativeArrayOK(@NonNegative Integer[] l) { - @NonNegative Integer[] i = l; - } + // Similar examples that work + public void assignNonNegativeArrayOK(@NonNegative Integer[] l) { + @NonNegative Integer[] i = l; + } - public void assignIntRangeListOK(List<@IntRange(from = 0) Integer> l) { - List<@IntRange(from = 0) Integer> i = l; - } + public void assignIntRangeListOK(List<@IntRange(from = 0) Integer> l) { + List<@IntRange(from = 0) Integer> i = l; + } } diff --git a/checker/tests/index/GreaterThanOrEqualTransfer.java b/checker/tests/index/GreaterThanOrEqualTransfer.java index 09deb96fbd7..61138be674f 100644 --- a/checker/tests/index/GreaterThanOrEqualTransfer.java +++ b/checker/tests/index/GreaterThanOrEqualTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class GreaterThanOrEqualTransfer { - void gte_check(int[] a) { - if (a.length >= 1) { - int @MinLen(1) [] b = a; - } + void gte_check(int[] a) { + if (a.length >= 1) { + int @MinLen(1) [] b = a; } + } - void gte_bad_check(int[] a) { - if (a.length >= 1) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void gte_bad_check(int[] a) { + if (a.length >= 1) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/checker/tests/index/GreaterThanTransfer.java b/checker/tests/index/GreaterThanTransfer.java index dbacd8374ef..a0cee5304a8 100644 --- a/checker/tests/index/GreaterThanTransfer.java +++ b/checker/tests/index/GreaterThanTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class GreaterThanTransfer { - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; - } + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; } + } - void gt_bad_check(int[] a) { - if (a.length > 0) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void gt_bad_check(int[] a) { + if (a.length > 0) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/checker/tests/index/GuavaPrimitives.java b/checker/tests/index/GuavaPrimitives.java index bac19a8055c..9b2f33b330b 100644 --- a/checker/tests/index/GuavaPrimitives.java +++ b/checker/tests/index/GuavaPrimitives.java @@ -1,3 +1,6 @@ +import java.util.AbstractList; +import java.util.Collections; +import java.util.List; import org.checkerframework.checker.index.qual.HasSubsequence; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; @@ -8,130 +11,126 @@ import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.value.qual.MinLen; -import java.util.AbstractList; -import java.util.Collections; -import java.util.List; - /** * A simplified version of the Guava primitives classes (such as Bytes, Longs, Shorts, etc.) with * all expected warnings suppressed. */ public class GuavaPrimitives extends AbstractList { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - final short @MinLen(1) [] array; - - final @IndexFor("array") @LessThan("end") int start; - final @Positive @LTEqLengthOf("array") int end; - - public static @IndexOrLow("#1") int indexOf(short[] array, short target) { - return indexOf(array, target, 0, array.length); - } - - private static @IndexOrLow("#1") @LessThan("#4") int indexOf( - short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( - short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - GuavaPrimitives(short @MinLen(1) [] array) { - this(array, 0, array.length); - } - - @SuppressWarnings( - "index" // these three fields need to be initialized in some order, and any ordering - // leads to the first two issuing errors - since each field is dependent on at least one of the - // others - ) - GuavaPrimitives( - short @MinLen(1) [] array, - @IndexFor("#1") @LessThan("#3") int start, - @Positive @LTEqLengthOf("#1") int end) { - // warnings in here might just need to be suppressed. A single @SuppressWarnings("index") to - // establish rep. invariant might be okay? - this.array = array; - this.start = start; - this.end = end; - } - - public @Positive @LTLengthOf( - value = {"this", "array"}, - offset = {"-1", "start - 1"}) int - size() { // INDEX: Annotation on a public method refers to private member. - return end - start; - } - - public boolean isEmpty() { - return false; + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + final short @MinLen(1) [] array; + + final @IndexFor("array") @LessThan("end") int start; + final @Positive @LTEqLengthOf("array") int end; + + public static @IndexOrLow("#1") int indexOf(short[] array, short target) { + return indexOf(array, target, 0, array.length); + } + + private static @IndexOrLow("#1") @LessThan("#4") int indexOf( + short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } } - - public Short get(@IndexFor("this") int index) { - return array[start + index]; + return -1; + } + + private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( + short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } } - - @SuppressWarnings( - "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 indexOf() - public @IndexOrLow("this") int indexOf(Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Short) { - int i = GuavaPrimitives.indexOf(array, (Short) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; + return -1; + } + + GuavaPrimitives(short @MinLen(1) [] array) { + this(array, 0, array.length); + } + + @SuppressWarnings( + "index" // these three fields need to be initialized in some order, and any ordering + // leads to the first two issuing errors - since each field is dependent on at least one of the + // others + ) + GuavaPrimitives( + short @MinLen(1) [] array, + @IndexFor("#1") @LessThan("#3") int start, + @Positive @LTEqLengthOf("#1") int end) { + // warnings in here might just need to be suppressed. A single @SuppressWarnings("index") to + // establish rep. invariant might be okay? + this.array = array; + this.start = start; + this.end = end; + } + + public @Positive @LTLengthOf( + value = {"this", "array"}, + offset = {"-1", "start - 1"}) int + size() { // INDEX: Annotation on a public method refers to private member. + return end - start; + } + + public boolean isEmpty() { + return false; + } + + public Short get(@IndexFor("this") int index) { + return array[start + index]; + } + + @SuppressWarnings( + "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 indexOf() + public @IndexOrLow("this") int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = GuavaPrimitives.indexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } } - - @SuppressWarnings( - "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 lastIndexOf() - public @IndexOrLow("this") int lastIndexOf(Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Short) { - int i = GuavaPrimitives.lastIndexOf(array, (Short) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; + return -1; + } + + @SuppressWarnings( + "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 lastIndexOf() + public @IndexOrLow("this") int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = GuavaPrimitives.lastIndexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } } - - public Short set(@IndexFor("this") int index, Short element) { - short oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = element; - return oldValue; + return -1; + } + + public Short set(@IndexFor("this") int index, Short element) { + short oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = element; + return oldValue; + } + + @SuppressWarnings("index") // needs https://github.com/kelloggm/checker-framework/issues/229 + public List subList( + @IndexOrHigh("this") @LessThan("#2") int fromIndex, @IndexOrHigh("this") int toIndex) { + int size = size(); + if (fromIndex == toIndex) { + return Collections.emptyList(); } - - @SuppressWarnings("index") // needs https://github.com/kelloggm/checker-framework/issues/229 - public List subList( - @IndexOrHigh("this") @LessThan("#2") int fromIndex, @IndexOrHigh("this") int toIndex) { - int size = size(); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new GuavaPrimitives(array, start + fromIndex, start + toIndex); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 6); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); + return new GuavaPrimitives(array, start + fromIndex, start + toIndex); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 6); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); } + return builder.append(']').toString(); + } } diff --git a/checker/tests/index/HexEncode.java b/checker/tests/index/HexEncode.java index e9d4bc8091c..77f3be1d284 100644 --- a/checker/tests/index/HexEncode.java +++ b/checker/tests/index/HexEncode.java @@ -1,16 +1,16 @@ @SuppressWarnings("array.access.unsafe.high") public class HexEncode { - private static final char[] digits = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; + private static final char[] digits = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; - public static String hexEncode(byte[] bytes) { - StringBuffer s = new StringBuffer(bytes.length * 2); - for (int i = 0; i < bytes.length; i++) { - byte b = bytes[i]; - s.append(digits[(b & 0xf0) >> 4]); - s.append(digits[b & 0x0f]); - } - return s.toString(); + public static String hexEncode(byte[] bytes) { + StringBuffer s = new StringBuffer(bytes.length * 2); + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + s.append(digits[(b & 0xf0) >> 4]); + s.append(digits[b & 0x0f]); } + return s.toString(); + } } diff --git a/checker/tests/index/Index115.java b/checker/tests/index/Index115.java index 080f02ec798..ae9cc8e78c7 100644 --- a/checker/tests/index/Index115.java +++ b/checker/tests/index/Index115.java @@ -2,27 +2,27 @@ public class Index115 { - public static void main(String[] args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); - } + public static void main(String[] args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); } + } - public static void main2(String... args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); - } + public static void main2(String... args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); } + } - public static void main3(String @ArrayLen({1, 2}) [] args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); - } + public static void main3(String @ArrayLen({1, 2}) [] args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); } + } - public static void main4(String @ArrayLen({1, 2}) ... args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); - } + public static void main4(String @ArrayLen({1, 2}) ... args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); } + } } diff --git a/checker/tests/index/Index118.java b/checker/tests/index/Index118.java index 6dda5c701ea..411851bb7ed 100644 --- a/checker/tests/index/Index118.java +++ b/checker/tests/index/Index118.java @@ -3,16 +3,16 @@ public class Index118 { - public static void foo(String @ArrayLen(4) [] args) { - for (int i = 1; i <= 3; i++) { - @IntRange(from = 1, to = 3) int x = i; - System.out.println(args[i]); - } + public static void foo(String @ArrayLen(4) [] args) { + for (int i = 1; i <= 3; i++) { + @IntRange(from = 1, to = 3) int x = i; + System.out.println(args[i]); } + } - public static void bar(@NonNegative int i, String @ArrayLen(4) [] args) { - if (i <= 3) { - System.out.println(args[i]); - } + public static void bar(@NonNegative int i, String @ArrayLen(4) [] args) { + if (i <= 3) { + System.out.println(args[i]); } + } } diff --git a/checker/tests/index/Index118NoLoop.java b/checker/tests/index/Index118NoLoop.java index 1006e87c5cf..6a3ba94a823 100644 --- a/checker/tests/index/Index118NoLoop.java +++ b/checker/tests/index/Index118NoLoop.java @@ -2,9 +2,9 @@ public class Index118NoLoop { - public static void foo(String @ArrayLen(4) [] args, int i) { - if (i >= 1 && i <= 3) { - System.out.println(args[i]); - } + public static void foo(String @ArrayLen(4) [] args, int i) { + if (i >= 1 && i <= 3) { + System.out.println(args[i]); } + } } diff --git a/checker/tests/index/Index132.java b/checker/tests/index/Index132.java index f7e321b124a..4cbfe2629c9 100644 --- a/checker/tests/index/Index132.java +++ b/checker/tests/index/Index132.java @@ -1,11 +1,11 @@ import org.checkerframework.common.value.qual.*; public class Index132 { - public static String @ArrayLen({3, 4}) [] esc_quantify(String @ArrayLen({1, 2}) ... vars) { - if (vars.length == 1) { - return new String[] {"hello", vars[0], ")"}; - } else { - return new String[] {"hello", vars[0], vars[1], ")"}; - } + public static String @ArrayLen({3, 4}) [] esc_quantify(String @ArrayLen({1, 2}) ... vars) { + if (vars.length == 1) { + return new String[] {"hello", vars[0], ")"}; + } else { + return new String[] {"hello", vars[0], vars[1], ")"}; } + } } diff --git a/checker/tests/index/Index166.java b/checker/tests/index/Index166.java index 93df6809dab..62ac0cdcac8 100644 --- a/checker/tests/index/Index166.java +++ b/checker/tests/index/Index166.java @@ -5,11 +5,11 @@ public class Index166 { - public void testMethodInvocation() { - requiresIndex("012345", 5); - // :: error: (argument.type.incompatible) - requiresIndex("012345", 6); - } + public void testMethodInvocation() { + requiresIndex("012345", 5); + // :: error: (argument.type.incompatible) + requiresIndex("012345", 6); + } - public void requiresIndex(String str, @IndexFor("#1") int index) {} + public void requiresIndex(String str, @IndexFor("#1") int index) {} } diff --git a/checker/tests/index/Index167.java b/checker/tests/index/Index167.java index 3f37b24cb64..d0d2b6a69e9 100644 --- a/checker/tests/index/Index167.java +++ b/checker/tests/index/Index167.java @@ -6,22 +6,22 @@ import org.checkerframework.checker.index.qual.NonNegative; public class Index167 { - static void fn1(int[] arr, @IndexFor("#1") int i) { - if (i >= 33) { - // :: error: (argument.type.incompatible) - fn2(arr, i); - } - if (i > 33) { - // :: error: (argument.type.incompatible) - fn2(arr, i); - } - if (i != 33) { - // :: error: (argument.type.incompatible) - fn2(arr, i); - } + static void fn1(int[] arr, @IndexFor("#1") int i) { + if (i >= 33) { + // :: error: (argument.type.incompatible) + fn2(arr, i); } - - static void fn2(int[] arr, @NonNegative @LTOMLengthOf("#1") int i) { - int c = arr[i + 1]; + if (i > 33) { + // :: error: (argument.type.incompatible) + fn2(arr, i); + } + if (i != 33) { + // :: error: (argument.type.incompatible) + fn2(arr, i); } + } + + static void fn2(int[] arr, @NonNegative @LTOMLengthOf("#1") int i) { + int c = arr[i + 1]; + } } diff --git a/checker/tests/index/Index176.java b/checker/tests/index/Index176.java index b0aadbc7168..c5030804c18 100644 --- a/checker/tests/index/Index176.java +++ b/checker/tests/index/Index176.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.index.qual.IndexFor; public class Index176 { - void test(String arglist, @IndexFor("#1") int pos) { - int semi_pos = arglist.indexOf(";"); - if (semi_pos == -1) { - throw new Error("Malformed arglist: " + arglist); - } - arglist.substring(pos, semi_pos + 1); - // :: error: (argument.type.incompatible) - arglist.substring(pos, semi_pos + 2); + void test(String arglist, @IndexFor("#1") int pos) { + int semi_pos = arglist.indexOf(";"); + if (semi_pos == -1) { + throw new Error("Malformed arglist: " + arglist); } + arglist.substring(pos, semi_pos + 1); + // :: error: (argument.type.incompatible) + arglist.substring(pos, semi_pos + 2); + } } diff --git a/checker/tests/index/IndexByChar.java b/checker/tests/index/IndexByChar.java index 263a0b656b8..df049c0f8b3 100644 --- a/checker/tests/index/IndexByChar.java +++ b/checker/tests/index/IndexByChar.java @@ -1,10 +1,10 @@ public class IndexByChar { - public int m(char c) { - int[] i = new int[128]; - if (c < 128) { - return i[c]; - } else { - return -1; - } + public int m(char c) { + int[] i = new int[128]; + if (c < 128) { + return i[c]; + } else { + return -1; } + } } diff --git a/checker/tests/index/IndexConditionalReport.java b/checker/tests/index/IndexConditionalReport.java index ef590170532..469b1fd333c 100644 --- a/checker/tests/index/IndexConditionalReport.java +++ b/checker/tests/index/IndexConditionalReport.java @@ -2,12 +2,12 @@ public class IndexConditionalReport { - public int getI(int len) { - for (int i = 0; i < len; i++) { - if (false) { - return i == 0 ? -1 : i; // unexpected error issued here - } - } - return -1; + public int getI(int len) { + for (int i = 0; i < len; i++) { + if (false) { + return i == 0 ? -1 : i; // unexpected error issued here + } } + return -1; + } } diff --git a/checker/tests/index/IndexForAverage.java b/checker/tests/index/IndexForAverage.java index a11712b76d0..756227a97b5 100644 --- a/checker/tests/index/IndexForAverage.java +++ b/checker/tests/index/IndexForAverage.java @@ -4,13 +4,13 @@ public class IndexForAverage { - public static void bug(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @IndexFor("a") int k = (i + j) / 2; - } + public static void bug(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @IndexFor("a") int k = (i + j) / 2; + } - public static void bug2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @LTLengthOf("a") int k = ((i - 1) + j) / 2; - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int h = ((i + 1) + j) / 2; - } + public static void bug2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @LTLengthOf("a") int k = ((i - 1) + j) / 2; + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int h = ((i + 1) + j) / 2; + } } diff --git a/checker/tests/index/IndexForTest.java b/checker/tests/index/IndexForTest.java index 74d0cbbcbb4..a9a3f599737 100644 --- a/checker/tests/index/IndexForTest.java +++ b/checker/tests/index/IndexForTest.java @@ -2,77 +2,77 @@ import org.checkerframework.common.value.qual.MinLen; public class IndexForTest { - int @MinLen(1) [] array = {0}; - - void test1(@IndexFor("array") int i) { - int x = array[i]; + int @MinLen(1) [] array = {0}; + + void test1(@IndexFor("array") int i) { + int x = array[i]; + } + + void callTest1(int x) { + test1(0); + // :: error: (argument.type.incompatible) + test1(1); + // :: error: (argument.type.incompatible) + test1(2); + // :: error: (argument.type.incompatible) + test1(array.length); + + if (array.length > 0) { + test1(array.length - 1); } - void callTest1(int x) { - test1(0); - // :: error: (argument.type.incompatible) - test1(1); - // :: error: (argument.type.incompatible) - test1(2); - // :: error: (argument.type.incompatible) - test1(array.length); - - if (array.length > 0) { - test1(array.length - 1); - } - - test1(array.length - 1); + test1(array.length - 1); - // :: error: (argument.type.incompatible) - test1(this.array.length); + // :: error: (argument.type.incompatible) + test1(this.array.length); - if (array.length > 0) { - test1(this.array.length - 1); - } - - test1(this.array.length - 1); + if (array.length > 0) { + test1(this.array.length - 1); + } - if (this.array.length > x && x >= 0) { - test1(x); - } + test1(this.array.length - 1); - if (array.length == x) { - // :: error: (argument.type.incompatible) - test1(x); - } + if (this.array.length > x && x >= 0) { + test1(x); } - void test2(@IndexFor("this.array") int i) { - int x = array[i]; + if (array.length == x) { + // :: error: (argument.type.incompatible) + test1(x); + } + } + + void test2(@IndexFor("this.array") int i) { + int x = array[i]; + } + + void callTest2(int x) { + test2(0); + // :: error: (argument.type.incompatible) + test2(1); + // :: error: (argument.type.incompatible) + test2(2); + // :: error: (argument.type.incompatible) + test2(array.length); + + if (array.length > 0) { + test2(array.length - 1); } - void callTest2(int x) { - test2(0); - // :: error: (argument.type.incompatible) - test2(1); - // :: error: (argument.type.incompatible) - test2(2); - // :: error: (argument.type.incompatible) - test2(array.length); - - if (array.length > 0) { - test2(array.length - 1); - } - - test2(array.length - 1); + test2(array.length - 1); - // :: error: (argument.type.incompatible) - test2(this.array.length); + // :: error: (argument.type.incompatible) + test2(this.array.length); - if (array.length > 0) { - test2(this.array.length - 1); - } + if (array.length > 0) { + test2(this.array.length - 1); + } - test2(this.array.length - 1); + test2(this.array.length - 1); - if (array.length == x && x >= 0) { - // :: error: (argument.type.incompatible) - test2(x); - } + if (array.length == x && x >= 0) { + // :: error: (argument.type.incompatible) + test2(x); } + } } diff --git a/checker/tests/index/IndexForTestLBC.java b/checker/tests/index/IndexForTestLBC.java index 74497b6df86..66eaf7dac82 100644 --- a/checker/tests/index/IndexForTestLBC.java +++ b/checker/tests/index/IndexForTestLBC.java @@ -4,26 +4,26 @@ @SuppressWarnings("upperbound") public class IndexForTestLBC { - int[] array = {0}; + int[] array = {0}; - void test1(@IndexFor("array") int i) { - int x = this.array[i]; - } + void test1(@IndexFor("array") int i) { + int x = this.array[i]; + } - void callTest1(int x) { - test1(0); - test1(1); - test1(2); - test1(array.length); - // :: error: (argument.type.incompatible) - test1(array.length - 1); - if (array.length > x) { - // :: error: (argument.type.incompatible) - test1(x); - } + void callTest1(int x) { + test1(0); + test1(1); + test1(2); + test1(array.length); + // :: error: (argument.type.incompatible) + test1(array.length - 1); + if (array.length > x) { + // :: error: (argument.type.incompatible) + test1(x); + } - if (array.length == x) { - test1(x); - } + if (array.length == x) { + test1(x); } + } } diff --git a/checker/tests/index/IndexForTwoArrays.java b/checker/tests/index/IndexForTwoArrays.java index 1afe76eb19a..4e31190b64c 100644 --- a/checker/tests/index/IndexForTwoArrays.java +++ b/checker/tests/index/IndexForTwoArrays.java @@ -1,15 +1,15 @@ public class IndexForTwoArrays { - public int compare(double[] a1, double[] a2) { - if (a1 == a2) { - return 0; - } - int len = Math.min(a1.length, a2.length); - for (int i = 0; i < len; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); - } - } - return a1.length - a2.length; + public int compare(double[] a1, double[] a2) { + if (a1 == a2) { + return 0; } + int len = Math.min(a1.length, a2.length); + for (int i = 0; i < len; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); + } + } + return a1.length - a2.length; + } } diff --git a/checker/tests/index/IndexForTwoArrays2.java b/checker/tests/index/IndexForTwoArrays2.java index b1f2d940ccf..9606190477b 100644 --- a/checker/tests/index/IndexForTwoArrays2.java +++ b/checker/tests/index/IndexForTwoArrays2.java @@ -2,17 +2,17 @@ public class IndexForTwoArrays2 { - public boolean equals(int[] da1, int[] da2) { - if (da1.length != da2.length) { - return false; - } - int k = 0; + public boolean equals(int[] da1, int[] da2) { + if (da1.length != da2.length) { + return false; + } + int k = 0; - for (int i = 0; i < da1.length; i++) { - if (da1[i] != da2[i]) { - return false; - } - } - return true; + for (int i = 0; i < da1.length; i++) { + if (da1[i] != da2[i]) { + return false; + } } + return true; + } } diff --git a/checker/tests/index/IndexForVarargs.java b/checker/tests/index/IndexForVarargs.java index 58445f31227..1673e062893 100644 --- a/checker/tests/index/IndexForVarargs.java +++ b/checker/tests/index/IndexForVarargs.java @@ -1,33 +1,33 @@ import org.checkerframework.checker.index.qual.IndexFor; public class IndexForVarargs { - String get(@IndexFor("#2") int i, String... varargs) { - return varargs[i]; - } + String get(@IndexFor("#2") int i, String... varargs) { + return varargs[i]; + } - void method(@IndexFor("#2") int i, String[]... varargs) {} + void method(@IndexFor("#2") int i, String[]... varargs) {} - void m() { - // :: error: (argument.type.incompatible) - get(1); + void m() { + // :: error: (argument.type.incompatible) + get(1); - get(1, "a", "b"); + get(1, "a", "b"); - // :: error: (argument.type.incompatible) - get(2, "abc"); + // :: error: (argument.type.incompatible) + get(2, "abc"); - String[] stringArg1 = new String[] {"a", "b"}; - String[] stringArg2 = new String[] {"c", "d", "e"}; - String[] stringArg3 = new String[] {"a", "b", "c"}; + String[] stringArg1 = new String[] {"a", "b"}; + String[] stringArg2 = new String[] {"c", "d", "e"}; + String[] stringArg3 = new String[] {"a", "b", "c"}; - method(1, stringArg1, stringArg2); + method(1, stringArg1, stringArg2); - // :: error: (argument.type.incompatible) - method(2, stringArg3); + // :: error: (argument.type.incompatible) + method(2, stringArg3); - get(1, stringArg1); + get(1, stringArg1); - // :: error: (argument.type.incompatible) - get(3, stringArg2); - } + // :: error: (argument.type.incompatible) + get(3, stringArg2); + } } diff --git a/checker/tests/index/IndexIntValVsConstant.java b/checker/tests/index/IndexIntValVsConstant.java index 113ad9baf3d..8c0c8f53c3c 100644 --- a/checker/tests/index/IndexIntValVsConstant.java +++ b/checker/tests/index/IndexIntValVsConstant.java @@ -8,22 +8,22 @@ public class IndexIntValVsConstant { - void m() { + void m() { - int @ArrayLen(7) [] a1 = new int[] {1, 2, 3, 4, 5, 6, 7}; + int @ArrayLen(7) [] a1 = new int[] {1, 2, 3, 4, 5, 6, 7}; - @IntVal(2) int i = 2; - @IntVal(4) int j = 4; + @IntVal(2) int i = 2; + @IntVal(4) int j = 4; - int[] s0 = internSubsequence(a1, 2, 4); - int[] s1 = internSubsequence(a1, i, j); - } + int[] s0 = internSubsequence(a1, 2, 4); + int[] s1 = internSubsequence(a1, i, j); + } - int @Interned [] internSubsequence( - int @Interned [] seq, - @IndexFor("#1") @LessThan("#3") int start, - @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { - // dummy implementation - return new int[0]; - } + int @Interned [] internSubsequence( + int @Interned [] seq, + @IndexFor("#1") @LessThan("#3") int start, + @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { + // dummy implementation + return new int[0]; + } } diff --git a/checker/tests/index/IndexOf.java b/checker/tests/index/IndexOf.java index 78c747404b0..0596d409593 100644 --- a/checker/tests/index/IndexOf.java +++ b/checker/tests/index/IndexOf.java @@ -4,12 +4,12 @@ public class IndexOf { - public static String m(String arg) { - int split_pos = arg.indexOf(",-"); - if (split_pos == 0) { - // Just discard the ',' if ",-" occurs at begining of string - arg = arg.substring(1); - } - return arg; + public static String m(String arg) { + int split_pos = arg.indexOf(",-"); + if (split_pos == 0) { + // Just discard the ',' if ",-" occurs at begining of string + arg = arg.substring(1); } + return arg; + } } diff --git a/checker/tests/index/IndexOrLowTests.java b/checker/tests/index/IndexOrLowTests.java index 150b6aad1e1..9eafdccea45 100644 --- a/checker/tests/index/IndexOrLowTests.java +++ b/checker/tests/index/IndexOrLowTests.java @@ -4,29 +4,29 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class IndexOrLowTests { - int[] array = {1, 2}; + int[] array = {1, 2}; - @IndexOrLow("array") int index = -1; + @IndexOrLow("array") int index = -1; - void test() { + void test() { - if (index != -1) { - array[index] = 1; - } - - @IndexOrHigh("array") int y = index + 1; - // :: error: (array.access.unsafe.high) - array[y] = 1; - if (y < array.length) { - array[y] = 1; - } - // :: error: (assignment.type.incompatible) - index = array.length; + if (index != -1) { + array[index] = 1; } - void test2(@LTLengthOf("array") @GTENegativeOne int param) { - index = array.length - 1; - @LTLengthOf("array") @GTENegativeOne int x = index; - index = param; + @IndexOrHigh("array") int y = index + 1; + // :: error: (array.access.unsafe.high) + array[y] = 1; + if (y < array.length) { + array[y] = 1; } + // :: error: (assignment.type.incompatible) + index = array.length; + } + + void test2(@LTLengthOf("array") @GTENegativeOne int param) { + index = array.length - 1; + @LTLengthOf("array") @GTENegativeOne int x = index; + index = param; + } } diff --git a/checker/tests/index/IndexSameLen.java b/checker/tests/index/IndexSameLen.java index 40b89ed8fdb..90d2bb18a0d 100644 --- a/checker/tests/index/IndexSameLen.java +++ b/checker/tests/index/IndexSameLen.java @@ -2,15 +2,15 @@ public class IndexSameLen { - public static void bug2() { - int[] a = {1, 2, 3, 4, 5}; - int @SameLen("a") [] b = a; + public static void bug2() { + int[] a = {1, 2, 3, 4, 5}; + int @SameLen("a") [] b = a; - @IndexFor("a") int i = 2; - a[i] = b[i]; + @IndexFor("a") int i = 2; + a[i] = b[i]; - for (int j = 0; j < a.length; j++) { - a[j] = b[j]; - } + for (int j = 0; j < a.length; j++) { + a[j] = b[j]; } + } } diff --git a/checker/tests/index/IntroAdd.java b/checker/tests/index/IntroAdd.java index 42316529eca..2545374c6ea 100644 --- a/checker/tests/index/IntroAdd.java +++ b/checker/tests/index/IntroAdd.java @@ -2,18 +2,18 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class IntroAdd { - void test(int[] arr) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int a = 3; - } + void test(int[] arr) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int a = 3; + } - void test(int[] arr, @LTLengthOf({"#1"}) int a) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int c = a + 1; - @LTEqLengthOf({"arr"}) int c1 = a + 1; - @LTLengthOf({"arr"}) int d = a + 0; - @LTLengthOf({"arr"}) int e = a + (-7); - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int f = a + 7; - } + void test(int[] arr, @LTLengthOf({"#1"}) int a) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int c = a + 1; + @LTEqLengthOf({"arr"}) int c1 = a + 1; + @LTLengthOf({"arr"}) int d = a + 0; + @LTLengthOf({"arr"}) int e = a + (-7); + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int f = a + 7; + } } diff --git a/checker/tests/index/IntroAnd.java b/checker/tests/index/IntroAnd.java index 340da58b8ae..bcdb28119dd 100644 --- a/checker/tests/index/IntroAnd.java +++ b/checker/tests/index/IntroAnd.java @@ -1,39 +1,39 @@ import org.checkerframework.checker.index.qual.*; public class IntroAnd { - void test() { - @NonNegative int a = 1 & 0; - @NonNegative int b = a & 5; + void test() { + @NonNegative int a = 1 & 0; + @NonNegative int b = a & 5; - // :: error: (assignment.type.incompatible) - @Positive int c = a & b; - @NonNegative int d = a & b; - @NonNegative int e = b & a; - } + // :: error: (assignment.type.incompatible) + @Positive int c = a & b; + @NonNegative int d = a & b; + @NonNegative int e = b & a; + } - void test_ubc_and( - @IndexFor("#2") int i, int[] a, @LTLengthOf("#2") int j, int k, @NonNegative int m) { - int x = a[i & k]; - int x1 = a[k & i]; - // :: error: (array.access.unsafe.low) :: error: (array.access.unsafe.high) - int y = a[j & k]; - if (j > -1) { - int z = a[j & k]; - } - // :: error: (array.access.unsafe.high) - int w = a[m & k]; - if (m < a.length) { - int u = a[m & k]; - } + void test_ubc_and( + @IndexFor("#2") int i, int[] a, @LTLengthOf("#2") int j, int k, @NonNegative int m) { + int x = a[i & k]; + int x1 = a[k & i]; + // :: error: (array.access.unsafe.low) :: error: (array.access.unsafe.high) + int y = a[j & k]; + if (j > -1) { + int z = a[j & k]; } - - void two_arrays(int[] a, int[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { - int l = a[i & j]; - l = b[i & j]; + // :: error: (array.access.unsafe.high) + int w = a[m & k]; + if (m < a.length) { + int u = a[m & k]; } + } - void test_pos(@Positive int x, @Positive int y) { - // :: error: (assignment.type.incompatible) - @Positive int z = x & y; - } + void two_arrays(int[] a, int[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { + int l = a[i & j]; + l = b[i & j]; + } + + void test_pos(@Positive int x, @Positive int y) { + // :: error: (assignment.type.incompatible) + @Positive int z = x & y; + } } diff --git a/checker/tests/index/IntroRules.java b/checker/tests/index/IntroRules.java index 75b1493fa05..65bd632b426 100644 --- a/checker/tests/index/IntroRules.java +++ b/checker/tests/index/IntroRules.java @@ -5,31 +5,31 @@ public class IntroRules { - void test() { - @Positive int a = 10; - @NonNegative int b = 9; - @GTENegativeOne int c = 8; - @LowerBoundUnknown int d = 7; + void test() { + @Positive int a = 10; + @NonNegative int b = 9; + @GTENegativeOne int c = 8; + @LowerBoundUnknown int d = 7; - // :: error: (assignment.type.incompatible) - @Positive int e = 0; - // :: error: (assignment.type.incompatible) - @Positive int f = -1; - // :: error: (assignment.type.incompatible) - @Positive int g = -6; + // :: error: (assignment.type.incompatible) + @Positive int e = 0; + // :: error: (assignment.type.incompatible) + @Positive int f = -1; + // :: error: (assignment.type.incompatible) + @Positive int g = -6; - @NonNegative int h = 0; - @GTENegativeOne int i = 0; - @LowerBoundUnknown int j = 0; - // :: error: (assignment.type.incompatible) - @NonNegative int k = -1; - // :: error: (assignment.type.incompatible) - @NonNegative int l = -4; + @NonNegative int h = 0; + @GTENegativeOne int i = 0; + @LowerBoundUnknown int j = 0; + // :: error: (assignment.type.incompatible) + @NonNegative int k = -1; + // :: error: (assignment.type.incompatible) + @NonNegative int l = -4; - @GTENegativeOne int m = -1; - @LowerBoundUnknown int n = -1; - // :: error: (assignment.type.incompatible) - @GTENegativeOne int o = -9; - } + @GTENegativeOne int m = -1; + @LowerBoundUnknown int n = -1; + // :: error: (assignment.type.incompatible) + @GTENegativeOne int o = -9; + } } // a comment diff --git a/checker/tests/index/IntroShift.java b/checker/tests/index/IntroShift.java index dff7b471fe8..ec075157e9b 100644 --- a/checker/tests/index/IntroShift.java +++ b/checker/tests/index/IntroShift.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.NonNegative; public class IntroShift { - void test() { - @NonNegative int a = 1 >> 1; - // :: error: (assignment.type.incompatible) - @NonNegative int b = -1 >> 0; - } + void test() { + @NonNegative int a = 1 >> 1; + // :: error: (assignment.type.incompatible) + @NonNegative int b = -1 >> 0; + } } diff --git a/checker/tests/index/IntroSub.java b/checker/tests/index/IntroSub.java index 778c6610756..42192843845 100644 --- a/checker/tests/index/IntroSub.java +++ b/checker/tests/index/IntroSub.java @@ -2,21 +2,21 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class IntroSub { - void test(int[] arr) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int a = 3; - } + void test(int[] arr) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int a = 3; + } - void test(int[] arr, @LTLengthOf({"#1"}) int a) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int c = a - (-1); - @LTEqLengthOf({"arr"}) int c1 = a - (-1); - @LTLengthOf({"arr"}) int d = a - 0; - @LTLengthOf({"arr"}) int e = a - 7; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int f = a - (-7); + void test(int[] arr, @LTLengthOf({"#1"}) int a) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int c = a - (-1); + @LTEqLengthOf({"arr"}) int c1 = a - (-1); + @LTLengthOf({"arr"}) int d = a - 0; + @LTLengthOf({"arr"}) int e = a - 7; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int f = a - (-7); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"arr"}) int j = 7; - } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"arr"}) int j = 7; + } } diff --git a/checker/tests/index/InvalidSubsequence.java b/checker/tests/index/InvalidSubsequence.java index f15b85d8a17..e4e29653ca5 100644 --- a/checker/tests/index/InvalidSubsequence.java +++ b/checker/tests/index/InvalidSubsequence.java @@ -4,43 +4,43 @@ import org.checkerframework.checker.index.qual.LessThan; public class InvalidSubsequence { - // :: error: flowexpr.parse.error :: error: not.final - @HasSubsequence(subsequence = "banana", from = "this.from", to = "this.to") - int[] a; + // :: error: flowexpr.parse.error :: error: not.final + @HasSubsequence(subsequence = "banana", from = "this.from", to = "this.to") + int[] a; - // :: error: flowexpr.parse.error :: error: not.final - @HasSubsequence(subsequence = "this", from = "banana", to = "this.to") - int[] b; + // :: error: flowexpr.parse.error :: error: not.final + @HasSubsequence(subsequence = "this", from = "banana", to = "this.to") + int[] b; - // :: error: flowexpr.parse.error :: error: not.final - @HasSubsequence(subsequence = "this", from = "this.from", to = "banana") - int[] c; + // :: error: flowexpr.parse.error :: error: not.final + @HasSubsequence(subsequence = "this", from = "this.from", to = "banana") + int[] c; - // :: error: not.final - @HasSubsequence(subsequence = "this", from = "this.from", to = "10") - int[] e; + // :: error: not.final + @HasSubsequence(subsequence = "this", from = "this.from", to = "10") + int[] e; - @IndexFor("a") @LessThan("to") int from; + @IndexFor("a") @LessThan("to") int from; - @IndexOrHigh("a") int to; + @IndexOrHigh("a") int to; - void assignA(int[] d) { - // :: error: to.not.ltel - a = d; - } + void assignA(int[] d) { + // :: error: to.not.ltel + a = d; + } - void assignB(int[] d) { - // :: error: from.gt.to :: error: from.not.nonnegative :: error: to.not.ltel - b = d; - } + void assignB(int[] d) { + // :: error: from.gt.to :: error: from.not.nonnegative :: error: to.not.ltel + b = d; + } - void assignC(int[] d) { - // :: error: from.gt.to :: error: to.not.ltel - c = d; - } + void assignC(int[] d) { + // :: error: from.gt.to :: error: to.not.ltel + c = d; + } - void assignE(int[] d) { - // :: error: from.gt.to :: error: to.not.ltel - e = d; - } + void assignE(int[] d) { + // :: error: from.gt.to :: error: to.not.ltel + e = d; + } } diff --git a/checker/tests/index/Issue1411.java b/checker/tests/index/Issue1411.java index ea57a3a761a..75b9bf10655 100644 --- a/checker/tests/index/Issue1411.java +++ b/checker/tests/index/Issue1411.java @@ -4,14 +4,14 @@ import org.checkerframework.dataflow.qual.Pure; interface IGeneric { - @Pure - public V get(); + @Pure + public V get(); } interface IConcrete extends IGeneric {} public class Issue1411 { - static void m(IConcrete ic) { - char[] val = ic.get(); - } + static void m(IConcrete ic) { + char[] val = ic.get(); + } } diff --git a/checker/tests/index/Issue194.java b/checker/tests/index/Issue194.java index a4788cbf9b9..19e06f1889a 100644 --- a/checker/tests/index/Issue194.java +++ b/checker/tests/index/Issue194.java @@ -6,38 +6,38 @@ import org.checkerframework.checker.index.qual.SameLen; public class Issue194 { - class Custom { - public @LengthOf("this") int length() { - throw new RuntimeException(); - } + class Custom { + public @LengthOf("this") int length() { + throw new RuntimeException(); + } - public Object get(@IndexFor("this") int i) { - return null; - } + public Object get(@IndexFor("this") int i) { + return null; + } - void call() { - length(); - } + void call() { + length(); } + } - public boolean m(Custom a, Custom b) { - if (a.length() != b.length()) { - return false; - } - for (int i = 0; i < a.length(); ++i) { - if (a.get(i) != b.get(i)) { - return false; - } - } - return true; + public boolean m(Custom a, Custom b) { + if (a.length() != b.length()) { + return false; + } + for (int i = 0; i < a.length(); ++i) { + if (a.get(i) != b.get(i)) { + return false; + } } + return true; + } - @SuppressWarnings("anno.on.irrelevant") - public void m2(Custom a, Custom b) { - if (a.length() != b.length()) { - return; - } - @SameLen("a") Custom a2 = b; - @SameLen("b") Custom b2 = a; + @SuppressWarnings("anno.on.irrelevant") + public void m2(Custom a, Custom b) { + if (a.length() != b.length()) { + return; } + @SameLen("a") Custom a2 = b; + @SameLen("b") Custom b2 = a; + } } diff --git a/checker/tests/index/Issue1984.java b/checker/tests/index/Issue1984.java index fd2e65709e9..1916f000ccf 100644 --- a/checker/tests/index/Issue1984.java +++ b/checker/tests/index/Issue1984.java @@ -4,8 +4,8 @@ import org.checkerframework.common.value.qual.IntRange; public class Issue1984 { - public int m(int[] a, @IntRange(from = 0, to = 12) int i) { - // :: error: (array.access.unsafe.high.range) - return a[i]; - } + public int m(int[] a, @IntRange(from = 0, to = 12) int i) { + // :: error: (array.access.unsafe.high.range) + return a[i]; + } } diff --git a/checker/tests/index/Issue20.java b/checker/tests/index/Issue20.java index f2dc36f942b..67d4a515da1 100644 --- a/checker/tests/index/Issue20.java +++ b/checker/tests/index/Issue20.java @@ -2,10 +2,10 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class Issue20 { - // An issue with LUB that results in losing information when unifying. - int[] a, b; + // An issue with LUB that results in losing information when unifying. + int[] a, b; - void test(@LTLengthOf("a") int i, @LTEqLengthOf({"a", "b"}) int j, boolean flag) { - @LTEqLengthOf("a") int k = flag ? i : j; - } + void test(@LTLengthOf("a") int i, @LTEqLengthOf({"a", "b"}) int j, boolean flag) { + @LTEqLengthOf("a") int k = flag ? i : j; + } } diff --git a/checker/tests/index/Issue2029.java b/checker/tests/index/Issue2029.java index d948712a273..5e49ce36311 100644 --- a/checker/tests/index/Issue2029.java +++ b/checker/tests/index/Issue2029.java @@ -3,30 +3,27 @@ import org.checkerframework.checker.index.qual.NonNegative; public class Issue2029 { - void lessThanUpperBound( - @NonNegative @LessThan("#2") int index, @NonNegative int size, char val) { - char[] arr = new char[size]; - arr[index] = val; - } + void lessThanUpperBound(@NonNegative @LessThan("#2") int index, @NonNegative int size, char val) { + char[] arr = new char[size]; + arr[index] = val; + } - void LessThanOffsetLowerBound( - int[] array, - @NonNegative @LTLengthOf("#1") int n, - @NonNegative @LessThan("#2 + 1") int k) { - array[n - k] = 10; - } + void LessThanOffsetLowerBound( + int[] array, @NonNegative @LTLengthOf("#1") int n, @NonNegative @LessThan("#2 + 1") int k) { + array[n - k] = 10; + } - void LessThanOffsetUpperBound( - @NonNegative int n, - @NonNegative @LessThan("#1 + 1") int k, - @NonNegative int size, - @NonNegative @LessThan("#3 + 1") int index) { - @NonNegative int m = n - k; - int[] arr = new int[size]; - // TODO: understand whether this is a false positive - // :: error: (unary.increment.type.incompatible) - for (; index < arr.length - 1; index++) { - arr[index] = 10; - } + void LessThanOffsetUpperBound( + @NonNegative int n, + @NonNegative @LessThan("#1 + 1") int k, + @NonNegative int size, + @NonNegative @LessThan("#3 + 1") int index) { + @NonNegative int m = n - k; + int[] arr = new int[size]; + // TODO: understand whether this is a false positive + // :: error: (unary.increment.type.incompatible) + for (; index < arr.length - 1; index++) { + arr[index] = 10; } + } } diff --git a/checker/tests/index/Issue2030.java b/checker/tests/index/Issue2030.java index a6a1a0b08c3..b6465b073b5 100644 --- a/checker/tests/index/Issue2030.java +++ b/checker/tests/index/Issue2030.java @@ -1,9 +1,9 @@ public class Issue2030 { - double roundIntermediate(double x) { - if (x >= 0.0) { - return x; - } else { - return (long) x - 1; - } + double roundIntermediate(double x) { + if (x >= 0.0) { + return x; + } else { + return (long) x - 1; } + } } diff --git a/checker/tests/index/Issue21.java b/checker/tests/index/Issue21.java index f474d7893f5..226a558201b 100644 --- a/checker/tests/index/Issue21.java +++ b/checker/tests/index/Issue21.java @@ -1,8 +1,8 @@ public class Issue21 { - void test(int[] arr, int[] arr2) { - for (int i = 0; i < arr2.length && i < arr.length; i++) { - arr[i] = arr2[i]; - } + void test(int[] arr, int[] arr2) { + for (int i = 0; i < arr2.length && i < arr.length; i++) { + arr[i] = arr2[i]; } + } } diff --git a/checker/tests/index/Issue2334.java b/checker/tests/index/Issue2334.java index c81a8b8115e..9213a89a403 100644 --- a/checker/tests/index/Issue2334.java +++ b/checker/tests/index/Issue2334.java @@ -4,37 +4,37 @@ public class Issue2334 { - void hasSideEffect() {} + void hasSideEffect() {} - String stringField; + String stringField; - void m1(String stringFormal) { - if (stringFormal.indexOf('d') != -1) { - hasSideEffect(); - @NonNegative int i = stringFormal.indexOf('d'); - } + void m1(String stringFormal) { + if (stringFormal.indexOf('d') != -1) { + hasSideEffect(); + @NonNegative int i = stringFormal.indexOf('d'); } + } - void m2() { - if (stringField.indexOf('d') != -1) { - hasSideEffect(); - // :: error: (assignment.type.incompatible) - @NonNegative int i = stringField.indexOf('d'); - } + void m2() { + if (stringField.indexOf('d') != -1) { + hasSideEffect(); + // :: error: (assignment.type.incompatible) + @NonNegative int i = stringField.indexOf('d'); } + } - void m3(String stringFormal) { - if (stringFormal.indexOf('d') != -1) { - System.out.println("hey"); - @NonNegative int i = stringFormal.indexOf('d'); - } + void m3(String stringFormal) { + if (stringFormal.indexOf('d') != -1) { + System.out.println("hey"); + @NonNegative int i = stringFormal.indexOf('d'); } + } - void m4() { - if (stringField.indexOf('d') != -1) { - System.out.println("hey"); - // :: error: (assignment.type.incompatible) - @NonNegative int i = stringField.indexOf('d'); - } + void m4() { + if (stringField.indexOf('d') != -1) { + System.out.println("hey"); + // :: error: (assignment.type.incompatible) + @NonNegative int i = stringField.indexOf('d'); } + } } diff --git a/checker/tests/index/Issue2420.java b/checker/tests/index/Issue2420.java index 7ed8cc7dd82..6820f56cedb 100644 --- a/checker/tests/index/Issue2420.java +++ b/checker/tests/index/Issue2420.java @@ -2,16 +2,16 @@ import org.checkerframework.common.value.qual.*; public class Issue2420 { - static void str(String argStr) { - if (argStr.isEmpty()) { - return; - } - if (argStr == "abc") { - return; - } - // :: error: (argument.type.incompatible) - char c = "abc".charAt(argStr.length() - 1); - // :: error: (argument.type.incompatible) - char c2 = "abc".charAt(argStr.length()); + static void str(String argStr) { + if (argStr.isEmpty()) { + return; } + if (argStr == "abc") { + return; + } + // :: error: (argument.type.incompatible) + char c = "abc".charAt(argStr.length() - 1); + // :: error: (argument.type.incompatible) + char c2 = "abc".charAt(argStr.length()); + } } diff --git a/checker/tests/index/Issue2452.java b/checker/tests/index/Issue2452.java index 02374f96d24..eca2e220508 100644 --- a/checker/tests/index/Issue2452.java +++ b/checker/tests/index/Issue2452.java @@ -1,5 +1,6 @@ // Test case for https://github.com/typetools/checker-framework/issues/2452 +import java.lang.reflect.Array; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.checker.index.qual.LTEqLengthOf; @@ -7,34 +8,32 @@ import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.value.qual.MinLen; -import java.lang.reflect.Array; - class Issue2452 { - Object m1(Object[] a1) { - if (Array.getLength(a1) > 0) { - return Array.get(a1, 0); - } else { - return null; - } + Object m1(Object[] a1) { + if (Array.getLength(a1) > 0) { + return Array.get(a1, 0); + } else { + return null; } + } - void m2() { - int[] arr = {1, 2, 3}; - @LTEqLengthOf({"arr"}) int a = Array.getLength(arr); - } + void m2() { + int[] arr = {1, 2, 3}; + @LTEqLengthOf({"arr"}) int a = Array.getLength(arr); + } - void testMinLenSubtractPositive(String @MinLen(10) [] s) { - @Positive int i1 = s.length - 9; - @NonNegative int i0 = Array.getLength(s) - 10; - // :: error: (assignment.type.incompatible) - @NonNegative int im1 = Array.getLength(s) - 11; - } + void testMinLenSubtractPositive(String @MinLen(10) [] s) { + @Positive int i1 = s.length - 9; + @NonNegative int i0 = Array.getLength(s) - 10; + // :: error: (assignment.type.incompatible) + @NonNegative int im1 = Array.getLength(s) - 11; + } - void testLessThanLength(String[] s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { - if (i < Array.getLength(s)) { - @IndexFor("s") int in = i; - // :: error: (assignment.type.incompatible) - @IndexFor("s") int jn = j; - } + void testLessThanLength(String[] s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { + if (i < Array.getLength(s)) { + @IndexFor("s") int in = i; + // :: error: (assignment.type.incompatible) + @IndexFor("s") int jn = j; } + } } diff --git a/checker/tests/index/Issue2493.java b/checker/tests/index/Issue2493.java index 2a65c9cd930..8dc274718dc 100644 --- a/checker/tests/index/Issue2493.java +++ b/checker/tests/index/Issue2493.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.index.qual.*; public class Issue2493 { - public static void test(int a[], int @SameLen("#1") [] b) { - for (@IndexOrHigh("b") int i = 0; i < a.length; i++) {} - } + public static void test(int a[], int @SameLen("#1") [] b) { + for (@IndexOrHigh("b") int i = 0; i < a.length; i++) {} + } } diff --git a/checker/tests/index/Issue2494.java b/checker/tests/index/Issue2494.java index 9ad50bca77c..a4bf87bc234 100644 --- a/checker/tests/index/Issue2494.java +++ b/checker/tests/index/Issue2494.java @@ -8,20 +8,20 @@ public final class Issue2494 { - static final long @MinLen(1) [] factorials = { - 1L, - 1L, - 1L * 2, - 1L * 2 * 3, - 1L * 2 * 3 * 4, - 1L * 2 * 3 * 4 * 5, - 1L * 2 * 3 * 4 * 5 * 6, - 1L * 2 * 3 * 4 * 5 * 6 * 7 - }; + static final long @MinLen(1) [] factorials = { + 1L, + 1L, + 1L * 2, + 1L * 2 * 3, + 1L * 2 * 3 * 4, + 1L * 2 * 3 * 4 * 5, + 1L * 2 * 3 * 4 * 5 * 6, + 1L * 2 * 3 * 4 * 5 * 6 * 7 + }; - static void binomialA( - @NonNegative @LTLengthOf("Issue2494.factorials") int n, - @NonNegative @LessThan("#1 + 1") int k) { - @IndexFor("factorials") int j = k; - } + static void binomialA( + @NonNegative @LTLengthOf("Issue2494.factorials") int n, + @NonNegative @LessThan("#1 + 1") int k) { + @IndexFor("factorials") int j = k; + } } diff --git a/checker/tests/index/Issue2505.java b/checker/tests/index/Issue2505.java index e20533009ac..5318f5b3626 100644 --- a/checker/tests/index/Issue2505.java +++ b/checker/tests/index/Issue2505.java @@ -1,10 +1,10 @@ import org.checkerframework.common.value.qual.MinLen; public class Issue2505 { - public static void warningIfStatement(int @MinLen(1) [] a) { - int i = a.length; - if (--i >= 0) { - a[i] = 0; - } + public static void warningIfStatement(int @MinLen(1) [] a) { + int i = a.length; + if (--i >= 0) { + a[i] = 0; } + } } diff --git a/checker/tests/index/Issue2613.java b/checker/tests/index/Issue2613.java index ac47cc6bb5e..0dc7603e917 100644 --- a/checker/tests/index/Issue2613.java +++ b/checker/tests/index/Issue2613.java @@ -3,22 +3,22 @@ public class Issue2613 { - private static final String STRING_CONSTANT = "Hello"; + private static final String STRING_CONSTANT = "Hello"; - void integerConstant() { - require_lt(0, Integer.MAX_VALUE); - } + void integerConstant() { + require_lt(0, Integer.MAX_VALUE); + } - void StringConstant() { - require_lt(0, STRING_CONSTANT); - } + void StringConstant() { + require_lt(0, STRING_CONSTANT); + } - void require_lt(@LessThan("#2") int a, int b) {} + void require_lt(@LessThan("#2") int a, int b) {} - void require_lt(@LTLengthOf("#2") int a, String b) {} + void require_lt(@LTLengthOf("#2") int a, String b) {} - void method(@LessThan("Integer.MAX_VALUE") long x, @LessThan("Integer.MAX_VALUE") long y) { - x = y; - @LessThan("2147483647") long z = y; - } + void method(@LessThan("Integer.MAX_VALUE") long x, @LessThan("Integer.MAX_VALUE") long y) { + x = y; + @LessThan("2147483647") long z = y; + } } diff --git a/checker/tests/index/Issue2629.java b/checker/tests/index/Issue2629.java index ea16381a0c3..851c8accc90 100644 --- a/checker/tests/index/Issue2629.java +++ b/checker/tests/index/Issue2629.java @@ -4,7 +4,7 @@ import org.checkerframework.checker.index.qual.LessThan; public class Issue2629 { - @LessThan("#1 + 1") int test(int a) { - return a; - } + @LessThan("#1 + 1") int test(int a) { + return a; + } } diff --git a/checker/tests/index/Issue3207.java b/checker/tests/index/Issue3207.java index b2f72a87e2c..01b6e306d66 100644 --- a/checker/tests/index/Issue3207.java +++ b/checker/tests/index/Issue3207.java @@ -5,16 +5,16 @@ public class Issue3207 { - void m(int @MinLen(1) [] arr) { - @LTLengthOf("arr") int j = 0; - } + void m(int @MinLen(1) [] arr) { + @LTLengthOf("arr") int j = 0; + } - void m2(int @MinLen(1) [] @MinLen(1) [] arr) { - @LTLengthOf("arr[0]") int j = 0; - } + void m2(int @MinLen(1) [] @MinLen(1) [] arr) { + @LTLengthOf("arr[0]") int j = 0; + } - void m3(int @MinLen(1) [] @MinLen(1) [] arr) { - int @MinLen(1) [] arr0 = arr[0]; - @LTLengthOf("arr0") int j = 0; - } + void m3(int @MinLen(1) [] @MinLen(1) [] arr) { + int @MinLen(1) [] arr0 = arr[0]; + @LTLengthOf("arr0") int j = 0; + } } diff --git a/checker/tests/index/Issue3224.java b/checker/tests/index/Issue3224.java index d37a6606c57..fa042b62c3e 100644 --- a/checker/tests/index/Issue3224.java +++ b/checker/tests/index/Issue3224.java @@ -1,41 +1,40 @@ // Test case for https://tinyurl.com/cfissue/3224 +import java.util.Arrays; import org.checkerframework.common.value.qual.IntRange; import org.checkerframework.common.value.qual.MinLen; -import java.util.Arrays; - public class Issue3224 { - static class Arrays { - static String[] copyOf(String[] args, int length) { - return args; - } - } - - public static void m1(String @MinLen(1) [] args) { - int i = 4; - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, i); - } - - public static void m2(String @MinLen(1) [] args) { - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); - } - - public static void m3(String @MinLen(1) ... args) { - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); - } - - public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) { - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, len); - } - - public static void m5(String @MinLen(1) [] args, String[] otherArray) { - // :: error: assignment.type.incompatible - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, otherArray.length); - } - - public static void m6(String @MinLen(1) [] args) { - // :: error: assignment.type.incompatible - String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length); + static class Arrays { + static String[] copyOf(String[] args, int length) { + return args; } + } + + public static void m1(String @MinLen(1) [] args) { + int i = 4; + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, i); + } + + public static void m2(String @MinLen(1) [] args) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); + } + + public static void m3(String @MinLen(1) ... args) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); + } + + public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, len); + } + + public static void m5(String @MinLen(1) [] args, String[] otherArray) { + // :: error: assignment.type.incompatible + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, otherArray.length); + } + + public static void m6(String @MinLen(1) [] args) { + // :: error: assignment.type.incompatible + String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length); + } } diff --git a/checker/tests/index/Issue5471.java b/checker/tests/index/Issue5471.java index 9f28c2c9150..9de1bf61766 100644 --- a/checker/tests/index/Issue5471.java +++ b/checker/tests/index/Issue5471.java @@ -3,20 +3,20 @@ import org.checkerframework.checker.index.qual.IndexFor; public class Issue5471 { - private static boolean atTheBeginning(@IndexFor("#2") int index, String line) { - return (index == 0); - } + private static boolean atTheBeginning(@IndexFor("#2") int index, String line) { + return (index == 0); + } - private static boolean hasDoubleQuestionMarkAtTheBeginning(String line) { - int i = line.indexOf("??"); - if (i != -1) { - return (atTheBeginning(i, line)); - } - return false; + private static boolean hasDoubleQuestionMarkAtTheBeginning(String line) { + int i = line.indexOf("??"); + if (i != -1) { + return (atTheBeginning(i, line)); } + return false; + } - public static void main(String[] args) { - String x = "Hello?World, this is our new program"; - if (hasDoubleQuestionMarkAtTheBeginning(x)) System.out.println("TRUE"); - } + public static void main(String[] args) { + String x = "Hello?World, this is our new program"; + if (hasDoubleQuestionMarkAtTheBeginning(x)) System.out.println("TRUE"); + } } diff --git a/checker/tests/index/Issue58Minimization.java b/checker/tests/index/Issue58Minimization.java index 1f8fcb98348..d384bbee93a 100644 --- a/checker/tests/index/Issue58Minimization.java +++ b/checker/tests/index/Issue58Minimization.java @@ -8,55 +8,55 @@ public class Issue58Minimization { - void test(@GTENegativeOne int x) { - int z; - if ((z = x) != -1) { - @NonNegative int y = z; - } - if ((z = x) != 1) { - // :: error: (assignment.type.incompatible) - @NonNegative int y = z; - } + void test(@GTENegativeOne int x) { + int z; + if ((z = x) != -1) { + @NonNegative int y = z; } + if ((z = x) != 1) { + // :: error: (assignment.type.incompatible) + @NonNegative int y = z; + } + } - void test2(@NonNegative int x) { - int z; - if ((z = x) != 0) { - @Positive int y = z; - } - if ((z = x) == 0) { - // do nothing - int y = 5; - } else { - @Positive int y = x; - } + void test2(@NonNegative int x) { + int z; + if ((z = x) != 0) { + @Positive int y = z; + } + if ((z = x) == 0) { + // do nothing + int y = 5; + } else { + @Positive int y = x; } + } - void ubc_test(int[] a, @LTEqLengthOf("#1") int x) { - int z; - if ((z = x) != a.length) { - @LTLengthOf("a") int y = z; - } + void ubc_test(int[] a, @LTEqLengthOf("#1") int x) { + int z; + if ((z = x) != a.length) { + @LTLengthOf("a") int y = z; } + } - void samelen_test(int[] a, int[] c) { - int[] b; - if ((b = a) == c) { - int @SameLen({"a", "b", "c"}) [] d = b; - } + void samelen_test(int[] a, int[] c) { + int[] b; + if ((b = a) == c) { + int @SameLen({"a", "b", "c"}) [] d = b; } + } - void minlen_test(int[] a, int @MinLen(1) [] c) { - int[] b; - if ((b = a) == c) { - int @MinLen(1) [] d = b; - } + void minlen_test(int[] a, int @MinLen(1) [] c) { + int[] b; + if ((b = a) == c) { + int @MinLen(1) [] d = b; } + } - void minlen_test2(int[] a, int x) { - int one = 1; - if ((x = one) == a.length) { - int @MinLen(1) [] b = a; - } + void minlen_test2(int[] a, int x) { + int one = 1; + if ((x = one) == a.length) { + int @MinLen(1) [] b = a; } + } } diff --git a/checker/tests/index/Issue60.java b/checker/tests/index/Issue60.java index eddfb9dc238..7c678b0ab86 100644 --- a/checker/tests/index/Issue60.java +++ b/checker/tests/index/Issue60.java @@ -5,16 +5,16 @@ public class Issue60 { - public static int[] fn_compose(@IndexFor("#2") int[] a, int[] b) { - int[] result = new int[a.length]; - for (int i = 0; i < a.length; i++) { - int inner = a[i]; - if (inner == -1) { - result[i] = -1; - } else { - result[i] = b[inner]; - } - } - return result; + public static int[] fn_compose(@IndexFor("#2") int[] a, int[] b) { + int[] result = new int[a.length]; + for (int i = 0; i < a.length; i++) { + int inner = a[i]; + if (inner == -1) { + result[i] = -1; + } else { + result[i] = b[inner]; + } } + return result; + } } diff --git a/checker/tests/index/IteratorVoid.java b/checker/tests/index/IteratorVoid.java index 36f7fc80fdd..db77a3f2036 100644 --- a/checker/tests/index/IteratorVoid.java +++ b/checker/tests/index/IteratorVoid.java @@ -4,10 +4,10 @@ // that Void is given the Positive type. public class IteratorVoid { - T next1; - Iterator itor1; + T next1; + Iterator itor1; - private void setnext1() { - next1 = itor1.hasNext() ? itor1.next() : null; - } + private void setnext1() { + next1 = itor1.hasNext() ? itor1.next() : null; + } } diff --git a/checker/tests/index/Kelloggm225.java b/checker/tests/index/Kelloggm225.java index 0bc334b6a53..1cb54bac9ea 100644 --- a/checker/tests/index/Kelloggm225.java +++ b/checker/tests/index/Kelloggm225.java @@ -2,12 +2,12 @@ import org.checkerframework.common.value.qual.*; public class Kelloggm225 { - void method(int @MinLen(1) [] bar) { - foo(bar, 0, bar.length); - } + void method(int @MinLen(1) [] bar) { + foo(bar, 0, bar.length); + } - void foo( - int @MinLen(1) [] bar, - @IndexFor("#1") @LessThan("#3") int start, - @IndexOrHigh("#1") int end) {} + void foo( + int @MinLen(1) [] bar, + @IndexFor("#1") @LessThan("#3") int start, + @IndexOrHigh("#1") int end) {} } diff --git a/checker/tests/index/Kelloggm228.java b/checker/tests/index/Kelloggm228.java index 3cc97b32389..4be1b6b91a4 100644 --- a/checker/tests/index/Kelloggm228.java +++ b/checker/tests/index/Kelloggm228.java @@ -3,13 +3,12 @@ import org.checkerframework.checker.index.qual.Positive; public class Kelloggm228 { - public void subList( - @IndexOrHigh("this") @LessThan("#2 + 1") int fromIndex, - @IndexOrHigh("this") int toIndex) { - if (fromIndex == toIndex) { - return; - } - - @Positive int x = toIndex; + public void subList( + @IndexOrHigh("this") @LessThan("#2 + 1") int fromIndex, @IndexOrHigh("this") int toIndex) { + if (fromIndex == toIndex) { + return; } + + @Positive int x = toIndex; + } } diff --git a/checker/tests/index/LBCSubtyping.java b/checker/tests/index/LBCSubtyping.java index 3ef5a1df2dc..0facc88224d 100644 --- a/checker/tests/index/LBCSubtyping.java +++ b/checker/tests/index/LBCSubtyping.java @@ -5,42 +5,42 @@ public class LBCSubtyping { - void foo() { + void foo() { - @GTENegativeOne int i = -1; + @GTENegativeOne int i = -1; - @LowerBoundUnknown int j = i; + @LowerBoundUnknown int j = i; - int k = -4; + int k = -4; - // not this one though - // :: error: (assignment.type.incompatible) - @GTENegativeOne int l = k; + // not this one though + // :: error: (assignment.type.incompatible) + @GTENegativeOne int l = k; - @NonNegative int n = 0; + @NonNegative int n = 0; - @Positive int a = 1; + @Positive int a = 1; - // check that everything is aboveboard - j = a; - j = n; - l = n; - n = a; + // check that everything is aboveboard + j = a; + j = n; + l = n; + n = a; - // error cases + // error cases - // :: error: (assignment.type.incompatible) - @NonNegative int p = i; - // :: error: (assignment.type.incompatible) - @Positive int b = i; + // :: error: (assignment.type.incompatible) + @NonNegative int p = i; + // :: error: (assignment.type.incompatible) + @Positive int b = i; - // :: error: (assignment.type.incompatible) - @NonNegative int r = k; - // :: error: (assignment.type.incompatible) - @Positive int c = k; + // :: error: (assignment.type.incompatible) + @NonNegative int r = k; + // :: error: (assignment.type.incompatible) + @Positive int c = k; - // :: error: (assignment.type.incompatible) - @Positive int d = r; - } + // :: error: (assignment.type.incompatible) + @Positive int d = r; + } } // a comment diff --git a/checker/tests/index/LTLDivide.java b/checker/tests/index/LTLDivide.java index 631c24cfa92..d3bd176e74f 100644 --- a/checker/tests/index/LTLDivide.java +++ b/checker/tests/index/LTLDivide.java @@ -2,25 +2,25 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class LTLDivide { - int[] test(int[] array) { - // @LTLengthOf("array") int len = array.length / 2; - int len = array.length / 2; - int[] arr = new int[len]; - for (int a = 0; a < len; a++) { - arr[a] = array[a]; - } - return arr; + int[] test(int[] array) { + // @LTLengthOf("array") int len = array.length / 2; + int len = array.length / 2; + int[] arr = new int[len]; + for (int a = 0; a < len; a++) { + arr[a] = array[a]; } + return arr; + } - void test2(int[] array) { - int len = array.length; - int lenM1 = array.length - 1; - int lenP1 = array.length + 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int x = len / 2; - @LTLengthOf("array") int y = lenM1 / 3; - @LTEqLengthOf("array") int z = len / 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int w = lenP1 / 2; - } + void test2(int[] array) { + int len = array.length; + int lenM1 = array.length - 1; + int lenP1 = array.length + 1; + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int x = len / 2; + @LTLengthOf("array") int y = lenM1 / 3; + @LTEqLengthOf("array") int z = len / 1; + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int w = lenP1 / 2; + } } diff --git a/checker/tests/index/LTLengthOfPostcondition.java b/checker/tests/index/LTLengthOfPostcondition.java index 8ec232b8372..0c79595e2c8 100644 --- a/checker/tests/index/LTLengthOfPostcondition.java +++ b/checker/tests/index/LTLengthOfPostcondition.java @@ -1,47 +1,42 @@ +import java.util.Arrays; import org.checkerframework.checker.index.qual.EnsuresLTLengthOf; import org.checkerframework.checker.index.qual.EnsuresLTLengthOfIf; import org.checkerframework.checker.index.qual.LTEqLengthOf; import org.checkerframework.checker.index.qual.NonNegative; -import java.util.Arrays; - public class LTLengthOfPostcondition { - Object[] array; - - @NonNegative @LTEqLengthOf("array") int end; - - @EnsuresLTLengthOf(value = "end", targetValue = "array", offset = "#1 - 1") - public void shiftIndex(@NonNegative int x) { - int newEnd = end - x; - if (newEnd < 0) throw new RuntimeException(); - end = newEnd; - } - - public void useShiftIndex(@NonNegative int x) { - // :: error: (argument.type.incompatible) - Arrays.fill(array, end, end + x, null); - shiftIndex(x); - Arrays.fill(array, end, end + x, null); - } - - @EnsuresLTLengthOfIf( - expression = "end", - result = true, - targetValue = "array", - offset = "#1 - 1") - public boolean tryShiftIndex(@NonNegative int x) { - int newEnd = end - x; - if (newEnd < 0) { - return false; - } - end = newEnd; - return true; + Object[] array; + + @NonNegative @LTEqLengthOf("array") int end; + + @EnsuresLTLengthOf(value = "end", targetValue = "array", offset = "#1 - 1") + public void shiftIndex(@NonNegative int x) { + int newEnd = end - x; + if (newEnd < 0) throw new RuntimeException(); + end = newEnd; + } + + public void useShiftIndex(@NonNegative int x) { + // :: error: (argument.type.incompatible) + Arrays.fill(array, end, end + x, null); + shiftIndex(x); + Arrays.fill(array, end, end + x, null); + } + + @EnsuresLTLengthOfIf(expression = "end", result = true, targetValue = "array", offset = "#1 - 1") + public boolean tryShiftIndex(@NonNegative int x) { + int newEnd = end - x; + if (newEnd < 0) { + return false; } + end = newEnd; + return true; + } - public void useTryShiftIndex(@NonNegative int x) { - if (tryShiftIndex(x)) { - Arrays.fill(array, end, end + x, null); - } + public void useTryShiftIndex(@NonNegative int x) { + if (tryShiftIndex(x)) { + Arrays.fill(array, end, end + x, null); } + } } diff --git a/checker/tests/index/LengthOfArrayMinusOne.java b/checker/tests/index/LengthOfArrayMinusOne.java index e575024e4d7..713002091d4 100644 --- a/checker/tests/index/LengthOfArrayMinusOne.java +++ b/checker/tests/index/LengthOfArrayMinusOne.java @@ -1,10 +1,10 @@ public class LengthOfArrayMinusOne { - void test(int[] arr) { - // :: error: (array.access.unsafe.low) - int i = arr[arr.length - 1]; + void test(int[] arr) { + // :: error: (array.access.unsafe.low) + int i = arr[arr.length - 1]; - if (arr.length > 0) { - int j = arr[arr.length - 1]; - } + if (arr.length > 0) { + int j = arr[arr.length - 1]; } + } } diff --git a/checker/tests/index/LengthOfTest.java b/checker/tests/index/LengthOfTest.java index 68f79675aca..ba0c0e05a8a 100644 --- a/checker/tests/index/LengthOfTest.java +++ b/checker/tests/index/LengthOfTest.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.index.qual.*; public class LengthOfTest { - void foo(int[] a, @LengthOf("#1") int x) { - @IndexOrHigh("a") int y = x; - // :: error: (assignment.type.incompatible) - @IndexFor("a") int w = x; - @LengthOf("a") int z = a.length; - } + void foo(int[] a, @LengthOf("#1") int x) { + @IndexOrHigh("a") int y = x; + // :: error: (assignment.type.incompatible) + @IndexFor("a") int w = x; + @LengthOf("a") int z = a.length; + } } diff --git a/checker/tests/index/LengthTransfer.java b/checker/tests/index/LengthTransfer.java index c7e5bbf6380..f8ad5db91cc 100644 --- a/checker/tests/index/LengthTransfer.java +++ b/checker/tests/index/LengthTransfer.java @@ -1,21 +1,21 @@ public class LengthTransfer { - void exceptional_control_flow(int[] a) { - if (a.length == 0) { - throw new IllegalArgumentException(); - } - int i = a[0]; + void exceptional_control_flow(int[] a) { + if (a.length == 0) { + throw new IllegalArgumentException(); } + int i = a[0]; + } - void equal_to_return(int[] a) { - if (a.length == 0) { - return; - } - int i = a[0]; + void equal_to_return(int[] a) { + if (a.length == 0) { + return; } + int i = a[0]; + } - void gt_check(int[] a) { - if (a.length > 0) { - int i = a[0]; - } + void gt_check(int[] a) { + if (a.length > 0) { + int i = a[0]; } + } } diff --git a/checker/tests/index/LengthTransfer2.java b/checker/tests/index/LengthTransfer2.java index b4b9ef91fe5..108a83c452f 100644 --- a/checker/tests/index/LengthTransfer2.java +++ b/checker/tests/index/LengthTransfer2.java @@ -3,17 +3,17 @@ import org.checkerframework.common.value.qual.*; public class LengthTransfer2 { - public static void main(String[] args) { - if (args.length != 2) { - System.err.println("Needs 2 arguments, got " + args.length); - System.exit(1); - } - int limit = Integer.parseInt(args[0]); - int period = Integer.parseInt(args[1]); + public static void main(String[] args) { + if (args.length != 2) { + System.err.println("Needs 2 arguments, got " + args.length); + System.exit(1); } + int limit = Integer.parseInt(args[0]); + int period = Integer.parseInt(args[1]); + } - void m(String @ArrayLen(2) [] args) { - int limit = Integer.parseInt(args[0]); - int period = Integer.parseInt(args[1]); - } + void m(String @ArrayLen(2) [] args) { + int limit = Integer.parseInt(args[0]); + int period = Integer.parseInt(args[1]); + } } diff --git a/checker/tests/index/LengthTransferForMinLen.java b/checker/tests/index/LengthTransferForMinLen.java index 21d9879f76d..e1f62cd9606 100644 --- a/checker/tests/index/LengthTransferForMinLen.java +++ b/checker/tests/index/LengthTransferForMinLen.java @@ -1,23 +1,23 @@ import org.checkerframework.common.value.qual.MinLen; public class LengthTransferForMinLen { - void exceptional_control_flow(int[] a) { - if (a.length == 0) { - throw new IllegalArgumentException(); - } - int @MinLen(1) [] b = a; + void exceptional_control_flow(int[] a) { + if (a.length == 0) { + throw new IllegalArgumentException(); } + int @MinLen(1) [] b = a; + } - void equal_to_return(int[] a) { - if (a.length == 0) { - return; - } - int @MinLen(1) [] b = a; + void equal_to_return(int[] a) { + if (a.length == 0) { + return; } + int @MinLen(1) [] b = a; + } - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; - } + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; } + } } diff --git a/checker/tests/index/LessThanBug.java b/checker/tests/index/LessThanBug.java index dd557f22311..b1ad7a9c1f0 100644 --- a/checker/tests/index/LessThanBug.java +++ b/checker/tests/index/LessThanBug.java @@ -4,12 +4,12 @@ public class LessThanBug { - void call() { - bug(30, 1); - } + void call() { + bug(30, 1); + } - void bug(@IntRange(to = 42) int a, @IntVal(1) int c) { - // :: error: (assignment.type.incompatible) - @LessThan("c") int x = a; - } + void bug(@IntRange(to = 42) int a, @IntVal(1) int c) { + // :: error: (assignment.type.incompatible) + @LessThan("c") int x = a; + } } diff --git a/checker/tests/index/LessThanConstantAddition.java b/checker/tests/index/LessThanConstantAddition.java index c1fbb492350..1986089e4c0 100644 --- a/checker/tests/index/LessThanConstantAddition.java +++ b/checker/tests/index/LessThanConstantAddition.java @@ -2,9 +2,9 @@ public class LessThanConstantAddition { - public static void checkedPow(int b) { - if (b <= 2) { - int c = (int) b; - } + public static void checkedPow(int b) { + if (b <= 2) { + int c = (int) b; } + } } diff --git a/checker/tests/index/LessThanCustomCollection.java b/checker/tests/index/LessThanCustomCollection.java index 35699f956cb..0568250caf7 100644 --- a/checker/tests/index/LessThanCustomCollection.java +++ b/checker/tests/index/LessThanCustomCollection.java @@ -12,67 +12,65 @@ // This class has a similar implementation to several Immutable*Array class in Guava, // such as com.google.common.primitives.ImmutableDoubleArray. public class LessThanCustomCollection { - // This object is a subset of array. So, if something is an index for "this" - // then it is >= start and < end. - private final int[] array; - private final @IndexOrHigh("array") @LessThan("end + 1") int start; - private final @LTLengthOf( - value = {"array", "this"}, - offset = {" - 1", "- start"}) int end; + // This object is a subset of array. So, if something is an index for "this" + // then it is >= start and < end. + private final int[] array; + private final @IndexOrHigh("array") @LessThan("end + 1") int start; + private final @LTLengthOf( + value = {"array", "this"}, + offset = {" - 1", "- start"}) int end; - private LessThanCustomCollection(int[] array) { - this(array, 0, array.length); - } + private LessThanCustomCollection(int[] array) { + this(array, 0, array.length); + } - private LessThanCustomCollection( - int[] array, - @IndexOrHigh("#1") @LessThan("#3 + 1") int start, - @IndexOrHigh("#1") int end) { - this.array = array; - // can't est. that end - start is the length of this. - // :: error: (assignment.type.incompatible) - this.end = end; - // start is @LessThan(end + 1) but should be @LessThan(this.end + 1) - // :: error: (assignment.type.incompatible) - this.start = start; - } + private LessThanCustomCollection( + int[] array, @IndexOrHigh("#1") @LessThan("#3 + 1") int start, @IndexOrHigh("#1") int end) { + this.array = array; + // can't est. that end - start is the length of this. + // :: error: (assignment.type.incompatible) + this.end = end; + // start is @LessThan(end + 1) but should be @LessThan(this.end + 1) + // :: error: (assignment.type.incompatible) + this.start = start; + } - @Pure - public @LengthOf("this") int length() { - return end - start; - } + @Pure + public @LengthOf("this") int length() { + return end - start; + } - public double get(@IndexFor("this") int index) { - // TODO: This is a bug. - // :: error: (argument.type.incompatible) - checkElementIndex(index, length()); - // Because index is an index for "this" the index + start - // must be an index for array. - // :: error: (array.access.unsafe.high) - return array[start + index]; - } + public double get(@IndexFor("this") int index) { + // TODO: This is a bug. + // :: error: (argument.type.incompatible) + checkElementIndex(index, length()); + // Because index is an index for "this" the index + start + // must be an index for array. + // :: error: (array.access.unsafe.high) + return array[start + index]; + } - public static @NonNegative int checkElementIndex( - @LessThan("#2") @NonNegative int index, @NonNegative int size) { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(); - } - return index; + public static @NonNegative int checkElementIndex( + @LessThan("#2") @NonNegative int index, @NonNegative int size) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(); } + return index; + } - public @IndexOrLow("this") int indexOf(double target) { - for (int i = start; i < end; i++) { - if (areEqual(array[i], target)) { - // Don't know that is is greater than start. - // :: error: (return.type.incompatible) - return i - start; - } - } - return -1; + public @IndexOrLow("this") int indexOf(double target) { + for (int i = start; i < end; i++) { + if (areEqual(array[i], target)) { + // Don't know that is is greater than start. + // :: error: (return.type.incompatible) + return i - start; + } } + return -1; + } - static boolean areEqual(int item, double target) { - // implementation not relevant - return true; - } + static boolean areEqual(int item, double target) { + // implementation not relevant + return true; + } } diff --git a/checker/tests/index/LessThanDec.java b/checker/tests/index/LessThanDec.java index 782119ad5b6..17bfebc9d91 100644 --- a/checker/tests/index/LessThanDec.java +++ b/checker/tests/index/LessThanDec.java @@ -3,13 +3,13 @@ import org.checkerframework.checker.index.qual.LessThan; public class LessThanDec { - private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( - short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; + private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( + short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } } + return -1; + } } diff --git a/checker/tests/index/LessThanFloat.java b/checker/tests/index/LessThanFloat.java index 9136a5da364..519f8c9de1a 100644 --- a/checker/tests/index/LessThanFloat.java +++ b/checker/tests/index/LessThanFloat.java @@ -1,62 +1,62 @@ import org.checkerframework.checker.index.qual.LessThan; public class LessThanFloat { - int bigger; + int bigger; - @LessThan("bigger") byte b; + @LessThan("bigger") byte b; - @LessThan("bigger") short s; + @LessThan("bigger") short s; - @LessThan("bigger") int i; + @LessThan("bigger") int i; - @LessThan("bigger") long l; + @LessThan("bigger") long l; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") float f; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") float f; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") double d; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") double d; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") boolean bool; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") boolean bool; - @LessThan("bigger") char c; + @LessThan("bigger") char c; - @LessThan("bigger") Byte bBoxed; + @LessThan("bigger") Byte bBoxed; - @LessThan("bigger") Short sBoxed; + @LessThan("bigger") Short sBoxed; - @LessThan("bigger") Integer iBoxed; + @LessThan("bigger") Integer iBoxed; - @LessThan("bigger") Long lBoxed; + @LessThan("bigger") Long lBoxed; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") Float fBoxed; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") Float fBoxed; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") Double dBoxed; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") Double dBoxed; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") Boolean boolBoxed; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") Boolean boolBoxed; - @LessThan("bigger") Character cBoxed; + @LessThan("bigger") Character cBoxed; - java.lang.@LessThan("bigger") Byte bBoxed2; + java.lang.@LessThan("bigger") Byte bBoxed2; - java.lang.@LessThan("bigger") Short sBoxed2; + java.lang.@LessThan("bigger") Short sBoxed2; - java.lang.@LessThan("bigger") Integer iBoxed2; + java.lang.@LessThan("bigger") Integer iBoxed2; - java.lang.@LessThan("bigger") Long lBoxed2; + java.lang.@LessThan("bigger") Long lBoxed2; - // :: error: (anno.on.irrelevant) - java.lang.@LessThan("bigger") Float fBoxed2; + // :: error: (anno.on.irrelevant) + java.lang.@LessThan("bigger") Float fBoxed2; - // :: error: (anno.on.irrelevant) - java.lang.@LessThan("bigger") Double dBoxed2; + // :: error: (anno.on.irrelevant) + java.lang.@LessThan("bigger") Double dBoxed2; - // :: error: (anno.on.irrelevant) - java.lang.@LessThan("bigger") Boolean boolBoxed2; + // :: error: (anno.on.irrelevant) + java.lang.@LessThan("bigger") Boolean boolBoxed2; - java.lang.@LessThan("bigger") Character cBoxed2; + java.lang.@LessThan("bigger") Character cBoxed2; } diff --git a/checker/tests/index/LessThanFloatLiteral.java b/checker/tests/index/LessThanFloatLiteral.java index 5242da9c202..659294de815 100644 --- a/checker/tests/index/LessThanFloatLiteral.java +++ b/checker/tests/index/LessThanFloatLiteral.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.index.qual.LessThan; class LessThanFloatLiteral { - void test(int x) { - if (1.0 > x) { - // TODO: It might be nice to handle comparisons against floats, - // but an array index is not generally compared to a float. - // :: error: assignment.type.incompatible - @LessThan("1") int y = x; - } + void test(int x) { + if (1.0 > x) { + // TODO: It might be nice to handle comparisons against floats, + // but an array index is not generally compared to a float. + // :: error: assignment.type.incompatible + @LessThan("1") int y = x; } + } } diff --git a/checker/tests/index/LessThanLen.java b/checker/tests/index/LessThanLen.java index 432b30d246b..fbb6475d075 100644 --- a/checker/tests/index/LessThanLen.java +++ b/checker/tests/index/LessThanLen.java @@ -3,55 +3,55 @@ public class LessThanLen { - public static void m1() { - int[] shorter = new int[5]; - int[] longer = new int[shorter.length * 2]; - for (int i = 0; i < shorter.length; i++) { - longer[i] = shorter[i]; - } + public static void m1() { + int[] shorter = new int[5]; + int[] longer = new int[shorter.length * 2]; + for (int i = 0; i < shorter.length; i++) { + longer[i] = shorter[i]; } + } - public static void m2(int @MinLen(1) [] shorter) { - int[] longer = new int[shorter.length * 2]; - for (int i = 0; i < shorter.length; i++) { - longer[i] = shorter[i]; - } + public static void m2(int @MinLen(1) [] shorter) { + int[] longer = new int[shorter.length * 2]; + for (int i = 0; i < shorter.length; i++) { + longer[i] = shorter[i]; } + } - public static void m3(int[] shorter) { - int[] longer = new int[shorter.length + 1]; - for (int i = 0; i < shorter.length; i++) { - longer[i] = shorter[i]; - } - } - - public static void m4(int @MinLen(1) [] shorter) { - int[] longer = new int[shorter.length * 1]; - // :: error: (assignment.type.incompatible) - @LTLengthOf("longer") int x = shorter.length; - @LTEqLengthOf("longer") int y = shorter.length; - } - - public static void m5(int[] shorter) { - // :: error: (array.length.negative) - int[] longer = new int[shorter.length * -1]; - // :: error: (assignment.type.incompatible) - @LTLengthOf("longer") int x = shorter.length; - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("longer") int y = shorter.length; - } - - public static void m6(int @MinLen(1) [] shorter) { - int[] longer = new int[4 * shorter.length]; - // TODO: enable when https://github.com/kelloggm/checker-framework/issues/211 is fixed - // // :: error: (assignment.type.incompatible) - // @LTLengthOf("longer") int x = shorter.length; - @LTEqLengthOf("longer") int y = shorter.length; - } - - public static void m7(int @MinLen(1) [] shorter) { - int[] longer = new int[4 * shorter.length]; - @LTLengthOf("longer") int x = shorter.length; - @LTEqLengthOf("longer") int y = shorter.length; + public static void m3(int[] shorter) { + int[] longer = new int[shorter.length + 1]; + for (int i = 0; i < shorter.length; i++) { + longer[i] = shorter[i]; } + } + + public static void m4(int @MinLen(1) [] shorter) { + int[] longer = new int[shorter.length * 1]; + // :: error: (assignment.type.incompatible) + @LTLengthOf("longer") int x = shorter.length; + @LTEqLengthOf("longer") int y = shorter.length; + } + + public static void m5(int[] shorter) { + // :: error: (array.length.negative) + int[] longer = new int[shorter.length * -1]; + // :: error: (assignment.type.incompatible) + @LTLengthOf("longer") int x = shorter.length; + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("longer") int y = shorter.length; + } + + public static void m6(int @MinLen(1) [] shorter) { + int[] longer = new int[4 * shorter.length]; + // TODO: enable when https://github.com/kelloggm/checker-framework/issues/211 is fixed + // // :: error: (assignment.type.incompatible) + // @LTLengthOf("longer") int x = shorter.length; + @LTEqLengthOf("longer") int y = shorter.length; + } + + public static void m7(int @MinLen(1) [] shorter) { + int[] longer = new int[4 * shorter.length]; + @LTLengthOf("longer") int x = shorter.length; + @LTEqLengthOf("longer") int y = shorter.length; + } } diff --git a/checker/tests/index/LessThanLenBug.java b/checker/tests/index/LessThanLenBug.java index 7fc1a40d551..572d86c44c4 100644 --- a/checker/tests/index/LessThanLenBug.java +++ b/checker/tests/index/LessThanLenBug.java @@ -2,20 +2,20 @@ import org.checkerframework.common.value.qual.MinLen; public class LessThanLenBug { - public static void m1(int[] shorter) { - int[] longer = new int[4 * shorter.length]; - // :: error: (assignment.type.incompatible) - @LTLengthOf("longer") int x = shorter.length; - int i = longer[x]; - } + public static void m1(int[] shorter) { + int[] longer = new int[4 * shorter.length]; + // :: error: (assignment.type.incompatible) + @LTLengthOf("longer") int x = shorter.length; + int i = longer[x]; + } - public static void m2(int @MinLen(1) [] shorter) { - int[] longer = new int[4 * shorter.length]; - @LTLengthOf("longer") int x = shorter.length; - int i = longer[x]; - } + public static void m2(int @MinLen(1) [] shorter) { + int[] longer = new int[4 * shorter.length]; + @LTLengthOf("longer") int x = shorter.length; + int i = longer[x]; + } - public static void main(String[] args) { - m1(new int[0]); - } + public static void main(String[] args) { + m1(new int[0]); + } } diff --git a/checker/tests/index/LessThanOrEqualTransfer.java b/checker/tests/index/LessThanOrEqualTransfer.java index cfc7efb01d4..3ab28347f58 100644 --- a/checker/tests/index/LessThanOrEqualTransfer.java +++ b/checker/tests/index/LessThanOrEqualTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class LessThanOrEqualTransfer { - void lte_check(int[] a) { - if (1 <= a.length) { - int @MinLen(1) [] b = a; - } + void lte_check(int[] a) { + if (1 <= a.length) { + int @MinLen(1) [] b = a; } + } - void lte_bad_check(int[] a) { - if (1 <= a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void lte_bad_check(int[] a) { + if (1 <= a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/checker/tests/index/LessThanTransferTest.java b/checker/tests/index/LessThanTransferTest.java index bdb55226ea3..2ee908abe9a 100644 --- a/checker/tests/index/LessThanTransferTest.java +++ b/checker/tests/index/LessThanTransferTest.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class LessThanTransferTest { - void lt_check(int[] a) { - if (0 < a.length) { - int @MinLen(1) [] b = a; - } + void lt_check(int[] a) { + if (0 < a.length) { + int @MinLen(1) [] b = a; } + } - void lt_bad_check(int[] a) { - if (0 < a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void lt_bad_check(int[] a) { + if (0 < a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/checker/tests/index/LessThanValue.java b/checker/tests/index/LessThanValue.java index a59f8117d93..63fa4bc8651 100644 --- a/checker/tests/index/LessThanValue.java +++ b/checker/tests/index/LessThanValue.java @@ -9,115 +9,115 @@ // Test for LessThanChecker public class LessThanValue { - void subtyping(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { - @LessThan("x") int q = a; - @LessThan({"x", "y"}) + void subtyping(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { + @LessThan("x") int q = a; + @LessThan({"x", "y"}) + // :: error: (assignment.type.incompatible) + int r = b; + } + + public static boolean flag; + + void lub(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { + @LessThan("x") int r = flag ? a : b; + @LessThan({"x", "y"}) + // :: error: (assignment.type.incompatible) + int s = flag ? a : b; + } + + void transitive(int a, int b, int c) { + if (a < b) { + if (b < c) { + // TODO: False positive // :: error: (assignment.type.incompatible) - int r = b; + @LessThan("c") int x = a; + } } + } - public static boolean flag; + void calls() { + isLessThan(0, 1); + isLessThanOrEqual(0, 0); + } - void lub(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { - @LessThan("x") int r = flag ? a : b; - @LessThan({"x", "y"}) - // :: error: (assignment.type.incompatible) - int s = flag ? a : b; - } + void isLessThan(@LessThan("#2") @NonNegative int start, int end) { + @NonNegative int x = end - start - 1; + @Positive int y = end - start; + } - void transitive(int a, int b, int c) { - if (a < b) { - if (b < c) { - // TODO: False positive - // :: error: (assignment.type.incompatible) - @LessThan("c") int x = a; - } - } - } + @NonNegative int isLessThanOrEqual(@LessThan("#2 + 1") @NonNegative int start, int end) { + return end - start; + } - void calls() { - isLessThan(0, 1); - isLessThanOrEqual(0, 0); + public void setMaximumItemCount(int maximum) { + if (maximum < 0) { + throw new IllegalArgumentException("Negative 'maximum' argument."); } - - void isLessThan(@LessThan("#2") @NonNegative int start, int end) { - @NonNegative int x = end - start - 1; - @Positive int y = end - start; + int count = getCount(); + if (count > maximum) { + @Positive int y = count - maximum; + @NonNegative int deleteIndex = count - maximum - 1; } + } - @NonNegative int isLessThanOrEqual(@LessThan("#2 + 1") @NonNegative int start, int end) { - return end - start; - } + int getCount() { + throw new RuntimeException(); + } - public void setMaximumItemCount(int maximum) { - if (maximum < 0) { - throw new IllegalArgumentException("Negative 'maximum' argument."); - } - int count = getCount(); - if (count > maximum) { - @Positive int y = count - maximum; - @NonNegative int deleteIndex = count - maximum - 1; - } - } + void method(@NonNegative int m) { + boolean[] has_modulus = new boolean[m]; + @LessThan("m") int x = foo(m); + @IndexFor("has_modulus") int rem = foo(m); + } - int getCount() { - throw new RuntimeException(); - } + @LessThan("#1") @NonNegative int foo(int in) { + throw new RuntimeException(); + } - void method(@NonNegative int m) { - boolean[] has_modulus = new boolean[m]; - @LessThan("m") int x = foo(m); - @IndexFor("has_modulus") int rem = foo(m); + void test(int maximum, int count) { + if (maximum < 0) { + throw new IllegalArgumentException("Negative 'maximum' argument."); } - - @LessThan("#1") @NonNegative int foo(int in) { - throw new RuntimeException(); + if (count > maximum) { + int deleteIndex = count - maximum - 1; + // TODO: shouldn't error + // :: error: (argument.type.incompatible) + isLessThanOrEqual(0, deleteIndex); } + } - void test(int maximum, int count) { - if (maximum < 0) { - throw new IllegalArgumentException("Negative 'maximum' argument."); - } - if (count > maximum) { - int deleteIndex = count - maximum - 1; - // TODO: shouldn't error - // :: error: (argument.type.incompatible) - isLessThanOrEqual(0, deleteIndex); - } - } + void count(int count) { + if (count > 0) { + if (count % 2 == 1) { - void count(int count) { - if (count > 0) { - if (count % 2 == 1) { - - } else { - // TODO: improve value checker - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int countDivMinus = count / 2 - 1; - // Reasign to update the value in the store. - countDivMinus = countDivMinus; - // :: error: (argument.type.incompatible) - isLessThan(0, countDivMinus); - isLessThanOrEqual(0, countDivMinus); - } - } + } else { + // TODO: improve value checker + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int countDivMinus = count / 2 - 1; + // Reasign to update the value in the store. + countDivMinus = countDivMinus; + // :: error: (argument.type.incompatible) + isLessThan(0, countDivMinus); + isLessThanOrEqual(0, countDivMinus); + } } + } - static @NonNegative @LessThan("#2 + 1") int expandedCapacity( - @NonNegative int oldCapacity, @NonNegative int minCapacity) { - if (minCapacity < 0) { - throw new AssertionError("cannot store more than MAX_VALUE elements"); - } - // careful of overflow! - int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; // expand by %50 - if (newCapacity < minCapacity) { - newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; - } - if (newCapacity < 0) { - newCapacity = Integer.MAX_VALUE; - // guaranteed to be >= newCapacity - } - // :: error: (return.type.incompatible) - return newCapacity; + static @NonNegative @LessThan("#2 + 1") int expandedCapacity( + @NonNegative int oldCapacity, @NonNegative int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; // expand by %50 + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; + // guaranteed to be >= newCapacity } + // :: error: (return.type.incompatible) + return newCapacity; + } } diff --git a/checker/tests/index/LessThanZeroArrayLength.java b/checker/tests/index/LessThanZeroArrayLength.java index ea0ccd2e11b..ba58d4c2ca8 100644 --- a/checker/tests/index/LessThanZeroArrayLength.java +++ b/checker/tests/index/LessThanZeroArrayLength.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.LessThan; public class LessThanZeroArrayLength { - void test(int[] a) { - foo(0, a.length); - } + void test(int[] a) { + foo(0, a.length); + } - void foo(@LessThan("#2 + 1") int x, int y) {} + void foo(@LessThan("#2 + 1") int x, int y) {} } diff --git a/checker/tests/index/ListAdd.java b/checker/tests/index/ListAdd.java index 51a75b6330d..2fd3250db15 100644 --- a/checker/tests/index/ListAdd.java +++ b/checker/tests/index/ListAdd.java @@ -7,70 +7,69 @@ public class ListAdd { - List listField; + List listField; - void ListAdd( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.add(index, 4); + void ListAdd(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.add(index, 4); - // :: error: (list.access.unsafe.high) - list.add(notIndex + 1, 4); - } + // :: error: (list.access.unsafe.high) + list.add(notIndex + 1, 4); + } - int[] arr = {0}; + int[] arr = {0}; - void ListAddWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.add(index, 4); - } + void ListAddWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.add(index, 4); + } - void ListAddField() { - listField.add(listField.size() - 1, 4); - listField.add(this.listField.size() - 1, 4); - this.listField.add(listField.size() - 1, 4); - this.listField.add(this.listField.size() - 1, 4); + void ListAddField() { + listField.add(listField.size() - 1, 4); + listField.add(this.listField.size() - 1, 4); + this.listField.add(listField.size() - 1, 4); + this.listField.add(this.listField.size() - 1, 4); - // :: error: (list.access.unsafe.high) - listField.add(listField.size(), 4); - // :: error: (list.access.unsafe.high) - listField.add(this.listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.add(listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.add(this.listField.size(), 4); - } + // :: error: (list.access.unsafe.high) + listField.add(listField.size(), 4); + // :: error: (list.access.unsafe.high) + listField.add(this.listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.add(listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.add(this.listField.size(), 4); + } - void ListAddFieldUserAnnotation(@IndexFor("listField") int i) { - listField.add(i, 4); - this.listField.add(i, 4); + void ListAddFieldUserAnnotation(@IndexFor("listField") int i) { + listField.add(i, 4); + this.listField.add(i, 4); - // :: error: (list.access.unsafe.high) - listField.add(i + 4, 4); - // :: error: (list.access.unsafe.high) - this.listField.add(i + 4, 4); - } + // :: error: (list.access.unsafe.high) + listField.add(i + 4, 4); + // :: error: (list.access.unsafe.high) + this.listField.add(i + 4, 4); + } - void ListAddUserAnnotation(@IndexFor("#2") int i, List list) { - list.add(i, 4); + void ListAddUserAnnotation(@IndexFor("#2") int i, List list) { + list.add(i, 4); - // :: error: (list.access.unsafe.high) - list.add(i + 4, 4); - } + // :: error: (list.access.unsafe.high) + list.add(i + 4, 4); + } - void ListAddUpdateValue(List list) { - @LTEqLengthOf("list") int i = list.size(); - @LTLengthOf("list") int r = list.size() - 1; - list.add(0); - @LTLengthOf("list") int k = i; - @LTOMLengthOf("list") int p = r; - } + void ListAddUpdateValue(List list) { + @LTEqLengthOf("list") int i = list.size(); + @LTLengthOf("list") int r = list.size() - 1; + list.add(0); + @LTLengthOf("list") int k = i; + @LTOMLengthOf("list") int p = r; + } - void ListAddTwo(@LTEqLengthOf({"#2", "#3"}) int i, List list, List list2) { - @LTEqLengthOf({"list", "list2"}) int j = i; - list.add(0); - // :: error: (list.access.unsafe.high) - list.get(i); - // :: error: (list.access.unsafe.high) - list2.get(i); - } + void ListAddTwo(@LTEqLengthOf({"#2", "#3"}) int i, List list, List list2) { + @LTEqLengthOf({"list", "list2"}) int j = i; + list.add(0); + // :: error: (list.access.unsafe.high) + list.get(i); + // :: error: (list.access.unsafe.high) + list2.get(i); + } } diff --git a/checker/tests/index/ListAddAll.java b/checker/tests/index/ListAddAll.java index 8fed1ddf4d9..b858491f62e 100644 --- a/checker/tests/index/ListAddAll.java +++ b/checker/tests/index/ListAddAll.java @@ -6,54 +6,54 @@ public class ListAddAll { - List listField; - List coll; - - void ListAddAll( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.addAll(index, coll); - - // :: error: (list.access.unsafe.high) - list.addAll(notIndex, coll); - } - - int[] arr = {0}; - - void ListAddAllWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.addAll(index, coll); - } - - void ListAddAllField() { - listField.addAll(listField.size() - 1, coll); - listField.addAll(this.listField.size() - 1, coll); - this.listField.addAll(listField.size() - 1, coll); - this.listField.addAll(this.listField.size() - 1, coll); - - // :: error: (list.access.unsafe.high) - listField.addAll(listField.size(), coll); - // :: error: (list.access.unsafe.high) - listField.addAll(this.listField.size(), coll); - // :: error: (list.access.unsafe.high) - this.listField.addAll(listField.size(), coll); - // :: error: (list.access.unsafe.high) - this.listField.addAll(this.listField.size(), coll); - } - - void ListAddAllFieldUserAnnotation(@IndexFor("listField") int i) { - listField.addAll(i, coll); - this.listField.addAll(i, coll); - - // :: error: (list.access.unsafe.high) - listField.addAll(i + 1, coll); - // :: error: (list.access.unsafe.high) - this.listField.addAll(i + 1, coll); - } - - void ListAddAllUserAnnotation(@IndexFor("#2") int i, List list) { - list.addAll(i, coll); - - // :: error: (list.access.unsafe.high) - list.addAll(i + 1, coll); - } + List listField; + List coll; + + void ListAddAll( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.addAll(index, coll); + + // :: error: (list.access.unsafe.high) + list.addAll(notIndex, coll); + } + + int[] arr = {0}; + + void ListAddAllWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.addAll(index, coll); + } + + void ListAddAllField() { + listField.addAll(listField.size() - 1, coll); + listField.addAll(this.listField.size() - 1, coll); + this.listField.addAll(listField.size() - 1, coll); + this.listField.addAll(this.listField.size() - 1, coll); + + // :: error: (list.access.unsafe.high) + listField.addAll(listField.size(), coll); + // :: error: (list.access.unsafe.high) + listField.addAll(this.listField.size(), coll); + // :: error: (list.access.unsafe.high) + this.listField.addAll(listField.size(), coll); + // :: error: (list.access.unsafe.high) + this.listField.addAll(this.listField.size(), coll); + } + + void ListAddAllFieldUserAnnotation(@IndexFor("listField") int i) { + listField.addAll(i, coll); + this.listField.addAll(i, coll); + + // :: error: (list.access.unsafe.high) + listField.addAll(i + 1, coll); + // :: error: (list.access.unsafe.high) + this.listField.addAll(i + 1, coll); + } + + void ListAddAllUserAnnotation(@IndexFor("#2") int i, List list) { + list.addAll(i, coll); + + // :: error: (list.access.unsafe.high) + list.addAll(i + 1, coll); + } } diff --git a/checker/tests/index/ListAddInfiniteLoop.java b/checker/tests/index/ListAddInfiniteLoop.java index 6c9fbd7ceb7..36a36bbd96e 100644 --- a/checker/tests/index/ListAddInfiniteLoop.java +++ b/checker/tests/index/ListAddInfiniteLoop.java @@ -2,9 +2,9 @@ public class ListAddInfiniteLoop { - void ListLoop(List list) { - while (true) { - list.add(4); - } + void ListLoop(List list) { + while (true) { + list.add(4); } + } } diff --git a/checker/tests/index/ListGet.java b/checker/tests/index/ListGet.java index e60cea1aef2..7487ab7fa81 100644 --- a/checker/tests/index/ListGet.java +++ b/checker/tests/index/ListGet.java @@ -6,52 +6,51 @@ public class ListGet { - List listField; - int[] arr = {0}; - - void ListGet( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.get(index); - - // :: error: (list.access.unsafe.high) - list.get(notIndex); - } - - void ListGetWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.get(index); - } - - void ListGetField() { - listField.get(listField.size() - 1); - listField.get(this.listField.size() - 1); - this.listField.get(listField.size() - 1); - this.listField.get(this.listField.size() - 1); - - // :: error: (list.access.unsafe.high) - listField.get(listField.size()); - // :: error: (list.access.unsafe.high) - listField.get(this.listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.get(listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.get(this.listField.size()); - } - - void ListGetFieldUserAnnotation(@IndexFor("listField") int i) { - listField.get(i); - this.listField.get(i); - - // :: error: (list.access.unsafe.high) - listField.get(i + 1); - // :: error: (list.access.unsafe.high) - this.listField.get(i + 1); - } - - void ListGetUserAnnotation(@IndexFor("#2") int i, List list) { - list.get(i); - - // :: error: (list.access.unsafe.high) - list.get(i + 1); - } + List listField; + int[] arr = {0}; + + void ListGet(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.get(index); + + // :: error: (list.access.unsafe.high) + list.get(notIndex); + } + + void ListGetWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.get(index); + } + + void ListGetField() { + listField.get(listField.size() - 1); + listField.get(this.listField.size() - 1); + this.listField.get(listField.size() - 1); + this.listField.get(this.listField.size() - 1); + + // :: error: (list.access.unsafe.high) + listField.get(listField.size()); + // :: error: (list.access.unsafe.high) + listField.get(this.listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.get(listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.get(this.listField.size()); + } + + void ListGetFieldUserAnnotation(@IndexFor("listField") int i) { + listField.get(i); + this.listField.get(i); + + // :: error: (list.access.unsafe.high) + listField.get(i + 1); + // :: error: (list.access.unsafe.high) + this.listField.get(i + 1); + } + + void ListGetUserAnnotation(@IndexFor("#2") int i, List list) { + list.get(i); + + // :: error: (list.access.unsafe.high) + list.get(i + 1); + } } diff --git a/checker/tests/index/ListIterator.java b/checker/tests/index/ListIterator.java index e2d85d69413..8e29d028c68 100644 --- a/checker/tests/index/ListIterator.java +++ b/checker/tests/index/ListIterator.java @@ -6,53 +6,53 @@ public class ListIterator { - List listField; - - void ListIterator( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.listIterator(index); - - // :: error: (list.access.unsafe.high) - list.listIterator(notIndex); - } - - int[] arr = {0}; - - void ListIteratorWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.listIterator(index); - } - - void ListIteratorField() { - listField.listIterator(listField.size() - 1); - listField.listIterator(this.listField.size() - 1); - this.listField.listIterator(listField.size() - 1); - this.listField.listIterator(this.listField.size() - 1); - - // :: error: (list.access.unsafe.high) - listField.listIterator(listField.size()); - // :: error: (list.access.unsafe.high) - listField.listIterator(this.listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.listIterator(listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.listIterator(this.listField.size()); - } - - void ListIteratorFieldUserAnnotation(@IndexFor("listField") int i) { - listField.listIterator(i); - this.listField.listIterator(i); - - // :: error: (list.access.unsafe.high) - listField.listIterator(i + 1); - // :: error: (list.access.unsafe.high) - this.listField.listIterator(i + 1); - } - - void ListIteratorUserAnnotation(@IndexFor("#2") int i, List list) { - list.listIterator(i); - - // :: error: (list.access.unsafe.high) - list.listIterator(i + 1); - } + List listField; + + void ListIterator( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.listIterator(index); + + // :: error: (list.access.unsafe.high) + list.listIterator(notIndex); + } + + int[] arr = {0}; + + void ListIteratorWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.listIterator(index); + } + + void ListIteratorField() { + listField.listIterator(listField.size() - 1); + listField.listIterator(this.listField.size() - 1); + this.listField.listIterator(listField.size() - 1); + this.listField.listIterator(this.listField.size() - 1); + + // :: error: (list.access.unsafe.high) + listField.listIterator(listField.size()); + // :: error: (list.access.unsafe.high) + listField.listIterator(this.listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.listIterator(listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.listIterator(this.listField.size()); + } + + void ListIteratorFieldUserAnnotation(@IndexFor("listField") int i) { + listField.listIterator(i); + this.listField.listIterator(i); + + // :: error: (list.access.unsafe.high) + listField.listIterator(i + 1); + // :: error: (list.access.unsafe.high) + this.listField.listIterator(i + 1); + } + + void ListIteratorUserAnnotation(@IndexFor("#2") int i, List list) { + list.listIterator(i); + + // :: error: (list.access.unsafe.high) + list.listIterator(i + 1); + } } diff --git a/checker/tests/index/ListLowerBound.java b/checker/tests/index/ListLowerBound.java index fef7cfeacfc..c8a520db72a 100644 --- a/checker/tests/index/ListLowerBound.java +++ b/checker/tests/index/ListLowerBound.java @@ -1,19 +1,18 @@ // @skip-test until we bring list support back -import org.checkerframework.checker.index.qual.GTENegativeOne; -import org.checkerframework.checker.index.qual.NonNegative; - import java.util.List; import java.util.ListIterator; +import org.checkerframework.checker.index.qual.GTENegativeOne; +import org.checkerframework.checker.index.qual.NonNegative; public class ListLowerBound { - private void m(List l) { - // :: error: (argument.type.incompatible) - l.get(-1); - // :: error: (argument.type.incompatible) - ListIterator li = l.listIterator(-1); + private void m(List l) { + // :: error: (argument.type.incompatible) + l.get(-1); + // :: error: (argument.type.incompatible) + ListIterator li = l.listIterator(-1); - @NonNegative int ni = li.nextIndex(); - @GTENegativeOne int pi = li.previousIndex(); - } + @NonNegative int ni = li.nextIndex(); + @GTENegativeOne int pi = li.previousIndex(); + } } diff --git a/checker/tests/index/ListRemove.java b/checker/tests/index/ListRemove.java index 32146eb10bf..8561a3316e0 100644 --- a/checker/tests/index/ListRemove.java +++ b/checker/tests/index/ListRemove.java @@ -8,72 +8,72 @@ public class ListRemove { - List listField; + List listField; - void ListRemove( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.remove(index); + void ListRemove( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.remove(index); - // :: error: (list.access.unsafe.high) - list.remove(notIndex); - } - - void ListRemoveWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.remove(index); - } + // :: error: (list.access.unsafe.high) + list.remove(notIndex); + } - void ListRemoveField() { - listField.remove(listField.size() - 1); - listField.remove(this.listField.size() - 1); - this.listField.remove(listField.size() - 1); - this.listField.remove(this.listField.size() - 1); + void ListRemoveWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.remove(index); + } - // :: error: (list.access.unsafe.high) - listField.remove(listField.size()); - // :: error: (list.access.unsafe.high) - listField.remove(this.listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.remove(listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.remove(this.listField.size()); - } + void ListRemoveField() { + listField.remove(listField.size() - 1); + listField.remove(this.listField.size() - 1); + this.listField.remove(listField.size() - 1); + this.listField.remove(this.listField.size() - 1); - void ListRemoveFieldUserAnnotation(@IndexFor("listField") int i) { - listField.remove(i); - this.listField.remove(i); + // :: error: (list.access.unsafe.high) + listField.remove(listField.size()); + // :: error: (list.access.unsafe.high) + listField.remove(this.listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.remove(listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.remove(this.listField.size()); + } - // :: error: (list.access.unsafe.high) - listField.remove(i + 1); - // :: error: (list.access.unsafe.high) - this.listField.remove(i + 1); - } + void ListRemoveFieldUserAnnotation(@IndexFor("listField") int i) { + listField.remove(i); + this.listField.remove(i); - void ListRemoveUserAnnotation(@IndexFor("list") int i, List list) { - list.remove(i); + // :: error: (list.access.unsafe.high) + listField.remove(i + 1); + // :: error: (list.access.unsafe.high) + this.listField.remove(i + 1); + } - // :: error: (list.access.unsafe.high) - list.remove(i + 1); - // :: error: (list.access.unsafe.high) - list.remove(i); - } + void ListRemoveUserAnnotation(@IndexFor("list") int i, List list) { + list.remove(i); - void FailRemove(List list) { - @LTLengthOf("list") int i = list.size() - 1; - try { - list.remove(1); - } catch (Exception e) { - } + // :: error: (list.access.unsafe.high) + list.remove(i + 1); + // :: error: (list.access.unsafe.high) + list.remove(i); + } - @LTLengthOf("list") int m = i; + void FailRemove(List list) { + @LTLengthOf("list") int i = list.size() - 1; + try { + list.remove(1); + } catch (Exception e) { } - void RemoveUpdate(List list) { - @LTLength("list") - int m = list.size() - 1; - list.get(m); - list.remove(m); - // :: error: (list.access.unsafe.high) - list.get(m); - } + @LTLengthOf("list") int m = i; + } + + void RemoveUpdate(List list) { + @LTLength("list") + int m = list.size() - 1; + list.get(m); + list.remove(m); + // :: error: (list.access.unsafe.high) + list.get(m); + } } diff --git a/checker/tests/index/ListSet.java b/checker/tests/index/ListSet.java index 943caac69e9..9b4a70c5f1f 100644 --- a/checker/tests/index/ListSet.java +++ b/checker/tests/index/ListSet.java @@ -6,53 +6,52 @@ public class ListSet { - List listField; - - void ListSet( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.set(index, 4); - - // :: error: (list.access.unsafe.high) - list.set(notIndex, 4); - } - - int[] arr = {0}; - - void ListSetWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.set(index, 4); - } - - void ListSetField() { - listField.set(listField.size() - 1, 4); - listField.set(this.listField.size() - 1, 4); - this.listField.set(listField.size() - 1, 4); - this.listField.set(this.listField.size() - 1, 4); - - // :: error: (list.access.unsafe.high) - listField.set(listField.size(), 4); - // :: error: (list.access.unsafe.high) - listField.set(this.listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.set(listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.set(this.listField.size(), 4); - } - - void ListSetFieldUserAnnotation(@IndexFor("listField") int i) { - listField.set(i, 4); - this.listField.set(i, 4); - - // :: error: (list.access.unsafe.high) - listField.set(i + 1, 4); - // :: error: (list.access.unsafe.high) - this.listField.set(i + 1, 4); - } - - void ListSetUserAnnotation(@IndexFor("#2") int i, List list) { - list.set(i, 4); - - // :: error: (list.access.unsafe.high) - list.set(i + 1, 4); - } + List listField; + + void ListSet(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.set(index, 4); + + // :: error: (list.access.unsafe.high) + list.set(notIndex, 4); + } + + int[] arr = {0}; + + void ListSetWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.set(index, 4); + } + + void ListSetField() { + listField.set(listField.size() - 1, 4); + listField.set(this.listField.size() - 1, 4); + this.listField.set(listField.size() - 1, 4); + this.listField.set(this.listField.size() - 1, 4); + + // :: error: (list.access.unsafe.high) + listField.set(listField.size(), 4); + // :: error: (list.access.unsafe.high) + listField.set(this.listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.set(listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.set(this.listField.size(), 4); + } + + void ListSetFieldUserAnnotation(@IndexFor("listField") int i) { + listField.set(i, 4); + this.listField.set(i, 4); + + // :: error: (list.access.unsafe.high) + listField.set(i + 1, 4); + // :: error: (list.access.unsafe.high) + this.listField.set(i + 1, 4); + } + + void ListSetUserAnnotation(@IndexFor("#2") int i, List list) { + list.set(i, 4); + + // :: error: (list.access.unsafe.high) + list.set(i + 1, 4); + } } diff --git a/checker/tests/index/ListSupport.java b/checker/tests/index/ListSupport.java index de94282ddf2..2f30cc3aa5b 100644 --- a/checker/tests/index/ListSupport.java +++ b/checker/tests/index/ListSupport.java @@ -6,62 +6,61 @@ public class ListSupport { - void indexOf(List list) { - int index = list.indexOf(0); + void indexOf(List list) { + int index = list.indexOf(0); - @LTLengthOf("list") int i = index; + @LTLengthOf("list") int i = index; - // :: error: (assignment.type.incompatible) - @UpperBoundBottom int i2 = index; - } + // :: error: (assignment.type.incompatible) + @UpperBoundBottom int i2 = index; + } - void lastIndexOf(List list) { - int index = list.lastIndexOf(0); + void lastIndexOf(List list) { + int index = list.lastIndexOf(0); - @LTLengthOf("list") int i = index; + @LTLengthOf("list") int i = index; - // :: error: (assignment.type.incompatible) - @UpperBoundBottom int i2 = index; - } + // :: error: (assignment.type.incompatible) + @UpperBoundBottom int i2 = index; + } - void subList( - List list, @LTLengthOf("#1") int index, @LTEqLengthOf("#1") int endIndex) { - List list2 = list.subList(index, endIndex); + void subList(List list, @LTLengthOf("#1") int index, @LTEqLengthOf("#1") int endIndex) { + List list2 = list.subList(index, endIndex); - // start index must be strictly lessthanlength - // :: error: (argument.type.incompatible) - list2 = list.subList(endIndex, endIndex); + // start index must be strictly lessthanlength + // :: error: (argument.type.incompatible) + list2 = list.subList(endIndex, endIndex); - // edindex must be less than or equal to Length - // :: error: (argument.type.incompatible) - list2 = list.subList(index, 28); - } + // edindex must be less than or equal to Length + // :: error: (argument.type.incompatible) + list2 = list.subList(index, 28); + } - void size(List list) { - int i = list.size(); - @LTEqLengthOf("list") int k = i; + void size(List list) { + int i = list.size(); + @LTEqLengthOf("list") int k = i; - // :: error: (assignment.type.incompatible) - @LTLengthOf("list") int m = i; - } + // :: error: (assignment.type.incompatible) + @LTLengthOf("list") int m = i; + } - void clear(List list) { - int lessThanLength = list.size() - 1; - int lessThanOrEq = list.size(); - list.get(lessThanLength); + void clear(List list) { + int lessThanLength = list.size() - 1; + int lessThanOrEq = list.size(); + list.get(lessThanLength); - list.clear(); + list.clear(); - // :: error: (list.access.unsafe.high) - list.get(lessThanLength); + // :: error: (list.access.unsafe.high) + list.get(lessThanLength); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("list") int m = lessThanLength; + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("list") int m = lessThanLength; - // :: error: (assignment.type.incompatible) - m = lessThanOrEq; + // :: error: (assignment.type.incompatible) + m = lessThanOrEq; - // :: error: (assignment.type.incompatible) - @LTLengthOf("list") int i = lessThanLength; - } + // :: error: (assignment.type.incompatible) + @LTLengthOf("list") int i = lessThanLength; + } } diff --git a/checker/tests/index/ListSupportLBC.java b/checker/tests/index/ListSupportLBC.java index 4b570c514f7..bb63870a8c0 100644 --- a/checker/tests/index/ListSupportLBC.java +++ b/checker/tests/index/ListSupportLBC.java @@ -1,90 +1,89 @@ +import java.util.ArrayList; import org.checkerframework.checker.index.qual.GTENegativeOne; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.index.qual.Positive; -import java.util.ArrayList; - // @skip-test until we bring list support back public class ListSupportLBC { - void testGet() { + void testGet() { - List list = new ArrayList<>(); - int i = -1; - int j = 0; + List list = new ArrayList<>(); + int i = -1; + int j = 0; - // try and use a negative to get, should fail - // :: error: (argument.type.incompatible) - Integer m = list.get(i); + // try and use a negative to get, should fail + // :: error: (argument.type.incompatible) + Integer m = list.get(i); - // try and use a nonnegative, should work - Integer l = list.get(j); - } + // try and use a nonnegative, should work + Integer l = list.get(j); + } - void testArrayListGet() { + void testArrayListGet() { - ArrayList list = new ArrayList<>(); - int i = -1; - int j = 0; + ArrayList list = new ArrayList<>(); + int i = -1; + int j = 0; - // try and use a negative to get, should fail - // :: error: (argument.type.incompatible) - Integer m = list.get(i); + // try and use a negative to get, should fail + // :: error: (argument.type.incompatible) + Integer m = list.get(i); - // try and use a nonnegative, should work - Integer l = list.get(j); - } + // try and use a nonnegative, should work + Integer l = list.get(j); + } - void testSet() { - List list = new ArrayList<>(); - int i = -1; - int j = 0; + void testSet() { + List list = new ArrayList<>(); + int i = -1; + int j = 0; - // try and use a negative to get, should fail - // :: error: (argument.type.incompatible) - Integer m = list.set(i, 34); + // try and use a negative to get, should fail + // :: error: (argument.type.incompatible) + Integer m = list.set(i, 34); - // try and use a nonnegative, should work - Integer l = list.set(j, 34); - } + // try and use a nonnegative, should work + Integer l = list.set(j, 34); + } - void testIndexOf() { - List list = new ArrayList<>(); - @GTENegativeOne int a = list.indexOf(1); + void testIndexOf() { + List list = new ArrayList<>(); + @GTENegativeOne int a = list.indexOf(1); - // :: error: (assignment.type.incompatible) - @NonNegative int n = a; + // :: error: (assignment.type.incompatible) + @NonNegative int n = a; - @GTENegativeOne int b = list.lastIndexOf(1); + @GTENegativeOne int b = list.lastIndexOf(1); - // :: error: (assignment.type.incompatible) - @NonNegative int m = b; - } + // :: error: (assignment.type.incompatible) + @NonNegative int m = b; + } - void testSize() { - List list = new ArrayList<>(); - @NonNegative int s = list.size(); + void testSize() { + List list = new ArrayList<>(); + @NonNegative int s = list.size(); - // :: error: (assignment.type.incompatible) - @Positive int r = s; - } + // :: error: (assignment.type.incompatible) + @Positive int r = s; + } - void testSublist() { - List list = new ArrayList<>(); - int i = -1; - int j = 0; + void testSublist() { + List list = new ArrayList<>(); + int i = -1; + int j = 0; - // :: error: (argument.type.incompatible) - List k = list.subList(i, i); + // :: error: (argument.type.incompatible) + List k = list.subList(i, i); - // :: error: (argument.type.incompatible) - List a = list.subList(i, j); + // :: error: (argument.type.incompatible) + List a = list.subList(i, j); - // :: error: (argument.type.incompatible) - List b = list.subList(j, i); + // :: error: (argument.type.incompatible) + List b = list.subList(j, i); - // should work since both are nonnegative - List c = list.subList(j, j); - } + // should work since both are nonnegative + List c = list.subList(j, j); + } } diff --git a/checker/tests/index/ListSupportML.java b/checker/tests/index/ListSupportML.java index bbe2e99e3d1..26c0762f0a7 100644 --- a/checker/tests/index/ListSupportML.java +++ b/checker/tests/index/ListSupportML.java @@ -1,71 +1,70 @@ -import org.checkerframework.common.value.qual.MinLen; - import java.util.ArrayList; +import org.checkerframework.common.value.qual.MinLen; // @skip-test until we bring list support back public class ListSupportML { - void newListMinLen() { - List list = new ArrayList<>(); + void newListMinLen() { + List list = new ArrayList<>(); - // :: error: (assignment.type.incompatible) - @MinLen(1) List list2 = list; + // :: error: (assignment.type.incompatible) + @MinLen(1) List list2 = list; - @MinLen(0) List list3 = list; - } + @MinLen(0) List list3 = list; + } - void listRemove(@MinLen(10) List lst) { - List list = lst; - list.remove(0); + void listRemove(@MinLen(10) List lst) { + List list = lst; + list.remove(0); - // :: error: (assignment.type.incompatible) - @MinLen(10) List list2 = list; + // :: error: (assignment.type.incompatible) + @MinLen(10) List list2 = list; - @MinLen(9) List list3 = list; - } + @MinLen(9) List list3 = list; + } - void listRemoveAliasing(@MinLen(10) List lst) { - List list = lst; - @MinLen(10) List list2 = list; + void listRemoveAliasing(@MinLen(10) List lst) { + List list = lst; + @MinLen(10) List list2 = list; - list2.remove(0); + list2.remove(0); - // :: error: (assignment.type.incompatible) - @MinLen(10) List list3 = list; + // :: error: (assignment.type.incompatible) + @MinLen(10) List list3 = list; - @MinLen(9) List list4 = list; - } + @MinLen(9) List list4 = list; + } - void listAdd(@MinLen(10) List lst) { - List list = lst; - list.add(0); + void listAdd(@MinLen(10) List lst) { + List list = lst; + list.add(0); - @MinLen(11) List list2 = list; - } + @MinLen(11) List list2 = list; + } - void listClear(@MinLen(10) List lst) { - List list = lst; - list.clear(); + void listClear(@MinLen(10) List lst) { + List list = lst; + list.clear(); - // :: error: (assignment.type.incompatible) - @MinLen(1) List list2 = list; + // :: error: (assignment.type.incompatible) + @MinLen(1) List list2 = list; - @MinLen(0) List list3 = list; - } + @MinLen(0) List list3 = list; + } - void listRemoveArrayAlter(@MinLen(10) List lst) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - int @MinLen(10) [] arr1 = arr; - List list = lst; - @MinLen(10) List list2 = list; + void listRemoveArrayAlter(@MinLen(10) List lst) { + int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int @MinLen(10) [] arr1 = arr; + List list = lst; + @MinLen(10) List list2 = list; - list2.remove(0); + list2.remove(0); - // :: error: (assignment.type.incompatible) - @MinLen(10) List list3 = list; + // :: error: (assignment.type.incompatible) + @MinLen(10) List list3 = list; - int @MinLen(10) [] arr2 = arr; - @MinLen(9) List list4 = list; - } + int @MinLen(10) [] arr2 = arr; + @MinLen(9) List list4 = list; + } } diff --git a/checker/tests/index/LiteralArray.java b/checker/tests/index/LiteralArray.java index 264ed32202d..78148df166a 100644 --- a/checker/tests/index/LiteralArray.java +++ b/checker/tests/index/LiteralArray.java @@ -5,15 +5,15 @@ public class LiteralArray { - private static final String[] timeFormat = { - ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), - }; + private static final String[] timeFormat = { + ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), + }; - public String format() { - return format(1); - } + public String format() { + return format(1); + } - public String format(@IndexFor("LiteralArray.timeFormat") int digits) { - return ""; - } + public String format(@IndexFor("LiteralArray.timeFormat") int digits) { + return ""; + } } diff --git a/checker/tests/index/LiteralString.java b/checker/tests/index/LiteralString.java index 3cb4cda097b..3e8866dd205 100644 --- a/checker/tests/index/LiteralString.java +++ b/checker/tests/index/LiteralString.java @@ -6,15 +6,15 @@ public class LiteralString { - private static final String[] finalField = {"This", "is", "an", "array"}; + private static final String[] finalField = {"This", "is", "an", "array"}; - void testLiteralString() { - @MinLen(10) String s = "This string is long enough"; - } + void testLiteralString() { + @MinLen(10) String s = "This string is long enough"; + } - void testLiteralArray() { - String @MinLen(2) [] a = new String[] {"This", "array", "is", "long", "enough"}; - String @MinLen(2) [] b = finalField; - @IndexFor("finalField") int i = 0; - } + void testLiteralArray() { + String @MinLen(2) [] a = new String[] {"This", "array", "is", "long", "enough"}; + String @MinLen(2) [] b = finalField; + @IndexFor("finalField") int i = 0; + } } diff --git a/checker/tests/index/LongAndIntegerBitsMethods.java b/checker/tests/index/LongAndIntegerBitsMethods.java index 864222c242f..1ce36bbf970 100644 --- a/checker/tests/index/LongAndIntegerBitsMethods.java +++ b/checker/tests/index/LongAndIntegerBitsMethods.java @@ -1,14 +1,14 @@ import org.checkerframework.common.value.qual.MinLen; public class LongAndIntegerBitsMethods { - void caseInteger( - int index, int @MinLen(33) [] arr1, int @MinLen(33) [] arr2, int val1, int val2) { - arr1[Integer.numberOfLeadingZeros(index)] = val1; - arr2[Integer.numberOfTrailingZeros(index)] = val2; - } + void caseInteger( + int index, int @MinLen(33) [] arr1, int @MinLen(33) [] arr2, int val1, int val2) { + arr1[Integer.numberOfLeadingZeros(index)] = val1; + arr2[Integer.numberOfTrailingZeros(index)] = val2; + } - void caseLong(int index, int @MinLen(65) [] arr1, int @MinLen(65) [] arr2, int val1, int val2) { - arr1[Long.numberOfLeadingZeros(index)] = val1; - arr2[Long.numberOfTrailingZeros(index)] = val2; - } + void caseLong(int index, int @MinLen(65) [] arr1, int @MinLen(65) [] arr2, int val1, int val2) { + arr1[Long.numberOfLeadingZeros(index)] = val1; + arr2[Long.numberOfTrailingZeros(index)] = val2; + } } diff --git a/checker/tests/index/Loops.java b/checker/tests/index/Loops.java index 977e56ba9fb..607df814d4b 100644 --- a/checker/tests/index/Loops.java +++ b/checker/tests/index/Loops.java @@ -1,83 +1,83 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public final class Loops { - public static boolean flag = false; + public static boolean flag = false; - public void test1a(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (unary.increment.type.incompatible) - offset++; - } + public void test1a(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (unary.increment.type.incompatible) + offset++; } + } - public void test1b(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (compound.assignment.type.incompatible) - offset += 1; - } + public void test1b(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (compound.assignment.type.incompatible) + offset += 1; } + } - public void test1c(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (compound.assignment.type.incompatible) - offset2 += offset; - } + public void test1c(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (compound.assignment.type.incompatible) + offset2 += offset; } + } - public void test2(int[] a, int[] array) { - int offset = array.length - 1; - int offset2 = array.length - 1; + public void test2(int[] a, int[] array) { + int offset = array.length - 1; + int offset2 = array.length - 1; - while (flag) { - offset++; - offset2 += offset; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int x = offset; - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int y = offset2; + while (flag) { + offset++; + offset2 += offset; } + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int x = offset; + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int y = offset2; + } - public void test3(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - offset--; - // :: error: (compound.assignment.type.incompatible) - offset2 -= offset; - } + public void test3(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + offset--; + // :: error: (compound.assignment.type.incompatible) + offset2 -= offset; } + } - public void test4(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (unary.increment.type.incompatible) - offset++; - // :: error: (compound.assignment.type.incompatible) - offset += 1; - // :: error: (compound.assignment.type.incompatible) - offset2 += offset; - } + public void test4(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (unary.increment.type.incompatible) + offset++; + // :: error: (compound.assignment.type.incompatible) + offset += 1; + // :: error: (compound.assignment.type.incompatible) + offset2 += offset; } + } - public void test4(int[] src) { - int patternLength = src.length; - int[] optoSft = new int[patternLength]; - for (int i = patternLength; i > 0; i--) {} - } + public void test4(int[] src) { + int patternLength = src.length; + int[] optoSft = new int[patternLength]; + for (int i = patternLength; i > 0; i--) {} + } - public void test5( - int[] a, - @LTLengthOf(value = "#1", offset = "-1000") int offset, - @LTLengthOf("#1") int offset2) { - int otherOffset = offset; - while (flag) { - otherOffset += 1; - // :: error: (unary.increment.type.incompatible) - offset++; - // :: error: (compound.assignment.type.incompatible) - offset += 1; - // :: error: (compound.assignment.type.incompatible) - offset2 += offset; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "#1", offset = "-1000") int x = otherOffset; + public void test5( + int[] a, + @LTLengthOf(value = "#1", offset = "-1000") int offset, + @LTLengthOf("#1") int offset2) { + int otherOffset = offset; + while (flag) { + otherOffset += 1; + // :: error: (unary.increment.type.incompatible) + offset++; + // :: error: (compound.assignment.type.incompatible) + offset += 1; + // :: error: (compound.assignment.type.incompatible) + offset2 += offset; } + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "#1", offset = "-1000") int x = otherOffset; + } } diff --git a/checker/tests/index/LubIndex.java b/checker/tests/index/LubIndex.java index 202fa55e0dd..81e1918af48 100644 --- a/checker/tests/index/LubIndex.java +++ b/checker/tests/index/LubIndex.java @@ -3,42 +3,42 @@ public class LubIndex { - public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; - } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; + public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; + } - public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; - } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; + public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; + } - public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; - } - int @MinLen(10) [] res = arr; - int @BottomVal [] res2 = arr; + public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; } + int @MinLen(10) [] res = arr; + int @BottomVal [] res2 = arr; + } } diff --git a/checker/tests/index/MLEqualTo.java b/checker/tests/index/MLEqualTo.java index 642ebbe8309..1f96bcebbbf 100644 --- a/checker/tests/index/MLEqualTo.java +++ b/checker/tests/index/MLEqualTo.java @@ -2,9 +2,9 @@ public class MLEqualTo { - public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { - if (r == m) { - int @MinLen(2) [] j = r; - } + public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { + if (r == m) { + int @MinLen(2) [] j = r; } + } } diff --git a/checker/tests/index/MathPlumeClasscastCrash.java b/checker/tests/index/MathPlumeClasscastCrash.java index c490963f5ab..8a68c914eab 100644 --- a/checker/tests/index/MathPlumeClasscastCrash.java +++ b/checker/tests/index/MathPlumeClasscastCrash.java @@ -8,9 +8,9 @@ public final class MathPlumeClasscastCrash { - @SuppressWarnings("index:return") - public static @NonNegative @LessThan("#2") @PolyUpperBound long modPositive( - long x, @PolyUpperBound long y) { - return 0; - } + @SuppressWarnings("index:return") + public static @NonNegative @LessThan("#2") @PolyUpperBound long modPositive( + long x, @PolyUpperBound long y) { + return 0; + } } diff --git a/checker/tests/index/MethodOverrides.java b/checker/tests/index/MethodOverrides.java index 9dd4f7bff83..64b946ef019 100644 --- a/checker/tests/index/MethodOverrides.java +++ b/checker/tests/index/MethodOverrides.java @@ -7,14 +7,14 @@ import org.checkerframework.checker.index.qual.GTENegativeOne; public class MethodOverrides { - @GTENegativeOne int read() { - return -1; - } + @GTENegativeOne int read() { + return -1; + } } class MethodOverrides2 extends MethodOverrides { - // :: error: (override.return.invalid) - int read() { - return -1; - } + // :: error: (override.return.invalid) + int read() { + return -1; + } } diff --git a/checker/tests/index/MinLenFieldInvar.java b/checker/tests/index/MinLenFieldInvar.java index 376a2e4648d..1794052b2fb 100644 --- a/checker/tests/index/MinLenFieldInvar.java +++ b/checker/tests/index/MinLenFieldInvar.java @@ -6,58 +6,58 @@ import org.checkerframework.framework.qual.FieldInvariant; public class MinLenFieldInvar { - class Super { - public final int @MinLen(2) [] minlen2; + class Super { + public final int @MinLen(2) [] minlen2; - public Super(int @MinLen(2) [] minlen2) { - this.minlen2 = minlen2; - } + public Super(int @MinLen(2) [] minlen2) { + this.minlen2 = minlen2; } + } - // :: error: (field.invariant.not.subtype) - @MinLenFieldInvariant(field = "minlen2", minLen = 1) - class InvalidSub extends Super { - public InvalidSub() { - super(new int[] {1, 2}); - } + // :: error: (field.invariant.not.subtype) + @MinLenFieldInvariant(field = "minlen2", minLen = 1) + class InvalidSub extends Super { + public InvalidSub() { + super(new int[] {1, 2}); } + } - @MinLenFieldInvariant(field = "minlen2", minLen = 4) - class ValidSub extends Super { - public final int[] validSubField; + @MinLenFieldInvariant(field = "minlen2", minLen = 4) + class ValidSub extends Super { + public final int[] validSubField; - public ValidSub(int[] validSubField) { - super(new int[] {1, 2, 3, 4}); - this.validSubField = validSubField; - } + public ValidSub(int[] validSubField) { + super(new int[] {1, 2, 3, 4}); + this.validSubField = validSubField; } + } - // :: error: (field.invariant.not.found.superclass) - @MinLenFieldInvariant(field = "validSubField", minLen = 3) - class InvalidSubSub1 extends ValidSub { - public InvalidSubSub1() { - super(new int[] {1, 2}); - } + // :: error: (field.invariant.not.found.superclass) + @MinLenFieldInvariant(field = "validSubField", minLen = 3) + class InvalidSubSub1 extends ValidSub { + public InvalidSubSub1() { + super(new int[] {1, 2}); } + } - // :: error: (field.invariant.not.subtype.superclass) - @MinLenFieldInvariant(field = "minlen2", minLen = 3) - class InvalidSubSub2 extends ValidSub { - public InvalidSubSub2() { - super(new int[] {1, 2}); - } + // :: error: (field.invariant.not.subtype.superclass) + @MinLenFieldInvariant(field = "minlen2", minLen = 3) + class InvalidSubSub2 extends ValidSub { + public InvalidSubSub2() { + super(new int[] {1, 2}); } + } - @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) - @MinLenFieldInvariant(field = "validSubField", minLen = 4) - class ValidSubSub extends ValidSub { - public ValidSubSub() { - super(null); - } - - void test() { - int @BottomVal [] bot = minlen2; - int @MinLen(4) [] four = validSubField; - } + @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) + @MinLenFieldInvariant(field = "validSubField", minLen = 4) + class ValidSubSub extends ValidSub { + public ValidSubSub() { + super(null); } + + void test() { + int @BottomVal [] bot = minlen2; + int @MinLen(4) [] four = validSubField; + } + } } diff --git a/checker/tests/index/MinLenFourShenanigans.java b/checker/tests/index/MinLenFourShenanigans.java index 5e99cc45d12..6dcb8e17228 100644 --- a/checker/tests/index/MinLenFourShenanigans.java +++ b/checker/tests/index/MinLenFourShenanigans.java @@ -1,23 +1,23 @@ package index; public class MinLenFourShenanigans { - public static boolean isInterned(Object value) { - if (value == null) { - // nothing to do - return true; - } else if (value instanceof String) { - // Used to issue the below error. - // MinLenFourShenanigans.java:7: warning: [cast.unsafe] "@MinLen(0) Object" may not be - // casted to the type "@MinLen(4) String" - return (value == ((String) value).intern()); - } - return false; + public static boolean isInterned(Object value) { + if (value == null) { + // nothing to do + return true; + } else if (value instanceof String) { + // Used to issue the below error. + // MinLenFourShenanigans.java:7: warning: [cast.unsafe] "@MinLen(0) Object" may not be + // casted to the type "@MinLen(4) String" + return (value == ((String) value).intern()); } + return false; + } - public static boolean isInterned2(Object value) { - if (value instanceof String) { - return (value == ((String) value).intern()); - } - return false; + public static boolean isInterned2(Object value) { + if (value instanceof String) { + return (value == ((String) value).intern()); } + return false; + } } diff --git a/checker/tests/index/MinLenFromPositive.java b/checker/tests/index/MinLenFromPositive.java index 7f0cb27af3f..f9d39fff264 100644 --- a/checker/tests/index/MinLenFromPositive.java +++ b/checker/tests/index/MinLenFromPositive.java @@ -3,61 +3,61 @@ public class MinLenFromPositive { - void test(@Positive int x) { - int @MinLen(1) [] y = new int[x]; - @IntRange(from = 1) int z = x; - @Positive int q = x; + void test(@Positive int x) { + int @MinLen(1) [] y = new int[x]; + @IntRange(from = 1) int z = x; + @Positive int q = x; + } + + @SuppressWarnings("index") + void foo(int x) { + test(x); + } + + void foo2(int x) { + // :: error: (argument.type.incompatible) + test(x); + } + + void test_lub1(boolean flag, @Positive int x, @IntRange(from = 6, to = 25) int y) { + int z; + if (flag) { + z = x; + } else { + z = y; } - - @SuppressWarnings("index") - void foo(int x) { - test(x); - } - - void foo2(int x) { - // :: error: (argument.type.incompatible) - test(x); - } - - void test_lub1(boolean flag, @Positive int x, @IntRange(from = 6, to = 25) int y) { - int z; - if (flag) { - z = x; - } else { - z = y; - } - @Positive int q = z; - @IntRange(from = 1) int w = z; - } - - void test_lub2(boolean flag, @Positive int x, @IntRange(from = -1, to = 11) int y) { - int z; - if (flag) { - z = x; - } else { - z = y; - } - // :: error: (assignment.type.incompatible) - @Positive int q = z; - @IntRange(from = -1) int w = z; + @Positive int q = z; + @IntRange(from = 1) int w = z; + } + + void test_lub2(boolean flag, @Positive int x, @IntRange(from = -1, to = 11) int y) { + int z; + if (flag) { + z = x; + } else { + z = y; } + // :: error: (assignment.type.incompatible) + @Positive int q = z; + @IntRange(from = -1) int w = z; + } - @Positive int id(@Positive int x) { - return x; - } + @Positive int id(@Positive int x) { + return x; + } - void test_id(int param) { - @Positive int x = id(5); - @IntRange(from = 1) int y = id(5); + void test_id(int param) { + @Positive int x = id(5); + @IntRange(from = 1) int y = id(5); - int @MinLen(1) [] a = new int[id(100)]; - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] c = new int[id(100)]; + int @MinLen(1) [] a = new int[id(100)]; + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] c = new int[id(100)]; - int q = id(10); + int q = id(10); - if (param == q) { - int @MinLen(1) [] d = new int[param]; - } + if (param == q) { + int @MinLen(1) [] d = new int[param]; } + } } diff --git a/checker/tests/index/MinLenIndexFor.java b/checker/tests/index/MinLenIndexFor.java index f3ba544ff78..6b3ba9f4743 100644 --- a/checker/tests/index/MinLenIndexFor.java +++ b/checker/tests/index/MinLenIndexFor.java @@ -3,39 +3,39 @@ import org.checkerframework.common.value.qual.MinLen; public class MinLenIndexFor { - int @MinLen(2) [] arrayLen2 = {0, 1, 2}; + int @MinLen(2) [] arrayLen2 = {0, 1, 2}; - void test(@IndexFor("this.arrayLen2") int i) { - int j = arrayLen2[i]; - int j2 = arrayLen2[1]; - } + void test(@IndexFor("this.arrayLen2") int i) { + int j = arrayLen2[i]; + int j2 = arrayLen2[1]; + } - void callTest(int x) { - test(0); - test(1); - // :: error: (argument.type.incompatible) - test(2); - // :: error: (argument.type.incompatible) - test(3); - test(arrayLen2.length - 1); - } + void callTest(int x) { + test(0); + test(1); + // :: error: (argument.type.incompatible) + test(2); + // :: error: (argument.type.incompatible) + test(3); + test(arrayLen2.length - 1); + } - int @MinLen(4) [] arrayLen4 = {0, 1, 2, 4, 5}; + int @MinLen(4) [] arrayLen4 = {0, 1, 2, 4, 5}; - void test2(@IndexOrHigh("this.arrayLen4") int i) { - if (i > 0) { - int j = arrayLen4[i - 1]; - } - int j2 = arrayLen4[1]; + void test2(@IndexOrHigh("this.arrayLen4") int i) { + if (i > 0) { + int j = arrayLen4[i - 1]; } + int j2 = arrayLen4[1]; + } - void callTest2(int x) { - test2(0); - test2(1); - test2(2); - test2(4); - // :: error: (argument.type.incompatible) - test2(5); - test2(arrayLen4.length); - } + void callTest2(int x) { + test2(0); + test2(1); + test2(2); + test2(4); + // :: error: (argument.type.incompatible) + test2(5); + test2(arrayLen4.length); + } } diff --git a/checker/tests/index/MinLenOneAndLength.java b/checker/tests/index/MinLenOneAndLength.java index be6a1dc78bd..ee50de3714a 100644 --- a/checker/tests/index/MinLenOneAndLength.java +++ b/checker/tests/index/MinLenOneAndLength.java @@ -2,9 +2,9 @@ import org.checkerframework.common.value.qual.MinLen; public class MinLenOneAndLength { - public void m1(int @MinLen(1) [] a, int[] b) { - @IndexFor("a") int i = a.length / 2; - // :: error: (assignment.type.incompatible) - @IndexFor("b") int j = b.length / 2; - } + public void m1(int @MinLen(1) [] a, int[] b) { + @IndexFor("a") int i = a.length / 2; + // :: error: (assignment.type.incompatible) + @IndexFor("b") int j = b.length / 2; + } } diff --git a/checker/tests/index/MinLenSameLenInteraction.java b/checker/tests/index/MinLenSameLenInteraction.java index 3c2b3dfe5cd..51ac8a76da0 100644 --- a/checker/tests/index/MinLenSameLenInteraction.java +++ b/checker/tests/index/MinLenSameLenInteraction.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.*; public class MinLenSameLenInteraction { - void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { - if (a.length == 1) { - int x = b[0]; - } + void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { + if (a.length == 1) { + int x = b[0]; } + } } diff --git a/checker/tests/index/MinMax.java b/checker/tests/index/MinMax.java index 8357a70b748..fe693778176 100644 --- a/checker/tests/index/MinMax.java +++ b/checker/tests/index/MinMax.java @@ -2,12 +2,12 @@ import org.checkerframework.checker.index.qual.Positive; public class MinMax { - // They call me a power gamer. I stole the test cases from issue 26. - void mathmax() { - @Positive int i = Math.max(-15, 2); - } + // They call me a power gamer. I stole the test cases from issue 26. + void mathmax() { + @Positive int i = Math.max(-15, 2); + } - void mathmin() { - @GTENegativeOne int i = Math.min(-1, 2); - } + void mathmin() { + @GTENegativeOne int i = Math.min(-1, 2); + } } diff --git a/checker/tests/index/MinMaxIndex.java b/checker/tests/index/MinMaxIndex.java index 588d19deace..f20a6d1ca76 100644 --- a/checker/tests/index/MinMaxIndex.java +++ b/checker/tests/index/MinMaxIndex.java @@ -6,31 +6,31 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class MinMaxIndex { - // Both min and max preserve IndexFor - void indexFor(char[] array, @IndexFor("#1") int i1, @IndexFor("#1") int i2) { - char c = array[Math.max(i1, i2)]; - char d = array[Math.min(i1, i2)]; - } + // Both min and max preserve IndexFor + void indexFor(char[] array, @IndexFor("#1") int i1, @IndexFor("#1") int i2) { + char c = array[Math.max(i1, i2)]; + char d = array[Math.min(i1, i2)]; + } - // Both min and max preserve IndexOrHigh - void indexOrHigh(String str, @IndexOrHigh("#1") int i1, @IndexOrHigh("#1") int i2) { - str.substring(Math.max(i1, i2)); - str.substring(Math.min(i1, i2)); - } + // Both min and max preserve IndexOrHigh + void indexOrHigh(String str, @IndexOrHigh("#1") int i1, @IndexOrHigh("#1") int i2) { + str.substring(Math.max(i1, i2)); + str.substring(Math.min(i1, i2)); + } - // Combining IndexFor and IndexOrHigh - void indexForOrHigh(String str, @IndexFor("#1") int i1, @IndexOrHigh("#1") int i2) { - str.substring(Math.max(i1, i2)); - str.substring(Math.min(i1, i2)); - // :: error: (argument.type.incompatible) - str.charAt(Math.max(i1, i2)); - str.charAt(Math.min(i1, i2)); - } + // Combining IndexFor and IndexOrHigh + void indexForOrHigh(String str, @IndexFor("#1") int i1, @IndexOrHigh("#1") int i2) { + str.substring(Math.max(i1, i2)); + str.substring(Math.min(i1, i2)); + // :: error: (argument.type.incompatible) + str.charAt(Math.max(i1, i2)); + str.charAt(Math.min(i1, i2)); + } - // max does not work with different sequences, min does - void twoSequences(String str1, String str2, @IndexFor("#1") int i1, @IndexFor("#2") int i2) { - // :: error: (argument.type.incompatible) - str1.charAt(Math.max(i1, i2)); - str1.charAt(Math.min(i1, i2)); - } + // max does not work with different sequences, min does + void twoSequences(String str1, String str2, @IndexFor("#1") int i1, @IndexFor("#2") int i2) { + // :: error: (argument.type.incompatible) + str1.charAt(Math.max(i1, i2)); + str1.charAt(Math.min(i1, i2)); + } } diff --git a/checker/tests/index/Modulo.java b/checker/tests/index/Modulo.java index edac3125886..313200d3973 100644 --- a/checker/tests/index/Modulo.java +++ b/checker/tests/index/Modulo.java @@ -8,19 +8,19 @@ import org.checkerframework.checker.index.qual.Positive; public class Modulo { - void m1(Object[] a, @IndexOrHigh("#1") int i, @NonNegative int j) { - @IndexFor("a") int k = j % i; - } + void m1(Object[] a, @IndexOrHigh("#1") int i, @NonNegative int j) { + @IndexFor("a") int k = j % i; + } - void m1p(Object[] a, @Positive @LTEqLengthOf("#1") int i, @Positive int j) { - @IndexFor("a") int k = j % i; - } + void m1p(Object[] a, @Positive @LTEqLengthOf("#1") int i, @Positive int j) { + @IndexFor("a") int k = j % i; + } - void m2(Object[] a, int i, @IndexFor("#1") int j) { - @IndexFor("a") int k = j % i; - } + void m2(Object[] a, int i, @IndexFor("#1") int j) { + @IndexFor("a") int k = j % i; + } - void m2(Object[] a, Object[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { - @IndexFor({"a", "b"}) int k = j % i; - } + void m2(Object[] a, Object[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { + @IndexFor({"a", "b"}) int k = j % i; + } } diff --git a/checker/tests/index/NegativeArray.java b/checker/tests/index/NegativeArray.java index 5848fea58cf..7335ce6af51 100644 --- a/checker/tests/index/NegativeArray.java +++ b/checker/tests/index/NegativeArray.java @@ -2,8 +2,8 @@ public class NegativeArray { - public static void negativeArray(@GTENegativeOne int len) { - // :: error: (array.length.negative) - int[] arr = new int[len]; - } + public static void negativeArray(@GTENegativeOne int len) { + // :: error: (array.length.negative) + int[] arr = new int[len]; + } } diff --git a/checker/tests/index/NegativeIndex.java b/checker/tests/index/NegativeIndex.java index 93b7372dc33..f21fc10217a 100644 --- a/checker/tests/index/NegativeIndex.java +++ b/checker/tests/index/NegativeIndex.java @@ -4,8 +4,8 @@ import org.checkerframework.common.value.qual.*; public class NegativeIndex { - @SuppressWarnings("lowerbound") - void m(int[] a) { - int x = a[-100]; - } + @SuppressWarnings("lowerbound") + void m(int[] a) { + int x = a[-100]; + } } diff --git a/checker/tests/index/NonNegArrayLength.java b/checker/tests/index/NonNegArrayLength.java index 559284533b8..7c14042cea6 100644 --- a/checker/tests/index/NonNegArrayLength.java +++ b/checker/tests/index/NonNegArrayLength.java @@ -3,7 +3,7 @@ public class NonNegArrayLength { - public static void NonNegArrayLength(int @MinLen(4) [] arr) { - @Positive int i = arr.length - 2; - } + public static void NonNegArrayLength(int @MinLen(4) [] arr) { + @Positive int i = arr.length - 2; + } } diff --git a/checker/tests/index/NonNegativeCharValue.java b/checker/tests/index/NonNegativeCharValue.java index 2c6e65ae056..4dcfa459283 100644 --- a/checker/tests/index/NonNegativeCharValue.java +++ b/checker/tests/index/NonNegativeCharValue.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.index.qual.NonNegative; public class NonNegativeCharValue { - public static String toString(final @NonNegative Character ch) { - return toString(ch.charValue()); - } + public static String toString(final @NonNegative Character ch) { + return toString(ch.charValue()); + } } diff --git a/checker/tests/index/NonnegativeChar.java b/checker/tests/index/NonnegativeChar.java index e3fe527d4aa..9b8faca95b1 100644 --- a/checker/tests/index/NonnegativeChar.java +++ b/checker/tests/index/NonnegativeChar.java @@ -1,38 +1,37 @@ +import java.util.ArrayList; import org.checkerframework.checker.index.qual.LowerBoundBottom; import org.checkerframework.checker.index.qual.PolyLowerBound; -import java.util.ArrayList; - public class NonnegativeChar { - void foreach(char[] array) { - for (char value : array) {} - } + void foreach(char[] array) { + for (char value : array) {} + } - char constant() { - return Character.MAX_VALUE; - } + char constant() { + return Character.MAX_VALUE; + } - char conversion(int i) { - return (char) i; - } + char conversion(int i) { + return (char) i; + } - public void takeList(ArrayList z) {} + public void takeList(ArrayList z) {} - public void passList() { - takeList(new ArrayList()); - } + public void passList() { + takeList(new ArrayList()); + } - static class CustomList extends ArrayList {} + static class CustomList extends ArrayList {} - public void passCustomList() { - takeList(new CustomList()); - } + public void passCustomList() { + takeList(new CustomList()); + } - public @LowerBoundBottom char bottomLB(@LowerBoundBottom char c) { - return c; - } + public @LowerBoundBottom char bottomLB(@LowerBoundBottom char c) { + return c; + } - public @PolyLowerBound char polyLB(@PolyLowerBound char c) { - return c; - } + public @PolyLowerBound char polyLB(@PolyLowerBound char c) { + return c; + } } diff --git a/checker/tests/index/NotEnoughOffsets.java b/checker/tests/index/NotEnoughOffsets.java index 58a1fbc02ad..d311537a5fe 100644 --- a/checker/tests/index/NotEnoughOffsets.java +++ b/checker/tests/index/NotEnoughOffsets.java @@ -2,21 +2,21 @@ public class NotEnoughOffsets { - int[] a; - int[] b; - int c, d; + int[] a; + int[] b; + int c, d; - void badParam( - // :: error: (different.length.sequences.offsets) - @LTLengthOf( - value = {"a", "b"}, - offset = {"c"}) - int x) {} + void badParam( + // :: error: (different.length.sequences.offsets) + @LTLengthOf( + value = {"a", "b"}, + offset = {"c"}) + int x) {} - void badParam2( - // :: error: (different.length.sequences.offsets) - @LTLengthOf( - value = {"a"}, - offset = {"c", "d"}) - int x) {} + void badParam2( + // :: error: (different.length.sequences.offsets) + @LTLengthOf( + value = {"a"}, + offset = {"c", "d"}) + int x) {} } diff --git a/checker/tests/index/NotEqualTransfer.java b/checker/tests/index/NotEqualTransfer.java index 24a21afae81..5cd3dbbceaa 100644 --- a/checker/tests/index/NotEqualTransfer.java +++ b/checker/tests/index/NotEqualTransfer.java @@ -1,26 +1,26 @@ import org.checkerframework.common.value.qual.MinLen; public class NotEqualTransfer { - void neq_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - int @MinLen(1) [] b = a; - } + void neq_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + int @MinLen(1) [] b = a; } + } - void neq_bad_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void neq_bad_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } - void neq_zero_special_case(int[] a) { - if (a.length != 0) { - int @MinLen(1) [] b = a; - } + void neq_zero_special_case(int[] a) { + if (a.length != 0) { + int @MinLen(1) [] b = a; } + } } diff --git a/checker/tests/index/ObjectClone.java b/checker/tests/index/ObjectClone.java index ab9faaf3b2d..ad7f8fc49fc 100644 --- a/checker/tests/index/ObjectClone.java +++ b/checker/tests/index/ObjectClone.java @@ -1,28 +1,27 @@ // Test case for issue 146: https://github.com/kelloggm/checker-framework/issues/146 -import org.checkerframework.checker.index.qual.*; - import java.util.Arrays; +import org.checkerframework.checker.index.qual.*; public class ObjectClone { - void test(int[] a, int @SameLen("#1") [] b) { - int @SameLen("a") [] c = b.clone(); - int @SameLen({"a", "d"}) [] d = b.clone(); - int @SameLen({"a", "e"}) [] e = b; - int @SameLen("f") [] f = b; - } + void test(int[] a, int @SameLen("#1") [] b) { + int @SameLen("a") [] c = b.clone(); + int @SameLen({"a", "d"}) [] d = b.clone(); + int @SameLen({"a", "e"}) [] e = b; + int @SameLen("f") [] f = b; + } - public static void main(String[] args) { - String @SameLen("args") [] args2 = args; - String @SameLen({"args", "args_sorted"}) [] args_sorted = args.clone(); - Arrays.sort(args_sorted); - String @SameLen({"args", "args_sorted"}) [] args_sorted2 = args_sorted.clone(); - if (args_sorted.length == 1) { - @IndexFor("args_sorted") int i = 0; - @IndexFor("args") int j = 0; - String @SameLen({"args", "args_sorted"}) [] k = args; - System.out.println(args[0]); - } + public static void main(String[] args) { + String @SameLen("args") [] args2 = args; + String @SameLen({"args", "args_sorted"}) [] args_sorted = args.clone(); + Arrays.sort(args_sorted); + String @SameLen({"args", "args_sorted"}) [] args_sorted2 = args_sorted.clone(); + if (args_sorted.length == 1) { + @IndexFor("args_sorted") int i = 0; + @IndexFor("args") int j = 0; + String @SameLen({"args", "args_sorted"}) [] k = args; + System.out.println(args[0]); } + } } diff --git a/checker/tests/index/Offset97.java b/checker/tests/index/Offset97.java index 7ef8afaed1f..057736fa047 100644 --- a/checker/tests/index/Offset97.java +++ b/checker/tests/index/Offset97.java @@ -3,12 +3,12 @@ import org.checkerframework.checker.index.qual.*; public class Offset97 { - public static void m2() { - int[] a = {1, 2, 3, 4, 5}; - @IndexFor("a") int i = 4; - @IndexFor("a") int j = 4; - if (j < a.length - i) { - @IndexFor("a") int k = i + j; - } + public static void m2() { + int[] a = {1, 2, 3, 4, 5}; + @IndexFor("a") int i = 4; + @IndexFor("a") int j = 4; + if (j < a.length - i) { + @IndexFor("a") int k = i + j; } + } } diff --git a/checker/tests/index/OffsetAnnotations.java b/checker/tests/index/OffsetAnnotations.java index c0440f9848f..ffafaeefd6c 100644 --- a/checker/tests/index/OffsetAnnotations.java +++ b/checker/tests/index/OffsetAnnotations.java @@ -1,17 +1,17 @@ import java.io.*; public class OffsetAnnotations { - public static void OffsetAnnotationsReader() throws IOException { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); - char[] buffer = new char[10]; - // :: error: (argument.type.incompatible) - bufferedReader.read(buffer, 5, 7); - } + public static void OffsetAnnotationsReader() throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + char[] buffer = new char[10]; + // :: error: (argument.type.incompatible) + bufferedReader.read(buffer, 5, 7); + } - public static void OffsetAnnotationsWriter() throws IOException { - BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out)); - char[] buffer = new char[10]; - // :: error: (argument.type.incompatible) - bufferedWriter.write(buffer, 5, 7); - } + public static void OffsetAnnotationsWriter() throws IOException { + BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out)); + char[] buffer = new char[10]; + // :: error: (argument.type.incompatible) + bufferedWriter.write(buffer, 5, 7); + } } diff --git a/checker/tests/index/OffsetExample.java b/checker/tests/index/OffsetExample.java index 39481908a9a..5adf3724d61 100644 --- a/checker/tests/index/OffsetExample.java +++ b/checker/tests/index/OffsetExample.java @@ -1,87 +1,86 @@ +import java.util.List; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.common.value.qual.MinLen; -import java.util.List; - @SuppressWarnings("lowerbound") public class OffsetExample { - void example1(int @MinLen(2) [] a) { - int j = 2; - int x = a.length; - int y = x - j; - for (int i = 0; i < y; i++) { - a[i + j] = 1; - } + void example1(int @MinLen(2) [] a) { + int j = 2; + int x = a.length; + int y = x - j; + for (int i = 0; i < y; i++) { + a[i + j] = 1; } + } - void example2(int @MinLen(2) [] a) { - int j = 2; - int x = a.length; - int y = x - j; - a[y] = 0; - for (int i = 0; i < y; i++) { - a[i + j] = 1; - a[j + i] = 1; - a[i + 0] = 1; - a[i - 1] = 1; - // ::error: (array.access.unsafe.high) - a[i + 2 + j] = 1; - } + void example2(int @MinLen(2) [] a) { + int j = 2; + int x = a.length; + int y = x - j; + a[y] = 0; + for (int i = 0; i < y; i++) { + a[i + j] = 1; + a[j + i] = 1; + a[i + 0] = 1; + a[i - 1] = 1; + // ::error: (array.access.unsafe.high) + a[i + 2 + j] = 1; } + } - void example3(int @MinLen(2) [] a) { - int j = 2; - for (int i = 0; i < a.length - 2; i++) { - a[i + j] = 1; - } + void example3(int @MinLen(2) [] a) { + int j = 2; + for (int i = 0; i < a.length - 2; i++) { + a[i + j] = 1; } + } - void example4(int[] a, int offset) { - int max_index = a.length - offset; - for (int i = 0; i < max_index; i++) { - a[i + offset] = 0; - } + void example4(int[] a, int offset) { + int max_index = a.length - offset; + for (int i = 0; i < max_index; i++) { + a[i + offset] = 0; } + } - void example5(int[] a, int offset) { - for (int i = 0; i < a.length - offset; i++) { - a[i + offset] = 0; - } + void example5(int[] a, int offset) { + for (int i = 0; i < a.length - offset; i++) { + a[i + offset] = 0; } + } - void test(@IndexFor("#3") int start, @IndexOrHigh("#3") int end, int[] a) { - if (end > start) { - // If start == 0, then end - start is end. end might be equal to the length of a. So - // the array access might be too high. - // ::error: (array.access.unsafe.high) - a[end - start] = 0; - } + void test(@IndexFor("#3") int start, @IndexOrHigh("#3") int end, int[] a) { + if (end > start) { + // If start == 0, then end - start is end. end might be equal to the length of a. So + // the array access might be too high. + // ::error: (array.access.unsafe.high) + a[end - start] = 0; + } - if (end > start) { - a[end - start - 1] = 0; - } + if (end > start) { + a[end - start - 1] = 0; } + } - public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { - int a_len = a.length - a_offset; - int sub_len = sub.length; - if (a_len < sub_len) { - return false; - } - for (int i = 0; i < sub_len; i++) { - if (sub[i] == a[a_offset + i]) { - return false; - } - } - return true; + public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { + int a_len = a.length - a_offset; + int sub_len = sub.length; + if (a_len < sub_len) { + return false; + } + for (int i = 0; i < sub_len; i++) { + if (sub[i] == a[a_offset + i]) { + return false; + } } + return true; + } - public void test2(int[] a, List b) { - int b_size = b.size(); - Object[] result = new Object[a.length + b_size]; - for (int i = 0; i < b_size; i++) { - result[i + a.length] = b.get(i); - } + public void test2(int[] a, List b) { + int b_size = b.size(); + Object[] result = new Object[a.length + b_size]; + for (int i = 0; i < b_size; i++) { + result[i + a.length] = b.get(i); } + } } diff --git a/checker/tests/index/OffsetsAndConstants.java b/checker/tests/index/OffsetsAndConstants.java index e7bebd04658..93c79cf5319 100644 --- a/checker/tests/index/OffsetsAndConstants.java +++ b/checker/tests/index/OffsetsAndConstants.java @@ -3,28 +3,28 @@ import org.checkerframework.checker.index.qual.NonNegative; public class OffsetsAndConstants { - static int read( - char[] a, - @IndexOrHigh("#1") int off, - @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int len) { - int sum = 0; - for (int i = 0; i < len; i++) { - sum += a[i + off]; - } - return sum; + static int read( + char[] a, + @IndexOrHigh("#1") int off, + @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int len) { + int sum = 0; + for (int i = 0; i < len; i++) { + sum += a[i + off]; } + return sum; + } - public static void main(String[] args) { - char[] a = new char[10]; + public static void main(String[] args) { + char[] a = new char[10]; - read(a, 5, 4); + read(a, 5, 4); - read(a, 5, 5); + read(a, 5, 5); - // :: error: (argument.type.incompatible) - read(a, 5, 6); + // :: error: (argument.type.incompatible) + read(a, 5, 6); - // :: error: (argument.type.incompatible) - read(a, 5, 7); - } + // :: error: (argument.type.incompatible) + read(a, 5, 7); + } } diff --git a/checker/tests/index/OneLTL.java b/checker/tests/index/OneLTL.java index a47bc8fbe8a..de83bb62cba 100644 --- a/checker/tests/index/OneLTL.java +++ b/checker/tests/index/OneLTL.java @@ -1,10 +1,10 @@ public class OneLTL { - public static boolean sorted(int[] a) { - for (int i = 0; i < a.length - 1; i++) { - if (a[i + 1] < a[i]) { - return false; - } - } - return true; + public static boolean sorted(int[] a) { + for (int i = 0; i < a.length - 1; i++) { + if (a[i + 1] < a[i]) { + return false; + } } + return true; + } } diff --git a/checker/tests/index/OneOrTwo.java b/checker/tests/index/OneOrTwo.java index 8adb3f5a2a6..7718be27a38 100644 --- a/checker/tests/index/OneOrTwo.java +++ b/checker/tests/index/OneOrTwo.java @@ -1,17 +1,17 @@ import org.checkerframework.common.value.qual.*; public class OneOrTwo { - @IntVal({1, 2}) int getOneOrTwo() { - return 1; - } + @IntVal({1, 2}) int getOneOrTwo() { + return 1; + } - void test(@BottomVal int x) { - int[] a = new int[Integer.valueOf(getOneOrTwo())]; - // :: error: (array.length.negative) - int[] b = new int[Integer.valueOf(x)]; - } + void test(@BottomVal int x) { + int[] a = new int[Integer.valueOf(getOneOrTwo())]; + // :: error: (array.length.negative) + int[] b = new int[Integer.valueOf(x)]; + } - @PolyValue int poly(@PolyValue int y) { - return y; - } + @PolyValue int poly(@PolyValue int y) { + return y; + } } diff --git a/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java b/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java index 2ec214612e4..a7780d2dca2 100644 --- a/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java +++ b/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java @@ -3,25 +3,25 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class OnlyCheckSubsequenceWhenAssigningToArray { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - int[] array; + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + int[] array; - final @IndexFor("array") int start; + final @IndexFor("array") int start; - final @IndexOrHigh("array") int end; + final @IndexOrHigh("array") int end; - private OnlyCheckSubsequenceWhenAssigningToArray( - @IndexFor("array") int s, @IndexOrHigh("array") int e) { - start = s; - end = e; - } + private OnlyCheckSubsequenceWhenAssigningToArray( + @IndexFor("array") int s, @IndexOrHigh("array") int e) { + start = s; + end = e; + } - void testAssignmentToArrayElement(@IndexFor("this") int x, int y) { - array[start + x] = y; - } + void testAssignmentToArrayElement(@IndexFor("this") int x, int y) { + array[start + x] = y; + } - void testAssignmentToArray(int[] a) { - // :: error: to.not.ltel :: error: from.gt.to - array = a; - } + void testAssignmentToArray(int[] a) { + // :: error: to.not.ltel :: error: from.gt.to + array = a; + } } diff --git a/checker/tests/index/OuterThisJavaExpression.java b/checker/tests/index/OuterThisJavaExpression.java index 9941ec5b55d..6b6180b31f4 100644 --- a/checker/tests/index/OuterThisJavaExpression.java +++ b/checker/tests/index/OuterThisJavaExpression.java @@ -6,54 +6,54 @@ public abstract class OuterThisJavaExpression { - String s; + String s; - OuterThisJavaExpression(String s) { - this.s = s; - } + OuterThisJavaExpression(String s) { + this.s = s; + } - final class Inner { + final class Inner { - String s = "different from " + OuterThisJavaExpression.this.s; + String s = "different from " + OuterThisJavaExpression.this.s; - @SameLen("s") String f1() { - return s; - } + @SameLen("s") String f1() { + return s; + } - @SameLen("s") String f2() { - return this.s; - } + @SameLen("s") String f2() { + return this.s; + } - @SameLen("s") String f3() { - // :: error: (return.type.incompatible) - return OuterThisJavaExpression.this.s; - } + @SameLen("s") String f3() { + // :: error: (return.type.incompatible) + return OuterThisJavaExpression.this.s; + } - @SameLen("this.s") String f4() { - return s; - } + @SameLen("this.s") String f4() { + return s; + } - @SameLen("this.s") String f5() { - return this.s; - } + @SameLen("this.s") String f5() { + return this.s; + } - @SameLen("this.s") String f6() { - // :: error: (return.type.incompatible) - return OuterThisJavaExpression.this.s; - } + @SameLen("this.s") String f6() { + // :: error: (return.type.incompatible) + return OuterThisJavaExpression.this.s; + } - @SameLen("OuterThisJavaExpression.this.s") String f7() { - // :: error: (return.type.incompatible) - return s; - } + @SameLen("OuterThisJavaExpression.this.s") String f7() { + // :: error: (return.type.incompatible) + return s; + } - @SameLen("OuterThisJavaExpression.this.s") String f8() { - // :: error: (return.type.incompatible) - return this.s; - } + @SameLen("OuterThisJavaExpression.this.s") String f8() { + // :: error: (return.type.incompatible) + return this.s; + } - @SameLen("OuterThisJavaExpression.this.s") String f9() { - return OuterThisJavaExpression.this.s; - } + @SameLen("OuterThisJavaExpression.this.s") String f9() { + return OuterThisJavaExpression.this.s; } + } } diff --git a/checker/tests/index/ParserOffsetTest.java b/checker/tests/index/ParserOffsetTest.java index cbc088c306d..125dea33c59 100644 --- a/checker/tests/index/ParserOffsetTest.java +++ b/checker/tests/index/ParserOffsetTest.java @@ -3,91 +3,91 @@ public class ParserOffsetTest { - public void subtraction1(String[] a, @IndexFor("#1") int i) { - int length = a.length; - if (i >= length - 1 || a[i + 1] == null) { - // body is irrelevant - } + public void subtraction1(String[] a, @IndexFor("#1") int i) { + int length = a.length; + if (i >= length - 1 || a[i + 1] == null) { + // body is irrelevant } + } - public void addition1(String[] a, @IndexFor("#1") int i) { - int length = a.length; - if ((i + 1) >= length || a[i + 1] == null) { - // body is irrelevant - } + public void addition1(String[] a, @IndexFor("#1") int i) { + int length = a.length; + if ((i + 1) >= length || a[i + 1] == null) { + // body is irrelevant } + } - public void subtraction2(String[] a, @IndexFor("#1") int i) { - if (i < a.length - 1) { - @IndexFor("a") int j = i + 1; - } + public void subtraction2(String[] a, @IndexFor("#1") int i) { + if (i < a.length - 1) { + @IndexFor("a") int j = i + 1; } + } - public void addition2(String[] a, @IndexFor("#1") int i) { - if ((i + 1) < a.length) { - @IndexFor("a") int j = i + 1; - } + public void addition2(String[] a, @IndexFor("#1") int i) { + if ((i + 1) < a.length) { + @IndexFor("a") int j = i + 1; } + } - public void addition3(String[] a, @IndexFor("#1") int i) { - if ((i + 5) < a.length) { - @IndexFor("a") int j = i + 5; - } + public void addition3(String[] a, @IndexFor("#1") int i) { + if ((i + 5) < a.length) { + @IndexFor("a") int j = i + 5; } + } - @SuppressWarnings("lowerbound") - public void subtraction3(String[] a, @NonNegative int k) { - if (k - 5 < a.length) { - String s = a[k - 5]; - @IndexFor("a") int j = k - 5; - } + @SuppressWarnings("lowerbound") + public void subtraction3(String[] a, @NonNegative int k) { + if (k - 5 < a.length) { + String s = a[k - 5]; + @IndexFor("a") int j = k - 5; } + } - @SuppressWarnings("lowerbound") - public void subtraction4(String[] a, @IndexFor("#1") int i) { - if (1 - i < a.length) { - // The error on this assignment is a false positive. - // :: error: (assignment.type.incompatible) - @IndexFor("a") int j = 1 - i; + @SuppressWarnings("lowerbound") + public void subtraction4(String[] a, @IndexFor("#1") int i) { + if (1 - i < a.length) { + // The error on this assignment is a false positive. + // :: error: (assignment.type.incompatible) + @IndexFor("a") int j = 1 - i; - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "a", offset = "1") int k = i; - } + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "a", offset = "1") int k = i; } + } - @SuppressWarnings("lowerbound") - public void subtraction5(String[] a, int i) { - if (1 - i < a.length) { - // :: error: (assignment.type.incompatible) - @IndexFor("a") int j = i; - } + @SuppressWarnings("lowerbound") + public void subtraction5(String[] a, int i) { + if (1 - i < a.length) { + // :: error: (assignment.type.incompatible) + @IndexFor("a") int j = i; } + } - @SuppressWarnings("lowerbound") - public void subtraction6(String[] a, int i, int j) { - if (i - j < a.length - 1) { - @IndexFor("a") int k = i - j; - // :: error: (assignment.type.incompatible) - @IndexFor("a") int k1 = i; - } + @SuppressWarnings("lowerbound") + public void subtraction6(String[] a, int i, int j) { + if (i - j < a.length - 1) { + @IndexFor("a") int k = i - j; + // :: error: (assignment.type.incompatible) + @IndexFor("a") int k1 = i; } + } - public void multiplication1(String[] a, int i, @Positive int j) { - if ((i * j) < (a.length + j)) { - // :: error: (assignment.type.incompatible) - @IndexFor("a") int k = i; - // :: error: (assignment.type.incompatible) - @IndexFor("a") int k1 = j; - } + public void multiplication1(String[] a, int i, @Positive int j) { + if ((i * j) < (a.length + j)) { + // :: error: (assignment.type.incompatible) + @IndexFor("a") int k = i; + // :: error: (assignment.type.incompatible) + @IndexFor("a") int k1 = j; } + } - public void multiplication2(String @ArrayLen(5) [] a, @IntVal(-2) int i, @IntVal(20) int j) { - if ((i * j) < (a.length - 20)) { - @LTLengthOf("a") int k1 = i; - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "a", offset = "20") int k2 = i; - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int k3 = j; - } + public void multiplication2(String @ArrayLen(5) [] a, @IntVal(-2) int i, @IntVal(20) int j) { + if ((i * j) < (a.length - 20)) { + @LTLengthOf("a") int k1 = i; + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "a", offset = "20") int k2 = i; + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int k3 = j; } + } } diff --git a/checker/tests/index/ParsingBug.java b/checker/tests/index/ParsingBug.java index d597ed21432..153e5595a78 100644 --- a/checker/tests/index/ParsingBug.java +++ b/checker/tests/index/ParsingBug.java @@ -1,10 +1,10 @@ public class ParsingBug { - void test() { - String[] saOrig = new String[] {"foo", "bar"}; - Object o1 = do_things((Object) saOrig); - } + void test() { + String[] saOrig = new String[] {"foo", "bar"}; + Object o1 = do_things((Object) saOrig); + } - Object do_things(Object o) { - return o; - } + Object do_things(Object o) { + return o; + } } diff --git a/checker/tests/index/Pilot2HalfLength.java b/checker/tests/index/Pilot2HalfLength.java index 601a7d5a564..04696f17a65 100644 --- a/checker/tests/index/Pilot2HalfLength.java +++ b/checker/tests/index/Pilot2HalfLength.java @@ -3,11 +3,11 @@ // @skip-test until fixed public class Pilot2HalfLength { - private static int[] getFirstHalf(int[] array) { - int[] firstHalf = new int[array.length / 2]; - for (int i = 0; i < firstHalf.length; i++) { - firstHalf[i] = array[i]; - } - return firstHalf; + private static int[] getFirstHalf(int[] array) { + int[] firstHalf = new int[array.length / 2]; + for (int i = 0; i < firstHalf.length; i++) { + firstHalf[i] = array[i]; } + return firstHalf; + } } diff --git a/checker/tests/index/Pilot3ArrayCreation.java b/checker/tests/index/Pilot3ArrayCreation.java index 493b6c36c3d..6494c07e4a2 100644 --- a/checker/tests/index/Pilot3ArrayCreation.java +++ b/checker/tests/index/Pilot3ArrayCreation.java @@ -1,10 +1,10 @@ // This test case is for issue 44: https://github.com/kelloggm/checker-framework/issues/44 public class Pilot3ArrayCreation { - void test(int[] firstArray, int[] secondArray[]) { - int[] newArray = new int[firstArray.length + secondArray.length]; - for (int i = 0; i < firstArray.length; i++) { - newArray[i] = firstArray[i]; // or newArray[i] = secondArray[i]; - } + void test(int[] firstArray, int[] secondArray[]) { + int[] newArray = new int[firstArray.length + secondArray.length]; + for (int i = 0; i < firstArray.length; i++) { + newArray[i] = firstArray[i]; // or newArray[i] = secondArray[i]; } + } } diff --git a/checker/tests/index/Pilot4Subtraction.java b/checker/tests/index/Pilot4Subtraction.java index 534fe0ab60d..36e85c4674d 100644 --- a/checker/tests/index/Pilot4Subtraction.java +++ b/checker/tests/index/Pilot4Subtraction.java @@ -4,14 +4,14 @@ public class Pilot4Subtraction { - private static int[] getSecondHalf(int[] array) { - int len = array.length / 2; - int b = len - 1; - int[] arr = new int[len]; - for (int a = 0; a < len; a++) { - arr[a] = array[b]; - b--; - } - return arr; + private static int[] getSecondHalf(int[] array) { + int len = array.length / 2; + int b = len - 1; + int[] arr = new int[len]; + for (int a = 0; a < len; a++) { + arr[a] = array[b]; + b--; } + return arr; + } } diff --git a/checker/tests/index/PlumeFail.java b/checker/tests/index/PlumeFail.java index 1ae9cd296cb..12fe95a7ad9 100644 --- a/checker/tests/index/PlumeFail.java +++ b/checker/tests/index/PlumeFail.java @@ -1,19 +1,18 @@ // Test case affected by eisop Issue 22: // https://github.com/eisop/checker-framework/issues/22 -import org.checkerframework.common.value.qual.MinLen; - import java.util.Arrays; +import org.checkerframework.common.value.qual.MinLen; public class PlumeFail { - void method() { - // Workaround by casting. - @SuppressWarnings({"index", "value"}) - String @MinLen(1) [] args = (String @MinLen(1) []) getArray(); - String[] argArray = Arrays.copyOfRange(args, 1, args.length); - } + void method() { + // Workaround by casting. + @SuppressWarnings({"index", "value"}) + String @MinLen(1) [] args = (String @MinLen(1) []) getArray(); + String[] argArray = Arrays.copyOfRange(args, 1, args.length); + } - String[] getArray() { - return null; - } + String[] getArray() { + return null; + } } diff --git a/checker/tests/index/PlumeFailMin.java b/checker/tests/index/PlumeFailMin.java index bf5a3691e46..8bf413a6414 100644 --- a/checker/tests/index/PlumeFailMin.java +++ b/checker/tests/index/PlumeFailMin.java @@ -7,19 +7,19 @@ import org.checkerframework.common.value.qual.MinLen; abstract class PlumeFailMin { - void ok() { - String @MinLen(1) [] args = getArrayOk(); - @IndexOrHigh("args") int x = 1; - } + void ok() { + String @MinLen(1) [] args = getArrayOk(); + @IndexOrHigh("args") int x = 1; + } - abstract String @MinLen(1) [] getArrayOk(); + abstract String @MinLen(1) [] getArrayOk(); - void fail() { - // Workaround by casting. - @SuppressWarnings({"index", "value"}) - String @MinLen(1) [] args = (String @MinLen(1) []) getArrayFail(); - @IndexOrHigh("args") int x = 1; - } + void fail() { + // Workaround by casting. + @SuppressWarnings({"index", "value"}) + String @MinLen(1) [] args = (String @MinLen(1) []) getArrayFail(); + @IndexOrHigh("args") int x = 1; + } - abstract String[] getArrayFail(); + abstract String[] getArrayFail(); } diff --git a/checker/tests/index/PlusPlusBug.java b/checker/tests/index/PlusPlusBug.java index 676ff8abaf7..16183384dfd 100644 --- a/checker/tests/index/PlusPlusBug.java +++ b/checker/tests/index/PlusPlusBug.java @@ -1,14 +1,14 @@ import org.checkerframework.checker.index.qual.*; public class PlusPlusBug { - int[] array = {}; + int[] array = {}; - void test(@LTLengthOf("array") int x) { - // :: error: (unary.increment.type.incompatible) - x++; - // :: error: (unary.increment.type.incompatible) - ++x; - // :: error: (assignment.type.incompatible) - x = x + 1; - } + void test(@LTLengthOf("array") int x) { + // :: error: (unary.increment.type.incompatible) + x++; + // :: error: (unary.increment.type.incompatible) + ++x; + // :: error: (assignment.type.incompatible) + x = x + 1; + } } diff --git a/checker/tests/index/PolyCrash.java b/checker/tests/index/PolyCrash.java index 121af3c1373..4db86d61e6b 100644 --- a/checker/tests/index/PolyCrash.java +++ b/checker/tests/index/PolyCrash.java @@ -1,5 +1,5 @@ public class PolyCrash { - void test1(Integer integer) { - ++integer; - } + void test1(Integer integer) { + ++integer; + } } diff --git a/checker/tests/index/PolyLengthTest.java b/checker/tests/index/PolyLengthTest.java index 379bd367d4e..52205816989 100644 --- a/checker/tests/index/PolyLengthTest.java +++ b/checker/tests/index/PolyLengthTest.java @@ -2,19 +2,19 @@ import org.checkerframework.common.value.qual.*; public class PolyLengthTest { - int @PolyLength [] id(int @PolyLength [] a) { - return a; - } + int @PolyLength [] id(int @PolyLength [] a) { + return a; + } - int @SameLen("#2") [] test0(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { - return id(a); - } + int @SameLen("#2") [] test0(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { + return id(a); + } - int @ArrayLen(3) [] test1(int @ArrayLen(3) [] a) { - return id(a); - } + int @ArrayLen(3) [] test1(int @ArrayLen(3) [] a) { + return id(a); + } - int @MinLen(3) [] test2(int @MinLen(3) [] a) { - return id(a); - } + int @MinLen(3) [] test2(int @MinLen(3) [] a) { + return id(a); + } } diff --git a/checker/tests/index/Polymorphic.java b/checker/tests/index/Polymorphic.java index 31f07063b0c..0585ba5835c 100644 --- a/checker/tests/index/Polymorphic.java +++ b/checker/tests/index/Polymorphic.java @@ -10,66 +10,66 @@ public class Polymorphic { - // Identity functions + // Identity functions - @PolyLowerBound int lbc_identity(@PolyLowerBound int a) { - return a; - } + @PolyLowerBound int lbc_identity(@PolyLowerBound int a) { + return a; + } - int @PolySameLen [] samelen_identity(int @PolySameLen [] a) { - int @SameLen("a") [] x = a; - return a; - } + int @PolySameLen [] samelen_identity(int @PolySameLen [] a) { + int @SameLen("a") [] x = a; + return a; + } - @PolyUpperBound int ubc_identity(@PolyUpperBound int a) { - return a; - } + @PolyUpperBound int ubc_identity(@PolyUpperBound int a) { + return a; + } - // SameLen tests - void samelen_id(int @SameLen("#2") [] a, int[] a2) { - int[] banana; - int @SameLen("a2") [] b = samelen_identity(a); - // :: error: (assignment.type.incompatible) - int @SameLen("banana") [] c = samelen_identity(b); - } + // SameLen tests + void samelen_id(int @SameLen("#2") [] a, int[] a2) { + int[] banana; + int @SameLen("a2") [] b = samelen_identity(a); + // :: error: (assignment.type.incompatible) + int @SameLen("banana") [] c = samelen_identity(b); + } - // UpperBound tests - void ubc_id( - int[] a, - int[] b, - @LTLengthOf("#1") int ai, - @LTEqLengthOf("#1") int al, - @LTLengthOf({"#1", "#2"}) int abi, - @LTEqLengthOf({"#1", "#2"}) int abl) { - int[] c; + // UpperBound tests + void ubc_id( + int[] a, + int[] b, + @LTLengthOf("#1") int ai, + @LTEqLengthOf("#1") int al, + @LTLengthOf({"#1", "#2"}) int abi, + @LTEqLengthOf({"#1", "#2"}) int abl) { + int[] c; - @LTLengthOf("a") int ai1 = ubc_identity(ai); - // :: error: (assignment.type.incompatible) - @LTLengthOf("b") int ai2 = ubc_identity(ai); + @LTLengthOf("a") int ai1 = ubc_identity(ai); + // :: error: (assignment.type.incompatible) + @LTLengthOf("b") int ai2 = ubc_identity(ai); - @LTEqLengthOf("a") int al1 = ubc_identity(al); - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int al2 = ubc_identity(al); + @LTEqLengthOf("a") int al1 = ubc_identity(al); + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int al2 = ubc_identity(al); - @LTLengthOf({"a", "b"}) int abi1 = ubc_identity(abi); - // :: error: (assignment.type.incompatible) - @LTLengthOf({"a", "b", "c"}) int abi2 = ubc_identity(abi); + @LTLengthOf({"a", "b"}) int abi1 = ubc_identity(abi); + // :: error: (assignment.type.incompatible) + @LTLengthOf({"a", "b", "c"}) int abi2 = ubc_identity(abi); - @LTEqLengthOf({"a", "b"}) int abl1 = ubc_identity(abl); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"a", "b", "c"}) int abl2 = ubc_identity(abl); - } + @LTEqLengthOf({"a", "b"}) int abl1 = ubc_identity(abl); + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"a", "b", "c"}) int abl2 = ubc_identity(abl); + } - // LowerBound tests - void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { - @NonNegative int an = lbc_identity(n); - // :: error: (assignment.type.incompatible) - @Positive int bn = lbc_identity(n); + // LowerBound tests + void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { + @NonNegative int an = lbc_identity(n); + // :: error: (assignment.type.incompatible) + @Positive int bn = lbc_identity(n); - @GTENegativeOne int ag = lbc_identity(g); - // :: error: (assignment.type.incompatible) - @NonNegative int bg = lbc_identity(g); + @GTENegativeOne int ag = lbc_identity(g); + // :: error: (assignment.type.incompatible) + @NonNegative int bg = lbc_identity(g); - @Positive int ap = lbc_identity(p); - } + @Positive int ap = lbc_identity(p); + } } diff --git a/checker/tests/index/Polymorphic2.java b/checker/tests/index/Polymorphic2.java index 9ed046ec2de..6913fab2874 100644 --- a/checker/tests/index/Polymorphic2.java +++ b/checker/tests/index/Polymorphic2.java @@ -8,46 +8,46 @@ import org.checkerframework.checker.index.qual.SameLen; public class Polymorphic2 { - public static boolean flag = false; - - int @PolySameLen [] mergeSameLen(int @PolySameLen [] a, int @PolySameLen [] b) { - return flag ? a : b; - } - - int[] array1 = new int[2]; - int[] array2 = new int[2]; - - void testSameLen(int @SameLen("array1") [] a, int @SameLen("array2") [] b) { - int[] x = mergeSameLen(a, b); - // :: error: (assignment.type.incompatible) - int @SameLen("array1") [] y = mergeSameLen(a, b); - } - - @PolyUpperBound int mergeUpperBound(@PolyUpperBound int a, @PolyUpperBound int b) { - return flag ? a : b; - } - - // UpperBound tests - void testUpperBound(@LTLengthOf("array1") int a, @LTLengthOf("array2") int b) { - int z = mergeUpperBound(a, b); - // :: error: (assignment.type.incompatible) - @LTLengthOf("array1") int zz = mergeUpperBound(a, b); - } - - void testUpperBound2(@LTLengthOf("array1") int a, @LTEqLengthOf("array1") int b) { - @LTEqLengthOf("array1") int z = mergeUpperBound(a, b); - // :: error: (assignment.type.incompatible) - @LTLengthOf("array1") int zz = mergeUpperBound(a, b); - } - - @PolyLowerBound int mergeLowerBound(@PolyLowerBound int a, @PolyLowerBound int b) { - return flag ? a : b; - } - - // LowerBound tests - void lbc_id(@NonNegative int n, @Positive int p) { - @NonNegative int z = mergeLowerBound(n, p); - // :: error: (assignment.type.incompatible) - @Positive int zz = mergeLowerBound(n, p); - } + public static boolean flag = false; + + int @PolySameLen [] mergeSameLen(int @PolySameLen [] a, int @PolySameLen [] b) { + return flag ? a : b; + } + + int[] array1 = new int[2]; + int[] array2 = new int[2]; + + void testSameLen(int @SameLen("array1") [] a, int @SameLen("array2") [] b) { + int[] x = mergeSameLen(a, b); + // :: error: (assignment.type.incompatible) + int @SameLen("array1") [] y = mergeSameLen(a, b); + } + + @PolyUpperBound int mergeUpperBound(@PolyUpperBound int a, @PolyUpperBound int b) { + return flag ? a : b; + } + + // UpperBound tests + void testUpperBound(@LTLengthOf("array1") int a, @LTLengthOf("array2") int b) { + int z = mergeUpperBound(a, b); + // :: error: (assignment.type.incompatible) + @LTLengthOf("array1") int zz = mergeUpperBound(a, b); + } + + void testUpperBound2(@LTLengthOf("array1") int a, @LTEqLengthOf("array1") int b) { + @LTEqLengthOf("array1") int z = mergeUpperBound(a, b); + // :: error: (assignment.type.incompatible) + @LTLengthOf("array1") int zz = mergeUpperBound(a, b); + } + + @PolyLowerBound int mergeLowerBound(@PolyLowerBound int a, @PolyLowerBound int b) { + return flag ? a : b; + } + + // LowerBound tests + void lbc_id(@NonNegative int n, @Positive int p) { + @NonNegative int z = mergeLowerBound(n, p); + // :: error: (assignment.type.incompatible) + @Positive int zz = mergeLowerBound(n, p); + } } diff --git a/checker/tests/index/Polymorphic3.java b/checker/tests/index/Polymorphic3.java index 5c29323b1c0..541bb8eae16 100644 --- a/checker/tests/index/Polymorphic3.java +++ b/checker/tests/index/Polymorphic3.java @@ -2,49 +2,49 @@ public class Polymorphic3 { - // Identity functions - - @PolyIndex int identity(@PolyIndex int a) { - return a; - } - - // UpperBound tests - void ubc_id( - int[] a, - int[] b, - @LTLengthOf("#1") int ai, - @LTEqLengthOf("#1") int al, - @LTLengthOf({"#1", "#2"}) int abi, - @LTEqLengthOf({"#1", "#2"}) int abl) { - int[] c; - - @LTLengthOf("a") int ai1 = identity(ai); - // :: error: (assignment.type.incompatible) - @LTLengthOf("b") int ai2 = identity(ai); - - @LTEqLengthOf("a") int al1 = identity(al); - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int al2 = identity(al); - - @LTLengthOf({"a", "b"}) int abi1 = identity(abi); - // :: error: (assignment.type.incompatible) - @LTLengthOf({"a", "b", "c"}) int abi2 = identity(abi); - - @LTEqLengthOf({"a", "b"}) int abl1 = identity(abl); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"a", "b", "c"}) int abl2 = identity(abl); - } - - // LowerBound tests - void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { - @NonNegative int an = identity(n); - // :: error: (assignment.type.incompatible) - @Positive int bn = identity(n); - - @GTENegativeOne int ag = identity(g); - // :: error: (assignment.type.incompatible) - @NonNegative int bg = identity(g); - - @Positive int ap = identity(p); - } + // Identity functions + + @PolyIndex int identity(@PolyIndex int a) { + return a; + } + + // UpperBound tests + void ubc_id( + int[] a, + int[] b, + @LTLengthOf("#1") int ai, + @LTEqLengthOf("#1") int al, + @LTLengthOf({"#1", "#2"}) int abi, + @LTEqLengthOf({"#1", "#2"}) int abl) { + int[] c; + + @LTLengthOf("a") int ai1 = identity(ai); + // :: error: (assignment.type.incompatible) + @LTLengthOf("b") int ai2 = identity(ai); + + @LTEqLengthOf("a") int al1 = identity(al); + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int al2 = identity(al); + + @LTLengthOf({"a", "b"}) int abi1 = identity(abi); + // :: error: (assignment.type.incompatible) + @LTLengthOf({"a", "b", "c"}) int abi2 = identity(abi); + + @LTEqLengthOf({"a", "b"}) int abl1 = identity(abl); + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"a", "b", "c"}) int abl2 = identity(abl); + } + + // LowerBound tests + void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { + @NonNegative int an = identity(n); + // :: error: (assignment.type.incompatible) + @Positive int bn = identity(n); + + @GTENegativeOne int ag = identity(g); + // :: error: (assignment.type.incompatible) + @NonNegative int bg = identity(g); + + @Positive int ap = identity(p); + } } diff --git a/checker/tests/index/Polymorphic4.java b/checker/tests/index/Polymorphic4.java index c1b2fcc41e4..f57677bc27e 100644 --- a/checker/tests/index/Polymorphic4.java +++ b/checker/tests/index/Polymorphic4.java @@ -4,10 +4,10 @@ public class Polymorphic4 { - public static String @PolyValue [] quantify(String @PolyValue [] vars) { + public static String @PolyValue [] quantify(String @PolyValue [] vars) { - String[] result = new String[vars.length]; + String[] result = new String[vars.length]; - return result; - } + return result; + } } diff --git a/checker/tests/index/PreAndPostDec.java b/checker/tests/index/PreAndPostDec.java index d50709f0ccc..ff876162a71 100644 --- a/checker/tests/index/PreAndPostDec.java +++ b/checker/tests/index/PreAndPostDec.java @@ -1,34 +1,34 @@ public class PreAndPostDec { - void pre1(int[] args) { - int ii = 0; - while ((ii < args.length)) { - // :: error: (array.access.unsafe.high) - int m = args[++ii]; - } + void pre1(int[] args) { + int ii = 0; + while ((ii < args.length)) { + // :: error: (array.access.unsafe.high) + int m = args[++ii]; } + } - void pre2(int[] args) { - int ii = 0; - while ((ii < args.length)) { - ii++; - // :: error: (array.access.unsafe.high) - int m = args[ii]; - } + void pre2(int[] args) { + int ii = 0; + while ((ii < args.length)) { + ii++; + // :: error: (array.access.unsafe.high) + int m = args[ii]; } + } - void post1(int[] args) { - int ii = 0; - while ((ii < args.length)) { - int m = args[ii++]; - } + void post1(int[] args) { + int ii = 0; + while ((ii < args.length)) { + int m = args[ii++]; } + } - void post2(int[] args) { - int ii = 0; - while ((ii < args.length)) { - int m = args[ii]; - ii++; - } + void post2(int[] args) { + int ii = 0; + while ((ii < args.length)) { + int m = args[ii]; + ii++; } + } } diff --git a/checker/tests/index/PredecrementTest.java b/checker/tests/index/PredecrementTest.java index a52ca2cf13d..bc5e152d10b 100644 --- a/checker/tests/index/PredecrementTest.java +++ b/checker/tests/index/PredecrementTest.java @@ -3,39 +3,39 @@ public class PredecrementTest { - public static void warningForLoop(int @MinLen(1) [] a) { - for (int i = a.length; --i >= 0; ) { - a[i] = 0; - } + public static void warningForLoop(int @MinLen(1) [] a) { + for (int i = a.length; --i >= 0; ) { + a[i] = 0; } + } - public static void warningIfStatement(int @MinLen(1) [] a) { - int i = a.length; - if (--i >= 0) { - a[i] = 0; - } + public static void warningIfStatement(int @MinLen(1) [] a) { + int i = a.length; + if (--i >= 0) { + a[i] = 0; } + } - public static void warningIfStatementRange1( - int @MinLen(1) [] a, @IntRange(from = 1, to = 1) int j) { - int i = j; - if (--i >= 0) { - a[i] = 0; - } + public static void warningIfStatementRange1( + int @MinLen(1) [] a, @IntRange(from = 1, to = 1) int j) { + int i = j; + if (--i >= 0) { + a[i] = 0; } + } - public static void warningIfStatementVal1(int @MinLen(1) [] a, @IntVal(1) int j) { - int i = j; - if (--i >= 0) { - a[i] = 0; - } + public static void warningIfStatementVal1(int @MinLen(1) [] a, @IntVal(1) int j) { + int i = j; + if (--i >= 0) { + a[i] = 0; } + } - public static void warningIfStatementRange12( - int @MinLen(2) [] a, @IntRange(from = 1, to = 2) int j) { - int i = j; - if (--i >= 0) { - a[i] = 0; - } + public static void warningIfStatementRange12( + int @MinLen(2) [] a, @IntRange(from = 1, to = 2) int j) { + int i = j; + if (--i >= 0) { + a[i] = 0; } + } } diff --git a/checker/tests/index/PrimitiveWrappers.java b/checker/tests/index/PrimitiveWrappers.java index c07fb338ce2..ab066fff05d 100644 --- a/checker/tests/index/PrimitiveWrappers.java +++ b/checker/tests/index/PrimitiveWrappers.java @@ -7,12 +7,12 @@ public class PrimitiveWrappers { - void int_Integer_access_equivalent(@IndexFor("#3") Integer i, @IndexFor("#3") int j, int[] a) { - a[i] = a[j]; - } + void int_Integer_access_equivalent(@IndexFor("#3") Integer i, @IndexFor("#3") int j, int[] a) { + a[i] = a[j]; + } - void array_creation(@NonNegative Integer i, @NonNegative int j) { - int[] a = new int[j]; - int[] b = new int[i]; - } + void array_creation(@NonNegative Integer i, @NonNegative int j) { + int[] a = new int[j]; + int[] b = new int[i]; + } } diff --git a/checker/tests/index/PrivateSameLen.java b/checker/tests/index/PrivateSameLen.java index 653bb7601ef..1510a9fe2b2 100644 --- a/checker/tests/index/PrivateSameLen.java +++ b/checker/tests/index/PrivateSameLen.java @@ -3,13 +3,13 @@ public class PrivateSameLen { - @Pure - private @SameLen("#1") String getSameLenString(String in) { - return in; - } + @Pure + private @SameLen("#1") String getSameLenString(String in) { + return in; + } - private void test() { - String in = "foo"; - @SameLen("this.getSameLenString(in)") String myStr = getSameLenString(in); - } + private void test() { + String in = "foo"; + @SameLen("this.getSameLenString(in)") String myStr = getSameLenString(in); + } } diff --git a/checker/tests/index/RandomTest.java b/checker/tests/index/RandomTest.java index 85196ace814..14e3aa27476 100644 --- a/checker/tests/index/RandomTest.java +++ b/checker/tests/index/RandomTest.java @@ -1,15 +1,14 @@ -import org.checkerframework.checker.index.qual.LTLengthOf; - import java.util.Random; +import org.checkerframework.checker.index.qual.LTLengthOf; public class RandomTest { - void test() { - Random rand = new Random(); - int[] a = new int[8]; - // :: error: (anno.on.irrelevant) - @LTLengthOf("a") double d1 = Math.random() * a.length; - @LTLengthOf("a") int deref = (int) (Math.random() * a.length); - @LTLengthOf("a") int deref2 = (int) (rand.nextDouble() * a.length); - @LTLengthOf("a") int deref3 = rand.nextInt(a.length); - } + void test() { + Random rand = new Random(); + int[] a = new int[8]; + // :: error: (anno.on.irrelevant) + @LTLengthOf("a") double d1 = Math.random() * a.length; + @LTLengthOf("a") int deref = (int) (Math.random() * a.length); + @LTLengthOf("a") int deref2 = (int) (rand.nextDouble() * a.length); + @LTLengthOf("a") int deref3 = rand.nextInt(a.length); + } } diff --git a/checker/tests/index/RandomTestLBC.java b/checker/tests/index/RandomTestLBC.java index ef1fd600e22..ad73b87eed7 100644 --- a/checker/tests/index/RandomTestLBC.java +++ b/checker/tests/index/RandomTestLBC.java @@ -1,19 +1,18 @@ -import org.checkerframework.checker.index.qual.NonNegative; - import java.util.Random; +import org.checkerframework.checker.index.qual.NonNegative; public class RandomTestLBC { - void test() { - Random rand = new Random(); - int[] a = new int[8]; - // Math.random() and Math.nextDouble() are always non-negative, but the Index Checker does - // not reason about floating-point values. - // :: error: (anno.on.irrelevant) - @NonNegative double d1 = Math.random() * a.length; - // :: error: (assignment.type.incompatible) - @NonNegative int deref = (int) (Math.random() * a.length); - // :: error: (assignment.type.incompatible) - @NonNegative int deref2 = (int) (rand.nextDouble() * a.length); - @NonNegative int deref3 = rand.nextInt(a.length); - } + void test() { + Random rand = new Random(); + int[] a = new int[8]; + // Math.random() and Math.nextDouble() are always non-negative, but the Index Checker does + // not reason about floating-point values. + // :: error: (anno.on.irrelevant) + @NonNegative double d1 = Math.random() * a.length; + // :: error: (assignment.type.incompatible) + @NonNegative int deref = (int) (Math.random() * a.length); + // :: error: (assignment.type.incompatible) + @NonNegative int deref2 = (int) (rand.nextDouble() * a.length); + @NonNegative int deref3 = rand.nextInt(a.length); + } } diff --git a/checker/tests/index/RangeIndex.java b/checker/tests/index/RangeIndex.java index eb705b2db10..ba1740e6249 100644 --- a/checker/tests/index/RangeIndex.java +++ b/checker/tests/index/RangeIndex.java @@ -1,8 +1,8 @@ import org.checkerframework.common.value.qual.*; public class RangeIndex { - void foo(@IntRange(from = 0, to = 11) int x, int @MinLen(10) [] a) { - // :: error: (array.access.unsafe.high.range) - int y = a[x]; - } + void foo(@IntRange(from = 0, to = 11) int x, int @MinLen(10) [] a) { + // :: error: (array.access.unsafe.high.range) + int y = a[x]; + } } diff --git a/checker/tests/index/Reassignment.java b/checker/tests/index/Reassignment.java index 0eb46be2598..fbec46c6466 100644 --- a/checker/tests/index/Reassignment.java +++ b/checker/tests/index/Reassignment.java @@ -3,11 +3,11 @@ // is worse than no solution and an obvious issue. public class Reassignment { - void test(int[] arr, int i) { - if (i > 0 && i < arr.length) { - arr = new int[0]; - // :: error: (array.access.unsafe.high) - int j = arr[i]; - } + void test(int[] arr, int i) { + if (i > 0 && i < arr.length) { + arr = new int[0]; + // :: error: (array.access.unsafe.high) + int j = arr[i]; } + } } diff --git a/checker/tests/index/RefineEq.java b/checker/tests/index/RefineEq.java index 5c4e13cdcc6..fea2b274ca1 100644 --- a/checker/tests/index/RefineEq.java +++ b/checker/tests/index/RefineEq.java @@ -2,38 +2,38 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineEq { - int[] arr = {1}; + int[] arr = {1}; - void testLTL(@LTLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); + void testLTL(@LTLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); - int b = 1; - if (test == b) { - @LTLengthOf("arr") int c = b; + int b = 1; + if (test == b) { + @LTLengthOf("arr") int c = b; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int e = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int e = b; } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - int b = 1; - if (test == b) { - @LTEqLengthOf("arr") int c = b; + int b = 1; + if (test == b) { + @LTEqLengthOf("arr") int c = b; - @LTLengthOf("arr") int g = b; - } else { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int e = b; - } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; + @LTLengthOf("arr") int g = b; + } else { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int e = b; } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; + } } diff --git a/checker/tests/index/RefineGT.java b/checker/tests/index/RefineGT.java index a73613c6d7d..abb87c3a390 100644 --- a/checker/tests/index/RefineGT.java +++ b/checker/tests/index/RefineGT.java @@ -2,52 +2,52 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineGT { - int[] arr = {1}; - - void testLTL(@LTLengthOf("arr") int test) { - // The reason for the parsing is so that the Value Checker - // can't figure it out but normal humans can. - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test > b) { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (a > b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } + int[] arr = {1}; + + void testLTL(@LTLengthOf("arr") int test) { + // The reason for the parsing is so that the Value Checker + // can't figure it out but normal humans can. + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test > b) { + @LTLengthOf("arr") int c = b; } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (a > b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } + } + + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test > b) { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (a > b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test > b) { + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (a > b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; } + } } diff --git a/checker/tests/index/RefineGTE.java b/checker/tests/index/RefineGTE.java index 766d11562e2..3f956a17f3e 100644 --- a/checker/tests/index/RefineGTE.java +++ b/checker/tests/index/RefineGTE.java @@ -2,52 +2,52 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineGTE { - int[] arr = {1}; - - void testLTL(@LTLengthOf("arr") int test) { - // The reason for the parsing is so that the Value Checker - // can't figure it out but normal humans can. - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test >= b) { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (a >= b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } + int[] arr = {1}; + + void testLTL(@LTLengthOf("arr") int test) { + // The reason for the parsing is so that the Value Checker + // can't figure it out but normal humans can. + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test >= b) { + @LTLengthOf("arr") int c = b; } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (a >= b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } + } + + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test >= b) { - @LTEqLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int c1 = b; - - if (a >= b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; - } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test >= b) { + @LTEqLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int c1 = b; + + if (a >= b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; } + } } diff --git a/checker/tests/index/RefineLT.java b/checker/tests/index/RefineLT.java index 8412cd7fbfc..b45c9fa395f 100644 --- a/checker/tests/index/RefineLT.java +++ b/checker/tests/index/RefineLT.java @@ -2,43 +2,43 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineLT { - int[] arr = {1}; + int[] arr = {1}; - void testLTL(@LTLengthOf("arr") int test, @LTLengthOf("arr") int a, @LTLengthOf("arr") int a3) { - int b = 2; - if (b < test) { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; + void testLTL(@LTLengthOf("arr") int test, @LTLengthOf("arr") int a, @LTLengthOf("arr") int a3) { + int b = 2; + if (b < test) { + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; - if (b < a3) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } + if (b < a3) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; } + } - void testLTEL(@LTLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + void testLTEL(@LTLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - int b = 2; - if (b < test) { - @LTEqLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int c1 = b; + int b = 2; + if (b < test) { + @LTEqLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int c1 = b; - if (b < a) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; - } + if (b < a) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; } + } } diff --git a/checker/tests/index/RefineLTE.java b/checker/tests/index/RefineLTE.java index bc0056aa3c8..50ea7c27cbc 100644 --- a/checker/tests/index/RefineLTE.java +++ b/checker/tests/index/RefineLTE.java @@ -2,52 +2,52 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineLTE { - int[] arr = {1}; - - void testLTL(@LTLengthOf("arr") int test) { - // The reason for the parsing is so that the Value Checker - // can't figure it out but normal humans can. - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (b <= test) { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (b <= a) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } + int[] arr = {1}; + + void testLTL(@LTLengthOf("arr") int test) { + // The reason for the parsing is so that the Value Checker + // can't figure it out but normal humans can. + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (b <= test) { + @LTLengthOf("arr") int c = b; } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (b <= a) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } + } + + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (b <= test) { - @LTEqLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (b <= a) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (b <= test) { + @LTEqLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (b <= a) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; } + } } diff --git a/checker/tests/index/RefineLTE2.java b/checker/tests/index/RefineLTE2.java index cdbcee45315..435b24e9bf3 100644 --- a/checker/tests/index/RefineLTE2.java +++ b/checker/tests/index/RefineLTE2.java @@ -7,18 +7,18 @@ @SuppressWarnings("lowerbound") public class RefineLTE2 { - protected int @MinLen(1) [] values; + protected int @MinLen(1) [] values; - @LTEqLengthOf("values") int num_values; + @LTEqLengthOf("values") int num_values; - public void add(int elt) { - if (num_values == values.length) { - values = null; - // :: error: (unary.increment.type.incompatible) - num_values++; - return; - } - values[num_values] = elt; - num_values++; + public void add(int elt) { + if (num_values == values.length) { + values = null; + // :: error: (unary.increment.type.incompatible) + num_values++; + return; } + values[num_values] = elt; + num_values++; + } } diff --git a/checker/tests/index/RefineNeq.java b/checker/tests/index/RefineNeq.java index 55327d513db..9c138aa49b0 100644 --- a/checker/tests/index/RefineNeq.java +++ b/checker/tests/index/RefineNeq.java @@ -2,39 +2,39 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineNeq { - int[] arr = {1}; + int[] arr = {1}; - void testLTL(@LTLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); + void testLTL(@LTLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); - int b = 1; - if (test != b) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int e = b; + int b = 1; + if (test != b) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int e = b; - } else { + } else { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; + @LTLengthOf("arr") int c = b; } - - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - - int b = 1; - if (test != b) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int e = b; - } else { - @LTEqLengthOf("arr") int c = b; - - @LTLengthOf("arr") int g = b; - } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } + + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + + int b = 1; + if (test != b) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int e = b; + } else { + @LTEqLengthOf("arr") int c = b; + + @LTLengthOf("arr") int g = b; } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; + } } diff --git a/checker/tests/index/RefineNeqLength.java b/checker/tests/index/RefineNeqLength.java index d11d3ce2b8d..7f3c54971c8 100644 --- a/checker/tests/index/RefineNeqLength.java +++ b/checker/tests/index/RefineNeqLength.java @@ -9,73 +9,73 @@ import org.checkerframework.common.value.qual.IntVal; public class RefineNeqLength { - void refineNeqLength(int[] array, @IndexOrHigh("#1") int i) { - // Refines i <= array.length to i < array.length - if (i != array.length) { - refineNeqLengthMOne(array, i); - } - // No refinement - if (i != array.length - 1) { - // :: error: (argument.type.incompatible) - refineNeqLengthMOne(array, i); - } + void refineNeqLength(int[] array, @IndexOrHigh("#1") int i) { + // Refines i <= array.length to i < array.length + if (i != array.length) { + refineNeqLengthMOne(array, i); } + // No refinement + if (i != array.length - 1) { + // :: error: (argument.type.incompatible) + refineNeqLengthMOne(array, i); + } + } - void refineNeqLengthMOne(int[] array, @IndexFor("#1") int i) { - // Refines i < array.length to i < array.length - 1 - if (i != array.length - 1) { - refineNeqLengthMTwo(array, i); - // :: error: (argument.type.incompatible) - refineNeqLengthMThree(array, i); - } + void refineNeqLengthMOne(int[] array, @IndexFor("#1") int i) { + // Refines i < array.length to i < array.length - 1 + if (i != array.length - 1) { + refineNeqLengthMTwo(array, i); + // :: error: (argument.type.incompatible) + refineNeqLengthMThree(array, i); } + } - void refineNeqLengthMTwo(int[] array, @NonNegative @LTOMLengthOf("#1") int i) { - // Refines i < array.length - 1 to i < array.length - 2 - if (i != array.length - 2) { - refineNeqLengthMThree(array, i); - } - // No refinement - if (i != array.length - 1) { - // :: error: (argument.type.incompatible) - refineNeqLengthMThree(array, i); - } + void refineNeqLengthMTwo(int[] array, @NonNegative @LTOMLengthOf("#1") int i) { + // Refines i < array.length - 1 to i < array.length - 2 + if (i != array.length - 2) { + refineNeqLengthMThree(array, i); + } + // No refinement + if (i != array.length - 1) { + // :: error: (argument.type.incompatible) + refineNeqLengthMThree(array, i); } + } - void refineNeqLengthMTwoNonLiteral( - int[] array, - @NonNegative @LTOMLengthOf("#1") int i, - @IntVal(3) int c3, - @IntVal({2, 3}) int c23) { - // Refines i < array.length - 1 to i < array.length - 2 - if (i != array.length - (5 - c3)) { - refineNeqLengthMThree(array, i); - } - // No refinement - if (i != array.length - c23) { - // :: error: (argument.type.incompatible) - refineNeqLengthMThree(array, i); - } + void refineNeqLengthMTwoNonLiteral( + int[] array, + @NonNegative @LTOMLengthOf("#1") int i, + @IntVal(3) int c3, + @IntVal({2, 3}) int c23) { + // Refines i < array.length - 1 to i < array.length - 2 + if (i != array.length - (5 - c3)) { + refineNeqLengthMThree(array, i); + } + // No refinement + if (i != array.length - c23) { + // :: error: (argument.type.incompatible) + refineNeqLengthMThree(array, i); } + } - @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( - int[] array, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { - // Refines i < array.length - 2 to i < array.length - 3 - if (i != array.length - 3) { - return i; - } - // :: error: (return.type.incompatible) - return i; + @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( + int[] array, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { + // Refines i < array.length - 2 to i < array.length - 3 + if (i != array.length - 3) { + return i; } + // :: error: (return.type.incompatible) + return i; + } - // The same test for a string. - @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( - String str, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { - // Refines i < str.length() - 2 to i < str.length() - 3 - if (i != str.length() - 3) { - return i; - } - // :: error: (return.type.incompatible) - return i; + // The same test for a string. + @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( + String str, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { + // Refines i < str.length() - 2 to i < str.length() - 3 + if (i != str.length() - 3) { + return i; } + // :: error: (return.type.incompatible) + return i; + } } diff --git a/checker/tests/index/RefineSubtrahend.java b/checker/tests/index/RefineSubtrahend.java index 363878f8418..ded8a7ef890 100644 --- a/checker/tests/index/RefineSubtrahend.java +++ b/checker/tests/index/RefineSubtrahend.java @@ -4,49 +4,49 @@ import org.checkerframework.checker.index.qual.NonNegative; public class RefineSubtrahend { - void withConstant(int[] a, @NonNegative int l) { - if (a.length - l > 10) { - int x = a[l + 10]; - } - if (a.length - 10 > l) { - int x = a[l + 10]; - } - if (a.length - l >= 10) { - // :: error: (array.access.unsafe.high) - int x = a[l + 10]; - int x1 = a[l + 9]; - } + void withConstant(int[] a, @NonNegative int l) { + if (a.length - l > 10) { + int x = a[l + 10]; } + if (a.length - 10 > l) { + int x = a[l + 10]; + } + if (a.length - l >= 10) { + // :: error: (array.access.unsafe.high) + int x = a[l + 10]; + int x1 = a[l + 9]; + } + } - void withVariable(int[] a, @NonNegative int l, @NonNegative int j, @NonNegative int k) { - if (a.length - l > j) { - if (k <= j) { - int x = a[l + k]; - } - } - if (a.length - j > l) { - if (k <= j) { - int x = a[l + k]; - } - } - if (a.length - j >= l) { - if (k <= j) { - // :: error: (array.access.unsafe.high) - int x = a[l + k]; - // :: error: (array.access.unsafe.low) - int x1 = a[l + k - 1]; - } - } + void withVariable(int[] a, @NonNegative int l, @NonNegative int j, @NonNegative int k) { + if (a.length - l > j) { + if (k <= j) { + int x = a[l + k]; + } + } + if (a.length - j > l) { + if (k <= j) { + int x = a[l + k]; + } + } + if (a.length - j >= l) { + if (k <= j) { + // :: error: (array.access.unsafe.high) + int x = a[l + k]; + // :: error: (array.access.unsafe.low) + int x1 = a[l + k - 1]; + } } + } - void cases(int[] a, @NonNegative int l) { - switch (a.length - l) { - case 1: - int x = a[l]; - break; - case 2: - int y = a[l + 1]; - break; - } + void cases(int[] a, @NonNegative int l) { + switch (a.length - l) { + case 1: + int x = a[l]; + break; + case 2: + int y = a[l + 1]; + break; } + } } diff --git a/checker/tests/index/RefinementEq.java b/checker/tests/index/RefinementEq.java index 9ae58aa43fb..3b96ef4b525 100644 --- a/checker/tests/index/RefinementEq.java +++ b/checker/tests/index/RefinementEq.java @@ -4,28 +4,28 @@ public class RefinementEq { - void test_equal(int a, int j, int s) { + void test_equal(int a, int j, int s) { - if (-1 == a) { - @GTENegativeOne int b = a; - } else { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int c = a; - } + if (-1 == a) { + @GTENegativeOne int b = a; + } else { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int c = a; + } - if (0 == j) { - @NonNegative int k = j; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int l = j; - } + if (0 == j) { + @NonNegative int k = j; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int l = j; + } - if (1 == s) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; - } + if (1 == s) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; } + } } // a comment diff --git a/checker/tests/index/RefinementGT.java b/checker/tests/index/RefinementGT.java index f5704fbcdd1..086c3d1575e 100644 --- a/checker/tests/index/RefinementGT.java +++ b/checker/tests/index/RefinementGT.java @@ -4,58 +4,58 @@ public class RefinementGT { - void test_forward(int a, int j, int s) { - /** forwards greater than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (a > -1) { - /** a is NN now */ - @NonNegative int b = a; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (a > -1) { + /** a is NN now */ + @NonNegative int b = a; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int c = a; + } - if (j > 0) { - /** j is POS now */ - @Positive int k = j; - } else { - // :: error: (assignment.type.incompatible) - @Positive int l = j; - } + if (j > 0) { + /** j is POS now */ + @Positive int k = j; + } else { + // :: error: (assignment.type.incompatible) + @Positive int l = j; + } - if (s > 1) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; - } + if (s > 1) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; } + } - void test_backwards(int a, int j, int s) { - /** backwards greater than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 > a) { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int b = a; - } else { - @GTENegativeOne int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 > a) { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int b = a; + } else { + @GTENegativeOne int c = a; + } - if (0 > j) { - // :: error: (assignment.type.incompatible) - @NonNegative int k = j; - } else { - @NonNegative int l = j; - } + if (0 > j) { + // :: error: (assignment.type.incompatible) + @NonNegative int k = j; + } else { + @NonNegative int l = j; + } - if (1 > s) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; - } + if (1 > s) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; } + } } // a comment diff --git a/checker/tests/index/RefinementGTE.java b/checker/tests/index/RefinementGTE.java index a929386ffb3..7ce66a80578 100644 --- a/checker/tests/index/RefinementGTE.java +++ b/checker/tests/index/RefinementGTE.java @@ -4,49 +4,49 @@ public class RefinementGTE { - void test_forward(int a, int j, int s) { - /** forwards greater than or equals */ - // :: error: (assignment.type.incompatible) - @GTENegativeOne int aa = a; - if (a >= -1) { - @GTENegativeOne int b = a; - } else { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than or equals */ + // :: error: (assignment.type.incompatible) + @GTENegativeOne int aa = a; + if (a >= -1) { + @GTENegativeOne int b = a; + } else { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int c = a; + } - if (j >= 0) { - @NonNegative int k = j; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int l = j; - } + if (j >= 0) { + @NonNegative int k = j; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int l = j; } + } - void test_backwards(int a, int j, int s) { - /** backwards greater than or equal */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 >= a) { - // :: error: (assignment.type.incompatible) - @NonNegative int b = a; - } else { - @NonNegative int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than or equal */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 >= a) { + // :: error: (assignment.type.incompatible) + @NonNegative int b = a; + } else { + @NonNegative int c = a; + } - if (0 >= j) { - // :: error: (assignment.type.incompatible) - @Positive int k = j; - } else { - @Positive int l = j; - } + if (0 >= j) { + // :: error: (assignment.type.incompatible) + @Positive int k = j; + } else { + @Positive int l = j; + } - if (1 >= s) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; - } + if (1 >= s) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; } + } } // a comment diff --git a/checker/tests/index/RefinementLT.java b/checker/tests/index/RefinementLT.java index 388863c837b..6cf7b75c7b8 100644 --- a/checker/tests/index/RefinementLT.java +++ b/checker/tests/index/RefinementLT.java @@ -4,56 +4,56 @@ public class RefinementLT { - void test_backwards(int a, int j, int s) { - /** backwards less than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 < a) { - @NonNegative int b = a; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 < a) { + @NonNegative int b = a; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int c = a; + } - if (0 < j) { - @Positive int k = j; - } else { - // :: error: (assignment.type.incompatible) - @Positive int l = j; - } + if (0 < j) { + @Positive int k = j; + } else { + // :: error: (assignment.type.incompatible) + @Positive int l = j; + } - if (1 < s) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; - } + if (1 < s) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; } + } - void test_forwards(int a, int j, int s) { - /** forwards less than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (a < -1) { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int b = a; - } else { - @GTENegativeOne int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (a < -1) { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int b = a; + } else { + @GTENegativeOne int c = a; + } - if (j < 0) { - // :: error: (assignment.type.incompatible) - @NonNegative int k = j; - } else { - @NonNegative int l = j; - } + if (j < 0) { + // :: error: (assignment.type.incompatible) + @NonNegative int k = j; + } else { + @NonNegative int l = j; + } - if (s < 1) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; - } + if (s < 1) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; } + } } // a comment diff --git a/checker/tests/index/RefinementLTE.java b/checker/tests/index/RefinementLTE.java index abe2342c4d2..48c4c148d80 100644 --- a/checker/tests/index/RefinementLTE.java +++ b/checker/tests/index/RefinementLTE.java @@ -4,56 +4,56 @@ public class RefinementLTE { - void test_backwards(int a, int j, int s) { - /** backwards less than or equals */ - // :: error: (assignment.type.incompatible) - @GTENegativeOne int aa = a; - if (-1 <= a) { - @GTENegativeOne int b = a; - } else { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than or equals */ + // :: error: (assignment.type.incompatible) + @GTENegativeOne int aa = a; + if (-1 <= a) { + @GTENegativeOne int b = a; + } else { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int c = a; + } - if (0 <= j) { - @NonNegative int k = j; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int l = j; - } + if (0 <= j) { + @NonNegative int k = j; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int l = j; + } - if (1 <= s) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; - } + if (1 <= s) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; } + } - void test_forwards(int a, int j, int s) { - /** forwards less than or equal */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (a <= -1) { - // :: error: (assignment.type.incompatible) - @NonNegative int b = a; - } else { - @NonNegative int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than or equal */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (a <= -1) { + // :: error: (assignment.type.incompatible) + @NonNegative int b = a; + } else { + @NonNegative int c = a; + } - if (j <= 0) { - // :: error: (assignment.type.incompatible) - @Positive int k = j; - } else { - @Positive int l = j; - } + if (j <= 0) { + // :: error: (assignment.type.incompatible) + @Positive int k = j; + } else { + @Positive int l = j; + } - if (s <= 1) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; - } + if (s <= 1) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; } + } } // a comment diff --git a/checker/tests/index/RefinementNEq.java b/checker/tests/index/RefinementNEq.java index 7963bd6f535..80b3e6c271e 100644 --- a/checker/tests/index/RefinementNEq.java +++ b/checker/tests/index/RefinementNEq.java @@ -4,30 +4,30 @@ public class RefinementNEq { - void test_not_equal(int a, int j, int s) { + void test_not_equal(int a, int j, int s) { - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 != a) { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int b = a; - } else { - @GTENegativeOne int c = a; - } + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 != a) { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int b = a; + } else { + @GTENegativeOne int c = a; + } - if (0 != j) { - // :: error: (assignment.type.incompatible) - @NonNegative int k = j; - } else { - @NonNegative int l = j; - } + if (0 != j) { + // :: error: (assignment.type.incompatible) + @NonNegative int k = j; + } else { + @NonNegative int l = j; + } - if (1 != s) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; - } + if (1 != s) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; } + } } // a comment diff --git a/checker/tests/index/ReflectArray.java b/checker/tests/index/ReflectArray.java index a974f08d154..126d836a744 100644 --- a/checker/tests/index/ReflectArray.java +++ b/checker/tests/index/ReflectArray.java @@ -1,26 +1,25 @@ -import org.checkerframework.common.value.qual.MinLen; - import java.lang.reflect.Array; +import org.checkerframework.common.value.qual.MinLen; public class ReflectArray { - void testNewInstance(int i) { - // :: error: (argument.type.incompatible) - Array.newInstance(Object.class, i); - if (i >= 0) { - Array.newInstance(Object.class, i); - } + void testNewInstance(int i) { + // :: error: (argument.type.incompatible) + Array.newInstance(Object.class, i); + if (i >= 0) { + Array.newInstance(Object.class, i); } + } - void testFor(Object a) { - for (int i = 0; i < Array.getLength(a); ++i) { - Array.setInt(a, i, 1 + Array.getInt(a, i)); - } + void testFor(Object a) { + for (int i = 0; i < Array.getLength(a); ++i) { + Array.setInt(a, i, 1 + Array.getInt(a, i)); } + } - void testMinLen(Object @MinLen(1) [] a) { - Array.get(a, 0); - // :: error: (argument.type.incompatible) - Array.get(a, 1); - } + void testMinLen(Object @MinLen(1) [] a) { + Array.get(a, 0); + // :: error: (argument.type.incompatible) + Array.get(a, 1); + } } diff --git a/checker/tests/index/RegexMatcher.java b/checker/tests/index/RegexMatcher.java index fae3bcc7745..cd3911910d7 100644 --- a/checker/tests/index/RegexMatcher.java +++ b/checker/tests/index/RegexMatcher.java @@ -1,25 +1,24 @@ // Test case for Issue panacekcz#8: // https://github.com/panacekcz/checker-framework/issues/8 -import org.checkerframework.checker.index.qual.NonNegative; - import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.index.qual.NonNegative; public class RegexMatcher { - static void m(String p, String s) { - Matcher matcher = Pattern.compile(p).matcher(s); - // The following line cannot be used as a test, because the relation of matcher to p is not - // tracked, so the upper bound is not known. + static void m(String p, String s) { + Matcher matcher = Pattern.compile(p).matcher(s); + // The following line cannot be used as a test, because the relation of matcher to p is not + // tracked, so the upper bound is not known. - // s.substring(matcher.start(), matcher.end()); + // s.substring(matcher.start(), matcher.end()); - @NonNegative int i; - i = matcher.start(); - i = matcher.end(); - // :: error: (assignment.type.incompatible) - i = matcher.start(1); - // :: error: (assignment.type.incompatible) - i = matcher.end(1); - } + @NonNegative int i; + i = matcher.start(); + i = matcher.end(); + // :: error: (assignment.type.incompatible) + i = matcher.start(1); + // :: error: (assignment.type.incompatible) + i = matcher.end(1); + } } diff --git a/checker/tests/index/RepeatLTLengthOf.java b/checker/tests/index/RepeatLTLengthOf.java index 14565c74034..03307ecb5b6 100644 --- a/checker/tests/index/RepeatLTLengthOf.java +++ b/checker/tests/index/RepeatLTLengthOf.java @@ -3,105 +3,97 @@ public class RepeatLTLengthOf { - protected String value1; - protected String value2; - protected String value3; - protected int v1; - protected int v2; - protected int v3; + protected String value1; + protected String value2; + protected String value3; + protected int v1; + protected int v2; + protected int v3; - public void func1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + public void func1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - public boolean func2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } + public boolean func2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void client1() { + withpostconditionsfunc1(); + } + + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean client2() { + return withcondpostconditionsfunc2(); + } + + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void client1() { - withpostconditionsfunc1(); - } + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void client3() { + withpostconditionfunc1(); + } - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true), @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean client2() { - return withcondpostconditionsfunc2(); - } + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean client4() { + return withcondpostconditionfunc2(); + } - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void client3() { - withpostconditionfunc1(); - } + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void withpostconditionsfunc1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf( - expression = "v1", - targetValue = "value1", - offset = "2", - result = true), - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean client4() { - return withcondpostconditionfunc2(); - } + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean withcondpostconditionsfunc2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void withpostconditionsfunc1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void withpostconditionfunc1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true), @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean withcondpostconditionsfunc2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } - - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void withpostconditionfunc1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } - - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf( - expression = "v1", - targetValue = "value1", - offset = "2", - result = true), - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean withcondpostconditionfunc2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean withcondpostconditionfunc2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } } diff --git a/checker/tests/index/RepeatLTLengthOfWithError.java b/checker/tests/index/RepeatLTLengthOfWithError.java index fd8b63a79a2..6eb05c17329 100644 --- a/checker/tests/index/RepeatLTLengthOfWithError.java +++ b/checker/tests/index/RepeatLTLengthOfWithError.java @@ -3,109 +3,101 @@ public class RepeatLTLengthOfWithError { - protected String value1; - protected String value2; - protected String value3; - protected int v1; - protected int v2; - protected int v3; + protected String value1; + protected String value2; + protected String value3; + protected int v1; + protected int v2; + protected int v3; - public void func1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + public void func1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - public boolean func2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } + public boolean func2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + public void client1() { + withpostconditionsfunc1(); + } + + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean client2() { + return withcondpostconditionsfunc2(); + } + + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - public void client1() { - withpostconditionsfunc1(); - } + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + public void client3() { + withpostconditionfunc1(); + } - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true), @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean client2() { - return withcondpostconditionsfunc2(); - } + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean client4() { + return withcondpostconditionfunc2(); + } - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - public void client3() { - withpostconditionfunc1(); - } + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionsfunc1() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf( - expression = "v1", - targetValue = "value1", - offset = "3", - result = true), - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean client4() { - return withcondpostconditionfunc2(); - } + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean withcondpostconditionsfunc2() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionsfunc1() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionfunc1() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true), @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean withcondpostconditionsfunc2() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionfunc1() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } - - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf( - expression = "v1", - targetValue = "value1", - offset = "3", - result = true), - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean withcondpostconditionfunc2() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean withcondpostconditionfunc2() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/index/Return.java b/checker/tests/index/Return.java index 008776c851a..71df9276000 100644 --- a/checker/tests/index/Return.java +++ b/checker/tests/index/Return.java @@ -1,5 +1,5 @@ public class Return { - int[] test() { - return null; - } + int[] test() { + return null; + } } diff --git a/checker/tests/index/SLSubtyping.java b/checker/tests/index/SLSubtyping.java index c99695bd776..848a546b76b 100644 --- a/checker/tests/index/SLSubtyping.java +++ b/checker/tests/index/SLSubtyping.java @@ -3,22 +3,22 @@ // This test checks whether the SameLen type system works as expected. public class SLSubtyping { - int[] f = {1}; + int[] f = {1}; - void subtype(int @SameLen("#2") [] a, int[] b) { - int @SameLen({"a", "b"}) [] c = a; + void subtype(int @SameLen("#2") [] a, int[] b) { + int @SameLen({"a", "b"}) [] c = a; - // :: error: (assignment.type.incompatible) - int @SameLen("c") [] q = {1, 2}; - int @SameLen("c") [] d = q; + // :: error: (assignment.type.incompatible) + int @SameLen("c") [] q = {1, 2}; + int @SameLen("c") [] d = q; - // :: error: (assignment.type.incompatible) - int @SameLen("f") [] e = a; - } + // :: error: (assignment.type.incompatible) + int @SameLen("f") [] e = a; + } - void subtype2(int[] a, int @SameLen("#1") [] b) { - a = b; - int @SameLen("b") [] c = b; - int @SameLen("f") [] d = f; - } + void subtype2(int[] a, int @SameLen("#1") [] b) { + a = b; + int @SameLen("b") [] c = b; + int @SameLen("f") [] d = f; + } } diff --git a/checker/tests/index/SameLenAssignmentTransfer.java b/checker/tests/index/SameLenAssignmentTransfer.java index f1fe52fccb4..850ed85bb91 100644 --- a/checker/tests/index/SameLenAssignmentTransfer.java +++ b/checker/tests/index/SameLenAssignmentTransfer.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.index.qual.*; public class SameLenAssignmentTransfer { - void transfer5(int @SameLen("#2") [] a, int[] b) { - int[] c = a; - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; - } + void transfer5(int @SameLen("#2") [] a, int[] b) { + int[] c = a; + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; } + } } diff --git a/checker/tests/index/SameLenEqualsRefinement.java b/checker/tests/index/SameLenEqualsRefinement.java index 6af7a52c528..a19dd6d3eec 100644 --- a/checker/tests/index/SameLenEqualsRefinement.java +++ b/checker/tests/index/SameLenEqualsRefinement.java @@ -2,41 +2,41 @@ import org.checkerframework.dataflow.qual.Pure; public class SameLenEqualsRefinement { - void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { - if (a == c) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; - } - } + void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { + if (a == c) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; + } } + } - void transfer4(int[] a, int[] b, int[] c) { - if (b == c) { - if (a == b) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - a[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; - } - } + void transfer4(int[] a, int[] b, int[] c) { + if (b == c) { + if (a == b) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + a[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; } + } } + } - void transfer5(int[] a, int[] b, int[] c, int[] d) { - if (a == b && b == c) { - int[] x = a; - int[] y = x; - int index = x.length - 1; - if (index > 0) { - f(a[index]); - f(b[index]); - f(c[index]); - f(x[index]); - f(y[index]); - } - } + void transfer5(int[] a, int[] b, int[] c, int[] d) { + if (a == b && b == c) { + int[] x = a; + int[] y = x; + int index = x.length - 1; + if (index > 0) { + f(a[index]); + f(b[index]); + f(c[index]); + f(x[index]); + f(y[index]); + } } + } - @Pure - void f(Object o) {} + @Pure + void f(Object o) {} } diff --git a/checker/tests/index/SameLenFormalParameter2.java b/checker/tests/index/SameLenFormalParameter2.java index 4e9d881c66e..1c4b333ef09 100644 --- a/checker/tests/index/SameLenFormalParameter2.java +++ b/checker/tests/index/SameLenFormalParameter2.java @@ -2,10 +2,10 @@ public class SameLenFormalParameter2 { - void lib(Object @SameLen({"#1", "#2"}) [] valsArg, int @SameLen({"#1", "#2"}) [] modsArg) {} + void lib(Object @SameLen({"#1", "#2"}) [] valsArg, int @SameLen({"#1", "#2"}) [] modsArg) {} - void client(Object[] myvals, int[] mymods) { - // :: error: (argument.type.incompatible) - lib(myvals, mymods); - } + void client(Object[] myvals, int[] mymods) { + // :: error: (argument.type.incompatible) + lib(myvals, mymods); + } } diff --git a/checker/tests/index/SameLenIrrelevant.java b/checker/tests/index/SameLenIrrelevant.java index 103f92aabda..c59ceefe46a 100644 --- a/checker/tests/index/SameLenIrrelevant.java +++ b/checker/tests/index/SameLenIrrelevant.java @@ -8,18 +8,18 @@ import org.checkerframework.checker.index.qual.SameLen; public class SameLenIrrelevant { - // NO :: error: (anno.on.irrelevant) - public void test(@SameLen("#2") int x, int y) { - // do nothing - } + // NO :: error: (anno.on.irrelevant) + public void test(@SameLen("#2") int x, int y) { + // do nothing + } - // NO :: error: (anno.on.irrelevant) - public void test(@SameLen("#2") double x, double y) { - // do nothing - } + // NO :: error: (anno.on.irrelevant) + public void test(@SameLen("#2") double x, double y) { + // do nothing + } - // NO :: error: (anno.on.irrelevant) - public void test(@SameLen("#2") char x, char y) { - // do nothing - } + // NO :: error: (anno.on.irrelevant) + public void test(@SameLen("#2") char x, char y) { + // do nothing + } } diff --git a/checker/tests/index/SameLenLUBStrangeness.java b/checker/tests/index/SameLenLUBStrangeness.java index 4d432f134e3..d258ca2ca1d 100644 --- a/checker/tests/index/SameLenLUBStrangeness.java +++ b/checker/tests/index/SameLenLUBStrangeness.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.index.qual.SameLen; public class SameLenLUBStrangeness { - void test(int[] a, boolean cond) { - int[] b; - if (cond) { - b = a; - } - // :: error: (assignment.type.incompatible) - int @SameLen({"a", "b"}) [] c = a; + void test(int[] a, boolean cond) { + int[] b; + if (cond) { + b = a; } + // :: error: (assignment.type.incompatible) + int @SameLen({"a", "b"}) [] c = a; + } } diff --git a/checker/tests/index/SameLenManyArrays.java b/checker/tests/index/SameLenManyArrays.java index 0e0c519c36b..db502c46ff1 100644 --- a/checker/tests/index/SameLenManyArrays.java +++ b/checker/tests/index/SameLenManyArrays.java @@ -2,55 +2,55 @@ import org.checkerframework.dataflow.qual.Pure; public class SameLenManyArrays { - void transfer1(int @SameLen("#2") [] a, int[] b) { - int[] c = new int[a.length]; - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; - } + void transfer1(int @SameLen("#2") [] a, int[] b) { + int[] c = new int[a.length]; + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; } + } - void transfer2(int @SameLen("#2") [] a, int[] b) { - for (int i = 0; i < b.length; i++) { // i's type is @LTL("b") - a[i] = 1; - } + void transfer2(int @SameLen("#2") [] a, int[] b) { + for (int i = 0; i < b.length; i++) { // i's type is @LTL("b") + a[i] = 1; } + } - void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { - if (a.length == c.length) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; - } - } + void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { + if (a.length == c.length) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; + } } + } - void transfer4(int[] a, int[] b, int[] c) { - if (b.length == c.length) { - if (a.length == b.length) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - a[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; - } - } + void transfer4(int[] a, int[] b, int[] c) { + if (b.length == c.length) { + if (a.length == b.length) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + a[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; } + } } + } - void transfer5(int[] a, int[] b, int[] c, int[] d) { - if (a.length == b.length && b.length == c.length) { - int[] x = a; - int[] y = x; - int index = x.length - 1; - if (index > 0) { - f(a[index]); - f(b[index]); - f(c[index]); - f(x[index]); - f(y[index]); - } - } + void transfer5(int[] a, int[] b, int[] c, int[] d) { + if (a.length == b.length && b.length == c.length) { + int[] x = a; + int[] y = x; + int index = x.length - 1; + if (index > 0) { + f(a[index]); + f(b[index]); + f(c[index]); + f(x[index]); + f(y[index]); + } } + } - @Pure - void f(Object o) {} + @Pure + void f(Object o) {} } diff --git a/checker/tests/index/SameLenNewArrayWithSameLength.java b/checker/tests/index/SameLenNewArrayWithSameLength.java index e4bd9654746..346143121a0 100644 --- a/checker/tests/index/SameLenNewArrayWithSameLength.java +++ b/checker/tests/index/SameLenNewArrayWithSameLength.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.index.qual.*; public class SameLenNewArrayWithSameLength { - public void m1(int[] a) { - int @SameLen("a") [] b = new int[a.length]; - } + public void m1(int[] a) { + int @SameLen("a") [] b = new int[a.length]; + } - public void m2(int[] a, int @SameLen("#1") [] b) { - int @SameLen({"a", "b"}) [] c = new int[b.length]; - } + public void m2(int[] a, int @SameLen("#1") [] b) { + int @SameLen({"a", "b"}) [] c = new int[b.length]; + } } diff --git a/checker/tests/index/SameLenOnFormalParameter.java b/checker/tests/index/SameLenOnFormalParameter.java index b8bb360cdf9..bef9d7dd0d9 100644 --- a/checker/tests/index/SameLenOnFormalParameter.java +++ b/checker/tests/index/SameLenOnFormalParameter.java @@ -3,26 +3,26 @@ import org.checkerframework.checker.index.qual.*; public class SameLenOnFormalParameter { - public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} + public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} - public void requiresSameLen2(@SameLen("#2") String x2, String y2) {} + public void requiresSameLen2(@SameLen("#2") String x2, String y2) {} - public void m1(@SameLen("#2") String a1, String b1) { - requiresSameLen1(a1, b1); - } + public void m1(@SameLen("#2") String a1, String b1) { + requiresSameLen1(a1, b1); + } - public void m2(@SameLen("#2") String a2, String b2) { - @SameLen("a2") String b22 = b2; - requiresSameLen1(a2, b22); - } + public void m2(@SameLen("#2") String a2, String b2) { + @SameLen("a2") String b22 = b2; + requiresSameLen1(a2, b22); + } - public void m3(@SameLen("#2") String a3, String b3) { - @SameLen("b3") String a2 = a3; - @SameLen("a3") String b32 = b3; - requiresSameLen1(a3, b32); - } + public void m3(@SameLen("#2") String a3, String b3) { + @SameLen("b3") String a2 = a3; + @SameLen("a3") String b32 = b3; + requiresSameLen1(a3, b32); + } - public void m4(@SameLen("#2") String a4, String b4) { - requiresSameLen2(a4, b4); - } + public void m4(@SameLen("#2") String a4, String b4) { + requiresSameLen2(a4, b4); + } } diff --git a/checker/tests/index/SameLenOnFormalParameterSimple.java b/checker/tests/index/SameLenOnFormalParameterSimple.java index 53e11cd1ae1..a067e975fd5 100644 --- a/checker/tests/index/SameLenOnFormalParameterSimple.java +++ b/checker/tests/index/SameLenOnFormalParameterSimple.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.index.qual.SameLen; public class SameLenOnFormalParameterSimple { - public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} + public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} - public void m1(@SameLen("#2") String a1, String b1) { - requiresSameLen1(a1, b1); - } + public void m1(@SameLen("#2") String a1, String b1) { + requiresSameLen1(a1, b1); + } } diff --git a/checker/tests/index/SameLenSelf.java b/checker/tests/index/SameLenSelf.java index f4740b26d37..1eb337164d9 100644 --- a/checker/tests/index/SameLenSelf.java +++ b/checker/tests/index/SameLenSelf.java @@ -3,12 +3,12 @@ import org.checkerframework.checker.index.qual.*; public class SameLenSelf { - int @SameLen("this.field") [] field = new int[10]; - int @SameLen("field2") [] field2 = new int[10]; - int @SameLen("field3") [] field3 = field2; + int @SameLen("this.field") [] field = new int[10]; + int @SameLen("field2") [] field2 = new int[10]; + int @SameLen("field3") [] field3 = field2; - void foo(int[] b) { - int @SameLen("a") [] a = b; - int @SameLen("c") [] c = new int[10]; - } + void foo(int[] b) { + int @SameLen("a") [] a = b; + int @SameLen("c") [] c = new int[10]; + } } diff --git a/checker/tests/index/SameLenSimpleCase.java b/checker/tests/index/SameLenSimpleCase.java index dceaf6dc163..eef3a5c6ded 100644 --- a/checker/tests/index/SameLenSimpleCase.java +++ b/checker/tests/index/SameLenSimpleCase.java @@ -1,13 +1,13 @@ public class SameLenSimpleCase { - public int compare(int[] a1, int[] a2) { - if (a1.length != a2.length) { - return a1.length - a2.length; - } - for (int i = 0; i < a1.length; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); - } - } - return 0; + public int compare(int[] a1, int[] a2) { + if (a1.length != a2.length) { + return a1.length - a2.length; } + for (int i = 0; i < a1.length; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); + } + } + return 0; + } } diff --git a/checker/tests/index/SameLenTripleThreat.java b/checker/tests/index/SameLenTripleThreat.java index 4051099b3f7..6bd016a6815 100644 --- a/checker/tests/index/SameLenTripleThreat.java +++ b/checker/tests/index/SameLenTripleThreat.java @@ -1,32 +1,32 @@ import org.checkerframework.checker.index.qual.*; public class SameLenTripleThreat { - public void foo(String[] vars) { - String[] qrets = new String[vars.length]; - String @SameLen("vars") [] y = qrets; - String[] indices = new String[vars.length]; - String @SameLen("qrets") [] x = indices; - } + public void foo(String[] vars) { + String[] qrets = new String[vars.length]; + String @SameLen("vars") [] y = qrets; + String[] indices = new String[vars.length]; + String @SameLen("qrets") [] x = indices; + } - String[] indices; + String[] indices; - public void foo2(String... vars) { - String[] qrets = new String[vars.length]; - indices = new String[vars.length]; - String[] indicesLocal = new String[vars.length]; - for (int i = 0; i < qrets.length; i++) { - indices[i] = "hello"; - indicesLocal[i] = "hello"; - } + public void foo2(String... vars) { + String[] qrets = new String[vars.length]; + indices = new String[vars.length]; + String[] indicesLocal = new String[vars.length]; + for (int i = 0; i < qrets.length; i++) { + indices[i] = "hello"; + indicesLocal[i] = "hello"; } + } - public void foo3(String... vars) { - String[] qrets = new String[vars.length]; - String[] indicesLocal = new String[vars.length]; - indices = new String[vars.length]; - for (int i = 0; i < qrets.length; i++) { - indices[i] = "hello"; - indicesLocal[i] = "hello"; - } + public void foo3(String... vars) { + String[] qrets = new String[vars.length]; + String[] indicesLocal = new String[vars.length]; + indices = new String[vars.length]; + for (int i = 0; i < qrets.length; i++) { + indices[i] = "hello"; + indicesLocal[i] = "hello"; } + } } diff --git a/checker/tests/index/SameLenWithObjects.java b/checker/tests/index/SameLenWithObjects.java index 60165d4cd9d..9b1c96b16ce 100644 --- a/checker/tests/index/SameLenWithObjects.java +++ b/checker/tests/index/SameLenWithObjects.java @@ -2,18 +2,18 @@ public class SameLenWithObjects { - class SimpleCollection { - Object[] var_infos; - } + class SimpleCollection { + Object[] var_infos; + } - static final class Invocation1 { - SimpleCollection sc; - Object @SameLen({"vals1", "this.sc.var_infos"}) [] vals1; + static final class Invocation1 { + SimpleCollection sc; + Object @SameLen({"vals1", "this.sc.var_infos"}) [] vals1; - void format1() { - for (int j = 0; j < vals1.length; j++) { - System.out.println(sc.var_infos[j]); - } - } + void format1() { + for (int j = 0; j < vals1.length; j++) { + System.out.println(sc.var_infos[j]); + } } + } } diff --git a/checker/tests/index/SearchIndexTests.java b/checker/tests/index/SearchIndexTests.java index 9cfce47efa8..4a5766090fe 100644 --- a/checker/tests/index/SearchIndexTests.java +++ b/checker/tests/index/SearchIndexTests.java @@ -1,55 +1,54 @@ -import org.checkerframework.checker.index.qual.*; - import java.util.Arrays; +import org.checkerframework.checker.index.qual.*; public class SearchIndexTests { - public void test(short[] a, short instant) { - int i = Arrays.binarySearch(a, instant); - @SearchIndexFor("a") int z = i; - // :: error: (assignment.type.incompatible) - @SearchIndexFor("a") int y = 7; - @LTLengthOf("a") int x = i; - } + public void test(short[] a, short instant) { + int i = Arrays.binarySearch(a, instant); + @SearchIndexFor("a") int z = i; + // :: error: (assignment.type.incompatible) + @SearchIndexFor("a") int y = 7; + @LTLengthOf("a") int x = i; + } - void test2(int[] a, @SearchIndexFor("#1") int xyz) { - if (0 > xyz) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; - } + void test2(int[] a, @SearchIndexFor("#1") int xyz) { + if (0 > xyz) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; } + } - void test3(int[] a, @SearchIndexFor("#1") int xyz) { - if (-1 >= xyz) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; - } + void test3(int[] a, @SearchIndexFor("#1") int xyz) { + if (-1 >= xyz) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; } + } - void test4(int[] a, @SearchIndexFor("#1") int xyz) { - if (xyz < 0) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; - } + void test4(int[] a, @SearchIndexFor("#1") int xyz) { + if (xyz < 0) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; } + } - void test5(int[] a, @SearchIndexFor("#1") int xyz) { - if (xyz <= -1) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; - } + void test5(int[] a, @SearchIndexFor("#1") int xyz) { + if (xyz <= -1) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; } + } - void subtyping1( - @SearchIndexFor({"#3", "#4"}) int x, @NegativeIndexFor("#3") int y, int[] a, int[] b) { - // :: error: (assignment.type.incompatible) - @SearchIndexFor({"a", "b"}) int z = y; - @SearchIndexFor("a") int w = y; - @SearchIndexFor("b") int p = x; - // :: error: (assignment.type.incompatible) - @NegativeIndexFor({"a", "b"}) int q = x; - } + void subtyping1( + @SearchIndexFor({"#3", "#4"}) int x, @NegativeIndexFor("#3") int y, int[] a, int[] b) { + // :: error: (assignment.type.incompatible) + @SearchIndexFor({"a", "b"}) int z = y; + @SearchIndexFor("a") int w = y; + @SearchIndexFor("b") int p = x; + // :: error: (assignment.type.incompatible) + @NegativeIndexFor({"a", "b"}) int q = x; + } } diff --git a/checker/tests/index/ShiftRight.java b/checker/tests/index/ShiftRight.java index 038b9f6667c..d8aef910948 100644 --- a/checker/tests/index/ShiftRight.java +++ b/checker/tests/index/ShiftRight.java @@ -6,22 +6,22 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class ShiftRight { - void indexFor(Object[] a, @IndexFor("#1") int i) { - @IndexFor("a") int o = i >> 2; - @IndexFor("a") int p = i >>> 2; - } + void indexFor(Object[] a, @IndexFor("#1") int i) { + @IndexFor("a") int o = i >> 2; + @IndexFor("a") int p = i >>> 2; + } - void indexOrHigh(Object[] a, @IndexOrHigh("#1") int i) { - @IndexOrHigh("a") int o = i >> 2; - @IndexOrHigh("a") int p = i >>> 2; - // Not true if a.length == 0 - // :: error: (assignment.type.incompatible) - @IndexFor("a") int q = i >> 2; - } + void indexOrHigh(Object[] a, @IndexOrHigh("#1") int i) { + @IndexOrHigh("a") int o = i >> 2; + @IndexOrHigh("a") int p = i >>> 2; + // Not true if a.length == 0 + // :: error: (assignment.type.incompatible) + @IndexFor("a") int q = i >> 2; + } - void negative(Object[] a, @LTLengthOf(value = "#1", offset = "100") int i) { - // Not true for some negative i - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "#1", offset = "100") int q = i >> 2; - } + void negative(Object[] a, @LTLengthOf(value = "#1", offset = "100") int i) { + // Not true for some negative i + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "#1", offset = "100") int q = i >> 2; + } } diff --git a/checker/tests/index/ShiftRightAverage.java b/checker/tests/index/ShiftRightAverage.java index d78edd8466b..174b2239201 100644 --- a/checker/tests/index/ShiftRightAverage.java +++ b/checker/tests/index/ShiftRightAverage.java @@ -5,12 +5,12 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class ShiftRightAverage { - public static void m(Object[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @IndexFor("#1") int k = (i + j) >> 1; - } + public static void m(Object[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @IndexFor("#1") int k = (i + j) >> 1; + } - public static void m2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int h = ((i + 1) + j) >> 1; - } + public static void m2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int h = ((i + 1) + j) >> 1; + } } diff --git a/checker/tests/index/SimpleCollection.java b/checker/tests/index/SimpleCollection.java index 24a2da1d8ab..e263534502d 100644 --- a/checker/tests/index/SimpleCollection.java +++ b/checker/tests/index/SimpleCollection.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.index.qual.*; public class SimpleCollection { - private int[] values; + private int[] values; - @IndexOrHigh("values") int size() { - return values.length; - } + @IndexOrHigh("values") int size() { + return values.length; + } - void interact_with_other(SimpleCollection other) { - int[] othervalues = other.values; - int @SameLen("other.values") [] x = othervalues; - for (int i = 0; i < other.size(); i++) { - int k = othervalues[i]; - } - for (int j = 0; j < other.size(); j++) { - int k = other.values[j]; - } + void interact_with_other(SimpleCollection other) { + int[] othervalues = other.values; + int @SameLen("other.values") [] x = othervalues; + for (int i = 0; i < other.size(); i++) { + int k = othervalues[i]; + } + for (int j = 0; j < other.size(); j++) { + int k = other.values[j]; } + } } diff --git a/checker/tests/index/SimpleTransferAdd.java b/checker/tests/index/SimpleTransferAdd.java index 38b5a330183..a037c0e45fb 100644 --- a/checker/tests/index/SimpleTransferAdd.java +++ b/checker/tests/index/SimpleTransferAdd.java @@ -2,16 +2,16 @@ import org.checkerframework.checker.index.qual.Positive; public class SimpleTransferAdd { - void test() { - int bs = -1; - // :: error: (assignment.type.incompatible) - @NonNegative int es = bs; + void test() { + int bs = -1; + // :: error: (assignment.type.incompatible) + @NonNegative int es = bs; - // @NonNegative int ds = 2 + bs; - int ds = 0; - // :: error: (assignment.type.incompatible) - @Positive int cs = ds++; - @Positive int fs = ds; - } + // @NonNegative int ds = 2 + bs; + int ds = 0; + // :: error: (assignment.type.incompatible) + @Positive int cs = ds++; + @Positive int fs = ds; + } } // a comment diff --git a/checker/tests/index/SimpleTransferSub.java b/checker/tests/index/SimpleTransferSub.java index 1546726c8e4..96769ae0541 100644 --- a/checker/tests/index/SimpleTransferSub.java +++ b/checker/tests/index/SimpleTransferSub.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.index.qual.Positive; public class SimpleTransferSub { - void test() { - // shows a bug in the Checker Framework. I don't think we can get around this bit... - int bs = 0; - // :: error: (assignment.type.incompatible) - @Positive int ds = bs--; - } + void test() { + // shows a bug in the Checker Framework. I don't think we can get around this bit... + int bs = 0; + // :: error: (assignment.type.incompatible) + @Positive int ds = bs--; + } } // a comment diff --git a/checker/tests/index/SizeVsLength.java b/checker/tests/index/SizeVsLength.java index 00208a61d3d..4459461d718 100644 --- a/checker/tests/index/SizeVsLength.java +++ b/checker/tests/index/SizeVsLength.java @@ -4,11 +4,11 @@ public class SizeVsLength { - public int[] getArray(@NonNegative int size) { - int[] values = new int[size]; - for (int i = 0; i < size; i++) { - values[i] = 22; - } - return values; + public int[] getArray(@NonNegative int size) { + int[] values = new int[size]; + for (int i = 0; i < size; i++) { + values[i] = 22; } + return values; + } } diff --git a/checker/tests/index/SkipBufferedReader.java b/checker/tests/index/SkipBufferedReader.java index 4ab69fd2e8c..c688c921fda 100644 --- a/checker/tests/index/SkipBufferedReader.java +++ b/checker/tests/index/SkipBufferedReader.java @@ -3,12 +3,12 @@ import java.io.InputStreamReader; public class SkipBufferedReader { - public static void method() throws IOException { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + public static void method() throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); - // :: error: (argument.type.incompatible) - bufferedReader.skip(-1); + // :: error: (argument.type.incompatible) + bufferedReader.skip(-1); - bufferedReader.skip(1); - } + bufferedReader.skip(1); + } } diff --git a/checker/tests/index/SpecialTransfersForEquality.java b/checker/tests/index/SpecialTransfersForEquality.java index 2a399df8238..3ce5de8d9e2 100644 --- a/checker/tests/index/SpecialTransfersForEquality.java +++ b/checker/tests/index/SpecialTransfersForEquality.java @@ -4,19 +4,19 @@ public class SpecialTransfersForEquality { - void gteN1Test(@GTENegativeOne int y) { - int[] arr = new int[10]; - if (-1 != y) { - @NonNegative int z = y; - if (z < 10) { - int k = arr[z]; - } - } + void gteN1Test(@GTENegativeOne int y) { + int[] arr = new int[10]; + if (-1 != y) { + @NonNegative int z = y; + if (z < 10) { + int k = arr[z]; + } } + } - void nnTest(@NonNegative int i) { - if (i != 0) { - @Positive int m = i; - } + void nnTest(@NonNegative int i) { + if (i != 0) { + @Positive int m = i; } + } } diff --git a/checker/tests/index/Split.java b/checker/tests/index/Split.java index 161feb8de48..38ec5c2392a 100644 --- a/checker/tests/index/Split.java +++ b/checker/tests/index/Split.java @@ -1,11 +1,10 @@ -import org.checkerframework.common.value.qual.MinLen; - import java.util.regex.Pattern; +import org.checkerframework.common.value.qual.MinLen; public class Split { - Pattern p = Pattern.compile(".*"); + Pattern p = Pattern.compile(".*"); - void test() { - String @MinLen(1) [] s = p.split("sdf"); - } + void test() { + String @MinLen(1) [] s = p.split("sdf"); + } } diff --git a/checker/tests/index/StartsEndsWith.java b/checker/tests/index/StartsEndsWith.java index 781d4c0c006..38121c26c2c 100644 --- a/checker/tests/index/StartsEndsWith.java +++ b/checker/tests/index/StartsEndsWith.java @@ -5,32 +5,31 @@ public class StartsEndsWith { - final String prefix; + final String prefix; - StartsEndsWith(String prefix) { - this.prefix = prefix; - } + StartsEndsWith(String prefix) { + this.prefix = prefix; + } - String propertyName(String methodName) { - if (methodName.startsWith(prefix)) { - @SuppressWarnings( - "index") // BUG: https://github.com/typetools/checker-framework/issues/5201 - String result = methodName.substring(prefix.length()); - return result; - } else { - return null; - } + String propertyName(String methodName) { + if (methodName.startsWith(prefix)) { + @SuppressWarnings("index") // BUG: https://github.com/typetools/checker-framework/issues/5201 + String result = methodName.substring(prefix.length()); + return result; + } else { + return null; } + } - // This particular test is here rather than in the framework tests because it depends on purity - // annotations for these particular JDK methods. - static void refineStartsConditional(String str, String prefix) { - if (prefix.length() > 10 && str.startsWith(prefix)) { - @MinLen(11) String s11 = str; - } + // This particular test is here rather than in the framework tests because it depends on purity + // annotations for these particular JDK methods. + static void refineStartsConditional(String str, String prefix) { + if (prefix.length() > 10 && str.startsWith(prefix)) { + @MinLen(11) String s11 = str; } + } } class StartsEndsWithExternal { - public static final String staticFinalField = "str"; + public static final String staticFinalField = "str"; } diff --git a/checker/tests/index/StaticInitializer.java b/checker/tests/index/StaticInitializer.java index 209f5c92c82..8c7955716bd 100644 --- a/checker/tests/index/StaticInitializer.java +++ b/checker/tests/index/StaticInitializer.java @@ -1,3 +1,3 @@ public final class StaticInitializer { - static final int MAX_SIGNED_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + static final int MAX_SIGNED_POWER_OF_TWO = 1 << (Integer.SIZE - 2); } diff --git a/checker/tests/index/Stopwatch.java b/checker/tests/index/Stopwatch.java index 6fa60d89d8b..08a4b522d40 100644 --- a/checker/tests/index/Stopwatch.java +++ b/checker/tests/index/Stopwatch.java @@ -1,17 +1,16 @@ -import org.checkerframework.checker.index.qual.IndexFor; - import java.text.DecimalFormat; +import org.checkerframework.checker.index.qual.IndexFor; public final class Stopwatch { - private static final DecimalFormat[] timeFormat = { - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - }; + private static final DecimalFormat[] timeFormat = { + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + }; - public DecimalFormat format(@IndexFor("Stopwatch.timeFormat") int digits) { - return Stopwatch.timeFormat[digits]; - } + public DecimalFormat format(@IndexFor("Stopwatch.timeFormat") int digits) { + return Stopwatch.timeFormat[digits]; + } } diff --git a/checker/tests/index/StringBuilderOffset.java b/checker/tests/index/StringBuilderOffset.java index d3fd340de67..3ea9423c944 100644 --- a/checker/tests/index/StringBuilderOffset.java +++ b/checker/tests/index/StringBuilderOffset.java @@ -1,11 +1,11 @@ public class StringBuilderOffset { - public static void OffsetStringBuilder() { - StringBuilder stringBuilder = new StringBuilder(); - char[] chars = new char[10]; + public static void OffsetStringBuilder() { + StringBuilder stringBuilder = new StringBuilder(); + char[] chars = new char[10]; - // :: error: (argument.type.incompatible) - stringBuilder.append(chars, 5, 7); + // :: error: (argument.type.incompatible) + stringBuilder.append(chars, 5, 7); - stringBuilder.append(chars, 5, 4); - } + stringBuilder.append(chars, 5, 4); + } } diff --git a/checker/tests/index/StringIndexOf.java b/checker/tests/index/StringIndexOf.java index 003aea18b3a..c3c7edc22c0 100644 --- a/checker/tests/index/StringIndexOf.java +++ b/checker/tests/index/StringIndexOf.java @@ -2,43 +2,43 @@ public class StringIndexOf { - public static String remove(String l, String s) { - int i = l.indexOf(s); - if (i != -1) { - return l.substring(0, i) + l.substring(i + s.length()); - } - return l; + public static String remove(String l, String s) { + int i = l.indexOf(s); + if (i != -1) { + return l.substring(0, i) + l.substring(i + s.length()); } + return l; + } - public static String nocheck(String l, String s) { - int i = l.indexOf(s); - // :: error: (argument.type.incompatible) - return l.substring(0, i) + l.substring(i + s.length()); - } + public static String nocheck(String l, String s) { + int i = l.indexOf(s); + // :: error: (argument.type.incompatible) + return l.substring(0, i) + l.substring(i + s.length()); + } - public static String remove(String l, String s, int from, boolean last) { - int i = last ? l.lastIndexOf(s, from) : l.indexOf(s, from); - if (i >= 0) { - return l.substring(0, i) + l.substring(i + s.length()); - } - return l; + public static String remove(String l, String s, int from, boolean last) { + int i = last ? l.lastIndexOf(s, from) : l.indexOf(s, from); + if (i >= 0) { + return l.substring(0, i) + l.substring(i + s.length()); } + return l; + } - public static String stringLiteral(String l) { - int i = l.indexOf("constant"); - if (i != -1) { - return l.substring(0, i) + l.substring(i + "constant".length()); - } - // :: error: (argument.type.incompatible) - return l.substring(0, i) + l.substring(i + "constant".length()); + public static String stringLiteral(String l) { + int i = l.indexOf("constant"); + if (i != -1) { + return l.substring(0, i) + l.substring(i + "constant".length()); } + // :: error: (argument.type.incompatible) + return l.substring(0, i) + l.substring(i + "constant".length()); + } - public static char character(String l, char c) { - int i = l.indexOf(c); - if (i > -1) { - return l.charAt(i); - } - // :: error: (argument.type.incompatible) - return l.charAt(i); + public static char character(String l, char c) { + int i = l.indexOf(c); + if (i > -1) { + return l.charAt(i); } + // :: error: (argument.type.incompatible) + return l.charAt(i); + } } diff --git a/checker/tests/index/StringLenRefinement.java b/checker/tests/index/StringLenRefinement.java index d7556bfa4e4..dd55e365222 100644 --- a/checker/tests/index/StringLenRefinement.java +++ b/checker/tests/index/StringLenRefinement.java @@ -4,39 +4,39 @@ public class StringLenRefinement { - void refineLenRange( - @ArrayLenRange(from = 3, to = 10) String range, - @ArrayLen({4, 6, 12}) String lens, - @StringVal({"aaaa", "bbbb", "cccccc", "dddddddddddd"}) String vals) { - if (range.length() <= 7) { - @ArrayLenRange(from = 3, to = 7) String shortRange = range; - } else { - @ArrayLenRange(from = 8, to = 10) String longRange = range; - } - - if (lens.length() <= 7) { - @ArrayLen({4, 6}) String shortLens = lens; - } else { - @ArrayLen({12}) String longLens = lens; - } - - if (vals.length() <= 7) { - @StringVal({"aaaa", "bbbb", "cccccc"}) String shortVals = vals; - } else { - - @StringVal({"dddddddddddd"}) String longVals = vals; - } + void refineLenRange( + @ArrayLenRange(from = 3, to = 10) String range, + @ArrayLen({4, 6, 12}) String lens, + @StringVal({"aaaa", "bbbb", "cccccc", "dddddddddddd"}) String vals) { + if (range.length() <= 7) { + @ArrayLenRange(from = 3, to = 7) String shortRange = range; + } else { + @ArrayLenRange(from = 8, to = 10) String longRange = range; } - void refineLen( - @ArrayLenRange(from = 3, to = 10) String range, @ArrayLen({4, 8, 12}) String lens) { + if (lens.length() <= 7) { + @ArrayLen({4, 6}) String shortLens = lens; + } else { + @ArrayLen({12}) String longLens = lens; + } + + if (vals.length() <= 7) { + @StringVal({"aaaa", "bbbb", "cccccc"}) String shortVals = vals; + } else { + + @StringVal({"dddddddddddd"}) String longVals = vals; + } + } + + void refineLen( + @ArrayLenRange(from = 3, to = 10) String range, @ArrayLen({4, 8, 12}) String lens) { - if (range.length() == 5 || range.length() == 8 || range.length() == 13) { - @ArrayLen({5, 8}) String refinedArg = range; - } + if (range.length() == 5 || range.length() == 8 || range.length() == 13) { + @ArrayLen({5, 8}) String refinedArg = range; + } - if (lens.length() == 5 || lens.length() == 8 || lens.length() == 13) { - @ArrayLen({8}) String refinedLens = lens; - } + if (lens.length() == 5 || lens.length() == 8 || lens.length() == 13) { + @ArrayLen({8}) String refinedLens = lens; } + } } diff --git a/checker/tests/index/StringLength.java b/checker/tests/index/StringLength.java index ab26225e537..9207147d646 100644 --- a/checker/tests/index/StringLength.java +++ b/checker/tests/index/StringLength.java @@ -1,5 +1,6 @@ // Tests that String.length() is supported in the same situations as array length +import java.util.Random; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.checker.index.qual.LTLengthOf; @@ -8,74 +9,72 @@ import org.checkerframework.checker.index.qual.SameLen; import org.checkerframework.common.value.qual.MinLen; -import java.util.Random; - public class StringLength { - void testMinLenSubtractPositive(@MinLen(10) String s) { - @Positive int i1 = s.length() - 9; - @NonNegative int i0 = s.length() - 10; - // :: error: (assignment.type.incompatible) - @NonNegative int im1 = s.length() - 11; - } + void testMinLenSubtractPositive(@MinLen(10) String s) { + @Positive int i1 = s.length() - 9; + @NonNegative int i0 = s.length() - 10; + // :: error: (assignment.type.incompatible) + @NonNegative int im1 = s.length() - 11; + } - void testNewArraySameLen(String s) { - int @SameLen("s") [] array = new int[s.length()]; - // :: error: (assignment.type.incompatible) - int @SameLen("s") [] array1 = new int[s.length() + 1]; - } + void testNewArraySameLen(String s) { + int @SameLen("s") [] array = new int[s.length()]; + // :: error: (assignment.type.incompatible) + int @SameLen("s") [] array1 = new int[s.length() + 1]; + } - void testStringAssignSameLen(String s, String r) { - @SameLen("s") String t = s; - // :: error: (assignment.type.incompatible) - @SameLen("s") String tN = r; - } + void testStringAssignSameLen(String s, String r) { + @SameLen("s") String t = s; + // :: error: (assignment.type.incompatible) + @SameLen("s") String tN = r; + } - void testStringLenEqualSameLen(String s, String r) { - if (s.length() == r.length()) { - @SameLen("s") String tN = r; - } + void testStringLenEqualSameLen(String s, String r) { + if (s.length() == r.length()) { + @SameLen("s") String tN = r; } + } - void testStringEqualSameLen(String s, String r) { - if (s == r) { - @SameLen("s") String tN = r; - } + void testStringEqualSameLen(String s, String r) { + if (s == r) { + @SameLen("s") String tN = r; } + } - void testOffsetRemoval( - String s, - String t, - @LTLengthOf(value = "#1", offset = "#2.length()") int i, - @LTLengthOf(value = "#2") int j, - int k) { - @LTLengthOf("s") int ij = i + j; - // :: error: (assignment.type.incompatible) - @LTLengthOf("s") int ik = i + k; - } + void testOffsetRemoval( + String s, + String t, + @LTLengthOf(value = "#1", offset = "#2.length()") int i, + @LTLengthOf(value = "#2") int j, + int k) { + @LTLengthOf("s") int ij = i + j; + // :: error: (assignment.type.incompatible) + @LTLengthOf("s") int ik = i + k; + } - void testLengthDivide(@MinLen(1) String s) { - @IndexFor("s") int i = s.length() / 2; - } + void testLengthDivide(@MinLen(1) String s) { + @IndexFor("s") int i = s.length() / 2; + } - void testAddDivide(@MinLen(1) String s, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @IndexFor("s") int ij = (i + j) / 2; - } + void testAddDivide(@MinLen(1) String s, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @IndexFor("s") int ij = (i + j) / 2; + } - void testRandomMultiply(@MinLen(1) String s, Random r) { - @LTLengthOf("s") int i = (int) (Math.random() * s.length()); - @LTLengthOf("s") int j = (int) (r.nextDouble() * s.length()); - } + void testRandomMultiply(@MinLen(1) String s, Random r) { + @LTLengthOf("s") int i = (int) (Math.random() * s.length()); + @LTLengthOf("s") int j = (int) (r.nextDouble() * s.length()); + } - void testNotEqualLength(String s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { - if (i != s.length()) { - @IndexFor("s") int in = i; - // :: error: (assignment.type.incompatible) - @IndexFor("s") int jn = j; - } + void testNotEqualLength(String s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { + if (i != s.length()) { + @IndexFor("s") int in = i; + // :: error: (assignment.type.incompatible) + @IndexFor("s") int jn = j; } + } - void testLength(String s) { - @IndexOrHigh("s") int i = s.length(); - @LTLengthOf("s") int j = s.length() - 1; - } + void testLength(String s) { + @IndexOrHigh("s") int i = s.length(); + @LTLengthOf("s") int j = s.length() - 1; + } } diff --git a/checker/tests/index/StringMethods.java b/checker/tests/index/StringMethods.java index 0c3368d5918..f26c8e23937 100644 --- a/checker/tests/index/StringMethods.java +++ b/checker/tests/index/StringMethods.java @@ -2,43 +2,43 @@ public class StringMethods { - void testCharAt(String s, int i) { - // :: error: (argument.type.incompatible) - s.charAt(i); - // :: error: (argument.type.incompatible) - s.codePointAt(i); + void testCharAt(String s, int i) { + // :: error: (argument.type.incompatible) + s.charAt(i); + // :: error: (argument.type.incompatible) + s.codePointAt(i); - if (i >= 0 && i < s.length()) { - s.charAt(i); - s.codePointAt(i); - } + if (i >= 0 && i < s.length()) { + s.charAt(i); + s.codePointAt(i); } + } - void testCodePointBefore(String s) { - // :: error: (argument.type.incompatible) - s.codePointBefore(0); + void testCodePointBefore(String s) { + // :: error: (argument.type.incompatible) + s.codePointBefore(0); - if (s.length() > 0) { - s.codePointBefore(s.length()); - } + if (s.length() > 0) { + s.codePointBefore(s.length()); } + } - void testSubstring(String s) { - s.substring(0); - s.substring(0, 0); - s.substring(s.length()); - s.substring(s.length(), s.length()); - s.substring(0, s.length()); - // :: error: (argument.type.incompatible) - s.substring(1); - // :: error: (argument.type.incompatible) - s.substring(0, 1); - } + void testSubstring(String s) { + s.substring(0); + s.substring(0, 0); + s.substring(s.length()); + s.substring(s.length(), s.length()); + s.substring(0, s.length()); + // :: error: (argument.type.incompatible) + s.substring(1); + // :: error: (argument.type.incompatible) + s.substring(0, 1); + } - void testIndexOf(String s, char c) { - int i = s.indexOf(c); - if (i != -1) { - s.charAt(i); - } + void testIndexOf(String s, char c) { + int i = s.indexOf(c); + if (i != -1) { + s.charAt(i); } + } } diff --git a/checker/tests/index/StringOffsetTest.java b/checker/tests/index/StringOffsetTest.java index b1b588c19e6..2005c714edb 100644 --- a/checker/tests/index/StringOffsetTest.java +++ b/checker/tests/index/StringOffsetTest.java @@ -1,10 +1,10 @@ public class StringOffsetTest { - public static void OffsetString() { - char[] chars = new char[10]; + public static void OffsetString() { + char[] chars = new char[10]; - // :: error: (argument.type.incompatible) - String string2 = new String(chars, 5, 7); + // :: error: (argument.type.incompatible) + String string2 = new String(chars, 5, 7); - String string3 = new String(chars, 5, 4); - } + String string3 = new String(chars, 5, 4); + } } diff --git a/checker/tests/index/StringSameLen.java b/checker/tests/index/StringSameLen.java index 1286990355b..fc854b8881f 100644 --- a/checker/tests/index/StringSameLen.java +++ b/checker/tests/index/StringSameLen.java @@ -1,53 +1,53 @@ public class StringSameLen { - public void m(String s) { - String t = s; + public void m(String s) { + String t = s; - for (int i = 0; i < s.length(); ++i) { - char c = t.charAt(i); - } + for (int i = 0; i < s.length(); ++i) { + char c = t.charAt(i); } + } - public void m2(String s) { - String t = s.toString(); + public void m2(String s) { + String t = s.toString(); - for (int i = 0; i < s.length(); ++i) { - char c = t.charAt(i); - } + for (int i = 0; i < s.length(); ++i) { + char c = t.charAt(i); } + } - public void m4(String s) { - char[] t = s.toCharArray(); + public void m4(String s) { + char[] t = s.toCharArray(); - for (int i = 0; i < s.length(); ++i) { - char c = t[i]; - } + for (int i = 0; i < s.length(); ++i) { + char c = t[i]; } + } - public void m6(char[] s) { - String t = String.valueOf(s); + public void m6(char[] s) { + String t = String.valueOf(s); - for (int i = 0; i < s.length; ++i) { - char c = t.charAt(i); - } + for (int i = 0; i < s.length; ++i) { + char c = t.charAt(i); } + } - public void m7(char[] s) { - String t = String.copyValueOf(s); + public void m7(char[] s) { + String t = String.copyValueOf(s); - for (int i = 0; i < s.length; ++i) { - char c = t.charAt(i); - } + for (int i = 0; i < s.length; ++i) { + char c = t.charAt(i); } + } - public void m8(String s) { - String t = s.intern(); + public void m8(String s) { + String t = s.intern(); - for (int i = 0; i < s.length(); ++i) { - char c = t.charAt(i); - } + for (int i = 0; i < s.length(); ++i) { + char c = t.charAt(i); } + } - public void constructor(String s) { - String t = new String(new char[] {'a'}); - } + public void constructor(String s) { + String t = new String(new char[] {'a'}); + } } diff --git a/checker/tests/index/StringTokenizerMinLen.java b/checker/tests/index/StringTokenizerMinLen.java index d91c481387e..97b697642d4 100644 --- a/checker/tests/index/StringTokenizerMinLen.java +++ b/checker/tests/index/StringTokenizerMinLen.java @@ -4,11 +4,11 @@ import java.util.StringTokenizer; public class StringTokenizerMinLen { - void test(String str, String delim, boolean returnDelims) { - StringTokenizer st = new StringTokenizer(str, delim, returnDelims); - while (st.hasMoreTokens()) { - String token = st.nextToken(); - char c = token.charAt(0); - } + void test(String str, String delim, boolean returnDelims) { + StringTokenizer st = new StringTokenizer(str, delim, returnDelims); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + char c = token.charAt(0); } + } } diff --git a/checker/tests/index/SubstringIndexForIrrelevant.java b/checker/tests/index/SubstringIndexForIrrelevant.java index 0a1ea70b543..289a2394afb 100644 --- a/checker/tests/index/SubstringIndexForIrrelevant.java +++ b/checker/tests/index/SubstringIndexForIrrelevant.java @@ -3,12 +3,12 @@ public class SubstringIndexForIrrelevant { - @SuppressWarnings( - "substringindex:return" // https://github.com/kelloggm/checker-framework/issues/206, - // 207, 208 - ) - public static @LTEqLengthOf("#1") @SubstringIndexFor(value = "#1", offset = "#2.length - 1") int - indexOf(boolean[] array, boolean[] target) { - return -1; - } + @SuppressWarnings( + "substringindex:return" // https://github.com/kelloggm/checker-framework/issues/206, + // 207, 208 + ) + public static @LTEqLengthOf("#1") @SubstringIndexFor(value = "#1", offset = "#2.length - 1") int + indexOf(boolean[] array, boolean[] target) { + return -1; + } } diff --git a/checker/tests/index/SubtractingNonNegatives.java b/checker/tests/index/SubtractingNonNegatives.java index 9ca1662e0d8..e9cd23835de 100644 --- a/checker/tests/index/SubtractingNonNegatives.java +++ b/checker/tests/index/SubtractingNonNegatives.java @@ -3,30 +3,30 @@ import org.checkerframework.checker.index.qual.*; public class SubtractingNonNegatives { - public static void m4(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - int k = i; - if (k >= j) { - @IndexFor("a") int y = k; - } - for (k = i; k >= j; k -= j) { - @IndexFor("a") int x = k; - } + public static void m4(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + int k = i; + if (k >= j) { + @IndexFor("a") int y = k; } - - @SuppressWarnings("lowerbound") - void test(int[] a, @Positive int y) { - @LTLengthOf("a") int x = a.length - 1; - @LTLengthOf( - value = {"a", "a"}, - offset = {"0", "y"}) - int z = x - y; - a[z + y] = 0; + for (k = i; k >= j; k -= j) { + @IndexFor("a") int x = k; } + } - @SuppressWarnings("lowerbound") - void test2(int[] a, @Positive int y) { - @LTLengthOf("a") int x = a.length - 1; - int z = x - y; - a[z + y] = 0; - } + @SuppressWarnings("lowerbound") + void test(int[] a, @Positive int y) { + @LTLengthOf("a") int x = a.length - 1; + @LTLengthOf( + value = {"a", "a"}, + offset = {"0", "y"}) + int z = x - y; + a[z + y] = 0; + } + + @SuppressWarnings("lowerbound") + void test2(int[] a, @Positive int y) { + @LTLengthOf("a") int x = a.length - 1; + int z = x - y; + a[z + y] = 0; + } } diff --git a/checker/tests/index/SubtractionIndex.java b/checker/tests/index/SubtractionIndex.java index ddfdfa60cc1..d032f581cc5 100644 --- a/checker/tests/index/SubtractionIndex.java +++ b/checker/tests/index/SubtractionIndex.java @@ -7,25 +7,25 @@ public class SubtractionIndex { - // Version without annotations - public static void main(String[] args) { - int N = 8; - int[] grid = new int[N]; - for (int i = 0; i < N; i++) { - System.out.println(grid[(N - 1) - i]); - } + // Version without annotations + public static void main(String[] args) { + int N = 8; + int[] grid = new int[N]; + for (int i = 0; i < N; i++) { + System.out.println(grid[(N - 1) - i]); } + } - // Version with annotations - public static void mainAnnotated(String[] args) { - int N = 8; - int @MinLen(8) [] grid = new int[N]; - @SuppressWarnings("upperbound") - @LTLengthOf("grid") int zero = 0; - for (@LTLengthOf("grid") int i = zero; i < N; i++) { - System.out.println(grid[(N - 1) - i]); - System.out.println(grid[(N - i)]); - System.out.println(grid[(N - i) - 1]); - } + // Version with annotations + public static void mainAnnotated(String[] args) { + int N = 8; + int @MinLen(8) [] grid = new int[N]; + @SuppressWarnings("upperbound") + @LTLengthOf("grid") int zero = 0; + for (@LTLengthOf("grid") int i = zero; i < N; i++) { + System.out.println(grid[(N - 1) - i]); + System.out.println(grid[(N - i)]); + System.out.println(grid[(N - i) - 1]); } + } } diff --git a/checker/tests/index/SwitchDataflowRefinement.java b/checker/tests/index/SwitchDataflowRefinement.java index 358f84fba09..6d0ea169444 100644 --- a/checker/tests/index/SwitchDataflowRefinement.java +++ b/checker/tests/index/SwitchDataflowRefinement.java @@ -1,23 +1,23 @@ public class SwitchDataflowRefinement { - void readInfo(String[] parts) { + void readInfo(String[] parts) { - if (parts.length >= 1) { - Integer.parseInt(parts[0]); - } + if (parts.length >= 1) { + Integer.parseInt(parts[0]); + } - switch (parts.length) { - case 1: - Integer.parseInt(parts[0]); - break; - } + switch (parts.length) { + case 1: + Integer.parseInt(parts[0]); + break; + } - switch (parts.length) { - case 0: - break; - default: - Integer.parseInt(parts[0]); - break; - } + switch (parts.length) { + case 0: + break; + default: + Integer.parseInt(parts[0]); + break; } + } } diff --git a/checker/tests/index/SwitchTest.java b/checker/tests/index/SwitchTest.java index 8f4aae6c550..2dd6ec73923 100644 --- a/checker/tests/index/SwitchTest.java +++ b/checker/tests/index/SwitchTest.java @@ -2,17 +2,17 @@ public class SwitchTest { - public String findSlice_unordered(String[] vis) { - switch (vis.length) { - case 1: - @IntVal(1) int x = vis.length; - return vis[0]; - case 2: - return vis[0] + vis[1]; - case 3: - return vis[0] + vis[1] + vis[2]; - default: - throw new RuntimeException("Bad length " + vis.length); - } + public String findSlice_unordered(String[] vis) { + switch (vis.length) { + case 1: + @IntVal(1) int x = vis.length; + return vis[0]; + case 2: + return vis[0] + vis[1]; + case 3: + return vis[0] + vis[1] + vis[2]; + default: + throw new RuntimeException("Bad length " + vis.length); } + } } diff --git a/checker/tests/index/TestAgainstLength.java b/checker/tests/index/TestAgainstLength.java index 27e754ba7b0..4f749e72fb1 100644 --- a/checker/tests/index/TestAgainstLength.java +++ b/checker/tests/index/TestAgainstLength.java @@ -5,16 +5,16 @@ public class TestAgainstLength { - protected int[] values; + protected int[] values; - /** The number of active elements (equivalently, the first unused index). */ - @IndexOrHigh("values") int num_values; + /** The number of active elements (equivalently, the first unused index). */ + @IndexOrHigh("values") int num_values; - public void add(int elt) { - if (num_values == values.length) { - return; - } - values[num_values] = elt; - num_values++; + public void add(int elt) { + if (num_values == values.length) { + return; } + values[num_values] = elt; + num_values++; + } } diff --git a/checker/tests/index/ToArrayIndex.java b/checker/tests/index/ToArrayIndex.java index 343d4ba16a8..7b438c5592c 100644 --- a/checker/tests/index/ToArrayIndex.java +++ b/checker/tests/index/ToArrayIndex.java @@ -1,12 +1,11 @@ -import org.checkerframework.common.value.qual.MinLen; - import java.util.ArrayList; +import org.checkerframework.common.value.qual.MinLen; // @skip-test until we bring list support back public class ToArrayIndex { - public String @MinLen(1) [] m(@MinLen(1) ArrayList compiler) { - return compiler.toArray(new String[0]); - } + public String @MinLen(1) [] m(@MinLen(1) ArrayList compiler) { + return compiler.toArray(new String[0]); + } } diff --git a/checker/tests/index/TransferAdd.java b/checker/tests/index/TransferAdd.java index 031d0209b20..497feacc132 100644 --- a/checker/tests/index/TransferAdd.java +++ b/checker/tests/index/TransferAdd.java @@ -4,82 +4,82 @@ public class TransferAdd { - void test() { + void test() { - // adding zero and one and two + // adding zero and one and two - int a = -1; + int a = -1; - @Positive int a1 = a + 2; + @Positive int a1 = a + 2; - @NonNegative int b = a + 1; - @NonNegative int c = 1 + a; + @NonNegative int b = a + 1; + @NonNegative int c = 1 + a; - @GTENegativeOne int d = a + 0; - @GTENegativeOne int e = 0 + a; + @GTENegativeOne int d = a + 0; + @GTENegativeOne int e = 0 + a; - // :: error: (assignment.type.incompatible) - @Positive int f = a + 1; + // :: error: (assignment.type.incompatible) + @Positive int f = a + 1; - @NonNegative int g = b + 0; + @NonNegative int g = b + 0; - @Positive int h = b + 1; + @Positive int h = b + 1; - @Positive int i = h + 1; - @Positive int j = h + 0; + @Positive int i = h + 1; + @Positive int j = h + 0; - // adding values + // adding values - @Positive int k = i + j; - // :: error: (assignment.type.incompatible) - @Positive int l = b + c; - // :: error: (assignment.type.incompatible) - @Positive int m = d + c; - // :: error: (assignment.type.incompatible) - @Positive int n = d + e; + @Positive int k = i + j; + // :: error: (assignment.type.incompatible) + @Positive int l = b + c; + // :: error: (assignment.type.incompatible) + @Positive int m = d + c; + // :: error: (assignment.type.incompatible) + @Positive int n = d + e; - @Positive int o = h + g; - // :: error: (assignment.type.incompatible) - @Positive int p = h + d; + @Positive int o = h + g; + // :: error: (assignment.type.incompatible) + @Positive int p = h + d; - @NonNegative int q = b + c; - // :: error: (assignment.type.incompatible) - @NonNegative int r = q + d; + @NonNegative int q = b + c; + // :: error: (assignment.type.incompatible) + @NonNegative int r = q + d; - @NonNegative int s = k + d; - @GTENegativeOne int t = s + d; + @NonNegative int s = k + d; + @GTENegativeOne int t = s + d; - // increments + // increments - // :: error: (assignment.type.incompatible) - @Positive int u = b++; + // :: error: (assignment.type.incompatible) + @Positive int u = b++; - @Positive int u1 = b; + @Positive int u1 = b; - @Positive int v = ++c; + @Positive int v = ++c; - @Positive int v1 = c; + @Positive int v1 = c; - int n1p1 = -1, n1p2 = -1; + int n1p1 = -1, n1p2 = -1; - @NonNegative int w = ++n1p1; + @NonNegative int w = ++n1p1; - @NonNegative int w1 = n1p1; + @NonNegative int w1 = n1p1; - // :: error: (assignment.type.incompatible) - @Positive int w2 = n1p1; - // :: error: (assignment.type.incompatible) - @Positive int w3 = n1p1++; + // :: error: (assignment.type.incompatible) + @Positive int w2 = n1p1; + // :: error: (assignment.type.incompatible) + @Positive int w3 = n1p1++; - // :: error: (assignment.type.incompatible) - @NonNegative int x = n1p2++; + // :: error: (assignment.type.incompatible) + @NonNegative int x = n1p2++; - @NonNegative int x1 = n1p2; + @NonNegative int x1 = n1p2; - // :: error: (assignment.type.incompatible) - @Positive int y = ++d; - // :: error: (assignment.type.incompatible) - @Positive int z = e++; - } + // :: error: (assignment.type.incompatible) + @Positive int y = ++d; + // :: error: (assignment.type.incompatible) + @Positive int z = e++; + } } // a comment diff --git a/checker/tests/index/TransferDivide.java b/checker/tests/index/TransferDivide.java index 8805cb3e518..5f60605ab97 100644 --- a/checker/tests/index/TransferDivide.java +++ b/checker/tests/index/TransferDivide.java @@ -4,58 +4,58 @@ public class TransferDivide { - void test() { - int a = -1; - int b = 0; - int c = 1; - int d = 2; - - /** literals */ - @Positive int e = -1 / -1; - - /** 0 / * -> NN */ - @NonNegative int f = 0 / a; - @NonNegative int g = 0 / d; - - /** * / 1 -> * */ - @GTENegativeOne int h = a / 1; - @NonNegative int i = b / 1; - @Positive int j = c / 1; - @Positive int k = d / 1; - - /** pos / pos -> nn */ - @NonNegative int l = d / c; - @NonNegative int m = c / d; - // :: error: (assignment.type.incompatible) - @Positive int n = c / d; - - /** nn / pos -> nn */ - @NonNegative int o = b / c; - // :: error: (assignment.type.incompatible) - @Positive int p = b / d; - - /** pos / nn -> nn */ - @NonNegative int q = d / l; - // :: error: (assignment.type.incompatible) - @Positive int r = c / l; - - /** nn / nn -> nn */ - @NonNegative int s = b / q; - // :: error: (assignment.type.incompatible) - @Positive int t = b / q; - - /** n1p / pos -> n1p */ - @GTENegativeOne int u = a / d; - @GTENegativeOne int v = a / c; - // :: error: (assignment.type.incompatible) - @NonNegative int w = a / c; - - /** n1p / nn -> n1p */ - @GTENegativeOne int x = a / l; - } - - void testDivideByTwo(@NonNegative int x) { - @NonNegative int y = x / 2; - } + void test() { + int a = -1; + int b = 0; + int c = 1; + int d = 2; + + /** literals */ + @Positive int e = -1 / -1; + + /** 0 / * -> NN */ + @NonNegative int f = 0 / a; + @NonNegative int g = 0 / d; + + /** * / 1 -> * */ + @GTENegativeOne int h = a / 1; + @NonNegative int i = b / 1; + @Positive int j = c / 1; + @Positive int k = d / 1; + + /** pos / pos -> nn */ + @NonNegative int l = d / c; + @NonNegative int m = c / d; + // :: error: (assignment.type.incompatible) + @Positive int n = c / d; + + /** nn / pos -> nn */ + @NonNegative int o = b / c; + // :: error: (assignment.type.incompatible) + @Positive int p = b / d; + + /** pos / nn -> nn */ + @NonNegative int q = d / l; + // :: error: (assignment.type.incompatible) + @Positive int r = c / l; + + /** nn / nn -> nn */ + @NonNegative int s = b / q; + // :: error: (assignment.type.incompatible) + @Positive int t = b / q; + + /** n1p / pos -> n1p */ + @GTENegativeOne int u = a / d; + @GTENegativeOne int v = a / c; + // :: error: (assignment.type.incompatible) + @NonNegative int w = a / c; + + /** n1p / nn -> n1p */ + @GTENegativeOne int x = a / l; + } + + void testDivideByTwo(@NonNegative int x) { + @NonNegative int y = x / 2; + } } // a comment diff --git a/checker/tests/index/TransferMod.java b/checker/tests/index/TransferMod.java index a98d62cd76b..2144ac4c13b 100644 --- a/checker/tests/index/TransferMod.java +++ b/checker/tests/index/TransferMod.java @@ -4,31 +4,31 @@ public class TransferMod { - void test() { - int aa = -100; - int a = -1; - int b = 0; - int c = 1; - int d = 2; + void test() { + int aa = -100; + int a = -1; + int b = 0; + int c = 1; + int d = 2; - @Positive int e = 5 % 3; - @NonNegative int f = -100 % 1; + @Positive int e = 5 % 3; + @NonNegative int f = -100 % 1; - @NonNegative int g = aa % -1; - @NonNegative int h = aa % 1; - @NonNegative int i = d % -1; - @NonNegative int j = d % 1; + @NonNegative int g = aa % -1; + @NonNegative int h = aa % 1; + @NonNegative int i = d % -1; + @NonNegative int j = d % 1; - @NonNegative int k = d % c; - @NonNegative int l = b % c; - @NonNegative int m = c % d; + @NonNegative int k = d % c; + @NonNegative int l = b % c; + @NonNegative int m = c % d; - @NonNegative int n = c % a; - @NonNegative int o = b % a; + @NonNegative int n = c % a; + @NonNegative int o = b % a; - @GTENegativeOne int p = a % a; - @GTENegativeOne int q = a % d; - @GTENegativeOne int r = a % c; - } + @GTENegativeOne int p = a % a; + @GTENegativeOne int q = a % d; + @GTENegativeOne int r = a % c; + } } // a comment diff --git a/checker/tests/index/TransferSub.java b/checker/tests/index/TransferSub.java index 209e092128a..bfab5416901 100644 --- a/checker/tests/index/TransferSub.java +++ b/checker/tests/index/TransferSub.java @@ -4,74 +4,74 @@ public class TransferSub { - void test() { - // zero, one, and two - int a = 1; - - @NonNegative int b = a - 1; - // :: error: (assignment.type.incompatible) - @Positive int c = a - 1; - @GTENegativeOne int d = a - 2; - - // :: error: (assignment.type.incompatible) - @NonNegative int e = a - 2; - - @GTENegativeOne int f = b - 1; - // :: error: (assignment.type.incompatible) - @NonNegative int g = b - 1; - - // :: error: (assignment.type.incompatible) - @GTENegativeOne int h = f - 1; - - @GTENegativeOne int i = f - 0; - @NonNegative int j = b - 0; - @Positive int k = a - 0; - - // :: error: (assignment.type.incompatible) - @Positive int l = j - 0; - // :: error: (assignment.type.incompatible) - @NonNegative int m = i - 0; - - // :: error: (assignment.type.incompatible) - @Positive int n = a - k; - // this would be an error if the values of b and j (both zero) weren't known at compile time - @NonNegative int o = b - j; - /* i and d both have compile time value -1, so this is legal. - The general case of GTEN1 - GTEN1 is not, though. */ - @GTENegativeOne int p = i - d; - - // decrements - - // :: error: (unary.decrement.type.incompatible) :: error: - // (assignment.type.incompatible) - @Positive int q = --k; // k = 0 - - // :: error: (unary.decrement.type.incompatible) - @NonNegative int r = k--; // after this k = -1 - - int k1 = 0; - @NonNegative int s = k1--; - - // :: error: (assignment.type.incompatible) - @NonNegative int s1 = k1; - - // transferred to SimpleTransferSub.java - // this section is failing due to CF bug - // int k2 = 0; - // // :: error: (assignment.type.incompatible) - // @Positive int s2 = k2--; - - k1 = 1; - @NonNegative int t = --k1; - - k1 = 1; - // :: error: (assignment.type.incompatible) - @Positive int t1 = --k1; - - int u1 = -1; - @GTENegativeOne int x = u1--; - // :: error: (assignment.type.incompatible) - @GTENegativeOne int x1 = u1; - } + void test() { + // zero, one, and two + int a = 1; + + @NonNegative int b = a - 1; + // :: error: (assignment.type.incompatible) + @Positive int c = a - 1; + @GTENegativeOne int d = a - 2; + + // :: error: (assignment.type.incompatible) + @NonNegative int e = a - 2; + + @GTENegativeOne int f = b - 1; + // :: error: (assignment.type.incompatible) + @NonNegative int g = b - 1; + + // :: error: (assignment.type.incompatible) + @GTENegativeOne int h = f - 1; + + @GTENegativeOne int i = f - 0; + @NonNegative int j = b - 0; + @Positive int k = a - 0; + + // :: error: (assignment.type.incompatible) + @Positive int l = j - 0; + // :: error: (assignment.type.incompatible) + @NonNegative int m = i - 0; + + // :: error: (assignment.type.incompatible) + @Positive int n = a - k; + // this would be an error if the values of b and j (both zero) weren't known at compile time + @NonNegative int o = b - j; + /* i and d both have compile time value -1, so this is legal. + The general case of GTEN1 - GTEN1 is not, though. */ + @GTENegativeOne int p = i - d; + + // decrements + + // :: error: (unary.decrement.type.incompatible) :: error: + // (assignment.type.incompatible) + @Positive int q = --k; // k = 0 + + // :: error: (unary.decrement.type.incompatible) + @NonNegative int r = k--; // after this k = -1 + + int k1 = 0; + @NonNegative int s = k1--; + + // :: error: (assignment.type.incompatible) + @NonNegative int s1 = k1; + + // transferred to SimpleTransferSub.java + // this section is failing due to CF bug + // int k2 = 0; + // // :: error: (assignment.type.incompatible) + // @Positive int s2 = k2--; + + k1 = 1; + @NonNegative int t = --k1; + + k1 = 1; + // :: error: (assignment.type.incompatible) + @Positive int t1 = --k1; + + int u1 = -1; + @GTENegativeOne int x = u1--; + // :: error: (assignment.type.incompatible) + @GTENegativeOne int x1 = u1; + } } // a comment diff --git a/checker/tests/index/TransferTimes.java b/checker/tests/index/TransferTimes.java index 2ad8745521d..9d95d84c91a 100644 --- a/checker/tests/index/TransferTimes.java +++ b/checker/tests/index/TransferTimes.java @@ -3,26 +3,26 @@ public class TransferTimes { - void test() { - int a = 1; - @Positive int b = a * 1; - @Positive int c = 1 * a; - @NonNegative int d = 0 * a; - // :: error: (assignment.type.incompatible) - @NonNegative int e = -1 * a; + void test() { + int a = 1; + @Positive int b = a * 1; + @Positive int c = 1 * a; + @NonNegative int d = 0 * a; + // :: error: (assignment.type.incompatible) + @NonNegative int e = -1 * a; - int g = -1; - @NonNegative int h = g * 0; - // :: error: (assignment.type.incompatible) - @Positive int i = g * 0; - // :: error: (assignment.type.incompatible) - @Positive int j = g * a; + int g = -1; + @NonNegative int h = g * 0; + // :: error: (assignment.type.incompatible) + @Positive int i = g * 0; + // :: error: (assignment.type.incompatible) + @Positive int j = g * a; - int k = 0; - int l = 1; - @Positive int m = a * l; - @NonNegative int n = k * l; - @NonNegative int o = k * k; - } + int k = 0; + int l = 1; + @Positive int m = a * l; + @NonNegative int n = k * l; + @NonNegative int o = k * k; + } } // a comment diff --git a/checker/tests/index/TypeArrayLengthWithSameLen.java b/checker/tests/index/TypeArrayLengthWithSameLen.java index a35bed67998..e134075123c 100644 --- a/checker/tests/index/TypeArrayLengthWithSameLen.java +++ b/checker/tests/index/TypeArrayLengthWithSameLen.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.*; public class TypeArrayLengthWithSameLen { - void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b, int[] c) { - if (a.length == c.length) { - @LTEqLengthOf({"a", "b", "c"}) int x = b.length; - } + void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b, int[] c) { + if (a.length == c.length) { + @LTEqLengthOf({"a", "b", "c"}) int x = b.length; } + } } diff --git a/checker/tests/index/UBLiteralFlow.java b/checker/tests/index/UBLiteralFlow.java index 2ffbdbf35a9..7fdf479bd39 100644 --- a/checker/tests/index/UBLiteralFlow.java +++ b/checker/tests/index/UBLiteralFlow.java @@ -4,185 +4,183 @@ public class UBLiteralFlow { - private static @IndexOrLow("#1") int lineStartIndexPartial( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; - } - return result; + private static @IndexOrLow("#1") int lineStartIndexPartial( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; } + return result; + } - private static @LTLengthOf("#1") int lineStartIndexPartial2( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; - } - return result; + private static @LTLengthOf("#1") int lineStartIndexPartial2( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; } + return result; + } - private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndexPartial3( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; - } - // :: error: (return.type.incompatible) - return result; + private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndexPartial3( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; } + // :: error: (return.type.incompatible) + return result; + } - private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndexPartial4( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; - } - return result; + private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndexPartial4( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; } + return result; + } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @IndexOrLow("#1") int lineStartIndex(String s, int start) { - if (s.length() == 0) { - return -1; - } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - return lineStart; - } + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @IndexOrLow("#1") int lineStartIndex(String s, int start) { + if (s.length() == 0) { + return -1; } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + return lineStart; + } + } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @LTLengthOf("#1") int lineStartIndex2(String s, int start) { - if (s.length() == 0) { - return -1; - } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - return lineStart; - } + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @LTLengthOf("#1") int lineStartIndex2(String s, int start) { + if (s.length() == 0) { + return -1; + } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + return lineStart; } + } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndex3( - String s, int start) { - if (s.length() == 0) { - // :: error: (return.type.incompatible) - return -1; - } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - // :: error: (return.type.incompatible) - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - // :: error: (return.type.incompatible) - return lineStart; - } + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndex3(String s, int start) { + if (s.length() == 0) { + // :: error: (return.type.incompatible) + return -1; } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + // :: error: (return.type.incompatible) + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + // :: error: (return.type.incompatible) + return lineStart; + } + } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndex4( - String s, int start) { - if (s.length() == 0) { - return -1; - } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - return lineStart; - } + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndex4(String s, int start) { + if (s.length() == 0) { + return -1; + } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + return lineStart; } + } } diff --git a/checker/tests/index/UBPoly.java b/checker/tests/index/UBPoly.java index 177650d45be..4ef9e2963e4 100644 --- a/checker/tests/index/UBPoly.java +++ b/checker/tests/index/UBPoly.java @@ -5,17 +5,17 @@ import org.checkerframework.checker.index.qual.PolyUpperBound; public class UBPoly { - public static void main(String[] args) { - char[] a = new char[10]; - poly(a, 100); - } + public static void main(String[] args) { + char[] a = new char[10]; + poly(a, 100); + } - public static void poly(char[] a, @NonNegative @PolyUpperBound int i) { - // :: error: (argument.type.incompatible) - access(a, i); - } + public static void poly(char[] a, @NonNegative @PolyUpperBound int i) { + // :: error: (argument.type.incompatible) + access(a, i); + } - public static void access(char[] a, @NonNegative @LTLengthOf("#1") int j) { - char c = a[j]; - } + public static void access(char[] a, @NonNegative @LTLengthOf("#1") int j) { + char c = a[j]; + } } diff --git a/checker/tests/index/UBSubtyping.java b/checker/tests/index/UBSubtyping.java index 8559353b99c..128c0d1e940 100644 --- a/checker/tests/index/UBSubtyping.java +++ b/checker/tests/index/UBSubtyping.java @@ -3,28 +3,28 @@ import org.checkerframework.checker.index.qual.UpperBoundUnknown; public class UBSubtyping { - int[] arr = {1}; - int[] arr2 = {1}; - int[] arr3 = {1}; + int[] arr = {1}; + int[] arr2 = {1}; + int[] arr3 = {1}; - void test(@LTEqLengthOf({"arr", "arr2", "arr3"}) int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"arr"}) int a = 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int a1 = 1; + void test(@LTEqLengthOf({"arr", "arr2", "arr3"}) int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"arr"}) int a = 1; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int a1 = 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int b = a; - @UpperBoundUnknown int d = a; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int b = a; + @UpperBoundUnknown int d = a; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr2"}) int g = a; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr2"}) int g = a; - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"arr", "arr2", "arr3"}) int h = 2; + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"arr", "arr2", "arr3"}) int h = 2; - @LTEqLengthOf({"arr", "arr2"}) int h2 = test; - @LTEqLengthOf({"arr"}) int i = test; - @LTEqLengthOf({"arr", "arr3"}) int j = test; - } + @LTEqLengthOf({"arr", "arr2"}) int h2 = test; + @LTEqLengthOf({"arr"}) int i = test; + @LTEqLengthOf({"arr", "arr3"}) int j = test; + } } diff --git a/checker/tests/index/UnaryOperationParsedIncorrectly.java b/checker/tests/index/UnaryOperationParsedIncorrectly.java index 5b0a7b63356..70964935d12 100644 --- a/checker/tests/index/UnaryOperationParsedIncorrectly.java +++ b/checker/tests/index/UnaryOperationParsedIncorrectly.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.index.qual.LessThan; public class UnaryOperationParsedIncorrectly { - void method1(@LessThan("#2") int var1, int var2) { - // Function implementation - } + void method1(@LessThan("#2") int var1, int var2) { + // Function implementation + } - void method2() { - method1(-10, 10); - method1(-10, +10); - } + void method2() { + method1(-10, 10); + method1(-10, +10); + } } diff --git a/checker/tests/index/UncheckedMinLen.java b/checker/tests/index/UncheckedMinLen.java index bdf254a0dad..be202073ee3 100644 --- a/checker/tests/index/UncheckedMinLen.java +++ b/checker/tests/index/UncheckedMinLen.java @@ -6,40 +6,40 @@ // test case for kelloggm#183: https://github.com/kelloggm/checker-framework/issues/183 public class UncheckedMinLen { - void addToNonNegative(@NonNegative int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToNonNegative(@NonNegative int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void addToPositive(@Positive int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToPositive(@Positive int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - // Similar code that correctly gives warnings - void addToPositiveOK(@NonNegative int l, Object v) { - Object[] o = new Object[l + 1]; - // :: error: (array.access.unsafe.high.constant) - o[99] = v; - } + // Similar code that correctly gives warnings + void addToPositiveOK(@NonNegative int l, Object v) { + Object[] o = new Object[l + 1]; + // :: error: (array.access.unsafe.high.constant) + o[99] = v; + } - void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void subtractFromPositiveOK(@Positive int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l - 1]; - o[99] = v; - } + void subtractFromPositiveOK(@Positive int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l - 1]; + o[99] = v; + } } diff --git a/checker/tests/index/UpperBoundRefinement.java b/checker/tests/index/UpperBoundRefinement.java index a54405829a9..ce08686ae18 100644 --- a/checker/tests/index/UpperBoundRefinement.java +++ b/checker/tests/index/UpperBoundRefinement.java @@ -2,40 +2,40 @@ @SuppressWarnings("lowerbound") public class UpperBoundRefinement { - // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and expression - // j is less than or equal to the length of f1, then the type of i + j is @LTLengthOf("f2") - void test(int[] f1, int[] f2) { - @LTLengthOf(value = "f2", offset = "f1.length") int i = (f2.length - 1) - f1.length; - @LTLengthOf("f1") int j = f1.length - 1; - @LTLengthOf("f2") int x = i + j; - @LTLengthOf("f2") int y = i + f1.length; - } + // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and expression + // j is less than or equal to the length of f1, then the type of i + j is @LTLengthOf("f2") + void test(int[] f1, int[] f2) { + @LTLengthOf(value = "f2", offset = "f1.length") int i = (f2.length - 1) - f1.length; + @LTLengthOf("f1") int j = f1.length - 1; + @LTLengthOf("f2") int x = i + j; + @LTLengthOf("f2") int y = i + f1.length; + } - void test2() { - double[] f1 = new double[10]; - double[] f2 = new double[20]; + void test2() { + double[] f1 = new double[10]; + double[] f2 = new double[20]; - for (int j = 0; j < f2.length; j++) { - f2[j] = j; - } - for (int i = 0; i < f2.length - f1.length; i++) { - // fill up f1 with elements of f2 - for (int j = 0; j < f1.length; j++) { - f1[j] = f2[i + j]; - } - } + for (int j = 0; j < f2.length; j++) { + f2[j] = j; + } + for (int i = 0; i < f2.length - f1.length; i++) { + // fill up f1 with elements of f2 + for (int j = 0; j < f1.length; j++) { + f1[j] = f2[i + j]; + } } + } - public void test3(double[] a, double[] sub) { - int a_index_max = a.length - sub.length; - // Has type @LTL(value={"a","sub"}, offset={"-1 + sub.length", "-1 + a.length"}) + public void test3(double[] a, double[] sub) { + int a_index_max = a.length - sub.length; + // Has type @LTL(value={"a","sub"}, offset={"-1 + sub.length", "-1 + a.length"}) - for (int i = 0; i <= a_index_max; i++) { // i has the same type as a_index_max - for (int j = 0; j < sub.length; j++) { // j is @LTL("sub") - // i + j is safe here. Because j is LTL("sub"), it should count as ("-1 + - // sub.length") - double d = a[i + j]; - } - } + for (int i = 0; i <= a_index_max; i++) { // i has the same type as a_index_max + for (int j = 0; j < sub.length; j++) { // j is @LTL("sub") + // i + j is safe here. Because j is LTL("sub"), it should count as ("-1 + + // sub.length") + double d = a[i + j]; + } } + } } diff --git a/checker/tests/index/ValueCheckerProblem.java b/checker/tests/index/ValueCheckerProblem.java index 63da5b99f35..2377ca0d5d1 100644 --- a/checker/tests/index/ValueCheckerProblem.java +++ b/checker/tests/index/ValueCheckerProblem.java @@ -1,5 +1,5 @@ public class ValueCheckerProblem { - void test() { - Object o = new Object[][] {null}; - } + void test() { + Object o = new Object[][] {null}; + } } diff --git a/checker/tests/index/VarArgsIncompatible.java b/checker/tests/index/VarArgsIncompatible.java index c585fca2764..5bf8739e85f 100644 --- a/checker/tests/index/VarArgsIncompatible.java +++ b/checker/tests/index/VarArgsIncompatible.java @@ -1,10 +1,10 @@ public class VarArgsIncompatible { - public static void test(int[] arr) { - help(arr); - } + public static void test(int[] arr) { + help(arr); + } - @SafeVarargs - @SuppressWarnings("varargs") - public static void help(T... arr) {} + @SafeVarargs + @SuppressWarnings("varargs") + public static void help(T... arr) {} } diff --git a/checker/tests/index/VarLteVar.java b/checker/tests/index/VarLteVar.java index 5059c64e279..4ea0eb10f7f 100644 --- a/checker/tests/index/VarLteVar.java +++ b/checker/tests/index/VarLteVar.java @@ -10,32 +10,32 @@ public class VarLteVar { - /** Returns an array that is equivalent to the set difference of seq1 and seq2. */ - public static boolean[] setDiff(boolean[] seq1, boolean[] seq2) { - boolean[] intermediate = new boolean[seq1.length]; - int length = 0; - for (int i = 0; i < seq1.length; i++) { - if (!memberOf(seq1[i], seq2)) { - intermediate[length++] = seq1[i]; - } - } - return subarray(intermediate, 0, length); + /** Returns an array that is equivalent to the set difference of seq1 and seq2. */ + public static boolean[] setDiff(boolean[] seq1, boolean[] seq2) { + boolean[] intermediate = new boolean[seq1.length]; + int length = 0; + for (int i = 0; i < seq1.length; i++) { + if (!memberOf(seq1[i], seq2)) { + intermediate[length++] = seq1[i]; + } } + return subarray(intermediate, 0, length); + } - public static boolean memberOf(boolean elt, boolean[] arr) { - for (int i = 0; i < arr.length; i++) { - if (arr[i] == elt) { - return true; - } - } - return false; + public static boolean memberOf(boolean elt, boolean[] arr) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] == elt) { + return true; + } } + return false; + } - @SuppressWarnings("index") // not relevant to this test case - public static boolean[] subarray( - boolean[] a, @IndexOrHigh("#1") int startindex, @IndexOrHigh("#1") int length) { - boolean[] result = new boolean[length]; - System.arraycopy(a, startindex, result, 0, length); - return result; - } + @SuppressWarnings("index") // not relevant to this test case + public static boolean[] subarray( + boolean[] a, @IndexOrHigh("#1") int startindex, @IndexOrHigh("#1") int length) { + boolean[] result = new boolean[length]; + System.arraycopy(a, startindex, result, 0, length); + return result; + } } diff --git a/checker/tests/index/ViewpointAdaptTest.java b/checker/tests/index/ViewpointAdaptTest.java index a15114fa2d8..b634143f7f4 100644 --- a/checker/tests/index/ViewpointAdaptTest.java +++ b/checker/tests/index/ViewpointAdaptTest.java @@ -5,12 +5,12 @@ public class ViewpointAdaptTest { - void ListGet( - @LTLengthOf("list") int index, @LTEqLengthOf("list") int notIndex, List list) { - // :: error: (argument.type.incompatible) - list.get(index); + void ListGet( + @LTLengthOf("list") int index, @LTEqLengthOf("list") int notIndex, List list) { + // :: error: (argument.type.incompatible) + list.get(index); - // :: error: (argument.type.incompatible) - list.get(notIndex); - } + // :: error: (argument.type.incompatible) + list.get(notIndex); + } } diff --git a/checker/tests/index/VoidType.java b/checker/tests/index/VoidType.java index b6b7b89d248..ad6ec89a0bf 100644 --- a/checker/tests/index/VoidType.java +++ b/checker/tests/index/VoidType.java @@ -1,3 +1,3 @@ public class VoidType { - private Class main_class = Void.TYPE; + private Class main_class = Void.TYPE; } diff --git a/checker/tests/index/ZeroMinLen.java b/checker/tests/index/ZeroMinLen.java index 2b74b88103d..3e2776bac83 100644 --- a/checker/tests/index/ZeroMinLen.java +++ b/checker/tests/index/ZeroMinLen.java @@ -3,16 +3,16 @@ public class ZeroMinLen { - int @MinLen(1) [] nums; - int[] nums2; + int @MinLen(1) [] nums; + int[] nums2; - @IndexFor("nums") int current_index; + @IndexFor("nums") int current_index; - @IndexFor("nums2") int current_index2; + @IndexFor("nums2") int current_index2; - void test() { - current_index = 0; - // :: error: (assignment.type.incompatible) - current_index2 = 0; - } + void test() { + current_index = 0; + // :: error: (assignment.type.incompatible) + current_index2 = 0; + } } diff --git a/checker/tests/initialization/AnonymousInit.java b/checker/tests/initialization/AnonymousInit.java index fdf72d86fd2..984217f99e4 100644 --- a/checker/tests/initialization/AnonymousInit.java +++ b/checker/tests/initialization/AnonymousInit.java @@ -1,21 +1,21 @@ // Ensure field initialization checks for anonymous // classes work. public class AnonymousInit { - Object o1 = - new Object() { - // :: error: (initialization.field.uninitialized) - Object s; + Object o1 = + new Object() { + // :: error: (initialization.field.uninitialized) + Object s; - public String toString() { - return s.toString(); - } - }; - Object o2 = - new Object() { - Object s = "hi"; + public String toString() { + return s.toString(); + } + }; + Object o2 = + new Object() { + Object s = "hi"; - public String toString() { - return s.toString(); - } - }; + public String toString() { + return s.toString(); + } + }; } diff --git a/checker/tests/initialization/CastInit.java b/checker/tests/initialization/CastInit.java index 631b7da1bc4..15587ba8802 100644 --- a/checker/tests/initialization/CastInit.java +++ b/checker/tests/initialization/CastInit.java @@ -3,9 +3,9 @@ public class CastInit { - public CastInit() { - @UnknownInitialization CastInit t1 = (@UnknownInitialization CastInit) this; - // :: warning: (cast.unsafe) - @Initialized CastInit t2 = (@Initialized CastInit) this; - } + public CastInit() { + @UnknownInitialization CastInit t1 = (@UnknownInitialization CastInit) this; + // :: warning: (cast.unsafe) + @Initialized CastInit t2 = (@Initialized CastInit) this; + } } diff --git a/checker/tests/initialization/ChainedInitialization.java b/checker/tests/initialization/ChainedInitialization.java index 774106348ea..7892432536e 100644 --- a/checker/tests/initialization/ChainedInitialization.java +++ b/checker/tests/initialization/ChainedInitialization.java @@ -2,13 +2,13 @@ public class ChainedInitialization { - @NonNull String f; - @NonNull String g = f = "hello"; + @NonNull String f; + @NonNull String g = f = "hello"; - // Adding this empty initializer suppresses the warning. - // {} + // Adding this empty initializer suppresses the warning. + // {} - // Adding this constructor does not suppress the warning. - // ChainedInitialization() {} + // Adding this constructor does not suppress the warning. + // ChainedInitialization() {} } diff --git a/checker/tests/initialization/Commitment.java b/checker/tests/initialization/Commitment.java index 43de35194d6..f5790beec3e 100644 --- a/checker/tests/initialization/Commitment.java +++ b/checker/tests/initialization/Commitment.java @@ -6,72 +6,72 @@ public class Commitment { - @NonNull String t; - - // :: error: (initialization.invalid.field.type) - @NonNull @UnderInitialization String a; - // :: error: (initialization.invalid.field.type) - @Initialized String b; - @UnknownInitialization @Nullable String c; - - // :: error: (initialization.invalid.constructor.return.type) - public @UnderInitialization Commitment(int i) { - a = ""; - t = ""; - b = ""; - } - - // :: error: (initialization.invalid.constructor.return.type) - public @Initialized Commitment(int i, int j) { - a = ""; - t = ""; - b = ""; - } - - // :: error: (initialization.invalid.constructor.return.type) - // :: error: (nullness.on.constructor) - public @Initialized @NonNull Commitment(boolean i) { - a = ""; - t = ""; - b = ""; - } - - public - // :: error: (nullness.on.constructor) - @Nullable Commitment(char i) { - a = ""; - t = ""; - b = ""; - } - - // :: error: (initialization.fields.uninitialized) - public Commitment() { - // :: error: (dereference.of.nullable) - t.toLowerCase(); - - t = ""; - - @UnderInitialization @NonNull Commitment c = this; - - @UnknownInitialization @NonNull Commitment c1 = this; - - // :: error: (assignment.type.incompatible) - @Initialized @NonNull Commitment c2 = this; - } - - // :: error: (initialization.fields.uninitialized) - public Commitment(@UnknownInitialization Commitment arg) { - t = ""; - - // :: error: (argument.type.incompatible) - @UnderInitialization Commitment t = new Commitment(this, 1); - - // :: error: (assignment.type.incompatible) - @Initialized Commitment t1 = new Commitment(this); - - @UnderInitialization Commitment t2 = new Commitment(this); - } - - // :: error: (initialization.fields.uninitialized) - public Commitment(Commitment arg, int i) {} + @NonNull String t; + + // :: error: (initialization.invalid.field.type) + @NonNull @UnderInitialization String a; + // :: error: (initialization.invalid.field.type) + @Initialized String b; + @UnknownInitialization @Nullable String c; + + // :: error: (initialization.invalid.constructor.return.type) + public @UnderInitialization Commitment(int i) { + a = ""; + t = ""; + b = ""; + } + + // :: error: (initialization.invalid.constructor.return.type) + public @Initialized Commitment(int i, int j) { + a = ""; + t = ""; + b = ""; + } + + // :: error: (initialization.invalid.constructor.return.type) + // :: error: (nullness.on.constructor) + public @Initialized @NonNull Commitment(boolean i) { + a = ""; + t = ""; + b = ""; + } + + public + // :: error: (nullness.on.constructor) + @Nullable Commitment(char i) { + a = ""; + t = ""; + b = ""; + } + + // :: error: (initialization.fields.uninitialized) + public Commitment() { + // :: error: (dereference.of.nullable) + t.toLowerCase(); + + t = ""; + + @UnderInitialization @NonNull Commitment c = this; + + @UnknownInitialization @NonNull Commitment c1 = this; + + // :: error: (assignment.type.incompatible) + @Initialized @NonNull Commitment c2 = this; + } + + // :: error: (initialization.fields.uninitialized) + public Commitment(@UnknownInitialization Commitment arg) { + t = ""; + + // :: error: (argument.type.incompatible) + @UnderInitialization Commitment t = new Commitment(this, 1); + + // :: error: (assignment.type.incompatible) + @Initialized Commitment t1 = new Commitment(this); + + @UnderInitialization Commitment t2 = new Commitment(this); + } + + // :: error: (initialization.fields.uninitialized) + public Commitment(Commitment arg, int i) {} } diff --git a/checker/tests/initialization/Commitment2.java b/checker/tests/initialization/Commitment2.java index 9eea37287c1..5221ed436cf 100644 --- a/checker/tests/initialization/Commitment2.java +++ b/checker/tests/initialization/Commitment2.java @@ -4,32 +4,32 @@ public class Commitment2 { - // :: error: (assignment.type.incompatible) - Commitment2 g = create(); + // :: error: (assignment.type.incompatible) + Commitment2 g = create(); - Commitment2 h; + Commitment2 h; - @NotOnlyInitialized Commitment2 c; + @NotOnlyInitialized Commitment2 c; - @NotOnlyInitialized Commitment2 f; + @NotOnlyInitialized Commitment2 f; - public void test(@UnderInitialization Commitment2 c) { - // :: error: (initialization.invalid.field.write.initialized) - f = c; - } + public void test(@UnderInitialization Commitment2 c) { + // :: error: (initialization.invalid.field.write.initialized) + f = c; + } - public static @UnknownInitialization Commitment2 create() { - return new Commitment2(); - } + public static @UnknownInitialization Commitment2 create() { + return new Commitment2(); + } - // :: error: (initialization.fields.uninitialized) - public Commitment2() {} + // :: error: (initialization.fields.uninitialized) + public Commitment2() {} - // :: error: (initialization.fields.uninitialized) - public Commitment2(@UnderInitialization Commitment2 likeAnEagle) { - // :: error: (assignment.type.incompatible) - h = likeAnEagle; + // :: error: (initialization.fields.uninitialized) + public Commitment2(@UnderInitialization Commitment2 likeAnEagle) { + // :: error: (assignment.type.incompatible) + h = likeAnEagle; - c = likeAnEagle; - } + c = likeAnEagle; + } } diff --git a/checker/tests/initialization/CommitmentFlow.java b/checker/tests/initialization/CommitmentFlow.java index e6a4d1d4abf..c9fd2107beb 100644 --- a/checker/tests/initialization/CommitmentFlow.java +++ b/checker/tests/initialization/CommitmentFlow.java @@ -4,22 +4,21 @@ public class CommitmentFlow { - @NonNull CommitmentFlow t; + @NonNull CommitmentFlow t; - public CommitmentFlow(CommitmentFlow arg) { - t = arg; - } + public CommitmentFlow(CommitmentFlow arg) { + t = arg; + } - void foo( - @UnknownInitialization CommitmentFlow mystery, - @Initialized CommitmentFlow triedAndTrue) { - CommitmentFlow local = null; + void foo( + @UnknownInitialization CommitmentFlow mystery, @Initialized CommitmentFlow triedAndTrue) { + CommitmentFlow local = null; - local = mystery; - // :: error: (method.invocation.invalid) - local.hashCode(); + local = mystery; + // :: error: (method.invocation.invalid) + local.hashCode(); - local = triedAndTrue; - local.hashCode(); // should determine that it is Initialized based on flow - } + local = triedAndTrue; + local.hashCode(); // should determine that it is Initialized based on flow + } } diff --git a/checker/tests/initialization/FBCList.java b/checker/tests/initialization/FBCList.java index 14f1f5beb74..a04bc8f7fe7 100644 --- a/checker/tests/initialization/FBCList.java +++ b/checker/tests/initialization/FBCList.java @@ -5,50 +5,50 @@ // This example is taken from the FBC paper, figure 1 (and has some additional code in main below). // We made the list generic. public class FBCList { - @NotOnlyInitialized FBCNode sentinel; + @NotOnlyInitialized FBCNode sentinel; - public FBCList() { - this.sentinel = new FBCNode<>(this); - } + public FBCList() { + this.sentinel = new FBCNode<>(this); + } - void insert(@Nullable T data) { - this.sentinel.insertAfter(data); - } + void insert(@Nullable T data) { + this.sentinel.insertAfter(data); + } - public static void main() { - FBCList l = new FBCList<>(); - l.insert(1); - l.insert(2); - } + public static void main() { + FBCList l = new FBCList<>(); + l.insert(1); + l.insert(2); + } } class FBCNode { - @NotOnlyInitialized FBCNode prev; - - @NotOnlyInitialized FBCNode next; - - @NotOnlyInitialized FBCList parent; - - @Nullable T data; - - // for sentinel construction - FBCNode(@UnderInitialization FBCList parent) { - this.parent = parent; - this.prev = this; - this.next = this; - } - - // for data node construction - FBCNode(FBCNode prev, FBCNode next, @Nullable T data) { - this.parent = prev.parent; - this.prev = prev; - this.next = next; - this.data = data; - } - - void insertAfter(@Nullable T data) { - FBCNode n = new FBCNode<>(this, this.next, data); - this.next.prev = n; - this.next = n; - } + @NotOnlyInitialized FBCNode prev; + + @NotOnlyInitialized FBCNode next; + + @NotOnlyInitialized FBCList parent; + + @Nullable T data; + + // for sentinel construction + FBCNode(@UnderInitialization FBCList parent) { + this.parent = parent; + this.prev = this; + this.next = this; + } + + // for data node construction + FBCNode(FBCNode prev, FBCNode next, @Nullable T data) { + this.parent = prev.parent; + this.prev = prev; + this.next = next; + this.data = data; + } + + void insertAfter(@Nullable T data) { + FBCNode n = new FBCNode<>(this, this.next, data); + this.next.prev = n; + this.next = n; + } } diff --git a/checker/tests/initialization/FieldSuppressWarnings.java b/checker/tests/initialization/FieldSuppressWarnings.java index 7248baa3973..38b5498f535 100644 --- a/checker/tests/initialization/FieldSuppressWarnings.java +++ b/checker/tests/initialization/FieldSuppressWarnings.java @@ -1,29 +1,29 @@ public class FieldSuppressWarnings { - static class FieldSuppressWarnings1 { - // :: error: (initialization.field.uninitialized) - private Object notInitialized; - } + static class FieldSuppressWarnings1 { + // :: error: (initialization.field.uninitialized) + private Object notInitialized; + } - static class FieldSuppressWarnings2 { - @SuppressWarnings("initialization.field.uninitialized") - private Object notInitializedButSuppressed1; - } + static class FieldSuppressWarnings2 { + @SuppressWarnings("initialization.field.uninitialized") + private Object notInitializedButSuppressed1; + } - static class FieldSuppressWarnings3 { - @SuppressWarnings("initialization") - private Object notInitializedButSuppressed2; - } + static class FieldSuppressWarnings3 { + @SuppressWarnings("initialization") + private Object notInitializedButSuppressed2; + } - static class FieldSuppressWarnings4 { - private Object initialized1; + static class FieldSuppressWarnings4 { + private Object initialized1; - { - initialized1 = new Object(); - } + { + initialized1 = new Object(); } + } - static class FieldSuppressWarnings5 { - private Object initialized2 = new Object(); - } + static class FieldSuppressWarnings5 { + private Object initialized2 = new Object(); + } } diff --git a/checker/tests/initialization/FieldWithInit.java b/checker/tests/initialization/FieldWithInit.java index b42ea03adce..223ac5cef46 100644 --- a/checker/tests/initialization/FieldWithInit.java +++ b/checker/tests/initialization/FieldWithInit.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class FieldWithInit { - Object f = foo(); + Object f = foo(); - Object foo(@UnknownInitialization FieldWithInit this) { - return new Object(); - } + Object foo(@UnknownInitialization FieldWithInit this) { + return new Object(); + } } diff --git a/checker/tests/initialization/FlowFbc.java b/checker/tests/initialization/FlowFbc.java index e9feac6f8bb..770cae8040c 100644 --- a/checker/tests/initialization/FlowFbc.java +++ b/checker/tests/initialization/FlowFbc.java @@ -5,42 +5,42 @@ public class FlowFbc { - @NonNull String f; - @NotOnlyInitialized @NonNull String g; - - public FlowFbc(String arg) { - // :: error: (dereference.of.nullable) - f.toLowerCase(); - - // We get a dereference.of.nullable error by the Nullness Checker because g may be null, - // as well as a method.invocation.invalid error by the Initialization Checker because g - // is declared as @NotOnlyInitialized and thus may not be @Initialized, - // but toLowerCase()'s receiver type is, by default, @Initialized. - // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) - g.toLowerCase(); - - f = arg; - g = arg; - foo(); - f.toLowerCase(); - // :: error: (method.invocation.invalid) - g.toLowerCase(); - f = arg; + @NonNull String f; + @NotOnlyInitialized @NonNull String g; + + public FlowFbc(String arg) { + // :: error: (dereference.of.nullable) + f.toLowerCase(); + + // We get a dereference.of.nullable error by the Nullness Checker because g may be null, + // as well as a method.invocation.invalid error by the Initialization Checker because g + // is declared as @NotOnlyInitialized and thus may not be @Initialized, + // but toLowerCase()'s receiver type is, by default, @Initialized. + // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) + g.toLowerCase(); + + f = arg; + g = arg; + foo(); + f.toLowerCase(); + // :: error: (method.invocation.invalid) + g.toLowerCase(); + f = arg; + } + + void test() { + @Nullable String s = null; + s = "a"; + s.toLowerCase(); + } + + void test2(@Nullable String s) { + if (s != null) { + s.toLowerCase(); } + } - void test() { - @Nullable String s = null; - s = "a"; - s.toLowerCase(); - } - - void test2(@Nullable String s) { - if (s != null) { - s.toLowerCase(); - } - } - - void foo(@UnknownInitialization FlowFbc this) {} + void foo(@UnknownInitialization FlowFbc this) {} - // TODO Pure, etc. + // TODO Pure, etc. } diff --git a/checker/tests/initialization/GenericTest12b.java b/checker/tests/initialization/GenericTest12b.java index 6a0cc594a63..f76c266131f 100644 --- a/checker/tests/initialization/GenericTest12b.java +++ b/checker/tests/initialization/GenericTest12b.java @@ -2,21 +2,21 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class GenericTest12b { - class Cell {} + class Cell {} - class Node { - public Node(Cell userObject) {} + class Node { + public Node(Cell userObject) {} - void nodecall(@UnderInitialization Node this, Cell userObject) {} - } - - class RootNode extends Node { - public RootNode() { - super(new Cell()); - call(new Cell()); - nodecall(new Cell()); - } + void nodecall(@UnderInitialization Node this, Cell userObject) {} + } - void call(@UnderInitialization RootNode this, Cell userObject) {} + class RootNode extends Node { + public RootNode() { + super(new Cell()); + call(new Cell()); + nodecall(new Cell()); } + + void call(@UnderInitialization RootNode this, Cell userObject) {} + } } diff --git a/checker/tests/initialization/InstanceOf.java b/checker/tests/initialization/InstanceOf.java index 6ced9d107e7..6dbada5e77c 100644 --- a/checker/tests/initialization/InstanceOf.java +++ b/checker/tests/initialization/InstanceOf.java @@ -4,23 +4,23 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; class PptTopLevel { - class Ppt { - Object method() { - return ""; - } + class Ppt { + Object method() { + return ""; } + } - class OtherPpt extends Ppt {} + class OtherPpt extends Ppt {} } public class InstanceOf { - void foo(PptTopLevel.@UnknownInitialization(PptTopLevel.class) Ppt ppt) { - // :: error: (method.invocation.invalid) - ppt.method(); - if (ppt instanceof PptTopLevel.OtherPpt) { - PptTopLevel.OtherPpt pslice = (PptTopLevel.OtherPpt) ppt; - // :: error: (method.invocation.invalid) - String samp_str = " s" + pslice.method(); - } + void foo(PptTopLevel.@UnknownInitialization(PptTopLevel.class) Ppt ppt) { + // :: error: (method.invocation.invalid) + ppt.method(); + if (ppt instanceof PptTopLevel.OtherPpt) { + PptTopLevel.OtherPpt pslice = (PptTopLevel.OtherPpt) ppt; + // :: error: (method.invocation.invalid) + String samp_str = " s" + pslice.method(); } + } } diff --git a/checker/tests/initialization/Issue1044.java b/checker/tests/initialization/Issue1044.java index 8660957d65a..8a8d72c220d 100644 --- a/checker/tests/initialization/Issue1044.java +++ b/checker/tests/initialization/Issue1044.java @@ -5,69 +5,69 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1044 { - static class Inner1 { - // :: error: (initialization.field.uninitialized) - V f; - } + static class Inner1 { + // :: error: (initialization.field.uninitialized) + V f; + } - static class Inner2<@Nullable T extends @Nullable Object> { - // :: error: (initialization.field.uninitialized) - @NonNull T f; - } + static class Inner2<@Nullable T extends @Nullable Object> { + // :: error: (initialization.field.uninitialized) + @NonNull T f; + } - static class Inner3 { - V f; + static class Inner3 { + V f; - // :: error: (initialization.fields.uninitialized) - Inner3() {} - } + // :: error: (initialization.fields.uninitialized) + Inner3() {} + } - static class Inner4<@Nullable T extends @Nullable Object> { - @NonNull T f; + static class Inner4<@Nullable T extends @Nullable Object> { + @NonNull T f; - // :: error: (initialization.fields.uninitialized) - Inner4() {} - } + // :: error: (initialization.fields.uninitialized) + Inner4() {} + } - static class Inner5 { - @Nullable V f; - } + static class Inner5 { + @Nullable V f; + } - static class Inner6<@Nullable T extends @Nullable Object> { - T f; - } + static class Inner6<@Nullable T extends @Nullable Object> { + T f; + } - static class Inner7 { - @Nullable V f; + static class Inner7 { + @Nullable V f; - Inner7() {} - } + Inner7() {} + } - static class Inner8<@Nullable T extends @Nullable Object> { - T f; + static class Inner8<@Nullable T extends @Nullable Object> { + T f; - Inner8() {} - } + Inner8() {} + } - static class Inner9 { - // :: error: (initialization.field.uninitialized) - V f; - } + static class Inner9 { + // :: error: (initialization.field.uninitialized) + V f; + } - static class Inner10 { - V f; + static class Inner10 { + V f; - // :: error: (initialization.fields.uninitialized) - Inner10() {} - } + // :: error: (initialization.fields.uninitialized) + Inner10() {} + } - static class Inner11 { - @Nullable V f; - } + static class Inner11 { + @Nullable V f; + } - static class Inner12 { - @Nullable V f; + static class Inner12 { + @Nullable V f; - Inner12() {} - } + Inner12() {} + } } diff --git a/checker/tests/initialization/Issue1120.java b/checker/tests/initialization/Issue1120.java index 3cb2358100c..1cdd3709883 100644 --- a/checker/tests/initialization/Issue1120.java +++ b/checker/tests/initialization/Issue1120.java @@ -4,32 +4,32 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; class Issue1120Super { - Object f = new Object(); + Object f = new Object(); } final class Issue1120Sub extends Issue1120Super { - Object g; + Object g; - Issue1120Sub() { - this.party(); - // this is @UnderInitialization(A.class) - g = new Object(); - // this is @Initialized now - this.party(); - this.bar(); - } + Issue1120Sub() { + this.party(); + // this is @UnderInitialization(A.class) + g = new Object(); + // this is @Initialized now + this.party(); + this.bar(); + } - Issue1120Sub(int i) { - // this is @UnderInitialization(A.class) - this.party(); - // :: error: (method.invocation.invalid) - this.bar(); - g = new Object(); - } + Issue1120Sub(int i) { + // this is @UnderInitialization(A.class) + this.party(); + // :: error: (method.invocation.invalid) + this.bar(); + g = new Object(); + } - void bar() { - g.toString(); - } + void bar() { + g.toString(); + } - void party(@UnknownInitialization Issue1120Sub this) {} + void party(@UnknownInitialization Issue1120Sub this) {} } diff --git a/checker/tests/initialization/Issue1347.java b/checker/tests/initialization/Issue1347.java index b0e2702e6b1..b473f2ea522 100644 --- a/checker/tests/initialization/Issue1347.java +++ b/checker/tests/initialization/Issue1347.java @@ -2,23 +2,23 @@ // https://github.com/typetools/checker-framework/issues/1347 public class Issue1347 { - T t; - T t2; - Object o; + T t; + T t2; + Object o; - Issue1347(T t) { - this(t, 0); - } + Issue1347(T t) { + this(t, 0); + } - Issue1347(T t, int i) { - this.t = t; - this.t2 = t; - this.o = new Object(); - } + Issue1347(T t, int i) { + this.t = t; + this.t2 = t; + this.o = new Object(); + } - // :: error: (initialization.fields.uninitialized) - Issue1347(T t, String s) { - this.t = t; - this.o = new Object(); - } + // :: error: (initialization.fields.uninitialized) + Issue1347(T t, String s) { + this.t = t; + this.o = new Object(); + } } diff --git a/checker/tests/initialization/Issue3407.java b/checker/tests/initialization/Issue3407.java index 6bb7c3c1a62..9a0ebe9624b 100644 --- a/checker/tests/initialization/Issue3407.java +++ b/checker/tests/initialization/Issue3407.java @@ -1,20 +1,20 @@ // @below-java9-jdk-skip-test public class Issue3407 { - final String foo; + final String foo; - String getFoo() { - return foo; - } + String getFoo() { + return foo; + } - Issue3407() { - var anon = - new Object() { - String bar() { - // :: error: (method.invocation.invalid) - return Issue3407.this.getFoo().substring(1); - } - }; - anon.bar(); // / WHOOPS... NPE, `getFoo()` returns `foo` which is still null - this.foo = "Hello world"; - } + Issue3407() { + var anon = + new Object() { + String bar() { + // :: error: (method.invocation.invalid) + return Issue3407.this.getFoo().substring(1); + } + }; + anon.bar(); // / WHOOPS... NPE, `getFoo()` returns `foo` which is still null + this.foo = "Hello world"; + } } diff --git a/checker/tests/initialization/Issue408Init.java b/checker/tests/initialization/Issue408Init.java index 5ad4aad5921..bfa60a230cf 100644 --- a/checker/tests/initialization/Issue408Init.java +++ b/checker/tests/initialization/Issue408Init.java @@ -4,27 +4,27 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class Issue408Init { - static class Bar { - Bar() { - doFoo(); - } + static class Bar { + Bar() { + doFoo(); + } - String doFoo(@UnderInitialization Bar this) { - return ""; - } + String doFoo(@UnderInitialization Bar this) { + return ""; } + } - static class Baz extends Bar { - String myString = "hello"; + static class Baz extends Bar { + String myString = "hello"; - @Override - String doFoo(@UnderInitialization Baz this) { - // :: error: (dereference.of.nullable) - return myString.toLowerCase(); - } + @Override + String doFoo(@UnderInitialization Baz this) { + // :: error: (dereference.of.nullable) + return myString.toLowerCase(); } + } - public static void main(String[] args) { - new Baz(); - } + public static void main(String[] args) { + new Baz(); + } } diff --git a/checker/tests/initialization/Issue409.java b/checker/tests/initialization/Issue409.java index 34dc6e5c79a..0f7e46a7af6 100644 --- a/checker/tests/initialization/Issue409.java +++ b/checker/tests/initialization/Issue409.java @@ -4,32 +4,31 @@ // @skip-test until the issue is fixed public class Issue409 { - public static void main(String[] args) { - new Callback(); - } + public static void main(String[] args) { + new Callback(); + } } class Callback { - class MyProc { - public void call() { - doStuff(); - } + class MyProc { + public void call() { + doStuff(); } + } - String foo; + String foo; - Callback() { - MyProc p = new MyProc(); - // This call is illegal. It passes an @UnderInitialization outer this, but MyProc.call is - // declared to take an @Initialized outer this (whith is the default type). - // :: error: (method.invocation.invalid) - p.call(); - foo = "hello"; - } + Callback() { + MyProc p = new MyProc(); + // This call is illegal. It passes an @UnderInitialization outer this, but MyProc.call is + // declared to take an @Initialized outer this (whith is the default type). + // :: error: (method.invocation.invalid) + p.call(); + foo = "hello"; + } - void doStuff() { - System.out.println( - foo.toLowerCase()); // this line throws a NullPointerException at run time - } + void doStuff() { + System.out.println(foo.toLowerCase()); // this line throws a NullPointerException at run time + } } diff --git a/checker/tests/initialization/Issue4567.java b/checker/tests/initialization/Issue4567.java index fed5256e684..ecdb2f29321 100644 --- a/checker/tests/initialization/Issue4567.java +++ b/checker/tests/initialization/Issue4567.java @@ -3,9 +3,9 @@ public class Issue4567 { - public Issue4567() { - this(null); - } + public Issue4567() { + this(null); + } - protected Issue4567(final @UnderInitialization @Nullable Object variableScope) {} + protected Issue4567(final @UnderInitialization @Nullable Object variableScope) {} } diff --git a/checker/tests/initialization/Issue556a.java b/checker/tests/initialization/Issue556a.java index f0d80f6cad5..9618ccffeb7 100644 --- a/checker/tests/initialization/Issue556a.java +++ b/checker/tests/initialization/Issue556a.java @@ -7,11 +7,11 @@ public class Issue556a { - public static final Issue556a SELF = new Issue556a(); - private static final Object OBJ = new Object(); + public static final Issue556a SELF = new Issue556a(); + private static final Object OBJ = new Object(); - private Issue556a() { - // :: error: (assignment.type.incompatible) - @NonNull Object o = OBJ; - } + private Issue556a() { + // :: error: (assignment.type.incompatible) + @NonNull Object o = OBJ; + } } diff --git a/checker/tests/initialization/Issue556b.java b/checker/tests/initialization/Issue556b.java index c0414feb3ef..e1cc1543c15 100644 --- a/checker/tests/initialization/Issue556b.java +++ b/checker/tests/initialization/Issue556b.java @@ -15,82 +15,82 @@ // assumed to be initialized within the constructor. public class Issue556b { - static class Parent { - private final Object o; + static class Parent { + private final Object o; - public Parent(final Object o) { - this.o = o; - } - - @Override - public String toString() { - return o.toString(); - } + public Parent(final Object o) { + this.o = o; } - static class Child extends Parent { - public static final Child CHILD = new Child(); - private static final Object OBJ = new Object(); - - private Child() { - // This call should not be legal, because at the time that the call occurs, the static - // initializers of Child have not yet finished executing and therefore CHILD and OBJ are - // not necessarily initialized and are not necessarily non-null. - // :: error: (method.invocation.invalid) - super(OBJ); - } + @Override + public String toString() { + return o.toString(); + } + } + + static class Child extends Parent { + public static final Child CHILD = new Child(); + private static final Object OBJ = new Object(); + + private Child() { + // This call should not be legal, because at the time that the call occurs, the static + // initializers of Child have not yet finished executing and therefore CHILD and OBJ are + // not necessarily initialized and are not necessarily non-null. + // :: error: (method.invocation.invalid) + super(OBJ); } + } + + static class Child2 extends Parent { + public static final Child2 CHILD; + private static final Object OBJ; - static class Child2 extends Parent { - public static final Child2 CHILD; - private static final Object OBJ; - - static { - CHILD = new Child2(); - OBJ = new Object(); - } - - private Child2() { - // This call should not be legal, because at the time that the call occurs, the static - // initializers of Child have not yet finished executing and therefore CHILD and OBJ are - // not necessarily initialized and are not necessarily non-null. - // :: error: (method.invocation.invalid) - super(OBJ); - } + static { + CHILD = new Child2(); + OBJ = new Object(); } - // Changing the order of the OBJ and CHILD fields prevents a null pointer exception. - static class ChildOk1 extends Parent { - private static final Object OBJ = new Object(); - public static final Child CHILD = new Child(); - - private ChildOk1() { - // This call is legal, because OBJ is non-null at the time of the - // call. That's because OBJ is initialized before CHILD and - // therefore before the call to "new Child()". - super(OBJ); - } + private Child2() { + // This call should not be legal, because at the time that the call occurs, the static + // initializers of Child have not yet finished executing and therefore CHILD and OBJ are + // not necessarily initialized and are not necessarily non-null. + // :: error: (method.invocation.invalid) + super(OBJ); } + } + + // Changing the order of the OBJ and CHILD fields prevents a null pointer exception. + static class ChildOk1 extends Parent { + private static final Object OBJ = new Object(); + public static final Child CHILD = new Child(); + + private ChildOk1() { + // This call is legal, because OBJ is non-null at the time of the + // call. That's because OBJ is initialized before CHILD and + // therefore before the call to "new Child()". + super(OBJ); + } + } + + // Changing the order of the OBJ and CHILD field assignments prevents a null pointer exception. + static class ChildOk2 extends Parent { + public static final ChildOk2 CHILD; + private static final Object OBJ; - // Changing the order of the OBJ and CHILD field assignments prevents a null pointer exception. - static class ChildOk2 extends Parent { - public static final ChildOk2 CHILD; - private static final Object OBJ; - - static { - OBJ = new Object(); - CHILD = new ChildOk2(); - } - - private ChildOk2() { - // This call is legal, because OBJ is non-null at the time of the - // call. That's because OBJ is initialized before CHILD and - // therefore before the call to "new Child()". - super(OBJ); - } + static { + OBJ = new Object(); + CHILD = new ChildOk2(); } - public static void main(final String[] args) { - System.out.println(Child.CHILD); + private ChildOk2() { + // This call is legal, because OBJ is non-null at the time of the + // call. That's because OBJ is initialized before CHILD and + // therefore before the call to "new Child()". + super(OBJ); } + } + + public static void main(final String[] args) { + System.out.println(Child.CHILD); + } } diff --git a/checker/tests/initialization/Issue574.java b/checker/tests/initialization/Issue574.java index 6a26eec4ebf..ef97ed310a1 100644 --- a/checker/tests/initialization/Issue574.java +++ b/checker/tests/initialization/Issue574.java @@ -3,51 +3,51 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; @SuppressWarnings({ - // A warning is issued that fields are not initialized in the constructor. - // That is expected and it is not what is being verified in this test. - "initialization.field.uninitialized", - // Normally @UnknownInitialization is the only initialization annotation allowed on fields. - // However, for the purposes of this test, fields must be annotated with @UnderInitialization. - "initialization.invalid.field.type" + // A warning is issued that fields are not initialized in the constructor. + // That is expected and it is not what is being verified in this test. + "initialization.field.uninitialized", + // Normally @UnknownInitialization is the only initialization annotation allowed on fields. + // However, for the purposes of this test, fields must be annotated with @UnderInitialization. + "initialization.invalid.field.type" }) public class Issue574 { - @UnderInitialization(Object.class) Object o1; + @UnderInitialization(Object.class) Object o1; - @UnderInitialization(String.class) Object o2; + @UnderInitialization(String.class) Object o2; - @UnderInitialization(Character.class) Object o3; + @UnderInitialization(Character.class) Object o3; - @UnderInitialization(Number.class) Object o4; + @UnderInitialization(Number.class) Object o4; - @UnderInitialization(Double.class) Object o5; + @UnderInitialization(Double.class) Object o5; - @UnderInitialization(Integer.class) Object o6; + @UnderInitialization(Integer.class) Object o6; - @UnderInitialization(CharSequence.class) Object i1; // CharSequence is an interface + @UnderInitialization(CharSequence.class) Object i1; // CharSequence is an interface - void testLubOfClasses(boolean flag) { - @UnderInitialization(Object.class) Object l1 = flag ? o2 : o3; - @UnderInitialization(Number.class) Object l2 = flag ? o5 : o6; + void testLubOfClasses(boolean flag) { + @UnderInitialization(Object.class) Object l1 = flag ? o2 : o3; + @UnderInitialization(Number.class) Object l2 = flag ? o5 : o6; - @UnderInitialization(Object.class) Object l3 = flag ? o1 : o2; - @UnderInitialization(Object.class) Object l4 = flag ? o1 : o3; + @UnderInitialization(Object.class) Object l3 = flag ? o1 : o2; + @UnderInitialization(Object.class) Object l4 = flag ? o1 : o3; - @UnderInitialization(Number.class) Object l5 = flag ? o4 : o5; - @UnderInitialization(Number.class) Object l6 = flag ? o4 : o6; + @UnderInitialization(Number.class) Object l5 = flag ? o4 : o5; + @UnderInitialization(Number.class) Object l6 = flag ? o4 : o6; - // :: error: (assignment.type.incompatible) - @UnderInitialization(Character.class) Object l7 = flag ? o1 : o2; - // :: error: (assignment.type.incompatible) - @UnderInitialization(Integer.class) Object l8 = flag ? o4 : o5; - } + // :: error: (assignment.type.incompatible) + @UnderInitialization(Character.class) Object l7 = flag ? o1 : o2; + // :: error: (assignment.type.incompatible) + @UnderInitialization(Integer.class) Object l8 = flag ? o4 : o5; + } - void testLubOfClassesAndInterfaces(boolean flag) { - @UnderInitialization(Object.class) Object l1 = flag ? i1 : o3; + void testLubOfClassesAndInterfaces(boolean flag) { + @UnderInitialization(Object.class) Object l1 = flag ? i1 : o3; - @UnderInitialization(Object.class) Object l2 = flag ? o1 : i1; - @UnderInitialization(Object.class) Object l3 = flag ? o1 : o3; + @UnderInitialization(Object.class) Object l2 = flag ? o1 : i1; + @UnderInitialization(Object.class) Object l3 = flag ? o1 : o3; - // :: error: (assignment.type.incompatible) - @UnderInitialization(Character.class) Object l4 = flag ? o1 : i1; - } + // :: error: (assignment.type.incompatible) + @UnderInitialization(Character.class) Object l4 = flag ? o1 : i1; + } } diff --git a/checker/tests/initialization/Issue779.java b/checker/tests/initialization/Issue779.java index 1d2db241f76..97e49c2ee2d 100644 --- a/checker/tests/initialization/Issue779.java +++ b/checker/tests/initialization/Issue779.java @@ -5,28 +5,28 @@ import org.checkerframework.checker.nullness.qual.*; class A { - Object g = new Object(); + Object g = new Object(); - A() { - foo(); - } + A() { + foo(); + } - void foo(@UnderInitialization(A.class) A this) { - System.out.println("foo A " + g.toString()); - } + void foo(@UnderInitialization(A.class) A this) { + System.out.println("foo A " + g.toString()); + } } class B extends A { - Object f = new Object(); + Object f = new Object(); - void foo(@UnderInitialization(A.class) B this) { - // :: error: (dereference.of.nullable) - System.out.println("foo B " + this.f.toString()); - } + void foo(@UnderInitialization(A.class) B this) { + // :: error: (dereference.of.nullable) + System.out.println("foo B " + this.f.toString()); + } } public class Issue779 { - public static void main(String[] args) { - new B(); - } + public static void main(String[] args) { + new B(); + } } diff --git a/checker/tests/initialization/Issue813.java b/checker/tests/initialization/Issue813.java index 30689628c14..fc8187d6412 100644 --- a/checker/tests/initialization/Issue813.java +++ b/checker/tests/initialization/Issue813.java @@ -6,21 +6,21 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class Issue813 { - static interface MyInterface {} + static interface MyInterface {} - static class MyClass { - MyClass(@UnderInitialization MyInterface stuff) {} - } + static class MyClass { + MyClass(@UnderInitialization MyInterface stuff) {} + } - static class Fails implements MyInterface { - @NotOnlyInitialized MyClass bar = new MyClass(this); - } + static class Fails implements MyInterface { + @NotOnlyInitialized MyClass bar = new MyClass(this); + } - static class Works implements MyInterface { - @NotOnlyInitialized MyClass bar; + static class Works implements MyInterface { + @NotOnlyInitialized MyClass bar; - { - bar = new MyClass(this); // works - } + { + bar = new MyClass(this); // works } + } } diff --git a/checker/tests/initialization/Issue904.java b/checker/tests/initialization/Issue904.java index 19f2b844a00..f8c2806a64e 100644 --- a/checker/tests/initialization/Issue904.java +++ b/checker/tests/initialization/Issue904.java @@ -2,22 +2,22 @@ // https://github.com/typetools/checker-framework/issues/904 public class Issue904 { - final Object mBar; - final Runnable mRunnable = - new Runnable() { - @Override - public void run() { - // :: error: (dereference.of.nullable) - mBar.toString(); - } - }; + final Object mBar; + final Runnable mRunnable = + new Runnable() { + @Override + public void run() { + // :: error: (dereference.of.nullable) + mBar.toString(); + } + }; - public Issue904() { - mRunnable.run(); - mBar = ""; - } + public Issue904() { + mRunnable.run(); + mBar = ""; + } - public static void main(String[] args) { - new Issue904(); - } + public static void main(String[] args) { + new Issue904(); + } } diff --git a/checker/tests/initialization/Issue905.java b/checker/tests/initialization/Issue905.java index dda7b6e8d0a..ecd45675b48 100644 --- a/checker/tests/initialization/Issue905.java +++ b/checker/tests/initialization/Issue905.java @@ -4,25 +4,25 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class Issue905 { - final Object mBar; + final Object mBar; - Issue905() { - // this should be @UnderInitialization(Object.class), so this call should be forbidden. - // :: error: (method.invocation.invalid) - baz(); - mBar = ""; - } + Issue905() { + // this should be @UnderInitialization(Object.class), so this call should be forbidden. + // :: error: (method.invocation.invalid) + baz(); + mBar = ""; + } - Issue905(int i) { - mBar = ""; - baz(); - } + Issue905(int i) { + mBar = ""; + baz(); + } - void baz(@UnknownInitialization(Issue905.class) Issue905 this) { - mBar.toString(); - } + void baz(@UnknownInitialization(Issue905.class) Issue905 this) { + mBar.toString(); + } - public static void main(String[] args) { - new Issue905(); - } + public static void main(String[] args) { + new Issue905(); + } } diff --git a/checker/tests/initialization/NotOnlyInitializedTest.java b/checker/tests/initialization/NotOnlyInitializedTest.java index 690de5b4610..5b5fe382aaa 100644 --- a/checker/tests/initialization/NotOnlyInitializedTest.java +++ b/checker/tests/initialization/NotOnlyInitializedTest.java @@ -3,37 +3,37 @@ public class NotOnlyInitializedTest { - @NotOnlyInitialized NotOnlyInitializedTest f; - NotOnlyInitializedTest g; + @NotOnlyInitialized NotOnlyInitializedTest f; + NotOnlyInitializedTest g; - public NotOnlyInitializedTest() { - f = new NotOnlyInitializedTest(); - g = new NotOnlyInitializedTest(); - } + public NotOnlyInitializedTest() { + f = new NotOnlyInitializedTest(); + g = new NotOnlyInitializedTest(); + } - public NotOnlyInitializedTest(char i) { - // we can store something that is under initialization (like this) in f, but not in g - f = this; - // :: error: (assignment.type.incompatible) - g = this; - } + public NotOnlyInitializedTest(char i) { + // we can store something that is under initialization (like this) in f, but not in g + f = this; + // :: error: (assignment.type.incompatible) + g = this; + } - static void testDeref(NotOnlyInitializedTest o) { - // o is fully iniatlized, so we can dereference its fields - o.f.toString(); - o.g.toString(); - } + static void testDeref(NotOnlyInitializedTest o) { + // o is fully iniatlized, so we can dereference its fields + o.f.toString(); + o.g.toString(); + } - static void testDeref2(@UnderInitialization NotOnlyInitializedTest o) { - // o is not fully iniatlized, so we cannot dereference its fields. - // We thus get a dereference.of.nullable error by the Nullness Checker for both o.f and o.g. - // For o.f, we also get a method.invocation.invalid error by the Initialization Checker - // because o.f is declared as @NotOnlyInitialized and thus may not be @Initialized, - // but toLowerCase()'s receiver type is, by default, @Initialized. + static void testDeref2(@UnderInitialization NotOnlyInitializedTest o) { + // o is not fully iniatlized, so we cannot dereference its fields. + // We thus get a dereference.of.nullable error by the Nullness Checker for both o.f and o.g. + // For o.f, we also get a method.invocation.invalid error by the Initialization Checker + // because o.f is declared as @NotOnlyInitialized and thus may not be @Initialized, + // but toLowerCase()'s receiver type is, by default, @Initialized. - // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) - o.f.toString(); - // :: error: (dereference.of.nullable) - o.g.toString(); - } + // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) + o.f.toString(); + // :: error: (dereference.of.nullable) + o.g.toString(); + } } diff --git a/checker/tests/initialization/RawMethodInvocation.java b/checker/tests/initialization/RawMethodInvocation.java index 8aa9117238d..0a24048204c 100644 --- a/checker/tests/initialization/RawMethodInvocation.java +++ b/checker/tests/initialization/RawMethodInvocation.java @@ -4,46 +4,46 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class RawMethodInvocation { - @NonNull String a; - @NonNull String b; - - RawMethodInvocation(boolean constructor_inits_a) { - a = ""; - init_b(); - } - - @EnsuresNonNull("b") - void init_b(@UnknownInitialization RawMethodInvocation this) { - b = ""; - } - - // :: error: (initialization.fields.uninitialized) - RawMethodInvocation(Byte constructor_inits_b) { - init_b(); - } - - // :: error: (initialization.fields.uninitialized) - RawMethodInvocation(byte constructor_inits_b) { - b = ""; - init_b(); - } - - RawMethodInvocation(int constructor_inits_none) { - init_ab(); - } - - @EnsuresNonNull({"a", "b"}) - void init_ab(@UnknownInitialization RawMethodInvocation this) { - a = ""; - b = ""; - } - - RawMethodInvocation(long constructor_escapes_raw) { - a = ""; - // :: error: (method.invocation.invalid) - nonRawMethod(); - b = ""; - } - - void nonRawMethod() {} + @NonNull String a; + @NonNull String b; + + RawMethodInvocation(boolean constructor_inits_a) { + a = ""; + init_b(); + } + + @EnsuresNonNull("b") + void init_b(@UnknownInitialization RawMethodInvocation this) { + b = ""; + } + + // :: error: (initialization.fields.uninitialized) + RawMethodInvocation(Byte constructor_inits_b) { + init_b(); + } + + // :: error: (initialization.fields.uninitialized) + RawMethodInvocation(byte constructor_inits_b) { + b = ""; + init_b(); + } + + RawMethodInvocation(int constructor_inits_none) { + init_ab(); + } + + @EnsuresNonNull({"a", "b"}) + void init_ab(@UnknownInitialization RawMethodInvocation this) { + a = ""; + b = ""; + } + + RawMethodInvocation(long constructor_escapes_raw) { + a = ""; + // :: error: (method.invocation.invalid) + nonRawMethod(); + b = ""; + } + + void nonRawMethod() {} } diff --git a/checker/tests/initialization/RawTypesInit.java b/checker/tests/initialization/RawTypesInit.java index ec828c2c8d1..d4c727d4519 100644 --- a/checker/tests/initialization/RawTypesInit.java +++ b/checker/tests/initialization/RawTypesInit.java @@ -7,278 +7,278 @@ public class RawTypesInit { - class Bad { - @NonNull String field; - - public Bad() { - // :: error: (method.invocation.invalid) - this.init(); // error - // :: error: (method.invocation.invalid) - init(); // error - - this.field = "field"; // valid - // :: error: (assignment.type.incompatible) - this.field = null; // error - field = "field"; // valid - // :: error: (assignment.type.incompatible) - field = null; // error - } - - void init() { - output(this.field.length()); // valid - } + class Bad { + @NonNull String field; + + public Bad() { + // :: error: (method.invocation.invalid) + this.init(); // error + // :: error: (method.invocation.invalid) + init(); // error + + this.field = "field"; // valid + // :: error: (assignment.type.incompatible) + this.field = null; // error + field = "field"; // valid + // :: error: (assignment.type.incompatible) + field = null; // error } - class A { - @NonNull String field; - - public A() { - this.field = "field"; // valid - field = "field"; // valid - this.init(); // valid - init(); // valid - } - - public void init(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); - } - - public void initExpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(this.field); - } - - public void initImpl1(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(field.length()); - } - - public void initImpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(field); - } + void init() { + output(this.field.length()); // valid } + } - class B extends A { - @NonNull String otherField; - - public B() { - super(); - // :: error: (assignment.type.incompatible) - this.otherField = null; // error - this.otherField = "otherField"; // valid - } - - @Override - public void init(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); // error (TODO: substitution) - super.init(); // valid - } - - public void initImpl1(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(field.length()); // error (TODO: substitution) - } - - public void initExpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.otherField.length()); // error - } - - public void initImpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(otherField.length()); // error - } - - void other() { - init(); // valid - this.init(); // valid - } - - void otherRaw(@UnknownInitialization B this) { - init(); // valid - this.init(); // valid - } + class A { + @NonNull String field; + + public A() { + this.field = "field"; // valid + field = "field"; // valid + this.init(); // valid + init(); // valid } - class C extends B { + public void init(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); + } - // :: error: (initialization.field.uninitialized) - @NonNull String[] strings; + public void initExpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(this.field); + } + + public void initImpl1(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(field.length()); + } - @Override - public void init(@UnknownInitialization C this) { - // :: error: (dereference.of.nullable) - output(this.strings.length); // error - System.out.println(); // valid - } + public void initImpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(field); } + } - // To test whether the argument is @NonNull and @Initialized - static void output(@NonNull Object o) {} + class B extends A { + @NonNull String otherField; - class D extends C { - @Override - public void init(@UnknownInitialization D this) { - this.field = "s"; - output(this.field.length()); - } + public B() { + super(); + // :: error: (assignment.type.incompatible) + this.otherField = null; // error + this.otherField = "otherField"; // valid } - class MyTest { - Integer i; + @Override + public void init(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); // error (TODO: substitution) + super.init(); // valid + } - MyTest(int i) { - this.i = i; - } + public void initImpl1(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(field.length()); // error (TODO: substitution) + } - void myTest(@UnknownInitialization MyTest this) { - // :: error: (unboxing.of.nullable) - i = i + 1; - } + public void initExpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.otherField.length()); // error } - class AllFieldsInitialized { - Integer elapsedMillis = 0; - Integer startTime = 0; + public void initImpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(otherField.length()); // error + } - public AllFieldsInitialized() { - // :: error: (method.invocation.invalid) - nonRawMethod(); - } + void other() { + init(); // valid + this.init(); // valid + } - public void nonRawMethod() {} + void otherRaw(@UnknownInitialization B this) { + init(); // valid + this.init(); // valid } + } - class AFSIICell { - // :: error: (initialization.field.uninitialized) - AllFieldsSetInInitializer afsii; + class C extends B { + + // :: error: (initialization.field.uninitialized) + @NonNull String[] strings; + + @Override + public void init(@UnknownInitialization C this) { + // :: error: (dereference.of.nullable) + output(this.strings.length); // error + System.out.println(); // valid } + } - class AllFieldsSetInInitializer { - Integer elapsedMillis; - Integer startTime; - - public AllFieldsSetInInitializer() { - elapsedMillis = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); // error - startTime = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); // error - // :: error: (initialization.invalid.field.write.initialized) - new AFSIICell().afsii = this; - } - - // :: error: (initialization.fields.uninitialized) - public AllFieldsSetInInitializer(boolean b) { - // :: error: (method.invocation.invalid) - nonRawMethod(); // error - } - - public void nonRawMethod() {} + // To test whether the argument is @NonNull and @Initialized + static void output(@NonNull Object o) {} + + class D extends C { + @Override + public void init(@UnknownInitialization D this) { + this.field = "s"; + output(this.field.length()); } + } + + class MyTest { + Integer i; - class ConstructorInvocations { - Integer v; + MyTest(int i) { + this.i = i; + } - public ConstructorInvocations(int v) { - this.v = v; - } + void myTest(@UnknownInitialization MyTest this) { + // :: error: (unboxing.of.nullable) + i = i + 1; + } + } - public ConstructorInvocations() { - this(0); - // :: error: (method.invocation.invalid) - nonRawMethod(); // invalid - } + class AllFieldsInitialized { + Integer elapsedMillis = 0; + Integer startTime = 0; - public void nonRawMethod() {} + public AllFieldsInitialized() { + // :: error: (method.invocation.invalid) + nonRawMethod(); } - class MethodAccess { - public MethodAccess() { - @NonNull String s = string(); - } + public void nonRawMethod() {} + } + + class AFSIICell { + // :: error: (initialization.field.uninitialized) + AllFieldsSetInInitializer afsii; + } + + class AllFieldsSetInInitializer { + Integer elapsedMillis; + Integer startTime; + + public AllFieldsSetInInitializer() { + elapsedMillis = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); // error + startTime = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); // error + // :: error: (initialization.invalid.field.write.initialized) + new AFSIICell().afsii = this; + } - public @NonNull String string(@UnknownInitialization MethodAccess this) { - return "nonnull"; - } + // :: error: (initialization.fields.uninitialized) + public AllFieldsSetInInitializer(boolean b) { + // :: error: (method.invocation.invalid) + nonRawMethod(); // error } - void cast(@UnknownInitialization Object... args) { + public void nonRawMethod() {} + } - @SuppressWarnings("rawtypes") - // :: error: (assignment.type.incompatible) - Object[] argsNonRaw1 = args; + class ConstructorInvocations { + Integer v; - @SuppressWarnings("cast") - Object[] argsNonRaw2 = (Object[]) args; + public ConstructorInvocations(int v) { + this.v = v; } - class RawAfterConstructorBad { - Object o; + public ConstructorInvocations() { + this(0); + // :: error: (method.invocation.invalid) + nonRawMethod(); // invalid + } - // :: error: (initialization.fields.uninitialized) - RawAfterConstructorBad() {} + public void nonRawMethod() {} + } + + class MethodAccess { + public MethodAccess() { + @NonNull String s = string(); + } + + public @NonNull String string(@UnknownInitialization MethodAccess this) { + return "nonnull"; } + } + + void cast(@UnknownInitialization Object... args) { + + @SuppressWarnings("rawtypes") + // :: error: (assignment.type.incompatible) + Object[] argsNonRaw1 = args; + + @SuppressWarnings("cast") + Object[] argsNonRaw2 = (Object[]) args; + } + + class RawAfterConstructorBad { + Object o; + + // :: error: (initialization.fields.uninitialized) + RawAfterConstructorBad() {} + } - class RawAfterConstructorOK1 { - @Nullable Object o; + class RawAfterConstructorOK1 { + @Nullable Object o; - RawAfterConstructorOK1() {} + RawAfterConstructorOK1() {} + } + + class RawAfterConstructorOK2 { + Integer a; + + // :: error: (initialization.fields.uninitialized) + RawAfterConstructorOK2() {} + } + + // TODO: reinstate. This shows desired features, for initialization in + // a helper method rather than in the constructor. + class InitInHelperMethod { + Integer a; + Integer b; + + InitInHelperMethod(short constructor_inits_ab) { + a = 1; + b = 1; + // :: error: (method.invocation.invalid) + nonRawMethod(); } - class RawAfterConstructorOK2 { - Integer a; + InitInHelperMethod(boolean constructor_inits_a) { + a = 1; + init_b(); + // :: error: (method.invocation.invalid) + nonRawMethod(); + } + + @RequiresNonNull("a") + @EnsuresNonNull("b") + void init_b(@UnknownInitialization InitInHelperMethod this) { + b = 2; + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - // :: error: (initialization.fields.uninitialized) - RawAfterConstructorOK2() {} + InitInHelperMethod(int constructor_inits_none) { + init_ab(); + // :: error: (method.invocation.invalid) + nonRawMethod(); } - // TODO: reinstate. This shows desired features, for initialization in - // a helper method rather than in the constructor. - class InitInHelperMethod { - Integer a; - Integer b; - - InitInHelperMethod(short constructor_inits_ab) { - a = 1; - b = 1; - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - InitInHelperMethod(boolean constructor_inits_a) { - a = 1; - init_b(); - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - @RequiresNonNull("a") - @EnsuresNonNull("b") - void init_b(@UnknownInitialization InitInHelperMethod this) { - b = 2; - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - InitInHelperMethod(int constructor_inits_none) { - init_ab(); - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - @EnsuresNonNull({"a", "b"}) - void init_ab(@UnknownInitialization InitInHelperMethod this) { - a = 1; - b = 2; - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - void nonRawMethod() {} + @EnsuresNonNull({"a", "b"}) + void init_ab(@UnknownInitialization InitInHelperMethod this) { + a = 1; + b = 2; + // :: error: (method.invocation.invalid) + nonRawMethod(); } + + void nonRawMethod() {} + } } diff --git a/checker/tests/initialization/ReceiverSuperInvocation.java b/checker/tests/initialization/ReceiverSuperInvocation.java index 6ec0be1e7cb..47d24ff8e34 100644 --- a/checker/tests/initialization/ReceiverSuperInvocation.java +++ b/checker/tests/initialization/ReceiverSuperInvocation.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class ReceiverSuperInvocation { - void foo(@UnderInitialization(ReceiverSuperInvocation.class) ReceiverSuperInvocation this) {} + void foo(@UnderInitialization(ReceiverSuperInvocation.class) ReceiverSuperInvocation this) {} } class ReceiverSuperInvocationSubclass extends ReceiverSuperInvocation { - @Override - void foo(@UnderInitialization(Object.class) ReceiverSuperInvocationSubclass this) { - // :: error: (method.invocation.invalid) - super.foo(); - } + @Override + void foo(@UnderInitialization(Object.class) ReceiverSuperInvocationSubclass this) { + // :: error: (method.invocation.invalid) + super.foo(); + } } diff --git a/checker/tests/initialization/SimpleFbc.java b/checker/tests/initialization/SimpleFbc.java index 967608be45c..c9c39df89c4 100644 --- a/checker/tests/initialization/SimpleFbc.java +++ b/checker/tests/initialization/SimpleFbc.java @@ -7,51 +7,51 @@ public class SimpleFbc { - SimpleFbc f; - @NotOnlyInitialized SimpleFbc g; + SimpleFbc f; + @NotOnlyInitialized SimpleFbc g; - @Pure - int pure() { - return 1; - } + @Pure + int pure() { + return 1; + } - // :: error: (initialization.fields.uninitialized) - public SimpleFbc(String arg) {} + // :: error: (initialization.fields.uninitialized) + public SimpleFbc(String arg) {} - void test() { - @NonNull String s = "234"; + void test() { + @NonNull String s = "234"; - // :: error: (assignment.type.incompatible) - s = null; - System.out.println(s); - } + // :: error: (assignment.type.incompatible) + s = null; + System.out.println(s); + } - void test2(@UnknownInitialization @NonNull SimpleFbc t) { - // :: error: (assignment.type.incompatible) - @Initialized @NonNull SimpleFbc a = t.f; - } + void test2(@UnknownInitialization @NonNull SimpleFbc t) { + // :: error: (assignment.type.incompatible) + @Initialized @NonNull SimpleFbc a = t.f; + } - // check initialized-only semantics for fields - void test3(@UnknownInitialization @NonNull SimpleFbc t) { - @Initialized @Nullable SimpleFbc a = t.f; + // check initialized-only semantics for fields + void test3(@UnknownInitialization @NonNull SimpleFbc t) { + @Initialized @Nullable SimpleFbc a = t.f; - // :: error: (assignment.type.incompatible) - @Initialized @Nullable SimpleFbc b = t.g; - } + // :: error: (assignment.type.incompatible) + @Initialized @Nullable SimpleFbc b = t.g; + } - void simplestTestEver() { - @NonNull String a = "abc"; + void simplestTestEver() { + @NonNull String a = "abc"; - // :: error: (assignment.type.incompatible) - a = null; + // :: error: (assignment.type.incompatible) + a = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = null; - } + // :: error: (assignment.type.incompatible) + @NonNull String b = null; + } - void anotherMethod() { - @Nullable String s = null; + void anotherMethod() { + @Nullable String s = null; - @Initialized @Nullable String t = s; - } + @Initialized @Nullable String t = s; + } } diff --git a/checker/tests/initialization/StaticInit.java b/checker/tests/initialization/StaticInit.java index 64f15adc396..01827e0fd50 100644 --- a/checker/tests/initialization/StaticInit.java +++ b/checker/tests/initialization/StaticInit.java @@ -4,9 +4,9 @@ public class StaticInit { - static String a; + static String a; - static { - a.toString(); - } + static { + a.toString(); + } } diff --git a/checker/tests/initialization/Subtyping.java b/checker/tests/initialization/Subtyping.java index 4ea0b1b36da..f302126929f 100644 --- a/checker/tests/initialization/Subtyping.java +++ b/checker/tests/initialization/Subtyping.java @@ -2,50 +2,50 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class Subtyping { - void test1( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - // ::error: (assignment.type.incompatible) - underObject = unknownObject; - underObject = underSubtyping; - // ::error: (assignment.type.incompatible) - underObject = unknownSubtyping; - } + void test1( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + // ::error: (assignment.type.incompatible) + underObject = unknownObject; + underObject = underSubtyping; + // ::error: (assignment.type.incompatible) + underObject = unknownSubtyping; + } - void test2( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - unknownObject = underSubtyping; - unknownObject = unknownSubtyping; - unknownObject = underObject; - } + void test2( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + unknownObject = underSubtyping; + unknownObject = unknownSubtyping; + unknownObject = underObject; + } - void test3( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - // ::error: (assignment.type.incompatible) - underSubtyping = unknownObject; - // ::error: (assignment.type.incompatible) - underSubtyping = unknownSubtyping; - // ::error: (assignment.type.incompatible) - underSubtyping = underObject; - } + void test3( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + // ::error: (assignment.type.incompatible) + underSubtyping = unknownObject; + // ::error: (assignment.type.incompatible) + underSubtyping = unknownSubtyping; + // ::error: (assignment.type.incompatible) + underSubtyping = underObject; + } - void test4( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - // ::error: (assignment.type.incompatible) - unknownSubtyping = unknownObject; - unknownSubtyping = underSubtyping; - // ::error: (assignment.type.incompatible) - unknownSubtyping = underObject; - } + void test4( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + // ::error: (assignment.type.incompatible) + unknownSubtyping = unknownObject; + unknownSubtyping = underSubtyping; + // ::error: (assignment.type.incompatible) + unknownSubtyping = underObject; + } } diff --git a/checker/tests/initialization/Suppression.java b/checker/tests/initialization/Suppression.java index 4c96654b453..ebc53041e72 100644 --- a/checker/tests/initialization/Suppression.java +++ b/checker/tests/initialization/Suppression.java @@ -6,13 +6,13 @@ public class Suppression { - Suppression t; + Suppression t; - @SuppressWarnings("initialization.fields.uninitialized") - public Suppression(Suppression arg) {} + @SuppressWarnings("initialization.fields.uninitialized") + public Suppression(Suppression arg) {} - @SuppressWarnings({"initialization"}) - void foo(@UnknownInitialization Suppression arg) { - t = arg; // initialization error - } + @SuppressWarnings({"initialization"}) + void foo(@UnknownInitialization Suppression arg) { + t = arg; // initialization error + } } diff --git a/checker/tests/initialization/TestPolyInitialized.java b/checker/tests/initialization/TestPolyInitialized.java index 0e34a2ec713..45caa073e42 100644 --- a/checker/tests/initialization/TestPolyInitialized.java +++ b/checker/tests/initialization/TestPolyInitialized.java @@ -5,44 +5,44 @@ public class TestPolyInitialized { - @NotOnlyInitialized String testStr; - - String test = "test"; - - TestPolyInitialized(@UnknownInitialization String str) { - this.testStr = identity(str); - // :: error: (assignment.type.incompatible) - this.test = identity(str); - } - - @PolyInitialized String identity(@UnknownInitialization TestPolyInitialized this, @PolyInitialized String str) { - return str; - } - - void test1() { - @UnknownInitialization String receiver = identity(testStr); - } - - void test2() { - @Initialized String receiver = identity(test); - } - - @Initialized String test3(@UnknownInitialization String str) { - @UnknownInitialization String localStr = str; - // :: error: (return.type.incompatible) - return identity(str); - } - - @UnknownInitialization String test4(@Initialized String str) { - return identity(str); - } - - @UnknownInitialization(Object.class) String test5(@Initialized String str) { - return identity(str); - } - - @Initialized String test6(@UnknownInitialization(Object.class) String str) { - // :: error: (return.type.incompatible) - return identity(str); - } + @NotOnlyInitialized String testStr; + + String test = "test"; + + TestPolyInitialized(@UnknownInitialization String str) { + this.testStr = identity(str); + // :: error: (assignment.type.incompatible) + this.test = identity(str); + } + + @PolyInitialized String identity(@UnknownInitialization TestPolyInitialized this, @PolyInitialized String str) { + return str; + } + + void test1() { + @UnknownInitialization String receiver = identity(testStr); + } + + void test2() { + @Initialized String receiver = identity(test); + } + + @Initialized String test3(@UnknownInitialization String str) { + @UnknownInitialization String localStr = str; + // :: error: (return.type.incompatible) + return identity(str); + } + + @UnknownInitialization String test4(@Initialized String str) { + return identity(str); + } + + @UnknownInitialization(Object.class) String test5(@Initialized String str) { + return identity(str); + } + + @Initialized String test6(@UnknownInitialization(Object.class) String str) { + // :: error: (return.type.incompatible) + return identity(str); + } } diff --git a/checker/tests/initialization/TryFinally.java b/checker/tests/initialization/TryFinally.java index 91c8e636ddc..ff5d973fc6f 100644 --- a/checker/tests/initialization/TryFinally.java +++ b/checker/tests/initialization/TryFinally.java @@ -21,31 +21,31 @@ public class TryFinally {} class TestCabsentFabsent { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private final String foo; + private final String foo; - public TestCabsentFabsent() { - this.foo = getFoo(); - } + public TestCabsentFabsent() { + this.foo = getFoo(); + } } class TestCabsentFnoaction { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private final String foo; + private final String foo; - public TestCabsentFnoaction() { - try { - this.foo = getFoo(); - } finally { - // no action in finally clause - } + public TestCabsentFnoaction() { + try { + this.foo = getFoo(); + } finally { + // no action in finally clause } + } } // Not legal in Java: error: variable foo might not have been initialized @@ -217,237 +217,237 @@ public TestCabsentFnoaction() { // } class TestCabsentFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCabsentFabsentNonfinal() { - this.foo = getFoo(); - } + public TestCabsentFabsentNonfinal() { + this.foo = getFoo(); + } } class TestCabsentFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCabsentFnoactionNonfinal() { - try { - this.foo = getFoo(); - } finally { - // no action in finally clause - } + public TestCabsentFnoactionNonfinal() { + try { + this.foo = getFoo(); + } finally { + // no action in finally clause } + } } class TestCtnoactionFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - // :: error: (initialization.fields.uninitialized) - public TestCtnoactionFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - // no action on exception - } + // :: error: (initialization.fields.uninitialized) + public TestCtnoactionFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + // no action on exception } + } } class TestCtnoactionFnoactionNonfinal { - static String getFoo() { - return "foo"; - } - - private String foo; - - // :: error: (initialization.fields.uninitialized) - public TestCtnoactionFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - // no action on exception - } finally { - // no action in finally clause - } - } + static String getFoo() { + return "foo"; + } + + private String foo; + + // :: error: (initialization.fields.uninitialized) + public TestCtnoactionFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + // no action on exception + } finally { + // no action in finally clause + } + } } class TestCtmethodFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtmethodFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = getFoo(); - } + public TestCtmethodFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = getFoo(); } + } } class TestCtmethodFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtmethodFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = getFoo(); - } finally { - // no action in finally clause - } + public TestCtmethodFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = getFoo(); + } finally { + // no action in finally clause } + } } class TestCtstringFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtstringFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = "foo"; - } + public TestCtstringFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = "foo"; } + } } class TestCtstringFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtstringFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = "foo"; - } finally { - // no action in finally clause - } + public TestCtstringFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = "foo"; + } finally { + // no action in finally clause } + } } class TestCenoactionFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - // :: error: (initialization.fields.uninitialized) - public TestCenoactionFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - // no action on exception - } + // :: error: (initialization.fields.uninitialized) + public TestCenoactionFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + // no action on exception } + } } class TestCenoactionFnoactionNonfinal { - static String getFoo() { - return "foo"; - } - - private String foo; - - // :: error: (initialization.fields.uninitialized) - public TestCenoactionFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - // no action on exception - } finally { - // no action in finally clause - } - } + static String getFoo() { + return "foo"; + } + + private String foo; + + // :: error: (initialization.fields.uninitialized) + public TestCenoactionFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + // no action on exception + } finally { + // no action in finally clause + } + } } class TestCemethodFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCemethodFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = getFoo(); - } + public TestCemethodFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = getFoo(); } + } } class TestCemethodFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCemethodFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = getFoo(); - } finally { - // no action in finally clause - } + public TestCemethodFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = getFoo(); + } finally { + // no action in finally clause } + } } class TestCestringFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCestringFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = "foo"; - } + public TestCestringFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = "foo"; } + } } class TestCestringFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCestringFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = "foo"; - } finally { - // no action in finally clause - } + public TestCestringFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = "foo"; + } finally { + // no action in finally clause } + } } diff --git a/checker/tests/initialization/TryFinally2.java b/checker/tests/initialization/TryFinally2.java index ce57f1d75b1..dcc1a832335 100644 --- a/checker/tests/initialization/TryFinally2.java +++ b/checker/tests/initialization/TryFinally2.java @@ -1,31 +1,30 @@ // Test case for Issue 1500: // https://github.com/typetools/checker-framework/issues/1500 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.io.InputStream; +import org.checkerframework.checker.nullness.qual.Nullable; public class TryFinally2 { - @SuppressWarnings("nullness") // dummy implementation - Process getProcess() { - return null; - } + @SuppressWarnings("nullness") // dummy implementation + Process getProcess() { + return null; + } - void performCommand() { - Process proc = null; - InputStream in = null; - try { - proc = getProcess(); - in = proc.getInputStream(); - return; - } finally { - closeQuietly(in); - if (proc != null) { - proc.destroy(); - } - } + void performCommand() { + Process proc = null; + InputStream in = null; + try { + proc = getProcess(); + in = proc.getInputStream(); + return; + } finally { + closeQuietly(in); + if (proc != null) { + proc.destroy(); + } } + } - public static void closeQuietly(final @Nullable InputStream input) {} + public static void closeQuietly(final @Nullable InputStream input) {} } diff --git a/checker/tests/initialization/TryFinallyBreak.java b/checker/tests/initialization/TryFinallyBreak.java index c78d8fd7b7a..9d9eec468be 100644 --- a/checker/tests/initialization/TryFinallyBreak.java +++ b/checker/tests/initialization/TryFinallyBreak.java @@ -2,571 +2,571 @@ // https://github.com/typetools/checker-framework/issues/548 public class TryFinallyBreak { - String testWhile1() { - String ans = "x"; - while (this.hashCode() > 10000) { - try { - // empty body - } finally { - ans = null; - } + String testWhile1() { + String ans = "x"; + while (this.hashCode() > 10000) { + try { + // empty body + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile2() { + String ans = "x"; + while (true) { + try { + // Note the additional break; + break; + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile3() { + String ans = "x"; + while (true) { + try { + testWhile3(); + } catch (Exception e) { + break; + } finally { + ans = null; + } + ans = "x"; + } + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile4() { + String ans = "x"; + while (true) { + if (true) { + try { + break; + } finally { + ans = null; + break; } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile2() { - String ans = "x"; - while (true) { - try { - // Note the additional break; - break; - } finally { - ans = null; - } + } + ans = "x"; + } + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile5() { + String ans = "x"; + while (true) { + while (true) { + try { + // Note the additional break; + break; + } finally { + ans = null; } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile3() { - String ans = "x"; - while (true) { - try { - testWhile3(); - } catch (Exception e) { - break; - } finally { - ans = null; - } - ans = "x"; + } + ans = "x"; + break; + } + return ans; + } + + String testWhile6(boolean cond) { + String ans = "x"; + OUTER: + while (cond) { + while (cond) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile4() { - String ans = "x"; - while (true) { - if (true) { - try { - break; - } finally { - ans = null; - break; - } - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile5() { - String ans = "x"; - while (true) { - while (true) { - try { - // Note the additional break; - break; - } finally { - ans = null; - } + } + ans = "x"; + } + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile7(boolean cond) { + String ans = "x"; + OUTER: + while (cond) { + try { + while (cond) { + try { + if (cond) { + break OUTER; } - ans = "x"; - break; + } finally { + ans = null; + } } - return ans; - } - - String testWhile6(boolean cond) { - String ans = "x"; - OUTER: + } finally { + ans = "x"; + } + } + return ans; + } + + String testWhile8(boolean cond) { + String ans = "x"; + OUTER: + while (cond) { + try { while (cond) { - while (cond) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; - } + try { + if (cond) { + break OUTER; } + } finally { ans = "x"; + } } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile7(boolean cond) { - String ans = "x"; - OUTER: - while (cond) { - try { - while (cond) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; - } - } - } finally { - ans = "x"; - } + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile1() { + String ans = "x"; + do { + try { + // empty body + } finally { + ans = null; + } + } while (this.hashCode() > 10000); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile2() { + String ans = "x"; + do { + try { + // Note the additional break; + break; + } finally { + ans = null; + } + } while (true); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile3() { + String ans = "x"; + do { + try { + testWhile3(); + } catch (Exception e) { + break; + } finally { + ans = null; + } + ans = "x"; + } while (true); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile4() { + String ans = "x"; + do { + if (true) { + try { + break; + } finally { + ans = null; + break; } - return ans; - } - - String testWhile8(boolean cond) { - String ans = "x"; - OUTER: - while (cond) { - try { - while (cond) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = "x"; - } - } - } finally { - ans = null; - } + } + ans = "x"; + } while (true); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile5() { + String ans = "x"; + do { + do { + try { + // Note the additional break; + break; + } finally { + ans = null; } - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile1() { - String ans = "x"; - do { - try { - // empty body - } finally { - ans = null; - } - } while (this.hashCode() > 10000); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile2() { - String ans = "x"; - do { - try { - // Note the additional break; - break; - } finally { - ans = null; - } - } while (true); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile3() { - String ans = "x"; - do { - try { - testWhile3(); - } catch (Exception e) { - break; - } finally { - ans = null; - } - ans = "x"; - } while (true); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile4() { - String ans = "x"; - do { - if (true) { - try { - break; - } finally { - ans = null; - break; - } - } - ans = "x"; - } while (true); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile5() { - String ans = "x"; - do { - do { - try { - // Note the additional break; - break; - } finally { - ans = null; - } - } while (true); - ans = "x"; - break; - } while (true); - return ans; - } - - String testDoWhile6(boolean cond) { - String ans = "x"; - OUTER: - do { - do { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; - } - } while (cond); - ans = "x"; - } while (cond); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile7(boolean cond) { - String ans = "x"; - OUTER: + } while (true); + ans = "x"; + break; + } while (true); + return ans; + } + + String testDoWhile6(boolean cond) { + String ans = "x"; + OUTER: + do { + do { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; + } + } while (cond); + ans = "x"; + } while (cond); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile7(boolean cond) { + String ans = "x"; + OUTER: + do { + try { do { - try { - do { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; - } - } while (cond); - } finally { - ans = "x"; + try { + if (cond) { + break OUTER; } + } finally { + ans = null; + } } while (cond); - return ans; - } - - String testDoWhile8(boolean cond) { - String ans = "x"; - OUTER: + } finally { + ans = "x"; + } + } while (cond); + return ans; + } + + String testDoWhile8(boolean cond) { + String ans = "x"; + OUTER: + do { + try { do { - try { - do { - try { - if (cond) { - break OUTER; - } - } finally { - ans = "x"; - } - } while (cond); - } finally { - ans = null; + try { + if (cond) { + break OUTER; } + } finally { + ans = "x"; + } } while (cond); - // :: error: (return.type.incompatible) - return ans; - } - - String testFor1() { - String ans = "x"; - for (; this.hashCode() > 10000; ) { - try { - // empty body - } finally { - ans = null; - } + } finally { + ans = null; + } + } while (cond); + // :: error: (return.type.incompatible) + return ans; + } + + String testFor1() { + String ans = "x"; + for (; this.hashCode() > 10000; ) { + try { + // empty body + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testFor2() { + String ans = "x"; + for (; ; ) { + try { + // Note the additional break; + break; + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testFor3() { + String ans = "x"; + for (; ; ) { + try { + testFor3(); + } catch (Exception e) { + break; + } finally { + ans = null; + } + ans = "x"; + } + // :: error: (return.type.incompatible) + return ans; + } + + String testFor4() { + String ans = "x"; + for (; ; ) { + if (true) { + try { + break; + } finally { + ans = null; + break; } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor2() { - String ans = "x"; - for (; ; ) { - try { - // Note the additional break; - break; - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor3() { - String ans = "x"; - for (; ; ) { - try { - testFor3(); - } catch (Exception e) { - break; - } finally { - ans = null; - } - ans = "x"; + } + ans = "x"; + } + // :: error: (return.type.incompatible) + return ans; + } + + String testFor5() { + String ans = "x"; + for (; ; ) { + for (; ; ) { + try { + // Note the additional break; + break; + } finally { + ans = null; } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor4() { - String ans = "x"; - for (; ; ) { - if (true) { - try { - break; - } finally { - ans = null; - break; - } - } - ans = "x"; + } + ans = "x"; + break; + } + return ans; + } + + String testFor6(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + for (; cond; ) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor5() { - String ans = "x"; + } + ans = "x"; + } + // :: error: (return.type.incompatible) + return ans; + } + + String testFor7(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + try { for (; ; ) { - for (; ; ) { - try { - // Note the additional break; - break; - } finally { - ans = null; - } + try { + if (cond) { + break OUTER; } - ans = "x"; - break; + } finally { + ans = null; + } } - return ans; - } - - String testFor6(boolean cond) { - String ans = "x"; - OUTER: + } finally { + ans = "x"; + } + } + return ans; + } + + String testFor8(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + try { for (; ; ) { - for (; cond; ) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; - } + try { + if (cond) { + break OUTER; } + } finally { ans = "x"; + } } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor7(boolean cond) { - String ans = "x"; - OUTER: - for (; ; ) { - try { - for (; ; ) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; - } - } - } finally { - ans = "x"; - } - } - return ans; - } - - String testFor8(boolean cond) { - String ans = "x"; - OUTER: - for (; ; ) { - try { - for (; ; ) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = "x"; - } - } - } finally { - ans = null; - } + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testIf1() { + String ans = "x"; + IF: + if (true) { + try { + break IF; + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testIf2(boolean cond) { + String ans = "x"; + IF: + if (cond) { + if (cond) { + try { + if (cond) { + break IF; + } + } finally { + ans = null; } - // :: error: (return.type.incompatible) - return ans; - } - - String testIf1() { - String ans = "x"; - IF: - if (true) { - try { - break IF; - } finally { - ans = null; + } + ans = "x"; + } + // :: error: (return.type.incompatible) + return ans; + } + + String testIf3(boolean cond) { + String ans = "x"; + IF: + if (cond) { + try { + if (cond) { + try { + if (cond) { + break IF; } + } finally { + ans = null; + } } - // :: error: (return.type.incompatible) - return ans; - } - - String testIf2(boolean cond) { - String ans = "x"; - IF: + } finally { + ans = "x"; + } + } + return ans; + } + + String testIf4(boolean cond) { + String ans = "x"; + IF: + if (cond) { + try { if (cond) { + try { if (cond) { - try { - if (cond) { - break IF; - } - } finally { - ans = null; - } + break IF; } + } finally { ans = "x"; + } } - // :: error: (return.type.incompatible) - return ans; - } - - String testIf3(boolean cond) { - String ans = "x"; - IF: - if (cond) { - try { - if (cond) { - try { - if (cond) { - break IF; - } - } finally { - ans = null; - } - } - } finally { - ans = "x"; - } + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testSwitch1() { + String ans = "x"; + switch (ans) { + case "x": + try { + break; + } finally { + ans = null; } - return ans; } + // :: error: (return.type.incompatible) + return ans; + } - String testIf4(boolean cond) { - String ans = "x"; - IF: - if (cond) { + String testSwitch2(boolean cond) { + String ans = "x"; + SWITCH: + switch (ans) { + case "x": + switch (ans) { + case "x": try { - if (cond) { - try { - if (cond) { - break IF; - } - } finally { - ans = "x"; - } - } + break SWITCH; } finally { - ans = null; + ans = null; } } - // :: error: (return.type.incompatible) - return ans; - } - - String testSwitch1() { - String ans = "x"; - switch (ans) { - case "x": - try { - break; - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; } - - String testSwitch2(boolean cond) { - String ans = "x"; - SWITCH: - switch (ans) { + // :: error: (return.type.incompatible) + return ans; + } + + String testSwitch3(boolean cond) { + String ans = "x"; + SWITCH: + switch (ans) { + case "x": + try { + switch (ans) { case "x": - switch (ans) { - case "x": - try { - break SWITCH; - } finally { - ans = null; - } - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testSwitch3(boolean cond) { - String ans = "x"; - SWITCH: - switch (ans) { - case "x": - try { - switch (ans) { - case "x": - try { - break SWITCH; - } finally { - ans = null; - } - } - } finally { - ans = "x"; - } + try { + break SWITCH; + } finally { + ans = null; + } + } + } finally { + ans = "x"; } - return ans; } + return ans; + } - String testSwitch4(boolean cond) { - String ans = "x"; - SWITCH: - switch (ans) { + String testSwitch4(boolean cond) { + String ans = "x"; + SWITCH: + switch (ans) { + case "x": + try { + switch (ans) { case "x": - try { - switch (ans) { - case "x": - try { - break SWITCH; - } finally { - ans = "x"; - } - } - } finally { - ans = null; - } + try { + break SWITCH; + } finally { + ans = "x"; + } + } + } finally { + ans = null; } - // :: error: (return.type.incompatible) - return ans; } + // :: error: (return.type.incompatible) + return ans; + } } diff --git a/checker/tests/initialization/TryFinallyContinue.java b/checker/tests/initialization/TryFinallyContinue.java index 6b22d5bcb04..62285aec6f1 100644 --- a/checker/tests/initialization/TryFinallyContinue.java +++ b/checker/tests/initialization/TryFinallyContinue.java @@ -2,123 +2,123 @@ // https://github.com/typetools/checker-framework/issues/548 public class TryFinallyContinue { - String testWhile1() { - String ans = "x"; - while (true) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } - if (true) { - try { - continue; - } finally { - ans = null; - } - } - ans = "x"; + String testWhile1() { + String ans = "x"; + while (true) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } + if (true) { + try { + continue; + } finally { + ans = null; } + } + ans = "x"; } + } - String testWhile2(boolean cond) { - String ans = "x"; - while (cond) { - if (true) { - return ans; - } - try { - ans = null; - continue; - } finally { - ans = "x"; - } - } + String testWhile2(boolean cond) { + String ans = "x"; + while (cond) { + if (true) { return ans; + } + try { + ans = null; + continue; + } finally { + ans = "x"; + } } + return ans; + } - String testWhile3(boolean cond) { - String ans = "x"; - OUTER: - while (true) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } + String testWhile3(boolean cond) { + String ans = "x"; + OUTER: + while (true) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } + try { + while (cond) { + if (true) { try { - while (cond) { - if (true) { - try { - continue OUTER; - } finally { - ans = "x"; - } - } - } + continue OUTER; } finally { - ans = null; + ans = "x"; } - ans = "x"; + } } + } finally { + ans = null; + } + ans = "x"; } + } - String testFor1() { - String ans = "x"; - for (; ; ) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } - if (true) { - try { - continue; - } finally { - ans = null; - } - } - ans = "x"; + String testFor1() { + String ans = "x"; + for (; ; ) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } + if (true) { + try { + continue; + } finally { + ans = null; } + } + ans = "x"; } + } - String testFor2(boolean cond) { - String ans = "x"; - for (; cond; ) { - if (true) { - return ans; - } - try { - ans = null; - continue; - } finally { - ans = "x"; - } - } + String testFor2(boolean cond) { + String ans = "x"; + for (; cond; ) { + if (true) { return ans; + } + try { + ans = null; + continue; + } finally { + ans = "x"; + } } + return ans; + } - String testFor3(boolean cond) { - String ans = "x"; - OUTER: - for (; ; ) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } + String testFor3(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } + try { + for (; cond; ) { + if (true) { try { - for (; cond; ) { - if (true) { - try { - continue OUTER; - } finally { - ans = "x"; - } - } - } + continue OUTER; } finally { - ans = null; + ans = "x"; } - ans = "x"; + } } + } finally { + ans = null; + } + ans = "x"; } + } } diff --git a/checker/tests/initialization/TypeFrames.java b/checker/tests/initialization/TypeFrames.java index 9d819cf871f..73f27d57b52 100644 --- a/checker/tests/initialization/TypeFrames.java +++ b/checker/tests/initialization/TypeFrames.java @@ -3,38 +3,38 @@ public class TypeFrames { - class A { - @NonNull String a; + class A { + @NonNull String a; - public A() { - @UnderInitialization A l1 = this; - // :: error: (assignment.type.incompatible) - @UnderInitialization(A.class) A l2 = this; - a = ""; - @UnderInitialization(A.class) A l3 = this; - } + public A() { + @UnderInitialization A l1 = this; + // :: error: (assignment.type.incompatible) + @UnderInitialization(A.class) A l2 = this; + a = ""; + @UnderInitialization(A.class) A l3 = this; } + } - interface I {} + interface I {} - class B extends A implements I { - @NonNull String b; + class B extends A implements I { + @NonNull String b; - public B() { - super(); - @UnderInitialization(A.class) A l1 = this; - // :: error: (assignment.type.incompatible) - @UnderInitialization(B.class) A l2 = this; - b = ""; - @UnderInitialization(B.class) A l3 = this; - } + public B() { + super(); + @UnderInitialization(A.class) A l1 = this; + // :: error: (assignment.type.incompatible) + @UnderInitialization(B.class) A l2 = this; + b = ""; + @UnderInitialization(B.class) A l3 = this; } + } - // subtyping - void t1(@UnderInitialization(A.class) B b1, @UnderInitialization(B.class) B b2) { - @UnderInitialization(A.class) B l1 = b1; - @UnderInitialization(A.class) B l2 = b2; - // :: error: (assignment.type.incompatible) - @UnderInitialization(B.class) B l3 = b1; - } + // subtyping + void t1(@UnderInitialization(A.class) B b1, @UnderInitialization(B.class) B b2) { + @UnderInitialization(A.class) B l1 = b1; + @UnderInitialization(A.class) B l2 = b2; + // :: error: (assignment.type.incompatible) + @UnderInitialization(B.class) B l3 = b1; + } } diff --git a/checker/tests/initialization/TypeFrames2.java b/checker/tests/initialization/TypeFrames2.java index 0b328a56614..369f6282a10 100644 --- a/checker/tests/initialization/TypeFrames2.java +++ b/checker/tests/initialization/TypeFrames2.java @@ -3,31 +3,31 @@ public class TypeFrames2 { - class A { - @NonNull String a; + class A { + @NonNull String a; - public A() { - // :: error: (method.invocation.invalid) - this.foo(); - a = ""; - this.foo(); - } - - public void foo(@UnderInitialization(A.class) A this) {} + public A() { + // :: error: (method.invocation.invalid) + this.foo(); + a = ""; + this.foo(); } - class B extends A { - @NonNull String b; + public void foo(@UnderInitialization(A.class) A this) {} + } - public B() { - super(); - this.foo(); - // :: error: (method.invocation.invalid) - this.bar(); - b = ""; - this.bar(); - } + class B extends A { + @NonNull String b; - public void bar(@UnderInitialization(B.class) B this) {} + public B() { + super(); + this.foo(); + // :: error: (method.invocation.invalid) + this.bar(); + b = ""; + this.bar(); } + + public void bar(@UnderInitialization(B.class) B this) {} + } } diff --git a/checker/tests/initialization/TypeFrames3.java b/checker/tests/initialization/TypeFrames3.java index 1a411dd4af4..bafd73322d3 100644 --- a/checker/tests/initialization/TypeFrames3.java +++ b/checker/tests/initialization/TypeFrames3.java @@ -2,23 +2,23 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class TypeFrames3 { - public Object f; + public Object f; - public TypeFrames3(boolean dummy) { - initF(); - foo(); - } + public TypeFrames3(boolean dummy) { + initF(); + foo(); + } - public TypeFrames3(int dummy) { - // :: error: (method.invocation.invalid) - foo(); - f = new Object(); - } + public TypeFrames3(int dummy) { + // :: error: (method.invocation.invalid) + foo(); + f = new Object(); + } - @EnsuresNonNull("this.f") - public void initF(@UnknownInitialization TypeFrames3 this) { - f = new Object(); - } + @EnsuresNonNull("this.f") + public void initF(@UnknownInitialization TypeFrames3 this) { + f = new Object(); + } - public void foo(@UnknownInitialization(TypeFrames3.class) TypeFrames3 this) {} + public void foo(@UnknownInitialization(TypeFrames3.class) TypeFrames3 this) {} } diff --git a/checker/tests/initialization/TypeFrames4.java b/checker/tests/initialization/TypeFrames4.java index 73d947353e1..5f0a749be33 100644 --- a/checker/tests/initialization/TypeFrames4.java +++ b/checker/tests/initialization/TypeFrames4.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class TypeFrames4 { - public Object f; + public Object f; - public TypeFrames4(boolean dummy) { - initF(); - @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; - } + public TypeFrames4(boolean dummy) { + initF(); + @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; + } - public TypeFrames4(int dummy) { - // :: error: (assignment.type.incompatible) - @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; - f = new Object(); - } + public TypeFrames4(int dummy) { + // :: error: (assignment.type.incompatible) + @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; + f = new Object(); + } - @EnsuresNonNull("this.f") - public void initF(@UnknownInitialization TypeFrames4 this) { - f = new Object(); - } + @EnsuresNonNull("this.f") + public void initF(@UnknownInitialization TypeFrames4 this) { + f = new Object(); + } } diff --git a/checker/tests/initialization/TypeFrames5.java b/checker/tests/initialization/TypeFrames5.java index 054722a48b9..4322da3983e 100644 --- a/checker/tests/initialization/TypeFrames5.java +++ b/checker/tests/initialization/TypeFrames5.java @@ -3,14 +3,14 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TypeFrames5 { - public @Nullable Object f; + public @Nullable Object f; - public TypeFrames5(boolean dummy) { - @UnderInitialization(TypeFrames5.class) TypeFrames5 a = this; - } + public TypeFrames5(boolean dummy) { + @UnderInitialization(TypeFrames5.class) TypeFrames5 a = this; + } - public TypeFrames5(int dummy) { - // :: error: (assignment.type.incompatible) - @Initialized TypeFrames5 a = this; - } + public TypeFrames5(int dummy) { + // :: error: (assignment.type.incompatible) + @Initialized TypeFrames5 a = this; + } } diff --git a/checker/tests/initialization/UnboxUninitalizedFieldTest.java b/checker/tests/initialization/UnboxUninitalizedFieldTest.java index ad8347ba826..b30c252ba77 100644 --- a/checker/tests/initialization/UnboxUninitalizedFieldTest.java +++ b/checker/tests/initialization/UnboxUninitalizedFieldTest.java @@ -3,10 +3,10 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class UnboxUninitalizedFieldTest { - @UnknownInitialization Integer n; + @UnknownInitialization Integer n; - UnboxUninitalizedFieldTest() { - // :: error: (unboxing.of.nullable) - int y = n; - } + UnboxUninitalizedFieldTest() { + // :: error: (unboxing.of.nullable) + int y = n; + } } diff --git a/checker/tests/initialization/Uninit.java b/checker/tests/initialization/Uninit.java index db69ec25d8b..3ac1787c4d7 100644 --- a/checker/tests/initialization/Uninit.java +++ b/checker/tests/initialization/Uninit.java @@ -1,4 +1,4 @@ public class Uninit { - // :: error: (initialization.field.uninitialized) - Object a; + // :: error: (initialization.field.uninitialized) + Object a; } diff --git a/checker/tests/initialization/Uninit10.java b/checker/tests/initialization/Uninit10.java index c5849cb19bd..8179491a93d 100644 --- a/checker/tests/initialization/Uninit10.java +++ b/checker/tests/initialization/Uninit10.java @@ -2,16 +2,16 @@ public class Uninit10 { - @NonNull String[] strings; + @NonNull String[] strings; - // :: error: (initialization.fields.uninitialized) - Uninit10() {} + // :: error: (initialization.fields.uninitialized) + Uninit10() {} - public class Inner { + public class Inner { - @NonNull String[] stringsInner; + @NonNull String[] stringsInner; - // :: error: (initialization.fields.uninitialized) - Inner() {} - } + // :: error: (initialization.fields.uninitialized) + Inner() {} + } } diff --git a/checker/tests/initialization/Uninit11.java b/checker/tests/initialization/Uninit11.java index d4e83fcbb8f..fa0f1d8911e 100644 --- a/checker/tests/initialization/Uninit11.java +++ b/checker/tests/initialization/Uninit11.java @@ -1,26 +1,25 @@ +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.Unused; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - @SubtypeOf({}) @Target(ElementType.TYPE_USE) @interface DoesNotUseF {} public class Uninit11 { - @Unused(when = DoesNotUseF.class) - public Object f; + @Unused(when = DoesNotUseF.class) + public Object f; - // parameter disambiguate_overloads is just to distinguish the overloaded constructors - public @DoesNotUseF Uninit11(int disambiguate_overloads) {} + // parameter disambiguate_overloads is just to distinguish the overloaded constructors + public @DoesNotUseF Uninit11(int disambiguate_overloads) {} - // :: error: (initialization.fields.uninitialized) - public Uninit11(boolean disambiguate_overloads) {} + // :: error: (initialization.fields.uninitialized) + public Uninit11(boolean disambiguate_overloads) {} - public Uninit11(long x) { - f = new Object(); - } + public Uninit11(long x) { + f = new Object(); + } } diff --git a/checker/tests/initialization/Uninit12.java b/checker/tests/initialization/Uninit12.java index ff778e78713..5d30e1f2853 100644 --- a/checker/tests/initialization/Uninit12.java +++ b/checker/tests/initialization/Uninit12.java @@ -5,29 +5,29 @@ public class Uninit12 { - // :: error: (initialization.static.field.uninitialized) - static Object f; + // :: error: (initialization.static.field.uninitialized) + static Object f; - public Uninit12() { - f.toString(); - } + public Uninit12() { + f.toString(); + } - static Object g = new Object(); + static Object g = new Object(); - static Object h; + static Object h; - static { - h = new Object(); - } + static { + h = new Object(); + } } class Uninit12_OK { - static Object g = new Object(); + static Object g = new Object(); - static Object h; + static Object h; - static { - h = new Object(); - } + static { + h = new Object(); + } } diff --git a/checker/tests/initialization/Uninit13.java b/checker/tests/initialization/Uninit13.java index 21bcfce3493..27678e80f1f 100644 --- a/checker/tests/initialization/Uninit13.java +++ b/checker/tests/initialization/Uninit13.java @@ -1,9 +1,9 @@ public class Uninit13 { - { - x = 1; - o = new Object(); - } + { + x = 1; + o = new Object(); + } - int x; - Object o; + int x; + Object o; } diff --git a/checker/tests/initialization/Uninit14.java b/checker/tests/initialization/Uninit14.java index 64e5abc8365..b6fc2131200 100644 --- a/checker/tests/initialization/Uninit14.java +++ b/checker/tests/initialization/Uninit14.java @@ -1,13 +1,13 @@ // Test case for Issue 144 (now fixed): // https://github.com/typetools/checker-framework/issues/144 public class Uninit14 { - private final Object o; + private final Object o; - { - try { - o = new Object(); - } catch (Exception e) { - throw new RuntimeException(e); - } + { + try { + o = new Object(); + } catch (Exception e) { + throw new RuntimeException(e); } + } } diff --git a/checker/tests/initialization/Uninit2.java b/checker/tests/initialization/Uninit2.java index f84a9137193..4f68bd43938 100644 --- a/checker/tests/initialization/Uninit2.java +++ b/checker/tests/initialization/Uninit2.java @@ -1,7 +1,7 @@ public class Uninit2 { - Object a; + Object a; - Uninit2() { - a = new Object(); - } + Uninit2() { + a = new Object(); + } } diff --git a/checker/tests/initialization/Uninit3.java b/checker/tests/initialization/Uninit3.java index 2176f9751b8..3b23d994973 100644 --- a/checker/tests/initialization/Uninit3.java +++ b/checker/tests/initialization/Uninit3.java @@ -1,3 +1,3 @@ public class Uninit3 { - Object a = new Object(); + Object a = new Object(); } diff --git a/checker/tests/initialization/Uninit4.java b/checker/tests/initialization/Uninit4.java index ac747824df0..45fcb9bb70c 100644 --- a/checker/tests/initialization/Uninit4.java +++ b/checker/tests/initialization/Uninit4.java @@ -1,36 +1,36 @@ public class Uninit4 { - class Mam { - Object a = new Object(); - } + class Mam { + Object a = new Object(); + } - class BadSon { - // :: error: (initialization.field.uninitialized) - Object b; - } + class BadSon { + // :: error: (initialization.field.uninitialized) + Object b; + } - class GoodSon { - Object b = new Object(); - } + class GoodSon { + Object b = new Object(); + } - class WeirdSon { - Object b; + class WeirdSon { + Object b; - // :: error: (initialization.fields.uninitialized) - WeirdSon() { - super(); - } + // :: error: (initialization.fields.uninitialized) + WeirdSon() { + super(); } + } - class Daughter { - Object b; + class Daughter { + Object b; - // :: error: (initialization.fields.uninitialized) - Daughter() {} + // :: error: (initialization.fields.uninitialized) + Daughter() {} - Daughter(Object val) { - this(); - b = val; - } + Daughter(Object val) { + this(); + b = val; } + } } diff --git a/checker/tests/initialization/Uninit5.java b/checker/tests/initialization/Uninit5.java index a151d5189ff..9a2c0781abc 100644 --- a/checker/tests/initialization/Uninit5.java +++ b/checker/tests/initialization/Uninit5.java @@ -1,4 +1,4 @@ public class Uninit5 { - // :: error: (initialization.field.uninitialized) - String x; + // :: error: (initialization.field.uninitialized) + String x; } diff --git a/checker/tests/initialization/Uninit6.java b/checker/tests/initialization/Uninit6.java index 856c5b7961a..7ca509407f5 100644 --- a/checker/tests/initialization/Uninit6.java +++ b/checker/tests/initialization/Uninit6.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.nullness.qual.*; public class Uninit6 { - // Failure to initialize these fields does not directly compromise the - // guarantee of no null pointer errors. - @MonotonicNonNull Object f; - @Nullable Object g; + // Failure to initialize these fields does not directly compromise the + // guarantee of no null pointer errors. + @MonotonicNonNull Object f; + @Nullable Object g; - Uninit6() {} + Uninit6() {} } diff --git a/checker/tests/initialization/Uninit7.java b/checker/tests/initialization/Uninit7.java index d50ff2ce7c8..da19da82976 100644 --- a/checker/tests/initialization/Uninit7.java +++ b/checker/tests/initialization/Uninit7.java @@ -1,7 +1,7 @@ public class Uninit7 { - Object f; + Object f; - Uninit7() { - throw new Error(); - } + Uninit7() { + throw new Error(); + } } diff --git a/checker/tests/initialization/Uninit8.java b/checker/tests/initialization/Uninit8.java index 606ec377116..f19f310fa84 100644 --- a/checker/tests/initialization/Uninit8.java +++ b/checker/tests/initialization/Uninit8.java @@ -3,15 +3,15 @@ public class Uninit8 { - Object f; + Object f; - Uninit8() { - setFields(); - f.toString(); - } + Uninit8() { + setFields(); + f.toString(); + } - @EnsuresNonNull("f") - void setFields(@UnknownInitialization Uninit8 this) { - f = new Object(); - } + @EnsuresNonNull("f") + void setFields(@UnknownInitialization Uninit8 this) { + f = new Object(); + } } diff --git a/checker/tests/initialization/Uninit9.java b/checker/tests/initialization/Uninit9.java index 76a4e13ed56..f94971c849d 100644 --- a/checker/tests/initialization/Uninit9.java +++ b/checker/tests/initialization/Uninit9.java @@ -3,19 +3,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Uninit9 { - public Object f; + public Object f; - Uninit9() { - f = new Object(); - } + Uninit9() { + f = new Object(); + } } class Uninit9Sub extends Uninit9 { - Uninit9Sub() { - super(); - fIsSetOnEntry(); - } + Uninit9Sub() { + super(); + fIsSetOnEntry(); + } - @RequiresNonNull("f") - void fIsSetOnEntry(@UnknownInitialization Uninit9Sub this) {} + @RequiresNonNull("f") + void fIsSetOnEntry(@UnknownInitialization Uninit9Sub this) {} } diff --git a/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java b/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java index d2d4b4c8e4e..3e3be2af221 100644 --- a/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java +++ b/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java @@ -1,5 +1,5 @@ import org.checkerframework.checker.interning.qual.Interned; public class RedundantAnnotationOnField { - static final @Interned String A_STRING = "a string"; + static final @Interned String A_STRING = "a string"; } diff --git a/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java b/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java index 7e8f33aa4b9..27b9faa26bc 100644 --- a/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java +++ b/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java @@ -1,6 +1,6 @@ import org.checkerframework.checker.interning.qual.Interned; public class StaticFinalStringDefault { - // The default type of str is not @Interned, even though it is later refined to it. - static final @Interned String str = "a"; + // The default type of str is not @Interned, even though it is later refined to it. + static final @Interned String str = "a"; } diff --git a/checker/tests/interning/ArrayInitializers.java b/checker/tests/interning/ArrayInitializers.java index cb24dfc0181..d2cf14d7d2b 100644 --- a/checker/tests/interning/ArrayInitializers.java +++ b/checker/tests/interning/ArrayInitializers.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.interning.qual.Interned; public class ArrayInitializers { - public static final String STATIC_FIELD = "m"; - public static final @Interned String OTHER_FIELD = "n"; + public static final String STATIC_FIELD = "m"; + public static final @Interned String OTHER_FIELD = "n"; - public static final @Interned String[] STATIC_ARRAY = {STATIC_FIELD, OTHER_FIELD}; + public static final @Interned String[] STATIC_ARRAY = {STATIC_FIELD, OTHER_FIELD}; } diff --git a/checker/tests/interning/Arrays.java b/checker/tests/interning/Arrays.java index 83fa107f30a..ee98724f3b1 100644 --- a/checker/tests/interning/Arrays.java +++ b/checker/tests/interning/Arrays.java @@ -1,77 +1,76 @@ -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.interning.qual.PolyInterned; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.interning.qual.PolyInterned; public class Arrays { - public static Integer[] arrayclone_simple(Integer[] a_old) { - int len = a_old.length; - Integer[] a_new = new Integer[len]; - for (int i = 0; i < len; i++) { - a_new[i] = Integer.valueOf(a_old[i]); // valid - } - return a_new; - } - - public static void test(@Interned Integer i, @Interned String s) { - String @Interned [] iarray1 = new String @Interned [2]; - String @Interned [] iarray2 = new String @Interned [] {"foo", "bar"}; - // :: error: (assignment.type.incompatible) - s = iarray1[1]; // error - - String[] sa = new String[22]; - // :: error: (assignment.type.incompatible) - iarray1 = sa; // error - sa = iarray1; // OK - - @Interned String[] istrings1 = new @Interned String[2]; - @Interned String[] istrings2 = new @Interned String[] {"foo", "bar"}; - s = istrings1[1]; // OK - - @Interned String @Interned [][] multi1 = new @Interned String @Interned [2][3]; - @Interned String @Interned [][] multi2 = new @Interned String @Interned [2][]; - } - - public final @Interned class InternedClass {} - - private static InternedClass[] returnToArray() { - List li = new ArrayList<>(); - return li.toArray(new InternedClass[li.size()]); - } - - private static void sortIt() { - java.util.Arrays.sort(new InternedClass[22]); - } - - private @Interned String[] elts_String; - - public @Interned String min_elt() { - return elts_String[0]; - } - - private double @Interned [] @Interned [] elts_da; - - public void add_mod_elem(double @Interned [] v, int count) { - elts_da[0] = v; - } - - public static @PolyInterned Object[] subarray( - @PolyInterned Object[] a, int startindex, int length) { - @PolyInterned Object[] result = new @PolyInterned Object[length]; - System.arraycopy(a, startindex, result, 0, length); - return result; - } - - public static void trim(int len) { - @Interned Object @Interned [] vals = null; - @Interned Object[] new_vals = subarray(vals, 0, len); - } - - public static @Interned Object @Interned [] internSubsequence( - @Interned Object @Interned [] seq, int start, int end) { - @Interned Object[] subseq_uninterned = subarray(seq, start, end - start); - return null; + public static Integer[] arrayclone_simple(Integer[] a_old) { + int len = a_old.length; + Integer[] a_new = new Integer[len]; + for (int i = 0; i < len; i++) { + a_new[i] = Integer.valueOf(a_old[i]); // valid } + return a_new; + } + + public static void test(@Interned Integer i, @Interned String s) { + String @Interned [] iarray1 = new String @Interned [2]; + String @Interned [] iarray2 = new String @Interned [] {"foo", "bar"}; + // :: error: (assignment.type.incompatible) + s = iarray1[1]; // error + + String[] sa = new String[22]; + // :: error: (assignment.type.incompatible) + iarray1 = sa; // error + sa = iarray1; // OK + + @Interned String[] istrings1 = new @Interned String[2]; + @Interned String[] istrings2 = new @Interned String[] {"foo", "bar"}; + s = istrings1[1]; // OK + + @Interned String @Interned [][] multi1 = new @Interned String @Interned [2][3]; + @Interned String @Interned [][] multi2 = new @Interned String @Interned [2][]; + } + + public final @Interned class InternedClass {} + + private static InternedClass[] returnToArray() { + List li = new ArrayList<>(); + return li.toArray(new InternedClass[li.size()]); + } + + private static void sortIt() { + java.util.Arrays.sort(new InternedClass[22]); + } + + private @Interned String[] elts_String; + + public @Interned String min_elt() { + return elts_String[0]; + } + + private double @Interned [] @Interned [] elts_da; + + public void add_mod_elem(double @Interned [] v, int count) { + elts_da[0] = v; + } + + public static @PolyInterned Object[] subarray( + @PolyInterned Object[] a, int startindex, int length) { + @PolyInterned Object[] result = new @PolyInterned Object[length]; + System.arraycopy(a, startindex, result, 0, length); + return result; + } + + public static void trim(int len) { + @Interned Object @Interned [] vals = null; + @Interned Object[] new_vals = subarray(vals, 0, len); + } + + public static @Interned Object @Interned [] internSubsequence( + @Interned Object @Interned [] seq, int start, int end) { + @Interned Object[] subseq_uninterned = subarray(seq, start, end - start); + return null; + } } diff --git a/checker/tests/interning/ArraysMDETest.java b/checker/tests/interning/ArraysMDETest.java index 88ed380e2e2..15495382868 100644 --- a/checker/tests/interning/ArraysMDETest.java +++ b/checker/tests/interning/ArraysMDETest.java @@ -4,10 +4,10 @@ public final class ArraysMDETest { - public static @PolyInterned Object[] subarray( - @PolyInterned Object[] a, int startindex, int length) { - @PolyInterned Object[] result = new @PolyInterned Object[length]; - System.arraycopy(a, startindex, result, 0, length); - return result; - } + public static @PolyInterned Object[] subarray( + @PolyInterned Object[] a, int startindex, int length) { + @PolyInterned Object[] result = new @PolyInterned Object[length]; + System.arraycopy(a, startindex, result, 0, length); + return result; + } } diff --git a/checker/tests/interning/Autoboxing.java b/checker/tests/interning/Autoboxing.java index cc53021822a..b1c2c7451c5 100644 --- a/checker/tests/interning/Autoboxing.java +++ b/checker/tests/interning/Autoboxing.java @@ -1,170 +1,170 @@ public class Autoboxing { - Byte b; - Short s; - Short sInterned; - Integer i; - Integer iInterned; - Long l; - Float f; - Double d; - Boolean z; - Character c; - Character cInterned; + Byte b; + Short s; + Short sInterned; + Integer i; + Integer iInterned; + Long l; + Float f; + Double d; + Boolean z; + Character c; + Character cInterned; - Autoboxing() { - b = -126; - s = 32000; - sInterned = 32; - i = 1234567; - iInterned = 123; - l = 1234567L; - f = 3.14f; - d = 3.14; - z = true; - c = 65000; - cInterned = 65; - } + Autoboxing() { + b = -126; + s = 32000; + sInterned = 32; + i = 1234567; + iInterned = 123; + l = 1234567L; + f = 3.14f; + d = 3.14; + z = true; + c = 65000; + cInterned = 65; + } - public static void main(String[] args) { - new Autoboxing().test(); - } + public static void main(String[] args) { + new Autoboxing().test(); + } - public void test() { - System.out.println(); - System.out.println("Byte"); - Byte b1 = -126; - Byte b2 = -126; - Byte b3 = Byte.valueOf((byte) -126); - System.out.println(b1 == b2); - // :: warning: (unnecessary.equals) - System.out.println(b1.equals(b2)); - // :: warning: (unnecessary.equals) - System.out.println(b3.equals(b2)); - System.out.println(b.equals(b2)); - System.out.println(b == -126); - // :: warning: (unnecessary.equals) - System.out.println(b1.equals(126)); + public void test() { + System.out.println(); + System.out.println("Byte"); + Byte b1 = -126; + Byte b2 = -126; + Byte b3 = Byte.valueOf((byte) -126); + System.out.println(b1 == b2); + // :: warning: (unnecessary.equals) + System.out.println(b1.equals(b2)); + // :: warning: (unnecessary.equals) + System.out.println(b3.equals(b2)); + System.out.println(b.equals(b2)); + System.out.println(b == -126); + // :: warning: (unnecessary.equals) + System.out.println(b1.equals(126)); - System.out.println(); - System.out.println("Short"); - Short s1 = 32000; - Short s2 = 32000; - Short s3 = Short.valueOf((short) 32000); - // :: error: (not.interned) - System.out.println(s1 == s2); - System.out.println(s1.equals(s2)); - System.out.println(s3.equals(s2)); - System.out.println(s.equals(s2)); - // TODO - // Short s1interned = 32; - // Short s2interned = 32; - // Short s3interned = Short.valueOf((short) 32); - // System.out.println(s1interned==s2interned); - // // :: warning: (unnecessary.equals) - // System.out.println(s1interned.equals(s2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(s3interned.equals(s2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(sInterned.equals(s2interned)); + System.out.println(); + System.out.println("Short"); + Short s1 = 32000; + Short s2 = 32000; + Short s3 = Short.valueOf((short) 32000); + // :: error: (not.interned) + System.out.println(s1 == s2); + System.out.println(s1.equals(s2)); + System.out.println(s3.equals(s2)); + System.out.println(s.equals(s2)); + // TODO + // Short s1interned = 32; + // Short s2interned = 32; + // Short s3interned = Short.valueOf((short) 32); + // System.out.println(s1interned==s2interned); + // // :: warning: (unnecessary.equals) + // System.out.println(s1interned.equals(s2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(s3interned.equals(s2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(sInterned.equals(s2interned)); - System.out.println(); - System.out.println("Integer"); - Integer i1 = 1234567; - Integer i2 = 1234567; - Integer i3 = Integer.valueOf(1234567); - // :: error: (not.interned) - System.out.println(i1 == i2); - System.out.println(i1.equals(i2)); - System.out.println(i3.equals(i2)); - System.out.println(i.equals(i2)); + System.out.println(); + System.out.println("Integer"); + Integer i1 = 1234567; + Integer i2 = 1234567; + Integer i3 = Integer.valueOf(1234567); + // :: error: (not.interned) + System.out.println(i1 == i2); + System.out.println(i1.equals(i2)); + System.out.println(i3.equals(i2)); + System.out.println(i.equals(i2)); - System.out.println(); - Integer i1interned = 123; - Integer i2interned = 123; - Integer i3interned = Integer.valueOf(123); - // TODO: - // Would be legal to use ==, but Interning Checker does not check the - // actual int value when deciding whether to warn for unnecessary.equals. - // // :: warning: (unnecessary.equals) - // System.out.println(i1interned==i2interned); - // // :: warning: (unnecessary.equals) - // System.out.println(i1interned.equals(i2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(i3interned.equals(i2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(iInterned.equals(i2interned)); - // System.out.println(i1interned==123); // ok - // // :: warning: (unnecessary.equals) - // System.out.println(i1interned.equals(123)); + System.out.println(); + Integer i1interned = 123; + Integer i2interned = 123; + Integer i3interned = Integer.valueOf(123); + // TODO: + // Would be legal to use ==, but Interning Checker does not check the + // actual int value when deciding whether to warn for unnecessary.equals. + // // :: warning: (unnecessary.equals) + // System.out.println(i1interned==i2interned); + // // :: warning: (unnecessary.equals) + // System.out.println(i1interned.equals(i2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(i3interned.equals(i2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(iInterned.equals(i2interned)); + // System.out.println(i1interned==123); // ok + // // :: warning: (unnecessary.equals) + // System.out.println(i1interned.equals(123)); - System.out.println(); - System.out.println("Long"); - Long l1 = 1234567L; - Long l2 = 1234567L; - Long l3 = Long.valueOf(1234567L); - // :: error: (not.interned) - System.out.println(l1 == l2); - System.out.println(l1.equals(l2)); - System.out.println(l3.equals(l2)); - System.out.println(l.equals(l2)); + System.out.println(); + System.out.println("Long"); + Long l1 = 1234567L; + Long l2 = 1234567L; + Long l3 = Long.valueOf(1234567L); + // :: error: (not.interned) + System.out.println(l1 == l2); + System.out.println(l1.equals(l2)); + System.out.println(l3.equals(l2)); + System.out.println(l.equals(l2)); - System.out.println(); - System.out.println("Float"); - Float f1 = 3.14f; - Float f2 = 3.14f; - Float f3 = Float.valueOf(3.14f); - // :: error: (not.interned) - System.out.println(f1 == f2); - System.out.println(f1.equals(f2)); - System.out.println(f3.equals(f2)); - System.out.println(f.equals(f2)); + System.out.println(); + System.out.println("Float"); + Float f1 = 3.14f; + Float f2 = 3.14f; + Float f3 = Float.valueOf(3.14f); + // :: error: (not.interned) + System.out.println(f1 == f2); + System.out.println(f1.equals(f2)); + System.out.println(f3.equals(f2)); + System.out.println(f.equals(f2)); - System.out.println(); - System.out.println("Double"); - Double d1 = 3.14; - Double d2 = 3.14; - Double d3 = Double.valueOf(3.14); - // :: error: (not.interned) - System.out.println(d1 == d2); - System.out.println(d1.equals(d2)); - System.out.println(d3.equals(d2)); - System.out.println(d.equals(d2)); + System.out.println(); + System.out.println("Double"); + Double d1 = 3.14; + Double d2 = 3.14; + Double d3 = Double.valueOf(3.14); + // :: error: (not.interned) + System.out.println(d1 == d2); + System.out.println(d1.equals(d2)); + System.out.println(d3.equals(d2)); + System.out.println(d.equals(d2)); - System.out.println(); - System.out.println("Boolean"); - Boolean z1 = true; - Boolean z2 = true; - Boolean z3 = Boolean.valueOf(true); - System.out.println(z1 == z2); - // :: warning: (unnecessary.equals) - System.out.println(z1.equals(z2)); - // :: warning: (unnecessary.equals) - System.out.println(z3.equals(z2)); - System.out.println(z.equals(z2)); - System.out.println(z1 == true); // ok - // :: warning: (unnecessary.equals) - System.out.println(z1.equals(true)); + System.out.println(); + System.out.println("Boolean"); + Boolean z1 = true; + Boolean z2 = true; + Boolean z3 = Boolean.valueOf(true); + System.out.println(z1 == z2); + // :: warning: (unnecessary.equals) + System.out.println(z1.equals(z2)); + // :: warning: (unnecessary.equals) + System.out.println(z3.equals(z2)); + System.out.println(z.equals(z2)); + System.out.println(z1 == true); // ok + // :: warning: (unnecessary.equals) + System.out.println(z1.equals(true)); - System.out.println(); - System.out.println("Character"); - Character c1 = 65000; - Character c2 = 65000; - Character c3 = Character.valueOf((char) 65000); - // :: error: (not.interned) - System.out.println(c1 == c2); - System.out.println(c1.equals(c2)); - System.out.println(c3.equals(c2)); - System.out.println(c.equals(c2)); - // TODO - // Character c1interned = 65; - // Character c2interned = 65; - // Character c3interned = Character.valueOf((char) 65); - // System.out.println(c1interned==c2interned); - // // :: warning: (unnecessary.equals) - // System.out.println(c1interned.equals(c2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(c3interned.equals(c2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(cInterned.equals(c2interned)); - } + System.out.println(); + System.out.println("Character"); + Character c1 = 65000; + Character c2 = 65000; + Character c3 = Character.valueOf((char) 65000); + // :: error: (not.interned) + System.out.println(c1 == c2); + System.out.println(c1.equals(c2)); + System.out.println(c3.equals(c2)); + System.out.println(c.equals(c2)); + // TODO + // Character c1interned = 65; + // Character c2interned = 65; + // Character c3interned = Character.valueOf((char) 65); + // System.out.println(c1interned==c2interned); + // // :: warning: (unnecessary.equals) + // System.out.println(c1interned.equals(c2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(c3interned.equals(c2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(cInterned.equals(c2interned)); + } } diff --git a/checker/tests/interning/BoxingInterning.java b/checker/tests/interning/BoxingInterning.java index cacceaae67f..5e33788d682 100644 --- a/checker/tests/interning/BoxingInterning.java +++ b/checker/tests/interning/BoxingInterning.java @@ -12,68 +12,68 @@ public class BoxingInterning { - void needsInterned(@Interned Object arg) {} - - void method() { - - boolean aprimitive = true; - needsInterned(aprimitive); - @Interned Boolean aboxed = aprimitive; - - byte bprimitive = 5; - needsInterned(bprimitive); - @Interned Byte bboxed = bprimitive; - - char cprimitive = 'a'; - needsInterned(cprimitive); - @Interned Character c2 = c; - - char cprimitive2 = (char) 0x2202; - // :: (argument.type.incompatible) - needsInterned(cprimitive2); - // :: (assignment.type.incompatible) - @Interned Character cboxed2 = cprimitive2; - - short dprimitive = 5; - needsInterned(dprimitive); - @Interned Short dboxed = dprimitive; - - short dprimitive2 = 500; - // :: (argument.type.incompatible) - needsInterned(dprimitive2); - // :: (assignment.type.incompatible) - @Interned Short dboxed2 = dprimitive2; - - int eprimitive = 5; - needsInterned(eprimitive); - @Interned Integer eboxed = eprimitive; - - int eprimitive2 = 500; - // :: (argument.type.incompatible) - needsInterned(eprimitive2); - // :: (assignment.type.incompatible) - @Interned Integer eboxed2 = eprimitive2; - - long fprimitive = 5; - needsInterned(fprimitive); - @Interned Long fboxed = fboxed; - - long fprimitive2 = 500; - // :: (argument.type.incompatible) - needsInterned(fprimitive2); - // :: (assignment.type.incompatible) - @Interned Long fboxed2 = fboxed2; - - float g = (float) 3.14; - // :: (argument.type.incompatible) - needsInterned(g); - // :: (assignment.type.incompatible) - @Interned Float gboxed = g; - - double h = 3.14; - // :: (argument.type.incompatible) - needsInterned(h); - // :: (assignment.type.incompatible) - @Interned Double hboxed = h; - } + void needsInterned(@Interned Object arg) {} + + void method() { + + boolean aprimitive = true; + needsInterned(aprimitive); + @Interned Boolean aboxed = aprimitive; + + byte bprimitive = 5; + needsInterned(bprimitive); + @Interned Byte bboxed = bprimitive; + + char cprimitive = 'a'; + needsInterned(cprimitive); + @Interned Character c2 = c; + + char cprimitive2 = (char) 0x2202; + // :: (argument.type.incompatible) + needsInterned(cprimitive2); + // :: (assignment.type.incompatible) + @Interned Character cboxed2 = cprimitive2; + + short dprimitive = 5; + needsInterned(dprimitive); + @Interned Short dboxed = dprimitive; + + short dprimitive2 = 500; + // :: (argument.type.incompatible) + needsInterned(dprimitive2); + // :: (assignment.type.incompatible) + @Interned Short dboxed2 = dprimitive2; + + int eprimitive = 5; + needsInterned(eprimitive); + @Interned Integer eboxed = eprimitive; + + int eprimitive2 = 500; + // :: (argument.type.incompatible) + needsInterned(eprimitive2); + // :: (assignment.type.incompatible) + @Interned Integer eboxed2 = eprimitive2; + + long fprimitive = 5; + needsInterned(fprimitive); + @Interned Long fboxed = fboxed; + + long fprimitive2 = 500; + // :: (argument.type.incompatible) + needsInterned(fprimitive2); + // :: (assignment.type.incompatible) + @Interned Long fboxed2 = fboxed2; + + float g = (float) 3.14; + // :: (argument.type.incompatible) + needsInterned(g); + // :: (assignment.type.incompatible) + @Interned Float gboxed = g; + + double h = 3.14; + // :: (argument.type.incompatible) + needsInterned(h); + // :: (assignment.type.incompatible) + @Interned Double hboxed = h; + } } diff --git a/checker/tests/interning/Casts.java b/checker/tests/interning/Casts.java index 0ae9a3c4e3d..2a239ca665d 100644 --- a/checker/tests/interning/Casts.java +++ b/checker/tests/interning/Casts.java @@ -1,5 +1,5 @@ public class Casts { - void method(Object o) { - char c = (char) o; - } + void method(Object o) { + char c = (char) o; + } } diff --git a/checker/tests/interning/ClassDefaults.java b/checker/tests/interning/ClassDefaults.java index 01bc6f33b6e..7ac06830b13 100644 --- a/checker/tests/interning/ClassDefaults.java +++ b/checker/tests/interning/ClassDefaults.java @@ -1,6 +1,5 @@ -import org.checkerframework.checker.interning.qual.Interned; - import java.util.List; +import org.checkerframework.checker.interning.qual.Interned; /* * This test case excercises the interaction between class annotations @@ -8,21 +7,21 @@ * A previously existing Unqualified annotation wasn't correctly removed. */ public class ClassDefaults { - @Interned class Test {} + @Interned class Test {} - public static interface Visitor {} + public static interface Visitor {} - class GuardingVisitor implements Visitor> { - void call() { - test(this); - } + class GuardingVisitor implements Visitor> { + void call() { + test(this); } + } - T test(Visitor p) { - return null; - } + T test(Visitor p) { + return null; + } - void call(GuardingVisitor p) { - test(p); - } + void call(GuardingVisitor p) { + test(p); + } } diff --git a/checker/tests/interning/Comparison.java b/checker/tests/interning/Comparison.java index b00a2ea874e..cf9d84bbad3 100644 --- a/checker/tests/interning/Comparison.java +++ b/checker/tests/interning/Comparison.java @@ -2,41 +2,41 @@ public class Comparison { - void testInterned() { - - @Interned String a = "foo"; - @Interned String b = "bar"; - - if (a == b) { - System.out.println("yes"); - } else { - System.out.println("no"); - } - - if (a != b) { - System.out.println("no"); - } else { - System.out.println("yes"); - } + void testInterned() { + + @Interned String a = "foo"; + @Interned String b = "bar"; + + if (a == b) { + System.out.println("yes"); + } else { + System.out.println("no"); } - void testNotInterned() { + if (a != b) { + System.out.println("no"); + } else { + System.out.println("yes"); + } + } - String c = new String("foo"); - String d = new String("bar"); + void testNotInterned() { - // :: error: (not.interned) - if (c == d) { - System.out.println("yes"); - } else { - System.out.println("no"); - } + String c = new String("foo"); + String d = new String("bar"); + + // :: error: (not.interned) + if (c == d) { + System.out.println("yes"); + } else { + System.out.println("no"); + } - // :: error: (not.interned) - if (c != d) { - System.out.println("no"); - } else { - System.out.println("yes"); - } + // :: error: (not.interned) + if (c != d) { + System.out.println("no"); + } else { + System.out.println("yes"); } + } } diff --git a/checker/tests/interning/CompileTimeConstants.java b/checker/tests/interning/CompileTimeConstants.java index fe6e257405e..28597dc7178 100644 --- a/checker/tests/interning/CompileTimeConstants.java +++ b/checker/tests/interning/CompileTimeConstants.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.interning.qual.Interned; public class CompileTimeConstants { - class A { - static final String a1 = "hello"; - @Interned String a2 = "a2"; + class A { + static final String a1 = "hello"; + @Interned String a2 = "a2"; - void method() { - if (a1 == "hello") {} - } + void method() { + if (a1 == "hello") {} } + } - class B { - static final String b1 = "hello"; + class B { + static final String b1 = "hello"; - void method() { - if (b1 == A.a1) {} - } + void method() { + if (b1 == A.a1) {} } + } } diff --git a/checker/tests/interning/CompileTimeConstants2.java b/checker/tests/interning/CompileTimeConstants2.java index f4c725db444..f04e7b216f7 100644 --- a/checker/tests/interning/CompileTimeConstants2.java +++ b/checker/tests/interning/CompileTimeConstants2.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.interning.qual.Interned; public class CompileTimeConstants2 { - @Interned String s1 = "" + ("" + 1); + @Interned String s1 = "" + ("" + 1); - @Interned String s2 = (("" + ("" + 1))); + @Interned String s2 = (("" + ("" + 1))); - @Interned String s3 = ("" + (("")) + 1); + @Interned String s3 = ("" + (("")) + 1); - @Interned String s4 = "" + Math.PI; + @Interned String s4 = "" + Math.PI; - // To make sure that we would get an error if the RHS is not interned - // :: error: (assignment.type.incompatible) - @Interned String err = "" + new Object(); + // To make sure that we would get an error if the RHS is not interned + // :: error: (assignment.type.incompatible) + @Interned String err = "" + new Object(); } diff --git a/checker/tests/interning/ComplexComparison.java b/checker/tests/interning/ComplexComparison.java index cc29f2b9e8e..d50c8048796 100644 --- a/checker/tests/interning/ComplexComparison.java +++ b/checker/tests/interning/ComplexComparison.java @@ -1,94 +1,93 @@ -import org.checkerframework.checker.interning.qual.Interned; - import java.util.Comparator; +import org.checkerframework.checker.interning.qual.Interned; public class ComplexComparison { - void testInterned() { + void testInterned() { - @Interned String a = "foo"; - @Interned String b = "bar"; + @Interned String a = "foo"; + @Interned String b = "bar"; - if (a != null && b != null && a == b) { - System.out.println("yes"); - } else { - System.out.println("no"); - } + if (a != null && b != null && a == b) { + System.out.println("yes"); + } else { + System.out.println("no"); } + } - void testInternedDueToFlow() { + void testInternedDueToFlow() { - String c = "foo"; - String d = "bar"; + String c = "foo"; + String d = "bar"; - if (c != null && d != null && c == d) { - System.out.println("yes"); - } else { - System.out.println("no"); - } + if (c != null && d != null && c == d) { + System.out.println("yes"); + } else { + System.out.println("no"); } + } - void testNotInterned() { + void testNotInterned() { - String e = new String("foo"); - String f = new String("bar"); + String e = new String("foo"); + String f = new String("bar"); - // :: error: (not.interned) - if (e != null && f != null && e == f) { - System.out.println("yes"); - } else { - System.out.println("no"); - } + // :: error: (not.interned) + if (e != null && f != null && e == f) { + System.out.println("yes"); + } else { + System.out.println("no"); } - - /* @ pure */ public class DoubleArrayComparatorLexical implements Comparator { - - /** - * Lexically compares o1 and o2 as double arrays. - * - * @return positive if o1 > 02, 0 if 01 == 02, negative if 01 < 02 - */ - public int compare(double[] a1, double[] a2) { - // Heuristic: permit "arg1 == arg2" in a test in the first statement - // of a "Comparator.compare" method, if the body just returns 0. - if (a1 == a2) { - return 0; - } - int len = Math.min(a1.length, a2.length); - for (int i = 0; i < len; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); - } - } - return a1.length - a2.length; + } + + /* @ pure */ public class DoubleArrayComparatorLexical implements Comparator { + + /** + * Lexically compares o1 and o2 as double arrays. + * + * @return positive if o1 > 02, 0 if 01 == 02, negative if 01 < 02 + */ + public int compare(double[] a1, double[] a2) { + // Heuristic: permit "arg1 == arg2" in a test in the first statement + // of a "Comparator.compare" method, if the body just returns 0. + if (a1 == a2) { + return 0; + } + int len = Math.min(a1.length, a2.length); + for (int i = 0; i < len; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); } + } + return a1.length - a2.length; } - - class C { - @Override - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object other) { - // Heuristic: permit "this == arg1" in a test in the first statement - // of a "Comparator.compare" method, if the body just returns true. - if (this == other) { - return true; - } - return super.equals(other); - } + } + + class C { + @Override + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object other) { + // Heuristic: permit "this == arg1" in a test in the first statement + // of a "Comparator.compare" method, if the body just returns true. + if (this == other) { + return true; + } + return super.equals(other); } - - // // TODO - // class D { - // @Override - // public boolean equals(Object other) { - // // Don't suppress warnings at "this == arg1" if arg1 has been reassigned - // other = new Object(); - // - // if (this == other) { - // return true; - // } - // return super.equals(other); - // } - // } + } + + // // TODO + // class D { + // @Override + // public boolean equals(Object other) { + // // Don't suppress warnings at "this == arg1" if arg1 has been reassigned + // other = new Object(); + // + // if (this == other) { + // return true; + // } + // return super.equals(other); + // } + // } } diff --git a/checker/tests/interning/ConditionalInterning.java b/checker/tests/interning/ConditionalInterning.java index 342cd77e981..0ae5b59af4b 100644 --- a/checker/tests/interning/ConditionalInterning.java +++ b/checker/tests/interning/ConditionalInterning.java @@ -1,7 +1,7 @@ public class ConditionalInterning { - int a, b, c; + int a, b, c; - boolean cmp() { - return (a > b ? a < c : a > c); - } + boolean cmp() { + return (a > b ? a < c : a > c); + } } diff --git a/checker/tests/interning/ConstantsInterning.java b/checker/tests/interning/ConstantsInterning.java index 1c416237214..ac50373b211 100644 --- a/checker/tests/interning/ConstantsInterning.java +++ b/checker/tests/interning/ConstantsInterning.java @@ -2,36 +2,36 @@ public class ConstantsInterning { - // All but D should be inferred to be @Interned String. - final String A = "A"; - final String B = "B"; - final String AB = A + B; - final String AC = A + "C"; - final String D = new String("D"); - final @Interned String E = new String("E").intern(); - final Object F = "F"; + // All but D should be inferred to be @Interned String. + final String A = "A"; + final String B = "B"; + final String AB = A + B; + final String AC = A + "C"; + final String D = new String("D"); + final @Interned String E = new String("E").intern(); + final Object F = "F"; - void foo() { - @Interned String is; - is = A; - is = B; - is = AB; - is = A + B; - is = AC; - is = A + "C"; - is = A + B + "C"; - // :: error: (assignment.type.incompatible) - is = D; - // :: error: (assignment.type.incompatible) - is = A + E; - // :: error: (assignment.type.incompatible) - is = is + is; - is = Constants2.E; - // :: error: (assignment.type.incompatible) - is = (String) F; - } + void foo() { + @Interned String is; + is = A; + is = B; + is = AB; + is = A + B; + is = AC; + is = A + "C"; + is = A + B + "C"; + // :: error: (assignment.type.incompatible) + is = D; + // :: error: (assignment.type.incompatible) + is = A + E; + // :: error: (assignment.type.incompatible) + is = is + is; + is = Constants2.E; + // :: error: (assignment.type.incompatible) + is = (String) F; + } } class Constants2 { - public static final String E = "e"; + public static final String E = "e"; } diff --git a/checker/tests/interning/Creation.java b/checker/tests/interning/Creation.java index 54f1675ad4b..f79182869e5 100644 --- a/checker/tests/interning/Creation.java +++ b/checker/tests/interning/Creation.java @@ -1,48 +1,48 @@ import org.checkerframework.checker.interning.qual.Interned; public class Creation { - @Interned Foo[] a = new @Interned Foo[22]; // valid - - class Foo {} - - @Interned Foo[] fa_field1 = new @Interned Foo[22]; // valid - @Interned Foo[] fa_field2 = new @Interned Foo[22]; // valid - - public void test() { - // :: error: (assignment.type.incompatible) - @Interned Foo f = new Foo(); // error - Foo g = new Foo(); // valid - // :: warning: (cast.unsafe.constructor.invocation) - @Interned Foo h = new @Interned Foo(); // valid - // :: error: (not.interned) - boolean b = (f == g); // error - - @Interned Foo[] fa1 = new @Interned Foo[22]; // valid - @Interned Foo[] fa2 = new @Interned Foo[22]; // valid - } - - public @Interned Object read_data_0() { - // :: error: (return.type.incompatible) - return new Object(); - } - - public @Interned Object read_data_1() { - // :: error: (return.type.incompatible) - return Integer.valueOf(22); - } - - public @Interned Integer read_data_2() { - // :: error: (return.type.incompatible) - return Integer.valueOf(22); - } - - public @Interned Object read_data_3() { - // :: error: (return.type.incompatible) - return new String("hello"); - } - - public @Interned String read_data_4() { - // :: error: (return.type.incompatible) - return new String("hello"); - } + @Interned Foo[] a = new @Interned Foo[22]; // valid + + class Foo {} + + @Interned Foo[] fa_field1 = new @Interned Foo[22]; // valid + @Interned Foo[] fa_field2 = new @Interned Foo[22]; // valid + + public void test() { + // :: error: (assignment.type.incompatible) + @Interned Foo f = new Foo(); // error + Foo g = new Foo(); // valid + // :: warning: (cast.unsafe.constructor.invocation) + @Interned Foo h = new @Interned Foo(); // valid + // :: error: (not.interned) + boolean b = (f == g); // error + + @Interned Foo[] fa1 = new @Interned Foo[22]; // valid + @Interned Foo[] fa2 = new @Interned Foo[22]; // valid + } + + public @Interned Object read_data_0() { + // :: error: (return.type.incompatible) + return new Object(); + } + + public @Interned Object read_data_1() { + // :: error: (return.type.incompatible) + return Integer.valueOf(22); + } + + public @Interned Integer read_data_2() { + // :: error: (return.type.incompatible) + return Integer.valueOf(22); + } + + public @Interned Object read_data_3() { + // :: error: (return.type.incompatible) + return new String("hello"); + } + + public @Interned String read_data_4() { + // :: error: (return.type.incompatible) + return new String("hello"); + } } diff --git a/checker/tests/interning/Creation2.java b/checker/tests/interning/Creation2.java index 0cf9eb03f2b..26bea580a5b 100644 --- a/checker/tests/interning/Creation2.java +++ b/checker/tests/interning/Creation2.java @@ -2,12 +2,12 @@ public class Creation2 { - @Interned class Baz { - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - @Interned Baz() {} - } + @Interned class Baz { + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @Interned Baz() {} + } - void test() { - Baz b = new Baz(); - } + void test() { + Baz b = new Baz(); + } } diff --git a/checker/tests/interning/Distinct.java b/checker/tests/interning/Distinct.java index 4c66f0860e3..db1e1ec6790 100644 --- a/checker/tests/interning/Distinct.java +++ b/checker/tests/interning/Distinct.java @@ -3,66 +3,66 @@ public class Distinct { - class Foo {} + class Foo {} - Foo f1; - Foo f2; - @Interned Foo i1; - @Interned Foo i2; - @InternedDistinct Foo d1; - @InternedDistinct Foo d2; + Foo f1; + Foo f2; + @Interned Foo i1; + @Interned Foo i2; + @InternedDistinct Foo d1; + @InternedDistinct Foo d2; - public void testEquals() { - // :: error: (not.interned) - if (f1 == f2) {} - // :: error: (not.interned) - if (f1 == i2) {} - if (f1 == d2) {} - // :: error: (not.interned) - if (i1 == f2) {} - if (i1 == i2) {} - if (i1 == d2) {} - if (d1 == f2) {} - if (d1 == i2) {} - if (d1 == d2) {} - } + public void testEquals() { + // :: error: (not.interned) + if (f1 == f2) {} + // :: error: (not.interned) + if (f1 == i2) {} + if (f1 == d2) {} + // :: error: (not.interned) + if (i1 == f2) {} + if (i1 == i2) {} + if (i1 == d2) {} + if (d1 == f2) {} + if (d1 == i2) {} + if (d1 == d2) {} + } - public void testAssignment1() { - f1 = f2; - } + public void testAssignment1() { + f1 = f2; + } - public void testAssignment2() { - f1 = i2; - } + public void testAssignment2() { + f1 = i2; + } - public void testAssignment3() { - f1 = d2; - } + public void testAssignment3() { + f1 = d2; + } - public void testAssignment4() { - // :: error: (assignment.type.incompatible) - i1 = f2; - } + public void testAssignment4() { + // :: error: (assignment.type.incompatible) + i1 = f2; + } - public void testAssignment5() { - i1 = i2; - } + public void testAssignment5() { + i1 = i2; + } - public void testAssignment6() { - i1 = d2; - } + public void testAssignment6() { + i1 = d2; + } - public void testAssignment7() { - // :: error: (assignment.type.incompatible) - d1 = f2; - } + public void testAssignment7() { + // :: error: (assignment.type.incompatible) + d1 = f2; + } - public void testAssignment8() { - // :: error: (assignment.type.incompatible) - d1 = i2; - } + public void testAssignment8() { + // :: error: (assignment.type.incompatible) + d1 = i2; + } - public void testAssignment9() { - d1 = d2; - } + public void testAssignment9() { + d1 = d2; + } } diff --git a/checker/tests/interning/DontCrash.java b/checker/tests/interning/DontCrash.java index 985f882b9ed..979fff9c61c 100644 --- a/checker/tests/interning/DontCrash.java +++ b/checker/tests/interning/DontCrash.java @@ -2,27 +2,26 @@ // shouldn't crash. (Maybe they shouldn't run at all if javac issues any errors?) // @skip-test -import org.checkerframework.checker.interning.qual.Interned; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.Interned; public class DontCrash { - // from VarInfoAux - static class VIA { - // :: non-static variable this cannot be referenced from a static context - // :: inner classes cannot have static declarations - // :: non-static variable this cannot be referenced from a static context - // :: inner classes cannot have static declarations - private static VIA theDefault = new VIA(); - private Map<@Interned String, @Interned String> map; + // from VarInfoAux + static class VIA { + // :: non-static variable this cannot be referenced from a static context + // :: inner classes cannot have static declarations + // :: non-static variable this cannot be referenced from a static context + // :: inner classes cannot have static declarations + private static VIA theDefault = new VIA(); + private Map<@Interned String, @Interned String> map; - void testMap() { - Map<@Interned String, @Interned String> mymap; - mymap = theDefault.map; - mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); - mymap = new HashMap<>(theDefault.map); - } + void testMap() { + Map<@Interned String, @Interned String> mymap; + mymap = theDefault.map; + mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); + mymap = new HashMap<>(theDefault.map); } + } } diff --git a/checker/tests/interning/Enumerations.java b/checker/tests/interning/Enumerations.java index 40047c45ef7..449a44754e3 100644 --- a/checker/tests/interning/Enumerations.java +++ b/checker/tests/interning/Enumerations.java @@ -1,29 +1,29 @@ public class Enumerations { - // All enumeration instances are interned; there should be no need for an annotation. - enum StudentYear { - FRESHMAN, - SOPHOMORE, - JUNIOR, - SENIOR; + // All enumeration instances are interned; there should be no need for an annotation. + enum StudentYear { + FRESHMAN, + SOPHOMORE, + JUNIOR, + SENIOR; - // check that receiver is OK - @org.checkerframework.dataflow.qual.Pure - public String toString() { - return "StudentYear: ..."; - } + // check that receiver is OK + @org.checkerframework.dataflow.qual.Pure + public String toString() { + return "StudentYear: ..."; } + } - public boolean isSophomore(StudentYear sy) { - return sy == StudentYear.SOPHOMORE; - } + public boolean isSophomore(StudentYear sy) { + return sy == StudentYear.SOPHOMORE; + } - public boolean flow(StudentYear s) { - StudentYear m = StudentYear.SOPHOMORE; - return s == m; - } + public boolean flow(StudentYear s) { + StudentYear m = StudentYear.SOPHOMORE; + return s == m; + } - StudentYear cast(Object o) { - return (StudentYear) o; - } + StudentYear cast(Object o) { + return (StudentYear) o; + } } diff --git a/checker/tests/interning/ExpressionsInterning.java b/checker/tests/interning/ExpressionsInterning.java index e8fe534ae01..4c4f1857f35 100644 --- a/checker/tests/interning/ExpressionsInterning.java +++ b/checker/tests/interning/ExpressionsInterning.java @@ -2,53 +2,53 @@ public class ExpressionsInterning { - class A { - B b; - } - - class B { - C c; + class A { + B b; + } - D d() { - return new D(); - } + class B { + C c; - Boolean bBoolean() { - return true; - } + D d() { + return new D(); } - class C {} - - class D {} - - public Boolean fieldThenMethod(A a) { - Boolean temp = a.b.bBoolean(); - return temp; + Boolean bBoolean() { + return true; } + } - class Foo { - public @Interned Foo returnThis(@Interned Foo this, @Interned Foo other) { - if (other == this) { - return this; - } else { - return null; - } - } - } + class C {} - // :: warning: (cast.unsafe.constructor.invocation) - public @Interned Foo THEONE = new @Interned Foo(); + class D {} - public boolean isItTheOne(Foo f) { - return THEONE.equals(f); - } + public Boolean fieldThenMethod(A a) { + Boolean temp = a.b.bBoolean(); + return temp; + } - // A warning when interned objects are compared via .equals helps me in determining whether it - // is a good idea to convert a given class or reference to @Interned -- I can see whether there - // are places that it is compared with .equals, which I might need to examine. - public boolean dontUseEqualsMethod(@Interned Foo f1, @Interned Foo f2) { - // :: warning: (unnecessary.equals) - return f1.equals(f2); + class Foo { + public @Interned Foo returnThis(@Interned Foo this, @Interned Foo other) { + if (other == this) { + return this; + } else { + return null; + } } + } + + // :: warning: (cast.unsafe.constructor.invocation) + public @Interned Foo THEONE = new @Interned Foo(); + + public boolean isItTheOne(Foo f) { + return THEONE.equals(f); + } + + // A warning when interned objects are compared via .equals helps me in determining whether it + // is a good idea to convert a given class or reference to @Interned -- I can see whether there + // are places that it is compared with .equals, which I might need to examine. + public boolean dontUseEqualsMethod(@Interned Foo f1, @Interned Foo f2) { + // :: warning: (unnecessary.equals) + return f1.equals(f2); + } } diff --git a/checker/tests/interning/FieldsImplicits.java b/checker/tests/interning/FieldsImplicits.java index 149e9536822..e46a07e377d 100644 --- a/checker/tests/interning/FieldsImplicits.java +++ b/checker/tests/interning/FieldsImplicits.java @@ -1,13 +1,13 @@ /** Tests that a final field annotation is inferred. */ public class FieldsImplicits { - final String finalField = "asdf"; - static final String finalStaticField = "asdf"; - String nonFinalField = "asdf"; + final String finalField = "asdf"; + static final String finalStaticField = "asdf"; + String nonFinalField = "asdf"; - void test() { - boolean a = finalField == "asdf"; - boolean b = finalStaticField == "asdf"; - // :: error: (not.interned) - boolean c = nonFinalField == "asdf"; - } + void test() { + boolean a = finalField == "asdf"; + boolean b = finalStaticField == "asdf"; + // :: error: (not.interned) + boolean c = nonFinalField == "asdf"; + } } diff --git a/checker/tests/interning/FindDistinctTest.java b/checker/tests/interning/FindDistinctTest.java index d1ab75cd3d6..238256349de 100644 --- a/checker/tests/interning/FindDistinctTest.java +++ b/checker/tests/interning/FindDistinctTest.java @@ -4,28 +4,28 @@ public class FindDistinctTest { - public void ok1(@FindDistinct Object o) { - // TODO: The fact that this type-checks is an (undesired) artifact of the current - // implementation of @FindDistinct. - @InternedDistinct Object o2 = o; - } + public void ok1(@FindDistinct Object o) { + // TODO: The fact that this type-checks is an (undesired) artifact of the current + // implementation of @FindDistinct. + @InternedDistinct Object o2 = o; + } - public void ok2(@FindDistinct Object findIt, Object other) { - boolean b = findIt == other; - } + public void ok2(@FindDistinct Object findIt, Object other) { + boolean b = findIt == other; + } - public void useOk1(Object notinterned, @Interned Object interned) { - ok1(notinterned); - ok1(interned); - } + public void useOk1(Object notinterned, @Interned Object interned) { + ok1(notinterned); + ok1(interned); + } - public void bad1(Object o) { - // :: error: (assignment.type.incompatible) - @InternedDistinct Object o2 = o; - } + public void bad1(Object o) { + // :: error: (assignment.type.incompatible) + @InternedDistinct Object o2 = o; + } - public void bad2(Object findIt, Object other) { - // :: error: (not.interned) - boolean b = findIt == other; - } + public void bad2(Object findIt, Object other) { + // :: error: (not.interned) + boolean b = findIt == other; + } } diff --git a/checker/tests/interning/FlowInterning.java b/checker/tests/interning/FlowInterning.java index bd78ffe7a53..247e3359fe2 100644 --- a/checker/tests/interning/FlowInterning.java +++ b/checker/tests/interning/FlowInterning.java @@ -3,58 +3,58 @@ public class FlowInterning { - // @skip-test - // Look at issue 47 - // public boolean isSame(Object a, Object b) { - // return ((a == null) - // ? (a == b) - // : (a.equals(b))); - // } + // @skip-test + // Look at issue 47 + // public boolean isSame(Object a, Object b) { + // return ((a == null) + // ? (a == b) + // : (a.equals(b))); + // } - public void testAppendingChar() { - String arg = ""; - arg += ' '; + public void testAppendingChar() { + String arg = ""; + arg += ' '; - // Interning Checker should NOT suggest == here. - if (!arg.equals("")) {} - } + // Interning Checker should NOT suggest == here. + if (!arg.equals("")) {} + } - public String[] parse(String args) { + public String[] parse(String args) { - // Split the args string on whitespace boundaries accounting for quoted strings. - args = args.trim(); - List arg_list = new ArrayList<>(); - String arg = ""; - char active_quote = 0; - for (int ii = 0; ii < args.length(); ii++) { - char ch = args.charAt(ii); - if ((ch == '\'') || (ch == '"')) { - arg += ch; - ii++; - while ((ii < args.length()) && (args.charAt(ii) != ch)) { - arg += args.charAt(ii++); - } - arg += ch; - } else if (Character.isWhitespace(ch)) { - // System.out.printf ("adding argument '%s'%n", arg); - arg_list.add(arg); - arg = ""; - while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { - ii++; - } - if (ii < args.length()) { - ii--; - } - } else { // must be part of current argument - arg += ch; - } + // Split the args string on whitespace boundaries accounting for quoted strings. + args = args.trim(); + List arg_list = new ArrayList<>(); + String arg = ""; + char active_quote = 0; + for (int ii = 0; ii < args.length(); ii++) { + char ch = args.charAt(ii); + if ((ch == '\'') || (ch == '"')) { + arg += ch; + ii++; + while ((ii < args.length()) && (args.charAt(ii) != ch)) { + arg += args.charAt(ii++); } - // Interning Checker should NOT suggest == here. - if (!arg.equals("")) { - arg_list.add(arg); + arg += ch; + } else if (Character.isWhitespace(ch)) { + // System.out.printf ("adding argument '%s'%n", arg); + arg_list.add(arg); + arg = ""; + while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { + ii++; } - - String[] argsArray = arg_list.toArray(new String[arg_list.size()]); - return argsArray; + if (ii < args.length()) { + ii--; + } + } else { // must be part of current argument + arg += ch; + } } + // Interning Checker should NOT suggest == here. + if (!arg.equals("")) { + arg_list.add(arg); + } + + String[] argsArray = arg_list.toArray(new String[arg_list.size()]); + return argsArray; + } } diff --git a/checker/tests/interning/FlowInterning1.java b/checker/tests/interning/FlowInterning1.java index 436a6f94daf..ef13e1f0ced 100644 --- a/checker/tests/interning/FlowInterning1.java +++ b/checker/tests/interning/FlowInterning1.java @@ -3,14 +3,14 @@ */ public class FlowInterning1 { - void test(String[] tokens, int i) { - String arg_type_name = tokens[i].intern(); - if (i + 1 >= tokens.length) { - throw new RuntimeException("No matching arg val for argument type " + arg_type_name); - } - String arg_val = tokens[i + 1]; - if (arg_type_name == "boolean") { // interned - // ... - } + void test(String[] tokens, int i) { + String arg_type_name = tokens[i].intern(); + if (i + 1 >= tokens.length) { + throw new RuntimeException("No matching arg val for argument type " + arg_type_name); } + String arg_val = tokens[i + 1]; + if (arg_type_name == "boolean") { // interned + // ... + } + } } diff --git a/checker/tests/interning/Generics.java b/checker/tests/interning/Generics.java index 5f145af44de..aa6b014b29d 100644 --- a/checker/tests/interning/Generics.java +++ b/checker/tests/interning/Generics.java @@ -1,5 +1,3 @@ -import org.checkerframework.checker.interning.qual.Interned; - import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -7,135 +5,136 @@ import java.util.List; import java.util.Map; import java.util.Vector; +import org.checkerframework.checker.interning.qual.Interned; public class Generics { - void testGenerics() { + void testGenerics() { - Map map = null; - map = new HashMap<>(); + Map map = null; + map = new HashMap<>(); - String a = new String("foo"); - @Interned String b = "bar"; + String a = new String("foo"); + @Interned String b = "bar"; - String notInterned; - @Interned String interned; + String notInterned; + @Interned String interned; - map.put(a, b); // valid - // :: error: (argument.type.incompatible) - map.put(b, a); // error + map.put(a, b); // valid + // :: error: (argument.type.incompatible) + map.put(b, a); // error - notInterned = map.get(a); // valid - interned = map.get(b); // valid + notInterned = map.get(a); // valid + interned = map.get(b); // valid - Collection<@Interned String> internedSet; - Collection notInternedSet; + Collection<@Interned String> internedSet; + Collection notInternedSet; - notInternedSet = map.keySet(); // valid - // :: error: (assignment.type.incompatible) - internedSet = map.keySet(); // error + notInternedSet = map.keySet(); // valid + // :: error: (assignment.type.incompatible) + internedSet = map.keySet(); // error - // :: error: (assignment.type.incompatible) - notInternedSet = map.values(); // error - internedSet = map.values(); // valid + // :: error: (assignment.type.incompatible) + notInternedSet = map.values(); // error + internedSet = map.values(); // valid - HashMap<@Interned String, Vector<@Interned Integer>> all_nums = new HashMap<>(); - Vector<@Interned Integer> v = all_nums.get("Hello"); - } + HashMap<@Interned String, Vector<@Interned Integer>> all_nums = new HashMap<>(); + Vector<@Interned Integer> v = all_nums.get("Hello"); + } - // The cells aren't interned, but their contents are - class CellOfImm { - T value; + // The cells aren't interned, but their contents are + class CellOfImm { + T value; - boolean equals(CellOfImm other) { - return value == other.value; // valid - } + boolean equals(CellOfImm other) { + return value == other.value; // valid } - - List<@Interned String> istrings = new ArrayList<>(); - List strings = new ArrayList<>(); - @Interned String istring = "interned"; - String string = new String("uninterned"); - - void testGenerics2() { - istrings.add(istring); - // :: error: (argument.type.incompatible) - istrings.add(string); // invalid - strings.add(istring); - strings.add(string); - istring = istrings.get(0); - string = istrings.get(0); - // :: error: (assignment.type.incompatible) - istring = strings.get(0); // invalid - string = strings.get(0); + } + + List<@Interned String> istrings = new ArrayList<>(); + List strings = new ArrayList<>(); + @Interned String istring = "interned"; + String string = new String("uninterned"); + + void testGenerics2() { + istrings.add(istring); + // :: error: (argument.type.incompatible) + istrings.add(string); // invalid + strings.add(istring); + strings.add(string); + istring = istrings.get(0); + string = istrings.get(0); + // :: error: (assignment.type.incompatible) + istring = strings.get(0); // invalid + string = strings.get(0); + } + + void testCollections() { + Collection strings = Collections.unmodifiableCollection(new ArrayList()); + + Collection<@Interned String> istrings = + Collections.unmodifiableCollection(new ArrayList<@Interned String>()); // valid + } + + class MyList extends ArrayList<@Interned String> { + // Correct return value is Iterator<@Interned String> + // :: error: (override.return.invalid) + public Iterator iterator() { + return null; } - - void testCollections() { - Collection strings = Collections.unmodifiableCollection(new ArrayList()); - - Collection<@Interned String> istrings = - Collections.unmodifiableCollection(new ArrayList<@Interned String>()); // valid + } + + // from VarInfoAux + static class VIA { + private static VIA theDefault = new VIA(); + private Map<@Interned String, @Interned String> map; + + void testMap() { + Map<@Interned String, @Interned String> mymap; + mymap = theDefault.map; + mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); + mymap = new HashMap<>(theDefault.map); } - - class MyList extends ArrayList<@Interned String> { - // Correct return value is Iterator<@Interned String> - // :: error: (override.return.invalid) - public Iterator iterator() { - return null; - } + } + + // type inference + T id(T m, Object t) { + return m; + } + + void useID() { + String o = id("m", null); + } + + // raw types again + void testRawTypes() { + ArrayList lst = null; + Collections.sort(lst); + } + + public static class Pair { + public T1 a; + public T2 b; + + public Pair(T1 a, T2 b) { + this.a = a; + this.b = b; } - // from VarInfoAux - static class VIA { - private static VIA theDefault = new VIA(); - private Map<@Interned String, @Interned String> map; - - void testMap() { - Map<@Interned String, @Interned String> mymap; - mymap = theDefault.map; - mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); - mymap = new HashMap<>(theDefault.map); - } - } - - // type inference - T id(T m, Object t) { - return m; - } - - void useID() { - String o = id("m", null); - } - - // raw types again - void testRawTypes() { - ArrayList lst = null; - Collections.sort(lst); - } - - public static class Pair { - public T1 a; - public T2 b; - - public Pair(T1 a, T2 b) { - this.a = a; - this.b = b; - } - - /** Factory method with short name and no need to name type parameters. */ - public static Pair of(A a, B b) { - return new Pair<>(a, b); - } - } - - static class C { - T next1; - - // @skip-test - // This test might be faulty - // private Pair return1() { - // Pair result = Pair.of(next1, (T)null); - // return result; - // } + /** Factory method with short name and no need to name type parameters. */ + public static Pair of(A a, B b) { + return new Pair<>(a, b); } + } + + static class C { + T next1; + + // @skip-test + // This test might be faulty + // private Pair return1() { + // Pair result = Pair.of(next1, (T)null); + // return result; + // } + } } diff --git a/checker/tests/interning/HeuristicsTest.java b/checker/tests/interning/HeuristicsTest.java index a5bb85ec070..bda33b5599c 100644 --- a/checker/tests/interning/HeuristicsTest.java +++ b/checker/tests/interning/HeuristicsTest.java @@ -1,239 +1,238 @@ +import java.util.Comparator; import org.checkerframework.checker.interning.qual.CompareToMethod; import org.checkerframework.checker.interning.qual.EqualsMethod; -import java.util.Comparator; - public class HeuristicsTest implements Comparable { - public static final class MyComparator implements Comparator { - // Using == is OK if it's the first statement in the compare method, - // it's comparing the arguments, and the return value is 0. - public int compare(String s1, String s2) { - if (s1 == s2) { - return 0; - } - return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); - } - } - - @Override - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - if (this == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == this) { - return true; - } - return false; - } - - @EqualsMethod - @org.checkerframework.dataflow.qual.Pure - public boolean equals2(Object o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - if (this == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == this) { - return true; - } - return false; - } - - @org.checkerframework.dataflow.qual.Pure - public boolean equals3(Object o) { - // Not equals() or annotated as @EqualsMethod. - // :: error: (not.interned) - if (this == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == this) { - return true; - } - return false; - } - - @EqualsMethod - @org.checkerframework.dataflow.qual.Pure - public static boolean equals4(Object thisOne, Object o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - if (thisOne == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == thisOne) { - return true; - } - return false; - } - - @org.checkerframework.dataflow.qual.Pure - public static boolean equals5(Object thisOne, Object o) { - // Not equals() or annotated as @EqualsMethod. - // :: error: (not.interned) - if (thisOne == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == thisOne) { - return true; - } - return false; - } - - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean equals6() { - return true; - } - - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean equals7(int a, int b, int c) { - return true; - } - - @Override - @org.checkerframework.dataflow.qual.Pure - public int compareTo(HeuristicsTest o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - - if (o == this) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (this == o) { - return 0; - } - return 0; - } - - @CompareToMethod - @org.checkerframework.dataflow.qual.Pure - public int compareTo2(HeuristicsTest o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - - if (o == this) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (this == o) { - return 0; - } - return 0; - } - - @org.checkerframework.dataflow.qual.Pure - public int compareTo3(HeuristicsTest o) { - // Not compareTo or annotated as @CompareToMethod - // :: error: (not.interned) - if (o == this) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (this == o) { - return 0; - } - return 0; - } - - @CompareToMethod - @org.checkerframework.dataflow.qual.Pure - public static int compareTo4(HeuristicsTest thisOne, HeuristicsTest o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - - if (o == thisOne) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (thisOne == o) { - return 0; - } - return 0; - } - - @org.checkerframework.dataflow.qual.Pure - public static int compareTo5(HeuristicsTest thisOne, HeuristicsTest o) { - // Not compareTo or annotated as @CompareToMethod - // :: error: (not.interned) - if (o == thisOne) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (thisOne == o) { - return 0; - } + public static final class MyComparator implements Comparator { + // Using == is OK if it's the first statement in the compare method, + // it's comparing the arguments, and the return value is 0. + public int compare(String s1, String s2) { + if (s1 == s2) { return 0; - } + } + return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); + } + } + + @Override + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @EqualsMethod + @org.checkerframework.dataflow.qual.Pure + public boolean equals2(Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @org.checkerframework.dataflow.qual.Pure + public boolean equals3(Object o) { + // Not equals() or annotated as @EqualsMethod. + // :: error: (not.interned) + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @EqualsMethod + @org.checkerframework.dataflow.qual.Pure + public static boolean equals4(Object thisOne, Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (thisOne == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == thisOne) { + return true; + } + return false; + } + + @org.checkerframework.dataflow.qual.Pure + public static boolean equals5(Object thisOne, Object o) { + // Not equals() or annotated as @EqualsMethod. + // :: error: (not.interned) + if (thisOne == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == thisOne) { + return true; + } + return false; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean equals6() { + return true; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean equals7(int a, int b, int c) { + return true; + } + + @Override + @org.checkerframework.dataflow.qual.Pure + public int compareTo(HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @CompareToMethod + @org.checkerframework.dataflow.qual.Pure + public int compareTo2(HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @org.checkerframework.dataflow.qual.Pure + public int compareTo3(HeuristicsTest o) { + // Not compareTo or annotated as @CompareToMethod + // :: error: (not.interned) + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @CompareToMethod + @org.checkerframework.dataflow.qual.Pure + public static int compareTo4(HeuristicsTest thisOne, HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == thisOne) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (thisOne == o) { + return 0; + } + return 0; + } + + @org.checkerframework.dataflow.qual.Pure + public static int compareTo5(HeuristicsTest thisOne, HeuristicsTest o) { + // Not compareTo or annotated as @CompareToMethod + // :: error: (not.interned) + if (o == thisOne) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (thisOne == o) { + return 0; + } + return 0; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean compareTo6() { + return true; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean compareTo7(int a, int b, int c) { + return true; + } - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean compareTo6() { - return true; + public boolean optimizeEqualsClient(Object a, Object b, Object[] arr) { + // Using == is OK if it's the left-hand side of an || whose right-hand + // side is a call to equals with the same arguments. + if (a == b || a.equals(b)) { + System.out.println("one"); } - - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean compareTo7(int a, int b, int c) { - return true; + if (a == b || b.equals(a)) { + System.out.println("two"); } - public boolean optimizeEqualsClient(Object a, Object b, Object[] arr) { - // Using == is OK if it's the left-hand side of an || whose right-hand - // side is a call to equals with the same arguments. - if (a == b || a.equals(b)) { - System.out.println("one"); - } - if (a == b || b.equals(a)) { - System.out.println("two"); - } + boolean c = (a == b || a.equals(b)); + c = (a == b || b.equals(a)); - boolean c = (a == b || a.equals(b)); - c = (a == b || b.equals(a)); + boolean d = (a == b) || (a != null ? a.equals(b) : false); - boolean d = (a == b) || (a != null ? a.equals(b) : false); + boolean e = (a == b || (a != null && a.equals(b))); - boolean e = (a == b || (a != null && a.equals(b))); + boolean f = (arr[0] == a || arr[0].equals(a)); - boolean f = (arr[0] == a || arr[0].equals(a)); + return (a == b || a.equals(b)); + } - return (a == b || a.equals(b)); + public > boolean optimizeCompareToClient(T a, T b) { + // Using == is OK if it's the left-hand side of an || whose right-hand + // side is a call to compareTo with the same arguments. + if (a == b || a.compareTo(b) == 0) { + System.out.println("one"); + } + if (a == b || b.compareTo(a) == 0) { + System.out.println("two"); } - public > boolean optimizeCompareToClient(T a, T b) { - // Using == is OK if it's the left-hand side of an || whose right-hand - // side is a call to compareTo with the same arguments. - if (a == b || a.compareTo(b) == 0) { - System.out.println("one"); - } - if (a == b || b.compareTo(a) == 0) { - System.out.println("two"); - } - - boolean c = (a == b || a.compareTo(b) == 0); - c = (a == b || a.compareTo(b) == 0); + boolean c = (a == b || a.compareTo(b) == 0); + c = (a == b || a.compareTo(b) == 0); - return (a == b || a.compareTo(b) == 0); - } + return (a == b || a.compareTo(b) == 0); + } } diff --git a/checker/tests/interning/InternMethodTest.java b/checker/tests/interning/InternMethodTest.java index 20ad501590e..3b7a43663f8 100644 --- a/checker/tests/interning/InternMethodTest.java +++ b/checker/tests/interning/InternMethodTest.java @@ -1,29 +1,28 @@ -import org.checkerframework.checker.interning.qual.Interned; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.Interned; public class InternMethodTest { - private static Map pool = new HashMap<>(); + private static Map pool = new HashMap<>(); - class Foo { + class Foo { - @SuppressWarnings("interning") - public @Interned Foo intern() { - if (!pool.containsKey(this)) { - pool.put(this, (@Interned Foo) this); - } - return pool.get(this); - } + @SuppressWarnings("interning") + public @Interned Foo intern() { + if (!pool.containsKey(this)) { + pool.put(this, (@Interned Foo) this); + } + return pool.get(this); } + } - void test() { - Foo f = new Foo(); - @Interned Foo g = f.intern(); - } + void test() { + Foo f = new Foo(); + @Interned Foo g = f.intern(); + } - public static @Interned String intern(String a) { - return (a == null) ? null : a.intern(); - } + public static @Interned String intern(String a) { + return (a == null) ? null : a.intern(); + } } diff --git a/checker/tests/interning/InternUnbox.java b/checker/tests/interning/InternUnbox.java index 4139e2ba6eb..dccd14e1603 100644 --- a/checker/tests/interning/InternUnbox.java +++ b/checker/tests/interning/InternUnbox.java @@ -1,12 +1,12 @@ public class InternUnbox { - void method() { - Boolean leftBoolean = getBooleanValue(); - createBooleanCFValue(!leftBoolean); - } + void method() { + Boolean leftBoolean = getBooleanValue(); + createBooleanCFValue(!leftBoolean); + } - private void createBooleanCFValue(boolean b) {} + private void createBooleanCFValue(boolean b) {} - private Boolean getBooleanValue() { - return Boolean.FALSE; - } + private Boolean getBooleanValue() { + return Boolean.FALSE; + } } diff --git a/checker/tests/interning/InternedClass.java b/checker/tests/interning/InternedClass.java index deaab6457e5..47dd3468a6a 100644 --- a/checker/tests/interning/InternedClass.java +++ b/checker/tests/interning/InternedClass.java @@ -1,157 +1,156 @@ -import org.checkerframework.checker.interning.qual.InternMethod; -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.interning.qual.UnknownInterned; - import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Vector; +import org.checkerframework.checker.interning.qual.InternMethod; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.interning.qual.UnknownInterned; // The @Interned annotation indicates that much like an enum, all variables // declared of this type are interned (except the constructor return value). public @Interned class InternedClass { - int value; - - InternedClass factory(int i) { - return new InternedClass(i).intern(); - } - + int value; + + InternedClass factory(int i) { + return new InternedClass(i).intern(); + } + + // Private constructor + private InternedClass(int i) { + value = i; + // "this" in the constructor is not interned. + // :: error: (assignment.type.incompatible) + @Interned InternedClass that = this; + } + + // Overriding method + @org.checkerframework.dataflow.qual.Pure + public String toString() { + @Interned InternedClass c = this; + return Integer.valueOf(value).toString(); + } + + // Factory method + private InternedClass(InternedClass ic) { + value = ic.value; + } + + // Equals method (used only by interning; clients should use ==) + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object other) { + if (!(other instanceof InternedClass)) { + return false; + } + return value == ((InternedClass) other).value; + } + + // Interning method + @SuppressWarnings("type.invalid.annotations.on.use") + private static Map<@UnknownInterned InternedClass, @Interned InternedClass> pool = + new HashMap<>(); + + @InternMethod + public @Interned InternedClass intern() { + if (!pool.containsKey(this)) { + pool.put(this, (@Interned InternedClass) this); + } + return pool.get(this); + } + + public void myMethod(InternedClass ic, InternedClass[] ica) { + boolean b1 = (this == ic); // valid + boolean b2 = (this == returnInternedObject()); // valid + boolean b3 = (this == ica[0]); // valid + InternedClass ic2 = returnArray()[0]; // valid + // :: error: (interned.object.creation) + ica[0] = new InternedClass(22); + InternedClass[] arr1 = returnArray(); // valid + InternedClass[] arr2 = new InternedClass[22]; // valid + InternedClass[] arr3 = new InternedClass[] {}; // valid + + Map map = new LinkedHashMap<>(); + for (Map.Entry e : map.entrySet()) { + InternedClass ic3 = e.getKey(); // valid + } + } + + public InternedClass returnInternedObject() { + return this; + } + + public InternedClass[] returnArray() { + return new InternedClass[] {}; + } + + public void internedVarargs(String name, InternedClass... args) { + InternedClass arg = args[0]; // valid + } + + public void internedVarargs2(String name, @Interned String... args) { + @Interned String arg = args[0]; // valid + } + + public static InternedClass[] arrayclone_simple(InternedClass[] a_old) { + int len = a_old.length; + InternedClass[] a_new = new InternedClass[len]; + for (int i = 0; i < len; i++) { + // :: error: (interned.object.creation) + a_new[i] = new InternedClass(a_old[i]); + } + return a_new; + } + + public @Interned class Subclass extends InternedClass { // Private constructor - private InternedClass(int i) { - value = i; - // "this" in the constructor is not interned. - // :: error: (assignment.type.incompatible) - @Interned InternedClass that = this; - } - - // Overriding method - @org.checkerframework.dataflow.qual.Pure - public String toString() { - @Interned InternedClass c = this; - return Integer.valueOf(value).toString(); - } - - // Factory method - private InternedClass(InternedClass ic) { - value = ic.value; - } - - // Equals method (used only by interning; clients should use ==) - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object other) { - if (!(other instanceof InternedClass)) { - return false; - } - return value == ((InternedClass) other).value; - } - - // Interning method - @SuppressWarnings("type.invalid.annotations.on.use") - private static Map<@UnknownInterned InternedClass, @Interned InternedClass> pool = - new HashMap<>(); - - @InternMethod - public @Interned InternedClass intern() { - if (!pool.containsKey(this)) { - pool.put(this, (@Interned InternedClass) this); - } - return pool.get(this); - } - - public void myMethod(InternedClass ic, InternedClass[] ica) { - boolean b1 = (this == ic); // valid - boolean b2 = (this == returnInternedObject()); // valid - boolean b3 = (this == ica[0]); // valid - InternedClass ic2 = returnArray()[0]; // valid - // :: error: (interned.object.creation) - ica[0] = new InternedClass(22); - InternedClass[] arr1 = returnArray(); // valid - InternedClass[] arr2 = new InternedClass[22]; // valid - InternedClass[] arr3 = new InternedClass[] {}; // valid - - Map map = new LinkedHashMap<>(); - for (Map.Entry e : map.entrySet()) { - InternedClass ic3 = e.getKey(); // valid - } - } - - public InternedClass returnInternedObject() { - return this; - } - - public InternedClass[] returnArray() { - return new InternedClass[] {}; - } - - public void internedVarargs(String name, InternedClass... args) { - InternedClass arg = args[0]; // valid - } - - public void internedVarargs2(String name, @Interned String... args) { - @Interned String arg = args[0]; // valid - } - - public static InternedClass[] arrayclone_simple(InternedClass[] a_old) { - int len = a_old.length; - InternedClass[] a_new = new InternedClass[len]; - for (int i = 0; i < len; i++) { - // :: error: (interned.object.creation) - a_new[i] = new InternedClass(a_old[i]); - } - return a_new; - } - - public @Interned class Subclass extends InternedClass { - // Private constructor - private Subclass(int i) { - super(i); - } - } - - public static void castFromInternedClass(InternedClass ic) { - Subclass s = (Subclass) ic; - } - - public static void castToInternedClass(Object o) { - InternedClass ic = (InternedClass) o; - } - - // Default implementation - @org.checkerframework.dataflow.qual.Pure - public InternedClass clone() throws CloneNotSupportedException { - return (InternedClass) super.clone(); - } - - // java.lang.Class should be considered interned - public static void classTest() { - Integer i = 5; - assert i.getClass() == Integer.class; - } - - // java.lang.Class is interned - public static void arrayOfClass() throws Exception { - Class c = String.class; - Class[] parameterTypes = new Class[1]; - parameterTypes[0] = String.class; - java.lang.reflect.Constructor ctor = c.getConstructor(parameterTypes); - } - - Class[] getSuperClasses(Class c) { - Vector> v = new Vector<>(); - while (true) { - // :: warning: (unnecessary.equals) - if (c.getSuperclass().equals((new Object()).getClass())) { - break; - } - c = c.getSuperclass(); - v.addElement(c); - } - return (Class[]) v.toArray(new Class[0]); - } - - void testCast(Object o) { - Object i = (InternedClass) o; - if (i == this) {} - } + private Subclass(int i) { + super(i); + } + } + + public static void castFromInternedClass(InternedClass ic) { + Subclass s = (Subclass) ic; + } + + public static void castToInternedClass(Object o) { + InternedClass ic = (InternedClass) o; + } + + // Default implementation + @org.checkerframework.dataflow.qual.Pure + public InternedClass clone() throws CloneNotSupportedException { + return (InternedClass) super.clone(); + } + + // java.lang.Class should be considered interned + public static void classTest() { + Integer i = 5; + assert i.getClass() == Integer.class; + } + + // java.lang.Class is interned + public static void arrayOfClass() throws Exception { + Class c = String.class; + Class[] parameterTypes = new Class[1]; + parameterTypes[0] = String.class; + java.lang.reflect.Constructor ctor = c.getConstructor(parameterTypes); + } + + Class[] getSuperClasses(Class c) { + Vector> v = new Vector<>(); + while (true) { + // :: warning: (unnecessary.equals) + if (c.getSuperclass().equals((new Object()).getClass())) { + break; + } + c = c.getSuperclass(); + v.addElement(c); + } + return (Class[]) v.toArray(new Class[0]); + } + + void testCast(Object o) { + Object i = (InternedClass) o; + if (i == this) {} + } } diff --git a/checker/tests/interning/InternedClass2.java b/checker/tests/interning/InternedClass2.java index b4ff45972ed..12177c55d43 100644 --- a/checker/tests/interning/InternedClass2.java +++ b/checker/tests/interning/InternedClass2.java @@ -1,79 +1,78 @@ -import org.checkerframework.checker.interning.qual.InternMethod; -import org.checkerframework.checker.interning.qual.Interned; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.InternMethod; +import org.checkerframework.checker.interning.qual.Interned; public @Interned class InternedClass2 { - private final int i; + private final int i; - // @UnknownInterned is the default annotation on constructor results even for @Interned classes. - private InternedClass2(int i) { - // Type of "this" inside a constructor of an @Interned class is @UnknownInterned. - // :: error: (assignment.type.incompatible) - @Interned InternedClass2 that = this; - this.i = i; - } + // @UnknownInterned is the default annotation on constructor results even for @Interned classes. + private InternedClass2(int i) { + // Type of "this" inside a constructor of an @Interned class is @UnknownInterned. + // :: error: (assignment.type.incompatible) + @Interned InternedClass2 that = this; + this.i = i; + } - InternedClass2 factory(int i) { - // :: error: (interned.object.creation) :: error: (method.invocation.invalid) - new InternedClass2(i).someMethod(); // error, call to constructor on for @Interned class. - (new InternedClass2(i)).intern(); // ok, call to constructor receiver to @InternMethod - ((((new InternedClass2(i))))).intern(); // ok, call to constructor receiver to @InternMethod - return new InternedClass2(i).intern(); // ok, call to constructor receiver to @InternMethod - } + InternedClass2 factory(int i) { + // :: error: (interned.object.creation) :: error: (method.invocation.invalid) + new InternedClass2(i).someMethod(); // error, call to constructor on for @Interned class. + (new InternedClass2(i)).intern(); // ok, call to constructor receiver to @InternMethod + ((((new InternedClass2(i))))).intern(); // ok, call to constructor receiver to @InternMethod + return new InternedClass2(i).intern(); // ok, call to constructor receiver to @InternMethod + } - void someMethod() { - // Type of "this" inside a method (not marked @InternedMethod) is @Interned, - // assuming the method is declared in an @Interned class. - @Interned InternedClass2 that = this; // ok - } + void someMethod() { + // Type of "this" inside a method (not marked @InternedMethod) is @Interned, + // assuming the method is declared in an @Interned class. + @Interned InternedClass2 that = this; // ok + } - private static Map pool = new HashMap<>(); + private static Map pool = new HashMap<>(); - @InternMethod - public InternedClass2 intern() { - // Type of "this" inside an @InternMethod is @UnknownInterned - // :: error: (assignment.type.incompatible) - @Interned InternedClass2 that = this; - if (!pool.containsKey(this.i)) { - // The above check proves "this" is interned. - @SuppressWarnings("interning:assignment.type.incompatible") - @Interned InternedClass2 internedThis = this; - pool.put(this.i, internedThis); - } - return pool.get(this.i); + @InternMethod + public InternedClass2 intern() { + // Type of "this" inside an @InternMethod is @UnknownInterned + // :: error: (assignment.type.incompatible) + @Interned InternedClass2 that = this; + if (!pool.containsKey(this.i)) { + // The above check proves "this" is interned. + @SuppressWarnings("interning:assignment.type.incompatible") + @Interned InternedClass2 internedThis = this; + pool.put(this.i, internedThis); } + return pool.get(this.i); + } - @Override // ok, no override invalid receiver error is issued. - public String toString() { - @Interned InternedClass2 that = this; // ok - return super.toString(); - } + @Override // ok, no override invalid receiver error is issued. + public String toString() { + @Interned InternedClass2 that = this; // ok + return super.toString(); + } - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } - InternedClass2 that = (InternedClass2) object; + InternedClass2 that = (InternedClass2) object; - return i == that.i; - } + return i == that.i; + } - @Override - public int hashCode() { - return i; - } + @Override + public int hashCode() { + return i; + } - public boolean hasNodeOfType(Class type) { - if (type == this.getClass()) { - return true; - } - return false; + public boolean hasNodeOfType(Class type) { + if (type == this.getClass()) { + return true; } + return false; + } } diff --git a/checker/tests/interning/InternedClassDecl.java b/checker/tests/interning/InternedClassDecl.java index b11da3bd1e1..b2974546e49 100644 --- a/checker/tests/interning/InternedClassDecl.java +++ b/checker/tests/interning/InternedClassDecl.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.interning.qual.Interned; public class InternedClassDecl { - static @Interned class InternedClass {} + static @Interned class InternedClass {} - static class Generic {} + static class Generic {} - static @Interned class RecursiveClass2> {} + static @Interned class RecursiveClass2> {} } diff --git a/checker/tests/interning/Issue2809.java b/checker/tests/interning/Issue2809.java index 64a37135293..dd2e9777f3e 100644 --- a/checker/tests/interning/Issue2809.java +++ b/checker/tests/interning/Issue2809.java @@ -6,26 +6,26 @@ public class Issue2809 { - void new1(MyType t, int @Interned [] non) { - t.self(new MyType<>(non)); - } + void new1(MyType t, int @Interned [] non) { + t.self(new MyType<>(non)); + } - void new2(MyType t, int @Interned [] non) { - t.self(new MyType(non)); - } + void new2(MyType t, int @Interned [] non) { + t.self(new MyType(non)); + } - void new3(MyType<@Interned MyType> t, @Interned MyType non) { - t.self(new MyType<>(non)); - } + void new3(MyType<@Interned MyType> t, @Interned MyType non) { + t.self(new MyType<>(non)); + } - void newFail(MyType t, int @UnknownInterned [] non) { - // :: error: (argument.type.incompatible) - t.self(new MyType<>(non)); - } + void newFail(MyType t, int @UnknownInterned [] non) { + // :: error: (argument.type.incompatible) + t.self(new MyType<>(non)); + } - class MyType { - MyType(T p) {} + class MyType { + MyType(T p) {} - void self(MyType myType) {} - } + void self(MyType myType) {} + } } diff --git a/checker/tests/interning/Issue3594.java b/checker/tests/interning/Issue3594.java index 4d231c06166..ab461faa641 100644 --- a/checker/tests/interning/Issue3594.java +++ b/checker/tests/interning/Issue3594.java @@ -1,10 +1,10 @@ public class Issue3594 { - // Throwable is annotated with @UsesObjectEquals, which is an inherited annotation. - // So, MyThrowable should be treated as @UsesObjectEquals, too. - static class MyThrowable extends Throwable {} + // Throwable is annotated with @UsesObjectEquals, which is an inherited annotation. + // So, MyThrowable should be treated as @UsesObjectEquals, too. + static class MyThrowable extends Throwable {} - void use(MyThrowable t, MyThrowable t2) { - boolean b = t == t2; - } + void use(MyThrowable t, MyThrowable t2) { + boolean b = t == t2; + } } diff --git a/checker/tests/interning/IterableGenerics.java b/checker/tests/interning/IterableGenerics.java index 8123cf74a7a..6b194e1c59e 100644 --- a/checker/tests/interning/IterableGenerics.java +++ b/checker/tests/interning/IterableGenerics.java @@ -1,11 +1,11 @@ public class IterableGenerics { - interface Data extends Iterable {} + interface Data extends Iterable {} - void typeParam(T t) { - for (String s : t) {} - } + void typeParam(T t) { + for (String s : t) {} + } - void wildcard(Iterable t) { - for (Object a : t.iterator().next()) {} - } + void wildcard(Iterable t) { + for (Object a : t.iterator().next()) {} + } } diff --git a/checker/tests/interning/MapEntryLubError.java b/checker/tests/interning/MapEntryLubError.java index 557b20d8d67..e20d5701d57 100644 --- a/checker/tests/interning/MapEntryLubError.java +++ b/checker/tests/interning/MapEntryLubError.java @@ -1,8 +1,8 @@ import java.util.Map; public class MapEntryLubError { - public boolean lubError(Map.Entry ent) { - Object v; - return (v = ent.getValue()) == null; - } + public boolean lubError(Map.Entry ent) { + Object v; + return (v = ent.getValue()) == null; + } } diff --git a/checker/tests/interning/MethodInvocation.java b/checker/tests/interning/MethodInvocation.java index d5eb4e077a0..dcae2e1fb7b 100644 --- a/checker/tests/interning/MethodInvocation.java +++ b/checker/tests/interning/MethodInvocation.java @@ -1,53 +1,53 @@ import org.checkerframework.checker.interning.qual.*; public class MethodInvocation { - @Interned MethodInvocation interned; - MethodInvocation nonInterned; - - void nonInternedMethod() { - nonInternedMethod(); - // :: error: (method.invocation.invalid) - internedMethod(); // should emit error - - this.nonInternedMethod(); - // :: error: (method.invocation.invalid) - this.internedMethod(); // should emit error - - interned.nonInternedMethod(); - interned.internedMethod(); - - nonInterned.nonInternedMethod(); - // :: error: (method.invocation.invalid) - nonInterned.internedMethod(); // should emit error - } - - void internedMethod(@Interned MethodInvocation this) { - nonInternedMethod(); - internedMethod(); - - this.nonInternedMethod(); - this.internedMethod(); - - interned.nonInternedMethod(); - interned.internedMethod(); - - nonInterned.nonInternedMethod(); - // :: error: (method.invocation.invalid) - nonInterned.internedMethod(); // should emit error - } - - // Now, test method parameters - void internedCharacterParameter(@Interned Character a) {} - - // See https://github.com/typetools/checker-framework/issues/84 - void internedCharacterParametersClient() { - // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) - internedCharacterParameter('\u00E4'); // lowercase a with umlaut - // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) - internedCharacterParameter('a'); - // :: error: (argument.type.incompatible) - internedCharacterParameter(Character.valueOf('a')); - // :: error: (argument.type.incompatible) - internedCharacterParameter(Character.valueOf('a')); - } + @Interned MethodInvocation interned; + MethodInvocation nonInterned; + + void nonInternedMethod() { + nonInternedMethod(); + // :: error: (method.invocation.invalid) + internedMethod(); // should emit error + + this.nonInternedMethod(); + // :: error: (method.invocation.invalid) + this.internedMethod(); // should emit error + + interned.nonInternedMethod(); + interned.internedMethod(); + + nonInterned.nonInternedMethod(); + // :: error: (method.invocation.invalid) + nonInterned.internedMethod(); // should emit error + } + + void internedMethod(@Interned MethodInvocation this) { + nonInternedMethod(); + internedMethod(); + + this.nonInternedMethod(); + this.internedMethod(); + + interned.nonInternedMethod(); + interned.internedMethod(); + + nonInterned.nonInternedMethod(); + // :: error: (method.invocation.invalid) + nonInterned.internedMethod(); // should emit error + } + + // Now, test method parameters + void internedCharacterParameter(@Interned Character a) {} + + // See https://github.com/typetools/checker-framework/issues/84 + void internedCharacterParametersClient() { + // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) + internedCharacterParameter('\u00E4'); // lowercase a with umlaut + // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) + internedCharacterParameter('a'); + // :: error: (argument.type.incompatible) + internedCharacterParameter(Character.valueOf('a')); + // :: error: (argument.type.incompatible) + internedCharacterParameter(Character.valueOf('a')); + } } diff --git a/checker/tests/interning/NestedGenerics.java b/checker/tests/interning/NestedGenerics.java index 07ab9e8b979..1c186f46cce 100644 --- a/checker/tests/interning/NestedGenerics.java +++ b/checker/tests/interning/NestedGenerics.java @@ -1,14 +1,13 @@ -import org.checkerframework.checker.interning.qual.Interned; - import java.util.List; +import org.checkerframework.checker.interning.qual.Interned; public class NestedGenerics { - public void test() { - List> foo = bar(); - } + public void test() { + List> foo = bar(); + } - public List> bar() { - return null; - } + public List> bar() { + return null; + } } diff --git a/checker/tests/interning/Options.java b/checker/tests/interning/Options.java index e6b6c1c470d..0d0efa4a7e5 100644 --- a/checker/tests/interning/Options.java +++ b/checker/tests/interning/Options.java @@ -1,91 +1,90 @@ -import org.checkerframework.checker.interning.qual.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.interning.qual.*; // Test case lifted from plume.Options public class Options { - public void minimal(String s) { - String arg = ""; // interned here - @Interned String arg2 = arg; - arg += s; // no longer interned - // :: error: (assignment.type.incompatible) - arg2 = arg; - } - - public void minimal2(char c) { - String arg = ""; // interned here - @Interned String arg2 = arg; - arg += c; // no longer interned - // :: error: (assignment.type.incompatible) - arg2 = arg; - } + public void minimal(String s) { + String arg = ""; // interned here + @Interned String arg2 = arg; + arg += s; // no longer interned + // :: error: (assignment.type.incompatible) + arg2 = arg; + } - public String[] otherparse(String args) { + public void minimal2(char c) { + String arg = ""; // interned here + @Interned String arg2 = arg; + arg += c; // no longer interned + // :: error: (assignment.type.incompatible) + arg2 = arg; + } - // Split the args string on whitespace boundaries accounting for quoted strings. - args = args.trim(); - List arg_list = new ArrayList<>(); - String arg = ""; - char active_quote = 0; - // for (int ii = 0; ii < args.length(); ii++) { - char ch = args.charAt(0); - // arg = arg + ch; + public String[] otherparse(String args) { - // if ((ch == '\'') || (ch == '"')) { - arg += ch; - // } - // } - // :: error: (assignment.type.incompatible) - @Interned String arg2 = arg; + // Split the args string on whitespace boundaries accounting for quoted strings. + args = args.trim(); + List arg_list = new ArrayList<>(); + String arg = ""; + char active_quote = 0; + // for (int ii = 0; ii < args.length(); ii++) { + char ch = args.charAt(0); + // arg = arg + ch; - if (!arg.equals("")) { - arg_list.add(arg); - } + // if ((ch == '\'') || (ch == '"')) { + arg += ch; + // } + // } + // :: error: (assignment.type.incompatible) + @Interned String arg2 = arg; - String[] argsArray = arg_list.toArray(new String[arg_list.size()]); - return null; + if (!arg.equals("")) { + arg_list.add(arg); } - public String[] parse(String args) { + String[] argsArray = arg_list.toArray(new String[arg_list.size()]); + return null; + } - // Split the args string on whitespace boundaries accounting for quoted strings. - args = args.trim(); - List arg_list = new ArrayList<>(); - String arg = ""; - char active_quote = 0; - for (int ii = 0; ii < args.length(); ii++) { - char ch = args.charAt(ii); - if ((ch == '\'') || (ch == '"')) { - arg += ch; - ii++; - while ((ii < args.length()) && (args.charAt(ii) != ch)) { - arg += args.charAt(ii++); - } - arg += ch; - } else if (Character.isWhitespace(ch)) { - // System.out.printf ("adding argument '%s'%n", arg); - arg_list.add(arg); - arg = ""; - while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { - ii++; - } - if (ii < args.length()) { - ii--; - } - } else { // must be part of current argument - arg += ch; - } - } - // :: error: (assignment.type.incompatible) - @Interned String arg2 = arg; + public String[] parse(String args) { - if (!arg.equals("")) { - arg_list.add(arg); + // Split the args string on whitespace boundaries accounting for quoted strings. + args = args.trim(); + List arg_list = new ArrayList<>(); + String arg = ""; + char active_quote = 0; + for (int ii = 0; ii < args.length(); ii++) { + char ch = args.charAt(ii); + if ((ch == '\'') || (ch == '"')) { + arg += ch; + ii++; + while ((ii < args.length()) && (args.charAt(ii) != ch)) { + arg += args.charAt(ii++); + } + arg += ch; + } else if (Character.isWhitespace(ch)) { + // System.out.printf ("adding argument '%s'%n", arg); + arg_list.add(arg); + arg = ""; + while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { + ii++; } + if (ii < args.length()) { + ii--; + } + } else { // must be part of current argument + arg += ch; + } + } + // :: error: (assignment.type.incompatible) + @Interned String arg2 = arg; - String[] argsArray = arg_list.toArray(new String[arg_list.size()]); - return null; + if (!arg.equals("")) { + arg_list.add(arg); } + + String[] argsArray = arg_list.toArray(new String[arg_list.size()]); + return null; + } } diff --git a/checker/tests/interning/OverrideInterned.java b/checker/tests/interning/OverrideInterned.java index ac22ca4bf32..a3774c42541 100644 --- a/checker/tests/interning/OverrideInterned.java +++ b/checker/tests/interning/OverrideInterned.java @@ -2,61 +2,61 @@ public class OverrideInterned { - // This code is extracted from FreePastry + // This code is extracted from FreePastry - @Interned class NodeHandle {} + @Interned class NodeHandle {} - public interface TransportLayer { - public void sendMessage(IDENTIFIER i); - } + public interface TransportLayer { + public void sendMessage(IDENTIFIER i); + } - public class CommonAPITransportLayerImpl - implements TransportLayer { - public void sendMessage(IDENTIFIER i) {} - } + public class CommonAPITransportLayerImpl + implements TransportLayer { + public void sendMessage(IDENTIFIER i) {} + } - interface MessageReceipt { - public NodeHandle getHint(); - } + interface MessageReceipt { + public NodeHandle getHint(); + } - void useAnonymousClass() { - MessageReceipt ret = - new MessageReceipt() { - public NodeHandle getHint() { - return null; - } - }; - } + void useAnonymousClass() { + MessageReceipt ret = + new MessageReceipt() { + public NodeHandle getHint() { + return null; + } + }; + } + + // This code is from Daikon - // This code is from Daikon + public abstract class TwoSequenceString { + public abstract Object check_modified1(@Interned String @Interned [] v1); - public abstract class TwoSequenceString { - public abstract Object check_modified1(@Interned String @Interned [] v1); + public abstract Object check_modified2(String @Interned [] v1); + } - public abstract Object check_modified2(String @Interned [] v1); + /* Changing the array component type in the overriding method is illegal. */ + public class PairwiseStringEqualBad extends TwoSequenceString { + // TODOINVARR:: error: (override.param.invalid) + public Object check_modified1(String @Interned [] a1) { + return new Object(); } - /* Changing the array component type in the overriding method is illegal. */ - public class PairwiseStringEqualBad extends TwoSequenceString { - // TODOINVARR:: error: (override.param.invalid) - public Object check_modified1(String @Interned [] a1) { - return new Object(); - } - - // :: error: (override.param.invalid) - public Object check_modified2(@Interned String @Interned [] a1) { - return new Object(); - } + // :: error: (override.param.invalid) + public Object check_modified2(@Interned String @Interned [] a1) { + return new Object(); } + } - /* Changing the main reference type is allowed, if it is a supertype. */ - public class PairwiseStringEqualGood extends TwoSequenceString { - public Object check_modified1(@Interned String[] a1) { - return new Object(); - } + /* Changing the main reference type is allowed, if it is a supertype. */ + public class PairwiseStringEqualGood extends TwoSequenceString { + public Object check_modified1(@Interned String[] a1) { + return new Object(); + } - public Object check_modified2(String[] a1) { - return new Object(); - } + public Object check_modified2(String[] a1) { + return new Object(); } + } } diff --git a/checker/tests/interning/Polymorphism.java b/checker/tests/interning/Polymorphism.java index f55431a74ff..6cf53c43464 100644 --- a/checker/tests/interning/Polymorphism.java +++ b/checker/tests/interning/Polymorphism.java @@ -1,92 +1,90 @@ -import org.checkerframework.checker.interning.qual.*; - import java.lang.ref.WeakReference; import java.util.Date; import java.util.List; import java.util.Map; +import org.checkerframework.checker.interning.qual.*; public class Polymorphism { - // Test parameter - public @PolyInterned String identity(@PolyInterned String s) { - return s; - } - - void testParam() { - String notInterned = new String("not interned"); - @Interned String interned = "interned"; - - interned = identity(interned); - // :: error: (assignment.type.incompatible) - interned = identity(notInterned); // invalid - } - - // test as receiver - @PolyInterned Polymorphism getSelf(@PolyInterned Polymorphism this) { - return this; - } - - void testReceiver() { - Polymorphism notInterned = new Polymorphism(); - @Interned Polymorphism interned = null; - - interned = interned.getSelf(); - // :: error: (assignment.type.incompatible) - interned = notInterned.getSelf(); // invalid - } - - // Test assinging interned to PolyInterned - public @PolyInterned String always(@PolyInterned String s) { - if (s.equals("n")) { - // This code type-checkd when the hierarchy contained just @UnknownInterned and - // @Interned, but no longer does because of @InternedDistinct. - // :: error: (return.type.incompatible) - return "m"; - } else { - // :: error: (return.type.incompatible) - return new String("m"); // invalid - } - } - - public static @PolyInterned Object[] id(@PolyInterned Object[] a) { - return a; - } - - public static void idTest(@Interned Object @Interned [] seq) { - @Interned Object[] copy_uninterned = id(seq); - } - - private static Map< - List<@Interned String @Interned []>, - WeakReference<@Interned String @Interned []>> - internedStringSequenceAndIndices; - private static List<@Interned String @Interned []> sai; - private static WeakReference<@Interned String @Interned []> wr; - - public static void testArrayInGeneric() { - internedStringSequenceAndIndices.put(sai, wr); - } - - // check for a crash when using raw types - void processMap(Map map) {} - - void testRaw() { - Map m = null; - // TODO: RAW TYPES WILL EVENTUALLY REQUIRE THAT THERE BOUNDS BE EXACTLY THE QUALIFIER - // EXPECTED. - // :: warning: [unchecked] unchecked method invocation: method processMap in class - // Polymorphism is applied to given types :: warning: [unchecked] unchecked conversion - processMap(m); - } - - // test anonymous classes - private void testAnonymous() { - new Object() { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object o) { - return true; - } - }.equals(null); - - Date d = new Date() {}; + // Test parameter + public @PolyInterned String identity(@PolyInterned String s) { + return s; + } + + void testParam() { + String notInterned = new String("not interned"); + @Interned String interned = "interned"; + + interned = identity(interned); + // :: error: (assignment.type.incompatible) + interned = identity(notInterned); // invalid + } + + // test as receiver + @PolyInterned Polymorphism getSelf(@PolyInterned Polymorphism this) { + return this; + } + + void testReceiver() { + Polymorphism notInterned = new Polymorphism(); + @Interned Polymorphism interned = null; + + interned = interned.getSelf(); + // :: error: (assignment.type.incompatible) + interned = notInterned.getSelf(); // invalid + } + + // Test assinging interned to PolyInterned + public @PolyInterned String always(@PolyInterned String s) { + if (s.equals("n")) { + // This code type-checkd when the hierarchy contained just @UnknownInterned and + // @Interned, but no longer does because of @InternedDistinct. + // :: error: (return.type.incompatible) + return "m"; + } else { + // :: error: (return.type.incompatible) + return new String("m"); // invalid } + } + + public static @PolyInterned Object[] id(@PolyInterned Object[] a) { + return a; + } + + public static void idTest(@Interned Object @Interned [] seq) { + @Interned Object[] copy_uninterned = id(seq); + } + + private static Map< + List<@Interned String @Interned []>, WeakReference<@Interned String @Interned []>> + internedStringSequenceAndIndices; + private static List<@Interned String @Interned []> sai; + private static WeakReference<@Interned String @Interned []> wr; + + public static void testArrayInGeneric() { + internedStringSequenceAndIndices.put(sai, wr); + } + + // check for a crash when using raw types + void processMap(Map map) {} + + void testRaw() { + Map m = null; + // TODO: RAW TYPES WILL EVENTUALLY REQUIRE THAT THERE BOUNDS BE EXACTLY THE QUALIFIER + // EXPECTED. + // :: warning: [unchecked] unchecked method invocation: method processMap in class + // Polymorphism is applied to given types :: warning: [unchecked] unchecked conversion + processMap(m); + } + + // test anonymous classes + private void testAnonymous() { + new Object() { + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object o) { + return true; + } + }.equals(null); + + Date d = new Date() {}; + } } diff --git a/checker/tests/interning/PrimitivesInterning.java b/checker/tests/interning/PrimitivesInterning.java index 6f7ab30d1ed..683637c3410 100644 --- a/checker/tests/interning/PrimitivesInterning.java +++ b/checker/tests/interning/PrimitivesInterning.java @@ -1,124 +1,123 @@ -import org.checkerframework.checker.interning.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.*; public class PrimitivesInterning { - void test() { - int a = 3; - - if (a == 3) { - System.out.println("yes"); - } else { - System.out.println("no"); - } - - if (a != 2) { - System.out.println("yes"); - } else { - System.out.println("no"); - } - - String name = "Interning"; - if ((name.indexOf('[') == -1) && (name.indexOf('(') == -1)) { - System.out.println("has no open punctuation"); - } else { - System.out.println("has open punctuation"); - } - - Number n = Integer.valueOf(22); - boolean is_double = (n instanceof Double); // valid - - int index = 0; - index = Integer.decode("22"); // valid: auto-unboxing conversion - - // auto-unboxing conversion again - Map m = new HashMap<>(); - if (m.get("hello") == 22) { - System.out.println("hello maps to 22"); - } + void test() { + int a = 3; + + if (a == 3) { + System.out.println("yes"); + } else { + System.out.println("no"); } - public static int pow_fast(int base, int expt) throws ArithmeticException { - if (expt < 0) { - throw new ArithmeticException("Negative base passed to pow"); - } - - int this_square_pow = base; - int result = 1; - while (expt > 0) { - if ((expt & 1) != 0) { - result *= this_square_pow; - } - expt >>= 1; - this_square_pow *= this_square_pow; - } - return result; + if (a != 2) { + System.out.println("yes"); + } else { + System.out.println("no"); } - /** Return the greatest common divisor of the two arguments. */ - public static int gcd(int a, int b) { - - // Euclid's method - if (b == 0) { - return (Math.abs(a)); - } - a = Math.abs(a); - b = Math.abs(b); - while (b != 0) { - int tmp = b; - b = a % b; - a = tmp; - } - return a; + String name = "Interning"; + if ((name.indexOf('[') == -1) && (name.indexOf('(') == -1)) { + System.out.println("has no open punctuation"); + } else { + System.out.println("has open punctuation"); } - /** Return the greatest common divisor of the elements of int array a. */ - public static int gcd(int[] a) { - // Euclid's method - if (a.length == 0) { - return 0; - } - int result = a[0]; - for (int i = 1; i < a.length; i++) { - result = gcd(a[i], result); - if ((result == 1) || (result == 0)) { - return result; - } - } - return result; + Number n = Integer.valueOf(22); + boolean is_double = (n instanceof Double); // valid + + int index = 0; + index = Integer.decode("22"); // valid: auto-unboxing conversion + + // auto-unboxing conversion again + Map m = new HashMap<>(); + if (m.get("hello") == 22) { + System.out.println("hello maps to 22"); } + } - /** - * Return the gcd (greatest common divisor) of the differences between the elements of int array - * a. - */ - public static int gcd_differences(int[] a) { - // Euclid's method - if (a.length < 2) { - return 0; - } - int result = a[1] - a[0]; - for (int i = 2; i < a.length; i++) { - result = gcd(a[i] - a[i - 1], result); - if ((result == 1) || (result == 0)) { - return result; - } - } - return result; + public static int pow_fast(int base, int expt) throws ArithmeticException { + if (expt < 0) { + throw new ArithmeticException("Negative base passed to pow"); } - void compounds() { - int res = 0; - res += 5; - res /= 9; + int this_square_pow = base; + int result = 1; + while (expt > 0) { + if ((expt & 1) != 0) { + result *= this_square_pow; + } + expt >>= 1; + this_square_pow *= this_square_pow; } + return result; + } - // TODO: enable after boxing is improved in AST creation - // void negation() { - // Boolean t = new Boolean(true); - // boolean b = !t; - // } + /** Return the greatest common divisor of the two arguments. */ + public static int gcd(int a, int b) { + + // Euclid's method + if (b == 0) { + return (Math.abs(a)); + } + a = Math.abs(a); + b = Math.abs(b); + while (b != 0) { + int tmp = b; + b = a % b; + a = tmp; + } + return a; + } + + /** Return the greatest common divisor of the elements of int array a. */ + public static int gcd(int[] a) { + // Euclid's method + if (a.length == 0) { + return 0; + } + int result = a[0]; + for (int i = 1; i < a.length; i++) { + result = gcd(a[i], result); + if ((result == 1) || (result == 0)) { + return result; + } + } + return result; + } + + /** + * Return the gcd (greatest common divisor) of the differences between the elements of int array + * a. + */ + public static int gcd_differences(int[] a) { + // Euclid's method + if (a.length < 2) { + return 0; + } + int result = a[1] - a[0]; + for (int i = 2; i < a.length; i++) { + result = gcd(a[i] - a[i - 1], result); + if ((result == 1) || (result == 0)) { + return result; + } + } + return result; + } + + void compounds() { + int res = 0; + res += 5; + res /= 9; + } + + // TODO: enable after boxing is improved in AST creation + // void negation() { + // Boolean t = new Boolean(true); + // boolean b = !t; + // } } diff --git a/checker/tests/interning/Raw3.java b/checker/tests/interning/Raw3.java index 509bd3e295e..ac3850f65e4 100644 --- a/checker/tests/interning/Raw3.java +++ b/checker/tests/interning/Raw3.java @@ -1,7 +1,6 @@ -import org.checkerframework.checker.interning.qual.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.interning.qual.*; /* * TODO: Make diamond cleverer: @@ -12,75 +11,75 @@ */ public class Raw3 { - // We would like behavior that is as similar as possible between the - // versions with no raw types and those with raw types. + // We would like behavior that is as similar as possible between the + // versions with no raw types and those with raw types. - // no raw types - List foo1() { - List sl = new ArrayList<>(); - return (List) sl; - } + // no raw types + List foo1() { + List sl = new ArrayList<>(); + return (List) sl; + } - // with raw types - List foo2() { - List sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } + // with raw types + List foo2() { + List sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } - // no raw types - List foo3() { - List<@Interned String> sl = new ArrayList<>(); - // :: error: (return.type.incompatible) - return (List<@Interned String>) sl; - } + // no raw types + List foo3() { + List<@Interned String> sl = new ArrayList<>(); + // :: error: (return.type.incompatible) + return (List<@Interned String>) sl; + } - // with raw types - List foo4() { - List<@Interned String> sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } + // with raw types + List foo4() { + List<@Interned String> sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } - // no raw types - List<@Interned String> foo5() { - List sl = new ArrayList<>(); - // :: error: (return.type.incompatible) - return (List) sl; - } + // no raw types + List<@Interned String> foo5() { + List sl = new ArrayList<>(); + // :: error: (return.type.incompatible) + return (List) sl; + } - // with raw types - List<@Interned String> foo6() { - List sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } + // with raw types + List<@Interned String> foo6() { + List sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } - class TestList { - List bar1() { - List sl = new ArrayList<>(); - return (List) sl; - } + class TestList { + List bar1() { + List sl = new ArrayList<>(); + return (List) sl; + } - List bar2() { - List sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } + List bar2() { + List sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } - List bar3(List sl) { - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } + List bar3(List sl) { + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } - class DuoList extends ArrayList {} + class DuoList extends ArrayList {} - List bar4(List sl) { - // This line was previously failing because we couldn't adequately infer the type of - // DuoList as a List; it works now, though the future checking of rawtypes may be more - // strict. - // :: warning: [unchecked] unchecked conversion - return (DuoList) sl; - } + List bar4(List sl) { + // This line was previously failing because we couldn't adequately infer the type of + // DuoList as a List; it works now, though the future checking of rawtypes may be more + // strict. + // :: warning: [unchecked] unchecked conversion + return (DuoList) sl; } + } } diff --git a/checker/tests/interning/RecursiveClass.java b/checker/tests/interning/RecursiveClass.java index 6bd2974c418..b100994bbe8 100644 --- a/checker/tests/interning/RecursiveClass.java +++ b/checker/tests/interning/RecursiveClass.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.interning.qual.Interned; public @Interned class RecursiveClass< - T extends RecursiveClass, F extends RecursiveClass> { - static @Interned class InternedClass {} + T extends RecursiveClass, F extends RecursiveClass> { + static @Interned class InternedClass {} - static class Generic {} + static class Generic {} - static @Interned class RecursiveClass2> {} + static @Interned class RecursiveClass2> {} } diff --git a/checker/tests/interning/SequenceAndIndices.java b/checker/tests/interning/SequenceAndIndices.java index 5a3838be660..e5c4e3b39d4 100644 --- a/checker/tests/interning/SequenceAndIndices.java +++ b/checker/tests/interning/SequenceAndIndices.java @@ -7,47 +7,47 @@ * subsequences on the same sequence. */ public final class SequenceAndIndices { - public T seq; - public int start; - public int end; + public T seq; + public int start; + public int end; - /** - * Create a SequenceAndIndices. - * - * @param seqpar an interned array - */ - public SequenceAndIndices(T seqpar, int start, int end) { - this.seq = seqpar; - this.start = start; - this.end = end; - // assert isInterned(seq); - } + /** + * Create a SequenceAndIndices. + * + * @param seqpar an interned array + */ + public SequenceAndIndices(T seqpar, int start, int end) { + this.seq = seqpar; + this.start = start; + this.end = end; + // assert isInterned(seq); + } - @SuppressWarnings("unchecked") - @Pure - public boolean equals(Object other) { - if (other instanceof SequenceAndIndices) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - return equals((SequenceAndIndices) other); // unchecked - } else { - return false; - } + @SuppressWarnings("unchecked") + @Pure + public boolean equals(Object other) { + if (other instanceof SequenceAndIndices) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + return equals((SequenceAndIndices) other); // unchecked + } else { + return false; } + } - public boolean equals(SequenceAndIndices other) { - return (this.seq == other.seq) && this.start == other.start && this.end == other.end; - } + public boolean equals(SequenceAndIndices other) { + return (this.seq == other.seq) && this.start == other.start && this.end == other.end; + } - @Pure - public int hashCode() { - return seq.hashCode() + start * 30 - end * 2; - } + @Pure + public int hashCode() { + return seq.hashCode() + start * 30 - end * 2; + } - // For debugging - @Pure - public String toString() { - // return "SAI(" + start + "," + end + ") from: " + ArraysMDE.toString(seq); - return "SAI(" + start + "," + end + ") from: " + seq; - } + // For debugging + @Pure + public String toString() { + // return "SAI(" + start + "," + end + ") from: " + ArraysMDE.toString(seq); + return "SAI(" + start + "," + end + ") from: " + seq; + } } diff --git a/checker/tests/interning/StaticInternMethod.java b/checker/tests/interning/StaticInternMethod.java index 07efcc538f6..a270256f7ec 100644 --- a/checker/tests/interning/StaticInternMethod.java +++ b/checker/tests/interning/StaticInternMethod.java @@ -1,30 +1,29 @@ -import org.checkerframework.checker.interning.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.*; public class StaticInternMethod { - private static Map pool = new HashMap<>(); - - @SuppressWarnings("interning") - public static @Interned Foo intern(Integer i) { - if (pool.containsKey(i)) { - return pool.get(i); - } + private static Map pool = new HashMap<>(); - @Interned Foo f = new @Interned Foo(i); - pool.put(i, f); - return f; + @SuppressWarnings("interning") + public static @Interned Foo intern(Integer i) { + if (pool.containsKey(i)) { + return pool.get(i); } - static class Foo { - public Foo(Integer i) {} - } + @Interned Foo f = new @Interned Foo(i); + pool.put(i, f); + return f; + } - void test() { - Integer i = 0; - Foo f = new Foo(i); - @Interned Foo g = intern(i); - } + static class Foo { + public Foo(Integer i) {} + } + + void test() { + Integer i = 0; + Foo f = new Foo(i); + @Interned Foo g = intern(i); + } } diff --git a/checker/tests/interning/StringIntern.java b/checker/tests/interning/StringIntern.java index 0085d3f0d15..e5e840fd33b 100644 --- a/checker/tests/interning/StringIntern.java +++ b/checker/tests/interning/StringIntern.java @@ -1,66 +1,65 @@ -import org.checkerframework.checker.interning.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.*; public class StringIntern { - // It would be very handy (and would eliminate quite a few annotations) - // if any final variable that is initialized to something interned - // (essentially, to a literal) were treated as implicitly @Interned. - final String finalStringInitializedToInterned = "foo"; // implicitly @Interned - final String finalString2 = new String("foo"); - static final String finalStringStatic1 = "foo"; // implicitly @Interned - static final String finalStringStatic2 = new String("foo"); + // It would be very handy (and would eliminate quite a few annotations) + // if any final variable that is initialized to something interned + // (essentially, to a literal) were treated as implicitly @Interned. + final String finalStringInitializedToInterned = "foo"; // implicitly @Interned + final String finalString2 = new String("foo"); + static final String finalStringStatic1 = "foo"; // implicitly @Interned + static final String finalStringStatic2 = new String("foo"); - static class HasFields { - static final String finalStringStatic3 = "foo"; // implicitly @Interned - static final String finalStringStatic4 = new String("foo"); - } + static class HasFields { + static final String finalStringStatic3 = "foo"; // implicitly @Interned + static final String finalStringStatic4 = new String("foo"); + } - static class Foo { - private static Map pool = new HashMap<>(); + static class Foo { + private static Map pool = new HashMap<>(); - @SuppressWarnings("interning") - public @Interned Foo intern() { - if (!pool.containsKey(this)) { - pool.put(this, (@Interned Foo) this); - } - return pool.get(this); - } + @SuppressWarnings("interning") + public @Interned Foo intern() { + if (!pool.containsKey(this)) { + pool.put(this, (@Interned Foo) this); + } + return pool.get(this); } + } - // Another example of the "final initialized to interned" rule - final Foo finalFooInitializedToInterned = new Foo().intern(); + // Another example of the "final initialized to interned" rule + final Foo finalFooInitializedToInterned = new Foo().intern(); - public void test(@Interned String arg) { - String notInternedStr = new String("foo"); - @Interned String internedStr = notInternedStr.intern(); - internedStr = finalStringInitializedToInterned; // OK - // :: error: (assignment.type.incompatible) - internedStr = finalString2; // error - // :: error: (assignment.type.incompatible) - @Interned Foo internedFoo = finalFooInitializedToInterned; - if (arg == finalStringStatic1) {} // OK - // :: error: (not.interned) - if (arg == finalStringStatic2) {} // error - if (arg == HasFields.finalStringStatic3) {} // OK - // :: error: (not.interned) - if (arg == HasFields.finalStringStatic4) {} // error - } + public void test(@Interned String arg) { + String notInternedStr = new String("foo"); + @Interned String internedStr = notInternedStr.intern(); + internedStr = finalStringInitializedToInterned; // OK + // :: error: (assignment.type.incompatible) + internedStr = finalString2; // error + // :: error: (assignment.type.incompatible) + @Interned Foo internedFoo = finalFooInitializedToInterned; + if (arg == finalStringStatic1) {} // OK + // :: error: (not.interned) + if (arg == finalStringStatic2) {} // error + if (arg == HasFields.finalStringStatic3) {} // OK + // :: error: (not.interned) + if (arg == HasFields.finalStringStatic4) {} // error + } - private @Interned String base; - static final String BASE_HASHCODE = "hashcode"; + private @Interned String base; + static final String BASE_HASHCODE = "hashcode"; - public void foo() { - if (base == BASE_HASHCODE) {} - } + public void foo() { + if (base == BASE_HASHCODE) {} + } - public @Interned String emptyString(boolean b) { - if (b) { - return ""; - } else { - return (""); - } + public @Interned String emptyString(boolean b) { + if (b) { + return ""; + } else { + return (""); } + } } diff --git a/checker/tests/interning/Subclass.java b/checker/tests/interning/Subclass.java index 12435c3e717..8fe2a85c4aa 100644 --- a/checker/tests/interning/Subclass.java +++ b/checker/tests/interning/Subclass.java @@ -4,8 +4,8 @@ public abstract class Subclass implements Comparable // note non-generic { - @Pure - public int compareTo(Subclass other) { - return 0; - } + @Pure + public int compareTo(Subclass other) { + return 0; + } } diff --git a/checker/tests/interning/SuppressWarningsClass.java b/checker/tests/interning/SuppressWarningsClass.java index d736aef4d79..30a788dda69 100644 --- a/checker/tests/interning/SuppressWarningsClass.java +++ b/checker/tests/interning/SuppressWarningsClass.java @@ -3,8 +3,8 @@ @SuppressWarnings("interning") public class SuppressWarningsClass { - public static void myMethod() { + public static void myMethod() { - @Interned String s = new String(); - } + @Interned String s = new String(); + } } diff --git a/checker/tests/interning/SuppressWarningsVar.java b/checker/tests/interning/SuppressWarningsVar.java index d26ba8f4ffb..7aa4b6c7965 100644 --- a/checker/tests/interning/SuppressWarningsVar.java +++ b/checker/tests/interning/SuppressWarningsVar.java @@ -2,9 +2,9 @@ public class SuppressWarningsVar { - public static void myMethod() { + public static void myMethod() { - @SuppressWarnings("interning") - @Interned String s = new String(); - } + @SuppressWarnings("interning") + @Interned String s = new String(); + } } diff --git a/checker/tests/interning/TVWCSuper.java b/checker/tests/interning/TVWCSuper.java index 896bf0e9983..d3a6415a10a 100644 --- a/checker/tests/interning/TVWCSuper.java +++ b/checker/tests/interning/TVWCSuper.java @@ -1,5 +1,5 @@ public class TVWCSuper { - class L {} + class L {} - public static > void sort(L t) {} + public static > void sort(L t) {} } diff --git a/checker/tests/interning/TestExtSup.java b/checker/tests/interning/TestExtSup.java index 85e20d74829..c05555d7d75 100644 --- a/checker/tests/interning/TestExtSup.java +++ b/checker/tests/interning/TestExtSup.java @@ -3,14 +3,14 @@ import java.util.List; interface A { - public abstract int transform(List function); + public abstract int transform(List function); } class B implements A { - @Override - public int transform(List function) { - return 0; - } + @Override + public int transform(List function) { + return 0; + } } public class TestExtSup {} diff --git a/checker/tests/interning/TestInfer.java b/checker/tests/interning/TestInfer.java index 16d5efed497..e08103d0e1f 100644 --- a/checker/tests/interning/TestInfer.java +++ b/checker/tests/interning/TestInfer.java @@ -4,27 +4,27 @@ import java.util.List; class TestInfer1 { - T getValue(List l) { - return l.get(0); - } + T getValue(List l) { + return l.get(0); + } - void bar(Object o) {} + void bar(Object o) {} - void foo() { - List ls = new ArrayList<>(); - bar(getValue(ls)); - } + void foo() { + List ls = new ArrayList<>(); + bar(getValue(ls)); + } } class TestInfer2 { - T getValue(List l) { - return l.get(0); - } + T getValue(List l) { + return l.get(0); + } - void bar(String o) {} + void bar(String o) {} - void foo() { - List ls = new ArrayList<>(); - bar(getValue(ls)); - } + void foo() { + List ls = new ArrayList<>(); + bar(getValue(ls)); + } } diff --git a/checker/tests/interning/ThreadUsesObjectEquals.java b/checker/tests/interning/ThreadUsesObjectEquals.java index e494d1fd364..ee96770e620 100644 --- a/checker/tests/interning/ThreadUsesObjectEquals.java +++ b/checker/tests/interning/ThreadUsesObjectEquals.java @@ -1,5 +1,5 @@ public class ThreadUsesObjectEquals { - boolean p(Thread a, Thread b) { - return a == b; - } + boolean p(Thread a, Thread b) { + return a == b; + } } diff --git a/checker/tests/interning/TypeVarPrimitivesInterning.java b/checker/tests/interning/TypeVarPrimitivesInterning.java index 1c43fc52bec..497fbbdf5ff 100644 --- a/checker/tests/interning/TypeVarPrimitivesInterning.java +++ b/checker/tests/interning/TypeVarPrimitivesInterning.java @@ -3,20 +3,19 @@ import org.checkerframework.checker.interning.qual.*; public class TypeVarPrimitivesInterning { - void method(T tLong) { - long l = tLong; - } + void method(T tLong) { + long l = tLong; + } - void methodIntersection( - T tLong) { - long l = tLong; - } + void methodIntersection(T tLong) { + long l = tLong; + } - void method2(T tLong) { - long l = tLong; - } + void method2(T tLong) { + long l = tLong; + } - void methodIntersection2(T tLong) { - long l = tLong; - } + void methodIntersection2(T tLong) { + long l = tLong; + } } diff --git a/checker/tests/interning/UnboxUninterned.java b/checker/tests/interning/UnboxUninterned.java index 324f46bd8fd..46490d01a7a 100644 --- a/checker/tests/interning/UnboxUninterned.java +++ b/checker/tests/interning/UnboxUninterned.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.interning.qual.*; public class UnboxUninterned { - void negation() { - Boolean t = Boolean.valueOf(true); - boolean b1 = !t.booleanValue(); - boolean b2 = !t; + void negation() { + Boolean t = Boolean.valueOf(true); + boolean b1 = !t.booleanValue(); + boolean b2 = !t; - Integer x = Integer.valueOf(222222); - int i1 = -x.intValue(); - int i2 = -x; - } + Integer x = Integer.valueOf(222222); + int i1 = -x.intValue(); + int i2 = -x; + } } diff --git a/checker/tests/interning/UsesObjectEqualsTest.java b/checker/tests/interning/UsesObjectEqualsTest.java index d878062678b..8a713ad9ce4 100644 --- a/checker/tests/interning/UsesObjectEqualsTest.java +++ b/checker/tests/interning/UsesObjectEqualsTest.java @@ -1,95 +1,94 @@ -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.interning.qual.UsesObjectEquals; - import java.util.LinkedList; import java.util.prefs.*; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.interning.qual.UsesObjectEquals; public class UsesObjectEqualsTest { - public @UsesObjectEquals class A { - public A() {} - } + public @UsesObjectEquals class A { + public A() {} + } - @UsesObjectEquals - class B extends A {} + @UsesObjectEquals + class B extends A {} - // :: error: (overrides.equals) - class B2 extends A { - @Override - public boolean equals(Object o) { - return super.equals(o); - } + // :: error: (overrides.equals) + class B2 extends A { + @Override + public boolean equals(Object o) { + return super.equals(o); } + } - @UsesObjectEquals - class B3 extends A { - @Override - public boolean equals(Object o3) { - return this == o3; - } + @UsesObjectEquals + class B3 extends A { + @Override + public boolean equals(Object o3) { + return this == o3; } + } - @UsesObjectEquals - class B4 extends A { - @Override - public boolean equals(Object o4) { - return o4 == this; - } + @UsesObjectEquals + class B4 extends A { + @Override + public boolean equals(Object o4) { + return o4 == this; } + } - // changed to inherited, no (superclass.annotated) warning - class C extends A {} + // changed to inherited, no (superclass.annotated) warning + class C extends A {} - class D {} + class D {} - @UsesObjectEquals - // :: error: (superclass.notannotated) - class E extends D {} + @UsesObjectEquals + // :: error: (superclass.notannotated) + class E extends D {} - @UsesObjectEquals - // :: error: (overrides.equals) - class TestEquals { + @UsesObjectEquals + // :: error: (overrides.equals) + class TestEquals { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object o) { - return true; - } + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object o) { + return true; } - - class TestComparison { - - public void comp(@Interned Object o, A a1, A a2) { - if (a1 == a2) { - System.out.println("one"); - } - if (a1 == o) { - System.out.println("two"); - } - if (o == a1) { - System.out.println("three"); - } - } + } + + class TestComparison { + + public void comp(@Interned Object o, A a1, A a2) { + if (a1 == a2) { + System.out.println("one"); + } + if (a1 == o) { + System.out.println("two"); + } + if (o == a1) { + System.out.println("three"); + } } + } - @UsesObjectEquals - class ExtendsInner1 extends UsesObjectEqualsTest.A {} + @UsesObjectEquals + class ExtendsInner1 extends UsesObjectEqualsTest.A {} - class ExtendsInner2 extends UsesObjectEqualsTest.A {} + class ExtendsInner2 extends UsesObjectEqualsTest.A {} - class MyList extends LinkedList {} + class MyList extends LinkedList {} - class DoesNotUseObjectEquals { - @Override - public boolean equals(Object o) { - return super.equals(o); - } + class DoesNotUseObjectEquals { + @Override + public boolean equals(Object o) { + return super.equals(o); } + } - @UsesObjectEquals - class SubclassUsesObjectEquals extends DoesNotUseObjectEquals { - @Override - public boolean equals(Object o) { - return this == o; - } + @UsesObjectEquals + class SubclassUsesObjectEquals extends DoesNotUseObjectEquals { + @Override + public boolean equals(Object o) { + return this == o; } + } } diff --git a/checker/tests/lock-records/LockRecord.java b/checker/tests/lock-records/LockRecord.java index 86cbe1cbedf..c9f179e6bfa 100644 --- a/checker/tests/lock-records/LockRecord.java +++ b/checker/tests/lock-records/LockRecord.java @@ -1,12 +1,11 @@ -import org.checkerframework.checker.lock.qual.LockingFree; - import java.util.concurrent.locks.ReentrantLock; +import org.checkerframework.checker.lock.qual.LockingFree; public record LockRecord(String s, ReentrantLock lock) { - @LockingFree + @LockingFree + // :: error: (method.guarantee.violated) + public LockRecord { // :: error: (method.guarantee.violated) - public LockRecord { - // :: error: (method.guarantee.violated) - lock.lock(); - } + lock.lock(); + } } diff --git a/checker/tests/lock-safedefaults/BasicLockTest.java b/checker/tests/lock-safedefaults/BasicLockTest.java index 72816357286..b1f33fda921 100644 --- a/checker/tests/lock-safedefaults/BasicLockTest.java +++ b/checker/tests/lock-safedefaults/BasicLockTest.java @@ -1,104 +1,103 @@ +import java.util.concurrent.locks.*; import org.checkerframework.checker.lock.qual.*; import org.checkerframework.framework.qual.AnnotatedFor; -import java.util.concurrent.locks.*; - public class BasicLockTest { - class MyClass { - public Object field; - } - - Object someValue = new Object(); - - MyClass newMyClass = new MyClass(); - - MyClass myUnannotatedMethod(MyClass param) { - return param; - } - - void myUnannotatedMethod2() {} - - @AnnotatedFor("lock") - MyClass myAnnotatedMethod(MyClass param) { - return param; - } - - @AnnotatedFor("lock") - void myAnnotatedMethod2() {} - - final @GuardedBy({}) ReentrantLock lockField = new ReentrantLock(); - - @GuardedBy("lockField") MyClass m; - - @GuardedBy({}) MyClass o1 = new MyClass(), p1; - - @AnnotatedFor("lock") - @MayReleaseLocks - void testFields() { - // Test in two ways that return values are @GuardedByUnknown. - // The first way is more durable as cannot.dereference is tied specifically to - // @GuardedByUnknown (and @GuardedByBottom, but it is unlikely to become the default for - // return values on unannotated methods). - // :: error: (lock.not.held) :: error: (argument.type.incompatible) - myUnannotatedMethod(o1).field = someValue; - // The second way is less durable because the default for fields is currently @GuardedBy({}) - // but could be changed to @GuardedByUnknown. - // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) - p1 = myUnannotatedMethod(o1); - - // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks - lockField.lock(); - myAnnotatedMethod2(); - m.field = someValue; - myUnannotatedMethod2(); - // :: error: (lock.not.held) - m.field = someValue; - } - - void unannotatedReleaseLock(ReentrantLock lock) { - lock.unlock(); - } - - @AnnotatedFor("lock") - @MayReleaseLocks - void testLocalVariables1() { - MyClass o2 = new MyClass(), p2; - // :: error: (argument.type.incompatible) - p2 = myUnannotatedMethod(o2); - MyClass o3 = new MyClass(); - myAnnotatedMethod(o3); - } - - @AnnotatedFor("lock") - @MayReleaseLocks - void testLocalVariables2() { - // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks - final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); - @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement - @GuardedBy("lock") MyClass q = newMyClass; - lock.lock(); - myAnnotatedMethod2(); - q.field = someValue; - // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local - // variable lock. - myUnannotatedMethod2(); - // :: error: (lock.not.held) - q.field = someValue; - } - - @AnnotatedFor("lock") - @MayReleaseLocks - void testLocalVariables3() { - // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks - final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); - @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement - @GuardedBy("lock") MyClass q = newMyClass; - lock.lock(); - // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local - // variable lock. - // :: error: (argument.type.incompatible) - unannotatedReleaseLock(lock); - // :: error: (lock.not.held) - q.field = someValue; - } + class MyClass { + public Object field; + } + + Object someValue = new Object(); + + MyClass newMyClass = new MyClass(); + + MyClass myUnannotatedMethod(MyClass param) { + return param; + } + + void myUnannotatedMethod2() {} + + @AnnotatedFor("lock") + MyClass myAnnotatedMethod(MyClass param) { + return param; + } + + @AnnotatedFor("lock") + void myAnnotatedMethod2() {} + + final @GuardedBy({}) ReentrantLock lockField = new ReentrantLock(); + + @GuardedBy("lockField") MyClass m; + + @GuardedBy({}) MyClass o1 = new MyClass(), p1; + + @AnnotatedFor("lock") + @MayReleaseLocks + void testFields() { + // Test in two ways that return values are @GuardedByUnknown. + // The first way is more durable as cannot.dereference is tied specifically to + // @GuardedByUnknown (and @GuardedByBottom, but it is unlikely to become the default for + // return values on unannotated methods). + // :: error: (lock.not.held) :: error: (argument.type.incompatible) + myUnannotatedMethod(o1).field = someValue; + // The second way is less durable because the default for fields is currently @GuardedBy({}) + // but could be changed to @GuardedByUnknown. + // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) + p1 = myUnannotatedMethod(o1); + + // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks + lockField.lock(); + myAnnotatedMethod2(); + m.field = someValue; + myUnannotatedMethod2(); + // :: error: (lock.not.held) + m.field = someValue; + } + + void unannotatedReleaseLock(ReentrantLock lock) { + lock.unlock(); + } + + @AnnotatedFor("lock") + @MayReleaseLocks + void testLocalVariables1() { + MyClass o2 = new MyClass(), p2; + // :: error: (argument.type.incompatible) + p2 = myUnannotatedMethod(o2); + MyClass o3 = new MyClass(); + myAnnotatedMethod(o3); + } + + @AnnotatedFor("lock") + @MayReleaseLocks + void testLocalVariables2() { + // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks + final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); + @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement + @GuardedBy("lock") MyClass q = newMyClass; + lock.lock(); + myAnnotatedMethod2(); + q.field = someValue; + // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local + // variable lock. + myUnannotatedMethod2(); + // :: error: (lock.not.held) + q.field = someValue; + } + + @AnnotatedFor("lock") + @MayReleaseLocks + void testLocalVariables3() { + // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks + final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); + @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement + @GuardedBy("lock") MyClass q = newMyClass; + lock.lock(); + // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local + // variable lock. + // :: error: (argument.type.incompatible) + unannotatedReleaseLock(lock); + // :: error: (lock.not.held) + q.field = someValue; + } } diff --git a/checker/tests/lock/ChapterExamples.java b/checker/tests/lock/ChapterExamples.java index f06c1797da6..e4eadc52d10 100644 --- a/checker/tests/lock/ChapterExamples.java +++ b/checker/tests/lock/ChapterExamples.java @@ -1,6 +1,12 @@ // This test contains the sample code from the Lock Checker manual chapter modified to fit testing // instead of illustrative purposes, and contains other miscellaneous Lock Checker testing. +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByBottom; @@ -11,586 +17,579 @@ import org.checkerframework.checker.lock.qual.ReleasesNoLocks; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.locks.ReentrantLock; - public class ChapterExamples { - // This code crashed when there was a bug before issue 524 was fixed. - // An attempt to take the LUB between 'val' in the store with type 'long' - // and 'val' in another store with type 'none' resulted in a crash. - private void foo(boolean b, int a) { - if (b) { - if (a == 0) { - boolean val = false; - } else if (a == 1) { - int val = 0; - } else if (a == 2) { - long val = 0; - } else if (a == 3) { - } - } else { - if (true) {} - } - } - - private abstract class Values extends AbstractCollection { - @SuppressWarnings("method.guarantee.violated") // side effect is only to local iterator - public T[] toArray(T[] a) { - Collection c = new ArrayList(size()); - for (Iterator i = iterator(); i.hasNext(); ) { - c.add(i.next()); - } - return c.toArray(a); - } - } - - // @GuardedByBottom, which represents the 'null' literal, is the default lower bound, - // so null can be returned in the following two methods: - T method1(T t, boolean b) { - return b ? null : t; - } - - T method2(T t, boolean b) { - return null; - } - - void bar(@NonNull Object nn1, boolean b) { - @NonNull Object nn2 = method1(nn1, b); - @NonNull Object nn3 = method2(nn1, b); - } - - void bar2(@GuardedByBottom Object bottomParam, boolean b) { - @GuardedByUnknown Object refinedToBottom1 = method1(bottomParam, b); - @GuardedByUnknown Object refinedToBottom2 = method2(bottomParam, b); - @GuardedByBottom Object bottom1 = method1(bottomParam, b); - @GuardedByBottom Object bottom2 = method2(bottomParam, b); + // This code crashed when there was a bug before issue 524 was fixed. + // An attempt to take the LUB between 'val' in the store with type 'long' + // and 'val' in another store with type 'none' resulted in a crash. + private void foo(boolean b, int a) { + if (b) { + if (a == 0) { + boolean val = false; + } else if (a == 1) { + int val = 0; + } else if (a == 2) { + long val = 0; + } else if (a == 3) { + } + } else { + if (true) {} } + } - private static boolean eq(@GuardSatisfied Object o1, @GuardSatisfied Object o2) { - return (o1 == null ? o2 == null : o1.equals(o2)); + private abstract class Values extends AbstractCollection { + @SuppressWarnings("method.guarantee.violated") // side effect is only to local iterator + public T[] toArray(T[] a) { + Collection c = new ArrayList(size()); + for (Iterator i = iterator(); i.hasNext(); ) { + c.add(i.next()); + } + return c.toArray(a); } + } - public void put( - K key, V value) { - @SuppressWarnings("unchecked") - K k = (K) maskNull(key); - } + // @GuardedByBottom, which represents the 'null' literal, is the default lower bound, + // so null can be returned in the following two methods: + T method1(T t, boolean b) { + return b ? null : t; + } - class GuardedByUnknownTest { + T method2(T t, boolean b) { + return null; + } - T m; + void bar(@NonNull Object nn1, boolean b) { + @NonNull Object nn2 = method1(nn1, b); + @NonNull Object nn3 = method2(nn1, b); + } - void test() { - // :: error: (method.invocation.invalid) - m.method(); + void bar2(@GuardedByBottom Object bottomParam, boolean b) { + @GuardedByUnknown Object refinedToBottom1 = method1(bottomParam, b); + @GuardedByUnknown Object refinedToBottom2 = method2(bottomParam, b); + @GuardedByBottom Object bottom1 = method1(bottomParam, b); + @GuardedByBottom Object bottom2 = method2(bottomParam, b); + } - @GuardedByUnknown MyClass local = new @GuardedByUnknown MyClass(); - // :: error: (lock.not.held) - local.field = new Object(); - // :: error: (method.invocation.invalid) - local.method(); + private static boolean eq(@GuardSatisfied Object o1, @GuardSatisfied Object o2) { + return (o1 == null ? o2 == null : o1.equals(o2)); + } - // :: error: (lock.not.held) - m.field = new Object(); - } - } + public void put( + K key, V value) { + @SuppressWarnings("unchecked") + K k = (K) maskNull(key); + } - class MyClass { - Object field = new Object(); + class GuardedByUnknownTest { - @LockingFree - Object method(@GuardSatisfied MyClass this) { - return new Object(); - } + T m; - @LockingFree - public @GuardSatisfied(1) MyClass append( - @GuardSatisfied(1) MyClass this, @GuardSatisfied(2) MyClass m) { - return this; - } + void test() { + // :: error: (method.invocation.invalid) + m.method(); - final Object myLock = new Object(); + @GuardedByUnknown MyClass local = new @GuardedByUnknown MyClass(); + // :: error: (lock.not.held) + local.field = new Object(); + // :: error: (method.invocation.invalid) + local.method(); - void testCallToMethod(@GuardedBy("myLock") MyClass this) { - // :: error: (lock.not.held) - this.method(); // method()'s receiver is annotated as @GuardSatisfied - } + // :: error: (lock.not.held) + m.field = new Object(); } + } - @MayReleaseLocks - @ReleasesNoLocks - // TODO: enable (multiple.sideeffect.annotation) - void testMultipleSideEffectAnnotations() {} - - void guardedByItselfOnReceiver(@GuardedBy("") ChapterExamples this) { - synchronized (this) { // Tests translation of '' to 'this' - // myField = new MyClass(); - myField.toString(); - this.myField = new MyClass(); - this.myField.toString(); - } - // :: error: (lock.not.held) - myField = new MyClass(); - // :: error: (lock.not.held) - myField.toString(); - // :: error: (lock.not.held) - this.myField = new MyClass(); - // :: error: (lock.not.held) - this.myField.toString(); - } - - void guardedByThisOnReceiver(@GuardedBy("this") ChapterExamples this) { - // :: error: (lock.not.held) - myField = new MyClass(); - // :: error: (lock.not.held) - myField.toString(); - // :: error: (lock.not.held) - this.myField = new MyClass(); - // :: error: (lock.not.held) - this.myField.toString(); - synchronized (this) { - myField = new MyClass(); - myField.toString(); - this.myField = new MyClass(); - this.myField.toString(); - } - } - - void testDereferenceOfReceiverAndParameter( - @GuardedBy("lock") ChapterExamples this, @GuardedBy("lock") MyClass m) { - // :: error: (lock.not.held) - myField = new MyClass(); - // :: error: (lock.not.held) - myField.toString(); - // :: error: (lock.not.held) - this.myField = new MyClass(); - // :: error: (lock.not.held) - this.myField.toString(); - // :: error: (lock.not.held) - m.field = new Object(); - // :: error: (lock.not.held) - m.field.toString(); - // The following error is due to the fact that you cannot access "this.lock" without first - // having acquired "lock". The right fix in a user scenario would be to not guard "this" - // with "this.lock". The current object could instead be guarded by "" or by some - // other lock expression that is not one of its fields. We are keeping this test case here - // to make sure this scenario issues a warning. - // :: error: (lock.not.held) - synchronized (lock) { - myField = new MyClass(); - myField.toString(); - this.myField = new MyClass(); - this.myField.toString(); - m.field = new Object(); - m.field.toString(); - } - } - - @GuardedBy("lock") MyClass myObj = new MyClass(); + class MyClass { + Object field = new Object(); @LockingFree - @GuardedBy("lock") MyClass myMethodReturningMyObj() { - return myObj; - } - - ChapterExamples() { - lock = new Object(); - } - - void myMethod8() { - // :: error: (lock.not.held) - boolean b4 = compare(p1, myMethod()); - - // An error is issued indicating that p2 might be dereferenced without - // "lock" being held. The method call need not be modified, since - // @GuardedBy({}) <: @GuardedByUnknown and @GuardedBy("lock") <: @GuardedByUnknown, - // but the lock must be acquired prior to the method call. - // :: error: (lock.not.held) - boolean b2 = compare(p1, p2); - // :: error: (lock.not.held) - boolean b3 = compare(p1, this.p2); - // :: error: (lock.not.held) - boolean b5 = compare(p1, this.myMethod()); - synchronized (lock) { - boolean b6 = compare(p1, p2); // OK - boolean b7 = compare(p1, this.p2); // OK - boolean b8 = compare(p1, myMethod()); // OK - boolean b9 = compare(p1, this.myMethod()); // OK - } - } - - // Keep in mind, the expression itself may or may not be a - // method call. Simple examples of expression.identifier : - // myObject.field - // myMethod().field - // myObject.method() - // myMethod().method() - - void myMethod7() { - // :: error: (lock.not.held) - Object f = myObj.field; - // :: error: (lock.not.held) - Object f2 = myMethodReturningMyObj().field; - // :: error: (lock.not.held) - myObj.method(); // method()'s receiver is annotated as @GuardSatisfied - // :: error: (lock.not.held) - myMethodReturningMyObj().method(); // method()'s receiver is annotated as @GuardSatisfied - - synchronized (lock) { - f = myObj.field; - f2 = myMethodReturningMyObj().field; - myObj.method(); - myMethodReturningMyObj().method(); - } - - // :: error: (lock.not.held) - myMethodReturningMyObj().field = new Object(); - // :: error: (lock.not.held) - x.field = new Object(); - synchronized (lock) { - myMethod().field = new Object(); - } - synchronized (lock) { - x.field = new Object(); // toString is not LockingFree. How annoying. - } - - this.x = new MyClass(); + Object method(@GuardSatisfied MyClass this) { + return new Object(); } - final Object lock; // Initialized in the constructor - - @GuardedBy("lock") MyClass x = new MyClass(); - - @GuardedBy("lock") MyClass y = x; // OK, because dereferences of y will require "lock" to be held. - - // :: error: (assignment.type.incompatible) - @GuardedBy({}) MyClass z = x; // ILLEGAL because dereferences of z do not require "lock" to be held. - @LockingFree - @GuardedBy("lock") MyClass myMethod() { - return x; // OK because the return type is @GuardedBy("lock") - } + public @GuardSatisfied(1) MyClass append( + @GuardSatisfied(1) MyClass this, @GuardSatisfied(2) MyClass m) { + return this; + } + + final Object myLock = new Object(); + + void testCallToMethod(@GuardedBy("myLock") MyClass this) { + // :: error: (lock.not.held) + this.method(); // method()'s receiver is annotated as @GuardSatisfied + } + } + + @MayReleaseLocks + @ReleasesNoLocks + // TODO: enable (multiple.sideeffect.annotation) + void testMultipleSideEffectAnnotations() {} + + void guardedByItselfOnReceiver(@GuardedBy("") ChapterExamples this) { + synchronized (this) { // Tests translation of '' to 'this' + // myField = new MyClass(); + myField.toString(); + this.myField = new MyClass(); + this.myField.toString(); + } + // :: error: (lock.not.held) + myField = new MyClass(); + // :: error: (lock.not.held) + myField.toString(); + // :: error: (lock.not.held) + this.myField = new MyClass(); + // :: error: (lock.not.held) + this.myField.toString(); + } + + void guardedByThisOnReceiver(@GuardedBy("this") ChapterExamples this) { + // :: error: (lock.not.held) + myField = new MyClass(); + // :: error: (lock.not.held) + myField.toString(); + // :: error: (lock.not.held) + this.myField = new MyClass(); + // :: error: (lock.not.held) + this.myField.toString(); + synchronized (this) { + myField = new MyClass(); + myField.toString(); + this.myField = new MyClass(); + this.myField.toString(); + } + } + + void testDereferenceOfReceiverAndParameter( + @GuardedBy("lock") ChapterExamples this, @GuardedBy("lock") MyClass m) { + // :: error: (lock.not.held) + myField = new MyClass(); + // :: error: (lock.not.held) + myField.toString(); + // :: error: (lock.not.held) + this.myField = new MyClass(); + // :: error: (lock.not.held) + this.myField.toString(); + // :: error: (lock.not.held) + m.field = new Object(); + // :: error: (lock.not.held) + m.field.toString(); + // The following error is due to the fact that you cannot access "this.lock" without first + // having acquired "lock". The right fix in a user scenario would be to not guard "this" + // with "this.lock". The current object could instead be guarded by "" or by some + // other lock expression that is not one of its fields. We are keeping this test case here + // to make sure this scenario issues a warning. + // :: error: (lock.not.held) + synchronized (lock) { + myField = new MyClass(); + myField.toString(); + this.myField = new MyClass(); + this.myField.toString(); + m.field = new Object(); + m.field.toString(); + } + } + + @GuardedBy("lock") MyClass myObj = new MyClass(); + + @LockingFree + @GuardedBy("lock") MyClass myMethodReturningMyObj() { + return myObj; + } + + ChapterExamples() { + lock = new Object(); + } + + void myMethod8() { + // :: error: (lock.not.held) + boolean b4 = compare(p1, myMethod()); + + // An error is issued indicating that p2 might be dereferenced without + // "lock" being held. The method call need not be modified, since + // @GuardedBy({}) <: @GuardedByUnknown and @GuardedBy("lock") <: @GuardedByUnknown, + // but the lock must be acquired prior to the method call. + // :: error: (lock.not.held) + boolean b2 = compare(p1, p2); + // :: error: (lock.not.held) + boolean b3 = compare(p1, this.p2); + // :: error: (lock.not.held) + boolean b5 = compare(p1, this.myMethod()); + synchronized (lock) { + boolean b6 = compare(p1, p2); // OK + boolean b7 = compare(p1, this.p2); // OK + boolean b8 = compare(p1, myMethod()); // OK + boolean b9 = compare(p1, this.myMethod()); // OK + } + } + + // Keep in mind, the expression itself may or may not be a + // method call. Simple examples of expression.identifier : + // myObject.field + // myMethod().field + // myObject.method() + // myMethod().method() + + void myMethod7() { + // :: error: (lock.not.held) + Object f = myObj.field; + // :: error: (lock.not.held) + Object f2 = myMethodReturningMyObj().field; + // :: error: (lock.not.held) + myObj.method(); // method()'s receiver is annotated as @GuardSatisfied + // :: error: (lock.not.held) + myMethodReturningMyObj().method(); // method()'s receiver is annotated as @GuardSatisfied + + synchronized (lock) { + f = myObj.field; + f2 = myMethodReturningMyObj().field; + myObj.method(); + myMethodReturningMyObj().method(); + } + + // :: error: (lock.not.held) + myMethodReturningMyObj().field = new Object(); + // :: error: (lock.not.held) + x.field = new Object(); + synchronized (lock) { + myMethod().field = new Object(); + } + synchronized (lock) { + x.field = new Object(); // toString is not LockingFree. How annoying. + } + + this.x = new MyClass(); + } + + final Object lock; // Initialized in the constructor + + @GuardedBy("lock") MyClass x = new MyClass(); + + @GuardedBy("lock") MyClass y = x; // OK, because dereferences of y will require "lock" to be held. + + // :: error: (assignment.type.incompatible) + @GuardedBy({}) MyClass z = x; // ILLEGAL because dereferences of z do not require "lock" to be held. + + @LockingFree + @GuardedBy("lock") MyClass myMethod() { + return x; // OK because the return type is @GuardedBy("lock") + } + + void exampleMethod() { + // :: error: (lock.not.held) + x.field = new Object(); // ILLEGAL because the lock is not known to be held + // :: error: (lock.not.held) + y.field = new Object(); // ILLEGAL because the lock is not known to be held + // :: error: (lock.not.held) + myMethod().field = new Object(); // ILLEGAL because the lock is not known to be held + synchronized (lock) { + x.field = new Object(); // OK: the lock is known to be held + y.field = new Object(); // OK: the lock is known to be held + myMethod().field = new Object(); // OK: the lock is known to be held + } + } + + final MyClass a = new MyClass(); + final MyClass b = new MyClass(); + + @GuardedBy("a") MyClass x5 = new MyClass(); + + @GuardedBy({"a", "b"}) MyClass y5 = new MyClass(); + + void myMethod2() { + // :: error: (assignment.type.incompatible) + y5 = x5; // ILLEGAL + } - void exampleMethod() { - // :: error: (lock.not.held) - x.field = new Object(); // ILLEGAL because the lock is not known to be held - // :: error: (lock.not.held) - y.field = new Object(); // ILLEGAL because the lock is not known to be held - // :: error: (lock.not.held) - myMethod().field = new Object(); // ILLEGAL because the lock is not known to be held - synchronized (lock) { - x.field = new Object(); // OK: the lock is known to be held - y.field = new Object(); // OK: the lock is known to be held - myMethod().field = new Object(); // OK: the lock is known to be held - } - } + // :: error: (immutable.type.guardedby) + @GuardedBy("a") String s = "string"; - final MyClass a = new MyClass(); - final MyClass b = new MyClass(); + @GuardedBy({}) MyClass o1; - @GuardedBy("a") MyClass x5 = new MyClass(); + @GuardedBy("lock") MyClass o2; - @GuardedBy({"a", "b"}) MyClass y5 = new MyClass(); + @GuardedBy("lock") MyClass o3; - void myMethod2() { - // :: error: (assignment.type.incompatible) - y5 = x5; // ILLEGAL - } + void someMethod() { + o3 = o2; // OK, since o2 and o3 are guarded by exactly the same lock set. + // :: error: (assignment.type.incompatible) + o1 = o2; // Assignment type incompatible errors are issued for both assignments, since + // :: error: (assignment.type.incompatible) + o2 = o1; // {"lock"} and {} are not identical sets. + } + + @SuppressWarnings("lock:cast.unsafe") + void someMethod2() { + // A cast can be used if the user knows it is safe to do so. + // However, the @SuppressWarnings must be added. + o1 = (@GuardedBy({}) MyClass) o2; + } + + static final Object myLock = new Object(); + + @GuardedBy("ChapterExamples.myLock") MyClass myMethod3() { + return new MyClass(); + } + + // reassignments without holding the lock are OK. + @GuardedBy("ChapterExamples.myLock") MyClass x2 = myMethod3(); + + @GuardedBy("ChapterExamples.myLock") MyClass y2 = x2; + + void myMethod4() { + // :: error: (lock.not.held) + x2.field = new Object(); // ILLEGAL because the lock is not held + synchronized (ChapterExamples.myLock) { + y2.field = new Object(); // OK: the lock is held + } + } + + void myMethod5(@GuardedBy("ChapterExamples.myLock") MyClass a) { + // :: error: (lock.not.held) + a.field = new Object(); // ILLEGAL: the lock is not held + synchronized (ChapterExamples.myLock) { + a.field = new Object(); // OK: the lock is held + } + } + + @LockingFree + boolean compare(@GuardSatisfied MyClass a, @GuardSatisfied MyClass b) { + return true; + } + + @GuardedBy({}) MyClass p1; + + @GuardedBy("lock") MyClass p2; + + void myMethod6() { + // It is the responsibility of callers to 'compare' to acquire the lock. + synchronized (lock) { + boolean b1 = compare(p1, p2); // OK. No error issued. + } + // :: error: (lock.not.held) + p2.field = new Object(); + // An error is issued indicating that p2 might be dereferenced without "lock" being held. + // The method call need not be modified, since @GuardedBy({}) <: @GuardedByUnknown and + // @GuardedBy("lock") <: @GuardedByUnknown, but the lock must be acquired prior to the + // method call. + // :: error: (lock.not.held) + boolean b2 = compare(p1, p2); + } + + void helper1(@GuardedBy("ChapterExamples.myLock") MyClass a) { + // :: error: (lock.not.held) + a.field = new Object(); // ILLEGAL: the lock is not held + synchronized (ChapterExamples.myLock) { + a.field = new Object(); // OK: the lock is held + } + } + + @Holding("ChapterExamples.myLock") + @LockingFree + void helper2(@GuardedBy("ChapterExamples.myLock") MyClass b) { + b.field = new Object(); // OK: the lock is held + } + + @LockingFree + void helper3(@GuardSatisfied MyClass c) { + c.field = new Object(); // OK: the guard is satisfied + } + + @LockingFree + void helper4(@GuardedBy("ChapterExamples.myLock") MyClass d) { + // :: error: (lock.not.held) + d.field = new Object(); // ILLEGAL: the lock is not held + } + + @ReleasesNoLocks + void helper5() {} + + // No annotation means @ReleasesNoLocks + void helper6() {} + + void myMethod2(@GuardedBy("ChapterExamples.myLock") MyClass e) { + helper1(e); // OK to pass to another routine without holding the lock. + // :: error: (lock.not.held) + e.field = new Object(); // ILLEGAL: the lock is not held + // :: error: (contracts.precondition.not.satisfied) + helper2(e); + // :: error: (lock.not.held) + helper3(e); + synchronized (ChapterExamples.myLock) { + helper2(e); + helper3(e); // OK, since parameter is @GuardSatisfied + helper4(e); // OK, but helper4's body still has an error. + helper5(); + helper6(); + helper2(e); // Can still be called after helper5() and helper6() + } + } + + private @GuardedBy({}) MyClass myField; + + int someInt = 1; + + // TODO: For now, boxed types are treated as primitive types. This may change in the future. + @SuppressWarnings({"deprecation", "removal"}) // new Integer + void unboxing() { + int a = someInt; // :: error: (immutable.type.guardedby) - @GuardedBy("a") String s = "string"; - - @GuardedBy({}) MyClass o1; - - @GuardedBy("lock") MyClass o2; - - @GuardedBy("lock") MyClass o3; - - void someMethod() { - o3 = o2; // OK, since o2 and o3 are guarded by exactly the same lock set. - - // :: error: (assignment.type.incompatible) - o1 = o2; // Assignment type incompatible errors are issued for both assignments, since - // :: error: (assignment.type.incompatible) - o2 = o1; // {"lock"} and {} are not identical sets. - } - - @SuppressWarnings("lock:cast.unsafe") - void someMethod2() { - // A cast can be used if the user knows it is safe to do so. - // However, the @SuppressWarnings must be added. - o1 = (@GuardedBy({}) MyClass) o2; + @GuardedBy("lock") Integer c; + synchronized (lock) { + // :: error: (assignment.type.incompatible) + c = a; } - static final Object myLock = new Object(); - - @GuardedBy("ChapterExamples.myLock") MyClass myMethod3() { - return new MyClass(); - } - - // reassignments without holding the lock are OK. - @GuardedBy("ChapterExamples.myLock") MyClass x2 = myMethod3(); - - @GuardedBy("ChapterExamples.myLock") MyClass y2 = x2; - - void myMethod4() { - // :: error: (lock.not.held) - x2.field = new Object(); // ILLEGAL because the lock is not held - synchronized (ChapterExamples.myLock) { - y2.field = new Object(); // OK: the lock is held - } - } - - void myMethod5(@GuardedBy("ChapterExamples.myLock") MyClass a) { - // :: error: (lock.not.held) - a.field = new Object(); // ILLEGAL: the lock is not held - synchronized (ChapterExamples.myLock) { - a.field = new Object(); // OK: the lock is held - } + // :: error: (immutable.type.guardedby) + @GuardedBy("lock") Integer b = 1; + int d; + synchronized (lock) { + d = b; + d = b.intValue(); // The de-sugared version } - @LockingFree - boolean compare(@GuardSatisfied MyClass a, @GuardSatisfied MyClass b) { - return true; - } + c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). - @GuardedBy({}) MyClass p1; - - @GuardedBy("lock") MyClass p2; - - void myMethod6() { - // It is the responsibility of callers to 'compare' to acquire the lock. - synchronized (lock) { - boolean b1 = compare(p1, p2); // OK. No error issued. - } - // :: error: (lock.not.held) - p2.field = new Object(); - // An error is issued indicating that p2 might be dereferenced without "lock" being held. - // The method call need not be modified, since @GuardedBy({}) <: @GuardedByUnknown and - // @GuardedBy("lock") <: @GuardedByUnknown, but the lock must be acquired prior to the - // method call. - // :: error: (lock.not.held) - boolean b2 = compare(p1, p2); - } + c = new Integer(c.intValue() + b.intValue()); // The de-sugared version + c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version - void helper1(@GuardedBy("ChapterExamples.myLock") MyClass a) { - // :: error: (lock.not.held) - a.field = new Object(); // ILLEGAL: the lock is not held - synchronized (ChapterExamples.myLock) { - a.field = new Object(); // OK: the lock is held - } - } - - @Holding("ChapterExamples.myLock") - @LockingFree - void helper2(@GuardedBy("ChapterExamples.myLock") MyClass b) { - b.field = new Object(); // OK: the lock is held - } + synchronized (lock) { + c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). - @LockingFree - void helper3(@GuardSatisfied MyClass c) { - c.field = new Object(); // OK: the guard is satisfied + c = new Integer(c.intValue() + b.intValue()); // The de-sugared version + c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version } - @LockingFree - void helper4(@GuardedBy("ChapterExamples.myLock") MyClass d) { - // :: error: (lock.not.held) - d.field = new Object(); // ILLEGAL: the lock is not held - } + a = b; + b = c; // OK + } - @ReleasesNoLocks - void helper5() {} - - // No annotation means @ReleasesNoLocks - void helper6() {} - - void myMethod2(@GuardedBy("ChapterExamples.myLock") MyClass e) { - helper1(e); // OK to pass to another routine without holding the lock. - // :: error: (lock.not.held) - e.field = new Object(); // ILLEGAL: the lock is not held - // :: error: (contracts.precondition.not.satisfied) - helper2(e); - // :: error: (lock.not.held) - helper3(e); - synchronized (ChapterExamples.myLock) { - helper2(e); - helper3(e); // OK, since parameter is @GuardSatisfied - helper4(e); // OK, but helper4's body still has an error. - helper5(); - helper6(); - helper2(e); // Can still be called after helper5() and helper6() - } + /* TODO Re-enable when guarding primitives is supported by the Lock Checker. + void boxingUnboxing() { + @GuardedBy("lock") int a = 1; + @GuardedBy({}) Integer c; + synchronized(lock) { + c = a; } - private @GuardedBy({}) MyClass myField; - - int someInt = 1; - - // TODO: For now, boxed types are treated as primitive types. This may change in the future. - @SuppressWarnings({"deprecation", "removal"}) // new Integer - void unboxing() { - int a = someInt; - // :: error: (immutable.type.guardedby) - @GuardedBy("lock") Integer c; - synchronized (lock) { - // :: error: (assignment.type.incompatible) - c = a; - } - - // :: error: (immutable.type.guardedby) - @GuardedBy("lock") Integer b = 1; - int d; - synchronized (lock) { - d = b; - d = b.intValue(); // The de-sugared version - } - - c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). - - c = new Integer(c.intValue() + b.intValue()); // The de-sugared version - c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version - - synchronized (lock) { - c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). - - c = new Integer(c.intValue() + b.intValue()); // The de-sugared version - c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version - } - - a = b; - b = c; // OK + @GuardedBy("lock") Integer b = 1; + @GuardedBy({}) int d; + synchronized(lock) { + // TODO re-enable this error (assignment.type.incompatible) + d = b; // TODO: This should not result in "assignment.type.incompatible" because 'b' is actually syntactic sugar for b.intValue(). + d = b.intValue(); // The de-sugared version does not issue an error. } - /* TODO Re-enable when guarding primitives is supported by the Lock Checker. - void boxingUnboxing() { - @GuardedBy("lock") int a = 1; - @GuardedBy({}) Integer c; - synchronized(lock) { - c = a; - } - - @GuardedBy("lock") Integer b = 1; - @GuardedBy({}) int d; - synchronized(lock) { - // TODO re-enable this error (assignment.type.incompatible) - d = b; // TODO: This should not result in "assignment.type.incompatible" because 'b' is actually syntactic sugar for b.intValue(). - d = b.intValue(); // The de-sugared version does not issue an error. - } + // TODO re-enable this error (lock.not.held) + c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()), hence 'lock' must be held. + // TODO re-enable this error (lock.not.held) + c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version - // TODO re-enable this error (lock.not.held) + synchronized(lock) { c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()), hence 'lock' must be held. - // TODO re-enable this error (lock.not.held) c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version - - synchronized(lock) { - c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()), hence 'lock' must be held. - c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version - } - - // TODO re-enable this error (lock.not.held) - a = b; // TODO: This assignment between two reference types should not require a lock to be held. - }*/ - - final ReentrantLock lock1 = new ReentrantLock(); - final ReentrantLock lock2 = new ReentrantLock(); - - @GuardedBy("lock1") MyClass filename; - - @GuardedBy("lock2") MyClass extension; - - void method0() { - // :: error: (lock.not.held) :: error: (lock.not.held) - filename = filename.append(extension); - } - - void method1() { - lock1.lock(); - // :: error: (lock.not.held) - filename = filename.append(extension); - } - - void method2() { - lock2.lock(); - // :: error: (lock.not.held) - filename = filename.append(extension); - } - - void method3() { - lock1.lock(); - lock2.lock(); - filename = filename.append(extension); - filename = filename.append(null); - // :: error: (assignment.type.incompatible) - filename = extension.append(extension); - // :: error: (assignment.type.incompatible) - filename = extension.append(filename); - } - - void matchingGSparams(@GuardSatisfied(1) MyClass m1, @GuardSatisfied(1) MyClass m2) {} - - void method4() { - lock1.lock(); - lock2.lock(); - matchingGSparams(filename, null); - matchingGSparams(null, filename); } - public static boolean deepEquals(Object o1, Object o2) { - if (o1 instanceof Object[] && o2 instanceof Object[]) { - return Arrays.deepEquals((Object[]) o1, (Object[]) o2); - } - return false; - } - - public static final class Comparer> { - public boolean compare(T[] a1, T[] a2) { - T elt1 = a1[0]; - T elt2 = a2[0]; - return elt1.equals(elt2); - } - } + // TODO re-enable this error (lock.not.held) + a = b; // TODO: This assignment between two reference types should not require a lock to be held. + }*/ - public static boolean indexOf(T[] a, Object elt) { - if (elt.equals(a[0])) { - return false; - } - return true; - // found : (@org.checkerframework.checker.lock.qual.GuardedBy({}) :: T)[ extends - // @GuardedByUnknown @LockPossiblyHeld Object super @GuardedBy({}) @LockHeld Void] - // required: @GuardedBy @LockPossiblyHeld Object - } + final ReentrantLock lock1 = new ReentrantLock(); + final ReentrantLock lock2 = new ReentrantLock(); - private static final Object NULL_KEY = new Object(); + @GuardedBy("lock1") MyClass filename; - // A guardsatisfied.location.disallowed error is issued for the cast. - @SuppressWarnings({"cast.unsafe", "guardsatisfied.location.disallowed"}) - private static @GuardSatisfied(1) Object maskNull(@GuardSatisfied(1) Object key) { - return (key == null ? (@GuardSatisfied(1) Object) NULL_KEY : key); - } + @GuardedBy("lock2") MyClass extension; - public void assignmentOfGSWithNoIndex(@GuardSatisfied Object a, @GuardSatisfied Object b) { - // :: error: (guardsatisfied.assignment.disallowed) - a = b; - } + void method0() { + // :: error: (lock.not.held) :: error: (lock.not.held) + filename = filename.append(extension); + } - class Session { - @Holding("this") - public void kill(@GuardSatisfied Session this) {} - } + void method1() { + lock1.lock(); + // :: error: (lock.not.held) + filename = filename.append(extension); + } - class SessionManager { - private @GuardedBy("") Session session = new Session(); + void method2() { + lock2.lock(); + // :: error: (lock.not.held) + filename = filename.append(extension); + } - private void session_done() { - final @GuardedBy("") Session tmp = session; - session = null; - synchronized (tmp) { - tmp.kill(); - } - } + void method3() { + lock1.lock(); + lock2.lock(); + filename = filename.append(extension); + filename = filename.append(null); + // :: error: (assignment.type.incompatible) + filename = extension.append(extension); + // :: error: (assignment.type.incompatible) + filename = extension.append(filename); + } + + void matchingGSparams(@GuardSatisfied(1) MyClass m1, @GuardSatisfied(1) MyClass m2) {} + + void method4() { + lock1.lock(); + lock2.lock(); + matchingGSparams(filename, null); + matchingGSparams(null, filename); + } + + public static boolean deepEquals(Object o1, Object o2) { + if (o1 instanceof Object[] && o2 instanceof Object[]) { + return Arrays.deepEquals((Object[]) o1, (Object[]) o2); + } + return false; + } + + public static final class Comparer> { + public boolean compare(T[] a1, T[] a2) { + T elt1 = a1[0]; + T elt2 = a2[0]; + return elt1.equals(elt2); + } + } + + public static boolean indexOf(T[] a, Object elt) { + if (elt.equals(a[0])) { + return false; + } + return true; + // found : (@org.checkerframework.checker.lock.qual.GuardedBy({}) :: T)[ extends + // @GuardedByUnknown @LockPossiblyHeld Object super @GuardedBy({}) @LockHeld Void] + // required: @GuardedBy @LockPossiblyHeld Object + } + + private static final Object NULL_KEY = new Object(); + + // A guardsatisfied.location.disallowed error is issued for the cast. + @SuppressWarnings({"cast.unsafe", "guardsatisfied.location.disallowed"}) + private static @GuardSatisfied(1) Object maskNull(@GuardSatisfied(1) Object key) { + return (key == null ? (@GuardSatisfied(1) Object) NULL_KEY : key); + } + + public void assignmentOfGSWithNoIndex(@GuardSatisfied Object a, @GuardSatisfied Object b) { + // :: error: (guardsatisfied.assignment.disallowed) + a = b; + } + + class Session { + @Holding("this") + public void kill(@GuardSatisfied Session this) {} + } + + class SessionManager { + private @GuardedBy("") Session session = new Session(); + + private void session_done() { + final @GuardedBy("") Session tmp = session; + session = null; + synchronized (tmp) { + tmp.kill(); + } } + } } diff --git a/checker/tests/lock/ClassLiterals.java b/checker/tests/lock/ClassLiterals.java index 04564529af6..6024614f849 100644 --- a/checker/tests/lock/ClassLiterals.java +++ b/checker/tests/lock/ClassLiterals.java @@ -3,30 +3,30 @@ import org.checkerframework.checker.lock.qual.Holding; public class ClassLiterals { - @Holding("ClassLiterals.class") - static Object method1() { - return new Object(); - } + @Holding("ClassLiterals.class") + static Object method1() { + return new Object(); + } - // a class literal may not terminate a JavaExpression string - @Holding("ClassLiterals") - // :: error: (flowexpr.parse.error) - static void method2() {} + // a class literal may not terminate a JavaExpression string + @Holding("ClassLiterals") + // :: error: (flowexpr.parse.error) + static void method2() {} - @Holding("ClassLiterals.method1()") - static void method3() {} + @Holding("ClassLiterals.method1()") + static void method3() {} - @Holding("testpackage.ClassLiterals.class") - static void method4() {} + @Holding("testpackage.ClassLiterals.class") + static void method4() {} - // a class literal may not terminate a JavaExpression string - @Holding("testpackage.ClassLiterals") - // :: error: (flowexpr.parse.error) - static void method5() {} + // a class literal may not terminate a JavaExpression string + @Holding("testpackage.ClassLiterals") + // :: error: (flowexpr.parse.error) + static void method5() {} - @Holding("testpackage.ClassLiterals.method1()") - static void method6() {} + @Holding("testpackage.ClassLiterals.method1()") + static void method6() {} - @Holding("java.lang.Comparable.class") - static void method7() {} + @Holding("java.lang.Comparable.class") + static void method7() {} } diff --git a/checker/tests/lock/ConstructorReturnNPE.java b/checker/tests/lock/ConstructorReturnNPE.java index 2d1be3cfd33..4f41dca2e03 100644 --- a/checker/tests/lock/ConstructorReturnNPE.java +++ b/checker/tests/lock/ConstructorReturnNPE.java @@ -5,6 +5,6 @@ // :: error: (expression.unparsable.type.invalid) @GuardedBy("lock") class ConstructorReturnNPE { - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("lock") ConstructorReturnNPE() {} + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("lock") ConstructorReturnNPE() {} } diff --git a/checker/tests/lock/ConstructorsLock.java b/checker/tests/lock/ConstructorsLock.java index a5e3d5de475..c7307953c5c 100644 --- a/checker/tests/lock/ConstructorsLock.java +++ b/checker/tests/lock/ConstructorsLock.java @@ -4,45 +4,45 @@ // but not over their class's fields public @GuardedBy({}) class ConstructorsLock { - static class MyClass { - public Object field; - } + static class MyClass { + public Object field; + } - final MyClass unlocked = new MyClass(); + final MyClass unlocked = new MyClass(); - @GuardedBy("this") MyClass guardedThis = new MyClass(); + @GuardedBy("this") MyClass guardedThis = new MyClass(); - @GuardedBy("unlocked") MyClass guardedOther = new MyClass(); + @GuardedBy("unlocked") MyClass guardedOther = new MyClass(); - static final MyClass unlockedStatic = new MyClass(); + static final MyClass unlockedStatic = new MyClass(); - @GuardedBy("unlockedStatic") MyClass nonstaticGuardedByStatic = new MyClass(); + @GuardedBy("unlockedStatic") MyClass nonstaticGuardedByStatic = new MyClass(); - // :: error: (expression.unparsable.type.invalid) - static @GuardedBy("unlocked") MyClass staticGuardedByNonStatic = new MyClass(); - static @GuardedBy("unlockedStatic") MyClass staticGuardedByStatic = new MyClass(); + // :: error: (expression.unparsable.type.invalid) + static @GuardedBy("unlocked") MyClass staticGuardedByNonStatic = new MyClass(); + static @GuardedBy("unlockedStatic") MyClass staticGuardedByStatic = new MyClass(); + + Object initializedObject1 = unlocked.field; + Object initializedObject2 = guardedThis.field; + // :: error: (lock.not.held) + Object initializedObject3 = guardedOther.field; + // :: error: (expression.unparsable.type.invalid) + Object initializedObject4 = staticGuardedByNonStatic.field; + // :: error: (lock.not.held) + Object initializedObject5 = nonstaticGuardedByStatic.field; + // :: error: (lock.not.held) + Object initializedObject6 = staticGuardedByStatic.field; - Object initializedObject1 = unlocked.field; - Object initializedObject2 = guardedThis.field; + ConstructorsLock() { + unlocked.field.toString(); + guardedThis.field.toString(); // :: error: (lock.not.held) - Object initializedObject3 = guardedOther.field; + guardedOther.field.toString(); // :: error: (expression.unparsable.type.invalid) - Object initializedObject4 = staticGuardedByNonStatic.field; + staticGuardedByNonStatic.field.toString(); // :: error: (lock.not.held) - Object initializedObject5 = nonstaticGuardedByStatic.field; + nonstaticGuardedByStatic.field.toString(); // :: error: (lock.not.held) - Object initializedObject6 = staticGuardedByStatic.field; - - ConstructorsLock() { - unlocked.field.toString(); - guardedThis.field.toString(); - // :: error: (lock.not.held) - guardedOther.field.toString(); - // :: error: (expression.unparsable.type.invalid) - staticGuardedByNonStatic.field.toString(); - // :: error: (lock.not.held) - nonstaticGuardedByStatic.field.toString(); - // :: error: (lock.not.held) - staticGuardedByStatic.field.toString(); - } + staticGuardedByStatic.field.toString(); + } } diff --git a/checker/tests/lock/Fields.java b/checker/tests/lock/Fields.java index 7cc74005736..27c6c44dbe1 100644 --- a/checker/tests/lock/Fields.java +++ b/checker/tests/lock/Fields.java @@ -1,103 +1,103 @@ import org.checkerframework.checker.lock.qual.*; public class Fields { - class MyClass { - public Object field; - } + class MyClass { + public Object field; + } - static @GuardedBy("Fields.class") MyClass lockedStatically; + static @GuardedBy("Fields.class") MyClass lockedStatically; - static synchronized void ssMethod() { - lockedStatically.field = new Object(); - } + static synchronized void ssMethod() { + lockedStatically.field = new Object(); + } - @GuardedBy("lockingObject") MyClass locked; + @GuardedBy("lockingObject") MyClass locked; - final Object lockingObject = new Object(); + final Object lockingObject = new Object(); - synchronized void wrongLock1() { - // locking over wrong lock - // :: error: (lock.not.held) - locked.field = new Object(); // error - } + synchronized void wrongLock1() { + // locking over wrong lock + // :: error: (lock.not.held) + locked.field = new Object(); // error + } - synchronized void wrongLock2() { - // locking over wrong lock - synchronized (this) { - // :: error: (lock.not.held) - locked.field = new Object(); // error - } + synchronized void wrongLock2() { + // locking over wrong lock + synchronized (this) { + // :: error: (lock.not.held) + locked.field = new Object(); // error } + } - void rightLock() { - synchronized (lockingObject) { - locked.field = new Object(); - } - - // accessing after the synchronized object - // :: error: (lock.not.held) - locked.field = new Object(); // error + void rightLock() { + synchronized (lockingObject) { + locked.field = new Object(); } - @Holding("lockingObject") - void usingHolding() { - locked.field = new Object(); - } + // accessing after the synchronized object + // :: error: (lock.not.held) + locked.field = new Object(); // error + } + + @Holding("lockingObject") + void usingHolding() { + locked.field = new Object(); + } - @GuardedBy("this") MyClass lockedByThis; + @GuardedBy("this") MyClass lockedByThis; - void wrongLocksb() { - // without locking - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error + void wrongLocksb() { + // without locking + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error - synchronized (Fields.class) { - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error - } + synchronized (Fields.class) { + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error } + } - void rightLockb() { - synchronized (this) { - lockedByThis.field = new Object(); - } + void rightLockb() { + synchronized (this) { + lockedByThis.field = new Object(); + } - // accessing after the synchronized object - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error + // accessing after the synchronized object + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error + } + + synchronized void synchronizedMethodb() { + lockedByThis.field = new Object(); + } + + void test() { + // synchronized over the right object + final Fields a = new Fields(); + final Fields b = new Fields(); + + synchronized (this) { + lockedByThis.field = new Object(); + // :: error: (lock.not.held) + a.lockedByThis.field = new Object(); // error + // :: error: (lock.not.held) + b.lockedByThis.field = new Object(); // error } - synchronized void synchronizedMethodb() { - lockedByThis.field = new Object(); + synchronized (a) { + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error + a.lockedByThis.field = new Object(); + // :: error: (lock.not.held) + b.lockedByThis.field = new Object(); // error } - void test() { - // synchronized over the right object - final Fields a = new Fields(); - final Fields b = new Fields(); - - synchronized (this) { - lockedByThis.field = new Object(); - // :: error: (lock.not.held) - a.lockedByThis.field = new Object(); // error - // :: error: (lock.not.held) - b.lockedByThis.field = new Object(); // error - } - - synchronized (a) { - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error - a.lockedByThis.field = new Object(); - // :: error: (lock.not.held) - b.lockedByThis.field = new Object(); // error - } - - synchronized (b) { - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error - // :: error: (lock.not.held) - a.lockedByThis.field = new Object(); // error - b.lockedByThis.field = new Object(); - } + synchronized (b) { + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error + // :: error: (lock.not.held) + a.lockedByThis.field = new Object(); // error + b.lockedByThis.field = new Object(); } + } } diff --git a/checker/tests/lock/FlowExpressionsTest.java b/checker/tests/lock/FlowExpressionsTest.java index 3902637684a..6c1c164f00e 100644 --- a/checker/tests/lock/FlowExpressionsTest.java +++ b/checker/tests/lock/FlowExpressionsTest.java @@ -2,36 +2,36 @@ import org.checkerframework.dataflow.qual.Pure; public class FlowExpressionsTest { - class MyClass { - public Object field; - } + class MyClass { + public Object field; + } - private final @GuardedBy({""}) MyClass m; + private final @GuardedBy({""}) MyClass m; - FlowExpressionsTest() { - m = new MyClass(); - } + FlowExpressionsTest() { + m = new MyClass(); + } - // private @GuardedBy({"nonexistentfield"}) MyClass m2; + // private @GuardedBy({"nonexistentfield"}) MyClass m2; - @Pure - private @GuardedBy({""}) MyClass getm() { - return m; - } + @Pure + private @GuardedBy({""}) MyClass getm() { + return m; + } - public void method() { - // :: error: (lock.not.held) - getm().field = new Object(); - // :: error: (lock.not.held) - m.field = new Object(); - // TODO: fix the Lock Checker code so that a flowexpr.parse.error is issued (due to the - // guard of "nonexistentfield" on m2) - // m2.field = new Object(); - synchronized (m) { - m.field = new Object(); - } - synchronized (getm()) { - getm().field = new Object(); - } + public void method() { + // :: error: (lock.not.held) + getm().field = new Object(); + // :: error: (lock.not.held) + m.field = new Object(); + // TODO: fix the Lock Checker code so that a flowexpr.parse.error is issued (due to the + // guard of "nonexistentfield" on m2) + // m2.field = new Object(); + synchronized (m) { + m.field = new Object(); + } + synchronized (getm()) { + getm().field = new Object(); } + } } diff --git a/checker/tests/lock/FullyQualified.java b/checker/tests/lock/FullyQualified.java index 23308ea8950..ba37de468ee 100644 --- a/checker/tests/lock/FullyQualified.java +++ b/checker/tests/lock/FullyQualified.java @@ -1,16 +1,15 @@ package com.example.mypackage; -import org.checkerframework.checker.lock.qual.GuardedBy; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.lock.qual.GuardedBy; public class FullyQualified { - public static final @GuardedBy("") List all_classes = new ArrayList<>(); + public static final @GuardedBy("") List all_classes = new ArrayList<>(); - void test() { - synchronized (com.example.mypackage.FullyQualified.all_classes) { - com.example.mypackage.FullyQualified.all_classes.add(new Object()); - } + void test() { + synchronized (com.example.mypackage.FullyQualified.all_classes) { + com.example.mypackage.FullyQualified.all_classes.add(new Object()); } + } } diff --git a/checker/tests/lock/GuardSatisfiedArray.java b/checker/tests/lock/GuardSatisfiedArray.java index 9d69fe5fa96..f64ef02a4ce 100644 --- a/checker/tests/lock/GuardSatisfiedArray.java +++ b/checker/tests/lock/GuardSatisfiedArray.java @@ -3,19 +3,18 @@ // Test case for Issue #917: // https://github.com/typetools/checker-framework/issues/917 -import org.checkerframework.checker.lock.qual.GuardSatisfied; - import java.util.List; +import org.checkerframework.checker.lock.qual.GuardSatisfied; public class GuardSatisfiedArray { - void foo(@GuardSatisfied Object arg1, @GuardSatisfied Object arg2) {} + void foo(@GuardSatisfied Object arg1, @GuardSatisfied Object arg2) {} - void bar(@GuardSatisfied Object[] args) { - foo(args[0], args[1]); - } + void bar(@GuardSatisfied Object[] args) { + foo(args[0], args[1]); + } - void baz(@GuardSatisfied List<@GuardSatisfied Object> args) { - foo(args.get(0), args.get(1)); - } + void baz(@GuardSatisfied List<@GuardSatisfied Object> args) { + foo(args.get(0), args.get(1)); + } } diff --git a/checker/tests/lock/GuardSatisfiedTest.java b/checker/tests/lock/GuardSatisfiedTest.java index 4d4b40e92be..b0b86a588bf 100644 --- a/checker/tests/lock/GuardSatisfiedTest.java +++ b/checker/tests/lock/GuardSatisfiedTest.java @@ -4,302 +4,302 @@ import org.checkerframework.checker.lock.qual.MayReleaseLocks; public class GuardSatisfiedTest { - void testGuardSatisfiedIndexMatching( - @GuardSatisfied GuardSatisfiedTest this, - @GuardSatisfied(1) Object o, - @GuardSatisfied(2) Object p, - @GuardSatisfied Object q) { + void testGuardSatisfiedIndexMatching( + @GuardSatisfied GuardSatisfiedTest this, + @GuardSatisfied(1) Object o, + @GuardSatisfied(2) Object p, + @GuardSatisfied Object q) { + methodToCall1(o, o); + methodToCall1(p, p); + // :: error: (guardsatisfied.parameters.must.match) + methodToCall1(o, p); + // :: error: (guardsatisfied.parameters.must.match) + methodToCall1(p, o); + } + + // Test defaulting of parameters - they must default to @GuardedBy({}), not @GuardSatisfied + void testDefaulting(Object mustDefaultToGuardedByNothing, @GuardSatisfied Object p) { + // Must assign in this direction to test the defaulting because assigning a RHS of + // @GuardedBy({}) to a LHS @GuardSatisfied is legal. + // :: error: (assignment.type.incompatible) + mustDefaultToGuardedByNothing = p; + @GuardedBy({}) Object q = mustDefaultToGuardedByNothing; + } + + void testMethodCall( + @GuardSatisfied GuardSatisfiedTest this, + @GuardedBy("lock1") Object o, + @GuardedBy("lock2") Object p, + @GuardSatisfied Object q) { + // Test matching parameters + + // :: error: (lock.not.held) + methodToCall1(o, o); + // :: error: (lock.not.held) :: error: (guardsatisfied.parameters.must.match) + methodToCall1(o, p); + // :: error: (lock.not.held) + methodToCall1(p, p); + synchronized (lock2) { + // :: error: (lock.not.held) + methodToCall1(o, o); + // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) + methodToCall1(o, p); + methodToCall1(p, p); + synchronized (lock1) { methodToCall1(o, o); - methodToCall1(p, p); // :: error: (guardsatisfied.parameters.must.match) methodToCall1(o, p); - // :: error: (guardsatisfied.parameters.must.match) - methodToCall1(p, o); + methodToCall1(p, p); + } } - // Test defaulting of parameters - they must default to @GuardedBy({}), not @GuardSatisfied - void testDefaulting(Object mustDefaultToGuardedByNothing, @GuardSatisfied Object p) { - // Must assign in this direction to test the defaulting because assigning a RHS of - // @GuardedBy({}) to a LHS @GuardSatisfied is legal. - // :: error: (assignment.type.incompatible) - mustDefaultToGuardedByNothing = p; - @GuardedBy({}) Object q = mustDefaultToGuardedByNothing; + // Test a return type matching a parameter. + + // :: error: (lock.not.held) + o = methodToCall2(o); + // :: error: (lock.not.held) :: error: (assignment.type.incompatible) + p = methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(p); + synchronized (lock2) { + // :: error: (lock.not.held) + o = methodToCall2(o); + // :: error: (lock.not.held) :: error: (assignment.type.incompatible) + p = methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(o); + methodToCall2(p); + } + synchronized (lock1) { + o = methodToCall2(o); + // :: error: (assignment.type.incompatible) + p = methodToCall2(o); + methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(p); } - void testMethodCall( - @GuardSatisfied GuardSatisfiedTest this, - @GuardedBy("lock1") Object o, - @GuardedBy("lock2") Object p, - @GuardSatisfied Object q) { - // Test matching parameters - - // :: error: (lock.not.held) - methodToCall1(o, o); - // :: error: (lock.not.held) :: error: (guardsatisfied.parameters.must.match) - methodToCall1(o, p); - // :: error: (lock.not.held) - methodToCall1(p, p); - synchronized (lock2) { - // :: error: (lock.not.held) - methodToCall1(o, o); - // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) - methodToCall1(o, p); - methodToCall1(p, p); - synchronized (lock1) { - methodToCall1(o, o); - // :: error: (guardsatisfied.parameters.must.match) - methodToCall1(o, p); - methodToCall1(p, p); - } - } - - // Test a return type matching a parameter. - - // :: error: (lock.not.held) - o = methodToCall2(o); - // :: error: (lock.not.held) :: error: (assignment.type.incompatible) - p = methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(p); - synchronized (lock2) { - // :: error: (lock.not.held) - o = methodToCall2(o); - // :: error: (lock.not.held) :: error: (assignment.type.incompatible) - p = methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(o); - methodToCall2(p); - } - synchronized (lock1) { - o = methodToCall2(o); - // :: error: (assignment.type.incompatible) - p = methodToCall2(o); - methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(p); - } - - // Test the receiver type matching a parameter - - // Two @GS parameters with no index are incomparable (as is the case for 'this' and 'q'). + // Test the receiver type matching a parameter + + // Two @GS parameters with no index are incomparable (as is the case for 'this' and 'q'). + // :: error: (guardsatisfied.parameters.must.match) + methodToCall3(q); + + // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) + methodToCall3(p); + synchronized (lock1) { + // Two @GS parameters with no index are incomparable (as is the case for 'this' and + // 'q'). + // :: error: (guardsatisfied.parameters.must.match) + methodToCall3(q); + // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) + methodToCall3(p); + synchronized (lock2) { + // Two @GS parameters with no index are incomparable (as is the case for 'this' and + // 'q'). // :: error: (guardsatisfied.parameters.must.match) methodToCall3(q); - - // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) + // :: error: (guardsatisfied.parameters.must.match) methodToCall3(p); - synchronized (lock1) { - // Two @GS parameters with no index are incomparable (as is the case for 'this' and - // 'q'). - // :: error: (guardsatisfied.parameters.must.match) - methodToCall3(q); - // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) - methodToCall3(p); - synchronized (lock2) { - // Two @GS parameters with no index are incomparable (as is the case for 'this' and - // 'q'). - // :: error: (guardsatisfied.parameters.must.match) - methodToCall3(q); - // :: error: (guardsatisfied.parameters.must.match) - methodToCall3(p); - } - } - - // Test the return type matching the receiver type - - methodToCall4(); - } - - // Test the return type NOT matching the receiver type - void testMethodCall(@GuardedBy("lock1") GuardSatisfiedTest this) { - @GuardedBy("lock2") Object g; - // :: error: (lock.not.held) - methodToCall4(); - // TODO: lock.not.held is getting swallowed below - // error (assignment.type.incompatible) error (lock.not.held) - // g = methodToCall4(); - - // Separate the above test case into two for now - // :: error: (lock.not.held) - methodToCall4(); - - // The following error is due to the fact that you cannot access "this.lock1" without first - // having acquired "lock1". The right fix in a user scenario would be to not guard "this" - // with "this.lock1". The current object could instead be guarded by "" or by some - // other lock expression that is not one of its fields. We are keeping this test case here - // to make sure this scenario issues a warning. - // :: error: (lock.not.held) - synchronized (lock1) { - // :: error: (assignment.type.incompatible) - g = methodToCall4(); - } - } - - // :: error: (guardsatisfied.return.must.have.index) - @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex1(@GuardSatisfied Object o) { - // If the two @GuardSatisfied had an index, this error would not be issued: - // :: error: (guardsatisfied.assignment.disallowed) - return o; + } } - @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex2(@GuardSatisfied(1) Object o) { - return o; + // Test the return type matching the receiver type + + methodToCall4(); + } + + // Test the return type NOT matching the receiver type + void testMethodCall(@GuardedBy("lock1") GuardSatisfiedTest this) { + @GuardedBy("lock2") Object g; + // :: error: (lock.not.held) + methodToCall4(); + // TODO: lock.not.held is getting swallowed below + // error (assignment.type.incompatible) error (lock.not.held) + // g = methodToCall4(); + + // Separate the above test case into two for now + // :: error: (lock.not.held) + methodToCall4(); + + // The following error is due to the fact that you cannot access "this.lock1" without first + // having acquired "lock1". The right fix in a user scenario would be to not guard "this" + // with "this.lock1". The current object could instead be guarded by "" or by some + // other lock expression that is not one of its fields. We are keeping this test case here + // to make sure this scenario issues a warning. + // :: error: (lock.not.held) + synchronized (lock1) { + // :: error: (assignment.type.incompatible) + g = methodToCall4(); } - - @GuardSatisfied(0) Object testReturnTypesMustMatchAndMustHaveAnIndex3(@GuardSatisfied(0) Object o) { - return o; - } - - // @GuardSatisfied is equivalent to @GuardSatisfied(-1). - // :: error: (guardsatisfied.return.must.have.index) - @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex4(@GuardSatisfied(-1) Object o) { - // If the two @GuardSatisfied had an index, this error would not be issued: - // :: error: (guardsatisfied.assignment.disallowed) - return o; - } - - @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex5(@GuardSatisfied(2) Object o) { - // :: error: (return.type.incompatible) - return o; - } - - // :: error: (guardsatisfied.return.must.have.index) - @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex6(@GuardSatisfied(2) Object o) { - // :: error: (return.type.incompatible) - return o; - } - - void testParamsMustMatch(@GuardSatisfied(1) Object o, @GuardSatisfied(2) Object p) { - // :: error: (assignment.type.incompatible) + } + + // :: error: (guardsatisfied.return.must.have.index) + @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex1(@GuardSatisfied Object o) { + // If the two @GuardSatisfied had an index, this error would not be issued: + // :: error: (guardsatisfied.assignment.disallowed) + return o; + } + + @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex2(@GuardSatisfied(1) Object o) { + return o; + } + + @GuardSatisfied(0) Object testReturnTypesMustMatchAndMustHaveAnIndex3(@GuardSatisfied(0) Object o) { + return o; + } + + // @GuardSatisfied is equivalent to @GuardSatisfied(-1). + // :: error: (guardsatisfied.return.must.have.index) + @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex4(@GuardSatisfied(-1) Object o) { + // If the two @GuardSatisfied had an index, this error would not be issued: + // :: error: (guardsatisfied.assignment.disallowed) + return o; + } + + @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex5(@GuardSatisfied(2) Object o) { + // :: error: (return.type.incompatible) + return o; + } + + // :: error: (guardsatisfied.return.must.have.index) + @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex6(@GuardSatisfied(2) Object o) { + // :: error: (return.type.incompatible) + return o; + } + + void testParamsMustMatch(@GuardSatisfied(1) Object o, @GuardSatisfied(2) Object p) { + // :: error: (assignment.type.incompatible) + o = p; + } + + void methodToCall1( + @GuardSatisfied GuardSatisfiedTest this, + @GuardSatisfied(1) Object o, + @GuardSatisfied(1) Object p) {} + + @GuardSatisfied(1) Object methodToCall2(@GuardSatisfied GuardSatisfiedTest this, @GuardSatisfied(1) Object o) { + return o; + } + + void methodToCall3(@GuardSatisfied(1) GuardSatisfiedTest this, @GuardSatisfied(1) Object o) {} + + @GuardSatisfied(1) Object methodToCall4(@GuardSatisfied(1) GuardSatisfiedTest this) { + return this; + } + + final Object lock1 = new Object(); + final Object lock2 = new Object(); + + // This method exists to prevent flow-sensitive refinement. + @GuardedBy({"lock1", "lock2"}) Object guardedByLock1Lock2() { + return new Object(); + } + + void testAssignment(@GuardSatisfied Object o) { + @GuardedBy({"lock1", "lock2"}) Object p = guardedByLock1Lock2(); + // :: error: (lock.not.held) + o = p; + synchronized (lock1) { + // :: error: (lock.not.held) + o = p; + synchronized (lock2) { o = p; + } } + } - void methodToCall1( - @GuardSatisfied GuardSatisfiedTest this, - @GuardSatisfied(1) Object o, - @GuardSatisfied(1) Object p) {} - - @GuardSatisfied(1) Object methodToCall2(@GuardSatisfied GuardSatisfiedTest this, @GuardSatisfied(1) Object o) { - return o; - } - - void methodToCall3(@GuardSatisfied(1) GuardSatisfiedTest this, @GuardSatisfied(1) Object o) {} - - @GuardSatisfied(1) Object methodToCall4(@GuardSatisfied(1) GuardSatisfiedTest this) { - return this; - } + // Test disallowed @GuardSatisfied locations. + // Whenever a disallowed location can be located within a method return type, receiver or + // parameter, test it there, because it's important to check that those are not mistakenly + // allowed, since annotations on method return types, receivers and parameters are allowed. By + // definition, fields and non-parameter local variables cannot be in one of these locations on a + // method declaration, but other locations can be. - final Object lock1 = new Object(); - final Object lock2 = new Object(); + // :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied Object field; - // This method exists to prevent flow-sensitive refinement. - @GuardedBy({"lock1", "lock2"}) Object guardedByLock1Lock2() { - return new Object(); - } + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArrayElementAndLocalVariable(@GuardSatisfied Object[] array) { + // :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied Object local; + } - void testAssignment(@GuardSatisfied Object o) { - @GuardedBy({"lock1", "lock2"}) Object p = guardedByLock1Lock2(); - // :: error: (lock.not.held) - o = p; - synchronized (lock1) { - // :: error: (lock.not.held) - o = p; - synchronized (lock2) { - o = p; - } - } - } + // :: error: (guardsatisfied.location.disallowed) + T testGuardSatisfiedOnBound(T t) { + return t; + } - // Test disallowed @GuardSatisfied locations. - // Whenever a disallowed location can be located within a method return type, receiver or - // parameter, test it there, because it's important to check that those are not mistakenly - // allowed, since annotations on method return types, receivers and parameters are allowed. By - // definition, fields and non-parameter local variables cannot be in one of these locations on a - // method declaration, but other locations can be. + class MyParameterizedClass1 { + void testGuardSatisfiedOnReceiverOfParameterizedClass( + @GuardSatisfied MyParameterizedClass1 this) {} - // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied Object field; + void testGuardSatisfiedOnArrayOfParameterizedType( + MyParameterizedClass1 @GuardSatisfied [] array) {} - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArrayElementAndLocalVariable(@GuardSatisfied Object[] array) { + void testGuardSatisfiedOnArrayComponentOfParameterizedType( // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied Object local; - } + @GuardSatisfied MyParameterizedClass1[] array) {} + } - // :: error: (guardsatisfied.location.disallowed) - T testGuardSatisfiedOnBound(T t) { - return t; - } + void testGuardSatisfiedOnWildCardExtendsBound( + // :: error: (guardsatisfied.location.disallowed) + MyParameterizedClass1 l) {} - class MyParameterizedClass1 { - void testGuardSatisfiedOnReceiverOfParameterizedClass( - @GuardSatisfied MyParameterizedClass1 this) {} + void testGuardSatisfiedOnWildCardSuperBound( + // :: error: (guardsatisfied.location.disallowed) + MyParameterizedClass1 l) {} - void testGuardSatisfiedOnArrayOfParameterizedType( - MyParameterizedClass1 @GuardSatisfied [] array) {} + @GuardSatisfied(1) Object testGuardSatisfiedOnParameters( + @GuardSatisfied GuardSatisfiedTest this, + Object @GuardSatisfied [] array, + @GuardSatisfied Object param, + @GuardSatisfied(1) Object param2) { + return param2; + } - void testGuardSatisfiedOnArrayComponentOfParameterizedType( - // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied MyParameterizedClass1[] array) {} - } + void testGuardSatisfiedOnArray1(Object @GuardSatisfied [][][] array) {} - void testGuardSatisfiedOnWildCardExtendsBound( - // :: error: (guardsatisfied.location.disallowed) - MyParameterizedClass1 l) {} + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArray2(@GuardSatisfied Object[][][] array) {} - void testGuardSatisfiedOnWildCardSuperBound( - // :: error: (guardsatisfied.location.disallowed) - MyParameterizedClass1 l) {} + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArray3(Object[] @GuardSatisfied [][] array) {} - @GuardSatisfied(1) Object testGuardSatisfiedOnParameters( - @GuardSatisfied GuardSatisfiedTest this, - Object @GuardSatisfied [] array, - @GuardSatisfied Object param, - @GuardSatisfied(1) Object param2) { - return param2; - } - - void testGuardSatisfiedOnArray1(Object @GuardSatisfied [][][] array) {} - - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArray2(@GuardSatisfied Object[][][] array) {} - - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArray3(Object[] @GuardSatisfied [][] array) {} - - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArray4(Object[][] @GuardSatisfied [] array) {} + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArray4(Object[][] @GuardSatisfied [] array) {} } class Foo { - @MayReleaseLocks - void m1() {} - - @MayReleaseLocks - // :: error: (guardsatisfied.with.mayreleaselocks) - void m2(@GuardSatisfied Foo f) { - // :: error: (method.invocation.invalid) - f.m1(); - } - - @MayReleaseLocks - void m2_2(Foo f) { - f.m1(); - } - - void m3(@GuardSatisfied Foo f) { - // :: error: (method.guarantee.violated) :: error: (method.invocation.invalid) - f.m1(); - } - - @MayReleaseLocks - void m4(Foo f) { - f.m1(); - } - - @MayReleaseLocks - void m5(Foo f) { - m3(f); - } + @MayReleaseLocks + void m1() {} + + @MayReleaseLocks + // :: error: (guardsatisfied.with.mayreleaselocks) + void m2(@GuardSatisfied Foo f) { + // :: error: (method.invocation.invalid) + f.m1(); + } + + @MayReleaseLocks + void m2_2(Foo f) { + f.m1(); + } + + void m3(@GuardSatisfied Foo f) { + // :: error: (method.guarantee.violated) :: error: (method.invocation.invalid) + f.m1(); + } + + @MayReleaseLocks + void m4(Foo f) { + f.m1(); + } + + @MayReleaseLocks + void m5(Foo f) { + m3(f); + } } diff --git a/checker/tests/lock/GuardedByLocalVariable.java b/checker/tests/lock/GuardedByLocalVariable.java index 083067823e4..311b368ff0e 100644 --- a/checker/tests/lock/GuardedByLocalVariable.java +++ b/checker/tests/lock/GuardedByLocalVariable.java @@ -1,40 +1,38 @@ // Test for Checker Framework issue 795 // https://github.com/typetools/checker-framework/issues/795 -import org.checkerframework.checker.lock.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.lock.qual.*; public class GuardedByLocalVariable { - public static void localVariableShadowing() { - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("m0") Object kk; - { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - final Map m0 = someValue(); - @GuardedBy("m0") Object k = "key"; - // If the type of kk were legal, this assignment would be illegal because the two - // instances of "m0" would refer to different variables. - kk = k; - } - { - @SuppressWarnings( - "assignment.type.incompatible") // prevent flow-sensitive type refinement - final Map m0 = someValue(); - // If the type of kk were legal, this assignment would be illegal because the two - // instances of "m0" would refer to different variables. - @GuardedBy("m0") Object k2 = kk; - } + public static void localVariableShadowing() { + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("m0") Object kk; + { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + final Map m0 = someValue(); + @GuardedBy("m0") Object k = "key"; + // If the type of kk were legal, this assignment would be illegal because the two + // instances of "m0" would refer to different variables. + kk = k; } - - public static void invalidLocalVariable() { - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("foobar") Object kk; + { + @SuppressWarnings("assignment.type.incompatible") // prevent flow-sensitive type refinement + final Map m0 = someValue(); + // If the type of kk were legal, this assignment would be illegal because the two + // instances of "m0" would refer to different variables. + @GuardedBy("m0") Object k2 = kk; } + } - static @GuardedByUnknown Map someValue() { - return new HashMap<>(); - } + public static void invalidLocalVariable() { + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("foobar") Object kk; + } + + static @GuardedByUnknown Map someValue() { + return new HashMap<>(); + } } diff --git a/checker/tests/lock/Issue152.java b/checker/tests/lock/Issue152.java index 8606d146ec8..84477e5bb6c 100644 --- a/checker/tests/lock/Issue152.java +++ b/checker/tests/lock/Issue152.java @@ -5,33 +5,33 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class Issue152 { - static class SuperClass { - protected final Object myLock = new Object(); + static class SuperClass { + protected final Object myLock = new Object(); - private @GuardedBy("myLock") Object locked; - } + private @GuardedBy("myLock") Object locked; + } - static class SubClass extends SuperClass { - private final Object myLock = new Object(); + static class SubClass extends SuperClass { + private final Object myLock = new Object(); - private @GuardedBy("myLock") Object locked; + private @GuardedBy("myLock") Object locked; - void method() { - // :: error: (assignment.type.incompatible) - this.locked = super.locked; - } + void method() { + // :: error: (assignment.type.incompatible) + this.locked = super.locked; } + } - class OuterClass { - private final Object lock = new Object(); + class OuterClass { + private final Object lock = new Object(); - @GuardedBy("this.lock") Object field; + @GuardedBy("this.lock") Object field; - class InnerClass { - private final Object lock = new Object(); + class InnerClass { + private final Object lock = new Object(); - // :: error: (assignment.type.incompatible) - @GuardedBy("this.lock") Object field2 = field; - } + // :: error: (assignment.type.incompatible) + @GuardedBy("this.lock") Object field2 = field; } + } } diff --git a/checker/tests/lock/Issue2163Lock.java b/checker/tests/lock/Issue2163Lock.java index 0b73b90922d..10695205d49 100644 --- a/checker/tests/lock/Issue2163Lock.java +++ b/checker/tests/lock/Issue2163Lock.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.lock.qual.*; public class Issue2163Lock { - @GuardedBy Issue2163Lock() {} + @GuardedBy Issue2163Lock() {} - void test() { - // :: error: (constructor.invocation.invalid) :: error: (guardsatisfied.location.disallowed) - new @GuardSatisfied Issue2163Lock(); - } + void test() { + // :: error: (constructor.invocation.invalid) :: error: (guardsatisfied.location.disallowed) + new @GuardSatisfied Issue2163Lock(); + } } diff --git a/checker/tests/lock/Issue523.java b/checker/tests/lock/Issue523.java index 1a29887ddba..96f2a06a828 100644 --- a/checker/tests/lock/Issue523.java +++ b/checker/tests/lock/Issue523.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.lock.qual.*; public class Issue523 { - static class MyClass { - Object field; - } + static class MyClass { + Object field; + } - static final @GuardedBy("") MyClass m = new MyClass(); + static final @GuardedBy("") MyClass m = new MyClass(); - static void foo() { - Thread t = - new Thread() { - public void run() { - synchronized (m) { - m.field = new Object(); - } - } - }; - } + static void foo() { + Thread t = + new Thread() { + public void run() { + synchronized (m) { + m.field = new Object(); + } + } + }; + } } diff --git a/checker/tests/lock/Issue524.java b/checker/tests/lock/Issue524.java index 6a6a43b328e..8e3b82651e6 100644 --- a/checker/tests/lock/Issue524.java +++ b/checker/tests/lock/Issue524.java @@ -1,11 +1,10 @@ // Test case for Issue 524: // https://github.com/typetools/checker-framework/issues/524 +import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByUnknown; -import java.util.concurrent.locks.ReentrantLock; - // WARNING: this test is nondeterministic, and has already been minimized - if you modify it by // removing what appears to be redundant code, it may no longer reproduce the issue or provide // coverage for the issue after a fix for the issue has been made. @@ -18,28 +17,28 @@ // Unfortunately a test case that always fails to typecheck using a Checker Framework build // prior to the fix for issue 524 has not been found. public class Issue524 { - class MyClass { - public Object field; - } + class MyClass { + public Object field; + } - @GuardedByUnknown MyClass someValue() { - return new MyClass(); - } + @GuardedByUnknown MyClass someValue() { + return new MyClass(); + } - void testLocalVariables() { - @GuardedBy({}) ReentrantLock localLock = new ReentrantLock(); + void testLocalVariables() { + @GuardedBy({}) ReentrantLock localLock = new ReentrantLock(); - { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - // :: error: (lock.expression.not.final) - @GuardedBy("localLock") MyClass q = someValue(); - localLock.lock(); - localLock.lock(); - // Without a fix for issue 524 in place, the error lock.not.held - // (unguarded access to field, variable or parameter 'q' guarded by 'localLock') is - // issued for the following line. - // :: error: (expression.unparsable.type.invalid) - q.field.toString(); - } + { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + // :: error: (lock.expression.not.final) + @GuardedBy("localLock") MyClass q = someValue(); + localLock.lock(); + localLock.lock(); + // Without a fix for issue 524 in place, the error lock.not.held + // (unguarded access to field, variable or parameter 'q' guarded by 'localLock') is + // issued for the following line. + // :: error: (expression.unparsable.type.invalid) + q.field.toString(); } + } } diff --git a/checker/tests/lock/Issue753.java b/checker/tests/lock/Issue753.java index 3a64bc53134..b5826062e84 100644 --- a/checker/tests/lock/Issue753.java +++ b/checker/tests/lock/Issue753.java @@ -1,128 +1,127 @@ // Test case for Issue 753: // https://github.com/typetools/checker-framework/issues/753 +import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.*; import org.checkerframework.dataflow.qual.*; -import java.util.concurrent.locks.ReentrantLock; - @SuppressWarnings({ - "purity", - "contracts.precondition.not.satisfied", - "lock.expression.possibly.not.final" + "purity", + "contracts.precondition.not.satisfied", + "lock.expression.possibly.not.final" }) // Only test parsing public class Issue753 extends ReentrantLock { - final Issue753 field = new Issue753(); - final Issue753[] fields = {this, field}; - final Issue753[][] fieldsArray = {fields}; - final int zero = 0; - final int[] zeros = {0}; - - @Pure - Issue753 getField(Object param) { - return field; - } - - @Pure - Issue753 getField2() { - return field; - } - - @Pure - Issue753 getField3(String str) { - return field; - } - - @Pure - Issue753[] getFields() { - return fields; - } - - @Pure - Issue753[][] getFieldsArray() { - return fieldsArray; - } - - @Pure - int length(String str) { - return str.length(); - } - - @Pure - int[] zeros() { - return zeros; - } - - void method() { - getField(field.field).field.lock(); - method2(); - getField(field.field).getField(field.field).field.lock(); - method3(); - getField(field.field).getField2().field.lock(); - method4(); - getField2().getField2().field.lock(); - method5(); - getField2().getField2().lock(); - method6(); - getField(getField(getField2()).field).field.lock(); - method7(); - this.getField3(")(in string.;))\")(still so.)\"").field.lock(); - method8(); - this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]].lock(); - method9(); - this.fieldsArray[length("[")][length("[")].fields[length("[")].field.lock(); - method10(); - this.fieldsArray["[".length()]["[".length()].fields["[".length()].field.lock(); - method11(); - this.getFields()[this.zero].field.lock(); - method12(); - this.getFieldsArray()[this.getField2().zero][this.zero].field.lock(); - method13(); - this.getFields()["][in string.;)]\"][still so.]\"".length()].field.lock(); - method14(); - this.fields[(("][in string.;)]\"][still so.]\"").length())].field.lock(); - method15(); - } - - @Holding("this.getField(this.field.field).field") - void method2() {} - - @Holding("this.getField(this.field.field).getField(this.field.field).field") - void method3() {} - - @Holding("this.getField(this.field.field).getField2().field") - void method4() {} - - @Holding("this.getField2().getField2().field") - void method5() {} - - @Holding("this.getField2().getField2()") - void method6() {} - - @Holding("this.getField(this.getField(this.getField2()).field).field") - void method7() {} - - @Holding("this.getField3(\")(in string.;))\\\")(still so.)\\\"\").field") - void method8() {} - - @Holding("this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]]") - void method9() {} - - @Holding("this.fieldsArray[length(\"[\")][length(\"[\")].fields[length(\"[\")].field") - void method10() {} - - @Holding("this.fieldsArray[\"[\".length()][\"[\".length()].fields[\"[\".length()].field") - void method11() {} - - @Holding("this.getFields()[this.zero].field") - void method12() {} - - @Holding("this.getFieldsArray()[this.getField2().zero][this.zero].field") - void method13() {} - - @Holding("this.getFields()[\"][in string.;)]\\\"][still so.]\\\"\".length()].field") - void method14() {} - - @Holding("this.fields[((\"][in string.;)]\\\"][still so.]\\\"\").length())].field") - void method15() {} + final Issue753 field = new Issue753(); + final Issue753[] fields = {this, field}; + final Issue753[][] fieldsArray = {fields}; + final int zero = 0; + final int[] zeros = {0}; + + @Pure + Issue753 getField(Object param) { + return field; + } + + @Pure + Issue753 getField2() { + return field; + } + + @Pure + Issue753 getField3(String str) { + return field; + } + + @Pure + Issue753[] getFields() { + return fields; + } + + @Pure + Issue753[][] getFieldsArray() { + return fieldsArray; + } + + @Pure + int length(String str) { + return str.length(); + } + + @Pure + int[] zeros() { + return zeros; + } + + void method() { + getField(field.field).field.lock(); + method2(); + getField(field.field).getField(field.field).field.lock(); + method3(); + getField(field.field).getField2().field.lock(); + method4(); + getField2().getField2().field.lock(); + method5(); + getField2().getField2().lock(); + method6(); + getField(getField(getField2()).field).field.lock(); + method7(); + this.getField3(")(in string.;))\")(still so.)\"").field.lock(); + method8(); + this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]].lock(); + method9(); + this.fieldsArray[length("[")][length("[")].fields[length("[")].field.lock(); + method10(); + this.fieldsArray["[".length()]["[".length()].fields["[".length()].field.lock(); + method11(); + this.getFields()[this.zero].field.lock(); + method12(); + this.getFieldsArray()[this.getField2().zero][this.zero].field.lock(); + method13(); + this.getFields()["][in string.;)]\"][still so.]\"".length()].field.lock(); + method14(); + this.fields[(("][in string.;)]\"][still so.]\"").length())].field.lock(); + method15(); + } + + @Holding("this.getField(this.field.field).field") + void method2() {} + + @Holding("this.getField(this.field.field).getField(this.field.field).field") + void method3() {} + + @Holding("this.getField(this.field.field).getField2().field") + void method4() {} + + @Holding("this.getField2().getField2().field") + void method5() {} + + @Holding("this.getField2().getField2()") + void method6() {} + + @Holding("this.getField(this.getField(this.getField2()).field).field") + void method7() {} + + @Holding("this.getField3(\")(in string.;))\\\")(still so.)\\\"\").field") + void method8() {} + + @Holding("this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]]") + void method9() {} + + @Holding("this.fieldsArray[length(\"[\")][length(\"[\")].fields[length(\"[\")].field") + void method10() {} + + @Holding("this.fieldsArray[\"[\".length()][\"[\".length()].fields[\"[\".length()].field") + void method11() {} + + @Holding("this.getFields()[this.zero].field") + void method12() {} + + @Holding("this.getFieldsArray()[this.getField2().zero][this.zero].field") + void method13() {} + + @Holding("this.getFields()[\"][in string.;)]\\\"][still so.]\\\"\".length()].field") + void method14() {} + + @Holding("this.fields[((\"][in string.;)]\\\"][still so.]\\\"\").length())].field") + void method15() {} } diff --git a/checker/tests/lock/Issue804.java b/checker/tests/lock/Issue804.java index 122b8a724af..220e1d18dbd 100644 --- a/checker/tests/lock/Issue804.java +++ b/checker/tests/lock/Issue804.java @@ -1,22 +1,21 @@ // Test case for Issue 804: // https://github.com/typetools/checker-framework/issues/804 -import org.checkerframework.checker.lock.qual.*; - import java.util.concurrent.locks.*; +import org.checkerframework.checker.lock.qual.*; public class Issue804 extends ReentrantLock { - @Holding("this") - @MayReleaseLocks - void bar() { - this.unlock(); - } + @Holding("this") + @MayReleaseLocks + void bar() { + this.unlock(); + } - @Holding("this") - @MayReleaseLocks - void method() { - bar(); - // :: error: (contracts.precondition.not.satisfied) - bar(); - } + @Holding("this") + @MayReleaseLocks + void method() { + bar(); + // :: error: (contracts.precondition.not.satisfied) + bar(); + } } diff --git a/checker/tests/lock/Issue805.java b/checker/tests/lock/Issue805.java index 6a721e772c4..301212d83fe 100644 --- a/checker/tests/lock/Issue805.java +++ b/checker/tests/lock/Issue805.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.lock.qual.Holding; public class Issue805 { - @Holding("this.Issue805.class") - // :: error: (flowexpr.parse.error) - void method() {} + @Holding("this.Issue805.class") + // :: error: (flowexpr.parse.error) + void method() {} - @Holding("Issue805.class") - void method2() {} + @Holding("Issue805.class") + void method2() {} - @Holding("java.lang.String.class") - void method3() {} + @Holding("java.lang.String.class") + void method3() {} } diff --git a/checker/tests/lock/ItselfExpressionCases.java b/checker/tests/lock/ItselfExpressionCases.java index dc09b416af9..4c1231dc222 100644 --- a/checker/tests/lock/ItselfExpressionCases.java +++ b/checker/tests/lock/ItselfExpressionCases.java @@ -3,142 +3,142 @@ import org.checkerframework.dataflow.qual.*; public class ItselfExpressionCases { - final Object somelock = new Object(); - - private @GuardedBy({""}) MyClass guardedBySelf() { - return new MyClass(); + final Object somelock = new Object(); + + private @GuardedBy({""}) MyClass guardedBySelf() { + return new MyClass(); + } + + private final @GuardedBy({""}) MyClass m = guardedBySelf(); + + @Pure + private @GuardedBy({""}) MyClass getm() { + return m; + } + + @Pure + private @GuardedBy({""}) MyClass getm2(@GuardedBy("") ItselfExpressionCases this) { + // The following error is due to the precondition of the this.m field dereference not being + // satisfied. + // :: error: (lock.not.held) + return m; + } + + @Pure + private Object getmfield() { + // :: error: (lock.not.held) + return getm().field; + } + + public void arrayTest(final Object @GuardedBy("") [] a1) { + // :: error: (lock.not.held) + Object a = a1[0]; + synchronized (a1) { + a = a1[0]; } + } - private final @GuardedBy({""}) MyClass m = guardedBySelf(); + Object @GuardedBy("") [] a2; - @Pure - private @GuardedBy({""}) MyClass getm() { - return m; - } + @Pure + public Object @GuardedBy("") [] geta2() { + return a2; + } - @Pure - private @GuardedBy({""}) MyClass getm2(@GuardedBy("") ItselfExpressionCases this) { - // The following error is due to the precondition of the this.m field dereference not being - // satisfied. - // :: error: (lock.not.held) - return m; + public void arrayTest() { + // :: error: (lock.not.held) + Object a = geta2()[0]; + synchronized (geta2()) { + a = geta2()[0]; + } + } + + public void testCheckPreconditions( + final @GuardedBy("") MyClass o, + @GuardSatisfied Object gs, + @GuardSatisfied MyClass gsMyClass) { + // :: error: (lock.not.held) + getm().field = new Object(); + synchronized (getm()) { + getm().field = new Object(); } - @Pure - private Object getmfield() { - // :: error: (lock.not.held) - return getm().field; + // :: error: (lock.not.held) + m.field = new Object(); + synchronized (m) { + m.field = new Object(); } - public void arrayTest(final Object @GuardedBy("") [] a1) { - // :: error: (lock.not.held) - Object a = a1[0]; - synchronized (a1) { - a = a1[0]; - } + // :: error: (lock.not.held) + gs = m.field; + synchronized (m) { + gs = m.field; } - Object @GuardedBy("") [] a2; + // :: error: (lock.not.held) + gs = getm().field; + synchronized (getm()) { + gs = getm().field; + } - @Pure - public Object @GuardedBy("") [] geta2() { - return a2; + // :: error: (lock.not.held) + gsMyClass = getm(); + synchronized (getm()) { + gsMyClass = getm(); } - public void arrayTest() { - // :: error: (lock.not.held) - Object a = geta2()[0]; - synchronized (geta2()) { - a = geta2()[0]; - } + // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) + o.foo(); + synchronized (o) { + // :: error: (contracts.precondition.not.satisfied) + o.foo(); + synchronized (somelock) { + // o.foo() requires o.somelock is held, not this.somelock. + // :: error: (contracts.precondition.not.satisfied) + o.foo(); + } } - public void testCheckPreconditions( - final @GuardedBy("") MyClass o, - @GuardSatisfied Object gs, - @GuardSatisfied MyClass gsMyClass) { - // :: error: (lock.not.held) - getm().field = new Object(); - synchronized (getm()) { - getm().field = new Object(); - } + // :: error: (lock.not.held) + o.foo2(); + synchronized (o) { + o.foo2(); + } + } - // :: error: (lock.not.held) - m.field = new Object(); - synchronized (m) { - m.field = new Object(); - } + class MyClass { + Object field = new Object(); - // :: error: (lock.not.held) - gs = m.field; - synchronized (m) { - gs = m.field; - } + @Holding("somelock") + void foo(@GuardSatisfied MyClass this) {} - // :: error: (lock.not.held) - gs = getm().field; - synchronized (getm()) { - gs = getm().field; - } + void foo2(@GuardSatisfied MyClass this) {} + void method(@GuardedBy("") MyClass this) { + // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) + this.foo(); + // :: error: (lock.not.held):: error: (contracts.precondition.not.satisfied) + foo(); + // :: error: (lock.not.held) + synchronized (somelock) { // :: error: (lock.not.held) - gsMyClass = getm(); - synchronized (getm()) { - gsMyClass = getm(); - } - - // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) - o.foo(); - synchronized (o) { - // :: error: (contracts.precondition.not.satisfied) - o.foo(); - synchronized (somelock) { - // o.foo() requires o.somelock is held, not this.somelock. - // :: error: (contracts.precondition.not.satisfied) - o.foo(); - } - } - + this.foo(); // :: error: (lock.not.held) - o.foo2(); - synchronized (o) { - o.foo2(); - } - } - - class MyClass { - Object field = new Object(); - - @Holding("somelock") - void foo(@GuardSatisfied MyClass this) {} - - void foo2(@GuardSatisfied MyClass this) {} - - void method(@GuardedBy("") MyClass this) { - // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) - this.foo(); - // :: error: (lock.not.held):: error: (contracts.precondition.not.satisfied) - foo(); - // :: error: (lock.not.held) - synchronized (somelock) { - // :: error: (lock.not.held) - this.foo(); - // :: error: (lock.not.held) - foo(); - synchronized (this) { - this.foo(); - foo(); - } - } - - // :: error: (lock.not.held) - this.foo2(); - // :: error: (lock.not.held) - foo2(); - synchronized (this) { - this.foo2(); - foo2(); - } + foo(); + synchronized (this) { + this.foo(); + foo(); } + } + + // :: error: (lock.not.held) + this.foo2(); + // :: error: (lock.not.held) + foo2(); + synchronized (this) { + this.foo2(); + foo2(); + } } + } } diff --git a/checker/tests/lock/JCIPAnnotations.java b/checker/tests/lock/JCIPAnnotations.java index 57c7fbbc4ae..0b8ef42932e 100644 --- a/checker/tests/lock/JCIPAnnotations.java +++ b/checker/tests/lock/JCIPAnnotations.java @@ -1,115 +1,114 @@ +import net.jcip.annotations.GuardedBy; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.lock.qual.Holding; import org.checkerframework.checker.lock.qual.LockingFree; -import net.jcip.annotations.GuardedBy; - // Smoke test for supporting JCIP and Javax annotations. // Note that JCIP and Javax @GuardedBy can only be written on fields and methods. public class JCIPAnnotations { - class MyClass { - Object field; - - @LockingFree - void methodWithUnguardedReceiver() {} - - @LockingFree - void methodWithGuardedReceiver( - @org.checkerframework.checker.lock.qual.GuardedBy("lock") MyClass this) {} - - @LockingFree - void methodWithGuardSatisfiedReceiver(@GuardSatisfied MyClass this) {} + class MyClass { + Object field; + + @LockingFree + void methodWithUnguardedReceiver() {} + + @LockingFree + void methodWithGuardedReceiver( + @org.checkerframework.checker.lock.qual.GuardedBy("lock") MyClass this) {} + + @LockingFree + void methodWithGuardSatisfiedReceiver(@GuardSatisfied MyClass this) {} + } + + final Object lock = new Object(); + + @GuardedBy("lock") MyClass jcipGuardedField; + + @javax.annotation.concurrent.GuardedBy("lock") MyClass javaxGuardedField; + + // Tests that Javax and JCIP @GuardedBy(...) typecheck against the Lock Checker @GuardedBy on a + // receiver. + void testReceivers() { + // :: error: (method.invocation.invalid) + jcipGuardedField.methodWithUnguardedReceiver(); + // :: error: (method.invocation.invalid) + jcipGuardedField.methodWithGuardedReceiver(); + // :: error: (lock.not.held) + jcipGuardedField.methodWithGuardSatisfiedReceiver(); + // :: error: (method.invocation.invalid) + javaxGuardedField.methodWithUnguardedReceiver(); + // :: error: (method.invocation.invalid) + javaxGuardedField.methodWithGuardedReceiver(); + // :: error: (lock.not.held) + javaxGuardedField.methodWithGuardSatisfiedReceiver(); + } + + void testDereferences() { + // :: error: (lock.not.held) + this.jcipGuardedField.field.toString(); + // :: error: (lock.not.held) + this.javaxGuardedField.field.toString(); + synchronized (lock) { + this.jcipGuardedField.field.toString(); + this.javaxGuardedField.field.toString(); } - - final Object lock = new Object(); - - @GuardedBy("lock") MyClass jcipGuardedField; - - @javax.annotation.concurrent.GuardedBy("lock") MyClass javaxGuardedField; - - // Tests that Javax and JCIP @GuardedBy(...) typecheck against the Lock Checker @GuardedBy on a - // receiver. - void testReceivers() { - // :: error: (method.invocation.invalid) - jcipGuardedField.methodWithUnguardedReceiver(); - // :: error: (method.invocation.invalid) - jcipGuardedField.methodWithGuardedReceiver(); - // :: error: (lock.not.held) - jcipGuardedField.methodWithGuardSatisfiedReceiver(); - // :: error: (method.invocation.invalid) - javaxGuardedField.methodWithUnguardedReceiver(); - // :: error: (method.invocation.invalid) - javaxGuardedField.methodWithGuardedReceiver(); - // :: error: (lock.not.held) - javaxGuardedField.methodWithGuardSatisfiedReceiver(); - } - - void testDereferences() { - // :: error: (lock.not.held) - this.jcipGuardedField.field.toString(); - // :: error: (lock.not.held) - this.javaxGuardedField.field.toString(); - synchronized (lock) { - this.jcipGuardedField.field.toString(); - this.javaxGuardedField.field.toString(); - } + } + + @GuardedBy("lock") + void testGuardedByAsHolding() { + this.jcipGuardedField.field.toString(); + this.javaxGuardedField.field.toString(); + jcipGuardedField.field.toString(); + javaxGuardedField.field.toString(); + } + + @GuardedBy("lock") Object testGuardedByAsHolding2( + @org.checkerframework.checker.lock.qual.GuardedBy({}) Object param) { + testGuardedByAsHolding(); + // Test that the JCIP GuardedBy applies to the method but not the return type. + return param; + } + + void testGuardedByAsHolding3() { + synchronized (lock) { + testGuardedByAsHolding(); } - - @GuardedBy("lock") - void testGuardedByAsHolding() { - this.jcipGuardedField.field.toString(); - this.javaxGuardedField.field.toString(); - jcipGuardedField.field.toString(); - javaxGuardedField.field.toString(); - } - - @GuardedBy("lock") Object testGuardedByAsHolding2( - @org.checkerframework.checker.lock.qual.GuardedBy({}) Object param) { - testGuardedByAsHolding(); - // Test that the JCIP GuardedBy applies to the method but not the return type. - return param; - } - - void testGuardedByAsHolding3() { - synchronized (lock) { - testGuardedByAsHolding(); - } - // :: error: (contracts.precondition.not.satisfied) - testGuardedByAsHolding(); - } - - @Holding("lock") - @GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations1() {} - - @Holding("lock") - @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations2() {} - - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations3() {} - - @Holding("lock") - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations4() {} - - @GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations1; - - @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations2; - - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations3; - - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations4; + // :: error: (contracts.precondition.not.satisfied) + testGuardedByAsHolding(); + } + + @Holding("lock") + @GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations1() {} + + @Holding("lock") + @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations2() {} + + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations3() {} + + @Holding("lock") + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations4() {} + + @GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations1; + + @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations2; + + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations3; + + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations4; } diff --git a/checker/tests/lock/LockEffectAnnotations.java b/checker/tests/lock/LockEffectAnnotations.java index 7f4bcddc817..c37479bf0df 100644 --- a/checker/tests/lock/LockEffectAnnotations.java +++ b/checker/tests/lock/LockEffectAnnotations.java @@ -1,3 +1,4 @@ +import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByBottom; @@ -8,147 +9,144 @@ import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; -import java.util.concurrent.locks.ReentrantLock; - public class LockEffectAnnotations { - class MyClass { - Object field = new Object(); - } - - private @GuardedBy({}) MyClass myField; - - private final ReentrantLock myLock2 = new ReentrantLock(); - private @GuardedBy("myLock2") MyClass x3; - - // This method does not use locks or synchronization but cannot - // be annotated as @SideEffectFree since it alters myField. - @LockingFree - void myMethod5() { - myField = new MyClass(); - } - - @SideEffectFree - int mySideEffectFreeMethod() { - return 0; + class MyClass { + Object field = new Object(); + } + + private @GuardedBy({}) MyClass myField; + + private final ReentrantLock myLock2 = new ReentrantLock(); + private @GuardedBy("myLock2") MyClass x3; + + // This method does not use locks or synchronization but cannot + // be annotated as @SideEffectFree since it alters myField. + @LockingFree + void myMethod5() { + myField = new MyClass(); + } + + @SideEffectFree + int mySideEffectFreeMethod() { + return 0; + } + + @MayReleaseLocks + void myUnlockingMethod() { + myLock2.unlock(); + } + + @MayReleaseLocks + void myReleaseLocksEmptyMethod() {} + + @MayReleaseLocks + // :: error: (guardsatisfied.with.mayreleaselocks) + void methodGuardSatisfiedReceiver(@GuardSatisfied LockEffectAnnotations this) {} + + @MayReleaseLocks + // :: error: (guardsatisfied.with.mayreleaselocks) + void methodGuardSatisfiedParameter(@GuardSatisfied Object o) {} + + @MayReleaseLocks + void myOtherMethod() { + if (myLock2.tryLock()) { + x3.field = new Object(); // OK: the lock is held + myMethod5(); + x3.field = new Object(); // OK: the lock is still held since myMethod is locking-free + mySideEffectFreeMethod(); + x3.field = new Object(); // OK: the lock is still held since mySideEffectFreeMethod is + // side-effect-free + myUnlockingMethod(); + // :: error: (lock.not.held) + x3.field = new Object(); // ILLEGAL: myLockingMethod is not locking-free } - - @MayReleaseLocks - void myUnlockingMethod() { - myLock2.unlock(); + if (myLock2.tryLock()) { + x3.field = new Object(); // OK: the lock is held + myReleaseLocksEmptyMethod(); + // :: error: (lock.not.held) + x3.field = new Object(); // ILLEGAL: even though myUnannotatedEmptyMethod is empty, since + // myReleaseLocksEmptyMethod() is annotated with @MayReleaseLocks and the Lock Checker + // no longer knows the state of the lock. + if (myLock2.isHeldByCurrentThread()) { + x3.field = new Object(); // OK: the lock is known to be held + } } + } - @MayReleaseLocks - void myReleaseLocksEmptyMethod() {} - - @MayReleaseLocks - // :: error: (guardsatisfied.with.mayreleaselocks) - void methodGuardSatisfiedReceiver(@GuardSatisfied LockEffectAnnotations this) {} - - @MayReleaseLocks - // :: error: (guardsatisfied.with.mayreleaselocks) - void methodGuardSatisfiedParameter(@GuardSatisfied Object o) {} - - @MayReleaseLocks - void myOtherMethod() { - if (myLock2.tryLock()) { - x3.field = new Object(); // OK: the lock is held - myMethod5(); - x3.field = new Object(); // OK: the lock is still held since myMethod is locking-free - mySideEffectFreeMethod(); - x3.field = new Object(); // OK: the lock is still held since mySideEffectFreeMethod is - // side-effect-free - myUnlockingMethod(); - // :: error: (lock.not.held) - x3.field = new Object(); // ILLEGAL: myLockingMethod is not locking-free - } - if (myLock2.tryLock()) { - x3.field = new Object(); // OK: the lock is held - myReleaseLocksEmptyMethod(); - // :: error: (lock.not.held) - x3.field = - new Object(); // ILLEGAL: even though myUnannotatedEmptyMethod is empty, since - // myReleaseLocksEmptyMethod() is annotated with @MayReleaseLocks and the Lock Checker - // no longer knows the state of the lock. - if (myLock2.isHeldByCurrentThread()) { - x3.field = new Object(); // OK: the lock is known to be held - } - } + @ReleasesNoLocks + void innerClassTest() { + class InnerClass { + @MayReleaseLocks + void innerClassMethod() {} } - @ReleasesNoLocks - void innerClassTest() { - class InnerClass { - @MayReleaseLocks - void innerClassMethod() {} - } - - InnerClass ic = new InnerClass(); - // :: error: (method.guarantee.violated) - ic.innerClassMethod(); - } + InnerClass ic = new InnerClass(); + // :: error: (method.guarantee.violated) + ic.innerClassMethod(); + } - @MayReleaseLocks - synchronized void mayReleaseLocksSynchronizedMethod() {} + @MayReleaseLocks + synchronized void mayReleaseLocksSynchronizedMethod() {} - @ReleasesNoLocks - synchronized void releasesNoLocksSynchronizedMethod() {} + @ReleasesNoLocks + synchronized void releasesNoLocksSynchronizedMethod() {} - @LockingFree - // :: error: (lockingfree.synchronized.method) - synchronized void lockingFreeSynchronizedMethod() {} + @LockingFree + // :: error: (lockingfree.synchronized.method) + synchronized void lockingFreeSynchronizedMethod() {} - @SideEffectFree - // :: error: (lockingfree.synchronized.method) - synchronized void sideEffectFreeSynchronizedMethod() {} + @SideEffectFree + // :: error: (lockingfree.synchronized.method) + synchronized void sideEffectFreeSynchronizedMethod() {} - @Pure - // :: error: (lockingfree.synchronized.method) - synchronized void pureSynchronizedMethod() {} + @Pure + // :: error: (lockingfree.synchronized.method) + synchronized void pureSynchronizedMethod() {} - @MayReleaseLocks - void mayReleaseLocksMethodWithSynchronizedBlock() { - synchronized (this) { - } + @MayReleaseLocks + void mayReleaseLocksMethodWithSynchronizedBlock() { + synchronized (this) { } + } - @ReleasesNoLocks - void releasesNoLocksMethodWithSynchronizedBlock() { - synchronized (this) { - } + @ReleasesNoLocks + void releasesNoLocksMethodWithSynchronizedBlock() { + synchronized (this) { } + } - @LockingFree - void lockingFreeMethodWithSynchronizedBlock() { - // :: error: (synchronized.block.in.lockingfree.method) - synchronized (this) { - } + @LockingFree + void lockingFreeMethodWithSynchronizedBlock() { + // :: error: (synchronized.block.in.lockingfree.method) + synchronized (this) { } + } - @SideEffectFree - void sideEffectFreeMethodWithSynchronizedBlock() { - // :: error: (synchronized.block.in.lockingfree.method) - synchronized (this) { - } + @SideEffectFree + void sideEffectFreeMethodWithSynchronizedBlock() { + // :: error: (synchronized.block.in.lockingfree.method) + synchronized (this) { } + } - @Pure - void pureMethodWithSynchronizedBlock() { - // :: error: (synchronized.block.in.lockingfree.method) - synchronized (this) { - } + @Pure + void pureMethodWithSynchronizedBlock() { + // :: error: (synchronized.block.in.lockingfree.method) + synchronized (this) { } + } - // :: warning: (inconsistent.constructor.type) - @GuardedByUnknown class MyClass2 {} + // :: warning: (inconsistent.constructor.type) + @GuardedByUnknown class MyClass2 {} - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("lock") class MyClass3 {} + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("lock") class MyClass3 {} - @GuardedBy({}) class MyClass4 {} + @GuardedBy({}) class MyClass4 {} - // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied class MyClass5 {} + // :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied class MyClass5 {} - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @GuardedByBottom class MyClass6 {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @GuardedByBottom class MyClass6 {} } diff --git a/checker/tests/lock/LockExpressionIsFinal.java b/checker/tests/lock/LockExpressionIsFinal.java index 23de75eb20f..da2316b3e5a 100644 --- a/checker/tests/lock/LockExpressionIsFinal.java +++ b/checker/tests/lock/LockExpressionIsFinal.java @@ -1,331 +1,328 @@ +import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByUnknown; import org.checkerframework.checker.lock.qual.MayReleaseLocks; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; -import java.util.concurrent.locks.ReentrantLock; - public class LockExpressionIsFinal { - class MyClass { - Object field = new Object(); - } - - class C1 { - final C1 field = - new C1(); // Infinite loop. This code is not meant to be executed, only type - // checked. - C1 field2; - - @Deterministic - C1 getFieldDeterministic() { - return field; - } - - @Pure - C1 getFieldPure(Object param1, Object param2) { - return field; - } - - @Pure - C1 getFieldPure2() { - return field; - } - - C1 getField() { - return field; - } - } - - final C1 c1 = new C1(); - - // Analogous to testExplicitLockExpressionIsFinal and testGuardedByExpressionIsFinal, but for - // monitor locks acquired in synchronized blocks. - void testSynchronizedExpressionIsFinal(boolean b) { - synchronized (c1) { - } + class MyClass { + Object field = new Object(); + } - Object o1 = new Object(); // o1 is effectively final - it is never reassigned - Object o2 = new Object(); // o2 is reassigned later - it is not effectively final - synchronized (o1) { - } - // :: error: (lock.expression.not.final) - synchronized (o2) { - } + class C1 { + final C1 field = new C1(); // Infinite loop. This code is not meant to be executed, only type + // checked. + C1 field2; - o2 = new Object(); // Reassignment that makes o2 not have been effectively final earlier. + @Deterministic + C1 getFieldDeterministic() { + return field; + } - // Tests that package names are considered final. - synchronized (java.lang.String.class) { - } + @Pure + C1 getFieldPure(Object param1, Object param2) { + return field; + } - // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal - // :: error: (lock.expression.possibly.not.final) - synchronized (c1.getFieldPure(b ? c1 : o1, c1)) { - } + @Pure + C1 getFieldPure2() { + return field; + } - synchronized ( - c1.field.field.field.getFieldPure( - c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) - .field) { - } + C1 getField() { + return field; + } + } - // The following negative test cases are the same as the one above but with one modification - // in each. + final C1 c1 = new C1(); - synchronized ( - // :: error: (lock.expression.not.final) - c1.field.field2.field.getFieldPure( - c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) - .field) { - } - synchronized ( - c1.field.field.field.getFieldPure( - // :: error: (lock.expression.not.final) - c1.field, c1.getField().getFieldPure(c1, c1.field)) - .field) { - } + // Analogous to testExplicitLockExpressionIsFinal and testGuardedByExpressionIsFinal, but for + // monitor locks acquired in synchronized blocks. + void testSynchronizedExpressionIsFinal(boolean b) { + synchronized (c1) { } - class C2 extends ReentrantLock { - final C2 field = - new C2(); // Infinite loop. The code is not meant to be executed, only type-checked. - C2 field2; + Object o1 = new Object(); // o1 is effectively final - it is never reassigned + Object o2 = new Object(); // o2 is reassigned later - it is not effectively final + synchronized (o1) { + } + // :: error: (lock.expression.not.final) + synchronized (o2) { + } - @Deterministic - C2 getFieldDeterministic() { - return field; - } + o2 = new Object(); // Reassignment that makes o2 not have been effectively final earlier. - @Pure - C2 getFieldPure(Object param1, Object param2) { - return field; - } + // Tests that package names are considered final. + synchronized (java.lang.String.class) { + } - C2 getField() { - return field; - } + // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal + // :: error: (lock.expression.possibly.not.final) + synchronized (c1.getFieldPure(b ? c1 : o1, c1)) { } - final C2 c2 = new C2(); + synchronized ( + c1.field.field.field.getFieldPure( + c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) + .field) { + } - // Analogous to testSynchronizedExpressionIsFinal and testGuardedByExpressionIsFinal, but for - // explicit locks. - @MayReleaseLocks - void testExplicitLockExpressionIsFinal(boolean b) { - c2.lock(); + // The following negative test cases are the same as the one above but with one modification + // in each. - ReentrantLock rl1 = - new ReentrantLock(); // rl1 is effectively final - it is never reassigned - ReentrantLock rl2 = - new ReentrantLock(); // rl2 is reassigned later - it is not effectively final - rl1.lock(); - rl1.unlock(); - // :: error: (lock.expression.not.final) - rl2.lock(); + synchronized ( // :: error: (lock.expression.not.final) - rl2.unlock(); - - rl2 = new ReentrantLock(); // Reassignment that makes rl2 not have been effectively final - // earlier. - - // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal - // :: error: (lock.expression.possibly.not.final) - c2.getFieldPure(b ? c2 : rl1, c2).lock(); - // :: error: (lock.expression.possibly.not.final) - c2.getFieldPure(b ? c2 : rl1, c2).unlock(); - - c2.field - .field - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .lock(); - c2.field - .field - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .unlock(); - - // The following negative test cases are the same as the one above but with one modification - // in each. - - c2.field - // :: error: (lock.expression.not.final) - .field2 - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .lock(); - c2.field - // :: error: (lock.expression.not.final) - .field2 - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .unlock(); - - c2.field - .field - .field - // :: error: (lock.expression.not.final) - .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) - .field - .lock(); - c2.field - .field - .field + c1.field.field2.field.getFieldPure( + c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) + .field) { + } + synchronized ( + c1.field.field.field.getFieldPure( // :: error: (lock.expression.not.final) - .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) - .field - .unlock(); + c1.field, c1.getField().getFieldPure(c1, c1.field)) + .field) { } + } - // Analogous to testSynchronizedExpressionIsFinal and testExplicitLockExpressionIsFinal, but for - // expressions in @GuardedBy annotations. - void testGuardedByExpressionIsFinal() { - @GuardedBy("c1") Object guarded1; + class C2 extends ReentrantLock { + final C2 field = + new C2(); // Infinite loop. The code is not meant to be executed, only type-checked. + C2 field2; - final Object o1 = new Object(); - Object o2 = new Object(); - // reassign so it's not effectively final - o2 = new Object(); - - @GuardedBy("o1") Object guarded2 = new Object(); - // :: error: (lock.expression.not.final) - @GuardedBy("o2") Object guarded3 = new Object(); - - // Test expressions that are not supported by LockVisitor.ensureExpressionIsEffectivelyFinal - @GuardedBy("java.lang.String.class") Object guarded4; - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("c1.getFieldPure(b ? c1 : o1, c1)") Object guarded5; - - @GuardedBy( - "c1.field.field.field.getFieldPure" - + "(c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)).field") - Object guarded6; - - @GuardedBy("c1.field.field.field.getFieldPure2().getFieldDeterministic().field") Object guarded7; - - // The following negative test cases are the same as the one above but with one modification - // in each. + @Deterministic + C2 getFieldDeterministic() { + return field; + } - // :: error: (lock.expression.not.final) - @GuardedBy("c1.field.field2.field.getFieldPure2().getFieldDeterministic().field") Object guarded8; - // :: error: (lock.expression.not.final) - @GuardedBy("c1.field.field.field.getField().getFieldDeterministic().field") Object guarded9; + @Pure + C2 getFieldPure(Object param1, Object param2) { + return field; + } - // Additional test cases to test that method parameters (in this case the parameters to - // getFieldPure) are parsed. - @GuardedBy("c1.field.field.field.getFieldPure(c1, c1).getFieldDeterministic().field") Object guarded10; - @GuardedBy("c1.field.field.field.getFieldPure(c1, o1).getFieldDeterministic().field") Object guarded11; - // :: error: (lock.expression.not.final) - @GuardedBy("c1.field.field.field.getFieldPure(c1, o2).getFieldDeterministic().field") Object guarded12; + C2 getField() { + return field; + } + } - // Test that @GuardedBy annotations on various tree kinds inside a method are visited + final C2 c2 = new C2(); - Object guarded13 = (@GuardedBy("o1") Object) guarded2; - // :: error: (lock.expression.not.final) - Object guarded14 = (@GuardedBy("o2") Object) guarded3; + // Analogous to testSynchronizedExpressionIsFinal and testGuardedByExpressionIsFinal, but for + // explicit locks. + @MayReleaseLocks + void testExplicitLockExpressionIsFinal(boolean b) { + c2.lock(); - @GuardedBy("o1") Object guarded15[] = new @GuardedBy("o1") MyClass[3]; + ReentrantLock rl1 = new ReentrantLock(); // rl1 is effectively final - it is never reassigned + ReentrantLock rl2 = + new ReentrantLock(); // rl2 is reassigned later - it is not effectively final + rl1.lock(); + rl1.unlock(); + // :: error: (lock.expression.not.final) + rl2.lock(); + // :: error: (lock.expression.not.final) + rl2.unlock(); + + rl2 = new ReentrantLock(); // Reassignment that makes rl2 not have been effectively final + // earlier. + + // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal + // :: error: (lock.expression.possibly.not.final) + c2.getFieldPure(b ? c2 : rl1, c2).lock(); + // :: error: (lock.expression.possibly.not.final) + c2.getFieldPure(b ? c2 : rl1, c2).unlock(); + + c2.field + .field + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .lock(); + c2.field + .field + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .unlock(); + + // The following negative test cases are the same as the one above but with one modification + // in each. + + c2.field // :: error: (lock.expression.not.final) - @GuardedBy("o2") Object guarded16[] = new @GuardedBy("o2") MyClass[3]; - - // Tests that the location of the @GB annotation inside a VariableTree does not matter (i.e. - // it does not need to be the leftmost subtree). - Object guarded17 @GuardedBy("o1") []; + .field2 + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .lock(); + c2.field // :: error: (lock.expression.not.final) - Object guarded18 @GuardedBy("o2") []; - - @GuardedBy("o1") Object guarded19[]; + .field2 + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .unlock(); + + c2.field + .field + .field // :: error: (lock.expression.not.final) - @GuardedBy("o2") Object guarded20[]; - - MyParameterizedClass1<@GuardedBy("o1") Object> m1; + .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) + .field + .lock(); + c2.field + .field + .field // :: error: (lock.expression.not.final) - MyParameterizedClass1<@GuardedBy("o2") Object> m2; - - boolean b = c1 instanceof @GuardedBy("o1") Object; - // instanceof expression have not effect on the type. - // // :: error: (lock.expression.not.final) - b = c1 instanceof @GuardedBy("o2") Object; - - // Additional tests just outside of this method below: - } + .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) + .field + .unlock(); + } + + // Analogous to testSynchronizedExpressionIsFinal and testExplicitLockExpressionIsFinal, but for + // expressions in @GuardedBy annotations. + void testGuardedByExpressionIsFinal() { + @GuardedBy("c1") Object guarded1; + + final Object o1 = new Object(); + Object o2 = new Object(); + // reassign so it's not effectively final + o2 = new Object(); + + @GuardedBy("o1") Object guarded2 = new Object(); + // :: error: (lock.expression.not.final) + @GuardedBy("o2") Object guarded3 = new Object(); - // Test that @GuardedBy annotations on various tree kinds outside a method are visited + // Test expressions that are not supported by LockVisitor.ensureExpressionIsEffectivelyFinal + @GuardedBy("java.lang.String.class") Object guarded4; + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("c1.getFieldPure(b ? c1 : o1, c1)") Object guarded5; - // Test that @GuardedBy annotations on method return types are visited. No need to test method - // receivers and parameters as they are covered by tests above that visit VariableTree. + @GuardedBy( + "c1.field.field.field.getFieldPure" + + "(c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)).field") + Object guarded6; - final Object finalField = new Object(); - Object nonFinalField = new Object(); + @GuardedBy("c1.field.field.field.getFieldPure2().getFieldDeterministic().field") Object guarded7; - @GuardedBy("finalField") Object testGuardedByExprIsFinal1() { - return null; - } + // The following negative test cases are the same as the one above but with one modification + // in each. // :: error: (lock.expression.not.final) - @GuardedBy("nonFinalField") Object testGuardedByExprIsFinal2() { - return null; - } - - T myMethodThatReturnsT_1(T t) { - return t; - } + @GuardedBy("c1.field.field2.field.getFieldPure2().getFieldDeterministic().field") Object guarded8; + // :: error: (lock.expression.not.final) + @GuardedBy("c1.field.field.field.getField().getFieldDeterministic().field") Object guarded9; + // Additional test cases to test that method parameters (in this case the parameters to + // getFieldPure) are parsed. + @GuardedBy("c1.field.field.field.getFieldPure(c1, c1).getFieldDeterministic().field") Object guarded10; + @GuardedBy("c1.field.field.field.getFieldPure(c1, o1).getFieldDeterministic().field") Object guarded11; // :: error: (lock.expression.not.final) - T myMethodThatReturnsT_2(T t) { - return t; - } + @GuardedBy("c1.field.field.field.getFieldPure(c1, o2).getFieldDeterministic().field") Object guarded12; - class MyParameterizedClass1 {} + // Test that @GuardedBy annotations on various tree kinds inside a method are visited - MyParameterizedClass1 m1; + Object guarded13 = (@GuardedBy("o1") Object) guarded2; // :: error: (lock.expression.not.final) - MyParameterizedClass1 m2; + Object guarded14 = (@GuardedBy("o2") Object) guarded3; - MyParameterizedClass1 m3; + @GuardedBy("o1") Object guarded15[] = new @GuardedBy("o1") MyClass[3]; // :: error: (lock.expression.not.final) - MyParameterizedClass1 m4; + @GuardedBy("o2") Object guarded16[] = new @GuardedBy("o2") MyClass[3]; - class MyClassContainingALock { - final ReentrantLock finalLock = new ReentrantLock(); - ReentrantLock nonFinalLock = new ReentrantLock(); - Object field; - } + // Tests that the location of the @GB annotation inside a VariableTree does not matter (i.e. + // it does not need to be the leftmost subtree). + Object guarded17 @GuardedBy("o1") []; + // :: error: (lock.expression.not.final) + Object guarded18 @GuardedBy("o2") []; - void testItselfFinalLock() { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - final @GuardedBy(".finalLock") MyClassContainingALock m = someValue(); - // :: error: (lock.not.held) - m.field = new Object(); - // Ignore this error: it is expected that an error will be issued for dereferencing 'm' in - // order to take the 'm.finalLock' lock. Typically, the Lock Checker does not support an - // object being guarded by one of its fields, but this is sometimes done in user code with a - // ReentrantLock field guarding its containing object. This unfortunately makes it a bit - // difficult for users since they have to add a @SuppressWarnings for this call while still - // making sure that warnings for other dereferences are not suppressed. - // :: error: (lock.not.held) - m.finalLock.lock(); - m.field = new Object(); - } + @GuardedBy("o1") Object guarded19[]; + // :: error: (lock.expression.not.final) + @GuardedBy("o2") Object guarded20[]; - void testItselfNonFinalLock() { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - final @GuardedBy(".nonFinalLock") MyClassContainingALock m = someValue(); - // ::error: (lock.not.held) :: error: (lock.expression.not.final) - m.field = new Object(); - // ::error: (lock.not.held) :: error: (lock.expression.not.final) - m.nonFinalLock.lock(); - // :: error: (lock.expression.not.final) - m.field = new Object(); - } + MyParameterizedClass1<@GuardedBy("o1") Object> m1; + // :: error: (lock.expression.not.final) + MyParameterizedClass1<@GuardedBy("o2") Object> m2; + + boolean b = c1 instanceof @GuardedBy("o1") Object; + // instanceof expression have not effect on the type. + // // :: error: (lock.expression.not.final) + b = c1 instanceof @GuardedBy("o2") Object; + + // Additional tests just outside of this method below: + } + + // Test that @GuardedBy annotations on various tree kinds outside a method are visited + + // Test that @GuardedBy annotations on method return types are visited. No need to test method + // receivers and parameters as they are covered by tests above that visit VariableTree. + + final Object finalField = new Object(); + Object nonFinalField = new Object(); + + @GuardedBy("finalField") Object testGuardedByExprIsFinal1() { + return null; + } + + // :: error: (lock.expression.not.final) + @GuardedBy("nonFinalField") Object testGuardedByExprIsFinal2() { + return null; + } + + T myMethodThatReturnsT_1(T t) { + return t; + } + + // :: error: (lock.expression.not.final) + T myMethodThatReturnsT_2(T t) { + return t; + } + + class MyParameterizedClass1 {} + + MyParameterizedClass1 m1; + // :: error: (lock.expression.not.final) + MyParameterizedClass1 m2; + + MyParameterizedClass1 m3; + // :: error: (lock.expression.not.final) + MyParameterizedClass1 m4; + + class MyClassContainingALock { + final ReentrantLock finalLock = new ReentrantLock(); + ReentrantLock nonFinalLock = new ReentrantLock(); + Object field; + } + + void testItselfFinalLock() { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + final @GuardedBy(".finalLock") MyClassContainingALock m = someValue(); + // :: error: (lock.not.held) + m.field = new Object(); + // Ignore this error: it is expected that an error will be issued for dereferencing 'm' in + // order to take the 'm.finalLock' lock. Typically, the Lock Checker does not support an + // object being guarded by one of its fields, but this is sometimes done in user code with a + // ReentrantLock field guarding its containing object. This unfortunately makes it a bit + // difficult for users since they have to add a @SuppressWarnings for this call while still + // making sure that warnings for other dereferences are not suppressed. + // :: error: (lock.not.held) + m.finalLock.lock(); + m.field = new Object(); + } + + void testItselfNonFinalLock() { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + final @GuardedBy(".nonFinalLock") MyClassContainingALock m = someValue(); + // ::error: (lock.not.held) :: error: (lock.expression.not.final) + m.field = new Object(); + // ::error: (lock.not.held) :: error: (lock.expression.not.final) + m.nonFinalLock.lock(); + // :: error: (lock.expression.not.final) + m.field = new Object(); + } - @GuardedByUnknown MyClassContainingALock someValue() { - return new MyClassContainingALock(); - } + @GuardedByUnknown MyClassContainingALock someValue() { + return new MyClassContainingALock(); + } } diff --git a/checker/tests/lock/LockInterfaceTest.java b/checker/tests/lock/LockInterfaceTest.java index 295b832eb70..434bdf62c05 100644 --- a/checker/tests/lock/LockInterfaceTest.java +++ b/checker/tests/lock/LockInterfaceTest.java @@ -1,56 +1,55 @@ // Test of use of Lock interface -import org.checkerframework.checker.lock.qual.Holding; -import org.checkerframework.checker.lock.qual.MayReleaseLocks; -import org.checkerframework.checker.lock.qual.ReleasesNoLocks; - import java.util.Date; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.checkerframework.checker.lock.qual.Holding; +import org.checkerframework.checker.lock.qual.MayReleaseLocks; +import org.checkerframework.checker.lock.qual.ReleasesNoLocks; public class LockInterfaceTest { - static final Lock myStaticLock = new ReentrantLock(true); - - private static final Date x = new Date((long) (System.currentTimeMillis() * Math.random())); - - @Holding("myStaticLock") - @ReleasesNoLocks - static void method4() { - System.out.println(x); - } - - @Holding("LockInterfaceTest.myStaticLock") - @ReleasesNoLocks - static void method5() { - System.out.println(x); - } - - @MayReleaseLocks - public static void test1() { - LockInterfaceTest.myStaticLock.lock(); - method4(); - LockInterfaceTest.myStaticLock.unlock(); - } - - @MayReleaseLocks - public static void test2() { - LockInterfaceTest.myStaticLock.lock(); - method5(); - LockInterfaceTest.myStaticLock.unlock(); - } - - @MayReleaseLocks - public static void test3() { - myStaticLock.lock(); - method4(); - myStaticLock.unlock(); - } - - @MayReleaseLocks - public static void test4() { - myStaticLock.lock(); - method5(); - myStaticLock.unlock(); - } + static final Lock myStaticLock = new ReentrantLock(true); + + private static final Date x = new Date((long) (System.currentTimeMillis() * Math.random())); + + @Holding("myStaticLock") + @ReleasesNoLocks + static void method4() { + System.out.println(x); + } + + @Holding("LockInterfaceTest.myStaticLock") + @ReleasesNoLocks + static void method5() { + System.out.println(x); + } + + @MayReleaseLocks + public static void test1() { + LockInterfaceTest.myStaticLock.lock(); + method4(); + LockInterfaceTest.myStaticLock.unlock(); + } + + @MayReleaseLocks + public static void test2() { + LockInterfaceTest.myStaticLock.lock(); + method5(); + LockInterfaceTest.myStaticLock.unlock(); + } + + @MayReleaseLocks + public static void test3() { + myStaticLock.lock(); + method4(); + myStaticLock.unlock(); + } + + @MayReleaseLocks + public static void test4() { + myStaticLock.lock(); + method5(); + myStaticLock.unlock(); + } } diff --git a/checker/tests/lock/Methods.java b/checker/tests/lock/Methods.java index 464083d1ec4..2f3c92e919c 100644 --- a/checker/tests/lock/Methods.java +++ b/checker/tests/lock/Methods.java @@ -2,55 +2,55 @@ public class Methods { - final Object lock = new Object(); - - @Holding("lock") - void lockedByLock() {} - - @Holding("this") - void lockedByThis() {} - - // unguarded calls - void unguardedCalls() { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - // :: error: (contracts.precondition.not.satisfied) - lockedByThis(); // error - } - - @Holding("lock") - void usingHolding1() { - lockedByLock(); - // :: error: (contracts.precondition.not.satisfied) - lockedByThis(); // error + final Object lock = new Object(); + + @Holding("lock") + void lockedByLock() {} + + @Holding("this") + void lockedByThis() {} + + // unguarded calls + void unguardedCalls() { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + // :: error: (contracts.precondition.not.satisfied) + lockedByThis(); // error + } + + @Holding("lock") + void usingHolding1() { + lockedByLock(); + // :: error: (contracts.precondition.not.satisfied) + lockedByThis(); // error + } + + @Holding("this") + void usingHolding2() { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + lockedByThis(); + } + + void usingSynchronization1() { + synchronized (lock) { + lockedByLock(); + // :: error: (contracts.precondition.not.satisfied) + lockedByThis(); // error } + } - @Holding("this") - void usingHolding2() { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - lockedByThis(); + void usingSynchronization2() { + synchronized (this) { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + lockedByThis(); } + } - void usingSynchronization1() { - synchronized (lock) { - lockedByLock(); - // :: error: (contracts.precondition.not.satisfied) - lockedByThis(); // error - } - } - - void usingSynchronization2() { - synchronized (this) { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - lockedByThis(); - } - } - - synchronized void usingMethodModifier() { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - lockedByThis(); - } + synchronized void usingMethodModifier() { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + lockedByThis(); + } } diff --git a/checker/tests/lock/NestedSynchronizedBlocks.java b/checker/tests/lock/NestedSynchronizedBlocks.java index edf58aa3943..ccbf3446942 100644 --- a/checker/tests/lock/NestedSynchronizedBlocks.java +++ b/checker/tests/lock/NestedSynchronizedBlocks.java @@ -1,42 +1,42 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class NestedSynchronizedBlocks { - class MyClass { - public Object field; - } + class MyClass { + public Object field; + } - @GuardedBy("lock1") MyClass m1; + @GuardedBy("lock1") MyClass m1; - @GuardedBy("lock2") MyClass m2; + @GuardedBy("lock2") MyClass m2; - @GuardedBy("lock3") MyClass m3; + @GuardedBy("lock3") MyClass m3; - @GuardedBy("lock4") MyClass m4; + @GuardedBy("lock4") MyClass m4; - final Object lock1 = new Object(), - lock2 = new Object(), - lock3 = new Object(), - lock4 = new Object(); + final Object lock1 = new Object(), + lock2 = new Object(), + lock3 = new Object(), + lock4 = new Object(); - void foo() { - synchronized (lock1) { - synchronized (lock2) { - synchronized (lock3) { - synchronized (lock4) { - } - } - } + void foo() { + synchronized (lock1) { + synchronized (lock2) { + synchronized (lock3) { + synchronized (lock4) { + } } - - // Test that the locks are known to have been released. - - // :: error:(lock.not.held) - m1.field = new Object(); - // :: error:(lock.not.held) - m2.field = new Object(); - // :: error:(lock.not.held) - m3.field = new Object(); - // :: error:(lock.not.held) - m4.field = new Object(); + } } + + // Test that the locks are known to have been released. + + // :: error:(lock.not.held) + m1.field = new Object(); + // :: error:(lock.not.held) + m2.field = new Object(); + // :: error:(lock.not.held) + m3.field = new Object(); + // :: error:(lock.not.held) + m4.field = new Object(); + } } diff --git a/checker/tests/lock/Overriding.java b/checker/tests/lock/Overriding.java index fae304c2070..670ff3c29e6 100644 --- a/checker/tests/lock/Overriding.java +++ b/checker/tests/lock/Overriding.java @@ -3,158 +3,158 @@ public class Overriding { - class SuperClass { - protected Object a, b, c; - - @Holding("a") - void guardedByOne() {} - - @Holding({"a", "b"}) - void guardedByTwo() {} - - @Holding({"a", "b", "c"}) - void guardedByThree() {} - - @ReleasesNoLocks - void rnlMethod() { - // :: error: (method.guarantee.violated) - mrlMethod(); - rnlMethod(); - implicitRnlMethod(); - lfMethod(); - } - - void implicitRnlMethod() { - // :: error: (method.guarantee.violated) - mrlMethod(); - rnlMethod(); - implicitRnlMethod(); - lfMethod(); - } - - @LockingFree - void lfMethod() { - // :: error: (method.guarantee.violated) - mrlMethod(); - // :: error: (method.guarantee.violated) - rnlMethod(); - // :: error: (method.guarantee.violated) - implicitRnlMethod(); - lfMethod(); - } - - @MayReleaseLocks - void mrlMethod() { - mrlMethod(); - rnlMethod(); - implicitRnlMethod(); - lfMethod(); - } - - @ReleasesNoLocks - void rnlMethod2() {} - - void implicitRnlMethod2() {} - - @LockingFree - void lfMethod2() {} - - @MayReleaseLocks - void mrlMethod2() {} - - @ReleasesNoLocks - void rnlMethod3() {} - - void implicitRnlMethod3() {} - - @LockingFree - void lfMethod3() {} - } + class SuperClass { + protected Object a, b, c; - class SubClass extends SuperClass { - @Holding({"a", "b"}) // error - @Override - // :: error: (contracts.precondition.override.invalid) - void guardedByOne() {} - - @Holding({"a", "b"}) - @Override - void guardedByTwo() {} - - @Holding({"a", "b"}) - @Override - void guardedByThree() {} - - @MayReleaseLocks - @Override - // :: error: (override.sideeffect.invalid) - void rnlMethod() {} - - @MayReleaseLocks - @Override - // :: error: (override.sideeffect.invalid) - void implicitRnlMethod() {} - - @ReleasesNoLocks - @Override - // :: error: (override.sideeffect.invalid) - void lfMethod() {} - - @MayReleaseLocks - @Override - void mrlMethod() {} - - @ReleasesNoLocks - @Override - void rnlMethod2() {} - - @Override - void implicitRnlMethod2() {} - - @LockingFree - @Override - void lfMethod2() {} - - @ReleasesNoLocks - @Override - void mrlMethod2() {} - - @LockingFree - @Override - void rnlMethod3() {} - - @LockingFree - @Override - void implicitRnlMethod3() {} - - @SideEffectFree - @Override - void lfMethod3() {} - } + @Holding("a") + void guardedByOne() {} - // Test overriding @Holding with JCIP @GuardedBy. - class SubClassJcip extends SuperClass { - @net.jcip.annotations.GuardedBy({"a", "b"}) @Override - // :: error: (contracts.precondition.override.invalid) - void guardedByOne() {} + @Holding({"a", "b"}) + void guardedByTwo() {} - @net.jcip.annotations.GuardedBy({"a", "b"}) @Override - void guardedByTwo() {} + @Holding({"a", "b", "c"}) + void guardedByThree() {} - @net.jcip.annotations.GuardedBy({"a", "b"}) @Override - void guardedByThree() {} + @ReleasesNoLocks + void rnlMethod() { + // :: error: (method.guarantee.violated) + mrlMethod(); + rnlMethod(); + implicitRnlMethod(); + lfMethod(); } - // Test overriding @Holding with Javax @GuardedBy. - class SubClassJavax extends SuperClass { - @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override - // :: error: (contracts.precondition.override.invalid) - void guardedByOne() {} + void implicitRnlMethod() { + // :: error: (method.guarantee.violated) + mrlMethod(); + rnlMethod(); + implicitRnlMethod(); + lfMethod(); + } - @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override - void guardedByTwo() {} + @LockingFree + void lfMethod() { + // :: error: (method.guarantee.violated) + mrlMethod(); + // :: error: (method.guarantee.violated) + rnlMethod(); + // :: error: (method.guarantee.violated) + implicitRnlMethod(); + lfMethod(); + } - @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override - void guardedByThree() {} + @MayReleaseLocks + void mrlMethod() { + mrlMethod(); + rnlMethod(); + implicitRnlMethod(); + lfMethod(); } + + @ReleasesNoLocks + void rnlMethod2() {} + + void implicitRnlMethod2() {} + + @LockingFree + void lfMethod2() {} + + @MayReleaseLocks + void mrlMethod2() {} + + @ReleasesNoLocks + void rnlMethod3() {} + + void implicitRnlMethod3() {} + + @LockingFree + void lfMethod3() {} + } + + class SubClass extends SuperClass { + @Holding({"a", "b"}) // error + @Override + // :: error: (contracts.precondition.override.invalid) + void guardedByOne() {} + + @Holding({"a", "b"}) + @Override + void guardedByTwo() {} + + @Holding({"a", "b"}) + @Override + void guardedByThree() {} + + @MayReleaseLocks + @Override + // :: error: (override.sideeffect.invalid) + void rnlMethod() {} + + @MayReleaseLocks + @Override + // :: error: (override.sideeffect.invalid) + void implicitRnlMethod() {} + + @ReleasesNoLocks + @Override + // :: error: (override.sideeffect.invalid) + void lfMethod() {} + + @MayReleaseLocks + @Override + void mrlMethod() {} + + @ReleasesNoLocks + @Override + void rnlMethod2() {} + + @Override + void implicitRnlMethod2() {} + + @LockingFree + @Override + void lfMethod2() {} + + @ReleasesNoLocks + @Override + void mrlMethod2() {} + + @LockingFree + @Override + void rnlMethod3() {} + + @LockingFree + @Override + void implicitRnlMethod3() {} + + @SideEffectFree + @Override + void lfMethod3() {} + } + + // Test overriding @Holding with JCIP @GuardedBy. + class SubClassJcip extends SuperClass { + @net.jcip.annotations.GuardedBy({"a", "b"}) @Override + // :: error: (contracts.precondition.override.invalid) + void guardedByOne() {} + + @net.jcip.annotations.GuardedBy({"a", "b"}) @Override + void guardedByTwo() {} + + @net.jcip.annotations.GuardedBy({"a", "b"}) @Override + void guardedByThree() {} + } + + // Test overriding @Holding with Javax @GuardedBy. + class SubClassJavax extends SuperClass { + @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override + // :: error: (contracts.precondition.override.invalid) + void guardedByOne() {} + + @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override + void guardedByTwo() {} + + @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override + void guardedByThree() {} + } } diff --git a/checker/tests/lock/PrimitivesLocking.java b/checker/tests/lock/PrimitivesLocking.java index 2149feb8f25..38ab2845c03 100644 --- a/checker/tests/lock/PrimitivesLocking.java +++ b/checker/tests/lock/PrimitivesLocking.java @@ -5,157 +5,157 @@ // Note that testing of the immutable.type.guardedby error message is done in TestTreeKinds.java public class PrimitivesLocking { - // @GuardedByName("lock") - int primitive = 1; + // @GuardedByName("lock") + int primitive = 1; + + // @GuardedByName("lock") + boolean primitiveBoolean; + public void testOperationsWithPrimitives() { + // @GuardedByName("lock") + int i = 0; // @GuardedByName("lock") - boolean primitiveBoolean; - - public void testOperationsWithPrimitives() { - // @GuardedByName("lock") - int i = 0; - // @GuardedByName("lock") - boolean b; - - // TODO reenable this error: (lock.not.held) - i = i >>> primitive; - // TODO reenable this error: (lock.not.held) - i = primitive >>> i; - - // TODO reenable this error: (lock.not.held) - i >>>= primitive; - // TODO reenable this error: (lock.not.held) - primitive >>>= i; - - // TODO reenable this error: (lock.not.held) - i %= primitive; - // TODO reenable this error: (lock.not.held) - i = 4 % primitive; - // TODO reenable this error: (lock.not.held) - i = primitive % 4; - - // TODO reenable this error: (lock.not.held) - primitive++; - // TODO reenable this error: (lock.not.held) - primitive--; - // TODO reenable this error: (lock.not.held) - ++primitive; - // TODO reenable this error: (lock.not.held) - --primitive; - - // TODO reenable this error: (lock.not.held) - if (primitive != 5) {} - - // TODO reenable this error: (lock.not.held) - i = primitive >> i; - // TODO reenable this error: (lock.not.held) - i = primitive << i; - // TODO reenable this error: (lock.not.held) - i = i >> primitive; - // TODO reenable this error: (lock.not.held) - i = i << primitive; - - // TODO reenable this error: (lock.not.held) - i <<= primitive; - // TODO reenable this error: (lock.not.held) - i >>= primitive; - // TODO reenable this error: (lock.not.held) - primitive <<= i; - // TODO reenable this error: (lock.not.held) - primitive >>= i; - - // TODO reenable this error: (lock.not.held) - assert (primitiveBoolean); - - // TODO reenable this error: (lock.not.held) - b = primitive >= i; - // TODO reenable this error: (lock.not.held) - b = primitive <= i; - // TODO reenable this error: (lock.not.held) - b = primitive > i; - // TODO reenable this error: (lock.not.held) - b = primitive < i; - // TODO reenable this error: (lock.not.held) - b = i >= primitive; - // TODO reenable this error: (lock.not.held) - b = i <= primitive; - // TODO reenable this error: (lock.not.held) - b = i > primitive; - // TODO reenable this error: (lock.not.held) - b = i < primitive; - - // TODO reenable this error: (lock.not.held) - i += primitive; - // TODO reenable this error: (lock.not.held) - i -= primitive; - // TODO reenable this error: (lock.not.held) - i *= primitive; - // TODO reenable this error: (lock.not.held) - i /= primitive; - - // TODO reenable this error: (lock.not.held) - i = 4 + primitive; - // TODO reenable this error: (lock.not.held) - i = 4 - primitive; - // TODO reenable this error: (lock.not.held) - i = 4 * primitive; - // TODO reenable this error: (lock.not.held) - i = 4 / primitive; - - // TODO reenable this error: (lock.not.held) - i = primitive + 4; - // TODO reenable this error: (lock.not.held) - i = primitive - 4; - // TODO reenable this error: (lock.not.held) - i = primitive * 4; - // TODO reenable this error: (lock.not.held) - i = primitive / 4; - - // TODO reenable this error: (lock.not.held) - if (primitiveBoolean) {} - - // TODO reenable this error: (lock.not.held) - i = ~primitive; - - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean || false; - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean | false; - - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean ^ true; - - // TODO reenable this error: (lock.not.held) - b &= primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b |= primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b ^= primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = !primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - i = primitive; - - // TODO reenable this error: (lock.not.held) - b = true && primitiveBoolean; - // TODO reenable this error: (lock.not.held) - b = true & primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = false || primitiveBoolean; - // TODO reenable this error: (lock.not.held) - b = false | primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = false ^ primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean && true; - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean & true; - } + boolean b; + + // TODO reenable this error: (lock.not.held) + i = i >>> primitive; + // TODO reenable this error: (lock.not.held) + i = primitive >>> i; + + // TODO reenable this error: (lock.not.held) + i >>>= primitive; + // TODO reenable this error: (lock.not.held) + primitive >>>= i; + + // TODO reenable this error: (lock.not.held) + i %= primitive; + // TODO reenable this error: (lock.not.held) + i = 4 % primitive; + // TODO reenable this error: (lock.not.held) + i = primitive % 4; + + // TODO reenable this error: (lock.not.held) + primitive++; + // TODO reenable this error: (lock.not.held) + primitive--; + // TODO reenable this error: (lock.not.held) + ++primitive; + // TODO reenable this error: (lock.not.held) + --primitive; + + // TODO reenable this error: (lock.not.held) + if (primitive != 5) {} + + // TODO reenable this error: (lock.not.held) + i = primitive >> i; + // TODO reenable this error: (lock.not.held) + i = primitive << i; + // TODO reenable this error: (lock.not.held) + i = i >> primitive; + // TODO reenable this error: (lock.not.held) + i = i << primitive; + + // TODO reenable this error: (lock.not.held) + i <<= primitive; + // TODO reenable this error: (lock.not.held) + i >>= primitive; + // TODO reenable this error: (lock.not.held) + primitive <<= i; + // TODO reenable this error: (lock.not.held) + primitive >>= i; + + // TODO reenable this error: (lock.not.held) + assert (primitiveBoolean); + + // TODO reenable this error: (lock.not.held) + b = primitive >= i; + // TODO reenable this error: (lock.not.held) + b = primitive <= i; + // TODO reenable this error: (lock.not.held) + b = primitive > i; + // TODO reenable this error: (lock.not.held) + b = primitive < i; + // TODO reenable this error: (lock.not.held) + b = i >= primitive; + // TODO reenable this error: (lock.not.held) + b = i <= primitive; + // TODO reenable this error: (lock.not.held) + b = i > primitive; + // TODO reenable this error: (lock.not.held) + b = i < primitive; + + // TODO reenable this error: (lock.not.held) + i += primitive; + // TODO reenable this error: (lock.not.held) + i -= primitive; + // TODO reenable this error: (lock.not.held) + i *= primitive; + // TODO reenable this error: (lock.not.held) + i /= primitive; + + // TODO reenable this error: (lock.not.held) + i = 4 + primitive; + // TODO reenable this error: (lock.not.held) + i = 4 - primitive; + // TODO reenable this error: (lock.not.held) + i = 4 * primitive; + // TODO reenable this error: (lock.not.held) + i = 4 / primitive; + + // TODO reenable this error: (lock.not.held) + i = primitive + 4; + // TODO reenable this error: (lock.not.held) + i = primitive - 4; + // TODO reenable this error: (lock.not.held) + i = primitive * 4; + // TODO reenable this error: (lock.not.held) + i = primitive / 4; + + // TODO reenable this error: (lock.not.held) + if (primitiveBoolean) {} + + // TODO reenable this error: (lock.not.held) + i = ~primitive; + + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean || false; + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean | false; + + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean ^ true; + + // TODO reenable this error: (lock.not.held) + b &= primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b |= primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b ^= primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = !primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + i = primitive; + + // TODO reenable this error: (lock.not.held) + b = true && primitiveBoolean; + // TODO reenable this error: (lock.not.held) + b = true & primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = false || primitiveBoolean; + // TODO reenable this error: (lock.not.held) + b = false | primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = false ^ primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean && true; + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean & true; + } } diff --git a/checker/tests/lock/ReturnsNewObjectTest.java b/checker/tests/lock/ReturnsNewObjectTest.java index 6d780b938ab..371f5118dde 100644 --- a/checker/tests/lock/ReturnsNewObjectTest.java +++ b/checker/tests/lock/ReturnsNewObjectTest.java @@ -2,16 +2,16 @@ import org.checkerframework.checker.lock.qual.NewObject; public class ReturnsNewObjectTest { - @NewObject Object factoryMethod() { - return new Object(); - } + @NewObject Object factoryMethod() { + return new Object(); + } - void m() { - @GuardedBy("this") Object x = factoryMethod(); - } + void m() { + @GuardedBy("this") Object x = factoryMethod(); + } - void m2() { - String @GuardedBy("this") [] a2 = new String[4]; - String @GuardedBy("this") [] a3 = new String[] {"a", "b", "c"}; - } + void m2() { + String @GuardedBy("this") [] a2 = new String[4]; + String @GuardedBy("this") [] a3 = new String[] {"a", "b", "c"}; + } } diff --git a/checker/tests/lock/SimpleLockTest.java b/checker/tests/lock/SimpleLockTest.java index 88222a99c9a..3709f9900a8 100644 --- a/checker/tests/lock/SimpleLockTest.java +++ b/checker/tests/lock/SimpleLockTest.java @@ -2,39 +2,36 @@ import org.checkerframework.checker.lock.qual.GuardedByUnknown; public class SimpleLockTest { - final Object lock1 = new Object(), lock2 = new Object(); + final Object lock1 = new Object(), lock2 = new Object(); - void testMethodCall(@GuardedBy("lock1") SimpleLockTest this) { - // :: error: (lock.not.held) - synchronized (lock1) { - } - // :: error: (lock.not.held) - synchronized (this.lock1) { - } - // :: error: (lock.not.held) - synchronized (lock2) { - } - // :: error: (lock.not.held) - synchronized (this.lock2) { - } - - @SuppressWarnings({ - "assignment", - "method.invocation" - }) // prevent flow-sensitive type refinement - final @GuardedBy("myClass.field") MyClass myClass = someValue(); - // :: error: (lock.not.held) - synchronized (myClass.field) { - } - synchronized (myClass) { - } + void testMethodCall(@GuardedBy("lock1") SimpleLockTest this) { + // :: error: (lock.not.held) + synchronized (lock1) { } - - @GuardedByUnknown MyClass someValue() { - return new MyClass(); + // :: error: (lock.not.held) + synchronized (this.lock1) { + } + // :: error: (lock.not.held) + synchronized (lock2) { + } + // :: error: (lock.not.held) + synchronized (this.lock2) { } - class MyClass { - final Object field = new Object(); + @SuppressWarnings({"assignment", "method.invocation"}) // prevent flow-sensitive type refinement + final @GuardedBy("myClass.field") MyClass myClass = someValue(); + // :: error: (lock.not.held) + synchronized (myClass.field) { } + synchronized (myClass) { + } + } + + @GuardedByUnknown MyClass someValue() { + return new MyClass(); + } + + class MyClass { + final Object field = new Object(); + } } diff --git a/checker/tests/lock/Strings.java b/checker/tests/lock/Strings.java index c29200e1d47..5328a0d3d98 100644 --- a/checker/tests/lock/Strings.java +++ b/checker/tests/lock/Strings.java @@ -4,60 +4,60 @@ import org.checkerframework.checker.lock.qual.GuardedByUnknown; public class Strings { - final Object lock = new Object(); - - // These casts are safe because if the casted Object is a String, it must be @GuardedBy({}) - void StringIsGBnothing( - @GuardedByUnknown Object o1, - @GuardedBy("lock") Object o2, - @GuardSatisfied Object o3, - @GuardedByBottom Object o4) { - String s1 = (String) o1; - String s2 = (String) o2; - String s3 = (String) o3; - String s4 = (String) o4; // OK + final Object lock = new Object(); + + // These casts are safe because if the casted Object is a String, it must be @GuardedBy({}) + void StringIsGBnothing( + @GuardedByUnknown Object o1, + @GuardedBy("lock") Object o2, + @GuardSatisfied Object o3, + @GuardedByBottom Object o4) { + String s1 = (String) o1; + String s2 = (String) o2; + String s3 = (String) o3; + String s4 = (String) o4; // OK + } + + // Tests that the resulting type of string concatenation is always @GuardedBy({}) + // (and not @GuardedByUnknown, which is the LUB of @GuardedBy({}) (the type of the + // string literal "a") and @GuardedBy("lock") (the type of param)) + void StringConcat(@GuardedBy("lock") MyClass param) { + { + String s1a = "a" + "a"; + // :: error: (lock.not.held) + String s1b = "a" + param; + // :: error: (lock.not.held) + String s1c = param + "a"; + // :: error: (lock.not.held) + String s1d = param.toString(); + + String s2 = "a"; + // :: error: (lock.not.held) + s2 += param; + + String s3 = "a"; + // In addition to testing whether "lock" is held, tests that the result of a string + // concatenation has type @GuardedBy({}). + // :: error: (lock.not.held) + String s4 = s3 += param; } - - // Tests that the resulting type of string concatenation is always @GuardedBy({}) - // (and not @GuardedByUnknown, which is the LUB of @GuardedBy({}) (the type of the - // string literal "a") and @GuardedBy("lock") (the type of param)) - void StringConcat(@GuardedBy("lock") MyClass param) { - { - String s1a = "a" + "a"; - // :: error: (lock.not.held) - String s1b = "a" + param; - // :: error: (lock.not.held) - String s1c = param + "a"; - // :: error: (lock.not.held) - String s1d = param.toString(); - - String s2 = "a"; - // :: error: (lock.not.held) - s2 += param; - - String s3 = "a"; - // In addition to testing whether "lock" is held, tests that the result of a string - // concatenation has type @GuardedBy({}). - // :: error: (lock.not.held) - String s4 = s3 += param; - } - synchronized (lock) { - String s1a = "a" + "a"; - String s1b = "a" + param; - String s1c = param + "a"; - String s1d = param.toString(); - - String s2 = "a"; - s2 += param; - - String s3 = "a"; - // In addition to testing whether "lock" is held, tests that the result of a string - // concatenation has type @GuardedBy({}). - String s4 = s3 += param; - } + synchronized (lock) { + String s1a = "a" + "a"; + String s1b = "a" + param; + String s1c = param + "a"; + String s1d = param.toString(); + + String s2 = "a"; + s2 += param; + + String s3 = "a"; + // In addition to testing whether "lock" is held, tests that the result of a string + // concatenation has type @GuardedBy({}). + String s4 = s3 += param; } + } - class MyClass { - Object field = new Object(); - } + class MyClass { + Object field = new Object(); + } } diff --git a/checker/tests/lock/TestAnon.java b/checker/tests/lock/TestAnon.java index 0966c61c641..0bb105edaea 100644 --- a/checker/tests/lock/TestAnon.java +++ b/checker/tests/lock/TestAnon.java @@ -1,10 +1,10 @@ public class TestAnon { - public void foo() { - String s = ""; - new Object() { - public String bar() { - return s; - } - }; - } + public void foo() { + String s = ""; + new Object() { + public String bar() { + return s; + } + }; + } } diff --git a/checker/tests/lock/TestConcurrentSemantics1.java b/checker/tests/lock/TestConcurrentSemantics1.java index 8636d166225..d3cce3b82a0 100644 --- a/checker/tests/lock/TestConcurrentSemantics1.java +++ b/checker/tests/lock/TestConcurrentSemantics1.java @@ -1,54 +1,53 @@ +import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByUnknown; -import java.util.concurrent.locks.ReentrantLock; - public class TestConcurrentSemantics1 { - /* This class tests the following critical scenario. - * - * Suppose the following lines from method1 are executed on thread A. - * - *
{@code
-     * @GuardedBy("lock1") MyClass local;
-     * m = local;
-     * }
- * - * Then a context switch occurs to method2 on thread B and the following lines are executed: - * - *
{@code
-     * @GuardedBy("lock2") MyClass local;
-     * m = local;
-     * }
- * - * Then a context switch back to method1 on thread A occurs and the following lines are executed: - * - *
{@code
-     * lock1.lock();
-     * m.field = new Object();
-     * }
- * - * In this case, it is absolutely critical that the dereference above not be allowed. - * - */ + /* This class tests the following critical scenario. + * + * Suppose the following lines from method1 are executed on thread A. + * + *
{@code
+   * @GuardedBy("lock1") MyClass local;
+   * m = local;
+   * }
+ * + * Then a context switch occurs to method2 on thread B and the following lines are executed: + * + *
{@code
+   * @GuardedBy("lock2") MyClass local;
+   * m = local;
+   * }
+ * + * Then a context switch back to method1 on thread A occurs and the following lines are executed: + * + *
{@code
+   * lock1.lock();
+   * m.field = new Object();
+   * }
+ * + * In this case, it is absolutely critical that the dereference above not be allowed. + * + */ - @GuardedByUnknown MyClass m; - final ReentrantLock lock1 = new ReentrantLock(); - final ReentrantLock lock2 = new ReentrantLock(); + @GuardedByUnknown MyClass m; + final ReentrantLock lock1 = new ReentrantLock(); + final ReentrantLock lock2 = new ReentrantLock(); - void method1() { - @GuardedBy("lock1") MyClass local = new MyClass(); - m = local; - lock1.lock(); - // :: error: (lock.not.held) - m.field = new Object(); - } + void method1() { + @GuardedBy("lock1") MyClass local = new MyClass(); + m = local; + lock1.lock(); + // :: error: (lock.not.held) + m.field = new Object(); + } - void method2() { - @GuardedBy("lock2") MyClass local = new MyClass(); - m = local; - } + void method2() { + @GuardedBy("lock2") MyClass local = new MyClass(); + m = local; + } - class MyClass { - Object field = new Object(); - } + class MyClass { + Object field = new Object(); + } } diff --git a/checker/tests/lock/TestConcurrentSemantics2.java b/checker/tests/lock/TestConcurrentSemantics2.java index 73253cf0ad9..39323991dda 100644 --- a/checker/tests/lock/TestConcurrentSemantics2.java +++ b/checker/tests/lock/TestConcurrentSemantics2.java @@ -1,29 +1,29 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class TestConcurrentSemantics2 { - final Object a = new Object(); - final Object b = new Object(); + final Object a = new Object(); + final Object b = new Object(); - @GuardedBy("a") Object o; + @GuardedBy("a") Object o; - void method() { - o = null; - // Assume the following happens: - // * Context switch to a different thread. - // * bar() is called on the other thread. - // * Context switch back to this thread. - // o is no longer null and an "assignment.type.incompatible" error should be issued. - // :: error: (assignment.type.incompatible) - @GuardedBy("b") Object o2 = o; - } + void method() { + o = null; + // Assume the following happens: + // * Context switch to a different thread. + // * bar() is called on the other thread. + // * Context switch back to this thread. + // o is no longer null and an "assignment.type.incompatible" error should be issued. + // :: error: (assignment.type.incompatible) + @GuardedBy("b") Object o2 = o; + } - void bar() { - o = new Object(); - } + void bar() { + o = new Object(); + } - // Test that field assignments do not cause their type to be refined: - @GuardedBy("a") Object myObject1 = null; + // Test that field assignments do not cause their type to be refined: + @GuardedBy("a") Object myObject1 = null; - // :: error: (assignment.type.incompatible) - @GuardedBy("b") Object myObject2 = myObject1; + // :: error: (assignment.type.incompatible) + @GuardedBy("b") Object myObject2 = myObject1; } diff --git a/checker/tests/lock/TestTreeKinds.java b/checker/tests/lock/TestTreeKinds.java index 5d5c2dc72e8..b177aaab82c 100644 --- a/checker/tests/lock/TestTreeKinds.java +++ b/checker/tests/lock/TestTreeKinds.java @@ -1,529 +1,528 @@ +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.*; import org.checkerframework.checker.lock.qual.GuardedByUnknown; import org.checkerframework.dataflow.qual.SideEffectFree; -import java.util.Random; -import java.util.concurrent.locks.ReentrantLock; - public class TestTreeKinds { - class MyClass { - Object field = new Object(); - - @LockingFree - Object method(@GuardSatisfied MyClass this) { - return new Object(); - } + class MyClass { + Object field = new Object(); - void method2(@GuardSatisfied MyClass this) {} + @LockingFree + Object method(@GuardSatisfied MyClass this) { + return new Object(); } - MyClass[] newMyClassArray() { - return new MyClass[3]; - } + void method2(@GuardSatisfied MyClass this) {} + } - @GuardedBy("lock") MyClass m; + MyClass[] newMyClassArray() { + return new MyClass[3]; + } - { - // In constructor/initializer, it's OK not to hold the lock on 'this', but other locks must - // be respected. - // :: error: (lock.not.held) - m.field = new Object(); - } + @GuardedBy("lock") MyClass m; - final ReentrantLock lock = new ReentrantLock(); - final ReentrantLock lock2 = new ReentrantLock(); + { + // In constructor/initializer, it's OK not to hold the lock on 'this', but other locks must + // be respected. + // :: error: (lock.not.held) + m.field = new Object(); + } - @GuardedBy("lock") MyClass foo = new MyClass(); + final ReentrantLock lock = new ReentrantLock(); + final ReentrantLock lock2 = new ReentrantLock(); - MyClass unguardedFoo = new MyClass(); + @GuardedBy("lock") MyClass foo = new MyClass(); - @EnsuresLockHeld("lock") - void lockTheLock() { - lock.lock(); - } + MyClass unguardedFoo = new MyClass(); - @EnsuresLockHeld("lock2") - void lockTheLock2() { - lock2.lock(); - } + @EnsuresLockHeld("lock") + void lockTheLock() { + lock.lock(); + } - @EnsuresLockHeldIf(expression = "lock", result = true) - boolean tryToLockTheLock() { - return lock.tryLock(); - } + @EnsuresLockHeld("lock2") + void lockTheLock2() { + lock2.lock(); + } - // This @MayReleaseLocks annotation causes dataflow analysis to assume 'lock' is released after - // unlockTheLock() is called. - @MayReleaseLocks - void unlockTheLock() {} + @EnsuresLockHeldIf(expression = "lock", result = true) + boolean tryToLockTheLock() { + return lock.tryLock(); + } - @SideEffectFree - void sideEffectFreeMethod() {} + // This @MayReleaseLocks annotation causes dataflow analysis to assume 'lock' is released after + // unlockTheLock() is called. + @MayReleaseLocks + void unlockTheLock() {} - @LockingFree - void lockingFreeMethod() {} + @SideEffectFree + void sideEffectFreeMethod() {} - @MayReleaseLocks - void nonSideEffectFreeMethod() {} + @LockingFree + void lockingFreeMethod() {} - @Holding("lock") - void requiresLockHeldMethod() {} + @MayReleaseLocks + void nonSideEffectFreeMethod() {} - @SuppressWarnings("assignment") - MyClass @GuardedBy("lock") [] fooArray = new MyClass[3]; + @Holding("lock") + void requiresLockHeldMethod() {} - @GuardedBy("lock") MyClass[] fooArray2 = new MyClass[3]; + @SuppressWarnings("assignment") + MyClass @GuardedBy("lock") [] fooArray = new MyClass[3]; - @SuppressWarnings("assignment") - @GuardedBy("lock") MyClass[][] fooArray3 = new MyClass[3][3]; + @GuardedBy("lock") MyClass[] fooArray2 = new MyClass[3]; - @SuppressWarnings("assignment") - MyClass @GuardedBy("lock") [][] fooArray4 = new MyClass[3][3]; + @SuppressWarnings("assignment") + @GuardedBy("lock") MyClass[][] fooArray3 = new MyClass[3][3]; - MyClass[] @GuardedBy("lock") [] fooArray5 = new MyClass[3][3]; + @SuppressWarnings("assignment") + MyClass @GuardedBy("lock") [][] fooArray4 = new MyClass[3][3]; - class myClass { - int i = 0; - } + MyClass[] @GuardedBy("lock") [] fooArray5 = new MyClass[3][3]; - @GuardedBy("lock") myClass myClassInstance = new myClass(); + class myClass { + int i = 0; + } - @GuardedBy("lock") Exception exception = new Exception(); + @GuardedBy("lock") myClass myClassInstance = new myClass(); - class MyParametrizedType { - T foo; - int l; - } + @GuardedBy("lock") Exception exception = new Exception(); - @GuardedBy("lock") MyParametrizedType myParametrizedType = new MyParametrizedType<>(); + class MyParametrizedType { + T foo; + int l; + } - MyClass getFooWithWrongReturnType() { - // :: error: (return.type.incompatible) - return foo; // return of guarded object - } + @GuardedBy("lock") MyParametrizedType myParametrizedType = new MyParametrizedType<>(); - @GuardedBy("lock") MyClass getFoo() { - return foo; - } + MyClass getFooWithWrongReturnType() { + // :: error: (return.type.incompatible) + return foo; // return of guarded object + } - MyClass @GuardedBy("lock") [] getFooArray() { - return fooArray; - } + @GuardedBy("lock") MyClass getFoo() { + return foo; + } - @GuardedBy("lock") MyClass[] getFooArray2() { - return fooArray2; - } + MyClass @GuardedBy("lock") [] getFooArray() { + return fooArray; + } - @GuardedBy("lock") MyClass[][] getFooArray3() { - return fooArray3; - } + @GuardedBy("lock") MyClass[] getFooArray2() { + return fooArray2; + } - MyClass @GuardedBy("lock") [][] getFooArray4() { - return fooArray4; - } + @GuardedBy("lock") MyClass[][] getFooArray3() { + return fooArray3; + } - MyClass[] @GuardedBy("lock") [] getFooArray5() { - return fooArray5; - } + MyClass @GuardedBy("lock") [][] getFooArray4() { + return fooArray4; + } - enum myEnumType { - ABC, - DEF - } + MyClass[] @GuardedBy("lock") [] getFooArray5() { + return fooArray5; + } - @GuardedBy("lock") myEnumType myEnum; + enum myEnumType { + ABC, + DEF + } - void testEnumType() { - // TODO: "assignment.type.incompatible" is technically correct, but we could - // make it friendlier for the user if constant enum values on the RHS - // automatically cast to the @GuardedBy annotation of the LHS. - // :: error: (assignment.type.incompatible) - myEnum = myEnumType.ABC; - } + @GuardedBy("lock") myEnumType myEnum; - final Object intrinsicLock = new Object(); + void testEnumType() { + // TODO: "assignment.type.incompatible" is technically correct, but we could + // make it friendlier for the user if constant enum values on the RHS + // automatically cast to the @GuardedBy annotation of the LHS. + // :: error: (assignment.type.incompatible) + myEnum = myEnumType.ABC; + } - void testThreadHoldsLock(@GuardedBy("intrinsicLock") MyClass m) { - if (Thread.holdsLock(intrinsicLock)) { - m.field.toString(); - } else { - // :: error: (lock.not.held) - m.field.toString(); - } - } + final Object intrinsicLock = new Object(); - void testTreeTypes() { - int i, l; - - MyClass o = new MyClass(); - MyClass f = new MyClass(); - - // The following test cases were inspired by - // org.checkerframework.afu.annotator.find.ASTPathCriterion.isSatisfiedBy - // in the Annotation File Utilities. - - // TODO: File a bug for the dataflow issue mentioned in the line below. - // TODO: uncomment: Hits a bug in dataflow: - // do { - // break; - // } while (foo.field != null); // access to guarded object in condition of do/while loop - // :: error: (lock.not.held) - for (foo = new MyClass(); foo.field != null; foo = new MyClass()) { - break; - } // access to guarded object in condition of for loop - // assignment to guarded object (OK) --- foo is still refined to @GuardedBy("lock") after - // this point, though. - foo = new MyClass(); - // A simple method call to a guarded object is not considered a dereference (only field - // accesses are considered dereferences). - unguardedFoo.method2(); - // Same as above, but the guard must be satisfied if the receiver is @GuardSatisfied. - // :: error: (lock.not.held) - foo.method2(); - // attempt to use guarded object in a switch statement - // :: error: (lock.not.held) - switch (foo.field.hashCode()) { - } - // attempt to use guarded object inside a try with resources - // try(foo = new MyClass()) { foo.field.toString(); } - - // Retrieving an element from a guarded array is a dereference - // :: error: (lock.not.held) - MyClass m = fooArray[0]; - - // method call on dereference of unguarded element of *guarded* array - // :: error: (lock.not.held) - fooArray[0].field.toString(); - // :: error: (lock.not.held) - l = fooArray.length; // dereference of guarded array itself - - // method call on dereference of guarded array element - // :: error: (lock.not.held) - fooArray2[0].field.toString(); - // method call on dereference of unguarded array - TODO: currently preconditions are not - // retrieved correctly from array types. This is not unique to the Lock Checker. - // fooArray2.field.toString(); - - // method call on dereference of guarded array element of multidimensional array - // :: error: (lock.not.held) - fooArray3[0][0].field.toString(); - // method call on dereference of unguarded single-dimensional array element of unguarded - // multidimensional array - TODO: currently preconditions are not retrieved correctly from - // array types. This is not unique to the Lock Checker. - // fooArray3[0].field.toString(); - // method call on dereference of unguarded multidimensional array - TODO: currently - // preconditions are not retrieved correctly from array types. This is not unique to the - // Lock Checker. - // fooArray3.field.toString(); - - // method call on dereference of unguarded array element of *guarded* multidimensional array - // :: error: (lock.not.held) - fooArray4[0][0].field.toString(); - // dereference of unguarded single-dimensional array element of *guarded* multidimensional - // array - // :: error: (lock.not.held) - l = fooArray4[0].length; - // dereference of guarded multidimensional array - // :: error: (lock.not.held) - l = fooArray4.length; - - // method call on dereference of unguarded array element of *guarded subarray* of - // multidimensional array - // :: error: (lock.not.held) - fooArray5[0][0].field.toString(); - // dereference of guarded single-dimensional array element of multidimensional array - // :: error: (lock.not.held) - l = fooArray5[0].length; - // dereference of unguarded multidimensional array - l = fooArray5.length; - - // :: error: (lock.not.held) - l = getFooArray().length; // dereference of guarded array returned by a method - - // method call on dereference of guarded array element returned by a method - // :: error: (lock.not.held) - getFooArray2()[0].field.toString(); - // dereference of unguarded array returned by a method - l = getFooArray2().length; - - // method call on dereference of guarded array element of multidimensional array returned by - // a method - // :: error: (lock.not.held) - getFooArray3()[0][0].field.toString(); - // dereference of unguarded single-dimensional array element of multidimensional array - // returned by a method - l = getFooArray3()[0].length; - // dereference of unguarded multidimensional array returned by a method - l = getFooArray3().length; - - // method call on dereference of unguarded array element of *guarded* multidimensional array - // returned by a method - // :: error: (lock.not.held) - getFooArray4()[0][0].field.toString(); - // dereference of unguarded single-dimensional array element of *guarded* multidimensional - // array returned by a method - // :: error: (lock.not.held) - l = getFooArray4()[0].length; - // dereference of guarded multidimensional array returned by a method - // :: error: (lock.not.held) - l = getFooArray4().length; - - // method call on dereference of unguarded array element of *guarded subarray* of - // multidimensional array returned by a method - // :: error: (lock.not.held) - getFooArray5()[0][0].field.toString(); - // dereference of guarded single-dimensional array element of multidimensional array - // returned by a method - // :: error: (lock.not.held) - l = getFooArray5()[0].length; - // dereference of unguarded multidimensional array returned by a method - l = getFooArray5().length; - - // Test different @GuardedBy(...) present on the element and array locations. - @SuppressWarnings("lock:assignment") // prevent flow-sensitive type refinement - @GuardedBy("lock") MyClass @GuardedBy("lock2") [] array = newMyClassArray(); - // :: error: (lock.not.held) + void testThreadHoldsLock(@GuardedBy("intrinsicLock") MyClass m) { + if (Thread.holdsLock(intrinsicLock)) { + m.field.toString(); + } else { + // :: error: (lock.not.held) + m.field.toString(); + } + } + + void testTreeTypes() { + int i, l; + + MyClass o = new MyClass(); + MyClass f = new MyClass(); + + // The following test cases were inspired by + // org.checkerframework.afu.annotator.find.ASTPathCriterion.isSatisfiedBy + // in the Annotation File Utilities. + + // TODO: File a bug for the dataflow issue mentioned in the line below. + // TODO: uncomment: Hits a bug in dataflow: + // do { + // break; + // } while (foo.field != null); // access to guarded object in condition of do/while loop + // :: error: (lock.not.held) + for (foo = new MyClass(); foo.field != null; foo = new MyClass()) { + break; + } // access to guarded object in condition of for loop + // assignment to guarded object (OK) --- foo is still refined to @GuardedBy("lock") after + // this point, though. + foo = new MyClass(); + // A simple method call to a guarded object is not considered a dereference (only field + // accesses are considered dereferences). + unguardedFoo.method2(); + // Same as above, but the guard must be satisfied if the receiver is @GuardSatisfied. + // :: error: (lock.not.held) + foo.method2(); + // attempt to use guarded object in a switch statement + // :: error: (lock.not.held) + switch (foo.field.hashCode()) { + } + // attempt to use guarded object inside a try with resources + // try(foo = new MyClass()) { foo.field.toString(); } + + // Retrieving an element from a guarded array is a dereference + // :: error: (lock.not.held) + MyClass m = fooArray[0]; + + // method call on dereference of unguarded element of *guarded* array + // :: error: (lock.not.held) + fooArray[0].field.toString(); + // :: error: (lock.not.held) + l = fooArray.length; // dereference of guarded array itself + + // method call on dereference of guarded array element + // :: error: (lock.not.held) + fooArray2[0].field.toString(); + // method call on dereference of unguarded array - TODO: currently preconditions are not + // retrieved correctly from array types. This is not unique to the Lock Checker. + // fooArray2.field.toString(); + + // method call on dereference of guarded array element of multidimensional array + // :: error: (lock.not.held) + fooArray3[0][0].field.toString(); + // method call on dereference of unguarded single-dimensional array element of unguarded + // multidimensional array - TODO: currently preconditions are not retrieved correctly from + // array types. This is not unique to the Lock Checker. + // fooArray3[0].field.toString(); + // method call on dereference of unguarded multidimensional array - TODO: currently + // preconditions are not retrieved correctly from array types. This is not unique to the + // Lock Checker. + // fooArray3.field.toString(); + + // method call on dereference of unguarded array element of *guarded* multidimensional array + // :: error: (lock.not.held) + fooArray4[0][0].field.toString(); + // dereference of unguarded single-dimensional array element of *guarded* multidimensional + // array + // :: error: (lock.not.held) + l = fooArray4[0].length; + // dereference of guarded multidimensional array + // :: error: (lock.not.held) + l = fooArray4.length; + + // method call on dereference of unguarded array element of *guarded subarray* of + // multidimensional array + // :: error: (lock.not.held) + fooArray5[0][0].field.toString(); + // dereference of guarded single-dimensional array element of multidimensional array + // :: error: (lock.not.held) + l = fooArray5[0].length; + // dereference of unguarded multidimensional array + l = fooArray5.length; + + // :: error: (lock.not.held) + l = getFooArray().length; // dereference of guarded array returned by a method + + // method call on dereference of guarded array element returned by a method + // :: error: (lock.not.held) + getFooArray2()[0].field.toString(); + // dereference of unguarded array returned by a method + l = getFooArray2().length; + + // method call on dereference of guarded array element of multidimensional array returned by + // a method + // :: error: (lock.not.held) + getFooArray3()[0][0].field.toString(); + // dereference of unguarded single-dimensional array element of multidimensional array + // returned by a method + l = getFooArray3()[0].length; + // dereference of unguarded multidimensional array returned by a method + l = getFooArray3().length; + + // method call on dereference of unguarded array element of *guarded* multidimensional array + // returned by a method + // :: error: (lock.not.held) + getFooArray4()[0][0].field.toString(); + // dereference of unguarded single-dimensional array element of *guarded* multidimensional + // array returned by a method + // :: error: (lock.not.held) + l = getFooArray4()[0].length; + // dereference of guarded multidimensional array returned by a method + // :: error: (lock.not.held) + l = getFooArray4().length; + + // method call on dereference of unguarded array element of *guarded subarray* of + // multidimensional array returned by a method + // :: error: (lock.not.held) + getFooArray5()[0][0].field.toString(); + // dereference of guarded single-dimensional array element of multidimensional array + // returned by a method + // :: error: (lock.not.held) + l = getFooArray5()[0].length; + // dereference of unguarded multidimensional array returned by a method + l = getFooArray5().length; + + // Test different @GuardedBy(...) present on the element and array locations. + @SuppressWarnings("lock:assignment") // prevent flow-sensitive type refinement + @GuardedBy("lock") MyClass @GuardedBy("lock2") [] array = newMyClassArray(); + // :: error: (lock.not.held) + array[0].field = new Object(); + if (lock.isHeldByCurrentThread()) { + // :: error: (lock.not.held) + array[0].field = new Object(); + if (lock2.isHeldByCurrentThread()) { array[0].field = new Object(); - if (lock.isHeldByCurrentThread()) { - // :: error: (lock.not.held) - array[0].field = new Object(); - if (lock2.isHeldByCurrentThread()) { - array[0].field = new Object(); - } - } - - // method call on guarded object within parenthesized expression - // :: error: (lock.not.held) - String s = (foo.field.toString()); - // :: error: (lock.not.held) - foo.field.toString(); // method call on guarded object - // :: error: (lock.not.held) - getFoo().field.toString(); // method call on guarded object returned by a method - // :: error: (lock.not.held) - this.foo.field.toString(); // method call on guarded object using 'this' literal - // dereference of guarded object in labeled statement - label: - // :: error: (lock.not.held) - foo.field.toString(); - // access to guarded object in instanceof expression (OK) - if (foo instanceof MyClass) {} - // access to guarded object in while condition of while loop (OK) - while (foo != null) { - break; - } - // binary operator on guarded object in else if condition (OK) - if (false) { - } else if (foo == o) { - } - // access to guarded object in a lambda expression - Runnable rn = - () -> { - // :: error: (lock.not.held) - foo.field.toString(); - }; - // :: error: (lock.not.held) - i = myClassInstance.i; // access to member field of guarded object - // MemberReferenceTrees? how do they work - fooArray = new MyClass[3]; // second allocation of guarded array (OK) - // dereference of guarded object in conditional expression tree - // :: error: (lock.not.held) - s = i == 5 ? foo.field.toString() : f.field.toString(); - // dereference of guarded object in conditional expression tree - // :: error: (lock.not.held) - s = i == 5 ? f.field.toString() : foo.field.toString(); - // Testing of 'return' is done in getFooWithWrongReturnType() - // throwing a guarded object - when throwing an exception, it must be @GuardedBy({}). Even - // @GuardedByUnknown is not allowed. - try { - // :: error: (throw.type.invalid) - throw exception; - } catch (Exception e) { - } - // casting of a guarded object to an unguarded object - // :: error: (assignment.type.incompatible) - @GuardedBy({}) Object e1 = (Object) exception; - // OK, since the local variable's type gets refined to @GuardedBy("lock") - Object e2 = (Object) exception; - // :: error: (lock.not.held) - l = myParametrizedType.l; // dereference of guarded object having a parameterized type - - // We need to support locking on local variables and protecting local variables because - // these locals may contain references to fields. Somehow we need to pass along the - // information of which field it was. - - if (foo == o) { // binary operator on guarded object (OK) - o.field.toString(); - } - - if (foo == null) { - // With -AconcurrentSemantics turned off, a cannot.dereference error would be expected, - // since there is an attempt to dereference an expression whose type has been refined to - // @GuardedByBottom (due to the comparison to null). However, with -AconcurrentSemantics - // turned on, foo may no longer be null by now, the refinement to @GuardedByBottom is - // lost and the refined type of foo is now the declared type ( @GuardedBy("lock") ), - // resulting in the lock.not.held error. - // :: error: (lock.not.held) - foo.field.toString(); - } - - // TODO: Reenable: - // @PolyGuardedBy should not be written here, but it is not explicitly forbidden by the - // framework. - // @PolyGuardedBy MyClass m2 = new MyClass(); - // (cannot.dereference) - // m2.field.toString(); + } } - @GuardedBy("lock") MyClass guardedByLock() { - return new MyClass(); + // method call on guarded object within parenthesized expression + // :: error: (lock.not.held) + String s = (foo.field.toString()); + // :: error: (lock.not.held) + foo.field.toString(); // method call on guarded object + // :: error: (lock.not.held) + getFoo().field.toString(); // method call on guarded object returned by a method + // :: error: (lock.not.held) + this.foo.field.toString(); // method call on guarded object using 'this' literal + // dereference of guarded object in labeled statement + label: + // :: error: (lock.not.held) + foo.field.toString(); + // access to guarded object in instanceof expression (OK) + if (foo instanceof MyClass) {} + // access to guarded object in while condition of while loop (OK) + while (foo != null) { + break; + } + // binary operator on guarded object in else if condition (OK) + if (false) { + } else if (foo == o) { + } + // access to guarded object in a lambda expression + Runnable rn = + () -> { + // :: error: (lock.not.held) + foo.field.toString(); + }; + // :: error: (lock.not.held) + i = myClassInstance.i; // access to member field of guarded object + // MemberReferenceTrees? how do they work + fooArray = new MyClass[3]; // second allocation of guarded array (OK) + // dereference of guarded object in conditional expression tree + // :: error: (lock.not.held) + s = i == 5 ? foo.field.toString() : f.field.toString(); + // dereference of guarded object in conditional expression tree + // :: error: (lock.not.held) + s = i == 5 ? f.field.toString() : foo.field.toString(); + // Testing of 'return' is done in getFooWithWrongReturnType() + // throwing a guarded object - when throwing an exception, it must be @GuardedBy({}). Even + // @GuardedByUnknown is not allowed. + try { + // :: error: (throw.type.invalid) + throw exception; + } catch (Exception e) { + } + // casting of a guarded object to an unguarded object + // :: error: (assignment.type.incompatible) + @GuardedBy({}) Object e1 = (Object) exception; + // OK, since the local variable's type gets refined to @GuardedBy("lock") + Object e2 = (Object) exception; + // :: error: (lock.not.held) + l = myParametrizedType.l; // dereference of guarded object having a parameterized type + + // We need to support locking on local variables and protecting local variables because + // these locals may contain references to fields. Somehow we need to pass along the + // information of which field it was. + + if (foo == o) { // binary operator on guarded object (OK) + o.field.toString(); } - @GuardedByUnknown MyClass someValue() { - return new MyClass(); + if (foo == null) { + // With -AconcurrentSemantics turned off, a cannot.dereference error would be expected, + // since there is an attempt to dereference an expression whose type has been refined to + // @GuardedByBottom (due to the comparison to null). However, with -AconcurrentSemantics + // turned on, foo may no longer be null by now, the refinement to @GuardedByBottom is + // lost and the refined type of foo is now the declared type ( @GuardedBy("lock") ), + // resulting in the lock.not.held error. + // :: error: (lock.not.held) + foo.field.toString(); } - @MayReleaseLocks - public void testLocals() { - final ReentrantLock localLock = new ReentrantLock(); + // TODO: Reenable: + // @PolyGuardedBy should not be written here, but it is not explicitly forbidden by the + // framework. + // @PolyGuardedBy MyClass m2 = new MyClass(); + // (cannot.dereference) + // m2.field.toString(); + } - @SuppressWarnings("assignment") // prevent flow-sensitive refinement - @GuardedBy("localLock") MyClass guardedByLocalLock = someValue(); + @GuardedBy("lock") MyClass guardedByLock() { + return new MyClass(); + } - // :: error: (lock.not.held) - guardedByLocalLock.field.toString(); + @GuardedByUnknown MyClass someValue() { + return new MyClass(); + } - @GuardedBy("lock") MyClass local = guardedByLock(); + @MayReleaseLocks + public void testLocals() { + final ReentrantLock localLock = new ReentrantLock(); - // :: error: (lock.not.held) - local.field.toString(); + @SuppressWarnings("assignment") // prevent flow-sensitive refinement + @GuardedBy("localLock") MyClass guardedByLocalLock = someValue(); - lockTheLock(); + // :: error: (lock.not.held) + guardedByLocalLock.field.toString(); - local.field.toString(); // No warning output + @GuardedBy("lock") MyClass local = guardedByLock(); - unlockTheLock(); - } + // :: error: (lock.not.held) + local.field.toString(); - @MayReleaseLocks - public void testMethodAnnotations() { - Random r = new Random(); - - if (r.nextBoolean()) { - lockTheLock(); - requiresLockHeldMethod(); - } else { - // :: error: (contracts.precondition.not.satisfied) - requiresLockHeldMethod(); - } - - if (r.nextBoolean()) { - lockTheLock(); - foo.field.toString(); - - unlockTheLock(); - - // :: error: (lock.not.held) - foo.field.toString(); - } else { - // :: error: (lock.not.held) - foo.field.toString(); - } - - if (tryToLockTheLock()) { - foo.field.toString(); - } else { - // :: error: (lock.not.held) - foo.field.toString(); - } - - if (r.nextBoolean()) { - lockTheLock(); - sideEffectFreeMethod(); - foo.field.toString(); - } else { - lockTheLock(); - nonSideEffectFreeMethod(); - // :: error: (lock.not.held) - foo.field.toString(); - } - - if (r.nextBoolean()) { - lockTheLock(); - lockingFreeMethod(); - foo.field.toString(); - } else { - lockTheLock(); - nonSideEffectFreeMethod(); - // :: error: (lock.not.held) - foo.field.toString(); - } - } + lockTheLock(); - void methodThatTakesAnInteger(Integer i) {} + local.field.toString(); // No warning output - void testBoxedPrimitiveType() { - Integer i = null; - if (i == null) {} + unlockTheLock(); + } - methodThatTakesAnInteger(i); + @MayReleaseLocks + public void testMethodAnnotations() { + Random r = new Random(); + + if (r.nextBoolean()) { + lockTheLock(); + requiresLockHeldMethod(); + } else { + // :: error: (contracts.precondition.not.satisfied) + requiresLockHeldMethod(); } - void testReceiverGuardedByItself(@GuardedBy("") TestTreeKinds this) { - // :: error: (lock.not.held) - method(); - synchronized (this) { - method(); - } + if (r.nextBoolean()) { + lockTheLock(); + foo.field.toString(); + + unlockTheLock(); + + // :: error: (lock.not.held) + foo.field.toString(); + } else { + // :: error: (lock.not.held) + foo.field.toString(); } - void method(@GuardSatisfied TestTreeKinds this) {} + if (tryToLockTheLock()) { + foo.field.toString(); + } else { + // :: error: (lock.not.held) + foo.field.toString(); + } - void testOtherClassReceiverGuardedByItself(final @GuardedBy("") OtherClass o) { - // :: error: (lock.not.held) - o.foo(); - synchronized (o) { - o.foo(); - } + if (r.nextBoolean()) { + lockTheLock(); + sideEffectFreeMethod(); + foo.field.toString(); + } else { + lockTheLock(); + nonSideEffectFreeMethod(); + // :: error: (lock.not.held) + foo.field.toString(); } - class OtherClass { - void foo(@GuardSatisfied OtherClass this) {} + if (r.nextBoolean()) { + lockTheLock(); + lockingFreeMethod(); + foo.field.toString(); + } else { + lockTheLock(); + nonSideEffectFreeMethod(); + // :: error: (lock.not.held) + foo.field.toString(); } + } + + void methodThatTakesAnInteger(Integer i) {} - void testExplicitLockSynchronized() { - final ReentrantLock lock = new ReentrantLock(); - // :: error: (explicit.lock.synchronized) - synchronized (lock) { - } + void testBoxedPrimitiveType() { + Integer i = null; + if (i == null) {} + + methodThatTakesAnInteger(i); + } + + void testReceiverGuardedByItself(@GuardedBy("") TestTreeKinds this) { + // :: error: (lock.not.held) + method(); + synchronized (this) { + method(); } + } + + void method(@GuardSatisfied TestTreeKinds this) {} - void testPrimitiveTypeGuardedby() { - // :: error: (immutable.type.guardedby) - @GuardedBy("lock") int a = 0; - // :: error: (immutable.type.guardedby) - @GuardedBy int b = 0; - // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied int c = 0; - // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied(1) int d = 0; - int e = 0; - // :: error: (immutable.type.guardedby) - @GuardedByUnknown int f = 0; - // :: error: (immutable.type.guardedby) :: error: (assignment.type.incompatible) - @GuardedByBottom int g = 0; + void testOtherClassReceiverGuardedByItself(final @GuardedBy("") OtherClass o) { + // :: error: (lock.not.held) + o.foo(); + synchronized (o) { + o.foo(); } + } - void testBinaryOperatorBooleanResultIsAlwaysGuardedByNothing() { - @GuardedBy("lock") Object o1 = new Object(); - Object o2 = new Object(); - // boolean variables are implicitly @GuardedBy({}). - boolean b1 = o1 == o2; - boolean b2 = o2 == o1; - boolean b3 = o1 != o2; - boolean b4 = o2 != o1; - boolean b5 = o1 instanceof Object; - boolean b6 = o2 instanceof Object; - boolean b7 = o1 instanceof @GuardedBy("lock") Object; - boolean b8 = o2 instanceof @GuardedBy("lock") Object; + class OtherClass { + void foo(@GuardSatisfied OtherClass this) {} + } + + void testExplicitLockSynchronized() { + final ReentrantLock lock = new ReentrantLock(); + // :: error: (explicit.lock.synchronized) + synchronized (lock) { } + } + + void testPrimitiveTypeGuardedby() { + // :: error: (immutable.type.guardedby) + @GuardedBy("lock") int a = 0; + // :: error: (immutable.type.guardedby) + @GuardedBy int b = 0; + // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied int c = 0; + // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied(1) int d = 0; + int e = 0; + // :: error: (immutable.type.guardedby) + @GuardedByUnknown int f = 0; + // :: error: (immutable.type.guardedby) :: error: (assignment.type.incompatible) + @GuardedByBottom int g = 0; + } + + void testBinaryOperatorBooleanResultIsAlwaysGuardedByNothing() { + @GuardedBy("lock") Object o1 = new Object(); + Object o2 = new Object(); + // boolean variables are implicitly @GuardedBy({}). + boolean b1 = o1 == o2; + boolean b2 = o2 == o1; + boolean b3 = o1 != o2; + boolean b4 = o2 != o1; + boolean b5 = o1 instanceof Object; + boolean b6 = o2 instanceof Object; + boolean b7 = o1 instanceof @GuardedBy("lock") Object; + boolean b8 = o2 instanceof @GuardedBy("lock") Object; + } } diff --git a/checker/tests/lock/ThisPostCondition.java b/checker/tests/lock/ThisPostCondition.java index 0e31d1ea5ef..59c58248504 100644 --- a/checker/tests/lock/ThisPostCondition.java +++ b/checker/tests/lock/ThisPostCondition.java @@ -4,65 +4,65 @@ import org.checkerframework.checker.lock.qual.Holding; class MyReentrantLock { - final Object myfield = new Object(); + final Object myfield = new Object(); - @Holding("myfield") - @EnsuresLockHeld("this") - void lock() { - this.lock(); - } + @Holding("myfield") + @EnsuresLockHeld("this") + void lock() { + this.lock(); + } - @EnsuresLockHeld("this") - void lock2() { - this.lock2(); - } + @EnsuresLockHeld("this") + void lock2() { + this.lock2(); + } - @Holding("myfield") - void notLock() {} + @Holding("myfield") + void notLock() {} - boolean b = false; + boolean b = false; - @EnsuresLockHeldIf(expression = "this", result = true) - boolean tryLock() { - if (b) { - lock2(); - return true; - } - return false; + @EnsuresLockHeldIf(expression = "this", result = true) + boolean tryLock() { + if (b) { + lock2(); + return true; } + return false; + } } public class ThisPostCondition { - final MyReentrantLock myLock = new MyReentrantLock(); + final MyReentrantLock myLock = new MyReentrantLock(); - @GuardedBy("myLock") Bar bar = new Bar(); + @GuardedBy("myLock") Bar bar = new Bar(); - @Holding("myLock.myfield") - void lockTheLock() { - myLock.lock(); - bar.field.toString(); - } + @Holding("myLock.myfield") + void lockTheLock() { + myLock.lock(); + bar.field.toString(); + } - void lockTheLock2() { - myLock.lock2(); - bar.field.toString(); - } + void lockTheLock2() { + myLock.lock2(); + bar.field.toString(); + } - void doNotLock() { - // :: error: (lock.not.held) - bar.field.toString(); - } + void doNotLock() { + // :: error: (lock.not.held) + bar.field.toString(); + } - void tryTryLock() { - if (myLock.tryLock()) { - bar.field.toString(); - } else { - // :: error: (lock.not.held) - bar.field.toString(); - } + void tryTryLock() { + if (myLock.tryLock()) { + bar.field.toString(); + } else { + // :: error: (lock.not.held) + bar.field.toString(); } + } } class Bar { - Object field; + Object field; } diff --git a/checker/tests/lock/ThisSuper.java b/checker/tests/lock/ThisSuper.java index a6a5b85b508..a3116a77743 100644 --- a/checker/tests/lock/ThisSuper.java +++ b/checker/tests/lock/ThisSuper.java @@ -7,85 +7,84 @@ public class ThisSuper { - class MyClass { - Object field; - } + class MyClass { + Object field; + } - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @GuardedBy("myLock") MyClass locked; + protected @GuardedBy("myLock") MyClass locked; - @GuardedBy("this.myLock") MyClass m1; + @GuardedBy("this.myLock") MyClass m1; - protected @GuardedBy("this.myLock") MyClass locked2; + protected @GuardedBy("this.myLock") MyClass locked2; - public void accessLock() { - synchronized (myLock) { - this.locked.field = new Object(); - } - } + public void accessLock() { + synchronized (myLock) { + this.locked.field = new Object(); + } + } + } + + class LockExampleSubclass extends LockExample { + private final Object myLock = new Object(); + + private @GuardedBy("this.myLock") MyClass locked; + + @GuardedBy("this.myLock") MyClass m2; + + public LockExampleSubclass() { + final LockExampleSubclass les1 = new LockExampleSubclass(); + final LockExampleSubclass les2 = new LockExampleSubclass(); + final LockExampleSubclass les3 = les2; + LockExample le1 = new LockExample(); + + synchronized (super.myLock) { + super.locked.toString(); + super.locked2.toString(); + // :: error: (contracts.precondition.not.satisfied) + locked.toString(); + } + synchronized (myLock) { + // :: error: (contracts.precondition.not.satisfied) + super.locked.toString(); + // :: error: (contracts.precondition.not.satisfied) + super.locked2.toString(); + locked.toString(); + } + + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked; + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked2; + + // :: error: (assignment.type.incompatible) + les1.locked = les2.locked; + + // :: error: (assignment.type.incompatible) + this.locked = super.locked; + // :: error: (assignment.type.incompatible) + this.locked = super.locked2; + + // :: error: (assignment.type.incompatible) + m1 = m2; } - class LockExampleSubclass extends LockExample { - private final Object myLock = new Object(); - - private @GuardedBy("this.myLock") MyClass locked; - - @GuardedBy("this.myLock") MyClass m2; - - public LockExampleSubclass() { - final LockExampleSubclass les1 = new LockExampleSubclass(); - final LockExampleSubclass les2 = new LockExampleSubclass(); - final LockExampleSubclass les3 = les2; - LockExample le1 = new LockExample(); - - synchronized (super.myLock) { - super.locked.toString(); - super.locked2.toString(); - // :: error: (contracts.precondition.not.satisfied) - locked.toString(); - } - synchronized (myLock) { - // :: error: (contracts.precondition.not.satisfied) - super.locked.toString(); - // :: error: (contracts.precondition.not.satisfied) - super.locked2.toString(); - locked.toString(); - } - - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked; - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked2; - - // :: error: (assignment.type.incompatible) - les1.locked = les2.locked; - - // :: error: (assignment.type.incompatible) - this.locked = super.locked; - // :: error: (assignment.type.incompatible) - this.locked = super.locked2; - - // :: error: (assignment.type.incompatible) - m1 = m2; - } - - @Override - public void accessLock() { - synchronized (myLock) { - this.locked.field = new Object(); + @Override + public void accessLock() { + synchronized (myLock) { + this.locked.field = new Object(); + // :: error: (lock.not.held) + super.locked.field = new Object(); + System.out.println( + this.locked.field + + " " + + // :: error: (lock.not.held) - super.locked.field = new Object(); - System.out.println( - this.locked.field - + " " - + - // :: error: (lock.not.held) - super.locked.field); - System.out.println( - "Are locks equal? " + (super.locked == this.locked ? "yes" : "no")); - } - } + super.locked.field); + System.out.println("Are locks equal? " + (super.locked == this.locked ? "yes" : "no")); + } } + } } diff --git a/checker/tests/lock/TypeVarNull.java b/checker/tests/lock/TypeVarNull.java index a4713a7d846..c3509773dc2 100644 --- a/checker/tests/lock/TypeVarNull.java +++ b/checker/tests/lock/TypeVarNull.java @@ -1,3 +1,3 @@ public class TypeVarNull { - T t = null; + T t = null; } diff --git a/checker/tests/lock/Update.java b/checker/tests/lock/Update.java index 66bd10f95d6..d1f3cd12320 100644 --- a/checker/tests/lock/Update.java +++ b/checker/tests/lock/Update.java @@ -2,12 +2,12 @@ public class Update { - void test() { - Object o1 = new Object(); - @GuardedBy({}) Object o2 = o1; - synchronized (o1) { - } - // o1 used to loss it refinement because of a bug. - @GuardedBy({}) Object o3 = o1; + void test() { + Object o1 = new Object(); + @GuardedBy({}) Object o2 = o1; + synchronized (o1) { } + // o1 used to loss it refinement because of a bug. + @GuardedBy({}) Object o3 = o1; + } } diff --git a/checker/tests/lock/ViewpointAdaptation.java b/checker/tests/lock/ViewpointAdaptation.java index a5dc4db90b7..fbc0afe3005 100644 --- a/checker/tests/lock/ViewpointAdaptation.java +++ b/checker/tests/lock/ViewpointAdaptation.java @@ -4,46 +4,46 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class ViewpointAdaptation { - // :: error: (expression.unparsable.type.invalid) - private final @GuardedBy("a") ViewpointAdaptation f = new ViewpointAdaptation(); + // :: error: (expression.unparsable.type.invalid) + private final @GuardedBy("a") ViewpointAdaptation f = new ViewpointAdaptation(); - private @GuardedBy("this.lock") ViewpointAdaptation g = new ViewpointAdaptation(); + private @GuardedBy("this.lock") ViewpointAdaptation g = new ViewpointAdaptation(); - private final Object lock = new Object(); + private final Object lock = new Object(); - private int counter; + private int counter; - public void method1(final String a) { - synchronized (a) { - // :: error: (expression.unparsable.type.invalid) - f.counter++; - } + public void method1(final String a) { + synchronized (a) { + // :: error: (expression.unparsable.type.invalid) + f.counter++; } + } - public void method2() { - ViewpointAdaptation t = new ViewpointAdaptation(); + public void method2() { + ViewpointAdaptation t = new ViewpointAdaptation(); - // :: error: (assignment.type.incompatible) - t.g = g; // "t.lock" != "this.lock" + // :: error: (assignment.type.incompatible) + t.g = g; // "t.lock" != "this.lock" - synchronized (t.lock) { - // :: error: (lock.not.held) - g.counter++; - } + synchronized (t.lock) { + // :: error: (lock.not.held) + g.counter++; } - - public void method3() { - final ViewpointAdaptation t = new ViewpointAdaptation(); - // The type of 'g' is refined from @GuardedByUnknown (the default for - // a local variable due to CLIMB-to-top semantics) to @GuardedBy("t.g") - final ViewpointAdaptation g = t.g; - Object l = t.lock; - - synchronized (l) { - // Aliasing of lock expressions is not tracked by the Lock Checker. - // The Lock Checker does not know that l == t.lock - // :: error: (lock.not.held) - g.counter++; - } + } + + public void method3() { + final ViewpointAdaptation t = new ViewpointAdaptation(); + // The type of 'g' is refined from @GuardedByUnknown (the default for + // a local variable due to CLIMB-to-top semantics) to @GuardedBy("t.g") + final ViewpointAdaptation g = t.g; + Object l = t.lock; + + synchronized (l) { + // Aliasing of lock expressions is not tracked by the Lock Checker. + // The Lock Checker does not know that l == t.lock + // :: error: (lock.not.held) + g.counter++; } + } } diff --git a/checker/tests/lock/ViewpointAdaptation2.java b/checker/tests/lock/ViewpointAdaptation2.java index a5a878b1a1e..6cd3b74ce46 100644 --- a/checker/tests/lock/ViewpointAdaptation2.java +++ b/checker/tests/lock/ViewpointAdaptation2.java @@ -5,44 +5,44 @@ public class ViewpointAdaptation2 { - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @GuardedBy("myLock") Object locked; + protected @GuardedBy("myLock") Object locked; - protected @GuardedBy("this.myLock") Object locked2; + protected @GuardedBy("this.myLock") Object locked2; - public @GuardedBy("myLock") Object getLocked() { - return locked; - } + public @GuardedBy("myLock") Object getLocked() { + return locked; } + } - class Use { - final LockExample lockExample1 = new LockExample(); - final Object myLock = new Object(); + class Use { + final LockExample lockExample1 = new LockExample(); + final Object myLock = new Object(); - @GuardedBy("lockExample1.myLock") Object o1 = lockExample1.locked; + @GuardedBy("lockExample1.myLock") Object o1 = lockExample1.locked; - @GuardedBy("lockExample1.myLock") Object o2 = lockExample1.locked2; + @GuardedBy("lockExample1.myLock") Object o2 = lockExample1.locked2; - // :: error: (assignment.type.incompatible) - @GuardedBy("myLock") Object o3 = lockExample1.locked; + // :: error: (assignment.type.incompatible) + @GuardedBy("myLock") Object o3 = lockExample1.locked; - // :: error: (assignment.type.incompatible) - @GuardedBy("this.myLock") Object o4 = lockExample1.locked2; + // :: error: (assignment.type.incompatible) + @GuardedBy("this.myLock") Object o4 = lockExample1.locked2; - @GuardedBy("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); + @GuardedBy("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); - // :: error: (assignment.type.incompatible) - @GuardedBy("myLock") Object oM2 = lockExample1.getLocked(); + // :: error: (assignment.type.incompatible) + @GuardedBy("myLock") Object oM2 = lockExample1.getLocked(); - // :: error: (assignment.type.incompatible) - @GuardedBy("this.myLock") Object oM3 = lockExample1.getLocked(); + // :: error: (assignment.type.incompatible) + @GuardedBy("this.myLock") Object oM3 = lockExample1.getLocked(); - void uses() { - lockExample1.locked = o1; - // :: error: (assignment.type.incompatible) - lockExample1.locked = o3; - } + void uses() { + lockExample1.locked = o1; + // :: error: (assignment.type.incompatible) + lockExample1.locked = o3; } + } } diff --git a/checker/tests/lock/ViewpointAdaptation3.java b/checker/tests/lock/ViewpointAdaptation3.java index 9cce33d8f1c..3eeb524d3dd 100644 --- a/checker/tests/lock/ViewpointAdaptation3.java +++ b/checker/tests/lock/ViewpointAdaptation3.java @@ -5,110 +5,110 @@ public class ViewpointAdaptation3 { - class MyClass { - Object field; - } + class MyClass { + Object field; + } - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @GuardedBy("myLock") MyClass locked; + protected @GuardedBy("myLock") MyClass locked; - @GuardedBy("this.myLock") MyClass m1; + @GuardedBy("this.myLock") MyClass m1; - protected @GuardedBy("this.myLock") MyClass locked2; + protected @GuardedBy("this.myLock") MyClass locked2; - public void accessLock() { - synchronized (myLock) { - this.locked.field = new Object(); - } - } + public void accessLock() { + synchronized (myLock) { + this.locked.field = new Object(); + } } + } - class LockExampleSubclass extends LockExample { - private final Object myLock = new Object(); + class LockExampleSubclass extends LockExample { + private final Object myLock = new Object(); - private @GuardedBy("this.myLock") MyClass locked; + private @GuardedBy("this.myLock") MyClass locked; - @GuardedBy("this.myLock") MyClass m2; + @GuardedBy("this.myLock") MyClass m2; - public LockExampleSubclass() { - final LockExampleSubclass les1 = new LockExampleSubclass(); - final LockExampleSubclass les2 = new LockExampleSubclass(); - final LockExampleSubclass les3 = les2; - LockExample le1 = new LockExample(); + public LockExampleSubclass() { + final LockExampleSubclass les1 = new LockExampleSubclass(); + final LockExampleSubclass les2 = new LockExampleSubclass(); + final LockExampleSubclass les3 = les2; + LockExample le1 = new LockExample(); - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked; - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked2; + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked; + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked2; - // :: error: (assignment.type.incompatible) - les1.locked = les2.locked; - } + // :: error: (assignment.type.incompatible) + les1.locked = les2.locked; } - - class Class1 { - public final Object lock = new Object(); - - @GuardedBy("lock") MyClass m = new MyClass(); - } - - class Class2 { - public final Object lock = new Object(); - - @GuardedBy("lock") MyClass m = new MyClass(); - - void method(final Class1 a) { - final Object lock = new Object(); - @GuardedBy("lock") MyClass local = new MyClass(); - - // :: error: (assignment.type.incompatible) - local = m; - - // :: error: (lock.not.held) - local.field = new Object(); - - synchronized (lock) { - // :: error: (lock.not.held) - a.m.field = new Object(); - } - synchronized (this.lock) { - // :: error: (lock.not.held) - a.m.field = new Object(); - } - synchronized (a.lock) { - a.m.field = new Object(); - } - - synchronized (lock) { - local.field = new Object(); - } - synchronized (this.lock) { - // :: error: (lock.not.held) - local.field = new Object(); - } - synchronized (a.lock) { - // :: error: (lock.not.held) - local.field = new Object(); - } - - synchronized (lock) { - // :: error: (lock.not.held) - this.m.field = new Object(); - // :: error: (lock.not.held) - m.field = new Object(); - } - synchronized (this.lock) { - this.m.field = new Object(); - m.field = new Object(); - } - synchronized (a.lock) { - // :: error: (lock.not.held) - this.m.field = new Object(); - // :: error: (lock.not.held) - m.field = new Object(); - } - } + } + + class Class1 { + public final Object lock = new Object(); + + @GuardedBy("lock") MyClass m = new MyClass(); + } + + class Class2 { + public final Object lock = new Object(); + + @GuardedBy("lock") MyClass m = new MyClass(); + + void method(final Class1 a) { + final Object lock = new Object(); + @GuardedBy("lock") MyClass local = new MyClass(); + + // :: error: (assignment.type.incompatible) + local = m; + + // :: error: (lock.not.held) + local.field = new Object(); + + synchronized (lock) { + // :: error: (lock.not.held) + a.m.field = new Object(); + } + synchronized (this.lock) { + // :: error: (lock.not.held) + a.m.field = new Object(); + } + synchronized (a.lock) { + a.m.field = new Object(); + } + + synchronized (lock) { + local.field = new Object(); + } + synchronized (this.lock) { + // :: error: (lock.not.held) + local.field = new Object(); + } + synchronized (a.lock) { + // :: error: (lock.not.held) + local.field = new Object(); + } + + synchronized (lock) { + // :: error: (lock.not.held) + this.m.field = new Object(); + // :: error: (lock.not.held) + m.field = new Object(); + } + synchronized (this.lock) { + this.m.field = new Object(); + m.field = new Object(); + } + synchronized (a.lock) { + // :: error: (lock.not.held) + this.m.field = new Object(); + // :: error: (lock.not.held) + m.field = new Object(); + } } + } } diff --git a/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java b/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java index 67a25a1a297..be274e3dabe 100644 --- a/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java +++ b/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java @@ -3,47 +3,47 @@ import org.checkerframework.checker.mustcall.qual.*; class BorrowOnReturn { - @InheritableMustCall("a") - class Foo { - void a() {} - } - - @Owning - Object getOwnedFoo() { - // :: error: (return.type.incompatible) - return new Foo(); - } - - Object getNoAnnoFoo() { - // Treat as owning, so warn - // :: error: (return.type.incompatible) - return new Foo(); - } - - @NotOwning - Object getNotOwningFooWrong() { - // :: error: (return.type.incompatible) - return new Foo(); - } - - Object getNotOwningFooRightButNoNotOwningAnno() { - Foo f = new Foo(); - f.a(); - // This is still an error for now, because it's treated as an owning pointer. TODO: fix this - // kind of FP? - // :: error: (return.type.incompatible) - return f; - } - - @NotOwning - Object getNotOwningFooRight() { - Foo f = new Foo(); - f.a(); - // :: error: (return.type.incompatible) - return f; - } - - @MustCall("a") Object getNotOwningFooRight2() { - return new Foo(); - } + @InheritableMustCall("a") + class Foo { + void a() {} + } + + @Owning + Object getOwnedFoo() { + // :: error: (return.type.incompatible) + return new Foo(); + } + + Object getNoAnnoFoo() { + // Treat as owning, so warn + // :: error: (return.type.incompatible) + return new Foo(); + } + + @NotOwning + Object getNotOwningFooWrong() { + // :: error: (return.type.incompatible) + return new Foo(); + } + + Object getNotOwningFooRightButNoNotOwningAnno() { + Foo f = new Foo(); + f.a(); + // This is still an error for now, because it's treated as an owning pointer. TODO: fix this + // kind of FP? + // :: error: (return.type.incompatible) + return f; + } + + @NotOwning + Object getNotOwningFooRight() { + Foo f = new Foo(); + f.a(); + // :: error: (return.type.incompatible) + return f; + } + + @MustCall("a") Object getNotOwningFooRight2() { + return new Foo(); + } } diff --git a/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java b/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java index 5ad580b4f78..a93deae7637 100644 --- a/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java +++ b/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java @@ -4,42 +4,41 @@ // This version is modified to expect that owning/notowning annotations do nothing, // for the -AnoLightweightOwnership flag. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.*; class NonOwningPolyInteraction { - void foo(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - void bar(@Owning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - void baz(InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - NonOwningPolyInteraction(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // extra param(s) here and on the next constructor because Java requires constructors to have - // different signatures. - NonOwningPolyInteraction(@Owning InputStream instream, int x) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - NonOwningPolyInteraction(InputStream instream, int x, int y) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } + void foo(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + void bar(@Owning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + void baz(InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + NonOwningPolyInteraction(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // extra param(s) here and on the next constructor because Java requires constructors to have + // different signatures. + NonOwningPolyInteraction(@Owning InputStream instream, int x) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + NonOwningPolyInteraction(InputStream instream, int x, int y) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } } diff --git a/checker/tests/mustcall-nolightweightownership/OwningParams.java b/checker/tests/mustcall-nolightweightownership/OwningParams.java index 4099b1a4434..29dd91ac418 100644 --- a/checker/tests/mustcall-nolightweightownership/OwningParams.java +++ b/checker/tests/mustcall-nolightweightownership/OwningParams.java @@ -5,9 +5,9 @@ import org.checkerframework.checker.mustcall.qual.*; class OwningParams { - static void o1(@Owning OwningParams o) {} + static void o1(@Owning OwningParams o) {} - void test(@Owning @MustCall({"a"}) OwningParams o) { - o1(o); - } + void test(@Owning @MustCall({"a"}) OwningParams o) { + o1(o); + } } diff --git a/checker/tests/mustcall/BinaryInputArchive.java b/checker/tests/mustcall/BinaryInputArchive.java index 978782c263e..8f414af5724 100644 --- a/checker/tests/mustcall/BinaryInputArchive.java +++ b/checker/tests/mustcall/BinaryInputArchive.java @@ -5,13 +5,13 @@ class BinaryInputArchive { - private DataInput in; + private DataInput in; - public BinaryInputArchive(DataInput in) { - this.in = in; - } + public BinaryInputArchive(DataInput in) { + this.in = in; + } - public static BinaryInputArchive getArchive(InputStream strm) { - return new BinaryInputArchive(new DataInputStream(strm)); - } + public static BinaryInputArchive getArchive(InputStream strm) { + return new BinaryInputArchive(new DataInputStream(strm)); + } } diff --git a/checker/tests/mustcall/BorrowOnReturn.java b/checker/tests/mustcall/BorrowOnReturn.java index fb432793112..af292dd4e04 100644 --- a/checker/tests/mustcall/BorrowOnReturn.java +++ b/checker/tests/mustcall/BorrowOnReturn.java @@ -3,46 +3,46 @@ import org.checkerframework.checker.mustcall.qual.*; class BorrowOnReturn { - @InheritableMustCall("a") - class Foo { - void a() {} - } - - @Owning - Object getOwnedFoo() { - // :: error: (return.type.incompatible) - return new Foo(); - } - - Object getNoAnnoFoo() { - // Treat as owning, so warn - // :: error: (return.type.incompatible) - return new Foo(); - } - - @NotOwning - Object getNotOwningFooWrong() { - // OCC must-call checker will warn about this; MC checker isn't responsible - return new Foo(); - } - - Object getNotOwningFooRightButNoNotOwningAnno() { - Foo f = new Foo(); - f.a(); - // This is still an error for now, because it's treated as an owning pointer. TODO: fix this - // kind of FP? - // :: error: (return.type.incompatible) - return f; - } - - @NotOwning - Object getNotOwningFooRight() { - Foo f = new Foo(); - f.a(); - return f; - } - - @MustCall("a") Object getNotOwningFooRight2() { - return new Foo(); - } + @InheritableMustCall("a") + class Foo { + void a() {} + } + + @Owning + Object getOwnedFoo() { + // :: error: (return.type.incompatible) + return new Foo(); + } + + Object getNoAnnoFoo() { + // Treat as owning, so warn + // :: error: (return.type.incompatible) + return new Foo(); + } + + @NotOwning + Object getNotOwningFooWrong() { + // OCC must-call checker will warn about this; MC checker isn't responsible + return new Foo(); + } + + Object getNotOwningFooRightButNoNotOwningAnno() { + Foo f = new Foo(); + f.a(); + // This is still an error for now, because it's treated as an owning pointer. TODO: fix this + // kind of FP? + // :: error: (return.type.incompatible) + return f; + } + + @NotOwning + Object getNotOwningFooRight() { + Foo f = new Foo(); + f.a(); + return f; + } + + @MustCall("a") Object getNotOwningFooRight2() { + return new Foo(); + } } diff --git a/checker/tests/mustcall/ClassForNameInit.java b/checker/tests/mustcall/ClassForNameInit.java index 28b83dc344a..de5904b791f 100644 --- a/checker/tests/mustcall/ClassForNameInit.java +++ b/checker/tests/mustcall/ClassForNameInit.java @@ -9,31 +9,31 @@ class ClassForNameInit { - public static InputStream inputStreamFactory() throws Exception { - // FYI this code will always fail if you run it, so don't. - // There's no ByteArrayInputStream constructor that takes no arguments. - Class baisClass = Class.forName("java.io.ByteArrayInputStream"); - Object bais = baisClass.getConstructor().newInstance(); - return (InputStream) bais; - } + public static InputStream inputStreamFactory() throws Exception { + // FYI this code will always fail if you run it, so don't. + // There's no ByteArrayInputStream constructor that takes no arguments. + Class baisClass = Class.forName("java.io.ByteArrayInputStream"); + Object bais = baisClass.getConstructor().newInstance(); + return (InputStream) bais; + } - public static Object objectFactory() throws Exception { - Class objClass = Class.forName("java.lang.Object"); - Object obj = objClass.getConstructor().newInstance(); - return (Object) obj; - } + public static Object objectFactory() throws Exception { + Class objClass = Class.forName("java.lang.Object"); + Object obj = objClass.getConstructor().newInstance(); + return (Object) obj; + } - private static Object getAuditLogger(String auditLoggerClass) { - if (auditLoggerClass == null) { - auditLoggerClass = Object.class.getName(); - } - try { - Constructor clientCxnConstructor = - Class.forName(auditLoggerClass).getDeclaredConstructor(); - Object auditLogger = (Object) clientCxnConstructor.newInstance(); - return auditLogger; - } catch (Exception e) { - throw new RuntimeException("Couldn't instantiate " + auditLoggerClass, e); - } + private static Object getAuditLogger(String auditLoggerClass) { + if (auditLoggerClass == null) { + auditLoggerClass = Object.class.getName(); + } + try { + Constructor clientCxnConstructor = + Class.forName(auditLoggerClass).getDeclaredConstructor(); + Object auditLogger = (Object) clientCxnConstructor.newInstance(); + return auditLogger; + } catch (Exception e) { + throw new RuntimeException("Couldn't instantiate " + auditLoggerClass, e); } + } } diff --git a/checker/tests/mustcall/CommandResponse.java b/checker/tests/mustcall/CommandResponse.java index a58f96a0937..39309ec5f9f 100644 --- a/checker/tests/mustcall/CommandResponse.java +++ b/checker/tests/mustcall/CommandResponse.java @@ -5,13 +5,13 @@ import java.util.Map; class CommandResponse { - Map data; + Map data; - public void putAll(Map m) { - data.putAll(m); - } + public void putAll(Map m) { + data.putAll(m); + } - public void putAll2(Map m) { - data.putAll(m); - } + public void putAll2(Map m) { + data.putAll(m); + } } diff --git a/checker/tests/mustcall/CreatesMustCallForSimple.java b/checker/tests/mustcall/CreatesMustCallForSimple.java index 097f4101e75..5ab7be89f43 100644 --- a/checker/tests/mustcall/CreatesMustCallForSimple.java +++ b/checker/tests/mustcall/CreatesMustCallForSimple.java @@ -5,53 +5,53 @@ @InheritableMustCall("a") class CreatesMustCallForSimple { - @CreatesMustCallFor - void reset() {} - - @CreatesMustCallFor("this") - void resetThis() {} - - static @MustCall({}) CreatesMustCallForSimple makeNoMC() { - return null; - } - - static void test1() { - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.reset(); - // :: error: assignment.type.incompatible - @MustCall({}) CreatesMustCallForSimple b = cos; - @MustCall("a") CreatesMustCallForSimple c = cos; - } - - static void test2() { - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.resetThis(); - // :: error: assignment.type.incompatible - @MustCall({}) CreatesMustCallForSimple b = cos; - @MustCall("a") CreatesMustCallForSimple c = cos; - } - - static void test3() { - Object cos = makeNoMC(); - @MustCall({}) Object a = cos; - // :: error: createsmustcallfor.target.unparseable - ((CreatesMustCallForSimple) cos).reset(); - // It would be better to issue an assignment incompatible error here, but the - // error above is okay too. - @MustCall({}) Object b = cos; - @MustCall("a") Object c = cos; - } - - // Rewrite of test3 that follows the instructions in the error message. - static void test4() { - Object cos = makeNoMC(); - @MustCall({}) Object a = cos; - CreatesMustCallForSimple r = ((CreatesMustCallForSimple) cos); - r.reset(); - // :: error: assignment.type.incompatible - @MustCall({}) Object b = r; - @MustCall("a") Object c = r; - } + @CreatesMustCallFor + void reset() {} + + @CreatesMustCallFor("this") + void resetThis() {} + + static @MustCall({}) CreatesMustCallForSimple makeNoMC() { + return null; + } + + static void test1() { + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.reset(); + // :: error: assignment.type.incompatible + @MustCall({}) CreatesMustCallForSimple b = cos; + @MustCall("a") CreatesMustCallForSimple c = cos; + } + + static void test2() { + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.resetThis(); + // :: error: assignment.type.incompatible + @MustCall({}) CreatesMustCallForSimple b = cos; + @MustCall("a") CreatesMustCallForSimple c = cos; + } + + static void test3() { + Object cos = makeNoMC(); + @MustCall({}) Object a = cos; + // :: error: createsmustcallfor.target.unparseable + ((CreatesMustCallForSimple) cos).reset(); + // It would be better to issue an assignment incompatible error here, but the + // error above is okay too. + @MustCall({}) Object b = cos; + @MustCall("a") Object c = cos; + } + + // Rewrite of test3 that follows the instructions in the error message. + static void test4() { + Object cos = makeNoMC(); + @MustCall({}) Object a = cos; + CreatesMustCallForSimple r = ((CreatesMustCallForSimple) cos); + r.reset(); + // :: error: assignment.type.incompatible + @MustCall({}) Object b = r; + @MustCall("a") Object c = r; + } } diff --git a/checker/tests/mustcall/EditLogInputStream.java b/checker/tests/mustcall/EditLogInputStream.java index 55691c6386c..4757aeb6736 100644 --- a/checker/tests/mustcall/EditLogInputStream.java +++ b/checker/tests/mustcall/EditLogInputStream.java @@ -2,13 +2,13 @@ import java.util.*; abstract class EditLogInputStream implements Closeable { - public abstract boolean isLocalLog(); + public abstract boolean isLocalLog(); } interface JournalSet extends Closeable { - static final Comparator LOCAL_LOG_PREFERENCE_COMPARATOR = - // This is an undesirable false positive that occurs because of the defaulting - // that the Must Call Checker uses for generics. - // :: error: (type.argument.type.incompatible) - Comparator.comparing(EditLogInputStream::isLocalLog).reversed(); + static final Comparator LOCAL_LOG_PREFERENCE_COMPARATOR = + // This is an undesirable false positive that occurs because of the defaulting + // that the Must Call Checker uses for generics. + // :: error: (type.argument.type.incompatible) + Comparator.comparing(EditLogInputStream::isLocalLog).reversed(); } diff --git a/checker/tests/mustcall/FieldInitializationWithGeneric.java b/checker/tests/mustcall/FieldInitializationWithGeneric.java index d3025a86e16..717d0b3f143 100644 --- a/checker/tests/mustcall/FieldInitializationWithGeneric.java +++ b/checker/tests/mustcall/FieldInitializationWithGeneric.java @@ -4,6 +4,6 @@ import java.util.concurrent.ConcurrentHashMap; class FieldInitializationWithGeneric { - private Set activeObservers = - Collections.newSetFromMap(new ConcurrentHashMap()); + private Set activeObservers = + Collections.newSetFromMap(new ConcurrentHashMap()); } diff --git a/checker/tests/mustcall/FileDescriptors.java b/checker/tests/mustcall/FileDescriptors.java index 5586abd420c..4f38ccc6fea 100644 --- a/checker/tests/mustcall/FileDescriptors.java +++ b/checker/tests/mustcall/FileDescriptors.java @@ -1,19 +1,18 @@ // A test for some issues related to the getFD() method in RandomAccessFile. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.*; class FileDescriptors { - void test(@Owning RandomAccessFile r) throws Exception { - @MustCall("close") FileDescriptor fd = r.getFD(); - // :: error: assignment.type.incompatible - @MustCall({}) FileDescriptor fd2 = r.getFD(); - } + void test(@Owning RandomAccessFile r) throws Exception { + @MustCall("close") FileDescriptor fd = r.getFD(); + // :: error: assignment.type.incompatible + @MustCall({}) FileDescriptor fd2 = r.getFD(); + } - void test2(@Owning RandomAccessFile r) throws Exception { - @MustCall("close") FileInputStream f = new FileInputStream(r.getFD()); - // :: error: assignment.type.incompatible - @MustCall({}) FileInputStream f2 = new FileInputStream(r.getFD()); - } + void test2(@Owning RandomAccessFile r) throws Exception { + @MustCall("close") FileInputStream f = new FileInputStream(r.getFD()); + // :: error: assignment.type.incompatible + @MustCall({}) FileInputStream f2 = new FileInputStream(r.getFD()); + } } diff --git a/checker/tests/mustcall/InferTypeArgs.java b/checker/tests/mustcall/InferTypeArgs.java index 7563486e277..86f6a18b400 100644 --- a/checker/tests/mustcall/InferTypeArgs.java +++ b/checker/tests/mustcall/InferTypeArgs.java @@ -6,16 +6,16 @@ class CFAbstractValue> {} class CFAbstractAnalysis> {} class GenericAnnotatedTypeFactoryMustCallTest< - Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { + Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { - protected FlowAnalysis createFlowAnalysis() { - FlowAnalysis result = invokeConstructorFor(); - return result; - } + protected FlowAnalysis createFlowAnalysis() { + FlowAnalysis result = invokeConstructorFor(); + return result; + } - // The difference between this version of this test and the all-systems version is the "extends - // Object" on the next line. - public static T invokeConstructorFor() { - return null; - } + // The difference between this version of this test and the all-systems version is the "extends + // Object" on the next line. + public static T invokeConstructorFor() { + return null; + } } diff --git a/checker/tests/mustcall/InheritableMustCallEmpty.java b/checker/tests/mustcall/InheritableMustCallEmpty.java index 113c69efaac..5eb1f16abce 100644 --- a/checker/tests/mustcall/InheritableMustCallEmpty.java +++ b/checker/tests/mustcall/InheritableMustCallEmpty.java @@ -1,26 +1,25 @@ // A simple test for @InheritableMustCall({}). -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.*; public class InheritableMustCallEmpty { - @InheritableMustCall({}) - // :: error: inconsistent.mustcall.subtype - class NoObligationCloseable implements Closeable { - @Override - public void close() throws IOException { - // no resource, nothing to do - } + @InheritableMustCall({}) + // :: error: inconsistent.mustcall.subtype + class NoObligationCloseable implements Closeable { + @Override + public void close() throws IOException { + // no resource, nothing to do } + } - @InheritableMustCall() - // :: error: inconsistent.mustcall.subtype - class NoObligationCloseable2 implements Closeable { - @Override - public void close() throws IOException { - // no resource, nothing to do - } + @InheritableMustCall() + // :: error: inconsistent.mustcall.subtype + class NoObligationCloseable2 implements Closeable { + @Override + public void close() throws IOException { + // no resource, nothing to do } + } } diff --git a/checker/tests/mustcall/ListOfMustCall.java b/checker/tests/mustcall/ListOfMustCall.java index 5a8fa58ca91..9ec53159f89 100644 --- a/checker/tests/mustcall/ListOfMustCall.java +++ b/checker/tests/mustcall/ListOfMustCall.java @@ -6,35 +6,34 @@ // (or equivalently List, etc.). // We should revisit this test if we ever revisit the idea of adding support for owning generics. -import org.checkerframework.checker.mustcall.qual.*; - import java.util.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("a") class ListOfMustCall { - static void test(ListOfMustCall lm) { - // :: error: (type.argument.type.incompatible) - List l = new ArrayList<>(); - // add(E e) takes an object of the type argument's type - l.add(lm); - // remove(Object e) takes an object - l.remove(lm); - } + static void test(ListOfMustCall lm) { + // :: error: (type.argument.type.incompatible) + List l = new ArrayList<>(); + // add(E e) takes an object of the type argument's type + l.add(lm); + // remove(Object e) takes an object + l.remove(lm); + } - static void test2(ListOfMustCall lm) { - // :: error: (type.argument.type.incompatible) - List<@MustCall("a") ListOfMustCall> l = new ArrayList<>(); - l.add(lm); - l.remove(lm); - } + static void test2(ListOfMustCall lm) { + // :: error: (type.argument.type.incompatible) + List<@MustCall("a") ListOfMustCall> l = new ArrayList<>(); + l.add(lm); + l.remove(lm); + } - static void test3(ListOfMustCall lm) { - List l = new ArrayList<>(); - l.remove(lm); - } + static void test3(ListOfMustCall lm) { + List l = new ArrayList<>(); + l.remove(lm); + } - static void test4(ListOfMustCall lm) { - List l = new ArrayList<>(); - l.remove(lm); - } + static void test4(ListOfMustCall lm) { + List l = new ArrayList<>(); + l.remove(lm); + } } diff --git a/checker/tests/mustcall/LogTheSocket.java b/checker/tests/mustcall/LogTheSocket.java index ddc235a9dc7..a7b2ba1c692 100644 --- a/checker/tests/mustcall/LogTheSocket.java +++ b/checker/tests/mustcall/LogTheSocket.java @@ -17,61 +17,60 @@ // This test is also coincidentally a test case for // https://github.com/typetools/checker-framework/pull/3867. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.ServerSocket; import java.nio.channels.SocketChannel; +import org.checkerframework.checker.mustcall.qual.*; class LogTheSocket { - @NotOwning ServerSocket s; + @NotOwning ServerSocket s; - @MustCall("") Object s2; + @MustCall("") Object s2; - void testAssign(@Owning ServerSocket s1) { - s = s1; - // :: error: assignment.type.incompatible - s2 = s1; - } + void testAssign(@Owning ServerSocket s1) { + s = s1; + // :: error: assignment.type.incompatible + s2 = s1; + } - void logVarargs(String s, Object... objects) {} + void logVarargs(String s, Object... objects) {} - void logNoVarargs(String s, Object object) {} + void logNoVarargs(String s, Object object) {} - void test(ServerSocket serverSocket) { - if (!serverSocket.isClosed()) { - try { - serverSocket.close(); - } catch (IOException e) { - logVarargs("Ignoring unexpected exception during close {}", serverSocket, e); - } - } + void test(ServerSocket serverSocket) { + if (!serverSocket.isClosed()) { + try { + serverSocket.close(); + } catch (IOException e) { + logVarargs("Ignoring unexpected exception during close {}", serverSocket, e); + } } + } - void test2(ServerSocket serverSocket) { - if (!serverSocket.isClosed()) { - try { - serverSocket.close(); - } catch (IOException e) { - logNoVarargs("Ignoring unexpected exception during close {}", serverSocket); - } - } + void test2(ServerSocket serverSocket) { + if (!serverSocket.isClosed()) { + try { + serverSocket.close(); + } catch (IOException e) { + logNoVarargs("Ignoring unexpected exception during close {}", serverSocket); + } } + } - // This is (mostly) copied from ACSocketTest; under a previous implementation of the - // ownership-transfer scheme, - // it caused false positive warnings from the Must Call checker. - SocketChannel createSock() throws IOException { - SocketChannel sock; - sock = SocketChannel.open(); - sock.configureBlocking(false); - sock.socket().setSoLinger(false, -1); - sock.socket().setTcpNoDelay(true); - return sock; - } + // This is (mostly) copied from ACSocketTest; under a previous implementation of the + // ownership-transfer scheme, + // it caused false positive warnings from the Must Call checker. + SocketChannel createSock() throws IOException { + SocketChannel sock; + sock = SocketChannel.open(); + sock.configureBlocking(false); + sock.socket().setSoLinger(false, -1); + sock.socket().setTcpNoDelay(true); + return sock; + } - void testPrintln(ServerSocket s) { - System.out.println(s); - } + void testPrintln(ServerSocket s) { + System.out.println(s); + } } diff --git a/checker/tests/mustcall/MapWrap.java b/checker/tests/mustcall/MapWrap.java index 52f792c691f..f2a65cdbc37 100644 --- a/checker/tests/mustcall/MapWrap.java +++ b/checker/tests/mustcall/MapWrap.java @@ -1,22 +1,21 @@ // A test for a class that wraps a map. I found a similar example in Zookeeper that causes false // positives. -import org.checkerframework.checker.mustcall.qual.*; - import java.util.HashMap; +import org.checkerframework.checker.mustcall.qual.*; class MapWrap { - HashMap impl = new HashMap(); + HashMap impl = new HashMap(); - String remove(E e) { - // remove should permit any object: its signature is remove(Object key), *not* remove(E key) - String old = impl.remove(e); - return old; - } + String remove(E e) { + // remove should permit any object: its signature is remove(Object key), *not* remove(E key) + String old = impl.remove(e); + return old; + } - String remove2(@MustCall({}) E e) { - // remove should permit any object: its signature is remove(Object key), *not* remove(E key) - String old = impl.remove(e); - return old; - } + String remove2(@MustCall({}) E e) { + // remove should permit any object: its signature is remove(Object key), *not* remove(E key) + String old = impl.remove(e); + return old; + } } diff --git a/checker/tests/mustcall/MustCallAliasImpl.java b/checker/tests/mustcall/MustCallAliasImpl.java index e2f87781f7a..3c23f659824 100644 --- a/checker/tests/mustcall/MustCallAliasImpl.java +++ b/checker/tests/mustcall/MustCallAliasImpl.java @@ -1,20 +1,19 @@ // A simple test that MustCallAlias annotations in source code don't issue // bogus annotations.on.use errors. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.*; public class MustCallAliasImpl implements Closeable { - @Owning final Closeable foo; + @Owning final Closeable foo; - public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { - this.foo = foo; - } + public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { + this.foo = foo; + } - @Override - public void close() throws IOException { - this.foo.close(); - } + @Override + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/mustcall/MustCallSubtypingTest.java b/checker/tests/mustcall/MustCallSubtypingTest.java index affa13cb52b..c0e5903f914 100644 --- a/checker/tests/mustcall/MustCallSubtypingTest.java +++ b/checker/tests/mustcall/MustCallSubtypingTest.java @@ -7,126 +7,126 @@ public class MustCallSubtypingTest { - @MustCall({"toString"}) String foo(@MustCall({"hashCode"}) String arg) { - // :: (return) - return arg; - } - - @MustCall({}) String mcEmpty; - - @MustCall({"hashCode"}) String mcHashCode; - - @MustCall({"toString"}) String mcToString; - - @MustCallUnknown String mcUnknown; - - void clientSetMcEmpty() { - mcEmpty = mcHashCode; - mcEmpty = mcToString; - mcEmpty = mcUnknown; - } - - void clientSetMcHashCode() { - mcHashCode = mcEmpty; - mcHashCode = mcToString; - mcHashCode = mcUnknown; - } - - void clientSetMcToString() { - mcToString = mcEmpty; - mcToString = mcHashCode; - mcToString = mcUnknown; - } - - void clientSetMcUnknown() { - mcUnknown = mcEmpty; - mcUnknown = mcHashCode; - mcUnknown = mcToString; - } - - void requiresMustCallEmptyObject(@MustCall({}) Object o) {} - - void requiresMustCallHashCodeObject(@MustCall({"hashCode"}) Object o) {} - - void requiresMustCallToStringObject(@MustCall({"toString"}) Object o) {} - - void requiresMustCallUnknownObject(@MustCallUnknown Object o) {} - - void requiresMustCallEmptyString(@MustCall({}) String s) {} - - void requiresMustCallHashCodeString(@MustCall({"hashCode"}) String s) {} - - void requiresMustCallToStringString(@MustCall({"toString"}) String s) {} - - void requiresMustCallUnknownString(@MustCallUnknown String s) {} - - void client(Integer i, Integer[] ia) { - requiresMustCallEmptyObject(i); - requiresMustCallEmptyObject(ia); - // :: (argument) - requiresMustCallEmptyObject(mcHashCode); - // :: (argument) - requiresMustCallEmptyObject(mcToString); - requiresMustCallEmptyObject(mcEmpty); - // :: (argument) - requiresMustCallEmptyObject(mcUnknown); - - // :: (argument) - requiresMustCallEmptyString(mcHashCode); - // :: (argument) - requiresMustCallEmptyString(mcToString); - requiresMustCallEmptyString(mcEmpty); - // :: (argument) - requiresMustCallEmptyString(mcUnknown); - - requiresMustCallHashCodeObject(i); - requiresMustCallHashCodeObject(ia); - requiresMustCallHashCodeObject(mcHashCode); - // :: (argument) - requiresMustCallHashCodeObject(mcToString); - requiresMustCallHashCodeObject(mcEmpty); - // :: (argument) - requiresMustCallHashCodeObject(mcUnknown); - - requiresMustCallHashCodeString(mcHashCode); - // :: (argument) - requiresMustCallHashCodeString(mcToString); - requiresMustCallHashCodeString(mcEmpty); - // :: (argument) - requiresMustCallHashCodeString(mcUnknown); - - requiresMustCallToStringObject(i); - requiresMustCallToStringObject(ia); - // :: (argument) - requiresMustCallToStringObject(mcHashCode); - requiresMustCallToStringObject(mcToString); - requiresMustCallToStringObject(mcEmpty); - // :: (argument) - requiresMustCallToStringObject(mcUnknown); - - // :: (argument) - requiresMustCallToStringString(mcHashCode); - requiresMustCallToStringString(mcToString); - requiresMustCallToStringString(mcEmpty); - // :: (argument) - requiresMustCallToStringString(mcUnknown); - - requiresMustCallUnknownObject(i); - requiresMustCallUnknownObject(ia); - // :: (argument) - requiresMustCallUnknownObject(mcHashCode); - // :: (argument) - requiresMustCallUnknownObject(mcToString); - // :: (argument) - requiresMustCallUnknownObject(mcEmpty); - requiresMustCallUnknownObject(mcUnknown); - - // :: (argument) - requiresMustCallUnknownString(mcHashCode); - // :: (argument) - requiresMustCallUnknownString(mcToString); - // :: (argument) - requiresMustCallUnknownString(mcEmpty); - requiresMustCallUnknownString(mcUnknown); - } + @MustCall({"toString"}) String foo(@MustCall({"hashCode"}) String arg) { + // :: (return) + return arg; + } + + @MustCall({}) String mcEmpty; + + @MustCall({"hashCode"}) String mcHashCode; + + @MustCall({"toString"}) String mcToString; + + @MustCallUnknown String mcUnknown; + + void clientSetMcEmpty() { + mcEmpty = mcHashCode; + mcEmpty = mcToString; + mcEmpty = mcUnknown; + } + + void clientSetMcHashCode() { + mcHashCode = mcEmpty; + mcHashCode = mcToString; + mcHashCode = mcUnknown; + } + + void clientSetMcToString() { + mcToString = mcEmpty; + mcToString = mcHashCode; + mcToString = mcUnknown; + } + + void clientSetMcUnknown() { + mcUnknown = mcEmpty; + mcUnknown = mcHashCode; + mcUnknown = mcToString; + } + + void requiresMustCallEmptyObject(@MustCall({}) Object o) {} + + void requiresMustCallHashCodeObject(@MustCall({"hashCode"}) Object o) {} + + void requiresMustCallToStringObject(@MustCall({"toString"}) Object o) {} + + void requiresMustCallUnknownObject(@MustCallUnknown Object o) {} + + void requiresMustCallEmptyString(@MustCall({}) String s) {} + + void requiresMustCallHashCodeString(@MustCall({"hashCode"}) String s) {} + + void requiresMustCallToStringString(@MustCall({"toString"}) String s) {} + + void requiresMustCallUnknownString(@MustCallUnknown String s) {} + + void client(Integer i, Integer[] ia) { + requiresMustCallEmptyObject(i); + requiresMustCallEmptyObject(ia); + // :: (argument) + requiresMustCallEmptyObject(mcHashCode); + // :: (argument) + requiresMustCallEmptyObject(mcToString); + requiresMustCallEmptyObject(mcEmpty); + // :: (argument) + requiresMustCallEmptyObject(mcUnknown); + + // :: (argument) + requiresMustCallEmptyString(mcHashCode); + // :: (argument) + requiresMustCallEmptyString(mcToString); + requiresMustCallEmptyString(mcEmpty); + // :: (argument) + requiresMustCallEmptyString(mcUnknown); + + requiresMustCallHashCodeObject(i); + requiresMustCallHashCodeObject(ia); + requiresMustCallHashCodeObject(mcHashCode); + // :: (argument) + requiresMustCallHashCodeObject(mcToString); + requiresMustCallHashCodeObject(mcEmpty); + // :: (argument) + requiresMustCallHashCodeObject(mcUnknown); + + requiresMustCallHashCodeString(mcHashCode); + // :: (argument) + requiresMustCallHashCodeString(mcToString); + requiresMustCallHashCodeString(mcEmpty); + // :: (argument) + requiresMustCallHashCodeString(mcUnknown); + + requiresMustCallToStringObject(i); + requiresMustCallToStringObject(ia); + // :: (argument) + requiresMustCallToStringObject(mcHashCode); + requiresMustCallToStringObject(mcToString); + requiresMustCallToStringObject(mcEmpty); + // :: (argument) + requiresMustCallToStringObject(mcUnknown); + + // :: (argument) + requiresMustCallToStringString(mcHashCode); + requiresMustCallToStringString(mcToString); + requiresMustCallToStringString(mcEmpty); + // :: (argument) + requiresMustCallToStringString(mcUnknown); + + requiresMustCallUnknownObject(i); + requiresMustCallUnknownObject(ia); + // :: (argument) + requiresMustCallUnknownObject(mcHashCode); + // :: (argument) + requiresMustCallUnknownObject(mcToString); + // :: (argument) + requiresMustCallUnknownObject(mcEmpty); + requiresMustCallUnknownObject(mcUnknown); + + // :: (argument) + requiresMustCallUnknownString(mcHashCode); + // :: (argument) + requiresMustCallUnknownString(mcToString); + // :: (argument) + requiresMustCallUnknownString(mcEmpty); + requiresMustCallUnknownString(mcUnknown); + } } diff --git a/checker/tests/mustcall/MyDataInputStream.java b/checker/tests/mustcall/MyDataInputStream.java index 4cbdf61c395..d77a39d6941 100644 --- a/checker/tests/mustcall/MyDataInputStream.java +++ b/checker/tests/mustcall/MyDataInputStream.java @@ -4,20 +4,20 @@ import java.nio.ByteBuffer; public class MyDataInputStream extends DataInputStream { - public MyDataInputStream(InputStream in) { - super(in); - } + public MyDataInputStream(InputStream in) { + super(in); + } - public void readFully(long position, ByteBuffer buf) throws IOException { - if (in instanceof ByteBufferPositionedReadable) { - ((ByteBufferPositionedReadable) in).readFully(position, buf); - } else { - throw new UnsupportedOperationException( - "Byte-buffer pread unsupported by " + in.getClass().getCanonicalName()); - } + public void readFully(long position, ByteBuffer buf) throws IOException { + if (in instanceof ByteBufferPositionedReadable) { + ((ByteBufferPositionedReadable) in).readFully(position, buf); + } else { + throw new UnsupportedOperationException( + "Byte-buffer pread unsupported by " + in.getClass().getCanonicalName()); } + } - interface ByteBufferPositionedReadable { - void readFully(long position, ByteBuffer buf) throws IOException; - } + interface ByteBufferPositionedReadable { + void readFully(long position, ByteBuffer buf) throws IOException; + } } diff --git a/checker/tests/mustcall/NonOwningPolyInteraction.java b/checker/tests/mustcall/NonOwningPolyInteraction.java index b8183f41264..39542fecd7c 100644 --- a/checker/tests/mustcall/NonOwningPolyInteraction.java +++ b/checker/tests/mustcall/NonOwningPolyInteraction.java @@ -1,44 +1,43 @@ // A test that non-owning method parameters are really treated as @MustCall({}) // wrt polymorphic types. Based on some false positives in Zookeeper. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.*; class NonOwningPolyInteraction { - void foo(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - void bar(@Owning InputStream instream) { - // :: error: assignment.type.incompatible - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - void baz(InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - NonOwningPolyInteraction(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // extra param(s) here and on the next constructor because Java requires constructors to have - // different signatures. - NonOwningPolyInteraction(@Owning InputStream instream, int x) { - // :: error: assignment.type.incompatible - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - NonOwningPolyInteraction(InputStream instream, int x, int y) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } + void foo(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + void bar(@Owning InputStream instream) { + // :: error: assignment.type.incompatible + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + void baz(InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + NonOwningPolyInteraction(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // extra param(s) here and on the next constructor because Java requires constructors to have + // different signatures. + NonOwningPolyInteraction(@Owning InputStream instream, int x) { + // :: error: assignment.type.incompatible + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + NonOwningPolyInteraction(InputStream instream, int x, int y) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } } diff --git a/checker/tests/mustcall/NotInheritableMustCallOnClassError.java b/checker/tests/mustcall/NotInheritableMustCallOnClassError.java index 12e053868b7..bce0288f500 100644 --- a/checker/tests/mustcall/NotInheritableMustCallOnClassError.java +++ b/checker/tests/mustcall/NotInheritableMustCallOnClassError.java @@ -7,5 +7,5 @@ @MustCall("foo") // :: warning: mustcall.not.inheritable public class NotInheritableMustCallOnClassError { - public void foo() {} + public void foo() {} } diff --git a/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java b/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java index d60849ae05f..002cde3cae9 100644 --- a/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java +++ b/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java @@ -4,5 +4,5 @@ import org.checkerframework.checker.mustcall.qual.*; @MustCall("foo") public final class NotInheritableMustCallOnFinalClassNoError { - public void foo() {} + public void foo() {} } diff --git a/checker/tests/mustcall/NullOutputStreamTest.java b/checker/tests/mustcall/NullOutputStreamTest.java index 6ece0518145..184fc2ca142 100644 --- a/checker/tests/mustcall/NullOutputStreamTest.java +++ b/checker/tests/mustcall/NullOutputStreamTest.java @@ -1,12 +1,11 @@ // @below-java11-jdk-skip-test OutputStream.nullOutputStream() was introduced in JDK 11. -import org.checkerframework.checker.mustcall.qual.MustCall; - import java.io.OutputStream; +import org.checkerframework.checker.mustcall.qual.MustCall; class NullOutputStreamTest { - void m() { - @MustCall() OutputStream nullOS = OutputStream.nullOutputStream(); - } + void m() { + @MustCall() OutputStream nullOS = OutputStream.nullOutputStream(); + } } diff --git a/checker/tests/mustcall/NullableTransfer.java b/checker/tests/mustcall/NullableTransfer.java index d716f8137a3..1505b4782f9 100644 --- a/checker/tests/mustcall/NullableTransfer.java +++ b/checker/tests/mustcall/NullableTransfer.java @@ -1,19 +1,18 @@ // A test that the must-call type of an object tested against null // is always empty. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.*; class NullableTransfer { - void test(@Owning InputStream is) { - if (is == null) { - @MustCall({}) InputStream is2 = is; - } else { - // :: error: assignment.type.incompatible - @MustCall({}) InputStream is3 = is; - @MustCall("close") InputStream is4 = is; - } + void test(@Owning InputStream is) { + if (is == null) { + @MustCall({}) InputStream is2 = is; + } else { + // :: error: assignment.type.incompatible + @MustCall({}) InputStream is3 = is; + @MustCall("close") InputStream is4 = is; } + } } diff --git a/checker/tests/mustcall/OwningMustCallNothing.java b/checker/tests/mustcall/OwningMustCallNothing.java index 2035a60b9c5..e5b1eda6411 100644 --- a/checker/tests/mustcall/OwningMustCallNothing.java +++ b/checker/tests/mustcall/OwningMustCallNothing.java @@ -1,49 +1,48 @@ +import java.io.Closeable; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; -import java.io.Closeable; - @InheritableMustCall({}) // :: error: (inconsistent.mustcall.subtype) public class OwningMustCallNothing implements Closeable { - protected @Owning AnnotationClassLoader loader; + protected @Owning AnnotationClassLoader loader; - @CreatesMustCallFor("this") - private void loadTypeAnnotationsFromQualDir() { - if (loader != null) { - loader.close(); - } - loader = createAnnotationClassLoader(); + @CreatesMustCallFor("this") + private void loadTypeAnnotationsFromQualDir() { + if (loader != null) { + loader.close(); } + loader = createAnnotationClassLoader(); + } - AnnotationClassLoader createAnnotationClassLoader() { - return null; - } + AnnotationClassLoader createAnnotationClassLoader() { + return null; + } - public void close() {} + public void close() {} } // :: error: (inconsistent.mustcall.subtype) @MustCall({}) class OwningMustCallNothing2 implements Closeable { - protected @Owning AnnotationClassLoader loader; + protected @Owning AnnotationClassLoader loader; - @CreatesMustCallFor("this") - private void loadTypeAnnotationsFromQualDir() { - if (loader != null) { - loader.close(); - } - loader = createAnnotationClassLoader(); + @CreatesMustCallFor("this") + private void loadTypeAnnotationsFromQualDir() { + if (loader != null) { + loader.close(); } + loader = createAnnotationClassLoader(); + } - AnnotationClassLoader createAnnotationClassLoader() { - return null; - } + AnnotationClassLoader createAnnotationClassLoader() { + return null; + } - public void close() {} + public void close() {} } @InheritableMustCall("close") @@ -63,5 +62,5 @@ class SubclassMustCallClose3 extends OwningMustCallNothing2 {} @InheritableMustCall({}) // Don't check whether AnnotationClassLoaders are closed. // :: error: (inconsistent.mustcall.subtype) class AnnotationClassLoader implements Closeable { - public void close() {} + public void close() {} } diff --git a/checker/tests/mustcall/OwningParams.java b/checker/tests/mustcall/OwningParams.java index 5430d509f3c..93eb9c71f4b 100644 --- a/checker/tests/mustcall/OwningParams.java +++ b/checker/tests/mustcall/OwningParams.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.mustcall.qual.*; class OwningParams { - static void o1(@Owning OwningParams o) {} + static void o1(@Owning OwningParams o) {} - void test(@Owning @MustCall({"a"}) OwningParams o, @Owning OwningParams p) { - // :: error: argument.type.incompatible - o1(o); - o1(p); - } + void test(@Owning @MustCall({"a"}) OwningParams o, @Owning OwningParams p) { + // :: error: argument.type.incompatible + o1(o); + o1(p); + } } diff --git a/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java b/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java index f5fdedb0d5d..d1da8166c26 100644 --- a/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java +++ b/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java @@ -14,47 +14,46 @@ // and https://github.com/plume-lib/plume-util/pull/126 for more details, especially // on why changing the default isn't feasible. +import java.util.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.*; - class PlumeUtilRequiredAnnotations { - // In the real version of this code, there is only one type parameter. - // T is the unannotated version of the parameter - i.e., what it was before - // we first ran the Must Call Checker. S is the annotated version. Adding the - // annotation itself is immaterial - what's important is that the bound - // must be explicit rather than implicit (see that the eqR field never issue errors, - // just like the eqS fields). - class MultiRandSelector { - // a type.argument error used to be issued here. - private Partitioner eqT; - private Partitioner eqS; - private Partitioner eqR; - - // Adding annotations to the definition of Partitioner doesn't fix this problem: - // a type.argument error used to be issued here. - private Partitioner2 eqT2; - private Partitioner2 eqS2; - private Partitioner2 eqR2; - - // But removing the explicit bounds on Partitioner does (not feasible in this case, though, - // because of the @Nullable annotations): - private Partitioner3 eqT3; - private Partitioner3 eqS3; - private Partitioner3 eqR3; - } - - interface Partitioner { - CLASS assignToBucket(ELEMENT obj); - } - - interface Partitioner2< - ELEMENT extends @Nullable @MustCall Object, CLASS extends @Nullable @MustCall Object> { - CLASS assignToBucket(ELEMENT obj); - } - - interface Partitioner3 { - CLASS assignToBucket(ELEMENT obj); - } + // In the real version of this code, there is only one type parameter. + // T is the unannotated version of the parameter - i.e., what it was before + // we first ran the Must Call Checker. S is the annotated version. Adding the + // annotation itself is immaterial - what's important is that the bound + // must be explicit rather than implicit (see that the eqR field never issue errors, + // just like the eqS fields). + class MultiRandSelector { + // a type.argument error used to be issued here. + private Partitioner eqT; + private Partitioner eqS; + private Partitioner eqR; + + // Adding annotations to the definition of Partitioner doesn't fix this problem: + // a type.argument error used to be issued here. + private Partitioner2 eqT2; + private Partitioner2 eqS2; + private Partitioner2 eqR2; + + // But removing the explicit bounds on Partitioner does (not feasible in this case, though, + // because of the @Nullable annotations): + private Partitioner3 eqT3; + private Partitioner3 eqS3; + private Partitioner3 eqR3; + } + + interface Partitioner { + CLASS assignToBucket(ELEMENT obj); + } + + interface Partitioner2< + ELEMENT extends @Nullable @MustCall Object, CLASS extends @Nullable @MustCall Object> { + CLASS assignToBucket(ELEMENT obj); + } + + interface Partitioner3 { + CLASS assignToBucket(ELEMENT obj); + } } diff --git a/checker/tests/mustcall/PolyMustCallDifferentNames.java b/checker/tests/mustcall/PolyMustCallDifferentNames.java index b3aa76b12f0..51f2bf8403b 100644 --- a/checker/tests/mustcall/PolyMustCallDifferentNames.java +++ b/checker/tests/mustcall/PolyMustCallDifferentNames.java @@ -6,71 +6,71 @@ class PolyMustCallDifferentNames { - @InheritableMustCall("a") - static class Wrapped { - void a() {} - } - - @InheritableMustCall("b") - static class Wrapper1 { - private final @Owning Wrapped field; + @InheritableMustCall("a") + static class Wrapped { + void a() {} + } - public @PolyMustCall Wrapper1(@PolyMustCall Wrapped w) { - // we get this error since we only have a field-assignment special case for - // @MustCallAlias, not @PolyMustCall - // :: error: (assignment.type.incompatible) - this.field = w; - } + @InheritableMustCall("b") + static class Wrapper1 { + private final @Owning Wrapped field; - @EnsuresCalledMethods( - value = {"this.field"}, - methods = {"a"}) - void b() { - this.field.a(); - } + public @PolyMustCall Wrapper1(@PolyMustCall Wrapped w) { + // we get this error since we only have a field-assignment special case for + // @MustCallAlias, not @PolyMustCall + // :: error: (assignment.type.incompatible) + this.field = w; } - static @PolyMustCall Wrapper1 getWrapper1(@PolyMustCall Wrapped w) { - return new Wrapper1(w); + @EnsuresCalledMethods( + value = {"this.field"}, + methods = {"a"}) + void b() { + this.field.a(); } + } - @InheritableMustCall("c") - static class Wrapper2 { - private final @Owning Wrapped field; + static @PolyMustCall Wrapper1 getWrapper1(@PolyMustCall Wrapped w) { + return new Wrapper1(w); + } - public @MustCallAlias Wrapper2(@MustCallAlias Wrapped w) { - this.field = w; - } + @InheritableMustCall("c") + static class Wrapper2 { + private final @Owning Wrapped field; - @EnsuresCalledMethods( - value = {"this.field"}, - methods = {"a"}) - void c() { - this.field.a(); - } + public @MustCallAlias Wrapper2(@MustCallAlias Wrapped w) { + this.field = w; } - static @MustCallAlias Wrapper2 getWrapper2(@MustCallAlias Wrapped w) { - return new Wrapper2(w); + @EnsuresCalledMethods( + value = {"this.field"}, + methods = {"a"}) + void c() { + this.field.a(); } + } - static void test1() { - @MustCall("a") Wrapped x = new Wrapped(); - @MustCall("b") Wrapper1 w1 = new Wrapper1(x); - @MustCall("b") Wrapper1 w2 = getWrapper1(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper1 w3 = new Wrapper1(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper1 w4 = getWrapper1(x); - } + static @MustCallAlias Wrapper2 getWrapper2(@MustCallAlias Wrapped w) { + return new Wrapper2(w); + } - static void test2() { - @MustCall("a") Wrapped x = new Wrapped(); - @MustCall("c") Wrapper2 w1 = new Wrapper2(x); - @MustCall("c") Wrapper2 w2 = getWrapper2(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper2 w3 = new Wrapper2(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper2 w4 = getWrapper2(x); - } + static void test1() { + @MustCall("a") Wrapped x = new Wrapped(); + @MustCall("b") Wrapper1 w1 = new Wrapper1(x); + @MustCall("b") Wrapper1 w2 = getWrapper1(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper1 w3 = new Wrapper1(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper1 w4 = getWrapper1(x); + } + + static void test2() { + @MustCall("a") Wrapped x = new Wrapped(); + @MustCall("c") Wrapper2 w1 = new Wrapper2(x); + @MustCall("c") Wrapper2 w2 = getWrapper2(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper2 w3 = new Wrapper2(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper2 w4 = getWrapper2(x); + } } diff --git a/checker/tests/mustcall/PolyTests.java b/checker/tests/mustcall/PolyTests.java index 4b986363052..e2ecc02358b 100644 --- a/checker/tests/mustcall/PolyTests.java +++ b/checker/tests/mustcall/PolyTests.java @@ -4,39 +4,39 @@ @InheritableMustCall("close") class PolyTests { - static @PolyMustCall Object id(@PolyMustCall Object obj) { - return obj; - } - - static void test1(@Owning @MustCall("close") Object o) { - @MustCall("close") Object o1 = id(o); - // :: error: assignment.type.incompatible - @MustCall({}) Object o2 = id(o); - } - - static void test2(@Owning @MustCall({}) Object o) { - @MustCall("close") Object o1 = id(o); - @MustCall({}) Object o2 = id(o); - } - - // These sort of constructors will always appear in stub files and are unverifiable for now. - @SuppressWarnings("mustcall:annotations.on.use") - @PolyMustCall PolyTests(@PolyMustCall Object obj) {} - - static void test3(@Owning @MustCall({"close"}) Object o) { - @MustCall("close") Object o1 = new PolyTests(o); - // :: error: assignment.type.incompatible - @MustCall({}) Object o2 = new PolyTests(o); - } - - static void test4(@Owning @MustCall({}) Object o) { - @MustCall("close") Object o1 = new PolyTests(o); - @MustCall({}) Object o2 = new PolyTests(o); - } - - static void testArbitary(@Owning PolyTests p) { - @MustCall("close") Object o1 = p; - // :: error: assignment.type.incompatible - @MustCall({}) Object o2 = p; - } + static @PolyMustCall Object id(@PolyMustCall Object obj) { + return obj; + } + + static void test1(@Owning @MustCall("close") Object o) { + @MustCall("close") Object o1 = id(o); + // :: error: assignment.type.incompatible + @MustCall({}) Object o2 = id(o); + } + + static void test2(@Owning @MustCall({}) Object o) { + @MustCall("close") Object o1 = id(o); + @MustCall({}) Object o2 = id(o); + } + + // These sort of constructors will always appear in stub files and are unverifiable for now. + @SuppressWarnings("mustcall:annotations.on.use") + @PolyMustCall PolyTests(@PolyMustCall Object obj) {} + + static void test3(@Owning @MustCall({"close"}) Object o) { + @MustCall("close") Object o1 = new PolyTests(o); + // :: error: assignment.type.incompatible + @MustCall({}) Object o2 = new PolyTests(o); + } + + static void test4(@Owning @MustCall({}) Object o) { + @MustCall("close") Object o1 = new PolyTests(o); + @MustCall({}) Object o2 = new PolyTests(o); + } + + static void testArbitary(@Owning PolyTests p) { + @MustCall("close") Object o1 = p; + // :: error: assignment.type.incompatible + @MustCall({}) Object o2 = p; + } } diff --git a/checker/tests/mustcall/SimpleException.java b/checker/tests/mustcall/SimpleException.java index d6cf38f2e51..b989d992920 100644 --- a/checker/tests/mustcall/SimpleException.java +++ b/checker/tests/mustcall/SimpleException.java @@ -1,15 +1,15 @@ // A test that throwing and catching exceptions doesn't cause false positives. class SimpleException { - void thrower() throws Exception { - throw new RuntimeException("some exception"); - } + void thrower() throws Exception { + throw new RuntimeException("some exception"); + } - void test() { - try { - thrower(); - } catch (Exception e) { - e.printStackTrace(); - } + void test() { + try { + thrower(); + } catch (Exception e) { + e.printStackTrace(); } + } } diff --git a/checker/tests/mustcall/SimpleSocketField.java b/checker/tests/mustcall/SimpleSocketField.java index a6a4e3c30ff..5536bc89234 100644 --- a/checker/tests/mustcall/SimpleSocketField.java +++ b/checker/tests/mustcall/SimpleSocketField.java @@ -1,22 +1,21 @@ // a test that sockets in fields are considered @MustCall("close") -import org.checkerframework.checker.mustcall.qual.MustCall; - import java.net.Socket; +import org.checkerframework.checker.mustcall.qual.MustCall; class SimpleSocketField { - Socket mySock = new Socket(); + Socket mySock = new Socket(); - SimpleSocketField() throws Exception { - @MustCall("close") Socket s = mySock; - // This assignment is safe, because the only possible value of mySock here is the - // unconnected socket in the field initializer. - @MustCall({}) Socket s1 = mySock; - } + SimpleSocketField() throws Exception { + @MustCall("close") Socket s = mySock; + // This assignment is safe, because the only possible value of mySock here is the + // unconnected socket in the field initializer. + @MustCall({}) Socket s1 = mySock; + } - void test() { - @MustCall("close") Socket s = mySock; - // :: error: assignment.type.incompatible - @MustCall({}) Socket s1 = mySock; - } + void test() { + @MustCall("close") Socket s = mySock; + // :: error: assignment.type.incompatible + @MustCall({}) Socket s1 = mySock; + } } diff --git a/checker/tests/mustcall/SimpleStreamExample.java b/checker/tests/mustcall/SimpleStreamExample.java index 8bb103f64f5..510978bb955 100644 --- a/checker/tests/mustcall/SimpleStreamExample.java +++ b/checker/tests/mustcall/SimpleStreamExample.java @@ -3,7 +3,7 @@ import java.util.*; class SimpleStreamExample { - static void test(List s) { - s.stream().filter(str -> str == null); - } + static void test(List s) { + s.stream().filter(str -> str == null); + } } diff --git a/checker/tests/mustcall/SocketBufferedReader.java b/checker/tests/mustcall/SocketBufferedReader.java index 40c9fdee0ea..2650d004edc 100644 --- a/checker/tests/mustcall/SocketBufferedReader.java +++ b/checker/tests/mustcall/SocketBufferedReader.java @@ -1,24 +1,23 @@ // a test for missing mustcall propagation that might have caused a false positive? -import org.checkerframework.checker.mustcall.qual.*; - import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; class SocketBufferedReader { - void test(String address, int port) { - try { - Socket socket = new Socket(address, 80); - PrintStream out = new PrintStream(socket.getOutputStream()); - BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - @MustCall("close") BufferedReader reader = in; - // :: error: assignment.type.incompatible - @MustCall({}) BufferedReader reader2 = in; - in.close(); - } catch (Exception e) { - e.printStackTrace(); - } + void test(String address, int port) { + try { + Socket socket = new Socket(address, 80); + PrintStream out = new PrintStream(socket.getOutputStream()); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + @MustCall("close") BufferedReader reader = in; + // :: error: assignment.type.incompatible + @MustCall({}) BufferedReader reader2 = in; + in.close(); + } catch (Exception e) { + e.printStackTrace(); } + } } diff --git a/checker/tests/mustcall/StreamBool.java b/checker/tests/mustcall/StreamBool.java index 6826763d9c6..f6c2b0fe71e 100644 --- a/checker/tests/mustcall/StreamBool.java +++ b/checker/tests/mustcall/StreamBool.java @@ -3,9 +3,9 @@ import java.io.InputStream; class StreamBool { - InputStream stream; + InputStream stream; - boolean isActive() { - return stream != null; - } + boolean isActive() { + return stream != null; + } } diff --git a/checker/tests/mustcall/StringSort.java b/checker/tests/mustcall/StringSort.java index bfb10cfd0e7..c76a0977a71 100644 --- a/checker/tests/mustcall/StringSort.java +++ b/checker/tests/mustcall/StringSort.java @@ -3,8 +3,8 @@ import java.util.*; class StringSort { - public static void sort() { - List myList = new ArrayList(); - Collections.sort(myList); - } + public static void sort() { + List myList = new ArrayList(); + Collections.sort(myList); + } } diff --git a/checker/tests/mustcall/Subtype0.java b/checker/tests/mustcall/Subtype0.java index cfd9950a337..3cc8ae8d214 100644 --- a/checker/tests/mustcall/Subtype0.java +++ b/checker/tests/mustcall/Subtype0.java @@ -5,57 +5,57 @@ @InheritableMustCall("a") public class Subtype0 { - public class Subtype1 extends Subtype0 { - void m1() {} - } + public class Subtype1 extends Subtype0 { + void m1() {} + } - public class Subtype2 extends Subtype1 {} + public class Subtype2 extends Subtype1 {} - static void test( - @Owning Subtype0 s0, - @Owning Subtype1 s1, - @Owning Subtype2 s2, - @Owning Subtype3 s3, - @Owning Subtype4 s4) { - // :: error: assignment.type.incompatible - @MustCall({}) Object obj1 = s0; - @MustCall({"a"}) Object obj2 = s0; + static void test( + @Owning Subtype0 s0, + @Owning Subtype1 s1, + @Owning Subtype2 s2, + @Owning Subtype3 s3, + @Owning Subtype4 s4) { + // :: error: assignment.type.incompatible + @MustCall({}) Object obj1 = s0; + @MustCall({"a"}) Object obj2 = s0; - // :: error: assignment.type.incompatible - @MustCall({}) Object obj3 = s1; - @MustCall({"a"}) Object obj4 = s1; + // :: error: assignment.type.incompatible + @MustCall({}) Object obj3 = s1; + @MustCall({"a"}) Object obj4 = s1; - // :: error: assignment.type.incompatible - @MustCall({}) Object obj5 = s2; - @MustCall({"a"}) Object obj6 = s2; + // :: error: assignment.type.incompatible + @MustCall({}) Object obj5 = s2; + @MustCall({"a"}) Object obj6 = s2; - @MustCall({}) Object obj7 = s3; - @MustCall({"a"}) Object obj8 = s3; + @MustCall({}) Object obj7 = s3; + @MustCall({"a"}) Object obj8 = s3; - @MustCall({}) Object obj9 = s4; - @MustCall({"a"}) Object obj10 = s4; - } + @MustCall({}) Object obj9 = s4; + @MustCall({"a"}) Object obj10 = s4; + } - @MustCall({}) - // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid - public class Subtype3 extends Subtype0 {} + @MustCall({}) + // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid + public class Subtype3 extends Subtype0 {} - @InheritableMustCall({}) - // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid - public class Subtype4 extends Subtype0 {} + @InheritableMustCall({}) + // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid + public class Subtype4 extends Subtype0 {} - @MustCall({"a"}) public class Subtype5 extends Subtype0 {} + @MustCall({"a"}) public class Subtype5 extends Subtype0 {} - @InheritableMustCall({"a"}) - public class Subtype6 extends Subtype0 {} + @InheritableMustCall({"a"}) + public class Subtype6 extends Subtype0 {} - public class Container { - Subtype0 in; + public class Container { + Subtype0 in; - void test() { - if (in instanceof Subtype1) { - ((Subtype1) in).m1(); - } - } + void test() { + if (in instanceof Subtype1) { + ((Subtype1) in).m1(); + } } + } } diff --git a/checker/tests/mustcall/Subtyping.java b/checker/tests/mustcall/Subtyping.java index df785f446e0..8f1c8ecf0e2 100644 --- a/checker/tests/mustcall/Subtyping.java +++ b/checker/tests/mustcall/Subtyping.java @@ -4,54 +4,54 @@ class Subtyping { - Object unannotatedObj; - - void test_act(@Owning @MustCallUnknown Object o) { - @MustCallUnknown Object act = o; - // :: error: assignment.type.incompatible - @MustCall("close") Object file = o; - // :: error: assignment.type.incompatible - @MustCall({"close", "read"}) Object f2 = o; - // :: error: assignment.type.incompatible - @MustCall({}) Object notAfile = o; - // :: error: assignment.type.incompatible - unannotatedObj = o; - } - - void test_close(@Owning @MustCall("close") Object o) { - @MustCallUnknown Object act = o; - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - // :: error: assignment.type.incompatible - @MustCall({}) Object notAfile = o; - // :: error: assignment.type.incompatible - unannotatedObj = o; - } - - void test_close_read(@Owning @MustCall({"close", "read"}) Object o) { - @MustCallUnknown Object act = o; - // :: error: assignment.type.incompatible - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - // :: error: assignment.type.incompatible - @MustCall({}) Object notAfile = o; - // :: error: assignment.type.incompatible - unannotatedObj = o; - } - - void test_blank(@Owning @MustCall({}) Object o) { - @MustCallUnknown Object act = o; - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - @MustCall({}) Object notAfile = o; - unannotatedObj = o; - } - - void test_unannotated(@Owning Object o) { - @MustCallUnknown Object act = o; - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - @MustCall({}) Object notAfile = o; - unannotatedObj = o; - } + Object unannotatedObj; + + void test_act(@Owning @MustCallUnknown Object o) { + @MustCallUnknown Object act = o; + // :: error: assignment.type.incompatible + @MustCall("close") Object file = o; + // :: error: assignment.type.incompatible + @MustCall({"close", "read"}) Object f2 = o; + // :: error: assignment.type.incompatible + @MustCall({}) Object notAfile = o; + // :: error: assignment.type.incompatible + unannotatedObj = o; + } + + void test_close(@Owning @MustCall("close") Object o) { + @MustCallUnknown Object act = o; + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + // :: error: assignment.type.incompatible + @MustCall({}) Object notAfile = o; + // :: error: assignment.type.incompatible + unannotatedObj = o; + } + + void test_close_read(@Owning @MustCall({"close", "read"}) Object o) { + @MustCallUnknown Object act = o; + // :: error: assignment.type.incompatible + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + // :: error: assignment.type.incompatible + @MustCall({}) Object notAfile = o; + // :: error: assignment.type.incompatible + unannotatedObj = o; + } + + void test_blank(@Owning @MustCall({}) Object o) { + @MustCallUnknown Object act = o; + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + @MustCall({}) Object notAfile = o; + unannotatedObj = o; + } + + void test_unannotated(@Owning Object o) { + @MustCallUnknown Object act = o; + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + @MustCall({}) Object notAfile = o; + unannotatedObj = o; + } } diff --git a/checker/tests/mustcall/SystemInOut.java b/checker/tests/mustcall/SystemInOut.java index 145c86a56e2..944974f779c 100644 --- a/checker/tests/mustcall/SystemInOut.java +++ b/checker/tests/mustcall/SystemInOut.java @@ -1,15 +1,14 @@ // A test that the checker doesn't ask you to close System.in, System.out, or System.err. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.util.Scanner; +import org.checkerframework.checker.mustcall.qual.*; class SystemInOut { - void test() { - @MustCall({}) InputStream in = System.in; - @MustCall({}) OutputStream out = System.out; - @MustCall({}) OutputStream err = System.err; - @MustCall({}) Scanner sysIn = new Scanner(System.in); - } + void test() { + @MustCall({}) InputStream in = System.in; + @MustCall({}) OutputStream out = System.out; + @MustCall({}) OutputStream err = System.err; + @MustCall({}) Scanner sysIn = new Scanner(System.in); + } } diff --git a/checker/tests/mustcall/ToStringOnSocket.java b/checker/tests/mustcall/ToStringOnSocket.java index 3a93f9becc0..02d80d8e9f5 100644 --- a/checker/tests/mustcall/ToStringOnSocket.java +++ b/checker/tests/mustcall/ToStringOnSocket.java @@ -4,15 +4,15 @@ import java.net.Socket; class ToStringOnSocket { - void log(String string) { - System.out.println(string); - } + void log(String string) { + System.out.println(string); + } - void test(Socket socket) { - log("bad socket: " + socket); - } + void test(Socket socket) { + log("bad socket: " + socket); + } - void test2(Socket socket) { - log("bad socket: " + socket.toString()); - } + void test2(Socket socket) { + log("bad socket: " + socket.toString()); + } } diff --git a/checker/tests/mustcall/TryWithResourcesCrash.java b/checker/tests/mustcall/TryWithResourcesCrash.java index f9a52f43477..8990b8d14bd 100644 --- a/checker/tests/mustcall/TryWithResourcesCrash.java +++ b/checker/tests/mustcall/TryWithResourcesCrash.java @@ -6,26 +6,26 @@ import java.io.OutputStream; class TryWithResourcesCrash { - void test(FileSystem fs, byte[] bytes, String path) throws IOException { - try (FSDataOutputStream out = fs.createFile(path).overwrite(true).build()) { - out.write(bytes); - } + void test(FileSystem fs, byte[] bytes, String path) throws IOException { + try (FSDataOutputStream out = fs.createFile(path).overwrite(true).build()) { + out.write(bytes); } + } - class FSDataOutputStream extends DataOutputStream { - FSDataOutputStream(OutputStream os) { - super(os); - } + class FSDataOutputStream extends DataOutputStream { + FSDataOutputStream(OutputStream os) { + super(os); } + } - abstract class FSDataOutputStreamBuilder< - S extends FSDataOutputStream, B extends FSDataOutputStreamBuilder> { - abstract S build(); + abstract class FSDataOutputStreamBuilder< + S extends FSDataOutputStream, B extends FSDataOutputStreamBuilder> { + abstract S build(); - abstract B overwrite(boolean b); - } + abstract B overwrite(boolean b); + } - abstract class FileSystem implements Closeable { - abstract FSDataOutputStreamBuilder createFile(String s); - } + abstract class FileSystem implements Closeable { + abstract FSDataOutputStreamBuilder createFile(String s); + } } diff --git a/checker/tests/mustcall/TryWithResourcesSimple.java b/checker/tests/mustcall/TryWithResourcesSimple.java index d71be20c953..c1fea21640b 100644 --- a/checker/tests/mustcall/TryWithResourcesSimple.java +++ b/checker/tests/mustcall/TryWithResourcesSimple.java @@ -1,52 +1,51 @@ // A test that try-with-resources variables are always @MustCall({"close"}). -import org.checkerframework.checker.mustcall.qual.MustCall; - import java.io.*; import java.net.*; +import org.checkerframework.checker.mustcall.qual.MustCall; public class TryWithResourcesSimple { - static void test(String address, int port) { - try (Socket socket = new Socket(address, port)) { - @MustCall({"close"}) Object s = socket; - } catch (Exception e) { + static void test(String address, int port) { + try (Socket socket = new Socket(address, port)) { + @MustCall({"close"}) Object s = socket; + } catch (Exception e) { - } } + } + + @SuppressWarnings("mustcall:annotations.on.use") + public static @MustCall({"close", "myMethod"}) Socket getFancySocket() { + return null; + } + + void test_fancy_sock(String address, int port) { + // This is illegal, because getFancySock()'s return type has another MC method beyond + // "close", + // which is the only MC method for Socket itself. + try (Socket socket = getFancySocket()) { + // :: error: (assignment.type.incompatible) + @MustCall({"close"}) Object s = socket; + } catch (Exception e) { - @SuppressWarnings("mustcall:annotations.on.use") - public static @MustCall({"close", "myMethod"}) Socket getFancySocket() { - return null; - } - - void test_fancy_sock(String address, int port) { - // This is illegal, because getFancySock()'s return type has another MC method beyond - // "close", - // which is the only MC method for Socket itself. - try (Socket socket = getFancySocket()) { - // :: error: (assignment.type.incompatible) - @MustCall({"close"}) Object s = socket; - } catch (Exception e) { - - } } + } - static void test_poly(String address, int port) { - try (Socket socket = new Socket(address, port)) { - // getChannel is @MustCallAlias (= poly) with the socket, so it should also be - // @MC({"close"}) - @MustCall({"close"}) Object s = socket.getChannel(); - } catch (Exception e) { + static void test_poly(String address, int port) { + try (Socket socket = new Socket(address, port)) { + // getChannel is @MustCallAlias (= poly) with the socket, so it should also be + // @MC({"close"}) + @MustCall({"close"}) Object s = socket.getChannel(); + } catch (Exception e) { - } } + } - static void test_two_mca_variables(String address, int port) { - try (Socket socket = new Socket(address, port); - InputStream in = socket.getInputStream()) { - @MustCall({"close"}) Object s = in; - } catch (Exception e) { + static void test_two_mca_variables(String address, int port) { + try (Socket socket = new Socket(address, port); + InputStream in = socket.getInputStream()) { + @MustCall({"close"}) Object s = in; + } catch (Exception e) { - } } + } } diff --git a/checker/tests/mustcall/TypeArgs.java b/checker/tests/mustcall/TypeArgs.java index 180001e919e..cb6181fa42d 100644 --- a/checker/tests/mustcall/TypeArgs.java +++ b/checker/tests/mustcall/TypeArgs.java @@ -2,90 +2,89 @@ public class TypeArgs { - static class A {} - - static class B extends A {} - - public void f1(Generic real, Generic other, boolean flag) { - f2(flag ? real : other); - } - - <@MustCall({"carly"}) Q extends @MustCall({"carly"}) Object> void f2( - Generic parm) {} - - interface Generic {} - - void m3( - @MustCall({}) Object a, - @MustCall({"foo"}) Object b, - @MustCall({"bar"}) Object c, - @MustCall({"foo", "bar"}) Object d) { - requireNothing1(a); - requireNothing2(a); - requireNothing1(b); - requireNothing2(b); - requireNothing1(c); - requireNothing2(c); - requireNothing1(d); - requireNothing2(d); - - requireFoo1(a); - requireFoo2(a); - requireFoo1(b); - requireFoo2(b); - requireFoo1(c); - requireFoo2(c); - requireFoo1(d); - requireFoo2(d); - - requireBar1(a); - requireBar2(a); - requireBar1(b); - requireBar2(b); - requireBar1(c); - requireBar2(c); - requireBar1(d); - requireBar2(d); - - requireFooBar1(a); - requireFooBar2(a); - requireFooBar1(b); - requireFooBar2(b); - requireFooBar1(c); - requireFooBar2(c); - requireFooBar1(d); - requireFooBar2(d); - } - - public static T requireNothing1(T obj) { - return obj; - } - - public static @MustCall({}) T requireNothing2(@MustCall({}) T obj) { - return obj; - } - - public static T requireFoo1(T obj) { - return obj; - } - - public static @MustCall({"foo"}) T requireFoo2(@MustCall({"foo"}) T obj) { - return obj; - } - - public static T requireBar1(T obj) { - return obj; - } - - public static @MustCall({"bar"}) T requireBar2(@MustCall({"bar"}) T obj) { - return obj; - } - - public static T requireFooBar1(T obj) { - return obj; - } - - public static @MustCall({"foo", "bar"}) T requireFooBar2(@MustCall({"foo", "bar"}) T obj) { - return obj; - } + static class A {} + + static class B extends A {} + + public void f1(Generic real, Generic other, boolean flag) { + f2(flag ? real : other); + } + + <@MustCall({"carly"}) Q extends @MustCall({"carly"}) Object> void f2(Generic parm) {} + + interface Generic {} + + void m3( + @MustCall({}) Object a, + @MustCall({"foo"}) Object b, + @MustCall({"bar"}) Object c, + @MustCall({"foo", "bar"}) Object d) { + requireNothing1(a); + requireNothing2(a); + requireNothing1(b); + requireNothing2(b); + requireNothing1(c); + requireNothing2(c); + requireNothing1(d); + requireNothing2(d); + + requireFoo1(a); + requireFoo2(a); + requireFoo1(b); + requireFoo2(b); + requireFoo1(c); + requireFoo2(c); + requireFoo1(d); + requireFoo2(d); + + requireBar1(a); + requireBar2(a); + requireBar1(b); + requireBar2(b); + requireBar1(c); + requireBar2(c); + requireBar1(d); + requireBar2(d); + + requireFooBar1(a); + requireFooBar2(a); + requireFooBar1(b); + requireFooBar2(b); + requireFooBar1(c); + requireFooBar2(c); + requireFooBar1(d); + requireFooBar2(d); + } + + public static T requireNothing1(T obj) { + return obj; + } + + public static @MustCall({}) T requireNothing2(@MustCall({}) T obj) { + return obj; + } + + public static T requireFoo1(T obj) { + return obj; + } + + public static @MustCall({"foo"}) T requireFoo2(@MustCall({"foo"}) T obj) { + return obj; + } + + public static T requireBar1(T obj) { + return obj; + } + + public static @MustCall({"bar"}) T requireBar2(@MustCall({"bar"}) T obj) { + return obj; + } + + public static T requireFooBar1(T obj) { + return obj; + } + + public static @MustCall({"foo", "bar"}) T requireFooBar2(@MustCall({"foo", "bar"}) T obj) { + return obj; + } } diff --git a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java index b0c4472ec50..a84df311681 100644 --- a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java +++ b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java @@ -5,72 +5,72 @@ public class AnnotatedForNullness { - @Initialized @NonNull Object initializedField = new Object(); - @Initialized @KeyForBottom @NonNull Object initializedKeyForBottomField = new Object(); + @Initialized @NonNull Object initializedField = new Object(); + @Initialized @KeyForBottom @NonNull Object initializedKeyForBottomField = new Object(); - @AnnotatedFor("initialization") - // No errors because AnnotatedFor("initialization") does not change the default for nullness. - Object annotatedForInitialization(Object test) { - return null; - } + @AnnotatedFor("initialization") + // No errors because AnnotatedFor("initialization") does not change the default for nullness. + Object annotatedForInitialization(Object test) { + return null; + } - @AnnotatedFor("nullness") - Object annotatedForNullness(Object test) { - // ::error: (return.type.incompatible) - return null; - } + @AnnotatedFor("nullness") + Object annotatedForNullness(Object test) { + // ::error: (return.type.incompatible) + return null; + } - // Method annotatedFor with both `nullness` and `initialization` should behave the same as - // annotatedForNullness. - @AnnotatedFor({"nullness", "initialization"}) - Object annotatedForNullnessAndInitialization(Object test) { - // ::error: (return.type.incompatible) - return null; - } + // Method annotatedFor with both `nullness` and `initialization` should behave the same as + // annotatedForNullness. + @AnnotatedFor({"nullness", "initialization"}) + Object annotatedForNullnessAndInitialization(Object test) { + // ::error: (return.type.incompatible) + return null; + } - Object unannotatedFor(Object test) { - return null; - } + Object unannotatedFor(Object test) { + return null; + } - @AnnotatedFor("nullness") - void foo(@Initialized AnnotatedForNullness this) { - // Expect two [argument.type.incompatible] errors in KeyForChecker and InitilizationChecker - // because conservative defaults are applied to `unannotatedFor` and it expects a @FBCBottom - // @KeyForBottom @Nonull Object. - // ::error: (argument.type.incompatible) - unannotatedFor(initializedField); - // Expect an error in KeyForChecker because conservative defaults are applied to - // `annotatedForInitialization` for hierarchies other than the Initialization Checker and - // it expects an @Initialized @KeyForBottom @Nonull Object. - // ::error: (argument.type.incompatible) - annotatedForInitialization(initializedField); - // Do not expect an error when conservative defaults are applied to - // `annotatedForInitialization` for hierarchies other than the Initialization Checker and - // it expects an @Initialized @KeyForBottom @Nonull Object. - annotatedForInitialization(initializedKeyForBottomField); - // Do not expect an error because these are AnnotatedFor("nullness") and these expect - // @Initialized @UnknownKeyFor @Nonnull Object. - annotatedForNullness(initializedField); - annotatedForNullnessAndInitialization(initializedField); - } + @AnnotatedFor("nullness") + void foo(@Initialized AnnotatedForNullness this) { + // Expect two [argument.type.incompatible] errors in KeyForChecker and InitilizationChecker + // because conservative defaults are applied to `unannotatedFor` and it expects a @FBCBottom + // @KeyForBottom @Nonull Object. + // ::error: (argument.type.incompatible) + unannotatedFor(initializedField); + // Expect an error in KeyForChecker because conservative defaults are applied to + // `annotatedForInitialization` for hierarchies other than the Initialization Checker and + // it expects an @Initialized @KeyForBottom @Nonull Object. + // ::error: (argument.type.incompatible) + annotatedForInitialization(initializedField); + // Do not expect an error when conservative defaults are applied to + // `annotatedForInitialization` for hierarchies other than the Initialization Checker and + // it expects an @Initialized @KeyForBottom @Nonull Object. + annotatedForInitialization(initializedKeyForBottomField); + // Do not expect an error because these are AnnotatedFor("nullness") and these expect + // @Initialized @UnknownKeyFor @Nonnull Object. + annotatedForNullness(initializedField); + annotatedForNullnessAndInitialization(initializedField); + } - @AnnotatedFor("initialization") - void bar() { - // Expect an error in InitilizationChecker because conservative defaults are applied to - // `unannotatedFor` and it expects a @FBCBottom @UnknownKeyFor @Nonull Object. - // ::error: (argument.type.incompatible) - unannotatedFor(initializedField); - // Do not expect an error because the warning is suppressed other than initialzation - // hierarchy when conservative defaults are applied to source code and it expects an - // @Initialized @KeyForBottom @Nonull Object. - annotatedForInitialization(initializedField); - // Do not expect an error when conservative defaults are applied to - // `annotatedForInitialization` for hierarchies other than the Initialization Checker and - // it expects an @Initialized @KeyForBottom @Nonull Object. - annotatedForInitialization(initializedKeyForBottomField); - // Do not expect an error because these are AnnotatedFor("nullness") and these expect - // @Initialized @UnknownKeyFor @Nonnull Object. - annotatedForNullness(initializedField); - annotatedForNullnessAndInitialization(initializedField); - } + @AnnotatedFor("initialization") + void bar() { + // Expect an error in InitilizationChecker because conservative defaults are applied to + // `unannotatedFor` and it expects a @FBCBottom @UnknownKeyFor @Nonull Object. + // ::error: (argument.type.incompatible) + unannotatedFor(initializedField); + // Do not expect an error because the warning is suppressed other than initialzation + // hierarchy when conservative defaults are applied to source code and it expects an + // @Initialized @KeyForBottom @Nonull Object. + annotatedForInitialization(initializedField); + // Do not expect an error when conservative defaults are applied to + // `annotatedForInitialization` for hierarchies other than the Initialization Checker and + // it expects an @Initialized @KeyForBottom @Nonull Object. + annotatedForInitialization(initializedKeyForBottomField); + // Do not expect an error because these are AnnotatedFor("nullness") and these expect + // @Initialized @UnknownKeyFor @Nonnull Object. + annotatedForNullness(initializedField); + annotatedForNullnessAndInitialization(initializedField); + } } diff --git a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java index 01c4054ae2e..aa44be38fc9 100644 --- a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java +++ b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java @@ -3,8 +3,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Test { - void foo(@Nullable Object o) { - // :: error: (dereference.of.nullable) - o.toString(); - } + void foo(@Nullable Object o) { + // :: error: (dereference.of.nullable) + o.toString(); + } } diff --git a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java index 7b949d49943..536fb635c08 100644 --- a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java +++ b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java @@ -3,8 +3,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Test { - void foo(@Nullable Object o) { - // No error because this package is not annotated for nullness. - o.toString(); - } + void foo(@Nullable Object o) { + // No error because this package is not annotated for nullness. + o.toString(); + } } diff --git a/checker/tests/nullness-asserts/NonNullMapValue.java b/checker/tests/nullness-asserts/NonNullMapValue.java index aa86bc5b8ce..a24db9c18e3 100644 --- a/checker/tests/nullness-asserts/NonNullMapValue.java +++ b/checker/tests/nullness-asserts/NonNullMapValue.java @@ -1,8 +1,3 @@ -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.KeyFor; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.io.PrintStream; import java.util.Date; import java.util.HashMap; @@ -11,202 +6,206 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.KeyFor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class NonNullMapValue { - // Discussion: - // - // It can be useful to indicate that all the values in a map are non-null. - // ("@NonNull" is redundant in this declaration, but I've written it - // explicitly because it is the annotation we are talking about.) - // HashMap myMap; - // - // However, the get method's declaration is misleading (in the context of the nullness type - // system), since it can always return null no matter whether the map values are non-null: - // V get(Object key) { ... return null; } - // The Nullness Checker does not use the signature as written. It has hard-coded rules for the - // get() method. It checks that the passed key to has type @KeyFor("theMap"). - - Map myMap; - - NonNullMapValue(Map myMap) { - this.myMap = myMap; - } - - void testMyMap(String key) { - @NonNull String value; - // :: error: (assignment.type.incompatible) - value = myMap.get(key); // should issue warning - if (myMap.containsKey(key)) { - value = myMap.get(key); - } - for (String keyInMap : myMap.keySet()) { - // :: error: (assignment.type.incompatible) - value = myMap.get(key); // should issue warning - } - for (String keyInMap : myMap.keySet()) { - value = myMap.get(keyInMap); - } - for (Map.Entry<@KeyFor("myMap") String, @NonNull String> entry : myMap.entrySet()) { - String keyInMap = entry.getKey(); - value = entry.getValue(); - } - for (Iterator<@KeyFor("myMap") String> iter = myMap.keySet().iterator(); iter.hasNext(); ) { - String keyInMap = iter.next(); - // value = myMap.get(keyInMap); - } - value = myMap.containsKey(key) ? myMap.get(key) : "hello"; - } - - public static void print( - Map> graph, PrintStream ps, int indent) { - for (T node : graph.keySet()) { - for (T child : graph.get(node)) { - ps.printf(" %s%n", child); - } - @NonNull List children = graph.get(node); - for (T child : children) { - ps.printf(" %s%n", child); - } - } - } - - public static void testAssertFlow(Map> preds, T node) { - assert preds.containsKey(node); - for (T pred : preds.get(node)) {} - } - - public static void testContainsKey1(Map> dom, T pred) { - assert dom.containsKey(pred); - // Both of the next two lines should type-check. The second one won't - // unless the checker knows that pred is a key in the map. - List dom_of_pred1 = dom.get(pred); - @NonNull List dom_of_pred2 = dom.get(pred); - } - - public static void testContainsKey2(Map> dom, T pred) { - if (!dom.containsKey(pred)) { - throw new Error(); - } - // Both of the next two lines should type-check. The second one won't - // unless the checker knows that pred is a key in the map. - List dom_of_pred1 = dom.get(pred); - @NonNull List dom_of_pred2 = dom.get(pred); - } - - public static void process_unmatched_procedure_entries() { - HashMap call_hashmap = new HashMap<>(); - for (Integer i : call_hashmap.keySet()) { - @NonNull Date d = call_hashmap.get(i); - } - Set<@KeyFor("call_hashmap") Integer> keys = call_hashmap.keySet(); - for (Integer i : keys) { - @NonNull Date d = call_hashmap.get(i); - } - Set<@KeyFor("call_hashmap") Integer> keys_sorted = - new TreeSet<@KeyFor("call_hashmap") Integer>(call_hashmap.keySet()); - for (Integer i : keys_sorted) { - @NonNull Date d = call_hashmap.get(i); - } - } - - public static Object testPut(Map map, Object key) { - if (!map.containsKey(key)) { - map.put(key, new Object()); - } - return map.get(key); - } - - public static Object testAssertGet(Map map, Object key) { - assert map.get(key) != null; - return map.get(key); - } - - public static Object testThrow(Map map, Object key) { - if (!map.containsKey(key)) { - if (true) { - return "m"; - } else { - throw new RuntimeException(); - } - } - return map.get(key); - } - - public void negateMap(Map map, Object key) { - if (!map.containsKey(key)) { - } else { - @NonNull Object v = map.get(key); - } - } - - public void withinElseInvalid(Map map, Object key) { - if (map.containsKey(key)) { - } else { - // :: error: (assignment.type.incompatible) - @NonNull Object v = map.get(key); // should issue warning - } - } - - // Map.get should be annotated as @org.checkerframework.dataflow.qual.Pure - public static int mapGetSize(MyMap> covered, Object file) { - return (covered.get(file) == null) ? 0 : covered.get(file).size(); - } - - interface MyMap extends Map { - // TODO: @AssertGenericNullnessIfTrue("get(#1)") - @org.checkerframework.dataflow.qual.Pure - public abstract boolean containsKey(@Nullable Object a1); - - // We get an override warning, because we do not use the annotated JDK in the - // test suite. Ignore this. - @SuppressWarnings("override.return.invalid") - @org.checkerframework.dataflow.qual.Pure - public @Nullable V get(@Nullable Object o); - } - - private static final String KEY = "key"; - private static final String KEY2 = "key2"; - - void testAnd(MyMap map, MyMap map2) { - if (map.containsKey(KEY)) { - map.get(KEY).toString(); - } - // :: warning: (nulltest.redundant) - if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) {} - // :: error: (dereference.of.nullable) :: warning: (nulltest.redundant) - if (map2.containsKey(KEY2) && map2.get(KEY2).toString() != null) {} - } - - void testAndWithIllegalMapAnnotation(MyMap2 map) { - if (map.containsKey(KEY)) { - map.get(KEY).toString(); - } - // :: warning: (nulltest.redundant) - if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { - // do nothing - } - } - - interface MyMap2 { - @org.checkerframework.dataflow.qual.Pure - // This annotation is not legal on containsKey in general. If the Map is declared as (say) - // Map, then get returns a nullable value. We really want to say - // that if containsKey returns non-null, then get returns V rather than @Nullable V, but I - // don't know how to say that. - @EnsuresNonNullIf(result = true, expression = "get(#1)") - public abstract boolean containsKey(@Nullable Object a1); - - @org.checkerframework.dataflow.qual.Pure - public abstract @Nullable V get(@Nullable Object a1); - } - - interface MyMap3 { - @org.checkerframework.dataflow.qual.Pure - @EnsuresNonNullIf(result = true, expression = "get(#1)") - // The following error is issued because, unlike in interface MyMap2, - // this interface has no get() method. - // :: error: (flowexpr.parse.error) - boolean containsKey(@Nullable Object a1); - } + // Discussion: + // + // It can be useful to indicate that all the values in a map are non-null. + // ("@NonNull" is redundant in this declaration, but I've written it + // explicitly because it is the annotation we are talking about.) + // HashMap myMap; + // + // However, the get method's declaration is misleading (in the context of the nullness type + // system), since it can always return null no matter whether the map values are non-null: + // V get(Object key) { ... return null; } + // The Nullness Checker does not use the signature as written. It has hard-coded rules for the + // get() method. It checks that the passed key to has type @KeyFor("theMap"). + + Map myMap; + + NonNullMapValue(Map myMap) { + this.myMap = myMap; + } + + void testMyMap(String key) { + @NonNull String value; + // :: error: (assignment.type.incompatible) + value = myMap.get(key); // should issue warning + if (myMap.containsKey(key)) { + value = myMap.get(key); + } + for (String keyInMap : myMap.keySet()) { + // :: error: (assignment.type.incompatible) + value = myMap.get(key); // should issue warning + } + for (String keyInMap : myMap.keySet()) { + value = myMap.get(keyInMap); + } + for (Map.Entry<@KeyFor("myMap") String, @NonNull String> entry : myMap.entrySet()) { + String keyInMap = entry.getKey(); + value = entry.getValue(); + } + for (Iterator<@KeyFor("myMap") String> iter = myMap.keySet().iterator(); iter.hasNext(); ) { + String keyInMap = iter.next(); + // value = myMap.get(keyInMap); + } + value = myMap.containsKey(key) ? myMap.get(key) : "hello"; + } + + public static void print( + Map> graph, PrintStream ps, int indent) { + for (T node : graph.keySet()) { + for (T child : graph.get(node)) { + ps.printf(" %s%n", child); + } + @NonNull List children = graph.get(node); + for (T child : children) { + ps.printf(" %s%n", child); + } + } + } + + public static void testAssertFlow(Map> preds, T node) { + assert preds.containsKey(node); + for (T pred : preds.get(node)) {} + } + + public static void testContainsKey1(Map> dom, T pred) { + assert dom.containsKey(pred); + // Both of the next two lines should type-check. The second one won't + // unless the checker knows that pred is a key in the map. + List dom_of_pred1 = dom.get(pred); + @NonNull List dom_of_pred2 = dom.get(pred); + } + + public static void testContainsKey2(Map> dom, T pred) { + if (!dom.containsKey(pred)) { + throw new Error(); + } + // Both of the next two lines should type-check. The second one won't + // unless the checker knows that pred is a key in the map. + List dom_of_pred1 = dom.get(pred); + @NonNull List dom_of_pred2 = dom.get(pred); + } + + public static void process_unmatched_procedure_entries() { + HashMap call_hashmap = new HashMap<>(); + for (Integer i : call_hashmap.keySet()) { + @NonNull Date d = call_hashmap.get(i); + } + Set<@KeyFor("call_hashmap") Integer> keys = call_hashmap.keySet(); + for (Integer i : keys) { + @NonNull Date d = call_hashmap.get(i); + } + Set<@KeyFor("call_hashmap") Integer> keys_sorted = + new TreeSet<@KeyFor("call_hashmap") Integer>(call_hashmap.keySet()); + for (Integer i : keys_sorted) { + @NonNull Date d = call_hashmap.get(i); + } + } + + public static Object testPut(Map map, Object key) { + if (!map.containsKey(key)) { + map.put(key, new Object()); + } + return map.get(key); + } + + public static Object testAssertGet(Map map, Object key) { + assert map.get(key) != null; + return map.get(key); + } + + public static Object testThrow(Map map, Object key) { + if (!map.containsKey(key)) { + if (true) { + return "m"; + } else { + throw new RuntimeException(); + } + } + return map.get(key); + } + + public void negateMap(Map map, Object key) { + if (!map.containsKey(key)) { + } else { + @NonNull Object v = map.get(key); + } + } + + public void withinElseInvalid(Map map, Object key) { + if (map.containsKey(key)) { + } else { + // :: error: (assignment.type.incompatible) + @NonNull Object v = map.get(key); // should issue warning + } + } + + // Map.get should be annotated as @org.checkerframework.dataflow.qual.Pure + public static int mapGetSize(MyMap> covered, Object file) { + return (covered.get(file) == null) ? 0 : covered.get(file).size(); + } + + interface MyMap extends Map { + // TODO: @AssertGenericNullnessIfTrue("get(#1)") + @org.checkerframework.dataflow.qual.Pure + public abstract boolean containsKey(@Nullable Object a1); + + // We get an override warning, because we do not use the annotated JDK in the + // test suite. Ignore this. + @SuppressWarnings("override.return.invalid") + @org.checkerframework.dataflow.qual.Pure + public @Nullable V get(@Nullable Object o); + } + + private static final String KEY = "key"; + private static final String KEY2 = "key2"; + + void testAnd(MyMap map, MyMap map2) { + if (map.containsKey(KEY)) { + map.get(KEY).toString(); + } + // :: warning: (nulltest.redundant) + if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) {} + // :: error: (dereference.of.nullable) :: warning: (nulltest.redundant) + if (map2.containsKey(KEY2) && map2.get(KEY2).toString() != null) {} + } + + void testAndWithIllegalMapAnnotation(MyMap2 map) { + if (map.containsKey(KEY)) { + map.get(KEY).toString(); + } + // :: warning: (nulltest.redundant) + if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { + // do nothing + } + } + + interface MyMap2 { + @org.checkerframework.dataflow.qual.Pure + // This annotation is not legal on containsKey in general. If the Map is declared as (say) + // Map, then get returns a nullable value. We really want to say + // that if containsKey returns non-null, then get returns V rather than @Nullable V, but I + // don't know how to say that. + @EnsuresNonNullIf(result = true, expression = "get(#1)") + public abstract boolean containsKey(@Nullable Object a1); + + @org.checkerframework.dataflow.qual.Pure + public abstract @Nullable V get(@Nullable Object a1); + } + + interface MyMap3 { + @org.checkerframework.dataflow.qual.Pure + @EnsuresNonNullIf(result = true, expression = "get(#1)") + // The following error is issued because, unlike in interface MyMap2, + // this interface has no get() method. + // :: error: (flowexpr.parse.error) + boolean containsKey(@Nullable Object a1); + } } diff --git a/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java b/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java index 3d639182b47..56aaed96169 100644 --- a/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java +++ b/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java @@ -2,13 +2,13 @@ public class TestAssumeAssertionsAreEnabled { - void foo(@Nullable String s1, @Nullable String s2) { - // :: error: (dereference.of.nullable) - assert s2.equals(s1); - } + void foo(@Nullable String s1, @Nullable String s2) { + // :: error: (dereference.of.nullable) + assert s2.equals(s1); + } - void bar(@Nullable String s1, @Nullable String s2) { - // :: error: (dereference.of.nullable) - assert s2.equals(s1) : "@AssumeAssertion(nullness)"; - } + void bar(@Nullable String s1, @Nullable String s2) { + // :: error: (dereference.of.nullable) + assert s2.equals(s1) : "@AssumeAssertion(nullness)"; + } } diff --git a/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java b/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java index 37b2fb419d6..ae3f09038b7 100644 --- a/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java +++ b/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java @@ -2,13 +2,13 @@ public class TestAssumeAssertionsAreDisabled { - void foo(@Nullable String s1, @Nullable String s2) { + void foo(@Nullable String s1, @Nullable String s2) { - // If assertions are disabled, then this cannot throw a NullPointerException - assert s2.equals(s1); + // If assertions are disabled, then this cannot throw a NullPointerException + assert s2.equals(s1); - // However, even with assertions disabled, @AssumeAssertion is still respected - // :: error: (dereference.of.nullable) - assert s2.equals(s1) : "@AssumeAssertion(nullness)"; - } + // However, even with assertions disabled, @AssumeAssertion is still respected + // :: error: (dereference.of.nullable) + assert s2.equals(s1) : "@AssumeAssertion(nullness)"; + } } diff --git a/checker/tests/nullness-assumeinitialized/AssumeInitTest.java b/checker/tests/nullness-assumeinitialized/AssumeInitTest.java index f6155ad083b..9a95b3acd14 100644 --- a/checker/tests/nullness-assumeinitialized/AssumeInitTest.java +++ b/checker/tests/nullness-assumeinitialized/AssumeInitTest.java @@ -3,27 +3,27 @@ public class AssumeInitTest { - AssumeInitTest f; + AssumeInitTest f; - public AssumeInitTest(String arg) {} + public AssumeInitTest(String arg) {} - void test() { - @NonNull String s = "234"; - // :: error: (assignment.type.incompatible) - s = null; - } + void test() { + @NonNull String s = "234"; + // :: error: (assignment.type.incompatible) + s = null; + } - void test2(@UnknownInitialization @NonNull AssumeInitTest t) { - @Initialized @NonNull AssumeInitTest a = t.f; - } + void test2(@UnknownInitialization @NonNull AssumeInitTest t) { + @Initialized @NonNull AssumeInitTest a = t.f; + } - void simplestTestEver() { - @NonNull String a = "abc"; + void simplestTestEver() { + @NonNull String a = "abc"; - // :: error: (assignment.type.incompatible) - a = null; + // :: error: (assignment.type.incompatible) + a = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = null; - } + // :: error: (assignment.type.incompatible) + @NonNull String b = null; + } } diff --git a/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java b/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java index 3c5fa2911e9..b9f9abd5830 100644 --- a/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java +++ b/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java @@ -1,52 +1,51 @@ +import java.util.HashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.HashMap; -import java.util.Map; - public class AssumeKeyForTest { - void m1(Map m, String k) { - @NonNull Integer x = m.get(k); - } + void m1(Map m, String k) { + @NonNull Integer x = m.get(k); + } - void m1b(HashMap m, String k) { - @NonNull Integer x = m.get(k); - } + void m1b(HashMap m, String k) { + @NonNull Integer x = m.get(k); + } - void m2(Map m, String k) { - @Nullable Integer x = m.get(k); - } + void m2(Map m, String k) { + @Nullable Integer x = m.get(k); + } - void m3(Map m, String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer x = m.get(k); - } + void m3(Map m, String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer x = m.get(k); + } - void m4(Map m, String k) { - @Nullable Integer x = m.get(k); - } + void m4(Map m, String k) { + @Nullable Integer x = m.get(k); + } - void m5(Map m, @KeyFor("#1") String k) { - @NonNull Integer x = m.get(k); - } + void m5(Map m, @KeyFor("#1") String k) { + @NonNull Integer x = m.get(k); + } - void m6(Map m, @KeyFor("#1") String k) { - @Nullable Integer x = m.get(k); - } + void m6(Map m, @KeyFor("#1") String k) { + @Nullable Integer x = m.get(k); + } - void m7(Map m, @KeyFor("#1") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer x = m.get(k); - } + void m7(Map m, @KeyFor("#1") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer x = m.get(k); + } - void m7b(HashMap m, @KeyFor("#1") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer x = m.get(k); - } + void m7b(HashMap m, @KeyFor("#1") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer x = m.get(k); + } - void m8(Map m, @KeyFor("#1") String k) { - @Nullable Integer x = m.get(k); - } + void m8(Map m, @KeyFor("#1") String k) { + @Nullable Integer x = m.get(k); + } } diff --git a/checker/tests/nullness-checkcastelementtype/Issue1315.java b/checker/tests/nullness-checkcastelementtype/Issue1315.java index 5c8b4ed68ab..8ccdb486700 100644 --- a/checker/tests/nullness-checkcastelementtype/Issue1315.java +++ b/checker/tests/nullness-checkcastelementtype/Issue1315.java @@ -4,34 +4,34 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1315 { - static class Box { - T f; + static class Box { + T f; - Box(T p) { - f = p; - } + Box(T p) { + f = p; + } - @SuppressWarnings("unchecked") - T test1(@Nullable Object p) { - // :: warning: (cast.unsafe) - return (T) p; - } + @SuppressWarnings("unchecked") + T test1(@Nullable Object p) { + // :: warning: (cast.unsafe) + return (T) p; + } - // The Nullness Checker should not issue a cast.unsafe warning, - // but the KeyFor Checker does, so suppress that warning. - @SuppressWarnings({"unchecked", "keyfor:cast.unsafe"}) - T test2(Object p) { - return (T) p; - } + // The Nullness Checker should not issue a cast.unsafe warning, + // but the KeyFor Checker does, so suppress that warning. + @SuppressWarnings({"unchecked", "keyfor:cast.unsafe"}) + T test2(Object p) { + return (T) p; } + } - static class Casts { - public static void test() { - Box bs = new Box<>(""); - bs.f = bs.test1(null); - // :: error: (argument.type.incompatible) - bs.f = bs.test2(null); - bs.f.toString(); - } + static class Casts { + public static void test() { + Box bs = new Box<>(""); + bs.f = bs.test1(null); + // :: error: (argument.type.incompatible) + bs.f = bs.test2(null); + bs.f.toString(); } + } } diff --git a/checker/tests/nullness-concurrent-semantics/Issue350.java b/checker/tests/nullness-concurrent-semantics/Issue350.java index ca5c49484f0..858acba80b9 100644 --- a/checker/tests/nullness-concurrent-semantics/Issue350.java +++ b/checker/tests/nullness-concurrent-semantics/Issue350.java @@ -5,47 +5,47 @@ class Test1 { - public @Nullable String y; + public @Nullable String y; - public void test2() { - y = ""; - // Sanity check that -AconcurrentSemantics is set - // :: error: (dereference.of.nullable) - y.toString(); - } + public void test2() { + y = ""; + // Sanity check that -AconcurrentSemantics is set + // :: error: (dereference.of.nullable) + y.toString(); + } - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - void test() { - if (x == null) { - x = ""; - } - x.toString(); + void test() { + if (x == null) { + x = ""; } + x.toString(); + } } class Test2 { - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - void setX(String x) { - this.x = x; - } + void setX(String x) { + this.x = x; + } - void test() { - if (x == null) { - x = ""; - } - setX(x); + void test() { + if (x == null) { + x = ""; } + setX(x); + } } class Test3 { - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - @EnsuresNonNull("#1") - void setX(final String x) { - this.x = x; - } + @EnsuresNonNull("#1") + void setX(final String x) { + this.x = x; + } } diff --git a/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java b/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java index d698e742b78..c19d828dd86 100644 --- a/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java +++ b/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java @@ -2,38 +2,38 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; class NullnessEnclosingExprTest { - class InnerWithImplicitEnclosingExpression { - // There is no possible NPE and therefore no expected error. - InnerWithImplicitEnclosingExpression() { - NullnessEnclosingExprTest.this.f.hashCode(); - } + class InnerWithImplicitEnclosingExpression { + // There is no possible NPE and therefore no expected error. + InnerWithImplicitEnclosingExpression() { + NullnessEnclosingExprTest.this.f.hashCode(); } + } - class InnerWithInitializedEnclosingExpression { - // The default type of enclosing expression is same as InnerWithImplicitEnclosingExpression, - // we just make it explicit for testing. - InnerWithInitializedEnclosingExpression( - @Initialized NullnessEnclosingExprTest NullnessEnclosingExprTest.this) {} - } + class InnerWithInitializedEnclosingExpression { + // The default type of enclosing expression is same as InnerWithImplicitEnclosingExpression, + // we just make it explicit for testing. + InnerWithInitializedEnclosingExpression( + @Initialized NullnessEnclosingExprTest NullnessEnclosingExprTest.this) {} + } - class InnerWithUnknownInitializationEnclosingExpression { - InnerWithUnknownInitializationEnclosingExpression( - @UnknownInitialization NullnessEnclosingExprTest NullnessEnclosingExprTest.this) { - // This should also never lead to an NPE, because that dereference should produce an - // type error. - // See Issue https://github.com/eisop/checker-framework/issues/412. - NullnessEnclosingExprTest.this.f.hashCode(); - } + class InnerWithUnknownInitializationEnclosingExpression { + InnerWithUnknownInitializationEnclosingExpression( + @UnknownInitialization NullnessEnclosingExprTest NullnessEnclosingExprTest.this) { + // This should also never lead to an NPE, because that dereference should produce an + // type error. + // See Issue https://github.com/eisop/checker-framework/issues/412. + NullnessEnclosingExprTest.this.f.hashCode(); } + } - NullnessEnclosingExprTest() { - // :: error: (enclosingexpr.type.incompatible) - this.new InnerWithImplicitEnclosingExpression(); - // :: error: (enclosingexpr.type.incompatible) - this.new InnerWithInitializedEnclosingExpression(); - this.new InnerWithUnknownInitializationEnclosingExpression(); - f = "a"; - } + NullnessEnclosingExprTest() { + // :: error: (enclosingexpr.type.incompatible) + this.new InnerWithImplicitEnclosingExpression(); + // :: error: (enclosingexpr.type.incompatible) + this.new InnerWithInitializedEnclosingExpression(); + this.new InnerWithUnknownInitializationEnclosingExpression(); + f = "a"; + } - Object f; + Object f; } diff --git a/checker/tests/nullness-extra/Bug109_A.java b/checker/tests/nullness-extra/Bug109_A.java index 784928e1c07..b3019e2a01c 100644 --- a/checker/tests/nullness-extra/Bug109_A.java +++ b/checker/tests/nullness-extra/Bug109_A.java @@ -1,9 +1,9 @@ public class Bug109_A { - int one = "1".length(); + int one = "1".length(); - // fix 1: public final int one; { one = "1".length(); } - // fix 2: public final int one = 0 + "1".length(); + // fix 1: public final int one; { one = "1".length(); } + // fix 2: public final int one = 0 + "1".length(); - int nl = 5; - int two = nl; + int nl = 5; + int two = nl; } diff --git a/checker/tests/nullness-extra/Bug109_B.java b/checker/tests/nullness-extra/Bug109_B.java index b4beca96922..9dfaf203eae 100644 --- a/checker/tests/nullness-extra/Bug109_B.java +++ b/checker/tests/nullness-extra/Bug109_B.java @@ -1,11 +1,11 @@ public class Bug109_B extends Bug109_A { - public Bug109_B() { - // Accessing field one causes NPE - // at org.checkerframework.checker.nullness.MapGetHeuristics.handle - // (MapGetHeuristics.java:91) + public Bug109_B() { + // Accessing field one causes NPE + // at org.checkerframework.checker.nullness.MapGetHeuristics.handle + // (MapGetHeuristics.java:91) - int myone = one; + int myone = one; - int mytwo = two; - } + int mytwo = two; + } } diff --git a/checker/tests/nullness-extra/compat/CompatTest.java b/checker/tests/nullness-extra/compat/CompatTest.java index 2241d42030d..49dc9dfb634 100644 --- a/checker/tests/nullness-extra/compat/CompatTest.java +++ b/checker/tests/nullness-extra/compat/CompatTest.java @@ -1,9 +1,8 @@ import lib.Lib; - import org.checkerframework.checker.nullness.qual.NonNull; public class CompatTest { - void m() { - @NonNull Object o = Lib.maybeGetObject(); - } + void m() { + @NonNull Object o = Lib.maybeGetObject(); + } } diff --git a/checker/tests/nullness-extra/compat/lib/Lib.java b/checker/tests/nullness-extra/compat/lib/Lib.java index 427b841cf20..4625a83a019 100644 --- a/checker/tests/nullness-extra/compat/lib/Lib.java +++ b/checker/tests/nullness-extra/compat/lib/Lib.java @@ -3,7 +3,7 @@ import javax.annotation.Nullable; public class Lib { - @Nullable public static Object maybeGetObject() { - return null; - } + @Nullable public static Object maybeGetObject() { + return null; + } } diff --git a/checker/tests/nullness-extra/issue265/Delta.java b/checker/tests/nullness-extra/issue265/Delta.java index f8b67139c70..bb5bb284b26 100644 --- a/checker/tests/nullness-extra/issue265/Delta.java +++ b/checker/tests/nullness-extra/issue265/Delta.java @@ -1,9 +1,9 @@ import java.util.List; public class Delta { - List field; + List field; - Delta(List field) { - this.field = ImmutableList.copyOf(field); - } + Delta(List field) { + this.field = ImmutableList.copyOf(field); + } } diff --git a/checker/tests/nullness-extra/issue265/ImmutableList.java b/checker/tests/nullness-extra/issue265/ImmutableList.java index 4383eceb918..b1b690b212e 100644 --- a/checker/tests/nullness-extra/issue265/ImmutableList.java +++ b/checker/tests/nullness-extra/issue265/ImmutableList.java @@ -2,7 +2,7 @@ import java.util.List; public abstract class ImmutableList implements List { - public static List copyOf(Iterable elements) { - return new ArrayList(); - } + public static List copyOf(Iterable elements) { + return new ArrayList(); + } } diff --git a/checker/tests/nullness-extra/issue309/Issue309.java b/checker/tests/nullness-extra/issue309/Issue309.java index 2c3a1029d3d..07e5186d4b9 100644 --- a/checker/tests/nullness-extra/issue309/Issue309.java +++ b/checker/tests/nullness-extra/issue309/Issue309.java @@ -1,7 +1,7 @@ import lib.Lib; public class Issue309 { - void bar() { - Lib.foo(); - } + void bar() { + Lib.foo(); + } } diff --git a/checker/tests/nullness-extra/issue309/lib/Lib.java b/checker/tests/nullness-extra/issue309/lib/Lib.java index a3344396a33..ead9c6bb5e2 100644 --- a/checker/tests/nullness-extra/issue309/lib/Lib.java +++ b/checker/tests/nullness-extra/issue309/lib/Lib.java @@ -1,6 +1,6 @@ package lib; public class Lib { - @Anno - public static void foo() {} + @Anno + public static void foo() {} } diff --git a/checker/tests/nullness-extra/issue348/Issue348.java b/checker/tests/nullness-extra/issue348/Issue348.java index f7dd6b82823..72ba0649ce2 100644 --- a/checker/tests/nullness-extra/issue348/Issue348.java +++ b/checker/tests/nullness-extra/issue348/Issue348.java @@ -2,8 +2,8 @@ public class Issue348 { - void test() { - Lib lib = new Lib(); - lib.foo(); - } + void test() { + Lib lib = new Lib(); + lib.foo(); + } } diff --git a/checker/tests/nullness-extra/issue348/lib/Lib.java b/checker/tests/nullness-extra/issue348/lib/Lib.java index fc9edc7da06..aed0f3b3ea4 100644 --- a/checker/tests/nullness-extra/issue348/lib/Lib.java +++ b/checker/tests/nullness-extra/issue348/lib/Lib.java @@ -1,6 +1,6 @@ package lib; public class Lib extends LibSuper { - @Override - public void foo() {} + @Override + public void foo() {} } diff --git a/checker/tests/nullness-extra/issue348/lib/LibSuper.java b/checker/tests/nullness-extra/issue348/lib/LibSuper.java index 3d8ed92d015..c995eed14e9 100644 --- a/checker/tests/nullness-extra/issue348/lib/LibSuper.java +++ b/checker/tests/nullness-extra/issue348/lib/LibSuper.java @@ -1,6 +1,6 @@ package lib; public class LibSuper { - @Anno - public void foo() {} + @Anno + public void foo() {} } diff --git a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java index 0a9be45d12c..11d72d63882 100644 --- a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java +++ b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java @@ -1,7 +1,7 @@ package testpkg; public class Issue3597A { - void f() { - System.err.println(new Issue3597B().f().toString()); - } + void f() { + System.err.println(new Issue3597B().f().toString()); + } } diff --git a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java index b365430b09f..4d0df950d9b 100644 --- a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java +++ b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3597B { - @Nullable Object f() { - return new Object(); - } + @Nullable Object f() { + return new Object(); + } } diff --git a/checker/tests/nullness-extra/issue502/Issue502.java b/checker/tests/nullness-extra/issue502/Issue502.java index a39f3ff3749..a1d3816b307 100644 --- a/checker/tests/nullness-extra/issue502/Issue502.java +++ b/checker/tests/nullness-extra/issue502/Issue502.java @@ -2,8 +2,8 @@ // https://github.com/typetools/checker-framework/issues/502 public class Issue502 { - @Override - public String toString() { - return ""; - } + @Override + public String toString() { + return ""; + } } diff --git a/checker/tests/nullness-extra/issue5174/Issue5174.java b/checker/tests/nullness-extra/issue5174/Issue5174.java index a234c2bb760..da84b645e7e 100644 --- a/checker/tests/nullness-extra/issue5174/Issue5174.java +++ b/checker/tests/nullness-extra/issue5174/Issue5174.java @@ -2,68 +2,68 @@ // https://github.com/typetools/checker-framework/issues/5174 class Issue5174Super { - S methodInner(S in) { - return in; - } + S methodInner(S in) { + return in; + } - S f; - static Object sf = ""; + S f; + static Object sf = ""; - Issue5174Super(S f) { - this.f = f; - } + Issue5174Super(S f) { + this.f = f; + } } class Issue5174Sub extends Issue5174Super { - Issue5174Sub(T f) { - super(f); - } + Issue5174Sub(T f) { + super(f); + } - void accMethImpl(T in) { - Object o = methodInner(in); - } + void accMethImpl(T in) { + Object o = methodInner(in); + } - void accMethExpl(T in) { - Object o = this.methodInner(in); - } + void accMethExpl(T in) { + Object o = this.methodInner(in); + } - void accFieldImpl() { - Object o = f; - } + void accFieldImpl() { + Object o = f; + } - void accFieldExpl() { - Object o = this.f; - } + void accFieldExpl() { + Object o = this.f; + } - void accStaticField() { - Object o; - o = sf; - o = Issue5174Sub.sf; - o = Issue5174Super.sf; - } + void accStaticField() { + Object o; + o = sf; + o = Issue5174Sub.sf; + o = Issue5174Super.sf; + } - class SubNested { - void nestedaccMethImpl(T in) { - Object o = methodInner(in); - } + class SubNested { + void nestedaccMethImpl(T in) { + Object o = methodInner(in); + } - void nestedaccMethExpl(T in) { - Object o = Issue5174Sub.this.methodInner(in); - } + void nestedaccMethExpl(T in) { + Object o = Issue5174Sub.this.methodInner(in); + } - void nestedaccFieldImpl() { - Object o = f; - } + void nestedaccFieldImpl() { + Object o = f; + } - void nestedaccFieldExpl() { - Object o = Issue5174Sub.this.f; - } + void nestedaccFieldExpl() { + Object o = Issue5174Sub.this.f; + } - void nestedaccStaticField() { - Object o; - o = sf; - o = Issue5174Sub.sf; - o = Issue5174Super.sf; - } + void nestedaccStaticField() { + Object o; + o = sf; + o = Issue5174Sub.sf; + o = Issue5174Super.sf; } + } } diff --git a/checker/tests/nullness-extra/issue559/Issue559.java b/checker/tests/nullness-extra/issue559/Issue559.java index 158dc6fa384..c18c74b1294 100644 --- a/checker/tests/nullness-extra/issue559/Issue559.java +++ b/checker/tests/nullness-extra/issue559/Issue559.java @@ -4,10 +4,10 @@ import java.util.Optional; public class Issue559 { - void bar(Optional o) { - // With myjdk.astub the following should fail with an - // argument.type.incompatible error. - o.orElse(null); - o.orElse("Hi"); - } + void bar(Optional o) { + // With myjdk.astub the following should fail with an + // argument.type.incompatible error. + o.orElse(null); + o.orElse("Hi"); + } } diff --git a/checker/tests/nullness-extra/issue594/Issue594.java b/checker/tests/nullness-extra/issue594/Issue594.java index af99af1d2e0..a34d199cd65 100644 --- a/checker/tests/nullness-extra/issue594/Issue594.java +++ b/checker/tests/nullness-extra/issue594/Issue594.java @@ -9,11 +9,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue594 { - private @Nullable T result = null; + private @Nullable T result = null; - // Should return @Nullable T - private T getResult() { - // :: error: (return.type.incompatible) - return result; - } + // Should return @Nullable T + private T getResult() { + // :: error: (return.type.incompatible) + return result; + } } diff --git a/checker/tests/nullness-extra/issue607/Issue607.java b/checker/tests/nullness-extra/issue607/Issue607.java index c441fc5784f..100e38a9f80 100644 --- a/checker/tests/nullness-extra/issue607/Issue607.java +++ b/checker/tests/nullness-extra/issue607/Issue607.java @@ -1,7 +1,7 @@ public class Issue607 extends Issue607SuperClass { - static String simpleString = "a"; + static String simpleString = "a"; - Issue607() { - super(Issue607SuperClass.issue, string -> simpleString); - } + Issue607() { + super(Issue607SuperClass.issue, string -> simpleString); + } } diff --git a/checker/tests/nullness-extra/issue607/Issue607Interface.java b/checker/tests/nullness-extra/issue607/Issue607Interface.java index a83fd7d8a06..4e942a4e40b 100644 --- a/checker/tests/nullness-extra/issue607/Issue607Interface.java +++ b/checker/tests/nullness-extra/issue607/Issue607Interface.java @@ -1,3 +1,3 @@ interface Issue607Interface { - Object method(Object string); + Object method(Object string); } diff --git a/checker/tests/nullness-extra/issue607/Issue607SuperClass.java b/checker/tests/nullness-extra/issue607/Issue607SuperClass.java index f5633fa297b..ec05dd3fd2c 100644 --- a/checker/tests/nullness-extra/issue607/Issue607SuperClass.java +++ b/checker/tests/nullness-extra/issue607/Issue607SuperClass.java @@ -1,5 +1,5 @@ public class Issue607SuperClass { - static Issue607Interface issue; + static Issue607Interface issue; - public Issue607SuperClass(Issue607Interface... issue) {} + public Issue607SuperClass(Issue607Interface... issue) {} } diff --git a/checker/tests/nullness-extra/multiple-errors/C1.java b/checker/tests/nullness-extra/multiple-errors/C1.java index 02e85c78db3..206a1613392 100644 --- a/checker/tests/nullness-extra/multiple-errors/C1.java +++ b/checker/tests/nullness-extra/multiple-errors/C1.java @@ -1,3 +1,3 @@ public class C1 { - Object o; + Object o; } diff --git a/checker/tests/nullness-extra/multiple-errors/C2.java b/checker/tests/nullness-extra/multiple-errors/C2.java index df3b294434e..ab0a5a5b613 100644 --- a/checker/tests/nullness-extra/multiple-errors/C2.java +++ b/checker/tests/nullness-extra/multiple-errors/C2.java @@ -1,3 +1,3 @@ public class C2 { - Object o = null; + Object o = null; } diff --git a/checker/tests/nullness-extra/multiple-errors/C3.java b/checker/tests/nullness-extra/multiple-errors/C3.java index ad56046a0cf..c5cf1a64a72 100644 --- a/checker/tests/nullness-extra/multiple-errors/C3.java +++ b/checker/tests/nullness-extra/multiple-errors/C3.java @@ -1,9 +1,9 @@ public class C3 { - void m() { - class C3b { - void bad(XXX p) { - p.toString(); - } - } + void m() { + class C3b { + void bad(XXX p) { + p.toString(); + } } + } } diff --git a/checker/tests/nullness-extra/multiple-errors/C4.java b/checker/tests/nullness-extra/multiple-errors/C4.java index 7249c80e9d0..b363b305c21 100644 --- a/checker/tests/nullness-extra/multiple-errors/C4.java +++ b/checker/tests/nullness-extra/multiple-errors/C4.java @@ -1,5 +1,5 @@ public class C4 { - void m(@org.checkerframework.checker.nullness.qual.Nullable Object p) { - p.toString(); - } + void m(@org.checkerframework.checker.nullness.qual.Nullable Object p) { + p.toString(); + } } diff --git a/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java b/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java index 87991eae4ff..dd9d005d7eb 100644 --- a/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java +++ b/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java @@ -1,6 +1,6 @@ package test; public class PackageAnnotationTest { - // Allowed because of package annotation. - Object f = null; + // Allowed because of package annotation. + Object f = null; } diff --git a/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java b/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java index d9bc93ab5ad..487d6336f4b 100644 --- a/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java +++ b/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java @@ -3,10 +3,10 @@ import java.util.regex.Pattern; public class NullnessRegexWithErrors { - String str = "(str"; + String str = "(str"; - void context() { - str = null; - Pattern.compile("\\I"); - } + void context() { + str = null; + Pattern.compile("\\I"); + } } diff --git a/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java b/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java index 9d63bb3e53c..da408842152 100644 --- a/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java +++ b/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java @@ -3,6 +3,6 @@ // If GwiParent is read from bytecode, the error occurs. public class GenericWildcardInheritance extends GwiParent { - @Override - public void syntaxError(Recognizer recognizer) {} + @Override + public void syntaxError(Recognizer recognizer) {} } diff --git a/checker/tests/nullness-genericwildcard/Issue511.java b/checker/tests/nullness-genericwildcard/Issue511.java index 48c09c71271..5266ad7947a 100644 --- a/checker/tests/nullness-genericwildcard/Issue511.java +++ b/checker/tests/nullness-genericwildcard/Issue511.java @@ -3,29 +3,29 @@ class MyGeneric {} class MySuperClass { - public void method(MyGeneric x) {} + public void method(MyGeneric x) {} } public class Issue511 extends MySuperClass { - @Override - public void method(MyGeneric x) { - super.method(x); - } + @Override + public void method(MyGeneric x) { + super.method(x); + } - // public void method(MyGeneric x) {} - // On the above method, javac issues the following error: - // Issue511.java:19: error: name clash: method(MyGeneric) in Issue511 and - // method(MyGeneric) in MySuperClass have the same erasure, yet neither - // overrides the other - // public void method(MyGeneric x) {} - // ^ - // 1 error + // public void method(MyGeneric x) {} + // On the above method, javac issues the following error: + // Issue511.java:19: error: name clash: method(MyGeneric) in Issue511 and + // method(MyGeneric) in MySuperClass have the same erasure, yet neither + // overrides the other + // public void method(MyGeneric x) {} + // ^ + // 1 error } class Use { - MyGeneric wildCardExtendsObject = new MyGeneric<>(); - MyGeneric wildCardExtendsNumber = wildCardExtendsObject; - MyGeneric wildCardNoBound = new MyGeneric<>(); - MyGeneric wildCardExtendsNumber2 = wildCardNoBound; + MyGeneric wildCardExtendsObject = new MyGeneric<>(); + MyGeneric wildCardExtendsNumber = wildCardExtendsObject; + MyGeneric wildCardNoBound = new MyGeneric<>(); + MyGeneric wildCardExtendsNumber2 = wildCardNoBound; } diff --git a/checker/tests/nullness-genericwildcardlib/GwiParent.java b/checker/tests/nullness-genericwildcardlib/GwiParent.java index 6fde1b3e7c2..60826b2e71e 100644 --- a/checker/tests/nullness-genericwildcardlib/GwiParent.java +++ b/checker/tests/nullness-genericwildcardlib/GwiParent.java @@ -1,7 +1,7 @@ // Library for issue #511: https://github.com/typetools/checker-framework/issues/511 public abstract class GwiParent { - abstract void syntaxError(Recognizer recognizer); + abstract void syntaxError(Recognizer recognizer); } abstract class ATNSimulator {} diff --git a/checker/tests/nullness-initialization/AssignmentDuringInitialization.java b/checker/tests/nullness-initialization/AssignmentDuringInitialization.java index fc0623c71c6..3f6f53f43fa 100644 --- a/checker/tests/nullness-initialization/AssignmentDuringInitialization.java +++ b/checker/tests/nullness-initialization/AssignmentDuringInitialization.java @@ -1,42 +1,42 @@ // This test covers Issue345 at: // https://github.com/typetools/checker-framework/issues/345 public class AssignmentDuringInitialization { - String f1; - String f2; + String f1; + String f2; - String f3; - String f4; + String f3; + String f4; - String f5; - String f6; + String f5; + String f6; - { - // :: error: (assignment.type.incompatible) - f1 = f2; - f2 = f1; - f2.toString(); // Null pointer exception here - } + { + // :: error: (assignment.type.incompatible) + f1 = f2; + f2 = f1; + f2.toString(); // Null pointer exception here + } - public AssignmentDuringInitialization() { - // :: error: (assignment.type.incompatible) - f3 = f4; - f4 = f3; - f4.toString(); // Null pointer exception here + public AssignmentDuringInitialization() { + // :: error: (assignment.type.incompatible) + f3 = f4; + f4 = f3; + f4.toString(); // Null pointer exception here - f5 = "hello"; - f6 = f5; - } + f5 = "hello"; + f6 = f5; + } - public void goodBehavior() { - // This isn't a constructor or initializer. - // The receiver of this method should already be initialized - // and therefore f1 and f2 should already be initialized. - f5 = f6; - f6 = f5; - f6.toString(); // No exception here - } + public void goodBehavior() { + // This isn't a constructor or initializer. + // The receiver of this method should already be initialized + // and therefore f1 and f2 should already be initialized. + f5 = f6; + f6 = f5; + f6.toString(); // No exception here + } - public static void main(String[] args) { - AssignmentDuringInitialization a = new AssignmentDuringInitialization(); - } + public static void main(String[] args) { + AssignmentDuringInitialization a = new AssignmentDuringInitialization(); + } } diff --git a/checker/tests/nullness-initialization/EisopIssue635.java b/checker/tests/nullness-initialization/EisopIssue635.java index d8416628d57..c50b8872c44 100644 --- a/checker/tests/nullness-initialization/EisopIssue635.java +++ b/checker/tests/nullness-initialization/EisopIssue635.java @@ -2,21 +2,21 @@ class EisopIssue635 { - private @Nullable Runnable r; + private @Nullable Runnable r; - private void f() { - // No crash without this assignment first. - r = null; - r = - new Runnable() { - @Override - public void run() { - if (r != this) { - return; - } - // No crash without this call. - f(); - } - }; - } + private void f() { + // No crash without this assignment first. + r = null; + r = + new Runnable() { + @Override + public void run() { + if (r != this) { + return; + } + // No crash without this call. + f(); + } + }; + } } diff --git a/checker/tests/nullness-initialization/EnumFieldUninit.java b/checker/tests/nullness-initialization/EnumFieldUninit.java index 2b8f2ad9ba9..e70439ce37b 100644 --- a/checker/tests/nullness-initialization/EnumFieldUninit.java +++ b/checker/tests/nullness-initialization/EnumFieldUninit.java @@ -1,23 +1,23 @@ enum EnumFieldUninit { - DUMMY; + DUMMY; - // :: error: (assignment.type.incompatible) - public static String s = null; + // :: error: (assignment.type.incompatible) + public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; + // :: error: (initialization.static.field.uninitialized) + public static String u; - static String[] arrayInit = new String[] {}; + static String[] arrayInit = new String[] {}; - static String[] arrayInitInBlock; + static String[] arrayInitInBlock; - static { - arrayInitInBlock = new String[] {}; - } + static { + arrayInitInBlock = new String[] {}; + } - // :: error: (assignment.type.incompatible) - static String[] arrayInitToNull = null; + // :: error: (assignment.type.incompatible) + static String[] arrayInitToNull = null; - // :: error: (initialization.static.field.uninitialized) - static String[] arrayUninit; + // :: error: (initialization.static.field.uninitialized) + static String[] arrayUninit; } diff --git a/checker/tests/nullness-initialization/FieldInit.java b/checker/tests/nullness-initialization/FieldInit.java index eb1ec8d773d..883fc520b54 100644 --- a/checker/tests/nullness-initialization/FieldInit.java +++ b/checker/tests/nullness-initialization/FieldInit.java @@ -1,12 +1,12 @@ public class FieldInit { - // :: error: (argument.type.incompatible) :: error: (method.invocation.invalid) - String f = init(this); + // :: error: (argument.type.incompatible) :: error: (method.invocation.invalid) + String f = init(this); - String init(FieldInit o) { - return ""; - } + String init(FieldInit o) { + return ""; + } - void test() { - String local = init(this); - } + void test() { + String local = init(this); + } } diff --git a/checker/tests/nullness-initialization/FinalClass.java b/checker/tests/nullness-initialization/FinalClass.java index 6408858942c..38213da7838 100644 --- a/checker/tests/nullness-initialization/FinalClass.java +++ b/checker/tests/nullness-initialization/FinalClass.java @@ -9,37 +9,37 @@ import org.checkerframework.checker.nullness.qual.Nullable; final class EisopIssue610_1 { - @MonotonicNonNull String s; + @MonotonicNonNull String s; - EisopIssue610_1() { - init(); - } + EisopIssue610_1() { + init(); + } - void init() {} + void init() {} } final class EisopIssue610_2 { - @Nullable String s; + @Nullable String s; - EisopIssue610_2() { - init(); - } + EisopIssue610_2() { + init(); + } - void init() {} + void init() {} } final class EisopIssue610_3 { - @MonotonicNonNull String s; + @MonotonicNonNull String s; - EisopIssue610_3() { - @Initialized EisopIssue610_3 other = this; - } + EisopIssue610_3() { + @Initialized EisopIssue610_3 other = this; + } } final class EisopIssue610_4 { - @Nullable String s; + @Nullable String s; - EisopIssue610_4() { - @Initialized EisopIssue610_4 other = this; - } + EisopIssue610_4() { + @Initialized EisopIssue610_4 other = this; + } } diff --git a/checker/tests/nullness-initialization/FinalClassLambda.java b/checker/tests/nullness-initialization/FinalClassLambda.java index f5f6afc4cd3..5a0bf83fe65 100644 --- a/checker/tests/nullness-initialization/FinalClassLambda.java +++ b/checker/tests/nullness-initialization/FinalClassLambda.java @@ -5,97 +5,97 @@ import org.checkerframework.checker.nullness.qual.Nullable; final class FinalClassLambda1 { - @Nullable String s; + @Nullable String s; - FinalClassLambda1() { - use(this::init); - } + FinalClassLambda1() { + use(this::init); + } - void init() {} + void init() {} - static void use(Runnable r) {} + static void use(Runnable r) {} } final class FinalClassLambda2 extends FinalClassLambda2Base { - @Nullable String s; - - FinalClassLambda2() { - use(() -> init()); - use( - new Runnable() { - @Override - public void run() { - init(); - } - }); - } - - void init() {} + @Nullable String s; + + FinalClassLambda2() { + use(() -> init()); + use( + new Runnable() { + @Override + public void run() { + init(); + } + }); + } + + void init() {} } class FinalClassLambda2Base { - void use(Runnable r) {} + void use(Runnable r) {} } final class FinalClassLambda3 { - String s; + String s; - FinalClassLambda3() { - s = "hello"; - use(this::init); - } + FinalClassLambda3() { + s = "hello"; + use(this::init); + } - void init() {} + void init() {} - static void use(Runnable r) {} + static void use(Runnable r) {} } final class FinalClassLambda4 extends FinalClassLambda2Base { - String s; - - FinalClassLambda4() { - s = "world"; - use(() -> init()); - use( - new Runnable() { - @Override - public void run() { - init(); - } - }); - } - - void init() {} + String s; + + FinalClassLambda4() { + s = "world"; + use(() -> init()); + use( + new Runnable() { + @Override + public void run() { + init(); + } + }); + } + + void init() {} } // Not a final class, but uses same name for consistency. class FinalClassLambda5 extends FinalClassLambda2Base { - String s; - - FinalClassLambda5() { - s = "hello"; - // :: error: (method.invocation.invalid) - use( - // :: error: (methodref.receiver.bound.invalid) - this::init); - } - - FinalClassLambda5(int dummy) { - s = "world"; - // :: error: (method.invocation.invalid) - use( - // :: error: (method.invocation.invalid) - () -> init()); + String s; + + FinalClassLambda5() { + s = "hello"; + // :: error: (method.invocation.invalid) + use( + // :: error: (methodref.receiver.bound.invalid) + this::init); + } + + FinalClassLambda5(int dummy) { + s = "world"; + // :: error: (method.invocation.invalid) + use( // :: error: (method.invocation.invalid) - use( - new Runnable() { - @Override - public void run() { - // :: error: (method.invocation.invalid) - init(); - } - }); - } - - void init() {} + () -> init()); + // :: error: (method.invocation.invalid) + use( + new Runnable() { + @Override + public void run() { + // :: error: (method.invocation.invalid) + init(); + } + }); + } + + void init() {} } diff --git a/checker/tests/nullness-initialization/FlowConstructor.java b/checker/tests/nullness-initialization/FlowConstructor.java index dbaea5c0fef..7b70e333c3f 100644 --- a/checker/tests/nullness-initialization/FlowConstructor.java +++ b/checker/tests/nullness-initialization/FlowConstructor.java @@ -3,36 +3,36 @@ public class FlowConstructor { - String a; - String b; + String a; + String b; - public FlowConstructor(float f) { - a = "m"; - b = "n"; - semiRawMethod(); - } + public FlowConstructor(float f) { + a = "m"; + b = "n"; + semiRawMethod(); + } - public FlowConstructor(int p) { - a = "m"; - b = "n"; - // :: error: (method.invocation.invalid) - nonRawMethod(); - } + public FlowConstructor(int p) { + a = "m"; + b = "n"; + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - public FlowConstructor(double p) { - a = "m"; - // :: error: (method.invocation.invalid) - nonRawMethod(); // error - b = "n"; - } + public FlowConstructor(double p) { + a = "m"; + // :: error: (method.invocation.invalid) + nonRawMethod(); // error + b = "n"; + } - void nonRawMethod() { - a.toString(); - b.toString(); - } + void nonRawMethod() { + a.toString(); + b.toString(); + } - void semiRawMethod(@UnderInitialization(FlowConstructor.class) FlowConstructor this) { - a.toString(); - b.toString(); - } + void semiRawMethod(@UnderInitialization(FlowConstructor.class) FlowConstructor this) { + a.toString(); + b.toString(); + } } diff --git a/checker/tests/nullness-initialization/FlowConstructor2.java b/checker/tests/nullness-initialization/FlowConstructor2.java index 9cd9771d370..06b8409cb8c 100644 --- a/checker/tests/nullness-initialization/FlowConstructor2.java +++ b/checker/tests/nullness-initialization/FlowConstructor2.java @@ -1,8 +1,8 @@ public class FlowConstructor2 { - String f; + String f; - public FlowConstructor2() { - // :: error: (dereference.of.nullable) - f.hashCode(); - } + public FlowConstructor2() { + // :: error: (dereference.of.nullable) + f.hashCode(); + } } diff --git a/checker/tests/nullness-initialization/FlowInitialization.java b/checker/tests/nullness-initialization/FlowInitialization.java index bfcfa09da42..df55a573a9f 100644 --- a/checker/tests/nullness-initialization/FlowInitialization.java +++ b/checker/tests/nullness-initialization/FlowInitialization.java @@ -4,51 +4,51 @@ public class FlowInitialization { - @NonNull String f; - @Nullable String g; + @NonNull String f; + @Nullable String g; - // :: error: (initialization.fields.uninitialized) - public FlowInitialization() {} + // :: error: (initialization.fields.uninitialized) + public FlowInitialization() {} - public FlowInitialization(long l) { - g = ""; - f = g; - } + public FlowInitialization(long l) { + g = ""; + f = g; + } - // :: error: (initialization.fields.uninitialized) - public FlowInitialization(boolean b) { - if (b) { - f = ""; - } + // :: error: (initialization.fields.uninitialized) + public FlowInitialization(boolean b) { + if (b) { + f = ""; } + } - // :: error: (initialization.fields.uninitialized) - public FlowInitialization(int i) { - if (i == 0) { - throw new RuntimeException(); - } + // :: error: (initialization.fields.uninitialized) + public FlowInitialization(int i) { + if (i == 0) { + throw new RuntimeException(); } + } - // :: error: (initialization.fields.uninitialized) - public FlowInitialization(char c) { - if (c == 'c') { - return; - } - f = ""; + // :: error: (initialization.fields.uninitialized) + public FlowInitialization(char c) { + if (c == 'c') { + return; } + f = ""; + } - public FlowInitialization(double d) { - setField(); - } + public FlowInitialization(double d) { + setField(); + } - @EnsuresQualifier(expression = "f", qualifier = NonNull.class) - public void setField(@UnknownInitialization FlowInitialization this) { - f = ""; - } + @EnsuresQualifier(expression = "f", qualifier = NonNull.class) + public void setField(@UnknownInitialization FlowInitialization this) { + f = ""; + } } class FlowPrimitives { - boolean b; - int t; - char c; + boolean b; + int t; + char c; } diff --git a/checker/tests/nullness-initialization/Initializer.java b/checker/tests/nullness-initialization/Initializer.java index 7a08abd1d95..6909e0d9277 100644 --- a/checker/tests/nullness-initialization/Initializer.java +++ b/checker/tests/nullness-initialization/Initializer.java @@ -5,92 +5,92 @@ public class Initializer { - public String a; - public String b = "abc"; + public String a; + public String b = "abc"; - // :: error: (assignment.type.incompatible) - public String c = null; + // :: error: (assignment.type.incompatible) + public String c = null; - public String d = (""); + public String d = (""); - // :: error: (initialization.fields.uninitialized) - public Initializer() { - // :: error: (assignment.type.incompatible) - a = null; - a = ""; - c = ""; + // :: error: (initialization.fields.uninitialized) + public Initializer() { + // :: error: (assignment.type.incompatible) + a = null; + a = ""; + c = ""; + } + + // :: error: (initialization.fields.uninitialized) + public Initializer(boolean foo) {} + + public Initializer(int foo) { + a = ""; + c = ""; + f = ""; + } + + public Initializer(float foo) { + setField(); + c = ""; + f = ""; + } + + public Initializer(double foo) { + if (!setFieldMaybe()) { + a = ""; } - - // :: error: (initialization.fields.uninitialized) - public Initializer(boolean foo) {} - - public Initializer(int foo) { - a = ""; - c = ""; - f = ""; + c = ""; + f = ""; + } + + // :: error: (initialization.fields.uninitialized) + public Initializer(double foo, boolean t) { + if (!setFieldMaybe()) { + // on this path, 'a' is not initialized } + c = ""; + } - public Initializer(float foo) { - setField(); - c = ""; - f = ""; - } + @EnsuresQualifier(expression = "a", qualifier = NonNull.class) + public void setField(@UnknownInitialization Initializer this) { + a = ""; + } - public Initializer(double foo) { - if (!setFieldMaybe()) { - a = ""; - } - c = ""; - f = ""; - } + @EnsuresQualifierIf(result = true, expression = "a", qualifier = NonNull.class) + public boolean setFieldMaybe(@UnknownInitialization Initializer this) { + a = ""; + return true; + } - // :: error: (initialization.fields.uninitialized) - public Initializer(double foo, boolean t) { - if (!setFieldMaybe()) { - // on this path, 'a' is not initialized - } - c = ""; - } + String f; - @EnsuresQualifier(expression = "a", qualifier = NonNull.class) - public void setField(@UnknownInitialization Initializer this) { - a = ""; - } + void t1(@UnknownInitialization Initializer this) { + // :: error: (dereference.of.nullable) + this.f.toString(); + } - @EnsuresQualifierIf(result = true, expression = "a", qualifier = NonNull.class) - public boolean setFieldMaybe(@UnknownInitialization Initializer this) { - a = ""; - return true; - } - - String f; - - void t1(@UnknownInitialization Initializer this) { - // :: error: (dereference.of.nullable) - this.f.toString(); - } - - String fieldF = ""; + String fieldF = ""; } class SubInitializer extends Initializer { - // :: error: (initialization.field.uninitialized) - String f; - - void subt1(@UnknownInitialization(Initializer.class) SubInitializer this) { - fieldF.toString(); - super.f.toString(); - // :: error: (dereference.of.nullable) - this.f.toString(); - } - - void subt2(@UnknownInitialization SubInitializer this) { - // :: error: (dereference.of.nullable) - fieldF.toString(); - // :: error: (dereference.of.nullable) - super.f.toString(); - // :: error: (dereference.of.nullable) - this.f.toString(); - } + // :: error: (initialization.field.uninitialized) + String f; + + void subt1(@UnknownInitialization(Initializer.class) SubInitializer this) { + fieldF.toString(); + super.f.toString(); + // :: error: (dereference.of.nullable) + this.f.toString(); + } + + void subt2(@UnknownInitialization SubInitializer this) { + // :: error: (dereference.of.nullable) + fieldF.toString(); + // :: error: (dereference.of.nullable) + super.f.toString(); + // :: error: (dereference.of.nullable) + this.f.toString(); + } } diff --git a/checker/tests/nullness-initialization/Issue1096.java b/checker/tests/nullness-initialization/Issue1096.java index 70b354fd650..dda21ecd1d7 100644 --- a/checker/tests/nullness-initialization/Issue1096.java +++ b/checker/tests/nullness-initialization/Issue1096.java @@ -6,58 +6,58 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; class PreCond { - Object f; - @Nullable Object g; - - PreCond() { - f = new Object(); - early(); - g = new Object(); - doNullable(); - } - - void earlyBad(@UnknownInitialization PreCond this) { - // :: error: (dereference.of.nullable) - f.toString(); - } - - @RequiresNonNull("this.f") - void early(@UnknownInitialization PreCond this) { - f.toString(); - } - - @RequiresNonNull("this.g") - void doNullable(@UnknownInitialization PreCond this) { - g.toString(); - } - - void foo(@UnknownInitialization PreCond this) { - // The receiver is not fully initialized, so raise an error. - // :: error: (contracts.precondition.not.satisfied) - early(); - } - - void bar() { - // The receiver is initialized, so non-null field f is definitely non-null. - early(); - // Nullable fields stay nullable - // :: error: (contracts.precondition.not.satisfied) - doNullable(); - } + Object f; + @Nullable Object g; + + PreCond() { + f = new Object(); + early(); + g = new Object(); + doNullable(); + } + + void earlyBad(@UnknownInitialization PreCond this) { + // :: error: (dereference.of.nullable) + f.toString(); + } + + @RequiresNonNull("this.f") + void early(@UnknownInitialization PreCond this) { + f.toString(); + } + + @RequiresNonNull("this.g") + void doNullable(@UnknownInitialization PreCond this) { + g.toString(); + } + + void foo(@UnknownInitialization PreCond this) { + // The receiver is not fully initialized, so raise an error. + // :: error: (contracts.precondition.not.satisfied) + early(); + } + + void bar() { + // The receiver is initialized, so non-null field f is definitely non-null. + early(); + // Nullable fields stay nullable + // :: error: (contracts.precondition.not.satisfied) + doNullable(); + } } class User { - void foo(@UnknownInitialization PreCond pc) { - // The receiver is not fully initialized, so raise an error. - // :: error: (contracts.precondition.not.satisfied) - pc.early(); - } - - void bar(PreCond pc) { - // The receiver is initialized, so non-null field f is definitely non-null. - pc.early(); - // Nullable fields stay nullable - // :: error: (contracts.precondition.not.satisfied) - pc.doNullable(); - } + void foo(@UnknownInitialization PreCond pc) { + // The receiver is not fully initialized, so raise an error. + // :: error: (contracts.precondition.not.satisfied) + pc.early(); + } + + void bar(PreCond pc) { + // The receiver is initialized, so non-null field f is definitely non-null. + pc.early(); + // Nullable fields stay nullable + // :: error: (contracts.precondition.not.satisfied) + pc.doNullable(); + } } diff --git a/checker/tests/nullness-initialization/Issue1590.java b/checker/tests/nullness-initialization/Issue1590.java index 326db23408f..e9c492474d5 100644 --- a/checker/tests/nullness-initialization/Issue1590.java +++ b/checker/tests/nullness-initialization/Issue1590.java @@ -1,16 +1,16 @@ @SuppressWarnings("initialization") public class Issue1590 { - private String a; + private String a; - public Issue1590() { - // valid because of suppressed warnings - init(); - // :: error: (dereference.of.nullable) - a.length(); - } + public Issue1590() { + // valid because of suppressed warnings + init(); + // :: error: (dereference.of.nullable) + a.length(); + } - public void init() { - a = "gude"; - } + public void init() { + a = "gude"; + } } diff --git a/checker/tests/nullness-initialization/Issue1590a.java b/checker/tests/nullness-initialization/Issue1590a.java index f4de70f5f5a..28dad73bbb3 100644 --- a/checker/tests/nullness-initialization/Issue1590a.java +++ b/checker/tests/nullness-initialization/Issue1590a.java @@ -2,15 +2,15 @@ @SuppressWarnings("initialization.") public class Issue1590a { - private String a; + private String a; - // :: error: (initialization.fields.uninitialized) - public Issue1590a() { - // :: error: (method.invocation.invalid) - init(); - } + // :: error: (initialization.fields.uninitialized) + public Issue1590a() { + // :: error: (method.invocation.invalid) + init(); + } - public void init() { - a = "gude"; - } + public void init() { + a = "gude"; + } } diff --git a/checker/tests/nullness-initialization/Issue261.java b/checker/tests/nullness-initialization/Issue261.java index 34630eafeae..f247cf76fa9 100644 --- a/checker/tests/nullness-initialization/Issue261.java +++ b/checker/tests/nullness-initialization/Issue261.java @@ -1,18 +1,18 @@ // Test case for Issue 261 // https://github.com/typetools/checker-framework/issues/261 public class Issue261 { - boolean b; + boolean b; - class Flag { - // :: error: (initialization.field.uninitialized) - T value; - } + class Flag { + // :: error: (initialization.field.uninitialized) + T value; + } - static T getValue(Flag flag) { - return flag.value; - } + static T getValue(Flag flag) { + return flag.value; + } - Issue261(Flag flag) { - this.b = getValue(flag); - } + Issue261(Flag flag) { + this.b = getValue(flag); + } } diff --git a/checker/tests/nullness-initialization/Issue345.java b/checker/tests/nullness-initialization/Issue345.java index 235b64880c0..ea8f94775e4 100644 --- a/checker/tests/nullness-initialization/Issue345.java +++ b/checker/tests/nullness-initialization/Issue345.java @@ -1,17 +1,17 @@ // This is a test case for Issue 345: // https://github.com/typetools/checker-framework/issues/345 public class Issue345 { - String f1; - String f2; + String f1; + String f2; - { - // :: error: (assignment.type.incompatible) - f1 = f2; - f2 = f1; - f2.toString(); // Null pointer exception here - } + { + // :: error: (assignment.type.incompatible) + f1 = f2; + f2 = f1; + f2.toString(); // Null pointer exception here + } - public static void main(String[] args) { - Issue345 a = new Issue345(); - } + public static void main(String[] args) { + Issue345 a = new Issue345(); + } } diff --git a/checker/tests/nullness-initialization/Issue354.java b/checker/tests/nullness-initialization/Issue354.java index f0047696739..cdeae0ea606 100644 --- a/checker/tests/nullness-initialization/Issue354.java +++ b/checker/tests/nullness-initialization/Issue354.java @@ -1,19 +1,19 @@ public class Issue354 { - String a; + String a; - { - Object o = - new Object() { - @Override - public String toString() { - // :: error: (dereference.of.nullable) - return a.toString(); - } - }.toString(); + { + Object o = + new Object() { + @Override + public String toString() { + // :: error: (dereference.of.nullable) + return a.toString(); + } + }.toString(); - // This is needed to avoid the initialization.fields.uninitialized warning. - // The NPE still occurs - a = ""; - } + // This is needed to avoid the initialization.fields.uninitialized warning. + // The NPE still occurs + a = ""; + } } diff --git a/checker/tests/nullness-initialization/Issue400.java b/checker/tests/nullness-initialization/Issue400.java index a66051580d1..460bd494cea 100644 --- a/checker/tests/nullness-initialization/Issue400.java +++ b/checker/tests/nullness-initialization/Issue400.java @@ -2,18 +2,18 @@ import java.util.Collection; public class Issue400 { - final class YYPair { - // :: error: (initialization.field.uninitialized) - T first; - // :: error: (initialization.field.uninitialized) - V second; - } + final class YYPair { + // :: error: (initialization.field.uninitialized) + T first; + // :: error: (initialization.field.uninitialized) + V second; + } - class YY { - public Collection> getX() { - final Collection> out = new ArrayList>(); - out.add(new YYPair()); - return out; - } + class YY { + public Collection> getX() { + final Collection> out = new ArrayList>(); + out.add(new YYPair()); + return out; } + } } diff --git a/checker/tests/nullness-initialization/Issue408.java b/checker/tests/nullness-initialization/Issue408.java index bec43dddbbe..b3229690a56 100644 --- a/checker/tests/nullness-initialization/Issue408.java +++ b/checker/tests/nullness-initialization/Issue408.java @@ -4,27 +4,27 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class Issue408 { - static class Bar { - Bar() { - doIssue408(); - } + static class Bar { + Bar() { + doIssue408(); + } - String doIssue408(@UnderInitialization Bar this) { - return ""; - } + String doIssue408(@UnderInitialization Bar this) { + return ""; } + } - static class Baz extends Bar { - String myString = "hello"; + static class Baz extends Bar { + String myString = "hello"; - @Override - String doIssue408(@UnderInitialization Baz this) { - // :: error: (dereference.of.nullable) - return myString.toLowerCase(); - } + @Override + String doIssue408(@UnderInitialization Baz this) { + // :: error: (dereference.of.nullable) + return myString.toLowerCase(); } + } - public static void main(String[] args) { - new Baz(); - } + public static void main(String[] args) { + new Baz(); + } } diff --git a/checker/tests/nullness-initialization/KeyForValidation.java b/checker/tests/nullness-initialization/KeyForValidation.java index 41d74c24e3b..12102addaed 100644 --- a/checker/tests/nullness-initialization/KeyForValidation.java +++ b/checker/tests/nullness-initialization/KeyForValidation.java @@ -1,142 +1,141 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; public class KeyForValidation { - // :: error: (expression.unparsable.type.invalid) - // :: error: (initialization.static.field.uninitialized) - static @KeyFor("this") Object f; + // :: error: (expression.unparsable.type.invalid) + // :: error: (initialization.static.field.uninitialized) + static @KeyFor("this") Object f; - // :: error: (initialization.field.uninitialized) - @KeyFor("this") Object g; + // :: error: (initialization.field.uninitialized) + @KeyFor("this") Object g; - // :: error: (expression.unparsable.type.invalid) - void m(@KeyFor("#0") Object p) {} + // :: error: (expression.unparsable.type.invalid) + void m(@KeyFor("#0") Object p) {} - // :: error: (expression.unparsable.type.invalid) - void m2(@KeyFor("#4") Object p) {} + // :: error: (expression.unparsable.type.invalid) + void m2(@KeyFor("#4") Object p) {} + + // OK + void m3(@KeyFor("#2") Object p, Map m) {} - // OK - void m3(@KeyFor("#2") Object p, Map m) {} + // TODO: index for a non-map + void m4(@KeyFor("#1") Object p, Map m) {} - // TODO: index for a non-map - void m4(@KeyFor("#1") Object p, Map m) {} + // TODO: index with wrong type + void m4(@KeyFor("#2") String p, Map m) {} - // TODO: index with wrong type - void m4(@KeyFor("#2") String p, Map m) {} + // :: error: (expression.unparsable.type.invalid) + // :: error: (initialization.field.uninitialized) + @KeyFor("INVALID") Object h; + // :: error: (initialization.field.uninitialized) + @KeyFor("f") Object i; + + void foo(Object p) { // :: error: (expression.unparsable.type.invalid) - // :: error: (initialization.field.uninitialized) - @KeyFor("INVALID") Object h; + @KeyFor("ALSOBAD") Object j; - // :: error: (initialization.field.uninitialized) - @KeyFor("f") Object i; + @KeyFor("j") Object k; + @KeyFor("f") Object l; - void foo(Object p) { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("ALSOBAD") Object j; + @KeyFor("p") Object o; + } - @KeyFor("j") Object k; - @KeyFor("f") Object l; + // :: error: (expression.unparsable.type.invalid) + void foo2(@KeyFor("ALSOBAD") Object o) {} - @KeyFor("p") Object o; - } + // :: error: (expression.unparsable.type.invalid) + void foo3(@KeyFor("ALSOBAD") Object[] o) {} - // :: error: (expression.unparsable.type.invalid) - void foo2(@KeyFor("ALSOBAD") Object o) {} + // :: error: (expression.unparsable.type.invalid) + void foo4(Map<@KeyFor("ALSOBAD") Object, Object> o) {} - // :: error: (expression.unparsable.type.invalid) - void foo3(@KeyFor("ALSOBAD") Object[] o) {} + // :: error: (expression.unparsable.type.invalid) + @KeyFor("ALSOBAD") Object[] foo5() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - void foo4(Map<@KeyFor("ALSOBAD") Object, Object> o) {} + // :: error: (expression.unparsable.type.invalid) + @KeyFor("ALSOBAD") Object foo6() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - @KeyFor("ALSOBAD") Object[] foo5() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + Map<@KeyFor("ALSOBAD") Object, Object> foo7() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - @KeyFor("ALSOBAD") Object foo6() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + <@KeyFor("ALSOBAD") T> void foo8() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - Map<@KeyFor("ALSOBAD") Object, Object> foo7() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + <@KeyForBottom T extends @KeyFor("ALSOBAD") Object> void foo9() {} - // :: error: (expression.unparsable.type.invalid) - <@KeyFor("ALSOBAD") T> void foo8() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + void foo10(@KeyFor("ALSOBAD") KeyForValidation this) {} + // :: error: (expression.unparsable.type.invalid) + public void test(Set<@KeyFor("BAD") String> keySet) { // :: error: (expression.unparsable.type.invalid) - <@KeyForBottom T extends @KeyFor("ALSOBAD") Object> void foo9() {} - + new ArrayList<@KeyFor("BAD") String>(keySet); // :: error: (expression.unparsable.type.invalid) - void foo10(@KeyFor("ALSOBAD") KeyForValidation this) {} + List<@KeyFor("BAD") String> list = new ArrayList<>(); // :: error: (expression.unparsable.type.invalid) - public void test(Set<@KeyFor("BAD") String> keySet) { - // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("BAD") String>(keySet); - // :: error: (expression.unparsable.type.invalid) - List<@KeyFor("BAD") String> list = new ArrayList<>(); + for (@KeyFor("BAD") String s : list) {} + } - // :: error: (expression.unparsable.type.invalid) - for (@KeyFor("BAD") String s : list) {} - } + // Test static context. - // Test static context. + Object instanceField = new Object(); - Object instanceField = new Object(); + // :: error: (expression.unparsable.type.invalid) + static void bar2(@KeyFor("this.instanceField") Object o) {} - // :: error: (expression.unparsable.type.invalid) - static void bar2(@KeyFor("this.instanceField") Object o) {} + // :: error: (expression.unparsable.type.invalid) + static void bar3(@KeyFor("this.instanceField") Object[] o) {} - // :: error: (expression.unparsable.type.invalid) - static void bar3(@KeyFor("this.instanceField") Object[] o) {} + // :: error: (expression.unparsable.type.invalid) + static void bar4(Map<@KeyFor("this.instanceField") Object, Object> o) {} - // :: error: (expression.unparsable.type.invalid) - static void bar4(Map<@KeyFor("this.instanceField") Object, Object> o) {} + // :: error: (expression.unparsable.type.invalid) + static @KeyFor("this.instanceField") Object[] bar5() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - static @KeyFor("this.instanceField") Object[] bar5() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static @KeyFor("this.instanceField") Object bar6() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - static @KeyFor("this.instanceField") Object bar6() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static Map<@KeyFor("this.instanceField") Object, Object> bar7() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - static Map<@KeyFor("this.instanceField") Object, Object> bar7() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static <@KeyFor("this.instanceField") T> void bar8() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - static <@KeyFor("this.instanceField") T> void bar8() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static <@KeyForBottom T extends @KeyFor("this.instanceField") Object> void bar9() {} + // :: error: (expression.unparsable.type.invalid) + public static void test2(Set<@KeyFor("this.instanceField") String> keySet) { // :: error: (expression.unparsable.type.invalid) - static <@KeyForBottom T extends @KeyFor("this.instanceField") Object> void bar9() {} - + new ArrayList<@KeyFor("this.instanceField") String>(keySet); // :: error: (expression.unparsable.type.invalid) - public static void test2(Set<@KeyFor("this.instanceField") String> keySet) { - // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("this.instanceField") String>(keySet); - // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("this.instanceField") String>(); - - List list = new ArrayList<>(); - // :: error: (enhancedfor.type.incompatible) :: error: (expression.unparsable.type.invalid) - for (@KeyFor("this.instanceField") String s : list) {} - } + new ArrayList<@KeyFor("this.instanceField") String>(); + + List list = new ArrayList<>(); + // :: error: (enhancedfor.type.incompatible) :: error: (expression.unparsable.type.invalid) + for (@KeyFor("this.instanceField") String s : list) {} + } } diff --git a/checker/tests/nullness-initialization/Listener.java b/checker/tests/nullness-initialization/Listener.java index a83ee5cd2f0..01eb9a20650 100644 --- a/checker/tests/nullness-initialization/Listener.java +++ b/checker/tests/nullness-initialization/Listener.java @@ -2,27 +2,27 @@ public class Listener { - @NonNull String f; + @NonNull String f; - public Listener() { - Talker w = new Talker(); - // :: error: (argument.type.incompatible) - w.register(this); + public Listener() { + Talker w = new Talker(); + // :: error: (argument.type.incompatible) + w.register(this); - f = "abc"; - } + f = "abc"; + } - public void callback() { - System.out.println(f.toLowerCase()); - } + public void callback() { + System.out.println(f.toLowerCase()); + } - public static class Talker { - public void register(Listener s) { - s.callback(); - } + public static class Talker { + public void register(Listener s) { + s.callback(); } + } - public static void main(String[] args) { - new Listener(); - } + public static void main(String[] args) { + new Listener(); + } } diff --git a/checker/tests/nullness-initialization/MethodInvocation.java b/checker/tests/nullness-initialization/MethodInvocation.java index 5625f95e9ca..3127d1a837b 100644 --- a/checker/tests/nullness-initialization/MethodInvocation.java +++ b/checker/tests/nullness-initialization/MethodInvocation.java @@ -3,31 +3,31 @@ public class MethodInvocation { - String s; + String s; - public MethodInvocation() { - // :: error: (method.invocation.invalid) - a(); - b(); - c(); - s = "abc"; - } + public MethodInvocation() { + // :: error: (method.invocation.invalid) + a(); + b(); + c(); + s = "abc"; + } - public MethodInvocation(boolean p) { - // :: error: (method.invocation.invalid) - a(); // still not okay to be initialized - s = "abc"; - } + public MethodInvocation(boolean p) { + // :: error: (method.invocation.invalid) + a(); // still not okay to be initialized + s = "abc"; + } - public void a() {} + public void a() {} - public void b(@UnderInitialization MethodInvocation this) { - // :: error: (dereference.of.nullable) - s.hashCode(); - } + public void b(@UnderInitialization MethodInvocation this) { + // :: error: (dereference.of.nullable) + s.hashCode(); + } - public void c(@UnknownInitialization MethodInvocation this) { - // :: error: (dereference.of.nullable) - s.hashCode(); - } + public void c(@UnknownInitialization MethodInvocation this) { + // :: error: (dereference.of.nullable) + s.hashCode(); + } } diff --git a/checker/tests/nullness-initialization/MultiConstructorInit.java b/checker/tests/nullness-initialization/MultiConstructorInit.java index 782a476533b..a0091a702e7 100644 --- a/checker/tests/nullness-initialization/MultiConstructorInit.java +++ b/checker/tests/nullness-initialization/MultiConstructorInit.java @@ -3,25 +3,25 @@ public class MultiConstructorInit { - String a; + String a; - public MultiConstructorInit(boolean t) { - a = ""; - } + public MultiConstructorInit(boolean t) { + a = ""; + } - public MultiConstructorInit() { - this(true); - } + public MultiConstructorInit() { + this(true); + } - // :: error: (initialization.fields.uninitialized) - public MultiConstructorInit(int t) { - new MultiConstructorInit(); - } + // :: error: (initialization.fields.uninitialized) + public MultiConstructorInit(int t) { + new MultiConstructorInit(); + } - // :: error: (initialization.fields.uninitialized) - public MultiConstructorInit(float t) {} + // :: error: (initialization.fields.uninitialized) + public MultiConstructorInit(float t) {} - public static void main(String[] args) { - new MultiConstructorInit(); - } + public static void main(String[] args) { + new MultiConstructorInit(); + } } diff --git a/checker/tests/nullness-initialization/ObjectArrayParam.java b/checker/tests/nullness-initialization/ObjectArrayParam.java index cf70acde51d..c7ed7f1d911 100644 --- a/checker/tests/nullness-initialization/ObjectArrayParam.java +++ b/checker/tests/nullness-initialization/ObjectArrayParam.java @@ -2,11 +2,11 @@ import org.checkerframework.checker.nullness.qual.*; class ObjectArrayParam { - void test(@UnknownInitialization Object... args) { - for (Object obj : args) { - boolean isClass = obj instanceof Class; - // :: warning: (cast.unsafe) - @Initialized @NonNull Class clazz = (isClass ? (@Initialized @NonNull Class) obj : obj.getClass()); - } + void test(@UnknownInitialization Object... args) { + for (Object obj : args) { + boolean isClass = obj instanceof Class; + // :: warning: (cast.unsafe) + @Initialized @NonNull Class clazz = (isClass ? (@Initialized @NonNull Class) obj : obj.getClass()); } + } } diff --git a/checker/tests/nullness-initialization/ObjectListParam.java b/checker/tests/nullness-initialization/ObjectListParam.java index c2c28d0ad7e..d711c9fb6de 100644 --- a/checker/tests/nullness-initialization/ObjectListParam.java +++ b/checker/tests/nullness-initialization/ObjectListParam.java @@ -1,15 +1,14 @@ +import java.util.List; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; -import java.util.List; - class ObjectListParam { - // :: error: type.argument.type.incompatible - void test(List<@UnknownInitialization Object> args) { - for (Object obj : args) { - boolean isClass = obj instanceof Class; - // :: warning: (cast.unsafe) - @Initialized Class clazz = (isClass ? (@Initialized Class) obj : obj.getClass()); - } + // :: error: type.argument.type.incompatible + void test(List<@UnknownInitialization Object> args) { + for (Object obj : args) { + boolean isClass = obj instanceof Class; + // :: warning: (cast.unsafe) + @Initialized Class clazz = (isClass ? (@Initialized Class) obj : obj.getClass()); } + } } diff --git a/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java b/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java index 270e8a87c89..9611dd3a7cd 100644 --- a/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java +++ b/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java @@ -2,17 +2,17 @@ public class PrivateMethodUnknownInit { - int x; + int x; - public PrivateMethodUnknownInit() { - x = 1; - m1(); - // :: error: (method.invocation.invalid) - m2(); - } + public PrivateMethodUnknownInit() { + x = 1; + m1(); + // :: error: (method.invocation.invalid) + m2(); + } - private void m1( - @UnknownInitialization(PrivateMethodUnknownInit.class) PrivateMethodUnknownInit this) {} + private void m1( + @UnknownInitialization(PrivateMethodUnknownInit.class) PrivateMethodUnknownInit this) {} - public void m2() {} + public void m2() {} } diff --git a/checker/tests/nullness-initialization/Raw2.java b/checker/tests/nullness-initialization/Raw2.java index f9b46115c77..00fb53439df 100644 --- a/checker/tests/nullness-initialization/Raw2.java +++ b/checker/tests/nullness-initialization/Raw2.java @@ -2,30 +2,30 @@ import org.checkerframework.checker.nullness.qual.*; public class Raw2 { - private @NonNull Object field; + private @NonNull Object field; - // :: error: (initialization.fields.uninitialized) - public Raw2(int i) { - this.method(this); - } + // :: error: (initialization.fields.uninitialized) + public Raw2(int i) { + this.method(this); + } - public Raw2() { - try { - this.method(this); - } catch (NullPointerException e) { - e.printStackTrace(); - } - field = 0L; + public Raw2() { + try { + this.method(this); + } catch (NullPointerException e) { + e.printStackTrace(); } + field = 0L; + } - private void method(@UnknownInitialization Raw2 this, @UnknownInitialization Raw2 arg) { - // :: error: (dereference.of.nullable) - arg.field.hashCode(); - // :: error: (dereference.of.nullable) - this.field.hashCode(); - } + private void method(@UnknownInitialization Raw2 this, @UnknownInitialization Raw2 arg) { + // :: error: (dereference.of.nullable) + arg.field.hashCode(); + // :: error: (dereference.of.nullable) + this.field.hashCode(); + } - public static void test() { - new Raw2(); - } + public static void test() { + new Raw2(); + } } diff --git a/checker/tests/nullness-initialization/RawField.java b/checker/tests/nullness-initialization/RawField.java index e6be359f3d3..f11ff1fcb44 100644 --- a/checker/tests/nullness-initialization/RawField.java +++ b/checker/tests/nullness-initialization/RawField.java @@ -5,49 +5,49 @@ // see https://github.com/typetools/checker-framework/issues/223 class RawField { - public @UnknownInitialization RawField a; + public @UnknownInitialization RawField a; - public RawField() { - // :: error: (assignment.type.incompatible) - a = null; - this.a = this; - a = this; - } + public RawField() { + // :: error: (assignment.type.incompatible) + a = null; + this.a = this; + a = this; + } - // :: error: (initialization.fields.uninitialized) - public RawField(boolean foo) {} + // :: error: (initialization.fields.uninitialized) + public RawField(boolean foo) {} - void t1() { - // :: error: (method.invocation.invalid) - a.t1(); - } + void t1() { + // :: error: (method.invocation.invalid) + a.t1(); + } - void t2(@UnknownInitialization RawField a) { - this.a = a; - } + void t2(@UnknownInitialization RawField a) { + this.a = a; + } } class Options { - @UnknownInitialization Object arg; + @UnknownInitialization Object arg; - public Options(@UnknownInitialization Object arg) { - this.arg = arg; - } + public Options(@UnknownInitialization Object arg) { + this.arg = arg; + } - public void parse_or_usage() { - // use arg only under the assumption that it is @UnknownInitialization - } + public void parse_or_usage() { + // use arg only under the assumption that it is @UnknownInitialization + } } class MultiVersionControl { - @SuppressWarnings( - "initialization") // see https://github.com/typetools/checker-framework/issues/223 - public void parseArgs(@UnknownInitialization MultiVersionControl this) { - Options options = new Options(this); - options.parse_or_usage(); - } + @SuppressWarnings( + "initialization") // see https://github.com/typetools/checker-framework/issues/223 + public void parseArgs(@UnknownInitialization MultiVersionControl this) { + Options options = new Options(this); + options.parse_or_usage(); + } } // TODO: This checks that forbidden field assignments do not occur. (The diff --git a/checker/tests/nullness-initialization/RawMethodInvocation.java b/checker/tests/nullness-initialization/RawMethodInvocation.java index 761d16893d3..83a781c69c3 100644 --- a/checker/tests/nullness-initialization/RawMethodInvocation.java +++ b/checker/tests/nullness-initialization/RawMethodInvocation.java @@ -4,36 +4,36 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class RawMethodInvocation { - Object a; - Object b; + Object a; + Object b; - RawMethodInvocation(boolean constructor_inits_a) { - a = 1; - init_b(); - } + RawMethodInvocation(boolean constructor_inits_a) { + a = 1; + init_b(); + } - @EnsuresNonNull("b") - void init_b(@UnknownInitialization RawMethodInvocation this) { - b = 2; - } + @EnsuresNonNull("b") + void init_b(@UnknownInitialization RawMethodInvocation this) { + b = 2; + } - RawMethodInvocation(int constructor_inits_none) { - init_ab(); - } + RawMethodInvocation(int constructor_inits_none) { + init_ab(); + } - @EnsuresNonNull({"a", "b"}) - void init_ab(@UnknownInitialization RawMethodInvocation this) { - a = 1; - b = 2; - } + @EnsuresNonNull({"a", "b"}) + void init_ab(@UnknownInitialization RawMethodInvocation this) { + a = 1; + b = 2; + } - RawMethodInvocation(long constructor_escapes_raw) { - a = 1; - // this call is not valid, this is still raw - // :: error: (method.invocation.invalid) - nonRawMethod(); - b = 2; - } + RawMethodInvocation(long constructor_escapes_raw) { + a = 1; + // this call is not valid, this is still raw + // :: error: (method.invocation.invalid) + nonRawMethod(); + b = 2; + } - void nonRawMethod() {} + void nonRawMethod() {} } diff --git a/checker/tests/nullness-initialization/RawTypesBounded.java b/checker/tests/nullness-initialization/RawTypesBounded.java index 1edb3c1f12e..84cbf54219a 100644 --- a/checker/tests/nullness-initialization/RawTypesBounded.java +++ b/checker/tests/nullness-initialization/RawTypesBounded.java @@ -6,228 +6,228 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class RawTypesBounded { - class Bad { - @NonNull String field; - - public Bad() { - // :: error: (method.invocation.invalid) - this.init(); // error - // :: error: (method.invocation.invalid) - init(); // error - - this.field = "field"; // valid - // :: error: (assignment.type.incompatible) - this.field = null; // error - field = "field"; // valid - // :: error: (assignment.type.incompatible) - field = null; // error - } - - void init() { - output(this.field.length()); // valid - } - } - - class A { - @NonNull String field; - - public A() { - this.field = "field"; // valid - field = "field"; // valid - this.init(); // valid - init(); // valid - } - - public void init(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); - } - - public void initExpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(this.field); - } - - public void initImpl1(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(field.length()); - } + class Bad { + @NonNull String field; + + public Bad() { + // :: error: (method.invocation.invalid) + this.init(); // error + // :: error: (method.invocation.invalid) + init(); // error + + this.field = "field"; // valid + // :: error: (assignment.type.incompatible) + this.field = null; // error + field = "field"; // valid + // :: error: (assignment.type.incompatible) + field = null; // error + } - public void initImpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(field); - } + void init() { + output(this.field.length()); // valid } + } - class B extends A { - @NonNull String otherField; + class A { + @NonNull String field; - public B() { - super(); - // :: error: (assignment.type.incompatible) - this.otherField = null; // error - this.otherField = "otherField"; // valid - } + public A() { + this.field = "field"; // valid + field = "field"; // valid + this.init(); // valid + init(); // valid + } - @Override - public void init(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); // error (TODO: substitution) - super.init(); // valid - } + public void init(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); + } - public void initImpl1(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(field.length()); // error (TODO: substitution) - } + public void initExpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(this.field); + } - public void initExpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.otherField.length()); // error - } + public void initImpl1(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(field.length()); + } - public void initImpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(otherField.length()); // error - } + public void initImpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(field); + } + } - void other() { - init(); // valid - this.init(); // valid - } + class B extends A { + @NonNull String otherField; - void otherRaw(@UnknownInitialization B this) { - init(); // valid - this.init(); // valid - } + public B() { + super(); + // :: error: (assignment.type.incompatible) + this.otherField = null; // error + this.otherField = "otherField"; // valid } - class C extends B { + @Override + public void init(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); // error (TODO: substitution) + super.init(); // valid + } - @NonNull String[] strings; + public void initImpl1(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(field.length()); // error (TODO: substitution) + } + + public void initExpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.otherField.length()); // error + } - @Override - public void init(@UnknownInitialization C this) { - // :: error: (dereference.of.nullable) - output(this.strings.length); // error - System.out.println(); // valid - } + public void initImpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(otherField.length()); // error } - // To test whether the argument is @NonNull and @Initialized - static void output(@NonNull Object o) {} + void other() { + init(); // valid + this.init(); // valid + } - class D extends C { - @Override - public void init(@UnknownInitialization D this) { - this.field = "s"; - output(this.field.length()); - } + void otherRaw(@UnknownInitialization B this) { + init(); // valid + this.init(); // valid } + } - class MyTest { - int i; + class C extends B { - MyTest(int i) { - this.i = i; - } + @NonNull String[] strings; - void myTest(@UnknownInitialization MyTest this) { - i++; - } + @Override + public void init(@UnknownInitialization C this) { + // :: error: (dereference.of.nullable) + output(this.strings.length); // error + System.out.println(); // valid } + } + + // To test whether the argument is @NonNull and @Initialized + static void output(@NonNull Object o) {} - class AllFieldsInitialized { - long elapsedMillis = 0; - long startTime = 0; + class D extends C { + @Override + public void init(@UnknownInitialization D this) { + this.field = "s"; + output(this.field.length()); + } + } - // If all fields have an initializer, then the type of "this" - // should still not be non-raw (there might be uninitilized subclasses) - public AllFieldsInitialized() { - // :: error: (method.invocation.invalid) - nonRawMethod(); - } + class MyTest { + int i; - public void nonRawMethod() {} + MyTest(int i) { + this.i = i; } - class AFSIICell { - AllFieldsSetInInitializer afsii; + void myTest(@UnknownInitialization MyTest this) { + i++; } + } - class AllFieldsSetInInitializer { - long elapsedMillis; - long startTime; + class AllFieldsInitialized { + long elapsedMillis = 0; + long startTime = 0; - public AllFieldsSetInInitializer() { - elapsedMillis = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); - startTime = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); // still error (subclasses...) - } + // If all fields have an initializer, then the type of "this" + // should still not be non-raw (there might be uninitilized subclasses) + public AllFieldsInitialized() { + // :: error: (method.invocation.invalid) + nonRawMethod(); + } + + public void nonRawMethod() {} + } + + class AFSIICell { + AllFieldsSetInInitializer afsii; + } - public AllFieldsSetInInitializer(boolean b) { - // :: error: (method.invocation.invalid) - nonRawMethod(); - } + class AllFieldsSetInInitializer { + long elapsedMillis; + long startTime; - public void nonRawMethod() {} + public AllFieldsSetInInitializer() { + elapsedMillis = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); + startTime = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); // still error (subclasses...) } - class ConstructorInvocations { - int v; + public AllFieldsSetInInitializer(boolean b) { + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - public ConstructorInvocations(int v) { - this.v = v; - } + public void nonRawMethod() {} + } - public ConstructorInvocations() { - this(0); - // :: error: (method.invocation.invalid) - nonRawMethod(); - } + class ConstructorInvocations { + int v; - public void nonRawMethod() {} + public ConstructorInvocations(int v) { + this.v = v; } - class MethodAccess { - public MethodAccess() { - @NonNull String s = string(); - } - - public @NonNull String string(@UnknownInitialization MethodAccess this) { - return "nonnull"; - } + public ConstructorInvocations() { + this(0); + // :: error: (method.invocation.invalid) + nonRawMethod(); } - void cast(@UnknownInitialization Object... args) { + public void nonRawMethod() {} + } - @SuppressWarnings("rawtypes") - // :: error: (assignment.type.incompatible) - Object[] argsNonRaw1 = args; + class MethodAccess { + public MethodAccess() { + @NonNull String s = string(); + } - @SuppressWarnings("cast") - Object[] argsNonRaw2 = (Object[]) args; + public @NonNull String string(@UnknownInitialization MethodAccess this) { + return "nonnull"; } + } - // default qualifier is @Nullable, so this is OK. - class RawAfterConstructorBad { - Object o; + void cast(@UnknownInitialization Object... args) { - RawAfterConstructorBad() {} - } + @SuppressWarnings("rawtypes") + // :: error: (assignment.type.incompatible) + Object[] argsNonRaw1 = args; - class RawAfterConstructorOK1 { - @Nullable Object o; + @SuppressWarnings("cast") + Object[] argsNonRaw2 = (Object[]) args; + } - RawAfterConstructorOK1() {} - } + // default qualifier is @Nullable, so this is OK. + class RawAfterConstructorBad { + Object o; - class RawAfterConstructorOK2 { - int a; + RawAfterConstructorBad() {} + } - RawAfterConstructorOK2() {} - } + class RawAfterConstructorOK1 { + @Nullable Object o; + + RawAfterConstructorOK1() {} + } + + class RawAfterConstructorOK2 { + int a; + + RawAfterConstructorOK2() {} + } } diff --git a/checker/tests/nullness-initialization/Simple2.java b/checker/tests/nullness-initialization/Simple2.java index dbeb07b9b0e..02f299f372a 100644 --- a/checker/tests/nullness-initialization/Simple2.java +++ b/checker/tests/nullness-initialization/Simple2.java @@ -2,26 +2,26 @@ public class Simple2 { - @NonNull String f; + @NonNull String f; - public Simple2() { - // :: error: (method.invocation.invalid) - test(); + public Simple2() { + // :: error: (method.invocation.invalid) + test(); - f = "abc"; - } + f = "abc"; + } - public void test() { - System.out.println(f.toLowerCase()); - } + public void test() { + System.out.println(f.toLowerCase()); + } - public void a(Simple2 arg) { - @Nullable String s = null; - // :: error: (dereference.of.nullable) - s.hashCode(); - } + public void a(Simple2 arg) { + @Nullable String s = null; + // :: error: (dereference.of.nullable) + s.hashCode(); + } - public static void main(String[] args) { - new Simple2(); - } + public static void main(String[] args) { + new Simple2(); + } } diff --git a/checker/tests/nullness-initialization/StaticInitialization.java b/checker/tests/nullness-initialization/StaticInitialization.java index b58c2ed138b..b11f4e58f4c 100644 --- a/checker/tests/nullness-initialization/StaticInitialization.java +++ b/checker/tests/nullness-initialization/StaticInitialization.java @@ -3,9 +3,9 @@ public class StaticInitialization { - @SuppressWarnings({"nullness", "initialization.fields.uninitialized"}) - public static Object dontWarnAboutThisField; + @SuppressWarnings({"nullness", "initialization.fields.uninitialized"}) + public static Object dontWarnAboutThisField; - static { - } + static { + } } diff --git a/checker/tests/nullness-initialization/StaticInitializer.java b/checker/tests/nullness-initialization/StaticInitializer.java index 6c6b5182a88..5d8767964fe 100644 --- a/checker/tests/nullness-initialization/StaticInitializer.java +++ b/checker/tests/nullness-initialization/StaticInitializer.java @@ -3,58 +3,58 @@ public class StaticInitializer { - public static String a; - // :: error: (initialization.static.field.uninitialized) - public static String b; + public static String a; + // :: error: (initialization.static.field.uninitialized) + public static String b; - static { - a = ""; - } + static { + a = ""; + } - public StaticInitializer() {} + public StaticInitializer() {} } class StaticInitializer2 { - // :: error: (initialization.static.field.uninitialized) - public static String a; - // :: error: (initialization.static.field.uninitialized) - public static String b; + // :: error: (initialization.static.field.uninitialized) + public static String a; + // :: error: (initialization.static.field.uninitialized) + public static String b; } class StaticInitializer3 { - public static String a = ""; + public static String a = ""; } class StaticInitializer4 { - public static String a = ""; - public static String b; + public static String a = ""; + public static String b; - static { - b = ""; - } + static { + b = ""; + } } class StaticInitializer5 { - public static String a = ""; + public static String a = ""; - static { - a.toString(); - } + static { + a.toString(); + } - public static String b = ""; + public static String b = ""; } class StaticInitializer6 { - public static String a = ""; + public static String a = ""; - public static String b; + public static String b; - static { - // TODO error expected. See #556. - b.toString(); - } + static { + // TODO error expected. See #556. + b.toString(); + } - static { - b = ""; - } + static { + b = ""; + } } diff --git a/checker/tests/nullness-initialization/SuperConstructorInit.java b/checker/tests/nullness-initialization/SuperConstructorInit.java index aa863fb05f9..dee48326795 100644 --- a/checker/tests/nullness-initialization/SuperConstructorInit.java +++ b/checker/tests/nullness-initialization/SuperConstructorInit.java @@ -2,19 +2,19 @@ public class SuperConstructorInit { - String a; + String a; - public SuperConstructorInit() { - a = ""; - } + public SuperConstructorInit() { + a = ""; + } - public static class B extends SuperConstructorInit { - String b; + public static class B extends SuperConstructorInit { + String b; - // :: error: (initialization.fields.uninitialized) - public B() { - super(); - a.toString(); - } + // :: error: (initialization.fields.uninitialized) + public B() { + super(); + a.toString(); } + } } diff --git a/checker/tests/nullness-initialization/Suppression.java b/checker/tests/nullness-initialization/Suppression.java index 93ea66a4f30..e182567a79b 100644 --- a/checker/tests/nullness-initialization/Suppression.java +++ b/checker/tests/nullness-initialization/Suppression.java @@ -1,24 +1,24 @@ public class Suppression { - Object f; + Object f; - @SuppressWarnings("nullnessnoinit") - void test() { - String a = null; - a.toString(); - } + @SuppressWarnings("nullnessnoinit") + void test() { + String a = null; + a.toString(); + } - @SuppressWarnings("initialization") - void test2() { - String a = null; - // :: error: (dereference.of.nullable) - a.toString(); - } + @SuppressWarnings("initialization") + void test2() { + String a = null; + // :: error: (dereference.of.nullable) + a.toString(); + } - @SuppressWarnings("nullness") - Suppression() {} + @SuppressWarnings("nullness") + Suppression() {} - @SuppressWarnings("nullnessnoinit") - // :: error: (initialization.fields.uninitialized) - Suppression(int dummy) {} + @SuppressWarnings("nullnessnoinit") + // :: error: (initialization.fields.uninitialized) + Suppression(int dummy) {} } diff --git a/checker/tests/nullness-initialization/ThisNodeTest.java b/checker/tests/nullness-initialization/ThisNodeTest.java index 1bb39b13975..d7ea48f2c25 100644 --- a/checker/tests/nullness-initialization/ThisNodeTest.java +++ b/checker/tests/nullness-initialization/ThisNodeTest.java @@ -2,20 +2,20 @@ import org.checkerframework.checker.nullness.qual.*; public class ThisNodeTest { - public ThisNodeTest() { - new Object() { - void test() { - @UnderInitialization ThisNodeTest l1 = ThisNodeTest.this; - // :: error: (assignment.type.incompatible) - @Initialized ThisNodeTest l2 = ThisNodeTest.this; + public ThisNodeTest() { + new Object() { + void test() { + @UnderInitialization ThisNodeTest l1 = ThisNodeTest.this; + // :: error: (assignment.type.incompatible) + @Initialized ThisNodeTest l2 = ThisNodeTest.this; - // :: error: (method.invocation.invalid) - ThisNodeTest.this.foo(); - // :: error: (method.invocation.invalid) - foo(); - } - }; - } + // :: error: (method.invocation.invalid) + ThisNodeTest.this.foo(); + // :: error: (method.invocation.invalid) + foo(); + } + }; + } - void foo() {} + void foo() {} } diff --git a/checker/tests/nullness-initialization/Throwing.java b/checker/tests/nullness-initialization/Throwing.java index 96b49e30070..63e43bb5950 100644 --- a/checker/tests/nullness-initialization/Throwing.java +++ b/checker/tests/nullness-initialization/Throwing.java @@ -2,21 +2,21 @@ public class Throwing { - String a; + String a; - // :: error: (initialization.fields.uninitialized) - public Throwing(boolean throwError) { - if (throwError) { - throw new RuntimeException("not a real error"); - } + // :: error: (initialization.fields.uninitialized) + public Throwing(boolean throwError) { + if (throwError) { + throw new RuntimeException("not a real error"); } + } - // :: error: (initialization.fields.uninitialized) - public Throwing(int input) { - try { - throw new RuntimeException("not a real error"); - } catch (RuntimeException e) { - // do nothing - } + // :: error: (initialization.fields.uninitialized) + public Throwing(int input) { + try { + throw new RuntimeException("not a real error"); + } catch (RuntimeException e) { + // do nothing } + } } diff --git a/checker/tests/nullness-initialization/TryCatch.java b/checker/tests/nullness-initialization/TryCatch.java index 6d8d9032849..2ed160d8407 100644 --- a/checker/tests/nullness-initialization/TryCatch.java +++ b/checker/tests/nullness-initialization/TryCatch.java @@ -1,42 +1,41 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.io.*; import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; class EntryReader { - public EntryReader() throws IOException {} + public EntryReader() throws IOException {} } public class TryCatch { - void constructorException() throws IOException { - List file_errors = new ArrayList<>(); - try { - new EntryReader(); - } catch (FileNotFoundException e) { - file_errors.add(e); - } + void constructorException() throws IOException { + List file_errors = new ArrayList<>(); + try { + new EntryReader(); + } catch (FileNotFoundException e) { + file_errors.add(e); } + } - void unreachableCatch(String[] xs) { - String t = ""; - t.toString(); - try { - } catch (Throwable e) { - // Note that this code is dead. - // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) - t.toString(); - } + void unreachableCatch(String[] xs) { + String t = ""; + t.toString(); + try { + } catch (Throwable e) { + // Note that this code is dead. + // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) + t.toString(); } + } - void noClassDefFoundError(@Nullable Object x) { - try { - Class cls = EntryReader.class; - } catch (NoClassDefFoundError e) { - if (x != null) { - // OK - x.toString(); - } - } + void noClassDefFoundError(@Nullable Object x) { + try { + Class cls = EntryReader.class; + } catch (NoClassDefFoundError e) { + if (x != null) { + // OK + x.toString(); + } } + } } diff --git a/checker/tests/nullness-initialization/TwoStaticInitBlocks.java b/checker/tests/nullness-initialization/TwoStaticInitBlocks.java index 18873846461..92657676bab 100644 --- a/checker/tests/nullness-initialization/TwoStaticInitBlocks.java +++ b/checker/tests/nullness-initialization/TwoStaticInitBlocks.java @@ -4,42 +4,42 @@ // initializers and a few other things public class TwoStaticInitBlocks { - String f2; - String f1 = (f2 = ""); - - { - t = ""; - f1.toString(); - f2.toString(); - } - - final String ws_regexp; - String t; - String s; - - { - ws_regexp = "hello"; - t.toString(); - // :: error: (dereference.of.nullable) - s.toString(); - } + String f2; + String f1 = (f2 = ""); + + { + t = ""; + f1.toString(); + f2.toString(); + } + + final String ws_regexp; + String t; + String s; + + { + ws_regexp = "hello"; + t.toString(); + // :: error: (dereference.of.nullable) + s.toString(); + } } class TwoStaticInitBlocks2 { - static String f2; - static String f1 = (f2 = ""); - - static { - t = ""; - f1.toString(); - f2.toString(); - } - - static final String ws_regexp; - static String t; - - static { - ws_regexp = "hello"; - t.toString(); - } + static String f2; + static String f1 = (f2 = ""); + + static { + t = ""; + f1.toString(); + f2.toString(); + } + + static final String ws_regexp; + static String t; + + static { + ws_regexp = "hello"; + t.toString(); + } } diff --git a/checker/tests/nullness-initialization/ValidType.java b/checker/tests/nullness-initialization/ValidType.java index f98a5efb3ce..0c0889f1dba 100644 --- a/checker/tests/nullness-initialization/ValidType.java +++ b/checker/tests/nullness-initialization/ValidType.java @@ -3,10 +3,10 @@ public class ValidType { - void t1() { - // :: error: (type.invalid.conflicting.annos) - @NonNull @Nullable String l1; - // :: error: (type.invalid.conflicting.annos) - @UnderInitialization @UnknownInitialization String f; - } + void t1() { + // :: error: (type.invalid.conflicting.annos) + @NonNull @Nullable String l1; + // :: error: (type.invalid.conflicting.annos) + @UnderInitialization @UnknownInitialization String f; + } } diff --git a/checker/tests/nullness-initialization/VarInfoName.java b/checker/tests/nullness-initialization/VarInfoName.java index 63c15569aa6..449f2c4b382 100644 --- a/checker/tests/nullness-initialization/VarInfoName.java +++ b/checker/tests/nullness-initialization/VarInfoName.java @@ -1,15 +1,15 @@ public abstract class VarInfoName { - public abstract T accept(Visitor v); + public abstract T accept(Visitor v); - public abstract static class Visitor {} + public abstract static class Visitor {} - public abstract static class BooleanAndVisitor extends Visitor { - private boolean result; + public abstract static class BooleanAndVisitor extends Visitor { + private boolean result; - public BooleanAndVisitor(VarInfoName name) { - // :: error: (argument.type.incompatible) :: warning: (nulltest.redundant) - result = (name.accept(this) != null); - } + public BooleanAndVisitor(VarInfoName name) { + // :: error: (argument.type.incompatible) :: warning: (nulltest.redundant) + result = (name.accept(this) != null); } + } } diff --git a/checker/tests/nullness-initialization/Wellformed.java b/checker/tests/nullness-initialization/Wellformed.java index e7a0a59733a..2aa83b2d16a 100644 --- a/checker/tests/nullness-initialization/Wellformed.java +++ b/checker/tests/nullness-initialization/Wellformed.java @@ -1,69 +1,69 @@ import org.checkerframework.checker.nullness.qual.*; public class Wellformed { - // :: error: (type.invalid.conflicting.annos) - @NonNull @Nullable Object f = null; - - // :: error: (type.invalid.conflicting.annos) - class Gen1a {} + // :: error: (type.invalid.conflicting.annos) + @NonNull @Nullable Object f = null; - class Gen1b { - // :: error: (type.invalid.conflicting.annos) - void m(T p) {} + // :: error: (type.invalid.conflicting.annos) + class Gen1a {} - // :: error: (type.invalid.conflicting.annos) - <@NonNull @Nullable T> void m2(T p) {} - } + class Gen1b { + // :: error: (type.invalid.conflicting.annos) + void m(T p) {} // :: error: (type.invalid.conflicting.annos) - class Gen1c<@NonNull @Nullable TTT> {} + <@NonNull @Nullable T> void m2(T p) {} + } + + // :: error: (type.invalid.conflicting.annos) + class Gen1c<@NonNull @Nullable TTT> {} - class Gen2a<@Nullable T> {} + class Gen2a<@Nullable T> {} - // :: error: (bound.type.incompatible) - class Gen2b<@Nullable T extends Object> {} + // :: error: (bound.type.incompatible) + class Gen2b<@Nullable T extends Object> {} - // :: error: (bound.type.incompatible) - class Gen2c<@Nullable T extends @NonNull Object> {} + // :: error: (bound.type.incompatible) + class Gen2c<@Nullable T extends @NonNull Object> {} - class Gen3a { - @Nullable T f; + class Gen3a { + @Nullable T f; - @Nullable T get() { - return null; - } + @Nullable T get() { + return null; } + } - class Gen3b { - @Nullable T f; + class Gen3b { + @Nullable T f; - @Nullable T get() { - return null; - } + @Nullable T get() { + return null; } + } - class Gen4 { - // :: error: (initialization.field.uninitialized) - @NonNull T f; + class Gen4 { + // :: error: (initialization.field.uninitialized) + @NonNull T f; - @NonNull T get() { - throw new RuntimeException(); - } - - void set(@NonNull T p) {} + @NonNull T get() { + throw new RuntimeException(); } - class Gen5a {} + void set(@NonNull T p) {} + } + + class Gen5a {} - class Gen5b extends Gen5a<@Nullable Object> {} + class Gen5b extends Gen5a<@Nullable Object> {} - class Gen5c extends Gen5a<@Nullable S> {} + class Gen5c extends Gen5a<@Nullable S> {} - class Gen6a {} + class Gen6a {} - // :: error: (type.argument.type.incompatible) - class Gen6b extends Gen6a<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class Gen6b extends Gen6a<@Nullable Object> {} - // :: error: (type.argument.type.incompatible) - class Gen6c extends Gen6a<@Nullable S> {} + // :: error: (type.argument.type.incompatible) + class Gen6c extends Gen6a<@Nullable S> {} } diff --git a/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java b/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java index 3c2f038d9e8..e33ab78a5fc 100644 --- a/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java +++ b/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java @@ -2,84 +2,84 @@ import org.checkerframework.dataflow.qual.*; public class AnnotatedGenerics { - public static void testNullableTypeVariable() { - class Test { - // :: error: (initialization.field.uninitialized) - T f; + public static void testNullableTypeVariable() { + class Test { + // :: error: (initialization.field.uninitialized) + T f; - @Nullable T get() { - return f; - } - } - Test> l = new Test<>(); - // :: error: (iterating.over.nullable) - for (String s : l.get()) {} + @Nullable T get() { + return f; + } } + Test> l = new Test<>(); + // :: error: (iterating.over.nullable) + for (String s : l.get()) {} + } - public static void testNonNullTypeVariable() { - class Test { - @NonNull T get() { - throw new RuntimeException(); - } - } - Test<@Nullable Iterable> l = new Test<>(); - for (String s : l.get()) {} - Test> n = new Test<>(); - for (String s : n.get()) {} + public static void testNonNullTypeVariable() { + class Test { + @NonNull T get() { + throw new RuntimeException(); + } } + Test<@Nullable Iterable> l = new Test<>(); + for (String s : l.get()) {} + Test> n = new Test<>(); + for (String s : n.get()) {} + } - static class MyClass implements MyIterator<@Nullable T> { - public boolean hasNext() { - return true; - } + static class MyClass implements MyIterator<@Nullable T> { + public boolean hasNext() { + return true; + } - public @Nullable T next() { - return null; - } + public @Nullable T next() { + return null; + } - public void remove() {} + public void remove() {} - static void test() { - MyClass c = new MyClass<>(); - String c1 = c.next(); - @Nullable String c2 = c.next(); - // :: error: (assignment.type.incompatible) - @NonNull String c3 = c.next(); - } + static void test() { + MyClass c = new MyClass<>(); + String c1 = c.next(); + @Nullable String c2 = c.next(); + // :: error: (assignment.type.incompatible) + @NonNull String c3 = c.next(); } + } - public static final class MyComprator> { - public void compare(T a1, T a2) { - a1.compareTo(a2); - } - - public void compare2(@NonNull T a1, @NonNull T a2) { - a1.compareTo(a2); - } + public static final class MyComprator> { + public void compare(T a1, T a2) { + a1.compareTo(a2); + } - public void compare3(T a1, @Nullable T a2) { - // :: error: (argument.type.incompatible) - a1.compareTo(a2); - } + public void compare2(@NonNull T a1, @NonNull T a2) { + a1.compareTo(a2); } - class MyComparable { - @Pure - public int compareTo(@NonNull T a1) { - return 0; - } + public void compare3(T a1, @Nullable T a2) { + // :: error: (argument.type.incompatible) + a1.compareTo(a2); } + } - T test(java.util.List> l) { - test(new java.util.ArrayList()); - throw new Error(); + class MyComparable { + @Pure + public int compareTo(@NonNull T a1) { + return 0; } + } - public interface MyIterator { - boolean hasNext(); + T test(java.util.List> l) { + test(new java.util.ArrayList()); + throw new Error(); + } - E next(); + public interface MyIterator { + boolean hasNext(); - void remove(); - } + E next(); + + void remove(); + } } diff --git a/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java b/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java index c229d4d0638..2e79b31aa64 100644 --- a/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java +++ b/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java @@ -1,135 +1,135 @@ import org.checkerframework.checker.nullness.qual.*; public class AnnotatedGenerics2 { - // Top-level class to ensure that both classes are processed. - - class AnnotatedGenerics2Nble { - // :: error: (initialization.field.uninitialized) - @NonNull T myFieldNN; - @Nullable T myFieldNble; - // :: error: (initialization.field.uninitialized) - T myFieldT; - - /* TODO: This test case gets affected by flow inference. - * Investigate what the desired behavior is later. - void fields() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // TODO:: error: (assignment.type.incompatible) - myFieldT = myFieldNble; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - */ - - void fields1() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; - } - - void fields2() { - // :: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // :: error: (assignment.type.incompatible) - myFieldT = myFieldNble; - } - - void fields3() { - // :: error: (assignment.type.incompatible) - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - - void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { - myFieldNN = myParamNN; - myFieldNble = myParamNN; - myFieldT = myParamNN; - - // :: error: (assignment.type.incompatible) - myFieldNN = myParamNble; - myFieldNble = myParamNble; - // :: error: (assignment.type.incompatible) - myFieldT = myParamNble; - - // :: error: (assignment.type.incompatible) - myFieldNN = myParamT; - myFieldNble = myParamT; - myFieldT = myParamT; - } + // Top-level class to ensure that both classes are processed. + + class AnnotatedGenerics2Nble { + // :: error: (initialization.field.uninitialized) + @NonNull T myFieldNN; + @Nullable T myFieldNble; + // :: error: (initialization.field.uninitialized) + T myFieldT; + + /* TODO: This test case gets affected by flow inference. + * Investigate what the desired behavior is later. + void fields() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // TODO:: error: (assignment.type.incompatible) + myFieldT = myFieldNble; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + */ + + void fields1() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; } - class AnnotatedGenerics2NN { - // :: error: (initialization.field.uninitialized) - @NonNull T myFieldNN; - @Nullable T myFieldNble; - // :: error: (initialization.field.uninitialized) - T myFieldT; - - /* TODO: This test case gets affected by flow inference. - * Investigate what the desired behavior is later. - void fields() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // TODO:: error: (assignment.type.incompatible) - myFieldT = myFieldNble; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - */ - - void fields1() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; - } - - void fields2() { - // :: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // :: error: (assignment.type.incompatible) - myFieldT = myFieldNble; - } - - void fields3() { - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - - void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { - myFieldNN = myParamNN; - myFieldNble = myParamNN; - myFieldT = myParamNN; - - // :: error: (assignment.type.incompatible) - myFieldNN = myParamNble; - myFieldNble = myParamNble; - // :: error: (assignment.type.incompatible) - myFieldT = myParamNble; - - myFieldNN = myParamT; - myFieldNble = myParamT; - myFieldT = myParamT; - } + void fields2() { + // :: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // :: error: (assignment.type.incompatible) + myFieldT = myFieldNble; } + + void fields3() { + // :: error: (assignment.type.incompatible) + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + + void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { + myFieldNN = myParamNN; + myFieldNble = myParamNN; + myFieldT = myParamNN; + + // :: error: (assignment.type.incompatible) + myFieldNN = myParamNble; + myFieldNble = myParamNble; + // :: error: (assignment.type.incompatible) + myFieldT = myParamNble; + + // :: error: (assignment.type.incompatible) + myFieldNN = myParamT; + myFieldNble = myParamT; + myFieldT = myParamT; + } + } + + class AnnotatedGenerics2NN { + // :: error: (initialization.field.uninitialized) + @NonNull T myFieldNN; + @Nullable T myFieldNble; + // :: error: (initialization.field.uninitialized) + T myFieldT; + + /* TODO: This test case gets affected by flow inference. + * Investigate what the desired behavior is later. + void fields() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // TODO:: error: (assignment.type.incompatible) + myFieldT = myFieldNble; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + */ + + void fields1() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; + } + + void fields2() { + // :: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // :: error: (assignment.type.incompatible) + myFieldT = myFieldNble; + } + + void fields3() { + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + + void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { + myFieldNN = myParamNN; + myFieldNble = myParamNN; + myFieldT = myParamNN; + + // :: error: (assignment.type.incompatible) + myFieldNN = myParamNble; + myFieldNble = myParamNble; + // :: error: (assignment.type.incompatible) + myFieldT = myParamNble; + + myFieldNN = myParamT; + myFieldNble = myParamT; + myFieldT = myParamT; + } + } } diff --git a/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java b/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java index cb5843ebe06..42fdf78c5d3 100644 --- a/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java +++ b/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java @@ -4,49 +4,48 @@ public class GenericBoundsExplicit<@NonNull T extends @Nullable Object> { - @SuppressWarnings("initialization.field.uninitialized") - T t; - - public void method() { - // :: error: (dereference.of.nullable) - String str = t.toString(); - } - - public static void doSomething() { - final GenericBoundsExplicit<@Nullable String> b = - new GenericBoundsExplicit<@Nullable String>(); - b.method(); - } + @SuppressWarnings("initialization.field.uninitialized") + T t; + + public void method() { + // :: error: (dereference.of.nullable) + String str = t.toString(); + } + + public static void doSomething() { + final GenericBoundsExplicit<@Nullable String> b = new GenericBoundsExplicit<@Nullable String>(); + b.method(); + } } class GenericBoundsExplicit2<@NonNull TT extends @Nullable Object> { - @Nullable TT tt1; - // :: error: (initialization.field.uninitialized) - @NonNull TT tt2; - // :: error: (initialization.field.uninitialized) - TT tt3; + @Nullable TT tt1; + // :: error: (initialization.field.uninitialized) + @NonNull TT tt2; + // :: error: (initialization.field.uninitialized) + TT tt3; - public void context() { + public void context() { - // :: error: (dereference.of.nullable) - tt1.toString(); - tt2.toString(); + // :: error: (dereference.of.nullable) + tt1.toString(); + tt2.toString(); - // :: error: (dereference.of.nullable) - tt3.toString(); - } + // :: error: (dereference.of.nullable) + tt3.toString(); + } } @SuppressWarnings("initialization.field.uninitialized") class GenericBoundsExplicit3<@NonNull TTT extends @NonNull Object> { - @Nullable TTT ttt1; - @NonNull TTT ttt2; - TTT ttt3; - - public void context() { - // :: error: (dereference.of.nullable) - ttt1.toString(); - ttt2.toString(); - ttt3.toString(); - } + @Nullable TTT ttt1; + @NonNull TTT ttt2; + TTT ttt3; + + public void context() { + // :: error: (dereference.of.nullable) + ttt1.toString(); + ttt2.toString(); + ttt3.toString(); + } } diff --git a/checker/tests/nullness-initialization/generics/Issue314.java b/checker/tests/nullness-initialization/generics/Issue314.java index 419cabb048b..5da71291a77 100644 --- a/checker/tests/nullness-initialization/generics/Issue314.java +++ b/checker/tests/nullness-initialization/generics/Issue314.java @@ -1,29 +1,28 @@ // Test case for Issue 314: // https://github.com/typetools/checker-framework/issues/314 +import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.List; - public class Issue314 { - List m1(List<@NonNull T> l1) { - return l1; - } + List m1(List<@NonNull T> l1) { + return l1; + } - List m2(List<@NonNull T> l1) { - // :: error: (return.type.incompatible) - return l1; - } + List m2(List<@NonNull T> l1) { + // :: error: (return.type.incompatible) + return l1; + } - class Also { - S f1; - @NonNull S f2; + class Also { + S f1; + @NonNull S f2; - { - // :: error: (assignment.type.incompatible) - f1 = f2; - f2 = f1; - } + { + // :: error: (assignment.type.incompatible) + f1 = f2; + f2 = f1; } + } } diff --git a/checker/tests/nullness-initialization/generics/Issue783c.java b/checker/tests/nullness-initialization/generics/Issue783c.java index 9838d5759a0..c84e72b3e21 100644 --- a/checker/tests/nullness-initialization/generics/Issue783c.java +++ b/checker/tests/nullness-initialization/generics/Issue783c.java @@ -1,12 +1,12 @@ public class Issue783c { - // :: error: (initialization.field.uninitialized) - private T val; + // :: error: (initialization.field.uninitialized) + private T val; - public void set(T val) { - this.val = val; - } + public void set(T val) { + this.val = val; + } - public T get() { - return val; - } + public T get() { + return val; + } } diff --git a/checker/tests/nullness-initialization/generics/NullableLUB.java b/checker/tests/nullness-initialization/generics/NullableLUB.java index 87d88d7d384..4c5e6a94a3e 100644 --- a/checker/tests/nullness-initialization/generics/NullableLUB.java +++ b/checker/tests/nullness-initialization/generics/NullableLUB.java @@ -5,29 +5,29 @@ * get raised, leading to a missed NPE. */ public class NullableLUB { - // :: error: (initialization.field.uninitialized) - T t; - @Nullable T nt; + // :: error: (initialization.field.uninitialized) + T t; + @Nullable T nt; - T m(boolean b, T p) { - T r1 = b ? p : null; - nt = r1; - // :: error: (assignment.type.incompatible) - t = r1; - // :: error: (return.type.incompatible) - return r1; - } + T m(boolean b, T p) { + T r1 = b ? p : null; + nt = r1; + // :: error: (assignment.type.incompatible) + t = r1; + // :: error: (return.type.incompatible) + return r1; + } - public static void main(String[] args) { - new NullableLUB<@NonNull Object>().m(false, new Object()).toString(); - } + public static void main(String[] args) { + new NullableLUB<@NonNull Object>().m(false, new Object()).toString(); + } - T m2(boolean b, T p) { - T r1 = b ? null : p; - nt = r1; - // :: error: (assignment.type.incompatible) - t = r1; - // :: error: (return.type.incompatible) - return r1; - } + T m2(boolean b, T p) { + T r1 = b ? null : p; + nt = r1; + // :: error: (assignment.type.incompatible) + t = r1; + // :: error: (return.type.incompatible) + return r1; + } } diff --git a/checker/tests/nullness-initialization/generics/WellformedBounds.java b/checker/tests/nullness-initialization/generics/WellformedBounds.java index 1f09132efd1..f9ccd100ca3 100644 --- a/checker/tests/nullness-initialization/generics/WellformedBounds.java +++ b/checker/tests/nullness-initialization/generics/WellformedBounds.java @@ -1,39 +1,39 @@ import org.checkerframework.checker.nullness.qual.*; class Param { - // Field f needs to be set, because the upper bound is @Initialized - // :: error: (initialization.field.uninitialized) - T f; + // Field f needs to be set, because the upper bound is @Initialized + // :: error: (initialization.field.uninitialized) + T f; - void foo() { - // Valid, because upper bound is @Initialized @NonNull - f.toString(); - } + void foo() { + // Valid, because upper bound is @Initialized @NonNull + f.toString(); + } } // :: error: (type.argument.type.incompatible) class Invalid> { - void bar(S s) { - s.foo(); - } + void bar(S s) { + s.foo(); + } - // :: error: (type.argument.type.incompatible) - > void foobar(M p) {} + // :: error: (type.argument.type.incompatible) + > void foobar(M p) {} } interface ParamI {} class Invalid2< - S extends - Number & - // :: error: (type.argument.type.incompatible) - ParamI<@Nullable Object>> {} + S extends + Number & + // :: error: (type.argument.type.incompatible) + ParamI<@Nullable Object>> {} class Invalid3 { - < - M extends - Number & - // :: error: (type.argument.type.incompatible) - ParamI<@Nullable Object>> - void foobar(M p) {} + < + M extends + Number & + // :: error: (type.argument.type.incompatible) + ParamI<@Nullable Object>> + void foobar(M p) {} } diff --git a/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java b/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java index 562ecdc9f95..6a5ffc3cb65 100644 --- a/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java +++ b/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java @@ -4,198 +4,198 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionInit { - R apply(T t); + R apply(T t); } interface Consumer { - void consume(T t); + void consume(T t); } public class LambdaInit { - String f1; - String f2 = ""; - @Nullable String f3 = ""; + String f1; + String f2 = ""; + @Nullable String f3 = ""; + + String f1b; + FunctionInit ff0 = + s -> { + // :: error: (dereference.of.nullable) + f1.toString(); + // :: error: (dereference.of.nullable) + f1b.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; + // Test field value refinement after initializer. f1b should still be @Nullable in the lambda. + Object o1 = f1b = ""; + + String f4; + + { + f3 = ""; + f4 = ""; + FunctionInit ff0 = + s -> { + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f4.toString(); + return ""; + }; + } + + String f5; + + @SuppressWarnings("initialization.fields.uninitialized") // f1 is not initialized + LambdaInit() { + f5 = ""; + FunctionInit ff0 = + s -> { + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f5.toString(); + return ""; + }; + } + + // Test for https://github.com/typetools/checker-framework/issues/5194 . + Object o = + new Object() { + @Override + public String toString() { + // BUG: this should not yield a warning. + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + return ""; + } + }; - String f1b; + // Works! + void method() { FunctionInit ff0 = - s -> { - // :: error: (dereference.of.nullable) - f1.toString(); - // :: error: (dereference.of.nullable) - f1b.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; - // Test field value refinement after initializer. f1b should still be @Nullable in the lambda. - Object o1 = f1b = ""; + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; + } + + // Test for nested + class Nested { + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; String f4; { - f3 = ""; - f4 = ""; - FunctionInit ff0 = - s -> { - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f4.toString(); - return ""; - }; + f3 = ""; + f4 = ""; + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f4.toString(); + return ""; + }; } String f5; - @SuppressWarnings("initialization.fields.uninitialized") // f1 is not initialized - LambdaInit() { - f5 = ""; - FunctionInit ff0 = - s -> { - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f5.toString(); - return ""; - }; + Nested() { + f5 = ""; + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f5.toString(); + return ""; + }; } - // Test for https://github.com/typetools/checker-framework/issues/5194 . - Object o = - new Object() { - @Override - public String toString() { - // BUG: this should not yield a warning. - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - return ""; - } - }; - - // Works! void method() { - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; - } - - // Test for nested - class Nested { - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; - - String f4; - - { - f3 = ""; - f4 = ""; - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f4.toString(); - return ""; - }; - } - - String f5; - - Nested() { - f5 = ""; - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f5.toString(); - return ""; - }; - } - - void method() { - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; - } + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; } - - // Test for nested in a lambda - Consumer func = - s -> { - Consumer ff0 = - s2 -> { - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - }; - }; - - // Tests for static initializers. - // :: error: (initialization.static.field.uninitialized) - static String sf1; - static String sf2 = ""; - static @Nullable String sf3 = ""; - static String sf1b; - static FunctionInit sff0 = - s -> { - - // This is an issue with static initializers in general - // // :: error: (dereference.of.nullable) - sf1.toString(); - // This is an issue with static initializers in general - // // :: error: (dereference.of.nullable) - sf1b.toString(); - sf2.toString(); - // :: error: (dereference.of.nullable) - sf3.toString(); - return ""; + } + + // Test for nested in a lambda + Consumer func = + s -> { + Consumer ff0 = + s2 -> { + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); }; - // Test field value refinement after initializer. f1b should still be null. - static Object so1 = sf1b = ""; - - static String sf4; - - static { - sf3 = ""; - sf4 = ""; - FunctionInit sff0 = - s -> { - - // This is an issue with static initializers in general - // // :: error: (dereference.of.nullable) - sf1.toString(); - sf2.toString(); - // :: error: (dereference.of.nullable) - sf3.toString(); - sf4.toString(); - return ""; - }; - } + }; + + // Tests for static initializers. + // :: error: (initialization.static.field.uninitialized) + static String sf1; + static String sf2 = ""; + static @Nullable String sf3 = ""; + static String sf1b; + static FunctionInit sff0 = + s -> { + + // This is an issue with static initializers in general + // // :: error: (dereference.of.nullable) + sf1.toString(); + // This is an issue with static initializers in general + // // :: error: (dereference.of.nullable) + sf1b.toString(); + sf2.toString(); + // :: error: (dereference.of.nullable) + sf3.toString(); + return ""; + }; + // Test field value refinement after initializer. f1b should still be null. + static Object so1 = sf1b = ""; + + static String sf4; + + static { + sf3 = ""; + sf4 = ""; + FunctionInit sff0 = + s -> { + + // This is an issue with static initializers in general + // // :: error: (dereference.of.nullable) + sf1.toString(); + sf2.toString(); + // :: error: (dereference.of.nullable) + sf3.toString(); + sf4.toString(); + return ""; + }; + } } diff --git a/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java b/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java index 5f43e8df2d1..7b01d2b6f69 100644 --- a/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java +++ b/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java @@ -4,30 +4,30 @@ // Tests for the nullable type system interface SupplierR { - @NonNull ReceiverTest supply(); + @NonNull ReceiverTest supply(); } interface FunctionRT { - R apply(T t); + R apply(T t); } class ReceiverTest { - // :: error: (method.invocation.invalid) - FunctionRT f1 = s -> this.toString(); - // :: error: (method.invocation.invalid) - FunctionRT f2 = s -> super.toString(); + // :: error: (method.invocation.invalid) + FunctionRT f1 = s -> this.toString(); + // :: error: (method.invocation.invalid) + FunctionRT f2 = s -> super.toString(); - // :: error: (nullness.on.receiver) - void context1(@NonNull ReceiverTest this) { - SupplierR s = () -> this; - } + // :: error: (nullness.on.receiver) + void context1(@NonNull ReceiverTest this) { + SupplierR s = () -> this; + } - // :: error: (nullness.on.receiver) - void context2(@Nullable ReceiverTest this) { - // TODO: This is bug that is not specific to lambdas - // https://github.com/typetools/checker-framework/issues/352 - // :: error: (return.type.incompatible) - SupplierR s = () -> this; - } + // :: error: (nullness.on.receiver) + void context2(@Nullable ReceiverTest this) { + // TODO: This is bug that is not specific to lambdas + // https://github.com/typetools/checker-framework/issues/352 + // :: error: (return.type.incompatible) + SupplierR s = () -> this; + } } diff --git a/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java b/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java index 9c745cb3db2..b0fceb272cf 100644 --- a/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java +++ b/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java @@ -3,11 +3,11 @@ public class TwoDimensionalArray { - public static void main(String[] args) { - assert any_null(new Object[][] {null}) == true; - } + public static void main(String[] args) { + assert any_null(new Object[][] {null}) == true; + } - public static boolean any_null(Object[] a) { - return true; - } + public static boolean any_null(Object[] a) { + return true; + } } diff --git a/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java b/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java index 2eeb33b2349..4fdeae36d0e 100644 --- a/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java +++ b/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java @@ -1,22 +1,21 @@ import com.sun.javadoc.Doc; - import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; // @above-java11-jdk-skip-test com.sun.javadoc.Doc doesn't exist above 11. public class JavadocJdkAnnotations { - @Nullable Object f = null; + @Nullable Object f = null; - @SuppressWarnings("removal") - void testPureAnnotation(Doc d) { - // This tests that @Pure and @SideEffectFree annotations are read. + @SuppressWarnings("removal") + void testPureAnnotation(Doc d) { + // This tests that @Pure and @SideEffectFree annotations are read. - f = "non-null value"; - d.isIncluded(); - @NonNull Object x = f; - d.tags(); - // :: error: (assignment.type.incompatible) - @NonNull Object y = f; - } + f = "non-null value"; + d.isIncluded(); + @NonNull Object x = f; + d.tags(); + // :: error: (assignment.type.incompatible) + @NonNull Object y = f; + } } diff --git a/checker/tests/nullness-nodelombok/UnsoundnessTest.java b/checker/tests/nullness-nodelombok/UnsoundnessTest.java index 2264637f10b..80c382c2e5a 100644 --- a/checker/tests/nullness-nodelombok/UnsoundnessTest.java +++ b/checker/tests/nullness-nodelombok/UnsoundnessTest.java @@ -3,15 +3,15 @@ @lombok.Builder class UnsoundnessTest { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - // An error should be issued here, but the code has not been delombok'd. - // If the CF and Lombok are ever able to work in the same invocation of javac - // (i.e. without delomboking first), then this error should be changed back to an - // expected error by re-adding the leading "::". - // error: (assignment.type.incompatible) - builder().foo(null).build(); - } + static void test() { + // An error should be issued here, but the code has not been delombok'd. + // If the CF and Lombok are ever able to work in the same invocation of javac + // (i.e. without delomboking first), then this error should be changed back to an + // expected error by re-adding the leading "::". + // error: (assignment.type.incompatible) + builder().foo(null).build(); + } } diff --git a/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java b/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java index 7813d480d7b..8d53b367003 100644 --- a/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java +++ b/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java @@ -3,5 +3,5 @@ import org.jspecify.annotations.Nullable; public class NotNullMarkedBecauseChildPackage { - void foo(NotNullMarkedBecauseChildPackage<@Nullable String> d) {} + void foo(NotNullMarkedBecauseChildPackage<@Nullable String> d) {} } diff --git a/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java b/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java index 0ac4b19cf17..da62455e63b 100644 --- a/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java +++ b/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java @@ -3,6 +3,6 @@ import org.jspecify.annotations.Nullable; public class NullMarkedBecausePackageIs { - // :: error: (type.argument.type.incompatible) - void foo(NullMarkedBecausePackageIs<@Nullable String> d) {} + // :: error: (type.argument.type.incompatible) + void foo(NullMarkedBecausePackageIs<@Nullable String> d) {} } diff --git a/checker/tests/nullness-permitClearProperty/PermitClearProperty.java b/checker/tests/nullness-permitClearProperty/PermitClearProperty.java index 487decaca71..88b073aa11d 100644 --- a/checker/tests/nullness-permitClearProperty/PermitClearProperty.java +++ b/checker/tests/nullness-permitClearProperty/PermitClearProperty.java @@ -1,126 +1,125 @@ // Same code (but different expected errors) as test PreventClearProperty.java . +import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.common.value.qual.StringVal; -import java.util.Properties; - public class PermitClearProperty { - static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; - - static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; - - @NonNull String getLineSeparator1() { - // :: error: (return.type.incompatible) - return System.getProperty("line.separator"); - } - - @NonNull String getLineSeparator2() { - // :: error: (return.type.incompatible) - return System.getProperty(LINE_SEPARATOR); - } - - @NonNull String getMyProperty1() { - // :: error: (return.type.incompatible) - return System.getProperty("my.property.name"); - } - - @NonNull String getMyProperty2() { - // :: error: (return.type.incompatible) - return System.getProperty(MY_PROPERTY_NAME); - } - - @NonNull String getAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.getProperty(propName); - } - - @NonNull String clearLineSeparator1() { - // :: error: (return.type.incompatible) - return System.clearProperty("line.separator"); - } - - @NonNull String clearLineSeparator2() { - // :: error: (return.type.incompatible) - return System.clearProperty(LINE_SEPARATOR); - } - - @NonNull String clearMyProperty1() { - // :: error: (return.type.incompatible) - return System.clearProperty("my.property.name"); - } - - @NonNull String clearMyProperty2() { - // :: error: (return.type.incompatible) - return System.clearProperty(MY_PROPERTY_NAME); - } - - @NonNull String clearAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.clearProperty(propName); - } - - void callSetProperties(Properties p) { - System.setProperties(p); - } - - // All calls to setProperty are legal because they cannot unset a property. - - @NonNull String setLineSeparator1() { - // :: error: (return.type.incompatible) - return System.setProperty("line.separator", "somevalue"); - } - - @NonNull String setLineSeparator2() { - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, "somevalue"); - } - - @NonNull String setMyProperty1() { - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", "somevalue"); - } - - @NonNull String setMyProperty2() { - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, "somevalue"); - } - - @NonNull String setAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.setProperty(propName, "somevalue"); - } - - // These calls to setProperty are illegal because null is not a permitted value. - - @NonNull String setLineSeparatorNull1() { - // :: error: (return.type.incompatible) - // :: error: (argument.type.incompatible) - return System.setProperty("line.separator", null); - } - - @NonNull String setLineSeparatorNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, null); - } - - @NonNull String setMyPropertyNull1() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", null); - } - - @NonNull String setMyPropertyNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, null); - } - - @NonNull String setAPropertyNull(String propName) { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(propName, null); - } + static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; + + static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; + + @NonNull String getLineSeparator1() { + // :: error: (return.type.incompatible) + return System.getProperty("line.separator"); + } + + @NonNull String getLineSeparator2() { + // :: error: (return.type.incompatible) + return System.getProperty(LINE_SEPARATOR); + } + + @NonNull String getMyProperty1() { + // :: error: (return.type.incompatible) + return System.getProperty("my.property.name"); + } + + @NonNull String getMyProperty2() { + // :: error: (return.type.incompatible) + return System.getProperty(MY_PROPERTY_NAME); + } + + @NonNull String getAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.getProperty(propName); + } + + @NonNull String clearLineSeparator1() { + // :: error: (return.type.incompatible) + return System.clearProperty("line.separator"); + } + + @NonNull String clearLineSeparator2() { + // :: error: (return.type.incompatible) + return System.clearProperty(LINE_SEPARATOR); + } + + @NonNull String clearMyProperty1() { + // :: error: (return.type.incompatible) + return System.clearProperty("my.property.name"); + } + + @NonNull String clearMyProperty2() { + // :: error: (return.type.incompatible) + return System.clearProperty(MY_PROPERTY_NAME); + } + + @NonNull String clearAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.clearProperty(propName); + } + + void callSetProperties(Properties p) { + System.setProperties(p); + } + + // All calls to setProperty are legal because they cannot unset a property. + + @NonNull String setLineSeparator1() { + // :: error: (return.type.incompatible) + return System.setProperty("line.separator", "somevalue"); + } + + @NonNull String setLineSeparator2() { + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, "somevalue"); + } + + @NonNull String setMyProperty1() { + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", "somevalue"); + } + + @NonNull String setMyProperty2() { + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, "somevalue"); + } + + @NonNull String setAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.setProperty(propName, "somevalue"); + } + + // These calls to setProperty are illegal because null is not a permitted value. + + @NonNull String setLineSeparatorNull1() { + // :: error: (return.type.incompatible) + // :: error: (argument.type.incompatible) + return System.setProperty("line.separator", null); + } + + @NonNull String setLineSeparatorNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, null); + } + + @NonNull String setMyPropertyNull1() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", null); + } + + @NonNull String setMyPropertyNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, null); + } + + @NonNull String setAPropertyNull(String propName) { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(propName, null); + } } diff --git a/checker/tests/nullness-records/BasicRecord.java b/checker/tests/nullness-records/BasicRecord.java index 0b2c793b76d..44b69654eae 100644 --- a/checker/tests/nullness-records/BasicRecord.java +++ b/checker/tests/nullness-records/BasicRecord.java @@ -3,12 +3,12 @@ // @below-java16-jdk-skip-test public record BasicRecord(String str) { - public static BasicRecord makeNonNull(String s) { - return new BasicRecord(s); - } + public static BasicRecord makeNonNull(String s) { + return new BasicRecord(s); + } - public static BasicRecord makeNull(@Nullable String s) { - // :: error: (argument.type.incompatible) - return new BasicRecord(s); - } + public static BasicRecord makeNull(@Nullable String s) { + // :: error: (argument.type.incompatible) + return new BasicRecord(s); + } } diff --git a/checker/tests/nullness-records/BasicRecordCanon.java b/checker/tests/nullness-records/BasicRecordCanon.java index 6785c803600..ec2f3985614 100644 --- a/checker/tests/nullness-records/BasicRecordCanon.java +++ b/checker/tests/nullness-records/BasicRecordCanon.java @@ -3,14 +3,14 @@ // @below-java16-jdk-skip-test public record BasicRecordCanon(String str) { - public static BasicRecordCanon makeNonNull(String s) { - return new BasicRecordCanon(s); - } + public static BasicRecordCanon makeNonNull(String s) { + return new BasicRecordCanon(s); + } - public static BasicRecordCanon makeNull(@Nullable String s) { - // :: error: (argument.type.incompatible) - return new BasicRecordCanon(s); - } + public static BasicRecordCanon makeNull(@Nullable String s) { + // :: error: (argument.type.incompatible) + return new BasicRecordCanon(s); + } - public BasicRecordCanon {} + public BasicRecordCanon {} } diff --git a/checker/tests/nullness-records/BasicRecordNullable.java b/checker/tests/nullness-records/BasicRecordNullable.java index 1c1064b3e11..91e52ebbfcb 100644 --- a/checker/tests/nullness-records/BasicRecordNullable.java +++ b/checker/tests/nullness-records/BasicRecordNullable.java @@ -3,29 +3,29 @@ // @below-java16-jdk-skip-test public record BasicRecordNullable(@Nullable String str) { - public static BasicRecordNullable makeNonNull(String s) { - return new BasicRecordNullable(s); - } + public static BasicRecordNullable makeNonNull(String s) { + return new BasicRecordNullable(s); + } - public static BasicRecordNullable makeNull(@Nullable String s) { - return new BasicRecordNullable(s); - } + public static BasicRecordNullable makeNull(@Nullable String s) { + return new BasicRecordNullable(s); + } - public @Nullable String getStringFromField() { - return str; - } + public @Nullable String getStringFromField() { + return str; + } - public @Nullable String getStringFromMethod() { - return str(); - } + public @Nullable String getStringFromMethod() { + return str(); + } - public String getStringFromFieldErr() { - // :: error: (return.type.incompatible) - return str; - } + public String getStringFromFieldErr() { + // :: error: (return.type.incompatible) + return str; + } - public String getStringFromMethodErr() { - // :: error: (return.type.incompatible) - return str(); - } + public String getStringFromMethodErr() { + // :: error: (return.type.incompatible) + return str(); + } } diff --git a/checker/tests/nullness-records/DefaultQualRecord.java b/checker/tests/nullness-records/DefaultQualRecord.java index 781c1da2ab3..cd2dfc1ab7f 100644 --- a/checker/tests/nullness-records/DefaultQualRecord.java +++ b/checker/tests/nullness-records/DefaultQualRecord.java @@ -2,61 +2,61 @@ import org.checkerframework.framework.qual.DefaultQualifier; class StandardQualClass { - // :: error: (assignment.type.incompatible) - public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; + // :: error: (assignment.type.incompatible) + public static String s = null; + // :: error: (initialization.static.field.uninitialized) + public static String u; } @DefaultQualifier(Nullable.class) class DefaultQualClass { - public static String s = null; - public static String u; + public static String s = null; + public static String u; } interface StandardQualInterface { - // :: error: (assignment.type.incompatible) - public static String s = null; + // :: error: (assignment.type.incompatible) + public static String s = null; } @DefaultQualifier(Nullable.class) interface DefaultQualInterface { - public static String s = null; + public static String s = null; } enum StandardQualEnum { - DUMMY; - // :: error: (assignment.type.incompatible) - public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; + DUMMY; + // :: error: (assignment.type.incompatible) + public static String s = null; + // :: error: (initialization.static.field.uninitialized) + public static String u; } @DefaultQualifier(Nullable.class) enum DefaultQualEnum { - DUMMY; - public static String s = null; - public static String u; + DUMMY; + public static String s = null; + public static String u; } record StandardQualRecord(String m) { - // :: error: (assignment.type.incompatible) - public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; + // :: error: (assignment.type.incompatible) + public static String s = null; + // :: error: (initialization.static.field.uninitialized) + public static String u; - StandardQualRecord { - // :: error: (assignment.type.incompatible) - m = null; - } + StandardQualRecord { + // :: error: (assignment.type.incompatible) + m = null; + } } @DefaultQualifier(Nullable.class) record DefaultQualRecord(String m) { - public static String s = null; - public static String u; + public static String s = null; + public static String u; - DefaultQualRecord { - m = null; - } + DefaultQualRecord { + m = null; + } } diff --git a/checker/tests/nullness-records/GenericPair.java b/checker/tests/nullness-records/GenericPair.java index 13e5a863ba3..e2955305222 100644 --- a/checker/tests/nullness-records/GenericPair.java +++ b/checker/tests/nullness-records/GenericPair.java @@ -3,9 +3,9 @@ // @below-java16-jdk-skip-test public record GenericPair(K key, V value) { - public static void foo() { - GenericPair p = new GenericPair<>("k", null); - // :: error: (dereference.of.nullable) - p.value().toString(); - } + public static void foo() { + GenericPair p = new GenericPair<>("k", null); + // :: error: (dereference.of.nullable) + p.value().toString(); + } } diff --git a/checker/tests/nullness-records/Issue5200.java b/checker/tests/nullness-records/Issue5200.java index 4044bf61b0a..873021b5547 100644 --- a/checker/tests/nullness-records/Issue5200.java +++ b/checker/tests/nullness-records/Issue5200.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Test { - record Foo(@Nullable String bar) { - @Nullable String baz() { - return Math.random() > 0.5 ? bar : null; - } + record Foo(@Nullable String bar) { + @Nullable String baz() { + return Math.random() > 0.5 ? bar : null; } + } - void main() { - checkEmpty(new Foo("")); - } + void main() { + checkEmpty(new Foo("")); + } - void checkEmpty(Foo foo) { - if (foo.bar() != null && !foo.bar().isEmpty()) { - System.out.println("ok"); - } - // :: error: (dereference.of.nullable) - if (foo.baz() != null && !foo.baz().isEmpty()) { - System.out.println("not ok"); - } + void checkEmpty(Foo foo) { + if (foo.bar() != null && !foo.bar().isEmpty()) { + System.out.println("ok"); + } + // :: error: (dereference.of.nullable) + if (foo.baz() != null && !foo.baz().isEmpty()) { + System.out.println("not ok"); } + } } diff --git a/checker/tests/nullness-records/LocalRecords.java b/checker/tests/nullness-records/LocalRecords.java index 64cf96399da..68f55c8d716 100644 --- a/checker/tests/nullness-records/LocalRecords.java +++ b/checker/tests/nullness-records/LocalRecords.java @@ -2,11 +2,11 @@ // @below-java16-jdk-skip-test public class LocalRecords { - public static void foo() { - record L(String key, @Nullable Integer value) {} - L a = new L("one", 1); - L b = new L("i", null); - // :: error: (argument.type.incompatible) - L c = new L(null, 6); - } + public static void foo() { + record L(String key, @Nullable Integer value) {} + L a = new L("one", 1); + L b = new L("i", null); + // :: error: (argument.type.incompatible) + L c = new L(null, 6); + } } diff --git a/checker/tests/nullness-records/NestedRecordTest.java b/checker/tests/nullness-records/NestedRecordTest.java index bc984708113..51154893298 100644 --- a/checker/tests/nullness-records/NestedRecordTest.java +++ b/checker/tests/nullness-records/NestedRecordTest.java @@ -5,94 +5,94 @@ public class NestedRecordTest { - static @NonNull String nn = "foo"; - static @Nullable String nble = null; - static @NonNull String nn2 = "foo"; - static @Nullable String nble2 = null; + static @NonNull String nn = "foo"; + static @Nullable String nble = null; + static @NonNull String nn2 = "foo"; + static @Nullable String nble2 = null; - public static class Nested { - public record NPerson(String familyName, @Nullable String maidenName) {} + public static class Nested { + public record NPerson(String familyName, @Nullable String maidenName) {} - void nclient() { - Nested.NPerson np1 = new Nested.NPerson(nn, nn); - Nested.NPerson np2 = new Nested.NPerson(nn, nble); - // :: error: (argument.type.incompatible) - Nested.NPerson np3 = new Nested.NPerson(nble, nn); - // :: error: (argument.type.incompatible) - Nested.NPerson np4 = new Nested.NPerson(nble, nble); - Inner.IPerson ip1 = new Inner.IPerson(nn, nn); - Inner.IPerson ip2 = new Inner.IPerson(nn, nble); - // :: error: (argument.type.incompatible) - Inner.IPerson ip3 = new Inner.IPerson(nble, nn); - // :: error: (argument.type.incompatible) - Inner.IPerson ip4 = new Inner.IPerson(nble, nble); + void nclient() { + Nested.NPerson np1 = new Nested.NPerson(nn, nn); + Nested.NPerson np2 = new Nested.NPerson(nn, nble); + // :: error: (argument.type.incompatible) + Nested.NPerson np3 = new Nested.NPerson(nble, nn); + // :: error: (argument.type.incompatible) + Nested.NPerson np4 = new Nested.NPerson(nble, nble); + Inner.IPerson ip1 = new Inner.IPerson(nn, nn); + Inner.IPerson ip2 = new Inner.IPerson(nn, nble); + // :: error: (argument.type.incompatible) + Inner.IPerson ip3 = new Inner.IPerson(nble, nn); + // :: error: (argument.type.incompatible) + Inner.IPerson ip4 = new Inner.IPerson(nble, nble); - nn2 = np2.familyName(); - nble2 = np2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = np2.maidenName(); - nble2 = np2.maidenName(); - nn2 = ip2.familyName(); - nble2 = ip2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = ip2.maidenName(); - nble2 = ip2.maidenName(); - } + nn2 = np2.familyName(); + nble2 = np2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = np2.maidenName(); + nble2 = np2.maidenName(); + nn2 = ip2.familyName(); + nble2 = ip2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = ip2.maidenName(); + nble2 = ip2.maidenName(); } + } - public class Inner { - public record IPerson(String familyName, @Nullable String maidenName) {} + public class Inner { + public record IPerson(String familyName, @Nullable String maidenName) {} - void iclient() { - Nested.NPerson np1 = new Nested.NPerson(nn, nn); - Nested.NPerson np2 = new Nested.NPerson(nn, nble); - // :: error: (argument.type.incompatible) - Nested.NPerson np3 = new Nested.NPerson(nble, nn); - // :: error: (argument.type.incompatible) - Nested.NPerson np4 = new Nested.NPerson(nble, nble); - Inner.IPerson ip1 = new Inner.IPerson(nn, nn); - Inner.IPerson ip2 = new Inner.IPerson(nn, nble); - // :: error: (argument.type.incompatible) - Inner.IPerson ip3 = new Inner.IPerson(nble, nn); - // :: error: (argument.type.incompatible) - Inner.IPerson ip4 = new Inner.IPerson(nble, nble); + void iclient() { + Nested.NPerson np1 = new Nested.NPerson(nn, nn); + Nested.NPerson np2 = new Nested.NPerson(nn, nble); + // :: error: (argument.type.incompatible) + Nested.NPerson np3 = new Nested.NPerson(nble, nn); + // :: error: (argument.type.incompatible) + Nested.NPerson np4 = new Nested.NPerson(nble, nble); + Inner.IPerson ip1 = new Inner.IPerson(nn, nn); + Inner.IPerson ip2 = new Inner.IPerson(nn, nble); + // :: error: (argument.type.incompatible) + Inner.IPerson ip3 = new Inner.IPerson(nble, nn); + // :: error: (argument.type.incompatible) + Inner.IPerson ip4 = new Inner.IPerson(nble, nble); - nn2 = np2.familyName(); - nble2 = np2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = np2.maidenName(); - nble2 = np2.maidenName(); - nn2 = ip2.familyName(); - nble2 = ip2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = ip2.maidenName(); - nble2 = ip2.maidenName(); - } + nn2 = np2.familyName(); + nble2 = np2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = np2.maidenName(); + nble2 = np2.maidenName(); + nn2 = ip2.familyName(); + nble2 = ip2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = ip2.maidenName(); + nble2 = ip2.maidenName(); } + } - void client() { - Nested.NPerson np1 = new Nested.NPerson(nn, nn); - Nested.NPerson np2 = new Nested.NPerson(nn, nble); - // :: error: (argument.type.incompatible) - Nested.NPerson np3 = new Nested.NPerson(nble, nn); - // :: error: (argument.type.incompatible) - Nested.NPerson np4 = new Nested.NPerson(nble, nble); - Inner.IPerson ip1 = new Inner.IPerson(nn, nn); - Inner.IPerson ip2 = new Inner.IPerson(nn, nble); - // :: error: (argument.type.incompatible) - Inner.IPerson ip3 = new Inner.IPerson(nble, nn); - // :: error: (argument.type.incompatible) - Inner.IPerson ip4 = new Inner.IPerson(nble, nble); + void client() { + Nested.NPerson np1 = new Nested.NPerson(nn, nn); + Nested.NPerson np2 = new Nested.NPerson(nn, nble); + // :: error: (argument.type.incompatible) + Nested.NPerson np3 = new Nested.NPerson(nble, nn); + // :: error: (argument.type.incompatible) + Nested.NPerson np4 = new Nested.NPerson(nble, nble); + Inner.IPerson ip1 = new Inner.IPerson(nn, nn); + Inner.IPerson ip2 = new Inner.IPerson(nn, nble); + // :: error: (argument.type.incompatible) + Inner.IPerson ip3 = new Inner.IPerson(nble, nn); + // :: error: (argument.type.incompatible) + Inner.IPerson ip4 = new Inner.IPerson(nble, nble); - nn2 = np2.familyName(); - nble2 = np2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = np2.maidenName(); - nble2 = np2.maidenName(); - nn2 = ip2.familyName(); - nble2 = ip2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = ip2.maidenName(); - nble2 = ip2.maidenName(); - } + nn2 = np2.familyName(); + nble2 = np2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = np2.maidenName(); + nble2 = np2.maidenName(); + nn2 = ip2.familyName(); + nble2 = ip2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = ip2.maidenName(); + nble2 = ip2.maidenName(); + } } diff --git a/checker/tests/nullness-records/NormalizingRecord.java b/checker/tests/nullness-records/NormalizingRecord.java index f15b7c434c8..664f4b51b27 100644 --- a/checker/tests/nullness-records/NormalizingRecord.java +++ b/checker/tests/nullness-records/NormalizingRecord.java @@ -9,52 +9,52 @@ public class NormalizingRecord {} // Framework. record NormalizingRecord1(@Nullable String s) { - NormalizingRecord1(String s) { - if (s.equals("")) { - this.s = null; - } else { - this.s = s; - } + NormalizingRecord1(String s) { + if (s.equals("")) { + this.s = null; + } else { + this.s = s; } + } } record NormalizingRecord2(String s) { - NormalizingRecord2(@Nullable String s) { - if (s == null) { - s = ""; - } - this.s = s; + NormalizingRecord2(@Nullable String s) { + if (s == null) { + s = ""; } + this.s = s; + } } record NormalizingRecordIllegalConstructor1(String s) { - NormalizingRecordIllegalConstructor1(@Nullable String s) { - // :: error: (assignment.type.incompatible) - this.s = s; - } + NormalizingRecordIllegalConstructor1(@Nullable String s) { + // :: error: (assignment.type.incompatible) + this.s = s; + } } record NormalizingRecordIllegalConstructor2(@Nullable String s) { - NormalizingRecordIllegalConstructor2(String s) { - if (s.equals("")) { - // The formal parametr type is @NonNull, so this assignment to it is illegal. - // :: error: (assignment.type.incompatible) - s = null; - } - this.s = s; + NormalizingRecordIllegalConstructor2(String s) { + if (s.equals("")) { + // The formal parametr type is @NonNull, so this assignment to it is illegal. + // :: error: (assignment.type.incompatible) + s = null; } + this.s = s; + } } class Client { - // :: error: (argument.type.incompatible) - NormalizingRecord1 nr1_1 = new NormalizingRecord1(null); - NormalizingRecord1 nr1_2 = new NormalizingRecord1(""); - NormalizingRecord1 nr1_3 = new NormalizingRecord1("hello"); - @Nullable String nble = nr1_2.s(); + // :: error: (argument.type.incompatible) + NormalizingRecord1 nr1_1 = new NormalizingRecord1(null); + NormalizingRecord1 nr1_2 = new NormalizingRecord1(""); + NormalizingRecord1 nr1_3 = new NormalizingRecord1("hello"); + @Nullable String nble = nr1_2.s(); - NormalizingRecord2 nr2_1 = new NormalizingRecord2(null); - NormalizingRecord2 nr2_2 = new NormalizingRecord2(""); - NormalizingRecord2 nr2_3 = new NormalizingRecord2("hello"); - @NonNull String nn = nr2_1.s(); + NormalizingRecord2 nr2_1 = new NormalizingRecord2(null); + NormalizingRecord2 nr2_2 = new NormalizingRecord2(""); + NormalizingRecord2 nr2_3 = new NormalizingRecord2("hello"); + @NonNull String nn = nr2_1.s(); } diff --git a/checker/tests/nullness-records/RecordPurity.java b/checker/tests/nullness-records/RecordPurity.java index f6a866d10f3..83be0b5d06d 100644 --- a/checker/tests/nullness-records/RecordPurity.java +++ b/checker/tests/nullness-records/RecordPurity.java @@ -3,90 +3,90 @@ // @below-java17-jdk-skip-test record RecordPurity(@Nullable String first, @Nullable String second) { - public String checkNullnessOfFields() { - // :: error: (dereference.of.nullable) - return first.toString() + " " + second.toString(); - } - - public String checkNullnessOfAccessors() { - // :: error: (dereference.of.nullable) - return first().toString() + " " + second().toString(); - } - - public String checkPurityOfFields() { - if (first == null || second == null) return ""; - else return "" + first.length() + second.length(); - } - - public static String checkPurityOfDefaultAccessor(RecordPurity r) { - if (r.first() == null || r.second() == null) return ""; - else return "" + r.first().length() + " " + r.second().length(); - } - - public String checkPurityOfDefaultAccessorSelf() { - if (first() == null || second() == null) return ""; - else return "" + first().length() + " " + second().length(); - } - - public String checkPurityOfDefaultAccessorSelf2() { - if (first() == null) return ""; - if (second() == null) return ""; - - return "" + first().length() + " " + second().length(); - } - - public String checkPurityOfDefaultAccessorSelfFirst() { - if (first() == null) return ""; - else return "" + "".length() + first().length(); - } - - public String checkPurityOfDefaultAccessorSelfFirst2() { - if (first() == null) return ""; - else return "" + "".length() + first().length() + first().length(); - } - - @Pure - public @Nullable String pureMethod() { - return ""; - } - - @Pure - public @Nullable String pureMethod2() { - return null; - } - - public String checkPurityOfAccessor4() { - if (pureMethod() == null) return ""; - else return "" + pureMethod().toString(); - } - - public String checkPurityOfAccessor5() { - if (pureMethod() == null || pureMethod2() == null) return ""; - else return "" + pureMethod().length() + pureMethod2().length(); - } - - // An unrelated non-pure method of same name: - public @Nullable String first(java.util.List ss) { - return ss.isEmpty() ? null : ss.get(0); - } - - // An unrelated pure method of same name: - @Pure - public @Nullable String second(java.util.List ss) { - return ss.isEmpty() ? null : ss.get(1); - } - - public String checkPurityOfImpureMethod() { - java.util.List ss = java.util.List.of(); - if (first(ss) == null) return ""; - else - // :: error: (dereference.of.nullable) - return "" + "".length() + first(ss).length(); - } - - public String checkPurityOfPureMethod() { - java.util.List ss = java.util.List.of(); - if (second(ss) == null) return ""; - else return "" + "".length() + second(ss).length(); - } + public String checkNullnessOfFields() { + // :: error: (dereference.of.nullable) + return first.toString() + " " + second.toString(); + } + + public String checkNullnessOfAccessors() { + // :: error: (dereference.of.nullable) + return first().toString() + " " + second().toString(); + } + + public String checkPurityOfFields() { + if (first == null || second == null) return ""; + else return "" + first.length() + second.length(); + } + + public static String checkPurityOfDefaultAccessor(RecordPurity r) { + if (r.first() == null || r.second() == null) return ""; + else return "" + r.first().length() + " " + r.second().length(); + } + + public String checkPurityOfDefaultAccessorSelf() { + if (first() == null || second() == null) return ""; + else return "" + first().length() + " " + second().length(); + } + + public String checkPurityOfDefaultAccessorSelf2() { + if (first() == null) return ""; + if (second() == null) return ""; + + return "" + first().length() + " " + second().length(); + } + + public String checkPurityOfDefaultAccessorSelfFirst() { + if (first() == null) return ""; + else return "" + "".length() + first().length(); + } + + public String checkPurityOfDefaultAccessorSelfFirst2() { + if (first() == null) return ""; + else return "" + "".length() + first().length() + first().length(); + } + + @Pure + public @Nullable String pureMethod() { + return ""; + } + + @Pure + public @Nullable String pureMethod2() { + return null; + } + + public String checkPurityOfAccessor4() { + if (pureMethod() == null) return ""; + else return "" + pureMethod().toString(); + } + + public String checkPurityOfAccessor5() { + if (pureMethod() == null || pureMethod2() == null) return ""; + else return "" + pureMethod().length() + pureMethod2().length(); + } + + // An unrelated non-pure method of same name: + public @Nullable String first(java.util.List ss) { + return ss.isEmpty() ? null : ss.get(0); + } + + // An unrelated pure method of same name: + @Pure + public @Nullable String second(java.util.List ss) { + return ss.isEmpty() ? null : ss.get(1); + } + + public String checkPurityOfImpureMethod() { + java.util.List ss = java.util.List.of(); + if (first(ss) == null) return ""; + else + // :: error: (dereference.of.nullable) + return "" + "".length() + first(ss).length(); + } + + public String checkPurityOfPureMethod() { + java.util.List ss = java.util.List.of(); + if (second(ss) == null) return ""; + else return "" + "".length() + second(ss).length(); + } } diff --git a/checker/tests/nullness-records/RecordPurityGeneric.java b/checker/tests/nullness-records/RecordPurityGeneric.java index e139599a8b0..be951f9691d 100644 --- a/checker/tests/nullness-records/RecordPurityGeneric.java +++ b/checker/tests/nullness-records/RecordPurityGeneric.java @@ -2,46 +2,46 @@ // @below-java17-jdk-skip-test record RecordPurityGeneric(A a, B b) { - public String checkNullnessOfFields() { - // :: error: (dereference.of.nullable) - return a.toString() + " " + b.toString(); - } - - public String checkNullnessOfAccessors() { - // :: error: (dereference.of.nullable) - return a().toString() + " " + b().toString(); - } - - public static String checkNullnessOfFields( - RecordPurityGeneric<@Nullable String, @Nullable String> r) { - // :: error: (dereference.of.nullable) - return r.a.toString() + " " + r.b.toString(); - } - - public static String checkNullnessOfAccessors( - RecordPurityGeneric<@Nullable String, @Nullable String> r) { - // :: error: (dereference.of.nullable) - return r.a().toString() + " " + r.b().toString(); - } - - public String checkPurityOfFields() { - if (a == null || b == null) return ""; - else return a.toString() + " " + b.toString(); - } - - public String checkPurityOfFields(RecordPurityGeneric<@Nullable String, @Nullable String> r) { - if (r.a == null || r.b == null) return ""; - else return r.a.toString() + " " + r.b.toString(); - } - - public static String checkPurityOfDefaultAccessor( - RecordPurityGeneric<@Nullable String, @Nullable String> r) { - if (r.a() == null || r.b() == null) return ""; - else return r.a().toString() + " " + r.b().toString(); - } - - public String checkPurityOfDefaultAccessorSelf() { - if (a() == null || b() == null) return ""; - else return a().toString() + " " + b().toString(); - } + public String checkNullnessOfFields() { + // :: error: (dereference.of.nullable) + return a.toString() + " " + b.toString(); + } + + public String checkNullnessOfAccessors() { + // :: error: (dereference.of.nullable) + return a().toString() + " " + b().toString(); + } + + public static String checkNullnessOfFields( + RecordPurityGeneric<@Nullable String, @Nullable String> r) { + // :: error: (dereference.of.nullable) + return r.a.toString() + " " + r.b.toString(); + } + + public static String checkNullnessOfAccessors( + RecordPurityGeneric<@Nullable String, @Nullable String> r) { + // :: error: (dereference.of.nullable) + return r.a().toString() + " " + r.b().toString(); + } + + public String checkPurityOfFields() { + if (a == null || b == null) return ""; + else return a.toString() + " " + b.toString(); + } + + public String checkPurityOfFields(RecordPurityGeneric<@Nullable String, @Nullable String> r) { + if (r.a == null || r.b == null) return ""; + else return r.a.toString() + " " + r.b.toString(); + } + + public static String checkPurityOfDefaultAccessor( + RecordPurityGeneric<@Nullable String, @Nullable String> r) { + if (r.a() == null || r.b() == null) return ""; + else return r.a().toString() + " " + r.b().toString(); + } + + public String checkPurityOfDefaultAccessorSelf() { + if (a() == null || b() == null) return ""; + else return a().toString() + " " + b().toString(); + } } diff --git a/checker/tests/nullness-records/RecordPurityOverride.java b/checker/tests/nullness-records/RecordPurityOverride.java index 422ef1b4fd7..d372cf04619 100644 --- a/checker/tests/nullness-records/RecordPurityOverride.java +++ b/checker/tests/nullness-records/RecordPurityOverride.java @@ -2,37 +2,37 @@ import org.checkerframework.dataflow.qual.Pure; record RecordPurityOverride(@Nullable String pure, @Nullable String impure) { - @Pure - public @Nullable String pure() { - return pure; - } + @Pure + public @Nullable String pure() { + return pure; + } - // Note: not @Pure - public @Nullable String impure() { - return impure; - } + // Note: not @Pure + public @Nullable String impure() { + return impure; + } - public String checkPurityOfFields() { - if (pure == null || impure == null) return ""; - else return pure.toString() + " " + impure.toString(); - } + public String checkPurityOfFields() { + if (pure == null || impure == null) return ""; + else return pure.toString() + " " + impure.toString(); + } - public String checkPurityOfAccessor1() { - if (pure() == null || impure() == null) return ""; - else - // :: error: (dereference.of.nullable) - return pure().toString() + " " + impure().toString(); - } + public String checkPurityOfAccessor1() { + if (pure() == null || impure() == null) return ""; + else + // :: error: (dereference.of.nullable) + return pure().toString() + " " + impure().toString(); + } - public String checkPurityOfAccessor2() { - if (pure() == null) return ""; - else return pure().toString(); - } + public String checkPurityOfAccessor2() { + if (pure() == null) return ""; + else return pure().toString(); + } - public String checkPurityOfAccessor3() { - if (impure() == null) return ""; - else - // :: error: (dereference.of.nullable) - return impure().toString(); - } + public String checkPurityOfAccessor3() { + if (impure() == null) return ""; + else + // :: error: (dereference.of.nullable) + return impure().toString(); + } } diff --git a/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java b/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java index efe11600cbf..a900a84eeca 100644 --- a/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java +++ b/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java @@ -1,34 +1,33 @@ -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.common.reflection.qual.MethodVal; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.reflection.qual.MethodVal; /** Example used in the reflection resolution section of the Checker Framework manual. */ public class NullnessReflectionExampleTest { - @NonNull Location getCurrentLocation() { - // ... - return new Location(); - } + @NonNull Location getCurrentLocation() { + // ... + return new Location(); + } - String getCurrentCity() - throws NoSuchMethodException, - SecurityException, - IllegalAccessException, - IllegalArgumentException, - InvocationTargetException { - @MethodVal( - className = "NullnessReflectionExampleTest", - methodName = "getCurrentLocation", - params = 0) - Method toLowerCase = getClass().getMethod("getCurrentLocation"); - Location currentLocation = (Location) toLowerCase.invoke(this); - return currentLocation.nameOfCity(); - } + String getCurrentCity() + throws NoSuchMethodException, + SecurityException, + IllegalAccessException, + IllegalArgumentException, + InvocationTargetException { + @MethodVal( + className = "NullnessReflectionExampleTest", + methodName = "getCurrentLocation", + params = 0) + Method toLowerCase = getClass().getMethod("getCurrentLocation"); + Location currentLocation = (Location) toLowerCase.invoke(this); + return currentLocation.nameOfCity(); + } } class Location { - String nameOfCity() { - return "Seattle"; - } + String nameOfCity() { + return "Seattle"; + } } diff --git a/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java b/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java index 34c011a5fe2..e5d55c865f9 100644 --- a/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java +++ b/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java @@ -1,50 +1,49 @@ +import java.lang.reflect.Method; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.reflection.qual.MethodVal; -import java.lang.reflect.Method; - /** Testing that reflection resolution uses more precise annotations for the Nullness Checker. */ public class NullnessReflectionResolutionTest { - @NonNull Object returnNonNull() { - return new Object(); - } + @NonNull Object returnNonNull() { + return new Object(); + } - void testReturnNonNull( - @MethodVal( - className = "NullnessReflectionResolutionTest", - methodName = "returnNonNull", - params = 0) - Method m) - throws Exception { - @NonNull Object o = m.invoke(this); - } + void testReturnNonNull( + @MethodVal( + className = "NullnessReflectionResolutionTest", + methodName = "returnNonNull", + params = 0) + Method m) + throws Exception { + @NonNull Object o = m.invoke(this); + } - void paramNullable(@Nullable Object param1, @Nullable Object param2) {} + void paramNullable(@Nullable Object param1, @Nullable Object param2) {} - void testParamNullable( - @MethodVal( - className = "NullnessReflectionResolutionTest", - methodName = "paramNullable", - params = 2) - Method m) - throws Exception { - @NonNull Object o = m.invoke(this, null, null); - } + void testParamNullable( + @MethodVal( + className = "NullnessReflectionResolutionTest", + methodName = "paramNullable", + params = 2) + Method m) + throws Exception { + @NonNull Object o = m.invoke(this, null, null); + } - static @NonNull Object paramAndReturnNonNullStatic( - @Nullable Object param1, @Nullable Object param2) { - return new Object(); - } + static @NonNull Object paramAndReturnNonNullStatic( + @Nullable Object param1, @Nullable Object param2) { + return new Object(); + } - void testParamAndReturnNonNullStatic( - @MethodVal( - className = "NullnessReflectionResolutionTest", - methodName = "paramAndReturnNonNullStatic", - params = 2) - Method m) - throws Exception { - @NonNull Object o1 = m.invoke(this, null, null); - @NonNull Object o2 = m.invoke(null, null, null); - } + void testParamAndReturnNonNullStatic( + @MethodVal( + className = "NullnessReflectionResolutionTest", + methodName = "paramAndReturnNonNullStatic", + params = 2) + Method m) + throws Exception { + @NonNull Object o1 = m.invoke(this, null, null); + @NonNull Object o2 = m.invoke(null, null, null); + } } diff --git a/checker/tests/nullness-reflection/VoidTest.java b/checker/tests/nullness-reflection/VoidTest.java index a6d88928b06..663612d0f04 100644 --- a/checker/tests/nullness-reflection/VoidTest.java +++ b/checker/tests/nullness-reflection/VoidTest.java @@ -1,6 +1,6 @@ public class VoidTest { - Class test() { - return void.class; - } + Class test() { + return void.class; + } } diff --git a/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java b/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java index 8fd522c1d31..976143529bf 100644 --- a/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java +++ b/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java @@ -1,16 +1,15 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; // There should be no warnings for the following operations // if the annotated JDK is loaded properly public class AnnotatedJdkTest { - String toStringTest(Object v) { - return v.toString(); - } + String toStringTest(Object v) { + return v.toString(); + } - Set<@KeyFor("#1") String> keySetTest(HashMap map) { - return map.keySet(); - } + Set<@KeyFor("#1") String> keySetTest(HashMap map) { + return map.keySet(); + } } diff --git a/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java b/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java index d5f02f2810d..7dd3163a004 100644 --- a/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java +++ b/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java @@ -4,27 +4,27 @@ public final class ArraysMDE { - public static int indexOf(Object[] a, Object[] sub) { - int a_index_max = a.length - sub.length + 1; - for (int i = 0; i <= a_index_max; i++) { - if (isSubarray(a, sub, i)) { - return i; - } - } - return -1; + public static int indexOf(Object[] a, Object[] sub) { + int a_index_max = a.length - sub.length + 1; + for (int i = 0; i <= a_index_max; i++) { + if (isSubarray(a, sub, i)) { + return i; + } } + return -1; + } - public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { - int a_len = a.length - a_offset; - int sub_len = sub.length; - if (a_len < sub_len) { - return false; - } - for (int i = 0; i < sub_len; i++) { - if (!Objects.equals(sub[i], a[a_offset + i])) { - return false; - } - } - return true; + public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { + int a_len = a.length - a_offset; + int sub_len = sub.length; + if (a_len < sub_len) { + return false; } + for (int i = 0; i < sub_len; i++) { + if (!Objects.equals(sub[i], a[a_offset + i])) { + return false; + } + } + return true; + } } diff --git a/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java b/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java index b2265edc9aa..e636d06894e 100644 --- a/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java +++ b/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java @@ -4,24 +4,24 @@ // affect defaulting nor suppress errors in source code. public class BytecodeDefaultsTest { - void f() { - g(""); - } + void f() { + g(""); + } - void g(String s) {} + void g(String s) {} } @AnnotatedFor("nullness") class HasErrors { - Object f() { - // :: error: (return.type.incompatible) - return null; - } + Object f() { + // :: error: (return.type.incompatible) + return null; + } } class HasErrors2 { - Object f() { - // :: error: (return.type.incompatible) - return null; - } + Object f() { + // :: error: (return.type.incompatible) + return null; + } } diff --git a/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java b/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java index a12e7ebc23a..c9dc12b87c9 100644 --- a/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java +++ b/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java @@ -7,44 +7,44 @@ @AnnotatedFor("nullness") public class BasicSafeDefaultsTest { - void m1() { - @NonNull Object x1 = SdfuscLib.unannotated(); - // :: error: (assignment.type.incompatible) - @NonNull Object x2 = SdfuscLib.returnsNullable(); - @NonNull Object x3 = SdfuscLib.returnsNonNull(); - // :: error: (assignment.type.incompatible) - @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - // :: error: (assignment.type.incompatible) - @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m1() { + @NonNull Object x1 = SdfuscLib.unannotated(); + // :: error: (assignment.type.incompatible) + @NonNull Object x2 = SdfuscLib.returnsNullable(); + @NonNull Object x3 = SdfuscLib.returnsNonNull(); + // :: error: (assignment.type.incompatible) + @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + // :: error: (assignment.type.incompatible) + @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } - void m2() { - @Nullable Object x1 = SdfuscLib.unannotated(); - @Nullable Object x2 = SdfuscLib.returnsNullable(); - @Nullable Object x3 = SdfuscLib.returnsNonNull(); - @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m2() { + @Nullable Object x1 = SdfuscLib.unannotated(); + @Nullable Object x2 = SdfuscLib.returnsNullable(); + @Nullable Object x3 = SdfuscLib.returnsNonNull(); + @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } } class BasicTestNotAnnotatedFor { - void m1() { - @NonNull Object x1 = SdfuscLib.unannotated(); - @NonNull Object x2 = SdfuscLib.returnsNullable(); - @NonNull Object x3 = SdfuscLib.returnsNonNull(); - @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m1() { + @NonNull Object x1 = SdfuscLib.unannotated(); + @NonNull Object x2 = SdfuscLib.returnsNullable(); + @NonNull Object x3 = SdfuscLib.returnsNonNull(); + @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } - void m2() { - @Nullable Object x1 = SdfuscLib.unannotated(); - @Nullable Object x2 = SdfuscLib.returnsNullable(); - @Nullable Object x3 = SdfuscLib.returnsNonNull(); - @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m2() { + @Nullable Object x1 = SdfuscLib.unannotated(); + @Nullable Object x2 = SdfuscLib.returnsNullable(); + @Nullable Object x3 = SdfuscLib.returnsNonNull(); + @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } } diff --git a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java index 417721ecb0a..b334898d2c9 100644 --- a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java +++ b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java @@ -5,11 +5,11 @@ @AnnotatedFor("nullness") public class Issue3449 { - int length; - Object[] objs; + int length; + Object[] objs; - public Issue3449(Object... args) { - length = args.length; - objs = args; - } + public Issue3449(Object... args) { + length = args.length; + objs = args; + } } diff --git a/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java b/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java index 1d70bcd18c4..a2d2be0f170 100644 --- a/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java +++ b/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java @@ -3,31 +3,31 @@ @AnnotatedFor("nullness") public class PrimitiveClassLiteral { - private static @Nullable Class unwrapPrimitive(Class c) { - if (c == Byte.class) { - return byte.class; - } - if (c == Character.class) { - return char.class; - } - if (c == Short.class) { - return short.class; - } - if (c == Integer.class) { - return int.class; - } - if (c == Long.class) { - return long.class; - } - if (c == Float.class) { - return float.class; - } - if (c == Double.class) { - return double.class; - } - if (c == Boolean.class) { - return boolean.class; - } - return c; + private static @Nullable Class unwrapPrimitive(Class c) { + if (c == Byte.class) { + return byte.class; } + if (c == Character.class) { + return char.class; + } + if (c == Short.class) { + return short.class; + } + if (c == Integer.class) { + return int.class; + } + if (c == Long.class) { + return long.class; + } + if (c == Float.class) { + return float.class; + } + if (c == Double.class) { + return double.class; + } + if (c == Boolean.class) { + return boolean.class; + } + return c; + } } diff --git a/checker/tests/nullness-safedefaultssourcecodelib/Lib.java b/checker/tests/nullness-safedefaultssourcecodelib/Lib.java index a6b2801d7e5..330bd55c562 100644 --- a/checker/tests/nullness-safedefaultssourcecodelib/Lib.java +++ b/checker/tests/nullness-safedefaultssourcecodelib/Lib.java @@ -7,29 +7,29 @@ @AnnotatedFor("nullness") class SdfuscLib { - static Object unannotated() { - return new Object(); - } + static Object unannotated() { + return new Object(); + } - static @Nullable Object returnsNullable() { - return new Object(); - } + static @Nullable Object returnsNullable() { + return new Object(); + } - static @NonNull Object returnsNonNull() { - return new Object(); - } + static @NonNull Object returnsNonNull() { + return new Object(); + } } class SdfuscLibNotAnnotatedFor { - static Object unannotated() { - return new Object(); - } + static Object unannotated() { + return new Object(); + } - static @Nullable Object returnsNullable() { - return new Object(); - } + static @Nullable Object returnsNullable() { + return new Object(); + } - static @NonNull Object returnsNonNull() { - return new Object(); - } + static @NonNull Object returnsNonNull() { + return new Object(); + } } diff --git a/checker/tests/nullness-skipdefs/SkipDefs1.java b/checker/tests/nullness-skipdefs/SkipDefs1.java index ba0320c7cbd..ed8329fa066 100644 --- a/checker/tests/nullness-skipdefs/SkipDefs1.java +++ b/checker/tests/nullness-skipdefs/SkipDefs1.java @@ -2,16 +2,16 @@ public class SkipDefs1 { - static class SkipMe { - static Object foo() { - return null; - } + static class SkipMe { + static Object foo() { + return null; } + } - static class DontSkip { - static Object foo() { - // :: error: (return.type.incompatible) - return null; - } + static class DontSkip { + static Object foo() { + // :: error: (return.type.incompatible) + return null; } + } } diff --git a/checker/tests/nullness-skipdefs/SkipDefs2.java b/checker/tests/nullness-skipdefs/SkipDefs2.java index da7c72e580e..3246aaa095e 100644 --- a/checker/tests/nullness-skipdefs/SkipDefs2.java +++ b/checker/tests/nullness-skipdefs/SkipDefs2.java @@ -2,17 +2,17 @@ public class SkipDefs2 { - static class SkipMe { - @Nullable Object f; + static class SkipMe { + @Nullable Object f; - @EnsuresNonNull("f") - static void foo() {} - } + @EnsuresNonNull("f") + static void foo() {} + } - static class DontSkip { - static Object foo() { - // :: error: (return.type.incompatible) - return null; - } + static class DontSkip { + static Object foo() { + // :: error: (return.type.incompatible) + return null; } + } } diff --git a/checker/tests/nullness-skipuses/SkipUses1.java b/checker/tests/nullness-skipuses/SkipUses1.java index 24d71768742..4ef6a0ae8ec 100644 --- a/checker/tests/nullness-skipuses/SkipUses1.java +++ b/checker/tests/nullness-skipuses/SkipUses1.java @@ -2,26 +2,26 @@ public class SkipUses1 { - static class SkipMe { - static @Nullable Object foo() { - return null; - } + static class SkipMe { + static @Nullable Object foo() { + return null; } + } - static class DontSkip { - static @Nullable Object foo() { - return null; - } + static class DontSkip { + static @Nullable Object foo() { + return null; } + } - static class Main { - void bar(boolean b) { - @NonNull Object x = SkipMe.foo(); - // :: error: (assignment.type.incompatible) - @NonNull Object y = DontSkip.foo(); + static class Main { + void bar(boolean b) { + @NonNull Object x = SkipMe.foo(); + // :: error: (assignment.type.incompatible) + @NonNull Object y = DontSkip.foo(); - // :: error: (assignment.type.incompatible) - @NonNull Object z = b ? SkipMe.foo() : DontSkip.foo(); - } + // :: error: (assignment.type.incompatible) + @NonNull Object z = b ? SkipMe.foo() : DontSkip.foo(); } + } } diff --git a/checker/tests/nullness-skipuses/SkipUses2.java b/checker/tests/nullness-skipuses/SkipUses2.java index dd12f4c8cd8..9c2a5ff5a52 100644 --- a/checker/tests/nullness-skipuses/SkipUses2.java +++ b/checker/tests/nullness-skipuses/SkipUses2.java @@ -3,29 +3,29 @@ public class SkipUses2 { - static class SkipMe { - static @Nullable Object f; + static class SkipMe { + static @Nullable Object f; - @RequiresNonNull("f") - static void foo() {} - } + @RequiresNonNull("f") + static void foo() {} + } - static class DontSkip { - static @Nullable Object f; + static class DontSkip { + static @Nullable Object f; - @RequiresNonNull("f") - static @Nullable Object foo() { - return null; - } + @RequiresNonNull("f") + static @Nullable Object foo() { + return null; } + } - static class Main { - void bar(boolean b) { - SkipMe.f = null; - SkipMe.foo(); - DontSkip.f = null; - // :: error: (contracts.precondition.not.satisfied) - DontSkip.foo(); - } + static class Main { + void bar(boolean b) { + SkipMe.f = null; + SkipMe.foo(); + DontSkip.f = null; + // :: error: (contracts.precondition.not.satisfied) + DontSkip.foo(); } + } } diff --git a/checker/tests/nullness-stubfile/Issue4598.java b/checker/tests/nullness-stubfile/Issue4598.java index 3b4d568d5e5..a5ab401f384 100644 --- a/checker/tests/nullness-stubfile/Issue4598.java +++ b/checker/tests/nullness-stubfile/Issue4598.java @@ -1,16 +1,15 @@ // Test case for Issue #4598 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue4598 { - final @Nullable Object d = null; + final @Nullable Object d = null; - public Object foo() { - Objects.requireNonNull(d, "destination"); - // :: error: (return.type.incompatible) - return d; - } + public Object foo() { + Objects.requireNonNull(d, "destination"); + // :: error: (return.type.incompatible) + return d; + } } diff --git a/checker/tests/nullness-stubfile/NullnessStubfileMerge.java b/checker/tests/nullness-stubfile/NullnessStubfileMerge.java index 6139ac75eb8..b1aec1cb611 100644 --- a/checker/tests/nullness-stubfile/NullnessStubfileMerge.java +++ b/checker/tests/nullness-stubfile/NullnessStubfileMerge.java @@ -22,26 +22,26 @@ public final class String { } */ public class NullnessStubfileMerge { - @Nullable String nullString = null; - @NonNull String nonNull = "Hello!"; + @Nullable String nullString = null; + @NonNull String nonNull = "Hello!"; - void method() { - // below fails because of stub file overruling annotated JDK - // :: error: (type.argument.type.incompatible) - java.util.List<@NonNull String> l; + void method() { + // below fails because of stub file overruling annotated JDK + // :: error: (type.argument.type.incompatible) + java.util.List<@NonNull String> l; - // :: error: (assignment.type.incompatible) - @NonNull String error1 = nonNull.intern(); + // :: error: (assignment.type.incompatible) + @NonNull String error1 = nonNull.intern(); - nonNull.substring('!'); + nonNull.substring('!'); - @NonNull String y = nonNull.substring('!'); + @NonNull String y = nonNull.substring('!'); - char[] nonNullChars = {'1', '1'}; - char[] nullChars = null; - nonNull.getChars(1, 1, nonNullChars, 1); + char[] nonNullChars = {'1', '1'}; + char[] nullChars = null; + nonNull.getChars(1, 1, nonNullChars, 1); - // :: error: (argument.type.incompatible) - nonNull.getChars(1, 1, nullChars, 1); - } + // :: error: (argument.type.incompatible) + nonNull.getChars(1, 1, nullChars, 1); + } } diff --git a/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java b/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java index 1f04a655db4..08e27d7111f 100644 --- a/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java +++ b/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class AnnoOnTypeVariableCrashCase { - @Nullable T test() { - return (@Nullable T) null; - } + @Nullable T test() { + return (@Nullable T) null; + } } diff --git a/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java b/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java index 1f541b09391..d1377fb53ec 100644 --- a/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java +++ b/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java @@ -5,15 +5,15 @@ @DefaultQualifier(Nullable.class) public class RedundantAnnoWithDefaultQualifier { - // :: warning: (redundant.anno) - void foo(@Nullable String message) {} + // :: warning: (redundant.anno) + void foo(@Nullable String message) {} - // :: warning: (redundant.anno) - @Nullable Integer foo() { - return 5; - } + // :: warning: (redundant.anno) + @Nullable Integer foo() { + return 5; + } - void bar(String p) {} + void bar(String p) {} - void baz(@NonNull String p) {} + void baz(@NonNull String p) {} } diff --git a/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java b/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java index c7cc62fe006..be9c3747887 100644 --- a/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java +++ b/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java @@ -1,7 +1,6 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.io.InputStream; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; /* Check for redundant annotations in the following locations @@ -27,65 +26,65 @@ */ @NonNull class RedundantAnnotation< - // TODO :: warning: (redundant.anno) - T extends @Nullable Object> { - - enum InnerEnum { - // TODO :: warning: (redundant.anno) - // :: error: (nullness.on.enum) - @NonNull EXPLICIT, - IMPLICIT, - } + // TODO :: warning: (redundant.anno) + T extends @Nullable Object> { - // :: warning: (redundant.anno) - @NonNull Object f; + enum InnerEnum { + // TODO :: warning: (redundant.anno) + // :: error: (nullness.on.enum) + @NonNull EXPLICIT, + IMPLICIT, + } - // :: warning: (redundant.anno) - @NonNull Integer foo(InputStream arg) { - // :: warning: (redundant.anno) - @Nullable Object local; - return Integer.valueOf(1); - } + // :: warning: (redundant.anno) + @NonNull Object f; + // :: warning: (redundant.anno) + @NonNull Integer foo(InputStream arg) { // :: warning: (redundant.anno) - void foo2(@NonNull Integer i) {} - - // TODO :: warning: (redundant.anno) - // :: error: (nullness.on.constructor) - @NonNull RedundantAnnotation() { - f = new Object(); - } - - // :: error: (nullness.on.receiver) + @Nullable Object local; + return Integer.valueOf(1); + } + + // :: warning: (redundant.anno) + void foo2(@NonNull Integer i) {} + + // TODO :: warning: (redundant.anno) + // :: error: (nullness.on.constructor) + @NonNull RedundantAnnotation() { + f = new Object(); + } + + // :: error: (nullness.on.receiver) + // :: warning: (redundant.anno) + void bar(@NonNull RedundantAnnotation this, InputStream arg) throws Exception { // :: warning: (redundant.anno) - void bar(@NonNull RedundantAnnotation this, InputStream arg) throws Exception { - // :: warning: (redundant.anno) - try (@Nullable InputStream in = arg) { + try (@Nullable InputStream in = arg) { - // :: warning: (redundant.anno) - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull Exception e) { + // :: warning: (redundant.anno) + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull Exception e) { - } + } - // TODO :: warning: (redundant.anno) warning on the upper bound - List l; + // TODO :: warning: (redundant.anno) warning on the upper bound + List l; - // TODO :: warning: (redundant.anno) warning on the lower bound - // :: error: (type.invalid.super.wildcard) - List l2; + // TODO :: warning: (redundant.anno) warning on the lower bound + // :: error: (type.invalid.super.wildcard) + List l2; - Object obj = null; - // TODO :: warning: (redundant.anno) for the typecast - String x = (@Nullable String) obj; + Object obj = null; + // TODO :: warning: (redundant.anno) for the typecast + String x = (@Nullable String) obj; - // TODO :: warning: (redundant.anno) for the instanceof - // :: error: (instanceof.nullable) - boolean b = x instanceof @Nullable String; + // TODO :: warning: (redundant.anno) for the instanceof + // :: error: (instanceof.nullable) + boolean b = x instanceof @Nullable String; - // TODO :: warning: (redundant.anno) on the component type - @NonNull String[] strs; - // TODO :: warning: (redundant.anno) on the component type - strs = new @NonNull String[10]; - } + // TODO :: warning: (redundant.anno) on the component type + @NonNull String[] strs; + // TODO :: warning: (redundant.anno) on the component type + strs = new @NonNull String[10]; + } } diff --git a/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java b/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java index 97ec97291f1..013bbcf798d 100644 --- a/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java +++ b/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java @@ -1,20 +1,19 @@ -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; class RedundantAnnotationOptions { - private static @Nullable T safeGetAnnotation( - Field f, Class annotationClass) { - @Nullable T annotation; - try { - @Nullable T cast = f.getAnnotation((Class<@NonNull T>) annotationClass); - annotation = cast; - } catch (Exception e) { - annotation = null; - } - - return annotation; + private static @Nullable T safeGetAnnotation( + Field f, Class annotationClass) { + @Nullable T annotation; + try { + @Nullable T cast = f.getAnnotation((Class<@NonNull T>) annotationClass); + annotation = cast; + } catch (Exception e) { + annotation = null; } + + return annotation; + } } diff --git a/checker/tests/nullness/AliasedAnnotations.java b/checker/tests/nullness/AliasedAnnotations.java index 2b1cfd08dd4..9495b8f9d9e 100644 --- a/checker/tests/nullness/AliasedAnnotations.java +++ b/checker/tests/nullness/AliasedAnnotations.java @@ -3,84 +3,84 @@ public class AliasedAnnotations { - void useNonNullAnnotations() { - // :: error: (assignment.type.incompatible) - @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; - // :: error: (assignment.type.incompatible) - @com.sun.istack.internal.NotNull Object nn2 = null; - // :: error: (assignment.type.incompatible) - @edu.umd.cs.findbugs.annotations.NonNull Object nn3 = null; - // :: error: (assignment.type.incompatible) - @javax.annotation.Nonnull Object nn4 = null; - // Invalid location for NonNull :: error: (assignment.type.incompatible) - // @javax.validation.constraints.NotNull Object nn5 = null; - // :: error: (assignment.type.incompatible) - @org.eclipse.jdt.annotation.NonNull Object nn6 = null; - // :: error: (assignment.type.incompatible) - @org.jetbrains.annotations.NotNull Object nn7 = null; - // :: error: (assignment.type.incompatible) - @org.netbeans.api.annotations.common.NonNull Object nn8 = null; - // :: error: (assignment.type.incompatible) - @org.jmlspecs.annotation.NonNull Object nn9 = null; - } + void useNonNullAnnotations() { + // :: error: (assignment.type.incompatible) + @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; + // :: error: (assignment.type.incompatible) + @com.sun.istack.internal.NotNull Object nn2 = null; + // :: error: (assignment.type.incompatible) + @edu.umd.cs.findbugs.annotations.NonNull Object nn3 = null; + // :: error: (assignment.type.incompatible) + @javax.annotation.Nonnull Object nn4 = null; + // Invalid location for NonNull :: error: (assignment.type.incompatible) + // @javax.validation.constraints.NotNull Object nn5 = null; + // :: error: (assignment.type.incompatible) + @org.eclipse.jdt.annotation.NonNull Object nn6 = null; + // :: error: (assignment.type.incompatible) + @org.jetbrains.annotations.NotNull Object nn7 = null; + // :: error: (assignment.type.incompatible) + @org.netbeans.api.annotations.common.NonNull Object nn8 = null; + // :: error: (assignment.type.incompatible) + @org.jmlspecs.annotation.NonNull Object nn9 = null; + } - void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations2(@com.sun.istack.internal.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations2(@com.sun.istack.internal.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations3(@edu.umd.cs.findbugs.annotations.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations3(@edu.umd.cs.findbugs.annotations.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations4(@edu.umd.cs.findbugs.annotations.CheckForNull Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations4(@edu.umd.cs.findbugs.annotations.CheckForNull Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations5(@edu.umd.cs.findbugs.annotations.UnknownNullness Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations5(@edu.umd.cs.findbugs.annotations.UnknownNullness Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations6(@javax.annotation.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations6(@javax.annotation.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations7(@javax.annotation.CheckForNull Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations7(@javax.annotation.CheckForNull Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations9(@org.eclipse.jdt.annotation.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations9(@org.eclipse.jdt.annotation.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations10(@org.jetbrains.annotations.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations10(@org.jetbrains.annotations.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations12(@org.netbeans.api.annotations.common.NullAllowed Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations12(@org.netbeans.api.annotations.common.NullAllowed Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations13(@org.netbeans.api.annotations.common.NullUnknown Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations13(@org.netbeans.api.annotations.common.NullUnknown Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations14(@org.jmlspecs.annotation.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations14(@org.jmlspecs.annotation.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } } diff --git a/checker/tests/nullness/Aliasing.java b/checker/tests/nullness/Aliasing.java index 40ff578c724..8e5676039f9 100644 --- a/checker/tests/nullness/Aliasing.java +++ b/checker/tests/nullness/Aliasing.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.nullness.qual.*; public class Aliasing { - @NonNull Object nno = new Object(); - @Nullable Object no = null; + @NonNull Object nno = new Object(); + @Nullable Object no = null; - public static void main(String[] args) { - Aliasing a = new Aliasing(); - Aliasing b = new Aliasing(); - m(a, b); - } + public static void main(String[] args) { + Aliasing a = new Aliasing(); + Aliasing b = new Aliasing(); + m(a, b); + } - static void m(@NonNull Aliasing a, @NonNull Aliasing b) { - a.no = b.nno; - // Changing a.no to nonnull does not mean that b.no is also nonnull - // :: error: (assignment.type.incompatible) - b.nno = b.no; + static void m(@NonNull Aliasing a, @NonNull Aliasing b) { + a.no = b.nno; + // Changing a.no to nonnull does not mean that b.no is also nonnull + // :: error: (assignment.type.incompatible) + b.nno = b.no; - System.out.println("@NonNull field b.nno is: " + b.nno); - } + System.out.println("@NonNull field b.nno is: " + b.nno); + } } diff --git a/checker/tests/nullness/AnnotatedJdkEqualsTest.java b/checker/tests/nullness/AnnotatedJdkEqualsTest.java index d5ea25699aa..9e1015b8f1a 100644 --- a/checker/tests/nullness/AnnotatedJdkEqualsTest.java +++ b/checker/tests/nullness/AnnotatedJdkEqualsTest.java @@ -5,14 +5,14 @@ import java.net.URL; public class AnnotatedJdkEqualsTest { - void foo(URL u) { - // As of this writing, the annotated JDK does not contain a URL.java file - // for the java.net.URL class. - // Nonetheless, the following code should type-check. - // This could be handled via inheritance of annotations from superclasses either during JDK - // creation or during type-checking. It would be impractical to manually annotate every - // method in the entire JDK: it would be too labor-intensive and there would be certain to - // be some oversights. - u.equals(null); - } + void foo(URL u) { + // As of this writing, the annotated JDK does not contain a URL.java file + // for the java.net.URL class. + // Nonetheless, the following code should type-check. + // This could be handled via inheritance of annotations from superclasses either during JDK + // creation or during type-checking. It would be impractical to manually annotate every + // method in the entire JDK: it would be too labor-intensive and there would be certain to + // be some oversights. + u.equals(null); + } } diff --git a/checker/tests/nullness/AnnotatedJdkTest.java b/checker/tests/nullness/AnnotatedJdkTest.java index 54267f3d2fb..86401e5250b 100644 --- a/checker/tests/nullness/AnnotatedJdkTest.java +++ b/checker/tests/nullness/AnnotatedJdkTest.java @@ -1,18 +1,17 @@ // Test case for issue 370: https://github.com/typetools/checker-framework/issues/370 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Arrays; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class AnnotatedJdkTest { - // This code should type-check because of the annotated JDK, which contains: - // class Arrays { - // public static List asList(T... a); - // } - // That JDK annotation should be equivalent to - // public static List asList(T... a); - // because of the CLIMB-to-top defaulting rule. + // This code should type-check because of the annotated JDK, which contains: + // class Arrays { + // public static List asList(T... a); + // } + // That JDK annotation should be equivalent to + // public static List asList(T... a); + // because of the CLIMB-to-top defaulting rule. - List<@Nullable String> lns = Arrays.asList("foo", null, "bar"); + List<@Nullable String> lns = Arrays.asList("foo", null, "bar"); } diff --git a/checker/tests/nullness/AnnotatedSupertype.java b/checker/tests/nullness/AnnotatedSupertype.java index 33b67fa2c08..8922a988609 100644 --- a/checker/tests/nullness/AnnotatedSupertype.java +++ b/checker/tests/nullness/AnnotatedSupertype.java @@ -1,19 +1,18 @@ +import java.io.Serializable; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.io.Serializable; - public class AnnotatedSupertype { - class NullableSupertype - // :: error: (nullness.on.supertype) - extends @Nullable Object - // :: error: (nullness.on.supertype) - implements @Nullable Serializable {} + class NullableSupertype + // :: error: (nullness.on.supertype) + extends @Nullable Object + // :: error: (nullness.on.supertype) + implements @Nullable Serializable {} - @NonNull class NonNullSupertype - // :: error: (nullness.on.supertype) - extends @NonNull Object - // :: error: (nullness.on.supertype) - implements @NonNull Serializable {} + @NonNull class NonNullSupertype + // :: error: (nullness.on.supertype) + extends @NonNull Object + // :: error: (nullness.on.supertype) + implements @NonNull Serializable {} } diff --git a/checker/tests/nullness/AnonymousSkipDefs.java b/checker/tests/nullness/AnonymousSkipDefs.java index 7b019357009..5374e68506d 100644 --- a/checker/tests/nullness/AnonymousSkipDefs.java +++ b/checker/tests/nullness/AnonymousSkipDefs.java @@ -3,20 +3,20 @@ public class AnonymousSkipDefs { - public static void main(String[] args) { - call( - new Runnable() { - @Override - public void run() { - @Nullable Object veryNull = null; - // :: error: (assignment.type.incompatible) - @NonNull Object notNull = veryNull; - notNull.toString(); - } - }); - } + public static void main(String[] args) { + call( + new Runnable() { + @Override + public void run() { + @Nullable Object veryNull = null; + // :: error: (assignment.type.incompatible) + @NonNull Object notNull = veryNull; + notNull.toString(); + } + }); + } - private static void call(Runnable r) { - r.run(); - } + private static void call(Runnable r) { + r.run(); + } } diff --git a/checker/tests/nullness/ArrayArgs.java b/checker/tests/nullness/ArrayArgs.java index 39c69a17eb2..a8c47ff2a00 100644 --- a/checker/tests/nullness/ArrayArgs.java +++ b/checker/tests/nullness/ArrayArgs.java @@ -3,30 +3,30 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class ArrayArgs { - public void test(@NonNull String[] args) {} + public void test(@NonNull String[] args) {} - public void test(Class<@NonNull ? extends java.lang.annotation.Annotation> cls) {} + public void test(Class<@NonNull ? extends java.lang.annotation.Annotation> cls) {} - public void test() { - test(NonNull.class); + public void test() { + test(NonNull.class); - String[] s1 = new String[] {null, null, null}; - // :: error: (argument.type.incompatible) - test(s1); - String[] s2 = new String[] {"hello", null, "goodbye"}; - // :: error: (argument.type.incompatible) - test(s2); - // :: error: (assignment.type.incompatible) - @NonNull String[] s3 = new String[] {"hello", null, "goodbye"}; - // :: error: (new.array.type.invalid) - @NonNull String[] s4 = new String[3]; + String[] s1 = new String[] {null, null, null}; + // :: error: (argument.type.incompatible) + test(s1); + String[] s2 = new String[] {"hello", null, "goodbye"}; + // :: error: (argument.type.incompatible) + test(s2); + // :: error: (assignment.type.incompatible) + @NonNull String[] s3 = new String[] {"hello", null, "goodbye"}; + // :: error: (new.array.type.invalid) + @NonNull String[] s4 = new String[3]; - // TODO: when issue 25 is fixed, the following is safe - // and no error needs to be raised. - String[] s5 = new String[] {"hello", "goodbye"}; - // :: error: (argument.type.incompatible) - test(s5); - @NonNull String[] s6 = new String[] {"hello", "goodbye"}; - test(s6); - } + // TODO: when issue 25 is fixed, the following is safe + // and no error needs to be raised. + String[] s5 = new String[] {"hello", "goodbye"}; + // :: error: (argument.type.incompatible) + test(s5); + @NonNull String[] s6 = new String[] {"hello", "goodbye"}; + test(s6); + } } diff --git a/checker/tests/nullness/ArrayAssignmentFlow.java b/checker/tests/nullness/ArrayAssignmentFlow.java index eeb50156d86..6fa0bad6f66 100644 --- a/checker/tests/nullness/ArrayAssignmentFlow.java +++ b/checker/tests/nullness/ArrayAssignmentFlow.java @@ -2,23 +2,23 @@ public class ArrayAssignmentFlow { - public void add_combined(MyPptTopLevel ppt) { + public void add_combined(MyPptTopLevel ppt) { - @Nullable Object[] vals = new Object[10]; + @Nullable Object[] vals = new Object[10]; - if (ppt.last_values != null) { - // Assigning to an array element should not cause flow information - // about ppt.last_values to be discarded. - vals[0] = ppt.last_values.vals; - ppt.last_values.toString(); - } + if (ppt.last_values != null) { + // Assigning to an array element should not cause flow information + // about ppt.last_values to be discarded. + vals[0] = ppt.last_values.vals; + ppt.last_values.toString(); } + } } class MyPptTopLevel { - public @Nullable MyValueTuple last_values = null; + public @Nullable MyValueTuple last_values = null; } class MyValueTuple { - public Object vals = new Object(); + public Object vals = new Object(); } diff --git a/checker/tests/nullness/ArrayCreation.java b/checker/tests/nullness/ArrayCreation.java index 71c2cd4a2fd..12401e37786 100644 --- a/checker/tests/nullness/ArrayCreation.java +++ b/checker/tests/nullness/ArrayCreation.java @@ -2,12 +2,12 @@ public class ArrayCreation { - void foo() { - // :: error: (nullness.on.new.array) - int[] o = new int @Nullable [10]; - } + void foo() { + // :: error: (nullness.on.new.array) + int[] o = new int @Nullable [10]; + } - void bar() { - int[] @Nullable [] o = new int[10] @Nullable []; - } + void bar() { + int[] @Nullable [] o = new int[10] @Nullable []; + } } diff --git a/checker/tests/nullness/ArrayCreationNullable.java b/checker/tests/nullness/ArrayCreationNullable.java index 3a8ca4533f7..dc8f20c5522 100644 --- a/checker/tests/nullness/ArrayCreationNullable.java +++ b/checker/tests/nullness/ArrayCreationNullable.java @@ -8,178 +8,178 @@ */ public class ArrayCreationNullable { - void testObjectArray(@NonNull Object @NonNull [] p) { - @NonNull Object @NonNull [] objs; - // :: error: (new.array.type.invalid) - objs = new Object[10]; - objs[0].toString(); - // :: error: (assignment.type.incompatible) - objs = new @Nullable Object[10]; - objs[0].toString(); - // :: error: (new.array.type.invalid) - objs = new @NonNull Object[10]; - objs[0].toString(); - // Allowed. - objs = p; - objs[0].toString(); - } - - @DefaultQualifier(NonNull.class) - void testObjectArray2() { - Object[] objs; - // Even if the default qualifier is NonNull, array component - // types must be Nullable. - // :: error: (new.array.type.invalid) - objs = new Object[10]; - objs[0].toString(); - } - - void testInitializers() { - Object[] objs = {1, 2, 3}; - objs = new Integer[] {1, 2, 3}; - objs = new Object[] {new Object(), "ha"}; - - @NonNull Object[] objs2 = {}; - // :: error: (assignment.type.incompatible) - objs2 = new Integer[] {1, null, 3}; - // :: error: (assignment.type.incompatible) - objs2 = new Object[] {new Object(), "ha", null}; - - @NonNull Object[] objs3 = new Integer[] {1, 2, 3}; - objs3 = new Integer[] {1, 2, 3}; - // :: error: (assignment.type.incompatible) - objs3 = new Integer[] {1, 2, 3, null}; - - (new Integer[] {1, 2, 3})[0].toString(); - // :: error: (dereference.of.nullable) - (new Integer[] {1, 2, 3, null})[0].toString(); - - // The assignment context is used to infer a @Nullable component type. - @Nullable Object[] objs4 = new Integer[] {1, 2, 3}; - // :: error: (dereference.of.nullable) - objs4[0].toString(); - objs4 = new Integer[] {1, 2, 3}; - } - - void testStringArray(@NonNull String @NonNull [] p) { - @NonNull String @NonNull [] strs; - // :: error: (new.array.type.invalid) - strs = new String[10]; - strs[0].toString(); - // :: error: (assignment.type.incompatible) - strs = new @Nullable String[10]; - strs[0].toString(); - // :: error: (new.array.type.invalid) - strs = new @NonNull String[10]; - strs[0].toString(); - // Allowed. - strs = p; - strs[0].toString(); - } - - void testIntegerArray(@NonNull Integer @NonNull [] p) { - @NonNull Integer @NonNull [] ints; - // :: error: (new.array.type.invalid) - ints = new Integer[10]; - ints[0].toString(); - // :: error: (assignment.type.incompatible) - ints = new @Nullable Integer[10]; - ints[0].toString(); - // :: error: (new.array.type.invalid) - ints = new @NonNull Integer[10]; - ints[0].toString(); - // Allowed. - ints = p; - ints[0].toString(); - } + void testObjectArray(@NonNull Object @NonNull [] p) { + @NonNull Object @NonNull [] objs; + // :: error: (new.array.type.invalid) + objs = new Object[10]; + objs[0].toString(); + // :: error: (assignment.type.incompatible) + objs = new @Nullable Object[10]; + objs[0].toString(); + // :: error: (new.array.type.invalid) + objs = new @NonNull Object[10]; + objs[0].toString(); + // Allowed. + objs = p; + objs[0].toString(); + } + + @DefaultQualifier(NonNull.class) + void testObjectArray2() { + Object[] objs; + // Even if the default qualifier is NonNull, array component + // types must be Nullable. + // :: error: (new.array.type.invalid) + objs = new Object[10]; + objs[0].toString(); + } - // The component type of zero-length arrays can be non-null - they will always generate - // IndexOutOfBoundsExceptions, but are usually just used for the type, e.g. in List.toArray. - void testLengthZero() { - @NonNull Object @NonNull [] objs; - objs = new Object[0]; - } + void testInitializers() { + Object[] objs = {1, 2, 3}; + objs = new Integer[] {1, 2, 3}; + objs = new Object[] {new Object(), "ha"}; - /* Test case for Issue 153. - // toArray re-uses the passed array, if it is of appropriate size. - // It is only guaranteed to be non-null, if it is at most the same size. - void testToArray(java.util.Set nns) { - @NonNull Object [] nna = nns.toArray(new Object[nns.size()]); - // Given array is too small -> new one is created. - nna = nns.toArray(new Object[nns.size()-2]); - // Padding elements will be null. - // TODO:: error: (assignment.type.incompatible) - nna = nns.toArray(new Object[nns.size() + 2]); - @Nullable Object [] nbla = nns.toArray(new Object[nns.size() + 2]); - } - */ - - void testMultiDim() { - // new double[10][10] has type double @NonNull[] @Nullable[] - // :: error: (new.array.type.invalid) - double @NonNull [] @NonNull [] daa = new double[10][10]; - double @NonNull [] @Nullable [] daa2 = new double[10][10]; - - // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] - // :: error: (new.array.type.invalid) - @Nullable Object @NonNull [] @NonNull [] oaa = new Object[10][10]; - @Nullable Object @NonNull [] @Nullable [] oaa2 = new Object[10][10]; - - // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] - // :: error: (new.array.type.invalid) - oaa2 = new Object @NonNull [10] @NonNull [10]; - - @MonotonicNonNull Object @NonNull [] @MonotonicNonNull [] oaa3 = - new @MonotonicNonNull Object @NonNull [10] @MonotonicNonNull [10]; - oaa3[0] = new @MonotonicNonNull Object[4]; - // :: error: (assignment.type.incompatible) - oaa3[0] = null; - // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) - oaa3[0][0] = null; - } + @NonNull Object[] objs2 = {}; + // :: error: (assignment.type.incompatible) + objs2 = new Integer[] {1, null, 3}; + // :: error: (assignment.type.incompatible) + objs2 = new Object[] {new Object(), "ha", null}; - @PolyNull Object[] testPolyNull(@PolyNull Object[] in) { - @PolyNull Object[] out = new @PolyNull Object[in.length]; - for (int i = 0; i < in.length; ++i) { - if (in[i] == null) { - out[i] = null; - } else { - out[i] = in[i].getClass().toString(); - // :: error: (assignment.type.incompatible) - out[i] = null; - } - } - return out; - } + @NonNull Object[] objs3 = new Integer[] {1, 2, 3}; + objs3 = new Integer[] {1, 2, 3}; + // :: error: (assignment.type.incompatible) + objs3 = new Integer[] {1, 2, 3, null}; - void testMonotonicNonNull() { - @MonotonicNonNull Object @NonNull [] loa = new @MonotonicNonNull Object @NonNull [10]; - loa = new Object @NonNull [10]; - loa[0] = new Object(); - @MonotonicNonNull Object @NonNull [] loa2 = new Object @NonNull [10]; - // :: error: (dereference.of.nullable) - loa2[0].toString(); - } + (new Integer[] {1, 2, 3})[0].toString(); + // :: error: (dereference.of.nullable) + (new Integer[] {1, 2, 3, null})[0].toString(); - @MonotonicNonNull Object @NonNull [] testReturnContext() { - return new Object[10]; - } + // The assignment context is used to infer a @Nullable component type. + @Nullable Object[] objs4 = new Integer[] {1, 2, 3}; + // :: error: (dereference.of.nullable) + objs4[0].toString(); + objs4 = new Integer[] {1, 2, 3}; + } + void testStringArray(@NonNull String @NonNull [] p) { + @NonNull String @NonNull [] strs; // :: error: (new.array.type.invalid) - @NonNull Object @NonNull [] oa0 = new Object[10]; + strs = new String[10]; + strs[0].toString(); + // :: error: (assignment.type.incompatible) + strs = new @Nullable String[10]; + strs[0].toString(); + // :: error: (new.array.type.invalid) + strs = new @NonNull String[10]; + strs[0].toString(); + // Allowed. + strs = p; + strs[0].toString(); + } + + void testIntegerArray(@NonNull Integer @NonNull [] p) { + @NonNull Integer @NonNull [] ints; + // :: error: (new.array.type.invalid) + ints = new Integer[10]; + ints[0].toString(); + // :: error: (assignment.type.incompatible) + ints = new @Nullable Integer[10]; + ints[0].toString(); + // :: error: (new.array.type.invalid) + ints = new @NonNull Integer[10]; + ints[0].toString(); + // Allowed. + ints = p; + ints[0].toString(); + } + + // The component type of zero-length arrays can be non-null - they will always generate + // IndexOutOfBoundsExceptions, but are usually just used for the type, e.g. in List.toArray. + void testLengthZero() { + @NonNull Object @NonNull [] objs; + objs = new Object[0]; + } + + /* Test case for Issue 153. + // toArray re-uses the passed array, if it is of appropriate size. + // It is only guaranteed to be non-null, if it is at most the same size. + void testToArray(java.util.Set nns) { + @NonNull Object [] nna = nns.toArray(new Object[nns.size()]); + // Given array is too small -> new one is created. + nna = nns.toArray(new Object[nns.size()-2]); + // Padding elements will be null. + // TODO:: error: (assignment.type.incompatible) + nna = nns.toArray(new Object[nns.size() + 2]); + @Nullable Object [] nbla = nns.toArray(new Object[nns.size() + 2]); + } + */ + + void testMultiDim() { + // new double[10][10] has type double @NonNull[] @Nullable[] + // :: error: (new.array.type.invalid) + double @NonNull [] @NonNull [] daa = new double[10][10]; + double @NonNull [] @Nullable [] daa2 = new double[10][10]; - // OK - @MonotonicNonNull Object @NonNull [] loa0 = new @MonotonicNonNull Object @NonNull [10]; + // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] + // :: error: (new.array.type.invalid) + @Nullable Object @NonNull [] @NonNull [] oaa = new Object[10][10]; + @Nullable Object @NonNull [] @Nullable [] oaa2 = new Object[10][10]; - Object[] oa1 = new Object[] {new Object()}; + // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] + // :: error: (new.array.type.invalid) + oaa2 = new Object @NonNull [10] @NonNull [10]; + @MonotonicNonNull Object @NonNull [] @MonotonicNonNull [] oaa3 = + new @MonotonicNonNull Object @NonNull [10] @MonotonicNonNull [10]; + oaa3[0] = new @MonotonicNonNull Object[4]; // :: error: (assignment.type.incompatible) - Object[] oa2 = new Object[] {new Object(), null}; - - public static void main(String[] args) { - ArrayCreationNullable e = new ArrayCreationNullable(); - Integer[] ints = new Integer[] {5, 6}; - // This would result in a NPE, if there were no error. - e.testIntegerArray(ints); + oaa3[0] = null; + // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) + oaa3[0][0] = null; + } + + @PolyNull Object[] testPolyNull(@PolyNull Object[] in) { + @PolyNull Object[] out = new @PolyNull Object[in.length]; + for (int i = 0; i < in.length; ++i) { + if (in[i] == null) { + out[i] = null; + } else { + out[i] = in[i].getClass().toString(); + // :: error: (assignment.type.incompatible) + out[i] = null; + } } + return out; + } + + void testMonotonicNonNull() { + @MonotonicNonNull Object @NonNull [] loa = new @MonotonicNonNull Object @NonNull [10]; + loa = new Object @NonNull [10]; + loa[0] = new Object(); + @MonotonicNonNull Object @NonNull [] loa2 = new Object @NonNull [10]; + // :: error: (dereference.of.nullable) + loa2[0].toString(); + } + + @MonotonicNonNull Object @NonNull [] testReturnContext() { + return new Object[10]; + } + + // :: error: (new.array.type.invalid) + @NonNull Object @NonNull [] oa0 = new Object[10]; + + // OK + @MonotonicNonNull Object @NonNull [] loa0 = new @MonotonicNonNull Object @NonNull [10]; + + Object[] oa1 = new Object[] {new Object()}; + + // :: error: (assignment.type.incompatible) + Object[] oa2 = new Object[] {new Object(), null}; + + public static void main(String[] args) { + ArrayCreationNullable e = new ArrayCreationNullable(); + Integer[] ints = new Integer[] {5, 6}; + // This would result in a NPE, if there were no error. + e.testIntegerArray(ints); + } } diff --git a/checker/tests/nullness/ArrayCreationSubArray.java b/checker/tests/nullness/ArrayCreationSubArray.java index e11b36383eb..75a13b111fe 100644 --- a/checker/tests/nullness/ArrayCreationSubArray.java +++ b/checker/tests/nullness/ArrayCreationSubArray.java @@ -7,17 +7,17 @@ public class ArrayCreationSubArray { - void m() { + void m() { - Object o1a = new Integer[] {1, 2, null, 3, 4}; - @Nullable Integer[] o1b = new Integer[] {1, 2, null, 3, 4}; + Object o1a = new Integer[] {1, 2, null, 3, 4}; + @Nullable Integer[] o1b = new Integer[] {1, 2, null, 3, 4}; - Object o2a = new Object[] {null}; - Object o2b = new @Nullable Object[] {null}; + Object o2a = new Object[] {null}; + Object o2b = new @Nullable Object[] {null}; - Object o3a = new Object[][] {new Object[] {null}}; - Object o3b = new Object[][] {new @Nullable Object[] {null}}; - Object o3c = new @Nullable Object[][] {new @Nullable Object[] {null}}; - Object o3d = new Object[][] {{null}}; - } + Object o3a = new Object[][] {new Object[] {null}}; + Object o3b = new Object[][] {new @Nullable Object[] {null}}; + Object o3c = new @Nullable Object[][] {new @Nullable Object[] {null}}; + Object o3d = new Object[][] {{null}}; + } } diff --git a/checker/tests/nullness/ArrayIndex.java b/checker/tests/nullness/ArrayIndex.java index 6be82600be4..6b7800daf9a 100644 --- a/checker/tests/nullness/ArrayIndex.java +++ b/checker/tests/nullness/ArrayIndex.java @@ -1,16 +1,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ArrayIndex { - void foo(@Nullable Object[] a, int i) { - if (a[i] != null) { - a[i].hashCode(); - } - if (a[i + 1] != null) { - a[i + 1].hashCode(); - } - if (a[i + 1] != null) { - // :: error: (dereference.of.nullable) - a[i].hashCode(); - } + void foo(@Nullable Object[] a, int i) { + if (a[i] != null) { + a[i].hashCode(); } + if (a[i + 1] != null) { + a[i + 1].hashCode(); + } + if (a[i + 1] != null) { + // :: error: (dereference.of.nullable) + a[i].hashCode(); + } + } } diff --git a/checker/tests/nullness/ArrayInitBug.java b/checker/tests/nullness/ArrayInitBug.java index 19fbb8d7fe7..a0fd8f4c6db 100644 --- a/checker/tests/nullness/ArrayInitBug.java +++ b/checker/tests/nullness/ArrayInitBug.java @@ -2,9 +2,9 @@ public class ArrayInitBug { - @Nullable Object @Nullable [] aa; + @Nullable Object @Nullable [] aa; - public ArrayInitBug() { - aa = null; - } + public ArrayInitBug() { + aa = null; + } } diff --git a/checker/tests/nullness/ArrayLazyNN.java b/checker/tests/nullness/ArrayLazyNN.java index 8054f1d23a1..8a84a84a9ef 100644 --- a/checker/tests/nullness/ArrayLazyNN.java +++ b/checker/tests/nullness/ArrayLazyNN.java @@ -6,14 +6,14 @@ * TODO: support for (i=0; i < a.length.... and change component type to non-null. */ public class ArrayLazyNN { - void test1() { - @MonotonicNonNull Object[] o1 = new @MonotonicNonNull Object[10]; - o1[0] = new Object(); - // :: error: (assignment.type.incompatible) - o1[0] = null; - // :: error: (assignment.type.incompatible) - @NonNull Object[] o2 = o1; - @SuppressWarnings("nullness") - @NonNull Object[] o3 = o1; - } + void test1() { + @MonotonicNonNull Object[] o1 = new @MonotonicNonNull Object[10]; + o1[0] = new Object(); + // :: error: (assignment.type.incompatible) + o1[0] = null; + // :: error: (assignment.type.incompatible) + @NonNull Object[] o2 = o1; + @SuppressWarnings("nullness") + @NonNull Object[] o3 = o1; + } } diff --git a/checker/tests/nullness/ArrayNew.java b/checker/tests/nullness/ArrayNew.java index 3afa35fd1ca..456173280d9 100644 --- a/checker/tests/nullness/ArrayNew.java +++ b/checker/tests/nullness/ArrayNew.java @@ -1,22 +1,21 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.Collection; +import org.checkerframework.checker.nullness.qual.*; public class ArrayNew { - void m(Collection seq1) { - Integer[] seq1_array = new @NonNull Integer[] {5}; - Integer[] seq2_array = seq1.toArray(new @NonNull Integer[0]); - } + void m(Collection seq1) { + Integer[] seq1_array = new @NonNull Integer[] {5}; + Integer[] seq2_array = seq1.toArray(new @NonNull Integer[0]); + } - void takePrim1d(int[] ar) {} + void takePrim1d(int[] ar) {} - void callPrim1d() { - takePrim1d(new int[] {1, 2, 1}); - } + void callPrim1d() { + takePrim1d(new int[] {1, 2, 1}); + } - void takePrim2d(int[][] ar) {} + void takePrim2d(int[][] ar) {} - void callPrim2d() { - takePrim2d(new int[][] {{1, 2, 1}, {3, 3, 7}}); - } + void callPrim2d() { + takePrim2d(new int[][] {{1, 2, 1}, {3, 3, 7}}); + } } diff --git a/checker/tests/nullness/ArrayRefs.java b/checker/tests/nullness/ArrayRefs.java index 934c6ecc8be..32f206fbc99 100644 --- a/checker/tests/nullness/ArrayRefs.java +++ b/checker/tests/nullness/ArrayRefs.java @@ -1,40 +1,39 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.Arrays; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class ArrayRefs { - public void test() { - - String[] s = null; - - // :: error: (dereference.of.nullable) - if (s.length > 0) { - System.out.println("s.length > 0"); - } - } - - public static void test2() { - Object a = new Object(); - takeNNList(Arrays.asList(new Object[] {a})); - takeNNList(Arrays.asList(new Object[] {a})); - takeNNList(Arrays.asList(a, a, a)); - // :: error: (argument.type.incompatible) - takeNNList(Arrays.asList(a, a, null)); - } - - static void takeNNList(List p) {} + public void test() { - void test(T[] a) { - test(a); - } - - List @Nullable [] antecedents_for_suppressors() { - return null; - } + String[] s = null; - public void find_suppressed_invs() { - List[] antecedents = antecedents_for_suppressors(); + // :: error: (dereference.of.nullable) + if (s.length > 0) { + System.out.println("s.length > 0"); } + } + + public static void test2() { + Object a = new Object(); + takeNNList(Arrays.asList(new Object[] {a})); + takeNNList(Arrays.asList(new Object[] {a})); + takeNNList(Arrays.asList(a, a, a)); + // :: error: (argument.type.incompatible) + takeNNList(Arrays.asList(a, a, null)); + } + + static void takeNNList(List p) {} + + void test(T[] a) { + test(a); + } + + List @Nullable [] antecedents_for_suppressors() { + return null; + } + + public void find_suppressed_invs() { + List[] antecedents = antecedents_for_suppressors(); + } } diff --git a/checker/tests/nullness/AssertAfter.java b/checker/tests/nullness/AssertAfter.java index 5aa0eb81433..328bd4275fe 100644 --- a/checker/tests/nullness/AssertAfter.java +++ b/checker/tests/nullness/AssertAfter.java @@ -3,74 +3,74 @@ public class AssertAfter { - protected @Nullable String value; + protected @Nullable String value; - @EnsuresNonNull("value") - public boolean setRepNonNull() { - value = ""; - return true; - } + @EnsuresNonNull("value") + public boolean setRepNonNull() { + value = ""; + return true; + } - public void plain() { - // :: error: (dereference.of.nullable) - value.toString(); - } + public void plain() { + // :: error: (dereference.of.nullable) + value.toString(); + } - public void testAfter() { - setRepNonNull(); - value.toString(); - } + public void testAfter() { + setRepNonNull(); + value.toString(); + } - public void testBefore() { - // :: error: (dereference.of.nullable) - value.toString(); - setRepNonNull(); - } + public void testBefore() { + // :: error: (dereference.of.nullable) + value.toString(); + setRepNonNull(); + } - public void withCondition(@Nullable String t) { - if (t == null) { - setRepNonNull(); - } - // :: error: (dereference.of.nullable) - value.toString(); + public void withCondition(@Nullable String t) { + if (t == null) { + setRepNonNull(); } + // :: error: (dereference.of.nullable) + value.toString(); + } - public void inConditionInTrue() { - if (setRepNonNull()) { - value.toString(); - } else { - // nothing to do - } + public void inConditionInTrue() { + if (setRepNonNull()) { + value.toString(); + } else { + // nothing to do } + } - // skip-test: Come back when working on improved flow - public void asCondition() { - if (setRepNonNull()) { - } else { - value.toString(); // valid! - } + // skip-test: Come back when working on improved flow + public void asCondition() { + if (setRepNonNull()) { + } else { + value.toString(); // valid! } + } } // Test that private fields can be mentioned in pre- and post-conditions. class A { - private @Nullable String privateField = null; + private @Nullable String privateField = null; - @EnsuresNonNull("privateField") - public void m1() { - privateField = "hello"; - } + @EnsuresNonNull("privateField") + public void m1() { + privateField = "hello"; + } - @RequiresNonNull("privateField") - public void m2() {} + @RequiresNonNull("privateField") + public void m2() {} } class B { - void f() { - A a = new A(); - a.m1(); - a.m2(); - } + void f() { + A a = new A(); + a.m1(); + a.m2(); + } } diff --git a/checker/tests/nullness/AssertAfter2.java b/checker/tests/nullness/AssertAfter2.java index 1cdaebb9e99..68783180fb8 100644 --- a/checker/tests/nullness/AssertAfter2.java +++ b/checker/tests/nullness/AssertAfter2.java @@ -1,106 +1,105 @@ -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; - import java.util.HashMap; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class AssertAfter2 { - public class Graph { - - HashMap> childMap; - - public Graph(HashMap> childMap) { - this.childMap = childMap; - } - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("childMap.get(#1)") - public void addNode(final T n) { - // body omitted, not relevant to test case - } - - public void addEdge(T parent, T child) { - addNode(parent); - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); - } - - public void addEdgeBad1(T parent, T child) { - // :: error: (assignment.type.incompatible) - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); - } - - public void addEdgeBad2(T parent, T child) { - addNode(parent); - // :: error: (assignment.type.incompatible) - @NonNull List<@KeyFor("childMap") T> l = childMap.get(child); - } - - public void addEdgeBad3(T parent, T child) { - addNode(parent); - parent = child; - // :: error: (assignment.type.incompatible) - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); - } - - public void addEdgeOK(T parent, T child) { - List<@KeyFor("childMap") T> l = childMap.get(parent); - } + public class Graph { + + HashMap> childMap; + + public Graph(HashMap> childMap) { + this.childMap = childMap; + } + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("childMap.get(#1)") + public void addNode(final T n) { + // body omitted, not relevant to test case + } + + public void addEdge(T parent, T child) { + addNode(parent); + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); + } + + public void addEdgeBad1(T parent, T child) { + // :: error: (assignment.type.incompatible) + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); + } + + public void addEdgeBad2(T parent, T child) { + addNode(parent); + // :: error: (assignment.type.incompatible) + @NonNull List<@KeyFor("childMap") T> l = childMap.get(child); + } + + public void addEdgeBad3(T parent, T child) { + addNode(parent); + parent = child; + // :: error: (assignment.type.incompatible) + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); + } + + public void addEdgeOK(T parent, T child) { + List<@KeyFor("childMap") T> l = childMap.get(parent); + } + } + + class MultiParam { + + MultiParam(MultiParam thing, Object f1, Object f2, Object f3) { + this.thing = thing; + this.f1 = f1; + this.f2 = f2; + this.f3 = f3; + } + + MultiParam thing; + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("get(#1, #2, #3)") + void add(final Object o1, final Object o2, final Object o3) { + // body omitted, not relevant to test case + } + + @org.checkerframework.dataflow.qual.Pure + @Nullable Object get(Object o1, Object o2, Object o3) { + return null; + } + + Object f1, f2, f3; + + void addGood1() { + thing.add(f1, f2, f3); + @NonNull Object nn = thing.get(f1, f2, f3); + } + + void addBad1() { + // :: error: (assignment.type.incompatible) + @NonNull Object nn = get(f1, f2, f3); + } + + void addBad2() { + thing.add(f1, f2, f3); + f1 = new Object(); + // :: error: (assignment.type.incompatible) + @NonNull Object nn = thing.get(f1, f2, f3); + } + + void addBad3() { + thing.add(f1, f2, f3); + f2 = new Object(); + // :: error: (assignment.type.incompatible) + @NonNull Object nn = thing.get(f1, f2, f3); } - class MultiParam { - - MultiParam(MultiParam thing, Object f1, Object f2, Object f3) { - this.thing = thing; - this.f1 = f1; - this.f2 = f2; - this.f3 = f3; - } - - MultiParam thing; - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("get(#1, #2, #3)") - void add(final Object o1, final Object o2, final Object o3) { - // body omitted, not relevant to test case - } - - @org.checkerframework.dataflow.qual.Pure - @Nullable Object get(Object o1, Object o2, Object o3) { - return null; - } - - Object f1, f2, f3; - - void addGood1() { - thing.add(f1, f2, f3); - @NonNull Object nn = thing.get(f1, f2, f3); - } - - void addBad1() { - // :: error: (assignment.type.incompatible) - @NonNull Object nn = get(f1, f2, f3); - } - - void addBad2() { - thing.add(f1, f2, f3); - f1 = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object nn = thing.get(f1, f2, f3); - } - - void addBad3() { - thing.add(f1, f2, f3); - f2 = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object nn = thing.get(f1, f2, f3); - } - - void addBad4() { - thing.add(f1, f2, f3); - f3 = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object nn = thing.get(f1, f2, f3); - } + void addBad4() { + thing.add(f1, f2, f3); + f3 = new Object(); + // :: error: (assignment.type.incompatible) + @NonNull Object nn = thing.get(f1, f2, f3); } + } } diff --git a/checker/tests/nullness/AssertAfterChecked.java b/checker/tests/nullness/AssertAfterChecked.java index 3baf7a82c44..8542d7f15ac 100644 --- a/checker/tests/nullness/AssertAfterChecked.java +++ b/checker/tests/nullness/AssertAfterChecked.java @@ -3,149 +3,149 @@ public class AssertAfterChecked { - class InitField { - @Nullable Object f; - - @EnsuresNonNull("f") - void init() { - f = new Object(); - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - void initBad() {} - - void testInit() { - init(); - f.toString(); - } - } - - static class InitStaticField { - static @Nullable Object f; - - @EnsuresNonNull("f") - void init() { - f = new Object(); - } - - @EnsuresNonNull("f") - void init2() { - InitStaticField.f = new Object(); - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - void initBad() {} - - void testInit() { - init(); - f.toString(); - } - - @EnsuresNonNull("InitStaticField.f") - void initE() { - f = new Object(); - } - - @EnsuresNonNull("InitStaticField.f") - void initE2() { - InitStaticField.f = new Object(); - } - - @EnsuresNonNull("InitStaticField.f") - // :: error: (contracts.postcondition.not.satisfied) - void initBadE() {} - - void testInitE() { - initE(); - // TODO: we need to also support the unqualified static field access? - // f.toString(); - } - - void testInitE2() { - initE(); - InitStaticField.f.toString(); - } - } - - class TestParams { - @EnsuresNonNull("get(#1)") - // :: error: (contracts.postcondition.not.satisfied) - void init(final TestParams p) {} - - @org.checkerframework.dataflow.qual.Pure - @Nullable Object get(Object o) { - return null; - } - - void testInit1() { - init(this); - get(this).toString(); - } - - void testInit1b() { - init(this); - this.get(this).toString(); - } - - void testInit2(TestParams p) { - init(p); - get(p).toString(); - } - - void testInit3(TestParams p) { - p.init(this); - p.get(this).toString(); - } - - void testInit4(TestParams p) { - p.init(this); - // :: error: (dereference.of.nullable) - this.get(this).toString(); - } - } - - class WithReturn { - @Nullable Object f; - - @EnsuresNonNull("f") - int init1() { - f = new Object(); - return 0; - } - - @EnsuresNonNull("f") - int init2() { - if (5 == 5) { - f = new Object(); - return 0; - } else { - f = new Object(); - return 1; - } - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - int initBad1() { - return 0; - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - int initBad2() { - if (5 == 5) { - return 0; - } else { - f = new Object(); - return 1; - } - } - - void testInit() { - init1(); - f.toString(); - } + class InitField { + @Nullable Object f; + + @EnsuresNonNull("f") + void init() { + f = new Object(); + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + void initBad() {} + + void testInit() { + init(); + f.toString(); + } + } + + static class InitStaticField { + static @Nullable Object f; + + @EnsuresNonNull("f") + void init() { + f = new Object(); + } + + @EnsuresNonNull("f") + void init2() { + InitStaticField.f = new Object(); + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + void initBad() {} + + void testInit() { + init(); + f.toString(); + } + + @EnsuresNonNull("InitStaticField.f") + void initE() { + f = new Object(); + } + + @EnsuresNonNull("InitStaticField.f") + void initE2() { + InitStaticField.f = new Object(); + } + + @EnsuresNonNull("InitStaticField.f") + // :: error: (contracts.postcondition.not.satisfied) + void initBadE() {} + + void testInitE() { + initE(); + // TODO: we need to also support the unqualified static field access? + // f.toString(); + } + + void testInitE2() { + initE(); + InitStaticField.f.toString(); + } + } + + class TestParams { + @EnsuresNonNull("get(#1)") + // :: error: (contracts.postcondition.not.satisfied) + void init(final TestParams p) {} + + @org.checkerframework.dataflow.qual.Pure + @Nullable Object get(Object o) { + return null; + } + + void testInit1() { + init(this); + get(this).toString(); + } + + void testInit1b() { + init(this); + this.get(this).toString(); + } + + void testInit2(TestParams p) { + init(p); + get(p).toString(); + } + + void testInit3(TestParams p) { + p.init(this); + p.get(this).toString(); + } + + void testInit4(TestParams p) { + p.init(this); + // :: error: (dereference.of.nullable) + this.get(this).toString(); + } + } + + class WithReturn { + @Nullable Object f; + + @EnsuresNonNull("f") + int init1() { + f = new Object(); + return 0; + } + + @EnsuresNonNull("f") + int init2() { + if (5 == 5) { + f = new Object(); + return 0; + } else { + f = new Object(); + return 1; + } + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + int initBad1() { + return 0; + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + int initBad2() { + if (5 == 5) { + return 0; + } else { + f = new Object(); + return 1; + } + } + + void testInit() { + init1(); + f.toString(); } + } } diff --git a/checker/tests/nullness/AssertIfChecked.java b/checker/tests/nullness/AssertIfChecked.java index dd86ff99f8d..86b02e156fd 100644 --- a/checker/tests/nullness/AssertIfChecked.java +++ b/checker/tests/nullness/AssertIfChecked.java @@ -3,162 +3,161 @@ public class AssertIfChecked { - boolean unknown = false; - - @Nullable Object value; - - @EnsuresNonNullIf(result = true, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public void badform1() {} - - @EnsuresNonNullIf(result = true, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public Object badform2() { - return new Object(); - } - - @EnsuresNonNullIf(result = false, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public void badform3() {} - - @EnsuresNonNullIf(result = false, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public Object badform4() { - return new Object(); - } - - @EnsuresNonNullIf(result = true, expression = "value") - public boolean goodt1() { - return value != null; - } - - @EnsuresNonNullIf(result = true, expression = "value") - public boolean badt1() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value == null; - } - - @EnsuresNonNullIf(result = false, expression = "value") - public boolean goodf1() { - return value == null; - } - - @EnsuresNonNullIf(result = false, expression = "value") - public boolean badf1() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value != null; - } - - @EnsuresNonNullIf(result = true, expression = "value") - public boolean bad2() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value == null || unknown; - } - - @EnsuresNonNullIf(result = false, expression = "value") - public boolean bad3() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value == null && unknown; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testParam(final @Nullable Object param) { - return param != null; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testLitTTgood1(final @Nullable Object param) { - if (param == null) { - return false; - } - return true; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testLitTTbad1(final @Nullable Object param) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFgood1(final @Nullable Object param) { - return true; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFgood2(final @Nullable Object param) { - if (param == null) { - return true; - } - return false; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFbad1(final @Nullable Object param) { - if (param == null) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - return true; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFbad2(final @Nullable Object param) { - // :: error: (contracts.conditional.postcondition.not.satisfied) + boolean unknown = false; + + @Nullable Object value; + + @EnsuresNonNullIf(result = true, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public void badform1() {} + + @EnsuresNonNullIf(result = true, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public Object badform2() { + return new Object(); + } + + @EnsuresNonNullIf(result = false, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public void badform3() {} + + @EnsuresNonNullIf(result = false, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public Object badform4() { + return new Object(); + } + + @EnsuresNonNullIf(result = true, expression = "value") + public boolean goodt1() { + return value != null; + } + + @EnsuresNonNullIf(result = true, expression = "value") + public boolean badt1() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value == null; + } + + @EnsuresNonNullIf(result = false, expression = "value") + public boolean goodf1() { + return value == null; + } + + @EnsuresNonNullIf(result = false, expression = "value") + public boolean badf1() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value != null; + } + + @EnsuresNonNullIf(result = true, expression = "value") + public boolean bad2() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value == null || unknown; + } + + @EnsuresNonNullIf(result = false, expression = "value") + public boolean bad3() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value == null && unknown; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testParam(final @Nullable Object param) { + return param != null; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testLitTTgood1(final @Nullable Object param) { + if (param == null) { + return false; + } + return true; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testLitTTbad1(final @Nullable Object param) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFgood1(final @Nullable Object param) { + return true; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFgood2(final @Nullable Object param) { + if (param == null) { + return true; + } + return false; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFbad1(final @Nullable Object param) { + if (param == null) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + return true; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFbad2(final @Nullable Object param) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + + @Nullable Object getValueUnpure() { + return value; + } + + @org.checkerframework.dataflow.qual.Pure + @Nullable Object getValuePure() { + return value; + } + + @EnsuresNonNullIf(result = true, expression = "getValuePure()") + public boolean hasValuePure() { + return getValuePure() != null; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + public static final boolean isComment(@Nullable String s) { + return s != null && (s.startsWith("//") || s.startsWith("#")); + } + + @EnsuresNonNullIf(result = true, expression = "#1") + public boolean myEquals(@Nullable Object o) { + return (o instanceof String) && equals((String) o); + } + + /* + * The next two methods are from Daikon's class Quant. They verify that + * EnsuresNonNullIf is correctly added to the assumptions after a check. + */ + + @EnsuresNonNullIf( + result = true, + expression = {"#1", "#2"}) + /* pure */ public static boolean sameLength( + boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + return ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length); + } + + /* pure */ public static boolean isReverse(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + if (!sameLength(seq1, seq2)) { + return false; + } + // This assert is not needed for inference. + // assert seq1 != null && seq2 != null; // because sameLength() = true + + int length = seq1.length; + for (int i = 0; i < length; i++) { + if (seq1[i] != seq2[length - i - 1]) { return false; + } } - - @Nullable Object getValueUnpure() { - return value; - } - - @org.checkerframework.dataflow.qual.Pure - @Nullable Object getValuePure() { - return value; - } - - @EnsuresNonNullIf(result = true, expression = "getValuePure()") - public boolean hasValuePure() { - return getValuePure() != null; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - public static final boolean isComment(@Nullable String s) { - return s != null && (s.startsWith("//") || s.startsWith("#")); - } - - @EnsuresNonNullIf(result = true, expression = "#1") - public boolean myEquals(@Nullable Object o) { - return (o instanceof String) && equals((String) o); - } - - /* - * The next two methods are from Daikon's class Quant. They verify that - * EnsuresNonNullIf is correctly added to the assumptions after a check. - */ - - @EnsuresNonNullIf( - result = true, - expression = {"#1", "#2"}) - /* pure */ public static boolean sameLength( - boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - return ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length); - } - - /* pure */ public static boolean isReverse( - boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - if (!sameLength(seq1, seq2)) { - return false; - } - // This assert is not needed for inference. - // assert seq1 != null && seq2 != null; // because sameLength() = true - - int length = seq1.length; - for (int i = 0; i < length; i++) { - if (seq1[i] != seq2[length - i - 1]) { - return false; - } - } - return true; - } + return true; + } } diff --git a/checker/tests/nullness/AssertIfClient.java b/checker/tests/nullness/AssertIfClient.java index 1a2e808c270..031158b01fb 100644 --- a/checker/tests/nullness/AssertIfClient.java +++ b/checker/tests/nullness/AssertIfClient.java @@ -3,57 +3,57 @@ public class AssertIfClient { - @RequiresNonNull("#1.rpcResponse()") - void rpcResponseNonNull(Proxy proxy) { - @NonNull Object response = proxy.rpcResponse(); - } - - void rpcResponseNullable(Proxy proxy) { - @Nullable Object response = proxy.rpcResponse(); - } - - void rpcResponseTypestate() { - Proxy proxy = new Proxy(); - // :: error: (assignment.type.incompatible) - @NonNull Object response1 = proxy.rpcResponse(); - // :: error: (contracts.precondition.not.satisfied) - rpcResponseNonNull(proxy); - rpcResponseNullable(proxy); - - proxy.issueRpc(); - @NonNull Object response2 = proxy.rpcResponse(); - @NonNull Object response3 = proxy.rpcResponse(); - rpcResponseNonNull(proxy); - rpcResponseNullable(proxy); - } + @RequiresNonNull("#1.rpcResponse()") + void rpcResponseNonNull(Proxy proxy) { + @NonNull Object response = proxy.rpcResponse(); + } + + void rpcResponseNullable(Proxy proxy) { + @Nullable Object response = proxy.rpcResponse(); + } + + void rpcResponseTypestate() { + Proxy proxy = new Proxy(); + // :: error: (assignment.type.incompatible) + @NonNull Object response1 = proxy.rpcResponse(); + // :: error: (contracts.precondition.not.satisfied) + rpcResponseNonNull(proxy); + rpcResponseNullable(proxy); + + proxy.issueRpc(); + @NonNull Object response2 = proxy.rpcResponse(); + @NonNull Object response3 = proxy.rpcResponse(); + rpcResponseNonNull(proxy); + rpcResponseNullable(proxy); + } } class Proxy { - // the RPC response, or null if not yet received - @MonotonicNonNull Object response = null; - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull({"response", "rpcResponse()"}) - void issueRpc() { - response = new Object(); - } - - // If this method returns true, - // then response is non-null and rpcResponse() returns non-null - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - @EnsuresNonNullIf( - expression = {"response", "rpcResponse()"}, - result = true) - boolean rpcResponseReceived() { - return response != null; - } - - // Returns non-null if the response has been received, null otherwise; but an - // @AssertNonNullIfNonNull annotation would states the converse, that if the result is non-null - // then the response hs been received. See rpcResponseReceived. - @Pure - @Nullable Object rpcResponse() { - return response; - } + // the RPC response, or null if not yet received + @MonotonicNonNull Object response = null; + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull({"response", "rpcResponse()"}) + void issueRpc() { + response = new Object(); + } + + // If this method returns true, + // then response is non-null and rpcResponse() returns non-null + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + @EnsuresNonNullIf( + expression = {"response", "rpcResponse()"}, + result = true) + boolean rpcResponseReceived() { + return response != null; + } + + // Returns non-null if the response has been received, null otherwise; but an + // @AssertNonNullIfNonNull annotation would states the converse, that if the result is non-null + // then the response hs been received. See rpcResponseReceived. + @Pure + @Nullable Object rpcResponse() { + return response; + } } diff --git a/checker/tests/nullness/AssertIfFalseTest.java b/checker/tests/nullness/AssertIfFalseTest.java index 79948ea07c9..a2af7a9f324 100644 --- a/checker/tests/nullness/AssertIfFalseTest.java +++ b/checker/tests/nullness/AssertIfFalseTest.java @@ -3,54 +3,52 @@ public class AssertIfFalseTest { - @org.checkerframework.dataflow.qual.Pure - @Nullable Object get() { - return "m"; - } - - @EnsuresNonNullIf(result = false, expression = "get()") - boolean isGettable() { - // don't bother with the implementation - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - - void simple() { - // :: error: (dereference.of.nullable) - get().toString(); - } - - void checkWrongly() { - if (isGettable()) { - // :: error: (dereference.of.nullable) - get().toString(); - } - } - - void checkCorrectly() { - if (!isGettable()) { - get().toString(); - } - } - - /** Returns whether or not constant_value is a legal constant. */ - @EnsuresNonNullIf(result = false, expression = "#1") - static boolean legalConstant(final @Nullable Object constant_value) { - if ((constant_value == null) - || ((constant_value instanceof Long) || (constant_value instanceof Double))) - return true; - return false; - } - - void useLegalConstant1(@Nullable Object static_constant_value) { - if (!legalConstant(static_constant_value)) { - throw new AssertionError( - "unexpected constant class " + static_constant_value.getClass()); - } - } - - void useLegalConstant2(@Nullable Object static_constant_value) { - assert legalConstant(static_constant_value) - : "unexpected constant class " + static_constant_value.getClass(); - } + @org.checkerframework.dataflow.qual.Pure + @Nullable Object get() { + return "m"; + } + + @EnsuresNonNullIf(result = false, expression = "get()") + boolean isGettable() { + // don't bother with the implementation + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + + void simple() { + // :: error: (dereference.of.nullable) + get().toString(); + } + + void checkWrongly() { + if (isGettable()) { + // :: error: (dereference.of.nullable) + get().toString(); + } + } + + void checkCorrectly() { + if (!isGettable()) { + get().toString(); + } + } + + /** Returns whether or not constant_value is a legal constant. */ + @EnsuresNonNullIf(result = false, expression = "#1") + static boolean legalConstant(final @Nullable Object constant_value) { + if ((constant_value == null) + || ((constant_value instanceof Long) || (constant_value instanceof Double))) return true; + return false; + } + + void useLegalConstant1(@Nullable Object static_constant_value) { + if (!legalConstant(static_constant_value)) { + throw new AssertionError("unexpected constant class " + static_constant_value.getClass()); + } + } + + void useLegalConstant2(@Nullable Object static_constant_value) { + assert legalConstant(static_constant_value) + : "unexpected constant class " + static_constant_value.getClass(); + } } diff --git a/checker/tests/nullness/AssertIfFalseTest2.java b/checker/tests/nullness/AssertIfFalseTest2.java index b5964201066..da9f98ba0c6 100644 --- a/checker/tests/nullness/AssertIfFalseTest2.java +++ b/checker/tests/nullness/AssertIfFalseTest2.java @@ -3,28 +3,28 @@ public class AssertIfFalseTest2 { - public static void usePriorityQueue(PriorityQueue1<@NonNull Object> active) { - while (!(active.isEmpty())) { - @NonNull Object queueMinPathNode = active.peek(); - } + public static void usePriorityQueue(PriorityQueue1<@NonNull Object> active) { + while (!(active.isEmpty())) { + @NonNull Object queueMinPathNode = active.peek(); } + } - /////////////////////////////////////////////////////////////////////////// - /// Classes copied from the annotated JDK - /// + /////////////////////////////////////////////////////////////////////////// + /// Classes copied from the annotated JDK + /// - public class PriorityQueue1 { - @EnsuresNonNullIf( - result = false, - expression = {"peek()"}) - @Pure - public boolean isEmpty() { - return true; - } + public class PriorityQueue1 { + @EnsuresNonNullIf( + result = false, + expression = {"peek()"}) + @Pure + public boolean isEmpty() { + return true; + } - @Pure - public @Nullable E peek() { - return null; - } + @Pure + public @Nullable E peek() { + return null; } + } } diff --git a/checker/tests/nullness/AssertIfNonNullTest.java b/checker/tests/nullness/AssertIfNonNullTest.java index 46feddc37a1..020748f17ad 100644 --- a/checker/tests/nullness/AssertIfNonNullTest.java +++ b/checker/tests/nullness/AssertIfNonNullTest.java @@ -3,15 +3,15 @@ public class AssertIfNonNullTest { - Long id; + Long id; - public AssertIfNonNullTest(Long id) { - this.id = id; - } + public AssertIfNonNullTest(Long id) { + this.id = id; + } - @AssertNonNullIfNonNull("id") - @Pure - public @Nullable Long getId() { - return id; - } + @AssertNonNullIfNonNull("id") + @Pure + public @Nullable Long getId() { + return id; + } } diff --git a/checker/tests/nullness/AssertInStatic.java b/checker/tests/nullness/AssertInStatic.java index 277f810ee6a..099628527a2 100644 --- a/checker/tests/nullness/AssertInStatic.java +++ b/checker/tests/nullness/AssertInStatic.java @@ -1,11 +1,11 @@ public class AssertInStatic { - static { - long x = 0; - try { - x = 0; - } catch (Throwable e) { - assert true; - } + static { + long x = 0; + try { + x = 0; + } catch (Throwable e) { + assert true; } + } } diff --git a/checker/tests/nullness/AssertMethodTest.java b/checker/tests/nullness/AssertMethodTest.java index c8bd1b81326..fc664332072 100644 --- a/checker/tests/nullness/AssertMethodTest.java +++ b/checker/tests/nullness/AssertMethodTest.java @@ -4,84 +4,84 @@ import org.checkerframework.dataflow.qual.SideEffectFree; public class AssertMethodTest { - @interface Anno { - Class value(); - } + @interface Anno { + Class value(); + } - @AssertMethod - @SideEffectFree - void assertMethod(boolean b) { - if (!b) { - throw new RuntimeException(); - } + @AssertMethod + @SideEffectFree + void assertMethod(boolean b) { + if (!b) { + throw new RuntimeException(); } + } - void test1(@Nullable Object o) { - assertMethod(o != null); - o.toString(); - } + void test1(@Nullable Object o) { + assertMethod(o != null); + o.toString(); + } - @Nullable Object getO() { - return null; - } + @Nullable Object getO() { + return null; + } - void test2() { - assertMethod(getO() != null); - // :: error: dereference.of.nullable - getO().toString(); // error - } + void test2() { + assertMethod(getO() != null); + // :: error: dereference.of.nullable + getO().toString(); // error + } - @Pure - @Nullable Object getPureO() { - return ""; - } + @Pure + @Nullable Object getPureO() { + return ""; + } - void test3() { - assertMethod(getPureO() != null); - getPureO().toString(); - } + void test3() { + assertMethod(getPureO() != null); + getPureO().toString(); + } - @Nullable Object field = null; + @Nullable Object field = null; - void test4() { - assertMethod(field != null); - field.toString(); - } + void test4() { + assertMethod(field != null); + field.toString(); + } - String getError() { - field = null; - return "error"; - } + String getError() { + field = null; + return "error"; + } - @AssertMethod(value = RuntimeException.class, parameter = 2) - @SideEffectFree - void assertMethod(Object p, boolean b, Object error) { - if (!b) { - throw new RuntimeException(); - } + @AssertMethod(value = RuntimeException.class, parameter = 2) + @SideEffectFree + void assertMethod(Object p, boolean b, Object error) { + if (!b) { + throw new RuntimeException(); } + } - void test5() { - assertMethod(getError(), field != null, getError()); - // :: error: dereference.of.nullable - field.toString(); // error - } + void test5() { + assertMethod(getError(), field != null, getError()); + // :: error: dereference.of.nullable + field.toString(); // error + } - void test5b() { - assertMethod(getError(), field != null, ""); - field.toString(); - } + void test5b() { + assertMethod(getError(), field != null, ""); + field.toString(); + } - @AssertMethod(isAssertFalse = true) - @SideEffectFree - void assertFalse(boolean b) { - if (b) { - throw new RuntimeException(); - } + @AssertMethod(isAssertFalse = true) + @SideEffectFree + void assertFalse(boolean b) { + if (b) { + throw new RuntimeException(); } + } - void test6() { - assertFalse(field == null); - field.toString(); - } + void test6() { + assertFalse(field == null); + field.toString(); + } } diff --git a/checker/tests/nullness/AssertNonNullIfNonNullTest.java b/checker/tests/nullness/AssertNonNullIfNonNullTest.java index e6a957ae63f..93eabf390c0 100644 --- a/checker/tests/nullness/AssertNonNullIfNonNullTest.java +++ b/checker/tests/nullness/AssertNonNullIfNonNullTest.java @@ -7,40 +7,40 @@ public class AssertNonNullIfNonNullTest { - private @Nullable String value; - - @Pure - @AssertNonNullIfNonNull("value") - public @Nullable String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - @EnsuresNonNullIf(expression = "value", result = true) - public boolean isValueNonNull1() { - return value != null; - } - - @EnsuresNonNullIf(expression = "getValue()", result = true) - public boolean isValueNonNull2() { - // The @AssertNonNullIfNonNull annotation implies that if getValue() is - // non-null, then is non-null, then value is non-null, but not the - // converse, so an error should be issued here. - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value != null; - } - - // The @AssertNonNullIfNonNull annotation should enable suppressing this error. - @EnsuresNonNullIf(expression = "value", result = true) - public boolean isValueNonNull3() { - return getValue() != null; - } - - @EnsuresNonNullIf(expression = "getValue()", result = true) - public boolean isValueNonNull4() { - return getValue() != null; - } + private @Nullable String value; + + @Pure + @AssertNonNullIfNonNull("value") + public @Nullable String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @EnsuresNonNullIf(expression = "value", result = true) + public boolean isValueNonNull1() { + return value != null; + } + + @EnsuresNonNullIf(expression = "getValue()", result = true) + public boolean isValueNonNull2() { + // The @AssertNonNullIfNonNull annotation implies that if getValue() is + // non-null, then is non-null, then value is non-null, but not the + // converse, so an error should be issued here. + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value != null; + } + + // The @AssertNonNullIfNonNull annotation should enable suppressing this error. + @EnsuresNonNullIf(expression = "value", result = true) + public boolean isValueNonNull3() { + return getValue() != null; + } + + @EnsuresNonNullIf(expression = "getValue()", result = true) + public boolean isValueNonNull4() { + return getValue() != null; + } } diff --git a/checker/tests/nullness/AssertNonNullTest.java b/checker/tests/nullness/AssertNonNullTest.java index c8e4a9ab4ce..79f1c94b461 100644 --- a/checker/tests/nullness/AssertNonNullTest.java +++ b/checker/tests/nullness/AssertNonNullTest.java @@ -2,19 +2,19 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class AssertNonNullTest { - public @Nullable String s; + public @Nullable String s; - // :: error: (contracts.postcondition.not.satisfied) - public @EnsuresNonNull("s") void makeNN() { - s = null; - } + // :: error: (contracts.postcondition.not.satisfied) + public @EnsuresNonNull("s") void makeNN() { + s = null; + } - public static void main(String[] args) { - AssertNonNullTest a = new AssertNonNullTest(); - // :: error: (dereference.of.nullable) - a.s.equals("we"); - AssertNonNullTest b = new AssertNonNullTest(); - b.makeNN(); - b.s.equals("we"); - } + public static void main(String[] args) { + AssertNonNullTest a = new AssertNonNullTest(); + // :: error: (dereference.of.nullable) + a.s.equals("we"); + AssertNonNullTest b = new AssertNonNullTest(); + b.makeNN(); + b.s.equals("we"); + } } diff --git a/checker/tests/nullness/AssertNullable.java b/checker/tests/nullness/AssertNullable.java index fbcb59bb8f1..814538fe29b 100644 --- a/checker/tests/nullness/AssertNullable.java +++ b/checker/tests/nullness/AssertNullable.java @@ -1,23 +1,23 @@ public class AssertNullable { - public static void main(String[] args) { - if (args.length >= 1) { - Boolean b = null; - // This will result in an NPE, not an AssertionError: - // Exception in thread "main" java.lang.NullPointerException - // Therefore, the Nullness Checker warns about this. - // :: error: (condition.nullable) - assert b; - } else { - String s = null; - // This is OK, the message will look like: - // Exception in thread "main" java.lang.AssertionError: null - assert 4 < 3 : s; - } + public static void main(String[] args) { + if (args.length >= 1) { + Boolean b = null; + // This will result in an NPE, not an AssertionError: + // Exception in thread "main" java.lang.NullPointerException + // Therefore, the Nullness Checker warns about this. + // :: error: (condition.nullable) + assert b; + } else { + String s = null; + // This is OK, the message will look like: + // Exception in thread "main" java.lang.AssertionError: null + assert 4 < 3 : s; } + } - void foo() { - String s = 3 > 2 ? null : "ba"; - // :: error: (dereference.of.nullable) - assert s.hashCode() > 4; - } + void foo() { + String s = 3 > 2 ? null : "ba"; + // :: error: (dereference.of.nullable) + assert s.hashCode() > 4; + } } diff --git a/checker/tests/nullness/AssertParameterNullness.java b/checker/tests/nullness/AssertParameterNullness.java index 51d760e15bb..8b9e1e111b5 100644 --- a/checker/tests/nullness/AssertParameterNullness.java +++ b/checker/tests/nullness/AssertParameterNullness.java @@ -3,29 +3,29 @@ public class AssertParameterNullness { - /** True iff both sequences are non-null and have the same length. */ - @EnsuresNonNullIf( - result = true, - expression = {"#1", "#2"}) - /* pure */ public static boolean sameLength( - final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { - if ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length) { - return true; - } - return false; + /** True iff both sequences are non-null and have the same length. */ + @EnsuresNonNullIf( + result = true, + expression = {"#1", "#2"}) + /* pure */ public static boolean sameLength( + final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { + if ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length) { + return true; } + return false; + } - /* pure */ public static boolean pairwiseEqual( - boolean @Nullable [] seq3, boolean @Nullable [] seq4) { - if (sameLength(seq3, seq4)) { - boolean b1 = seq3[0]; - boolean b2 = seq4[0]; - } else { - // :: error: (accessing.nullable) - boolean b1 = seq3[0]; - // :: error: (accessing.nullable) - boolean b2 = seq4[0]; - } - return true; + /* pure */ public static boolean pairwiseEqual( + boolean @Nullable [] seq3, boolean @Nullable [] seq4) { + if (sameLength(seq3, seq4)) { + boolean b1 = seq3[0]; + boolean b2 = seq4[0]; + } else { + // :: error: (accessing.nullable) + boolean b1 = seq3[0]; + // :: error: (accessing.nullable) + boolean b2 = seq4[0]; } + return true; + } } diff --git a/checker/tests/nullness/AssertTwice.java b/checker/tests/nullness/AssertTwice.java index cfad53777d6..41e45d5b7db 100644 --- a/checker/tests/nullness/AssertTwice.java +++ b/checker/tests/nullness/AssertTwice.java @@ -1,31 +1,31 @@ public class AssertTwice { - private void assertOnce() { - String methodDeclaration = null; - assert methodDeclaration != null; - methodDeclaration = null; - } + private void assertOnce() { + String methodDeclaration = null; + assert methodDeclaration != null; + methodDeclaration = null; + } - private void assertTwice() { - String methodDeclaration = null; - assert methodDeclaration != null; - assert methodDeclaration != null; - methodDeclaration = null; - } + private void assertTwice() { + String methodDeclaration = null; + assert methodDeclaration != null; + assert methodDeclaration != null; + methodDeclaration = null; + } - private void assertTwiceWithUse() { - String methodDeclaration = null; - assert methodDeclaration != null : "@AssumeAssertion(nullness)"; - methodDeclaration.toString(); - // :: warning: (nulltest.redundant) - assert methodDeclaration != null; - methodDeclaration = null; - } + private void assertTwiceWithUse() { + String methodDeclaration = null; + assert methodDeclaration != null : "@AssumeAssertion(nullness)"; + methodDeclaration.toString(); + // :: warning: (nulltest.redundant) + assert methodDeclaration != null; + methodDeclaration = null; + } - public static @org.checkerframework.checker.nullness.qual.Nullable Object n = "m"; + public static @org.checkerframework.checker.nullness.qual.Nullable Object n = "m"; - private void twiceWithChecks() { - assert n != null; - n = null; - } + private void twiceWithChecks() { + assert n != null; + n = null; + } } diff --git a/checker/tests/nullness/AssertWithStatic.java b/checker/tests/nullness/AssertWithStatic.java index 1dc393e0dac..ce819e2c753 100644 --- a/checker/tests/nullness/AssertWithStatic.java +++ b/checker/tests/nullness/AssertWithStatic.java @@ -4,53 +4,53 @@ public class AssertWithStatic { - static @Nullable String f; - - @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") - public boolean hasSysOut1() { - return AssertWithStatic.f != null; - } - - @EnsuresNonNullIf(result = true, expression = "f") - public boolean hasSysOut2() { - return AssertWithStatic.f != null; - } - - @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") - public boolean hasSysOut3() { - return f != null; - } - - @EnsuresNonNullIf(result = true, expression = "f") - public boolean hasSysOut4() { - return f != null; - } - - @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") - public boolean noSysOut1() { - return AssertWithStatic.f == null; - } - - @EnsuresNonNullIf(result = false, expression = "f") - public boolean noSysOut2() { - return AssertWithStatic.f == null; - } - - @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") - public boolean noSysOut3() { - return f == null; - } - - @EnsuresNonNullIf(result = false, expression = "f") - public boolean noSysOut4() { - return f == null; - } - - @EnsuresNonNull("AssertWithStatic.f") - // :: error: (contracts.postcondition.not.satisfied) - public void sysOutAfter1() {} - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - public void sysOutAfter2() {} + static @Nullable String f; + + @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") + public boolean hasSysOut1() { + return AssertWithStatic.f != null; + } + + @EnsuresNonNullIf(result = true, expression = "f") + public boolean hasSysOut2() { + return AssertWithStatic.f != null; + } + + @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") + public boolean hasSysOut3() { + return f != null; + } + + @EnsuresNonNullIf(result = true, expression = "f") + public boolean hasSysOut4() { + return f != null; + } + + @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") + public boolean noSysOut1() { + return AssertWithStatic.f == null; + } + + @EnsuresNonNullIf(result = false, expression = "f") + public boolean noSysOut2() { + return AssertWithStatic.f == null; + } + + @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") + public boolean noSysOut3() { + return f == null; + } + + @EnsuresNonNullIf(result = false, expression = "f") + public boolean noSysOut4() { + return f == null; + } + + @EnsuresNonNull("AssertWithStatic.f") + // :: error: (contracts.postcondition.not.satisfied) + public void sysOutAfter1() {} + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + public void sysOutAfter2() {} } diff --git a/checker/tests/nullness/Asserts.java b/checker/tests/nullness/Asserts.java index 3e4db381dac..7baeb88e6a5 100644 --- a/checker/tests/nullness/Asserts.java +++ b/checker/tests/nullness/Asserts.java @@ -3,76 +3,76 @@ public class Asserts { - void propogateToExpr() { - String s = "m"; - assert false : s.getClass(); - } + void propogateToExpr() { + String s = "m"; + assert false : s.getClass(); + } - void incorrectAssertExpr() { - String s = null; - assert s != null : "@AssumeAssertion(nullness)"; // error - s.getClass(); // OK - } + void incorrectAssertExpr() { + String s = null; + assert s != null : "@AssumeAssertion(nullness)"; // error + s.getClass(); // OK + } - void correctAssertExpr() { - String s = null; - assert s == null : "@AssumeAssertion(nullness)"; - // :: error: (dereference.of.nullable) - s.getClass(); // error - } + void correctAssertExpr() { + String s = null; + assert s == null : "@AssumeAssertion(nullness)"; + // :: error: (dereference.of.nullable) + s.getClass(); // error + } - class ArrayCell { - @Nullable Object[] vals = new @Nullable Object[0]; - } + class ArrayCell { + @Nullable Object[] vals = new @Nullable Object[0]; + } - void assertComplexExpr(ArrayCell ac, int i) { - assert ac.vals[i] != null : "@AssumeAssertion(nullness)"; - @NonNull Object o = ac.vals[i]; - i = 10; - // :: error: (assignment.type.incompatible) - @NonNull Object o2 = ac.vals[i]; - } + void assertComplexExpr(ArrayCell ac, int i) { + assert ac.vals[i] != null : "@AssumeAssertion(nullness)"; + @NonNull Object o = ac.vals[i]; + i = 10; + // :: error: (assignment.type.incompatible) + @NonNull Object o2 = ac.vals[i]; + } - boolean pairwiseEqual(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - if (!sameLength(seq1, seq2)) { - return false; - } - if (ne(seq1[0], seq2[0])) {} - return true; + boolean pairwiseEqual(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + if (!sameLength(seq1, seq2)) { + return false; } + if (ne(seq1[0], seq2[0])) {} + return true; + } - @EnsuresNonNullIf( - result = true, - expression = {"#1", "#2"}) - boolean sameLength(final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { - // don't bother with the implementation - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf( + result = true, + expression = {"#1", "#2"}) + boolean sameLength(final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { + // don't bother with the implementation + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - static boolean ne(boolean a, boolean b) { - return true; - } + static boolean ne(boolean a, boolean b) { + return true; + } - void testAssertBad(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - assert sameLength(seq1, seq2); - // the @EnsuresNonNullIf is not taken from the assert, as it doesn't contain "nullness" - // :: error: (accessing.nullable) - if (seq1[0]) {} - } + void testAssertBad(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + assert sameLength(seq1, seq2); + // the @EnsuresNonNullIf is not taken from the assert, as it doesn't contain "nullness" + // :: error: (accessing.nullable) + if (seq1[0]) {} + } - void testAssertGood(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - assert sameLength(seq1, seq2) : "@AssumeAssertion(nullness)"; - // The explanation contains "nullness" and we therefore take the additional assumption - if (seq1[0]) {} - } + void testAssertGood(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + assert sameLength(seq1, seq2) : "@AssumeAssertion(nullness)"; + // The explanation contains "nullness" and we therefore take the additional assumption + if (seq1[0]) {} + } - void testAssertAnd(@Nullable Object o) { - assert o != null && o.hashCode() > 6; - } + void testAssertAnd(@Nullable Object o) { + assert o != null && o.hashCode() > 6; + } - void testAssertOr(@Nullable Object o) { - // :: error: (dereference.of.nullable) - assert o != null || o.hashCode() > 6; - } + void testAssertOr(@Nullable Object o) { + // :: error: (dereference.of.nullable) + assert o != null || o.hashCode() > 6; + } } diff --git a/checker/tests/nullness/BinaryOp.java b/checker/tests/nullness/BinaryOp.java index 64a96590dc8..5ee9d3d29d1 100644 --- a/checker/tests/nullness/BinaryOp.java +++ b/checker/tests/nullness/BinaryOp.java @@ -2,7 +2,7 @@ import org.checkerframework.checker.nullness.qual.*; public class BinaryOp { - void test(@UnknownInitialization Object obj) { - throw new Error("" + obj); - } + void test(@UnknownInitialization Object obj) { + throw new Error("" + obj); + } } diff --git a/checker/tests/nullness/BinarySearch.java b/checker/tests/nullness/BinarySearch.java index d1be308f32f..727b467d1c1 100644 --- a/checker/tests/nullness/BinarySearch.java +++ b/checker/tests/nullness/BinarySearch.java @@ -4,12 +4,12 @@ // Searching through nullable array components // and for nullable keys is forbidden. public class BinarySearch { - @Nullable Object @NonNull [] arr = {"a", "b", null}; + @Nullable Object @NonNull [] arr = {"a", "b", null}; - void search(@Nullable Object key) { - // :: error: (argument.type.incompatible) - int res = java.util.Arrays.binarySearch(arr, key); - // :: error: (argument.type.incompatible) - res = java.util.Arrays.binarySearch(arr, 0, 4, key); - } + void search(@Nullable Object key) { + // :: error: (argument.type.incompatible) + int res = java.util.Arrays.binarySearch(arr, key); + // :: error: (argument.type.incompatible) + res = java.util.Arrays.binarySearch(arr, 0, 4, key); + } } diff --git a/checker/tests/nullness/BoxingNullness.java b/checker/tests/nullness/BoxingNullness.java index c6b30888c16..112aec26b9c 100644 --- a/checker/tests/nullness/BoxingNullness.java +++ b/checker/tests/nullness/BoxingNullness.java @@ -1,145 +1,145 @@ import org.checkerframework.checker.nullness.qual.*; public class BoxingNullness { - void withinOperation() { - Integer i1 = 3; - int i1u = i1 + 2; // valid - Integer i2 = null; - // :: error: (unboxing.of.nullable) - int i2u = i2 + 2; // invalid - Integer i3 = i1; - i3.toString(); - } - - void withinAssignment() { - Integer i1 = 5; - int i1u = i1; - Integer i2 = null; - // :: error: (unboxing.of.nullable) - int i2u = i2; // invalid - } - - void validWithinUnary() { - // within blocks to stop flow - Integer i1 = 1, i2 = 1, i3 = 1, i4 = 1; - ++i1; - i2++; - } - - void invalidWithinUnary() { - // within blocks to stop flow - Integer i1 = null, i2 = null, i3 = null, i4 = null; - // :: error: (unboxing.of.nullable) - ++i1; // invalid - // :: error: (unboxing.of.nullable) - i2++; // invalid - } - - void validCompoundAssignmentsAsVariable() { - @NonNull Integer i = 0; // nonnull is needed because flow is buggy - i += 1; - i -= 1; - @NonNull Boolean b = true; - b &= true; - } - - void invalidCompoundAssignmentsAsVariable() { - Integer i = null; - // :: error: (unboxing.of.nullable) - i += 1; // invalid - Boolean b = null; - // :: error: (unboxing.of.nullable) - b &= true; // invalid - } - - void invalidCompoundAssignmentAsValue() { - @NonNull Integer var = 3; - Integer val = null; - // :: error: (unboxing.of.nullable) - var += val; - Boolean b1 = null; - boolean b2 = true; - // :: error: (unboxing.of.nullable) - b2 &= b1; // invalid - } - - void randomValidStringOperations() { - String s = null; - s += null; - } - - void equalityTest() { - Integer bN = null; - Integer bN1 = null; - Integer bN2 = null; - Integer bN3 = null; - Integer bN4 = null; - Integer bN5 = null; - Integer bN6 = null; - Integer bN7 = null; - Integer b1 = 1; - int u1 = 1; - System.out.println(bN == bN1); // valid - System.out.println(bN2 == b1); // valid - System.out.println(bN3 != bN4); // valid - System.out.println(bN5 != b1); // valid - - System.out.println(u1 == b1); - System.out.println(u1 != b1); - System.out.println(u1 == u1); - System.out.println(u1 != u1); - - // :: error: (unboxing.of.nullable) - System.out.println(bN6 == u1); // invalid - // :: error: (unboxing.of.nullable) - System.out.println(bN7 != u1); // invalid - } - - void addition() { - Integer bN = null; - Integer bN1 = null; - Integer bN2 = null; - Integer bN3 = null; - Integer b1 = 1; - int u1 = 1; - // :: error: (unboxing.of.nullable) - System.out.println(bN + bN1); // invalid - // :: error: (unboxing.of.nullable) - System.out.println(bN2 + b1); // invalid - - System.out.println(u1 + b1); - System.out.println(u1 + u1); - - // :: error: (unboxing.of.nullable) - System.out.println(bN3 + u1); // invalid - } - - void visitCast() { - Integer bN = null; - Integer bN2 = null; - Integer b1 = 1; - int u1 = 1; - - println(bN); - // :: error: (unboxing.of.nullable) - println((int) bN2); // invalid - - println(b1); - println((int) b1); - - println(u1); - println((int) u1); - } - - void println(@Nullable Object o) {} - - void testObjectString() { - Object o = null; - o += "m"; - } - - void testCharString() { - CharSequence cs = null; - cs += "m"; - } + void withinOperation() { + Integer i1 = 3; + int i1u = i1 + 2; // valid + Integer i2 = null; + // :: error: (unboxing.of.nullable) + int i2u = i2 + 2; // invalid + Integer i3 = i1; + i3.toString(); + } + + void withinAssignment() { + Integer i1 = 5; + int i1u = i1; + Integer i2 = null; + // :: error: (unboxing.of.nullable) + int i2u = i2; // invalid + } + + void validWithinUnary() { + // within blocks to stop flow + Integer i1 = 1, i2 = 1, i3 = 1, i4 = 1; + ++i1; + i2++; + } + + void invalidWithinUnary() { + // within blocks to stop flow + Integer i1 = null, i2 = null, i3 = null, i4 = null; + // :: error: (unboxing.of.nullable) + ++i1; // invalid + // :: error: (unboxing.of.nullable) + i2++; // invalid + } + + void validCompoundAssignmentsAsVariable() { + @NonNull Integer i = 0; // nonnull is needed because flow is buggy + i += 1; + i -= 1; + @NonNull Boolean b = true; + b &= true; + } + + void invalidCompoundAssignmentsAsVariable() { + Integer i = null; + // :: error: (unboxing.of.nullable) + i += 1; // invalid + Boolean b = null; + // :: error: (unboxing.of.nullable) + b &= true; // invalid + } + + void invalidCompoundAssignmentAsValue() { + @NonNull Integer var = 3; + Integer val = null; + // :: error: (unboxing.of.nullable) + var += val; + Boolean b1 = null; + boolean b2 = true; + // :: error: (unboxing.of.nullable) + b2 &= b1; // invalid + } + + void randomValidStringOperations() { + String s = null; + s += null; + } + + void equalityTest() { + Integer bN = null; + Integer bN1 = null; + Integer bN2 = null; + Integer bN3 = null; + Integer bN4 = null; + Integer bN5 = null; + Integer bN6 = null; + Integer bN7 = null; + Integer b1 = 1; + int u1 = 1; + System.out.println(bN == bN1); // valid + System.out.println(bN2 == b1); // valid + System.out.println(bN3 != bN4); // valid + System.out.println(bN5 != b1); // valid + + System.out.println(u1 == b1); + System.out.println(u1 != b1); + System.out.println(u1 == u1); + System.out.println(u1 != u1); + + // :: error: (unboxing.of.nullable) + System.out.println(bN6 == u1); // invalid + // :: error: (unboxing.of.nullable) + System.out.println(bN7 != u1); // invalid + } + + void addition() { + Integer bN = null; + Integer bN1 = null; + Integer bN2 = null; + Integer bN3 = null; + Integer b1 = 1; + int u1 = 1; + // :: error: (unboxing.of.nullable) + System.out.println(bN + bN1); // invalid + // :: error: (unboxing.of.nullable) + System.out.println(bN2 + b1); // invalid + + System.out.println(u1 + b1); + System.out.println(u1 + u1); + + // :: error: (unboxing.of.nullable) + System.out.println(bN3 + u1); // invalid + } + + void visitCast() { + Integer bN = null; + Integer bN2 = null; + Integer b1 = 1; + int u1 = 1; + + println(bN); + // :: error: (unboxing.of.nullable) + println((int) bN2); // invalid + + println(b1); + println((int) b1); + + println(u1); + println((int) u1); + } + + void println(@Nullable Object o) {} + + void testObjectString() { + Object o = null; + o += "m"; + } + + void testCharString() { + CharSequence cs = null; + cs += "m"; + } } diff --git a/checker/tests/nullness/Bug102.java b/checker/tests/nullness/Bug102.java index c178b8d653e..b3602602aff 100644 --- a/checker/tests/nullness/Bug102.java +++ b/checker/tests/nullness/Bug102.java @@ -1,19 +1,19 @@ // Test case for Issue 102 public final class Bug102 { - class C {} + class C {} - void bug1() { - C c = new C<>(); - m(c); - m(c); // note: the bug disapear if calling m only once - } + void bug1() { + C c = new C<>(); + m(c); + m(c); // note: the bug disapear if calling m only once + } - void bug2() { - C c = new C<>(); - m(c); - } + void bug2() { + C c = new C<>(); + m(c); + } - // :: error: (invalid.polymorphic.qualifier) - <@org.checkerframework.checker.nullness.qual.PolyNull S> void m( - final C<@org.checkerframework.checker.nullness.qual.PolyNull String> a) {} + // :: error: (invalid.polymorphic.qualifier) + <@org.checkerframework.checker.nullness.qual.PolyNull S> void m( + final C<@org.checkerframework.checker.nullness.qual.PolyNull String> a) {} } diff --git a/checker/tests/nullness/Bug103.java b/checker/tests/nullness/Bug103.java index 64f984d645b..7b2b16460dc 100644 --- a/checker/tests/nullness/Bug103.java +++ b/checker/tests/nullness/Bug103.java @@ -10,78 +10,78 @@ class HR {} // Crazy: remove the "extends HR" and it compiles public class Bug103 extends HR { - // Crazy: add a 23th element as for example "hello" and it compiles - // Crazy: replace IG.C with IG.C+"" and it compiles - // Crazy: remove final and it compiles - // Crazy: replace with new String[22] and it compiles - // Crazy: reduce to less than 5 distinct values and it compiles (replace IG.D with IG.C) - final String[] ids = { - IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, - IG.C, IG.C, IG.C, IG.D, IG.E, IG.F, IG.G - }; + // Crazy: add a 23th element as for example "hello" and it compiles + // Crazy: replace IG.C with IG.C+"" and it compiles + // Crazy: remove final and it compiles + // Crazy: replace with new String[22] and it compiles + // Crazy: reduce to less than 5 distinct values and it compiles (replace IG.D with IG.C) + final String[] ids = { + IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, + IG.C, IG.C, IG.D, IG.E, IG.F, IG.G + }; - // Crazy: remove arg u and it compiles - // Crazy: remove any line of m1 and it compiles - // Crazy: replace two o args by null and it compiles - void m1(CC o, Object u) { - String cc = m2(o); - String dd = m2(o); - } + // Crazy: remove arg u and it compiles + // Crazy: remove any line of m1 and it compiles + // Crazy: replace two o args by null and it compiles + void m1(CC o, Object u) { + String cc = m2(o); + String dd = m2(o); + } - String m2(final CC c) { - return "a"; - } + String m2(final CC c) { + return "a"; + } - // Crazy: remove ids.length and it compiles - // replace return type List with ArrayList and it compiles - List m3(CC c) { - ArrayList lc = new ArrayList<>(ids.length); - return lc; - } + // Crazy: remove ids.length and it compiles + // replace return type List with ArrayList and it compiles + List m3(CC c) { + ArrayList lc = new ArrayList<>(ids.length); + return lc; + } - // Crazy: comment out the whole unused LV class and it compiles - // Crazy: comment one of the following 4 lines out and it compiles - static class LV { - static String a = "a"; - static String b = "a"; - static String c = "a"; - static String d = "a"; - } + // Crazy: comment out the whole unused LV class and it compiles + // Crazy: comment one of the following 4 lines out and it compiles + static class LV { + static String a = "a"; + static String b = "a"; + static String c = "a"; + static String d = "a"; + } - class IG { - // Crazy: comment one of the following 8 lines out and it compiles - String C1 = "1"; - String C2 = "1"; - String C3 = "1"; - String C4 = "1"; - String C5 = "1"; - String C6 = "1"; - String C7 = "1"; - String C8 = "1"; + class IG { + // Crazy: comment one of the following 8 lines out and it compiles + String C1 = "1"; + String C2 = "1"; + String C3 = "1"; + String C4 = "1"; + String C5 = "1"; + String C6 = "1"; + String C7 = "1"; + String C8 = "1"; - static final String C = "c"; - static final String D = C; - static final String E = C; - static final String F = C; + static final String C = "c"; + static final String D = C; + static final String E = C; + static final String F = C; - // Crazy: comment one of the following 18 lines out and it compiles - static final String G = C; - static final String H = C; - static final String I = C; - static final String J = C; - static final String K = C; - static final String L = C; - static final String M = C; - static final String N = C; - static final String O = C; - static final String P = C; - static final String Q = C; - static final String R = C; - static final String S = C; - static final String T = C; - static final String U = C; - static final String V = C; - static final String W = C; - static final String X = C; - } + // Crazy: comment one of the following 18 lines out and it compiles + static final String G = C; + static final String H = C; + static final String I = C; + static final String J = C; + static final String K = C; + static final String L = C; + static final String M = C; + static final String N = C; + static final String O = C; + static final String P = C; + static final String Q = C; + static final String R = C; + static final String S = C; + static final String T = C; + static final String U = C; + static final String V = C; + static final String W = C; + static final String X = C; + } } diff --git a/checker/tests/nullness/CallSuper.java b/checker/tests/nullness/CallSuper.java index 6f3ecbb9789..f9407105c0c 100644 --- a/checker/tests/nullness/CallSuper.java +++ b/checker/tests/nullness/CallSuper.java @@ -1,16 +1,15 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.io.*; +import org.checkerframework.checker.nullness.qual.*; class MyFilterInputStream { - MyFilterInputStream(InputStream in) {} + MyFilterInputStream(InputStream in) {} } public class CallSuper extends MyFilterInputStream { - CallSuper(@Nullable InputStream in) { - // The MyFilterInputStream constructor takes a NonNull argument - // (but that's not true of FilterInputStream itself). - // :: error: (argument.type.incompatible) - super(in); - } + CallSuper(@Nullable InputStream in) { + // The MyFilterInputStream constructor takes a NonNull argument + // (but that's not true of FilterInputStream itself). + // :: error: (argument.type.incompatible) + super(in); + } } diff --git a/checker/tests/nullness/CastTypeVariable.java b/checker/tests/nullness/CastTypeVariable.java index 323ed8c0487..367af70b8e8 100644 --- a/checker/tests/nullness/CastTypeVariable.java +++ b/checker/tests/nullness/CastTypeVariable.java @@ -1,17 +1,17 @@ import java.util.Map; class MyAnnotatedTypeMirror { - void addAnnotations() {} + void addAnnotations() {} } class MyAnnotatedTypeVariable extends MyAnnotatedTypeMirror {} public class CastTypeVariable { - public static V mapGetHelper( - Map mappings, MyAnnotatedTypeVariable key) { - V possValue = (V) mappings.get(key); - // :: error: (dereference.of.nullable) - possValue.addAnnotations(); - return possValue; - } + public static V mapGetHelper( + Map mappings, MyAnnotatedTypeVariable key) { + V possValue = (V) mappings.get(key); + // :: error: (dereference.of.nullable) + possValue.addAnnotations(); + return possValue; + } } diff --git a/checker/tests/nullness/CastsNullness.java b/checker/tests/nullness/CastsNullness.java index cb5deb34eec..ce91cd71d24 100644 --- a/checker/tests/nullness/CastsNullness.java +++ b/checker/tests/nullness/CastsNullness.java @@ -2,101 +2,101 @@ public class CastsNullness { - void test(String nonNullParam) { - Object lc1 = (Object) nonNullParam; - lc1.toString(); + void test(String nonNullParam) { + Object lc1 = (Object) nonNullParam; + lc1.toString(); - String nullable = null; - Object lc2 = (Object) nullable; - // :: error: (dereference.of.nullable) - lc2.toString(); // error - } + String nullable = null; + Object lc2 = (Object) nullable; + // :: error: (dereference.of.nullable) + lc2.toString(); // error + } - void testBoxing() { - Integer b = null; - // :: error: (unboxing.of.nullable) - int i = b; - // no error, because there was already a nullpointer exception - Object o = (int) b; - } + void testBoxing() { + Integer b = null; + // :: error: (unboxing.of.nullable) + int i = b; + // no error, because there was already a nullpointer exception + Object o = (int) b; + } - void testUnsafeCast(@Nullable Object x) { - // :: warning: (cast.unsafe) - @NonNull Object y = (@NonNull Object) x; - y.toString(); - } + void testUnsafeCast(@Nullable Object x) { + // :: warning: (cast.unsafe) + @NonNull Object y = (@NonNull Object) x; + y.toString(); + } - void testUnsafeCastArray1(@Nullable Object[] x) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - @NonNull Object[] y = (@NonNull Object[]) x; - y[0].toString(); - } + void testUnsafeCastArray1(@Nullable Object[] x) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + @NonNull Object[] y = (@NonNull Object[]) x; + y[0].toString(); + } - void testUnsafeCastArray2(@NonNull Object x) { - // We don't know about the component type of x -> warn - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - @NonNull Object[] y = (@NonNull Object[]) x; - y[0].toString(); - } + void testUnsafeCastArray2(@NonNull Object x) { + // We don't know about the component type of x -> warn + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + @NonNull Object[] y = (@NonNull Object[]) x; + y[0].toString(); + } - void testUnsafeCastList1(java.util.ArrayList<@Nullable Object> x) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - java.util.List<@NonNull Object> y = (java.util.List<@NonNull Object>) x; - y.get(0).toString(); - // TODO:: warning: (cast.unsafe) - java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; - java.util.List<@Nullable Object> y3 = (java.util.List<@Nullable Object>) x; - } + void testUnsafeCastList1(java.util.ArrayList<@Nullable Object> x) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + java.util.List<@NonNull Object> y = (java.util.List<@NonNull Object>) x; + y.get(0).toString(); + // TODO:: warning: (cast.unsafe) + java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; + java.util.List<@Nullable Object> y3 = (java.util.List<@Nullable Object>) x; + } - void testUnsafeCastList2(java.util.List<@Nullable Object> x) { - java.util.List<@Nullable Object> y = (java.util.ArrayList<@Nullable Object>) x; - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; - } + void testUnsafeCastList2(java.util.List<@Nullable Object> x) { + java.util.List<@Nullable Object> y = (java.util.ArrayList<@Nullable Object>) x; + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; + } - void testUnsafeCastList3(@NonNull Object x) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - java.util.List<@Nullable Object> y = (java.util.List<@Nullable Object>) x; - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; - } + void testUnsafeCastList3(@NonNull Object x) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + java.util.List<@Nullable Object> y = (java.util.List<@Nullable Object>) x; + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; + } - void testSuppression(@Nullable Object x) { - // :: error: (assignment.type.incompatible) - @NonNull String s1 = (String) x; - @SuppressWarnings("nullness") - @NonNull String s2 = (String) x; - } + void testSuppression(@Nullable Object x) { + // :: error: (assignment.type.incompatible) + @NonNull String s1 = (String) x; + @SuppressWarnings("nullness") + @NonNull String s2 = (String) x; + } - class Generics { - T t; - @Nullable T nt; + class Generics { + T t; + @Nullable T nt; - Generics(T t) { - this.t = t; - this.nt = t; - } - - void m() { - // :: error: (assignment.type.incompatible) - t = (@Nullable T) null; - nt = (@Nullable T) null; - // :: warning: (cast.unsafe) - t = (T) null; - // :: warning: (cast.unsafe) - nt = (T) null; - } + Generics(T t) { + this.t = t; + this.nt = t; } - void testSafeCasts() { - // :: error: (nullness.on.primitive) - Integer x = (@Nullable int) 1; + void m() { + // :: error: (assignment.type.incompatible) + t = (@Nullable T) null; + nt = (@Nullable T) null; + // :: warning: (cast.unsafe) + t = (T) null; + // :: warning: (cast.unsafe) + nt = (T) null; } + } + + void testSafeCasts() { + // :: error: (nullness.on.primitive) + Integer x = (@Nullable int) 1; + } } diff --git a/checker/tests/nullness/ChainAssignment.java b/checker/tests/nullness/ChainAssignment.java index 45557c6c4af..db5c86a16d3 100644 --- a/checker/tests/nullness/ChainAssignment.java +++ b/checker/tests/nullness/ChainAssignment.java @@ -1,44 +1,44 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ChainAssignment { - @Nullable Object a; - @Nullable Object b; - Object x = new Object(); - Object y = new Object(); - - void m1() { - a = b = new Object(); - } - - void m2() { - this.a = this.b = new Object(); - } - - void m3() { - a = this.b = new Object(); - } - - void m4() { - this.a = b = new Object(); - } - - void n1() { - // :: error: (assignment.type.incompatible) - x = y = null; - } - - void n2() { - // :: error: (assignment.type.incompatible) - this.x = this.y = null; - } - - void n3() { - // :: error: (assignment.type.incompatible) - x = this.y = null; - } - - void n4() { - // :: error: (assignment.type.incompatible) - this.x = y = null; - } + @Nullable Object a; + @Nullable Object b; + Object x = new Object(); + Object y = new Object(); + + void m1() { + a = b = new Object(); + } + + void m2() { + this.a = this.b = new Object(); + } + + void m3() { + a = this.b = new Object(); + } + + void m4() { + this.a = b = new Object(); + } + + void n1() { + // :: error: (assignment.type.incompatible) + x = y = null; + } + + void n2() { + // :: error: (assignment.type.incompatible) + this.x = this.y = null; + } + + void n3() { + // :: error: (assignment.type.incompatible) + x = this.y = null; + } + + void n4() { + // :: error: (assignment.type.incompatible) + this.x = y = null; + } } diff --git a/checker/tests/nullness/ChicoryPremain.java b/checker/tests/nullness/ChicoryPremain.java index 10c72e033a5..98110b742ae 100644 --- a/checker/tests/nullness/ChicoryPremain.java +++ b/checker/tests/nullness/ChicoryPremain.java @@ -1,13 +1,13 @@ package daikon.chicory; public class ChicoryPremain { - public static void premain(ClassLoader loader) { - Object transformer = null; - try { - transformer = loader.loadClass("Foo").getDeclaredConstructor().newInstance(); - transformer.getClass(); - } catch (Exception e1) { - throw new RuntimeException("Exception", e1); - } + public static void premain(ClassLoader loader) { + Object transformer = null; + try { + transformer = loader.loadClass("Foo").getDeclaredConstructor().newInstance(); + transformer.getClass(); + } catch (Exception e1) { + throw new RuntimeException("Exception", e1); } + } } diff --git a/checker/tests/nullness/ClassGetCanonicalName.java b/checker/tests/nullness/ClassGetCanonicalName.java index beaba3127fe..0c1779fa316 100644 --- a/checker/tests/nullness/ClassGetCanonicalName.java +++ b/checker/tests/nullness/ClassGetCanonicalName.java @@ -1,5 +1,5 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class ClassGetCanonicalName { - @NonNull String s = ClassGetCanonicalName.class.getCanonicalName(); + @NonNull String s = ClassGetCanonicalName.class.getCanonicalName(); } diff --git a/checker/tests/nullness/CompoundAssign.java b/checker/tests/nullness/CompoundAssign.java index e9cf4075d09..ed7c53266e6 100644 --- a/checker/tests/nullness/CompoundAssign.java +++ b/checker/tests/nullness/CompoundAssign.java @@ -1,13 +1,13 @@ public class CompoundAssign { - void m(String args) { - String arg = ""; - for (int ii = 0; ii < args.length(); ii++) { - if ('x' == 'y') { - arg += 'x'; - } else { - arg = ""; - } - } - if (arg.equals("")) {} + void m(String args) { + String arg = ""; + for (int ii = 0; ii < args.length(); ii++) { + if ('x' == 'y') { + arg += 'x'; + } else { + arg = ""; + } } + if (arg.equals("")) {} + } } diff --git a/checker/tests/nullness/ConditionalNullness.java b/checker/tests/nullness/ConditionalNullness.java index 549f4d9df6c..167104f840f 100644 --- a/checker/tests/nullness/ConditionalNullness.java +++ b/checker/tests/nullness/ConditionalNullness.java @@ -3,94 +3,94 @@ public class ConditionalNullness { - @EnsuresNonNullIf( - expression = {"field", "method()"}, - result = true) - boolean checkNonNull() { - // don't bother with the implementation - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf( + expression = {"field", "method()"}, + result = true) + boolean checkNonNull() { + // don't bother with the implementation + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @Nullable Object field = null; + @Nullable Object field = null; - @org.checkerframework.dataflow.qual.Pure - @Nullable Object method() { - return "m"; - } + @org.checkerframework.dataflow.qual.Pure + @Nullable Object method() { + return "m"; + } - void testSelfWithCheck() { - ConditionalNullness other = new ConditionalNullness(); - if (checkNonNull()) { - field.toString(); - method().toString(); - // :: error: (dereference.of.nullable) - other.field.toString(); // error - // :: error: (dereference.of.nullable) - other.method().toString(); // error - } - // :: error: (dereference.of.nullable) - method().toString(); // error + void testSelfWithCheck() { + ConditionalNullness other = new ConditionalNullness(); + if (checkNonNull()) { + field.toString(); + method().toString(); + // :: error: (dereference.of.nullable) + other.field.toString(); // error + // :: error: (dereference.of.nullable) + other.method().toString(); // error } + // :: error: (dereference.of.nullable) + method().toString(); // error + } - void testSelfWithoutCheck() { - // :: error: (dereference.of.nullable) - field.toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error - } + void testSelfWithoutCheck() { + // :: error: (dereference.of.nullable) + field.toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error + } - void testSelfWithCheckNegation() { - if (checkNonNull()) { - // nothing to do - } else { - // :: error: (dereference.of.nullable) - field.toString(); // error - } - field.toString(); // error + void testSelfWithCheckNegation() { + if (checkNonNull()) { + // nothing to do + } else { + // :: error: (dereference.of.nullable) + field.toString(); // error } + field.toString(); // error + } - void testOtherWithCheck() { - ConditionalNullness other = new ConditionalNullness(); - if (other.checkNonNull()) { - other.field.toString(); - other.method().toString(); - // :: error: (dereference.of.nullable) - field.toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error - } - // :: error: (dereference.of.nullable) - other.method().toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error + void testOtherWithCheck() { + ConditionalNullness other = new ConditionalNullness(); + if (other.checkNonNull()) { + other.field.toString(); + other.method().toString(); + // :: error: (dereference.of.nullable) + field.toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error } + // :: error: (dereference.of.nullable) + other.method().toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error + } - void testOtherWithoutCheck() { - ConditionalNullness other = new ConditionalNullness(); - // :: error: (dereference.of.nullable) - other.field.toString(); // error - // :: error: (dereference.of.nullable) - other.method().toString(); // error - // :: error: (dereference.of.nullable) - field.toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error - } + void testOtherWithoutCheck() { + ConditionalNullness other = new ConditionalNullness(); + // :: error: (dereference.of.nullable) + other.field.toString(); // error + // :: error: (dereference.of.nullable) + other.method().toString(); // error + // :: error: (dereference.of.nullable) + field.toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error + } - void testOtherWithCheckNegation() { - ConditionalNullness other = new ConditionalNullness(); - if (other.checkNonNull()) { - // nothing to do - } else { - // :: error: (dereference.of.nullable) - other.field.toString(); // error - // :: error: (dereference.of.nullable) - other.method().toString(); // error - // :: error: (dereference.of.nullable) - field.toString(); // error - } - // :: error: (dereference.of.nullable) - field.toString(); // error + void testOtherWithCheckNegation() { + ConditionalNullness other = new ConditionalNullness(); + if (other.checkNonNull()) { + // nothing to do + } else { + // :: error: (dereference.of.nullable) + other.field.toString(); // error + // :: error: (dereference.of.nullable) + other.method().toString(); // error + // :: error: (dereference.of.nullable) + field.toString(); // error } + // :: error: (dereference.of.nullable) + field.toString(); // error + } } diff --git a/checker/tests/nullness/ConditionalOr.java b/checker/tests/nullness/ConditionalOr.java index 86981c2b07a..a4b66f2dd6b 100644 --- a/checker/tests/nullness/ConditionalOr.java +++ b/checker/tests/nullness/ConditionalOr.java @@ -2,9 +2,9 @@ public class ConditionalOr { - void test(@Nullable Object o) { - if (o == null || o.toString() == "...") { - // ... - } + void test(@Nullable Object o) { + if (o == null || o.toString() == "...") { + // ... } + } } diff --git a/checker/tests/nullness/ConditionalPolyNull.java b/checker/tests/nullness/ConditionalPolyNull.java index 69cad88a08a..91b183ad25f 100644 --- a/checker/tests/nullness/ConditionalPolyNull.java +++ b/checker/tests/nullness/ConditionalPolyNull.java @@ -3,38 +3,38 @@ import org.checkerframework.checker.nullness.qual.PolyNull; class ConditionalPolyNull { - @PolyNull String toLowerCaseA(@PolyNull String text) { - return text == null ? null : text.toLowerCase(); - } + @PolyNull String toLowerCaseA(@PolyNull String text) { + return text == null ? null : text.toLowerCase(); + } - @PolyNull String toLowerCaseB(@PolyNull String text) { - return text != null ? text.toLowerCase() : null; - } + @PolyNull String toLowerCaseB(@PolyNull String text) { + return text != null ? text.toLowerCase() : null; + } - @PolyNull String toLowerCaseC(@PolyNull String text) { - // :: error: (dereference.of.nullable) - // :: error: (return.type.incompatible) - return text == null ? text.toLowerCase() : null; - } + @PolyNull String toLowerCaseC(@PolyNull String text) { + // :: error: (dereference.of.nullable) + // :: error: (return.type.incompatible) + return text == null ? text.toLowerCase() : null; + } - @PolyNull String toLowerCaseD(@PolyNull String text) { - // :: error: (return.type.incompatible) - // :: error: (dereference.of.nullable) - return text != null ? null : text.toLowerCase(); - } + @PolyNull String toLowerCaseD(@PolyNull String text) { + // :: error: (return.type.incompatible) + // :: error: (dereference.of.nullable) + return text != null ? null : text.toLowerCase(); + } - @PolyNull String foo(@PolyNull String param) { - if (param != null) { - // @PolyNull is really @NonNull, so change - // the type of param to @NonNull. - return param.toString(); - } - if (param == null) { - // @PolyNull is really @Nullable, so change - // the type of param to @Nullable. - param = null; - return null; - } - return param; + @PolyNull String foo(@PolyNull String param) { + if (param != null) { + // @PolyNull is really @NonNull, so change + // the type of param to @NonNull. + return param.toString(); + } + if (param == null) { + // @PolyNull is really @Nullable, so change + // the type of param to @Nullable. + param = null; + return null; } + return param; + } } diff --git a/checker/tests/nullness/Conditions.java b/checker/tests/nullness/Conditions.java index 68428001bbc..fb7b52ace03 100644 --- a/checker/tests/nullness/Conditions.java +++ b/checker/tests/nullness/Conditions.java @@ -3,38 +3,38 @@ public class Conditions { - @Nullable Object f; + @Nullable Object f; - void test1(Conditions c) { - if (!(c.f != null)) { - return; - } - c.f.hashCode(); + void test1(Conditions c) { + if (!(c.f != null)) { + return; } + c.f.hashCode(); + } - void test2(Conditions c) { - if (!(c.f != null) || 5 > 9) { - return; - } - c.f.hashCode(); + void test2(Conditions c) { + if (!(c.f != null) || 5 > 9) { + return; } + c.f.hashCode(); + } - @EnsuresNonNullIf(expression = "f", result = true) - public boolean isNN() { - return (f != null); - } + @EnsuresNonNullIf(expression = "f", result = true) + public boolean isNN() { + return (f != null); + } - void test1m(Conditions c) { - if (!(c.isNN())) { - return; - } - c.f.hashCode(); + void test1m(Conditions c) { + if (!(c.isNN())) { + return; } + c.f.hashCode(); + } - void test2m(Conditions c) { - if (!(c.isNN()) || 5 > 9) { - return; - } - c.f.hashCode(); + void test2m(Conditions c) { + if (!(c.isNN()) || 5 > 9) { + return; } + c.f.hashCode(); + } } diff --git a/checker/tests/nullness/ConstructorPostcondition.java b/checker/tests/nullness/ConstructorPostcondition.java index 3068540e5d0..da78185951e 100644 --- a/checker/tests/nullness/ConstructorPostcondition.java +++ b/checker/tests/nullness/ConstructorPostcondition.java @@ -2,21 +2,21 @@ public class ConstructorPostcondition { - class Box { - @Nullable Object f; - } + class Box { + @Nullable Object f; + } - @EnsuresNonNull("#1.f") - // :: error: (contracts.postcondition.not.satisfied) - ConstructorPostcondition(Box b) {} + @EnsuresNonNull("#1.f") + // :: error: (contracts.postcondition.not.satisfied) + ConstructorPostcondition(Box b) {} - @EnsuresNonNull("#1.f") - ConstructorPostcondition(Box b, Object o) { - b.f = o; - } + @EnsuresNonNull("#1.f") + ConstructorPostcondition(Box b, Object o) { + b.f = o; + } - void foo(Box b) { - ConstructorPostcondition x = new ConstructorPostcondition(b, "x"); - b.f.hashCode(); - } + void foo(Box b) { + ConstructorPostcondition x = new ConstructorPostcondition(b, "x"); + b.f.hashCode(); + } } diff --git a/checker/tests/nullness/ControlFlow.java b/checker/tests/nullness/ControlFlow.java index fecc4f26bb5..f8e614b8c24 100644 --- a/checker/tests/nullness/ControlFlow.java +++ b/checker/tests/nullness/ControlFlow.java @@ -2,15 +2,15 @@ // test-case for issue 160 public class ControlFlow { - public static void main(String[] args) { - String s = null; - if (s == null) { - // Important! - } else { - // Can also throw exception or call System#exit - return; - } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); + public static void main(String[] args) { + String s = null; + if (s == null) { + // Important! + } else { + // Can also throw exception or call System#exit + return; } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); + } } diff --git a/checker/tests/nullness/CopyOfArray.java b/checker/tests/nullness/CopyOfArray.java index f93a9916163..8474905eb04 100644 --- a/checker/tests/nullness/CopyOfArray.java +++ b/checker/tests/nullness/CopyOfArray.java @@ -1,14 +1,13 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Arrays; +import org.checkerframework.checker.nullness.qual.Nullable; public class CopyOfArray { - protected void makeCopy(Object[] args, int i) { - Object[] copyExact1 = Arrays.copyOf(args, args.length); - @Nullable Object[] copyExact2 = Arrays.copyOf(args, args.length); + protected void makeCopy(Object[] args, int i) { + Object[] copyExact1 = Arrays.copyOf(args, args.length); + @Nullable Object[] copyExact2 = Arrays.copyOf(args, args.length); - // :: error: (assignment.type.incompatible) - Object[] copyInexact1 = Arrays.copyOf(args, i); - @Nullable Object[] copyInexact2 = Arrays.copyOf(args, i); - } + // :: error: (assignment.type.incompatible) + Object[] copyInexact1 = Arrays.copyOf(args, i); + @Nullable Object[] copyInexact2 = Arrays.copyOf(args, i); + } } diff --git a/checker/tests/nullness/DaikonEnhancedFor.java b/checker/tests/nullness/DaikonEnhancedFor.java index a0534eb4571..bd30c426cd8 100644 --- a/checker/tests/nullness/DaikonEnhancedFor.java +++ b/checker/tests/nullness/DaikonEnhancedFor.java @@ -1,31 +1,30 @@ // Based on a false positive encountered in Daikon related to common CFGs // by the KeyFor checker. -import org.checkerframework.checker.nullness.qual.*; - import java.util.*; +import org.checkerframework.checker.nullness.qual.*; class DaikonEnhancedFor { - @SuppressWarnings("nullness") - Map> cmap = null; + @SuppressWarnings("nullness") + Map> cmap = null; - @SuppressWarnings("nullness") - Object[] getObjects() { - return null; - } + @SuppressWarnings("nullness") + Object[] getObjects() { + return null; + } - void process(@KeyFor("this.cmap") Object super_c) { - @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap - @KeyFor("this.cmap") Object[] clazzes = getObjects(); - // go through all of the classes and intialize the map - for (Object cd : clazzes) { - cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); - } - // go through the list again and put in the derived class information - for (Object cd : clazzes) { - Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); - derived.add(cd); - } + void process(@KeyFor("this.cmap") Object super_c) { + @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap + @KeyFor("this.cmap") Object[] clazzes = getObjects(); + // go through all of the classes and intialize the map + for (Object cd : clazzes) { + cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); + } + // go through the list again and put in the derived class information + for (Object cd : clazzes) { + Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); + derived.add(cd); } + } } diff --git a/checker/tests/nullness/DaikonEnhancedForNoThis.java b/checker/tests/nullness/DaikonEnhancedForNoThis.java index e4f954ade27..da3b6787133 100644 --- a/checker/tests/nullness/DaikonEnhancedForNoThis.java +++ b/checker/tests/nullness/DaikonEnhancedForNoThis.java @@ -3,31 +3,30 @@ // before it was modified to avoid missing standardization. See DaikonEnhancedFor.java // for the "fixed" version. There are no longer expected errors in this test. -import org.checkerframework.checker.nullness.qual.*; - import java.util.*; +import org.checkerframework.checker.nullness.qual.*; class DaikonEnhancedForNoThis { - @SuppressWarnings("nullness") - Map> cmap = null; + @SuppressWarnings("nullness") + Map> cmap = null; - @SuppressWarnings("nullness") - Object[] getObjects() { - return null; - } + @SuppressWarnings("nullness") + Object[] getObjects() { + return null; + } - void process(@KeyFor("this.cmap") Object super_c) { - @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap - @KeyFor("cmap") Object[] clazzes = getObjects(); - // go through all of the classes and intialize the map - for (Object cd : clazzes) { - cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); - } - // go through the list again and put in the derived class information - for (Object cd : clazzes) { - Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); - derived.add(cd); - } + void process(@KeyFor("this.cmap") Object super_c) { + @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap + @KeyFor("cmap") Object[] clazzes = getObjects(); + // go through all of the classes and intialize the map + for (Object cd : clazzes) { + cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); + } + // go through the list again and put in the derived class information + for (Object cd : clazzes) { + Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); + derived.add(cd); } + } } diff --git a/checker/tests/nullness/DaikonTests.java b/checker/tests/nullness/DaikonTests.java index 903d3b2ab68..a0923041239 100644 --- a/checker/tests/nullness/DaikonTests.java +++ b/checker/tests/nullness/DaikonTests.java @@ -7,133 +7,133 @@ */ public class DaikonTests { - // Based on a problem found in PPtSlice. - class Bug1 { - @Nullable Object field; - - public void cond1() { - if (this.hashCode() > 6 && Bug1Other.field != null) { - // spurious dereference error - Bug1Other.field.toString(); - } - } - - public void cond1(Bug1 p) { - if (this.hashCode() > 6 && p.field != null) { - // works - p.field.toString(); - } - } - - public void cond2() { - if (Bug1Other.field != null && this.hashCode() > 6) { - // works - Bug1Other.field.toString(); - } - } + // Based on a problem found in PPtSlice. + class Bug1 { + @Nullable Object field; + + public void cond1() { + if (this.hashCode() > 6 && Bug1Other.field != null) { + // spurious dereference error + Bug1Other.field.toString(); + } } - // Based on problem found in PptCombined. - // Not yet able to reproduce the problem :-( + public void cond1(Bug1 p) { + if (this.hashCode() > 6 && p.field != null) { + // works + p.field.toString(); + } + } + + public void cond2() { + if (Bug1Other.field != null && this.hashCode() > 6) { + // works + Bug1Other.field.toString(); + } + } + } + + // Based on problem found in PptCombined. + // Not yet able to reproduce the problem :-( + + class Bug2Data { + Bug2Data(Bug2Super o) {} + } - class Bug2Data { - Bug2Data(Bug2Super o) {} + class Bug2Super { + public @MonotonicNonNull Bug2Data field; + } + + class Bug2 extends Bug2Super { + private void m() { + field = new Bug2Data(this); + field.hashCode(); } + } - class Bug2Super { - public @MonotonicNonNull Bug2Data field; + // Based on problem found in FloatEqual. + class Bug3 { + @EnsuresNonNullIf(expression = "derived", result = true) + public boolean isDerived() { + return (derived != null); } - class Bug2 extends Bug2Super { - private void m() { - field = new Bug2Data(this); - field.hashCode(); - } + @Nullable Object derived; + + void good1(Bug3 v1) { + if (!v1.isDerived() || !(5 > 9)) { + return; + } + v1.derived.hashCode(); } - // Based on problem found in FloatEqual. - class Bug3 { - @EnsuresNonNullIf(expression = "derived", result = true) - public boolean isDerived() { - return (derived != null); - } - - @Nullable Object derived; - - void good1(Bug3 v1) { - if (!v1.isDerived() || !(5 > 9)) { - return; - } - v1.derived.hashCode(); - } - - // TODO: this is currently not supported - // void good2(Bug3 v1) { - // if (!(v1.isDerived() && (5 > 9))) - // return; - // v1.derived.hashCode(); - // } - - void good3(Bug3 v1) { - if (!v1.isDerived() || !(v1 instanceof Bug3)) { - return; - } - Object o = (Object) v1.derived; - o.hashCode(); - } + // TODO: this is currently not supported + // void good2(Bug3 v1) { + // if (!(v1.isDerived() && (5 > 9))) + // return; + // v1.derived.hashCode(); + // } + + void good3(Bug3 v1) { + if (!v1.isDerived() || !(v1 instanceof Bug3)) { + return; + } + Object o = (Object) v1.derived; + o.hashCode(); } + } + + // Based on problem found in PrintInvariants. + // Not yet able to reproduce the problem :-( - // Based on problem found in PrintInvariants. - // Not yet able to reproduce the problem :-( + class Bug4 { + @MonotonicNonNull Object field; + + void m(Bug4 p) { + if (false && p.field != null) { + p.field.hashCode(); + } + } + } - class Bug4 { - @MonotonicNonNull Object field; + // Based on problem found in chicory.Runtime: + class Bug5 { + @Nullable Object clazz; - void m(Bug4 p) { - if (false && p.field != null) { - p.field.hashCode(); - } - } + @EnsuresNonNull("clazz") + void init() { + clazz = new Object(); } - // Based on problem found in chicory.Runtime: - class Bug5 { - @Nullable Object clazz; - - @EnsuresNonNull("clazz") - void init() { - clazz = new Object(); - } - - void test(Bug5 b) { - if (b.clazz == null) { - b.init(); - } - - // The problem is: - // In the "then" branch, we have in "nnExpr" that "clazz" is non-null. - // In the "else" branch, we have in "annos" that the variable is non-null. - // However, as these are facts in two different representations, the merge keeps - // neither! - // - // no error message expected - b.clazz.hashCode(); - } + void test(Bug5 b) { + if (b.clazz == null) { + b.init(); + } + + // The problem is: + // In the "then" branch, we have in "nnExpr" that "clazz" is non-null. + // In the "else" branch, we have in "annos" that the variable is non-null. + // However, as these are facts in two different representations, the merge keeps + // neither! + // + // no error message expected + b.clazz.hashCode(); } + } - // From LimitedSizeSet. The following initialization of the values array - // has caused a NullPointerException. - class Bug6 { - protected @Nullable T @Nullable [] values; + // From LimitedSizeSet. The following initialization of the values array + // has caused a NullPointerException. + class Bug6 { + protected @Nullable T @Nullable [] values; - public Bug6() { - // :: warning: [unchecked] unchecked cast - @Nullable T[] new_values_array = (@Nullable T[]) new @Nullable Object[4]; - values = new_values_array; - } + public Bug6() { + // :: warning: [unchecked] unchecked cast + @Nullable T[] new_values_array = (@Nullable T[]) new @Nullable Object[4]; + values = new_values_array; } + } } class Bug1Other { - static @Nullable Object field; + static @Nullable Object field; } diff --git a/checker/tests/nullness/DefaultAnnotation.java b/checker/tests/nullness/DefaultAnnotation.java index c983c260110..87ceed970b3 100644 --- a/checker/tests/nullness/DefaultAnnotation.java +++ b/checker/tests/nullness/DefaultAnnotation.java @@ -1,140 +1,139 @@ +import java.util.Iterator; +import java.util.List; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; -import java.util.Iterator; -import java.util.List; - public class DefaultAnnotation { - public void testNoDefault() { + public void testNoDefault() { - String s = null; - } + String s = null; + } + + @DefaultQualifier.List( + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL})) + public void testDefault() { + + // :: error: (assignment.type.incompatible) + String s = null; // error + List lst = new List<>(); // valid + // :: error: (argument.type.incompatible) + lst.add(null); // error + } + + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL}) + public class InnerDefault { - @DefaultQualifier.List( - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL})) public void testDefault() { + // :: error: (assignment.type.incompatible) + String s = null; // error + List lst = new List<>(); // valid + // :: error: (argument.type.incompatible) + lst.add(null); // error + s = lst.get(0); // valid + + List<@Nullable String> nullList = new List<>(); // valid + nullList.add(null); // valid + // :: error: (assignment.type.incompatible) + s = nullList.get(0); // error + } + } + + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL}) + public static class DefaultDefs { - // :: error: (assignment.type.incompatible) - String s = null; // error - List lst = new List<>(); // valid - // :: error: (argument.type.incompatible) - lst.add(null); // error + public String getNNString() { + return "foo"; // valid } - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL}) - public class InnerDefault { - - public void testDefault() { - // :: error: (assignment.type.incompatible) - String s = null; // error - List lst = new List<>(); // valid - // :: error: (argument.type.incompatible) - lst.add(null); // error - s = lst.get(0); // valid - - List<@Nullable String> nullList = new List<>(); // valid - nullList.add(null); // valid - // :: error: (assignment.type.incompatible) - s = nullList.get(0); // error - } + public String getNNString2() { + // :: error: (return.type.incompatible) + return null; // error } - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL}) - public static class DefaultDefs { - - public String getNNString() { - return "foo"; // valid - } - - public String getNNString2() { - // :: error: (return.type.incompatible) - return null; // error - } - - public T getNull(T t) { - // :: error: (return.type.incompatible) - return null; // invalid - } - - public T getNonNull(T t) { - // :: error: (return.type.incompatible) - return null; // error - } + public T getNull(T t) { + // :: error: (return.type.incompatible) + return null; // invalid + } + + public T getNonNull(T t) { + // :: error: (return.type.incompatible) + return null; // error } + } - public class DefaultUses { + public class DefaultUses { - public void test() { + public void test() { - DefaultDefs d = new DefaultDefs(); + DefaultDefs d = new DefaultDefs(); - @NonNull String s = d.getNNString(); // valid - } + @NonNull String s = d.getNNString(); // valid + } - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL}) - public void testDefaultArgs() { + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL}) + public void testDefaultArgs() { - DefaultDefs d = new DefaultDefs(); + DefaultDefs d = new DefaultDefs(); - // :: error: (assignment.type.incompatible) - String s1 = d.<@Nullable String>getNull(null); // error - String s2 = d.getNonNull("foo"); // valid - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - String s3 = d.<@Nullable String>getNonNull("foo"); // error - } + // :: error: (assignment.type.incompatible) + String s1 = d.<@Nullable String>getNull(null); // error + String s2 = d.getNonNull("foo"); // valid + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + String s3 = d.<@Nullable String>getNonNull("foo"); // error } + } - @DefaultQualifier(value = org.checkerframework.checker.nullness.qual.NonNull.class) - static class DefaultExtends implements Iterator, Iterable { + @DefaultQualifier(value = org.checkerframework.checker.nullness.qual.NonNull.class) + static class DefaultExtends implements Iterator, Iterable { - @Override - public boolean hasNext() { - throw new UnsupportedOperationException(); - } + @Override + public boolean hasNext() { + throw new UnsupportedOperationException(); + } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } - @Override - public String next() { - throw new UnsupportedOperationException(); - } + @Override + public String next() { + throw new UnsupportedOperationException(); + } - @Override - public Iterator iterator() { - return this; - } + @Override + public Iterator iterator() { + return this; } + } - class List { - public E get(int i) { - throw new RuntimeException(); - } + class List { + public E get(int i) { + throw new RuntimeException(); + } - public boolean add(E e) { - throw new RuntimeException(); - } + public boolean add(E e) { + throw new RuntimeException(); } + } - @DefaultQualifier(value = NonNull.class) - public void testDefaultUnqualified() { + @DefaultQualifier(value = NonNull.class) + public void testDefaultUnqualified() { - // :: error: (assignment.type.incompatible) - String s = null; // error - List lst = new List<>(); // valid - // :: error: (argument.type.incompatible) - lst.add(null); // error - } + // :: error: (assignment.type.incompatible) + String s = null; // error + List lst = new List<>(); // valid + // :: error: (argument.type.incompatible) + lst.add(null); // error + } } diff --git a/checker/tests/nullness/DefaultFlow.java b/checker/tests/nullness/DefaultFlow.java index e04e8f7e18f..04fa42f665d 100644 --- a/checker/tests/nullness/DefaultFlow.java +++ b/checker/tests/nullness/DefaultFlow.java @@ -3,19 +3,19 @@ @org.checkerframework.framework.qual.DefaultQualifier(NonNull.class) public class DefaultFlow { - void test() { + void test() { - @Nullable String reader = null; - if (reader == null) { - return; - } - - reader.startsWith("hello"); + @Nullable String reader = null; + if (reader == null) { + return; } - void tesVariableInitialization() { - @Nullable Object elts = null; - assert elts != null : "@AssumeAssertion(nullness)"; - @NonNull Object elem = elts; - } + reader.startsWith("hello"); + } + + void tesVariableInitialization() { + @Nullable Object elts = null; + assert elts != null : "@AssumeAssertion(nullness)"; + @NonNull Object elem = elts; + } } diff --git a/checker/tests/nullness/DefaultInterface.java b/checker/tests/nullness/DefaultInterface.java index d5751902de4..4da53e42e43 100644 --- a/checker/tests/nullness/DefaultInterface.java +++ b/checker/tests/nullness/DefaultInterface.java @@ -3,14 +3,14 @@ @DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class) interface Foo { - void foo(String a, String b); + void foo(String a, String b); } @DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class) public class DefaultInterface { - public void test() { + public void test() { - @Nullable Foo foo = null; - } + @Nullable Foo foo = null; + } } diff --git a/checker/tests/nullness/DefaultLoops.java b/checker/tests/nullness/DefaultLoops.java index 5eafbcc3986..f440169df3c 100644 --- a/checker/tests/nullness/DefaultLoops.java +++ b/checker/tests/nullness/DefaultLoops.java @@ -1,37 +1,36 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.LinkedList; +import org.checkerframework.checker.nullness.qual.*; class MyTS extends LinkedList {} public class DefaultLoops { - void m() { - MyTS ts = new MyTS(); - // s should default to @Nullable - for (Object s : ts) {} - } + void m() { + MyTS ts = new MyTS(); + // s should default to @Nullable + for (Object s : ts) {} + } - void bar() { - for (int i = 0; i < 100; ++i) { - // nullable by default - Object o; - o = null; - // :: error: (dereference.of.nullable) - o.hashCode(); - o = new Object(); - o.hashCode(); - } - for (int i = 0; i < 100; ++i) { - // nullable by default - Object o; - o = new Object(); - o.hashCode(); - } - int i = 0; - // nullable by default - for (Object o2; i < 100; ++i) { - o2 = null; - int i3 = new Object().hashCode(); - } + void bar() { + for (int i = 0; i < 100; ++i) { + // nullable by default + Object o; + o = null; + // :: error: (dereference.of.nullable) + o.hashCode(); + o = new Object(); + o.hashCode(); + } + for (int i = 0; i < 100; ++i) { + // nullable by default + Object o; + o = new Object(); + o.hashCode(); + } + int i = 0; + // nullable by default + for (Object o2; i < 100; ++i) { + o2 = null; + int i3 = new Object().hashCode(); } + } } diff --git a/checker/tests/nullness/DefaultingForEach.java b/checker/tests/nullness/DefaultingForEach.java index 923aace8ba4..4b8b170d80d 100644 --- a/checker/tests/nullness/DefaultingForEach.java +++ b/checker/tests/nullness/DefaultingForEach.java @@ -1,45 +1,44 @@ // Test case for issue #4248: https://github.com/typetools/checker-framework/issues/4248 +import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.DefaultQualifier; -import java.util.List; - class DefaultForEach { - @DefaultQualifier(Nullable.class) - Object @NonNull [] foo() { - return new Object[] {null}; + @DefaultQualifier(Nullable.class) + Object @NonNull [] foo() { + return new Object[] {null}; + } + + void bar() { + for (Object p : foo()) { + // :: error: dereference.of.nullable + p.toString(); } + } - void bar() { - for (Object p : foo()) { - // :: error: dereference.of.nullable - p.toString(); - } - } + @DefaultQualifier(Nullable.class) + @NonNull List foo2() { + throw new RuntimeException(); + } - @DefaultQualifier(Nullable.class) - @NonNull List foo2() { - throw new RuntimeException(); + void bar2() { + for (Object p : foo2()) { + // :: error: (dereference.of.nullable) + p.toString(); } + } - void bar2() { - for (Object p : foo2()) { - // :: error: (dereference.of.nullable) - p.toString(); - } - } - - double[][] foo3() { - throw new RuntimeException(); - } + double[][] foo3() { + throw new RuntimeException(); + } - void bar3() { - for (double[] pa : foo3()) { - for (Double p : pa) { - p.toString(); - } - } + void bar3() { + for (double[] pa : foo3()) { + for (Double p : pa) { + p.toString(); + } } + } } diff --git a/checker/tests/nullness/DefaultsNullness.java b/checker/tests/nullness/DefaultsNullness.java index 0c21ec58cc5..f34f1459e3a 100644 --- a/checker/tests/nullness/DefaultsNullness.java +++ b/checker/tests/nullness/DefaultsNullness.java @@ -3,17 +3,17 @@ public class DefaultsNullness { - // local variable defaults - void test(@UnknownInitialization DefaultsNullness para, @Initialized DefaultsNullness comm) { - // @Nullable @UnknownInitialization by default - String s = "abc"; + // local variable defaults + void test(@UnknownInitialization DefaultsNullness para, @Initialized DefaultsNullness comm) { + // @Nullable @UnknownInitialization by default + String s = "abc"; - s = null; + s = null; - DefaultsNullness d; - d = null; // null okay (default == @Nullable) + DefaultsNullness d; + d = null; // null okay (default == @Nullable) - d = comm; // initialized okay (default == @Initialized) - d.hashCode(); - } + d = comm; // initialized okay (default == @Initialized) + d.hashCode(); + } } diff --git a/checker/tests/nullness/DotClass.java b/checker/tests/nullness/DotClass.java index 058d19c9aa3..ecd9c9fd694 100644 --- a/checker/tests/nullness/DotClass.java +++ b/checker/tests/nullness/DotClass.java @@ -1,17 +1,16 @@ -import org.checkerframework.checker.nullness.qual.NonNull; - import java.lang.annotation.Annotation; +import org.checkerframework.checker.nullness.qual.NonNull; public class DotClass { - void test() { - doStuff(NonNull.class); - } + void test() { + doStuff(NonNull.class); + } - void doStuff(Class cl) {} + void doStuff(Class cl) {} - void access() { - Object.class.toString(); - int.class.toString(); - } + void access() { + Object.class.toString(); + int.class.toString(); + } } diff --git a/checker/tests/nullness/EisopIssue308.java b/checker/tests/nullness/EisopIssue308.java index b5e613a8d6f..68c4a2aff57 100644 --- a/checker/tests/nullness/EisopIssue308.java +++ b/checker/tests/nullness/EisopIssue308.java @@ -2,15 +2,15 @@ // https://github.com/eisop/checker-framework/issues/308 class EisopIssue308Other { - abstract class Inner implements Runnable {} + abstract class Inner implements Runnable {} } class EisopIssue308 { - EisopIssue308Other other = new EisopIssue308Other(); + EisopIssue308Other other = new EisopIssue308Other(); - EisopIssue308Other.Inner foo() { - return other.new Inner() { - public void run() {} - }; - } + EisopIssue308Other.Inner foo() { + return other.new Inner() { + public void run() {} + }; + } } diff --git a/checker/tests/nullness/EmptyConstructor.java b/checker/tests/nullness/EmptyConstructor.java index c37bd11c117..e0ab8494f66 100644 --- a/checker/tests/nullness/EmptyConstructor.java +++ b/checker/tests/nullness/EmptyConstructor.java @@ -4,11 +4,11 @@ import org.checkerframework.dataflow.qual.*; public class SuperClass { - static int count = 0; + static int count = 0; - public SuperClass() { - count++; - } + public SuperClass() { + count++; + } } // The error message is very confusing: @@ -20,6 +20,6 @@ public SuperClass() { // to non-side-effect-free superclass constructor not allowed in side-effect-free constructor" public class EmptyConstructor extends SuperClass { - @SideEffectFree - public EmptyConstructor() {} + @SideEffectFree + public EmptyConstructor() {} } diff --git a/checker/tests/nullness/EnsuresKeyForOverriding.java b/checker/tests/nullness/EnsuresKeyForOverriding.java index 9d87eb34d71..bdcde726c77 100644 --- a/checker/tests/nullness/EnsuresKeyForOverriding.java +++ b/checker/tests/nullness/EnsuresKeyForOverriding.java @@ -1,24 +1,23 @@ -import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; public class EnsuresKeyForOverriding { - static class MyClass { - Object field = new Object(); - } + static class MyClass { + Object field = new Object(); + } - MyClass o = new MyClass(); + MyClass o = new MyClass(); - @EnsuresKeyFor(value = "#1.field", map = "#2") - void method(MyClass o, Map map) { - map.put(o.field, "Hello"); - } + @EnsuresKeyFor(value = "#1.field", map = "#2") + void method(MyClass o, Map map) { + map.put(o.field, "Hello"); + } - static class SubEnsuresKeyForOverriding extends EnsuresKeyForOverriding { - @Override - @EnsuresKeyFor(value = "#1.field", map = "#2") - void method(MyClass q, Map subMap) { - super.method(q, subMap); - } + static class SubEnsuresKeyForOverriding extends EnsuresKeyForOverriding { + @Override + @EnsuresKeyFor(value = "#1.field", map = "#2") + void method(MyClass q, Map subMap) { + super.method(q, subMap); } + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java b/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java index 2eb7d321bd6..b5b94d3c14c 100644 --- a/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java +++ b/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java @@ -1,44 +1,44 @@ import org.checkerframework.checker.nullness.qual.*; class Node { - int id; - @Nullable Node next; + int id; + @Nullable Node next; - Node(int id, @Nullable Node next) { - this.id = id; - this.next = next; - } + Node(int id, @Nullable Node next) { + this.id = id; + this.next = next; + } } class SubEnumerate { - protected @Nullable Node current; + protected @Nullable Node current; - public SubEnumerate(Node node) { - this.current = node; - } + public SubEnumerate(Node node) { + this.current = node; + } - @EnsuresNonNullIf(expression = "current", result = true) - public boolean hasMoreElements() { - return (current != null); - } + @EnsuresNonNullIf(expression = "current", result = true) + public boolean hasMoreElements() { + return (current != null); + } } class Enumerate extends SubEnumerate { - public Enumerate(Node node) { - super(node); - } + public Enumerate(Node node) { + super(node); + } - public boolean hasMoreElements() { - return (current != null); - } + public boolean hasMoreElements() { + return (current != null); + } } class Main { - public static final void main(String args[]) { - Node n2 = new Node(2, null); - Node n1 = new Node(1, n2); - Enumerate e = new Enumerate(n1); - while (e.hasMoreElements()) {} - } + public static final void main(String args[]) { + Node n2 = new Node(2, null); + Node n1 = new Node(1, n2); + Enumerate e = new Enumerate(n1); + while (e.hasMoreElements()) {} + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTest.java b/checker/tests/nullness/EnsuresNonNullIfTest.java index a7b88dd0792..ef5ee288afb 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTest.java +++ b/checker/tests/nullness/EnsuresNonNullIfTest.java @@ -7,18 +7,18 @@ public class EnsuresNonNullIfTest { - public static void fromDirPos(File1 dbdir) { - if (dbdir.isDirectory()) { - File1 @NonNull [] files = dbdir.listFiles(); - } + public static void fromDirPos(File1 dbdir) { + if (dbdir.isDirectory()) { + File1 @NonNull [] files = dbdir.listFiles(); } + } - public static void fromDirNeg(File1 dbdir) { - if (!dbdir.isDirectory()) { - throw new Error("Not a directory: " + dbdir); - } - File1 @NonNull [] files = dbdir.listFiles(); + public static void fromDirNeg(File1 dbdir) { + if (!dbdir.isDirectory()) { + throw new Error("Not a directory: " + dbdir); } + File1 @NonNull [] files = dbdir.listFiles(); + } } /////////////////////////////////////////////////////////////////////////// @@ -31,36 +31,36 @@ public static void fromDirNeg(File1 dbdir) { // TODO: Have a way of saying the property holds no matter what value is used in a given expression. public class File1 { - @EnsuresNonNullIf( - result = true, - expression = { - "list()", - "list(String)", // TODO: has no effect - "listFiles()", - "listFiles(String)", // TODO: has no effect - "listFiles(Double)" // TODO: has no effect - }) - public boolean isDirectory() { - throw new RuntimeException("skeleton method"); - } + @EnsuresNonNullIf( + result = true, + expression = { + "list()", + "list(String)", // TODO: has no effect + "listFiles()", + "listFiles(String)", // TODO: has no effect + "listFiles(Double)" // TODO: has no effect + }) + public boolean isDirectory() { + throw new RuntimeException("skeleton method"); + } - public String @Nullable [] list() { - throw new RuntimeException("skeleton method"); - } + public String @Nullable [] list() { + throw new RuntimeException("skeleton method"); + } - public String @Nullable [] list(@Nullable String FilenameFilter_a1) { - throw new RuntimeException("skeleton method"); - } + public String @Nullable [] list(@Nullable String FilenameFilter_a1) { + throw new RuntimeException("skeleton method"); + } - public File1 @Nullable [] listFiles() { - throw new RuntimeException("skeleton method"); - } + public File1 @Nullable [] listFiles() { + throw new RuntimeException("skeleton method"); + } - public File1 @Nullable [] listFiles(@Nullable String FilenameFilter_a1) { - throw new RuntimeException("skeleton method"); - } + public File1 @Nullable [] listFiles(@Nullable String FilenameFilter_a1) { + throw new RuntimeException("skeleton method"); + } - public File1 @Nullable [] listFiles(@Nullable Double FileFilter_a1) { - throw new RuntimeException("skeleton method"); - } + public File1 @Nullable [] listFiles(@Nullable Double FileFilter_a1) { + throw new RuntimeException("skeleton method"); + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTest2.java b/checker/tests/nullness/EnsuresNonNullIfTest2.java index 27f397d423e..f4476519d7e 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTest2.java +++ b/checker/tests/nullness/EnsuresNonNullIfTest2.java @@ -4,64 +4,64 @@ /** Test case for issue 53: https://github.com/typetools/checker-framework/issues/53 */ public class EnsuresNonNullIfTest2 { - private @Nullable Long id; + private @Nullable Long id; - public @org.checkerframework.dataflow.qual.Pure @Nullable Long getId() { - return id; - } + public @org.checkerframework.dataflow.qual.Pure @Nullable Long getId() { + return id; + } - @EnsuresNonNullIf(result = true, expression = "getId()") - public boolean hasId2() { - return getId() != null; - } + @EnsuresNonNullIf(result = true, expression = "getId()") + public boolean hasId2() { + return getId() != null; + } - @EnsuresNonNullIf(result = true, expression = "id") - public boolean hasId11() { - return id != null; - } + @EnsuresNonNullIf(result = true, expression = "id") + public boolean hasId11() { + return id != null; + } - @EnsuresNonNullIf(result = true, expression = "id") - public boolean hasId12() { - return this.id != null; - } + @EnsuresNonNullIf(result = true, expression = "id") + public boolean hasId12() { + return this.id != null; + } - @EnsuresNonNullIf(result = true, expression = "this.id") - public boolean hasId13() { - return id != null; - } + @EnsuresNonNullIf(result = true, expression = "this.id") + public boolean hasId13() { + return id != null; + } - @EnsuresNonNullIf(result = true, expression = "this.id") - public boolean hasId14() { - return this.id != null; - } + @EnsuresNonNullIf(result = true, expression = "this.id") + public boolean hasId14() { + return this.id != null; + } - void client() { - if (hasId11()) { - id.toString(); - } - if (hasId12()) { - id.toString(); - } - if (hasId13()) { - id.toString(); - } - if (hasId14()) { - id.toString(); - } - // :: error: (dereference.of.nullable) - id.toString(); + void client() { + if (hasId11()) { + id.toString(); + } + if (hasId12()) { + id.toString(); + } + if (hasId13()) { + id.toString(); + } + if (hasId14()) { + id.toString(); } + // :: error: (dereference.of.nullable) + id.toString(); + } - // Expressions referring to enclosing classes should be resolved. - class Inner { - @EnsuresNonNullIf(result = true, expression = "getId()") - public boolean innerHasGetIdMethod() { - return getId() != null; - } + // Expressions referring to enclosing classes should be resolved. + class Inner { + @EnsuresNonNullIf(result = true, expression = "getId()") + public boolean innerHasGetIdMethod() { + return getId() != null; + } - @EnsuresNonNullIf(result = true, expression = "id") - public boolean innerHasIdField() { - return id != null; - } + @EnsuresNonNullIf(result = true, expression = "id") + public boolean innerHasIdField() { + return id != null; } + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTest4.java b/checker/tests/nullness/EnsuresNonNullIfTest4.java index d7f577fa904..c6ab3f7208a 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTest4.java +++ b/checker/tests/nullness/EnsuresNonNullIfTest4.java @@ -3,29 +3,29 @@ public class EnsuresNonNullIfTest4 { - public void add_bottom_up(MyInvariant inv) { + public void add_bottom_up(MyInvariant inv) { - // The problem goes away if the below line is deleted or replaced with: - // Object x = new Object[100]; - Object x = new @Nullable Object[100]; + // The problem goes away if the below line is deleted or replaced with: + // Object x = new Object[100]; + Object x = new @Nullable Object[100]; - if (inv.is_ni_suppressed()) { - Object ss = inv.get_ni_suppressions(); - ss.toString(); - } + if (inv.is_ni_suppressed()) { + Object ss = inv.get_ni_suppressions(); + ss.toString(); } + } } class MyInvariant { - @Pure - public @Nullable Object get_ni_suppressions() { - return (null); - } + @Pure + public @Nullable Object get_ni_suppressions() { + return (null); + } - @SuppressWarnings("nullness") - @EnsuresNonNullIf(result = true, expression = "get_ni_suppressions()") - @Pure - public boolean is_ni_suppressed() { - return true; - } + @SuppressWarnings("nullness") + @EnsuresNonNullIf(result = true, expression = "get_ni_suppressions()") + @Pure + public boolean is_ni_suppressed() { + return true; + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTestSimple.java b/checker/tests/nullness/EnsuresNonNullIfTestSimple.java index 1ea3ccfff3d..b07a41050c3 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTestSimple.java +++ b/checker/tests/nullness/EnsuresNonNullIfTestSimple.java @@ -7,44 +7,44 @@ */ public class EnsuresNonNullIfTestSimple { - protected int @Nullable [] values; + protected int @Nullable [] values; - @EnsuresNonNullIf(result = true, expression = "values") - public boolean repNulledBAD() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return values == null; - } + @EnsuresNonNullIf(result = true, expression = "values") + public boolean repNulledBAD() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return values == null; + } - @EnsuresNonNullIf(result = false, expression = "values") - public boolean repNulled() { - return values == null; - } + @EnsuresNonNullIf(result = false, expression = "values") + public boolean repNulled() { + return values == null; + } - public void addAll(EnsuresNonNullIfTestSimple s) { - if (repNulled()) { - return; - } - @NonNull Object x = values; + public void addAll(EnsuresNonNullIfTestSimple s) { + if (repNulled()) { + return; + } + @NonNull Object x = values; - /* TODO skip-tests - * The two errors are not raised currently - * The assumption that "values" is NN is added above. - * However, as repNulled is not pure, it should be removed again here. - if (s.repNulled()) { - // : : (dereference.of.nullable) - values.hashCode(); - } else { - // we called on "s", so we don't know anything about "values". - // : : (assignment.type.incompatible) - @NonNull Object y = values; - } - */ + /* TODO skip-tests + * The two errors are not raised currently + * The assumption that "values" is NN is added above. + * However, as repNulled is not pure, it should be removed again here. + if (s.repNulled()) { + // : : (dereference.of.nullable) + values.hashCode(); + } else { + // we called on "s", so we don't know anything about "values". + // : : (assignment.type.incompatible) + @NonNull Object y = values; + } + */ - if (s.repNulled()) { - // :: error: (dereference.of.nullable) - s.values.hashCode(); - } else { - @NonNull Object y = s.values; - } + if (s.repNulled()) { + // :: error: (dereference.of.nullable) + s.values.hashCode(); + } else { + @NonNull Object y = s.values; } + } } diff --git a/checker/tests/nullness/EnumStaticBlock.java b/checker/tests/nullness/EnumStaticBlock.java index dc056ffd9c1..a2defa09871 100644 --- a/checker/tests/nullness/EnumStaticBlock.java +++ b/checker/tests/nullness/EnumStaticBlock.java @@ -2,15 +2,15 @@ import java.util.List; public class EnumStaticBlock { - public enum Section { - ME, - OTHER; - private static final List l = new ArrayList<>(); + public enum Section { + ME, + OTHER; + private static final List l = new ArrayList<>(); - static { - for (int i = 0; i < 10; ++i) { - l.add(i); - } - } + static { + for (int i = 0; i < 10; ++i) { + l.add(i); + } } + } } diff --git a/checker/tests/nullness/EnumsNullness.java b/checker/tests/nullness/EnumsNullness.java index 203884ba4fd..7af06af98cf 100644 --- a/checker/tests/nullness/EnumsNullness.java +++ b/checker/tests/nullness/EnumsNullness.java @@ -2,38 +2,38 @@ public class EnumsNullness { - enum MyEnum { - A, - B, - C, - D - } + enum MyEnum { + A, + B, + C, + D + } - // :: error: (assignment.type.incompatible) - MyEnum myEnum = null; // invalid - @Nullable MyEnum myNullableEnum = null; + // :: error: (assignment.type.incompatible) + MyEnum myEnum = null; // invalid + @Nullable MyEnum myNullableEnum = null; - void testLocalEnum() { - // Enums are allowed to be null: no error here. - MyEnum myNullableEnum = null; - // :: error: (assignment.type.incompatible) - @NonNull MyEnum myEnum = null; // invalid - } + void testLocalEnum() { + // Enums are allowed to be null: no error here. + MyEnum myNullableEnum = null; + // :: error: (assignment.type.incompatible) + @NonNull MyEnum myEnum = null; // invalid + } - enum EnumBadAnnos { - A, - // :: error: (nullness.on.enum) - @NonNull B, - // :: error: (nullness.on.enum) - @Nullable C, - D; + enum EnumBadAnnos { + A, + // :: error: (nullness.on.enum) + @NonNull B, + // :: error: (nullness.on.enum) + @Nullable C, + D; - public static final EnumBadAnnos A2 = A; - public static final @NonNull EnumBadAnnos B2 = B; - public static final @Nullable EnumBadAnnos C2 = C; + public static final EnumBadAnnos A2 = A; + public static final @NonNull EnumBadAnnos B2 = B; + public static final @Nullable EnumBadAnnos C2 = C; - @Nullable String method() { - return null; - } + @Nullable String method() { + return null; } + } } diff --git a/checker/tests/nullness/EqualToNullness.java b/checker/tests/nullness/EqualToNullness.java index 9fd3280cba0..2c2dcca3574 100644 --- a/checker/tests/nullness/EqualToNullness.java +++ b/checker/tests/nullness/EqualToNullness.java @@ -3,37 +3,37 @@ public class EqualToNullness { - // @Nullable String f; - // - // void t1(@Nullable String g) { - // // :: error: (dereference.of.nullable) - // g.toLowerCase(); - // if (g != null) { - // g.toLowerCase(); - // } - // } - // - // void t2() { - // // :: error: (dereference.of.nullable) - // f.toLowerCase(); - // if (f == null) {} else { - // f.toLowerCase(); - // } - // } - // - // void t1b(@Nullable String g) { - // // :: error: (dereference.of.nullable) - // g.toLowerCase(); - // if (null != g) { - // g.toLowerCase(); - // } - // } - // - // void t2b() { - // // :: error: (dereference.of.nullable) - // f.toLowerCase(); - // if (null == f) {} else { - // f.toLowerCase(); - // } - // } + // @Nullable String f; + // + // void t1(@Nullable String g) { + // // :: error: (dereference.of.nullable) + // g.toLowerCase(); + // if (g != null) { + // g.toLowerCase(); + // } + // } + // + // void t2() { + // // :: error: (dereference.of.nullable) + // f.toLowerCase(); + // if (f == null) {} else { + // f.toLowerCase(); + // } + // } + // + // void t1b(@Nullable String g) { + // // :: error: (dereference.of.nullable) + // g.toLowerCase(); + // if (null != g) { + // g.toLowerCase(); + // } + // } + // + // void t2b() { + // // :: error: (dereference.of.nullable) + // f.toLowerCase(); + // if (null == f) {} else { + // f.toLowerCase(); + // } + // } } diff --git a/checker/tests/nullness/ExceptionParam.java b/checker/tests/nullness/ExceptionParam.java index bcbdbf32430..5275ca91eca 100644 --- a/checker/tests/nullness/ExceptionParam.java +++ b/checker/tests/nullness/ExceptionParam.java @@ -5,26 +5,26 @@ /** Exception parameters are non-null, even if the default is nullable. */ @DefaultQualifier(org.checkerframework.checker.nullness.qual.Nullable.class) public class ExceptionParam { - void exc1() { - try { - } catch (AssertionError e) { - @NonNull Object o = e; - } + void exc1() { + try { + } catch (AssertionError e) { + @NonNull Object o = e; } + } - void exc2() { - try { - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull AssertionError e) { - @NonNull Object o = e; - } + void exc2() { + try { + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull AssertionError e) { + @NonNull Object o = e; } + } - void exc3() { - try { - // :: warning: (nullness.on.exception.parameter) - } catch (@Nullable AssertionError e) { - @NonNull Object o = e; - } + void exc3() { + try { + // :: warning: (nullness.on.exception.parameter) + } catch (@Nullable AssertionError e) { + @NonNull Object o = e; } + } } diff --git a/checker/tests/nullness/Exceptions.java b/checker/tests/nullness/Exceptions.java index 8e77f9457a7..e255ecc1710 100644 --- a/checker/tests/nullness/Exceptions.java +++ b/checker/tests/nullness/Exceptions.java @@ -1,47 +1,47 @@ import org.checkerframework.checker.nullness.qual.*; public class Exceptions { - void exceptionParam(@Nullable Exception m) { - // :: error: (dereference.of.nullable) - m.getClass(); // should emit error - } + void exceptionParam(@Nullable Exception m) { + // :: error: (dereference.of.nullable) + m.getClass(); // should emit error + } - void nonnullExceptionParam(@NonNull Exception m) { - m.getClass(); - } + void nonnullExceptionParam(@NonNull Exception m) { + m.getClass(); + } - void exception(@Nullable Exception m) { - try { - throwException(); - } catch (Exception e) { - e.getClass(); - // :: error: (dereference.of.nullable) - m.getClass(); // should emit error - } + void exception(@Nullable Exception m) { + try { + throwException(); + } catch (Exception e) { + e.getClass(); + // :: error: (dereference.of.nullable) + m.getClass(); // should emit error } + } - void throwException() { - int a = 0; - if (a == 0) { - // :: error: (throwing.nullable) - throw null; - } else if (a == 1) { - RuntimeException e = null; - // :: error: (throwing.nullable) - throw e; - } else { - RuntimeException e = new RuntimeException(); - throw e; - } + void throwException() { + int a = 0; + if (a == 0) { + // :: error: (throwing.nullable) + throw null; + } else if (a == 1) { + RuntimeException e = null; + // :: error: (throwing.nullable) + throw e; + } else { + RuntimeException e = new RuntimeException(); + throw e; } + } - void reassignException() { - try { - throwException(); - } catch (RuntimeException e) { - // :: error: (assignment.type.incompatible) - e = null; - throw e; - } + void reassignException() { + try { + throwException(); + } catch (RuntimeException e) { + // :: error: (assignment.type.incompatible) + e = null; + throw e; } + } } diff --git a/checker/tests/nullness/ExplictTypeVarAnnos.java b/checker/tests/nullness/ExplictTypeVarAnnos.java index 4f2d43ac9b1..5149a3210c9 100644 --- a/checker/tests/nullness/ExplictTypeVarAnnos.java +++ b/checker/tests/nullness/ExplictTypeVarAnnos.java @@ -2,46 +2,46 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ExplictTypeVarAnnos { - interface Consumer {} - - public static Consumer cast( - final @Nullable Consumer consumer) { - throw new RuntimeException(); - } - - public static Consumer getConsumer0() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - public static <@Nullable D> Consumer getConsumer1() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - public Consumer getConsumer2() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - public Consumer getConsumer3() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - @SuppressWarnings("method.invocation.invalid") - Consumer field = getConsumer2(); - - public Consumer getField() { - return field; - } - - static class A {} - - // :: error: (type.argument.type.incompatible) - static class B extends A {} + interface Consumer {} + + public static Consumer cast( + final @Nullable Consumer consumer) { + throw new RuntimeException(); + } + + public static Consumer getConsumer0() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + public static <@Nullable D> Consumer getConsumer1() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + public Consumer getConsumer2() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + public Consumer getConsumer3() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + @SuppressWarnings("method.invocation.invalid") + Consumer field = getConsumer2(); + + public Consumer getField() { + return field; + } + + static class A {} + + // :: error: (type.argument.type.incompatible) + static class B extends A {} } diff --git a/checker/tests/nullness/ExpressionsNullness.java b/checker/tests/nullness/ExpressionsNullness.java index 824c42b7233..b0f026faea2 100644 --- a/checker/tests/nullness/ExpressionsNullness.java +++ b/checker/tests/nullness/ExpressionsNullness.java @@ -1,6 +1,3 @@ -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.framework.qual.DefaultQualifier; - import java.io.*; import java.util.Date; import java.util.HashMap; @@ -9,62 +6,64 @@ import java.util.List; import java.util.Set; import java.util.regex.*; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.framework.qual.DefaultQualifier; @DefaultQualifier(NonNull.class) public class ExpressionsNullness { - public static double[] returnDoubleArray() { - return new double[] {3.14, 2.7}; - } + public static double[] returnDoubleArray() { + return new double[] {3.14, 2.7}; + } - public static void staticMembers() { - Pattern.compile("^>entry *()"); - System.out.println(ExpressionsNullness.class); - ExpressionsNullness.class.getAnnotations(); // valid - } + public static void staticMembers() { + Pattern.compile("^>entry *()"); + System.out.println(ExpressionsNullness.class); + ExpressionsNullness.class.getAnnotations(); // valid + } - private HashMap map = new HashMap<>(); + private HashMap map = new HashMap<>(); - public void test() { - @SuppressWarnings("nullness") - String s = map.get("foo"); + public void test() { + @SuppressWarnings("nullness") + String s = map.get("foo"); - Class cl = Boolean.TYPE; + Class cl = Boolean.TYPE; - List foo = new LinkedList(); - // :: error: (dereference.of.nullable) - foo.get(0).toString(); // default applies to wildcard extends + List foo = new LinkedList(); + // :: error: (dereference.of.nullable) + foo.get(0).toString(); // default applies to wildcard extends - Set set = new HashSet(); - for (@Nullable Object o : set) System.out.println(); - } + Set set = new HashSet(); + for (@Nullable Object o : set) System.out.println(); + } - void test2() { - List lst = new LinkedList<@NonNull String>(); - for (String s : lst) { - s.length(); - } + void test2() { + List lst = new LinkedList<@NonNull String>(); + for (String s : lst) { + s.length(); } + } - void test3(T o) { - o.getClass(); // valid - } + void test3(T o) { + o.getClass(); // valid + } - void test4(List o) { - o.get(0).getClass(); // valid - } + void test4(List o) { + o.get(0).getClass(); // valid + } - void test5() { - Comparable d = new Date(); - } + void test5() { + Comparable d = new Date(); + } - void testIntersection() { - java.util.Arrays.asList("m", 1); - } + void testIntersection() { + java.util.Arrays.asList("m", 1); + } - Object obj; + Object obj; - public ExpressionsNullness(Object obj) { - this.obj = obj; - } + public ExpressionsNullness(Object obj) { + this.obj = obj; + } } diff --git a/checker/tests/nullness/ExtendsArrayList.java b/checker/tests/nullness/ExtendsArrayList.java index 19740363155..82429b674ce 100644 --- a/checker/tests/nullness/ExtendsArrayList.java +++ b/checker/tests/nullness/ExtendsArrayList.java @@ -3,10 +3,10 @@ public final class ExtendsArrayList extends ArrayList { - public int removeMany(List toRemove) { - for (String inv : this) { - if (!toRemove.contains(inv)) {} - } - return 0; + public int removeMany(List toRemove) { + for (String inv : this) { + if (!toRemove.contains(inv)) {} } + return 0; + } } diff --git a/checker/tests/nullness/FenumExplicit.java b/checker/tests/nullness/FenumExplicit.java index 7290269205d..d2b5e78e403 100644 --- a/checker/tests/nullness/FenumExplicit.java +++ b/checker/tests/nullness/FenumExplicit.java @@ -6,18 +6,18 @@ class EnumExplicit { - public static enum EnumWithMethod { - VALUE { - @Override - public void call(@Nullable String string) { - // Null string is acceptable in this function. - } - }; + public static enum EnumWithMethod { + VALUE { + @Override + public void call(@Nullable String string) { + // Null string is acceptable in this function. + } + }; - public abstract void call(String string); - } + public abstract void call(String string); + } - public static void main(String[] args) { - EnumWithMethod.VALUE.call(null); - } + public static void main(String[] args) { + EnumWithMethod.VALUE.call(null); + } } diff --git a/checker/tests/nullness/FieldWithAnnotatedLambda.java b/checker/tests/nullness/FieldWithAnnotatedLambda.java index 80151cb9f77..34c311e999b 100644 --- a/checker/tests/nullness/FieldWithAnnotatedLambda.java +++ b/checker/tests/nullness/FieldWithAnnotatedLambda.java @@ -1,18 +1,17 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; class FieldWithAnnotatedLambda { - Function f1 = (@Nullable Object in) -> in; + Function f1 = (@Nullable Object in) -> in; - class Outer { - class Inner {} - } + class Outer { + class Inner {} + } - Function f2 = (Outer.@Nullable Inner in) -> in; + Function f2 = (Outer.@Nullable Inner in) -> in; - // Lambda parameter is defaulted to be @NonNull, raising an error - // for the lambda parameter type. - // :: error: (lambda.param.type.incompatible) - Function f3 = (Outer.Inner in) -> in; + // Lambda parameter is defaulted to be @NonNull, raising an error + // for the lambda parameter type. + // :: error: (lambda.param.type.incompatible) + Function f3 = (Outer.Inner in) -> in; } diff --git a/checker/tests/nullness/FinalFields.java b/checker/tests/nullness/FinalFields.java index e385837b552..e7c0febccee 100644 --- a/checker/tests/nullness/FinalFields.java +++ b/checker/tests/nullness/FinalFields.java @@ -1,47 +1,47 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Upper { - @Nullable String fs = "NonNull init"; - private final @Nullable String ffs = "NonNull init"; + @Nullable String fs = "NonNull init"; + private final @Nullable String ffs = "NonNull init"; - void access() { - // Error, because non-final field type is not refined - // :: error: (dereference.of.nullable) - fs.hashCode(); - // private final field is refined - ffs.hashCode(); - } + void access() { + // Error, because non-final field type is not refined + // :: error: (dereference.of.nullable) + fs.hashCode(); + // private final field is refined + ffs.hashCode(); + } } public class FinalFields { - public void foo(Upper u) { - // Error, because final field in different class is not refined - // :: error: (dereference.of.nullable) - u.fs.hashCode(); - } + public void foo(Upper u) { + // Error, because final field in different class is not refined + // :: error: (dereference.of.nullable) + u.fs.hashCode(); + } - public void bar(Lower l) { - // Error, because final field in different class is not refined - // :: error: (dereference.of.nullable) - l.fs.hashCode(); - } + public void bar(Lower l) { + // Error, because final field in different class is not refined + // :: error: (dereference.of.nullable) + l.fs.hashCode(); + } - public void local() { - @Nullable String ls = "Locals"; - // Local variable is refined - ls.hashCode(); - } + public void local() { + @Nullable String ls = "Locals"; + // Local variable is refined + ls.hashCode(); + } } class Lower { - @Nullable String fs = "NonNull init, too"; - private final @Nullable String ffs = "NonNull init, too"; + @Nullable String fs = "NonNull init, too"; + private final @Nullable String ffs = "NonNull init, too"; - void access() { - // Error, because non-final field type is not refined - // :: error: (dereference.of.nullable) - fs.hashCode(); - // private final field is refined - ffs.hashCode(); - } + void access() { + // Error, because non-final field type is not refined + // :: error: (dereference.of.nullable) + fs.hashCode(); + // private final field is refined + ffs.hashCode(); + } } diff --git a/checker/tests/nullness/FinalVar.java b/checker/tests/nullness/FinalVar.java index 1aba0708f94..216a99e65ce 100644 --- a/checker/tests/nullness/FinalVar.java +++ b/checker/tests/nullness/FinalVar.java @@ -2,18 +2,18 @@ public class FinalVar { - public Object pptIterator() { - // Only test with (effectively) final variables; Java only permits final or - // effectively final variables to be accessed from an anonymous class. - final String iter_view_1 = "I am not null"; - @NonNull String iter_view_2 = "Neither am I"; - final @NonNull String iter_view_3 = "Dittos"; - return new Object() { - public void useFinalVar() { - iter_view_1.hashCode(); - iter_view_2.hashCode(); - iter_view_3.hashCode(); - } - }; - } + public Object pptIterator() { + // Only test with (effectively) final variables; Java only permits final or + // effectively final variables to be accessed from an anonymous class. + final String iter_view_1 = "I am not null"; + @NonNull String iter_view_2 = "Neither am I"; + final @NonNull String iter_view_3 = "Dittos"; + return new Object() { + public void useFinalVar() { + iter_view_1.hashCode(); + iter_view_2.hashCode(); + iter_view_3.hashCode(); + } + }; + } } diff --git a/checker/tests/nullness/FinalVar2.java b/checker/tests/nullness/FinalVar2.java index af05348ff8c..9201396cfbb 100644 --- a/checker/tests/nullness/FinalVar2.java +++ b/checker/tests/nullness/FinalVar2.java @@ -5,43 +5,43 @@ public class FinalVar2 { - static Object method1(@Nullable Object arg) { - final Object tmp = arg; - if (tmp == null) { - return "hello"; - } - return new Object() { - public void useFinalVar() { - // should be OK - tmp.hashCode(); - } - }; + static Object method1(@Nullable Object arg) { + final Object tmp = arg; + if (tmp == null) { + return "hello"; } + return new Object() { + public void useFinalVar() { + // should be OK + tmp.hashCode(); + } + }; + } - static Object method2(final @Nullable Object arg) { - if (arg == null) { - return "hello"; - } - return new Object() { - public void useFinalVar() { - // should be OK - arg.hashCode(); - } - }; + static Object method2(final @Nullable Object arg) { + if (arg == null) { + return "hello"; } + return new Object() { + public void useFinalVar() { + // should be OK + arg.hashCode(); + } + }; + } - static Object method3(@Nullable Object arg) { - final Object tmp = arg; - Object result = - new Object() { - public void useFinalVar() { - // :: error: (dereference.of.nullable) - tmp.hashCode(); - } - }; - if (tmp == null) { - return "hello"; - } - return result; + static Object method3(@Nullable Object arg) { + final Object tmp = arg; + Object result = + new Object() { + public void useFinalVar() { + // :: error: (dereference.of.nullable) + tmp.hashCode(); + } + }; + if (tmp == null) { + return "hello"; } + return result; + } } diff --git a/checker/tests/nullness/FinalVar3.java b/checker/tests/nullness/FinalVar3.java index 0a024047ec6..05be5b25762 100644 --- a/checker/tests/nullness/FinalVar3.java +++ b/checker/tests/nullness/FinalVar3.java @@ -2,13 +2,13 @@ public class FinalVar3 { - static Object method1(@Nullable Object arg) { - final Object tmp = arg; - if (tmp == null) { - return "hello"; - } - // The type of the final variable is correctly refined. - tmp.hashCode(); - return "bye"; + static Object method1(@Nullable Object arg) { + final Object tmp = arg; + if (tmp == null) { + return "hello"; } + // The type of the final variable is correctly refined. + tmp.hashCode(); + return "bye"; + } } diff --git a/checker/tests/nullness/FindBugs.java b/checker/tests/nullness/FindBugs.java index 8036af6f575..97456f5115e 100644 --- a/checker/tests/nullness/FindBugs.java +++ b/checker/tests/nullness/FindBugs.java @@ -2,30 +2,30 @@ public class FindBugs { - @CheckForNull - Object getNull() { - return null; - } + @CheckForNull + Object getNull() { + return null; + } - @NonNull MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> getListOfNulls() { - // :: error: (return.type.incompatible) - return null; // error - } + @NonNull MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> getListOfNulls() { + // :: error: (return.type.incompatible) + return null; // error + } - void test() { - Object o = getNull(); - // :: error: (dereference.of.nullable) - o.toString(); // error + void test() { + Object o = getNull(); + // :: error: (dereference.of.nullable) + o.toString(); // error - MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> l = getListOfNulls(); - l.toString(); - // :: error: (dereference.of.nullable) - l.get().toString(); // error - } + MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> l = getListOfNulls(); + l.toString(); + // :: error: (dereference.of.nullable) + l.get().toString(); // error + } } class MyList { - T get() { - throw new RuntimeException(); - } + T get() { + throw new RuntimeException(); + } } diff --git a/checker/tests/nullness/FlowAssignment.java b/checker/tests/nullness/FlowAssignment.java index 55a86a58c11..8d77c0bdb1c 100644 --- a/checker/tests/nullness/FlowAssignment.java +++ b/checker/tests/nullness/FlowAssignment.java @@ -2,10 +2,10 @@ public class FlowAssignment { - void test() { - @NonNull String s = "foo"; + void test() { + @NonNull String s = "foo"; - String t = s; - t.startsWith("f"); - } + String t = s; + t.startsWith("f"); + } } diff --git a/checker/tests/nullness/FlowCompound.java b/checker/tests/nullness/FlowCompound.java index 67846105552..0178e2e3b26 100644 --- a/checker/tests/nullness/FlowCompound.java +++ b/checker/tests/nullness/FlowCompound.java @@ -2,56 +2,56 @@ public class FlowCompound { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(@Nullable Object o) { - return o != null && this.getClass() != o.getClass(); - } - - void test(@Nullable String s) { - - if (s == null || s.length() > 0) { - // :: error: (assignment.type.incompatible) - @NonNull String test = s; - } - - String tmp; - @NonNull String notNull; - tmp = "hello"; - notNull = tmp; - notNull = tmp = "hello"; - } - - public static boolean equal(@Nullable Object a, @Nullable Object b) { - assert b != null : "suppress nullness"; - return a == b || (a != null && a.equals(b)); - } - - public static void testCompoundAssignmentWithString() { - String s = "m"; - s += "n"; - s.toString(); - } - - public static void testCompoundAssignmentWithChar() { - String s = "m"; - s += 'n'; - s.toString(); - } - - public static void testCompoundAssignWithNull() { - String s = "m"; - s += null; - s.toString(); - } - - public static void testPrimitiveArray() { - int[] a = {0}; - a[0] += 2; - System.out.println(a[0]); - } - - public static void testPrimitive() { - Integer i = 1; - i -= 2; - } + @org.checkerframework.dataflow.qual.Pure + public boolean equals(@Nullable Object o) { + return o != null && this.getClass() != o.getClass(); + } + + void test(@Nullable String s) { + + if (s == null || s.length() > 0) { + // :: error: (assignment.type.incompatible) + @NonNull String test = s; + } + + String tmp; + @NonNull String notNull; + tmp = "hello"; + notNull = tmp; + notNull = tmp = "hello"; + } + + public static boolean equal(@Nullable Object a, @Nullable Object b) { + assert b != null : "suppress nullness"; + return a == b || (a != null && a.equals(b)); + } + + public static void testCompoundAssignmentWithString() { + String s = "m"; + s += "n"; + s.toString(); + } + + public static void testCompoundAssignmentWithChar() { + String s = "m"; + s += 'n'; + s.toString(); + } + + public static void testCompoundAssignWithNull() { + String s = "m"; + s += null; + s.toString(); + } + + public static void testPrimitiveArray() { + int[] a = {0}; + a[0] += 2; + System.out.println(a[0]); + } + + public static void testPrimitive() { + Integer i = 1; + i -= 2; + } } diff --git a/checker/tests/nullness/FlowCompoundConcatenation.java b/checker/tests/nullness/FlowCompoundConcatenation.java index 8ab7c81a7a6..81819d77490 100644 --- a/checker/tests/nullness/FlowCompoundConcatenation.java +++ b/checker/tests/nullness/FlowCompoundConcatenation.java @@ -1,17 +1,17 @@ public class FlowCompoundConcatenation { - static String getNonNullString() { - return ""; - } + static String getNonNullString() { + return ""; + } - public static void testCompoundAssignWithNullAndMethodCall() { - String s = null; - s += getNonNullString(); - s.toString(); - } + public static void testCompoundAssignWithNullAndMethodCall() { + String s = null; + s += getNonNullString(); + s.toString(); + } - public static void testCompoundAssignWithNull() { - String s = null; - s += "hello"; - s.toString(); - } + public static void testCompoundAssignWithNull() { + String s = null; + s += "hello"; + s.toString(); + } } diff --git a/checker/tests/nullness/FlowConditions.java b/checker/tests/nullness/FlowConditions.java index bef7c35fa56..71c0e8b602b 100644 --- a/checker/tests/nullness/FlowConditions.java +++ b/checker/tests/nullness/FlowConditions.java @@ -1,41 +1,40 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; public class FlowConditions { - void m(@Nullable Object x, @Nullable Object y) { - if (x == null || y == null) { - // :: error: (dereference.of.nullable) - x.toString(); - // :: error: (dereference.of.nullable) - y.toString(); - } else { - x.toString(); - y.toString(); - } + void m(@Nullable Object x, @Nullable Object y) { + if (x == null || y == null) { + // :: error: (dereference.of.nullable) + x.toString(); + // :: error: (dereference.of.nullable) + y.toString(); + } else { + x.toString(); + y.toString(); } + } - private final Map> graph = new HashMap<>(); + private final Map> graph = new HashMap<>(); - public void addEdge1(String e, String parent, String child) { - if (!graph.containsKey(parent)) { - throw new NoSuchElementException(); - } - if (!graph.containsKey(child)) { - throw new NoSuchElementException(); - } - @NonNull Set edges = graph.get(parent); + public void addEdge1(String e, String parent, String child) { + if (!graph.containsKey(parent)) { + throw new NoSuchElementException(); + } + if (!graph.containsKey(child)) { + throw new NoSuchElementException(); } + @NonNull Set edges = graph.get(parent); + } - // TODO: Re-enable when issue 221 is resolved. - // public void addEdge2(String e, String parent, String child) { - // if ( (!graph.containsKey(parent)) || - // (!graph.containsKey(child))) - // throw new NoSuchElementException(); - // @NonNull Set edges = graph.get(parent); - // } + // TODO: Re-enable when issue 221 is resolved. + // public void addEdge2(String e, String parent, String child) { + // if ( (!graph.containsKey(parent)) || + // (!graph.containsKey(child))) + // throw new NoSuchElementException(); + // @NonNull Set edges = graph.get(parent); + // } } diff --git a/checker/tests/nullness/FlowExpressionParsingBug.java b/checker/tests/nullness/FlowExpressionParsingBug.java index b2acf73c976..0727a7be6e5 100644 --- a/checker/tests/nullness/FlowExpressionParsingBug.java +++ b/checker/tests/nullness/FlowExpressionParsingBug.java @@ -1,93 +1,92 @@ +import javax.swing.JMenuBar; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import javax.swing.JMenuBar; - public abstract class FlowExpressionParsingBug { - //// Check that JavaExpressions with explicit and implicit 'this' work - - protected @Nullable JMenuBar menuBar = null; - - @RequiresNonNull("menuBar") - public void addFavorite() {} - - @RequiresNonNull("this.menuBar") - public void addFavorite1() {} - - //// Check JavaExpressions for static fields with different ways to access the field - - static @Nullable String i = null; - - @RequiresNonNull("FlowExpressionParsingBug.i") - public void a() {} - - @RequiresNonNull("i") - public void b() {} - - @RequiresNonNull("this.i") - public void c() {} - - void test1() { - // :: error: (contracts.precondition.not.satisfied) - a(); - FlowExpressionParsingBug.i = ""; - a(); - } - - void test1b() { - // :: error: (contracts.precondition.not.satisfied) - a(); - i = ""; - a(); - } - - void test1c() { - // :: error: (contracts.precondition.not.satisfied) - a(); - this.i = ""; - a(); - } - - void test2() { - // :: error: (contracts.precondition.not.satisfied) - b(); - FlowExpressionParsingBug.i = ""; - b(); - } - - void test2b() { - // :: error: (contracts.precondition.not.satisfied) - b(); - i = ""; - b(); - } - - void test2c() { - // :: error: (contracts.precondition.not.satisfied) - b(); - this.i = ""; - b(); - } - - void test3() { - // :: error: (contracts.precondition.not.satisfied) - c(); - FlowExpressionParsingBug.i = ""; - c(); - } - - void test3b() { - // :: error: (contracts.precondition.not.satisfied) - c(); - i = ""; - c(); - } - - void test3c() { - // :: error: (contracts.precondition.not.satisfied) - c(); - this.i = ""; - c(); - } + //// Check that JavaExpressions with explicit and implicit 'this' work + + protected @Nullable JMenuBar menuBar = null; + + @RequiresNonNull("menuBar") + public void addFavorite() {} + + @RequiresNonNull("this.menuBar") + public void addFavorite1() {} + + //// Check JavaExpressions for static fields with different ways to access the field + + static @Nullable String i = null; + + @RequiresNonNull("FlowExpressionParsingBug.i") + public void a() {} + + @RequiresNonNull("i") + public void b() {} + + @RequiresNonNull("this.i") + public void c() {} + + void test1() { + // :: error: (contracts.precondition.not.satisfied) + a(); + FlowExpressionParsingBug.i = ""; + a(); + } + + void test1b() { + // :: error: (contracts.precondition.not.satisfied) + a(); + i = ""; + a(); + } + + void test1c() { + // :: error: (contracts.precondition.not.satisfied) + a(); + this.i = ""; + a(); + } + + void test2() { + // :: error: (contracts.precondition.not.satisfied) + b(); + FlowExpressionParsingBug.i = ""; + b(); + } + + void test2b() { + // :: error: (contracts.precondition.not.satisfied) + b(); + i = ""; + b(); + } + + void test2c() { + // :: error: (contracts.precondition.not.satisfied) + b(); + this.i = ""; + b(); + } + + void test3() { + // :: error: (contracts.precondition.not.satisfied) + c(); + FlowExpressionParsingBug.i = ""; + c(); + } + + void test3b() { + // :: error: (contracts.precondition.not.satisfied) + c(); + i = ""; + c(); + } + + void test3c() { + // :: error: (contracts.precondition.not.satisfied) + c(); + this.i = ""; + c(); + } } diff --git a/checker/tests/nullness/FlowField.java b/checker/tests/nullness/FlowField.java index 9e2a29d08cd..2663e1f87ce 100644 --- a/checker/tests/nullness/FlowField.java +++ b/checker/tests/nullness/FlowField.java @@ -3,74 +3,74 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class FlowField { - public @Nullable String s = null; + public @Nullable String s = null; - void test() { - if (s != null) { - s.startsWith("foo"); - } + void test() { + if (s != null) { + s.startsWith("foo"); } + } - static String field = "asdf"; + static String field = "asdf"; - static { - field = "asdf"; - } + static { + field = "asdf"; + } - void testFields() { - // :: error: (dereference.of.nullable) - System.out.println(field.length()); - } + void testFields() { + // :: error: (dereference.of.nullable) + System.out.println(field.length()); + } - // Fowrard reference to static finals - void test1() { - nonnull.toString(); - } + // Fowrard reference to static finals + void test1() { + nonnull.toString(); + } - private static final String nonnull = new String(); + private static final String nonnull = new String(); - class A { - protected String field = null; - } + class A { + protected String field = null; + } - class B extends A { - void test() { - assert field != null : "@AssumeAssertion(nullness)"; - field.length(); - } + class B extends A { + void test() { + assert field != null : "@AssumeAssertion(nullness)"; + field.length(); } + } - static class BooleanWrapper { - @Nullable Object b; - } + static class BooleanWrapper { + @Nullable Object b; + } - @Nullable BooleanWrapper bw; + @Nullable BooleanWrapper bw; - void testBitwise(@NonNull FlowField a, @NonNull FlowField b) { - Object r; - if (a.bw != null) { - if (b.bw != null) { - r = a.bw.b; - r = b.bw.b; - } - } - if (a.bw != null && b.bw != null) { - r = a.bw.b; - r = b.bw.b; - } + void testBitwise(@NonNull FlowField a, @NonNull FlowField b) { + Object r; + if (a.bw != null) { + if (b.bw != null) { + r = a.bw.b; + r = b.bw.b; + } } - - void testInstanceOf(@NonNull FlowField a) { - if (!(a.s instanceof String)) { - return; - } - @NonNull String s = a.s; + if (a.bw != null && b.bw != null) { + r = a.bw.b; + r = b.bw.b; } + } - void testTwoLevels(@NonNull FlowField a, BooleanWrapper bwArg) { - // :: error: (dereference.of.nullable) - if (!(a.bw.hashCode() == 0)) // warning here - return; - Object o = a.bw.b; // but not here + void testInstanceOf(@NonNull FlowField a) { + if (!(a.s instanceof String)) { + return; } + @NonNull String s = a.s; + } + + void testTwoLevels(@NonNull FlowField a, BooleanWrapper bwArg) { + // :: error: (dereference.of.nullable) + if (!(a.bw.hashCode() == 0)) // warning here + return; + Object o = a.bw.b; // but not here + } } diff --git a/checker/tests/nullness/FlowLoop.java b/checker/tests/nullness/FlowLoop.java index 6f91788a7ea..1beef9f7fac 100644 --- a/checker/tests/nullness/FlowLoop.java +++ b/checker/tests/nullness/FlowLoop.java @@ -1,162 +1,162 @@ import org.checkerframework.checker.nullness.qual.*; public class FlowLoop { - void simpleWhileLoop() { - String s = "m"; + void simpleWhileLoop() { + String s = "m"; - while (s != null) { - s.toString(); - s = null; - } - // :: error: (dereference.of.nullable) - s.toString(); // error - } - - void whileConditionError() { - String s = "m"; - - // :: error: (dereference.of.nullable) - while (s.toString() == "m") { // error - s.toString(); - s = null; - } - s.toString(); - } - - void simpleForLoop() { - for (String s = "m"; s != null; s = null) { - s.toString(); - } - } - - void forLoopConditionError() { - for (String s = "m"; - // :: error: (dereference.of.nullable) - s.toString() != "m"; // error - s = null) { - s.toString(); - } - } - - class Link { - Object val; - @Nullable Link next; - - public Link(Object val, @Nullable Link next) { - this.val = val; - this.next = next; - } - } - - // Both dereferences of l should succeed - void test(@Nullable Link in) { - for (@Nullable Link l = in; l != null; l = l.next) { - Object o; - o = l.val; - } - } - - void multipleRuns() { - String s = "m"; - while (true) { - // :: error: (dereference.of.nullable) - s.toString(); // error - s = null; - } - } - - void multipleRunsDo() { - String s = "m"; - do { - // :: error: (dereference.of.nullable) - s.toString(); // error - s = null; - } while (true); - } - - void alwaysRunForLoop() { - String s = "m"; - for (s = null; s != null; s = "m") { - s.toString(); // ok - } - // :: error: (dereference.of.nullable) - s.toString(); // error - } - - public void badIterator() { - Class opt_doc1 = null; - // :: error: (dereference.of.nullable) - opt_doc1.getInterfaces(); - Class opt_doc2 = null; - // :: error: (dereference.of.nullable) - for (Class fd : opt_doc2.getInterfaces()) { - // empty loop body - } + while (s != null) { + s.toString(); + s = null; } + // :: error: (dereference.of.nullable) + s.toString(); // error + } - void testContinue(@Nullable Object o) { - for (; ; ) { - // :: error: (dereference.of.nullable) - o.toString(); - if (true) continue; - } - } + void whileConditionError() { + String s = "m"; - void testBreak(@Nullable Object o) { - while (true) { - // :: error: (dereference.of.nullable) - o.toString(); - if (true) break; - } + // :: error: (dereference.of.nullable) + while (s.toString() == "m") { // error + s.toString(); + s = null; } + s.toString(); + } - void testSimpleNull() { - String r1 = null; - while (r1 != null) {} - // :: error: (dereference.of.nullable) - r1.toString(); // error + void simpleForLoop() { + for (String s = "m"; s != null; s = null) { + s.toString(); } + } - void testMulticheckNull() { - String r1 = null; - while (r1 != null && r1.equals("m")) {} + void forLoopConditionError() { + for (String s = "m"; // :: error: (dereference.of.nullable) - r1.toString(); // error - } - - void testAssignInLoopSimple() { - String r1 = ""; - while (r1 != null) { - r1 = null; - } - // :: error: (dereference.of.nullable) - r1.toString(); // error - } - - void testAssignInLoopMulti() { - String r1 = ""; - while (r1 != null && r1.isEmpty()) { - r1 = null; - } - // :: error: (dereference.of.nullable) - r1.toString(); // error - } - - void testBreakWithCheck() { - String s = null; - while (true) { - if (s == null) break; - s.toString(); - } - } - - void test1() { - while (true) { - String s = null; - if (s == null) { - return; - } - s.toString(); - } - } + s.toString() != "m"; // error + s = null) { + s.toString(); + } + } + + class Link { + Object val; + @Nullable Link next; + + public Link(Object val, @Nullable Link next) { + this.val = val; + this.next = next; + } + } + + // Both dereferences of l should succeed + void test(@Nullable Link in) { + for (@Nullable Link l = in; l != null; l = l.next) { + Object o; + o = l.val; + } + } + + void multipleRuns() { + String s = "m"; + while (true) { + // :: error: (dereference.of.nullable) + s.toString(); // error + s = null; + } + } + + void multipleRunsDo() { + String s = "m"; + do { + // :: error: (dereference.of.nullable) + s.toString(); // error + s = null; + } while (true); + } + + void alwaysRunForLoop() { + String s = "m"; + for (s = null; s != null; s = "m") { + s.toString(); // ok + } + // :: error: (dereference.of.nullable) + s.toString(); // error + } + + public void badIterator() { + Class opt_doc1 = null; + // :: error: (dereference.of.nullable) + opt_doc1.getInterfaces(); + Class opt_doc2 = null; + // :: error: (dereference.of.nullable) + for (Class fd : opt_doc2.getInterfaces()) { + // empty loop body + } + } + + void testContinue(@Nullable Object o) { + for (; ; ) { + // :: error: (dereference.of.nullable) + o.toString(); + if (true) continue; + } + } + + void testBreak(@Nullable Object o) { + while (true) { + // :: error: (dereference.of.nullable) + o.toString(); + if (true) break; + } + } + + void testSimpleNull() { + String r1 = null; + while (r1 != null) {} + // :: error: (dereference.of.nullable) + r1.toString(); // error + } + + void testMulticheckNull() { + String r1 = null; + while (r1 != null && r1.equals("m")) {} + // :: error: (dereference.of.nullable) + r1.toString(); // error + } + + void testAssignInLoopSimple() { + String r1 = ""; + while (r1 != null) { + r1 = null; + } + // :: error: (dereference.of.nullable) + r1.toString(); // error + } + + void testAssignInLoopMulti() { + String r1 = ""; + while (r1 != null && r1.isEmpty()) { + r1 = null; + } + // :: error: (dereference.of.nullable) + r1.toString(); // error + } + + void testBreakWithCheck() { + String s = null; + while (true) { + if (s == null) break; + s.toString(); + } + } + + void test1() { + while (true) { + String s = null; + if (s == null) { + return; + } + s.toString(); + } + } } diff --git a/checker/tests/nullness/FlowNegation.java b/checker/tests/nullness/FlowNegation.java index cc3716447a4..88afc48a148 100644 --- a/checker/tests/nullness/FlowNegation.java +++ b/checker/tests/nullness/FlowNegation.java @@ -2,98 +2,98 @@ public class FlowNegation { - void testSimpleValid() { - String s = "m"; - s.toString(); - } + void testSimpleValid() { + String s = "m"; + s.toString(); + } - void testCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s != null) { - } else { - // nothing to do - } - s.toString(); + void testCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s != null) { + } else { + // nothing to do } + s.toString(); + } - void testCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s == null) { - } else { - // nothing to do - } - s.toString(); + void testCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s == null) { + } else { + // nothing to do } + s.toString(); + } - void testInvalidCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s != null) { - s = null; - } else { - // nothing to do - } - // :: error: (dereference.of.nullable) - s.toString(); // error + void testInvalidCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s != null) { + s = null; + } else { + // nothing to do } + // :: error: (dereference.of.nullable) + s.toString(); // error + } - void testInvalidCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s != null) { - // nothing to do - } else { - s = null; - } - // :: error: (dereference.of.nullable) - s.toString(); // error + void testInvalidCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s != null) { + // nothing to do + } else { + s = null; } + // :: error: (dereference.of.nullable) + s.toString(); // error + } - void testSimpleValidTernary() { - String s = "m"; - s.toString(); - } + void testSimpleValidTernary() { + String s = "m"; + s.toString(); + } - void testTernaryCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s != null) ? "m" : "n"; - s.toString(); - } + void testTernaryCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s != null) ? "m" : "n"; + s.toString(); + } - void testTernaryCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s == null) ? "m" : "n"; - s.toString(); - } + void testTernaryCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s == null) ? "m" : "n"; + s.toString(); + } - void testTernaryInvalidCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s != null) ? (s = null) : "n"; - // :: error: (dereference.of.nullable) - s.toString(); // error - } + void testTernaryInvalidCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s != null) ? (s = null) : "n"; + // :: error: (dereference.of.nullable) + s.toString(); // error + } - void testTernaryInvalidCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s != null) ? "m" : (s = null); - // :: error: (dereference.of.nullable) - s.toString(); // error - } + void testTernaryInvalidCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s != null) ? "m" : (s = null); + // :: error: (dereference.of.nullable) + s.toString(); // error + } - void testAssignInCond() { - String s = "m"; - if ((s = null) != "m") { - // :: error: (assignment.type.incompatible) - @NonNull String l0 = s; - } else { - } - // :: error: (assignment.type.incompatible) - @NonNull String l1 = s; + void testAssignInCond() { + String s = "m"; + if ((s = null) != "m") { + // :: error: (assignment.type.incompatible) + @NonNull String l0 = s; + } else { } + // :: error: (assignment.type.incompatible) + @NonNull String l1 = s; + } } diff --git a/checker/tests/nullness/FlowNonThis.java b/checker/tests/nullness/FlowNonThis.java index 22dbf3c145b..6a13e4bdf96 100644 --- a/checker/tests/nullness/FlowNonThis.java +++ b/checker/tests/nullness/FlowNonThis.java @@ -1,43 +1,42 @@ +import java.io.*; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import java.io.*; - public class FlowNonThis { - @Nullable String c; - - public static void main(String[] args) { - FlowNonThis t = new FlowNonThis(); - t.setup(); - System.out.println(t.c.length()); - t.erase(); - // :: error: (dereference.of.nullable) - System.out.println(t.c.length()); - } - - public void setupThenErase() { - setup(); - System.out.println(c.length()); - erase(); - // :: error: (dereference.of.nullable) - System.out.println(c.length()); - } - - public void justErase() { - // :: error: (dereference.of.nullable) - System.out.println(c.length()); - erase(); - // :: error: (dereference.of.nullable) - System.out.println(c.length()); - } - - @EnsuresNonNull("c") - public void setup() { - c = "setup"; - } - - public void erase() { - c = null; - } + @Nullable String c; + + public static void main(String[] args) { + FlowNonThis t = new FlowNonThis(); + t.setup(); + System.out.println(t.c.length()); + t.erase(); + // :: error: (dereference.of.nullable) + System.out.println(t.c.length()); + } + + public void setupThenErase() { + setup(); + System.out.println(c.length()); + erase(); + // :: error: (dereference.of.nullable) + System.out.println(c.length()); + } + + public void justErase() { + // :: error: (dereference.of.nullable) + System.out.println(c.length()); + erase(); + // :: error: (dereference.of.nullable) + System.out.println(c.length()); + } + + @EnsuresNonNull("c") + public void setup() { + c = "setup"; + } + + public void erase() { + c = null; + } } diff --git a/checker/tests/nullness/FlowNullness.java b/checker/tests/nullness/FlowNullness.java index 308e39cec1e..4d5941840b0 100644 --- a/checker/tests/nullness/FlowNullness.java +++ b/checker/tests/nullness/FlowNullness.java @@ -2,337 +2,337 @@ public class FlowNullness { - public void testIf() { - - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str != null) { - a = str; - } - - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + public void testIf() { + + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str != null) { + a = str; } - public void testIfNoBlock() { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str != null) { - a = str; - } + public void testIfNoBlock() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str != null) { + a = str; } - public void testElse() { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - } else { - a = str; - } + public void testElse() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + } else { + a = str; } - public void testElseNoBlock() { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - } else { - a = str; - } + public void testElseNoBlock() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + } else { + a = str; } - public void testReturnIf() { - - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - return; - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testReturnIf() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + return; } - public void testReturnElse() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str != null) { - testAssert(); - } else { - return; - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testReturnElse() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str != null) { + testAssert(); + } else { + return; } - public void testThrowIf() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - throw new RuntimeException("foo"); - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testThrowIf() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + throw new RuntimeException("foo"); } - public void testThrowElse() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str != null) { - testAssert(); - } else { - throw new RuntimeException("foo"); - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testThrowElse() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str != null) { + testAssert(); + } else { + throw new RuntimeException("foo"); } - public void testAssert() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - assert str != null; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testAssert() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + String str = "foo"; + // :: warning: (nulltest.redundant) + assert str != null; - public void testWhile() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - while (str != null) { - @NonNull String a = str; - break; - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + public void testWhile() { + + String str = "foo"; + // :: warning: (nulltest.redundant) + while (str != null) { + @NonNull String a = str; + break; } - public void testIfInstanceOf() { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - String str = "foo"; - @NonNull String a; - if (str instanceof String) { - a = str; - } + public void testIfInstanceOf() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + @NonNull String a; + if (str instanceof String) { + a = str; } - public void testNew() { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - String str = "foo"; - @NonNull String a = str; + public void testNew() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String str = "foo"; + @NonNull String a = str; - String s2 = new String(); - s2.toString(); - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; - public void testExit() { + String s2 = new String(); + s2.toString(); + } - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str == null) { - System.exit(0); - } + public void testExit() { - @NonNull String a = str; + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str == null) { + System.exit(0); } - void testMore() { - String str = null + " foo"; - @NonNull String a = str; - } + @NonNull String a = str; + } - void orderOfEvaluation() { - class MyClass { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(@Nullable Object o) { - return o != null; - } - - void test(@Nullable Object a, @Nullable Object b) {} - } - MyClass m = new MyClass(); - m.equals(m = null); - - MyClass n = new MyClass(); - // :: error: (dereference.of.nullable) - n.test(n = null, n.toString()); // error - - MyClass o = null; - // :: error: (dereference.of.nullable) - o.equals(o == new MyClass()); // error - } + void testMore() { + String str = null + " foo"; + @NonNull String a = str; + } - void instanceOf(@Nullable Object o) { - if (o instanceof String) { - // cannot be null here - o.toString(); - return; - } - // :: error: (dereference.of.nullable) - o.toString(); // error - } + void orderOfEvaluation() { + class MyClass { + @org.checkerframework.dataflow.qual.Pure + public boolean equals(@Nullable Object o) { + return o != null; + } - public static void checkConditional1(@Nullable Object a) { - if (a == null) { - } else { - a.getClass(); // not an error - } + void test(@Nullable Object a, @Nullable Object b) {} } - - public static void checkConditional2(@Nullable Object a) { - if (a == null) { - } else if (a instanceof String) { - } else { - a.getClass(); // not an error - } + MyClass m = new MyClass(); + m.equals(m = null); + + MyClass n = new MyClass(); + // :: error: (dereference.of.nullable) + n.test(n = null, n.toString()); // error + + MyClass o = null; + // :: error: (dereference.of.nullable) + o.equals(o == new MyClass()); // error + } + + void instanceOf(@Nullable Object o) { + if (o instanceof String) { + // cannot be null here + o.toString(); + return; } - - public static String spf(String format, @NonNull Object[] args) { - int current_arg = 0; - Object arg = args[current_arg]; - if (false) { - return arg.toString(); // not an error - } - if (arg instanceof long[]) { - return "foo"; - } else { - return arg.toString(); // still not an error - } + // :: error: (dereference.of.nullable) + o.toString(); // error + } + + public static void checkConditional1(@Nullable Object a) { + if (a == null) { + } else { + a.getClass(); // not an error } + } - void empty_makes_no_change() { - String o1 = "not null!"; - if (false) { - // empty branch - } else { - o1 = "still not null!"; - } - System.out.println(o1.toString()); + public static void checkConditional2(@Nullable Object a) { + if (a == null) { + } else if (a instanceof String) { + } else { + a.getClass(); // not an error } + } - @org.checkerframework.dataflow.qual.Pure - public boolean equals(@Nullable Object o) { - if (!(o instanceof Integer)) { - return false; - } - @NonNull Object nno = o; - @NonNull Integer nni = (Integer) o; - return true; + public static String spf(String format, @NonNull Object[] args) { + int current_arg = 0; + Object arg = args[current_arg]; + if (false) { + return arg.toString(); // not an error } - - void while_set_and_test(@Nullable String s) { - String line; - // imagine "s" is "reader.readLine()" (but avoid use of libraries in unit tests) - while ((line = s) != null) { - line.trim(); - } + if (arg instanceof long[]) { + return "foo"; + } else { + return arg.toString(); // still not an error } - - void equality_test(@Nullable String s) { - @NonNull String n = "m"; - if (s == n) { - s.toString(); - } + } + + void empty_makes_no_change() { + String o1 = "not null!"; + if (false) { + // empty branch + } else { + o1 = "still not null!"; } + System.out.println(o1.toString()); + } - @Nullable Object returnNullable() { - return null; + @org.checkerframework.dataflow.qual.Pure + public boolean equals(@Nullable Object o) { + if (!(o instanceof Integer)) { + return false; } - - void testNullableCall() { - if (returnNullable() != null) { - // :: error: (dereference.of.nullable) - returnNullable().toString(); // error - } + @NonNull Object nno = o; + @NonNull Integer nni = (Integer) o; + return true; + } + + void while_set_and_test(@Nullable String s) { + String line; + // imagine "s" is "reader.readLine()" (but avoid use of libraries in unit tests) + while ((line = s) != null) { + line.trim(); } + } - void nonNullArg(@NonNull Object arg) { - // empty body + void equality_test(@Nullable String s) { + @NonNull String n = "m"; + if (s == n) { + s.toString(); } + } - void testNonNullArg(@Nullable Object arg) { - // :: error: (argument.type.incompatible) - nonNullArg(arg); // error - nonNullArg(arg); // no error - } + @Nullable Object returnNullable() { + return null; + } - void test() { - String[] s = null; - // :: error: (dereference.of.nullable) - for (int i = 0; i < s.length; ++i) { // error - String m = s[i]; // fine.. s cannot be null - } + void testNullableCall() { + if (returnNullable() != null) { + // :: error: (dereference.of.nullable) + returnNullable().toString(); // error } - - private double @MonotonicNonNull [] - intersect; // = null; TODO: do we want to allow assignments of null to MonotonicNonNull? - - public void add_modified(double[] a, int count) { - // System.out.println ("common: " + ArraysMDE.toString (a)); - // :: warning: (nulltest.redundant) - if (a == null) { - return; - } else if (intersect == null) { - intersect = a; - return; - } - - double[] tmp = new double[intersect.length]; + } + + void nonNullArg(@NonNull Object arg) { + // empty body + } + + void testNonNullArg(@Nullable Object arg) { + // :: error: (argument.type.incompatible) + nonNullArg(arg); // error + nonNullArg(arg); // no error + } + + void test() { + String[] s = null; + // :: error: (dereference.of.nullable) + for (int i = 0; i < s.length; ++i) { // error + String m = s[i]; // fine.. s cannot be null } + } + + private double @MonotonicNonNull [] + intersect; // = null; TODO: do we want to allow assignments of null to MonotonicNonNull? + + public void add_modified(double[] a, int count) { + // System.out.println ("common: " + ArraysMDE.toString (a)); + // :: warning: (nulltest.redundant) + if (a == null) { + return; + } else if (intersect == null) { + intersect = a; + return; + } + + double[] tmp = new double[intersect.length]; + } } diff --git a/checker/tests/nullness/FlowSelf.java b/checker/tests/nullness/FlowSelf.java index 10ba4e5d3a4..8a49f5b8508 100644 --- a/checker/tests/nullness/FlowSelf.java +++ b/checker/tests/nullness/FlowSelf.java @@ -2,14 +2,14 @@ public class FlowSelf { - void test(@Nullable String s) { + void test(@Nullable String s) { - if (s == null) { - return; - } - // :: warning: (nulltest.redundant) - assert s != null; - - s = s.substring(1); + if (s == null) { + return; } + // :: warning: (nulltest.redundant) + assert s != null; + + s = s.substring(1); + } } diff --git a/checker/tests/nullness/ForEachMin.java b/checker/tests/nullness/ForEachMin.java index a3a27b9842f..de66b833426 100644 --- a/checker/tests/nullness/ForEachMin.java +++ b/checker/tests/nullness/ForEachMin.java @@ -1,22 +1,21 @@ -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; class MyTop { - List children = new ArrayList<>(); + List children = new ArrayList<>(); } abstract class PptRelationMin { - abstract MyTop getPpt(); + abstract MyTop getPpt(); - void init_hierarchy_new() { - MyTop ppt = getPpt(); + void init_hierarchy_new() { + MyTop ppt = getPpt(); - @NonNull Object o1 = ppt.children; + @NonNull Object o1 = ppt.children; - for (String rel : ppt.children) {} + for (String rel : ppt.children) {} - @NonNull Object o2 = ppt.children; - } + @NonNull Object o2 = ppt.children; + } } diff --git a/checker/tests/nullness/FullyQualifiedAnnotation.java b/checker/tests/nullness/FullyQualifiedAnnotation.java index 438a9f2d6cf..fda3122451d 100644 --- a/checker/tests/nullness/FullyQualifiedAnnotation.java +++ b/checker/tests/nullness/FullyQualifiedAnnotation.java @@ -2,27 +2,27 @@ public class FullyQualifiedAnnotation { - void client1(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client1(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } - void client2(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client2(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } - void client3(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client3(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } - void client4(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client4(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } } diff --git a/checker/tests/nullness/GeneralATFStore.java b/checker/tests/nullness/GeneralATFStore.java index e282e3a8502..fa09f950fe9 100644 --- a/checker/tests/nullness/GeneralATFStore.java +++ b/checker/tests/nullness/GeneralATFStore.java @@ -9,14 +9,14 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface FailAnno { - String value(); + String value(); - boolean flag() default false; + boolean flag() default false; } class Fail { - @FailAnno(value = "Fail", flag = true) - String f = "fail"; + @FailAnno(value = "Fail", flag = true) + String f = "fail"; - Object x = Fail.class; + Object x = Fail.class; } diff --git a/checker/tests/nullness/GenericCast.java b/checker/tests/nullness/GenericCast.java index 5ea1ce3cd49..739703fe433 100644 --- a/checker/tests/nullness/GenericCast.java +++ b/checker/tests/nullness/GenericCast.java @@ -1,14 +1,14 @@ // @skip-test Fails, but commented out to avoid breaking the build public class GenericCast { - @SuppressWarnings("unchecked") - T tObject = (T) new Object(); + @SuppressWarnings("unchecked") + T tObject = (T) new Object(); - T field1 = tObject; + T field1 = tObject; - T field2; + T field2; - GenericCast() { - field2 = tObject; - } + GenericCast() { + field2 = tObject; + } } diff --git a/checker/tests/nullness/GetConstantStr.java b/checker/tests/nullness/GetConstantStr.java index d40a7a3be9c..59a3a5d60fa 100644 --- a/checker/tests/nullness/GetConstantStr.java +++ b/checker/tests/nullness/GetConstantStr.java @@ -1,6 +1,6 @@ public class GetConstantStr { - public static void get_constant_str(Object obj) { - // :: warning: (nulltest.redundant) - assert obj != null; - } + public static void get_constant_str(Object obj) { + // :: warning: (nulltest.redundant) + assert obj != null; + } } diff --git a/checker/tests/nullness/GetInterfacesPurity.java b/checker/tests/nullness/GetInterfacesPurity.java index 1241779c4b1..8d27b531c57 100644 --- a/checker/tests/nullness/GetInterfacesPurity.java +++ b/checker/tests/nullness/GetInterfacesPurity.java @@ -2,10 +2,10 @@ public class GetInterfacesPurity { - @Pure - public static boolean isSubtypeTestMethod(Class sub, Class sup) { - // :: error: (purity.not.deterministic.call) - Class[] interfaces = sub.getInterfaces(); - return interfaces.length == 0; - } + @Pure + public static boolean isSubtypeTestMethod(Class sub, Class sup) { + // :: error: (purity.not.deterministic.call) + Class[] interfaces = sub.getInterfaces(); + return interfaces.length == 0; + } } diff --git a/checker/tests/nullness/GetPackage1.java b/checker/tests/nullness/GetPackage1.java index 4171d19cdd5..9ae68d34da6 100644 --- a/checker/tests/nullness/GetPackage1.java +++ b/checker/tests/nullness/GetPackage1.java @@ -4,9 +4,9 @@ public class GetPackage { - void callGetPackage() { + void callGetPackage() { - @NonNull Package p1 = GetPackage.class.getPackage(); - @NonNull Package p2 = java.util.List.class.getPackage(); - } + @NonNull Package p1 = GetPackage.class.getPackage(); + @NonNull Package p2 = java.util.List.class.getPackage(); + } } diff --git a/checker/tests/nullness/GetProperty.java b/checker/tests/nullness/GetProperty.java index 9c27c55e40f..02ad7e94ab5 100644 --- a/checker/tests/nullness/GetProperty.java +++ b/checker/tests/nullness/GetProperty.java @@ -1,26 +1,25 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.Properties; +import org.checkerframework.checker.nullness.qual.*; public class GetProperty { - @NonNull Object nno = new Object(); + @NonNull Object nno = new Object(); - void m(Properties p) { + void m(Properties p) { - String s = "line.separator"; + String s = "line.separator"; - nno = System.getProperty("line.separator"); - // :: error: (assignment.type.incompatible) - nno = System.getProperty(s); - // :: error: (assignment.type.incompatible) - nno = System.getProperty("not.a.builtin.property"); + nno = System.getProperty("line.separator"); + // :: error: (assignment.type.incompatible) + nno = System.getProperty(s); + // :: error: (assignment.type.incompatible) + nno = System.getProperty("not.a.builtin.property"); - // :: error: (assignment.type.incompatible) - nno = p.getProperty("line.separator"); - // :: error: (assignment.type.incompatible) - nno = p.getProperty(s); - // :: error: (assignment.type.incompatible) - nno = p.getProperty("not.a.builtin.property"); - } + // :: error: (assignment.type.incompatible) + nno = p.getProperty("line.separator"); + // :: error: (assignment.type.incompatible) + nno = p.getProperty(s); + // :: error: (assignment.type.incompatible) + nno = p.getProperty("not.a.builtin.property"); + } } diff --git a/checker/tests/nullness/GetRefArg.java b/checker/tests/nullness/GetRefArg.java index 2f398aca44a..7df75fae6a1 100644 --- a/checker/tests/nullness/GetRefArg.java +++ b/checker/tests/nullness/GetRefArg.java @@ -1,11 +1,10 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.lang.reflect.*; +import org.checkerframework.checker.nullness.qual.*; public class GetRefArg { - private void get_ref_arg(Constructor constructor) throws Exception { - Object val = constructor.newInstance(); - // :: warning: (nulltest.redundant) - assert val != null; - } + private void get_ref_arg(Constructor constructor) throws Exception { + Object val = constructor.newInstance(); + // :: warning: (nulltest.redundant) + assert val != null; + } } diff --git a/checker/tests/nullness/HasInnerClass.java b/checker/tests/nullness/HasInnerClass.java index 0a93c3eaa17..298ef0faaef 100644 --- a/checker/tests/nullness/HasInnerClass.java +++ b/checker/tests/nullness/HasInnerClass.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.*; public class HasInnerClass { - public class InternalEdge { - public void m() { - HasInnerClass.InternalEdge other = null; - } + public class InternalEdge { + public void m() { + HasInnerClass.InternalEdge other = null; } + } } diff --git a/checker/tests/nullness/HierarchicalInit.java b/checker/tests/nullness/HierarchicalInit.java index eb85da39275..6987dd2f59b 100644 --- a/checker/tests/nullness/HierarchicalInit.java +++ b/checker/tests/nullness/HierarchicalInit.java @@ -2,18 +2,18 @@ public class HierarchicalInit { - String a; + String a; - public HierarchicalInit() { - a = ""; - } + public HierarchicalInit() { + a = ""; + } - public static class B extends HierarchicalInit { - String b; + public static class B extends HierarchicalInit { + String b; - public B() { - super(); - b = ""; - } + public B() { + super(); + b = ""; } + } } diff --git a/checker/tests/nullness/ImplementInterface.java b/checker/tests/nullness/ImplementInterface.java index 610c165307f..45540596798 100644 --- a/checker/tests/nullness/ImplementInterface.java +++ b/checker/tests/nullness/ImplementInterface.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.nullness.qual.*; interface TestInterface { - public char @Nullable [] getChars(); + public char @Nullable [] getChars(); } public class ImplementInterface implements TestInterface { - @Override - public char @Nullable [] getChars() { - return null; - } + @Override + public char @Nullable [] getChars() { + return null; + } } diff --git a/checker/tests/nullness/Imports1.java b/checker/tests/nullness/Imports1.java index 7017b330fe5..d218d71ca48 100644 --- a/checker/tests/nullness/Imports1.java +++ b/checker/tests/nullness/Imports1.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.nullness.qual.*; public class Imports1 { - void call() { - java.util.Arrays.asList("m", 1); - } + void call() { + java.util.Arrays.asList("m", 1); + } } diff --git a/checker/tests/nullness/Imports2.java b/checker/tests/nullness/Imports2.java index 655b5225514..806ad1622c1 100644 --- a/checker/tests/nullness/Imports2.java +++ b/checker/tests/nullness/Imports2.java @@ -1,5 +1,5 @@ public class Imports2 { - void call() { - java.util.Arrays.asList("m", 1); - } + void call() { + java.util.Arrays.asList("m", 1); + } } diff --git a/checker/tests/nullness/InferListParam.java b/checker/tests/nullness/InferListParam.java index 981823e98d8..c1d8c405f34 100644 --- a/checker/tests/nullness/InferListParam.java +++ b/checker/tests/nullness/InferListParam.java @@ -2,9 +2,9 @@ import java.util.List; public class InferListParam { - List fieldValues; + List fieldValues; - InferListParam() { - fieldValues = Collections.emptyList(); - } + InferListParam() { + fieldValues = Collections.emptyList(); + } } diff --git a/checker/tests/nullness/InferNullType.java b/checker/tests/nullness/InferNullType.java index 8f9eacbab8b..b1722fa68e4 100644 --- a/checker/tests/nullness/InferNullType.java +++ b/checker/tests/nullness/InferNullType.java @@ -1,36 +1,36 @@ // Version of framework/tests/all-systems/InferNullType.java with expected Nullness Checker warnings public class InferNullType { - T toInfer(T input) { - return input; - } + T toInfer(T input) { + return input; + } - T toInfer2(T input) { - return input; - } + T toInfer2(T input) { + return input; + } - T toInfer3(T input, S p2) { - return input; - } + T toInfer3(T input, S p2) { + return input; + } - T toInfer4(T input, S p2) { - return input; - } + T toInfer4(T input, S p2) { + return input; + } - void x() { - // :: error: (type.argument.type.incompatible) - Object m = toInfer(null); - Object m2 = toInfer2(null); + void x() { + // :: error: (type.argument.type.incompatible) + Object m = toInfer(null); + Object m2 = toInfer2(null); - Object m3 = toInfer3(null, null); - Object m4 = toInfer3(1, null); - Object m5 = toInfer3(null, 1); + Object m3 = toInfer3(null, null); + Object m4 = toInfer3(1, null); + Object m5 = toInfer3(null, 1); - // :: error: (type.argument.type.incompatible) - Object m6 = toInfer4(null, null); - // :: error: (type.argument.type.incompatible) - Object m7 = toInfer4(1, null); - // :: error: (type.argument.type.incompatible) - Object m8 = toInfer4(null, 1); - } + // :: error: (type.argument.type.incompatible) + Object m6 = toInfer4(null, null); + // :: error: (type.argument.type.incompatible) + Object m7 = toInfer4(1, null); + // :: error: (type.argument.type.incompatible) + Object m8 = toInfer4(null, 1); + } } diff --git a/checker/tests/nullness/InferTypeArgsConditionalExpression.java b/checker/tests/nullness/InferTypeArgsConditionalExpression.java index b0103be2130..32b43ff8da3 100644 --- a/checker/tests/nullness/InferTypeArgsConditionalExpression.java +++ b/checker/tests/nullness/InferTypeArgsConditionalExpression.java @@ -8,12 +8,12 @@ public class InferTypeArgsConditionalExpression { - public void foo(Generic real, Generic other, boolean flag) { - // :: error: (type.argument.type.incompatible) - bar(flag ? real : other); - } + public void foo(Generic real, Generic other, boolean flag) { + // :: error: (type.argument.type.incompatible) + bar(flag ? real : other); + } - <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} + <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} - interface Generic {} + interface Generic {} } diff --git a/checker/tests/nullness/InfiniteLoopIsSameType.java b/checker/tests/nullness/InfiniteLoopIsSameType.java index 4cd8017eebf..b37cbe188ab 100644 --- a/checker/tests/nullness/InfiniteLoopIsSameType.java +++ b/checker/tests/nullness/InfiniteLoopIsSameType.java @@ -7,22 +7,22 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public class InfiniteLoopIsSameType { - private interface Intf1 { - R apply(); - } + private interface Intf1 { + R apply(); + } - public void compute( - // checker works fine if not annotated - Intf1 remappingFunction) { - // must assign null - Object nullval = null; - for (; ; ) { - // must assign to the null object - nullval = remappingFunction.apply(); - // break must be in an if statement - if (true) { - break; - } - } + public void compute( + // checker works fine if not annotated + Intf1 remappingFunction) { + // must assign null + Object nullval = null; + for (; ; ) { + // must assign to the null object + nullval = remappingFunction.apply(); + // break must be in an if statement + if (true) { + break; + } } + } } diff --git a/checker/tests/nullness/InitSuppressWarnings.java b/checker/tests/nullness/InitSuppressWarnings.java index 5c28f43b770..ecc059a680a 100644 --- a/checker/tests/nullness/InitSuppressWarnings.java +++ b/checker/tests/nullness/InitSuppressWarnings.java @@ -3,8 +3,8 @@ public class InitSuppressWarnings { - private void init_vars(@UnderInitialization(Object.class) InitSuppressWarnings this) { - @SuppressWarnings({"initialization"}) - @Initialized InitSuppressWarnings initializedThis = this; - } + private void init_vars(@UnderInitialization(Object.class) InitSuppressWarnings this) { + @SuppressWarnings({"initialization"}) + @Initialized InitSuppressWarnings initializedThis = this; + } } diff --git a/checker/tests/nullness/InitThrows.java b/checker/tests/nullness/InitThrows.java index ae0fc36cf9d..c496279a492 100644 --- a/checker/tests/nullness/InitThrows.java +++ b/checker/tests/nullness/InitThrows.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.nullness.qual.*; public class InitThrows { - private final Object o; + private final Object o; - { - try { - o = new Object(); - } catch (Exception e) { - throw new RuntimeException(e); - } + { + try { + o = new Object(); + } catch (Exception e) { + throw new RuntimeException(e); } + } } diff --git a/checker/tests/nullness/InitializationAssertionFailure.java b/checker/tests/nullness/InitializationAssertionFailure.java index 3c81951d5a5..a083b04cb45 100644 --- a/checker/tests/nullness/InitializationAssertionFailure.java +++ b/checker/tests/nullness/InitializationAssertionFailure.java @@ -5,7 +5,7 @@ public class InitializationAssertionFailure implements Serializable { - static final long serialVersionUID = 20030819L; + static final long serialVersionUID = 20030819L; - private InitializationAssertionFailure() {} + private InitializationAssertionFailure() {} } diff --git a/checker/tests/nullness/InitializedField.java b/checker/tests/nullness/InitializedField.java index 86976a6274b..4fd0e2b2f90 100644 --- a/checker/tests/nullness/InitializedField.java +++ b/checker/tests/nullness/InitializedField.java @@ -1,23 +1,22 @@ +import java.util.Stack; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.dataflow.qual.*; -import java.util.Stack; - public final class InitializedField { - private Stack stack; + private Stack stack; - InitializedField() { - stack = new Stack(); - iPeek(); - } + InitializedField() { + stack = new Stack(); + iPeek(); + } - @RequiresNonNull("stack") - public Object iPeek(@UnknownInitialization InitializedField this) { - return stack.peek(); - } + @RequiresNonNull("stack") + public Object iPeek(@UnknownInitialization InitializedField this) { + return stack.peek(); + } - public static void testJavaClass(InitializedField initField) { - initField.iPeek(); - } + public static void testJavaClass(InitializedField initField) { + initField.iPeek(); + } } diff --git a/checker/tests/nullness/InnerCrash.java b/checker/tests/nullness/InnerCrash.java index 3aafe26b9d0..3169bc0e101 100644 --- a/checker/tests/nullness/InnerCrash.java +++ b/checker/tests/nullness/InnerCrash.java @@ -1,14 +1,14 @@ import org.checkerframework.checker.nullness.qual.Nullable; class InnerCrash { - class Inner {} + class Inner {} - static @Nullable InnerCrash getInnerCrash() { - return null; - } + static @Nullable InnerCrash getInnerCrash() { + return null; + } - static void foo() { - // :: error: (dereference.of.nullable) - Object o = getInnerCrash().new Inner(); - } + static void foo() { + // :: error: (dereference.of.nullable) + Object o = getInnerCrash().new Inner(); + } } diff --git a/checker/tests/nullness/InvariantTypes.java b/checker/tests/nullness/InvariantTypes.java index 2ea035b4cd2..32f751c6907 100644 --- a/checker/tests/nullness/InvariantTypes.java +++ b/checker/tests/nullness/InvariantTypes.java @@ -1,30 +1,30 @@ import org.checkerframework.checker.nullness.qual.*; public class InvariantTypes { - // The RHS is @NonNull [], but context decides to make it @Nullable - @Nullable Object[] noa = {"non-null!"}; + // The RHS is @NonNull [], but context decides to make it @Nullable + @Nullable Object[] noa = {"non-null!"}; - // Type for array creation is propagated from LHS - @MonotonicNonNull Object[] f = new Object[5]; + // Type for array creation is propagated from LHS + @MonotonicNonNull Object[] f = new Object[5]; - void testAsLocal() { - @MonotonicNonNull Object[] lo; - lo = new Object[5]; - // :: error: (assignment.type.incompatible) - lo[0] = null; - lo[0] = new Object(); - // :: error: (dereference.of.nullable) - lo[1].toString(); - } + void testAsLocal() { + @MonotonicNonNull Object[] lo; + lo = new Object[5]; + // :: error: (assignment.type.incompatible) + lo[0] = null; + lo[0] = new Object(); + // :: error: (dereference.of.nullable) + lo[1].toString(); + } - // Type for array creation is propagated from LHS - @SuppressWarnings("invalid.polymorphic.qualifier.use") - @PolyNull Object[] po = new Object[5]; + // Type for array creation is propagated from LHS + @SuppressWarnings("invalid.polymorphic.qualifier.use") + @PolyNull Object[] po = new Object[5]; - void testDecl(@MonotonicNonNull Object[] p) {} + void testDecl(@MonotonicNonNull Object[] p) {} - void testCall() { - // Type for array creation is propaged from parameter type - testDecl(new Object[5]); - } + void testCall() { + // Type for array creation is propaged from parameter type + testDecl(new Object[5]); + } } diff --git a/checker/tests/nullness/IsEmptyPoll.java b/checker/tests/nullness/IsEmptyPoll.java index 30f618aca32..0cd3be1e053 100644 --- a/checker/tests/nullness/IsEmptyPoll.java +++ b/checker/tests/nullness/IsEmptyPoll.java @@ -3,29 +3,28 @@ // @skip-test until the issue is fixed -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.ArrayList; import java.util.Queue; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public final class IsEmptyPoll extends ArrayList { - void mNonNull(Queue q) { - while (!q.isEmpty()) { - @NonNull String firstNode = q.poll(); - } + void mNonNull(Queue q) { + while (!q.isEmpty()) { + @NonNull String firstNode = q.poll(); } + } - void mNullable(Queue<@Nullable String> q) { - while (!q.isEmpty()) { - // :: error: (assignment.type.incompatible) - @NonNull String firstNode = q.poll(); - } + void mNullable(Queue<@Nullable String> q) { + while (!q.isEmpty()) { + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q.poll(); } + } - void mNoCheck(Queue<@Nullable String> q) { - // :: error: (assignment.type.incompatible) - @NonNull String firstNode = q.poll(); - } + void mNoCheck(Queue<@Nullable String> q) { + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q.poll(); + } } diff --git a/checker/tests/nullness/Issue1027.java b/checker/tests/nullness/Issue1027.java index b593b288a4e..26c90d90660 100644 --- a/checker/tests/nullness/Issue1027.java +++ b/checker/tests/nullness/Issue1027.java @@ -4,42 +4,41 @@ // Use -J-XX:MaxJavaStackTraceDepth=1000000 as parameter // to javac to see a longer stacktrace. -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.KeyFor; public class Issue1027 { - // Stand-alone reproduction + // Stand-alone reproduction - class Repr { - void bar(Function p) {} - } + class Repr { + void bar(Function p) {} + } - @SuppressWarnings({"nullness", "keyfor"}) - Repr<@KeyFor("this") String> foo() { - return null; - } + @SuppressWarnings({"nullness", "keyfor"}) + Repr<@KeyFor("this") String> foo() { + return null; + } - void zoo(Issue1027 p) { - p.foo().bar(x -> ""); - } + void zoo(Issue1027 p) { + p.foo().bar(x -> ""); + } - // Various longer versions that also used to give SOE + // Various longer versions that also used to give SOE - void foo(Map arg) { - arg.keySet().stream().map(key -> key); - } + void foo(Map arg) { + arg.keySet().stream().map(key -> key); + } - Stream foo(Set arg) { - return arg.stream().map(key -> key); - } + Stream foo(Set arg) { + return arg.stream().map(key -> key); + } - String foo(Stream stream) { - return stream.map(key -> key).collect(Collectors.joining()); - } + String foo(Stream stream) { + return stream.map(key -> key).collect(Collectors.joining()); + } } diff --git a/checker/tests/nullness/Issue1046Java7.java b/checker/tests/nullness/Issue1046Java7.java index a94041af67e..b08381fbb41 100644 --- a/checker/tests/nullness/Issue1046Java7.java +++ b/checker/tests/nullness/Issue1046Java7.java @@ -6,29 +6,29 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1046Java7 { - interface MyInterface {} + interface MyInterface {} - class MyClass implements MyInterface {} + class MyClass implements MyInterface {} - class Function {} + class Function {} - abstract static class NotSubtype2 { - static void transform(Function q) {} + abstract static class NotSubtype2 { + static void transform(Function q) {} - static void transform2(Function q) {} + static void transform2(Function q) {} - void test1(Function p, Function p2) { - transform(p); - // :: error: (argument.type.incompatible) - transform2(p); - transform(p2); - transform2(p2); - } + void test1(Function p, Function p2) { + transform(p); + // :: error: (argument.type.incompatible) + transform2(p); + transform(p2); + transform2(p2); + } - @Nullable Function NULL = null; + @Nullable Function NULL = null; - void test2(@Nullable Function queue) { - Function x = (queue == null) ? NULL : queue; - } + void test2(@Nullable Function queue) { + Function x = (queue == null) ? NULL : queue; } + } } diff --git a/checker/tests/nullness/Issue1059.java b/checker/tests/nullness/Issue1059.java index df652f8720e..1c2e7f49d6e 100644 --- a/checker/tests/nullness/Issue1059.java +++ b/checker/tests/nullness/Issue1059.java @@ -5,18 +5,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1059 { - @Nullable Object f; + @Nullable Object f; - @EnsuresNonNull({"f"}) - void foo() { - f = new Object(); - } + @EnsuresNonNull({"f"}) + void foo() { + f = new Object(); + } - void bar() { - switch (this.hashCode()) { - case 1: - foo(); - Object dada = f.toString(); - } + void bar() { + switch (this.hashCode()) { + case 1: + foo(); + Object dada = f.toString(); } + } } diff --git a/checker/tests/nullness/Issue1102.java b/checker/tests/nullness/Issue1102.java index c3fba7d9d06..56bcb47a16b 100644 --- a/checker/tests/nullness/Issue1102.java +++ b/checker/tests/nullness/Issue1102.java @@ -9,21 +9,20 @@ interface Issue1102Itf {} class Issue1102Base {} class Issue1102Decl extends Issue1102Base { - static Issue1102Decl newInstance( - T s) { - return new Issue1102Decl(); - } + static Issue1102Decl newInstance(T s) { + return new Issue1102Decl(); + } } class Issue1102Use { - @SuppressWarnings("initialization.field.uninitialized") - U f; + @SuppressWarnings("initialization.field.uninitialized") + U f; - @Nullable U g = null; + @Nullable U g = null; - void bar() { - Issue1102Decl d = Issue1102Decl.newInstance(f); - // :: error: (type.argument.type.incompatible) - d = Issue1102Decl.newInstance(g); - } + void bar() { + Issue1102Decl d = Issue1102Decl.newInstance(f); + // :: error: (type.argument.type.incompatible) + d = Issue1102Decl.newInstance(g); + } } diff --git a/checker/tests/nullness/Issue1147.java b/checker/tests/nullness/Issue1147.java index 65ae001664a..49921678397 100644 --- a/checker/tests/nullness/Issue1147.java +++ b/checker/tests/nullness/Issue1147.java @@ -4,14 +4,14 @@ public class Issue1147 { - public static void main(String[] args) { + public static void main(String[] args) { - StringJoiner sj = new StringJoiner(","); + StringJoiner sj = new StringJoiner(","); - sj.add("a"); + sj.add("a"); - sj.add(null); + sj.add(null); - System.out.println(sj); - } + System.out.println(sj); + } } diff --git a/checker/tests/nullness/Issue1307.java b/checker/tests/nullness/Issue1307.java index a5978de12d8..55c26de5e98 100644 --- a/checker/tests/nullness/Issue1307.java +++ b/checker/tests/nullness/Issue1307.java @@ -7,9 +7,9 @@ @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.FIELD) @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.PARAMETER) public class Issue1307 { - Object nullableField = null; + Object nullableField = null; - void perl(Integer a) { - a = null; - } + void perl(Integer a) { + a = null; + } } diff --git a/checker/tests/nullness/Issue1406.java b/checker/tests/nullness/Issue1406.java index 1f33f7bd2fc..c33418b004c 100644 --- a/checker/tests/nullness/Issue1406.java +++ b/checker/tests/nullness/Issue1406.java @@ -1,38 +1,37 @@ // Test case for Issue 1406 // https://github.com/typetools/checker-framework/issues/1406 -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.dataflow.qual.Pure; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.dataflow.qual.Pure; @SuppressWarnings({"purity", "contracts.postcondition.not.satisfied"}) // Only test parsing public class Issue1406 { - public static void main(String[] args) {} - - @Pure - @EnsuresNonNull("myMethod(#1).get(0)") - List myMethod(int arg) { - List result = new ArrayList<>(); - result.add("non-null value"); - return result; - } - - String client(int arg) { - return myMethod(arg).get(0); - } - - @Pure - @EnsuresNonNull("myMethod2().get(0)") - List myMethod2() { - List result = new ArrayList<>(); - result.add("non-null value"); - return result; - } - - String client2() { - return myMethod2().get(0); - } + public static void main(String[] args) {} + + @Pure + @EnsuresNonNull("myMethod(#1).get(0)") + List myMethod(int arg) { + List result = new ArrayList<>(); + result.add("non-null value"); + return result; + } + + String client(int arg) { + return myMethod(arg).get(0); + } + + @Pure + @EnsuresNonNull("myMethod2().get(0)") + List myMethod2() { + List result = new ArrayList<>(); + result.add("non-null value"); + return result; + } + + String client2() { + return myMethod2().get(0); + } } diff --git a/checker/tests/nullness/Issue1522.java b/checker/tests/nullness/Issue1522.java index 032e1dee193..67cb8e22401 100644 --- a/checker/tests/nullness/Issue1522.java +++ b/checker/tests/nullness/Issue1522.java @@ -1,53 +1,52 @@ // Test case for Issue 1522 // https://github.com/typetools/checker-framework/issues/1522 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Vector; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1522 { - void copyInto(String p) {} - - void bar() { - copyInto("Hi"); - } - - void copyVector(Vector v, Integer[] intArray, String[] stringArray) { - // Java types aren't compatible - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(intArray); - v.copyInto(stringArray); - } - - void copyStack(SubClassVector v, Integer[] intArray, String[] stringArray) { - // Java types aren't compatible - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(intArray); - v.copyInto(stringArray); - } - - void copyVectorErrors(Vector<@Nullable String> v, String[] stringArray) { - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(stringArray); - } - - void copyStackErrors(SubClassVector<@Nullable String> v, String[] stringArray) { - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(stringArray); - } - - void copyVectorNullable(Vector<@Nullable String> v, @Nullable String[] stringArray) { - v.copyInto(stringArray); - } - - void copyStackNullable(SubClassVector<@Nullable String> v, @Nullable String[] stringArray) { - v.copyInto(stringArray); - } - - static class SubClassVector extends Vector { - @Override - public synchronized void copyInto(@Nullable Object[] anArray) { - super.copyInto(anArray); - } - } + void copyInto(String p) {} + + void bar() { + copyInto("Hi"); + } + + void copyVector(Vector v, Integer[] intArray, String[] stringArray) { + // Java types aren't compatible + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(intArray); + v.copyInto(stringArray); + } + + void copyStack(SubClassVector v, Integer[] intArray, String[] stringArray) { + // Java types aren't compatible + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(intArray); + v.copyInto(stringArray); + } + + void copyVectorErrors(Vector<@Nullable String> v, String[] stringArray) { + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(stringArray); + } + + void copyStackErrors(SubClassVector<@Nullable String> v, String[] stringArray) { + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(stringArray); + } + + void copyVectorNullable(Vector<@Nullable String> v, @Nullable String[] stringArray) { + v.copyInto(stringArray); + } + + void copyStackNullable(SubClassVector<@Nullable String> v, @Nullable String[] stringArray) { + v.copyInto(stringArray); + } + + static class SubClassVector extends Vector { + @Override + public synchronized void copyInto(@Nullable Object[] anArray) { + super.copyInto(anArray); + } + } } diff --git a/checker/tests/nullness/Issue1555.java b/checker/tests/nullness/Issue1555.java index 451989b72ab..7c355c0ea77 100644 --- a/checker/tests/nullness/Issue1555.java +++ b/checker/tests/nullness/Issue1555.java @@ -6,9 +6,9 @@ public class Issue1555 { - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - String test() { - return NullnessUtil.castNonNull(x); - } + String test() { + return NullnessUtil.castNonNull(x); + } } diff --git a/checker/tests/nullness/Issue160.java b/checker/tests/nullness/Issue160.java index 69a3727a83c..9832262c291 100644 --- a/checker/tests/nullness/Issue160.java +++ b/checker/tests/nullness/Issue160.java @@ -1,58 +1,58 @@ public class Issue160 { - public static void t1() { - String s = null; - if (s != null) { - } else { - return; - } - System.out.println(s.toString()); + public static void t1() { + String s = null; + if (s != null) { + } else { + return; } + System.out.println(s.toString()); + } - public static void t2() { - String s = null; - if (s != null) { - } else { - throw new RuntimeException(); - } - System.out.println(s.toString()); + public static void t2() { + String s = null; + if (s != null) { + } else { + throw new RuntimeException(); } + System.out.println(s.toString()); + } - public static void t3() { - String s = null; - if (s != null) { - } else { - System.exit(0); - } - System.out.println(s.toString()); + public static void t3() { + String s = null; + if (s != null) { + } else { + System.exit(0); } + System.out.println(s.toString()); + } - public static void t1b() { - String s = null; - if (s == null) { - } else { - return; - } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); + public static void t1b() { + String s = null; + if (s == null) { + } else { + return; } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); + } - public static void t2b() { - String s = null; - if (s == null) { - } else { - throw new RuntimeException(); - } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); + public static void t2b() { + String s = null; + if (s == null) { + } else { + throw new RuntimeException(); } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); + } - public static void t3b() { - String s = null; - if (s == null) { - } else { - System.exit(0); - } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); + public static void t3b() { + String s = null; + if (s == null) { + } else { + System.exit(0); } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); + } } diff --git a/checker/tests/nullness/Issue1628.java b/checker/tests/nullness/Issue1628.java index 363571381cc..5dc324cb437 100644 --- a/checker/tests/nullness/Issue1628.java +++ b/checker/tests/nullness/Issue1628.java @@ -6,16 +6,16 @@ public class Issue1628> implements Issue1628R { - public boolean isEmpty() { - return false; - } + public boolean isEmpty() { + return false; + } - public boolean equals(@Nullable Object o) { - return (o instanceof Issue1628R) && ((Issue1628R) o).isEmpty(); - } + public boolean equals(@Nullable Object o) { + return (o instanceof Issue1628R) && ((Issue1628R) o).isEmpty(); + } } interface Issue1628R> { - @Pure - boolean isEmpty(); + @Pure + boolean isEmpty(); } diff --git a/checker/tests/nullness/Issue1712.java b/checker/tests/nullness/Issue1712.java index f3275df48d9..e5cddb429f8 100644 --- a/checker/tests/nullness/Issue1712.java +++ b/checker/tests/nullness/Issue1712.java @@ -3,24 +3,24 @@ abstract class Issue1712 { - abstract T match( - Function visitA, Function visitB); + abstract T match( + Function visitA, Function visitB); - class SubclassA extends Issue1712 { - @Override - T match(Function visitA, Function visitB) { - return visitA.apply(this); // line 11 - } + class SubclassA extends Issue1712 { + @Override + T match(Function visitA, Function visitB) { + return visitA.apply(this); // line 11 } + } - class SubclassB extends Issue1712 { - @Override - T match(Function visitA, Function visitB) { - return visitB.apply(this); // line 18 - } + class SubclassB extends Issue1712 { + @Override + T match(Function visitA, Function visitB) { + return visitB.apply(this); // line 18 } + } - abstract class Function { - abstract T2 apply(T1 arg); - } + abstract class Function { + abstract T2 apply(T1 arg); + } } diff --git a/checker/tests/nullness/Issue1797.java b/checker/tests/nullness/Issue1797.java index f1bcf42dfe8..92e31791487 100644 --- a/checker/tests/nullness/Issue1797.java +++ b/checker/tests/nullness/Issue1797.java @@ -4,255 +4,255 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1797 { - void fooReturn(@Nullable Object o) { - try { - return; - } finally { - if (o != null) { - o.toString(); - } - } - } - - void fooWhileReturn(@Nullable Object o) { - while (this.hashCode() < 5) { - try { - return; - } finally { - if (o != null) { - o.toString(); - } - } - } - } - - void fooReturnNested(@Nullable Object o) { - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { - try { - return; - } finally { - if (o != null) { - o.toString(); - } - // go to exit, not either loop - } - } - } + void fooReturn(@Nullable Object o) { + try { + return; + } finally { + if (o != null) { + o.toString(); + } } + } - void fooBreak(@Nullable Object o) { - while (this.hashCode() < 5) { - try { - break; - } finally { - if (o != null) { - o.toString(); - } - } + void fooWhileReturn(@Nullable Object o) { + while (this.hashCode() < 5) { + try { + return; + } finally { + if (o != null) { + o.toString(); } + } } + } - void fooBreakLabel(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { - try { - break outer; - } finally { - if (o != null) { - o.toString(); - } - // continue after outer - } - } + void fooReturnNested(@Nullable Object o) { + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { + try { + return; + } finally { + if (o != null) { + o.toString(); + } + // go to exit, not either loop } + } } + } - void fooBreakLabel2(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - try { - inner: - while (this.hashCode() < 5) { - if (this.hashCode() < 2) { - break outer; - // go to finally - } else { - break inner; - // do not go to finally - } - } - } finally { - if (o != null) { - o.toString(); - } - // continue either at outer or after outer - } + void fooBreak(@Nullable Object o) { + while (this.hashCode() < 5) { + try { + break; + } finally { + if (o != null) { + o.toString(); } + } } + } - void fooBreakNoLabel(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - inner: - while (this.hashCode() < 5) { - try { - break; - } finally { - if (o != null) { - o.toString(); - } - // continue at outer - } - } + void fooBreakLabel(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { + try { + break outer; + } finally { + if (o != null) { + o.toString(); + } + // continue after outer } + } } + } - void fooContinue(@Nullable Object o) { + void fooBreakLabel2(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + try { + inner: while (this.hashCode() < 5) { - try { - continue; - } finally { - if (o != null) { - o.toString(); - } - } + if (this.hashCode() < 2) { + break outer; + // go to finally + } else { + break inner; + // do not go to finally + } + } + } finally { + if (o != null) { + o.toString(); } + // continue either at outer or after outer + } } + } - void fooContinueLabel(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { - try { - continue outer; - } finally { - if (o != null) { - o.toString(); - } - } - } + void fooBreakNoLabel(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + inner: + while (this.hashCode() < 5) { + try { + break; + } finally { + if (o != null) { + o.toString(); + } + // continue at outer } + } } + } - void fooSwitch(@Nullable Object o) { - switch (this.hashCode()) { - case 1: - try { - break; - } finally { - if (o != null) { - o.toString(); - } - } - default: + void fooContinue(@Nullable Object o) { + while (this.hashCode() < 5) { + try { + continue; + } finally { + if (o != null) { + o.toString(); } + } } + } - // A few tests to make sure also return with expression works. - - int barReturn(@Nullable Object o) { + void fooContinueLabel(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { try { - return 5; + continue outer; } finally { - if (o != null) { - o.toString(); - } + if (o != null) { + o.toString(); + } } + } } + } - int barReturnInFinally(@Nullable Object o) { + void fooSwitch(@Nullable Object o) { + switch (this.hashCode()) { + case 1: try { - return 5; + break; } finally { - if (o != null) { - o.toString(); - } - return 10; + if (o != null) { + o.toString(); + } } + default: } + } - int barReturnNested(@Nullable Object o) { - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { - try { - return 5; - } finally { - if (o != null) { - o.toString(); - } - // goes to return 5, not either loop! - } - } - } - return 10; + // A few tests to make sure also return with expression works. + + int barReturn(@Nullable Object o) { + try { + return 5; + } finally { + if (o != null) { + o.toString(); + } } + } - @FunctionalInterface - interface NullableParamFunction { - String takeVal(@Nullable Object x); + int barReturnInFinally(@Nullable Object o) { + try { + return 5; + } finally { + if (o != null) { + o.toString(); + } + return 10; } + } - void testLambda() { - NullableParamFunction n1 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); + int barReturnNested(@Nullable Object o) { + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { try { - NullableParamFunction n2 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); + return 5; } finally { - NullableParamFunction n3 = (x) -> (x == null) ? "null" : x.toString(); + if (o != null) { + o.toString(); + } + // goes to return 5, not either loop! } + } } + return 10; + } - boolean nestedCFGConstructionTest(@Nullable Object o) { - boolean result = true; - java.io.BufferedWriter out = null; - try { - try { - } finally { - out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); - } - if (o != null) { - o.toString(); - out.write(' '); - } - } catch (Exception e) { - } finally { - } - return result; + @FunctionalInterface + interface NullableParamFunction { + String takeVal(@Nullable Object x); + } + + void testLambda() { + NullableParamFunction n1 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); + try { + NullableParamFunction n2 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); + } finally { + NullableParamFunction n3 = (x) -> (x == null) ? "null" : x.toString(); } + } - void nestedTryFinally() { - try { - try { - } finally { - } - } finally { - } + boolean nestedCFGConstructionTest(@Nullable Object o) { + boolean result = true; + java.io.BufferedWriter out = null; + try { + try { + } finally { + out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); + } + if (o != null) { + o.toString(); + out.write(' '); + } + } catch (Exception e) { + } finally { } + return result; + } - boolean nestedCFGConstructionTest2() throws java.io.IOException { - java.io.BufferedWriter out = - new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); - try { - try { - return true; - } finally { - } - } finally { - out.write(' '); - out.close(); - } + void nestedTryFinally() { + try { + try { + } finally { + } + } finally { } + } - void nestedTryFinally2(java.io.BufferedWriter out) throws java.io.IOException { - try { - try { - return; - } finally { - } - } finally { - out.write(' '); - out.close(); - } + boolean nestedCFGConstructionTest2() throws java.io.IOException { + java.io.BufferedWriter out = + new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); + try { + try { + return true; + } finally { + } + } finally { + out.write(' '); + out.close(); + } + } + + void nestedTryFinally2(java.io.BufferedWriter out) throws java.io.IOException { + try { + try { + return; + } finally { + } + } finally { + out.write(' '); + out.close(); } + } } diff --git a/checker/tests/nullness/Issue1847.java b/checker/tests/nullness/Issue1847.java index f4ff9f85520..50e2b5acc33 100644 --- a/checker/tests/nullness/Issue1847.java +++ b/checker/tests/nullness/Issue1847.java @@ -1,32 +1,30 @@ -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.KeyFor; public class Issue1847 { - final Map map = new HashMap<>(); + final Map map = new HashMap<>(); - public void test() { - // Should give null error here: - // :: error: (dereference.of.nullable) - withLookup((String myVar) -> map.get(myVar).length()); - for (Iterator> iterator = - map.entrySet().iterator(); - iterator.hasNext(); ) { - Map.Entry<@KeyFor("map") String, String> entry = iterator.next(); - // Problem is that myVar gets inferred as @KeyFor("map") here, - // and this variable is not distinguished from the lambda variables of the same name, - // even though their scopes do not overlap and they are different variables. - // Change this variable name to myVar2 and you will see the null errors on the lambdas: - String myVar = entry.getKey(); - } - - // Should also give null error here: - // :: error: (dereference.of.nullable) - withLookup(myVar -> map.get(myVar).length()); + public void test() { + // Should give null error here: + // :: error: (dereference.of.nullable) + withLookup((String myVar) -> map.get(myVar).length()); + for (Iterator> iterator = map.entrySet().iterator(); + iterator.hasNext(); ) { + Map.Entry<@KeyFor("map") String, String> entry = iterator.next(); + // Problem is that myVar gets inferred as @KeyFor("map") here, + // and this variable is not distinguished from the lambda variables of the same name, + // even though their scopes do not overlap and they are different variables. + // Change this variable name to myVar2 and you will see the null errors on the lambdas: + String myVar = entry.getKey(); } - public void withLookup(Function getFromMap) {} + // Should also give null error here: + // :: error: (dereference.of.nullable) + withLookup(myVar -> map.get(myVar).length()); + } + + public void withLookup(Function getFromMap) {} } diff --git a/checker/tests/nullness/Issue1847B.java b/checker/tests/nullness/Issue1847B.java index 5366b3e7277..ca198cab16e 100644 --- a/checker/tests/nullness/Issue1847B.java +++ b/checker/tests/nullness/Issue1847B.java @@ -1,20 +1,19 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1847B { - public void test1() { - // :: error: (dereference.of.nullable) - Function<@Nullable String, String> f1 = (@Nullable String myVar) -> myVar.toString(); - { - String myVar = "hello"; - } - // :: error: (dereference.of.nullable) - Function<@Nullable String, String> f2 = (@Nullable String myVar) -> myVar.toString(); + public void test1() { + // :: error: (dereference.of.nullable) + Function<@Nullable String, String> f1 = (@Nullable String myVar) -> myVar.toString(); + { + String myVar = "hello"; } + // :: error: (dereference.of.nullable) + Function<@Nullable String, String> f2 = (@Nullable String myVar) -> myVar.toString(); + } - public void test2() { - // :: error: (dereference.of.nullable) - Function f1 = (@Nullable String myVar) -> myVar.toString(); - } + public void test2() { + // :: error: (dereference.of.nullable) + Function f1 = (@Nullable String myVar) -> myVar.toString(); + } } diff --git a/checker/tests/nullness/Issue1922.java b/checker/tests/nullness/Issue1922.java index 097d187b074..614daad7faf 100644 --- a/checker/tests/nullness/Issue1922.java +++ b/checker/tests/nullness/Issue1922.java @@ -1,33 +1,32 @@ -import org.checkerframework.checker.nullness.qual.KeyFor; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.KeyFor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1922 { - // A method to find a K in the collection and return it, or return null. - public static @Nullable K findKey(Collection<@NonNull K> keys, Object target) { - for (K key : keys) { - if (target.equals(key)) { - return key; - } - } - return null; + // A method to find a K in the collection and return it, or return null. + public static @Nullable K findKey(Collection<@NonNull K> keys, Object target) { + for (K key : keys) { + if (target.equals(key)) { + return key; + } } + return null; + } - // Find a key in a map and return String version of its value. - public static String findKeyAndFetchString(Map someMap) { - // :: error: (type.argument.type.incompatible) - @Nullable @KeyFor("someMap") String myKey = Issue1922.<@KeyFor("someMap") String>findKey(someMap.keySet(), "Foo"); + // Find a key in a map and return String version of its value. + public static String findKeyAndFetchString(Map someMap) { + // :: error: (type.argument.type.incompatible) + @Nullable @KeyFor("someMap") String myKey = Issue1922.<@KeyFor("someMap") String>findKey(someMap.keySet(), "Foo"); - // :: error: (argument.type.incompatible) - Object value = someMap.get(myKey); - return value.toString(); - } + // :: error: (argument.type.incompatible) + Object value = someMap.get(myKey); + return value.toString(); + } - public static void main(String[] args) { - findKeyAndFetchString(new HashMap<>()); - } + public static void main(String[] args) { + findKeyAndFetchString(new HashMap<>()); + } } diff --git a/checker/tests/nullness/Issue1949.java b/checker/tests/nullness/Issue1949.java index 6de4e8618d9..0bd3216639b 100644 --- a/checker/tests/nullness/Issue1949.java +++ b/checker/tests/nullness/Issue1949.java @@ -1,22 +1,21 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class Issue1949 { - public interface Base {} + public interface Base {} - public interface Child extends Base<@Nullable R> {} + public interface Child extends Base<@Nullable R> {} - public abstract static class BaseClass implements Child { - abstract List> foo(); - } + public abstract static class BaseClass implements Child { + abstract List> foo(); + } - public static class ChildClass extends BaseClass { + public static class ChildClass extends BaseClass { - @Override - public List> foo() { - return new ArrayList<>(); - } + @Override + public List> foo() { + return new ArrayList<>(); } + } } diff --git a/checker/tests/nullness/Issue1981.java b/checker/tests/nullness/Issue1981.java index 98dd8b92426..7328ba9c8ff 100644 --- a/checker/tests/nullness/Issue1981.java +++ b/checker/tests/nullness/Issue1981.java @@ -5,17 +5,17 @@ public class Issue1981 { - void test(List ids) { - for (List l : func2(func1(ids))) {} - } + void test(List ids) { + for (List l : func2(func1(ids))) {} + } - static > List func1(Iterable elements) { - // :: error: (return.type.incompatible) - return null; - } + static > List func1(Iterable elements) { + // :: error: (return.type.incompatible) + return null; + } - static List> func2(List list) { - // :: error: (return.type.incompatible) - return null; - } + static List> func2(List list) { + // :: error: (return.type.incompatible) + return null; + } } diff --git a/checker/tests/nullness/Issue1983.java b/checker/tests/nullness/Issue1983.java index 80ea4fcad59..038cccb292f 100644 --- a/checker/tests/nullness/Issue1983.java +++ b/checker/tests/nullness/Issue1983.java @@ -1,40 +1,39 @@ // Test case for Issue 1983: // https://github.com/typetools/checker-framework/issues/1983 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1983 { - @SuppressWarnings("initialization.field.uninitialized") - Converter converter; - - void test(List params) { - func1(transform(params, p -> of(converter.as((String) p[0])))); - } - - static class Converter { + @SuppressWarnings("initialization.field.uninitialized") + Converter converter; - @SuppressWarnings("nullness") - public Converter as(@Nullable T value) { - return null; - } - } - - @SuppressWarnings("nullness") - static List of(T t) { - return null; - } + void test(List params) { + func1(transform(params, p -> of(converter.as((String) p[0])))); + } - @SuppressWarnings("nullness") - V func1(List>> bulkParameterValues) { - return null; - } + static class Converter { @SuppressWarnings("nullness") - static List transform(List fromList, Function function) { - return null; + public Converter as(@Nullable T value) { + return null; } + } + + @SuppressWarnings("nullness") + static List of(T t) { + return null; + } + + @SuppressWarnings("nullness") + V func1(List>> bulkParameterValues) { + return null; + } + + @SuppressWarnings("nullness") + static List transform(List fromList, Function function) { + return null; + } } diff --git a/checker/tests/nullness/Issue2013.java b/checker/tests/nullness/Issue2013.java index 316932224d2..791c36151b5 100644 --- a/checker/tests/nullness/Issue2013.java +++ b/checker/tests/nullness/Issue2013.java @@ -5,104 +5,104 @@ // @skip-test public class Issue2013 { - static class Super { - private @Nullable String name = null; - - @EnsuresNonNull("name()") - // :: error: (contracts.postcondition.not.satisfied) - void ensureNameNonNull() { - name = "name"; - } - - @RequiresNonNull("name()") - void requiresNameNonNull() { - name().equals("name"); - } - - @Pure - @Nullable String name() { - return name; - } + static class Super { + private @Nullable String name = null; + + @EnsuresNonNull("name()") + // :: error: (contracts.postcondition.not.satisfied) + void ensureNameNonNull() { + name = "name"; + } + + @RequiresNonNull("name()") + void requiresNameNonNull() { + name().equals("name"); } - static class Sub extends Super { - @Nullable String subname = null; - - @Override - // :: error: (contracts.postcondition.not.satisfied) - void ensureNameNonNull() { - super.ensureNameNonNull(); - subname = "Sub"; - } - - public static boolean flag; - - @Override - @RequiresNonNull("name()") - void requiresNameNonNull() { - if (flag) { - name().toString(); - } else { - super.requiresNameNonNull(); - } - } - - @Override - @Nullable String name() { - return subname; - } - - void use() { - if (super.name() != null) { - // :: error: (contracts.precondition.not.satisfied) - requiresNameNonNull(); - } - - if (this.name() != null) { - requiresNameNonNull(); - } - - if (super.name() != null) { - // :: error: (contracts.precondition.not.satisfied) - super.requiresNameNonNull(); - } - - if (this.name() != null) { - super.requiresNameNonNull(); - } - - super.ensureNameNonNull(); - // :: error: (contracts.precondition.not.satisfied) - requiresNameNonNull(); - - super.ensureNameNonNull(); - // :: error: (contracts.precondition.not.satisfied) - super.requiresNameNonNull(); - - ensureNameNonNull(); - super.requiresNameNonNull(); - - ensureNameNonNull(); - requiresNameNonNull(); - } + @Pure + @Nullable String name() { + return name; + } + } + + static class Sub extends Super { + @Nullable String subname = null; + + @Override + // :: error: (contracts.postcondition.not.satisfied) + void ensureNameNonNull() { + super.ensureNameNonNull(); + subname = "Sub"; } - void method(Super superObj) { - if (superObj.name() != null) { - superObj.requiresNameNonNull(); - } + public static boolean flag; - superObj.ensureNameNonNull(); - superObj.requiresNameNonNull(); + @Override + @RequiresNonNull("name()") + void requiresNameNonNull() { + if (flag) { + name().toString(); + } else { + super.requiresNameNonNull(); + } } - void method2(Sub subObj) { - if (subObj.name() != null) { - subObj.requiresNameNonNull(); - } + @Override + @Nullable String name() { + return subname; + } + + void use() { + if (super.name() != null) { + // :: error: (contracts.precondition.not.satisfied) + requiresNameNonNull(); + } + + if (this.name() != null) { + requiresNameNonNull(); + } + + if (super.name() != null) { + // :: error: (contracts.precondition.not.satisfied) + super.requiresNameNonNull(); + } + + if (this.name() != null) { + super.requiresNameNonNull(); + } + + super.ensureNameNonNull(); + // :: error: (contracts.precondition.not.satisfied) + requiresNameNonNull(); + + super.ensureNameNonNull(); + // :: error: (contracts.precondition.not.satisfied) + super.requiresNameNonNull(); + + ensureNameNonNull(); + super.requiresNameNonNull(); + + ensureNameNonNull(); + requiresNameNonNull(); + } + } + + void method(Super superObj) { + if (superObj.name() != null) { + superObj.requiresNameNonNull(); + } + + superObj.ensureNameNonNull(); + superObj.requiresNameNonNull(); + } + + void method2(Sub subObj) { + if (subObj.name() != null) { + subObj.requiresNameNonNull(); + } - if (subObj.name() != null) { - subObj.requiresNameNonNull(); - } + if (subObj.name() != null) { + subObj.requiresNameNonNull(); } + } } diff --git a/checker/tests/nullness/Issue2031.java b/checker/tests/nullness/Issue2031.java index 81c68709e69..1ea36ed0c6b 100644 --- a/checker/tests/nullness/Issue2031.java +++ b/checker/tests/nullness/Issue2031.java @@ -1,43 +1,42 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue2031 { - public interface InterfaceA {} - - public interface InterfaceB {} - - abstract static class OperatorSection & InterfaceB> { - C makeExpression(Map expressions) { - @Nullable C e = expressions.get(""); - if (e != null) { - return e; - } else { - throw new RuntimeException(""); - } - } + public interface InterfaceA {} + + public interface InterfaceB {} + + abstract static class OperatorSection & InterfaceB> { + C makeExpression(Map expressions) { + @Nullable C e = expressions.get(""); + if (e != null) { + return e; + } else { + throw new RuntimeException(""); + } } + } - static class RecursiveTypes { - public interface A {} + static class RecursiveTypes { + public interface A {} - public interface B {} + public interface B {} - abstract static class OperatorSection & B> { - abstract EXPRESSION makeExpression(Map expressions); - } + abstract static class OperatorSection & B> { + abstract EXPRESSION makeExpression(Map expressions); + } - static class BinaryOperatorSection & B> - extends OperatorSection { - @Override - EXPRESSION makeExpression(Map expressions) { - @Nullable EXPRESSION e = expressions.get(""); - if (e != null) { - return e; - } else { - throw new RuntimeException(""); - } - } + static class BinaryOperatorSection & B> + extends OperatorSection { + @Override + EXPRESSION makeExpression(Map expressions) { + @Nullable EXPRESSION e = expressions.get(""); + if (e != null) { + return e; + } else { + throw new RuntimeException(""); } + } } + } } diff --git a/checker/tests/nullness/Issue2048.java b/checker/tests/nullness/Issue2048.java index e99e61623e3..ad9d477639d 100644 --- a/checker/tests/nullness/Issue2048.java +++ b/checker/tests/nullness/Issue2048.java @@ -8,24 +8,24 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue2048 { - interface Foo {} + interface Foo {} - static class Fooer {} + static class Fooer {} - class UseNbl { - void foo(Fooer fooer) {} - } + class UseNbl { + void foo(Fooer fooer) {} + } - // :: error: (type.argument.type.incompatible) - Fooer<@Nullable Foo> nblFooer = new Fooer<>(); - Fooer<@NonNull Foo> nnFooer = new Fooer<>(); + // :: error: (type.argument.type.incompatible) + Fooer<@Nullable Foo> nblFooer = new Fooer<>(); + Fooer<@NonNull Foo> nnFooer = new Fooer<>(); - void use(UseNbl<@Nullable Foo> useNbl) { - useNbl.foo(nblFooer); - useNbl.foo(nnFooer); - } + void use(UseNbl<@Nullable Foo> useNbl) { + useNbl.foo(nblFooer); + useNbl.foo(nnFooer); + } - class UseNN { - void foo(Fooer fooer) {} - } + class UseNN { + void foo(Fooer fooer) {} + } } diff --git a/checker/tests/nullness/Issue2052.java b/checker/tests/nullness/Issue2052.java index 365f5624027..01b01cb9a38 100644 --- a/checker/tests/nullness/Issue2052.java +++ b/checker/tests/nullness/Issue2052.java @@ -1,18 +1,18 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class Issue2052 { - public static class ParentW { - protected final String field; + public static class ParentW { + protected final String field; - public ParentW() { - // Initializing "field" at the declaration, did not trigger the bug. - field = ""; - } + public ParentW() { + // Initializing "field" at the declaration, did not trigger the bug. + field = ""; } + } - public static class ChildW extends ParentW { - public String getField(@UnknownInitialization(ParentW.class) ChildW this) { - return this.field; - } + public static class ChildW extends ParentW { + public String getField(@UnknownInitialization(ParentW.class) ChildW this) { + return this.field; } + } } diff --git a/checker/tests/nullness/Issue2171.java b/checker/tests/nullness/Issue2171.java index bf93a890dab..481b013f146 100644 --- a/checker/tests/nullness/Issue2171.java +++ b/checker/tests/nullness/Issue2171.java @@ -1,37 +1,35 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class Issue2171 { - static void varArgsMethod(@PolyNull Object... args) {} + static void varArgsMethod(@PolyNull Object... args) {} - static void callToVarArgsObject( - @PolyNull Object pn, @NonNull Object nn, @Nullable Object nble) { - varArgsMethod(nble, nble); - varArgsMethod(nble, nn); - varArgsMethod(nble, pn); - varArgsMethod(nn, nble); - varArgsMethod(nn, nn); - varArgsMethod(nn, pn); - varArgsMethod(pn, nble); - varArgsMethod(pn, nn); - varArgsMethod(pn, pn); - } + static void callToVarArgsObject(@PolyNull Object pn, @NonNull Object nn, @Nullable Object nble) { + varArgsMethod(nble, nble); + varArgsMethod(nble, nn); + varArgsMethod(nble, pn); + varArgsMethod(nn, nble); + varArgsMethod(nn, nn); + varArgsMethod(nn, pn); + varArgsMethod(pn, nble); + varArgsMethod(pn, nn); + varArgsMethod(pn, pn); + } - @SuppressWarnings("unchecked") - static void genVarArgsMethod(List... args) {} + @SuppressWarnings("unchecked") + static void genVarArgsMethod(List... args) {} - @SuppressWarnings("unchecked") - static void genCallToVarArgsObject( - List<@PolyNull Object> pn, List<@NonNull Object> nn, List<@Nullable Object> nble) { - genVarArgsMethod(nble, nble); - genVarArgsMethod(nble, nn); - genVarArgsMethod(nble, pn); - genVarArgsMethod(nn, nble); - genVarArgsMethod(nn, nn); - genVarArgsMethod(nn, pn); - genVarArgsMethod(pn, nble); - genVarArgsMethod(pn, nn); - genVarArgsMethod(pn, pn); - } + @SuppressWarnings("unchecked") + static void genCallToVarArgsObject( + List<@PolyNull Object> pn, List<@NonNull Object> nn, List<@Nullable Object> nble) { + genVarArgsMethod(nble, nble); + genVarArgsMethod(nble, nn); + genVarArgsMethod(nble, pn); + genVarArgsMethod(nn, nble); + genVarArgsMethod(nn, nn); + genVarArgsMethod(nn, pn); + genVarArgsMethod(pn, nble); + genVarArgsMethod(pn, nn); + genVarArgsMethod(pn, pn); + } } diff --git a/checker/tests/nullness/Issue2247.java b/checker/tests/nullness/Issue2247.java index 4bc729457cc..872889f4958 100644 --- a/checker/tests/nullness/Issue2247.java +++ b/checker/tests/nullness/Issue2247.java @@ -1,42 +1,41 @@ // This is a test case for issue 2247: // https://github.com/typetools/checker-framework/issues/2247 -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue2247 { - @NonNull static class DeclaredClass {} + @NonNull static class DeclaredClass {} - class ValidUseType { + class ValidUseType { - // :: error: (type.invalid.annotations.on.use) - void test1(@Nullable DeclaredClass object) {} + // :: error: (type.invalid.annotations.on.use) + void test1(@Nullable DeclaredClass object) {} - // :: error: (type.invalid.annotations.on.use) - @Nullable DeclaredClass test2() { - return null; - } + // :: error: (type.invalid.annotations.on.use) + @Nullable DeclaredClass test2() { + return null; + } - // :: error: (type.invalid.annotations.on.use) - void test3(List<@Nullable DeclaredClass> param) { - @Nullable DeclaredClass object = null; - // :: error: (type.invalid.annotations.on.use) - @Nullable DeclaredClass[] array = null; - } + // :: error: (type.invalid.annotations.on.use) + void test3(List<@Nullable DeclaredClass> param) { + @Nullable DeclaredClass object = null; + // :: error: (type.invalid.annotations.on.use) + @Nullable DeclaredClass[] array = null; + } - // :: error: (type.invalid.annotations.on.use) - void test4(@NonNull T t) {} + // :: error: (type.invalid.annotations.on.use) + void test4(@NonNull T t) {} - void test5(Map map) { - @Nullable DeclaredClass value = map.get("somekey"); - System.out.println(value); - if (value != null) { - @NonNull DeclaredClass nonnull = value; - } - } + void test5(Map map) { + @Nullable DeclaredClass value = map.get("somekey"); + System.out.println(value); + if (value != null) { + @NonNull DeclaredClass nonnull = value; + } } + } } diff --git a/checker/tests/nullness/Issue2407.java b/checker/tests/nullness/Issue2407.java index 1510ccbd2f1..53bc5bde174 100644 --- a/checker/tests/nullness/Issue2407.java +++ b/checker/tests/nullness/Issue2407.java @@ -4,20 +4,20 @@ public class Issue2407 { - @RequiresNonNull("#1") - void setMessage(String message) {} + @RequiresNonNull("#1") + void setMessage(String message) {} - @EnsuresNonNull("1") - // :: error: (flowexpr.parse.error) - void method() {} + @EnsuresNonNull("1") + // :: error: (flowexpr.parse.error) + void method() {} - @EnsuresNonNullIf(expression = "1", result = true) - // :: error: (flowexpr.parse.error) - void method2() {} + @EnsuresNonNullIf(expression = "1", result = true) + // :: error: (flowexpr.parse.error) + void method2() {} - void main() { - Issue2407 object = new Issue2407(); - // :: error: (contracts.precondition.not.satisfied) - object.setMessage(new Object() + "bar"); - } + void main() { + Issue2407 object = new Issue2407(); + // :: error: (contracts.precondition.not.satisfied) + object.setMessage(new Object() + "bar"); + } } diff --git a/checker/tests/nullness/Issue2432.java b/checker/tests/nullness/Issue2432.java index e35a633e07e..74512737241 100644 --- a/checker/tests/nullness/Issue2432.java +++ b/checker/tests/nullness/Issue2432.java @@ -1,120 +1,119 @@ // Test case for issue 2432: // https://github.com/typetools/checker-framework/issues/2432 +import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; -import java.util.List; - public class Issue2432 { - void jdkAnnotation(List<@PolyNull Object> nl, @Nullable Object no, @PolyNull Object po) { - // :: error: (argument.type.incompatible) - nl.add(null); - // :: error: (argument.type.incompatible) - nl.add(no); - // OK - nl.add(po); - } - - // receiver's poly annotations in declaration are different from the ones in invocation - void polyReceiverType( - TypeArgClass<@PolyNull Object> tc, - @NonNull Object nno, - @Nullable Object no, - @PolyNull Object po) { - // :: error: (argument.type.incompatible) - Object object = tc.echo(no); - // :: error: (assignment.type.incompatible) - nno = tc.echo(po); - // No error. Return value remains @PolyNull. - // Note po's @PolyNull is unsubstitutable (from parameter but not from declaration) - po = tc.echo(po); - } - - // the following two methods tests pesudo assignment of arguments with poly annotation - void polyAssignment(TypeArgClass<@NonNull Object> nnc, @PolyNull Object po) { - // :: error: (argument.type.incompatible) - nnc.echo(po); - } - - void polyAssignment2(TypeArgClass<@Nullable Object> nc, @PolyNull Object po) { - // No error - nc.echo(po); - // :: error: (assignment.type.incompatible) - po = nc.echo(po); + void jdkAnnotation(List<@PolyNull Object> nl, @Nullable Object no, @PolyNull Object po) { + // :: error: (argument.type.incompatible) + nl.add(null); + // :: error: (argument.type.incompatible) + nl.add(no); + // OK + nl.add(po); + } + + // receiver's poly annotations in declaration are different from the ones in invocation + void polyReceiverType( + TypeArgClass<@PolyNull Object> tc, + @NonNull Object nno, + @Nullable Object no, + @PolyNull Object po) { + // :: error: (argument.type.incompatible) + Object object = tc.echo(no); + // :: error: (assignment.type.incompatible) + nno = tc.echo(po); + // No error. Return value remains @PolyNull. + // Note po's @PolyNull is unsubstitutable (from parameter but not from declaration) + po = tc.echo(po); + } + + // the following two methods tests pesudo assignment of arguments with poly annotation + void polyAssignment(TypeArgClass<@NonNull Object> nnc, @PolyNull Object po) { + // :: error: (argument.type.incompatible) + nnc.echo(po); + } + + void polyAssignment2(TypeArgClass<@Nullable Object> nc, @PolyNull Object po) { + // No error + nc.echo(po); + // :: error: (assignment.type.incompatible) + po = nc.echo(po); + } + + // a "foo function" with 2 poly annotations, where one of them appears in type argument + // purpose: test invocation without using explicit receiver + @PolyNull Object foo2PolyTypeArg( + TypeArgClass<@PolyNull Object> pc, @PolyNull Object po, @NonNull Object nno) { + return pc.add(nno, po); + } + + // lub tests without receiver + // lub combination: (@Nullable, @Nullable) = @Nullable + void lubWithTypeArgNoReceiver1(TypeArgClass<@Nullable Object> nc, @Nullable Object no) { + @NonNull Object nno = new Object(); + // No error + foo2PolyTypeArg(nc, no, nno); + } + + // lub combination: (@NonNull, @NonNull) = @NonNull + void lubWithTypeArgNoReceiver2(TypeArgClass<@NonNull Object> nnc, @NonNull Object nno) { + // No error + foo2PolyTypeArg(nnc, nno, nno); + } + + // lub combination: (@Nullable, @NonNull) = @Nullable + void lubWithTypeArgNoReceiver3(TypeArgClass<@Nullable Object> nc, @NonNull Object nno) { + // No error + foo2PolyTypeArg(nc, nno, nno); + } + + // lub combination: (@NonNull, @Nullable) = @Nullable + void lubWithTypeArgNoReceiver4(TypeArgClass<@NonNull Object> nnc, @Nullable Object no) { + // :: error: (argument.type.incompatible) + foo2PolyTypeArg(nnc, no, new Object()); + } + + // lub test with receiver + // T dummy in tripleAdd is to ensure poly annotations from declaration is handled separately + void lubWithReceiver( + TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { + // :: error: (argument.type.incompatible) + pc.tripleAdd(no, nno, no); + // No error + pc.tripleAdd(no, nno, nno); + } + + // ensure poly annotations from declaration is handled separately from poly from other context + void declarationPolyInParameter( + TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { + // No error + pc.echo(nno, no); + + // the invocation is valid, while the assignment is not + // :: error: (assignment.type.incompatible) + @NonNull Object nonnull = pc.echo(nno, no); + } + + private class TypeArgClass { + @PolyNull Object add(@PolyNull Object obj, T dummy) { + return obj; } - // a "foo function" with 2 poly annotations, where one of them appears in type argument - // purpose: test invocation without using explicit receiver - @PolyNull Object foo2PolyTypeArg( - TypeArgClass<@PolyNull Object> pc, @PolyNull Object po, @NonNull Object nno) { - return pc.add(nno, po); + @PolyNull Object tripleAdd(@PolyNull Object o1, @PolyNull Object o2, T dummy) { + return o1; } - // lub tests without receiver - // lub combination: (@Nullable, @Nullable) = @Nullable - void lubWithTypeArgNoReceiver1(TypeArgClass<@Nullable Object> nc, @Nullable Object no) { - @NonNull Object nno = new Object(); - // No error - foo2PolyTypeArg(nc, no, nno); + T echo(T obj) { + return obj; } - // lub combination: (@NonNull, @NonNull) = @NonNull - void lubWithTypeArgNoReceiver2(TypeArgClass<@NonNull Object> nnc, @NonNull Object nno) { - // No error - foo2PolyTypeArg(nnc, nno, nno); - } - - // lub combination: (@Nullable, @NonNull) = @Nullable - void lubWithTypeArgNoReceiver3(TypeArgClass<@Nullable Object> nc, @NonNull Object nno) { - // No error - foo2PolyTypeArg(nc, nno, nno); - } - - // lub combination: (@NonNull, @Nullable) = @Nullable - void lubWithTypeArgNoReceiver4(TypeArgClass<@NonNull Object> nnc, @Nullable Object no) { - // :: error: (argument.type.incompatible) - foo2PolyTypeArg(nnc, no, new Object()); - } - - // lub test with receiver - // T dummy in tripleAdd is to ensure poly annotations from declaration is handled separately - void lubWithReceiver( - TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { - // :: error: (argument.type.incompatible) - pc.tripleAdd(no, nno, no); - // No error - pc.tripleAdd(no, nno, nno); - } - - // ensure poly annotations from declaration is handled separately from poly from other context - void declarationPolyInParameter( - TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { - // No error - pc.echo(nno, no); - - // the invocation is valid, while the assignment is not - // :: error: (assignment.type.incompatible) - @NonNull Object nonnull = pc.echo(nno, no); - } - - private class TypeArgClass { - @PolyNull Object add(@PolyNull Object obj, T dummy) { - return obj; - } - - @PolyNull Object tripleAdd(@PolyNull Object o1, @PolyNull Object o2, T dummy) { - return o1; - } - - T echo(T obj) { - return obj; - } - - T echo(T obj, @PolyNull Object dummy) { - return obj; - } + T echo(T obj, @PolyNull Object dummy) { + return obj; } + } } diff --git a/checker/tests/nullness/Issue2432b.java b/checker/tests/nullness/Issue2432b.java index 804b32eeba0..de243108199 100644 --- a/checker/tests/nullness/Issue2432b.java +++ b/checker/tests/nullness/Issue2432b.java @@ -1,43 +1,42 @@ // Ensure correct handling of type parameters and arrays. // https://github.com/typetools/checker-framework/issues/2432 -import org.checkerframework.checker.nullness.qual.PolyNull; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.PolyNull; public class Issue2432b { - void objectAsTypeArg() { - List objs = new ArrayList<>(); - // no error - Object[] objarray = objs.toArray(); + void objectAsTypeArg() { + List objs = new ArrayList<>(); + // no error + Object[] objarray = objs.toArray(); + } + + void myClassAsTypeArg() { + MyClass objs = new MyClass<>(); + Object[] objarray = objs.toArray(); + // no error + Object[] objarray2 = objs.toArrayPoly(); + } + + void stringAsTypeArg() { + List strs = new ArrayList<>(); + Object[] strarray = strs.toArray(); + } + + void listAsTypeArg() { + List lists = new ArrayList<>(); + Object[] listarray = lists.toArray(); + } + + private static class MyClass { + + Object[] toArray() { + return new Object[] {new Object()}; } - void myClassAsTypeArg() { - MyClass objs = new MyClass<>(); - Object[] objarray = objs.toArray(); - // no error - Object[] objarray2 = objs.toArrayPoly(); - } - - void stringAsTypeArg() { - List strs = new ArrayList<>(); - Object[] strarray = strs.toArray(); - } - - void listAsTypeArg() { - List lists = new ArrayList<>(); - Object[] listarray = lists.toArray(); - } - - private static class MyClass { - - Object[] toArray() { - return new Object[] {new Object()}; - } - - @PolyNull Object[] toArrayPoly(MyClass<@PolyNull MyTypeParam> this) { - return new Object[] {new Object()}; - } + @PolyNull Object[] toArrayPoly(MyClass<@PolyNull MyTypeParam> this) { + return new Object[] {new Object()}; } + } } diff --git a/checker/tests/nullness/Issue2470.java b/checker/tests/nullness/Issue2470.java index d5017e6d4ca..96d90806742 100644 --- a/checker/tests/nullness/Issue2470.java +++ b/checker/tests/nullness/Issue2470.java @@ -3,60 +3,60 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Issue2470 { - static class Example { - @MonotonicNonNull String s; - - public Example() {} - - @EnsuresNonNull("this.s") - public Example setS(String s1) { - this.s = s1; - return this; - } - - // TODO: Support "return" in Java Expression syntax. - // @EnsuresNonNull("return.s") - @EnsuresNonNull("this.s") - public Example setS2(String s1) { - this.s = s1; - return this; - } - - @RequiresNonNull("this.s") - public void print() { - System.out.println(this.s.toString()); - } - } - - static void buggy() { - new Example() - // :: error: (contracts.precondition.not.satisfied) - .print(); - } + static class Example { + @MonotonicNonNull String s; - static void ok() { - Example e = new Example(); - e.setS("test"); - e.print(); - } + public Example() {} - static void buggy2() { - new Example() - .setS("test") - // :: error:(contracts.precondition.not.satisfied) - .print(); + @EnsuresNonNull("this.s") + public Example setS(String s1) { + this.s = s1; + return this; } - // TODO: These should be legal, once "return" is supported in Java Expression syntax. - // of a method. - /* - static void ok3() { - Example e = new Example().setS2("test"); - e.print(); + // TODO: Support "return" in Java Expression syntax. + // @EnsuresNonNull("return.s") + @EnsuresNonNull("this.s") + public Example setS2(String s1) { + this.s = s1; + return this; } - static void ok2() { - new Example().setS2("test").print(); + @RequiresNonNull("this.s") + public void print() { + System.out.println(this.s.toString()); } - */ + } + + static void buggy() { + new Example() + // :: error: (contracts.precondition.not.satisfied) + .print(); + } + + static void ok() { + Example e = new Example(); + e.setS("test"); + e.print(); + } + + static void buggy2() { + new Example() + .setS("test") + // :: error:(contracts.precondition.not.satisfied) + .print(); + } + + // TODO: These should be legal, once "return" is supported in Java Expression syntax. + // of a method. + /* + static void ok3() { + Example e = new Example().setS2("test"); + e.print(); + } + + static void ok2() { + new Example().setS2("test").print(); + } + */ } diff --git a/checker/tests/nullness/Issue2564.java b/checker/tests/nullness/Issue2564.java index 769f7442e0a..41f96aa2cde 100644 --- a/checker/tests/nullness/Issue2564.java +++ b/checker/tests/nullness/Issue2564.java @@ -1,21 +1,20 @@ -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.KeyFor; public abstract class Issue2564 { - public enum EnumType { - // :: error: (enum.declaration.type.incompatible) - @KeyFor("myMap") MY_KEY, - // :: error: (enum.declaration.type.incompatible) - @KeyFor("enumMap") ENUM_KEY; - private static final Map enumMap = new HashMap<>(); + public enum EnumType { + // :: error: (enum.declaration.type.incompatible) + @KeyFor("myMap") MY_KEY, + // :: error: (enum.declaration.type.incompatible) + @KeyFor("enumMap") ENUM_KEY; + private static final Map enumMap = new HashMap<>(); - void method() { - @KeyFor("enumMap") EnumType t = ENUM_KEY; - int x = enumMap.get(ENUM_KEY); - } + void method() { + @KeyFor("enumMap") EnumType t = ENUM_KEY; + int x = enumMap.get(ENUM_KEY); } + } - private static final Map myMap = new HashMap<>(); + private static final Map myMap = new HashMap<>(); } diff --git a/checker/tests/nullness/Issue2565.java b/checker/tests/nullness/Issue2565.java index 257ed7fb67b..64e8b554b03 100644 --- a/checker/tests/nullness/Issue2565.java +++ b/checker/tests/nullness/Issue2565.java @@ -2,10 +2,10 @@ public abstract class Issue2565 { - // Broken Case: - abstract void processErrors(List>> errors); + // Broken Case: + abstract void processErrors(List>> errors); - static class Error & Hoo> {} + static class Error & Hoo> {} - interface Hoo {} + interface Hoo {} } diff --git a/checker/tests/nullness/Issue2587.java b/checker/tests/nullness/Issue2587.java index bc1db838c01..468e036b37d 100644 --- a/checker/tests/nullness/Issue2587.java +++ b/checker/tests/nullness/Issue2587.java @@ -1,39 +1,38 @@ -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.KeyFor; @SuppressWarnings({ - "enum.declaration.type.incompatible", - "assignment.type.incompatible" + "enum.declaration.type.incompatible", + "assignment.type.incompatible" }) // These warnings are not relevant public abstract class Issue2587 { - public enum EnumType { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("myMap") MY_KEY, + public enum EnumType { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("myMap") MY_KEY, - @KeyFor("enumMap") ENUM_KEY; - private static final Map enumMap = new HashMap<>(); + @KeyFor("enumMap") ENUM_KEY; + private static final Map enumMap = new HashMap<>(); - void method() { - @KeyFor("enumMap") EnumType t = ENUM_KEY; - int x = enumMap.get(ENUM_KEY); - } + void method() { + @KeyFor("enumMap") EnumType t = ENUM_KEY; + int x = enumMap.get(ENUM_KEY); } + } - public static class Inner { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("myMap") String MY_KEY = ""; + public static class Inner { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("myMap") String MY_KEY = ""; - public static class Inner2 { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("myMap") String MY_KEY2 = ""; + public static class Inner2 { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("myMap") String MY_KEY2 = ""; - @KeyFor("innerMap") String MY_KEY3 = ""; - } - - private static final Map innerMap = new HashMap<>(); + @KeyFor("innerMap") String MY_KEY3 = ""; } - private final Map myMap = new HashMap<>(); + private static final Map innerMap = new HashMap<>(); + } + + private final Map myMap = new HashMap<>(); } diff --git a/checker/tests/nullness/Issue2619.java b/checker/tests/nullness/Issue2619.java index f72e1b177a8..c89632f73cd 100644 --- a/checker/tests/nullness/Issue2619.java +++ b/checker/tests/nullness/Issue2619.java @@ -1,52 +1,51 @@ +import java.util.HashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.dataflow.qual.Pure; -import java.util.HashMap; -import java.util.Map; - public class Issue2619 { - public Map map = new HashMap<>(); + public Map map = new HashMap<>(); - void m00(Aux aux1) { - if (aux1.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map"}) String s1 = Aux.MINIMUM_VALUE; - } + void m00(Aux aux1) { + if (aux1.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map"}) String s1 = Aux.MINIMUM_VALUE; } + } - void m01(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; - } + void m01(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; } + } - void m02(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; - } + void m02(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; } + } - void m03(Aux aux1, Aux aux2) { - if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; - } + void m03(Aux aux1, Aux aux2) { + if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; } + } - static class Aux { + static class Aux { - public Map map = new HashMap<>(); + public Map map = new HashMap<>(); - public static final String MINIMUM_VALUE = "minvalue"; + public static final String MINIMUM_VALUE = "minvalue"; - @Pure - @EnsuresKeyForIf(result = true, expression = "#1", map = "map") - public boolean hasValue(String key) { - return map.containsKey(key); - } + @Pure + @EnsuresKeyForIf(result = true, expression = "#1", map = "map") + public boolean hasValue(String key) { + return map.containsKey(key); + } - @Pure - public int getInt(@KeyFor("this.map") String key) { - return 22; - } + @Pure + public int getInt(@KeyFor("this.map") String key) { + return 22; } + } } diff --git a/checker/tests/nullness/Issue2619b.java b/checker/tests/nullness/Issue2619b.java index 8eda3e0e3e7..2ccb97aeb5b 100644 --- a/checker/tests/nullness/Issue2619b.java +++ b/checker/tests/nullness/Issue2619b.java @@ -1,56 +1,55 @@ -import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; +import org.checkerframework.checker.nullness.qual.KeyFor; public class Issue2619b { - public Map map = new HashMap<>(); - - void m01(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { - // hasValue is not side-effect-free, so the @KeyFor("aux1.map") is cleared rather than - // glb'ed. - // :: error: (assignment.type.incompatible) - @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; - } + public Map map = new HashMap<>(); + + void m01(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { + // hasValue is not side-effect-free, so the @KeyFor("aux1.map") is cleared rather than + // glb'ed. + // :: error: (assignment.type.incompatible) + @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; } + } - void m02(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor("aux2.map") String s1 = Aux.MINIMUM_VALUE; - } + void m02(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor("aux2.map") String s1 = Aux.MINIMUM_VALUE; } + } - void m03(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { - // ok because map.containsKey is side-effect-free. - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; - @KeyFor("map") String s2 = Aux.MINIMUM_VALUE; - } + void m03(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { + // ok because map.containsKey is side-effect-free. + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + @KeyFor("map") String s2 = Aux.MINIMUM_VALUE; } + } - void m04(Aux aux1, Aux aux2) { - if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { - // :: error: (assignment.type.incompatible) - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; - @KeyFor("aux1.map") String s2 = Aux.MINIMUM_VALUE; - } + void m04(Aux aux1, Aux aux2) { + if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { + // :: error: (assignment.type.incompatible) + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + @KeyFor("aux1.map") String s2 = Aux.MINIMUM_VALUE; } + } - static class Aux { + static class Aux { - public Map map = new HashMap<>(); + public Map map = new HashMap<>(); - public static String MINIMUM_VALUE = "minvalue"; + public static String MINIMUM_VALUE = "minvalue"; - @EnsuresKeyForIf(result = true, expression = "#1", map = "map") - public boolean hasValue(String key) { - return map.containsKey(key); - } + @EnsuresKeyForIf(result = true, expression = "#1", map = "map") + public boolean hasValue(String key) { + return map.containsKey(key); + } - public int getInt(@KeyFor("this.map") String key) { - return 22; - } + public int getInt(@KeyFor("this.map") String key) { + return 22; } + } } diff --git a/checker/tests/nullness/Issue266.java b/checker/tests/nullness/Issue266.java index 0b1df73bc96..bead2544396 100644 --- a/checker/tests/nullness/Issue266.java +++ b/checker/tests/nullness/Issue266.java @@ -5,19 +5,19 @@ public class Issue266 { - abstract static class Inner { - abstract String getThing(); - } + abstract static class Inner { + abstract String getThing(); + } - static @Nullable Inner method(@Nullable Object arg) { - final Object tmp = arg; - if (tmp == null) { - return null; - } - return new Inner() { - String getThing() { - return tmp.toString(); - } - }; + static @Nullable Inner method(@Nullable Object arg) { + final Object tmp = arg; + if (tmp == null) { + return null; } + return new Inner() { + String getThing() { + return tmp.toString(); + } + }; + } } diff --git a/checker/tests/nullness/Issue266a.java b/checker/tests/nullness/Issue266a.java index 9919404293c..397dbdace14 100644 --- a/checker/tests/nullness/Issue266a.java +++ b/checker/tests/nullness/Issue266a.java @@ -4,18 +4,18 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue266a { - private final Object mBar; + private final Object mBar; - public Issue266a() { - mBar = "test"; - Runnable runnable = - new Runnable() { - @Override - public void run() { - // unexpected [dereference.of.nullable] error here - mBar.toString(); - } - }; - runnable.run(); - } + public Issue266a() { + mBar = "test"; + Runnable runnable = + new Runnable() { + @Override + public void run() { + // unexpected [dereference.of.nullable] error here + mBar.toString(); + } + }; + runnable.run(); + } } diff --git a/checker/tests/nullness/Issue2721.java b/checker/tests/nullness/Issue2721.java index 089a9828127..00ce5eea296 100644 --- a/checker/tests/nullness/Issue2721.java +++ b/checker/tests/nullness/Issue2721.java @@ -4,11 +4,11 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue2721 { - void foo() { - passThrough(asList(asList(1))).get(0).get(0).intValue(); - } + void foo() { + passThrough(asList(asList(1))).get(0).get(0).intValue(); + } - List passThrough(List object) { - return object; - } + List passThrough(List object) { + return object; + } } diff --git a/checker/tests/nullness/Issue273.java b/checker/tests/nullness/Issue273.java index bfb30c619ab..ff343fdcb5a 100644 --- a/checker/tests/nullness/Issue273.java +++ b/checker/tests/nullness/Issue273.java @@ -1,27 +1,26 @@ // Test case for issue #273: // https://github.com/typetools/checker-framework/issues/273 -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class Issue273 { - public static void main(String... p) { - Map m0 = new HashMap<>(); - Map m1 = new HashMap<>(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("m0") String k = "key"; - m0.put(k, 1); + public static void main(String... p) { + Map m0 = new HashMap<>(); + Map m1 = new HashMap<>(); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("m0") String k = "key"; + m0.put(k, 1); - // :: error: (argument.type.incompatible) - getMap2(m0, m1, k).toString(); - } + // :: error: (argument.type.incompatible) + getMap2(m0, m1, k).toString(); + } - public static @NonNull Integer getMap2( - Map m1, // m1,m0 flipped - Map m0, - @KeyFor("#2") String k) { - return m0.get(k); - } + public static @NonNull Integer getMap2( + Map m1, // m1,m0 flipped + Map m0, + @KeyFor("#2") String k) { + return m0.get(k); + } } diff --git a/checker/tests/nullness/Issue2865.java b/checker/tests/nullness/Issue2865.java index faf135b850b..44dd3af351a 100644 --- a/checker/tests/nullness/Issue2865.java +++ b/checker/tests/nullness/Issue2865.java @@ -2,24 +2,24 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue2865 { - public class C { - public C(T a) {} + public class C { + public C(T a) {} - public void f(T a) { - new C(a); - // :: error: (argument.type.incompatible) - new C(null); - } + public void f(T a) { + new C(a); + // :: error: (argument.type.incompatible) + new C(null); } + } - void test(Issue2865<@NonNull String> s) { - // :: error: (argument.type.incompatible) - s.new C(null); - s.new C(""); - } + void test(Issue2865<@NonNull String> s) { + // :: error: (argument.type.incompatible) + s.new C(null); + s.new C(""); + } - void test2(Issue2865<@Nullable String> s) { - s.new C(null); - s.new C(""); - } + void test2(Issue2865<@Nullable String> s) { + s.new C(null); + s.new C(""); + } } diff --git a/checker/tests/nullness/Issue2888.java b/checker/tests/nullness/Issue2888.java index 6713ae74329..18572568d7b 100644 --- a/checker/tests/nullness/Issue2888.java +++ b/checker/tests/nullness/Issue2888.java @@ -1,33 +1,33 @@ import com.sun.istack.internal.Nullable; public class Issue2888 { - @Nullable Object[] noa; + @Nullable Object[] noa; - void foo() { - noa = null; - // :: error: (accessing.nullable) :: error: (assignment.type.incompatible) - noa[0] = null; - } + void foo() { + noa = null; + // :: error: (accessing.nullable) :: error: (assignment.type.incompatible) + noa[0] = null; + } - @Nullable Object[] foo2(@Nullable Object[] p) { - noa = p; - noa = foo2(noa); - noa = foo2(p); - return p; - } + @Nullable Object[] foo2(@Nullable Object[] p) { + noa = p; + noa = foo2(noa); + noa = foo2(p); + return p; + } - // The below is copied from Issue 2923. - public void bar1(@Nullable String... args) { - bar2(args); - } + // The below is copied from Issue 2923. + public void bar1(@Nullable String... args) { + bar2(args); + } - private void bar2(@Nullable String... args) { - if (args != null && args.length > 0) { - @Nullable final String arg0 = args[0]; - // :: warning: (nulltest.redundant) - if (arg0 != null) { - System.out.println("arg0: " + arg0); - } - } + private void bar2(@Nullable String... args) { + if (args != null && args.length > 0) { + @Nullable final String arg0 = args[0]; + // :: warning: (nulltest.redundant) + if (arg0 != null) { + System.out.println("arg0: " + arg0); + } } + } } diff --git a/checker/tests/nullness/Issue289.java b/checker/tests/nullness/Issue289.java index 4caf0afb135..48763453fe7 100644 --- a/checker/tests/nullness/Issue289.java +++ b/checker/tests/nullness/Issue289.java @@ -1,42 +1,41 @@ // Test for Issue 289: // https://github.com/typetools/checker-framework/issues/289 -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class Issue289 { - void simple() { - List lo = new ArrayList<>(); - List ls = new ArrayList<>(); + void simple() { + List lo = new ArrayList<>(); + List ls = new ArrayList<>(); - List<@Nullable Object> lno = new ArrayList<>(); - List<@Nullable String> lns = new ArrayList<>(); + List<@Nullable Object> lno = new ArrayList<>(); + List<@Nullable String> lns = new ArrayList<>(); - List> lls = new ArrayList<>(); - lls.add(new ArrayList<>()); + List> lls = new ArrayList<>(); + lls.add(new ArrayList<>()); - // TODO: add a similar test that uses method type variables. - } + // TODO: add a similar test that uses method type variables. + } - // TODO: work on more complex examples: + // TODO: work on more complex examples: - class Upper {} + class Upper {} - class Middle extends Upper {} + class Middle extends Upper {} - class Lower1 extends Middle {} + class Lower1 extends Middle {} - class Lower2 extends Middle {} + class Lower2 extends Middle {} - void complex() { - Upper<@Nullable String> uns = new Lower1<>(); - // :: error: (assignment.type.incompatible) - Upper us = new Lower1<>(); + void complex() { + Upper<@Nullable String> uns = new Lower1<>(); + // :: error: (assignment.type.incompatible) + Upper us = new Lower1<>(); - // :: error: (assignment.type.incompatible) - uns = new Lower2<>(); - us = new Lower2<>(); - } + // :: error: (assignment.type.incompatible) + uns = new Lower2<>(); + us = new Lower2<>(); + } } diff --git a/checker/tests/nullness/Issue293.java b/checker/tests/nullness/Issue293.java index c17ab75fe76..6bb42e041a3 100644 --- a/checker/tests/nullness/Issue293.java +++ b/checker/tests/nullness/Issue293.java @@ -2,59 +2,59 @@ // https://github.com/typetools/checker-framework/issues/293 public class Issue293 { - void test1() { - String s; - try { - s = read(); - } catch (Exception e) { - // Because of definite assignment, s cannot be mentioned here. - write("Catch."); - return; - } finally { - // Because of definite assignment, s cannot be mentioned here. - write("Finally."); - } - - // s is definitely initialized here. - write(s); + void test1() { + String s; + try { + s = read(); + } catch (Exception e) { + // Because of definite assignment, s cannot be mentioned here. + write("Catch."); + return; + } finally { + // Because of definite assignment, s cannot be mentioned here. + write("Finally."); } - void test2() { - String s2 = ""; - try { - } finally { - write(s2); - } - } + // s is definitely initialized here. + write(s); + } - void test3() throws Exception { - String s = ""; - try { - throw new Exception(); - } finally { - write(s); - } + void test2() { + String s2 = ""; + try { + } finally { + write(s2); } + } - void test4() throws Exception { - String s = ""; - try { - if (true) { - throw new Exception(); - } else { - s = null; - } - } finally { - // :: error: argument.type.incompatible - write(s); - } + void test3() throws Exception { + String s = ""; + try { + throw new Exception(); + } finally { + write(s); } + } - String read() throws Exception { + void test4() throws Exception { + String s = ""; + try { + if (true) { throw new Exception(); + } else { + s = null; + } + } finally { + // :: error: argument.type.incompatible + write(s); } + } - void write(String p) { - System.out.println(p); - } + String read() throws Exception { + throw new Exception(); + } + + void write(String p) { + System.out.println(p); + } } diff --git a/checker/tests/nullness/Issue295.java b/checker/tests/nullness/Issue295.java index 50f0c47aa05..95bc55e47ce 100644 --- a/checker/tests/nullness/Issue295.java +++ b/checker/tests/nullness/Issue295.java @@ -5,46 +5,46 @@ abstract class Issue295 { - static class Box { - T value; + static class Box { + T value; - Box(T value) { - this.value = value; - } + Box(T value) { + this.value = value; } + } - abstract MTL load(Factory p); + abstract MTL load(Factory p); - abstract class Factory { - abstract TF create(); - } + abstract class Factory { + abstract TF create(); + } - void f1(Factory> f) { - Box<@Nullable MT1> v = f.create(); - v = load(f); - } + void f1(Factory> f) { + Box<@Nullable MT1> v = f.create(); + v = load(f); + } - void f2(Factory> f) { - Box v = load(f); - } + void f2(Factory> f) { + Box v = load(f); + } - void f3(Factory> f) { - Box v = load(f); - } + void f3(Factory> f) { + Box v = load(f); + } - void f4(Factory> f) { - Box v = load(f); - } + void f4(Factory> f) { + Box v = load(f); + } - void f5(Factory> f) { - Box v = load(f); - } + void f5(Factory> f) { + Box v = load(f); + } - void f6(Factory> f) { - Box v = load(f); - } + void f6(Factory> f) { + Box v = load(f); + } - void f1noquals(Factory> f) { - Box v = load(f); - } + void f1noquals(Factory> f) { + Box v = load(f); + } } diff --git a/checker/tests/nullness/Issue296.java b/checker/tests/nullness/Issue296.java index 889164d0fad..b1bbf3eef77 100644 --- a/checker/tests/nullness/Issue296.java +++ b/checker/tests/nullness/Issue296.java @@ -1,30 +1,29 @@ // Test case for Issue 296: // https://github.com/typetools/checker-framework/issues/296 +import java.util.Arrays; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Arrays; - // Note that with -AinvariantArrays we would get additional errors. public class Issue296 { - public static void f1(T[] a) { - @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); - // :: error: (argument.type.incompatible) - @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); - @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); - } + public static void f1(T[] a) { + @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); + // :: error: (argument.type.incompatible) + @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); + @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); + } - public static void f2(@NonNull T[] a) { - @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); - @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); - @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); - } + public static void f2(@NonNull T[] a) { + @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); + @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); + @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); + } - public static void f3(@Nullable T[] a) { - @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); - // :: error: (argument.type.incompatible) - @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); - @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); - } + public static void f3(@Nullable T[] a) { + @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); + // :: error: (argument.type.incompatible) + @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); + @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); + } } diff --git a/checker/tests/nullness/Issue3013.java b/checker/tests/nullness/Issue3013.java index 94040e59dfc..0611ddf03d7 100644 --- a/checker/tests/nullness/Issue3013.java +++ b/checker/tests/nullness/Issue3013.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; abstract class Issue3013 { - static class Nested { - Nested(Issue3013 list) { - for (Object o : list.asIterable()) {} - } + static class Nested { + Nested(Issue3013 list) { + for (Object o : list.asIterable()) {} } + } - abstract Iterable asIterable(); + abstract Iterable asIterable(); } diff --git a/checker/tests/nullness/Issue3015.java b/checker/tests/nullness/Issue3015.java index d4a503d66c2..275600618ec 100644 --- a/checker/tests/nullness/Issue3015.java +++ b/checker/tests/nullness/Issue3015.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/3015 class Issue3015 { - void acquire() { - String s = ""; + void acquire() { + String s = ""; - try { - return; - } finally { - try { - signal(); - } finally { - s.toString(); - } - } + try { + return; + } finally { + try { + signal(); + } finally { + s.toString(); + } } + } - void signal() {} + void signal() {} } diff --git a/checker/tests/nullness/Issue3020.java b/checker/tests/nullness/Issue3020.java index b8d735d9eba..4d10b8ab139 100644 --- a/checker/tests/nullness/Issue3020.java +++ b/checker/tests/nullness/Issue3020.java @@ -1,17 +1,17 @@ enum Issue3020 { - INSTANCE; + INSTANCE; - void retrieveConstant() { - Class theClass = Issue3020.class; - // :: error: (accessing.nullable) - Object unused = passThrough(theClass.getEnumConstants())[0]; - } + void retrieveConstant() { + Class theClass = Issue3020.class; + // :: error: (accessing.nullable) + Object unused = passThrough(theClass.getEnumConstants())[0]; + } - void nonNullArray(String[] p) { - Object unused = passThrough(p)[0]; - } + void nonNullArray(String[] p) { + Object unused = passThrough(p)[0]; + } - T passThrough(T t) { - return t; - } + T passThrough(T t) { + return t; + } } diff --git a/checker/tests/nullness/Issue3022.java b/checker/tests/nullness/Issue3022.java index 08fbc24ab2e..dd890d504ed 100644 --- a/checker/tests/nullness/Issue3022.java +++ b/checker/tests/nullness/Issue3022.java @@ -1,11 +1,11 @@ abstract class Super3022 { - class Wrapper { - Wrapper(T key) {} - } + class Wrapper { + Wrapper(T key) {} + } } class Sub3022 extends Super3022 { - void wrap(K key) { - new Wrapper(key); - } + void wrap(K key) { + new Wrapper(key); + } } diff --git a/checker/tests/nullness/Issue3033.java b/checker/tests/nullness/Issue3033.java index c636dbd9b80..aeec5926a9a 100644 --- a/checker/tests/nullness/Issue3033.java +++ b/checker/tests/nullness/Issue3033.java @@ -3,25 +3,25 @@ public class Issue3033 { - class Test { + class Test { - void main() { - Test obj1 = new Test(); + void main() { + Test obj1 = new Test(); - // No error as no explicit @Nullable or @NonNull annotation is given. - if (obj1 instanceof Test) { - Test obj2 = new Test(); + // No error as no explicit @Nullable or @NonNull annotation is given. + if (obj1 instanceof Test) { + Test obj2 = new Test(); - // :: error: (instanceof.nullable) - if (obj1 instanceof @Nullable Test) { - obj1 = null; - } + // :: error: (instanceof.nullable) + if (obj1 instanceof @Nullable Test) { + obj1 = null; + } - // :: warning: (instanceof.nonnull.redundant) - if (obj2 instanceof @NonNull Test) { - obj2 = obj1; - } - } + // :: warning: (instanceof.nonnull.redundant) + if (obj2 instanceof @NonNull Test) { + obj2 = obj1; } + } } + } } diff --git a/checker/tests/nullness/Issue306.java b/checker/tests/nullness/Issue306.java index 5c81748c455..65a1dad52ba 100644 --- a/checker/tests/nullness/Issue306.java +++ b/checker/tests/nullness/Issue306.java @@ -7,37 +7,37 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue306 { - @MonotonicNonNull Object x; - - static T check(T var) { - return var; - } - - void fakeMethod() { - // @MonotonicNonNull is not reflexive. - // However, it is the most specific type argument inferred for check. Therefore, an error is - // raised. - // We need a mechanism to not consider a qualifier in type inference. - check(x); - - // Ugly way around the problem: - Issue306.<@Nullable Object>check(x); - - // The following error has to be raised: from the signature it is not guaranteed that the - // parameter is returned. - // :: error: (monotonic.type.incompatible) - x = check(x); - } - - @MonotonicNonNull Object y; - - void realError(@MonotonicNonNull Object p) { - // :: error: (monotonic.type.incompatible) - x = y; - // :: error: (monotonic.type.incompatible) - x = p; - // It would be nice not to raise the following error. - // :: error: (monotonic.type.incompatible) - x = x; - } + @MonotonicNonNull Object x; + + static T check(T var) { + return var; + } + + void fakeMethod() { + // @MonotonicNonNull is not reflexive. + // However, it is the most specific type argument inferred for check. Therefore, an error is + // raised. + // We need a mechanism to not consider a qualifier in type inference. + check(x); + + // Ugly way around the problem: + Issue306.<@Nullable Object>check(x); + + // The following error has to be raised: from the signature it is not guaranteed that the + // parameter is returned. + // :: error: (monotonic.type.incompatible) + x = check(x); + } + + @MonotonicNonNull Object y; + + void realError(@MonotonicNonNull Object p) { + // :: error: (monotonic.type.incompatible) + x = y; + // :: error: (monotonic.type.incompatible) + x = p; + // It would be nice not to raise the following error. + // :: error: (monotonic.type.incompatible) + x = x; + } } diff --git a/checker/tests/nullness/Issue308.java b/checker/tests/nullness/Issue308.java index 5b00672f072..6af58df8294 100644 --- a/checker/tests/nullness/Issue308.java +++ b/checker/tests/nullness/Issue308.java @@ -1,24 +1,23 @@ -import org.checkerframework.checker.nullness.qual.*; - import javax.validation.constraints.NotNull; +import org.checkerframework.checker.nullness.qual.*; // @skip-test The clean-room implementation of javax.validation.constraints.NotNull is not in this // repository because Oracle claims a license over its specification and is lawsuit-happy. public class Issue308 { - @NonNull Object nonnull = new Object(); - @Nullable Object nullable; + @NonNull Object nonnull = new Object(); + @Nullable Object nullable; - @NotNull(message = "hi") Object notnull1 = new Object(); + @NotNull(message = "hi") Object notnull1 = new Object(); - @NotNull(groups = {Object.class}) Object notnull2 = new Object(); + @NotNull(groups = {Object.class}) Object notnull2 = new Object(); - void foo() { - nonnull = notnull1; - notnull2 = nonnull; - notnull1 = notnull2; + void foo() { + nonnull = notnull1; + notnull2 = nonnull; + notnull1 = notnull2; - nullable = notnull1; - nullable = notnull2; - } + nullable = notnull1; + nullable = notnull2; + } } diff --git a/checker/tests/nullness/Issue3150.java b/checker/tests/nullness/Issue3150.java index 24d2507cc99..c6b1639113e 100644 --- a/checker/tests/nullness/Issue3150.java +++ b/checker/tests/nullness/Issue3150.java @@ -4,26 +4,26 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3150 { - void foo(@Nullable Object nble, @NonNull Object nn) { - // :: error: (type.argument.type.incompatible) - requireNonNull1(null); - // :: error: (type.argument.type.incompatible) - requireNonNull1(nble); - requireNonNull1("hello"); - requireNonNull1(nn); - // :: error: (argument.type.incompatible) - requireNonNull2(null); - // :: error: (argument.type.incompatible) - requireNonNull2(nble); - requireNonNull1("hello"); - requireNonNull1(nn); - } + void foo(@Nullable Object nble, @NonNull Object nn) { + // :: error: (type.argument.type.incompatible) + requireNonNull1(null); + // :: error: (type.argument.type.incompatible) + requireNonNull1(nble); + requireNonNull1("hello"); + requireNonNull1(nn); + // :: error: (argument.type.incompatible) + requireNonNull2(null); + // :: error: (argument.type.incompatible) + requireNonNull2(nble); + requireNonNull1("hello"); + requireNonNull1(nn); + } - public static T requireNonNull1(T obj) { - return obj; - } + public static T requireNonNull1(T obj) { + return obj; + } - public static @NonNull T requireNonNull2(@NonNull T obj) { - return obj; - } + public static @NonNull T requireNonNull2(@NonNull T obj) { + return obj; + } } diff --git a/checker/tests/nullness/Issue328.java b/checker/tests/nullness/Issue328.java index 81c902e1726..94febdba971 100644 --- a/checker/tests/nullness/Issue328.java +++ b/checker/tests/nullness/Issue328.java @@ -1,18 +1,17 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class Issue328 { - public static void m(Map a, Map b, Object ka, Object kb) { - if (a.containsKey(ka)) { - @NonNull Object i = a.get(ka); // OK - } - if (b.containsKey(kb)) { - @NonNull Object i = b.get(kb); // OK - } - if (a.containsKey(ka) && b.containsKey(kb)) { - @NonNull Object i = a.get(ka); // ERROR - @NonNull Object j = b.get(kb); // ERROR - } + public static void m(Map a, Map b, Object ka, Object kb) { + if (a.containsKey(ka)) { + @NonNull Object i = a.get(ka); // OK + } + if (b.containsKey(kb)) { + @NonNull Object i = b.get(kb); // OK + } + if (a.containsKey(ka) && b.containsKey(kb)) { + @NonNull Object i = a.get(ka); // ERROR + @NonNull Object j = b.get(kb); // ERROR } + } } diff --git a/checker/tests/nullness/Issue331.java b/checker/tests/nullness/Issue331.java index d3e4f043f20..efb22630a18 100644 --- a/checker/tests/nullness/Issue331.java +++ b/checker/tests/nullness/Issue331.java @@ -4,8 +4,8 @@ import java.util.List; class TestTeranry { - void foo(boolean b, List res) { - Object o = b ? "x" : (b ? "y" : "z"); - res.add(o); - } + void foo(boolean b, List res) { + Object o = b ? "x" : (b ? "y" : "z"); + res.add(o); + } } diff --git a/checker/tests/nullness/Issue3349.java b/checker/tests/nullness/Issue3349.java index 7d38d5929b3..7cd64d72966 100644 --- a/checker/tests/nullness/Issue3349.java +++ b/checker/tests/nullness/Issue3349.java @@ -1,11 +1,10 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.io.Serializable; +import org.checkerframework.checker.nullness.qual.*; // :: warning: (explicit.annotation.ignored) public class Issue3349 { - void foo(T p1) { - @NonNull Serializable s = p1; - @NonNull Object o = p1; - } + void foo(T p1) { + @NonNull Serializable s = p1; + @NonNull Object o = p1; + } } diff --git a/checker/tests/nullness/Issue338.java b/checker/tests/nullness/Issue338.java index f2f1e66099d..d7c22783ae1 100644 --- a/checker/tests/nullness/Issue338.java +++ b/checker/tests/nullness/Issue338.java @@ -1,9 +1,9 @@ interface Foo338 { - Class get(); + Class get(); } public class Issue338 { - static void m2(Foo338 foo) { - Class clazz = foo.get(); - } + static void m2(Foo338 foo) { + Class clazz = foo.get(); + } } diff --git a/checker/tests/nullness/Issue3443.java b/checker/tests/nullness/Issue3443.java index 34057774d1d..8dd2d14be55 100644 --- a/checker/tests/nullness/Issue3443.java +++ b/checker/tests/nullness/Issue3443.java @@ -1,19 +1,19 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3443 { - static > Supplier3443 passThrough(T t) { - // :: error: (return.type.incompatible) - return t; - } + static > Supplier3443 passThrough(T t) { + // :: error: (return.type.incompatible) + return t; + } - public static void main(String[] args) { - Supplier3443<@Nullable String> s1 = () -> null; - // TODO: passThrough(s1) should cause an error. #979. - Supplier3443 s2 = passThrough(s1); - s2.get().toString(); - } + public static void main(String[] args) { + Supplier3443<@Nullable String> s1 = () -> null; + // TODO: passThrough(s1) should cause an error. #979. + Supplier3443 s2 = passThrough(s1); + s2.get().toString(); + } } interface Supplier3443 { - T get(); + T get(); } diff --git a/checker/tests/nullness/Issue355.java b/checker/tests/nullness/Issue355.java index 5a6849917e9..3ade143de9c 100644 --- a/checker/tests/nullness/Issue355.java +++ b/checker/tests/nullness/Issue355.java @@ -1,59 +1,58 @@ // Test case for Issue 355: // https://github.com/typetools/checker-framework/issues/355 +import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.List; - public class Issue355 { - static @NonNull T checkNotNull(@Nullable T sample) { - throw new RuntimeException(); - } + static @NonNull T checkNotNull(@Nullable T sample) { + throw new RuntimeException(); + } - void m(List xs) { - for (String x : checkNotNull(xs)) {} - } + void m(List xs) { + for (String x : checkNotNull(xs)) {} + } } class Issue355b { - static T checkNotNull(T sample) { - throw new RuntimeException(); - } + static T checkNotNull(T sample) { + throw new RuntimeException(); + } - void m(List xs) { - for (Object x : checkNotNull(xs)) {} - } + void m(List xs) { + for (Object x : checkNotNull(xs)) {} + } } class Issue355c { - static T checkNotNull(@NonNull T sample) { - throw new RuntimeException(); - } + static T checkNotNull(@NonNull T sample) { + throw new RuntimeException(); + } - void m(List xs) { - for (Object x : checkNotNull(xs)) {} - } + void m(List xs) { + for (Object x : checkNotNull(xs)) {} + } } class Issue355d { - static @Nullable T checkNotNull(@NonNull T sample) { - throw new RuntimeException(); - } - - void m(List xs) { - // :: error: (iterating.over.nullable) - for (Object x : checkNotNull(xs)) {} - } + static @Nullable T checkNotNull(@NonNull T sample) { + throw new RuntimeException(); + } + + void m(List xs) { + // :: error: (iterating.over.nullable) + for (Object x : checkNotNull(xs)) {} + } } class Issue355e { - static @NonNull T checkNotNull(@NonNull T sample) { - throw new RuntimeException(); - } - - void m(@Nullable List xs) { - // :: error: (argument.type.incompatible) - for (Object x : checkNotNull(xs)) {} - } + static @NonNull T checkNotNull(@NonNull T sample) { + throw new RuntimeException(); + } + + void m(@Nullable List xs) { + // :: error: (argument.type.incompatible) + for (Object x : checkNotNull(xs)) {} + } } diff --git a/checker/tests/nullness/Issue3614.java b/checker/tests/nullness/Issue3614.java index 17e278775fb..707d609a8c7 100644 --- a/checker/tests/nullness/Issue3614.java +++ b/checker/tests/nullness/Issue3614.java @@ -6,112 +6,112 @@ public class Issue3614 { - public static @Nullable Boolean m1(@PolyNull Boolean b) { - return (b == null) ? b : b; - } - - public static @NonNull Boolean m2(@PolyNull Boolean b) { - return (b == null) ? Boolean.TRUE : !b; - } - - public static @PolyNull Boolean m3(@PolyNull Boolean b) { - return (b == null) ? null : Boolean.TRUE; - } - - public static @PolyNull Boolean m4(@PolyNull Boolean b) { - return (b == null) ? null : b; - } - - public static @PolyNull Boolean m5(@PolyNull Boolean b) { - return (b == null) ? b : Boolean.TRUE; - } - - public static @PolyNull Boolean not1(@PolyNull Boolean b) { - return (b == null) ? null : !b; - } - - public static @PolyNull Boolean not2(@PolyNull Boolean b) { - // :: error: (unboxing.of.nullable) - return (b == null) ? b : !b; - } - - public static @PolyNull Boolean not3(@PolyNull Boolean b) { - if (b == null) { - return null; - } else { - return !b; - } - } - - public static <@Nullable T> T of1(T a) { - return a == null ? null : a; - } - - public static <@Nullable T> T of2(T a) { - if (a == null) { - return null; - } else { - return a; - } - } - - public static @PolyNull Integer plus1(@PolyNull Integer b0, @PolyNull Integer b1) { - return (b0 == null || b1 == null) ? null : (b0 + b1); - } - - public static @PolyNull Integer plus2(@PolyNull Integer b0, @PolyNull Integer b1) { - if (b0 == null || b1 == null) { - return null; - } else { - return b0 + b1; - } - } - - public static @PolyNull Integer plus3(@PolyNull Integer a, @PolyNull Integer b) { - if (a == null) { - return null; - } - if (b == null) { - return null; - } - return a + b; - } - - public static @PolyNull Integer plus1Err(@PolyNull Integer b0, @PolyNull Integer b1) { - // :: error: (return.type.incompatible) :: error: (unboxing.of.nullable) - return (b0 == null) ? null : (b0 + b1); - } - - public static @PolyNull Integer plus2Err(@PolyNull Integer b0, @PolyNull Integer b1) { - if (b0 == null) { - return null; - } else { - // :: error: (unboxing.of.nullable) - return b0 + b1; - } - } - - public static @PolyNull Integer plus3Err(@PolyNull Integer a, @PolyNull Integer b) { - if (a == null) { - return null; - } - // :: error: (unboxing.of.nullable) - return a + b; - } - - public static @PolyNull /*("elt")*/ String @PolyNull /*("container")*/ [] typeArray( - @PolyNull /*("elt")*/ Object @PolyNull /*("container")*/ [] seq) { - if (seq == null) { - return null; - } - @PolyNull /*("elt")*/ String[] retval = new @PolyNull /*("elt")*/ String[seq.length]; - for (int i = 0; i < seq.length; i++) { - if (seq[i] == null) { - retval[i] = null; - } else { - retval[i] = seq[i].getClass().toString(); - } - } - return retval; - } + public static @Nullable Boolean m1(@PolyNull Boolean b) { + return (b == null) ? b : b; + } + + public static @NonNull Boolean m2(@PolyNull Boolean b) { + return (b == null) ? Boolean.TRUE : !b; + } + + public static @PolyNull Boolean m3(@PolyNull Boolean b) { + return (b == null) ? null : Boolean.TRUE; + } + + public static @PolyNull Boolean m4(@PolyNull Boolean b) { + return (b == null) ? null : b; + } + + public static @PolyNull Boolean m5(@PolyNull Boolean b) { + return (b == null) ? b : Boolean.TRUE; + } + + public static @PolyNull Boolean not1(@PolyNull Boolean b) { + return (b == null) ? null : !b; + } + + public static @PolyNull Boolean not2(@PolyNull Boolean b) { + // :: error: (unboxing.of.nullable) + return (b == null) ? b : !b; + } + + public static @PolyNull Boolean not3(@PolyNull Boolean b) { + if (b == null) { + return null; + } else { + return !b; + } + } + + public static <@Nullable T> T of1(T a) { + return a == null ? null : a; + } + + public static <@Nullable T> T of2(T a) { + if (a == null) { + return null; + } else { + return a; + } + } + + public static @PolyNull Integer plus1(@PolyNull Integer b0, @PolyNull Integer b1) { + return (b0 == null || b1 == null) ? null : (b0 + b1); + } + + public static @PolyNull Integer plus2(@PolyNull Integer b0, @PolyNull Integer b1) { + if (b0 == null || b1 == null) { + return null; + } else { + return b0 + b1; + } + } + + public static @PolyNull Integer plus3(@PolyNull Integer a, @PolyNull Integer b) { + if (a == null) { + return null; + } + if (b == null) { + return null; + } + return a + b; + } + + public static @PolyNull Integer plus1Err(@PolyNull Integer b0, @PolyNull Integer b1) { + // :: error: (return.type.incompatible) :: error: (unboxing.of.nullable) + return (b0 == null) ? null : (b0 + b1); + } + + public static @PolyNull Integer plus2Err(@PolyNull Integer b0, @PolyNull Integer b1) { + if (b0 == null) { + return null; + } else { + // :: error: (unboxing.of.nullable) + return b0 + b1; + } + } + + public static @PolyNull Integer plus3Err(@PolyNull Integer a, @PolyNull Integer b) { + if (a == null) { + return null; + } + // :: error: (unboxing.of.nullable) + return a + b; + } + + public static @PolyNull /*("elt")*/ String @PolyNull /*("container")*/ [] typeArray( + @PolyNull /*("elt")*/ Object @PolyNull /*("container")*/ [] seq) { + if (seq == null) { + return null; + } + @PolyNull /*("elt")*/ String[] retval = new @PolyNull /*("elt")*/ String[seq.length]; + for (int i = 0; i < seq.length; i++) { + if (seq[i] == null) { + retval[i] = null; + } else { + retval[i] = seq[i].getClass().toString(); + } + } + return retval; + } } diff --git a/checker/tests/nullness/Issue3622.java b/checker/tests/nullness/Issue3622.java index 23b57dc5bd4..48414cb6438 100644 --- a/checker/tests/nullness/Issue3622.java +++ b/checker/tests/nullness/Issue3622.java @@ -1,118 +1,117 @@ // Test case for https://tinyurl.com/cfissue/3622 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3622 { - public class ImmutableIntList1 { + public class ImmutableIntList1 { - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof ImmutableIntList1) { - return true; - } else { - return obj instanceof List; - } - } + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof ImmutableIntList1) { + return true; + } else { + return obj instanceof List; + } } + } - public class ImmutableIntList2 { + public class ImmutableIntList2 { - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList2; - } + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList2; } + } - public class ImmutableIntList3 { + public class ImmutableIntList3 { - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof ImmutableIntList3) { - return true; - } else { - return false; - } - } + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof ImmutableIntList3) { + return true; + } else { + return false; + } } + } - public class ImmutableIntList4 { + public class ImmutableIntList4 { - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList4 ? true : obj instanceof List; - } + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList4 ? true : obj instanceof List; } + } - public class ImmutableIntList5 { + public class ImmutableIntList5 { - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList5 - ? obj instanceof ImmutableIntList5 - : obj instanceof ImmutableIntList5; - } + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList5 + ? obj instanceof ImmutableIntList5 + : obj instanceof ImmutableIntList5; } + } - public class ImmutableIntList6 { + public class ImmutableIntList6 { - @Override - public boolean equals(@Nullable Object obj) { - return true ? obj instanceof ImmutableIntList6 : obj instanceof ImmutableIntList6; - } + @Override + public boolean equals(@Nullable Object obj) { + return true ? obj instanceof ImmutableIntList6 : obj instanceof ImmutableIntList6; } + } - public class ImmutableIntList7 { - @Override - public boolean equals(@Nullable Object obj) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return (obj instanceof ImmutableIntList7) ? true : !(obj instanceof List); - } + public class ImmutableIntList7 { + @Override + public boolean equals(@Nullable Object obj) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return (obj instanceof ImmutableIntList7) ? true : !(obj instanceof List); } - - public class ImmutableIntList8 { - - @Override - // The ternary expression has the condition of literal `true`, so the false-expression is - // unreachable. However the store in the unreachable false-branch (where `obj` is @Nullable) - // is propagated to the merge point, which causes the false positive. - // TODO: prune the dead branch like https://github.com/typetools/checker-framework/pull/3389 - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - public boolean equals(@Nullable Object obj) { - return true ? obj instanceof ImmutableIntList8 : false; - } + } + + public class ImmutableIntList8 { + + @Override + // The ternary expression has the condition of literal `true`, so the false-expression is + // unreachable. However the store in the unreachable false-branch (where `obj` is @Nullable) + // is propagated to the merge point, which causes the false positive. + // TODO: prune the dead branch like https://github.com/typetools/checker-framework/pull/3389 + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + public boolean equals(@Nullable Object obj) { + return true ? obj instanceof ImmutableIntList8 : false; } - - public class ImmutableIntList9 { - - @Override - // The false expression of the tenary expression is literal `false`. In this case only the - // else-store after `false` should be propagated to the else-store of the merge point. - // TODO: adapt the way of store propagation for boolean variables. i.e. for `true`, only - // then-store is propagated; and for `false`, only else-store is propagated. - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList9 ? true : false; - } + } + + public class ImmutableIntList9 { + + @Override + // The false expression of the tenary expression is literal `false`. In this case only the + // else-store after `false` should be propagated to the else-store of the merge point. + // TODO: adapt the way of store propagation for boolean variables. i.e. for `true`, only + // then-store is propagated; and for `false`, only else-store is propagated. + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList9 ? true : false; } - - public class ImmutableIntList10 { - - @Override - // The false positive is because in the Nullness analysis the values of boolean variables - // are not stored, therefore the relation between boolean variable `b` and `obj` is not - // known - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - public boolean equals(@Nullable Object obj) { - boolean b; - if (obj instanceof ImmutableIntList10) { - b = true; - } else { - b = false; - } - return b; - } + } + + public class ImmutableIntList10 { + + @Override + // The false positive is because in the Nullness analysis the values of boolean variables + // are not stored, therefore the relation between boolean variable `b` and `obj` is not + // known + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + public boolean equals(@Nullable Object obj) { + boolean b; + if (obj instanceof ImmutableIntList10) { + b = true; + } else { + b = false; + } + return b; } + } } diff --git a/checker/tests/nullness/Issue3631.java b/checker/tests/nullness/Issue3631.java index 4704ec06e9e..e88ce736de7 100644 --- a/checker/tests/nullness/Issue3631.java +++ b/checker/tests/nullness/Issue3631.java @@ -2,17 +2,17 @@ public class Issue3631 { - void f(Object otherArg) { - // Casts aren't a supported JavaExpression. - // :: error: (contracts.precondition.not.satisfied) - ((Issue3631Helper) otherArg).m(); - } + void f(Object otherArg) { + // Casts aren't a supported JavaExpression. + // :: error: (contracts.precondition.not.satisfied) + ((Issue3631Helper) otherArg).m(); + } } class Issue3631Helper { - String type = "foo"; + String type = "foo"; - @RequiresNonNull("type") - void m() {} + @RequiresNonNull("type") + void m() {} } diff --git a/checker/tests/nullness/Issue3681.java b/checker/tests/nullness/Issue3681.java index 6637dedb049..b4a7f8d29f4 100644 --- a/checker/tests/nullness/Issue3681.java +++ b/checker/tests/nullness/Issue3681.java @@ -4,46 +4,46 @@ package org.jro.tests.checkerfwk.utils; public class Issue3681 { - interface PartialFunction { - R apply(T t); - - boolean isDefinedAt(T value); - } - - interface Either { - R get(); - - boolean isRight(); - } - - public static PartialFunction, R> createKeepRight() { - return new PartialFunction<>() { - - @Override - public R apply(final Either either) { - return either.get(); - } - - @Override - public boolean isDefinedAt(final Either value) { - return value.isRight(); - } - }; - } - - public static - PartialFunction, ? extends R> createRCovariantKeepRight() { - return new PartialFunction<>() { - - @Override - public R apply(final Either either) { - return either.get(); - } - - @Override - public boolean isDefinedAt(final Either value) { - return value.isRight(); - } - }; - } + interface PartialFunction { + R apply(T t); + + boolean isDefinedAt(T value); + } + + interface Either { + R get(); + + boolean isRight(); + } + + public static PartialFunction, R> createKeepRight() { + return new PartialFunction<>() { + + @Override + public R apply(final Either either) { + return either.get(); + } + + @Override + public boolean isDefinedAt(final Either value) { + return value.isRight(); + } + }; + } + + public static + PartialFunction, ? extends R> createRCovariantKeepRight() { + return new PartialFunction<>() { + + @Override + public R apply(final Either either) { + return either.get(); + } + + @Override + public boolean isDefinedAt(final Either value) { + return value.isRight(); + } + }; + } } diff --git a/checker/tests/nullness/Issue369.java b/checker/tests/nullness/Issue369.java index 165d687ce38..ae1acdac700 100644 --- a/checker/tests/nullness/Issue369.java +++ b/checker/tests/nullness/Issue369.java @@ -6,7 +6,7 @@ import java.util.stream.Stream; public class Issue369 { - static void test(Stream stream) { - stream.collect(toSet()); - } + static void test(Stream stream) { + stream.collect(toSet()); + } } diff --git a/checker/tests/nullness/Issue370.java b/checker/tests/nullness/Issue370.java index 293556fb5fb..fae04fa74bc 100644 --- a/checker/tests/nullness/Issue370.java +++ b/checker/tests/nullness/Issue370.java @@ -5,7 +5,7 @@ public class Issue370 { - Iterable foo() { - return Collections.emptyList(); - } + Iterable foo() { + return Collections.emptyList(); + } } diff --git a/checker/tests/nullness/Issue372.java b/checker/tests/nullness/Issue372.java index 8fcdb417c66..f80773572ca 100644 --- a/checker/tests/nullness/Issue372.java +++ b/checker/tests/nullness/Issue372.java @@ -1,16 +1,15 @@ // Test case for Issue 372: // https://github.com/typetools/checker-framework/issues/372 -import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; public class Issue372 { - private final Map labels = new HashMap<>(); + private final Map labels = new HashMap<>(); - @EnsuresKeyFor(value = "#1", map = "labels") - void foo(String v) { - labels.put(v, ""); - } + @EnsuresKeyFor(value = "#1", map = "labels") + void foo(String v) { + labels.put(v, ""); + } } diff --git a/checker/tests/nullness/Issue3754.java b/checker/tests/nullness/Issue3754.java index 4e952d798aa..3b91fff0aea 100644 --- a/checker/tests/nullness/Issue3754.java +++ b/checker/tests/nullness/Issue3754.java @@ -3,19 +3,19 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue3754 { - interface Supplier { - U get(); - } + interface Supplier { + U get(); + } - Object x(Supplier bar) { - return bar.get(); - } + Object x(Supplier bar) { + return bar.get(); + } - interface Supplier2 { - U get(); - } + interface Supplier2 { + U get(); + } - Object x(Supplier2 bar) { - return bar.get(); - } + Object x(Supplier2 bar) { + return bar.get(); + } } diff --git a/checker/tests/nullness/Issue376.java b/checker/tests/nullness/Issue376.java index 34219f1d53a..4a918ee6a85 100644 --- a/checker/tests/nullness/Issue376.java +++ b/checker/tests/nullness/Issue376.java @@ -3,9 +3,9 @@ public class Issue376 { - interface I {} + interface I {} - & I> void m(Class clazz, String name) { - I i = Enum.valueOf(clazz, name); - } + & I> void m(Class clazz, String name) { + I i = Enum.valueOf(clazz, name); + } } diff --git a/checker/tests/nullness/Issue3792.java b/checker/tests/nullness/Issue3792.java index d63f2b1a56e..4f8f0b99fbf 100644 --- a/checker/tests/nullness/Issue3792.java +++ b/checker/tests/nullness/Issue3792.java @@ -2,15 +2,13 @@ import java.util.NavigableMap; public abstract class Issue3792 { - static class Instant {} + static class Instant {} - void method( - NavigableMap> contents, - Instant minTimestamp, - Instant limitTimestamp) { - contents.subMap(minTimestamp, true, limitTimestamp, false).entrySet().stream() - .flatMap(e -> e.getValue().stream().map(v -> of(v, e.getKey()))); - } + void method( + NavigableMap> contents, Instant minTimestamp, Instant limitTimestamp) { + contents.subMap(minTimestamp, true, limitTimestamp, false).entrySet().stream() + .flatMap(e -> e.getValue().stream().map(v -> of(v, e.getKey()))); + } - abstract Object of(T v, Instant key); + abstract Object of(T v, Instant key); } diff --git a/checker/tests/nullness/Issue3845.java b/checker/tests/nullness/Issue3845.java index 8decaf633fa..b30f64c37fb 100644 --- a/checker/tests/nullness/Issue3845.java +++ b/checker/tests/nullness/Issue3845.java @@ -5,26 +5,26 @@ import org.checkerframework.framework.qual.TypeUseLocation; public class Issue3845 { - static class Holder { - final T value; + static class Holder { + final T value; - Holder(T value) { - this.value = value; - } + Holder(T value) { + this.value = value; } + } - interface HolderSupplier> { - H get(); - } + interface HolderSupplier> { + H get(); + } - @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.ALL) - static class DefaultClash { + @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.ALL) + static class DefaultClash { - Object go(HolderSupplier s) { - if (s != null) { - return s.get(); - } - return ""; - } + Object go(HolderSupplier s) { + if (s != null) { + return s.get(); + } + return ""; } + } } diff --git a/checker/tests/nullness/Issue3850.java b/checker/tests/nullness/Issue3850.java index 056dbb9eb21..57c053513a9 100644 --- a/checker/tests/nullness/Issue3850.java +++ b/checker/tests/nullness/Issue3850.java @@ -2,14 +2,14 @@ public class Issue3850 { - private static Iterable<@PolyNull String> toPos(Iterable nodes) { - // :: error: (return.type.incompatible) - return transform(nodes, node -> node == null ? null : node.toString()); - } + private static Iterable<@PolyNull String> toPos(Iterable nodes) { + // :: error: (return.type.incompatible) + return transform(nodes, node -> node == null ? null : node.toString()); + } - public static Iterable transform( - Iterable iterable, - java.util.function.Function function) { - throw new Error("implementation is irrelevant"); - } + public static Iterable transform( + Iterable iterable, + java.util.function.Function function) { + throw new Error("implementation is irrelevant"); + } } diff --git a/checker/tests/nullness/Issue388.java b/checker/tests/nullness/Issue388.java index 702eeb19016..6712f9e3134 100644 --- a/checker/tests/nullness/Issue388.java +++ b/checker/tests/nullness/Issue388.java @@ -4,15 +4,15 @@ import java.util.Map; public class Issue388 { - static class Holder { - static final String KEY = "key"; - } + static class Holder { + static final String KEY = "key"; + } - public String getOrDefault(Map map, String defaultValue) { - if (map.containsKey(Holder.KEY)) { - return map.get(Holder.KEY); - } else { - return defaultValue; - } + public String getOrDefault(Map map, String defaultValue) { + if (map.containsKey(Holder.KEY)) { + return map.get(Holder.KEY); + } else { + return defaultValue; } + } } diff --git a/checker/tests/nullness/Issue3884.java b/checker/tests/nullness/Issue3884.java index 68609d9b1c9..e59c071c49a 100644 --- a/checker/tests/nullness/Issue3884.java +++ b/checker/tests/nullness/Issue3884.java @@ -1,18 +1,18 @@ interface Issue3884 { - String go(Kind kind); + String go(Kind kind); - Issue3884 FOO = - kind -> { - switch (kind) { - case A: - break; - } - return ""; - }; + Issue3884 FOO = + kind -> { + switch (kind) { + case A: + break; + } + return ""; + }; - enum Kind { - A, - B, - C; - } + enum Kind { + A, + B, + C; + } } diff --git a/checker/tests/nullness/Issue3888.java b/checker/tests/nullness/Issue3888.java index 585d3438e55..f999c50f304 100644 --- a/checker/tests/nullness/Issue3888.java +++ b/checker/tests/nullness/Issue3888.java @@ -2,17 +2,17 @@ abstract class Issue3888 { - interface L {} + interface L {} - interface E {} + interface E {} - public interface F { - Y a(V v); - } + public interface F { + Y a(V v); + } - abstract void f(F f); + abstract void f(F f); - void c(F o) { - f((E vm) -> o.a(vm) == null); - } + void c(F o) { + f((E vm) -> o.a(vm) == null); + } } diff --git a/checker/tests/nullness/Issue391.java b/checker/tests/nullness/Issue391.java index 2a86823ef13..e27c5eb3888 100644 --- a/checker/tests/nullness/Issue391.java +++ b/checker/tests/nullness/Issue391.java @@ -6,44 +6,44 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; class ClassA { - private @Nullable String value = null; + private @Nullable String value = null; - @EnsuresNonNull("value") - public void ensuresNonNull() { - value = ""; - } + @EnsuresNonNull("value") + public void ensuresNonNull() { + value = ""; + } - @RequiresNonNull("value") - public String getValue() { - return value; - } + @RequiresNonNull("value") + public String getValue() { + return value; + } } public class Issue391 { - ClassA field = new ClassA(); - - @RequiresNonNull("field.value") - void method() {} - - @EnsuresNonNull("field.value") - void ensuresNonNull() { - field.ensuresNonNull(); - } - - void method2() { - ClassA a = new ClassA(); - // :: error: (contracts.precondition.not.satisfied) - a.getValue(); - // :: error: (contracts.precondition.not.satisfied) - method(); - } - - void method3() { - ensuresNonNull(); - method(); - - ClassA a = new ClassA(); - a.ensuresNonNull(); - a.getValue(); - } + ClassA field = new ClassA(); + + @RequiresNonNull("field.value") + void method() {} + + @EnsuresNonNull("field.value") + void ensuresNonNull() { + field.ensuresNonNull(); + } + + void method2() { + ClassA a = new ClassA(); + // :: error: (contracts.precondition.not.satisfied) + a.getValue(); + // :: error: (contracts.precondition.not.satisfied) + method(); + } + + void method3() { + ensuresNonNull(); + method(); + + ClassA a = new ClassA(); + a.ensuresNonNull(); + a.getValue(); + } } diff --git a/checker/tests/nullness/Issue3929.java b/checker/tests/nullness/Issue3929.java index ac6d4b37719..574b196d259 100644 --- a/checker/tests/nullness/Issue3929.java +++ b/checker/tests/nullness/Issue3929.java @@ -1,35 +1,34 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3929 { - public void endElement(MyClass3929 arg) { - for (Object o : arg.getKeys()) { - o.toString(); - } + public void endElement(MyClass3929 arg) { + for (Object o : arg.getKeys()) { + o.toString(); } + } - public void endElement(NullableMyClass3929 arg) { - for (Object o : arg.getKeys()) { - // TODO: add a conservative option to get a warning here - o.toString(); - } + public void endElement(NullableMyClass3929 arg) { + for (Object o : arg.getKeys()) { + // TODO: add a conservative option to get a warning here + o.toString(); } + } } class MyClass3929> { - public List getKeys() { - return new ArrayList<>(); - } + public List getKeys() { + return new ArrayList<>(); + } } // TODO: This is a false positive. // See https://github.com/typetools/checker-framework/issues/2174 // :: error: (type.argument.type.incompatible) class NullableMyClass3929> { - public List getKeys() { - return new ArrayList<>(); - } + public List getKeys() { + return new ArrayList<>(); + } } diff --git a/checker/tests/nullness/Issue3935.java b/checker/tests/nullness/Issue3935.java index a180ae339bf..14a0a5e49ec 100644 --- a/checker/tests/nullness/Issue3935.java +++ b/checker/tests/nullness/Issue3935.java @@ -1,10 +1,10 @@ import android.annotation.Nullable; public class Issue3935 { - // Note: Nullable is a declaration annotation and applies to the array, not the array element. - private @Nullable byte[] data; + // Note: Nullable is a declaration annotation and applies to the array, not the array element. + private @Nullable byte[] data; - // Declaration annotations on primitives are ignored, but this should issue - // a nullness.on.primitive error. - @Nullable byte b; + // Declaration annotations on primitives are ignored, but this should issue + // a nullness.on.primitive error. + @Nullable byte b; } diff --git a/checker/tests/nullness/Issue3970.java b/checker/tests/nullness/Issue3970.java index 5c7711fd70a..abecf2801ed 100644 --- a/checker/tests/nullness/Issue3970.java +++ b/checker/tests/nullness/Issue3970.java @@ -2,18 +2,18 @@ public class Issue3970 { - public interface InterfaceA> extends InterfaceB {} + public interface InterfaceA> extends InterfaceB {} - public interface InterfaceB> { - int f(); + public interface InterfaceB> { + int f(); - @Nullable T g(); - } + @Nullable T g(); + } - void t(InterfaceA a) { - if (a.f() == 1) { - // :: error: (assignment.type.incompatible) - InterfaceA a2 = a.g(); - } + void t(InterfaceA a) { + if (a.f() == 1) { + // :: error: (assignment.type.incompatible) + InterfaceA a2 = a.g(); } + } } diff --git a/checker/tests/nullness/Issue4007.java b/checker/tests/nullness/Issue4007.java index c85c937a29c..dd59d94892f 100644 --- a/checker/tests/nullness/Issue4007.java +++ b/checker/tests/nullness/Issue4007.java @@ -2,34 +2,33 @@ // @skip-test until the issue is fixed -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.List; import java.util.Optional; +import org.checkerframework.checker.nullness.qual.NonNull; final class Issue4007 { - Optional m1(List list) { - return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); - } - - Optional> m2(List list) { - return Optional.of(list.isEmpty() ? Optional.empty() : Optional.of(list.get(0))); - } - - Optional> m3(List list) { - return Optional.of( - list.isEmpty() ? Optional.<@NonNull String>empty() : Optional.of(list.get(0))); - } - - Optional> m4(List list) { - return Optional.of( - list.isEmpty() ? Optional.empty() : Optional.<@NonNull String>of(list.get(0))); - } - - Optional> m5(List list) { - return Optional.of( - list.isEmpty() - ? Optional.<@NonNull String>empty() - : Optional.<@NonNull String>of(list.get(0))); - } + Optional m1(List list) { + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); + } + + Optional> m2(List list) { + return Optional.of(list.isEmpty() ? Optional.empty() : Optional.of(list.get(0))); + } + + Optional> m3(List list) { + return Optional.of( + list.isEmpty() ? Optional.<@NonNull String>empty() : Optional.of(list.get(0))); + } + + Optional> m4(List list) { + return Optional.of( + list.isEmpty() ? Optional.empty() : Optional.<@NonNull String>of(list.get(0))); + } + + Optional> m5(List list) { + return Optional.of( + list.isEmpty() + ? Optional.<@NonNull String>empty() + : Optional.<@NonNull String>of(list.get(0))); + } } diff --git a/checker/tests/nullness/Issue411.java b/checker/tests/nullness/Issue411.java index 6274162a797..4e5cb14f0d5 100644 --- a/checker/tests/nullness/Issue411.java +++ b/checker/tests/nullness/Issue411.java @@ -5,26 +5,26 @@ public class Issue411 { - @MonotonicNonNull Object field1 = null; - final @Nullable Object field2 = null; + @MonotonicNonNull Object field1 = null; + final @Nullable Object field2 = null; - void m() { - if (field1 != null) { - new Object() { - void f() { - field1.toString(); - } - }; + void m() { + if (field1 != null) { + new Object() { + void f() { + field1.toString(); } + }; } + } - void n() { - if (field2 != null) { - new Object() { - void f() { - field2.toString(); - } - }; + void n() { + if (field2 != null) { + new Object() { + void f() { + field2.toString(); } + }; } + } } diff --git a/checker/tests/nullness/Issue414.java b/checker/tests/nullness/Issue414.java index 17e2b178292..5da328b00dc 100644 --- a/checker/tests/nullness/Issue414.java +++ b/checker/tests/nullness/Issue414.java @@ -1,55 +1,54 @@ // Test case for Issue 414. // https://github.com/typetools/checker-framework/issues/414 -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.KeyFor; public class Issue414 { - void simple(String s) { - Map mymap = new HashMap<>(); - mymap.put(s, 1); - @KeyFor("mymap") String s2 = s; - } + void simple(String s) { + Map mymap = new HashMap<>(); + mymap.put(s, 1); + @KeyFor("mymap") String s2 = s; + } - Map someField = new HashMap<>(); + Map someField = new HashMap<>(); - void semiSimple(@KeyFor("this.someField") String s) { - Map mymap = new HashMap<>(); - mymap.put(s, 1); - @KeyFor({"this.someField", "mymap"}) String s2 = s; - } + void semiSimple(@KeyFor("this.someField") String s) { + Map mymap = new HashMap<>(); + mymap.put(s, 1); + @KeyFor({"this.someField", "mymap"}) String s2 = s; + } - void dominatorsNoGenerics(Map preds) { + void dominatorsNoGenerics(Map preds) { - Map dom = new HashMap<>(); - @KeyFor({"preds", "dom"}) String root; + Map dom = new HashMap<>(); + @KeyFor({"preds", "dom"}) String root; - List<@KeyFor({"preds", "dom"}) String> roots = new ArrayList(); + List<@KeyFor({"preds", "dom"}) String> roots = new ArrayList(); - for (String node : preds.keySet()) { - dom.put(node, 1); - root = node; - roots.add(node); - } + for (String node : preds.keySet()) { + dom.put(node, 1); + root = node; + roots.add(node); } + } - void dominators(Map> preds) { + void dominators(Map> preds) { - Map dom = new HashMap<>(); + Map dom = new HashMap<>(); - @KeyFor({"preds", "dom"}) T root; + @KeyFor({"preds", "dom"}) T root; - List<@KeyFor({"preds", "dom"}) T> roots = new ArrayList(); + List<@KeyFor({"preds", "dom"}) T> roots = new ArrayList(); - for (T node : preds.keySet()) { - dom.put(node, 1); - root = node; - roots.add(node); - } + for (T node : preds.keySet()) { + dom.put(node, 1); + root = node; + roots.add(node); } + } } diff --git a/checker/tests/nullness/Issue415.java b/checker/tests/nullness/Issue415.java index 22d74fcb654..b461d045b0a 100644 --- a/checker/tests/nullness/Issue415.java +++ b/checker/tests/nullness/Issue415.java @@ -1,46 +1,45 @@ // Test case for Issue 415 // https://github.com/typetools/checker-framework/issues/415 -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.dataflow.qual.*; - import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.dataflow.qual.*; public final class Issue415 { - Map mymap = new HashMap<>(); + Map mymap = new HashMap<>(); + // :: error: (expression.unparsable.type.invalid) + public static void usesField(Set<@KeyFor("this.mymap") String> keySet) { // :: error: (expression.unparsable.type.invalid) - public static void usesField(Set<@KeyFor("this.mymap") String> keySet) { - // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("this.mymap") String>(keySet); - } - - public static void usesParameter(Map m, Set<@KeyFor("#1") String> keySet) { - new ArrayList<@KeyFor("#1") String>(keySet); - } - - public static void sortedKeySet1(Map m, Set<@KeyFor("#1") String> keySet) { - new ArrayList<@KeyFor("#1") String>(keySet); - } - - public static void sortedKeySet2(Map m) { - Set<@KeyFor("#1") String> keySet = m.keySet(); - } - - public static void sortedKeySet3(Map m) { - Set<@KeyFor("#1") String> keySet = m.keySet(); - new ArrayList<@KeyFor("#1") String>(keySet); - } - - public static void sortedKeySet4(Map m) { - new ArrayList<@KeyFor("#1") String>(m.keySet()); - } - - public static , V> void sortedKeySet(Map m) { - new ArrayList<@KeyFor("#1") K>(m.keySet()); - } + new ArrayList<@KeyFor("this.mymap") String>(keySet); + } + + public static void usesParameter(Map m, Set<@KeyFor("#1") String> keySet) { + new ArrayList<@KeyFor("#1") String>(keySet); + } + + public static void sortedKeySet1(Map m, Set<@KeyFor("#1") String> keySet) { + new ArrayList<@KeyFor("#1") String>(keySet); + } + + public static void sortedKeySet2(Map m) { + Set<@KeyFor("#1") String> keySet = m.keySet(); + } + + public static void sortedKeySet3(Map m) { + Set<@KeyFor("#1") String> keySet = m.keySet(); + new ArrayList<@KeyFor("#1") String>(keySet); + } + + public static void sortedKeySet4(Map m) { + new ArrayList<@KeyFor("#1") String>(m.keySet()); + } + + public static , V> void sortedKeySet(Map m) { + new ArrayList<@KeyFor("#1") K>(m.keySet()); + } } diff --git a/checker/tests/nullness/Issue419.java b/checker/tests/nullness/Issue419.java index 46e787ee69b..385145578e3 100644 --- a/checker/tests/nullness/Issue419.java +++ b/checker/tests/nullness/Issue419.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue419 { - @SuppressWarnings("nullness") - @NonNull T verifyNotNull(@Nullable T o) { - return o; - } + @SuppressWarnings("nullness") + @NonNull T verifyNotNull(@Nullable T o) { + return o; + } - interface Pair { - @Nullable A getFirst(); - } + interface Pair { + @Nullable A getFirst(); + } - void m(Pair p) { - for (String s : verifyNotNull(p.getFirst())) {} - } + void m(Pair p) { + for (String s : verifyNotNull(p.getFirst())) {} + } } diff --git a/checker/tests/nullness/Issue425.java b/checker/tests/nullness/Issue425.java index 4346cebabac..a581f03282d 100644 --- a/checker/tests/nullness/Issue425.java +++ b/checker/tests/nullness/Issue425.java @@ -3,33 +3,32 @@ // @skip-test until the issue is fixed -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.HashSet; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue425 { - private @Nullable Set field = null; + private @Nullable Set field = null; - class EvilSet extends HashSet { - public boolean add(T e) { - field = null; - return super.add(e); - } + class EvilSet extends HashSet { + public boolean add(T e) { + field = null; + return super.add(e); } + } - public void fail() { - if (field == null) { - field = new EvilSet<>(); - } - - field.add(1); - // This line throws an exception at run time. - field.add(2); + public void fail() { + if (field == null) { + field = new EvilSet<>(); } - public static void main(String[] args) throws Exception { - Issue425 m = new Issue425(); - m.fail(); - } + field.add(1); + // This line throws an exception at run time. + field.add(2); + } + + public static void main(String[] args) throws Exception { + Issue425 m = new Issue425(); + m.fail(); + } } diff --git a/checker/tests/nullness/Issue427.java b/checker/tests/nullness/Issue427.java index 09c54640635..fc86027f6be 100644 --- a/checker/tests/nullness/Issue427.java +++ b/checker/tests/nullness/Issue427.java @@ -3,21 +3,20 @@ // We need to add a warning when an @AssumeAssertion is missing its @ symbol (as below). -import org.checkerframework.checker.nullness.qual.*; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class Issue427 { - public static void assumeAssertionKeyFor1(String var, Map m) { - assert m.containsKey(var) - : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; - boolean b = (m.get(var) >= 22); - } + public static void assumeAssertionKeyFor1(String var, Map m) { + assert m.containsKey(var) + : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; + boolean b = (m.get(var) >= 22); + } - public static void assumeAssertionKeyFor2(String var, Map m) { - assert m.containsKey(var) - : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; - int x = m.get(var); - } + public static void assumeAssertionKeyFor2(String var, Map m) { + assert m.containsKey(var) + : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; + int x = m.get(var); + } } diff --git a/checker/tests/nullness/Issue4372.java b/checker/tests/nullness/Issue4372.java index aeffd463f17..90858d799b4 100644 --- a/checker/tests/nullness/Issue4372.java +++ b/checker/tests/nullness/Issue4372.java @@ -2,19 +2,19 @@ import java.util.Optional; public class Issue4372 { - private Optional> o; + private Optional> o; - public Issue4372() { - this.o = Optional.>empty(); - } + public Issue4372() { + this.o = Optional.>empty(); + } - void f(String k, Optional x) { - if (!o.isPresent() || !o.get().containsKey(k)) { - return; - } - Integer y = o.get().get(k); - if (!x.isPresent()) { - return; - } + void f(String k, Optional x) { + if (!o.isPresent() || !o.get().containsKey(k)) { + return; + } + Integer y = o.get().get(k); + if (!x.isPresent()) { + return; } + } } diff --git a/checker/tests/nullness/Issue4381.java b/checker/tests/nullness/Issue4381.java index 2ed7c491120..8e071879fdc 100644 --- a/checker/tests/nullness/Issue4381.java +++ b/checker/tests/nullness/Issue4381.java @@ -3,17 +3,17 @@ abstract class Issue4381 { - public void t() { - int m = 0; - try { - f(); - } catch (IllegalArgumentException | IOException e) { - } catch (GeneralSecurityException e) { - g(m); - } + public void t() { + int m = 0; + try { + f(); + } catch (IllegalArgumentException | IOException e) { + } catch (GeneralSecurityException e) { + g(m); } + } - abstract void g(int x); + abstract void g(int x); - abstract void f() throws IllegalArgumentException, IOException, GeneralSecurityException; + abstract void f() throws IllegalArgumentException, IOException, GeneralSecurityException; } diff --git a/checker/tests/nullness/Issue4412.java b/checker/tests/nullness/Issue4412.java index 1410e155eae..6474cae1a9e 100644 --- a/checker/tests/nullness/Issue4412.java +++ b/checker/tests/nullness/Issue4412.java @@ -1,227 +1,210 @@ // @skip-test This test passes, but is slow. So skip it until performance improves. -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.NonNull; public interface Issue4412< - LeftLeftType extends Issue4412.LeftLeft, - LeftType extends Issue4412.Left, - RightType extends Issue4412.Right, - RightRightType extends - Issue4412.RightRight> { - - T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer); - - void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction); - - interface LeftLeft< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class LeftLeftImpl< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - implements LeftLeft { - - private final Class selfClass; - - protected LeftLeftImpl(Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return leftLeftReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - leftLeftAction.apply(getSelf()); - } - - private LeftLeftType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); - } - } - - interface Left< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class LeftImpl< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - implements Left { - - private final Class selfClass; - - protected LeftImpl(@NonNull Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return leftReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - leftAction.apply(getSelf()); - } - - private LeftType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); - } - } - - interface Right< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class RightImpl< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - implements Right { - - private final Class selfClass; - - protected RightImpl(@NonNull Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return rightReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - rightAction.apply(getSelf()); - } - - private RightType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); - } - } - - interface RightRight< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class RightRightImpl< - LeftLeftType extends - LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends - RightRight> - implements RightRight { - - private final Class selfClass; - - protected RightRightImpl(@NonNull Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return rightRightReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - rightRightAction.apply(getSelf()); - } - - private RightRightType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); - } - } - - interface VoidFunction1 { - - void apply(@NonNull T t); - } - - interface Function1 { - - @NonNull R apply(@NonNull T t); + LeftLeftType extends Issue4412.LeftLeft, + LeftType extends Issue4412.Left, + RightType extends Issue4412.Right, + RightRightType extends + Issue4412.RightRight> { + + T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer); + + void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction); + + interface LeftLeft< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class LeftLeftImpl< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + implements LeftLeft { + + private final Class selfClass; + + protected LeftLeftImpl(Class selfClass) { + this.selfClass = selfClass; + } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return leftLeftReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + leftLeftAction.apply(getSelf()); + } + + private LeftLeftType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface Left< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class LeftImpl< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + implements Left { + + private final Class selfClass; + + protected LeftImpl(@NonNull Class selfClass) { + this.selfClass = selfClass; + } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return leftReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + leftAction.apply(getSelf()); + } + + private LeftType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface Right< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class RightImpl< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + implements Right { + + private final Class selfClass; + + protected RightImpl(@NonNull Class selfClass) { + this.selfClass = selfClass; } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return rightReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + rightAction.apply(getSelf()); + } + + private RightType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface RightRight< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class RightRightImpl< + LeftLeftType extends LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends RightRight> + implements RightRight { + + private final Class selfClass; + + protected RightRightImpl(@NonNull Class selfClass) { + this.selfClass = selfClass; + } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return rightRightReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + rightRightAction.apply(getSelf()); + } + + private RightRightType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface VoidFunction1 { + + void apply(@NonNull T t); + } + + interface Function1 { + + @NonNull R apply(@NonNull T t); + } } diff --git a/checker/tests/nullness/Issue4523.java b/checker/tests/nullness/Issue4523.java index e1795e9c343..bf1ba98bc09 100644 --- a/checker/tests/nullness/Issue4523.java +++ b/checker/tests/nullness/Issue4523.java @@ -2,13 +2,13 @@ public class Issue4523 { - interface InterfaceA> extends InterfaceB {} + interface InterfaceA> extends InterfaceB {} - interface InterfaceB> { - @Nullable T g(); - } + interface InterfaceB> { + @Nullable T g(); + } - void f(InterfaceA x) { - InterfaceA y = x.g() != null ? x.g() : x; - } + void f(InterfaceA x) { + InterfaceA y = x.g() != null ? x.g() : x; + } } diff --git a/checker/tests/nullness/Issue4579.java b/checker/tests/nullness/Issue4579.java index 5c0e490340b..bd1ea4bb882 100644 --- a/checker/tests/nullness/Issue4579.java +++ b/checker/tests/nullness/Issue4579.java @@ -4,38 +4,37 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; - import javax.swing.JButton; public class Issue4579 { - public Issue4579() { - final JButton button = new JButton(); + public Issue4579() { + final JButton button = new JButton(); - // this reports a warning about under initialization - button.addActionListener(l -> doAction()); + // this reports a warning about under initialization + button.addActionListener(l -> doAction()); - // this reports no warnings - button.addActionListener(new ListenerClass()); + // this reports no warnings + button.addActionListener(new ListenerClass()); - // this reports a warning about under initialization - button.addActionListener( - new ActionListener() { - public void actionPerformed(final ActionEvent e) { - doAction(); - } - }); - } + // this reports a warning about under initialization + button.addActionListener( + new ActionListener() { + public void actionPerformed(final ActionEvent e) { + doAction(); + } + }); + } - private void doAction() { - System.out.println("Action"); - } + private void doAction() { + System.out.println("Action"); + } - private class ListenerClass implements ActionListener { + private class ListenerClass implements ActionListener { - @Override - public void actionPerformed(final ActionEvent e) { - doAction(); - } + @Override + public void actionPerformed(final ActionEvent e) { + doAction(); } + } } diff --git a/checker/tests/nullness/Issue4593.java b/checker/tests/nullness/Issue4593.java index c4552e6f02f..698127ebed5 100644 --- a/checker/tests/nullness/Issue4593.java +++ b/checker/tests/nullness/Issue4593.java @@ -2,20 +2,19 @@ // @skip-test until the bug is fixed -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue4593 { - void getContext(@Nullable String nble) { - Map map = new HashMap<>(); - map.put("configDir", nble); - } + void getContext(@Nullable String nble) { + Map map = new HashMap<>(); + map.put("configDir", nble); + } - void getContextWithVar(@Nullable String nble) { - var map = new HashMap(); - map.put("configDir", nble); - } + void getContextWithVar(@Nullable String nble) { + var map = new HashMap(); + map.put("configDir", nble); + } } diff --git a/checker/tests/nullness/Issue4614.java b/checker/tests/nullness/Issue4614.java index e913ff1fce0..ea67892fd62 100644 --- a/checker/tests/nullness/Issue4614.java +++ b/checker/tests/nullness/Issue4614.java @@ -4,23 +4,23 @@ public final class Issue4614 { - public static Map getAllVersionInformation() { - return new HashMap<>(); - } + public static Map getAllVersionInformation() { + return new HashMap<>(); + } - public void method1() { - final String versionInfo = - Issue4614.getAllVersionInformation().entrySet().stream() // - .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // - .collect(Collectors.joining("\n")); - } + public void method1() { + final String versionInfo = + Issue4614.getAllVersionInformation().entrySet().stream() // + .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // + .collect(Collectors.joining("\n")); + } - Map allVersionInformation = new HashMap<>(); + Map allVersionInformation = new HashMap<>(); - public void method2() { - final String versionInfo = - allVersionInformation.entrySet().stream() // - .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // - .collect(Collectors.joining("\n")); - } + public void method2() { + final String versionInfo = + allVersionInformation.entrySet().stream() // + .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // + .collect(Collectors.joining("\n")); + } } diff --git a/checker/tests/nullness/Issue471.java b/checker/tests/nullness/Issue471.java index 7026790f3cc..2666c729ba3 100644 --- a/checker/tests/nullness/Issue471.java +++ b/checker/tests/nullness/Issue471.java @@ -5,9 +5,9 @@ import javax.annotation.Nullable; public class Issue471 { - @Nullable T t; + @Nullable T t; - Issue471(@Nullable T t) { - this.t = t; - } + Issue471(@Nullable T t) { + this.t = t; + } } diff --git a/checker/tests/nullness/Issue4853Nullness.java b/checker/tests/nullness/Issue4853Nullness.java index 2d49a3000d2..d6f4502e4bd 100644 --- a/checker/tests/nullness/Issue4853Nullness.java +++ b/checker/tests/nullness/Issue4853Nullness.java @@ -1,18 +1,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue4853Nullness { - interface Interface {} + interface Interface {} - static class MyClass { - class InnerMyClass implements Interface {} - } - - abstract static class SubMyClass extends MyClass<@Nullable String> { - protected void f() { - // :: error: (argument.type.incompatible) - method(new InnerMyClass()); - } + static class MyClass { + class InnerMyClass implements Interface {} + } - abstract void method(Interface callback); + abstract static class SubMyClass extends MyClass<@Nullable String> { + protected void f() { + // :: error: (argument.type.incompatible) + method(new InnerMyClass()); } + + abstract void method(Interface callback); + } } diff --git a/checker/tests/nullness/Issue4889.java b/checker/tests/nullness/Issue4889.java index e9a8e8f6e3b..f30aa016776 100644 --- a/checker/tests/nullness/Issue4889.java +++ b/checker/tests/nullness/Issue4889.java @@ -1,15 +1,14 @@ +import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Objects; - class Issue4889 { - void f(@Nullable String s) { - Objects.toString(s, "").toString(); - } + void f(@Nullable String s) { + Objects.toString(s, "").toString(); + } - void g(@Nullable String s) { - @NonNull String x = Objects.toString(s, ""); - @Nullable String y = Objects.toString(s, null); - } + void g(@Nullable String s) { + @NonNull String x = Objects.toString(s, ""); + @Nullable String y = Objects.toString(s, null); + } } diff --git a/checker/tests/nullness/Issue4923.java b/checker/tests/nullness/Issue4923.java index 829c8f61d08..7d1e81f459a 100644 --- a/checker/tests/nullness/Issue4923.java +++ b/checker/tests/nullness/Issue4923.java @@ -1,19 +1,19 @@ class Issue4923 { - interface Go { - void go(); - } + interface Go { + void go(); + } - final Go go = - new Go() { - @Override - public void go() { - synchronized (x) { - } - } - }; - final Object x = new Object(); + final Go go = + new Go() { + @Override + public void go() { + synchronized (x) { + } + } + }; + final Object x = new Object(); - // Make sure that initializer type is compatible with declared type - // :: error: (assignment.type.incompatible) - final Object y = null; + // Make sure that initializer type is compatible with declared type + // :: error: (assignment.type.incompatible) + final Object y = null; } diff --git a/checker/tests/nullness/Issue4924.java b/checker/tests/nullness/Issue4924.java index bd982f5c2a3..cb24e8660c7 100644 --- a/checker/tests/nullness/Issue4924.java +++ b/checker/tests/nullness/Issue4924.java @@ -5,21 +5,21 @@ class Issue4924 {} interface Callback4924 {} class Template4924 { - interface Putter4924 { - void put(T result); - } + interface Putter4924 { + void put(T result); + } - class Adapter4924 implements Callback4924 { - Adapter4924(Putter4924 putter) {} - } + class Adapter4924 implements Callback4924 { + Adapter4924(Putter4924 putter) {} + } } class Super4924 extends Template4924 {} class Issue extends Super4924 { - void go(Callback4924 callback) {} + void go(Callback4924 callback) {} - void foo() { - go(new Adapter4924(result -> {})); - } + void foo() { + go(new Adapter4924(result -> {})); + } } diff --git a/checker/tests/nullness/Issue500.java b/checker/tests/nullness/Issue500.java index b905c84a03d..0f8f032dabd 100644 --- a/checker/tests/nullness/Issue500.java +++ b/checker/tests/nullness/Issue500.java @@ -1,29 +1,25 @@ // Test case for Issue 500: // https://github.com/typetools/checker-framework/issues/500 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.AbstractList; import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue500 { - // Tests GLB - public Issue500(@Nullable List list) { - if (list instanceof ArrayList) {} - } + // Tests GLB + public Issue500(@Nullable List list) { + if (list instanceof ArrayList) {} + } - // Tests GLB - public Issue500(@Nullable AbstractList list) { - if (list instanceof ArrayList) {} - } + // Tests GLB + public Issue500(@Nullable AbstractList list) { + if (list instanceof ArrayList) {} + } - // Tests LUB - void foo( - @Nullable AbstractList l1, - ArrayList l2, - @Nullable AbstractList list, - boolean b) { - list = b ? l1 : l2; - } + // Tests LUB + void foo( + @Nullable AbstractList l1, ArrayList l2, @Nullable AbstractList list, boolean b) { + list = b ? l1 : l2; + } } diff --git a/checker/tests/nullness/Issue5042.java b/checker/tests/nullness/Issue5042.java index 5f3f01a2d61..0bc0e99d0a8 100644 --- a/checker/tests/nullness/Issue5042.java +++ b/checker/tests/nullness/Issue5042.java @@ -1,68 +1,67 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue5042 { - interface PromptViewModel { - boolean isPending(); + interface PromptViewModel { + boolean isPending(); - @Nullable PromptButtonViewModel getConfirmationButton(); - } + @Nullable PromptButtonViewModel getConfirmationButton(); + } - interface PromptButtonViewModel { - @Nullable ConfirmationPopupViewModel getConfirmationPopup(); - } + interface PromptButtonViewModel { + @Nullable ConfirmationPopupViewModel getConfirmationPopup(); + } - interface ConfirmationPopupViewModel { - boolean isShowingConfirmation(); - } + interface ConfirmationPopupViewModel { + boolean isShowingConfirmation(); + } - boolean f(PromptViewModel viewModel) { - PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - } + boolean f(PromptViewModel viewModel) { + PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + } - static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = - (viewModel) -> { - @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - promptLambda != null ? promptLambda.getConfirmationPopup() : null; - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = + (viewModel) -> { + @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + promptLambda != null ? promptLambda.getConfirmationPopup() : null; + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; - final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = - (viewModel) -> { - @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - prompt == null ? null : prompt.getConfirmationPopup(); - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = + (viewModel) -> { + @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + prompt == null ? null : prompt.getConfirmationPopup(); + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; - @Nullable PromptButtonViewModel promptfield; - Producer o = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield == null ? null : promptfield.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + @Nullable PromptButtonViewModel promptfield; + Producer o = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield == null ? null : promptfield.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - static @Nullable PromptButtonViewModel promptfield2; + static @Nullable PromptButtonViewModel promptfield2; - static Producer o2 = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield2 == null ? null : promptfield2.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + static Producer o2 = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield2 == null ? null : promptfield2.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - interface Producer { - Object apply(); - } + interface Producer { + Object apply(); + } - Issue5042(int i, int i2) {} + Issue5042(int i, int i2) {} - Issue5042(int i) {} + Issue5042(int i) {} - Issue5042() {} + Issue5042() {} } diff --git a/checker/tests/nullness/Issue5075NPE.java b/checker/tests/nullness/Issue5075NPE.java index 7cf68db88ec..114757fb80d 100644 --- a/checker/tests/nullness/Issue5075NPE.java +++ b/checker/tests/nullness/Issue5075NPE.java @@ -3,43 +3,43 @@ public class Issue5075NPE { - static class C { - void useBoxer(Boxer b) { - // :: error: (assignment.type.incompatible) - Box o = b.getBox(); - o.get().toString(); - } - - void useC(C c) {} - - class Boxer { - V v; - - Boxer(V in) { - this.v = in; - } - - Box getBox() { - return new Box(v); - } - } + static class C { + void useBoxer(Boxer b) { + // :: error: (assignment.type.incompatible) + Box o = b.getBox(); + o.get().toString(); } - // Doesn't matter whether T's bound is explicit - static class Box { - T f; + void useC(C c) {} - Box(T p) { - this.f = p; - } + class Boxer { + V v; - T get() { - return f; - } + Boxer(V in) { + this.v = in; + } + + Box getBox() { + return new Box(v); + } } + } + + // Doesn't matter whether T's bound is explicit + static class Box { + T f; - public static void main(String[] args) { - C<@Nullable Issue5075NPE> c = new C<>(); - c.useBoxer(c.new Boxer(null)); + Box(T p) { + this.f = p; } + + T get() { + return f; + } + } + + public static void main(String[] args) { + C<@Nullable Issue5075NPE> c = new C<>(); + c.useBoxer(c.new Boxer(null)); + } } diff --git a/checker/tests/nullness/Issue5075a.java b/checker/tests/nullness/Issue5075a.java index 42d4dd137d3..0f5a942cc57 100644 --- a/checker/tests/nullness/Issue5075a.java +++ b/checker/tests/nullness/Issue5075a.java @@ -1,31 +1,31 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue5075a { - class AExpl { - I i1() { - // Type arguments aren't inferred correctly, test with #979 - // :: error: (return.type.incompatible) :: error: (argument.type.incompatible) - return new BExpl<>(this); - } - - I i2() { - return new BExpl(this); - } + class AExpl { + I i1() { + // Type arguments aren't inferred correctly, test with #979 + // :: error: (return.type.incompatible) :: error: (argument.type.incompatible) + return new BExpl<>(this); } - class BExpl implements I { - BExpl(AExpl a) {} + I i2() { + return new BExpl(this); } + } - class AImpl { - I i() { - return new BImpl<>(this); - } - } + class BExpl implements I { + BExpl(AExpl a) {} + } - class BImpl implements I { - BImpl(AImpl a) {} + class AImpl { + I i() { + return new BImpl<>(this); } + } + + class BImpl implements I { + BImpl(AImpl a) {} + } - interface I {} + interface I {} } diff --git a/checker/tests/nullness/Issue5075b.java b/checker/tests/nullness/Issue5075b.java index 021128efaa0..97d7b8f8674 100644 --- a/checker/tests/nullness/Issue5075b.java +++ b/checker/tests/nullness/Issue5075b.java @@ -1,33 +1,33 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue5075b { - static class CExpl { - I c(N n) { - return h(n.i()); - } + static class CExpl { + I c(N n) { + return h(n.i()); + } - abstract class N { - abstract I i(); - } + abstract class N { + abstract I i(); + } - static I h(I i) { - return i; - } + static I h(I i) { + return i; } + } - static class CImpl { - I c(N n) { - return h(n.i()); - } + static class CImpl { + I c(N n) { + return h(n.i()); + } - abstract class N { - abstract I i(); - } + abstract class N { + abstract I i(); + } - static I h(I i) { - return i; - } + static I h(I i) { + return i; } + } - interface I {} + interface I {} } diff --git a/checker/tests/nullness/Issue5189a.java b/checker/tests/nullness/Issue5189a.java index 5e37c9523f1..95de7f0ca65 100644 --- a/checker/tests/nullness/Issue5189a.java +++ b/checker/tests/nullness/Issue5189a.java @@ -1,5 +1,5 @@ enum Issue5189a { - EMPTY() {}; + EMPTY() {}; - Issue5189a(String... args) {} + Issue5189a(String... args) {} } diff --git a/checker/tests/nullness/Issue5189b.java b/checker/tests/nullness/Issue5189b.java index e82058012c8..ca2a3b2a78e 100644 --- a/checker/tests/nullness/Issue5189b.java +++ b/checker/tests/nullness/Issue5189b.java @@ -1,9 +1,9 @@ class Issue5189b { - class Inner { - Inner(String... args) {} - } + class Inner { + Inner(String... args) {} + } - void foo() { - Object o = new Inner() {}; - } + void foo() { + Object o = new Inner() {}; + } } diff --git a/checker/tests/nullness/Issue520.java b/checker/tests/nullness/Issue520.java index 9d3da80a214..26d0cfaf45d 100644 --- a/checker/tests/nullness/Issue520.java +++ b/checker/tests/nullness/Issue520.java @@ -5,40 +5,38 @@ // compile with: $CHECKERFRAMEWORK/checker/bin/javac -g Issue520.java -processor nullness // -AprintAllQualifiers -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class Issue520 {} abstract class Parent { - protected final List list; + protected final List list; - public Parent(List list) { - this.list = list; - } + public Parent(List list) { + this.list = list; + } } abstract class Child extends Parent { - public Child(List list) { - super(list); - } + public Child(List list) { + super(list); + } - public void add(CharSequence seq) { - list.add(seq); - } + public void add(CharSequence seq) { + list.add(seq); + } } class WildCardAdd { - List<@UnknownKeyFor ? super @KeyForBottom CharSequence> wildCardList = - new ArrayList<@KeyForBottom CharSequence>(); - - void foo( - List<@KeyFor("m") CharSequence> keyForMCharSeq, - @UnknownKeyFor CharSequence unknownCharSeq) { - wildCardList = keyForMCharSeq; - wildCardList.add(unknownCharSeq); - @KeyFor("y") Object o = wildCardList.get(0); - } + List<@UnknownKeyFor ? super @KeyForBottom CharSequence> wildCardList = + new ArrayList<@KeyForBottom CharSequence>(); + + void foo( + List<@KeyFor("m") CharSequence> keyForMCharSeq, @UnknownKeyFor CharSequence unknownCharSeq) { + wildCardList = keyForMCharSeq; + wildCardList.add(unknownCharSeq); + @KeyFor("y") Object o = wildCardList.get(0); + } } diff --git a/checker/tests/nullness/Issue5245.java b/checker/tests/nullness/Issue5245.java index c0d469ea1f4..a0d02a81c83 100644 --- a/checker/tests/nullness/Issue5245.java +++ b/checker/tests/nullness/Issue5245.java @@ -3,7 +3,7 @@ import java.util.List; class Issue5245 { - final Issue5245> repro = new Issue5245<>(List.of()); + final Issue5245> repro = new Issue5245<>(List.of()); - Issue5245(V unknownObj) {} + Issue5245(V unknownObj) {} } diff --git a/checker/tests/nullness/Issue531.java b/checker/tests/nullness/Issue531.java index 16e35a5b051..2aab9d9d054 100644 --- a/checker/tests/nullness/Issue531.java +++ b/checker/tests/nullness/Issue531.java @@ -5,15 +5,15 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue531 { - public MyList test(MyStream stream) { - return stream.collect(toList()); - } + public MyList test(MyStream stream) { + return stream.collect(toList()); + } - void foo(MyStream stream) {} + void foo(MyStream stream) {} - static MyCollector> toList() { - return new MyCollector<>(); - } + static MyCollector> toList() { + return new MyCollector<>(); + } } class MyList {} @@ -21,5 +21,5 @@ class MyList {} class MyCollector {} abstract class MyStream { - public abstract R collect(MyCollector c); + public abstract R collect(MyCollector c); } diff --git a/checker/tests/nullness/Issue554.java b/checker/tests/nullness/Issue554.java index 05d27d1118b..e81a741f567 100644 --- a/checker/tests/nullness/Issue554.java +++ b/checker/tests/nullness/Issue554.java @@ -5,65 +5,65 @@ import org.checkerframework.checker.nullness.qual.*; class MonotonicNonNullConstructorTest1 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest1(final Data data) { - this.data = data; - this.object = data.field; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest1(final Data data) { + this.data = data; + this.object = data.field; + } } class MonotonicNonNullConstructorTest2 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest2(final Data data) { - // reverse the assignments - this.object = data.field; - this.data = data; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest2(final Data data) { + // reverse the assignments + this.object = data.field; + this.data = data; + } } class MonotonicNonNullConstructorTest3 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest3(final Data dataParam) { - // use a parameter name that does not shadow the field - this.data = dataParam; - this.object = dataParam.field; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest3(final Data dataParam) { + // use a parameter name that does not shadow the field + this.data = dataParam; + this.object = dataParam.field; + } } class MonotonicNonNullConstructorTest4 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest4(final Data dataParam) { - // use a parameter name that does not shadow the field - // and reverse the assignments - this.object = dataParam.field; - this.data = dataParam; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest4(final Data dataParam) { + // use a parameter name that does not shadow the field + // and reverse the assignments + this.object = dataParam.field; + this.data = dataParam; + } } diff --git a/checker/tests/nullness/Issue563.java b/checker/tests/nullness/Issue563.java index ab48ea41f83..238c37803c6 100644 --- a/checker/tests/nullness/Issue563.java +++ b/checker/tests/nullness/Issue563.java @@ -1,10 +1,10 @@ // Test case for Issue 563: // https://github.com/typetools/checker-framework/issues/563 public class Issue563 { - void bar() { - Object x = null; - if (Object.class.isInstance(x)) { - x.toString(); - } + void bar() { + Object x = null; + if (Object.class.isInstance(x)) { + x.toString(); } + } } diff --git a/checker/tests/nullness/Issue577.java b/checker/tests/nullness/Issue577.java index 5d0057cdaf8..59af2bbac44 100644 --- a/checker/tests/nullness/Issue577.java +++ b/checker/tests/nullness/Issue577.java @@ -5,69 +5,69 @@ import org.checkerframework.checker.nullness.qual.*; class Banana extends Apple { - @Override - void fooOuter(int[] array) {} + @Override + void fooOuter(int[] array) {} - class InnerBanana extends InnerApple { - @Override - // :: error: (override.param.invalid) - void foo(int[] array, long[] array2, F2 param3) {} - } + class InnerBanana extends InnerApple { + @Override + // :: error: (override.param.invalid) + void foo(int[] array, long[] array2, F2 param3) {} + } } class Apple { - void fooOuter(T param) {} + void fooOuter(T param) {} - class InnerApple { - void foo(T param, E param2, F param3) {} - } + class InnerApple { + void foo(T param, E param2, F param3) {} + } } class Pineapple extends Apple { - @Override - void fooOuter(E array) {} + @Override + void fooOuter(E array) {} - class InnerPineapple extends InnerApple<@Nullable String> { - @Override - // :: error: (override.param.invalid) - void foo(E array, String array2, F3 param3) {} - } + class InnerPineapple extends InnerApple<@Nullable String> { + @Override + // :: error: (override.param.invalid) + void foo(E array, String array2, F3 param3) {} + } } class IntersectionAsMemberOf { - interface MyGenericInterface { - F getF(); - } + interface MyGenericInterface { + F getF(); + } - > void foo(T param) { - @NonNull String s = param.getF(); - } + > void foo(T param) { + @NonNull String s = param.getF(); + } } class UnionAsMemberOf { - interface MyInterface { - T getT(); - } + interface MyInterface { + T getT(); + } - class MyExceptionA extends Throwable implements Cloneable, MyInterface<@NonNull String> { - public String getT() { - return "t"; - } + class MyExceptionA extends Throwable implements Cloneable, MyInterface<@NonNull String> { + public String getT() { + return "t"; } + } - class MyExceptionB extends Throwable implements Cloneable, MyInterface { - public String getT() { - return "t"; - } + class MyExceptionB extends Throwable implements Cloneable, MyInterface { + public String getT() { + return "t"; } + } - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - @NonNull String s = ex1.getT(); - } + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + @NonNull String s = ex1.getT(); } + } } diff --git a/checker/tests/nullness/Issue578.java b/checker/tests/nullness/Issue578.java index a80d4af3972..13228aefcf5 100644 --- a/checker/tests/nullness/Issue578.java +++ b/checker/tests/nullness/Issue578.java @@ -1,16 +1,16 @@ // Test case for issue #578: https://github.com/typetools/checker-framework/issues/578 public class Issue578 { - void eval(Helper helper, Interface anInterface) { - Object o = new SomeGenericClass<>(helper.helperMethod(anInterface)); - } + void eval(Helper helper, Interface anInterface) { + Object o = new SomeGenericClass<>(helper.helperMethod(anInterface)); + } } abstract class Helper { - abstract Interface helperMethod(Interface anInterface); + abstract Interface helperMethod(Interface anInterface); } interface Interface {} final class SomeGenericClass { - SomeGenericClass(Interface s) {} + SomeGenericClass(Interface s) {} } diff --git a/checker/tests/nullness/Issue579Error.java b/checker/tests/nullness/Issue579Error.java index 55f6e35b427..eeebdcf1051 100644 --- a/checker/tests/nullness/Issue579Error.java +++ b/checker/tests/nullness/Issue579Error.java @@ -5,12 +5,12 @@ public class Issue579Error { - public void foo(Generic real, Generic other, boolean flag) { - // :: error: (type.argument.type.incompatible) - bar(flag ? real : other); - } + public void foo(Generic real, Generic other, boolean flag) { + // :: error: (type.argument.type.incompatible) + bar(flag ? real : other); + } - <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} + <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} - interface Generic {} + interface Generic {} } diff --git a/checker/tests/nullness/Issue580.java b/checker/tests/nullness/Issue580.java index 983f62f29ed..b24c441ce40 100644 --- a/checker/tests/nullness/Issue580.java +++ b/checker/tests/nullness/Issue580.java @@ -1,9 +1,9 @@ // Test case for issue #580: https://github.com/typetools/checker-framework/issues/580 abstract class InitCheckAssertionFailure { - public static > void noneOf(F[] array) { - Enum[] universe = array; - // Accessing universe on this line causes the error. - int len = universe.length; - } + public static > void noneOf(F[] array) { + Enum[] universe = array; + // Accessing universe on this line causes the error. + int len = universe.length; + } } diff --git a/checker/tests/nullness/Issue602.java b/checker/tests/nullness/Issue602.java index 1e9fcffb359..cf4b9c7a6f9 100644 --- a/checker/tests/nullness/Issue602.java +++ b/checker/tests/nullness/Issue602.java @@ -5,19 +5,19 @@ // https://github.com/typetools/checker-framework/issues/602 // @skip-test public class Issue602 { - @PolyNull String id(@PolyNull String o) { - return o; - } + @PolyNull String id(@PolyNull String o) { + return o; + } - void loop(boolean condition) { - @NonNull String notNull = "hello"; - String nullable = ""; - while (condition) { - // :: error: (assignment.type.incompatible) - notNull = nullable; - // :: error: (assignment.type.incompatible) - notNull = id(nullable); - nullable = null; - } + void loop(boolean condition) { + @NonNull String notNull = "hello"; + String nullable = ""; + while (condition) { + // :: error: (assignment.type.incompatible) + notNull = nullable; + // :: error: (assignment.type.incompatible) + notNull = id(nullable); + nullable = null; } + } } diff --git a/checker/tests/nullness/Issue6260.java b/checker/tests/nullness/Issue6260.java index a175ab9831c..34cc607de31 100644 --- a/checker/tests/nullness/Issue6260.java +++ b/checker/tests/nullness/Issue6260.java @@ -1,21 +1,21 @@ // A similar test is in // framework/tests/all-systems/EnumSwitch.java public class Issue6260 { - enum MyE { - FOO; + enum MyE { + FOO; - MyE getIt() { - return FOO; - } + MyE getIt() { + return FOO; + } - String go() { - MyE e = getIt(); - switch (e) { - case FOO: - return "foo"; - } - // This is not dead code! - throw new AssertionError(e); - } + String go() { + MyE e = getIt(); + switch (e) { + case FOO: + return "foo"; + } + // This is not dead code! + throw new AssertionError(e); } + } } diff --git a/checker/tests/nullness/Issue653.java b/checker/tests/nullness/Issue653.java index 84ed2694ad0..651eddcbfbe 100644 --- a/checker/tests/nullness/Issue653.java +++ b/checker/tests/nullness/Issue653.java @@ -5,33 +5,33 @@ public class Issue653 { - public static @PolyNull String[] concat( - @PolyNull String @Nullable [] a, @PolyNull String @Nullable [] b) { - if (a == null) { - if (b == null) { - return new String[0]; - } else { - return b; - } - } else { - if (b == null) { - return a; - } else { - @PolyNull String[] result = new String[a.length + b.length]; + public static @PolyNull String[] concat( + @PolyNull String @Nullable [] a, @PolyNull String @Nullable [] b) { + if (a == null) { + if (b == null) { + return new String[0]; + } else { + return b; + } + } else { + if (b == null) { + return a; + } else { + @PolyNull String[] result = new String[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } - } + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } } + } - public static String[] debugTrackPpt = {}; + public static String[] debugTrackPpt = {}; - public static void add_track(String ppt) { - String[] newArray = new String[] {ppt}; - debugTrackPpt = concat(debugTrackPpt, newArray); + public static void add_track(String ppt) { + String[] newArray = new String[] {ppt}; + debugTrackPpt = concat(debugTrackPpt, newArray); - debugTrackPpt = concat(debugTrackPpt, new String[] {ppt}); - } + debugTrackPpt = concat(debugTrackPpt, new String[] {ppt}); + } } diff --git a/checker/tests/nullness/Issue67.java b/checker/tests/nullness/Issue67.java index e2f6db08199..804137c743a 100644 --- a/checker/tests/nullness/Issue67.java +++ b/checker/tests/nullness/Issue67.java @@ -5,17 +5,17 @@ import java.util.Map; public class Issue67 { - private static final String KEY = "key"; - private static final String KEY2 = "key2"; + private static final String KEY = "key"; + private static final String KEY2 = "key2"; - void test() { - Map map = new HashMap<>(); - if (map.containsKey(KEY)) { - map.get(KEY).toString(); // no problem - } - // :: warning: (nulltest.redundant) - if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { // error - // do nothing - } + void test() { + Map map = new HashMap<>(); + if (map.containsKey(KEY)) { + map.get(KEY).toString(); // no problem } + // :: warning: (nulltest.redundant) + if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { // error + // do nothing + } + } } diff --git a/checker/tests/nullness/Issue672.java b/checker/tests/nullness/Issue672.java index 79c7d7c7d81..343cb344704 100644 --- a/checker/tests/nullness/Issue672.java +++ b/checker/tests/nullness/Issue672.java @@ -2,21 +2,21 @@ // https://github.com/typetools/checker-framework/issues/672 final class Issue672 extends Throwable { - final Throwable ex; + final Throwable ex; - Issue672(Throwable x) { - ex = x; - } + Issue672(Throwable x) { + ex = x; + } - static Issue672 test1(Throwable x, boolean flag) { - return new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x)))); - } + static Issue672 test1(Throwable x, boolean flag) { + return new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x)))); + } - static Issue672 test2(Throwable x, boolean flag) { - return (new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x))))); - } + static Issue672 test2(Throwable x, boolean flag) { + return (new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x))))); + } - static Issue672 test3(Throwable x) { - return test1(x instanceof Exception ? x : new Issue672(x), false); - } + static Issue672 test3(Throwable x) { + return test1(x instanceof Exception ? x : new Issue672(x), false); + } } diff --git a/checker/tests/nullness/Issue679.java b/checker/tests/nullness/Issue679.java index 15d794cb1a2..a6c182feadc 100644 --- a/checker/tests/nullness/Issue679.java +++ b/checker/tests/nullness/Issue679.java @@ -5,11 +5,11 @@ // https://github.com/typetools/checker-framework/issues/679 // @skip-test public class Issue679 { - interface Interface {} + interface Interface {} - class B implements Interface<@NonNull Number> {} + class B implements Interface<@NonNull Number> {} - // :: error: Interface cannot be inherited with different arguments: <@NonNull Number> and - // <@Nullable Number> - class A extends B implements Interface<@Nullable Number> {} + // :: error: Interface cannot be inherited with different arguments: <@NonNull Number> and + // <@Nullable Number> + class A extends B implements Interface<@Nullable Number> {} } diff --git a/checker/tests/nullness/Issue738.java b/checker/tests/nullness/Issue738.java index 1d8661e38a3..1e94be90366 100644 --- a/checker/tests/nullness/Issue738.java +++ b/checker/tests/nullness/Issue738.java @@ -5,35 +5,35 @@ // https://github.com/typetools/checker-framework/issues/738 // Also, see framework/tests/all-systems/Issue738.java public class Issue738 { - void methodA(int[] is, Object @Nullable [] os, int i) { - // The type argument to methodB* for each call below is Cloneable & Serializable + void methodA(int[] is, Object @Nullable [] os, int i) { + // The type argument to methodB* for each call below is Cloneable & Serializable - // NullnessTransfer changes the type of an argument that is assigned to a @NonNull parameter - // to @NonNull. Use a switch statement to prevent this. - switch (i) { - case 1: - methodB(is, os); - break; - case 2: - // :: error: (argument.type.incompatible) - methodB2(is, os); - break; - case 3: - // :: error: (type.argument.type.incompatible) - methodB3(is, os); - break; - case 4: - // :: error: (type.argument.type.incompatible) - methodB4(is, os); - break; - } + // NullnessTransfer changes the type of an argument that is assigned to a @NonNull parameter + // to @NonNull. Use a switch statement to prevent this. + switch (i) { + case 1: + methodB(is, os); + break; + case 2: + // :: error: (argument.type.incompatible) + methodB2(is, os); + break; + case 3: + // :: error: (type.argument.type.incompatible) + methodB3(is, os); + break; + case 4: + // :: error: (type.argument.type.incompatible) + methodB4(is, os); + break; } + } - void methodB(T paramA, T paramB) {} + void methodB(T paramA, T paramB) {} - void methodB2(T paramA, @NonNull T paramB) {} + void methodB2(T paramA, @NonNull T paramB) {} - <@NonNull T extends @NonNull Object> void methodB3(T paramA, T paramB) {} + <@NonNull T extends @NonNull Object> void methodB3(T paramA, T paramB) {} - void methodB4(T paramA, T paramB) {} + void methodB4(T paramA, T paramB) {} } diff --git a/checker/tests/nullness/Issue741.java b/checker/tests/nullness/Issue741.java index 3e2fb4fd34a..2661c6de0fe 100644 --- a/checker/tests/nullness/Issue741.java +++ b/checker/tests/nullness/Issue741.java @@ -2,18 +2,18 @@ // https://github.com/typetools/checker-framework/pull/741 // @skip-test public class Issue741 { - @SuppressWarnings("unchecked") - public T incompatibleTypes(Object o) { - final T x = (T) o; - if (x != null) {} - // invaild error here - return x; - } + @SuppressWarnings("unchecked") + public T incompatibleTypes(Object o) { + final T x = (T) o; + if (x != null) {} + // invaild error here + return x; + } - @SuppressWarnings("unchecked") - public T noIncompatibleTypes(Object o) { - final T x = (T) o; - // no error here - return x; - } + @SuppressWarnings("unchecked") + public T noIncompatibleTypes(Object o) { + final T x = (T) o; + // no error here + return x; + } } diff --git a/checker/tests/nullness/Issue752.java b/checker/tests/nullness/Issue752.java index c78cdb06ca2..6ad259e75b5 100644 --- a/checker/tests/nullness/Issue752.java +++ b/checker/tests/nullness/Issue752.java @@ -4,67 +4,67 @@ public class Issue752 { - Issue752 field = new Issue752(); - static Issue752 staticField = new Issue752(); + Issue752 field = new Issue752(); + static Issue752 staticField = new Issue752(); - Issue752 method() { - return field; - } + Issue752 method() { + return field; + } - static Issue752 staticMethod() { - return staticField; - } + static Issue752 staticMethod() { + return staticField; + } - // A package name without a class name is not a valid JavaExpression string. - @RequiresNonNull("java.lang") - // :: error: (flowexpr.parse.error) - void method1() {} + // A package name without a class name is not a valid JavaExpression string. + @RequiresNonNull("java.lang") + // :: error: (flowexpr.parse.error) + void method1() {} - @RequiresNonNull("java.lang.String.class") - void method2() {} + @RequiresNonNull("java.lang.String.class") + void method2() {} - // A package name without a class name is not a valid JavaExpression string. - @RequiresNonNull("a.b.c") - // :: error: (flowexpr.parse.error) - void method3() {} + // A package name without a class name is not a valid JavaExpression string. + @RequiresNonNull("a.b.c") + // :: error: (flowexpr.parse.error) + void method3() {} - // notaclass does not exist. - @RequiresNonNull("a.b.c.notaclass") - // :: error: (flowexpr.parse.error) - void method4() {} + // notaclass does not exist. + @RequiresNonNull("a.b.c.notaclass") + // :: error: (flowexpr.parse.error) + void method4() {} - @RequiresNonNull("a.b.c.Issue752.class") - void method5() {} + @RequiresNonNull("a.b.c.Issue752.class") + void method5() {} - @RequiresNonNull("a.b.c.Issue752.staticField") - void method6() {} + @RequiresNonNull("a.b.c.Issue752.staticField") + void method6() {} - @RequiresNonNull("a.b.c.Issue752.staticField.field") - void method7() {} + @RequiresNonNull("a.b.c.Issue752.staticField.field") + void method7() {} - // field is an instance field, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.field") - // :: error: (flowexpr.parse.error) - void method8() {} + // field is an instance field, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.field") + // :: error: (flowexpr.parse.error) + void method8() {} - // field is an instance field, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.field.field") - // :: error: (flowexpr.parse.error) - void method9() {} + // field is an instance field, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.field.field") + // :: error: (flowexpr.parse.error) + void method9() {} - @RequiresNonNull("a.b.c.Issue752.staticMethod()") - void method10() {} + @RequiresNonNull("a.b.c.Issue752.staticMethod()") + void method10() {} - @RequiresNonNull("a.b.c.Issue752.staticMethod().field") - void method11() {} + @RequiresNonNull("a.b.c.Issue752.staticMethod().field") + void method11() {} - // method() is an instance method, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.method()") - // :: error: (flowexpr.parse.error) - void method12() {} + // method() is an instance method, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.method()") + // :: error: (flowexpr.parse.error) + void method12() {} - // method() is an instance method, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.method().field") - // :: error: (flowexpr.parse.error) - void method13() {} + // method() is an instance method, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.method().field") + // :: error: (flowexpr.parse.error) + void method13() {} } diff --git a/checker/tests/nullness/Issue759.java b/checker/tests/nullness/Issue759.java index 2df06be2b4a..291e77a6f4b 100644 --- a/checker/tests/nullness/Issue759.java +++ b/checker/tests/nullness/Issue759.java @@ -6,46 +6,46 @@ @SuppressWarnings("unchecked") public class Issue759 { - void possibleValues(final Class enumType) { - lowercase(enumType.getEnumConstants()); - lowercase2(enumType.getEnumConstants()); - lowercase3(enumType.getEnumConstants()); - } + void possibleValues(final Class enumType) { + lowercase(enumType.getEnumConstants()); + lowercase2(enumType.getEnumConstants()); + lowercase3(enumType.getEnumConstants()); + } - > void lowercase(final T @Nullable ... items) {} + > void lowercase(final T @Nullable ... items) {} - > void lowercase2(final T @Nullable [] items) {} + > void lowercase2(final T @Nullable [] items) {} - void lowercase3(final T items) {} + void lowercase3(final T items) {} } interface Gen> { - T[] getConstants(); + T[] getConstants(); - T @Nullable [] getNullableConstants(); + T @Nullable [] getNullableConstants(); } class IncompatibleTypes { - void possibleValues(final Gen genType) { - lowercase(genType.getConstants()); - lowercase(genType.getNullableConstants()); - } + void possibleValues(final Gen genType) { + lowercase(genType.getConstants()); + lowercase(genType.getNullableConstants()); + } - void lowercase(final S items) {} + void lowercase(final S items) {} - void possibleValues2(final Gen genType) { - lowercase2(genType.getConstants()); - // :: error: (type.argument.type.incompatible) - lowercase2(genType.getNullableConstants()); - } + void possibleValues2(final Gen genType) { + lowercase2(genType.getConstants()); + // :: error: (type.argument.type.incompatible) + lowercase2(genType.getNullableConstants()); + } - void lowercase2(final S items) {} + void lowercase2(final S items) {} - void possibleValues3(final Gen genType) { - lowercase3(genType.getConstants()); - // :: error: (argument.type.incompatible) - lowercase3(genType.getNullableConstants()); - } + void possibleValues3(final Gen genType) { + lowercase3(genType.getConstants()); + // :: error: (argument.type.incompatible) + lowercase3(genType.getNullableConstants()); + } - void lowercase3(final @NonNull S items) {} + void lowercase3(final @NonNull S items) {} } diff --git a/checker/tests/nullness/Issue764.java b/checker/tests/nullness/Issue764.java index 0d1eb660032..e33bdc9b077 100644 --- a/checker/tests/nullness/Issue764.java +++ b/checker/tests/nullness/Issue764.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue764 { - public static @Nullable Object field = null; + public static @Nullable Object field = null; - static class MyClass { - @RequiresNonNull("field") - public static void method() {} + static class MyClass { + @RequiresNonNull("field") + public static void method() {} - public void otherMethod() { - field = new Object(); - method(); - } + public void otherMethod() { + field = new Object(); + method(); + } - public void otherMethod2() { - // :: error: (contracts.precondition.not.satisfied) - method(); - } + public void otherMethod2() { + // :: error: (contracts.precondition.not.satisfied) + method(); } + } } diff --git a/checker/tests/nullness/Issue765.java b/checker/tests/nullness/Issue765.java index ae47273e0ca..10d54fdadc5 100644 --- a/checker/tests/nullness/Issue765.java +++ b/checker/tests/nullness/Issue765.java @@ -1,14 +1,14 @@ // Test case for Issue 765 // https://github.com/typetools/checker-framework/issues/765 public class Issue765 { - Thread thread = new Thread() {}; + Thread thread = new Thread() {}; - void execute() { - thread = - new Thread() { - @Override - public void run() {} - }; - thread.start(); - } + void execute() { + thread = + new Thread() { + @Override + public void run() {} + }; + thread.start(); + } } diff --git a/checker/tests/nullness/Issue811.java b/checker/tests/nullness/Issue811.java index 24a175bfc61..976981a7c95 100644 --- a/checker/tests/nullness/Issue811.java +++ b/checker/tests/nullness/Issue811.java @@ -4,25 +4,25 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class Issue811 { - static class T { - void xyz() {} - } + static class T { + void xyz() {} + } - interface U { - void method(); - } + interface U { + void method(); + } - private final @NonNull T tField; - private U uField; + private final @NonNull T tField; + private U uField; - public Issue811(@NonNull T t) { - tField = t; - uField = - new U() { - @Override - public void method() { - tField.xyz(); - } - }; - } + public Issue811(@NonNull T t) { + tField = t; + uField = + new U() { + @Override + public void method() { + tField.xyz(); + } + }; + } } diff --git a/checker/tests/nullness/Issue829.java b/checker/tests/nullness/Issue829.java index e5b196536de..09e47ac6e22 100644 --- a/checker/tests/nullness/Issue829.java +++ b/checker/tests/nullness/Issue829.java @@ -4,12 +4,12 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue829 { - public static @Nullable Double getDouble(boolean flag) { - return flag ? null : 1.0; - } + public static @Nullable Double getDouble(boolean flag) { + return flag ? null : 1.0; + } - public static Double getDoubleError(boolean flag) { - // :: error: (return.type.incompatible) - return flag ? null : 1.0; - } + public static Double getDoubleError(boolean flag) { + // :: error: (return.type.incompatible) + return flag ? null : 1.0; + } } diff --git a/checker/tests/nullness/Issue868.java b/checker/tests/nullness/Issue868.java index 0a2f9960082..6d2aa61766f 100644 --- a/checker/tests/nullness/Issue868.java +++ b/checker/tests/nullness/Issue868.java @@ -5,55 +5,55 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue868 { - interface MyList {} - - void test1(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void test2(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void test3(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void test4(E e) { - e.toString(); - } - - // :: warning: (explicit.annotation.ignored) - void test5(E e) { - e.toString(); - } - - // :: warning: (explicit.annotation.ignored) - void test6(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void use() { - this.<@Nullable MyList>test1(null); - this.<@Nullable MyList>test2(null); - this.<@Nullable MyList>test3(null); - // :: error: (type.argument.type.incompatible) - this.<@Nullable MyList>test4(null); - // :: error: (type.argument.type.incompatible) - this.<@Nullable MyList>test5(null); - this.<@Nullable MyList>test6(null); - } - - void use2(T t, @NonNull T nonNullT) { - this.test1(t); - // :: error: (argument.type.incompatible) - this.<@NonNull T>test3(t); - this.<@NonNull T>test3(nonNullT); - // :: error: (type.argument.type.incompatible) - this.test5(t); - } + interface MyList {} + + void test1(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void test2(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void test3(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void test4(E e) { + e.toString(); + } + + // :: warning: (explicit.annotation.ignored) + void test5(E e) { + e.toString(); + } + + // :: warning: (explicit.annotation.ignored) + void test6(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void use() { + this.<@Nullable MyList>test1(null); + this.<@Nullable MyList>test2(null); + this.<@Nullable MyList>test3(null); + // :: error: (type.argument.type.incompatible) + this.<@Nullable MyList>test4(null); + // :: error: (type.argument.type.incompatible) + this.<@Nullable MyList>test5(null); + this.<@Nullable MyList>test6(null); + } + + void use2(T t, @NonNull T nonNullT) { + this.test1(t); + // :: error: (argument.type.incompatible) + this.<@NonNull T>test3(t); + this.<@NonNull T>test3(nonNullT); + // :: error: (type.argument.type.incompatible) + this.test5(t); + } } diff --git a/checker/tests/nullness/Issue906.java b/checker/tests/nullness/Issue906.java index 71a22872d0b..d55ad077714 100644 --- a/checker/tests/nullness/Issue906.java +++ b/checker/tests/nullness/Issue906.java @@ -4,12 +4,12 @@ * @author Michael Grafl */ public class Issue906 { - @SuppressWarnings("unchecked") - public void start(A a, Class cb) { - // :: error: (dereference.of.nullable) - Class c = (Class) a.getClass(); - x(a, c); - } + @SuppressWarnings("unchecked") + public void start(A a, Class cb) { + // :: error: (dereference.of.nullable) + Class c = (Class) a.getClass(); + x(a, c); + } - private void x(B a, Class c) {} + private void x(B a, Class c) {} } diff --git a/checker/tests/nullness/Issue961.java b/checker/tests/nullness/Issue961.java index e9f5e621e92..19cf230f4f6 100644 --- a/checker/tests/nullness/Issue961.java +++ b/checker/tests/nullness/Issue961.java @@ -1,49 +1,48 @@ -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.NonNull; // Test case for Issue 961 // https://github.com/typetools/checker-framework/issues/961 public class Issue961 { - T method(T param, Map map) { - if (map.containsKey(param)) { - @NonNull Object o = map.get(param); - return param; - } - return param; + T method(T param, Map map) { + if (map.containsKey(param)) { + @NonNull Object o = map.get(param); + return param; } + return param; + } - abstract class MapContains { - // this isn't initialized, but just ignore the error. - @SuppressWarnings("method.invocation.invalid") - V def = setDef(); + abstract class MapContains { + // this isn't initialized, but just ignore the error. + @SuppressWarnings("method.invocation.invalid") + V def = setDef(); - Map map = new HashMap<>(); + Map map = new HashMap<>(); - V get(K p) { - if (!map.containsKey(p)) { - return def; - } - return map.get(p); - } - - abstract V setDef(); + V get(K p) { + if (!map.containsKey(p)) { + return def; + } + return map.get(p); } - class MapContains2 { - String get1(Map map, Object k) { - if (!map.containsKey(k)) { - return ""; - } - return map.get(k); - } + abstract V setDef(); + } + + class MapContains2 { + String get1(Map map, Object k) { + if (!map.containsKey(k)) { + return ""; + } + return map.get(k); + } - String get2(Map map, KeyTV k) { - if (!map.containsKey(k)) { - return ""; - } - return map.get(k); - } + String get2(Map map, KeyTV k) { + if (!map.containsKey(k)) { + return ""; + } + return map.get(k); } + } } diff --git a/checker/tests/nullness/Issue986.java b/checker/tests/nullness/Issue986.java index f92956e6a2f..c5cf5164c55 100644 --- a/checker/tests/nullness/Issue986.java +++ b/checker/tests/nullness/Issue986.java @@ -5,31 +5,31 @@ public class Issue986 { - public static void main(String[] args) { - String array[] = new String[3]; - array[0].length(); // NPE here - } + public static void main(String[] args) { + String array[] = new String[3]; + array[0].length(); // NPE here + } - // Flow should refine @MonotonicNonNull component types to @NonNull. - void testArr4(@NonNull Object @NonNull [] nno1, @MonotonicNonNull Object @NonNull [] lnno1) { - @MonotonicNonNull Object[] lnno2; - @NonNull Object[] nno2; - nno2 = nno1; - lnno2 = lnno1; - lnno2 = nno1; - // :: error: (assignment.type.incompatible) - nno2 = lnno1; - lnno2 = NullnessUtil.castNonNullDeep(nno1); - nno2 = NullnessUtil.castNonNullDeep(lnno1); - lnno2 = NullnessUtil.castNonNullDeep(nno1); - nno2 = NullnessUtil.castNonNullDeep(lnno1); - } + // Flow should refine @MonotonicNonNull component types to @NonNull. + void testArr4(@NonNull Object @NonNull [] nno1, @MonotonicNonNull Object @NonNull [] lnno1) { + @MonotonicNonNull Object[] lnno2; + @NonNull Object[] nno2; + nno2 = nno1; + lnno2 = lnno1; + lnno2 = nno1; + // :: error: (assignment.type.incompatible) + nno2 = lnno1; + lnno2 = NullnessUtil.castNonNullDeep(nno1); + nno2 = NullnessUtil.castNonNullDeep(lnno1); + lnno2 = NullnessUtil.castNonNullDeep(nno1); + nno2 = NullnessUtil.castNonNullDeep(lnno1); + } - // Flow should refine @MonotonicNonNull component types to @NonNull. - // This is a prerequisite for issue #986 (or for workarounds to issue #986). - void testArr5(@MonotonicNonNull Object @NonNull [] a) { - @MonotonicNonNull Object[] l5 = NullnessUtil.castNonNullDeep(a); - @NonNull Object[] l6 = l5; - @NonNull Object[] l7 = NullnessUtil.castNonNullDeep(a); - } + // Flow should refine @MonotonicNonNull component types to @NonNull. + // This is a prerequisite for issue #986 (or for workarounds to issue #986). + void testArr5(@MonotonicNonNull Object @NonNull [] a) { + @MonotonicNonNull Object[] l5 = NullnessUtil.castNonNullDeep(a); + @NonNull Object[] l6 = l5; + @NonNull Object[] l7 = NullnessUtil.castNonNullDeep(a); + } } diff --git a/checker/tests/nullness/Issue989.java b/checker/tests/nullness/Issue989.java index 5f96a498a54..01f311792b3 100644 --- a/checker/tests/nullness/Issue989.java +++ b/checker/tests/nullness/Issue989.java @@ -1,11 +1,10 @@ // Test case for Issue 989: // https://github.com/typetools/checker-framework/issues/989 -import org.checkerframework.checker.nullness.qual.NonNull; - import java.io.Serializable; import java.util.Collection; import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; interface ListWrapper989a extends List<@NonNull E> {} @@ -14,15 +13,15 @@ interface ListWrapper989b extends Serializable, List<@NonNull E> {} interface ListWrapper989c extends Collection<@NonNull E>, Serializable, List<@NonNull E> {} public class Issue989 { - void usea(ListWrapper989a list) { - list.get(0); - } + void usea(ListWrapper989a list) { + list.get(0); + } - void useb(ListWrapper989b list) { - list.get(0); - } + void useb(ListWrapper989b list) { + list.get(0); + } - void usec(ListWrapper989c list) { - list.get(0); - } + void usec(ListWrapper989c list) { + list.get(0); + } } diff --git a/checker/tests/nullness/Iterate.java b/checker/tests/nullness/Iterate.java index d607d2a2018..7cbc6c1f8ba 100644 --- a/checker/tests/nullness/Iterate.java +++ b/checker/tests/nullness/Iterate.java @@ -3,9 +3,9 @@ package wildcards; public class Iterate { - void method(Iterable files) { - for (Object file : files) { - file.getClass(); - } + void method(Iterable files) { + for (Object file : files) { + file.getClass(); } + } } diff --git a/checker/tests/nullness/IteratorEarlyExit.java b/checker/tests/nullness/IteratorEarlyExit.java index 41f134027b9..44b30e59724 100644 --- a/checker/tests/nullness/IteratorEarlyExit.java +++ b/checker/tests/nullness/IteratorEarlyExit.java @@ -1,39 +1,38 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.io.*; import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class IteratorEarlyExit { - public static void m1() { - List array = new ArrayList<>(); - String local = null; - for (String str : array) { - local = str; - break; - } - // :: error: (dereference.of.nullable) - System.out.println(local.length()); + public static void m1() { + List array = new ArrayList<>(); + String local = null; + for (String str : array) { + local = str; + break; } + // :: error: (dereference.of.nullable) + System.out.println(local.length()); + } - public static void m2() { - List array = new ArrayList<>(); - String local = null; - for (String str : array) { - local = str; - } - // :: error: (dereference.of.nullable) - System.out.println(local.length()); + public static void m2() { + List array = new ArrayList<>(); + String local = null; + for (String str : array) { + local = str; } + // :: error: (dereference.of.nullable) + System.out.println(local.length()); + } - public static void m3() { - List array = new ArrayList<>(); - Object local = new Object(); - for (String str : array) { - // :: error: (dereference.of.nullable) - System.out.println(local.toString()); - // The next iteration might throw a NPE - local = null; - } + public static void m3() { + List array = new ArrayList<>(); + Object local = new Object(); + for (String str : array) { + // :: error: (dereference.of.nullable) + System.out.println(local.toString()); + // The next iteration might throw a NPE + local = null; } + } } diff --git a/checker/tests/nullness/JUnitNull.java b/checker/tests/nullness/JUnitNull.java index 37b3e32d556..5f060b4d53c 100644 --- a/checker/tests/nullness/JUnitNull.java +++ b/checker/tests/nullness/JUnitNull.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Assertions; class JUnitNull { - { - Assertions.assertEquals(null, "dummy"); - } + { + Assertions.assertEquals(null, "dummy"); + } } diff --git a/checker/tests/nullness/JavaCopExplosion.java b/checker/tests/nullness/JavaCopExplosion.java index fde4396cc7c..ba36faad208 100644 --- a/checker/tests/nullness/JavaCopExplosion.java +++ b/checker/tests/nullness/JavaCopExplosion.java @@ -1,124 +1,123 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; +import org.checkerframework.checker.nullness.qual.*; @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class JavaCopExplosion { - public static class ExplosiveException extends Exception {} + public static class ExplosiveException extends Exception {} - @NonNull Integer m_nni = 1; - final String m_astring; + @NonNull Integer m_nni = 1; + final String m_astring; - JavaCopExplosion() { - // m_nni = 1;\ - m_astring = "hi"; - try { - throw new RuntimeException(); - } catch (Exception e) { - System.out.println(m_astring.length()); - } - return; + JavaCopExplosion() { + // m_nni = 1;\ + m_astring = "hi"; + try { + throw new RuntimeException(); + } catch (Exception e) { + System.out.println(m_astring.length()); } + return; + } - static void main(String @NonNull [] args) { - @NonNull String s = "Dan"; - String s2; - s2 = null; - // :: warning: (nulltest.redundant) - if (s2 != null || s != null) { - // :: error: (assignment.type.incompatible) - s = s2; - } else { - s = new String("Levitan"); - } - s2 = args[0]; - // :: error: (dereference.of.nullable) - System.out.println("Possibly cause null pointer with this: " + s2.length()); - // :: warning: (nulltest.redundant) - if (s2 == null) { - // do nothing - } else { - System.out.println("Can't cause null pointer here: " + s2.length()); - s = s2; - } - // :: warning: (nulltest.redundant) - if (s == null ? s2 != null : s2 != null) { - s = s2; - } - System.out.println("Hello " + s); - System.out.println("Hello " + s.length()); - f(); + static void main(String @NonNull [] args) { + @NonNull String s = "Dan"; + String s2; + s2 = null; + // :: warning: (nulltest.redundant) + if (s2 != null || s != null) { + // :: error: (assignment.type.incompatible) + s = s2; + } else { + s = new String("Levitan"); + } + s2 = args[0]; + // :: error: (dereference.of.nullable) + System.out.println("Possibly cause null pointer with this: " + s2.length()); + // :: warning: (nulltest.redundant) + if (s2 == null) { + // do nothing + } else { + System.out.println("Can't cause null pointer here: " + s2.length()); + s = s2; + } + // :: warning: (nulltest.redundant) + if (s == null ? s2 != null : s2 != null) { + s = s2; } + System.out.println("Hello " + s); + System.out.println("Hello " + s.length()); + f(); + } - private static int f() { - while (true) { - try { - throw new ExplosiveException(); - } finally { - // break; - return 1; - // throw new RuntimeException(); - } - } + private static int f() { + while (true) { + try { + throw new ExplosiveException(); + } finally { + // break; + return 1; + // throw new RuntimeException(); + } } + } - public static int foo() { - final int v; - int x; - Integer z; - Integer y; - @NonNull Integer nnz = 3; - z = Integer.valueOf(5); - try { - x = 3; - x = 5; - // y = z; - nnz = z; - z = null; - // :: error: (assignment.type.incompatible) - nnz = z; + public static int foo() { + final int v; + int x; + Integer z; + Integer y; + @NonNull Integer nnz = 3; + z = Integer.valueOf(5); + try { + x = 3; + x = 5; + // y = z; + nnz = z; + z = null; + // :: error: (assignment.type.incompatible) + nnz = z; - while (z == null) { - break; - } - // :: error: (assignment.type.incompatible) - nnz = z; - while (z == null) { - // do nothing - } - nnz = z; - // v = 1; - return 1; - // v = 2; - // throw new RuntimeException (); - } catch (NullPointerException e) { - e.printStackTrace(); - // e = null; - // v = 1; - } catch (RuntimeException e) { - // nnz = z; - // v = 2; - } finally { - // v = 1 + x; - } - return 1; - // return v + x; + while (z == null) { + break; + } + // :: error: (assignment.type.incompatible) + nnz = z; + while (z == null) { + // do nothing + } + nnz = z; + // v = 1; + return 1; + // v = 2; + // throw new RuntimeException (); + } catch (NullPointerException e) { + e.printStackTrace(); + // e = null; + // v = 1; + } catch (RuntimeException e) { + // nnz = z; + // v = 2; + } finally { + // v = 1 + x; } + return 1; + // return v + x; + } - private void bar(List<@NonNull String> ss, String b, String c) { - @NonNull String a; - // :: error: (iterating.over.nullable) - for (@NonNull String s : ss) { - a = s; - } - if (b == null || b.length() == 0) { - System.out.println("hey"); - } - if (b != null) { - // :: error: (dereference.of.nullable) - for (; b.length() > 0; b = null) { - System.out.println(b.length()); - } - } + private void bar(List<@NonNull String> ss, String b, String c) { + @NonNull String a; + // :: error: (iterating.over.nullable) + for (@NonNull String s : ss) { + a = s; + } + if (b == null || b.length() == 0) { + System.out.println("hey"); + } + if (b != null) { + // :: error: (dereference.of.nullable) + for (; b.length() > 0; b = null) { + System.out.println(b.length()); + } } + } } diff --git a/checker/tests/nullness/JavaCopFlow.java b/checker/tests/nullness/JavaCopFlow.java index 959848862e0..2c327331f42 100644 --- a/checker/tests/nullness/JavaCopFlow.java +++ b/checker/tests/nullness/JavaCopFlow.java @@ -3,218 +3,218 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class JavaCopFlow { - public void testIf(String str) { + public void testIf(String str) { - // String str = "foo"; - @NonNull String a; - if (str != null) { - a = str; - } + // String str = "foo"; + @NonNull String a; + if (str != null) { + a = str; + } + + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + public void testIfNoBlock(String str) { + + // String str = "foo"; + @NonNull String a; + if (str != null) { + a = str; } - public void testIfNoBlock(String str) { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - // String str = "foo"; - @NonNull String a; - if (str != null) { - a = str; - } + public void testElse(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + @NonNull String a; + if (str == null) { + testAssert(""); + } else { + a = str; } - public void testElse(String str) { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - // String str = "foo"; - @NonNull String a; - if (str == null) { - testAssert(""); - } else { - a = str; - } + public void testElseNoBlock(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + @NonNull String a; + if (str == null) { + testAssert(""); + } else { + a = str; } - public void testElseNoBlock(String str) { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - // String str = "foo"; - @NonNull String a; - if (str == null) { - testAssert(""); - } else { - a = str; - } + public void testReturnIf(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + if (str == null) { + testAssert(""); + return; } - public void testReturnIf(String str) { + @NonNull String a = str; - // String str = "foo"; - if (str == null) { - testAssert(""); - return; - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testReturnElse(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + if (str != null) { + testAssert(""); + } else { + return; } - public void testReturnElse(String str) { + @NonNull String a = str; - // String str = "foo"; - if (str != null) { - testAssert(""); - } else { - return; - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testThrowIf(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + if (str == null) { + testAssert(""); + throw new RuntimeException("foo"); } - public void testThrowIf(String str) { + @NonNull String a = str; - // String str = "foo"; - if (str == null) { - testAssert(""); - throw new RuntimeException("foo"); - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testThrowElse(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + if (str != null) { + testAssert(""); + } else { + throw new RuntimeException("foo"); } - public void testThrowElse(String str) { + @NonNull String a = str; - // String str = "foo"; - if (str != null) { - testAssert(""); - } else { - throw new RuntimeException("foo"); - } + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testAssert(@Nullable String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + assert str != null : "@AssumeAssertion(nullness)"; - public void testAssert(@Nullable String str) { + @NonNull String a = str; - assert str != null : "@AssumeAssertion(nullness)"; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testWhile(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + while (str != null) { + @NonNull String a = str; + break; } - public void testWhile(String str) { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - // String str = "foo"; - while (str != null) { - @NonNull String a = str; - break; - } + public void testIfInstanceOf(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = "foo"; + @NonNull String a; + if (str instanceof String) { + a = str; } - public void testIfInstanceOf(String str) { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - // String str = "foo"; - @NonNull String a; - if (str instanceof String) { - a = str; - } + public void testNew() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + String str = "foo"; + @NonNull String a = str; - public void testNew() { + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - String str = "foo"; - @NonNull String a = str; + public void testExit(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + // String str = null; + if (str == null) { + System.exit(0); } - public void testExit(String str) { + @NonNull String a = str; + } + + void methodThatThrowsRuntime() { + throw new RuntimeException(); + } - // String str = null; - if (str == null) { - System.exit(0); - } + public void retestWhile(@Nullable String str) { - @NonNull String a = str; + while (str != null) { + @NonNull String a = str; + break; } - void methodThatThrowsRuntime() { - throw new RuntimeException(); + int i = 0; + while (true) { + // :: error: (assignment.type.incompatible) + @NonNull String a = str; + str = null; + i++; + if (i > 2) break; } - public void retestWhile(@Nullable String str) { - - while (str != null) { - @NonNull String a = str; - break; - } - - int i = 0; - while (true) { - // :: error: (assignment.type.incompatible) - @NonNull String a = str; - str = null; - i++; - if (i > 2) break; - } - - str = null; - @NonNull String b = "hi"; - try { - // :: error: (assignment.type.incompatible) - b = str; - methodThatThrowsRuntime(); - str = "bar"; - } finally { - // :: error: (assignment.type.incompatible) - b = str; - } - - str = null; - // :: error: (assignment.type.incompatible) - b = str; - - str = "hi"; - b = (String) str; + str = null; + @NonNull String b = "hi"; + try { + // :: error: (assignment.type.incompatible) + b = str; + methodThatThrowsRuntime(); + str = "bar"; + } finally { + // :: error: (assignment.type.incompatible) + b = str; } + + str = null; + // :: error: (assignment.type.incompatible) + b = str; + + str = "hi"; + b = (String) str; + } } diff --git a/checker/tests/nullness/JavaCopRandomTests.java b/checker/tests/nullness/JavaCopRandomTests.java index 8ecde2bdf51..185cc9ba463 100644 --- a/checker/tests/nullness/JavaCopRandomTests.java +++ b/checker/tests/nullness/JavaCopRandomTests.java @@ -1,27 +1,27 @@ import org.checkerframework.checker.nullness.qual.*; public class JavaCopRandomTests { - final int a; - final int b = 1; - final int c; + final int a; + final int b = 1; + final int c; - JavaCopRandomTests() { - String s = null; - a = 2; - } + JavaCopRandomTests() { + String s = null; + a = 2; + } - JavaCopRandomTests(String s) throws Exception { - // this(); - a = 2; - if (a > 1) { - throw new Exception("dude"); - } - throw new RuntimeException("dude"); + JavaCopRandomTests(String s) throws Exception { + // this(); + a = 2; + if (a > 1) { + throw new Exception("dude"); } + throw new RuntimeException("dude"); + } - // initializer block - { - c = 4; - // throw new Exception("dude"); - } + // initializer block + { + c = 4; + // throw new Exception("dude"); + } } diff --git a/checker/tests/nullness/JavaExprContext.java b/checker/tests/nullness/JavaExprContext.java index 28e5ceeafd6..5ec7b637964 100644 --- a/checker/tests/nullness/JavaExprContext.java +++ b/checker/tests/nullness/JavaExprContext.java @@ -1,7 +1,6 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; // See issue 241: https://github.com/typetools/checker-framework/issues/241 @@ -15,162 +14,153 @@ public class JavaExprContext { - // Classes to perform tests on + // Classes to perform tests on - // The methods return booleans instead of void simply so they can - // be tested as field initializers. + // The methods return booleans instead of void simply so they can + // be tested as field initializers. - public static class staticGraphClass { - private Map adjList = new HashMap<>(); + public static class staticGraphClass { + private Map adjList = new HashMap<>(); - public boolean addEdge(@KeyFor("adjList") String source) { - return true; - } + public boolean addEdge(@KeyFor("adjList") String source) { + return true; + } - public static boolean addEdge2( - @KeyFor("#2.adjList") String source, staticGraphClass theGraph) { - return true; - } + public static boolean addEdge2(@KeyFor("#2.adjList") String source, staticGraphClass theGraph) { + return true; + } - public boolean addEdge3(@KeyFor("this.adjList") String source) { - return true; - } + public boolean addEdge3(@KeyFor("this.adjList") String source) { + return true; } + } - public class nonstaticGraphClass { - private Map adjList = new HashMap<>(); + public class nonstaticGraphClass { + private Map adjList = new HashMap<>(); - public boolean addEdge(@KeyFor("adjList") String source) { - return true; - } + public boolean addEdge(@KeyFor("adjList") String source) { + return true; + } - public boolean addEdge2(@KeyFor("this.adjList") String source) { - return true; - } + public boolean addEdge2(@KeyFor("this.adjList") String source) { + return true; } + } + + // Non-static field initialization + + staticGraphClass graphField1 = new staticGraphClass(); + nonstaticGraphClass graphField2 = new nonstaticGraphClass(); + + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("graphField1.adjList") String key1 = ""; - // Non-static field initialization + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("graphField2.adjList") String key2 = ""; + + boolean b1 = staticGraphClass.addEdge2(key1, graphField1); + boolean b2 = graphField1.addEdge(key1); + boolean b3 = graphField1.addEdge2(key1, graphField1); + boolean b4 = graphField1.addEdge3(key1); + + boolean b5 = graphField2.addEdge(key2); + boolean b6 = graphField2.addEdge2(key2); + + // Classes that perform tests + + public class nonstaticTestClass { staticGraphClass graphField1 = new staticGraphClass(); nonstaticGraphClass graphField2 = new nonstaticGraphClass(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("graphField1.adjList") String key1 = ""; - - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("graphField2.adjList") String key2 = ""; - - boolean b1 = staticGraphClass.addEdge2(key1, graphField1); - boolean b2 = graphField1.addEdge(key1); - boolean b3 = graphField1.addEdge2(key1, graphField1); - boolean b4 = graphField1.addEdge3(key1); - - boolean b5 = graphField2.addEdge(key2); - boolean b6 = graphField2.addEdge2(key2); - - // Classes that perform tests - - public class nonstaticTestClass { - - staticGraphClass graphField1 = new staticGraphClass(); - nonstaticGraphClass graphField2 = new nonstaticGraphClass(); - - public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField1); - graphField1.addEdge(hero); - graphField1.addEdge2( - hero, - graphField1); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField1.addEdge3(hero); - } - - public void buildGraph2(@KeyFor("graphField2.adjList") String hero) { - graphField2.addEdge(hero); - graphField2.addEdge2(hero); - } - - public void buildGraph3(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - staticGraphClass.addEdge2(hero, myGraph); - myGraph.addEdge(hero); - myGraph.addEdge2( - hero, - myGraph); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - myGraph.addEdge3(hero); - } - - public void buildGraph4(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - myGraph.addEdge(hero); - myGraph.addEdge2(hero); - } - } - - public static class staticTestClass { - - staticGraphClass graphField1 = new staticGraphClass(); - static staticGraphClass graphField2 = new staticGraphClass(); - - public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField1); - graphField1.addEdge(hero); - graphField1.addEdge2( - hero, - graphField1); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField1.addEdge3(hero); - } - - public void buildGraph3(@KeyFor("graphField2.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField2); - graphField2.addEdge(hero); - graphField2.addEdge2( - hero, - graphField2); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField2.addEdge3(hero); - } - - public void buildGraph5(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - staticGraphClass.addEdge2(hero, myGraph); - myGraph.addEdge(hero); - myGraph.addEdge2( - hero, - myGraph); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - myGraph.addEdge3(hero); - } - - public void buildGraph6(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - myGraph.addEdge(hero); - myGraph.addEdge2(hero); - } - - public static void buildGraph7(@KeyFor("graphField2.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField2); - graphField2.addEdge(hero); - graphField2.addEdge2( - hero, - graphField2); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField2.addEdge3(hero); - } - - public static void buildGraph9( - staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - staticGraphClass.addEdge2(hero, myGraph); - myGraph.addEdge(hero); - myGraph.addEdge2( - hero, - myGraph); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - myGraph.addEdge3(hero); - } - - public static void buildGraph10( - nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - myGraph.addEdge(hero); - myGraph.addEdge2(hero); - } + public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField1); + graphField1.addEdge(hero); + graphField1.addEdge2( + hero, graphField1); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField1.addEdge3(hero); + } + + public void buildGraph2(@KeyFor("graphField2.adjList") String hero) { + graphField2.addEdge(hero); + graphField2.addEdge2(hero); + } + + public void buildGraph3(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + staticGraphClass.addEdge2(hero, myGraph); + myGraph.addEdge(hero); + myGraph.addEdge2( + hero, myGraph); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + myGraph.addEdge3(hero); + } + + public void buildGraph4(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + myGraph.addEdge(hero); + myGraph.addEdge2(hero); + } + } + + public static class staticTestClass { + + staticGraphClass graphField1 = new staticGraphClass(); + static staticGraphClass graphField2 = new staticGraphClass(); + + public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField1); + graphField1.addEdge(hero); + graphField1.addEdge2( + hero, graphField1); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField1.addEdge3(hero); + } + + public void buildGraph3(@KeyFor("graphField2.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField2); + graphField2.addEdge(hero); + graphField2.addEdge2( + hero, graphField2); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField2.addEdge3(hero); + } + + public void buildGraph5(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + staticGraphClass.addEdge2(hero, myGraph); + myGraph.addEdge(hero); + myGraph.addEdge2( + hero, myGraph); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + myGraph.addEdge3(hero); + } + + public void buildGraph6(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + myGraph.addEdge(hero); + myGraph.addEdge2(hero); + } + + public static void buildGraph7(@KeyFor("graphField2.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField2); + graphField2.addEdge(hero); + graphField2.addEdge2( + hero, graphField2); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField2.addEdge3(hero); + } + + public static void buildGraph9(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + staticGraphClass.addEdge2(hero, myGraph); + myGraph.addEdge(hero); + myGraph.addEdge2( + hero, myGraph); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + myGraph.addEdge3(hero); + } + + public static void buildGraph10( + nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + myGraph.addEdge(hero); + myGraph.addEdge2(hero); } + } } diff --git a/checker/tests/nullness/KeyForAutoboxing.java b/checker/tests/nullness/KeyForAutoboxing.java index baecb423e3a..c960d6ef940 100644 --- a/checker/tests/nullness/KeyForAutoboxing.java +++ b/checker/tests/nullness/KeyForAutoboxing.java @@ -7,56 +7,56 @@ public abstract class KeyForAutoboxing { - public void working1(Object key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); - } - m.get(key).toString(); + public void working1(Object key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); } + m.get(key).toString(); + } - public void working2(Integer key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); - } - m.get(key).toString(); + public void working2(Integer key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); } + m.get(key).toString(); + } - public void working3(Double key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); - } - m.get(key).toString(); + public void working3(Double key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); } + m.get(key).toString(); + } - public void notWorking1(int key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); - } - m.get(key).toString(); // Should not generate error but does + public void notWorking1(int key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); } + m.get(key).toString(); // Should not generate error but does + } - public void notWorking2(double key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); - } - m.get(key).toString(); // Should not generate error but does + public void notWorking2(double key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); } + m.get(key).toString(); // Should not generate error but does + } - public void notWorking3(double key, Map m) { - if (m.containsKey(key)) { - m.get(key).toString(); // Should not generate error but does - } + public void notWorking3(double key, Map m) { + if (m.containsKey(key)) { + m.get(key).toString(); // Should not generate error but does } + } - public void notWorking4(double key, Map m) { - if (m.get(key) != null) { - m.get(key).toString(); // Should not generate error but does - } + public void notWorking4(double key, Map m) { + if (m.get(key) != null) { + m.get(key).toString(); // Should not generate error but does } + } - public void notWorking5(double key, Map m) { - if (m.get(Double.valueOf(key)) != null) { - m.get(Double.valueOf(key)).toString(); // Should not generate error but does - } + public void notWorking5(double key, Map m) { + if (m.get(Double.valueOf(key)) != null) { + m.get(Double.valueOf(key)).toString(); // Should not generate error but does } + } } diff --git a/checker/tests/nullness/KeyForChecked.java b/checker/tests/nullness/KeyForChecked.java index ebfe724c751..a736b48b2a7 100644 --- a/checker/tests/nullness/KeyForChecked.java +++ b/checker/tests/nullness/KeyForChecked.java @@ -1,3 +1,9 @@ +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.NonNull; @@ -7,154 +13,147 @@ import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND) public class KeyForChecked { - interface KFMap<@KeyForBottom K extends @NonNull Object, V extends @NonNull Object> { - @Covariant(0) - public static interface Entry< - @KeyForBottom K1 extends @Nullable Object, V1 extends @Nullable Object> { - K1 getKey(); - - V1 getValue(); - } - - @Pure - boolean containsKey(@Nullable Object a1); - - @Pure - @Nullable V get(@Nullable Object a1); - - @Nullable V put(K a1, V a2); - - Set<@KeyFor("this") K> keySet(); - - Set> entrySet(); - - KFIterator iterator(); - } - - class KFHashMap<@KeyForBottom K2 extends @NonNull Object, V2 extends @NonNull Object> - implements KFMap { - @Pure - public boolean containsKey(@Nullable Object a1) { - return false; - } - - @Pure - public @Nullable V2 get(@Nullable Object a1) { - return null; - } - - public @Nullable V2 put(K2 a1, V2 a2) { - return null; - } - - public Set<@KeyFor("this") K2> keySet() { - return new HashSet<@KeyFor("this") K2>(); - } - - public Set> entrySet() { - return new HashSet>(); - } - - public KFIterator iterator() { - return new KFIterator(); - } - } - + interface KFMap<@KeyForBottom K extends @NonNull Object, V extends @NonNull Object> { @Covariant(0) - class KFIterator<@KeyForBottom E extends @Nullable Object> {} - - void incorrect1(Object map) { - String nonkey = ""; - // :: error: (assignment.type.incompatible) - @KeyFor("map") String key = nonkey; - } + public static interface Entry< + @KeyForBottom K1 extends @Nullable Object, V1 extends @Nullable Object> { + K1 getKey(); - void correct1(Object map) { - String nonkey = ""; - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("map") String key = nonkey; + V1 getValue(); } - void incorrect2() { - KFMap m = new KFHashMap<>(); - m.put("a", new Object()); - m.put("b", new Object()); - m.put("c", new Object()); - - Collection<@KeyFor("m") String> coll = m.keySet(); + @Pure + boolean containsKey(@Nullable Object a1); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("m") String newkey = "new"; + @Pure + @Nullable V get(@Nullable Object a1); - coll.add(newkey); - // TODO: at this point, the @KeyFor annotation is violated - m.put("new", new Object()); - } + @Nullable V put(K a1, V a2); - void correct2() { - KFMap m = new KFHashMap<>(); - m.put("a", new Object()); - m.put("b", new Object()); - m.put("c", new Object()); + Set<@KeyFor("this") K> keySet(); - Collection<@KeyFor("m") String> coll = m.keySet(); + Set> entrySet(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("m") String newkey = "new"; + KFIterator iterator(); + } - m.put(newkey, new Object()); - coll.add(newkey); + class KFHashMap<@KeyForBottom K2 extends @NonNull Object, V2 extends @NonNull Object> + implements KFMap { + @Pure + public boolean containsKey(@Nullable Object a1) { + return false; } - void iter() { - KFMap emap = new KFHashMap<>(); - Set<@KeyFor("emap") String> s = emap.keySet(); - Iterator<@KeyFor("emap") String> it = emap.keySet().iterator(); - Iterator<@KeyFor("emap") String> it2 = s.iterator(); - - Collection<@KeyFor("emap") String> x = Collections.unmodifiableSet(emap.keySet()); - - for (@KeyFor("emap") String st : s) {} - for (String st : s) {} - Object bubu = new Object(); - // :: error: (enhancedfor.type.incompatible) - for (@KeyFor("bubu") String st : s) {} + @Pure + public @Nullable V2 get(@Nullable Object a1) { + return null; } - void dominators(KFMap> preds) { - for (T node : preds.keySet()) {} - - for (@KeyFor("preds") T node : preds.keySet()) {} + public @Nullable V2 put(K2 a1, V2 a2) { + return null; } - void entrySet() { - KFMap emap = new KFHashMap<>(); - Set> es = emap.entrySet(); - - // KeyFor has to be explicit on the component to Entry sets because - // a) it's not clear which map the Entry set may have come from - // b) and there is no guarantee the map is still accessible - // :: error: (assignment.type.incompatible) - Set> es2 = emap.entrySet(); + public Set<@KeyFor("this") K2> keySet() { + return new HashSet<@KeyFor("this") K2>(); } - public static void mapToString(KFMap m) { - Set> eset = m.entrySet(); - - for (KFMap.Entry<@KeyFor("m") K, V> entry : m.entrySet()) {} + public Set> entrySet() { + return new HashSet>(); } - void testWF(KFMap m) { - KFIterator it = m.iterator(); + public KFIterator iterator() { + return new KFIterator(); } + } + + @Covariant(0) + class KFIterator<@KeyForBottom E extends @Nullable Object> {} + + void incorrect1(Object map) { + String nonkey = ""; + // :: error: (assignment.type.incompatible) + @KeyFor("map") String key = nonkey; + } + + void correct1(Object map) { + String nonkey = ""; + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("map") String key = nonkey; + } + + void incorrect2() { + KFMap m = new KFHashMap<>(); + m.put("a", new Object()); + m.put("b", new Object()); + m.put("c", new Object()); + + Collection<@KeyFor("m") String> coll = m.keySet(); + + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("m") String newkey = "new"; + + coll.add(newkey); + // TODO: at this point, the @KeyFor annotation is violated + m.put("new", new Object()); + } + + void correct2() { + KFMap m = new KFHashMap<>(); + m.put("a", new Object()); + m.put("b", new Object()); + m.put("c", new Object()); + + Collection<@KeyFor("m") String> coll = m.keySet(); + + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("m") String newkey = "new"; + + m.put(newkey, new Object()); + coll.add(newkey); + } + + void iter() { + KFMap emap = new KFHashMap<>(); + Set<@KeyFor("emap") String> s = emap.keySet(); + Iterator<@KeyFor("emap") String> it = emap.keySet().iterator(); + Iterator<@KeyFor("emap") String> it2 = s.iterator(); + + Collection<@KeyFor("emap") String> x = Collections.unmodifiableSet(emap.keySet()); + + for (@KeyFor("emap") String st : s) {} + for (String st : s) {} + Object bubu = new Object(); + // :: error: (enhancedfor.type.incompatible) + for (@KeyFor("bubu") String st : s) {} + } + + void dominators(KFMap> preds) { + for (T node : preds.keySet()) {} + + for (@KeyFor("preds") T node : preds.keySet()) {} + } + + void entrySet() { + KFMap emap = new KFHashMap<>(); + Set> es = emap.entrySet(); + + // KeyFor has to be explicit on the component to Entry sets because + // a) it's not clear which map the Entry set may have come from + // b) and there is no guarantee the map is still accessible + // :: error: (assignment.type.incompatible) + Set> es2 = emap.entrySet(); + } + + public static void mapToString(KFMap m) { + Set> eset = m.entrySet(); + + for (KFMap.Entry<@KeyFor("m") K, V> entry : m.entrySet()) {} + } + + void testWF(KFMap m) { + KFIterator it = m.iterator(); + } } diff --git a/checker/tests/nullness/KeyForDiamond.java b/checker/tests/nullness/KeyForDiamond.java index 15a3a6de8c0..7a3a02a801c 100644 --- a/checker/tests/nullness/KeyForDiamond.java +++ b/checker/tests/nullness/KeyForDiamond.java @@ -1,17 +1,16 @@ -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.*; +import org.checkerframework.checker.nullness.qual.KeyFor; public class KeyForDiamond { - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); - public void method() { - Map<@KeyFor("map") Integer, Double> paths = new HashMap<>(); - Set<@KeyFor("map") Integer> set = new HashSet<>(); - List<@KeyFor("map") Integer> list = new ArrayList<>(); - } + public void method() { + Map<@KeyFor("map") Integer, Double> paths = new HashMap<>(); + Set<@KeyFor("map") Integer> set = new HashSet<>(); + List<@KeyFor("map") Integer> list = new ArrayList<>(); + } - public static void main(String[] args) { - KeyForDiamond t = new KeyForDiamond(); - } + public static void main(String[] args) { + KeyForDiamond t = new KeyForDiamond(); + } } diff --git a/checker/tests/nullness/KeyForFlow.java b/checker/tests/nullness/KeyForFlow.java index 6e3117dece2..05ccf8c0613 100644 --- a/checker/tests/nullness/KeyForFlow.java +++ b/checker/tests/nullness/KeyForFlow.java @@ -1,173 +1,172 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Vector; +import org.checkerframework.checker.nullness.qual.*; public class KeyForFlow extends HashMap { - String k = "key"; - HashMap m = new HashMap<>(); - - void testContainsKeyForLocalKeyAndLocalMap() { - String k_local = "key"; - HashMap m_local = new HashMap<>(); - - if (m_local.containsKey(k_local)) { - @KeyFor("m_local") Object s = k_local; - } - - // :: error: (assignment.type.incompatible) - @KeyFor("m_local") String s2 = k_local; - } - - void testContainsKeyForLocalKeyAndFieldMap() { - String k_local = "key"; - - if (m.containsKey(k_local)) { - @KeyFor("m") Object s = k_local; - } - - // :: error: (assignment.type.incompatible) - @KeyFor("m") String s2 = k_local; - } - - void testContainsKeyForFieldKeyAndLocalMap() { - HashMap m_local = new HashMap<>(); - - if (m_local.containsKey(k)) { - @KeyFor("m_local") Object s = k; - } + String k = "key"; + HashMap m = new HashMap<>(); - // :: error: (assignment.type.incompatible) - @KeyFor("m_local") String s2 = k; - } - - void testContainsKeyForFieldKeyAndFieldMap() { - if (m.containsKey(k)) { - @KeyFor("m") Object s = k; - } + void testContainsKeyForLocalKeyAndLocalMap() { + String k_local = "key"; + HashMap m_local = new HashMap<>(); - // :: error: (assignment.type.incompatible) - @KeyFor("m") String s2 = k; + if (m_local.containsKey(k_local)) { + @KeyFor("m_local") Object s = k_local; } - static String k_s = "key"; + // :: error: (assignment.type.incompatible) + @KeyFor("m_local") String s2 = k_local; + } - void testContainsKeyForStaticKeyAndFieldMap() { - if (m.containsKey(k_s)) { - @KeyFor("m") Object s = k_s; - } + void testContainsKeyForLocalKeyAndFieldMap() { + String k_local = "key"; - // :: error: (assignment.type.incompatible) - @KeyFor("m") String s2 = k_s; + if (m.containsKey(k_local)) { + @KeyFor("m") Object s = k_local; } - static HashMap m_s = new HashMap<>(); + // :: error: (assignment.type.incompatible) + @KeyFor("m") String s2 = k_local; + } - void testContainsKeyForFieldKeyAndStaticMap() { - if (m_s.containsKey(k)) { - // Currently for this to work, the user must write @KeyFor("classname.static_field") - @KeyFor("m_s") Object s = k; - } + void testContainsKeyForFieldKeyAndLocalMap() { + HashMap m_local = new HashMap<>(); - // :: error: (assignment.type.incompatible) - @KeyFor("m_s") String s2 = k; + if (m_local.containsKey(k)) { + @KeyFor("m_local") Object s = k; } - void testContainsKeyForFieldKeyAndReceiverMap() { - if (containsKey(k)) { - @KeyFor("this") Object s = k; - } + // :: error: (assignment.type.incompatible) + @KeyFor("m_local") String s2 = k; + } - // :: error: (assignment.type.incompatible) - @KeyFor("this") String s2 = k; + void testContainsKeyForFieldKeyAndFieldMap() { + if (m.containsKey(k)) { + @KeyFor("m") Object s = k; } - // TODO: The diamond operator does not work here: - // Vector<@KeyFor("m2") String> coll = new Vector<>(); - // Figure out why not. - Vector<@KeyFor("m2") String> coll = new Vector<@KeyFor("m2") String>(); - HashMap m2 = new HashMap<>(); - String k2 = "key2"; - - void testCallingPutAfterAdd() { - // :: error: (argument.type.incompatible) - coll.add(k2); - m2.put(k2, new Object()); - } + // :: error: (assignment.type.incompatible) + @KeyFor("m") String s2 = k; + } - void testPutForLocalKeyAndLocalMap() { - HashMap m2_local = new HashMap<>(); - Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); - String k2_local = "key2"; + static String k_s = "key"; - m2_local.put(k2_local, new Object()); - coll_local.add(k2_local); + void testContainsKeyForStaticKeyAndFieldMap() { + if (m.containsKey(k_s)) { + @KeyFor("m") Object s = k_s; } - void testPutForLocalKeyAndFieldMap() { - String k2_local = "key2"; + // :: error: (assignment.type.incompatible) + @KeyFor("m") String s2 = k_s; + } - m2.put(k2_local, new Object()); - coll.add(k2_local); - } - - void testPutForFieldKeyAndLocalMap() { - HashMap m2_local = new HashMap<>(); - Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); - - m2_local.put(k2, new Object()); - coll_local.add(k2); - } + static HashMap m_s = new HashMap<>(); - void testPutForFieldKeyAndFieldMap() { - m2.put(k2, new Object()); - coll.add(k2); + void testContainsKeyForFieldKeyAndStaticMap() { + if (m_s.containsKey(k)) { + // Currently for this to work, the user must write @KeyFor("classname.static_field") + @KeyFor("m_s") Object s = k; } - /* - This scenario is not working since in Vector, "this" gets translated to "coll_local". - The same thing happens if the collection is a field instead of a local. - However this seems like a low-priority scenario to enable. - - void testPutForFieldKeyAndReceiverMap() { - Vector<@KeyFor("this") String> coll_local = new Vector<>(); - - put(k2, new Object()); - coll_local.add(k2); - }*/ - - class foo { - public HashMap m = new HashMap<>(); - } - - void testContainsKeyForFieldKeyAndMapFieldOfOtherClass() { - foo f = new foo(); - - if (f.m.containsKey(k)) { - @KeyFor("f.m") Object s = k; - } + // :: error: (assignment.type.incompatible) + @KeyFor("m_s") String s2 = k; + } - // :: error: (assignment.type.incompatible) - @KeyFor("f.m") String s2 = k; + void testContainsKeyForFieldKeyAndReceiverMap() { + if (containsKey(k)) { + @KeyFor("this") Object s = k; } - void testPutForFieldKeyAndMapFieldOfOtherClass() { - foo f = new foo(); - Vector<@KeyFor("f.m") String> coll_local = new Vector<>(); - f.m.put(k2, new Object()); - coll_local.add(k2); + // :: error: (assignment.type.incompatible) + @KeyFor("this") String s2 = k; + } + + // TODO: The diamond operator does not work here: + // Vector<@KeyFor("m2") String> coll = new Vector<>(); + // Figure out why not. + Vector<@KeyFor("m2") String> coll = new Vector<@KeyFor("m2") String>(); + HashMap m2 = new HashMap<>(); + String k2 = "key2"; + + void testCallingPutAfterAdd() { + // :: error: (argument.type.incompatible) + coll.add(k2); + m2.put(k2, new Object()); + } + + void testPutForLocalKeyAndLocalMap() { + HashMap m2_local = new HashMap<>(); + Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); + String k2_local = "key2"; + + m2_local.put(k2_local, new Object()); + coll_local.add(k2_local); + } + + void testPutForLocalKeyAndFieldMap() { + String k2_local = "key2"; + + m2.put(k2_local, new Object()); + coll.add(k2_local); + } + + void testPutForFieldKeyAndLocalMap() { + HashMap m2_local = new HashMap<>(); + Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); + + m2_local.put(k2, new Object()); + coll_local.add(k2); + } + + void testPutForFieldKeyAndFieldMap() { + m2.put(k2, new Object()); + coll.add(k2); + } + + /* + This scenario is not working since in Vector, "this" gets translated to "coll_local". + The same thing happens if the collection is a field instead of a local. + However this seems like a low-priority scenario to enable. + + void testPutForFieldKeyAndReceiverMap() { + Vector<@KeyFor("this") String> coll_local = new Vector<>(); + + put(k2, new Object()); + coll_local.add(k2); + }*/ + + class foo { + public HashMap m = new HashMap<>(); + } + + void testContainsKeyForFieldKeyAndMapFieldOfOtherClass() { + foo f = new foo(); + + if (f.m.containsKey(k)) { + @KeyFor("f.m") Object s = k; } - /*public void testAddToListInsteadOfMap(List<@KeyFor("#4") String> la, String b, @KeyFor("#4") String c, Map a) { - // Disabled error (assignment.type.incompatible) - List ls1 = la; - List<@KeyFor("#4") String> ls2 = la; - ls1.add(b); - // Disabled error (argument.type.incompatible) - la.add(b); - ls2.add(c); - la.add(c); - @NonNull String astr = a.get(ls2.get(0)); - }*/ + // :: error: (assignment.type.incompatible) + @KeyFor("f.m") String s2 = k; + } + + void testPutForFieldKeyAndMapFieldOfOtherClass() { + foo f = new foo(); + Vector<@KeyFor("f.m") String> coll_local = new Vector<>(); + f.m.put(k2, new Object()); + coll_local.add(k2); + } + + /*public void testAddToListInsteadOfMap(List<@KeyFor("#4") String> la, String b, @KeyFor("#4") String c, Map a) { + // Disabled error (assignment.type.incompatible) + List ls1 = la; + List<@KeyFor("#4") String> ls2 = la; + ls1.add(b); + // Disabled error (argument.type.incompatible) + la.add(b); + ls2.add(c); + la.add(c); + @NonNull String astr = a.get(ls2.get(0)); + }*/ } diff --git a/checker/tests/nullness/KeyForIssue328.java b/checker/tests/nullness/KeyForIssue328.java index fe92c91e85c..24ab6fac586 100644 --- a/checker/tests/nullness/KeyForIssue328.java +++ b/checker/tests/nullness/KeyForIssue328.java @@ -1,21 +1,20 @@ // Test case for Issue 328: // https://github.com/typetools/checker-framework/issues/328 -import org.checkerframework.checker.nullness.qual.*; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class KeyForIssue328 { - public static void m(Map a, Map b, Object ka, Object kb) { - if (a.containsKey(ka)) { - @NonNull Object i = a.get(ka); // OK - } - if (b.containsKey(kb)) { - @NonNull Object i = b.get(kb); // OK - } - if (a.containsKey(ka) && b.containsKey(kb)) { - @NonNull Object i = a.get(ka); // OK - @NonNull Object j = b.get(kb); // OK - } + public static void m(Map a, Map b, Object ka, Object kb) { + if (a.containsKey(ka)) { + @NonNull Object i = a.get(ka); // OK + } + if (b.containsKey(kb)) { + @NonNull Object i = b.get(kb); // OK + } + if (a.containsKey(ka) && b.containsKey(kb)) { + @NonNull Object i = a.get(ka); // OK + @NonNull Object j = b.get(kb); // OK } + } } diff --git a/checker/tests/nullness/KeyForLocalSideEffect.java b/checker/tests/nullness/KeyForLocalSideEffect.java index 5c1bca529e1..c58f34cc128 100644 --- a/checker/tests/nullness/KeyForLocalSideEffect.java +++ b/checker/tests/nullness/KeyForLocalSideEffect.java @@ -1,24 +1,23 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; +import org.checkerframework.checker.nullness.qual.*; public class KeyForLocalSideEffect { - String k = "key"; - HashMap m = new HashMap<>(); + String k = "key"; + HashMap m = new HashMap<>(); - void testContainsKeyForFieldKeyAndLocalMap() { - HashMap m_local = m; + void testContainsKeyForFieldKeyAndLocalMap() { + HashMap m_local = m; - if (m_local.containsKey(k)) { - @KeyFor("m_local") String s = k; - havoc(); - // TODO: This should be an error, because s is no longer a key for m_local. - @NonNull Integer val = m_local.get(s); - } + if (m_local.containsKey(k)) { + @KeyFor("m_local") String s = k; + havoc(); + // TODO: This should be an error, because s is no longer a key for m_local. + @NonNull Integer val = m_local.get(s); } + } - void havoc() { - m = new HashMap<>(); - } + void havoc() { + m = new HashMap<>(); + } } diff --git a/checker/tests/nullness/KeyForLocalVariable.java b/checker/tests/nullness/KeyForLocalVariable.java index baa1283bcb3..089473c956d 100644 --- a/checker/tests/nullness/KeyForLocalVariable.java +++ b/checker/tests/nullness/KeyForLocalVariable.java @@ -1,32 +1,31 @@ // Test for Checker Framework issue 795 // https://github.com/typetools/checker-framework/issues/795 -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class KeyForLocalVariable { - public static void localVariableShadowing() { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("m0") String kk; - { - Map m0 = new HashMap<>(); - @SuppressWarnings("keyfor") - @KeyFor("m0") String k = "key"; - // :: error: (assignment.type.incompatible) - kk = k; - } - { - Map m0 = new HashMap<>(); - // :: error: (assignment.type.incompatible) - @KeyFor("m0") String k2 = kk; - } + public static void localVariableShadowing() { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("m0") String kk; + { + Map m0 = new HashMap<>(); + @SuppressWarnings("keyfor") + @KeyFor("m0") String k = "key"; + // :: error: (assignment.type.incompatible) + kk = k; } - - public static void invalidLocalVariable() { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("foobar") String kk; + { + Map m0 = new HashMap<>(); + // :: error: (assignment.type.incompatible) + @KeyFor("m0") String k2 = kk; } + } + + public static void invalidLocalVariable() { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("foobar") String kk; + } } diff --git a/checker/tests/nullness/KeyForLub.java b/checker/tests/nullness/KeyForLub.java index 94b2b62fca7..0308fcca614 100644 --- a/checker/tests/nullness/KeyForLub.java +++ b/checker/tests/nullness/KeyForLub.java @@ -1,43 +1,42 @@ package keyfor; +import java.util.HashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.PolyKeyFor; import org.checkerframework.checker.nullness.qual.UnknownKeyFor; -import java.util.HashMap; -import java.util.Map; - public class KeyForLub { - public static boolean flag; - Map map1 = new HashMap<>(); - Map map2 = new HashMap<>(); - Map map3 = new HashMap<>(); - - void method( - @KeyFor({"map1", "map2"}) String key12, - @KeyFor({"map1", "map3"}) String key13, - @UnknownKeyFor String unknown) { - @KeyFor("map1") String key1 = flag ? key12 : key13; - - // :: error: (assignment.type.incompatible) - @KeyFor({"map1", "map2"}) String key2 = flag ? key12 : key13; - - // :: error: (assignment.type.incompatible) - @KeyFor({"map1", "map2"}) String key3 = flag ? key12 : unknown; - } - - @PolyKeyFor String poly1(@KeyFor("map1") String key1, @PolyKeyFor String poly) { - // :: error: (return.type.incompatible) - return flag ? key1 : poly; - } - - // :: error: (type.invalid.annotations.on.location) - void poly2(@PolyKeyFor String poly, @UnknownKeyFor String unknown, @KeyForBottom String bot) { - // :: error: (assignment.type.incompatible) - @PolyKeyFor String s1 = flag ? poly : unknown; - @PolyKeyFor String s2 = flag ? poly : bot; - // :: error: (assignment.type.incompatible) :: error: (type.invalid.annotations.on.location) - @KeyForBottom String s3 = flag ? poly : bot; - } + public static boolean flag; + Map map1 = new HashMap<>(); + Map map2 = new HashMap<>(); + Map map3 = new HashMap<>(); + + void method( + @KeyFor({"map1", "map2"}) String key12, + @KeyFor({"map1", "map3"}) String key13, + @UnknownKeyFor String unknown) { + @KeyFor("map1") String key1 = flag ? key12 : key13; + + // :: error: (assignment.type.incompatible) + @KeyFor({"map1", "map2"}) String key2 = flag ? key12 : key13; + + // :: error: (assignment.type.incompatible) + @KeyFor({"map1", "map2"}) String key3 = flag ? key12 : unknown; + } + + @PolyKeyFor String poly1(@KeyFor("map1") String key1, @PolyKeyFor String poly) { + // :: error: (return.type.incompatible) + return flag ? key1 : poly; + } + + // :: error: (type.invalid.annotations.on.location) + void poly2(@PolyKeyFor String poly, @UnknownKeyFor String unknown, @KeyForBottom String bot) { + // :: error: (assignment.type.incompatible) + @PolyKeyFor String s1 = flag ? poly : unknown; + @PolyKeyFor String s2 = flag ? poly : bot; + // :: error: (assignment.type.incompatible) :: error: (type.invalid.annotations.on.location) + @KeyForBottom String s3 = flag ? poly : bot; + } } diff --git a/checker/tests/nullness/KeyForMultiple.java b/checker/tests/nullness/KeyForMultiple.java index c22be7d16cd..58c4ac6029e 100644 --- a/checker/tests/nullness/KeyForMultiple.java +++ b/checker/tests/nullness/KeyForMultiple.java @@ -2,45 +2,42 @@ // @skip-test until the bug is fixed. -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.KeyFor; public class KeyForMultiple { - void m1() { + void m1() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); - Set<@KeyFor({"sharedCounts1"}) String> sharedCountsKeys1 = sharedCounts1.keySet(); - } + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); + Set<@KeyFor({"sharedCounts1"}) String> sharedCountsKeys1 = sharedCounts1.keySet(); + } - void m2() { + void m2() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); - Set<@KeyFor({"sharedBooks", "sharedCounts1"}) String> otherChars1 = sharedCounts1.keySet(); - } + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); + Set<@KeyFor({"sharedBooks", "sharedCounts1"}) String> otherChars1 = sharedCounts1.keySet(); + } - void m3() { + void m3() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = - new HashMap<>(); - Set<@KeyFor({"sharedCounts2"}) String> sharedCountsKeys2 = sharedCounts2.keySet(); - } + Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = new HashMap<>(); + Set<@KeyFor({"sharedCounts2"}) String> sharedCountsKeys2 = sharedCounts2.keySet(); + } - void m4() { + void m4() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = - new HashMap<>(); - Set<@KeyFor({"sharedBooks", "sharedCounts2"}) String> otherChars2 = sharedCounts2.keySet(); - } + Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = new HashMap<>(); + Set<@KeyFor({"sharedBooks", "sharedCounts2"}) String> otherChars2 = sharedCounts2.keySet(); + } } diff --git a/checker/tests/nullness/KeyForPolymorphism.java b/checker/tests/nullness/KeyForPolymorphism.java index 462727d834e..8acc3b2b12c 100644 --- a/checker/tests/nullness/KeyForPolymorphism.java +++ b/checker/tests/nullness/KeyForPolymorphism.java @@ -1,20 +1,19 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; // test related to issue 429: https://github.com/typetools/checker-framework/issues/429 public class KeyForPolymorphism { - Map m1 = new HashMap<>(); - Map m2 = new HashMap<>(); + Map m1 = new HashMap<>(); + Map m2 = new HashMap<>(); - void method(@KeyFor("m1") String k1m1, @KeyFor("m2") String k1m2) { - @KeyFor("m1") String k2m1 = identity1(k1m1); - @KeyFor("m2") String k2m2 = identity1(k1m2); - } + void method(@KeyFor("m1") String k1m1, @KeyFor("m2") String k1m2) { + @KeyFor("m1") String k2m1 = identity1(k1m1); + @KeyFor("m2") String k2m2 = identity1(k1m2); + } - static @PolyKeyFor String identity1(@PolyKeyFor String arg) { - return arg; - } + static @PolyKeyFor String identity1(@PolyKeyFor String arg) { + return arg; + } } diff --git a/checker/tests/nullness/KeyForPostcondition.java b/checker/tests/nullness/KeyForPostcondition.java index efed1c0d8b2..04c13fd4f75 100644 --- a/checker/tests/nullness/KeyForPostcondition.java +++ b/checker/tests/nullness/KeyForPostcondition.java @@ -1,48 +1,47 @@ +import java.util.HashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; import org.checkerframework.checker.nullness.qual.KeyFor; -import java.util.HashMap; -import java.util.Map; - public class KeyForPostcondition { - public static Map m = new HashMap<>(); + public static Map m = new HashMap<>(); - // public static @KeyFor("m") String key = "hello"; + // public static @KeyFor("m") String key = "hello"; - public static boolean b; + public static boolean b; - @EnsuresKeyFor(value = "#1", map = "m") - public void putKey(String x) { - m.put(x, 22); - } + @EnsuresKeyFor(value = "#1", map = "m") + public void putKey(String x) { + m.put(x, 22); + } - public void usePutKey(String x) { - // :: error: (assignment.type.incompatible) - @KeyFor("m") String a = x; - putKey(x); - @KeyFor("m") String b = x; - } + public void usePutKey(String x) { + // :: error: (assignment.type.incompatible) + @KeyFor("m") String a = x; + putKey(x); + @KeyFor("m") String b = x; + } - @EnsuresKeyForIf(expression = "#1", result = true, map = "m") - public boolean tryPutKey(String x) { - if (b) { - putKey(x); - return true; - } else { - return false; - } + @EnsuresKeyForIf(expression = "#1", result = true, map = "m") + public boolean tryPutKey(String x) { + if (b) { + putKey(x); + return true; + } else { + return false; } + } - public void useTryPutKey(String x) { - // :: error: (assignment.type.incompatible) - @KeyFor("m") String a = x; - if (tryPutKey(x)) { - @KeyFor("m") String b = x; - } - - // :: error: (assignment.type.incompatible) - @KeyFor("m") String c = x; + public void useTryPutKey(String x) { + // :: error: (assignment.type.incompatible) + @KeyFor("m") String a = x; + if (tryPutKey(x)) { + @KeyFor("m") String b = x; } + + // :: error: (assignment.type.incompatible) + @KeyFor("m") String c = x; + } } diff --git a/checker/tests/nullness/KeyForPropagation.java b/checker/tests/nullness/KeyForPropagation.java index 9e5e7e8890f..efec4c99932 100644 --- a/checker/tests/nullness/KeyForPropagation.java +++ b/checker/tests/nullness/KeyForPropagation.java @@ -1,9 +1,8 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; // interface Dest { // } @@ -16,21 +15,21 @@ public class KeyForPropagation { - { - List<@KeyFor("a") String> a = new ArrayList(); - } + { + List<@KeyFor("a") String> a = new ArrayList(); + } - static { - List<@KeyFor("b") String> b = new ArrayList(); - } + static { + List<@KeyFor("b") String> b = new ArrayList(); + } - List<@KeyFor("c") String> c = new ArrayList(); + List<@KeyFor("c") String> c = new ArrayList(); - void method() { - List<@KeyFor("d") String> d = new ArrayList(); - } + void method() { + List<@KeyFor("d") String> d = new ArrayList(); + } - void method(Map v) { - Set ks = v.keySet(); - } + void method(Map v) { + Set ks = v.keySet(); + } } diff --git a/checker/tests/nullness/KeyForShadowing.java b/checker/tests/nullness/KeyForShadowing.java index 46d5aca3b7e..df517bf49ab 100644 --- a/checker/tests/nullness/KeyForShadowing.java +++ b/checker/tests/nullness/KeyForShadowing.java @@ -1,73 +1,72 @@ // Test for Checker Framework issue 273: // https://github.com/typetools/checker-framework/issues/273 -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class KeyForShadowing { - public static void main(String... p) { - Map m0 = new HashMap<>(); - Map m1 = new HashMap<>(); - String k = "key"; - m0.put(k, 1); // k is @KeyFor("m0") after this line + public static void main(String... p) { + Map m0 = new HashMap<>(); + Map m1 = new HashMap<>(); + String k = "key"; + m0.put(k, 1); // k is @KeyFor("m0") after this line - // We expect an error for the next one since we are not respecting the method contract. It - // expects the key to be for the second parameter, not the first. + // We expect an error for the next one since we are not respecting the method contract. It + // expects the key to be for the second parameter, not the first. - // :: error: (argument.type.incompatible) - getMap3(m0, m1, k).toString(); + // :: error: (argument.type.incompatible) + getMap3(m0, m1, k).toString(); - // We expect an error for the next one since although we are respecting the method contract, - // since the key is for the first parameter, the Nullness Checker is misinterpreting "m1" to - // be the local m1 to this method, and not the first parameter to the method. + // We expect an error for the next one since although we are respecting the method contract, + // since the key is for the first parameter, the Nullness Checker is misinterpreting "m1" to + // be the local m1 to this method, and not the first parameter to the method. - // :: error: (argument.type.incompatible) - getMap2(m0, m1, k).toString(); + // :: error: (argument.type.incompatible) + getMap2(m0, m1, k).toString(); - // :: error: (argument.type.incompatible) - getMap1(m0, m1, k).toString(); + // :: error: (argument.type.incompatible) + getMap1(m0, m1, k).toString(); - getMap4(m0, m1, k).toString(); - } + getMap4(m0, m1, k).toString(); + } - public static @NonNull Integer getMap1( - Map m1, // m1,m0 flipped - Map m0, - // :: error: (expression.unparsable.type.invalid) - @KeyFor("m0") String k) { - // :: error: (return.type.incompatible) - return m0.get(k); - } + public static @NonNull Integer getMap1( + Map m1, // m1,m0 flipped + Map m0, + // :: error: (expression.unparsable.type.invalid) + @KeyFor("m0") String k) { + // :: error: (return.type.incompatible) + return m0.get(k); + } - public static @NonNull Integer getMap2( - Map m1, // m1,m0 flipped - Map m0, - // :: error: (expression.unparsable.type.invalid) - @KeyFor("m1") String k) { - // This method body is incorrect. - // We expect this error because we are indicating that - // the key is for m1, so m0.get(k) is @Nullable. - // :: error: (return.type.incompatible) - return m0.get(k); - } + public static @NonNull Integer getMap2( + Map m1, // m1,m0 flipped + Map m0, + // :: error: (expression.unparsable.type.invalid) + @KeyFor("m1") String k) { + // This method body is incorrect. + // We expect this error because we are indicating that + // the key is for m1, so m0.get(k) is @Nullable. + // :: error: (return.type.incompatible) + return m0.get(k); + } - public static @NonNull Integer getMap3( - Map m1, // m1,m0 flipped - Map m0, - @KeyFor("#2") String k) { - return m0.get(k); - } + public static @NonNull Integer getMap3( + Map m1, // m1,m0 flipped + Map m0, + @KeyFor("#2") String k) { + return m0.get(k); + } - public static @NonNull Integer getMap4( - Map m1, // m1,m0 flipped - Map m0, - @KeyFor("#1") String k) { - // This method body is incorrect. - // We expect this error because we are indicating that - // the key is for m1, so m0.get(k) is @Nullable. - // :: error: (return.type.incompatible) - return m0.get(k); - } + public static @NonNull Integer getMap4( + Map m1, // m1,m0 flipped + Map m0, + @KeyFor("#1") String k) { + // This method body is incorrect. + // We expect this error because we are indicating that + // the key is for m1, so m0.get(k) is @Nullable. + // :: error: (return.type.incompatible) + return m0.get(k); + } } diff --git a/checker/tests/nullness/KeyForStaticField.java b/checker/tests/nullness/KeyForStaticField.java index f87920ad6ed..38c0da1e5c3 100644 --- a/checker/tests/nullness/KeyForStaticField.java +++ b/checker/tests/nullness/KeyForStaticField.java @@ -2,31 +2,30 @@ // https://github.com/typetools/checker-framework/issues/877 // @skip-test until the issue is fixed. -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class KeyForStaticField { - @SuppressWarnings("keyfor") - public static final @KeyFor("this.map") String STATIC_KEY = "some text"; + @SuppressWarnings("keyfor") + public static final @KeyFor("this.map") String STATIC_KEY = "some text"; - private Map map; + private Map map; - public KeyForStaticField() { - map = new HashMap<>(); - map.put(STATIC_KEY, 0); - } + public KeyForStaticField() { + map = new HashMap<>(); + map.put(STATIC_KEY, 0); + } - /** Returns the value for the given key, which must be present in the map. */ - public Integer getValue(@KeyFor("this.map") String key) { - assert map.containsKey(key) : "Map does not contain key " + key; - return map.get(key); - } + /** Returns the value for the given key, which must be present in the map. */ + public Integer getValue(@KeyFor("this.map") String key) { + assert map.containsKey(key) : "Map does not contain key " + key; + return map.get(key); + } - public void m(KeyForStaticField other) { - getValue(STATIC_KEY); - this.getValue(STATIC_KEY); - other.getValue(STATIC_KEY); - } + public void m(KeyForStaticField other) { + getValue(STATIC_KEY); + this.getValue(STATIC_KEY); + other.getValue(STATIC_KEY); + } } diff --git a/checker/tests/nullness/KeyForSubst.java b/checker/tests/nullness/KeyForSubst.java index a5c38ef908e..0572c080438 100644 --- a/checker/tests/nullness/KeyForSubst.java +++ b/checker/tests/nullness/KeyForSubst.java @@ -1,44 +1,43 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class KeyForSubst { - /* - static class MyClass { - public T next() { return null; } - } - */ - - @KeyFor("#1") String getMain(Object m) { - throw new RuntimeException(); - } - - List<@KeyFor("#1") String> getDeep(Object m) { - throw new RuntimeException(); - } - - @KeyFor("#1") List<@KeyFor("#2") String> getBoth(Object l, Object m) { - throw new RuntimeException(); - } - - // OK, I think the annotation on the index is overdoing it, but it works. - @KeyFor("#1") String @KeyFor("#2") [] getArray(Object l, Object m) { - throw new RuntimeException(); - } - - public void testAssignMain(Object lastMap) { - @KeyFor("lastMap") String key = getMain(lastMap); - } - - public void testAssignDeep(Object lastMap) { - List<@KeyFor("lastMap") String> key = getDeep(lastMap); - } - - public void testAssignBoth(Object lastMap, Object newMap) { - @KeyFor("lastMap") List<@KeyFor("newMap") String> key = getBoth(lastMap, newMap); - } - - public void testAssignArray(Object lastMap, Object newMap) { - @KeyFor("lastMap") String @KeyFor("newMap") [] key = getArray(lastMap, newMap); - } + /* + static class MyClass { + public T next() { return null; } + } + */ + + @KeyFor("#1") String getMain(Object m) { + throw new RuntimeException(); + } + + List<@KeyFor("#1") String> getDeep(Object m) { + throw new RuntimeException(); + } + + @KeyFor("#1") List<@KeyFor("#2") String> getBoth(Object l, Object m) { + throw new RuntimeException(); + } + + // OK, I think the annotation on the index is overdoing it, but it works. + @KeyFor("#1") String @KeyFor("#2") [] getArray(Object l, Object m) { + throw new RuntimeException(); + } + + public void testAssignMain(Object lastMap) { + @KeyFor("lastMap") String key = getMain(lastMap); + } + + public void testAssignDeep(Object lastMap) { + List<@KeyFor("lastMap") String> key = getDeep(lastMap); + } + + public void testAssignBoth(Object lastMap, Object newMap) { + @KeyFor("lastMap") List<@KeyFor("newMap") String> key = getBoth(lastMap, newMap); + } + + public void testAssignArray(Object lastMap, Object newMap) { + @KeyFor("lastMap") String @KeyFor("newMap") [] key = getArray(lastMap, newMap); + } } diff --git a/checker/tests/nullness/KeyForSubtyping.java b/checker/tests/nullness/KeyForSubtyping.java index 7ba7654f9a0..bb0e03cd87b 100644 --- a/checker/tests/nullness/KeyForSubtyping.java +++ b/checker/tests/nullness/KeyForSubtyping.java @@ -1,130 +1,129 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; +import org.checkerframework.checker.nullness.qual.*; public class KeyForSubtyping { - HashMap mapA = new HashMap<>(); - HashMap mapB = new HashMap<>(); - HashMap mapC = new HashMap<>(); - - public void testSubtypeAssignments( - String not_a_key, - @KeyFor("this.mapA") String a, - @KeyFor("this.mapB") String b, - @KeyFor({"this.mapA", "this.mapB"}) String ab) { - // Try the error cases first, otherwise dataflow will change the inferred annotations on the - // variables such that a line of code can have an effect on a subsequent line of code. We - // want each of these tests to be independent. - - // :: error: (assignment.type.incompatible) - ab = a; - // :: error: (assignment.type.incompatible) - ab = b; - // :: error: (assignment.type.incompatible) - a = b; - // :: error: (assignment.type.incompatible) - a = not_a_key; - // :: error: (assignment.type.incompatible) - b = not_a_key; - // :: error: (assignment.type.incompatible) - ab = not_a_key; - - // Now try the success cases - - a = ab; - b = ab; - not_a_key = ab; - not_a_key = a; - } - - public void testDataFlow( - String not_yet_a_key, - @KeyFor("this.mapA") String a, - @KeyFor("this.mapB") String b, - @KeyFor({"this.mapA", "this.mapB"}) String ab) { - // Test that when a valid assignment is made, dataflow transfers the - // KeyFor type qualifier from the right hand side to the left hand side. - - // :: error: (argument.type.incompatible) - method1(not_yet_a_key); - not_yet_a_key = a; - method1(not_yet_a_key); - - method1(a); - // :: error: (argument.type.incompatible) - method1(b); - method1(ab); - - b = ab; - method1(b); - } - - public void testSetOrdering( - @KeyFor({"this.mapC", "this.mapA"}) String ac, - @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { - // Test that the order of elements in the set doesn't matter when doing subtyping checks, - // @KeyFor("A, B, C") <: @KeyFor("C, A") - - // Try the error case first - see the note in method testSubtypeAssignments - - // :: error: (assignment.type.incompatible) - abc = ac; - - ac = abc; - } - - public void testDataflowTransitivity( - @KeyFor({"this.mapA"}) String a, - @KeyFor({"this.mapA", "this.mapB"}) String ab, - @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { - ab = abc; - // At this point, dataflow should have refined the type of ab to - // @KeyFor({"this.mapA","this.mapB","this.mapC"}) - a = ab; - // At this point, dataflow should have refined the type of a to - // @KeyFor({"this.mapA","this.mapB","this.mapC"}) - - // This would not succeed without the previous two assignments, but should now because of - // dataflow. - abc = a; - } - - private void method1(@KeyFor("this.mapA") String a) {} - - private void testWithNullnessAnnotation( - String not_a_key, - @KeyFor("this.mapA") String a, - @KeyFor("this.mapB") String b, - @Nullable @KeyFor({"this.mapA", "this.mapB"}) String ab) { - // These fail only because a @Nullable RHS cannot be assigned to a @NonNull LHS. - - // :: error: (assignment.type.incompatible) - a = ab; - // :: error: (assignment.type.incompatible) - b = ab; - // :: error: (assignment.type.incompatible) - not_a_key = ab; - - not_a_key = a; // Succeeds because both sides are @NonNull - } - - // Test overriding - - static class Super { - HashMap map1 = new HashMap<>(); - HashMap map2 = new HashMap<>(); - - void method1(@KeyFor({"this.map1", "this.map2"}) String s) {} - - void method2(@KeyFor("this.map1") String s) {} - } - - static class Sub extends Super { - @Override - void method1(@KeyFor("this.map1") String s) {} - - @Override - // :: error: (override.param.invalid) - void method2(@KeyFor({"this.map1", "this.map2"}) String s) {} - } + HashMap mapA = new HashMap<>(); + HashMap mapB = new HashMap<>(); + HashMap mapC = new HashMap<>(); + + public void testSubtypeAssignments( + String not_a_key, + @KeyFor("this.mapA") String a, + @KeyFor("this.mapB") String b, + @KeyFor({"this.mapA", "this.mapB"}) String ab) { + // Try the error cases first, otherwise dataflow will change the inferred annotations on the + // variables such that a line of code can have an effect on a subsequent line of code. We + // want each of these tests to be independent. + + // :: error: (assignment.type.incompatible) + ab = a; + // :: error: (assignment.type.incompatible) + ab = b; + // :: error: (assignment.type.incompatible) + a = b; + // :: error: (assignment.type.incompatible) + a = not_a_key; + // :: error: (assignment.type.incompatible) + b = not_a_key; + // :: error: (assignment.type.incompatible) + ab = not_a_key; + + // Now try the success cases + + a = ab; + b = ab; + not_a_key = ab; + not_a_key = a; + } + + public void testDataFlow( + String not_yet_a_key, + @KeyFor("this.mapA") String a, + @KeyFor("this.mapB") String b, + @KeyFor({"this.mapA", "this.mapB"}) String ab) { + // Test that when a valid assignment is made, dataflow transfers the + // KeyFor type qualifier from the right hand side to the left hand side. + + // :: error: (argument.type.incompatible) + method1(not_yet_a_key); + not_yet_a_key = a; + method1(not_yet_a_key); + + method1(a); + // :: error: (argument.type.incompatible) + method1(b); + method1(ab); + + b = ab; + method1(b); + } + + public void testSetOrdering( + @KeyFor({"this.mapC", "this.mapA"}) String ac, + @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { + // Test that the order of elements in the set doesn't matter when doing subtyping checks, + // @KeyFor("A, B, C") <: @KeyFor("C, A") + + // Try the error case first - see the note in method testSubtypeAssignments + + // :: error: (assignment.type.incompatible) + abc = ac; + + ac = abc; + } + + public void testDataflowTransitivity( + @KeyFor({"this.mapA"}) String a, + @KeyFor({"this.mapA", "this.mapB"}) String ab, + @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { + ab = abc; + // At this point, dataflow should have refined the type of ab to + // @KeyFor({"this.mapA","this.mapB","this.mapC"}) + a = ab; + // At this point, dataflow should have refined the type of a to + // @KeyFor({"this.mapA","this.mapB","this.mapC"}) + + // This would not succeed without the previous two assignments, but should now because of + // dataflow. + abc = a; + } + + private void method1(@KeyFor("this.mapA") String a) {} + + private void testWithNullnessAnnotation( + String not_a_key, + @KeyFor("this.mapA") String a, + @KeyFor("this.mapB") String b, + @Nullable @KeyFor({"this.mapA", "this.mapB"}) String ab) { + // These fail only because a @Nullable RHS cannot be assigned to a @NonNull LHS. + + // :: error: (assignment.type.incompatible) + a = ab; + // :: error: (assignment.type.incompatible) + b = ab; + // :: error: (assignment.type.incompatible) + not_a_key = ab; + + not_a_key = a; // Succeeds because both sides are @NonNull + } + + // Test overriding + + static class Super { + HashMap map1 = new HashMap<>(); + HashMap map2 = new HashMap<>(); + + void method1(@KeyFor({"this.map1", "this.map2"}) String s) {} + + void method2(@KeyFor("this.map1") String s) {} + } + + static class Sub extends Super { + @Override + void method1(@KeyFor("this.map1") String s) {} + + @Override + // :: error: (override.param.invalid) + void method2(@KeyFor({"this.map1", "this.map2"}) String s) {} + } } diff --git a/checker/tests/nullness/KeyForTypeVar.java b/checker/tests/nullness/KeyForTypeVar.java index f14f9e14e23..cdfa81fbd1b 100644 --- a/checker/tests/nullness/KeyForTypeVar.java +++ b/checker/tests/nullness/KeyForTypeVar.java @@ -1,12 +1,11 @@ +import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.Map; - public class KeyForTypeVar { - <@KeyForBottom E extends @KeyFor("#1") T> T method(Map m, E key) { - @NonNull String s = m.get(key); - throw new RuntimeException(); - } + <@KeyForBottom E extends @KeyFor("#1") T> T method(Map m, E key) { + @NonNull String s = m.get(key); + throw new RuntimeException(); + } } diff --git a/checker/tests/nullness/KeyFor_DirectionsFinder.java b/checker/tests/nullness/KeyFor_DirectionsFinder.java index 9762b45cd20..f5190ade1bc 100644 --- a/checker/tests/nullness/KeyFor_DirectionsFinder.java +++ b/checker/tests/nullness/KeyFor_DirectionsFinder.java @@ -1,45 +1,44 @@ // @skip-test -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; public class KeyFor_DirectionsFinder { - class GeoPoint {} + class GeoPoint {} - class StreetSegment {} + class StreetSegment {} - class Graph { - public void addEdge(StreetSegment endSeg, StreetSegment beginSeg) {} - } + class Graph { + public void addEdge(StreetSegment endSeg, StreetSegment beginSeg) {} + } - public void buildGraph(List segs) { - Map> endMap = new HashMap<>(); - Map<@KeyFor("endMap") GeoPoint, Set> beginMap = new HashMap<>(); - Graph graph = new Graph(); + public void buildGraph(List segs) { + Map> endMap = new HashMap<>(); + Map<@KeyFor("endMap") GeoPoint, Set> beginMap = new HashMap<>(); + Graph graph = new Graph(); - for (StreetSegment seg : segs) { - GeoPoint p1 = new GeoPoint(); + for (StreetSegment seg : segs) { + GeoPoint p1 = new GeoPoint(); - if (!(beginMap.containsKey(p1))) { - endMap.put(p1, new HashSet()); - beginMap.put(p1, new HashSet()); - } - endMap.get(p1).add(seg); - beginMap.get(p1).add(seg); - } + if (!(beginMap.containsKey(p1))) { + endMap.put(p1, new HashSet()); + beginMap.put(p1, new HashSet()); + } + endMap.get(p1).add(seg); + beginMap.get(p1).add(seg); + } - for (@KeyFor("endMap") GeoPoint p : beginMap.keySet()) { - for (StreetSegment beginSeg : beginMap.get(p)) { - for (StreetSegment endSeg : endMap.get(p)) { - graph.addEdge(endSeg, beginSeg); // endSeg and beginSeg are @NonNull - } - } + for (@KeyFor("endMap") GeoPoint p : beginMap.keySet()) { + for (StreetSegment beginSeg : beginMap.get(p)) { + for (StreetSegment endSeg : endMap.get(p)) { + graph.addEdge(endSeg, beginSeg); // endSeg and beginSeg are @NonNull } + } } + } } diff --git a/checker/tests/nullness/KeyFors.java b/checker/tests/nullness/KeyFors.java index d90caf6c5ac..2fa06159d0f 100644 --- a/checker/tests/nullness/KeyFors.java +++ b/checker/tests/nullness/KeyFors.java @@ -1,5 +1,3 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -8,123 +6,124 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; public class KeyFors { - public void withoutKeyFor() { - Map map = new HashMap<>(); - String key = "key"; - - // :: error: (assignment.type.incompatible) - @NonNull String value = map.get(key); - } - - public void withKeyFor() { - Map map = new HashMap<>(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("map") String key = "key"; - - @NonNull String value = map.get(key); - } + public void withoutKeyFor() { + Map map = new HashMap<>(); + String key = "key"; - public void withCollection() { - Map map = new HashMap<>(); - List<@KeyFor("map") String> keys = new ArrayList<>(); + // :: error: (assignment.type.incompatible) + @NonNull String value = map.get(key); + } - @KeyFor("map") String key = keys.get(0); - @NonNull String value = map.get(key); - value = map.get(keys.get(0)); - } + public void withKeyFor() { + Map map = new HashMap<>(); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("map") String key = "key"; - public void withIndirectReference() { - class Container { - Map map = new HashMap<>(); - } + @NonNull String value = map.get(key); + } - Container container = new Container(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("container.map") String key = "m"; + public void withCollection() { + Map map = new HashMap<>(); + List<@KeyFor("map") String> keys = new ArrayList<>(); - @NonNull String value = container.map.get(key); - } + @KeyFor("map") String key = keys.get(0); + @NonNull String value = map.get(key); + value = map.get(keys.get(0)); + } - /** Returns a sorted version of m.keySet(). */ - public static , V> Collection<@KeyFor("#1") K> sortedKeySet( - Map m) { - throw new RuntimeException(); + public void withIndirectReference() { + class Container { + Map map = new HashMap<>(); } - static HashMap call_hashmap = new HashMap<>(); + Container container = new Container(); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("container.map") String key = "m"; - public void testForLoop(HashMap lastMap) { - Collection<@KeyFor("lastMap") String> sorted = sortedKeySet(lastMap); - for (@KeyFor("lastMap") String key : sorted) { - @NonNull String al = lastMap.get(key); - } - for (@KeyFor("call_hashmap") Integer i : sortedKeySet(call_hashmap)) {} - } + @NonNull String value = container.map.get(key); + } - static class Otherclass { - static Map map = new HashMap<>(); - } + /** Returns a sorted version of m.keySet(). */ + public static , V> Collection<@KeyFor("#1") K> sortedKeySet( + Map m) { + throw new RuntimeException(); + } - public void testStaticKeyFor(@KeyFor("Otherclass.map") String s1, String s2) { - Otherclass.map.get(s1).toString(); - // :: error: (dereference.of.nullable) - Otherclass.map.get(s2).toString(); + static HashMap call_hashmap = new HashMap<>(); - Otherclass o = new Otherclass(); - o.map.get(s1).toString(); - // TODO:: error: (dereference.of.nullable) - o.map.get(s2).toString(); + public void testForLoop(HashMap lastMap) { + Collection<@KeyFor("lastMap") String> sorted = sortedKeySet(lastMap); + for (@KeyFor("lastMap") String key : sorted) { + @NonNull String al = lastMap.get(key); } + for (@KeyFor("call_hashmap") Integer i : sortedKeySet(call_hashmap)) {} + } - public class Graph { - - HashMap> childMap; + static class Otherclass { + static Map map = new HashMap<>(); + } - public Graph(HashMap> childMap) { - this.childMap = childMap; - } + public void testStaticKeyFor(@KeyFor("Otherclass.map") String s1, String s2) { + Otherclass.map.get(s1).toString(); + // :: error: (dereference.of.nullable) + Otherclass.map.get(s2).toString(); - public void addNode(T n) { - // body omitted, not relevant to test case - } + Otherclass o = new Otherclass(); + o.map.get(s1).toString(); + // TODO:: error: (dereference.of.nullable) + o.map.get(s2).toString(); + } - public void addEdge2(T parent, T child) { - addNode(parent); - @SuppressWarnings("cast.unsafe") - @KeyFor("childMap") T parent2 = (@KeyFor("childMap") T) parent; - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent2); - } + public class Graph { - // TODO: This is a feature request to have KeyFor inferred - // public void addEdge3( T parent, T child ) { - // addNode(parent); - // parent = (@KeyFor("childMap") T) parent; - // @NonNull List l = childMap.get(parent); - // } + HashMap> childMap; + public Graph(HashMap> childMap) { + this.childMap = childMap; } - /* TODO: add logic that after a call to "put" the first argument is - annotated with @KeyFor. A "@KeyForAfter" annotation to - support this in a general way might be overkill. - Similarly, for calls to "remove" we need to invalidate all (?) - KeyFor annotations.*/ - - void keyForFlow() { - Map leaders = new LinkedHashMap<>(); - Set<@KeyFor("leaders") String> varsUsedPreviously = - new LinkedHashSet<@KeyFor("leaders") String>(); - String varName = "hello"; - leaders.put(varName, "goodbye"); - @KeyFor("leaders") String kf = varName; + public void addNode(T n) { + // body omitted, not relevant to test case } - public static void mapPut(String start) { - Map n2e = new HashMap<>(); - n2e.put(start, Integer.valueOf(0)); - @KeyFor("n2e") String start2 = start; + public void addEdge2(T parent, T child) { + addNode(parent); + @SuppressWarnings("cast.unsafe") + @KeyFor("childMap") T parent2 = (@KeyFor("childMap") T) parent; + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent2); } + + // TODO: This is a feature request to have KeyFor inferred + // public void addEdge3( T parent, T child ) { + // addNode(parent); + // parent = (@KeyFor("childMap") T) parent; + // @NonNull List l = childMap.get(parent); + // } + + } + + /* TODO: add logic that after a call to "put" the first argument is + annotated with @KeyFor. A "@KeyForAfter" annotation to + support this in a general way might be overkill. + Similarly, for calls to "remove" we need to invalidate all (?) + KeyFor annotations.*/ + + void keyForFlow() { + Map leaders = new LinkedHashMap<>(); + Set<@KeyFor("leaders") String> varsUsedPreviously = + new LinkedHashSet<@KeyFor("leaders") String>(); + String varName = "hello"; + leaders.put(varName, "goodbye"); + @KeyFor("leaders") String kf = varName; + } + + public static void mapPut(String start) { + Map n2e = new HashMap<>(); + n2e.put(start, Integer.valueOf(0)); + @KeyFor("n2e") String start2 = start; + } } diff --git a/checker/tests/nullness/Lazy.java b/checker/tests/nullness/Lazy.java index 20afaa22b73..c1ac9a9fe66 100644 --- a/checker/tests/nullness/Lazy.java +++ b/checker/tests/nullness/Lazy.java @@ -3,55 +3,55 @@ public class Lazy { - @NonNull String f; - @MonotonicNonNull String g; - @MonotonicNonNull String g2; - @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g; - @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g2; - - // Initialization with null is allowed for legacy reasons. - @MonotonicNonNull String init = null; - - public Lazy() { - f = ""; - // does not have to initialize g - } - - void test() { - g = ""; - test2(); // retain non-null property across method calls - g.toLowerCase(); - } - - void _test() { - _g = ""; - test2(); // retain non-null property across method calls - _g.toLowerCase(); - } - - void test2() {} - - void test3() { - // :: error: (dereference.of.nullable) - g.toLowerCase(); - } - - void test4() { - // :: error: (assignment.type.incompatible) - g = null; - // :: error: (monotonic.type.incompatible) - g = g2; - } - - void _test3() { - // :: error: (dereference.of.nullable) - _g.toLowerCase(); - } - - void _test4() { - // :: error: (assignment.type.incompatible) - _g = null; - // :: error: (monotonic.type.incompatible) - _g = _g2; - } + @NonNull String f; + @MonotonicNonNull String g; + @MonotonicNonNull String g2; + @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g; + @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g2; + + // Initialization with null is allowed for legacy reasons. + @MonotonicNonNull String init = null; + + public Lazy() { + f = ""; + // does not have to initialize g + } + + void test() { + g = ""; + test2(); // retain non-null property across method calls + g.toLowerCase(); + } + + void _test() { + _g = ""; + test2(); // retain non-null property across method calls + _g.toLowerCase(); + } + + void test2() {} + + void test3() { + // :: error: (dereference.of.nullable) + g.toLowerCase(); + } + + void test4() { + // :: error: (assignment.type.incompatible) + g = null; + // :: error: (monotonic.type.incompatible) + g = g2; + } + + void _test3() { + // :: error: (dereference.of.nullable) + _g.toLowerCase(); + } + + void _test4() { + // :: error: (assignment.type.incompatible) + _g = null; + // :: error: (monotonic.type.incompatible) + _g = _g2; + } } diff --git a/checker/tests/nullness/LazyInitialization.java b/checker/tests/nullness/LazyInitialization.java index 5f44913c446..2466f13fae8 100644 --- a/checker/tests/nullness/LazyInitialization.java +++ b/checker/tests/nullness/LazyInitialization.java @@ -1,100 +1,100 @@ import org.checkerframework.checker.nullness.qual.*; public class LazyInitialization { - @Nullable Object nullable; - @NonNull Object nonnull; - @MonotonicNonNull Object lazy; - @MonotonicNonNull Object lazy2 = null; - final @Nullable Object lazy3; - - public LazyInitialization(@Nullable Object arg) { - lazy3 = arg; - nonnull = new Object(); + @Nullable Object nullable; + @NonNull Object nonnull; + @MonotonicNonNull Object lazy; + @MonotonicNonNull Object lazy2 = null; + final @Nullable Object lazy3; + + public LazyInitialization(@Nullable Object arg) { + lazy3 = arg; + nonnull = new Object(); + } + + void randomMethod() {} + + void testAssignment() { + lazy = "m"; + // :: error: (assignment.type.incompatible) + lazy = null; // null + } + + void testLazyBeingNull() { + // :: error: (dereference.of.nullable) + nullable.toString(); // error + nonnull.toString(); + // :: error: (dereference.of.nullable) + lazy.toString(); // error + // :: error: (dereference.of.nullable) + lazy3.toString(); // error + } + + void testAfterInvocation() { + nullable = "m"; + nonnull = "m"; + lazy = "m"; + if (lazy3 == null) { + return; } - void randomMethod() {} + randomMethod(); - void testAssignment() { - lazy = "m"; - // :: error: (assignment.type.incompatible) - lazy = null; // null - } - - void testLazyBeingNull() { - // :: error: (dereference.of.nullable) - nullable.toString(); // error - nonnull.toString(); - // :: error: (dereference.of.nullable) - lazy.toString(); // error - // :: error: (dereference.of.nullable) - lazy3.toString(); // error - } - - void testAfterInvocation() { - nullable = "m"; - nonnull = "m"; - lazy = "m"; - if (lazy3 == null) { - return; - } - - randomMethod(); - - // :: error: (dereference.of.nullable) - nullable.toString(); // error - nonnull.toString(); - lazy.toString(); - lazy3.toString(); - } - - private double @MonotonicNonNull [] intersect; + // :: error: (dereference.of.nullable) + nullable.toString(); // error + nonnull.toString(); + lazy.toString(); + lazy3.toString(); + } - public void check_modified(double[] a, int count) { - if (intersect != null) { - double @NonNull [] nnda = intersect; - } - } + private double @MonotonicNonNull [] intersect; - class PptRelation1 { - public void init_hierarchy_new(PptTopLevel ppt, Object eq) { - ppt.equality_view = eq; - ppt.equality_view.toString(); - } + public void check_modified(double[] a, int count) { + if (intersect != null) { + double @NonNull [] nnda = intersect; } + } - class PptTopLevel { - public @MonotonicNonNull Object equality_view; + class PptRelation1 { + public void init_hierarchy_new(PptTopLevel ppt, Object eq) { + ppt.equality_view = eq; + ppt.equality_view.toString(); } - - class PptRelation1b { - // This is the same code as in PptRelation1, but comes after the class - // declaration of PptTopLevel. This works as expected. - public void init_hierarchy_new(PptTopLevel ppt, Object eq) { - ppt.equality_view = eq; - ppt.equality_view.toString(); - } + } + + class PptTopLevel { + public @MonotonicNonNull Object equality_view; + } + + class PptRelation1b { + // This is the same code as in PptRelation1, but comes after the class + // declaration of PptTopLevel. This works as expected. + public void init_hierarchy_new(PptTopLevel ppt, Object eq) { + ppt.equality_view = eq; + ppt.equality_view.toString(); } + } - class PptRelation2 { - public @MonotonicNonNull Object equality_view2; + class PptRelation2 { + public @MonotonicNonNull Object equality_view2; - public void init_hierarchy_new(PptRelation2 pr1, PptRelation2 pr2, Object eq) { - // :: error: (dereference.of.nullable) - pr1.equality_view2.toString(); + public void init_hierarchy_new(PptRelation2 pr1, PptRelation2 pr2, Object eq) { + // :: error: (dereference.of.nullable) + pr1.equality_view2.toString(); - pr1.equality_view2 = eq; - pr1.equality_view2.toString(); + pr1.equality_view2 = eq; + pr1.equality_view2.toString(); - // :: error: (dereference.of.nullable) - pr2.equality_view2.toString(); - // :: error: (dereference.of.nullable) - this.equality_view2.toString(); + // :: error: (dereference.of.nullable) + pr2.equality_view2.toString(); + // :: error: (dereference.of.nullable) + this.equality_view2.toString(); - pr2.equality_view2 = eq; - pr2.equality_view2.toString(); + pr2.equality_view2 = eq; + pr2.equality_view2.toString(); - this.equality_view2 = eq; - this.equality_view2.toString(); - } + this.equality_view2 = eq; + this.equality_view2.toString(); } + } } diff --git a/checker/tests/nullness/LogRecordTest.java b/checker/tests/nullness/LogRecordTest.java index ffd91f7b88a..9d605da6bbd 100644 --- a/checker/tests/nullness/LogRecordTest.java +++ b/checker/tests/nullness/LogRecordTest.java @@ -5,22 +5,22 @@ public class LogRecordTest { - void test(Level level) { + void test(Level level) { - LogRecord logRecord = new LogRecord(level, null); + LogRecord logRecord = new LogRecord(level, null); - logRecord.setLoggerName(null); + logRecord.setLoggerName(null); - logRecord.setResourceBundle(null); + logRecord.setResourceBundle(null); - logRecord.setSourceClassName(null); + logRecord.setSourceClassName(null); - logRecord.setMessage(null); + logRecord.setMessage(null); - logRecord.setSourceMethodName(null); + logRecord.setSourceMethodName(null); - logRecord.setParameters(null); + logRecord.setParameters(null); - logRecord.setThrown(null); - } + logRecord.setThrown(null); + } } diff --git a/checker/tests/nullness/LogicOperations.java b/checker/tests/nullness/LogicOperations.java index 3333958daf6..deb9973b08b 100644 --- a/checker/tests/nullness/LogicOperations.java +++ b/checker/tests/nullness/LogicOperations.java @@ -2,71 +2,71 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class LogicOperations { - void andTrueClause(@Nullable Object a) { - if (a != null && helper()) { - a.toString(); - } + void andTrueClause(@Nullable Object a) { + if (a != null && helper()) { + a.toString(); } + } - void andTrueClauseReverse(@Nullable Object a) { - if (helper() && a != null) { - a.toString(); - } + void andTrueClauseReverse(@Nullable Object a) { + if (helper() && a != null) { + a.toString(); } + } - void oneAndComplement(@Nullable Object a) { - if (a != null && helper()) { - a.toString(); - return; - } - // :: error: (dereference.of.nullable) - a.toString(); // error + void oneAndComplement(@Nullable Object a) { + if (a != null && helper()) { + a.toString(); + return; } + // :: error: (dereference.of.nullable) + a.toString(); // error + } - void repAndComplement(@Nullable Object a, @Nullable Object b) { - if (a == null && b == null) { - // :: error: (dereference.of.nullable) - a.toString(); // error - return; - } - // :: error: (dereference.of.nullable) - a.toString(); // error + void repAndComplement(@Nullable Object a, @Nullable Object b) { + if (a == null && b == null) { + // :: error: (dereference.of.nullable) + a.toString(); // error + return; } + // :: error: (dereference.of.nullable) + a.toString(); // error + } - void oneOrComplement(@Nullable Object a) { - if (a == null || helper()) { - // :: error: (dereference.of.nullable) - a.toString(); // error - return; - } - a.toString(); + void oneOrComplement(@Nullable Object a) { + if (a == null || helper()) { + // :: error: (dereference.of.nullable) + a.toString(); // error + return; } + a.toString(); + } - void simpleOr1(@Nullable Object a, @Nullable Object b) { - if (a != null || b != null) { - // :: error: (dereference.of.nullable) - a.toString(); // error - } + void simpleOr1(@Nullable Object a, @Nullable Object b) { + if (a != null || b != null) { + // :: error: (dereference.of.nullable) + a.toString(); // error } + } - void simpleOr2(@Nullable Object a, @Nullable Object b) { - if (a != null || b != null) { - // :: error: (dereference.of.nullable) - b.toString(); // error - } + void simpleOr2(@Nullable Object a, @Nullable Object b) { + if (a != null || b != null) { + // :: error: (dereference.of.nullable) + b.toString(); // error } + } - void sideeffect() { - Object a = "m"; - if ((a = null) != "n") { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = a; - } - // :: error: (assignment.type.incompatible) - @NonNull Object l2 = a; + void sideeffect() { + Object a = "m"; + if ((a = null) != "n") { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = a; } + // :: error: (assignment.type.incompatible) + @NonNull Object l2 = a; + } - static boolean helper() { - return true; - } + static boolean helper() { + return true; + } } diff --git a/checker/tests/nullness/LubTest.java b/checker/tests/nullness/LubTest.java index b5e1ad2df9e..708cb4d1318 100644 --- a/checker/tests/nullness/LubTest.java +++ b/checker/tests/nullness/LubTest.java @@ -2,25 +2,25 @@ public class LubTest { - @Nullable String str; + @Nullable String str; - public void setStr(@Nullable String text) { - str = text; - } + public void setStr(@Nullable String text) { + str = text; + } - public @Nullable String getStr() { - return str; - } + public @Nullable String getStr() { + return str; + } - public void ok(@Nullable LubTest t) { - if (t == null) { - this.setStr(""); - } else { - this.setStr(t.getStr()); - } + public void ok(@Nullable LubTest t) { + if (t == null) { + this.setStr(""); + } else { + this.setStr(t.getStr()); } + } - public void notok(@Nullable LubTest t) { - this.setStr((t == null) ? "" : t.getStr()); - } + public void notok(@Nullable LubTest t) { + this.setStr((t == null) ? "" : t.getStr()); + } } diff --git a/checker/tests/nullness/MapGetNullable.java b/checker/tests/nullness/MapGetNullable.java index 306098ce470..514350ab758 100644 --- a/checker/tests/nullness/MapGetNullable.java +++ b/checker/tests/nullness/MapGetNullable.java @@ -1,147 +1,146 @@ +import java.util.HashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.HashMap; -import java.util.Map; - public class MapGetNullable { - void foo0(Map m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + void foo0(Map m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - V get0(Map m, @KeyFor("#1") String key) { - return m.get(key); - } + V get0(Map m, @KeyFor("#1") String key) { + return m.get(key); + } - public static class MyMap1 extends HashMap { - // TODO: These test cases do not work yet, because of the generic types. - // void useget(@KeyFor("this") String k) { - // V val = get(k); - // } - // void useget2(@KeyFor("this") String k) { - // V val = this.get(k); - // } - } - - void foo1(MyMap1 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + public static class MyMap1 extends HashMap { + // TODO: These test cases do not work yet, because of the generic types. + // void useget(@KeyFor("this") String k) { + // V val = get(k); + // } + // void useget2(@KeyFor("this") String k) { + // V val = this.get(k); + // } + } - V get1(MyMap1 m, @KeyFor("#1") String key) { - return m.get(key); - } + void foo1(MyMap1 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - public static class MyMap2 extends HashMap {} + V get1(MyMap1 m, @KeyFor("#1") String key) { + return m.get(key); + } - void foo2(MyMap2<@Nullable Integer, String> m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + public static class MyMap2 extends HashMap {} - V get2(MyMap2 m, @KeyFor("#1") String key) { - return m.get(key); - } + void foo2(MyMap2<@Nullable Integer, String> m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - public static class MyMap3 extends HashMap {} + V get2(MyMap2 m, @KeyFor("#1") String key) { + return m.get(key); + } - void foo3(MyMap3 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + public static class MyMap3 extends HashMap {} - @Nullable Integer get3(MyMap3 m, @KeyFor("#1") String key) { - return m.get(key); - } + void foo3(MyMap3 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - public static class MyMap4 extends HashMap {} + @Nullable Integer get3(MyMap3 m, @KeyFor("#1") String key) { + return m.get(key); + } - void foo4(MyMap4 m, @KeyFor("#1") String key) { - Integer val = m.get(key); - } + public static class MyMap4 extends HashMap {} - Integer get4(MyMap4 m, @KeyFor("#1") String key) { - return m.get(key); - } + void foo4(MyMap4 m, @KeyFor("#1") String key) { + Integer val = m.get(key); + } - public static class MyMap5 extends HashMap {} + Integer get4(MyMap4 m, @KeyFor("#1") String key) { + return m.get(key); + } - void foo5(MyMap5<@Nullable Integer> m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + public static class MyMap5 extends HashMap {} - V get5(MyMap5 m, @KeyFor("#1") String key) { - return m.get(key); - } + void foo5(MyMap5<@Nullable Integer> m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - public static class MyMap6 extends HashMap { - void useget(@KeyFor("this") String k) { - @NonNull Integer val = get(k); - } + V get5(MyMap5 m, @KeyFor("#1") String key) { + return m.get(key); + } - void useget2(@KeyFor("this") String k) { - @NonNull Integer val = this.get(k); - } + public static class MyMap6 extends HashMap { + void useget(@KeyFor("this") String k) { + @NonNull Integer val = get(k); } - void foo6(MyMap6 m, @KeyFor("#1") String key) { - @NonNull Integer val = m.get(key); + void useget2(@KeyFor("this") String k) { + @NonNull Integer val = this.get(k); } + } - Integer get6(MyMap6 m, @KeyFor("#1") String key) { - return m.get(key); - } + void foo6(MyMap6 m, @KeyFor("#1") String key) { + @NonNull Integer val = m.get(key); + } - public static class MyMap7 extends HashMap { - void useget(@KeyFor("this") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = get(k); - } + Integer get6(MyMap6 m, @KeyFor("#1") String key) { + return m.get(key); + } - void useget2(@KeyFor("this") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = this.get(k); - } + public static class MyMap7 extends HashMap { + void useget(@KeyFor("this") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = get(k); } - void foo7(MyMap7 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); + void useget2(@KeyFor("this") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = this.get(k); } + } - Integer get7(MyMap7 m, @KeyFor("#1") String key) { - // :: error: (return.type.incompatible) - return m.get(key); - } + void foo7(MyMap7 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - // MyMap9 ensures that no changes are made to the return type of overloaded versions of get(). + Integer get7(MyMap7 m, @KeyFor("#1") String key) { + // :: error: (return.type.incompatible) + return m.get(key); + } - public static class MyMap9 extends HashMap { - @Nullable V get(@Nullable Object key, int itIsOverloaded) { - return null; - } - } + // MyMap9 ensures that no changes are made to the return type of overloaded versions of get(). - void foo9(MyMap9 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); + public static class MyMap9 extends HashMap { + @Nullable V get(@Nullable Object key, int itIsOverloaded) { + return null; } + } - void foo9a(MyMap9 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key, 22); - } + void foo9(MyMap9 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - V get9(MyMap9 m, @KeyFor("#1") String key) { - return m.get(key); - } + void foo9a(MyMap9 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key, 22); + } - V get9a(MyMap9 m, @KeyFor("#1") String key) { - // :: error: (return.type.incompatible) - return m.get(key, 22); - } + V get9(MyMap9 m, @KeyFor("#1") String key) { + return m.get(key); + } + + V get9a(MyMap9 m, @KeyFor("#1") String key) { + // :: error: (return.type.incompatible) + return m.get(key, 22); + } } diff --git a/checker/tests/nullness/MapMerge.java b/checker/tests/nullness/MapMerge.java index 24922889f36..fae8eacc58d 100644 --- a/checker/tests/nullness/MapMerge.java +++ b/checker/tests/nullness/MapMerge.java @@ -3,23 +3,23 @@ import java.util.function.BiFunction; public class MapMerge { - public static void main(String[] args) { - Map map = new HashMap<>(); - map.put("k", "v"); - // :: error: (return.type.incompatible) - map.merge("k", "v", (a, b) -> null).toString(); - } + public static void main(String[] args) { + Map map = new HashMap<>(); + map.put("k", "v"); + // :: error: (return.type.incompatible) + map.merge("k", "v", (a, b) -> null).toString(); + } - void foo(Map map) { - // :: error: (return.type.incompatible) - merge(map, "k", "v", (a, b) -> null).toString(); - } + void foo(Map map) { + // :: error: (return.type.incompatible) + merge(map, "k", "v", (a, b) -> null).toString(); + } - V merge( - Map map, - K key, - V value, - BiFunction remappingFunction) { - return value; - } + V merge( + Map map, + K key, + V value, + BiFunction remappingFunction) { + return value; + } } diff --git a/checker/tests/nullness/Marino.java b/checker/tests/nullness/Marino.java index 028cef7bc61..69fde3ba53a 100644 --- a/checker/tests/nullness/Marino.java +++ b/checker/tests/nullness/Marino.java @@ -4,62 +4,62 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class Marino { - @NonNull String m_str; - static String ms_str; - String m_nullableStr; + @NonNull String m_str; + static String ms_str; + String m_nullableStr; - public Marino(@NonNull String m_str, String m_nullableStr) { - this.m_str = m_str; - this.m_nullableStr = m_nullableStr; + public Marino(@NonNull String m_str, String m_nullableStr) { + this.m_str = m_str; + this.m_nullableStr = m_nullableStr; + } + + void testWhile() throws Exception { + String s = "foo"; + while (true) { + @NonNull String a = s; + System.out.println("a has length: " + a.length()); + break; } + int i = 1; + while (true) { - void testWhile() throws Exception { - String s = "foo"; - while (true) { - @NonNull String a = s; - System.out.println("a has length: " + a.length()); - break; - } - int i = 1; - while (true) { + @NonNull String a = s; // s cannot be null here + s = null; + // :: error: (dereference.of.nullable) + System.out.println("hi" + s.length()); + if (i > 2) break; + // :: error: (assignment.type.incompatible) + a = null; + } + // Checker doesn't catch that m_str not initialized. + // This is Caveat 2 in the manual, but note that it is not limited to contructors. + System.out.println("Member string has length: " + m_str.length()); - @NonNull String a = s; // s cannot be null here - s = null; + // Dereference of any static field is allowed. + // I suppose this is a design decision for practicality in interacting with libraries...? + // :: error: (dereference.of.nullable) + System.out.println("Member string has length: " + ms_str.length()); + System.out.println( + "Everyone should get this error: " + + // :: error: (dereference.of.nullable) - System.out.println("hi" + s.length()); - if (i > 2) break; - // :: error: (assignment.type.incompatible) - a = null; - } - // Checker doesn't catch that m_str not initialized. - // This is Caveat 2 in the manual, but note that it is not limited to contructors. - System.out.println("Member string has length: " + m_str.length()); + m_nullableStr.length()); - // Dereference of any static field is allowed. - // I suppose this is a design decision for practicality in interacting with libraries...? - // :: error: (dereference.of.nullable) - System.out.println("Member string has length: " + ms_str.length()); - System.out.println( - "Everyone should get this error: " - + - // :: error: (dereference.of.nullable) - m_nullableStr.length()); - - s = null; - @NonNull String b = "hi"; - try { - System.out.println("b has length: " + b.length()); - methodThatThrowsEx(); - s = "bye"; - } finally { - // Checker doesn't catch that s will be null here. - // :: error: (assignment.type.incompatible) - b = s; - System.out.println("b has length: " + b.length()); - } + s = null; + @NonNull String b = "hi"; + try { + System.out.println("b has length: " + b.length()); + methodThatThrowsEx(); + s = "bye"; + } finally { + // Checker doesn't catch that s will be null here. + // :: error: (assignment.type.incompatible) + b = s; + System.out.println("b has length: " + b.length()); } + } - void methodThatThrowsEx() throws Exception { - throw new Exception(); - } + void methodThatThrowsEx() throws Exception { + throw new Exception(); + } } diff --git a/checker/tests/nullness/MethodOverloadingContractsKeyFor.java b/checker/tests/nullness/MethodOverloadingContractsKeyFor.java index 8ad6f6ad193..9afffdde326 100644 --- a/checker/tests/nullness/MethodOverloadingContractsKeyFor.java +++ b/checker/tests/nullness/MethodOverloadingContractsKeyFor.java @@ -1,42 +1,41 @@ -import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; -import org.checkerframework.dataflow.qual.Pure; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; +import org.checkerframework.dataflow.qual.Pure; public class MethodOverloadingContractsKeyFor { - static class ClassA {} + static class ClassA {} - static class ClassB extends ClassA {} + static class ClassB extends ClassA {} - @Pure - String name(ClassA classA) { - return "asClassA"; - } + @Pure + String name(ClassA classA) { + return "asClassA"; + } - @Pure - Object name(ClassB classB) { - return "asClassB"; - } + @Pure + Object name(ClassB classB) { + return "asClassB"; + } - Map map = new HashMap<>(); + Map map = new HashMap<>(); - @EnsuresKeyFor(value = "name(#1)", map = "map") - void put(ClassA classA) { - map.put(name(classA), ""); - } + @EnsuresKeyFor(value = "name(#1)", map = "map") + void put(ClassA classA) { + map.put(name(classA), ""); + } - void test(ClassA classA, ClassB classB) { - put(classA); - map.get(name(classA)).toString(); + void test(ClassA classA, ClassB classB) { + put(classA); + map.get(name(classA)).toString(); - put(classB); - // :: error: (dereference.of.nullable) - map.get(name(classB)).toString(); - } + put(classB); + // :: error: (dereference.of.nullable) + map.get(name(classB)).toString(); + } - public static void main(String[] args) { - new MethodOverloadingContractsKeyFor().test(new ClassA(), new ClassB()); - } + public static void main(String[] args) { + new MethodOverloadingContractsKeyFor().test(new ClassA(), new ClassB()); + } } diff --git a/checker/tests/nullness/MethodTypeVars4.java b/checker/tests/nullness/MethodTypeVars4.java index efa4846b4d3..fd95e400c6d 100644 --- a/checker/tests/nullness/MethodTypeVars4.java +++ b/checker/tests/nullness/MethodTypeVars4.java @@ -1,30 +1,29 @@ +import java.util.List; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; -import java.util.List; - public class MethodTypeVars4 { - @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND) - interface I { - T doit(); + @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND) + interface I { + T doit(); - List doit2(); + List doit2(); - T doit3(); - } + T doit3(); + } - void f1(I i) { - // s is implicitly Nullable - String s = i.doit(); - List ls = i.doit2(); - String s2 = i.doit3(); - } + void f1(I i) { + // s is implicitly Nullable + String s = i.doit(); + List ls = i.doit2(); + String s2 = i.doit3(); + } - void f2(I i) { - @NonNull String s = i.doit(); - s = i.doit3(); - // :: error: (type.argument.type.incompatible) - List<@Nullable String> ls = i.doit2(); - } + void f2(I i) { + @NonNull String s = i.doit(); + s = i.doit3(); + // :: error: (type.argument.type.incompatible) + List<@Nullable String> ls = i.doit2(); + } } diff --git a/checker/tests/nullness/MissingBoundAnnotations.java b/checker/tests/nullness/MissingBoundAnnotations.java index 096ca97ea12..c96a8bdea44 100644 --- a/checker/tests/nullness/MissingBoundAnnotations.java +++ b/checker/tests/nullness/MissingBoundAnnotations.java @@ -1,22 +1,21 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public final class MissingBoundAnnotations { - public static , V> Collection<@KeyFor("#1") K> sortedKeySet( - Map m) { - ArrayList<@KeyFor("m") K> theKeys = new ArrayList<>(m.keySet()); - Collections.sort(theKeys); - return theKeys; - } + public static , V> Collection<@KeyFor("#1") K> sortedKeySet( + Map m) { + ArrayList<@KeyFor("m") K> theKeys = new ArrayList<>(m.keySet()); + Collections.sort(theKeys); + return theKeys; + } - public static , V> - Collection<@KeyFor("#1") K> sortedKeySetSimpler(ArrayList<@KeyFor("#1") K> theKeys) { - Collections.sort(theKeys); - return theKeys; - } + public static , V> + Collection<@KeyFor("#1") K> sortedKeySetSimpler(ArrayList<@KeyFor("#1") K> theKeys) { + Collections.sort(theKeys); + return theKeys; + } } diff --git a/checker/tests/nullness/MisuseProperties.java b/checker/tests/nullness/MisuseProperties.java index 999dabce412..6872b03dca0 100644 --- a/checker/tests/nullness/MisuseProperties.java +++ b/checker/tests/nullness/MisuseProperties.java @@ -1,5 +1,3 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.Collection; import java.util.Collections; import java.util.Dictionary; @@ -7,78 +5,79 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; public class MisuseProperties { - void propertiesToHashtable(Properties p) { - // :: error: (argument.type.incompatible) - p.setProperty("line.separator", null); - // :: error: (argument.type.incompatible) - p.put("line.separator", null); - Hashtable h = p; - // Error, because HashTable value has NonNull bound. - // put(K,V) as a member of the raw type java.util.Hashtable - // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type - // java.util.Hashtable - // :: error: (argument.type.incompatible) - h.put("line.separator", null); - // :: error: (argument.type.incompatible) - System.setProperty("line.separator", null); - - Dictionary d1 = p; - // No error, because Dictionary value has Nullable bound. - // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type - // java.util.Dictionary - d1.put("line.separator", null); - - // :: error: (assignment.type.incompatible) - Dictionary d2 = p; - d2.put("line.separator", null); - - // :: error: (clear.system.property) - System.setProperties(p); // OK; p has no null values - - System.clearProperty("foo.bar"); // OK - - // Each of the following should cause an error, because it leaves line.separator null. - - // These first few need to be special-cased, I think: - - // :: error: (clear.system.property) - System.clearProperty("line.separator"); - - p.remove("line.separator"); - p.clear(); - - // These are OK because they seem to only add, not remove, properties: - // p.load(InputStream), p.load(Reader), p.loadFromXML(InputStream) - - // The following problems are a result of treating a Properties as one - // of its supertypes. Here are some solutions: - // * Forbid treating a Properties object as any of its supertypes. - // * Create an annotation on a Properties object, such as - // @HasSystemProperties, and forbid some operations (or any - // treatment as a supertype) for such properties. - - Set<@KeyFor("p") Object> keys = p.keySet(); - // now remove "line.separator" from the set - keys.remove("line.separator"); - keys.removeAll(keys); - keys.clear(); - keys.retainAll(Collections.EMPTY_SET); - - Set> entries = p.entrySet(); - // now remove the pair containing "line.separator" from the set, as above - - Collection values = p.values(); - // now remove the line separator value from values, as above - - Hashtable h9 = p; - h9.remove("line.separator"); - h9.clear(); - // also access via entrySet, keySet, values - - Dictionary d9 = p; - d9.remove("line.separator"); - } + void propertiesToHashtable(Properties p) { + // :: error: (argument.type.incompatible) + p.setProperty("line.separator", null); + // :: error: (argument.type.incompatible) + p.put("line.separator", null); + Hashtable h = p; + // Error, because HashTable value has NonNull bound. + // put(K,V) as a member of the raw type java.util.Hashtable + // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type + // java.util.Hashtable + // :: error: (argument.type.incompatible) + h.put("line.separator", null); + // :: error: (argument.type.incompatible) + System.setProperty("line.separator", null); + + Dictionary d1 = p; + // No error, because Dictionary value has Nullable bound. + // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type + // java.util.Dictionary + d1.put("line.separator", null); + + // :: error: (assignment.type.incompatible) + Dictionary d2 = p; + d2.put("line.separator", null); + + // :: error: (clear.system.property) + System.setProperties(p); // OK; p has no null values + + System.clearProperty("foo.bar"); // OK + + // Each of the following should cause an error, because it leaves line.separator null. + + // These first few need to be special-cased, I think: + + // :: error: (clear.system.property) + System.clearProperty("line.separator"); + + p.remove("line.separator"); + p.clear(); + + // These are OK because they seem to only add, not remove, properties: + // p.load(InputStream), p.load(Reader), p.loadFromXML(InputStream) + + // The following problems are a result of treating a Properties as one + // of its supertypes. Here are some solutions: + // * Forbid treating a Properties object as any of its supertypes. + // * Create an annotation on a Properties object, such as + // @HasSystemProperties, and forbid some operations (or any + // treatment as a supertype) for such properties. + + Set<@KeyFor("p") Object> keys = p.keySet(); + // now remove "line.separator" from the set + keys.remove("line.separator"); + keys.removeAll(keys); + keys.clear(); + keys.retainAll(Collections.EMPTY_SET); + + Set> entries = p.entrySet(); + // now remove the pair containing "line.separator" from the set, as above + + Collection values = p.values(); + // now remove the line separator value from values, as above + + Hashtable h9 = p; + h9.remove("line.separator"); + h9.clear(); + // also access via entrySet, keySet, values + + Dictionary d9 = p; + d9.remove("line.separator"); + } } diff --git a/checker/tests/nullness/MonotonicNonNullFieldTest.java b/checker/tests/nullness/MonotonicNonNullFieldTest.java index cb69a4f1335..3666d5ddb16 100644 --- a/checker/tests/nullness/MonotonicNonNullFieldTest.java +++ b/checker/tests/nullness/MonotonicNonNullFieldTest.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.nullness.qual.*; public class MonotonicNonNullFieldTest { - class Data { - @MonotonicNonNull Object field; - } + class Data { + @MonotonicNonNull Object field; + } - void method(Object object) {} + void method(Object object) {} - @RequiresNonNull("#1.field") - void test(final Data data) { - method(data.field); // checks OK + @RequiresNonNull("#1.field") + void test(final Data data) { + method(data.field); // checks OK - Runnable callback = - new Runnable() { - public void run() { - method(data.field); // used to issue error - } - }; - } + Runnable callback = + new Runnable() { + public void run() { + method(data.field); // used to issue error + } + }; + } } diff --git a/checker/tests/nullness/MonotonicNonNullTest.java b/checker/tests/nullness/MonotonicNonNullTest.java index 39c295025e1..8059c00e87a 100644 --- a/checker/tests/nullness/MonotonicNonNullTest.java +++ b/checker/tests/nullness/MonotonicNonNullTest.java @@ -2,15 +2,15 @@ public final class MonotonicNonNullTest { - public static @MonotonicNonNull Boolean new_decl_format = null; + public static @MonotonicNonNull Boolean new_decl_format = null; - static final class SerialFormat { + static final class SerialFormat { - public boolean new_decl_format = false; + public boolean new_decl_format = false; - @RequiresNonNull("MonotonicNonNullTest.new_decl_format") - public SerialFormat() { - this.new_decl_format = MonotonicNonNullTest.new_decl_format; - } + @RequiresNonNull("MonotonicNonNullTest.new_decl_format") + public SerialFormat() { + this.new_decl_format = MonotonicNonNullTest.new_decl_format; } + } } diff --git a/checker/tests/nullness/MultiAnnotations.java b/checker/tests/nullness/MultiAnnotations.java index ee0a76ca71b..e0c2a7800c1 100644 --- a/checker/tests/nullness/MultiAnnotations.java +++ b/checker/tests/nullness/MultiAnnotations.java @@ -2,11 +2,11 @@ public final @Interned class MultiAnnotations { - private MultiAnnotations() {} + private MultiAnnotations() {} - public static final MultiAnnotations NO_CHANGE = new MultiAnnotations(); + public static final MultiAnnotations NO_CHANGE = new MultiAnnotations(); - MultiAnnotations foo() { - return MultiAnnotations.NO_CHANGE; - } + MultiAnnotations foo() { + return MultiAnnotations.NO_CHANGE; + } } diff --git a/checker/tests/nullness/MultipleErrors.java b/checker/tests/nullness/MultipleErrors.java index c23ea1c1fb7..ebcede7bf1b 100644 --- a/checker/tests/nullness/MultipleErrors.java +++ b/checker/tests/nullness/MultipleErrors.java @@ -2,16 +2,16 @@ // the same compilation unit are all shown. class MultipleErrors1 { - // :: error: (assignment.type.incompatible) - Object o1 = null; + // :: error: (assignment.type.incompatible) + Object o1 = null; } class MultipleErrors2 { - // :: error: (assignment.type.incompatible) - Object o2 = null; + // :: error: (assignment.type.incompatible) + Object o2 = null; } interface MultipleErrors3 { - // :: error: (assignment.type.incompatible) - Object o3 = null; + // :: error: (assignment.type.incompatible) + Object o3 = null; } diff --git a/checker/tests/nullness/MyException.java b/checker/tests/nullness/MyException.java index 3867e50b04a..eefe65288cf 100644 --- a/checker/tests/nullness/MyException.java +++ b/checker/tests/nullness/MyException.java @@ -1,22 +1,22 @@ @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.Nullable.class) + org.checkerframework.checker.nullness.qual.Nullable.class) public class MyException extends Exception { - public MyException() {} + public MyException() {} - public final String getTotalTrace() { - final StringBuilder sb = new StringBuilder(); - // :: error: (iterating.over.nullable) - for (StackTraceElement st : getStackTrace()) { - // :: error: (dereference.of.nullable) - sb.append(st.toString()); - sb.append(System.lineSeparator()); - } - return sb.toString(); + public final String getTotalTrace() { + final StringBuilder sb = new StringBuilder(); + // :: error: (iterating.over.nullable) + for (StackTraceElement st : getStackTrace()) { + // :: error: (dereference.of.nullable) + sb.append(st.toString()); + sb.append(System.lineSeparator()); } + return sb.toString(); + } - @SuppressWarnings("nullness") - public StackTraceElement[] getStackTrace() { - throw new RuntimeException("not implemented yet"); - } + @SuppressWarnings("nullness") + public StackTraceElement[] getStackTrace() { + throw new RuntimeException("not implemented yet"); + } } diff --git a/checker/tests/nullness/NNOEMoreTests.java b/checker/tests/nullness/NNOEMoreTests.java index 0e8cb3f7f81..c88e66cc705 100644 --- a/checker/tests/nullness/NNOEMoreTests.java +++ b/checker/tests/nullness/NNOEMoreTests.java @@ -2,46 +2,46 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class NNOEMoreTests { - class NNOEMain { - protected @Nullable String nullable = null; - @Nullable String otherNullable = null; - - @RequiresNonNull("nullable") - void test1() { - nullable.toString(); - } - - @RequiresNonNull("xxx") - // :: error: (flowexpr.parse.error) - void test2() { - // :: error: (dereference.of.nullable) - nullable.toString(); - } + class NNOEMain { + protected @Nullable String nullable = null; + @Nullable String otherNullable = null; + + @RequiresNonNull("nullable") + void test1() { + nullable.toString(); + } + + @RequiresNonNull("xxx") + // :: error: (flowexpr.parse.error) + void test2() { + // :: error: (dereference.of.nullable) + nullable.toString(); } + } - class NNOESeparate { - void call1(NNOEMain p) { - // :: error: (contracts.precondition.not.satisfied) - p.test1(); + class NNOESeparate { + void call1(NNOEMain p) { + // :: error: (contracts.precondition.not.satisfied) + p.test1(); - Object xxx = new Object(); - // :: error: (flowexpr.parse.error) - p.test2(); - } + Object xxx = new Object(); + // :: error: (flowexpr.parse.error) + p.test2(); + } - void call2(NNOEMain p) { - p.nullable = ""; - p.test1(); - } + void call2(NNOEMain p) { + p.nullable = ""; + p.test1(); } + } - @Nullable Object field1; + @Nullable Object field1; - @RequiresNonNull("field1") - void methWithIf1() { - if (5 < 99) { - } else { - field1.hashCode(); - } + @RequiresNonNull("field1") + void methWithIf1() { + if (5 < 99) { + } else { + field1.hashCode(); } + } } diff --git a/checker/tests/nullness/NNOEStaticFields.java b/checker/tests/nullness/NNOEStaticFields.java index a3f61adfc07..b0ec82a28a0 100644 --- a/checker/tests/nullness/NNOEStaticFields.java +++ b/checker/tests/nullness/NNOEStaticFields.java @@ -1,115 +1,114 @@ +import java.util.Collections; +import java.util.Set; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import java.util.Collections; -import java.util.Set; - public class NNOEStaticFields { - static @Nullable String nullable = null; - static @Nullable String otherNullable = null; - - @RequiresNonNull("nullable") - void testF() { - nullable.toString(); + static @Nullable String nullable = null; + static @Nullable String otherNullable = null; + + @RequiresNonNull("nullable") + void testF() { + nullable.toString(); + } + + @RequiresNonNull("NNOEStaticFields.nullable") + void testF2() { + nullable.toString(); + } + + @RequiresNonNull("nullable") + void testF3() { + NNOEStaticFields.nullable.toString(); + } + + @RequiresNonNull("NNOEStaticFields.nullable") + void testF4() { + NNOEStaticFields.nullable.toString(); + } + + class Inner { + void m1(NNOEStaticFields out) { + NNOEStaticFields.nullable = "haha!"; + out.testF4(); } @RequiresNonNull("NNOEStaticFields.nullable") - void testF2() { - nullable.toString(); - } - - @RequiresNonNull("nullable") - void testF3() { - NNOEStaticFields.nullable.toString(); - } - - @RequiresNonNull("NNOEStaticFields.nullable") - void testF4() { - NNOEStaticFields.nullable.toString(); - } - - class Inner { - void m1(NNOEStaticFields out) { - NNOEStaticFields.nullable = "haha!"; - out.testF4(); - } - - @RequiresNonNull("NNOEStaticFields.nullable") - void m2(NNOEStaticFields out) { - out.testF4(); - } - } - - @RequiresNonNull("NoClueWhatThisShouldBe") - // :: error: (flowexpr.parse.error) - void testF5() { - // :: error: (dereference.of.nullable) - NNOEStaticFields.nullable.toString(); - } - - void trueNegative() { - // :: error: (dereference.of.nullable) - nullable.toString(); - // :: error: (dereference.of.nullable) - otherNullable.toString(); - } - - @RequiresNonNull("nullable") - void test1() { - nullable.toString(); - // :: error: (dereference.of.nullable) - otherNullable.toString(); - } - - @RequiresNonNull("otherNullable") - void test2() { - // :: error: (dereference.of.nullable) - nullable.toString(); - otherNullable.toString(); + void m2(NNOEStaticFields out) { + out.testF4(); } - - @RequiresNonNull({"nullable", "otherNullable"}) - void test3() { - nullable.toString(); - otherNullable.toString(); + } + + @RequiresNonNull("NoClueWhatThisShouldBe") + // :: error: (flowexpr.parse.error) + void testF5() { + // :: error: (dereference.of.nullable) + NNOEStaticFields.nullable.toString(); + } + + void trueNegative() { + // :: error: (dereference.of.nullable) + nullable.toString(); + // :: error: (dereference.of.nullable) + otherNullable.toString(); + } + + @RequiresNonNull("nullable") + void test1() { + nullable.toString(); + // :: error: (dereference.of.nullable) + otherNullable.toString(); + } + + @RequiresNonNull("otherNullable") + void test2() { + // :: error: (dereference.of.nullable) + nullable.toString(); + otherNullable.toString(); + } + + @RequiresNonNull({"nullable", "otherNullable"}) + void test3() { + nullable.toString(); + otherNullable.toString(); + } + + @RequiresNonNull("System.out") + void test4() { + @NonNull Object f = System.out; + } + + /////////////////////////////////////////////////////////////////////////// + /// Copied from Daikon's ChicoryPremain + /// + + static class ChicoryPremain1 { + + // Non-null if doPurity == true + private static @MonotonicNonNull Set pureMethods = null; + + private static boolean doPurity = false; + + @EnsuresNonNullIf(result = true, expression = "ChicoryPremain1.pureMethods") + // this postcondition cannot be proved with the Checker Framework, as the relation + // between doPurity and pureMethods is not explicit + public static boolean shouldDoPurity() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return doPurity; } - @RequiresNonNull("System.out") - void test4() { - @NonNull Object f = System.out; - } - - /////////////////////////////////////////////////////////////////////////// - /// Copied from Daikon's ChicoryPremain - /// - - static class ChicoryPremain1 { - - // Non-null if doPurity == true - private static @MonotonicNonNull Set pureMethods = null; - - private static boolean doPurity = false; - - @EnsuresNonNullIf(result = true, expression = "ChicoryPremain1.pureMethods") - // this postcondition cannot be proved with the Checker Framework, as the relation - // between doPurity and pureMethods is not explicit - public static boolean shouldDoPurity() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return doPurity; - } - - @RequiresNonNull("ChicoryPremain1.pureMethods") - public static Set getPureMethods() { - return Collections.unmodifiableSet(pureMethods); - } + @RequiresNonNull("ChicoryPremain1.pureMethods") + public static Set getPureMethods() { + return Collections.unmodifiableSet(pureMethods); } + } - static class ClassInfo1 { - public void initViaReflection() { - if (ChicoryPremain1.shouldDoPurity()) { - for (String pureMeth : ChicoryPremain1.getPureMethods()) {} - } - } + static class ClassInfo1 { + public void initViaReflection() { + if (ChicoryPremain1.shouldDoPurity()) { + for (String pureMeth : ChicoryPremain1.getPureMethods()) {} + } } + } } diff --git a/checker/tests/nullness/NegatingConditionalNullness.java b/checker/tests/nullness/NegatingConditionalNullness.java index a193d51308f..c28602dcf7c 100644 --- a/checker/tests/nullness/NegatingConditionalNullness.java +++ b/checker/tests/nullness/NegatingConditionalNullness.java @@ -1,58 +1,57 @@ +import java.util.List; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import java.util.List; - public class NegatingConditionalNullness { - public @MonotonicNonNull List splitters = null; + public @MonotonicNonNull List splitters = null; - @EnsuresNonNullIf(result = true, expression = "splitters") - public boolean has_splitters() { - return (splitters != null); - } + @EnsuresNonNullIf(result = true, expression = "splitters") + public boolean has_splitters() { + return (splitters != null); + } - static void test(NegatingConditionalNullness ppt) { - if (!ppt.has_splitters()) { - return; - } - @NonNull Object s2 = ppt.splitters; + static void test(NegatingConditionalNullness ppt) { + if (!ppt.has_splitters()) { + return; } + @NonNull Object s2 = ppt.splitters; + } - static void testAssert(NegatingConditionalNullness ppt) { - assert ppt.has_splitters() : "@AssumeAssertion(nullness)"; - @NonNull Object s2 = ppt.splitters; - } + static void testAssert(NegatingConditionalNullness ppt) { + assert ppt.has_splitters() : "@AssumeAssertion(nullness)"; + @NonNull Object s2 = ppt.splitters; + } - static void testSimple(NegatingConditionalNullness ppt) { - if (ppt.has_splitters()) { - @NonNull Object s2 = ppt.splitters; - } + static void testSimple(NegatingConditionalNullness ppt) { + if (ppt.has_splitters()) { + @NonNull Object s2 = ppt.splitters; } - - // False tests - static void testFalse(NegatingConditionalNullness ppt) { - // :: error: (dereference.of.nullable) - ppt.splitters.toString(); // error + } + + // False tests + static void testFalse(NegatingConditionalNullness ppt) { + // :: error: (dereference.of.nullable) + ppt.splitters.toString(); // error + } + + static void testFalseNoAssertion(NegatingConditionalNullness ppt) { + ppt.has_splitters(); + // :: error: (dereference.of.nullable) + ppt.splitters.toString(); // error + } + + static void testFalseIf(NegatingConditionalNullness ppt) { + if (ppt.has_splitters()) { + return; } - - static void testFalseNoAssertion(NegatingConditionalNullness ppt) { - ppt.has_splitters(); - // :: error: (dereference.of.nullable) - ppt.splitters.toString(); // error - } - - static void testFalseIf(NegatingConditionalNullness ppt) { - if (ppt.has_splitters()) { - return; - } - // :: error: (dereference.of.nullable) - ppt.splitters.toString(); // error - } - - // static void testFalseIfBody(NegatingConditionalNullness ppt) { - // if (!ppt.has_splitters()) { - // // :: error: (dereference.of.nullable) - // ppt.splitters.toString(); // error - // } - // } + // :: error: (dereference.of.nullable) + ppt.splitters.toString(); // error + } + + // static void testFalseIfBody(NegatingConditionalNullness ppt) { + // if (!ppt.has_splitters()) { + // // :: error: (dereference.of.nullable) + // ppt.splitters.toString(); // error + // } + // } } diff --git a/checker/tests/nullness/NewNullable.java b/checker/tests/nullness/NewNullable.java index 861dd794758..0e0370ecf62 100644 --- a/checker/tests/nullness/NewNullable.java +++ b/checker/tests/nullness/NewNullable.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.nullness.qual.*; public class NewNullable { - Object o = new Object(); - Object nn = new @NonNull Object(); - // :: error: (nullness.on.new.object) - @Nullable Object lazy = new @MonotonicNonNull Object(); - // :: error: (nullness.on.new.object) - // :: error: (invalid.polymorphic.qualifier.use) - @Nullable Object poly = new @PolyNull Object(); - // :: error: (nullness.on.new.object) - @Nullable Object nbl = new @Nullable Object(); + Object o = new Object(); + Object nn = new @NonNull Object(); + // :: error: (nullness.on.new.object) + @Nullable Object lazy = new @MonotonicNonNull Object(); + // :: error: (nullness.on.new.object) + // :: error: (invalid.polymorphic.qualifier.use) + @Nullable Object poly = new @PolyNull Object(); + // :: error: (nullness.on.new.object) + @Nullable Object nbl = new @Nullable Object(); } diff --git a/checker/tests/nullness/NewObjectNonNull.java b/checker/tests/nullness/NewObjectNonNull.java index b58321dd5d2..66b78927424 100644 --- a/checker/tests/nullness/NewObjectNonNull.java +++ b/checker/tests/nullness/NewObjectNonNull.java @@ -2,19 +2,19 @@ import org.checkerframework.framework.qual.DefaultQualifier; public class NewObjectNonNull { - @DefaultQualifier(Nullable.class) - class A { - A() {} - } + @DefaultQualifier(Nullable.class) + class A { + A() {} + } - @DefaultQualifier(Nullable.class) - class B { - // No explicit constructor. - // B() {} - } + @DefaultQualifier(Nullable.class) + class B { + // No explicit constructor. + // B() {} + } - void m() { - new A().toString(); - new B().toString(); - } + void m() { + new A().toString(); + new B().toString(); + } } diff --git a/checker/tests/nullness/NonEmptyCollection.java b/checker/tests/nullness/NonEmptyCollection.java index 80edd7fb450..a230ceb93af 100644 --- a/checker/tests/nullness/NonEmptyCollection.java +++ b/checker/tests/nullness/NonEmptyCollection.java @@ -1,50 +1,49 @@ -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; - import java.io.*; import java.util.SortedMap; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; public class NonEmptyCollection { - public static @NonNull String returnRemove(@NonNull PriorityQueue1<@NonNull String> pq) { - return pq.remove(); - } + public static @NonNull String returnRemove(@NonNull PriorityQueue1<@NonNull String> pq) { + return pq.remove(); + } + + public static @NonNull String returnPoll1(PriorityQueue1<@NonNull String> pq) { + // :: error: (return.type.incompatible) + return pq.poll(); + } - public static @NonNull String returnPoll1(PriorityQueue1<@NonNull String> pq) { - // :: error: (return.type.incompatible) - return pq.poll(); + public static @NonNull String returnPoll2(PriorityQueue1<@NonNull String> pq) { + if (pq.isEmpty()) { + return "hello"; + } else { + return pq.poll(); } + } + + public static @NonNull String returnFirstKey(SortedMap sm) { + return sm.firstKey(); + } + + /////////////////////////////////////////////////////////////////////////// + /// Helper classes copied from JDK + /// - public static @NonNull String returnPoll2(PriorityQueue1<@NonNull String> pq) { - if (pq.isEmpty()) { - return "hello"; - } else { - return pq.poll(); - } + public class PriorityQueue1 { + @SuppressWarnings("purity") // object creation is forbidden in pure methods + @org.checkerframework.dataflow.qual.Pure + public @Nullable E poll() { + throw new RuntimeException("skeleton method"); } - public static @NonNull String returnFirstKey(SortedMap sm) { - return sm.firstKey(); + public E remove() { + throw new RuntimeException("skeleton method"); } - /////////////////////////////////////////////////////////////////////////// - /// Helper classes copied from JDK - /// - - public class PriorityQueue1 { - @SuppressWarnings("purity") // object creation is forbidden in pure methods - @org.checkerframework.dataflow.qual.Pure - public @Nullable E poll() { - throw new RuntimeException("skeleton method"); - } - - public E remove() { - throw new RuntimeException("skeleton method"); - } - - @EnsuresNonNullIf(result = false, expression = "poll()") - public boolean isEmpty() { - throw new RuntimeException("skeleton method"); - } + @EnsuresNonNullIf(result = false, expression = "poll()") + public boolean isEmpty() { + throw new RuntimeException("skeleton method"); } + } } diff --git a/checker/tests/nullness/NonNullInitialization.java b/checker/tests/nullness/NonNullInitialization.java index 9e39a99636c..a8acf65a8ce 100644 --- a/checker/tests/nullness/NonNullInitialization.java +++ b/checker/tests/nullness/NonNullInitialization.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.nullness.qual.*; public class NonNullInitialization { - private String test = "test"; + private String test = "test"; - public static void main(String[] args) { - NonNullInitialization n = new NonNullInitialization(); - n.test.equals("ASD"); - } + public static void main(String[] args) { + NonNullInitialization n = new NonNullInitialization(); + n.test.equals("ASD"); + } } diff --git a/checker/tests/nullness/NonNullIteratorNext.java b/checker/tests/nullness/NonNullIteratorNext.java index 7930447d4d2..bd2e94b19d2 100644 --- a/checker/tests/nullness/NonNullIteratorNext.java +++ b/checker/tests/nullness/NonNullIteratorNext.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class NonNullIteratorNext { - interface MyIterator extends java.util.Iterator { - @NonNull E next(); - } + interface MyIterator extends java.util.Iterator { + @NonNull E next(); + } - interface MyList extends java.util.Collection { - MyIterator iterator(); - } + interface MyList extends java.util.Collection { + MyIterator iterator(); + } - void forEachLoop(MyList list) { - for (T elem : list) {} - } + void forEachLoop(MyList list) { + for (T elem : list) {} + } } diff --git a/checker/tests/nullness/NullableArrays.java b/checker/tests/nullness/NullableArrays.java index 5a9cc220d2c..f068d18e149 100644 --- a/checker/tests/nullness/NullableArrays.java +++ b/checker/tests/nullness/NullableArrays.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.*; public class NullableArrays { - private byte @Nullable [] padding; + private byte @Nullable [] padding; - public NullableArrays(byte @Nullable [] padding) { - this.padding = padding; - } + public NullableArrays(byte @Nullable [] padding) { + this.padding = padding; + } } diff --git a/checker/tests/nullness/NullableConstructor.java b/checker/tests/nullness/NullableConstructor.java index deeea46ea72..a6f8d4bf9cb 100644 --- a/checker/tests/nullness/NullableConstructor.java +++ b/checker/tests/nullness/NullableConstructor.java @@ -2,6 +2,6 @@ public class NullableConstructor { - // :: error: (nullness.on.constructor) - @Nullable NullableConstructor() {} + // :: error: (nullness.on.constructor) + @Nullable NullableConstructor() {} } diff --git a/checker/tests/nullness/NullableObject.java b/checker/tests/nullness/NullableObject.java index 2567fd58aca..c95cdb5551e 100644 --- a/checker/tests/nullness/NullableObject.java +++ b/checker/tests/nullness/NullableObject.java @@ -2,12 +2,12 @@ public class NullableObject { - void foo() { - // :: error: (nullness.on.new.object) - Object nbl = new @Nullable Object(); - } + void foo() { + // :: error: (nullness.on.new.object) + Object nbl = new @Nullable Object(); + } - void bar() { - Object nn = new Object(); - } + void bar() { + Object nn = new Object(); + } } diff --git a/checker/tests/nullness/NullnessFieldInvar.java b/checker/tests/nullness/NullnessFieldInvar.java index 44c4365092f..9eb8fbc55e8 100644 --- a/checker/tests/nullness/NullnessFieldInvar.java +++ b/checker/tests/nullness/NullnessFieldInvar.java @@ -5,172 +5,172 @@ import org.checkerframework.framework.qual.FieldInvariant; public class NullnessFieldInvar { - public class Super { - public final @Nullable Object o; - public @Nullable Object nonfinal = null; + public class Super { + public final @Nullable Object o; + public @Nullable Object nonfinal = null; - public Super(@Nullable Object o) { - this.o = o; - } + public Super(@Nullable Object o) { + this.o = o; } + } - @FieldInvariant(field = "o", qualifier = NonNull.class) - class Sub extends Super { - public final @Nullable Object subO; - - public Sub(@NonNull Object o) { - super(o); - subO = null; - } - - public Sub(@NonNull Object o, @Nullable Object subO) { - super(o); - this.subO = subO; - } - - void test() { - @NonNull Object x1 = this.o; - @NonNull Object x2 = o; - @NonNull Object x3 = super.o; - } - } + @FieldInvariant(field = "o", qualifier = NonNull.class) + class Sub extends Super { + public final @Nullable Object subO; - class SubSub1 extends Sub { - public SubSub1(@NonNull Object o) { - super(o); - } + public Sub(@NonNull Object o) { + super(o); + subO = null; } - @FieldInvariant( - field = {"o", "subO"}, - qualifier = NonNull.class) - class SubSub2 extends Sub { - public SubSub2(@NonNull Object o) { - super(o); - } + public Sub(@NonNull Object o, @Nullable Object subO) { + super(o); + this.subO = subO; } - class Use { - void test(Super superO, Sub sub, SubSub1 subSub1, SubSub2 subSub2) { - // :: error: (assignment.type.incompatible) - @NonNull Object x1 = superO.o; - @NonNull Object x2 = sub.o; - @NonNull Object x3 = subSub1.o; - - // :: error: (assignment.type.incompatible) - @NonNull Object x5 = sub.subO; - // :: error: (assignment.type.incompatible) - @NonNull Object x6 = subSub1.subO; - @NonNull Object x7 = subSub2.subO; - } - - void test2( - SP superO, SB sub, SS1 subSub1, SS2 subSub2) { - // :: error: (assignment.type.incompatible) - @NonNull Object x1 = superO.o; - @NonNull Object x2 = sub.o; - @NonNull Object x3 = subSub1.o; - - // :: error: (assignment.type.incompatible) - @NonNull Object x5 = sub.subO; - // :: error: (assignment.type.incompatible) - @NonNull Object x6 = subSub1.subO; - @NonNull Object x7 = subSub2.subO; - } + void test() { + @NonNull Object x1 = this.o; + @NonNull Object x2 = o; + @NonNull Object x3 = super.o; } + } - class SuperWithNonFinal { - @Nullable Object nonfinal = null; + class SubSub1 extends Sub { + public SubSub1(@NonNull Object o) { + super(o); } - - // nonfinal isn't final - // :: error: (field.invariant.not.final) - @FieldInvariant(field = "nonfinal", qualifier = NonNull.class) - class SubSubInvalid extends SuperWithNonFinal {} - - // field is declared in this class - // :: error: (field.invariant.not.found) - @FieldInvariant(field = "field", qualifier = NonNull.class) - class Invalid { - final Object field = new Object(); + } + + @FieldInvariant( + field = {"o", "subO"}, + qualifier = NonNull.class) + class SubSub2 extends Sub { + public SubSub2(@NonNull Object o) { + super(o); + } + } + + class Use { + void test(Super superO, Sub sub, SubSub1 subSub1, SubSub2 subSub2) { + // :: error: (assignment.type.incompatible) + @NonNull Object x1 = superO.o; + @NonNull Object x2 = sub.o; + @NonNull Object x3 = subSub1.o; + + // :: error: (assignment.type.incompatible) + @NonNull Object x5 = sub.subO; + // :: error: (assignment.type.incompatible) + @NonNull Object x6 = subSub1.subO; + @NonNull Object x7 = subSub2.subO; } - @FieldInvariant( - field = {"o", "subO"}, - qualifier = NonNull.class) - class Shadowing extends SubSub2 { - @Nullable Object o; - @Nullable Object subO; - - void test() { - // :: error: (assignment.type.incompatible) - @NonNull Object x = o; // error - // :: error: (assignment.type.incompatible) - @NonNull Object x2 = subO; // error - - @NonNull Object x3 = super.o; - @NonNull Object x4 = super.subO; - } - - public Shadowing() { - super(""); - } + void test2( + SP superO, SB sub, SS1 subSub1, SS2 subSub2) { + // :: error: (assignment.type.incompatible) + @NonNull Object x1 = superO.o; + @NonNull Object x2 = sub.o; + @NonNull Object x3 = subSub1.o; + + // :: error: (assignment.type.incompatible) + @NonNull Object x5 = sub.subO; + // :: error: (assignment.type.incompatible) + @NonNull Object x6 = subSub1.subO; + @NonNull Object x7 = subSub2.subO; + } + } + + class SuperWithNonFinal { + @Nullable Object nonfinal = null; + } + + // nonfinal isn't final + // :: error: (field.invariant.not.final) + @FieldInvariant(field = "nonfinal", qualifier = NonNull.class) + class SubSubInvalid extends SuperWithNonFinal {} + + // field is declared in this class + // :: error: (field.invariant.not.found) + @FieldInvariant(field = "field", qualifier = NonNull.class) + class Invalid { + final Object field = new Object(); + } + + @FieldInvariant( + field = {"o", "subO"}, + qualifier = NonNull.class) + class Shadowing extends SubSub2 { + @Nullable Object o; + @Nullable Object subO; + + void test() { + // :: error: (assignment.type.incompatible) + @NonNull Object x = o; // error + // :: error: (assignment.type.incompatible) + @NonNull Object x2 = subO; // error + + @NonNull Object x3 = super.o; + @NonNull Object x4 = super.subO; } - // inherits: @FieldInvariant(field = {"o", "subO"}, qualifier = NonNull.class) - class Inherits extends SubSub2 { - @Nullable Object o; - @Nullable Object subO; + public Shadowing() { + super(""); + } + } - void test() { - // :: error: (assignment.type.incompatible) - @NonNull Object x = o; // error - // :: error: (assignment.type.incompatible) - @NonNull Object x2 = subO; // error + // inherits: @FieldInvariant(field = {"o", "subO"}, qualifier = NonNull.class) + class Inherits extends SubSub2 { + @Nullable Object o; + @Nullable Object subO; - @NonNull Object x3 = super.o; - @NonNull Object x4 = super.subO; - } + void test() { + // :: error: (assignment.type.incompatible) + @NonNull Object x = o; // error + // :: error: (assignment.type.incompatible) + @NonNull Object x2 = subO; // error - public Inherits() { - super(""); - } + @NonNull Object x3 = super.o; + @NonNull Object x4 = super.subO; } - class Super2 {} - - // :: error: (field.invariant.not.wellformed) - @FieldInvariant( - field = {}, - qualifier = NonNull.class) - class Invalid1 extends Super2 {} - - // :: error: (field.invariant.not.wellformed) - @FieldInvariant( - field = {"a", "b"}, - qualifier = {NonNull.class, NonNull.class, NonNull.class}) - class Invalid2 extends Super2 {} - - // :: error: (field.invariant.not.found) - @FieldInvariant(field = "x", qualifier = NonNull.class) - class NoSuper {} - - class SuperManyFields { - public final @Nullable Object field1 = null; - public final @Nullable Object field2 = null; - public final @Nullable Object field3 = null; - public final @Nullable Object field4 = null; + public Inherits() { + super(""); } - - @FieldInvariant( - field = {"field1", "field2", "field3", "field4"}, - qualifier = NonNull.class) - class SubManyFields extends SuperManyFields { - void test() { - field1.toString(); - field2.toString(); - field3.toString(); - field4.toString(); - } + } + + class Super2 {} + + // :: error: (field.invariant.not.wellformed) + @FieldInvariant( + field = {}, + qualifier = NonNull.class) + class Invalid1 extends Super2 {} + + // :: error: (field.invariant.not.wellformed) + @FieldInvariant( + field = {"a", "b"}, + qualifier = {NonNull.class, NonNull.class, NonNull.class}) + class Invalid2 extends Super2 {} + + // :: error: (field.invariant.not.found) + @FieldInvariant(field = "x", qualifier = NonNull.class) + class NoSuper {} + + class SuperManyFields { + public final @Nullable Object field1 = null; + public final @Nullable Object field2 = null; + public final @Nullable Object field3 = null; + public final @Nullable Object field4 = null; + } + + @FieldInvariant( + field = {"field1", "field2", "field3", "field4"}, + qualifier = NonNull.class) + class SubManyFields extends SuperManyFields { + void test() { + field1.toString(); + field2.toString(); + field3.toString(); + field4.toString(); } + } } diff --git a/checker/tests/nullness/NullnessIssue4996.java b/checker/tests/nullness/NullnessIssue4996.java index 05688b3a00b..9362f87a41c 100644 --- a/checker/tests/nullness/NullnessIssue4996.java +++ b/checker/tests/nullness/NullnessIssue4996.java @@ -1,21 +1,21 @@ class NullnessIssue4996 { - abstract class CaptureOuter { - abstract T get(); + abstract class CaptureOuter { + abstract T get(); - abstract class Inner { - abstract T get(); - } + abstract class Inner { + abstract T get(); } + } - class Client { - Object getFrom(CaptureOuter o) { - // :: error: (return.type.incompatible) - return o.get(); - } + class Client { + Object getFrom(CaptureOuter o) { + // :: error: (return.type.incompatible) + return o.get(); + } - Object getFrom(CaptureOuter.Inner o) { - // :: error: (return.type.incompatible) - return o.get(); - } + Object getFrom(CaptureOuter.Inner o) { + // :: error: (return.type.incompatible) + return o.get(); } + } } diff --git a/checker/tests/nullness/ObjectsRequireNonNull.java b/checker/tests/nullness/ObjectsRequireNonNull.java index a4274f05b49..3d2850c955c 100644 --- a/checker/tests/nullness/ObjectsRequireNonNull.java +++ b/checker/tests/nullness/ObjectsRequireNonNull.java @@ -1,17 +1,16 @@ // Test case for https://tinyurl.com/cfissue/3149 . +import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Objects; - public class ObjectsRequireNonNull { - void foo(@Nullable Object nble, @NonNull Object nn) { - // :: error: (argument.type.incompatible) - Objects.requireNonNull(null); - // :: error: (argument.type.incompatible) - Objects.requireNonNull(nble); - Objects.requireNonNull("hello"); - Objects.requireNonNull(nn); - } + void foo(@Nullable Object nble, @NonNull Object nn) { + // :: error: (argument.type.incompatible) + Objects.requireNonNull(null); + // :: error: (argument.type.incompatible) + Objects.requireNonNull(nble); + Objects.requireNonNull("hello"); + Objects.requireNonNull(nn); + } } diff --git a/checker/tests/nullness/ObjectsRequireNonNullElse.java b/checker/tests/nullness/ObjectsRequireNonNullElse.java index 90d1c24c182..54615b74bcc 100644 --- a/checker/tests/nullness/ObjectsRequireNonNullElse.java +++ b/checker/tests/nullness/ObjectsRequireNonNullElse.java @@ -7,12 +7,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class ObjectsRequireNonNullElse { - public static void main(String[] args) { - @NonNull String value = requireNonNullElse(null, "Something"); - System.err.println(requireNonNullElse(null, "Something")); + public static void main(String[] args) { + @NonNull String value = requireNonNullElse(null, "Something"); + System.err.println(requireNonNullElse(null, "Something")); - // This should fail typechecks, because it fails at run time. - // :: error: (argument.type.incompatible) - System.err.println((Object) requireNonNullElse(null, null)); - } + // This should fail typechecks, because it fails at run time. + // :: error: (argument.type.incompatible) + System.err.println((Object) requireNonNullElse(null, null)); + } } diff --git a/checker/tests/nullness/OptTest.java b/checker/tests/nullness/OptTest.java index be6f20fabfe..026f8c514a5 100644 --- a/checker/tests/nullness/OptTest.java +++ b/checker/tests/nullness/OptTest.java @@ -5,12 +5,12 @@ public class OptTest { - @SuppressWarnings("dereference.of.nullable") // requires refinement like for Optional.ifPresent. - void m1(@Nullable String o) { - Opt.ifPresent(o, s -> o.toString()); - } + @SuppressWarnings("dereference.of.nullable") // requires refinement like for Optional.ifPresent. + void m1(@Nullable String o) { + Opt.ifPresent(o, s -> o.toString()); + } - void m2(@Nullable String o) { - Opt.ifPresent(o, s -> s.toString()); - } + void m2(@Nullable String o) { + Opt.ifPresent(o, s -> s.toString()); + } } diff --git a/checker/tests/nullness/OverrideANNA.java b/checker/tests/nullness/OverrideANNA.java index 1cc7cba6e54..1da46a28534 100644 --- a/checker/tests/nullness/OverrideANNA.java +++ b/checker/tests/nullness/OverrideANNA.java @@ -2,27 +2,27 @@ import org.checkerframework.checker.nullness.qual.*; public class OverrideANNA { - static class Super { - Object f; + static class Super { + Object f; - @EnsuresNonNull("f") - void setf(@UnknownInitialization Super this) { - f = new Object(); - } - - Super() { - setf(); - } + @EnsuresNonNull("f") + void setf(@UnknownInitialization Super this) { + f = new Object(); } - static class Sub extends Super { - @Override - // :: error: (contracts.postcondition.not.satisfied) - void setf(@UnknownInitialization Sub this) {} + Super() { + setf(); } + } - public static void main(String[] args) { - Super s = new Sub(); - s.f.hashCode(); - } + static class Sub extends Super { + @Override + // :: error: (contracts.postcondition.not.satisfied) + void setf(@UnknownInitialization Sub this) {} + } + + public static void main(String[] args) { + Super s = new Sub(); + s.f.hashCode(); + } } diff --git a/checker/tests/nullness/OverrideANNA2.java b/checker/tests/nullness/OverrideANNA2.java index 90f3782358b..693d9312ff6 100644 --- a/checker/tests/nullness/OverrideANNA2.java +++ b/checker/tests/nullness/OverrideANNA2.java @@ -2,38 +2,38 @@ import org.checkerframework.checker.nullness.qual.*; public class OverrideANNA2 { - static class Super { - Object f; + static class Super { + Object f; - @EnsuresNonNull("f") // Super.f must be non-null - void setf(@UnknownInitialization Super this) { - f = new Object(); - } - - Super() { - setf(); - } + @EnsuresNonNull("f") // Super.f must be non-null + void setf(@UnknownInitialization Super this) { + f = new Object(); } - static class Sub extends Super { - Object f; // This shadows super.f + Super() { + setf(); + } + } - @Override - @EnsuresNonNull("f") - // We cannot ensure that Super.f is non-null since it is - // shadowed by Sub.f, hence we get an error. - // :: error: (contracts.postcondition.override.invalid) - void setf(@UnknownInitialization Sub this) { - f = new Object(); - } + static class Sub extends Super { + Object f; // This shadows super.f - Sub() { - setf(); - } + @Override + @EnsuresNonNull("f") + // We cannot ensure that Super.f is non-null since it is + // shadowed by Sub.f, hence we get an error. + // :: error: (contracts.postcondition.override.invalid) + void setf(@UnknownInitialization Sub this) { + f = new Object(); } - public static void main(String[] args) { - Super s = new Sub(); - s.f.hashCode(); + Sub() { + setf(); } + } + + public static void main(String[] args) { + Super s = new Sub(); + s.f.hashCode(); + } } diff --git a/checker/tests/nullness/OverrideANNA3.java b/checker/tests/nullness/OverrideANNA3.java index 98f38ca0ea1..1abd4f5c611 100644 --- a/checker/tests/nullness/OverrideANNA3.java +++ b/checker/tests/nullness/OverrideANNA3.java @@ -2,32 +2,32 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class OverrideANNA3 { - static class Super { - Object f; - Object g; + static class Super { + Object f; + Object g; - @EnsuresNonNull({"f", "g"}) - void setfg(@UnknownInitialization Super this) { - f = new Object(); - g = new Object(); - } - - Super() { - setfg(); - } + @EnsuresNonNull({"f", "g"}) + void setfg(@UnknownInitialization Super this) { + f = new Object(); + g = new Object(); } - static class Sub extends Super { - @Override - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.override.invalid) - void setfg(@UnknownInitialization Sub this) { - f = new Object(); - } + Super() { + setfg(); } + } - public static void main(String[] args) { - Super s = new Sub(); - s.g.hashCode(); + static class Sub extends Super { + @Override + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.override.invalid) + void setfg(@UnknownInitialization Sub this) { + f = new Object(); } + } + + public static void main(String[] args) { + Super s = new Sub(); + s.g.hashCode(); + } } diff --git a/checker/tests/nullness/OverrideGenerics.java b/checker/tests/nullness/OverrideGenerics.java index faed45db09a..893905ea241 100644 --- a/checker/tests/nullness/OverrideGenerics.java +++ b/checker/tests/nullness/OverrideGenerics.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.nullness.qual.*; class OGSuper { - public void m(S p) {} + public void m(S p) {} } class OGImpl1 extends OGSuper { - public void m(T p) {} + public void m(T p) {} } class OGImpl2 extends OGSuper { - public void m(T p) {} + public void m(T p) {} } diff --git a/checker/tests/nullness/OverrideNNOE.java b/checker/tests/nullness/OverrideNNOE.java index dd96eea0cde..090612af73a 100644 --- a/checker/tests/nullness/OverrideNNOE.java +++ b/checker/tests/nullness/OverrideNNOE.java @@ -2,23 +2,23 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class OverrideNNOE { - static class Super { - @Nullable Object f; + static class Super { + @Nullable Object f; - void call() {} - } + void call() {} + } - static class Sub extends Super { - @Override - @RequiresNonNull("f") - // :: error: (contracts.precondition.override.invalid) - void call() { - f.hashCode(); - } + static class Sub extends Super { + @Override + @RequiresNonNull("f") + // :: error: (contracts.precondition.override.invalid) + void call() { + f.hashCode(); } + } - public static void main(String[] args) { - Super s = new Sub(); - s.call(); - } + public static void main(String[] args) { + Super s = new Sub(); + s.call(); + } } diff --git a/checker/tests/nullness/OverrideNNOE2.java b/checker/tests/nullness/OverrideNNOE2.java index 7f98f003967..e122ea9f629 100644 --- a/checker/tests/nullness/OverrideNNOE2.java +++ b/checker/tests/nullness/OverrideNNOE2.java @@ -2,27 +2,27 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class OverrideNNOE2 { - static class Super { - @Nullable Object f; + static class Super { + @Nullable Object f; - @RequiresNonNull("f") - void call() {} - } + @RequiresNonNull("f") + void call() {} + } - static class Sub extends Super { - @Nullable Object g; + static class Sub extends Super { + @Nullable Object g; - @Override - @RequiresNonNull({"f", "g"}) - // :: error: (contracts.precondition.override.invalid) - void call() { - g.hashCode(); - } + @Override + @RequiresNonNull({"f", "g"}) + // :: error: (contracts.precondition.override.invalid) + void call() { + g.hashCode(); } + } - public static void main(String[] args) { - Super s = new Sub(); - s.f = new Object(); - s.call(); - } + public static void main(String[] args) { + Super s = new Sub(); + s.f = new Object(); + s.call(); + } } diff --git a/checker/tests/nullness/ParameterExpression.java b/checker/tests/nullness/ParameterExpression.java index 24dae25094e..8fa8ec24be6 100644 --- a/checker/tests/nullness/ParameterExpression.java +++ b/checker/tests/nullness/ParameterExpression.java @@ -1,171 +1,170 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class ParameterExpression { - public void m1( - @Nullable Object o, @Nullable Object o1, @Nullable Object o2, @Nullable Object o3) { - // :: error: (flowexpr.parse.error.postcondition) - m2(o); - // :: error: (dereference.of.nullable) - o.toString(); - m3(o); - o.toString(); - m4(o1, o2, o3); - // :: error: (dereference.of.nullable) - o1.toString(); - // :: error: (dereference.of.nullable) - o2.toString(); - o3.toString(); - } - - @SuppressWarnings("assert.postcondition.not.satisfied") - // "#0" is illegal syntax; it should be "#1" - @EnsuresNonNull("#0") - // :: error: (flowexpr.parse.error) - public void m2(final @Nullable Object o) {} - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("#1") - public void m3(final @Nullable Object o) {} - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("#3") - public void m4(@Nullable Object x1, @Nullable Object x2, final @Nullable Object x3) {} - - // Formal parameter names should not be used in signatures (pre/postcondition, conditional - // postcondition, and formal parameter annotations). Use "#paramNum", because the parameter - // names are not saved in bytecode. - - @Nullable Object field = null; - - // Postconditions - @EnsuresNonNull("field") // OK - public void m5() { - field = new Object(); - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6a(Object param) { - param = new Object(); - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6b(Object param) { - // :: error: (assignment.type.incompatible) - param = null; - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6c(@Nullable Object param) { - param = new Object(); - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6d(@Nullable Object param) { - param = null; - } - - @EnsuresNonNull("param.toString()") - // :: error: (flowexpr.parse.error) - public void m6e(@Nullable Object param) { - param = null; - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7a(Object field) { - field = new Object(); - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7b(Object field) { - // :: error: (assignment.type.incompatible) - field = null; - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7c(@Nullable Object field) { - field = new Object(); - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7d(@Nullable Object field) { - field = null; - } - - // Preconditions - @RequiresNonNull("field") // OK - public void m8() {} - - @RequiresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m9(Object param) {} - - // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a - // formal parameter. - @RequiresNonNull("field") - // :: warning: (expression.parameter.name.shadows.field) - public void m10(Object field) {} - - // Conditional postconditions - @EnsuresNonNullIf(result = true, expression = "field") // OK - public boolean m11() { - field = new Object(); - return true; - } - - @EnsuresNonNullIf(result = true, expression = "param") - // :: error: (flowexpr.parse.error) - public boolean m12(Object param) { - param = new Object(); - return true; - } - - // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a - // formal parameter. - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13a(@Nullable Object field) { - field = new Object(); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13b(@Nullable Object field) { - field = new Object(); - return false; - } - - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13c(@Nullable Object field) { - field = null; - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13d(@Nullable Object field) { - field = null; - return false; - } - - // Annotations on formal parameters referring to a formal parameter of the same method. - // :: error: (expression.unparsable.type.invalid) - public void m14(@KeyFor("param2") Object param1, Map param2) {} + public void m1( + @Nullable Object o, @Nullable Object o1, @Nullable Object o2, @Nullable Object o3) { + // :: error: (flowexpr.parse.error.postcondition) + m2(o); + // :: error: (dereference.of.nullable) + o.toString(); + m3(o); + o.toString(); + m4(o1, o2, o3); + // :: error: (dereference.of.nullable) + o1.toString(); + // :: error: (dereference.of.nullable) + o2.toString(); + o3.toString(); + } + + @SuppressWarnings("assert.postcondition.not.satisfied") + // "#0" is illegal syntax; it should be "#1" + @EnsuresNonNull("#0") + // :: error: (flowexpr.parse.error) + public void m2(final @Nullable Object o) {} + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("#1") + public void m3(final @Nullable Object o) {} + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("#3") + public void m4(@Nullable Object x1, @Nullable Object x2, final @Nullable Object x3) {} + + // Formal parameter names should not be used in signatures (pre/postcondition, conditional + // postcondition, and formal parameter annotations). Use "#paramNum", because the parameter + // names are not saved in bytecode. + + @Nullable Object field = null; + + // Postconditions + @EnsuresNonNull("field") // OK + public void m5() { + field = new Object(); + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6a(Object param) { + param = new Object(); + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6b(Object param) { + // :: error: (assignment.type.incompatible) + param = null; + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6c(@Nullable Object param) { + param = new Object(); + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6d(@Nullable Object param) { + param = null; + } + + @EnsuresNonNull("param.toString()") + // :: error: (flowexpr.parse.error) + public void m6e(@Nullable Object param) { + param = null; + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7a(Object field) { + field = new Object(); + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7b(Object field) { + // :: error: (assignment.type.incompatible) + field = null; + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7c(@Nullable Object field) { + field = new Object(); + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7d(@Nullable Object field) { + field = null; + } + + // Preconditions + @RequiresNonNull("field") // OK + public void m8() {} + + @RequiresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m9(Object param) {} + + // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a + // formal parameter. + @RequiresNonNull("field") + // :: warning: (expression.parameter.name.shadows.field) + public void m10(Object field) {} + + // Conditional postconditions + @EnsuresNonNullIf(result = true, expression = "field") // OK + public boolean m11() { + field = new Object(); + return true; + } + + @EnsuresNonNullIf(result = true, expression = "param") + // :: error: (flowexpr.parse.error) + public boolean m12(Object param) { + param = new Object(); + return true; + } + + // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a + // formal parameter. + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13a(@Nullable Object field) { + field = new Object(); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13b(@Nullable Object field) { + field = new Object(); + return false; + } + + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13c(@Nullable Object field) { + field = null; + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13d(@Nullable Object field) { + field = null; + return false; + } + + // Annotations on formal parameters referring to a formal parameter of the same method. + // :: error: (expression.unparsable.type.invalid) + public void m14(@KeyFor("param2") Object param1, Map param2) {} } diff --git a/checker/tests/nullness/PolyTest.java b/checker/tests/nullness/PolyTest.java index 05bdee71b0f..22621772c10 100644 --- a/checker/tests/nullness/PolyTest.java +++ b/checker/tests/nullness/PolyTest.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.nullness.qual.PolyNull; class PolyTest { - void foo1(@PolyNull Object nbl) { - if (nbl == null) { - // :: error: (dereference.of.nullable) - nbl.toString(); - } + void foo1(@PolyNull Object nbl) { + if (nbl == null) { + // :: error: (dereference.of.nullable) + nbl.toString(); } + } - void foo2(@PolyNull Object nbl) { - // :: error: (dereference.of.nullable) - nbl.toString(); - } + void foo2(@PolyNull Object nbl) { + // :: error: (dereference.of.nullable) + nbl.toString(); + } } diff --git a/checker/tests/nullness/Polymorphism.java b/checker/tests/nullness/Polymorphism.java index 9d833825508..6fdc99a4f6c 100644 --- a/checker/tests/nullness/Polymorphism.java +++ b/checker/tests/nullness/Polymorphism.java @@ -1,40 +1,40 @@ import org.checkerframework.checker.nullness.qual.*; public class Polymorphism { - // Test parameters - @PolyNull String identity(@PolyNull String s) { - return s; - } + // Test parameters + @PolyNull String identity(@PolyNull String s) { + return s; + } - void testParam() { - // Test without inference - String nullable = null; - @NonNull String nonNull = "m"; + void testParam() { + // Test without inference + String nullable = null; + @NonNull String nonNull = "m"; - // :: error: (assignment.type.incompatible) - nonNull = identity(nullable); // invalid - nonNull = identity(nonNull); + // :: error: (assignment.type.incompatible) + nonNull = identity(nullable); // invalid + nonNull = identity(nonNull); - // test flow - nullable = "m"; - nonNull = identity(nullable); // valid - } + // test flow + nullable = "m"; + nonNull = identity(nullable); // valid + } - // Test within a method - @PolyNull String random(@PolyNull String m) { - if (m == "d") { - // :: error: (return.type.incompatible) - return null; // invalid - } - return "m"; // valid + // Test within a method + @PolyNull String random(@PolyNull String m) { + if (m == "d") { + // :: error: (return.type.incompatible) + return null; // invalid } + return "m"; // valid + } - public static @PolyNull Object staticIdentity(@PolyNull Object a) { - return a; - } + public static @PolyNull Object staticIdentity(@PolyNull Object a) { + return a; + } - void testStatic(@Nullable Object nullable, @NonNull Object nonnull) { - @Nullable Object nullable2 = staticIdentity(nullable); - @NonNull Object nonnull2 = staticIdentity(nonnull); - } + void testStatic(@Nullable Object nullable, @NonNull Object nonnull) { + @Nullable Object nullable2 = staticIdentity(nullable); + @NonNull Object nonnull2 = staticIdentity(nonnull); + } } diff --git a/checker/tests/nullness/PolymorphismArrays.java b/checker/tests/nullness/PolymorphismArrays.java index 1311b3998bd..18a60f71975 100644 --- a/checker/tests/nullness/PolymorphismArrays.java +++ b/checker/tests/nullness/PolymorphismArrays.java @@ -2,56 +2,56 @@ public class PolymorphismArrays { - public PolymorphismArrays(String[][] elts) { - this.elts = elts; - } - - public static boolean @PolyNull [] bad(boolean @PolyNull [] seq) { - // Cannot directly return null; - // :: error: (return.type.incompatible) - return null; - } - - public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, int start, int end) { - // Know from comparison that argument is nullable -> also return is nullable. - if (seq == null) { - return null; - } - return new boolean[] {}; - } - - public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, long start, int end) { - return slice(seq, (int) start, end); - } - - public static @PolyNull String[] intern(@PolyNull String[] a) { - return a; - } - - // from OneOfStringSequence.java - private String[][] elts; - - @SuppressWarnings("purity") // ignore, analysis too strict. - @org.checkerframework.dataflow.qual.Pure - public PolymorphismArrays clone() { - PolymorphismArrays result = new PolymorphismArrays(elts.clone()); - for (int i = 0; i < elts.length; i++) { - result.elts[i] = intern(elts[i].clone()); - } - return result; - } - - public void simplified() { - String[][] elts = new String[0][0]; - String[][] clone = elts.clone(); - String[] results = intern(elts[0].clone()); - } - - public static int indexOf(T[] a) { - return indexOfEq(a); - } - - public static int indexOfEq(@PolyNull Object[] a) { - return -1; - } + public PolymorphismArrays(String[][] elts) { + this.elts = elts; + } + + public static boolean @PolyNull [] bad(boolean @PolyNull [] seq) { + // Cannot directly return null; + // :: error: (return.type.incompatible) + return null; + } + + public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, int start, int end) { + // Know from comparison that argument is nullable -> also return is nullable. + if (seq == null) { + return null; + } + return new boolean[] {}; + } + + public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, long start, int end) { + return slice(seq, (int) start, end); + } + + public static @PolyNull String[] intern(@PolyNull String[] a) { + return a; + } + + // from OneOfStringSequence.java + private String[][] elts; + + @SuppressWarnings("purity") // ignore, analysis too strict. + @org.checkerframework.dataflow.qual.Pure + public PolymorphismArrays clone() { + PolymorphismArrays result = new PolymorphismArrays(elts.clone()); + for (int i = 0; i < elts.length; i++) { + result.elts[i] = intern(elts[i].clone()); + } + return result; + } + + public void simplified() { + String[][] elts = new String[0][0]; + String[][] clone = elts.clone(); + String[] results = intern(elts[0].clone()); + } + + public static int indexOf(T[] a) { + return indexOfEq(a); + } + + public static int indexOfEq(@PolyNull Object[] a) { + return -1; + } } diff --git a/checker/tests/nullness/PostconditionBug.java b/checker/tests/nullness/PostconditionBug.java index 55e5b740644..25174618d25 100644 --- a/checker/tests/nullness/PostconditionBug.java +++ b/checker/tests/nullness/PostconditionBug.java @@ -3,9 +3,9 @@ public class PostconditionBug { - void a(@UnknownInitialization PostconditionBug this) { - @NonNull String f = "abc"; - // :: error: (assignment.type.incompatible) - f = null; - } + void a(@UnknownInitialization PostconditionBug this) { + @NonNull String f = "abc"; + // :: error: (assignment.type.incompatible) + f = null; + } } diff --git a/checker/tests/nullness/PreconditionFieldNotInStore.java b/checker/tests/nullness/PreconditionFieldNotInStore.java index aab732f1cc6..4c1aa59fe4c 100644 --- a/checker/tests/nullness/PreconditionFieldNotInStore.java +++ b/checker/tests/nullness/PreconditionFieldNotInStore.java @@ -6,20 +6,20 @@ class PreconditionFieldNotInStore { - private @org.checkerframework.checker.nullness.qual.MonotonicNonNull String filename; + private @org.checkerframework.checker.nullness.qual.MonotonicNonNull String filename; - @org.checkerframework.framework.qual.RequiresQualifier( - expression = {"this.filename"}, - qualifier = org.checkerframework.checker.nullness.qual.Nullable.class) - @org.checkerframework.checker.nullness.qual.NonNull String getIndentString() { - return "indentString"; - } + @org.checkerframework.framework.qual.RequiresQualifier( + expression = {"this.filename"}, + qualifier = org.checkerframework.checker.nullness.qual.Nullable.class) + @org.checkerframework.checker.nullness.qual.NonNull String getIndentString() { + return "indentString"; + } - public void logStackTrace(PrintStream logfile, int[] ste_arr) { - for (int ii = 2; ii < ste_arr.length; ii++) { - int ste = ste_arr[ii]; - logfile.printf("%s %s%n", getIndentString(), ste); - } - logfile.flush(); + public void logStackTrace(PrintStream logfile, int[] ste_arr) { + for (int ii = 2; ii < ste_arr.length; ii++) { + int ste = ste_arr[ii]; + logfile.printf("%s %s%n", getIndentString(), ste); } + logfile.flush(); + } } diff --git a/checker/tests/nullness/PreventClearProperty.java b/checker/tests/nullness/PreventClearProperty.java index 64fa8700d2c..9c8158fbd1c 100644 --- a/checker/tests/nullness/PreventClearProperty.java +++ b/checker/tests/nullness/PreventClearProperty.java @@ -1,128 +1,127 @@ // Same code (but different expected errors) as test PermitClearProperty.java . +import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.common.value.qual.StringVal; -import java.util.Properties; - public class PreventClearProperty { - static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; - - static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; - - @NonNull String getLineSeparator1() { - return System.getProperty("line.separator"); - } - - @NonNull String getLineSeparator2() { - // :: error: (return.type.incompatible) - return System.getProperty(LINE_SEPARATOR); - } - - @NonNull String getMyProperty1() { - // :: error: (return.type.incompatible) - return System.getProperty("my.property.name"); - } - - @NonNull String getMyProperty2() { - // :: error: (return.type.incompatible) - return System.getProperty(MY_PROPERTY_NAME); - } - - @NonNull String getAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.getProperty(propName); - } - - @NonNull String clearLineSeparator1() { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty("line.separator"); - } - - @NonNull String clearLineSeparator2() { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty(LINE_SEPARATOR); - } - - @NonNull String clearMyProperty1() { - // :: error: (return.type.incompatible) - return System.clearProperty("my.property.name"); - } - - @NonNull String clearMyProperty2() { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty(MY_PROPERTY_NAME); - } - - @NonNull String clearAProperty(String propName) { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty(propName); - } - - void callSetProperties(Properties p) { - // :: error: (clear.system.property) - System.setProperties(p); - } - - // All calls to setProperty are legal because they cannot unset a property. - - @NonNull String setLineSeparator1() { - return System.setProperty("line.separator", "somevalue"); - } - - @NonNull String setLineSeparator2() { - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, "somevalue"); - } - - @NonNull String setMyProperty1() { - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", "somevalue"); - } - - @NonNull String setMyProperty2() { - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, "somevalue"); - } - - @NonNull String setAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.setProperty(propName, "somevalue"); - } - - // These calls to setProperty are illegal because null is not a permitted value. - - @NonNull String setLineSeparatorNull1() { - // :: error: (argument.type.incompatible) - return System.setProperty("line.separator", null); - } - - @NonNull String setLineSeparatorNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, null); - } - - @NonNull String setMyPropertyNull1() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", null); - } - - @NonNull String setMyPropertyNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, null); - } - - @NonNull String setAPropertyNull(String propName) { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(propName, null); - } + static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; + + static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; + + @NonNull String getLineSeparator1() { + return System.getProperty("line.separator"); + } + + @NonNull String getLineSeparator2() { + // :: error: (return.type.incompatible) + return System.getProperty(LINE_SEPARATOR); + } + + @NonNull String getMyProperty1() { + // :: error: (return.type.incompatible) + return System.getProperty("my.property.name"); + } + + @NonNull String getMyProperty2() { + // :: error: (return.type.incompatible) + return System.getProperty(MY_PROPERTY_NAME); + } + + @NonNull String getAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.getProperty(propName); + } + + @NonNull String clearLineSeparator1() { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty("line.separator"); + } + + @NonNull String clearLineSeparator2() { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty(LINE_SEPARATOR); + } + + @NonNull String clearMyProperty1() { + // :: error: (return.type.incompatible) + return System.clearProperty("my.property.name"); + } + + @NonNull String clearMyProperty2() { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty(MY_PROPERTY_NAME); + } + + @NonNull String clearAProperty(String propName) { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty(propName); + } + + void callSetProperties(Properties p) { + // :: error: (clear.system.property) + System.setProperties(p); + } + + // All calls to setProperty are legal because they cannot unset a property. + + @NonNull String setLineSeparator1() { + return System.setProperty("line.separator", "somevalue"); + } + + @NonNull String setLineSeparator2() { + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, "somevalue"); + } + + @NonNull String setMyProperty1() { + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", "somevalue"); + } + + @NonNull String setMyProperty2() { + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, "somevalue"); + } + + @NonNull String setAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.setProperty(propName, "somevalue"); + } + + // These calls to setProperty are illegal because null is not a permitted value. + + @NonNull String setLineSeparatorNull1() { + // :: error: (argument.type.incompatible) + return System.setProperty("line.separator", null); + } + + @NonNull String setLineSeparatorNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, null); + } + + @NonNull String setMyPropertyNull1() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", null); + } + + @NonNull String setMyPropertyNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, null); + } + + @NonNull String setAPropertyNull(String propName) { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(propName, null); + } } diff --git a/checker/tests/nullness/PrimitivesNullness.java b/checker/tests/nullness/PrimitivesNullness.java index 74d9d1b9ddf..570a563eb3b 100644 --- a/checker/tests/nullness/PrimitivesNullness.java +++ b/checker/tests/nullness/PrimitivesNullness.java @@ -1,6 +1,6 @@ public class PrimitivesNullness { - public static void main(String[] args) { - for (String line : new String[] {"a"}) {} - } + public static void main(String[] args) { + for (String line : new String[] {"a"}) {} + } } diff --git a/checker/tests/nullness/PureTest.java b/checker/tests/nullness/PureTest.java index bcad3f3d378..4b559ef008c 100644 --- a/checker/tests/nullness/PureTest.java +++ b/checker/tests/nullness/PureTest.java @@ -2,131 +2,131 @@ import org.checkerframework.dataflow.qual.*; public class PureTest { - @org.checkerframework.dataflow.qual.Pure - @Nullable Object puremethod(@Nullable Object a) { - return a; + @org.checkerframework.dataflow.qual.Pure + @Nullable Object puremethod(@Nullable Object a) { + return a; + } + + public void test() { + // :: error: (assignment.type.incompatible) + @NonNull Object l0 = puremethod(null); + + if (puremethod(null) == null) { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = puremethod(null); } - public void test() { - // :: error: (assignment.type.incompatible) - @NonNull Object l0 = puremethod(null); - - if (puremethod(null) == null) { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = puremethod(null); - } - - if (puremethod("m") != null) { - @NonNull Object l1 = puremethod("m"); - } - - if (puremethod("m") != null) { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = puremethod(null); - } + if (puremethod("m") != null) { + @NonNull Object l1 = puremethod("m"); + } - if (puremethod("m") != null) { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = puremethod("n"); - } + if (puremethod("m") != null) { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = puremethod(null); + } - Object x = new Object(); + if (puremethod("m") != null) { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = puremethod("n"); + } - if (puremethod(x) == null) { - return; - } + Object x = new Object(); - @NonNull Object l2 = puremethod(x); + if (puremethod(x) == null) { + return; + } - x = new Object(); + @NonNull Object l2 = puremethod(x); - // :: error: (assignment.type.incompatible) - @NonNull Object l3 = puremethod(x); + x = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object l4 = puremethod("n"); - } + // :: error: (assignment.type.incompatible) + @NonNull Object l3 = puremethod(x); - public @org.checkerframework.dataflow.qual.Pure @Nullable Object getSuperclass() { - return null; - } + // :: error: (assignment.type.incompatible) + @NonNull Object l4 = puremethod("n"); + } - static void shortCircuitAnd(PureTest pt) { - if ((pt.getSuperclass() != null) && pt.getSuperclass().equals(Enum.class)) { - // empty body - } - } + public @org.checkerframework.dataflow.qual.Pure @Nullable Object getSuperclass() { + return null; + } - static void shortCircuitOr(PureTest pt) { - if ((pt.getSuperclass() == null) || pt.getSuperclass().equals(Enum.class)) { - // empty body - } + static void shortCircuitAnd(PureTest pt) { + if ((pt.getSuperclass() != null) && pt.getSuperclass().equals(Enum.class)) { + // empty body } + } - static void testInstanceofNegative(PureTest pt) { - if (pt.getSuperclass() instanceof Object) { - return; - } - // :: error: (dereference.of.nullable) - pt.getSuperclass().toString(); + static void shortCircuitOr(PureTest pt) { + if ((pt.getSuperclass() == null) || pt.getSuperclass().equals(Enum.class)) { + // empty body } + } - static void testInstanceofPositive(PureTest pt) { - if (!(pt.getSuperclass() instanceof Object)) { - return; - } - pt.getSuperclass().toString(); + static void testInstanceofNegative(PureTest pt) { + if (pt.getSuperclass() instanceof Object) { + return; } + // :: error: (dereference.of.nullable) + pt.getSuperclass().toString(); + } - static void testInstanceofPositive2(PureTest pt) { - if (!(pt.getSuperclass() instanceof Object)) { - } else { - pt.getSuperclass().toString(); - } + static void testInstanceofPositive(PureTest pt) { + if (!(pt.getSuperclass() instanceof Object)) { + return; } + pt.getSuperclass().toString(); + } - static void testInstanceofNegative2(PureTest pt) { - if (pt.getSuperclass() instanceof Object) { - } else { - return; - } - pt.getSuperclass().toString(); + static void testInstanceofPositive2(PureTest pt) { + if (!(pt.getSuperclass() instanceof Object)) { + } else { + pt.getSuperclass().toString(); } + } - static void testInstanceofString(PureTest pt) { - if (!(pt.getSuperclass() instanceof String)) { - return; - } - pt.getSuperclass().toString(); + static void testInstanceofNegative2(PureTest pt) { + if (pt.getSuperclass() instanceof Object) { + } else { + return; } + pt.getSuperclass().toString(); + } - static void testContinue(PureTest pt) { - for (; ; ) { - if (pt.getSuperclass() == null) { - System.out.println("m"); - continue; - } - pt.getSuperclass().toString(); - } + static void testInstanceofString(PureTest pt) { + if (!(pt.getSuperclass() instanceof String)) { + return; } - - void setSuperclass(@Nullable Object no) { - // set the field returned by getSuperclass. + pt.getSuperclass().toString(); + } + + static void testContinue(PureTest pt) { + for (; ; ) { + if (pt.getSuperclass() == null) { + System.out.println("m"); + continue; + } + pt.getSuperclass().toString(); } + } - static void testInstanceofPositive3(PureTest pt) { - if (!(pt.getSuperclass() instanceof Object)) { - return; - } else { - pt.setSuperclass(null); - } - // :: error: (dereference.of.nullable) - pt.getSuperclass().toString(); - } + void setSuperclass(@Nullable Object no) { + // set the field returned by getSuperclass. + } - @Override - @SideEffectFree - public String toString() { - return "foo"; + static void testInstanceofPositive3(PureTest pt) { + if (!(pt.getSuperclass() instanceof Object)) { + return; + } else { + pt.setSuperclass(null); } + // :: error: (dereference.of.nullable) + pt.getSuperclass().toString(); + } + + @Override + @SideEffectFree + public String toString() { + return "foo"; + } } diff --git a/checker/tests/nullness/RawAndPrimitive.java b/checker/tests/nullness/RawAndPrimitive.java index 2d50290f8b6..1b106ce56ee 100644 --- a/checker/tests/nullness/RawAndPrimitive.java +++ b/checker/tests/nullness/RawAndPrimitive.java @@ -1,15 +1,15 @@ public class RawAndPrimitive { - public T foo(T startValue) { - return startValue; - } + public T foo(T startValue) { + return startValue; + } - // this tests that DefaultTypeHierarchy.visitPrimitive_Wildcard works - public static void bar(float f) { - // the lower bound of the resultant wildcard (which replaces the raw type argument) will be - // lower than the default annotation on float + // this tests that DefaultTypeHierarchy.visitPrimitive_Wildcard works + public static void bar(float f) { + // the lower bound of the resultant wildcard (which replaces the raw type argument) will be + // lower than the default annotation on float - // :: warning: [unchecked] unchecked call to foo(T) as a member of the raw type - // RawAndPrimitive - new RawAndPrimitive().foo(f); - } + // :: warning: [unchecked] unchecked call to foo(T) as a member of the raw type + // RawAndPrimitive + new RawAndPrimitive().foo(f); + } } diff --git a/checker/tests/nullness/RawInt.java b/checker/tests/nullness/RawInt.java index abca2cddf22..2337e2b65a6 100644 --- a/checker/tests/nullness/RawInt.java +++ b/checker/tests/nullness/RawInt.java @@ -1,15 +1,15 @@ public class RawInt { - public void compare(int name1in2, int name2in1, VarInfo vi) { - int cmp1 = (name1in2 == -1) ? 0 : vi.varinfo_index - name1in2; - MathMDE.sign(cmp1); - } + public void compare(int name1in2, int name2in1, VarInfo vi) { + int cmp1 = (name1in2 == -1) ? 0 : vi.varinfo_index - name1in2; + MathMDE.sign(cmp1); + } } class VarInfo { - int varinfo_index; + int varinfo_index; } class MathMDE { - public static void sign(int a) {} + public static void sign(int a) {} } diff --git a/checker/tests/nullness/RawInt2.java b/checker/tests/nullness/RawInt2.java index 83213b49ebe..f936301e5e1 100644 --- a/checker/tests/nullness/RawInt2.java +++ b/checker/tests/nullness/RawInt2.java @@ -1,18 +1,18 @@ public abstract class RawInt2 { - public void compare(MyVarInfo vi1, MyVarInfo vi2) { + public void compare(MyVarInfo vi1, MyVarInfo vi2) { - int name1in2 = 1; - int name2in1 = 2; - int cmp1 = (name1in2 == -1) ? 0 : vi1.varinfo_index - 1; - // Removing this line eliminates the error, even though cmp2 is not used - int cmp2 = false ? 0 : 15; - sign(cmp1); - } + int name1in2 = 1; + int name2in1 = 2; + int cmp1 = (name1in2 == -1) ? 0 : vi1.varinfo_index - 1; + // Removing this line eliminates the error, even though cmp2 is not used + int cmp2 = false ? 0 : 15; + sign(cmp1); + } - public void sign(int x) {} + public void sign(int x) {} } final class MyVarInfo { - public int varinfo_index = 22; + public int varinfo_index = 22; } diff --git a/checker/tests/nullness/RawParameter.java b/checker/tests/nullness/RawParameter.java index c1d18f4ac06..f192c7930ad 100644 --- a/checker/tests/nullness/RawParameter.java +++ b/checker/tests/nullness/RawParameter.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class RawParameter { - private @Nullable T payload; + private @Nullable T payload; - // Using a parameterized type here avoids the exception - @SuppressWarnings("unchecked") - public void clearPayload(RawParameter node) { - node.payload = null; - } + // Using a parameterized type here avoids the exception + @SuppressWarnings("unchecked") + public void clearPayload(RawParameter node) { + node.payload = null; + } } diff --git a/checker/tests/nullness/RawSuper.java b/checker/tests/nullness/RawSuper.java index 74b9e92a675..faad56f499b 100644 --- a/checker/tests/nullness/RawSuper.java +++ b/checker/tests/nullness/RawSuper.java @@ -4,82 +4,82 @@ // This test is broken as it uses multiple classes. Javac halts when seeing the first error public class RawSuper { - class A { - @NonNull Object afield; - - A() { - super(); - mRA(this); - // :: error: (type.incompatible) - mA(this); - afield = new Object(); - mRA(this); - mA(this); - } - - A(int ignore) { - this.raw(); - afield = new Object(); - } - - void raw(A this) {} - - void nonRaw() {} + class A { + @NonNull Object afield; + + A() { + super(); + mRA(this); + // :: error: (type.incompatible) + mA(this); + afield = new Object(); + mRA(this); + mA(this); } - class B extends A { - @NonNull Object bfield; - - B() { - mRA(this); - mA(this); - mRB(this); - // :: error: (type.incompatible) - mB(this); - bfield = new Object(); - mRA(this); - mA(this); - mRB(this); - mB(this); - } - - void raw(B this) { - // :: error: (type.incompatible) - super.nonRaw(); - } + A(int ignore) { + this.raw(); + afield = new Object(); } - // This test may be extraneous - class C extends B { - @NonNull Object cfield; - - C() { - mRA(this); - mA(this); - mRB(this); - mB(this); - mRC(this); - // :: error: (type.incompatible) - mC(this); - cfield = new Object(); - mRA(this); - mA(this); - mRB(this); - mB(this); - mRC(this); - mC(this); - } + void raw(A this) {} + + void nonRaw() {} + } + + class B extends A { + @NonNull Object bfield; + + B() { + mRA(this); + mA(this); + mRB(this); + // :: error: (type.incompatible) + mB(this); + bfield = new Object(); + mRA(this); + mA(this); + mRB(this); + mB(this); } - void mA(A a) {} + void raw(B this) { + // :: error: (type.incompatible) + super.nonRaw(); + } + } + + // This test may be extraneous + class C extends B { + @NonNull Object cfield; + + C() { + mRA(this); + mA(this); + mRB(this); + mB(this); + mRC(this); + // :: error: (type.incompatible) + mC(this); + cfield = new Object(); + mRA(this); + mA(this); + mRB(this); + mB(this); + mRC(this); + mC(this); + } + } + + void mA(A a) {} - void mRA(A a) {} + void mRA(A a) {} - void mB(B b) {} + void mB(B b) {} - void mRB(B b) {} + void mRB(B b) {} - void mC(C c) {} + void mC(C c) {} - void mRC(C c) {} + void mRC(C c) {} } diff --git a/checker/tests/nullness/RawTypesAssignment.java b/checker/tests/nullness/RawTypesAssignment.java index 481fbcf1069..965733f1311 100644 --- a/checker/tests/nullness/RawTypesAssignment.java +++ b/checker/tests/nullness/RawTypesAssignment.java @@ -1,31 +1,30 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.HashMap; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; public class RawTypesAssignment { - Map rawMap = new HashMap(); - Map> notRawMapDiamondRec = new HashMap<>(); - // :: warning: [unchecked] unchecked conversion - Map> notRawMapRawHashMapRec = new HashMap(); + Map rawMap = new HashMap(); + Map> notRawMapDiamondRec = new HashMap<>(); + // :: warning: [unchecked] unchecked conversion + Map> notRawMapRawHashMapRec = new HashMap(); - Map notRawMapDiamond = new HashMap<>(); - // :: warning: [unchecked] unchecked conversion - Map notRawMapRawHashMap = new HashMap(); + Map notRawMapDiamond = new HashMap<>(); + // :: warning: [unchecked] unchecked conversion + Map notRawMapRawHashMap = new HashMap(); - Map notRawMapDiamondObjectObject = new HashMap<>(); - // :: warning: [unchecked] unchecked conversion - Map notRawMapDiamondObjectObjectRaw = new HashMap(); + Map notRawMapDiamondObjectObject = new HashMap<>(); + // :: warning: [unchecked] unchecked conversion + Map notRawMapDiamondObjectObjectRaw = new HashMap(); - RecursiveGeneric rawRecursiveGeneric = new RecursiveGeneric(); - RecursiveGeneric notRawRecursiveGenericDiamond = new RecursiveGeneric<>(); - // :: warning: [unchecked] unchecked conversion - RecursiveGeneric notRawRecursiveGenericRaw = new RecursiveGeneric(); + RecursiveGeneric rawRecursiveGeneric = new RecursiveGeneric(); + RecursiveGeneric notRawRecursiveGenericDiamond = new RecursiveGeneric<>(); + // :: warning: [unchecked] unchecked conversion + RecursiveGeneric notRawRecursiveGenericRaw = new RecursiveGeneric(); - class Generic {} + class Generic {} - class RecursiveGeneric> {} + class RecursiveGeneric> {} - class MyClass extends Generic {} + class MyClass extends Generic {} } diff --git a/checker/tests/nullness/RawTypesNullness.java b/checker/tests/nullness/RawTypesNullness.java index 8d58fcd4d23..20991ebb4f2 100644 --- a/checker/tests/nullness/RawTypesNullness.java +++ b/checker/tests/nullness/RawTypesNullness.java @@ -9,69 +9,69 @@ class MyClass extends Generic {} class BoundedGeneric {} class RawTypesNullness { - Generic rawReturn() { - return new Generic(); - } + Generic rawReturn() { + return new Generic(); + } - Generic rawField = new Generic(); + Generic rawField = new Generic(); - void use() { - Generic rawLocal = new Generic<>(); - Generic generic1 = rawReturn(); - Generic generic2 = rawField; - Generic generic3 = rawLocal; - } + void use() { + Generic rawLocal = new Generic<>(); + Generic generic1 = rawReturn(); + Generic generic2 = rawField; + Generic generic3 = rawLocal; + } } class TestBounded { - BoundedGeneric rawReturn() { - return new BoundedGeneric<>(); - } + BoundedGeneric rawReturn() { + return new BoundedGeneric<>(); + } - BoundedGeneric rawField = new BoundedGeneric(); + BoundedGeneric rawField = new BoundedGeneric(); - void useWildCard() { - BoundedGeneric rawLocal = new BoundedGeneric(); - BoundedGeneric generic1 = rawReturn(); - BoundedGeneric generic2 = rawField; - BoundedGeneric generic3 = rawLocal; - } + void useWildCard() { + BoundedGeneric rawLocal = new BoundedGeneric(); + BoundedGeneric generic1 = rawReturn(); + BoundedGeneric generic2 = rawField; + BoundedGeneric generic3 = rawLocal; + } - @SuppressWarnings("unchecked") // only needed on JDK 17 and lower - void useBoundedWildCard() { - BoundedGeneric rawLocal = new BoundedGeneric(); - BoundedGeneric generic1 = rawReturn(); - BoundedGeneric generic2 = rawField; - BoundedGeneric generic3 = rawLocal; - } + @SuppressWarnings("unchecked") // only needed on JDK 17 and lower + void useBoundedWildCard() { + BoundedGeneric rawLocal = new BoundedGeneric(); + BoundedGeneric generic1 = rawReturn(); + BoundedGeneric generic2 = rawField; + BoundedGeneric generic3 = rawLocal; + } - void useBoundedWildCard2() { - BoundedGeneric rawLocal = new BoundedGeneric(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic1 = rawReturn(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic2 = rawField; - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic3 = rawLocal; - } + void useBoundedWildCard2() { + BoundedGeneric rawLocal = new BoundedGeneric(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic1 = rawReturn(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic2 = rawField; + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic3 = rawLocal; + } - void useTypeArg() { - BoundedGeneric rawLocal = new BoundedGeneric(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic1 = rawReturn(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic2 = rawField; - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic3 = rawLocal; - } + void useTypeArg() { + BoundedGeneric rawLocal = new BoundedGeneric(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic1 = rawReturn(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic2 = rawField; + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic3 = rawLocal; + } - void useAnnotatedTypeArg() { - BoundedGeneric rawLocal = new BoundedGeneric(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric<@Nullable String> generic1 = rawReturn(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric<@Nullable String> generic2 = rawField; - // :: warning: [unchecked] unchecked conversion - BoundedGeneric<@Nullable String> generic3 = rawLocal; - } + void useAnnotatedTypeArg() { + BoundedGeneric rawLocal = new BoundedGeneric(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric<@Nullable String> generic1 = rawReturn(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric<@Nullable String> generic2 = rawField; + // :: warning: [unchecked] unchecked conversion + BoundedGeneric<@Nullable String> generic3 = rawLocal; + } } diff --git a/checker/tests/nullness/RawTypesUses.java b/checker/tests/nullness/RawTypesUses.java index 45a974dbcc8..f1ac24a9fe1 100644 --- a/checker/tests/nullness/RawTypesUses.java +++ b/checker/tests/nullness/RawTypesUses.java @@ -2,50 +2,50 @@ import org.checkerframework.checker.nullness.qual.Nullable; public abstract class RawTypesUses { - class Generic { - G foo() { - throw new RuntimeException(); - } - } - - void foo() { - Generic<@Nullable String> notRawNullable = new Generic<>(); - // :: error: (assignment.type.incompatible) - @NonNull Object o1 = notRawNullable.foo(); - - Generic rawNullable = new Generic<@Nullable String>(); - // :: error: (assignment.type.incompatible) - @NonNull Object o2 = rawNullable.foo(); - - Generic<@NonNull String> notRawNonNull = new Generic<>(); - @NonNull Object o3 = notRawNonNull.foo(); - - Generic rawNonNull = new Generic<@NonNull String>(); - Generic rawNonNullAlais = rawNonNull; - // :: error: (assignment.type.incompatible) - @NonNull Object o4 = rawNonNull.foo(); - // :: error: (assignment.type.incompatible) - @NonNull Object o5 = rawNonNullAlais.foo(); - } - - abstract Generic rawReturn(); - - void bar() { - // :: warning: [unchecked] unchecked conversion - Generic<@Nullable String> notRawNullable = rawReturn(); - // :: error: (assignment.type.incompatible) - @NonNull Object o1 = notRawNullable.foo(); - - Generic rawNullable = rawReturn(); - // :: error: (assignment.type.incompatible) - @NonNull Object o2 = rawNullable.foo(); - - // :: error: (assignment.type.incompatible) - @NonNull Object o3 = rawReturn().foo(); - - Generic local = rawReturn(); - Generic localAlias = local; - // :: error: (assignment.type.incompatible) - @NonNull Object o4 = local.foo(); + class Generic { + G foo() { + throw new RuntimeException(); } + } + + void foo() { + Generic<@Nullable String> notRawNullable = new Generic<>(); + // :: error: (assignment.type.incompatible) + @NonNull Object o1 = notRawNullable.foo(); + + Generic rawNullable = new Generic<@Nullable String>(); + // :: error: (assignment.type.incompatible) + @NonNull Object o2 = rawNullable.foo(); + + Generic<@NonNull String> notRawNonNull = new Generic<>(); + @NonNull Object o3 = notRawNonNull.foo(); + + Generic rawNonNull = new Generic<@NonNull String>(); + Generic rawNonNullAlais = rawNonNull; + // :: error: (assignment.type.incompatible) + @NonNull Object o4 = rawNonNull.foo(); + // :: error: (assignment.type.incompatible) + @NonNull Object o5 = rawNonNullAlais.foo(); + } + + abstract Generic rawReturn(); + + void bar() { + // :: warning: [unchecked] unchecked conversion + Generic<@Nullable String> notRawNullable = rawReturn(); + // :: error: (assignment.type.incompatible) + @NonNull Object o1 = notRawNullable.foo(); + + Generic rawNullable = rawReturn(); + // :: error: (assignment.type.incompatible) + @NonNull Object o2 = rawNullable.foo(); + + // :: error: (assignment.type.incompatible) + @NonNull Object o3 = rawReturn().foo(); + + Generic local = rawReturn(); + Generic localAlias = local; + // :: error: (assignment.type.incompatible) + @NonNull Object o4 = local.foo(); + } } diff --git a/checker/tests/nullness/ReadyReadLine.java b/checker/tests/nullness/ReadyReadLine.java index 77a7c5eac64..8acc3540835 100644 --- a/checker/tests/nullness/ReadyReadLine.java +++ b/checker/tests/nullness/ReadyReadLine.java @@ -4,30 +4,30 @@ public class ReadyReadLine { - void m(MyBufferedReader buf) throws Exception { - if (buf.ready()) { - String line = buf.readLine(); - line.toString(); - } + void m(MyBufferedReader buf) throws Exception { + if (buf.ready()) { + String line = buf.readLine(); + line.toString(); + } - if (buf.readLine() != null) { - // :: error: (dereference.of.nullable) - buf.readLine().toString(); - } + if (buf.readLine() != null) { + // :: error: (dereference.of.nullable) + buf.readLine().toString(); } + } } // This is a replication of the JDK BufferedReader, with only the relevant methods. class MyBufferedReader { - public @Nullable String readLine() throws Exception { - return null; - } + public @Nullable String readLine() throws Exception { + return null; + } - @EnsuresNonNullIf(expression = "readLine()", result = true) - @Pure - public boolean ready() throws Exception { - // don't bother with implementation. - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf(expression = "readLine()", result = true) + @Pure + public boolean ready() throws Exception { + // don't bother with implementation. + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/ReceiverAnnotation.java b/checker/tests/nullness/ReceiverAnnotation.java index 9f2fc729fdf..1a4cef23920 100644 --- a/checker/tests/nullness/ReceiverAnnotation.java +++ b/checker/tests/nullness/ReceiverAnnotation.java @@ -3,11 +3,11 @@ public class ReceiverAnnotation { - void receiver1(ReceiverAnnotation this) {} + void receiver1(ReceiverAnnotation this) {} - // :: error: (nullness.on.receiver) - void receiver2(@NonNull ReceiverAnnotation this) {} + // :: error: (nullness.on.receiver) + void receiver2(@NonNull ReceiverAnnotation this) {} - // :: error: (nullness.on.receiver) - void receiver3(@Nullable ReceiverAnnotation this) {} + // :: error: (nullness.on.receiver) + void receiver3(@Nullable ReceiverAnnotation this) {} } diff --git a/checker/tests/nullness/ReferencesDefaults.java b/checker/tests/nullness/ReferencesDefaults.java index f68110b49eb..0f5bd9fe13f 100644 --- a/checker/tests/nullness/ReferencesDefaults.java +++ b/checker/tests/nullness/ReferencesDefaults.java @@ -1,17 +1,17 @@ public class ReferencesDefaults { - @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.Nullable.class) - class Decl { - Object test() { - // legal, because of changed default. - return null; - } + @org.checkerframework.framework.qual.DefaultQualifier( + org.checkerframework.checker.nullness.qual.Nullable.class) + class Decl { + Object test() { + // legal, because of changed default. + return null; } + } - class Use { - Decl d = new Decl(); - // here the default for f is NonNull -> error - // :: error: (assignment.type.incompatible) - Object f = d.test(); - } + class Use { + Decl d = new Decl(); + // here the default for f is NonNull -> error + // :: error: (assignment.type.incompatible) + Object f = d.test(); + } } diff --git a/checker/tests/nullness/RefineArray.java b/checker/tests/nullness/RefineArray.java index a9e34be7b1f..7c40ef78c17 100644 --- a/checker/tests/nullness/RefineArray.java +++ b/checker/tests/nullness/RefineArray.java @@ -2,26 +2,26 @@ // TODO: Add as test public class RefineArray { - public static T[] concat(T @Nullable [] a, T @Nullable [] b) { - if (a == null) { - if (b != null) { - return b; - } else { - @SuppressWarnings("unchecked") - T[] result = (T[]) new Object[0]; - return result; - } - } else { - if (b == null) { - return a; - } else { - @SuppressWarnings("unchecked") - T[] result = (T[]) new @MonotonicNonNull Object[a.length + b.length]; + public static T[] concat(T @Nullable [] a, T @Nullable [] b) { + if (a == null) { + if (b != null) { + return b; + } else { + @SuppressWarnings("unchecked") + T[] result = (T[]) new Object[0]; + return result; + } + } else { + if (b == null) { + return a; + } else { + @SuppressWarnings("unchecked") + T[] result = (T[]) new @MonotonicNonNull Object[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } - } + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } } + } } diff --git a/checker/tests/nullness/RefineOverride.java b/checker/tests/nullness/RefineOverride.java index afd025365bb..9c2762f2dbe 100644 --- a/checker/tests/nullness/RefineOverride.java +++ b/checker/tests/nullness/RefineOverride.java @@ -5,214 +5,214 @@ public class RefineOverride { - void m(Sub<@Nullable String> snb, Sub<@NonNull String> snn) { - snb.m7(null); - snn.m7(null); - } + void m(Sub<@Nullable String> snb, Sub<@NonNull String> snn) { + snb.m7(null); + snn.m7(null); + } - class Super { + class Super { - void m1(@NonNull String s) {} + void m1(@NonNull String s) {} - void m2(@NonNull String s) {} + void m2(@NonNull String s) {} - void m5(@NonNull String s) {} + void m5(@NonNull String s) {} - void m6(@Nullable String s) {} + void m6(@Nullable String s) {} - void m7(T s) {} + void m7(T s) {} - void m11(@NonNull String s1, @NonNull String s2) {} + void m11(@NonNull String s1, @NonNull String s2) {} - void m12(@NonNull String s1, @Nullable String s2) {} + void m12(@NonNull String s1, @Nullable String s2) {} - void m13(@Nullable String s1, @NonNull String s2) {} + void m13(@Nullable String s1, @NonNull String s2) {} - void m14(@Nullable String s1, @Nullable String s2) {} + void m14(@Nullable String s1, @Nullable String s2) {} - void m15(T s1, T s2) {} + void m15(T s1, T s2) {} - void m16(@NonNull T s1, @NonNull T s2) {} + void m16(@NonNull T s1, @NonNull T s2) {} - void m17(@NonNull T s1, @Nullable T s2) {} + void m17(@NonNull T s1, @Nullable T s2) {} - void m18(@Nullable T s1, @NonNull T s2) {} + void m18(@Nullable T s1, @NonNull T s2) {} - void m19(@Nullable T s1, @Nullable T s2) {} + void m19(@Nullable T s1, @Nullable T s2) {} - void m21(@Nullable String[] a) {} + void m21(@Nullable String[] a) {} - void m22(@NonNull String[] a) {} + void m22(@NonNull String[] a) {} - void m23(@Nullable String[] a) {} + void m23(@Nullable String[] a) {} - void m24(@NonNull String[] a) {} + void m24(@NonNull String[] a) {} - void m25(T[] a) {} + void m25(T[] a) {} - void m26(@Nullable T[] a) {} + void m26(@Nullable T[] a) {} - void m27(@NonNull T[] a) {} + void m27(@NonNull T[] a) {} - void m28(@Nullable T[] a) {} + void m28(@Nullable T[] a) {} - void m29(@NonNull T[] a) {} - } + void m29(@NonNull T[] a) {} + } - class Sub extends Super { + class Sub extends Super { - @Override - void m1(@Nullable String s) {} + @Override + void m1(@Nullable String s) {} - @Override - void m2(@PolyNull String s) {} + @Override + void m2(@PolyNull String s) {} - // In the following declarations, all previously-valid invocations remain - // valid, so the compiler should permit the overriding. + // In the following declarations, all previously-valid invocations remain + // valid, so the compiler should permit the overriding. - // Case 1. A single parameter type is changed from anything to @PolyNull - // in an overriding method. + // Case 1. A single parameter type is changed from anything to @PolyNull + // in an overriding method. - @Override - void m5(@PolyNull String s) {} + @Override + void m5(@PolyNull String s) {} - // TODO: should be legal - @Override - void m6(@PolyNull String s) {} + // TODO: should be legal + @Override + void m6(@PolyNull String s) {} - // TODO: should be legal - @Override - void m7(@PolyNull T s) {} + // TODO: should be legal + @Override + void m7(@PolyNull T s) {} - // Case 2. Multiple parameter types are changed to @PolyNull in an - // overriding method. + // Case 2. Multiple parameter types are changed to @PolyNull in an + // overriding method. - // (The types for m14 might be better written as "@PolyNull(1) - // ... @PolyNull(2)", but all invocations remain valid. + // (The types for m14 might be better written as "@PolyNull(1) + // ... @PolyNull(2)", but all invocations remain valid. - @Override - void m11(@PolyNull String s1, @PolyNull String s2) {} + @Override + void m11(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m12(@PolyNull String s1, @PolyNull String s2) {} + // TODO: should be legal + @Override + void m12(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m13(@PolyNull String s1, @PolyNull String s2) {} + // TODO: should be legal + @Override + void m13(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m14(@PolyNull String s1, @PolyNull String s2) {} + // TODO: should be legal + @Override + void m14(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m15(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m15(@PolyNull T s1, @PolyNull T s2) {} - @Override - void m16(@PolyNull T s1, @PolyNull T s2) {} + @Override + void m16(@PolyNull T s1, @PolyNull T s2) {} - // TODO: should be legal - @Override - void m17(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m17(@PolyNull T s1, @PolyNull T s2) {} - // TODO: should be legal - @Override - void m18(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m18(@PolyNull T s1, @PolyNull T s2) {} - // TODO: should be legal - @Override - void m19(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m19(@PolyNull T s1, @PolyNull T s2) {} - // Case 3. Expand the element type of an array. - // The new permissible types are not supertypes of the old types, - // but they still expand the set of permitted invocations. + // Case 3. Expand the element type of an array. + // The new permissible types are not supertypes of the old types, + // but they still expand the set of permitted invocations. - // :: error: (override.param.invalid) - @Override - void m21(@NonNull String[] a) {} + // :: error: (override.param.invalid) + @Override + void m21(@NonNull String[] a) {} - // :: error: Changing incompatibly to forbid old invocations is not permitted. - @Override - void m22(@Nullable String[] a) {} + // :: error: Changing incompatibly to forbid old invocations is not permitted. + @Override + void m22(@Nullable String[] a) {} - // TODO: should be legal - @Override - void m23(@PolyNull String[] a) {} + // TODO: should be legal + @Override + void m23(@PolyNull String[] a) {} - @Override - void m24(@PolyNull String[] a) {} + @Override + void m24(@PolyNull String[] a) {} - @Override - void m25(@PolyNull T[] a) {} + @Override + void m25(@PolyNull T[] a) {} - // :: error: (override.param.invalid) - @Override - void m26(@NonNull T[] a) {} + // :: error: (override.param.invalid) + @Override + void m26(@NonNull T[] a) {} - // :: error: Changing incompatibly to forbid old invocations is not permitted. - @Override - void m27(@Nullable T[] a) {} + // :: error: Changing incompatibly to forbid old invocations is not permitted. + @Override + void m27(@Nullable T[] a) {} - // TODO: should be legal - @Override - void m28(@PolyNull T[] a) {} + // TODO: should be legal + @Override + void m28(@PolyNull T[] a) {} - @Override - void m29(@PolyNull T[] a) {} - } + @Override + void m29(@PolyNull T[] a) {} + } - class Super2 { + class Super2 { - void t1(String s) {} + void t1(String s) {} - void t2(String s) {} + void t2(String s) {} - void t3(@Nullable String s) {} + void t3(@Nullable String s) {} - void t4(String s) {} + void t4(String s) {} - void t5(String[] s) {} + void t5(String[] s) {} - void t6(T s) {} + void t6(T s) {} - void t7(T s) {} + void t7(T s) {} - void t8(T[] s) {} + void t8(T[] s) {} - void t9(T[] s) {} - } + void t9(T[] s) {} + } - class Sub2 extends Super2 { + class Sub2 extends Super2 { - @Override - void t1(String s) {} + @Override + void t1(String s) {} - @Override - void t2(@Nullable String s) {} + @Override + void t2(@Nullable String s) {} - // :: error: (override.param.invalid) - @Override - void t3(String s) {} + // :: error: (override.param.invalid) + @Override + void t3(String s) {} - @Override - void t4(@PolyNull String s) {} + @Override + void t4(@PolyNull String s) {} - @Override - void t5(@PolyNull String[] s) {} + @Override + void t5(@PolyNull String[] s) {} - @Override - void t6(@Nullable T s) {} + @Override + void t6(@Nullable T s) {} - // TODO: should be legal - @Override - void t7(@PolyNull T s) {} + // TODO: should be legal + @Override + void t7(@PolyNull T s) {} - @Override - void t8(@Nullable T[] s) {} + @Override + void t8(@Nullable T[] s) {} - // TODO: should be legal - @Override - void t9(@PolyNull T[] s) {} - } + // TODO: should be legal + @Override + void t9(@PolyNull T[] s) {} + } } diff --git a/checker/tests/nullness/RepeatEnsuresKeyFor.java b/checker/tests/nullness/RepeatEnsuresKeyFor.java index 7bc85529c78..f911bb85346 100644 --- a/checker/tests/nullness/RepeatEnsuresKeyFor.java +++ b/checker/tests/nullness/RepeatEnsuresKeyFor.java @@ -1,102 +1,101 @@ -import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; -import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; +import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; public class RepeatEnsuresKeyFor { - Map map = new HashMap<>(); + Map map = new HashMap<>(); - public void func1(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - } + public void func1(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + } - public boolean func2(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - return true; - } + public boolean func2(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + return true; + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client1(String a, String b, String c) { - withpostconditionsfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client1(String a, String b, String c) { + withpostconditionsfunc1(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client2(String a, String b, String c) { - withpostconditionfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client2(String a, String b, String c) { + withpostconditionfunc1(a, b, c); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client3(String a, String b, String c) { - return withcondpostconditionsfunc2(a, b, c); - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client3(String a, String b, String c) { + return withcondpostconditionsfunc2(a, b, c); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client4(String a, String b, String c) { - return withcondpostconditionfunc2(a, b, c); - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client4(String a, String b, String c) { + return withcondpostconditionfunc2(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void withpostconditionsfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void withpostconditionsfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionsfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - return true; - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionsfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + return true; + } - @EnsuresKeyFor.List({ - @EnsuresKeyFor(value = "#1", map = "map"), - @EnsuresKeyFor(value = "#2", map = "map"), - }) - @EnsuresKeyFor(value = "#3", map = "map") - public void withpostconditionfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - } + @EnsuresKeyFor.List({ + @EnsuresKeyFor(value = "#1", map = "map"), + @EnsuresKeyFor(value = "#2", map = "map"), + }) + @EnsuresKeyFor(value = "#3", map = "map") + public void withpostconditionfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - return true; - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + return true; + } } diff --git a/checker/tests/nullness/RepeatEnsuresKeyForWithError.java b/checker/tests/nullness/RepeatEnsuresKeyForWithError.java index 8a2e77b8ed0..025af3478f1 100644 --- a/checker/tests/nullness/RepeatEnsuresKeyForWithError.java +++ b/checker/tests/nullness/RepeatEnsuresKeyForWithError.java @@ -1,100 +1,99 @@ -import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; -import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; +import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; public class RepeatEnsuresKeyForWithError { - Map map = new HashMap<>(); + Map map = new HashMap<>(); - public void func1(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - } + public void func1(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + } - public boolean func2(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - return true; - } + public boolean func2(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + return true; + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client1(String a, String b, String c) { - withpostconditionsfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client1(String a, String b, String c) { + withpostconditionsfunc1(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client2(String a, String b, String c) { - withpostconditionfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client2(String a, String b, String c) { + withpostconditionfunc1(a, b, c); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client3(String a, String b, String c) { - return withcondpostconditionsfunc2(a, b, c); - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client3(String a, String b, String c) { + return withcondpostconditionsfunc2(a, b, c); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client4(String a, String b, String c) { - return withcondpostconditionfunc2(a, b, c); - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client4(String a, String b, String c) { + return withcondpostconditionfunc2(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionsfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionsfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionsfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionsfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresKeyFor.List({ - @EnsuresKeyFor(value = "#1", map = "map"), - @EnsuresKeyFor(value = "#2", map = "map"), - }) - @EnsuresKeyFor(value = "#3", map = "map") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - } + @EnsuresKeyFor.List({ + @EnsuresKeyFor(value = "#1", map = "map"), + @EnsuresKeyFor(value = "#2", map = "map"), + }) + @EnsuresKeyFor(value = "#3", map = "map") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/RepeatEnsuresNonNull.java b/checker/tests/nullness/RepeatEnsuresNonNull.java index 67e9f96a6d1..7c82a52ede0 100644 --- a/checker/tests/nullness/RepeatEnsuresNonNull.java +++ b/checker/tests/nullness/RepeatEnsuresNonNull.java @@ -4,88 +4,88 @@ public class RepeatEnsuresNonNull { - protected @Nullable String value1; - protected @Nullable String value2; - protected @Nullable String value3; + protected @Nullable String value1; + protected @Nullable String value2; + protected @Nullable String value3; - public boolean func1() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - return true; - } + public boolean func1() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + return true; + } - public void func2() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - } + public void func2() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - public void client2() { - withpostconditionsfunc2(); - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + public void client2() { + withpostconditionsfunc2(); + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client3() { - return withcondpostconditionfunc1(); - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client3() { + return withcondpostconditionfunc1(); + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - public void client4() { - withpostconditionfunc2(); - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + public void client4() { + withpostconditionfunc2(); + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionsfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - return true; - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionsfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + return true; + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - public void withpostconditionsfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + public void withpostconditionsfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - return true; - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + return true; + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - public void withpostconditionfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + public void withpostconditionfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + } } diff --git a/checker/tests/nullness/RepeatEnsuresNonNullWithError.java b/checker/tests/nullness/RepeatEnsuresNonNullWithError.java index decf53a6cc9..a2b0782dde7 100644 --- a/checker/tests/nullness/RepeatEnsuresNonNullWithError.java +++ b/checker/tests/nullness/RepeatEnsuresNonNullWithError.java @@ -4,92 +4,92 @@ public class RepeatEnsuresNonNullWithError { - protected @Nullable String value1; - protected @Nullable String value2; - protected @Nullable String value3; + protected @Nullable String value1; + protected @Nullable String value2; + protected @Nullable String value3; - public boolean func1() { - value1 = "value1"; - value2 = "value2"; - value3 = null; - return true; - } + public boolean func1() { + value1 = "value1"; + value2 = "value2"; + value3 = null; + return true; + } - public void func2() { - value1 = "value1"; - value2 = "value2"; - value3 = null; - } + public void func2() { + value1 = "value1"; + value2 = "value2"; + value3 = null; + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - public void client2() { - withpostconditionsfunc2(); - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + public void client2() { + withpostconditionsfunc2(); + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client3() { - return withcondpostconditionfunc1(); - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client3() { + return withcondpostconditionfunc1(); + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - public void client4() { - withpostconditionfunc2(); - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + public void client4() { + withpostconditionfunc2(); + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionsfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionsfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionsfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionsfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + } } diff --git a/checker/tests/nullness/RepeatedRequiresNonNull.java b/checker/tests/nullness/RepeatedRequiresNonNull.java index 5809f8afb09..dfc460e804a 100644 --- a/checker/tests/nullness/RepeatedRequiresNonNull.java +++ b/checker/tests/nullness/RepeatedRequiresNonNull.java @@ -5,74 +5,74 @@ import org.checkerframework.framework.qual.RequiresQualifier; class RepeatedRequiresNonNull { - @Nullable Object f1; - @Nullable Object f2; + @Nullable Object f1; + @Nullable Object f2; - @RequiresNonNull("this.f1") - @RequiresNonNull("this.f2") - void test() { - f1.toString(); - f2.toString(); - } + @RequiresNonNull("this.f1") + @RequiresNonNull("this.f2") + void test() { + f1.toString(); + f2.toString(); + } - void use1() { - // :: error: (contracts.precondition.not.satisfied) - test(); - } + void use1() { + // :: error: (contracts.precondition.not.satisfied) + test(); + } - void use2() { - if (this.f1 != null) { - // :: error: (contracts.precondition.not.satisfied) - test(); - } + void use2() { + if (this.f1 != null) { + // :: error: (contracts.precondition.not.satisfied) + test(); } + } - void use3() { - if (this.f2 != null) { - // :: error: (contracts.precondition.not.satisfied) - test(); - } + void use3() { + if (this.f2 != null) { + // :: error: (contracts.precondition.not.satisfied) + test(); } + } - void use4() { - if (this.f1 != null && this.f2 != null) { - test(); - } + void use4() { + if (this.f1 != null && this.f2 != null) { + test(); } + } - // This part of the test is to ensure that @RequiresNonNull and @RequiresQualifier behave - // the same way. It is identical, but uses @RequiresQualifier on the test2() method instead - // of the @RequiresNonNull on the test() method. + // This part of the test is to ensure that @RequiresNonNull and @RequiresQualifier behave + // the same way. It is identical, but uses @RequiresQualifier on the test2() method instead + // of the @RequiresNonNull on the test() method. - @RequiresQualifier(expression = "this.f1", qualifier = NonNull.class) - @RequiresQualifier(expression = "this.f2", qualifier = NonNull.class) - void test2() { - f1.toString(); - f2.toString(); - } + @RequiresQualifier(expression = "this.f1", qualifier = NonNull.class) + @RequiresQualifier(expression = "this.f2", qualifier = NonNull.class) + void test2() { + f1.toString(); + f2.toString(); + } - void use21() { - // :: error: (contracts.precondition.not.satisfied) - test2(); - } + void use21() { + // :: error: (contracts.precondition.not.satisfied) + test2(); + } - void use22() { - if (this.f1 != null) { - // :: error: (contracts.precondition.not.satisfied) - test2(); - } + void use22() { + if (this.f1 != null) { + // :: error: (contracts.precondition.not.satisfied) + test2(); } + } - void use23() { - if (this.f2 != null) { - // :: error: (contracts.precondition.not.satisfied) - test2(); - } + void use23() { + if (this.f2 != null) { + // :: error: (contracts.precondition.not.satisfied) + test2(); } + } - void use24() { - if (this.f1 != null && this.f2 != null) { - test2(); - } + void use24() { + if (this.f1 != null && this.f2 != null) { + test2(); } + } } diff --git a/checker/tests/nullness/RequiresNonNullTest.java b/checker/tests/nullness/RequiresNonNullTest.java index 2966042e1a5..da2a4b759ae 100644 --- a/checker/tests/nullness/RequiresNonNullTest.java +++ b/checker/tests/nullness/RequiresNonNullTest.java @@ -3,151 +3,151 @@ public class RequiresNonNullTest { - @Nullable Object field1; - @Nullable Object field2; - - @RequiresNonNull("field1") - void method1() { - field1.toString(); // OK, field1 is known to be non-null - this.field1.toString(); // OK, field1 is known to be non-null - // :: error: (dereference.of.nullable) - field2.toString(); // error, might throw NullPointerException + @Nullable Object field1; + @Nullable Object field2; + + @RequiresNonNull("field1") + void method1() { + field1.toString(); // OK, field1 is known to be non-null + this.field1.toString(); // OK, field1 is known to be non-null + // :: error: (dereference.of.nullable) + field2.toString(); // error, might throw NullPointerException + } + + @RequiresNonNull("field1") + void method1also() { + // ok, precondition satisfied by NNOE + method1(); + } + + void method2() { + field1 = new Object(); + method1(); // OK, satisfies method precondition + field1 = null; + // :: error: (contracts.precondition.not.satisfied) + method1(); // error, does not satisfy method precondition + } + + protected @Nullable Object field; + + @RequiresNonNull("field") + public void requiresNonNullField() {} + + public void clientFail(RequiresNonNullTest arg1) { + // :: error: (contracts.precondition.not.satisfied) + arg1.requiresNonNullField(); + } + + public void clientOK(RequiresNonNullTest arg2) { + arg2.field = new Object(); + // note that the following line works + @NonNull Object o = arg2.field; + + arg2.requiresNonNullField(); // OK, field is known to be non-null + } + + // TODO: forbid the field in @NNOE to be less visible than the method + + protected static @Nullable Object staticfield; + + @Pure + @RequiresNonNull("staticfield") + // :: warning: (purity.deterministic.void.method) + public void reqStaticName() { + reqStaticQualName(); + } + + @Pure + @RequiresNonNull("RequiresNonNullTest.staticfield") + // :: warning: (purity.deterministic.void.method) + public void reqStaticQualName() { + reqStaticName(); + } + + public void statClientOK(RequiresNonNullTest arg1) { + staticfield = new Object(); + arg1.reqStaticName(); + + staticfield = new Object(); + arg1.reqStaticQualName(); + + RequiresNonNullTest.staticfield = new Object(); + arg1.reqStaticName(); + RequiresNonNullTest.staticfield = new Object(); + arg1.reqStaticQualName(); + } + + public void statClientFail(RequiresNonNullTest arg1) { + // :: error: (contracts.precondition.not.satisfied) + arg1.reqStaticName(); + // :: error: (contracts.precondition.not.satisfied) + arg1.reqStaticQualName(); + } + + class NNOESubTest extends RequiresNonNullTest { + public void subClientOK(NNOESubTest arg3) { + arg3.field = new Object(); + arg3.requiresNonNullField(); } - @RequiresNonNull("field1") - void method1also() { - // ok, precondition satisfied by NNOE - method1(); + public void subClientFail(NNOESubTest arg4) { + // :: error: (contracts.precondition.not.satisfied) + arg4.requiresNonNullField(); } - void method2() { - field1 = new Object(); - method1(); // OK, satisfies method precondition - field1 = null; - // :: error: (contracts.precondition.not.satisfied) - method1(); // error, does not satisfy method precondition - } - - protected @Nullable Object field; + public void subStat(NNOESubTest arg5) { + RequiresNonNullTest.staticfield = new Object(); + arg5.reqStaticQualName(); - @RequiresNonNull("field") - public void requiresNonNullField() {} + staticfield = new Object(); + arg5.reqStaticQualName(); - public void clientFail(RequiresNonNullTest arg1) { - // :: error: (contracts.precondition.not.satisfied) - arg1.requiresNonNullField(); + NNOESubTest.staticfield = new Object(); + arg5.reqStaticQualName(); } + } - public void clientOK(RequiresNonNullTest arg2) { - arg2.field = new Object(); - // note that the following line works - @NonNull Object o = arg2.field; + private @Nullable Object notHidden; - arg2.requiresNonNullField(); // OK, field is known to be non-null - } + class NNOEHidingTest extends RequiresNonNullTest { - // TODO: forbid the field in @NNOE to be less visible than the method + protected @Nullable String field; - protected static @Nullable Object staticfield; + public void hidingClient1(NNOEHidingTest arg5) { + arg5.field = "ha!"; - @Pure - @RequiresNonNull("staticfield") - // :: warning: (purity.deterministic.void.method) - public void reqStaticName() { - reqStaticQualName(); - } + // TODO: The error message should say something about the hidden field. + // :: error: (contracts.precondition.not.satisfied) + arg5.requiresNonNullField(); - @Pure - @RequiresNonNull("RequiresNonNullTest.staticfield") - // :: warning: (purity.deterministic.void.method) - public void reqStaticQualName() { - reqStaticName(); + // TODO: Add test like: + // arg5.ensuresNonNullField(); + // arg5.requiresNonNullField(); } - public void statClientOK(RequiresNonNullTest arg1) { - staticfield = new Object(); - arg1.reqStaticName(); - - staticfield = new Object(); - arg1.reqStaticQualName(); - - RequiresNonNullTest.staticfield = new Object(); - arg1.reqStaticName(); - RequiresNonNullTest.staticfield = new Object(); - arg1.reqStaticQualName(); - } - - public void statClientFail(RequiresNonNullTest arg1) { - // :: error: (contracts.precondition.not.satisfied) - arg1.reqStaticName(); - // :: error: (contracts.precondition.not.satisfied) - arg1.reqStaticQualName(); - } - - class NNOESubTest extends RequiresNonNullTest { - public void subClientOK(NNOESubTest arg3) { - arg3.field = new Object(); - arg3.requiresNonNullField(); - } - - public void subClientFail(NNOESubTest arg4) { - // :: error: (contracts.precondition.not.satisfied) - arg4.requiresNonNullField(); - } - - public void subStat(NNOESubTest arg5) { - RequiresNonNullTest.staticfield = new Object(); - arg5.reqStaticQualName(); - - staticfield = new Object(); - arg5.reqStaticQualName(); - - NNOESubTest.staticfield = new Object(); - arg5.reqStaticQualName(); - } + public void hidingClient2(NNOEHidingTest arg6) { + // :: error: (contracts.precondition.not.satisfied) + arg6.requiresNonNullField(); } - private @Nullable Object notHidden; - - class NNOEHidingTest extends RequiresNonNullTest { - - protected @Nullable String field; - - public void hidingClient1(NNOEHidingTest arg5) { - arg5.field = "ha!"; + protected @Nullable Object notHidden; - // TODO: The error message should say something about the hidden field. - // :: error: (contracts.precondition.not.satisfied) - arg5.requiresNonNullField(); - - // TODO: Add test like: - // arg5.ensuresNonNullField(); - // arg5.requiresNonNullField(); - } - - public void hidingClient2(NNOEHidingTest arg6) { - // :: error: (contracts.precondition.not.satisfied) - arg6.requiresNonNullField(); - } - - protected @Nullable Object notHidden; - - @RequiresNonNull("notHidden") - void notHiddenTest() { - // the field in the superclass is private -> don't complain about hiding - } + @RequiresNonNull("notHidden") + void notHiddenTest() { + // the field in the superclass is private -> don't complain about hiding } + } - static @Nullable Object o = "m"; + static @Nullable Object o = "m"; - @RequiresNonNull("o") - void test() { - o = null; - } + @RequiresNonNull("o") + void test() { + o = null; + } - @RequiresNonNull("thisShouldIssue1Error") - // Test case for Issue 1051 - // https://github.com/typetools/checker-framework/issues/1051 - // :: error: (flowexpr.parse.error) - void testIssue1051() {} + @RequiresNonNull("thisShouldIssue1Error") + // Test case for Issue 1051 + // https://github.com/typetools/checker-framework/issues/1051 + // :: error: (flowexpr.parse.error) + void testIssue1051() {} } diff --git a/checker/tests/nullness/RequiresPrivateField.java b/checker/tests/nullness/RequiresPrivateField.java index 4be9012fdb7..127f81852cf 100644 --- a/checker/tests/nullness/RequiresPrivateField.java +++ b/checker/tests/nullness/RequiresPrivateField.java @@ -5,13 +5,13 @@ public class RequiresPrivateField { - @RequiresNonNull("PptCombined.assemblies") - public void testFindIntermediateBlocks1() { - // no body - } + @RequiresNonNull("PptCombined.assemblies") + public void testFindIntermediateBlocks1() { + // no body + } } class PptCombined { - @SpecPublic private static @MonotonicNonNull String assemblies = null; + @SpecPublic private static @MonotonicNonNull String assemblies = null; } diff --git a/checker/tests/nullness/SAMLineParser.java b/checker/tests/nullness/SAMLineParser.java index 221978acf8c..f02dc8bf615 100644 --- a/checker/tests/nullness/SAMLineParser.java +++ b/checker/tests/nullness/SAMLineParser.java @@ -1,8 +1,8 @@ public class SAMLineParser { - private int x; + private int x; - private String makeErrorString() { - return "" + (this.x <= 0 ? "" : this.x); - } + private String makeErrorString() { + return "" + (this.x <= 0 ? "" : this.x); + } } diff --git a/checker/tests/nullness/SamFileValidator.java b/checker/tests/nullness/SamFileValidator.java index f0f470f2167..8a10b245a2f 100644 --- a/checker/tests/nullness/SamFileValidator.java +++ b/checker/tests/nullness/SamFileValidator.java @@ -5,9 +5,9 @@ public class SamFileValidator { - private class Codec { - public Map.Entry decode() { - return new AbstractMap.SimpleEntry("hello", "goodbye"); - } + private class Codec { + public Map.Entry decode() { + return new AbstractMap.SimpleEntry("hello", "goodbye"); } + } } diff --git a/checker/tests/nullness/ScopingConstruct.java b/checker/tests/nullness/ScopingConstruct.java index 22099cee3d6..e8cb7e47ce5 100644 --- a/checker/tests/nullness/ScopingConstruct.java +++ b/checker/tests/nullness/ScopingConstruct.java @@ -3,508 +3,508 @@ @SuppressWarnings("initialization.field.uninitialized") public class ScopingConstruct { - // TODO: add nested classes within these two? - static class StaticNested implements AutoCloseable { - public void close() {} + // TODO: add nested classes within these two? + static class StaticNested implements AutoCloseable { + public void close() {} - static class NestedNested implements AutoCloseable { - public void close() {} - } + static class NestedNested implements AutoCloseable { + public void close() {} + } - class NestedInner implements AutoCloseable { - public void close() {} - } + class NestedInner implements AutoCloseable { + public void close() {} } + } - class Inner implements AutoCloseable { - public void close() {} + class Inner implements AutoCloseable { + public void close() {} - // This is a Java error. - // static class InnerNested {} + // This is a Java error. + // static class InnerNested {} - class InnerInner implements AutoCloseable { - public void close() {} - } + class InnerInner implements AutoCloseable { + public void close() {} } + } - StaticNested sn; + StaticNested sn; - @Nullable StaticNested nsn; + @Nullable StaticNested nsn; - Inner i; + Inner i; - @Nullable Inner ni; + @Nullable Inner ni; - ScopingConstruct.StaticNested scsn; + ScopingConstruct.StaticNested scsn; - // This is a Java error. - // @Nullable ScopingConstruct.StaticNested nscsn; + // This is a Java error. + // @Nullable ScopingConstruct.StaticNested nscsn; - ScopingConstruct.@Nullable StaticNested scnsn; + ScopingConstruct.@Nullable StaticNested scnsn; - // This is a Java error. - // ScopingConstruct.@Nullable StaticNested.NestedNested scnsnnn; + // This is a Java error. + // ScopingConstruct.@Nullable StaticNested.NestedNested scnsnnn; - // This is a Java error. - // ScopingConstruct.@Nullable StaticNested.@Nullable NestedNested scnsnnnn; + // This is a Java error. + // ScopingConstruct.@Nullable StaticNested.@Nullable NestedNested scnsnnnn; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; - ScopingConstruct.Inner sci; + ScopingConstruct.Inner sci; - ScopingConstruct.Inner.InnerInner sciii; + ScopingConstruct.Inner.InnerInner sciii; - ScopingConstruct.Inner.@Nullable InnerInner scinii; + ScopingConstruct.Inner.@Nullable InnerInner scinii; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner nsci; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner nsci; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner nsciii; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner nsciii; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; - ScopingConstruct.@Nullable Inner scni; + ScopingConstruct.@Nullable Inner scni; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner scniii; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner scniii; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; - ScopingConstruct.StaticNested.NestedInner scsnni; + ScopingConstruct.StaticNested.NestedInner scsnni; - ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; + ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; - // This is a Java error. - // @Nullable ScopingConstruct.StaticNested.NestedInner nscsnni; + // This is a Java error. + // @Nullable ScopingConstruct.StaticNested.NestedInner nscsnni; - // This is a Java error. - // @Nullable ScopingConstruct.StaticNested.@Nullable NestedInner nscsnnni; + // This is a Java error. + // @Nullable ScopingConstruct.StaticNested.@Nullable NestedInner nscsnnni; - // This is a Java error. - // @Nullable ScopingConstruct.@Nullable StaticNested.NestedInner nscnsnni; + // This is a Java error. + // @Nullable ScopingConstruct.@Nullable StaticNested.NestedInner nscnsnni; - // This is a Java error. - // @Nullable ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner nscnsnnni; + // This is a Java error. + // @Nullable ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner nscnsnnni; - ScopingConstruct.Inner @Nullable [] scina; + ScopingConstruct.Inner @Nullable [] scina; - ScopingConstruct.Inner.InnerInner @Nullable [] sciiina; + ScopingConstruct.Inner.InnerInner @Nullable [] sciiina; - ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniina; + ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniina; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner @Nullable [] nscina; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner @Nullable [] nscina; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiina; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiina; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniina; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniina; - ScopingConstruct.@Nullable Inner @Nullable [] scnina; + ScopingConstruct.@Nullable Inner @Nullable [] scnina; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniina; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniina; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniina; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniina; - ScopingConstruct.Inner sci() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner sci() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.InnerInner sciii() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.InnerInner sciii() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.@Nullable InnerInner scinii() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.@Nullable InnerInner scinii() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner nsci() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner nsci() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner nsciii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner nsciii() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii() { + throw new Error("not implemented"); + } - ScopingConstruct.@Nullable Inner scni() { - throw new Error("not implemented"); - } + ScopingConstruct.@Nullable Inner scni() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner scniii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner scniii() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner @Nullable [] scin() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner @Nullable [] scin() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.InnerInner @Nullable [] sciiin() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.InnerInner @Nullable [] sciiin() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniin() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner @Nullable [] nscin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner @Nullable [] nscin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniin() { + throw new Error("not implemented"); + } - ScopingConstruct.@Nullable Inner @Nullable [] scnin() { - throw new Error("not implemented"); - } + ScopingConstruct.@Nullable Inner @Nullable [] scnin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniiin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniiin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniin() { + throw new Error("not implemented"); + } - /// - /// Formal parameters - /// + /// + /// Formal parameters + /// - void fsn(StaticNested sn) {} + void fsn(StaticNested sn) {} - void fnsn(@Nullable StaticNested nsn) {} + void fnsn(@Nullable StaticNested nsn) {} - void fi(Inner i) {} + void fi(Inner i) {} - void fni(@Nullable Inner ni) {} + void fni(@Nullable Inner ni) {} - void fscsn(ScopingConstruct.StaticNested scsn) {} + void fscsn(ScopingConstruct.StaticNested scsn) {} - void fscnsn(ScopingConstruct.@Nullable StaticNested scnsn) {} + void fscnsn(ScopingConstruct.@Nullable StaticNested scnsn) {} - // :: error: (nullness.on.outer) - void fscnsnni(ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni) {} + // :: error: (nullness.on.outer) + void fscnsnni(ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni) {} - // :: error: (nullness.on.outer) - void fscnsnnni(ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni) {} + // :: error: (nullness.on.outer) + void fscnsnnni(ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni) {} - void fsci(ScopingConstruct.Inner sci) {} + void fsci(ScopingConstruct.Inner sci) {} - void fsciii(ScopingConstruct.Inner.InnerInner sciii) {} + void fsciii(ScopingConstruct.Inner.InnerInner sciii) {} - void fscinii(ScopingConstruct.Inner.@Nullable InnerInner scinii) {} + void fscinii(ScopingConstruct.Inner.@Nullable InnerInner scinii) {} - // :: error: (nullness.on.outer) - void fnsci(@Nullable ScopingConstruct.Inner nsci) {} + // :: error: (nullness.on.outer) + void fnsci(@Nullable ScopingConstruct.Inner nsci) {} - // :: error: (nullness.on.outer) - void fnsciii(@Nullable ScopingConstruct.Inner.InnerInner nsciii) {} + // :: error: (nullness.on.outer) + void fnsciii(@Nullable ScopingConstruct.Inner.InnerInner nsciii) {} - // :: error: (nullness.on.outer) - void fnscinii(@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii) {} + // :: error: (nullness.on.outer) + void fnscinii(@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii) {} - void fscni(ScopingConstruct.@Nullable Inner scni) {} + void fscni(ScopingConstruct.@Nullable Inner scni) {} - // :: error: (nullness.on.outer) - void fscniii(ScopingConstruct.@Nullable Inner.InnerInner scniii) {} + // :: error: (nullness.on.outer) + void fscniii(ScopingConstruct.@Nullable Inner.InnerInner scniii) {} - // :: error: (nullness.on.outer) - void fscninii(ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii) {} + // :: error: (nullness.on.outer) + void fscninii(ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii) {} - void fscsnni(ScopingConstruct.StaticNested.NestedInner scsnni) {} + void fscsnni(ScopingConstruct.StaticNested.NestedInner scsnni) {} - void fscsnnni(ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni) {} + void fscsnnni(ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni) {} - /// - /// Local variables - /// + /// + /// Local variables + /// - void lvsn() { - StaticNested sn; - } + void lvsn() { + StaticNested sn; + } - void lvnsn() { - @Nullable StaticNested nsn; - } + void lvnsn() { + @Nullable StaticNested nsn; + } - void lvi() { - Inner i; - } + void lvi() { + Inner i; + } - void lvni() { - @Nullable Inner ni; - } + void lvni() { + @Nullable Inner ni; + } - void lvscsn() { - ScopingConstruct.StaticNested scsn; - } + void lvscsn() { + ScopingConstruct.StaticNested scsn; + } - void lvscnsn() { - ScopingConstruct.@Nullable StaticNested scnsn; - } + void lvscnsn() { + ScopingConstruct.@Nullable StaticNested scnsn; + } - void lvscnsnni() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; - } + void lvscnsnni() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; + } - void lvscnsnnni() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; - } + void lvscnsnnni() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; + } - void lvsci() { - ScopingConstruct.Inner sci; - } + void lvsci() { + ScopingConstruct.Inner sci; + } - void lvsciii() { - ScopingConstruct.Inner.InnerInner sciii; - } + void lvsciii() { + ScopingConstruct.Inner.InnerInner sciii; + } - void lvscinii() { - ScopingConstruct.Inner.@Nullable InnerInner scinii; - } + void lvscinii() { + ScopingConstruct.Inner.@Nullable InnerInner scinii; + } - void lvnsci() { - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner nsci; - } + void lvnsci() { + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner nsci; + } - void lvnsciii() { - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner nsciii; - } + void lvnsciii() { + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner nsciii; + } - void lvnscinii() { - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; - } + void lvnscinii() { + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; + } - void lvscni() { - ScopingConstruct.@Nullable Inner scni; - } + void lvscni() { + ScopingConstruct.@Nullable Inner scni; + } - void lvscniii() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner scniii; - } + void lvscniii() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner scniii; + } - void lvscninii() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; - } + void lvscninii() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; + } - void lvscsnni() { - ScopingConstruct.StaticNested.NestedInner scsnni; - } + void lvscsnni() { + ScopingConstruct.StaticNested.NestedInner scsnni; + } - void lvscsnnni() { - ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; - } + void lvscsnnni() { + ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; + } - /// - /// Resource variables - /// + /// + /// Resource variables + /// - void rvsn() { - try (StaticNested sn = null) {} - } + void rvsn() { + try (StaticNested sn = null) {} + } - void rvnsn() { - try (@Nullable StaticNested nsn = null) {} - } + void rvnsn() { + try (@Nullable StaticNested nsn = null) {} + } - void rvi() { - try (Inner i = null) {} - } + void rvi() { + try (Inner i = null) {} + } - void rvni() { - try (@Nullable Inner ni = null) {} - } + void rvni() { + try (@Nullable Inner ni = null) {} + } - void rvscsn() { - try (ScopingConstruct.StaticNested scsn = null) {} - } + void rvscsn() { + try (ScopingConstruct.StaticNested scsn = null) {} + } - void rvscnsn() { - try (ScopingConstruct.@Nullable StaticNested scnsn = null) {} - } + void rvscnsn() { + try (ScopingConstruct.@Nullable StaticNested scnsn = null) {} + } - void rvscnsnni() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null) {} - } + void rvscnsnni() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null) {} + } - void rvscnsnnni() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null) {} - } + void rvscnsnnni() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null) {} + } - void rvsci() { - try (ScopingConstruct.Inner sci = null) {} - } + void rvsci() { + try (ScopingConstruct.Inner sci = null) {} + } - void rvsciii() { - try (ScopingConstruct.Inner.InnerInner sciii = null) {} - } + void rvsciii() { + try (ScopingConstruct.Inner.InnerInner sciii = null) {} + } - void rvscinii() { - try (ScopingConstruct.Inner.@Nullable InnerInner scinii = null) {} - } + void rvscinii() { + try (ScopingConstruct.Inner.@Nullable InnerInner scinii = null) {} + } - void rvnsci() { - // :: error: (nullness.on.outer) - try (@Nullable ScopingConstruct.Inner nsci = null) {} - } + void rvnsci() { + // :: error: (nullness.on.outer) + try (@Nullable ScopingConstruct.Inner nsci = null) {} + } - void rvnsciii() { - // :: error: (nullness.on.outer) - try (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null) {} - } + void rvnsciii() { + // :: error: (nullness.on.outer) + try (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null) {} + } - void rvnscinii() { - // :: error: (nullness.on.outer) - try (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null) {} - } + void rvnscinii() { + // :: error: (nullness.on.outer) + try (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null) {} + } - void rvscni() { - try (ScopingConstruct.@Nullable Inner scni = null) {} - } + void rvscni() { + try (ScopingConstruct.@Nullable Inner scni = null) {} + } - void rvscniii() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable Inner.InnerInner scniii = null) {} - } + void rvscniii() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable Inner.InnerInner scniii = null) {} + } - void rvscninii() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null) {} - } + void rvscninii() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null) {} + } - void rvscsnni() { - try (ScopingConstruct.StaticNested.NestedInner scsnni = null) {} - } + void rvscsnni() { + try (ScopingConstruct.StaticNested.NestedInner scsnni = null) {} + } - void rvscsnnni() { - try (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null) {} - } + void rvscsnnni() { + try (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null) {} + } - /// - /// For variables - /// + /// + /// For variables + /// - void fvsn() { - for (StaticNested sn = null; ; ) {} - } + void fvsn() { + for (StaticNested sn = null; ; ) {} + } - void fvnsn() { - for (@Nullable StaticNested nsn = null; ; ) {} - } + void fvnsn() { + for (@Nullable StaticNested nsn = null; ; ) {} + } - void fvi() { - for (Inner i = null; ; ) {} - } + void fvi() { + for (Inner i = null; ; ) {} + } - void fvni() { - for (@Nullable Inner ni = null; ; ) {} - } + void fvni() { + for (@Nullable Inner ni = null; ; ) {} + } - void fvscsn() { - for (ScopingConstruct.StaticNested scsn = null; ; ) {} - } + void fvscsn() { + for (ScopingConstruct.StaticNested scsn = null; ; ) {} + } - void fvscnsn() { - for (ScopingConstruct.@Nullable StaticNested scnsn = null; ; ) {} - } + void fvscnsn() { + for (ScopingConstruct.@Nullable StaticNested scnsn = null; ; ) {} + } - void fvscnsnni() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null; ; ) {} - } + void fvscnsnni() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null; ; ) {} + } - void fvscnsnnni() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null; ; ) {} - } + void fvscnsnnni() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null; ; ) {} + } - void fvsci() { - for (ScopingConstruct.Inner sci = null; ; ) {} - } + void fvsci() { + for (ScopingConstruct.Inner sci = null; ; ) {} + } - void fvsciii() { - for (ScopingConstruct.Inner.InnerInner sciii = null; ; ) {} - } + void fvsciii() { + for (ScopingConstruct.Inner.InnerInner sciii = null; ; ) {} + } - void fvscinii() { - for (ScopingConstruct.Inner.@Nullable InnerInner scinii = null; ; ) {} - } + void fvscinii() { + for (ScopingConstruct.Inner.@Nullable InnerInner scinii = null; ; ) {} + } - void fvnsci() { - // :: error: (nullness.on.outer) - for (@Nullable ScopingConstruct.Inner nsci = null; ; ) {} - } + void fvnsci() { + // :: error: (nullness.on.outer) + for (@Nullable ScopingConstruct.Inner nsci = null; ; ) {} + } - void fvnsciii() { - // :: error: (nullness.on.outer) - for (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null; ; ) {} - } + void fvnsciii() { + // :: error: (nullness.on.outer) + for (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null; ; ) {} + } - void fvnscinii() { - // :: error: (nullness.on.outer) - for (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null; ; ) {} - } + void fvnscinii() { + // :: error: (nullness.on.outer) + for (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null; ; ) {} + } - void fvscni() { - for (ScopingConstruct.@Nullable Inner scni = null; ; ) {} - } + void fvscni() { + for (ScopingConstruct.@Nullable Inner scni = null; ; ) {} + } - void fvscniii() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable Inner.InnerInner scniii = null; ; ) {} - } + void fvscniii() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable Inner.InnerInner scniii = null; ; ) {} + } - void fvscninii() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null; ; ) {} - } + void fvscninii() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null; ; ) {} + } - void fvscsnni() { - for (ScopingConstruct.StaticNested.NestedInner scsnni = null; ; ) {} - } + void fvscsnni() { + for (ScopingConstruct.StaticNested.NestedInner scsnni = null; ; ) {} + } - void fvscsnnni() { - for (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null; ; ) {} - } + void fvscsnnni() { + for (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null; ; ) {} + } } diff --git a/checker/tests/nullness/SelfAssignment.java b/checker/tests/nullness/SelfAssignment.java index 3fb4a63cee9..6f85b0d4ea3 100644 --- a/checker/tests/nullness/SelfAssignment.java +++ b/checker/tests/nullness/SelfAssignment.java @@ -5,15 +5,15 @@ public class SelfAssignment { - void test(@Nullable String s) { - assertNonNull(s); - s = s.trim(); - } + void test(@Nullable String s) { + assertNonNull(s); + s = s.trim(); + } - @EnsuresNonNull("#1") - void assertNonNull(final @Nullable Object o) { - if (o == null) { - throw new AssertionError(); - } + @EnsuresNonNull("#1") + void assertNonNull(final @Nullable Object o) { + if (o == null) { + throw new AssertionError(); } + } } diff --git a/checker/tests/nullness/SelfDependentType.java b/checker/tests/nullness/SelfDependentType.java index 155f8272719..a3eb3059ea1 100644 --- a/checker/tests/nullness/SelfDependentType.java +++ b/checker/tests/nullness/SelfDependentType.java @@ -2,152 +2,151 @@ // @skip-test until the issue is fixed -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class SelfDependentType { - public void copy1( - HashMap> a, - HashMap> b) { - a = b; - } + public void copy1( + HashMap> a, + HashMap> b) { + a = b; + } - public void copy2() { - HashMap> a = null; - HashMap> b = null; - a = b; - } + public void copy2() { + HashMap> a = null; + HashMap> b = null; + a = b; + } - class SdtGraph1 { + class SdtGraph1 { - HashMap> childMap; + HashMap> childMap; - // :: error: (expression.parameter.name) - public SdtGraph1(HashMap> childMap) { - this.childMap = childMap; - } + // :: error: (expression.parameter.name) + public SdtGraph1(HashMap> childMap) { + this.childMap = childMap; } + } - class SdtGraph2 { + class SdtGraph2 { - HashMap> childMap; + HashMap> childMap; - // :: error: (expression.parameter.name) - public SdtGraph2(HashMap> childMap) { - this.childMap = childMap; - } + // :: error: (expression.parameter.name) + public SdtGraph2(HashMap> childMap) { + this.childMap = childMap; } + } - class SdtGraph3 { + class SdtGraph3 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph3(HashMap> childMap) { - this.childMap = childMap; - } + public SdtGraph3(HashMap> childMap) { + this.childMap = childMap; } + } - class SdtGraph4 { + class SdtGraph4 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph4(HashMap> childMap) { - this.childMap = childMap; - } + public SdtGraph4(HashMap> childMap) { + this.childMap = childMap; } + } - class SdtGraph5 { + class SdtGraph5 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph5(HashMap> childMap) { - this.childMap = childMap; - } + public SdtGraph5(HashMap> childMap) { + this.childMap = childMap; } + } - class SdtGraph6 { + class SdtGraph6 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph6(HashMap> childMap) { - this.childMap = childMap; - } + public SdtGraph6(HashMap> childMap) { + this.childMap = childMap; } + } - class SdtGraph11 { + class SdtGraph11 { - HashMap> childMapField; + HashMap> childMapField; - // :: error: (expression.parameter.name) - public SdtGraph11(HashMap> childMap) { - this.childMapField = childMap; - } + // :: error: (expression.parameter.name) + public SdtGraph11(HashMap> childMap) { + this.childMapField = childMap; } + } - class SdtGraph12 { + class SdtGraph12 { - HashMap> childMapField; + HashMap> childMapField; - // :: error: (expression.parameter.name) - public SdtGraph12(HashMap> childMap) { - this.childMapField = childMap; - } + // :: error: (expression.parameter.name) + public SdtGraph12(HashMap> childMap) { + this.childMapField = childMap; } + } - class SdtGraph13 { + class SdtGraph13 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph13(HashMap> childMap) { - this.childMapField = childMap; - } + public SdtGraph13(HashMap> childMap) { + this.childMapField = childMap; } + } - class SdtGraph14 { + class SdtGraph14 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph14(HashMap> childMap) { - this.childMapField = childMap; - } + public SdtGraph14(HashMap> childMap) { + this.childMapField = childMap; } + } - class SdtGraph15 { + class SdtGraph15 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph15(HashMap> childMap) { - this.childMapField = childMap; - } + public SdtGraph15(HashMap> childMap) { + this.childMapField = childMap; } + } - class SdtGraph16 { + class SdtGraph16 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph16(HashMap> childMap) { - this.childMapField = childMap; - } + public SdtGraph16(HashMap> childMap) { + this.childMapField = childMap; } + } - class SdtGraph17 { + class SdtGraph17 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph17(HashMap> childMap) { - this.childMapField = childMap; - } + public SdtGraph17(HashMap> childMap) { + this.childMapField = childMap; } + } - class SdtGraph18 { + class SdtGraph18 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph18(HashMap> childMap) { - this.childMapField = childMap; - } + public SdtGraph18(HashMap> childMap) { + this.childMapField = childMap; } + } } diff --git a/checker/tests/nullness/SequenceAndIndices.java b/checker/tests/nullness/SequenceAndIndices.java index 0d319bd9af2..f94a0a98255 100644 --- a/checker/tests/nullness/SequenceAndIndices.java +++ b/checker/tests/nullness/SequenceAndIndices.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.interning.qual.*; public final class SequenceAndIndices { - public T seq; + public T seq; - public SequenceAndIndices(T seq) { - this.seq = seq; - } + public SequenceAndIndices(T seq) { + this.seq = seq; + } } diff --git a/checker/tests/nullness/SetIteratorTest.java b/checker/tests/nullness/SetIteratorTest.java index 8f73508d1ad..9c60dbbba1d 100644 --- a/checker/tests/nullness/SetIteratorTest.java +++ b/checker/tests/nullness/SetIteratorTest.java @@ -9,45 +9,45 @@ public class SetIteratorTest { - private SortedSet nodes; - private Map>> edges; - - public SetIteratorTest() { - nodes = new TreeSet(); - edges = new HashMap>>(); - } - - public Set listNodes() { - return Collections.unmodifiableSet(nodes); - } - - public String listChildren(String parentNode) { - String childrenString = ""; - - if (edges.get(parentNode) != null) { - for (String childNode : edges.get(parentNode).keySet()) { - // :: error: (dereference.of.nullable) - edges.get(parentNode).toString(); - for (String childNodeEdgeX : edges.get(parentNode).get(childNode)) { - childrenString += " " + childNode + "(" + childNodeEdgeX + ")"; - } - } + private SortedSet nodes; + private Map>> edges; + + public SetIteratorTest() { + nodes = new TreeSet(); + edges = new HashMap>>(); + } + + public Set listNodes() { + return Collections.unmodifiableSet(nodes); + } + + public String listChildren(String parentNode) { + String childrenString = ""; + + if (edges.get(parentNode) != null) { + for (String childNode : edges.get(parentNode).keySet()) { + // :: error: (dereference.of.nullable) + edges.get(parentNode).toString(); + for (String childNodeEdgeX : edges.get(parentNode).get(childNode)) { + childrenString += " " + childNode + "(" + childNodeEdgeX + ")"; } - - return childrenString; + } } - public void listChildren2(String parentNode) { - if (edges.get(parentNode) != null) { - Iterator itor = edges.get(parentNode).keySet().iterator(); - edges.get(parentNode).toString(); - String s = itor.next(); - // :: error: (dereference.of.nullable) - edges.get(parentNode).toString(); - } - } + return childrenString; + } - public boolean containsNode(String node) { - return nodes.contains(node); + public void listChildren2(String parentNode) { + if (edges.get(parentNode) != null) { + Iterator itor = edges.get(parentNode).keySet().iterator(); + edges.get(parentNode).toString(); + String s = itor.next(); + // :: error: (dereference.of.nullable) + edges.get(parentNode).toString(); } + } + + public boolean containsNode(String node) { + return nodes.contains(node); + } } diff --git a/checker/tests/nullness/SortingCollection.java b/checker/tests/nullness/SortingCollection.java index dc68e8fb178..8b3adef60c0 100644 --- a/checker/tests/nullness/SortingCollection.java +++ b/checker/tests/nullness/SortingCollection.java @@ -11,13 +11,13 @@ public class SortingCollection { - class MergingIterator { - private final PollableTreeSet queue = null; + class MergingIterator { + private final PollableTreeSet queue = null; - public boolean hasNext() { - return !queue.isEmpty(); - } + public boolean hasNext() { + return !queue.isEmpty(); } + } - static class PollableTreeSet extends TreeSet {} + static class PollableTreeSet extends TreeSet {} } diff --git a/checker/tests/nullness/StaticInLoop.java b/checker/tests/nullness/StaticInLoop.java index faa2ca3ac25..736bc146dc9 100644 --- a/checker/tests/nullness/StaticInLoop.java +++ b/checker/tests/nullness/StaticInLoop.java @@ -3,13 +3,13 @@ public final class StaticInLoop { - public static @MonotonicNonNull String data_trace_state = null; + public static @MonotonicNonNull String data_trace_state = null; - @RequiresNonNull("StaticInLoop.data_trace_state") - private static void read_vals_and_mods_from_trace_file(Object[] vals, int[] mods) { - for (; ; ) { - data_trace_state.toString(); - vals[0] = "hello"; - } + @RequiresNonNull("StaticInLoop.data_trace_state") + private static void read_vals_and_mods_from_trace_file(Object[] vals, int[] mods) { + for (; ; ) { + data_trace_state.toString(); + vals[0] = "hello"; } + } } diff --git a/checker/tests/nullness/StaticInitializer2.java b/checker/tests/nullness/StaticInitializer2.java index dfcf577ba6c..5e100734f71 100644 --- a/checker/tests/nullness/StaticInitializer2.java +++ b/checker/tests/nullness/StaticInitializer2.java @@ -10,10 +10,10 @@ public class StaticInitializer2 { - static String a; + static String a; - static { - // :: error: (dereference.of.nullable) - a.toString(); - } + static { + // :: error: (dereference.of.nullable) + a.toString(); + } } diff --git a/checker/tests/nullness/Stats.java b/checker/tests/nullness/Stats.java index 63cf1782e71..58a550f7da3 100644 --- a/checker/tests/nullness/Stats.java +++ b/checker/tests/nullness/Stats.java @@ -1,19 +1,18 @@ // @skip-tests Failing, but commented out to avoid breaking the build -import org.checkerframework.checker.nullness.qual.*; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class Stats { - @Nullable Map inv_map = null; + @Nullable Map inv_map = null; - void dump() { + void dump() { - assert inv_map != null : "@AssumeAssertion(nullness)"; + assert inv_map != null : "@AssumeAssertion(nullness)"; - for (Integer inv_class : inv_map.keySet()) { - inv_map.get(inv_class); - } + for (Integer inv_class : inv_map.keySet()) { + inv_map.get(inv_class); } + } } diff --git a/checker/tests/nullness/StringTernaryConcat.java b/checker/tests/nullness/StringTernaryConcat.java index d0bb6952e41..b72e8e6e731 100644 --- a/checker/tests/nullness/StringTernaryConcat.java +++ b/checker/tests/nullness/StringTernaryConcat.java @@ -1,6 +1,6 @@ public class StringTernaryConcat { - public String s(Integer start) { - return start + (start.equals(start) ? "" : "-"); - } + public String s(Integer start) { + return start + (start.equals(start) ? "" : "-"); + } } diff --git a/checker/tests/nullness/SuperCall.java b/checker/tests/nullness/SuperCall.java index d38af0f6591..7ed9954d26d 100644 --- a/checker/tests/nullness/SuperCall.java +++ b/checker/tests/nullness/SuperCall.java @@ -2,14 +2,14 @@ public class SuperCall { - public static class A { - public A(@NonNull Object arg) {} - } + public static class A { + public A(@NonNull Object arg) {} + } - public static class B extends A { - public B(@Nullable Object arg) { - // :: error: (argument.type.incompatible) - super(arg); - } + public static class B extends A { + public B(@Nullable Object arg) { + // :: error: (argument.type.incompatible) + super(arg); } + } } diff --git a/checker/tests/nullness/SuppressDeprecation.java b/checker/tests/nullness/SuppressDeprecation.java index 97b88c300b0..453155df1d0 100644 --- a/checker/tests/nullness/SuppressDeprecation.java +++ b/checker/tests/nullness/SuppressDeprecation.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.nullness.qual.*; class SuppressDeprecationOther { - @Deprecated - void old() {} + @Deprecated + void old() {} } public class SuppressDeprecation { - @MonotonicNonNull String tz1; + @MonotonicNonNull String tz1; - @SuppressWarnings("deprecation") - void processOptions(String tz, SuppressDeprecationOther o) { - tz1 = tz; + @SuppressWarnings("deprecation") + void processOptions(String tz, SuppressDeprecationOther o) { + tz1 = tz; - // There should be no deprecation warning here. - o.old(); + // There should be no deprecation warning here. + o.old(); - parseTime("hello"); - } + parseTime("hello"); + } - @RequiresNonNull("tz1") - void parseTime(String time) {} + @RequiresNonNull("tz1") + void parseTime(String time) {} } diff --git a/checker/tests/nullness/SuppressWarningsPartialKeys.java b/checker/tests/nullness/SuppressWarningsPartialKeys.java index b06fa284045..2a49b5cac34 100644 --- a/checker/tests/nullness/SuppressWarningsPartialKeys.java +++ b/checker/tests/nullness/SuppressWarningsPartialKeys.java @@ -2,115 +2,115 @@ public class SuppressWarningsPartialKeys { - @SuppressWarnings("return.type.incompatible") - @NonNull Object suppressed2() { - return null; - } - - @SuppressWarnings("return.type") - @NonNull Object suppressed3() { - return null; - } - - @SuppressWarnings("type.incompatible") - @NonNull Object suppressed4() { - return null; - } - - @SuppressWarnings("type") - @NonNull Object suppressed5() { - return null; - } - - @SuppressWarnings("nullness:return.type.incompatible") - @NonNull Object suppressedn2() { - return null; - } - - @SuppressWarnings("nullness:return.type") - @NonNull Object suppressedn3() { - return null; - } - - @SuppressWarnings("nullness:type.incompatible") - @NonNull Object suppressedn4() { - return null; - } - - @SuppressWarnings("nullness:type") - @NonNull Object suppressedn5() { - return null; - } - - @SuppressWarnings("i") - @NonNull Object err1() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("eturn.type") - @NonNull Object err2() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("typ") - @NonNull Object err3() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("ype.incompatible") - @NonNull Object err4() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("return.type.") - @NonNull Object err5() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings(".type.incompatible") - @NonNull Object err6() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:i") - @NonNull Object errn1() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:eturn.type") - @NonNull Object errn2() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:typ") - @NonNull Object errn3() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:ype.incompatible") - @NonNull Object errn4() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:return.type.") - @NonNull Object errn5() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:.type.incompatible") - @NonNull Object errn6() { - // :: error: (return.type.incompatible) - return null; - } + @SuppressWarnings("return.type.incompatible") + @NonNull Object suppressed2() { + return null; + } + + @SuppressWarnings("return.type") + @NonNull Object suppressed3() { + return null; + } + + @SuppressWarnings("type.incompatible") + @NonNull Object suppressed4() { + return null; + } + + @SuppressWarnings("type") + @NonNull Object suppressed5() { + return null; + } + + @SuppressWarnings("nullness:return.type.incompatible") + @NonNull Object suppressedn2() { + return null; + } + + @SuppressWarnings("nullness:return.type") + @NonNull Object suppressedn3() { + return null; + } + + @SuppressWarnings("nullness:type.incompatible") + @NonNull Object suppressedn4() { + return null; + } + + @SuppressWarnings("nullness:type") + @NonNull Object suppressedn5() { + return null; + } + + @SuppressWarnings("i") + @NonNull Object err1() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("eturn.type") + @NonNull Object err2() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("typ") + @NonNull Object err3() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("ype.incompatible") + @NonNull Object err4() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("return.type.") + @NonNull Object err5() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings(".type.incompatible") + @NonNull Object err6() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:i") + @NonNull Object errn1() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:eturn.type") + @NonNull Object errn2() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:typ") + @NonNull Object errn3() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:ype.incompatible") + @NonNull Object errn4() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:return.type.") + @NonNull Object errn5() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:.type.incompatible") + @NonNull Object errn6() { + // :: error: (return.type.incompatible) + return null; + } } diff --git a/checker/tests/nullness/SuppressWarningsTest.java b/checker/tests/nullness/SuppressWarningsTest.java index 8a64666a133..73e09dc6c0c 100644 --- a/checker/tests/nullness/SuppressWarningsTest.java +++ b/checker/tests/nullness/SuppressWarningsTest.java @@ -2,9 +2,9 @@ public class SuppressWarningsTest { - @SuppressWarnings("all") - void test() { - String a = null; - a.toString(); - } + @SuppressWarnings("all") + void test() { + String a = null; + a.toString(); + } } diff --git a/checker/tests/nullness/SwitchTest.java b/checker/tests/nullness/SwitchTest.java index f54088485fd..b972fe2c9c0 100644 --- a/checker/tests/nullness/SwitchTest.java +++ b/checker/tests/nullness/SwitchTest.java @@ -1,36 +1,36 @@ import org.checkerframework.checker.nullness.qual.*; public class SwitchTest { - public static void main(String[] args) { - // :: error: (switching.nullable) - switch (getNbl()) { - case X: - System.out.println("X"); - break; - default: - System.out.println("default"); - } + public static void main(String[] args) { + // :: error: (switching.nullable) + switch (getNbl()) { + case X: + System.out.println("X"); + break; + default: + System.out.println("default"); } + } - public static void goodUse() { - switch (getNN()) { - case X: - System.out.println("X"); - break; - default: - System.out.println("default"); - } + public static void goodUse() { + switch (getNN()) { + case X: + System.out.println("X"); + break; + default: + System.out.println("default"); } + } - public static @Nullable A getNbl() { - return null; - } + public static @Nullable A getNbl() { + return null; + } - public static A getNN() { - return A.X; - } + public static A getNN() { + return A.X; + } - public static enum A { - X - } + public static enum A { + X + } } diff --git a/checker/tests/nullness/Synchronization.java b/checker/tests/nullness/Synchronization.java index 9205578f37f..5a4b07314cb 100644 --- a/checker/tests/nullness/Synchronization.java +++ b/checker/tests/nullness/Synchronization.java @@ -2,34 +2,34 @@ public class Synchronization { - // Plain - public void bad() { - Object o = null; - // :: error: (locking.nullable) - synchronized (o) { - } // should emit error - } + // Plain + public void bad() { + Object o = null; + // :: error: (locking.nullable) + synchronized (o) { + } // should emit error + } - public void ok() { - // NonNull specifically - @NonNull Object o1 = "m"; - synchronized (o1) { - } + public void ok() { + // NonNull specifically + @NonNull Object o1 = "m"; + synchronized (o1) { } + } - public void flow() { - Object o = null; - o = "m"; - synchronized (o) { - } // valid - o = null; - // :: error: (locking.nullable) - synchronized (o) { - } // invalid - } + public void flow() { + Object o = null; + o = "m"; + synchronized (o) { + } // valid + o = null; + // :: error: (locking.nullable) + synchronized (o) { + } // invalid + } - public Synchronization() { - synchronized (this) { - } + public Synchronization() { + synchronized (this) { } + } } diff --git a/checker/tests/nullness/TernaryNested.java b/checker/tests/nullness/TernaryNested.java index 84926f7226a..293e327d99c 100644 --- a/checker/tests/nullness/TernaryNested.java +++ b/checker/tests/nullness/TernaryNested.java @@ -1,19 +1,18 @@ // Test case for Issue 331: // https://github.com/typetools/checker-framework/issues/331 +import java.util.List; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; -import java.util.List; - public class TernaryNested { - Object foo(boolean b) { - Object o = b ? "" : (b ? "" : ""); - return o; - } + Object foo(boolean b) { + Object o = b ? "" : (b ? "" : ""); + return o; + } - void bar(List l, boolean b) { - Object o = b ? "" : (b ? "" : ""); - l.add(o); - } + void bar(List l, boolean b) { + Object o = b ? "" : (b ? "" : ""); + l.add(o); + } } diff --git a/checker/tests/nullness/TernaryNullness.java b/checker/tests/nullness/TernaryNullness.java index a094ec04d22..43806b630f9 100644 --- a/checker/tests/nullness/TernaryNullness.java +++ b/checker/tests/nullness/TernaryNullness.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.nullness.qual.*; abstract class TernaryNullness { - void f(@Nullable Object o) { - g(42, o != null ? o.hashCode() : 0); - } + void f(@Nullable Object o) { + g(42, o != null ? o.hashCode() : 0); + } - abstract void g(Object x, Object xs); + abstract void g(Object x, Object xs); } diff --git a/checker/tests/nullness/TestAssignment.java b/checker/tests/nullness/TestAssignment.java index 463bb4e877a..e687de23f6b 100644 --- a/checker/tests/nullness/TestAssignment.java +++ b/checker/tests/nullness/TestAssignment.java @@ -5,14 +5,14 @@ public class TestAssignment { - void a() { - @NonNull String f = "abc"; + void a() { + @NonNull String f = "abc"; - // :: error: (assignment.type.incompatible) - f = null; - } + // :: error: (assignment.type.incompatible) + f = null; + } - void b() { - @UnknownInitialization @NonNull TestAssignment f = new TestAssignment(); - } + void b() { + @UnknownInitialization @NonNull TestAssignment f = new TestAssignment(); + } } diff --git a/checker/tests/nullness/TestFromPullRequest880.java b/checker/tests/nullness/TestFromPullRequest880.java index 3f8624f00ef..7d3104cf1ca 100644 --- a/checker/tests/nullness/TestFromPullRequest880.java +++ b/checker/tests/nullness/TestFromPullRequest880.java @@ -5,23 +5,22 @@ // Also note a test that uses multiple compilation units at: // checker/jtreg/nullness/annotationsOnExtends/ -import org.checkerframework.checker.nullness.qual.NonNull; - import java.io.Serializable; import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; class TFPR880Test implements Serializable {} class TFPR880Use { - void foo() { - TFPR880Test other = null; - } + void foo() { + TFPR880Test other = null; + } } abstract class TFPR880TestSub extends TFPR880Test implements List<@NonNull String> {} class TFPR880SubUse { - void foo() { - TFPR880TestSub other = null; - } + void foo() { + TFPR880TestSub other = null; + } } diff --git a/checker/tests/nullness/TestInfer.java b/checker/tests/nullness/TestInfer.java index a16b8fed2d5..c20857d09b2 100644 --- a/checker/tests/nullness/TestInfer.java +++ b/checker/tests/nullness/TestInfer.java @@ -1,22 +1,21 @@ // Test case for issue #238: https://github.com/typetools/checker-framework/issues/238 -import org.checkerframework.checker.nullness.qual.UnknownKeyFor; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.UnknownKeyFor; public class TestInfer { - T getValue(List l) { - return l.get(0); - } + T getValue(List l) { + return l.get(0); + } - void bar(Object o) {} + void bar(Object o) {} - void foo() { - List<@UnknownKeyFor ? extends Object> ls = new ArrayList<>(); - bar(getValue(ls)); // this fails, but just getValue(ls) is OK - // casting is also OK, ie bar((Object)getValue(ls)) - // The constraint should be T<:Object, which should typecheck since ls:List unifies with List where T<:Object. - } + void foo() { + List<@UnknownKeyFor ? extends Object> ls = new ArrayList<>(); + bar(getValue(ls)); // this fails, but just getValue(ls) is OK + // casting is also OK, ie bar((Object)getValue(ls)) + // The constraint should be T<:Object, which should typecheck since ls:List unifies with List where T<:Object. + } } diff --git a/checker/tests/nullness/TestPolyNull.java b/checker/tests/nullness/TestPolyNull.java index 452205b0263..f730bcecb5d 100644 --- a/checker/tests/nullness/TestPolyNull.java +++ b/checker/tests/nullness/TestPolyNull.java @@ -1,50 +1,50 @@ import org.checkerframework.checker.nullness.qual.*; public class TestPolyNull { - @PolyNull String identity(@PolyNull String str) { - return str; - } + @PolyNull String identity(@PolyNull String str) { + return str; + } - void test1() { - identity(null); - } + void test1() { + identity(null); + } - void test2() { - identity((@Nullable String) null); - } + void test2() { + identity((@Nullable String) null); + } - public static @PolyNull String[] typeArray(@PolyNull Object[] seq, @Nullable String nullable) { - @SuppressWarnings("nullness") // ignore array initialization here. - @PolyNull String[] retval = new @Nullable String[seq.length]; - for (int i = 0; i < seq.length; i++) { - if (seq[i] == null) { - // null can be assigned into the PolyNull array, because we - // performed a test on seq and know that it is nullable. - retval[i] = null; - // and so can something that is nullable - retval[i] = nullable; - // One can always add a dummy value: nonnull is the bottom - // type and legal for any instantiation of PolyNull. - retval[i] = "dummy"; - } else { - retval[i] = seq[i].getClass().toString(); - // :: error: (assignment.type.incompatible) - retval[i] = null; - // :: error: (assignment.type.incompatible) - retval[i] = nullable; - } - } - return retval; + public static @PolyNull String[] typeArray(@PolyNull Object[] seq, @Nullable String nullable) { + @SuppressWarnings("nullness") // ignore array initialization here. + @PolyNull String[] retval = new @Nullable String[seq.length]; + for (int i = 0; i < seq.length; i++) { + if (seq[i] == null) { + // null can be assigned into the PolyNull array, because we + // performed a test on seq and know that it is nullable. + retval[i] = null; + // and so can something that is nullable + retval[i] = nullable; + // One can always add a dummy value: nonnull is the bottom + // type and legal for any instantiation of PolyNull. + retval[i] = "dummy"; + } else { + retval[i] = seq[i].getClass().toString(); + // :: error: (assignment.type.incompatible) + retval[i] = null; + // :: error: (assignment.type.incompatible) + retval[i] = nullable; + } } + return retval; + } - public static @PolyNull String identity2(@PolyNull String a) { - return (a == null) ? null : a; - } + public static @PolyNull String identity2(@PolyNull String a) { + return (a == null) ? null : a; + } - public static @PolyNull String identity3(@PolyNull String a) { - if (a == null) { - return null; - } - return a; + public static @PolyNull String identity3(@PolyNull String a) { + if (a == null) { + return null; } + return a; + } } diff --git a/checker/tests/nullness/TestValOf.java b/checker/tests/nullness/TestValOf.java index 31952742d68..32cf9af0f58 100644 --- a/checker/tests/nullness/TestValOf.java +++ b/checker/tests/nullness/TestValOf.java @@ -2,13 +2,13 @@ public class TestValOf> { - private final Class enumClass; + private final Class enumClass; - private TestValOf(Class enumClass) { - this.enumClass = enumClass; - } + private TestValOf(Class enumClass) { + this.enumClass = enumClass; + } - T foo(String value) { - return Enum.valueOf(enumClass, value); - } + T foo(String value) { + return Enum.valueOf(enumClass, value); + } } diff --git a/checker/tests/nullness/ThisIsNN.java b/checker/tests/nullness/ThisIsNN.java index 139e94ad590..d9d904155a7 100644 --- a/checker/tests/nullness/ThisIsNN.java +++ b/checker/tests/nullness/ThisIsNN.java @@ -1,29 +1,29 @@ import org.checkerframework.checker.nullness.qual.*; public class ThisIsNN { - Object out = new Object(); + Object out = new Object(); - class Inner { - void test1() { - out = this; - out = ThisIsNN.this; - } - - Object in = new Object(); + class Inner { + void test1() { + out = this; + out = ThisIsNN.this; + } - void test2(Inner this) { - Object nonRawThis = this; - out = nonRawThis; - } + Object in = new Object(); - void test3(Inner this) { - Object nonRawThis = ThisIsNN.this; - out = nonRawThis; - } + void test2(Inner this) { + Object nonRawThis = this; + out = nonRawThis; } - void test4(ThisIsNN this) { - Object nonRawThis = this; - out = nonRawThis; + void test3(Inner this) { + Object nonRawThis = ThisIsNN.this; + out = nonRawThis; } + } + + void test4(ThisIsNN this) { + Object nonRawThis = this; + out = nonRawThis; + } } diff --git a/checker/tests/nullness/ThisQualified.java b/checker/tests/nullness/ThisQualified.java index 87c8f07efd6..fb182d9f634 100644 --- a/checker/tests/nullness/ThisQualified.java +++ b/checker/tests/nullness/ThisQualified.java @@ -4,9 +4,9 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class ThisQualified { - public ThisQualified() { - super(); - @UnderInitialization ThisQualified a = this; - @UnderInitialization ThisQualified b = ThisQualified.this; - } + public ThisQualified() { + super(); + @UnderInitialization ThisQualified a = this; + @UnderInitialization ThisQualified b = ThisQualified.this; + } } diff --git a/checker/tests/nullness/ThisTest.java b/checker/tests/nullness/ThisTest.java index 3c33ff02678..8961a8a7bbc 100644 --- a/checker/tests/nullness/ThisTest.java +++ b/checker/tests/nullness/ThisTest.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.nullness.qual.*; @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.NonNull.class) + org.checkerframework.checker.nullness.qual.NonNull.class) public class ThisTest { - public String field; + public String field; - public ThisTest(String field) { - this.field = field; - } + public ThisTest(String field) { + this.field = field; + } - void doNothing() {} + void doNothing() {} - class InnerClass { - public void accessOuterThis() { - ThisTest.this.doNothing(); - String s = ThisTest.this.field; - } + class InnerClass { + public void accessOuterThis() { + ThisTest.this.doNothing(); + String s = ThisTest.this.field; } + } } diff --git a/checker/tests/nullness/ThreadLocalTest.java b/checker/tests/nullness/ThreadLocalTest.java index e0042449a6e..c86b35f86c3 100644 --- a/checker/tests/nullness/ThreadLocalTest.java +++ b/checker/tests/nullness/ThreadLocalTest.java @@ -2,22 +2,22 @@ public class ThreadLocalTest { - // implementation MUST override initialValue(), or SuppressWarnings is unsound - @SuppressWarnings("nullness:type.argument.type.incompatible") - class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { - @Override - protected Integer initialValue() { - return Integer.valueOf(0); - } + // implementation MUST override initialValue(), or SuppressWarnings is unsound + @SuppressWarnings("nullness:type.argument.type.incompatible") + class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { + @Override + protected Integer initialValue() { + return Integer.valueOf(0); } + } - void foo() { - // :: error: (type.argument.type.incompatible) - new ThreadLocal<@NonNull Object>(); - // :: error: (type.argument.type.incompatible) - new InheritableThreadLocal<@NonNull Object>(); - new ThreadLocal<@Nullable Object>(); - new InheritableThreadLocal<@Nullable Object>(); - new MyThreadLocalNN(); - } + void foo() { + // :: error: (type.argument.type.incompatible) + new ThreadLocal<@NonNull Object>(); + // :: error: (type.argument.type.incompatible) + new InheritableThreadLocal<@NonNull Object>(); + new ThreadLocal<@Nullable Object>(); + new InheritableThreadLocal<@Nullable Object>(); + new MyThreadLocalNN(); + } } diff --git a/checker/tests/nullness/ThreadLocalTest2.java b/checker/tests/nullness/ThreadLocalTest2.java index 53f205b369b..9dd03f79d46 100644 --- a/checker/tests/nullness/ThreadLocalTest2.java +++ b/checker/tests/nullness/ThreadLocalTest2.java @@ -8,64 +8,64 @@ public class ThreadLocalTest2 { - private static int unwrap(final ThreadLocal tl) { - return tl.get().intValue(); - } - - private static void wrap(final ThreadLocal tl, final int value) { - tl.set(Integer.valueOf(value)); - } + private static int unwrap(final ThreadLocal tl) { + return tl.get().intValue(); + } - private static ThreadLocal consumed_chars = - new ThreadLocal() { + private static void wrap(final ThreadLocal tl, final int value) { + tl.set(Integer.valueOf(value)); + } - @Override - protected Integer initialValue() { - return Integer.valueOf(0); - } - }; + private static ThreadLocal consumed_chars = + new ThreadLocal() { - class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { @Override protected Integer initialValue() { - return Integer.valueOf(0); + return Integer.valueOf(0); } + }; + + class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { + @Override + protected Integer initialValue() { + return Integer.valueOf(0); } + } - class MyThreadLocalNnIncorrectOverride extends ThreadLocal<@NonNull Integer> { - @Override - // :: error: (override.return.invalid) - protected @Nullable Integer initialValue() { - return null; - } + class MyThreadLocalNnIncorrectOverride extends ThreadLocal<@NonNull Integer> { + @Override + // :: error: (override.return.invalid) + protected @Nullable Integer initialValue() { + return null; } + } - // :: error: (method.not.overridden) - class MyThreadLocalNnNoOverride extends ThreadLocal<@NonNull Integer> {} + // :: error: (method.not.overridden) + class MyThreadLocalNnNoOverride extends ThreadLocal<@NonNull Integer> {} - class MyThreadLocalNble extends ThreadLocal<@Nullable Integer> { - @Override - protected @Nullable Integer initialValue() { - return null; - } + class MyThreadLocalNble extends ThreadLocal<@Nullable Integer> { + @Override + protected @Nullable Integer initialValue() { + return null; } + } - class MyThreadLocalNbleStrongerOverride extends ThreadLocal<@Nullable Integer> { - @Override - protected @NonNull Integer initialValue() { - return Integer.valueOf(0); - } + class MyThreadLocalNbleStrongerOverride extends ThreadLocal<@Nullable Integer> { + @Override + protected @NonNull Integer initialValue() { + return Integer.valueOf(0); } + } - class MyThreadLocalNbleNoOverride extends ThreadLocal<@Nullable Integer> {} + class MyThreadLocalNbleNoOverride extends ThreadLocal<@Nullable Integer> {} - void foo() { - // :: error: (type.argument.type.incompatible) - new ThreadLocal<@NonNull Object>(); - // :: error: (type.argument.type.incompatible) - new InheritableThreadLocal<@NonNull Object>(); - new ThreadLocal<@Nullable Object>(); - new InheritableThreadLocal<@Nullable Object>(); - new MyThreadLocalNN(); - } + void foo() { + // :: error: (type.argument.type.incompatible) + new ThreadLocal<@NonNull Object>(); + // :: error: (type.argument.type.incompatible) + new InheritableThreadLocal<@NonNull Object>(); + new ThreadLocal<@Nullable Object>(); + new InheritableThreadLocal<@Nullable Object>(); + new MyThreadLocalNN(); + } } diff --git a/checker/tests/nullness/ToArrayDiagnostics.java b/checker/tests/nullness/ToArrayDiagnostics.java index 174e51ee0e8..13d047b86a3 100644 --- a/checker/tests/nullness/ToArrayDiagnostics.java +++ b/checker/tests/nullness/ToArrayDiagnostics.java @@ -2,29 +2,29 @@ public class ToArrayDiagnostics { - String[] ok2(ArrayList list) { - return list.toArray(new String[] {}); - } + String[] ok2(ArrayList list) { + return list.toArray(new String[] {}); + } - String[] ok3(ArrayList list) { - return list.toArray(new String[0]); - } + String[] ok3(ArrayList list) { + return list.toArray(new String[0]); + } - String[] ok4(ArrayList list) { - return list.toArray(new String[list.size()]); - } + String[] ok4(ArrayList list) { + return list.toArray(new String[list.size()]); + } - String[] warn1(ArrayList list) { - // :: error: (new.array.type.invalid) - String[] resultArray = new String[list.size()]; - // :: error: (return.type.incompatible) :: warning: (toarray.nullable.elements.not.newarray) - return list.toArray(resultArray); - } + String[] warn1(ArrayList list) { + // :: error: (new.array.type.invalid) + String[] resultArray = new String[list.size()]; + // :: error: (return.type.incompatible) :: warning: (toarray.nullable.elements.not.newarray) + return list.toArray(resultArray); + } - String[] warn2(ArrayList list) { - int size = list.size(); - // :: error: (new.array.type.invalid) :: error: (return.type.incompatible) :: warning: - // (toarray.nullable.elements.mismatched.size) - return list.toArray(new String[size]); - } + String[] warn2(ArrayList list) { + int size = list.size(); + // :: error: (new.array.type.invalid) :: error: (return.type.incompatible) :: warning: + // (toarray.nullable.elements.mismatched.size) + return list.toArray(new String[size]); + } } diff --git a/checker/tests/nullness/ToArrayNullness.java b/checker/tests/nullness/ToArrayNullness.java index c42943bf234..77590ea8d1d 100644 --- a/checker/tests/nullness/ToArrayNullness.java +++ b/checker/tests/nullness/ToArrayNullness.java @@ -1,108 +1,107 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.Collection; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class ToArrayNullness { - private List<@Nullable String> nullableList = new ArrayList<>(); - private List<@NonNull String> nonnullList = new ArrayList<>(); + private List<@Nullable String> nullableList = new ArrayList<>(); + private List<@NonNull String> nonnullList = new ArrayList<>(); - void listToArrayObject() { - for (@Nullable Object o : nullableList.toArray()) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull Object o : nullableList.toArray()) {} + void listToArrayObject() { + for (@Nullable Object o : nullableList.toArray()) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull Object o : nullableList.toArray()) {} - for (@Nullable Object o : nonnullList.toArray()) {} - for (@NonNull Object o : nonnullList.toArray()) {} - } + for (@Nullable Object o : nonnullList.toArray()) {} + for (@NonNull Object o : nonnullList.toArray()) {} + } - void listToArrayE() { - for (@Nullable String o : nullableList.toArray(new @Nullable String[0])) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableList.toArray(new @Nullable String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - for (@Nullable String o : nullableList.toArray(new @NonNull String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableList.toArray(new @NonNull String[0])) {} + void listToArrayE() { + for (@Nullable String o : nullableList.toArray(new @Nullable String[0])) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableList.toArray(new @Nullable String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + for (@Nullable String o : nullableList.toArray(new @NonNull String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableList.toArray(new @NonNull String[0])) {} - for (@Nullable String o : nonnullList.toArray(new String[0])) {} - // No error expected here. Note that the heuristics determine that the given array - // is not used and that a new one will be created. - for (@NonNull String o : nonnullList.toArray(new @Nullable String[0])) {} - for (@Nullable String o : nonnullList.toArray(new @NonNull String[0])) {} - for (@NonNull String o : nonnullList.toArray(new @NonNull String[0])) {} - } + for (@Nullable String o : nonnullList.toArray(new String[0])) {} + // No error expected here. Note that the heuristics determine that the given array + // is not used and that a new one will be created. + for (@NonNull String o : nonnullList.toArray(new @Nullable String[0])) {} + for (@Nullable String o : nonnullList.toArray(new @NonNull String[0])) {} + for (@NonNull String o : nonnullList.toArray(new @NonNull String[0])) {} + } - private Collection<@Nullable String> nullableCol = new ArrayList<@Nullable String>(); - private Collection<@NonNull String> nonnullCol = new ArrayList<@NonNull String>(); + private Collection<@Nullable String> nullableCol = new ArrayList<@Nullable String>(); + private Collection<@NonNull String> nonnullCol = new ArrayList<@NonNull String>(); - void colToArrayObject() { - for (@Nullable Object o : nullableCol.toArray()) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull Object o : nullableCol.toArray()) {} + void colToArrayObject() { + for (@Nullable Object o : nullableCol.toArray()) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull Object o : nullableCol.toArray()) {} - for (@Nullable Object o : nonnullCol.toArray()) {} - for (@NonNull Object o : nonnullCol.toArray()) {} - } + for (@Nullable Object o : nonnullCol.toArray()) {} + for (@NonNull Object o : nonnullCol.toArray()) {} + } - void colToArrayE() { - for (@Nullable String o : nullableCol.toArray(new @Nullable String[0])) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableCol.toArray(new @Nullable String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - for (@Nullable String o : nullableCol.toArray(new @NonNull String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableCol.toArray(new @NonNull String[0])) {} + void colToArrayE() { + for (@Nullable String o : nullableCol.toArray(new @Nullable String[0])) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableCol.toArray(new @Nullable String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + for (@Nullable String o : nullableCol.toArray(new @NonNull String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableCol.toArray(new @NonNull String[0])) {} - for (@Nullable String o : nonnullCol.toArray(new String[0])) {} - // No error expected here. Note that the heuristics determine that the given array - // is not used and that a new one will be created. - for (@NonNull String o : nonnullCol.toArray(new @Nullable String[0])) {} - for (@Nullable String o : nonnullCol.toArray(new @NonNull String[0])) {} - for (@NonNull String o : nonnullCol.toArray(new @NonNull String[0])) {} - } + for (@Nullable String o : nonnullCol.toArray(new String[0])) {} + // No error expected here. Note that the heuristics determine that the given array + // is not used and that a new one will be created. + for (@NonNull String o : nonnullCol.toArray(new @Nullable String[0])) {} + for (@Nullable String o : nonnullCol.toArray(new @NonNull String[0])) {} + for (@NonNull String o : nonnullCol.toArray(new @NonNull String[0])) {} + } - void testHearusitics() { - for (@Nullable String o : nonnullCol.toArray(new String[] {})) {} - for (@NonNull String o : nonnullCol.toArray(new String[] {})) {} - for (@Nullable String o : nonnullCol.toArray(new String[0])) {} - for (@NonNull String o : nonnullCol.toArray(new String[0])) {} - for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} - for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} + void testHearusitics() { + for (@Nullable String o : nonnullCol.toArray(new String[] {})) {} + for (@NonNull String o : nonnullCol.toArray(new String[] {})) {} + for (@Nullable String o : nonnullCol.toArray(new String[0])) {} + for (@NonNull String o : nonnullCol.toArray(new String[0])) {} + for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} + for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} - // :: warning: (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new @Nullable String[] {null})) {} - // :: error: (enhancedfor.type.incompatible) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new @Nullable String[] {null})) {} - // Size 1 is too big for an empty array. Complain. TODO: Could allow as result is Nullable. - // :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new String[1])) {} - // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new String[1])) {} - // Array too big -> complain. TODO: Could allow as result is Nullable. - // :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} - // Array too big -> complain. - // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} + // :: warning: (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new @Nullable String[] {null})) {} + // :: error: (enhancedfor.type.incompatible) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new @Nullable String[] {null})) {} + // Size 1 is too big for an empty array. Complain. TODO: Could allow as result is Nullable. + // :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new String[1])) {} + // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new String[1])) {} + // Array too big -> complain. TODO: Could allow as result is Nullable. + // :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} + // Array too big -> complain. + // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} - // cannot handle the following cases for now - // new array not size 0 or .size -> complain about cration. TODO: Could allow as result is - // Nullable. - // :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} - // New array not size 0 or .size -> complain about creation. - // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} - } + // cannot handle the following cases for now + // new array not size 0 or .size -> complain about cration. TODO: Could allow as result is + // Nullable. + // :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} + // New array not size 0 or .size -> complain about creation. + // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} + } } diff --git a/checker/tests/nullness/ToArrayTest.java b/checker/tests/nullness/ToArrayTest.java index 513578bcaa5..ff8809e913f 100644 --- a/checker/tests/nullness/ToArrayTest.java +++ b/checker/tests/nullness/ToArrayTest.java @@ -1,21 +1,20 @@ -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.Collection; +import org.checkerframework.checker.nullness.qual.NonNull; public final class ToArrayTest { - public static void isReverse1(@NonNull Collection seq1) { - Object[] seq1_array_TMP2 = seq1.toArray(); - Object[] seq1_array = seq1.toArray(new Object[] {}); - } + public static void isReverse1(@NonNull Collection seq1) { + Object[] seq1_array_TMP2 = seq1.toArray(); + Object[] seq1_array = seq1.toArray(new Object[] {}); + } - public static void isReverse2(@NonNull Collection seq1) { - @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; - Object[] seq1_array = seq1.toArray(new Object[] {}); - } + public static void isReverse2(@NonNull Collection seq1) { + @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; + Object[] seq1_array = seq1.toArray(new Object[] {}); + } - public static void isReverse3(@NonNull Collection seq1) { - @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; - Object[] seq1_array = seq1.toArray(new Object[] {}); - } + public static void isReverse3(@NonNull Collection seq1) { + @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; + Object[] seq1_array = seq1.toArray(new Object[] {}); + } } diff --git a/checker/tests/nullness/TryWithResources.java b/checker/tests/nullness/TryWithResources.java index cbe611cecd4..5436db01274 100644 --- a/checker/tests/nullness/TryWithResources.java +++ b/checker/tests/nullness/TryWithResources.java @@ -1,46 +1,45 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.io.*; import java.util.zip.ZipFile; +import org.checkerframework.checker.nullness.qual.Nullable; public class TryWithResources { - void m1(InputStream stream) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) { - in.toString(); - } catch (Exception e) { - } + void m1(InputStream stream) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) { + in.toString(); + } catch (Exception e) { } + } - void m2() { - try (BufferedReader in = null) { - // :: error: (dereference.of.nullable) - in.toString(); - } catch (Exception e) { - } + void m2() { + try (BufferedReader in = null) { + // :: error: (dereference.of.nullable) + in.toString(); + } catch (Exception e) { } + } - // Check that catch blocks and code after try-catch are part of CFG (and flow-sensitive - // type-refinements work there). - boolean m3(@Nullable Object x) { - try (ZipFile f = openZipFile()) { - return true; - } catch (IOException e) { - if (x != null) { - // OK - x.toString(); - } - } - - if (x != null) { - // OK - return x.equals(x); - } - - return false; + // Check that catch blocks and code after try-catch are part of CFG (and flow-sensitive + // type-refinements work there). + boolean m3(@Nullable Object x) { + try (ZipFile f = openZipFile()) { + return true; + } catch (IOException e) { + if (x != null) { + // OK + x.toString(); + } } - // Helper - private static ZipFile openZipFile() throws IOException { - throw new IOException("No zip-file for you!"); + if (x != null) { + // OK + return x.equals(x); } + + return false; + } + + // Helper + private static ZipFile openZipFile() throws IOException { + throw new IOException("No zip-file for you!"); + } } diff --git a/checker/tests/nullness/TryWithResourcesAnno.java b/checker/tests/nullness/TryWithResourcesAnno.java index a2639a16a86..18b6cecef7c 100644 --- a/checker/tests/nullness/TryWithResourcesAnno.java +++ b/checker/tests/nullness/TryWithResourcesAnno.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TryWithResourcesAnno { - public static void f() { - try (@Nullable AutoCloseable obj = null) { - } catch (Exception e) { - } + public static void f() { + try (@Nullable AutoCloseable obj = null) { + } catch (Exception e) { } + } - public static void g() { - try (@Nullable AutoCloseable obj1 = null; - AutoCloseable obj2 = null) { - } catch (Exception e) { - } + public static void g() { + try (@Nullable AutoCloseable obj1 = null; + AutoCloseable obj2 = null) { + } catch (Exception e) { } + } - public static void h() { - try (AutoCloseable obj1 = null; - @Nullable AutoCloseable obj2 = null) { - } catch (Exception e) { - } + public static void h() { + try (AutoCloseable obj1 = null; + @Nullable AutoCloseable obj2 = null) { + } catch (Exception e) { } + } } diff --git a/checker/tests/nullness/TypeVarPrimitivesNullness.java b/checker/tests/nullness/TypeVarPrimitivesNullness.java index 939129406eb..0049b465a45 100644 --- a/checker/tests/nullness/TypeVarPrimitivesNullness.java +++ b/checker/tests/nullness/TypeVarPrimitivesNullness.java @@ -3,31 +3,31 @@ import org.checkerframework.checker.nullness.qual.*; public class TypeVarPrimitivesNullness { - void method(T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void method(T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } - void methodIntersection(T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void methodIntersection(T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } - void method2(@NonNull T tLong) { - long l = tLong; - } + void method2(@NonNull T tLong) { + long l = tLong; + } - void methodIntersection2(@NonNull T tLong) { - long l = tLong; - } + void methodIntersection2(@NonNull T tLong) { + long l = tLong; + } - void method3(@Nullable T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void method3(@Nullable T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } - void methodIntersection3(@Nullable T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void methodIntersection3(@Nullable T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } } diff --git a/checker/tests/nullness/UnannoPrimitives.java b/checker/tests/nullness/UnannoPrimitives.java index 4e70f9ae072..c2bb0ce6bef 100644 --- a/checker/tests/nullness/UnannoPrimitives.java +++ b/checker/tests/nullness/UnannoPrimitives.java @@ -1,58 +1,58 @@ import org.checkerframework.checker.nullness.qual.*; public class UnannoPrimitives { + // :: error: (nullness.on.primitive) + @Nullable int f; + + // :: error: (nullness.on.primitive) + @NonNull int g; + + void local() { + // test whether an arbitrary declaration annotation gets confused + @SuppressWarnings("tata") + int h = Integer.valueOf(5); + + int i = Integer.valueOf(99) + 1900; + int j = 7 + 1900; + // :: error: (nullness.on.primitive) @Nullable int f; // :: error: (nullness.on.primitive) @NonNull int g; + } - void local() { - // test whether an arbitrary declaration annotation gets confused - @SuppressWarnings("tata") - int h = Integer.valueOf(5); + static void testDate() { + @SuppressWarnings("deprecation") // for iCal4j + int year = new java.util.Date().getYear() + 1900; + String strDate = "/" + year; + } - int i = Integer.valueOf(99) + 1900; - int j = 7 + 1900; + // :: error: (nullness.on.primitive) + @Nullable byte[] d1 = {4}; + byte @Nullable [] d1b = {4}; - // :: error: (nullness.on.primitive) - @Nullable int f; + // :: error: (nullness.on.primitive) + @Nullable byte[][] twoD = {{4}}; - // :: error: (nullness.on.primitive) - @NonNull int g; - } + // :: error: (nullness.on.primitive) + @Nullable byte[][][] threeD = {{{4}}}; - static void testDate() { - @SuppressWarnings("deprecation") // for iCal4j - int year = new java.util.Date().getYear() + 1900; - String strDate = "/" + year; - } + // :: error: (nullness.on.primitive) + @Nullable byte[][][][] fourD = {{{{4}}}}; - // :: error: (nullness.on.primitive) - @Nullable byte[] d1 = {4}; - byte @Nullable [] d1b = {4}; + @SuppressWarnings("ha!") + byte[] d2 = {4}; - // :: error: (nullness.on.primitive) - @Nullable byte[][] twoD = {{4}}; + // :: error: (nullness.on.primitive) + Object ar = new @Nullable byte[] {4}; - // :: error: (nullness.on.primitive) - @Nullable byte[][][] threeD = {{{4}}}; + // :: error: (nullness.on.primitive) + Object ar2 = new @NonNull byte[] {42}; + void testCasts(Integer i1) { + Object i2 = (int) i1; // :: error: (nullness.on.primitive) - @Nullable byte[][][][] fourD = {{{{4}}}}; - - @SuppressWarnings("ha!") - byte[] d2 = {4}; - - // :: error: (nullness.on.primitive) - Object ar = new @Nullable byte[] {4}; - - // :: error: (nullness.on.primitive) - Object ar2 = new @NonNull byte[] {42}; - - void testCasts(Integer i1) { - Object i2 = (int) i1; - // :: error: (nullness.on.primitive) - Object i3 = (@Nullable int) i1; - } + Object i3 = (@Nullable int) i1; + } } diff --git a/checker/tests/nullness/UnannoPrimitivesDefaults.java b/checker/tests/nullness/UnannoPrimitivesDefaults.java index 53f20b3271b..016d0596111 100644 --- a/checker/tests/nullness/UnannoPrimitivesDefaults.java +++ b/checker/tests/nullness/UnannoPrimitivesDefaults.java @@ -1,16 +1,16 @@ public class UnannoPrimitivesDefaults { - @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.NonNull.class) - class Decl { - // The return type is not annotated with @NonNull, because - // the implicit annotation for @Primitive takes precedence. - int test() { - return 5; - } + @org.checkerframework.framework.qual.DefaultQualifier( + org.checkerframework.checker.nullness.qual.NonNull.class) + class Decl { + // The return type is not annotated with @NonNull, because + // the implicit annotation for @Primitive takes precedence. + int test() { + return 5; } + } - class Use { - Decl d = new Decl(); - int x = d.test(); - } + class Use { + Decl d = new Decl(); + int x = d.test(); + } } diff --git a/checker/tests/nullness/UnboxConditions.java b/checker/tests/nullness/UnboxConditions.java index 856e9e0c85a..8ac19930616 100644 --- a/checker/tests/nullness/UnboxConditions.java +++ b/checker/tests/nullness/UnboxConditions.java @@ -1,26 +1,26 @@ public class UnboxConditions { - public static void main(String[] args) { - Boolean b = null; - Boolean b1 = null; - Boolean b2 = null; - Boolean b3 = null; - Boolean b4 = null; - // :: error: (condition.nullable) - if (b) {} - // :: error: (condition.nullable) - b = b1 ? b : b; - // :: error: (condition.nullable) - while (b2) {} - do { - // :: error: (condition.nullable) - } while (b3); - // :: error: (condition.nullable) - for (; b4; ) {} - // legal! - for (; ; ) { - break; - } - // Eliding the condition in a "while" is illegal Java syntax. - // while () {} + public static void main(String[] args) { + Boolean b = null; + Boolean b1 = null; + Boolean b2 = null; + Boolean b3 = null; + Boolean b4 = null; + // :: error: (condition.nullable) + if (b) {} + // :: error: (condition.nullable) + b = b1 ? b : b; + // :: error: (condition.nullable) + while (b2) {} + do { + // :: error: (condition.nullable) + } while (b3); + // :: error: (condition.nullable) + for (; b4; ) {} + // legal! + for (; ; ) { + break; } + // Eliding the condition in a "while" is illegal Java syntax. + // while () {} + } } diff --git a/checker/tests/nullness/Unboxing.java b/checker/tests/nullness/Unboxing.java index 3a8c762a04e..30682b45b1d 100644 --- a/checker/tests/nullness/Unboxing.java +++ b/checker/tests/nullness/Unboxing.java @@ -2,42 +2,42 @@ public class Unboxing { - @Nullable Integer f; + @Nullable Integer f; - public void t1() { - // :: error: (unboxing.of.nullable) - int l = f + 1; - // no error, since f has been unboxed - f.toString(); - } + public void t1() { + // :: error: (unboxing.of.nullable) + int l = f + 1; + // no error, since f has been unboxed + f.toString(); + } - public void t2() { - try { - // :: error: (unboxing.of.nullable) - int l = f + 1; - } catch (NullPointerException npe) { - // f is known to be null on the exception edge - // :: error: (unboxing.of.nullable) - int m = f + 1; - } - // after the merge, f cannot be null - f.toString(); + public void t2() { + try { + // :: error: (unboxing.of.nullable) + int l = f + 1; + } catch (NullPointerException npe) { + // f is known to be null on the exception edge + // :: error: (unboxing.of.nullable) + int m = f + 1; } + // after the merge, f cannot be null + f.toString(); + } - void foo(@Nullable Integer in) { - // :: error: (unboxing.of.nullable) - int q = in; - } + void foo(@Nullable Integer in) { + // :: error: (unboxing.of.nullable) + int q = in; + } - int bar(@Nullable Integer in) { - // :: error: (unboxing.of.nullable) - return in; - } + int bar(@Nullable Integer in) { + // :: error: (unboxing.of.nullable) + return in; + } - int barT(T in) { - // :: error: (unboxing.of.nullable) - int q = in; - // :: error: (unboxing.of.nullable) - return in; - } + int barT(T in) { + // :: error: (unboxing.of.nullable) + int q = in; + // :: error: (unboxing.of.nullable) + return in; + } } diff --git a/checker/tests/nullness/UnexpectedRaw.java b/checker/tests/nullness/UnexpectedRaw.java index dfaf44027d6..dbcfd040fa8 100644 --- a/checker/tests/nullness/UnexpectedRaw.java +++ b/checker/tests/nullness/UnexpectedRaw.java @@ -1,58 +1,58 @@ import org.checkerframework.checker.nullness.qual.*; interface Consumer { - public void consume(A object); + public void consume(A object); } class Utils { - public static Consumer cast( - final @Nullable Consumer consumer) { - throw new RuntimeException(); - } + public static Consumer cast( + final @Nullable Consumer consumer) { + throw new RuntimeException(); + } - public static Consumer getConsumer() { - // null for simplicity, but could be anything - Consumer<@Nullable Object> nullConsumer = null; + public static Consumer getConsumer() { + // null for simplicity, but could be anything + Consumer<@Nullable Object> nullConsumer = null; - // Previous reasoning for this to generate an (argument.type.incompatible) error was: - // C could be @NonNull Object, so argument is incompatible? - // - // This is poor reasoning, however, because the type of the formal parameter should be: - // @Nullable Consumer< ? [ - // super C[ extends @Nullable Object - // super @NonNull Void - // ] - // extends @Nullable Object - // ] - // The primary annotations on nullConsumer and the formal parameter consumer are - // identical, so it comes down to the annotations on the type arguments. + // Previous reasoning for this to generate an (argument.type.incompatible) error was: + // C could be @NonNull Object, so argument is incompatible? + // + // This is poor reasoning, however, because the type of the formal parameter should be: + // @Nullable Consumer< ? [ + // super C[ extends @Nullable Object + // super @NonNull Void + // ] + // extends @Nullable Object + // ] + // The primary annotations on nullConsumer and the formal parameter consumer are + // identical, so it comes down to the annotations on the type arguments. - // Let X stand in for the type argument of nullConsumer. For it to be a valid parameter, X - // must be contained by the type argument of the formal parameter, ? super C. - // - // In other words, the following constraints must hold: - // - // C1: X <: upper bound of (? super C) - // C2: lower bound of (? super C) <: X - // - // we can simplify these constraints by substituting out the lower and upper bound of - // ? super C. - // C1: X <: @Nullable Object - // C2: C <: X - // - // we can simplify the constraints again by substituting X with the actual type argument to - // nullConsumer and in C2, we can substitute C with its upper bound, since for the - // constraint to hold X must be above C's upper bound. This yields: - // - // C1: @Nullable Object <: @Nullable Object - // C2: @Nullable Object <: @Nullable Object - // - // Since, for all type's T => T <: T, both C1 and C2 are upheld and the following statement - // should NOT report an error - Consumer result = Utils.cast(nullConsumer); + // Let X stand in for the type argument of nullConsumer. For it to be a valid parameter, X + // must be contained by the type argument of the formal parameter, ? super C. + // + // In other words, the following constraints must hold: + // + // C1: X <: upper bound of (? super C) + // C2: lower bound of (? super C) <: X + // + // we can simplify these constraints by substituting out the lower and upper bound of + // ? super C. + // C1: X <: @Nullable Object + // C2: C <: X + // + // we can simplify the constraints again by substituting X with the actual type argument to + // nullConsumer and in C2, we can substitute C with its upper bound, since for the + // constraint to hold X must be above C's upper bound. This yields: + // + // C1: @Nullable Object <: @Nullable Object + // C2: @Nullable Object <: @Nullable Object + // + // Since, for all type's T => T <: T, both C1 and C2 are upheld and the following statement + // should NOT report an error + Consumer result = Utils.cast(nullConsumer); - // on a side note, I am not sure why this is called unexpected raw - return result; - } + // on a side note, I am not sure why this is called unexpected raw + return result; + } } diff --git a/checker/tests/nullness/UnusedNullness.java b/checker/tests/nullness/UnusedNullness.java index e35f8e5d00d..b0f3fadd770 100644 --- a/checker/tests/nullness/UnusedNullness.java +++ b/checker/tests/nullness/UnusedNullness.java @@ -1,10 +1,9 @@ +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.Unused; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - // TODO: feature request: the Nullness Checker should be aware of the @Unused annotation. // This is difficult to implement: one needs to determine the correct AnnotatedTypeFactory for the // "when" type system and use it to determine the right annotated type. We currently don't have a @@ -13,42 +12,42 @@ // @skip-test public class UnusedNullness { - @SubtypeOf({}) - @Target(ElementType.TYPE_USE) - public @interface Prototype {} - - @Unused(when = Prototype.class) - public Object ppt; - - protected @Prototype UnusedNullness() { - // It should be legal to initialize an unused field to null in - // a constructor with @Prototype receiver. - this.ppt = null; - } - - protected @Prototype UnusedNullness(int disambiguate_overloading) { - // It should be legal to NOT initialize an unused field in - // a constructor with @Prototype receiver. - } - - protected void protometh(@Prototype UnusedNullness this) { - // It should be legal to initialize the unused field to null in - // a method with @Prototype receiver. - this.ppt = null; - } - - protected void meth() { - // Otherwise it's not legal. - // :: error: (assignment.type.incompatible) - this.ppt = null; - } - - protected void useUnusedField1(@Prototype UnusedNullness this) { - // :: error: (assignment.type.incompatible) - @NonNull Object x = this.ppt; - } - - protected void useUnusedField2() { - @NonNull Object x = this.ppt; - } + @SubtypeOf({}) + @Target(ElementType.TYPE_USE) + public @interface Prototype {} + + @Unused(when = Prototype.class) + public Object ppt; + + protected @Prototype UnusedNullness() { + // It should be legal to initialize an unused field to null in + // a constructor with @Prototype receiver. + this.ppt = null; + } + + protected @Prototype UnusedNullness(int disambiguate_overloading) { + // It should be legal to NOT initialize an unused field in + // a constructor with @Prototype receiver. + } + + protected void protometh(@Prototype UnusedNullness this) { + // It should be legal to initialize the unused field to null in + // a method with @Prototype receiver. + this.ppt = null; + } + + protected void meth() { + // Otherwise it's not legal. + // :: error: (assignment.type.incompatible) + this.ppt = null; + } + + protected void useUnusedField1(@Prototype UnusedNullness this) { + // :: error: (assignment.type.incompatible) + @NonNull Object x = this.ppt; + } + + protected void useUnusedField2() { + @NonNull Object x = this.ppt; + } } diff --git a/checker/tests/nullness/UnusedOnClass.java b/checker/tests/nullness/UnusedOnClass.java index 26b554089df..ec2c73c8db0 100644 --- a/checker/tests/nullness/UnusedOnClass.java +++ b/checker/tests/nullness/UnusedOnClass.java @@ -1,20 +1,19 @@ +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.Unused; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - public final class UnusedOnClass { - public static void read_serialized_pptmap2(@MyNonPrototype MyInvariant2 inv) { - inv.ppt.toString(); - } + public static void read_serialized_pptmap2(@MyNonPrototype MyInvariant2 inv) { + inv.ppt.toString(); + } } @MyPrototype abstract class MyInvariant2 { - @Unused(when = MyPrototype.class) - public String ppt = "hello"; + @Unused(when = MyPrototype.class) + public String ppt = "hello"; } @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/checker/tests/nullness/UtilArrays.java b/checker/tests/nullness/UtilArrays.java index 5c9f3e91ad6..66b5c31be03 100644 --- a/checker/tests/nullness/UtilArrays.java +++ b/checker/tests/nullness/UtilArrays.java @@ -1,52 +1,51 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Arrays; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; // Illustrate various mis-uses of java.util.Arrays. public class UtilArrays { - public static void main(String[] args) { - @Nullable Object[] arrayWithNull = {"", null, ""}; - Object[] arrayWithoutNull = {""}; + public static void main(String[] args) { + @Nullable Object[] arrayWithNull = {"", null, ""}; + Object[] arrayWithoutNull = {""}; - try { - // :: error: (argument.type.incompatible) - Arrays.binarySearch(arrayWithNull, ""); - } catch (NullPointerException e) { - System.out.println("got NPE for array containing null"); - } + try { + // :: error: (argument.type.incompatible) + Arrays.binarySearch(arrayWithNull, ""); + } catch (NullPointerException e) { + System.out.println("got NPE for array containing null"); + } - try { - // :: error: (argument.type.incompatible) - Arrays.binarySearch(arrayWithoutNull, null); - } catch (NullPointerException e) { - System.out.println("got NPE for null key"); - } + try { + // :: error: (argument.type.incompatible) + Arrays.binarySearch(arrayWithoutNull, null); + } catch (NullPointerException e) { + System.out.println("got NPE for null key"); + } - try { - // :: error: (argument.type.incompatible) - Arrays.sort(arrayWithNull, null); - } catch (NullPointerException e) { - System.out.println("got NPE for sort"); - } + try { + // :: error: (argument.type.incompatible) + Arrays.sort(arrayWithNull, null); + } catch (NullPointerException e) { + System.out.println("got NPE for sort"); + } - try { - // TODO: false negative: covariant arrays and polymorphism cause that this call is - // allowed. - Arrays.fill(arrayWithoutNull, null); - arrayWithoutNull[0].toString(); - } catch (NullPointerException e) { - System.out.println("got NPE for fill"); - } + try { + // TODO: false negative: covariant arrays and polymorphism cause that this call is + // allowed. + Arrays.fill(arrayWithoutNull, null); + arrayWithoutNull[0].toString(); + } catch (NullPointerException e) { + System.out.println("got NPE for fill"); + } - try { - // TODO: false negative: covariant arrays and captured argument cause that this call is - // allowed. - List<@Nullable Object> ls = Arrays.asList(arrayWithoutNull); - ls.set(0, null); - arrayWithoutNull[0].toString(); - } catch (NullPointerException e) { - System.out.println("got NPE for asList"); - } + try { + // TODO: false negative: covariant arrays and captured argument cause that this call is + // allowed. + List<@Nullable Object> ls = Arrays.asList(arrayWithoutNull); + ls.set(0, null); + arrayWithoutNull[0].toString(); + } catch (NullPointerException e) { + System.out.println("got NPE for asList"); } + } } diff --git a/checker/tests/nullness/VarargsNullness.java b/checker/tests/nullness/VarargsNullness.java index 32b1771e177..b0c637780d9 100644 --- a/checker/tests/nullness/VarargsNullness.java +++ b/checker/tests/nullness/VarargsNullness.java @@ -2,52 +2,52 @@ public class VarargsNullness { - public void test(@NonNull Object @NonNull ... o) { - for (@NonNull Object p : o) { - System.out.println(p); - } - } - - public void test2(Object o1, Object o2) { - System.out.println(o1); - System.out.println(o2); - } - - public void testVarargs() { - test("foo", "bar", "baz"); - } - - public void testVarargsNoArgs() { - test(); - } - - public void testNonVarargs() { - test2("foo", "bar"); - } - - public void format1(java.lang.String a1, java.lang.@Nullable Object... a2) { - int x = a2.length; // no warning - // :: error: (enhancedfor.type.incompatible) - for (@NonNull Object p : a2) // warning - System.out.println(p); - } - - public void format2(java.lang.String a1, java.lang.Object @Nullable ... a2) { - // :: error: (dereference.of.nullable) - int x = a2.length; // warning - for (@NonNull Object p : a2) // no warning - System.out.println(p); - } - - public void testPrintf() { - String s = null; - printf("%s", s); - // tests do not use annotated JDK - // System.out.printf ("%s", s); - } - - // printf declaration is taken from PrintStream - public java.io.PrintStream printf(java.lang.String a1, java.lang.@Nullable Object... a2) { - throw new RuntimeException("skeleton method"); - } + public void test(@NonNull Object @NonNull ... o) { + for (@NonNull Object p : o) { + System.out.println(p); + } + } + + public void test2(Object o1, Object o2) { + System.out.println(o1); + System.out.println(o2); + } + + public void testVarargs() { + test("foo", "bar", "baz"); + } + + public void testVarargsNoArgs() { + test(); + } + + public void testNonVarargs() { + test2("foo", "bar"); + } + + public void format1(java.lang.String a1, java.lang.@Nullable Object... a2) { + int x = a2.length; // no warning + // :: error: (enhancedfor.type.incompatible) + for (@NonNull Object p : a2) // warning + System.out.println(p); + } + + public void format2(java.lang.String a1, java.lang.Object @Nullable ... a2) { + // :: error: (dereference.of.nullable) + int x = a2.length; // warning + for (@NonNull Object p : a2) // no warning + System.out.println(p); + } + + public void testPrintf() { + String s = null; + printf("%s", s); + // tests do not use annotated JDK + // System.out.printf ("%s", s); + } + + // printf declaration is taken from PrintStream + public java.io.PrintStream printf(java.lang.String a1, java.lang.@Nullable Object... a2) { + throw new RuntimeException("skeleton method"); + } } diff --git a/checker/tests/nullness/VarargsNullness2.java b/checker/tests/nullness/VarargsNullness2.java index 2e9ec028052..2b079ace922 100644 --- a/checker/tests/nullness/VarargsNullness2.java +++ b/checker/tests/nullness/VarargsNullness2.java @@ -2,17 +2,17 @@ public class VarargsNullness2 { - public static void method1(Object... args) {} + public static void method1(Object... args) {} - public static void method2(Object @Nullable ... args) {} + public static void method2(Object @Nullable ... args) {} - public static void main(String[] args) { - // :: error: (argument.type.incompatible) - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - method1(null); - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - method2(null); - } + public static void main(String[] args) { + // :: error: (argument.type.incompatible) + // :: warning: non-varargs call of varargs method with inexact argument type for last + // parameter; + method1(null); + // :: warning: non-varargs call of varargs method with inexact argument type for last + // parameter; + method2(null); + } } diff --git a/checker/tests/nullness/VoidUse.java b/checker/tests/nullness/VoidUse.java index ca636bc26b3..6b519387b33 100644 --- a/checker/tests/nullness/VoidUse.java +++ b/checker/tests/nullness/VoidUse.java @@ -2,52 +2,52 @@ public class VoidUse { - private Class main_class1 = Void.TYPE; + private Class main_class1 = Void.TYPE; - private Class main_class2 = Void.TYPE; + private Class main_class2 = Void.TYPE; - public Void voidReturn(Void p) { - voidReturn(null); - return null; - } + public Void voidReturn(Void p) { + voidReturn(null); + return null; + } - // Void is treated as Nullable. Is there a value on having it be NonNull? - public abstract static class VoidTestNode {} + // Void is treated as Nullable. Is there a value on having it be NonNull? + public abstract static class VoidTestNode {} - public static class VoidTestInvNode extends VoidTestNode<@NonNull Void> {} + public static class VoidTestInvNode extends VoidTestNode<@NonNull Void> {} - class Scanner

{ - public void scan(Object tree, P p) {} - } + class Scanner

{ + public void scan(Object tree, P p) {} + } - // :: error: (type.argument.type.incompatible) - class MyScanner extends Scanner { - void use(MyScanner ms) { - ms.scan(new Object(), null); - } + // :: error: (type.argument.type.incompatible) + class MyScanner extends Scanner { + void use(MyScanner ms) { + ms.scan(new Object(), null); } + } - // :: error: (type.argument.type.incompatible) - class MyScanner2 extends Scanner<@Nullable Object> { - void use(MyScanner2 ms) { - ms.scan(new Object(), null); - } + // :: error: (type.argument.type.incompatible) + class MyScanner2 extends Scanner<@Nullable Object> { + void use(MyScanner2 ms) { + ms.scan(new Object(), null); } + } - // Test case for issue #230 - Class voidClass() { - return void.class; - } + // Test case for issue #230 + Class voidClass() { + return void.class; + } - Class VoidClass() { - return Void.class; - } + Class VoidClass() { + return Void.class; + } - Class intClass() { - return int.class; - } + Class intClass() { + return int.class; + } - Class ListClass() { - return java.util.List.class; - } + Class ListClass() { + return java.util.List.class; + } } diff --git a/checker/tests/nullness/WeakHasherMapNonNull.java b/checker/tests/nullness/WeakHasherMapNonNull.java index 05e1325c065..94deaf9747e 100644 --- a/checker/tests/nullness/WeakHasherMapNonNull.java +++ b/checker/tests/nullness/WeakHasherMapNonNull.java @@ -1,21 +1,20 @@ -import org.checkerframework.checker.initialization.qual.*; -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.checker.regex.qual.*; - import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.initialization.qual.*; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.checker.regex.qual.*; public abstract class WeakHasherMapNonNull extends AbstractMap implements Map { - private Map hash = new HashMap<>(); + private Map hash = new HashMap<>(); - @org.checkerframework.dataflow.qual.Pure - public boolean containsKey(@NonNull Object key) { - // :: warning: [unchecked] unchecked cast - K kkey = (K) key; - // :: error: (argument.type.incompatible) - hash.containsKey(null); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @org.checkerframework.dataflow.qual.Pure + public boolean containsKey(@NonNull Object key) { + // :: warning: [unchecked] unchecked cast + K kkey = (K) key; + // :: error: (argument.type.incompatible) + hash.containsKey(null); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/WeakHasherMapNullable.java b/checker/tests/nullness/WeakHasherMapNullable.java index 4949ec8021f..ee33890cf70 100644 --- a/checker/tests/nullness/WeakHasherMapNullable.java +++ b/checker/tests/nullness/WeakHasherMapNullable.java @@ -1,20 +1,19 @@ -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; - import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; public abstract class WeakHasherMapNullable extends AbstractMap implements Map { - private Map hash = new HashMap<>(); + private Map hash = new HashMap<>(); - @Pure - public boolean containsKey(@Nullable Object key) { - // :: warning: [unchecked] unchecked cast - K kkey = (K) key; - // :: error: (argument.type.incompatible) - hash.containsKey(null); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @Pure + public boolean containsKey(@Nullable Object key) { + // :: warning: [unchecked] unchecked cast + K kkey = (K) key; + // :: error: (argument.type.incompatible) + hash.containsKey(null); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/WeakIdentityPair.java b/checker/tests/nullness/WeakIdentityPair.java index d2b608e0c49..4a16b36189d 100644 --- a/checker/tests/nullness/WeakIdentityPair.java +++ b/checker/tests/nullness/WeakIdentityPair.java @@ -1,16 +1,15 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.lang.ref.WeakReference; +import org.checkerframework.checker.nullness.qual.*; public class WeakIdentityPair { - private final WeakReference a; + private final WeakReference a; - public WeakIdentityPair(T1 a) { - this.a = new WeakReference<>(a); - } + public WeakIdentityPair(T1 a) { + this.a = new WeakReference<>(a); + } - public @Nullable T1 getA() { - return a.get(); - } + public @Nullable T1 getA() { + return a.get(); + } } diff --git a/checker/tests/nullness/WeakRef.java b/checker/tests/nullness/WeakRef.java index fc846f494c7..5b7fcddd39f 100644 --- a/checker/tests/nullness/WeakRef.java +++ b/checker/tests/nullness/WeakRef.java @@ -1,9 +1,8 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.lang.ref.WeakReference; +import org.checkerframework.checker.nullness.qual.*; public class WeakRef { - @PolyNull Object @Nullable [] foo(WeakReference<@PolyNull Object[]> lookup) { - return lookup.get(); - } + @PolyNull Object @Nullable [] foo(WeakReference<@PolyNull Object[]> lookup) { + return lookup.get(); + } } diff --git a/checker/tests/nullness/WhileTest.java b/checker/tests/nullness/WhileTest.java index 2b21b114e25..9ddf95e2948 100644 --- a/checker/tests/nullness/WhileTest.java +++ b/checker/tests/nullness/WhileTest.java @@ -2,59 +2,59 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class WhileTest { - @Nullable Integer z; - @NonNull Integer nnz = Integer.valueOf(22); + @Nullable Integer z; + @NonNull Integer nnz = Integer.valueOf(22); - public static void main(String[] args) { - new WhileTest().testwhile1(); - } + public static void main(String[] args) { + new WhileTest().testwhile1(); + } - public void testwhile1() { - z = null; - // :: error: (assignment.type.incompatible) - nnz = z; - - while (z == null) { - break; - } - // :: error: (assignment.type.incompatible) - nnz = z; - nnz.toString(); - } + public void testwhile1() { + z = null; + // :: error: (assignment.type.incompatible) + nnz = z; - public void testwhile2() { - z = null; - while (z == null) {} - nnz = z; + while (z == null) { + break; } - - public void testdo1() { - z = null; - do { - break; - } while (z == null); - // :: error: (assignment.type.incompatible) - nnz = z; - } - - public void testdo2() { - z = null; - do {} while (z == null); - nnz = z; - } - - public void testfor1() { - z = null; - for (; z == null; ) { - break; - } - // :: error: (assignment.type.incompatible) - nnz = z; - } - - public void testfor2() { - z = null; - for (; z == null; ) {} - nnz = z; + // :: error: (assignment.type.incompatible) + nnz = z; + nnz.toString(); + } + + public void testwhile2() { + z = null; + while (z == null) {} + nnz = z; + } + + public void testdo1() { + z = null; + do { + break; + } while (z == null); + // :: error: (assignment.type.incompatible) + nnz = z; + } + + public void testdo2() { + z = null; + do {} while (z == null); + nnz = z; + } + + public void testfor1() { + z = null; + for (; z == null; ) { + break; } + // :: error: (assignment.type.incompatible) + nnz = z; + } + + public void testfor2() { + z = null; + for (; z == null; ) {} + nnz = z; + } } diff --git a/checker/tests/nullness/Widening.java b/checker/tests/nullness/Widening.java index 4da7df66ecf..53f51e33e7e 100644 --- a/checker/tests/nullness/Widening.java +++ b/checker/tests/nullness/Widening.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Widening { - @Nullable Integer i; + @Nullable Integer i; - void inc(long amt) {} + void inc(long amt) {} - void foo() { - inc(i == null ? 0 : i); - } + void foo() { + inc(i == null ? 0 : i); + } } diff --git a/checker/tests/nullness/WildcardGLB.java b/checker/tests/nullness/WildcardGLB.java index 0d7634a29da..3dfa129f63d 100644 --- a/checker/tests/nullness/WildcardGLB.java +++ b/checker/tests/nullness/WildcardGLB.java @@ -1,67 +1,66 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class WildcardGLB { - static class MyClass> { - E getE() { - throw new RuntimeException(); - } + static class MyClass> { + E getE() { + throw new RuntimeException(); } + } - // The captured type variable for - // ? extends @List<@NonNull String> - // is - // capture#865 extends @NonNull List<@Nullable String> - // . The upper bound of the captured type variable is not a subtype of the extends bound of the - // wildcard because the glb of the type parameter bound and the wildcard extends bound does not - // exist. I don't think this leads to unsoundness, but it makes it so that this method can't be - // called without an error. The method testUse below demos this. - // :: error: (type.argument.type.incompatible) - void use(MyClass> s) { - // :: error: (assignment.type.incompatible) - List f = s.getE(); - List<@Nullable String> f2 = s.getE(); - } + // The captured type variable for + // ? extends @List<@NonNull String> + // is + // capture#865 extends @NonNull List<@Nullable String> + // . The upper bound of the captured type variable is not a subtype of the extends bound of the + // wildcard because the glb of the type parameter bound and the wildcard extends bound does not + // exist. I don't think this leads to unsoundness, but it makes it so that this method can't be + // called without an error. The method testUse below demos this. + // :: error: (type.argument.type.incompatible) + void use(MyClass> s) { + // :: error: (assignment.type.incompatible) + List f = s.getE(); + List<@Nullable String> f2 = s.getE(); + } - void testUse( - // :: error: (type.argument.type.incompatible) - MyClass> p1, - // A comment to force a line break. - MyClass> p2) { - use(p1); - // :: error: (argument.type.incompatible) - use(p2); - } + void testUse( + // :: error: (type.argument.type.incompatible) + MyClass> p1, + // A comment to force a line break. + MyClass> p2) { + use(p1); + // :: error: (argument.type.incompatible) + use(p2); + } - // capture#196 extends @NonNull ArrayList<@NonNull String> - // :: error: (type.argument.type.incompatible) - void use2(MyClass> s) { // error: type.argument - List f = s.getE(); - // :: error: (assignment.type.incompatible) - List<@Nullable String> f2 = s.getE(); // error: assignment - } + // capture#196 extends @NonNull ArrayList<@NonNull String> + // :: error: (type.argument.type.incompatible) + void use2(MyClass> s) { // error: type.argument + List f = s.getE(); + // :: error: (assignment.type.incompatible) + List<@Nullable String> f2 = s.getE(); // error: assignment + } - static class MyClass2> { - E getE() { - throw new RuntimeException(); - } + static class MyClass2> { + E getE() { + throw new RuntimeException(); } + } - // capture#952 extends @NonNull ArrayList<@Nullable String> - // :: error: (type.argument.type.incompatible) - void use3(MyClass2> s) { - // :: error: (assignment.type.incompatible) - List f = s.getE(); - List<@Nullable String> f2 = s.getE(); - } + // capture#952 extends @NonNull ArrayList<@Nullable String> + // :: error: (type.argument.type.incompatible) + void use3(MyClass2> s) { + // :: error: (assignment.type.incompatible) + List f = s.getE(); + List<@Nullable String> f2 = s.getE(); + } - // :: error: (type.argument.type.incompatible) - void use4(MyClass2> s) { - // :: error: (assignment.type.incompatible) - List f = s.getE(); - List<@Nullable String> f2 = s.getE(); // ok - } + // :: error: (type.argument.type.incompatible) + void use4(MyClass2> s) { + // :: error: (assignment.type.incompatible) + List f = s.getE(); + List<@Nullable String> f2 = s.getE(); // ok + } } diff --git a/checker/tests/nullness/WildcardSubtype.java b/checker/tests/nullness/WildcardSubtype.java index 66fea84d5c8..9bc4bf1f598 100644 --- a/checker/tests/nullness/WildcardSubtype.java +++ b/checker/tests/nullness/WildcardSubtype.java @@ -2,53 +2,53 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class WildcardSubtype { - class MyClass {} + class MyClass {} - class Visitor { - String visit(T p) { - return ""; - } - } - - class MyClassVisitor extends Visitor<@Nullable MyClass> {} - - class NonNullMyClassVisitor extends Visitor<@NonNull MyClass> {} - - void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object>()); - - Visitor visitor1 = myClassVisitor; - Visitor visitor2 = nonNullMyClassVisitor; - - // :: error: (assignment.type.incompatible) - Visitor visitor3 = myClassVisitor; - Visitor visitor4 = nonNullMyClassVisitor; - - // :: error: (assignment.type.incompatible) - Visitor visitor5 = new MyClassVisitor(); - // :: error: (assignment.type.incompatible) - Visitor visitor6 = new MyClassVisitor(); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } - - void take(Visitor<@NonNull ? extends @NonNull Object> v) {} - - void bar() { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } - - void baz() { - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - take(new NonNullMyClassVisitor()); + class Visitor { + String visit(T p) { + return ""; } + } + + class MyClassVisitor extends Visitor<@Nullable MyClass> {} + + class NonNullMyClassVisitor extends Visitor<@NonNull MyClass> {} + + void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object>()); + + Visitor visitor1 = myClassVisitor; + Visitor visitor2 = nonNullMyClassVisitor; + + // :: error: (assignment.type.incompatible) + Visitor visitor3 = myClassVisitor; + Visitor visitor4 = nonNullMyClassVisitor; + + // :: error: (assignment.type.incompatible) + Visitor visitor5 = new MyClassVisitor(); + // :: error: (assignment.type.incompatible) + Visitor visitor6 = new MyClassVisitor(); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void take(Visitor<@NonNull ? extends @NonNull Object> v) {} + + void bar() { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void baz() { + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + take(new NonNullMyClassVisitor()); + } } diff --git a/checker/tests/nullness/WildcardSubtype2.java b/checker/tests/nullness/WildcardSubtype2.java index 77e1eb0717c..c0f5802de64 100644 --- a/checker/tests/nullness/WildcardSubtype2.java +++ b/checker/tests/nullness/WildcardSubtype2.java @@ -2,55 +2,54 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class WildcardSubtype2 { - class MyClass {} + class MyClass {} - class Visitor { - String visit(T p) { - return ""; - } + class Visitor { + String visit(T p) { + return ""; } + } - class MyClassVisitor extends Visitor<@Nullable MyClass, @Nullable MyClass> {} + class MyClassVisitor extends Visitor<@Nullable MyClass, @Nullable MyClass> {} - class NonNullMyClassVisitor extends Visitor<@NonNull MyClass, @NonNull MyClass> {} + class NonNullMyClassVisitor extends Visitor<@NonNull MyClass, @NonNull MyClass> {} - void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object, @Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object, @Nullable Object>()); - Visitor visitor1 = myClassVisitor; - Visitor visitor2 = nonNullMyClassVisitor; + void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object, @Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object, @Nullable Object>()); + Visitor visitor1 = myClassVisitor; + Visitor visitor2 = nonNullMyClassVisitor; - // :: error: (assignment.type.incompatible) - Visitor visitor3 = myClassVisitor; - Visitor visitor4 = - nonNullMyClassVisitor; - - Visitor visitor5 = - // :: error: (assignment.type.incompatible) - new MyClassVisitor(); - Visitor visitor6 = - // :: error: (assignment.type.incompatible) - new MyClassVisitor(); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } - - void take(Visitor<@NonNull ? extends @NonNull Object, @NonNull ? extends @NonNull Object> v) {} - - void bar() { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object, @Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } + // :: error: (assignment.type.incompatible) + Visitor visitor3 = myClassVisitor; + Visitor visitor4 = nonNullMyClassVisitor; - void baz() { - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - take(new NonNullMyClassVisitor()); - } + Visitor visitor5 = + // :: error: (assignment.type.incompatible) + new MyClassVisitor(); + Visitor visitor6 = + // :: error: (assignment.type.incompatible) + new MyClassVisitor(); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void take(Visitor<@NonNull ? extends @NonNull Object, @NonNull ? extends @NonNull Object> v) {} + + void bar() { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object, @Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void baz() { + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + take(new NonNullMyClassVisitor()); + } } diff --git a/checker/tests/nullness/Wildcards.java b/checker/tests/nullness/Wildcards.java index 54dd0cef5d9..5ee09b86497 100644 --- a/checker/tests/nullness/Wildcards.java +++ b/checker/tests/nullness/Wildcards.java @@ -1,38 +1,37 @@ // Test case for issue #234. -import org.checkerframework.checker.nullness.qual.*; - import java.util.Iterator; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class Wildcards { - public static void client1(List strings) { - join1(strings.iterator()); - join2(strings.iterator()); - join3(strings.iterator()); - join4(strings.iterator()); - } + public static void client1(List strings) { + join1(strings.iterator()); + join2(strings.iterator()); + join3(strings.iterator()); + join4(strings.iterator()); + } - public static void client2(Iterator itor) { - join1(itor); - join2(itor); - join3(itor); - join4(itor); - } + public static void client2(Iterator itor) { + join1(itor); + join2(itor); + join3(itor); + join4(itor); + } - public static void client3(Iterator itor) { - Iterator parts1 = itor; - Iterator parts2 = itor; - Iterator parts3 = itor; - Iterator parts4 = itor; - } + public static void client3(Iterator itor) { + Iterator parts1 = itor; + Iterator parts2 = itor; + Iterator parts3 = itor; + Iterator parts4 = itor; + } - static void join1(Iterator parts) {} + static void join1(Iterator parts) {} - static void join2(Iterator parts) {} + static void join2(Iterator parts) {} - static void join3(Iterator parts) {} + static void join3(Iterator parts) {} - static void join4(Iterator parts) {} + static void join4(Iterator parts) {} } diff --git a/checker/tests/nullness/ZeroVarargs.java b/checker/tests/nullness/ZeroVarargs.java index 42d8ea7d359..92654c7016a 100644 --- a/checker/tests/nullness/ZeroVarargs.java +++ b/checker/tests/nullness/ZeroVarargs.java @@ -1,7 +1,7 @@ // Test case for https://tinyurl.com/cfissue/5189 enum CFRepro { - EMPTY() {}; + EMPTY() {}; - CFRepro(String... args) {} + CFRepro(String... args) {} } diff --git a/checker/tests/nullness/flow/EisopIssue300.java b/checker/tests/nullness/flow/EisopIssue300.java index 097f5c870e1..36b661eff32 100644 --- a/checker/tests/nullness/flow/EisopIssue300.java +++ b/checker/tests/nullness/flow/EisopIssue300.java @@ -4,24 +4,24 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class EisopIssue300 { - class Bug { - void setFieldNull(Bug b) { - EisopIssue300.this.currentNode = null; - } + class Bug { + void setFieldNull(Bug b) { + EisopIssue300.this.currentNode = null; } + } - @Nullable Bug currentNode = new Bug(); + @Nullable Bug currentNode = new Bug(); - void test() { - if (currentNode == null) { - return; - } - currentNode.setFieldNull(currentNode); - // :: error: (dereference.of.nullable) - currentNode.toString(); + void test() { + if (currentNode == null) { + return; } + currentNode.setFieldNull(currentNode); + // :: error: (dereference.of.nullable) + currentNode.toString(); + } - public static void main(String[] args) { - new EisopIssue300().test(); - } + public static void main(String[] args) { + new EisopIssue300().test(); + } } diff --git a/checker/tests/nullness/flow/EisopIssue300B.java b/checker/tests/nullness/flow/EisopIssue300B.java index 704f8303437..d5c9aab6318 100644 --- a/checker/tests/nullness/flow/EisopIssue300B.java +++ b/checker/tests/nullness/flow/EisopIssue300B.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class EisopIssue300B { - @Nullable Object f = ""; + @Nullable Object f = ""; - void m(Object o) { - f = null; - } - - public static void main(String[] args) { - EisopIssue300B r = new EisopIssue300B(); - if (r.f == null) { - return; - } + void m(Object o) { + f = null; + } - r.m(r.f); - // :: error: (dereference.of.nullable) - r.f.toString(); + public static void main(String[] args) { + EisopIssue300B r = new EisopIssue300B(); + if (r.f == null) { + return; } + + r.m(r.f); + // :: error: (dereference.of.nullable) + r.f.toString(); + } } diff --git a/checker/tests/nullness/flow/EisopIssue300C.java b/checker/tests/nullness/flow/EisopIssue300C.java index 9c64deeabde..eaf66f9863c 100644 --- a/checker/tests/nullness/flow/EisopIssue300C.java +++ b/checker/tests/nullness/flow/EisopIssue300C.java @@ -6,28 +6,28 @@ import org.checkerframework.dataflow.qual.*; public final class EisopIssue300C { - @NotOnlyInitialized @Nullable EisopIssue300C f; + @NotOnlyInitialized @Nullable EisopIssue300C f; - EisopIssue300C() { - this.f = this; - } + EisopIssue300C() { + this.f = this; + } - void m2() { - f = null; - } + void m2() { + f = null; + } - @Pure - @Nullable EisopIssue300C getF() { - return f; - } + @Pure + @Nullable EisopIssue300C getF() { + return f; + } - public static void main(String[] args) { - EisopIssue300C r = new EisopIssue300C(); + public static void main(String[] args) { + EisopIssue300C r = new EisopIssue300C(); - if (r.getF() != null) { - r.getF().m2(); - // :: error: (dereference.of.nullable) - r.getF().toString(); - } + if (r.getF() != null) { + r.getF().m2(); + // :: error: (dereference.of.nullable) + r.getF().toString(); } + } } diff --git a/checker/tests/nullness/flow/EisopIssue553.java b/checker/tests/nullness/flow/EisopIssue553.java index acdf33bd716..24ce4ec4f20 100644 --- a/checker/tests/nullness/flow/EisopIssue553.java +++ b/checker/tests/nullness/flow/EisopIssue553.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class EisopIssue553 { - static @Nullable Object sfield = ""; - Object field = ""; + static @Nullable Object sfield = ""; + Object field = ""; - static void n(Object o) { - sfield = null; - } + static void n(Object o) { + sfield = null; + } - public static void main(String[] args) { - EisopIssue553 x = null; - Object o = x.sfield; - // :: error: (dereference.of.nullable) - o = x.field; - if (x.sfield == null) { - return; - } - x.n(x.sfield); - // :: error: (dereference.of.nullable) - x.sfield.toString(); + public static void main(String[] args) { + EisopIssue553 x = null; + Object o = x.sfield; + // :: error: (dereference.of.nullable) + o = x.field; + if (x.sfield == null) { + return; } + x.n(x.sfield); + // :: error: (dereference.of.nullable) + x.sfield.toString(); + } } diff --git a/checker/tests/nullness/flow/Issue1214.java b/checker/tests/nullness/flow/Issue1214.java index 81b461f5edd..1b12713a21f 100644 --- a/checker/tests/nullness/flow/Issue1214.java +++ b/checker/tests/nullness/flow/Issue1214.java @@ -2,125 +2,125 @@ // https://github.com/typetools/checker-framework/issues/1214 public class Issue1214 { - static String ng1() { - String s = "not null"; - try { - int data = 50 / 0; - } catch (Exception e) { - s = null; - } - // :: error: (return.type.incompatible) - return s; + static String ng1() { + String s = "not null"; + try { + int data = 50 / 0; + } catch (Exception e) { + s = null; } + // :: error: (return.type.incompatible) + return s; + } - static String ng2(int x) { - String s = "not null"; - try { - short data = (short) (50 / x); - } catch (Exception e) { - try { - s = null; - } catch (Exception ee) { - } - } - // :: error: (return.type.incompatible) - return s; + static String ng2(int x) { + String s = "not null"; + try { + short data = (short) (50 / x); + } catch (Exception e) { + try { + s = null; + } catch (Exception ee) { + } } + // :: error: (return.type.incompatible) + return s; + } - static String ng3() { - String s = "not null"; - try { - int data = 50 % 0; - } catch (Exception e) { - try { - // some statements... - } catch (Exception ee) { - } finally { - s = null; - } - } - // :: error: (return.type.incompatible) - return s; + static String ng3() { + String s = "not null"; + try { + int data = 50 % 0; + } catch (Exception e) { + try { + // some statements... + } catch (Exception ee) { + } finally { + s = null; + } } + // :: error: (return.type.incompatible) + return s; + } - static String ng4(int data) { - String s = "not null"; - try { - data /= 0; - } catch (Exception e) { - s = null; - } - // :: error: (return.type.incompatible) - return s; + static String ng4(int data) { + String s = "not null"; + try { + data /= 0; + } catch (Exception e) { + s = null; } + // :: error: (return.type.incompatible) + return s; + } - static String ng5(short data) { - String s = "not null"; - try { - data /= 0; - } catch (Exception e) { - try { - s = null; - } catch (Exception ee) { - } - } - // :: error: (return.type.incompatible) - return s; + static String ng5(short data) { + String s = "not null"; + try { + data /= 0; + } catch (Exception e) { + try { + s = null; + } catch (Exception ee) { + } } + // :: error: (return.type.incompatible) + return s; + } - static String ng6(int data) { - String s = "not null"; - try { - data %= 0; - } catch (Exception e) { - try { - // some statements... - } catch (Exception ee) { - } finally { - s = null; - } - } - // :: error: (return.type.incompatible) - return s; + static String ng6(int data) { + String s = "not null"; + try { + data %= 0; + } catch (Exception e) { + try { + // some statements... + } catch (Exception ee) { + } finally { + s = null; + } } + // :: error: (return.type.incompatible) + return s; + } - static String ok1() { - String s = "not null"; - try { - double data = 50 / 0.0; - } catch (Exception e) { - s = null; - } - return s; + static String ok1() { + String s = "not null"; + try { + double data = 50 / 0.0; + } catch (Exception e) { + s = null; } + return s; + } - static String ok2() { - String s = "not null"; - try { - double data = 50 % 0.0; - } catch (Exception e) { - s = null; - } - return s; + static String ok2() { + String s = "not null"; + try { + double data = 50 % 0.0; + } catch (Exception e) { + s = null; } + return s; + } - static String ok3(double data) { - String s = "not null"; - try { - data /= 0; - } catch (Exception e) { - s = null; - } - return s; + static String ok3(double data) { + String s = "not null"; + try { + data /= 0; + } catch (Exception e) { + s = null; } + return s; + } - static String ok4(float data) { - String s = "not null"; - try { - data %= 0; - } catch (Exception e) { - s = null; - } - return s; + static String ok4(float data) { + String s = "not null"; + try { + data %= 0; + } catch (Exception e) { + s = null; } + return s; + } } diff --git a/checker/tests/nullness/flow/Issue1345.java b/checker/tests/nullness/flow/Issue1345.java index 859f31e13bc..1dcb1548713 100644 --- a/checker/tests/nullness/flow/Issue1345.java +++ b/checker/tests/nullness/flow/Issue1345.java @@ -3,24 +3,23 @@ // @skip-test until the issue is resolved -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.checker.nullness.util.Opt; - import java.math.BigDecimal; import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.checker.nullness.util.Opt; public class Issue1345 { - @EnsuresNonNullIf(expression = "#1", result = true) - static boolean isNonNull(@Nullable Object o) { - return o != null; - } + @EnsuresNonNullIf(expression = "#1", result = true) + static boolean isNonNull(@Nullable Object o) { + return o != null; + } - void filterPresent_Optional(Stream<@Nullable BigDecimal> s) { - Stream<@NonNull BigDecimal> filtered = s.filter(Issue1345::isNonNull); - } + void filterPresent_Optional(Stream<@Nullable BigDecimal> s) { + Stream<@NonNull BigDecimal> filtered = s.filter(Issue1345::isNonNull); + } - void filterPresent_Opt(@Nullable Object p) { - @NonNull Object o = Opt.filter(p, Opt::isPresent); - } + void filterPresent_Opt(@Nullable Object p) { + @NonNull Object o = Opt.filter(p, Opt::isPresent); + } } diff --git a/checker/tests/nullness/flow/Issue1727.java b/checker/tests/nullness/flow/Issue1727.java index bf730923992..8a6dc26b744 100644 --- a/checker/tests/nullness/flow/Issue1727.java +++ b/checker/tests/nullness/flow/Issue1727.java @@ -7,25 +7,25 @@ class B {} public class Issue1727 { - private B foo() { - // Default type for local variable b is @UnknownInitialization @Nullable - B b; + private B foo() { + // Default type for local variable b is @UnknownInitialization @Nullable + B b; - while (true) { - B op = getB(); - if (op == null) { - b = new B(); - break; - } else { - b = op; - break; - } - } - - return b; + while (true) { + B op = getB(); + if (op == null) { + b = new B(); + break; + } else { + b = op; + break; + } } - private @Nullable B getB() { - return new B(); - } + return b; + } + + private @Nullable B getB() { + return new B(); + } } diff --git a/checker/tests/nullness/flow/Issue3249.java b/checker/tests/nullness/flow/Issue3249.java index d3ca0f5c81d..7baf899d4d9 100644 --- a/checker/tests/nullness/flow/Issue3249.java +++ b/checker/tests/nullness/flow/Issue3249.java @@ -3,62 +3,62 @@ public class Issue3249 { - private final double field; + private final double field; - Issue3249() { - double local; - while (true) { - local = 1; - break; - } - field = local; + Issue3249() { + double local; + while (true) { + local = 1; + break; } + field = local; + } - Issue3249(int x) { - double local; - while (!false) { - local = 1; - break; - } - field = local; + Issue3249(int x) { + double local; + while (!false) { + local = 1; + break; } + field = local; + } - Issue3249(float x) { - double local; - while (true || x > 0) { - local = 1; - break; - } - field = local; + Issue3249(float x) { + double local; + while (true || x > 0) { + local = 1; + break; } + field = local; + } - Issue3249(double x) { - double local; - while (!false && true && !false) { - local = 1; - break; - } - field = local; + Issue3249(double x) { + double local; + while (!false && true && !false) { + local = 1; + break; } + field = local; + } - // Case for while conditions that contain final variables, - // which are treated as constant. - Issue3249(String x) { - double local; - final int i = 1; - while ((i > 0) && !false) { - local = 1; - break; - } - field = local; + // Case for while conditions that contain final variables, + // which are treated as constant. + Issue3249(String x) { + double local; + final int i = 1; + while ((i > 0) && !false) { + local = 1; + break; } + field = local; + } - Issue3249(boolean x) { - double local; - while (6 > 4) { - local = 1; - break; - } - field = local; + Issue3249(boolean x) { + double local; + while (6 > 4) { + local = 1; + break; } + field = local; + } } diff --git a/checker/tests/nullness/flow/Issue3267.java b/checker/tests/nullness/flow/Issue3267.java index 57b392e9b8b..fac6ccd9f8e 100644 --- a/checker/tests/nullness/flow/Issue3267.java +++ b/checker/tests/nullness/flow/Issue3267.java @@ -4,36 +4,36 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3267 { - void m1(@Nullable Object obj) { - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void m1(@Nullable Object obj) { + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void m2(@Nullable Object obj) { - if (obj != null) {} - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void m2(@Nullable Object obj) { + if (obj != null) {} + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void m3(@Nullable Object obj) { - if (obj != null) { - } else { - } - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void m3(@Nullable Object obj) { + if (obj != null) { + } else { } + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); + } + } - void m4(@Nullable Object obj) { - boolean bool = obj != null; - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void m4(@Nullable Object obj) { + boolean bool = obj != null; + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } } diff --git a/checker/tests/nullness/flow/Issue3275.java b/checker/tests/nullness/flow/Issue3275.java index 05b615b457f..4809fd31fe2 100644 --- a/checker/tests/nullness/flow/Issue3275.java +++ b/checker/tests/nullness/flow/Issue3275.java @@ -5,202 +5,202 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3275 { - public @NonNull Object f = new Object(); - public boolean b = false; + public @NonNull Object f = new Object(); + public boolean b = false; - void return_n(@Nullable Object obj) { - if (obj != null) { - obj.toString(); - } + void return_n(@Nullable Object obj) { + if (obj != null) { + obj.toString(); } + } - void return_np(@Nullable Object obj) { - if ((obj != null)) { - obj.toString(); - } + void return_np(@Nullable Object obj) { + if ((obj != null)) { + obj.toString(); } + } - void return_en(@Nullable Object obj) { - if (!(obj == null)) { - obj.toString(); - } + void return_en(@Nullable Object obj) { + if (!(obj == null)) { + obj.toString(); } + } - void return_eet(@Nullable Object obj) { - if ((obj == null) == true) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_eet(@Nullable Object obj) { + if ((obj == null) == true) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void return_eef(@Nullable Object obj) { - if ((obj == null) == false) { - obj.toString(); - } + void return_eef(@Nullable Object obj) { + if ((obj == null) == false) { + obj.toString(); } + } - void return_eeb(@Nullable Object obj) { - if ((obj == null) == b) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_eeb(@Nullable Object obj) { + if ((obj == null) == b) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void return_ent(@Nullable Object obj) { - if ((obj == null) != true) { - obj.toString(); - } + void return_ent(@Nullable Object obj) { + if ((obj == null) != true) { + obj.toString(); } + } - void return_enf(@Nullable Object obj) { - if ((obj == null) != false) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_enf(@Nullable Object obj) { + if ((obj == null) != false) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void return_enb(@Nullable Object obj) { - if ((obj == null) != b) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_enb(@Nullable Object obj) { + if ((obj == null) != b) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void return_net(@Nullable Object obj) { - if ((obj != null) == true) { - obj.toString(); - } + void return_net(@Nullable Object obj) { + if ((obj != null) == true) { + obj.toString(); } + } - void return_nef(@Nullable Object obj) { - if ((obj != null) == false) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_nef(@Nullable Object obj) { + if ((obj != null) == false) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void return_neb(@Nullable Object obj) { - if ((obj != null) == b) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_neb(@Nullable Object obj) { + if ((obj != null) == b) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void return_nnt(@Nullable Object obj) { - if ((obj != null) != true) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_nnt(@Nullable Object obj) { + if ((obj != null) != true) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void return_nnf(@Nullable Object obj) { - if ((obj != null) != false) { - obj.toString(); - } + void return_nnf(@Nullable Object obj) { + if ((obj != null) != false) { + obj.toString(); } + } - void return_nnb(@Nullable Object obj) { - if ((obj != null) != b) { - // :: error: (dereference.of.nullable) - obj.toString(); - } + void return_nnb(@Nullable Object obj) { + if ((obj != null) != b) { + // :: error: (dereference.of.nullable) + obj.toString(); } + } - void assign_n(@Nullable Object obj) { - if (obj != null) { - f = obj; - } + void assign_n(@Nullable Object obj) { + if (obj != null) { + f = obj; } + } - void assign_np(@Nullable Object obj) { - if ((obj != null)) { - f = obj; - } + void assign_np(@Nullable Object obj) { + if ((obj != null)) { + f = obj; } + } - void assign_en(@Nullable Object obj) { - if (!(obj == null)) { - f = obj; - } + void assign_en(@Nullable Object obj) { + if (!(obj == null)) { + f = obj; } + } - void assign_eet(@Nullable Object obj) { - if ((obj == null) == true) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_eet(@Nullable Object obj) { + if ((obj == null) == true) { + // :: error: (assignment.type.incompatible) + f = obj; } + } - void assign_eef(@Nullable Object obj) { - if ((obj == null) == false) { - f = obj; - } + void assign_eef(@Nullable Object obj) { + if ((obj == null) == false) { + f = obj; } + } - void assign_eeb(@Nullable Object obj) { - if ((obj == null) == b) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_eeb(@Nullable Object obj) { + if ((obj == null) == b) { + // :: error: (assignment.type.incompatible) + f = obj; } + } - void assign_ent(@Nullable Object obj) { - if ((obj == null) != true) { - f = obj; - } + void assign_ent(@Nullable Object obj) { + if ((obj == null) != true) { + f = obj; } + } - void assign_enf(@Nullable Object obj) { - if ((obj == null) != false) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_enf(@Nullable Object obj) { + if ((obj == null) != false) { + // :: error: (assignment.type.incompatible) + f = obj; } + } - void assign_enb(@Nullable Object obj) { - if ((obj == null) != b) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_enb(@Nullable Object obj) { + if ((obj == null) != b) { + // :: error: (assignment.type.incompatible) + f = obj; } + } - void assign_net(@Nullable Object obj) { - if ((obj != null) == true) { - f = obj; - } + void assign_net(@Nullable Object obj) { + if ((obj != null) == true) { + f = obj; } + } - void assign_nef(@Nullable Object obj) { - if ((obj != null) == false) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_nef(@Nullable Object obj) { + if ((obj != null) == false) { + // :: error: (assignment.type.incompatible) + f = obj; } + } - void assign_neb(@Nullable Object obj) { - if ((obj != null) == b) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_neb(@Nullable Object obj) { + if ((obj != null) == b) { + // :: error: (assignment.type.incompatible) + f = obj; } + } - void assign_nnt(@Nullable Object obj) { - if ((obj != null) != true) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_nnt(@Nullable Object obj) { + if ((obj != null) != true) { + // :: error: (assignment.type.incompatible) + f = obj; } + } - void assign_nnf(@Nullable Object obj) { - if ((obj != null) != false) { - f = obj; - } + void assign_nnf(@Nullable Object obj) { + if ((obj != null) != false) { + f = obj; } + } - void assign_nnb(@Nullable Object obj) { - if ((obj != null) != b) { - // :: error: (assignment.type.incompatible) - f = obj; - } + void assign_nnb(@Nullable Object obj) { + if ((obj != null) != b) { + // :: error: (assignment.type.incompatible) + f = obj; } + } } diff --git a/checker/tests/nullness/flow/Issue341.java b/checker/tests/nullness/flow/Issue341.java index a22df59fe93..ce96a4d3acc 100644 --- a/checker/tests/nullness/flow/Issue341.java +++ b/checker/tests/nullness/flow/Issue341.java @@ -3,16 +3,16 @@ public class Issue341 { - static class Provider { - public final Object get = new Object(); - } + static class Provider { + public final Object get = new Object(); + } - Object execute(Provider p) { - final Object result; - try { - result = p.get; - } finally { - } - return result; + Object execute(Provider p) { + final Object result; + try { + result = p.get; + } finally { } + return result; + } } diff --git a/checker/tests/nullness/flow/Issue818.java b/checker/tests/nullness/flow/Issue818.java index 7005fa925ce..d0ac6e414bb 100644 --- a/checker/tests/nullness/flow/Issue818.java +++ b/checker/tests/nullness/flow/Issue818.java @@ -4,69 +4,69 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue818 { - public static @Nullable Object o = null; + public static @Nullable Object o = null; + void method() { + Issue818.o = new Object(); + o.toString(); + } + + void method2() { + o = new Object(); + Issue818.o.toString(); + } + + void method3() { + o = new Object(); + o.toString(); + } + + void method4() { + Issue818.o = new Object(); + Issue818.o.toString(); + } + + static class StaticInnerClass { void method() { - Issue818.o = new Object(); - o.toString(); + Issue818.o = new Object(); + o.toString(); } void method2() { - o = new Object(); - Issue818.o.toString(); + o = new Object(); + Issue818.o.toString(); } void method3() { - o = new Object(); - o.toString(); + o = new Object(); + o.toString(); } void method4() { - Issue818.o = new Object(); - Issue818.o.toString(); + Issue818.o = new Object(); + Issue818.o.toString(); } + } - static class StaticInnerClass { - void method() { - Issue818.o = new Object(); - o.toString(); - } - - void method2() { - o = new Object(); - Issue818.o.toString(); - } - - void method3() { - o = new Object(); - o.toString(); - } - - void method4() { - Issue818.o = new Object(); - Issue818.o.toString(); - } + class NonStaticInnerClass { + void method() { + Issue818.o = new Object(); + o.toString(); } - class NonStaticInnerClass { - void method() { - Issue818.o = new Object(); - o.toString(); - } - - void method2() { - o = new Object(); - Issue818.o.toString(); - } + void method2() { + o = new Object(); + Issue818.o.toString(); + } - void method3() { - o = new Object(); - o.toString(); - } + void method3() { + o = new Object(); + o.toString(); + } - void method4() { - Issue818.o = new Object(); - Issue818.o.toString(); - } + void method4() { + Issue818.o = new Object(); + Issue818.o.toString(); } + } } diff --git a/checker/tests/nullness/flow/MapGet.java b/checker/tests/nullness/flow/MapGet.java index 7d74f678ea3..f66def0ebe9 100644 --- a/checker/tests/nullness/flow/MapGet.java +++ b/checker/tests/nullness/flow/MapGet.java @@ -3,27 +3,26 @@ // @skip-test until the issue is fixed -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; public class MapGet { - private final Map labels = new HashMap<>(); + private final Map labels = new HashMap<>(); - void foo1(String v) { - labels.put(v, ""); - labels.get(v).toString(); - } + void foo1(String v) { + labels.put(v, ""); + labels.get(v).toString(); + } - @NonNull String foo2(String v) { - labels.put(v, ""); - return labels.get(v); - } + @NonNull String foo2(String v) { + labels.put(v, ""); + return labels.get(v); + } - @EnsuresNonNull("labels.get(#1)") - void foo3(String v) { - labels.put(v, ""); - } + @EnsuresNonNull("labels.get(#1)") + void foo3(String v) { + labels.put(v, ""); + } } diff --git a/checker/tests/nullness/flow/PathJoins.java b/checker/tests/nullness/flow/PathJoins.java index 305a7faa028..7e8c88a6b2b 100644 --- a/checker/tests/nullness/flow/PathJoins.java +++ b/checker/tests/nullness/flow/PathJoins.java @@ -2,16 +2,16 @@ public class PathJoins { - public void testJoiningMultipleBranches() { - Object intersect = null; - if (false) { - return; - } else if (intersect == null) { - return; - } else { - intersect = "m"; - } - - intersect.toString(); + public void testJoiningMultipleBranches() { + Object intersect = null; + if (false) { + return; + } else if (intersect == null) { + return; + } else { + intersect = "m"; } + + intersect.toString(); + } } diff --git a/checker/tests/nullness/flow/PureAndFlow.java b/checker/tests/nullness/flow/PureAndFlow.java index 88d2d99a414..7ff0e1cf243 100644 --- a/checker/tests/nullness/flow/PureAndFlow.java +++ b/checker/tests/nullness/flow/PureAndFlow.java @@ -2,59 +2,59 @@ public abstract class PureAndFlow { - @Nullable String s1; - @Nullable String s2; + @Nullable String s1; + @Nullable String s2; - void nonpure(String s1) {} + void nonpure(String s1) {} - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - void pure(String s2) {} + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + void pure(String s2) {} - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.void.method) - void det(String s3) {} + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.void.method) + void det(String s3) {} - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - abstract void abstractpure(String s4); + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + abstract void abstractpure(String s4); - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.void.method) - abstract void abstractdet(String s4); - - void withNonRow() { - if (s2 != null) { - nonpure("m"); - // :: error: (argument.type.incompatible) - pure(s2); - } + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.void.method) + abstract void abstractdet(String s4); + + void withNonRow() { + if (s2 != null) { + nonpure("m"); + // :: error: (argument.type.incompatible) + pure(s2); } + } - void withPure() { - if (s2 != null) { - pure("m"); - pure(s2); - } + void withPure() { + if (s2 != null) { + pure("m"); + pure(s2); } + } - interface IFace { - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - void ifacepure(String s); + interface IFace { + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + void ifacepure(String s); - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.void.method) - void ifacedet(String s); - } + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.void.method) + void ifacedet(String s); + } - class Cons { - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.constructor) - Cons(String s) {} + class Cons { + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.constructor) + Cons(String s) {} - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.constructor) - Cons(int i) {} - } + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.constructor) + Cons(int i) {} + } } diff --git a/checker/tests/nullness/flow/PurityError.java b/checker/tests/nullness/flow/PurityError.java index 746dfbd6316..9e52cdc9c6a 100644 --- a/checker/tests/nullness/flow/PurityError.java +++ b/checker/tests/nullness/flow/PurityError.java @@ -2,13 +2,13 @@ import org.checkerframework.dataflow.qual.SideEffectFree; public class PurityError { - @SideEffectFree - void method() {} + @SideEffectFree + void method() {} - @Pure - Object method2() { - // :: error: (purity.not.deterministic.call) - method(); - return ""; - } + @Pure + Object method2() { + // :: error: (purity.not.deterministic.call) + method(); + return ""; + } } diff --git a/checker/tests/nullness/flow/TestNullnessUtil.java b/checker/tests/nullness/flow/TestNullnessUtil.java index aec51c8426d..92146f6fe83 100644 --- a/checker/tests/nullness/flow/TestNullnessUtil.java +++ b/checker/tests/nullness/flow/TestNullnessUtil.java @@ -3,87 +3,87 @@ /** Test class org.checkerframework.checker.nullness.util.NullnessUtil. */ public class TestNullnessUtil { - void testRef1(@Nullable Object o) { - // one way to use as a cast: - @NonNull Object l1 = NullnessUtil.castNonNull(o); - } + void testRef1(@Nullable Object o) { + // one way to use as a cast: + @NonNull Object l1 = NullnessUtil.castNonNull(o); + } - void testRef2(@Nullable Object o) { - // another way to use as a cast: - NullnessUtil.castNonNull(o).toString(); - } + void testRef2(@Nullable Object o) { + // another way to use as a cast: + NullnessUtil.castNonNull(o).toString(); + } - void testRef3(@Nullable Object o) { - // use as statement: - NullnessUtil.castNonNull(o); - o.toString(); - } + void testRef3(@Nullable Object o) { + // use as statement: + NullnessUtil.castNonNull(o); + o.toString(); + } - void testArr1(@Nullable Object @NonNull [] a) { - // one way to use as a cast: - @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); - // Careful, the non-deep version only casts the main modifier. - // :: error: (assignment.type.incompatible) - @NonNull Object[] l2b = NullnessUtil.castNonNull(a); - // OK - @Nullable Object[] l2c = NullnessUtil.castNonNull(a); - } + void testArr1(@Nullable Object @NonNull [] a) { + // one way to use as a cast: + @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); + // Careful, the non-deep version only casts the main modifier. + // :: error: (assignment.type.incompatible) + @NonNull Object[] l2b = NullnessUtil.castNonNull(a); + // OK + @Nullable Object[] l2c = NullnessUtil.castNonNull(a); + } - void testArr1b(@Nullable Object @Nullable [] a) { - // one way to use as a cast: - @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); - // Careful, the non-deep version only casts the main modifier. - // :: error: (assignment.type.incompatible) - @NonNull Object[] l2b = NullnessUtil.castNonNull(a); - // OK - @Nullable Object[] l2c = NullnessUtil.castNonNull(a); - } + void testArr1b(@Nullable Object @Nullable [] a) { + // one way to use as a cast: + @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); + // Careful, the non-deep version only casts the main modifier. + // :: error: (assignment.type.incompatible) + @NonNull Object[] l2b = NullnessUtil.castNonNull(a); + // OK + @Nullable Object[] l2c = NullnessUtil.castNonNull(a); + } - void testArr2(@Nullable Object @NonNull [] a) { - // another way to use as a cast: - NullnessUtil.castNonNullDeep(a)[0].toString(); - } + void testArr2(@Nullable Object @NonNull [] a) { + // another way to use as a cast: + NullnessUtil.castNonNullDeep(a)[0].toString(); + } - void testArr3(@Nullable Object @NonNull [] a) { - // use as statement: - NullnessUtil.castNonNullDeep(a); - a.toString(); - // TODO: @EnsuresNonNull cannot express that - // all the array components are non-null. - // a[0].toString(); - } + void testArr3(@Nullable Object @NonNull [] a) { + // use as statement: + NullnessUtil.castNonNullDeep(a); + a.toString(); + // TODO: @EnsuresNonNull cannot express that + // all the array components are non-null. + // a[0].toString(); + } - /* - // TODO: flow does not propagate component types. - void testArr3(@Nullable Object @NonNull [] a) { - // one way to use as a statement: - NullnessUtil.castNonNull(a); - a[0].toString(); - } - */ + /* + // TODO: flow does not propagate component types. + void testArr3(@Nullable Object @NonNull [] a) { + // one way to use as a statement: + NullnessUtil.castNonNull(a); + a[0].toString(); + } + */ - void testMultiArr1(@Nullable Object @NonNull [] @Nullable [] a) { - // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) - @NonNull Object l3 = a[0][0]; - // one way to use as a cast: - @NonNull Object[][] l4 = NullnessUtil.castNonNullDeep(a); - } + void testMultiArr1(@Nullable Object @NonNull [] @Nullable [] a) { + // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) + @NonNull Object l3 = a[0][0]; + // one way to use as a cast: + @NonNull Object[][] l4 = NullnessUtil.castNonNullDeep(a); + } - void testMultiArr2(@Nullable Object @NonNull [] @Nullable [] a) { - // another way to use as a cast: - NullnessUtil.castNonNullDeep(a)[0][0].toString(); - } + void testMultiArr2(@Nullable Object @NonNull [] @Nullable [] a) { + // another way to use as a cast: + NullnessUtil.castNonNullDeep(a)[0][0].toString(); + } - void testMultiArr3(@Nullable Object @Nullable [] @Nullable [] @Nullable [] a) { - // :: error: (dereference.of.nullable) :: error: (accessing.nullable) - a[0][0][0].toString(); - // another way to use as a cast: - NullnessUtil.castNonNullDeep(a)[0][0][0].toString(); - } + void testMultiArr3(@Nullable Object @Nullable [] @Nullable [] @Nullable [] a) { + // :: error: (dereference.of.nullable) :: error: (accessing.nullable) + a[0][0][0].toString(); + // another way to use as a cast: + NullnessUtil.castNonNullDeep(a)[0][0][0].toString(); + } - public static void main(String[] args) { - Object[] @Nullable [] err = new Object[10][10]; - Object[][] e1 = NullnessUtil.castNonNullDeep(err); - e1[0][0].toString(); - } + public static void main(String[] args) { + Object[] @Nullable [] err = new Object[10][10]; + Object[][] e1 = NullnessUtil.castNonNullDeep(err); + e1[0][0].toString(); + } } diff --git a/checker/tests/nullness/flow/TestOpt.java b/checker/tests/nullness/flow/TestOpt.java index f2407e1dad1..2b66952f205 100644 --- a/checker/tests/nullness/flow/TestOpt.java +++ b/checker/tests/nullness/flow/TestOpt.java @@ -3,73 +3,73 @@ /** Test class org.checkerframework.checker.nullness.util.Opt. */ public class TestOpt { - void foo1(@Nullable Object p) { - if (Opt.isPresent(p)) { - p.toString(); // Flow refinement - } + void foo1(@Nullable Object p) { + if (Opt.isPresent(p)) { + p.toString(); // Flow refinement } + } - void foo1b(@Nullable Object p) { - if (!Opt.isPresent(p)) { - // :: error: (dereference.of.nullable) - p.toString(); - } + void foo1b(@Nullable Object p) { + if (!Opt.isPresent(p)) { + // :: error: (dereference.of.nullable) + p.toString(); } + } - void foo2(@Nullable Object p) { - Opt.ifPresent(p, x -> System.out.println("Got: " + x)); - } + void foo2(@Nullable Object p) { + Opt.ifPresent(p, x -> System.out.println("Got: " + x)); + } - void foo2b(@Nullable Object p) { - Opt.ifPresent(p, x -> System.out.println("Got: " + x.toString())); - } + void foo2b(@Nullable Object p) { + Opt.ifPresent(p, x -> System.out.println("Got: " + x.toString())); + } - void foo3(@Nullable Object p) { - Object o = Opt.filter(p, x -> x.hashCode() > 10); - } + void foo3(@Nullable Object p) { + Object o = Opt.filter(p, x -> x.hashCode() > 10); + } - void foo4(@Nullable Object p) { - String s = Opt.map(p, x -> x.toString()); - } + void foo4(@Nullable Object p) { + String s = Opt.map(p, x -> x.toString()); + } - void foo4b(@Nullable Object p) { - // :: error: (argument.type.incompatible) - String s = Opt.map(p, null); - } + void foo4b(@Nullable Object p) { + // :: error: (argument.type.incompatible) + String s = Opt.map(p, null); + } - void foo5(@Nullable Object p) { - @NonNull Object o = Opt.orElse(p, new Object()); - } + void foo5(@Nullable Object p) { + @NonNull Object o = Opt.orElse(p, new Object()); + } - void foo5b(@Nullable Object p) { - // :: error: (argument.type.incompatible) - @NonNull Object o = Opt.orElse(p, null); - } + void foo5b(@Nullable Object p) { + // :: error: (argument.type.incompatible) + @NonNull Object o = Opt.orElse(p, null); + } - void foo6(@Nullable Object p) { - @NonNull Object o = Opt.orElseGet(p, () -> new Object()); - } + void foo6(@Nullable Object p) { + @NonNull Object o = Opt.orElseGet(p, () -> new Object()); + } - void foo6b(@Nullable Object p) { - // :: error: (return.type.incompatible) - @NonNull Object o = Opt.orElseGet(p, () -> null); - } + void foo6b(@Nullable Object p) { + // :: error: (return.type.incompatible) + @NonNull Object o = Opt.orElseGet(p, () -> null); + } - void foo7(Object p) { - try { - @NonNull Object o = Opt.orElseThrow(p, () -> new Throwable()); - } catch (Throwable t) { - // p was null - } + void foo7(Object p) { + try { + @NonNull Object o = Opt.orElseThrow(p, () -> new Throwable()); + } catch (Throwable t) { + // p was null } + } - void foo7b(@Nullable Object p) { - try { - // :: error: (assignment.type.incompatible) :: error: (type.argument.type.incompatible) - // :: error: (return.type.incompatible) - @NonNull Object o = Opt.orElseThrow(p, () -> null); - } catch (Throwable t) { - // p was null - } + void foo7b(@Nullable Object p) { + try { + // :: error: (assignment.type.incompatible) :: error: (type.argument.type.incompatible) + // :: error: (return.type.incompatible) + @NonNull Object o = Opt.orElseThrow(p, () -> null); + } catch (Throwable t) { + // p was null } + } } diff --git a/checker/tests/nullness/generics/AnnotatedGenerics3.java b/checker/tests/nullness/generics/AnnotatedGenerics3.java index 31490a8d7f9..97c3829f634 100644 --- a/checker/tests/nullness/generics/AnnotatedGenerics3.java +++ b/checker/tests/nullness/generics/AnnotatedGenerics3.java @@ -1,42 +1,42 @@ import org.checkerframework.checker.nullness.qual.*; public class AnnotatedGenerics3 { - class Cell { - T f; - - Cell(T i) { - f = i; - } - - void setNull(Cell<@Nullable T> p) { - p.f = null; - } - - void indirect(Cell p) { - // :: error: (argument.type.incompatible) - setNull(p); - } - - void setField(@Nullable T p) { - // :: error: (assignment.type.incompatible) - this.f = p; - } - } + class Cell { + T f; - void run() { - Cell<@NonNull Object> c = new Cell<>(new Object()); - // :: error: (argument.type.incompatible) - c.setNull(c); - c.f.hashCode(); + Cell(T i) { + f = i; + } - c.indirect(c); - c.f.hashCode(); + void setNull(Cell<@Nullable T> p) { + p.f = null; + } - c.setField(null); - c.f.hashCode(); + void indirect(Cell p) { + // :: error: (argument.type.incompatible) + setNull(p); } - public static void main(String[] args) { - new AnnotatedGenerics3().run(); + void setField(@Nullable T p) { + // :: error: (assignment.type.incompatible) + this.f = p; } + } + + void run() { + Cell<@NonNull Object> c = new Cell<>(new Object()); + // :: error: (argument.type.incompatible) + c.setNull(c); + c.f.hashCode(); + + c.indirect(c); + c.f.hashCode(); + + c.setField(null); + c.f.hashCode(); + } + + public static void main(String[] args) { + new AnnotatedGenerics3().run(); + } } diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams.java b/checker/tests/nullness/generics/AnnotatedTypeParams.java index eaa84903061..fa413d9e56b 100644 --- a/checker/tests/nullness/generics/AnnotatedTypeParams.java +++ b/checker/tests/nullness/generics/AnnotatedTypeParams.java @@ -1,18 +1,18 @@ import org.checkerframework.checker.nullness.qual.*; class MyClass<@Nullable T> { - T get() { - throw new RuntimeException(); - } + T get() { + throw new RuntimeException(); + } - void testPositive() { - MyClass<@Nullable String> l = new MyClass<>(); - // :: error: (dereference.of.nullable) - l.get().toString(); - } + void testPositive() { + MyClass<@Nullable String> l = new MyClass<>(); + // :: error: (dereference.of.nullable) + l.get().toString(); + } - void testInvalidParam() { - // :: error: (type.argument.type.incompatible) - MyClass<@NonNull String> l; - } + void testInvalidParam() { + // :: error: (type.argument.type.incompatible) + MyClass<@NonNull String> l; + } } diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams2.java b/checker/tests/nullness/generics/AnnotatedTypeParams2.java index 99c89df3ba0..94f19a60187 100644 --- a/checker/tests/nullness/generics/AnnotatedTypeParams2.java +++ b/checker/tests/nullness/generics/AnnotatedTypeParams2.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.nullness.qual.*; class SomeClass<@Nullable T> { - T get() { - throw new RuntimeException(); - } + T get() { + throw new RuntimeException(); + } } public class AnnotatedTypeParams2 { - void testPositive() { - SomeClass<@Nullable String> l = new SomeClass<>(); - // :: error: (dereference.of.nullable) - l.get().toString(); - } + void testPositive() { + SomeClass<@Nullable String> l = new SomeClass<>(); + // :: error: (dereference.of.nullable) + l.get().toString(); + } - void testInvalidParam() { - // :: error: (type.argument.type.incompatible) - SomeClass<@NonNull String> l; - } + void testInvalidParam() { + // :: error: (type.argument.type.incompatible) + SomeClass<@NonNull String> l; + } } diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams4.java b/checker/tests/nullness/generics/AnnotatedTypeParams4.java index ee3c93ce00b..e53ae36086b 100644 --- a/checker/tests/nullness/generics/AnnotatedTypeParams4.java +++ b/checker/tests/nullness/generics/AnnotatedTypeParams4.java @@ -2,76 +2,76 @@ public class AnnotatedTypeParams4 { - class Test1 { - CONTENT a; + class Test1 { + CONTENT a; - // To prevent the warning about un-initialized fields. - Test1(CONTENT p1) { - a = p1; - } - - public CONTENT get() { - return a; - } + // To prevent the warning about un-initialized fields. + Test1(CONTENT p1) { + a = p1; + } - @org.checkerframework.dataflow.qual.Pure - public CONTENT get2() { - return a; - } + public CONTENT get() { + return a; } - class Test2 { - @NonNull CONTENT a; + @org.checkerframework.dataflow.qual.Pure + public CONTENT get2() { + return a; + } + } - // To prevent the warning about un-initialized fields. - Test2(@NonNull CONTENT p1) { - a = p1; - } + class Test2 { + @NonNull CONTENT a; - public @NonNull CONTENT get() { - return a; - } + // To prevent the warning about un-initialized fields. + Test2(@NonNull CONTENT p1) { + a = p1; + } - @org.checkerframework.dataflow.qual.Pure - public @NonNull CONTENT get2() { - return a; - } + public @NonNull CONTENT get() { + return a; } - /* - class Test3 { - // Change @Pure to be allowed on fields, or add some other anno. - @Pure CONTENT f; - // Strangely this assignment succeeded - Test3(CONTENT p1) { f = p1; } - // But this assignment failed, because the @Pure caused the - // other annotations to be erased. - public void get3(CONTENT p) { - f = p; - } + @org.checkerframework.dataflow.qual.Pure + public @NonNull CONTENT get2() { + return a; } - */ + } - class Test4 { - private MyPair userObject; + /* + class Test3 { + // Change @Pure to be allowed on fields, or add some other anno. + @Pure CONTENT f; + // Strangely this assignment succeeded + Test3(CONTENT p1) { f = p1; } + // But this assignment failed, because the @Pure caused the + // other annotations to be erased. + public void get3(CONTENT p) { + f = p; + } + } + */ - Test4(MyPair p) { - userObject = p; - } + class Test4 { + private MyPair userObject; - @org.checkerframework.dataflow.qual.Pure - public CONTENT getUserLeft() { - return userObject.a; - } + Test4(MyPair p) { + userObject = p; + } + + @org.checkerframework.dataflow.qual.Pure + public CONTENT getUserLeft() { + return userObject.a; + } - public class MyPair { - public T1 a; - public T2 b; + public class MyPair { + public T1 a; + public T2 b; - public MyPair(T1 a, T2 b) { - this.a = a; - this.b = b; - } - } + public MyPair(T1 a, T2 b) { + this.a = a; + this.b = b; + } } + } } diff --git a/checker/tests/nullness/generics/AnonymousClass.java b/checker/tests/nullness/generics/AnonymousClass.java index 9fd3545eda8..a22112b2d14 100644 --- a/checker/tests/nullness/generics/AnonymousClass.java +++ b/checker/tests/nullness/generics/AnonymousClass.java @@ -2,16 +2,16 @@ public class AnonymousClass { - class Bound {} + class Bound {} - void test() { - // :: error: (type.argument.type.incompatible) - new Bound<@Nullable String>() {}; - } + void test() { + // :: error: (type.argument.type.incompatible) + new Bound<@Nullable String>() {}; + } - // The dummy parameter tests ParamApplier - void test(Object dummy) { - // :: error: (type.argument.type.incompatible) - new Bound<@Nullable String>() {}; - } + // The dummy parameter tests ParamApplier + void test(Object dummy) { + // :: error: (type.argument.type.incompatible) + new Bound<@Nullable String>() {}; + } } diff --git a/checker/tests/nullness/generics/BoundedWildcardTest.java b/checker/tests/nullness/generics/BoundedWildcardTest.java index 71876cede1e..51b015837a7 100644 --- a/checker/tests/nullness/generics/BoundedWildcardTest.java +++ b/checker/tests/nullness/generics/BoundedWildcardTest.java @@ -1,45 +1,44 @@ // Test case from // http://stackoverflow.com/questions/38339332/in-a-bounded-wildcard-where-does-the-annotation-belong -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; class Styleable {} public class BoundedWildcardTest { - private void locChildren(Styleable c) { - // ... - } + private void locChildren(Styleable c) { + // ... + } - public void initLoc(List s) { - for (Styleable c : s) { - locChildren(c); - } + public void initLoc(List s) { + for (Styleable c : s) { + locChildren(c); } + } - // :: error: (bound.type.incompatible) - public void initLoc1(@Nullable List<@Nullable ? extends Styleable> s) { - // :: error: (iterating.over.nullable) - for (Styleable c : s) { - locChildren(c); - } + // :: error: (bound.type.incompatible) + public void initLoc1(@Nullable List<@Nullable ? extends Styleable> s) { + // :: error: (iterating.over.nullable) + for (Styleable c : s) { + locChildren(c); } + } - public void initLoc2(@Nullable List<@Nullable ? extends @Nullable Styleable> s) { - // :: error: (iterating.over.nullable) - for (Styleable c : s) { - // :: error: argument.type.incompatible - locChildren(c); - } + public void initLoc2(@Nullable List<@Nullable ? extends @Nullable Styleable> s) { + // :: error: (iterating.over.nullable) + for (Styleable c : s) { + // :: error: argument.type.incompatible + locChildren(c); } + } - public void initLoc3(@Nullable List s) { - // :: error: (iterating.over.nullable) - for (Styleable c : s) { - // :: error: argument.type.incompatible - locChildren(c); - } + public void initLoc3(@Nullable List s) { + // :: error: (iterating.over.nullable) + for (Styleable c : s) { + // :: error: argument.type.incompatible + locChildren(c); } + } } diff --git a/checker/tests/nullness/generics/BoxingGenerics.java b/checker/tests/nullness/generics/BoxingGenerics.java index 13fb50f2bb9..a97441a4975 100644 --- a/checker/tests/nullness/generics/BoxingGenerics.java +++ b/checker/tests/nullness/generics/BoxingGenerics.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.nullness.qual.*; public class BoxingGenerics { - static class X { - public static X foo(T x) { - return new X<>(); - } - - public void bar(X x) {} + static class X { + public static X foo(T x) { + return new X<>(); } - public void getText() { - X var = new X<>(); - X.foo(Integer.valueOf(5)).bar(var); - X.foo(5).bar(var); - } + public void bar(X x) {} + } + + public void getText() { + X var = new X<>(); + X.foo(Integer.valueOf(5)).bar(var); + X.foo(5).bar(var); + } } diff --git a/checker/tests/nullness/generics/CapturedWildcards.java b/checker/tests/nullness/generics/CapturedWildcards.java index 96984bc06f2..f193812ccf2 100644 --- a/checker/tests/nullness/generics/CapturedWildcards.java +++ b/checker/tests/nullness/generics/CapturedWildcards.java @@ -1,18 +1,17 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class CapturedWildcards { - abstract static class MyClass { - abstract boolean contains(MyClass other); - } + abstract static class MyClass { + abstract boolean contains(MyClass other); + } - public boolean pass(List list, MyClass other) { - return list.stream().anyMatch(je -> je != null && je.contains(other)); - } + public boolean pass(List list, MyClass other) { + return list.stream().anyMatch(je -> je != null && je.contains(other)); + } - public boolean fail(List list, MyClass other) { - // :: error: (dereference.of.nullable) - return list.stream().anyMatch(je -> je.contains(other)); - } + public boolean fail(List list, MyClass other) { + // :: error: (dereference.of.nullable) + return list.stream().anyMatch(je -> je.contains(other)); + } } diff --git a/checker/tests/nullness/generics/CollectionsAnnotations.java b/checker/tests/nullness/generics/CollectionsAnnotations.java index 60a4d9b172e..c583d81b869 100644 --- a/checker/tests/nullness/generics/CollectionsAnnotations.java +++ b/checker/tests/nullness/generics/CollectionsAnnotations.java @@ -2,67 +2,67 @@ // This is how I propose the Collection interface be annotated: interface Collection1 { - public void add(E elt); + public void add(E elt); } class PriorityQueue1 implements Collection1 { - public void add(E elt) { - // just to dereference elt - elt.hashCode(); - } + public void add(E elt) { + // just to dereference elt + elt.hashCode(); + } } class PriorityQueue2 implements Collection1 { - public void add(E elt) { - // just to dereference elt - elt.hashCode(); - } + public void add(E elt) { + // just to dereference elt + elt.hashCode(); + } } // This is how the Collection interface is currently annotated interface Collection2 { - public void add(E elt); + public void add(E elt); } class PriorityQueue3 implements Collection2 { - public void add(E elt) { - // just to dereference elt - elt.hashCode(); - } + public void add(E elt) { + // just to dereference elt + elt.hashCode(); + } } class Methods { - static void addNull1(Collection1 l) { - // Allowed, because upper bound of Collection1 is Nullable. - // :: warning: [unchecked] unchecked call to add(E) as a member of the raw type Collection1 - l.add(null); - } + static void addNull1(Collection1 l) { + // Allowed, because upper bound of Collection1 is Nullable. + // :: warning: [unchecked] unchecked call to add(E) as a member of the raw type Collection1 + l.add(null); + } - static void bad1() { - addNull1(new PriorityQueue1()); - } + static void bad1() { + addNull1(new PriorityQueue1()); + } - // If the types are parameterized (as they should be) - static <@Nullable E extends @Nullable Object> void addNull2(Collection1 l) { - l.add(null); - } + // If the types are parameterized (as they should be) + static <@Nullable E extends @Nullable Object> void addNull2(Collection1 l) { + l.add(null); + } - static void bad2() { - // :: error: (type.argument.type.incompatible) - addNull2(new PriorityQueue1<@NonNull Object>()); - } - - public static void main(String[] args) { - bad2(); - } + static void bad2() { + // :: error: (type.argument.type.incompatible) + addNull2(new PriorityQueue1<@NonNull Object>()); + } - static void bad3() { - // :: error: (type.argument.type.incompatible) - addNull2(new PriorityQueue2<@NonNull Object>()); - } + public static void main(String[] args) { + bad2(); + } + static void bad3() { // :: error: (type.argument.type.incompatible) - static <@Nullable E> void addNull3(Collection2 l) { - l.add(null); - } + addNull2(new PriorityQueue2<@NonNull Object>()); + } + + // :: error: (type.argument.type.incompatible) + static <@Nullable E> void addNull3(Collection2 l) { + l.add(null); + } } diff --git a/checker/tests/nullness/generics/CollectionsAnnotationsMin.java b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java index 67120e3985b..5f328cd0b5c 100644 --- a/checker/tests/nullness/generics/CollectionsAnnotationsMin.java +++ b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java @@ -1,58 +1,58 @@ import org.checkerframework.checker.nullness.qual.*; public class CollectionsAnnotationsMin { - static class Collection1 { - public void add(E elt) { - // :: error: (dereference.of.nullable) - elt.hashCode(); - } + static class Collection1 { + public void add(E elt) { + // :: error: (dereference.of.nullable) + elt.hashCode(); } + } - static class PriorityQueue1 extends Collection1 { - public void add(E elt) { - // dereference allowed here - elt.hashCode(); - } + static class PriorityQueue1 extends Collection1 { + public void add(E elt) { + // dereference allowed here + elt.hashCode(); } + } - // This is allowed, as "null" cannot be added to f1 - static Collection1 f1 = new PriorityQueue1<@NonNull Object>(); + // This is allowed, as "null" cannot be added to f1 + static Collection1 f1 = new PriorityQueue1<@NonNull Object>(); - // :: error: (assignment.type.incompatible) - static Collection1<@Nullable Object> f2 = new PriorityQueue1<@NonNull Object>(); + // :: error: (assignment.type.incompatible) + static Collection1<@Nullable Object> f2 = new PriorityQueue1<@NonNull Object>(); - static void addNull1(Collection1<@Nullable Object> l) { - l.add(null); - } + static void addNull1(Collection1<@Nullable Object> l) { + l.add(null); + } - // The upper bound on E is implicitly from Collection1 - static void addNull2(Collection1 l) { - // :: error: (argument.type.incompatible) - l.add(null); - } + // The upper bound on E is implicitly from Collection1 + static void addNull2(Collection1 l) { + // :: error: (argument.type.incompatible) + l.add(null); + } - // The upper bound on E is implicitly from Collection1 - static E addNull2b(Collection1 l, E p) { - // :: error: (argument.type.incompatible) - l.add(null); - return p; - } + // The upper bound on E is implicitly from Collection1 + static E addNull2b(Collection1 l, E p) { + // :: error: (argument.type.incompatible) + l.add(null); + return p; + } - static <@Nullable E extends @Nullable Object> void addNull3(Collection1 l) { - l.add(null); - } + static <@Nullable E extends @Nullable Object> void addNull3(Collection1 l) { + l.add(null); + } - static void bad() { - // :: error: (argument.type.incompatible) - addNull1(new PriorityQueue1<@NonNull Object>()); + static void bad() { + // :: error: (argument.type.incompatible) + addNull1(new PriorityQueue1<@NonNull Object>()); - addNull2(new PriorityQueue1<@NonNull Object>()); - addNull2b(new PriorityQueue1<@NonNull Object>(), new Object()); + addNull2(new PriorityQueue1<@NonNull Object>()); + addNull2b(new PriorityQueue1<@NonNull Object>(), new Object()); - // :: error: (type.argument.type.incompatible) - addNull3(new PriorityQueue1<@NonNull Object>()); + // :: error: (type.argument.type.incompatible) + addNull3(new PriorityQueue1<@NonNull Object>()); - // :: error: (argument.type.incompatible) - f1.add(null); - } + // :: error: (argument.type.incompatible) + f1.add(null); + } } diff --git a/checker/tests/nullness/generics/GenericArgs.java b/checker/tests/nullness/generics/GenericArgs.java index eb2e5428151..f116774fcf3 100644 --- a/checker/tests/nullness/generics/GenericArgs.java +++ b/checker/tests/nullness/generics/GenericArgs.java @@ -1,61 +1,60 @@ -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.dataflow.qual.*; - import java.io.*; import java.util.Comparator; import java.util.HashSet; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.dataflow.qual.*; @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class GenericArgs { - public @NonNull Set<@NonNull String> strings = new HashSet<>(); - - void test() { - @NonNull HashSet<@NonNull String> s = new HashSet<>(); - - strings.addAll(s); - strings.add("foo"); - } - - static class X<@NonNull T extends @NonNull Object> { - T value() { - // :: error: (return.type.incompatible) - return null; - } - } - - public static void test2() { - // :: error: (type.argument.type.incompatible) - Object o = new X().value(); - } - - static <@NonNull Z extends @NonNull Object> void test3(Z z) {} + public @NonNull Set<@NonNull String> strings = new HashSet<>(); - void test4() { - // :: error: (type.argument.type.incompatible) - GenericArgs.<@Nullable Object>test3(null); - // :: error: (argument.type.incompatible) - GenericArgs.<@NonNull Object>test3(null); - } + void test() { + @NonNull HashSet<@NonNull String> s = new HashSet<>(); - static class GenericConstructor { - <@NonNull T extends @NonNull Object> GenericConstructor(T t) {} - } + strings.addAll(s); + strings.add("foo"); + } - void test5() { - // :: error: (argument.type.incompatible) - new <@NonNull String>GenericConstructor(null); + static class X<@NonNull T extends @NonNull Object> { + T value() { + // :: error: (return.type.incompatible) + return null; } - - void testRecursiveDeclarations() { - class MyComparator<@NonNull T extends @NonNull Comparable> - implements Comparator { - @Pure - public int compare(T[] a, T[] b) { - return 0; - } - } - Comparator<@NonNull String @NonNull []> temp = new MyComparator<@NonNull String>(); + } + + public static void test2() { + // :: error: (type.argument.type.incompatible) + Object o = new X().value(); + } + + static <@NonNull Z extends @NonNull Object> void test3(Z z) {} + + void test4() { + // :: error: (type.argument.type.incompatible) + GenericArgs.<@Nullable Object>test3(null); + // :: error: (argument.type.incompatible) + GenericArgs.<@NonNull Object>test3(null); + } + + static class GenericConstructor { + <@NonNull T extends @NonNull Object> GenericConstructor(T t) {} + } + + void test5() { + // :: error: (argument.type.incompatible) + new <@NonNull String>GenericConstructor(null); + } + + void testRecursiveDeclarations() { + class MyComparator<@NonNull T extends @NonNull Comparable> + implements Comparator { + @Pure + public int compare(T[] a, T[] b) { + return 0; + } } + Comparator<@NonNull String @NonNull []> temp = new MyComparator<@NonNull String>(); + } } diff --git a/checker/tests/nullness/generics/GenericArgs2.java b/checker/tests/nullness/generics/GenericArgs2.java index 29d7de40d90..dde3d4e6418 100644 --- a/checker/tests/nullness/generics/GenericArgs2.java +++ b/checker/tests/nullness/generics/GenericArgs2.java @@ -1,48 +1,45 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.io.*; import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; class Cell { - void add(T arg) {} + void add(T arg) {} } public class GenericArgs2 { - static void test1(Cell collection) { - // :: error: (argument.type.incompatible) - collection.add(null); // should fail - } - - static void test2(Cell collection) { - // :: error: (argument.type.incompatible) - collection.add(null); // should fail - } - - static void test3(Cell<@Nullable Object> collection) { - collection.add(null); // valid - } - - // No "" version of the above, as that is illegal in Java. - - static class InvariantFilter {} - - static class Invariant {} - - HashMap, Map, Integer>> filter_map1; - MyMap<@Nullable Class, Map, Integer>> - filter_map2; - - public GenericArgs2( - HashMap, Map, Integer>> - filter_map1, - MyMap< - @Nullable Class, - Map, Integer>> - filter_map2) { - this.filter_map1 = filter_map1; - this.filter_map2 = filter_map2; - } - - class MyMap {} + static void test1(Cell collection) { + // :: error: (argument.type.incompatible) + collection.add(null); // should fail + } + + static void test2(Cell collection) { + // :: error: (argument.type.incompatible) + collection.add(null); // should fail + } + + static void test3(Cell<@Nullable Object> collection) { + collection.add(null); // valid + } + + // No "" version of the above, as that is illegal in Java. + + static class InvariantFilter {} + + static class Invariant {} + + HashMap, Map, Integer>> filter_map1; + MyMap<@Nullable Class, Map, Integer>> + filter_map2; + + public GenericArgs2( + HashMap, Map, Integer>> + filter_map1, + MyMap<@Nullable Class, Map, Integer>> + filter_map2) { + this.filter_map1 = filter_map1; + this.filter_map2 = filter_map2; + } + + class MyMap {} } diff --git a/checker/tests/nullness/generics/GenericArgs3.java b/checker/tests/nullness/generics/GenericArgs3.java index 8d4f008df9c..6f8f5ee4860 100644 --- a/checker/tests/nullness/generics/GenericArgs3.java +++ b/checker/tests/nullness/generics/GenericArgs3.java @@ -1,89 +1,88 @@ -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; - import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; class Other { - public static final class StaticIterator implements Iterator { - Enumeration e; + public static final class StaticIterator implements Iterator { + Enumeration e; - public StaticIterator(Enumeration e) { - this.e = e; - } - - public boolean hasNext() { - return e.hasMoreElements(); - } - - public T next() { - return e.nextElement(); - } - - public void remove() { - throw new UnsupportedOperationException(); - } + public StaticIterator(Enumeration e) { + this.e = e; } - public final class FinalIterator implements Iterator { - Enumeration e; + public boolean hasNext() { + return e.hasMoreElements(); + } - public FinalIterator(Enumeration e) { - this.e = e; - } + public T next() { + return e.nextElement(); + } - public boolean hasNext() { - return e.hasMoreElements(); - } + public void remove() { + throw new UnsupportedOperationException(); + } + } - public T next() { - return e.nextElement(); - } + public final class FinalIterator implements Iterator { + Enumeration e; - public void remove() { - throw new UnsupportedOperationException(); - } + public FinalIterator(Enumeration e) { + this.e = e; } -} -class Entry implements Map.Entry { - public V setValue(V newValue) { - throw new RuntimeException(); + public boolean hasNext() { + return e.hasMoreElements(); } - @SuppressWarnings("purity") // new and throw are not allowed, ignore - @Pure - public K getKey() { - throw new RuntimeException(); + public T next() { + return e.nextElement(); } - @SuppressWarnings("purity") // new and throw are not allowed, ignore - @Pure - public V getValue() { - throw new RuntimeException(); + public void remove() { + throw new UnsupportedOperationException(); } + } +} + +class Entry implements Map.Entry { + public V setValue(V newValue) { + throw new RuntimeException(); + } + + @SuppressWarnings("purity") // new and throw are not allowed, ignore + @Pure + public K getKey() { + throw new RuntimeException(); + } + + @SuppressWarnings("purity") // new and throw are not allowed, ignore + @Pure + public V getValue() { + throw new RuntimeException(); + } } interface Function { - T apply(@Nullable F from); + T apply(@Nullable F from); - @Pure - boolean equals(@Nullable Object obj); + @Pure + boolean equals(@Nullable Object obj); } enum IdentityFunction implements Function { - INSTANCE; + INSTANCE; - public @Nullable Object apply(@Nullable Object o) { - return o; - } + public @Nullable Object apply(@Nullable Object o) { + return o; + } } abstract class FilteredCollection implements Collection { - public boolean addAll(Collection collection) { - for (E element : collection) {} - return true; - } + public boolean addAll(Collection collection) { + for (E element : collection) {} + return true; + } } diff --git a/checker/tests/nullness/generics/GenericReturnField.java b/checker/tests/nullness/generics/GenericReturnField.java index 6983deea9d6..26331091651 100644 --- a/checker/tests/nullness/generics/GenericReturnField.java +++ b/checker/tests/nullness/generics/GenericReturnField.java @@ -6,11 +6,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class GenericReturnField { - private @Nullable T result = null; + private @Nullable T result = null; - // Should return @Nullable T - private T getResult() { - // :: error: (return.type.incompatible) - return result; - } + // Should return @Nullable T + private T getResult() { + // :: error: (return.type.incompatible) + return result; + } } diff --git a/checker/tests/nullness/generics/GenericTest11.java b/checker/tests/nullness/generics/GenericTest11.java index 560303c00b3..6abd7d3b8a2 100644 --- a/checker/tests/nullness/generics/GenericTest11.java +++ b/checker/tests/nullness/generics/GenericTest11.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericTest11 { - public void m(BeanManager beanManager) { - Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); - CreationalContext context = beanManager.createCreationalContext(bean); - } + public void m(BeanManager beanManager) { + Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); + CreationalContext context = beanManager.createCreationalContext(bean); + } - static interface BeanManager { - java.util.Set> getBeans( - java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); + static interface BeanManager { + java.util.Set> getBeans( + java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); - CreationalContext createCreationalContext(Contextual arg0); - } + CreationalContext createCreationalContext(Contextual arg0); + } - static interface Contextual {} + static interface Contextual {} - static interface Bean extends Contextual {} + static interface Bean extends Contextual {} - static interface CreationalContext {} + static interface CreationalContext {} } diff --git a/checker/tests/nullness/generics/GenericsBounds1.java b/checker/tests/nullness/generics/GenericsBounds1.java index 8508234b1cb..b16047c7f89 100644 --- a/checker/tests/nullness/generics/GenericsBounds1.java +++ b/checker/tests/nullness/generics/GenericsBounds1.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.nullness.qual.*; interface GBList { - void add(E p); + void add(E p); } /* @@ -10,18 +10,18 @@ interface GBList { * annotation on the type variable itself. */ public class GenericsBounds1 { - void m1(@NonNull GBList g1, @NonNull GBList<@Nullable X> g2) { - // :: error: (assignment.type.incompatible) - g1 = null; - // :: error: (argument.type.incompatible) - g1.add(null); + void m1(@NonNull GBList g1, @NonNull GBList<@Nullable X> g2) { + // :: error: (assignment.type.incompatible) + g1 = null; + // :: error: (argument.type.incompatible) + g1.add(null); - // :: error: (assignment.type.incompatible) - g2 = null; - g2.add(null); + // :: error: (assignment.type.incompatible) + g2 = null; + g2.add(null); - // :: error: (assignment.type.incompatible) - g2 = g1; - g2.add(null); - } + // :: error: (assignment.type.incompatible) + g2 = g1; + g2.add(null); + } } diff --git a/checker/tests/nullness/generics/GenericsBounds2.java b/checker/tests/nullness/generics/GenericsBounds2.java index d23d33de6fb..ab0a418d206 100644 --- a/checker/tests/nullness/generics/GenericsBounds2.java +++ b/checker/tests/nullness/generics/GenericsBounds2.java @@ -4,26 +4,26 @@ * Illustrate a problem with annotations on type variables. */ public class GenericsBounds2 { - void m1(X @NonNull [] a1, @Nullable X @NonNull [] a2) { - // :: error: (assignment.type.incompatible) - a1 = null; - // :: error: (assignment.type.incompatible) - a1[0] = null; + void m1(X @NonNull [] a1, @Nullable X @NonNull [] a2) { + // :: error: (assignment.type.incompatible) + a1 = null; + // :: error: (assignment.type.incompatible) + a1[0] = null; - // :: error: (assignment.type.incompatible) - a2 = null; - a2[0] = null; + // :: error: (assignment.type.incompatible) + a2 = null; + a2[0] = null; - // This error is expected when arrays are invariant. - // Currently, this error is not raised. - // TODOINVARR:: error: (assignment.type.incompatible) - a2 = a1; - a2[0] = null; - } + // This error is expected when arrays are invariant. + // Currently, this error is not raised. + // TODOINVARR:: error: (assignment.type.incompatible) + a2 = a1; + a2[0] = null; + } - void aaa(@Nullable Object[] p1, @NonNull Object[] p2) { - // This one is only expected when we switch the default for arrays to be invariant. - // TODOINVARR:: error: (assignment.type.incompatible) - p1 = p2; - } + void aaa(@Nullable Object[] p1, @NonNull Object[] p2) { + // This one is only expected when we switch the default for arrays to be invariant. + // TODOINVARR:: error: (assignment.type.incompatible) + p1 = p2; + } } diff --git a/checker/tests/nullness/generics/GenericsBounds3.java b/checker/tests/nullness/generics/GenericsBounds3.java index d28713e9625..9c4faaaecf5 100644 --- a/checker/tests/nullness/generics/GenericsBounds3.java +++ b/checker/tests/nullness/generics/GenericsBounds3.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericsBounds3 { - class Sup {} + class Sup {} - // :: error: (type.argument.type.incompatible) - class Sub extends Sup<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class Sub extends Sup<@Nullable Object> {} - class SubGood extends Sup<@NonNull Object> {} + class SubGood extends Sup<@NonNull Object> {} - interface ISup {} + interface ISup {} - // :: error: (type.argument.type.incompatible) - class ISub implements ISup<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class ISub implements ISup<@Nullable Object> {} - class ISubGood implements ISup<@NonNull Object> {} + class ISubGood implements ISup<@NonNull Object> {} - // :: error: (type.argument.type.incompatible) - class ISub2 extends Sup implements java.io.Serializable, ISup<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class ISub2 extends Sup implements java.io.Serializable, ISup<@Nullable Object> {} } diff --git a/checker/tests/nullness/generics/GenericsBounds4.java b/checker/tests/nullness/generics/GenericsBounds4.java index 6c87882a971..fc055c832d3 100644 --- a/checker/tests/nullness/generics/GenericsBounds4.java +++ b/checker/tests/nullness/generics/GenericsBounds4.java @@ -1,28 +1,28 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericsBounds4 { - class Collection1 { - public void add(E elt) { - // :: error: (dereference.of.nullable) - elt.hashCode(); - } + class Collection1 { + public void add(E elt) { + // :: error: (dereference.of.nullable) + elt.hashCode(); } + } - Collection1 f1 = new Collection1<@NonNull Object>(); - // :: error: (assignment.type.incompatible) - Collection1<@Nullable ? extends @Nullable Object> f2 = new Collection1<@NonNull Object>(); - Collection1<@Nullable ? extends @Nullable Object> f3 = new Collection1<@Nullable Object>(); + Collection1 f1 = new Collection1<@NonNull Object>(); + // :: error: (assignment.type.incompatible) + Collection1<@Nullable ? extends @Nullable Object> f2 = new Collection1<@NonNull Object>(); + Collection1<@Nullable ? extends @Nullable Object> f3 = new Collection1<@Nullable Object>(); - void bad() { - // This has to be forbidden, because f1 might refer to a - // collection that has NonNull as type argument. - // :: error: (argument.type.incompatible) - f1.add(null); + void bad() { + // This has to be forbidden, because f1 might refer to a + // collection that has NonNull as type argument. + // :: error: (argument.type.incompatible) + f1.add(null); - // This is forbidden by the Java type rules: - // f1.add(new Object()); + // This is forbidden by the Java type rules: + // f1.add(new Object()); - // ok - f3.add(null); - } + // ok + f3.add(null); + } } diff --git a/checker/tests/nullness/generics/GenericsBounds5.java b/checker/tests/nullness/generics/GenericsBounds5.java index 8a5a170af92..89858b72aae 100644 --- a/checker/tests/nullness/generics/GenericsBounds5.java +++ b/checker/tests/nullness/generics/GenericsBounds5.java @@ -1,46 +1,46 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericsBounds5 { - class Collection1 { - public void add(E elt) { - // This call is forbidden, because elt might be null. - // :: error: (dereference.of.nullable) - elt.hashCode(); - } - } - - <@Nullable F extends @Nullable Object> void addNull1(Collection1 l) { - // This call is allowed, because F is definitely @Nullable. - l.add(null); - } - - // Effectively, this should be the same signature as above. - // TODO: the type "@Nullable ?" is "@Nullable ? extends @NonNull Object", - // with the wrong extends bound. - void addNull2(Collection1<@Nullable ? extends @Nullable Object> l) { - // This call has to pass, like above. - l.add(null); - } - - <@Nullable F extends @Nullable Object> void addNull3(Collection1 l, F p) { - // This call is allowed, because F is definitely @Nullable. - l.add(null); - l.add(p); - } - - // :: error: (assignment.type.incompatible) - Collection1<@Nullable ? extends @Nullable Integer> f = new Collection1<@NonNull Integer>(); - - void bad(Collection1<@NonNull Integer> nnarg) { - // These have to be forbidden, because f1 might refer to a - // collection that has NonNull as type argument. - // :: error: (type.argument.type.incompatible) - addNull1(nnarg); - - // :: error: (argument.type.incompatible) - addNull2(nnarg); - - // :: error: (type.argument.type.incompatible) - addNull3(nnarg, Integer.valueOf(4)); + class Collection1 { + public void add(E elt) { + // This call is forbidden, because elt might be null. + // :: error: (dereference.of.nullable) + elt.hashCode(); } + } + + <@Nullable F extends @Nullable Object> void addNull1(Collection1 l) { + // This call is allowed, because F is definitely @Nullable. + l.add(null); + } + + // Effectively, this should be the same signature as above. + // TODO: the type "@Nullable ?" is "@Nullable ? extends @NonNull Object", + // with the wrong extends bound. + void addNull2(Collection1<@Nullable ? extends @Nullable Object> l) { + // This call has to pass, like above. + l.add(null); + } + + <@Nullable F extends @Nullable Object> void addNull3(Collection1 l, F p) { + // This call is allowed, because F is definitely @Nullable. + l.add(null); + l.add(p); + } + + // :: error: (assignment.type.incompatible) + Collection1<@Nullable ? extends @Nullable Integer> f = new Collection1<@NonNull Integer>(); + + void bad(Collection1<@NonNull Integer> nnarg) { + // These have to be forbidden, because f1 might refer to a + // collection that has NonNull as type argument. + // :: error: (type.argument.type.incompatible) + addNull1(nnarg); + + // :: error: (argument.type.incompatible) + addNull2(nnarg); + + // :: error: (type.argument.type.incompatible) + addNull3(nnarg, Integer.valueOf(4)); + } } diff --git a/checker/tests/nullness/generics/GenericsConstructor.java b/checker/tests/nullness/generics/GenericsConstructor.java index 1434d3ec6f1..764b44195e0 100644 --- a/checker/tests/nullness/generics/GenericsConstructor.java +++ b/checker/tests/nullness/generics/GenericsConstructor.java @@ -1,15 +1,15 @@ public class GenericsConstructor { - class Test { - Test(T param) {} + class Test { + Test(T param) {} - Test(T1 p1, T2 p2) {} - } + Test(T1 p1, T2 p2) {} + } - void call() { - new Test("Ha!"); - new Test("Ha!"); - new Test(new Object()); + void call() { + new Test("Ha!"); + new Test("Ha!"); + new Test(new Object()); - // new Test("Hi", "Ho"); - } + // new Test("Hi", "Ho"); + } } diff --git a/checker/tests/nullness/generics/GenericsExample.java b/checker/tests/nullness/generics/GenericsExample.java index 4b4ad2dd653..8108b5627e1 100644 --- a/checker/tests/nullness/generics/GenericsExample.java +++ b/checker/tests/nullness/generics/GenericsExample.java @@ -5,175 +5,175 @@ // whose source code is ../../../docs/manual/advanced-features.tex public class GenericsExample { - class MyList1<@Nullable T> { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList1(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - t = null; - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - // :: error: (assignment.type.incompatible) - nn = this.get(0); - this.add(t); - this.add(nble); - this.add(nn); - } + class MyList1<@Nullable T> { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList1(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; } - class MyList1a<@Nullable T extends @Nullable Object> { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList1a(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - t = null; - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - // :: error: (assignment.type.incompatible) - nn = this.get(0); - this.add(t); - this.add(nble); - this.add(nn); - } + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + t = null; + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + // :: error: (assignment.type.incompatible) + nn = this.get(0); + this.add(t); + this.add(nble); + this.add(nn); + } + } + + class MyList1a<@Nullable T extends @Nullable Object> { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList1a(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; } - class MyList2<@NonNull T extends @NonNull Object> { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList2(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - // :: error: (assignment.type.incompatible) - t = null; - // :: error: (assignment.type.incompatible) - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - nn = this.get(0); - this.add(t); - // :: error: (argument.type.incompatible) - this.add(nble); - this.add(nn); - } + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + t = null; + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + // :: error: (assignment.type.incompatible) + nn = this.get(0); + this.add(t); + this.add(nble); + this.add(nn); + } + } + + class MyList2<@NonNull T extends @NonNull Object> { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList2(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; } - class MyList2a { // same as MyList2 - T t; - @Nullable T nble; - @NonNull T nn; - - MyList2a(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - // :: error: (assignment.type.incompatible) - t = null; - // :: error: (assignment.type.incompatible) - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - nn = this.get(0); - this.add(t); - // :: error: (argument.type.incompatible) - this.add(nble); - this.add(nn); - } + void m() { + // :: error: (assignment.type.incompatible) + t = null; + // :: error: (assignment.type.incompatible) + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + nn = this.get(0); + this.add(t); + // :: error: (argument.type.incompatible) + this.add(nble); + this.add(nn); + } + } + + class MyList2a { // same as MyList2 + T t; + @Nullable T nble; + @NonNull T nn; + + MyList2a(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + // :: error: (assignment.type.incompatible) + t = null; + // :: error: (assignment.type.incompatible) + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + nn = this.get(0); + this.add(t); + // :: error: (argument.type.incompatible) + this.add(nble); + this.add(nn); + } + } + + class MyList3 { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList3(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; } - class MyList3 { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList3(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - // :: error: (assignment.type.incompatible) - t = null; - // :: error: (assignment.type.incompatible) - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - // :: error: (assignment.type.incompatible) - nn = this.get(0); - this.add(t); - // :: error: (argument.type.incompatible) - this.add(nble); - this.add(nn); - } + void m() { + // :: error: (assignment.type.incompatible) + t = null; + // :: error: (assignment.type.incompatible) + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + // :: error: (assignment.type.incompatible) + nn = this.get(0); + this.add(t); + // :: error: (argument.type.incompatible) + this.add(nble); + this.add(nn); } + } } diff --git a/checker/tests/nullness/generics/GenericsExampleMin.java b/checker/tests/nullness/generics/GenericsExampleMin.java index 4dfa292039d..9097387d622 100644 --- a/checker/tests/nullness/generics/GenericsExampleMin.java +++ b/checker/tests/nullness/generics/GenericsExampleMin.java @@ -5,89 +5,89 @@ // whose source code is ../../../docs/manual/advanced-features.tex public class GenericsExampleMin { - class MyList1<@Nullable T> { - T t; - @Nullable T nble; - @NonNull T nn; + class MyList1<@Nullable T> { + T t; + @Nullable T nble; + @NonNull T nn; - public MyList1(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - this.t = this.nble; - } + public MyList1(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + this.t = this.nble; + } - T get(int i) { - return t; - } + T get(int i) { + return t; + } - // This method works. - // Note that it fails to work if it is moved after m2() in the syntax tree. - // TODO: the above comment seems out-of-date, as method m3 below works. - void m1() { - t = this.get(0); - nble = this.get(0); - } + // This method works. + // Note that it fails to work if it is moved after m2() in the syntax tree. + // TODO: the above comment seems out-of-date, as method m3 below works. + void m1() { + t = this.get(0); + nble = this.get(0); + } - // When the assignment to nn is added, the assignments to t and nble also fail, which is - // unexpected. - void m2() { - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - } + // When the assignment to nn is added, the assignments to t and nble also fail, which is + // unexpected. + void m2() { + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + } - void m3() { - t = this.get(0); - nble = this.get(0); - } + void m3() { + t = this.get(0); + nble = this.get(0); } + } - class MyList2<@NonNull T> { - T t; - @Nullable T nble; + class MyList2<@NonNull T> { + T t; + @Nullable T nble; - public MyList2(T t, @Nullable T nble) { - // :: error: (assignment.type.incompatible) - this.t = this.nble; // error - // :: error: (assignment.type.incompatible) - this.t = nble; // error - } + public MyList2(T t, @Nullable T nble) { + // :: error: (assignment.type.incompatible) + this.t = this.nble; // error + // :: error: (assignment.type.incompatible) + this.t = nble; // error } + } - class MyList3 { - T t; - @Nullable T nble; - @NonNull T nn; + class MyList3 { + T t; + @Nullable T nble; + @NonNull T nn; - public MyList3(T t, @Nullable T nble, @NonNull T nn) { - // :: error: (assignment.type.incompatible) - this.t = nble; - this.t = nn; - // :: error: (assignment.type.incompatible) - this.nn = t; - // :: error: (assignment.type.incompatible) - this.nn = nble; - this.nn = nn; - } + public MyList3(T t, @Nullable T nble, @NonNull T nn) { + // :: error: (assignment.type.incompatible) + this.t = nble; + this.t = nn; + // :: error: (assignment.type.incompatible) + this.nn = t; + // :: error: (assignment.type.incompatible) + this.nn = nble; + this.nn = nn; } + } - class MyList4 { - T t; - @Nullable T nble; - @NonNull T nn; + class MyList4 { + T t; + @Nullable T nble; + @NonNull T nn; - public MyList4(T t, @Nullable T nble, @NonNull T nn) { - // :: error: (assignment.type.incompatible) - this.t = nble; - this.t = nn; - this.nn = t; - // :: error: (assignment.type.incompatible) - this.nn = nble; - this.nn = nn; - this.nn = t; - this.nble = t; - } + public MyList4(T t, @Nullable T nble, @NonNull T nn) { + // :: error: (assignment.type.incompatible) + this.t = nble; + this.t = nn; + this.nn = t; + // :: error: (assignment.type.incompatible) + this.nn = nble; + this.nn = nn; + this.nn = t; + this.nble = t; } + } } diff --git a/checker/tests/nullness/generics/InferMethod.java b/checker/tests/nullness/generics/InferMethod.java index 6691d26b129..49f3e8501ff 100644 --- a/checker/tests/nullness/generics/InferMethod.java +++ b/checker/tests/nullness/generics/InferMethod.java @@ -2,36 +2,36 @@ // https://github.com/typetools/checker-framework/issues/216 public class InferMethod { - public abstract static class Generic { - public class Nested { - public void nestedMethod(T item) {} - } + public abstract static class Generic { + public class Nested { + public void nestedMethod(T item) {} + } - public static class NestedStatic { - public void nestedMethod2(TInner item) {} - } + public static class NestedStatic { + public void nestedMethod2(TInner item) {} + } - public abstract void method(); + public abstract void method(); - public abstract void method2(); + public abstract void method2(); - public void method3(T item) {} - } + public void method3(T item) {} + } - public static class Concrete extends Generic { + public static class Concrete extends Generic { - @Override - public void method() { - Nested o = new Nested(); - o.nestedMethod("test"); - } + @Override + public void method() { + Nested o = new Nested(); + o.nestedMethod("test"); + } - @Override - public void method2() { - NestedStatic o = new NestedStatic<>(); - o.nestedMethod2("test"); + @Override + public void method2() { + NestedStatic o = new NestedStatic<>(); + o.nestedMethod2("test"); - this.method3("test"); - } + this.method3("test"); } + } } diff --git a/checker/tests/nullness/generics/InferredPrimitive.java b/checker/tests/nullness/generics/InferredPrimitive.java index 68288facda1..fbe61401729 100644 --- a/checker/tests/nullness/generics/InferredPrimitive.java +++ b/checker/tests/nullness/generics/InferredPrimitive.java @@ -1,6 +1,6 @@ /** Test case for Issue 143: https://github.com/typetools/checker-framework/issues/143 */ public class InferredPrimitive { - public static void main(String[] args) { - java.util.Set s = java.util.Collections.singleton(123L); - } + public static void main(String[] args) { + java.util.Set s = java.util.Collections.singleton(123L); + } } diff --git a/checker/tests/nullness/generics/Issue134.java b/checker/tests/nullness/generics/Issue134.java index 8e9a262edd0..8ad53cff216 100644 --- a/checker/tests/nullness/generics/Issue134.java +++ b/checker/tests/nullness/generics/Issue134.java @@ -4,24 +4,24 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Wrap { - class Inner { - T of(T in) { - return in; - } + class Inner { + T of(T in) { + return in; } + } - Inner get() { - return new Inner(); - } + Inner get() { + return new Inner(); + } } class Bug { - void bar(Wrap w, Integer f) { - w.get().of(f).toString(); - } + void bar(Wrap w, Integer f) { + w.get().of(f).toString(); + } - void baz(Wrap<@Nullable Integer> w, Integer f) { - // :: error: (dereference.of.nullable) - w.get().of(f).toString(); - } + void baz(Wrap<@Nullable Integer> w, Integer f) { + // :: error: (dereference.of.nullable) + w.get().of(f).toString(); + } } diff --git a/checker/tests/nullness/generics/Issue1838.java b/checker/tests/nullness/generics/Issue1838.java index 269f1995d0a..1cd3a1f8d9c 100644 --- a/checker/tests/nullness/generics/Issue1838.java +++ b/checker/tests/nullness/generics/Issue1838.java @@ -1,30 +1,29 @@ // Test case for Issue 1838: // https://github.com/typetools/checker-framework/issues/1838 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1838 { - public static void main(String[] args) { - f(); - } + public static void main(String[] args) { + f(); + } - public static void f() { - List<@Nullable Object> list = new ArrayList<>(); - list.add(null); - List> listList = new ArrayList>(); - listList.add(list); - // :: error: (argument.type.incompatible) - processElements(listList); - } + public static void f() { + List<@Nullable Object> list = new ArrayList<>(); + list.add(null); + List> listList = new ArrayList>(); + listList.add(list); + // :: error: (argument.type.incompatible) + processElements(listList); + } - private static void processElements(List> listList) { - for (List list : listList) { - for (Object element : list) { - element.toString(); - } - } + private static void processElements(List> listList) { + for (List list : listList) { + for (Object element : list) { + element.toString(); + } } + } } diff --git a/checker/tests/nullness/generics/Issue1838Min.java b/checker/tests/nullness/generics/Issue1838Min.java index 96ae2c2ecca..e7e04cc7a7a 100644 --- a/checker/tests/nullness/generics/Issue1838Min.java +++ b/checker/tests/nullness/generics/Issue1838Min.java @@ -1,13 +1,12 @@ // Test case for Issue 1838: // https://github.com/typetools/checker-framework/issues/1838 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1838Min { - List> llno = new ArrayList<>(); - // :: error: (assignment.type.incompatible) - List> lweo = llno; + List> llno = new ArrayList<>(); + // :: error: (assignment.type.incompatible) + List> lweo = llno; } diff --git a/checker/tests/nullness/generics/Issue269.java b/checker/tests/nullness/generics/Issue269.java index 4705dc6aba7..1ffb14ce51c 100644 --- a/checker/tests/nullness/generics/Issue269.java +++ b/checker/tests/nullness/generics/Issue269.java @@ -1,28 +1,28 @@ // Test case for Issue 269 // https://github.com/typetools/checker-framework/issues/269 class Issue269 { - // Implicitly G has bound @Nullable Object - interface Callback { - public boolean handler(G arg); - } + // Implicitly G has bound @Nullable Object + interface Callback { + public boolean handler(G arg); + } - void method1(Callback callback) { - // Allow this call. - // :: warning: [unchecked] unchecked call to handler(G) as a member of the raw type - // Issue269.Callback - callback.handler(this); - } + void method1(Callback callback) { + // Allow this call. + // :: warning: [unchecked] unchecked call to handler(G) as a member of the raw type + // Issue269.Callback + callback.handler(this); + } - // Implicitly H has bound @NonNull Object - interface CallbackNN { - public boolean handler(H arg); - } + // Implicitly H has bound @NonNull Object + interface CallbackNN { + public boolean handler(H arg); + } - void method2(CallbackNN callback) { - // Forbid this call, because the bound is not respected. - // :: error: (argument.type.incompatible) - // :: warning: [unchecked] unchecked call to handler(H) as a member of the raw type - // Issue269.CallbackNN - callback.handler(null); - } + void method2(CallbackNN callback) { + // Forbid this call, because the bound is not respected. + // :: error: (argument.type.incompatible) + // :: warning: [unchecked] unchecked call to handler(H) as a member of the raw type + // Issue269.CallbackNN + callback.handler(null); + } } diff --git a/checker/tests/nullness/generics/Issue270.java b/checker/tests/nullness/generics/Issue270.java index 500fcdc5d78..0ea00972084 100644 --- a/checker/tests/nullness/generics/Issue270.java +++ b/checker/tests/nullness/generics/Issue270.java @@ -2,11 +2,11 @@ // ::error: (bound.type.incompatible) public class Issue270<@Nullable TypeParam extends @NonNull Object> { - public static void main() { + public static void main() { - // ::error: (type.argument.type.incompatible) - @Nullable Issue270<@Nullable String> strWAtv = null; - // ::error: (type.argument.type.incompatible) - @Nullable Issue270<@NonNull Integer> intWAtv = null; - } + // ::error: (type.argument.type.incompatible) + @Nullable Issue270<@Nullable String> strWAtv = null; + // ::error: (type.argument.type.incompatible) + @Nullable Issue270<@NonNull Integer> intWAtv = null; + } } diff --git a/checker/tests/nullness/generics/Issue2722.java b/checker/tests/nullness/generics/Issue2722.java index d364e68ef6a..ec1eca2d1c4 100644 --- a/checker/tests/nullness/generics/Issue2722.java +++ b/checker/tests/nullness/generics/Issue2722.java @@ -5,15 +5,15 @@ import java.util.List; class Issue2722 { - void foo() { - passThrough(Arrays.asList("x")).get(0).length(); - } + void foo() { + passThrough(Arrays.asList("x")).get(0).length(); + } - String bar() { - return passThrough(Arrays.asList("x")).get(0); - } + String bar() { + return passThrough(Arrays.asList("x")).get(0); + } - List passThrough(List object) { - return object; - } + List passThrough(List object) { + return object; + } } diff --git a/checker/tests/nullness/generics/Issue282.java b/checker/tests/nullness/generics/Issue282.java index 268d7b60fbf..635b883c6f8 100644 --- a/checker/tests/nullness/generics/Issue282.java +++ b/checker/tests/nullness/generics/Issue282.java @@ -1,30 +1,29 @@ // Test case for Issue 282 // https://github.com/typetools/checker-framework/issues/282 -import org.checkerframework.checker.nullness.qual.*; - import java.util.Collection; import java.util.Comparator; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; @SuppressWarnings("nullness") abstract class ImmutableSortedSet implements Set { - static ImmutableSortedSet copyOf( - Comparator comparator, Collection elements) { - return null; - } + static ImmutableSortedSet copyOf( + Comparator comparator, Collection elements) { + return null; + } } @SuppressWarnings("nullness") abstract class Ordering implements Comparator { - static Ordering usingToString() { - return null; - } + static Ordering usingToString() { + return null; + } } abstract class Example { - private static <@NonNull T extends @NonNull Object> ImmutableSortedSet setSortedByToString( - Collection set) { - return ImmutableSortedSet.copyOf(Ordering.usingToString(), set); - } + private static <@NonNull T extends @NonNull Object> ImmutableSortedSet setSortedByToString( + Collection set) { + return ImmutableSortedSet.copyOf(Ordering.usingToString(), set); + } } diff --git a/checker/tests/nullness/generics/Issue282Min.java b/checker/tests/nullness/generics/Issue282Min.java index 887fa641f71..7b590a2187d 100644 --- a/checker/tests/nullness/generics/Issue282Min.java +++ b/checker/tests/nullness/generics/Issue282Min.java @@ -1,21 +1,20 @@ // Test case for Issue 282 (minimized) // https://github.com/typetools/checker-framework/issues/282 -import org.checkerframework.checker.nullness.qual.*; - import java.util.Collection; import java.util.Comparator; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; public class Issue282Min { - static Set copyOf(Comparator comparator, Collection elements) { - // :: error: (return.type.incompatible) - return null; - } + static Set copyOf(Comparator comparator, Collection elements) { + // :: error: (return.type.incompatible) + return null; + } } class Example282Min { - Set foo(Comparator ord, Collection set) { - return Issue282Min.copyOf(ord, set); - } + Set foo(Comparator ord, Collection set) { + return Issue282Min.copyOf(ord, set); + } } diff --git a/checker/tests/nullness/generics/Issue2995.java b/checker/tests/nullness/generics/Issue2995.java index 0a0baa1a2d2..a21a7c8f268 100644 --- a/checker/tests/nullness/generics/Issue2995.java +++ b/checker/tests/nullness/generics/Issue2995.java @@ -4,11 +4,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue2995 { - interface Set {} + interface Set {} - class Map { - Set keySet = new KeySet(); + class Map { + Set keySet = new KeySet(); - class KeySet implements Set {} - } + class KeySet implements Set {} + } } diff --git a/checker/tests/nullness/generics/Issue3025.java b/checker/tests/nullness/generics/Issue3025.java index 8e7bab4f2c2..1fd11bb8f4c 100644 --- a/checker/tests/nullness/generics/Issue3025.java +++ b/checker/tests/nullness/generics/Issue3025.java @@ -5,12 +5,12 @@ // Classes need to be separate top-level classes to reproduce the issue class Issue3025Caller { - void foo(Issue3025Sub arg) { - bar(arg); - hashCode(); - } + void foo(Issue3025Sub arg) { + bar(arg); + hashCode(); + } - void bar(Issue3025Sub arg) {} + void bar(Issue3025Sub arg) {} } interface Issue3025Super {} diff --git a/checker/tests/nullness/generics/Issue3027.java b/checker/tests/nullness/generics/Issue3027.java index c58933839b1..56d8dbfa6f4 100644 --- a/checker/tests/nullness/generics/Issue3027.java +++ b/checker/tests/nullness/generics/Issue3027.java @@ -4,15 +4,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue3027 { - class Caller { - void foo(Multiset multiset) { - Entry entry = multiset.someEntry(); - } + class Caller { + void foo(Multiset multiset) { + Entry entry = multiset.someEntry(); } + } - interface Multiset { - Entry someEntry(); - } + interface Multiset { + Entry someEntry(); + } - interface Entry {} + interface Entry {} } diff --git a/checker/tests/nullness/generics/Issue312.java b/checker/tests/nullness/generics/Issue312.java index 45b2b1b064d..daf35b9b7e7 100644 --- a/checker/tests/nullness/generics/Issue312.java +++ b/checker/tests/nullness/generics/Issue312.java @@ -7,23 +7,23 @@ // see below for the code that uses Guava. @SuppressWarnings("nullness") class Ordering312 { - public static Ordering312 natural() { - return null; - } + public static Ordering312 natural() { + return null; + } - public Ordering312 reverse() { - return null; - } + public Ordering312 reverse() { + return null; + } - public List sortedCopy(Iterable elements) { - return null; - } + public List sortedCopy(Iterable elements) { + return null; + } } public class Issue312 { - void test(List list) { - Ordering312.natural().reverse().sortedCopy(list); - } + void test(List list) { + Ordering312.natural().reverse().sortedCopy(list); + } } /* Original test using Guava: diff --git a/checker/tests/nullness/generics/Issue313.java b/checker/tests/nullness/generics/Issue313.java index fcaa819ffda..583ce6775e2 100644 --- a/checker/tests/nullness/generics/Issue313.java +++ b/checker/tests/nullness/generics/Issue313.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue313 { - class A<@NonNull T extends @Nullable Object> {} + class A<@NonNull T extends @Nullable Object> {} - <@NonNull X extends @Nullable Object> void m() { - new A(); - } + <@NonNull X extends @Nullable Object> void m() { + new A(); + } } diff --git a/checker/tests/nullness/generics/Issue319.java b/checker/tests/nullness/generics/Issue319.java index fadf581879a..4cc7dbb8bfc 100644 --- a/checker/tests/nullness/generics/Issue319.java +++ b/checker/tests/nullness/generics/Issue319.java @@ -4,46 +4,46 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue319 { - class Foo { - Foo(@Nullable T t) {} - } + class Foo { + Foo(@Nullable T t) {} + } - Foo newFoo(@Nullable T t) { - return new Foo<>(t); - } + Foo newFoo(@Nullable T t) { + return new Foo<>(t); + } - void pass() { - Foo f = newFoo(Boolean.FALSE); - } + void pass() { + Foo f = newFoo(Boolean.FALSE); + } - void fail() { - Foo f = newFoo(null); - } + void fail() { + Foo f = newFoo(null); + } - void workaround() { - Foo f = Issue319.this.newFoo(null); - } + void workaround() { + Foo f = Issue319.this.newFoo(null); + } } class Issue319NN { - class Foo { - Foo(@NonNull T t) {} - } - - Foo newFoo(@NonNull T t) { - return new Foo<>(t); - } - - void pass() { - Foo f = newFoo(Boolean.FALSE); - } - - void fail() { - // :: error: (argument.type.incompatible) - Foo f = newFoo(null); - } - - void pass2() { - Foo<@Nullable Boolean> f = newFoo(Boolean.FALSE); - } + class Foo { + Foo(@NonNull T t) {} + } + + Foo newFoo(@NonNull T t) { + return new Foo<>(t); + } + + void pass() { + Foo f = newFoo(Boolean.FALSE); + } + + void fail() { + // :: error: (argument.type.incompatible) + Foo f = newFoo(null); + } + + void pass2() { + Foo<@Nullable Boolean> f = newFoo(Boolean.FALSE); + } } diff --git a/checker/tests/nullness/generics/Issue326.java b/checker/tests/nullness/generics/Issue326.java index 2a3c7048379..dc275a9c88a 100644 --- a/checker/tests/nullness/generics/Issue326.java +++ b/checker/tests/nullness/generics/Issue326.java @@ -1,12 +1,11 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.HashSet; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue326 { - { - Set<@Nullable String> local = new HashSet<>(); - } + { + Set<@Nullable String> local = new HashSet<>(); + } - Set<@Nullable String> field = new HashSet<>(); + Set<@Nullable String> field = new HashSet<>(); } diff --git a/checker/tests/nullness/generics/Issue329.java b/checker/tests/nullness/generics/Issue329.java index f337ce51aac..04d20232bf4 100644 --- a/checker/tests/nullness/generics/Issue329.java +++ b/checker/tests/nullness/generics/Issue329.java @@ -4,37 +4,37 @@ import org.checkerframework.checker.nullness.qual.Nullable; abstract class Issue329 { - interface Flag {} + interface Flag {} - abstract void setExtension(X value); + abstract void setExtension(X value); - abstract T getValue(Flag flag); + abstract T getValue(Flag flag); - void f(Flag flag) { - String s = getValue(flag); - setExtension(s); + void f(Flag flag) { + String s = getValue(flag); + setExtension(s); - setExtension(getValue(flag)); - } + setExtension(getValue(flag)); + } } abstract class Issue329NN { - interface Flag {} + interface Flag {} - // Explicit bound makes it NonNull - abstract void setExtension(X value); + // Explicit bound makes it NonNull + abstract void setExtension(X value); - abstract T getValue(Flag flag); + abstract T getValue(Flag flag); - void f1(Flag<@Nullable String> flag) { - String s = getValue(flag); - // :: error: (type.argument.type.incompatible) - setExtension(s); - } + void f1(Flag<@Nullable String> flag) { + String s = getValue(flag); + // :: error: (type.argument.type.incompatible) + setExtension(s); + } - void f2(Flag<@Nullable String> flag) { - // TODO: false negative. See #979. - //// :: error: (type.argument.type.incompatible) - setExtension(getValue(flag)); - } + void f2(Flag<@Nullable String> flag) { + // TODO: false negative. See #979. + //// :: error: (type.argument.type.incompatible) + setExtension(getValue(flag)); + } } diff --git a/checker/tests/nullness/generics/Issue335.java b/checker/tests/nullness/generics/Issue335.java index 78037ab5d7c..ddf023c7625 100644 --- a/checker/tests/nullness/generics/Issue335.java +++ b/checker/tests/nullness/generics/Issue335.java @@ -4,19 +4,19 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Pair { - static Pair of(@Nullable C first, @Nullable D second) { - throw new RuntimeException(); - } + static Pair of(@Nullable C first, @Nullable D second) { + throw new RuntimeException(); + } } class Optional { - static Optional of(T reference) { - throw new RuntimeException(); - } + static Optional of(T reference) { + throw new RuntimeException(); + } } public class Issue335 { - Optional> m(String one, String two) { - return Optional.of(Pair.of(one, two)); - } + Optional> m(String one, String two) { + return Optional.of(Pair.of(one, two)); + } } diff --git a/checker/tests/nullness/generics/Issue337.java b/checker/tests/nullness/generics/Issue337.java index 142b24be048..34fa80d2624 100644 --- a/checker/tests/nullness/generics/Issue337.java +++ b/checker/tests/nullness/generics/Issue337.java @@ -4,29 +4,29 @@ import javax.annotation.Nullable; abstract class Issue337 { - abstract R getThing(String key); + abstract R getThing(String key); - @Nullable R m1(@Nullable String key) { - return (key == null) ? null : getThing(key); - } + @Nullable R m1(@Nullable String key) { + return (key == null) ? null : getThing(key); + } - @Nullable R m1b(@Nullable String key) { - return (key != null) ? getThing(key) : null; - } + @Nullable R m1b(@Nullable String key) { + return (key != null) ? getThing(key) : null; + } - @Nullable R m2(@Nullable String key) { - return (key == null) - ? - // :: error: (argument.type.incompatible) - getThing(key) - : null; - } + @Nullable R m2(@Nullable String key) { + return (key == null) + ? + // :: error: (argument.type.incompatible) + getThing(key) + : null; + } - @Nullable R m2b(@Nullable String key) { - return (key != null) - ? null - : - // :: error: (argument.type.incompatible) - getThing(key); - } + @Nullable R m2b(@Nullable String key) { + return (key != null) + ? null + : + // :: error: (argument.type.incompatible) + getThing(key); + } } diff --git a/checker/tests/nullness/generics/Issue339.java b/checker/tests/nullness/generics/Issue339.java index 34c67a9544d..a092331e3b6 100644 --- a/checker/tests/nullness/generics/Issue339.java +++ b/checker/tests/nullness/generics/Issue339.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue339 { - static @NonNull T checkNotNull(T p) { - throw new RuntimeException(); - } + static @NonNull T checkNotNull(T p) { + throw new RuntimeException(); + } - void m(@Nullable S s) { - @NonNull S r1 = Issue339.<@Nullable S>checkNotNull(s); - @NonNull S r2 = Issue339.checkNotNull(s); - @NonNull S r3 = Issue339.checkNotNull(null); - } + void m(@Nullable S s) { + @NonNull S r1 = Issue339.<@Nullable S>checkNotNull(s); + @NonNull S r2 = Issue339.checkNotNull(s); + @NonNull S r3 = Issue339.checkNotNull(null); + } } diff --git a/checker/tests/nullness/generics/Issue421.java b/checker/tests/nullness/generics/Issue421.java index 02f80b90b8a..fd0d400e9cb 100644 --- a/checker/tests/nullness/generics/Issue421.java +++ b/checker/tests/nullness/generics/Issue421.java @@ -1,16 +1,16 @@ public class Issue421 { - abstract static class C { - abstract X getX(); - } + abstract static class C { + abstract X getX(); + } - interface X {} + interface X {} - abstract static class R { - abstract boolean d(X id); - } + abstract static class R { + abstract boolean d(X id); + } - private void f(C c, R r) { - X x = c.getX(); - boolean bval = r.d(x); - } + private void f(C c, R r) { + X x = c.getX(); + boolean bval = r.d(x); + } } diff --git a/checker/tests/nullness/generics/Issue422.java b/checker/tests/nullness/generics/Issue422.java index 3d9ee4dad52..5ebec4ce3c7 100644 --- a/checker/tests/nullness/generics/Issue422.java +++ b/checker/tests/nullness/generics/Issue422.java @@ -1,6 +1,6 @@ public class Issue422 { - public boolean f(T newValue, T oldValue) { - return (oldValue instanceof Boolean || oldValue instanceof Integer) - && oldValue.equals(newValue); - } + public boolean f(T newValue, T oldValue) { + return (oldValue instanceof Boolean || oldValue instanceof Integer) + && oldValue.equals(newValue); + } } diff --git a/checker/tests/nullness/generics/Issue428.java b/checker/tests/nullness/generics/Issue428.java index c5bca87fe00..26bf85889e0 100644 --- a/checker/tests/nullness/generics/Issue428.java +++ b/checker/tests/nullness/generics/Issue428.java @@ -6,7 +6,7 @@ public interface Issue428 {} class Test428 { - void m(List> is) { - Issue428 i = is.get(0); - } + void m(List> is) { + Issue428 i = is.get(0); + } } diff --git a/checker/tests/nullness/generics/Issue459.java b/checker/tests/nullness/generics/Issue459.java index aff1ad8e6ec..18a7f84d230 100644 --- a/checker/tests/nullness/generics/Issue459.java +++ b/checker/tests/nullness/generics/Issue459.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue459 { - public class Generic {} + public class Generic {} - interface Iface { - public Generic foo(Generic arg); + interface Iface { + public Generic foo(Generic arg); - public Generic foo2( - Generic arg, K2 strArg); - } + public Generic foo2( + Generic arg, K2 strArg); + } - void f(Iface arg, @NonNull String nnString) { - final Generic obj = new Generic<>(); - arg.foo(obj); + void f(Iface arg, @NonNull String nnString) { + final Generic obj = new Generic<>(); + arg.foo(obj); - final Generic<@Nullable String, Integer> obj2 = new Generic<>(); - arg.foo2(obj2, nnString); - } + final Generic<@Nullable String, Integer> obj2 = new Generic<>(); + arg.foo2(obj2, nnString); + } } diff --git a/checker/tests/nullness/generics/Issue5006.java b/checker/tests/nullness/generics/Issue5006.java index 30c88dc5393..5689a117131 100644 --- a/checker/tests/nullness/generics/Issue5006.java +++ b/checker/tests/nullness/generics/Issue5006.java @@ -1,18 +1,18 @@ public class Issue5006 { - static class C { - T get() { - throw new RuntimeException(""); - } + static class C { + T get() { + throw new RuntimeException(""); } + } - interface X { - C get(); - } + interface X { + C get(); + } - interface Y extends X { - @Override - // :: error: (type.invalid.super.wildcard) - C get(); - } + interface Y extends X { + @Override + // :: error: (type.invalid.super.wildcard) + C get(); + } } diff --git a/checker/tests/nullness/generics/Issue6374.java b/checker/tests/nullness/generics/Issue6374.java index 199a46d9ffc..37c6933f54e 100644 --- a/checker/tests/nullness/generics/Issue6374.java +++ b/checker/tests/nullness/generics/Issue6374.java @@ -7,31 +7,31 @@ public class Issue6374 { - @SuppressWarnings("unchecked") // ignore heap pollution - static class Lib { - // element type inferred, array non-null - static void none(T... o) {} + @SuppressWarnings("unchecked") // ignore heap pollution + static class Lib { + // element type inferred, array non-null + static void none(T... o) {} - // element type inferred, array non-null - static void decl(@NonNull T... o) {} + // element type inferred, array non-null + static void decl(@NonNull T... o) {} - // element type nullable, array non-null - static void type(@Nullable T... o) {} + // element type nullable, array non-null + static void type(@Nullable T... o) {} - // element type nullable, array nullable - static void typenn(@Nullable T @Nullable ... o) {} - } + // element type nullable, array nullable + static void typenn(@Nullable T @Nullable ... o) {} + } - class User { - void go() { - Lib.decl("", null); - // :: error: (argument.type.incompatible) - Lib.decl((Object[]) null); - Lib.type("", null); - // :: error: (argument.type.incompatible) - Lib.type((Object[]) null); - Lib.typenn("", null); - Lib.typenn((Object[]) null); - } + class User { + void go() { + Lib.decl("", null); + // :: error: (argument.type.incompatible) + Lib.decl((Object[]) null); + Lib.type("", null); + // :: error: (argument.type.incompatible) + Lib.type((Object[]) null); + Lib.typenn("", null); + Lib.typenn((Object[]) null); } + } } diff --git a/checker/tests/nullness/generics/Issue783a.java b/checker/tests/nullness/generics/Issue783a.java index aabebcfc67c..7b3649ed89e 100644 --- a/checker/tests/nullness/generics/Issue783a.java +++ b/checker/tests/nullness/generics/Issue783a.java @@ -10,13 +10,13 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue783a { - private @Nullable T val; + private @Nullable T val; - public void set(@Nullable T val) { - this.val = val; - } + public void set(@Nullable T val) { + this.val = val; + } - public @Nullable T get() { - return val; - } + public @Nullable T get() { + return val; + } } diff --git a/checker/tests/nullness/generics/Issue783b.java b/checker/tests/nullness/generics/Issue783b.java index 684dcdf3b98..5e1bc1a3aff 100644 --- a/checker/tests/nullness/generics/Issue783b.java +++ b/checker/tests/nullness/generics/Issue783b.java @@ -10,13 +10,13 @@ import javax.annotation.Nullable; public class Issue783b { - private @Nullable T val; + private @Nullable T val; - public void set(@Nullable T val) { - this.val = val; - } + public void set(@Nullable T val) { + this.val = val; + } - @Nullable public T get() { - return val; - } + @Nullable public T get() { + return val; + } } diff --git a/checker/tests/nullness/generics/Issue849.java b/checker/tests/nullness/generics/Issue849.java index fc51a7ee45a..48a654e23fa 100644 --- a/checker/tests/nullness/generics/Issue849.java +++ b/checker/tests/nullness/generics/Issue849.java @@ -5,10 +5,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue849 { - class Gen {} + class Gen {} - void nullness(Gen> genGenNonNull) { - // :: error: (assignment.type.incompatible) - Gen<@Nullable ? extends @Nullable Gen<@Nullable Object>> a = genGenNonNull; - } + void nullness(Gen> genGenNonNull) { + // :: error: (assignment.type.incompatible) + Gen<@Nullable ? extends @Nullable Gen<@Nullable Object>> a = genGenNonNull; + } } diff --git a/checker/tests/nullness/generics/KeyForPolyKeyFor.java b/checker/tests/nullness/generics/KeyForPolyKeyFor.java index 4ed32c406e3..a9f0ea4a331 100644 --- a/checker/tests/nullness/generics/KeyForPolyKeyFor.java +++ b/checker/tests/nullness/generics/KeyForPolyKeyFor.java @@ -1,27 +1,26 @@ package nullness.generics; -import org.checkerframework.checker.nullness.qual.*; - import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; // test related to issue 429: https://github.com/typetools/checker-framework/issues/429 public class KeyForPolyKeyFor { - // TODO: Figure out why diamond operator does not work: - // Map<@KeyFor("dict") String, String> dict = new HashMap<>(); - Map<@KeyFor("dict") String, String> dict = new HashMap<@KeyFor("dict") String, String>(); + // TODO: Figure out why diamond operator does not work: + // Map<@KeyFor("dict") String, String> dict = new HashMap<>(); + Map<@KeyFor("dict") String, String> dict = new HashMap<@KeyFor("dict") String, String>(); - void m() { - Set<@KeyFor("dict") String> s = nounSubset(dict.keySet()); + void m() { + Set<@KeyFor("dict") String> s = nounSubset(dict.keySet()); - for (@KeyFor("dict") String noun : nounSubset(dict.keySet())) {} - } + for (@KeyFor("dict") String noun : nounSubset(dict.keySet())) {} + } - // This method's declaration uses no @KeyFor annotations because in addition to being used by - // the dictionary feature, it is also used by a spell checker that only stores sets of words and - // does not use the notions of dictionaries, maps or keys. - Set<@PolyKeyFor String> nounSubset(Set<@PolyKeyFor String> words) { - return words; - } + // This method's declaration uses no @KeyFor annotations because in addition to being used by + // the dictionary feature, it is also used by a spell checker that only stores sets of words and + // does not use the notions of dictionaries, maps or keys. + Set<@PolyKeyFor String> nounSubset(Set<@PolyKeyFor String> words) { + return words; + } } diff --git a/checker/tests/nullness/generics/MapLoop.java b/checker/tests/nullness/generics/MapLoop.java index 0017b9463b5..616ac7364be 100644 --- a/checker/tests/nullness/generics/MapLoop.java +++ b/checker/tests/nullness/generics/MapLoop.java @@ -1,18 +1,17 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.*; public class MapLoop { - void test1(Map map) { - for (Map.Entry<@KeyFor("map") String, String> entry : map.entrySet()) {} - } + void test1(Map map) { + for (Map.Entry<@KeyFor("map") String, String> entry : map.entrySet()) {} + } - void test2(Map map) { - for (Map.Entry entry : map.entrySet()) {} - } + void test2(Map map) { + for (Map.Entry entry : map.entrySet()) {} + } - void test3(Map map) { - for (Map.Entry entry : map.entrySet()) {} - for (Object val : map.values()) {} - } + void test3(Map map) { + for (Map.Entry entry : map.entrySet()) {} + for (Object val : map.values()) {} + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars.java b/checker/tests/nullness/generics/MethodTypeVars.java index 38e0423b63a..5839d93fc80 100644 --- a/checker/tests/nullness/generics/MethodTypeVars.java +++ b/checker/tests/nullness/generics/MethodTypeVars.java @@ -5,34 +5,34 @@ * https://github.com/typetools/checker-framework/issues/93 */ public class MethodTypeVars { - void m() { - // :: error: (type.argument.type.incompatible) - Object a = A.badMethod(null); - Object b = A.badMethod(new Object()); + void m() { + // :: error: (type.argument.type.incompatible) + Object a = A.badMethod(null); + Object b = A.badMethod(new Object()); - // :: error: (type.argument.type.incompatible) - A.goodMethod(null); - A.goodMethod(new Object()); - } + // :: error: (type.argument.type.incompatible) + A.goodMethod(null); + A.goodMethod(new Object()); + } } class A { - public static T badMethod(T t) { - // :: warning: [unchecked] unchecked cast - return (T) new Object(); - } + public static T badMethod(T t) { + // :: warning: [unchecked] unchecked cast + return (T) new Object(); + } - public static void goodMethod(T t) {} + public static void goodMethod(T t) {} } class B { - public void indexOf1(T[] a, @Nullable Object elt) {} + public void indexOf1(T[] a, @Nullable Object elt) {} - // This is not valid Java syntax. - // public void indexOf2(?[] a, @Nullable Object elt) {} + // This is not valid Java syntax. + // public void indexOf2(?[] a, @Nullable Object elt) {} - void call() { - Integer[] arg = new Integer[] {1, 2, 3, 4}; - indexOf1(arg, Integer.valueOf(5)); - } + void call() { + Integer[] arg = new Integer[] {1, 2, 3, 4}; + indexOf1(arg, Integer.valueOf(5)); + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars2.java b/checker/tests/nullness/generics/MethodTypeVars2.java index 68885a3a54b..603b7060609 100644 --- a/checker/tests/nullness/generics/MethodTypeVars2.java +++ b/checker/tests/nullness/generics/MethodTypeVars2.java @@ -2,34 +2,34 @@ public class MethodTypeVars2 { - class GeoSegment {} + class GeoSegment {} - interface Path> {} + interface Path> {} - private static > @Nullable Object pathToRoute( - Path path) { - return null; - } + private static > @Nullable Object pathToRoute( + Path path) { + return null; + } - class StreetSegment extends GeoSegment {} + class StreetSegment extends GeoSegment {} - class StreetSegmentPath implements Path {} + class StreetSegmentPath implements Path {} - void call(StreetSegmentPath p) { - Object r = pathToRoute(p); - } + void call(StreetSegmentPath p) { + Object r = pathToRoute(p); + } - static class WorkingWithOne { - interface GPath

> {} + static class WorkingWithOne { + interface GPath

> {} - class GStreetSegmentPath implements GPath {} + class GStreetSegmentPath implements GPath {} - private static

> @Nullable Object pathToRoute(GPath

path) { - return null; - } + private static

> @Nullable Object pathToRoute(GPath

path) { + return null; + } - void call(GStreetSegmentPath p) { - Object r = pathToRoute(p); - } + void call(GStreetSegmentPath p) { + Object r = pathToRoute(p); } + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars3.java b/checker/tests/nullness/generics/MethodTypeVars3.java index 79e2cbf10db..3880bcb60af 100644 --- a/checker/tests/nullness/generics/MethodTypeVars3.java +++ b/checker/tests/nullness/generics/MethodTypeVars3.java @@ -1,44 +1,43 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; public class MethodTypeVars3 { - public static <@KeyFor("#1") T extends @KeyFor("#1") Object> Map> dominators( - Map> preds) { - List nodes = new ArrayList<>(preds.keySet()); - - // Compute roots & non-roots, for convenience - List<@KeyFor("preds") T> roots = new ArrayList<>(); - List<@KeyFor("preds") T> non_roots = new ArrayList<>(); - - Map<@KeyFor("preds") T, List> dom = new HashMap<>(); - - // Initialize result: for roots just the root, otherwise everything - for (@KeyFor("preds") T node : preds.keySet()) { - if (preds.get(node).isEmpty()) { - // This is a root - roots.add(node); - // Its only dominator is itself. - Set<@KeyFor("preds") T> set = Collections.singleton(node); - - dom.put(node, new ArrayList(set)); - - dom.put(node, new ArrayList(Collections.singleton(node))); - } else { - non_roots.add(node); - dom.put(node, new ArrayList(nodes)); - } - } - - return dom; + public static <@KeyFor("#1") T extends @KeyFor("#1") Object> Map> dominators( + Map> preds) { + List nodes = new ArrayList<>(preds.keySet()); + + // Compute roots & non-roots, for convenience + List<@KeyFor("preds") T> roots = new ArrayList<>(); + List<@KeyFor("preds") T> non_roots = new ArrayList<>(); + + Map<@KeyFor("preds") T, List> dom = new HashMap<>(); + + // Initialize result: for roots just the root, otherwise everything + for (@KeyFor("preds") T node : preds.keySet()) { + if (preds.get(node).isEmpty()) { + // This is a root + roots.add(node); + // Its only dominator is itself. + Set<@KeyFor("preds") T> set = Collections.singleton(node); + + dom.put(node, new ArrayList(set)); + + dom.put(node, new ArrayList(Collections.singleton(node))); + } else { + non_roots.add(node); + dom.put(node, new ArrayList(nodes)); + } } - void test(Map> dom, XXX node) { - dom.put(node, new ArrayList(Collections.singleton(node))); - } + return dom; + } + + void test(Map> dom, XXX node) { + dom.put(node, new ArrayList(Collections.singleton(node))); + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars5.java b/checker/tests/nullness/generics/MethodTypeVars5.java index 13bbb31d158..e8cb11b61c4 100644 --- a/checker/tests/nullness/generics/MethodTypeVars5.java +++ b/checker/tests/nullness/generics/MethodTypeVars5.java @@ -1,89 +1,89 @@ import org.checkerframework.checker.nullness.qual.*; public class MethodTypeVars5 { - class B { - S t; + class B { + S t; - B(S t) { - this.t = t; - } - - S get() { - return t; - } - } - - B b = new B<>("Hello World"); - - String doit1() { - return doit1(b); - } - - U doit1(B x) { - return x.get(); - } - - String doit2() { - // Passing the null argument has no effect on the inferred type argument: - // the second parameter type doesn't contain the type variable at all. - return doit2(b, null); - } - - T doit2(B x, @Nullable String y) { - return x.get(); - } - - String doit3() { - // Passing the null argument has no effect on the inferred type argument: - // the type variable only appears as nested type. - return doit3(null); - } - - String doit3b() { - return doit3(new B("Hi")); + B(S t) { + this.t = t; } - String doit3b2() { - return doit3(new B<>("Hi")); + S get() { + return t; } - - String doit3c() { - // :: error: (return.type.incompatible) - return doit3(new B<@Nullable String>("Hi")); - } - - void doit3d() { - // :: error: (assignment.type.incompatible) - @NonNull String s = doit3(new B<@Nullable String>("Hi")); - } - - void doit3e() { - String s = doit3(new B("Hi")); - } - - void doit3e2() { - String s = doit3(new B<>("Hi")); - } - - T doit3(@Nullable B x) { - if (x != null) { - return x.get(); - } else { - // This won't work at runtime, but whatever. - @SuppressWarnings("unchecked") - T res = (T) new Object(); - return res; - } - } - - String doit4() { - // Passing the null argument has an impact on the inferred type argument: - // the type variable appears as the top-level type. - // :: error: (return.type.incompatible) - return doit4("Ha!", null); - } - - T doit4(T x, T y) { - return x; + } + + B b = new B<>("Hello World"); + + String doit1() { + return doit1(b); + } + + U doit1(B x) { + return x.get(); + } + + String doit2() { + // Passing the null argument has no effect on the inferred type argument: + // the second parameter type doesn't contain the type variable at all. + return doit2(b, null); + } + + T doit2(B x, @Nullable String y) { + return x.get(); + } + + String doit3() { + // Passing the null argument has no effect on the inferred type argument: + // the type variable only appears as nested type. + return doit3(null); + } + + String doit3b() { + return doit3(new B("Hi")); + } + + String doit3b2() { + return doit3(new B<>("Hi")); + } + + String doit3c() { + // :: error: (return.type.incompatible) + return doit3(new B<@Nullable String>("Hi")); + } + + void doit3d() { + // :: error: (assignment.type.incompatible) + @NonNull String s = doit3(new B<@Nullable String>("Hi")); + } + + void doit3e() { + String s = doit3(new B("Hi")); + } + + void doit3e2() { + String s = doit3(new B<>("Hi")); + } + + T doit3(@Nullable B x) { + if (x != null) { + return x.get(); + } else { + // This won't work at runtime, but whatever. + @SuppressWarnings("unchecked") + T res = (T) new Object(); + return res; } + } + + String doit4() { + // Passing the null argument has an impact on the inferred type argument: + // the type variable appears as the top-level type. + // :: error: (return.type.incompatible) + return doit4("Ha!", null); + } + + T doit4(T x, T y) { + return x; + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars6.java b/checker/tests/nullness/generics/MethodTypeVars6.java index 974fd962803..eb8dd0edcb4 100644 --- a/checker/tests/nullness/generics/MethodTypeVars6.java +++ b/checker/tests/nullness/generics/MethodTypeVars6.java @@ -1,62 +1,62 @@ import org.checkerframework.checker.nullness.qual.*; class APair { - static APair of(U p1, V p2) { - return new APair(); - } + static APair of(U p1, V p2) { + return new APair(); + } - static APair of2(U p1, V p2) { - return new APair<>(); - } + static APair of2(U p1, V p2) { + return new APair<>(); + } } class PairSub extends APair { - static PairSub of( - US p1, VS p2) { - return new PairSub(); - } + static PairSub of( + US p1, VS p2) { + return new PairSub(); + } } class PairSubSwitching - extends APair { - static - PairSubSwitching ofPSS(US p1, VS p2) { - return new PairSubSwitching(); - } + extends APair { + static PairSubSwitching ofPSS( + US p1, VS p2) { + return new PairSubSwitching(); + } } class Test1 { - APair<@Nullable X, @Nullable X> test1(@Nullable X p) { - return APair.<@Nullable X, @Nullable X>of(p, (X) null); - } + APair<@Nullable X, @Nullable X> test1(@Nullable X p) { + return APair.<@Nullable X, @Nullable X>of(p, (X) null); + } } class Test2 { - APair<@Nullable X, @Nullable X> test1(@Nullable X p) { - return APair.of(p, (@Nullable X) null); - } - /* - APair<@Nullable X, @Nullable X> test2(@Nullable X p) { - // TODO cast: should this X mean the same as above?? - return APair.of(p, (X) null); - } - */ + APair<@Nullable X, @Nullable X> test1(@Nullable X p) { + return APair.of(p, (@Nullable X) null); + } + /* + APair<@Nullable X, @Nullable X> test2(@Nullable X p) { + // TODO cast: should this X mean the same as above?? + return APair.of(p, (X) null); + } + */ } class Test3 { - APair<@NonNull X, @NonNull X> test1(@Nullable X p) { - // :: error: (return.type.incompatible) - return APair.of(p, (X) null); - } + APair<@NonNull X, @NonNull X> test1(@Nullable X p) { + // :: error: (return.type.incompatible) + return APair.of(p, (X) null); + } } class Test4 { - APair<@Nullable String, Integer> psi = PairSub.of("Hi", 42); - APair<@Nullable String, Integer> psi2 = PairSub.of(null, 42); - // :: error: (assignment.type.incompatible) - APair psi3 = PairSub.of(null, 42); - - APair<@Nullable String, Integer> psisw = PairSubSwitching.ofPSS(42, null); - // :: error: (assignment.type.incompatible) - APair psisw2 = PairSubSwitching.ofPSS(42, null); + APair<@Nullable String, Integer> psi = PairSub.of("Hi", 42); + APair<@Nullable String, Integer> psi2 = PairSub.of(null, 42); + // :: error: (assignment.type.incompatible) + APair psi3 = PairSub.of(null, 42); + + APair<@Nullable String, Integer> psisw = PairSubSwitching.ofPSS(42, null); + // :: error: (assignment.type.incompatible) + APair psisw2 = PairSubSwitching.ofPSS(42, null); } diff --git a/checker/tests/nullness/generics/MethodTypeVars7.java b/checker/tests/nullness/generics/MethodTypeVars7.java index aad846e5f47..56c96be1f88 100644 --- a/checker/tests/nullness/generics/MethodTypeVars7.java +++ b/checker/tests/nullness/generics/MethodTypeVars7.java @@ -6,76 +6,76 @@ abstract class MethodTypeVars7 { - abstract T val(@Nullable T value, T defaultValue); + abstract T val(@Nullable T value, T defaultValue); - void tests(@Nullable String t1, @NonNull String t2) { - @Nullable String s3 = val(t1, null); - } - - T validate(@Nullable T value, T defaultValue) { - return value != null && !value.toString().isEmpty() ? value : defaultValue; - } - - T validateIf(@Nullable T value, T defaultValue) { - if (value != null && !value.toString().isEmpty()) { - return value; - } else { - return defaultValue; - } - } - - T validate2(@Nullable T value, T defaultValue) { - return value == null || value.toString().isEmpty() ? defaultValue : value; - } - - T validate3(@Nullable T value, T defaultValue) { - return value != null ? value : defaultValue; - } - - T validate4(@Nullable T value, T defaultValue) { - return value == null ? defaultValue : value; - } + void tests(@Nullable String t1, @NonNull String t2) { + @Nullable String s3 = val(t1, null); + } - T validatefail(@Nullable T value, T defaultValue) { - // :: error: (return.type.incompatible) - return ((value == null || !value.toString().isEmpty()) ? value : defaultValue); - } - - T validate2fail(@Nullable T value, T defaultValue) { - // :: error: (return.type.incompatible) - return ((value != null && value.toString().isEmpty()) ? defaultValue : value); - } - - T validate3fail(@Nullable T value3, T defaultValue3) { - // :: error: (return.type.incompatible) - return value3 == null ? value3 : defaultValue3; - } - - T validate4fail(@Nullable T value, T defaultValue) { - // :: error: (return.type.incompatible) - return value != null ? defaultValue : value; - } - - String test1(@Nullable String t1, @NonNull String t2) { - @Nullable String s1 = validate(t1, null); - @Nullable String s2 = validate(t2, null); - @NonNull String s3 = validate(t1, "N/A"); - @NonNull String s4 = validate(t2, "N/A"); - return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; - } - - String test2(@Nullable String t1, @NonNull String t2) { - @Nullable String s1 = validate(t1, t1); - @Nullable String s2 = validate(t2, t1); - @NonNull String s3 = validate(t1, t2); - @NonNull String s4 = validate(t2, t2); - return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; - } + T validate(@Nullable T value, T defaultValue) { + return value != null && !value.toString().isEmpty() ? value : defaultValue; + } - void main(String[] args) { - System.out.println("test 1 " + test1("s_1", "s_2")); - System.out.println("test 2 " + test2("s_1", "s_2")); - System.out.println("test 1 " + test1(null, "s_2")); - System.out.println("test 2 " + test2(null, "s_2")); + T validateIf(@Nullable T value, T defaultValue) { + if (value != null && !value.toString().isEmpty()) { + return value; + } else { + return defaultValue; } + } + + T validate2(@Nullable T value, T defaultValue) { + return value == null || value.toString().isEmpty() ? defaultValue : value; + } + + T validate3(@Nullable T value, T defaultValue) { + return value != null ? value : defaultValue; + } + + T validate4(@Nullable T value, T defaultValue) { + return value == null ? defaultValue : value; + } + + T validatefail(@Nullable T value, T defaultValue) { + // :: error: (return.type.incompatible) + return ((value == null || !value.toString().isEmpty()) ? value : defaultValue); + } + + T validate2fail(@Nullable T value, T defaultValue) { + // :: error: (return.type.incompatible) + return ((value != null && value.toString().isEmpty()) ? defaultValue : value); + } + + T validate3fail(@Nullable T value3, T defaultValue3) { + // :: error: (return.type.incompatible) + return value3 == null ? value3 : defaultValue3; + } + + T validate4fail(@Nullable T value, T defaultValue) { + // :: error: (return.type.incompatible) + return value != null ? defaultValue : value; + } + + String test1(@Nullable String t1, @NonNull String t2) { + @Nullable String s1 = validate(t1, null); + @Nullable String s2 = validate(t2, null); + @NonNull String s3 = validate(t1, "N/A"); + @NonNull String s4 = validate(t2, "N/A"); + return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; + } + + String test2(@Nullable String t1, @NonNull String t2) { + @Nullable String s1 = validate(t1, t1); + @Nullable String s2 = validate(t2, t1); + @NonNull String s3 = validate(t1, t2); + @NonNull String s4 = validate(t2, t2); + return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; + } + + void main(String[] args) { + System.out.println("test 1 " + test1("s_1", "s_2")); + System.out.println("test 2 " + test2("s_1", "s_2")); + System.out.println("test 1 " + test1(null, "s_2")); + System.out.println("test 2 " + test2(null, "s_2")); + } } diff --git a/checker/tests/nullness/generics/MixTypeAndDeclAnno.java b/checker/tests/nullness/generics/MixTypeAndDeclAnno.java index 3e326cce681..358e9e0f630 100644 --- a/checker/tests/nullness/generics/MixTypeAndDeclAnno.java +++ b/checker/tests/nullness/generics/MixTypeAndDeclAnno.java @@ -4,35 +4,35 @@ import org.checkerframework.checker.nullness.qual.Nullable; class MixTypeAndDeclAnno { - @NonNull T t; - @android.annotation.NonNull T tdecl; - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @Nullable T tdecl2; + @NonNull T t; + @android.annotation.NonNull T tdecl; + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @Nullable T tdecl2; + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @android.annotation.Nullable Object f1; + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @Nullable Object f2; + // Nullable applies to the array itself, while NonNull apply to components of the array. + @android.annotation.Nullable @NonNull Object[] g; + // :: error: (type.invalid.conflicting.annos) + @android.annotation.Nullable Object @NonNull [] k; + + MixTypeAndDeclAnno( + @NonNull T t, + @android.annotation.NonNull T tdecl, + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @android.annotation.Nullable Object f1, + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @Nullable Object f2, + // :: error: (type.invalid.conflicting.annos) + @android.annotation.Nullable Object @NonNull [] k) { + this.t = t; + this.tdecl = tdecl; // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @android.annotation.Nullable Object f1; + this.f1 = f1; // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @Nullable Object f2; - // Nullable applies to the array itself, while NonNull apply to components of the array. - @android.annotation.Nullable @NonNull Object[] g; + this.f2 = f2; // :: error: (type.invalid.conflicting.annos) - @android.annotation.Nullable Object @NonNull [] k; - - MixTypeAndDeclAnno( - @NonNull T t, - @android.annotation.NonNull T tdecl, - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @android.annotation.Nullable Object f1, - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @Nullable Object f2, - // :: error: (type.invalid.conflicting.annos) - @android.annotation.Nullable Object @NonNull [] k) { - this.t = t; - this.tdecl = tdecl; - // :: error: (type.invalid.conflicting.annos) - this.f1 = f1; - // :: error: (type.invalid.conflicting.annos) - this.f2 = f2; - // :: error: (type.invalid.conflicting.annos) - this.k = k; - } + this.k = k; + } } diff --git a/checker/tests/nullness/generics/MyMap.java b/checker/tests/nullness/generics/MyMap.java index f81f38ba34a..b3d46b281d5 100644 --- a/checker/tests/nullness/generics/MyMap.java +++ b/checker/tests/nullness/generics/MyMap.java @@ -1,20 +1,19 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; // Test case for Issue 173 // https://github.com/typetools/checker-framework/issues/173 public abstract class MyMap implements Map { - @Override - // :: error: (contracts.postcondition.not.satisfied) - public @Nullable V put(K key, V value) { - return null; - } + @Override + // :: error: (contracts.postcondition.not.satisfied) + public @Nullable V put(K key, V value) { + return null; + } - @Override - public void putAll(Map map) { - for (Map.Entry entry : map.entrySet()) { - put(entry.getKey(), entry.getValue()); - } + @Override + public void putAll(Map map) { + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); } + } } diff --git a/checker/tests/nullness/generics/NullableGeneric.java b/checker/tests/nullness/generics/NullableGeneric.java index 447dd5e659a..801bae356a0 100644 --- a/checker/tests/nullness/generics/NullableGeneric.java +++ b/checker/tests/nullness/generics/NullableGeneric.java @@ -2,36 +2,36 @@ public class NullableGeneric { - public static class NullablePair { - public @Nullable T1 a; - public @Nullable T2 b; - public @NonNull T1 nna; - public @NonNull T2 nnb; + public static class NullablePair { + public @Nullable T1 a; + public @Nullable T2 b; + public @NonNull T1 nna; + public @NonNull T2 nnb; - public NullablePair(T1 a, T2 b) { - this.a = a; - this.b = b; - // :: error: (assignment.type.incompatible) - this.nna = a; - // :: error: (assignment.type.incompatible) - this.nnb = b; - } + public NullablePair(T1 a, T2 b) { + this.a = a; + this.b = b; + // :: error: (assignment.type.incompatible) + this.nna = a; + // :: error: (assignment.type.incompatible) + this.nnb = b; } + } - @Nullable T next1 = null, next2 = null; + @Nullable T next1 = null, next2 = null; - private NullablePair<@Nullable T, @Nullable T> return1() { - NullablePair<@Nullable T, @Nullable T> result = - new NullablePair<@Nullable T, @Nullable T>(next1, null); - // setnext1(); - return result; - } + private NullablePair<@Nullable T, @Nullable T> return1() { + NullablePair<@Nullable T, @Nullable T> result = + new NullablePair<@Nullable T, @Nullable T>(next1, null); + // setnext1(); + return result; + } - public static @NonNull T3 checkNotNull(@Nullable T3 object) { - if (object == null) { - throw new NullPointerException(); - } else { - return object; - } + public static @NonNull T3 checkNotNull(@Nullable T3 object) { + if (object == null) { + throw new NullPointerException(); + } else { + return object; } + } } diff --git a/checker/tests/nullness/generics/NullnessBound.java b/checker/tests/nullness/generics/NullnessBound.java index 70911d72edf..bf407d2ef93 100644 --- a/checker/tests/nullness/generics/NullnessBound.java +++ b/checker/tests/nullness/generics/NullnessBound.java @@ -2,23 +2,23 @@ public class NullnessBound { - public void test() { - Gen1<@Nullable String> t1 = new Gen1<>(); - t1.add(null); + public void test() { + Gen1<@Nullable String> t1 = new Gen1<>(); + t1.add(null); - Gen2<@Nullable String> t2 = new Gen2<>(); - t2.add(null); + Gen2<@Nullable String> t2 = new Gen2<>(); + t2.add(null); - Gen1<@NonNull String> t3; - // :: error: (type.argument.type.incompatible) - Gen2<@NonNull String> t4; - } + Gen1<@NonNull String> t3; + // :: error: (type.argument.type.incompatible) + Gen2<@NonNull String> t4; + } - class Gen1 { - public void add(E e) {} - } + class Gen1 { + public void add(E e) {} + } - class Gen2<@Nullable E> { - public void add(E e) {} - } + class Gen2<@Nullable E> { + public void add(E e) {} + } } diff --git a/checker/tests/nullness/generics/OptionsTest.java b/checker/tests/nullness/generics/OptionsTest.java index d8d5f3a3acc..6b1b1da6605 100644 --- a/checker/tests/nullness/generics/OptionsTest.java +++ b/checker/tests/nullness/generics/OptionsTest.java @@ -2,27 +2,27 @@ public class OptionsTest { - class MyAnnotation {} + class MyAnnotation {} - // Annotated identically to java.lang.reflect.Field.getAnnotation - public static @Nullable T1 getAnnotation( - Class<@NonNull T1> obj) { - return null; - } + // Annotated identically to java.lang.reflect.Field.getAnnotation + public static @Nullable T1 getAnnotation( + Class<@NonNull T1> obj) { + return null; + } - public static @Nullable MyAnnotation safeGetAnnotationNonGeneric( - Class<@NonNull MyAnnotation> annotationClass) { - @Nullable MyAnnotation cast = getAnnotation(annotationClass); - @Nullable MyAnnotation annotation = cast; - return annotation; - } + public static @Nullable MyAnnotation safeGetAnnotationNonGeneric( + Class<@NonNull MyAnnotation> annotationClass) { + @Nullable MyAnnotation cast = getAnnotation(annotationClass); + @Nullable MyAnnotation annotation = cast; + return annotation; + } - public static @Nullable T2 safeGetAnnotationGeneric( - Class<@NonNull T2> annotationClass) { - @Nullable T2 cast = getAnnotation(annotationClass); - @Nullable T2 annotation = cast; - return annotation; - } + public static @Nullable T2 safeGetAnnotationGeneric( + Class<@NonNull T2> annotationClass) { + @Nullable T2 cast = getAnnotation(annotationClass); + @Nullable T2 annotation = cast; + return annotation; + } } /* Local Variables: */ diff --git a/checker/tests/nullness/generics/RawTypesGenerics.java b/checker/tests/nullness/generics/RawTypesGenerics.java index 634f23fa1f6..fbc5ee84bda 100644 --- a/checker/tests/nullness/generics/RawTypesGenerics.java +++ b/checker/tests/nullness/generics/RawTypesGenerics.java @@ -1,26 +1,26 @@ import org.checkerframework.checker.nullness.qual.*; public class RawTypesGenerics { - void m() throws ClassNotFoundException { - Class c1 = Class.forName("bla"); - Class c2 = Class.forName("bla"); - } + void m() throws ClassNotFoundException { + Class c1 = Class.forName("bla"); + Class c2 = Class.forName("bla"); + } - class Test {} + class Test {} - void bar() { - // Java will complain about this: - // Test x = new Test(); + void bar() { + // Java will complain about this: + // Test x = new Test(); - // ok - Test y = new Test(); + // ok + Test y = new Test(); - // :: error: (type.argument.type.incompatible) - Test z = new Test<@Nullable Integer>(); - } + // :: error: (type.argument.type.incompatible) + Test z = new Test<@Nullable Integer>(); + } - void m(java.lang.reflect.Constructor c) { - Class cls1 = c.getParameterTypes()[0]; - Class cls2 = c.getParameterTypes()[0]; - } + void m(java.lang.reflect.Constructor c) { + Class cls1 = c.getParameterTypes()[0]; + Class cls2 = c.getParameterTypes()[0]; + } } diff --git a/checker/tests/nullness/generics/SourceVsJdk.java b/checker/tests/nullness/generics/SourceVsJdk.java index 48d5b8e2966..415fe6ade7e 100644 --- a/checker/tests/nullness/generics/SourceVsJdk.java +++ b/checker/tests/nullness/generics/SourceVsJdk.java @@ -8,7 +8,7 @@ import java.util.Map; public class SourceVsJdk { - public Map getMap() { - return Collections.emptyMap(); - } + public Map getMap() { + return Collections.emptyMap(); + } } diff --git a/checker/tests/nullness/generics/SuperRawness.java b/checker/tests/nullness/generics/SuperRawness.java index d6f7e9f6e90..d506b7aa654 100644 --- a/checker/tests/nullness/generics/SuperRawness.java +++ b/checker/tests/nullness/generics/SuperRawness.java @@ -2,11 +2,11 @@ import java.util.Set; public class SuperRawness { - // :: warning: [unchecked] Possible heap pollution from parameterized vararg type - // java.util.Set - static void test(Set... args) { - test2(Arrays.asList(args)); - } + // :: warning: [unchecked] Possible heap pollution from parameterized vararg type + // java.util.Set + static void test(Set... args) { + test2(Arrays.asList(args)); + } - static void test2(Iterable> args) {} + static void test2(Iterable> args) {} } diff --git a/checker/tests/nullness/generics/TernaryGenerics.java b/checker/tests/nullness/generics/TernaryGenerics.java index dadbdc49b09..0076f5c7666 100644 --- a/checker/tests/nullness/generics/TernaryGenerics.java +++ b/checker/tests/nullness/generics/TernaryGenerics.java @@ -1,48 +1,48 @@ import org.checkerframework.checker.nullness.qual.*; public class TernaryGenerics { - class Generic1 { - void cond(boolean b, T p) { - // :: error: (assignment.type.incompatible) - @NonNull T r1 = b ? p : null; - // :: error: (assignment.type.incompatible) - @NonNull T r2 = b ? null : p; - } + class Generic1 { + void cond(boolean b, T p) { + // :: error: (assignment.type.incompatible) + @NonNull T r1 = b ? p : null; + // :: error: (assignment.type.incompatible) + @NonNull T r2 = b ? null : p; } + } - class Generic2 { - void cond(boolean b, T p) { - // :: error: (assignment.type.incompatible) - @NonNull T r1 = b ? p : null; - // :: error: (assignment.type.incompatible) - @NonNull T r2 = b ? null : p; - } + class Generic2 { + void cond(boolean b, T p) { + // :: error: (assignment.type.incompatible) + @NonNull T r1 = b ? p : null; + // :: error: (assignment.type.incompatible) + @NonNull T r2 = b ? null : p; } + } - class Generic3 { - void cond(boolean b, @Nullable T p) { - @Nullable T r1 = b ? p : null; - @Nullable T r2 = b ? null : p; - // :: error: (assignment.type.incompatible) - @NonNull T r3 = b ? null : p; - } + class Generic3 { + void cond(boolean b, @Nullable T p) { + @Nullable T r1 = b ? p : null; + @Nullable T r2 = b ? null : p; + // :: error: (assignment.type.incompatible) + @NonNull T r3 = b ? null : p; } + } - void array(boolean b) { - String[] s = b ? new String[] {""} : null; - // :: error: (dereference.of.nullable) - s.toString(); - } + void array(boolean b) { + String[] s = b ? new String[] {""} : null; + // :: error: (dereference.of.nullable) + s.toString(); + } - void generic(boolean b, Generic1 p) { - Generic1 s = b ? p : null; - // :: error: (dereference.of.nullable) - s.toString(); - } + void generic(boolean b, Generic1 p) { + Generic1 s = b ? p : null; + // :: error: (dereference.of.nullable) + s.toString(); + } - void primarray(boolean b) { - long[] result = b ? null : new long[10]; - // :: error: (dereference.of.nullable) - result.toString(); - } + void primarray(boolean b) { + long[] result = b ? null : new long[10]; + // :: error: (dereference.of.nullable) + result.toString(); + } } diff --git a/checker/tests/nullness/generics/VarArgsTest.java b/checker/tests/nullness/generics/VarArgsTest.java index 3c563308536..835ae0cd753 100644 --- a/checker/tests/nullness/generics/VarArgsTest.java +++ b/checker/tests/nullness/generics/VarArgsTest.java @@ -2,11 +2,11 @@ import java.util.Set; public class VarArgsTest { - // :: warning: [unchecked] Possible heap pollution from parameterized vararg type - // java.util.Set - void test(Set... args) { - Arrays.asList(args); - } - // static void test(Set... args) { test2(Arrays.asList(args)); } - // static void test2(Iterable> args) {} + // :: warning: [unchecked] Possible heap pollution from parameterized vararg type + // java.util.Set + void test(Set... args) { + Arrays.asList(args); + } + // static void test(Set... args) { test2(Arrays.asList(args)); } + // static void test2(Iterable> args) {} } diff --git a/checker/tests/nullness/generics/WildcardAnnos.java b/checker/tests/nullness/generics/WildcardAnnos.java index efb5a21ca58..6b02a2ca05e 100644 --- a/checker/tests/nullness/generics/WildcardAnnos.java +++ b/checker/tests/nullness/generics/WildcardAnnos.java @@ -1,39 +1,38 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class WildcardAnnos { - // :: error: (bound.type.incompatible) - @Nullable List<@Nullable ? extends @NonNull Object> l1 = null; - @Nullable List<@NonNull ? extends @Nullable Object> l2 = null; - - // The implicit upper bound is Nullable, because the annotation - // on the wildcard is propagated. Therefore this type is: - // @Nullable List l3 = null; - @Nullable List<@Nullable ? super @NonNull String> l3 = null; - - // The bounds need to have the same annotations because capture conversion - // converts the type argument to just Object. - // :: error: (type.invalid.super.wildcard) - @Nullable List<@Nullable ? super @NonNull Object> l3b = null; - - // :: error: (bound.type.incompatible) - @Nullable List<@NonNull ? super @Nullable String> l4 = null; - - @Nullable List l5 = null; - - @Nullable List inReturn() { - return null; - } - - void asParam(List p) {} - - // :: error: (type.invalid.conflicting.annos) - @Nullable List<@Nullable @NonNull ? extends @Nullable Object> l6 = null; - // :: error: (type.invalid.conflicting.annos) - @Nullable List<@Nullable @NonNull ? super @NonNull String> l7 = null; - // :: error: (type.invalid.conflicting.annos) - @Nullable List l8 = null; - // :: error: (type.invalid.conflicting.annos) - @Nullable List l9 = null; + // :: error: (bound.type.incompatible) + @Nullable List<@Nullable ? extends @NonNull Object> l1 = null; + @Nullable List<@NonNull ? extends @Nullable Object> l2 = null; + + // The implicit upper bound is Nullable, because the annotation + // on the wildcard is propagated. Therefore this type is: + // @Nullable List l3 = null; + @Nullable List<@Nullable ? super @NonNull String> l3 = null; + + // The bounds need to have the same annotations because capture conversion + // converts the type argument to just Object. + // :: error: (type.invalid.super.wildcard) + @Nullable List<@Nullable ? super @NonNull Object> l3b = null; + + // :: error: (bound.type.incompatible) + @Nullable List<@NonNull ? super @Nullable String> l4 = null; + + @Nullable List l5 = null; + + @Nullable List inReturn() { + return null; + } + + void asParam(List p) {} + + // :: error: (type.invalid.conflicting.annos) + @Nullable List<@Nullable @NonNull ? extends @Nullable Object> l6 = null; + // :: error: (type.invalid.conflicting.annos) + @Nullable List<@Nullable @NonNull ? super @NonNull String> l7 = null; + // :: error: (type.invalid.conflicting.annos) + @Nullable List l8 = null; + // :: error: (type.invalid.conflicting.annos) + @Nullable List l9 = null; } diff --git a/checker/tests/nullness/generics/WildcardBoundDefault.java b/checker/tests/nullness/generics/WildcardBoundDefault.java index b922e8df12d..ee6a3fa3050 100644 --- a/checker/tests/nullness/generics/WildcardBoundDefault.java +++ b/checker/tests/nullness/generics/WildcardBoundDefault.java @@ -6,13 +6,13 @@ class MyGenClass {} @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.UPPER_BOUND) public class WildcardBoundDefault { - void test() { - ignore(newInstance()); - } + void test() { + ignore(newInstance()); + } - static void ignore(MyGenClass... consumer) {} + static void ignore(MyGenClass... consumer) {} - static MyGenClass newInstance() { - return new MyGenClass(); - } + static MyGenClass newInstance() { + return new MyGenClass(); + } } diff --git a/checker/tests/nullness/generics/WildcardBounds.java b/checker/tests/nullness/generics/WildcardBounds.java index 7faadc6add1..8b35636c961 100644 --- a/checker/tests/nullness/generics/WildcardBounds.java +++ b/checker/tests/nullness/generics/WildcardBounds.java @@ -3,132 +3,132 @@ class WildcardBounds { - abstract class OuterNbl { - abstract T get(); - - abstract class Inner { - abstract U get(); - - abstract class Chain { - abstract W get(); - } - - Object m0(Chain p) { - return p.get(); - } - - Object m1(Chain p) { - return p.get(); - } - - Object m2(Chain p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m3(Chain p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m4(Chain<@NonNull ?, @NonNull ?> p) { - return p.get(); - } - - void callsNonNull( - OuterNbl.Inner i, - OuterNbl.Inner.Chain n) { - i.m0(n); - i.m1(n); - i.m2(n); - i.m3(n); - i.m4(n); - } - - void callsNullable( - OuterNbl<@Nullable Object>.Inner<@Nullable Number> i, - OuterNbl<@Nullable Object>.Inner<@Nullable Number>.Chain< - @Nullable Integer, @Nullable Integer> - n) { - // :: error: (argument.type.incompatible) - i.m0(n); - // :: error: (argument.type.incompatible) - i.m1(n); - // OK - i.m2(n); - // OK - i.m3(n); - // :: error: (argument.type.incompatible) - i.m4(n); - } - } - - Object m0(Inner p) { - return p.get(); - } - - Object m1(Inner p) { - return p.get(); - } - - Object m2(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m3(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m4(Inner<@NonNull ?> p) { - return p.get(); - } - - // We could add calls for these methods. - } + abstract class OuterNbl { + abstract T get(); + + abstract class Inner { + abstract U get(); + + abstract class Chain { + abstract W get(); + } - Object m0(OuterNbl p) { + Object m0(Chain p) { return p.get(); - } + } - Object m1(OuterNbl p) { + Object m1(Chain p) { return p.get(); - } + } - Object m2(OuterNbl p) { + Object m2(Chain p) { // :: error: (return.type.incompatible) return p.get(); - } + } - Object m3(OuterNbl p) { + Object m3(Chain p) { // :: error: (return.type.incompatible) return p.get(); - } + } - Object m4(OuterNbl<@NonNull ?> p) { + Object m4(Chain<@NonNull ?, @NonNull ?> p) { return p.get(); - } - - void callsOuter(OuterNbl s, OuterNbl<@Nullable String> ns) { - m0(s); - m1(s); - m2(s); - m3(s); - m4(s); - + } + + void callsNonNull( + OuterNbl.Inner i, + OuterNbl.Inner.Chain n) { + i.m0(n); + i.m1(n); + i.m2(n); + i.m3(n); + i.m4(n); + } + + void callsNullable( + OuterNbl<@Nullable Object>.Inner<@Nullable Number> i, + OuterNbl<@Nullable Object>.Inner<@Nullable Number>.Chain< + @Nullable Integer, @Nullable Integer> + n) { // :: error: (argument.type.incompatible) - m0(ns); + i.m0(n); // :: error: (argument.type.incompatible) - m1(ns); + i.m1(n); // OK - m2(ns); + i.m2(n); // OK - m3(ns); + i.m3(n); // :: error: (argument.type.incompatible) - m4(ns); + i.m4(n); + } + } + + Object m0(Inner p) { + return p.get(); + } + + Object m1(Inner p) { + return p.get(); + } + + Object m2(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m3(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m4(Inner<@NonNull ?> p) { + return p.get(); } - // We could add an OuterNonNull to also test with a non-null upper bound. - // But we probably already test that enough. + // We could add calls for these methods. + } + + Object m0(OuterNbl p) { + return p.get(); + } + + Object m1(OuterNbl p) { + return p.get(); + } + + Object m2(OuterNbl p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m3(OuterNbl p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m4(OuterNbl<@NonNull ?> p) { + return p.get(); + } + + void callsOuter(OuterNbl s, OuterNbl<@Nullable String> ns) { + m0(s); + m1(s); + m2(s); + m3(s); + m4(s); + + // :: error: (argument.type.incompatible) + m0(ns); + // :: error: (argument.type.incompatible) + m1(ns); + // OK + m2(ns); + // OK + m3(ns); + // :: error: (argument.type.incompatible) + m4(ns); + } + + // We could add an OuterNonNull to also test with a non-null upper bound. + // But we probably already test that enough. } diff --git a/checker/tests/nullness/generics/WildcardOverride.java b/checker/tests/nullness/generics/WildcardOverride.java index c57fe4dc71f..7ea7f0037af 100644 --- a/checker/tests/nullness/generics/WildcardOverride.java +++ b/checker/tests/nullness/generics/WildcardOverride.java @@ -2,29 +2,28 @@ // see also framework/tests/all-systems/WildcardSuper2 -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; interface ToOverride { - public abstract int transform(List function); + public abstract int transform(List function); } public class WildcardOverride implements ToOverride { - @Override - public int transform(List function) { - return 0; - } + @Override + public int transform(List function) { + return 0; + } } interface ToOverride2 { - // :: error: (bound.type.incompatible) - public abstract int transform(List<@NonNull ? super T> function); + // :: error: (bound.type.incompatible) + public abstract int transform(List<@NonNull ? super T> function); } class WildcardOverride2 implements ToOverride2 { - @Override - public int transform(List function) { - return 0; - } + @Override + public int transform(List function) { + return 0; + } } diff --git a/checker/tests/nullness/generics/WildcardOverrideMore.java b/checker/tests/nullness/generics/WildcardOverrideMore.java index 8e49434f8de..5077071dc2a 100644 --- a/checker/tests/nullness/generics/WildcardOverrideMore.java +++ b/checker/tests/nullness/generics/WildcardOverrideMore.java @@ -2,47 +2,47 @@ import org.checkerframework.checker.nullness.qual.Nullable; class WildcardOverrideMore { - interface Box {} + interface Box {} - interface Super { - void foo(Box lib); + interface Super { + void foo(Box lib); - Box retfoo(); + Box retfoo(); - void bar(Box lib); + void bar(Box lib); - Box retbar(); - } + Box retbar(); + } - interface Sub extends Super { - @Override - void foo(Box lib); + interface Sub extends Super { + @Override + void foo(Box lib); - @Override - Box retfoo(); + @Override + Box retfoo(); - @Override - void bar(Box lib); + @Override + void bar(Box lib); - @Override - Box retbar(); - } + @Override + Box retbar(); + } - interface SubErrors extends Super { - @Override - // :: error: (override.param.invalid) - void foo(Box lib); + interface SubErrors extends Super { + @Override + // :: error: (override.param.invalid) + void foo(Box lib); - @Override - // :: error: (override.return.invalid) - Box retfoo(); + @Override + // :: error: (override.return.invalid) + Box retfoo(); - @Override - // :: error: (override.param.invalid) - void bar(Box<@NonNull ? super @NonNull W> lib); + @Override + // :: error: (override.param.invalid) + void bar(Box<@NonNull ? super @NonNull W> lib); - @Override - // :: error: (override.return.invalid) - Box<@Nullable ? super @NonNull W> retbar(); - } + @Override + // :: error: (override.return.invalid) + Box<@Nullable ? super @NonNull W> retbar(); + } } diff --git a/checker/tests/nullness/generics/WildcardSubtyping.java b/checker/tests/nullness/generics/WildcardSubtyping.java index 80d8300870f..944c80780a8 100644 --- a/checker/tests/nullness/generics/WildcardSubtyping.java +++ b/checker/tests/nullness/generics/WildcardSubtyping.java @@ -1,66 +1,65 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.*; class Utils { - void test(List list, A object) { - list.add(object); - } + void test(List list, A object) { + list.add(object); + } - interface Consumer { - public void consume(A object); - } + interface Consumer { + public void consume(A object); + } - public static Consumer cast( - final Consumer<@Nullable ? super A> consumer) { - return new Consumer() { - @Override - public void consume(A object) { - consumer.consume(object); - } - }; - } + public static Consumer cast( + final Consumer<@Nullable ? super A> consumer) { + return new Consumer() { + @Override + public void consume(A object) { + consumer.consume(object); + } + }; + } - public static Consumer getConsumer( - Consumer<@Nullable Object> nullConsumer) { - return Utils.cast(nullConsumer); - } + public static Consumer getConsumer( + Consumer<@Nullable Object> nullConsumer) { + return Utils.cast(nullConsumer); + } - Map> mss = new HashMap<>(); + Map> mss = new HashMap<>(); - Set> foo() { - Set> l = new HashSet<>(this.foo()); - return l; - } + Set> foo() { + Set> l = new HashSet<>(this.foo()); + return l; + } } class MyGeneric<@NonNull T extends @Nullable Number> {} class UseMyGeneric { - MyGeneric wildcardUnbounded = new MyGeneric<>(); + MyGeneric wildcardUnbounded = new MyGeneric<>(); - // :: error: (assignment.type.incompatible) - MyGeneric wildcardOutsideUB = wildcardUnbounded; - MyGeneric wildcardInsideUB = wildcardOutsideUB; - // :: error: (assignment.type.incompatible) - MyGeneric wildcardInsideUB2 = wildcardUnbounded; + // :: error: (assignment.type.incompatible) + MyGeneric wildcardOutsideUB = wildcardUnbounded; + MyGeneric wildcardInsideUB = wildcardOutsideUB; + // :: error: (assignment.type.incompatible) + MyGeneric wildcardInsideUB2 = wildcardUnbounded; - MyGeneric wildcardInsideUBNullable = wildcardOutsideUB; + MyGeneric wildcardInsideUBNullable = wildcardOutsideUB; } class MyGenericExactBounds<@NonNull T extends @NonNull Number> {} class UseMyGenericExactBounds { - MyGenericExactBounds wildcardOutsideUBError = - new MyGenericExactBounds<>(); - MyGenericExactBounds wildcardOutside = new MyGenericExactBounds<>(); - MyGenericExactBounds wildcardInsideUB = wildcardOutside; + MyGenericExactBounds wildcardOutsideUBError = + new MyGenericExactBounds<>(); + MyGenericExactBounds wildcardOutside = new MyGenericExactBounds<>(); + MyGenericExactBounds wildcardInsideUB = wildcardOutside; - MyGenericExactBounds wildcardOutsideUB = wildcardOutside; + MyGenericExactBounds wildcardOutsideUB = wildcardOutside; } diff --git a/checker/tests/nullness/generics/WildcardSubtyping2.java b/checker/tests/nullness/generics/WildcardSubtyping2.java index 8b01eeb591c..ef9fb6138cb 100644 --- a/checker/tests/nullness/generics/WildcardSubtyping2.java +++ b/checker/tests/nullness/generics/WildcardSubtyping2.java @@ -1,28 +1,28 @@ import org.checkerframework.checker.nullness.qual.*; public class WildcardSubtyping2 { - class MyClass {} + class MyClass {} - class MyCloneClass extends MyClass implements Cloneable {} + class MyCloneClass extends MyClass implements Cloneable {} - class MyGeneric<@NonNull T extends @Nullable MyClass> {} + class MyGeneric<@NonNull T extends @Nullable MyClass> {} - class UseMyGeneric { - MyGeneric<@NonNull MyCloneClass> nonNull = new MyGeneric<>(); - MyGeneric<@Nullable MyCloneClass> nullable = new MyGeneric<>(); + class UseMyGeneric { + MyGeneric<@NonNull MyCloneClass> nonNull = new MyGeneric<>(); + MyGeneric<@Nullable MyCloneClass> nullable = new MyGeneric<>(); - MyGeneric interfaceNN = nonNull; - MyGeneric interfaceNull = nullable; - } + MyGeneric interfaceNN = nonNull; + MyGeneric interfaceNull = nullable; + } - class MyGenericEB<@NonNull T extends @NonNull MyClass> {} + class MyGenericEB<@NonNull T extends @NonNull MyClass> {} - class UseMyGenericEB { - MyGenericEB<@NonNull MyCloneClass> nonNull = new MyGenericEB<>(); - // :: error: (type.argument.type.incompatible) - MyGenericEB<@Nullable MyCloneClass> nullable = new MyGenericEB<>(); + class UseMyGenericEB { + MyGenericEB<@NonNull MyCloneClass> nonNull = new MyGenericEB<>(); + // :: error: (type.argument.type.incompatible) + MyGenericEB<@Nullable MyCloneClass> nullable = new MyGenericEB<>(); - MyGenericEB interfaceNN = nonNull; - MyGenericEB interfaceNull = nullable; - } + MyGenericEB interfaceNN = nonNull; + MyGenericEB interfaceNull = nullable; + } } diff --git a/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java b/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java index 1735657e68f..e202a666916 100644 --- a/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java +++ b/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java @@ -1,11 +1,10 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class WildcardSubtypingTypeArray { - void test(List list) { - test2(list.get(0)); - } + void test(List list) { + test2(list.get(0)); + } - void test2(A x) {} + void test2(A x) {} } diff --git a/checker/tests/nullness/generics/WildcardSuper.java b/checker/tests/nullness/generics/WildcardSuper.java index ae7a9f22463..8629e8bc2a0 100644 --- a/checker/tests/nullness/generics/WildcardSuper.java +++ b/checker/tests/nullness/generics/WildcardSuper.java @@ -3,35 +3,35 @@ public class WildcardSuper { - void testWithSuper(Cell cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithSuper(Cell cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - void testWithContradiction(Cell cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithContradiction(Cell cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - @DefaultQualifier(Nullable.class) - void testWithImplicitNullable(@NonNull Cell cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + @DefaultQualifier(Nullable.class) + void testWithImplicitNullable(@NonNull Cell cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - void testWithExplicitNullable(Cell<@Nullable ? extends @Nullable String> cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithExplicitNullable(Cell<@Nullable ? extends @Nullable String> cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - void testWithDoubleNullable(Cell<@Nullable ? extends @Nullable String> cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithDoubleNullable(Cell<@Nullable ? extends @Nullable String> cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - class Cell { - E get() { - throw new RuntimeException(); - } + class Cell { + E get() { + throw new RuntimeException(); } + } } diff --git a/checker/tests/nullness/java-unsound/Figure1.java b/checker/tests/nullness/java-unsound/Figure1.java index 77ae99a175c..38a62f3bfe5 100644 --- a/checker/tests/nullness/java-unsound/Figure1.java +++ b/checker/tests/nullness/java-unsound/Figure1.java @@ -2,22 +2,22 @@ // @skip-test no need to test for the javac error. public class Figure1 { - static class Constrain {} + static class Constrain {} - static class Bind { - A upcast(Constrain constrain, B b) { - return b; - } + static class Bind { + A upcast(Constrain constrain, B b) { + return b; } + } - static U coerce(T t) { - Constrain constrain = null; - Bind bind = new Bind(); - // :: error: method upcast in class Figure1.Bind cannot be applied to given types; - return bind.upcast(constrain, t); - } + static U coerce(T t) { + Constrain constrain = null; + Bind bind = new Bind(); + // :: error: method upcast in class Figure1.Bind cannot be applied to given types; + return bind.upcast(constrain, t); + } - public static void main(String[] args) { - String zero = Figure1.coerce(0); - } + public static void main(String[] args) { + String zero = Figure1.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure3.java b/checker/tests/nullness/java-unsound/Figure3.java index 37dc8925901..cf831e43286 100644 --- a/checker/tests/nullness/java-unsound/Figure3.java +++ b/checker/tests/nullness/java-unsound/Figure3.java @@ -1,41 +1,41 @@ public class Figure3 { - static class Type { - class Constraint extends Type {} + static class Type { + class Constraint extends Type {} - Constraint bad() { - // :: error: (return.type.incompatible) - return null; - } + Constraint bad() { + // :: error: (return.type.incompatible) + return null; + } - A coerce(B b) { - // type of expression: capture#703[ extends @Initialized @Nullable Object super B[ - // extends @Initialized @Nullable Object super @Initialized @NonNull Void]] - // method return type: A[ extends @Initialized @Nullable Object super @Initialized - // @NonNull Void] - return pair(this.bad(), b).value; - } + A coerce(B b) { + // type of expression: capture#703[ extends @Initialized @Nullable Object super B[ + // extends @Initialized @Nullable Object super @Initialized @NonNull Void]] + // method return type: A[ extends @Initialized @Nullable Object super @Initialized + // @NonNull Void] + return pair(this.bad(), b).value; } + } - static class Sum { - Type type; - T value; + static class Sum { + Type type; + T value; - Sum(Type t, T v) { - type = t; - value = v; - } + Sum(Type t, T v) { + type = t; + value = v; } + } - static Sum pair(Type type, T value) { - return new Sum(type, value); - } + static Sum pair(Type type, T value) { + return new Sum(type, value); + } - static U coerce(T t) { - Type type = new Type(); - return type.coerce(t); - } + static U coerce(T t) { + Type type = new Type(); + return type.coerce(t); + } - public static void main(String[] args) { - String zero = Figure3.coerce(0); - } + public static void main(String[] args) { + String zero = Figure3.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure3NC.java b/checker/tests/nullness/java-unsound/Figure3NC.java index a312b41278c..f579d7932ed 100644 --- a/checker/tests/nullness/java-unsound/Figure3NC.java +++ b/checker/tests/nullness/java-unsound/Figure3NC.java @@ -3,38 +3,38 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Figure3NC { - static class Type { - class Constraint extends Type {} + static class Type { + class Constraint extends Type {} - @Nullable Constraint bad() { - return null; - } + @Nullable Constraint bad() { + return null; + } - A coerce(B b) { - return pair(this.bad(), b).value; - } + A coerce(B b) { + return pair(this.bad(), b).value; } + } - static class Sum { - @Nullable Type type; - T value; + static class Sum { + @Nullable Type type; + T value; - Sum(@Nullable Type t, T v) { - type = t; - value = v; - } + Sum(@Nullable Type t, T v) { + type = t; + value = v; } + } - static Sum pair(@Nullable Type type, T value) { - return new Sum(type, value); - } + static Sum pair(@Nullable Type type, T value) { + return new Sum(type, value); + } - static U coerce(T t) { - Type type = new Type(); - return type.coerce(t); - } + static U coerce(T t) { + Type type = new Type(); + return type.coerce(t); + } - public static void main(String[] args) { - String zero = Figure3NC.coerce(0); - } + public static void main(String[] args) { + String zero = Figure3NC.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure4.java b/checker/tests/nullness/java-unsound/Figure4.java index b6df5c030f4..efb885e2334 100644 --- a/checker/tests/nullness/java-unsound/Figure4.java +++ b/checker/tests/nullness/java-unsound/Figure4.java @@ -2,19 +2,19 @@ // @skip-test no need to test for the javac error. public class Figure4 { - static class Constrain {} + static class Constrain {} - static A upcast(Constrain constrain, B b) { - return b; - } + static A upcast(Constrain constrain, B b) { + return b; + } - static U coerce(T t) { - Constrain constrain = null; - // :: error: method upcast in class Figure4 cannot be applied to given types; - return upcast(constrain, t); - } + static U coerce(T t) { + Constrain constrain = null; + // :: error: method upcast in class Figure4 cannot be applied to given types; + return upcast(constrain, t); + } - public static void main(String[] args) { - String zero = coerce(0); - } + public static void main(String[] args) { + String zero = coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure6.java b/checker/tests/nullness/java-unsound/Figure6.java index a15aa5bd5a7..79ca5c41dd2 100644 --- a/checker/tests/nullness/java-unsound/Figure6.java +++ b/checker/tests/nullness/java-unsound/Figure6.java @@ -1,26 +1,26 @@ public class Figure6 { - static class Bind { - class Curry { - A curry(B b) { - return b; - } - } + static class Bind { + class Curry { + A curry(B b) { + return b; + } + } - Curry upcast(Constraint constraint) { - return new Curry(); - } + Curry upcast(Constraint constraint) { + return new Curry(); + } - class Constraint {} + class Constraint {} - A coerce(B t) { - Constraint constraint = null; - // :: error: (argument.type.incompatible) - return upcast(constraint).curry(t); - } + A coerce(B t) { + Constraint constraint = null; + // :: error: (argument.type.incompatible) + return upcast(constraint).curry(t); } + } - public static void main(String[] args) { - Bind bind = new Bind(); - String zero = bind.coerce(0); - } + public static void main(String[] args) { + Bind bind = new Bind(); + String zero = bind.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure6NC.java b/checker/tests/nullness/java-unsound/Figure6NC.java index 39f0b987d2b..a6047ed567c 100644 --- a/checker/tests/nullness/java-unsound/Figure6NC.java +++ b/checker/tests/nullness/java-unsound/Figure6NC.java @@ -3,27 +3,27 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Figure6NC { - static class Bind { - class Curry { - A curry(B b) { - return b; - } - } + static class Bind { + class Curry { + A curry(B b) { + return b; + } + } - Curry upcast(@Nullable Constraint constraint) { - return new Curry(); - } + Curry upcast(@Nullable Constraint constraint) { + return new Curry(); + } - class Constraint {} + class Constraint {} - A coerce(B t) { - Constraint constraint = null; - return upcast(constraint).curry(t); - } + A coerce(B t) { + Constraint constraint = null; + return upcast(constraint).curry(t); } + } - public static void main(String[] args) { - Bind bind = new Bind(); - String zero = bind.coerce(0); - } + public static void main(String[] args) { + Bind bind = new Bind(); + String zero = bind.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure7.java b/checker/tests/nullness/java-unsound/Figure7.java index 9abf06ef270..e75b05dda54 100644 --- a/checker/tests/nullness/java-unsound/Figure7.java +++ b/checker/tests/nullness/java-unsound/Figure7.java @@ -2,30 +2,30 @@ // @skip-test no need to test for the javac error. public class Figure7 { - class Constrain {} + class Constrain {} - final Constrain constrain; - final U u; + final Constrain constrain; + final U u; - Figure7(T t) { - u = coerce(t); - constrain = getConstrain(); - } + Figure7(T t) { + u = coerce(t); + constrain = getConstrain(); + } - U upcast(Constrain constrain, B b) { - return b; - } + U upcast(Constrain constrain, B b) { + return b; + } - U coerce(T t) { - // :: error: method upcast in class Figure7 cannot be applied to given types; - return upcast(constrain, t); - } + U coerce(T t) { + // :: error: method upcast in class Figure7 cannot be applied to given types; + return upcast(constrain, t); + } - Constrain getConstrain() { - return constrain; - } + Constrain getConstrain() { + return constrain; + } - public static void main(String[] args) { - String zero = new Figure7(0).u; - } + public static void main(String[] args) { + String zero = new Figure7(0).u; + } } diff --git a/checker/tests/nullness/java17/Greeting.java b/checker/tests/nullness/java17/Greeting.java index 3507794645e..e9dcf4cd28e 100644 --- a/checker/tests/nullness/java17/Greeting.java +++ b/checker/tests/nullness/java17/Greeting.java @@ -2,29 +2,28 @@ // Test case for https://github.com/typetools/checker-framework/issues/5039 package com.example.hello_world; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; public final class Greeting { - public final @Nullable String name; + public final @Nullable String name; - public Greeting(@Nullable String name) { - this.name = name; - } + public Greeting(@Nullable String name) { + this.name = name; + } - @Override - public int hashCode() { - return Objects.hash(name); - } + @Override + public int hashCode() { + return Objects.hash(name); + } - @Override - public boolean equals(@Nullable Object o) { - return o == this || o instanceof Greeting that && Objects.equals(name, that.name); - } + @Override + public boolean equals(@Nullable Object o) { + return o == this || o instanceof Greeting that && Objects.equals(name, that.name); + } - @Override - public String toString() { - return name == null ? "World" : name; - } + @Override + public String toString() { + return name == null ? "World" : name; + } } diff --git a/checker/tests/nullness/java17/InstanceOfPatternVariable.java b/checker/tests/nullness/java17/InstanceOfPatternVariable.java index 6159498aecb..0c9e300c473 100644 --- a/checker/tests/nullness/java17/InstanceOfPatternVariable.java +++ b/checker/tests/nullness/java17/InstanceOfPatternVariable.java @@ -1,17 +1,16 @@ // @below-java17-jdk-skip-test // Test case for https://github.com/typetools/checker-framework/issues/5240 -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.Map; +import org.checkerframework.checker.nullness.qual.KeyFor; public class InstanceOfPatternVariable { - public void doSomething(final Object x) { - if (x instanceof Map m) { - // final var ct = (ClassOrInterfaceType) type; + public void doSomething(final Object x) { + if (x instanceof Map m) { + // final var ct = (ClassOrInterfaceType) type; - @KeyFor("m") Object y = m.keySet().iterator().next(); - } + @KeyFor("m") Object y = m.keySet().iterator().next(); } + } } diff --git a/checker/tests/nullness/java17/Issue5047.java b/checker/tests/nullness/java17/Issue5047.java index 595f68e5d64..bb320d493f1 100644 --- a/checker/tests/nullness/java17/Issue5047.java +++ b/checker/tests/nullness/java17/Issue5047.java @@ -1,25 +1,24 @@ // @below-java16-jdk-skip-test // Test case for issue #5047: https://tinyurl.com/cfissue/5047 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue5047 {} class NumberParameterBuilder { - @Nullable Object minimum; - @Nullable Object maximum; + @Nullable Object minimum; + @Nullable Object maximum; - public boolean equals(final @Nullable Object o) { + public boolean equals(final @Nullable Object o) { - if (o instanceof NumberParameterBuilder b) { - return super.equals(o) - && Objects.equals(this.minimum, b.minimum) - && Objects.equals(this.maximum, b.maximum); - } else { - return false; - } + if (o instanceof NumberParameterBuilder b) { + return super.equals(o) + && Objects.equals(this.minimum, b.minimum) + && Objects.equals(this.maximum, b.maximum); + } else { + return false; } + } } diff --git a/checker/tests/nullness/java17/Issue5967.java b/checker/tests/nullness/java17/Issue5967.java index 4531b034d8e..78708e93310 100644 --- a/checker/tests/nullness/java17/Issue5967.java +++ b/checker/tests/nullness/java17/Issue5967.java @@ -1,25 +1,24 @@ // @below-java17-jdk-skip-test -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.NonNull; public final class Issue5967 { - enum TestEnum { - FIRST, - SECOND; - } + enum TestEnum { + FIRST, + SECOND; + } - public static void main(String[] args) { - TestEnum testEnum = TestEnum.FIRST; - Supplier supplier = - switch (testEnum) { - case FIRST: - yield () -> 1; - case SECOND: - yield () -> 2; - }; - @NonNull Supplier supplier1 = supplier; - } + public static void main(String[] args) { + TestEnum testEnum = TestEnum.FIRST; + Supplier supplier = + switch (testEnum) { + case FIRST: + yield () -> 1; + case SECOND: + yield () -> 2; + }; + @NonNull Supplier supplier1 = supplier; + } } diff --git a/checker/tests/nullness/java17/NullnessInstanceOf.java b/checker/tests/nullness/java17/NullnessInstanceOf.java index f65b99629e6..3881650ec6d 100644 --- a/checker/tests/nullness/java17/NullnessInstanceOf.java +++ b/checker/tests/nullness/java17/NullnessInstanceOf.java @@ -4,50 +4,50 @@ public class NullnessInstanceOf { - public void testClassicInstanceOfNullable(Object x) { - // :: error: (instanceof.nullable) - if (x instanceof @Nullable String) { - System.out.println("Nullable String instanceof check."); - } + public void testClassicInstanceOfNullable(Object x) { + // :: error: (instanceof.nullable) + if (x instanceof @Nullable String) { + System.out.println("Nullable String instanceof check."); } + } - public void testClassicInstanceOfNonNull(Object x) { - // :: warning: (instanceof.nonnull.redundant) - if (x instanceof @NonNull Number) { - System.out.println("NonNull Number instanceof check."); - } + public void testClassicInstanceOfNonNull(Object x) { + // :: warning: (instanceof.nonnull.redundant) + if (x instanceof @NonNull Number) { + System.out.println("NonNull Number instanceof check."); } + } - public void testPatternVariableNullable(Object x) { - // :: error: (instanceof.nullable) - if (x instanceof @Nullable String n) { - System.out.println("Length of String: " + n.length()); - } + public void testPatternVariableNullable(Object x) { + // :: error: (instanceof.nullable) + if (x instanceof @Nullable String n) { + System.out.println("Length of String: " + n.length()); } + } - public void testPatternVariableNonNull(Object x) { - // :: warning: (instanceof.nonnull.redundant) - if (x instanceof @NonNull Number nn) { - System.out.println("Number's hashCode: " + nn.hashCode()); - } + public void testPatternVariableNonNull(Object x) { + // :: warning: (instanceof.nonnull.redundant) + if (x instanceof @NonNull Number nn) { + System.out.println("Number's hashCode: " + nn.hashCode()); } + } - public void testUnannotatedClassic(Object x) { - if (x instanceof String) { - System.out.println("Unannotated String instanceof check."); - } + public void testUnannotatedClassic(Object x) { + if (x instanceof String) { + System.out.println("Unannotated String instanceof check."); } + } - public void testUnannotatedPatternVariable(Object x) { - if (x instanceof String unannotatedString) { - System.out.println("Unannotated String length: " + unannotatedString.length()); - } - } - - public void testUnusedPatternVariable(Object x) { - // :: error: (instanceof.nullable) - if (x instanceof @Nullable String unusedString) {} - // :: warning: (instanceof.nonnull.redundant) - if (x instanceof @NonNull Number unusedNumber) {} + public void testUnannotatedPatternVariable(Object x) { + if (x instanceof String unannotatedString) { + System.out.println("Unannotated String length: " + unannotatedString.length()); } + } + + public void testUnusedPatternVariable(Object x) { + // :: error: (instanceof.nullable) + if (x instanceof @Nullable String unusedString) {} + // :: warning: (instanceof.nonnull.redundant) + if (x instanceof @NonNull Number unusedNumber) {} + } } diff --git a/checker/tests/nullness/java17/NullnessSwitchArrows.java b/checker/tests/nullness/java17/NullnessSwitchArrows.java index 4ee842d21cb..34f56e79906 100644 --- a/checker/tests/nullness/java17/NullnessSwitchArrows.java +++ b/checker/tests/nullness/java17/NullnessSwitchArrows.java @@ -1,78 +1,78 @@ // @below-java14-jdk-skip-test public class NullnessSwitchArrows { - public enum Day { - SUNDAY, - MONDAY, - TUESDAY, - WEDNESDAY, - THURSDAY, - FRIDAY, - SATURDAY; - } - - void method1() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> o = "hello"; - case TUESDAY -> o = null; - case THURSDAY, SATURDAY -> o = "hello"; - case WEDNESDAY -> o = "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - } + public enum Day { + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY; + } - // :: error: (dereference.of.nullable) - o.toString(); + void method1() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> o = "hello"; + case TUESDAY -> o = null; + case THURSDAY, SATURDAY -> o = "hello"; + case WEDNESDAY -> o = "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); } - void method2() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> o = "hello"; - case TUESDAY -> o = "hello"; - case THURSDAY, SATURDAY -> o = "hello"; - case WEDNESDAY -> o = "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - } + // :: error: (dereference.of.nullable) + o.toString(); + } - o.toString(); + void method2() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> o = "hello"; + case TUESDAY -> o = "hello"; + case THURSDAY, SATURDAY -> o = "hello"; + case WEDNESDAY -> o = "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); } - void method2b() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY: - o = "hello"; - break; - case TUESDAY: - o = "hello"; - break; - case THURSDAY, SATURDAY: - o = "hello"; - break; - case WEDNESDAY: - o = "hello"; - break; - default: - throw new IllegalStateException("Invalid day: " + day); - } + o.toString(); + } - o.toString(); + void method2b() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY: + o = "hello"; + break; + case TUESDAY: + o = "hello"; + break; + case THURSDAY, SATURDAY: + o = "hello"; + break; + case WEDNESDAY: + o = "hello"; + break; + default: + throw new IllegalStateException("Invalid day: " + day); } - void method3() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> o = "hello"; - case TUESDAY -> o = "hello"; - case THURSDAY, SATURDAY -> o = "hello"; - case WEDNESDAY -> o = "hello"; - default -> o = "hello"; - } + o.toString(); + } - o.toString(); + void method3() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> o = "hello"; + case TUESDAY -> o = "hello"; + case THURSDAY, SATURDAY -> o = "hello"; + case WEDNESDAY -> o = "hello"; + default -> o = "hello"; } + + o.toString(); + } } diff --git a/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java b/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java index a42799c0d0d..c54c39d44c5 100644 --- a/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java +++ b/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java @@ -1,20 +1,19 @@ // @below-java14-jdk-skip-test -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; public class NullnessSwitchExpressionLambda { - int anInt; + int anInt; - void switchExprLambda() { - Function f = - (n) -> - switch (n.anInt) { - case 3, 4, 5 -> new Object(); - default -> null; - }; - Object o = f.apply(new NullnessSwitchExpressionLambda()); - // :: error: (dereference.of.nullable) - o.toString(); - } + void switchExprLambda() { + Function f = + (n) -> + switch (n.anInt) { + case 3, 4, 5 -> new Object(); + default -> null; + }; + Object o = f.apply(new NullnessSwitchExpressionLambda()); + // :: error: (dereference.of.nullable) + o.toString(); + } } diff --git a/checker/tests/nullness/java17/NullnessSwitchExpressions.java b/checker/tests/nullness/java17/NullnessSwitchExpressions.java index bc70a587b79..c81971b4e8b 100644 --- a/checker/tests/nullness/java17/NullnessSwitchExpressions.java +++ b/checker/tests/nullness/java17/NullnessSwitchExpressions.java @@ -1,63 +1,63 @@ // @below-java14-jdk-skip-test public class NullnessSwitchExpressions { - public enum Day { - SUNDAY, - MONDAY, - TUESDAY, - WEDNESDAY, - THURSDAY, - FRIDAY, - SATURDAY; - } + public enum Day { + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY; + } - void method1() { - Day day = Day.WEDNESDAY; - Object o = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> "hello"; - case TUESDAY -> null; - case THURSDAY, SATURDAY -> "hello"; - case WEDNESDAY -> "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - }; + void method1() { + Day day = Day.WEDNESDAY; + Object o = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> "hello"; + case TUESDAY -> null; + case THURSDAY, SATURDAY -> "hello"; + case WEDNESDAY -> "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + }; - // :: error: (dereference.of.nullable) - o.toString(); - } + // :: error: (dereference.of.nullable) + o.toString(); + } - void method2() { - Day day = Day.WEDNESDAY; - Object o = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> "hello"; - case TUESDAY -> "hello"; - case THURSDAY, SATURDAY -> "hello"; - case WEDNESDAY -> "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - }; + void method2() { + Day day = Day.WEDNESDAY; + Object o = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> "hello"; + case TUESDAY -> "hello"; + case THURSDAY, SATURDAY -> "hello"; + case WEDNESDAY -> "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + }; - o.toString(); - } + o.toString(); + } - void method3() { - Day day = Day.WEDNESDAY; - Object o = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> "hello"; - case TUESDAY -> "hello"; - case THURSDAY, SATURDAY -> { - String s = null; - if (day == Day.THURSDAY) { - s = "hello"; - s.toString(); - } - yield s; - } - case WEDNESDAY -> "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - }; + void method3() { + Day day = Day.WEDNESDAY; + Object o = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> "hello"; + case TUESDAY -> "hello"; + case THURSDAY, SATURDAY -> { + String s = null; + if (day == Day.THURSDAY) { + s = "hello"; + s.toString(); + } + yield s; + } + case WEDNESDAY -> "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + }; - // :: error: (dereference.of.nullable) - o.toString(); - } + // :: error: (dereference.of.nullable) + o.toString(); + } } diff --git a/checker/tests/nullness/java17/NullnessSwitchStatementRules.java b/checker/tests/nullness/java17/NullnessSwitchStatementRules.java index bf460af93f1..3cb143d46e4 100644 --- a/checker/tests/nullness/java17/NullnessSwitchStatementRules.java +++ b/checker/tests/nullness/java17/NullnessSwitchStatementRules.java @@ -2,42 +2,42 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class NullnessSwitchStatementRules { - @Nullable Object field = null; + @Nullable Object field = null; - void method(int selector) { - field = new Object(); - switch (selector) { - case 1 -> field = null; - case 2 -> field.toString(); - } + void method(int selector) { + field = new Object(); + switch (selector) { + case 1 -> field = null; + case 2 -> field.toString(); + } - field = new Object(); - switch (selector) { - case 1 -> { - field = null; - } - case 2 -> { - field.toString(); - } - } + field = new Object(); + switch (selector) { + case 1 -> { + field = null; + } + case 2 -> { + field.toString(); + } + } - field = new Object(); - switch (selector) { - case 1 -> { - field = null; - } - case 2 -> { - field.toString(); - } - } + field = new Object(); + switch (selector) { + case 1 -> { + field = null; + } + case 2 -> { + field.toString(); + } + } - field = new Object(); - switch (selector) { - case 1: - field = null; - case 2: - // :: error: (dereference.of.nullable) - field.toString(); - } + field = new Object(); + switch (selector) { + case 1: + field = null; + case 2: + // :: error: (dereference.of.nullable) + field.toString(); } + } } diff --git a/checker/tests/nullness/java17/SwitchExpressionInvariant.java b/checker/tests/nullness/java17/SwitchExpressionInvariant.java index c43ab5a23bf..6f5e03a3028 100644 --- a/checker/tests/nullness/java17/SwitchExpressionInvariant.java +++ b/checker/tests/nullness/java17/SwitchExpressionInvariant.java @@ -1,30 +1,27 @@ // @below-java14-jdk-skip-test +import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.List; - public class SwitchExpressionInvariant { - public static boolean flag = false; + public static boolean flag = false; - void method( - List<@NonNull String> nonnullStrings, - List<@Nullable String> nullableStrings, - int fenum) { + void method( + List<@NonNull String> nonnullStrings, List<@Nullable String> nullableStrings, int fenum) { - List<@NonNull String> list = - // :: error: (assignment.type.incompatible) - switch (fenum) { - // :: error: (switch.expression.type.incompatible) - case 1 -> nonnullStrings; - default -> nullableStrings; - }; + List<@NonNull String> list = + // :: error: (assignment.type.incompatible) + switch (fenum) { + // :: error: (switch.expression.type.incompatible) + case 1 -> nonnullStrings; + default -> nullableStrings; + }; - List<@Nullable String> list2 = - switch (fenum) { - // :: error: (switch.expression.type.incompatible) - case 1 -> nonnullStrings; - default -> nullableStrings; - }; - } + List<@Nullable String> list2 = + switch (fenum) { + // :: error: (switch.expression.type.incompatible) + case 1 -> nonnullStrings; + default -> nullableStrings; + }; + } } diff --git a/checker/tests/nullness/java17/SwitchTestIssue5412.java b/checker/tests/nullness/java17/SwitchTestIssue5412.java index d7055c3190f..23c0fa52969 100644 --- a/checker/tests/nullness/java17/SwitchTestIssue5412.java +++ b/checker/tests/nullness/java17/SwitchTestIssue5412.java @@ -5,125 +5,125 @@ import org.checkerframework.checker.nullness.qual.NonNull; enum MyEnum { - VAL1, - VAL2, - VAL3 + VAL1, + VAL2, + VAL3 } class SwitchTestExhaustive { - public String foo1(MyEnum b) { - final var s = - switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; - }; - return s; - } - - public String foo1a(MyEnum b) { - final var s = - switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; - // The default case is dead code, so it would be possible for type-checking - // to skip it and not issue this warning. But giving the warning is also - // good. - default -> null; - }; - // :: error: (return.type.incompatible) - return s; - } - - public String foo2(MyEnum b) { - final var s = - switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; - default -> throw new RuntimeException(); - }; - return s; - } + public String foo1(MyEnum b) { + final var s = + switch (b) { + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + }; + return s; + } - public String foo3(MyEnum b) { - return switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; + public String foo1a(MyEnum b) { + final var s = + switch (b) { + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + // The default case is dead code, so it would be possible for type-checking + // to skip it and not issue this warning. But giving the warning is also + // good. + default -> null; }; - } + // :: error: (return.type.incompatible) + return s; + } - public String foo4(MyEnum b) { - String aString = "foo"; + public String foo2(MyEnum b) { + final var s = switch (b) { - case VAL1: - return "a"; - case VAL2: - return "b"; - case VAL3: - return "c"; - default: - System.out.println(aString.hashCode()); - throw new Error(); - } + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + default -> throw new RuntimeException(); + }; + return s; + } + + public String foo3(MyEnum b) { + return switch (b) { + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + }; + } + + public String foo4(MyEnum b) { + String aString = "foo"; + switch (b) { + case VAL1: + return "a"; + case VAL2: + return "b"; + case VAL3: + return "c"; + default: + System.out.println(aString.hashCode()); + throw new Error(); } + } - public String foo4a(MyEnum b) { - String aString = null; - switch (b) { - case VAL1: - aString = "a"; - break; - case VAL2: - aString = "b"; - break; - case VAL3: - aString = "c"; - break; - // The `default:` case is dead code, so it is acceptable for this method to compile - // without nullness errors. - default: - break; - } - // :: error: (return.type.incompatible) - return aString; + public String foo4a(MyEnum b) { + String aString = null; + switch (b) { + case VAL1: + aString = "a"; + break; + case VAL2: + aString = "b"; + break; + case VAL3: + aString = "c"; + break; + // The `default:` case is dead code, so it is acceptable for this method to compile + // without nullness errors. + default: + break; } + // :: error: (return.type.incompatible) + return aString; + } - public String foo4b(MyEnum b) { - String aString; - switch (b) { - case VAL1: - aString = "a"; - break; - case VAL2: - aString = "b"; - break; - case VAL3: - aString = "c"; - break; - // The `default:` case is dead code, so it is acceptable for this method to compile - // without nullness errors. - default: - aString = null; - break; - } - // :: error: (return.type.incompatible) - return aString; + public String foo4b(MyEnum b) { + String aString; + switch (b) { + case VAL1: + aString = "a"; + break; + case VAL2: + aString = "b"; + break; + case VAL3: + aString = "c"; + break; + // The `default:` case is dead code, so it is acceptable for this method to compile + // without nullness errors. + default: + aString = null; + break; } + // :: error: (return.type.incompatible) + return aString; + } - // TODO: test fallthrough to the default: case. - public @NonNull String foo5(MyEnum b) { - String aString = "foo"; - switch (b) { - case VAL1: - return aString; - case VAL2: - return aString; - case VAL3: - default: - return aString; - } + // TODO: test fallthrough to the default: case. + public @NonNull String foo5(MyEnum b) { + String aString = "foo"; + switch (b) { + case VAL1: + return aString; + case VAL2: + return aString; + case VAL3: + default: + return aString; } + } } diff --git a/checker/tests/nullness/java21/FlowSwitch.java b/checker/tests/nullness/java21/FlowSwitch.java index 82933ec93c2..65519df251e 100644 --- a/checker/tests/nullness/java21/FlowSwitch.java +++ b/checker/tests/nullness/java21/FlowSwitch.java @@ -8,101 +8,101 @@ public class FlowSwitch { - void test0(Number n) { - String s = null; - switch (n) { - default: - { - // TODO: this should issue a dereference of nullable error. - n.toString(); - s = ""; - } + void test0(Number n) { + String s = null; + switch (n) { + default: + { + // TODO: this should issue a dereference of nullable error. + n.toString(); + s = ""; } - s.toString(); } + s.toString(); + } - void test1(Integer i) { - String msg = null; - switch (i) { - case -1, 1: - msg = "-1 or 1"; - break; - case Integer j when j > 0: - msg = "pos"; - break; - case Integer j: - msg = "everything else"; - break; - } - msg.toString(); + void test1(Integer i) { + String msg = null; + switch (i) { + case -1, 1: + msg = "-1 or 1"; + break; + case Integer j when j > 0: + msg = "pos"; + break; + case Integer j: + msg = "everything else"; + break; } + msg.toString(); + } - void test2(Integer i) { - String msg = null; - switch (i) { - case -1, 1: - msg = "-1 or 1"; - break; - default: - msg = "everythingything else"; - break; - case 2: - msg = "pos"; - break; - } - msg.toString(); + void test2(Integer i) { + String msg = null; + switch (i) { + case -1, 1: + msg = "-1 or 1"; + break; + default: + msg = "everythingything else"; + break; + case 2: + msg = "pos"; + break; } + msg.toString(); + } - class A {} + class A {} - class B extends A {} + class B extends A {} - sealed interface I permits C, D {} + sealed interface I permits C, D {} - final class C implements I {} + final class C implements I {} - final class D implements I {} + final class D implements I {} - record Pair(T x, T y) {} + record Pair(T x, T y) {} - void testE(Pair p1) { - B e = - switch (p1) { - case Pair(A a, B b) -> b; - case Pair(B b, A a) -> b; - default -> null; - }; - B e2 = null; + void testE(Pair p1) { + B e = switch (p1) { - case Pair(A a, B b) -> e2 = b; - case Pair(B b, A a) -> e2 = b; - default -> e2 = new B(); - } - e2.toString(); + case Pair(A a, B b) -> b; + case Pair(B b, A a) -> b; + default -> null; + }; + B e2 = null; + switch (p1) { + case Pair(A a, B b) -> e2 = b; + case Pair(B b, A a) -> e2 = b; + default -> e2 = new B(); } + e2.toString(); + } - void test3(Pair p2) { - String s = null; - I e = null; - switch (p2) { - case Pair(I i, C c) -> { - e = c; - s = ""; - } - case Pair(I i, D d) -> { - e = d; - s = ""; - } - } - s.toString(); - e.toString(); + void test3(Pair p2) { + String s = null; + I e = null; + switch (p2) { + case Pair(I i, C c) -> { + e = c; + s = ""; + } + case Pair(I i, D d) -> { + e = d; + s = ""; + } + } + s.toString(); + e.toString(); - I e2 = null; - switch (p2) { - case Pair(C c, I i) -> e2 = c; - case Pair(D d, C c) -> e2 = d; - case Pair(D d1, D d2) -> e2 = d2; - } - e2.toString(); + I e2 = null; + switch (p2) { + case Pair(C c, I i) -> e2 = c; + case Pair(D d, C c) -> e2 = d; + case Pair(D d1, D d2) -> e2 = d2; } + e2.toString(); + } } diff --git a/checker/tests/nullness/java21/Issue6290.java b/checker/tests/nullness/java21/Issue6290.java index 96f421d9587..e44a696ed55 100644 --- a/checker/tests/nullness/java21/Issue6290.java +++ b/checker/tests/nullness/java21/Issue6290.java @@ -3,9 +3,9 @@ // @below-java17-jdk-skip-test public class Issue6290 { - public Optional test(String param) { - var first = Optional.ofNullable(param); - var second = first.isPresent() ? first : Optional.ofNullable(param); - return second; - } + public Optional test(String param) { + var first = Optional.ofNullable(param); + var second = first.isPresent() ? first : Optional.ofNullable(param); + return second; + } } diff --git a/checker/tests/nullness/java21/NullRedundant.java b/checker/tests/nullness/java21/NullRedundant.java index 9a671569014..b4351dfa5a9 100644 --- a/checker/tests/nullness/java21/NullRedundant.java +++ b/checker/tests/nullness/java21/NullRedundant.java @@ -6,107 +6,107 @@ import org.checkerframework.checker.nullness.qual.Nullable; class NullRedundant { - void test1(Object o) { - // :: warning: (nulltest.redundant) - if (o == null) { - System.out.println("o is null"); - } + void test1(Object o) { + // :: warning: (nulltest.redundant) + if (o == null) { + System.out.println("o is null"); + } - switch (o) { - case Number n: - System.out.println("Number: " + n); - break; - // :: warning: (nulltest.redundant) - case null: - System.out.println("null"); - break; - default: - System.out.println("anything else"); - } + switch (o) { + case Number n: + System.out.println("Number: " + n); + break; + // :: warning: (nulltest.redundant) + case null: + System.out.println("null"); + break; + default: + System.out.println("anything else"); + } - switch (o) { - // :: warning: (nulltest.redundant) - case null, default: - System.out.println("null"); - break; - } + switch (o) { + // :: warning: (nulltest.redundant) + case null, default: + System.out.println("null"); + break; } + } + + Object test2(Object o) { + switch (o) { + case Number n -> System.out.println("Number: " + n); + // :: warning: (nulltest.redundant) + case null -> System.out.println("null"); + default -> System.out.println("anything else"); + } + ; - Object test2(Object o) { - switch (o) { - case Number n -> System.out.println("Number: " + n); - // :: warning: (nulltest.redundant) - case null -> System.out.println("null"); - default -> System.out.println("anything else"); - } - ; + switch (o) { + // :: warning: (nulltest.redundant) + case null, default -> System.out.println("null"); + } + var output = switch (o) { - // :: warning: (nulltest.redundant) - case null, default -> System.out.println("null"); - } - - var output = - switch (o) { - case Number n -> "Number: " + n; - // :: warning: (nulltest.redundant) - case null -> "null"; - default -> "anything else"; - }; - - return switch (o) { - // :: warning: (nulltest.redundant) - case null -> "null"; - default -> "anything else"; + case Number n -> "Number: " + n; + // :: warning: (nulltest.redundant) + case null -> "null"; + default -> "anything else"; }; + + return switch (o) { + // :: warning: (nulltest.redundant) + case null -> "null"; + default -> "anything else"; + }; + } + + // Test with Nullable argument to make sure there is no false positive. + void test3(@Nullable Object o) { + if (o == null) { + System.out.println("o is null"); } - // Test with Nullable argument to make sure there is no false positive. - void test3(@Nullable Object o) { - if (o == null) { - System.out.println("o is null"); - } + switch (o) { + case Number n: + System.out.println("Number: " + n); + break; + case null: + System.out.println("null"); + break; + default: + System.out.println("anything else"); + } - switch (o) { - case Number n: - System.out.println("Number: " + n); - break; - case null: - System.out.println("null"); - break; - default: - System.out.println("anything else"); - } + switch (o) { + case null, default: + System.out.println("null"); + break; + } + } - switch (o) { - case null, default: - System.out.println("null"); - break; - } + Object test4(@Nullable Object o) { + switch (o) { + case Number n -> System.out.println("Number: " + n); + case null -> System.out.println("null"); + default -> System.out.println("anything else"); } + ; - Object test4(@Nullable Object o) { - switch (o) { - case Number n -> System.out.println("Number: " + n); - case null -> System.out.println("null"); - default -> System.out.println("anything else"); - } - ; + switch (o) { + case null, default -> System.out.println("null"); + } + var output = switch (o) { - case null, default -> System.out.println("null"); - } - - var output = - switch (o) { - case Number n -> "Number: " + n; - case null -> "null"; - default -> "anything else"; - }; - - return switch (o) { - case null -> "null"; - default -> "anything else"; + case Number n -> "Number: " + n; + case null -> "null"; + default -> "anything else"; }; - } + + return switch (o) { + case null -> "null"; + default -> "anything else"; + }; + } } diff --git a/checker/tests/nullness/java21/NullableSwitchSelector.java b/checker/tests/nullness/java21/NullableSwitchSelector.java index 9cb5a40ecf0..9c4e8bfb2d2 100644 --- a/checker/tests/nullness/java21/NullableSwitchSelector.java +++ b/checker/tests/nullness/java21/NullableSwitchSelector.java @@ -9,34 +9,34 @@ // @infer-stubs-skip-test public class NullableSwitchSelector { - static String formatterPatternSwitch1(@Nullable Object obj) { - return switch (obj) { - case Integer i -> obj.toString(); - case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) - case null -> obj.toString(); - default -> obj.toString(); - }; - } + static String formatterPatternSwitch1(@Nullable Object obj) { + return switch (obj) { + case Integer i -> obj.toString(); + case String s -> String.format("String %s", s); + // :: error: (dereference.of.nullable) + case null -> obj.toString(); + default -> obj.toString(); + }; + } - static String formatterPatternSwitch2(@Nullable Object obj) { - // :: error: (switching.nullable) - return switch (obj) { - case Integer i -> obj.toString(); - case String s -> String.format("String %s", s); - // TODO: If obj is null, this case isn't reachable, because a null pointer exception - // happens at the selector expression. - // :: error: (dereference.of.nullable) - default -> obj.toString(); - }; - } + static String formatterPatternSwitch2(@Nullable Object obj) { + // :: error: (switching.nullable) + return switch (obj) { + case Integer i -> obj.toString(); + case String s -> String.format("String %s", s); + // TODO: If obj is null, this case isn't reachable, because a null pointer exception + // happens at the selector expression. + // :: error: (dereference.of.nullable) + default -> obj.toString(); + }; + } - static String formatterPatternSwitch3(@Nullable Object obj) { - return switch (obj) { - case Integer i -> obj.toString(); - case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) - case null, default -> obj.toString(); - }; - } + static String formatterPatternSwitch3(@Nullable Object obj) { + return switch (obj) { + case Integer i -> obj.toString(); + case String s -> String.format("String %s", s); + // :: error: (dereference.of.nullable) + case null, default -> obj.toString(); + }; + } } diff --git a/checker/tests/nullness/java21/SimpleCaseGuard.java b/checker/tests/nullness/java21/SimpleCaseGuard.java index 9f3ea97d3a4..7d28129e944 100644 --- a/checker/tests/nullness/java21/SimpleCaseGuard.java +++ b/checker/tests/nullness/java21/SimpleCaseGuard.java @@ -11,21 +11,21 @@ public class SimpleCaseGuard { - @Nullable String field; + @Nullable String field; - void test2(Object obj, boolean b) { - switch (obj) { - case String s when field != null -> { - @NonNull String z = field; - } - case String s -> { - // :: error: (assignment.type.incompatible) - @NonNull String z = field; - } - default -> { - // :: error: (assignment.type.incompatible) - @NonNull String z = field; - } - } + void test2(Object obj, boolean b) { + switch (obj) { + case String s when field != null -> { + @NonNull String z = field; + } + case String s -> { + // :: error: (assignment.type.incompatible) + @NonNull String z = field; + } + default -> { + // :: error: (assignment.type.incompatible) + @NonNull String z = field; + } } + } } diff --git a/checker/tests/nullness/java8/DefaultMethods.java b/checker/tests/nullness/java8/DefaultMethods.java index 8dcc96ed18b..de4be578b90 100644 --- a/checker/tests/nullness/java8/DefaultMethods.java +++ b/checker/tests/nullness/java8/DefaultMethods.java @@ -1,15 +1,15 @@ interface DefaultMethods { - default void method(String param) { - // :: error: (assignment.type.incompatible) - param = null; + default void method(String param) { + // :: error: (assignment.type.incompatible) + param = null; - String s = null; - // :: error: (dereference.of.nullable) - s.toString(); + String s = null; + // :: error: (dereference.of.nullable) + s.toString(); - // Ensure dataflow is running - s = ""; - s.toString(); - } + // Ensure dataflow is running + s = ""; + s.toString(); + } } diff --git a/checker/tests/nullness/java8/Issue1000.java b/checker/tests/nullness/java8/Issue1000.java index bdffbc42a62..df048fe39bd 100644 --- a/checker/tests/nullness/java8/Issue1000.java +++ b/checker/tests/nullness/java8/Issue1000.java @@ -1,19 +1,18 @@ // Test case for issue #1000: // https://github.com/typetools/checker-framework/issues/1000 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1000 { - void illegalInstantiation(Optional<@Nullable String> arg) {} + void illegalInstantiation(Optional<@Nullable String> arg) {} - String orElseAppliedToNonNull(Optional opt) { - return opt.orElse(""); - } + String orElseAppliedToNonNull(Optional opt) { + return opt.orElse(""); + } - String orElseAppliedToNullable(Optional opt) { - // :: error: (return.type.incompatible) - return opt.orElse(null); - } + String orElseAppliedToNullable(Optional opt) { + // :: error: (return.type.incompatible) + return opt.orElse(null); + } } diff --git a/checker/tests/nullness/java8/Issue1046Java8.java b/checker/tests/nullness/java8/Issue1046Java8.java index 81a20acc3f8..9a525c09d1b 100644 --- a/checker/tests/nullness/java8/Issue1046Java8.java +++ b/checker/tests/nullness/java8/Issue1046Java8.java @@ -2,36 +2,35 @@ // https://github.com/typetools/checker-framework/issues/1046 // Additonal test case: checker/tests/nullness/Issue1046.java -import org.checkerframework.checker.nullness.qual.UnknownKeyFor; - import java.util.List; import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.UnknownKeyFor; public class Issue1046Java8 { - interface EnumMarker {} + interface EnumMarker {} - enum MyEnum implements EnumMarker { - A, - B; - } + enum MyEnum implements EnumMarker { + A, + B; + } - static class NS2Lists { - @SuppressWarnings("nullness") - static List transform(List p, Function q) { - return null; - } - - static List transform2(List p, Function q) { - return p; - } + static class NS2Lists { + @SuppressWarnings("nullness") + static List transform(List p, Function q) { + return null; } - abstract class NotSubtype2 { - void test(List p) { - NS2Lists.transform2(p, foo()); - NS2Lists.transform(p, foo()); - } + static List transform2(List p, Function q) { + return p; + } + } - abstract Function foo(); + abstract class NotSubtype2 { + void test(List p) { + NS2Lists.transform2(p, foo()); + NS2Lists.transform(p, foo()); } + + abstract Function foo(); + } } diff --git a/checker/tests/nullness/java8/Issue1098.java b/checker/tests/nullness/java8/Issue1098.java index 8067c7b4797..987132be6ed 100644 --- a/checker/tests/nullness/java8/Issue1098.java +++ b/checker/tests/nullness/java8/Issue1098.java @@ -4,16 +4,16 @@ import java.util.Optional; public class Issue1098 { - void opt(Optional p1, T p2) {} + void opt(Optional p1, T p2) {} - void cls(Class p1, T p2) {} + void cls(Class p1, T p2) {} - @SuppressWarnings("keyfor:type.argument") - void use() { - opt(Optional.empty(), null); - // TODO: false positive, because type argument inference does not account for @Covariant. - // See https://github.com/typetools/checker-framework/issues/979. - // :: error: (argument.type.incompatible) - cls(this.getClass(), null); - } + @SuppressWarnings("keyfor:type.argument") + void use() { + opt(Optional.empty(), null); + // TODO: false positive, because type argument inference does not account for @Covariant. + // See https://github.com/typetools/checker-framework/issues/979. + // :: error: (argument.type.incompatible) + cls(this.getClass(), null); + } } diff --git a/checker/tests/nullness/java8/Issue1098NoJdk.java b/checker/tests/nullness/java8/Issue1098NoJdk.java index da7c3ee4f4d..642bc9276a2 100644 --- a/checker/tests/nullness/java8/Issue1098NoJdk.java +++ b/checker/tests/nullness/java8/Issue1098NoJdk.java @@ -3,18 +3,18 @@ @SuppressWarnings({"nullness", "initialization.fields.uninitialized"}) class MyObject { - Class getMyClass() { - return null; - } + Class getMyClass() { + return null; + } } class Issue1098NoJdk { - void cls2(Class p1, T p2) {} + void cls2(Class p1, T p2) {} - void use2(MyObject ths) { - // TODO: false positive, because type argument inference does not account for @Covariant. - // See https://github.com/typetools/checker-framework/issues/979. - // :: error: (argument.type.incompatible) - cls2(ths.getMyClass(), null); - } + void use2(MyObject ths) { + // TODO: false positive, because type argument inference does not account for @Covariant. + // See https://github.com/typetools/checker-framework/issues/979. + // :: error: (argument.type.incompatible) + cls2(ths.getMyClass(), null); + } } diff --git a/checker/tests/nullness/java8/Issue1633.java b/checker/tests/nullness/java8/Issue1633.java index b3a04c57608..846616dcb28 100644 --- a/checker/tests/nullness/java8/Issue1633.java +++ b/checker/tests/nullness/java8/Issue1633.java @@ -1,127 +1,126 @@ // Test case for Issue 1633: // https://github.com/typetools/checker-framework/issues/1633 +import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.Covariant; -import java.util.function.Supplier; - public class Issue1633 { - // supplyNullable is a supplier that may return null. - // supplyNonNull is a supplier that does not return null. - - void foo1(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - // :: error: (argument.type.incompatible) - @Nullable String str = o.orElseGetUnannotated(supplyNullable); - } - - void foo2(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str1 = o.orElseGetNullable(supplyNullable); - } - - void foo2nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str1 = o.orElseGetNullableNoWildcard(supplyNullable); - } - - void foo3(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - // :: error: (argument.type.incompatible) - @Nullable String str2 = o.orElseGetNonNull(supplyNullable); - } - - void foo4(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str3 = o.orElseGetPolyNull(supplyNullable); - } - - void foo4nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str3 = o.orElseGetPolyNullNoWildcard(supplyNullable); - } - - void foo41(Optional1633 o) { - @SuppressWarnings("return.type.incompatible") // https://tinyurl.com/cfissue/979 - @Nullable String str3 = o.orElseGetPolyNull(() -> null); - } - - void foo41nw(Optional1633 o) { - @SuppressWarnings("return.type.incompatible") // https://tinyurl.com/cfissue/979 - @Nullable String str3 = o.orElseGetPolyNullNoWildcard(() -> null); - } - - void foo5(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str = o.orElseGetUnannotated(supplyNonNull); - } - - void foo6(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - // :: error: (assignment.type.incompatible) - @NonNull String str1 = o.orElseGetNullable(supplyNonNull); - } - - void foo6nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - // :: error: (assignment.type.incompatible) - @NonNull String str1 = o.orElseGetNullableNoWildcard(supplyNonNull); - } - - void foo7(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str2 = o.orElseGetNonNull(supplyNonNull); - } - - void foo8(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str3 = o.orElseGetPolyNull(supplyNonNull); - } - - void foo8nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str3 = o.orElseGetPolyNullNoWildcard(supplyNonNull); - } + // supplyNullable is a supplier that may return null. + // supplyNonNull is a supplier that does not return null. + + void foo1(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + // :: error: (argument.type.incompatible) + @Nullable String str = o.orElseGetUnannotated(supplyNullable); + } + + void foo2(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str1 = o.orElseGetNullable(supplyNullable); + } + + void foo2nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str1 = o.orElseGetNullableNoWildcard(supplyNullable); + } + + void foo3(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + // :: error: (argument.type.incompatible) + @Nullable String str2 = o.orElseGetNonNull(supplyNullable); + } + + void foo4(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str3 = o.orElseGetPolyNull(supplyNullable); + } + + void foo4nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str3 = o.orElseGetPolyNullNoWildcard(supplyNullable); + } + + void foo41(Optional1633 o) { + @SuppressWarnings("return.type.incompatible") // https://tinyurl.com/cfissue/979 + @Nullable String str3 = o.orElseGetPolyNull(() -> null); + } + + void foo41nw(Optional1633 o) { + @SuppressWarnings("return.type.incompatible") // https://tinyurl.com/cfissue/979 + @Nullable String str3 = o.orElseGetPolyNullNoWildcard(() -> null); + } + + void foo5(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str = o.orElseGetUnannotated(supplyNonNull); + } + + void foo6(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + // :: error: (assignment.type.incompatible) + @NonNull String str1 = o.orElseGetNullable(supplyNonNull); + } + + void foo6nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + // :: error: (assignment.type.incompatible) + @NonNull String str1 = o.orElseGetNullableNoWildcard(supplyNonNull); + } + + void foo7(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str2 = o.orElseGetNonNull(supplyNonNull); + } + + void foo8(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str3 = o.orElseGetPolyNull(supplyNonNull); + } + + void foo8nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str3 = o.orElseGetPolyNullNoWildcard(supplyNonNull); + } } // From the JDK @Covariant(0) @NonNull final class Optional1633 { - /** If non-null, the value; if null, indicates no value is present. */ - private final @Nullable T value = null; - - // TODO: there are conceptually two versions of this method: - // public @Nullable T orElseGet(Supplier other) { - // public @NonNull T orElseGet(Supplier other) { - // Issue #1633 says that this annotation doesn't help at all: - // public @PolyNull T orElseGet(Supplier other) { - // but it does seem to work in this test case. - public T orElseGetUnannotated(Supplier other) { - return value != null ? value : other.get(); - } - - public @Nullable T orElseGetNullable(Supplier<@Nullable ? extends @Nullable T> other) { - return value != null ? value : other.get(); - } - - public @Nullable T orElseGetNullableNoWildcard(Supplier other) { - // The commented-out line fails to typecheck due to issue #979 - // return value != null ? value : other.get(); - if (value != null) { - return value; - } else { - return other.get(); - } - } - - public @NonNull T orElseGetNonNull(Supplier<@NonNull ? extends @NonNull T> other) { - return value != null ? value : other.get(); - } - - public @PolyNull T orElseGetPolyNull(Supplier<@PolyNull ? extends @PolyNull T> other) { - return value != null ? value : other.get(); - } - - public @PolyNull T orElseGetPolyNullNoWildcard(Supplier other) { - // The commented-out line fails to typecheck due to issue #979 - // return value != null ? value : other.get(); - if (value != null) { - return value; - } else { - return other.get(); - } - } + /** If non-null, the value; if null, indicates no value is present. */ + private final @Nullable T value = null; + + // TODO: there are conceptually two versions of this method: + // public @Nullable T orElseGet(Supplier other) { + // public @NonNull T orElseGet(Supplier other) { + // Issue #1633 says that this annotation doesn't help at all: + // public @PolyNull T orElseGet(Supplier other) { + // but it does seem to work in this test case. + public T orElseGetUnannotated(Supplier other) { + return value != null ? value : other.get(); + } + + public @Nullable T orElseGetNullable(Supplier<@Nullable ? extends @Nullable T> other) { + return value != null ? value : other.get(); + } + + public @Nullable T orElseGetNullableNoWildcard(Supplier other) { + // The commented-out line fails to typecheck due to issue #979 + // return value != null ? value : other.get(); + if (value != null) { + return value; + } else { + return other.get(); + } + } + + public @NonNull T orElseGetNonNull(Supplier<@NonNull ? extends @NonNull T> other) { + return value != null ? value : other.get(); + } + + public @PolyNull T orElseGetPolyNull(Supplier<@PolyNull ? extends @PolyNull T> other) { + return value != null ? value : other.get(); + } + + public @PolyNull T orElseGetPolyNullNoWildcard(Supplier other) { + // The commented-out line fails to typecheck due to issue #979 + // return value != null ? value : other.get(); + if (value != null) { + return value; + } else { + return other.get(); + } + } } diff --git a/checker/tests/nullness/java8/Issue363.java b/checker/tests/nullness/java8/Issue363.java index e20d6dc25e8..900ab481b3d 100644 --- a/checker/tests/nullness/java8/Issue363.java +++ b/checker/tests/nullness/java8/Issue363.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/363 public class Issue363 { - void foo(java.util.OptionalInt value) { - value.orElseThrow(() -> new Error()); - } + void foo(java.util.OptionalInt value) { + value.orElseThrow(() -> new Error()); + } - void bar(java.util.OptionalInt value) { - java.util.function.Supplier s = () -> new Error(); - value.orElseThrow(s); - } + void bar(java.util.OptionalInt value) { + java.util.function.Supplier s = () -> new Error(); + value.orElseThrow(s); + } } diff --git a/checker/tests/nullness/java8/Issue366.java b/checker/tests/nullness/java8/Issue366.java index fa46b9ffb25..01d3a0ff1dc 100644 --- a/checker/tests/nullness/java8/Issue366.java +++ b/checker/tests/nullness/java8/Issue366.java @@ -3,17 +3,16 @@ // but amended for Issue 1098: // https://github.com/typetools/checker-framework/issues/1098 +import java.util.Optional; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Optional; - public class Issue366 { - static Optional<@NonNull String> getPossiblyEmptyString() { - return Optional.ofNullable(null); - } + static Optional<@NonNull String> getPossiblyEmptyString() { + return Optional.ofNullable(null); + } - static Optional<@Nullable String> getPossiblyEmptyString2() { - return Optional.ofNullable(null); - } + static Optional<@Nullable String> getPossiblyEmptyString2() { + return Optional.ofNullable(null); + } } diff --git a/checker/tests/nullness/java8/Issue448.java b/checker/tests/nullness/java8/Issue448.java index a36a32c79ca..271492ef963 100644 --- a/checker/tests/nullness/java8/Issue448.java +++ b/checker/tests/nullness/java8/Issue448.java @@ -4,9 +4,9 @@ import java.util.Arrays; enum Issue448 { - ONE; + ONE; - void method() { - Arrays.stream(values()).filter(key -> true); - } + void method() { + Arrays.stream(values()).filter(key -> true); + } } diff --git a/checker/tests/nullness/java8/Issue448Ext.java b/checker/tests/nullness/java8/Issue448Ext.java index 866b1c08dfa..9356e39814d 100644 --- a/checker/tests/nullness/java8/Issue448Ext.java +++ b/checker/tests/nullness/java8/Issue448Ext.java @@ -6,17 +6,15 @@ import java.util.stream.IntStream; public class Issue448Ext { - void getFor(int[] ia, int index) { - Arrays.stream(ia).filter(x -> true); - } + void getFor(int[] ia, int index) { + Arrays.stream(ia).filter(x -> true); + } - Object getFor(int[] ia, IntPredicate p) { - return Arrays.stream(ia).filter(p); - } + Object getFor(int[] ia, IntPredicate p) { + return Arrays.stream(ia).filter(p); + } - Object getFor(IntStream is, int index) { - return is.filter(key -> key == index) - .findFirst() - .orElseThrow(IllegalArgumentException::new); - } + Object getFor(IntStream is, int index) { + return is.filter(key -> key == index).findFirst().orElseThrow(IllegalArgumentException::new); + } } diff --git a/checker/tests/nullness/java8/Issue496.java b/checker/tests/nullness/java8/Issue496.java index 00961e91ff7..071be672b14 100644 --- a/checker/tests/nullness/java8/Issue496.java +++ b/checker/tests/nullness/java8/Issue496.java @@ -5,17 +5,17 @@ public class Issue496 { - public static class Entity { - public final T value; - public final Class cls; + public static class Entity { + public final T value; + public final Class cls; - public Entity(T value, Class cls) { - this.value = value; - this.cls = cls; - } + public Entity(T value, Class cls) { + this.value = value; + this.cls = cls; } + } - public static Optional> testCase(Class targetClass) { - return Optional.empty().map((T val) -> new Entity(val, targetClass)); - } + public static Optional> testCase(Class targetClass) { + return Optional.empty().map((T val) -> new Entity(val, targetClass)); + } } diff --git a/checker/tests/nullness/java8/Issue529.java b/checker/tests/nullness/java8/Issue529.java index 3ea4b4d70e5..158f91a5b22 100644 --- a/checker/tests/nullness/java8/Issue529.java +++ b/checker/tests/nullness/java8/Issue529.java @@ -6,18 +6,18 @@ public class Issue529 { - // Crashes: - public Stream test(List list) { - return list.stream().map(e -> e); - } + // Crashes: + public Stream test(List list) { + return list.stream().map(e -> e); + } - // OK: - public Stream test2(List list) { - return list.stream(); - } + // OK: + public Stream test2(List list) { + return list.stream(); + } - // OK: - public Stream test3(Stream stream) { - return stream.map(e -> e); - } + // OK: + public Stream test3(Stream stream) { + return stream.map(e -> e); + } } diff --git a/checker/tests/nullness/java8/Issue557.java b/checker/tests/nullness/java8/Issue557.java index 706f2ddffb6..9fed28c2145 100644 --- a/checker/tests/nullness/java8/Issue557.java +++ b/checker/tests/nullness/java8/Issue557.java @@ -1,89 +1,88 @@ // Test case for issue 557: // https://github.com/typetools/checker-framework/issues/557 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("nullness") class MyOpt { - static MyOpt of(S p) { - return null; - } + static MyOpt of(S p) { + return null; + } - static MyOpt empty() { - return null; - } + static MyOpt empty() { + return null; + } } @SuppressWarnings("nullness") class MyOpt2 { - static MyOpt2 of(S p) { - return null; - } + static MyOpt2 of(S p) { + return null; + } - static MyOpt2 empty() { - return null; - } + static MyOpt2 empty() { + return null; + } } @SuppressWarnings("nullness") class MyOpt3 { - static MyOpt3 of(S p) { - return null; - } + static MyOpt3 of(S p) { + return null; + } - static MyOpt3 empty() { - return null; - } + static MyOpt3 empty() { + return null; + } } class Issue557a { - MyOpt opt(boolean flag) { - return flag ? MyOpt.of("Hello") : MyOpt.empty(); - } + MyOpt opt(boolean flag) { + return flag ? MyOpt.of("Hello") : MyOpt.empty(); + } - MyOpt opt2() { - return MyOpt.empty(); - } + MyOpt opt2() { + return MyOpt.empty(); + } - MyOpt opt3(boolean flag) { - return flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty()); - } + MyOpt opt3(boolean flag) { + return flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty()); + } - void foo(MyOpt param) {} + void foo(MyOpt param) {} - void callFoo(boolean flag) { - foo(flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty())); - } + void callFoo(boolean flag) { + foo(flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty())); + } } class Issue557b { - MyOpt2 opt(boolean flag) { - return flag ? MyOpt2.of("Hello") : MyOpt2.empty(); - } + MyOpt2 opt(boolean flag) { + return flag ? MyOpt2.of("Hello") : MyOpt2.empty(); + } - MyOpt2 opt2() { - return MyOpt2.empty(); - } + MyOpt2 opt2() { + return MyOpt2.empty(); + } } class Issue557c { - MyOpt3 opt(boolean flag) { - return flag ? MyOpt3.of("Hello") : MyOpt3.empty(); - } + MyOpt3 opt(boolean flag) { + return flag ? MyOpt3.of("Hello") : MyOpt3.empty(); + } - MyOpt3 opt2() { - return MyOpt3.empty(); - } + MyOpt3 opt2() { + return MyOpt3.empty(); + } } class Issue557d { - Optional opt(boolean flag) { - return flag ? Optional.of("Hello") : Optional.empty(); - } + Optional opt(boolean flag) { + return flag ? Optional.of("Hello") : Optional.empty(); + } - Optional opt2() { - return Optional.empty(); - } + Optional opt2() { + return Optional.empty(); + } } diff --git a/checker/tests/nullness/java8/Issue579.java b/checker/tests/nullness/java8/Issue579.java index b658af227f0..7e25ded7780 100644 --- a/checker/tests/nullness/java8/Issue579.java +++ b/checker/tests/nullness/java8/Issue579.java @@ -4,21 +4,21 @@ import java.util.Comparator; public class Issue579 implements Comparator { - private final Comparator real; + private final Comparator real; - @SuppressWarnings("unchecked") - Issue579(Comparator real) { - this.real = (Comparator) real; - } + @SuppressWarnings("unchecked") + Issue579(Comparator real) { + this.real = (Comparator) real; + } - @Override - public int compare(T a, T b) { - throw new RuntimeException(); - } + @Override + public int compare(T a, T b) { + throw new RuntimeException(); + } - @Override - public Comparator thenComparing(Comparator other) { - // :: warning: (nulltest.redundant) - return new Issue579<>(real == null ? other : real.thenComparing(other)); - } + @Override + public Comparator thenComparing(Comparator other) { + // :: warning: (nulltest.redundant) + return new Issue579<>(real == null ? other : real.thenComparing(other)); + } } diff --git a/checker/tests/nullness/java8/Issue596.java b/checker/tests/nullness/java8/Issue596.java index cc52b1cd7a8..c1978e9ea16 100644 --- a/checker/tests/nullness/java8/Issue596.java +++ b/checker/tests/nullness/java8/Issue596.java @@ -1,25 +1,24 @@ // Test case for Issue 596: // https://github.com/typetools/checker-framework/issues/596 -import org.checkerframework.checker.nullness.qual.*; - import java.util.concurrent.atomic.AtomicReference; +import org.checkerframework.checker.nullness.qual.*; public class Issue596 { - private static String getOrEmpty(AtomicReference<@Nullable String> ref) { - return Optional596.fromNullable(ref.get()).or(""); - } + private static String getOrEmpty(AtomicReference<@Nullable String> ref) { + return Optional596.fromNullable(ref.get()).or(""); + } } // From Google Guava class Optional596 { - public static Optional596 fromNullable(@Nullable T nullableReference) { - return new Optional596(); - } + public static Optional596 fromNullable(@Nullable T nullableReference) { + return new Optional596(); + } - public T or(T defaultValue) { - return defaultValue; - } + public T or(T defaultValue) { + return defaultValue; + } } diff --git a/checker/tests/nullness/java8/Issue704.java b/checker/tests/nullness/java8/Issue704.java index 905e668bef4..401c8db90a7 100644 --- a/checker/tests/nullness/java8/Issue704.java +++ b/checker/tests/nullness/java8/Issue704.java @@ -4,5 +4,5 @@ import java.util.function.IntSupplier; interface Issue704 { - IntSupplier zero = () -> 0; + IntSupplier zero = () -> 0; } diff --git a/checker/tests/nullness/java8/Issue720.java b/checker/tests/nullness/java8/Issue720.java index d747160f515..92beb01ad41 100644 --- a/checker/tests/nullness/java8/Issue720.java +++ b/checker/tests/nullness/java8/Issue720.java @@ -4,9 +4,9 @@ import java.util.function.IntConsumer; public class Issue720 { - static IntConsumer consumer = Issue720::method; + static IntConsumer consumer = Issue720::method; - static int method(int x) { - return x; - } + static int method(int x) { + return x; + } } diff --git a/checker/tests/nullness/java8/UnionTypeBug.java b/checker/tests/nullness/java8/UnionTypeBug.java index 4d441a037fc..4140e30dcdc 100644 --- a/checker/tests/nullness/java8/UnionTypeBug.java +++ b/checker/tests/nullness/java8/UnionTypeBug.java @@ -9,32 +9,31 @@ abstract class UnionTypeBug { - void method() { - try { + void method() { + try { - badBoy(); + badBoy(); - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull InnerException1 | @NonNull InnerException2 e) { + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull InnerException1 | @NonNull InnerException2 e) { - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull InnerException3 | @NonNull InnerException4 e) { + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull InnerException3 | @NonNull InnerException4 e) { - } } + } - abstract void badBoy() - throws InnerException1, InnerException2, InnerException3, InnerException4; + abstract void badBoy() throws InnerException1, InnerException2, InnerException3, InnerException4; - @PolyUIType - class InnerException1 extends Exception {} + @PolyUIType + class InnerException1 extends Exception {} - @PolyUIType - class InnerException2 extends Exception {} + @PolyUIType + class InnerException2 extends Exception {} - @PolyUIType - class InnerException3 extends Exception {} + @PolyUIType + class InnerException3 extends Exception {} - @PolyUIType - class InnerException4 extends Exception {} + @PolyUIType + class InnerException4 extends Exception {} } diff --git a/checker/tests/nullness/java8/lambda/Dataflow.java b/checker/tests/nullness/java8/lambda/Dataflow.java index 0eae76bdbb4..49946fce85f 100644 --- a/checker/tests/nullness/java8/lambda/Dataflow.java +++ b/checker/tests/nullness/java8/lambda/Dataflow.java @@ -3,18 +3,18 @@ import org.checkerframework.checker.nullness.qual.*; public class Dataflow { - void context() { - FunctionDF<@Nullable Object, Object> o = - a -> { - // :: error: (dereference.of.nullable) - a.toString(); - a = ""; - a.toString(); - return ""; - }; - } + void context() { + FunctionDF<@Nullable Object, Object> o = + a -> { + // :: error: (dereference.of.nullable) + a.toString(); + a = ""; + a.toString(); + return ""; + }; + } } interface FunctionDF { - R apply(T t); + R apply(T t); } diff --git a/checker/tests/nullness/java8/lambda/FinalLocalVariables.java b/checker/tests/nullness/java8/lambda/FinalLocalVariables.java index b73365e1d8b..aa04782bfe9 100644 --- a/checker/tests/nullness/java8/lambda/FinalLocalVariables.java +++ b/checker/tests/nullness/java8/lambda/FinalLocalVariables.java @@ -3,101 +3,101 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionLE { - R apply(T t); + R apply(T t); } class LambdaEnclosing { - // Test static initializer - static { + // Test static initializer + static { + String local1 = ""; + String local2 = null; + FunctionLE f0 = + s -> { + local1.toString(); + // :: error: (dereference.of.nullable) + local2.toString(); + return ""; + }; + } + + // Test instance initializer + { + String local1 = ""; + String local2 = null; + FunctionLE f0 = + s -> { + local1.toString(); + // :: error: (dereference.of.nullable) + local2.toString(); + return ""; + }; + } + + FunctionLE functionField = + s -> { String local1 = ""; String local2 = null; FunctionLE f0 = - s -> { + s2 -> { + local1.toString(); + // :: error: (dereference.of.nullable) + local2.toString(); + return ""; + }; + return ""; + }; + + void context() { + String local1 = ""; + String local2 = null; + + FunctionLE f1 = + s -> { + local1.toString(); + // :: error: (dereference.of.nullable) + local2.toString(); + class Inner { + + void context2() { + String local3 = ""; + String local4 = null; + + FunctionLE f2 = + s2 -> { local1.toString(); - // :: error: (dereference.of.nullable) local2.toString(); - return ""; - }; - } - - // Test instance initializer - { - String local1 = ""; - String local2 = null; - FunctionLE f0 = - s -> { - local1.toString(); + local3.toString(); // :: error: (dereference.of.nullable) - local2.toString(); + local4.toString(); + return ""; - }; - } - - FunctionLE functionField = - s -> { - String local1 = ""; - String local2 = null; - FunctionLE f0 = - s2 -> { - local1.toString(); - // :: error: (dereference.of.nullable) - local2.toString(); - return ""; - }; - return ""; - }; + }; + } + } - void context() { - String local1 = ""; - String local2 = null; + new Object() { + + @Override() + public String toString() { + String local3 = ""; + String local4 = null; - FunctionLE f1 = - s -> { + FunctionLE f2 = + s2 -> { local1.toString(); - // :: error: (dereference.of.nullable) local2.toString(); - class Inner { - - void context2() { - String local3 = ""; - String local4 = null; - - FunctionLE f2 = - s2 -> { - local1.toString(); - local2.toString(); - local3.toString(); - // :: error: (dereference.of.nullable) - local4.toString(); - - return ""; - }; - } - } - - new Object() { - - @Override() - public String toString() { - String local3 = ""; - String local4 = null; - - FunctionLE f2 = - s2 -> { - local1.toString(); - local2.toString(); - local3.toString(); - // :: error: (dereference.of.nullable) - local4.toString(); - - return ""; - }; - return ""; - } - }.toString(); + local3.toString(); + // :: error: (dereference.of.nullable) + local4.toString(); return ""; - }; - } + }; + return ""; + } + }.toString(); + + return ""; + }; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue1864.java b/checker/tests/nullness/java8/lambda/Issue1864.java index 77952073622..895efc088ca 100644 --- a/checker/tests/nullness/java8/lambda/Issue1864.java +++ b/checker/tests/nullness/java8/lambda/Issue1864.java @@ -5,14 +5,14 @@ import java.util.function.Supplier; abstract class Issue1864 { - interface A {} + interface A {} - abstract List g(); + abstract List g(); - abstract void h(Supplier> s); + abstract void h(Supplier> s); - void f() { - Iterable xs = g(); - h(() -> xs); - } + void f() { + Iterable xs = g(); + h(() -> xs); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue1864b.java b/checker/tests/nullness/java8/lambda/Issue1864b.java index 074488ffd7b..0b6c6ab3ab3 100644 --- a/checker/tests/nullness/java8/lambda/Issue1864b.java +++ b/checker/tests/nullness/java8/lambda/Issue1864b.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/1864 public class Issue1864b { - interface Supplier { - Object get(); - } + interface Supplier { + Object get(); + } - Supplier foo() { - Object foo = new Object(); - return () -> foo; - } + Supplier foo() { + Object foo = new Object(); + return () -> foo; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue1897.java b/checker/tests/nullness/java8/lambda/Issue1897.java index aba8e531382..0aefd11ce2f 100644 --- a/checker/tests/nullness/java8/lambda/Issue1897.java +++ b/checker/tests/nullness/java8/lambda/Issue1897.java @@ -4,10 +4,10 @@ import java.util.function.Function; public class Issue1897 { - Issue1897() { - final int length = 1; - takesLambda(s -> length); - } + Issue1897() { + final int length = 1; + takesLambda(s -> length); + } - static void takesLambda(Function function) {} + static void takesLambda(Function function) {} } diff --git a/checker/tests/nullness/java8/lambda/Issue3217.java b/checker/tests/nullness/java8/lambda/Issue3217.java index b1540e06687..2af70615bb7 100644 --- a/checker/tests/nullness/java8/lambda/Issue3217.java +++ b/checker/tests/nullness/java8/lambda/Issue3217.java @@ -1,21 +1,20 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3217 { - private final Function, Function> - proxyFunction; + private final Function, Function> + proxyFunction; - public Issue3217( - Function, Function> - proxyFunction) { - this.proxyFunction = proxyFunction; - } + public Issue3217( + Function, Function> + proxyFunction) { + this.proxyFunction = proxyFunction; + } } class SubClass extends Issue3217 { - public SubClass() { - super(x -> x); - Function, Function> p = y -> y; - } + public SubClass() { + super(x -> x); + Function, Function> p = y -> y; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue367.java b/checker/tests/nullness/java8/lambda/Issue367.java index 816cc13d6a0..cad34103a99 100644 --- a/checker/tests/nullness/java8/lambda/Issue367.java +++ b/checker/tests/nullness/java8/lambda/Issue367.java @@ -1,7 +1,7 @@ // Test case for Issue 367: // https://github.com/typetools/checker-framework/issues/367 public class Issue367 { - static void test(Iterable threads) { - threads.forEach(thread -> System.out.println(thread)); - } + static void test(Iterable threads) { + threads.forEach(thread -> System.out.println(thread)); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue403.java b/checker/tests/nullness/java8/lambda/Issue403.java index 6bb0a82f8a5..3b84fc9f85c 100644 --- a/checker/tests/nullness/java8/lambda/Issue403.java +++ b/checker/tests/nullness/java8/lambda/Issue403.java @@ -4,11 +4,11 @@ import java.util.Comparator; public class Issue403 { - Comparator COMPARATOR = Comparator.comparing(w -> w.value); + Comparator COMPARATOR = Comparator.comparing(w -> w.value); - String value; + String value; - Issue403(final String value) { - this.value = value; - } + Issue403(final String value) { + this.value = value; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue436.java b/checker/tests/nullness/java8/lambda/Issue436.java index 5710d916e0b..2dddaa80be1 100644 --- a/checker/tests/nullness/java8/lambda/Issue436.java +++ b/checker/tests/nullness/java8/lambda/Issue436.java @@ -4,16 +4,16 @@ import java.util.function.Supplier; public class Issue436 { - public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) { - // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the - // lambda return for those return statements below - Supplier> supplier = - () -> { - if (makeAll) { - return asList("beer", "peanuts"); - } else { - return asList("cheese", "wine"); - } - }; - } + public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) { + // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the + // lambda return for those return statements below + Supplier> supplier = + () -> { + if (makeAll) { + return asList("beer", "peanuts"); + } else { + return asList("cheese", "wine"); + } + }; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue572.java b/checker/tests/nullness/java8/lambda/Issue572.java index c2a0bbd4049..0569b67aa12 100644 --- a/checker/tests/nullness/java8/lambda/Issue572.java +++ b/checker/tests/nullness/java8/lambda/Issue572.java @@ -1,17 +1,16 @@ // Test case for issue #572: https://github.com/typetools/checker-framework/issues/572 +import java.util.function.BiFunction; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.function.BiFunction; - public class Issue572 { - static C biApply(BiFunction f, A a, B b) { - return f.apply(a, b); - } + static C biApply(BiFunction f, A a, B b) { + return f.apply(a, b); + } - public A konst(@NonNull A a, @Nullable B b) { - // :: error: (argument.type.incompatible) - return biApply(((first, second) -> first), a, b); - } + public A konst(@NonNull A a, @Nullable B b) { + // :: error: (argument.type.incompatible) + return biApply(((first, second) -> first), a, b); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue870.java b/checker/tests/nullness/java8/lambda/Issue870.java index 9ee246006f4..389c3ac4aca 100644 --- a/checker/tests/nullness/java8/lambda/Issue870.java +++ b/checker/tests/nullness/java8/lambda/Issue870.java @@ -5,15 +5,13 @@ import java.util.zip.ZipFile; public class Issue870 { - public static Stream entries(ZipFile zipFile) { - return zipFile.stream() - .filter(entry -> !entry.isDirectory() && entry.getName().endsWith(".xml")); - } + public static Stream entries(ZipFile zipFile) { + return zipFile.stream() + .filter(entry -> !entry.isDirectory() && entry.getName().endsWith(".xml")); + } - public static Stream entries2(ZipFile zipFile) { - return zipFile.stream() - .filter( - (ZipEntry entry) -> - !entry.isDirectory() && entry.getName().endsWith(".xml")); - } + public static Stream entries2(ZipFile zipFile) { + return zipFile.stream() + .filter((ZipEntry entry) -> !entry.isDirectory() && entry.getName().endsWith(".xml")); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue953bLambda.java b/checker/tests/nullness/java8/lambda/Issue953bLambda.java index 50b93240f18..ee855686bac 100644 --- a/checker/tests/nullness/java8/lambda/Issue953bLambda.java +++ b/checker/tests/nullness/java8/lambda/Issue953bLambda.java @@ -1,27 +1,26 @@ // Test case for #953 // https://github.com/typetools/checker-framework/issues/953 -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.NonNull; @SuppressWarnings("all") public class Issue953bLambda { - private static List> strs = new ArrayList<>(); + private static List> strs = new ArrayList<>(); - public static List<@NonNull R> mapList( - List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { - throw new RuntimeException(); - } + public static List<@NonNull R> mapList( + List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { + throw new RuntimeException(); + } - public static void test() { - List list = - mapList( - strs, - s -> { - return ""; - }); - } + public static void test() { + List list = + mapList( + strs, + s -> { + return ""; + }); + } } diff --git a/checker/tests/nullness/java8/lambda/LambdaNullness.java b/checker/tests/nullness/java8/lambda/LambdaNullness.java index 74377dbd719..18688ff8cbd 100644 --- a/checker/tests/nullness/java8/lambda/LambdaNullness.java +++ b/checker/tests/nullness/java8/lambda/LambdaNullness.java @@ -3,107 +3,107 @@ import org.checkerframework.checker.nullness.qual.*; interface Noop { - void noop(); + void noop(); } interface FunctionNull { - R apply(T t); + R apply(T t); } interface Supplier { - R supply(); + R supply(); } interface BiFunctionNull { - R apply(T t, U u); + R apply(T t, U u); } public class LambdaNullness { - // Annotations in lamba expressions, in static, instance of fields initializers are stored on - // the last declared constructor. - // - // For example, the annotation for @Nullable Integer x on f7's initializer - // is stored on here because it is the last defined constructor. - // - // See TypeFromElement::annotateParam - LambdaNullness(FunctionNull f, Object e) {} - - // No parameters; result is void - Noop f1 = () -> {}; - // No parameters, expression body - Supplier f2a = () -> 42; - - // No parameters, expression body - // :: error: (return.type.incompatible) - Supplier f2b = () -> null; - - // No parameters, expression body - Supplier<@Nullable Void> f3 = () -> null; - // No parameters, block body with return - Supplier f4a = - () -> { - return 42; - }; - // No parameters, block body with return - Supplier<@Nullable Integer> f4b = - () -> { - // :: error: (assignment.type.incompatible) - @NonNull String s = null; - - return null; - }; - // No parameters, void block body - Noop f5 = - () -> { - System.gc(); - }; - - // Complex block body with returns - Supplier f6 = - () -> { - if (true) { - return 12; - } else { - int result = 15; - for (int i = 1; i < 10; i++) { - result *= i; - } - // :: error: (return.type.incompatible) - return null; - } - }; - - // Single declared-type parameter - FunctionNull<@Nullable Integer, Integer> f7 = (@Nullable Integer x) -> 1; - - // Single declared-type parameter - FunctionNull<@Nullable String, String> f9 = - // :: error: (lambda.param.type.incompatible) - (@NonNull String x) -> { - return x + ""; - }; - // Single inferred-type parameter - FunctionNull<@NonNull Integer, Integer> f10 = (x) -> x + 1; - // Parentheses optional for single - FunctionNull<@Nullable Integer, Integer> f11 = x -> 1; - - // Multiple declared-type parameters - BiFunctionNull f16 = - (@Nullable Integer x, final Integer y) -> { - x = null; - // :: error: (unboxing.of.nullable) - return x + y; - }; - - // Multiple inferred-type parameters - BiFunctionNull f18 = (x, y) -> x + y; - - // Infer based on context. - FunctionNull<@Nullable String, String> fn = - (s) -> { - // :: error: (dereference.of.nullable) - s.toString(); - return ""; - }; + // Annotations in lamba expressions, in static, instance of fields initializers are stored on + // the last declared constructor. + // + // For example, the annotation for @Nullable Integer x on f7's initializer + // is stored on here because it is the last defined constructor. + // + // See TypeFromElement::annotateParam + LambdaNullness(FunctionNull f, Object e) {} + + // No parameters; result is void + Noop f1 = () -> {}; + // No parameters, expression body + Supplier f2a = () -> 42; + + // No parameters, expression body + // :: error: (return.type.incompatible) + Supplier f2b = () -> null; + + // No parameters, expression body + Supplier<@Nullable Void> f3 = () -> null; + // No parameters, block body with return + Supplier f4a = + () -> { + return 42; + }; + // No parameters, block body with return + Supplier<@Nullable Integer> f4b = + () -> { + // :: error: (assignment.type.incompatible) + @NonNull String s = null; + + return null; + }; + // No parameters, void block body + Noop f5 = + () -> { + System.gc(); + }; + + // Complex block body with returns + Supplier f6 = + () -> { + if (true) { + return 12; + } else { + int result = 15; + for (int i = 1; i < 10; i++) { + result *= i; + } + // :: error: (return.type.incompatible) + return null; + } + }; + + // Single declared-type parameter + FunctionNull<@Nullable Integer, Integer> f7 = (@Nullable Integer x) -> 1; + + // Single declared-type parameter + FunctionNull<@Nullable String, String> f9 = + // :: error: (lambda.param.type.incompatible) + (@NonNull String x) -> { + return x + ""; + }; + // Single inferred-type parameter + FunctionNull<@NonNull Integer, Integer> f10 = (x) -> x + 1; + // Parentheses optional for single + FunctionNull<@Nullable Integer, Integer> f11 = x -> 1; + + // Multiple declared-type parameters + BiFunctionNull f16 = + (@Nullable Integer x, final Integer y) -> { + x = null; + // :: error: (unboxing.of.nullable) + return x + y; + }; + + // Multiple inferred-type parameters + BiFunctionNull f18 = (x, y) -> x + y; + + // Infer based on context. + FunctionNull<@Nullable String, String> fn = + (s) -> { + // :: error: (dereference.of.nullable) + s.toString(); + return ""; + }; } diff --git a/checker/tests/nullness/java8/lambda/Parameters.java b/checker/tests/nullness/java8/lambda/Parameters.java index bdc6bfca4e7..3fa4b8a2abb 100644 --- a/checker/tests/nullness/java8/lambda/Parameters.java +++ b/checker/tests/nullness/java8/lambda/Parameters.java @@ -3,68 +3,68 @@ import org.checkerframework.checker.nullness.qual.*; interface NullConsumer { - void method(@Nullable String s); + void method(@Nullable String s); } interface NNConsumer { - void method(@NonNull String s); + void method(@NonNull String s); } class LambdaParam { - NullConsumer fn1 = - // :: error: (lambda.param.type.incompatible) - (@NonNull String i) -> {}; + NullConsumer fn1 = + // :: error: (lambda.param.type.incompatible) + (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; + // :: error: (lambda.param.type.incompatible) + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + + { + // :: error: (lambda.param.type.incompatible) + NullConsumer fn1 = (@NonNull String i) -> {}; NullConsumer fn2 = (@Nullable String i) -> {}; // :: error: (lambda.param.type.incompatible) NullConsumer fn3 = (String i) -> {}; NNConsumer fn4 = (String i) -> {}; NNConsumer fn5 = (@Nullable String i) -> {}; NNConsumer fn6 = (@NonNull String i) -> {}; + } - { - // :: error: (lambda.param.type.incompatible) - NullConsumer fn1 = (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; - // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - } - - static { - // :: error: (lambda.param.type.incompatible) - NullConsumer fn1 = (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; - // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - } + static { + // :: error: (lambda.param.type.incompatible) + NullConsumer fn1 = (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; + // :: error: (lambda.param.type.incompatible) + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + } - static void foo() { - NullConsumer fn1 = - // :: error: (lambda.param.type.incompatible) - (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; + static void foo() { + NullConsumer fn1 = // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - } + (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; + // :: error: (lambda.param.type.incompatible) + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + } - void bar() { - NullConsumer fn1 = - // :: error: (lambda.param.type.incompatible) - (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; + void bar() { + NullConsumer fn1 = // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - } + (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; + // :: error: (lambda.param.type.incompatible) + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + } } diff --git a/checker/tests/nullness/java8/lambda/ParametersInBody.java b/checker/tests/nullness/java8/lambda/ParametersInBody.java index 234793a2a3a..a5932aecbed 100644 --- a/checker/tests/nullness/java8/lambda/ParametersInBody.java +++ b/checker/tests/nullness/java8/lambda/ParametersInBody.java @@ -3,48 +3,48 @@ import org.checkerframework.checker.nullness.qual.*; interface ConsumerLPB { - void method(@Nullable String s); + void method(@Nullable String s); } interface NNConsumerLPB { - void method(@NonNull String s); + void method(@NonNull String s); } class LambdaParamBody { - // :: error: (lambda.param.type.incompatible) - ConsumerLPB fn0 = (String i) -> i.toString(); - ConsumerLPB fn2 = - (@Nullable String i) -> { - // :: error: (dereference.of.nullable) - i.toString(); - }; - ConsumerLPB fn3 = - // :: error: (lambda.param.type.incompatible) - (String i) -> { - i.toString(); - }; - ConsumerLPB fn3b = - (i) -> { - // :: error: (dereference.of.nullable) - i.toString(); - }; + // :: error: (lambda.param.type.incompatible) + ConsumerLPB fn0 = (String i) -> i.toString(); + ConsumerLPB fn2 = + (@Nullable String i) -> { + // :: error: (dereference.of.nullable) + i.toString(); + }; + ConsumerLPB fn3 = + // :: error: (lambda.param.type.incompatible) + (String i) -> { + i.toString(); + }; + ConsumerLPB fn3b = + (i) -> { + // :: error: (dereference.of.nullable) + i.toString(); + }; - NNConsumerLPB fn4 = - (String i) -> { - i.toString(); - }; - NNConsumerLPB fn4b = - (i) -> { - i.toString(); - }; - NNConsumerLPB fn5 = - (@Nullable String i) -> { - // :: error: (dereference.of.nullable) - i.toString(); - }; - NNConsumerLPB fn6 = - (@NonNull String i) -> { - i.toString(); - }; + NNConsumerLPB fn4 = + (String i) -> { + i.toString(); + }; + NNConsumerLPB fn4b = + (i) -> { + i.toString(); + }; + NNConsumerLPB fn5 = + (@Nullable String i) -> { + // :: error: (dereference.of.nullable) + i.toString(); + }; + NNConsumerLPB fn6 = + (@NonNull String i) -> { + i.toString(); + }; } diff --git a/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java b/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java index c7ae4909590..f847f64a228 100644 --- a/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java +++ b/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java @@ -1,44 +1,43 @@ // Test that parameter annotations are correct in the body of a lambda -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public class ParametersInBodyGenerics { - interface NullableConsumer { - void method(List<@Nullable String> s); - } + interface NullableConsumer { + void method(List<@Nullable String> s); + } - interface NonNullConsumer { - void method(@NonNull List s); - } + interface NonNullConsumer { + void method(@NonNull List s); + } - void test() { + void test() { + // :: error: (lambda.param.type.incompatible) + NullableConsumer fn0 = (List i) -> i.get(0).toString(); + NullableConsumer fn2 = + (List<@Nullable String> i) -> { + // :: error: (dereference.of.nullable) + i.get(0).toString(); + }; + NullableConsumer fn3 = // :: error: (lambda.param.type.incompatible) - NullableConsumer fn0 = (List i) -> i.get(0).toString(); - NullableConsumer fn2 = - (List<@Nullable String> i) -> { - // :: error: (dereference.of.nullable) - i.get(0).toString(); - }; - NullableConsumer fn3 = - // :: error: (lambda.param.type.incompatible) - (List i) -> { - i.get(0).toString(); - }; - NullableConsumer fn3b = - (i) -> { - // :: error: (dereference.of.nullable) - i.get(0).toString(); - }; + (List i) -> { + i.get(0).toString(); + }; + NullableConsumer fn3b = + (i) -> { + // :: error: (dereference.of.nullable) + i.get(0).toString(); + }; - NonNullConsumer fn4 = - (List i) -> { - i.get(0).toString(); - }; - NonNullConsumer fn4b = - (i) -> { - i.get(0).toString(); - }; - } + NonNullConsumer fn4 = + (List i) -> { + i.get(0).toString(); + }; + NonNullConsumer fn4b = + (i) -> { + i.get(0).toString(); + }; + } } diff --git a/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java b/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java index f723d6dc61f..b9370f609e9 100644 --- a/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java +++ b/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java @@ -1,35 +1,34 @@ // Test case for issue #1248: // https://github.com/typetools/checker-framework/issues/1248 +import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.function.Predicate; - public class RefinedLocalInLambda { - public static void main(String[] args) { - printIntegersGreaterThan(10); - } + public static void main(String[] args) { + printIntegersGreaterThan(10); + } - public static void printIntegersGreaterThan(@Nullable Integer limit) { - // :: error: (unboxing.of.nullable) - printIntegersWithPredicate(i -> i > limit); // type-checking fails - if (limit == null) { - return; - } - printIntegersWithPredicate(i -> i > limit); // type-checking succeeds - @NonNull Integer limit2 = limit; - printIntegersWithPredicate(i -> i > limit2); // type-checking succeeds - Integer limit3 = limit; - printIntegersWithPredicate(i -> i > limit3); // type-checking succeeds + public static void printIntegersGreaterThan(@Nullable Integer limit) { + // :: error: (unboxing.of.nullable) + printIntegersWithPredicate(i -> i > limit); // type-checking fails + if (limit == null) { + return; } + printIntegersWithPredicate(i -> i > limit); // type-checking succeeds + @NonNull Integer limit2 = limit; + printIntegersWithPredicate(i -> i > limit2); // type-checking succeeds + Integer limit3 = limit; + printIntegersWithPredicate(i -> i > limit3); // type-checking succeeds + } - public static void printIntegersWithPredicate(Predicate tester) { - for (int i = 0; i < 100; i++) { - if (tester.test(i)) { - System.out.println(i); - } - } + public static void printIntegersWithPredicate(Predicate tester) { + for (int i = 0; i < 100; i++) { + if (tester.test(i)) { + System.out.println(i); + } } + } } diff --git a/checker/tests/nullness/java8/lambda/Returns.java b/checker/tests/nullness/java8/lambda/Returns.java index 072a36ab004..c3f20140ade 100644 --- a/checker/tests/nullness/java8/lambda/Returns.java +++ b/checker/tests/nullness/java8/lambda/Returns.java @@ -3,41 +3,41 @@ // The return of a lambda is a lambda interface ConsumerSupplier { - ConsumerR get(); + ConsumerR get(); } interface ConsumerR { - void method(@Nullable String s); + void method(@Nullable String s); } interface SupplierSupplier { - SupplierRe get(); + SupplierRe get(); } interface SupplierRe { - @NonNull String method(); + @NonNull String method(); } class MetaReturn { - // :: error: (dereference.of.nullable) - ConsumerSupplier t1 = () -> (s) -> s.toString(); - ConsumerSupplier t2 = - () -> { - // :: error: (lambda.param.type.incompatible) - return (String s) -> { - s.toString(); - }; - }; - - SupplierSupplier t3 = - () -> { - // :: error: (return.type.incompatible) - return () -> null; - }; - - SupplierSupplier t4 = - () -> { - return ""::toString; - }; + // :: error: (dereference.of.nullable) + ConsumerSupplier t1 = () -> (s) -> s.toString(); + ConsumerSupplier t2 = + () -> { + // :: error: (lambda.param.type.incompatible) + return (String s) -> { + s.toString(); + }; + }; + + SupplierSupplier t3 = + () -> { + // :: error: (return.type.incompatible) + return () -> null; + }; + + SupplierSupplier t4 = + () -> { + return ""::toString; + }; } diff --git a/checker/tests/nullness/java8/lambda/Shadowed.java b/checker/tests/nullness/java8/lambda/Shadowed.java index ff706b5ba43..aa2b665ddfe 100644 --- a/checker/tests/nullness/java8/lambda/Shadowed.java +++ b/checker/tests/nullness/java8/lambda/Shadowed.java @@ -3,26 +3,26 @@ // Test shadowing of parameters interface ConsumerS { - void take(@Nullable String s); + void take(@Nullable String s); } interface NNConsumerS { - void take(String s); + void take(String s); } public class Shadowed { - ConsumerS c = - s -> { - // :: error: (dereference.of.nullable) - s.toString(); + ConsumerS c = + s -> { + // :: error: (dereference.of.nullable) + s.toString(); - class Inner { - NNConsumerS n = - s -> { - // No error - s.toString(); - }; - } - }; + class Inner { + NNConsumerS n = + s -> { + // No error + s.toString(); + }; + } + }; } diff --git a/checker/tests/nullness/java8/lambda/TypeVarAssign.java b/checker/tests/nullness/java8/lambda/TypeVarAssign.java index cf1f9092df2..8e24949783b 100644 --- a/checker/tests/nullness/java8/lambda/TypeVarAssign.java +++ b/checker/tests/nullness/java8/lambda/TypeVarAssign.java @@ -3,13 +3,13 @@ // @skip-test We can only handle this after we get better method inference. interface Fn { - T func(T t); + T func(T t); } class TestAssign { - void foo(Fn f) {} + void foo(Fn f) {} - void context() { - foo((@NonNull String s) -> s); - } + void context() { + foo((@NonNull String s) -> s); + } } diff --git a/checker/tests/nullness/java8/methodref/AssignmentContextTest.java b/checker/tests/nullness/java8/methodref/AssignmentContextTest.java index 8c18c233916..a5bf3819d92 100644 --- a/checker/tests/nullness/java8/methodref/AssignmentContextTest.java +++ b/checker/tests/nullness/java8/methodref/AssignmentContextTest.java @@ -1,43 +1,43 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionAC { - String apply(String s); + String apply(String s); } interface FunctionAC2 { - String apply(@Nullable String s); + String apply(@Nullable String s); } public class AssignmentContextTest { - // Test assign - FunctionAC f1 = String::toString; + // Test assign + FunctionAC f1 = String::toString; + // :: error: (methodref.receiver.invalid) + FunctionAC2 f2 = String::toString; + + // Test casts + Object o1 = (Object) (FunctionAC) String::toString; + // :: error: (methodref.receiver.invalid) + Object o2 = (Object) (FunctionAC2) String::toString; + + void take(FunctionAC f) { + // Test argument assingment + take(String::toString); + } + + void take2(FunctionAC2 f) { + // Test argument assingment // :: error: (methodref.receiver.invalid) - FunctionAC2 f2 = String::toString; + take2(String::toString); + } - // Test casts - Object o1 = (Object) (FunctionAC) String::toString; + FunctionAC supply() { + // Test return assingment + return String::toString; + } + + FunctionAC2 supply2() { + // Test return assingment // :: error: (methodref.receiver.invalid) - Object o2 = (Object) (FunctionAC2) String::toString; - - void take(FunctionAC f) { - // Test argument assingment - take(String::toString); - } - - void take2(FunctionAC2 f) { - // Test argument assingment - // :: error: (methodref.receiver.invalid) - take2(String::toString); - } - - FunctionAC supply() { - // Test return assingment - return String::toString; - } - - FunctionAC2 supply2() { - // Test return assingment - // :: error: (methodref.receiver.invalid) - return String::toString; - } + return String::toString; + } } diff --git a/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java index c3c43bd6034..f0b7e7b4f04 100644 --- a/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java +++ b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java @@ -1,38 +1,38 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ClassTypeArgInference { - public static void main(String[] args) { - Gen o = new Gen<>(""); - // :: error: (methodref.param.invalid) - Factory f = Gen::make; - // :: error: (methodref.param.invalid) - Factory f2 = Gen::make; - // :: error: (methodref.receiver.invalid) :: error: (methodref.return.invalid) - Factory f3 = Gen<@Nullable String>::make; - f2.make(o, null).toString(); - } - - static class Gen { - G field; + public static void main(String[] args) { + Gen o = new Gen<>(""); + // :: error: (methodref.param.invalid) + Factory f = Gen::make; + // :: error: (methodref.param.invalid) + Factory f2 = Gen::make; + // :: error: (methodref.receiver.invalid) :: error: (methodref.return.invalid) + Factory f3 = Gen<@Nullable String>::make; + f2.make(o, null).toString(); + } - Gen(G g) { - field = g; - } + static class Gen { + G field; - public G getField() { - return field; - } + Gen(G g) { + field = g; + } - G make(G g) { - return g; - } + public G getField() { + return field; + } - Gen id() { - return this; - } + G make(G g) { + return g; } - interface Factory { - String make(Gen g, @Nullable String t); + Gen id() { + return this; } + } + + interface Factory { + String make(Gen g, @Nullable String t); + } } diff --git a/checker/tests/nullness/java8/methodref/FromByteCode.java b/checker/tests/nullness/java8/methodref/FromByteCode.java index 89cb5611488..4981d2c3a39 100644 --- a/checker/tests/nullness/java8/methodref/FromByteCode.java +++ b/checker/tests/nullness/java8/methodref/FromByteCode.java @@ -1,14 +1,14 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionBC { - R apply(T t); + R apply(T t); } public class FromByteCode { - FunctionBC f1 = String::toString; + FunctionBC f1 = String::toString; - // Make sure there aren't any issues generating an error with a method from byte code - // :: error: (methodref.param.invalid) - FunctionBC<@Nullable String, String> f2 = String::new; + // Make sure there aren't any issues generating an error with a method from byte code + // :: error: (methodref.param.invalid) + FunctionBC<@Nullable String, String> f2 = String::new; } diff --git a/checker/tests/nullness/java8/methodref/GenericArity.java b/checker/tests/nullness/java8/methodref/GenericArity.java index 8b24d8aeeaf..7ee4ba4d6d9 100644 --- a/checker/tests/nullness/java8/methodref/GenericArity.java +++ b/checker/tests/nullness/java8/methodref/GenericArity.java @@ -5,21 +5,21 @@ import org.checkerframework.checker.nullness.qual.*; interface GenFunc { - T apply(U u); + T apply(U u); } interface GenFunc2 { - T apply(U u); + T apply(U u); } class TestGenFunc { - static V apply(P u) { - throw new RuntimeException(""); - } + static V apply(P u) { + throw new RuntimeException(""); + } - void context() { - GenFunc f = TestGenFunc::apply; - // :: error: (methodref.param.invalid) - GenFunc2 f2 = TestGenFunc::apply; - } + void context() { + GenFunc f = TestGenFunc::apply; + // :: error: (methodref.param.invalid) + GenFunc2 f2 = TestGenFunc::apply; + } } diff --git a/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java b/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java index 082321a3dcb..a13b6ed85e0 100644 --- a/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java +++ b/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.nullness.qual.*; interface Supplier { - T supply(); + T supply(); } interface Supplier2 { - T supply(); + T supply(); } class GroundTargetType { - static @Nullable Object myMethod() { - return null; - } + static @Nullable Object myMethod() { + return null; + } - Supplier fn = GroundTargetType::myMethod; - // :: error: (methodref.return.invalid) - Supplier fn2 = GroundTargetType::myMethod; + Supplier fn = GroundTargetType::myMethod; + // :: error: (methodref.return.invalid) + Supplier fn2 = GroundTargetType::myMethod; - // Supplier2 - // :: error: (methodref.return.invalid) - Supplier2 fn3 = GroundTargetType::myMethod; + // Supplier2 + // :: error: (methodref.return.invalid) + Supplier2 fn3 = GroundTargetType::myMethod; } diff --git a/checker/tests/nullness/java8/methodref/MemberReferences.java b/checker/tests/nullness/java8/methodref/MemberReferences.java index bda36457e0a..07a828f1a9e 100644 --- a/checker/tests/nullness/java8/methodref/MemberReferences.java +++ b/checker/tests/nullness/java8/methodref/MemberReferences.java @@ -2,46 +2,46 @@ abstract class References { - void context(References c) { + void context(References c) { - // No error - FuncA funcA1 = References::aMethod1; - // No error, covariant parameters - FuncA funcA2 = References::aMethod2; - // :: error: (methodref.return.invalid) - FuncA funcA3 = References::aMethod3; - // :: error: (methodref.return.invalid) - FuncA funcA4 = References::aMethod4; + // No error + FuncA funcA1 = References::aMethod1; + // No error, covariant parameters + FuncA funcA2 = References::aMethod2; + // :: error: (methodref.return.invalid) + FuncA funcA3 = References::aMethod3; + // :: error: (methodref.return.invalid) + FuncA funcA4 = References::aMethod4; - // :: error: (methodref.param.invalid) - FuncB funcB1 = References::aMethod1; - // No error - FuncB funcB2 = References::aMethod2; - // :: error: (methodref.return.invalid) :: error: (methodref.param.invalid) - FuncB funcB3 = References::aMethod3; - // :: error: (methodref.return.invalid) - FuncB funcB4 = References::aMethod4; + // :: error: (methodref.param.invalid) + FuncB funcB1 = References::aMethod1; + // No error + FuncB funcB2 = References::aMethod2; + // :: error: (methodref.return.invalid) :: error: (methodref.param.invalid) + FuncB funcB3 = References::aMethod3; + // :: error: (methodref.return.invalid) + FuncB funcB4 = References::aMethod4; - FuncA typeArg1 = References::<@NonNull String>aMethod5; - // :: error: (methodref.param.invalid) - FuncB typeArg2 = References::<@NonNull String>aMethod5; - } + FuncA typeArg1 = References::<@NonNull String>aMethod5; + // :: error: (methodref.param.invalid) + FuncB typeArg2 = References::<@NonNull String>aMethod5; + } - abstract @NonNull String aMethod1(@NonNull String s); + abstract @NonNull String aMethod1(@NonNull String s); - abstract @NonNull String aMethod2(@Nullable String s); + abstract @NonNull String aMethod2(@Nullable String s); - abstract @Nullable String aMethod3(@NonNull String s); + abstract @Nullable String aMethod3(@NonNull String s); - abstract @Nullable String aMethod4(@Nullable String s); + abstract @Nullable String aMethod4(@Nullable String s); - abstract T aMethod5(T t); + abstract T aMethod5(T t); - interface FuncA { - @NonNull String method(References a, @NonNull String b); - } + interface FuncA { + @NonNull String method(References a, @NonNull String b); + } - interface FuncB { - @NonNull String method(References a, @Nullable String b); - } + interface FuncB { + @NonNull String method(References a, @Nullable String b); + } } diff --git a/checker/tests/nullness/java8/methodref/PolyNullness.java b/checker/tests/nullness/java8/methodref/PolyNullness.java index 5eebf1397e8..6a941f46ce1 100644 --- a/checker/tests/nullness/java8/methodref/PolyNullness.java +++ b/checker/tests/nullness/java8/methodref/PolyNullness.java @@ -1,34 +1,34 @@ import org.checkerframework.checker.nullness.qual.*; interface PolyFunc { - @PolyNull String method(@PolyNull String in); + @PolyNull String method(@PolyNull String in); } interface NonNullFunc { - @NonNull String method(@NonNull String in); + @NonNull String method(@NonNull String in); } interface MixedFunc { - @NonNull String method(@Nullable String in); + @NonNull String method(@Nullable String in); } class Context { - static @PolyNull String poly(@PolyNull String in) { - return in; - } + static @PolyNull String poly(@PolyNull String in) { + return in; + } - static String nonPoly(String in) { - return in; - } + static String nonPoly(String in) { + return in; + } - void context() { - PolyFunc f1 = Context::poly; - // :: error: (methodref.param.invalid) - PolyFunc f2 = Context::nonPoly; + void context() { + PolyFunc f1 = Context::poly; + // :: error: (methodref.param.invalid) + PolyFunc f2 = Context::nonPoly; - NonNullFunc f3 = Context::poly; - // :: error: (methodref.return.invalid) - MixedFunc f4 = Context::poly; - } + NonNullFunc f3 = Context::poly; + // :: error: (methodref.return.invalid) + MixedFunc f4 = Context::poly; + } } diff --git a/checker/tests/nullness/java8/methodref/Postconditions.java b/checker/tests/nullness/java8/methodref/Postconditions.java index 5dd0d9f02f4..cf53f38ea85 100644 --- a/checker/tests/nullness/java8/methodref/Postconditions.java +++ b/checker/tests/nullness/java8/methodref/Postconditions.java @@ -6,28 +6,28 @@ import org.checkerframework.checker.nullness.qual.*; interface AssertFunc { - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testParam(final @Nullable Object param); + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testParam(final @Nullable Object param); } interface AssertFunc2 { - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testParam(final @Nullable Object param); + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testParam(final @Nullable Object param); } public class AssertionTest { - @EnsuresNonNullIf(result = true, expression = "#1") - static boolean override(final @Nullable Object param) { - return param != null; - } + @EnsuresNonNullIf(result = true, expression = "#1") + static boolean override(final @Nullable Object param) { + return param != null; + } - static boolean overrideAssertFunc2(final @Nullable Object param) { - return param != null; - } + static boolean overrideAssertFunc2(final @Nullable Object param) { + return param != null; + } - void context() { - AssertFunc f = AssertionTest::override; - // :: error: (methodref.receiver.postcondition) - AssertFunc2 f2 = AssertionTest::overrideAssertFunc2; - } + void context() { + AssertFunc f = AssertionTest::override; + // :: error: (methodref.receiver.postcondition) + AssertFunc2 f2 = AssertionTest::overrideAssertFunc2; + } } diff --git a/checker/tests/nullness/java8/methodref/ReceiversMethodref.java b/checker/tests/nullness/java8/methodref/ReceiversMethodref.java index 153ca368665..eb64281ef06 100644 --- a/checker/tests/nullness/java8/methodref/ReceiversMethodref.java +++ b/checker/tests/nullness/java8/methodref/ReceiversMethodref.java @@ -5,92 +5,92 @@ // It could just use tainted. interface Unbound1 { - void apply(@NonNull MyClass my); + void apply(@NonNull MyClass my); } interface Unbound2 { - void apply(@Nullable MyClass my); + void apply(@Nullable MyClass my); } interface Supplier1 { - R supply(); + R supply(); } interface Bound { - void apply(); + void apply(); } class MyClass { - // :: error: (nullness.on.receiver) - void take(@NonNull MyClass this) {} + // :: error: (nullness.on.receiver) + void take(@NonNull MyClass this) {} - // :: error: (nullness.on.receiver) - void context1(@Nullable MyClass this, @NonNull MyClass my1, @Nullable MyClass my2) { + // :: error: (nullness.on.receiver) + void context1(@Nullable MyClass this, @NonNull MyClass my1, @Nullable MyClass my2) { - Unbound1 u1 = MyClass::take; - // :: error: (methodref.receiver.invalid) - Unbound2 u2 = MyClass::take; + Unbound1 u1 = MyClass::take; + // :: error: (methodref.receiver.invalid) + Unbound2 u2 = MyClass::take; - Bound b1 = my1::take; - // :: error: (methodref.receiver.bound.invalid) - Bound b2 = my2::take; + Bound b1 = my1::take; + // :: error: (methodref.receiver.bound.invalid) + Bound b2 = my2::take; - // :: error: (methodref.receiver.bound.invalid) - Bound b11 = this::take; - } + // :: error: (methodref.receiver.bound.invalid) + Bound b11 = this::take; + } + + // :: error: (nullness.on.receiver) + void context2(@NonNull MyClass this) { + Bound b21 = this::take; + } + + class MySubClass extends MyClass { // :: error: (nullness.on.receiver) - void context2(@NonNull MyClass this) { - Bound b21 = this::take; + void context1(@Nullable MySubClass this) { + // :: error: (methodref.receiver.bound.invalid) + Bound b = super::take; } - class MySubClass extends MyClass { - - // :: error: (nullness.on.receiver) - void context1(@Nullable MySubClass this) { - // :: error: (methodref.receiver.bound.invalid) - Bound b = super::take; - } - - // :: error: (nullness.on.receiver) - void context2(@NonNull MySubClass this) { - Bound b = super::take; - } - - class Nested { - // :: error: (nullness.on.receiver) - void context1(@Nullable Nested this) { - // :: error: (methodref.receiver.bound.invalid) - Bound b = MySubClass.super::take; - } - - // :: error: (nullness.on.receiver) - void context2(@NonNull Nested this) { - Bound b = MySubClass.super::take; - } - } + // :: error: (nullness.on.receiver) + void context2(@NonNull MySubClass this) { + Bound b = super::take; } -} -class Outer { - class Inner1 { - // :: error: (nullness.on.receiver) - Inner1(@Nullable Outer Outer.this) {} - } + class Nested { + // :: error: (nullness.on.receiver) + void context1(@Nullable Nested this) { + // :: error: (methodref.receiver.bound.invalid) + Bound b = MySubClass.super::take; + } - class Inner2 { - // :: error: (nullness.on.receiver) - Inner2(@NonNull Outer Outer.this) {} + // :: error: (nullness.on.receiver) + void context2(@NonNull Nested this) { + Bound b = MySubClass.super::take; + } } + } +} +class Outer { + class Inner1 { // :: error: (nullness.on.receiver) - void context(@Nullable Outer this) { - // This one is unbound and needs an Outer as a param - Supplier1 f1 = Inner1::new; - // :: error: (methodref.receiver.bound.invalid) - Supplier1 f2 = Inner2::new; + Inner1(@Nullable Outer Outer.this) {} + } - // Supplier1 f = /*4*/Inner::new; - // 4 <: 3? Constructor annotations? - } + class Inner2 { + // :: error: (nullness.on.receiver) + Inner2(@NonNull Outer Outer.this) {} + } + + // :: error: (nullness.on.receiver) + void context(@Nullable Outer this) { + // This one is unbound and needs an Outer as a param + Supplier1 f1 = Inner1::new; + // :: error: (methodref.receiver.bound.invalid) + Supplier1 f2 = Inner2::new; + + // Supplier1 f = /*4*/Inner::new; + // 4 <: 3? Constructor annotations? + } } diff --git a/checker/tests/nullness/java8inference/FalsePositives.java b/checker/tests/nullness/java8inference/FalsePositives.java index 340975a60f2..c8ff17b0a64 100644 --- a/checker/tests/nullness/java8inference/FalsePositives.java +++ b/checker/tests/nullness/java8inference/FalsePositives.java @@ -9,58 +9,55 @@ import java.util.Queue; public class FalsePositives { - static class Partitioning {} + static class Partitioning {} - public static List> partitionInto(Queue elts, int k) { - if (elts.size() < k) { - throw new IllegalArgumentException(); - } - return partitionIntoHelper(elts, Arrays.asList(new Partitioning()), k, 0); + public static List> partitionInto(Queue elts, int k) { + if (elts.size() < k) { + throw new IllegalArgumentException(); } + return partitionIntoHelper(elts, Arrays.asList(new Partitioning()), k, 0); + } - public static List> partitionIntoHelper( - Queue elts, - List> resultSoFar, - int numEmptyParts, - int numNonemptyParts) { - throw new RuntimeException(); - } + public static List> partitionIntoHelper( + Queue elts, List> resultSoFar, int numEmptyParts, int numNonemptyParts) { + throw new RuntimeException(); + } - interface Box {} + interface Box {} - interface Function { - R apply(P p); - } + interface Function { + R apply(P p); + } - interface Utils { - Box foo(Box input, Function function); + interface Utils { + Box foo(Box input, Function function); - Function bar(Function function); - } + Function bar(Function function); + } - class Test { - Box demo(Utils u, Box bs) { - return u.foo(bs, u.bar((String s) -> 5)); - } + class Test { + Box demo(Utils u, Box bs) { + return u.foo(bs, u.bar((String s) -> 5)); + } - Integer ugh(String n) { - return 5; - } + Integer ugh(String n) { + return 5; + } - Box demo2(Utils u, Box bs) { - return u.foo(bs, u.bar(this::ugh)); - } + Box demo2(Utils u, Box bs) { + return u.foo(bs, u.bar(this::ugh)); } + } - abstract class Test2 { - abstract Box> foo(Box> p); + abstract class Test2 { + abstract Box> foo(Box> p); - abstract Box> bar(Function f); + abstract Box> bar(Function f); - abstract String baz(Number p); + abstract String baz(Number p); - Box> demo() { - return foo(bar(this::baz)); - } + Box> demo() { + return foo(bar(this::baz)); } + } } diff --git a/checker/tests/nullness/java8inference/InLambda.java b/checker/tests/nullness/java8inference/InLambda.java index e7ff0430fdf..b82b9dc3501 100644 --- a/checker/tests/nullness/java8inference/InLambda.java +++ b/checker/tests/nullness/java8inference/InLambda.java @@ -1,29 +1,29 @@ public class InLambda { - static class Mine { - @SuppressWarnings("nullness") // just a utility - static Mine some() { - return null; - } + static class Mine { + @SuppressWarnings("nullness") // just a utility + static Mine some() { + return null; } + } - interface Function { - R apply(T t); - } + interface Function { + R apply(T t); + } - interface Box {} + interface Box {} - static class Boxes { - @SuppressWarnings("nullness") // just a utility - static Box transform(Function function) { - return null; - } + static class Boxes { + @SuppressWarnings("nullness") // just a utility + static Box transform(Function function) { + return null; } + } - class Infer { - Box> f = - Boxes.transform( - el -> { - return Mine.some(); - }); - } + class Infer { + Box> f = + Boxes.transform( + el -> { + return Mine.some(); + }); + } } diff --git a/checker/tests/nullness/java8inference/InLambdaAnnotated.java b/checker/tests/nullness/java8inference/InLambdaAnnotated.java index f15a3ca576f..d4aa703844a 100644 --- a/checker/tests/nullness/java8inference/InLambdaAnnotated.java +++ b/checker/tests/nullness/java8inference/InLambdaAnnotated.java @@ -1,38 +1,38 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class InLambdaAnnotated { - static class Mine { - @SuppressWarnings("nullness") // just a utility - static Mine some() { - return null; - } + static class Mine { + @SuppressWarnings("nullness") // just a utility + static Mine some() { + return null; } + } - interface Function { - R apply(T t); - } + interface Function { + R apply(T t); + } - interface Box {} + interface Box {} - static class Boxes { - @SuppressWarnings("nullness") // just a utility - static Box transform(Function function) { - return null; - } + static class Boxes { + @SuppressWarnings("nullness") // just a utility + static Box transform(Function function) { + return null; } + } - class Infer { - // The nested Mine.some() needs to infer the right type. - Box> g = - // TODO: This is a false positive. - // :: error: (assignment.type.incompatible) - Boxes.transform( - el -> { - return Mine.some(); - }); + class Infer { + // The nested Mine.some() needs to infer the right type. + Box> g = + // TODO: This is a false positive. + // :: error: (assignment.type.incompatible) + Boxes.transform( + el -> { + return Mine.some(); + }); - void bar(Function> fun) { - Box> h = Boxes.transform(fun); - } + void bar(Function> fun) { + Box> h = Boxes.transform(fun); } + } } diff --git a/checker/tests/nullness/java8inference/Inference.java b/checker/tests/nullness/java8inference/Inference.java index 7152027ce85..3aec44122c9 100644 --- a/checker/tests/nullness/java8inference/Inference.java +++ b/checker/tests/nullness/java8inference/Inference.java @@ -2,28 +2,28 @@ // https://github.com/typetools/checker-framework/issues/979 class MyStream { - @SuppressWarnings("nullness") - R collect(MyCollector collector) { - return null; - } + @SuppressWarnings("nullness") + R collect(MyCollector collector) { + return null; + } } interface MyCollector {} public class Inference { - @SuppressWarnings("nullness") - static MyCollector> toImmutableStream() { - return null; - } + @SuppressWarnings("nullness") + static MyCollector> toImmutableStream() { + return null; + } - MyStream test(MyStream p) { - /* Need Java 8 assignment context to correctly infer type arguments. - return p.collect(toImmutableStream()); - ^ - found : @Initialized @NonNull MyStream - required: @Initialized @NonNull MyStream<@Initialized @NonNull String> - */ - return p.collect(toImmutableStream()); - } + MyStream test(MyStream p) { + /* Need Java 8 assignment context to correctly infer type arguments. + return p.collect(toImmutableStream()); + ^ + found : @Initialized @NonNull MyStream + required: @Initialized @NonNull MyStream<@Initialized @NonNull String> + */ + return p.collect(toImmutableStream()); + } } diff --git a/checker/tests/nullness/java8inference/InferenceSimpler.java b/checker/tests/nullness/java8inference/InferenceSimpler.java index 909e797c7ab..f7ea9b8adca 100644 --- a/checker/tests/nullness/java8inference/InferenceSimpler.java +++ b/checker/tests/nullness/java8inference/InferenceSimpler.java @@ -5,17 +5,17 @@ @SuppressWarnings("nullness") // don't bother with implementations class ISOuter { - static List wrap(V value) { - return null; - } + static List wrap(V value) { + return null; + } - static List empty() { - return null; - } + static List empty() { + return null; + } } public class InferenceSimpler { - List> foo() { - return ISOuter.wrap(ISOuter.empty()); - } + List> foo() { + return ISOuter.wrap(ISOuter.empty()); + } } diff --git a/checker/tests/nullness/java8inference/Issue1032.java b/checker/tests/nullness/java8inference/Issue1032.java index b09a59413fd..62351cef87e 100644 --- a/checker/tests/nullness/java8inference/Issue1032.java +++ b/checker/tests/nullness/java8inference/Issue1032.java @@ -1,33 +1,32 @@ // Test case for issue #1032: // https://github.com/typetools/checker-framework/issues/1032 -import org.checkerframework.checker.nullness.qual.*; - import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.*; public class Issue1032 { - @SuppressWarnings("nullness") - static @NonNull String castStringToNonNull(@Nullable String arg) { - return (@NonNull String) arg; - } - - Stream<@NonNull String> mapStringCast1(Stream<@Nullable String> arg) { - return arg.map(Issue1032::castStringToNonNull); - } - - @SuppressWarnings("nullness") - static @NonNull T castTToNonNull(@Nullable T arg) { - return (@NonNull T) arg; - } - - Stream<@NonNull String> mapStringCast2(Stream<@Nullable String> arg) { - return arg.map(Issue1032::castTToNonNull); - } - - Stream<@NonNull T> mapTCast(Stream<@Nullable T> arg) { - // TODO: false postive - // :: error: (return.type.incompatible) - return arg.map(Issue1032::castTToNonNull); - } + @SuppressWarnings("nullness") + static @NonNull String castStringToNonNull(@Nullable String arg) { + return (@NonNull String) arg; + } + + Stream<@NonNull String> mapStringCast1(Stream<@Nullable String> arg) { + return arg.map(Issue1032::castStringToNonNull); + } + + @SuppressWarnings("nullness") + static @NonNull T castTToNonNull(@Nullable T arg) { + return (@NonNull T) arg; + } + + Stream<@NonNull String> mapStringCast2(Stream<@Nullable String> arg) { + return arg.map(Issue1032::castTToNonNull); + } + + Stream<@NonNull T> mapTCast(Stream<@Nullable T> arg) { + // TODO: false postive + // :: error: (return.type.incompatible) + return arg.map(Issue1032::castTToNonNull); + } } diff --git a/checker/tests/nullness/java8inference/Issue1084.java b/checker/tests/nullness/java8inference/Issue1084.java index 62dfeedf970..521a264d5dd 100644 --- a/checker/tests/nullness/java8inference/Issue1084.java +++ b/checker/tests/nullness/java8inference/Issue1084.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; class MyOpt { - static MyOpt<@NonNull S> empty() { - throw new RuntimeException(); - } + static MyOpt<@NonNull S> empty() { + throw new RuntimeException(); + } - // :: error: (type.argument.type.incompatible) - static MyOpt of(S p) { - throw new RuntimeException(); - } + // :: error: (type.argument.type.incompatible) + static MyOpt of(S p) { + throw new RuntimeException(); + } } public class Issue1084 { - MyOpt get() { - return this.hashCode() > 0 ? MyOpt.of(5L) : MyOpt.empty(); - } + MyOpt get() { + return this.hashCode() > 0 ? MyOpt.of(5L) : MyOpt.empty(); + } - MyOpt oba = MyOpt.empty(); + MyOpt oba = MyOpt.empty(); } diff --git a/checker/tests/nullness/java8inference/Issue1366.java b/checker/tests/nullness/java8inference/Issue1366.java index b4ca486bc66..9cb3b376513 100644 --- a/checker/tests/nullness/java8inference/Issue1366.java +++ b/checker/tests/nullness/java8inference/Issue1366.java @@ -1,13 +1,13 @@ // Test case for Issue 1366. // https://github.com/typetools/checker-framework/issues/1366 abstract class Issue1366 { - abstract Issue1366 m1(Issue1366 p1, Issue1366 p2); + abstract Issue1366 m1(Issue1366 p1, Issue1366 p2); - abstract Issue1366 m2(Issue1366 p); + abstract Issue1366 m2(Issue1366 p); - abstract void m3(Issue1366 p); + abstract void m3(Issue1366 p); - void foo(Issue1366 s) { - s.m3(s.m2(s.m1(s, s))); - } + void foo(Issue1366 s) { + s.m3(s.m2(s.m1(s, s))); + } } diff --git a/checker/tests/nullness/java8inference/Issue1464.java b/checker/tests/nullness/java8inference/Issue1464.java index b01adb35728..743f014a097 100644 --- a/checker/tests/nullness/java8inference/Issue1464.java +++ b/checker/tests/nullness/java8inference/Issue1464.java @@ -3,20 +3,20 @@ public class Issue1464 { - public interface Variable { + public interface Variable { - void addChangedListener(VariableChangedListener listener); - } + void addChangedListener(VariableChangedListener listener); + } - public interface VariableChangedListener { + public interface VariableChangedListener { - void variableChanged(final Variable variable); - } + void variableChanged(final Variable variable); + } - protected void addChangedListener( - final Variable variable, final VariableChangedListener listener) {} + protected void addChangedListener( + final Variable variable, final VariableChangedListener listener) {} - public void main(final Variable tmp) { - addChangedListener(tmp, variable -> System.out.println(variable)); - } + public void main(final Variable tmp) { + addChangedListener(tmp, variable -> System.out.println(variable)); + } } diff --git a/checker/tests/nullness/java8inference/Issue1630.java b/checker/tests/nullness/java8inference/Issue1630.java index 33d5d319150..c242af258d2 100644 --- a/checker/tests/nullness/java8inference/Issue1630.java +++ b/checker/tests/nullness/java8inference/Issue1630.java @@ -1,22 +1,18 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1630 { - static @Nullable String toString(Object o) { - return null; - } + static @Nullable String toString(Object o) { + return null; + } - @SuppressWarnings("nullness") // Issue 979 - public static List f(List xs) { - return xs != null - ? xs.stream() - .map(Issue1630::toString) - .filter(Objects::nonNull) - .collect(Collectors.toList()) - : Collections.emptyList(); - } + @SuppressWarnings("nullness") // Issue 979 + public static List f(List xs) { + return xs != null + ? xs.stream().map(Issue1630::toString).filter(Objects::nonNull).collect(Collectors.toList()) + : Collections.emptyList(); + } } diff --git a/checker/tests/nullness/java8inference/Issue1818.java b/checker/tests/nullness/java8inference/Issue1818.java index 79a6ec85b77..df18d9c60f3 100644 --- a/checker/tests/nullness/java8inference/Issue1818.java +++ b/checker/tests/nullness/java8inference/Issue1818.java @@ -1,12 +1,11 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; import java.util.function.Consumer; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1818 { - void f() { - Consumer> c = values -> values.forEach(value -> g(value)); - } + void f() { + Consumer> c = values -> values.forEach(value -> g(value)); + } - void g(@Nullable Object o) {} + void g(@Nullable Object o) {} } diff --git a/checker/tests/nullness/java8inference/Issue1954.java b/checker/tests/nullness/java8inference/Issue1954.java index 85129b1e55d..f5091c84283 100644 --- a/checker/tests/nullness/java8inference/Issue1954.java +++ b/checker/tests/nullness/java8inference/Issue1954.java @@ -1,27 +1,26 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.function.*; import java.util.stream.*; +import org.checkerframework.checker.nullness.qual.*; // @skip-test public class Issue1954 { - public interface Getter { - R get(); - } + public interface Getter { + R get(); + } - public interface NullStringGetter extends Getter<@Nullable String> {} + public interface NullStringGetter extends Getter<@Nullable String> {} - public Getter transform(Function, R> fn, Getter getter) { - return () -> fn.apply(Stream.of(getter.get())); - } + public Getter transform(Function, R> fn, Getter getter) { + return () -> fn.apply(Stream.of(getter.get())); + } - public static @Nullable T fn(Stream arg) { - return arg.findFirst().orElse(null); - } + public static @Nullable T fn(Stream arg) { + return arg.findFirst().orElse(null); + } - public void doo() { - NullStringGetter nullStringGetter = () -> null; - // :: error: type inference failed. - transform(Issue1954::fn, nullStringGetter).get(); - } + public void doo() { + NullStringGetter nullStringGetter = () -> null; + // :: error: type inference failed. + transform(Issue1954::fn, nullStringGetter).get(); + } } diff --git a/checker/tests/nullness/java8inference/Issue2235.java b/checker/tests/nullness/java8inference/Issue2235.java index 7c683aa2333..4d0eb00ed73 100644 --- a/checker/tests/nullness/java8inference/Issue2235.java +++ b/checker/tests/nullness/java8inference/Issue2235.java @@ -6,25 +6,25 @@ // @skip-test public class Issue2235 { - // Simple wrapper class with a public generic method - // to make an instance: - static class Holder { - T t; + // Simple wrapper class with a public generic method + // to make an instance: + static class Holder { + T t; - private Holder(T t) { - this.t = t; - } - - public static Holder make(T t) { - return new Holder<>(t); - } + private Holder(T t) { + this.t = t; } - public static void main(String[] args) throws Exception { - // Null is hidden via nested calls, but assigned to a non-null type: - // :: error: (TODO) - Holder> h = Holder.make(Holder.make(null)); - // NullPointerException will fire here: - h.t.t.toString(); + public static Holder make(T t) { + return new Holder<>(t); } + } + + public static void main(String[] args) throws Exception { + // Null is hidden via nested calls, but assigned to a non-null type: + // :: error: (TODO) + Holder> h = Holder.make(Holder.make(null)); + // NullPointerException will fire here: + h.t.t.toString(); + } } diff --git a/checker/tests/nullness/java8inference/Issue2719.java b/checker/tests/nullness/java8inference/Issue2719.java index 1562e63207d..5b5fdfc3554 100644 --- a/checker/tests/nullness/java8inference/Issue2719.java +++ b/checker/tests/nullness/java8inference/Issue2719.java @@ -1,19 +1,18 @@ import static java.util.Arrays.asList; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue2719 { - public static void main(String[] args) { - List iList = asList(0); - List<@Nullable Integer> jList = asList((Integer) null); - // TODO:: error: (assignment.type.incompatible) - List> both = passThrough(asList(iList, jList)); - System.out.println(both.get(1).get(0).intValue()); - } + public static void main(String[] args) { + List iList = asList(0); + List<@Nullable Integer> jList = asList((Integer) null); + // TODO:: error: (assignment.type.incompatible) + List> both = passThrough(asList(iList, jList)); + System.out.println(both.get(1).get(0).intValue()); + } - static T passThrough(T object) { - return object; - } + static T passThrough(T object) { + return object; + } } diff --git a/checker/tests/nullness/java8inference/Issue402.java b/checker/tests/nullness/java8inference/Issue402.java index 63e0e0ef797..8aa18c8cd70 100644 --- a/checker/tests/nullness/java8inference/Issue402.java +++ b/checker/tests/nullness/java8inference/Issue402.java @@ -2,7 +2,6 @@ // https://github.com/typetools/checker-framework/issues/979 import java.util.Comparator; - import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -10,27 +9,25 @@ // Once Issue 979 is fixed, this suppression should be removed. @SuppressWarnings({"nullness", "keyfor"}) // Issue 979 public final class Issue402 { - static final Comparator COMPARATOR = - Comparator.comparing( - Issue402::getStr1, Comparator.nullsFirst(Comparator.naturalOrder())) - .thenComparing( - Issue402::getStr2, Comparator.nullsFirst(Comparator.naturalOrder())); + static final Comparator COMPARATOR = + Comparator.comparing(Issue402::getStr1, Comparator.nullsFirst(Comparator.naturalOrder())) + .thenComparing(Issue402::getStr2, Comparator.nullsFirst(Comparator.naturalOrder())); - @CheckForNull private final String str1; - @CheckForNull private final String str2; + @CheckForNull private final String str1; + @CheckForNull private final String str2; - Issue402(@Nullable final String str1, @Nullable final String str2) { - this.str1 = str1; - this.str2 = str2; - } + Issue402(@Nullable final String str1, @Nullable final String str2) { + this.str1 = str1; + this.str2 = str2; + } - @CheckForNull - String getStr1() { - return this.str1; - } + @CheckForNull + String getStr1() { + return this.str1; + } - @CheckForNull - String getStr2() { - return this.str2; - } + @CheckForNull + String getStr2() { + return this.str2; + } } diff --git a/checker/tests/nullness/java8inference/Issue4048.java b/checker/tests/nullness/java8inference/Issue4048.java index a573de57734..846b8a06a8a 100644 --- a/checker/tests/nullness/java8inference/Issue4048.java +++ b/checker/tests/nullness/java8inference/Issue4048.java @@ -2,20 +2,19 @@ // @skip-test until the issue is fixed -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; abstract class Issue4048 { - @Nullable Number m1(List numbers) { - return getOnlyElement1(numbers); - } + @Nullable Number m1(List numbers) { + return getOnlyElement1(numbers); + } - abstract @Nullable T getOnlyElement1(Iterable values); + abstract @Nullable T getOnlyElement1(Iterable values); - @Nullable Number m2(List numbers) { - return getOnlyElement2(numbers); - } + @Nullable Number m2(List numbers) { + return getOnlyElement2(numbers); + } - abstract @Nullable T getOnlyElement2(Iterable values); + abstract @Nullable T getOnlyElement2(Iterable values); } diff --git a/checker/tests/nullness/java8inference/Issue887.java b/checker/tests/nullness/java8inference/Issue887.java index 6bd83931ea6..6f331fc8c60 100644 --- a/checker/tests/nullness/java8inference/Issue887.java +++ b/checker/tests/nullness/java8inference/Issue887.java @@ -2,22 +2,21 @@ // https://github.com/typetools/checker-framework/issues/887 // Additional test case in framework/tests/all-systems/Issue887.java -import org.checkerframework.checker.nullness.qual.*; - import java.util.List; +import org.checkerframework.checker.nullness.qual.*; public abstract class Issue887 { - void test() { - // :: error: (argument.type.incompatible) :: error: (type.argument.type.incompatible) - method(foo(null).get(0)); - methodNullable(fooNullable(null).get(0)); - } + void test() { + // :: error: (argument.type.incompatible) :: error: (type.argument.type.incompatible) + method(foo(null).get(0)); + methodNullable(fooNullable(null).get(0)); + } - void method(Number o) {} + void method(Number o) {} - void methodNullable(@Nullable Number o) {} + void methodNullable(@Nullable Number o) {} - abstract List foo(T t); + abstract List foo(T t); - abstract List fooNullable(T t); + abstract List fooNullable(T t); } diff --git a/checker/tests/nullness/java8inference/Issue953bInference.java b/checker/tests/nullness/java8inference/Issue953bInference.java index e9081a9b140..3c68a878214 100644 --- a/checker/tests/nullness/java8inference/Issue953bInference.java +++ b/checker/tests/nullness/java8inference/Issue953bInference.java @@ -1,26 +1,25 @@ // Test case that was submitted in Issue 953, but was combined with Issue 979 // https://github.com/typetools/checker-framework/issues/979 -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.*; import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.NonNull; public class Issue953bInference { - private static List> strs = new ArrayList<>(); + private static List> strs = new ArrayList<>(); - public static List<@NonNull R> mapList( - List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { - ArrayList<@NonNull R> r = new ArrayList<>(list.size()); - for (T t : list) r.add(func.apply(t)); - return r; - } + public static List<@NonNull R> mapList( + List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { + ArrayList<@NonNull R> r = new ArrayList<>(list.size()); + for (T t : list) r.add(func.apply(t)); + return r; + } - public static List test() { - return mapList( - strs, - s -> { - return new String(); - }); - } + public static List test() { + return mapList( + strs, + s -> { + return new String(); + }); + } } diff --git a/checker/tests/nullness/java8inference/Issue980.java b/checker/tests/nullness/java8inference/Issue980.java index 42b8874c2d1..570a3dd1f46 100644 --- a/checker/tests/nullness/java8inference/Issue980.java +++ b/checker/tests/nullness/java8inference/Issue980.java @@ -3,24 +3,23 @@ // @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue980 { - void m(List strings) { - Stream s = strings.stream(); + void m(List strings) { + Stream s = strings.stream(); - // This works: - List collectedStrings1 = s.collect(Collectors.toList()); - // This works: - List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); + // This works: + List collectedStrings1 = s.collect(Collectors.toList()); + // This works: + List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); - List collectedStrings = s.collect(Collectors.toList()); + List collectedStrings = s.collect(Collectors.toList()); - collectedStrings.forEach(System.out::println); - } + collectedStrings.forEach(System.out::println); + } } diff --git a/checker/tests/nullness/java8inference/OneOf.java b/checker/tests/nullness/java8inference/OneOf.java index 7a30da6972b..352459c785d 100644 --- a/checker/tests/nullness/java8inference/OneOf.java +++ b/checker/tests/nullness/java8inference/OneOf.java @@ -5,19 +5,19 @@ @SuppressWarnings({"initialization", "nullness"}) // don't bother with implementations public class OneOf { - static List alist; + static List alist; - static V oneof(V v1, V v2) { - return v1; - } + static V oneof(V v1, V v2) { + return v1; + } - static List empty() { - return null; - } + static List empty() { + return null; + } } class OneOfUse { - List foo() { - return OneOf.oneof(OneOf.alist, OneOf.empty()); - } + List foo() { + return OneOf.oneof(OneOf.alist, OneOf.empty()); + } } diff --git a/checker/tests/nullness/java8inference/SimpleLambda.java b/checker/tests/nullness/java8inference/SimpleLambda.java index 7bae745c3b1..59f64277c58 100644 --- a/checker/tests/nullness/java8inference/SimpleLambda.java +++ b/checker/tests/nullness/java8inference/SimpleLambda.java @@ -1,18 +1,17 @@ // @skip-test until Issue 979 is fixed. +import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.function.Supplier; - public class SimpleLambda { - T perform(Supplier p) { - return p.get(); - } + T perform(Supplier p) { + return p.get(); + } - void test() { - @Nullable String s1 = perform(() -> (String) null); - @Nullable String s2 = this.<@Nullable String>perform(() -> (String) null); - @NonNull String s3 = perform(() -> ""); - } + void test() { + @Nullable String s1 = perform(() -> (String) null); + @Nullable String s2 = this.<@Nullable String>perform(() -> (String) null); + @NonNull String s3 = perform(() -> ""); + } } diff --git a/checker/tests/nullness/jdkannotations/EisopIssue270.java b/checker/tests/nullness/jdkannotations/EisopIssue270.java index b77e1be6c12..56135b16945 100644 --- a/checker/tests/nullness/jdkannotations/EisopIssue270.java +++ b/checker/tests/nullness/jdkannotations/EisopIssue270.java @@ -1,10 +1,10 @@ import java.util.Set; public class EisopIssue270 { - // In annotated jdk, the package-info of java.util defines KeyForBottom as the - // default qualifier for lower bound. - void foo(Set so, Set seo) { - // No errors if package-info is loaded correctly. - so.retainAll(seo); - } + // In annotated jdk, the package-info of java.util defines KeyForBottom as the + // default qualifier for lower bound. + void foo(Set so, Set seo) { + // No errors if package-info is loaded correctly. + so.retainAll(seo); + } } diff --git a/checker/tests/nullness/jdkannotations/HashtableTest.java b/checker/tests/nullness/jdkannotations/HashtableTest.java index 20b58c04e6d..84ba3d724ac 100644 --- a/checker/tests/nullness/jdkannotations/HashtableTest.java +++ b/checker/tests/nullness/jdkannotations/HashtableTest.java @@ -1,21 +1,20 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Hashtable; +import org.checkerframework.checker.nullness.qual.Nullable; public class HashtableTest { - public static void main(String[] args) { + public static void main(String[] args) { - // :: error: (type.argument.type.incompatible) - Hashtable<@Nullable Integer, String> ht1 = new Hashtable<>(); + // :: error: (type.argument.type.incompatible) + Hashtable<@Nullable Integer, String> ht1 = new Hashtable<>(); - // Suffers null pointer exception - ht1.put(null, "hello"); + // Suffers null pointer exception + ht1.put(null, "hello"); - // :: error: (type.argument.type.incompatible) - Hashtable ht2 = new Hashtable<>(); + // :: error: (type.argument.type.incompatible) + Hashtable ht2 = new Hashtable<>(); - // Suffers null pointer exception - ht2.put(42, null); - } + // Suffers null pointer exception + ht2.put(42, null); + } } diff --git a/checker/tests/nullness/jdkannotations/Issue1142.java b/checker/tests/nullness/jdkannotations/Issue1142.java index 8051eca322d..a6ebd43380c 100644 --- a/checker/tests/nullness/jdkannotations/Issue1142.java +++ b/checker/tests/nullness/jdkannotations/Issue1142.java @@ -1,14 +1,13 @@ // Issue 1142 https://github.com/typetools/checker-framework/issues/1142 -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.concurrent.ConcurrentHashMap; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1142 { - void foo() { - // :: error: (type.argument.type.incompatible) - ConcurrentHashMap chm1 = new ConcurrentHashMap<>(); - chm1.put(1, null); - } + void foo() { + // :: error: (type.argument.type.incompatible) + ConcurrentHashMap chm1 = new ConcurrentHashMap<>(); + chm1.put(1, null); + } } diff --git a/checker/tests/nullness/jdkannotations/Issue1402EnumName.java b/checker/tests/nullness/jdkannotations/Issue1402EnumName.java index c3f8a28e8a3..880e7805a4e 100644 --- a/checker/tests/nullness/jdkannotations/Issue1402EnumName.java +++ b/checker/tests/nullness/jdkannotations/Issue1402EnumName.java @@ -4,12 +4,12 @@ // https://github.com/typetools/checker-framework/issues/1402 public enum Issue1402EnumName { - TEST_ONE("abc"), - TEST_TWO("def"); + TEST_ONE("abc"), + TEST_TWO("def"); - private final String newName; + private final String newName; - Issue1402EnumName(String customData) { - this.newName = name(); - } + Issue1402EnumName(String customData) { + this.newName = name(); + } } diff --git a/checker/tests/nullness/jdkannotations/TreeSetTest.java b/checker/tests/nullness/jdkannotations/TreeSetTest.java index dbe751224e9..932f9af9d7f 100644 --- a/checker/tests/nullness/jdkannotations/TreeSetTest.java +++ b/checker/tests/nullness/jdkannotations/TreeSetTest.java @@ -3,18 +3,17 @@ // @skip-test until we fix the issue -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.TreeSet; +import org.checkerframework.checker.nullness.qual.Nullable; public class TreeSetTest { - public static void main(String[] args) { + public static void main(String[] args) { - // :: error: (type.argument.type.incompatible) - TreeSet<@Nullable Integer> ts = new TreeSet<>(); + // :: error: (type.argument.type.incompatible) + TreeSet<@Nullable Integer> ts = new TreeSet<>(); - // This throws a null pointer exception - ts.add(null); - } + // This throws a null pointer exception + ts.add(null); + } } diff --git a/checker/tests/optional-pure-getters/PureGetterTest.java b/checker/tests/optional-pure-getters/PureGetterTest.java index 2ca45545806..5252974b96e 100644 --- a/checker/tests/optional-pure-getters/PureGetterTest.java +++ b/checker/tests/optional-pure-getters/PureGetterTest.java @@ -2,77 +2,77 @@ class PureGetterTest { - @SuppressWarnings("optional.field") - Optional field; + @SuppressWarnings("optional.field") + Optional field; - // This method will be treated as @Pure because of -AassumePureGetters. - Optional getOptional() { - return Optional.of("hello"); - } + // This method will be treated as @Pure because of -AassumePureGetters. + Optional getOptional() { + return Optional.of("hello"); + } - Optional otherOptional() { - return Optional.of("hello"); - } + Optional otherOptional() { + return Optional.of("hello"); + } - void sideEffect() {} + void sideEffect() {} - void foo() { - if (field.isPresent()) { - field.get(); - } - if (field.isPresent()) { - sideEffect(); - // :: error: (method.invocation.invalid) - field.get(); - } - if (field.isPresent()) { - getOptional(); - field.get(); - } - if (field.isPresent()) { - otherOptional(); - // :: error: (method.invocation.invalid) - field.get(); - } + void foo() { + if (field.isPresent()) { + field.get(); + } + if (field.isPresent()) { + sideEffect(); + // :: error: (method.invocation.invalid) + field.get(); + } + if (field.isPresent()) { + getOptional(); + field.get(); + } + if (field.isPresent()) { + otherOptional(); + // :: error: (method.invocation.invalid) + field.get(); + } - if (getOptional().isPresent()) { - getOptional().get(); - } - if (getOptional().isPresent()) { - sideEffect(); - // :: error: (method.invocation.invalid) - getOptional().get(); - } - if (getOptional().isPresent()) { - getOptional(); - getOptional().get(); - } - if (getOptional().isPresent()) { - otherOptional(); - // :: error: (method.invocation.invalid) - getOptional().get(); - } + if (getOptional().isPresent()) { + getOptional().get(); + } + if (getOptional().isPresent()) { + sideEffect(); + // :: error: (method.invocation.invalid) + getOptional().get(); + } + if (getOptional().isPresent()) { + getOptional(); + getOptional().get(); + } + if (getOptional().isPresent()) { + otherOptional(); + // :: error: (method.invocation.invalid) + getOptional().get(); + } - if (otherOptional().isPresent()) { - // BUG: https://github.com/typetools/checker-framework/issues/6291 error: - // (method.invocation.invalid) - otherOptional().get(); - } - if (otherOptional().isPresent()) { - sideEffect(); - // :: error: (method.invocation.invalid) - otherOptional().get(); - } - if (otherOptional().isPresent()) { - getOptional(); - // BUG: https://github.com/typetools/checker-framework/issues/6291 error: - // (method.invocation.invalid) - otherOptional().get(); - } - if (otherOptional().isPresent()) { - otherOptional(); - // :: error: (method.invocation.invalid) - otherOptional().get(); - } + if (otherOptional().isPresent()) { + // BUG: https://github.com/typetools/checker-framework/issues/6291 error: + // (method.invocation.invalid) + otherOptional().get(); + } + if (otherOptional().isPresent()) { + sideEffect(); + // :: error: (method.invocation.invalid) + otherOptional().get(); + } + if (otherOptional().isPresent()) { + getOptional(); + // BUG: https://github.com/typetools/checker-framework/issues/6291 error: + // (method.invocation.invalid) + otherOptional().get(); + } + if (otherOptional().isPresent()) { + otherOptional(); + // :: error: (method.invocation.invalid) + otherOptional().get(); } + } } diff --git a/checker/tests/optional/EnsuresPresentIfTest.java b/checker/tests/optional/EnsuresPresentIfTest.java index 804fa121792..55c18ac1cd6 100644 --- a/checker/tests/optional/EnsuresPresentIfTest.java +++ b/checker/tests/optional/EnsuresPresentIfTest.java @@ -1,86 +1,85 @@ +import java.util.Optional; import org.checkerframework.checker.optional.qual.EnsuresPresentIf; import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.framework.qual.EnsuresQualifierIf; -import java.util.Optional; - public class EnsuresPresentIfTest { - // :: warning: (optional.field) - private Optional optId = Optional.of("abc"); + // :: warning: (optional.field) + private Optional optId = Optional.of("abc"); - @Pure - public Optional getOptId() { - return Optional.of("abc"); - } + @Pure + public Optional getOptId() { + return Optional.of("abc"); + } - @EnsuresPresentIf(result = true, expression = "getOptId()") - public boolean hasPresentId1() { - return getOptId().isPresent(); - } + @EnsuresPresentIf(result = true, expression = "getOptId()") + public boolean hasPresentId1() { + return getOptId().isPresent(); + } - @EnsuresPresentIf(result = true, expression = "this.getOptId()") - public boolean hasPresentId2() { - return getOptId().isPresent(); - } + @EnsuresPresentIf(result = true, expression = "this.getOptId()") + public boolean hasPresentId2() { + return getOptId().isPresent(); + } - @EnsuresQualifierIf(result = true, expression = "getOptId()", qualifier = Present.class) - public boolean hasPresentId3() { - return getOptId().isPresent(); - } + @EnsuresQualifierIf(result = true, expression = "getOptId()", qualifier = Present.class) + public boolean hasPresentId3() { + return getOptId().isPresent(); + } - @EnsuresQualifierIf(result = true, expression = "this.getOptId()", qualifier = Present.class) - public boolean hasPresentId4() { - return getOptId().isPresent(); - } + @EnsuresQualifierIf(result = true, expression = "this.getOptId()", qualifier = Present.class) + public boolean hasPresentId4() { + return getOptId().isPresent(); + } - @EnsuresPresentIf(result = true, expression = "optId") - public boolean hasPresentId5() { - return optId.isPresent(); - } + @EnsuresPresentIf(result = true, expression = "optId") + public boolean hasPresentId5() { + return optId.isPresent(); + } - @EnsuresPresentIf(result = true, expression = "this.optId") - public boolean hasPresentId6() { - return optId.isPresent(); - } + @EnsuresPresentIf(result = true, expression = "this.optId") + public boolean hasPresentId6() { + return optId.isPresent(); + } - @EnsuresQualifierIf(result = true, expression = "optId", qualifier = Present.class) - public boolean hasPresentId7() { - return optId.isPresent(); - } + @EnsuresQualifierIf(result = true, expression = "optId", qualifier = Present.class) + public boolean hasPresentId7() { + return optId.isPresent(); + } - @EnsuresQualifierIf(result = true, expression = "this.optId", qualifier = Present.class) - public boolean hasPresentId8() { - return optId.isPresent(); - } + @EnsuresQualifierIf(result = true, expression = "this.optId", qualifier = Present.class) + public boolean hasPresentId8() { + return optId.isPresent(); + } - void client() { - if (hasPresentId1()) { - getOptId().get(); - } - if (hasPresentId2()) { - getOptId().get(); - } - if (hasPresentId3()) { - getOptId().get(); - } - if (hasPresentId4()) { - getOptId().get(); - } - if (hasPresentId5()) { - optId.get(); - } - if (hasPresentId6()) { - optId.get(); - } - if (hasPresentId7()) { - optId.get(); - } - if (hasPresentId8()) { - optId.get(); - } - // :: error: (method.invocation.invalid) - optId.get(); + void client() { + if (hasPresentId1()) { + getOptId().get(); + } + if (hasPresentId2()) { + getOptId().get(); + } + if (hasPresentId3()) { + getOptId().get(); + } + if (hasPresentId4()) { + getOptId().get(); + } + if (hasPresentId5()) { + optId.get(); + } + if (hasPresentId6()) { + optId.get(); + } + if (hasPresentId7()) { + optId.get(); + } + if (hasPresentId8()) { + optId.get(); } + // :: error: (method.invocation.invalid) + optId.get(); + } } diff --git a/checker/tests/optional/FilterIspresentMapGetTest.java b/checker/tests/optional/FilterIspresentMapGetTest.java index 7dfaab9b185..cb993c151f2 100644 --- a/checker/tests/optional/FilterIspresentMapGetTest.java +++ b/checker/tests/optional/FilterIspresentMapGetTest.java @@ -3,7 +3,7 @@ class FilterIspresentMapGetTest { - void m(Stream> ss) { - ss.filter(Optional::isPresent).map(Optional::get); - } + void m(Stream> ss) { + ss.filter(Optional::isPresent).map(Optional::get); + } } diff --git a/checker/tests/optional/FlowSensitivity.java b/checker/tests/optional/FlowSensitivity.java index d1f45d4259a..ba23547bf6b 100644 --- a/checker/tests/optional/FlowSensitivity.java +++ b/checker/tests/optional/FlowSensitivity.java @@ -4,23 +4,23 @@ @SuppressWarnings("optional.parameter") public class FlowSensitivity { - String noCheck(Optional opt) { - // :: error: (method.invocation.invalid) - return opt.get(); - } + String noCheck(Optional opt) { + // :: error: (method.invocation.invalid) + return opt.get(); + } - String hasCheck1(Optional opt) { - if (opt.isPresent()) { - return opt.get(); - } else { - return "default"; - } + String hasCheck1(Optional opt) { + if (opt.isPresent()) { + return opt.get(); + } else { + return "default"; } + } - String hasCheck2(Optional opt) { - if (!opt.isPresent()) { - return "default"; - } - return opt.get(); + String hasCheck2(Optional opt) { + if (!opt.isPresent()) { + return "default"; } + return opt.get(); + } } diff --git a/checker/tests/optional/IfPresentRefinement.java b/checker/tests/optional/IfPresentRefinement.java index d0a62644219..25c72b00f6f 100644 --- a/checker/tests/optional/IfPresentRefinement.java +++ b/checker/tests/optional/IfPresentRefinement.java @@ -5,16 +5,16 @@ @SuppressWarnings("optional.parameter") public class IfPresentRefinement { - void m1(Optional o) { - o.ifPresent(s -> o.get()); - } + void m1(Optional o) { + o.ifPresent(s -> o.get()); + } - void m2(Optional o) { - o.ifPresentOrElse(s -> o.get(), () -> {}); - } + void m2(Optional o) { + o.ifPresentOrElse(s -> o.get(), () -> {}); + } - void m3(Optional o) { - // :: error: (method.invocation.invalid) - o.ifPresentOrElse(s -> o.get(), () -> o.get()); - } + void m3(Optional o) { + // :: error: (method.invocation.invalid) + o.ifPresentOrElse(s -> o.get(), () -> o.get()); + } } diff --git a/checker/tests/optional/JdkCheck.java b/checker/tests/optional/JdkCheck.java index 14dcf8e3b72..0403ede8dfa 100644 --- a/checker/tests/optional/JdkCheck.java +++ b/checker/tests/optional/JdkCheck.java @@ -1,78 +1,77 @@ -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.optional.qual.Present; - import java.util.Optional; import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.optional.qual.Present; /** Test JDK annotations. */ @SuppressWarnings("optional.parameter") public class JdkCheck { - boolean isPresentTest1(@Present Optional pos) { - return pos.isPresent(); - } - - boolean isPresentTest2(Optional mos) { - return mos.isPresent(); - } - - String orElseThrowTest1( - @Present Optional pos, Supplier exceptionSupplier) { - return pos.orElseThrow(exceptionSupplier); - } - - String orElseThrowTest2(Optional mos, Supplier exceptionSupplier) { - return mos.orElseThrow(exceptionSupplier); - } - - String orElseThrowTestFlow(Optional mos, Supplier exceptionSupplier) { - mos.orElseThrow(exceptionSupplier); - return mos.get(); - } - - String getTest1(@Present Optional pos) { - return pos.get(); - } - - String getTest2(Optional mos) { - // :: error: (method.invocation.invalid) - return mos.get(); - } - - @Present Optional ofTestPNn(String s) { - return Optional.of(s); - } - - Optional ofTestMNn(String s) { - return Optional.of(s); - } - - @Present Optional ofTestPNble(@Nullable String s) { - // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) - return Optional.of(s); - } - - Optional ofTestMNble(@Nullable String s) { - // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) - return Optional.of(s); - } - - @Present Optional ofNullableTestPNble(@Nullable String s) { - // :: error: (return.type.incompatible) - return Optional.ofNullable(s); - } - - /* TODO: ofNullable with non-null arg gives @Present (+ a warning?) - @Present Optional ofNullableTestPNn(String s) { - return Optional.ofNullable(s); - } - */ - - Optional ofNullableTestMNble(@Nullable String s) { - return Optional.ofNullable(s); - } - - Optional ofNullableTestMNn(String s) { - return Optional.ofNullable(s); - } + boolean isPresentTest1(@Present Optional pos) { + return pos.isPresent(); + } + + boolean isPresentTest2(Optional mos) { + return mos.isPresent(); + } + + String orElseThrowTest1( + @Present Optional pos, Supplier exceptionSupplier) { + return pos.orElseThrow(exceptionSupplier); + } + + String orElseThrowTest2(Optional mos, Supplier exceptionSupplier) { + return mos.orElseThrow(exceptionSupplier); + } + + String orElseThrowTestFlow(Optional mos, Supplier exceptionSupplier) { + mos.orElseThrow(exceptionSupplier); + return mos.get(); + } + + String getTest1(@Present Optional pos) { + return pos.get(); + } + + String getTest2(Optional mos) { + // :: error: (method.invocation.invalid) + return mos.get(); + } + + @Present Optional ofTestPNn(String s) { + return Optional.of(s); + } + + Optional ofTestMNn(String s) { + return Optional.of(s); + } + + @Present Optional ofTestPNble(@Nullable String s) { + // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) + return Optional.of(s); + } + + Optional ofTestMNble(@Nullable String s) { + // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) + return Optional.of(s); + } + + @Present Optional ofNullableTestPNble(@Nullable String s) { + // :: error: (return.type.incompatible) + return Optional.ofNullable(s); + } + + /* TODO: ofNullable with non-null arg gives @Present (+ a warning?) + @Present Optional ofNullableTestPNn(String s) { + return Optional.ofNullable(s); + } + */ + + Optional ofNullableTestMNble(@Nullable String s) { + return Optional.ofNullable(s); + } + + Optional ofNullableTestMNn(String s) { + return Optional.ofNullable(s); + } } diff --git a/checker/tests/optional/JdkCheck11.java b/checker/tests/optional/JdkCheck11.java index 94fd70dd9db..84790aaf56a 100644 --- a/checker/tests/optional/JdkCheck11.java +++ b/checker/tests/optional/JdkCheck11.java @@ -1,32 +1,31 @@ // @below-java11-jdk-skip-test -import org.checkerframework.checker.optional.qual.Present; - import java.util.Optional; +import org.checkerframework.checker.optional.qual.Present; /** Test JDK annotations, for methods added after JDK 8. */ @SuppressWarnings("optional.parameter") public class JdkCheck11 { - String isEmptyTest1(Optional pos, String fallback) { - if (pos.isEmpty()) { - return fallback; - } - return pos.get(); + String isEmptyTest1(Optional pos, String fallback) { + if (pos.isEmpty()) { + return fallback; } + return pos.get(); + } - String orElseThrowTest1(@Present Optional pos) { - return pos.orElseThrow(); - } + String orElseThrowTest1(@Present Optional pos) { + return pos.orElseThrow(); + } - String orElseThrowTest2(Optional mos) { - // :: error: (method.invocation.invalid) - return mos.orElseThrow(); - } + String orElseThrowTest2(Optional mos) { + // :: error: (method.invocation.invalid) + return mos.orElseThrow(); + } - String orElseThrowTestFlow(Optional mos) { - // :: error: (method.invocation.invalid) - mos.orElseThrow(); - return mos.get(); - } + String orElseThrowTestFlow(Optional mos) { + // :: error: (method.invocation.invalid) + mos.orElseThrow(); + return mos.get(); + } } diff --git a/checker/tests/optional/MapNoNewNull.java b/checker/tests/optional/MapNoNewNull.java index 8552b38b6d4..f9148ee235b 100644 --- a/checker/tests/optional/MapNoNewNull.java +++ b/checker/tests/optional/MapNoNewNull.java @@ -3,15 +3,14 @@ class MapNoNewNull { - @SuppressWarnings("optional.parameter") - void m(Optional digitsAnnotation) { - if (digitsAnnotation.isPresent()) { - BigInteger maxValue = - digitsAnnotation.map(Digits::integer).map(BigInteger::valueOf).get(); - } + @SuppressWarnings("optional.parameter") + void m(Optional digitsAnnotation) { + if (digitsAnnotation.isPresent()) { + BigInteger maxValue = digitsAnnotation.map(Digits::integer).map(BigInteger::valueOf).get(); } + } } @interface Digits { - public int integer(); + public int integer(); } diff --git a/checker/tests/optional/Marks1Partial.java b/checker/tests/optional/Marks1Partial.java index 8b48b8d0282..56e58f1a9a6 100644 --- a/checker/tests/optional/Marks1Partial.java +++ b/checker/tests/optional/Marks1Partial.java @@ -9,70 +9,70 @@ */ public class Marks1Partial { - @SuppressWarnings("optional.field") - Optional optField = Optional.ofNullable("f1"); + @SuppressWarnings("optional.field") + Optional optField = Optional.ofNullable("f1"); - @SuppressWarnings("optional.parameter") - void simpleEqualsCheck(Optional o1) { - // :: warning: (optional.null.comparison) - if (o1 != null) { - System.out.println("Don't compare optionals (lhs) to null literals."); - } - // :: warning: (optional.null.comparison) - if (null != o1) { - System.out.println("Don't compare optionals (rhs) to null literals."); - } - // :: warning: (optional.null.comparison) - if (o1 == null) { - System.out.println("Don't compare optionals (lhs) to null literals."); - } - // :: warning: (optional.null.comparison) - if (null == o1) { - System.out.println("Don't compare optionals (rhs) to null literals."); - } + @SuppressWarnings("optional.parameter") + void simpleEqualsCheck(Optional o1) { + // :: warning: (optional.null.comparison) + if (o1 != null) { + System.out.println("Don't compare optionals (lhs) to null literals."); } - - @SuppressWarnings("optional.parameter") - void moreComplexEqualsChecks(Optional o1) { - // :: warning: (optional.null.comparison) - if (o1 != null || 1 + 2 == 4) { - System.out.println("Don't compare optionals (lhs) to null literals."); - } + // :: warning: (optional.null.comparison) + if (null != o1) { + System.out.println("Don't compare optionals (rhs) to null literals."); } - - @SuppressWarnings("optional.parameter") - void checkAgainstOptionalField() { - // :: warning: (optional.null.comparison) - if (this.getOptField() != null || 1 + 2 == 4) { - System.out.println("Don't compare optionals (lhs) to null literals."); - } + // :: warning: (optional.null.comparison) + if (o1 == null) { + System.out.println("Don't compare optionals (lhs) to null literals."); } - - public Optional getOptField() { - return optField; + // :: warning: (optional.null.comparison) + if (null == o1) { + System.out.println("Don't compare optionals (rhs) to null literals."); } + } - public void assignOptField() { - // :: warning: (optional.null.assignment) - optField = null; + @SuppressWarnings("optional.parameter") + void moreComplexEqualsChecks(Optional o1) { + // :: warning: (optional.null.comparison) + if (o1 != null || 1 + 2 == 4) { + System.out.println("Don't compare optionals (lhs) to null literals."); } + } - public void assignOptionalDeclaration() { - // :: warning: (optional.null.assignment) - Optional os1 = null; - Optional os2; - if (Math.random() > 0.5) { - os2 = Optional.of("hello"); - } else { - // :: warning: (optional.null.assignment) - os2 = null; - } - // :: warning: (optional.null.assignment) - Optional os3 = Math.random() > 0.5 ? Optional.of("hello") : null; + @SuppressWarnings("optional.parameter") + void checkAgainstOptionalField() { + // :: warning: (optional.null.comparison) + if (this.getOptField() != null || 1 + 2 == 4) { + System.out.println("Don't compare optionals (lhs) to null literals."); } + } - public Optional returnNullOptional() { - // :: warning: (optional.null.assignment) - return (null); + public Optional getOptField() { + return optField; + } + + public void assignOptField() { + // :: warning: (optional.null.assignment) + optField = null; + } + + public void assignOptionalDeclaration() { + // :: warning: (optional.null.assignment) + Optional os1 = null; + Optional os2; + if (Math.random() > 0.5) { + os2 = Optional.of("hello"); + } else { + // :: warning: (optional.null.assignment) + os2 = null; } + // :: warning: (optional.null.assignment) + Optional os3 = Math.random() > 0.5 ? Optional.of("hello") : null; + } + + public Optional returnNullOptional() { + // :: warning: (optional.null.assignment) + return (null); + } } diff --git a/checker/tests/optional/Marks2.java b/checker/tests/optional/Marks2.java index ef6108d52d1..b85b6b9475b 100644 --- a/checker/tests/optional/Marks2.java +++ b/checker/tests/optional/Marks2.java @@ -7,19 +7,19 @@ */ public class Marks2 { - class Customer { - int getID() { - return 42; - } - - String getName() { - return "Fozzy Bear"; - } + class Customer { + int getID() { + return 42; } - String customerNameByID(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - // :: error: (method.invocation.invalid) - return opt.get().getName(); + String getName() { + return "Fozzy Bear"; } + } + + String customerNameByID(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + // :: error: (method.invocation.invalid) + return opt.get().getName(); + } } diff --git a/checker/tests/optional/Marks3a.java b/checker/tests/optional/Marks3a.java index 293b3a8e5df..3850624829c 100644 --- a/checker/tests/optional/Marks3a.java +++ b/checker/tests/optional/Marks3a.java @@ -6,33 +6,33 @@ */ public class Marks3a { - class Customer { - int getID() { - return 42; - } - - String getName() { - return "Fozzy Bear"; - } + class Customer { + int getID() { + return 42; } - String customerNameByID_acceptable(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - - // :: warning: (prefer.map.and.orelse) - return opt.isPresent() ? opt.get().getName() : "UNKNOWN"; + String getName() { + return "Fozzy Bear"; } + } - String customerNameByID_acceptable2(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + String customerNameByID_acceptable(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - // :: warning: (prefer.map.and.orelse) - return !opt.isPresent() ? "UNKNOWN" : opt.get().getName(); - } + // :: warning: (prefer.map.and.orelse) + return opt.isPresent() ? opt.get().getName() : "UNKNOWN"; + } - String customerNameByID_better(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + String customerNameByID_acceptable2(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - return opt.map(Customer::getName).orElse("UNKNOWN"); - } + // :: warning: (prefer.map.and.orelse) + return !opt.isPresent() ? "UNKNOWN" : opt.get().getName(); + } + + String customerNameByID_better(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + + return opt.map(Customer::getName).orElse("UNKNOWN"); + } } diff --git a/checker/tests/optional/Marks3aJdk11.java b/checker/tests/optional/Marks3aJdk11.java index 1114afdd945..ab38e5d01e7 100644 --- a/checker/tests/optional/Marks3aJdk11.java +++ b/checker/tests/optional/Marks3aJdk11.java @@ -8,20 +8,20 @@ */ public class Marks3aJdk11 { - class Customer { - int getID() { - return 42; - } + class Customer { + int getID() { + return 42; + } - String getName() { - return "Fozzy Bear"; - } + String getName() { + return "Fozzy Bear"; } + } - String customerNameByID_acceptable3(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + String customerNameByID_acceptable3(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - // :: warning: (prefer.map.and.orelse) - return opt.isEmpty() ? "UNKNOWN" : opt.get().getName(); - } + // :: warning: (prefer.map.and.orelse) + return opt.isEmpty() ? "UNKNOWN" : opt.get().getName(); + } } diff --git a/checker/tests/optional/Marks3b.java b/checker/tests/optional/Marks3b.java index 742a8cddd7c..877cf3e90f6 100644 --- a/checker/tests/optional/Marks3b.java +++ b/checker/tests/optional/Marks3b.java @@ -6,27 +6,27 @@ @SuppressWarnings("optional.parameter") public class Marks3b { - class Task {} + class Task {} - class Executor { - void runTask(Task t) {} - } + class Executor { + void runTask(Task t) {} + } - Executor executor = new Executor(); + Executor executor = new Executor(); - void bad(Optional oTask) { - // :: warning: (prefer.ifpresent) - if (oTask.isPresent()) { - executor.runTask(oTask.get()); - } + void bad(Optional oTask) { + // :: warning: (prefer.ifpresent) + if (oTask.isPresent()) { + executor.runTask(oTask.get()); } + } - void better(Optional oTask) { - // no warning; better code is possible but has nothing to do with Optional - oTask.ifPresent(task -> executor.runTask(task)); - } + void better(Optional oTask) { + // no warning; better code is possible but has nothing to do with Optional + oTask.ifPresent(task -> executor.runTask(task)); + } - void best(Optional oTask) { - oTask.ifPresent(executor::runTask); - } + void best(Optional oTask) { + oTask.ifPresent(executor::runTask); + } } diff --git a/checker/tests/optional/Marks3bJdk11.java b/checker/tests/optional/Marks3bJdk11.java index 0e9209390ee..feae955395f 100644 --- a/checker/tests/optional/Marks3bJdk11.java +++ b/checker/tests/optional/Marks3bJdk11.java @@ -8,26 +8,26 @@ @SuppressWarnings("optional.parameter") public class Marks3bJdk11 { - class Task {} + class Task {} - class Executor { - void runTask(Task t) {} - } + class Executor { + void runTask(Task t) {} + } - Executor executor = new Executor(); + Executor executor = new Executor(); - void bad2(Optional oTask) { - // :: warning: (prefer.ifpresent) - if (!oTask.isEmpty()) { - executor.runTask(oTask.get()); - } + void bad2(Optional oTask) { + // :: warning: (prefer.ifpresent) + if (!oTask.isEmpty()) { + executor.runTask(oTask.get()); } + } - void bad3(Optional oTask) { - // :: warning: (prefer.ifpresent) - if (oTask.isEmpty()) { - } else { - executor.runTask(oTask.get()); - } + void bad3(Optional oTask) { + // :: warning: (prefer.ifpresent) + if (oTask.isEmpty()) { + } else { + executor.runTask(oTask.get()); } + } } diff --git a/checker/tests/optional/Marks4.java b/checker/tests/optional/Marks4.java index 9f93aa02ab9..87f6eabe094 100644 --- a/checker/tests/optional/Marks4.java +++ b/checker/tests/optional/Marks4.java @@ -7,54 +7,54 @@ */ public class Marks4 { - String getDefault() { - return "Fozzy Bear"; - } - - String process_bad(String s) { - // :: warning: (introduce.eliminate) - return Optional.ofNullable(s).orElseGet(this::getDefault); - } - - String process_bad2(String s) { - // :: warning: (introduce.eliminate) - return Optional.empty().orElseGet(this::getDefault); - } - - String process_bad3(String s) { - // :: warning: (introduce.eliminate) - return Optional.of(s).orElseGet(this::getDefault); - } - - String process_good(String s) { - return (s != null) ? s : getDefault(); - } - - String m1(String s) { - // :: warning: (introduce.eliminate) - return Optional.ofNullable(s).orElseGet(this::getDefault) + "hello"; - } - - boolean m2(String s) { - // :: warning: (introduce.eliminate) - return Objects.equals("hello", Optional.ofNullable(s).orElseGet(this::getDefault)); - } - - boolean m3(String s) { - // :: warning: (introduce.eliminate) - return "hello" == Optional.ofNullable(s).orElseGet(this::getDefault); - } - - String m4(String s) { + String getDefault() { + return "Fozzy Bear"; + } + + String process_bad(String s) { + // :: warning: (introduce.eliminate) + return Optional.ofNullable(s).orElseGet(this::getDefault); + } + + String process_bad2(String s) { + // :: warning: (introduce.eliminate) + return Optional.empty().orElseGet(this::getDefault); + } + + String process_bad3(String s) { + // :: warning: (introduce.eliminate) + return Optional.of(s).orElseGet(this::getDefault); + } + + String process_good(String s) { + return (s != null) ? s : getDefault(); + } + + String m1(String s) { + // :: warning: (introduce.eliminate) + return Optional.ofNullable(s).orElseGet(this::getDefault) + "hello"; + } + + boolean m2(String s) { + // :: warning: (introduce.eliminate) + return Objects.equals("hello", Optional.ofNullable(s).orElseGet(this::getDefault)); + } + + boolean m3(String s) { + // :: warning: (introduce.eliminate) + return "hello" == Optional.ofNullable(s).orElseGet(this::getDefault); + } + + String m4(String s) { + // :: warning: (introduce.eliminate) + return Optional.ofNullable(s).map(Object::toString).orElseGet(this::getDefault); + } + + String m5(String s) { + return Optional.ofNullable(s) + .map(Object::toString) + .map(Object::toString) // :: warning: (introduce.eliminate) - return Optional.ofNullable(s).map(Object::toString).orElseGet(this::getDefault); - } - - String m5(String s) { - return Optional.ofNullable(s) - .map(Object::toString) - .map(Object::toString) - // :: warning: (introduce.eliminate) - .orElseGet(this::getDefault); - } + .orElseGet(this::getDefault); + } } diff --git a/checker/tests/optional/Marks5.java b/checker/tests/optional/Marks5.java index 54dbc0fb2ca..c2f1b971c7b 100644 --- a/checker/tests/optional/Marks5.java +++ b/checker/tests/optional/Marks5.java @@ -1,8 +1,7 @@ -import org.checkerframework.checker.optional.qual.Present; - import java.math.BigDecimal; import java.util.Optional; import java.util.stream.Stream; +import org.checkerframework.checker.optional.qual.Present; /** * Test case for rule #5: "If an Optional chain has a nested Optional chain, or has an intermediate @@ -11,44 +10,44 @@ @SuppressWarnings("optional.parameter") public class Marks5 { - // Each method adds first and second, treating empty as zero, returning an Optional of the sum, - // unless BOTH are empty, in which case return an empty Optional. + // Each method adds first and second, treating empty as zero, returning an Optional of the sum, + // unless BOTH are empty, in which case return an empty Optional. - Optional clever(Optional first, Optional second) { - @SuppressWarnings({"methodref.inference.unimplemented", "methodref.receiver.invalid"}) - Optional result = - Stream.of(first, second) - .filter(Optional::isPresent) - .map(Optional::get) - .reduce(BigDecimal::add); - return result; - } + Optional clever(Optional first, Optional second) { + @SuppressWarnings({"methodref.inference.unimplemented", "methodref.receiver.invalid"}) + Optional result = + Stream.of(first, second) + .filter(Optional::isPresent) + .map(Optional::get) + .reduce(BigDecimal::add); + return result; + } - Optional clever2(Optional first, Optional second) { - Stream> s = Stream.of(first, second); - @SuppressWarnings("assignment.type.incompatible") - Stream<@Present Optional> filtered = - s.>filter(Optional::isPresent); - Stream present = filtered.map(Optional::get); - Optional result = present.reduce(BigDecimal::add); - return result; - } + Optional clever2(Optional first, Optional second) { + Stream> s = Stream.of(first, second); + @SuppressWarnings("assignment.type.incompatible") + Stream<@Present Optional> filtered = + s.>filter(Optional::isPresent); + Stream present = filtered.map(Optional::get); + Optional result = present.reduce(BigDecimal::add); + return result; + } - // The use of `map(Optional::of)` creates Optional, so a warning should be issued - // there. - Optional moreClever(Optional first, Optional second) { - Optional result = - first.map(b -> second.map(b::add).orElse(b)).map(Optional::of).orElse(second); - return result; - } + // The use of `map(Optional::of)` creates Optional, so a warning should be issued + // there. + Optional moreClever(Optional first, Optional second) { + Optional result = + first.map(b -> second.map(b::add).orElse(b)).map(Optional::of).orElse(second); + return result; + } - Optional clear(Optional first, Optional second) { - Optional result; - if (!first.isPresent() && !second.isPresent()) { - result = Optional.empty(); - } else { - result = Optional.of(first.orElse(BigDecimal.ZERO).add(second.orElse(BigDecimal.ZERO))); - } - return result; + Optional clear(Optional first, Optional second) { + Optional result; + if (!first.isPresent() && !second.isPresent()) { + result = Optional.empty(); + } else { + result = Optional.of(first.orElse(BigDecimal.ZERO).add(second.orElse(BigDecimal.ZERO))); } + return result; + } } diff --git a/checker/tests/optional/Marks6.java b/checker/tests/optional/Marks6.java index d82d4eb3968..4d780a5b413 100644 --- a/checker/tests/optional/Marks6.java +++ b/checker/tests/optional/Marks6.java @@ -7,23 +7,23 @@ /** Test cases for Rule #6: "Avoid using Optional in fields, method parameters, and collections." */ public class Marks6 { - // :: warning: (optional.field) - Optional optionalField = Optional.ofNullable(null); + // :: warning: (optional.field) + Optional optionalField = Optional.ofNullable(null); - // :: warning: (optional.parameter) - void optionalParameter(Optional arg) {} + // :: warning: (optional.parameter) + void optionalParameter(Optional arg) {} - Optional okUses() { - Optional os = Optional.of("hello world"); - return os; - } + Optional okUses() { + Optional os = Optional.of("hello world"); + return os; + } - void illegalInstantiations() { - // :: warning: (optional.as.element.type) - List> los = new ArrayList<>(); - // :: warning: (optional.as.element.type) - List> los2 = new ArrayList>(); - // :: warning: (optional.as.element.type) - Set> sos = new HashSet<>(); - } + void illegalInstantiations() { + // :: warning: (optional.as.element.type) + List> los = new ArrayList<>(); + // :: warning: (optional.as.element.type) + List> los2 = new ArrayList>(); + // :: warning: (optional.as.element.type) + Set> sos = new HashSet<>(); + } } diff --git a/checker/tests/optional/Marks7.java b/checker/tests/optional/Marks7.java index 0161389f6ea..c3d677caaf9 100644 --- a/checker/tests/optional/Marks7.java +++ b/checker/tests/optional/Marks7.java @@ -10,10 +10,10 @@ */ public class Marks7 { - void illegalInstantiations() { - // :: warning: (optional.collection) - Optional> ols = Optional.of(new ArrayList()); - // :: warning: (optional.collection) - Optional> oss = Optional.of(new HashSet()); - } + void illegalInstantiations() { + // :: warning: (optional.collection) + Optional> ols = Optional.of(new ArrayList()); + // :: warning: (optional.collection) + Optional> oss = Optional.of(new HashSet()); + } } diff --git a/checker/tests/optional/NestedOptionalTest.java b/checker/tests/optional/NestedOptionalTest.java index 4ce96e2994c..c982f108bcc 100644 --- a/checker/tests/optional/NestedOptionalTest.java +++ b/checker/tests/optional/NestedOptionalTest.java @@ -5,29 +5,29 @@ class NestedOptional { - Object field; + Object field; - @SuppressWarnings("optional.parameter") - // :: warning: (optional.nesting) - Optional bar(Optional> optOptStr) { - if (optOptStr.isPresent()) { - return optOptStr.get(); - } - return Optional.empty(); + @SuppressWarnings("optional.parameter") + // :: warning: (optional.nesting) + Optional bar(Optional> optOptStr) { + if (optOptStr.isPresent()) { + return optOptStr.get(); } + return Optional.empty(); + } - void foo() { - // Explicitly providing a type annotation triggers the error - // :: warning: (optional.nesting) - var x = Optional.of(Optional.of("foo")); // I expect an error here. + void foo() { + // Explicitly providing a type annotation triggers the error + // :: warning: (optional.nesting) + var x = Optional.of(Optional.of("foo")); // I expect an error here. - // :: warning: (optional.nesting) - bar(Optional.of(Optional.of("bar"))); + // :: warning: (optional.nesting) + bar(Optional.of(Optional.of("bar"))); - // :: warning: (optional.nesting) - field = Optional.of(Optional.of("baz")); + // :: warning: (optional.nesting) + field = Optional.of(Optional.of("baz")); - // :: warning: (optional.collection) - field = Optional.of(Collections.singleton("baz")); - } + // :: warning: (optional.collection) + field = Optional.of(Collections.singleton("baz")); + } } diff --git a/checker/tests/optional/OptionalBoxed.java b/checker/tests/optional/OptionalBoxed.java index c13430d402f..766d10ff428 100644 --- a/checker/tests/optional/OptionalBoxed.java +++ b/checker/tests/optional/OptionalBoxed.java @@ -5,20 +5,20 @@ class OptionalBoxed { - // :: warning: (optional.field) - OptionalDouble aField; + // :: warning: (optional.field) + OptionalDouble aField; - // :: warning: (optional.parameter) - void m(OptionalInt aParam) { - // :: warning: (introduce.eliminate) - int x = OptionalLong.of(1L).hashCode(); - // :: warning: (introduce.eliminate) - long y = OptionalLong.of(1L).orElse(2L); - // :: warning: (introduce.eliminate) - boolean b = Optional.empty().isPresent(); - // :: warning: (introduce.eliminate) - OptionalDouble.empty().ifPresent(d -> {}); - // :: warning: (introduce.eliminate) - boolean b4 = OptionalLong.empty().isPresent(); - } + // :: warning: (optional.parameter) + void m(OptionalInt aParam) { + // :: warning: (introduce.eliminate) + int x = OptionalLong.of(1L).hashCode(); + // :: warning: (introduce.eliminate) + long y = OptionalLong.of(1L).orElse(2L); + // :: warning: (introduce.eliminate) + boolean b = Optional.empty().isPresent(); + // :: warning: (introduce.eliminate) + OptionalDouble.empty().ifPresent(d -> {}); + // :: warning: (introduce.eliminate) + boolean b4 = OptionalLong.empty().isPresent(); + } } diff --git a/checker/tests/optional/OptionalMapMethodReference.java b/checker/tests/optional/OptionalMapMethodReference.java index f3f9d47026d..02efa7d6ddf 100644 --- a/checker/tests/optional/OptionalMapMethodReference.java +++ b/checker/tests/optional/OptionalMapMethodReference.java @@ -1,35 +1,34 @@ +import java.util.Optional; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.checker.optional.qual.Present; -import java.util.Optional; - public class OptionalMapMethodReference { - Optional getString() { - return Optional.of(""); - } + Optional getString() { + return Optional.of(""); + } - @Present Optional method() { - Optional o = getString(); - @Present Optional oInt; - if (o.isPresent()) { - // :: error: (assignment.type.incompatible) - oInt = o.map(this::convertNull); - oInt = o.map(this::convertPoly); - return o.map(this::convert); - } - return Optional.of(0); + @Present Optional method() { + Optional o = getString(); + @Present Optional oInt; + if (o.isPresent()) { + // :: error: (assignment.type.incompatible) + oInt = o.map(this::convertNull); + oInt = o.map(this::convertPoly); + return o.map(this::convert); } + return Optional.of(0); + } - @Nullable Integer convertNull(String s) { - return null; - } + @Nullable Integer convertNull(String s) { + return null; + } - @PolyNull Integer convertPoly(@PolyNull String s) { - return null; - } + @PolyNull Integer convertPoly(@PolyNull String s) { + return null; + } - Integer convert(String s) { - return 0; - } + Integer convert(String s) { + return 0; + } } diff --git a/checker/tests/optional/OptionalParameterTest.java b/checker/tests/optional/OptionalParameterTest.java index d1f200e1bc5..eb68d1d24a0 100644 --- a/checker/tests/optional/OptionalParameterTest.java +++ b/checker/tests/optional/OptionalParameterTest.java @@ -4,10 +4,10 @@ import java.util.stream.Stream; class OptionalParameterTest { - public void findDatesByIds2(List ids) { - ids.stream() - .map(Optional::ofNullable) - .flatMap(optional -> optional.map(Stream::of).orElseGet(Stream::empty)) - .collect(Collectors.toList()); - } + public void findDatesByIds2(List ids) { + ids.stream() + .map(Optional::ofNullable) + .flatMap(optional -> optional.map(Stream::of).orElseGet(Stream::empty)) + .collect(Collectors.toList()); + } } diff --git a/checker/tests/optional/RequiresPresentTest.java b/checker/tests/optional/RequiresPresentTest.java index 966ae641c5b..8e7d833b223 100644 --- a/checker/tests/optional/RequiresPresentTest.java +++ b/checker/tests/optional/RequiresPresentTest.java @@ -1,73 +1,72 @@ -import org.checkerframework.checker.optional.qual.*; - import java.util.Optional; +import org.checkerframework.checker.optional.qual.*; public class RequiresPresentTest { - // :: warning: (optional.field) - Optional field1 = Optional.of("abc"); - // :: warning: (optional.field) - Optional field2 = Optional.empty(); - - @RequiresPresent("field1") - void method1() { - field1.get().length(); // OK, field1 is known to be present (non-empty) - this.field1.get().length(); // OK, field1 is known to be present (non-empty) - // :: error: (method.invocation.invalid) - field2.get().length(); // error, might throw NoSuchElementException - } - - @RequiresPresent("field1") - void method2() { - // OK, an indirect call to method1. - method1(); - } - - void method3() { - field1 = Optional.of("abc"); - method1(); // OK, satisfied method precondition. - field1 = Optional.empty(); - // :: error: (contracts.precondition.not.satisfied) - method1(); // error, does not satisfy method precondition. - } - - // :: warning: (optional.field) - protected Optional field; - - @RequiresPresent("field") - public void requiresPresentField() {} - - public void clientFail(RequiresPresentTest arg1) { - // :: error: (contracts.precondition.not.satisfied) - arg1.requiresPresentField(); - } - - public void clientOK(RequiresPresentTest arg2) { - arg2.field = Optional.of("def"); - - // this is legal. - @Present Optional optField = arg2.field; - - // OK, field is known to be present. - arg2.requiresPresentField(); - } - - @RequiresPresent({"field1", "field2"}) - void method4() { - field1.get().length(); // OK, field1 is known to be present (non-empty) - this.field1.get().length(); // OK, field1 is known to be present (non-empty) - - field2.get().length(); // OK, field2 is known to be preent (non-empty) - this.field2.get().length(); // OK, field2 is known to be present (non-empty) - } - - void method5() { - field1 = Optional.of("abc"); - field2 = Optional.of("def"); - method4(); // OK, both preconditions now hold at this point. - - field1 = Optional.empty(); - // :: error: (contracts.precondition.not.satisfied) - method4(); // error, field1 is no longer present. - } + // :: warning: (optional.field) + Optional field1 = Optional.of("abc"); + // :: warning: (optional.field) + Optional field2 = Optional.empty(); + + @RequiresPresent("field1") + void method1() { + field1.get().length(); // OK, field1 is known to be present (non-empty) + this.field1.get().length(); // OK, field1 is known to be present (non-empty) + // :: error: (method.invocation.invalid) + field2.get().length(); // error, might throw NoSuchElementException + } + + @RequiresPresent("field1") + void method2() { + // OK, an indirect call to method1. + method1(); + } + + void method3() { + field1 = Optional.of("abc"); + method1(); // OK, satisfied method precondition. + field1 = Optional.empty(); + // :: error: (contracts.precondition.not.satisfied) + method1(); // error, does not satisfy method precondition. + } + + // :: warning: (optional.field) + protected Optional field; + + @RequiresPresent("field") + public void requiresPresentField() {} + + public void clientFail(RequiresPresentTest arg1) { + // :: error: (contracts.precondition.not.satisfied) + arg1.requiresPresentField(); + } + + public void clientOK(RequiresPresentTest arg2) { + arg2.field = Optional.of("def"); + + // this is legal. + @Present Optional optField = arg2.field; + + // OK, field is known to be present. + arg2.requiresPresentField(); + } + + @RequiresPresent({"field1", "field2"}) + void method4() { + field1.get().length(); // OK, field1 is known to be present (non-empty) + this.field1.get().length(); // OK, field1 is known to be present (non-empty) + + field2.get().length(); // OK, field2 is known to be preent (non-empty) + this.field2.get().length(); // OK, field2 is known to be present (non-empty) + } + + void method5() { + field1 = Optional.of("abc"); + field2 = Optional.of("def"); + method4(); // OK, both preconditions now hold at this point. + + field1 = Optional.empty(); + // :: error: (contracts.precondition.not.satisfied) + method4(); // error, field1 is no longer present. + } } diff --git a/checker/tests/optional/SubtypeCheck.java b/checker/tests/optional/SubtypeCheck.java index 1d4d926a2d2..f45131a9d8a 100644 --- a/checker/tests/optional/SubtypeCheck.java +++ b/checker/tests/optional/SubtypeCheck.java @@ -1,28 +1,27 @@ +import java.util.Optional; import org.checkerframework.checker.optional.qual.MaybePresent; import org.checkerframework.checker.optional.qual.OptionalBottom; import org.checkerframework.checker.optional.qual.Present; -import java.util.Optional; - /** Basic test of subtyping. */ public class SubtypeCheck { - @SuppressWarnings("optional.parameter") - void foo( - @MaybePresent Optional mp, - @Present Optional p, - @OptionalBottom Optional ob) { - @MaybePresent Optional mp2 = mp; - @MaybePresent Optional mp3 = p; - @MaybePresent Optional mp4 = ob; - // :: error: assignment.type.incompatible - @Present Optional p2 = mp; - @Present Optional p3 = p; - @Present Optional p4 = ob; - // :: error: assignment.type.incompatible - @OptionalBottom Optional ob2 = mp; - // :: error: assignment.type.incompatible - @OptionalBottom Optional ob3 = p; - @OptionalBottom Optional ob4 = ob; - } + @SuppressWarnings("optional.parameter") + void foo( + @MaybePresent Optional mp, + @Present Optional p, + @OptionalBottom Optional ob) { + @MaybePresent Optional mp2 = mp; + @MaybePresent Optional mp3 = p; + @MaybePresent Optional mp4 = ob; + // :: error: assignment.type.incompatible + @Present Optional p2 = mp; + @Present Optional p3 = p; + @Present Optional p4 = ob; + // :: error: assignment.type.incompatible + @OptionalBottom Optional ob2 = mp; + // :: error: assignment.type.incompatible + @OptionalBottom Optional ob3 = p; + @OptionalBottom Optional ob4 = ob; + } } diff --git a/checker/tests/optional/java17/OptionalSwitch.java b/checker/tests/optional/java17/OptionalSwitch.java index 656c9a01fcc..ea93f342bbd 100644 --- a/checker/tests/optional/java17/OptionalSwitch.java +++ b/checker/tests/optional/java17/OptionalSwitch.java @@ -1,11 +1,11 @@ // @below-java17-jdk-skip-test public class OptionalSwitch { - public static boolean flag; + public static boolean flag; - public Object test(int c) { - return switch (c) { - case 3 -> flag ? "" : "obj.getBaseStat();"; - default -> null; - }; - } + public Object test(int c) { + return switch (c) { + case 3 -> flag ? "" : "obj.getBaseStat();"; + default -> null; + }; + } } diff --git a/checker/tests/regex/AllowedTypes.java b/checker/tests/regex/AllowedTypes.java index 28860cbc062..50573dc899b 100644 --- a/checker/tests/regex/AllowedTypes.java +++ b/checker/tests/regex/AllowedTypes.java @@ -1,61 +1,59 @@ -import org.checkerframework.checker.regex.qual.Regex; - import java.util.ArrayList; import java.util.List; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.swing.text.Segment; +import org.checkerframework.checker.regex.qual.Regex; public class AllowedTypes { + @Regex CharSequence cs; + @Regex String s11; + @Regex StringBuilder sb; + @Regex Segment s21; + @Regex char c; + @Regex Pattern p; + @Regex Matcher m; + @Regex Character c2; + @Regex Object o; + + abstract static class MyMatchResult implements MatchResult {} + + @Regex MyMatchResult mp; + + // :: error: (anno.on.irrelevant) + @Regex List l; + // :: error: (anno.on.irrelevant) + ArrayList<@Regex Double> al; + // :: error: (anno.on.irrelevant) + @Regex int i; + // :: error: (anno.on.irrelevant) + @Regex boolean b; + // :: error: (anno.on.irrelevant) + @Regex Integer i2; + + void testAllowedTypes() { @Regex CharSequence cs; @Regex String s11; @Regex StringBuilder sb; @Regex Segment s21; @Regex char c; - @Regex Pattern p; - @Regex Matcher m; - @Regex Character c2; @Regex Object o; - abstract static class MyMatchResult implements MatchResult {} - - @Regex MyMatchResult mp; - - // :: error: (anno.on.irrelevant) - @Regex List l; // :: error: (anno.on.irrelevant) - ArrayList<@Regex Double> al; + @Regex List l; // error // :: error: (anno.on.irrelevant) - @Regex int i; + ArrayList<@Regex Double> al; // error // :: error: (anno.on.irrelevant) - @Regex boolean b; + @Regex int i; // error // :: error: (anno.on.irrelevant) - @Regex Integer i2; - - void testAllowedTypes() { - @Regex CharSequence cs; - @Regex String s11; - @Regex StringBuilder sb; - @Regex Segment s21; - @Regex char c; - @Regex Object o; - - // :: error: (anno.on.irrelevant) - @Regex List l; // error - // :: error: (anno.on.irrelevant) - ArrayList<@Regex Double> al; // error - // :: error: (anno.on.irrelevant) - @Regex int i; // error - // :: error: (anno.on.irrelevant) - @Regex boolean b; // error - - @Regex String regex = "a"; - // :: error: (compound.assignment.type.incompatible) - regex += "("; - - String nonRegex = "a"; - nonRegex += "("; - } + @Regex boolean b; // error + + @Regex String regex = "a"; + // :: error: (compound.assignment.type.incompatible) + regex += "("; + + String nonRegex = "a"; + nonRegex += "("; + } } diff --git a/checker/tests/regex/AnnotatedTypeParams3.java b/checker/tests/regex/AnnotatedTypeParams3.java index cde1ef5ec9d..031e88c52b9 100644 --- a/checker/tests/regex/AnnotatedTypeParams3.java +++ b/checker/tests/regex/AnnotatedTypeParams3.java @@ -1,50 +1,49 @@ -import org.checkerframework.checker.regex.qual.Regex; - import java.lang.annotation.Annotation; import java.lang.reflect.*; +import org.checkerframework.checker.regex.qual.Regex; public class AnnotatedTypeParams3 { - private T safeGetAnnotation(Field f, Class annotationClass) { - T annotation; - try { - annotation = f.getAnnotation((Class) annotationClass); - } catch (Exception e) { - annotation = null; - } - return annotation; + private T safeGetAnnotation(Field f, Class annotationClass) { + T annotation; + try { + annotation = f.getAnnotation((Class) annotationClass); + } catch (Exception e) { + annotation = null; } - - private T safeGetAnnotation2(Field f, Class annotationClass) { - T annotation; - try { - annotation = f.getAnnotation(annotationClass); - } catch (Exception e) { - annotation = null; - } - return annotation; - } - - <@Regex T extends @Regex Object> void test(T p) { - Object o = p; - @Regex Object re = o; - } - - void test2(T p) { - Object o = p; - @Regex Object re = o; - } - - // TODO: do we want to infer the type variable annotation on local variable "o"? - void test3(@Regex T p) { - T o = p; - @Regex T re = o; + return annotation; + } + + private T safeGetAnnotation2(Field f, Class annotationClass) { + T annotation; + try { + annotation = f.getAnnotation(annotationClass); + } catch (Exception e) { + annotation = null; } + return annotation; + } + + <@Regex T extends @Regex Object> void test(T p) { + Object o = p; + @Regex Object re = o; + } + + void test2(T p) { + Object o = p; + @Regex Object re = o; + } + + // TODO: do we want to infer the type variable annotation on local variable "o"? + void test3(@Regex T p) { + T o = p; + @Regex T re = o; + } } class OuterClass { - public InnerClass method() { - return new InnerClass<>(); - } + public InnerClass method() { + return new InnerClass<>(); + } - class InnerClass {} + class InnerClass {} } diff --git a/checker/tests/regex/Annotation.java b/checker/tests/regex/Annotation.java index a0b60517db9..db17e526cc1 100644 --- a/checker/tests/regex/Annotation.java +++ b/checker/tests/regex/Annotation.java @@ -1,21 +1,21 @@ @interface A1 { - String[] value() default {}; + String[] value() default {}; } @interface A2 { - String[] value(); + String[] value(); } public class Annotation { - @A1({"a", "b"}) - void m1() {} + @A1({"a", "b"}) + void m1() {} - @A1(value = {"a", "b"}) - void m2() {} + @A1(value = {"a", "b"}) + void m2() {} - @A2({"a", "b"}) - void m3() {} + @A2({"a", "b"}) + void m3() {} - @A2(value = {"a", "b"}) - void m4() {} + @A2(value = {"a", "b"}) + void m4() {} } diff --git a/checker/tests/regex/Constructors.java b/checker/tests/regex/Constructors.java index 2a7ff6c30d1..635a8f4527f 100644 --- a/checker/tests/regex/Constructors.java +++ b/checker/tests/regex/Constructors.java @@ -1,27 +1,27 @@ import org.checkerframework.checker.regex.qual.Regex; public class Constructors { - public Constructors(Constructors con) {} + public Constructors(Constructors con) {} - public Constructors(@Regex String s, int i) {} + public Constructors(@Regex String s, int i) {} - public class MyConstructors extends Constructors { - public MyConstructors(@Regex String s) { - super(s, 0); - } + public class MyConstructors extends Constructors { + public MyConstructors(@Regex String s) { + super(s, 0); } + } - public void testAnonymousConstructor(String s) { + public void testAnonymousConstructor(String s) { - Constructors m = new Constructors(null); + Constructors m = new Constructors(null); - // :: error: (argument.type.incompatible) - new MyConstructors(s); - // :: error: (argument.type.incompatible) - new MyConstructors(s) {}; - // :: error: (argument.type.incompatible) - m.new MyConstructors(s); - // :: error: (argument.type.incompatible) - m.new MyConstructors(s) {}; - } + // :: error: (argument.type.incompatible) + new MyConstructors(s); + // :: error: (argument.type.incompatible) + new MyConstructors(s) {}; + // :: error: (argument.type.incompatible) + m.new MyConstructors(s); + // :: error: (argument.type.incompatible) + m.new MyConstructors(s) {}; + } } diff --git a/checker/tests/regex/Continue.java b/checker/tests/regex/Continue.java index ecca0b44614..e497e0fec61 100644 --- a/checker/tests/regex/Continue.java +++ b/checker/tests/regex/Continue.java @@ -1,56 +1,55 @@ -import org.checkerframework.checker.regex.util.RegexUtil; - import java.util.regex.Pattern; +import org.checkerframework.checker.regex.util.RegexUtil; public class Continue { - void test1(String[] a) { - for (String s : a) { - if (!RegexUtil.isRegex(s)) { - continue; - } - Pattern.compile(s); - } + void test1(String[] a) { + for (String s : a) { + if (!RegexUtil.isRegex(s)) { + continue; + } + Pattern.compile(s); } + } - void test2(String[] a, boolean b) { - for (String s : a) { - if (!RegexUtil.isRegex(s)) { - continue; - } else if (b) { - continue; - } - Pattern.compile(s); - } + void test2(String[] a, boolean b) { + for (String s : a) { + if (!RegexUtil.isRegex(s)) { + continue; + } else if (b) { + continue; + } + Pattern.compile(s); } + } - // Reverse the if statements from the previous test. - void test3(String[] a, boolean b) { - for (String s : a) { - if (b) { - continue; - } else if (!RegexUtil.isRegex(s)) { - continue; - } - Pattern.compile(s); - } + // Reverse the if statements from the previous test. + void test3(String[] a, boolean b) { + for (String s : a) { + if (b) { + continue; + } else if (!RegexUtil.isRegex(s)) { + continue; + } + Pattern.compile(s); } + } - void twoThrows(String s) { - if (s == null) { - throw new RuntimeException(); - } else if (!RegexUtil.isRegex(s)) { - throw new RuntimeException(); - } - Pattern.compile(s); + void twoThrows(String s) { + if (s == null) { + throw new RuntimeException(); + } else if (!RegexUtil.isRegex(s)) { + throw new RuntimeException(); } + Pattern.compile(s); + } - void twoReturns(String s) { - if (s == null) { - return; - } else if (!RegexUtil.isRegex(s)) { - return; - } - Pattern.compile(s); + void twoReturns(String s) { + if (s == null) { + return; + } else if (!RegexUtil.isRegex(s)) { + return; } + Pattern.compile(s); + } } diff --git a/checker/tests/regex/ForEach.java b/checker/tests/regex/ForEach.java index e7c90c7873c..ffa8d214407 100644 --- a/checker/tests/regex/ForEach.java +++ b/checker/tests/regex/ForEach.java @@ -1,8 +1,8 @@ public class ForEach { - T iterate(T[] constants) { - for (T constant : constants) { - return constant; - } - return null; + T iterate(T[] constants) { + for (T constant : constants) { + return constant; } + return null; + } } diff --git a/checker/tests/regex/GenericsBoundsRange.java b/checker/tests/regex/GenericsBoundsRange.java index 88df34b1c88..8b3c7c2f5b8 100644 --- a/checker/tests/regex/GenericsBoundsRange.java +++ b/checker/tests/regex/GenericsBoundsRange.java @@ -1,38 +1,37 @@ package regex; -import org.checkerframework.checker.regex.qual.Regex; - import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.regex.qual.Regex; /** Designed to test whether or not a bounds range of generics actually works. */ public class GenericsBoundsRange<@Regex(3) T extends @Regex(1) String> { - public T t; + public T t; - public GenericsBoundsRange(T t) { - Matcher matcher = Pattern.compile(t).matcher("some str"); - if (matcher.matches()) { - matcher.group(0); - matcher.group(1); + public GenericsBoundsRange(T t) { + Matcher matcher = Pattern.compile(t).matcher("some str"); + if (matcher.matches()) { + matcher.group(0); + matcher.group(1); - // T has at least 1 group so the above 2 group calls are good - // however, T MAY or MAY NOT have 2 or 3 groups, so issue an error + // T has at least 1 group so the above 2 group calls are good + // however, T MAY or MAY NOT have 2 or 3 groups, so issue an error - // :: error: (group.count.invalid) - matcher.group(2); + // :: error: (group.count.invalid) + matcher.group(2); - // :: error: (group.count.invalid) - matcher.group(3); + // :: error: (group.count.invalid) + matcher.group(3); - // T definitely does not have 4 groups, issue an error + // T definitely does not have 4 groups, issue an error - // :: error: (group.count.invalid) - matcher.group(4); - } + // :: error: (group.count.invalid) + matcher.group(4); } + } - // Bounds used to not actually be bounds but instead exactly the lower bound - // so line below would fail because the argument could only be Regex(0). So this - // tests BaseTypeValidator.checkTypeArguments range checking. - public void method(GenericsBoundsRange<@Regex(2) String> gbr) {} + // Bounds used to not actually be bounds but instead exactly the lower bound + // so line below would fail because the argument could only be Regex(0). So this + // tests BaseTypeValidator.checkTypeArguments range checking. + public void method(GenericsBoundsRange<@Regex(2) String> gbr) {} } diff --git a/checker/tests/regex/GenericsEnclosing.java b/checker/tests/regex/GenericsEnclosing.java index 4bf3e7d8666..90f46503577 100644 --- a/checker/tests/regex/GenericsEnclosing.java +++ b/checker/tests/regex/GenericsEnclosing.java @@ -7,24 +7,24 @@ *

Also see all-systems/GenericsEnclosing for the type-system independent test. */ class MyG { - X f; + X f; - void m(X p) {} + void m(X p) {} } class ExtMyG extends MyG<@Regex String> { - class EInner1 { - class EInner2 { - void bar() { - String s = f; - f = "hi"; - // :: error: (assignment.type.incompatible) - f = "\\ no regex("; + class EInner1 { + class EInner2 { + void bar() { + String s = f; + f = "hi"; + // :: error: (assignment.type.incompatible) + f = "\\ no regex("; - m("hi!"); - // :: error: (argument.type.incompatible) - m("\\ no regex("); - } - } + m("hi!"); + // :: error: (argument.type.incompatible) + m("\\ no regex("); + } } + } } diff --git a/checker/tests/regex/GroupCounts.java b/checker/tests/regex/GroupCounts.java index c610fdf74ca..6b17458c583 100644 --- a/checker/tests/regex/GroupCounts.java +++ b/checker/tests/regex/GroupCounts.java @@ -1,123 +1,122 @@ -import org.checkerframework.checker.regex.qual.Regex; -import org.checkerframework.checker.regex.util.RegexUtil; - import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.checker.regex.util.RegexUtil; public class GroupCounts { - void testGroupCount() { - @Regex(0) String s1 = "abc"; - @Regex(1) String s2 = "(abc)"; - @Regex(2) String s3 = "()(abc)"; - @Regex(3) String s4 = "(abc())()"; - @Regex(4) String s5 = "((((abc))))"; - - @Regex(0) String s7 = "(abc)"; - @Regex String s9 = "()()(())"; - @Regex(2) String s10 = "()()(())"; - @Regex(3) String s11 = "()()(())"; - - // :: error: (assignment.type.incompatible) - @Regex(2) String s6 = "nonregex("; // error - // :: error: (assignment.type.incompatible) - @Regex(1) String s8 = "abc"; // error - // :: error: (assignment.type.incompatible) - @Regex(3) String s12 = "()()"; // error - // :: error: (assignment.type.incompatible) - @Regex(4) String s13 = "(())()"; // error - } - - void testPatternCompileGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { - @Regex(5) Pattern p1 = Pattern.compile(r5); - @Regex(5) Pattern p1a = Pattern.compile(r5, 0); - @Regex Pattern p2 = Pattern.compile(r5); - @Regex Pattern p3 = Pattern.compile(r); - - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p4 = Pattern.compile(r5); // error - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p4a = Pattern.compile(r5, 0); // error - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p5 = Pattern.compile(r3); // error - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p5a = Pattern.compile(r3, 0); // error - - // Make sure Pattern.compile still works when passed an @UnknownRegex String - // that's actually a regex, with the warning suppressed. - @SuppressWarnings("regex:argument.type.incompatible") - Pattern p6 = Pattern.compile("(" + r + ")"); - @SuppressWarnings("regex:argument.type.incompatible") - Pattern p6a = Pattern.compile("(" + r + ")", 0); - } - - void testConcatenationGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { - @Regex(0) String s1 = r + r; - @Regex(3) String s2 = r + r3; - @Regex(8) String s3 = r3 + r5; - - // :: error: (assignment.type.incompatible) - @Regex(1) String s4 = r + r; - // :: error: (assignment.type.incompatible) - @Regex(4) String s5 = r + r3; - // :: error: (assignment.type.incompatible) - @Regex(9) String s6 = r3 + r5; - } - - void testCompoundConcatenationWithGroups( - @Regex String s0, @Regex(1) String s1, @Regex(3) String s3) { - s0 += s0; - @Regex String test0 = s0; - // :: error: (assignment.type.incompatible) - @Regex(1) String test01 = s0; - - s0 += s1; - @Regex(1) String test1 = s0; - // :: error: (assignment.type.incompatible) - @Regex(2) String test12 = s0; - - s1 += s3; - @Regex(4) String test4 = s1; - // :: error: (assignment.type.incompatible) - @Regex(5) String test45 = s1; - } - - void testAsRegexGroupCounts(String s) { - @Regex String test1 = RegexUtil.asRegex(s); - // :: error: (assignment.type.incompatible) - @Regex(1) String test2 = RegexUtil.asRegex(s); - - @Regex(3) String test3 = RegexUtil.asRegex(s, 3); - // :: error: (assignment.type.incompatible) - @Regex(4) String test4 = RegexUtil.asRegex(s, 3); - } - - void testMatcherGroupCounts( - @Regex Matcher m0, @Regex(1) Matcher m1, @Regex(4) Matcher m4, int n) { - m0.end(0); - m0.group(0); - m0.start(0); - - // :: error: (group.count.invalid) - m0.end(1); - // :: error: (group.count.invalid) - m0.group(1); - // :: error: (group.count.invalid) - m0.start(1); - - m1.start(0); - m1.start(1); - - // :: error: (group.count.invalid) - m1.start(2); - - m4.start(0); - m4.start(2); - m4.start(4); - - // :: error: (group.count.invalid) - m4.start(5); - - // :: warning: (group.count.unknown) - m0.start(n); - } + void testGroupCount() { + @Regex(0) String s1 = "abc"; + @Regex(1) String s2 = "(abc)"; + @Regex(2) String s3 = "()(abc)"; + @Regex(3) String s4 = "(abc())()"; + @Regex(4) String s5 = "((((abc))))"; + + @Regex(0) String s7 = "(abc)"; + @Regex String s9 = "()()(())"; + @Regex(2) String s10 = "()()(())"; + @Regex(3) String s11 = "()()(())"; + + // :: error: (assignment.type.incompatible) + @Regex(2) String s6 = "nonregex("; // error + // :: error: (assignment.type.incompatible) + @Regex(1) String s8 = "abc"; // error + // :: error: (assignment.type.incompatible) + @Regex(3) String s12 = "()()"; // error + // :: error: (assignment.type.incompatible) + @Regex(4) String s13 = "(())()"; // error + } + + void testPatternCompileGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { + @Regex(5) Pattern p1 = Pattern.compile(r5); + @Regex(5) Pattern p1a = Pattern.compile(r5, 0); + @Regex Pattern p2 = Pattern.compile(r5); + @Regex Pattern p3 = Pattern.compile(r); + + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p4 = Pattern.compile(r5); // error + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p4a = Pattern.compile(r5, 0); // error + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p5 = Pattern.compile(r3); // error + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p5a = Pattern.compile(r3, 0); // error + + // Make sure Pattern.compile still works when passed an @UnknownRegex String + // that's actually a regex, with the warning suppressed. + @SuppressWarnings("regex:argument.type.incompatible") + Pattern p6 = Pattern.compile("(" + r + ")"); + @SuppressWarnings("regex:argument.type.incompatible") + Pattern p6a = Pattern.compile("(" + r + ")", 0); + } + + void testConcatenationGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { + @Regex(0) String s1 = r + r; + @Regex(3) String s2 = r + r3; + @Regex(8) String s3 = r3 + r5; + + // :: error: (assignment.type.incompatible) + @Regex(1) String s4 = r + r; + // :: error: (assignment.type.incompatible) + @Regex(4) String s5 = r + r3; + // :: error: (assignment.type.incompatible) + @Regex(9) String s6 = r3 + r5; + } + + void testCompoundConcatenationWithGroups( + @Regex String s0, @Regex(1) String s1, @Regex(3) String s3) { + s0 += s0; + @Regex String test0 = s0; + // :: error: (assignment.type.incompatible) + @Regex(1) String test01 = s0; + + s0 += s1; + @Regex(1) String test1 = s0; + // :: error: (assignment.type.incompatible) + @Regex(2) String test12 = s0; + + s1 += s3; + @Regex(4) String test4 = s1; + // :: error: (assignment.type.incompatible) + @Regex(5) String test45 = s1; + } + + void testAsRegexGroupCounts(String s) { + @Regex String test1 = RegexUtil.asRegex(s); + // :: error: (assignment.type.incompatible) + @Regex(1) String test2 = RegexUtil.asRegex(s); + + @Regex(3) String test3 = RegexUtil.asRegex(s, 3); + // :: error: (assignment.type.incompatible) + @Regex(4) String test4 = RegexUtil.asRegex(s, 3); + } + + void testMatcherGroupCounts( + @Regex Matcher m0, @Regex(1) Matcher m1, @Regex(4) Matcher m4, int n) { + m0.end(0); + m0.group(0); + m0.start(0); + + // :: error: (group.count.invalid) + m0.end(1); + // :: error: (group.count.invalid) + m0.group(1); + // :: error: (group.count.invalid) + m0.start(1); + + m1.start(0); + m1.start(1); + + // :: error: (group.count.invalid) + m1.start(2); + + m4.start(0); + m4.start(2); + m4.start(4); + + // :: error: (group.count.invalid) + m4.start(5); + + // :: warning: (group.count.unknown) + m0.start(n); + } } diff --git a/checker/tests/regex/IntCast.java b/checker/tests/regex/IntCast.java index 0973b19a2f4..100690c30b3 100644 --- a/checker/tests/regex/IntCast.java +++ b/checker/tests/regex/IntCast.java @@ -1,6 +1,6 @@ public class IntCast { - int m() { - return (int) '\n'; - } + int m() { + return (int) '\n'; + } } diff --git a/checker/tests/regex/InvariantTypes.java b/checker/tests/regex/InvariantTypes.java index 97356307a97..1ee10103b57 100644 --- a/checker/tests/regex/InvariantTypes.java +++ b/checker/tests/regex/InvariantTypes.java @@ -1,110 +1,109 @@ -import org.checkerframework.checker.regex.qual.Regex; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.regex.qual.Regex; public class InvariantTypes { - String[] sa = {"a"}; - String[] sa2 = {"a", "b"}; - public String[] sa3 = {"a", "b"}; - public static String[] sa4 = {"a", "b"}; - public final String[] sa5 = {"a", "b"}; - public static final String[] sa6 = {"a", "b"}; - final String[] sa7 = {"a", "b"}; - - // tested above: String[] sa = {"a"}; - @Regex String[] rsa = {"a"}; - String[] nrsa = {"(a"}; - // :: error: (array.initializer.type.incompatible) :: error: (assignment.type.incompatible) - @Regex String[] rsaerr = {"(a"}; - - List ls = Arrays.asList("alice", "bob", "carol"); - List<@Regex String> lrs = Arrays.asList("alice", "bob", "carol"); - List lnrs = Arrays.asList("(alice", "bob", "carol"); - // :: error: (assignment.type.incompatible) - List<@Regex String> lrserr = Arrays.asList("(alice", "bob", "carol"); - - void unqm(String[] sa) {} - - void rem(@Regex String[] rsa) {} - - void recalls() { - unqm(new String[] {"a"}); - // TODOINVARR:: error: (argument.type.incompatible) - unqm(new @Regex String[] {"a"}); - rem(new String[] {"a"}); - rem(new @Regex String[] {"a"}); - } - - void unqcalls() { - unqm(new String[] {"a("}); - // TODOINVARR:: error: (argument.type.incompatible) - // :: error: (array.initializer.type.incompatible) - unqm(new @Regex String[] {"a("}); - // :: error: (argument.type.incompatible) - rem(new String[] {"a("}); - // :: error: (array.initializer.type.incompatible) - rem(new @Regex String[] {"a("}); - } - - // method argument context - - String[] retunqm(String[] sa) { - return sa; - } - - @Regex String[] retrem(@Regex String[] rsa) { - return rsa; - } - - @Regex String[] mixedm(String[] rsa) { - return null; - } - - void retunqcalls() { - @Regex String[] re = mixedm(new String[] {"a("}); - // TODOINVARR:: error: (argument.type.incompatible) - String[] u = retunqm(new String[] {"a"}); - // TODOINVARR:: error: (argument.type.incompatible) - re = mixedm(new String[2]); - } - - void lrem(List<@Regex String> p) {} - - void lunqm(List p) {} - - void listcalls() { - lunqm(Arrays.asList("alice", "bob", "carol")); - lrem(Arrays.asList("alice", "bob", "carol")); - lunqm(Arrays.asList("(alice", "bob", "carol")); - // :: error: (argument.type.incompatible) - lrem(Arrays.asList("(alice", "bob", "carol")); - } - - class ReTests { - ReTests(List<@Regex String> p) {} - - ReTests(List p, int i) {} - } - - void listctrs() { - new ReTests(Arrays.asList("alice", "bob", "carol"), 0); - new ReTests(Arrays.asList("alice", "bob", "carol")); - new ReTests(Arrays.asList("(alice", "bob", "carol"), 0); - // :: error: (argument.type.incompatible) - new ReTests(Arrays.asList("(alice", "bob", "carol")); - } - - String join(final String delimiter, final Collection objs) { - return delimiter; - } - - String s1 = join(" ", Arrays.asList("1", "2", "3")); - String s2 = "xxx" + join(" ", Arrays.asList("1", "2", "3")); - - class TV { - List> emptylist = Collections.emptyList(); - } + String[] sa = {"a"}; + String[] sa2 = {"a", "b"}; + public String[] sa3 = {"a", "b"}; + public static String[] sa4 = {"a", "b"}; + public final String[] sa5 = {"a", "b"}; + public static final String[] sa6 = {"a", "b"}; + final String[] sa7 = {"a", "b"}; + + // tested above: String[] sa = {"a"}; + @Regex String[] rsa = {"a"}; + String[] nrsa = {"(a"}; + // :: error: (array.initializer.type.incompatible) :: error: (assignment.type.incompatible) + @Regex String[] rsaerr = {"(a"}; + + List ls = Arrays.asList("alice", "bob", "carol"); + List<@Regex String> lrs = Arrays.asList("alice", "bob", "carol"); + List lnrs = Arrays.asList("(alice", "bob", "carol"); + // :: error: (assignment.type.incompatible) + List<@Regex String> lrserr = Arrays.asList("(alice", "bob", "carol"); + + void unqm(String[] sa) {} + + void rem(@Regex String[] rsa) {} + + void recalls() { + unqm(new String[] {"a"}); + // TODOINVARR:: error: (argument.type.incompatible) + unqm(new @Regex String[] {"a"}); + rem(new String[] {"a"}); + rem(new @Regex String[] {"a"}); + } + + void unqcalls() { + unqm(new String[] {"a("}); + // TODOINVARR:: error: (argument.type.incompatible) + // :: error: (array.initializer.type.incompatible) + unqm(new @Regex String[] {"a("}); + // :: error: (argument.type.incompatible) + rem(new String[] {"a("}); + // :: error: (array.initializer.type.incompatible) + rem(new @Regex String[] {"a("}); + } + + // method argument context + + String[] retunqm(String[] sa) { + return sa; + } + + @Regex String[] retrem(@Regex String[] rsa) { + return rsa; + } + + @Regex String[] mixedm(String[] rsa) { + return null; + } + + void retunqcalls() { + @Regex String[] re = mixedm(new String[] {"a("}); + // TODOINVARR:: error: (argument.type.incompatible) + String[] u = retunqm(new String[] {"a"}); + // TODOINVARR:: error: (argument.type.incompatible) + re = mixedm(new String[2]); + } + + void lrem(List<@Regex String> p) {} + + void lunqm(List p) {} + + void listcalls() { + lunqm(Arrays.asList("alice", "bob", "carol")); + lrem(Arrays.asList("alice", "bob", "carol")); + lunqm(Arrays.asList("(alice", "bob", "carol")); + // :: error: (argument.type.incompatible) + lrem(Arrays.asList("(alice", "bob", "carol")); + } + + class ReTests { + ReTests(List<@Regex String> p) {} + + ReTests(List p, int i) {} + } + + void listctrs() { + new ReTests(Arrays.asList("alice", "bob", "carol"), 0); + new ReTests(Arrays.asList("alice", "bob", "carol")); + new ReTests(Arrays.asList("(alice", "bob", "carol"), 0); + // :: error: (argument.type.incompatible) + new ReTests(Arrays.asList("(alice", "bob", "carol")); + } + + String join(final String delimiter, final Collection objs) { + return delimiter; + } + + String s1 = join(" ", Arrays.asList("1", "2", "3")); + String s2 = "xxx" + join(" ", Arrays.asList("1", "2", "3")); + + class TV { + List> emptylist = Collections.emptyList(); + } } diff --git a/checker/tests/regex/InvariantTypesAtm.java b/checker/tests/regex/InvariantTypesAtm.java index b5492eb4063..8d358c96253 100644 --- a/checker/tests/regex/InvariantTypesAtm.java +++ b/checker/tests/regex/InvariantTypesAtm.java @@ -1,14 +1,13 @@ -import org.checkerframework.framework.type.AnnotatedTypeMirror; - import java.util.Map; +import org.checkerframework.framework.type.AnnotatedTypeMirror; public class InvariantTypesAtm { - V mapGetHelper( - Map mappings) { - return null; - } + V mapGetHelper( + Map mappings) { + return null; + } - Map mappings; - AnnotatedTypeMirror found = mapGetHelper(mappings); + Map mappings; + AnnotatedTypeMirror found = mapGetHelper(mappings); } diff --git a/checker/tests/regex/Issue3267.java b/checker/tests/regex/Issue3267.java index 91f866aa11f..dff63c6abbf 100644 --- a/checker/tests/regex/Issue3267.java +++ b/checker/tests/regex/Issue3267.java @@ -1,18 +1,17 @@ // Test case for issue #3267: // https://github.com/typetools/checker-framework/issues/3267 -import org.checkerframework.checker.regex.util.RegexUtil; - import java.util.regex.Pattern; +import org.checkerframework.checker.regex.util.RegexUtil; public class Issue3267 { - void foo(String s) { - if (RegexUtil.isRegex(s)) { - } else { - } - if (true) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); - } + void foo(String s) { + if (RegexUtil.isRegex(s)) { + } else { + } + if (true) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); } + } } diff --git a/checker/tests/regex/Issue3281.java b/checker/tests/regex/Issue3281.java index 95feaa90bfb..215009a94c2 100644 --- a/checker/tests/regex/Issue3281.java +++ b/checker/tests/regex/Issue3281.java @@ -1,81 +1,80 @@ // Test case for Issue 3281: // https://github.com/typetools/checker-framework/issues/3281 +import java.util.regex.Pattern; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.checker.regex.util.RegexUtil; -import java.util.regex.Pattern; - public class Issue3281 { - @Regex String f = null; + @Regex String f = null; - public boolean b = false; + public boolean b = false; - void m1(String s) { - if (true) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); - } + void m1(String s) { + if (true) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); } + } - void m2(String s) { - RegexUtil.isRegex(s); - if (true) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); - } + void m2(String s) { + RegexUtil.isRegex(s); + if (true) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); } + } - void m2f(String s) { - RegexUtil.isRegex(s); - if (true) { - // :: error: (assignment.type.incompatible) - f = s; - } + void m2f(String s) { + RegexUtil.isRegex(s); + if (true) { + // :: error: (assignment.type.incompatible) + f = s; } + } - void m3(String s) { - if (RegexUtil.isRegex(s)) { - Pattern.compile(s); - } + void m3(String s) { + if (RegexUtil.isRegex(s)) { + Pattern.compile(s); } + } - void m4(String s, String s2) { - RegexUtil.isRegex(s); - if (RegexUtil.isRegex(s2)) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); - } + void m4(String s, String s2) { + RegexUtil.isRegex(s); + if (RegexUtil.isRegex(s2)) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); } + } - void m4f(String s, String s2) { - RegexUtil.isRegex(s); - if (RegexUtil.isRegex(s2)) { - // :: error: (assignment.type.incompatible) - f = s; - } + void m4f(String s, String s2) { + RegexUtil.isRegex(s); + if (RegexUtil.isRegex(s2)) { + // :: error: (assignment.type.incompatible) + f = s; } + } - void m5f(String s, String s2) { - RegexUtil.isRegex(s); - if (b) { - // :: error: (assignment.type.incompatible) - f = s; - } + void m5f(String s, String s2) { + RegexUtil.isRegex(s); + if (b) { + // :: error: (assignment.type.incompatible) + f = s; } + } - void foo(String s1, String s2) { - bar( - RegexUtil.isRegex(s1), - // :: error: (argument.type.incompatible) - Pattern.compile(s1)); - boolean b; - bar( - b = RegexUtil.isRegex(s2), - // :: error: (argument.type.incompatible) - Pattern.compile(s2)); - } + void foo(String s1, String s2) { + bar( + RegexUtil.isRegex(s1), + // :: error: (argument.type.incompatible) + Pattern.compile(s1)); + boolean b; + bar( + b = RegexUtil.isRegex(s2), + // :: error: (argument.type.incompatible) + Pattern.compile(s2)); + } - void bar(boolean b, Object o) {} + void bar(boolean b, Object o) {} } diff --git a/checker/tests/regex/Issue809.java b/checker/tests/regex/Issue809.java index 6ce471420fa..d064ecd4228 100644 --- a/checker/tests/regex/Issue809.java +++ b/checker/tests/regex/Issue809.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/809 public class Issue809> { - K[] array; + K[] array; - int index = 0; + int index = 0; - String m() { - return array[index] + "="; - } + String m() { + return array[index] + "="; + } } diff --git a/checker/tests/regex/LubRegex.java b/checker/tests/regex/LubRegex.java index b97214aa136..2e9dde3ff3a 100644 --- a/checker/tests/regex/LubRegex.java +++ b/checker/tests/regex/LubRegex.java @@ -2,65 +2,65 @@ public class LubRegex { - void test1(@Regex(4) String s4, boolean b) { - String s = null; - if (b) { - s = s4; - } - @Regex(4) String test = s; - - // :: error: (assignment.type.incompatible) - @Regex(5) String test2 = s; + void test1(@Regex(4) String s4, boolean b) { + String s = null; + if (b) { + s = s4; } + @Regex(4) String test = s; - void test2(@Regex(2) String s2, @Regex(4) String s4, boolean b) { - String s = s4; - if (b) { - s = s2; - } - @Regex(2) String test = s; + // :: error: (assignment.type.incompatible) + @Regex(5) String test2 = s; + } - // :: error: (assignment.type.incompatible) - @Regex(3) String test2 = s; + void test2(@Regex(2) String s2, @Regex(4) String s4, boolean b) { + String s = s4; + if (b) { + s = s2; } + @Regex(2) String test = s; - void test3(@Regex(6) String s6, boolean b) { - String s; - if (b) { - s = s6; - } else { - s = null; - } - @Regex(6) String test = s; + // :: error: (assignment.type.incompatible) + @Regex(3) String test2 = s; + } - // :: error: (assignment.type.incompatible) - @Regex(7) String test2 = s; + void test3(@Regex(6) String s6, boolean b) { + String s; + if (b) { + s = s6; + } else { + s = null; } + @Regex(6) String test = s; - void test4(@Regex(8) String s8, @Regex(9) String s9, boolean b) { - String s; - if (b) { - s = s8; - } else { - s = s9; - } - @Regex(8) String test = s; + // :: error: (assignment.type.incompatible) + @Regex(7) String test2 = s; + } - // :: error: (assignment.type.incompatible) - @Regex(9) String test2 = s; + void test4(@Regex(8) String s8, @Regex(9) String s9, boolean b) { + String s; + if (b) { + s = s8; + } else { + s = s9; } + @Regex(8) String test = s; - void test5(@Regex(10) String s10, @Regex(11) String s11, boolean b) { - String s; - if (b) { - s = s11; - } else { - s = s10; - return; - } - @Regex(11) String test = s; + // :: error: (assignment.type.incompatible) + @Regex(9) String test2 = s; + } - // :: error: (assignment.type.incompatible) - @Regex(12) String test2 = s; + void test5(@Regex(10) String s10, @Regex(11) String s11, boolean b) { + String s; + if (b) { + s = s11; + } else { + s = s10; + return; } + @Regex(11) String test = s; + + // :: error: (assignment.type.incompatible) + @Regex(12) String test2 = s; + } } diff --git a/checker/tests/regex/MatcherGroupCount.java b/checker/tests/regex/MatcherGroupCount.java index 11cabfe28db..f138e39cb9a 100644 --- a/checker/tests/regex/MatcherGroupCount.java +++ b/checker/tests/regex/MatcherGroupCount.java @@ -1,52 +1,48 @@ // Test case for Issue 291 // https://github.com/typetools/checker-framework/issues/291 -import org.checkerframework.checker.regex.util.RegexUtil; - import java.util.regex.*; +import org.checkerframework.checker.regex.util.RegexUtil; public class MatcherGroupCount { - public static void main(String[] args) { - String regex = args[0]; - String content = args[1]; + public static void main(String[] args) { + String regex = args[0]; + String content = args[1]; - if (!RegexUtil.isRegex(regex)) { - System.out.println( - "Error parsing regex \"" - + regex - + "\": " - + RegexUtil.regexException(regex).getMessage()); - System.exit(1); - } + if (!RegexUtil.isRegex(regex)) { + System.out.println( + "Error parsing regex \"" + regex + "\": " + RegexUtil.regexException(regex).getMessage()); + System.exit(1); + } - Pattern pat = Pattern.compile(regex); - Matcher mat = pat.matcher(content); + Pattern pat = Pattern.compile(regex); + Matcher mat = pat.matcher(content); - if (mat.matches()) { - if (mat.groupCount() > 0) { - System.out.println("Group: " + mat.group(1)); - } else { - System.out.println("No group found!"); - } - if (mat.groupCount() >= 2) { - System.out.println("Group: " + mat.group(2)); - } - if (mat.groupCount() >= 2) { - // :: error: (group.count.invalid) - System.out.println("Group: " + mat.group(3)); - } - if (2 < mat.groupCount()) { - System.out.println("Group: " + mat.group(3)); - } - if (!(mat.groupCount() > 4)) { - System.out.println("Group: " + mat.group(0)); - } else { - System.out.println("Group: " + mat.group(5)); - // :: error: (group.count.invalid) - System.out.println("Group: " + mat.group(6)); - } - } else { - System.out.println("No match!"); - } + if (mat.matches()) { + if (mat.groupCount() > 0) { + System.out.println("Group: " + mat.group(1)); + } else { + System.out.println("No group found!"); + } + if (mat.groupCount() >= 2) { + System.out.println("Group: " + mat.group(2)); + } + if (mat.groupCount() >= 2) { + // :: error: (group.count.invalid) + System.out.println("Group: " + mat.group(3)); + } + if (2 < mat.groupCount()) { + System.out.println("Group: " + mat.group(3)); + } + if (!(mat.groupCount() > 4)) { + System.out.println("Group: " + mat.group(0)); + } else { + System.out.println("Group: " + mat.group(5)); + // :: error: (group.count.invalid) + System.out.println("Group: " + mat.group(6)); + } + } else { + System.out.println("No match!"); } + } } diff --git a/checker/tests/regex/MyMatchResult.java b/checker/tests/regex/MyMatchResult.java index 670b9ccabd4..e08362735b1 100644 --- a/checker/tests/regex/MyMatchResult.java +++ b/checker/tests/regex/MyMatchResult.java @@ -5,45 +5,45 @@ @SuppressWarnings("regex") public class MyMatchResult implements MatchResult { - @Override - public int start() { - group(0); - group(); - end(); - end(1); - groupCount(); - start(); - start(19); - return 0; - } - - @Override - public int start(int group) { - return 0; - } - - @Override - public int end() { - return 0; - } - - @Override - public int end(int group) { - return 0; - } - - @Override - public String group() { - return null; - } - - @Override - public String group(int group) { - return null; - } - - @Override - public int groupCount() { - return 0; - } + @Override + public int start() { + group(0); + group(); + end(); + end(1); + groupCount(); + start(); + start(19); + return 0; + } + + @Override + public int start(int group) { + return 0; + } + + @Override + public int end() { + return 0; + } + + @Override + public int end(int group) { + return 0; + } + + @Override + public String group() { + return null; + } + + @Override + public String group(int group) { + return null; + } + + @Override + public int groupCount() { + return 0; + } } diff --git a/checker/tests/regex/Nested.java b/checker/tests/regex/Nested.java index add8452bc3f..fa868683fce 100644 --- a/checker/tests/regex/Nested.java +++ b/checker/tests/regex/Nested.java @@ -3,15 +3,15 @@ // TODO: @Regex is not allowed on arbitrary types. Find a better test case. public class Nested { - // :: error: (anno.on.irrelevant) - OuterI.@Regex InnerA fa = new OuterI.@Regex InnerA() {}; + // :: error: (anno.on.irrelevant) + OuterI.@Regex InnerA fa = new OuterI.@Regex InnerA() {}; - // :: error: (anno.on.irrelevant) - OuterI.@Regex InnerB fb = new OuterI.@Regex InnerB() {}; + // :: error: (anno.on.irrelevant) + OuterI.@Regex InnerB fb = new OuterI.@Regex InnerB() {}; } class OuterI { - static class InnerA {} + static class InnerA {} - static class InnerB {} + static class InnerB {} } diff --git a/checker/tests/regex/PartialRegex.java b/checker/tests/regex/PartialRegex.java index abfee658f46..d11f758e8eb 100644 --- a/checker/tests/regex/PartialRegex.java +++ b/checker/tests/regex/PartialRegex.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.regex.qual.Regex; public class PartialRegex { - void m(@Regex String re, String non) { - String l = "("; - String r = ")"; + void m(@Regex String re, String non) { + String l = "("; + String r = ")"; - @Regex String test1 = l + r; - @Regex String test2 = l + re + r; - @Regex String test3 = l + r + l + r; - @Regex String test4 = l + l + r + r; - @Regex String test5 = l + l + re + r + r; + @Regex String test1 = l + r; + @Regex String test2 = l + re + r; + @Regex String test3 = l + r + l + r; + @Regex String test4 = l + l + r + r; + @Regex String test5 = l + l + re + r + r; - // :: error: (assignment.type.incompatible) - @Regex String fail1 = r + l; - // :: error: (assignment.type.incompatible) - @Regex String fail2 = r + non + l; - // :: error: (assignment.type.incompatible) - @Regex String fail3 = l + r + r; - } + // :: error: (assignment.type.incompatible) + @Regex String fail1 = r + l; + // :: error: (assignment.type.incompatible) + @Regex String fail2 = r + non + l; + // :: error: (assignment.type.incompatible) + @Regex String fail3 = l + r + r; + } } diff --git a/checker/tests/regex/RawTypeTest.java b/checker/tests/regex/RawTypeTest.java index 37ed0cccc73..b692173c335 100644 --- a/checker/tests/regex/RawTypeTest.java +++ b/checker/tests/regex/RawTypeTest.java @@ -1,104 +1,103 @@ -import org.checkerframework.checker.regex.qual.*; - import java.lang.ref.WeakReference; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.List; +import org.checkerframework.checker.regex.qual.*; public class RawTypeTest { - public void m1(Class c) { - Class x = c.asSubclass(I2.class); - - new WeakReference(x); - new WeakReference(x); - new WeakReference>(x); - - new WeakReference(c.asSubclass(I2.class)); - new WeakReference(c.asSubclass(I2.class)); - new WeakReference>(c.asSubclass(I2.class)); + public void m1(Class c) { + Class x = c.asSubclass(I2.class); + + new WeakReference(x); + new WeakReference(x); + new WeakReference>(x); + + new WeakReference(c.asSubclass(I2.class)); + new WeakReference(c.asSubclass(I2.class)); + new WeakReference>(c.asSubclass(I2.class)); + } + + /* It would be desirable to optionally check the following code without + * warnings. See issue 119: + * + * https://github.com/typetools/checker-framework/issues/119 + * + class Raw { + public void m2(Class c) {} + + public void m3(Class c) { + m2(c); + } + + public void m4() { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return null; + }}); + } + + public void m5(List list, C4 c) { + list.add(c); + } + + public void m6(List list, long l) { + list.add(l); + } + }*/ + + class NonRaw { + public void m2(Class c) {} + + public void m3(Class c) { + m2(c); } - /* It would be desirable to optionally check the following code without - * warnings. See issue 119: - * - * https://github.com/typetools/checker-framework/issues/119 - * - class Raw { - public void m2(Class c) {} - - public void m3(Class c) { - m2(c); - } - - public void m4() { - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - return null; - }}); - } - - public void m5(List list, C4 c) { - list.add(c); - } - - public void m6(List list, long l) { - list.add(l); - } - }*/ - - class NonRaw { - public void m2(Class c) {} - - public void m3(Class c) { - m2(c); - } - - @SuppressWarnings("removal") // AccessController is deprecated for removal in Java 17 - public void m4() { - AccessController.doPrivileged( - new PrivilegedAction() { - public Object run() { - return null; - } - }); - } - - public void m5(List list, C4 c) { - list.add(c); - } - - public void m6(List list, long l) { - list.add(l); - } + @SuppressWarnings("removal") // AccessController is deprecated for removal in Java 17 + public void m4() { + AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return null; + } + }); } - class MyList { - X f; + public void m5(List list, C4 c) { + list.add(c); } - interface I1 { - public void m(MyList l); + public void m6(List list, long l) { + list.add(l); } + } - class C1 implements I1 { - public void m(MyList par) { - @Regex String xxx = par.f; - } - } + class MyList { + X f; + } - interface I2 { - public void m(MyList<@Regex String> l); - } + interface I1 { + public void m(MyList l); + } - class C2 implements I2 { - public void m(MyList<@Regex String> l) {} + class C1 implements I1 { + public void m(MyList par) { + @Regex String xxx = par.f; } + } - class C3 implements I2 { - // :: error: (override.param.invalid) :: error: (type.argument.type.incompatible) - public void m(MyList l) {} - } + interface I2 { + public void m(MyList<@Regex String> l); + } + + class C2 implements I2 { + public void m(MyList<@Regex String> l) {} + } + + class C3 implements I2 { + // :: error: (override.param.invalid) :: error: (type.argument.type.incompatible) + public void m(MyList l) {} + } - class C4 {} + class C4 {} } diff --git a/checker/tests/regex/RegexUtilClient.java b/checker/tests/regex/RegexUtilClient.java index 9ed183bb1e4..ebca45f73ea 100644 --- a/checker/tests/regex/RegexUtilClient.java +++ b/checker/tests/regex/RegexUtilClient.java @@ -2,96 +2,96 @@ import org.checkerframework.framework.qual.EnsuresQualifierIf; public class RegexUtilClient { - void fullyQualifiedRegexUtil(String s) { - if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s, 2)) { - @Regex(2) String s2 = s; - } - @Regex(2) String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s, 2); + void fullyQualifiedRegexUtil(String s) { + if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s, 2)) { + @Regex(2) String s2 = s; } + @Regex(2) String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s, 2); + } - void unqualifiedRegexUtil(String s) { - if (RegexUtil.isRegex(s, 2)) { - @Regex(2) String s2 = s; - } - @Regex(2) String s2 = RegexUtil.asRegex(s, 2); + void unqualifiedRegexUtil(String s) { + if (RegexUtil.isRegex(s, 2)) { + @Regex(2) String s2 = s; } + @Regex(2) String s2 = RegexUtil.asRegex(s, 2); + } - void fullyQualifiedRegexUtilNoParamsArg(String s) { - if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s)) { - @Regex String s2 = s; - @Regex(0) String s3 = s; - } - @Regex String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); - @Regex(0) String s3 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); + void fullyQualifiedRegexUtilNoParamsArg(String s) { + if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s)) { + @Regex String s2 = s; + @Regex(0) String s3 = s; } + @Regex String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); + @Regex(0) String s3 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); + } - void unqualifiedRegexUtilNoParamsArg(String s) { - if (RegexUtil.isRegex(s)) { - @Regex String s2 = s; - @Regex(0) String s3 = s; - } - @Regex String s2 = RegexUtil.asRegex(s, 2); - @Regex(0) String s3 = RegexUtil.asRegex(s, 2); + void unqualifiedRegexUtilNoParamsArg(String s) { + if (RegexUtil.isRegex(s)) { + @Regex String s2 = s; + @Regex(0) String s3 = s; } + @Regex String s2 = RegexUtil.asRegex(s, 2); + @Regex(0) String s3 = RegexUtil.asRegex(s, 2); + } - void illegalName(String s) { - if (IllegalName.isRegex(s, 2)) { - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = s; - } - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = IllegalName.asRegex(s, 2); + void illegalName(String s) { + if (IllegalName.isRegex(s, 2)) { + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = s; } + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = IllegalName.asRegex(s, 2); + } - void illegalNameRegexUtil(String s) { - if (IllegalNameRegexUtil.isRegex(s, 2)) { - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = s; - } - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = IllegalNameRegexUtil.asRegex(s, 2); + void illegalNameRegexUtil(String s) { + if (IllegalNameRegexUtil.isRegex(s, 2)) { + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = s; } + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = IllegalNameRegexUtil.asRegex(s, 2); + } } // A dummy RegexUtil class to make sure RegexUtil in no package works. class RegexUtil { - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(final String s, int n) { - return false; - } + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(final String s, int n) { + return false; + } - public static @Regex String asRegex(String s, int n) { - return null; - } + public static @Regex String asRegex(String s, int n) { + return null; + } - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(final String s) { - return false; - } + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(final String s) { + return false; + } - public static @Regex String asRegex(String s) { - return null; - } + public static @Regex String asRegex(String s) { + return null; + } } // These methods shouldn't work. class IllegalName { - public static boolean isRegex(String s, int n) { - return false; - } + public static boolean isRegex(String s, int n) { + return false; + } - public static @Regex String asRegex(String s, int n) { - return null; - } + public static @Regex String asRegex(String s, int n) { + return null; + } } // These methods shouldn't work. class IllegalNameRegexUtil { - public static boolean isRegex(String s, int n) { - return false; - } + public static boolean isRegex(String s, int n) { + return false; + } - public static @Regex String asRegex(String s, int n) { - return null; - } + public static @Regex String asRegex(String s, int n) { + return null; + } } diff --git a/checker/tests/regex/SimpleRegex.java b/checker/tests/regex/SimpleRegex.java index 989e2b97093..f0a073f2dc8 100644 --- a/checker/tests/regex/SimpleRegex.java +++ b/checker/tests/regex/SimpleRegex.java @@ -1,142 +1,141 @@ -import org.checkerframework.checker.regex.qual.Regex; - import java.util.regex.Pattern; +import org.checkerframework.checker.regex.qual.Regex; public class SimpleRegex { - void regString() { - String s1 = "validRegex"; - String s2 = "(InvalidRegex"; - } - - void validRegString() { - @Regex String s1 = "validRegex"; - // :: error: (assignment.type.incompatible) - @Regex String s2 = "(InvalidRegex"; // error - } - - void compileCall() { - Pattern.compile("test.*[^123]$"); - // :: error: (argument.type.incompatible) - Pattern.compile("$test.*[^123"); // error - } - - void requireValidReg(@Regex String reg, String nonReg) { - Pattern.compile(reg); - // :: error: (argument.type.incompatible) - Pattern.compile(nonReg); // error - } - - void testAddition(@Regex String reg, String nonReg) { - @Regex String s1 = reg; - @Regex String s2 = reg + "d.*sf"; - @Regex String s3 = reg + reg; - - // :: error: (assignment.type.incompatible) - @Regex String n1 = nonReg; // error - // :: error: (assignment.type.incompatible) - @Regex String n2 = reg + "(df"; // error - // :: error: (assignment.type.incompatible) - @Regex String n3 = reg + nonReg; // error - - // :: error: (assignment.type.incompatible) - @Regex String o1 = nonReg; // error - // :: error: (assignment.type.incompatible) - @Regex String o2 = nonReg + "sdf"; // error - // :: error: (assignment.type.incompatible) - @Regex String o3 = nonReg + reg; // error - } - - @Regex String regex = "()"; - String nonRegex = "()"; - - void testCompoundConcatenation() { - takesRegex(regex); - // :: error: (compound.assignment.type.incompatible) - regex += ")"; // error - takesRegex(regex); - - nonRegex = "()"; - // nonRegex is refined by flow to be a regular expression - takesRegex(nonRegex); - nonRegex += ")"; - // :: error: (argument.type.incompatible) - takesRegex(nonRegex); // error - } - - void takesRegex(@Regex String s) {} - - void testChar() { - @Regex char c1 = 'c'; - @Regex Character c2 = 'c'; - - // :: error: (assignment.type.incompatible) - @Regex char c3 = '('; // error - // :: error: (assignment.type.incompatible) - @Regex Character c4 = '('; // error - } - - void testCharConcatenation() { - @Regex String s1 = "rege" + 'x'; - @Regex String s2 = 'r' + "egex"; - - // :: error: (assignment.type.incompatible) - @Regex String s4 = "rege" + '('; // error - // :: error: (assignment.type.incompatible) - @Regex String s5 = "reg(" + 'x'; // error - // :: error: (assignment.type.incompatible) - @Regex String s6 = '(' + "egex"; // error - // :: error: (assignment.type.incompatible) - @Regex String s7 = 'r' + "ege("; // error - } - - void testPatternLiteral() { - Pattern.compile("non(", Pattern.LITERAL); - Pattern.compile(foo("regex"), Pattern.LITERAL); - - // :: error: (argument.type.incompatible) - Pattern.compile(foo("regex("), Pattern.LITERAL); // error - // :: error: (argument.type.incompatible) - Pattern.compile("non("); // error - // :: error: (argument.type.incompatible) - Pattern.compile(foo("regex")); // error - // :: error: (argument.type.incompatible) - Pattern.compile("non(", Pattern.CASE_INSENSITIVE); // error - } - - public static String foo(@Regex String s) { - return "non(("; - } - - // TODO: This is not supported until the framework can read explicit - // annotations from arrays. - // void testArrayAllowedTypes() { - // @Regex char[] ca1; - // char @Regex [] ca2; - // @Regex char @Regex [] ca3; - // @Regex String[] s1; - // - // // :: error: (type.invalid) - // @Regex double[] da1; // error - // // :: error: (type.invalid) - // double @Regex [] da2; // error - // // :: error: (type.invalid) - // @Regex double @Regex [] da3; // error - // // :: error: (type.invalid) - // String @Regex [] s2; // error - // } - - // TODO: This is not supported until the Regex Checker supports flow - // sensitivity. See the associated comment at - // org.checkerframework.checker/regex/RegexAnnotatedTypeFactory.java:visitNewArray - // void testCharArrays(char c, @Regex char r) { - // char @Regex [] c1 = {'r', 'e', 'g', 'e', 'x'}; - // char @Regex [] c2 = {'(', 'r', 'e', 'g', 'e', 'x', ')', '.', '*'}; - // char @Regex [] c3 = {r, 'e', 'g', 'e', 'x'}; - // - // // :: error: (assignment.type.incompatible) - // char @Regex [] c4 = {'(', 'r', 'e', 'g', 'e', 'x'}; // error - // // :: error: (assignment.type.incompatible) - // char @Regex [] c5 = {c, '.', '*'}; // error - // } + void regString() { + String s1 = "validRegex"; + String s2 = "(InvalidRegex"; + } + + void validRegString() { + @Regex String s1 = "validRegex"; + // :: error: (assignment.type.incompatible) + @Regex String s2 = "(InvalidRegex"; // error + } + + void compileCall() { + Pattern.compile("test.*[^123]$"); + // :: error: (argument.type.incompatible) + Pattern.compile("$test.*[^123"); // error + } + + void requireValidReg(@Regex String reg, String nonReg) { + Pattern.compile(reg); + // :: error: (argument.type.incompatible) + Pattern.compile(nonReg); // error + } + + void testAddition(@Regex String reg, String nonReg) { + @Regex String s1 = reg; + @Regex String s2 = reg + "d.*sf"; + @Regex String s3 = reg + reg; + + // :: error: (assignment.type.incompatible) + @Regex String n1 = nonReg; // error + // :: error: (assignment.type.incompatible) + @Regex String n2 = reg + "(df"; // error + // :: error: (assignment.type.incompatible) + @Regex String n3 = reg + nonReg; // error + + // :: error: (assignment.type.incompatible) + @Regex String o1 = nonReg; // error + // :: error: (assignment.type.incompatible) + @Regex String o2 = nonReg + "sdf"; // error + // :: error: (assignment.type.incompatible) + @Regex String o3 = nonReg + reg; // error + } + + @Regex String regex = "()"; + String nonRegex = "()"; + + void testCompoundConcatenation() { + takesRegex(regex); + // :: error: (compound.assignment.type.incompatible) + regex += ")"; // error + takesRegex(regex); + + nonRegex = "()"; + // nonRegex is refined by flow to be a regular expression + takesRegex(nonRegex); + nonRegex += ")"; + // :: error: (argument.type.incompatible) + takesRegex(nonRegex); // error + } + + void takesRegex(@Regex String s) {} + + void testChar() { + @Regex char c1 = 'c'; + @Regex Character c2 = 'c'; + + // :: error: (assignment.type.incompatible) + @Regex char c3 = '('; // error + // :: error: (assignment.type.incompatible) + @Regex Character c4 = '('; // error + } + + void testCharConcatenation() { + @Regex String s1 = "rege" + 'x'; + @Regex String s2 = 'r' + "egex"; + + // :: error: (assignment.type.incompatible) + @Regex String s4 = "rege" + '('; // error + // :: error: (assignment.type.incompatible) + @Regex String s5 = "reg(" + 'x'; // error + // :: error: (assignment.type.incompatible) + @Regex String s6 = '(' + "egex"; // error + // :: error: (assignment.type.incompatible) + @Regex String s7 = 'r' + "ege("; // error + } + + void testPatternLiteral() { + Pattern.compile("non(", Pattern.LITERAL); + Pattern.compile(foo("regex"), Pattern.LITERAL); + + // :: error: (argument.type.incompatible) + Pattern.compile(foo("regex("), Pattern.LITERAL); // error + // :: error: (argument.type.incompatible) + Pattern.compile("non("); // error + // :: error: (argument.type.incompatible) + Pattern.compile(foo("regex")); // error + // :: error: (argument.type.incompatible) + Pattern.compile("non(", Pattern.CASE_INSENSITIVE); // error + } + + public static String foo(@Regex String s) { + return "non(("; + } + + // TODO: This is not supported until the framework can read explicit + // annotations from arrays. + // void testArrayAllowedTypes() { + // @Regex char[] ca1; + // char @Regex [] ca2; + // @Regex char @Regex [] ca3; + // @Regex String[] s1; + // + // // :: error: (type.invalid) + // @Regex double[] da1; // error + // // :: error: (type.invalid) + // double @Regex [] da2; // error + // // :: error: (type.invalid) + // @Regex double @Regex [] da3; // error + // // :: error: (type.invalid) + // String @Regex [] s2; // error + // } + + // TODO: This is not supported until the Regex Checker supports flow + // sensitivity. See the associated comment at + // org.checkerframework.checker/regex/RegexAnnotatedTypeFactory.java:visitNewArray + // void testCharArrays(char c, @Regex char r) { + // char @Regex [] c1 = {'r', 'e', 'g', 'e', 'x'}; + // char @Regex [] c2 = {'(', 'r', 'e', 'g', 'e', 'x', ')', '.', '*'}; + // char @Regex [] c3 = {r, 'e', 'g', 'e', 'x'}; + // + // // :: error: (assignment.type.incompatible) + // char @Regex [] c4 = {'(', 'r', 'e', 'g', 'e', 'x'}; // error + // // :: error: (assignment.type.incompatible) + // char @Regex [] c5 = {c, '.', '*'}; // error + // } } diff --git a/checker/tests/regex/TestIsRegex.java b/checker/tests/regex/TestIsRegex.java index 71b27d195f7..b07f51d8275 100644 --- a/checker/tests/regex/TestIsRegex.java +++ b/checker/tests/regex/TestIsRegex.java @@ -1,132 +1,125 @@ -import org.checkerframework.checker.regex.qual.*; -import org.checkerframework.checker.regex.util.RegexUtil; - import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.regex.qual.*; +import org.checkerframework.checker.regex.util.RegexUtil; public class TestIsRegex { - void test1(String str1) throws Exception { - if (!RegexUtil.isRegex(str1)) { - throw new Exception(); - } - Pattern.compile(str1); + void test1(String str1) throws Exception { + if (!RegexUtil.isRegex(str1)) { + throw new Exception(); } + Pattern.compile(str1); + } - void test2(String str2) throws Exception { - if (!RegexUtil.isRegex(str2)) { - // :: error: (argument.type.incompatible) - Pattern.compile(str2); - } + void test2(String str2) throws Exception { + if (!RegexUtil.isRegex(str2)) { + // :: error: (argument.type.incompatible) + Pattern.compile(str2); } + } - void test3(String str3) throws Exception { - if (RegexUtil.isRegex(str3)) { - Pattern.compile(str3); - } else { - throw new Exception(); - } + void test3(String str3) throws Exception { + if (RegexUtil.isRegex(str3)) { + Pattern.compile(str3); + } else { + throw new Exception(); } - - void test4(String str4) throws Exception { - if (RegexUtil.isRegex(str4)) { - Pattern.compile(str4); - } else { - // :: error: (argument.type.incompatible) - Pattern.compile(str4); - } + } + + void test4(String str4) throws Exception { + if (RegexUtil.isRegex(str4)) { + Pattern.compile(str4); + } else { + // :: error: (argument.type.incompatible) + Pattern.compile(str4); } + } - void test5(String str5) throws Exception { - if (!RegexUtil.isRegex(str5, 3)) { - throw new Exception(); - } - Pattern.compile(str5).matcher("test").group(3); + void test5(String str5) throws Exception { + if (!RegexUtil.isRegex(str5, 3)) { + throw new Exception(); } - - void test6(String str6) throws Exception { - if (RegexUtil.isRegex(str6, 4)) { - Pattern.compile(str6).matcher("4kdfj").group(4); - } else { - // :: error: (argument.type.incompatible) - Pattern.compile(str6); - } + Pattern.compile(str5).matcher("test").group(3); + } + + void test6(String str6) throws Exception { + if (RegexUtil.isRegex(str6, 4)) { + Pattern.compile(str6).matcher("4kdfj").group(4); + } else { + // :: error: (argument.type.incompatible) + Pattern.compile(str6); } + } - void test7(String str7) throws Exception { - if (RegexUtil.isRegex(str7, 5)) { - // :: error: (group.count.invalid) - Pattern.compile(str7).matcher("4kdfj").group(6); - } + void test7(String str7) throws Exception { + if (RegexUtil.isRegex(str7, 5)) { + // :: error: (group.count.invalid) + Pattern.compile(str7).matcher("4kdfj").group(6); } - - @Regex Pattern test8(String input) { - String datePattern = null; - - if (input != null) { - datePattern = "regexkdafj"; - if (!RegexUtil.isRegex(datePattern, 1)) { - throw new Error( - "error parsing regex " - + datePattern - + ": " - + RegexUtil.regexError(datePattern)); - } - return Pattern.compile(datePattern); - } - @Regex(1) String dp = datePattern; - - if (input != null) { // just some test... - Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; - return pattern; - } else { - Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; - return pattern; - } + } + + @Regex Pattern test8(String input) { + String datePattern = null; + + if (input != null) { + datePattern = "regexkdafj"; + if (!RegexUtil.isRegex(datePattern, 1)) { + throw new Error( + "error parsing regex " + datePattern + ": " + RegexUtil.regexError(datePattern)); + } + return Pattern.compile(datePattern); } - - @Regex(1) Pattern test9(String input) { - String datePattern = null; - - if (input != null) { - datePattern = "regexkdafj"; - if (!RegexUtil.isRegex(datePattern, 1)) { - throw new Error( - "error parsing regex " - + datePattern - + ": " - + RegexUtil.regexError(datePattern)); - } - return Pattern.compile(datePattern); - } - @Regex(1) String dp = datePattern; - - if (input != null) { // just some test... - Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; - return pattern; - } else { - Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; - return pattern; - } + @Regex(1) String dp = datePattern; + + if (input != null) { // just some test... + Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; + return pattern; + } else { + Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; + return pattern; } - - void test10(String s) throws Exception { - if (!RegexUtil.isRegex(s, 2)) { - throw new Exception(); - } - Pattern p = Pattern.compile(s); - Matcher m = p.matcher("abc"); - String g = m.group(1); + } + + @Regex(1) Pattern test9(String input) { + String datePattern = null; + + if (input != null) { + datePattern = "regexkdafj"; + if (!RegexUtil.isRegex(datePattern, 1)) { + throw new Error( + "error parsing regex " + datePattern + ": " + RegexUtil.regexError(datePattern)); + } + return Pattern.compile(datePattern); } - - void test11(String s) throws Exception { - @Regex(2) String l1 = RegexUtil.asRegex(s, 2); - @Regex(1) String l2 = RegexUtil.asRegex(s, 2); - @Regex String l3 = RegexUtil.asRegex(s, 2); - // :: error: (assignment.type.incompatible) - @Regex(3) String l4 = RegexUtil.asRegex(s, 2); + @Regex(1) String dp = datePattern; + + if (input != null) { // just some test... + Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; + return pattern; + } else { + Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; + return pattern; } + } - @Regex(2) String test12(String s, boolean b) throws Exception { - return b ? null : RegexUtil.asRegex(s, 2); + void test10(String s) throws Exception { + if (!RegexUtil.isRegex(s, 2)) { + throw new Exception(); } + Pattern p = Pattern.compile(s); + Matcher m = p.matcher("abc"); + String g = m.group(1); + } + + void test11(String s) throws Exception { + @Regex(2) String l1 = RegexUtil.asRegex(s, 2); + @Regex(1) String l2 = RegexUtil.asRegex(s, 2); + @Regex String l3 = RegexUtil.asRegex(s, 2); + // :: error: (assignment.type.incompatible) + @Regex(3) String l4 = RegexUtil.asRegex(s, 2); + } + + @Regex(2) String test12(String s, boolean b) throws Exception { + return b ? null : RegexUtil.asRegex(s, 2); + } } diff --git a/checker/tests/regex/TestRegex.java b/checker/tests/regex/TestRegex.java index f932b12e7a7..b5a1b459281 100644 --- a/checker/tests/regex/TestRegex.java +++ b/checker/tests/regex/TestRegex.java @@ -3,19 +3,19 @@ // test-case for issue 128 public class TestRegex { - public void Concatenation2() { - @Regex String a = "a"; - // :: error: (compound.assignment.type.incompatible) - a += "("; - } + public void Concatenation2() { + @Regex String a = "a"; + // :: error: (compound.assignment.type.incompatible) + a += "("; + } } // test-case for issue 148 class Search { - public static void main(String[] args) { - if (!org.checkerframework.checker.regex.util.RegexUtil.isRegex(args[0], 4)) { - return; - } - @Regex(4) String regex = args[0]; + public static void main(String[] args) { + if (!org.checkerframework.checker.regex.util.RegexUtil.isRegex(args[0], 4)) { + return; } + @Regex(4) String regex = args[0]; + } } diff --git a/checker/tests/regex/TypeParamSubtype.java b/checker/tests/regex/TypeParamSubtype.java index 372ba987a9b..2eeff3937d8 100644 --- a/checker/tests/regex/TypeParamSubtype.java +++ b/checker/tests/regex/TypeParamSubtype.java @@ -1,24 +1,23 @@ -import org.checkerframework.checker.regex.qual.Regex; - import java.util.Collection; +import org.checkerframework.checker.regex.qual.Regex; public class TypeParamSubtype { - // These are legal because null has type @Regex String - // void nullRegexSubtype(Collection col) { - // // :: error: (argument.type.incompatible) - // col.add(null); - // } - // - // void nullSimpleSubtype(Collection col) { - // // :: error: (argument.type.incompatible) - // col.add(null); - // } + // These are legal because null has type @Regex String + // void nullRegexSubtype(Collection col) { + // // :: error: (argument.type.incompatible) + // col.add(null); + // } + // + // void nullSimpleSubtype(Collection col) { + // // :: error: (argument.type.incompatible) + // col.add(null); + // } - void nullRegexSubtype(Collection col, U u) { - col.add(u); - } + void nullRegexSubtype(Collection col, U u) { + col.add(u); + } - void nullSimpleSubtype(Collection col, U u) { - col.add(u); - } + void nullSimpleSubtype(Collection col, U u) { + col.add(u); + } } diff --git a/checker/tests/regex/TypeVarMemberSelect.java b/checker/tests/regex/TypeVarMemberSelect.java index 38f58acafac..207a71ce99d 100644 --- a/checker/tests/regex/TypeVarMemberSelect.java +++ b/checker/tests/regex/TypeVarMemberSelect.java @@ -1,19 +1,19 @@ import org.checkerframework.checker.regex.qual.*; class Box { - @Regex(1) T t1; + @Regex(1) T t1; - T t2; + T t2; } class TypeVarMemberSelect> { - void test(V v) { - // :: error: (assignment.type.incompatible) - @Regex(2) String local1 = v.t1; + void test(V v) { + // :: error: (assignment.type.incompatible) + @Regex(2) String local1 = v.t1; - // Previously the type of the right hand side would have been T which is wrong. This test - // was added to make sure we call viewpoint adaptation when type variables are the receiver. - @Regex(2) String local2 = v.t2; - } + // Previously the type of the right hand side would have been T which is wrong. This test + // was added to make sure we call viewpoint adaptation when type variables are the receiver. + @Regex(2) String local2 = v.t2; + } } diff --git a/checker/tests/regex/WildcardInvoke.java b/checker/tests/regex/WildcardInvoke.java index de0135bb944..646bb0ff8da 100644 --- a/checker/tests/regex/WildcardInvoke.java +++ b/checker/tests/regex/WildcardInvoke.java @@ -1,10 +1,10 @@ public class WildcardInvoke { - class Demo { - void call(T p) {} - } + class Demo { + void call(T p) {} + } - void m() { - Demo d = null; - d.call(null); - } + void m() { + Demo d = null; + d.call(null); + } } diff --git a/checker/tests/regex_poly/PolyRegexTests.java b/checker/tests/regex_poly/PolyRegexTests.java index fb3b00fc914..45c46fdeff2 100644 --- a/checker/tests/regex_poly/PolyRegexTests.java +++ b/checker/tests/regex_poly/PolyRegexTests.java @@ -3,80 +3,80 @@ public class PolyRegexTests { - @Regex(0) String field1 = "abc".toString(); - - public static @PolyRegex String method(@PolyRegex String s) { - return s; - } - - public void testRegex(@Regex String str) { - @Regex String s = method(str); - } - - public void testNonRegex(String str) { - // :: error: (assignment.type.incompatible) - @Regex String s = method(str); // error - } - - public void testInternRegex(@Regex String str) { - @Regex String s = str.intern(); - } - - public void testInternNonRegex(String str) { - // :: error: (assignment.type.incompatible) - @Regex String s = str.intern(); // error - } - - public void testToStringRegex(@Regex String str) { - @Regex String s = str.toString(); - } - - public void testToStringNonRegex(String str) { - // :: error: (assignment.type.incompatible) - @Regex String s = str.toString(); // error - } - - public @PolyRegex String testPolyRegexConcat(@PolyRegex String s1, @PolyRegex String s2) { - return s1 + s2; - } - - public void testPolyRegexConcatErrors(@PolyRegex String polyReg, String nonPolyReg) { - // :: error: (assignment.type.incompatible) - @PolyRegex String test1 = polyReg + nonPolyReg; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test2 = nonPolyReg + polyReg; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test3 = nonPolyReg + nonPolyReg; // error - } - - public void testRegexPolyRegexConcat(@PolyRegex String polyReg, @Regex String reg) { - @PolyRegex String test1 = polyReg + reg; - @PolyRegex String test2 = reg + polyReg; - } - - public void testRegexPolyRegexConcatErrors( - @PolyRegex String polyReg, @Regex String reg, String str) { - // :: error: (assignment.type.incompatible) - @PolyRegex String test1 = polyReg + str; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test2 = str + polyReg; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test3 = reg + str; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test4 = str + reg; // error - - // :: error: (assignment.type.incompatible) - @PolyRegex String test5 = str + str; // error + @Regex(0) String field1 = "abc".toString(); + + public static @PolyRegex String method(@PolyRegex String s) { + return s; + } + + public void testRegex(@Regex String str) { + @Regex String s = method(str); + } + + public void testNonRegex(String str) { + // :: error: (assignment.type.incompatible) + @Regex String s = method(str); // error + } + + public void testInternRegex(@Regex String str) { + @Regex String s = str.intern(); + } + + public void testInternNonRegex(String str) { + // :: error: (assignment.type.incompatible) + @Regex String s = str.intern(); // error + } + + public void testToStringRegex(@Regex String str) { + @Regex String s = str.toString(); + } + + public void testToStringNonRegex(String str) { + // :: error: (assignment.type.incompatible) + @Regex String s = str.toString(); // error + } + + public @PolyRegex String testPolyRegexConcat(@PolyRegex String s1, @PolyRegex String s2) { + return s1 + s2; + } + + public void testPolyRegexConcatErrors(@PolyRegex String polyReg, String nonPolyReg) { + // :: error: (assignment.type.incompatible) + @PolyRegex String test1 = polyReg + nonPolyReg; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test2 = nonPolyReg + polyReg; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test3 = nonPolyReg + nonPolyReg; // error + } + + public void testRegexPolyRegexConcat(@PolyRegex String polyReg, @Regex String reg) { + @PolyRegex String test1 = polyReg + reg; + @PolyRegex String test2 = reg + polyReg; + } + + public void testRegexPolyRegexConcatErrors( + @PolyRegex String polyReg, @Regex String reg, String str) { + // :: error: (assignment.type.incompatible) + @PolyRegex String test1 = polyReg + str; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test2 = str + polyReg; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test3 = reg + str; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test4 = str + reg; // error + + // :: error: (assignment.type.incompatible) + @PolyRegex String test5 = str + str; // error + } + + public static @PolyRegex String slice(@PolyRegex String seq, int start, int end) { + if (seq == null) { + return null; } + return seq; + } - public static @PolyRegex String slice(@PolyRegex String seq, int start, int end) { - if (seq == null) { - return null; - } - return seq; - } - - public static @PolyRegex String slice(@PolyRegex String seq, long start, int end) { - return slice(seq, (int) start, end); - } + public static @PolyRegex String slice(@PolyRegex String seq, long start, int end) { + return slice(seq, (int) start, end); + } } diff --git a/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java b/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java index 82b3636b09c..514601a0da9 100644 --- a/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java +++ b/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java @@ -7,7 +7,7 @@ class StringBuilderToStringPolyRegex { - void createPattern(final @Regex(1) StringBuilder regex) { - @Regex(1) String s = regex.toString(); - } + void createPattern(final @Regex(1) StringBuilder regex) { + @Regex(1) String s = regex.toString(); + } } diff --git a/checker/tests/resourceleak-customignoredexceptions/BasicTest.java b/checker/tests/resourceleak-customignoredexceptions/BasicTest.java index a9d7987c318..e6f41bcd264 100644 --- a/checker/tests/resourceleak-customignoredexceptions/BasicTest.java +++ b/checker/tests/resourceleak-customignoredexceptions/BasicTest.java @@ -1,60 +1,59 @@ +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - abstract class BasicTest { - abstract Closeable alloc(); + abstract Closeable alloc(); - abstract void method(); + abstract void method(); - public void runtimeExceptionManuallyThrown() throws IOException { - // this code is obviously wrong - // ::error: (required.method.not.called) - Closeable r = alloc(); - if (true) { - throw new RuntimeException(); - } - r.close(); + public void runtimeExceptionManuallyThrown() throws IOException { + // this code is obviously wrong + // ::error: (required.method.not.called) + Closeable r = alloc(); + if (true) { + throw new RuntimeException(); } - - public void runtimeExceptionFromMethod() throws IOException { - // method() may throw RuntimeException, so this code is not OK - // ::error: (required.method.not.called) - Closeable r = alloc(); - method(); - r.close(); + r.close(); + } + + public void runtimeExceptionFromMethod() throws IOException { + // method() may throw RuntimeException, so this code is not OK + // ::error: (required.method.not.called) + Closeable r = alloc(); + method(); + r.close(); + } + + // Note that even just constructing an instance of NullPointerException can throw all kinds + // of exceptions: ClassCircularityError, OutOfMemoryError, etc. Even RuntimeException is + // possible in theory. So, to really test what we're trying to test, we have to isolate + // the construction of the exception out here. + static final NullPointerException NPE = new NullPointerException(); + + public void ignoreNPE() throws IOException { + // this code is obviously wrong, but it is allowed because our ignored exceptions list + // includes NullPointerException + Closeable r = alloc(); + if (true) { + throw NPE; } - - // Note that even just constructing an instance of NullPointerException can throw all kinds - // of exceptions: ClassCircularityError, OutOfMemoryError, etc. Even RuntimeException is - // possible in theory. So, to really test what we're trying to test, we have to isolate - // the construction of the exception out here. - static final NullPointerException NPE = new NullPointerException(); - - public void ignoreNPE() throws IOException { - // this code is obviously wrong, but it is allowed because our ignored exceptions list - // includes NullPointerException - Closeable r = alloc(); - if (true) { - throw NPE; - } - r.close(); - } - - static class CustomNPESubtype extends NullPointerException { - static final CustomNPESubtype INSTANCE = new CustomNPESubtype(); - } - - public void doNotIgnoreNPESubtype() throws IOException { - // Only NullPointerException should be ignored, not its subtypes, since the options - // specified "=java.lang.NullPointerException". - // ::error: (required.method.not.called) - Closeable r = alloc(); - if (true) { - throw CustomNPESubtype.INSTANCE; - } - r.close(); + r.close(); + } + + static class CustomNPESubtype extends NullPointerException { + static final CustomNPESubtype INSTANCE = new CustomNPESubtype(); + } + + public void doNotIgnoreNPESubtype() throws IOException { + // Only NullPointerException should be ignored, not its subtypes, since the options + // specified "=java.lang.NullPointerException". + // ::error: (required.method.not.called) + Closeable r = alloc(); + if (true) { + throw CustomNPESubtype.INSTANCE; } + r.close(); + } } diff --git a/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java b/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java index 5d1942c30d2..d1a3324910e 100644 --- a/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java +++ b/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java @@ -1,37 +1,36 @@ +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - abstract class BasicTest { - abstract Closeable alloc(); + abstract Closeable alloc(); - abstract void method(); + abstract void method(); - public void runtimeExceptionManuallyThrown() throws IOException { - // this code is obviously wrong, but RuntimeException is ignored by default - Closeable r = alloc(); - if (true) { - throw new RuntimeException(); - } - r.close(); + public void runtimeExceptionManuallyThrown() throws IOException { + // this code is obviously wrong, but RuntimeException is ignored by default + Closeable r = alloc(); + if (true) { + throw new RuntimeException(); } + r.close(); + } - public void runtimeExceptionFromMethod() throws IOException { - // method() may throw RuntimeException, but RuntimeException is ignored by default - Closeable r = alloc(); - method(); - r.close(); - } + public void runtimeExceptionFromMethod() throws IOException { + // method() may throw RuntimeException, but RuntimeException is ignored by default + Closeable r = alloc(); + method(); + r.close(); + } - public void ignoreIllegalStateException() throws IOException { - // this code is obviously wrong, but it is allowed because our ignored exceptions list - // includes IllegalStateException - Closeable r = alloc(); - if (true) { - throw new IllegalStateException(); - } - r.close(); + public void ignoreIllegalStateException() throws IOException { + // this code is obviously wrong, but it is allowed because our ignored exceptions list + // includes IllegalStateException + Closeable r = alloc(); + if (true) { + throw new IllegalStateException(); } + r.close(); + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java index 76de8c024ae..1dae1480586 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java @@ -5,47 +5,46 @@ // @CreatesMustCallFor annotation // and its accompanying logic) has been disabled. -import org.checkerframework.checker.mustcall.qual.*; - import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; class ConnectingServerSockets { - static void simple_ss_test(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - ServerSocket s = new ServerSocket(); - s.bind(sa); - } - - static void simple_ss_test2(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - ServerSocket s = new ServerSocket(); - // s.bind(sa); - } - - static void simple_ss_test4(SocketAddress sa, int to) throws Exception { - // :: error: (required.method.not.called) - ServerSocket s = new ServerSocket(); - s.bind(sa, to); - } - - static @MustCall({}) ServerSocket makeUnconnected() throws Exception { - // :: error: (return.type.incompatible) - return new ServerSocket(); - } - - static void simple_ss_test5(SocketAddress sa) throws Exception { - ServerSocket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ss_test6(SocketAddress sa) throws Exception { - ServerSocket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ss_test8(SocketAddress sa, int to) throws Exception { - ServerSocket s = makeUnconnected(); - s.bind(sa, to); - } + static void simple_ss_test(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + ServerSocket s = new ServerSocket(); + s.bind(sa); + } + + static void simple_ss_test2(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + ServerSocket s = new ServerSocket(); + // s.bind(sa); + } + + static void simple_ss_test4(SocketAddress sa, int to) throws Exception { + // :: error: (required.method.not.called) + ServerSocket s = new ServerSocket(); + s.bind(sa, to); + } + + static @MustCall({}) ServerSocket makeUnconnected() throws Exception { + // :: error: (return.type.incompatible) + return new ServerSocket(); + } + + static void simple_ss_test5(SocketAddress sa) throws Exception { + ServerSocket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ss_test6(SocketAddress sa) throws Exception { + ServerSocket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ss_test8(SocketAddress sa, int to) throws Exception { + ServerSocket s = makeUnconnected(); + s.bind(sa, to); + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java index d57057a1c73..32b99390943 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java @@ -1,58 +1,57 @@ // a set of test cases that demonstrate that errors are actually issued in appropriate // places when Sockets are connected -import org.checkerframework.checker.mustcall.qual.*; - import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; class ConnectingSockets { - static void simple_ns_test(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - s.bind(sa); - } - - static void simple_ns_test2(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - // s.bind(sa); - } - - static void simple_ns_test3(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - s.connect(sa); - } - - static void simple_ns_test4(SocketAddress sa, int to) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - s.connect(sa, to); - } - - static @MustCall({}) Socket makeUnconnected() throws Exception { - // :: error: (return.type.incompatible) - return new Socket(); - } - - static void simple_ns_test5(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ns_test6(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ns_test7(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - s.connect(sa); - } - - static void simple_ns_test8(SocketAddress sa, int to) throws Exception { - Socket s = makeUnconnected(); - s.connect(sa, to); - } + static void simple_ns_test(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + s.bind(sa); + } + + static void simple_ns_test2(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + // s.bind(sa); + } + + static void simple_ns_test3(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + s.connect(sa); + } + + static void simple_ns_test4(SocketAddress sa, int to) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + s.connect(sa, to); + } + + static @MustCall({}) Socket makeUnconnected() throws Exception { + // :: error: (return.type.incompatible) + return new Socket(); + } + + static void simple_ns_test5(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ns_test6(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ns_test7(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + s.connect(sa); + } + + static void simple_ns_test8(SocketAddress sa, int to) throws Exception { + Socket s = makeUnconnected(); + s.connect(sa, to); + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java b/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java index cdb72d010ca..ec19c425fdf 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java @@ -8,24 +8,24 @@ @InheritableMustCall("a") class CreatesMustCallForSimpler { - @CreatesMustCallFor - void reset() {} - - @CreatesMustCallFor("this") - void resetThis() {} - - void a() {} - - static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { - // :: error: (return.type.incompatible) - return new CreatesMustCallForSimpler(); - } - - static void test1() { - CreatesMustCallForSimpler cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimpler a = cos; - cos.reset(); - @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; - @CalledMethods({}) CreatesMustCallForSimpler c = cos; - } + @CreatesMustCallFor + void reset() {} + + @CreatesMustCallFor("this") + void resetThis() {} + + void a() {} + + static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { + // :: error: (return.type.incompatible) + return new CreatesMustCallForSimpler(); + } + + static void test1() { + CreatesMustCallForSimpler cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimpler a = cos; + cos.reset(); + @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; + @CalledMethods({}) CreatesMustCallForSimpler c = cos; + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java b/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java index 8b0a5b0edde..b9d626f214d 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java @@ -6,21 +6,21 @@ @SuppressWarnings("required.method.not.called") class DifferentSWKeys { - void test(@Owning @MustCall("foo") Object obj) { - // :: warning: unneeded.suppression - @SuppressWarnings("mustcall") - @MustCall("foo") Object bar = obj; - } + void test(@Owning @MustCall("foo") Object obj) { + // :: warning: unneeded.suppression + @SuppressWarnings("mustcall") + @MustCall("foo") Object bar = obj; + } - void test2(@Owning @MustCall("foo") Object obj) { - // actually needed suppression - @SuppressWarnings("mustcall") - @MustCall({}) Object bar = obj; - } + void test2(@Owning @MustCall("foo") Object obj) { + // actually needed suppression + @SuppressWarnings("mustcall") + @MustCall({}) Object bar = obj; + } - void test3(@Owning @MustCall("foo") Object obj) { - // test that the option-specific suppression key works - @SuppressWarnings("mustcallnocreatesmustcallfor") - @MustCall({}) Object bar = obj; - } + void test3(@Owning @MustCall("foo") Object obj) { + // test that the option-specific suppression key works + @SuppressWarnings("mustcallnocreatesmustcallfor") + @MustCall({}) Object bar = obj; + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java b/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java index 5d2e4c3858b..2ca752820ef 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java @@ -2,34 +2,33 @@ // This is a modified version of tests/socket/SocketContainer.java // for checking that without CO support we can't assign to non-final owning fields at all. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("close") class SocketContainer { - @Owning Socket sock; + @Owning Socket sock; - public SocketContainer(String host, int port) throws Exception { - // Assignments to owning fields should not be permitted. - // :: error: required.method.not.called - sock = new Socket(host, port); - } + public SocketContainer(String host, int port) throws Exception { + // Assignments to owning fields should not be permitted. + // :: error: required.method.not.called + sock = new Socket(host, port); + } - // No missing create obligation error is issued, since CO is disabled... - public void reassign(String host, int port) throws Exception { - sock.close(); - // For the RHS, because the field can't take ownership - // :: error: required.method.not.called - Socket sr = new Socket(host, port); - // No warning for overwriting the field, since it can't take ownership! - sock = sr; - } + // No missing create obligation error is issued, since CO is disabled... + public void reassign(String host, int port) throws Exception { + sock.close(); + // For the RHS, because the field can't take ownership + // :: error: required.method.not.called + Socket sr = new Socket(host, port); + // No warning for overwriting the field, since it can't take ownership! + sock = sr; + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + } } diff --git a/checker/tests/resourceleak-nolightweightownership/ACOwning.java b/checker/tests/resourceleak-nolightweightownership/ACOwning.java index ddbbbe61103..c8c00786890 100644 --- a/checker/tests/resourceleak-nolightweightownership/ACOwning.java +++ b/checker/tests/resourceleak-nolightweightownership/ACOwning.java @@ -8,53 +8,53 @@ class ACOwning { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - Foo makeFoo() { - return new Foo(); - } - - static void takeOwnership(@Owning Foo foo) { - foo.a(); - } - - static void noOwnership(Foo foo) {} - - static void takeOwnershipWrong(@Owning Foo foo) {} - - static @NotOwning Foo getNonOwningFoo() { - return new Foo(); - } - - static void callGetNonOwningFoo() { - // :: error: (required.method.not.called) - getNonOwningFoo(); - } - - static void ownershipInCallee() { - // :: error: (required.method.not.called) - Foo f = new Foo(); - takeOwnership(f); - // :: error: (required.method.not.called) - Foo g = new Foo(); - noOwnership(g); - } - - @Owning - public Foo owningAtReturn() { - return new Foo(); - } - - void owningAtReturnTest() { - // :: error: (required.method.not.called) - Foo f = owningAtReturn(); - } - - void ownershipTest() { - // :: error: (required.method.not.called) - takeOwnership(new Foo()); - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + Foo makeFoo() { + return new Foo(); + } + + static void takeOwnership(@Owning Foo foo) { + foo.a(); + } + + static void noOwnership(Foo foo) {} + + static void takeOwnershipWrong(@Owning Foo foo) {} + + static @NotOwning Foo getNonOwningFoo() { + return new Foo(); + } + + static void callGetNonOwningFoo() { + // :: error: (required.method.not.called) + getNonOwningFoo(); + } + + static void ownershipInCallee() { + // :: error: (required.method.not.called) + Foo f = new Foo(); + takeOwnership(f); + // :: error: (required.method.not.called) + Foo g = new Foo(); + noOwnership(g); + } + + @Owning + public Foo owningAtReturn() { + return new Foo(); + } + + void owningAtReturnTest() { + // :: error: (required.method.not.called) + Foo f = owningAtReturn(); + } + + void ownershipTest() { + // :: error: (required.method.not.called) + takeOwnership(new Foo()); + } } diff --git a/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java b/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java index 946bd3e826a..e7f65924150 100644 --- a/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java +++ b/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java @@ -2,62 +2,61 @@ // This version has been modified to expect that @MustCallAlias annotations // are always ignored, as if running with -AnoResourceAliases. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.io.IOException; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; class MustCallAliasExamples { - void test_two_locals(String address) { - Socket socket = null; - try { - socket = new Socket(address, 8000); - // :: error: required.method.not.called - DataInputStream d = new DataInputStream(socket.getInputStream()); - } catch (IOException e) { - - } finally { - closeSocket(socket); - } - } + void test_two_locals(String address) { + Socket socket = null; + try { + socket = new Socket(address, 8000); + // :: error: required.method.not.called + DataInputStream d = new DataInputStream(socket.getInputStream()); + } catch (IOException e) { - // :: error: required.method.not.called - void test_close_wrapper(@Owning InputStream b) throws IOException { - DataInputStream d = new DataInputStream(b); - d.close(); + } finally { + closeSocket(socket); } + } - void test_close_nonwrapper(@Owning InputStream b) throws IOException { - // :: error: required.method.not.called - DataInputStream d = new DataInputStream(b); - b.close(); - } + // :: error: required.method.not.called + void test_close_wrapper(@Owning InputStream b) throws IOException { + DataInputStream d = new DataInputStream(b); + d.close(); + } + void test_close_nonwrapper(@Owning InputStream b) throws IOException { // :: error: required.method.not.called - void test_no_close(@Owning InputStream b) { - // :: error: required.method.not.called - DataInputStream d = new DataInputStream(b); - } + DataInputStream d = new DataInputStream(b); + b.close(); + } + // :: error: required.method.not.called + void test_no_close(@Owning InputStream b) { // :: error: required.method.not.called - void test_no_assign(@Owning InputStream b) { + DataInputStream d = new DataInputStream(b); + } + + // :: error: required.method.not.called + void test_no_assign(@Owning InputStream b) { + // :: error: required.method.not.called + new DataInputStream( // :: error: required.method.not.called - new DataInputStream( - // :: error: required.method.not.called - new BufferedInputStream(b)); - } + new BufferedInputStream(b)); + } - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeSocket(Socket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeSocket(Socket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { - } } + } } diff --git a/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java b/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java index 12ec04fe639..fdba664ba89 100644 --- a/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java +++ b/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java @@ -2,25 +2,24 @@ // This version has been modified to expect errors, as if running under // -AnoResourceAliases - so @MustCallAlias annotations are ignored. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughLocal extends FilterInputStream { - MustCallAliasPassthroughLocal(File f) throws Exception { - // This is safe - this MCA constructor of FilterInputStream means that the result of this - // constructor - i.e. the caller - is taking ownership of this newly-created output stream. - // :: error: required.method.not.called - super(new FileInputStream(f)); - } + MustCallAliasPassthroughLocal(File f) throws Exception { + // This is safe - this MCA constructor of FilterInputStream means that the result of this + // constructor - i.e. the caller - is taking ownership of this newly-created output stream. + // :: error: required.method.not.called + super(new FileInputStream(f)); + } - static void test(File f) throws Exception { - // :: error: required.method.not.called - new MustCallAliasPassthroughLocal(f); - } + static void test(File f) throws Exception { + // :: error: required.method.not.called + new MustCallAliasPassthroughLocal(f); + } - static void test_ok(File f) throws Exception { - new MustCallAliasPassthroughLocal(f).close(); - } + static void test_ok(File f) throws Exception { + new MustCallAliasPassthroughLocal(f).close(); + } } diff --git a/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java b/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java index 17c268604a2..890a5ef2765 100644 --- a/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java +++ b/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java @@ -2,53 +2,52 @@ // In the resourceleak-permitinitializationleak/ directory, it's a test that the // checker is unsound with the -ApermitInitializationLeak command-line argument. -import org.checkerframework.checker.mustcall.qual.*; - import java.net.Socket; +import org.checkerframework.checker.mustcall.qual.*; class InstanceInitializer { - // :: error: required.method.not.called - private @Owning Socket s; - - private final int DEFAULT_PORT = 5; - private final String DEFAULT_ADDR = "localhost"; - - { - try { - // This assignment is OK, because it's the first assignment. - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } - } + // :: error: required.method.not.called + private @Owning Socket s; - { - try { - // This assignment is not OK, because it's a reassignment without satisfying the - // mustcall obligations of the previous value of `s`. - // With -ApermitInitializationLeak, the Resource Leak Checker unsoundly permits it. - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } - } + private final int DEFAULT_PORT = 5; + private final String DEFAULT_ADDR = "localhost"; - { - try { - // :: error: required.method.not.called - Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } + { + try { + // This assignment is OK, because it's the first assignment. + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } + } + + { + try { + // This assignment is not OK, because it's a reassignment without satisfying the + // mustcall obligations of the previous value of `s`. + // With -ApermitInitializationLeak, the Resource Leak Checker unsoundly permits it. + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { } + } - { - Socket s1 = null; - try { - s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } - s1.close(); + { + try { + // :: error: required.method.not.called + Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { } + } - public InstanceInitializer() throws Exception { - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + { + Socket s1 = null; + try { + s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { } + s1.close(); + } + + public InstanceInitializer() throws Exception { + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } } diff --git a/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java b/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java index 96e6c96effc..954fe8732f3 100644 --- a/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java +++ b/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java @@ -2,29 +2,28 @@ // This test exists to check that we gracefully handle assignments 1) // in the constructor and 2) to null. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("close") class SocketContainer { - @Owning Socket sock; + @Owning Socket sock; - public SocketContainer(String host, int port) throws Exception { - sock = new Socket(host, port); - try { - sock = new Socket(host, port); - } catch (Exception ignored) { - } + public SocketContainer(String host, int port) throws Exception { + sock = new Socket(host, port); + try { + sock = new Socket(host, port); + } catch (Exception ignored) { } + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - // It's okay to assign a field to null after its obligations have been fulfilled, - // without inducing a reset. - sock = null; - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + // It's okay to assign a field to null after its obligations have been fulfilled, + // without inducing a reset. + sock = null; + } } diff --git a/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java b/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java index 9016c5d1ebe..19e69eae1a1 100644 --- a/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java +++ b/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java @@ -1,64 +1,63 @@ +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintStream; - @InheritableMustCall("close") class StaticOwningField implements Closeable { - // Instance field + // Instance field - private @Owning @MustCall("close") PrintStream ps_instance; + private @Owning @MustCall("close") PrintStream ps_instance; - @CreatesMustCallFor("this") - void m_instance() throws IOException { - ps_instance.close(); - ps_instance = new PrintStream("filename.txt"); - } + @CreatesMustCallFor("this") + void m_instance() throws IOException { + ps_instance.close(); + ps_instance = new PrintStream("filename.txt"); + } - @EnsuresCalledMethods(value = "ps_instance", methods = "close") - @Override - public void close() { - ps_instance.close(); - } + @EnsuresCalledMethods(value = "ps_instance", methods = "close") + @Override + public void close() { + ps_instance.close(); + } - // Static field + // Static field - private static @Owning @MustCall("close") PrintStream ps_static; + private static @Owning @MustCall("close") PrintStream ps_static; - static void m_static() throws IOException { - ps_static.close(); - ps_static = new PrintStream("filename.txt"); - } + static void m_static() throws IOException { + ps_static.close(); + ps_static = new PrintStream("filename.txt"); + } - private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = - newPrintStreamWithoutExceptions(); + private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = + newPrintStreamWithoutExceptions(); - private static @Owning @MustCall("close") PrintStream ps_static_initialized2; + private static @Owning @MustCall("close") PrintStream ps_static_initialized2; - static { - ps_static_initialized2 = newPrintStreamWithoutExceptions(); - } + static { + ps_static_initialized2 = newPrintStreamWithoutExceptions(); + } - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = - newPrintStreamWithoutExceptions(); + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = + newPrintStreamWithoutExceptions(); - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; - static { - ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); - } + static { + ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); + } - public static PrintStream newPrintStreamWithoutExceptions() { - try { - return new PrintStream("filename.txt"); - } catch (Exception e) { - throw new Error(e); - } + public static PrintStream newPrintStreamWithoutExceptions() { + try { + return new PrintStream("filename.txt"); + } catch (Exception e) { + throw new Error(e); } + } } diff --git a/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java b/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java index 6bddb48357c..151693e7dae 100644 --- a/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java +++ b/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java @@ -1,21 +1,20 @@ -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.FileWriter; import java.io.IOException; import java.io.UncheckedIOException; +import org.checkerframework.checker.mustcall.qual.Owning; public class StaticOwningFieldOtherClass {} abstract class HasStaticOwningField { - public static @Owning FileWriter log = null; + public static @Owning FileWriter log = null; } class TestUtils { - public static void setLog(String filename) { - try { - HasStaticOwningField.log = new FileWriter(filename); - } catch (IOException ioe) { - throw new UncheckedIOException("Cannot write file " + filename, ioe); - } + public static void setLog(String filename) { + try { + HasStaticOwningField.log = new FileWriter(filename); + } catch (IOException ioe) { + throw new UncheckedIOException("Cannot write file " + filename, ioe); } + } } diff --git a/checker/tests/resourceleak/ACExceptionalExitPointTest.java b/checker/tests/resourceleak/ACExceptionalExitPointTest.java index d547df21583..efd7d23c556 100644 --- a/checker/tests/resourceleak/ACExceptionalExitPointTest.java +++ b/checker/tests/resourceleak/ACExceptionalExitPointTest.java @@ -4,36 +4,36 @@ class ACExceptionalExitPointTest { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; - } - - void c() {} - } - - Foo makeFoo() { - return new Foo(); - } - - @CalledMethods({"a"}) Foo makeFoo2() { - Foo f = new Foo(); - f.a(); - return f; - } - - void exceptionalExitWrong() throws Exception { - // :: error: required.method.not.called - Foo fw = makeFoo(); - throw new Exception(); + @This Foo b() { + return this; } - void exceptionalExitCorrect() throws Exception { - Foo fw = new Foo(); - fw.a(); - throw new Exception(); - } + void c() {} + } + + Foo makeFoo() { + return new Foo(); + } + + @CalledMethods({"a"}) Foo makeFoo2() { + Foo f = new Foo(); + f.a(); + return f; + } + + void exceptionalExitWrong() throws Exception { + // :: error: required.method.not.called + Foo fw = makeFoo(); + throw new Exception(); + } + + void exceptionalExitCorrect() throws Exception { + Foo fw = new Foo(); + fw.a(); + throw new Exception(); + } } diff --git a/checker/tests/resourceleak/ACMethodInvocationTest.java b/checker/tests/resourceleak/ACMethodInvocationTest.java index 5d1850e24d7..ca59ef15f4c 100644 --- a/checker/tests/resourceleak/ACMethodInvocationTest.java +++ b/checker/tests/resourceleak/ACMethodInvocationTest.java @@ -4,95 +4,95 @@ class ACMethodInvocationTest { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; - } - - void c() {} - } - - @Owning - Foo makeFoo() { - return new Foo(); - } - - @CalledMethods({"a"}) Foo makeFooFinalize() { - Foo f = new Foo(); - f.a(); - return f; - } - - @Owning - @CalledMethods({"b"}) Foo makeFooFinalize2() { - Foo f = new Foo(); - f.b(); - return f; - } - - void CallMethodsInSequence() { - makeFoo().a(); - } - - void CallMethodsInSequence2() { - makeFoo().b().a(); - } - - void testFluentAPIWrong() { - // :: error: required.method.not.called - makeFoo().b(); - } - - void testFluentAPIWrong2() { - // :: error: required.method.not.called - makeFoo(); - } - - void invokeMethodWithCallA() { - makeFooFinalize(); - } - - void invokeMethodWithCallBWrong() { - // :: error: required.method.not.called - makeFooFinalize2(); - } - - void invokeMethodAndCallCWrong() { - // :: error: required.method.not.called - makeFoo().c(); - } - - Foo returnMakeFoo() { - return makeFoo(); - } - - Foo testField1; - Foo testField2; - Foo testField3; - - void testStoringInField() { - // :: error: required.method.not.called - testField1 = makeFoo(); - // :: error: required.method.not.called - testField2 = new Foo(); - - testField3 = makeFooFinalize(); + @This Foo b() { + return this; } - void tryCatchFinally() { - Foo f = null; - try { - f = new Foo(); - try { - throw new RuntimeException(); - } catch (Exception e) { - - } - } finally { - f.a(); - } + void c() {} + } + + @Owning + Foo makeFoo() { + return new Foo(); + } + + @CalledMethods({"a"}) Foo makeFooFinalize() { + Foo f = new Foo(); + f.a(); + return f; + } + + @Owning + @CalledMethods({"b"}) Foo makeFooFinalize2() { + Foo f = new Foo(); + f.b(); + return f; + } + + void CallMethodsInSequence() { + makeFoo().a(); + } + + void CallMethodsInSequence2() { + makeFoo().b().a(); + } + + void testFluentAPIWrong() { + // :: error: required.method.not.called + makeFoo().b(); + } + + void testFluentAPIWrong2() { + // :: error: required.method.not.called + makeFoo(); + } + + void invokeMethodWithCallA() { + makeFooFinalize(); + } + + void invokeMethodWithCallBWrong() { + // :: error: required.method.not.called + makeFooFinalize2(); + } + + void invokeMethodAndCallCWrong() { + // :: error: required.method.not.called + makeFoo().c(); + } + + Foo returnMakeFoo() { + return makeFoo(); + } + + Foo testField1; + Foo testField2; + Foo testField3; + + void testStoringInField() { + // :: error: required.method.not.called + testField1 = makeFoo(); + // :: error: required.method.not.called + testField2 = new Foo(); + + testField3 = makeFooFinalize(); + } + + void tryCatchFinally() { + Foo f = null; + try { + f = new Foo(); + try { + throw new RuntimeException(); + } catch (Exception e) { + + } + } finally { + f.a(); } + } } diff --git a/checker/tests/resourceleak/ACOwning.java b/checker/tests/resourceleak/ACOwning.java index 2b5f0da9363..1263c515100 100644 --- a/checker/tests/resourceleak/ACOwning.java +++ b/checker/tests/resourceleak/ACOwning.java @@ -3,79 +3,79 @@ class ACOwning { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - Foo makeFoo() { - return new Foo(); - } + Foo makeFoo() { + return new Foo(); + } - static void takeOwnership(@Owning Foo foo, Foo f) { - foo.a(); - } + static void takeOwnership(@Owning Foo foo, Foo f) { + foo.a(); + } - static void noOwnership(Foo foo) {} + static void noOwnership(Foo foo) {} - // :: error: required.method.not.called - static void takeOwnershipWrong(@Owning Foo foo) {} + // :: error: required.method.not.called + static void takeOwnershipWrong(@Owning Foo foo) {} - static @NotOwning Foo getNonOwningFoo() { - // :: error: required.method.not.called - return new Foo(); - } + static @NotOwning Foo getNonOwningFoo() { + // :: error: required.method.not.called + return new Foo(); + } - static void callGetNonOwningFoo() { - getNonOwningFoo(); - } + static void callGetNonOwningFoo() { + getNonOwningFoo(); + } - static void ownershipInCallee() { - Foo f = new Foo(); - // :: error: required.method.not.called - takeOwnership(f, new Foo()); - // :: error: required.method.not.called - Foo g = new Foo(); - noOwnership(g); - } + static void ownershipInCallee() { + Foo f = new Foo(); + // :: error: required.method.not.called + takeOwnership(f, new Foo()); + // :: error: required.method.not.called + Foo g = new Foo(); + noOwnership(g); + } + + // make sure enum doesn't crash things + static enum TestEnum { + CASE1, + CASE2, + CASE3 + } + + @Owning + public Foo owningAtReturn() { + return new Foo(); + } + + void owningAtReturnTest() { + // :: error: required.method.not.called + Foo f = owningAtReturn(); + } - // make sure enum doesn't crash things - static enum TestEnum { - CASE1, - CASE2, - CASE3 - } + void ownershipTest() { + // :: error: required.method.not.called + takeOwnership(new Foo(), makeFoo()); + } - @Owning - public Foo owningAtReturn() { - return new Foo(); - } + @InheritableMustCall({}) + // :: error: super.invocation.invalid :: error: inconsistent.mustcall.subtype + private class SubFoo extends Foo { - void owningAtReturnTest() { - // :: error: required.method.not.called - Foo f = owningAtReturn(); + void test() { + SubFoo f = new SubFoo(); } - void ownershipTest() { - // :: error: required.method.not.called - takeOwnership(new Foo(), makeFoo()); + void test2() { + // :: error: required.method.not.called + Foo f = new Foo(); } - @InheritableMustCall({}) - // :: error: super.invocation.invalid :: error: inconsistent.mustcall.subtype - private class SubFoo extends Foo { - - void test() { - SubFoo f = new SubFoo(); - } - - void test2() { - // :: error: required.method.not.called - Foo f = new Foo(); - } - - void test3() { - Foo f = new SubFoo(); - } + void test3() { + Foo f = new SubFoo(); } + } } diff --git a/checker/tests/resourceleak/ACRegularExitPointTest.java b/checker/tests/resourceleak/ACRegularExitPointTest.java index 16f19a8693a..39d18f46dad 100644 --- a/checker/tests/resourceleak/ACRegularExitPointTest.java +++ b/checker/tests/resourceleak/ACRegularExitPointTest.java @@ -1,309 +1,308 @@ +import java.io.IOException; +import java.util.function.Function; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; -import java.io.IOException; -import java.util.function.Function; - class ACRegularExitPointTest { - @InheritableMustCall("a") - class Foo { - void a() {} - - @This Foo b() { - return this; - } - - void c(@CalledMethods("a") Foo this) {} - } - - class SubFoo extends Foo {} - - Foo makeFoo() { - return new Foo(); - } - - @CalledMethods("a") Foo makeFooCallA() { - Foo f = new Foo(); - f.a(); - return f; - } - - @EnsuresCalledMethods(value = "#1", methods = "a") - void callA(Foo f) { - f.a(); - } - - void makeFooFinalize() { - Foo f = new Foo(); - f.a(); - } - - void makeFooFinalizeWrong() { - Foo m; - // :: error: required.method.not.called - m = new Foo(); - // :: error: required.method.not.called - Foo f = new Foo(); - f.b(); - } - - void testStoringInLocalWrong() { - // :: error: required.method.not.called - Foo foo = makeFoo(); - } - - void testStoringInLocalWrong2() { - Foo f; - // :: error: required.method.not.called - f = makeFoo(); - } - - void testStoringInLocal() { - Foo foo = makeFooCallA(); - } - - void testStoringInLocalWrong3() { - // :: error: required.method.not.called - Foo foo = new Foo(); - } - - void emptyFuncWithFormalPram(Foo f) {} - - void innerFunc(Foo f) { - Runnable r = - new Runnable() { - public void run() { - Foo f; - } - ; - }; - r.run(); - } - - void innerFuncWrong(Foo f) { - Runnable r = - new Runnable() { - public void run() { - // :: error: required.method.not.called - Foo g = new Foo(); - } - ; - }; - r.run(); - } - - void innerFunc2(Foo f) { - Runnable r = - new Runnable() { - public void run() { - Foo g = makeFoo(); - g.a(); - } - ; - }; - r.run(); - } - - void innerfunc3() { - - Foo f = makeFoo(); - f.a(); - Function<@MustCall Foo, @CalledMethods("a") @MustCall Foo> innerfunc = - st -> { - // :: error: required.method.not.called - Foo fn1 = new Foo(); - Foo fn2 = makeFoo(); - fn2.a(); - // The need for this cast is undesirable, but is a consequence of our approach - // to generic types. In this case, this cast is clearly safe (a() has already - // been called, so the obligation is satisfied on the returned value, as - // intended). - // :: warning: cast.unsafe - return ((@MustCall Foo) fn2); - }; - - innerfunc.apply(f); - } - - void ifElse(boolean b) { - if (b) { - Foo f1 = new Foo(); - f1.a(); - } else { - // :: error: required.method.not.called - Foo f2 = new Foo(); - } - } - - Foo ifElseWithReturnExit(boolean b, boolean c) { - // :: error: required.method.not.called - Foo f1 = makeFoo(); - // :: error: required.method.not.called - Foo f3 = new Foo(); - // :: error: required.method.not.called - Foo f4 = new Foo(); - - if (b) { - // :: error: required.method.not.called - Foo f2 = new Foo(); - if (c) { - f4.a(); - } else { - f4.b(); - } - return f1; - } else { - // :: error: required.method.not.called - Foo f2 = new Foo(); - f2 = new Foo(); - f2.a(); - } - return f3; - } - - void ifElseWithDeclaration(boolean b) { - Foo f1; - Foo f2; - if (b) { - f1 = new Foo(); - f1.a(); - } else { - // :: error: required.method.not.called - f2 = new Foo(); - } - } - - void ifElseWithInitialization(boolean b) { - // :: error: required.method.not.called - Foo f2 = new Foo(); - Foo f11 = null; - if (b) { - f11 = makeFoo(); - f11.a(); - } else { - // :: error: required.method.not.called - f2 = new Foo(); - } - } - - void ifWithInitialization(boolean b) { - // :: error: required.method.not.called - Foo f1 = new Foo(); - // :: error: required.method.not.called - Foo f2 = new Foo(); - if (b) { - f1.a(); - } - } - - void variableGoesOutOfScope(boolean b) { - if (b) { - Foo f1 = new Foo(); - f1.a(); - } - } - - void ifWithNullInitialization(boolean b) { - Foo f1 = null; - Foo f2 = null; - if (b) { - f1 = new Foo(); - f1.a(); - } else { - // :: error: required.method.not.called - f2 = new Foo(); - } - } - - void variableInitializedWithNull() { - Foo f = null; - } - - void testLoop() { - Foo f = null; - while (true) { + @InheritableMustCall("a") + class Foo { + void a() {} + + @This Foo b() { + return this; + } + + void c(@CalledMethods("a") Foo this) {} + } + + class SubFoo extends Foo {} + + Foo makeFoo() { + return new Foo(); + } + + @CalledMethods("a") Foo makeFooCallA() { + Foo f = new Foo(); + f.a(); + return f; + } + + @EnsuresCalledMethods(value = "#1", methods = "a") + void callA(Foo f) { + f.a(); + } + + void makeFooFinalize() { + Foo f = new Foo(); + f.a(); + } + + void makeFooFinalizeWrong() { + Foo m; + // :: error: required.method.not.called + m = new Foo(); + // :: error: required.method.not.called + Foo f = new Foo(); + f.b(); + } + + void testStoringInLocalWrong() { + // :: error: required.method.not.called + Foo foo = makeFoo(); + } + + void testStoringInLocalWrong2() { + Foo f; + // :: error: required.method.not.called + f = makeFoo(); + } + + void testStoringInLocal() { + Foo foo = makeFooCallA(); + } + + void testStoringInLocalWrong3() { + // :: error: required.method.not.called + Foo foo = new Foo(); + } + + void emptyFuncWithFormalPram(Foo f) {} + + void innerFunc(Foo f) { + Runnable r = + new Runnable() { + public void run() { + Foo f; + } + ; + }; + r.run(); + } + + void innerFuncWrong(Foo f) { + Runnable r = + new Runnable() { + public void run() { // :: error: required.method.not.called - f = new Foo(); - } - } - - void overWrittingVarInLoop() { + Foo g = new Foo(); + } + ; + }; + r.run(); + } + + void innerFunc2(Foo f) { + Runnable r = + new Runnable() { + public void run() { + Foo g = makeFoo(); + g.a(); + } + ; + }; + r.run(); + } + + void innerfunc3() { + + Foo f = makeFoo(); + f.a(); + Function<@MustCall Foo, @CalledMethods("a") @MustCall Foo> innerfunc = + st -> { + // :: error: required.method.not.called + Foo fn1 = new Foo(); + Foo fn2 = makeFoo(); + fn2.a(); + // The need for this cast is undesirable, but is a consequence of our approach + // to generic types. In this case, this cast is clearly safe (a() has already + // been called, so the obligation is satisfied on the returned value, as + // intended). + // :: warning: cast.unsafe + return ((@MustCall Foo) fn2); + }; + + innerfunc.apply(f); + } + + void ifElse(boolean b) { + if (b) { + Foo f1 = new Foo(); + f1.a(); + } else { + // :: error: required.method.not.called + Foo f2 = new Foo(); + } + } + + Foo ifElseWithReturnExit(boolean b, boolean c) { + // :: error: required.method.not.called + Foo f1 = makeFoo(); + // :: error: required.method.not.called + Foo f3 = new Foo(); + // :: error: required.method.not.called + Foo f4 = new Foo(); + + if (b) { + // :: error: required.method.not.called + Foo f2 = new Foo(); + if (c) { + f4.a(); + } else { + f4.b(); + } + return f1; + } else { + // :: error: required.method.not.called + Foo f2 = new Foo(); + f2 = new Foo(); + f2.a(); + } + return f3; + } + + void ifElseWithDeclaration(boolean b) { + Foo f1; + Foo f2; + if (b) { + f1 = new Foo(); + f1.a(); + } else { + // :: error: required.method.not.called + f2 = new Foo(); + } + } + + void ifElseWithInitialization(boolean b) { + // :: error: required.method.not.called + Foo f2 = new Foo(); + Foo f11 = null; + if (b) { + f11 = makeFoo(); + f11.a(); + } else { + // :: error: required.method.not.called + f2 = new Foo(); + } + } + + void ifWithInitialization(boolean b) { + // :: error: required.method.not.called + Foo f1 = new Foo(); + // :: error: required.method.not.called + Foo f2 = new Foo(); + if (b) { + f1.a(); + } + } + + void variableGoesOutOfScope(boolean b) { + if (b) { + Foo f1 = new Foo(); + f1.a(); + } + } + + void ifWithNullInitialization(boolean b) { + Foo f1 = null; + Foo f2 = null; + if (b) { + f1 = new Foo(); + f1.a(); + } else { + // :: error: required.method.not.called + f2 = new Foo(); + } + } + + void variableInitializedWithNull() { + Foo f = null; + } + + void testLoop() { + Foo f = null; + while (true) { + // :: error: required.method.not.called + f = new Foo(); + } + } + + void overWrittingVarInLoop() { + // :: error: required.method.not.called + Foo f = new Foo(); + while (true) { + // :: error: required.method.not.called + f = new Foo(); + } + } + + void loopWithNestedBranches(boolean b) { + Foo frodo = null; + while (true) { + if (b) { // :: error: required.method.not.called - Foo f = new Foo(); - while (true) { - // :: error: required.method.not.called - f = new Foo(); - } - } - - void loopWithNestedBranches(boolean b) { - Foo frodo = null; - while (true) { - if (b) { - // :: error: required.method.not.called - frodo = new Foo(); - } else { - // this is a known false positive, due to lack of path sensitivity in the - // Called Methods Checker - // :: error: required.method.not.called - frodo = new Foo(); - frodo.a(); - } - } - } - - void replaceVarWithNull(boolean b, boolean c) { + frodo = new Foo(); + } else { + // this is a known false positive, due to lack of path sensitivity in the + // Called Methods Checker // :: error: required.method.not.called - Foo f = new Foo(); - if (b) { - f = null; - } else if (c) { - f = null; - } else { - - } - } - - void ownershipTransfer() { - Foo f1 = new Foo(); - Foo f2 = f1; - Foo f3 = f2.b(); - f3.a(); - } - - void ownershipTransfer2() { - Foo f1 = null; - Foo f2 = f1; - } - - void testECM() { - Foo f = new Foo(); - callA(f); - } - - void testFinallyBlock(boolean b) { - Foo f = null; - try { - f = new Foo(); - if (true) { - throw new IOException(); - } - } catch (IOException e) { - - } finally { - f.a(); - } - } - - void testSubFoo() { - // :: error: required.method.not.called - Foo f = new SubFoo(); - } - - void testSubFoo2() { - // :: error: required.method.not.called - SubFoo f = new SubFoo(); - } + frodo = new Foo(); + frodo.a(); + } + } + } + + void replaceVarWithNull(boolean b, boolean c) { + // :: error: required.method.not.called + Foo f = new Foo(); + if (b) { + f = null; + } else if (c) { + f = null; + } else { + + } + } + + void ownershipTransfer() { + Foo f1 = new Foo(); + Foo f2 = f1; + Foo f3 = f2.b(); + f3.a(); + } + + void ownershipTransfer2() { + Foo f1 = null; + Foo f2 = f1; + } + + void testECM() { + Foo f = new Foo(); + callA(f); + } + + void testFinallyBlock(boolean b) { + Foo f = null; + try { + f = new Foo(); + if (true) { + throw new IOException(); + } + } catch (IOException e) { + + } finally { + f.a(); + } + } + + void testSubFoo() { + // :: error: required.method.not.called + Foo f = new SubFoo(); + } + + void testSubFoo2() { + // :: error: required.method.not.called + SubFoo f = new SubFoo(); + } } diff --git a/checker/tests/resourceleak/ACSocketTest.java b/checker/tests/resourceleak/ACSocketTest.java index ca7cb03e350..9686a4a9a15 100644 --- a/checker/tests/resourceleak/ACSocketTest.java +++ b/checker/tests/resourceleak/ACSocketTest.java @@ -1,7 +1,3 @@ -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; -import org.checkerframework.common.returnsreceiver.qual.*; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -10,444 +6,439 @@ import java.nio.channels.*; import java.util.*; import java.util.concurrent.atomic.AtomicReference; - import javax.net.ssl.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; +import org.checkerframework.common.returnsreceiver.qual.*; public class ACSocketTest { - @Owning - Socket makeSocket(String address, int port) { - - try { - Socket socket = new Socket(address, port); - return socket; - } catch (IOException i) { - return null; - } - } - - void basicTest(String address, int port) { - try { - // :: error: required.method.not.called - Socket socket2 = new Socket(address, port); - Socket specialSocket = new Socket(address, port); - specialSocket.close(); - } catch (IOException i) { - } - } - - void tryWithResourcesTest(String address, int port) throws IOException { - try (Socket s = new Socket(address, port)) {} - } - - void callMakeSocketAndClose(String address, int port) { - Socket socket = makeSocket(address, port); - try { - socket.close(); - } catch (IOException i) { - } - } - - void callMakeSocket(String address, int port) { + @Owning + Socket makeSocket(String address, int port) { + + try { + Socket socket = new Socket(address, port); + return socket; + } catch (IOException i) { + return null; + } + } + + void basicTest(String address, int port) { + try { + // :: error: required.method.not.called + Socket socket2 = new Socket(address, port); + Socket specialSocket = new Socket(address, port); + specialSocket.close(); + } catch (IOException i) { + } + } + + void tryWithResourcesTest(String address, int port) throws IOException { + try (Socket s = new Socket(address, port)) {} + } + + void callMakeSocketAndClose(String address, int port) { + Socket socket = makeSocket(address, port); + try { + socket.close(); + } catch (IOException i) { + } + } + + void callMakeSocket(String address, int port) { + // :: error: required.method.not.called + Socket socket = makeSocket(address, port); + } + + void ifElseWithDeclaration(String address, int port, boolean b) { + Socket s1; + Socket s2; + try { + if (b) { + s1 = new Socket(address, port); + s1.close(); + } else { // :: error: required.method.not.called - Socket socket = makeSocket(address, port); - } + s2 = new Socket(address, port + 1); + } + } catch (IOException i) { - void ifElseWithDeclaration(String address, int port, boolean b) { - Socket s1; - Socket s2; - try { - if (b) { - s1 = new Socket(address, port); - s1.close(); - } else { - // :: error: required.method.not.called - s2 = new Socket(address, port + 1); - } - } catch (IOException i) { - - } } + } - void testLoop(String address, int port) { - Socket s = null; - while (true) { - try { - s = new Socket(address, port); - s.close(); - } catch (IOException e) { + void testLoop(String address, int port) { + Socket s = null; + while (true) { + try { + s = new Socket(address, port); + s.close(); + } catch (IOException e) { - } - } + } } + } - void overWrittingVarInLoop(String address, int port) { + void overWrittingVarInLoop(String address, int port) { + // :: error: required.method.not.called + Socket s = makeSocket(address, port); + while (true) { + try { // :: error: required.method.not.called - Socket s = makeSocket(address, port); - while (true) { - try { - // :: error: required.method.not.called - s = new Socket(address, port); - } catch (IOException e) { - - } - } - } + s = new Socket(address, port); + } catch (IOException e) { - void loopWithNestedBranches(String address, int port, boolean b) { - Socket s = null; - while (true) { - if (b) { - // :: error: required.method.not.called - s = makeSocket(address, port); - } else { - // :: error: required.method.not.called - s = makeSocket(address, port); - } - } + } } + } - void replaceVarWithNull(String address, int port, boolean b, boolean c) { - Socket s; - try { - // :: error: required.method.not.called - s = new Socket(address, port); - } catch (IOException e) { - - } - if (b) { - s = null; - } else if (c) { - s = null; - } else { - - } - } - - void ownershipTransfer(String address, int port) { - Socket s1 = null; - try { - // :: error: required.method.not.called - s1 = new Socket(address, port); - } catch (IOException e) { - - } - // It is equally correct to report an error here. - Socket s2 = s1; - if (true) { - closeSocket(s2); - } - } - - void test(String address, int port) { - try { - // :: error: required.method.not.called - Socket socket = new Socket(address, 80); - PrintStream out = new PrintStream(socket.getOutputStream()); - BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - in.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - protected Socket sock; - - // This type.argument error is undesirable, but is a necessary consquence of our approach to - // handling generics in the Must Call Checker, which prevents containers from having - // @MustCall("close") type arguments without errors (in exchange for avoiding many false - // positives on containers that do not have must-call obligations on their component types). - // :: error: type.argument.type.incompatible - void connectToLeader(AtomicReference socket) throws IOException { + void loopWithNestedBranches(String address, int port, boolean b) { + Socket s = null; + while (true) { + if (b) { // :: error: required.method.not.called - if (socket.get() == null) { - throw new IOException("Failed connect to "); - } else { - // :: error: required.method.not.called - sock = socket.get(); - } - } - - Socket createSocket(boolean b, String address, int port) throws IOException { - Socket sock; - if (b) { - // :: error: required.method.not.called - sock = new Socket(address, port); - } else { - // :: error: required.method.not.called - sock = new Socket(address, port); - } - - sock.setSoTimeout(10000); - closeSocket(sock); - return sock; - } - - // @EnsuresCalledMethodsIf(expression = "#1", methods = {"close"}, result = true) - // void closeSocket(Socket sock) { - //// if (sock == null) { - //// return; - //// } - // - // try { - // sock.close(); - // } catch (IOException ie) { - // - // } - // } - - public static void ruok(String host, int port) { - Socket s = null; - try { - s = new Socket(host, port); - } catch (IOException e) { - - } finally { - - try { - s.close(); - } catch (IOException e) { - - } - } - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeSocket(Socket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { - - } - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeServerSocket(ServerSocket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { - - } - } - - void useCloseSocket(String address, int port) throws IOException { - Socket sock = new Socket(address, port); - Socket s = getSocket(sock); - closeSocket(sock); - } - - void setSockOpts(Socket sock) throws SocketException { - sock.setTcpNoDelay(true); - sock.setKeepAlive(true); - sock.setSoTimeout(1000); - } - - void initiateConnection( - SocketAddress endpoint, int timeout, SSLContext context, final Long sid) { - Socket sock = null; - try { - sock = context.getSocketFactory().createSocket(); - setSockOpts(sock); - sock.connect(endpoint, timeout); - if (sock instanceof SSLSocket) { - SSLSocket sslSock = (SSLSocket) sock; - sslSock.startHandshake(); - } - } catch (ClassCastException e) { - closeSocket(sock); - return; - } catch (IOException e) { - closeSocket(sock); - return; - } - - try { - startConnection(sock); - } catch (IOException e) { - closeSocket(sock); - } - } - - private boolean startConnection(@Owning Socket s) throws IOException { - closeSocket(s); - return true; - } - - private boolean startConnection(@Owning SSLSocket s) throws IOException { - closeSocket(s); - return true; - } - - @MustCall({"close"}) class PrependableSocket extends Socket { - - public PrependableSocket(SocketImpl base) throws IOException { - super(base); - } - } - - void makePrependableSocket() throws IOException { + s = makeSocket(address, port); + } else { // :: error: required.method.not.called - final PrependableSocket prependableSocket = new PrependableSocket(null); - } - - // private void acceptConnections() { - // int numRetries = 0; - // Socket client = null; - // - // while ((!shutdown) && (portBindMaxRetry == 0 || numRetries < portBindMaxRetry)) { - // try { - // serverSocket = createNewServerSocket(); - // LOG.info("{} is accepting connections now, my election bind port: {}", - // QuorumCnxManager.this.mySid, address.toString()); - // while (!shutdown) { - // try { - // client = serverSocket.accept(); - // setSockOpts(client); - // LOG.info("Received connection request from {}", - // client.getRemoteSocketAddress()); - // // Receive and handle the connection request - // // asynchronously if the quorum sasl authentication is - // // enabled. This is required because sasl server - // // authentication process may take few seconds to finish, - // // this may delay next peer connection requests. - // if (quorumSaslAuthEnabled) { - // receiveConnectionAsync(client); - // } else { - // receiveConnection(client); - // } - // numRetries = 0; - // } catch (SocketTimeoutException e) { - // LOG.warn("The socket is listening for the election accepted " - // + "and it timed out unexpectedly, but will retry." - // + "see ZOOKEEPER-2836"); - // } - // } - // } catch (IOException e) { - // if (shutdown) { - // break; - // } - // - // LOG.error("Exception while listening", e); - // - // if (e instanceof SocketException) { - // socketException.set(true); - // } - // - // numRetries++; - // try { - // close(); - // Thread.sleep(1000); - // } catch (IOException ie) { - // LOG.error("Error closing server socket", ie); - // } catch (InterruptedException ie) { - // LOG.error("Interrupted while sleeping. Ignoring exception", ie); - // } - // closeSocket(client); - // } - // } - // if (!shutdown) { - // LOG.error( - // "Leaving listener thread for address {} after {} errors. Use {} property - // to - // increase retry count.", - // formatInetAddr(address), - // numRetries, - // ELECTION_PORT_BIND_RETRY); - // } - // } - - void createNewServerSocket(InetSocketAddress address, boolean b, boolean c) throws IOException { - ServerSocket socket; - - if (b) { - socket = new ServerSocket(); - } else if (c) { - socket = new ServerSocket(); - } else { - socket = new ServerSocket(); - } - - socket.setReuseAddress(true); - socket.bind(address); - closeServerSocket(socket); - } - - @Owning - public SSLServerSocket createSSLServerSocket(SSLContext sslContext) throws IOException { - SSLServerSocket sslServerSocket = - (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); - return configureSSLServerSocket(sslServerSocket); - } - - private SSLServerSocket nonOwningSSField; - - void assignToNonOwningViaCast(SSLContext sslContext) throws IOException { - nonOwningSSField = - (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); - } - - private SSLServerSocket configureSSLServerSocket(@Owning SSLServerSocket socket) { - return socket; - } - - public SSLSocket createSSLSocket( - @Owning Socket socket, byte[] pushbackBytes, SSLContext sslContext) throws IOException { - SSLSocket sslSocket; - if (pushbackBytes != null && pushbackBytes.length > 0) { - sslSocket = - (SSLSocket) - sslContext - .getSocketFactory() - .createSocket(socket, null, socket.getPort(), true); - } else { - sslSocket = - (SSLSocket) - sslContext - .getSocketFactory() - .createSocket(socket, null, socket.getPort(), true); - } - return configureSSLSocket(sslSocket, false); - } - - private SSLSocket configureSSLSocket(@Owning SSLSocket socket, boolean isClientSocket) { - SSLParameters sslParameters = socket.getSSLParameters(); - // configureSslParameters(sslParameters, isClientSocket); - socket.setSSLParameters(sslParameters); - socket.setUseClientMode(isClientSocket); - return socket; - } - - private void updateSocketAddresses(SelectionKey sockKey) { - // no error here as SelectionKey.channel()'s return is @NotOwning - Socket socket = ((SocketChannel) sockKey.channel()).socket(); - SocketAddress localSocketAddress = socket.getLocalSocketAddress(); - SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); - } - - private void recieverParameterWithCasting(@Owning SelectableChannel channel1) - throws IOException { - try { - ((SocketChannel) channel1).socket(); - } finally { - channel1.close(); - } - } - - @NotOwning - Socket getSocket(Socket s) { - return s; - } - - private ServerSocket testMCAParamInReturn() throws IOException { - ServerSocketChannel chan = ServerSocketChannel.open(); - return chan.socket(); - } - - private void testMCAParamInReturn2() throws IOException { - ServerSocket chan = ServerSocketChannel.open().socket(); - } + s = makeSocket(address, port); + } + } + } + + void replaceVarWithNull(String address, int port, boolean b, boolean c) { + Socket s; + try { + // :: error: required.method.not.called + s = new Socket(address, port); + } catch (IOException e) { + + } + if (b) { + s = null; + } else if (c) { + s = null; + } else { + + } + } + + void ownershipTransfer(String address, int port) { + Socket s1 = null; + try { + // :: error: required.method.not.called + s1 = new Socket(address, port); + } catch (IOException e) { + + } + // It is equally correct to report an error here. + Socket s2 = s1; + if (true) { + closeSocket(s2); + } + } + + void test(String address, int port) { + try { + // :: error: required.method.not.called + Socket socket = new Socket(address, 80); + PrintStream out = new PrintStream(socket.getOutputStream()); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected Socket sock; + + // This type.argument error is undesirable, but is a necessary consquence of our approach to + // handling generics in the Must Call Checker, which prevents containers from having + // @MustCall("close") type arguments without errors (in exchange for avoiding many false + // positives on containers that do not have must-call obligations on their component types). + // :: error: type.argument.type.incompatible + void connectToLeader(AtomicReference socket) throws IOException { + // :: error: required.method.not.called + if (socket.get() == null) { + throw new IOException("Failed connect to "); + } else { + // :: error: required.method.not.called + sock = socket.get(); + } + } + + Socket createSocket(boolean b, String address, int port) throws IOException { + Socket sock; + if (b) { + // :: error: required.method.not.called + sock = new Socket(address, port); + } else { + // :: error: required.method.not.called + sock = new Socket(address, port); + } + + sock.setSoTimeout(10000); + closeSocket(sock); + return sock; + } + + // @EnsuresCalledMethodsIf(expression = "#1", methods = {"close"}, result = true) + // void closeSocket(Socket sock) { + //// if (sock == null) { + //// return; + //// } + // + // try { + // sock.close(); + // } catch (IOException ie) { + // + // } + // } + + public static void ruok(String host, int port) { + Socket s = null; + try { + s = new Socket(host, port); + } catch (IOException e) { + + } finally { + + try { + s.close(); + } catch (IOException e) { + + } + } + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeSocket(Socket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { + + } + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeServerSocket(ServerSocket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { + + } + } + + void useCloseSocket(String address, int port) throws IOException { + Socket sock = new Socket(address, port); + Socket s = getSocket(sock); + closeSocket(sock); + } + + void setSockOpts(Socket sock) throws SocketException { + sock.setTcpNoDelay(true); + sock.setKeepAlive(true); + sock.setSoTimeout(1000); + } + + void initiateConnection(SocketAddress endpoint, int timeout, SSLContext context, final Long sid) { + Socket sock = null; + try { + sock = context.getSocketFactory().createSocket(); + setSockOpts(sock); + sock.connect(endpoint, timeout); + if (sock instanceof SSLSocket) { + SSLSocket sslSock = (SSLSocket) sock; + sslSock.startHandshake(); + } + } catch (ClassCastException e) { + closeSocket(sock); + return; + } catch (IOException e) { + closeSocket(sock); + return; + } + + try { + startConnection(sock); + } catch (IOException e) { + closeSocket(sock); + } + } + + private boolean startConnection(@Owning Socket s) throws IOException { + closeSocket(s); + return true; + } + + private boolean startConnection(@Owning SSLSocket s) throws IOException { + closeSocket(s); + return true; + } + + @MustCall({"close"}) class PrependableSocket extends Socket { + + public PrependableSocket(SocketImpl base) throws IOException { + super(base); + } + } + + void makePrependableSocket() throws IOException { + // :: error: required.method.not.called + final PrependableSocket prependableSocket = new PrependableSocket(null); + } + + // private void acceptConnections() { + // int numRetries = 0; + // Socket client = null; + // + // while ((!shutdown) && (portBindMaxRetry == 0 || numRetries < portBindMaxRetry)) { + // try { + // serverSocket = createNewServerSocket(); + // LOG.info("{} is accepting connections now, my election bind port: {}", + // QuorumCnxManager.this.mySid, address.toString()); + // while (!shutdown) { + // try { + // client = serverSocket.accept(); + // setSockOpts(client); + // LOG.info("Received connection request from {}", + // client.getRemoteSocketAddress()); + // // Receive and handle the connection request + // // asynchronously if the quorum sasl authentication is + // // enabled. This is required because sasl server + // // authentication process may take few seconds to finish, + // // this may delay next peer connection requests. + // if (quorumSaslAuthEnabled) { + // receiveConnectionAsync(client); + // } else { + // receiveConnection(client); + // } + // numRetries = 0; + // } catch (SocketTimeoutException e) { + // LOG.warn("The socket is listening for the election accepted " + // + "and it timed out unexpectedly, but will retry." + // + "see ZOOKEEPER-2836"); + // } + // } + // } catch (IOException e) { + // if (shutdown) { + // break; + // } + // + // LOG.error("Exception while listening", e); + // + // if (e instanceof SocketException) { + // socketException.set(true); + // } + // + // numRetries++; + // try { + // close(); + // Thread.sleep(1000); + // } catch (IOException ie) { + // LOG.error("Error closing server socket", ie); + // } catch (InterruptedException ie) { + // LOG.error("Interrupted while sleeping. Ignoring exception", ie); + // } + // closeSocket(client); + // } + // } + // if (!shutdown) { + // LOG.error( + // "Leaving listener thread for address {} after {} errors. Use {} property + // to + // increase retry count.", + // formatInetAddr(address), + // numRetries, + // ELECTION_PORT_BIND_RETRY); + // } + // } + + void createNewServerSocket(InetSocketAddress address, boolean b, boolean c) throws IOException { + ServerSocket socket; + + if (b) { + socket = new ServerSocket(); + } else if (c) { + socket = new ServerSocket(); + } else { + socket = new ServerSocket(); + } + + socket.setReuseAddress(true); + socket.bind(address); + closeServerSocket(socket); + } + + @Owning + public SSLServerSocket createSSLServerSocket(SSLContext sslContext) throws IOException { + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); + return configureSSLServerSocket(sslServerSocket); + } + + private SSLServerSocket nonOwningSSField; + + void assignToNonOwningViaCast(SSLContext sslContext) throws IOException { + nonOwningSSField = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); + } + + private SSLServerSocket configureSSLServerSocket(@Owning SSLServerSocket socket) { + return socket; + } + + public SSLSocket createSSLSocket( + @Owning Socket socket, byte[] pushbackBytes, SSLContext sslContext) throws IOException { + SSLSocket sslSocket; + if (pushbackBytes != null && pushbackBytes.length > 0) { + sslSocket = + (SSLSocket) + sslContext.getSocketFactory().createSocket(socket, null, socket.getPort(), true); + } else { + sslSocket = + (SSLSocket) + sslContext.getSocketFactory().createSocket(socket, null, socket.getPort(), true); + } + return configureSSLSocket(sslSocket, false); + } + + private SSLSocket configureSSLSocket(@Owning SSLSocket socket, boolean isClientSocket) { + SSLParameters sslParameters = socket.getSSLParameters(); + // configureSslParameters(sslParameters, isClientSocket); + socket.setSSLParameters(sslParameters); + socket.setUseClientMode(isClientSocket); + return socket; + } + + private void updateSocketAddresses(SelectionKey sockKey) { + // no error here as SelectionKey.channel()'s return is @NotOwning + Socket socket = ((SocketChannel) sockKey.channel()).socket(); + SocketAddress localSocketAddress = socket.getLocalSocketAddress(); + SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); + } + + private void recieverParameterWithCasting(@Owning SelectableChannel channel1) throws IOException { + try { + ((SocketChannel) channel1).socket(); + } finally { + channel1.close(); + } + } + + @NotOwning + Socket getSocket(Socket s) { + return s; + } + + private ServerSocket testMCAParamInReturn() throws IOException { + ServerSocketChannel chan = ServerSocketChannel.open(); + return chan.socket(); + } + + private void testMCAParamInReturn2() throws IOException { + ServerSocket chan = ServerSocketChannel.open().socket(); + } } diff --git a/checker/tests/resourceleak/AccumulationValueFieldTest.java b/checker/tests/resourceleak/AccumulationValueFieldTest.java index e145aa32426..51afe3b63b4 100644 --- a/checker/tests/resourceleak/AccumulationValueFieldTest.java +++ b/checker/tests/resourceleak/AccumulationValueFieldTest.java @@ -5,36 +5,36 @@ public class AccumulationValueFieldTest { - @InheritableMustCall({"a"}) - class MCAB { - void a() {} + @InheritableMustCall({"a"}) + class MCAB { + void a() {} - void b() {} + void b() {} + } + + @InheritableMustCall({"a"}) + class FieldTest { + + @Owning + @MustCall({"a"}) T m = null; + + FieldTest(@Owning @MustCall({"a"}) T mcab) { + m = mcab; + } + + @RequiresCalledMethods( + value = {"this.m"}, + methods = {"a"}) + @CreatesMustCallFor("this") + void overwriteMCorrect(@Owning @MustCall({"a"}) T mcab) { + this.m = mcab; } - @InheritableMustCall({"a"}) - class FieldTest { - - @Owning - @MustCall({"a"}) T m = null; - - FieldTest(@Owning @MustCall({"a"}) T mcab) { - m = mcab; - } - - @RequiresCalledMethods( - value = {"this.m"}, - methods = {"a"}) - @CreatesMustCallFor("this") - void overwriteMCorrect(@Owning @MustCall({"a"}) T mcab) { - this.m = mcab; - } - - @EnsuresCalledMethods( - value = {"this.m"}, - methods = {"a"}) - void a() { - m.a(); - } + @EnsuresCalledMethods( + value = {"this.m"}, + methods = {"a"}) + void a() { + m.a(); } + } } diff --git a/checker/tests/resourceleak/AccumulationValueTest.java b/checker/tests/resourceleak/AccumulationValueTest.java index ddd99fdf11d..373db0ee398 100644 --- a/checker/tests/resourceleak/AccumulationValueTest.java +++ b/checker/tests/resourceleak/AccumulationValueTest.java @@ -5,93 +5,93 @@ public class AccumulationValueTest { - @InheritableMustCall({"a", "b"}) - class MCAB { - void a() {} + @InheritableMustCall({"a", "b"}) + class MCAB { + void a() {} - void b() {} + void b() {} - void c() {} - } + void c() {} + } - void simple1(@Owning @MustCall({"a", "b"}) T mcab) { - // test that an accumulation value can accumulate more than one item - mcab.a(); - mcab.b(); - } + void simple1(@Owning @MustCall({"a", "b"}) T mcab) { + // test that an accumulation value can accumulate more than one item + mcab.a(); + mcab.b(); + } - // :: error: required.method.not.called - void simple2(@Owning @MustCall({"a", "b"}) T mcab) { - // test that the RLC handles missing call to a() - mcab.b(); - } + // :: error: required.method.not.called + void simple2(@Owning @MustCall({"a", "b"}) T mcab) { + // test that the RLC handles missing call to a() + mcab.b(); + } - void simple3(@Owning @MustCall({"a", "b"}) T mcab) { - // test that an accumulation value can accumulate extra items without issue (this tests - // mostSpecific) - mcab.a(); - mcab.b(); - mcab.c(); - } + void simple3(@Owning @MustCall({"a", "b"}) T mcab) { + // test that an accumulation value can accumulate extra items without issue (this tests + // mostSpecific) + mcab.a(); + mcab.b(); + mcab.c(); + } - // :: error: required.method.not.called - void lub1(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join - if (b) { - mcab.a(); - } - mcab.b(); + // :: error: required.method.not.called + void lub1(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join + if (b) { + mcab.a(); } + mcab.b(); + } - void lub2(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join - if (b) mcab.a(); - else mcab.a(); - mcab.b(); - } + void lub2(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join + if (b) mcab.a(); + else mcab.a(); + mcab.b(); + } - // :: error: required.method.not.called - void lub3(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join if both are non-empty but non-intersecting - if (b) { - mcab.a(); - } else { - mcab.b(); - } + // :: error: required.method.not.called + void lub3(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join if both are non-empty but non-intersecting + if (b) { + mcab.a(); + } else { + mcab.b(); } + } - // :: error: required.method.not.called - void lub4(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting - if (b) { - mcab.a(); - mcab.c(); - } else { - mcab.a(); - mcab.b(); - } + // :: error: required.method.not.called + void lub4(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting + if (b) { + mcab.a(); + mcab.c(); + } else { + mcab.a(); + mcab.b(); } + } - void lub5(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting - if (b) { - mcab.a(); - mcab.b(); - mcab.c(); - } else { - mcab.a(); - mcab.b(); - } + void lub5(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting + if (b) { + mcab.a(); + mcab.b(); + mcab.c(); + } else { + mcab.a(); + mcab.b(); } + } - // These two paired methods show what happens when the @MustCall type is "too small": - // errors at call sites. - void wrongMCAnno(@Owning @MustCall({"a"}) T mcab) { - mcab.a(); - } + // These two paired methods show what happens when the @MustCall type is "too small": + // errors at call sites. + void wrongMCAnno(@Owning @MustCall({"a"}) T mcab) { + mcab.a(); + } - void wrongMCAnnoUse(@Owning MCAB mcab) { - // :: error: argument.type.incompatible - wrongMCAnno(mcab); - } + void wrongMCAnnoUse(@Owning MCAB mcab) { + // :: error: argument.type.incompatible + wrongMCAnno(mcab); + } } diff --git a/checker/tests/resourceleak/BindChannel.java b/checker/tests/resourceleak/BindChannel.java index 5db5525fabc..a43e1cb008c 100644 --- a/checker/tests/resourceleak/BindChannel.java +++ b/checker/tests/resourceleak/BindChannel.java @@ -5,36 +5,36 @@ import java.nio.channels.*; class BindChannel { - static void test(InetSocketAddress addr, boolean b) { - try { - // This channel is bound - so even with unconnected socket support, we need to - // treat either this channel or the .socket() expression as must-close. - // - // Even though there's now a temporary in the Must Call Checker for the value that - // has the reset method (bind) called on it below, we can't successfully translate - // the reset expression to that temporary, since all we have is a string (from the - // reset annotation) and so we have to go through the type factory's parsing facility, - // which doesn't know about the temporaries and so doesn't return them. We're therefore - // limited to issuing the reset.not.owning error below, - // instead of the preferable required.method.not.called error on this line - as in - // the method below, which extracts the socket into a local variable, which can be - // parsed as an CO target. - ServerSocketChannel httpChannel = ServerSocketChannel.open(); - // :: error: reset.not.owning - httpChannel.socket().bind(addr); - } catch (IOException io) { + static void test(InetSocketAddress addr, boolean b) { + try { + // This channel is bound - so even with unconnected socket support, we need to + // treat either this channel or the .socket() expression as must-close. + // + // Even though there's now a temporary in the Must Call Checker for the value that + // has the reset method (bind) called on it below, we can't successfully translate + // the reset expression to that temporary, since all we have is a string (from the + // reset annotation) and so we have to go through the type factory's parsing facility, + // which doesn't know about the temporaries and so doesn't return them. We're therefore + // limited to issuing the reset.not.owning error below, + // instead of the preferable required.method.not.called error on this line - as in + // the method below, which extracts the socket into a local variable, which can be + // parsed as an CO target. + ServerSocketChannel httpChannel = ServerSocketChannel.open(); + // :: error: reset.not.owning + httpChannel.socket().bind(addr); + } catch (IOException io) { - } } + } - static void test_lv(InetSocketAddress addr, boolean b) { - try { - ServerSocketChannel httpChannel = ServerSocketChannel.open(); - // :: error: required.method.not.called - ServerSocket httpSock = httpChannel.socket(); - httpSock.bind(addr); - } catch (IOException io) { + static void test_lv(InetSocketAddress addr, boolean b) { + try { + ServerSocketChannel httpChannel = ServerSocketChannel.open(); + // :: error: required.method.not.called + ServerSocket httpSock = httpChannel.socket(); + httpSock.bind(addr); + } catch (IOException io) { - } } + } } diff --git a/checker/tests/resourceleak/COAnonymousClass.java b/checker/tests/resourceleak/COAnonymousClass.java index 2efbccf4a23..0171ff81c10 100644 --- a/checker/tests/resourceleak/COAnonymousClass.java +++ b/checker/tests/resourceleak/COAnonymousClass.java @@ -3,58 +3,58 @@ import org.checkerframework.checker.mustcall.qual.*; class COAnonymousClass { - @InheritableMustCall("foo") - static class Foo { - - void foo() {} - - @CreatesMustCallFor("this") - void resetFoo() {} - - void other() { - - Runnable r = - new Runnable() { - @Override - @CreatesMustCallFor("Foo.this") - // :: error: creates.mustcall.for.invalid.target - // :: error: creates.mustcall.for.override.invalid - public void run() { - // [The following explanation is incorrect. The problem is a bug in - // creating implicit "this" expressions.] - // Ideally, we would not issue the following error. However, the Checker - // Framework's JavaExpression support - // (https://eisop.github.io/cf/manual/#java-expressions-as-arguments) - // treats all versions of "this" (including "Foo.this") as referring to - // the object that directly contains the annotation, so we treat this - // call to resetFoo as not permitted. - // :: error: (reset.not.owning) - resetFoo(); - } - }; - call_run(r); - } - - void other2() { - - Runnable r = - new Runnable() { - @Override - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - // :: error: creates.mustcall.for.override.invalid - public void run() { - // This error definitely must be issued, since Foo.this != this. - // :: error: reset.not.owning - resetFoo(); - } - }; - call_run(r); - } - - // If this call to run() were permitted with no errors, this would be unsound. - void call_run(Runnable r) { - r.run(); - } + @InheritableMustCall("foo") + static class Foo { + + void foo() {} + + @CreatesMustCallFor("this") + void resetFoo() {} + + void other() { + + Runnable r = + new Runnable() { + @Override + @CreatesMustCallFor("Foo.this") + // :: error: creates.mustcall.for.invalid.target + // :: error: creates.mustcall.for.override.invalid + public void run() { + // [The following explanation is incorrect. The problem is a bug in + // creating implicit "this" expressions.] + // Ideally, we would not issue the following error. However, the Checker + // Framework's JavaExpression support + // (https://eisop.github.io/cf/manual/#java-expressions-as-arguments) + // treats all versions of "this" (including "Foo.this") as referring to + // the object that directly contains the annotation, so we treat this + // call to resetFoo as not permitted. + // :: error: (reset.not.owning) + resetFoo(); + } + }; + call_run(r); } + + void other2() { + + Runnable r = + new Runnable() { + @Override + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + // :: error: creates.mustcall.for.override.invalid + public void run() { + // This error definitely must be issued, since Foo.this != this. + // :: error: reset.not.owning + resetFoo(); + } + }; + call_run(r); + } + + // If this call to run() were permitted with no errors, this would be unsound. + void call_run(Runnable r) { + r.run(); + } + } } diff --git a/checker/tests/resourceleak/COInSubtype.java b/checker/tests/resourceleak/COInSubtype.java index 13ff2d5b52f..3f67ca3769a 100644 --- a/checker/tests/resourceleak/COInSubtype.java +++ b/checker/tests/resourceleak/COInSubtype.java @@ -5,25 +5,25 @@ import org.checkerframework.checker.mustcall.qual.*; class COInSubtype { - static class Foo { + static class Foo { - void foo() {} + void foo() {} - // This is not supported, even though a sub-class may have must-call obligations. - // This pattern is not used in realistic code, and supporting it hurts checker performance. - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - void resetFoo() {} - } + // This is not supported, even though a sub-class may have must-call obligations. + // This pattern is not used in realistic code, and supporting it hurts checker performance. + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + void resetFoo() {} + } - @InheritableMustCall("a") - static class Bar extends Foo { - void a() {} - } + @InheritableMustCall("a") + static class Bar extends Foo { + void a() {} + } - static void test() { - // :: error: required.method.not.called - @MustCall("a") Foo f = new Bar(); - f.resetFoo(); - } + static void test() { + // :: error: required.method.not.called + @MustCall("a") Foo f = new Bar(); + f.resetFoo(); + } } diff --git a/checker/tests/resourceleak/CheckFields.java b/checker/tests/resourceleak/CheckFields.java index 62e1872d99b..c135a60a7a0 100644 --- a/checker/tests/resourceleak/CheckFields.java +++ b/checker/tests/resourceleak/CheckFields.java @@ -4,168 +4,168 @@ class CheckFields { - @InheritableMustCall("a") - static class Foo { - void a() {} - - void c() {} + @InheritableMustCall("a") + static class Foo { + void a() {} + + void c() {} + } + + Foo makeFoo() { + return new Foo(); + } + + @InheritableMustCall("b") + static class FooField { + private final @Owning Foo finalOwningFoo; + // :: error: required.method.not.called + private final @Owning Foo finalOwningFooWrong; + private final Foo finalNotOwningFoo; + private @Owning Foo owningFoo; + private @Owning @MustCall({}) Foo owningEmptyMustCallFoo; + private Foo notOwningFoo; + + public FooField() { + this.finalOwningFoo = new Foo(); + this.finalOwningFooWrong = new Foo(); + // :: error: required.method.not.called + this.finalNotOwningFoo = new Foo(); } - Foo makeFoo() { - return new Foo(); + @CreatesMustCallFor + void assingToOwningFieldWrong() { + Foo f = new Foo(); + // :: error: required.method.not.called + this.owningFoo = f; } - @InheritableMustCall("b") - static class FooField { - private final @Owning Foo finalOwningFoo; - // :: error: required.method.not.called - private final @Owning Foo finalOwningFooWrong; - private final Foo finalNotOwningFoo; - private @Owning Foo owningFoo; - private @Owning @MustCall({}) Foo owningEmptyMustCallFoo; - private Foo notOwningFoo; - - public FooField() { - this.finalOwningFoo = new Foo(); - this.finalOwningFooWrong = new Foo(); - // :: error: required.method.not.called - this.finalNotOwningFoo = new Foo(); - } - - @CreatesMustCallFor - void assingToOwningFieldWrong() { - Foo f = new Foo(); - // :: error: required.method.not.called - this.owningFoo = f; - } - - @CreatesMustCallFor - void assignToOwningFieldWrong2() { - // :: error: required.method.not.called - this.owningFoo = new Foo(); - } - - @CreatesMustCallFor - void assingToOwningField() { - // this is a safe re-assignment. - if (this.owningFoo == null) { - Foo f = new Foo(); - this.owningFoo = f; - } - } - - void assingToFinalNotOwningField() { - // :: error: required.method.not.called - Foo f = new Foo(); - this.notOwningFoo = f; - } - - Foo getOwningFoo() { - return this.owningFoo; - } - - @EnsuresCalledMethods( - value = {"this.finalOwningFoo", "this.owningFoo"}, - methods = {"a"}) - void b() { - this.finalOwningFoo.a(); - this.finalOwningFoo.c(); - this.owningFoo.a(); - } + @CreatesMustCallFor + void assignToOwningFieldWrong2() { + // :: error: required.method.not.called + this.owningFoo = new Foo(); } - void testField() { - FooField fooField = new FooField(); - fooField.b(); + @CreatesMustCallFor + void assingToOwningField() { + // this is a safe re-assignment. + if (this.owningFoo == null) { + Foo f = new Foo(); + this.owningFoo = f; + } } - void testAccessField() { - FooField fooField = new FooField(); - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - fooField.b(); + void assingToFinalNotOwningField() { + // :: error: required.method.not.called + Foo f = new Foo(); + this.notOwningFoo = f; } - void testAccessField2() { - FooField fooField = new FooField(); - if (fooField.owningFoo == null) { - fooField.owningFoo = new Foo(); - } - fooField.b(); + Foo getOwningFoo() { + return this.owningFoo; } - void testAccessFieldWrong() { - // :: error: required.method.not.called - FooField fooField = new FooField(); - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - // :: error: required.method.not.called - fooField.notOwningFoo = new Foo(); + @EnsuresCalledMethods( + value = {"this.finalOwningFoo", "this.owningFoo"}, + methods = {"a"}) + void b() { + this.finalOwningFoo.a(); + this.finalOwningFoo.c(); + this.owningFoo.a(); } - - @CreatesMustCallFor("#1") - void testAccessField_param(FooField fooField) { - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - fooField.b(); + } + + void testField() { + FooField fooField = new FooField(); + fooField.b(); + } + + void testAccessField() { + FooField fooField = new FooField(); + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + fooField.b(); + } + + void testAccessField2() { + FooField fooField = new FooField(); + if (fooField.owningFoo == null) { + fooField.owningFoo = new Foo(); } - - // :: error: missing.creates.mustcall.for - void testAccessField_param_no_co(FooField fooField) { - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - fooField.b(); + fooField.b(); + } + + void testAccessFieldWrong() { + // :: error: required.method.not.called + FooField fooField = new FooField(); + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + // :: error: required.method.not.called + fooField.notOwningFoo = new Foo(); + } + + @CreatesMustCallFor("#1") + void testAccessField_param(FooField fooField) { + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + fooField.b(); + } + + // :: error: missing.creates.mustcall.for + void testAccessField_param_no_co(FooField fooField) { + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + fooField.b(); + } + + static class NestedWrong { + + // Non-final owning fields also require the surrounding class to have an appropriate MC + // annotation. + // :: error: required.method.not.called + @Owning Foo foo; + + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } + } + } + + @InheritableMustCall("f") + static class NestedWrong2 { + // Non-final owning fields also require the surrounding class to have an appropriate MC + // annotation. + // :: error: required.method.not.called + @Owning Foo foo; + + @CreatesMustCallFor("this") + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } } - static class NestedWrong { - - // Non-final owning fields also require the surrounding class to have an appropriate MC - // annotation. - // :: error: required.method.not.called - @Owning Foo foo; + void f() {} + } - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } - } - } + @InheritableMustCall("f") + static class NestedRight { + // Non-final owning fields also require the surrounding class to have an appropriate MC + // annotation. + @Owning Foo foo; - @InheritableMustCall("f") - static class NestedWrong2 { - // Non-final owning fields also require the surrounding class to have an appropriate MC - // annotation. - // :: error: required.method.not.called - @Owning Foo foo; - - @CreatesMustCallFor("this") - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } - } - - void f() {} + @CreatesMustCallFor("this") + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } } - @InheritableMustCall("f") - static class NestedRight { - // Non-final owning fields also require the surrounding class to have an appropriate MC - // annotation. - @Owning Foo foo; - - @CreatesMustCallFor("this") - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } - } - - @EnsuresCalledMethods(value = "this.foo", methods = "a") - void f() { - this.foo.a(); - } + @EnsuresCalledMethods(value = "this.foo", methods = "a") + void f() { + this.foo.a(); } + } } diff --git a/checker/tests/resourceleak/CloseSuper.java b/checker/tests/resourceleak/CloseSuper.java index 3636436a79d..b37fc0b5d91 100644 --- a/checker/tests/resourceleak/CloseSuper.java +++ b/checker/tests/resourceleak/CloseSuper.java @@ -1,40 +1,39 @@ // Test case for https://github.com/typetools/checker-framework/issues/6204 -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.Closeable; import java.io.IOException; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.mustcall.qual.Owning; public class CloseSuper { - public static class A implements Closeable { - private final @Owning Closeable resource; + public static class A implements Closeable { + private final @Owning Closeable resource; - public A(@Owning Closeable resource) { - this.resource = resource; - } + public A(@Owning Closeable resource) { + this.resource = resource; + } - @Override - @EnsuresCalledMethods( - value = "resource", - methods = {"close"}) - public void close() throws IOException { - resource.close(); - } + @Override + @EnsuresCalledMethods( + value = "resource", + methods = {"close"}) + public void close() throws IOException { + resource.close(); } + } - public static class B extends A { - public B(@Owning Closeable resource) { - super(resource); - } + public static class B extends A { + public B(@Owning Closeable resource) { + super(resource); + } - @Override - @EnsuresCalledMethods( - value = "resource", - methods = {"close"}) - public void close() throws IOException { - super.close(); - } + @Override + @EnsuresCalledMethods( + value = "resource", + methods = {"close"}) + public void close() throws IOException { + super.close(); } + } } diff --git a/checker/tests/resourceleak/CloseableAndMore.java b/checker/tests/resourceleak/CloseableAndMore.java index 7237d45ab3c..838f8cd0a39 100644 --- a/checker/tests/resourceleak/CloseableAndMore.java +++ b/checker/tests/resourceleak/CloseableAndMore.java @@ -1,31 +1,30 @@ // A test that when a class implements autocloseable and has another must-call obligation, // errors are still issued about the other obligation even when it used as a resource variable. -import org.checkerframework.checker.mustcall.qual.InheritableMustCall; - import java.io.IOException; +import org.checkerframework.checker.mustcall.qual.InheritableMustCall; @SuppressWarnings( - "declaration.inconsistent.with.implements.clause") // stronger @InheritableMustCall + "declaration.inconsistent.with.implements.clause") // stronger @InheritableMustCall @InheritableMustCall({"close", "foo"}) public class CloseableAndMore implements AutoCloseable { - void foo() {} + void foo() {} - @Override - public void close() throws IOException {} + @Override + public void close() throws IOException {} - public static void test_bad() { - // :: error: required.method.not.called - try (CloseableAndMore c = new CloseableAndMore()) { - // empty body - } catch (Exception e) { - } + public static void test_bad() { + // :: error: required.method.not.called + try (CloseableAndMore c = new CloseableAndMore()) { + // empty body + } catch (Exception e) { } + } - public static void test_good() { - try (CloseableAndMore c = new CloseableAndMore()) { - c.foo(); - } catch (Exception e) { - } + public static void test_good() { + try (CloseableAndMore c = new CloseableAndMore()) { + c.foo(); + } catch (Exception e) { } + } } diff --git a/checker/tests/resourceleak/CommonModuleCrash.java b/checker/tests/resourceleak/CommonModuleCrash.java index cf3eb5703cb..87b3a92bb87 100644 --- a/checker/tests/resourceleak/CommonModuleCrash.java +++ b/checker/tests/resourceleak/CommonModuleCrash.java @@ -1,11 +1,11 @@ import java.net.*; class CommonModuleCrash { - Socket bar = new Socket(); + Socket bar = new Socket(); - static void baz(Socket s) {} + static void baz(Socket s) {} - static { - baz(new Socket()); - } + static { + baz(new Socket()); + } } diff --git a/checker/tests/resourceleak/ConnectingServerSockets.java b/checker/tests/resourceleak/ConnectingServerSockets.java index 1856d26b31d..c09b8a9845e 100644 --- a/checker/tests/resourceleak/ConnectingServerSockets.java +++ b/checker/tests/resourceleak/ConnectingServerSockets.java @@ -1,47 +1,46 @@ // a set of test cases that demonstrate that errors are actually insued in appropriate // places when ServerSockets are connected -import org.checkerframework.checker.mustcall.qual.*; - import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; class ConnectingServerSockets { - static void simple_ss_test(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - ServerSocket s = new ServerSocket(); - s.bind(sa); - } - - static void simple_ss_test2(SocketAddress sa) throws Exception { - ServerSocket s = new ServerSocket(); - // s.bind(sa); - } - - static void simple_ss_test4(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - ServerSocket s = new ServerSocket(); - s.bind(sa, to); - } - - static @MustCall({}) ServerSocket makeUnconnected() throws Exception { - return new ServerSocket(); - } - - static void simple_ss_test5(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - ServerSocket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ss_test6(SocketAddress sa) throws Exception { - ServerSocket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ss_test8(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - ServerSocket s = makeUnconnected(); - s.bind(sa, to); - } + static void simple_ss_test(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + ServerSocket s = new ServerSocket(); + s.bind(sa); + } + + static void simple_ss_test2(SocketAddress sa) throws Exception { + ServerSocket s = new ServerSocket(); + // s.bind(sa); + } + + static void simple_ss_test4(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + ServerSocket s = new ServerSocket(); + s.bind(sa, to); + } + + static @MustCall({}) ServerSocket makeUnconnected() throws Exception { + return new ServerSocket(); + } + + static void simple_ss_test5(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + ServerSocket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ss_test6(SocketAddress sa) throws Exception { + ServerSocket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ss_test8(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + ServerSocket s = makeUnconnected(); + s.bind(sa, to); + } } diff --git a/checker/tests/resourceleak/ConnectingSockets.java b/checker/tests/resourceleak/ConnectingSockets.java index d91c6b04a25..b433015a3b1 100644 --- a/checker/tests/resourceleak/ConnectingSockets.java +++ b/checker/tests/resourceleak/ConnectingSockets.java @@ -1,59 +1,58 @@ // a set of test cases that demonstrate that errors are actually insued in appropriate // places when Sockets are connected -import org.checkerframework.checker.mustcall.qual.*; - import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; class ConnectingSockets { - static void simple_ns_test(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.bind(sa); - } - - static void simple_ns_test2(SocketAddress sa) throws Exception { - Socket s = new Socket(); - // s.bind(sa); - } - - static void simple_ns_test3(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.connect(sa); - } - - static void simple_ns_test4(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.connect(sa, to); - } - - static @MustCall({}) Socket makeUnconnected() throws Exception { - return new Socket(); - } - - static void simple_ns_test5(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ns_test6(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ns_test7(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = makeUnconnected(); - s.connect(sa); - } - - static void simple_ns_test8(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - Socket s = makeUnconnected(); - s.connect(sa, to); - } + static void simple_ns_test(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.bind(sa); + } + + static void simple_ns_test2(SocketAddress sa) throws Exception { + Socket s = new Socket(); + // s.bind(sa); + } + + static void simple_ns_test3(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.connect(sa); + } + + static void simple_ns_test4(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.connect(sa, to); + } + + static @MustCall({}) Socket makeUnconnected() throws Exception { + return new Socket(); + } + + static void simple_ns_test5(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ns_test6(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ns_test7(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = makeUnconnected(); + s.connect(sa); + } + + static void simple_ns_test8(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + Socket s = makeUnconnected(); + s.connect(sa, to); + } } diff --git a/checker/tests/resourceleak/ConnectingSockets2.java b/checker/tests/resourceleak/ConnectingSockets2.java index 529af682426..73e04e1f786 100644 --- a/checker/tests/resourceleak/ConnectingSockets2.java +++ b/checker/tests/resourceleak/ConnectingSockets2.java @@ -5,12 +5,12 @@ class ConnectingSockets2 { - void run(InetSocketAddress isa) { - try (Socket serverSocket = new Socket()) { - serverSocket.close(); - serverSocket.connect(isa); - } catch (IOException e) { - // do nothing - } + void run(InetSocketAddress isa) { + try (Socket serverSocket = new Socket()) { + serverSocket.close(); + serverSocket.connect(isa); + } catch (IOException e) { + // do nothing } + } } diff --git a/checker/tests/resourceleak/ConstructorAddsMustCall.java b/checker/tests/resourceleak/ConstructorAddsMustCall.java index 319139d2e3a..bd0f14ef2c8 100644 --- a/checker/tests/resourceleak/ConstructorAddsMustCall.java +++ b/checker/tests/resourceleak/ConstructorAddsMustCall.java @@ -4,19 +4,19 @@ import org.checkerframework.checker.mustcall.qual.MustCall; public class ConstructorAddsMustCall { - static class Foo { - void a() {} + static class Foo { + void a() {} - Foo() {} + Foo() {} - @MustCall("a") Foo(String s) {} - } + @MustCall("a") Foo(String s) {} + } - static void useFoo() { - // no obligation for this one - Foo f1 = new Foo(); - // obligation for this one - // :: error: required.method.not.called - Foo f2 = new Foo("hi"); - } + static void useFoo() { + // no obligation for this one + Foo f1 = new Foo(); + // obligation for this one + // :: error: required.method.not.called + Foo f2 = new Foo("hi"); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForIndirect.java b/checker/tests/resourceleak/CreatesMustCallForIndirect.java index c941f6694b4..123f89975f2 100644 --- a/checker/tests/resourceleak/CreatesMustCallForIndirect.java +++ b/checker/tests/resourceleak/CreatesMustCallForIndirect.java @@ -6,58 +6,58 @@ @InheritableMustCall("a") class CreatesMustCallForIndirect { - @CreatesMustCallFor - void reset() {} - - void a() {} - - static @MustCall({}) CreatesMustCallForSimple makeNoMC() { - return null; - } - - public static void resetIndirect_no_anno(CreatesMustCallForIndirect r) { - // :: error: reset.not.owning - r.reset(); - } - - @CreatesMustCallFor("#1") - public static void resetIndirect_anno(CreatesMustCallForIndirect r) { - r.reset(); - } - - public static void reset_local() { - // :: error: required.method.not.called - CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); - r.reset(); - } - - public static void reset_local2() { - CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); - r.reset(); - r.a(); - } - - public static void reset_local3() { - // :: error: required.method.not.called - CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); - // Ideally, we'd issue a reset.not.owning error on the next line instead, but not being able - // to parse the case and requiring it to be in a local var is okay too. - // :: error: createsmustcallfor.target.unparseable - ((CreatesMustCallForIndirect) r).reset(); - } + @CreatesMustCallFor + void reset() {} + void a() {} + + static @MustCall({}) CreatesMustCallForSimple makeNoMC() { + return null; + } + + public static void resetIndirect_no_anno(CreatesMustCallForIndirect r) { + // :: error: reset.not.owning + r.reset(); + } + + @CreatesMustCallFor("#1") + public static void resetIndirect_anno(CreatesMustCallForIndirect r) { + r.reset(); + } + + public static void reset_local() { + // :: error: required.method.not.called + CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); + r.reset(); + } + + public static void reset_local2() { + CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); + r.reset(); + r.a(); + } + + public static void reset_local3() { // :: error: required.method.not.called - public static void test(@Owning CreatesMustCallForIndirect r) { - resetIndirect_anno(r); - } - - public static void test2(CreatesMustCallForIndirect r) { - // :: error: reset.not.owning - resetIndirect_anno(r); - } - - public static void test3(@Owning CreatesMustCallForIndirect r) { - resetIndirect_anno(r); - r.a(); - } + CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); + // Ideally, we'd issue a reset.not.owning error on the next line instead, but not being able + // to parse the case and requiring it to be in a local var is okay too. + // :: error: createsmustcallfor.target.unparseable + ((CreatesMustCallForIndirect) r).reset(); + } + + // :: error: required.method.not.called + public static void test(@Owning CreatesMustCallForIndirect r) { + resetIndirect_anno(r); + } + + public static void test2(CreatesMustCallForIndirect r) { + // :: error: reset.not.owning + resetIndirect_anno(r); + } + + public static void test3(@Owning CreatesMustCallForIndirect r) { + resetIndirect_anno(r); + r.a(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForInnerClass.java b/checker/tests/resourceleak/CreatesMustCallForInnerClass.java index f230f4dc739..91df837f901 100644 --- a/checker/tests/resourceleak/CreatesMustCallForInnerClass.java +++ b/checker/tests/resourceleak/CreatesMustCallForInnerClass.java @@ -3,31 +3,31 @@ import org.checkerframework.checker.mustcall.qual.*; class CreatesMustCallForInnerClass { - @InheritableMustCall("foo") - static class Foo { + @InheritableMustCall("foo") + static class Foo { - void foo() {} + void foo() {} - @CreatesMustCallFor("this") - void resetFoo() {} + @CreatesMustCallFor("this") + void resetFoo() {} - /** non-static inner class */ - class Bar { - @CreatesMustCallFor - // :: error: creates.mustcall.for.invalid.target - void bar() { - // :: error: reset.not.owning - resetFoo(); - } - } + /** non-static inner class */ + class Bar { + @CreatesMustCallFor + // :: error: creates.mustcall.for.invalid.target + void bar() { + // :: error: reset.not.owning + resetFoo(); + } + } - void callBar() { - Bar b = new Bar(); - // If this call to bar() were permitted with no errors, this would be unsound. - // b is in fact an owning pointer, but we don't track it as such because - // Bar objects cannot have must-call obligations created for them. - // :: error: reset.not.owning - b.bar(); - } + void callBar() { + Bar b = new Bar(); + // If this call to bar() were permitted with no errors, this would be unsound. + // b is in fact an owning pointer, but we don't track it as such because + // Bar objects cannot have must-call obligations created for them. + // :: error: reset.not.owning + b.bar(); } + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForOverride.java b/checker/tests/resourceleak/CreatesMustCallForOverride.java index 698b7feca4c..d9145f2b4f5 100644 --- a/checker/tests/resourceleak/CreatesMustCallForOverride.java +++ b/checker/tests/resourceleak/CreatesMustCallForOverride.java @@ -5,28 +5,28 @@ @InheritableMustCall("a") class CreatesMustCallForOverride { - @CreatesMustCallFor - @Override - // :: error: creates.mustcall.for.override.invalid - public String toString() { - return "this method could re-assign a field or do something else it shouldn't"; - } + @CreatesMustCallFor + @Override + // :: error: creates.mustcall.for.override.invalid + public String toString() { + return "this method could re-assign a field or do something else it shouldn't"; + } - public void a() {} + public void a() {} - public static void test_no_cast() { - // :: error: required.method.not.called - CreatesMustCallForOverride co = new CreatesMustCallForOverride(); - co.a(); - co.toString(); - } + public static void test_no_cast() { + // :: error: required.method.not.called + CreatesMustCallForOverride co = new CreatesMustCallForOverride(); + co.a(); + co.toString(); + } - public static void test_cast() { - // it would be ideal if the checker issued an error directly here, but the best we can do is - // issue the error above when the offending version of toString() is defined - CreatesMustCallForOverride co = new CreatesMustCallForOverride(); - co.a(); - Object o = co; - o.toString(); - } + public static void test_cast() { + // it would be ideal if the checker issued an error directly here, but the best we can do is + // issue the error above when the offending version of toString() is defined + CreatesMustCallForOverride co = new CreatesMustCallForOverride(); + co.a(); + Object o = co; + o.toString(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForOverride2.java b/checker/tests/resourceleak/CreatesMustCallForOverride2.java index 28d3d359687..94e47c7adfa 100644 --- a/checker/tests/resourceleak/CreatesMustCallForOverride2.java +++ b/checker/tests/resourceleak/CreatesMustCallForOverride2.java @@ -6,171 +6,171 @@ class CreatesMustCallForOverride2 { - @InheritableMustCall("a") - static class Foo { - - @CreatesMustCallFor - public void b() {} - - public void a() {} - } - - static class Bar extends Foo { - - @Override - @CreatesMustCallFor - public void b() {} - } - - static class Baz extends Foo {} - - static class Qux extends Foo { - @Override - public void b() {} - } - - static class Razz extends Foo { - - public @Owning Foo myFoo; - - @Override - @EnsuresCalledMethods(value = "this.myFoo", methods = "a") - public void a() { - super.a(); - myFoo.a(); - } - - // this version isn't permitted, since it adds a new obligation - @Override - @CreatesMustCallFor("this.myFoo") - // :: error: creates.mustcall.for.override.invalid - public void b() {} - } - - static class Thud extends Foo { - - public @Owning Foo myFoo; - - @Override - @EnsuresCalledMethods(value = "this.myFoo", methods = "a") - public void a() { - super.a(); - myFoo.a(); - } - - // this method isn't permitted, since it's also adding a new obligation - @Override - @CreatesMustCallFor("this.myFoo") - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.override.invalid - public void b() {} - } - - static class Thudless extends Thud { - // this method override is also NOT permitted, because the @CreatesMustCallFor("this.myFoo") - // annotation from Thud is inherited! - @Override - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.override.invalid - public void b() {} - } - - static void test1() { - // :: error: required.method.not.called - Foo foo = new Foo(); - foo.a(); - foo.b(); - } - - static void test2() { - // :: error: required.method.not.called - Foo foo = new Bar(); - foo.a(); - foo.b(); - } - - static void test3() { - // :: error: required.method.not.called - Foo foo = new Baz(); - foo.a(); - foo.b(); - } - - static void test4() { - // :: error: required.method.not.called - Foo foo = new Qux(); - foo.a(); - foo.b(); - } - - static void test5() { - // :: error: required.method.not.called - Bar foo = new Bar(); - foo.a(); - foo.b(); - } - - static void test6() { - // :: error: required.method.not.called - Baz foo = new Baz(); - foo.a(); - foo.b(); - } - - static void test7() { - // :: error: required.method.not.called - Qux foo = new Qux(); - foo.a(); - foo.b(); - } - - static void test8() { - // :: error: required.method.not.called - Foo foo = new Razz(); - foo.a(); - foo.b(); - } - - static void test9() { - // No error is issued here, because Razz#b is *only* @CreatesMustCallFor("this.myFoo"), not - // @CreatesMustCallFor("this"). An error is issued at the declaration of Razz#b instead. - Razz foo = new Razz(); - foo.a(); - foo.b(); - } - - static void test10() { - // :: error: required.method.not.called - Foo foo = new Thud(); - foo.a(); - foo.b(); - } - - static void test11() { - // :: error: required.method.not.called - Thud foo = new Thud(); - foo.a(); - foo.b(); - } - - static void test12() { - // :: error: required.method.not.called - Foo foo = new Thudless(); - foo.a(); - foo.b(); - } - - static void test13() { - // :: error: required.method.not.called - Thud foo = new Thudless(); - foo.a(); - foo.b(); - } - - static void test14() { - // :: error: required.method.not.called - Thudless foo = new Thudless(); - foo.a(); - foo.b(); - } + @InheritableMustCall("a") + static class Foo { + + @CreatesMustCallFor + public void b() {} + + public void a() {} + } + + static class Bar extends Foo { + + @Override + @CreatesMustCallFor + public void b() {} + } + + static class Baz extends Foo {} + + static class Qux extends Foo { + @Override + public void b() {} + } + + static class Razz extends Foo { + + public @Owning Foo myFoo; + + @Override + @EnsuresCalledMethods(value = "this.myFoo", methods = "a") + public void a() { + super.a(); + myFoo.a(); + } + + // this version isn't permitted, since it adds a new obligation + @Override + @CreatesMustCallFor("this.myFoo") + // :: error: creates.mustcall.for.override.invalid + public void b() {} + } + + static class Thud extends Foo { + + public @Owning Foo myFoo; + + @Override + @EnsuresCalledMethods(value = "this.myFoo", methods = "a") + public void a() { + super.a(); + myFoo.a(); + } + + // this method isn't permitted, since it's also adding a new obligation + @Override + @CreatesMustCallFor("this.myFoo") + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.override.invalid + public void b() {} + } + + static class Thudless extends Thud { + // this method override is also NOT permitted, because the @CreatesMustCallFor("this.myFoo") + // annotation from Thud is inherited! + @Override + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.override.invalid + public void b() {} + } + + static void test1() { + // :: error: required.method.not.called + Foo foo = new Foo(); + foo.a(); + foo.b(); + } + + static void test2() { + // :: error: required.method.not.called + Foo foo = new Bar(); + foo.a(); + foo.b(); + } + + static void test3() { + // :: error: required.method.not.called + Foo foo = new Baz(); + foo.a(); + foo.b(); + } + + static void test4() { + // :: error: required.method.not.called + Foo foo = new Qux(); + foo.a(); + foo.b(); + } + + static void test5() { + // :: error: required.method.not.called + Bar foo = new Bar(); + foo.a(); + foo.b(); + } + + static void test6() { + // :: error: required.method.not.called + Baz foo = new Baz(); + foo.a(); + foo.b(); + } + + static void test7() { + // :: error: required.method.not.called + Qux foo = new Qux(); + foo.a(); + foo.b(); + } + + static void test8() { + // :: error: required.method.not.called + Foo foo = new Razz(); + foo.a(); + foo.b(); + } + + static void test9() { + // No error is issued here, because Razz#b is *only* @CreatesMustCallFor("this.myFoo"), not + // @CreatesMustCallFor("this"). An error is issued at the declaration of Razz#b instead. + Razz foo = new Razz(); + foo.a(); + foo.b(); + } + + static void test10() { + // :: error: required.method.not.called + Foo foo = new Thud(); + foo.a(); + foo.b(); + } + + static void test11() { + // :: error: required.method.not.called + Thud foo = new Thud(); + foo.a(); + foo.b(); + } + + static void test12() { + // :: error: required.method.not.called + Foo foo = new Thudless(); + foo.a(); + foo.b(); + } + + static void test13() { + // :: error: required.method.not.called + Thud foo = new Thudless(); + foo.a(); + foo.b(); + } + + static void test14() { + // :: error: required.method.not.called + Thudless foo = new Thudless(); + foo.a(); + foo.b(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForRepeat.java b/checker/tests/resourceleak/CreatesMustCallForRepeat.java index d1c9a849f61..27fb36d3dde 100644 --- a/checker/tests/resourceleak/CreatesMustCallForRepeat.java +++ b/checker/tests/resourceleak/CreatesMustCallForRepeat.java @@ -6,66 +6,66 @@ @InheritableMustCall("a") class CreatesMustCallForRepeat { - @CreatesMustCallFor("this") - @CreatesMustCallFor("#1") - void reset(CreatesMustCallForRepeat r) {} + @CreatesMustCallFor("this") + @CreatesMustCallFor("#1") + void reset(CreatesMustCallForRepeat r) {} - void a() {} + void a() {} - static @MustCall({}) CreatesMustCallForRepeat makeNoMC() { - return null; - } + static @MustCall({}) CreatesMustCallForRepeat makeNoMC() { + return null; + } - static void test1() { - // :: error: required.method.not.called - CreatesMustCallForRepeat cos1 = makeNoMC(); - // :: error: required.method.not.called - CreatesMustCallForRepeat cos2 = makeNoMC(); - @MustCall({}) CreatesMustCallForRepeat a = cos2; - @MustCall({}) CreatesMustCallForRepeat a2 = cos2; - cos2.a(); - cos1.reset(cos2); - // :: error: assignment.type.incompatible - @CalledMethods({"reset"}) CreatesMustCallForRepeat b = cos1; - @CalledMethods({}) CreatesMustCallForRepeat c = cos1; - @CalledMethods({}) CreatesMustCallForRepeat d = cos2; - // :: error: assignment.type.incompatible - @CalledMethods({"a"}) CreatesMustCallForRepeat e = cos2; - } + static void test1() { + // :: error: required.method.not.called + CreatesMustCallForRepeat cos1 = makeNoMC(); + // :: error: required.method.not.called + CreatesMustCallForRepeat cos2 = makeNoMC(); + @MustCall({}) CreatesMustCallForRepeat a = cos2; + @MustCall({}) CreatesMustCallForRepeat a2 = cos2; + cos2.a(); + cos1.reset(cos2); + // :: error: assignment.type.incompatible + @CalledMethods({"reset"}) CreatesMustCallForRepeat b = cos1; + @CalledMethods({}) CreatesMustCallForRepeat c = cos1; + @CalledMethods({}) CreatesMustCallForRepeat d = cos2; + // :: error: assignment.type.incompatible + @CalledMethods({"a"}) CreatesMustCallForRepeat e = cos2; + } - static void test3() { - // :: error: required.method.not.called - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - // :: error: required.method.not.called - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - } + static void test3() { + // :: error: required.method.not.called + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + // :: error: required.method.not.called + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + } - static void test4() { - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - // :: error: required.method.not.called - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - cos.a(); - } + static void test4() { + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + // :: error: required.method.not.called + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + cos.a(); + } - static void test5() { - // :: error: required.method.not.called - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - cos2.a(); - } + static void test5() { + // :: error: required.method.not.called + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + cos2.a(); + } - static void test6() { - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - cos2.a(); - cos.a(); - } + static void test6() { + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + cos2.a(); + cos.a(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForSimple.java b/checker/tests/resourceleak/CreatesMustCallForSimple.java index 4f5829dd5c0..dfa47b7e82a 100644 --- a/checker/tests/resourceleak/CreatesMustCallForSimple.java +++ b/checker/tests/resourceleak/CreatesMustCallForSimple.java @@ -6,72 +6,72 @@ @InheritableMustCall("a") class CreatesMustCallForSimple { - @CreatesMustCallFor - void reset() {} + @CreatesMustCallFor + void reset() {} - @CreatesMustCallFor("this") - void resetThis() {} + @CreatesMustCallFor("this") + void resetThis() {} - void a() {} + void a() {} - static @MustCall({}) CreatesMustCallForSimple makeNoMC() { - return null; - } + static @MustCall({}) CreatesMustCallForSimple makeNoMC() { + return null; + } - static void test1() { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.reset(); - // :: error: assignment.type.incompatible - @CalledMethods({"reset"}) CreatesMustCallForSimple b = cos; - @CalledMethods({}) CreatesMustCallForSimple c = cos; - } + static void test1() { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.reset(); + // :: error: assignment.type.incompatible + @CalledMethods({"reset"}) CreatesMustCallForSimple b = cos; + @CalledMethods({}) CreatesMustCallForSimple c = cos; + } - static void test2() { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.resetThis(); - // :: error: assignment.type.incompatible - @CalledMethods({"resetThis"}) CreatesMustCallForSimple b = cos; - @CalledMethods({}) CreatesMustCallForSimple c = cos; - } + static void test2() { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.resetThis(); + // :: error: assignment.type.incompatible + @CalledMethods({"resetThis"}) CreatesMustCallForSimple b = cos; + @CalledMethods({}) CreatesMustCallForSimple c = cos; + } - static void test3() { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.a(); - cos.resetThis(); - } + static void test3() { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.a(); + cos.resetThis(); + } - static void test4() { - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.a(); - cos.resetThis(); - cos.a(); - } + static void test4() { + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.a(); + cos.resetThis(); + cos.a(); + } - static void test5() { - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.resetThis(); - cos.a(); - } + static void test5() { + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.resetThis(); + cos.a(); + } - static void test6(boolean b) { - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - if (b) { - cos.resetThis(); - } - cos.a(); + static void test6(boolean b) { + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + if (b) { + cos.resetThis(); } + cos.a(); + } - static void test7(boolean b) { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.a(); - if (b) { - cos.resetThis(); - } + static void test7(boolean b) { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.a(); + if (b) { + cos.resetThis(); } + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForSimpler.java b/checker/tests/resourceleak/CreatesMustCallForSimpler.java index c5b2a6a8ed6..0789ebf3dba 100644 --- a/checker/tests/resourceleak/CreatesMustCallForSimpler.java +++ b/checker/tests/resourceleak/CreatesMustCallForSimpler.java @@ -6,25 +6,25 @@ @InheritableMustCall("a") class CreatesMustCallForSimpler { - @CreatesMustCallFor - void reset() {} + @CreatesMustCallFor + void reset() {} - @CreatesMustCallFor("this") - void resetThis() {} + @CreatesMustCallFor("this") + void resetThis() {} - void a() {} + void a() {} - static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { - return null; - } + static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { + return null; + } - static void test1() { - // :: error: required.method.not.called - CreatesMustCallForSimpler cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimpler a = cos; - cos.reset(); - // :: error: assignment.type.incompatible - @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; - @CalledMethods({}) CreatesMustCallForSimpler c = cos; - } + static void test1() { + // :: error: required.method.not.called + CreatesMustCallForSimpler cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimpler a = cos; + cos.reset(); + // :: error: assignment.type.incompatible + @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; + @CalledMethods({}) CreatesMustCallForSimpler c = cos; + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForTargets.java b/checker/tests/resourceleak/CreatesMustCallForTargets.java index e809b865878..1389505f1d7 100644 --- a/checker/tests/resourceleak/CreatesMustCallForTargets.java +++ b/checker/tests/resourceleak/CreatesMustCallForTargets.java @@ -1,90 +1,85 @@ // A test that errors are correctly issued when re-assignments don't match the // create obligation annotation on a method. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - @InheritableMustCall("a") class CreatesMustCallForTargets { - @Owning InputStream is1; + @Owning InputStream is1; - @CreatesMustCallFor - // :: error: createsmustcallfor.target.unparseable - // :: error: incompatible.creates.mustcall.for - static void resetObj1(CreatesMustCallForTargets r) throws Exception { - if (r.is1 == null) { - r.is1 = new FileInputStream("foo.txt"); - } + @CreatesMustCallFor + // :: error: createsmustcallfor.target.unparseable + // :: error: incompatible.creates.mustcall.for + static void resetObj1(CreatesMustCallForTargets r) throws Exception { + if (r.is1 == null) { + r.is1 = new FileInputStream("foo.txt"); } + } - @CreatesMustCallFor("#2") - // :: error: incompatible.creates.mustcall.for - static void resetObj2(CreatesMustCallForTargets r, CreatesMustCallForTargets other) - throws Exception { - if (r.is1 == null) { - r.is1 = new FileInputStream("foo.txt"); - } + @CreatesMustCallFor("#2") + // :: error: incompatible.creates.mustcall.for + static void resetObj2(CreatesMustCallForTargets r, CreatesMustCallForTargets other) + throws Exception { + if (r.is1 == null) { + r.is1 = new FileInputStream("foo.txt"); } + } - @CreatesMustCallFor("#1") - static void resetObj3(CreatesMustCallForTargets r, CreatesMustCallForTargets other) - throws Exception { - if (r.is1 == null) { - r.is1 = new FileInputStream("foo.txt"); - } + @CreatesMustCallFor("#1") + static void resetObj3(CreatesMustCallForTargets r, CreatesMustCallForTargets other) + throws Exception { + if (r.is1 == null) { + r.is1 = new FileInputStream("foo.txt"); } + } - @CreatesMustCallFor - void resetObj4(CreatesMustCallForTargets this, CreatesMustCallForTargets other) - throws Exception { - if (is1 == null) { - is1 = new FileInputStream("foo.txt"); - } + @CreatesMustCallFor + void resetObj4(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { + if (is1 == null) { + is1 = new FileInputStream("foo.txt"); } + } - @CreatesMustCallFor - // :: error: incompatible.creates.mustcall.for - void resetObj5(CreatesMustCallForTargets this, CreatesMustCallForTargets other) - throws Exception { - if (other.is1 == null) { - other.is1 = new FileInputStream("foo.txt"); - } + @CreatesMustCallFor + // :: error: incompatible.creates.mustcall.for + void resetObj5(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { + if (other.is1 == null) { + other.is1 = new FileInputStream("foo.txt"); } + } - @CreatesMustCallFor("#2") - // :: error: createsmustcallfor.target.unparseable - // :: error: incompatible.creates.mustcall.for - void resetObj6(CreatesMustCallForTargets this, CreatesMustCallForTargets other) - throws Exception { - if (other.is1 == null) { - other.is1 = new FileInputStream("foo.txt"); - } + @CreatesMustCallFor("#2") + // :: error: createsmustcallfor.target.unparseable + // :: error: incompatible.creates.mustcall.for + void resetObj6(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { + if (other.is1 == null) { + other.is1 = new FileInputStream("foo.txt"); } + } - @CreatesMustCallFor("#1") - void resetObj7(CreatesMustCallForTargets this, CreatesMustCallForTargets other) - throws Exception { - if (other.is1 == null) { - other.is1 = new FileInputStream("foo.txt"); - } + @CreatesMustCallFor("#1") + void resetObj7(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { + if (other.is1 == null) { + other.is1 = new FileInputStream("foo.txt"); } + } - @EnsuresCalledMethods(value = "this.is1", methods = "close") - void a() throws Exception { - is1.close(); - } + @EnsuresCalledMethods(value = "this.is1", methods = "close") + void a() throws Exception { + is1.close(); + } - @CreatesMustCallFor("#1") - // :: error: creates.mustcall.for.invalid.target - static void testBadCreates(Object o) {} + @CreatesMustCallFor("#1") + // :: error: creates.mustcall.for.invalid.target + static void testBadCreates(Object o) {} - static class BadCreatesField { - @Owning Object o; + static class BadCreatesField { + @Owning Object o; - @CreatesMustCallFor("this.o") - // :: error: creates.mustcall.for.invalid.target - void badCreatesOnField() {} - } + @CreatesMustCallFor("this.o") + // :: error: creates.mustcall.for.invalid.target + void badCreatesOnField() {} + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java b/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java index 80c3025af5b..c9827c4c47e 100644 --- a/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java +++ b/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java @@ -3,50 +3,50 @@ import org.checkerframework.checker.mustcall.qual.*; public class CreatesMustCallForTwoAliases { - @InheritableMustCall("a") - static class Foo { + @InheritableMustCall("a") + static class Foo { - @SuppressWarnings("mustcall") - @MustCall() Foo() { - // unconnected socket like - } - - @CreatesMustCallFor("this") - void reset() {} - - void a() {} - } - - public static void test1() { - Foo a = new Foo(); - // :: error: required.method.not.called - Foo b = a; - b.reset(); - } - - @CreatesMustCallFor("#1") - public static void sneakyReset(Foo f) { - f.reset(); + @SuppressWarnings("mustcall") + @MustCall() Foo() { + // unconnected socket like } - public static void test2() { - Foo a = new Foo(); - // :: error: required.method.not.called - Foo b = a; - sneakyReset(b); - } - - public static void test3(Foo b) { - Foo a = new Foo(); - // :: error: required.method.not.called - b = a; - sneakyReset(b); - } - - public static void test4(Foo b) { - // :: error: required.method.not.called - Foo a = new Foo(); - b = a; - sneakyReset(a); - } + @CreatesMustCallFor("this") + void reset() {} + + void a() {} + } + + public static void test1() { + Foo a = new Foo(); + // :: error: required.method.not.called + Foo b = a; + b.reset(); + } + + @CreatesMustCallFor("#1") + public static void sneakyReset(Foo f) { + f.reset(); + } + + public static void test2() { + Foo a = new Foo(); + // :: error: required.method.not.called + Foo b = a; + sneakyReset(b); + } + + public static void test3(Foo b) { + Foo a = new Foo(); + // :: error: required.method.not.called + b = a; + sneakyReset(b); + } + + public static void test4(Foo b) { + // :: error: required.method.not.called + Foo a = new Foo(); + b = a; + sneakyReset(a); + } } diff --git a/checker/tests/resourceleak/DifferentSWKeys.java b/checker/tests/resourceleak/DifferentSWKeys.java index 4bb74911abe..3ff3b4d8ca8 100644 --- a/checker/tests/resourceleak/DifferentSWKeys.java +++ b/checker/tests/resourceleak/DifferentSWKeys.java @@ -6,22 +6,22 @@ @SuppressWarnings("required.method.not.called") class DifferentSWKeys { - void test(@Owning @MustCall("foo") Object obj) { - // :: warning: unneeded.suppression - @SuppressWarnings("mustcall") - @MustCall("foo") Object bar = obj; - } + void test(@Owning @MustCall("foo") Object obj) { + // :: warning: unneeded.suppression + @SuppressWarnings("mustcall") + @MustCall("foo") Object bar = obj; + } - void test2(@Owning @MustCall("foo") Object obj) { - // actually needed suppression - @SuppressWarnings("mustcall") - @MustCall({}) Object bar = obj; - } + void test2(@Owning @MustCall("foo") Object obj) { + // actually needed suppression + @SuppressWarnings("mustcall") + @MustCall({}) Object bar = obj; + } - void test3(@Owning @MustCall("foo") Object obj) { - // test that the option-specific suppression key doesn't work - @SuppressWarnings("mustcallnocreatesmustcallfor") - // :: error: assignment.type.incompatible - @MustCall({}) Object bar = obj; - } + void test3(@Owning @MustCall("foo") Object obj) { + // test that the option-specific suppression key doesn't work + @SuppressWarnings("mustcallnocreatesmustcallfor") + // :: error: assignment.type.incompatible + @MustCall({}) Object bar = obj; + } } diff --git a/checker/tests/resourceleak/DoubleIf.java b/checker/tests/resourceleak/DoubleIf.java index 7c4163d09f3..a0c41d30e24 100644 --- a/checker/tests/resourceleak/DoubleIf.java +++ b/checker/tests/resourceleak/DoubleIf.java @@ -3,53 +3,52 @@ // Adding an @CalledMethods annotation, like in parse4, also // makes this code verifiable... -import org.checkerframework.checker.calledmethods.qual.CalledMethods; - import java.io.*; +import org.checkerframework.checker.calledmethods.qual.CalledMethods; class DoubleIf { - String fn; + String fn; - public void parse(boolean b, boolean c) throws Exception { - if (c) { - FileInputStream fis1 = new FileInputStream(fn); - try { - } finally { - fis1.close(); - } - if (b) {} - } + public void parse(boolean b, boolean c) throws Exception { + if (c) { + FileInputStream fis1 = new FileInputStream(fn); + try { + } finally { + fis1.close(); + } + if (b) {} } - - public void parse2(boolean c) throws Exception { - if (c) { - FileInputStream fis2 = new FileInputStream(fn); - try { - } finally { - fis2.close(); - } - } + } + + public void parse2(boolean c) throws Exception { + if (c) { + FileInputStream fis2 = new FileInputStream(fn); + try { + } finally { + fis2.close(); + } } + } - public void parse3(boolean b) throws Exception { - FileInputStream fis3 = new FileInputStream(fn); - try { - } finally { - fis3.close(); - } - if (b) {} + public void parse3(boolean b) throws Exception { + FileInputStream fis3 = new FileInputStream(fn); + try { + } finally { + fis3.close(); } - - public void parse4(boolean b, boolean c) throws Exception { - if (c) { - FileInputStream fis4 = new FileInputStream(fn); - try { - } finally { - fis4.close(); - } - if (b) {} - @CalledMethods("close") FileInputStream fis24 = fis4; - } + if (b) {} + } + + public void parse4(boolean b, boolean c) throws Exception { + if (c) { + FileInputStream fis4 = new FileInputStream(fn); + try { + } finally { + fis4.close(); + } + if (b) {} + @CalledMethods("close") FileInputStream fis24 = fis4; } + } } diff --git a/checker/tests/resourceleak/DuplicateError.java b/checker/tests/resourceleak/DuplicateError.java index 6fb0b88d92f..0bc42ead16d 100644 --- a/checker/tests/resourceleak/DuplicateError.java +++ b/checker/tests/resourceleak/DuplicateError.java @@ -1,22 +1,21 @@ +import java.util.List; +import java.util.function.Function; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.UnknownKeyFor; -import java.util.List; -import java.util.function.Function; - public class DuplicateError { - void m(List values) { - @SuppressWarnings("lambda.param.type.incompatible") - List stringVals = DECollectionsPlume.mapList((Object o) -> (String) o, values); - } + void m(List values) { + @SuppressWarnings("lambda.param.type.incompatible") + List stringVals = DECollectionsPlume.mapList((Object o) -> (String) o, values); + } } class DECollectionsPlume { - public static < - @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, - @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> - List mapList(Function f, Iterable iterable) { - return null; - } + public static < + @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, + @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> + List mapList(Function f, Iterable iterable) { + return null; + } } diff --git a/checker/tests/resourceleak/Enclosing.java b/checker/tests/resourceleak/Enclosing.java index eccdb4ff03b..b9953331fb0 100644 --- a/checker/tests/resourceleak/Enclosing.java +++ b/checker/tests/resourceleak/Enclosing.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.mustcall.qual.*; class Enclosing { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - static class Nested { - // :: error: required.method.not.called - @Owning Foo foo; + static class Nested { + // :: error: required.method.not.called + @Owning Foo foo; - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } - } + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } } + } } diff --git a/checker/tests/resourceleak/EnhancedFor.java b/checker/tests/resourceleak/EnhancedFor.java index d8e8f84f222..c4d706a7ea1 100644 --- a/checker/tests/resourceleak/EnhancedFor.java +++ b/checker/tests/resourceleak/EnhancedFor.java @@ -1,67 +1,66 @@ // Based on some false positives I found in ZK. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.Socket; import java.util.List; +import org.checkerframework.checker.mustcall.qual.*; class EnhancedFor { - void test(List<@MustCall Socket> list) { - for (Socket s : list) { - try { - s.close(); - } catch (IOException i) { - } - } + void test(List<@MustCall Socket> list) { + for (Socket s : list) { + try { + s.close(); + } catch (IOException i) { + } } + } - void test2(List<@MustCall Socket> list) { - for (int i = 0; i < list.size(); i++) { - Socket s = list.get(i); - try { - s.close(); - } catch (IOException io) { - } - } + void test2(List<@MustCall Socket> list) { + for (int i = 0; i < list.size(); i++) { + Socket s = list.get(i); + try { + s.close(); + } catch (IOException io) { + } } + } - // :: error: (type.argument.type.incompatible) - void test3(List list) { - // This error is issued because `s` is a local variable, and - // the foreach loop under the hood assigns the result of a call - // to Iterator#next into it (which is owning by default, because it's - // a method return type). Both this error and the type.argument error - // above can be suppressed by writing @MustCall on the Socket type, as in - // test4 below (but note that this will make call sites difficult to verify). - // :: error: (required.method.not.called) - for (Socket s : list) {} - } + // :: error: (type.argument.type.incompatible) + void test3(List list) { + // This error is issued because `s` is a local variable, and + // the foreach loop under the hood assigns the result of a call + // to Iterator#next into it (which is owning by default, because it's + // a method return type). Both this error and the type.argument error + // above can be suppressed by writing @MustCall on the Socket type, as in + // test4 below (but note that this will make call sites difficult to verify). + // :: error: (required.method.not.called) + for (Socket s : list) {} + } - void test4(List<@MustCall Socket> list) { - for (Socket s : list) {} - } + void test4(List<@MustCall Socket> list) { + for (Socket s : list) {} + } - void test5(List list) { - for (Socket s : list) {} - } + void test5(List list) { + for (Socket s : list) {} + } - void test6(List list) { - for (Socket s : list) { - try { - s.close(); - } catch (IOException i) { - } - } + void test6(List list) { + for (Socket s : list) { + try { + s.close(); + } catch (IOException i) { + } } + } - void test7(List list) { - for (int i = 0; i < list.size(); i++) { - Socket s = list.get(i); - try { - s.close(); - } catch (IOException io) { - } - } + void test7(List list) { + for (int i = 0; i < list.size(); i++) { + Socket s = list.get(i); + try { + s.close(); + } catch (IOException io) { + } } + } } diff --git a/checker/tests/resourceleak/FileDescriptorTest.java b/checker/tests/resourceleak/FileDescriptorTest.java index 50aad87d5f2..5301ba0899f 100644 --- a/checker/tests/resourceleak/FileDescriptorTest.java +++ b/checker/tests/resourceleak/FileDescriptorTest.java @@ -1,58 +1,57 @@ -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.io.IOException; import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; public abstract class FileDescriptorTest { - // This is the original test case. It fails because `in.close()` might throw an exception, which - // is - // not caught; therefore, file might still be open. - public static void readPropertiesFile(File from) throws IOException { - // This is a false positive. - // :: error: required.method.not.called - RandomAccessFile file = new RandomAccessFile(from, "rws"); - FileInputStream in = null; - try { - in = new FileInputStream(file.getFD()); - file.seek(0); - } finally { - if (in != null) { - in.close(); - } - file.close(); - } + // This is the original test case. It fails because `in.close()` might throw an exception, which + // is + // not caught; therefore, file might still be open. + public static void readPropertiesFile(File from) throws IOException { + // This is a false positive. + // :: error: required.method.not.called + RandomAccessFile file = new RandomAccessFile(from, "rws"); + FileInputStream in = null; + try { + in = new FileInputStream(file.getFD()); + file.seek(0); + } finally { + if (in != null) { + in.close(); + } + file.close(); } + } - abstract Socket createSocket(); + abstract Socket createSocket(); - // This is a similar test to the above, but without using the indirection through getFD(). - // This test case demonstrates that the problem is not related to getFD(). - // This warning is a false positive, and should be resolved at the same time as the warning - // above. - public void sameScenario_noFD() throws IOException { - // :: error: (required.method.not.called) - Socket sock = createSocket(); - InputStream in = null; - try { - in = sock.getInputStream(); - } finally { - if (in != null) { - in.close(); - } - sock.close(); - } + // This is a similar test to the above, but without using the indirection through getFD(). + // This test case demonstrates that the problem is not related to getFD(). + // This warning is a false positive, and should be resolved at the same time as the warning + // above. + public void sameScenario_noFD() throws IOException { + // :: error: (required.method.not.called) + Socket sock = createSocket(); + InputStream in = null; + try { + in = sock.getInputStream(); + } finally { + if (in != null) { + in.close(); + } + sock.close(); } + } - // This version, written by Narges, does not issue a false positive. - public static void readPropertiesFile_noFP(File from) throws IOException { - RandomAccessFile file = new RandomAccessFile(from, "rws"); - FileInputStream in = null; - try { - in = new FileInputStream(file.getFD()); - in.close(); - } catch (IOException e) { - file.close(); - } + // This version, written by Narges, does not issue a false positive. + public static void readPropertiesFile_noFP(File from) throws IOException { + RandomAccessFile file = new RandomAccessFile(from, "rws"); + FileInputStream in = null; + try { + in = new FileInputStream(file.getFD()); + in.close(); + } catch (IOException e) { + file.close(); } + } } diff --git a/checker/tests/resourceleak/FilesTest.java b/checker/tests/resourceleak/FilesTest.java index cdcd41c87bb..755f249514a 100644 --- a/checker/tests/resourceleak/FilesTest.java +++ b/checker/tests/resourceleak/FilesTest.java @@ -5,14 +5,14 @@ public class FilesTest { - void bad(Path p) throws IOException { - // :: error: (required.method.not.called) - Stream s = Files.list(p); - } + void bad(Path p) throws IOException { + // :: error: (required.method.not.called) + Stream s = Files.list(p); + } - void good(Path p) throws IOException { - try (Stream s = Files.list(p)) { - // empty body - } + void good(Path p) throws IOException { + try (Stream s = Files.list(p)) { + // empty body } + } } diff --git a/checker/tests/resourceleak/GetChannelOnLocks.java b/checker/tests/resourceleak/GetChannelOnLocks.java index aafa376a502..50141c09bbb 100644 --- a/checker/tests/resourceleak/GetChannelOnLocks.java +++ b/checker/tests/resourceleak/GetChannelOnLocks.java @@ -5,36 +5,35 @@ import java.nio.channels.FileLock; class GetChannelOnLocks { - public boolean isLockSupported(FileLock lock, FileLock lock1, FileLock lock2) - throws IOException { - FileLock firstLock = null; - FileLock secondLock = null; - try { - firstLock = lock1; - if (firstLock == null) { - return true; - } - secondLock = lock2; - if (secondLock == null) { - return true; - } - } finally { - if (firstLock != null && firstLock != lock) { - firstLock.release(); - firstLock.channel().close(); - } - if (secondLock != null) { - secondLock.release(); - secondLock.channel().close(); - } - } - return false; + public boolean isLockSupported(FileLock lock, FileLock lock1, FileLock lock2) throws IOException { + FileLock firstLock = null; + FileLock secondLock = null; + try { + firstLock = lock1; + if (firstLock == null) { + return true; + } + secondLock = lock2; + if (secondLock == null) { + return true; + } + } finally { + if (firstLock != null && firstLock != lock) { + firstLock.release(); + firstLock.channel().close(); + } + if (secondLock != null) { + secondLock.release(); + secondLock.channel().close(); + } } + return false; + } - public void isLockSupported2(FileLock lock, FileLock firstLock) throws IOException { - if (firstLock != null && firstLock != lock) { - firstLock.release(); - firstLock.channel().close(); - } + public void isLockSupported2(FileLock lock, FileLock firstLock) throws IOException { + if (firstLock != null && firstLock != lock) { + firstLock.release(); + firstLock.channel().close(); } + } } diff --git a/checker/tests/resourceleak/HBaseReport1.java b/checker/tests/resourceleak/HBaseReport1.java index d08960e8de9..2206658f64c 100644 --- a/checker/tests/resourceleak/HBaseReport1.java +++ b/checker/tests/resourceleak/HBaseReport1.java @@ -8,29 +8,29 @@ class HBaseReport1 { - public static void test(String fileName) { - FileWriter fstream; - try { - // :: error: required.method.not.called - fstream = new FileWriter(fileName); - } catch (IOException e) { - return; - } + public static void test(String fileName) { + FileWriter fstream; + try { + // :: error: required.method.not.called + fstream = new FileWriter(fileName); + } catch (IOException e) { + return; + } - BufferedWriter out = new BufferedWriter(fstream); + BufferedWriter out = new BufferedWriter(fstream); + try { + try { + out.write(fileName + "\n"); + } finally { try { - try { - out.write(fileName + "\n"); - } finally { - try { - out.close(); - } finally { - fstream.close(); - } - } - } catch (IOException e) { - + out.close(); + } finally { + fstream.close(); } + } + } catch (IOException e) { + } + } } diff --git a/checker/tests/resourceleak/HDFSReport.java b/checker/tests/resourceleak/HDFSReport.java index f141eef2190..78014a1b3ab 100644 --- a/checker/tests/resourceleak/HDFSReport.java +++ b/checker/tests/resourceleak/HDFSReport.java @@ -1,24 +1,24 @@ class Handler extends Thread { - boolean running; + boolean running; - static class Call { - boolean isResponseDeferred() { - return true; - } + static class Call { + boolean isResponseDeferred() { + return true; } + } - @Override - public void run() { - while (running) { - Call call = null; - try { - if (running) { - continue; - } - } catch (Exception e) { - } finally { - String s = call.isResponseDeferred() ? ", deferred" : ""; - } + @Override + public void run() { + while (running) { + Call call = null; + try { + if (running) { + continue; } + } catch (Exception e) { + } finally { + String s = call.isResponseDeferred() ? ", deferred" : ""; + } } + } } diff --git a/checker/tests/resourceleak/HDFSReport2.java b/checker/tests/resourceleak/HDFSReport2.java index d5cea231798..03773b9676b 100644 --- a/checker/tests/resourceleak/HDFSReport2.java +++ b/checker/tests/resourceleak/HDFSReport2.java @@ -2,21 +2,21 @@ class RamDiskAsyncLazyPersistService { - public interface FsVolumeReference extends Closeable {} + public interface FsVolumeReference extends Closeable {} - class ReplicaLazyPersistTask implements Runnable { - private final FsVolumeReference targetVolume; + class ReplicaLazyPersistTask implements Runnable { + private final FsVolumeReference targetVolume; - ReplicaLazyPersistTask(FsVolumeReference targetVolume) { - this.targetVolume = targetVolume; - } + ReplicaLazyPersistTask(FsVolumeReference targetVolume) { + this.targetVolume = targetVolume; + } - @Override - public void run() { - try (FsVolumeReference ref = this.targetVolume) { + @Override + public void run() { + try (FsVolumeReference ref = this.targetVolume) { - } catch (Exception e) { - } - } + } catch (Exception e) { + } } + } } diff --git a/checker/tests/resourceleak/HdfsReport3.java b/checker/tests/resourceleak/HdfsReport3.java index 358550eb5fb..dbb13767e9c 100644 --- a/checker/tests/resourceleak/HdfsReport3.java +++ b/checker/tests/resourceleak/HdfsReport3.java @@ -1,28 +1,26 @@ // Based on a false positive in hdfs -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; -import org.checkerframework.common.returnsreceiver.qual.*; - import java.io.*; import java.net.*; import java.nio.*; import java.nio.file.*; import java.security.*; import java.util.*; - import javax.net.ssl.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; +import org.checkerframework.common.returnsreceiver.qual.*; class HdfsReport3 { - private StringBuffer nonObligationTest(int id) { - final StringWriter out = new StringWriter(); - dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(), id); - return out.getBuffer(); - } + private StringBuffer nonObligationTest(int id) { + final StringWriter out = new StringWriter(); + dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(), id); + return out.getBuffer(); + } - public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, int snapshotId) {} + public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, int snapshotId) {} - // StringBuilder doesn't implement closeable - private final StringBuilder sb = new StringBuilder(); - private final Formatter formatter = new Formatter(sb); + // StringBuilder doesn't implement closeable + private final StringBuilder sb = new StringBuilder(); + private final Formatter formatter = new Formatter(sb); } diff --git a/checker/tests/resourceleak/IOUtilsTest.java b/checker/tests/resourceleak/IOUtilsTest.java index eaa1721c8fa..936c1d2df20 100644 --- a/checker/tests/resourceleak/IOUtilsTest.java +++ b/checker/tests/resourceleak/IOUtilsTest.java @@ -1,17 +1,16 @@ -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.*; class IOUtilsTest { - static void test1(@Owning InputStream inputStream) { - org.apache.commons.io.IOUtils.closeQuietly(inputStream); - } + static void test1(@Owning InputStream inputStream) { + org.apache.commons.io.IOUtils.closeQuietly(inputStream); + } - static void test2(@Owning InputStream inputStream) throws IOException { - try { - InputStream other = org.apache.commons.io.IOUtils.toBufferedInputStream(inputStream); - } finally { - inputStream.close(); - } + static void test2(@Owning InputStream inputStream) throws IOException { + try { + InputStream other = org.apache.commons.io.IOUtils.toBufferedInputStream(inputStream); + } finally { + inputStream.close(); } + } } diff --git a/checker/tests/resourceleak/IgnoredExceptionECM.java b/checker/tests/resourceleak/IgnoredExceptionECM.java index 4aabe7988ac..d214a1355f8 100644 --- a/checker/tests/resourceleak/IgnoredExceptionECM.java +++ b/checker/tests/resourceleak/IgnoredExceptionECM.java @@ -7,14 +7,14 @@ @InheritableMustCall("foo") class IgnoredExceptionECM { - @Owning - @MustCall("toString") Object obj; + @Owning + @MustCall("toString") Object obj; - @EnsuresCalledMethods(value = "this.obj", methods = "toString") - void foo() { - // This line will produce an exception, - // which the RLC should ignore and verify the method. - int y = 5 / 0; - this.obj.toString(); - } + @EnsuresCalledMethods(value = "this.obj", methods = "toString") + void foo() { + // This line will produce an exception, + // which the RLC should ignore and verify the method. + int y = 5 / 0; + this.obj.toString(); + } } diff --git a/checker/tests/resourceleak/IndexMode.java b/checker/tests/resourceleak/IndexMode.java index d5235c3539b..47740afb349 100644 --- a/checker/tests/resourceleak/IndexMode.java +++ b/checker/tests/resourceleak/IndexMode.java @@ -1,84 +1,83 @@ // A test for a new false positive issued in release 3.36.0 but not 3.35.0. // Reported as part of https://github.com/typetools/checker-framework/issues/6077. -import org.checkerframework.checker.mustcall.qual.MustCall; - import java.io.InputStream; import java.util.Map; +import org.checkerframework.checker.mustcall.qual.MustCall; public class IndexMode { - public static Object getMode(Map indexOptions) { - // Try-catch is needed, otherwise no FP. - try { - String literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { - } - - // Actual return type rather than void is needed, otherwise no FP. - return null; + public static Object getMode(Map indexOptions) { + // Try-catch is needed, otherwise no FP. + try { + String literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String. - public static Object getMode2(Map indexOptions) { - try { - // TODO: a required.method.not.called error should be issued on this line, but currently - // it is not. The reason is an interaction between type variable defaulting, - // local dataflow, and the rules that the RLC uses for choosing a variable's must-call - // obligations: local inference defaults literalOption to @MustCallUnknown (i.e., the - // top MustCall type) even though the RHS expression's type is @MustCall("hashCode"). - // Then, the rule for obligations says that if a variable has the top must-call type, - // use the type's default must-call type instead. For String, this is @MustCall({}), - // so no error is issued. This rule is important to avoid false positives in realistic - // code (such as the first getMode() method in this class). - String literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { - } + // Actual return type rather than void is needed, otherwise no FP. + return null; + } - return null; + // This copy of getMode() adds an explicit `@MustCall` annotation to the String. + public static Object getMode2(Map indexOptions) { + try { + // TODO: a required.method.not.called error should be issued on this line, but currently + // it is not. The reason is an interaction between type variable defaulting, + // local dataflow, and the rules that the RLC uses for choosing a variable's must-call + // obligations: local inference defaults literalOption to @MustCallUnknown (i.e., the + // top MustCall type) even though the RHS expression's type is @MustCall("hashCode"). + // Then, the rule for obligations says that if a variable has the top must-call type, + // use the type's default must-call type instead. For String, this is @MustCall({}), + // so no error is issued. This rule is important to avoid false positives in realistic + // code (such as the first getMode() method in this class). + String literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String and to - // the local variable. This version currently works as expected, unlike getMode2(). - public static Object getMode2a(Map indexOptions) { - try { - // :: error: required.method.not.called - @MustCall("hashCode") String literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { - } + return null; + } - return null; + // This copy of getMode() adds an explicit `@MustCall` annotation to the String and to + // the local variable. This version currently works as expected, unlike getMode2(). + public static Object getMode2a(Map indexOptions) { + try { + // :: error: required.method.not.called + @MustCall("hashCode") String literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String and removes - // the try-catch. - public static Object getMode3(Map indexOptions) { - // :: error: required.method.not.called - String literalOption = indexOptions.get("is_literal"); - return null; - } + return null; + } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String, removes - // the try-catch, and makes the return type void. - public static void getMode4(Map indexOptions) { - // :: error: required.method.not.called - String literalOption = indexOptions.get("is_literal"); - } + // This copy of getMode() adds an explicit `@MustCall` annotation to the String and removes + // the try-catch. + public static Object getMode3(Map indexOptions) { + // :: error: required.method.not.called + String literalOption = indexOptions.get("is_literal"); + return null; + } - // This copy of getMode() removes the try-catch and makes the return type void. - public static void getMode5(Map indexOptions) { - String literalOption = indexOptions.get("is_literal"); - } + // This copy of getMode() adds an explicit `@MustCall` annotation to the String, removes + // the try-catch, and makes the return type void. + public static void getMode4(Map indexOptions) { + // :: error: required.method.not.called + String literalOption = indexOptions.get("is_literal"); + } - // This variant uses an InputStream (which has a MustCall type by default) as the - // value type in the map. - // :: error: type.argument.type.incompatible - public static Object getModeIS(Map indexOptions) { - try { - // :: error: required.method.not.called - InputStream literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { - } + // This copy of getMode() removes the try-catch and makes the return type void. + public static void getMode5(Map indexOptions) { + String literalOption = indexOptions.get("is_literal"); + } - return null; + // This variant uses an InputStream (which has a MustCall type by default) as the + // value type in the map. + // :: error: type.argument.type.incompatible + public static Object getModeIS(Map indexOptions) { + try { + // :: error: required.method.not.called + InputStream literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { } + + return null; + } } diff --git a/checker/tests/resourceleak/InheritanceStream.java b/checker/tests/resourceleak/InheritanceStream.java index 893319b7dfe..36f85881836 100644 --- a/checker/tests/resourceleak/InheritanceStream.java +++ b/checker/tests/resourceleak/InheritanceStream.java @@ -4,19 +4,19 @@ import java.io.*; class InheritanceStream { - void testBAIS(byte[] buf) { - new ByteArrayInputStream(buf); - } + void testBAIS(byte[] buf) { + new ByteArrayInputStream(buf); + } - void testBAOS() { - new ByteArrayOutputStream(); - } + void testBAOS() { + new ByteArrayOutputStream(); + } - void testSR(String buf) { - new StringReader(buf); - } + void testSR(String buf) { + new StringReader(buf); + } - void testSW() { - new StringWriter(); - } + void testSW() { + new StringWriter(); + } } diff --git a/checker/tests/resourceleak/InitializationAfterSuperTest.java b/checker/tests/resourceleak/InitializationAfterSuperTest.java index c723c79e3e3..bee8853a49a 100644 --- a/checker/tests/resourceleak/InitializationAfterSuperTest.java +++ b/checker/tests/resourceleak/InitializationAfterSuperTest.java @@ -2,26 +2,25 @@ // @skip-test until the bug is fixed +import java.io.IOException; +import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.Owning; -import java.io.IOException; -import java.net.Socket; - @InheritableMustCall("close") public class InitializationAfterSuperTest implements AutoCloseable { - @Owning Socket mySocket; + @Owning Socket mySocket; - public InitializationAfterSuperTest(@Owning Socket mySocket) { - super(); - this.mySocket = mySocket; - } + public InitializationAfterSuperTest(@Owning Socket mySocket) { + super(); + this.mySocket = mySocket; + } - @EnsuresCalledMethods(value = "mySocket", methods = "close") - @Override - public void close() throws IOException { - mySocket.close(); - } + @EnsuresCalledMethods(value = "mySocket", methods = "close") + @Override + public void close() throws IOException { + mySocket.close(); + } } diff --git a/checker/tests/resourceleak/InputOutputStreams.java b/checker/tests/resourceleak/InputOutputStreams.java index 9d45ae2f9d5..cc9b3fc87c7 100644 --- a/checker/tests/resourceleak/InputOutputStreams.java +++ b/checker/tests/resourceleak/InputOutputStreams.java @@ -1,188 +1,187 @@ // A MustCallAlias test wrt sockets. Also coincidentally tests that MCA sets can be larger than two. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.io.IOException; import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; abstract class InputOutputStreams { - abstract Socket newSocket(); + abstract Socket newSocket(); - void test_close_sock(@Owning Socket sock) throws IOException { - try { - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - } catch (IOException e) { + void test_close_sock(@Owning Socket sock) throws IOException { + try { + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + } catch (IOException e) { - } finally { - sock.close(); - } + } finally { + sock.close(); } - - // getInputStream()/getOutputStream() can throw IOException in three different scenarios: - // 1) The underlying socket is already closed - // 2) The underlying socket is not connected - // 3) The underlysing socket input is shutdown - // In the first case our checker always reports a false positive but for the second case - // and third case our checker has to verify that close is called on the underlying resource. - // So, because sock.getInputStream() can throw IOException, "is" can be null, then sock will - // remain open. So, it's a true positive warning. - // :: error: required.method.not.called - void test_close_is(@Owning Socket sock) throws IOException { - InputStream is = null; - OutputStream os = null; - try { - is = sock.getInputStream(); - os = sock.getOutputStream(); - } catch (IOException e) { - - } finally { - is.close(); - } + } + + // getInputStream()/getOutputStream() can throw IOException in three different scenarios: + // 1) The underlying socket is already closed + // 2) The underlying socket is not connected + // 3) The underlysing socket input is shutdown + // In the first case our checker always reports a false positive but for the second case + // and third case our checker has to verify that close is called on the underlying resource. + // So, because sock.getInputStream() can throw IOException, "is" can be null, then sock will + // remain open. So, it's a true positive warning. + // :: error: required.method.not.called + void test_close_is(@Owning Socket sock) throws IOException { + InputStream is = null; + OutputStream os = null; + try { + is = sock.getInputStream(); + os = sock.getOutputStream(); + } catch (IOException e) { + + } finally { + is.close(); } - - // NOTE (2023/12/8): Previously the RLC reported required.method.not.called - // on this method. However, the code is actually safe because we are not - // obligated to close @Owning parameters when a method throws an exception - // (see https://github.com/typetools/checker-framework/pull/6241). - void test_close_os_old(@Owning Socket sock) throws IOException { - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - os.close(); + } + + // NOTE (2023/12/8): Previously the RLC reported required.method.not.called + // on this method. However, the code is actually safe because we are not + // obligated to close @Owning parameters when a method throws an exception + // (see https://github.com/typetools/checker-framework/pull/6241). + void test_close_os_old(@Owning Socket sock) throws IOException { + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + os.close(); + } + + void test_close_os() throws IOException { + // :: error: (required.method.not.called) + Socket sock = newSocket(); + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + os.close(); + } + + // :: error: required.method.not.called + void test_close_os2(@Owning Socket sock) throws IOException { + OutputStream os = null; + InputStream is = null; + try { + is = sock.getInputStream(); + } catch (IOException e) { + try { + os = sock.getOutputStream(); + } catch (IOException ee) { + } + } finally { + os.close(); } - - void test_close_os() throws IOException { - // :: error: (required.method.not.called) - Socket sock = newSocket(); - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - os.close(); + } + + // NOTE (2023/12/8): Previously the RLC reported required.method.not.called + // on this method. However, the code is actually safe because we are not + // obligated to close @Owning parameters when a method throws an exception + // (see https://github.com/typetools/checker-framework/pull/6241). + void test_close_os3_old(@Owning Socket sock) throws IOException { + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { } - - // :: error: required.method.not.called - void test_close_os2(@Owning Socket sock) throws IOException { - OutputStream os = null; - InputStream is = null; - try { - is = sock.getInputStream(); - } catch (IOException e) { - try { - os = sock.getOutputStream(); - } catch (IOException ee) { - } - } finally { - os.close(); - } + try { + os = sock.getOutputStream(); + } finally { + os.close(); } - - // NOTE (2023/12/8): Previously the RLC reported required.method.not.called - // on this method. However, the code is actually safe because we are not - // obligated to close @Owning parameters when a method throws an exception - // (see https://github.com/typetools/checker-framework/pull/6241). - void test_close_os3_old(@Owning Socket sock) throws IOException { - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { - } - try { - os = sock.getOutputStream(); - } finally { - os.close(); - } + } + + void test_close_os3() throws IOException { + // :: error: (required.method.not.called) + Socket sock = newSocket(); + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { } - - void test_close_os3() throws IOException { - // :: error: (required.method.not.called) - Socket sock = newSocket(); - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { - } - try { - os = sock.getOutputStream(); - } finally { - os.close(); - } + try { + os = sock.getOutputStream(); + } finally { + os.close(); } - - // NOTE (2023/12/8): Previously the RLC reported required.method.not.called - // on this method. However, the code is actually safe because we are not - // obligated to close @Owning parameters when a method throws an exception - // (see https://github.com/typetools/checker-framework/pull/6241). - void test_close_os4_old(@Owning Socket sock) throws IOException { - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { - } - try { - os = sock.getOutputStream(); - } finally { - if (os != null) { - os.close(); - } else { - sock.close(); - } - } + } + + // NOTE (2023/12/8): Previously the RLC reported required.method.not.called + // on this method. However, the code is actually safe because we are not + // obligated to close @Owning parameters when a method throws an exception + // (see https://github.com/typetools/checker-framework/pull/6241). + void test_close_os4_old(@Owning Socket sock) throws IOException { + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { } - - // TODO this case requires more general tracking of additional boolean conditions - // If getOutputStream() throws an IOException, then the os variable remains definitely null. - // When our worklist analysis gets to the finally block along the corresponding CFG edge, - // it does not know that os is definitely null, and it is only tracking sock as a name for - // the resource. So, it analyzes a path through the "then" branch of the conditional, - // where sock.close() is not invoked, and reports an error. - void test_close_os4() throws IOException { - // :: error: (required.method.not.called) - Socket sock = newSocket(); - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { - } - try { - os = sock.getOutputStream(); - } finally { - if (os != null) { - os.close(); - } else { - sock.close(); - } - } + try { + os = sock.getOutputStream(); + } finally { + if (os != null) { + os.close(); + } else { + sock.close(); + } } - - // :: error: required.method.not.called - void test_close_buff(@Owning Socket sock) throws IOException { - BufferedOutputStream buff = null; - try { - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - buff = new BufferedOutputStream(os); - } catch (IOException e) { - - } finally { - buff.close(); - } + } + + // TODO this case requires more general tracking of additional boolean conditions + // If getOutputStream() throws an IOException, then the os variable remains definitely null. + // When our worklist analysis gets to the finally block along the corresponding CFG edge, + // it does not know that os is definitely null, and it is only tracking sock as a name for + // the resource. So, it analyzes a path through the "then" branch of the conditional, + // where sock.close() is not invoked, and reports an error. + void test_close_os4() throws IOException { + // :: error: (required.method.not.called) + Socket sock = newSocket(); + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { } - - void test_write(String host, int port) { - Socket sock = null; + try { + os = sock.getOutputStream(); + } finally { + if (os != null) { + os.close(); + } else { + sock.close(); + } + } + } + + // :: error: required.method.not.called + void test_close_buff(@Owning Socket sock) throws IOException { + BufferedOutputStream buff = null; + try { + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + buff = new BufferedOutputStream(os); + } catch (IOException e) { + + } finally { + buff.close(); + } + } + + void test_write(String host, int port) { + Socket sock = null; + try { + sock = new Socket(host, port); + sock.getOutputStream().write("isro".getBytes()); + } catch (Exception e) { + System.err.println("write failed: " + e); + } finally { + if (sock != null) { try { - sock = new Socket(host, port); - sock.getOutputStream().write("isro".getBytes()); - } catch (Exception e) { - System.err.println("write failed: " + e); - } finally { - if (sock != null) { - try { - sock.close(); - } catch (IOException e) { - System.err.println("couldn't close socket!"); - } - } + sock.close(); + } catch (IOException e) { + System.err.println("couldn't close socket!"); } + } } + } } diff --git a/checker/tests/resourceleak/InstanceInitializer.java b/checker/tests/resourceleak/InstanceInitializer.java index 4bb2a951ed4..74eb6e61ada 100644 --- a/checker/tests/resourceleak/InstanceInitializer.java +++ b/checker/tests/resourceleak/InstanceInitializer.java @@ -2,56 +2,55 @@ // In the resourceleak-permitinitializationleak/ directory, it's a test that the // checker is unsound with the -ApermitInitializationLeak command-line argument. -import org.checkerframework.checker.mustcall.qual.*; - import java.net.Socket; +import org.checkerframework.checker.mustcall.qual.*; class InstanceInitializer { - // :: error: required.method.not.called - private @Owning Socket s; - - private final int DEFAULT_PORT = 5; - private final String DEFAULT_ADDR = "localhost"; - - { - try { - // This assignment is OK, because it's the first assignment. - // However, the Resource Leak Checker issues a false positive warning. - // :: error: required.method.not.called - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } + // :: error: required.method.not.called + private @Owning Socket s; + + private final int DEFAULT_PORT = 5; + private final String DEFAULT_ADDR = "localhost"; + + { + try { + // This assignment is OK, because it's the first assignment. + // However, the Resource Leak Checker issues a false positive warning. + // :: error: required.method.not.called + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { } - - { - try { - // This assignment is not OK, because it's a reassignment without satisfying the - // mustcall obligations of the previous value of `s`. - // :: error: required.method.not.called - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } + } + + { + try { + // This assignment is not OK, because it's a reassignment without satisfying the + // mustcall obligations of the previous value of `s`. + // :: error: required.method.not.called + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { } + } - { - try { - // :: error: required.method.not.called - Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } + { + try { + // :: error: required.method.not.called + Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { } + } - { - Socket s1 = null; - try { - s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { - } - s1.close(); + { + Socket s1 = null; + try { + s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { } + s1.close(); + } - public InstanceInitializer() throws Exception { - // :: error: required.method.not.called - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } + public InstanceInitializer() throws Exception { + // :: error: required.method.not.called + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } } diff --git a/checker/tests/resourceleak/IsClosed.java b/checker/tests/resourceleak/IsClosed.java index 207027f646e..44150381f30 100644 --- a/checker/tests/resourceleak/IsClosed.java +++ b/checker/tests/resourceleak/IsClosed.java @@ -1,29 +1,28 @@ // A test that the EnsuresCalledMethodsIf annotations in // the Socket stub files are respected. -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.*; import java.net.*; +import org.checkerframework.checker.mustcall.qual.Owning; class IsClosed { - void test_socket(@Owning Socket sock) { - if (!sock.isClosed()) { - try { - sock.close(); - } catch (IOException io) { + void test_socket(@Owning Socket sock) { + if (!sock.isClosed()) { + try { + sock.close(); + } catch (IOException io) { - } - } + } } + } - void test_server_socket(@Owning ServerSocket sock) { - if (!sock.isClosed()) { - try { - sock.close(); - } catch (IOException io) { + void test_server_socket(@Owning ServerSocket sock) { + if (!sock.isClosed()) { + try { + sock.close(); + } catch (IOException io) { - } - } + } } + } } diff --git a/checker/tests/resourceleak/Issue4815.java b/checker/tests/resourceleak/Issue4815.java index 212d7cdeb82..ec9178ad1e5 100644 --- a/checker/tests/resourceleak/Issue4815.java +++ b/checker/tests/resourceleak/Issue4815.java @@ -1,18 +1,17 @@ // Test case for https://tinyurl.com/cfissue/4815 +import java.util.List; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; -import java.util.List; - public class Issue4815 { - public void initialize( - List list, @Owning @MustCall("initialize") T object) { - object.initialize(); - list.add(object); - } + public void initialize( + List list, @Owning @MustCall("initialize") T object) { + object.initialize(); + list.add(object); + } - private static class Component { - void initialize() {} - } + private static class Component { + void initialize() {} + } } diff --git a/checker/tests/resourceleak/Issue6030.java b/checker/tests/resourceleak/Issue6030.java index 7ae668c4442..abb621a5cfd 100644 --- a/checker/tests/resourceleak/Issue6030.java +++ b/checker/tests/resourceleak/Issue6030.java @@ -1,30 +1,29 @@ // Test case for https://github.com/typetools/checker-framework/issues/6030 -import org.checkerframework.checker.mustcall.qual.Owning; - import java.util.*; +import org.checkerframework.checker.mustcall.qual.Owning; public class Issue6030 { - interface CloseableIterator extends Iterator, java.io.Closeable {} - - static class MyScanner> implements CloseableIterator { - @Owning I iterator; + interface CloseableIterator extends Iterator, java.io.Closeable {} - // :: error: missing.creates.mustcall.for - public boolean hasNext() { - if (iterator == null) iterator = createIterator(); - return iterator.hasNext(); - } + static class MyScanner> implements CloseableIterator { + @Owning I iterator; - public T next() { - return null; - } + // :: error: missing.creates.mustcall.for + public boolean hasNext() { + if (iterator == null) iterator = createIterator(); + return iterator.hasNext(); + } - private I createIterator() { - return null; - } + public T next() { + return null; + } - public void close() {} + private I createIterator() { + return null; } + + public void close() {} + } } diff --git a/checker/tests/resourceleak/JavaEETest.java b/checker/tests/resourceleak/JavaEETest.java index b4fc459de04..bb1f7431b49 100644 --- a/checker/tests/resourceleak/JavaEETest.java +++ b/checker/tests/resourceleak/JavaEETest.java @@ -1,7 +1,7 @@ // Test for https://github.com/typetools/checker-framework/issues/5472 class JavaEETest { - static void foo(javax.servlet.ServletResponse s) throws java.io.IOException { - s.getWriter(); - } + static void foo(javax.servlet.ServletResponse s) throws java.io.IOException { + s.getWriter(); + } } diff --git a/checker/tests/resourceleak/LemmaStack.java b/checker/tests/resourceleak/LemmaStack.java index 2aaeae52e77..b8348656899 100644 --- a/checker/tests/resourceleak/LemmaStack.java +++ b/checker/tests/resourceleak/LemmaStack.java @@ -9,41 +9,40 @@ // ^ // 1 error +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.UncheckedIOException; - @MustCall("close") public class LemmaStack implements Closeable { - private @Owning @MustCall("close") PrintWriter session; + private @Owning @MustCall("close") PrintWriter session; - @CreatesMustCallFor("this") - @EnsuresNonNull("session") - private void startProver() { - try { - if (session != null) { - session.close(); - } - session = new PrintWriter("filename.txt"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + @CreatesMustCallFor("this") + @EnsuresNonNull("session") + private void startProver() { + try { + if (session != null) { + session.close(); + } + session = new PrintWriter("filename.txt"); + } catch (IOException e) { + throw new UncheckedIOException(e); } + } - public LemmaStack() { - startProver(); - } + public LemmaStack() { + startProver(); + } - @EnsuresCalledMethods(value = "session", methods = "close") - @Override - public void close(LemmaStack this) { - session.close(); - } + @EnsuresCalledMethods(value = "session", methods = "close") + @Override + public void close(LemmaStack this) { + session.close(); + } } diff --git a/checker/tests/resourceleak/LhsArrayCast.java b/checker/tests/resourceleak/LhsArrayCast.java index 452fe70d1e2..e324f9f185b 100644 --- a/checker/tests/resourceleak/LhsArrayCast.java +++ b/checker/tests/resourceleak/LhsArrayCast.java @@ -1,8 +1,8 @@ class LhsArrayCast { - void populateWithSamples(Object[] currentSample) { - int j = 22; - int k = 42; - ((String[]) currentSample[j])[k] = "hello"; - } + void populateWithSamples(Object[] currentSample) { + int j = 22; + int k = 42; + ((String[]) currentSample[j])[k] = "hello"; + } } diff --git a/checker/tests/resourceleak/LineNumberReaderTest.java b/checker/tests/resourceleak/LineNumberReaderTest.java index 011c0df7daf..73813de5b3f 100644 --- a/checker/tests/resourceleak/LineNumberReaderTest.java +++ b/checker/tests/resourceleak/LineNumberReaderTest.java @@ -1,38 +1,37 @@ +import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import java.io.*; - class LineNumberReaderTest implements Closeable { - private final @Owning LineNumberReader reader; + private final @Owning LineNumberReader reader; - LineNumberReaderTest(File toRead) { - LineNumberReader reader = null; + LineNumberReaderTest(File toRead) { + LineNumberReader reader = null; + try { + reader = new LineNumberReader(new FileReader(toRead)); + this.reader = reader; + advance(); + } catch (IOException e) { + if (reader != null) { try { - reader = new LineNumberReader(new FileReader(toRead)); - this.reader = reader; - advance(); - } catch (IOException e) { - if (reader != null) { - try { - reader.close(); - } catch (Exception exceptionOnClose) { - e.addSuppressed(exceptionOnClose); - } - } - throw new RuntimeException(e); + reader.close(); + } catch (Exception exceptionOnClose) { + e.addSuppressed(exceptionOnClose); } + } + throw new RuntimeException(e); } + } - @RequiresNonNull("reader") - protected void advance(@UnknownInitialization LineNumberReaderTest this) throws IOException {} + @RequiresNonNull("reader") + protected void advance(@UnknownInitialization LineNumberReaderTest this) throws IOException {} - @Override - @EnsuresCalledMethods(value = "reader", methods = "close") - public void close() throws IOException { - reader.close(); - } + @Override + @EnsuresCalledMethods(value = "reader", methods = "close") + public void close() throws IOException { + reader.close(); + } } diff --git a/checker/tests/resourceleak/MCANotOwningField.java b/checker/tests/resourceleak/MCANotOwningField.java index 5015283c5a1..25aa4087117 100644 --- a/checker/tests/resourceleak/MCANotOwningField.java +++ b/checker/tests/resourceleak/MCANotOwningField.java @@ -5,13 +5,13 @@ class MCANotOwningField { - final Socket s; + final Socket s; - MCANotOwningField(Socket s) throws Exception { - this.s = s; - } + MCANotOwningField(Socket s) throws Exception { + this.s = s; + } - void simple() throws Exception { - s.getInputStream(); - } + void simple() throws Exception { + s.getInputStream(); + } } diff --git a/checker/tests/resourceleak/MCAOwningField.java b/checker/tests/resourceleak/MCAOwningField.java index 4173742c8cf..ae5be0d4196 100644 --- a/checker/tests/resourceleak/MCAOwningField.java +++ b/checker/tests/resourceleak/MCAOwningField.java @@ -1,26 +1,25 @@ // A test case for a common pattern in Zookeeper: something is must-call-alias // with an owning field, and therefore a false positive was issued. +import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.net.Socket; - @InheritableMustCall("stop") class MCAOwningField { - @Owning final Socket s; + @Owning final Socket s; - MCAOwningField() throws Exception { - s = new Socket(); - } + MCAOwningField() throws Exception { + s = new Socket(); + } - void simple() throws Exception { - s.getInputStream(); - } + void simple() throws Exception { + s.getInputStream(); + } - @EnsuresCalledMethods(value = "s", methods = "close") - void stop() throws Exception { - s.close(); - } + @EnsuresCalledMethods(value = "s", methods = "close") + void stop() throws Exception { + s.close(); + } } diff --git a/checker/tests/resourceleak/MCAWithThis.java b/checker/tests/resourceleak/MCAWithThis.java index ae76db6fbb2..d9451bb2700 100644 --- a/checker/tests/resourceleak/MCAWithThis.java +++ b/checker/tests/resourceleak/MCAWithThis.java @@ -4,15 +4,15 @@ import java.net.Socket; class MCAWithThis extends Socket { - public MCAWithThis() { - super(); - } + public MCAWithThis() { + super(); + } - public void test() throws Exception { - this.getInputStream(); - } + public void test() throws Exception { + this.getInputStream(); + } - public void test2() throws Exception { - getInputStream(); - } + public void test2() throws Exception { + getInputStream(); + } } diff --git a/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java b/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java index f15c12984c0..ac3547dac63 100644 --- a/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java +++ b/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java @@ -1,26 +1,25 @@ // test for https://github.com/kelloggm/object-construction-checker/issues/326 +import java.io.InputStream; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; -import java.io.InputStream; - class ManualMustCallEmptyOnConstructor { - // Test that writing @MustCall({}) on a constructor results in an error - @InheritableMustCall("a") - static class Foo { - final @Owning InputStream is; + // Test that writing @MustCall({}) on a constructor results in an error + @InheritableMustCall("a") + static class Foo { + final @Owning InputStream is; - // :: error: inconsistent.constructor.type - @MustCall({}) Foo(@Owning InputStream is) { - this.is = is; - } + // :: error: inconsistent.constructor.type + @MustCall({}) Foo(@Owning InputStream is) { + this.is = is; + } - @EnsuresCalledMethods(value = "this.is", methods = "close") - void a() throws Exception { - is.close(); - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + void a() throws Exception { + is.close(); } + } } diff --git a/checker/tests/resourceleak/MultipleIdenticalReturns.java b/checker/tests/resourceleak/MultipleIdenticalReturns.java index ba24c6fde73..f25c0a5ee5c 100644 --- a/checker/tests/resourceleak/MultipleIdenticalReturns.java +++ b/checker/tests/resourceleak/MultipleIdenticalReturns.java @@ -6,32 +6,32 @@ * a method */ class MultipleIdenticalReturns { - static class Repro { - private java.lang.ClassLoader loader; + static class Repro { + private java.lang.ClassLoader loader; - public Object loadClass(final String className) throws ClassNotFoundException { - final String classFile = className.replace('.', '/'); - Object RC = null; - if (RC != null) { - return RC; - } - try (InputStream is = loader.getResourceAsStream(classFile + ".class")) { - // no warning here, since parse() is invoked in parser - ClassParser parser = new ClassParser(); - RC = parser.parse(); - return RC; - } catch (final IOException e) { - throw new ClassNotFoundException(className + " not found: " + e, e); - } - } + public Object loadClass(final String className) throws ClassNotFoundException { + final String classFile = className.replace('.', '/'); + Object RC = null; + if (RC != null) { + return RC; + } + try (InputStream is = loader.getResourceAsStream(classFile + ".class")) { + // no warning here, since parse() is invoked in parser + ClassParser parser = new ClassParser(); + RC = parser.parse(); + return RC; + } catch (final IOException e) { + throw new ClassNotFoundException(className + " not found: " + e, e); + } } + } - @org.checkerframework.checker.mustcall.qual.InheritableMustCall("parse") - static class ClassParser { - public ClassParser() {} + @org.checkerframework.checker.mustcall.qual.InheritableMustCall("parse") + static class ClassParser { + public ClassParser() {} - public Object parse() { - return null; - } + public Object parse() { + return null; } + } } diff --git a/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java b/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java index 7a7ee24c056..794113d2f70 100644 --- a/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java +++ b/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java @@ -1,92 +1,91 @@ +import java.io.*; +import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; -import java.io.*; -import java.net.*; - class MultipleMethodParamsMustCallAliasTest { - void testMultiMethodParamsCorrect1(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + void testMultiMethodParamsCorrect1(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - r.close(); - } + r.close(); + } - void testMultiMethodParamsCorrect2(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + void testMultiMethodParamsCorrect2(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - try { - in1.close(); - } catch (IOException e) { - } finally { - in2.close(); - } + try { + in1.close(); + } catch (IOException e) { + } finally { + in2.close(); } + } - void testMultiMethodParamsCorrect3(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + void testMultiMethodParamsCorrect3(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - try { - in1.close(); - } finally { - in2.close(); - } + try { + in1.close(); + } finally { + in2.close(); } + } - // :: error: required.method.not.called - void testMultiMethodParamsWrong1(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + // :: error: required.method.not.called + void testMultiMethodParamsWrong1(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - in1.close(); - } + in1.close(); + } - // :: error: required.method.not.called - void testMultiMethodParamsWrong2(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + // :: error: required.method.not.called + void testMultiMethodParamsWrong2(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - in2.close(); - } + in2.close(); + } + // :: error: required.method.not.called + void testMultiMethodParamsWrong3(@Owning InputStream in1) throws IOException { // :: error: required.method.not.called - void testMultiMethodParamsWrong3(@Owning InputStream in1) throws IOException { - // :: error: required.method.not.called - Socket socket = new Socket("address", 12); - ReplicaInputStreams r = new ReplicaInputStreams(in1, socket.getInputStream()); + Socket socket = new Socket("address", 12); + ReplicaInputStreams r = new ReplicaInputStreams(in1, socket.getInputStream()); + } + + class ReplicaInputStreams implements Closeable { + + private final @Owning InputStream in1; + private final @Owning InputStream in2; + + public @MustCallAlias ReplicaInputStreams( + // This class is unsafe: calling close on i1 doesn't result in calling close on i2, + // so this MustCallAlias relationship shouldn't be verified. + // :: error: mustcallalias.out.of.scope + @MustCallAlias InputStream i1, @MustCallAlias InputStream i2) { + this.in1 = i1; + this.in2 = i2; } - class ReplicaInputStreams implements Closeable { - - private final @Owning InputStream in1; - private final @Owning InputStream in2; - - public @MustCallAlias ReplicaInputStreams( - // This class is unsafe: calling close on i1 doesn't result in calling close on i2, - // so this MustCallAlias relationship shouldn't be verified. - // :: error: mustcallalias.out.of.scope - @MustCallAlias InputStream i1, @MustCallAlias InputStream i2) { - this.in1 = i1; - this.in2 = i2; - } - - @Override - @EnsuresCalledMethods( - value = {"this.in1", "this.in2"}, - methods = {"close"}) - // :: error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - in1.close(); - in2.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.in1", "this.in2"}, + methods = {"close"}) + // :: error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + in1.close(); + in2.close(); } + } } diff --git a/checker/tests/resourceleak/MultipleOwnedResources.java b/checker/tests/resourceleak/MultipleOwnedResources.java index 5ae8e56825b..5e34f57b02c 100644 --- a/checker/tests/resourceleak/MultipleOwnedResources.java +++ b/checker/tests/resourceleak/MultipleOwnedResources.java @@ -1,29 +1,28 @@ // Test case for https://github.com/typetools/checker-framework/issues/5911 +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MultipleOwnedResources implements Closeable { - private final @Owning Closeable r1; - private final @Owning Closeable r2; + private final @Owning Closeable r1; + private final @Owning Closeable r2; - public MultipleOwnedResources(@Owning Closeable r1, @Owning Closeable r2) { - this.r1 = r1; - this.r2 = r2; - } + public MultipleOwnedResources(@Owning Closeable r1, @Owning Closeable r2) { + this.r1 = r1; + this.r2 = r2; + } - @Override - @EnsuresCalledMethods( - value = {"r1", "r2"}, - methods = {"close"}) - public void close() throws IOException { - try { - r1.close(); - } finally { - r2.close(); - } + @Override + @EnsuresCalledMethods( + value = {"r1", "r2"}, + methods = {"close"}) + public void close() throws IOException { + try { + r1.close(); + } finally { + r2.close(); } + } } diff --git a/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java b/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java index 1478d1f4676..5a656764a6d 100644 --- a/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java +++ b/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java @@ -5,40 +5,40 @@ class MultipleOwnedResourcesOfDifferentTypes { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + @InheritableMustCall("b") + static class Bar { + void b() {} + } + + @InheritableMustCall("finalizer") + static class OwningField { + + private final @Owning Foo owningFoo; + + private final @Owning Bar owningBar; - @InheritableMustCall("b") - static class Bar { - void b() {} + public OwningField() { + this.owningFoo = new Foo(); + this.owningBar = new Bar(); } - @InheritableMustCall("finalizer") - static class OwningField { - - private final @Owning Foo owningFoo; - - private final @Owning Bar owningBar; - - public OwningField() { - this.owningFoo = new Foo(); - this.owningBar = new Bar(); - } - - @EnsuresCalledMethods( - value = {"owningBar"}, - methods = {"b"}) - @EnsuresCalledMethods( - value = {"this.owningFoo"}, - methods = {"a"}) - void finalizer() { - try { - this.owningFoo.a(); - } finally { - this.owningBar.b(); - } - } + @EnsuresCalledMethods( + value = {"owningBar"}, + methods = {"b"}) + @EnsuresCalledMethods( + value = {"this.owningFoo"}, + methods = {"a"}) + void finalizer() { + try { + this.owningFoo.a(); + } finally { + this.owningBar.b(); + } } + } } diff --git a/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java b/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java index 4e61e6c94fa..9c3650f5a4a 100644 --- a/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java +++ b/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java @@ -9,38 +9,38 @@ class MustCallAliasDifferentMethodNames { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - @InheritableMustCall("b") - static class FooField { - private final @Owning Foo finalOwningFoo; + @InheritableMustCall("a") + static class Foo { + void a() {} + } - public @MustCallAlias FooField(@MustCallAlias Foo f) { - this.finalOwningFoo = f; - } - - @EnsuresCalledMethods( - value = {"this.finalOwningFoo"}, - methods = {"a"}) - void b() { - this.finalOwningFoo.a(); - } - } + @InheritableMustCall("b") + static class FooField { + private final @Owning Foo finalOwningFoo; - void testField1() { - Foo f = new Foo(); - FooField fooFieldWrapper = new FooField(f); - // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation - fooFieldWrapper.b(); + public @MustCallAlias FooField(@MustCallAlias Foo f) { + this.finalOwningFoo = f; } - void testField2() { - Foo f = new Foo(); - FooField fooFieldWrapper = new FooField(f); - // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation - f.a(); + @EnsuresCalledMethods( + value = {"this.finalOwningFoo"}, + methods = {"a"}) + void b() { + this.finalOwningFoo.a(); } + } + + void testField1() { + Foo f = new Foo(); + FooField fooFieldWrapper = new FooField(f); + // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation + fooFieldWrapper.b(); + } + + void testField2() { + Foo f = new Foo(); + FooField fooFieldWrapper = new FooField(f); + // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation + f.a(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasExamples.java b/checker/tests/resourceleak/MustCallAliasExamples.java index d21d5908c6e..e45513be55d 100644 --- a/checker/tests/resourceleak/MustCallAliasExamples.java +++ b/checker/tests/resourceleak/MustCallAliasExamples.java @@ -1,54 +1,53 @@ // Simple tests of @MustCallAlias functionality on wrapper streams. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.io.IOException; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; class MustCallAliasExamples { - void test_two_locals(String address) { - Socket socket = null; - try { - socket = new Socket(address, 8000); - DataInputStream d = new DataInputStream(socket.getInputStream()); - } catch (IOException e) { - - } finally { - closeSocket(socket); - } - } - - void test_close_wrapper(@Owning InputStream b) throws IOException { - DataInputStream d = new DataInputStream(b); - d.close(); - } - - void test_close_nonwrapper(@Owning InputStream b) throws IOException { - DataInputStream d = new DataInputStream(b); - b.close(); - } + void test_two_locals(String address) { + Socket socket = null; + try { + socket = new Socket(address, 8000); + DataInputStream d = new DataInputStream(socket.getInputStream()); + } catch (IOException e) { - // :: error: required.method.not.called - void test_no_close(@Owning InputStream b) { - DataInputStream d = new DataInputStream(b); + } finally { + closeSocket(socket); } + } + + void test_close_wrapper(@Owning InputStream b) throws IOException { + DataInputStream d = new DataInputStream(b); + d.close(); + } + + void test_close_nonwrapper(@Owning InputStream b) throws IOException { + DataInputStream d = new DataInputStream(b); + b.close(); + } + + // :: error: required.method.not.called + void test_no_close(@Owning InputStream b) { + DataInputStream d = new DataInputStream(b); + } + + // :: error: required.method.not.called + void test_no_assign(@Owning InputStream b) { + new DataInputStream(new BufferedInputStream(b)); + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeSocket(Socket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { - // :: error: required.method.not.called - void test_no_assign(@Owning InputStream b) { - new DataInputStream(new BufferedInputStream(b)); - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeSocket(Socket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { - - } } + } } diff --git a/checker/tests/resourceleak/MustCallAliasImpl.java b/checker/tests/resourceleak/MustCallAliasImpl.java index 91f17a4b915..66b6794c089 100644 --- a/checker/tests/resourceleak/MustCallAliasImpl.java +++ b/checker/tests/resourceleak/MustCallAliasImpl.java @@ -1,24 +1,23 @@ // A simple test that the extra obligations that MustCallAlias imposes are // respected. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public class MustCallAliasImpl implements Closeable { - final @Owning Closeable foo; + final @Owning Closeable foo; - public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { - this.foo = foo; - } + public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { + this.foo = foo; + } - @Override - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) - public void close() throws IOException { - this.foo.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"close"}) + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasImplWrong1.java b/checker/tests/resourceleak/MustCallAliasImplWrong1.java index 7d07c69e6ba..912cc7b0b7b 100644 --- a/checker/tests/resourceleak/MustCallAliasImplWrong1.java +++ b/checker/tests/resourceleak/MustCallAliasImplWrong1.java @@ -2,25 +2,24 @@ // respected. This version gets it wrong by not assigning the MCA param // to a field. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public class MustCallAliasImplWrong1 implements Closeable { - final @Owning Closeable foo; + final @Owning Closeable foo; - // :: error: mustcallalias.out.of.scope - public @MustCallAlias MustCallAliasImplWrong1(@MustCallAlias Closeable foo) { - this.foo = null; - } + // :: error: mustcallalias.out.of.scope + public @MustCallAlias MustCallAliasImplWrong1(@MustCallAlias Closeable foo) { + this.foo = null; + } - @Override - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) - public void close() throws IOException { - this.foo.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"close"}) + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasImplWrong2.java b/checker/tests/resourceleak/MustCallAliasImplWrong2.java index e031e88eeb2..a5943151058 100644 --- a/checker/tests/resourceleak/MustCallAliasImplWrong2.java +++ b/checker/tests/resourceleak/MustCallAliasImplWrong2.java @@ -2,24 +2,23 @@ // respected. This version gets it wrong by assigning the MCA param to a non-owning // field. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public class MustCallAliasImplWrong2 implements Closeable { - final /*@Owning*/ Closeable foo; + final /*@Owning*/ Closeable foo; - // :: error: (mustcallalias.out.of.scope) - public @MustCallAlias MustCallAliasImplWrong2(@MustCallAlias Closeable foo) { - // The following error isn't really desirable, but occurs because the special case - // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields - // is not triggered. - // :: error: (assignment.type.incompatible) - this.foo = foo; - } + // :: error: (mustcallalias.out.of.scope) + public @MustCallAlias MustCallAliasImplWrong2(@MustCallAlias Closeable foo) { + // The following error isn't really desirable, but occurs because the special case + // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields + // is not triggered. + // :: error: (assignment.type.incompatible) + this.foo = foo; + } - @Override - public void close() {} + @Override + public void close() {} } diff --git a/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java b/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java index 18701862772..07f964c9438 100644 --- a/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java +++ b/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java @@ -1,33 +1,32 @@ // Test that methods like `allocateAndInitializeService` are accepted by // the resource leak checker. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public class MustCallAliasInitializeWithOwningParameter { - public Service allocateAndInitializeService(@Owning Closeable resource) throws IOException { - Service service = new Service(resource); - service.initialize(); - return service; - } + public Service allocateAndInitializeService(@Owning Closeable resource) throws IOException { + Service service = new Service(resource); + service.initialize(); + return service; + } - private static class Service implements Closeable { + private static class Service implements Closeable { - private final @Owning Closeable wrappedResource; + private final @Owning Closeable wrappedResource; - public @MustCallAlias Service(@MustCallAlias Closeable resource) { - this.wrappedResource = resource; - } + public @MustCallAlias Service(@MustCallAlias Closeable resource) { + this.wrappedResource = resource; + } - public void initialize() throws IOException {} + public void initialize() throws IOException {} - @Override - @EnsuresCalledMethods(value = "wrappedResource", methods = "close") - public void close() throws IOException { - wrappedResource.close(); - } + @Override + @EnsuresCalledMethods(value = "wrappedResource", methods = "close") + public void close() throws IOException { + wrappedResource.close(); } + } } diff --git a/checker/tests/resourceleak/MustCallAliasLayeredStreams.java b/checker/tests/resourceleak/MustCallAliasLayeredStreams.java index 29303050e63..2129a1206a6 100644 --- a/checker/tests/resourceleak/MustCallAliasLayeredStreams.java +++ b/checker/tests/resourceleak/MustCallAliasLayeredStreams.java @@ -1,23 +1,22 @@ // Test case based on an MCA situation in Zookeeper. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasLayeredStreams { - InputStream cache; + InputStream cache; - public InputStream createInputStream(String filename) throws FileNotFoundException { - if (cache == null) { - // The real version of this uses a mix of JDK and custom streams, so it makes more - // sense... - // TODO we shouldn't report a warning here and the code is okay because the cache is - // non-owning, and the caller of createInputStream is the owner of all of these streams. - cache = - new DataInputStream( - // :: error: required.method.not.called - new BufferedInputStream(new FileInputStream(new File(filename)))); - } - return cache; + public InputStream createInputStream(String filename) throws FileNotFoundException { + if (cache == null) { + // The real version of this uses a mix of JDK and custom streams, so it makes more + // sense... + // TODO we shouldn't report a warning here and the code is okay because the cache is + // non-owning, and the caller of createInputStream is the owner of all of these streams. + cache = + new DataInputStream( + // :: error: required.method.not.called + new BufferedInputStream(new FileInputStream(new File(filename)))); } + return cache; + } } diff --git a/checker/tests/resourceleak/MustCallAliasLocal.java b/checker/tests/resourceleak/MustCallAliasLocal.java index 86b60579bdd..7e6afcfb511 100644 --- a/checker/tests/resourceleak/MustCallAliasLocal.java +++ b/checker/tests/resourceleak/MustCallAliasLocal.java @@ -1,27 +1,26 @@ // Test case for a set of false positives caused by the Must Call Checker's handling // of assigning @MustCallAlias parameters to @Owning fields as a special case. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public class MustCallAliasLocal implements Closeable { - final @Owning Closeable foo; + final @Owning Closeable foo; - public @MustCallAlias MustCallAliasLocal(@MustCallAlias Closeable foo) { - Closeable local = foo; - // The error on the following line is a false positive: - // :: error: (assignment.type.incompatible) - this.foo = local; - } + public @MustCallAlias MustCallAliasLocal(@MustCallAlias Closeable foo) { + Closeable local = foo; + // The error on the following line is a false positive: + // :: error: (assignment.type.incompatible) + this.foo = local; + } - @Override - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) - public void close() throws IOException { - this.foo.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"close"}) + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasNormalExit.java b/checker/tests/resourceleak/MustCallAliasNormalExit.java index 103d341ac41..5982d3c56eb 100644 --- a/checker/tests/resourceleak/MustCallAliasNormalExit.java +++ b/checker/tests/resourceleak/MustCallAliasNormalExit.java @@ -1,74 +1,73 @@ // Test case for https://github.com/typetools/checker-framework/issues/5597 +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - @InheritableMustCall("close") public class MustCallAliasNormalExit { - final @Owning InputStream is; + final @Owning InputStream is; - @MustCallAlias MustCallAliasNormalExit(@MustCallAlias InputStream p, boolean b) throws Exception { - if (b) { - throw new Exception("an exception!"); - } - this.is = p; + @MustCallAlias MustCallAliasNormalExit(@MustCallAlias InputStream p, boolean b) throws Exception { + if (b) { + throw new Exception("an exception!"); } + this.is = p; + } - @EnsuresCalledMethods(value = "this.is", methods = "close") - public void close() throws IOException { - this.is.close(); - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } - public static @MustCallAlias MustCallAliasNormalExit mcaneFactory(InputStream is) - throws Exception { - return new MustCallAliasNormalExit(is, false); - } + public static @MustCallAlias MustCallAliasNormalExit mcaneFactory(InputStream is) + throws Exception { + return new MustCallAliasNormalExit(is, false); + } - // :: error: required.method.not.called - public static void testUse1(@Owning InputStream inputStream) throws IOException { - MustCallAliasNormalExit mcane = null; - try { - mcane = new MustCallAliasNormalExit(inputStream, true); // at run time, this WILL throw - } catch (Exception e) { - // At run time would fail (NPE), but for illustrative purposes this is fine. - // This absolutely must not cause inputStream to be considered closed because of the - // MustCallAlias - // relationship. - mcane.close(); - } + // :: error: required.method.not.called + public static void testUse1(@Owning InputStream inputStream) throws IOException { + MustCallAliasNormalExit mcane = null; + try { + mcane = new MustCallAliasNormalExit(inputStream, true); // at run time, this WILL throw + } catch (Exception e) { + // At run time would fail (NPE), but for illustrative purposes this is fine. + // This absolutely must not cause inputStream to be considered closed because of the + // MustCallAlias + // relationship. + mcane.close(); } + } - // :: error: required.method.not.called - public static void testUse2(@Owning InputStream inputStream) throws IOException { - MustCallAliasNormalExit mcane = null; - try { - mcane = mcaneFactory(inputStream); - } catch (Exception e) { - mcane.close(); - } + // :: error: required.method.not.called + public static void testUse2(@Owning InputStream inputStream) throws IOException { + MustCallAliasNormalExit mcane = null; + try { + mcane = mcaneFactory(inputStream); + } catch (Exception e) { + mcane.close(); } + } - // :: error: required.method.not.called - public static void testUse3(@Owning InputStream inputStream) throws Exception { - // if mcaneFactory throws, then inputStream goes out of scope w/o being closed - MustCallAliasNormalExit mcane = mcaneFactory(inputStream); - mcane.close(); - } + // :: error: required.method.not.called + public static void testUse3(@Owning InputStream inputStream) throws Exception { + // if mcaneFactory throws, then inputStream goes out of scope w/o being closed + MustCallAliasNormalExit mcane = mcaneFactory(inputStream); + mcane.close(); + } - // TODO: this appears to be a false positive, but the RLC doesn't handle it correctly because - // close() is called on different aliases on different branches. - // :: error: required.method.not.called - public static void testUse4(@Owning InputStream inputStream) throws Exception { - MustCallAliasNormalExit mcane = null; - try { - mcane = mcaneFactory(inputStream); - } catch (Exception e) { - // this makes it safe - inputStream.close(); - } - mcane.close(); + // TODO: this appears to be a false positive, but the RLC doesn't handle it correctly because + // close() is called on different aliases on different branches. + // :: error: required.method.not.called + public static void testUse4(@Owning InputStream inputStream) throws Exception { + MustCallAliasNormalExit mcane = null; + try { + mcane = mcaneFactory(inputStream); + } catch (Exception e) { + // this makes it safe + inputStream.close(); } + mcane.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasNotThis.java b/checker/tests/resourceleak/MustCallAliasNotThis.java index 8082b9aa61c..7f267d016c9 100644 --- a/checker/tests/resourceleak/MustCallAliasNotThis.java +++ b/checker/tests/resourceleak/MustCallAliasNotThis.java @@ -1,56 +1,55 @@ // A test case with examples of code that shouldn't pass the checker where an @MustCallAlias // parameter is passed to an owning field, but not an owning field of this. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public class MustCallAliasNotThis implements Closeable { - @Owning Closeable foo; - - // Both of these constructors are wrong: the first assigns to the owning field of another - // object of the same class, but not the "this" object; the second assigns to another class' - // owning field entirely. Both of these assignments would require @CreatesMustCallFor - // annotations - // to verify, so it's okay that the @MustCallAlias annotations are verified here (because it - // is impossible to write the required @CreatesMustCallFor annotations, since they can only be - // written on methods, not on constructors). If we ever permit @CreatesMustCallFor annotations - // on constructors, this test should be revisited: it might be necessary to make a corresponding - // change in the rules for verifying @MustCallAlias. - - // :: error: missing.creates.mustcall.for - public @MustCallAlias MustCallAliasNotThis( - @MustCallAlias Closeable foo, MustCallAliasNotThis other) throws IOException { - other.close(); - other.foo = foo; - } - - // :: error: missing.creates.mustcall.for - public @MustCallAlias MustCallAliasNotThis(@MustCallAlias Closeable foo, Bar other) - throws IOException { - other.close(); - other.baz = foo; - } + @Owning Closeable foo; + + // Both of these constructors are wrong: the first assigns to the owning field of another + // object of the same class, but not the "this" object; the second assigns to another class' + // owning field entirely. Both of these assignments would require @CreatesMustCallFor + // annotations + // to verify, so it's okay that the @MustCallAlias annotations are verified here (because it + // is impossible to write the required @CreatesMustCallFor annotations, since they can only be + // written on methods, not on constructors). If we ever permit @CreatesMustCallFor annotations + // on constructors, this test should be revisited: it might be necessary to make a corresponding + // change in the rules for verifying @MustCallAlias. + + // :: error: missing.creates.mustcall.for + public @MustCallAlias MustCallAliasNotThis( + @MustCallAlias Closeable foo, MustCallAliasNotThis other) throws IOException { + other.close(); + other.foo = foo; + } + + // :: error: missing.creates.mustcall.for + public @MustCallAlias MustCallAliasNotThis(@MustCallAlias Closeable foo, Bar other) + throws IOException { + other.close(); + other.baz = foo; + } + + @Override + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"close"}) + public void close() throws IOException { + this.foo.close(); + } + + class Bar implements Closeable { + @Owning Closeable baz; @Override @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) + value = {"this.baz"}, + methods = {"close"}) public void close() throws IOException { - this.foo.close(); - } - - class Bar implements Closeable { - @Owning Closeable baz; - - @Override - @EnsuresCalledMethods( - value = {"this.baz"}, - methods = {"close"}) - public void close() throws IOException { - this.baz.close(); - } + this.baz.close(); } + } } diff --git a/checker/tests/resourceleak/MustCallAliasNullConstructor.java b/checker/tests/resourceleak/MustCallAliasNullConstructor.java index d9add720829..ccaac0400da 100644 --- a/checker/tests/resourceleak/MustCallAliasNullConstructor.java +++ b/checker/tests/resourceleak/MustCallAliasNullConstructor.java @@ -1,27 +1,26 @@ // test for https://github.com/typetools/checker-framework/issues/5777 -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.Closeable; import java.io.IOException; import java.net.Socket; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; public class MustCallAliasNullConstructor implements Closeable { - @Owning Socket socket; + @Owning Socket socket; - @MustCallAlias MustCallAliasNullConstructor(@MustCallAlias Socket s) throws IOException { - if (this.socket != null) { - this.socket.close(); - } - this.socket = s; - // :: error: required.method.not.called - this.socket = null; + @MustCallAlias MustCallAliasNullConstructor(@MustCallAlias Socket s) throws IOException { + if (this.socket != null) { + this.socket.close(); } + this.socket = s; + // :: error: required.method.not.called + this.socket = null; + } - @Override - @EnsuresCalledMethods(value = "this.socket", methods = "close") - public void close() throws IOException { - this.socket.close(); - } + @Override + @EnsuresCalledMethods(value = "this.socket", methods = "close") + public void close() throws IOException { + this.socket.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasOwningField.java b/checker/tests/resourceleak/MustCallAliasOwningField.java index 95509805295..1c7ace724d1 100644 --- a/checker/tests/resourceleak/MustCallAliasOwningField.java +++ b/checker/tests/resourceleak/MustCallAliasOwningField.java @@ -1,30 +1,29 @@ // Based on a MustCallAlias scenario in Zookeeper. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public @InheritableMustCall("shutdown") class MustCallAliasOwningField { - private final @Owning BufferedInputStream input; + private final @Owning BufferedInputStream input; - public MustCallAliasOwningField(@Owning BufferedInputStream input, boolean b) { - this.input = input; - if (b) { - DataInputStream d = new DataInputStream(input); - authenticate(d); - } + public MustCallAliasOwningField(@Owning BufferedInputStream input, boolean b) { + this.input = input; + if (b) { + DataInputStream d = new DataInputStream(input); + authenticate(d); } + } - @EnsuresCalledMethods(value = "this.input", methods = "close") - public void shutdown() throws IOException { - input.close(); - } + @EnsuresCalledMethods(value = "this.input", methods = "close") + public void shutdown() throws IOException { + input.close(); + } - public static void authenticate(InputStream is) {} + public static void authenticate(InputStream is) {} - public void wrapField() { - DataInputStream dis = new DataInputStream(input); - } + public void wrapField() { + DataInputStream dis = new DataInputStream(input); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthrough.java b/checker/tests/resourceleak/MustCallAliasPassthrough.java index d3dd4bcbef8..86a58ca7ccc 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthrough.java +++ b/checker/tests/resourceleak/MustCallAliasPassthrough.java @@ -1,13 +1,12 @@ // A test that a class can extend another class with an MCA constructor, // and have its own constructor be MCA as well. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthrough extends FilterInputStream { - @MustCallAlias MustCallAliasPassthrough(@MustCallAlias InputStream is) { - super(is); - } + @MustCallAlias MustCallAliasPassthrough(@MustCallAlias InputStream is) { + super(is); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughChain.java b/checker/tests/resourceleak/MustCallAliasPassthroughChain.java index 4bebae6aa29..c75c3d05a80 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughChain.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughChain.java @@ -2,53 +2,52 @@ // chain // leads to errors. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughChain { - static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { - return is; - } - - static @MustCallAlias InputStream chain1(@MustCallAlias InputStream is) { - return withMCA(is); - } - - static @MustCallAlias InputStream chain2(@MustCallAlias InputStream is) { - InputStream s = withMCA(is); - return s; - } - - static @MustCallAlias InputStream chain3(@MustCallAlias InputStream is) { - return withMCA(chain1(is)); - } - - static @MustCallAlias InputStream chain4(@MustCallAlias InputStream is) { - return withMCA(chain1(chain3(is))); - } - - static @MustCallAlias InputStream chain5(@MustCallAlias InputStream is) { - InputStream s = withMCA(chain1(is)); - return s; - } - - // :: error: mustcallalias.out.of.scope - static @MustCallAlias InputStream chain_bad1(@MustCallAlias InputStream is) { - InputStream s = withMCA(chain1(is)); - return null; - } - - // :: error: mustcallalias.out.of.scope - static @MustCallAlias InputStream chain_bad2(@MustCallAlias InputStream is) { - withMCA(chain1(is)); - return null; - } - - // :: error: mustcallalias.out.of.scope - static @MustCallAlias InputStream chain_bad3(@MustCallAlias InputStream is, boolean b) { - return b ? null : withMCA(chain1(is)); - } + static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { + return is; + } + + static @MustCallAlias InputStream chain1(@MustCallAlias InputStream is) { + return withMCA(is); + } + + static @MustCallAlias InputStream chain2(@MustCallAlias InputStream is) { + InputStream s = withMCA(is); + return s; + } + + static @MustCallAlias InputStream chain3(@MustCallAlias InputStream is) { + return withMCA(chain1(is)); + } + + static @MustCallAlias InputStream chain4(@MustCallAlias InputStream is) { + return withMCA(chain1(chain3(is))); + } + + static @MustCallAlias InputStream chain5(@MustCallAlias InputStream is) { + InputStream s = withMCA(chain1(is)); + return s; + } + + // :: error: mustcallalias.out.of.scope + static @MustCallAlias InputStream chain_bad1(@MustCallAlias InputStream is) { + InputStream s = withMCA(chain1(is)); + return null; + } + + // :: error: mustcallalias.out.of.scope + static @MustCallAlias InputStream chain_bad2(@MustCallAlias InputStream is) { + withMCA(chain1(is)); + return null; + } + + // :: error: mustcallalias.out.of.scope + static @MustCallAlias InputStream chain_bad3(@MustCallAlias InputStream is, boolean b) { + return b ? null : withMCA(chain1(is)); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java b/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java index f51266173cf..67424865bea 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java @@ -1,23 +1,22 @@ // A test that passing a local to an MCA super constructor is allowed. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughLocal extends FilterInputStream { - MustCallAliasPassthroughLocal(File f) throws Exception { - // This is safe - this MCA constructor of FilterInputStream means that the result of this - // constructor - i.e. the caller - is taking ownership of this newly-created output stream. - super(new FileInputStream(f)); - } + MustCallAliasPassthroughLocal(File f) throws Exception { + // This is safe - this MCA constructor of FilterInputStream means that the result of this + // constructor - i.e. the caller - is taking ownership of this newly-created output stream. + super(new FileInputStream(f)); + } - static void test(File f) throws Exception { - // :: error: required.method.not.called - new MustCallAliasPassthroughLocal(f); - } + static void test(File f) throws Exception { + // :: error: required.method.not.called + new MustCallAliasPassthroughLocal(f); + } - static void test_ok(File f) throws Exception { - new MustCallAliasPassthroughLocal(f).close(); - } + static void test_ok(File f) throws Exception { + new MustCallAliasPassthroughLocal(f).close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughThis.java b/checker/tests/resourceleak/MustCallAliasPassthroughThis.java index 3ecb02e1d11..decc01adf57 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughThis.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughThis.java @@ -1,16 +1,15 @@ // A test that a class can have multiple MCA constructors. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughThis extends FilterInputStream { - @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is) { - super(is); - } + @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is) { + super(is); + } - @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is, int x) { - this(is); - } + @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is, int x) { + this(is); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java index 063f8a9b427..c3a4a05f117 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java @@ -2,14 +2,13 @@ // and have its own constructor be MCA as well. // This version just throws away the input rather than passing it to the super constructor. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughWrong1 extends FilterInputStream { - // :: error: mustcallalias.out.of.scope - @MustCallAlias MustCallAliasPassthroughWrong1(@MustCallAlias InputStream is) { - super(null); - } + // :: error: mustcallalias.out.of.scope + @MustCallAlias MustCallAliasPassthroughWrong1(@MustCallAlias InputStream is) { + super(null); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java index f5a8741d06e..fd7bbb7e09b 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java @@ -5,23 +5,22 @@ // MCA annotation on the return type is super misleading and will lead to FPs. It would be better // to annotate code like this with @Owning on the constructor. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughWrong2 extends FilterInputStream { - // :: error: mustcallalias.out.of.scope - @MustCallAlias MustCallAliasPassthroughWrong2(@MustCallAlias InputStream is) throws Exception { - super(null); - // The following error isn't really desirable, but occurs because the special case - // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields - // is not triggered, and @MustCallAlias is treated as @PolyMustCall otherwise. - // :: error: (argument.type.incompatible) - closeIS(is); - } + // :: error: mustcallalias.out.of.scope + @MustCallAlias MustCallAliasPassthroughWrong2(@MustCallAlias InputStream is) throws Exception { + super(null); + // The following error isn't really desirable, but occurs because the special case + // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields + // is not triggered, and @MustCallAlias is treated as @PolyMustCall otherwise. + // :: error: (argument.type.incompatible) + closeIS(is); + } - void closeIS(@Owning InputStream is) throws Exception { - is.close(); - } + void closeIS(@Owning InputStream is) throws Exception { + is.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java index 7191b2fe438..fedd16c7228 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java @@ -1,29 +1,28 @@ // This is a test for what happens when there's a missing MCA return type. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughWrong3 { - static InputStream missingMCA(@MustCallAlias InputStream is) { - // :: error: (return.type.incompatible) - return is; - } + static InputStream missingMCA(@MustCallAlias InputStream is) { + // :: error: (return.type.incompatible) + return is; + } - static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { - return is; - } + static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { + return is; + } - // :: error: (required.method.not.called) - void use_bad(@Owning InputStream is) throws Exception { - InputStream is2 = missingMCA(is); - is2.close(); - } + // :: error: (required.method.not.called) + void use_bad(@Owning InputStream is) throws Exception { + InputStream is2 = missingMCA(is); + is2.close(); + } - void use_good(@Owning InputStream is) throws Exception { - InputStream is2 = withMCA(is); - is2.close(); - } + void use_good(@Owning InputStream is) throws Exception { + InputStream is2 = withMCA(is); + is2.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java index 1be0c7cfda3..08f0b882ebc 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java @@ -4,15 +4,14 @@ // test for it. Issuing an error here is appropriate, because no aliasing relationship // actually exists after the constructor returns. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class MustCallAliasPassthroughWrong4 extends FilterInputStream { - // :: error: mustcallalias.out.of.scope - @MustCallAlias MustCallAliasPassthroughWrong4(@MustCallAlias InputStream is) throws Exception { - super(null); - is.close(); - } + // :: error: mustcallalias.out.of.scope + @MustCallAlias MustCallAliasPassthroughWrong4(@MustCallAlias InputStream is) throws Exception { + super(null); + is.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasSocketException.java b/checker/tests/resourceleak/MustCallAliasSocketException.java index ef5dffc989a..56db01e82c5 100644 --- a/checker/tests/resourceleak/MustCallAliasSocketException.java +++ b/checker/tests/resourceleak/MustCallAliasSocketException.java @@ -5,24 +5,24 @@ class MustCallAliasSocketException { - public boolean quorumRequireSasl; + public boolean quorumRequireSasl; - // This socket isn't owning, so we shouldn't warn on it. - public void authenticate(Socket sock, String hostName) throws IOException { - if (!quorumRequireSasl) { - // I kept this block in the test case because it demonstrates - // that sock is definitely non-owning. - System.out.println("Skipping SASL authentication as"); - return; - } - try { - DataOutputStream dout = new DataOutputStream(sock.getOutputStream()); - // Before MCA was implemented, the call to getInputStream() below triggered - // a false positive warning that dout had not been closed. - DataInputStream din = new DataInputStream(sock.getInputStream()); - // ~30 lines omitted... - } finally { - // do some other things that are definitely not closing sock - } + // This socket isn't owning, so we shouldn't warn on it. + public void authenticate(Socket sock, String hostName) throws IOException { + if (!quorumRequireSasl) { + // I kept this block in the test case because it demonstrates + // that sock is definitely non-owning. + System.out.println("Skipping SASL authentication as"); + return; } + try { + DataOutputStream dout = new DataOutputStream(sock.getOutputStream()); + // Before MCA was implemented, the call to getInputStream() below triggered + // a false positive warning that dout had not been closed. + DataInputStream din = new DataInputStream(sock.getInputStream()); + // ~30 lines omitted... + } finally { + // do some other things that are definitely not closing sock + } + } } diff --git a/checker/tests/resourceleak/MustCallAliasSubstitution.java b/checker/tests/resourceleak/MustCallAliasSubstitution.java index 2b1d08ec6e2..5e27e189334 100644 --- a/checker/tests/resourceleak/MustCallAliasSubstitution.java +++ b/checker/tests/resourceleak/MustCallAliasSubstitution.java @@ -2,25 +2,24 @@ // substituting a fresh object is not counted as an alias, for the purpose of MustCallAlias // verification. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.Socket; +import org.checkerframework.checker.mustcall.qual.*; class MustCallAliasSubstitution { - // :: error: mustcallalias.out.of.scope - static @MustCallAlias Closeable example(@MustCallAlias Closeable p) throws IOException { - p.close(); - return new Socket("localhost", 5000); - } + // :: error: mustcallalias.out.of.scope + static @MustCallAlias Closeable example(@MustCallAlias Closeable p) throws IOException { + p.close(); + return new Socket("localhost", 5000); + } - // This method demonstrates how a false negative could occur, if no error was issued - // on example(). - void use(Closeable c) throws IOException { - // s never gets closed, but the checker permits this code, because it believes - // that s and c are aliased. - Closeable s = example(c); - c.close(); - } + // This method demonstrates how a false negative could occur, if no error was issued + // on example(). + void use(Closeable c) throws IOException { + // s never gets closed, but the checker permits this code, because it believes + // that s and c are aliased. + Closeable s = example(c); + c.close(); + } } diff --git a/checker/tests/resourceleak/MustCallNullStore.java b/checker/tests/resourceleak/MustCallNullStore.java index 07551a3c19d..0b88c11696d 100644 --- a/checker/tests/resourceleak/MustCallNullStore.java +++ b/checker/tests/resourceleak/MustCallNullStore.java @@ -3,18 +3,18 @@ // input exposing a bug where store after the return is null class MustCallNullStore { - int inflateDirect(ByteBuffer src, ByteBuffer dst) throws java.io.IOException { + int inflateDirect(ByteBuffer src, ByteBuffer dst) throws java.io.IOException { - ByteBuffer presliced = dst; - if (dst.position() > 0) { - dst = dst.slice(); - } + ByteBuffer presliced = dst; + if (dst.position() > 0) { + dst = dst.slice(); + } - int n = 0; - try { - presliced.position(presliced.position() + n); - } finally { - } - return n; + int n = 0; + try { + presliced.position(presliced.position() + n); + } finally { } + return n; + } } diff --git a/checker/tests/resourceleak/MustCloseIntoObject.java b/checker/tests/resourceleak/MustCloseIntoObject.java index 200568364a5..b82c5143c15 100644 --- a/checker/tests/resourceleak/MustCloseIntoObject.java +++ b/checker/tests/resourceleak/MustCloseIntoObject.java @@ -4,8 +4,8 @@ import java.net.Socket; class MustCloseIntoObject { - void test() throws Exception { - // :: error: required.method.not.called - Object o = new Socket("", 0); - } + void test() throws Exception { + // :: error: required.method.not.called + Object o = new Socket("", 0); + } } diff --git a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java index bf2dea2d533..244ff3ffde2 100644 --- a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java +++ b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java @@ -1,89 +1,88 @@ // A test that must-call close errors are not issued when overwriting a field // if the field is definitely null. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import java.io.*; - @InheritableMustCall("close") class NonFinalFieldOnlyOverwrittenIfNull { - @Owning @MonotonicNonNull InputStream is; + @Owning @MonotonicNonNull InputStream is; - @CreatesMustCallFor - void set(String fn) throws FileNotFoundException { - if (is == null) { - is = new FileInputStream(fn); - } + @CreatesMustCallFor + void set(String fn) throws FileNotFoundException { + if (is == null) { + is = new FileInputStream(fn); } + } - @CreatesMustCallFor - void set_after_close(String fn, boolean b) throws IOException { - if (b) { - is.close(); - is = new FileInputStream(fn); - } + @CreatesMustCallFor + void set_after_close(String fn, boolean b) throws IOException { + if (b) { + is.close(); + is = new FileInputStream(fn); } + } - @CreatesMustCallFor - void set_error(String fn, boolean b) throws FileNotFoundException { - if (b) { - // :: error: required.method.not.called - is = new FileInputStream(fn); - } + @CreatesMustCallFor + void set_error(String fn, boolean b) throws FileNotFoundException { + if (b) { + // :: error: required.method.not.called + is = new FileInputStream(fn); } + } - // These three methods are copies of the three above, without the appropriate annotation. - // :: error: missing.creates.mustcall.for - void set2(String fn) throws FileNotFoundException { - if (is == null) { - is = new FileInputStream(fn); - } + // These three methods are copies of the three above, without the appropriate annotation. + // :: error: missing.creates.mustcall.for + void set2(String fn) throws FileNotFoundException { + if (is == null) { + is = new FileInputStream(fn); } + } - // :: error: missing.creates.mustcall.for - void set_after_close2(String fn, boolean b) throws IOException { - if (b) { - is.close(); - is = new FileInputStream(fn); - } + // :: error: missing.creates.mustcall.for + void set_after_close2(String fn, boolean b) throws IOException { + if (b) { + is.close(); + is = new FileInputStream(fn); } + } - // :: error: missing.creates.mustcall.for - void set_error2(String fn, boolean b) throws FileNotFoundException { - if (b) { - // :: error: required.method.not.called - is = new FileInputStream(fn); - } + // :: error: missing.creates.mustcall.for + void set_error2(String fn, boolean b) throws FileNotFoundException { + if (b) { + // :: error: required.method.not.called + is = new FileInputStream(fn); } + } - /* This version of close() doesn't verify, because in the `catch` block - `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker - @EnsuresCalledMethods(value="this.is", methods="close") - void close_real() { - if (is != null) { - try { - is.close(); - } catch (Exception ie) { + /* This version of close() doesn't verify, because in the `catch` block + `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker + @EnsuresCalledMethods(value="this.is", methods="close") + void close_real() { + if (is != null) { + try { + is.close(); + } catch (Exception ie) { - } - } - } */ + } + } + } */ - @EnsuresCalledMethods(value = "this.is", methods = "close") - void close() throws Exception { - if (is != null) { - is.close(); - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + void close() throws Exception { + if (is != null) { + is.close(); } + } - public static void test_leak() throws Exception { - // :: error: required.method.not.called - NonFinalFieldOnlyOverwrittenIfNull n = new NonFinalFieldOnlyOverwrittenIfNull(); - n.close(); - n.set_after_close("bar.txt", true); - } + public static void test_leak() throws Exception { + // :: error: required.method.not.called + NonFinalFieldOnlyOverwrittenIfNull n = new NonFinalFieldOnlyOverwrittenIfNull(); + n.close(); + n.set_after_close("bar.txt", true); + } } diff --git a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java index c06aeba8a9c..182fa5c8e59 100644 --- a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java +++ b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java @@ -1,68 +1,67 @@ // Another test that must-call close errors are not issued when overwriting a field // if the field is definitely null. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import java.io.*; - @InheritableMustCall("close") class NonFinalFieldOnlyOverwrittenIfNull2 { - @Owning @MonotonicNonNull InputStream is; + @Owning @MonotonicNonNull InputStream is; - @CreatesMustCallFor - void set(String fn) throws FileNotFoundException { - if (is == null) { - is = new FileInputStream(fn); - } + @CreatesMustCallFor + void set(String fn) throws FileNotFoundException { + if (is == null) { + is = new FileInputStream(fn); } + } - @CreatesMustCallFor - void set_after_close(String fn, boolean b) throws IOException { - if (b) { - is.close(); - is = new FileInputStream(fn); - } + @CreatesMustCallFor + void set_after_close(String fn, boolean b) throws IOException { + if (b) { + is.close(); + is = new FileInputStream(fn); } + } - @CreatesMustCallFor - void set_error(String fn, boolean b) throws FileNotFoundException { - if (b) { - // :: error: required.method.not.called - is = new FileInputStream(fn); - } + @CreatesMustCallFor + void set_error(String fn, boolean b) throws FileNotFoundException { + if (b) { + // :: error: required.method.not.called + is = new FileInputStream(fn); } + } - /* This version of close() doesn't verify, because in the `catch` block - `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker - @EnsuresCalledMethods(value="this.is", methods="close") - void close_real() { - if (is != null) { - try { - is.close(); - } catch (Exception ie) { + /* This version of close() doesn't verify, because in the `catch` block + `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker + @EnsuresCalledMethods(value="this.is", methods="close") + void close_real() { + if (is != null) { + try { + is.close(); + } catch (Exception ie) { - } - } - } */ + } + } + } */ - @EnsuresCalledMethods(value = "this.is", methods = "close") - @CreatesMustCallFor - void close() throws Exception { - if (is != null) { - is.close(); - is = null; - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + @CreatesMustCallFor + void close() throws Exception { + if (is != null) { + is.close(); + is = null; } + } - public static void test_leak() throws Exception { - // :: error: required.method.not.called - NonFinalFieldOnlyOverwrittenIfNull2 n = new NonFinalFieldOnlyOverwrittenIfNull2(); - n.set("foo.txt"); - n.close(); - n.set("bar.txt"); - } + public static void test_leak() throws Exception { + // :: error: required.method.not.called + NonFinalFieldOnlyOverwrittenIfNull2 n = new NonFinalFieldOnlyOverwrittenIfNull2(); + n.set("foo.txt"); + n.close(); + n.set("bar.txt"); + } } diff --git a/checker/tests/resourceleak/OptionalSocket.java b/checker/tests/resourceleak/OptionalSocket.java index 60f726e8c7f..f1153eeca00 100644 --- a/checker/tests/resourceleak/OptionalSocket.java +++ b/checker/tests/resourceleak/OptionalSocket.java @@ -4,23 +4,22 @@ // which case it's fine with me to skip this test for now. - Martin // @skip-test -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; import java.util.*; +import org.checkerframework.checker.mustcall.qual.*; class OptionalSocket { - void test_close_get_null(@Owning Optional sock) throws IOException { - // TODO can't pass this - if (sock.get() != null) { - // TODO can't pass this - sock.get().close(); - } + void test_close_get_null(@Owning Optional sock) throws IOException { + // TODO can't pass this + if (sock.get() != null) { + // TODO can't pass this + sock.get().close(); } + } - void test_close_get(@Owning Optional sock) throws IOException { - // TODO can't pass this - sock.get().close(); - } + void test_close_get(@Owning Optional sock) throws IOException { + // TODO can't pass this + sock.get().close(); + } } diff --git a/checker/tests/resourceleak/OwnershipTransferAtReassignment.java b/checker/tests/resourceleak/OwnershipTransferAtReassignment.java index 85ddab1d576..53582ab46a0 100644 --- a/checker/tests/resourceleak/OwnershipTransferAtReassignment.java +++ b/checker/tests/resourceleak/OwnershipTransferAtReassignment.java @@ -9,30 +9,30 @@ @InheritableMustCall("disconnect") public class OwnershipTransferAtReassignment { - private @Owning @Nullable Node head = null; + private @Owning @Nullable Node head = null; - @CreatesMustCallFor("this") - public boolean add() { - head = new Node(head); - return true; - } + @CreatesMustCallFor("this") + public boolean add() { + head = new Node(head); + return true; + } - @EnsuresCalledMethods(value = "this.head", methods = "disconnect") - public void disconnect() { - head.disconnect(); - } + @EnsuresCalledMethods(value = "this.head", methods = "disconnect") + public void disconnect() { + head.disconnect(); + } - @InheritableMustCall("disconnect") - private static class Node { - @Owning private final @Nullable Node next; + @InheritableMustCall("disconnect") + private static class Node { + @Owning private final @Nullable Node next; - public Node(@Owning @Nullable Node next) { - this.next = next; - } + public Node(@Owning @Nullable Node next) { + this.next = next; + } - @EnsuresCalledMethods(value = "this.next", methods = "disconnect") - public void disconnect() { - next.disconnect(); - } + @EnsuresCalledMethods(value = "this.next", methods = "disconnect") + public void disconnect() { + next.disconnect(); } + } } diff --git a/checker/tests/resourceleak/OwnershipWithExceptions.java b/checker/tests/resourceleak/OwnershipWithExceptions.java index f9403669387..39e794c34e4 100644 --- a/checker/tests/resourceleak/OwnershipWithExceptions.java +++ b/checker/tests/resourceleak/OwnershipWithExceptions.java @@ -1,266 +1,265 @@ // Test case for https://github.com/typetools/checker-framework/issues/6179 // (and other rules regarding @Owning and exceptions) -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.io.Closeable; import java.io.IOException; import java.net.Socket; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; +import org.checkerframework.dataflow.qual.SideEffectFree; abstract class OwnershipWithExceptions { - static class ManualExample1 { - void example(String myHost, int myPort) throws IOException { - Socket s = new Socket(myHost, myPort); - closeSocket(s); - } + static class ManualExample1 { + void example(String myHost, int myPort) throws IOException { + Socket s = new Socket(myHost, myPort); + closeSocket(s); + } - void closeSocket(@Owning @MustCall("close") Socket t) { - try { - t.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } + void closeSocket(@Owning @MustCall("close") Socket t) { + try { + t.close(); + } catch (IOException e) { + e.printStackTrace(); + } } + } - static class ManualExample2 { - void example(String myHost, int myPort) throws Exception { - // Error: `s` is not closed on all paths - // ::error: (required.method.not.called) - Socket s = new Socket(myHost, myPort); + static class ManualExample2 { + void example(String myHost, int myPort) throws Exception { + // Error: `s` is not closed on all paths + // ::error: (required.method.not.called) + Socket s = new Socket(myHost, myPort); - // `closeSocket` does not have to close `s` when it throws IOException. - // Instead, this method has to catch the exception and close `s`. - closeSocket(s); - } + // `closeSocket` does not have to close `s` when it throws IOException. + // Instead, this method has to catch the exception and close `s`. + closeSocket(s); + } - void closeSocket(@Owning Socket t) throws IOException { - throw new IOException(); - } + void closeSocket(@Owning Socket t) throws IOException { + throw new IOException(); } + } - abstract @Owning Closeable alloc(); + abstract @Owning Closeable alloc(); - @SideEffectFree - abstract boolean arbitraryChoice(); + @SideEffectFree + abstract boolean arbitraryChoice(); - abstract void transfer(@Owning Closeable resource) throws IOException; + abstract void transfer(@Owning Closeable resource) throws IOException; - void transferAndPropagateException(@Owning Closeable resource) throws IOException { - transfer(resource); - } + void transferAndPropagateException(@Owning Closeable resource) throws IOException { + transfer(resource); + } - void transferHasNoObligationsOnException(@Owning Closeable resource) throws IOException { - throw new IOException(); - } + void transferHasNoObligationsOnException(@Owning Closeable resource) throws IOException { + throw new IOException(); + } - // :: error: (required.method.not.called) - void transferAndIgnoreExceptionWithoutClosing(@Owning Closeable zzz) { - try { - transfer(zzz); - } catch (IOException ignored) { - } + // :: error: (required.method.not.called) + void transferAndIgnoreExceptionWithoutClosing(@Owning Closeable zzz) { + try { + transfer(zzz); + } catch (IOException ignored) { } - - boolean transferAndIgnoreExceptionCorrectly(@Owning Closeable resource) { - try { - transfer(resource); - return true; - } catch (Exception e) { - try { - resource.close(); - } catch (Exception other) { - } - return false; - } + } + + boolean transferAndIgnoreExceptionCorrectly(@Owning Closeable resource) { + try { + transfer(resource); + return true; + } catch (Exception e) { + try { + resource.close(); + } catch (Exception other) { + } + return false; } - - // Passing an argument as an @Owning parameter does not transfer ownership if - // the called method throws. So, this is not correct: if transfer(resource) - // throws an exception, it leaks the resource. - void noExceptionHandling() throws IOException { - // ::error: (required.method.not.called) - Closeable resource = alloc(); - // ::error: (assignment.type.incompatible) - @CalledMethods("close") Closeable a = resource; - transfer(resource); - // ::error: (assignment.type.incompatible) - @CalledMethods("close") Closeable b = resource; + } + + // Passing an argument as an @Owning parameter does not transfer ownership if + // the called method throws. So, this is not correct: if transfer(resource) + // throws an exception, it leaks the resource. + void noExceptionHandling() throws IOException { + // ::error: (required.method.not.called) + Closeable resource = alloc(); + // ::error: (assignment.type.incompatible) + @CalledMethods("close") Closeable a = resource; + transfer(resource); + // ::error: (assignment.type.incompatible) + @CalledMethods("close") Closeable b = resource; + } + + class FinalOwnedField implements Closeable { + + final @Owning Closeable resource; + + FinalOwnedField() throws IOException { + // Field assignments in constructors are special. When the constructor + // exits by exception, the field becomes permanently inaccessible, and + // therefore the allocated resource is leaked. + // :: error: (required.method.not.called) + resource = alloc(); + if (arbitraryChoice()) { + throw new IOException(); + } } - class FinalOwnedField implements Closeable { - - final @Owning Closeable resource; - - FinalOwnedField() throws IOException { - // Field assignments in constructors are special. When the constructor - // exits by exception, the field becomes permanently inaccessible, and - // therefore the allocated resource is leaked. - // :: error: (required.method.not.called) - resource = alloc(); - if (arbitraryChoice()) { - throw new IOException(); - } - } - - FinalOwnedField(@Owning Closeable resource) throws IOException { - // Although, when the resource was passed by a caller, then we can be - // more relaxed. On exception, ownership remains with the caller. - this.resource = resource; - if (arbitraryChoice()) { - throw new IOException(); - } - } - - FinalOwnedField(@Owning Closeable resource, boolean arg) throws IOException { - // Same as the previous constructor, but in the other order. - if (arbitraryChoice()) { - throw new IOException(); - } - this.resource = resource; - } + FinalOwnedField(@Owning Closeable resource) throws IOException { + // Although, when the resource was passed by a caller, then we can be + // more relaxed. On exception, ownership remains with the caller. + this.resource = resource; + if (arbitraryChoice()) { + throw new IOException(); + } + } - FinalOwnedField(int ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 1). - resource = alloc(); - try { - if (arbitraryChoice()) { - throw new IOException(); - } - } catch (Exception e) { - resource.close(); - throw e; - } - } + FinalOwnedField(@Owning Closeable resource, boolean arg) throws IOException { + // Same as the previous constructor, but in the other order. + if (arbitraryChoice()) { + throw new IOException(); + } + this.resource = resource; + } - FinalOwnedField(float ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 2). - Closeable r = alloc(); - resource = r; - try { - if (arbitraryChoice()) { - throw new IOException(); - } - } catch (Exception e) { - r.close(); - throw e; - } + FinalOwnedField(int ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 1). + resource = alloc(); + try { + if (arbitraryChoice()) { + throw new IOException(); } + } catch (Exception e) { + resource.close(); + throw e; + } + } - // Not allowed: destructors have to close @Owning fields even on exception. - @Override - @EnsuresCalledMethods( - value = "this.resource", - methods = {"close"}) - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - throw new IOException(); + FinalOwnedField(float ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 2). + Closeable r = alloc(); + resource = r; + try { + if (arbitraryChoice()) { + throw new IOException(); } + } catch (Exception e) { + r.close(); + throw e; + } } - // Classes with >1 owned field are treated slightly differently - // (see ./TwoOwningMCATest.java) - class TwoOwnedFields implements Closeable { + // Not allowed: destructors have to close @Owning fields even on exception. + @Override + @EnsuresCalledMethods( + value = "this.resource", + methods = {"close"}) + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + throw new IOException(); + } + } - final @Owning Closeable unused = null; + // Classes with >1 owned field are treated slightly differently + // (see ./TwoOwningMCATest.java) + class TwoOwnedFields implements Closeable { - final @Owning Closeable resource; + final @Owning Closeable unused = null; - TwoOwnedFields() throws IOException { - // Field assignments in constructors are special. When the constructor - // exits by exception, the field becomes permanently inaccessible, and - // therefore the allocated resource is leaked. - // :: error: (required.method.not.called) - resource = alloc(); - if (arbitraryChoice()) { - throw new IOException(); - } - } + final @Owning Closeable resource; - TwoOwnedFields(@Owning Closeable resource) throws IOException { - // Although, when the resource was passed by a caller, then we can be - // more relaxed. On exception, ownership remains with the caller. - this.resource = resource; - if (arbitraryChoice()) { - throw new IOException(); - } - } + TwoOwnedFields() throws IOException { + // Field assignments in constructors are special. When the constructor + // exits by exception, the field becomes permanently inaccessible, and + // therefore the allocated resource is leaked. + // :: error: (required.method.not.called) + resource = alloc(); + if (arbitraryChoice()) { + throw new IOException(); + } + } - TwoOwnedFields(@Owning Closeable resource, boolean arg) throws IOException { - // Same as the previous constructor, but in the other order. - if (arbitraryChoice()) { - throw new IOException(); - } - this.resource = resource; - } + TwoOwnedFields(@Owning Closeable resource) throws IOException { + // Although, when the resource was passed by a caller, then we can be + // more relaxed. On exception, ownership remains with the caller. + this.resource = resource; + if (arbitraryChoice()) { + throw new IOException(); + } + } - TwoOwnedFields(int ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 1). - resource = alloc(); - try { - if (arbitraryChoice()) { - throw new IOException(); - } - } catch (Exception e) { - resource.close(); - throw e; - } - } + TwoOwnedFields(@Owning Closeable resource, boolean arg) throws IOException { + // Same as the previous constructor, but in the other order. + if (arbitraryChoice()) { + throw new IOException(); + } + this.resource = resource; + } - TwoOwnedFields(float ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 2). - Closeable r = alloc(); - resource = r; - try { - if (arbitraryChoice()) { - throw new IOException(); - } - } catch (Exception e) { - r.close(); - throw e; - } + TwoOwnedFields(int ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 1). + resource = alloc(); + try { + if (arbitraryChoice()) { + throw new IOException(); } + } catch (Exception e) { + resource.close(); + throw e; + } + } - // Not allowed: destructors have to close @Owning fields even on exception. - // In this case, the exception from `unused.close()` can prematurely stop the method. - @Override - @EnsuresCalledMethods( - value = {"this.resource", "this.unused"}, - methods = {"close"}) - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - if (unused != null) unused.close(); - if (resource != null) resource.close(); + TwoOwnedFields(float ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 2). + Closeable r = alloc(); + resource = r; + try { + if (arbitraryChoice()) { + throw new IOException(); } + } catch (Exception e) { + r.close(); + throw e; + } } - class MutableOwnedField implements Closeable { - - @Owning Closeable resource; - - @RequiresCalledMethods( - value = "this.resource", - methods = {"close"}) - @CreatesMustCallFor("this") - void realloc() throws IOException { - // Unlike in a constructor, field assignments in normal methods are not - // leaked when the method exits with an exception, since the reciever - // is still accessible to the caller. - resource = alloc(); - if (arbitraryChoice()) { - throw new IOException(); - } - } + // Not allowed: destructors have to close @Owning fields even on exception. + // In this case, the exception from `unused.close()` can prematurely stop the method. + @Override + @EnsuresCalledMethods( + value = {"this.resource", "this.unused"}, + methods = {"close"}) + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + if (unused != null) unused.close(); + if (resource != null) resource.close(); + } + } + + class MutableOwnedField implements Closeable { + + @Owning Closeable resource; + + @RequiresCalledMethods( + value = "this.resource", + methods = {"close"}) + @CreatesMustCallFor("this") + void realloc() throws IOException { + // Unlike in a constructor, field assignments in normal methods are not + // leaked when the method exits with an exception, since the reciever + // is still accessible to the caller. + resource = alloc(); + if (arbitraryChoice()) { + throw new IOException(); + } + } - @Override - @EnsuresCalledMethods( - value = "this.resource", - methods = {"close"}) - public void close() throws IOException { - resource.close(); - } + @Override + @EnsuresCalledMethods( + value = "this.resource", + methods = {"close"}) + public void close() throws IOException { + resource.close(); } + } } diff --git a/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java b/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java index c5066ff802a..e9da8f2f516 100644 --- a/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java +++ b/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java @@ -1,72 +1,71 @@ // Test case for a Resource Leak manual example that involves interaction // between @Owning and @EnsuresCalledMethodsOnException. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class OwningAndEnsuresCalledMethodsOnException implements Closeable { - private final @Owning Closeable resource; + private final @Owning Closeable resource; - // Good constructor, as illustrated in the manual. - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource) throws IOException { - this.resource = resource; - try { - initialize(resource); - } catch (Exception e) { - resource.close(); - throw e; - } + // Good constructor, as illustrated in the manual. + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource) throws IOException { + this.resource = resource; + try { + initialize(resource); + } catch (Exception e) { + resource.close(); + throw e; } + } - // Alternative constructor with a weaker contract (specifically, the default - // contract for @Owning: only takes ownership on normal return) - public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource, int ignored) - throws IOException { - this.resource = resource; - initialize(resource); - } + // Alternative constructor with a weaker contract (specifically, the default + // contract for @Owning: only takes ownership on normal return) + public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource, int ignored) + throws IOException { + this.resource = resource; + initialize(resource); + } - public OwningAndEnsuresCalledMethodsOnException() throws IOException { - // OK: the good delegate constructor will either take ownership or close the argument - // This will issue a false positive warning due to - // https://github.com/typetools/checker-framework/issues/6270 - // ::error: (required.method.not.called) - this(new Resource()); - } + public OwningAndEnsuresCalledMethodsOnException() throws IOException { + // OK: the good delegate constructor will either take ownership or close the argument + // This will issue a false positive warning due to + // https://github.com/typetools/checker-framework/issues/6270 + // ::error: (required.method.not.called) + this(new Resource()); + } - public OwningAndEnsuresCalledMethodsOnException(int x) throws IOException { - // WRONG: the bad delegate constructor does not close the argument on exception - // ::error: (required.method.not.called) - this(new Resource(), x); - } + public OwningAndEnsuresCalledMethodsOnException(int x) throws IOException { + // WRONG: the bad delegate constructor does not close the argument on exception + // ::error: (required.method.not.called) + this(new Resource(), x); + } - static void exampleUseInNormalMethod1() throws IOException { - // OK: the constructor will either take ownership or close the argument - // This will issue a false positive warning due to - // https://github.com/typetools/checker-framework/issues/6270 - // ::error: (required.method.not.called) - new OwningAndEnsuresCalledMethodsOnException(new Resource()); - } + static void exampleUseInNormalMethod1() throws IOException { + // OK: the constructor will either take ownership or close the argument + // This will issue a false positive warning due to + // https://github.com/typetools/checker-framework/issues/6270 + // ::error: (required.method.not.called) + new OwningAndEnsuresCalledMethodsOnException(new Resource()); + } - static void exampleUseInNormalMethod2() throws IOException { - // WRONG: the bad constructor does not close the argument on exception - // ::error: (required.method.not.called) - new OwningAndEnsuresCalledMethodsOnException(new Resource(), 0); - } + static void exampleUseInNormalMethod2() throws IOException { + // WRONG: the bad constructor does not close the argument on exception + // ::error: (required.method.not.called) + new OwningAndEnsuresCalledMethodsOnException(new Resource(), 0); + } - static void initialize(Closeable resource) throws IOException {} + static void initialize(Closeable resource) throws IOException {} - @EnsuresCalledMethods(value = "resource", methods = "close") - public void close() throws IOException { - resource.close(); - } + @EnsuresCalledMethods(value = "resource", methods = "close") + public void close() throws IOException { + resource.close(); + } - private static class Resource implements Closeable { - @Override - public void close() throws IOException {} - } + private static class Resource implements Closeable { + @Override + public void close() throws IOException {} + } } diff --git a/checker/tests/resourceleak/OwningEnsuresCalledMethods.java b/checker/tests/resourceleak/OwningEnsuresCalledMethods.java index bac8ab3a404..b17dcf5643e 100644 --- a/checker/tests/resourceleak/OwningEnsuresCalledMethods.java +++ b/checker/tests/resourceleak/OwningEnsuresCalledMethods.java @@ -11,27 +11,26 @@ // must-call methods at some point in the future through a different alias; it // does not promise to call those methods before returning. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.Socket; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("dispose") public class OwningEnsuresCalledMethods { - @Owning Socket con; + @Owning Socket con; - @EnsuresCalledMethods(value = "this.con", methods = "close") - // ::error: (contracts.postcondition.not.satisfied) - void dispose() { - closeCon(con); - } + @EnsuresCalledMethods(value = "this.con", methods = "close") + // ::error: (contracts.postcondition.not.satisfied) + void dispose() { + closeCon(con); + } - static void closeCon(@Owning Socket con) { - try { - con.close(); - } catch (IOException e) { - } + static void closeCon(@Owning Socket con) { + try { + con.close(); + } catch (IOException e) { } + } } diff --git a/checker/tests/resourceleak/OwningFieldStringComparison.java b/checker/tests/resourceleak/OwningFieldStringComparison.java index 3865402b1f6..c89fe62351f 100644 --- a/checker/tests/resourceleak/OwningFieldStringComparison.java +++ b/checker/tests/resourceleak/OwningFieldStringComparison.java @@ -1,33 +1,32 @@ // Test case for https://github.com/typetools/checker-framework/issues/6276 +import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.net.Socket; - @InheritableMustCall("a") public class OwningFieldStringComparison { - // :: error: required.method.not.called - @Owning Socket s; + // :: error: required.method.not.called + @Owning Socket s; - // important to the bug: the name of this field must contain - // the name of the owning socket - /* @NotOwning */ Socket s2; + // important to the bug: the name of this field must contain + // the name of the owning socket + /* @NotOwning */ Socket s2; - // Note this "destructor" closes the wrong socket - @EnsuresCalledMethods(value = "this.s2", methods = "close") - public void a() { - try { - this.s2.close(); - } catch (Exception e) { + // Note this "destructor" closes the wrong socket + @EnsuresCalledMethods(value = "this.s2", methods = "close") + public void a() { + try { + this.s2.close(); + } catch (Exception e) { - } finally { - try { - this.s2.close(); - } catch (Exception e) { + } finally { + try { + this.s2.close(); + } catch (Exception e) { - } - } + } } + } } diff --git a/checker/tests/resourceleak/OwningMCU.java b/checker/tests/resourceleak/OwningMCU.java index 226ce1ae6a4..482eda06fd9 100644 --- a/checker/tests/resourceleak/OwningMCU.java +++ b/checker/tests/resourceleak/OwningMCU.java @@ -6,10 +6,10 @@ public class OwningMCU { - @Owning @MustCallUnknown Object foo; + @Owning @MustCallUnknown Object foo; - // :: error: missing.creates.mustcall.for - void test() { - foo = new Object(); - } + // :: error: missing.creates.mustcall.for + void test() { + foo = new Object(); + } } diff --git a/checker/tests/resourceleak/OwningOverride.java b/checker/tests/resourceleak/OwningOverride.java index b5bb749c757..ee12fd76d48 100644 --- a/checker/tests/resourceleak/OwningOverride.java +++ b/checker/tests/resourceleak/OwningOverride.java @@ -1,29 +1,28 @@ // Test case for https://github.com/typetools/checker-framework/issues/5722 -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; public class OwningOverride { - abstract static class A { - public abstract void closeStream(@Owning InputStream s) throws IOException; - } + abstract static class A { + public abstract void closeStream(@Owning InputStream s) throws IOException; + } - static class B extends A { - @Override - // :: error: owning.override.param - public void closeStream(InputStream s) {} - } + static class B extends A { + @Override + // :: error: owning.override.param + public void closeStream(InputStream s) {} + } - static void main(String[] args) throws IOException { - // no resource leak reported for x - InputStream x = new FileInputStream("foo.txt"); - A a = new B(); - try { - a.closeStream(x); - } catch (Exception e) { - x.close(); - } + static void main(String[] args) throws IOException { + // no resource leak reported for x + InputStream x = new FileInputStream("foo.txt"); + A a = new B(); + try { + a.closeStream(x); + } catch (Exception e) { + x.close(); } + } } diff --git a/checker/tests/resourceleak/OwningOverride2.java b/checker/tests/resourceleak/OwningOverride2.java index ea4ce962ee5..26ab41a831a 100644 --- a/checker/tests/resourceleak/OwningOverride2.java +++ b/checker/tests/resourceleak/OwningOverride2.java @@ -2,21 +2,20 @@ // https://github.com/typetools/checker-framework/issues/5722 // Test that an owning return cannot override a non-owning return -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; public class OwningOverride2 { - abstract static class A { - public abstract @NotOwning Socket get(); - } + abstract static class A { + public abstract @NotOwning Socket get(); + } - static class B extends A { - @Override - // :: error: owning.override.return - public Socket get() { - return null; - } + static class B extends A { + @Override + // :: error: owning.override.return + public Socket get() { + return null; } + } } diff --git a/checker/tests/resourceleak/PaperExample.java b/checker/tests/resourceleak/PaperExample.java index 0838504d7ac..2d337ee3646 100644 --- a/checker/tests/resourceleak/PaperExample.java +++ b/checker/tests/resourceleak/PaperExample.java @@ -1,16 +1,16 @@ import java.net.Socket; class PaperExample { - void test(String myHost, int myPort) throws Exception { - Socket s = null; - try { - s = new Socket(myHost, myPort); /* 1 */ - } catch (Exception e) { - } finally { - if (s != null) { - /* 2 */ - s.close(); - } - } + void test(String myHost, int myPort) throws Exception { + Socket s = null; + try { + s = new Socket(myHost, myPort); /* 1 */ + } catch (Exception e) { + } finally { + if (s != null) { + /* 2 */ + s.close(); + } } + } } diff --git a/checker/tests/resourceleak/PrimitiveCast.java b/checker/tests/resourceleak/PrimitiveCast.java index b28d7375ea1..7f5ec7bcd3d 100644 --- a/checker/tests/resourceleak/PrimitiveCast.java +++ b/checker/tests/resourceleak/PrimitiveCast.java @@ -1,38 +1,37 @@ +import java.util.List; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.MustCallUnknown; -import java.util.List; - public class PrimitiveCast { - char foo(List values) { - for (Object o : values) { - return (char) o; - } - return 'A'; + char foo(List values) { + for (Object o : values) { + return (char) o; } + return 'A'; + } - char toChar1(@MustCall("hashCode") Character c) { - return (char) c; - } + char toChar1(@MustCall("hashCode") Character c) { + return (char) c; + } - char toChar2(@MustCallUnknown Character c) { - return (char) c; - } + char toChar2(@MustCallUnknown Character c) { + return (char) c; + } - char toChar3(@MustCall Character c) { - return (char) c; - } + char toChar3(@MustCall Character c) { + return (char) c; + } - @MustCall("hashCode") Character toCharacter1(char c) { - return (char) c; - } + @MustCall("hashCode") Character toCharacter1(char c) { + return (char) c; + } - @MustCallUnknown Character toCharacter2(char c) { - return (char) c; - } + @MustCallUnknown Character toCharacter2(char c) { + return (char) c; + } - @MustCall Character toCharacter3(char c) { - return (char) c; - } + @MustCall Character toCharacter3(char c) { + return (char) c; + } } diff --git a/checker/tests/resourceleak/ReassignmentWithMCA.java b/checker/tests/resourceleak/ReassignmentWithMCA.java index 360839acbc7..5d0c4dfb50b 100644 --- a/checker/tests/resourceleak/ReassignmentWithMCA.java +++ b/checker/tests/resourceleak/ReassignmentWithMCA.java @@ -1,50 +1,49 @@ -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.security.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; public class ReassignmentWithMCA { - void testReassignment(File newFile, MessageDigest digester) throws IOException { - FileOutputStream fout = new FileOutputStream(newFile); - DigestOutputStream fos = new DigestOutputStream(fout, digester); - DataOutputStream out = new DataOutputStream(fos); - try { - out = new DataOutputStream(new BufferedOutputStream(fos)); - fout.getChannel(); - } finally { - out.close(); - } + void testReassignment(File newFile, MessageDigest digester) throws IOException { + FileOutputStream fout = new FileOutputStream(newFile); + DigestOutputStream fos = new DigestOutputStream(fout, digester); + DataOutputStream out = new DataOutputStream(fos); + try { + out = new DataOutputStream(new BufferedOutputStream(fos)); + fout.getChannel(); + } finally { + out.close(); } + } - void testReassignmentWithoutMCA( - @Owning FileOutputStream fout1, @Owning FileOutputStream fout2, MessageDigest digester) - throws IOException { - DigestOutputStream fos1 = new DigestOutputStream(fout1, digester); - DataOutputStream out = new DataOutputStream(fos1); - try { - DigestOutputStream fos2 = new DigestOutputStream(fout2, digester); - out = new DataOutputStream(new BufferedOutputStream(fos2)); - fout1.getChannel(); - } finally { - callClose(fout1); - callClose(fout2); - } + void testReassignmentWithoutMCA( + @Owning FileOutputStream fout1, @Owning FileOutputStream fout2, MessageDigest digester) + throws IOException { + DigestOutputStream fos1 = new DigestOutputStream(fout1, digester); + DataOutputStream out = new DataOutputStream(fos1); + try { + DigestOutputStream fos2 = new DigestOutputStream(fout2, digester); + out = new DataOutputStream(new BufferedOutputStream(fos2)); + fout1.getChannel(); + } finally { + callClose(fout1); + callClose(fout2); } + } - void testReassignmentSetSizeOne(@Owning FilterOutputStream out) throws IOException { - out = new DataOutputStream(out); - out.close(); - } + void testReassignmentSetSizeOne(@Owning FilterOutputStream out) throws IOException { + out = new DataOutputStream(out); + out.close(); + } - @EnsuresCalledMethods(value = "#1", methods = "close") - void callClose(Closeable c) { - try { - if (c != null) { - c.close(); - } - } catch (IOException e) { + @EnsuresCalledMethods(value = "#1", methods = "close") + void callClose(Closeable c) { + try { + if (c != null) { + c.close(); + } + } catch (IOException e) { - } } + } } diff --git a/checker/tests/resourceleak/ReplicaInputStreams.java b/checker/tests/resourceleak/ReplicaInputStreams.java index c72b00c5243..d0b030752f4 100644 --- a/checker/tests/resourceleak/ReplicaInputStreams.java +++ b/checker/tests/resourceleak/ReplicaInputStreams.java @@ -1,29 +1,28 @@ // A test case for https://github.com/typetools/checker-framework/issues/4838. -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.Closeable; import java.io.IOException; import java.io.InputStream; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.mustcall.qual.Owning; class ReplicaInputStreams implements Closeable { - private final @Owning InputStream in1; - private final @Owning InputStream in2; + private final @Owning InputStream in1; + private final @Owning InputStream in2; - public ReplicaInputStreams(@Owning InputStream i1, @Owning InputStream i2) { - this.in1 = i1; - this.in2 = i2; - } + public ReplicaInputStreams(@Owning InputStream i1, @Owning InputStream i2) { + this.in1 = i1; + this.in2 = i2; + } - @Override - @EnsuresCalledMethods( - value = {"this.in1", "this.in2"}, - methods = {"close"}) - // :: error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - in1.close(); - in2.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.in1", "this.in2"}, + methods = {"close"}) + // :: error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + in1.close(); + in2.close(); + } } diff --git a/checker/tests/resourceleak/ReplicaInputStreams2.java b/checker/tests/resourceleak/ReplicaInputStreams2.java index 089f4eaec83..c5df98e1ba0 100644 --- a/checker/tests/resourceleak/ReplicaInputStreams2.java +++ b/checker/tests/resourceleak/ReplicaInputStreams2.java @@ -1,32 +1,31 @@ // A test case for https://github.com/typetools/checker-framework/issues/4838. // This variant uses a try-finally in the destructor, so it is correct. -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.Closeable; import java.io.IOException; import java.io.InputStream; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.mustcall.qual.Owning; class ReplicaInputStreams2 implements Closeable { - private final @Owning InputStream in1; - private final @Owning InputStream in2; + private final @Owning InputStream in1; + private final @Owning InputStream in2; - public ReplicaInputStreams2(@Owning InputStream i1, @Owning InputStream i2) { - this.in1 = i1; - this.in2 = i2; - } + public ReplicaInputStreams2(@Owning InputStream i1, @Owning InputStream i2) { + this.in1 = i1; + this.in2 = i2; + } - @Override - @EnsuresCalledMethods( - value = {"this.in1", "this.in2"}, - methods = {"close"}) - public void close() throws IOException { - try { - in1.close(); - } finally { - in2.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.in1", "this.in2"}, + methods = {"close"}) + public void close() throws IOException { + try { + in1.close(); + } finally { + in2.close(); } + } } diff --git a/checker/tests/resourceleak/RequiresCalledMethodsTest.java b/checker/tests/resourceleak/RequiresCalledMethodsTest.java index 3178e6a2329..4cbb9a6b6b6 100644 --- a/checker/tests/resourceleak/RequiresCalledMethodsTest.java +++ b/checker/tests/resourceleak/RequiresCalledMethodsTest.java @@ -1,54 +1,53 @@ +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - public class RequiresCalledMethodsTest { - @InheritableMustCall("a") - static class Foo { - void a() {} + @InheritableMustCall("a") + static class Foo { + void a() {} + + void c() {} + } + + @InheritableMustCall("releaseFoo") + static class FooField { + private @Owning Foo foo = null; + + @RequiresCalledMethods( + value = {"this.foo"}, + methods = {"a"}) + @CreatesMustCallFor("this") + void overwriteFooCorrect() { + this.foo = new Foo(); + } + + @CreatesMustCallFor("this") + void overwriteFooWrong() { + // :: error: required.method.not.called + this.foo = new Foo(); + } + + @CreatesMustCallFor("this") + void overwriteFooWithoutReleasing() { + // :: error: contracts.precondition.not.satisfied + overwriteFooCorrect(); + } - void c() {} + void releaseThenOverwriteFoo() { + releaseFoo(); + // :: error: reset.not.owning + overwriteFooCorrect(); } - @InheritableMustCall("releaseFoo") - static class FooField { - private @Owning Foo foo = null; - - @RequiresCalledMethods( - value = {"this.foo"}, - methods = {"a"}) - @CreatesMustCallFor("this") - void overwriteFooCorrect() { - this.foo = new Foo(); - } - - @CreatesMustCallFor("this") - void overwriteFooWrong() { - // :: error: required.method.not.called - this.foo = new Foo(); - } - - @CreatesMustCallFor("this") - void overwriteFooWithoutReleasing() { - // :: error: contracts.precondition.not.satisfied - overwriteFooCorrect(); - } - - void releaseThenOverwriteFoo() { - releaseFoo(); - // :: error: reset.not.owning - overwriteFooCorrect(); - } - - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"a"}) - void releaseFoo() { - if (this.foo != null) { - foo.a(); - } - } + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"a"}) + void releaseFoo() { + if (this.foo != null) { + foo.a(); + } } + } } diff --git a/checker/tests/resourceleak/ReturnOwningObject.java b/checker/tests/resourceleak/ReturnOwningObject.java index 20d3fa208fa..822c0cd5d0a 100644 --- a/checker/tests/resourceleak/ReturnOwningObject.java +++ b/checker/tests/resourceleak/ReturnOwningObject.java @@ -5,19 +5,19 @@ import org.checkerframework.checker.mustcall.qual.MustCall; public class ReturnOwningObject { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - // This is unsatisfiable without a cast, but - // for soundness the RLC still needs to track it. - public static @MustCall("a") Object getFoo() { - return new Foo(); - } + // This is unsatisfiable without a cast, but + // for soundness the RLC still needs to track it. + public static @MustCall("a") Object getFoo() { + return new Foo(); + } - public static void useGetFoo() { - // :: error: required.method.not.called - Object obj = getFoo(); - } + public static void useGetFoo() { + // :: error: required.method.not.called + Object obj = getFoo(); + } } diff --git a/checker/tests/resourceleak/RlcThisTest.java b/checker/tests/resourceleak/RlcThisTest.java index 113b02daa5b..dc41cb142dc 100644 --- a/checker/tests/resourceleak/RlcThisTest.java +++ b/checker/tests/resourceleak/RlcThisTest.java @@ -6,59 +6,59 @@ class RlcThisTest { - @InheritableMustCall("a") - private class Foo { + @InheritableMustCall("a") + private class Foo { - void a() {} + void a() {} - @This Foo b1(Foo this) { - return this; - } - - @MustCallAlias Foo b2(@MustCallAlias Foo this) { - return this; - } + @This Foo b1(Foo this) { + return this; } - void test1() { - Foo f = new Foo(); - f.b1(); - f.a(); + @MustCallAlias Foo b2(@MustCallAlias Foo this) { + return this; } + } - void test2() { - Foo f = new Foo(); - f.b2(); - f.a(); - } + void test1() { + Foo f = new Foo(); + f.b1(); + f.a(); + } - void test3() { - Foo f = new Foo(); - Foo ff = f.b1(); - ff.a(); - } + void test2() { + Foo f = new Foo(); + f.b2(); + f.a(); + } - void test4() { - Foo f = new Foo(); - Foo ff = f.b2(); - ff.a(); - } + void test3() { + Foo f = new Foo(); + Foo ff = f.b1(); + ff.a(); + } - void testA() { - Foo f = new Foo(); - f.b1(); // RLC reports a FP at this line - f.a(); + void test4() { + Foo f = new Foo(); + Foo ff = f.b2(); + ff.a(); + } - f = new Foo(); - f.a(); - } + void testA() { + Foo f = new Foo(); + f.b1(); // RLC reports a FP at this line + f.a(); - void testB() { - Foo f = new Foo(); - f.b2(); // RLC reports a FP at this line - f.a(); + f = new Foo(); + f.a(); + } - f = new Foo(); - f.a(); - } + void testB() { + Foo f = new Foo(); + f.b2(); // RLC reports a FP at this line + f.a(); + + f = new Foo(); + f.a(); + } } diff --git a/checker/tests/resourceleak/SSLSocketFactoryTest.java b/checker/tests/resourceleak/SSLSocketFactoryTest.java index c7108731abd..40570ada578 100644 --- a/checker/tests/resourceleak/SSLSocketFactoryTest.java +++ b/checker/tests/resourceleak/SSLSocketFactoryTest.java @@ -1,21 +1,17 @@ // A test for a bug that came up while porting the Resource Leak Checker into the // Checker Framework proper. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.Socket; - import javax.net.ssl.*; +import org.checkerframework.checker.mustcall.qual.*; class SSLSocketFactoryTest { - public SSLSocket createSSLSocket(@Owning Socket socket, SSLContext sslContext) - throws IOException { - SSLSocket sslSocket = - (SSLSocket) - sslContext - .getSocketFactory() - .createSocket(socket, null, socket.getPort(), true); - return sslSocket; - } + public SSLSocket createSSLSocket(@Owning Socket socket, SSLContext sslContext) + throws IOException { + SSLSocket sslSocket = + (SSLSocket) + sslContext.getSocketFactory().createSocket(socket, null, socket.getPort(), true); + return sslSocket; + } } diff --git a/checker/tests/resourceleak/SelfAssign.java b/checker/tests/resourceleak/SelfAssign.java index d3c9fa5205b..d658faf67c7 100644 --- a/checker/tests/resourceleak/SelfAssign.java +++ b/checker/tests/resourceleak/SelfAssign.java @@ -1,39 +1,36 @@ // test assignments of the same variable to itself +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - class SelfAssign { - static void test0() throws IOException { - InputStream selfAssignIn0 = new FileInputStream("file.txt"); - try { - selfAssignIn0 = selfAssignIn0; - } finally { - selfAssignIn0.close(); - } + static void test0() throws IOException { + InputStream selfAssignIn0 = new FileInputStream("file.txt"); + try { + selfAssignIn0 = selfAssignIn0; + } finally { + selfAssignIn0.close(); } + } - static void test1(boolean b) throws IOException { - InputStream selfAssignIn = new FileInputStream("file.txt"); - try { - selfAssignIn = - selfAssignIn.markSupported() - ? selfAssignIn - : new BufferedInputStream(selfAssignIn); - } finally { - selfAssignIn.close(); - } + static void test1(boolean b) throws IOException { + InputStream selfAssignIn = new FileInputStream("file.txt"); + try { + selfAssignIn = + selfAssignIn.markSupported() ? selfAssignIn : new BufferedInputStream(selfAssignIn); + } finally { + selfAssignIn.close(); } + } - static void test2(boolean b) throws IOException { - InputStream in = new FileInputStream("file.txt"); - try { - in = new BufferedInputStream(in); - } finally { - in.close(); - } + static void test2(boolean b) throws IOException { + InputStream in = new FileInputStream("file.txt"); + try { + in = new BufferedInputStream(in); + } finally { + in.close(); } + } } diff --git a/checker/tests/resourceleak/SimpleSocketExample.java b/checker/tests/resourceleak/SimpleSocketExample.java index b5297624659..e3e09484305 100644 --- a/checker/tests/resourceleak/SimpleSocketExample.java +++ b/checker/tests/resourceleak/SimpleSocketExample.java @@ -4,14 +4,14 @@ import java.net.Socket; class SimpleSocketExample { - void basicTest(String address, int port) { - try { - // :: error: required.method.not.called - Socket socket2 = new Socket(address, port); - Socket specialSocket = new Socket(address, port); - specialSocket.close(); - } catch (IOException i) { + void basicTest(String address, int port) { + try { + // :: error: required.method.not.called + Socket socket2 = new Socket(address, port); + Socket specialSocket = new Socket(address, port); + specialSocket.close(); + } catch (IOException i) { - } } + } } diff --git a/checker/tests/resourceleak/SneakyDestructor.java b/checker/tests/resourceleak/SneakyDestructor.java index 5437af3d1af..586646ae600 100644 --- a/checker/tests/resourceleak/SneakyDestructor.java +++ b/checker/tests/resourceleak/SneakyDestructor.java @@ -3,24 +3,23 @@ // is not fooled by a must-call method that closes the owned field // of another instance of the class. +import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.io.*; - @InheritableMustCall("close") public class SneakyDestructor { - // :: error: required.method.not.called - private final @Owning Closeable resource; + // :: error: required.method.not.called + private final @Owning Closeable resource; - public SneakyDestructor(Closeable r) { - this.resource = r; - } + public SneakyDestructor(Closeable r) { + this.resource = r; + } - // ... + // ... - @EnsuresCalledMethods(value = "#1.resource", methods = "close") - public void close(SneakyDestructor other) throws IOException { - other.resource.close(); - } + @EnsuresCalledMethods(value = "#1.resource", methods = "close") + public void close(SneakyDestructor other) throws IOException { + other.resource.close(); + } } diff --git a/checker/tests/resourceleak/SneakyDrop.java b/checker/tests/resourceleak/SneakyDrop.java index cc81c0b216d..05255cdeb5e 100644 --- a/checker/tests/resourceleak/SneakyDrop.java +++ b/checker/tests/resourceleak/SneakyDrop.java @@ -3,63 +3,63 @@ import org.checkerframework.checker.mustcall.qual.*; class Resource implements java.io.Closeable { - @Override - public void close() {} + @Override + public void close() {} } public class SneakyDrop { - public static void sneakyDrop(@Owning T value) {} + public static void sneakyDrop(@Owning T value) {} - public static void main(String[] args) throws Exception { - Resource x = new Resource(); - // :: error: (type.argument.type.incompatible) - sneakyDrop(x); - } + public static void main(String[] args) throws Exception { + Resource x = new Resource(); + // :: error: (type.argument.type.incompatible) + sneakyDrop(x); + } - // :: error: (required.method.not.called) - public static void sneakyDrop2(@Owning @MustCall("close") T value) {} + // :: error: (required.method.not.called) + public static void sneakyDrop2(@Owning @MustCall("close") T value) {} - public static void main2(String[] args) throws Exception { - Resource x = new Resource(); - sneakyDrop2(x); - } + public static void main2(String[] args) throws Exception { + Resource x = new Resource(); + sneakyDrop2(x); + } - // :: error: (required.method.not.called) - public static void sneakyDrop3(@Owning T value) {} + // :: error: (required.method.not.called) + public static void sneakyDrop3(@Owning T value) {} - public static void main3(String[] args) throws Exception { - Resource x = new Resource(); - sneakyDrop3(x); - } + public static void main3(String[] args) throws Exception { + Resource x = new Resource(); + sneakyDrop3(x); + } - public static void sneakyDrop4(@Owning T value) {} + public static void sneakyDrop4(@Owning T value) {} - public static void main4(String[] args) throws Exception { - Resource x = new Resource(); - // :: error: (type.argument.type.incompatible) - sneakyDrop4(x); - } + public static void main4(String[] args) throws Exception { + Resource x = new Resource(); + // :: error: (type.argument.type.incompatible) + sneakyDrop4(x); + } - // :: error: (required.method.not.called) - public static void sneakyDrop5(@Owning T value) {} + // :: error: (required.method.not.called) + public static void sneakyDrop5(@Owning T value) {} - public static void main5(String[] args) throws Exception { - Resource x = new Resource(); - sneakyDrop5(x); - } + public static void main5(String[] args) throws Exception { + Resource x = new Resource(); + sneakyDrop5(x); + } - public static void sneakyDropCorrect( - @Owning @MustCall("close") T value) throws Exception { - value.close(); - } + public static void sneakyDropCorrect( + @Owning @MustCall("close") T value) throws Exception { + value.close(); + } - public static void main6(String[] args) throws Exception { - Resource x = new Resource(); - try { - sneakyDropCorrect(x); - } catch (Exception e) { - x.close(); - } + public static void main6(String[] args) throws Exception { + Resource x = new Resource(); + try { + sneakyDropCorrect(x); + } catch (Exception e) { + x.close(); } + } } diff --git a/checker/tests/resourceleak/SocketContainer.java b/checker/tests/resourceleak/SocketContainer.java index 302e597a325..d431d665271 100644 --- a/checker/tests/resourceleak/SocketContainer.java +++ b/checker/tests/resourceleak/SocketContainer.java @@ -2,31 +2,30 @@ // This test exists to check that we gracefully handle assignments 1) // in the constructor and 2) to null. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("close") class SocketContainer { - @Owning Socket sock; + @Owning Socket sock; - public SocketContainer(String host, int port) throws Exception { - // It should be okay to assign to uninitialized owning fields in the constructor. - // But it isn't! Why? - // :: error: required.method.not.called - sock = new Socket(host, port); - // It's definitely not okay to do it twice! - // :: error: required.method.not.called - sock = new Socket(host, port); - } + public SocketContainer(String host, int port) throws Exception { + // It should be okay to assign to uninitialized owning fields in the constructor. + // But it isn't! Why? + // :: error: required.method.not.called + sock = new Socket(host, port); + // It's definitely not okay to do it twice! + // :: error: required.method.not.called + sock = new Socket(host, port); + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - // It's okay to assign a field to null after its obligations have been fulfilled, - // without inducing a reset. - sock = null; - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + // It's okay to assign a field to null after its obligations have been fulfilled, + // without inducing a reset. + sock = null; + } } diff --git a/checker/tests/resourceleak/SocketContainer2.java b/checker/tests/resourceleak/SocketContainer2.java index fc4dfd7a180..3d039c8e953 100644 --- a/checker/tests/resourceleak/SocketContainer2.java +++ b/checker/tests/resourceleak/SocketContainer2.java @@ -1,25 +1,24 @@ // A simple class that has a Socket as an owning field. // This test exists to check that we gracefully handle assignments to it. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("close") class SocketContainer2 { - @Owning Socket sock = new Socket(); + @Owning Socket sock = new Socket(); - public SocketContainer2(String host, int port) throws Exception { - // This assignment is safe, because the only possible value of sock here is the unconnected - // socket in the field initializer. - sock = new Socket(host, port); - } + public SocketContainer2(String host, int port) throws Exception { + // This assignment is safe, because the only possible value of sock here is the unconnected + // socket in the field initializer. + sock = new Socket(host, port); + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + } } diff --git a/checker/tests/resourceleak/SocketContainer3.java b/checker/tests/resourceleak/SocketContainer3.java index bcb212ebc5e..0dec9beec1b 100644 --- a/checker/tests/resourceleak/SocketContainer3.java +++ b/checker/tests/resourceleak/SocketContainer3.java @@ -1,22 +1,21 @@ // A simple class that has a Socket as an owning field. // This test exists to check that we gracefully handle assignments. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("close") class SocketContainer3 { - @Owning Socket sock = null; + @Owning Socket sock = null; - public SocketContainer3(String host, int port) throws Exception { - sock = new Socket(host, port); - } + public SocketContainer3(String host, int port) throws Exception { + sock = new Socket(host, port); + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + } } diff --git a/checker/tests/resourceleak/SocketField.java b/checker/tests/resourceleak/SocketField.java index 29a505f109f..198890ed708 100644 --- a/checker/tests/resourceleak/SocketField.java +++ b/checker/tests/resourceleak/SocketField.java @@ -1,67 +1,66 @@ // test case for https://github.com/kelloggm/object-construction-checker/issues/381 +import java.io.IOException; +import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.dataflow.qual.Pure; -import java.io.IOException; -import java.net.Socket; - @InheritableMustCall("closeSocket") class SocketField { - protected @Owning Socket socket = null; + protected @Owning Socket socket = null; - @CreatesMustCallFor("this") - protected void setupConnection(javax.net.SocketFactory socketFactory) throws IOException { - // This is the original test case. Before this issue was fixed, an error was issued on the - // second line. - this.socket.close(); - this.socket = socketFactory.createSocket(); - } + @CreatesMustCallFor("this") + protected void setupConnection(javax.net.SocketFactory socketFactory) throws IOException { + // This is the original test case. Before this issue was fixed, an error was issued on the + // second line. + this.socket.close(); + this.socket = socketFactory.createSocket(); + } - @CreatesMustCallFor("this") - protected void setupConnectionWithLocal(javax.net.SocketFactory socketFactory) - throws IOException { - // This is the original test case, modified to include an assignment to a local that - // demonstrates that - // the correct value was in the store at some point. - this.socket.close(); - @CalledMethods("close") Socket s = this.socket; - this.socket = socketFactory.createSocket(); - } + @CreatesMustCallFor("this") + protected void setupConnectionWithLocal(javax.net.SocketFactory socketFactory) + throws IOException { + // This is the original test case, modified to include an assignment to a local that + // demonstrates that + // the correct value was in the store at some point. + this.socket.close(); + @CalledMethods("close") Socket s = this.socket; + this.socket = socketFactory.createSocket(); + } - @CreatesMustCallFor("this") - protected void setupConnectionWithConstructor(javax.net.SocketFactory socketFactory) - throws IOException { - // This is the original test case, modified to replace the call to createSocket() with a new - // Socket() call. - // This version succeeded, even before the bug was fixed. - this.socket.close(); - this.socket = new Socket(); - } + @CreatesMustCallFor("this") + protected void setupConnectionWithConstructor(javax.net.SocketFactory socketFactory) + throws IOException { + // This is the original test case, modified to replace the call to createSocket() with a new + // Socket() call. + // This version succeeded, even before the bug was fixed. + this.socket.close(); + this.socket = new Socket(); + } - @CreatesMustCallFor("this") - protected void setupConnection2(javax.net.SocketFactory socketFactory) throws IOException { - this.socket.close(); - // This version succeeds, because getSocket is @Pure, so no side-effects can occur. - this.socket = getSocket(socketFactory); - } + @CreatesMustCallFor("this") + protected void setupConnection2(javax.net.SocketFactory socketFactory) throws IOException { + this.socket.close(); + // This version succeeds, because getSocket is @Pure, so no side-effects can occur. + this.socket = getSocket(socketFactory); + } - @CreatesMustCallFor("this") - protected void setupConnection3(javax.net.SocketFactory socketFactory) throws IOException { - // This version demonstrates a work-around. - Socket s = socketFactory.createSocket(); - this.socket.close(); - this.socket = s; - } + @CreatesMustCallFor("this") + protected void setupConnection3(javax.net.SocketFactory socketFactory) throws IOException { + // This version demonstrates a work-around. + Socket s = socketFactory.createSocket(); + this.socket.close(); + this.socket = s; + } - @Pure - private Socket getSocket(javax.net.SocketFactory socketFactory) throws IOException { - return socketFactory.createSocket(); - } + @Pure + private Socket getSocket(javax.net.SocketFactory socketFactory) throws IOException { + return socketFactory.createSocket(); + } - @EnsuresCalledMethods(value = "this.socket", methods = "close") - private void closeSocket() throws IOException { - this.socket.close(); - } + @EnsuresCalledMethods(value = "this.socket", methods = "close") + private void closeSocket() throws IOException { + this.socket.close(); + } } diff --git a/checker/tests/resourceleak/SocketIntoList.java b/checker/tests/resourceleak/SocketIntoList.java index d56e87fa044..57603d7cbe1 100644 --- a/checker/tests/resourceleak/SocketIntoList.java +++ b/checker/tests/resourceleak/SocketIntoList.java @@ -2,45 +2,44 @@ // a socket with an obligation is stored into a List (which cannot be // owning). -import org.checkerframework.checker.mustcall.qual.*; - import java.net.*; import java.util.List; +import org.checkerframework.checker.mustcall.qual.*; public class SocketIntoList { - public void test1(List<@MustCall({}) Socket> l) { - // s is unconnected, so no error is expected when it's stored into a list - Socket s = new Socket(); - l.add(s); - } + public void test1(List<@MustCall({}) Socket> l) { + // s is unconnected, so no error is expected when it's stored into a list + Socket s = new Socket(); + l.add(s); + } - // :: error: type.argument.type.incompatible - public void test2(List l) { - // s is unconnected, so no error is expected when it's stored into the list. - // But, if the list is unannotated, we do get an error at its declaration site - // (as expected, due to #5912). - Socket s = new Socket(); - l.add(s); - } + // :: error: type.argument.type.incompatible + public void test2(List l) { + // s is unconnected, so no error is expected when it's stored into the list. + // But, if the list is unannotated, we do get an error at its declaration site + // (as expected, due to #5912). + Socket s = new Socket(); + l.add(s); + } - public void test3(List<@MustCall({}) Socket> l) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.bind(new InetSocketAddress("192.168.0.1", 0)); - l.add(s); - } + public void test3(List<@MustCall({}) Socket> l) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.bind(new InetSocketAddress("192.168.0.1", 0)); + l.add(s); + } - // This input list might have been produced by e.g., test1() - public void test4(List<@MustCall({}) Socket> l) throws Exception { - // :: error: required.method.not.called - Socket s = l.get(0); - s.bind(new InetSocketAddress("192.168.0.1", 0)); - } + // This input list might have been produced by e.g., test1() + public void test4(List<@MustCall({}) Socket> l) throws Exception { + // :: error: required.method.not.called + Socket s = l.get(0); + s.bind(new InetSocketAddress("192.168.0.1", 0)); + } - // This input list might have been produced by e.g., test1() - public void test5(List<@MustCall({}) Socket> l) throws Exception { - // No error is expected here, because the socket should be @MustCall({}) when - // it is retrieved from the list. (Equivalently, the list is not owning). - Socket s = l.get(0); - } + // This input list might have been produced by e.g., test1() + public void test5(List<@MustCall({}) Socket> l) throws Exception { + // No error is expected here, because the socket should be @MustCall({}) when + // it is retrieved from the list. (Equivalently, the list is not owning). + Socket s = l.get(0); + } } diff --git a/checker/tests/resourceleak/SocketNullOverwrite.java b/checker/tests/resourceleak/SocketNullOverwrite.java index 72b6e1b93cf..2b6a3af93ea 100644 --- a/checker/tests/resourceleak/SocketNullOverwrite.java +++ b/checker/tests/resourceleak/SocketNullOverwrite.java @@ -4,13 +4,13 @@ import java.net.Socket; class SocketNullOverwrite { - void replaceVarWithNull(String address, int port) { - try { - // :: error: required.method.not.called - Socket s = new Socket(address, port); - s = null; - } catch (IOException e) { + void replaceVarWithNull(String address, int port) { + try { + // :: error: required.method.not.called + Socket s = new Socket(address, port); + s = null; + } catch (IOException e) { - } } + } } diff --git a/checker/tests/resourceleak/StaticOwningField.java b/checker/tests/resourceleak/StaticOwningField.java index 2da76b120cb..101664cab72 100644 --- a/checker/tests/resourceleak/StaticOwningField.java +++ b/checker/tests/resourceleak/StaticOwningField.java @@ -1,67 +1,66 @@ +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintStream; - @MustCall("close") class StaticOwningField implements Closeable { - // Instance field + // Instance field - private @Owning @MustCall("close") PrintStream ps_instance; + private @Owning @MustCall("close") PrintStream ps_instance; - @CreatesMustCallFor("this") - void m_instance() throws IOException { - ps_instance.close(); - ps_instance = new PrintStream("filename.txt"); - } + @CreatesMustCallFor("this") + void m_instance() throws IOException { + ps_instance.close(); + ps_instance = new PrintStream("filename.txt"); + } - @EnsuresCalledMethods(value = "ps_instance", methods = "close") - @Override - public void close() { - ps_instance.close(); - } + @EnsuresCalledMethods(value = "ps_instance", methods = "close") + @Override + public void close() { + ps_instance.close(); + } - // Static field + // Static field - // :: error: (required.method.not.called) - private static @Owning @MustCall("close") PrintStream ps_static; + // :: error: (required.method.not.called) + private static @Owning @MustCall("close") PrintStream ps_static; - // :: error: (missing.creates.mustcall.for) - static void m_static() throws IOException { - ps_static.close(); - ps_static = new PrintStream("filename.txt"); - } + // :: error: (missing.creates.mustcall.for) + static void m_static() throws IOException { + ps_static.close(); + ps_static = new PrintStream("filename.txt"); + } - // :: error: (required.method.not.called) - private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = - newPrintStreamWithoutExceptions(); + // :: error: (required.method.not.called) + private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = + newPrintStreamWithoutExceptions(); - // :: error: (required.method.not.called) - private static @Owning @MustCall("close") PrintStream ps_static_initialized2; + // :: error: (required.method.not.called) + private static @Owning @MustCall("close") PrintStream ps_static_initialized2; - static { - // :: error: (required.method.not.called) - ps_static_initialized2 = newPrintStreamWithoutExceptions(); - } + static { + // :: error: (required.method.not.called) + ps_static_initialized2 = newPrintStreamWithoutExceptions(); + } - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = - newPrintStreamWithoutExceptions(); + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = + newPrintStreamWithoutExceptions(); - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; - static { - ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); - } + static { + ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); + } - public static PrintStream newPrintStreamWithoutExceptions() { - try { - return new PrintStream("filename.txt"); - } catch (Exception e) { - throw new Error(e); - } + public static PrintStream newPrintStreamWithoutExceptions() { + try { + return new PrintStream("filename.txt"); + } catch (Exception e) { + throw new Error(e); } + } } diff --git a/checker/tests/resourceleak/StaticOwningFieldOtherClass.java b/checker/tests/resourceleak/StaticOwningFieldOtherClass.java index 1999a06499a..d4ff42d3ad7 100644 --- a/checker/tests/resourceleak/StaticOwningFieldOtherClass.java +++ b/checker/tests/resourceleak/StaticOwningFieldOtherClass.java @@ -1,24 +1,23 @@ -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.FileWriter; import java.io.IOException; import java.io.UncheckedIOException; +import org.checkerframework.checker.mustcall.qual.Owning; public class StaticOwningFieldOtherClass {} abstract class HasStaticOwningField { - // :: error: (required.method.not.called) - public static @Owning FileWriter log = null; + // :: error: (required.method.not.called) + public static @Owning FileWriter log = null; } class TestUtils { - // :: error: (missing.creates.mustcall.for) - public static void setLog(String filename) { - try { - // :: error: (required.method.not.called) - HasStaticOwningField.log = new FileWriter(filename); - } catch (IOException ioe) { - throw new UncheckedIOException("Cannot write file " + filename, ioe); - } + // :: error: (missing.creates.mustcall.for) + public static void setLog(String filename) { + try { + // :: error: (required.method.not.called) + HasStaticOwningField.log = new FileWriter(filename); + } catch (IOException ioe) { + throw new UncheckedIOException("Cannot write file " + filename, ioe); } + } } diff --git a/checker/tests/resourceleak/StringConcatenation.java b/checker/tests/resourceleak/StringConcatenation.java index 017a8b105b7..fc27e816cae 100644 --- a/checker/tests/resourceleak/StringConcatenation.java +++ b/checker/tests/resourceleak/StringConcatenation.java @@ -1,17 +1,17 @@ public class StringConcatenation { - public final V1 first; - public final V2 second; + public final V1 first; + public final V2 second; - private StringConcatenation(V1 v1, V2 v2) { - this.first = v1; - this.second = v2; - } + private StringConcatenation(V1 v1, V2 v2) { + this.first = v1; + this.second = v2; + } - public static StringConcatenation of(V1 v1, V2 v2) { - return new StringConcatenation<>(v1, v2); - } + public static StringConcatenation of(V1 v1, V2 v2) { + return new StringConcatenation<>(v1, v2); + } - public String toString() { - return "StringConcatenation(" + first + ", " + second + ")"; - } + public String toString() { + return "StringConcatenation(" + first + ", " + second + ")"; + } } diff --git a/checker/tests/resourceleak/StringFromObject.java b/checker/tests/resourceleak/StringFromObject.java index 0d108848d24..bccdc042c19 100644 --- a/checker/tests/resourceleak/StringFromObject.java +++ b/checker/tests/resourceleak/StringFromObject.java @@ -6,15 +6,15 @@ import java.util.Map.Entry; class StringFromObject { - boolean test(Map map) { - boolean isHierarchical = false; - for (Entry entry : map.entrySet()) { - String key = entry.getKey().toString().trim(); - if (key.startsWith("group") || key.startsWith("weight")) { - isHierarchical = true; - break; - } - } - return isHierarchical; + boolean test(Map map) { + boolean isHierarchical = false; + for (Entry entry : map.entrySet()) { + String key = entry.getKey().toString().trim(); + if (key.startsWith("group") || key.startsWith("weight")) { + isHierarchical = true; + break; + } } + return isHierarchical; + } } diff --git a/checker/tests/resourceleak/TernaryExpressions.java b/checker/tests/resourceleak/TernaryExpressions.java index 710e18ced66..c9656429d41 100644 --- a/checker/tests/resourceleak/TernaryExpressions.java +++ b/checker/tests/resourceleak/TernaryExpressions.java @@ -4,118 +4,118 @@ class TernaryExpressions { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; - } - - void c(@CalledMethods("a") Foo this) {} + @This Foo b() { + return this; } - Foo makeFoo() { - return new Foo(); - } + void c(@CalledMethods("a") Foo this) {} + } - static void takeOwnership(@Owning Foo foo) { - foo.a(); - } + Foo makeFoo() { + return new Foo(); + } - /** cases where ternary expressions are assigned to a variable */ - void testTernaryAssigned(boolean b) { - Foo ternary1 = b ? new Foo() : makeFoo(); - ternary1.a(); + static void takeOwnership(@Owning Foo foo) { + foo.a(); + } - // :: error: required.method.not.called - Foo ternary2 = b ? new Foo() : makeFoo(); + /** cases where ternary expressions are assigned to a variable */ + void testTernaryAssigned(boolean b) { + Foo ternary1 = b ? new Foo() : makeFoo(); + ternary1.a(); - // :: error: required.method.not.called - Foo x = new Foo(); - Foo ternary3 = b ? new Foo() : x; - ternary3.a(); + // :: error: required.method.not.called + Foo ternary2 = b ? new Foo() : makeFoo(); - Foo y = new Foo(); - Foo ternary4 = b ? y : y; - ternary4.a(); + // :: error: required.method.not.called + Foo x = new Foo(); + Foo ternary3 = b ? new Foo() : x; + ternary3.a(); - takeOwnership(b ? new Foo() : makeFoo()); + Foo y = new Foo(); + Foo ternary4 = b ? y : y; + ternary4.a(); - // :: error: required.method.not.called - Foo x2 = new Foo(); - takeOwnership(b ? x2 : null); + takeOwnership(b ? new Foo() : makeFoo()); - int i = 10; - Foo ternaryInLoop = null; - while (i > 0) { - // :: error: required.method.not.called - ternaryInLoop = b ? null : new Foo(); - i--; - } - ternaryInLoop.a(); + // :: error: required.method.not.called + Foo x2 = new Foo(); + takeOwnership(b ? x2 : null); - (b ? new Foo() : makeFoo()).a(); + int i = 10; + Foo ternaryInLoop = null; + while (i > 0) { + // :: error: required.method.not.called + ternaryInLoop = b ? null : new Foo(); + i--; } - - /** - * tests where ternary and cast expressions (possibly nested) may or may not be assigned to a - * variable - */ - void testTernaryCastUnassigned(boolean b) { - // :: error: required.method.not.called - if ((b ? new Foo() : null) != null) { - b = !b; - } - - // :: error: required.method.not.called - if ((b ? makeFoo() : null) != null) { - b = !b; - } - - Foo x = new Foo(); - if ((b ? x : null) != null) { - b = !b; - } - x.a(); - - // :: error: required.method.not.called - if (((Foo) new Foo()) != null) { - b = !b; - } - - // double cast; no error - Foo doubleCast = (Foo) ((Foo) makeFoo()); - doubleCast.a(); - - // nesting casts and ternary expressions; no error - Foo deepNesting = (b ? (!b ? makeFoo() : (Foo) makeFoo()) : ((Foo) new Foo())); - deepNesting.a(); + ternaryInLoop.a(); + + (b ? new Foo() : makeFoo()).a(); + } + + /** + * tests where ternary and cast expressions (possibly nested) may or may not be assigned to a + * variable + */ + void testTernaryCastUnassigned(boolean b) { + // :: error: required.method.not.called + if ((b ? new Foo() : null) != null) { + b = !b; } - @Owning - Foo testTernaryReturnOk(boolean b) { - return b ? new Foo() : makeFoo(); + // :: error: required.method.not.called + if ((b ? makeFoo() : null) != null) { + b = !b; } - @Owning - Foo testTernaryReturnBad(boolean b) { - // :: error: required.method.not.called - Foo x = new Foo(); - return b ? x : makeFoo(); + Foo x = new Foo(); + if ((b ? x : null) != null) { + b = !b; } + x.a(); - @InheritableMustCall("toString") - static class Sub1 extends Object {} - - @InheritableMustCall("clone") - static class Sub2 extends Object {} - - static void testTernarySubtyping(boolean b) { - // :: error: required.method.not.called - Object toStringAndClone = b ? new Sub1() : new Sub2(); - // at this point, for soundness, we should be responsible for calling both toString and - // clone on obj... - toStringAndClone.toString(); + // :: error: required.method.not.called + if (((Foo) new Foo()) != null) { + b = !b; } + + // double cast; no error + Foo doubleCast = (Foo) ((Foo) makeFoo()); + doubleCast.a(); + + // nesting casts and ternary expressions; no error + Foo deepNesting = (b ? (!b ? makeFoo() : (Foo) makeFoo()) : ((Foo) new Foo())); + deepNesting.a(); + } + + @Owning + Foo testTernaryReturnOk(boolean b) { + return b ? new Foo() : makeFoo(); + } + + @Owning + Foo testTernaryReturnBad(boolean b) { + // :: error: required.method.not.called + Foo x = new Foo(); + return b ? x : makeFoo(); + } + + @InheritableMustCall("toString") + static class Sub1 extends Object {} + + @InheritableMustCall("clone") + static class Sub2 extends Object {} + + static void testTernarySubtyping(boolean b) { + // :: error: required.method.not.called + Object toStringAndClone = b ? new Sub1() : new Sub2(); + // at this point, for soundness, we should be responsible for calling both toString and + // clone on obj... + toStringAndClone.toString(); + } } diff --git a/checker/tests/resourceleak/TryWithResourcesDeclaration.java b/checker/tests/resourceleak/TryWithResourcesDeclaration.java index ebf7baf54b6..b25e22eaaf9 100644 --- a/checker/tests/resourceleak/TryWithResourcesDeclaration.java +++ b/checker/tests/resourceleak/TryWithResourcesDeclaration.java @@ -6,38 +6,38 @@ import java.util.*; class TryWithResourcesDeclaration { - static void test(String address, int port) { - try (Socket socket = new Socket(address, port)) { + static void test(String address, int port) { + try (Socket socket = new Socket(address, port)) { - } catch (Exception e) { + } catch (Exception e) { - } } + } - public boolean isPreUpgradableLayout(File oldF) throws IOException { + public boolean isPreUpgradableLayout(File oldF) throws IOException { - if (!oldF.exists()) { - return false; - } - // check the layout version inside the storage file - // Lock and Read old storage file - try (RandomAccessFile oldFile = new RandomAccessFile(oldF, "rws"); - FileLock oldLock = oldFile.getChannel().tryLock()) { - if (null == oldLock) { - throw new OverlappingFileLockException(); - } - oldFile.seek(0); - int oldVersion = oldFile.readInt(); - return false; - } + if (!oldF.exists()) { + return false; } + // check the layout version inside the storage file + // Lock and Read old storage file + try (RandomAccessFile oldFile = new RandomAccessFile(oldF, "rws"); + FileLock oldLock = oldFile.getChannel().tryLock()) { + if (null == oldLock) { + throw new OverlappingFileLockException(); + } + oldFile.seek(0); + int oldVersion = oldFile.readInt(); + return false; + } + } - public void testNestedTryWithResourcesDecls(Properties prop, ClassLoader cl, String propfile) - throws Exception { - try (InputStream in = cl.getResourceAsStream(propfile)) { - try (InputStream fis = new FileInputStream(propfile)) { - prop.load(fis); - } - } + public void testNestedTryWithResourcesDecls(Properties prop, ClassLoader cl, String propfile) + throws Exception { + try (InputStream in = cl.getResourceAsStream(propfile)) { + try (InputStream fis = new FileInputStream(propfile)) { + prop.load(fis); + } } + } } diff --git a/checker/tests/resourceleak/TryWithResourcesFP.java b/checker/tests/resourceleak/TryWithResourcesFP.java index b49932f95f0..69f584449df 100644 --- a/checker/tests/resourceleak/TryWithResourcesFP.java +++ b/checker/tests/resourceleak/TryWithResourcesFP.java @@ -1,29 +1,27 @@ // Based on a false positive reported on the BibTeX project -import org.plumelib.util.EntryReader; -import org.plumelib.util.UtilPlume; - import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import org.plumelib.util.EntryReader; +import org.plumelib.util.UtilPlume; @SuppressWarnings("deprecation") public final class TryWithResourcesFP { - public static void main(String[] args) { - for (String filename : args) { - File inFile = new File(filename); - File outFile = new File(inFile.getName()); // in current directory - // Delete the file to work around a bug. Files.newBufferedWriter (which is called by - // UtilPlume.bufferedFileWriter) seems to have a bug where it does not correctly - // truncate the file first. If the target file already exists, then characters beyond - // what is written remain in the file. - outFile.delete(); - try (PrintWriter out = - new PrintWriter(UtilPlume.bufferedFileWriter(outFile.toString())); - EntryReader er = new EntryReader(filename)) { - } catch (IOException e) { + public static void main(String[] args) { + for (String filename : args) { + File inFile = new File(filename); + File outFile = new File(inFile.getName()); // in current directory + // Delete the file to work around a bug. Files.newBufferedWriter (which is called by + // UtilPlume.bufferedFileWriter) seems to have a bug where it does not correctly + // truncate the file first. If the target file already exists, then characters beyond + // what is written remain in the file. + outFile.delete(); + try (PrintWriter out = new PrintWriter(UtilPlume.bufferedFileWriter(outFile.toString())); + EntryReader er = new EntryReader(filename)) { + } catch (IOException e) { - } - } + } } + } } diff --git a/checker/tests/resourceleak/TryWithResourcesMultiResources.java b/checker/tests/resourceleak/TryWithResourcesMultiResources.java index de39bfc0399..c2ada57e2f0 100644 --- a/checker/tests/resourceleak/TryWithResourcesMultiResources.java +++ b/checker/tests/resourceleak/TryWithResourcesMultiResources.java @@ -1,43 +1,42 @@ -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.Socket; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; public class TryWithResourcesMultiResources { - class OuterResource implements java.io.Closeable { - private final @Owning Socket socket; + class OuterResource implements java.io.Closeable { + private final @Owning Socket socket; - public @MustCallAlias OuterResource(@MustCallAlias Socket sock) throws IOException { - this.socket = sock; - } + public @MustCallAlias OuterResource(@MustCallAlias Socket sock) throws IOException { + this.socket = sock; + } - @Override - @EnsuresCalledMethods( - value = {"this.socket"}, - methods = {"close"}) - public void close() throws IOException { - this.socket.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.socket"}, + methods = {"close"}) + public void close() throws IOException { + this.socket.close(); } + } - // If "new OuterResource" throws an exception, then the socket won't be released. - public void multiResourcesWrong(String address, int port) { - // :: error: required.method.not.called - try (OuterResource outer = new OuterResource(new Socket(address, port))) { + // If "new OuterResource" throws an exception, then the socket won't be released. + public void multiResourcesWrong(String address, int port) { + // :: error: required.method.not.called + try (OuterResource outer = new OuterResource(new Socket(address, port))) { - } catch (Exception e) { + } catch (Exception e) { - } } + } - public void multiResourcesCorrect(String address, int port) { - try (Socket s = new Socket(address, port); - OuterResource outer = new OuterResource(s)) { + public void multiResourcesCorrect(String address, int port) { + try (Socket s = new Socket(address, port); + OuterResource outer = new OuterResource(s)) { - } catch (Exception e) { + } catch (Exception e) { - } } + } } diff --git a/checker/tests/resourceleak/TryWithResourcesVariable.java b/checker/tests/resourceleak/TryWithResourcesVariable.java index 34568261fba..7642e22fcd6 100644 --- a/checker/tests/resourceleak/TryWithResourcesVariable.java +++ b/checker/tests/resourceleak/TryWithResourcesVariable.java @@ -2,126 +2,125 @@ // @below-java9-jdk-skip-test -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; class TryWithResourcesVariable { - static void test1() throws Exception { - Socket socket = new Socket("127.0.0.1", 5050); - try (socket) { + static void test1() throws Exception { + Socket socket = new Socket("127.0.0.1", 5050); + try (socket) { - } catch (Exception e) { + } catch (Exception e) { - } } + } - static void test2(@Owning Socket socket) { - try (socket) { + static void test2(@Owning Socket socket) { + try (socket) { - } catch (Exception e) { + } catch (Exception e) { - } } + } - static void test3(InetSocketAddress isa) { - Socket socket = new Socket(); - try (socket) { - socket.connect(isa); - } catch (Exception e) { + static void test3(InetSocketAddress isa) { + Socket socket = new Socket(); + try (socket) { + socket.connect(isa); + } catch (Exception e) { - } } + } - // :: error: (required.method.not.called) - static void test4(@Owning InputStream i1, @Owning InputStream i2) { - try { - try (i2) {} - // This will not run if i2.close() throws an IOException - i1.close(); - } catch (Exception e) { - - } - } + // :: error: (required.method.not.called) + static void test4(@Owning InputStream i1, @Owning InputStream i2) { + try { + try (i2) {} + // This will not run if i2.close() throws an IOException + i1.close(); + } catch (Exception e) { - static void test4Fixed(@Owning InputStream i1, @Owning InputStream i2) throws IOException { - try { - try (i2) {} - } catch (Exception e) { - } - i1.close(); } + } - @InheritableMustCall("disposer") - static class FinalResourceField { - final @Owning Socket socketField; - - FinalResourceField() { - try { - socketField = new Socket("127.0.0.1", 5050); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + static void test4Fixed(@Owning InputStream i1, @Owning InputStream i2) throws IOException { + try { + try (i2) {} + } catch (Exception e) { + } + i1.close(); + } + + @InheritableMustCall("disposer") + static class FinalResourceField { + final @Owning Socket socketField; + + FinalResourceField() { + try { + socketField = new Socket("127.0.0.1", 5050); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - @EnsuresCalledMethods(value = "this.socketField", methods = "close") - void disposer() { - try (socketField) { + @EnsuresCalledMethods(value = "this.socketField", methods = "close") + void disposer() { + try (socketField) { - } catch (Exception e) { + } catch (Exception e) { - } - } + } } + } - static void closeFinalFieldUnsupported() throws Exception { - // This is a false positive (i.e., there is no resource leak), but our checker reports a - // warning since it does not support this coding pattern. - // :: error: (required.method.not.called) - FinalResourceField finalResourceField = new FinalResourceField(); - try (finalResourceField.socketField) {} - } + static void closeFinalFieldUnsupported() throws Exception { + // This is a false positive (i.e., there is no resource leak), but our checker reports a + // warning since it does not support this coding pattern. + // :: error: (required.method.not.called) + FinalResourceField finalResourceField = new FinalResourceField(); + try (finalResourceField.socketField) {} + } - @InheritableMustCall("disposer") - static class FinalResourceFieldWrapper { + @InheritableMustCall("disposer") + static class FinalResourceFieldWrapper { - final @Owning FinalResourceField frField = new FinalResourceField(); + final @Owning FinalResourceField frField = new FinalResourceField(); - @EnsuresCalledMethods(value = "this.frField", methods = "disposer") - void disposer() { - this.frField.disposer(); - } + @EnsuresCalledMethods(value = "this.frField", methods = "disposer") + void disposer() { + this.frField.disposer(); } + } - static void closeWrapperUnsupported() throws Exception { - // This is a false positive (i.e., there is no resource leak), but our checker reports a - // warning since it does not support this coding pattern. - // :: error: (required.method.not.called) - FinalResourceFieldWrapper finalResourceFieldWrapper = new FinalResourceFieldWrapper(); - try (finalResourceFieldWrapper.frField.socketField) {} + static void closeWrapperUnsupported() throws Exception { + // This is a false positive (i.e., there is no resource leak), but our checker reports a + // warning since it does not support this coding pattern. + // :: error: (required.method.not.called) + FinalResourceFieldWrapper finalResourceFieldWrapper = new FinalResourceFieldWrapper(); + try (finalResourceFieldWrapper.frField.socketField) {} + } + + @InheritableMustCall("disposer") + static class TwoFinalResourceFields { + final @Owning Socket socketField1; + final @Owning Socket socketField2; + + TwoFinalResourceFields(@Owning Socket socket1, @Owning Socket socket2) { + socketField1 = socket1; + socketField2 = socket2; } - @InheritableMustCall("disposer") - static class TwoFinalResourceFields { - final @Owning Socket socketField1; - final @Owning Socket socketField2; - - TwoFinalResourceFields(@Owning Socket socket1, @Owning Socket socket2) { - socketField1 = socket1; - socketField2 = socket2; - } - - @EnsuresCalledMethods(value = "this.socketField1", methods = "close") - @EnsuresCalledMethods(value = "this.socketField2", methods = "close") - void disposer() { - try (socketField1; - socketField2) { + @EnsuresCalledMethods(value = "this.socketField1", methods = "close") + @EnsuresCalledMethods(value = "this.socketField2", methods = "close") + void disposer() { + try (socketField1; + socketField2) { - } catch (Exception e) { + } catch (Exception e) { - } - } + } } + } } diff --git a/checker/tests/resourceleak/TwoConstructorsCloseable.java b/checker/tests/resourceleak/TwoConstructorsCloseable.java index 47a20424728..282ce8d1a1e 100644 --- a/checker/tests/resourceleak/TwoConstructorsCloseable.java +++ b/checker/tests/resourceleak/TwoConstructorsCloseable.java @@ -3,17 +3,17 @@ import java.io.Closeable; public class TwoConstructorsCloseable implements Closeable { - public TwoConstructorsCloseable(Object obj) {} + public TwoConstructorsCloseable(Object obj) {} - public TwoConstructorsCloseable() { - this(null); - } + public TwoConstructorsCloseable() { + this(null); + } - public void close() {} + public void close() {} - class Derivative extends TwoConstructorsCloseable { - Derivative() { - super(null); - } + class Derivative extends TwoConstructorsCloseable { + Derivative() { + super(null); } + } } diff --git a/checker/tests/resourceleak/TwoOwningMCATest.java b/checker/tests/resourceleak/TwoOwningMCATest.java index 5166c73253d..6167a508f6c 100644 --- a/checker/tests/resourceleak/TwoOwningMCATest.java +++ b/checker/tests/resourceleak/TwoOwningMCATest.java @@ -6,33 +6,33 @@ @InheritableMustCall({"finish1", "finish2"}) class TwoOwningMCATest { - @Owning private final Foo f1 = new Foo(); - - @Owning private final Foo f2; - - @MustCallAlias - // :: error: mustcallalias.out.of.scope - TwoOwningMCATest(@MustCallAlias Foo g) { - this.f2 = g; - } - - @EnsuresCalledMethods(value = "this.f1", methods = "a") - void finish1() { - this.f1.a(); - } - - @EnsuresCalledMethods(value = "this.f2", methods = "a") - void finish2() { - this.f2.a(); - } - - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - public static void test(Foo f) { - TwoOwningMCATest t = new TwoOwningMCATest(f); - f.a(); - } + @Owning private final Foo f1 = new Foo(); + + @Owning private final Foo f2; + + @MustCallAlias + // :: error: mustcallalias.out.of.scope + TwoOwningMCATest(@MustCallAlias Foo g) { + this.f2 = g; + } + + @EnsuresCalledMethods(value = "this.f1", methods = "a") + void finish1() { + this.f1.a(); + } + + @EnsuresCalledMethods(value = "this.f2", methods = "a") + void finish2() { + this.f2.a(); + } + + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + public static void test(Foo f) { + TwoOwningMCATest t = new TwoOwningMCATest(f); + f.a(); + } } diff --git a/checker/tests/resourceleak/TwoResourcesECM.java b/checker/tests/resourceleak/TwoResourcesECM.java index aea95247566..e048b813ea8 100644 --- a/checker/tests/resourceleak/TwoResourcesECM.java +++ b/checker/tests/resourceleak/TwoResourcesECM.java @@ -3,40 +3,39 @@ // This test that shows that no unsoundess occurs when a single close() method is responsible // for closing two resources. -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.Socket; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; @InheritableMustCall("dispose") class TwoResourcesECM { - @Owning Socket s1, s2; + @Owning Socket s1, s2; - // The contracts.postcondition error below is thrown because s1 is not final, - // and therefore might theoretically be side-effected by the call to s2.close() - // even on the non-exceptional path. See ReplicaInputStreams.java for a variant - // of this test where such an error is not issued. Because this method can leak - // along both regular and exceptional exits, both errors are issued. - // - // The contracts.exceptional.postcondition.not.satisfied error is thrown because destructors - // have to close their resources even on exception. If s1.close() throws an exception, then - // s2.close() will not be called. - @EnsuresCalledMethods( - value = {"this.s1", "this.s2"}, - methods = {"close"}) - // :: error: (contracts.postcondition.not.satisfied) - // :: error: (contracts.exceptional.postcondition.not.satisfied) - public void dispose() throws IOException { - s1.close(); - s2.close(); - } + // The contracts.postcondition error below is thrown because s1 is not final, + // and therefore might theoretically be side-effected by the call to s2.close() + // even on the non-exceptional path. See ReplicaInputStreams.java for a variant + // of this test where such an error is not issued. Because this method can leak + // along both regular and exceptional exits, both errors are issued. + // + // The contracts.exceptional.postcondition.not.satisfied error is thrown because destructors + // have to close their resources even on exception. If s1.close() throws an exception, then + // s2.close() will not be called. + @EnsuresCalledMethods( + value = {"this.s1", "this.s2"}, + methods = {"close"}) + // :: error: (contracts.postcondition.not.satisfied) + // :: error: (contracts.exceptional.postcondition.not.satisfied) + public void dispose() throws IOException { + s1.close(); + s2.close(); + } - static void test1(TwoResourcesECM obj) { - try { - obj.dispose(); - } catch (IOException ioe) { + static void test1(TwoResourcesECM obj) { + try { + obj.dispose(); + } catch (IOException ioe) { - } } + } } diff --git a/checker/tests/resourceleak/TwoSocketContainer.java b/checker/tests/resourceleak/TwoSocketContainer.java index f290e89aaf0..0b21e58d130 100644 --- a/checker/tests/resourceleak/TwoSocketContainer.java +++ b/checker/tests/resourceleak/TwoSocketContainer.java @@ -1,38 +1,37 @@ // A test that a class with two owned sockets cannot be @MustCallAliased with both of them. +import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.net.Socket; - @InheritableMustCall({"close1", "close2"}) public class TwoSocketContainer { - @Owning private final Socket s1, s2; + @Owning private final Socket s1, s2; - // :: error: mustcallalias.out.of.scope - public @MustCallAlias TwoSocketContainer(@MustCallAlias Socket s1, @MustCallAlias Socket s2) { - this.s1 = s1; - this.s2 = s2; - } + // :: error: mustcallalias.out.of.scope + public @MustCallAlias TwoSocketContainer(@MustCallAlias Socket s1, @MustCallAlias Socket s2) { + this.s1 = s1; + this.s2 = s2; + } - @EnsuresCalledMethods( - value = "this.s1", - methods = {"close"}) - public void close1() throws java.io.IOException { - s1.close(); - } + @EnsuresCalledMethods( + value = "this.s1", + methods = {"close"}) + public void close1() throws java.io.IOException { + s1.close(); + } - @EnsuresCalledMethods( - value = "this.s2", - methods = {"close"}) - public void close2() throws java.io.IOException { - s2.close(); - } + @EnsuresCalledMethods( + value = "this.s2", + methods = {"close"}) + public void close2() throws java.io.IOException { + s2.close(); + } - // The following error should be thrown about at least sock2 - // :: error: required.method.not.called - public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { - TwoSocketContainer tsc = new TwoSocketContainer(sock1, sock2); - sock1.close(); - } + // The following error should be thrown about at least sock2 + // :: error: required.method.not.called + public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { + TwoSocketContainer tsc = new TwoSocketContainer(sock1, sock2); + sock1.close(); + } } diff --git a/checker/tests/resourceleak/TwoSocketContainerSafe.java b/checker/tests/resourceleak/TwoSocketContainerSafe.java index 6e1482a1e76..f7cea334fe4 100644 --- a/checker/tests/resourceleak/TwoSocketContainerSafe.java +++ b/checker/tests/resourceleak/TwoSocketContainerSafe.java @@ -1,43 +1,42 @@ // This is the safe version of TwoSocketContainer.java. +import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -import java.net.Socket; - @InheritableMustCall({"close1", "close2"}) public class TwoSocketContainerSafe { - @Owning private final Socket s1, s2; + @Owning private final Socket s1, s2; - public TwoSocketContainerSafe(@Owning Socket s1, @Owning Socket s2) { - this.s1 = s1; - this.s2 = s2; - } + public TwoSocketContainerSafe(@Owning Socket s1, @Owning Socket s2) { + this.s1 = s1; + this.s2 = s2; + } - @EnsuresCalledMethods( - value = "this.s1", - methods = {"close"}) - public void close1() throws java.io.IOException { - s1.close(); - } + @EnsuresCalledMethods( + value = "this.s1", + methods = {"close"}) + public void close1() throws java.io.IOException { + s1.close(); + } - @EnsuresCalledMethods( - value = "this.s2", - methods = {"close"}) - public void close2() throws java.io.IOException { - s2.close(); - } + @EnsuresCalledMethods( + value = "this.s2", + methods = {"close"}) + public void close2() throws java.io.IOException { + s2.close(); + } - public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { - TwoSocketContainerSafe tsc = new TwoSocketContainerSafe(sock1, sock2); - try { - tsc.close1(); - } catch (Exception io) { - } finally { - try { - tsc.close2(); - } catch (Exception io2) { - } - } + public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { + TwoSocketContainerSafe tsc = new TwoSocketContainerSafe(sock1, sock2); + try { + tsc.close1(); + } catch (Exception io) { + } finally { + try { + tsc.close2(); + } catch (Exception io2) { + } } + } } diff --git a/checker/tests/resourceleak/TypeProcessError.java b/checker/tests/resourceleak/TypeProcessError.java index 4db149eee1c..b10816c1f88 100644 --- a/checker/tests/resourceleak/TypeProcessError.java +++ b/checker/tests/resourceleak/TypeProcessError.java @@ -1,33 +1,32 @@ -import org.checkerframework.checker.mustcall.qual.MustCall; -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.IOException; import java.io.PrintStream; +import org.checkerframework.checker.mustcall.qual.MustCall; +import org.checkerframework.checker.mustcall.qual.Owning; public class TypeProcessError { - @SuppressWarnings("required.method.not.called") - @Owning - @MustCall("close") PrintStream ps_instance; + @SuppressWarnings("required.method.not.called") + @Owning + @MustCall("close") PrintStream ps_instance; - @SuppressWarnings("required.method.not.called") - private static @Owning @MustCall("close") PrintStream ps_static; + @SuppressWarnings("required.method.not.called") + private static @Owning @MustCall("close") PrintStream ps_static; - @SuppressWarnings("missing.creates.mustcall.for") - static void m_static() throws IOException { - ps_static.close(); - ps_static = new PrintStream("filename.txt"); - } + @SuppressWarnings("missing.creates.mustcall.for") + static void m_static() throws IOException { + ps_static.close(); + ps_static = new PrintStream("filename.txt"); + } } class TypeProcessError2 extends TypeProcessError { - @SuppressWarnings("required.method.not.called") - @Owning - @MustCall("close") PrintStream ps_instance; + @SuppressWarnings("required.method.not.called") + @Owning + @MustCall("close") PrintStream ps_instance; - @SuppressWarnings("missing.creates.mustcall.for") - void m() throws IOException { - super.ps_instance.close(); - super.ps_instance = new PrintStream("filename.txt"); - } + @SuppressWarnings("missing.creates.mustcall.for") + void m() throws IOException { + super.ps_instance.close(); + super.ps_instance = new PrintStream("filename.txt"); + } } diff --git a/checker/tests/resourceleak/TypevarDefault.java b/checker/tests/resourceleak/TypevarDefault.java index e6641b69a3a..e3911ee8dc4 100644 --- a/checker/tests/resourceleak/TypevarDefault.java +++ b/checker/tests/resourceleak/TypevarDefault.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.mustcall.qual.*; class IATF< - Value extends CFAV, - Store extends IS, - Transfer extends IT, - Flow extends CFAA> - extends GATF {} + Value extends CFAV, + Store extends IS, + Transfer extends IT, + Flow extends CFAA> + extends GATF {} class CFAV> {} @@ -22,27 +22,27 @@ class CFAT, S extends CFAS, T extends CFAT> {} class CFAS, S extends CFAS> {} class GATF< - Value extends CFAV, - Store extends CFAS, - TransferFunction extends CFAT, - FlowAnalysis extends CFAA> { - - public @MustCall({}) Store getRegularExitStore() { - return null; - } + Value extends CFAV, + Store extends CFAS, + TransferFunction extends CFAT, + FlowAnalysis extends CFAA> { + + public @MustCall({}) Store getRegularExitStore() { + return null; + } } class BTV> {} class IV< - Factory extends IATF, - Value extends CFAV, - Store extends IS> - extends BTV { + Factory extends IATF, + Value extends CFAV, + Store extends IS> + extends BTV { - Factory atypefactory; + Factory atypefactory; - public void test() { - Store store = this.atypefactory.getRegularExitStore(); - } + public void test() { + Store store = this.atypefactory.getRegularExitStore(); + } } diff --git a/checker/tests/resourceleak/TypevarSimple.java b/checker/tests/resourceleak/TypevarSimple.java index 639ec7a80cb..5376d73c2b8 100644 --- a/checker/tests/resourceleak/TypevarSimple.java +++ b/checker/tests/resourceleak/TypevarSimple.java @@ -4,8 +4,8 @@ import org.checkerframework.checker.mustcall.qual.*; public class TypevarSimple { - public static void sneakyDropCorrect( - @Owning @MustCall("close") T value1) throws Exception { - value1.close(); - } + public static void sneakyDropCorrect( + @Owning @MustCall("close") T value1) throws Exception { + value1.close(); + } } diff --git a/checker/tests/resourceleak/UnconnectedSocketAlias.java b/checker/tests/resourceleak/UnconnectedSocketAlias.java index 7e1bc0b1bb3..55bd1e96897 100644 --- a/checker/tests/resourceleak/UnconnectedSocketAlias.java +++ b/checker/tests/resourceleak/UnconnectedSocketAlias.java @@ -4,11 +4,11 @@ import java.net.*; class UnconnectedSocketAlias { - void test(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - Socket t = s; - t.close(); - s.connect(sa); - } + void test(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + Socket t = s; + t.close(); + s.connect(sa); + } } diff --git a/checker/tests/resourceleak/WrapperStream.java b/checker/tests/resourceleak/WrapperStream.java index ad3c9e90908..79f187c5193 100644 --- a/checker/tests/resourceleak/WrapperStream.java +++ b/checker/tests/resourceleak/WrapperStream.java @@ -4,7 +4,7 @@ import java.io.*; class WrapperStream { - void test(byte[] buf) { - InputStream is = new ByteArrayInputStream(buf); - } + void test(byte[] buf) { + InputStream is = new ByteArrayInputStream(buf); + } } diff --git a/checker/tests/resourceleak/WrapperStreamPoly.java b/checker/tests/resourceleak/WrapperStreamPoly.java index 7a3eafbd897..8a50fdf0917 100644 --- a/checker/tests/resourceleak/WrapperStreamPoly.java +++ b/checker/tests/resourceleak/WrapperStreamPoly.java @@ -2,18 +2,17 @@ // "polymorphic" streams like DataInputStream and DataOutputStream are treated as // their constituent stream. -import org.checkerframework.checker.mustcall.qual.Owning; - import java.io.*; +import org.checkerframework.checker.mustcall.qual.Owning; class WrapperStreamPoly { - void test_no_close_needed(@Owning ByteArrayInputStream b) { - // b doesn't need to be closed, so neither does this stream. - DataInputStream d = new DataInputStream(b); - } + void test_no_close_needed(@Owning ByteArrayInputStream b) { + // b doesn't need to be closed, so neither does this stream. + DataInputStream d = new DataInputStream(b); + } - // :: error: required.method.not.called - void test_close_needed(@Owning InputStream b) { - DataInputStream d = new DataInputStream(b); - } + // :: error: required.method.not.called + void test_close_needed(@Owning InputStream b) { + DataInputStream d = new DataInputStream(b); + } } diff --git a/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java b/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java index 0cb36deee57..187ca36ff15 100644 --- a/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java +++ b/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java @@ -5,59 +5,58 @@ // regression // test that at least one error is still issued. -import org.checkerframework.checker.mustcall.qual.MustCall; - import java.io.*; import java.nio.ByteBuffer; +import org.checkerframework.checker.mustcall.qual.MustCall; @MustCall({}) // :: error: inconsistent.mustcall.subtype public class ZookeeperByteBufferInputStream extends InputStream { - ByteBuffer bb; + ByteBuffer bb; - // :: error: super.invocation.invalid - public ZookeeperByteBufferInputStream(ByteBuffer bb) { - this.bb = bb; - } + // :: error: super.invocation.invalid + public ZookeeperByteBufferInputStream(ByteBuffer bb) { + this.bb = bb; + } - @Override - public int read() throws IOException { - if (bb.remaining() == 0) { - return -1; - } - return bb.get() & 0xff; + @Override + public int read() throws IOException { + if (bb.remaining() == 0) { + return -1; } - - @Override - public int available() throws IOException { - return bb.remaining(); + return bb.get() & 0xff; + } + + @Override + public int available() throws IOException { + return bb.remaining(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (bb.remaining() == 0) { + return -1; } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (bb.remaining() == 0) { - return -1; - } - if (len > bb.remaining()) { - len = bb.remaining(); - } - bb.get(b, off, len); - return len; - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); + if (len > bb.remaining()) { + len = bb.remaining(); } - - @Override - public long skip(long n) throws IOException { - if (n < 0L) { - return 0; - } - n = Math.min(n, bb.remaining()); - bb.position(bb.position() + (int) n); - return n; + bb.get(b, off, len); + return len; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public long skip(long n) throws IOException { + if (n < 0L) { + return 0; } + n = Math.min(n, bb.remaining()); + bb.position(bb.position() + (int) n); + return n; + } } diff --git a/checker/tests/resourceleak/ZookeeperReport1.java b/checker/tests/resourceleak/ZookeeperReport1.java index a38b2be1a21..f00cad7ff77 100644 --- a/checker/tests/resourceleak/ZookeeperReport1.java +++ b/checker/tests/resourceleak/ZookeeperReport1.java @@ -1,72 +1,71 @@ // Based on a Zookeeper false positive that requires unconnected socket support. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; +import org.checkerframework.checker.mustcall.qual.*; class ZookeeperReport1 { - static int tickTime, initLimit; + static int tickTime, initLimit; - protected static @MustCall({}) Socket createSocket() throws IOException { - Socket sock; - sock = new Socket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall({}) Socket createSocket() throws IOException { + Socket sock; + sock = new Socket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - protected static @MustCall({}) Socket createSocket2() throws IOException { - Socket sock; - sock = createCustomSocket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall({}) Socket createSocket2() throws IOException { + Socket sock; + sock = createCustomSocket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - // This is the full version of case 1. - protected static @MustCall({}) Socket createSocket3(boolean b) throws IOException { - Socket sock; - if (b) { - sock = createCustomSocket(); - } else { - sock = new Socket(); - } - sock.setSoTimeout(tickTime * initLimit); - return sock; + // This is the full version of case 1. + protected static @MustCall({}) Socket createSocket3(boolean b) throws IOException { + Socket sock; + if (b) { + sock = createCustomSocket(); + } else { + sock = new Socket(); } + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - private static @MustCall({}) Socket createCustomSocket() { - return new Socket(); - } + private static @MustCall({}) Socket createCustomSocket() { + return new Socket(); + } - static void use1() throws IOException { - Socket s = createSocket(); - } + static void use1() throws IOException { + Socket s = createSocket(); + } - static void use2(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket(); - s.connect(endpoint); - } + static void use2(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket(); + s.connect(endpoint); + } - static void use3() throws IOException { - Socket s = createSocket2(); - } + static void use3() throws IOException { + Socket s = createSocket2(); + } - static void use4(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket2(); - s.connect(endpoint); - } + static void use4(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket2(); + s.connect(endpoint); + } - static void use5(boolean b) throws IOException { - Socket s = createSocket3(b); - } + static void use5(boolean b) throws IOException { + Socket s = createSocket3(b); + } - static void use6(SocketAddress endpoint, boolean b) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket3(b); - s.connect(endpoint); - } + static void use6(SocketAddress endpoint, boolean b) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket3(b); + s.connect(endpoint); + } } diff --git a/checker/tests/resourceleak/ZookeeperReport1a.java b/checker/tests/resourceleak/ZookeeperReport1a.java index 5462d47ad3b..a286418ca9d 100644 --- a/checker/tests/resourceleak/ZookeeperReport1a.java +++ b/checker/tests/resourceleak/ZookeeperReport1a.java @@ -1,73 +1,72 @@ // Based on a Zookeeper false positive that requires unconnected socket support. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; +import org.checkerframework.checker.mustcall.qual.*; // Like ZookeeperReport1, but using "@MustCall()" instead of "@MustCall({})" class ZookeeperReport1a { - static int tickTime, initLimit; + static int tickTime, initLimit; - protected static @MustCall() Socket createSocket() throws IOException { - Socket sock; - sock = new Socket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall() Socket createSocket() throws IOException { + Socket sock; + sock = new Socket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - protected static @MustCall() Socket createSocket2() throws IOException { - Socket sock; - sock = createCustomSocket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall() Socket createSocket2() throws IOException { + Socket sock; + sock = createCustomSocket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - // This is the full version of case 1. - protected static @MustCall() Socket createSocket3(boolean b) throws IOException { - Socket sock; - if (b) { - sock = createCustomSocket(); - } else { - sock = new Socket(); - } - sock.setSoTimeout(tickTime * initLimit); - return sock; + // This is the full version of case 1. + protected static @MustCall() Socket createSocket3(boolean b) throws IOException { + Socket sock; + if (b) { + sock = createCustomSocket(); + } else { + sock = new Socket(); } + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - private static @MustCall() Socket createCustomSocket() { - return new Socket(); - } + private static @MustCall() Socket createCustomSocket() { + return new Socket(); + } - static void use1() throws IOException { - Socket s = createSocket(); - } + static void use1() throws IOException { + Socket s = createSocket(); + } - static void use2(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket(); - s.connect(endpoint); - } + static void use2(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket(); + s.connect(endpoint); + } - static void use3() throws IOException { - Socket s = createSocket2(); - } + static void use3() throws IOException { + Socket s = createSocket2(); + } - static void use4(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket2(); - s.connect(endpoint); - } + static void use4(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket2(); + s.connect(endpoint); + } - static void use5(boolean b) throws IOException { - Socket s = createSocket3(b); - } + static void use5(boolean b) throws IOException { + Socket s = createSocket3(b); + } - static void use6(SocketAddress endpoint, boolean b) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket3(b); - s.connect(endpoint); - } + static void use6(SocketAddress endpoint, boolean b) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket3(b); + s.connect(endpoint); + } } diff --git a/checker/tests/resourceleak/ZookeeperReport3.java b/checker/tests/resourceleak/ZookeeperReport3.java index 099519e95fd..43907dcc88f 100644 --- a/checker/tests/resourceleak/ZookeeperReport3.java +++ b/checker/tests/resourceleak/ZookeeperReport3.java @@ -3,71 +3,70 @@ // ("createNewServerSocket"). This version of the test is incomplete: the real version // of this test also requires support for Optional. See ZookeeperTest3WithOptional.java. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; +import org.checkerframework.checker.mustcall.qual.*; class ZookeeperReport3 { - // This is a simpler version of case 3. - ServerSocket createServerSocket_easy( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - serverSocket = new ServerSocket(); - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return serverSocket; - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); - } - return null; + // This is a simpler version of case 3. + ServerSocket createServerSocket_easy( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + serverSocket = new ServerSocket(); + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return serverSocket; + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); } + return null; + } - ServerSocket createServerSocket( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - if (portUnification || sslQuorum) { - serverSocket = new UnifiedServerSocket(portUnification); - } else { - serverSocket = new ServerSocket(); - } - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return serverSocket; - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); - } - return null; + ServerSocket createServerSocket( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + if (portUnification || sslQuorum) { + serverSocket = new UnifiedServerSocket(portUnification); + } else { + serverSocket = new ServerSocket(); + } + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return serverSocket; + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); } + return null; + } - private ServerSocket createNewServerSocket( - SocketAddress address, boolean portUnification, boolean sslQuorum) throws IOException { - ServerSocket socket; + private ServerSocket createNewServerSocket( + SocketAddress address, boolean portUnification, boolean sslQuorum) throws IOException { + ServerSocket socket; - if (portUnification) { - System.out.println("Creating TLS-enabled quorum server socket"); - socket = new UnifiedServerSocket(true); - } else if (sslQuorum) { - System.out.println("Creating TLS-only quorum server socket"); - socket = new UnifiedServerSocket(false); - } else { - socket = new ServerSocket(); - } + if (portUnification) { + System.out.println("Creating TLS-enabled quorum server socket"); + socket = new UnifiedServerSocket(true); + } else if (sslQuorum) { + System.out.println("Creating TLS-only quorum server socket"); + socket = new UnifiedServerSocket(false); + } else { + socket = new ServerSocket(); + } - socket.setReuseAddress(true); - socket.bind(address); + socket.setReuseAddress(true); + socket.bind(address); - return socket; - } + return socket; + } - class UnifiedServerSocket extends ServerSocket { - // A human has to verify that this constructor actually does produce an unconnected socket. - @SuppressWarnings("inconsistent.constructor.type") - public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { - super(); - } + class UnifiedServerSocket extends ServerSocket { + // A human has to verify that this constructor actually does produce an unconnected socket. + @SuppressWarnings("inconsistent.constructor.type") + public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { + super(); } + } } diff --git a/checker/tests/resourceleak/ZookeeperReport3WithOptional.java b/checker/tests/resourceleak/ZookeeperReport3WithOptional.java index 7a2fa20ca70..d21f746cce0 100644 --- a/checker/tests/resourceleak/ZookeeperReport3WithOptional.java +++ b/checker/tests/resourceleak/ZookeeperReport3WithOptional.java @@ -3,51 +3,50 @@ // @skip-test until Optional is supported. For now, users should use null instead. -import org.checkerframework.checker.mustcall.qual.*; - import java.io.*; import java.net.*; import java.util.Optional; +import org.checkerframework.checker.mustcall.qual.*; class ZookeeperReport3WithOptional { - // This is a simpler version of case 3. - Optional createServerSocket_easy( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - serverSocket = new ServerSocket(); - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return Optional.of(serverSocket); - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); - } - return Optional.empty(); + // This is a simpler version of case 3. + Optional createServerSocket_easy( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + serverSocket = new ServerSocket(); + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return Optional.of(serverSocket); + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); } + return Optional.empty(); + } - Optional createServerSocket( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - if (portUnification || sslQuorum) { - serverSocket = new UnifiedServerSocket(portUnification); - } else { - serverSocket = new ServerSocket(); - } - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return Optional.of(serverSocket); - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); - } - return Optional.empty(); + Optional createServerSocket( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + if (portUnification || sslQuorum) { + serverSocket = new UnifiedServerSocket(portUnification); + } else { + serverSocket = new ServerSocket(); + } + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return Optional.of(serverSocket); + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); } + return Optional.empty(); + } - class UnifiedServerSocket extends ServerSocket { - // A human has to verify that this constructor actually does produce an unconnected socket. - public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { - super(); - } + class UnifiedServerSocket extends ServerSocket { + // A human has to verify that this constructor actually does produce an unconnected socket. + public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { + super(); } + } } diff --git a/checker/tests/resourceleak/ZookeeperReport6.java b/checker/tests/resourceleak/ZookeeperReport6.java index 3adef15563d..1f0a0d0c0dd 100644 --- a/checker/tests/resourceleak/ZookeeperReport6.java +++ b/checker/tests/resourceleak/ZookeeperReport6.java @@ -4,12 +4,12 @@ import java.nio.channels.SocketChannel; class ZookeeperReport6 { - SocketChannel createSock() throws IOException { - SocketChannel sock; - sock = SocketChannel.open(); - sock.configureBlocking(false); - sock.socket().setSoLinger(false, -1); - sock.socket().setTcpNoDelay(true); - return sock; - } + SocketChannel createSock() throws IOException { + SocketChannel sock; + sock = SocketChannel.open(); + sock.configureBlocking(false); + sock.socket().setSoLinger(false, -1); + sock.socket().setTcpNoDelay(true); + return sock; + } } diff --git a/checker/tests/resourceleak/ZookeeperTernaryCrash.java b/checker/tests/resourceleak/ZookeeperTernaryCrash.java index 62df18e405d..3c795c56a74 100644 --- a/checker/tests/resourceleak/ZookeeperTernaryCrash.java +++ b/checker/tests/resourceleak/ZookeeperTernaryCrash.java @@ -1,76 +1,74 @@ -import org.checkerframework.checker.mustcall.qual.MustCall; - import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.*; +import org.checkerframework.checker.mustcall.qual.MustCall; final class ZookeeperTernaryCrash { - private static List getSubjectAltNames(final X509Certificate cert) { - try { - final Collection> entries = cert.getSubjectAlternativeNames(); - if (entries == null) { - return Collections.emptyList(); - } - final List result = new ArrayList(); - for (List entry : entries) { - // the need to add this annotation is annoying, but it's better than the - // alternative, which would be to prevent boxed primitives from having must-call - // types at all. - final Integer type = - entry.size() >= 2 ? (@MustCall({}) Integer) entry.get(0) : null; - if (type != null) { - if (type == SubjectName.DNS || type == SubjectName.IP) { - final Object o = entry.get(1); - if (o instanceof String) { - result.add(new SubjectName((String) o, type)); - } else if (o instanceof byte[]) { - // TODO ASN.1 DER encoded form - } - } - } + private static List getSubjectAltNames(final X509Certificate cert) { + try { + final Collection> entries = cert.getSubjectAlternativeNames(); + if (entries == null) { + return Collections.emptyList(); + } + final List result = new ArrayList(); + for (List entry : entries) { + // the need to add this annotation is annoying, but it's better than the + // alternative, which would be to prevent boxed primitives from having must-call + // types at all. + final Integer type = entry.size() >= 2 ? (@MustCall({}) Integer) entry.get(0) : null; + if (type != null) { + if (type == SubjectName.DNS || type == SubjectName.IP) { + final Object o = entry.get(1); + if (o instanceof String) { + result.add(new SubjectName((String) o, type)); + } else if (o instanceof byte[]) { + // TODO ASN.1 DER encoded form } - return result; - } catch (final CertificateParsingException ignore) { - return Collections.emptyList(); + } } + } + return result; + } catch (final CertificateParsingException ignore) { + return Collections.emptyList(); } + } - private static final class SubjectName { + private static final class SubjectName { - static final int DNS = 2; - static final int IP = 7; + static final int DNS = 2; + static final int IP = 7; - private final String value; - private final int type; + private final String value; + private final int type; - static SubjectName IP(final String value) { - return new SubjectName(value, IP); - } + static SubjectName IP(final String value) { + return new SubjectName(value, IP); + } - static SubjectName DNS(final String value) { - return new SubjectName(value, DNS); - } + static SubjectName DNS(final String value) { + return new SubjectName(value, DNS); + } - SubjectName(final String value, final int type) { - if (type != DNS && type != IP) { - throw new IllegalArgumentException("Invalid type: " + type); - } - this.value = Objects.requireNonNull(value); - this.type = type; - } + SubjectName(final String value, final int type) { + if (type != DNS && type != IP) { + throw new IllegalArgumentException("Invalid type: " + type); + } + this.value = Objects.requireNonNull(value); + this.type = type; + } - public int getType() { - return type; - } + public int getType() { + return type; + } - public String getValue() { - return value; - } + public String getValue() { + return value; + } - @Override - public String toString() { - return value; - } + @Override + public String toString() { + return value; } + } } diff --git a/checker/tests/resourceleak/java17/SwitchExpressions.java b/checker/tests/resourceleak/java17/SwitchExpressions.java index f5a4521578f..e5b64ff974a 100644 --- a/checker/tests/resourceleak/java17/SwitchExpressions.java +++ b/checker/tests/resourceleak/java17/SwitchExpressions.java @@ -5,181 +5,181 @@ class SwitchExpressions { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; - } - - void c(@CalledMethods("a") Foo this) {} + @This Foo b() { + return this; } - Foo makeFoo() { - return new Foo(); - } + void c(@CalledMethods("a") Foo this) {} + } - static void takeOwnership(@Owning Foo foo) { - foo.a(); - } + Foo makeFoo() { + return new Foo(); + } - /** cases where switch expressions are assigned to a variable */ - void testSwitchAssigned(int i) { - Foo switch1 = - switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }; - switch1.a(); - - // :: error: required.method.not.called - Foo switch2 = - switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }; - - // :: error: required.method.not.called - Foo x = new Foo(); - Foo switch3 = - switch (i) { - case 3 -> new Foo(); - default -> x; - }; - switch3.a(); - - Foo y = new Foo(); - Foo switch4 = - switch (i) { - case 3 -> y; - default -> y; - }; - switch4.a(); - - takeOwnership( - switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }); - - // :: error: required.method.not.called - Foo x2 = new Foo(); - takeOwnership( - switch (i) { - case 3 -> x2; - default -> null; - }); - - int j = 10; - Foo switchInLoop = null; - while (j > 0) { - // :: error: required.method.not.called - switchInLoop = - switch (i) { - case 3 -> null; - default -> new Foo(); - }; - j--; - } - switchInLoop.a(); + static void takeOwnership(@Owning Foo foo) { + foo.a(); + } - (switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }) - .a(); - } + /** cases where switch expressions are assigned to a variable */ + void testSwitchAssigned(int i) { + Foo switch1 = + switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }; + switch1.a(); - /** - * tests where switch and cast expressions (possibly nested) may or may not be assigned to a - * variable - */ - void testSwitchCastUnassigned(int i) { - // :: error: required.method.not.called - if ((switch (i) { - case 3 -> new Foo(); - default -> null; - }) - != null) { - i = -i; - } + // :: error: required.method.not.called + Foo switch2 = + switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }; - // :: error: required.method.not.called - if (switch (i) { - case 3 -> makeFoo(); - default -> null; - } - != null) { - i = -i; - } + // :: error: required.method.not.called + Foo x = new Foo(); + Foo switch3 = + switch (i) { + case 3 -> new Foo(); + default -> x; + }; + switch3.a(); - Foo x = new Foo(); - if (switch (i) { - case 3 -> x; - default -> null; - } - != null) { - i = -i; - } - x.a(); + Foo y = new Foo(); + Foo switch4 = + switch (i) { + case 3 -> y; + default -> y; + }; + switch4.a(); + + takeOwnership( + switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }); + + // :: error: required.method.not.called + Foo x2 = new Foo(); + takeOwnership( + switch (i) { + case 3 -> x2; + default -> null; + }); + + int j = 10; + Foo switchInLoop = null; + while (j > 0) { + // :: error: required.method.not.called + switchInLoop = + switch (i) { + case 3 -> null; + default -> new Foo(); + }; + j--; + } + switchInLoop.a(); + + (switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }) + .a(); + } + + /** + * tests where switch and cast expressions (possibly nested) may or may not be assigned to a + * variable + */ + void testSwitchCastUnassigned(int i) { + // :: error: required.method.not.called + if ((switch (i) { + case 3 -> new Foo(); + default -> null; + }) + != null) { + i = -i; + } - // :: error: required.method.not.called - if (((Foo) new Foo()) != null) { - i = -i; + // :: error: required.method.not.called + if (switch (i) { + case 3 -> makeFoo(); + default -> null; } - - // double cast; no error - Foo doubleCast = (Foo) ((Foo) makeFoo()); - doubleCast.a(); - - // nesting casts and switch expressions; no error - Foo deepNesting = - (switch (i) { - case 3 -> - (switch (-i) { - case -3 -> makeFoo(); - default -> (Foo) makeFoo(); - }); - default -> ((Foo) new Foo()); - }); - deepNesting.a(); + != null) { + i = -i; } - @Owning - Foo testSwitchReturnOk(int i) { - return switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }; + Foo x = new Foo(); + if (switch (i) { + case 3 -> x; + default -> null; + } + != null) { + i = -i; } + x.a(); - @Owning - Foo testSwitchReturnBad(int i) { - // :: error: required.method.not.called - Foo x = new Foo(); - return switch (i) { - case 3 -> x; - default -> makeFoo(); - }; + // :: error: required.method.not.called + if (((Foo) new Foo()) != null) { + i = -i; } - @InheritableMustCall("toString") - static class Sub1 extends Object {} - - @InheritableMustCall("clone") - static class Sub2 extends Object {} - - static void testSwitchSubtyping(int i) { - // :: error: required.method.not.called - Object toStringAndClone = - switch (i) { - case 3 -> new Sub1(); - default -> new Sub2(); - }; - // at this point, for soundness, we should be responsible for calling both toString and - // clone on - // obj... - toStringAndClone.toString(); - } + // double cast; no error + Foo doubleCast = (Foo) ((Foo) makeFoo()); + doubleCast.a(); + + // nesting casts and switch expressions; no error + Foo deepNesting = + (switch (i) { + case 3 -> + (switch (-i) { + case -3 -> makeFoo(); + default -> (Foo) makeFoo(); + }); + default -> ((Foo) new Foo()); + }); + deepNesting.a(); + } + + @Owning + Foo testSwitchReturnOk(int i) { + return switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }; + } + + @Owning + Foo testSwitchReturnBad(int i) { + // :: error: required.method.not.called + Foo x = new Foo(); + return switch (i) { + case 3 -> x; + default -> makeFoo(); + }; + } + + @InheritableMustCall("toString") + static class Sub1 extends Object {} + + @InheritableMustCall("clone") + static class Sub2 extends Object {} + + static void testSwitchSubtyping(int i) { + // :: error: required.method.not.called + Object toStringAndClone = + switch (i) { + case 3 -> new Sub1(); + default -> new Sub2(); + }; + // at this point, for soundness, we should be responsible for calling both toString and + // clone on + // obj... + toStringAndClone.toString(); + } } diff --git a/checker/tests/signature/ArraysAsList.java b/checker/tests/signature/ArraysAsList.java index cd20e5d5cfa..b8ae2754534 100644 --- a/checker/tests/signature/ArraysAsList.java +++ b/checker/tests/signature/ArraysAsList.java @@ -1,11 +1,10 @@ -import org.checkerframework.checker.signature.qual.*; - import java.util.Arrays; import java.util.List; +import org.checkerframework.checker.signature.qual.*; public class ArraysAsList { - List m() { - return Arrays.asList("id", "department_id", "permission_id", "expected_connection_time"); - } + List m() { + return Arrays.asList("id", "department_id", "permission_id", "expected_connection_time"); + } } diff --git a/checker/tests/signature/CanonicalNameNonEmptyTest.java b/checker/tests/signature/CanonicalNameNonEmptyTest.java index fd7a3eac882..bd2a9ea2dd4 100644 --- a/checker/tests/signature/CanonicalNameNonEmptyTest.java +++ b/checker/tests/signature/CanonicalNameNonEmptyTest.java @@ -2,28 +2,28 @@ public class CanonicalNameNonEmptyTest { - @CanonicalName String nonEmpty1(@CanonicalNameOrEmpty String s) { - if (s.isEmpty()) { - return null; - } else { - return s; - } + @CanonicalName String nonEmpty1(@CanonicalNameOrEmpty String s) { + if (s.isEmpty()) { + return null; + } else { + return s; } + } - @CanonicalName String nonEmpty2(@CanonicalNameOrEmpty String s) { - if (!s.isEmpty()) { - return s; - } else { - return null; - } + @CanonicalName String nonEmpty2(@CanonicalNameOrEmpty String s) { + if (!s.isEmpty()) { + return s; + } else { + return null; } + } - @CanonicalName String nonEmpty3(@FullyQualifiedName String s) { - if (s.isEmpty()) { - return null; - } else { - // :: error: (return.type.incompatible) - return s; - } + @CanonicalName String nonEmpty3(@FullyQualifiedName String s) { + if (s.isEmpty()) { + return null; + } else { + // :: error: (return.type.incompatible) + return s; } + } } diff --git a/checker/tests/signature/ClassGetNameBinaryName.java b/checker/tests/signature/ClassGetNameBinaryName.java index 544031cef8f..ccb38588842 100644 --- a/checker/tests/signature/ClassGetNameBinaryName.java +++ b/checker/tests/signature/ClassGetNameBinaryName.java @@ -4,117 +4,117 @@ public class ClassGetNameBinaryName { - static class Nested {} + static class Nested {} - class Inner {} + class Inner {} - class TestGetName { + class TestGetName { - @DotSeparatedIdentifiers String s1 = ClassGetNameBinaryName.class.getName(); + @DotSeparatedIdentifiers String s1 = ClassGetNameBinaryName.class.getName(); - @DotSeparatedIdentifiers String s2a = Integer.class.getName(); + @DotSeparatedIdentifiers String s2a = Integer.class.getName(); - @DotSeparatedIdentifiers String s2b = java.lang.Integer.class.getName(); + @DotSeparatedIdentifiers String s2b = java.lang.Integer.class.getName(); - @DotSeparatedIdentifiers String s4a = Boolean.class.getName(); + @DotSeparatedIdentifiers String s4a = Boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String s4b = Boolean.class.getName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String s4b = Boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s12 = Nested.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s12 = Nested.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s13 = Inner.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s13 = Inner.class.getName(); - /// Primitive types + /// Primitive types - @PrimitiveType String prim1 = int.class.getName(); + @PrimitiveType String prim1 = int.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String prim2 = int.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String prim2 = int.class.getName(); - @PrimitiveType String prim3 = boolean.class.getName(); + @PrimitiveType String prim3 = boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String prim4 = boolean.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String prim4 = boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String prim5 = void.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String prim5 = void.class.getName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String prim6 = void.class.getName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String prim6 = void.class.getName(); - /// Arrays + /// Arrays - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s6 = int[].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s6 = int[].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s7 = int[][].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s7 = int[][].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s8 = boolean[].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s8 = boolean[].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s9 = Integer[].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s9 = Integer[].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s10 = Boolean[].class.getName(); - } + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s10 = Boolean[].class.getName(); + } - class TestGetCanonicalName { + class TestGetCanonicalName { - @CanonicalNameAndBinaryName String s1 = ClassGetNameBinaryName.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s1 = ClassGetNameBinaryName.class.getCanonicalName(); - @CanonicalNameAndBinaryName String s2a = Integer.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s2a = Integer.class.getCanonicalName(); - @CanonicalNameAndBinaryName String s2b = java.lang.Integer.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s2b = java.lang.Integer.class.getCanonicalName(); - @CanonicalNameAndBinaryName String s4a = Boolean.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s4a = Boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String s4b = Boolean.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String s4b = Boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s12 = Nested.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s12 = Nested.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s13 = Inner.class.getName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s13 = Inner.class.getName(); - /// Primitive types + /// Primitive types - @PrimitiveType String prim1 = int.class.getCanonicalName(); + @PrimitiveType String prim1 = int.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String prim2 = int.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String prim2 = int.class.getCanonicalName(); - @PrimitiveType String prim3 = boolean.class.getCanonicalName(); + @PrimitiveType String prim3 = boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String prim4 = boolean.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String prim4 = boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String prim5 = void.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String prim5 = void.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String prim6 = void.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String prim6 = void.class.getCanonicalName(); - /// Arrays + /// Arrays - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s6 = int[].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s6 = int[].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s7 = int[][].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s7 = int[][].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s8 = boolean[].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s8 = boolean[].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s9 = Integer[].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s9 = Integer[].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s10 = Boolean[].class.getCanonicalName(); - } + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s10 = Boolean[].class.getCanonicalName(); + } } diff --git a/checker/tests/signature/Conversion.java b/checker/tests/signature/Conversion.java index e3be2665134..57d3e1db7fb 100644 --- a/checker/tests/signature/Conversion.java +++ b/checker/tests/signature/Conversion.java @@ -2,103 +2,103 @@ public class Conversion { - class CharChar { - @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { - return bn.replace('.', '/'); - } - - @BinaryName String internalFormToBinaryName(@InternalForm String iform) { - return iform.replace('/', '.'); - } - - @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace('/', '.'); - } - - @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace(':', '/'); - } - - @InternalForm String binaryNameToInternalFormWRONG3(String bn) { - // :: error: (return.type.incompatible) - return bn.replace('.', '/'); - } - - @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace('.', '/'); - } - - @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace('/', ':'); - } - - @BinaryName String internalFormToBinaryNameWRONG3(String iform) { - // :: error: (return.type.incompatible) - return iform.replace('/', '.'); - } - - @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace('$', '.'); - } - - @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace('$', '.'); - } - } - - class CharSequenceCharSequence { - @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { - return bn.replace(".", "/"); - } - - @BinaryName String internalFormToBinaryName(@InternalForm String iform) { - return iform.replace("/", "."); - } - - @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace("/", "."); - } - - @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace(":", "/"); - } - - @InternalForm String binaryNameToInternalFormWRONG3(String bn) { - // :: error: (return.type.incompatible) - return bn.replace(".", "/"); - } - - @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace(".", "/"); - } - - @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace("/", ":"); - } - - @BinaryName String internalFormToBinaryNameWRONG3(String iform) { - // :: error: (return.type.incompatible) - return iform.replace("/", "."); - } - - @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace("$", "."); - } - - @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace("$", "."); - } + class CharChar { + @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { + return bn.replace('.', '/'); } + + @BinaryName String internalFormToBinaryName(@InternalForm String iform) { + return iform.replace('/', '.'); + } + + @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace('/', '.'); + } + + @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace(':', '/'); + } + + @InternalForm String binaryNameToInternalFormWRONG3(String bn) { + // :: error: (return.type.incompatible) + return bn.replace('.', '/'); + } + + @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace('.', '/'); + } + + @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace('/', ':'); + } + + @BinaryName String internalFormToBinaryNameWRONG3(String iform) { + // :: error: (return.type.incompatible) + return iform.replace('/', '.'); + } + + @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace('$', '.'); + } + + @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace('$', '.'); + } + } + + class CharSequenceCharSequence { + @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { + return bn.replace(".", "/"); + } + + @BinaryName String internalFormToBinaryName(@InternalForm String iform) { + return iform.replace("/", "."); + } + + @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace("/", "."); + } + + @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace(":", "/"); + } + + @InternalForm String binaryNameToInternalFormWRONG3(String bn) { + // :: error: (return.type.incompatible) + return bn.replace(".", "/"); + } + + @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace(".", "/"); + } + + @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace("/", ":"); + } + + @BinaryName String internalFormToBinaryNameWRONG3(String iform) { + // :: error: (return.type.incompatible) + return iform.replace("/", "."); + } + + @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace("$", "."); + } + + @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace("$", "."); + } + } } diff --git a/checker/tests/signature/DiamondTest.java b/checker/tests/signature/DiamondTest.java index 04402b728fd..e23d7900d75 100644 --- a/checker/tests/signature/DiamondTest.java +++ b/checker/tests/signature/DiamondTest.java @@ -1,10 +1,9 @@ -import org.checkerframework.checker.signature.qual.*; - import java.util.ArrayList; +import org.checkerframework.checker.signature.qual.*; public class DiamondTest { - void m() { - ArrayList list = new ArrayList<>(); - } + void m() { + ArrayList list = new ArrayList<>(); + } } diff --git a/checker/tests/signature/FakeOverridePoly.java b/checker/tests/signature/FakeOverridePoly.java index b46a947e734..a4e6978bf5e 100644 --- a/checker/tests/signature/FakeOverridePoly.java +++ b/checker/tests/signature/FakeOverridePoly.java @@ -1,12 +1,11 @@ // @skip-test until fake overrides affect formal parameter types as well as return types -import org.checkerframework.checker.signature.qual.CanonicalName; - import javax.lang.model.element.Name; +import org.checkerframework.checker.signature.qual.CanonicalName; public class FakeOverridePoly { - void m(@CanonicalName Name n) { - @CanonicalName String s = n.toString(); - } + void m(@CanonicalName Name n) { + @CanonicalName String s = n.toString(); + } } diff --git a/checker/tests/signature/PolySignatureTest.java b/checker/tests/signature/PolySignatureTest.java index 356118c0fc8..de87622966c 100644 --- a/checker/tests/signature/PolySignatureTest.java +++ b/checker/tests/signature/PolySignatureTest.java @@ -2,12 +2,12 @@ public class PolySignatureTest { - @PolySignature String polyMethod(@PolySignature String arg) { - return arg; - } + @PolySignature String polyMethod(@PolySignature String arg) { + return arg; + } - void m(@ClassGetName String s) { - @ClassGetName String s1 = polyMethod(s); - @ClassGetName String s2 = s.intern(); - } + void m(@ClassGetName String s) { + @ClassGetName String s1 = polyMethod(s); + @ClassGetName String s2 = s.intern(); + } } diff --git a/checker/tests/signature/PolySignatureTest2.java b/checker/tests/signature/PolySignatureTest2.java index 2dc859add40..56507d195d6 100644 --- a/checker/tests/signature/PolySignatureTest2.java +++ b/checker/tests/signature/PolySignatureTest2.java @@ -1,19 +1,18 @@ // Test for stub files and https://tinyurl.com/cfissue/658 . // Commented in part because that issue is not yet fixed. -import org.checkerframework.checker.signature.qual.*; - import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; +import org.checkerframework.checker.signature.qual.*; public class PolySignatureTest2 { - @CanonicalNameOrEmpty Name m1(TypeElement e) { - return e.getQualifiedName(); - } + @CanonicalNameOrEmpty Name m1(TypeElement e) { + return e.getQualifiedName(); + } - @DotSeparatedIdentifiers String m2(@DotSeparatedIdentifiers Name n) { - // :: error: (return.type.incompatible) - return n.toString(); - } + @DotSeparatedIdentifiers String m2(@DotSeparatedIdentifiers Name n) { + // :: error: (return.type.incompatible) + return n.toString(); + } } diff --git a/checker/tests/signature/RefinedReturnTest.java b/checker/tests/signature/RefinedReturnTest.java index 1c59ac15a6d..1628b070c78 100644 --- a/checker/tests/signature/RefinedReturnTest.java +++ b/checker/tests/signature/RefinedReturnTest.java @@ -5,20 +5,20 @@ public class RefinedReturnTest { - public class Super { - public @FullyQualifiedName String aString() { - return "java.lang.Integer[][]"; - } + public class Super { + public @FullyQualifiedName String aString() { + return "java.lang.Integer[][]"; } + } - public class Sub extends Super { - @Override - public @ArrayWithoutPackage String aString() { - return "Integer[]"; - } + public class Sub extends Super { + @Override + public @ArrayWithoutPackage String aString() { + return "Integer[]"; } + } - void m() { - @ArrayWithoutPackage String s = new Sub().aString(); - } + void m() { + @ArrayWithoutPackage String s = new Sub().aString(); + } } diff --git a/checker/tests/signature/SignatureConcatenation.java b/checker/tests/signature/SignatureConcatenation.java index e55b4da9023..901c7adb7d2 100644 --- a/checker/tests/signature/SignatureConcatenation.java +++ b/checker/tests/signature/SignatureConcatenation.java @@ -2,8 +2,8 @@ public class SignatureConcatenation { - @ClassGetSimpleName String m(@ClassGetSimpleName String arg1, @ClassGetSimpleName String arg2) { - // :: error: (return.type.incompatible) - return arg1 + arg2; - } + @ClassGetSimpleName String m(@ClassGetSimpleName String arg1, @ClassGetSimpleName String arg2) { + // :: error: (return.type.incompatible) + return arg1 + arg2; + } } diff --git a/checker/tests/signature/SignatureLiteralTest.java b/checker/tests/signature/SignatureLiteralTest.java index 66cf1d73ad8..11739b5f5b6 100644 --- a/checker/tests/signature/SignatureLiteralTest.java +++ b/checker/tests/signature/SignatureLiteralTest.java @@ -2,6 +2,6 @@ public class SignatureLiteralTest { - protected static final @FullyQualifiedName String FORMAT_NAME = - "org.checkerframework.checker.formatter.qual.Format"; + protected static final @FullyQualifiedName String FORMAT_NAME = + "org.checkerframework.checker.formatter.qual.Format"; } diff --git a/checker/tests/signature/SignatureTypeFactoryTest.java b/checker/tests/signature/SignatureTypeFactoryTest.java index 017fa38a4de..2a62fda0921 100644 --- a/checker/tests/signature/SignatureTypeFactoryTest.java +++ b/checker/tests/signature/SignatureTypeFactoryTest.java @@ -2,879 +2,879 @@ public class SignatureTypeFactoryTest { - // The hierarchy of type representations contains: - // - // SignatureUnknown.class, - // - // FullyQualifiedName.class, - // ClassGetName.class, - // FieldDescriptor.class, - // InternalForm.class, - // ClassGetSimpleName.class, - // FqBinaryName.class, - // - // BinaryName.class, - // FieldDescriptorWithoutPackage.class, - // - // ArrayWithoutPackage.class, - // DotSeparatedIdentifiers.class, - // BinaryNameWithoutPackage.class, - // - // Identifier.class, - // - // FieldDescriptorForPrimitive.class - // - // SignatureBottom.class - // - // There are also signature representations, which are not handled yet. - - void m() { - - String s1 = "a"; - String s2 = "a.b"; - String s3 = "a.b$c"; - String s4 = "B"; - String s5 = "[B"; - String s6 = "Ljava/lang/String;"; - String s7 = "Ljava/lang/String"; - // TODO: Should be @MethodDescriptor - String s8 = "foo()V"; - String s9 = "java.lang.annotation.Retention"; - String s10 = "dummy"; - String s11 = null; - String s12 = "a.b$c[][]"; - String s13 = "a.b.c[][]"; - String s14 = "[[Ljava/lang/String;"; - String s15 = ""; - String s16 = "[]"; - String s17 = "[][]"; - String s18 = "null"; - String s19 = "abstract"; - String s20 = "float"; - String s21 = "float "; - String s22 = " Foo"; - - // All the examples from the manual - String t13 = "int"; - String t14 = "int[][]"; - String t1 = "I"; - String t12 = "[[I"; - - String t5 = "MyClass"; - String t2 = "LMyClass;"; - String t6 = "MyClass[]"; - String t7 = "[LMyClass;"; - - String t29 = ""; - String t33 = "[]"; - - String t15 = "java.lang.Integer"; - String t16 = "java.lang.Integer[]"; - String t22 = "java/lang/Integer"; - String t23 = "java/lang/Integer[]"; - String t3 = "Ljava/lang/Integer;"; - String t8 = "[Ljava.lang.Integer;"; - String t9 = "[Ljava/lang/Integer;"; - - String t24 = "pakkage/Outer$Inner"; - String t25 = "pakkage/Outer$Inner[]"; - - String t28 = "pakkage/Outer$22"; - String t27 = "Lpakkage/Outer$22;"; - String t26 = "pakkage.Outer$22"; - String t32 = "pakkage/Outer$22[]"; - String t30 = "pakkage.Outer$22[]"; - String t31 = "[Lpakkage.Outer$22;"; - - String t34 = "org.plumelib.reflection.TestReflectionPlume$Inner.InnerInner"; - String t17 = "pakkage.Outer.Inner"; - String t18 = "pakkage.Outer.Inner[]"; - String t19 = "pakkage.Outer$Inner"; - String t21 = "pakkage.Outer$Inner[]"; - String t20 = "Lpakkage.Outer$Inner;"; - String t10 = "[Lpakkage.Outer$Inner;"; - String t4 = "Lpakkage/Outer$Inner;"; - String t11 = "[Lpakkage/Outer$Inner;"; - - String us; // @SignatureUnknown - @FullyQualifiedName String fqn; - @ClassGetName String cgn; - @FieldDescriptor String fd; - @InternalForm String iform; - @ClassGetSimpleName String sn; - @FqBinaryName String fbn; - @BinaryName String bn; - // not public, so a user can't write it. - // @SignatureBottom String sb; - - us = s1; - fqn = s1; - cgn = s1; - // :: error: (assignment.type.incompatible) - fd = s1; - iform = s1; - sn = s1; - bn = s1; - fbn = s1; - - us = s2; - fqn = s2; - cgn = s2; - // :: error: (assignment.type.incompatible) - fd = s2; - // :: error: (assignment.type.incompatible) - iform = s2; - // :: error: (assignment.type.incompatible) - sn = s2; - bn = s2; - fbn = s2; - - us = s3; - fqn = s3; - cgn = s3; - // :: error: (assignment.type.incompatible) - fd = s3; - // :: error: (assignment.type.incompatible) - iform = s3; - // :: error: (assignment.type.incompatible) - sn = s3; - bn = s3; - fbn = s3; - - us = s4; - fqn = s4; - cgn = s4; - fd = s4; - iform = s4; - sn = s4; - bn = s4; - fbn = s4; - - us = s5; - // :: error: (assignment.type.incompatible) - fqn = s5; - cgn = s5; - fd = s5; - // :: error: (assignment.type.incompatible) - iform = s5; - // :: error: (assignment.type.incompatible) - sn = s5; - // :: error: (assignment.type.incompatible) - bn = s5; - // :: error: (assignment.type.incompatible) - fbn = s5; - - us = s6; - // :: error: (assignment.type.incompatible) - fqn = s6; - // :: error: (assignment.type.incompatible) - cgn = s6; - fd = s6; - // :: error: (assignment.type.incompatible) - iform = s6; - // :: error: (assignment.type.incompatible) - sn = s6; - // :: error: (assignment.type.incompatible) - bn = s6; - // :: error: (assignment.type.incompatible) - fbn = s6; - - us = s7; - // :: error: (assignment.type.incompatible) - fqn = s7; - // :: error: (assignment.type.incompatible) - cgn = s7; - // :: error: (assignment.type.incompatible) - fd = s7; - iform = s7; - // :: error: (assignment.type.incompatible) - sn = s7; - // :: error: (assignment.type.incompatible) - bn = s7; - // :: error: (assignment.type.incompatible) - fbn = s7; - - us = s8; - // :: error: (assignment.type.incompatible) - fqn = s8; - // :: error: (assignment.type.incompatible) - cgn = s8; - // :: error: (assignment.type.incompatible) - fd = s8; - // :: error: (assignment.type.incompatible) - iform = s8; - // :: error: (assignment.type.incompatible) - sn = s8; - // :: error: (assignment.type.incompatible) - bn = s8; - // :: error: (assignment.type.incompatible) - fbn = s8; - - us = s9; - fqn = s9; - cgn = s9; - // :: error: (assignment.type.incompatible) - fd = s9; - // :: error: (assignment.type.incompatible) - iform = s9; - // :: error: (assignment.type.incompatible) - sn = s9; - bn = s9; - fbn = s9; - - us = s10; - fqn = s10; - cgn = s10; - // :: error: (assignment.type.incompatible) - fd = s10; - iform = s10; - sn = s10; - bn = s10; - fbn = s10; - - us = s11; - fqn = s11; - cgn = s11; - fd = s11; - iform = s11; - sn = s11; - bn = s11; - fbn = s11; - - us = s12; - fqn = s12; - // :: error: (assignment.type.incompatible) - cgn = s12; - // :: error: (assignment.type.incompatible) - fd = s12; - // :: error: (assignment.type.incompatible) - iform = s12; - // :: error: (assignment.type.incompatible) - sn = s12; - // :: error: (assignment.type.incompatible) - bn = s12; - fbn = s12; - - us = s13; - fqn = s13; - // :: error: (assignment.type.incompatible) - cgn = s13; - // :: error: (assignment.type.incompatible) - fd = s13; - // :: error: (assignment.type.incompatible) - iform = s13; - // :: error: (assignment.type.incompatible) - sn = s13; - // :: error: (assignment.type.incompatible) - bn = s13; - fbn = s13; - - us = s14; - // :: error: (assignment.type.incompatible) - fqn = s14; - // :: error: (assignment.type.incompatible) - cgn = s14; - fd = s14; - // :: error: (assignment.type.incompatible) - iform = s14; - // :: error: (assignment.type.incompatible) - sn = s14; - // :: error: (assignment.type.incompatible) - bn = s14; - // :: error: (assignment.type.incompatible) - fbn = s14; - - us = s15; - // :: error: (assignment.type.incompatible) - fqn = s15; - // :: error: (assignment.type.incompatible) - cgn = s15; - // :: error: (assignment.type.incompatible) - fd = s15; - // :: error: (assignment.type.incompatible) - iform = s15; - sn = s15; - // :: error: (assignment.type.incompatible) - bn = s15; - // :: error: (assignment.type.incompatible) - fbn = s15; - - us = s16; - // :: error: (assignment.type.incompatible) - fqn = s16; - // :: error: (assignment.type.incompatible) - cgn = s16; - // :: error: (assignment.type.incompatible) - fd = s16; - // :: error: (assignment.type.incompatible) - iform = s16; - sn = s16; - // :: error: (assignment.type.incompatible) - bn = s16; - // :: error: (assignment.type.incompatible) - fbn = s16; - - us = s17; - // :: error: (assignment.type.incompatible) - fqn = s17; - // :: error: (assignment.type.incompatible) - cgn = s17; - // :: error: (assignment.type.incompatible) - fd = s17; - // :: error: (assignment.type.incompatible) - iform = s17; - sn = s17; - // :: error: (assignment.type.incompatible) - bn = s17; - // :: error: (assignment.type.incompatible) - fbn = s17; - - us = s18; - // :: error: (assignment.type.incompatible) - fqn = s18; - // :: error: (assignment.type.incompatible) - cgn = s18; - // :: error: (assignment.type.incompatible) - fd = s18; - // :: error: (assignment.type.incompatible) - iform = s18; - // :: error: (assignment.type.incompatible) - sn = s18; - // :: error: (assignment.type.incompatible) - bn = s18; - // :: error: (assignment.type.incompatible) - fbn = s18; - - us = s19; - // :: error: (assignment.type.incompatible) - fqn = s19; - // :: error: (assignment.type.incompatible) - cgn = s19; - // :: error: (assignment.type.incompatible) - fd = s19; - // :: error: (assignment.type.incompatible) - iform = s19; - // :: error: (assignment.type.incompatible) - sn = s19; - // :: error: (assignment.type.incompatible) - bn = s19; - // :: error: (assignment.type.incompatible) - fbn = s19; - - us = s20; - fqn = s20; - cgn = s20; - // :: error: (assignment.type.incompatible) - fd = s20; - // :: error: (assignment.type.incompatible) - iform = s20; - sn = s20; - // :: error: (assignment.type.incompatible) - bn = s20; - fbn = s20; - - us = s21; - // :: error: (assignment.type.incompatible) - fqn = s21; - // :: error: (assignment.type.incompatible) - cgn = s21; - // :: error: (assignment.type.incompatible) - fd = s21; - // :: error: (assignment.type.incompatible) - iform = s21; - // :: error: (assignment.type.incompatible) - sn = s21; - // :: error: (assignment.type.incompatible) - bn = s21; - // :: error: (assignment.type.incompatible) - fbn = s21; - - us = s22; - // :: error: (assignment.type.incompatible) - fqn = s22; - // :: error: (assignment.type.incompatible) - cgn = s22; - // :: error: (assignment.type.incompatible) - fd = s22; - // :: error: (assignment.type.incompatible) - iform = s22; - // :: error: (assignment.type.incompatible) - sn = s22; - // :: error: (assignment.type.incompatible) - bn = s22; - // :: error: (assignment.type.incompatible) - fbn = s22; - - // Examples from the manual start here - - us = t13; - fqn = t13; - cgn = t13; - // :: error: (assignment.type.incompatible) - fd = t13; - // :: error: (assignment.type.incompatible) - iform = t13; - sn = t13; - // :: error: (assignment.type.incompatible) - bn = t13; - fbn = t13; - - us = t14; - fqn = t14; - // :: error: (assignment.type.incompatible) - cgn = t14; - // :: error: (assignment.type.incompatible) - fd = t14; - // :: error: (assignment.type.incompatible) - iform = t14; - sn = t14; - // :: error: (assignment.type.incompatible) - bn = t14; // t14 is int[][] - - us = t1; - fqn = t1; - cgn = t1; - fd = t1; - iform = t1; - sn = t1; - bn = t1; - fbn = t1; - - us = t12; - // :: error: (assignment.type.incompatible) - fqn = t12; - cgn = t12; - fd = t12; - // :: error: (assignment.type.incompatible) - iform = t12; - // :: error: (assignment.type.incompatible) - sn = t12; - // :: error: (assignment.type.incompatible) - bn = t12; - // :: error: (assignment.type.incompatible) - fbn = t12; - - us = t5; - fqn = t5; - cgn = t5; - // :: error: (assignment.type.incompatible) - fd = t5; - iform = t5; - sn = t5; - bn = t5; - fbn = t5; - - us = t2; - // :: error: (assignment.type.incompatible) - fqn = t2; - // :: error: (assignment.type.incompatible) - cgn = t2; - fd = t2; - // :: error: (assignment.type.incompatible) - iform = t2; - // :: error: (assignment.type.incompatible) - sn = t2; - // :: error: (assignment.type.incompatible) - bn = t2; - // :: error: (assignment.type.incompatible) - fbn = t2; - - us = t6; - fqn = t6; - // :: error: (assignment.type.incompatible) - cgn = t6; - // :: error: (assignment.type.incompatible) - fd = t6; - // :: error: (assignment.type.incompatible) - iform = t6; - sn = t6; - // :: error: (assignment.type.incompatible) - bn = t6; - fbn = t6; - - us = t7; - // :: error: (assignment.type.incompatible) - fqn = t7; - cgn = t7; - fd = t7; - // :: error: (assignment.type.incompatible) - iform = t7; - // :: error: (assignment.type.incompatible) - sn = t7; - // :: error: (assignment.type.incompatible) - bn = t7; - // :: error: (assignment.type.incompatible) - fbn = t7; - - us = t29; - // :: error: (assignment.type.incompatible) - fqn = t29; - // :: error: (assignment.type.incompatible) - cgn = t29; - // :: error: (assignment.type.incompatible) - fd = t29; - // :: error: (assignment.type.incompatible) - iform = t29; - sn = t29; - // :: error: (assignment.type.incompatible) - bn = t29; - // :: error: (assignment.type.incompatible) - fbn = t29; - - us = t33; - // :: error: (assignment.type.incompatible) - fqn = t33; - // :: error: (assignment.type.incompatible) - cgn = t33; - // :: error: (assignment.type.incompatible) - fd = t33; - // :: error: (assignment.type.incompatible) - iform = t33; - sn = t33; - // :: error: (assignment.type.incompatible) - bn = t33; - // :: error: (assignment.type.incompatible) - fbn = t33; - - us = t15; - fqn = t15; - cgn = t15; - // :: error: (assignment.type.incompatible) - fd = t15; - // :: error: (assignment.type.incompatible) - iform = t15; - // :: error: (assignment.type.incompatible) - sn = t15; - bn = t15; - fbn = t15; - - us = t16; - fqn = t16; - // :: error: (assignment.type.incompatible) - cgn = t16; - // :: error: (assignment.type.incompatible) - fd = t16; - // :: error: (assignment.type.incompatible) - iform = t16; - // :: error: (assignment.type.incompatible) - sn = t16; - // :: error: (assignment.type.incompatible) - bn = t16; // t16 is java.lang.Integer[] - - us = t22; - // :: error: (assignment.type.incompatible) - fqn = t22; - // :: error: (assignment.type.incompatible) - cgn = t22; - // :: error: (assignment.type.incompatible) - fd = t22; - iform = t22; - // :: error: (assignment.type.incompatible) - sn = t22; - // :: error: (assignment.type.incompatible) - bn = t22; - // :: error: (assignment.type.incompatible) - fbn = t22; - - us = t23; - // :: error: (assignment.type.incompatible) - fqn = t23; - // :: error: (assignment.type.incompatible) - cgn = t23; - // :: error: (assignment.type.incompatible) - fd = t23; - // :: error: (assignment.type.incompatible) - iform = t23; // t23 is java/lang/Integer[] - // :: error: (assignment.type.incompatible) - sn = t23; - // :: error: (assignment.type.incompatible) - bn = t23; - // :: error: (assignment.type.incompatible) - fbn = t23; - - us = t3; - // :: error: (assignment.type.incompatible) - fqn = t3; - // :: error: (assignment.type.incompatible) - cgn = t3; - fd = t3; - // :: error: (assignment.type.incompatible) - iform = t3; - // :: error: (assignment.type.incompatible) - sn = t3; - // :: error: (assignment.type.incompatible) - bn = t3; - // :: error: (assignment.type.incompatible) - fbn = t3; - - us = t8; - // :: error: (assignment.type.incompatible) - fqn = t8; - cgn = t8; - // :: error: (assignment.type.incompatible) - fd = t8; - // :: error: (assignment.type.incompatible) - iform = t8; - // :: error: (assignment.type.incompatible) - sn = t8; - // :: error: (assignment.type.incompatible) - bn = t8; - // :: error: (assignment.type.incompatible) - fbn = t8; - - us = t9; - // :: error: (assignment.type.incompatible) - fqn = t9; - // :: error: (assignment.type.incompatible) - cgn = t9; - fd = t9; - // :: error: (assignment.type.incompatible) - iform = t9; - // :: error: (assignment.type.incompatible) - sn = t9; - // :: error: (assignment.type.incompatible) - bn = t9; - // :: error: (assignment.type.incompatible) - fbn = t9; - - us = t24; - // :: error: (assignment.type.incompatible) - fqn = t24; - // :: error: (assignment.type.incompatible) - cgn = t24; - // :: error: (assignment.type.incompatible) - fd = t24; - iform = t24; - // :: error: (assignment.type.incompatible) - sn = t24; - // :: error: (assignment.type.incompatible) - bn = t24; - // :: error: (assignment.type.incompatible) - fbn = t24; - - us = t25; - // :: error: (assignment.type.incompatible) - fqn = t25; - // :: error: (assignment.type.incompatible) - cgn = t25; - // :: error: (assignment.type.incompatible) - fd = t25; - // :: error: (assignment.type.incompatible) - iform = t25; // rhs is pakkage/Outer$Inner[] - // :: error: (assignment.type.incompatible) - sn = t25; - // :: error: (assignment.type.incompatible) - bn = t25; - // :: error: (assignment.type.incompatible) - fbn = t25; - - us = t28; - // :: error: (assignment.type.incompatible) - fqn = t28; - // :: error: (assignment.type.incompatible) - cgn = t28; - // :: error: (assignment.type.incompatible) - fd = t28; - iform = t28; - // :: error: (assignment.type.incompatible) - sn = t28; - // :: error: (assignment.type.incompatible) - bn = t28; - // :: error: (assignment.type.incompatible) - fbn = t28; - - us = t27; - // :: error: (assignment.type.incompatible) - fqn = t27; - // :: error: (assignment.type.incompatible) - cgn = t27; - fd = t27; - // :: error: (assignment.type.incompatible) - iform = t27; - // :: error: (assignment.type.incompatible) - sn = t27; - // :: error: (assignment.type.incompatible) - bn = t27; - // :: error: (assignment.type.incompatible) - fbn = t27; - - us = t26; - fqn = t26; - cgn = t26; - // :: error: (assignment.type.incompatible) - fd = t26; - // :: error: (assignment.type.incompatible) - iform = t26; - // :: error: (assignment.type.incompatible) - sn = t26; - bn = t26; - fbn = t26; - - us = t32; - // :: error: (assignment.type.incompatible) - fqn = t32; - // :: error: (assignment.type.incompatible) - cgn = t32; - // :: error: (assignment.type.incompatible) - fd = t32; - // :: error: (assignment.type.incompatible) - iform = t32; // t32 is array - // :: error: (assignment.type.incompatible) - sn = t32; - // :: error: (assignment.type.incompatible) - bn = t32; - // :: error: (assignment.type.incompatible) - fbn = t32; - - us = t30; - fqn = t30; - // :: error: (assignment.type.incompatible) - cgn = t30; - // :: error: (assignment.type.incompatible) - fd = t30; - // :: error: (assignment.type.incompatible) - iform = t30; - // :: error: (assignment.type.incompatible) - sn = t30; - // :: error: (assignment.type.incompatible) - bn = t30; // rhs is array - - us = t31; - // :: error: (assignment.type.incompatible) - fqn = t31; - cgn = t31; - // :: error: (assignment.type.incompatible) - fd = t31; - // :: error: (assignment.type.incompatible) - iform = t31; - // :: error: (assignment.type.incompatible) - sn = t31; - // :: error: (assignment.type.incompatible) - bn = t31; - // :: error: (assignment.type.incompatible) - fbn = t31; - - us = t34; - fqn = t34; - cgn = t34; - // :: error: (assignment.type.incompatible) - fd = t34; - // :: error: (assignment.type.incompatible) - iform = t34; - // :: error: (assignment.type.incompatible) - sn = t34; - bn = t34; - fbn = t34; - - us = t17; - fqn = t17; - cgn = t17; - // :: error: (assignment.type.incompatible) - fd = t17; - // :: error: (assignment.type.incompatible) - iform = t17; - // :: error: (assignment.type.incompatible) - sn = t17; - bn = t17; - fbn = t17; - - us = t18; - fqn = t18; - // :: error: (assignment.type.incompatible) - cgn = t18; - // :: error: (assignment.type.incompatible) - fd = t18; - // :: error: (assignment.type.incompatible) - iform = t18; - // :: error: (assignment.type.incompatible) - sn = t18; - // :: error: (assignment.type.incompatible) - bn = t18; // t18 is pakkage.Outer.Inner[] - - us = t19; - fqn = t19; - cgn = t19; - // :: error: (assignment.type.incompatible) - fd = t19; - // :: error: (assignment.type.incompatible) - iform = t19; - // :: error: (assignment.type.incompatible) - sn = t19; - bn = t19; - fbn = t19; - - us = t21; - fqn = t21; - // :: error: (assignment.type.incompatible) - cgn = t21; - // :: error: (assignment.type.incompatible) - fd = t21; - // :: error: (assignment.type.incompatible) - iform = t21; - // :: error: (assignment.type.incompatible) - sn = t21; - // :: error: (assignment.type.incompatible) - bn = t21; // t21 is pakkage.Outer$Inner[] - - us = t20; - // :: error: (assignment.type.incompatible) - fqn = t20; - // :: error: (assignment.type.incompatible) - cgn = t20; - // :: error: (assignment.type.incompatible) - fd = t20; - // :: error: (assignment.type.incompatible) - iform = t20; - // :: error: (assignment.type.incompatible) - sn = t20; - // :: error: (assignment.type.incompatible) - bn = t20; - // :: error: (assignment.type.incompatible) - fbn = t20; - - us = t10; - // :: error: (assignment.type.incompatible) - fqn = t10; - cgn = t10; - // :: error: (assignment.type.incompatible) - fd = t10; - // :: error: (assignment.type.incompatible) - iform = t10; - // :: error: (assignment.type.incompatible) - sn = t10; - // :: error: (assignment.type.incompatible) - bn = t10; - // :: error: (assignment.type.incompatible) - fbn = t10; - - us = t4; - // :: error: (assignment.type.incompatible) - fqn = t4; - // :: error: (assignment.type.incompatible) - cgn = t4; - fd = t4; - // :: error: (assignment.type.incompatible) - iform = t4; - // :: error: (assignment.type.incompatible) - sn = t4; - // :: error: (assignment.type.incompatible) - bn = t4; - // :: error: (assignment.type.incompatible) - fbn = t4; - - us = t11; - // :: error: (assignment.type.incompatible) - fqn = t11; - // :: error: (assignment.type.incompatible) - cgn = t11; - fd = t11; - // :: error: (assignment.type.incompatible) - iform = t11; - // :: error: (assignment.type.incompatible) - sn = t11; - // :: error: (assignment.type.incompatible) - bn = t11; - // :: error: (assignment.type.incompatible) - fbn = t11; - } + // The hierarchy of type representations contains: + // + // SignatureUnknown.class, + // + // FullyQualifiedName.class, + // ClassGetName.class, + // FieldDescriptor.class, + // InternalForm.class, + // ClassGetSimpleName.class, + // FqBinaryName.class, + // + // BinaryName.class, + // FieldDescriptorWithoutPackage.class, + // + // ArrayWithoutPackage.class, + // DotSeparatedIdentifiers.class, + // BinaryNameWithoutPackage.class, + // + // Identifier.class, + // + // FieldDescriptorForPrimitive.class + // + // SignatureBottom.class + // + // There are also signature representations, which are not handled yet. + + void m() { + + String s1 = "a"; + String s2 = "a.b"; + String s3 = "a.b$c"; + String s4 = "B"; + String s5 = "[B"; + String s6 = "Ljava/lang/String;"; + String s7 = "Ljava/lang/String"; + // TODO: Should be @MethodDescriptor + String s8 = "foo()V"; + String s9 = "java.lang.annotation.Retention"; + String s10 = "dummy"; + String s11 = null; + String s12 = "a.b$c[][]"; + String s13 = "a.b.c[][]"; + String s14 = "[[Ljava/lang/String;"; + String s15 = ""; + String s16 = "[]"; + String s17 = "[][]"; + String s18 = "null"; + String s19 = "abstract"; + String s20 = "float"; + String s21 = "float "; + String s22 = " Foo"; + + // All the examples from the manual + String t13 = "int"; + String t14 = "int[][]"; + String t1 = "I"; + String t12 = "[[I"; + + String t5 = "MyClass"; + String t2 = "LMyClass;"; + String t6 = "MyClass[]"; + String t7 = "[LMyClass;"; + + String t29 = ""; + String t33 = "[]"; + + String t15 = "java.lang.Integer"; + String t16 = "java.lang.Integer[]"; + String t22 = "java/lang/Integer"; + String t23 = "java/lang/Integer[]"; + String t3 = "Ljava/lang/Integer;"; + String t8 = "[Ljava.lang.Integer;"; + String t9 = "[Ljava/lang/Integer;"; + + String t24 = "pakkage/Outer$Inner"; + String t25 = "pakkage/Outer$Inner[]"; + + String t28 = "pakkage/Outer$22"; + String t27 = "Lpakkage/Outer$22;"; + String t26 = "pakkage.Outer$22"; + String t32 = "pakkage/Outer$22[]"; + String t30 = "pakkage.Outer$22[]"; + String t31 = "[Lpakkage.Outer$22;"; + + String t34 = "org.plumelib.reflection.TestReflectionPlume$Inner.InnerInner"; + String t17 = "pakkage.Outer.Inner"; + String t18 = "pakkage.Outer.Inner[]"; + String t19 = "pakkage.Outer$Inner"; + String t21 = "pakkage.Outer$Inner[]"; + String t20 = "Lpakkage.Outer$Inner;"; + String t10 = "[Lpakkage.Outer$Inner;"; + String t4 = "Lpakkage/Outer$Inner;"; + String t11 = "[Lpakkage/Outer$Inner;"; + + String us; // @SignatureUnknown + @FullyQualifiedName String fqn; + @ClassGetName String cgn; + @FieldDescriptor String fd; + @InternalForm String iform; + @ClassGetSimpleName String sn; + @FqBinaryName String fbn; + @BinaryName String bn; + // not public, so a user can't write it. + // @SignatureBottom String sb; + + us = s1; + fqn = s1; + cgn = s1; + // :: error: (assignment.type.incompatible) + fd = s1; + iform = s1; + sn = s1; + bn = s1; + fbn = s1; + + us = s2; + fqn = s2; + cgn = s2; + // :: error: (assignment.type.incompatible) + fd = s2; + // :: error: (assignment.type.incompatible) + iform = s2; + // :: error: (assignment.type.incompatible) + sn = s2; + bn = s2; + fbn = s2; + + us = s3; + fqn = s3; + cgn = s3; + // :: error: (assignment.type.incompatible) + fd = s3; + // :: error: (assignment.type.incompatible) + iform = s3; + // :: error: (assignment.type.incompatible) + sn = s3; + bn = s3; + fbn = s3; + + us = s4; + fqn = s4; + cgn = s4; + fd = s4; + iform = s4; + sn = s4; + bn = s4; + fbn = s4; + + us = s5; + // :: error: (assignment.type.incompatible) + fqn = s5; + cgn = s5; + fd = s5; + // :: error: (assignment.type.incompatible) + iform = s5; + // :: error: (assignment.type.incompatible) + sn = s5; + // :: error: (assignment.type.incompatible) + bn = s5; + // :: error: (assignment.type.incompatible) + fbn = s5; + + us = s6; + // :: error: (assignment.type.incompatible) + fqn = s6; + // :: error: (assignment.type.incompatible) + cgn = s6; + fd = s6; + // :: error: (assignment.type.incompatible) + iform = s6; + // :: error: (assignment.type.incompatible) + sn = s6; + // :: error: (assignment.type.incompatible) + bn = s6; + // :: error: (assignment.type.incompatible) + fbn = s6; + + us = s7; + // :: error: (assignment.type.incompatible) + fqn = s7; + // :: error: (assignment.type.incompatible) + cgn = s7; + // :: error: (assignment.type.incompatible) + fd = s7; + iform = s7; + // :: error: (assignment.type.incompatible) + sn = s7; + // :: error: (assignment.type.incompatible) + bn = s7; + // :: error: (assignment.type.incompatible) + fbn = s7; + + us = s8; + // :: error: (assignment.type.incompatible) + fqn = s8; + // :: error: (assignment.type.incompatible) + cgn = s8; + // :: error: (assignment.type.incompatible) + fd = s8; + // :: error: (assignment.type.incompatible) + iform = s8; + // :: error: (assignment.type.incompatible) + sn = s8; + // :: error: (assignment.type.incompatible) + bn = s8; + // :: error: (assignment.type.incompatible) + fbn = s8; + + us = s9; + fqn = s9; + cgn = s9; + // :: error: (assignment.type.incompatible) + fd = s9; + // :: error: (assignment.type.incompatible) + iform = s9; + // :: error: (assignment.type.incompatible) + sn = s9; + bn = s9; + fbn = s9; + + us = s10; + fqn = s10; + cgn = s10; + // :: error: (assignment.type.incompatible) + fd = s10; + iform = s10; + sn = s10; + bn = s10; + fbn = s10; + + us = s11; + fqn = s11; + cgn = s11; + fd = s11; + iform = s11; + sn = s11; + bn = s11; + fbn = s11; + + us = s12; + fqn = s12; + // :: error: (assignment.type.incompatible) + cgn = s12; + // :: error: (assignment.type.incompatible) + fd = s12; + // :: error: (assignment.type.incompatible) + iform = s12; + // :: error: (assignment.type.incompatible) + sn = s12; + // :: error: (assignment.type.incompatible) + bn = s12; + fbn = s12; + + us = s13; + fqn = s13; + // :: error: (assignment.type.incompatible) + cgn = s13; + // :: error: (assignment.type.incompatible) + fd = s13; + // :: error: (assignment.type.incompatible) + iform = s13; + // :: error: (assignment.type.incompatible) + sn = s13; + // :: error: (assignment.type.incompatible) + bn = s13; + fbn = s13; + + us = s14; + // :: error: (assignment.type.incompatible) + fqn = s14; + // :: error: (assignment.type.incompatible) + cgn = s14; + fd = s14; + // :: error: (assignment.type.incompatible) + iform = s14; + // :: error: (assignment.type.incompatible) + sn = s14; + // :: error: (assignment.type.incompatible) + bn = s14; + // :: error: (assignment.type.incompatible) + fbn = s14; + + us = s15; + // :: error: (assignment.type.incompatible) + fqn = s15; + // :: error: (assignment.type.incompatible) + cgn = s15; + // :: error: (assignment.type.incompatible) + fd = s15; + // :: error: (assignment.type.incompatible) + iform = s15; + sn = s15; + // :: error: (assignment.type.incompatible) + bn = s15; + // :: error: (assignment.type.incompatible) + fbn = s15; + + us = s16; + // :: error: (assignment.type.incompatible) + fqn = s16; + // :: error: (assignment.type.incompatible) + cgn = s16; + // :: error: (assignment.type.incompatible) + fd = s16; + // :: error: (assignment.type.incompatible) + iform = s16; + sn = s16; + // :: error: (assignment.type.incompatible) + bn = s16; + // :: error: (assignment.type.incompatible) + fbn = s16; + + us = s17; + // :: error: (assignment.type.incompatible) + fqn = s17; + // :: error: (assignment.type.incompatible) + cgn = s17; + // :: error: (assignment.type.incompatible) + fd = s17; + // :: error: (assignment.type.incompatible) + iform = s17; + sn = s17; + // :: error: (assignment.type.incompatible) + bn = s17; + // :: error: (assignment.type.incompatible) + fbn = s17; + + us = s18; + // :: error: (assignment.type.incompatible) + fqn = s18; + // :: error: (assignment.type.incompatible) + cgn = s18; + // :: error: (assignment.type.incompatible) + fd = s18; + // :: error: (assignment.type.incompatible) + iform = s18; + // :: error: (assignment.type.incompatible) + sn = s18; + // :: error: (assignment.type.incompatible) + bn = s18; + // :: error: (assignment.type.incompatible) + fbn = s18; + + us = s19; + // :: error: (assignment.type.incompatible) + fqn = s19; + // :: error: (assignment.type.incompatible) + cgn = s19; + // :: error: (assignment.type.incompatible) + fd = s19; + // :: error: (assignment.type.incompatible) + iform = s19; + // :: error: (assignment.type.incompatible) + sn = s19; + // :: error: (assignment.type.incompatible) + bn = s19; + // :: error: (assignment.type.incompatible) + fbn = s19; + + us = s20; + fqn = s20; + cgn = s20; + // :: error: (assignment.type.incompatible) + fd = s20; + // :: error: (assignment.type.incompatible) + iform = s20; + sn = s20; + // :: error: (assignment.type.incompatible) + bn = s20; + fbn = s20; + + us = s21; + // :: error: (assignment.type.incompatible) + fqn = s21; + // :: error: (assignment.type.incompatible) + cgn = s21; + // :: error: (assignment.type.incompatible) + fd = s21; + // :: error: (assignment.type.incompatible) + iform = s21; + // :: error: (assignment.type.incompatible) + sn = s21; + // :: error: (assignment.type.incompatible) + bn = s21; + // :: error: (assignment.type.incompatible) + fbn = s21; + + us = s22; + // :: error: (assignment.type.incompatible) + fqn = s22; + // :: error: (assignment.type.incompatible) + cgn = s22; + // :: error: (assignment.type.incompatible) + fd = s22; + // :: error: (assignment.type.incompatible) + iform = s22; + // :: error: (assignment.type.incompatible) + sn = s22; + // :: error: (assignment.type.incompatible) + bn = s22; + // :: error: (assignment.type.incompatible) + fbn = s22; + + // Examples from the manual start here + + us = t13; + fqn = t13; + cgn = t13; + // :: error: (assignment.type.incompatible) + fd = t13; + // :: error: (assignment.type.incompatible) + iform = t13; + sn = t13; + // :: error: (assignment.type.incompatible) + bn = t13; + fbn = t13; + + us = t14; + fqn = t14; + // :: error: (assignment.type.incompatible) + cgn = t14; + // :: error: (assignment.type.incompatible) + fd = t14; + // :: error: (assignment.type.incompatible) + iform = t14; + sn = t14; + // :: error: (assignment.type.incompatible) + bn = t14; // t14 is int[][] + + us = t1; + fqn = t1; + cgn = t1; + fd = t1; + iform = t1; + sn = t1; + bn = t1; + fbn = t1; + + us = t12; + // :: error: (assignment.type.incompatible) + fqn = t12; + cgn = t12; + fd = t12; + // :: error: (assignment.type.incompatible) + iform = t12; + // :: error: (assignment.type.incompatible) + sn = t12; + // :: error: (assignment.type.incompatible) + bn = t12; + // :: error: (assignment.type.incompatible) + fbn = t12; + + us = t5; + fqn = t5; + cgn = t5; + // :: error: (assignment.type.incompatible) + fd = t5; + iform = t5; + sn = t5; + bn = t5; + fbn = t5; + + us = t2; + // :: error: (assignment.type.incompatible) + fqn = t2; + // :: error: (assignment.type.incompatible) + cgn = t2; + fd = t2; + // :: error: (assignment.type.incompatible) + iform = t2; + // :: error: (assignment.type.incompatible) + sn = t2; + // :: error: (assignment.type.incompatible) + bn = t2; + // :: error: (assignment.type.incompatible) + fbn = t2; + + us = t6; + fqn = t6; + // :: error: (assignment.type.incompatible) + cgn = t6; + // :: error: (assignment.type.incompatible) + fd = t6; + // :: error: (assignment.type.incompatible) + iform = t6; + sn = t6; + // :: error: (assignment.type.incompatible) + bn = t6; + fbn = t6; + + us = t7; + // :: error: (assignment.type.incompatible) + fqn = t7; + cgn = t7; + fd = t7; + // :: error: (assignment.type.incompatible) + iform = t7; + // :: error: (assignment.type.incompatible) + sn = t7; + // :: error: (assignment.type.incompatible) + bn = t7; + // :: error: (assignment.type.incompatible) + fbn = t7; + + us = t29; + // :: error: (assignment.type.incompatible) + fqn = t29; + // :: error: (assignment.type.incompatible) + cgn = t29; + // :: error: (assignment.type.incompatible) + fd = t29; + // :: error: (assignment.type.incompatible) + iform = t29; + sn = t29; + // :: error: (assignment.type.incompatible) + bn = t29; + // :: error: (assignment.type.incompatible) + fbn = t29; + + us = t33; + // :: error: (assignment.type.incompatible) + fqn = t33; + // :: error: (assignment.type.incompatible) + cgn = t33; + // :: error: (assignment.type.incompatible) + fd = t33; + // :: error: (assignment.type.incompatible) + iform = t33; + sn = t33; + // :: error: (assignment.type.incompatible) + bn = t33; + // :: error: (assignment.type.incompatible) + fbn = t33; + + us = t15; + fqn = t15; + cgn = t15; + // :: error: (assignment.type.incompatible) + fd = t15; + // :: error: (assignment.type.incompatible) + iform = t15; + // :: error: (assignment.type.incompatible) + sn = t15; + bn = t15; + fbn = t15; + + us = t16; + fqn = t16; + // :: error: (assignment.type.incompatible) + cgn = t16; + // :: error: (assignment.type.incompatible) + fd = t16; + // :: error: (assignment.type.incompatible) + iform = t16; + // :: error: (assignment.type.incompatible) + sn = t16; + // :: error: (assignment.type.incompatible) + bn = t16; // t16 is java.lang.Integer[] + + us = t22; + // :: error: (assignment.type.incompatible) + fqn = t22; + // :: error: (assignment.type.incompatible) + cgn = t22; + // :: error: (assignment.type.incompatible) + fd = t22; + iform = t22; + // :: error: (assignment.type.incompatible) + sn = t22; + // :: error: (assignment.type.incompatible) + bn = t22; + // :: error: (assignment.type.incompatible) + fbn = t22; + + us = t23; + // :: error: (assignment.type.incompatible) + fqn = t23; + // :: error: (assignment.type.incompatible) + cgn = t23; + // :: error: (assignment.type.incompatible) + fd = t23; + // :: error: (assignment.type.incompatible) + iform = t23; // t23 is java/lang/Integer[] + // :: error: (assignment.type.incompatible) + sn = t23; + // :: error: (assignment.type.incompatible) + bn = t23; + // :: error: (assignment.type.incompatible) + fbn = t23; + + us = t3; + // :: error: (assignment.type.incompatible) + fqn = t3; + // :: error: (assignment.type.incompatible) + cgn = t3; + fd = t3; + // :: error: (assignment.type.incompatible) + iform = t3; + // :: error: (assignment.type.incompatible) + sn = t3; + // :: error: (assignment.type.incompatible) + bn = t3; + // :: error: (assignment.type.incompatible) + fbn = t3; + + us = t8; + // :: error: (assignment.type.incompatible) + fqn = t8; + cgn = t8; + // :: error: (assignment.type.incompatible) + fd = t8; + // :: error: (assignment.type.incompatible) + iform = t8; + // :: error: (assignment.type.incompatible) + sn = t8; + // :: error: (assignment.type.incompatible) + bn = t8; + // :: error: (assignment.type.incompatible) + fbn = t8; + + us = t9; + // :: error: (assignment.type.incompatible) + fqn = t9; + // :: error: (assignment.type.incompatible) + cgn = t9; + fd = t9; + // :: error: (assignment.type.incompatible) + iform = t9; + // :: error: (assignment.type.incompatible) + sn = t9; + // :: error: (assignment.type.incompatible) + bn = t9; + // :: error: (assignment.type.incompatible) + fbn = t9; + + us = t24; + // :: error: (assignment.type.incompatible) + fqn = t24; + // :: error: (assignment.type.incompatible) + cgn = t24; + // :: error: (assignment.type.incompatible) + fd = t24; + iform = t24; + // :: error: (assignment.type.incompatible) + sn = t24; + // :: error: (assignment.type.incompatible) + bn = t24; + // :: error: (assignment.type.incompatible) + fbn = t24; + + us = t25; + // :: error: (assignment.type.incompatible) + fqn = t25; + // :: error: (assignment.type.incompatible) + cgn = t25; + // :: error: (assignment.type.incompatible) + fd = t25; + // :: error: (assignment.type.incompatible) + iform = t25; // rhs is pakkage/Outer$Inner[] + // :: error: (assignment.type.incompatible) + sn = t25; + // :: error: (assignment.type.incompatible) + bn = t25; + // :: error: (assignment.type.incompatible) + fbn = t25; + + us = t28; + // :: error: (assignment.type.incompatible) + fqn = t28; + // :: error: (assignment.type.incompatible) + cgn = t28; + // :: error: (assignment.type.incompatible) + fd = t28; + iform = t28; + // :: error: (assignment.type.incompatible) + sn = t28; + // :: error: (assignment.type.incompatible) + bn = t28; + // :: error: (assignment.type.incompatible) + fbn = t28; + + us = t27; + // :: error: (assignment.type.incompatible) + fqn = t27; + // :: error: (assignment.type.incompatible) + cgn = t27; + fd = t27; + // :: error: (assignment.type.incompatible) + iform = t27; + // :: error: (assignment.type.incompatible) + sn = t27; + // :: error: (assignment.type.incompatible) + bn = t27; + // :: error: (assignment.type.incompatible) + fbn = t27; + + us = t26; + fqn = t26; + cgn = t26; + // :: error: (assignment.type.incompatible) + fd = t26; + // :: error: (assignment.type.incompatible) + iform = t26; + // :: error: (assignment.type.incompatible) + sn = t26; + bn = t26; + fbn = t26; + + us = t32; + // :: error: (assignment.type.incompatible) + fqn = t32; + // :: error: (assignment.type.incompatible) + cgn = t32; + // :: error: (assignment.type.incompatible) + fd = t32; + // :: error: (assignment.type.incompatible) + iform = t32; // t32 is array + // :: error: (assignment.type.incompatible) + sn = t32; + // :: error: (assignment.type.incompatible) + bn = t32; + // :: error: (assignment.type.incompatible) + fbn = t32; + + us = t30; + fqn = t30; + // :: error: (assignment.type.incompatible) + cgn = t30; + // :: error: (assignment.type.incompatible) + fd = t30; + // :: error: (assignment.type.incompatible) + iform = t30; + // :: error: (assignment.type.incompatible) + sn = t30; + // :: error: (assignment.type.incompatible) + bn = t30; // rhs is array + + us = t31; + // :: error: (assignment.type.incompatible) + fqn = t31; + cgn = t31; + // :: error: (assignment.type.incompatible) + fd = t31; + // :: error: (assignment.type.incompatible) + iform = t31; + // :: error: (assignment.type.incompatible) + sn = t31; + // :: error: (assignment.type.incompatible) + bn = t31; + // :: error: (assignment.type.incompatible) + fbn = t31; + + us = t34; + fqn = t34; + cgn = t34; + // :: error: (assignment.type.incompatible) + fd = t34; + // :: error: (assignment.type.incompatible) + iform = t34; + // :: error: (assignment.type.incompatible) + sn = t34; + bn = t34; + fbn = t34; + + us = t17; + fqn = t17; + cgn = t17; + // :: error: (assignment.type.incompatible) + fd = t17; + // :: error: (assignment.type.incompatible) + iform = t17; + // :: error: (assignment.type.incompatible) + sn = t17; + bn = t17; + fbn = t17; + + us = t18; + fqn = t18; + // :: error: (assignment.type.incompatible) + cgn = t18; + // :: error: (assignment.type.incompatible) + fd = t18; + // :: error: (assignment.type.incompatible) + iform = t18; + // :: error: (assignment.type.incompatible) + sn = t18; + // :: error: (assignment.type.incompatible) + bn = t18; // t18 is pakkage.Outer.Inner[] + + us = t19; + fqn = t19; + cgn = t19; + // :: error: (assignment.type.incompatible) + fd = t19; + // :: error: (assignment.type.incompatible) + iform = t19; + // :: error: (assignment.type.incompatible) + sn = t19; + bn = t19; + fbn = t19; + + us = t21; + fqn = t21; + // :: error: (assignment.type.incompatible) + cgn = t21; + // :: error: (assignment.type.incompatible) + fd = t21; + // :: error: (assignment.type.incompatible) + iform = t21; + // :: error: (assignment.type.incompatible) + sn = t21; + // :: error: (assignment.type.incompatible) + bn = t21; // t21 is pakkage.Outer$Inner[] + + us = t20; + // :: error: (assignment.type.incompatible) + fqn = t20; + // :: error: (assignment.type.incompatible) + cgn = t20; + // :: error: (assignment.type.incompatible) + fd = t20; + // :: error: (assignment.type.incompatible) + iform = t20; + // :: error: (assignment.type.incompatible) + sn = t20; + // :: error: (assignment.type.incompatible) + bn = t20; + // :: error: (assignment.type.incompatible) + fbn = t20; + + us = t10; + // :: error: (assignment.type.incompatible) + fqn = t10; + cgn = t10; + // :: error: (assignment.type.incompatible) + fd = t10; + // :: error: (assignment.type.incompatible) + iform = t10; + // :: error: (assignment.type.incompatible) + sn = t10; + // :: error: (assignment.type.incompatible) + bn = t10; + // :: error: (assignment.type.incompatible) + fbn = t10; + + us = t4; + // :: error: (assignment.type.incompatible) + fqn = t4; + // :: error: (assignment.type.incompatible) + cgn = t4; + fd = t4; + // :: error: (assignment.type.incompatible) + iform = t4; + // :: error: (assignment.type.incompatible) + sn = t4; + // :: error: (assignment.type.incompatible) + bn = t4; + // :: error: (assignment.type.incompatible) + fbn = t4; + + us = t11; + // :: error: (assignment.type.incompatible) + fqn = t11; + // :: error: (assignment.type.incompatible) + cgn = t11; + fd = t11; + // :: error: (assignment.type.incompatible) + iform = t11; + // :: error: (assignment.type.incompatible) + sn = t11; + // :: error: (assignment.type.incompatible) + bn = t11; + // :: error: (assignment.type.incompatible) + fbn = t11; + } } diff --git a/checker/tests/signature/StubLibraryTest.java b/checker/tests/signature/StubLibraryTest.java index 2d3439ac9c5..18a0716ac33 100644 --- a/checker/tests/signature/StubLibraryTest.java +++ b/checker/tests/signature/StubLibraryTest.java @@ -5,13 +5,13 @@ public class StubLibraryTest { - void testJdk() { - @ClassGetName String s3 = String.class.getName(); - } + void testJdk() { + @ClassGetName String s3 = String.class.getName(); + } - // void testBcel(ClassGen cg) { - // @ClassGetName String cgn = cg.getClassName(); - // @BinaryName String bn = cg.getClassName(); - // } + // void testBcel(ClassGen cg) { + // @ClassGetName String cgn = cg.getClassName(); + // @BinaryName String bn = cg.getClassName(); + // } } diff --git a/checker/tests/signedness-initialized-fields/SignednessFields.java b/checker/tests/signedness-initialized-fields/SignednessFields.java index 4113c0b3d9d..a21fc5791f0 100644 --- a/checker/tests/signedness-initialized-fields/SignednessFields.java +++ b/checker/tests/signedness-initialized-fields/SignednessFields.java @@ -2,9 +2,9 @@ public class SignednessFields { - int intField; - @Signed int signedField; - double doubleField; - Object o; - boolean b; + int intField; + @Signed int signedField; + double doubleField; + Object o; + boolean b; } diff --git a/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java index 4d1dcba7dca..4c5168fe8fc 100644 --- a/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java @@ -2,20 +2,20 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - @UnknownSignedness Object field; + @UnknownSignedness Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - // :: error: (argument.type.incompatible) - field = param.getInt(1); - // Signedness Checker doesn't default boxed primitives correctly. - // https://github.com/typetools/checker-framework/issues/797 - // :: error: (argument.type.incompatible) - field = param.getInteger(i); - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - field = param.identity("hello"); - } + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + // :: error: (argument.type.incompatible) + field = param.getInt(1); + // Signedness Checker doesn't default boxed primitives correctly. + // https://github.com/typetools/checker-framework/issues/797 + // :: error: (argument.type.incompatible) + field = param.getInteger(i); + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + field = param.identity("hello"); + } } diff --git a/checker/tests/signedness/AdditionWithChar.java b/checker/tests/signedness/AdditionWithChar.java index 92bcd8b3eba..8066d42ec34 100644 --- a/checker/tests/signedness/AdditionWithChar.java +++ b/checker/tests/signedness/AdditionWithChar.java @@ -1,7 +1,7 @@ public class AdditionWithChar { - int i2; + int i2; - void additionWithChar(int i1, char c) { - i2 = i1 + c; - } + void additionWithChar(int i1, char c) { + i2 = i1 + c; + } } diff --git a/checker/tests/signedness/AnnoBeforeModifier.java b/checker/tests/signedness/AnnoBeforeModifier.java index 26e74f47f62..2e4177fe605 100644 --- a/checker/tests/signedness/AnnoBeforeModifier.java +++ b/checker/tests/signedness/AnnoBeforeModifier.java @@ -3,98 +3,98 @@ public class AnnoBeforeModifier { - // :: warning: (type.anno.before.modifier) - @Unsigned public int i = 0; + // :: warning: (type.anno.before.modifier) + @Unsigned public int i = 0; - public @Unsigned int j = 0; + public @Unsigned int j = 0; - // :: warning: (type.anno.before.modifier) - public @Unsigned final int k = 0; + // :: warning: (type.anno.before.modifier) + public @Unsigned final int k = 0; - @SuppressWarnings("foobar") - @Unsigned public int l = 0; + @SuppressWarnings("foobar") + @Unsigned public int l = 0; - public @SuppressWarnings("foobar") @Unsigned int m = 0; + public @SuppressWarnings("foobar") @Unsigned int m = 0; - @SuppressWarnings("foobar") - @Unsigned public int n = 0; + @SuppressWarnings("foobar") + @Unsigned public int n = 0; - // TODO: :: warning: (type.anno.before.modifier) - public @SuppressWarnings("foobar") @Unsigned final int o = 0; + // TODO: :: warning: (type.anno.before.modifier) + public @SuppressWarnings("foobar") @Unsigned final int o = 0; - // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) - public @Unsigned @SuppressWarnings("foobar") final int p = 0; + // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) + public @Unsigned @SuppressWarnings("foobar") final int p = 0; - public @SuppressWarnings("foobar") final @Unsigned int q = 0; + public @SuppressWarnings("foobar") final @Unsigned int q = 0; - @SuppressWarnings("foobar") - public int r = 0; + @SuppressWarnings("foobar") + public int r = 0; - public @SuppressWarnings("foobar") int s = 0; + public @SuppressWarnings("foobar") int s = 0; - public @SuppressWarnings("foobar") final int t = 0; + public @SuppressWarnings("foobar") final int t = 0; - // :: warning: (type.anno.before.modifier) - @Unsigned public int iMethod() { - return 0; - } + // :: warning: (type.anno.before.modifier) + @Unsigned public int iMethod() { + return 0; + } - public @Unsigned int jMethod() { - return 0; - } + public @Unsigned int jMethod() { + return 0; + } - // :: warning: (type.anno.before.modifier) - public @Unsigned final int kMethod() { - return 0; - } + // :: warning: (type.anno.before.modifier) + public @Unsigned final int kMethod() { + return 0; + } - @SuppressWarnings("foobar") - @Unsigned public int lMethod() { - return 0; - } + @SuppressWarnings("foobar") + @Unsigned public int lMethod() { + return 0; + } - public @SuppressWarnings("foobar") @Unsigned int mMethod() { - return 0; - } + public @SuppressWarnings("foobar") @Unsigned int mMethod() { + return 0; + } - @SuppressWarnings("foobar") - @Unsigned public int nMethod() { - return 0; - } + @SuppressWarnings("foobar") + @Unsigned public int nMethod() { + return 0; + } - // TODO: :: warning: (type.anno.before.modifier) - public @SuppressWarnings("foobar") @Unsigned final int oMethod() { - return 0; - } + // TODO: :: warning: (type.anno.before.modifier) + public @SuppressWarnings("foobar") @Unsigned final int oMethod() { + return 0; + } - // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) - public @Unsigned @SuppressWarnings("foobar") final int pMethod() { - return 0; - } + // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) + public @Unsigned @SuppressWarnings("foobar") final int pMethod() { + return 0; + } - public @SuppressWarnings("foobar") final @Unsigned int qMethod() { - return 0; - } + public @SuppressWarnings("foobar") final @Unsigned int qMethod() { + return 0; + } - @SuppressWarnings("foobar") - public int rMethod() { - return 0; - } + @SuppressWarnings("foobar") + public int rMethod() { + return 0; + } - public @SuppressWarnings("foobar") int sMethod() { - return 0; - } + public @SuppressWarnings("foobar") int sMethod() { + return 0; + } - public @SuppressWarnings("foobar") final int tMethod() { - return 0; - } + public @SuppressWarnings("foobar") final int tMethod() { + return 0; + } - // Use @NonNull rather than a signedness annotation to avoid errors. - @NonNull public enum MyEnum { - @NonNull CONSTANT - } + // Use @NonNull rather than a signedness annotation to avoid errors. + @NonNull public enum MyEnum { + @NonNull CONSTANT + } - interface MyInterface { - @NonNull String myMethod(); - } + interface MyInterface { + @NonNull String myMethod(); + } } diff --git a/checker/tests/signedness/Arrays.java b/checker/tests/signedness/Arrays.java index c60c1966c6e..3d839d06627 100644 --- a/checker/tests/signedness/Arrays.java +++ b/checker/tests/signedness/Arrays.java @@ -1,5 +1,5 @@ public class Arrays { - void test() { - Object[] os = new Double[234]; - } + void test() { + Object[] os = new Double[234]; + } } diff --git a/checker/tests/signedness/BinaryOperations.java b/checker/tests/signedness/BinaryOperations.java index 47bc19c3e65..06064a79bac 100644 --- a/checker/tests/signedness/BinaryOperations.java +++ b/checker/tests/signedness/BinaryOperations.java @@ -2,161 +2,161 @@ public class BinaryOperations { - public void DivModTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void DivModTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @Unsigned int unsignedresult; - @UnknownSignedness int unknownresult; + @Unsigned int unsignedresult; + @UnknownSignedness int unknownresult; - // :: error: (operation.unsignedrhs) - unknownresult = unknown / unsigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown / unsigned; - // :: error: (operation.unsignedlhs) - unknownresult = unsigned / unknown; + // :: error: (operation.unsignedlhs) + unknownresult = unsigned / unknown; - // :: error: (operation.unsignedlhs) - unsignedresult = unsigned / constant; + // :: error: (operation.unsignedlhs) + unsignedresult = unsigned / constant; - // :: error: (operation.unsignedrhs) - unsignedresult = constant / unsigned; + // :: error: (operation.unsignedrhs) + unsignedresult = constant / unsigned; - // :: error: (operation.unsignedrhs) - unknownresult = unknown / polysigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown / polysigned; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned / unknown; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned / unknown; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned / constant; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned / constant; - // :: error: (operation.unsignedrhs) - unknownresult = constant / polysigned; + // :: error: (operation.unsignedrhs) + unknownresult = constant / polysigned; - // :: error: (operation.unsignedrhs) - unknownresult = unknown % unsigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown % unsigned; - // :: error: (operation.unsignedlhs) - unknownresult = unsigned % unknown; + // :: error: (operation.unsignedlhs) + unknownresult = unsigned % unknown; - // :: error: (operation.unsignedrhs) - unknownresult = unknown % polysigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown % polysigned; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned % unknown; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned % unknown; - // :: error: (operation.unsignedlhs) - unsignedresult = unsigned % constant; + // :: error: (operation.unsignedlhs) + unsignedresult = unsigned % constant; - // :: error: (operation.unsignedrhs) - unsignedresult = constant % unsigned; + // :: error: (operation.unsignedrhs) + unsignedresult = constant % unsigned; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned % constant; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned % constant; - // :: error: (operation.unsignedrhs) - unknownresult = constant % polysigned; - } + // :: error: (operation.unsignedrhs) + unknownresult = constant % polysigned; + } - public void SignedRightShiftTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void SignedRightShiftTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @Unsigned int unsignedresult; - @PolySigned int polysignedresult; - @UnknownSignedness int unknownresult; - int result; + @Unsigned int unsignedresult; + @PolySigned int polysignedresult; + @UnknownSignedness int unknownresult; + int result; - // :: error: (shift.signed) - unsignedresult = unsigned >> constant; + // :: error: (shift.signed) + unsignedresult = unsigned >> constant; - result = constant >> unsigned; + result = constant >> unsigned; - // :: error: (shift.signed) - polysignedresult = polysigned >> constant; + // :: error: (shift.signed) + polysignedresult = polysigned >> constant; - result = constant >> polysigned; + result = constant >> polysigned; - // :: error: (shift.signed) - unsignedresult = unsigned >> unknown; + // :: error: (shift.signed) + unsignedresult = unsigned >> unknown; - unknownresult = unknown >> unsigned; + unknownresult = unknown >> unsigned; - // :: error: (shift.signed) - polysignedresult = polysigned >> unknown; + // :: error: (shift.signed) + polysignedresult = polysigned >> unknown; - unknownresult = unknown >> polysigned; - } + unknownresult = unknown >> polysigned; + } - public void UnsignedRightShiftTest( - @Signed int signed, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void UnsignedRightShiftTest( + @Signed int signed, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @PolySigned int polysignedresult; - @UnknownSignedness int unknownresult; - int result; + @PolySigned int polysignedresult; + @UnknownSignedness int unknownresult; + int result; - // :: error: (shift.unsigned) - result = signed >>> constant; + // :: error: (shift.unsigned) + result = signed >>> constant; - result = constant >>> signed; + result = constant >>> signed; - // :: error: (shift.unsigned) - result = signed >>> unknown; + // :: error: (shift.unsigned) + result = signed >>> unknown; - unknownresult = unknown >>> signed; + unknownresult = unknown >>> signed; - // :: error: (shift.unsigned) - polysignedresult = polysigned >>> constant; + // :: error: (shift.unsigned) + polysignedresult = polysigned >>> constant; - result = constant >>> polysigned; + result = constant >>> polysigned; - // :: error: (shift.unsigned) - polysignedresult = polysigned >>> unknown; + // :: error: (shift.unsigned) + polysignedresult = polysigned >>> unknown; - unknownresult = unknown >>> polysigned; - } + unknownresult = unknown >>> polysigned; + } - public void LeftShiftTest( - @Signed int signed, - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void LeftShiftTest( + @Signed int signed, + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @PolySigned int polysignedresult; - @UnknownSignedness int unknownresult; - @Unsigned int unsignedresult; - int result; + @PolySigned int polysignedresult; + @UnknownSignedness int unknownresult; + @Unsigned int unsignedresult; + int result; - result = signed << constant; + result = signed << constant; - result = constant << signed; + result = constant << signed; - result = signed << unknown; + result = signed << unknown; - unknownresult = unknown << signed; + unknownresult = unknown << signed; - unsignedresult = unsigned << constant; + unsignedresult = unsigned << constant; - result = constant << unsigned; + result = constant << unsigned; - unsignedresult = unsigned << unknown; + unsignedresult = unsigned << unknown; - unknownresult = unknown << unsigned; + unknownresult = unknown << unsigned; - polysignedresult = polysigned << constant; + polysignedresult = polysigned << constant; - result = constant << polysigned; + result = constant << polysigned; - polysignedresult = polysigned << unknown; + polysignedresult = polysigned << unknown; - unknownresult = unknown << polysigned; - } + unknownresult = unknown << polysigned; + } } diff --git a/checker/tests/signedness/BooleansTest.java b/checker/tests/signedness/BooleansTest.java index 46ab2c34c96..16815a6ef35 100644 --- a/checker/tests/signedness/BooleansTest.java +++ b/checker/tests/signedness/BooleansTest.java @@ -2,24 +2,24 @@ public final class BooleansTest { - private static int indexOf(boolean[] array, boolean target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; + private static int indexOf(boolean[] array, boolean target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } } + return -1; + } - static class BooleanArrayAsList { - boolean[] array = new boolean[] {}; + static class BooleanArrayAsList { + boolean[] array = new boolean[] {}; - int start = 0; - int end = 0; + int start = 0; + int end = 0; - public boolean contains(@UnknownSignedness Object target) { - return (target instanceof Boolean) - && BooleansTest.indexOf(array, (Boolean) target, start, end) != -1; - } + public boolean contains(@UnknownSignedness Object target) { + return (target instanceof Boolean) + && BooleansTest.indexOf(array, (Boolean) target, start, end) != -1; } + } } diff --git a/checker/tests/signedness/BoxedPrimitives.java b/checker/tests/signedness/BoxedPrimitives.java index a182725f607..661f522ebad 100644 --- a/checker/tests/signedness/BoxedPrimitives.java +++ b/checker/tests/signedness/BoxedPrimitives.java @@ -1,84 +1,83 @@ +import java.util.LinkedList; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.Unsigned; -import java.util.LinkedList; - public class BoxedPrimitives { - @Signed int si; - @Unsigned int ui; + @Signed int si; + @Unsigned int ui; - @Signed Integer sbi; - @Unsigned Integer ubi; + @Signed Integer sbi; + @Unsigned Integer ubi; - void argSigned(@Signed int x) { - si = x; - sbi = x; - // :: error: (assignment.type.incompatible) - ui = x; - // :: error: (assignment.type.incompatible) - ubi = x; - } + void argSigned(@Signed int x) { + si = x; + sbi = x; + // :: error: (assignment.type.incompatible) + ui = x; + // :: error: (assignment.type.incompatible) + ubi = x; + } - void argUnsigned(@Unsigned int x) { - // :: error: (assignment.type.incompatible) - si = x; - // :: error: (assignment.type.incompatible) - sbi = x; - ui = x; - ubi = x; - } + void argUnsigned(@Unsigned int x) { + // :: error: (assignment.type.incompatible) + si = x; + // :: error: (assignment.type.incompatible) + sbi = x; + ui = x; + ubi = x; + } - void argSignedBoxed(@Signed Integer x) { - si = x; - sbi = x; - // :: error: (assignment.type.incompatible) - ui = x; - // :: error: (assignment.type.incompatible) - ubi = x; - } + void argSignedBoxed(@Signed Integer x) { + si = x; + sbi = x; + // :: error: (assignment.type.incompatible) + ui = x; + // :: error: (assignment.type.incompatible) + ubi = x; + } - void argUnsignedBoxed(@Unsigned Integer x) { - // :: error: (assignment.type.incompatible) - si = x; - // :: error: (assignment.type.incompatible) - sbi = x; - ui = x; - ubi = x; - } + void argUnsignedBoxed(@Unsigned Integer x) { + // :: error: (assignment.type.incompatible) + si = x; + // :: error: (assignment.type.incompatible) + sbi = x; + ui = x; + ubi = x; + } - void client() { - argSigned(si); - argSignedBoxed(si); - argSigned(sbi); - argSignedBoxed(sbi); - // :: error: (argument.type.incompatible) - argUnsigned(si); - // :: error: (argument.type.incompatible) - argUnsignedBoxed(si); - // :: error: (argument.type.incompatible) - argUnsigned(sbi); - // :: error: (argument.type.incompatible) - argUnsignedBoxed(sbi); - // :: error: (argument.type.incompatible) - argSigned(ui); - // :: error: (argument.type.incompatible) - argSignedBoxed(ui); - // :: error: (argument.type.incompatible) - argSigned(ubi); - // :: error: (argument.type.incompatible) - argSignedBoxed(ubi); - argUnsigned(ui); - argUnsignedBoxed(ui); - argUnsigned(ubi); - argUnsignedBoxed(ubi); - } + void client() { + argSigned(si); + argSignedBoxed(si); + argSigned(sbi); + argSignedBoxed(sbi); + // :: error: (argument.type.incompatible) + argUnsigned(si); + // :: error: (argument.type.incompatible) + argUnsignedBoxed(si); + // :: error: (argument.type.incompatible) + argUnsigned(sbi); + // :: error: (argument.type.incompatible) + argUnsignedBoxed(sbi); + // :: error: (argument.type.incompatible) + argSigned(ui); + // :: error: (argument.type.incompatible) + argSignedBoxed(ui); + // :: error: (argument.type.incompatible) + argSigned(ubi); + // :: error: (argument.type.incompatible) + argSignedBoxed(ubi); + argUnsigned(ui); + argUnsignedBoxed(ui); + argUnsigned(ubi); + argUnsignedBoxed(ubi); + } - public LinkedList commands; + public LinkedList commands; - void forLoop() { - for (Integer ix : this.commands) { - argSigned(ix); - } + void forLoop() { + for (Integer ix : this.commands) { + argSigned(ix); } + } } diff --git a/checker/tests/signedness/Cast.java b/checker/tests/signedness/Cast.java index 40122c62fc7..51b43bd9443 100644 --- a/checker/tests/signedness/Cast.java +++ b/checker/tests/signedness/Cast.java @@ -2,21 +2,21 @@ public class Cast { - static final Object object = 1; + static final Object object = 1; - void client() { - objectiveParameter(object); - } + void client() { + objectiveParameter(object); + } - void objectiveParameter(Object object) { - integralParameter((Integer) object); - } + void objectiveParameter(Object object) { + integralParameter((Integer) object); + } - // This passes when object is initialized within objectiveArgument(). - void objectiveArgument() { - Object object = -3; - integralParameter((Integer) object); - } + // This passes when object is initialized within objectiveArgument(). + void objectiveArgument() { + Object object = -3; + integralParameter((Integer) object); + } - void integralParameter(int x) {} + void integralParameter(int x) {} } diff --git a/checker/tests/signedness/CastedShifts.java b/checker/tests/signedness/CastedShifts.java index a703a9e0c5a..d82b9f048d8 100644 --- a/checker/tests/signedness/CastedShifts.java +++ b/checker/tests/signedness/CastedShifts.java @@ -2,374 +2,374 @@ public class CastedShifts { - public void CastedIntShifts(@Unsigned int unsigned, @Signed int signed) { - // Cast to byte. - @UnknownSignedness byte byteRes; - - // Shifting right by 23, the introduced bits are cast away - byteRes = (@Unsigned byte) (unsigned >>> 23); - byteRes = (@Unsigned byte) (unsigned >> 23); - byteRes = (@Signed byte) (signed >>> 23); - byteRes = (@Signed byte) (signed >> 23); - byteRes = (byte) (signed >> 23); - - // Shifting right by 24, the introduced bits are still cast away. - byteRes = (@Unsigned byte) (unsigned >>> 24); - byteRes = (@Unsigned byte) (unsigned >> 24); - byteRes = (@Signed byte) (signed >>> 24); - byteRes = (@Signed byte) (signed >> 24); - - // Shifting right by 25, now the MSB matters. - byteRes = (@Unsigned byte) (unsigned >>> 25); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) (unsigned >> 25); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) (signed >>> 25); - byteRes = (@Signed byte) (signed >> 25); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) (unsigned >>> 0); - byteRes = (@Unsigned byte) (unsigned >> 0); - byteRes = (@Signed byte) (signed >>> 0); - byteRes = (@Signed byte) (signed >> 0); - - // Cast to short. - @UnknownSignedness short shortRes; - - // Shifting right by 15, the introduced bits are cast away - shortRes = (@Unsigned short) (unsigned >>> 15); - shortRes = (@Unsigned short) (unsigned >> 15); - shortRes = (@Signed short) (signed >>> 15); - shortRes = (@Signed short) (signed >> 15); - - // Shifting right by 16, the introduced bits are still cast away. - shortRes = (@Unsigned short) (unsigned >>> 16); - shortRes = (@Unsigned short) (unsigned >> 16); - shortRes = (@Signed short) (signed >>> 16); - shortRes = (@Signed short) (signed >> 16); - - // Shifting right by 17, now the MSB matters. - shortRes = (@Unsigned short) (unsigned >>> 17); - - // :: error: (shift.signed) - shortRes = (@Unsigned short) (unsigned >> 17); - - // :: error: (shift.unsigned) - shortRes = (@Signed short) (signed >>> 17); - shortRes = (@Signed short) (signed >> 17); - - // Shifting right by zero should behave as assignment - shortRes = (@Unsigned short) (unsigned >>> 0); - shortRes = (@Unsigned short) (unsigned >> 0); - shortRes = (@Signed short) (signed >>> 0); - shortRes = (@Signed short) (signed >> 0); - - // Cast to int. - @UnknownSignedness int intRes; - - // Now shift signedness matters again - intRes = (@Unsigned int) (unsigned >>> 1); - - // :: error: (shift.signed) - intRes = (@Unsigned int) (unsigned >> 1); - - // :: error: (shift.unsigned) - intRes = (@Signed int) (signed >>> 1); - intRes = (@Signed int) (signed >> 1); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) (unsigned >>> 0); - intRes = (@Unsigned int) (unsigned >> 0); - intRes = (@Signed int) (signed >>> 0); - intRes = (@Signed int) (signed >> 0); - - // Cast to long. - @UnknownSignedness long longRes; - - // Now shift signedness matters again - longRes = (@Unsigned long) (unsigned >>> 1); - - // :: error: (shift.signed) - longRes = (@Unsigned long) (unsigned >> 1); - - // :: error: (shift.unsigned) - longRes = (@Signed long) (signed >>> 1); - longRes = (@Signed long) (signed >> 1); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) (unsigned >>> 0); - longRes = (@Unsigned long) (unsigned >> 0); - longRes = (@Signed long) (signed >>> 0); - longRes = (@Signed long) (signed >> 0); - - // Tests with double parenthesis (only byte and int) - - // Cast to byte. - // Shifting right by 23, the introduced bits are cast away - byteRes = (@Unsigned byte) ((unsigned >>> 23)); - byteRes = (@Unsigned byte) ((unsigned >> 23)); - byteRes = (@Signed byte) ((signed >>> 23)); - byteRes = (@Signed byte) ((signed >> 23)); - - // Shifting right by 24, the introduced bits are still cast away. - byteRes = (@Unsigned byte) ((unsigned >>> 24)); - byteRes = (@Unsigned byte) ((unsigned >> 24)); - byteRes = (@Signed byte) ((signed >>> 24)); - byteRes = (@Signed byte) ((signed >> 24)); - - // Shifting right by 25, now the MSB matters. - byteRes = (@Unsigned byte) ((unsigned >>> 25)); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) ((unsigned >> 25)); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) ((signed >>> 25)); - byteRes = (@Signed byte) ((signed >> 25)); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) ((unsigned >>> 0)); - byteRes = (@Unsigned byte) ((unsigned >> 0)); - byteRes = (@Signed byte) ((signed >>> 0)); - byteRes = (@Signed byte) ((signed >> 0)); - - // Cast to int. - // Now shift signedness matters again - intRes = (@Unsigned int) ((unsigned >>> 1)); - - // :: error: (shift.signed) - intRes = (@Unsigned int) ((unsigned >> 1)); - - // :: error: (shift.unsigned) - intRes = (@Signed int) ((signed >>> 1)); - intRes = (@Signed int) ((signed >> 1)); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) ((unsigned >>> 0)); - intRes = (@Unsigned int) ((unsigned >> 0)); - intRes = (@Signed int) ((signed >>> 0)); - intRes = (@Signed int) ((signed >> 0)); - - // Test outside Java Specification shift ranges - // Cast to int. - // Now shift signedness matters again - intRes = (@Unsigned int) ((unsigned >>> 33)); - - // :: error: (shift.signed) - intRes = (@Unsigned int) ((unsigned >> 33)); - - // :: error: (shift.unsigned) - intRes = (@Signed int) ((signed >>> 33)); - intRes = (@Signed int) ((signed >> 33)); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) ((unsigned >>> 32)); - intRes = (@Unsigned int) ((unsigned >> 32)); - intRes = (@Signed int) ((signed >>> 32)); - intRes = (@Signed int) ((signed >> 32)); - } - - public void CastedLongShifts(@Unsigned long unsigned, @Signed long signed) { - // Cast to byte. - @UnknownSignedness byte byteRes; - - // Shifting right by 55, the introduced bits are cast away - byteRes = (@Unsigned byte) (unsigned >>> 55); - byteRes = (@Unsigned byte) (unsigned >> 55); - byteRes = (@Signed byte) (signed >>> 55); - byteRes = (@Signed byte) (signed >> 55); - - // Shifting right by 56, the introduced bits are still cast away. - byteRes = (@Unsigned byte) (unsigned >>> 56); - byteRes = (@Unsigned byte) (unsigned >> 56); - byteRes = (@Signed byte) (signed >>> 56); - byteRes = (@Signed byte) (signed >> 56); - - // Shifting right by 57, now the MSB matters. - byteRes = (@Unsigned byte) (unsigned >>> 57); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) (unsigned >> 57); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) (signed >>> 57); - byteRes = (@Signed byte) (signed >> 57); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) (unsigned >>> 0); - byteRes = (@Unsigned byte) (unsigned >> 0); - byteRes = (@Signed byte) (signed >>> 0); - byteRes = (@Signed byte) (signed >> 0); - - // Cast to char. - char charRes; - - // Shifting right by 55, the introduced bits are cast away - charRes = (char) (unsigned >>> 55); - charRes = (char) (unsigned >> 55); - - // Shifting right by 56, the introduced bits are still cast away. - charRes = (char) (unsigned >>> 56); - charRes = (char) (unsigned >> 56); - - // Shifting right by 57, now the MSB matters. - charRes = (char) (unsigned >>> 57); - - // :: error: (shift.signed) - charRes = (char) (unsigned >> 57); - - // Shifting right by zero should behave as assignment - charRes = (char) (unsigned >>> 0); - charRes = (char) (unsigned >> 0); - - // Cast to short. - @UnknownSignedness short shortRes; - - // Shifting right by 47, the introduced bits are cast away - shortRes = (@Unsigned short) (unsigned >>> 47); - shortRes = (@Unsigned short) (unsigned >> 47); - shortRes = (@Signed short) (signed >>> 47); - shortRes = (@Signed short) (signed >> 47); - - // Shifting right by 48, the introduced bits are still cast away. - shortRes = (@Unsigned short) (unsigned >>> 48); - shortRes = (@Unsigned short) (unsigned >> 48); - shortRes = (@Signed short) (signed >>> 48); - shortRes = (@Signed short) (signed >> 48); - - // Shifting right by 49, now the MSB matters. - shortRes = (@Unsigned short) (unsigned >>> 49); - - // :: error: (shift.signed) - shortRes = (@Unsigned short) (unsigned >> 49); - - // :: error: (shift.unsigned) - shortRes = (@Signed short) (signed >>> 49); - shortRes = (@Signed short) (signed >> 49); - - // Shifting right by zero should behave as assignment - shortRes = (@Unsigned short) (unsigned >>> 0); - shortRes = (@Unsigned short) (unsigned >> 0); - shortRes = (@Signed short) (signed >>> 0); - shortRes = (@Signed short) (signed >> 0); - - // Cast to int. - @UnknownSignedness int intRes; - - // Shifting right by 31, the introduced bits are cast away - intRes = (@Unsigned int) (unsigned >>> 31); - intRes = (@Unsigned int) (unsigned >> 31); - intRes = (@Signed int) (signed >>> 31); - intRes = (@Signed int) (signed >> 31); - - // Shifting right by 32, the introduced bits are still cast away. - intRes = (@Unsigned int) (unsigned >>> 32); - intRes = (@Unsigned int) (unsigned >> 32); - intRes = (@Signed int) (signed >>> 32); - intRes = (@Signed int) (signed >> 32); - - // Shifting right by 33, now the MSB matters. - intRes = (@Unsigned int) (unsigned >>> 33); - - // :: error: (shift.signed) - intRes = (@Unsigned int) (unsigned >> 33); - - // :: error: (shift.unsigned) - intRes = (@Signed int) (signed >>> 33); - intRes = (@Signed int) (signed >> 33); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) (unsigned >>> 0); - intRes = (@Unsigned int) (unsigned >> 0); - intRes = (@Signed int) (signed >>> 0); - intRes = (@Signed int) (signed >> 0); - - // Cast to long. - @UnknownSignedness long longRes; - - // Now shift signedness matters again - longRes = (@Unsigned long) (unsigned >>> 1); - - // :: error: (shift.signed) - longRes = (@Unsigned long) (unsigned >> 1); - - // :: error: (shift.unsigned) - longRes = (@Signed long) (signed >>> 1); - longRes = (@Signed long) (signed >> 1); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) (unsigned >>> 0); - longRes = (@Unsigned long) (unsigned >> 0); - longRes = (@Signed long) (signed >>> 0); - longRes = (@Signed long) (signed >> 0); - - // Tests with double parenthesis (only byte and long) - - // Cast to byte. - // Shifting right by 55, the introduced bits are cast away - byteRes = (@Unsigned byte) ((unsigned >>> 55)); - byteRes = (@Unsigned byte) ((unsigned >> 55)); - byteRes = (@Signed byte) ((signed >>> 55)); - byteRes = (@Signed byte) ((signed >> 55)); - - // Shifting right by 56, the introduced bits are still cast away. - byteRes = (@Unsigned byte) ((unsigned >>> 56)); - byteRes = (@Unsigned byte) ((unsigned >> 56)); - byteRes = (@Signed byte) ((signed >>> 56)); - byteRes = (@Signed byte) ((signed >> 56)); - - // Shifting right by 9, now the MSB matters. - byteRes = (@Unsigned byte) ((unsigned >>> 57)); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) ((unsigned >> 57)); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) ((signed >>> 57)); - byteRes = (@Signed byte) ((signed >> 57)); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) ((unsigned >>> 0)); - byteRes = (@Unsigned byte) ((unsigned >> 0)); - byteRes = (@Signed byte) ((signed >>> 0)); - byteRes = (@Signed byte) ((signed >> 0)); - - // Cast to long. - // Now shift signedness matters again - longRes = (@Unsigned long) ((unsigned >>> 1)); - - // :: error: (shift.signed) - longRes = (@Unsigned long) ((unsigned >> 1)); - - // :: error: (shift.unsigned) - longRes = (@Signed long) ((signed >>> 1)); - longRes = (@Signed long) ((signed >> 1)); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) ((unsigned >>> 0)); - longRes = (@Unsigned long) ((unsigned >> 0)); - longRes = (@Signed long) ((signed >>> 0)); - longRes = (@Signed long) ((signed >> 0)); - - // Test outside Java Specification shift ranges - // Cast to long. - // Now shift signedness matters again - longRes = (@Unsigned long) ((unsigned >>> 65)); - - // :: error: (shift.signed) - longRes = (@Unsigned long) ((unsigned >> 65)); - - // :: error: (shift.unsigned) - longRes = (@Signed long) ((signed >>> 65)); - longRes = (@Signed long) ((signed >> 65)); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) ((unsigned >>> 64)); - longRes = (@Unsigned long) ((unsigned >> 64)); - longRes = (@Signed long) ((signed >>> 64)); - longRes = (@Signed long) ((signed >> 64)); - longRes = (long) ((signed >> 64)); - } + public void CastedIntShifts(@Unsigned int unsigned, @Signed int signed) { + // Cast to byte. + @UnknownSignedness byte byteRes; + + // Shifting right by 23, the introduced bits are cast away + byteRes = (@Unsigned byte) (unsigned >>> 23); + byteRes = (@Unsigned byte) (unsigned >> 23); + byteRes = (@Signed byte) (signed >>> 23); + byteRes = (@Signed byte) (signed >> 23); + byteRes = (byte) (signed >> 23); + + // Shifting right by 24, the introduced bits are still cast away. + byteRes = (@Unsigned byte) (unsigned >>> 24); + byteRes = (@Unsigned byte) (unsigned >> 24); + byteRes = (@Signed byte) (signed >>> 24); + byteRes = (@Signed byte) (signed >> 24); + + // Shifting right by 25, now the MSB matters. + byteRes = (@Unsigned byte) (unsigned >>> 25); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) (unsigned >> 25); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) (signed >>> 25); + byteRes = (@Signed byte) (signed >> 25); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) (unsigned >>> 0); + byteRes = (@Unsigned byte) (unsigned >> 0); + byteRes = (@Signed byte) (signed >>> 0); + byteRes = (@Signed byte) (signed >> 0); + + // Cast to short. + @UnknownSignedness short shortRes; + + // Shifting right by 15, the introduced bits are cast away + shortRes = (@Unsigned short) (unsigned >>> 15); + shortRes = (@Unsigned short) (unsigned >> 15); + shortRes = (@Signed short) (signed >>> 15); + shortRes = (@Signed short) (signed >> 15); + + // Shifting right by 16, the introduced bits are still cast away. + shortRes = (@Unsigned short) (unsigned >>> 16); + shortRes = (@Unsigned short) (unsigned >> 16); + shortRes = (@Signed short) (signed >>> 16); + shortRes = (@Signed short) (signed >> 16); + + // Shifting right by 17, now the MSB matters. + shortRes = (@Unsigned short) (unsigned >>> 17); + + // :: error: (shift.signed) + shortRes = (@Unsigned short) (unsigned >> 17); + + // :: error: (shift.unsigned) + shortRes = (@Signed short) (signed >>> 17); + shortRes = (@Signed short) (signed >> 17); + + // Shifting right by zero should behave as assignment + shortRes = (@Unsigned short) (unsigned >>> 0); + shortRes = (@Unsigned short) (unsigned >> 0); + shortRes = (@Signed short) (signed >>> 0); + shortRes = (@Signed short) (signed >> 0); + + // Cast to int. + @UnknownSignedness int intRes; + + // Now shift signedness matters again + intRes = (@Unsigned int) (unsigned >>> 1); + + // :: error: (shift.signed) + intRes = (@Unsigned int) (unsigned >> 1); + + // :: error: (shift.unsigned) + intRes = (@Signed int) (signed >>> 1); + intRes = (@Signed int) (signed >> 1); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) (unsigned >>> 0); + intRes = (@Unsigned int) (unsigned >> 0); + intRes = (@Signed int) (signed >>> 0); + intRes = (@Signed int) (signed >> 0); + + // Cast to long. + @UnknownSignedness long longRes; + + // Now shift signedness matters again + longRes = (@Unsigned long) (unsigned >>> 1); + + // :: error: (shift.signed) + longRes = (@Unsigned long) (unsigned >> 1); + + // :: error: (shift.unsigned) + longRes = (@Signed long) (signed >>> 1); + longRes = (@Signed long) (signed >> 1); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) (unsigned >>> 0); + longRes = (@Unsigned long) (unsigned >> 0); + longRes = (@Signed long) (signed >>> 0); + longRes = (@Signed long) (signed >> 0); + + // Tests with double parenthesis (only byte and int) + + // Cast to byte. + // Shifting right by 23, the introduced bits are cast away + byteRes = (@Unsigned byte) ((unsigned >>> 23)); + byteRes = (@Unsigned byte) ((unsigned >> 23)); + byteRes = (@Signed byte) ((signed >>> 23)); + byteRes = (@Signed byte) ((signed >> 23)); + + // Shifting right by 24, the introduced bits are still cast away. + byteRes = (@Unsigned byte) ((unsigned >>> 24)); + byteRes = (@Unsigned byte) ((unsigned >> 24)); + byteRes = (@Signed byte) ((signed >>> 24)); + byteRes = (@Signed byte) ((signed >> 24)); + + // Shifting right by 25, now the MSB matters. + byteRes = (@Unsigned byte) ((unsigned >>> 25)); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) ((unsigned >> 25)); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) ((signed >>> 25)); + byteRes = (@Signed byte) ((signed >> 25)); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) ((unsigned >>> 0)); + byteRes = (@Unsigned byte) ((unsigned >> 0)); + byteRes = (@Signed byte) ((signed >>> 0)); + byteRes = (@Signed byte) ((signed >> 0)); + + // Cast to int. + // Now shift signedness matters again + intRes = (@Unsigned int) ((unsigned >>> 1)); + + // :: error: (shift.signed) + intRes = (@Unsigned int) ((unsigned >> 1)); + + // :: error: (shift.unsigned) + intRes = (@Signed int) ((signed >>> 1)); + intRes = (@Signed int) ((signed >> 1)); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) ((unsigned >>> 0)); + intRes = (@Unsigned int) ((unsigned >> 0)); + intRes = (@Signed int) ((signed >>> 0)); + intRes = (@Signed int) ((signed >> 0)); + + // Test outside Java Specification shift ranges + // Cast to int. + // Now shift signedness matters again + intRes = (@Unsigned int) ((unsigned >>> 33)); + + // :: error: (shift.signed) + intRes = (@Unsigned int) ((unsigned >> 33)); + + // :: error: (shift.unsigned) + intRes = (@Signed int) ((signed >>> 33)); + intRes = (@Signed int) ((signed >> 33)); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) ((unsigned >>> 32)); + intRes = (@Unsigned int) ((unsigned >> 32)); + intRes = (@Signed int) ((signed >>> 32)); + intRes = (@Signed int) ((signed >> 32)); + } + + public void CastedLongShifts(@Unsigned long unsigned, @Signed long signed) { + // Cast to byte. + @UnknownSignedness byte byteRes; + + // Shifting right by 55, the introduced bits are cast away + byteRes = (@Unsigned byte) (unsigned >>> 55); + byteRes = (@Unsigned byte) (unsigned >> 55); + byteRes = (@Signed byte) (signed >>> 55); + byteRes = (@Signed byte) (signed >> 55); + + // Shifting right by 56, the introduced bits are still cast away. + byteRes = (@Unsigned byte) (unsigned >>> 56); + byteRes = (@Unsigned byte) (unsigned >> 56); + byteRes = (@Signed byte) (signed >>> 56); + byteRes = (@Signed byte) (signed >> 56); + + // Shifting right by 57, now the MSB matters. + byteRes = (@Unsigned byte) (unsigned >>> 57); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) (unsigned >> 57); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) (signed >>> 57); + byteRes = (@Signed byte) (signed >> 57); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) (unsigned >>> 0); + byteRes = (@Unsigned byte) (unsigned >> 0); + byteRes = (@Signed byte) (signed >>> 0); + byteRes = (@Signed byte) (signed >> 0); + + // Cast to char. + char charRes; + + // Shifting right by 55, the introduced bits are cast away + charRes = (char) (unsigned >>> 55); + charRes = (char) (unsigned >> 55); + + // Shifting right by 56, the introduced bits are still cast away. + charRes = (char) (unsigned >>> 56); + charRes = (char) (unsigned >> 56); + + // Shifting right by 57, now the MSB matters. + charRes = (char) (unsigned >>> 57); + + // :: error: (shift.signed) + charRes = (char) (unsigned >> 57); + + // Shifting right by zero should behave as assignment + charRes = (char) (unsigned >>> 0); + charRes = (char) (unsigned >> 0); + + // Cast to short. + @UnknownSignedness short shortRes; + + // Shifting right by 47, the introduced bits are cast away + shortRes = (@Unsigned short) (unsigned >>> 47); + shortRes = (@Unsigned short) (unsigned >> 47); + shortRes = (@Signed short) (signed >>> 47); + shortRes = (@Signed short) (signed >> 47); + + // Shifting right by 48, the introduced bits are still cast away. + shortRes = (@Unsigned short) (unsigned >>> 48); + shortRes = (@Unsigned short) (unsigned >> 48); + shortRes = (@Signed short) (signed >>> 48); + shortRes = (@Signed short) (signed >> 48); + + // Shifting right by 49, now the MSB matters. + shortRes = (@Unsigned short) (unsigned >>> 49); + + // :: error: (shift.signed) + shortRes = (@Unsigned short) (unsigned >> 49); + + // :: error: (shift.unsigned) + shortRes = (@Signed short) (signed >>> 49); + shortRes = (@Signed short) (signed >> 49); + + // Shifting right by zero should behave as assignment + shortRes = (@Unsigned short) (unsigned >>> 0); + shortRes = (@Unsigned short) (unsigned >> 0); + shortRes = (@Signed short) (signed >>> 0); + shortRes = (@Signed short) (signed >> 0); + + // Cast to int. + @UnknownSignedness int intRes; + + // Shifting right by 31, the introduced bits are cast away + intRes = (@Unsigned int) (unsigned >>> 31); + intRes = (@Unsigned int) (unsigned >> 31); + intRes = (@Signed int) (signed >>> 31); + intRes = (@Signed int) (signed >> 31); + + // Shifting right by 32, the introduced bits are still cast away. + intRes = (@Unsigned int) (unsigned >>> 32); + intRes = (@Unsigned int) (unsigned >> 32); + intRes = (@Signed int) (signed >>> 32); + intRes = (@Signed int) (signed >> 32); + + // Shifting right by 33, now the MSB matters. + intRes = (@Unsigned int) (unsigned >>> 33); + + // :: error: (shift.signed) + intRes = (@Unsigned int) (unsigned >> 33); + + // :: error: (shift.unsigned) + intRes = (@Signed int) (signed >>> 33); + intRes = (@Signed int) (signed >> 33); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) (unsigned >>> 0); + intRes = (@Unsigned int) (unsigned >> 0); + intRes = (@Signed int) (signed >>> 0); + intRes = (@Signed int) (signed >> 0); + + // Cast to long. + @UnknownSignedness long longRes; + + // Now shift signedness matters again + longRes = (@Unsigned long) (unsigned >>> 1); + + // :: error: (shift.signed) + longRes = (@Unsigned long) (unsigned >> 1); + + // :: error: (shift.unsigned) + longRes = (@Signed long) (signed >>> 1); + longRes = (@Signed long) (signed >> 1); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) (unsigned >>> 0); + longRes = (@Unsigned long) (unsigned >> 0); + longRes = (@Signed long) (signed >>> 0); + longRes = (@Signed long) (signed >> 0); + + // Tests with double parenthesis (only byte and long) + + // Cast to byte. + // Shifting right by 55, the introduced bits are cast away + byteRes = (@Unsigned byte) ((unsigned >>> 55)); + byteRes = (@Unsigned byte) ((unsigned >> 55)); + byteRes = (@Signed byte) ((signed >>> 55)); + byteRes = (@Signed byte) ((signed >> 55)); + + // Shifting right by 56, the introduced bits are still cast away. + byteRes = (@Unsigned byte) ((unsigned >>> 56)); + byteRes = (@Unsigned byte) ((unsigned >> 56)); + byteRes = (@Signed byte) ((signed >>> 56)); + byteRes = (@Signed byte) ((signed >> 56)); + + // Shifting right by 9, now the MSB matters. + byteRes = (@Unsigned byte) ((unsigned >>> 57)); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) ((unsigned >> 57)); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) ((signed >>> 57)); + byteRes = (@Signed byte) ((signed >> 57)); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) ((unsigned >>> 0)); + byteRes = (@Unsigned byte) ((unsigned >> 0)); + byteRes = (@Signed byte) ((signed >>> 0)); + byteRes = (@Signed byte) ((signed >> 0)); + + // Cast to long. + // Now shift signedness matters again + longRes = (@Unsigned long) ((unsigned >>> 1)); + + // :: error: (shift.signed) + longRes = (@Unsigned long) ((unsigned >> 1)); + + // :: error: (shift.unsigned) + longRes = (@Signed long) ((signed >>> 1)); + longRes = (@Signed long) ((signed >> 1)); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) ((unsigned >>> 0)); + longRes = (@Unsigned long) ((unsigned >> 0)); + longRes = (@Signed long) ((signed >>> 0)); + longRes = (@Signed long) ((signed >> 0)); + + // Test outside Java Specification shift ranges + // Cast to long. + // Now shift signedness matters again + longRes = (@Unsigned long) ((unsigned >>> 65)); + + // :: error: (shift.signed) + longRes = (@Unsigned long) ((unsigned >> 65)); + + // :: error: (shift.unsigned) + longRes = (@Signed long) ((signed >>> 65)); + longRes = (@Signed long) ((signed >> 65)); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) ((unsigned >>> 64)); + longRes = (@Unsigned long) ((unsigned >> 64)); + longRes = (@Signed long) ((signed >>> 64)); + longRes = (@Signed long) ((signed >> 64)); + longRes = (long) ((signed >> 64)); + } } diff --git a/checker/tests/signedness/CharCast.java b/checker/tests/signedness/CharCast.java index 3d83353bfbc..5d17303ff68 100644 --- a/checker/tests/signedness/CharCast.java +++ b/checker/tests/signedness/CharCast.java @@ -2,28 +2,28 @@ public class CharCast { - void m(@SignedPositive int i) { - char c = (char) i; - } + void m(@SignedPositive int i) { + char c = (char) i; + } - void m1(short s) { - int x = s; - char c = (char) x; - } + void m1(short s) { + int x = s; + char c = (char) x; + } - void m2(int i) { - int x = (short) i; - char c = (char) x; - } + void m2(int i) { + int x = (short) i; + char c = (char) x; + } - void m3() { - int x = (short) 1; - char c = (char) x; - } + void m3() { + int x = (short) 1; + char c = (char) x; + } - void m4() { - short x = 1; - int y = x; - char c = (char) y; - } + void m4() { + short x = 1; + int y = x; + char c = (char) y; + } } diff --git a/checker/tests/signedness/CharCastedToInt.java b/checker/tests/signedness/CharCastedToInt.java index 25cc6924c23..25f99f10c68 100644 --- a/checker/tests/signedness/CharCastedToInt.java +++ b/checker/tests/signedness/CharCastedToInt.java @@ -1,8 +1,8 @@ public class CharCastedToInt { - int charCastToInt(char c) { - intParameter((int) c); - return (int) c; - } + int charCastToInt(char c) { + intParameter((int) c); + return (int) c; + } - void intParameter(int x) {} + void intParameter(int x) {} } diff --git a/checker/tests/signedness/CharComparisons.java b/checker/tests/signedness/CharComparisons.java index c44551f233a..860a5026b7f 100644 --- a/checker/tests/signedness/CharComparisons.java +++ b/checker/tests/signedness/CharComparisons.java @@ -1,30 +1,30 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class CharComparisons { - char c; - @Unsigned byte b; + char c; + @Unsigned byte b; - void unsignedComparison(char c, @Unsigned byte b) { - // :: error: (comparison.unsignedrhs) - boolean res = c > b; - // :: error: (comparison.unsignedrhs) - res = c >= b; - // :: error: (comparison.unsignedrhs) - res = c < b; - // :: error: (comparison.unsignedrhs) - res = c <= b; - res = c == b; - } + void unsignedComparison(char c, @Unsigned byte b) { + // :: error: (comparison.unsignedrhs) + boolean res = c > b; + // :: error: (comparison.unsignedrhs) + res = c >= b; + // :: error: (comparison.unsignedrhs) + res = c < b; + // :: error: (comparison.unsignedrhs) + res = c <= b; + res = c == b; + } - void unsignedComparisonFields() { - // :: error: (comparison.unsignedrhs) - boolean res = this.c > this.b; - // :: error: (comparison.unsignedrhs) - res = this.c >= this.b; - // :: error: (comparison.unsignedrhs) - res = this.c < this.b; - // :: error: (comparison.unsignedrhs) - res = this.c <= this.b; - res = this.c == this.b; - } + void unsignedComparisonFields() { + // :: error: (comparison.unsignedrhs) + boolean res = this.c > this.b; + // :: error: (comparison.unsignedrhs) + res = this.c >= this.b; + // :: error: (comparison.unsignedrhs) + res = this.c < this.b; + // :: error: (comparison.unsignedrhs) + res = this.c <= this.b; + res = this.c == this.b; + } } diff --git a/checker/tests/signedness/CharSignedObject.java b/checker/tests/signedness/CharSignedObject.java index e4fd6e79bfc..2d0ecbc9bbc 100644 --- a/checker/tests/signedness/CharSignedObject.java +++ b/checker/tests/signedness/CharSignedObject.java @@ -1,5 +1,5 @@ public final class CharSignedObject { - void m(int ttype) { - System.out.printf(" bad ttype %c%n", (char) ttype); - } + void m(int ttype) { + System.out.printf(" bad ttype %c%n", (char) ttype); + } } diff --git a/checker/tests/signedness/CharToFloat.java b/checker/tests/signedness/CharToFloat.java index 1caaea9f8f0..6a2a6b3bc7d 100644 --- a/checker/tests/signedness/CharToFloat.java +++ b/checker/tests/signedness/CharToFloat.java @@ -1,17 +1,17 @@ // Test case for issue #3711: https://github.com/typetools/checker-framework/issues/3711 public class CharToFloat { - void castCharacter(Object o) { - floatParameter((Character) o); - doubleParameter((Character) o); - } + void castCharacter(Object o) { + floatParameter((Character) o); + doubleParameter((Character) o); + } - void passCharacter(Character c) { - floatParameter(c); - doubleParameter(c); - } + void passCharacter(Character c) { + floatParameter(c); + doubleParameter(c); + } - void floatParameter(float f) {} + void floatParameter(float f) {} - void doubleParameter(double d) {} + void doubleParameter(double d) {} } diff --git a/checker/tests/signedness/CombinationIterator.java b/checker/tests/signedness/CombinationIterator.java index d37c8f8e2d1..0e5e411dafb 100644 --- a/checker/tests/signedness/CombinationIterator.java +++ b/checker/tests/signedness/CombinationIterator.java @@ -4,18 +4,18 @@ import java.util.List; public class CombinationIterator implements Iterator> { - public CombinationIterator(Collection> collectionsOfCandidates) { - ArrayList> listOfCollectionsOfCanditates = - new ArrayList<>(collectionsOfCandidates); - } + public CombinationIterator(Collection> collectionsOfCandidates) { + ArrayList> listOfCollectionsOfCanditates = + new ArrayList<>(collectionsOfCandidates); + } - @Override - public boolean hasNext() { - return false; - } + @Override + public boolean hasNext() { + return false; + } - @Override - public List next() { - return null; - } + @Override + public List next() { + return null; + } } diff --git a/checker/tests/signedness/CompareChars.java b/checker/tests/signedness/CompareChars.java index bbacec7b5bf..1ca055906fc 100644 --- a/checker/tests/signedness/CompareChars.java +++ b/checker/tests/signedness/CompareChars.java @@ -2,16 +2,16 @@ // https://github.com/typetools/checker-framework/issues/3669 public class CompareChars { - void compareUnsignedChars(char c2) { - char c1 = 'a'; - boolean res = c1 > c2; - res = c1 >= c2; - res = c1 < c2; - res = c1 <= c2; - } + void compareUnsignedChars(char c2) { + char c1 = 'a'; + boolean res = c1 > c2; + res = c1 >= c2; + res = c1 < c2; + res = c1 <= c2; + } - // Test case for issue #5166: https://tinyurl.com/cfissue/5166 - private static boolean isWhitespace(char c) { - return c <= '\u0020'; - } + // Test case for issue #5166: https://tinyurl.com/cfissue/5166 + private static boolean isWhitespace(char c) { + return c <= '\u0020'; + } } diff --git a/checker/tests/signedness/Comparisons.java b/checker/tests/signedness/Comparisons.java index 0baeee52f90..680c003cdcf 100644 --- a/checker/tests/signedness/Comparisons.java +++ b/checker/tests/signedness/Comparisons.java @@ -2,74 +2,74 @@ public class Comparisons { - public void ComparisonTest( - @Unsigned int unsigned, @PolySigned int polysigned, @UnknownSignedness int unknown) { + public void ComparisonTest( + @Unsigned int unsigned, @PolySigned int polysigned, @UnknownSignedness int unknown) { - boolean testRes; + boolean testRes; - // :: error: (comparison.unsignedlhs) - testRes = unsigned < unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned < unknown; - // :: error: (comparison.unsignedlhs) - testRes = polysigned < unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned < unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown < unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown < unsigned; - // :: error: (comparison.unsignedrhs) - testRes = unknown < polysigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown < polysigned; - // :: error: (comparison.unsignedlhs) - testRes = unsigned <= unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned <= unknown; - // :: error: (comparison.unsignedlhs) - testRes = polysigned <= unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned <= unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown <= unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown <= unsigned; - // :: error: (comparison.unsignedrhs) - testRes = unknown <= polysigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown <= polysigned; - // :: error: (comparison.unsignedlhs) - testRes = unsigned > unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned > unknown; - // :: error: (comparison.unsignedlhs) - testRes = polysigned > unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned > unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown > unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown > unsigned; - // :: error: (comparison.unsignedrhs) - testRes = unknown > polysigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown > polysigned; - // :: error: (comparison.unsignedlhs) - testRes = unsigned >= unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned >= unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown >= unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown >= unsigned; - // :: error: (comparison.unsignedlhs) - testRes = polysigned >= unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned >= unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown >= polysigned; - } + // :: error: (comparison.unsignedrhs) + testRes = unknown >= polysigned; + } - public void EqualsTest(@Unsigned int unsigned, @Signed int signed) { + public void EqualsTest(@Unsigned int unsigned, @Signed int signed) { - boolean testRes; + boolean testRes; - // :: error: (comparison.mixed.unsignedlhs) - testRes = unsigned == signed; + // :: error: (comparison.mixed.unsignedlhs) + testRes = unsigned == signed; - // :: error: (comparison.mixed.unsignedrhs) - testRes = signed == unsigned; + // :: error: (comparison.mixed.unsignedrhs) + testRes = signed == unsigned; - // :: error: (comparison.mixed.unsignedlhs) - testRes = unsigned != signed; + // :: error: (comparison.mixed.unsignedlhs) + testRes = unsigned != signed; - // :: error: (comparison.mixed.unsignedrhs) - testRes = signed != unsigned; - } + // :: error: (comparison.mixed.unsignedrhs) + testRes = signed != unsigned; + } } diff --git a/checker/tests/signedness/CompoundAssignmentsSignedness.java b/checker/tests/signedness/CompoundAssignmentsSignedness.java index 62dccb004a1..a1efead0989 100644 --- a/checker/tests/signedness/CompoundAssignmentsSignedness.java +++ b/checker/tests/signedness/CompoundAssignmentsSignedness.java @@ -2,165 +2,165 @@ public class CompoundAssignmentsSignedness { - public void DivModTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void DivModTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - // :: error: (compound.assignment.unsigned.expression) - unknown /= unsigned; + // :: error: (compound.assignment.unsigned.expression) + unknown /= unsigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - unsigned /= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + unsigned /= unknown; - // :: error: (compound.assignment.unsigned.variable) - unsigned /= constant; + // :: error: (compound.assignment.unsigned.variable) + unsigned /= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant /= unsigned; + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant /= unsigned; - // :: error: (compound.assignment.unsigned.expression) - unknown /= polysigned; + // :: error: (compound.assignment.unsigned.expression) + unknown /= polysigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned /= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned /= unknown; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned /= constant; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned /= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant /= polysigned; + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant /= polysigned; - // :: error: (compound.assignment.unsigned.expression) - unknown %= unsigned; + // :: error: (compound.assignment.unsigned.expression) + unknown %= unsigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - unsigned %= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + unsigned %= unknown; - // :: error: (compound.assignment.unsigned.expression) - unknown %= polysigned; + // :: error: (compound.assignment.unsigned.expression) + unknown %= polysigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned %= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned %= unknown; - // :: error: (compound.assignment.unsigned.variable) - unsigned %= constant; + // :: error: (compound.assignment.unsigned.variable) + unsigned %= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant %= unsigned; + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant %= unsigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned %= constant; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned %= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant %= polysigned; - } + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant %= polysigned; + } - public void SignedRightShiftTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void SignedRightShiftTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - // :: error: (compound.assignment.shift.signed) - unsigned >>= constant; + // :: error: (compound.assignment.shift.signed) + unsigned >>= constant; - constant >>= unsigned; + constant >>= unsigned; - // :: error: (compound.assignment.shift.signed) - polysigned >>= constant; + // :: error: (compound.assignment.shift.signed) + polysigned >>= constant; - constant >>= polysigned; + constant >>= polysigned; - // :: error: (compound.assignment.shift.signed) - unsigned >>= unknown; + // :: error: (compound.assignment.shift.signed) + unsigned >>= unknown; - unknown >>= unsigned; + unknown >>= unsigned; - // :: error: (compound.assignment.shift.signed) - polysigned >>= unknown; + // :: error: (compound.assignment.shift.signed) + polysigned >>= unknown; - unknown >>= polysigned; - } + unknown >>= polysigned; + } - public void UnsignedRightShiftTest( - @Signed int signed, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void UnsignedRightShiftTest( + @Signed int signed, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - // :: error: (compound.assignment.shift.unsigned) - signed >>>= constant; + // :: error: (compound.assignment.shift.unsigned) + signed >>>= constant; - constant >>>= signed; + constant >>>= signed; - // :: error: (compound.assignment.shift.unsigned) - signed >>>= unknown; + // :: error: (compound.assignment.shift.unsigned) + signed >>>= unknown; - unknown >>>= signed; + unknown >>>= signed; - // :: error: (compound.assignment.shift.unsigned) - polysigned >>>= constant; + // :: error: (compound.assignment.shift.unsigned) + polysigned >>>= constant; - constant >>>= polysigned; + constant >>>= polysigned; - // :: error: (compound.assignment.shift.unsigned) - polysigned >>>= unknown; + // :: error: (compound.assignment.shift.unsigned) + polysigned >>>= unknown; - unknown >>>= polysigned; - } + unknown >>>= polysigned; + } - public void LeftShiftTest( - @Signed int signed, - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void LeftShiftTest( + @Signed int signed, + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - signed <<= constant; + signed <<= constant; - constant <<= signed; + constant <<= signed; - signed <<= unknown; + signed <<= unknown; - unknown <<= signed; + unknown <<= signed; - unsigned <<= constant; + unsigned <<= constant; - constant <<= unsigned; + constant <<= unsigned; - unsigned <<= unknown; + unsigned <<= unknown; - unknown <<= unsigned; + unknown <<= unsigned; - polysigned <<= constant; + polysigned <<= constant; - constant <<= polysigned; + constant <<= polysigned; - polysigned <<= unknown; + polysigned <<= unknown; - unknown <<= polysigned; - } + unknown <<= polysigned; + } - public void mixedTest(@Unsigned int unsigned, @Signed int signed) { + public void mixedTest(@Unsigned int unsigned, @Signed int signed) { - // :: error: (compound.assignment.mixed.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - unsigned += signed; + // :: error: (compound.assignment.mixed.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + unsigned += signed; - // :: error: (compound.assignment.mixed.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - signed += unsigned; - } + // :: error: (compound.assignment.mixed.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + signed += unsigned; + } } diff --git a/checker/tests/signedness/CompoundAssignmentsSignedness2.java b/checker/tests/signedness/CompoundAssignmentsSignedness2.java index 1710a351631..cb49f0af424 100644 --- a/checker/tests/signedness/CompoundAssignmentsSignedness2.java +++ b/checker/tests/signedness/CompoundAssignmentsSignedness2.java @@ -1,63 +1,63 @@ // Test case for issue #3709: https://github.com/typetools/checker-framework/issues/3709 public class CompoundAssignmentsSignedness2 { - void additionWithCompoundAssignment(char c, int i1) { - i1 += c; - } + void additionWithCompoundAssignment(char c, int i1) { + i1 += c; + } - void additionWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 + c); - } + void additionWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 + c); + } - void additionWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 + c; - } + void additionWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 + c; + } - void subtractionWithCompoundAssignment(char c, int i1) { - i1 -= c; - } + void subtractionWithCompoundAssignment(char c, int i1) { + i1 -= c; + } - void subtractionWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 - c); - } + void subtractionWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 - c); + } - void subtractionWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 - c; - } + void subtractionWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 - c; + } - void multiplicationWithCompoundAssignment(char c, int i1) { - i1 *= c; - } + void multiplicationWithCompoundAssignment(char c, int i1) { + i1 *= c; + } - void multiplicationWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 * c); - } + void multiplicationWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 * c); + } - void multiplicationWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 * c; - } + void multiplicationWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 * c; + } - void divisionWithCompoundAssignment(char c, int i1) { - i1 /= c; - } + void divisionWithCompoundAssignment(char c, int i1) { + i1 /= c; + } - void divisionWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 / c); - } + void divisionWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 / c); + } - void divisionWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 / c; - } + void divisionWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 / c; + } - void modulusWithCompoundAssignment(char c, int i1) { - i1 %= c; - } + void modulusWithCompoundAssignment(char c, int i1) { + i1 %= c; + } - void modulusWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 % c); - } + void modulusWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 % c); + } - void modulusWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 % c; - } + void modulusWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 % c; + } } diff --git a/checker/tests/signedness/ConstantTests.java b/checker/tests/signedness/ConstantTests.java index 313ec42050a..55b50a13acb 100644 --- a/checker/tests/signedness/ConstantTests.java +++ b/checker/tests/signedness/ConstantTests.java @@ -5,29 +5,29 @@ public class ConstantTests { - @Unsigned int uint_negative_one = (@Unsigned int) -1; + @Unsigned int uint_negative_one = (@Unsigned int) -1; - @Unsigned int u1lit = 0xFFFFFFFE; // unsigned: 2^32 - 2, signed: -2 + @Unsigned int u1lit = 0xFFFFFFFE; // unsigned: 2^32 - 2, signed: -2 - void m() { + void m() { - int s = -2 / -1; + int s = -2 / -1; - @Unsigned int u = -1 / -2; + @Unsigned int u = -1 / -2; - int a = -1; - int b = -2; - int c = a / b; + int a = -1; + int b = -2; + int c = a / b; - @UnknownSignedness int x = 0xFFFFFFFE / 2; + @UnknownSignedness int x = 0xFFFFFFFE / 2; - int s1 = 0xFFFFFFFE; - @UnknownSignedness int y = s1 / 2; + int s1 = 0xFFFFFFFE; + @UnknownSignedness int y = s1 / 2; - // :: error: (operation.unsignedlhs) - @UnknownSignedness int z = (uint_negative_one) / -2; + // :: error: (operation.unsignedlhs) + @UnknownSignedness int z = (uint_negative_one) / -2; - // :: error: (operation.unsignedlhs) - @UnknownSignedness int w = u1lit / 2; - } + // :: error: (operation.unsignedlhs) + @UnknownSignedness int w = u1lit / 2; + } } diff --git a/checker/tests/signedness/DefaultsSignedness.java b/checker/tests/signedness/DefaultsSignedness.java index 8b19c1d08d1..34228e34f16 100644 --- a/checker/tests/signedness/DefaultsSignedness.java +++ b/checker/tests/signedness/DefaultsSignedness.java @@ -2,180 +2,180 @@ public class DefaultsSignedness { - public void ConstantTest() { + public void ConstantTest() { - // Test bytes with literal values - @SignednessGlb byte conByte; - @SignednessBottom byte botByte; + // Test bytes with literal values + @SignednessGlb byte conByte; + @SignednessBottom byte botByte; - byte testByte = 0; + byte testByte = 0; - conByte = testByte; + conByte = testByte; - // :: error: (assignment.type.incompatible) - botByte = testByte; + // :: error: (assignment.type.incompatible) + botByte = testByte; - // Test shorts with literal values - @SignednessGlb short conShort; - @SignednessBottom short botShort; + // Test shorts with literal values + @SignednessGlb short conShort; + @SignednessBottom short botShort; - short testShort = 128; + short testShort = 128; - conShort = testShort; + conShort = testShort; - // :: error: (assignment.type.incompatible) - botShort = testShort; + // :: error: (assignment.type.incompatible) + botShort = testShort; - // Test ints with literal values - @SignednessGlb int conInt; - @SignednessBottom int botInt; + // Test ints with literal values + @SignednessGlb int conInt; + @SignednessBottom int botInt; - int testInt = 32768; + int testInt = 32768; - conInt = testInt; + conInt = testInt; - // :: error: (assignment.type.incompatible) - botInt = testInt; + // :: error: (assignment.type.incompatible) + botInt = testInt; - // Test longs with literal values - @SignednessGlb long conLong; - @SignednessBottom long botLong; + // Test longs with literal values + @SignednessGlb long conLong; + @SignednessBottom long botLong; - long testLong = 2147483648L; + long testLong = 2147483648L; - conLong = testLong; + conLong = testLong; - // :: error: (assignment.type.incompatible) - botLong = testLong; - } + // :: error: (assignment.type.incompatible) + botLong = testLong; + } - public void SignedTest( - byte testByte, - short testShort, - int testInt, - long testLong, - float testFloat, - double testDouble, - char testChar, - boolean testBool, - Byte testBoxedByte, - Short testBoxedShort, - Integer testBoxedInteger, - Long testBoxedLong) { + public void SignedTest( + byte testByte, + short testShort, + int testInt, + long testLong, + float testFloat, + double testDouble, + char testChar, + boolean testBool, + Byte testBoxedByte, + Short testBoxedShort, + Integer testBoxedInteger, + Long testBoxedLong) { - // Test bytes - @Signed byte sinByte; - @SignednessGlb byte conByte; + // Test bytes + @Signed byte sinByte; + @SignednessGlb byte conByte; - sinByte = testByte; + sinByte = testByte; - // :: error: (assignment.type.incompatible) - conByte = testByte; + // :: error: (assignment.type.incompatible) + conByte = testByte; - // Test shorts - @Signed short sinShort; - @SignednessGlb short conShort; + // Test shorts + @Signed short sinShort; + @SignednessGlb short conShort; - sinShort = testShort; + sinShort = testShort; - // :: error: (assignment.type.incompatible) - conShort = testShort; + // :: error: (assignment.type.incompatible) + conShort = testShort; - // Test ints - @Signed int sinInt; - @SignednessGlb int conInt; + // Test ints + @Signed int sinInt; + @SignednessGlb int conInt; - sinInt = testInt; + sinInt = testInt; - // :: error: (assignment.type.incompatible) - conInt = testInt; + // :: error: (assignment.type.incompatible) + conInt = testInt; - // Test longs - @Signed long sinLong; - @SignednessGlb long conLong; + // Test longs + @Signed long sinLong; + @SignednessGlb long conLong; - sinLong = testLong; + sinLong = testLong; - // :: error: (assignment.type.incompatible) - conLong = testLong; + // :: error: (assignment.type.incompatible) + conLong = testLong; - // Test floats - // :: error: (anno.on.irrelevant) - @Signed float sinFloat; + // Test floats + // :: error: (anno.on.irrelevant) + @Signed float sinFloat; - sinFloat = testFloat; + sinFloat = testFloat; - // Test doubles - // :: error: (anno.on.irrelevant) - @Signed double sinDouble; + // Test doubles + // :: error: (anno.on.irrelevant) + @Signed double sinDouble; - sinDouble = testDouble; + sinDouble = testDouble; - /* - // Test boxed bytes - @Signed Byte sinBoxedByte; - @SignednessGlb Byte conBoxedByte; + /* + // Test boxed bytes + @Signed Byte sinBoxedByte; + @SignednessGlb Byte conBoxedByte; - sinBoxedByte = testBoxedByte; + sinBoxedByte = testBoxedByte; - //// :: error: (assignment.type.incompatible) - conBoxedByte = testBoxedByte; + //// :: error: (assignment.type.incompatible) + conBoxedByte = testBoxedByte; - // Test boxed shorts - @Signed Short sinBoxedShort; - @SignednessGlb Short conBoxedShort; + // Test boxed shorts + @Signed Short sinBoxedShort; + @SignednessGlb Short conBoxedShort; - sinBoxedShort = testBoxedShort; + sinBoxedShort = testBoxedShort; - //// :: error: (assignment.type.incompatible) - conBoxedShort = testBoxedShort; + //// :: error: (assignment.type.incompatible) + conBoxedShort = testBoxedShort; - // Test boxed Integers - @Signed Integer sinBoxedInteger; - @SignednessGlb Integer conBoxedInteger; + // Test boxed Integers + @Signed Integer sinBoxedInteger; + @SignednessGlb Integer conBoxedInteger; - sinBoxedInteger = testBoxedInteger; + sinBoxedInteger = testBoxedInteger; - //// :: error: (assignment.type.incompatible) - conBoxedInteger = testBoxedInteger; + //// :: error: (assignment.type.incompatible) + conBoxedInteger = testBoxedInteger; - // Test boxed Longs - @Signed Long sinBoxedLong; - @SignednessGlb Long conBoxedLong; + // Test boxed Longs + @Signed Long sinBoxedLong; + @SignednessGlb Long conBoxedLong; - sinBoxedLong = testBoxedLong; + sinBoxedLong = testBoxedLong; - //// :: error: (assignment.type.incompatible) - conBoxedLong = testBoxedLong; - */ - } + //// :: error: (assignment.type.incompatible) + conBoxedLong = testBoxedLong; + */ + } - public void SignednessBottom() { + public void SignednessBottom() { - @SignednessBottom Object botObj; + @SignednessBottom Object botObj; - Object testObj = null; + Object testObj = null; - botObj = testObj; - } + botObj = testObj; + } - public void UnknownSignedness(Object testObj, @Unsigned int unsigned, @Signed int signed) { + public void UnknownSignedness(Object testObj, @Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness Object unkObj; - @Unsigned Object unsinObj; + @UnknownSignedness Object unkObj; + @Unsigned Object unsinObj; - unkObj = testObj; + unkObj = testObj; - // :: error: (assignment.type.incompatible) - unsinObj = testObj; - } + // :: error: (assignment.type.incompatible) + unsinObj = testObj; + } - public void booleanProblem(@Unsigned int unsigned, @Signed int signed) { - boolean testBool = unsigned == 1 || signed > 1; - } + public void booleanProblem(@Unsigned int unsigned, @Signed int signed) { + boolean testBool = unsigned == 1 || signed > 1; + } - void method(Object[] obj_tags, int field_num) { - Object o = new DefaultsSignedness(); - obj_tags[field_num] = o; - } + void method(Object[] obj_tags, int field_num) { + Object o = new DefaultsSignedness(); + obj_tags[field_num] = o; + } } diff --git a/checker/tests/signedness/Desugar.java b/checker/tests/signedness/Desugar.java index b7315e47c17..4f6719c4949 100644 --- a/checker/tests/signedness/Desugar.java +++ b/checker/tests/signedness/Desugar.java @@ -1,32 +1,31 @@ +import java.util.Map; import org.checkerframework.checker.signedness.qual.PolySigned; import org.checkerframework.checker.signedness.qual.Signed; -import java.util.Map; - public class Desugar { - void test(int x) { - int i = getI(); - Integer box = i; - @Signed Integer boxy = box; - @Signed Integer box2 = method(box); - } + void test(int x) { + int i = getI(); + Integer box = i; + @Signed Integer boxy = box; + @Signed Integer box2 = method(box); + } - @PolySigned Integer method(@PolySigned Integer i) { - return i; - } + @PolySigned Integer method(@PolySigned Integer i) { + return i; + } - @Signed int getI() { - return 0; - } + @Signed int getI() { + return 0; + } - void test2(Map nonceMap, String nextInvo) { - int invoNonce = calcNonce(nextInvo); - Integer key = invoNonce; - String enterInvo = nonceMap.get(key); - } + void test2(Map nonceMap, String nextInvo) { + int invoNonce = calcNonce(nextInvo); + Integer key = invoNonce; + String enterInvo = nonceMap.get(key); + } - private @Signed int calcNonce(String invocation) { - return 0; - } + private @Signed int calcNonce(String invocation) { + return 0; + } } diff --git a/checker/tests/signedness/IrrelevantAnnotationsTest.java b/checker/tests/signedness/IrrelevantAnnotationsTest.java index bd701eb0400..b165a50223c 100644 --- a/checker/tests/signedness/IrrelevantAnnotationsTest.java +++ b/checker/tests/signedness/IrrelevantAnnotationsTest.java @@ -3,13 +3,13 @@ public final class IrrelevantAnnotationsTest { - // :: error: (anno.on.irrelevant) - @Signed Boolean b1; + // :: error: (anno.on.irrelevant) + @Signed Boolean b1; - // :: error: (anno.on.irrelevant) - @Unsigned Boolean b2; + // :: error: (anno.on.irrelevant) + @Unsigned Boolean b2; - @Signed Object o1; + @Signed Object o1; - @Unsigned Object o2; + @Unsigned Object o2; } diff --git a/checker/tests/signedness/Issue2482.java b/checker/tests/signedness/Issue2482.java index 8872f3c91e3..d2bdc72d5bc 100644 --- a/checker/tests/signedness/Issue2482.java +++ b/checker/tests/signedness/Issue2482.java @@ -1,68 +1,68 @@ public class Issue2482 { - void regularAssignment(byte[] b, int c) { - int a = b.length; - a = a + c; - } + void regularAssignment(byte[] b, int c) { + int a = b.length; + a = a + c; + } - void compoundAssignment(byte[] b, int c) { - int a = b.length; - a += c; - } + void compoundAssignment(byte[] b, int c) { + int a = b.length; + a += c; + } - void stringLenAdd(String s, int a) { - int len = s.length(); - len += a; - } + void stringLenAdd(String s, int a) { + int len = s.length(); + len += a; + } - void stringLenSub(String s, int a) { - int len = s.length(); - len -= a; - } + void stringLenSub(String s, int a) { + int len = s.length(); + len -= a; + } - void stringLenDiv(String s, int a) { - int len = s.length(); - len /= a; - } + void stringLenDiv(String s, int a) { + int len = s.length(); + len /= a; + } - void stringLenMul(String s, int a) { - int len = s.length(); - len *= a; - } + void stringLenMul(String s, int a) { + int len = s.length(); + len *= a; + } - void arrayLenAdd(byte[] b, int a) { - int len = b.length; - len += a; - } + void arrayLenAdd(byte[] b, int a) { + int len = b.length; + len += a; + } - void arrayLenSub(byte[] b, int a) { - int len = b.length; - len -= a; - } + void arrayLenSub(byte[] b, int a) { + int len = b.length; + len -= a; + } - void arrayLenDiv(byte[] b, int a) { - int len = b.length; - len /= a; - } + void arrayLenDiv(byte[] b, int a) { + int len = b.length; + len /= a; + } - void arrayLenMul(byte[] b, int a) { - int len = b.length; - len *= a; - } + void arrayLenMul(byte[] b, int a) { + int len = b.length; + len *= a; + } - void m3(int a) { + void m3(int a) { - int len = -1; // Negative - int len2 = 1; // Positive + int len = -1; // Negative + int len2 = 1; // Positive - len += a; - len -= a; - len /= a; - len *= a; + len += a; + len -= a; + len /= a; + len *= a; - len2 += a; - len2 -= a; - len2 /= a; - len2 *= a; - } + len2 += a; + len2 -= a; + len2 /= a; + len2 *= a; + } } diff --git a/checker/tests/signedness/Issue2483.java b/checker/tests/signedness/Issue2483.java index a0abf1dbd29..916ea70f6a8 100644 --- a/checker/tests/signedness/Issue2483.java +++ b/checker/tests/signedness/Issue2483.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.signedness.qual.*; public class Issue2483 { - void foo(String a, byte[] b) { - @Unsigned int len = a.length(); - @Unsigned int len2 = b.length; - } + void foo(String a, byte[] b) { + @Unsigned int len = a.length(); + @Unsigned int len2 = b.length; + } } diff --git a/checker/tests/signedness/Issue2534.java b/checker/tests/signedness/Issue2534.java index 742f7535bc6..9f1de834be6 100644 --- a/checker/tests/signedness/Issue2534.java +++ b/checker/tests/signedness/Issue2534.java @@ -3,25 +3,25 @@ public class Issue2534 { - @IntRange(from = 0, to = Integer.MAX_VALUE) int field = 3; + @IntRange(from = 0, to = Integer.MAX_VALUE) int field = 3; - @IntRange(from = 0, to = Integer.MAX_VALUE) int qwe() { - return 3; - } + @IntRange(from = 0, to = Integer.MAX_VALUE) int qwe() { + return 3; + } - void m1() { - @Unsigned int c = qwe(); - } + void m1() { + @Unsigned int c = qwe(); + } - void m2() { - @Unsigned int c = field; - } + void m2() { + @Unsigned int c = field; + } - void m3() { - @Unsigned int c = this.field; - } + void m3() { + @Unsigned int c = this.field; + } - void m4(@IntRange(from = 0, to = Integer.MAX_VALUE) int array[]) { - @Unsigned int c = array[0]; - } + void m4(@IntRange(from = 0, to = Integer.MAX_VALUE) int array[]) { + @Unsigned int c = array[0]; + } } diff --git a/checker/tests/signedness/Issue2543.java b/checker/tests/signedness/Issue2543.java index 704ca5b6b2d..d85e678097f 100644 --- a/checker/tests/signedness/Issue2543.java +++ b/checker/tests/signedness/Issue2543.java @@ -5,41 +5,40 @@ public class Issue2543 { - public static @PolySigned int rotateRightPart1(@PolySigned int i, int distance) { - // :: error: (shift.unsigned) - return i >>> distance; - } - - public static @PolySigned int rotateRightPart2(@PolySigned int i, int distance) { - return i << -distance; - } - - public static @PolySigned int rotateRight(@PolySigned int i, int distance) { - // :: error: (shift.unsigned) - return (i >>> distance) | (i << -distance); - } - - public static @Signed int rotateRightSignedPart1(@Signed int i, int distance) { - // :: error: (shift.unsigned) - return i >>> distance; - } - - public static @Signed int rotateRightSignedPart2(@Signed int i, int distance) { - return i << -distance; - } - - public static @Signed int rotateRightSigned(@Signed int i, int distance) { - // :: error: (shift.unsigned) - return (i >>> distance) | (i << -distance); - } - - public static @Unsigned int rotateRightUnsigned(@Unsigned int i, int distance) { - return (i >>> distance) | (i << -distance); - } - - public static @Unsigned int rotateRightUnknownSignedness( - @UnknownSignedness int i, int distance) { - // :: error: (return.type.incompatible) - return (i >>> distance) | (i << -distance); - } + public static @PolySigned int rotateRightPart1(@PolySigned int i, int distance) { + // :: error: (shift.unsigned) + return i >>> distance; + } + + public static @PolySigned int rotateRightPart2(@PolySigned int i, int distance) { + return i << -distance; + } + + public static @PolySigned int rotateRight(@PolySigned int i, int distance) { + // :: error: (shift.unsigned) + return (i >>> distance) | (i << -distance); + } + + public static @Signed int rotateRightSignedPart1(@Signed int i, int distance) { + // :: error: (shift.unsigned) + return i >>> distance; + } + + public static @Signed int rotateRightSignedPart2(@Signed int i, int distance) { + return i << -distance; + } + + public static @Signed int rotateRightSigned(@Signed int i, int distance) { + // :: error: (shift.unsigned) + return (i >>> distance) | (i << -distance); + } + + public static @Unsigned int rotateRightUnsigned(@Unsigned int i, int distance) { + return (i >>> distance) | (i << -distance); + } + + public static @Unsigned int rotateRightUnknownSignedness(@UnknownSignedness int i, int distance) { + // :: error: (return.type.incompatible) + return (i >>> distance) | (i << -distance); + } } diff --git a/checker/tests/signedness/Issue3710.java b/checker/tests/signedness/Issue3710.java index 138c32c95db..4293121fd8c 100644 --- a/checker/tests/signedness/Issue3710.java +++ b/checker/tests/signedness/Issue3710.java @@ -1,21 +1,21 @@ // Test case for issue #3711: https://github.com/typetools/checker-framework/issues/3710 public class Issue3710 { - int returnIntWithLocalVariable(char c) { - int i = c; - return i; - } + int returnIntWithLocalVariable(char c) { + int i = c; + return i; + } - long returnLongWithLocalVariable(char c) { - long l = c; - return l; - } + long returnLongWithLocalVariable(char c) { + long l = c; + return l; + } - int returnIntWithoutLocalVariable(char c) { - return c; - } + int returnIntWithoutLocalVariable(char c) { + return c; + } - long returnLongWithoutLocalVariable(char c) { - return c; - } + long returnLongWithoutLocalVariable(char c) { + return c; + } } diff --git a/checker/tests/signedness/Issue5256.java b/checker/tests/signedness/Issue5256.java index 280115c1a44..f598ccade7f 100644 --- a/checker/tests/signedness/Issue5256.java +++ b/checker/tests/signedness/Issue5256.java @@ -1,16 +1,16 @@ final class Issue5256 { - char c; + char c; - public int foo1() { - int x = 1; - x = x + (int) c; - return x; - } + public int foo1() { + int x = 1; + x = x + (int) c; + return x; + } - public int foo2() { - char c = 65535; - int x = 1; - x = x + (int) c; - return x; - } + public int foo2() { + char c = 65535; + int x = 1; + x = x + (int) c; + return x; + } } diff --git a/checker/tests/signedness/JdkConstantsTest.java b/checker/tests/signedness/JdkConstantsTest.java index 2321d2ca4fb..27cb52cf368 100644 --- a/checker/tests/signedness/JdkConstantsTest.java +++ b/checker/tests/signedness/JdkConstantsTest.java @@ -2,13 +2,13 @@ public class JdkConstantsTest { - static @PolySigned int integerMinValue(@PolySigned int value) { - // :: error: (return.type.incompatible) - return Integer.MIN_VALUE; - } + static @PolySigned int integerMinValue(@PolySigned int value) { + // :: error: (return.type.incompatible) + return Integer.MIN_VALUE; + } - static @PolySigned int flip(@PolySigned int value) { - // :: error: (return.type.incompatible) - return value ^ Integer.MIN_VALUE; - } + static @PolySigned int flip(@PolySigned int value) { + // :: error: (return.type.incompatible) + return value ^ Integer.MIN_VALUE; + } } diff --git a/checker/tests/signedness/LiteralCast.java b/checker/tests/signedness/LiteralCast.java index c1ec2552c68..f4372da8709 100644 --- a/checker/tests/signedness/LiteralCast.java +++ b/checker/tests/signedness/LiteralCast.java @@ -1,73 +1,72 @@ +import java.util.Arrays; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.Unsigned; import org.checkerframework.checker.units.qual.m; -import java.util.Arrays; - public class LiteralCast { - @Unsigned int u; - @Signed int s; + @Unsigned int u; + @Signed int s; - void m() { - testCompile(2); - // manifest literals are treated as @SignednessGlb - testCompile(-2); - // :: error: (argument.type.incompatible) - testCompile((@Signed int) 2); - testCompile((@Unsigned int) 2); - testCompile((int) 2); - testCompile((@m int) 2); + void m() { + testCompile(2); + // manifest literals are treated as @SignednessGlb + testCompile(-2); + // :: error: (argument.type.incompatible) + testCompile((@Signed int) 2); + testCompile((@Unsigned int) 2); + testCompile((int) 2); + testCompile((@m int) 2); - requireSigned((@Signed int) 2); - // :: error: (argument.type.incompatible) - requireSigned((@Unsigned int) 2); - requireSigned((int) 2); - requireSigned((@m int) 2); - // :: warning: (cast.unsafe) - requireSigned((@Signed int) u); - // :: error: (argument.type.incompatible) - requireSigned((@Unsigned int) u); - // :: error: (argument.type.incompatible) - requireSigned((int) u); - // :: error: (argument.type.incompatible) - requireSigned((@m int) u); - requireSigned((@Signed int) s); - // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) - requireSigned((@Unsigned int) s); - requireSigned((int) s); - requireSigned((@m int) s); + requireSigned((@Signed int) 2); + // :: error: (argument.type.incompatible) + requireSigned((@Unsigned int) 2); + requireSigned((int) 2); + requireSigned((@m int) 2); + // :: warning: (cast.unsafe) + requireSigned((@Signed int) u); + // :: error: (argument.type.incompatible) + requireSigned((@Unsigned int) u); + // :: error: (argument.type.incompatible) + requireSigned((int) u); + // :: error: (argument.type.incompatible) + requireSigned((@m int) u); + requireSigned((@Signed int) s); + // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) + requireSigned((@Unsigned int) s); + requireSigned((int) s); + requireSigned((@m int) s); - // :: error: (argument.type.incompatible) - requireUnsigned((@Signed int) 2); - requireUnsigned((@Unsigned int) 2); - requireUnsigned((int) 2); - requireUnsigned((@m int) 2); - // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) - requireUnsigned((@Signed int) u); - requireUnsigned((@Unsigned int) u); - requireUnsigned((int) u); - requireUnsigned((@m int) u); - // :: error: (argument.type.incompatible) - requireUnsigned((@Signed int) s); - // :: warning: (cast.unsafe) - requireUnsigned((@Unsigned int) s); - // :: error: (argument.type.incompatible) - requireUnsigned((int) s); - // :: error: (argument.type.incompatible) - requireUnsigned((@m int) s); - } + // :: error: (argument.type.incompatible) + requireUnsigned((@Signed int) 2); + requireUnsigned((@Unsigned int) 2); + requireUnsigned((int) 2); + requireUnsigned((@m int) 2); + // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) + requireUnsigned((@Signed int) u); + requireUnsigned((@Unsigned int) u); + requireUnsigned((int) u); + requireUnsigned((@m int) u); + // :: error: (argument.type.incompatible) + requireUnsigned((@Signed int) s); + // :: warning: (cast.unsafe) + requireUnsigned((@Unsigned int) s); + // :: error: (argument.type.incompatible) + requireUnsigned((int) s); + // :: error: (argument.type.incompatible) + requireUnsigned((@m int) s); + } - void requireSigned(@Signed int arg) {} + void requireSigned(@Signed int arg) {} - void requireUnsigned(@Unsigned int arg) {} + void requireUnsigned(@Unsigned int arg) {} - public static void testCompile(@Unsigned int x) { - @Unsigned int[] arr = {1, 2, 3, 4, 5, 56}; + public static void testCompile(@Unsigned int x) { + @Unsigned int[] arr = {1, 2, 3, 4, 5, 56}; - Arrays.fill(arr, x); - Arrays.fill(arr, (@Unsigned int) Integer.valueOf(-2)); - Arrays.fill(arr, Integer.valueOf(-2)); - Arrays.fill(arr, (@Unsigned int) Integer.valueOf(2)); - } + Arrays.fill(arr, x); + Arrays.fill(arr, (@Unsigned int) Integer.valueOf(-2)); + Arrays.fill(arr, Integer.valueOf(-2)); + Arrays.fill(arr, (@Unsigned int) Integer.valueOf(2)); + } } diff --git a/checker/tests/signedness/LocalVarDefaults.java b/checker/tests/signedness/LocalVarDefaults.java index 60efe1b1081..a42237b57b9 100644 --- a/checker/tests/signedness/LocalVarDefaults.java +++ b/checker/tests/signedness/LocalVarDefaults.java @@ -3,25 +3,25 @@ public class LocalVarDefaults { - void methodInt(@Unsigned int unsignedInt, @Signed int signedInt) { - int local = unsignedInt; - int local2 = signedInt; - } + void methodInt(@Unsigned int unsignedInt, @Signed int signedInt) { + int local = unsignedInt; + int local2 = signedInt; + } - // :: error: (anno.on.irrelevant) - void methodDouble(@Unsigned double unsigned, @Signed double signed) { - double local = unsigned; - double local2 = signed; - } + // :: error: (anno.on.irrelevant) + void methodDouble(@Unsigned double unsigned, @Signed double signed) { + double local = unsigned; + double local2 = signed; + } - void methodInteger(@Unsigned Integer unsignedInt, @Signed Integer signedInt) { - Integer local = unsignedInt; - Integer local2 = signedInt; - } + void methodInteger(@Unsigned Integer unsignedInt, @Signed Integer signedInt) { + Integer local = unsignedInt; + Integer local2 = signedInt; + } - // :: error: (anno.on.irrelevant) - void methodDoubleWrapper(@Unsigned Double unsigned, @Signed Double signed) { - Double local = unsigned; - Double local2 = signed; - } + // :: error: (anno.on.irrelevant) + void methodDoubleWrapper(@Unsigned Double unsigned, @Signed Double signed) { + Double local = unsigned; + Double local2 = signed; + } } diff --git a/checker/tests/signedness/LowerUpperBound.java b/checker/tests/signedness/LowerUpperBound.java index 33e52cb3a3a..a005aa268de 100644 --- a/checker/tests/signedness/LowerUpperBound.java +++ b/checker/tests/signedness/LowerUpperBound.java @@ -2,36 +2,36 @@ public class LowerUpperBound { - public void LowerUpperBoundTest( - @UnknownSignedness int unknown, - @Unsigned int unsigned, - @Signed int signed, - @SignednessGlb int constant) { + public void LowerUpperBoundTest( + @UnknownSignedness int unknown, + @Unsigned int unsigned, + @Signed int signed, + @SignednessGlb int constant) { - @UnknownSignedness int unkTest; - @Unsigned int unsTest; - @Signed int sinTest; - @SignednessGlb int conTest; - @SignednessBottom int botTest; + @UnknownSignedness int unkTest; + @Unsigned int unsTest; + @Signed int sinTest; + @SignednessGlb int conTest; + @SignednessBottom int botTest; - unkTest = unknown + unknown; + unkTest = unknown + unknown; - // :: error: (assignment.type.incompatible) - sinTest = unknown + unknown; + // :: error: (assignment.type.incompatible) + sinTest = unknown + unknown; - unkTest = unknown + signed; + unkTest = unknown + signed; - // :: error: (assignment.type.incompatible) - sinTest = unknown + signed; + // :: error: (assignment.type.incompatible) + sinTest = unknown + signed; - sinTest = signed + signed; + sinTest = signed + signed; - // :: error: (assignment.type.incompatible) - conTest = signed + signed; + // :: error: (assignment.type.incompatible) + conTest = signed + signed; - sinTest = signed + constant; + sinTest = signed + constant; - // :: error: (assignment.type.incompatible) - conTest = signed + constant; - } + // :: error: (assignment.type.incompatible) + conTest = signed + constant; + } } diff --git a/checker/tests/signedness/MaskedShifts.java b/checker/tests/signedness/MaskedShifts.java index e4609faaf9f..2bedaa100b0 100644 --- a/checker/tests/signedness/MaskedShifts.java +++ b/checker/tests/signedness/MaskedShifts.java @@ -2,364 +2,364 @@ public class MaskedShifts { - public void MaskedAndShifts(@Unsigned int unsigned, @Signed int signed) { + public void MaskedAndShifts(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // Use mask that renders the 9 MSB_s irrelevant. + // Use mask that renders the 9 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are masked away - testRes = (unsigned >>> 8) & 0x7FFFFF; - testRes = (unsigned >> 8) & 0x7FFFFF; - testRes = (signed >>> 8) & 0x7FFFFF; - testRes = (signed >> 8) & 0x7FFFFF; + // Shifting right by 8, the introduced bits are masked away + testRes = (unsigned >>> 8) & 0x7FFFFF; + testRes = (unsigned >> 8) & 0x7FFFFF; + testRes = (signed >>> 8) & 0x7FFFFF; + testRes = (signed >> 8) & 0x7FFFFF; - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = (unsigned >>> 8) & 0xFFFFFF; - testRes = (unsigned >> 8) & 0xFFFFFF; - testRes = (signed >>> 8) & 0xFFFFFF; - testRes = (signed >> 8) & 0xFFFFFF; + // Shifting right by 8, the introduced bits are still masked away. + testRes = (unsigned >>> 8) & 0xFFFFFF; + testRes = (unsigned >> 8) & 0xFFFFFF; + testRes = (signed >>> 8) & 0xFFFFFF; + testRes = (signed >> 8) & 0xFFFFFF; - // Use mask that renders the 7 MSB_s irrelevant + // Use mask that renders the 7 MSB_s irrelevant - // Now the right-most introduced bit matters - testRes = (unsigned >>> 8) & 0x1FFFFFF; + // Now the right-most introduced bit matters + testRes = (unsigned >>> 8) & 0x1FFFFFF; - // :: error: (shift.signed) - testRes = (unsigned >> 8) & 0x1FFFFFF; + // :: error: (shift.signed) + testRes = (unsigned >> 8) & 0x1FFFFFF; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) & 0x1FFFFFF; - testRes = (signed >> 8) & 0x1FFFFFF; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) & 0x1FFFFFF; + testRes = (signed >> 8) & 0x1FFFFFF; - // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s - // irrelevant. + // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s + // irrelevant. - // Now the left-most introduced bit matters - testRes = (unsigned >>> 8) & 0x90FFFFFF; + // Now the left-most introduced bit matters + testRes = (unsigned >>> 8) & 0x90FFFFFF; - // :: error: (shift.signed) - testRes = (unsigned >> 8) & 0x90FFFFFF; + // :: error: (shift.signed) + testRes = (unsigned >> 8) & 0x90FFFFFF; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) & 0x90FFFFFF; - testRes = (signed >> 8) & 0x90FFFFFF; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) & 0x90FFFFFF; + testRes = (signed >> 8) & 0x90FFFFFF; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = (unsigned >>> 8) & 0xFFFFFFFF; + testRes = (unsigned >>> 8) & 0xFFFFFFFF; - // :: error: (shift.signed) - testRes = (unsigned >> 8) & 0xFFFFFFFF; + // :: error: (shift.signed) + testRes = (unsigned >> 8) & 0xFFFFFFFF; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) & 0xFFFFFFFF; - testRes = (signed >> 8) & 0xFFFFFFFF; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) & 0xFFFFFFFF; + testRes = (signed >> 8) & 0xFFFFFFFF; - // Tests with no parenthesis (only 8 and 0 MSB_s) + // Tests with no parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 & 0xFFFFFF; - testRes = unsigned >> 8 & 0xFFFFFF; - testRes = signed >>> 8 & 0xFFFFFF; - testRes = signed >> 8 & 0xFFFFFF; + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 & 0xFFFFFF; + testRes = unsigned >> 8 & 0xFFFFFF; + testRes = signed >>> 8 & 0xFFFFFF; + testRes = signed >> 8 & 0xFFFFFF; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 & 0xFFFFFFFF; + testRes = unsigned >>> 8 & 0xFFFFFFFF; - // :: error: (shift.signed) - testRes = unsigned >> 8 & 0xFFFFFFFF; + // :: error: (shift.signed) + testRes = unsigned >> 8 & 0xFFFFFFFF; - // :: error: (shift.unsigned) - testRes = signed >>> 8 & 0xFFFFFFFF; - testRes = signed >> 8 & 0xFFFFFFFF; + // :: error: (shift.unsigned) + testRes = signed >>> 8 & 0xFFFFFFFF; + testRes = signed >> 8 & 0xFFFFFFFF; - // Tests with double parenthesis (only 8 and 0 MSB_s) + // Tests with double parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = ((unsigned >>> 8)) & 0xFFFFFF; - testRes = ((unsigned >> 8)) & 0xFFFFFF; - testRes = ((signed >>> 8)) & 0xFFFFFF; - testRes = ((signed >> 8)) & 0xFFFFFF; + // Shifting right by 8, the introduced bits are still masked away. + testRes = ((unsigned >>> 8)) & 0xFFFFFF; + testRes = ((unsigned >> 8)) & 0xFFFFFF; + testRes = ((signed >>> 8)) & 0xFFFFFF; + testRes = ((signed >> 8)) & 0xFFFFFF; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = ((unsigned >>> 8)) & 0xFFFFFFFF; + testRes = ((unsigned >>> 8)) & 0xFFFFFFFF; - // :: error: (shift.signed) - testRes = ((unsigned >> 8)) & 0xFFFFFFFF; + // :: error: (shift.signed) + testRes = ((unsigned >> 8)) & 0xFFFFFFFF; - // :: error: (shift.unsigned) - testRes = ((signed >>> 8)) & 0xFFFFFFFF; - testRes = ((signed >> 8)) & 0xFFFFFFFF; + // :: error: (shift.unsigned) + testRes = ((signed >>> 8)) & 0xFFFFFFFF; + testRes = ((signed >> 8)) & 0xFFFFFFFF; - // Tests shift on right (only 8 and 0 MSB_s) + // Tests shift on right (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = 0xFFFFFF & (unsigned >>> 8); - testRes = 0xFFFFFF & (unsigned >> 8); - testRes = 0xFFFFFF & (signed >>> 8); - testRes = 0xFFFFFF & (signed >> 8); + // Shifting right by 8, the introduced bits are still masked away. + testRes = 0xFFFFFF & (unsigned >>> 8); + testRes = 0xFFFFFF & (unsigned >> 8); + testRes = 0xFFFFFF & (signed >>> 8); + testRes = 0xFFFFFF & (signed >> 8); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = 0xFFFFFFFF & (unsigned >>> 8); + testRes = 0xFFFFFFFF & (unsigned >>> 8); - // :: error: (shift.signed) - testRes = 0xFFFFFFFF & (unsigned >> 8); + // :: error: (shift.signed) + testRes = 0xFFFFFFFF & (unsigned >> 8); - // :: error: (shift.unsigned) - testRes = 0xFFFFFFFF & (signed >>> 8); - testRes = 0xFFFFFFFF & (signed >> 8); + // :: error: (shift.unsigned) + testRes = 0xFFFFFFFF & (signed >>> 8); + testRes = 0xFFFFFFFF & (signed >> 8); - // Tests shift on right (only 8 and 0 MSB_s), with no parenthesis on right + // Tests shift on right (only 8 and 0 MSB_s), with no parenthesis on right - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = 0xFFFFFF & unsigned >>> 8; - testRes = 0xFFFFFF & unsigned >> 8; - testRes = 0xFFFFFF & signed >>> 8; - testRes = 0xFFFFFF & signed >> 8; + // Shifting right by 8, the introduced bits are still masked away. + testRes = 0xFFFFFF & unsigned >>> 8; + testRes = 0xFFFFFF & unsigned >> 8; + testRes = 0xFFFFFF & signed >>> 8; + testRes = 0xFFFFFF & signed >> 8; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = 0xFFFFFFFF & unsigned >>> 8; + testRes = 0xFFFFFFFF & unsigned >>> 8; - // :: error: (shift.signed) - testRes = 0xFFFFFFFF & unsigned >> 8; + // :: error: (shift.signed) + testRes = 0xFFFFFFFF & unsigned >> 8; - // :: error: (shift.unsigned) - testRes = 0xFFFFFFFF & signed >>> 8; - testRes = 0xFFFFFFFF & signed >> 8; + // :: error: (shift.unsigned) + testRes = 0xFFFFFFFF & signed >>> 8; + testRes = 0xFFFFFFFF & signed >> 8; - // Tests with parenthesis on mask (only 8 and 0 MSB_s) + // Tests with parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 & (0xFFFFFF); - testRes = unsigned >> 8 & (0xFFFFFF); - testRes = signed >>> 8 & (0xFFFFFF); - testRes = signed >> 8 & (0xFFFFFF); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 & (0xFFFFFF); + testRes = unsigned >> 8 & (0xFFFFFF); + testRes = signed >>> 8 & (0xFFFFFF); + testRes = signed >> 8 & (0xFFFFFF); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 & (0xFFFFFFFF); + testRes = unsigned >>> 8 & (0xFFFFFFFF); - // :: error: (shift.signed) - testRes = unsigned >> 8 & (0xFFFFFFFF); + // :: error: (shift.signed) + testRes = unsigned >> 8 & (0xFFFFFFFF); - // :: error: (shift.unsigned) - testRes = signed >>> 8 & (0xFFFFFFFF); - testRes = signed >> 8 & (0xFFFFFFFF); + // :: error: (shift.unsigned) + testRes = signed >>> 8 & (0xFFFFFFFF); + testRes = signed >> 8 & (0xFFFFFFFF); - // Tests with double parenthesis on mask (only 8 and 0 MSB_s) + // Tests with double parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 & ((0xFFFFFF)); - testRes = unsigned >> 8 & ((0xFFFFFF)); - testRes = signed >>> 8 & ((0xFFFFFF)); - testRes = signed >> 8 & ((0xFFFFFF)); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 & ((0xFFFFFF)); + testRes = unsigned >> 8 & ((0xFFFFFF)); + testRes = signed >>> 8 & ((0xFFFFFF)); + testRes = signed >> 8 & ((0xFFFFFF)); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 & ((0xFFFFFFFF)); + testRes = unsigned >>> 8 & ((0xFFFFFFFF)); - // :: error: (shift.signed) - testRes = unsigned >> 8 & ((0xFFFFFFFF)); + // :: error: (shift.signed) + testRes = unsigned >> 8 & ((0xFFFFFFFF)); - // :: error: (shift.unsigned) - testRes = signed >>> 8 & ((0xFFFFFFFF)); - testRes = signed >> 8 & ((0xFFFFFFFF)); - } + // :: error: (shift.unsigned) + testRes = signed >>> 8 & ((0xFFFFFFFF)); + testRes = signed >> 8 & ((0xFFFFFFFF)); + } - public void MaskedOrShifts(@Unsigned int unsigned, @Signed int signed) { + public void MaskedOrShifts(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // Use mask that renders the 9 MSB_s irrelevant. + // Use mask that renders the 9 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are masked away. - testRes = (unsigned >>> 8) | 0xFF800000; - testRes = (unsigned >> 8) | 0xFF800000; - testRes = (signed >>> 8) | 0xFF800000; - testRes = (signed >> 8) | 0xFF800000; + // Shifting right by 8, the introduced bits are masked away. + testRes = (unsigned >>> 8) | 0xFF800000; + testRes = (unsigned >> 8) | 0xFF800000; + testRes = (signed >>> 8) | 0xFF800000; + testRes = (signed >> 8) | 0xFF800000; - // Use mask that render ths 8 MSB_s irrelevant. + // Use mask that render ths 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = (unsigned >>> 8) | 0xFF000000; - testRes = (unsigned >> 8) | 0xFF000000; - testRes = (signed >>> 8) | 0xFF000000; - testRes = (signed >> 8) | 0xFF000000; + // Shifting right by 8, the introduced bits are still masked away. + testRes = (unsigned >>> 8) | 0xFF000000; + testRes = (unsigned >> 8) | 0xFF000000; + testRes = (signed >>> 8) | 0xFF000000; + testRes = (signed >> 8) | 0xFF000000; - // Use mask that renders the 7 MSB_s irrelevant. + // Use mask that renders the 7 MSB_s irrelevant. - // The right-most introduced bit now matters. - testRes = (unsigned >>> 8) | 0xFE000000; + // The right-most introduced bit now matters. + testRes = (unsigned >>> 8) | 0xFE000000; - // :: error: (shift.signed) - testRes = (unsigned >> 8) | 0xFE000000; + // :: error: (shift.signed) + testRes = (unsigned >> 8) | 0xFE000000; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) | 0xFE000000; - testRes = (signed >> 8) | 0xFE000000; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) | 0xFE000000; + testRes = (signed >> 8) | 0xFE000000; - // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s - // irrelevant. + // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s + // irrelevant. - // Now the left-most introduced bit matters - testRes = (unsigned >>> 8) | 0x8F000000; + // Now the left-most introduced bit matters + testRes = (unsigned >>> 8) | 0x8F000000; - // :: error: (shift.signed) - testRes = (unsigned >> 8) | 0x8F000000; + // :: error: (shift.signed) + testRes = (unsigned >> 8) | 0x8F000000; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) | 0x8F000000; - testRes = (signed >> 8) | 0x8F000000; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) | 0x8F000000; + testRes = (signed >> 8) | 0x8F000000; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = (unsigned >>> 8) | 0x0; + testRes = (unsigned >>> 8) | 0x0; - // :: error: (shift.signed) - testRes = (unsigned >> 8) | 0x0; + // :: error: (shift.signed) + testRes = (unsigned >> 8) | 0x0; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) | 0x0; - testRes = (signed >> 8) | 0x0; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) | 0x0; + testRes = (signed >> 8) | 0x0; - // Tests with no parenthesis (only 8 and 0 MSB_s) + // Tests with no parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 | 0xFF000000; - testRes = unsigned >> 8 | 0xFF000000; - testRes = signed >>> 8 | 0xFF000000; - testRes = signed >> 8 | 0xFF000000; + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 | 0xFF000000; + testRes = unsigned >> 8 | 0xFF000000; + testRes = signed >>> 8 | 0xFF000000; + testRes = signed >> 8 | 0xFF000000; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 | 0x0; + testRes = unsigned >>> 8 | 0x0; - // :: error: (shift.signed) - testRes = unsigned >> 8 | 0x0; + // :: error: (shift.signed) + testRes = unsigned >> 8 | 0x0; - // :: error: (shift.unsigned) - testRes = signed >>> 8 | 0x0; - testRes = signed >> 8 | 0x0; + // :: error: (shift.unsigned) + testRes = signed >>> 8 | 0x0; + testRes = signed >> 8 | 0x0; - // Tests with double parenthesis (only 8 and 0 MSB_s) + // Tests with double parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = ((unsigned >>> 8)) | 0xFF000000; - testRes = ((unsigned >> 8)) | 0xFF000000; - testRes = ((signed >>> 8)) | 0xFF000000; - testRes = ((signed >> 8)) | 0xFF000000; + // Shifting right by 8, the introduced bits are still masked away. + testRes = ((unsigned >>> 8)) | 0xFF000000; + testRes = ((unsigned >> 8)) | 0xFF000000; + testRes = ((signed >>> 8)) | 0xFF000000; + testRes = ((signed >> 8)) | 0xFF000000; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = ((unsigned >>> 8)) | 0x0; + testRes = ((unsigned >>> 8)) | 0x0; - // :: error: (shift.signed) - testRes = ((unsigned >> 8)) | 0x0; + // :: error: (shift.signed) + testRes = ((unsigned >> 8)) | 0x0; - // :: error: (shift.unsigned) - testRes = ((signed >>> 8)) | 0x0; - testRes = ((signed >> 8)) | 0x0; + // :: error: (shift.unsigned) + testRes = ((signed >>> 8)) | 0x0; + testRes = ((signed >> 8)) | 0x0; - // Tests shift on right (only 8 and 0 MSB_s) + // Tests shift on right (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = 0xFF000000 | (unsigned >>> 8); - testRes = 0xFF000000 | (unsigned >> 8); - testRes = 0xFF000000 | (signed >>> 8); - testRes = 0xFF000000 | (signed >> 8); + // Shifting right by 8, the introduced bits are still masked away. + testRes = 0xFF000000 | (unsigned >>> 8); + testRes = 0xFF000000 | (unsigned >> 8); + testRes = 0xFF000000 | (signed >>> 8); + testRes = 0xFF000000 | (signed >> 8); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = 0x0 | (unsigned >>> 8); + testRes = 0x0 | (unsigned >>> 8); - // :: error: (shift.signed) - testRes = 0x0 | (unsigned >> 8); + // :: error: (shift.signed) + testRes = 0x0 | (unsigned >> 8); - // :: error: (shift.unsigned) - testRes = 0x0 | (signed >>> 8); - testRes = 0x0 | (signed >> 8); + // :: error: (shift.unsigned) + testRes = 0x0 | (signed >>> 8); + testRes = 0x0 | (signed >> 8); - // Tests with parenthesis on mask (only 8 and 0 MSB_s) + // Tests with parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 | (0xFF000000); - testRes = unsigned >> 8 | (0xFF000000); - testRes = signed >>> 8 | (0xFF000000); - testRes = signed >> 8 | (0xFF000000); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 | (0xFF000000); + testRes = unsigned >> 8 | (0xFF000000); + testRes = signed >>> 8 | (0xFF000000); + testRes = signed >> 8 | (0xFF000000); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 | (0x0); + testRes = unsigned >>> 8 | (0x0); - // :: error: (shift.signed) - testRes = unsigned >> 8 | (0x0); + // :: error: (shift.signed) + testRes = unsigned >> 8 | (0x0); - // :: error: (shift.unsigned) - testRes = signed >>> 8 | (0x0); - testRes = signed >> 8 | (0x0); + // :: error: (shift.unsigned) + testRes = signed >>> 8 | (0x0); + testRes = signed >> 8 | (0x0); - // Tests with double parenthesis on mask (only 8 and 0 MSB_s) + // Tests with double parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 | ((0xFF000000)); - testRes = unsigned >> 8 | ((0xFF000000)); - testRes = signed >>> 8 | ((0xFF000000)); - testRes = signed >> 8 | ((0xFF000000)); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 | ((0xFF000000)); + testRes = unsigned >> 8 | ((0xFF000000)); + testRes = signed >>> 8 | ((0xFF000000)); + testRes = signed >> 8 | ((0xFF000000)); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 | ((0x0)); + testRes = unsigned >>> 8 | ((0x0)); - // :: error: (shift.signed) - testRes = unsigned >> 8 | ((0x0)); + // :: error: (shift.signed) + testRes = unsigned >> 8 | ((0x0)); - // :: error: (shift.unsigned) - testRes = signed >>> 8 | ((0x0)); - testRes = signed >> 8 | ((0x0)); - } + // :: error: (shift.unsigned) + testRes = signed >>> 8 | ((0x0)); + testRes = signed >> 8 | ((0x0)); + } - public void ZeroShiftTests(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + public void ZeroShiftTests(@Unsigned int unsigned, @Signed int signed) { + @UnknownSignedness int testRes; - // Tests shift by zero followed by "and" mask - testRes = (unsigned >>> 0) & 0xFFFFFFFF; - testRes = (unsigned >> 0) & 0xFFFFFFFF; - testRes = (signed >>> 0) & 0xFFFFFFFF; - testRes = (signed >> 0) & 0xFFFFFFFF; + // Tests shift by zero followed by "and" mask + testRes = (unsigned >>> 0) & 0xFFFFFFFF; + testRes = (unsigned >> 0) & 0xFFFFFFFF; + testRes = (signed >>> 0) & 0xFFFFFFFF; + testRes = (signed >> 0) & 0xFFFFFFFF; - // Tests shift by zero followed by "or" mask - testRes = (unsigned >>> 0) | 0x0; - testRes = (unsigned >> 0) | 0x0; - testRes = (signed >>> 0) | 0x0; - testRes = (signed >> 0) | 0x0; - } + // Tests shift by zero followed by "or" mask + testRes = (unsigned >>> 0) | 0x0; + testRes = (unsigned >> 0) | 0x0; + testRes = (signed >>> 0) | 0x0; + testRes = (signed >> 0) | 0x0; + } } diff --git a/checker/tests/signedness/ObjectCasts.java b/checker/tests/signedness/ObjectCasts.java index 9537a197421..d59dff45944 100644 --- a/checker/tests/signedness/ObjectCasts.java +++ b/checker/tests/signedness/ObjectCasts.java @@ -5,103 +5,103 @@ public class ObjectCasts { - Integer castObjectToInteger1(Object o) { - return (Integer) o; - } - - Integer castObjectToInteger2(@Unsigned Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - Integer castObjectToInteger3(@Signed Object o) { - return (Integer) o; - } - - @Signed Integer castObjectToInteger4(Object o) { - return (Integer) o; - } - - @Signed Integer castObjectToInteger5(@Unsigned Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - @Signed Integer castObjectToInteger6(@Signed Object o) { - return (Integer) o; - } - - @Unsigned Integer castObjectToInteger7(Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - @Unsigned Integer castObjectToInteger8(@Unsigned Object o) { - return (Integer) o; - } - - @Unsigned Integer castObjectToInteger9(@Signed Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - Object castIntegerToObject1(Integer o) { - return (Object) o; - } - - Object castIntegerToObject2(@Unsigned Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - Object castIntegerToObject3(@Signed Integer o) { - return (Object) o; - } - - @Signed Object castIntegerToObject4(Integer o) { - return (Object) o; - } - - @Signed Object castIntegerToObject5(@Unsigned Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - @Signed Object castIntegerToObject6(@Signed Integer o) { - return (Object) o; - } - - @Unsigned Object castIntegerToObject7(Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - @Unsigned Object castIntegerToObject8(@Unsigned Integer o) { - return (Object) o; - } - - @Unsigned Object castIntegerToObject9(@Signed Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - void castObjectToBoxedVariants() { - byte b1 = 1; - short s1 = 1; - int i1 = 1; - long l1 = 1; - Object[] obj = new Object[] {b1, s1, i1, l1}; - byteParameter((Byte) obj[0]); - shortParameter((Short) obj[1]); - integralParameter((Integer) obj[2]); - longParameter((Long) obj[3]); - } - - void byteParameter(byte b) {} - - void shortParameter(short s) {} - - void integralParameter(int i) {} - - void longParameter(long l) {} + Integer castObjectToInteger1(Object o) { + return (Integer) o; + } + + Integer castObjectToInteger2(@Unsigned Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + Integer castObjectToInteger3(@Signed Object o) { + return (Integer) o; + } + + @Signed Integer castObjectToInteger4(Object o) { + return (Integer) o; + } + + @Signed Integer castObjectToInteger5(@Unsigned Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + @Signed Integer castObjectToInteger6(@Signed Object o) { + return (Integer) o; + } + + @Unsigned Integer castObjectToInteger7(Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + @Unsigned Integer castObjectToInteger8(@Unsigned Object o) { + return (Integer) o; + } + + @Unsigned Integer castObjectToInteger9(@Signed Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + Object castIntegerToObject1(Integer o) { + return (Object) o; + } + + Object castIntegerToObject2(@Unsigned Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + Object castIntegerToObject3(@Signed Integer o) { + return (Object) o; + } + + @Signed Object castIntegerToObject4(Integer o) { + return (Object) o; + } + + @Signed Object castIntegerToObject5(@Unsigned Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + @Signed Object castIntegerToObject6(@Signed Integer o) { + return (Object) o; + } + + @Unsigned Object castIntegerToObject7(Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + @Unsigned Object castIntegerToObject8(@Unsigned Integer o) { + return (Object) o; + } + + @Unsigned Object castIntegerToObject9(@Signed Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + void castObjectToBoxedVariants() { + byte b1 = 1; + short s1 = 1; + int i1 = 1; + long l1 = 1; + Object[] obj = new Object[] {b1, s1, i1, l1}; + byteParameter((Byte) obj[0]); + shortParameter((Short) obj[1]); + integralParameter((Integer) obj[2]); + longParameter((Long) obj[3]); + } + + void byteParameter(byte b) {} + + void shortParameter(short s) {} + + void integralParameter(int i) {} + + void longParameter(long l) {} } diff --git a/checker/tests/signedness/Operations.java b/checker/tests/signedness/Operations.java index d5311b6dd95..8dd068dff58 100644 --- a/checker/tests/signedness/Operations.java +++ b/checker/tests/signedness/Operations.java @@ -2,77 +2,77 @@ public class Operations { - public void DivModTest(@Unsigned int unsigned) { + public void DivModTest(@Unsigned int unsigned) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (operation.unsignedlhs) - testRes = unsigned / 1; + // :: error: (operation.unsignedlhs) + testRes = unsigned / 1; - // :: error: (operation.unsignedrhs) - testRes = 1 / unsigned; + // :: error: (operation.unsignedrhs) + testRes = 1 / unsigned; - // :: error: (operation.unsignedlhs) - testRes = unsigned % 1; + // :: error: (operation.unsignedlhs) + testRes = unsigned % 1; - // :: error: (operation.unsignedrhs) - testRes = 1 % unsigned; - } + // :: error: (operation.unsignedrhs) + testRes = 1 % unsigned; + } - public void SignedRightShiftTest(@Unsigned int unsigned) { + public void SignedRightShiftTest(@Unsigned int unsigned) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (shift.signed) - testRes = unsigned >> 1; - } + // :: error: (shift.signed) + testRes = unsigned >> 1; + } - public void UnsignedRightShiftTest(@Signed int signed) { + public void UnsignedRightShiftTest(@Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (shift.unsigned) - testRes = signed >>> 1; - } + // :: error: (shift.unsigned) + testRes = signed >>> 1; + } - public void BinaryOperationTest(@Unsigned int unsigned, @Signed int signed) { + public void BinaryOperationTest(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned * signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned * signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed * unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed * unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned + signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned + signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed + unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed + unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned - signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned - signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed - unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed - unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned & signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned & signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed & unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed & unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned ^ signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned ^ signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed ^ unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed ^ unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned | signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned | signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed | unsigned; - } + // :: error: (operation.mixed.unsignedrhs) + testRes = signed | unsigned; + } } diff --git a/checker/tests/signedness/PolymorphicReturnType.java b/checker/tests/signedness/PolymorphicReturnType.java index b513a150fab..93ba4c48ceb 100644 --- a/checker/tests/signedness/PolymorphicReturnType.java +++ b/checker/tests/signedness/PolymorphicReturnType.java @@ -5,8 +5,8 @@ public class PolymorphicReturnType { - public @PolySigned byte get() { - // :: error: (return.type.incompatible) - return 0; - } + public @PolySigned byte get() { + // :: error: (return.type.incompatible) + return 0; + } } diff --git a/checker/tests/signedness/PrimitiveCasts.java b/checker/tests/signedness/PrimitiveCasts.java index 515fc2589ba..0b62c101d5b 100644 --- a/checker/tests/signedness/PrimitiveCasts.java +++ b/checker/tests/signedness/PrimitiveCasts.java @@ -2,39 +2,39 @@ public class PrimitiveCasts { - void shortToChar1(short s) { - char c = (char) s; - } - - // These are Java errors. - // void shortToChar2(short s) { - // char c = s; - // } - // char shortToChar3(short s) { - // return s; - // } - - void intToDouble1(@Unsigned int ui) { - double d = (double) ui; - } - - void intToDouble2(@Unsigned int ui) { - double d = ui; - } - - double intToDouble3(@Unsigned int ui) { - return ui; - } - - void shortToDouble1(@Unsigned short ui) { - double d = (double) ui; - } - - void shortToDouble2(@Unsigned short ui) { - double d = ui; - } - - double shortToDouble3(@Unsigned short ui) { - return ui; - } + void shortToChar1(short s) { + char c = (char) s; + } + + // These are Java errors. + // void shortToChar2(short s) { + // char c = s; + // } + // char shortToChar3(short s) { + // return s; + // } + + void intToDouble1(@Unsigned int ui) { + double d = (double) ui; + } + + void intToDouble2(@Unsigned int ui) { + double d = ui; + } + + double intToDouble3(@Unsigned int ui) { + return ui; + } + + void shortToDouble1(@Unsigned short ui) { + double d = (double) ui; + } + + void shortToDouble2(@Unsigned short ui) { + double d = ui; + } + + double shortToDouble3(@Unsigned short ui) { + return ui; + } } diff --git a/checker/tests/signedness/RestrictedPolymorphism.java b/checker/tests/signedness/RestrictedPolymorphism.java index 7658afc4f36..c86293fe684 100644 --- a/checker/tests/signedness/RestrictedPolymorphism.java +++ b/checker/tests/signedness/RestrictedPolymorphism.java @@ -4,17 +4,17 @@ public class RestrictedPolymorphism { - @Signed Number sn; - @Unsigned Number un; + @Signed Number sn; + @Unsigned Number un; - public void foo(@PolySigned Object a, @PolySigned Object b) {} + public void foo(@PolySigned Object a, @PolySigned Object b) {} - void client() { - foo(sn, sn); - // :: error: (argument.type.incompatible) - foo(sn, un); - // :: error: (argument.type.incompatible) - foo(un, sn); - foo(un, un); - } + void client() { + foo(sn, sn); + // :: error: (argument.type.incompatible) + foo(sn, un); + // :: error: (argument.type.incompatible) + foo(un, sn); + foo(un, un); + } } diff --git a/checker/tests/signedness/ShiftAndMask.java b/checker/tests/signedness/ShiftAndMask.java index f287dfafca7..95a7e1067ae 100644 --- a/checker/tests/signedness/ShiftAndMask.java +++ b/checker/tests/signedness/ShiftAndMask.java @@ -1,7 +1,7 @@ public class ShiftAndMask { - void m(long longValue) { - byte b1 = (byte) ((longValue >>> 32) & 0xFF); - byte b2 = (byte) ((longValue >>> 40) & 0xFF); - } + void m(long longValue) { + byte b1 = (byte) ((longValue >>> 32) & 0xFF); + byte b2 = (byte) ((longValue >>> 40) & 0xFF); + } } diff --git a/checker/tests/signedness/ShiftPropogation.java b/checker/tests/signedness/ShiftPropogation.java index dbaf3866ed4..f84ea5a42da 100644 --- a/checker/tests/signedness/ShiftPropogation.java +++ b/checker/tests/signedness/ShiftPropogation.java @@ -2,29 +2,29 @@ public class ShiftPropogation { - public void ShiftOperationTests(@Unsigned int unsigned, @Signed int signed) { - @Unsigned int uur = unsigned >>> unsigned; - @Unsigned int usr = unsigned >>> signed; + public void ShiftOperationTests(@Unsigned int unsigned, @Signed int signed) { + @Unsigned int uur = unsigned >>> unsigned; + @Unsigned int usr = unsigned >>> signed; - @Signed int sur = signed >> unsigned; - @Signed int ssr = signed >> signed; + @Signed int sur = signed >> unsigned; + @Signed int ssr = signed >> signed; - @Unsigned int uul = unsigned << unsigned; - @Unsigned int usl = unsigned << signed; - @Signed int sul = signed << unsigned; - @Signed int ssl = signed << signed; - } + @Unsigned int uul = unsigned << unsigned; + @Unsigned int usl = unsigned << signed; + @Signed int sul = signed << unsigned; + @Signed int ssl = signed << signed; + } - public void ShiftAssignmentTests(@Unsigned int unsigned, @Signed int signed) { - @Unsigned int uur = unsigned >>>= unsigned; - @Unsigned int usr = unsigned >>>= signed; + public void ShiftAssignmentTests(@Unsigned int unsigned, @Signed int signed) { + @Unsigned int uur = unsigned >>>= unsigned; + @Unsigned int usr = unsigned >>>= signed; - @Signed int sur = signed >>= unsigned; - @Signed int ssr = signed >>= signed; + @Signed int sur = signed >>= unsigned; + @Signed int ssr = signed >>= signed; - @Unsigned int uul = unsigned <<= unsigned; - @Unsigned int usl = unsigned <<= signed; - @Signed int sul = signed <<= unsigned; - @Signed int ssl = signed <<= signed; - } + @Unsigned int uul = unsigned <<= unsigned; + @Unsigned int usl = unsigned <<= signed; + @Signed int sul = signed <<= unsigned; + @Signed int ssl = signed <<= signed; + } } diff --git a/checker/tests/signedness/SignednessAnnotationError.java b/checker/tests/signedness/SignednessAnnotationError.java index 6ce74cfd5ac..6a0b4a09d17 100644 --- a/checker/tests/signedness/SignednessAnnotationError.java +++ b/checker/tests/signedness/SignednessAnnotationError.java @@ -1,11 +1,10 @@ -import org.checkerframework.checker.nullness.qual.KeyFor; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.KeyFor; public class SignednessAnnotationError { - void test() { - List<@KeyFor("hello") String> s = new ArrayList<>(); - @KeyFor("hell") Object o = new Object(); - } + void test() { + List<@KeyFor("hello") String> s = new ArrayList<>(); + @KeyFor("hell") Object o = new Object(); + } } diff --git a/checker/tests/signedness/SignednessAssignments.java b/checker/tests/signedness/SignednessAssignments.java index 73d32d18cd7..e8c726b8301 100644 --- a/checker/tests/signedness/SignednessAssignments.java +++ b/checker/tests/signedness/SignednessAssignments.java @@ -4,133 +4,133 @@ public class SignednessAssignments { - @Signed byte sb; - @Unsigned byte ub; - @Signed Byte sB; - @Unsigned Byte uB; - - @Signed short ss; - @Unsigned short us; - @Signed Short sS; - @Unsigned Short uS; - - @Signed int si; - @Unsigned int ui; - @Signed Integer sI; - @Unsigned Integer uI; - - @Signed long sl; - @Unsigned long ul; - @Signed Long sL; - @Unsigned Long uL; - - void assignmentsByte() { - @Signed byte i1 = sb; - @Unsigned byte i2 = ub; - @Signed byte i3 = sB; - @Unsigned byte i4 = uB; - - @Signed Byte i91 = sb; - @Unsigned Byte i92 = ub; - @Signed Byte i93 = sB; - @Unsigned Byte i94 = uB; - } - - void assignmentsShort() { - // :: error: (assignment.type.incompatible) - @SignedPositive short i1 = sb; - // :: error: (assignment.type.incompatible) - @SignedPositive short i2 = ub; - // :: error: (assignment.type.incompatible) - @SignedPositive short i3 = sB; - // :: error: (assignment.type.incompatible) - @SignedPositive short i4 = uB; - - @Signed short i9 = ss; - @Unsigned short i10 = us; - @Signed short i11 = sS; - @Unsigned short i12 = uS; - - @Signed Short i91 = ss; - @Unsigned Short i92 = us; - @Signed Short i93 = sS; - @Unsigned Short i94 = uS; - } - - void assignmentsChar() { - // These are commented out because they are Java errors. - // @Unsigned char i2 = ub; - // @Unsigned char i4 = uB; - // @Unsigned char i10 = us; - // @Unsigned char i12 = uS; - } - - void assignmentsInt() { - // :: error: (assignment.type.incompatible) - @SignedPositive int i1 = sb; - // :: error: (assignment.type.incompatible) - @SignedPositive int i2 = ub; - // :: error: (assignment.type.incompatible) - @SignedPositive int i3 = sB; - // :: error: (assignment.type.incompatible) - @SignedPositive int i4 = uB; - - // :: error: (assignment.type.incompatible) - @SignedPositive int i9 = ss; - // :: error: (assignment.type.incompatible) - @SignedPositive int i10 = us; - // :: error: (assignment.type.incompatible) - @SignedPositive int i11 = sS; - // :: error: (assignment.type.incompatible) - @SignedPositive int i12 = uS; - - @Signed int i13 = si; - @Unsigned int i14 = ui; - @Signed int i15 = sI; - @Unsigned int i16 = uI; - - @Signed Integer i91 = si; - @Unsigned Integer i92 = ui; - @Signed Integer i93 = sI; - @Unsigned Integer i94 = uI; - } - - void assignmentsLong() { - // :: error: (assignment.type.incompatible) - @SignedPositive long i1 = sb; - // :: error: (assignment.type.incompatible) - @SignedPositive long i2 = ub; - // :: error: (assignment.type.incompatible) - @SignedPositive long i3 = sB; - // :: error: (assignment.type.incompatible) - @SignedPositive long i4 = uB; - - // :: error: (assignment.type.incompatible) - @SignedPositive long i9 = ss; - // :: error: (assignment.type.incompatible) - @SignedPositive long i10 = us; - // :: error: (assignment.type.incompatible) - @SignedPositive long i11 = sS; - // :: error: (assignment.type.incompatible) - @SignedPositive long i12 = uS; - - // :: error: (assignment.type.incompatible) - @SignedPositive long i13 = si; - // :: error: (assignment.type.incompatible) - @SignedPositive long i14 = ui; - // :: error: (assignment.type.incompatible) - @SignedPositive long i15 = sI; - // :: error: (assignment.type.incompatible) - @SignedPositive long i16 = uI; - - @Signed long i17 = sl; - @Unsigned long i18 = ul; - @Signed long i19 = sL; - @Unsigned long i20 = uL; - - @Signed Long i91 = sl; - @Unsigned Long i92 = ul; - @Signed Long i93 = sL; - @Unsigned Long i94 = uL; - } + @Signed byte sb; + @Unsigned byte ub; + @Signed Byte sB; + @Unsigned Byte uB; + + @Signed short ss; + @Unsigned short us; + @Signed Short sS; + @Unsigned Short uS; + + @Signed int si; + @Unsigned int ui; + @Signed Integer sI; + @Unsigned Integer uI; + + @Signed long sl; + @Unsigned long ul; + @Signed Long sL; + @Unsigned Long uL; + + void assignmentsByte() { + @Signed byte i1 = sb; + @Unsigned byte i2 = ub; + @Signed byte i3 = sB; + @Unsigned byte i4 = uB; + + @Signed Byte i91 = sb; + @Unsigned Byte i92 = ub; + @Signed Byte i93 = sB; + @Unsigned Byte i94 = uB; + } + + void assignmentsShort() { + // :: error: (assignment.type.incompatible) + @SignedPositive short i1 = sb; + // :: error: (assignment.type.incompatible) + @SignedPositive short i2 = ub; + // :: error: (assignment.type.incompatible) + @SignedPositive short i3 = sB; + // :: error: (assignment.type.incompatible) + @SignedPositive short i4 = uB; + + @Signed short i9 = ss; + @Unsigned short i10 = us; + @Signed short i11 = sS; + @Unsigned short i12 = uS; + + @Signed Short i91 = ss; + @Unsigned Short i92 = us; + @Signed Short i93 = sS; + @Unsigned Short i94 = uS; + } + + void assignmentsChar() { + // These are commented out because they are Java errors. + // @Unsigned char i2 = ub; + // @Unsigned char i4 = uB; + // @Unsigned char i10 = us; + // @Unsigned char i12 = uS; + } + + void assignmentsInt() { + // :: error: (assignment.type.incompatible) + @SignedPositive int i1 = sb; + // :: error: (assignment.type.incompatible) + @SignedPositive int i2 = ub; + // :: error: (assignment.type.incompatible) + @SignedPositive int i3 = sB; + // :: error: (assignment.type.incompatible) + @SignedPositive int i4 = uB; + + // :: error: (assignment.type.incompatible) + @SignedPositive int i9 = ss; + // :: error: (assignment.type.incompatible) + @SignedPositive int i10 = us; + // :: error: (assignment.type.incompatible) + @SignedPositive int i11 = sS; + // :: error: (assignment.type.incompatible) + @SignedPositive int i12 = uS; + + @Signed int i13 = si; + @Unsigned int i14 = ui; + @Signed int i15 = sI; + @Unsigned int i16 = uI; + + @Signed Integer i91 = si; + @Unsigned Integer i92 = ui; + @Signed Integer i93 = sI; + @Unsigned Integer i94 = uI; + } + + void assignmentsLong() { + // :: error: (assignment.type.incompatible) + @SignedPositive long i1 = sb; + // :: error: (assignment.type.incompatible) + @SignedPositive long i2 = ub; + // :: error: (assignment.type.incompatible) + @SignedPositive long i3 = sB; + // :: error: (assignment.type.incompatible) + @SignedPositive long i4 = uB; + + // :: error: (assignment.type.incompatible) + @SignedPositive long i9 = ss; + // :: error: (assignment.type.incompatible) + @SignedPositive long i10 = us; + // :: error: (assignment.type.incompatible) + @SignedPositive long i11 = sS; + // :: error: (assignment.type.incompatible) + @SignedPositive long i12 = uS; + + // :: error: (assignment.type.incompatible) + @SignedPositive long i13 = si; + // :: error: (assignment.type.incompatible) + @SignedPositive long i14 = ui; + // :: error: (assignment.type.incompatible) + @SignedPositive long i15 = sI; + // :: error: (assignment.type.incompatible) + @SignedPositive long i16 = uI; + + @Signed long i17 = sl; + @Unsigned long i18 = ul; + @Signed long i19 = sL; + @Unsigned long i20 = uL; + + @Signed Long i91 = sl; + @Unsigned Long i92 = ul; + @Signed Long i93 = sL; + @Unsigned Long i94 = uL; + } } diff --git a/checker/tests/signedness/SignednessCast.java b/checker/tests/signedness/SignednessCast.java index 36f2aef1f5e..f3b23993760 100644 --- a/checker/tests/signedness/SignednessCast.java +++ b/checker/tests/signedness/SignednessCast.java @@ -2,38 +2,38 @@ @SuppressWarnings("deprecation") // newInstance is deprecated. public class SignednessCast { - static class Instruction {} + static class Instruction {} - public Instruction createCast(String name) { - Instruction i = null; - try { - i = (Instruction) java.lang.Class.forName(name).newInstance(); - } catch (final Exception e) { - throw new IllegalArgumentException("Could not find instruction: " + name, e); - } - return i; + public Instruction createCast(String name) { + Instruction i = null; + try { + i = (Instruction) java.lang.Class.forName(name).newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException("Could not find instruction: " + name, e); } + return i; + } - static class SerializableInstruction implements Serializable {} + static class SerializableInstruction implements Serializable {} - SerializableInstruction other(String name) { - SerializableInstruction i = null; - try { - i = (SerializableInstruction) java.lang.Class.forName(name).newInstance(); - } catch (final Exception e) { - throw new IllegalArgumentException("Could not find instruction: " + name, e); - } - return i; + SerializableInstruction other(String name) { + SerializableInstruction i = null; + try { + i = (SerializableInstruction) java.lang.Class.forName(name).newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException("Could not find instruction: " + name, e); } + return i; + } - Serializable maybeNumber(String name) { - Serializable i = null; - try { - i = (Serializable) java.lang.Class.forName(name).newInstance(); - } catch (final Exception e) { - throw new IllegalArgumentException("Could not find instruction: " + name, e); - } - // :: error: (return.type.incompatible) - return i; + Serializable maybeNumber(String name) { + Serializable i = null; + try { + i = (Serializable) java.lang.Class.forName(name).newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException("Could not find instruction: " + name, e); } + // :: error: (return.type.incompatible) + return i; + } } diff --git a/checker/tests/signedness/SignednessEquals.java b/checker/tests/signedness/SignednessEquals.java index fc34b7ed3bf..5709ac70f6e 100644 --- a/checker/tests/signedness/SignednessEquals.java +++ b/checker/tests/signedness/SignednessEquals.java @@ -1,97 +1,96 @@ +import java.util.Objects; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.Unsigned; -import java.util.Objects; - public class SignednessEquals { - @Signed Object so; - @Unsigned Object uo; - - @Signed Number sn; - @Unsigned Number un; - - @Signed byte sb; - @Unsigned byte ub; - @Signed Byte sB; - @Unsigned Byte uB; - - char uc; - Character uC; - - @Signed short ss; - @Unsigned short us; - @Signed Short sS; - @Unsigned Short uS; - - @Signed int si; - @Unsigned int ui; - @Signed Integer sI; - @Unsigned Integer uI; - - @Signed long sl; - @Unsigned long ul; - @Signed Long sL; - @Unsigned Long uL; - - void nonIntegralEquality() { - so.equals(sn); - // :: error: (comparison.mixed.unsignedrhs) - so.equals(un); - // :: error: (comparison.mixed.unsignedlhs) - uo.equals(sn); - uo.equals(un); - - Objects.equals(so, sn); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(so, un); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uo, sn); - Objects.equals(uo, un); - - sI.equals(sn); - // :: error: (comparison.mixed.unsignedrhs) - sI.equals(un); - // :: error: (comparison.mixed.unsignedlhs) - uI.equals(sn); - uI.equals(un); - - Objects.equals(sI, sn); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(sI, un); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uI, sn); - Objects.equals(uI, un); - } - - void integralEquality() { - - so.equals(sS); - // :: error: (comparison.mixed.unsignedrhs) - so.equals(uS); - // :: error: (comparison.mixed.unsignedlhs) - uo.equals(sS); - uo.equals(uS); - - Objects.equals(so, sS); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(so, uS); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uo, sS); - Objects.equals(uo, uS); - - sB.equals(sS); - // :: error: (comparison.mixed.unsignedrhs) - sB.equals(uS); - // :: error: (comparison.mixed.unsignedlhs) - uB.equals(sS); - uB.equals(uS); - - Objects.equals(sB, sS); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(sB, uS); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uB, sS); - Objects.equals(uB, uS); - } + @Signed Object so; + @Unsigned Object uo; + + @Signed Number sn; + @Unsigned Number un; + + @Signed byte sb; + @Unsigned byte ub; + @Signed Byte sB; + @Unsigned Byte uB; + + char uc; + Character uC; + + @Signed short ss; + @Unsigned short us; + @Signed Short sS; + @Unsigned Short uS; + + @Signed int si; + @Unsigned int ui; + @Signed Integer sI; + @Unsigned Integer uI; + + @Signed long sl; + @Unsigned long ul; + @Signed Long sL; + @Unsigned Long uL; + + void nonIntegralEquality() { + so.equals(sn); + // :: error: (comparison.mixed.unsignedrhs) + so.equals(un); + // :: error: (comparison.mixed.unsignedlhs) + uo.equals(sn); + uo.equals(un); + + Objects.equals(so, sn); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(so, un); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uo, sn); + Objects.equals(uo, un); + + sI.equals(sn); + // :: error: (comparison.mixed.unsignedrhs) + sI.equals(un); + // :: error: (comparison.mixed.unsignedlhs) + uI.equals(sn); + uI.equals(un); + + Objects.equals(sI, sn); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(sI, un); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uI, sn); + Objects.equals(uI, un); + } + + void integralEquality() { + + so.equals(sS); + // :: error: (comparison.mixed.unsignedrhs) + so.equals(uS); + // :: error: (comparison.mixed.unsignedlhs) + uo.equals(sS); + uo.equals(uS); + + Objects.equals(so, sS); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(so, uS); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uo, sS); + Objects.equals(uo, uS); + + sB.equals(sS); + // :: error: (comparison.mixed.unsignedrhs) + sB.equals(uS); + // :: error: (comparison.mixed.unsignedlhs) + uB.equals(sS); + uB.equals(uS); + + Objects.equals(sB, sS); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(sB, uS); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uB, sS); + Objects.equals(uB, uS); + } } diff --git a/checker/tests/signedness/SignednessManualExample.java b/checker/tests/signedness/SignednessManualExample.java index e713a07d5b7..6f45522c494 100644 --- a/checker/tests/signedness/SignednessManualExample.java +++ b/checker/tests/signedness/SignednessManualExample.java @@ -4,17 +4,40 @@ public class SignednessManualExample { + int s1 = -2; + int s2 = -1; + + @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2 + @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1 + + void m() { + int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 + // :: error: (operation.unsignedlhs) + int x = u1 / u2; // ERROR: result is 2, which is incorrect for (2^32 - 2) / (2^32 - 1) + } + + int s3 = -1; + int s4 = 5; + + @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1 + @Unsigned int u4 = 5; + + void m2() { + int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 + // :: error: (operation.unsignedlhs) + int z = u3 % u4; // ERROR: result is -1, which is incorrect for (2^32 - 1) % 5 = 2 + } + + void useLocalVariables() { + int s1 = -2; int s2 = -1; @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2 @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1 - void m() { - int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 - // :: error: (operation.unsignedlhs) - int x = u1 / u2; // ERROR: result is 2, which is incorrect for (2^32 - 2) / (2^32 - 1) - } + int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 + int x = u1 / u2; // OK; computation over constants, interpreted as signed; result is signed int s3 = -1; int s4 = 5; @@ -22,30 +45,7 @@ void m() { @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1 @Unsigned int u4 = 5; - void m2() { - int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 - // :: error: (operation.unsignedlhs) - int z = u3 % u4; // ERROR: result is -1, which is incorrect for (2^32 - 1) % 5 = 2 - } - - void useLocalVariables() { - - int s1 = -2; - int s2 = -1; - - @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2 - @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1 - - int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 - int x = u1 / u2; // OK; computation over constants, interpreted as signed; result is signed - - int s3 = -1; - int s4 = 5; - - @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1 - @Unsigned int u4 = 5; - - int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 - int z = u3 % u4; // OK; computation over constants, interpreted as signed; result is signed - } + int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 + int z = u3 % u4; // OK; computation over constants, interpreted as signed; result is signed + } } diff --git a/checker/tests/signedness/SignednessNumberCasts.java b/checker/tests/signedness/SignednessNumberCasts.java index 5fc3bf3d600..ff8229f7158 100644 --- a/checker/tests/signedness/SignednessNumberCasts.java +++ b/checker/tests/signedness/SignednessNumberCasts.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.signedness.qual.Signed; public class SignednessNumberCasts { - Double d2; + Double d2; - void test(MyClass o, MyClass signed) { - // :: error: (assignment.type.incompatible) - @Signed int i = (Integer) o.get(); - @Signed int i2 = (Integer) signed.get(); - Double d = (Double) o.get(); - d2 = (Double) signed.get(); - } + void test(MyClass o, MyClass signed) { + // :: error: (assignment.type.incompatible) + @Signed int i = (Integer) o.get(); + @Signed int i2 = (Integer) signed.get(); + Double d = (Double) o.get(); + d2 = (Double) signed.get(); + } - static interface MyClass { - T get(); - } + static interface MyClass { + T get(); + } } diff --git a/checker/tests/signedness/SignednessRangeTest.java b/checker/tests/signedness/SignednessRangeTest.java index eab0658df22..03206984730 100644 --- a/checker/tests/signedness/SignednessRangeTest.java +++ b/checker/tests/signedness/SignednessRangeTest.java @@ -2,9 +2,9 @@ class SignednessRangeTest { - private static final @Unsigned int UINT8_MAX = 255; - private static final @Unsigned int UINT16_MAX = 65_535; - private static final @Unsigned byte SOFT_MAX = (@Unsigned byte) UINT8_MAX; - private static final @Unsigned byte SOFT_MAX_2 = (@Unsigned byte) 255; - private static final @Unsigned short DISTANCE_MAX = (@Unsigned short) UINT16_MAX; + private static final @Unsigned int UINT8_MAX = 255; + private static final @Unsigned int UINT16_MAX = 65_535; + private static final @Unsigned byte SOFT_MAX = (@Unsigned byte) UINT8_MAX; + private static final @Unsigned byte SOFT_MAX_2 = (@Unsigned byte) 255; + private static final @Unsigned short DISTANCE_MAX = (@Unsigned short) UINT16_MAX; } diff --git a/checker/tests/signedness/StringConcat.java b/checker/tests/signedness/StringConcat.java index bc13a9af2ab..c9b6d684735 100644 --- a/checker/tests/signedness/StringConcat.java +++ b/checker/tests/signedness/StringConcat.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class StringConcat { - public String doConcat(@Unsigned int i, String s) { - // :: error: (unsigned.concat) - return s + i; - } + public String doConcat(@Unsigned int i, String s) { + // :: error: (unsigned.concat) + return s + i; + } } diff --git a/checker/tests/signedness/TestPrintln.java b/checker/tests/signedness/TestPrintln.java index 3f36f969f0f..c2882e11dda 100644 --- a/checker/tests/signedness/TestPrintln.java +++ b/checker/tests/signedness/TestPrintln.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.signedness.qual.*; public class TestPrintln { - public static void main(String[] args) { - // The first call produces the intended result, but the next two do not. + public static void main(String[] args) { + // The first call produces the intended result, but the next two do not. - @Unsigned int a = Integer.parseUnsignedInt("2147483647"); - System.out.println(a); - @Unsigned int b = Integer.parseUnsignedInt("2147483648"); - // :: error: (argument.type.incompatible) - System.out.println(b); - @Unsigned int c = Integer.parseUnsignedInt("4000000000"); - // :: error: (argument.type.incompatible) - System.out.println(c); - } + @Unsigned int a = Integer.parseUnsignedInt("2147483647"); + System.out.println(a); + @Unsigned int b = Integer.parseUnsignedInt("2147483648"); + // :: error: (argument.type.incompatible) + System.out.println(b); + @Unsigned int c = Integer.parseUnsignedInt("4000000000"); + // :: error: (argument.type.incompatible) + System.out.println(c); + } } diff --git a/checker/tests/signedness/ToHexString.java b/checker/tests/signedness/ToHexString.java index 8ef65748442..128e141f4f3 100644 --- a/checker/tests/signedness/ToHexString.java +++ b/checker/tests/signedness/ToHexString.java @@ -2,15 +2,15 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class ToHexString { - void toHexString(int x) { - Integer.toHexString(x); - } + void toHexString(int x) { + Integer.toHexString(x); + } - void toHexStringU(@Unsigned int x) { - Integer.toHexString(x); - } + void toHexStringU(@Unsigned int x) { + Integer.toHexString(x); + } - void toHexStringS(@Signed int x) { - Integer.toHexString(x); - } + void toHexStringS(@Signed int x) { + Integer.toHexString(x); + } } diff --git a/checker/tests/signedness/UnsignedConcat.java b/checker/tests/signedness/UnsignedConcat.java index df21c73bb97..454c27e2403 100644 --- a/checker/tests/signedness/UnsignedConcat.java +++ b/checker/tests/signedness/UnsignedConcat.java @@ -3,55 +3,55 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class UnsignedConcat { - @UnknownSignedness int unknownInt = -3; - @Unsigned short unsignedShort = -2; - @Unsigned int unsignedInt = -2; - @Signed short signedShort = -2; - @Signed int signedInt = -2; + @UnknownSignedness int unknownInt = -3; + @Unsigned short unsignedShort = -2; + @Unsigned int unsignedInt = -2; + @Signed short signedShort = -2; + @Signed int signedInt = -2; - void test1(char c, Character charObj) { - // :: error: (unsigned.concat) - String s1 = "" + unsignedShort; - // :: error: (unsigned.concat) - String s2 = "" + unsignedInt; - // :: error: (unsigned.concat) - String s1b = unsignedShort + ""; - // :: error: (unsigned.concat) - String s2b = "" + unsignedInt + ""; - String s3 = "" + signedShort; - String s4 = "" + signedInt; - // :: error: (unsigned.concat) - String s5 = "" + unknownInt; - String s6 = "" + -1; + void test1(char c, Character charObj) { + // :: error: (unsigned.concat) + String s1 = "" + unsignedShort; + // :: error: (unsigned.concat) + String s2 = "" + unsignedInt; + // :: error: (unsigned.concat) + String s1b = unsignedShort + ""; + // :: error: (unsigned.concat) + String s2b = "" + unsignedInt + ""; + String s3 = "" + signedShort; + String s4 = "" + signedInt; + // :: error: (unsigned.concat) + String s5 = "" + unknownInt; + String s6 = "" + -1; - String s7 = "" + c; - String s8 = "" + charObj; - } + String s7 = "" + c; + String s8 = "" + charObj; + } - void test2(String s, char c, Character charObj) { - // :: error: (unsigned.concat) - s += unsignedShort; - // :: error: (unsigned.concat) - s += +unsignedInt; - s += "" + signedShort; - s += signedInt; - // :: error: (unsigned.concat) - s += unknownInt; - s += 9; - s += c; - s += charObj; - } + void test2(String s, char c, Character charObj) { + // :: error: (unsigned.concat) + s += unsignedShort; + // :: error: (unsigned.concat) + s += +unsignedInt; + s += "" + signedShort; + s += signedInt; + // :: error: (unsigned.concat) + s += unknownInt; + s += 9; + s += c; + s += charObj; + } - void test3() { - String a = "World"; - String s = "Hi " + (int) a.charAt(0); - } + void test3() { + String a = "World"; + String s = "Hi " + (int) a.charAt(0); + } - void test4(String s) { - Class sc = null; - if (s != null) { - sc = s.getClass(); - } - System.out.println("In: " + sc); + void test4(String s) { + Class sc = null; + if (s != null) { + sc = s.getClass(); } + System.out.println("In: " + sc); + } } diff --git a/checker/tests/signedness/UnsignedConcat2.java b/checker/tests/signedness/UnsignedConcat2.java index 19e8d5f6769..78f62093510 100644 --- a/checker/tests/signedness/UnsignedConcat2.java +++ b/checker/tests/signedness/UnsignedConcat2.java @@ -1,23 +1,22 @@ +import java.util.Set; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Set; - public class UnsignedConcat2 { - protected @Nullable T value; + protected @Nullable T value; - protected Set set; + protected Set set; - @Override - public String toString() { - switch (set.size()) { - case 0: - return "[]"; - case 1: - return "[" + value + "]"; - default: - return set.toString(); - } + @Override + public String toString() { + switch (set.size()) { + case 0: + return "[]"; + case 1: + return "[" + value + "]"; + default: + return set.toString(); } + } } diff --git a/checker/tests/signedness/UnsignedRightShiftTest.java b/checker/tests/signedness/UnsignedRightShiftTest.java index cc51fd23df5..414f7aaf0eb 100644 --- a/checker/tests/signedness/UnsignedRightShiftTest.java +++ b/checker/tests/signedness/UnsignedRightShiftTest.java @@ -5,46 +5,46 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class UnsignedRightShiftTest { - int length; - - void unsignedRightShiftWithLiteral() { - int length = Integer.MAX_VALUE; - byte b = (byte) (length >>> 24); - } - - void unsignedRightShiftWithParameter(int length) { - byte b1 = (byte) (length >>> 24); - byte b2 = (@Signed byte) (length >>> 24); - byte b3 = (@Unsigned byte) (length >>> 24); - } - - void unsignedRightShiftWithField() { - byte b = (byte) (this.length >>> 24); - } - - void unsignedRightShiftComplex() { - int length = return12(); - byte[] byteArray = new byte[4]; - byteArray[0] = (byte) (length >>> 24); - byteArray[1] = (byte) (length >>> 16); - byteArray[2] = (byte) (length >>> 8); - byteArray[3] = (byte) length; - } - - void testWrite64(long x) { - write32((int) (x >>> 32)); - } - - void testWrite64() { - long myLong = Long.MAX_VALUE; - int z = (int) (myLong >>> 32); - int myInt = 2; - short w = (short) (myInt >>> 16); - } - - int return12() { - return 12; - } - - void write32(int x) {} + int length; + + void unsignedRightShiftWithLiteral() { + int length = Integer.MAX_VALUE; + byte b = (byte) (length >>> 24); + } + + void unsignedRightShiftWithParameter(int length) { + byte b1 = (byte) (length >>> 24); + byte b2 = (@Signed byte) (length >>> 24); + byte b3 = (@Unsigned byte) (length >>> 24); + } + + void unsignedRightShiftWithField() { + byte b = (byte) (this.length >>> 24); + } + + void unsignedRightShiftComplex() { + int length = return12(); + byte[] byteArray = new byte[4]; + byteArray[0] = (byte) (length >>> 24); + byteArray[1] = (byte) (length >>> 16); + byteArray[2] = (byte) (length >>> 8); + byteArray[3] = (byte) length; + } + + void testWrite64(long x) { + write32((int) (x >>> 32)); + } + + void testWrite64() { + long myLong = Long.MAX_VALUE; + int z = (int) (myLong >>> 32); + int myInt = 2; + short w = (short) (myInt >>> 16); + } + + int return12() { + return 12; + } + + void write32(int x) {} } diff --git a/checker/tests/signedness/Utils.java b/checker/tests/signedness/Utils.java index 304ff37486d..d6135b9ee0a 100644 --- a/checker/tests/signedness/Utils.java +++ b/checker/tests/signedness/Utils.java @@ -1,170 +1,166 @@ +import java.nio.ByteBuffer; import org.checkerframework.checker.signedness.qual.*; import org.checkerframework.checker.signedness.util.SignednessUtil; -import java.nio.ByteBuffer; - public class Utils { - public void getTests( - @Unsigned int uint, - @Signed int sint, - @Unsigned short ushort, - @Signed short sshort, - @Unsigned byte ubyte, - @Signed byte sbyte, - @Unsigned byte[] ubyteArr, - @Signed byte[] sbyteArr, - ByteBuffer b) { + public void getTests( + @Unsigned int uint, + @Signed int sint, + @Unsigned short ushort, + @Signed short sshort, + @Unsigned byte ubyte, + @Signed byte sbyte, + @Unsigned byte[] ubyteArr, + @Signed byte[] sbyteArr, + ByteBuffer b) { - // :: error: (assignment.type.incompatible) - sint = SignednessUtil.getUnsignedInt(b); + // :: error: (assignment.type.incompatible) + sint = SignednessUtil.getUnsignedInt(b); - uint = SignednessUtil.getUnsignedInt(b); + uint = SignednessUtil.getUnsignedInt(b); - // :: error: (assignment.type.incompatible) - sshort = SignednessUtil.getUnsignedShort(b); + // :: error: (assignment.type.incompatible) + sshort = SignednessUtil.getUnsignedShort(b); - ushort = SignednessUtil.getUnsignedShort(b); + ushort = SignednessUtil.getUnsignedShort(b); - // :: error: (assignment.type.incompatible) - sbyte = SignednessUtil.getUnsigned(b); + // :: error: (assignment.type.incompatible) + sbyte = SignednessUtil.getUnsigned(b); - ubyte = SignednessUtil.getUnsigned(b); + ubyte = SignednessUtil.getUnsigned(b); - // :: error: (argument.type.incompatible) - SignednessUtil.getUnsigned(b, sbyteArr); + // :: error: (argument.type.incompatible) + SignednessUtil.getUnsigned(b, sbyteArr); - SignednessUtil.getUnsigned(b, ubyteArr); - } + SignednessUtil.getUnsigned(b, ubyteArr); + } - public void compTests( - @Unsigned long ulong, - @Signed long slong, - @Unsigned int uint, - @Signed int sint, - @Unsigned short ushort, - @Signed short sshort, - @Unsigned byte ubyte, - @Signed byte sbyte) { + public void compTests( + @Unsigned long ulong, + @Signed long slong, + @Unsigned int uint, + @Signed int sint, + @Unsigned short ushort, + @Signed short sshort, + @Unsigned byte ubyte, + @Signed byte sbyte) { - int res; + int res; - // :: error: (argument.type.incompatible) - res = Long.compareUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + res = Long.compareUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - res = Long.compareUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + res = Long.compareUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - res = Long.compareUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + res = Long.compareUnsigned(ulong, slong); - res = Long.compareUnsigned(ulong, ulong); + res = Long.compareUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - res = Integer.compareUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + res = Integer.compareUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - res = Integer.compareUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + res = Integer.compareUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - res = Integer.compareUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + res = Integer.compareUnsigned(uint, sint); - res = Integer.compareUnsigned(uint, uint); + res = Integer.compareUnsigned(uint, uint); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sshort, sshort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sshort, sshort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sshort, ushort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sshort, ushort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(ushort, sshort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(ushort, sshort); - res = SignednessUtil.compareUnsigned(ushort, ushort); + res = SignednessUtil.compareUnsigned(ushort, ushort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sbyte, sbyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sbyte, sbyte); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sbyte, ubyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sbyte, ubyte); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(ubyte, sbyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(ubyte, sbyte); - res = SignednessUtil.compareUnsigned(ubyte, ubyte); - } + res = SignednessUtil.compareUnsigned(ubyte, ubyte); + } - public void stringTests( - @Unsigned long ulong, - @Signed long slong, - @Unsigned int uint, - @Signed int sint, - @Unsigned short ushort, - @Signed short sshort, - @Unsigned byte ubyte, - @Signed byte sbyte) { + public void stringTests( + @Unsigned long ulong, + @Signed long slong, + @Unsigned int uint, + @Signed int sint, + @Unsigned short ushort, + @Signed short sshort, + @Unsigned byte ubyte, + @Signed byte sbyte) { - String res; + String res; - // :: error: (argument.type.incompatible) - res = Long.toUnsignedString(slong); + // :: error: (argument.type.incompatible) + res = Long.toUnsignedString(slong); - res = Long.toUnsignedString(ulong); + res = Long.toUnsignedString(ulong); - // :: error: (argument.type.incompatible) - res = Long.toUnsignedString(slong, 10); + // :: error: (argument.type.incompatible) + res = Long.toUnsignedString(slong, 10); - res = Long.toUnsignedString(ulong, 10); + res = Long.toUnsignedString(ulong, 10); - // :: error: (argument.type.incompatible) - res = Integer.toUnsignedString(sint); + // :: error: (argument.type.incompatible) + res = Integer.toUnsignedString(sint); - res = Integer.toUnsignedString(uint); + res = Integer.toUnsignedString(uint); - // :: error: (argument.type.incompatible) - res = Integer.toUnsignedString(sint, 10); + // :: error: (argument.type.incompatible) + res = Integer.toUnsignedString(sint, 10); - res = Integer.toUnsignedString(uint, 10); + res = Integer.toUnsignedString(uint, 10); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sshort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sshort); - res = SignednessUtil.toUnsignedString(ushort); + res = SignednessUtil.toUnsignedString(ushort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sshort, 10); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sshort, 10); - res = SignednessUtil.toUnsignedString(ushort, 10); + res = SignednessUtil.toUnsignedString(ushort, 10); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sbyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sbyte); - res = SignednessUtil.toUnsignedString(ubyte); + res = SignednessUtil.toUnsignedString(ubyte); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sbyte, 10); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sbyte, 10); - res = SignednessUtil.toUnsignedString(ubyte, 10); - } + res = SignednessUtil.toUnsignedString(ubyte, 10); + } - public void floatingPointConversionTests( - @Unsigned long ulong, - @Unsigned int uint, - @Unsigned short ushort, - @Unsigned byte ubyte) { + public void floatingPointConversionTests( + @Unsigned long ulong, @Unsigned int uint, @Unsigned short ushort, @Unsigned byte ubyte) { - float resFloat; + float resFloat; - resFloat = SignednessUtil.toFloat(ubyte); - resFloat = SignednessUtil.toFloat(ushort); - resFloat = SignednessUtil.toFloat(uint); - resFloat = SignednessUtil.toFloat(ulong); + resFloat = SignednessUtil.toFloat(ubyte); + resFloat = SignednessUtil.toFloat(ushort); + resFloat = SignednessUtil.toFloat(uint); + resFloat = SignednessUtil.toFloat(ulong); - double resDouble; + double resDouble; - resDouble = SignednessUtil.toDouble(ubyte); - resDouble = SignednessUtil.toDouble(ushort); - resDouble = SignednessUtil.toDouble(uint); - resDouble = SignednessUtil.toDouble(ulong); - } + resDouble = SignednessUtil.toDouble(ubyte); + resDouble = SignednessUtil.toDouble(ushort); + resDouble = SignednessUtil.toDouble(uint); + resDouble = SignednessUtil.toDouble(ulong); + } } diff --git a/checker/tests/signedness/UtilsJava8.java b/checker/tests/signedness/UtilsJava8.java index 1421299ea9f..94a62a81b4b 100644 --- a/checker/tests/signedness/UtilsJava8.java +++ b/checker/tests/signedness/UtilsJava8.java @@ -4,138 +4,138 @@ // Test Java 8 unsigned utils public class UtilsJava8 { - public void annotatedJDKTests( - @Unsigned long ulong, - @Signed long slong, - @Unsigned int uint, - @Signed int sint, - char[] buf, - String s) { + public void annotatedJDKTests( + @Unsigned long ulong, + @Signed long slong, + @Unsigned int uint, + @Signed int sint, + char[] buf, + String s) { - String resString; - int resInt; - long resLong; + String resString; + int resInt; + long resLong; - // :: error: (argument.type.incompatible) - resString = Long.toUnsignedString(slong, 10); + // :: error: (argument.type.incompatible) + resString = Long.toUnsignedString(slong, 10); - resString = Long.toUnsignedString(ulong, 10); + resString = Long.toUnsignedString(ulong, 10); - // :: error: (argument.type.incompatible) - resString = Long.toUnsignedString(slong); + // :: error: (argument.type.incompatible) + resString = Long.toUnsignedString(slong); - resString = Long.toUnsignedString(ulong); + resString = Long.toUnsignedString(ulong); - // :: error: (assignment.type.incompatible) - slong = Long.parseUnsignedLong(s, 10); + // :: error: (assignment.type.incompatible) + slong = Long.parseUnsignedLong(s, 10); - ulong = Long.parseUnsignedLong(s, 10); + ulong = Long.parseUnsignedLong(s, 10); - // :: error: (assignment.type.incompatible) - slong = Long.parseUnsignedLong(s); + // :: error: (assignment.type.incompatible) + slong = Long.parseUnsignedLong(s); - ulong = Long.parseUnsignedLong(s); + ulong = Long.parseUnsignedLong(s); - // :: error: (argument.type.incompatible) - resInt = Long.compareUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + resInt = Long.compareUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - resInt = Long.compareUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + resInt = Long.compareUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - resInt = Long.compareUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + resInt = Long.compareUnsigned(ulong, slong); - resInt = Long.compareUnsigned(ulong, ulong); + resInt = Long.compareUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.divideUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.divideUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - ulong = Long.divideUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + ulong = Long.divideUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.divideUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.divideUnsigned(ulong, slong); - // :: error: (assignment.type.incompatible) - slong = Long.divideUnsigned(ulong, ulong); + // :: error: (assignment.type.incompatible) + slong = Long.divideUnsigned(ulong, ulong); - ulong = Long.divideUnsigned(ulong, ulong); + ulong = Long.divideUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.remainderUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.remainderUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - ulong = Long.remainderUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + ulong = Long.remainderUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.remainderUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.remainderUnsigned(ulong, slong); - // :: error: (assignment.type.incompatible) - slong = Long.remainderUnsigned(ulong, ulong); + // :: error: (assignment.type.incompatible) + slong = Long.remainderUnsigned(ulong, ulong); - ulong = Long.remainderUnsigned(ulong, ulong); + ulong = Long.remainderUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - resString = Integer.toUnsignedString(sint, 10); + // :: error: (argument.type.incompatible) + resString = Integer.toUnsignedString(sint, 10); - resString = Integer.toUnsignedString(uint, 10); + resString = Integer.toUnsignedString(uint, 10); - // :: error: (argument.type.incompatible) - resString = Integer.toUnsignedString(sint); + // :: error: (argument.type.incompatible) + resString = Integer.toUnsignedString(sint); - resString = Integer.toUnsignedString(uint); + resString = Integer.toUnsignedString(uint); - // :: error: (assignment.type.incompatible) - sint = Integer.parseUnsignedInt(s, 10); + // :: error: (assignment.type.incompatible) + sint = Integer.parseUnsignedInt(s, 10); - uint = Integer.parseUnsignedInt(s, 10); + uint = Integer.parseUnsignedInt(s, 10); - // :: error: (assignment.type.incompatible) - sint = Integer.parseUnsignedInt(s); + // :: error: (assignment.type.incompatible) + sint = Integer.parseUnsignedInt(s); - uint = Integer.parseUnsignedInt(s); + uint = Integer.parseUnsignedInt(s); - // :: error: (argument.type.incompatible) - resInt = Integer.compareUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + resInt = Integer.compareUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - resInt = Integer.compareUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + resInt = Integer.compareUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - resInt = Integer.compareUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + resInt = Integer.compareUnsigned(uint, sint); - resInt = Integer.compareUnsigned(uint, uint); + resInt = Integer.compareUnsigned(uint, uint); - resLong = Integer.toUnsignedLong(sint); + resLong = Integer.toUnsignedLong(sint); - ulong = Integer.toUnsignedLong(uint); + ulong = Integer.toUnsignedLong(uint); - // :: error: (argument.type.incompatible) - uint = Integer.divideUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.divideUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - uint = Integer.divideUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + uint = Integer.divideUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - uint = Integer.divideUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.divideUnsigned(uint, sint); - // :: error: (assignment.type.incompatible) - sint = Integer.divideUnsigned(uint, uint); + // :: error: (assignment.type.incompatible) + sint = Integer.divideUnsigned(uint, uint); - uint = Integer.divideUnsigned(uint, uint); + uint = Integer.divideUnsigned(uint, uint); - // :: error: (argument.type.incompatible) - uint = Integer.remainderUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.remainderUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - uint = Integer.remainderUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + uint = Integer.remainderUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - uint = Integer.remainderUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.remainderUnsigned(uint, sint); - // :: error: (assignment.type.incompatible) - sint = Integer.remainderUnsigned(uint, uint); + // :: error: (assignment.type.incompatible) + sint = Integer.remainderUnsigned(uint, uint); - uint = Integer.remainderUnsigned(uint, uint); - } + uint = Integer.remainderUnsigned(uint, uint); + } } diff --git a/checker/tests/signedness/ValueIntegration.java b/checker/tests/signedness/ValueIntegration.java index ae4ecc860d7..48c3c647891 100644 --- a/checker/tests/signedness/ValueIntegration.java +++ b/checker/tests/signedness/ValueIntegration.java @@ -7,491 +7,491 @@ import org.checkerframework.common.value.qual.IntVal; public class ValueIntegration { - public void ByteValRules( - @IntVal({0, 127}) byte c, - @IntVal({128, 255}) byte upure, - @IntVal({0, 128}) byte umixed, // 128 is another way to write -128 - @IntVal({-128, -1}) byte spure, - @IntVal({-1, 127}) byte smixed, - @IntVal({-128, 0, 128}) byte bmixed) { - @Signed byte stest; - @SignednessGlb byte gtest; - @SignedPositive byte ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - // Character and char are always @Unsigned, never @Signed. - /* - public void CharValRules( - @IntVal({0, 127}) char c, - @IntVal({128, 255}) char upure, - @IntVal({0, 128}) char umixed, - @IntVal({-128, -1}) char spure, - @IntVal({-1, 127}) char smixed, - @IntVal({-128, 0, 128}) char bmixed) { - @Signed char stest; - @SignednessGlb char gtest; - @SignedPositive char ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // XX error: (assignment.type.incompatible) - gtest = upure; - // XX error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // XX error: (assignment.type.incompatible) - gtest = umixed; - // XX error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // XX error: (assignment.type.incompatible) - gtest = spure; - // XX error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // XX error: (assignment.type.incompatible) - gtest = smixed; - // XX error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // XX error: (assignment.type.incompatible) - gtest = bmixed; - // XX error: (assignment.type.incompatible) - ptest = bmixed; - } - */ - - public void ShortValRules( - @IntVal({0, 32767}) short c, - @IntVal({32768, 65535}) short upure, - @IntVal({0, 32768}) short umixed, - @IntVal({-32768, -1}) short spure, - @IntVal({-1, 32767}) short smixed, - @IntVal({-32768, 0, 32768}) short bmixed) { - @Signed short stest; - @SignednessGlb short gtest; - @SignedPositive short ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void IntValRules( - @IntVal({0, 2147483647}) int c, - @IntVal({2147483648L, 4294967295L}) int upure, - @IntVal({0, 2147483648L}) int umixed, - @IntVal({-2147483648, -1}) int spure, - @IntVal({-1, 2147483647}) int smixed, - @IntVal({-2147483648, 0, 2147483648L}) int bmixed) { - @Signed int stest; - @SignednessGlb int gtest; - @SignedPositive int ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void LongValRules( - @IntVal({0, Long.MAX_VALUE}) long c, - @IntVal({Long.MIN_VALUE, -1}) long spure, - @IntVal({-1, Long.MAX_VALUE}) long smixed, - @IntVal({Long.MIN_VALUE, 0, Long.MAX_VALUE}) long bmixed) { - @Signed long stest; - @SignednessGlb long gtest; - @SignedPositive long ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void ByteRangeRules( - @IntRange(from = 0, to = 127) byte c, - @NonNegative byte nnc, - @Positive byte pc, - @IntRange(from = 128, to = 255) byte upure, - @IntRange(from = 0, to = 128) byte umixed, - @IntRange(from = -128, to = -1) byte spure, - @IntRange(from = -1, to = 127) byte smixed, - @IntRange(from = -128, to = 128) byte bmixed) { - @Signed byte stest; - @SignednessGlb byte gtest; - @SignedPositive byte ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - // Character and char are always @Unsigned, never @Signed. - /* - public void CharRangeRules( - @IntRange(from = 0, to = 127) char c, - @NonNegative char nnc, - @Positive char pc, - @IntRange(from = 128, to = 255) char upure, - @IntRange(from = 0, to = 128) char umixed, - @IntRange(from = -128, to = -1) char spure, - @IntRange(from = -1, to = 127) char smixed, - @IntRange(from = -128, to = 128) char bmixed) { - @Signed char stest; - @SignednessGlb char gtest; - @SignedPositive char ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // XX error: (assignment.type.incompatible) - gtest = upure; - // XX error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // XX error: (assignment.type.incompatible) - gtest = umixed; - // XX error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // XX error: (assignment.type.incompatible) - gtest = spure; - // XX error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // XX error: (assignment.type.incompatible) - gtest = smixed; - // XX error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // XX error: (assignment.type.incompatible) - gtest = bmixed; - // XX error: (assignment.type.incompatible) - ptest = bmixed; - } - */ - - public void ShortRangeRules( - @IntRange(from = 0, to = 32767) short c, - @NonNegative short nnc, - @Positive short pc, - @IntRange(from = 32768, to = 65535) short upure, - @IntRange(from = 0, to = 32768) short umixed, - @IntRange(from = -32768, to = -1) short spure, - @IntRange(from = -1, to = 32767) short smixed, - @IntRange(from = -32768, to = 32768) short bmixed) { - @Signed short stest; - @SignednessGlb short gtest; - @SignedPositive short ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void IntRangeRules( - @IntRange(from = 0, to = 2147483647) int c, - @NonNegative int nnc, - @Positive int pc, - @IntRange(from = 2147483648L, to = 4294967295L) int upure, - @IntRange(from = 0, to = 2147483648L) int umixed, - @IntRange(from = -2147483648, to = -1) int spure, - @IntRange(from = -1, to = 2147483647) int smixed, - @IntRange(from = -2147483648, to = 2147483648L) int bmixed) { - @Signed int stest; - @SignednessGlb int gtest; - @SignedPositive int ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void LongRangeRules( - @IntRange(from = 0, to = Long.MAX_VALUE) long c, - @NonNegative long nnc, - @Positive long pc, - @IntRange(from = Long.MIN_VALUE, to = -1) long spure, - @IntRange(from = -1, to = Long.MAX_VALUE) long smixed, - @IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long bmixed) { - @Signed long stest; - @SignednessGlb long gtest; - @SignedPositive long ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } + public void ByteValRules( + @IntVal({0, 127}) byte c, + @IntVal({128, 255}) byte upure, + @IntVal({0, 128}) byte umixed, // 128 is another way to write -128 + @IntVal({-128, -1}) byte spure, + @IntVal({-1, 127}) byte smixed, + @IntVal({-128, 0, 128}) byte bmixed) { + @Signed byte stest; + @SignednessGlb byte gtest; + @SignedPositive byte ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + // Character and char are always @Unsigned, never @Signed. + /* + public void CharValRules( + @IntVal({0, 127}) char c, + @IntVal({128, 255}) char upure, + @IntVal({0, 128}) char umixed, + @IntVal({-128, -1}) char spure, + @IntVal({-1, 127}) char smixed, + @IntVal({-128, 0, 128}) char bmixed) { + @Signed char stest; + @SignednessGlb char gtest; + @SignedPositive char ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // XX error: (assignment.type.incompatible) + gtest = upure; + // XX error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // XX error: (assignment.type.incompatible) + gtest = umixed; + // XX error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // XX error: (assignment.type.incompatible) + gtest = spure; + // XX error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // XX error: (assignment.type.incompatible) + gtest = smixed; + // XX error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // XX error: (assignment.type.incompatible) + gtest = bmixed; + // XX error: (assignment.type.incompatible) + ptest = bmixed; + } + */ + + public void ShortValRules( + @IntVal({0, 32767}) short c, + @IntVal({32768, 65535}) short upure, + @IntVal({0, 32768}) short umixed, + @IntVal({-32768, -1}) short spure, + @IntVal({-1, 32767}) short smixed, + @IntVal({-32768, 0, 32768}) short bmixed) { + @Signed short stest; + @SignednessGlb short gtest; + @SignedPositive short ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void IntValRules( + @IntVal({0, 2147483647}) int c, + @IntVal({2147483648L, 4294967295L}) int upure, + @IntVal({0, 2147483648L}) int umixed, + @IntVal({-2147483648, -1}) int spure, + @IntVal({-1, 2147483647}) int smixed, + @IntVal({-2147483648, 0, 2147483648L}) int bmixed) { + @Signed int stest; + @SignednessGlb int gtest; + @SignedPositive int ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void LongValRules( + @IntVal({0, Long.MAX_VALUE}) long c, + @IntVal({Long.MIN_VALUE, -1}) long spure, + @IntVal({-1, Long.MAX_VALUE}) long smixed, + @IntVal({Long.MIN_VALUE, 0, Long.MAX_VALUE}) long bmixed) { + @Signed long stest; + @SignednessGlb long gtest; + @SignedPositive long ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void ByteRangeRules( + @IntRange(from = 0, to = 127) byte c, + @NonNegative byte nnc, + @Positive byte pc, + @IntRange(from = 128, to = 255) byte upure, + @IntRange(from = 0, to = 128) byte umixed, + @IntRange(from = -128, to = -1) byte spure, + @IntRange(from = -1, to = 127) byte smixed, + @IntRange(from = -128, to = 128) byte bmixed) { + @Signed byte stest; + @SignednessGlb byte gtest; + @SignedPositive byte ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + // Character and char are always @Unsigned, never @Signed. + /* + public void CharRangeRules( + @IntRange(from = 0, to = 127) char c, + @NonNegative char nnc, + @Positive char pc, + @IntRange(from = 128, to = 255) char upure, + @IntRange(from = 0, to = 128) char umixed, + @IntRange(from = -128, to = -1) char spure, + @IntRange(from = -1, to = 127) char smixed, + @IntRange(from = -128, to = 128) char bmixed) { + @Signed char stest; + @SignednessGlb char gtest; + @SignedPositive char ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // XX error: (assignment.type.incompatible) + gtest = upure; + // XX error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // XX error: (assignment.type.incompatible) + gtest = umixed; + // XX error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // XX error: (assignment.type.incompatible) + gtest = spure; + // XX error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // XX error: (assignment.type.incompatible) + gtest = smixed; + // XX error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // XX error: (assignment.type.incompatible) + gtest = bmixed; + // XX error: (assignment.type.incompatible) + ptest = bmixed; + } + */ + + public void ShortRangeRules( + @IntRange(from = 0, to = 32767) short c, + @NonNegative short nnc, + @Positive short pc, + @IntRange(from = 32768, to = 65535) short upure, + @IntRange(from = 0, to = 32768) short umixed, + @IntRange(from = -32768, to = -1) short spure, + @IntRange(from = -1, to = 32767) short smixed, + @IntRange(from = -32768, to = 32768) short bmixed) { + @Signed short stest; + @SignednessGlb short gtest; + @SignedPositive short ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void IntRangeRules( + @IntRange(from = 0, to = 2147483647) int c, + @NonNegative int nnc, + @Positive int pc, + @IntRange(from = 2147483648L, to = 4294967295L) int upure, + @IntRange(from = 0, to = 2147483648L) int umixed, + @IntRange(from = -2147483648, to = -1) int spure, + @IntRange(from = -1, to = 2147483647) int smixed, + @IntRange(from = -2147483648, to = 2147483648L) int bmixed) { + @Signed int stest; + @SignednessGlb int gtest; + @SignedPositive int ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void LongRangeRules( + @IntRange(from = 0, to = Long.MAX_VALUE) long c, + @NonNegative long nnc, + @Positive long pc, + @IntRange(from = Long.MIN_VALUE, to = -1) long spure, + @IntRange(from = -1, to = Long.MAX_VALUE) long smixed, + @IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long bmixed) { + @Signed long stest; + @SignednessGlb long gtest; + @SignedPositive long ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } } diff --git a/checker/tests/signedness/WideningConversion.java b/checker/tests/signedness/WideningConversion.java index 10e0dc177ae..a43a650a366 100644 --- a/checker/tests/signedness/WideningConversion.java +++ b/checker/tests/signedness/WideningConversion.java @@ -3,98 +3,98 @@ public class WideningConversion { - char c1; - char c2; - int i1; - int i2; - @Signed int si1; - @Signed int si2; - @Unsigned int ui1; - @Unsigned int ui2; - @Unsigned short us1; - @Unsigned short us2; + char c1; + char c2; + int i1; + int i2; + @Signed int si1; + @Signed int si2; + @Unsigned int ui1; + @Unsigned int ui2; + @Unsigned short us1; + @Unsigned short us2; - void compare() { - boolean b; - b = c1 > c2; - b = c1 > i2; - b = i1 > c2; - b = i1 > i2; - } + void compare() { + boolean b; + b = c1 > c2; + b = c1 > i2; + b = i1 > c2; + b = i1 > i2; + } - void plus() { - // Not just "int si" because it's defaulted to TOP so every assignment would work. - @Signed int si; - si = c1 + c2; - si = c1 + i2; - si = i1 + c2; - si = i1 + i2; + void plus() { + // Not just "int si" because it's defaulted to TOP so every assignment would work. + @Signed int si; + si = c1 + c2; + si = c1 + i2; + si = i1 + c2; + si = i1 + i2; - si = c1 + c2; - si = c1 + si2; - si = si1 + c2; - si = si1 + si2; + si = c1 + c2; + si = c1 + si2; + si = si1 + c2; + si = si1 + si2; - si = c1 + c2; - // :: error: (assignment.type.incompatible) - si = c1 + ui2; - // :: error: (assignment.type.incompatible) - si = ui1 + c2; - // :: error: (assignment.type.incompatible) - si = ui1 + ui2; + si = c1 + c2; + // :: error: (assignment.type.incompatible) + si = c1 + ui2; + // :: error: (assignment.type.incompatible) + si = ui1 + c2; + // :: error: (assignment.type.incompatible) + si = ui1 + ui2; - @Unsigned int ui; - ui = c1 + c2; - // :: error: (assignment.type.incompatible) - ui = c1 + i2; - // :: error: (assignment.type.incompatible) - ui = i1 + c2; - // :: error: (assignment.type.incompatible) - ui = i1 + i2; + @Unsigned int ui; + ui = c1 + c2; + // :: error: (assignment.type.incompatible) + ui = c1 + i2; + // :: error: (assignment.type.incompatible) + ui = i1 + c2; + // :: error: (assignment.type.incompatible) + ui = i1 + i2; - ui = c1 + c2; - // :: error: (assignment.type.incompatible) - ui = c1 + si2; - // :: error: (assignment.type.incompatible) - ui = si1 + c2; - // :: error: (assignment.type.incompatible) - ui = si1 + si2; + ui = c1 + c2; + // :: error: (assignment.type.incompatible) + ui = c1 + si2; + // :: error: (assignment.type.incompatible) + ui = si1 + c2; + // :: error: (assignment.type.incompatible) + ui = si1 + si2; - ui = c1 + c2; - ui = c1 + ui2; - ui = ui1 + c2; - ui = ui1 + ui2; + ui = c1 + c2; + ui = c1 + ui2; + ui = ui1 + c2; + ui = ui1 + ui2; - // All of these are illegal in Java, without an explicit cast. - // char c; - // c = c1 + c2; - // c = c1 + i2; - // c = i1 + c2; - // c = i1 + i2; + // All of these are illegal in Java, without an explicit cast. + // char c; + // c = c1 + c2; + // c = c1 + i2; + // c = i1 + c2; + // c = i1 + i2; - char c; - c = (char) (c1 + c2); - c = (char) (c1 + i2); - c = (char) (i1 + c2); - c = (char) (i1 + i2); + char c; + c = (char) (c1 + c2); + c = (char) (c1 + i2); + c = (char) (i1 + c2); + c = (char) (i1 + i2); - c = (char) (c1 + c2); - c = (char) (c1 + si2); - c = (char) (si1 + c2); - c = (char) (si1 + si2); + c = (char) (c1 + c2); + c = (char) (c1 + si2); + c = (char) (si1 + c2); + c = (char) (si1 + si2); - c = (char) (c1 + c2); - c = (char) (c1 + ui2); - c = (char) (ui1 + c2); - c = (char) (ui1 + ui2); - } + c = (char) (c1 + c2); + c = (char) (c1 + ui2); + c = (char) (ui1 + c2); + c = (char) (ui1 + ui2); + } - void to_string() { - // :: error: (unsigned.concat) - String s1 = "" + us1; - // :: error: (argument.type.incompatible) - String s2 = String.valueOf(us2); - // :: error: (argument.type.incompatible) - String s3 = Short.toString(us1); - } + void to_string() { + // :: error: (unsigned.concat) + String s1 = "" + us1; + // :: error: (argument.type.incompatible) + String s2 = String.valueOf(us2); + // :: error: (argument.type.incompatible) + String s3 = Short.toString(us1); + } } diff --git a/checker/tests/signedness/WideningFloat.java b/checker/tests/signedness/WideningFloat.java index 11a4c575520..cbdeb73a17d 100644 --- a/checker/tests/signedness/WideningFloat.java +++ b/checker/tests/signedness/WideningFloat.java @@ -2,21 +2,21 @@ public class WideningFloat { - void floatArg(float x) {} + void floatArg(float x) {} - void m(Object arg) { - floatArg((Byte) arg); - } + void m(Object arg) { + floatArg((Byte) arg); + } - void m2(@UnknownSignedness Byte arg) { - floatArg(arg); - } + void m2(@UnknownSignedness Byte arg) { + floatArg(arg); + } - void m3(@UnknownSignedness Byte arg) { - float f = arg; - } + void m3(@UnknownSignedness Byte arg) { + float f = arg; + } - void m3(@UnknownSignedness byte arg) { - float f = arg; - } + void m3(@UnknownSignedness byte arg) { + float f = arg; + } } diff --git a/checker/tests/signedness/WideningInitialization.java b/checker/tests/signedness/WideningInitialization.java index 3422848d812..469ca80996c 100644 --- a/checker/tests/signedness/WideningInitialization.java +++ b/checker/tests/signedness/WideningInitialization.java @@ -1,34 +1,34 @@ import org.checkerframework.checker.signedness.qual.Signed; public class WideningInitialization { - public int findLineNr(int pc, char startPC, char lineNr) { - int ln = 0; - for (int i = 0; i < 3; i++) { - if (startPC <= pc) { - ln = lineNr; - } else { - return ln; - } - } - return ln; - } - - public int findLineNr2(char lineNr) { - int ln = 0; + public int findLineNr(int pc, char startPC, char lineNr) { + int ln = 0; + for (int i = 0; i < 3; i++) { + if (startPC <= pc) { ln = lineNr; + } else { return ln; + } } + return ln; + } - public void findLineNr3a(char lineNr) { - @Signed int ln = lineNr; - } + public int findLineNr2(char lineNr) { + int ln = 0; + ln = lineNr; + return ln; + } - public int findLineNr3b(char lineNr) { - int ln = lineNr; - return ln; - } + public void findLineNr3a(char lineNr) { + @Signed int ln = lineNr; + } - public int findLineNr4(char lineNr) { - return lineNr; - } + public int findLineNr3b(char lineNr) { + int ln = lineNr; + return ln; + } + + public int findLineNr4(char lineNr) { + return lineNr; + } } diff --git a/checker/tests/signedness/java17/Issue6100.java b/checker/tests/signedness/java17/Issue6100.java index eaa84fb7cbe..6fa84d0270b 100644 --- a/checker/tests/signedness/java17/Issue6100.java +++ b/checker/tests/signedness/java17/Issue6100.java @@ -1,18 +1,17 @@ // @below-java17-jdk-skip-test // @infer-jaifs-skip-test The AFU's JAIF reading/writing libraries don't support records. -import org.checkerframework.checker.index.qual.NonNegative; - import java.util.List; +import org.checkerframework.checker.index.qual.NonNegative; public record Issue6100(List<@NonNegative Integer> bar) { - public Issue6100 { - List<@NonNegative Integer> b = bar; - // :: error: (assignment.type.incompatible) - List b2 = bar; - if (bar.size() < 0) { - throw new IllegalArgumentException(); - } + public Issue6100 { + List<@NonNegative Integer> b = bar; + // :: error: (assignment.type.incompatible) + List b2 = bar; + if (bar.size() < 0) { + throw new IllegalArgumentException(); } + } } diff --git a/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java b/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java index d2bec564ad8..a60b6569f52 100644 --- a/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java +++ b/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java @@ -20,197 +20,197 @@ */ public class MultidimentionalArrayAnnotationTest { - int numb = 1; - - // Declared 8 3-dimentional variables. - Object @Nullable [] @Nullable [] @Nullable [] obj1 = new Object[numb][numb][numb]; - Object @NonNull [] @Nullable [] @Nullable [] obj2 = new Object[numb][numb][numb]; - Object @Nullable [] @NonNull [] @Nullable [] obj3 = new Object[numb][numb][numb]; - Object @Nullable [] @Nullable [] @NonNull [] obj4 = new Object[numb][numb][numb]; - Object @NonNull [] @NonNull [] @Nullable [] obj5 = new Object[numb][numb][numb]; - Object @NonNull [] @Nullable [] @NonNull [] obj6 = new Object[numb][numb][numb]; - Object @Nullable [] @NonNull [] @NonNull [] obj7 = new Object[numb][numb][numb]; - Object @NonNull [] @NonNull [] @NonNull [] obj8 = new Object[numb][numb][numb]; - - /* - * Call to method 1 that returns Object @NonNull [] @NonNull [] @NonNull []. - * Errors are not expected. - */ - void callTomethod1() { - obj1 = method1(); - obj2 = method1(); - obj3 = method1(); - obj4 = method1(); - obj5 = method1(); - obj6 = method1(); - obj7 = method1(); - obj8 = method1(); - } - - /* - * Call to method 2 that returns Object @Nullable [] @NonNull [] @NonNull []. - */ - void callTomethod2() { - obj1 = method2(); - // :: error: (assignment.type.incompatible) - obj2 = method2(); - obj3 = method2(); - obj4 = method2(); - // :: error: (assignment.type.incompatible) - obj5 = method2(); - // :: error: (assignment.type.incompatible) - obj6 = method2(); - obj7 = method2(); - // :: error: (assignment.type.incompatible) - obj8 = method2(); - } - - /* - * Call to method 3 that returns Object @NonNull [] @Nullable [] @NonNull []. - */ - void callTomethod3() { - obj1 = method3(); - obj2 = method3(); - // :: error: (assignment.type.incompatible) - obj3 = method3(); - obj4 = method3(); - // :: error: (assignment.type.incompatible) - obj5 = method3(); - obj6 = method3(); - // :: error: (assignment.type.incompatible) - obj7 = method3(); - // :: error: (assignment.type.incompatible) - obj8 = method3(); - } - - /* - * Call to method 4 that returns Object @NonNull [] @NonNull [] @Nullable []. - */ - void callTomethod4() { - obj1 = method4(); - obj2 = method4(); - obj3 = method4(); - // :: error: (assignment.type.incompatible) - obj4 = method4(); - obj5 = method4(); - // :: error: (assignment.type.incompatible) - obj6 = method4(); - // :: error: (assignment.type.incompatible) - obj7 = method4(); - // :: error: (assignment.type.incompatible) - obj8 = method4(); - } - - /* - * Call to method 5 that returns Object @Nullable [] @Nullable [] @NonNull []. - */ - void callTomethod5() { - obj1 = method5(); - // :: error: (assignment.type.incompatible) - obj2 = method5(); - // :: error: (assignment.type.incompatible) - obj3 = method5(); - obj4 = method5(); - // :: error: (assignment.type.incompatible) - obj5 = method5(); - // :: error: (assignment.type.incompatible) - obj6 = method5(); - // :: error: (assignment.type.incompatible) - obj7 = method5(); - // :: error: (assignment.type.incompatible) - obj8 = method5(); - } - - /* - * Call to method 6 that returns Object @Nullable [] @NonNull [] @Nullable []. - */ - void callTomethod6() { - obj1 = method6(); - // :: error: (assignment.type.incompatible) - obj2 = method6(); - obj3 = method6(); - // :: error: (assignment.type.incompatible) - obj4 = method6(); - // :: error: (assignment.type.incompatible) - obj5 = method6(); - // :: error: (assignment.type.incompatible) - obj6 = method6(); - // :: error: (assignment.type.incompatible) - obj7 = method6(); - // :: error: (assignment.type.incompatible) - obj8 = method6(); - } - - /* - * Call to method 7 that returns Object @NonNull [] @Nullable [] @Nullable []. - */ - void callTomethod7() { - obj1 = method7(); - obj2 = method7(); - // :: error: (assignment.type.incompatible) - obj3 = method7(); - // :: error: (assignment.type.incompatible) - obj4 = method7(); - // :: error: (assignment.type.incompatible) - obj5 = method7(); - // :: error: (assignment.type.incompatible) - obj6 = method7(); - // :: error: (assignment.type.incompatible) - obj7 = method7(); - // :: error: (assignment.type.incompatible) - obj8 = method7(); - } - - /* - * Call to method 8 that returns Object @Nullable [] @Nullable [] @Nullable []. - */ - void callTomethod8() { - obj1 = method8(); - // :: error: (assignment.type.incompatible) - obj2 = method8(); - // :: error: (assignment.type.incompatible) - obj3 = method8(); - // :: error: (assignment.type.incompatible) - obj4 = method8(); - // :: error: (assignment.type.incompatible) - obj5 = method8(); - // :: error: (assignment.type.incompatible) - obj6 = method8(); - // :: error: (assignment.type.incompatible) - obj7 = method8(); - // :: error: (assignment.type.incompatible) - obj8 = method8(); - } - - Object[][][] method1() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [][][] method2() { - return new Object[numb][numb][numb]; - } - - Object[] @Nullable [][] method3() { - return new Object[numb][numb][numb]; - } - - Object[][] @Nullable [] method4() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [] @Nullable [][] method5() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [][] @Nullable [] method6() { - return new Object[numb][numb][numb]; - } - - Object[] @Nullable [] @Nullable [] method7() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [] @Nullable [] @Nullable [] method8() { - return new Object[numb][numb][numb]; - } + int numb = 1; + + // Declared 8 3-dimentional variables. + Object @Nullable [] @Nullable [] @Nullable [] obj1 = new Object[numb][numb][numb]; + Object @NonNull [] @Nullable [] @Nullable [] obj2 = new Object[numb][numb][numb]; + Object @Nullable [] @NonNull [] @Nullable [] obj3 = new Object[numb][numb][numb]; + Object @Nullable [] @Nullable [] @NonNull [] obj4 = new Object[numb][numb][numb]; + Object @NonNull [] @NonNull [] @Nullable [] obj5 = new Object[numb][numb][numb]; + Object @NonNull [] @Nullable [] @NonNull [] obj6 = new Object[numb][numb][numb]; + Object @Nullable [] @NonNull [] @NonNull [] obj7 = new Object[numb][numb][numb]; + Object @NonNull [] @NonNull [] @NonNull [] obj8 = new Object[numb][numb][numb]; + + /* + * Call to method 1 that returns Object @NonNull [] @NonNull [] @NonNull []. + * Errors are not expected. + */ + void callTomethod1() { + obj1 = method1(); + obj2 = method1(); + obj3 = method1(); + obj4 = method1(); + obj5 = method1(); + obj6 = method1(); + obj7 = method1(); + obj8 = method1(); + } + + /* + * Call to method 2 that returns Object @Nullable [] @NonNull [] @NonNull []. + */ + void callTomethod2() { + obj1 = method2(); + // :: error: (assignment.type.incompatible) + obj2 = method2(); + obj3 = method2(); + obj4 = method2(); + // :: error: (assignment.type.incompatible) + obj5 = method2(); + // :: error: (assignment.type.incompatible) + obj6 = method2(); + obj7 = method2(); + // :: error: (assignment.type.incompatible) + obj8 = method2(); + } + + /* + * Call to method 3 that returns Object @NonNull [] @Nullable [] @NonNull []. + */ + void callTomethod3() { + obj1 = method3(); + obj2 = method3(); + // :: error: (assignment.type.incompatible) + obj3 = method3(); + obj4 = method3(); + // :: error: (assignment.type.incompatible) + obj5 = method3(); + obj6 = method3(); + // :: error: (assignment.type.incompatible) + obj7 = method3(); + // :: error: (assignment.type.incompatible) + obj8 = method3(); + } + + /* + * Call to method 4 that returns Object @NonNull [] @NonNull [] @Nullable []. + */ + void callTomethod4() { + obj1 = method4(); + obj2 = method4(); + obj3 = method4(); + // :: error: (assignment.type.incompatible) + obj4 = method4(); + obj5 = method4(); + // :: error: (assignment.type.incompatible) + obj6 = method4(); + // :: error: (assignment.type.incompatible) + obj7 = method4(); + // :: error: (assignment.type.incompatible) + obj8 = method4(); + } + + /* + * Call to method 5 that returns Object @Nullable [] @Nullable [] @NonNull []. + */ + void callTomethod5() { + obj1 = method5(); + // :: error: (assignment.type.incompatible) + obj2 = method5(); + // :: error: (assignment.type.incompatible) + obj3 = method5(); + obj4 = method5(); + // :: error: (assignment.type.incompatible) + obj5 = method5(); + // :: error: (assignment.type.incompatible) + obj6 = method5(); + // :: error: (assignment.type.incompatible) + obj7 = method5(); + // :: error: (assignment.type.incompatible) + obj8 = method5(); + } + + /* + * Call to method 6 that returns Object @Nullable [] @NonNull [] @Nullable []. + */ + void callTomethod6() { + obj1 = method6(); + // :: error: (assignment.type.incompatible) + obj2 = method6(); + obj3 = method6(); + // :: error: (assignment.type.incompatible) + obj4 = method6(); + // :: error: (assignment.type.incompatible) + obj5 = method6(); + // :: error: (assignment.type.incompatible) + obj6 = method6(); + // :: error: (assignment.type.incompatible) + obj7 = method6(); + // :: error: (assignment.type.incompatible) + obj8 = method6(); + } + + /* + * Call to method 7 that returns Object @NonNull [] @Nullable [] @Nullable []. + */ + void callTomethod7() { + obj1 = method7(); + obj2 = method7(); + // :: error: (assignment.type.incompatible) + obj3 = method7(); + // :: error: (assignment.type.incompatible) + obj4 = method7(); + // :: error: (assignment.type.incompatible) + obj5 = method7(); + // :: error: (assignment.type.incompatible) + obj6 = method7(); + // :: error: (assignment.type.incompatible) + obj7 = method7(); + // :: error: (assignment.type.incompatible) + obj8 = method7(); + } + + /* + * Call to method 8 that returns Object @Nullable [] @Nullable [] @Nullable []. + */ + void callTomethod8() { + obj1 = method8(); + // :: error: (assignment.type.incompatible) + obj2 = method8(); + // :: error: (assignment.type.incompatible) + obj3 = method8(); + // :: error: (assignment.type.incompatible) + obj4 = method8(); + // :: error: (assignment.type.incompatible) + obj5 = method8(); + // :: error: (assignment.type.incompatible) + obj6 = method8(); + // :: error: (assignment.type.incompatible) + obj7 = method8(); + // :: error: (assignment.type.incompatible) + obj8 = method8(); + } + + Object[][][] method1() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [][][] method2() { + return new Object[numb][numb][numb]; + } + + Object[] @Nullable [][] method3() { + return new Object[numb][numb][numb]; + } + + Object[][] @Nullable [] method4() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [] @Nullable [][] method5() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [][] @Nullable [] method6() { + return new Object[numb][numb][numb]; + } + + Object[] @Nullable [] @Nullable [] method7() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [] @Nullable [] @Nullable [] method8() { + return new Object[numb][numb][numb]; + } } diff --git a/checker/tests/stubparser-nullness/NoExplicitAnnotations.java b/checker/tests/stubparser-nullness/NoExplicitAnnotations.java index 82e72708ca5..5aa61cf314f 100644 --- a/checker/tests/stubparser-nullness/NoExplicitAnnotations.java +++ b/checker/tests/stubparser-nullness/NoExplicitAnnotations.java @@ -4,61 +4,61 @@ public class NoExplicitAnnotations {} class NoExplicitAnnotationsSuper { - @Nullable String method1() { - return helper(); - } + @Nullable String method1() { + return helper(); + } - @Nullable String method2() { - return helper(); - } + @Nullable String method2() { + return helper(); + } - @Nullable String method3() { - return helper(); - } + @Nullable String method3() { + return helper(); + } - @Nullable String helper() { - return null; - } + @Nullable String helper() { + return null; + } } class NoExplicitAnnotationsSub1 extends NoExplicitAnnotationsSuper { - @Override - String helper() { - return "hello"; - } + @Override + String helper() { + return "hello"; + } } class NoExplicitAnnotationsSub2 extends NoExplicitAnnotationsSuper { - @Override - String helper() { - return "hello"; - } + @Override + String helper() { + return "hello"; + } } class NoExplicitAnnotationsSub3 extends NoExplicitAnnotationsSuper { - @Override - String helper() { - return "hello"; - } + @Override + String helper() { + return "hello"; + } } class NoExplicitAnnotationsUse { - @Nullable String nble = null; - @NonNull String nn = "hello"; + @Nullable String nble = null; + @NonNull String nn = "hello"; - void use( - NoExplicitAnnotationsSub1 sub1, - NoExplicitAnnotationsSub2 sub2, - NoExplicitAnnotationsSub3 sub3) { - nble = sub1.method1(); - nn = sub1.method1(); - nble = sub2.method2(); - nn = sub2.method2(); - nble = sub3.method3(); - // :: error: (assignment.type.incompatible) - nn = sub3.method3(); + void use( + NoExplicitAnnotationsSub1 sub1, + NoExplicitAnnotationsSub2 sub2, + NoExplicitAnnotationsSub3 sub3) { + nble = sub1.method1(); + nn = sub1.method1(); + nble = sub2.method2(); + nn = sub2.method2(); + nble = sub3.method3(); + // :: error: (assignment.type.incompatible) + nn = sub3.method3(); - // :: error: (assignment.type.incompatible) - nn = nble; - } + // :: error: (assignment.type.incompatible) + nn = nble; + } } diff --git a/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java b/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java index fc027aa2567..ce0c9783a7f 100644 --- a/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java +++ b/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java @@ -5,21 +5,21 @@ */ public class VarargConstructorParameterAnnotationTest { - public void strArraysNonNull(@NonNull String[] parameter) { - new ProcessBuilder(parameter); - } + public void strArraysNonNull(@NonNull String[] parameter) { + new ProcessBuilder(parameter); + } - public void strArraysNullable(@Nullable String[] parameter) { - // :: error: (argument.type.incompatible) - new ProcessBuilder(parameter); - } + public void strArraysNullable(@Nullable String[] parameter) { + // :: error: (argument.type.incompatible) + new ProcessBuilder(parameter); + } - public void strVarargNonNull(@NonNull String... parameter) { - new ProcessBuilder(parameter); - } + public void strVarargNonNull(@NonNull String... parameter) { + new ProcessBuilder(parameter); + } - public void strVarargNullable(@Nullable String... parameter) { - // :: error: (argument.type.incompatible) - new ProcessBuilder(parameter); - } + public void strVarargNullable(@Nullable String... parameter) { + // :: error: (argument.type.incompatible) + new ProcessBuilder(parameter); + } } diff --git a/checker/tests/stubparser-records/PairRecord.java b/checker/tests/stubparser-records/PairRecord.java index 0b868db501e..eb7fbf66386 100644 --- a/checker/tests/stubparser-records/PairRecord.java +++ b/checker/tests/stubparser-records/PairRecord.java @@ -1,6 +1,6 @@ record PairRecord(String key, Object value) { - PairRecord(String val) { - this("", val); - } + PairRecord(String val) { + this("", val); + } } diff --git a/checker/tests/stubparser-records/RecordStubbed.java b/checker/tests/stubparser-records/RecordStubbed.java index e04bc66620f..fc48281915e 100644 --- a/checker/tests/stubparser-records/RecordStubbed.java +++ b/checker/tests/stubparser-records/RecordStubbed.java @@ -6,7 +6,7 @@ * record but nullable in constructor and accessor via stubs */ public record RecordStubbed(@Nullable String nxx, String nsxx, Integer xnn) { - RecordStubbed(Integer a, String b, String c) { - this(c, b, a); - } + RecordStubbed(Integer a, String b, String c) { + this(c, b, a); + } } diff --git a/checker/tests/stubparser-records/RecordUsage.java b/checker/tests/stubparser-records/RecordUsage.java index 228959e2660..88258035fbd 100644 --- a/checker/tests/stubparser-records/RecordUsage.java +++ b/checker/tests/stubparser-records/RecordUsage.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.nullness.qual.NonNull; class PairUsage { - public void makePairs() { - PairRecord a = new PairRecord("key", "value"); - PairRecord b = new PairRecord(null); - // :: error: (assignment.type.incompatible) - @NonNull Object o = a.value(); - PairRecord p = new PairRecord("key", null); - } + public void makePairs() { + PairRecord a = new PairRecord("key", "value"); + PairRecord b = new PairRecord(null); + // :: error: (assignment.type.incompatible) + @NonNull Object o = a.value(); + PairRecord p = new PairRecord("key", null); + } - public void makeStubbed() { - RecordStubbed r = new RecordStubbed("a", "b", 7); - RecordStubbed r1 = new RecordStubbed("a", "b", null); - // :: error: (argument.type.incompatible) - RecordStubbed r2 = new RecordStubbed((String) null, "b", null); - // :: error: (argument.type.incompatible) - RecordStubbed r3 = new RecordStubbed("a", null, null); - @NonNull Object o = r.nxx(); - @NonNull Object o2 = r.nsxx(); - // :: error: (assignment.type.incompatible) - @NonNull Object o3 = r.xnn(); - } + public void makeStubbed() { + RecordStubbed r = new RecordStubbed("a", "b", 7); + RecordStubbed r1 = new RecordStubbed("a", "b", null); + // :: error: (argument.type.incompatible) + RecordStubbed r2 = new RecordStubbed((String) null, "b", null); + // :: error: (argument.type.incompatible) + RecordStubbed r3 = new RecordStubbed("a", null, null); + @NonNull Object o = r.nxx(); + @NonNull Object o2 = r.nsxx(); + // :: error: (assignment.type.incompatible) + @NonNull Object o3 = r.xnn(); + } } diff --git a/checker/tests/stubparser-tainting/FakeOverrideRSuper.java b/checker/tests/stubparser-tainting/FakeOverrideRSuper.java index 25a990c38a2..6af907de5b6 100644 --- a/checker/tests/stubparser-tainting/FakeOverrideRSuper.java +++ b/checker/tests/stubparser-tainting/FakeOverrideRSuper.java @@ -6,27 +6,27 @@ @SuppressWarnings("tainting") public class FakeOverrideRSuper { - public @Tainted int returnsTaintedInt() { - return 0; - } + public @Tainted int returnsTaintedInt() { + return 0; + } - public @Untainted int returnsUntaintedInt() { - return 0; - } + public @Untainted int returnsUntaintedInt() { + return 0; + } - public @Tainted int returnsTaintedIntWithFakeOverride() { - return 0; - } + public @Tainted int returnsTaintedIntWithFakeOverride() { + return 0; + } - public @Untainted int returnsUntaintedIntWithFakeOverride() { - return 0; - } + public @Untainted int returnsUntaintedIntWithFakeOverride() { + return 0; + } - public @Untainted int returnsUntaintedIntWithFakeOverride2() { - return 0; - } + public @Untainted int returnsUntaintedIntWithFakeOverride2() { + return 0; + } - public @PolyTainted int returnsPolyTaintedIntWithFakeOverride() { - return 0; - } + public @PolyTainted int returnsPolyTaintedIntWithFakeOverride() { + return 0; + } } diff --git a/checker/tests/stubparser-tainting/FakeOverrideReturn.java b/checker/tests/stubparser-tainting/FakeOverrideReturn.java index 5af14e890e4..951b71efda6 100644 --- a/checker/tests/stubparser-tainting/FakeOverrideReturn.java +++ b/checker/tests/stubparser-tainting/FakeOverrideReturn.java @@ -4,56 +4,56 @@ public class FakeOverrideReturn { - @Tainted int tf; - - @Untainted int uf; - - void m(@Tainted int t, @Untainted int u) { - - FakeOverrideRSuper sup = new FakeOverrideRSuper(); - FakeOverrideRMid mid = new FakeOverrideRMid(); - FakeOverrideRSub sub = new FakeOverrideRSub(); - - tf = sup.returnsTaintedInt(); - tf = mid.returnsTaintedInt(); - tf = sub.returnsTaintedInt(); - // :: error: (assignment.type.incompatible) - uf = sup.returnsTaintedInt(); - // :: error: (assignment.type.incompatible) - uf = mid.returnsTaintedInt(); - // :: error: (assignment.type.incompatible) - uf = sub.returnsTaintedInt(); - - tf = sup.returnsUntaintedInt(); - tf = mid.returnsUntaintedInt(); - tf = sub.returnsUntaintedInt(); - uf = sup.returnsUntaintedInt(); - uf = mid.returnsUntaintedInt(); - uf = sub.returnsUntaintedInt(); - - tf = sup.returnsTaintedIntWithFakeOverride(); - tf = mid.returnsTaintedIntWithFakeOverride(); - tf = sub.returnsTaintedIntWithFakeOverride(); - // :: error: (assignment.type.incompatible) - uf = sup.returnsTaintedIntWithFakeOverride(); - uf = mid.returnsTaintedIntWithFakeOverride(); - uf = sub.returnsTaintedIntWithFakeOverride(); - - tf = sup.returnsUntaintedIntWithFakeOverride(); - tf = mid.returnsUntaintedIntWithFakeOverride(); - tf = sub.returnsUntaintedIntWithFakeOverride(); - uf = sup.returnsUntaintedIntWithFakeOverride(); - // :: error: (assignment.type.incompatible) - uf = mid.returnsUntaintedIntWithFakeOverride(); - // :: error: (assignment.type.incompatible) - uf = sub.returnsUntaintedIntWithFakeOverride(); - } - - void poly() { - FakeOverrideRSuper sup = new FakeOverrideRSuper(); - FakeOverrideRMid mid = new FakeOverrideRMid(); - - @Untainted int j = mid.returnsUntaintedIntWithFakeOverride2(); - @Untainted int k = sup.returnsPolyTaintedIntWithFakeOverride(); - } + @Tainted int tf; + + @Untainted int uf; + + void m(@Tainted int t, @Untainted int u) { + + FakeOverrideRSuper sup = new FakeOverrideRSuper(); + FakeOverrideRMid mid = new FakeOverrideRMid(); + FakeOverrideRSub sub = new FakeOverrideRSub(); + + tf = sup.returnsTaintedInt(); + tf = mid.returnsTaintedInt(); + tf = sub.returnsTaintedInt(); + // :: error: (assignment.type.incompatible) + uf = sup.returnsTaintedInt(); + // :: error: (assignment.type.incompatible) + uf = mid.returnsTaintedInt(); + // :: error: (assignment.type.incompatible) + uf = sub.returnsTaintedInt(); + + tf = sup.returnsUntaintedInt(); + tf = mid.returnsUntaintedInt(); + tf = sub.returnsUntaintedInt(); + uf = sup.returnsUntaintedInt(); + uf = mid.returnsUntaintedInt(); + uf = sub.returnsUntaintedInt(); + + tf = sup.returnsTaintedIntWithFakeOverride(); + tf = mid.returnsTaintedIntWithFakeOverride(); + tf = sub.returnsTaintedIntWithFakeOverride(); + // :: error: (assignment.type.incompatible) + uf = sup.returnsTaintedIntWithFakeOverride(); + uf = mid.returnsTaintedIntWithFakeOverride(); + uf = sub.returnsTaintedIntWithFakeOverride(); + + tf = sup.returnsUntaintedIntWithFakeOverride(); + tf = mid.returnsUntaintedIntWithFakeOverride(); + tf = sub.returnsUntaintedIntWithFakeOverride(); + uf = sup.returnsUntaintedIntWithFakeOverride(); + // :: error: (assignment.type.incompatible) + uf = mid.returnsUntaintedIntWithFakeOverride(); + // :: error: (assignment.type.incompatible) + uf = sub.returnsUntaintedIntWithFakeOverride(); + } + + void poly() { + FakeOverrideRSuper sup = new FakeOverrideRSuper(); + FakeOverrideRMid mid = new FakeOverrideRMid(); + + @Untainted int j = mid.returnsUntaintedIntWithFakeOverride2(); + @Untainted int k = sup.returnsPolyTaintedIntWithFakeOverride(); + } } diff --git a/checker/tests/stubparser-tainting/TypeParamWithInner.java b/checker/tests/stubparser-tainting/TypeParamWithInner.java index 6a96162f7ed..e491bc0cb59 100644 --- a/checker/tests/stubparser-tainting/TypeParamWithInner.java +++ b/checker/tests/stubparser-tainting/TypeParamWithInner.java @@ -3,9 +3,9 @@ // T extends @Untainted String in stub file. Tests bug where the presence of an inner class causes // annotations on type variables to be forgotten. public class TypeParamWithInner { - public class Inner {} + public class Inner {} - public void requiresUntainted(T param) { - @Untainted String s = param; - } + public void requiresUntainted(T param) { + @Untainted String s = param; + } } diff --git a/checker/tests/tainting/AnonymousProblem.java b/checker/tests/tainting/AnonymousProblem.java index dac7b2d4a78..a1b78a76c6a 100644 --- a/checker/tests/tainting/AnonymousProblem.java +++ b/checker/tests/tainting/AnonymousProblem.java @@ -1,5 +1,5 @@ import java.nio.file.SimpleFileVisitor; public class AnonymousProblem { - SimpleFileVisitor s = new SimpleFileVisitor() {}; + SimpleFileVisitor s = new SimpleFileVisitor() {}; } diff --git a/checker/tests/tainting/Buffer.java b/checker/tests/tainting/Buffer.java index c3d2b1bc534..79acdebdbe5 100644 --- a/checker/tests/tainting/Buffer.java +++ b/checker/tests/tainting/Buffer.java @@ -1,84 +1,83 @@ +import java.util.ArrayList; +import java.util.List; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.framework.qual.HasQualifierParameter; -import java.util.ArrayList; -import java.util.List; - @HasQualifierParameter(Tainted.class) public class Buffer { - final List<@PolyTainted String> list = new ArrayList<>(); - @PolyTainted String someString = ""; - // :: error: (invalid.polymorphic.qualifier.use) - static @PolyTainted Object staticField; + final List<@PolyTainted String> list = new ArrayList<>(); + @PolyTainted String someString = ""; + // :: error: (invalid.polymorphic.qualifier.use) + static @PolyTainted Object staticField; - public @PolyTainted Buffer() {} + public @PolyTainted Buffer() {} - public @Untainted Buffer(@Tainted String s) { - // :: error: (assignment.type.incompatible) - this.someString = s; - } + public @Untainted Buffer(@Tainted String s) { + // :: error: (assignment.type.incompatible) + this.someString = s; + } - public @PolyTainted Buffer(@PolyTainted Buffer copy) {} + public @PolyTainted Buffer(@PolyTainted Buffer copy) {} - public @PolyTainted Buffer append(@PolyTainted Buffer this, @PolyTainted String s) { - list.add(s); - someString = s; - return this; - } + public @PolyTainted Buffer append(@PolyTainted Buffer this, @PolyTainted String s) { + list.add(s); + someString = s; + return this; + } - public @PolyTainted String prettyPrint(@PolyTainted Buffer this) { - String prettyString = ""; - for (String s : list) { - prettyString += s + " ~~ "; - } - return prettyString; + public @PolyTainted String prettyPrint(@PolyTainted Buffer this) { + String prettyString = ""; + for (String s : list) { + prettyString += s + " ~~ "; } + return prettyString; + } - public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { - // :: error: (argument.type.incompatible) - list.add(s); - // :: error: (assignment.type.incompatible) - someString = s; - return s; - } + public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { + // :: error: (argument.type.incompatible) + list.add(s); + // :: error: (assignment.type.incompatible) + someString = s; + return s; + } - static class Use { - void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { - buffer.list.add(untainted); - buffer.someString = untainted; - buffer.append(untainted); - } + static class Use { + void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { + buffer.list.add(untainted); + buffer.someString = untainted; + buffer.append(untainted); + } - void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { - // :: error: (argument.type.incompatible) - buffer.list.add(tainted); - // :: error: (assignment.type.incompatible) - buffer.someString = tainted; - // :: error: (argument.type.incompatible) - buffer.append(tainted); - } + void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { + // :: error: (argument.type.incompatible) + buffer.list.add(tainted); + // :: error: (assignment.type.incompatible) + buffer.someString = tainted; + // :: error: (argument.type.incompatible) + buffer.append(tainted); + } - void casts(@Untainted Object untainted, @Tainted Object tainted) { - @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Untainted Buffer b2 = (@Untainted Buffer) tainted; + void casts(@Untainted Object untainted, @Tainted Object tainted) { + @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Untainted Buffer b2 = (@Untainted Buffer) tainted; - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error - @Untainted Buffer b5 = (Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b6 = (Buffer) tainted; - } + @Untainted Buffer b5 = (Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b6 = (Buffer) tainted; + } - void creation() { - @Untainted Buffer b1 = new @Untainted Buffer(); - @Tainted Buffer b2 = new @Tainted Buffer(); - @PolyTainted Buffer b3 = new @PolyTainted Buffer(); - } + void creation() { + @Untainted Buffer b1 = new @Untainted Buffer(); + @Tainted Buffer b2 = new @Tainted Buffer(); + @PolyTainted Buffer b3 = new @PolyTainted Buffer(); } + } } diff --git a/checker/tests/tainting/CaptureSubtype.java b/checker/tests/tainting/CaptureSubtype.java index 086ace7e52c..10c729097cf 100644 --- a/checker/tests/tainting/CaptureSubtype.java +++ b/checker/tests/tainting/CaptureSubtype.java @@ -3,15 +3,14 @@ public class CaptureSubtype { - class MyGeneric {} + class MyGeneric {} - class SubGeneric extends MyGeneric {} + class SubGeneric extends MyGeneric {} - class UseMyGeneric { - SubGeneric wildcardUnbounded = - new SubGeneric<@Untainted Number>(); + class UseMyGeneric { + SubGeneric wildcardUnbounded = new SubGeneric<@Untainted Number>(); - MyGeneric wildcardOutsideUB = wildcardUnbounded; - MyGeneric wildcardInsideUB2 = wildcardUnbounded; - } + MyGeneric wildcardOutsideUB = wildcardUnbounded; + MyGeneric wildcardInsideUB2 = wildcardUnbounded; + } } diff --git a/checker/tests/tainting/CaptureSubtype2.java b/checker/tests/tainting/CaptureSubtype2.java index e8080626ee2..95785eef19e 100644 --- a/checker/tests/tainting/CaptureSubtype2.java +++ b/checker/tests/tainting/CaptureSubtype2.java @@ -1,28 +1,25 @@ -import org.checkerframework.checker.tainting.qual.Untainted; - import java.util.function.Function; +import org.checkerframework.checker.tainting.qual.Untainted; public class CaptureSubtype2 { - interface FFunction extends Function {} + interface FFunction extends Function {} - interface DInterface {} + interface DInterface {} - interface MInterface

{} + interface MInterface

{} - interface QInterface, V extends MInterface

, P> {} + interface QInterface, V extends MInterface

, P> {} - FFunction> r; + FFunction> r; - CaptureSubtype2( - FFunction< - String, - QInterface< - ? extends MInterface, - ? extends MInterface, - DInterface>> - r) { - // :: error: (assignment.type.incompatible) - this.r = r; - } + CaptureSubtype2( + FFunction< + String, + QInterface< + ? extends MInterface, ? extends MInterface, DInterface>> + r) { + // :: error: (assignment.type.incompatible) + this.r = r; + } } diff --git a/checker/tests/tainting/Casts.java b/checker/tests/tainting/Casts.java index 2713c0c67bc..8fd5c998ad0 100644 --- a/checker/tests/tainting/Casts.java +++ b/checker/tests/tainting/Casts.java @@ -3,68 +3,68 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class Casts { - @HasQualifierParameter(Tainted.class) - static class Buffer {} + @HasQualifierParameter(Tainted.class) + static class Buffer {} - @HasQualifierParameter(Tainted.class) - static class MyBuffer extends Buffer {} + @HasQualifierParameter(Tainted.class) + static class MyBuffer extends Buffer {} - void test( - @Tainted Buffer taintedBuf, - @Untainted Buffer untaintedBuf, - @Tainted Object taintedObj, - @Untainted Object untaintedObj) { - @Tainted Object o = (@Tainted Object) taintedBuf; - o = (@Tainted Object) untaintedObj; - o = (@Tainted Object) untaintedBuf; + void test( + @Tainted Buffer taintedBuf, + @Untainted Buffer untaintedBuf, + @Tainted Object taintedObj, + @Untainted Object untaintedObj) { + @Tainted Object o = (@Tainted Object) taintedBuf; + o = (@Tainted Object) untaintedObj; + o = (@Tainted Object) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) taintedObj; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedObj; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) taintedObj; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedObj; - // :: warning: (cast.unsafe) - o = (@Untainted Object) taintedObj; - // :: warning: (cast.unsafe) - o = (@Untainted Object) taintedBuf; - o = (@Untainted Object) untaintedBuf; + // :: warning: (cast.unsafe) + o = (@Untainted Object) taintedObj; + // :: warning: (cast.unsafe) + o = (@Untainted Object) taintedBuf; + o = (@Untainted Object) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedObj; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedBuf; - o = (@Untainted Buffer) untaintedObj; - } + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedObj; + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedBuf; + o = (@Untainted Buffer) untaintedObj; + } - void test2( - @Tainted Buffer taintedBuf, - @Untainted Buffer untaintedBuf, - @Tainted MyBuffer taintedMyBuf, - @Untainted MyBuffer untaintedMyBuff) { - @Tainted Object o = (@Tainted Buffer) taintedMyBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedMyBuff; + void test2( + @Tainted Buffer taintedBuf, + @Untainted Buffer untaintedBuf, + @Tainted MyBuffer taintedMyBuf, + @Untainted MyBuffer untaintedMyBuff) { + @Tainted Object o = (@Tainted Buffer) taintedMyBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedMyBuff; - o = (@Tainted MyBuffer) taintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted MyBuffer) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted MyBuffer) untaintedMyBuff; + o = (@Tainted MyBuffer) taintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted MyBuffer) untaintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted MyBuffer) untaintedMyBuff; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedMyBuf; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedMyBuf; - o = (@Untainted Buffer) untaintedMyBuff; + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedMyBuf; + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedMyBuf; + o = (@Untainted Buffer) untaintedMyBuff; - // :: error: (invariant.cast.unsafe) - o = (@Untainted MyBuffer) taintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Untainted MyBuffer) taintedMyBuf; - o = (@Untainted MyBuffer) untaintedMyBuff; - } + // :: error: (invariant.cast.unsafe) + o = (@Untainted MyBuffer) taintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Untainted MyBuffer) taintedMyBuf; + o = (@Untainted MyBuffer) untaintedMyBuff; + } } diff --git a/checker/tests/tainting/ClassQPTypeVarTest.java b/checker/tests/tainting/ClassQPTypeVarTest.java index 18d1b6db016..1be952d2d77 100644 --- a/checker/tests/tainting/ClassQPTypeVarTest.java +++ b/checker/tests/tainting/ClassQPTypeVarTest.java @@ -4,34 +4,34 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class ClassQPTypeVarTest { - @HasQualifierParameter(Tainted.class) - interface Buffer { - void append(@PolyTainted String s); - } + @HasQualifierParameter(Tainted.class) + interface Buffer { + void append(@PolyTainted String s); + } - @Tainted T cast(T param) { - return param; - } + @Tainted T cast(T param) { + return param; + } - void bug(@Untainted Buffer b, @Tainted String s) { - // :: error: (argument.type.incompatible) - b.append(s); - // :: error: (type.argument.invalid.hasqualparam) - cast(b).append(s); - } + void bug(@Untainted Buffer b, @Tainted String s) { + // :: error: (argument.type.incompatible) + b.append(s); + // :: error: (type.argument.invalid.hasqualparam) + cast(b).append(s); + } - @Tainted T castBuffer(T param) { - return param; - } + @Tainted T castBuffer(T param) { + return param; + } - T identity(T param) { - @Tainted Buffer b = param; - return param; // ok - } + T identity(T param) { + @Tainted Buffer b = param; + return param; // ok + } - void use(@Untainted Buffer ub, @Tainted Buffer tb) { - // :: error: (type.argument.type.incompatible) - identity(ub); - identity(tb); // ok - } + void use(@Untainted Buffer ub, @Tainted Buffer tb) { + // :: error: (type.argument.type.incompatible) + identity(ub); + identity(tb); // ok + } } diff --git a/checker/tests/tainting/EnumTypeArgs.java b/checker/tests/tainting/EnumTypeArgs.java index 9742e75a806..bd1c46a7cd1 100644 --- a/checker/tests/tainting/EnumTypeArgs.java +++ b/checker/tests/tainting/EnumTypeArgs.java @@ -3,12 +3,12 @@ public class EnumTypeArgs { - enum MyEnum { - CONST1, - CONST2, - } + enum MyEnum { + CONST1, + CONST2, + } - void method(@Untainted MyEnum e1, @Tainted MyEnum e2) { - e1.compareTo(e2); - } + void method(@Untainted MyEnum e1, @Tainted MyEnum e2) { + e1.compareTo(e2); + } } diff --git a/checker/tests/tainting/ExtendHasQual.java b/checker/tests/tainting/ExtendHasQual.java index 3b4f643f23b..7c1af290fe5 100644 --- a/checker/tests/tainting/ExtendHasQual.java +++ b/checker/tests/tainting/ExtendHasQual.java @@ -4,38 +4,38 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class ExtendHasQual { - static class Super { - @SuppressWarnings("super.invocation.invalid") - @Untainted Super() {} - } + static class Super { + @SuppressWarnings("super.invocation.invalid") + @Untainted Super() {} + } - @HasQualifierParameter(Tainted.class) - static class Buffer extends Super {} + @HasQualifierParameter(Tainted.class) + static class Buffer extends Super {} - static class MyBuffer1 extends Buffer {} + static class MyBuffer1 extends Buffer {} - @HasQualifierParameter(Tainted.class) - static class MyBuffer2 extends Buffer {} + @HasQualifierParameter(Tainted.class) + static class MyBuffer2 extends Buffer {} - @HasQualifierParameter(Nullable.class) - // :: error: (missing.has.qual.param) - static class MyBuffer3 extends Buffer {} + @HasQualifierParameter(Nullable.class) + // :: error: (missing.has.qual.param) + static class MyBuffer3 extends Buffer {} - @HasQualifierParameter({Tainted.class, Nullable.class}) - static class MyBuffer4 extends Buffer {} + @HasQualifierParameter({Tainted.class, Nullable.class}) + static class MyBuffer4 extends Buffer {} - @HasQualifierParameter(Tainted.class) - interface BufferInterface {} + @HasQualifierParameter(Tainted.class) + interface BufferInterface {} - static class ImplementsBufferInterface1 implements BufferInterface {} + static class ImplementsBufferInterface1 implements BufferInterface {} - @HasQualifierParameter(Tainted.class) - static class ImplementsBufferInterface2 implements BufferInterface {} + @HasQualifierParameter(Tainted.class) + static class ImplementsBufferInterface2 implements BufferInterface {} - static class Both1 extends Buffer implements BufferInterface {} + static class Both1 extends Buffer implements BufferInterface {} - @HasQualifierParameter(Tainted.class) - static class Both2 extends Buffer implements BufferInterface {} + @HasQualifierParameter(Tainted.class) + static class Both2 extends Buffer implements BufferInterface {} - static class Both3 extends Super implements BufferInterface {} + static class Both3 extends Super implements BufferInterface {} } diff --git a/checker/tests/tainting/ExtendsAndAnnotation.java b/checker/tests/tainting/ExtendsAndAnnotation.java index f569bff3c1d..f49bfe90d10 100644 --- a/checker/tests/tainting/ExtendsAndAnnotation.java +++ b/checker/tests/tainting/ExtendsAndAnnotation.java @@ -6,14 +6,14 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class ExtendsAndAnnotation extends @Tainted Object { - void test(@Untainted ExtendsAndAnnotation c) { - // :: warning: (cast.unsafe.constructor.invocation) - Object o = new @Untainted ExtendsAndAnnotation(); - o = new @Tainted ExtendsAndAnnotation(); - } + void test(@Untainted ExtendsAndAnnotation c) { + // :: warning: (cast.unsafe.constructor.invocation) + Object o = new @Untainted ExtendsAndAnnotation(); + o = new @Tainted ExtendsAndAnnotation(); + } - @HasQualifierParameter(Tainted.class) - // :: error: (invalid.polymorphic.qualifier) - // :: error: (declaration.inconsistent.with.extends.clause) - static class Banana extends @PolyTainted Object {} + @HasQualifierParameter(Tainted.class) + // :: error: (invalid.polymorphic.qualifier) + // :: error: (declaration.inconsistent.with.extends.clause) + static class Banana extends @PolyTainted Object {} } diff --git a/checker/tests/tainting/GenericsEnclosing.java b/checker/tests/tainting/GenericsEnclosing.java index 45e1773a7b1..dc06dbc6250 100644 --- a/checker/tests/tainting/GenericsEnclosing.java +++ b/checker/tests/tainting/GenericsEnclosing.java @@ -7,21 +7,21 @@ *

Also see all-systems/GenericsEnclosing for the type-system independent test. */ class MyG { - X f; + X f; - void m(X p) {} + void m(X p) {} } class ExtMyG extends MyG<@Untainted Object> { - class EInner1 { - class EInner2 { - void bar() { - // :: error: (assignment.type.incompatible) - f = 1; - m("test"); - // :: error: (argument.type.incompatible) - m(1); - } - } + class EInner1 { + class EInner2 { + void bar() { + // :: error: (assignment.type.incompatible) + f = 1; + m("test"); + // :: error: (argument.type.incompatible) + m(1); + } } + } } diff --git a/checker/tests/tainting/HasQualParamDefaults.java b/checker/tests/tainting/HasQualParamDefaults.java index d62dd15d03c..6a6b7d8e48e 100644 --- a/checker/tests/tainting/HasQualParamDefaults.java +++ b/checker/tests/tainting/HasQualParamDefaults.java @@ -1,157 +1,156 @@ +import java.util.ArrayList; +import java.util.List; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.framework.qual.HasQualifierParameter; -import java.util.ArrayList; -import java.util.List; - public class HasQualParamDefaults { - @HasQualifierParameter(Tainted.class) - public class Buffer { - final List<@PolyTainted String> list = new ArrayList<>(); - @PolyTainted String someString = ""; - - public Buffer() {} - - public @Untainted Buffer(@Tainted String s) { - // :: error: (assignment.type.incompatible) - this.someString = s; - } - - public Buffer(Buffer copy) { - this.list.addAll(copy.list); - this.someString = copy.someString; - } - - public Buffer append(@PolyTainted String s) { - list.add(s); - someString = s; - return this; - } - - public @PolyTainted String prettyPrint() { - String prettyString = list.get(1); - for (@PolyTainted String s : list) { - prettyString += s + " ~~ "; - } - return prettyString; - } - - public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { - // :: error: (argument.type.incompatible) - list.add(s); - // :: error: (assignment.type.incompatible) - someString = s; - return s; - } - - void initializeLocalTainted(@Tainted Buffer b) { - Buffer local = b; - @Tainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Untainted Buffer copy2 = local; - } - - void initializeLocalUntainted(@Untainted Buffer b) { - Buffer local = b; - @Untainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Tainted Buffer copy2 = local; - } - - void initializeLocalPolyTainted(@PolyTainted Buffer b) { - Buffer local = b; - @PolyTainted Buffer copy = local; - } - - void noInitializer(@Untainted Buffer b) { - Buffer local; - // :: error: (assignment.type.incompatible) - local = b; - } - } - - class Use { - void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { - buffer.list.add(untainted); - buffer.someString = untainted; - buffer.append(untainted); - } - - void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { - // :: error: (argument.type.incompatible) - buffer.list.add(tainted); - // :: error: (assignment.type.incompatible) - buffer.someString = tainted; - // :: error: (argument.type.incompatible) - buffer.append(tainted); - } - - void casts(@Untainted Object untainted, @Tainted Object tainted) { - @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Untainted Buffer b2 = (@Untainted Buffer) tainted; - - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error - - @Untainted Buffer b5 = (Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b6 = (Buffer) tainted; - } - - void creation() { - @Untainted Buffer b1 = new @Untainted Buffer(); - @Tainted Buffer b2 = new @Tainted Buffer(); - @PolyTainted Buffer b3 = new @PolyTainted Buffer(); - } - } - - // For classes with @HasQualifierParameter, different defaulting rules are applied on that type - // inside the class body and outside the class body, so local variables need to be tested - // outside the class as well. - class LocalVars { - void initializeLocalTainted(@Tainted Buffer b) { - Buffer local = b; - @Tainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Untainted Buffer copy2 = local; - } - - void initializeLocalUntainted(@Untainted Buffer b) { - Buffer local = b; - @Untainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Tainted Buffer copy2 = local; - } - - void initializeLocalPolyTainted(@PolyTainted Buffer b) { - Buffer local = b; - @PolyTainted Buffer copy = local; - } - - void noInitializer(@Untainted Buffer b) { - Buffer local; - // :: error: (assignment.type.incompatible) - local = b; - } - - // These next two cases test circular dependencies. Calculating the type of a local variable - // looks at the type of initializer, but if the type of the initializer depends on the type - // of the variable, then infinite recursion could occur. - - void testTypeVariableInference() { - GenericWithQualParam set = new GenericWithQualParam<>(); - } - - void testVariableInOwnInitializer() { - Buffer b = (b = null); - } - } - - @HasQualifierParameter(Tainted.class) - static class GenericWithQualParam {} + @HasQualifierParameter(Tainted.class) + public class Buffer { + final List<@PolyTainted String> list = new ArrayList<>(); + @PolyTainted String someString = ""; + + public Buffer() {} + + public @Untainted Buffer(@Tainted String s) { + // :: error: (assignment.type.incompatible) + this.someString = s; + } + + public Buffer(Buffer copy) { + this.list.addAll(copy.list); + this.someString = copy.someString; + } + + public Buffer append(@PolyTainted String s) { + list.add(s); + someString = s; + return this; + } + + public @PolyTainted String prettyPrint() { + String prettyString = list.get(1); + for (@PolyTainted String s : list) { + prettyString += s + " ~~ "; + } + return prettyString; + } + + public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { + // :: error: (argument.type.incompatible) + list.add(s); + // :: error: (assignment.type.incompatible) + someString = s; + return s; + } + + void initializeLocalTainted(@Tainted Buffer b) { + Buffer local = b; + @Tainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Untainted Buffer copy2 = local; + } + + void initializeLocalUntainted(@Untainted Buffer b) { + Buffer local = b; + @Untainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Tainted Buffer copy2 = local; + } + + void initializeLocalPolyTainted(@PolyTainted Buffer b) { + Buffer local = b; + @PolyTainted Buffer copy = local; + } + + void noInitializer(@Untainted Buffer b) { + Buffer local; + // :: error: (assignment.type.incompatible) + local = b; + } + } + + class Use { + void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { + buffer.list.add(untainted); + buffer.someString = untainted; + buffer.append(untainted); + } + + void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { + // :: error: (argument.type.incompatible) + buffer.list.add(tainted); + // :: error: (assignment.type.incompatible) + buffer.someString = tainted; + // :: error: (argument.type.incompatible) + buffer.append(tainted); + } + + void casts(@Untainted Object untainted, @Tainted Object tainted) { + @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Untainted Buffer b2 = (@Untainted Buffer) tainted; + + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error + + @Untainted Buffer b5 = (Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b6 = (Buffer) tainted; + } + + void creation() { + @Untainted Buffer b1 = new @Untainted Buffer(); + @Tainted Buffer b2 = new @Tainted Buffer(); + @PolyTainted Buffer b3 = new @PolyTainted Buffer(); + } + } + + // For classes with @HasQualifierParameter, different defaulting rules are applied on that type + // inside the class body and outside the class body, so local variables need to be tested + // outside the class as well. + class LocalVars { + void initializeLocalTainted(@Tainted Buffer b) { + Buffer local = b; + @Tainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Untainted Buffer copy2 = local; + } + + void initializeLocalUntainted(@Untainted Buffer b) { + Buffer local = b; + @Untainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Tainted Buffer copy2 = local; + } + + void initializeLocalPolyTainted(@PolyTainted Buffer b) { + Buffer local = b; + @PolyTainted Buffer copy = local; + } + + void noInitializer(@Untainted Buffer b) { + Buffer local; + // :: error: (assignment.type.incompatible) + local = b; + } + + // These next two cases test circular dependencies. Calculating the type of a local variable + // looks at the type of initializer, but if the type of the initializer depends on the type + // of the variable, then infinite recursion could occur. + + void testTypeVariableInference() { + GenericWithQualParam set = new GenericWithQualParam<>(); + } + + void testVariableInOwnInitializer() { + Buffer b = (b = null); + } + } + + @HasQualifierParameter(Tainted.class) + static class GenericWithQualParam {} } diff --git a/checker/tests/tainting/InheritQualifierParameter.java b/checker/tests/tainting/InheritQualifierParameter.java index 3da5f7da47f..5d77712a21c 100644 --- a/checker/tests/tainting/InheritQualifierParameter.java +++ b/checker/tests/tainting/InheritQualifierParameter.java @@ -7,10 +7,10 @@ public class InheritQualifierParameter {} class SubHasQualifierParameter extends InheritQualifierParameter { - void test(@Untainted SubHasQualifierParameter arg) { - // :: error: (assignment.type.incompatible) - @Tainted SubHasQualifierParameter local = arg; - } + void test(@Untainted SubHasQualifierParameter arg) { + // :: error: (assignment.type.incompatible) + @Tainted SubHasQualifierParameter local = arg; + } } @NoQualifierParameter(Tainted.class) @@ -21,9 +21,9 @@ class SubHasQualifierParameter1 extends InheritQualifierParameter {} class InheritNoQualifierParameter {} class SubNoQualifierParameter extends InheritNoQualifierParameter { - void test(@Untainted SubNoQualifierParameter arg) { - @Tainted SubNoQualifierParameter local = arg; - } + void test(@Untainted SubNoQualifierParameter arg) { + @Tainted SubNoQualifierParameter local = arg; + } } @HasQualifierParameter(Tainted.class) diff --git a/checker/tests/tainting/InitializerDataflow.java b/checker/tests/tainting/InitializerDataflow.java index 73d1f484e98..7e3afe0b760 100644 --- a/checker/tests/tainting/InitializerDataflow.java +++ b/checker/tests/tainting/InitializerDataflow.java @@ -4,20 +4,20 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class InitializerDataflow { - @HasQualifierParameter(Tainted.class) - static class Buffer {} + @HasQualifierParameter(Tainted.class) + static class Buffer {} - @PolyTainted Buffer id(@PolyTainted String s) { - return null; - } + @PolyTainted Buffer id(@PolyTainted String s) { + return null; + } - void methodBuffer(@Untainted String s) { - Buffer b1 = id(s); + void methodBuffer(@Untainted String s) { + Buffer b1 = id(s); - String local = s; - Buffer b2 = id(local); + String local = s; + Buffer b2 = id(local); - @Untainted String local2 = s; - Buffer b3 = id(local2); - } + @Untainted String local2 = s; + Buffer b3 = id(local2); + } } diff --git a/checker/tests/tainting/InnerHasQualifierParameter.java b/checker/tests/tainting/InnerHasQualifierParameter.java index 17b241271fe..34af014eb14 100644 --- a/checker/tests/tainting/InnerHasQualifierParameter.java +++ b/checker/tests/tainting/InnerHasQualifierParameter.java @@ -4,15 +4,15 @@ @HasQualifierParameter(Tainted.class) public class InnerHasQualifierParameter { - @HasQualifierParameter(Tainted.class) - interface TestInterface { - public void testMethod(); - } + @HasQualifierParameter(Tainted.class) + interface TestInterface { + public void testMethod(); + } - public void test() { - TestInterface test = - new TestInterface() { - public void testMethod() {} - }; - } + public void test() { + TestInterface test = + new TestInterface() { + public void testMethod() {} + }; + } } diff --git a/checker/tests/tainting/Issue1111.java b/checker/tests/tainting/Issue1111.java index 9578f6ab5c2..8e382454b85 100644 --- a/checker/tests/tainting/Issue1111.java +++ b/checker/tests/tainting/Issue1111.java @@ -2,22 +2,21 @@ // https://github.com/typetools/checker-framework/issues/1111 // Additional test case in framework/tests/all-systems/Issue1111.java -import org.checkerframework.checker.tainting.qual.Untainted; - import java.util.List; +import org.checkerframework.checker.tainting.qual.Untainted; public class Issue1111 { - void foo(Box box, List list) { - // :: error: (argument.type.incompatible) - bar(box, list); - } + void foo(Box box, List list) { + // :: error: (argument.type.incompatible) + bar(box, list); + } - void foo2(Box<@Untainted ? super Integer> box, List list) { - // :: error: (argument.type.incompatible) - bar(box, list); - } + void foo2(Box<@Untainted ? super Integer> box, List list) { + // :: error: (argument.type.incompatible) + bar(box, list); + } - void bar(Box box, Iterable list) {} + void bar(Box box, Iterable list) {} - class Box {} + class Box {} } diff --git a/checker/tests/tainting/Issue1705.java b/checker/tests/tainting/Issue1705.java index 871b788feb6..351fbc31a75 100644 --- a/checker/tests/tainting/Issue1705.java +++ b/checker/tests/tainting/Issue1705.java @@ -1,27 +1,26 @@ // Test case for Issue 1705 // https://github.com/typetools/checker-framework/issues/1705 +import java.util.function.Function; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Untainted; -import java.util.function.Function; - public class Issue1705 { - static class MySecondClass { - @PolyTainted MySecondClass doOnComplete(@PolyTainted MySecondClass this) { - throw new RuntimeException(); - } + static class MySecondClass { + @PolyTainted MySecondClass doOnComplete(@PolyTainted MySecondClass this) { + throw new RuntimeException(); } + } - @PolyTainted R to(@PolyTainted Issue1705 this, Function arg0) { - throw new RuntimeException(); - } + @PolyTainted R to(@PolyTainted Issue1705 this, Function arg0) { + throw new RuntimeException(); + } - static Function> empty() { - throw new RuntimeException(); - } + static Function> empty() { + throw new RuntimeException(); + } - void test(@Untainted Issue1705 a) { - @Untainted Object z = a.to(empty()).doOnComplete(); - } + void test(@Untainted Issue1705 a) { + @Untainted Object z = a.to(empty()).doOnComplete(); + } } diff --git a/checker/tests/tainting/Issue1942.java b/checker/tests/tainting/Issue1942.java index b9087f418f5..8b874798554 100644 --- a/checker/tests/tainting/Issue1942.java +++ b/checker/tests/tainting/Issue1942.java @@ -1,19 +1,18 @@ -import org.checkerframework.checker.tainting.qual.Untainted; - import java.util.List; +import org.checkerframework.checker.tainting.qual.Untainted; public class Issue1942 { - public interface LoadableExpression {} + public interface LoadableExpression {} - abstract static class OperatorSection> { - abstract A makeExpression(List<@Untainted A> expressions); - } + abstract static class OperatorSection> { + abstract A makeExpression(List<@Untainted A> expressions); + } - static class BinaryOperatorSection> extends OperatorSection { - @Override - // Override used to fail. - B makeExpression(List<@Untainted B> expressions) { - throw new RuntimeException(""); - } + static class BinaryOperatorSection> extends OperatorSection { + @Override + // Override used to fail. + B makeExpression(List<@Untainted B> expressions) { + throw new RuntimeException(""); } + } } diff --git a/checker/tests/tainting/Issue2107.java b/checker/tests/tainting/Issue2107.java index e2436174643..6dc0f1f14dc 100644 --- a/checker/tests/tainting/Issue2107.java +++ b/checker/tests/tainting/Issue2107.java @@ -2,13 +2,13 @@ public abstract class Issue2107 { - abstract @PolyTainted int method(@PolyTainted Issue2107 this); + abstract @PolyTainted int method(@PolyTainted Issue2107 this); - @PolyTainted int method2(@PolyTainted Issue2107 this) { - return this.method(); - } + @PolyTainted int method2(@PolyTainted Issue2107 this) { + return this.method(); + } - @PolyTainted int method3(@PolyTainted Issue2107 this) { - return method(); - } + @PolyTainted int method3(@PolyTainted Issue2107 this) { + return method(); + } } diff --git a/checker/tests/tainting/Issue2156.java b/checker/tests/tainting/Issue2156.java index 2372d3b14c6..3a09e369863 100644 --- a/checker/tests/tainting/Issue2156.java +++ b/checker/tests/tainting/Issue2156.java @@ -7,16 +7,16 @@ import org.checkerframework.checker.tainting.qual.Untainted; enum SampleEnum { - @Untainted FIRST, - @Tainted SECOND; + @Untainted FIRST, + @Tainted SECOND; } public class Issue2156 { - void test() { - requireUntainted(SampleEnum.FIRST); - // :: error: (assignment.type.incompatible) - requireUntainted(SampleEnum.SECOND); - } + void test() { + requireUntainted(SampleEnum.FIRST); + // :: error: (assignment.type.incompatible) + requireUntainted(SampleEnum.SECOND); + } - void requireUntainted(@Untainted SampleEnum sEnum) {} + void requireUntainted(@Untainted SampleEnum sEnum) {} } diff --git a/checker/tests/tainting/Issue2159.java b/checker/tests/tainting/Issue2159.java index f5fa4202554..1d569780f2a 100644 --- a/checker/tests/tainting/Issue2159.java +++ b/checker/tests/tainting/Issue2159.java @@ -3,29 +3,29 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue2159 { - @Tainted Issue2159() {} + @Tainted Issue2159() {} - static class MyClass extends Issue2159 { - MyClass() {} + static class MyClass extends Issue2159 { + MyClass() {} - // :: error: (super.invocation.invalid) - @PolyTainted MyClass(@PolyTainted Object x) {} + // :: error: (super.invocation.invalid) + @PolyTainted MyClass(@PolyTainted Object x) {} - void testPolyTaintedLocal( - @PolyTainted Object input, @Untainted Object untainted, @Tainted Object tainted) { - // :: warning: (cast.unsafe) - @PolyTainted Object local = (@PolyTainted MyClass) new MyClass(); - // :: warning: (cast.unsafe.constructor.invocation) - @PolyTainted Object local1 = new @PolyTainted MyClass(); - // :: warning: (cast.unsafe.constructor.invocation) - @Untainted Object local2 = new @Untainted MyClass(); + void testPolyTaintedLocal( + @PolyTainted Object input, @Untainted Object untainted, @Tainted Object tainted) { + // :: warning: (cast.unsafe) + @PolyTainted Object local = (@PolyTainted MyClass) new MyClass(); + // :: warning: (cast.unsafe.constructor.invocation) + @PolyTainted Object local1 = new @PolyTainted MyClass(); + // :: warning: (cast.unsafe.constructor.invocation) + @Untainted Object local2 = new @Untainted MyClass(); - @PolyTainted Object local3 = new @PolyTainted MyClass(input); - // :: warning: (cast.unsafe.constructor.invocation) - @Untainted Object local4 = new @Untainted MyClass(input); - // :: warning: (cast.unsafe.constructor.invocation) - @PolyTainted Object local5 = new @PolyTainted MyClass(tainted); - @Untainted Object local6 = new @Untainted MyClass(untainted); - } + @PolyTainted Object local3 = new @PolyTainted MyClass(input); + // :: warning: (cast.unsafe.constructor.invocation) + @Untainted Object local4 = new @Untainted MyClass(input); + // :: warning: (cast.unsafe.constructor.invocation) + @PolyTainted Object local5 = new @PolyTainted MyClass(tainted); + @Untainted Object local6 = new @Untainted MyClass(untainted); } + } } diff --git a/checker/tests/tainting/Issue2243.java b/checker/tests/tainting/Issue2243.java index 2a993a705da..c4a185399c8 100644 --- a/checker/tests/tainting/Issue2243.java +++ b/checker/tests/tainting/Issue2243.java @@ -13,12 +13,12 @@ class ExtendsSubTypingExplicit2243 extends @Untainted X2243 {} class X2243 {} class MyClass2243 { - @Tainted MyClass2243() {} + @Tainted MyClass2243() {} } @Untainted class Y2243 extends MyClass2243 { - // :: error: (super.invocation.invalid) - @Untainted Y2243() {} + // :: error: (super.invocation.invalid) + @Untainted Y2243() {} } @Untainted interface SuperClass2243 {} @@ -27,6 +27,6 @@ class MyClass2243 { @Tainted class Z2243 implements SuperClass2243 {} class Issue2243Test { - @Untainted ExtendsSubTypingExplicit2243 field; - @Tainted ExtendsSubTypingExplicit2243 field2; + @Untainted ExtendsSubTypingExplicit2243 field; + @Tainted ExtendsSubTypingExplicit2243 field2; } diff --git a/checker/tests/tainting/Issue2330.java b/checker/tests/tainting/Issue2330.java index eab2937f0c9..c796c81998e 100644 --- a/checker/tests/tainting/Issue2330.java +++ b/checker/tests/tainting/Issue2330.java @@ -3,16 +3,16 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue2330 { - // Checker can't verify that this creates an untainted Issue2330 - @SuppressWarnings("tainting") - public @Untainted Issue2330(@PolyTainted int i) {} + // Checker can't verify that this creates an untainted Issue2330 + @SuppressWarnings("tainting") + public @Untainted Issue2330(@PolyTainted int i) {} - // Checker can't verify that this creates an untainted Issue2330 - @SuppressWarnings("tainting") - public @Untainted Issue2330() {} + // Checker can't verify that this creates an untainted Issue2330 + @SuppressWarnings("tainting") + public @Untainted Issue2330() {} - public static void f(@PolyTainted int i) { - new @Untainted Issue2330<@PolyTainted Integer>(i); - new @Untainted Issue2330<@PolyTainted Integer>(); - } + public static void f(@PolyTainted int i) { + new @Untainted Issue2330<@PolyTainted Integer>(i); + new @Untainted Issue2330<@PolyTainted Integer>(); + } } diff --git a/checker/tests/tainting/Issue3033.java b/checker/tests/tainting/Issue3033.java index 7abe60e7bda..fe3b4ff7800 100644 --- a/checker/tests/tainting/Issue3033.java +++ b/checker/tests/tainting/Issue3033.java @@ -3,18 +3,18 @@ public class Issue3033 { - void main() { - @Tainted String a = getTainted(); - // :: warning: (instanceof.unsafe) - if (a instanceof @Untainted String) { - // `a` is now refined to @Untainted String - isUntainted(a); - } + void main() { + @Tainted String a = getTainted(); + // :: warning: (instanceof.unsafe) + if (a instanceof @Untainted String) { + // `a` is now refined to @Untainted String + isUntainted(a); } + } - static void isUntainted(@Untainted String a) {} + static void isUntainted(@Untainted String a) {} - static @Tainted String getTainted() { - return "hi"; - } + static @Tainted String getTainted() { + return "hi"; + } } diff --git a/checker/tests/tainting/Issue352.java b/checker/tests/tainting/Issue352.java index 73c42874f86..5132e2c375c 100644 --- a/checker/tests/tainting/Issue352.java +++ b/checker/tests/tainting/Issue352.java @@ -4,9 +4,9 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue352 { - class Nested { - @Untainted Issue352 context(@Untainted Issue352.@Untainted Nested this) { - return Issue352.this; - } + class Nested { + @Untainted Issue352 context(@Untainted Issue352.@Untainted Nested this) { + return Issue352.this; } + } } diff --git a/checker/tests/tainting/Issue3561.java b/checker/tests/tainting/Issue3561.java index 2e4c44c9923..ca5fde378cd 100644 --- a/checker/tests/tainting/Issue3561.java +++ b/checker/tests/tainting/Issue3561.java @@ -1,16 +1,16 @@ import org.checkerframework.checker.tainting.qual.*; public class Issue3561 { - void outerMethod(@Untainted Issue3561 this) {} + void outerMethod(@Untainted Issue3561 this) {} - class Inner { - void innerMethod(@Untainted Issue3561.@Untainted Inner this) { - Issue3561.this.outerMethod(); - } + class Inner { + void innerMethod(@Untainted Issue3561.@Untainted Inner this) { + Issue3561.this.outerMethod(); + } - void innerMethod2(@Tainted Issue3561.@Untainted Inner this) { - // :: error: (method.invocation.invalid) - Issue3561.this.outerMethod(); - } + void innerMethod2(@Tainted Issue3561.@Untainted Inner this) { + // :: error: (method.invocation.invalid) + Issue3561.this.outerMethod(); } + } } diff --git a/checker/tests/tainting/Issue3562.java b/checker/tests/tainting/Issue3562.java index 2600b9649b2..1b067b7fda3 100644 --- a/checker/tests/tainting/Issue3562.java +++ b/checker/tests/tainting/Issue3562.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.tainting.qual.*; public class Issue3562 { - // This used to issue type.invalid.conflicting.annos - @Tainted Issue3562.@Untainted Inner field; + // This used to issue type.invalid.conflicting.annos + @Tainted Issue3562.@Untainted Inner field; - class Inner {} + class Inner {} } diff --git a/checker/tests/tainting/Issue3776.java b/checker/tests/tainting/Issue3776.java index 302a09588a0..3044f1d6537 100644 --- a/checker/tests/tainting/Issue3776.java +++ b/checker/tests/tainting/Issue3776.java @@ -2,44 +2,44 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue3776 { - class MyInnerClass { - public MyInnerClass() {} + class MyInnerClass { + public MyInnerClass() {} - public MyInnerClass(@Untainted String s) {} + public MyInnerClass(@Untainted String s) {} - public MyInnerClass(int... i) {} - } + public MyInnerClass(int... i) {} + } - static class MyClass { - public MyClass() {} + static class MyClass { + public MyClass() {} - public MyClass(@Untainted String s) {} + public MyClass(@Untainted String s) {} - public MyClass(int... i) {} - } + public MyClass(int... i) {} + } - void test(Issue3776 outer, @Tainted String tainted) { - new MyInnerClass("1") {}; - this.new MyInnerClass("2") {}; - new MyClass() {}; - // :: error: (argument.type.incompatible) - new MyClass(tainted) {}; - new MyClass(1, 2, 3) {}; - new MyClass(1) {}; - new MyInnerClass() {}; - // :: error: (argument.type.incompatible) - new MyInnerClass(tainted) {}; - new MyInnerClass(1) {}; - new MyInnerClass(1, 2, 3) {}; - this.new MyInnerClass() {}; - // :: error: (argument.type.incompatible) - this.new MyInnerClass(tainted) {}; - this.new MyInnerClass(1) {}; - this.new MyInnerClass(1, 2, 3) {}; - outer.new MyInnerClass() {}; - // :: error: (argument.type.incompatible) - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(1) {}; - outer.new MyInnerClass(1, 2, 3) {}; - } + void test(Issue3776 outer, @Tainted String tainted) { + new MyInnerClass("1") {}; + this.new MyInnerClass("2") {}; + new MyClass() {}; + // :: error: (argument.type.incompatible) + new MyClass(tainted) {}; + new MyClass(1, 2, 3) {}; + new MyClass(1) {}; + new MyInnerClass() {}; + // :: error: (argument.type.incompatible) + new MyInnerClass(tainted) {}; + new MyInnerClass(1) {}; + new MyInnerClass(1, 2, 3) {}; + this.new MyInnerClass() {}; + // :: error: (argument.type.incompatible) + this.new MyInnerClass(tainted) {}; + this.new MyInnerClass(1) {}; + this.new MyInnerClass(1, 2, 3) {}; + outer.new MyInnerClass() {}; + // :: error: (argument.type.incompatible) + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(1) {}; + outer.new MyInnerClass(1, 2, 3) {}; + } } diff --git a/checker/tests/tainting/Issue4170.java b/checker/tests/tainting/Issue4170.java index 0ce21cbefbb..a53508fad78 100644 --- a/checker/tests/tainting/Issue4170.java +++ b/checker/tests/tainting/Issue4170.java @@ -1,59 +1,58 @@ // @below-java10-jdk-skip-test -import org.checkerframework.checker.tainting.qual.Tainted; -import org.checkerframework.checker.tainting.qual.Untainted; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.tainting.qual.Tainted; +import org.checkerframework.checker.tainting.qual.Untainted; public class Issue4170 { - public void method1() { - var list = new ArrayList<@Untainted String>(); - ArrayList<@Untainted String> list2 = list; - // :: error: (assignment.type.incompatible) - ArrayList list3 = new ArrayList<@Untainted String>(); - ArrayList<@Tainted String> list4 = list3; - var stream = list.stream(); - } - - public void method2() { - var list = new ArrayList(); - var stream = list.stream(); - } - - public void method3() { - var list = new ArrayList<@Tainted String>(); - var stream = list.stream(); - } - - public ArrayList<@Untainted String> method4() { - var list = new ArrayList<@Untainted String>(); - return list; - } - - public ArrayList<@Tainted String> method5() { - var list = new ArrayList<@Untainted String>(); - // :: error: (return.type.incompatible) - return list; - } - - public ArrayList<@Untainted String> method6() { - var list = new ArrayList(); - // :: error: (return.type.incompatible) - return list; - } - - public void method7() { - var list = new ArrayList<@Untainted String>(); - method8(list); - } - - public void method8(ArrayList<@Untainted String> data) {} - - public void method9(List<@Tainted String> taintedlist, List<@Untainted String> untaintedList) { - var list1 = taintedlist; - List<@Tainted String> l = list1; - // :: error: (assignment.type.incompatible) - list1 = untaintedList; - } + public void method1() { + var list = new ArrayList<@Untainted String>(); + ArrayList<@Untainted String> list2 = list; + // :: error: (assignment.type.incompatible) + ArrayList list3 = new ArrayList<@Untainted String>(); + ArrayList<@Tainted String> list4 = list3; + var stream = list.stream(); + } + + public void method2() { + var list = new ArrayList(); + var stream = list.stream(); + } + + public void method3() { + var list = new ArrayList<@Tainted String>(); + var stream = list.stream(); + } + + public ArrayList<@Untainted String> method4() { + var list = new ArrayList<@Untainted String>(); + return list; + } + + public ArrayList<@Tainted String> method5() { + var list = new ArrayList<@Untainted String>(); + // :: error: (return.type.incompatible) + return list; + } + + public ArrayList<@Untainted String> method6() { + var list = new ArrayList(); + // :: error: (return.type.incompatible) + return list; + } + + public void method7() { + var list = new ArrayList<@Untainted String>(); + method8(list); + } + + public void method8(ArrayList<@Untainted String> data) {} + + public void method9(List<@Tainted String> taintedlist, List<@Untainted String> untaintedList) { + var list1 = taintedlist; + List<@Tainted String> l = list1; + // :: error: (assignment.type.incompatible) + list1 = untaintedList; + } } diff --git a/checker/tests/tainting/Issue5435.java b/checker/tests/tainting/Issue5435.java index 61140363fc0..9b3196e590b 100644 --- a/checker/tests/tainting/Issue5435.java +++ b/checker/tests/tainting/Issue5435.java @@ -1,7 +1,7 @@ class Issue5435 { - public @interface A1 {} + public @interface A1 {} - public @interface A2 { - A1[] m() default {@A1()}; - } + public @interface A2 { + A1[] m() default {@A1()}; + } } diff --git a/checker/tests/tainting/Issue6110.java b/checker/tests/tainting/Issue6110.java index 2e22c799320..74ba3afd389 100644 --- a/checker/tests/tainting/Issue6110.java +++ b/checker/tests/tainting/Issue6110.java @@ -1,23 +1,22 @@ +import java.util.EnumSet; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; -import java.util.EnumSet; - class Issue6110 { - enum TestEnum { - ONE, - @Untainted TWO - } + enum TestEnum { + ONE, + @Untainted TWO + } - static void test(Enum<@Untainted TestEnum> o) { + static void test(Enum<@Untainted TestEnum> o) { - @Tainted TestEnum e = TestEnum.ONE; - o.compareTo(TestEnum.ONE); - o.compareTo(TestEnum.TWO); + @Tainted TestEnum e = TestEnum.ONE; + o.compareTo(TestEnum.ONE); + o.compareTo(TestEnum.TWO); - EnumSet<@Tainted TestEnum> s1 = EnumSet.of(TestEnum.ONE); - // :: error: (assignment.type.incompatible) - EnumSet<@Untainted TestEnum> s2 = EnumSet.of(TestEnum.ONE); - EnumSet<@Untainted TestEnum> s3 = EnumSet.of(TestEnum.TWO); - } + EnumSet<@Tainted TestEnum> s1 = EnumSet.of(TestEnum.ONE); + // :: error: (assignment.type.incompatible) + EnumSet<@Untainted TestEnum> s2 = EnumSet.of(TestEnum.ONE); + EnumSet<@Untainted TestEnum> s3 = EnumSet.of(TestEnum.TWO); + } } diff --git a/checker/tests/tainting/Issue6113.java b/checker/tests/tainting/Issue6113.java index f05089095bc..e25789af7cf 100644 --- a/checker/tests/tainting/Issue6113.java +++ b/checker/tests/tainting/Issue6113.java @@ -3,8 +3,8 @@ import java.util.List; class Issue6113 { - public void bar(List list) { - Collection c = - list != null ? list : Collections.emptyList(); // reported error here - } + public void bar(List list) { + Collection c = + list != null ? list : Collections.emptyList(); // reported error here + } } diff --git a/checker/tests/tainting/Issue6116.java b/checker/tests/tainting/Issue6116.java index c4367118cca..4579ea0860d 100644 --- a/checker/tests/tainting/Issue6116.java +++ b/checker/tests/tainting/Issue6116.java @@ -2,13 +2,13 @@ import java.util.function.Consumer; class Issue6116 { - private final Iterator it; + private final Iterator it; - public Issue6116(Iterator iterator) { - this.it = iterator; - } + public Issue6116(Iterator iterator) { + this.it = iterator; + } - public void test(Consumer action) { - it.forEachRemaining(action); - } + public void test(Consumer action) { + it.forEachRemaining(action); + } } diff --git a/checker/tests/tainting/LambdaParameterDefaulting.java b/checker/tests/tainting/LambdaParameterDefaulting.java index f8dd5a1d146..90706dada23 100644 --- a/checker/tests/tainting/LambdaParameterDefaulting.java +++ b/checker/tests/tainting/LambdaParameterDefaulting.java @@ -1,38 +1,37 @@ +import java.util.function.Function; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; -import java.util.function.Function; - public class LambdaParameterDefaulting { - @DefaultQualifier(locations = TypeUseLocation.PARAMETER, value = Untainted.class) - void method() { - // :: error: (lambda.param.type.incompatible) - Function function = (String s) -> untainted(s); - Function<@Untainted String, String> function2 = (String s) -> untainted(s); - Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); + @DefaultQualifier(locations = TypeUseLocation.PARAMETER, value = Untainted.class) + void method() { + // :: error: (lambda.param.type.incompatible) + Function function = (String s) -> untainted(s); + Function<@Untainted String, String> function2 = (String s) -> untainted(s); + Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function function4 = s -> untainted(s); - Function<@Untainted String, String> function5 = s -> untainted(s); - Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); - } + // :: error: (argument.type.incompatible) + Function function4 = s -> untainted(s); + Function<@Untainted String, String> function5 = s -> untainted(s); + Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); + } - void method2() { - // :: error: (argument.type.incompatible) - Function function = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function<@Untainted String, String> function2 = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); + void method2() { + // :: error: (argument.type.incompatible) + Function function = (String s) -> untainted(s); + // :: error: (argument.type.incompatible) + Function<@Untainted String, String> function2 = (String s) -> untainted(s); + // :: error: (argument.type.incompatible) + Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function function4 = s -> untainted(s); - Function<@Untainted String, String> function5 = s -> untainted(s); - Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); - } + // :: error: (argument.type.incompatible) + Function function4 = s -> untainted(s); + Function<@Untainted String, String> function5 = s -> untainted(s); + Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); + } - @Untainted String untainted(@Untainted String s) { - return s; - } + @Untainted String untainted(@Untainted String s) { + return s; + } } diff --git a/checker/tests/tainting/NestedAnonymous.java b/checker/tests/tainting/NestedAnonymous.java index 4fe15a787e2..150181ea776 100644 --- a/checker/tests/tainting/NestedAnonymous.java +++ b/checker/tests/tainting/NestedAnonymous.java @@ -2,13 +2,13 @@ import org.checkerframework.checker.tainting.qual.Untainted; class NestedAnonymous { - Object o1 = new @Tainted Object() {}; - Object o2 = new Outer.@Tainted Inner() {}; + Object o1 = new @Tainted Object() {}; + Object o2 = new Outer.@Tainted Inner() {}; - // :: error: (assignment.type.incompatible) - Outer.@Untainted Inner unt = new Outer.@Tainted Inner() {}; + // :: error: (assignment.type.incompatible) + Outer.@Untainted Inner unt = new Outer.@Tainted Inner() {}; - static class Outer { - static class Inner {} - } + static class Outer { + static class Inner {} + } } diff --git a/checker/tests/tainting/NestedTypeConstructor.java b/checker/tests/tainting/NestedTypeConstructor.java index ae6b50ba7cb..641d40ac3bf 100644 --- a/checker/tests/tainting/NestedTypeConstructor.java +++ b/checker/tests/tainting/NestedTypeConstructor.java @@ -4,7 +4,7 @@ // https://github.com/typetools/checker-framework/issues/275 // Not tainting-specific, but a convenient location. public class NestedTypeConstructor { - class Inner { - @Tainted Inner() {} - } + class Inner { + @Tainted Inner() {} + } } diff --git a/checker/tests/tainting/ObjectCreation.java b/checker/tests/tainting/ObjectCreation.java index 35b607ad9b7..09e5a43bfbd 100644 --- a/checker/tests/tainting/ObjectCreation.java +++ b/checker/tests/tainting/ObjectCreation.java @@ -5,47 +5,47 @@ public class ObjectCreation { - @HasQualifierParameter(Tainted.class) - static class Buffer { // Which constructors and super calls are legal? - @PolyTainted Buffer() { - super(); // ok this creates an @Untainted object - } + @HasQualifierParameter(Tainted.class) + static class Buffer { // Which constructors and super calls are legal? + @PolyTainted Buffer() { + super(); // ok this creates an @Untainted object + } - @Untainted Buffer(int p) { - super(); // ok, super is untainted and creating an untainted buffer - } + @Untainted Buffer(int p) { + super(); // ok, super is untainted and creating an untainted buffer + } - @Tainted Buffer(String s) { - super(); // ok, super is not @HasQualifierParameter @Tainted Object >: the type of - // super. - } + @Tainted Buffer(String s) { + super(); // ok, super is not @HasQualifierParameter @Tainted Object >: the type of + // super. } + } - @HasQualifierParameter(Tainted.class) - static class MyBuffer extends Buffer { - @PolyTainted MyBuffer() { - super(); // ok, if super is @PolyTainted. - } + @HasQualifierParameter(Tainted.class) + static class MyBuffer extends Buffer { + @PolyTainted MyBuffer() { + super(); // ok, if super is @PolyTainted. + } - @Untainted MyBuffer(int p) { - super(p); - } + @Untainted MyBuffer(int p) { + super(p); + } - @Tainted MyBuffer(String s) { - super(s); - } + @Tainted MyBuffer(String s) { + super(s); + } - @PolyTainted MyBuffer(Object o) { - // :: error: (super.invocation.invalid) - super(""); - } + @PolyTainted MyBuffer(Object o) { + // :: error: (super.invocation.invalid) + super(""); + } - @Untainted MyBuffer(Object o, int p) { - super(); - } + @Untainted MyBuffer(Object o, int p) { + super(); + } - @Tainted MyBuffer(Object o, String s) { - super(); - } + @Tainted MyBuffer(Object o, String s) { + super(); } + } } diff --git a/checker/tests/tainting/PolyClassDecl.java b/checker/tests/tainting/PolyClassDecl.java index 1cae9303f72..02ffbdc0d90 100644 --- a/checker/tests/tainting/PolyClassDecl.java +++ b/checker/tests/tainting/PolyClassDecl.java @@ -1,34 +1,33 @@ -import org.checkerframework.checker.tainting.qual.PolyTainted; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.tainting.qual.PolyTainted; public class PolyClassDecl { - // :: error: (invalid.polymorphic.qualifier) - @PolyTainted static class Class1 {} + // :: error: (invalid.polymorphic.qualifier) + @PolyTainted static class Class1 {} - // :: error: (invalid.polymorphic.qualifier) - static class Class2<@PolyTainted T> {} + // :: error: (invalid.polymorphic.qualifier) + static class Class2<@PolyTainted T> {} - // :: error: (invalid.polymorphic.qualifier) - abstract static class Class3> {} + // :: error: (invalid.polymorphic.qualifier) + abstract static class Class3> {} - // :: error: (invalid.polymorphic.qualifier) - interface Class4 extends List<@PolyTainted String> {} + // :: error: (invalid.polymorphic.qualifier) + interface Class4 extends List<@PolyTainted String> {} - // :: error: (invalid.polymorphic.qualifier) - // :: error: (declaration.inconsistent.with.implements.clause) - interface Class5 extends @PolyTainted List {} + // :: error: (invalid.polymorphic.qualifier) + // :: error: (declaration.inconsistent.with.implements.clause) + interface Class5 extends @PolyTainted List {} - // :: error: (invalid.polymorphic.qualifier) - abstract static class Class6 implements List<@PolyTainted String> {} + // :: error: (invalid.polymorphic.qualifier) + abstract static class Class6 implements List<@PolyTainted String> {} - void method() { - ArrayList<@PolyTainted String> s = new ArrayList<@PolyTainted String>() {}; - } + void method() { + ArrayList<@PolyTainted String> s = new ArrayList<@PolyTainted String>() {}; + } - // :: error: (invalid.polymorphic.qualifier) - <@PolyTainted T> T identity(T arg) { - return arg; - } + // :: error: (invalid.polymorphic.qualifier) + <@PolyTainted T> T identity(T arg) { + return arg; + } } diff --git a/checker/tests/tainting/PolyConstructor.java b/checker/tests/tainting/PolyConstructor.java index 8db3a14990a..1358636c448 100644 --- a/checker/tests/tainting/PolyConstructor.java +++ b/checker/tests/tainting/PolyConstructor.java @@ -6,22 +6,22 @@ @HasQualifierParameter(Tainted.class) public class PolyConstructor { - @PolyTainted PolyConstructor() {} + @PolyTainted PolyConstructor() {} - @PolyTainted PolyConstructor(@PolyTainted Object o) {} + @PolyTainted PolyConstructor(@PolyTainted Object o) {} - static void uses(@Tainted Object tainted, @Untainted Object untainted) { - @Untainted PolyConstructor o1 = new @Untainted PolyConstructor(); - @Tainted PolyConstructor o2 = new @Tainted PolyConstructor(); - @PolyTainted PolyConstructor o3 = new @PolyTainted PolyConstructor(); + static void uses(@Tainted Object tainted, @Untainted Object untainted) { + @Untainted PolyConstructor o1 = new @Untainted PolyConstructor(); + @Tainted PolyConstructor o2 = new @Tainted PolyConstructor(); + @PolyTainted PolyConstructor o3 = new @PolyTainted PolyConstructor(); - // :: error: (assignment.type.incompatible) - @Untainted PolyConstructor o4 = new @Tainted PolyConstructor(untainted); - @Untainted PolyConstructor o5 = new PolyConstructor(untainted); + // :: error: (assignment.type.incompatible) + @Untainted PolyConstructor o4 = new @Tainted PolyConstructor(untainted); + @Untainted PolyConstructor o5 = new PolyConstructor(untainted); - // This currently isn't supported, but could be in the future. - @Untainted PolyConstructor o6 = new PolyConstructor(); - // :: error: (assignment.type.incompatible) - @Tainted PolyConstructor o7 = new PolyConstructor(); - } + // This currently isn't supported, but could be in the future. + @Untainted PolyConstructor o6 = new PolyConstructor(); + // :: error: (assignment.type.incompatible) + @Tainted PolyConstructor o7 = new PolyConstructor(); + } } diff --git a/checker/tests/tainting/PolyReceivers.java b/checker/tests/tainting/PolyReceivers.java index f96b3ade677..162daca45d5 100644 --- a/checker/tests/tainting/PolyReceivers.java +++ b/checker/tests/tainting/PolyReceivers.java @@ -2,44 +2,42 @@ public class PolyReceivers { - static class MyClass { - public void start(@PolyTainted MyClass this) {} - } + static class MyClass { + public void start(@PolyTainted MyClass this) {} + } - PolyReceivers(int i, Runnable... runnables) {} + PolyReceivers(int i, Runnable... runnables) {} - PolyReceivers(Consumer consumer) { - consumer.consume("hello"); // Use lambda as a constructor argument - } + PolyReceivers(Consumer consumer) { + consumer.consume("hello"); // Use lambda as a constructor argument + } - interface Top { - public void consume(String s); - } + interface Top { + public void consume(String s); + } - interface Sub extends Top { - public default void otherMethod() {} - } + interface Sub extends Top { + public default void otherMethod() {} + } - interface Consumer { - void consume(T t); - } + interface Consumer { + void consume(T t); + } - void varargs(Runnable... runnables) {} + void varargs(Runnable... runnables) {} - public static void consumeStr(String str) {} + public static void consumeStr(String str) {} - public static void consumeStr2(String str) {} + public static void consumeStr2(String str) {} - > void context(E e, Sub s) { - new PolyReceivers(PolyReceivers::consumeStr); + > void context(E e, Sub s) { + new PolyReceivers(PolyReceivers::consumeStr); - Consumer cs1 = (false) ? PolyReceivers::consumeStr2 : PolyReceivers::consumeStr; - Consumer cs2 = (false) ? e : PolyReceivers::consumeStr; - Top t = (false) ? s : PolyReceivers::consumeStr; + Consumer cs1 = (false) ? PolyReceivers::consumeStr2 : PolyReceivers::consumeStr; + Consumer cs2 = (false) ? e : PolyReceivers::consumeStr; + Top t = (false) ? s : PolyReceivers::consumeStr; - new PolyReceivers(42, new MyClass()::start); // Use lambda as a constructor argument - varargs( - new MyClass()::start, - new MyClass()::start); // Use lambda in a var arg list of method - } + new PolyReceivers(42, new MyClass()::start); // Use lambda as a constructor argument + varargs(new MyClass()::start, new MyClass()::start); // Use lambda in a var arg list of method + } } diff --git a/checker/tests/tainting/PolyReturn.java b/checker/tests/tainting/PolyReturn.java index 761b73857cc..592ceb3a39b 100644 --- a/checker/tests/tainting/PolyReturn.java +++ b/checker/tests/tainting/PolyReturn.java @@ -3,25 +3,25 @@ import org.checkerframework.checker.tainting.qual.Untainted; @SuppressWarnings({ - "inconsistent.constructor.type", - "super.invocation.invalid" + "inconsistent.constructor.type", + "super.invocation.invalid" }) // ignore these warnings public class PolyReturn { - @PolyTainted PolyReturn() {} + @PolyTainted PolyReturn() {} - @PolyTainted PolyReturn method() { - return new PolyReturn(); - } + @PolyTainted PolyReturn method() { + return new PolyReturn(); + } - void use() { - @Untainted PolyReturn untainted = new PolyReturn(); - @Untainted PolyReturn untainted2 = new @Untainted PolyReturn(); + void use() { + @Untainted PolyReturn untainted = new PolyReturn(); + @Untainted PolyReturn untainted2 = new @Untainted PolyReturn(); - @Untainted PolyReturn untainted3 = method(); + @Untainted PolyReturn untainted3 = method(); - @Tainted PolyReturn tainted = new PolyReturn(); - @Tainted PolyReturn tainted2 = new @Tainted PolyReturn(); + @Tainted PolyReturn tainted = new PolyReturn(); + @Tainted PolyReturn tainted2 = new @Tainted PolyReturn(); - @Tainted PolyReturn tainted3 = method(); - } + @Tainted PolyReturn tainted3 = method(); + } } diff --git a/checker/tests/tainting/Refine.java b/checker/tests/tainting/Refine.java index 7fde0d6d991..b0262615b06 100644 --- a/checker/tests/tainting/Refine.java +++ b/checker/tests/tainting/Refine.java @@ -4,29 +4,29 @@ @HasQualifierParameter(Tainted.class) public class Refine { - void method(@Tainted Refine tainted, @Untainted Refine untainted) { - // :: error: (assignment.type.incompatible) - @Tainted Refine local = untainted; - // :: error: (assignment.type.incompatible) - @Untainted Refine untaintedLocal = local; - @Untainted Refine untaintedLocal2 = untaintedLocal; - } + void method(@Tainted Refine tainted, @Untainted Refine untainted) { + // :: error: (assignment.type.incompatible) + @Tainted Refine local = untainted; + // :: error: (assignment.type.incompatible) + @Untainted Refine untaintedLocal = local; + @Untainted Refine untaintedLocal2 = untaintedLocal; + } - void methodNull() { - @Tainted Refine local = null; - @Untainted Refine untaintedLocal = local; - } + void methodNull() { + @Tainted Refine local = null; + @Untainted Refine untaintedLocal = local; + } - public static class SuperClass { - @Untainted SuperClass() {} - } + public static class SuperClass { + @Untainted SuperClass() {} + } - @HasQualifierParameter(Tainted.class) - public static class SubClass extends SuperClass {} + @HasQualifierParameter(Tainted.class) + public static class SubClass extends SuperClass {} - static void method2(@Untainted SubClass subClass) { - @Untainted SuperClass untainted1 = subClass; - @Tainted SuperClass superClass = subClass; - @Untainted SuperClass untainted2 = superClass; - } + static void method2(@Untainted SubClass subClass) { + @Untainted SuperClass untainted1 = subClass; + @Tainted SuperClass superClass = subClass; + @Untainted SuperClass untainted2 = superClass; + } } diff --git a/checker/tests/tainting/SameTypeBounds.java b/checker/tests/tainting/SameTypeBounds.java index 67c05f7703b..92a710cbbe7 100644 --- a/checker/tests/tainting/SameTypeBounds.java +++ b/checker/tests/tainting/SameTypeBounds.java @@ -4,43 +4,43 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class SameTypeBounds { - static class MyGen {} + static class MyGen {} - void test1(MyGen p) { - // The upper and lower bound must have the same annotation because the bounds are collasped - // during capture conversion. - // :: error: (type.invalid.super.wildcard) - MyGen o = p; - // :: error: (assignment.type.incompatible) - p = o; - } + void test1(MyGen p) { + // The upper and lower bound must have the same annotation because the bounds are collasped + // during capture conversion. + // :: error: (type.invalid.super.wildcard) + MyGen o = p; + // :: error: (assignment.type.incompatible) + p = o; + } - void test2(MyGen p) { - // :: error: (assignment.type.incompatible) - MyGen<@Untainted ? super @Untainted Object> o = p; - // :: error: (assignment.type.incompatible) - p = o; - } + void test2(MyGen p) { + // :: error: (assignment.type.incompatible) + MyGen<@Untainted ? super @Untainted Object> o = p; + // :: error: (assignment.type.incompatible) + p = o; + } - void test3(MyGen<@Untainted Object> p) { - // :: error: (assignment.type.incompatible) - MyGen o = p; - // :: error: (assignment.type.incompatible) - p = o; - } + void test3(MyGen<@Untainted Object> p) { + // :: error: (assignment.type.incompatible) + MyGen o = p; + // :: error: (assignment.type.incompatible) + p = o; + } - static class MyClass {} + static class MyClass {} - static class MySubClass extends MyClass {} + static class MySubClass extends MyClass {} - class Gen {} + class Gen {} + // :: error: (type.invalid.super.wildcard) + void test3(Gen p, Gen p2) { // :: error: (type.invalid.super.wildcard) - void test3(Gen p, Gen p2) { - // :: error: (type.invalid.super.wildcard) - Gen o = p; - o = p2; - // :: error: (assignment.type.incompatible) - p = p2; - } + Gen o = p; + o = p2; + // :: error: (assignment.type.incompatible) + p = p2; + } } diff --git a/checker/tests/tainting/SimplePrims.java b/checker/tests/tainting/SimplePrims.java index b784473601f..91bdefc4cdb 100644 --- a/checker/tests/tainting/SimplePrims.java +++ b/checker/tests/tainting/SimplePrims.java @@ -2,49 +2,49 @@ public class SimplePrims { - void execute(@Untainted int s) {} - - void tainted(int s) {} - - void intLiteral() { - // :: error: (argument.type.incompatible) - execute(5); - tainted(6); - } - - void intRef(int ref) { - // :: error: (argument.type.incompatible) - execute(ref); - tainted(ref); - } - - void untaintedRef(@Untainted int ref) { - execute(ref); - tainted(ref); - } - - void concatenation(@Untainted int s1, int s2) { - execute(s1 + s1); - execute(s1 += s1); - // :: error: (argument.type.incompatible) - execute(s1 + 3); - - // :: error: (argument.type.incompatible) - execute(s1 + s2); - - // :: error: (argument.type.incompatible) - execute(s2 + s1); - // :: error: (argument.type.incompatible) - execute(s2 + 4); - // :: error: (argument.type.incompatible) - execute(s2 + s2); - - tainted(s1 + s1); - tainted(s1 + 7); - tainted(s1 + s2); - - tainted(s2 + s1); - tainted(s2 + 8); - tainted(s2 + s2); - } + void execute(@Untainted int s) {} + + void tainted(int s) {} + + void intLiteral() { + // :: error: (argument.type.incompatible) + execute(5); + tainted(6); + } + + void intRef(int ref) { + // :: error: (argument.type.incompatible) + execute(ref); + tainted(ref); + } + + void untaintedRef(@Untainted int ref) { + execute(ref); + tainted(ref); + } + + void concatenation(@Untainted int s1, int s2) { + execute(s1 + s1); + execute(s1 += s1); + // :: error: (argument.type.incompatible) + execute(s1 + 3); + + // :: error: (argument.type.incompatible) + execute(s1 + s2); + + // :: error: (argument.type.incompatible) + execute(s2 + s1); + // :: error: (argument.type.incompatible) + execute(s2 + 4); + // :: error: (argument.type.incompatible) + execute(s2 + s2); + + tainted(s1 + s1); + tainted(s1 + 7); + tainted(s1 + s2); + + tainted(s2 + s1); + tainted(s2 + 8); + tainted(s2 + s2); + } } diff --git a/checker/tests/tainting/SimpleTainting.java b/checker/tests/tainting/SimpleTainting.java index 498b9c11933..8dd46a6837c 100644 --- a/checker/tests/tainting/SimpleTainting.java +++ b/checker/tests/tainting/SimpleTainting.java @@ -2,46 +2,46 @@ public class SimpleTainting { - void execute(@Untainted String s) {} - - void tainted(String s) {} - - void stringLiteral() { - execute("ldskjfldj"); - tainted("lksjdflkjdf"); - } - - void stringRef(String ref) { - // :: error: (argument.type.incompatible) - execute(ref); // error - tainted(ref); - } - - void untaintedRef(@Untainted String ref) { - execute(ref); - tainted(ref); - } - - void concatenation(@Untainted String s1, String s2) { - execute(s1 + s1); - execute(s1 += s1); - execute(s1 + "m"); - // :: error: (argument.type.incompatible) - execute(s1 + s2); // error - - // :: error: (argument.type.incompatible) - execute(s2 + s1); // error - // :: error: (argument.type.incompatible) - execute(s2 + "m"); // error - // :: error: (argument.type.incompatible) - execute(s2 + s2); // error - - tainted(s1 + s1); - tainted(s1 + "m"); - tainted(s1 + s2); - - tainted(s2 + s1); - tainted(s2 + "m"); - tainted(s2 + s2); - } + void execute(@Untainted String s) {} + + void tainted(String s) {} + + void stringLiteral() { + execute("ldskjfldj"); + tainted("lksjdflkjdf"); + } + + void stringRef(String ref) { + // :: error: (argument.type.incompatible) + execute(ref); // error + tainted(ref); + } + + void untaintedRef(@Untainted String ref) { + execute(ref); + tainted(ref); + } + + void concatenation(@Untainted String s1, String s2) { + execute(s1 + s1); + execute(s1 += s1); + execute(s1 + "m"); + // :: error: (argument.type.incompatible) + execute(s1 + s2); // error + + // :: error: (argument.type.incompatible) + execute(s2 + s1); // error + // :: error: (argument.type.incompatible) + execute(s2 + "m"); // error + // :: error: (argument.type.incompatible) + execute(s2 + s2); // error + + tainted(s1 + s1); + tainted(s1 + "m"); + tainted(s1 + s2); + + tainted(s2 + s1); + tainted(s2 + "m"); + tainted(s2 + s2); + } } diff --git a/checker/tests/tainting/SubClassHasQP.java b/checker/tests/tainting/SubClassHasQP.java index 8a7783c6096..a91dbc02d00 100644 --- a/checker/tests/tainting/SubClassHasQP.java +++ b/checker/tests/tainting/SubClassHasQP.java @@ -5,45 +5,45 @@ // @skip-test https://github.com/typetools/checker-framework/issues/3400 public class SubClassHasQP { - @HasQualifierParameter(Tainted.class) - static class Buffer { - void append(@PolyTainted Buffer this, @PolyTainted String s) {} - - void append2(@PolyTainted Buffer this, @PolyTainted String s) {} - } - - @HasQualifierParameter(Tainted.class) - static @Untainted class UntaintedBuffer extends @Untainted Buffer { - @Override - // :: error: (type.invalid.annotations.on.use) - void append(@Tainted UntaintedBuffer this, @Tainted String s) {} - - @Override - void append2(@Untainted UntaintedBuffer this, @Untainted String s) {} - } - - @HasQualifierParameter(Tainted.class) - static @Tainted class TaintedBuffer extends @Tainted Buffer { - @Override - void append(@Tainted TaintedBuffer this, @Tainted String s) {} // legal override - - @Override - void append2(@Untainted TaintedBuffer this, String s) { - @Untainted Buffer that = this; - } - } - - @HasQualifierParameter(Tainted.class) - // :: error: (super.invocation.invalid) - static class MyTaintedBuffer extends TaintedBuffer { - @Override - // :: error: (override.receiver.invalid) - void append(MyTaintedBuffer this, String s) {} // legal override - } - - @HasQualifierParameter(Tainted.class) - @Tainted class MyTaintedBuffer2 extends TaintedBuffer { - @Override - void append(@Tainted MyTaintedBuffer2 this, String s) {} // legal override + @HasQualifierParameter(Tainted.class) + static class Buffer { + void append(@PolyTainted Buffer this, @PolyTainted String s) {} + + void append2(@PolyTainted Buffer this, @PolyTainted String s) {} + } + + @HasQualifierParameter(Tainted.class) + static @Untainted class UntaintedBuffer extends @Untainted Buffer { + @Override + // :: error: (type.invalid.annotations.on.use) + void append(@Tainted UntaintedBuffer this, @Tainted String s) {} + + @Override + void append2(@Untainted UntaintedBuffer this, @Untainted String s) {} + } + + @HasQualifierParameter(Tainted.class) + static @Tainted class TaintedBuffer extends @Tainted Buffer { + @Override + void append(@Tainted TaintedBuffer this, @Tainted String s) {} // legal override + + @Override + void append2(@Untainted TaintedBuffer this, String s) { + @Untainted Buffer that = this; } + } + + @HasQualifierParameter(Tainted.class) + // :: error: (super.invocation.invalid) + static class MyTaintedBuffer extends TaintedBuffer { + @Override + // :: error: (override.receiver.invalid) + void append(MyTaintedBuffer this, String s) {} // legal override + } + + @HasQualifierParameter(Tainted.class) + @Tainted class MyTaintedBuffer2 extends TaintedBuffer { + @Override + void append(@Tainted MyTaintedBuffer2 this, String s) {} // legal override + } } diff --git a/checker/tests/tainting/TaintedIntersections.java b/checker/tests/tainting/TaintedIntersections.java index 52510441e1c..abafc9ec8ef 100644 --- a/checker/tests/tainting/TaintedIntersections.java +++ b/checker/tests/tainting/TaintedIntersections.java @@ -2,48 +2,48 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class TaintedIntersections { - interface MyInterface {} + interface MyInterface {} - void test1() { - // null is @Untainted - @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) null; - // :: warning: (explicit.annotation.ignored) - @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) null; - // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) - @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) null; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) null; - } + void test1() { + // null is @Untainted + @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) null; + // :: warning: (explicit.annotation.ignored) + @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) null; + // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) + @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) null; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) null; + } - void test2() { - // null is @Untainted - @Untainted Object o1 = (@Untainted Object & MyInterface) null; - @Untainted Object o3 = (Object & @Untainted MyInterface) null; - // :: error: (assignment.type.incompatible) - @Untainted Object o2 = (Object & @Tainted MyInterface) null; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & MyInterface) null; - } + void test2() { + // null is @Untainted + @Untainted Object o1 = (@Untainted Object & MyInterface) null; + @Untainted Object o3 = (Object & @Untainted MyInterface) null; + // :: error: (assignment.type.incompatible) + @Untainted Object o2 = (Object & @Tainted MyInterface) null; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & MyInterface) null; + } - void test3(@Tainted MyInterface i) { - // :: warning: (cast.unsafe) - @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) i; - // :: warning: (explicit.annotation.ignored) :: warning: (cast.unsafe) - @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) i; - // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) - @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) i; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) i; - } + void test3(@Tainted MyInterface i) { + // :: warning: (cast.unsafe) + @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) i; + // :: warning: (explicit.annotation.ignored) :: warning: (cast.unsafe) + @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) i; + // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) + @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) i; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) i; + } - void test4(@Tainted MyInterface i) { - // :: warning: (cast.unsafe) - @Untainted Object o1 = (@Untainted Object & MyInterface) i; - // :: warning: (cast.unsafe) - @Untainted Object o3 = (Object & @Untainted MyInterface) i; - // :: error: (assignment.type.incompatible) - @Untainted Object o2 = (Object & @Tainted MyInterface) i; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & MyInterface) i; - } + void test4(@Tainted MyInterface i) { + // :: warning: (cast.unsafe) + @Untainted Object o1 = (@Untainted Object & MyInterface) i; + // :: warning: (cast.unsafe) + @Untainted Object o3 = (Object & @Untainted MyInterface) i; + // :: error: (assignment.type.incompatible) + @Untainted Object o2 = (Object & @Tainted MyInterface) i; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & MyInterface) i; + } } diff --git a/checker/tests/tainting/TaintingDiamondInference.java b/checker/tests/tainting/TaintingDiamondInference.java index 59ece7471a2..a232435d8d0 100644 --- a/checker/tests/tainting/TaintingDiamondInference.java +++ b/checker/tests/tainting/TaintingDiamondInference.java @@ -1,18 +1,17 @@ // Test case for issue #660: https://github.com/typetools/checker-framework/issues/660 -import org.checkerframework.checker.tainting.qual.Untainted; - import java.util.Set; import java.util.TreeSet; +import org.checkerframework.checker.tainting.qual.Untainted; public class TaintingDiamondInference { - private @Untainted Set<@Untainted String> s; + private @Untainted Set<@Untainted String> s; - public TaintingDiamondInference() { - // :: warning: (cast.unsafe.constructor.invocation) - s = new @Untainted TreeSet<>(); - // :: warning: (cast.unsafe.constructor.invocation) - s = new @Untainted TreeSet<@Untainted String>(); - } + public TaintingDiamondInference() { + // :: warning: (cast.unsafe.constructor.invocation) + s = new @Untainted TreeSet<>(); + // :: warning: (cast.unsafe.constructor.invocation) + s = new @Untainted TreeSet<@Untainted String>(); + } } diff --git a/checker/tests/tainting/TaintingIssue6025.java b/checker/tests/tainting/TaintingIssue6025.java index e0b8c2fa1d7..a2a5a418da1 100644 --- a/checker/tests/tainting/TaintingIssue6025.java +++ b/checker/tests/tainting/TaintingIssue6025.java @@ -2,19 +2,19 @@ public class TaintingIssue6025 { - public interface A {} + public interface A {} - private static final class B {} + private static final class B {} - public interface C, T2 extends A> {} + public interface C, T2 extends A> {} - private final B, ? extends A>> one = new B<>(); - private final B, ? extends A>> two = new B<>(); + private final B, ? extends A>> one = new B<>(); + private final B, ? extends A>> two = new B<>(); - void f(boolean b) { - // :: error: (assignment.type.incompatible) - B> three1 = one; - // :: error: (assignment.type.incompatible) - B> three = b ? two : one; - } + void f(boolean b) { + // :: error: (assignment.type.incompatible) + B> three1 = one; + // :: error: (assignment.type.incompatible) + B> three = b ? two : one; + } } diff --git a/checker/tests/tainting/TaintingIssue6060.java b/checker/tests/tainting/TaintingIssue6060.java index e2e6e6a8769..59032163572 100644 --- a/checker/tests/tainting/TaintingIssue6060.java +++ b/checker/tests/tainting/TaintingIssue6060.java @@ -1,26 +1,25 @@ -import org.checkerframework.checker.tainting.qual.Untainted; - import java.util.Spliterator; import java.util.function.Consumer; +import org.checkerframework.checker.tainting.qual.Untainted; public interface TaintingIssue6060 extends Iterable<@Untainted R> { - default Spliterator<@Untainted R> spliterator() { - return Iterable.super.spliterator(); - } + default Spliterator<@Untainted R> spliterator() { + return Iterable.super.spliterator(); + } - default Spliterator spliterator2() { - // :: error: (return.type.incompatible) - return Iterable.super.spliterator(); - } + default Spliterator spliterator2() { + // :: error: (return.type.incompatible) + return Iterable.super.spliterator(); + } - default Spliterator spliterator3() { - // :: error: (return.type.incompatible) - return this.spliterator(); - } + default Spliterator spliterator3() { + // :: error: (return.type.incompatible) + return this.spliterator(); + } - // :: error: (override.param.invalid) - default void forEach(Consumer action) { - Iterable.super.forEach(action); - } + // :: error: (override.param.invalid) + default void forEach(Consumer action) { + Iterable.super.forEach(action); + } } diff --git a/checker/tests/tainting/TaintingPolyFields.java b/checker/tests/tainting/TaintingPolyFields.java index 52fd672239f..208c10db9af 100644 --- a/checker/tests/tainting/TaintingPolyFields.java +++ b/checker/tests/tainting/TaintingPolyFields.java @@ -1,50 +1,49 @@ +import java.util.List; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; -import java.util.List; - public class TaintingPolyFields { - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted Integer x; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted List<@PolyTainted String> lst; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted String @PolyTainted [] str; - // :: error: (invalid.polymorphic.qualifier.use) - List<@PolyTainted String> lst1; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted String[] str1; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted List lst2; - // :: error: (invalid.polymorphic.qualifier.use) - String @PolyTainted [] str2; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted int z; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted Integer x; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted List<@PolyTainted String> lst; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted String @PolyTainted [] str; + // :: error: (invalid.polymorphic.qualifier.use) + List<@PolyTainted String> lst1; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted String[] str1; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted List lst2; + // :: error: (invalid.polymorphic.qualifier.use) + String @PolyTainted [] str2; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted int z; - // Access of poly fields outside of the declaring class. - static void test() { - @Tainted TaintingPolyFields obj = new @Tainted TaintingPolyFields(); - // :: error: (assignment.type.incompatible) - @Untainted Integer myX = obj.x; - // :: error: (assignment.type.incompatible) - @Untainted List<@Untainted String> myLst = obj.lst; - // :: error: (assignment.type.incompatible) - @Untainted String @Untainted [] myStr = obj.str; + // Access of poly fields outside of the declaring class. + static void test() { + @Tainted TaintingPolyFields obj = new @Tainted TaintingPolyFields(); + // :: error: (assignment.type.incompatible) + @Untainted Integer myX = obj.x; + // :: error: (assignment.type.incompatible) + @Untainted List<@Untainted String> myLst = obj.lst; + // :: error: (assignment.type.incompatible) + @Untainted String @Untainted [] myStr = obj.str; - // :: warning: (cast.unsafe.constructor.invocation) - @Untainted TaintingPolyFields obj1 = new @Untainted TaintingPolyFields(); - @Untainted Integer myX1 = obj1.x; - TaintingPolyFields obj2 = new TaintingPolyFields(); - // :: error: (assignment.type.incompatible) - @Untainted List<@Untainted String> myLst2 = obj2.lst; - } + // :: warning: (cast.unsafe.constructor.invocation) + @Untainted TaintingPolyFields obj1 = new @Untainted TaintingPolyFields(); + @Untainted Integer myX1 = obj1.x; + TaintingPolyFields obj2 = new TaintingPolyFields(); + // :: error: (assignment.type.incompatible) + @Untainted List<@Untainted String> myLst2 = obj2.lst; + } - static void polyTest(@PolyTainted TaintingPolyFields o) { - @PolyTainted Integer f = o.x; - } + static void polyTest(@PolyTainted TaintingPolyFields o) { + @PolyTainted Integer f = o.x; + } } class TypeParam { - T field; + T field; } diff --git a/checker/tests/tainting/TestFieldPolymorphism.java b/checker/tests/tainting/TestFieldPolymorphism.java index 78d6cdea920..7864f7ff953 100644 --- a/checker/tests/tainting/TestFieldPolymorphism.java +++ b/checker/tests/tainting/TestFieldPolymorphism.java @@ -5,54 +5,54 @@ @HasQualifierParameter(Tainted.class) public class TestFieldPolymorphism { - @PolyTainted String field; - - @PolyTainted TestFieldPolymorphism(@PolyTainted String s) { - this.field = s; - } - - @PolyTainted TestFieldPolymorphism testConstructor(@PolyTainted String s) { - return new TestFieldPolymorphism(s); - } - - void testSetter1(@PolyTainted TestFieldPolymorphism this, @PolyTainted String s) { - this.field = s; - } - - void testSetter2(@PolyTainted TestFieldPolymorphism this, @Untainted String s) { - this.field = s; - } - - void testSetter3(@PolyTainted TestFieldPolymorphism this, @Tainted String s) { - // :: error: (assignment.type.incompatible) - this.field = s; - } - - @PolyTainted String testGetter1(@PolyTainted TestFieldPolymorphism this) { - return this.field; - } - - @Untainted String testGetter2(@PolyTainted TestFieldPolymorphism this) { - // :: error: (return.type.incompatible) - return this.field; - } - - static @Untainted String testInstantiateUntaintedGetter(@Untainted TestFieldPolymorphism c) { - return c.field; - } - - static void testInstantiateUntaintedSetter( - @Untainted TestFieldPolymorphism c, @Tainted String s) { - // :: error: (assignment.type.incompatible) - c.field = s; - } - - static @Untainted String testInstantiateTaintedGetter(@Tainted TestFieldPolymorphism c) { - // :: error: (return.type.incompatible) - return c.field; - } - - static void testInstantiateTaintedSetter(@Tainted TestFieldPolymorphism c, @Tainted String s) { - c.field = s; - } + @PolyTainted String field; + + @PolyTainted TestFieldPolymorphism(@PolyTainted String s) { + this.field = s; + } + + @PolyTainted TestFieldPolymorphism testConstructor(@PolyTainted String s) { + return new TestFieldPolymorphism(s); + } + + void testSetter1(@PolyTainted TestFieldPolymorphism this, @PolyTainted String s) { + this.field = s; + } + + void testSetter2(@PolyTainted TestFieldPolymorphism this, @Untainted String s) { + this.field = s; + } + + void testSetter3(@PolyTainted TestFieldPolymorphism this, @Tainted String s) { + // :: error: (assignment.type.incompatible) + this.field = s; + } + + @PolyTainted String testGetter1(@PolyTainted TestFieldPolymorphism this) { + return this.field; + } + + @Untainted String testGetter2(@PolyTainted TestFieldPolymorphism this) { + // :: error: (return.type.incompatible) + return this.field; + } + + static @Untainted String testInstantiateUntaintedGetter(@Untainted TestFieldPolymorphism c) { + return c.field; + } + + static void testInstantiateUntaintedSetter( + @Untainted TestFieldPolymorphism c, @Tainted String s) { + // :: error: (assignment.type.incompatible) + c.field = s; + } + + static @Untainted String testInstantiateTaintedGetter(@Tainted TestFieldPolymorphism c) { + // :: error: (return.type.incompatible) + return c.field; + } + + static void testInstantiateTaintedSetter(@Tainted TestFieldPolymorphism c, @Tainted String s) { + c.field = s; + } } diff --git a/checker/tests/tainting/TestNoQualifierParameterConflicting.java b/checker/tests/tainting/TestNoQualifierParameterConflicting.java index 250380a2938..0e8b2e41fbb 100644 --- a/checker/tests/tainting/TestNoQualifierParameterConflicting.java +++ b/checker/tests/tainting/TestNoQualifierParameterConflicting.java @@ -7,10 +7,10 @@ // :: error: (conflicting.qual.param) public class TestNoQualifierParameterConflicting { - @HasQualifierParameter(Tainted.class) - static class Super {} + @HasQualifierParameter(Tainted.class) + static class Super {} - @NoQualifierParameter(Tainted.class) - // :: error: (conflicting.qual.param) - static class Sup extends Super {} + @NoQualifierParameter(Tainted.class) + // :: error: (conflicting.qual.param) + static class Sup extends Super {} } diff --git a/checker/tests/tainting/TypeInvalid.java b/checker/tests/tainting/TypeInvalid.java index 65b4623a35f..274db2ca051 100644 --- a/checker/tests/tainting/TypeInvalid.java +++ b/checker/tests/tainting/TypeInvalid.java @@ -9,53 +9,53 @@ import org.checkerframework.checker.tainting.qual.Untainted; abstract class TypeInvalid { - // :: error: (type.invalid.conflicting.annos) - static @Untainted @Tainted class Inner {} + // :: error: (type.invalid.conflicting.annos) + static @Untainted @Tainted class Inner {} - // Duplication forbidden + // Duplication forbidden + // :: error: (type.invalid.conflicting.annos) + void bad(@Tainted @Untainted TypeInvalid c) { // :: error: (type.invalid.conflicting.annos) - void bad(@Tainted @Untainted TypeInvalid c) { - // :: error: (type.invalid.conflicting.annos) - Object o = new @Tainted @Untainted Object(); - // :: error: (type.invalid.conflicting.annos) - o = new @Tainted @Untainted Object(); - // :: error: (type.invalid.conflicting.annos) - o = o.equals(new @Tainted @Untainted Object()); - // :: error: (type.invalid.conflicting.annos) - o = (Object) new @Tainted @Untainted Object(); - // :: error: (type.invalid.conflicting.annos) - o = (@Tainted @Untainted TypeInvalid) o; - // :: error: (type.invalid.conflicting.annos) - o = (new @Tainted @Untainted Object()) instanceof Object; - // :: error: (type.invalid.conflicting.annos) - // :: warning: (instanceof.unsafe) - o = o instanceof @Tainted @Untainted TypeInvalid; - } - + Object o = new @Tainted @Untainted Object(); // :: error: (type.invalid.conflicting.annos) - @Tainted @Untainted Object bar() { - return null; - } - + o = new @Tainted @Untainted Object(); + // :: error: (type.invalid.conflicting.annos) + o = o.equals(new @Tainted @Untainted Object()); + // :: error: (type.invalid.conflicting.annos) + o = (Object) new @Tainted @Untainted Object(); + // :: error: (type.invalid.conflicting.annos) + o = (@Tainted @Untainted TypeInvalid) o; + // :: error: (type.invalid.conflicting.annos) + o = (new @Tainted @Untainted Object()) instanceof Object; // :: error: (type.invalid.conflicting.annos) - abstract @Tainted @Untainted Object absbar(); + // :: warning: (instanceof.unsafe) + o = o instanceof @Tainted @Untainted TypeInvalid; + } - void voidmethod() {} + // :: error: (type.invalid.conflicting.annos) + @Tainted @Untainted Object bar() { + return null; + } - TypeInvalid() {} + // :: error: (type.invalid.conflicting.annos) + abstract @Tainted @Untainted Object absbar(); - // :: error: (type.invalid.conflicting.annos) - @Tainted @Untainted TypeInvalid(int p) {} + void voidmethod() {} - // :: error: (type.invalid.conflicting.annos) - void recv(@Tainted @Untainted TypeInvalid this) {} + TypeInvalid() {} - // :: error: (type.invalid.conflicting.annos) - @Tainted @Untainted Object field; + // :: error: (type.invalid.conflicting.annos) + @Tainted @Untainted TypeInvalid(int p) {} - // TODO: Note the error marker positions for the errors on fields - // and method return types. Maybe these should be improved. + // :: error: (type.invalid.conflicting.annos) + void recv(@Tainted @Untainted TypeInvalid this) {} - // :: error: (type.invalid.conflicting.annos) - void athro() throws @Tainted @Untainted Exception {} + // :: error: (type.invalid.conflicting.annos) + @Tainted @Untainted Object field; + + // TODO: Note the error marker positions for the errors on fields + // and method return types. Maybe these should be improved. + + // :: error: (type.invalid.conflicting.annos) + void athro() throws @Tainted @Untainted Exception {} } diff --git a/checker/tests/tainting/WildcardArrayBound.java b/checker/tests/tainting/WildcardArrayBound.java index d17e55d9555..b8c7da7fc26 100644 --- a/checker/tests/tainting/WildcardArrayBound.java +++ b/checker/tests/tainting/WildcardArrayBound.java @@ -1,40 +1,39 @@ package wildcards; +import java.io.Serializable; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; -import java.io.Serializable; - public class WildcardArrayBound { - interface MyInterface {} - - abstract static class Other { - void use1( - Other x, - Other<@Tainted MyInterface @Untainted []> y) { - Other z = y; - // :: error: (assignment.type.incompatible) - x = y; - } - - void use( - Other x, - Other<@Tainted MyInterface @Untainted []> y) { - // :: error: (assignment.type.incompatible) - x = y; - @Untainted Serializable s = x.getU(); - @Untainted MyInterface @Untainted [] sw = x.getU(); - } + interface MyInterface {} + + abstract static class Other { + void use1( + Other x, + Other<@Tainted MyInterface @Untainted []> y) { + Other z = y; + // :: error: (assignment.type.incompatible) + x = y; + } - abstract U getU(); + void use( + Other x, + Other<@Tainted MyInterface @Untainted []> y) { + // :: error: (assignment.type.incompatible) + x = y; + @Untainted Serializable s = x.getU(); + @Untainted MyInterface @Untainted [] sw = x.getU(); } - abstract static class Another { - void use(Another x) { - Serializable s = x.getU(); - MyInterface[] sw = x.getU(); - } + abstract U getU(); + } - abstract U getU(); + abstract static class Another { + void use(Another x) { + Serializable s = x.getU(); + MyInterface[] sw = x.getU(); } + + abstract U getU(); + } } diff --git a/checker/tests/tainting/WildcardMethodArgument.java b/checker/tests/tainting/WildcardMethodArgument.java index 1403ab76e4a..d9ad256d297 100644 --- a/checker/tests/tainting/WildcardMethodArgument.java +++ b/checker/tests/tainting/WildcardMethodArgument.java @@ -1,14 +1,13 @@ import com.sun.source.tree.ExpressionTree; - import java.util.ArrayList; import java.util.List; public class WildcardMethodArgument { - abstract static class MyClass { - abstract List getArguments(); - } + abstract static class MyClass { + abstract List getArguments(); + } - void method(MyClass myClass) { - List javacArgs = new ArrayList<>(myClass.getArguments()); - } + void method(MyClass myClass) { + List javacArgs = new ArrayList<>(myClass.getArguments()); + } } diff --git a/checker/tests/tainting/java17/TaintingBindingVariable.java b/checker/tests/tainting/java17/TaintingBindingVariable.java index 0cea4fb27f1..edb634a7f8f 100644 --- a/checker/tests/tainting/java17/TaintingBindingVariable.java +++ b/checker/tests/tainting/java17/TaintingBindingVariable.java @@ -3,30 +3,30 @@ public class TaintingBindingVariable { - void bar(@Untainted Object o) { - if (o instanceof @Untainted String s) { - @Untainted String f = s; - } - if (o instanceof String s) { - @Untainted String f2 = s; - } + void bar(@Untainted Object o) { + if (o instanceof @Untainted String s) { + @Untainted String f = s; } + if (o instanceof String s) { + @Untainted String f2 = s; + } + } - void bar2(Object o) { - // :: warning: (instanceof.pattern.unsafe) - if (o instanceof @Untainted String s) { - @Untainted String f = s; - } - if (o instanceof String s) { - // :: error: (assignment.type.incompatible) - @Untainted String f2 = s; - } + void bar2(Object o) { + // :: warning: (instanceof.pattern.unsafe) + if (o instanceof @Untainted String s) { + @Untainted String f = s; + } + if (o instanceof String s) { + // :: error: (assignment.type.incompatible) + @Untainted String f2 = s; } + } - void bar3(Object o, boolean b) { - // :: warning: (instanceof.pattern.unsafe) - if (b && o instanceof @Untainted String s) { - @Untainted String f = s; - } + void bar3(Object o, boolean b) { + // :: warning: (instanceof.pattern.unsafe) + if (b && o instanceof @Untainted String s) { + @Untainted String f = s; } + } } diff --git a/checker/tests/tainting/withdefault/NoQualifierTest.java b/checker/tests/tainting/withdefault/NoQualifierTest.java index 2ca037e1e88..409daf01a26 100644 --- a/checker/tests/tainting/withdefault/NoQualifierTest.java +++ b/checker/tests/tainting/withdefault/NoQualifierTest.java @@ -6,6 +6,6 @@ @NoQualifierParameter(Tainted.class) public class NoQualifierTest { - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted int field; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted int field; } diff --git a/checker/tests/tainting/withdefault/WithDefault.java b/checker/tests/tainting/withdefault/WithDefault.java index 59e1ee33cbd..2e10f8df626 100644 --- a/checker/tests/tainting/withdefault/WithDefault.java +++ b/checker/tests/tainting/withdefault/WithDefault.java @@ -3,5 +3,5 @@ import org.checkerframework.checker.tainting.qual.PolyTainted; public class WithDefault { - @PolyTainted int field; + @PolyTainted int field; } diff --git a/checker/tests/units/Addition.java b/checker/tests/units/Addition.java index 557911d16a4..244e8736a2d 100644 --- a/checker/tests/units/Addition.java +++ b/checker/tests/units/Addition.java @@ -36,391 +36,391 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Addition { - // Addition is legal when the operands have the same units. - void good() { - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - @A int sAmpere = aAmpere + bAmpere; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - @cd int sCandela = aCandela + bCandela; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - @C int sCelsius = aCelsius + bCelsius; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - @g int sGram = aGram + bGram; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - @h int sHour = aHour + bHour; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - @K int sKelvin = aKelvin + bKelvin; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - @kg int sKilogram = aKilogram + bKilogram; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - @km int sKilometer = aKilometer + bKilometer; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - @km2 int sSquareKilometer = aSquareKilometer + bSquareKilometer; - - // Cubic kilometer - @km3 int aCubicKilometer = 5 * UnitsTools.km3; - @km3 int bCubicKilometer = 5 * UnitsTools.km3; - @km3 int sCubicKilometer = aCubicKilometer + bCubicKilometer; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int sKilometerPerHour = aKilometerPerHour + bKilometerPerHour; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - @m int sMeter = aMeter + bMeter; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - @m2 int sSquareMeter = aSquareMeter + bSquareMeter; - - // Cubic meter - @m3 int aCubicMeter = 5 * UnitsTools.m3; - @m3 int bCubicMeter = 5 * UnitsTools.m3; - @m3 int sCubicMeter = aCubicMeter + bCubicMeter; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int sMeterPerSecond = aMeterPerSecond + bMeterPerSecond; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeterPerSecondSquare; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - @min int sMinute = aMinute + bMinute; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - @mm int sMillimeter = aMillimeter + bMillimeter; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int sSquareMillimeter = aSquareMillimeter + bSquareMillimeter; - - // Cubic millimeter - @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int sCubicMillimeter = aCubicMillimeter + bCubicMillimeter; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - @mol int sMole = aMole + bMole; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - @N int sNewton = aNewton + bNewton; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - @kN int sKilonewton = aKilonewton + bKilonewton; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - @s int sSecond = aSecond + bSecond; - } - - // Addition is illegal when the operands have different units or one is unqualified. In these - // tests, we cycle between the result and the first or second operand having an incorrect type. - void bad() { - // Dimensions - // Acceleration - @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; - @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; - - // Area - @Area int aArea = 5 * UnitsTools.km2; - @Area int bArea = 5 * UnitsTools.mm2; - - // Current - @Current int aCurrent = 5 * UnitsTools.A; - @Current int bCurrent = 5 * UnitsTools.A; - - // Force - @Force int aForce = 5 * UnitsTools.N; - @Force int bForce = 5 * UnitsTools.N; - - // Length - @Length int aLength = 5 * UnitsTools.m; - @Length int bLength = 5 * UnitsTools.mm; - - // Luminance - @Luminance int aLuminance = 5 * UnitsTools.cd; - @Luminance int bLuminance = 5 * UnitsTools.cd; - - // Mass - @Mass int aMass = 5 * UnitsTools.kg; - @Mass int bMass = 5 * UnitsTools.g; - - // Substance - @Substance int aSubstance = 5 * UnitsTools.mol; - @Substance int bSubstance = 5 * UnitsTools.mol; - - // Temperature - @Temperature int aTemperature = 5 * UnitsTools.K; - @Temperature int bTemperature = 5 * UnitsTools.K; - - // Time - @Time int aTime = 5 * UnitsTools.min; - @Time int bTime = 5 * UnitsTools.h; - - // Dimensions - // Acceleration - // :: error: (assignment.type.incompatible) - @Acceleration int sAcceleration = aAcceleration + bMass; - - // Area - // :: error: (assignment.type.incompatible) - @Luminance int sLuminance = aArea + bArea; - - // Current - // :: error: (assignment.type.incompatible) - @Current int sCurrent = aMass + bCurrent; - - // Length - // :: error: (assignment.type.incompatible) - @Length int sLength = aLength + bSubstance; - - // Luminance - // :: error: (assignment.type.incompatible) - @Temperature int sTemperature = aLuminance + bLuminance; - - // Mass - // :: error: (assignment.type.incompatible) - @Mass int sMass = aTemperature + bMass; - - // Substance - // :: error: (assignment.type.incompatible) - @Substance int sSubstance = aSubstance + bCurrent; - - // Temperature - // :: error: (assignment.type.incompatible) - @Area int sArea = aTemperature + bTemperature; - - // Time - // :: error: (assignment.type.incompatible) - @Time int sTime = aArea + bTime; - - // Force - // :: error: (assignment.type.incompatible) - sMass = aForce + bForce; - - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - - // Metric Ton - @t int aMetricTon = 5 * UnitsTools.t; - @t int bMetricTon = 5 * UnitsTools.t; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - - // Units - // Amperes - // :: error: (assignment.type.incompatible) - @g int sGram = aAmpere + bAmpere; - - // Candela - // :: error: (assignment.type.incompatible) - @cd int sCandela = aTemperature + bCandela; - - // Celsius - // :: error: (assignment.type.incompatible) - @C int sCelsius = aCelsius + bMillimeter; - - // Gram - // :: error: (assignment.type.incompatible) - @kg int sKilogram = aGram + bGram; - - // Hour - // :: error: (assignment.type.incompatible) - @h int sHour = aSquareMeter + bHour; - - // Kelvin - // :: error: (assignment.type.incompatible) - @K int sKelvin = aKelvin + bSecond; - - // Kilogram - // :: error: (assignment.type.incompatible) - @kmPERh int sKilometerPerHour = aKilogram + bKilogram; - - // Kilometer - // :: error: (assignment.type.incompatible) - @km int sKilometer = aCandela + bKilometer; - - // Square kilometer - // :: error: (assignment.type.incompatible) - @km2 int sSquareKilometer = aSquareKilometer + bAmpere; - - // Kilometer per hour - // :: error: (assignment.type.incompatible) - @mPERs int sMeterPerSecond = aKilometerPerHour + bKilometerPerHour; - - // Meter - // :: error: (assignment.type.incompatible) - @m int sMeter = aHour + bMeter; - - // Square meter - // :: error: (assignment.type.incompatible) - @m2 int sSquareMeter = aSquareMeter + bGram; - - // Meter per second - // :: error: (assignment.type.incompatible) - @mm2 int sSquareMillimeter = aMeterPerSecond + bMeterPerSecond; - - // Meter per second square - // :: error: (assignment.type.incompatible) - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeter; - - // Minute - // :: error: (assignment.type.incompatible) - @min int sMinute = aMole + bMinute; - - // Millimeter - // :: error: (assignment.type.incompatible) - @mm int sMillimeter = aMillimeter + bHour; - - // Square millimeter - // :: error: (assignment.type.incompatible) - @A int sAmpere = aSquareMillimeter + bSquareMillimeter; - - // Mole - // :: error: (assignment.type.incompatible) - @mol int sMole = aCandela + bMole; - - // Second - // :: error: (assignment.type.incompatible) - @s int sSecond = aSecond + bSquareKilometer; - - // Newton - // :: error: (assignment.type.incompatible) - sKilogram = aNewton + bNewton; - - // Kilonewton - // :: error: (assignment.type.incompatible) - @kN int sKilonewton = aKilonewton + bNewton; - - // Metric Ton - // :: error: (assignment.type.incompatible) - @N int sNewton = aNewton + bMetricTon; - } + // Addition is legal when the operands have the same units. + void good() { + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + @A int sAmpere = aAmpere + bAmpere; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + @cd int sCandela = aCandela + bCandela; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + @C int sCelsius = aCelsius + bCelsius; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + @g int sGram = aGram + bGram; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + @h int sHour = aHour + bHour; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + @K int sKelvin = aKelvin + bKelvin; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + @kg int sKilogram = aKilogram + bKilogram; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + @km int sKilometer = aKilometer + bKilometer; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + @km2 int sSquareKilometer = aSquareKilometer + bSquareKilometer; + + // Cubic kilometer + @km3 int aCubicKilometer = 5 * UnitsTools.km3; + @km3 int bCubicKilometer = 5 * UnitsTools.km3; + @km3 int sCubicKilometer = aCubicKilometer + bCubicKilometer; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int sKilometerPerHour = aKilometerPerHour + bKilometerPerHour; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + @m int sMeter = aMeter + bMeter; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + @m2 int sSquareMeter = aSquareMeter + bSquareMeter; + + // Cubic meter + @m3 int aCubicMeter = 5 * UnitsTools.m3; + @m3 int bCubicMeter = 5 * UnitsTools.m3; + @m3 int sCubicMeter = aCubicMeter + bCubicMeter; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int sMeterPerSecond = aMeterPerSecond + bMeterPerSecond; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeterPerSecondSquare; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + @min int sMinute = aMinute + bMinute; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + @mm int sMillimeter = aMillimeter + bMillimeter; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int sSquareMillimeter = aSquareMillimeter + bSquareMillimeter; + + // Cubic millimeter + @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int sCubicMillimeter = aCubicMillimeter + bCubicMillimeter; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + @mol int sMole = aMole + bMole; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + @N int sNewton = aNewton + bNewton; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + @kN int sKilonewton = aKilonewton + bKilonewton; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + @s int sSecond = aSecond + bSecond; + } + + // Addition is illegal when the operands have different units or one is unqualified. In these + // tests, we cycle between the result and the first or second operand having an incorrect type. + void bad() { + // Dimensions + // Acceleration + @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; + @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; + + // Area + @Area int aArea = 5 * UnitsTools.km2; + @Area int bArea = 5 * UnitsTools.mm2; + + // Current + @Current int aCurrent = 5 * UnitsTools.A; + @Current int bCurrent = 5 * UnitsTools.A; + + // Force + @Force int aForce = 5 * UnitsTools.N; + @Force int bForce = 5 * UnitsTools.N; + + // Length + @Length int aLength = 5 * UnitsTools.m; + @Length int bLength = 5 * UnitsTools.mm; + + // Luminance + @Luminance int aLuminance = 5 * UnitsTools.cd; + @Luminance int bLuminance = 5 * UnitsTools.cd; + + // Mass + @Mass int aMass = 5 * UnitsTools.kg; + @Mass int bMass = 5 * UnitsTools.g; + + // Substance + @Substance int aSubstance = 5 * UnitsTools.mol; + @Substance int bSubstance = 5 * UnitsTools.mol; + + // Temperature + @Temperature int aTemperature = 5 * UnitsTools.K; + @Temperature int bTemperature = 5 * UnitsTools.K; + + // Time + @Time int aTime = 5 * UnitsTools.min; + @Time int bTime = 5 * UnitsTools.h; + + // Dimensions + // Acceleration + // :: error: (assignment.type.incompatible) + @Acceleration int sAcceleration = aAcceleration + bMass; + + // Area + // :: error: (assignment.type.incompatible) + @Luminance int sLuminance = aArea + bArea; + + // Current + // :: error: (assignment.type.incompatible) + @Current int sCurrent = aMass + bCurrent; + + // Length + // :: error: (assignment.type.incompatible) + @Length int sLength = aLength + bSubstance; + + // Luminance + // :: error: (assignment.type.incompatible) + @Temperature int sTemperature = aLuminance + bLuminance; + + // Mass + // :: error: (assignment.type.incompatible) + @Mass int sMass = aTemperature + bMass; + + // Substance + // :: error: (assignment.type.incompatible) + @Substance int sSubstance = aSubstance + bCurrent; + + // Temperature + // :: error: (assignment.type.incompatible) + @Area int sArea = aTemperature + bTemperature; + + // Time + // :: error: (assignment.type.incompatible) + @Time int sTime = aArea + bTime; + + // Force + // :: error: (assignment.type.incompatible) + sMass = aForce + bForce; + + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + + // Metric Ton + @t int aMetricTon = 5 * UnitsTools.t; + @t int bMetricTon = 5 * UnitsTools.t; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + + // Units + // Amperes + // :: error: (assignment.type.incompatible) + @g int sGram = aAmpere + bAmpere; + + // Candela + // :: error: (assignment.type.incompatible) + @cd int sCandela = aTemperature + bCandela; + + // Celsius + // :: error: (assignment.type.incompatible) + @C int sCelsius = aCelsius + bMillimeter; + + // Gram + // :: error: (assignment.type.incompatible) + @kg int sKilogram = aGram + bGram; + + // Hour + // :: error: (assignment.type.incompatible) + @h int sHour = aSquareMeter + bHour; + + // Kelvin + // :: error: (assignment.type.incompatible) + @K int sKelvin = aKelvin + bSecond; + + // Kilogram + // :: error: (assignment.type.incompatible) + @kmPERh int sKilometerPerHour = aKilogram + bKilogram; + + // Kilometer + // :: error: (assignment.type.incompatible) + @km int sKilometer = aCandela + bKilometer; + + // Square kilometer + // :: error: (assignment.type.incompatible) + @km2 int sSquareKilometer = aSquareKilometer + bAmpere; + + // Kilometer per hour + // :: error: (assignment.type.incompatible) + @mPERs int sMeterPerSecond = aKilometerPerHour + bKilometerPerHour; + + // Meter + // :: error: (assignment.type.incompatible) + @m int sMeter = aHour + bMeter; + + // Square meter + // :: error: (assignment.type.incompatible) + @m2 int sSquareMeter = aSquareMeter + bGram; + + // Meter per second + // :: error: (assignment.type.incompatible) + @mm2 int sSquareMillimeter = aMeterPerSecond + bMeterPerSecond; + + // Meter per second square + // :: error: (assignment.type.incompatible) + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeter; + + // Minute + // :: error: (assignment.type.incompatible) + @min int sMinute = aMole + bMinute; + + // Millimeter + // :: error: (assignment.type.incompatible) + @mm int sMillimeter = aMillimeter + bHour; + + // Square millimeter + // :: error: (assignment.type.incompatible) + @A int sAmpere = aSquareMillimeter + bSquareMillimeter; + + // Mole + // :: error: (assignment.type.incompatible) + @mol int sMole = aCandela + bMole; + + // Second + // :: error: (assignment.type.incompatible) + @s int sSecond = aSecond + bSquareKilometer; + + // Newton + // :: error: (assignment.type.incompatible) + sKilogram = aNewton + bNewton; + + // Kilonewton + // :: error: (assignment.type.incompatible) + @kN int sKilonewton = aKilonewton + bNewton; + + // Metric Ton + // :: error: (assignment.type.incompatible) + @N int sNewton = aNewton + bMetricTon; + } } diff --git a/checker/tests/units/BasicUnits.java b/checker/tests/units/BasicUnits.java index 68167f36ebb..16a42e9ba23 100644 --- a/checker/tests/units/BasicUnits.java +++ b/checker/tests/units/BasicUnits.java @@ -20,130 +20,130 @@ public class BasicUnits { - void demo() { - // :: error: (assignment.type.incompatible) - @m int merr = 5; + void demo() { + // :: error: (assignment.type.incompatible) + @m int merr = 5; - @m int m = 5 * UnitsTools.m; - @s int s = 9 * UnitsTools.s; + @m int m = 5 * UnitsTools.m; + @s int s = 9 * UnitsTools.s; - // :: error: (assignment.type.incompatible) - @km int kmerr = 10; - @km int km = 10 * UnitsTools.km; + // :: error: (assignment.type.incompatible) + @km int kmerr = 10; + @km int km = 10 * UnitsTools.km; - // this is allowed, unqualified is a supertype of all units - int bad = m / s; + // this is allowed, unqualified is a supertype of all units + int bad = m / s; - @mPERs int good = m / s; + @mPERs int good = m / s; - // :: error: (assignment.type.incompatible) - @mPERs int b1 = s / m; + // :: error: (assignment.type.incompatible) + @mPERs int b1 = s / m; - // :: error: (assignment.type.incompatible) - @mPERs int b2 = m * s; + // :: error: (assignment.type.incompatible) + @mPERs int b2 = m * s; - @mPERs2 int goodaccel = m / s / s; + @mPERs2 int goodaccel = m / s / s; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel1 = s / m / s; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel1 = s / m / s; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel2 = s / s / m; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel2 = s / s / m; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel3 = s * s / m; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel3 = s * s / m; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel4 = m * s * s; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel4 = m * s * s; - @Area int ae = m * m; - @m2 int gae = m * m; + @Area int ae = m * m; + @m2 int gae = m * m; - // :: error: (assignment.type.incompatible) - @Area int bae = m * m * m; + // :: error: (assignment.type.incompatible) + @Area int bae = m * m * m; - // :: error: (assignment.type.incompatible) - @km2 int bae1 = m * m; + // :: error: (assignment.type.incompatible) + @km2 int bae1 = m * m; - @Volume int vol = m * m * m; - @m3 int gvol = m * m * m; + @Volume int vol = m * m * m; + @m3 int gvol = m * m * m; - // :: error: (assignment.type.incompatible) - @Volume int bvol = m * m * m * m; + // :: error: (assignment.type.incompatible) + @Volume int bvol = m * m * m * m; - // :: error: (assignment.type.incompatible) - @km3 int bvol1 = m * m * m; + // :: error: (assignment.type.incompatible) + @km3 int bvol1 = m * m * m; - @radians double rad = 20.0d * UnitsTools.rad; - @degrees double deg = 30.0d * UnitsTools.deg; + @radians double rad = 20.0d * UnitsTools.rad; + @degrees double deg = 30.0d * UnitsTools.deg; - @degrees double rToD1 = UnitsTools.toDegrees(rad); - // :: error: (argument.type.incompatible) - @degrees double rToD2 = UnitsTools.toDegrees(deg); - // :: error: (assignment.type.incompatible) - @radians double rToD3 = UnitsTools.toDegrees(rad); + @degrees double rToD1 = UnitsTools.toDegrees(rad); + // :: error: (argument.type.incompatible) + @degrees double rToD2 = UnitsTools.toDegrees(deg); + // :: error: (assignment.type.incompatible) + @radians double rToD3 = UnitsTools.toDegrees(rad); - @radians double dToR1 = UnitsTools.toRadians(deg); - // :: error: (argument.type.incompatible) - @radians double rToR2 = UnitsTools.toRadians(rad); - // :: error: (assignment.type.incompatible) - @degrees double rToR3 = UnitsTools.toRadians(deg); + @radians double dToR1 = UnitsTools.toRadians(deg); + // :: error: (argument.type.incompatible) + @radians double rToR2 = UnitsTools.toRadians(rad); + // :: error: (assignment.type.incompatible) + @degrees double rToR3 = UnitsTools.toRadians(deg); - // speed conversion - @mPERs int mPs = 30 * UnitsTools.mPERs; - @kmPERh int kmPhr = 20 * UnitsTools.kmPERh; + // speed conversion + @mPERs int mPs = 30 * UnitsTools.mPERs; + @kmPERh int kmPhr = 20 * UnitsTools.kmPERh; - @kmPERh int kmPhrRes = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); - @mPERs int mPsRes = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); + @kmPERh int kmPhrRes = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); + @mPERs int mPsRes = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); - // :: error: (assignment.type.incompatible) - @mPERs int mPsResBad = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); - // :: error: (assignment.type.incompatible) - @kmPERh int kmPhrResBad = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); + // :: error: (assignment.type.incompatible) + @mPERs int mPsResBad = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); + // :: error: (assignment.type.incompatible) + @kmPERh int kmPhrResBad = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); - // speeds - @km int kilometers = 10 * UnitsTools.km; - @h int hours = UnitsTools.h; - @kmPERh int speed = kilometers / hours; + // speeds + @km int kilometers = 10 * UnitsTools.km; + @h int hours = UnitsTools.h; + @kmPERh int speed = kilometers / hours; - // Addition/substraction only accepts another @kmPERh value - // :: error: (assignment.type.incompatible) - speed = speed + 5; - // :: error: (compound.assignment.type.incompatible) - speed += 5; + // Addition/substraction only accepts another @kmPERh value + // :: error: (assignment.type.incompatible) + speed = speed + 5; + // :: error: (compound.assignment.type.incompatible) + speed += 5; - speed += speed; - speed = (speed += speed); + speed += speed; + speed = (speed += speed); - // Multiplication/division with an unqualified type is allowed - speed = kilometers / hours * 2; - speed /= 2; + // Multiplication/division with an unqualified type is allowed + speed = kilometers / hours * 2; + speed /= 2; - speed = (speed /= 2); + speed = (speed /= 2); - @kg int kiloGrams = 1000 * UnitsTools.kg; - @t int metricTons = UnitsTools.fromKiloGramToMetricTon(kiloGrams); - kiloGrams = UnitsTools.fromMetricTonToKiloGram(metricTons); - } + @kg int kiloGrams = 1000 * UnitsTools.kg; + @t int metricTons = UnitsTools.fromKiloGramToMetricTon(kiloGrams); + kiloGrams = UnitsTools.fromMetricTonToKiloGram(metricTons); + } - void prefixOutputTest() { - @m int x = 5 * UnitsTools.m; - @m(Prefix.kilo) int y = 2 * UnitsTools.km; - @m(Prefix.one) int z = 3 * UnitsTools.m; - @km int y2 = 3 * UnitsTools.km; + void prefixOutputTest() { + @m int x = 5 * UnitsTools.m; + @m(Prefix.kilo) int y = 2 * UnitsTools.km; + @m(Prefix.one) int z = 3 * UnitsTools.m; + @km int y2 = 3 * UnitsTools.km; - // :: error: (assignment.type.incompatible) - y2 = z; - // :: error: (assignment.type.incompatible) - y2 = x; - // :: error: (assignment.type.incompatible) - y = z; - // :: error: (assignment.type.incompatible) - y = x; + // :: error: (assignment.type.incompatible) + y2 = z; + // :: error: (assignment.type.incompatible) + y2 = x; + // :: error: (assignment.type.incompatible) + y = z; + // :: error: (assignment.type.incompatible) + y = x; - // :: error: (assignment.type.incompatible) - y2 = x * x; - // :: error: (assignment.type.incompatible) - y2 = z * z; - } + // :: error: (assignment.type.incompatible) + y2 = x * x; + // :: error: (assignment.type.incompatible) + y2 = z * z; + } } diff --git a/checker/tests/units/Consistency.java b/checker/tests/units/Consistency.java index 3a42759a77b..727d39bfe7d 100644 --- a/checker/tests/units/Consistency.java +++ b/checker/tests/units/Consistency.java @@ -14,34 +14,34 @@ */ public class Consistency { - @UnitsSame({0, 1}) - @UnitsProduct({0, 1, -1}) - @Area int calcArea(@Length int width, @Length int height) { - return width * height; - } + @UnitsSame({0, 1}) + @UnitsProduct({0, 1, -1}) + @Area int calcArea(@Length int width, @Length int height) { + return width * height; + } - void use() { - @m int m1, m2; - m1 = UnitsTools.toMeter(5); - m2 = UnitsTools.toMeter(51); + void use() { + @m int m1, m2; + m1 = UnitsTools.toMeter(5); + m2 = UnitsTools.toMeter(51); - @km int km1, km2; - km1 = UnitsTools.toMeter(5); - km2 = UnitsTools.toMeter(5); + @km int km1, km2; + km1 = UnitsTools.toMeter(5); + km2 = UnitsTools.toMeter(5); - @m2 int msq; - @km2 int kmsq; + @m2 int msq; + @km2 int kmsq; - // good - msq = calcArea(m1, m2); + // good + msq = calcArea(m1, m2); - // :: bad args - msq = calcArea(m1, km2); + // :: bad args + msq = calcArea(m1, km2); - // :: bad return - kmsq = calcArea(m1, m2); + // :: bad return + kmsq = calcArea(m1, m2); - // good - kmsq = calcArea(km1, km2); - } + // good + kmsq = calcArea(km1, km2); + } } diff --git a/checker/tests/units/Division.java b/checker/tests/units/Division.java index bc0ff74bbf5..2e2d37b0c33 100644 --- a/checker/tests/units/Division.java +++ b/checker/tests/units/Division.java @@ -19,127 +19,127 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Division { - void d() { - // Basic division of same units, no units constraint on x - @m int am = 6 * UnitsTools.m, bm = 3 * UnitsTools.m; - int x = am / bm; - - // :: error: (assignment.type.incompatible) - @m int bad = am / bm; - - // Division removes the unit. - // As unqualified would be a supertype, we add another multiplication - // to make sure the result of the division is unqualified. - @s int div = (am / UnitsTools.m) * UnitsTools.s; - - // units setup - @m int m = 2 * UnitsTools.m; - @mm int mm = 8 * UnitsTools.mm; - @km int km = 4 * UnitsTools.km; - @s int s = 3 * UnitsTools.s; - @h int h = 5 * UnitsTools.h; - @m2 int m2 = 25 * UnitsTools.m2; - @km2 int km2 = 9 * UnitsTools.km2; - @mm2 int mm2 = 16 * UnitsTools.mm2; - @mPERs int mPERs = 20 * UnitsTools.mPERs; - @kmPERh int kmPERh = 2 * UnitsTools.kmPERh; - @mPERs2 int mPERs2 = 30 * UnitsTools.mPERs2; - @m3 int m3 = 125 * UnitsTools.m3; - @km3 int km3 = 27 * UnitsTools.km3; - @mm3 int mm3 = 64 * UnitsTools.mm3; - @kg int kg = 11 * UnitsTools.kg; - @t int t = 19 * UnitsTools.t; - @N int N = 7 * UnitsTools.N; - @kN int kN = 13 * UnitsTools.kN; - - // m / s = mPERs - @mPERs int velocitym = m / s; - // :: error: (assignment.type.incompatible) - velocitym = m / h; - - // km / h = kmPERh - @kmPERh int velocitykm = km / h; - // :: error: (assignment.type.incompatible) - velocitykm = km / s; - - // m2 / m = m - @m int distancem = m2 / m; - // :: error: (assignment.type.incompatible) - distancem = m2 / km; - - // km2 / km = km - @km int distancekm = km2 / km; - // :: error: (assignment.type.incompatible) - distancekm = km2 / m; - - // mm2 / mm = mm - @mm int distancemm = mm2 / mm; - // :: error: (assignment.type.incompatible) - distancemm = km2 / mm; - - // m3 / m2 = m - distancem = m3 / m2; - // :: error: (assignment.type.incompatible) - distancem = m3 / km2; - - // km3 / km2 = km - distancekm = km3 / km2; - // :: error: (assignment.type.incompatible) - distancekm = km3 / m2; - - // mm3 / mm2 = mm - distancemm = mm3 / mm2; - // :: error: (assignment.type.incompatible) - distancemm = km3 / mm2; - - // m / mPERs = s - @s int times = m / mPERs; - // :: error: (assignment.type.incompatible) - times = km / mPERs; - - // km / kmPERh = h - @h int timeh = km / kmPERh; - // :: error: (assignment.type.incompatible) - timeh = m / kmPERh; - - // mPERs / s = mPERs2 - @mPERs2 int accel1 = mPERs / s; - // :: error: (assignment.type.incompatible) - accel1 = kmPERh / s; - - // mPERs / mPERs2 = s - @s int times2 = mPERs / mPERs2; - // :: error: (assignment.type.incompatible) - times2 = kmPERh / mPERs2; - - // mPERs2 = N / kg - @mPERs2 int accel2 = N / kg; - // :: error: (assignment.type.incompatible) - accel2 = N / km; - - // mPERs2 = kN / t - @mPERs2 int accel3 = kN / t; - // :: error: (assignment.type.incompatible) - accel3 = N / t; - - // kg = N / mPERs2 - @kg int mass = N / mPERs2; - // :: error: (assignment.type.incompatible) - mass = s / mPERs2; - - // t = kN / mPERs2 - @t int mass2 = kN / mPERs2; - // :: error: (assignment.type.incompatible) - mass2 = N / mPERs2; - } - - void SpeedOfSoundTests() { - @mPERs double speedOfSound = (340.29 * UnitsTools.m) / (UnitsTools.s); - - @s double tenSeconds = 10.0 * UnitsTools.s; - @m double soundIn10Seconds = speedOfSound * tenSeconds; - - @m double length = 100.0 * UnitsTools.m; - @s double soundNeedTimeForLength = length / speedOfSound; - } + void d() { + // Basic division of same units, no units constraint on x + @m int am = 6 * UnitsTools.m, bm = 3 * UnitsTools.m; + int x = am / bm; + + // :: error: (assignment.type.incompatible) + @m int bad = am / bm; + + // Division removes the unit. + // As unqualified would be a supertype, we add another multiplication + // to make sure the result of the division is unqualified. + @s int div = (am / UnitsTools.m) * UnitsTools.s; + + // units setup + @m int m = 2 * UnitsTools.m; + @mm int mm = 8 * UnitsTools.mm; + @km int km = 4 * UnitsTools.km; + @s int s = 3 * UnitsTools.s; + @h int h = 5 * UnitsTools.h; + @m2 int m2 = 25 * UnitsTools.m2; + @km2 int km2 = 9 * UnitsTools.km2; + @mm2 int mm2 = 16 * UnitsTools.mm2; + @mPERs int mPERs = 20 * UnitsTools.mPERs; + @kmPERh int kmPERh = 2 * UnitsTools.kmPERh; + @mPERs2 int mPERs2 = 30 * UnitsTools.mPERs2; + @m3 int m3 = 125 * UnitsTools.m3; + @km3 int km3 = 27 * UnitsTools.km3; + @mm3 int mm3 = 64 * UnitsTools.mm3; + @kg int kg = 11 * UnitsTools.kg; + @t int t = 19 * UnitsTools.t; + @N int N = 7 * UnitsTools.N; + @kN int kN = 13 * UnitsTools.kN; + + // m / s = mPERs + @mPERs int velocitym = m / s; + // :: error: (assignment.type.incompatible) + velocitym = m / h; + + // km / h = kmPERh + @kmPERh int velocitykm = km / h; + // :: error: (assignment.type.incompatible) + velocitykm = km / s; + + // m2 / m = m + @m int distancem = m2 / m; + // :: error: (assignment.type.incompatible) + distancem = m2 / km; + + // km2 / km = km + @km int distancekm = km2 / km; + // :: error: (assignment.type.incompatible) + distancekm = km2 / m; + + // mm2 / mm = mm + @mm int distancemm = mm2 / mm; + // :: error: (assignment.type.incompatible) + distancemm = km2 / mm; + + // m3 / m2 = m + distancem = m3 / m2; + // :: error: (assignment.type.incompatible) + distancem = m3 / km2; + + // km3 / km2 = km + distancekm = km3 / km2; + // :: error: (assignment.type.incompatible) + distancekm = km3 / m2; + + // mm3 / mm2 = mm + distancemm = mm3 / mm2; + // :: error: (assignment.type.incompatible) + distancemm = km3 / mm2; + + // m / mPERs = s + @s int times = m / mPERs; + // :: error: (assignment.type.incompatible) + times = km / mPERs; + + // km / kmPERh = h + @h int timeh = km / kmPERh; + // :: error: (assignment.type.incompatible) + timeh = m / kmPERh; + + // mPERs / s = mPERs2 + @mPERs2 int accel1 = mPERs / s; + // :: error: (assignment.type.incompatible) + accel1 = kmPERh / s; + + // mPERs / mPERs2 = s + @s int times2 = mPERs / mPERs2; + // :: error: (assignment.type.incompatible) + times2 = kmPERh / mPERs2; + + // mPERs2 = N / kg + @mPERs2 int accel2 = N / kg; + // :: error: (assignment.type.incompatible) + accel2 = N / km; + + // mPERs2 = kN / t + @mPERs2 int accel3 = kN / t; + // :: error: (assignment.type.incompatible) + accel3 = N / t; + + // kg = N / mPERs2 + @kg int mass = N / mPERs2; + // :: error: (assignment.type.incompatible) + mass = s / mPERs2; + + // t = kN / mPERs2 + @t int mass2 = kN / mPERs2; + // :: error: (assignment.type.incompatible) + mass2 = N / mPERs2; + } + + void SpeedOfSoundTests() { + @mPERs double speedOfSound = (340.29 * UnitsTools.m) / (UnitsTools.s); + + @s double tenSeconds = 10.0 * UnitsTools.s; + @m double soundIn10Seconds = speedOfSound * tenSeconds; + + @m double length = 100.0 * UnitsTools.m; + @s double soundNeedTimeForLength = length / speedOfSound; + } } diff --git a/checker/tests/units/Issue4549.java b/checker/tests/units/Issue4549.java index 41c0fd9c082..5df72565720 100644 --- a/checker/tests/units/Issue4549.java +++ b/checker/tests/units/Issue4549.java @@ -2,10 +2,10 @@ import java.util.Map; class Issue4549 { - private Map map = new HashMap<>(); + private Map map = new HashMap<>(); - void testMethod(String s, int i) { - Long l = map.get(s); - l += i; - } + void testMethod(String s, int i) { + Long l = map.get(s); + l += i; + } } diff --git a/checker/tests/units/Manual.java b/checker/tests/units/Manual.java index d4df3842e11..608b7833ac8 100644 --- a/checker/tests/units/Manual.java +++ b/checker/tests/units/Manual.java @@ -6,9 +6,9 @@ // Include all the examples from the manual here, // to ensure they work as expected. public class Manual { - void demo1() { - @m int meters = 5 * UnitsTools.m; - @s int secs = 2 * UnitsTools.s; - @mPERs int speed = meters / secs; - } + void demo1() { + @m int meters = 5 * UnitsTools.m; + @s int secs = 2 * UnitsTools.s; + @mPERs int speed = meters / secs; + } } diff --git a/checker/tests/units/Multiples.java b/checker/tests/units/Multiples.java index 250e5b3f1ba..0a21f98885b 100644 --- a/checker/tests/units/Multiples.java +++ b/checker/tests/units/Multiples.java @@ -21,187 +21,187 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Multiples { - void m() { - // Prefix assignment tests - // kg - @kg int kg = 5 * UnitsTools.kg; - @g(Prefix.kilo) int alsokg = kg; - // :: error: (assignment.type.incompatible) - @g(Prefix.giga) int notkg = kg; - // :: error: (assignment.type.incompatible) - kg = notkg; - kg = alsokg; - - // g - @g int g = 5 * UnitsTools.g; - @g(Prefix.one) int alsog = g; - // :: error: (assignment.type.incompatible) - @g(Prefix.milli) int notg = g; - // :: error: (assignment.type.incompatible) - notg = g; - g = alsog; - - // m - @m int m = 5 * UnitsTools.m; - @m(Prefix.one) int alsom = m; - // :: error: (assignment.type.incompatible) - @m(Prefix.giga) int notm = m; - // :: error: (assignment.type.incompatible) - m = notm; - m = alsom; - - // km - @km int km = 5 * UnitsTools.km; - @m(Prefix.kilo) int alsokm = km; - // :: error: (assignment.type.incompatible) - @m(Prefix.giga) int notkm = km; - // :: error: (assignment.type.incompatible) - km = notkm; - km = alsokm; - - // mm - @mm int mm = 5 * UnitsTools.mm; - @m(Prefix.milli) int alsomm = mm; - // :: error: (assignment.type.incompatible) - @m(Prefix.giga) int notmm = mm; - // :: error: (assignment.type.incompatible) - mm = notmm; - mm = alsomm; - - // N - @N int N = 5 * UnitsTools.N; - @N(Prefix.one) int alsoN = N; - @N(Prefix.giga) - // :: error: (assignment.type.incompatible) - int notN = N; - // :: error: (assignment.type.incompatible) - N = notN; - N = alsoN; - - // kN - @kN int kN = 5 * UnitsTools.kN; - @N(Prefix.kilo) int alsokN = kN; - @N(Prefix.giga) - // :: error: (assignment.type.incompatible) - int notkN = kN; - // :: error: (assignment.type.incompatible) - kN = notkN; - kN = alsokN; - - // s - @s int s = 5 * UnitsTools.s; - - // h - @h int h = 5 * UnitsTools.h; - - // m * m = m2 - @m2 int area = m * m; - // :: error: (assignment.type.incompatible) - @km2 int areambad1 = m * m; - // :: error: (assignment.type.incompatible) - @mm2 int areambad2 = m * m; - - // km * km = km2 - @km2 int karea = km * km; - // :: error: (assignment.type.incompatible) - @m2 int areakmbad1 = km * km; - // :: error: (assignment.type.incompatible) - @mm2 int areakmbad2 = km * km; - - // mm * mm = mm2 - @mm2 int marea = mm * mm; - // :: error: (assignment.type.incompatible) - @m2 int areammbad1 = mm * mm; - // :: error: (assignment.type.incompatible) - @km2 int areammbad2 = mm * mm; - - // m * m2 = m3 - @m3 int volume = m * area; - // :: error: (assignment.type.incompatible) - @km3 int volumembad1 = m * area; - // :: error: (assignment.type.incompatible) - @mm3 int volumembad2 = m * area; - - // km * km2 = km3 - @km3 int kvolume = km * karea; - // :: error: (assignment.type.incompatible) - @m3 int volumekmbad1 = km * karea; - // :: error: (assignment.type.incompatible) - @mm3 int volumekmbad2 = km * karea; - - // mm * mm2 = mm3 - @mm3 int mvolume = mm * marea; - // :: error: (assignment.type.incompatible) - @m3 int volumemmbad1 = mm * marea; - // :: error: (assignment.type.incompatible) - @km3 int volumemmbad2 = mm * marea; - - // m2 * m = m3 - volume = area * m; - // :: error: (assignment.type.incompatible) - volumembad1 = area * m; - // :: error: (assignment.type.incompatible) - volumembad2 = area * m; - - // km2 * km = km3 - kvolume = karea * km; - // :: error: (assignment.type.incompatible) - volumekmbad1 = karea * km; - // :: error: (assignment.type.incompatible) - volumekmbad2 = karea * km; - - // mm2 * mm = mm3 - mvolume = marea * mm; - // :: error: (assignment.type.incompatible) - volumemmbad1 = marea * mm; - // :: error: (assignment.type.incompatible) - volumemmbad2 = marea * mm; - - // s * mPERs = m - @mPERs int speedm = 10 * UnitsTools.mPERs; - @m int lengthm = s * speedm; - lengthm = speedm * s; - // :: error: (assignment.type.incompatible) - @km int lengthmbad1 = s * speedm; - // :: error: (assignment.type.incompatible) - @mm int lengthmbad2 = s * speedm; - - // s * mPERs2 = mPERs - @mPERs2 int accelm = 20 * UnitsTools.mPERs2; - @mPERs int speedm2 = s * accelm; - speedm2 = accelm * s; - // :: error: (assignment.type.incompatible) - @kmPERh int speedm2bad1 = s * accelm; - - // h * kmPERh = km - @kmPERh int speedkm = 30 * UnitsTools.kmPERh; - @km int lengthkm = h * speedkm; - lengthkm = speedkm * h; - // :: error: (assignment.type.incompatible) - @m int lengthkmbad1 = h * speedkm; - // :: error: (assignment.type.incompatible) - @mm int lengthkmbad2 = h * speedkm; - - // kg * mPERs2 = N - @kg int mass = 40 * UnitsTools.kg; - @mPERs2 int accel = 50 * UnitsTools.mPERs2; - @N int force = mass * accel; - - // mPERs2 * kg = N - @N int alsoforce = accel * mass; - - @t int massMetricTons = 50 * UnitsTools.t; - @kN int forceKiloNewtons = massMetricTons * accel; - forceKiloNewtons = accel * massMetricTons; - - // s * s * mPERs2 = m - // TODO: fix checker so it is insensitive to order of operations as long as final results' - // unit makes sense. - // Currently due to left associativity, and the lack of an s2 annotation, this tries to - // evaluate (s * s) * mPERs2 which causes the type assignment incompatible error. - // :: error: (assignment.type.incompatible) - @m int distance = s * s * accelm; - // if we bracket for order of operations, it works fine - distance = s * (s * accelm); - } + void m() { + // Prefix assignment tests + // kg + @kg int kg = 5 * UnitsTools.kg; + @g(Prefix.kilo) int alsokg = kg; + // :: error: (assignment.type.incompatible) + @g(Prefix.giga) int notkg = kg; + // :: error: (assignment.type.incompatible) + kg = notkg; + kg = alsokg; + + // g + @g int g = 5 * UnitsTools.g; + @g(Prefix.one) int alsog = g; + // :: error: (assignment.type.incompatible) + @g(Prefix.milli) int notg = g; + // :: error: (assignment.type.incompatible) + notg = g; + g = alsog; + + // m + @m int m = 5 * UnitsTools.m; + @m(Prefix.one) int alsom = m; + // :: error: (assignment.type.incompatible) + @m(Prefix.giga) int notm = m; + // :: error: (assignment.type.incompatible) + m = notm; + m = alsom; + + // km + @km int km = 5 * UnitsTools.km; + @m(Prefix.kilo) int alsokm = km; + // :: error: (assignment.type.incompatible) + @m(Prefix.giga) int notkm = km; + // :: error: (assignment.type.incompatible) + km = notkm; + km = alsokm; + + // mm + @mm int mm = 5 * UnitsTools.mm; + @m(Prefix.milli) int alsomm = mm; + // :: error: (assignment.type.incompatible) + @m(Prefix.giga) int notmm = mm; + // :: error: (assignment.type.incompatible) + mm = notmm; + mm = alsomm; + + // N + @N int N = 5 * UnitsTools.N; + @N(Prefix.one) int alsoN = N; + @N(Prefix.giga) + // :: error: (assignment.type.incompatible) + int notN = N; + // :: error: (assignment.type.incompatible) + N = notN; + N = alsoN; + + // kN + @kN int kN = 5 * UnitsTools.kN; + @N(Prefix.kilo) int alsokN = kN; + @N(Prefix.giga) + // :: error: (assignment.type.incompatible) + int notkN = kN; + // :: error: (assignment.type.incompatible) + kN = notkN; + kN = alsokN; + + // s + @s int s = 5 * UnitsTools.s; + + // h + @h int h = 5 * UnitsTools.h; + + // m * m = m2 + @m2 int area = m * m; + // :: error: (assignment.type.incompatible) + @km2 int areambad1 = m * m; + // :: error: (assignment.type.incompatible) + @mm2 int areambad2 = m * m; + + // km * km = km2 + @km2 int karea = km * km; + // :: error: (assignment.type.incompatible) + @m2 int areakmbad1 = km * km; + // :: error: (assignment.type.incompatible) + @mm2 int areakmbad2 = km * km; + + // mm * mm = mm2 + @mm2 int marea = mm * mm; + // :: error: (assignment.type.incompatible) + @m2 int areammbad1 = mm * mm; + // :: error: (assignment.type.incompatible) + @km2 int areammbad2 = mm * mm; + + // m * m2 = m3 + @m3 int volume = m * area; + // :: error: (assignment.type.incompatible) + @km3 int volumembad1 = m * area; + // :: error: (assignment.type.incompatible) + @mm3 int volumembad2 = m * area; + + // km * km2 = km3 + @km3 int kvolume = km * karea; + // :: error: (assignment.type.incompatible) + @m3 int volumekmbad1 = km * karea; + // :: error: (assignment.type.incompatible) + @mm3 int volumekmbad2 = km * karea; + + // mm * mm2 = mm3 + @mm3 int mvolume = mm * marea; + // :: error: (assignment.type.incompatible) + @m3 int volumemmbad1 = mm * marea; + // :: error: (assignment.type.incompatible) + @km3 int volumemmbad2 = mm * marea; + + // m2 * m = m3 + volume = area * m; + // :: error: (assignment.type.incompatible) + volumembad1 = area * m; + // :: error: (assignment.type.incompatible) + volumembad2 = area * m; + + // km2 * km = km3 + kvolume = karea * km; + // :: error: (assignment.type.incompatible) + volumekmbad1 = karea * km; + // :: error: (assignment.type.incompatible) + volumekmbad2 = karea * km; + + // mm2 * mm = mm3 + mvolume = marea * mm; + // :: error: (assignment.type.incompatible) + volumemmbad1 = marea * mm; + // :: error: (assignment.type.incompatible) + volumemmbad2 = marea * mm; + + // s * mPERs = m + @mPERs int speedm = 10 * UnitsTools.mPERs; + @m int lengthm = s * speedm; + lengthm = speedm * s; + // :: error: (assignment.type.incompatible) + @km int lengthmbad1 = s * speedm; + // :: error: (assignment.type.incompatible) + @mm int lengthmbad2 = s * speedm; + + // s * mPERs2 = mPERs + @mPERs2 int accelm = 20 * UnitsTools.mPERs2; + @mPERs int speedm2 = s * accelm; + speedm2 = accelm * s; + // :: error: (assignment.type.incompatible) + @kmPERh int speedm2bad1 = s * accelm; + + // h * kmPERh = km + @kmPERh int speedkm = 30 * UnitsTools.kmPERh; + @km int lengthkm = h * speedkm; + lengthkm = speedkm * h; + // :: error: (assignment.type.incompatible) + @m int lengthkmbad1 = h * speedkm; + // :: error: (assignment.type.incompatible) + @mm int lengthkmbad2 = h * speedkm; + + // kg * mPERs2 = N + @kg int mass = 40 * UnitsTools.kg; + @mPERs2 int accel = 50 * UnitsTools.mPERs2; + @N int force = mass * accel; + + // mPERs2 * kg = N + @N int alsoforce = accel * mass; + + @t int massMetricTons = 50 * UnitsTools.t; + @kN int forceKiloNewtons = massMetricTons * accel; + forceKiloNewtons = accel * massMetricTons; + + // s * s * mPERs2 = m + // TODO: fix checker so it is insensitive to order of operations as long as final results' + // unit makes sense. + // Currently due to left associativity, and the lack of an s2 annotation, this tries to + // evaluate (s * s) * mPERs2 which causes the type assignment incompatible error. + // :: error: (assignment.type.incompatible) + @m int distance = s * s * accelm; + // if we bracket for order of operations, it works fine + distance = s * (s * accelm); + } } diff --git a/checker/tests/units/PolyUnitTest.java b/checker/tests/units/PolyUnitTest.java index df848df97ba..ec44c892189 100644 --- a/checker/tests/units/PolyUnitTest.java +++ b/checker/tests/units/PolyUnitTest.java @@ -5,18 +5,18 @@ public class PolyUnitTest { - @PolyUnit int triplePolyUnit(@PolyUnit int amount) { - return 3 * amount; - } + @PolyUnit int triplePolyUnit(@PolyUnit int amount) { + return 3 * amount; + } - void testPolyUnit() { - @m int m1 = 7 * UnitsTools.m; - @m int m2 = triplePolyUnit(m1); + void testPolyUnit() { + @m int m1 = 7 * UnitsTools.m; + @m int m2 = triplePolyUnit(m1); - @s int sec1 = 7 * UnitsTools.s; - @s int sec2 = triplePolyUnit(sec1); + @s int sec1 = 7 * UnitsTools.s; + @s int sec2 = triplePolyUnit(sec1); - // :: error: (assignment.type.incompatible) - @s int sec3 = triplePolyUnit(m1); - } + // :: error: (assignment.type.incompatible) + @s int sec3 = triplePolyUnit(m1); + } } diff --git a/checker/tests/units/SubtractionUnits.java b/checker/tests/units/SubtractionUnits.java index 9201cd5b745..63f4217d28f 100644 --- a/checker/tests/units/SubtractionUnits.java +++ b/checker/tests/units/SubtractionUnits.java @@ -37,419 +37,419 @@ import org.checkerframework.checker.units.util.UnitsTools; public class SubtractionUnits { - // Subtraction is legal when the operands have the same units. - void good() { - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - @A int sAmpere = aAmpere - bAmpere; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - @cd int sCandela = aCandela - bCandela; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - @C int sCelsius = aCelsius - bCelsius; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - @g int sGram = aGram - bGram; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - @h int sHour = aHour - bHour; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - @K int sKelvin = aKelvin - bKelvin; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - @kN int sKiloewton = aKilonewton - bKilonewton; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - @kg int sKilogram = aKilogram - bKilogram; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - @km int sKilometer = aKilometer - bKilometer; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - @km2 int sSquareKilometer = aSquareKilometer - bSquareKilometer; - - // Cubic kilometer - @km3 int aCubicKilometer = 5 * UnitsTools.km3; - @km3 int bCubicKilometer = 5 * UnitsTools.km3; - @km3 int sCubicKilometer = aCubicKilometer - bCubicKilometer; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int sKilometerPerHour = aKilometerPerHour - bKilometerPerHour; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - @m int sMeter = aMeter - bMeter; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - @m2 int sSquareMeter = aSquareMeter - bSquareMeter; - - // Cubic meter - @m3 int aCubicMeter = 5 * UnitsTools.m3; - @m3 int bCubicMeter = 5 * UnitsTools.m3; - @m3 int sCubicMeter = aCubicMeter - bCubicMeter; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int sMeterPerSecond = aMeterPerSecond - bMeterPerSecond; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeterPerSecondSquare; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - @min int sMinute = aMinute - bMinute; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - @mm int sMillimeter = aMillimeter - bMillimeter; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int sSquareMillimeter = aSquareMillimeter - bSquareMillimeter; - - // Cubic millimeter - @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int sCubicMillimeter = aCubicMillimeter - bCubicMillimeter; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - @mol int sMole = aMole - bMole; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - @N int sNewton = aNewton - bNewton; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - @s int sSecond = aSecond - bSecond; - } - - // Subtraction is illegal when the operands have different units or one is unqualified. In - // these tests, we cycle between the result and the first or second operand having an incorrect - // type. - void bad() { - // Dimensions - // Acceleration - @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; - @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; - - // Area - @Area int aArea = 5 * UnitsTools.km2; - @Area int bArea = 5 * UnitsTools.mm2; - - // Current - @Current int aCurrent = 5 * UnitsTools.A; - @Current int bCurrent = 5 * UnitsTools.A; - - // Force - @Force int aForce = 5 * UnitsTools.N; - @Force int bForce = 5 * UnitsTools.N; - - // Length - @Length int aLength = 5 * UnitsTools.m; - @Length int bLength = 5 * UnitsTools.mm; - - // Luminance - @Luminance int aLuminance = 5 * UnitsTools.cd; - @Luminance int bLuminance = 5 * UnitsTools.cd; - - // Mass - @Mass int aMass = 5 * UnitsTools.kg; - @Mass int bMass = 5 * UnitsTools.g; - - // Substance - @Substance int aSubstance = 5 * UnitsTools.mol; - @Substance int bSubstance = 5 * UnitsTools.mol; - - // Temperature - @Temperature int aTemperature = 5 * UnitsTools.K; - @Temperature int bTemperature = 5 * UnitsTools.K; - - // Time - @Time int aTime = 5 * UnitsTools.min; - @Time int bTime = 5 * UnitsTools.h; - - // Volume - @Volume int aVolume = 5 * UnitsTools.m3; - @Volume int bVolume = 5 * UnitsTools.km3; - - // Dimensions - // :: error: (assignment.type.incompatible) - @Acceleration int sAcceleration = aAcceleration - bMass; - - // Area - // :: error: (assignment.type.incompatible) - @Luminance int sLuminance = aArea - bArea; - - // Current - // :: error: (assignment.type.incompatible) - @Current int sCurrent = aMass - bCurrent; - - // Length - // :: error: (assignment.type.incompatible) - @Length int sLength = aLength - bSubstance; - - // Luminance - // :: error: (assignment.type.incompatible) - @Temperature int sTemperature = aLuminance - bLuminance; - - // Mass - // :: error: (assignment.type.incompatible) - @Mass int sMass = aTemperature - bMass; - - // Substance - // :: error: (assignment.type.incompatible) - @Substance int sSubstance = aSubstance - bCurrent; - - // Temperature - // :: error: (assignment.type.incompatible) - @Area int sArea = aTemperature - bTemperature; - - // Time - // :: error: (assignment.type.incompatible) - @Time int sTime = aArea - bTime; - - // Volume - // :: error: (assignment.type.incompatible) - @Volume int sVolume = aVolume - bArea; - - // Force - // :: error: (assignment.type.incompatible) - sMass = aForce - bForce; - - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - - // Cubic kilometer - @km3 int aCubicKilometer = 5 * UnitsTools.km3; - @km3 int bCubicKilometer = 5 * UnitsTools.km3; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - - // Cubic meter - @m3 int aCubicMeter = 5 * UnitsTools.m3; - @m3 int bCubicMeter = 5 * UnitsTools.m3; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - - // Cubic millimeter - @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - - // Metric Tons - @t int aMetricTon = 5 * UnitsTools.t; - @t int bMetricTon = 5 * UnitsTools.t; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - - // Units - // Amperes - // :: error: (assignment.type.incompatible) - @g int sGram = aAmpere - bAmpere; - - // Candela - // :: error: (assignment.type.incompatible) - @cd int sCandela = aTemperature - bCandela; - - // Celsius - // :: error: (assignment.type.incompatible) - @C int sCelsius = aCelsius - bMillimeter; - - // Gram - // :: error: (assignment.type.incompatible) - @kg int sKilogram = aGram - bGram; - - // Hour - // :: error: (assignment.type.incompatible) - @h int sHour = aSquareMeter - bHour; - - // Kelvin - // :: error: (assignment.type.incompatible) - @K int sKelvin = aKelvin - bSecond; - - // Kilogram - // :: error: (assignment.type.incompatible) - @kmPERh int sKilometerPerHour = aKilogram - bKilogram; - - // Kilometer - // :: error: (assignment.type.incompatible) - @km int sKilometer = aCandela - bKilometer; - - // Square kilometer - // :: error: (assignment.type.incompatible) - @km2 int sSquareKilometer = aSquareKilometer - bAmpere; - - // Cubic kilometer - // :: error: (assignment.type.incompatible) - @km3 int sCubicKilometer = aCubicKilometer - bAmpere; - - // Kilometer per hour - // :: error: (assignment.type.incompatible) - @mPERs int sMeterPerSecond = aKilometerPerHour - bKilometerPerHour; - - // Meter - // :: error: (assignment.type.incompatible) - @m int sMeter = aHour - bMeter; - - // Square meter - // :: error: (assignment.type.incompatible) - @m2 int sSquareMeter = aSquareMeter - bGram; - - // Cubic meter - // :: error: (assignment.type.incompatible) - @m3 int sCubicMeter = aCubicMeter - bGram; - - // Meter per second - // :: error: (assignment.type.incompatible) - @mm2 int sSquareMillimeter = aMeterPerSecond - bMeterPerSecond; - - // Meter per second square - // :: error: (assignment.type.incompatible) - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeter; - - // Minute - // :: error: (assignment.type.incompatible) - @min int sMinute = aMole - bMinute; - - // Millimeter - // :: error: (assignment.type.incompatible) - @mm int sMillimeter = aMillimeter - bHour; - - // Square millimeter - // :: error: (assignment.type.incompatible) - @A int sAmpere = aSquareMillimeter - bSquareMillimeter; - - // Mole - // :: error: (assignment.type.incompatible) - @mol int sMole = aCandela - bMole; - - // Second - // :: error: (assignment.type.incompatible) - @s int sSecond = aSecond - bSquareKilometer; - - // Newton - // :: error: (assignment.type.incompatible) - sKilogram = aNewton - bNewton; - - // Kilonewton - // :: error: (assignment.type.incompatible) - @kN int sKilonewton = aKilonewton - bNewton; - - // Metric Ton - // :: error: (assignment.type.incompatible) - @N int sNewton = aNewton - bMetricTon; - } + // Subtraction is legal when the operands have the same units. + void good() { + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + @A int sAmpere = aAmpere - bAmpere; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + @cd int sCandela = aCandela - bCandela; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + @C int sCelsius = aCelsius - bCelsius; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + @g int sGram = aGram - bGram; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + @h int sHour = aHour - bHour; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + @K int sKelvin = aKelvin - bKelvin; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + @kN int sKiloewton = aKilonewton - bKilonewton; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + @kg int sKilogram = aKilogram - bKilogram; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + @km int sKilometer = aKilometer - bKilometer; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + @km2 int sSquareKilometer = aSquareKilometer - bSquareKilometer; + + // Cubic kilometer + @km3 int aCubicKilometer = 5 * UnitsTools.km3; + @km3 int bCubicKilometer = 5 * UnitsTools.km3; + @km3 int sCubicKilometer = aCubicKilometer - bCubicKilometer; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int sKilometerPerHour = aKilometerPerHour - bKilometerPerHour; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + @m int sMeter = aMeter - bMeter; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + @m2 int sSquareMeter = aSquareMeter - bSquareMeter; + + // Cubic meter + @m3 int aCubicMeter = 5 * UnitsTools.m3; + @m3 int bCubicMeter = 5 * UnitsTools.m3; + @m3 int sCubicMeter = aCubicMeter - bCubicMeter; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int sMeterPerSecond = aMeterPerSecond - bMeterPerSecond; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeterPerSecondSquare; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + @min int sMinute = aMinute - bMinute; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + @mm int sMillimeter = aMillimeter - bMillimeter; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int sSquareMillimeter = aSquareMillimeter - bSquareMillimeter; + + // Cubic millimeter + @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int sCubicMillimeter = aCubicMillimeter - bCubicMillimeter; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + @mol int sMole = aMole - bMole; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + @N int sNewton = aNewton - bNewton; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + @s int sSecond = aSecond - bSecond; + } + + // Subtraction is illegal when the operands have different units or one is unqualified. In + // these tests, we cycle between the result and the first or second operand having an incorrect + // type. + void bad() { + // Dimensions + // Acceleration + @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; + @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; + + // Area + @Area int aArea = 5 * UnitsTools.km2; + @Area int bArea = 5 * UnitsTools.mm2; + + // Current + @Current int aCurrent = 5 * UnitsTools.A; + @Current int bCurrent = 5 * UnitsTools.A; + + // Force + @Force int aForce = 5 * UnitsTools.N; + @Force int bForce = 5 * UnitsTools.N; + + // Length + @Length int aLength = 5 * UnitsTools.m; + @Length int bLength = 5 * UnitsTools.mm; + + // Luminance + @Luminance int aLuminance = 5 * UnitsTools.cd; + @Luminance int bLuminance = 5 * UnitsTools.cd; + + // Mass + @Mass int aMass = 5 * UnitsTools.kg; + @Mass int bMass = 5 * UnitsTools.g; + + // Substance + @Substance int aSubstance = 5 * UnitsTools.mol; + @Substance int bSubstance = 5 * UnitsTools.mol; + + // Temperature + @Temperature int aTemperature = 5 * UnitsTools.K; + @Temperature int bTemperature = 5 * UnitsTools.K; + + // Time + @Time int aTime = 5 * UnitsTools.min; + @Time int bTime = 5 * UnitsTools.h; + + // Volume + @Volume int aVolume = 5 * UnitsTools.m3; + @Volume int bVolume = 5 * UnitsTools.km3; + + // Dimensions + // :: error: (assignment.type.incompatible) + @Acceleration int sAcceleration = aAcceleration - bMass; + + // Area + // :: error: (assignment.type.incompatible) + @Luminance int sLuminance = aArea - bArea; + + // Current + // :: error: (assignment.type.incompatible) + @Current int sCurrent = aMass - bCurrent; + + // Length + // :: error: (assignment.type.incompatible) + @Length int sLength = aLength - bSubstance; + + // Luminance + // :: error: (assignment.type.incompatible) + @Temperature int sTemperature = aLuminance - bLuminance; + + // Mass + // :: error: (assignment.type.incompatible) + @Mass int sMass = aTemperature - bMass; + + // Substance + // :: error: (assignment.type.incompatible) + @Substance int sSubstance = aSubstance - bCurrent; + + // Temperature + // :: error: (assignment.type.incompatible) + @Area int sArea = aTemperature - bTemperature; + + // Time + // :: error: (assignment.type.incompatible) + @Time int sTime = aArea - bTime; + + // Volume + // :: error: (assignment.type.incompatible) + @Volume int sVolume = aVolume - bArea; + + // Force + // :: error: (assignment.type.incompatible) + sMass = aForce - bForce; + + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + + // Cubic kilometer + @km3 int aCubicKilometer = 5 * UnitsTools.km3; + @km3 int bCubicKilometer = 5 * UnitsTools.km3; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + + // Cubic meter + @m3 int aCubicMeter = 5 * UnitsTools.m3; + @m3 int bCubicMeter = 5 * UnitsTools.m3; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + + // Cubic millimeter + @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + + // Metric Tons + @t int aMetricTon = 5 * UnitsTools.t; + @t int bMetricTon = 5 * UnitsTools.t; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + + // Units + // Amperes + // :: error: (assignment.type.incompatible) + @g int sGram = aAmpere - bAmpere; + + // Candela + // :: error: (assignment.type.incompatible) + @cd int sCandela = aTemperature - bCandela; + + // Celsius + // :: error: (assignment.type.incompatible) + @C int sCelsius = aCelsius - bMillimeter; + + // Gram + // :: error: (assignment.type.incompatible) + @kg int sKilogram = aGram - bGram; + + // Hour + // :: error: (assignment.type.incompatible) + @h int sHour = aSquareMeter - bHour; + + // Kelvin + // :: error: (assignment.type.incompatible) + @K int sKelvin = aKelvin - bSecond; + + // Kilogram + // :: error: (assignment.type.incompatible) + @kmPERh int sKilometerPerHour = aKilogram - bKilogram; + + // Kilometer + // :: error: (assignment.type.incompatible) + @km int sKilometer = aCandela - bKilometer; + + // Square kilometer + // :: error: (assignment.type.incompatible) + @km2 int sSquareKilometer = aSquareKilometer - bAmpere; + + // Cubic kilometer + // :: error: (assignment.type.incompatible) + @km3 int sCubicKilometer = aCubicKilometer - bAmpere; + + // Kilometer per hour + // :: error: (assignment.type.incompatible) + @mPERs int sMeterPerSecond = aKilometerPerHour - bKilometerPerHour; + + // Meter + // :: error: (assignment.type.incompatible) + @m int sMeter = aHour - bMeter; + + // Square meter + // :: error: (assignment.type.incompatible) + @m2 int sSquareMeter = aSquareMeter - bGram; + + // Cubic meter + // :: error: (assignment.type.incompatible) + @m3 int sCubicMeter = aCubicMeter - bGram; + + // Meter per second + // :: error: (assignment.type.incompatible) + @mm2 int sSquareMillimeter = aMeterPerSecond - bMeterPerSecond; + + // Meter per second square + // :: error: (assignment.type.incompatible) + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeter; + + // Minute + // :: error: (assignment.type.incompatible) + @min int sMinute = aMole - bMinute; + + // Millimeter + // :: error: (assignment.type.incompatible) + @mm int sMillimeter = aMillimeter - bHour; + + // Square millimeter + // :: error: (assignment.type.incompatible) + @A int sAmpere = aSquareMillimeter - bSquareMillimeter; + + // Mole + // :: error: (assignment.type.incompatible) + @mol int sMole = aCandela - bMole; + + // Second + // :: error: (assignment.type.incompatible) + @s int sSecond = aSecond - bSquareKilometer; + + // Newton + // :: error: (assignment.type.incompatible) + sKilogram = aNewton - bNewton; + + // Kilonewton + // :: error: (assignment.type.incompatible) + @kN int sKilonewton = aKilonewton - bNewton; + + // Metric Ton + // :: error: (assignment.type.incompatible) + @N int sNewton = aNewton - bMetricTon; + } } diff --git a/checker/tests/units/TypeVarsArrays.java b/checker/tests/units/TypeVarsArrays.java index b12054f2803..629d2eb50c6 100644 --- a/checker/tests/units/TypeVarsArrays.java +++ b/checker/tests/units/TypeVarsArrays.java @@ -1,8 +1,8 @@ public class TypeVarsArrays { - private T[] array; + private T[] array; - public void triggerBug(int index, T val) { - array[index] = val; - array[index] = null; - } + public void triggerBug(int index, T val) { + array[index] = val; + array[index] = null; + } } diff --git a/checker/tests/units/Units.java b/checker/tests/units/Units.java index 3244e266138..712c3159188 100644 --- a/checker/tests/units/Units.java +++ b/checker/tests/units/Units.java @@ -5,12 +5,12 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Units { - @m int m1 = 5 * UnitsTools.m; + @m int m1 = 5 * UnitsTools.m; - // The advantage of using the multiplication with a unit is that also double, float, etc. are - // easily handled and we don't need to end a huge number of methods to UnitsTools. - @m double dm = 9.34d * UnitsTools.m; + // The advantage of using the multiplication with a unit is that also double, float, etc. are + // easily handled and we don't need to end a huge number of methods to UnitsTools. + @m double dm = 9.34d * UnitsTools.m; - // With a static import: - @s float time = 5.32f * s; + // With a static import: + @s float time = 5.32f * s; } diff --git a/checker/tests/units/UnqualTest.java b/checker/tests/units/UnqualTest.java index 3e7b0704ecd..c300a014ef4 100644 --- a/checker/tests/units/UnqualTest.java +++ b/checker/tests/units/UnqualTest.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.units.qual.kg; public class UnqualTest { - // :: error: (assignment.type.incompatible) - @kg int kg = 5; - int nonkg = kg; - // :: error: (assignment.type.incompatible) - @kg int alsokg = nonkg; + // :: error: (assignment.type.incompatible) + @kg int kg = 5; + int nonkg = kg; + // :: error: (assignment.type.incompatible) + @kg int alsokg = nonkg; } diff --git a/checker/tests/value-index-interaction/MethodOverrides.java b/checker/tests/value-index-interaction/MethodOverrides.java index c3419416921..80f834592ae 100644 --- a/checker/tests/value-index-interaction/MethodOverrides.java +++ b/checker/tests/value-index-interaction/MethodOverrides.java @@ -7,13 +7,13 @@ import org.checkerframework.checker.index.qual.GTENegativeOne; public class MethodOverrides { - @GTENegativeOne int read() { - return -1; - } + @GTENegativeOne int read() { + return -1; + } } class MethodOverrides2 extends MethodOverrides { - int read() { - return -1; - } + int read() { + return -1; + } } diff --git a/checker/tests/value-index-interaction/MethodOverrides3.java b/checker/tests/value-index-interaction/MethodOverrides3.java index f0a8ecfd5bb..5cc8792f192 100644 --- a/checker/tests/value-index-interaction/MethodOverrides3.java +++ b/checker/tests/value-index-interaction/MethodOverrides3.java @@ -1,18 +1,17 @@ // This class should not issues any errors, since these annotations are identical to the ones // on java.io.PrintWriter in the Index JDK. -import org.checkerframework.checker.index.qual.IndexFor; -import org.checkerframework.checker.index.qual.IndexOrHigh; - import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; +import org.checkerframework.checker.index.qual.IndexFor; +import org.checkerframework.checker.index.qual.IndexOrHigh; public class MethodOverrides3 extends PrintWriter { - public MethodOverrides3(File file) throws FileNotFoundException { - super(file); - } + public MethodOverrides3(File file) throws FileNotFoundException { + super(file); + } - @Override - public void write(char[] buf, @IndexFor("#1") int off, @IndexOrHigh("#1") int len) {} + @Override + public void write(char[] buf, @IndexFor("#1") int off, @IndexOrHigh("#1") int len) {} } diff --git a/checker/tests/value-index-interaction/MinLenFromPositive.java b/checker/tests/value-index-interaction/MinLenFromPositive.java index f14a9d556eb..6eb4beecb11 100644 --- a/checker/tests/value-index-interaction/MinLenFromPositive.java +++ b/checker/tests/value-index-interaction/MinLenFromPositive.java @@ -5,52 +5,52 @@ public class MinLenFromPositive { - public @Positive int x = 0; - - void testField() { - this.x = -1; - @IntRange(from = 1) int f = this.x; - int @MinLen(1) [] y = new int[x]; - } - - void testArray(@Positive int @ArrayLen(1) [] x) { - int @MinLen(1) [] array = new int[x[0]]; - } - - void useTestArray(int @ArrayLen(1) [] x, int[] y) { - testArray(x); - // :: error: (argument.type.incompatible) - testArray(y); - } - - void test(@Positive int x) { - @IntRange(from = 1) int z = x; - @Positive int q = x; - @Positive int a = -1; - int @MinLen(1) [] array = new int[a]; - } - - // Ensure that just running the value checker doesn't result in an LHS warning. - void foo2(int x) { - test(x); - } - - @Positive int id(@Positive int x) { - return -1; - } - - @Positive int plus(@Positive int x, @Positive int y) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int z = x + y; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int q = x + y; - - return x + y; - } - - // Ensure that LHS warnings aren't issued even for arrays of Positives - @Positive int[] array_test() { - int[] a = {-1, 2, 3}; - return a; - } + public @Positive int x = 0; + + void testField() { + this.x = -1; + @IntRange(from = 1) int f = this.x; + int @MinLen(1) [] y = new int[x]; + } + + void testArray(@Positive int @ArrayLen(1) [] x) { + int @MinLen(1) [] array = new int[x[0]]; + } + + void useTestArray(int @ArrayLen(1) [] x, int[] y) { + testArray(x); + // :: error: (argument.type.incompatible) + testArray(y); + } + + void test(@Positive int x) { + @IntRange(from = 1) int z = x; + @Positive int q = x; + @Positive int a = -1; + int @MinLen(1) [] array = new int[a]; + } + + // Ensure that just running the value checker doesn't result in an LHS warning. + void foo2(int x) { + test(x); + } + + @Positive int id(@Positive int x) { + return -1; + } + + @Positive int plus(@Positive int x, @Positive int y) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int z = x + y; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int q = x + y; + + return x + y; + } + + // Ensure that LHS warnings aren't issued even for arrays of Positives + @Positive int[] array_test() { + int[] a = {-1, 2, 3}; + return a; + } } diff --git a/checker/tests/value-index-interaction/OverrideIntVal.java b/checker/tests/value-index-interaction/OverrideIntVal.java index 8baffc1b5d2..8e3e37ac9ad 100644 --- a/checker/tests/value-index-interaction/OverrideIntVal.java +++ b/checker/tests/value-index-interaction/OverrideIntVal.java @@ -4,61 +4,61 @@ public class OverrideIntVal { - @NonNegative int foo(@IntVal(0) int zero) { - return zero; - } + @NonNegative int foo(@IntVal(0) int zero) { + return zero; + } - @NonNegative int bar(@BottomVal int bottom) { - return bottom; - } + @NonNegative int bar(@BottomVal int bottom) { + return bottom; + } - @NonNegative int m() { - return 0; - } + @NonNegative int m() { + return 0; + } - @IntVal({0, 1, 2, 3}) int m2() { - return 0; - } + @IntVal({0, 1, 2, 3}) int m2() { + return 0; + } - @GTENegativeOne int n() { - return -1; - } + @GTENegativeOne int n() { + return -1; + } - @Positive int p() { - return 1; - } + @Positive int p() { + return 1; + } } class OverrideIntValSub extends OverrideIntVal { - @Override - @IntVal(0) int m() { - return 0; - } + @Override + @IntVal(0) int m() { + return 0; + } - @Override - @IntVal(0) int m2() { - return 0; - } + @Override + @IntVal(0) int m2() { + return 0; + } - @Override - @IntVal(0) int n() { - return 0; - } + @Override + @IntVal(0) int n() { + return 0; + } - @Override - @IntVal(2) int p() { - return 2; - } + @Override + @IntVal(2) int p() { + return 2; + } } class OverrideIntValBottom extends OverrideIntVal { - @Override - @BottomVal int m() { - throw new Error("never returns normally"); - } + @Override + @BottomVal int m() { + throw new Error("never returns normally"); + } - @Override - @BottomVal int m2() { - throw new Error("never returns normally"); - } + @Override + @BottomVal int m2() { + throw new Error("never returns normally"); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java index 6dec6dc02e6..0d9a181c5f0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java @@ -3,7 +3,15 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; - +import java.util.Comparator; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; @@ -21,17 +29,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; -import java.util.Comparator; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.PriorityQueue; -import java.util.Set; - -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - /** * Implementation of common features for {@link BackwardAnalysisImpl} and {@link * ForwardAnalysisImpl}. @@ -41,534 +38,529 @@ * @param the transfer function type that is used to approximated runtime behavior */ public abstract class AbstractAnalysis< - V extends AbstractValue, S extends Store, T extends TransferFunction> - implements Analysis { - - /** The direction of this analysis. */ - protected final Direction direction; - - /** Is the analysis currently running? */ - protected boolean isRunning = false; - - /** The transfer function for regular nodes. */ - // TODO: make final. Currently, the transferFunction has a reference to the analysis, so it - // can't be created until the Analysis is initialized. - protected @MonotonicNonNull T transferFunction; - - /** The current control flow graph to perform the analysis on. */ - protected @MonotonicNonNull ControlFlowGraph cfg; - - /** - * The transfer inputs of every basic block (assumed to be 'no information' if not present, - * inputs before blocks in forward analysis, after blocks in backward analysis). - */ - protected final IdentityHashMap> inputs = new IdentityHashMap<>(); - - /** The worklist used for the fix-point iteration. */ - protected final Worklist worklist; - - /** Abstract values of nodes. */ - protected final IdentityHashMap nodeValues = new IdentityHashMap<>(); - - /** Map from (effectively final) local variable elements to their abstract value. */ - protected final HashMap finalLocalValues = new HashMap<>(); - - /** - * The node that is currently handled in the analysis (if it is running). The following - * invariant holds: - * - *
-     *   !isRunning ⇒ (currentNode == null)
-     * 
- */ - // currentNode == null when isRunning is true. - // See https://github.com/typetools/checker-framework/issues/4115 - protected @InternedDistinct @Nullable Node currentNode; - - /** - * The tree that is currently being looked at. The transfer function can set this tree to make - * sure that calls to {@code getValue} will not return information for this given tree. - */ - protected @InternedDistinct @Nullable Tree currentTree; - - /** The current transfer input when the analysis is running. */ - protected @Nullable TransferInput currentInput; - - /** - * Returns the tree that is currently being looked at. The transfer function can set this tree - * to make sure that calls to {@code getValue} will not return information for this given tree. - * - * @return the tree that is currently being looked at - */ - public @Nullable Tree getCurrentTree() { - return currentTree; + V extends AbstractValue, S extends Store, T extends TransferFunction> + implements Analysis { + + /** The direction of this analysis. */ + protected final Direction direction; + + /** Is the analysis currently running? */ + protected boolean isRunning = false; + + /** The transfer function for regular nodes. */ + // TODO: make final. Currently, the transferFunction has a reference to the analysis, so it + // can't be created until the Analysis is initialized. + protected @MonotonicNonNull T transferFunction; + + /** The current control flow graph to perform the analysis on. */ + protected @MonotonicNonNull ControlFlowGraph cfg; + + /** + * The transfer inputs of every basic block (assumed to be 'no information' if not present, inputs + * before blocks in forward analysis, after blocks in backward analysis). + */ + protected final IdentityHashMap> inputs = new IdentityHashMap<>(); + + /** The worklist used for the fix-point iteration. */ + protected final Worklist worklist; + + /** Abstract values of nodes. */ + protected final IdentityHashMap nodeValues = new IdentityHashMap<>(); + + /** Map from (effectively final) local variable elements to their abstract value. */ + protected final HashMap finalLocalValues = new HashMap<>(); + + /** + * The node that is currently handled in the analysis (if it is running). The following invariant + * holds: + * + *
+   *   !isRunning ⇒ (currentNode == null)
+   * 
+ */ + // currentNode == null when isRunning is true. + // See https://github.com/typetools/checker-framework/issues/4115 + protected @InternedDistinct @Nullable Node currentNode; + + /** + * The tree that is currently being looked at. The transfer function can set this tree to make + * sure that calls to {@code getValue} will not return information for this given tree. + */ + protected @InternedDistinct @Nullable Tree currentTree; + + /** The current transfer input when the analysis is running. */ + protected @Nullable TransferInput currentInput; + + /** + * Returns the tree that is currently being looked at. The transfer function can set this tree to + * make sure that calls to {@code getValue} will not return information for this given tree. + * + * @return the tree that is currently being looked at + */ + public @Nullable Tree getCurrentTree() { + return currentTree; + } + + /** + * Set the tree that is currently being looked at. + * + * @param currentTree the tree that should be currently looked at + */ + public void setCurrentTree(@FindDistinct Tree currentTree) { + this.currentTree = currentTree; + } + + /** + * Set the node that is currently being looked at. + * + * @param currentNode the node that should be currently looked at + */ + protected void setCurrentNode(@FindDistinct @Nullable Node currentNode) { + this.currentNode = currentNode; + } + + /** + * Implementation of common features for {@link BackwardAnalysisImpl} and {@link + * ForwardAnalysisImpl}. + * + * @param direction direction of the analysis + */ + protected AbstractAnalysis(Direction direction) { + this.direction = direction; + this.worklist = new Worklist(this.direction); + } + + /** Initialize the transfer inputs of every basic block before performing the analysis. */ + @RequiresNonNull("cfg") + protected abstract void initInitialInputs(); + + /** + * Propagate the stores in {@code currentInput} to the next block in the direction of analysis, + * according to the {@code flowRule}. + * + * @param nextBlock the target block to propagate the stores to + * @param node the node of the target block + * @param currentInput the current transfer input + * @param flowRule the flow rule being used + * @param addToWorklistAgain whether the block should be added to {@link #worklist} again + */ + protected abstract void propagateStoresTo( + Block nextBlock, + Node node, + TransferInput currentInput, + Store.FlowRule flowRule, + boolean addToWorklistAgain); + + @Override + public boolean isRunning() { + return isRunning; + } + + @Override + public Direction getDirection() { + return this.direction; + } + + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public AnalysisResult getResult() { + if (isRunning) { + throw new BugInCF( + "AbstractAnalysis::getResult() shouldn't be called when the analysis is" + " running."); } - - /** - * Set the tree that is currently being looked at. - * - * @param currentTree the tree that should be currently looked at - */ - public void setCurrentTree(@FindDistinct Tree currentTree) { - this.currentTree = currentTree; + return new AnalysisResult<>( + nodeValues, inputs, cfg.getTreeLookup(), cfg.getPostfixNodeLookup(), finalLocalValues); + } + + @Override + public @Nullable T getTransferFunction() { + return transferFunction; + } + + @Override + public @Nullable V getValue(Node n) { + if (isRunning) { + // we don't have a org.checkerframework.dataflow fact about the current node yet + if (currentNode == null + || currentNode == n + || (currentTree != null && currentTree == n.getTree())) { + return null; + } + // check that 'n' is a subnode of 'currentNode'. Check immediate operands + // first for efficiency. + assert !n.isLValue() : "Did not expect an lvalue, but got " + n; + if (!currentNode.getOperands().contains(n) + && !currentNode.getTransitiveOperands().contains(n)) { + return null; + } + // fall through when the current node is not 'n', and 'n' is not a subnode. } - - /** - * Set the node that is currently being looked at. - * - * @param currentNode the node that should be currently looked at - */ - protected void setCurrentNode(@FindDistinct @Nullable Node currentNode) { - this.currentNode = currentNode; + return nodeValues.get(n); + } + + /** + * Returns all current node values. + * + * @return {@link #nodeValues} + */ + public IdentityHashMap getNodeValues() { + return nodeValues; + } + + /** + * Set all current node values to the given map. + * + * @param in the current node values + */ + /*package-private*/ void setNodeValues(IdentityHashMap in) { + assert !isRunning; + nodeValues.clear(); + nodeValues.putAll(in); + } + + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public @Nullable S getRegularExitStore() { + SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); + if (inputs.containsKey(regularExitBlock)) { + return inputs.get(regularExitBlock).getRegularStore(); + } else { + return null; } - - /** - * Implementation of common features for {@link BackwardAnalysisImpl} and {@link - * ForwardAnalysisImpl}. - * - * @param direction direction of the analysis - */ - protected AbstractAnalysis(Direction direction) { - this.direction = direction; - this.worklist = new Worklist(this.direction); + } + + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public @Nullable S getExceptionalExitStore() { + SpecialBlock exceptionalExitBlock = cfg.getExceptionalExitBlock(); + if (inputs.containsKey(exceptionalExitBlock)) { + S exceptionalExitStore = inputs.get(exceptionalExitBlock).getRegularStore(); + return exceptionalExitStore; + } else { + return null; } - - /** Initialize the transfer inputs of every basic block before performing the analysis. */ - @RequiresNonNull("cfg") - protected abstract void initInitialInputs(); - - /** - * Propagate the stores in {@code currentInput} to the next block in the direction of analysis, - * according to the {@code flowRule}. - * - * @param nextBlock the target block to propagate the stores to - * @param node the node of the target block - * @param currentInput the current transfer input - * @param flowRule the flow rule being used - * @param addToWorklistAgain whether the block should be added to {@link #worklist} again - */ - protected abstract void propagateStoresTo( - Block nextBlock, - Node node, - TransferInput currentInput, - Store.FlowRule flowRule, - boolean addToWorklistAgain); - - @Override - public boolean isRunning() { - return isRunning; + } + + /** + * Get the set of {@link Node}s for a given {@link Tree}. Returns null for trees that don't + * produce a value. + * + * @param t the given tree + * @return the set of corresponding nodes to the given tree + */ + public @Nullable Set getNodesForTree(Tree t) { + if (cfg == null) { + return null; } - - @Override - public Direction getDirection() { - return this.direction; + return cfg.getNodesCorrespondingToTree(t); + } + + @Override + public @Nullable V getValue(Tree t) { + // Dataflow is analyzing the tree, so no value is available. + if (t == currentTree || cfg == null) { + return null; } - - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public AnalysisResult getResult() { - if (isRunning) { - throw new BugInCF( - "AbstractAnalysis::getResult() shouldn't be called when the analysis is" - + " running."); - } - return new AnalysisResult<>( - nodeValues, - inputs, - cfg.getTreeLookup(), - cfg.getPostfixNodeLookup(), - finalLocalValues); + V result = getValue(getNodesForTree(t)); + if (result == null) { + result = getValue(cfg.getTreeLookup().get(t)); } - - @Override - public @Nullable T getTransferFunction() { - return transferFunction; + return result; + } + + /** + * Returns the least upper bound of the values of {@code nodes}. + * + * @param nodes a set of nodes + * @return the least upper bound of the values of {@code nodes} + */ + private @Nullable V getValue(@Nullable Set nodes) { + if (nodes == null) { + return null; } - @Override - public @Nullable V getValue(Node n) { - if (isRunning) { - // we don't have a org.checkerframework.dataflow fact about the current node yet - if (currentNode == null - || currentNode == n - || (currentTree != null && currentTree == n.getTree())) { - return null; - } - // check that 'n' is a subnode of 'currentNode'. Check immediate operands - // first for efficiency. - assert !n.isLValue() : "Did not expect an lvalue, but got " + n; - if (!currentNode.getOperands().contains(n) - && !currentNode.getTransitiveOperands().contains(n)) { - return null; - } - // fall through when the current node is not 'n', and 'n' is not a subnode. - } - return nodeValues.get(n); + V merged = null; + for (Node aNode : nodes) { + if (aNode.isLValue()) { + return null; + } + V v = getValue(aNode); + if (merged == null) { + merged = v; + } else if (v != null) { + merged = merged.leastUpperBound(v); + } } - /** - * Returns all current node values. - * - * @return {@link #nodeValues} - */ - public IdentityHashMap getNodeValues() { - return nodeValues; + return merged; + } + + /** + * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps to a {@link + * Node} in the CFG or {@code null} otherwise. + * + * @param t the given tree + * @return the contained method tree of the given tree + */ + public @Nullable MethodTree getContainingMethod(Tree t) { + if (cfg == null) { + return null; } - - /** - * Set all current node values to the given map. - * - * @param in the current node values - */ - /*package-private*/ void setNodeValues(IdentityHashMap in) { - assert !isRunning; - nodeValues.clear(); - nodeValues.putAll(in); + return cfg.getContainingMethod(t); + } + + /** + * Get the {@link ClassTree} of the current CFG if the argument {@link Tree} maps to a {@link + * Node} in the CFG or {@code null} otherwise. + * + * @param t the given tree + * @return the contained class tree of the given tree + */ + public @Nullable ClassTree getContainingClass(Tree t) { + if (cfg == null) { + return null; } - - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public @Nullable S getRegularExitStore() { - SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); - if (inputs.containsKey(regularExitBlock)) { - return inputs.get(regularExitBlock).getRegularStore(); - } else { - return null; - } + return cfg.getContainingClass(t); + } + + /** + * Call the transfer function for node {@code node}, and set that node as current node first. This + * method requires a {@code transferInput} that the method can modify. + * + * @param node the given node + * @param transferInput the transfer input + * @return the output of the transfer function + */ + protected TransferResult callTransferFunction( + Node node, TransferInput transferInput) { + assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; + if (node.isLValue()) { + // TODO: should the default behavior return a regular transfer result, a conditional + // transfer result (depending on store.containsTwoStores()), or is the following + // correct? + return new RegularTransferResult<>(null, transferInput.getRegularStore()); } - - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public @Nullable S getExceptionalExitStore() { - SpecialBlock exceptionalExitBlock = cfg.getExceptionalExitBlock(); - if (inputs.containsKey(exceptionalExitBlock)) { - S exceptionalExitStore = inputs.get(exceptionalExitBlock).getRegularStore(); - return exceptionalExitStore; - } else { - return null; + transferInput.node = node; + setCurrentNode(node); + TransferResult transferResult = node.accept(transferFunction, transferInput); + setCurrentNode(null); + if (node instanceof AssignmentNode) { + // store the flow-refined value effectively for final local variables + AssignmentNode assignment = (AssignmentNode) node; + Node lhst = assignment.getTarget(); + if (lhst instanceof LocalVariableNode) { + LocalVariableNode lhs = (LocalVariableNode) lhst; + VariableElement elem = lhs.getElement(); + if (ElementUtils.isEffectivelyFinal(elem)) { + V resval = transferResult.getResultValue(); + if (resval != null) { + finalLocalValues.put(elem, resval); + } } + } } - - /** - * Get the set of {@link Node}s for a given {@link Tree}. Returns null for trees that don't - * produce a value. - * - * @param t the given tree - * @return the set of corresponding nodes to the given tree - */ - public @Nullable Set getNodesForTree(Tree t) { - if (cfg == null) { - return null; - } - return cfg.getNodesCorrespondingToTree(t); + return transferResult; + } + + /** + * Initialize the analysis with a new control flow graph. + * + * @param cfg the control flow graph to use + */ + protected final void init(ControlFlowGraph cfg) { + initFields(cfg); + initInitialInputs(); + } + + /** + * Should exceptional control flow for a particular exception type be ignored? + * + *

The default implementation always returns {@code false}. Subclasses should override the + * method to implement a different policy. + * + * @param exceptionType the exception type + * @return {@code true} if exceptional control flow due to {@code exceptionType} should be + * ignored, {@code false} otherwise + */ + protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { + return false; + } + + /** + * Initialize fields of this object based on a given control flow graph. Sub-class may override + * this method to initialize customized fields. + * + * @param cfg a given control flow graph + */ + @EnsuresNonNull("this.cfg") + protected void initFields(ControlFlowGraph cfg) { + inputs.clear(); + nodeValues.clear(); + finalLocalValues.clear(); + this.cfg = cfg; + } + + /** + * Updates the value of node {@code node} to the value of the {@code transferResult}. Returns true + * if the node's value changed, or a store was updated. + * + * @param node the node to update + * @param transferResult the transfer result being updated + * @return true if the node's value changed, or a store was updated + */ + protected boolean updateNodeValues(Node node, TransferResult transferResult) { + V newVal = transferResult.getResultValue(); + boolean nodeValueChanged = false; + if (newVal != null) { + V oldVal = nodeValues.get(node); + nodeValues.put(node, newVal); + nodeValueChanged = !Objects.equals(oldVal, newVal); } - - @Override - public @Nullable V getValue(Tree t) { - // Dataflow is analyzing the tree, so no value is available. - if (t == currentTree || cfg == null) { - return null; - } - V result = getValue(getNodesForTree(t)); - if (result == null) { - result = getValue(cfg.getTreeLookup().get(t)); - } - return result; + return nodeValueChanged || transferResult.storeChanged(); + } + + /** + * Read the store for a particular basic block from a map of stores (or {@code null} if none + * exists yet). + * + * @param stores a map of stores + * @param b the target block + * @param method return type should be a subtype of {@link Store} + * @return the store for the target block + */ + protected static @Nullable S readFromStore(Map stores, Block b) { + return stores.get(b); + } + + /** + * Add a basic block to {@link #worklist}. If {@code b} is already present, the method does + * nothing. + * + * @param b the block to add to {@link #worklist} + */ + protected void addToWorklist(Block b) { + // TODO: use a more efficient way to check if b is already present + if (!worklist.contains(b)) { + worklist.add(b); } + } - /** - * Returns the least upper bound of the values of {@code nodes}. - * - * @param nodes a set of nodes - * @return the least upper bound of the values of {@code nodes} - */ - private @Nullable V getValue(@Nullable Set nodes) { - if (nodes == null) { - return null; - } - - V merged = null; - for (Node aNode : nodes) { - if (aNode.isLValue()) { - return null; - } - V v = getValue(aNode); - if (merged == null) { - merged = v; - } else if (v != null) { - merged = merged.leastUpperBound(v); - } - } + /** + * A worklist is a priority queue of blocks in which the order is given by depth-first ordering to + * place non-loop predecessors ahead of successors. + */ + protected static class Worklist { - return merged; - } + /** Map all blocks in the CFG to their depth-first order. */ + protected final IdentityHashMap depthFirstOrder = new IdentityHashMap<>(); /** - * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps to a {@link - * Node} in the CFG or {@code null} otherwise. - * - * @param t the given tree - * @return the contained method tree of the given tree + * Comparators to allow priority queue to order blocks by their depth-first order, using by + * forward analysis. */ - public @Nullable MethodTree getContainingMethod(Tree t) { - if (cfg == null) { - return null; - } - return cfg.getContainingMethod(t); + public class ForwardDFOComparator implements Comparator { + @SuppressWarnings("nullness:unboxing.of.nullable") + @Override + public int compare(Block b1, Block b2) { + return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); + } } /** - * Get the {@link ClassTree} of the current CFG if the argument {@link Tree} maps to a {@link - * Node} in the CFG or {@code null} otherwise. - * - * @param t the given tree - * @return the contained class tree of the given tree + * Comparators to allow priority queue to order blocks by their depth-first order, using by + * backward analysis. */ - public @Nullable ClassTree getContainingClass(Tree t) { - if (cfg == null) { - return null; - } - return cfg.getContainingClass(t); + public class BackwardDFOComparator implements Comparator { + @SuppressWarnings("nullness:unboxing.of.nullable") + @Override + public int compare(Block b1, Block b2) { + return depthFirstOrder.get(b2) - depthFirstOrder.get(b1); + } } - /** - * Call the transfer function for node {@code node}, and set that node as current node first. - * This method requires a {@code transferInput} that the method can modify. - * - * @param node the given node - * @param transferInput the transfer input - * @return the output of the transfer function - */ - protected TransferResult callTransferFunction( - Node node, TransferInput transferInput) { - assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; - if (node.isLValue()) { - // TODO: should the default behavior return a regular transfer result, a conditional - // transfer result (depending on store.containsTwoStores()), or is the following - // correct? - return new RegularTransferResult<>(null, transferInput.getRegularStore()); - } - transferInput.node = node; - setCurrentNode(node); - TransferResult transferResult = node.accept(transferFunction, transferInput); - setCurrentNode(null); - if (node instanceof AssignmentNode) { - // store the flow-refined value effectively for final local variables - AssignmentNode assignment = (AssignmentNode) node; - Node lhst = assignment.getTarget(); - if (lhst instanceof LocalVariableNode) { - LocalVariableNode lhs = (LocalVariableNode) lhst; - VariableElement elem = lhs.getElement(); - if (ElementUtils.isEffectivelyFinal(elem)) { - V resval = transferResult.getResultValue(); - if (resval != null) { - finalLocalValues.put(elem, resval); - } - } - } - } - return transferResult; - } + /** The backing priority queue. */ + protected final PriorityQueue queue; /** - * Initialize the analysis with a new control flow graph. + * Create a Worklist. * - * @param cfg the control flow graph to use + * @param direction the direction (forward or backward) */ - protected final void init(ControlFlowGraph cfg) { - initFields(cfg); - initInitialInputs(); + public Worklist(Direction direction) { + if (direction == Direction.FORWARD) { + queue = new PriorityQueue<>(new ForwardDFOComparator()); + } else if (direction == Direction.BACKWARD) { + queue = new PriorityQueue<>(new BackwardDFOComparator()); + } else { + throw new BugInCF("Unexpected Direction meet: " + direction.name()); + } } /** - * Should exceptional control flow for a particular exception type be ignored? - * - *

The default implementation always returns {@code false}. Subclasses should override the - * method to implement a different policy. + * Process the control flow graph, add the blocks to {@link #depthFirstOrder}. * - * @param exceptionType the exception type - * @return {@code true} if exceptional control flow due to {@code exceptionType} should be - * ignored, {@code false} otherwise + * @param cfg the control flow graph to process */ - protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { - return false; + public void process(ControlFlowGraph cfg) { + depthFirstOrder.clear(); + int count = 1; + for (Block b : cfg.getDepthFirstOrderedBlocks()) { + depthFirstOrder.put(b, count++); + } + + queue.clear(); } /** - * Initialize fields of this object based on a given control flow graph. Sub-class may override - * this method to initialize customized fields. + * See {@link PriorityQueue#isEmpty}. * - * @param cfg a given control flow graph + * @see PriorityQueue#isEmpty + * @return true if {@link #queue} is empty else false */ - @EnsuresNonNull("this.cfg") - protected void initFields(ControlFlowGraph cfg) { - inputs.clear(); - nodeValues.clear(); - finalLocalValues.clear(); - this.cfg = cfg; + @Pure + @EnsuresNonNullIf(result = false, expression = "poll()") + @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") // forwarded + public boolean isEmpty() { + return queue.isEmpty(); } /** - * Updates the value of node {@code node} to the value of the {@code transferResult}. Returns - * true if the node's value changed, or a store was updated. + * Check if {@link #queue} contains the block which is passed as the argument. * - * @param node the node to update - * @param transferResult the transfer result being updated - * @return true if the node's value changed, or a store was updated + * @param block the given block to check + * @return true if {@link #queue} contains the given block */ - protected boolean updateNodeValues(Node node, TransferResult transferResult) { - V newVal = transferResult.getResultValue(); - boolean nodeValueChanged = false; - if (newVal != null) { - V oldVal = nodeValues.get(node); - nodeValues.put(node, newVal); - nodeValueChanged = !Objects.equals(oldVal, newVal); - } - return nodeValueChanged || transferResult.storeChanged(); + public boolean contains(Block block) { + return queue.contains(block); } /** - * Read the store for a particular basic block from a map of stores (or {@code null} if none - * exists yet). + * Add the given block to {@link #queue}. Adds unconditionally: does not check containment + * first. * - * @param stores a map of stores - * @param b the target block - * @param method return type should be a subtype of {@link Store} - * @return the store for the target block + * @param block the block to add to {@link #queue} */ - protected static @Nullable S readFromStore(Map stores, Block b) { - return stores.get(b); + public void add(Block block) { + queue.add(block); } /** - * Add a basic block to {@link #worklist}. If {@code b} is already present, the method does - * nothing. + * See {@link PriorityQueue#poll}. * - * @param b the block to add to {@link #worklist} + * @see PriorityQueue#poll + * @return the head of {@link #queue} */ - protected void addToWorklist(Block b) { - // TODO: use a more efficient way to check if b is already present - if (!worklist.contains(b)) { - worklist.add(b); - } + @Pure + public @Nullable Block poll() { + return queue.poll(); } - /** - * A worklist is a priority queue of blocks in which the order is given by depth-first ordering - * to place non-loop predecessors ahead of successors. - */ - protected static class Worklist { - - /** Map all blocks in the CFG to their depth-first order. */ - protected final IdentityHashMap depthFirstOrder = new IdentityHashMap<>(); - - /** - * Comparators to allow priority queue to order blocks by their depth-first order, using by - * forward analysis. - */ - public class ForwardDFOComparator implements Comparator { - @SuppressWarnings("nullness:unboxing.of.nullable") - @Override - public int compare(Block b1, Block b2) { - return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); - } - } - - /** - * Comparators to allow priority queue to order blocks by their depth-first order, using by - * backward analysis. - */ - public class BackwardDFOComparator implements Comparator { - @SuppressWarnings("nullness:unboxing.of.nullable") - @Override - public int compare(Block b1, Block b2) { - return depthFirstOrder.get(b2) - depthFirstOrder.get(b1); - } - } - - /** The backing priority queue. */ - protected final PriorityQueue queue; - - /** - * Create a Worklist. - * - * @param direction the direction (forward or backward) - */ - public Worklist(Direction direction) { - if (direction == Direction.FORWARD) { - queue = new PriorityQueue<>(new ForwardDFOComparator()); - } else if (direction == Direction.BACKWARD) { - queue = new PriorityQueue<>(new BackwardDFOComparator()); - } else { - throw new BugInCF("Unexpected Direction meet: " + direction.name()); - } - } - - /** - * Process the control flow graph, add the blocks to {@link #depthFirstOrder}. - * - * @param cfg the control flow graph to process - */ - public void process(ControlFlowGraph cfg) { - depthFirstOrder.clear(); - int count = 1; - for (Block b : cfg.getDepthFirstOrderedBlocks()) { - depthFirstOrder.put(b, count++); - } - - queue.clear(); - } - - /** - * See {@link PriorityQueue#isEmpty}. - * - * @see PriorityQueue#isEmpty - * @return true if {@link #queue} is empty else false - */ - @Pure - @EnsuresNonNullIf(result = false, expression = "poll()") - @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") // forwarded - public boolean isEmpty() { - return queue.isEmpty(); - } - - /** - * Check if {@link #queue} contains the block which is passed as the argument. - * - * @param block the given block to check - * @return true if {@link #queue} contains the given block - */ - public boolean contains(Block block) { - return queue.contains(block); - } - - /** - * Add the given block to {@link #queue}. Adds unconditionally: does not check containment - * first. - * - * @param block the block to add to {@link #queue} - */ - public void add(Block block) { - queue.add(block); - } - - /** - * See {@link PriorityQueue#poll}. - * - * @see PriorityQueue#poll - * @return the head of {@link #queue} - */ - @Pure - public @Nullable Block poll() { - return queue.poll(); - } - - @Override - public String toString() { - return "Worklist(" + queue + ")"; - } + @Override + public String toString() { + return "Worklist(" + queue + ")"; } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java index f226a8feb6e..e503db4ee0a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java @@ -3,22 +3,22 @@ /** An abstract value used in the org.checkerframework.dataflow analysis. */ public interface AbstractValue> { - /** - * Compute the least upper bound of two values. - * - *

Important: This method must fulfill the following contract: - * - *

    - *
  • Does not change {@code this}. - *
  • Does not change {@code other}. - *
  • Returns a fresh object which is not aliased yet. - *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
  • Is commutative. - *
- * - * @param other the other value - * @return the least upper bound of the two values - */ - V leastUpperBound(V other); + /** + * Compute the least upper bound of two values. + * + *

Important: This method must fulfill the following contract: + * + *

    + *
  • Does not change {@code this}. + *
  • Does not change {@code other}. + *
  • Returns a fresh object which is not aliased yet. + *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
  • Is commutative. + *
+ * + * @param other the other value + * @return the least upper bound of the two values + */ + V leastUpperBound(V other); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java index 9ec9af400dd..30e9cfbf798 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java @@ -1,15 +1,13 @@ package org.checkerframework.dataflow.analysis; import com.sun.source.tree.Tree; - +import java.util.IdentityHashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.block.Block; import org.checkerframework.dataflow.cfg.node.Node; -import java.util.IdentityHashMap; -import java.util.Map; - /** * This interface defines a dataflow analysis, given a control flow graph and a transfer function. A * dataflow analysis has a direction, either forward or backward. The direction of corresponding @@ -21,138 +19,136 @@ * @param the transfer function type that is used to approximated runtime behavior */ public interface Analysis< - V extends AbstractValue, S extends Store, T extends TransferFunction> { - - /** The direction of an analysis instance. */ - enum Direction { - /** The forward direction. */ - FORWARD, - /** The backward direction. */ - BACKWARD - } - - /** - * In calls to {@code Analysis#runAnalysisFor}, whether to return the store before or after the - * given node. - */ - enum BeforeOrAfter { - /** Return the pre-store. */ - BEFORE, - /** Return the post-store. */ - AFTER - } - - /** - * Get the direction of this analysis. - * - * @return the direction of this analysis - */ - Direction getDirection(); - - /** - * Is the analysis currently running? - * - * @return true if the analysis is running currently, else false - */ - boolean isRunning(); - - /** - * Perform the actual analysis. - * - * @param cfg the control flow graph - */ - void performAnalysis(ControlFlowGraph cfg); - - /** - * Perform the actual analysis on one block. - * - * @param b the block to analyze - */ - void performAnalysisBlock(Block b); - - /** - * Runs the analysis again within the block of {@code node} and returns the store at the - * location of {@code node}. If {@code before} is true, then the store immediately before the - * {@link Node} {@code node} is returned. Otherwise, the store immediately after {@code node} is - * returned. If {@code analysisCaches} is not null, this method uses a cache. {@code - * analysisCaches} is a map of a block of node to the cached analysis result. If the cache for - * {@code transferInput} is not in {@code analysisCaches}, this method creates new cache and - * stores it in {@code analysisCaches}. The cache is a map of nodes to the analysis results of - * the nodes. - * - * @param node the node to analyze - * @param preOrPost which store to return: the store immediately before {@code node} or the - * store after {@code node} - * @param blockTransferInput the transfer input of the block of this node - * @param nodeValues abstract values of nodes - * @param analysisCaches caches of analysis results - * @return the store before or after {@code node} (depends on the value of {@code before}) after - * running the analysis - */ - S runAnalysisFor( - Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput blockTransferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches); - - /** - * The result of running the analysis. This is only available once the analysis finished - * running. - * - * @return the result of running the analysis - */ - AnalysisResult getResult(); - - /** - * Get the transfer function of this analysis. - * - * @return the transfer function of this analysis - */ - @Nullable T getTransferFunction(); - - /** - * Get the transfer input of a given {@link Block} b. - * - * @param b a given Block - * @return the transfer input of this Block - */ - @Nullable TransferInput getInput(Block b); - - /** - * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param n n a node - * @return the abstract value for node {@code n}, or {@code null} if no information is available - */ - @Nullable V getValue(Node n); - - /** - * Return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param t the given tree - * @return the abstract value for the given tree - */ - @Nullable V getValue(Tree t); - - /** - * Returns the regular exit store, or {@code null}, if there is no such store (because the - * method cannot exit through the regular exit block). - * - * @return the regular exit store, or {@code null}, if there is no such store (because the - * method cannot exit through the regular exit block) - */ - @Nullable S getRegularExitStore(); - - /** - * Returns the exceptional exit store. - * - * @return the exceptional exit store - */ - @Nullable S getExceptionalExitStore(); + V extends AbstractValue, S extends Store, T extends TransferFunction> { + + /** The direction of an analysis instance. */ + enum Direction { + /** The forward direction. */ + FORWARD, + /** The backward direction. */ + BACKWARD + } + + /** + * In calls to {@code Analysis#runAnalysisFor}, whether to return the store before or after the + * given node. + */ + enum BeforeOrAfter { + /** Return the pre-store. */ + BEFORE, + /** Return the post-store. */ + AFTER + } + + /** + * Get the direction of this analysis. + * + * @return the direction of this analysis + */ + Direction getDirection(); + + /** + * Is the analysis currently running? + * + * @return true if the analysis is running currently, else false + */ + boolean isRunning(); + + /** + * Perform the actual analysis. + * + * @param cfg the control flow graph + */ + void performAnalysis(ControlFlowGraph cfg); + + /** + * Perform the actual analysis on one block. + * + * @param b the block to analyze + */ + void performAnalysisBlock(Block b); + + /** + * Runs the analysis again within the block of {@code node} and returns the store at the location + * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node} + * {@code node} is returned. Otherwise, the store immediately after {@code node} is returned. If + * {@code analysisCaches} is not null, this method uses a cache. {@code analysisCaches} is a map + * of a block of node to the cached analysis result. If the cache for {@code transferInput} is not + * in {@code analysisCaches}, this method creates new cache and stores it in {@code + * analysisCaches}. The cache is a map of nodes to the analysis results of the nodes. + * + * @param node the node to analyze + * @param preOrPost which store to return: the store immediately before {@code node} or the store + * after {@code node} + * @param blockTransferInput the transfer input of the block of this node + * @param nodeValues abstract values of nodes + * @param analysisCaches caches of analysis results + * @return the store before or after {@code node} (depends on the value of {@code before}) after + * running the analysis + */ + S runAnalysisFor( + Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput blockTransferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches); + + /** + * The result of running the analysis. This is only available once the analysis finished running. + * + * @return the result of running the analysis + */ + AnalysisResult getResult(); + + /** + * Get the transfer function of this analysis. + * + * @return the transfer function of this analysis + */ + @Nullable T getTransferFunction(); + + /** + * Get the transfer input of a given {@link Block} b. + * + * @param b a given Block + * @return the transfer input of this Block + */ + @Nullable TransferInput getInput(Block b); + + /** + * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param n n a node + * @return the abstract value for node {@code n}, or {@code null} if no information is available + */ + @Nullable V getValue(Node n); + + /** + * Return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param t the given tree + * @return the abstract value for the given tree + */ + @Nullable V getValue(Tree t); + + /** + * Returns the regular exit store, or {@code null}, if there is no such store (because the method + * cannot exit through the regular exit block). + * + * @return the regular exit store, or {@code null}, if there is no such store (because the method + * cannot exit through the regular exit block) + */ + @Nullable S getRegularExitStore(); + + /** + * Returns the exceptional exit store. + * + * @return the exceptional exit store + */ + @Nullable S getExceptionalExitStore(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java index c66379b2b01..77ea632a04c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java @@ -3,16 +3,6 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.cfg.block.Block; -import org.checkerframework.dataflow.cfg.node.Node; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; -import org.plumelib.util.UniqueId; -import org.plumelib.util.UnmodifiableIdentityHashMap; - import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; @@ -20,8 +10,15 @@ import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLong; - import javax.lang.model.element.VariableElement; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.cfg.block.Block; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; +import org.plumelib.util.UniqueId; +import org.plumelib.util.UnmodifiableIdentityHashMap; /** * An {@link AnalysisResult} represents the result of a org.checkerframework.dataflow analysis by @@ -33,511 +30,492 @@ */ public class AnalysisResult, S extends Store> implements UniqueId { - /** - * For efficiency, certain maps stored in the result are only copied lazily, when they need to - * be mutated. This flag tracks if the copying has occurred. - */ - private boolean mapsCopied = false; - - /** Abstract values of nodes. */ - protected IdentityHashMap nodeValues; - - /** - * Map from AST {@link Tree}s to sets of {@link Node}s. - * - *

Some of those Nodes might not be keys in {@link #nodeValues}. One reason is that the Node - * is unreachable in the control flow graph, so dataflow never gave it a value. - */ - protected IdentityHashMap> treeLookup; - - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the - * synthetic tree that is {@code v + 1} or {@code v - 1}. - */ - protected IdentityHashMap postfixLookup; - - /** Map from (effectively final) local variable elements to their abstract value. */ - protected final Map finalLocalValues; - - /** The stores before every method call. */ - protected final IdentityHashMap> stores; - - /** - * Caches of the analysis results for each input for the block of the node and each node. - * - * @see #runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, IdentityHashMap, Map) - */ - protected final Map, IdentityHashMap>> - analysisCaches; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid(@UnknownInitialization AnalysisResult this) { - return uid; - } - - /** - * Initialize with given mappings. - * - * @param nodeValues {@link #nodeValues} - * @param stores {@link #stores} - * @param treeLookup {@link #treeLookup} - * @param postfixLookup {@link #postfixLookup} - * @param finalLocalValues {@link #finalLocalValues} - * @param analysisCaches {@link #analysisCaches} - */ - protected AnalysisResult( - IdentityHashMap nodeValues, - IdentityHashMap> stores, - IdentityHashMap> treeLookup, - IdentityHashMap postfixLookup, - Map finalLocalValues, - Map, IdentityHashMap>> analysisCaches) { - this.nodeValues = UnmodifiableIdentityHashMap.wrap(nodeValues); - this.treeLookup = UnmodifiableIdentityHashMap.wrap(treeLookup); - this.postfixLookup = UnmodifiableIdentityHashMap.wrap(postfixLookup); - // TODO: why are stores and finalLocalValues captured? - this.stores = stores; - this.finalLocalValues = finalLocalValues; - this.analysisCaches = analysisCaches; - } - - /** - * Initialize with given mappings and empty cache. - * - * @param nodeValues {@link #nodeValues} - * @param stores {@link #stores} - * @param treeLookup {@link #treeLookup} - * @param postfixLookup {@link #postfixLookup} - * @param finalLocalValues {@link #finalLocalValues} - */ - public AnalysisResult( - IdentityHashMap nodeValues, - IdentityHashMap> stores, - IdentityHashMap> treeLookup, - IdentityHashMap postfixLookup, - Map finalLocalValues) { - this( - nodeValues, - stores, - treeLookup, - postfixLookup, - finalLocalValues, - new IdentityHashMap<>()); + /** + * For efficiency, certain maps stored in the result are only copied lazily, when they need to be + * mutated. This flag tracks if the copying has occurred. + */ + private boolean mapsCopied = false; + + /** Abstract values of nodes. */ + protected IdentityHashMap nodeValues; + + /** + * Map from AST {@link Tree}s to sets of {@link Node}s. + * + *

Some of those Nodes might not be keys in {@link #nodeValues}. One reason is that the Node is + * unreachable in the control flow graph, so dataflow never gave it a value. + */ + protected IdentityHashMap> treeLookup; + + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic + * tree that is {@code v + 1} or {@code v - 1}. + */ + protected IdentityHashMap postfixLookup; + + /** Map from (effectively final) local variable elements to their abstract value. */ + protected final Map finalLocalValues; + + /** The stores before every method call. */ + protected final IdentityHashMap> stores; + + /** + * Caches of the analysis results for each input for the block of the node and each node. + * + * @see #runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, IdentityHashMap, Map) + */ + protected final Map, IdentityHashMap>> + analysisCaches; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid(@UnknownInitialization AnalysisResult this) { + return uid; + } + + /** + * Initialize with given mappings. + * + * @param nodeValues {@link #nodeValues} + * @param stores {@link #stores} + * @param treeLookup {@link #treeLookup} + * @param postfixLookup {@link #postfixLookup} + * @param finalLocalValues {@link #finalLocalValues} + * @param analysisCaches {@link #analysisCaches} + */ + protected AnalysisResult( + IdentityHashMap nodeValues, + IdentityHashMap> stores, + IdentityHashMap> treeLookup, + IdentityHashMap postfixLookup, + Map finalLocalValues, + Map, IdentityHashMap>> analysisCaches) { + this.nodeValues = UnmodifiableIdentityHashMap.wrap(nodeValues); + this.treeLookup = UnmodifiableIdentityHashMap.wrap(treeLookup); + this.postfixLookup = UnmodifiableIdentityHashMap.wrap(postfixLookup); + // TODO: why are stores and finalLocalValues captured? + this.stores = stores; + this.finalLocalValues = finalLocalValues; + this.analysisCaches = analysisCaches; + } + + /** + * Initialize with given mappings and empty cache. + * + * @param nodeValues {@link #nodeValues} + * @param stores {@link #stores} + * @param treeLookup {@link #treeLookup} + * @param postfixLookup {@link #postfixLookup} + * @param finalLocalValues {@link #finalLocalValues} + */ + public AnalysisResult( + IdentityHashMap nodeValues, + IdentityHashMap> stores, + IdentityHashMap> treeLookup, + IdentityHashMap postfixLookup, + Map finalLocalValues) { + this(nodeValues, stores, treeLookup, postfixLookup, finalLocalValues, new IdentityHashMap<>()); + } + + /** + * Initialize empty result with specified cache. + * + * @param analysisCaches {@link #analysisCaches} + */ + public AnalysisResult( + Map, IdentityHashMap>> analysisCaches) { + this( + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new HashMap<>(), + analysisCaches); + } + + /** + * Combine with another analysis result. + * + * @param other an analysis result to combine with this + */ + public void combine(AnalysisResult other) { + copyMapsIfNeeded(); + nodeValues.putAll(other.nodeValues); + mergeTreeLookup(treeLookup, other.treeLookup); + postfixLookup.putAll(other.postfixLookup); + stores.putAll(other.stores); + finalLocalValues.putAll(other.finalLocalValues); + } + + /** Make copies of certain internal IdentityHashMaps, if they have not been copied already. */ + private void copyMapsIfNeeded() { + if (!mapsCopied) { + nodeValues = new IdentityHashMap<>(nodeValues); + treeLookup = new IdentityHashMap<>(treeLookup); + postfixLookup = new IdentityHashMap<>(postfixLookup); + mapsCopied = true; } - - /** - * Initialize empty result with specified cache. - * - * @param analysisCaches {@link #analysisCaches} - */ - public AnalysisResult( - Map, IdentityHashMap>> analysisCaches) { - this( - new IdentityHashMap<>(), - new IdentityHashMap<>(), - new IdentityHashMap<>(), - new IdentityHashMap<>(), - new HashMap<>(), - analysisCaches); - } - - /** - * Combine with another analysis result. - * - * @param other an analysis result to combine with this - */ - public void combine(AnalysisResult other) { - copyMapsIfNeeded(); - nodeValues.putAll(other.nodeValues); - mergeTreeLookup(treeLookup, other.treeLookup); - postfixLookup.putAll(other.postfixLookup); - stores.putAll(other.stores); - finalLocalValues.putAll(other.finalLocalValues); - } - - /** Make copies of certain internal IdentityHashMaps, if they have not been copied already. */ - private void copyMapsIfNeeded() { - if (!mapsCopied) { - nodeValues = new IdentityHashMap<>(nodeValues); - treeLookup = new IdentityHashMap<>(treeLookup); - postfixLookup = new IdentityHashMap<>(postfixLookup); - mapsCopied = true; - } - } - - /** - * Merge all entries from otherTreeLookup into treeLookup. Merge sets if already present. - * - * @param treeLookup a map from abstract syntax trees to sets of nodes - * @param otherTreeLookup another treeLookup that will be merged into {@code treeLookup} - */ - private static void mergeTreeLookup( - IdentityHashMap> treeLookup, - IdentityHashMap> otherTreeLookup) { - for (Map.Entry> entry : otherTreeLookup.entrySet()) { - Set hit = treeLookup.get(entry.getKey()); - if (hit == null) { - treeLookup.put(entry.getKey(), entry.getValue()); - } else { - hit.addAll(entry.getValue()); - } - } + } + + /** + * Merge all entries from otherTreeLookup into treeLookup. Merge sets if already present. + * + * @param treeLookup a map from abstract syntax trees to sets of nodes + * @param otherTreeLookup another treeLookup that will be merged into {@code treeLookup} + */ + private static void mergeTreeLookup( + IdentityHashMap> treeLookup, + IdentityHashMap> otherTreeLookup) { + for (Map.Entry> entry : otherTreeLookup.entrySet()) { + Set hit = treeLookup.get(entry.getKey()); + if (hit == null) { + treeLookup.put(entry.getKey(), entry.getValue()); + } else { + hit.addAll(entry.getValue()); + } } - - /** - * Returns the value of effectively final local variables. - * - * @return the value of effectively final local variables - */ - public Map getFinalLocalValues() { - return finalLocalValues; + } + + /** + * Returns the value of effectively final local variables. + * + * @return the value of effectively final local variables + */ + public Map getFinalLocalValues() { + return finalLocalValues; + } + + /** + * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param n a node + * @return the abstract value for {@link Node} {@code n}, or {@code null} if no information is + * available + */ + public @Nullable V getValue(Node n) { + return nodeValues.get(n); + } + + /** + * Returns the abstract value for {@link Tree} {@code t}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param t a tree + * @return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is + * available + */ + public @Nullable V getValue(Tree t) { + Set nodes = treeLookup.get(t); + + if (nodes == null) { + return null; } - - /** - * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param n a node - * @return the abstract value for {@link Node} {@code n}, or {@code null} if no information is - * available - */ - public @Nullable V getValue(Node n) { - return nodeValues.get(n); + V merged = null; + for (Node aNode : nodes) { + V a = getValue(aNode); + if (merged == null) { + merged = a; + } else if (a != null) { + merged = merged.leastUpperBound(a); + } } - - /** - * Returns the abstract value for {@link Tree} {@code t}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param t a tree - * @return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is - * available - */ - public @Nullable V getValue(Tree t) { - Set nodes = treeLookup.get(t); - - if (nodes == null) { - return null; - } - V merged = null; - for (Node aNode : nodes) { - V a = getValue(aNode); - if (merged == null) { - merged = a; - } else if (a != null) { - merged = merged.leastUpperBound(a); - } - } - return merged; + return merged; + } + + /** + * Returns the {@code Node}s corresponding to a particular {@code Tree}. Multiple {@code Node}s + * can correspond to a single {@code Tree} because of several reasons: + * + *

    + *
  1. In a lambda expression such as {@code () -> 5} the {@code 5} is both an {@code + * IntegerLiteralNode} and a {@code LambdaResultExpressionNode}. + *
  2. Widening and narrowing primitive conversions can result in {@code WideningConversionNode} + * and {@code NarrowingConversionNode}. + *
  3. Automatic String conversion can result in a {@code StringConversionNode}. + *
  4. Trees for {@code finally} blocks are cloned to achieve a precise CFG. Any {@code Tree} + * within a finally block can have multiple corresponding {@code Node}s attached to them. + *
+ * + * Callers of this method should always iterate through the returned set, possibly ignoring all + * {@code Node}s they are not interested in. + * + * @param tree a tree + * @return the set of {@link Node}s for a given {@link Tree} + */ + public @Nullable Set getNodesForTree(Tree tree) { + return treeLookup.get(tree); + } + + /** + * Returns the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment or + * decrement tree. + * + * @param postfixTree a postfix increment or decrement tree + * @return the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment or + * decrement tree + */ + public BinaryTree getPostfixBinaryTree(UnaryTree postfixTree) { + if (!postfixLookup.containsKey(postfixTree)) { + throw new BugInCF(postfixTree + " is not in postfixLookup"); } - - /** - * Returns the {@code Node}s corresponding to a particular {@code Tree}. Multiple {@code Node}s - * can correspond to a single {@code Tree} because of several reasons: - * - *
    - *
  1. In a lambda expression such as {@code () -> 5} the {@code 5} is both an {@code - * IntegerLiteralNode} and a {@code LambdaResultExpressionNode}. - *
  2. Widening and narrowing primitive conversions can result in {@code - * WideningConversionNode} and {@code NarrowingConversionNode}. - *
  3. Automatic String conversion can result in a {@code StringConversionNode}. - *
  4. Trees for {@code finally} blocks are cloned to achieve a precise CFG. Any {@code Tree} - * within a finally block can have multiple corresponding {@code Node}s attached to them. - *
- * - * Callers of this method should always iterate through the returned set, possibly ignoring all - * {@code Node}s they are not interested in. - * - * @param tree a tree - * @return the set of {@link Node}s for a given {@link Tree} - */ - public @Nullable Set getNodesForTree(Tree tree) { - return treeLookup.get(tree); + return postfixLookup.get(postfixTree); + } + + /** + * Returns the store immediately before a given {@link Tree}. + * + * @param tree a tree + * @return the store immediately before a given {@link Tree} + */ + public @Nullable S getStoreBefore(Tree tree) { + Set nodes = getNodesForTree(tree); + if (nodes == null) { + return null; } - - /** - * Returns the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment - * or decrement tree. - * - * @param postfixTree a postfix increment or decrement tree - * @return the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment - * or decrement tree - */ - public BinaryTree getPostfixBinaryTree(UnaryTree postfixTree) { - if (!postfixLookup.containsKey(postfixTree)) { - throw new BugInCF(postfixTree + " is not in postfixLookup"); - } - return postfixLookup.get(postfixTree); + S merged = null; + for (Node node : nodes) { + S s = getStoreBefore(node); + if (merged == null) { + merged = s; + } else if (s != null) { + merged = merged.leastUpperBound(s); + } } - - /** - * Returns the store immediately before a given {@link Tree}. - * - * @param tree a tree - * @return the store immediately before a given {@link Tree} - */ - public @Nullable S getStoreBefore(Tree tree) { - Set nodes = getNodesForTree(tree); - if (nodes == null) { - return null; + return merged; + } + + /** + * Returns the store immediately before a given {@link Node}. + * + * @param node a node + * @return the store immediately before a given {@link Node} + */ + public @Nullable S getStoreBefore(Node node) { + return runAnalysisFor(node, Analysis.BeforeOrAfter.BEFORE); + } + + /** + * Returns the regular store immediately before a given {@link Block}. + * + * @param block a block + * @return the store right before the given block + */ + public S getStoreBefore(Block block) { + TransferInput transferInput = stores.get(block); + assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null"; + Analysis analysis = transferInput.analysis; + switch (analysis.getDirection()) { + case FORWARD: + return transferInput.getRegularStore(); + case BACKWARD: + List nodes = block.getNodes(); + if (nodes.isEmpty()) { + // This block doesn't contain any node, return the store in the transfer input. + return transferInput.getRegularStore(); + } else { + Node firstNode = nodes.get(0); + return analysis.runAnalysisFor( + firstNode, Analysis.BeforeOrAfter.BEFORE, transferInput, nodeValues, analysisCaches); } - S merged = null; - for (Node node : nodes) { - S s = getStoreBefore(node); - if (merged == null) { - merged = s; - } else if (s != null) { - merged = merged.leastUpperBound(s); - } - } - return merged; - } - - /** - * Returns the store immediately before a given {@link Node}. - * - * @param node a node - * @return the store immediately before a given {@link Node} - */ - public @Nullable S getStoreBefore(Node node) { - return runAnalysisFor(node, Analysis.BeforeOrAfter.BEFORE); + default: + throw new BugInCF("Unknown direction: " + analysis.getDirection()); } - - /** - * Returns the regular store immediately before a given {@link Block}. - * - * @param block a block - * @return the store right before the given block - */ - public S getStoreBefore(Block block) { - TransferInput transferInput = stores.get(block); - assert transferInput != null - : "@AssumeAssertion(nullness): transferInput should be non-null"; - Analysis analysis = transferInput.analysis; - switch (analysis.getDirection()) { - case FORWARD: - return transferInput.getRegularStore(); - case BACKWARD: - List nodes = block.getNodes(); - if (nodes.isEmpty()) { - // This block doesn't contain any node, return the store in the transfer input. - return transferInput.getRegularStore(); - } else { - Node firstNode = nodes.get(0); - return analysis.runAnalysisFor( - firstNode, - Analysis.BeforeOrAfter.BEFORE, - transferInput, - nodeValues, - analysisCaches); - } - default: - throw new BugInCF("Unknown direction: " + analysis.getDirection()); + } + + /** + * Returns the regular store immediately after a given block. + * + * @param block a block + * @return the store after the given block + */ + public S getStoreAfter(Block block) { + TransferInput transferInput = stores.get(block); + assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null"; + Analysis analysis = transferInput.analysis; + switch (analysis.getDirection()) { + case FORWARD: + Node lastNode = block.getLastNode(); + if (lastNode == null) { + // This block doesn't contain any node, return the store in the transfer input. + return transferInput.getRegularStore(); + } else { + return analysis.runAnalysisFor( + lastNode, Analysis.BeforeOrAfter.AFTER, transferInput, nodeValues, analysisCaches); } + case BACKWARD: + return transferInput.getRegularStore(); + default: + throw new BugInCF("Unknown direction: " + analysis.getDirection()); } - - /** - * Returns the regular store immediately after a given block. - * - * @param block a block - * @return the store after the given block - */ - public S getStoreAfter(Block block) { - TransferInput transferInput = stores.get(block); - assert transferInput != null - : "@AssumeAssertion(nullness): transferInput should be non-null"; - Analysis analysis = transferInput.analysis; - switch (analysis.getDirection()) { - case FORWARD: - Node lastNode = block.getLastNode(); - if (lastNode == null) { - // This block doesn't contain any node, return the store in the transfer input. - return transferInput.getRegularStore(); - } else { - return analysis.runAnalysisFor( - lastNode, - Analysis.BeforeOrAfter.AFTER, - transferInput, - nodeValues, - analysisCaches); - } - case BACKWARD: - return transferInput.getRegularStore(); - default: - throw new BugInCF("Unknown direction: " + analysis.getDirection()); - } + } + + /** + * Returns the store immediately after a given {@link Tree}. + * + * @param tree a tree + * @return the store immediately after a given {@link Tree} + */ + public @Nullable S getStoreAfter(Tree tree) { + Set nodes = getNodesForTree(tree); + if (nodes == null) { + return null; } - - /** - * Returns the store immediately after a given {@link Tree}. - * - * @param tree a tree - * @return the store immediately after a given {@link Tree} - */ - public @Nullable S getStoreAfter(Tree tree) { - Set nodes = getNodesForTree(tree); - if (nodes == null) { - return null; - } - S merged = null; - for (Node node : nodes) { - S s = getStoreAfter(node); - if (merged == null) { - merged = s; - } else if (s != null) { - merged = merged.leastUpperBound(s); - } - } - return merged; + S merged = null; + for (Node node : nodes) { + S s = getStoreAfter(node); + if (merged == null) { + merged = s; + } else if (s != null) { + merged = merged.leastUpperBound(s); + } } - - /** - * Returns the store immediately after a given {@link Node}. - * - * @param node a node - * @return the store immediately after a given {@link Node} - */ - public @Nullable S getStoreAfter(Node node) { - return runAnalysisFor(node, Analysis.BeforeOrAfter.AFTER); + return merged; + } + + /** + * Returns the store immediately after a given {@link Node}. + * + * @param node a node + * @return the store immediately after a given {@link Node} + */ + public @Nullable S getStoreAfter(Node node) { + return runAnalysisFor(node, Analysis.BeforeOrAfter.AFTER); + } + + /** + * Runs the analysis again within the block of {@code node} and returns the store at the location + * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node} + * {@code node} is returned. Otherwise, the store after {@code node} is returned. + * + *

If the given {@link Node} cannot be reached (in the control flow graph), then {@code null} + * is returned. + * + * @param node the node to analyze + * @param preOrPost which store to return: the store immediately before {@code node} or the store + * after {@code node} + * @return the store before or after {@code node} (depends on the value of {@code before}) after + * running the analysis + */ + protected @Nullable S runAnalysisFor(Node node, Analysis.BeforeOrAfter preOrPost) { + // block is null if node is a formal parameter of a method, or is a field access thereof + Block block = node.getBlock(); + assert block != null : "@AssumeAssertion(nullness): null block for node " + node; + TransferInput transferInput = stores.get(block); + if (transferInput == null) { + return null; } - - /** - * Runs the analysis again within the block of {@code node} and returns the store at the - * location of {@code node}. If {@code before} is true, then the store immediately before the - * {@link Node} {@code node} is returned. Otherwise, the store after {@code node} is returned. - * - *

If the given {@link Node} cannot be reached (in the control flow graph), then {@code null} - * is returned. - * - * @param node the node to analyze - * @param preOrPost which store to return: the store immediately before {@code node} or the - * store after {@code node} - * @return the store before or after {@code node} (depends on the value of {@code before}) after - * running the analysis - */ - protected @Nullable S runAnalysisFor(Node node, Analysis.BeforeOrAfter preOrPost) { - // block is null if node is a formal parameter of a method, or is a field access thereof - Block block = node.getBlock(); - assert block != null : "@AssumeAssertion(nullness): null block for node " + node; - TransferInput transferInput = stores.get(block); - if (transferInput == null) { - return null; - } - // Calling Analysis.runAnalysisFor() may mutate the internal nodeValues map inside an - // AbstractAnalysis object, and by default the AnalysisResult constructor just wraps this - // map without copying it. So here the AnalysisResult maps must be copied, to preserve - // them. - // TODO: Wouldn't it be safer to do at the beginning of the called method? - copyMapsIfNeeded(); - return runAnalysisFor(node, preOrPost, transferInput, nodeValues, analysisCaches); + // Calling Analysis.runAnalysisFor() may mutate the internal nodeValues map inside an + // AbstractAnalysis object, and by default the AnalysisResult constructor just wraps this + // map without copying it. So here the AnalysisResult maps must be copied, to preserve + // them. + // TODO: Wouldn't it be safer to do at the beginning of the called method? + copyMapsIfNeeded(); + return runAnalysisFor(node, preOrPost, transferInput, nodeValues, analysisCaches); + } + + /** + * Runs the analysis again within the block of {@code node} and returns the store at the location + * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node} + * {@code node} is returned. Otherwise, the store immediately after {@code node} is returned. If + * {@code analysisCaches} is not null, this method uses a cache. {@code analysisCaches} is a map + * of a block of node to the cached analysis result. If the cache for {@code transferInput} is not + * in {@code analysisCaches}, this method creates new cache and stores it in {@code + * analysisCaches}. The cache is a map of nodes to the analysis results of the nodes. + * + * @param the abstract value type to be tracked by the analysis + * @param the store type used in the analysis + * @param node the node to analyze + * @param preOrPost which store to return: the store immediately before {@code node} or the store + * after {@code node} + * @param transferInput a transfer input + * @param nodeValues {@link #nodeValues} + * @param analysisCaches {@link #analysisCaches} + * @return the store before or after {@code node} (depends on the value of {@code before}) after + * running the analysis + */ + public static , S extends Store> S runAnalysisFor( + Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput transferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches) { + if (transferInput.analysis == null) { + throw new BugInCF("Analysis in transferInput cannot be null."); } - - /** - * Runs the analysis again within the block of {@code node} and returns the store at the - * location of {@code node}. If {@code before} is true, then the store immediately before the - * {@link Node} {@code node} is returned. Otherwise, the store immediately after {@code node} is - * returned. If {@code analysisCaches} is not null, this method uses a cache. {@code - * analysisCaches} is a map of a block of node to the cached analysis result. If the cache for - * {@code transferInput} is not in {@code analysisCaches}, this method creates new cache and - * stores it in {@code analysisCaches}. The cache is a map of nodes to the analysis results of - * the nodes. - * - * @param the abstract value type to be tracked by the analysis - * @param the store type used in the analysis - * @param node the node to analyze - * @param preOrPost which store to return: the store immediately before {@code node} or the - * store after {@code node} - * @param transferInput a transfer input - * @param nodeValues {@link #nodeValues} - * @param analysisCaches {@link #analysisCaches} - * @return the store before or after {@code node} (depends on the value of {@code before}) after - * running the analysis - */ - public static , S extends Store> S runAnalysisFor( - Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput transferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches) { - if (transferInput.analysis == null) { - throw new BugInCF("Analysis in transferInput cannot be null."); - } - return transferInput.analysis.runAnalysisFor( - node, preOrPost, transferInput, nodeValues, analysisCaches); + return transferInput.analysis.runAnalysisFor( + node, preOrPost, transferInput, nodeValues, analysisCaches); + } + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public String toStringDebug() { + StringJoiner result = + new StringJoiner( + String.format("%n "), String.format("AnalysisResult{%n "), String.format("%n}")); + result.add("nodeValues = " + nodeValuesToString(nodeValues)); + result.add("treeLookup = " + treeLookupToString(treeLookup)); + result.add("postfixLookup = " + postfixLookup); + result.add("finalLocalValues = " + finalLocalValues); + result.add("stores = " + stores); + result.add("analysisCaches = " + analysisCaches); + return result.toString(); + } + + /** + * Returns a verbose string representation, useful for debugging. The map has the same type as the + * {@code nodeValues} field. + * + * @param the type of values in the map + * @param nodeValues a map to format + * @return a printed representation of the given map + */ + public static String nodeValuesToString(Map nodeValues) { + if (nodeValues.isEmpty()) { + return "{}"; } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public String toStringDebug() { - StringJoiner result = - new StringJoiner( - String.format("%n "), - String.format("AnalysisResult{%n "), - String.format("%n}")); - result.add("nodeValues = " + nodeValuesToString(nodeValues)); - result.add("treeLookup = " + treeLookupToString(treeLookup)); - result.add("postfixLookup = " + postfixLookup); - result.add("finalLocalValues = " + finalLocalValues); - result.add("stores = " + stores); - result.add("analysisCaches = " + analysisCaches); - return result.toString(); + StringJoiner result = new StringJoiner(String.format("%n ")); + result.add("{"); + for (Map.Entry entry : nodeValues.entrySet()) { + Node key = entry.getKey(); + result.add(String.format("%s => %s", key.toStringDebug(), entry.getValue())); } - - /** - * Returns a verbose string representation, useful for debugging. The map has the same type as - * the {@code nodeValues} field. - * - * @param the type of values in the map - * @param nodeValues a map to format - * @return a printed representation of the given map - */ - public static String nodeValuesToString(Map nodeValues) { - if (nodeValues.isEmpty()) { - return "{}"; - } - StringJoiner result = new StringJoiner(String.format("%n ")); - result.add("{"); - for (Map.Entry entry : nodeValues.entrySet()) { - Node key = entry.getKey(); - result.add(String.format("%s => %s", key.toStringDebug(), entry.getValue())); - } - result.add("}"); - return result.toString(); + result.add("}"); + return result.toString(); + } + + /** + * Returns a verbose string representation of a map, useful for debugging. The map has the same + * type as the {@code treeLookup} field. + * + * @param treeLookup a map to format + * @return a printed representation of the given map + */ + public static String treeLookupToString(Map> treeLookup) { + if (treeLookup.isEmpty()) { + return "{}"; } - - /** - * Returns a verbose string representation of a map, useful for debugging. The map has the same - * type as the {@code treeLookup} field. - * - * @param treeLookup a map to format - * @return a printed representation of the given map - */ - public static String treeLookupToString(Map> treeLookup) { - if (treeLookup.isEmpty()) { - return "{}"; - } - StringJoiner result = new StringJoiner(String.format("%n ")); - result.add("{"); - for (Map.Entry> entry : treeLookup.entrySet()) { - Tree key = entry.getKey(); - result.add( - TreeUtils.toStringTruncated(key, 65) - + " => " - + Node.nodeCollectionToString(entry.getValue())); - } - result.add("}"); - return result.toString(); + StringJoiner result = new StringJoiner(String.format("%n ")); + result.add("{"); + for (Map.Entry> entry : treeLookup.entrySet()) { + Tree key = entry.getKey(); + result.add( + TreeUtils.toStringTruncated(key, 65) + + " => " + + Node.nodeCollectionToString(entry.getValue())); } + result.add("}"); + return result.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java index c59110d61f4..91c768b4e2e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java @@ -11,17 +11,14 @@ * @param the backward transfer function type that is used to approximate runtime behavior */ public interface BackwardAnalysis< - V extends AbstractValue, - S extends Store, - T extends BackwardTransferFunction> - extends Analysis { + V extends AbstractValue, S extends Store, T extends BackwardTransferFunction> + extends Analysis { - /** - * Get the output store at the entry block of a given control flow graph. For a backward - * analysis, the output store contains the analyzed flow information from the exit block to the - * entry block. - * - * @return the output store at the entry block of a given control flow graph - */ - @Nullable S getEntryStore(); + /** + * Get the output store at the entry block of a given control flow graph. For a backward analysis, + * the output store contains the analyzed flow information from the exit block to the entry block. + * + * @return the output store at the entry block of a given control flow graph + */ + @Nullable S getEntryStore(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java index f6e63262977..7b0eec9c58a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java @@ -1,5 +1,11 @@ package org.checkerframework.dataflow.analysis; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -16,14 +22,6 @@ import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.checkerframework.javacutil.BugInCF; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; - /** * An implementation of a backward analysis to solve a org.checkerframework.dataflow problem given a * control flow graph and a backward transfer function. @@ -33,391 +31,378 @@ * @param the transfer function type that is used to approximate runtime behavior */ public class BackwardAnalysisImpl< - V extends AbstractValue, - S extends Store, - T extends BackwardTransferFunction> - extends AbstractAnalysis implements BackwardAnalysis { + V extends AbstractValue, S extends Store, T extends BackwardTransferFunction> + extends AbstractAnalysis implements BackwardAnalysis { - // TODO: Add widening support like what the forward analysis does. + // TODO: Add widening support like what the forward analysis does. - /** Out stores after every basic block (assumed to be 'no information' if not present). */ - protected final IdentityHashMap outStores = new IdentityHashMap<>(); + /** Out stores after every basic block (assumed to be 'no information' if not present). */ + protected final IdentityHashMap outStores = new IdentityHashMap<>(); - /** - * Exception store of an exception block, propagated by exceptional successors of its exception - * block, and merged with the normal {@link TransferResult}. - */ - protected final IdentityHashMap exceptionStores = new IdentityHashMap<>(); + /** + * Exception store of an exception block, propagated by exceptional successors of its exception + * block, and merged with the normal {@link TransferResult}. + */ + protected final IdentityHashMap exceptionStores = new IdentityHashMap<>(); - /** The store right before the entry block. */ - protected @Nullable S storeAtEntry = null; + /** The store right before the entry block. */ + protected @Nullable S storeAtEntry = null; - // `@code`, not `@link`, because dataflow module doesn't depend on framework module. - /** - * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a - * control flow graph. When using this constructor, the transfer function is set later by the - * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. - */ - public BackwardAnalysisImpl() { - super(Direction.BACKWARD); - } + // `@code`, not `@link`, because dataflow module doesn't depend on framework module. + /** + * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a + * control flow graph. When using this constructor, the transfer function is set later by the + * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. + */ + public BackwardAnalysisImpl() { + super(Direction.BACKWARD); + } + + /** + * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a + * control flow graph given a transfer function. + * + * @param transferFunction the transfer function + */ + public BackwardAnalysisImpl(T transferFunction) { + this(); + this.transferFunction = transferFunction; + } - /** - * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a - * control flow graph given a transfer function. - * - * @param transferFunction the transfer function - */ - public BackwardAnalysisImpl(T transferFunction) { - this(); - this.transferFunction = transferFunction; + @Override + public void performAnalysis(ControlFlowGraph cfg) { + if (isRunning) { + throw new BugInCF("performAnalysis() shouldn't be called when the analysis is running."); } + isRunning = true; + try { + init(cfg); + while (!worklist.isEmpty()) { + Block b = worklist.poll(); + performAnalysisBlock(b); + } + } finally { + assert isRunning; + // In case performAnalysisBlock crashed, reset isRunning to false. + isRunning = false; + } + } - @Override - public void performAnalysis(ControlFlowGraph cfg) { - if (isRunning) { - throw new BugInCF( - "performAnalysis() shouldn't be called when the analysis is running."); + @Override + public void performAnalysisBlock(Block b) { + switch (b.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rb = (RegularBlock) b; + TransferInput inputAfter = getInput(rb); + assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputAfter.copy(); + Node firstNode = null; + boolean addToWorklistAgain = false; + List nodeList = rb.getNodes(); + ListIterator reverseIter = nodeList.listIterator(nodeList.size()); + while (reverseIter.hasPrevious()) { + Node node = reverseIter.previous(); + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + TransferResult transferResult = callTransferFunction(node, currentInput); + addToWorklistAgain |= updateNodeValues(node, transferResult); + currentInput = new TransferInput<>(node, this, transferResult); + firstNode = node; + } + // Propagate store to predecessors + for (Block pred : rb.getPredecessors()) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + propagateStoresTo( + pred, firstNode, currentInput, FlowRule.EACH_TO_EACH, addToWorklistAgain); + } + break; + } + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) b; + TransferInput inputAfter = getInput(eb); + assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputAfter.copy(); + Node node = eb.getNode(); + TransferResult transferResult = callTransferFunction(node, currentInput); + boolean addToWorklistAgain = updateNodeValues(node, transferResult); + // Merge transferResult with exceptionStore if there exists one + S exceptionStore = exceptionStores.get(eb); + S mergedStore = + exceptionStore != null + ? transferResult.getRegularStore().leastUpperBound(exceptionStore) + : transferResult.getRegularStore(); + for (Block pred : eb.getPredecessors()) { + addStoreAfter(pred, node, mergedStore, addToWorklistAgain); + } + break; + } + case CONDITIONAL_BLOCK: + { + ConditionalBlock cb = (ConditionalBlock) b; + TransferInput inputAfter = getInput(cb); + assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; + TransferInput input = inputAfter.copy(); + for (Block pred : cb.getPredecessors()) { + propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); + } + break; } - isRunning = true; - try { - init(cfg); - while (!worklist.isEmpty()) { - Block b = worklist.poll(); - performAnalysisBlock(b); + case SPECIAL_BLOCK: + { + // Special basic blocks are empty and cannot throw exceptions, + // thus there is no need to perform any analysis. + SpecialBlock sb = (SpecialBlock) b; + SpecialBlockType sType = sb.getSpecialType(); + if (sType == SpecialBlockType.ENTRY) { + // storage the store at entry + storeAtEntry = outStores.get(sb); + } else { + assert sType == SpecialBlockType.EXIT || sType == SpecialBlockType.EXCEPTIONAL_EXIT; + TransferInput input = getInput(sb); + assert input != null : "@AssumeAssertion(nullness): invariant"; + for (Block pred : sb.getPredecessors()) { + propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); } - } finally { - assert isRunning; - // In case performAnalysisBlock crashed, reset isRunning to false. - isRunning = false; + } + break; } + default: + throw new BugInCF("Unexpected block type: " + b.getType()); } + } - @Override - public void performAnalysisBlock(Block b) { - switch (b.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rb = (RegularBlock) b; - TransferInput inputAfter = getInput(rb); - assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputAfter.copy(); - Node firstNode = null; - boolean addToWorklistAgain = false; - List nodeList = rb.getNodes(); - ListIterator reverseIter = nodeList.listIterator(nodeList.size()); - while (reverseIter.hasPrevious()) { - Node node = reverseIter.previous(); - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - TransferResult transferResult = - callTransferFunction(node, currentInput); - addToWorklistAgain |= updateNodeValues(node, transferResult); - currentInput = new TransferInput<>(node, this, transferResult); - firstNode = node; - } - // Propagate store to predecessors - for (Block pred : rb.getPredecessors()) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - propagateStoresTo( - pred, - firstNode, - currentInput, - FlowRule.EACH_TO_EACH, - addToWorklistAgain); - } - break; - } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) b; - TransferInput inputAfter = getInput(eb); - assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputAfter.copy(); - Node node = eb.getNode(); - TransferResult transferResult = callTransferFunction(node, currentInput); - boolean addToWorklistAgain = updateNodeValues(node, transferResult); - // Merge transferResult with exceptionStore if there exists one - S exceptionStore = exceptionStores.get(eb); - S mergedStore = - exceptionStore != null - ? transferResult - .getRegularStore() - .leastUpperBound(exceptionStore) - : transferResult.getRegularStore(); - for (Block pred : eb.getPredecessors()) { - addStoreAfter(pred, node, mergedStore, addToWorklistAgain); - } - break; - } - case CONDITIONAL_BLOCK: - { - ConditionalBlock cb = (ConditionalBlock) b; - TransferInput inputAfter = getInput(cb); - assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; - TransferInput input = inputAfter.copy(); - for (Block pred : cb.getPredecessors()) { - propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); - } - break; - } - case SPECIAL_BLOCK: - { - // Special basic blocks are empty and cannot throw exceptions, - // thus there is no need to perform any analysis. - SpecialBlock sb = (SpecialBlock) b; - SpecialBlockType sType = sb.getSpecialType(); - if (sType == SpecialBlockType.ENTRY) { - // storage the store at entry - storeAtEntry = outStores.get(sb); - } else { - assert sType == SpecialBlockType.EXIT - || sType == SpecialBlockType.EXCEPTIONAL_EXIT; - TransferInput input = getInput(sb); - assert input != null : "@AssumeAssertion(nullness): invariant"; - for (Block pred : sb.getPredecessors()) { - propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); - } - } - break; - } - default: - throw new BugInCF("Unexpected block type: " + b.getType()); - } - } + @Override + public @Nullable TransferInput getInput(Block b) { + return inputs.get(b); + } - @Override - public @Nullable TransferInput getInput(Block b) { - return inputs.get(b); - } + @Override + public @Nullable S getEntryStore() { + return storeAtEntry; + } - @Override - public @Nullable S getEntryStore() { - return storeAtEntry; - } + @Override + protected void initFields(ControlFlowGraph cfg) { + super.initFields(cfg); + outStores.clear(); + exceptionStores.clear(); + // storeAtEntry is null before analysis begin + storeAtEntry = null; + } - @Override - protected void initFields(ControlFlowGraph cfg) { - super.initFields(cfg); - outStores.clear(); - exceptionStores.clear(); - // storeAtEntry is null before analysis begin - storeAtEntry = null; + @Override + @RequiresNonNull("cfg") + protected void initInitialInputs() { + worklist.process(cfg); + SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); + SpecialBlock exceptionExitBlock = cfg.getExceptionalExitBlock(); + if (worklist.depthFirstOrder.get(regularExitBlock) == null + && worklist.depthFirstOrder.get(exceptionExitBlock) == null) { + throw new BugInCF( + "regularExitBlock and exceptionExitBlock should never both be null at the same" + + " time."); + } + UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); + List returnNodes = cfg.getReturnNodes(); + assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; + S normalInitialStore = transferFunction.initialNormalExitStore(underlyingAST, returnNodes); + S exceptionalInitialStore = transferFunction.initialExceptionalExitStore(underlyingAST); + // If regularExitBlock or exceptionExitBlock is reachable in the control flow graph, then + // initialize it as a start point of the analysis. + if (worklist.depthFirstOrder.get(regularExitBlock) != null) { + worklist.add(regularExitBlock); + inputs.put(regularExitBlock, new TransferInput<>(null, this, normalInitialStore)); + outStores.put(regularExitBlock, normalInitialStore); + } + if (worklist.depthFirstOrder.get(exceptionExitBlock) != null) { + worklist.add(exceptionExitBlock); + inputs.put(exceptionExitBlock, new TransferInput<>(null, this, exceptionalInitialStore)); + outStores.put(exceptionExitBlock, exceptionalInitialStore); } + if (worklist.isEmpty()) { + throw new BugInCF("The worklist needs at least one exit block as starting point."); + } + if (inputs.isEmpty() || outStores.isEmpty()) { + throw new BugInCF("At least one input and one output store are required."); + } + } - @Override - @RequiresNonNull("cfg") - protected void initInitialInputs() { - worklist.process(cfg); - SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); - SpecialBlock exceptionExitBlock = cfg.getExceptionalExitBlock(); - if (worklist.depthFirstOrder.get(regularExitBlock) == null - && worklist.depthFirstOrder.get(exceptionExitBlock) == null) { - throw new BugInCF( - "regularExitBlock and exceptionExitBlock should never both be null at the same" - + " time."); - } - UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); - List returnNodes = cfg.getReturnNodes(); - assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; - S normalInitialStore = transferFunction.initialNormalExitStore(underlyingAST, returnNodes); - S exceptionalInitialStore = transferFunction.initialExceptionalExitStore(underlyingAST); - // If regularExitBlock or exceptionExitBlock is reachable in the control flow graph, then - // initialize it as a start point of the analysis. - if (worklist.depthFirstOrder.get(regularExitBlock) != null) { - worklist.add(regularExitBlock); - inputs.put(regularExitBlock, new TransferInput<>(null, this, normalInitialStore)); - outStores.put(regularExitBlock, normalInitialStore); - } - if (worklist.depthFirstOrder.get(exceptionExitBlock) != null) { - worklist.add(exceptionExitBlock); - inputs.put( - exceptionExitBlock, new TransferInput<>(null, this, exceptionalInitialStore)); - outStores.put(exceptionExitBlock, exceptionalInitialStore); - } - if (worklist.isEmpty()) { - throw new BugInCF("The worklist needs at least one exit block as starting point."); - } - if (inputs.isEmpty() || outStores.isEmpty()) { - throw new BugInCF("At least one input and one output store are required."); - } + @Override + protected void propagateStoresTo( + Block pred, + @Nullable Node node, + TransferInput currentInput, + FlowRule flowRule, + boolean addToWorklistAgain) { + if (flowRule != FlowRule.EACH_TO_EACH) { + throw new BugInCF( + "Backward analysis always propagates EACH to EACH, because there is no control" + + " flow."); } - @Override - protected void propagateStoresTo( - Block pred, - @Nullable Node node, - TransferInput currentInput, - FlowRule flowRule, - boolean addToWorklistAgain) { - if (flowRule != FlowRule.EACH_TO_EACH) { - throw new BugInCF( - "Backward analysis always propagates EACH to EACH, because there is no control" - + " flow."); - } + addStoreAfter(pred, node, currentInput.getRegularStore(), addToWorklistAgain); + } - addStoreAfter(pred, node, currentInput.getRegularStore(), addToWorklistAgain); + /** + * Add a store after the basic block {@code pred} by merging with the existing stores for that + * location. + * + * @param pred the basic block + * @param node the node of the basic block {@code b} + * @param s the store being added + * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code + * Worklist} + */ + protected void addStoreAfter(Block pred, @Nullable Node node, S s, boolean addBlockToWorklist) { + // If the block pred is an exception block, decide whether the block of passing node is an + // exceptional successor of the block pred + TypeMirror excSuccType = getSuccExceptionType(pred, node); + if (excSuccType != null) { + if (isIgnoredExceptionType(excSuccType)) { + return; + } + // If the block of passing node is an exceptional successor of Block pred, propagate + // store to the exceptionStores. Currently it doesn't track the label of an + // exceptional edge from exception block to its exceptional successors in backward + // direction. Instead, all exception stores of exceptional successors of an + // exception block will merge to one exception store at the exception block + ExceptionBlock ebPred = (ExceptionBlock) pred; + S exceptionStore = exceptionStores.get(ebPred); + S newExceptionStore = (exceptionStore != null) ? exceptionStore.leastUpperBound(s) : s; + if (!newExceptionStore.equals(exceptionStore)) { + exceptionStores.put(ebPred, newExceptionStore); + inputs.put(ebPred, new TransferInput(node, this, newExceptionStore)); + addBlockToWorklist = true; + } + } else { + S predOutStore = getStoreAfter(pred); + S newPredOutStore = (predOutStore != null) ? predOutStore.leastUpperBound(s) : s; + if (!newPredOutStore.equals(predOutStore)) { + outStores.put(pred, newPredOutStore); + inputs.put(pred, new TransferInput<>(node, this, newPredOutStore)); + addBlockToWorklist = true; + } } - - /** - * Add a store after the basic block {@code pred} by merging with the existing stores for that - * location. - * - * @param pred the basic block - * @param node the node of the basic block {@code b} - * @param s the store being added - * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code - * Worklist} - */ - protected void addStoreAfter(Block pred, @Nullable Node node, S s, boolean addBlockToWorklist) { - // If the block pred is an exception block, decide whether the block of passing node is an - // exceptional successor of the block pred - TypeMirror excSuccType = getSuccExceptionType(pred, node); - if (excSuccType != null) { - if (isIgnoredExceptionType(excSuccType)) { - return; - } - // If the block of passing node is an exceptional successor of Block pred, propagate - // store to the exceptionStores. Currently it doesn't track the label of an - // exceptional edge from exception block to its exceptional successors in backward - // direction. Instead, all exception stores of exceptional successors of an - // exception block will merge to one exception store at the exception block - ExceptionBlock ebPred = (ExceptionBlock) pred; - S exceptionStore = exceptionStores.get(ebPred); - S newExceptionStore = (exceptionStore != null) ? exceptionStore.leastUpperBound(s) : s; - if (!newExceptionStore.equals(exceptionStore)) { - exceptionStores.put(ebPred, newExceptionStore); - inputs.put(ebPred, new TransferInput(node, this, newExceptionStore)); - addBlockToWorklist = true; - } - } else { - S predOutStore = getStoreAfter(pred); - S newPredOutStore = (predOutStore != null) ? predOutStore.leastUpperBound(s) : s; - if (!newPredOutStore.equals(predOutStore)) { - outStores.put(pred, newPredOutStore); - inputs.put(pred, new TransferInput<>(node, this, newPredOutStore)); - addBlockToWorklist = true; - } - } - if (addBlockToWorklist) { - addToWorklist(pred); - } + if (addBlockToWorklist) { + addToWorklist(pred); } + } - /** - * Checks if the block for a node is an exceptional successor of a predecessor block, and if so, - * returns the exception type for the control-flow edge. - * - * @param pred the predecessor block - * @param node the successor node - * @return the exception type leading to a control flow edge from {@code pred} to the block for - * {@code node}, if it exists; {@code null} otherwise - */ - @SuppressWarnings("interning:not.interned") // Block equality - private @Nullable TypeMirror getSuccExceptionType(Block pred, @Nullable Node node) { - if (!(pred instanceof ExceptionBlock) || node == null) { - return null; - } - Block block = node.getBlock(); - if (block == null) { - return null; - } - Map> exceptionalSuccessors = - ((ExceptionBlock) pred).getExceptionalSuccessors(); - for (Map.Entry> excTypeEntry : exceptionalSuccessors.entrySet()) { - for (Block excSuccBlock : excTypeEntry.getValue()) { - if (excSuccBlock == block) { - return excTypeEntry.getKey(); - } - } + /** + * Checks if the block for a node is an exceptional successor of a predecessor block, and if so, + * returns the exception type for the control-flow edge. + * + * @param pred the predecessor block + * @param node the successor node + * @return the exception type leading to a control flow edge from {@code pred} to the block for + * {@code node}, if it exists; {@code null} otherwise + */ + @SuppressWarnings("interning:not.interned") // Block equality + private @Nullable TypeMirror getSuccExceptionType(Block pred, @Nullable Node node) { + if (!(pred instanceof ExceptionBlock) || node == null) { + return null; + } + Block block = node.getBlock(); + if (block == null) { + return null; + } + Map> exceptionalSuccessors = + ((ExceptionBlock) pred).getExceptionalSuccessors(); + for (Map.Entry> excTypeEntry : exceptionalSuccessors.entrySet()) { + for (Block excSuccBlock : excTypeEntry.getValue()) { + if (excSuccBlock == block) { + return excTypeEntry.getKey(); } - return null; + } } + return null; + } - /** - * Returns the store corresponding to the location right after the basic block {@code b}. - * - * @param b the given block - * @return the store right after the given block - */ - protected @Nullable S getStoreAfter(Block b) { - return readFromStore(outStores, b); - } + /** + * Returns the store corresponding to the location right after the basic block {@code b}. + * + * @param b the given block + * @return the store right after the given block + */ + protected @Nullable S getStoreAfter(Block b) { + return readFromStore(outStores, b); + } - @Override - public S runAnalysisFor( - @FindDistinct Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput blockTransferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches) { - Block block = node.getBlock(); - assert block != null : "@AssumeAssertion(nullness): invariant"; - Node oldCurrentNode = currentNode; - if (isRunning) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - return currentInput.getRegularStore(); - } - isRunning = true; - try { - switch (block.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rBlock = (RegularBlock) block; - // Apply transfer function to contents until we found the node we are - // looking for. - TransferInput store = blockTransferInput; - List nodeList = rBlock.getNodes(); - ListIterator reverseIter = nodeList.listIterator(nodeList.size()); - while (reverseIter.hasPrevious()) { - Node n = reverseIter.previous(); - setCurrentNode(n); - if (n == node && preOrPost == Analysis.BeforeOrAfter.AFTER) { - return store.getRegularStore(); - } - // Copy the store to avoid changing other blocks' transfer inputs in - // {@link #inputs} - TransferResult transferResult = - callTransferFunction(n, store.copy()); - if (n == node) { - return transferResult.getRegularStore(); - } - store = new TransferInput<>(n, this, transferResult); - } - throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); - } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) block; - if (eb.getNode() != node) { - throw new BugInCF( - "Node should be equal to eb.getNode(). But get: node: " - + node - + "\teb.getNode(): " - + eb.getNode()); - } - if (preOrPost == Analysis.BeforeOrAfter.AFTER) { - return blockTransferInput.getRegularStore(); - } - setCurrentNode(node); - // Copy the store to avoid changing other blocks' transfer inputs in {@link - // #inputs} - TransferResult transferResult = - callTransferFunction(node, blockTransferInput.copy()); - // Merge transfer result with the exception store of this exceptional block - S exceptionStore = exceptionStores.get(eb); - return exceptionStore == null - ? transferResult.getRegularStore() - : transferResult.getRegularStore().leastUpperBound(exceptionStore); - } - default: - // Only regular blocks and exceptional blocks can hold nodes. - throw new BugInCF("Unexpected block type: " + block.getType()); + @Override + public S runAnalysisFor( + @FindDistinct Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput blockTransferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches) { + Block block = node.getBlock(); + assert block != null : "@AssumeAssertion(nullness): invariant"; + Node oldCurrentNode = currentNode; + if (isRunning) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + return currentInput.getRegularStore(); + } + isRunning = true; + try { + switch (block.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rBlock = (RegularBlock) block; + // Apply transfer function to contents until we found the node we are + // looking for. + TransferInput store = blockTransferInput; + List nodeList = rBlock.getNodes(); + ListIterator reverseIter = nodeList.listIterator(nodeList.size()); + while (reverseIter.hasPrevious()) { + Node n = reverseIter.previous(); + setCurrentNode(n); + if (n == node && preOrPost == Analysis.BeforeOrAfter.AFTER) { + return store.getRegularStore(); + } + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} + TransferResult transferResult = callTransferFunction(n, store.copy()); + if (n == node) { + return transferResult.getRegularStore(); + } + store = new TransferInput<>(n, this, transferResult); } + throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); + } + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) block; + if (eb.getNode() != node) { + throw new BugInCF( + "Node should be equal to eb.getNode(). But get: node: " + + node + + "\teb.getNode(): " + + eb.getNode()); + } + if (preOrPost == Analysis.BeforeOrAfter.AFTER) { + return blockTransferInput.getRegularStore(); + } + setCurrentNode(node); + // Copy the store to avoid changing other blocks' transfer inputs in {@link + // #inputs} + TransferResult transferResult = + callTransferFunction(node, blockTransferInput.copy()); + // Merge transfer result with the exception store of this exceptional block + S exceptionStore = exceptionStores.get(eb); + return exceptionStore == null + ? transferResult.getRegularStore() + : transferResult.getRegularStore().leastUpperBound(exceptionStore); + } + default: + // Only regular blocks and exceptional blocks can hold nodes. + throw new BugInCF("Unexpected block type: " + block.getType()); + } - } finally { - setCurrentNode(oldCurrentNode); - isRunning = false; - } + } finally { + setCurrentNode(oldCurrentNode); + isRunning = false; } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java index e1c3e070172..28c0bfa9c8c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java @@ -1,11 +1,10 @@ package org.checkerframework.dataflow.analysis; +import java.util.List; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.checkerframework.dataflow.qual.SideEffectFree; -import java.util.List; - /** * Interface of a backward transfer function for the abstract interpretation used for the backward * flow analysis. @@ -18,26 +17,26 @@ * @param the store type used in the analysis */ public interface BackwardTransferFunction, S extends Store> - extends TransferFunction { + extends TransferFunction { - /** - * Returns the initial store that should be used at the normal exit block. - * - * @param underlyingAST the underlying AST of the given control flow graph - * @param returnNodes the return nodes of the given control flow graph (an empty list if the - * underlying AST is not a method) - * @return the initial store that should be used at the normal exit block - */ - @SideEffectFree - S initialNormalExitStore(UnderlyingAST underlyingAST, List returnNodes); + /** + * Returns the initial store that should be used at the normal exit block. + * + * @param underlyingAST the underlying AST of the given control flow graph + * @param returnNodes the return nodes of the given control flow graph (an empty list if the + * underlying AST is not a method) + * @return the initial store that should be used at the normal exit block + */ + @SideEffectFree + S initialNormalExitStore(UnderlyingAST underlyingAST, List returnNodes); - /** - * Returns the initial store that should be used at the exceptional exit block or given the - * underlying AST of a control flow graph. - * - * @param underlyingAST the underlying AST of the given control flow graph - * @return the initial store that should be used at the exceptional exit block - */ - @SideEffectFree - S initialExceptionalExitStore(UnderlyingAST underlyingAST); + /** + * Returns the initial store that should be used at the exceptional exit block or given the + * underlying AST of a control flow graph. + * + * @param underlyingAST the underlying AST of the given control flow graph + * @return the initial store that should be used at the exceptional exit block + */ + @SideEffectFree + S initialExceptionalExitStore(UnderlyingAST underlyingAST); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java index 2cda1f9b1d8..59ba8fe6683 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java @@ -1,12 +1,10 @@ package org.checkerframework.dataflow.analysis; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.StringsPlume; - import java.util.Map; import java.util.StringJoiner; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.StringsPlume; /** * Implementation of a {@link TransferResult} with two non-exceptional stores. The 'then' store @@ -19,139 +17,138 @@ * @param the store type used in the analysis */ public class ConditionalTransferResult, S extends Store> - extends TransferResult { - - /** Whether the store changed. */ - private final boolean storeChanged; - - /** The 'then' result store. */ - protected final S thenStore; - - /** The 'else' result store. */ - protected final S elseStore; - - /** - * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, - * using {@code null} for {@link #exceptionalStores}. - * - *

Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no - * special handling is necessary and the store before the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. - * - *

Aliasing: {@code thenStore} and {@code elseStore} are not allowed to be used - * anywhere outside of this class (including use through aliases). Complete control over the - * objects is transferred to this class. - * - * @param value the abstract value produced by the transfer function - * @param thenStore 'then' result store - * @param elseStore 'else' result store - * @param storeChanged whether the store changed - * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) - */ - public ConditionalTransferResult( - @Nullable V value, S thenStore, S elseStore, boolean storeChanged) { - this(value, thenStore, elseStore, null, storeChanged); - } - - /** - * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, - * using {@code false} for whether the store changed and {@code null} for {@link - * #exceptionalStores}. - * - * @param value the abstract value produced by the transfer function - * @param thenStore {@link #thenStore} - * @param elseStore {@link #elseStore} - * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) - */ - public ConditionalTransferResult(@Nullable V value, S thenStore, S elseStore) { - this(value, thenStore, elseStore, false); - } - - /** - * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, - * using {@code false} for the {@code storeChanged} formal parameter. - * - * @param value the abstract value produced by the transfer function - * @param thenStore {@link #thenStore} - * @param elseStore {@link #elseStore} - * @param exceptionalStores {@link #exceptionalStores} - * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) - */ - public ConditionalTransferResult( - V value, S thenStore, S elseStore, @Nullable Map exceptionalStores) { - this(value, thenStore, elseStore, exceptionalStores, false); - } - - /** - * Create a {@code ConditionalTransferResult} with {@code thenStore} as the resulting store if - * the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} evaluates to {@code - * true} and {@code elseStore} otherwise. - * - *

Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding - * store in {@code exceptionalStores} is used. If no exception is found in {@code - * exceptionalStores}, then it is assumed that no special handling is necessary and the store - * before the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed - * along any exceptional edge. - * - *

Aliasing: {@code thenStore}, {@code elseStore}, and any store in {@code - * exceptionalStores} are not allowed to be used anywhere outside of this class (including use - * through aliases). Complete control over the objects is transferred to this class. - * - * @param value the abstract value produced by the transfer function - * @param thenStore {@link #thenStore} - * @param elseStore {@link #elseStore} - * @param exceptionalStores {@link #exceptionalStores} - * @param storeChanged whether the store changed; see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - */ - public ConditionalTransferResult( - @Nullable V value, - S thenStore, - S elseStore, - @Nullable Map exceptionalStores, - boolean storeChanged) { - super(value, exceptionalStores); - this.thenStore = thenStore; - this.elseStore = elseStore; - this.storeChanged = storeChanged; - } - - /** The regular result store. */ - @Override - public S getRegularStore() { - return thenStore.leastUpperBound(elseStore); - } - - @Override - public S getThenStore() { - return thenStore; - } - - @Override - public S getElseStore() { - return elseStore; - } - - @Override - public boolean containsTwoStores() { - return true; - } - - @Override - public String toString() { - StringJoiner result = new StringJoiner(System.lineSeparator()); - result.add("RegularTransferResult("); - result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); - result.add(" thenStore = " + StringsPlume.indentLinesExceptFirst(2, thenStore)); - result.add(" elseStore = " + StringsPlume.indentLinesExceptFirst(2, elseStore)); - result.add(")"); - return result.toString(); - } - - @Override - public boolean storeChanged() { - return storeChanged; - } + extends TransferResult { + + /** Whether the store changed. */ + private final boolean storeChanged; + + /** The 'then' result store. */ + protected final S thenStore; + + /** The 'else' result store. */ + protected final S elseStore; + + /** + * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, + * using {@code null} for {@link #exceptionalStores}. + * + *

Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no + * special handling is necessary and the store before the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + *

Aliasing: {@code thenStore} and {@code elseStore} are not allowed to be used + * anywhere outside of this class (including use through aliases). Complete control over the + * objects is transferred to this class. + * + * @param value the abstract value produced by the transfer function + * @param thenStore 'then' result store + * @param elseStore 'else' result store + * @param storeChanged whether the store changed + * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) + */ + public ConditionalTransferResult( + @Nullable V value, S thenStore, S elseStore, boolean storeChanged) { + this(value, thenStore, elseStore, null, storeChanged); + } + + /** + * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, + * using {@code false} for whether the store changed and {@code null} for {@link + * #exceptionalStores}. + * + * @param value the abstract value produced by the transfer function + * @param thenStore {@link #thenStore} + * @param elseStore {@link #elseStore} + * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) + */ + public ConditionalTransferResult(@Nullable V value, S thenStore, S elseStore) { + this(value, thenStore, elseStore, false); + } + + /** + * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, + * using {@code false} for the {@code storeChanged} formal parameter. + * + * @param value the abstract value produced by the transfer function + * @param thenStore {@link #thenStore} + * @param elseStore {@link #elseStore} + * @param exceptionalStores {@link #exceptionalStores} + * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) + */ + public ConditionalTransferResult( + V value, S thenStore, S elseStore, @Nullable Map exceptionalStores) { + this(value, thenStore, elseStore, exceptionalStores, false); + } + + /** + * Create a {@code ConditionalTransferResult} with {@code thenStore} as the resulting store if the + * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} evaluates to {@code true} and + * {@code elseStore} otherwise. + * + *

Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding store + * in {@code exceptionalStores} is used. If no exception is found in {@code exceptionalStores}, + * then it is assumed that no special handling is necessary and the store before the corresponding + * {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + *

Aliasing: {@code thenStore}, {@code elseStore}, and any store in {@code + * exceptionalStores} are not allowed to be used anywhere outside of this class (including use + * through aliases). Complete control over the objects is transferred to this class. + * + * @param value the abstract value produced by the transfer function + * @param thenStore {@link #thenStore} + * @param elseStore {@link #elseStore} + * @param exceptionalStores {@link #exceptionalStores} + * @param storeChanged whether the store changed; see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + */ + public ConditionalTransferResult( + @Nullable V value, + S thenStore, + S elseStore, + @Nullable Map exceptionalStores, + boolean storeChanged) { + super(value, exceptionalStores); + this.thenStore = thenStore; + this.elseStore = elseStore; + this.storeChanged = storeChanged; + } + + /** The regular result store. */ + @Override + public S getRegularStore() { + return thenStore.leastUpperBound(elseStore); + } + + @Override + public S getThenStore() { + return thenStore; + } + + @Override + public S getElseStore() { + return elseStore; + } + + @Override + public boolean containsTwoStores() { + return true; + } + + @Override + public String toString() { + StringJoiner result = new StringJoiner(System.lineSeparator()); + result.add("RegularTransferResult("); + result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); + result.add(" thenStore = " + StringsPlume.indentLinesExceptFirst(2, thenStore)); + result.add(" elseStore = " + StringsPlume.indentLinesExceptFirst(2, elseStore)); + result.add(")"); + return result.toString(); + } + + @Override + public boolean storeChanged() { + return storeChanged; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java index 9e60cc62565..68d81e27f2d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java @@ -1,11 +1,10 @@ package org.checkerframework.dataflow.analysis; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.plumelib.util.IPair; -import java.util.List; - /** * This interface defines a forward analysis, given a control flow graph and a forward transfer * function. @@ -15,17 +14,15 @@ * @param the forward transfer function type that is used to approximated runtime behavior */ public interface ForwardAnalysis< - V extends AbstractValue, - S extends Store, - T extends ForwardTransferFunction> - extends Analysis { + V extends AbstractValue, S extends Store, T extends ForwardTransferFunction> + extends Analysis { - /** - * Get stores at return statements. These stores are transfer results at return node. Thus for a - * forward analysis, these stores contain the analyzed flow information from entry nodes to - * return nodes. - * - * @return the transfer results for each return node in the CFG - */ - List>> getReturnStatementStores(); + /** + * Get stores at return statements. These stores are transfer results at return node. Thus for a + * forward analysis, these stores contain the analyzed flow information from entry nodes to return + * nodes. + * + * @return the transfer results for each return node in the CFG + */ + List>> getReturnStatementStores(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java index a3fba8c5864..4c939757a52 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java @@ -2,7 +2,12 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MethodTree; - +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -23,14 +28,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; - /** * An implementation of a forward analysis to solve a org.checkerframework.dataflow problem given a * control flow graph and a forward transfer function. @@ -40,564 +37,522 @@ * @param the transfer function type that is used to approximate runtime behavior */ public class ForwardAnalysisImpl< - V extends AbstractValue, - S extends Store, - T extends ForwardTransferFunction> - extends AbstractAnalysis implements ForwardAnalysis { + V extends AbstractValue, S extends Store, T extends ForwardTransferFunction> + extends AbstractAnalysis implements ForwardAnalysis { + + /** + * Number of times each block has been analyzed since the last time widening was applied. Null if + * maxCountBeforeWidening is -1, which implies widening isn't used for this analysis. + */ + protected final @Nullable IdentityHashMap blockCount; + + /** + * Number of times a block can be analyzed before widening. -1 implies that widening shouldn't be + * used. + */ + protected final int maxCountBeforeWidening; - /** - * Number of times each block has been analyzed since the last time widening was applied. Null - * if maxCountBeforeWidening is -1, which implies widening isn't used for this analysis. - */ - protected final @Nullable IdentityHashMap blockCount; + /** Then stores before every basic block (assumed to be 'no information' if not present). */ + protected final IdentityHashMap thenStores; - /** - * Number of times a block can be analyzed before widening. -1 implies that widening shouldn't - * be used. - */ - protected final int maxCountBeforeWidening; + /** Else stores before every basic block (assumed to be 'no information' if not present). */ + protected final IdentityHashMap elseStores; - /** Then stores before every basic block (assumed to be 'no information' if not present). */ - protected final IdentityHashMap thenStores; + /** The stores after every return statement. */ + protected final IdentityHashMap> storesAtReturnStatements; - /** Else stores before every basic block (assumed to be 'no information' if not present). */ - protected final IdentityHashMap elseStores; + // `@code`, not `@link`, because dataflow module doesn't depend on framework module. + /** + * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a + * control flow graph. When using this constructor, the transfer function is set later by the + * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. + * + * @param maxCountBeforeWidening number of times a block can be analyzed before widening + */ + public ForwardAnalysisImpl(int maxCountBeforeWidening) { + super(Direction.FORWARD); + this.maxCountBeforeWidening = maxCountBeforeWidening; + this.blockCount = maxCountBeforeWidening == -1 ? null : new IdentityHashMap<>(); + this.thenStores = new IdentityHashMap<>(); + this.elseStores = new IdentityHashMap<>(); + this.storesAtReturnStatements = new IdentityHashMap<>(); + } - /** The stores after every return statement. */ - protected final IdentityHashMap> storesAtReturnStatements; + /** + * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a + * control flow graph given a transfer function. + * + * @param transferFunction the transfer function + */ + public ForwardAnalysisImpl(T transferFunction) { + this(-1); + this.transferFunction = transferFunction; + } - // `@code`, not `@link`, because dataflow module doesn't depend on framework module. - /** - * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a - * control flow graph. When using this constructor, the transfer function is set later by the - * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. - * - * @param maxCountBeforeWidening number of times a block can be analyzed before widening - */ - public ForwardAnalysisImpl(int maxCountBeforeWidening) { - super(Direction.FORWARD); - this.maxCountBeforeWidening = maxCountBeforeWidening; - this.blockCount = maxCountBeforeWidening == -1 ? null : new IdentityHashMap<>(); - this.thenStores = new IdentityHashMap<>(); - this.elseStores = new IdentityHashMap<>(); - this.storesAtReturnStatements = new IdentityHashMap<>(); + @Override + public void performAnalysis(ControlFlowGraph cfg) { + if (isRunning) { + throw new BugInCF( + "ForwardAnalysisImpl::performAnalysis() shouldn't be called when the analysis" + + " is running."); } + isRunning = true; - /** - * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a - * control flow graph given a transfer function. - * - * @param transferFunction the transfer function - */ - public ForwardAnalysisImpl(T transferFunction) { - this(-1); - this.transferFunction = transferFunction; + try { + init(cfg); + while (!worklist.isEmpty()) { + Block b = worklist.poll(); + performAnalysisBlock(b); + } + } finally { + assert isRunning; + // In case performAnalysisBlock crashed, reset isRunning to false. + isRunning = false; } + } - @Override - public void performAnalysis(ControlFlowGraph cfg) { - if (isRunning) { - throw new BugInCF( - "ForwardAnalysisImpl::performAnalysis() shouldn't be called when the analysis" - + " is running."); + @Override + public void performAnalysisBlock(Block b) { + switch (b.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rb = (RegularBlock) b; + // Apply transfer function to contents + TransferInput inputBefore = getInputBefore(rb); + assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputBefore.copy(); + Node lastNode = null; + boolean addToWorklistAgain = false; + for (Node n : rb.getNodes()) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + TransferResult transferResult = callTransferFunction(n, currentInput); + addToWorklistAgain |= updateNodeValues(n, transferResult); + currentInput = new TransferInput<>(n, this, transferResult); + lastNode = n; + } + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + // Loop will run at least once, making transferResult non-null + // Propagate store to successors + Block succ = rb.getSuccessor(); + assert succ != null + : "@AssumeAssertion(nullness): regular basic block without" + + " non-exceptional successor unexpected"; + propagateStoresTo(succ, lastNode, currentInput, rb.getFlowRule(), addToWorklistAgain); + break; } - isRunning = true; - - try { - init(cfg); - while (!worklist.isEmpty()) { - Block b = worklist.poll(); - performAnalysisBlock(b); + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) b; + // Apply transfer function to content + TransferInput inputBefore = getInputBefore(eb); + assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputBefore.copy(); + Node node = eb.getNode(); + TransferResult transferResult = callTransferFunction(node, currentInput); + boolean addToWorklistAgain = updateNodeValues(node, transferResult); + // Propagate store to successor + Block succ = eb.getSuccessor(); + if (succ != null) { + currentInput = new TransferInput<>(node, this, transferResult); + propagateStoresTo(succ, node, currentInput, eb.getFlowRule(), addToWorklistAgain); + } + // Propagate store to exceptional successors + for (Map.Entry> e : eb.getExceptionalSuccessors().entrySet()) { + TypeMirror cause = e.getKey(); + if (isIgnoredExceptionType(cause)) { + continue; + } + S exceptionalStore = transferResult.getExceptionalStore(cause); + if (exceptionalStore != null) { + for (Block exceptionSucc : e.getValue()) { + addStoreBefore( + exceptionSucc, node, exceptionalStore, Store.Kind.BOTH, addToWorklistAgain); + } + } else { + for (Block exceptionSucc : e.getValue()) { + addStoreBefore( + exceptionSucc, + node, + inputBefore.copy().getRegularStore(), + Store.Kind.BOTH, + addToWorklistAgain); + } } - } finally { - assert isRunning; - // In case performAnalysisBlock crashed, reset isRunning to false. - isRunning = false; + } + break; } - } - - @Override - public void performAnalysisBlock(Block b) { - switch (b.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rb = (RegularBlock) b; - // Apply transfer function to contents - TransferInput inputBefore = getInputBefore(rb); - assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputBefore.copy(); - Node lastNode = null; - boolean addToWorklistAgain = false; - for (Node n : rb.getNodes()) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - TransferResult transferResult = callTransferFunction(n, currentInput); - addToWorklistAgain |= updateNodeValues(n, transferResult); - currentInput = new TransferInput<>(n, this, transferResult); - lastNode = n; - } - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - // Loop will run at least once, making transferResult non-null - // Propagate store to successors - Block succ = rb.getSuccessor(); - assert succ != null - : "@AssumeAssertion(nullness): regular basic block without" - + " non-exceptional successor unexpected"; - propagateStoresTo( - succ, lastNode, currentInput, rb.getFlowRule(), addToWorklistAgain); - break; - } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) b; - // Apply transfer function to content - TransferInput inputBefore = getInputBefore(eb); - assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputBefore.copy(); - Node node = eb.getNode(); - TransferResult transferResult = callTransferFunction(node, currentInput); - boolean addToWorklistAgain = updateNodeValues(node, transferResult); - // Propagate store to successor - Block succ = eb.getSuccessor(); - if (succ != null) { - currentInput = new TransferInput<>(node, this, transferResult); - propagateStoresTo( - succ, node, currentInput, eb.getFlowRule(), addToWorklistAgain); - } - // Propagate store to exceptional successors - for (Map.Entry> e : - eb.getExceptionalSuccessors().entrySet()) { - TypeMirror cause = e.getKey(); - if (isIgnoredExceptionType(cause)) { - continue; - } - S exceptionalStore = transferResult.getExceptionalStore(cause); - if (exceptionalStore != null) { - for (Block exceptionSucc : e.getValue()) { - addStoreBefore( - exceptionSucc, - node, - exceptionalStore, - Store.Kind.BOTH, - addToWorklistAgain); - } - } else { - for (Block exceptionSucc : e.getValue()) { - addStoreBefore( - exceptionSucc, - node, - inputBefore.copy().getRegularStore(), - Store.Kind.BOTH, - addToWorklistAgain); - } - } - } - break; - } - case CONDITIONAL_BLOCK: - { - ConditionalBlock cb = (ConditionalBlock) b; - // Get store before - TransferInput inputBefore = getInputBefore(cb); - assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; - TransferInput input = inputBefore.copy(); - // Propagate store to successor - Block thenSucc = cb.getThenSuccessor(); - Block elseSucc = cb.getElseSuccessor(); - propagateStoresTo(thenSucc, null, input, cb.getThenFlowRule(), false); - propagateStoresTo(elseSucc, null, input, cb.getElseFlowRule(), false); - break; - } - case SPECIAL_BLOCK: - { - // Special basic blocks are empty and cannot throw exceptions, - // thus there is no need to perform any analysis. - SpecialBlock sb = (SpecialBlock) b; - Block succ = sb.getSuccessor(); - if (succ != null) { - TransferInput input = getInputBefore(b); - assert input != null : "@AssumeAssertion(nullness): invariant"; - propagateStoresTo(succ, null, input, sb.getFlowRule(), false); - } - break; - } - default: - throw new BugInCF("Unexpected block type: " + b.getType()); + case CONDITIONAL_BLOCK: + { + ConditionalBlock cb = (ConditionalBlock) b; + // Get store before + TransferInput inputBefore = getInputBefore(cb); + assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; + TransferInput input = inputBefore.copy(); + // Propagate store to successor + Block thenSucc = cb.getThenSuccessor(); + Block elseSucc = cb.getElseSuccessor(); + propagateStoresTo(thenSucc, null, input, cb.getThenFlowRule(), false); + propagateStoresTo(elseSucc, null, input, cb.getElseFlowRule(), false); + break; } + case SPECIAL_BLOCK: + { + // Special basic blocks are empty and cannot throw exceptions, + // thus there is no need to perform any analysis. + SpecialBlock sb = (SpecialBlock) b; + Block succ = sb.getSuccessor(); + if (succ != null) { + TransferInput input = getInputBefore(b); + assert input != null : "@AssumeAssertion(nullness): invariant"; + propagateStoresTo(succ, null, input, sb.getFlowRule(), false); + } + break; + } + default: + throw new BugInCF("Unexpected block type: " + b.getType()); } + } - @Override - public @Nullable TransferInput getInput(Block b) { - return getInputBefore(b); - } + @Override + public @Nullable TransferInput getInput(Block b) { + return getInputBefore(b); + } - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public List>> getReturnStatementStores() { - return CollectionsPlume - .>>mapList( - returnNode -> - IPair.of(returnNode, storesAtReturnStatements.get(returnNode)), - cfg.getReturnNodes()); - } + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public List>> getReturnStatementStores() { + return CollectionsPlume.>>mapList( + returnNode -> IPair.of(returnNode, storesAtReturnStatements.get(returnNode)), + cfg.getReturnNodes()); + } - @Override - public S runAnalysisFor( - @FindDistinct Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput blockTransferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches) { - Block block = node.getBlock(); - assert block != null : "@AssumeAssertion(nullness): invariant"; - Node oldCurrentNode = currentNode; + @Override + public S runAnalysisFor( + @FindDistinct Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput blockTransferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches) { + Block block = node.getBlock(); + assert block != null : "@AssumeAssertion(nullness): invariant"; + Node oldCurrentNode = currentNode; - // Prepare cache - IdentityHashMap> cache; - if (analysisCaches != null) { - cache = - analysisCaches.computeIfAbsent( - blockTransferInput, __ -> new IdentityHashMap<>()); - } else { - cache = null; - } + // Prepare cache + IdentityHashMap> cache; + if (analysisCaches != null) { + cache = analysisCaches.computeIfAbsent(blockTransferInput, __ -> new IdentityHashMap<>()); + } else { + cache = null; + } - if (isRunning) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - return currentInput.getRegularStore(); - } - setNodeValues(nodeValues); - isRunning = true; - try { - switch (block.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rb = (RegularBlock) block; - // Apply transfer function to contents until we found the node we are - // looking for. - TransferInput store = blockTransferInput; - TransferResult transferResult; - for (Node n : rb.getNodes()) { - setCurrentNode(n); - if (n == node && preOrPost == Analysis.BeforeOrAfter.BEFORE) { - return store.getRegularStore(); - } - if (cache != null && cache.containsKey(n)) { - transferResult = cache.get(n); - } else { - // Copy the store to avoid changing other blocks' transfer inputs in - // {@link #inputs} - transferResult = callTransferFunction(n, store.copy()); - if (cache != null) { - cache.put(n, transferResult); - } - } - if (n == node) { - return transferResult.getRegularStore(); - } - store = new TransferInput<>(n, this, transferResult); - } - throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); - } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) block; - // Apply the transfer function to content - if (eb.getNode() != node) { - throw new BugInCF( - "Node should be equal to eb.getNode(). But get: node: " - + node - + "\teb.getNode(): " - + eb.getNode()); - } - if (preOrPost == Analysis.BeforeOrAfter.BEFORE) { - return blockTransferInput.getRegularStore(); - } - setCurrentNode(node); - // Copy the store to avoid changing other blocks' transfer inputs in {@link - // #inputs} - TransferResult transferResult; - if (cache != null && cache.containsKey(node)) { - transferResult = cache.get(node); - } else { - // Copy the store to avoid changing other blocks' transfer inputs in - // {@link #inputs} - transferResult = callTransferFunction(node, blockTransferInput.copy()); - if (cache != null) { - cache.put(node, transferResult); - } - } - return transferResult.getRegularStore(); - } - default: - // Only regular blocks and exceptional blocks can hold nodes. - throw new BugInCF("Unexpected block type: " + block.getType()); + if (isRunning) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + return currentInput.getRegularStore(); + } + setNodeValues(nodeValues); + isRunning = true; + try { + switch (block.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rb = (RegularBlock) block; + // Apply transfer function to contents until we found the node we are + // looking for. + TransferInput store = blockTransferInput; + TransferResult transferResult; + for (Node n : rb.getNodes()) { + setCurrentNode(n); + if (n == node && preOrPost == Analysis.BeforeOrAfter.BEFORE) { + return store.getRegularStore(); + } + if (cache != null && cache.containsKey(n)) { + transferResult = cache.get(n); + } else { + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} + transferResult = callTransferFunction(n, store.copy()); + if (cache != null) { + cache.put(n, transferResult); + } + } + if (n == node) { + return transferResult.getRegularStore(); + } + store = new TransferInput<>(n, this, transferResult); } - } finally { - setCurrentNode(oldCurrentNode); - isRunning = false; - } + throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); + } + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) block; + // Apply the transfer function to content + if (eb.getNode() != node) { + throw new BugInCF( + "Node should be equal to eb.getNode(). But get: node: " + + node + + "\teb.getNode(): " + + eb.getNode()); + } + if (preOrPost == Analysis.BeforeOrAfter.BEFORE) { + return blockTransferInput.getRegularStore(); + } + setCurrentNode(node); + // Copy the store to avoid changing other blocks' transfer inputs in {@link + // #inputs} + TransferResult transferResult; + if (cache != null && cache.containsKey(node)) { + transferResult = cache.get(node); + } else { + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} + transferResult = callTransferFunction(node, blockTransferInput.copy()); + if (cache != null) { + cache.put(node, transferResult); + } + } + return transferResult.getRegularStore(); + } + default: + // Only regular blocks and exceptional blocks can hold nodes. + throw new BugInCF("Unexpected block type: " + block.getType()); + } + } finally { + setCurrentNode(oldCurrentNode); + isRunning = false; } + } - @Override - protected void initFields(ControlFlowGraph cfg) { - thenStores.clear(); - elseStores.clear(); - if (blockCount != null) { - blockCount.clear(); - } - storesAtReturnStatements.clear(); - super.initFields(cfg); + @Override + protected void initFields(ControlFlowGraph cfg) { + thenStores.clear(); + elseStores.clear(); + if (blockCount != null) { + blockCount.clear(); } + storesAtReturnStatements.clear(); + super.initFields(cfg); + } - @Override - @RequiresNonNull("cfg") - protected void initInitialInputs() { - worklist.process(cfg); - Block entry = cfg.getEntryBlock(); - worklist.add(entry); - UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); - List parameters = getParameters(underlyingAST); - assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; - S initialStore = transferFunction.initialStore(underlyingAST, parameters); - thenStores.put(entry, initialStore); - elseStores.put(entry, initialStore); - inputs.put(entry, new TransferInput<>(null, this, initialStore)); - } + @Override + @RequiresNonNull("cfg") + protected void initInitialInputs() { + worklist.process(cfg); + Block entry = cfg.getEntryBlock(); + worklist.add(entry); + UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); + List parameters = getParameters(underlyingAST); + assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; + S initialStore = transferFunction.initialStore(underlyingAST, parameters); + thenStores.put(entry, initialStore); + elseStores.put(entry, initialStore); + inputs.put(entry, new TransferInput<>(null, this, initialStore)); + } - /** - * Returns the formal parameters for a method. - * - * @param underlyingAST the AST for the method - * @return the formal parameters for the method - */ - @SideEffectFree - private List getParameters(UnderlyingAST underlyingAST) { - switch (underlyingAST.getKind()) { - case METHOD: - MethodTree tree = ((CFGMethod) underlyingAST).getMethod(); - // TODO: document that LocalVariableNode has no block that it belongs to - return CollectionsPlume.mapList(LocalVariableNode::new, tree.getParameters()); - case LAMBDA: - LambdaExpressionTree lambda = ((CFGLambda) underlyingAST).getLambdaTree(); - // TODO: document that LocalVariableNode has no block that it belongs to - return CollectionsPlume.mapList(LocalVariableNode::new, lambda.getParameters()); - default: - return Collections.emptyList(); - } + /** + * Returns the formal parameters for a method. + * + * @param underlyingAST the AST for the method + * @return the formal parameters for the method + */ + @SideEffectFree + private List getParameters(UnderlyingAST underlyingAST) { + switch (underlyingAST.getKind()) { + case METHOD: + MethodTree tree = ((CFGMethod) underlyingAST).getMethod(); + // TODO: document that LocalVariableNode has no block that it belongs to + return CollectionsPlume.mapList(LocalVariableNode::new, tree.getParameters()); + case LAMBDA: + LambdaExpressionTree lambda = ((CFGLambda) underlyingAST).getLambdaTree(); + // TODO: document that LocalVariableNode has no block that it belongs to + return CollectionsPlume.mapList(LocalVariableNode::new, lambda.getParameters()); + default: + return Collections.emptyList(); } + } - @Override - protected TransferResult callTransferFunction(Node node, TransferInput input) { - TransferResult transferResult = super.callTransferFunction(node, input); + @Override + protected TransferResult callTransferFunction(Node node, TransferInput input) { + TransferResult transferResult = super.callTransferFunction(node, input); - if (node instanceof ReturnNode) { - // Save a copy of the store to later check if some property holds at a given return - // statement. - storesAtReturnStatements.put((ReturnNode) node, transferResult); - } - return transferResult; + if (node instanceof ReturnNode) { + // Save a copy of the store to later check if some property holds at a given return + // statement. + storesAtReturnStatements.put((ReturnNode) node, transferResult); } + return transferResult; + } - @Override - protected void propagateStoresTo( - Block succ, - @Nullable Node node, - TransferInput currentInput, - Store.FlowRule flowRule, - boolean addToWorklistAgain) { - switch (flowRule) { - case EACH_TO_EACH: - if (currentInput.containsTwoStores()) { - addStoreBefore( - succ, - node, - currentInput.getThenStore(), - Store.Kind.THEN, - addToWorklistAgain); - addStoreBefore( - succ, - node, - currentInput.getElseStore(), - Store.Kind.ELSE, - addToWorklistAgain); - } else { - addStoreBefore( - succ, - node, - currentInput.getRegularStore(), - Store.Kind.BOTH, - addToWorklistAgain); - } - break; - case THEN_TO_BOTH: - addStoreBefore( - succ, - node, - currentInput.getThenStore(), - Store.Kind.BOTH, - addToWorklistAgain); - break; - case ELSE_TO_BOTH: - addStoreBefore( - succ, - node, - currentInput.getElseStore(), - Store.Kind.BOTH, - addToWorklistAgain); - break; - case THEN_TO_THEN: - addStoreBefore( - succ, - node, - currentInput.getThenStore(), - Store.Kind.THEN, - addToWorklistAgain); - break; - case ELSE_TO_ELSE: - addStoreBefore( - succ, - node, - currentInput.getElseStore(), - Store.Kind.ELSE, - addToWorklistAgain); - break; + @Override + protected void propagateStoresTo( + Block succ, + @Nullable Node node, + TransferInput currentInput, + Store.FlowRule flowRule, + boolean addToWorklistAgain) { + switch (flowRule) { + case EACH_TO_EACH: + if (currentInput.containsTwoStores()) { + addStoreBefore( + succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain); + addStoreBefore( + succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain); + } else { + addStoreBefore( + succ, node, currentInput.getRegularStore(), Store.Kind.BOTH, addToWorklistAgain); } + break; + case THEN_TO_BOTH: + addStoreBefore( + succ, node, currentInput.getThenStore(), Store.Kind.BOTH, addToWorklistAgain); + break; + case ELSE_TO_BOTH: + addStoreBefore( + succ, node, currentInput.getElseStore(), Store.Kind.BOTH, addToWorklistAgain); + break; + case THEN_TO_THEN: + addStoreBefore( + succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain); + break; + case ELSE_TO_ELSE: + addStoreBefore( + succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain); + break; } + } - /** - * Add a store before the basic block {@code b} by merging with the existing stores for that - * location. - * - * @param b a basic block - * @param node the node of the basic block {@code b} - * @param s the store being added - * @param kind the kind of store {@code s} - * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code - * Worklist} - */ - protected void addStoreBefore( - Block b, @Nullable Node node, S s, Store.Kind kind, boolean addBlockToWorklist) { - S thenStore = getStoreBefore(b, Store.Kind.THEN); - S elseStore = getStoreBefore(b, Store.Kind.ELSE); - boolean shouldWiden = false; - if (blockCount != null) { - Integer count = blockCount.getOrDefault(b, 0); - shouldWiden = count >= maxCountBeforeWidening; - if (shouldWiden) { - blockCount.put(b, 0); - } else { - blockCount.put(b, count + 1); + /** + * Add a store before the basic block {@code b} by merging with the existing stores for that + * location. + * + * @param b a basic block + * @param node the node of the basic block {@code b} + * @param s the store being added + * @param kind the kind of store {@code s} + * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code + * Worklist} + */ + protected void addStoreBefore( + Block b, @Nullable Node node, S s, Store.Kind kind, boolean addBlockToWorklist) { + S thenStore = getStoreBefore(b, Store.Kind.THEN); + S elseStore = getStoreBefore(b, Store.Kind.ELSE); + boolean shouldWiden = false; + if (blockCount != null) { + Integer count = blockCount.getOrDefault(b, 0); + shouldWiden = count >= maxCountBeforeWidening; + if (shouldWiden) { + blockCount.put(b, 0); + } else { + blockCount.put(b, count + 1); + } + } + switch (kind) { + case THEN: + { + // Update the then store + S newThenStore = mergeStores(s, thenStore, shouldWiden); + if (!newThenStore.equals(thenStore)) { + thenStores.put(b, newThenStore); + if (elseStore != null) { + inputs.put(b, new TransferInput<>(node, this, newThenStore, elseStore)); + addBlockToWorklist = true; } + } + break; } - switch (kind) { - case THEN: - { - // Update the then store - S newThenStore = mergeStores(s, thenStore, shouldWiden); - if (!newThenStore.equals(thenStore)) { - thenStores.put(b, newThenStore); - if (elseStore != null) { - inputs.put(b, new TransferInput<>(node, this, newThenStore, elseStore)); - addBlockToWorklist = true; - } - } - break; - } - case ELSE: - { - // Update the else store - S newElseStore = mergeStores(s, elseStore, shouldWiden); - if (!newElseStore.equals(elseStore)) { - elseStores.put(b, newElseStore); - if (thenStore != null) { - inputs.put(b, new TransferInput<>(node, this, thenStore, newElseStore)); - addBlockToWorklist = true; - } - } - break; - } - case BOTH: - @SuppressWarnings("interning:not.interned") - boolean sameStore = (thenStore == elseStore); - if (sameStore) { - // Currently there is only one regular store - S newStore = mergeStores(s, thenStore, shouldWiden); - if (!newStore.equals(thenStore)) { - thenStores.put(b, newStore); - elseStores.put(b, newStore); - inputs.put(b, new TransferInput<>(node, this, newStore)); - addBlockToWorklist = true; - } - } else { - boolean storeChanged = false; - S newThenStore = mergeStores(s, thenStore, shouldWiden); - if (!newThenStore.equals(thenStore)) { - thenStores.put(b, newThenStore); - storeChanged = true; - } - S newElseStore = mergeStores(s, elseStore, shouldWiden); - if (!newElseStore.equals(elseStore)) { - elseStores.put(b, newElseStore); - storeChanged = true; - } - if (storeChanged) { - inputs.put(b, new TransferInput<>(node, this, newThenStore, newElseStore)); - addBlockToWorklist = true; - } - } - } - if (addBlockToWorklist) { - addToWorklist(b); + case ELSE: + { + // Update the else store + S newElseStore = mergeStores(s, elseStore, shouldWiden); + if (!newElseStore.equals(elseStore)) { + elseStores.put(b, newElseStore); + if (thenStore != null) { + inputs.put(b, new TransferInput<>(node, this, thenStore, newElseStore)); + addBlockToWorklist = true; + } + } + break; } - } - - /** - * Merge two stores, possibly widening the result. - * - * @param newStore the new Store - * @param previousStore the previous Store - * @param shouldWiden should widen or not - * @return the merged Store - */ - private S mergeStores(S newStore, @Nullable S previousStore, boolean shouldWiden) { - if (previousStore == null) { - return newStore; - } else if (shouldWiden) { - return newStore.widenedUpperBound(previousStore); + case BOTH: + @SuppressWarnings("interning:not.interned") + boolean sameStore = (thenStore == elseStore); + if (sameStore) { + // Currently there is only one regular store + S newStore = mergeStores(s, thenStore, shouldWiden); + if (!newStore.equals(thenStore)) { + thenStores.put(b, newStore); + elseStores.put(b, newStore); + inputs.put(b, new TransferInput<>(node, this, newStore)); + addBlockToWorklist = true; + } } else { - return newStore.leastUpperBound(previousStore); + boolean storeChanged = false; + S newThenStore = mergeStores(s, thenStore, shouldWiden); + if (!newThenStore.equals(thenStore)) { + thenStores.put(b, newThenStore); + storeChanged = true; + } + S newElseStore = mergeStores(s, elseStore, shouldWiden); + if (!newElseStore.equals(elseStore)) { + elseStores.put(b, newElseStore); + storeChanged = true; + } + if (storeChanged) { + inputs.put(b, new TransferInput<>(node, this, newThenStore, newElseStore)); + addBlockToWorklist = true; + } } } + if (addBlockToWorklist) { + addToWorklist(b); + } + } - /** - * Return the store corresponding to the location right before the basic block {@code b}. - * - * @param b a block - * @param kind the kind of store which will be returned - * @return the store corresponding to the location right before the basic block {@code b} - */ - protected @Nullable S getStoreBefore(Block b, Store.Kind kind) { - switch (kind) { - case THEN: - return readFromStore(thenStores, b); - case ELSE: - return readFromStore(elseStores, b); - default: - throw new BugInCF("Unexpected Store.Kind: " + kind); - } + /** + * Merge two stores, possibly widening the result. + * + * @param newStore the new Store + * @param previousStore the previous Store + * @param shouldWiden should widen or not + * @return the merged Store + */ + private S mergeStores(S newStore, @Nullable S previousStore, boolean shouldWiden) { + if (previousStore == null) { + return newStore; + } else if (shouldWiden) { + return newStore.widenedUpperBound(previousStore); + } else { + return newStore.leastUpperBound(previousStore); } + } - /** - * Returns the transfer input corresponding to the location right before the basic block {@code - * b}. - * - * @param b a block - * @return the transfer input corresponding to the location right before the basic block {@code - * b} - */ - protected @Nullable TransferInput getInputBefore(Block b) { - return inputs.get(b); + /** + * Return the store corresponding to the location right before the basic block {@code b}. + * + * @param b a block + * @param kind the kind of store which will be returned + * @return the store corresponding to the location right before the basic block {@code b} + */ + protected @Nullable S getStoreBefore(Block b, Store.Kind kind) { + switch (kind) { + case THEN: + return readFromStore(thenStores, b); + case ELSE: + return readFromStore(elseStores, b); + default: + throw new BugInCF("Unexpected Store.Kind: " + kind); } + } + + /** + * Returns the transfer input corresponding to the location right before the basic block {@code + * b}. + * + * @param b a block + * @return the transfer input corresponding to the location right before the basic block {@code b} + */ + protected @Nullable TransferInput getInputBefore(Block b) { + return inputs.get(b); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java index 8856e8bc416..3c5f8e58515 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java @@ -1,10 +1,9 @@ package org.checkerframework.dataflow.analysis; +import java.util.List; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; -import java.util.List; - /** * Interface of a forward transfer function for the abstract interpretation used for the forward * flow analysis. @@ -17,14 +16,14 @@ * @param the store type used in the analysis */ public interface ForwardTransferFunction, S extends Store> - extends TransferFunction { + extends TransferFunction { - /** - * Returns the initial store to be used by the org.checkerframework.dataflow analysis. - * - * @param underlyingAST an abstract syntax tree - * @param parameters a list of local variable nodes representing formal parameters (if any) - * @return the initial store - */ - S initialStore(UnderlyingAST underlyingAST, List parameters); + /** + * Returns the initial store to be used by the org.checkerframework.dataflow analysis. + * + * @param underlyingAST an abstract syntax tree + * @param parameters a list of local variable nodes representing formal parameters (if any) + * @return the initial store + */ + S initialStore(UnderlyingAST underlyingAST, List parameters); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java index 1885f0d9451..6bf69b6dcaf 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java @@ -1,12 +1,10 @@ package org.checkerframework.dataflow.analysis; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.StringsPlume; - import java.util.Map; import java.util.StringJoiner; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.StringsPlume; /** * Implementation of a {@link TransferResult} with just one non-exceptional store. The result of @@ -16,144 +14,141 @@ * @param the store type used in the analysis */ public class RegularTransferResult, S extends Store> - extends TransferResult { - - /** The regular result store. */ - protected final S store; - - /** - * Whether the store changed; see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - */ - private final boolean storeChanged; - - /** - * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code - * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores}. - * - *

Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no - * special handling is necessary and the store before the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. - * - *

Aliasing: {@code resultStore} is not allowed to be used anywhere outside of this - * class (including use through aliases). Complete control over the object is transferred to - * this class. - * - * @param value the abstract value produced by the transfer function - * @param resultStore regular result store - * @param storeChanged whether the store changed; see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} - * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) - */ - public RegularTransferResult(@Nullable V value, S resultStore, boolean storeChanged) { - this(value, resultStore, null, storeChanged); - } - - /** - * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code - * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores} and - * {@code false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - * - * @param value the abstract value produced by the transfer function - * @param resultStore regular result store - * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) - */ - public RegularTransferResult(@Nullable V value, S resultStore) { - this(value, resultStore, false); - } - - /** - * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code - * false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - * - * @param value the abstract value produced by the transfer function - * @param resultStore the regular result store - * @param exceptionalStores the stores in case the basic block throws an exception, or null if - * the basic block does not throw any exceptions - * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) - */ - public RegularTransferResult( - @Nullable V value, S resultStore, @Nullable Map exceptionalStores) { - this(value, resultStore, exceptionalStores, false); - } - - /** - * Create a {@code TransferResult} with {@code resultStore} as the resulting store. If the - * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then - * {@code resultStore} is used for both the 'then' and 'else' edge. - * - *

Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding - * store in {@code exceptionalStores} is used. If no exception is found in {@code - * exceptionalStores}, then it is assumed that no special handling is necessary and the store - * before the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed - * along any exceptional edge. - * - *

Aliasing: {@code resultStore} and any store in {@code exceptionalStores} are not - * allowed to be used anywhere outside of this class (including use through aliases). Complete - * control over the objects is transferred to this class. - * - * @param value the abstract value produced by the transfer function - * @param resultStore the regular result store - * @param exceptionalStores the stores in case the basic block throws an exception, or null if - * the basic block does not throw any exceptions - * @param storeChanged see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} - */ - public RegularTransferResult( - @Nullable V value, - S resultStore, - @Nullable Map exceptionalStores, - boolean storeChanged) { - super(value, exceptionalStores); - this.store = resultStore; - this.storeChanged = storeChanged; - } - - /** The regular result store. */ - @Override - public S getRegularStore() { - return store; - } - - @Override - public S getThenStore() { - return store; - } - - @Override - public S getElseStore() { - // copy the store such that it is the same as the result of getThenStore - // (that is, identical according to equals), but two different objects. - return store.copy(); - } - - @Override - public boolean containsTwoStores() { - return false; - } - - @Override - public String toString() { - StringJoiner result = new StringJoiner(System.lineSeparator()); - result.add("RegularTransferResult("); - result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); - // "toString().trim()" works around bug where toString ends with a newline. - result.add( - " store = " - + StringsPlume.indentLinesExceptFirst(2, store.toString().trim()) - + ")"); - return result.toString(); - } - - /** - * See {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged()}. - * - * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged() - */ - @Override - public boolean storeChanged() { - return storeChanged; - } + extends TransferResult { + + /** The regular result store. */ + protected final S store; + + /** + * Whether the store changed; see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + */ + private final boolean storeChanged; + + /** + * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code + * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores}. + * + *

Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no + * special handling is necessary and the store before the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + *

Aliasing: {@code resultStore} is not allowed to be used anywhere outside of this + * class (including use through aliases). Complete control over the object is transferred to this + * class. + * + * @param value the abstract value produced by the transfer function + * @param resultStore regular result store + * @param storeChanged whether the store changed; see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} + * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) + */ + public RegularTransferResult(@Nullable V value, S resultStore, boolean storeChanged) { + this(value, resultStore, null, storeChanged); + } + + /** + * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code + * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores} and + * {@code false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + * + * @param value the abstract value produced by the transfer function + * @param resultStore regular result store + * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) + */ + public RegularTransferResult(@Nullable V value, S resultStore) { + this(value, resultStore, false); + } + + /** + * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code + * false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + * + * @param value the abstract value produced by the transfer function + * @param resultStore the regular result store + * @param exceptionalStores the stores in case the basic block throws an exception, or null if the + * basic block does not throw any exceptions + * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) + */ + public RegularTransferResult( + @Nullable V value, S resultStore, @Nullable Map exceptionalStores) { + this(value, resultStore, exceptionalStores, false); + } + + /** + * Create a {@code TransferResult} with {@code resultStore} as the resulting store. If the + * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then + * {@code resultStore} is used for both the 'then' and 'else' edge. + * + *

Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding store + * in {@code exceptionalStores} is used. If no exception is found in {@code exceptionalStores}, + * then it is assumed that no special handling is necessary and the store before the corresponding + * {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + *

Aliasing: {@code resultStore} and any store in {@code exceptionalStores} are not + * allowed to be used anywhere outside of this class (including use through aliases). Complete + * control over the objects is transferred to this class. + * + * @param value the abstract value produced by the transfer function + * @param resultStore the regular result store + * @param exceptionalStores the stores in case the basic block throws an exception, or null if the + * basic block does not throw any exceptions + * @param storeChanged see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} + */ + public RegularTransferResult( + @Nullable V value, + S resultStore, + @Nullable Map exceptionalStores, + boolean storeChanged) { + super(value, exceptionalStores); + this.store = resultStore; + this.storeChanged = storeChanged; + } + + /** The regular result store. */ + @Override + public S getRegularStore() { + return store; + } + + @Override + public S getThenStore() { + return store; + } + + @Override + public S getElseStore() { + // copy the store such that it is the same as the result of getThenStore + // (that is, identical according to equals), but two different objects. + return store.copy(); + } + + @Override + public boolean containsTwoStores() { + return false; + } + + @Override + public String toString() { + StringJoiner result = new StringJoiner(System.lineSeparator()); + result.add("RegularTransferResult("); + result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); + // "toString().trim()" works around bug where toString ends with a newline. + result.add( + " store = " + StringsPlume.indentLinesExceptFirst(2, store.toString().trim()) + ")"); + return result.toString(); + } + + /** + * See {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged()}. + * + * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged() + */ + @Override + public boolean storeChanged() { + return storeChanged; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java index cbf403ece59..0588bb2c12a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java @@ -14,102 +14,101 @@ */ public interface Store> { - // We maintain a then store and an else store before each basic block. - // When they are identical (by reference equality), they can be treated - // as a regular unconditional store. - // Once we have some information for both the then and else store, we - // create a TransferInput for the block and allow it to be analyzed. - public enum Kind { - THEN, - ELSE, - BOTH - } - - /** A flow rule describes how stores flow along one edge between basic blocks. */ - public enum FlowRule { - /** - * The normal case: then store flows to the then store, and else store flows to the else - * store. - */ - EACH_TO_EACH, - /** Then store flows to both then and else of successor. */ - THEN_TO_BOTH, - /** Else store flows to both then and else of successor. */ - ELSE_TO_BOTH, - /** Then store flows to the then of successor. Else store is ignored. */ - THEN_TO_THEN, - /** Else store flows to the else of successor. Then store is ignored. */ - ELSE_TO_ELSE, - } + // We maintain a then store and an else store before each basic block. + // When they are identical (by reference equality), they can be treated + // as a regular unconditional store. + // Once we have some information for both the then and else store, we + // create a TransferInput for the block and allow it to be analyzed. + public enum Kind { + THEN, + ELSE, + BOTH + } + /** A flow rule describes how stores flow along one edge between basic blocks. */ + public enum FlowRule { /** - * Returns an exact copy of this store. - * - * @return an exact copy of this store + * The normal case: then store flows to the then store, and else store flows to the else store. */ - S copy(); + EACH_TO_EACH, + /** Then store flows to both then and else of successor. */ + THEN_TO_BOTH, + /** Else store flows to both then and else of successor. */ + ELSE_TO_BOTH, + /** Then store flows to the then of successor. Else store is ignored. */ + THEN_TO_THEN, + /** Else store flows to the else of successor. Then store is ignored. */ + ELSE_TO_ELSE, + } - /** - * Compute the least upper bound of two stores. - * - *

Important: This method must fulfill the following contract: - * - *

    - *
  • Does not change {@code this}. - *
  • Does not change {@code other}. - *
  • Returns a fresh object which is not aliased yet. - *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
  • Is commutative. - *
- */ - S leastUpperBound(S other); + /** + * Returns an exact copy of this store. + * + * @return an exact copy of this store + */ + S copy(); - /** - * Compute an upper bound of two stores that is wider than the least upper bound of the two - * stores. Used to jump to a higher abstraction to allow faster termination of the fixed point - * computations in {@link Analysis}. {@code previous} must be the previous store. - * - *

A particular analysis might not require widening and should implement this method by - * calling leastUpperBound. - * - *

Important: This method must fulfill the following contract: - * - *

    - *
  • Does not change {@code this}. - *
  • Does not change {@code previous}. - *
  • Returns a fresh object which is not aliased yet. - *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
  • Is commutative. - *
- * - * @param previous must be the previous store - */ - S widenedUpperBound(S previous); + /** + * Compute the least upper bound of two stores. + * + *

Important: This method must fulfill the following contract: + * + *

    + *
  • Does not change {@code this}. + *
  • Does not change {@code other}. + *
  • Returns a fresh object which is not aliased yet. + *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
  • Is commutative. + *
+ */ + S leastUpperBound(S other); - /** - * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., - * returns {@code true} if not enough information is available to determine aliasing). - */ - boolean canAlias(JavaExpression a, JavaExpression b); + /** + * Compute an upper bound of two stores that is wider than the least upper bound of the two + * stores. Used to jump to a higher abstraction to allow faster termination of the fixed point + * computations in {@link Analysis}. {@code previous} must be the previous store. + * + *

A particular analysis might not require widening and should implement this method by calling + * leastUpperBound. + * + *

Important: This method must fulfill the following contract: + * + *

    + *
  • Does not change {@code this}. + *
  • Does not change {@code previous}. + *
  • Returns a fresh object which is not aliased yet. + *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
  • Is commutative. + *
+ * + * @param previous must be the previous store + */ + S widenedUpperBound(S previous); - /** - * Delegate visualization responsibility to a visualizer. - * - * @param viz the visualizer to visualize this store - * @return the String representation of this store - */ - String visualize(CFGVisualizer viz); + /** + * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., + * returns {@code true} if not enough information is available to determine aliasing). + */ + boolean canAlias(JavaExpression a, JavaExpression b); - // I'm not sure why this is necessary, but without it `Store.equals()` is treated as requiring a - // @NonNull argument. TODO: fix. - /** - * Returns true if this is equal to the given argument. - * - * @param o the object to compare against this - * @return true if this is equal to the given argument - */ - @Override - boolean equals(@Nullable Object o); + /** + * Delegate visualization responsibility to a visualizer. + * + * @param viz the visualizer to visualize this store + * @return the String representation of this store + */ + String visualize(CFGVisualizer viz); + + // I'm not sure why this is necessary, but without it `Store.equals()` is treated as requiring a + // @NonNull argument. TODO: fix. + /** + * Returns true if this is equal to the given argument. + * + * @param o the object to compare against this + * @return true if this is equal to the given argument + */ + @Override + boolean equals(@Nullable Object o); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java index 2634333a45f..0469edee0e6 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java @@ -29,4 +29,4 @@ * @param the store type used in the analysis */ public interface TransferFunction, S extends Store> - extends NodeVisitor, TransferInput> {} + extends NodeVisitor, TransferInput> {} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java index 0dfe3f22cc9..ced4f8be570 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java @@ -1,14 +1,13 @@ package org.checkerframework.dataflow.analysis; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.plumelib.util.StringsPlume; import org.plumelib.util.UniqueId; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; - /** * {@code TransferInput} is used as the input type of the individual transfer functions of a {@link * ForwardTransferFunction} or a {@link BackwardTransferFunction}. It also contains a reference to @@ -22,297 +21,294 @@ */ public class TransferInput, S extends Store> implements UniqueId { - /** The corresponding node. */ - // TODO: explain when the node is changed. - protected @Nullable Node node; + /** The corresponding node. */ + // TODO: explain when the node is changed. + protected @Nullable Node node; - /** - * The regular result store (or {@code null} if none is present, because {@link #thenStore} and - * {@link #elseStore} are set). The following invariant is maintained: - * - *

-     * store == null ⇔ thenStore != null && elseStore != null
-     * 
- */ - protected final @Nullable S store; + /** + * The regular result store (or {@code null} if none is present, because {@link #thenStore} and + * {@link #elseStore} are set). The following invariant is maintained: + * + *

+   * store == null ⇔ thenStore != null && elseStore != null
+   * 
+ */ + protected final @Nullable S store; - /** - * The 'then' result store (or {@code null} if none is present). See invariant at {@link - * #store}. - */ - protected final @Nullable S thenStore; + /** + * The 'then' result store (or {@code null} if none is present). See invariant at {@link #store}. + */ + protected final @Nullable S thenStore; - /** - * The 'else' result store (or {@code null} if none is present). See invariant at {@link - * #store}. - */ - protected final @Nullable S elseStore; + /** + * The 'else' result store (or {@code null} if none is present). See invariant at {@link #store}. + */ + protected final @Nullable S elseStore; - /** The corresponding analysis class to get intermediate flow results. */ - protected final Analysis analysis; + /** The corresponding analysis class to get intermediate flow results. */ + protected final Analysis analysis; - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); - @Override - public long getUid(@UnknownInitialization TransferInput this) { - return uid; - } + @Override + public long getUid(@UnknownInitialization TransferInput this) { + return uid; + } - /** - * Private helper constructor; all TransferInput construction bottoms out here. - * - * @param node the corresponding node - * @param store the regular result store, or {@code null} if none is present - * @param thenStore the 'then' result store, or {@code null} if none is present - * @param elseStore the 'else' result store, or {@code null} if none is present - * @param analysis analysis the corresponding analysis class to get intermediate flow results - */ - private TransferInput( - @Nullable Node node, - @Nullable S store, - @Nullable S thenStore, - @Nullable S elseStore, - Analysis analysis) { - if (store == null) { - assert thenStore != null && elseStore != null; - } else { - assert thenStore == null && elseStore == null; - } - this.node = node; - this.store = store; - this.thenStore = thenStore; - this.elseStore = elseStore; - this.analysis = analysis; + /** + * Private helper constructor; all TransferInput construction bottoms out here. + * + * @param node the corresponding node + * @param store the regular result store, or {@code null} if none is present + * @param thenStore the 'then' result store, or {@code null} if none is present + * @param elseStore the 'else' result store, or {@code null} if none is present + * @param analysis analysis the corresponding analysis class to get intermediate flow results + */ + private TransferInput( + @Nullable Node node, + @Nullable S store, + @Nullable S thenStore, + @Nullable S elseStore, + Analysis analysis) { + if (store == null) { + assert thenStore != null && elseStore != null; + } else { + assert thenStore == null && elseStore == null; } + this.node = node; + this.store = store; + this.thenStore = thenStore; + this.elseStore = elseStore; + this.analysis = analysis; + } - /** - * Create a {@link TransferInput}, given a {@link TransferResult} and a node-value mapping. - * - *

Aliasing: The stores returned by any methods of {@code to} will be stored - * internally and are not allowed to be used elsewhere. Full control of them is transferred to - * this object. - * - *

The node-value mapping {@code nodeValues} is provided by the analysis and is only read - * from within this {@link TransferInput}. - * - * @param n {@link #node} - * @param analysis {@link #analysis} - * @param to a transfer result - */ - public TransferInput(Node n, Analysis analysis, TransferResult to) { - this( - n, - to.containsTwoStores() ? null : to.getRegularStore(), - to.containsTwoStores() ? to.getThenStore() : null, - to.containsTwoStores() ? to.getElseStore() : null, - analysis); - } + /** + * Create a {@link TransferInput}, given a {@link TransferResult} and a node-value mapping. + * + *

Aliasing: The stores returned by any methods of {@code to} will be stored + * internally and are not allowed to be used elsewhere. Full control of them is transferred to + * this object. + * + *

The node-value mapping {@code nodeValues} is provided by the analysis and is only read from + * within this {@link TransferInput}. + * + * @param n {@link #node} + * @param analysis {@link #analysis} + * @param to a transfer result + */ + public TransferInput(Node n, Analysis analysis, TransferResult to) { + this( + n, + to.containsTwoStores() ? null : to.getRegularStore(), + to.containsTwoStores() ? to.getThenStore() : null, + to.containsTwoStores() ? to.getElseStore() : null, + analysis); + } - /** - * Create a {@link TransferInput}, given a store and a node-value mapping. - * - *

Aliasing: The store {@code s} will be stored internally and is not allowed to be - * used elsewhere. Full control over {@code s} is transferred to this object. - * - *

The node-value mapping {@code nodeValues} is provided by the analysis and is only read - * from within this {@link TransferInput}. - * - * @param n {@link #node} - * @param analysis {@link #analysis} - * @param s {@link #store} - */ - public TransferInput(@Nullable Node n, Analysis analysis, S s) { - this(n, s, null, null, analysis); - } + /** + * Create a {@link TransferInput}, given a store and a node-value mapping. + * + *

Aliasing: The store {@code s} will be stored internally and is not allowed to be + * used elsewhere. Full control over {@code s} is transferred to this object. + * + *

The node-value mapping {@code nodeValues} is provided by the analysis and is only read from + * within this {@link TransferInput}. + * + * @param n {@link #node} + * @param analysis {@link #analysis} + * @param s {@link #store} + */ + public TransferInput(@Nullable Node n, Analysis analysis, S s) { + this(n, s, null, null, analysis); + } - /** - * Create a {@link TransferInput}, given two stores and a node-value mapping. - * - *

Aliasing: The two stores {@code s1} and {@code s2} will be stored internally and - * are not allowed to be used elsewhere. Full control of them is transferred to this object. - * - * @param n a node - * @param analysis {@link #analysis} - * @param s1 {@link #thenStore} - * @param s2 {@link #elseStore} - */ - public TransferInput(@Nullable Node n, Analysis analysis, S s1, S s2) { - this(n, null, s1, s2, analysis); - } + /** + * Create a {@link TransferInput}, given two stores and a node-value mapping. + * + *

Aliasing: The two stores {@code s1} and {@code s2} will be stored internally and + * are not allowed to be used elsewhere. Full control of them is transferred to this object. + * + * @param n a node + * @param analysis {@link #analysis} + * @param s1 {@link #thenStore} + * @param s2 {@link #elseStore} + */ + public TransferInput(@Nullable Node n, Analysis analysis, S s1, S s2) { + this(n, null, s1, s2, analysis); + } - /** - * Copy constructor. - * - * @param from a {@link TransferInput} to copy - */ - @SuppressWarnings("nullness:dereference.of.nullable") // object invariant: store vs thenStore - protected TransferInput(TransferInput from) { - this( - from.node, - from.store == null ? null : from.store.copy(), - from.store == null ? from.thenStore.copy() : null, - from.store == null ? from.elseStore.copy() : null, - from.analysis); - } + /** + * Copy constructor. + * + * @param from a {@link TransferInput} to copy + */ + @SuppressWarnings("nullness:dereference.of.nullable") // object invariant: store vs thenStore + protected TransferInput(TransferInput from) { + this( + from.node, + from.store == null ? null : from.store.copy(), + from.store == null ? from.thenStore.copy() : null, + from.store == null ? from.elseStore.copy() : null, + from.analysis); + } - /** - * Returns the {@link Node} for this {@link TransferInput}. - * - * @return the {@link Node} for this {@link TransferInput} - */ - public @Nullable Node getNode() { - return node; - } + /** + * Returns the {@link Node} for this {@link TransferInput}. + * + * @return the {@link Node} for this {@link TransferInput} + */ + public @Nullable Node getNode() { + return node; + } - /** - * Returns the abstract value of node {@code n}, which is required to be a 'sub-node' (that is, - * a direct or indirect child) of the node this transfer input is associated with. Furthermore, - * {@code n} cannot be a l-value node. Returns {@code null} if no value is available. - * - * @param n a node - * @return the abstract value of node {@code n}, or {@code null} if no value is available - */ - public @Nullable V getValueOfSubNode(Node n) { - return analysis.getValue(n); - } + /** + * Returns the abstract value of node {@code n}, which is required to be a 'sub-node' (that is, a + * direct or indirect child) of the node this transfer input is associated with. Furthermore, + * {@code n} cannot be a l-value node. Returns {@code null} if no value is available. + * + * @param n a node + * @return the abstract value of node {@code n}, or {@code null} if no value is available + */ + public @Nullable V getValueOfSubNode(Node n) { + return analysis.getValue(n); + } - /** - * Returns the regular result store produced if no exception is thrown by the {@link Node} - * corresponding to this transfer function result. - * - * @return the regular result store produced if no exception is thrown by the {@link Node} - * corresponding to this transfer function result - */ - public S getRegularStore() { - if (store == null) { - assert thenStore != null && elseStore != null : "@AssumeAssertion(nullness): invariant"; - return thenStore.leastUpperBound(elseStore); - } else { - return store; - } + /** + * Returns the regular result store produced if no exception is thrown by the {@link Node} + * corresponding to this transfer function result. + * + * @return the regular result store produced if no exception is thrown by the {@link Node} + * corresponding to this transfer function result + */ + public S getRegularStore() { + if (store == null) { + assert thenStore != null && elseStore != null : "@AssumeAssertion(nullness): invariant"; + return thenStore.leastUpperBound(elseStore); + } else { + return store; } + } - /** - * Returns the result store produced if the {@link Node} this result belongs to evaluates to - * {@code true}. - * - * @return the result store produced if the {@link Node} this result belongs to evaluates to - * {@code true} - */ - public S getThenStore() { - if (store == null) { - assert thenStore != null : "@AssumeAssertion(nullness): invariant"; - return thenStore; - } - return store; + /** + * Returns the result store produced if the {@link Node} this result belongs to evaluates to + * {@code true}. + * + * @return the result store produced if the {@link Node} this result belongs to evaluates to + * {@code true} + */ + public S getThenStore() { + if (store == null) { + assert thenStore != null : "@AssumeAssertion(nullness): invariant"; + return thenStore; } + return store; + } - /** - * Returns the result store produced if the {@link Node} this result belongs to evaluates to - * {@code false}. - * - * @return the result store produced if the {@link Node} this result belongs to evaluates to - * {@code false} - */ - public S getElseStore() { - if (store == null) { - assert elseStore != null : "@AssumeAssertion(nullness): invariant"; - return elseStore; - } - // copy the store such that it is the same as the result of getThenStore - // (that is, identical according to equals), but two different objects. - return store.copy(); + /** + * Returns the result store produced if the {@link Node} this result belongs to evaluates to + * {@code false}. + * + * @return the result store produced if the {@link Node} this result belongs to evaluates to + * {@code false} + */ + public S getElseStore() { + if (store == null) { + assert elseStore != null : "@AssumeAssertion(nullness): invariant"; + return elseStore; } + // copy the store such that it is the same as the result of getThenStore + // (that is, identical according to equals), but two different objects. + return store.copy(); + } - /** - * Returns {@code true} if and only if this transfer input contains two stores that are - * potentially not equal. Note that the result {@code true} does not imply that {@code - * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates - * that {@code getThenStore} or {@code getElseStore} can be used to give more precise results. - * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, - * {@code getThenStore}, and {@code getElseStore} return equivalent stores. - * - * @return {@code true} if and only if this transfer input contains two stores that are - * potentially not equal - */ - public boolean containsTwoStores() { - return store == null; - } + /** + * Returns {@code true} if and only if this transfer input contains two stores that are + * potentially not equal. Note that the result {@code true} does not imply that {@code + * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates that + * {@code getThenStore} or {@code getElseStore} can be used to give more precise results. + * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, + * {@code getThenStore}, and {@code getElseStore} return equivalent stores. + * + * @return {@code true} if and only if this transfer input contains two stores that are + * potentially not equal + */ + public boolean containsTwoStores() { + return store == null; + } - /** - * Returns an exact copy of this store. - * - * @return an exact copy of this store - */ - public TransferInput copy() { - return new TransferInput<>(this); - } + /** + * Returns an exact copy of this store. + * + * @return an exact copy of this store + */ + public TransferInput copy() { + return new TransferInput<>(this); + } - /** - * Compute the least upper bound of two stores. - * - *

Important: This method must fulfill the same contract as {@code leastUpperBound} - * of {@link Store}. - * - * @param other a transfer input - * @return the least upper bound of this and {@code other} - */ - public TransferInput leastUpperBound(TransferInput other) { - if (store == null) { - S newThenStore = getThenStore().leastUpperBound(other.getThenStore()); - S newElseStore = getElseStore().leastUpperBound(other.getElseStore()); - return new TransferInput<>(node, analysis, newThenStore, newElseStore); - } else { - if (other.store == null) { - // make sure we do not lose precision and keep two stores if at - // least one of the two TransferInput's has two stores. - return other.leastUpperBound(this); - } - return new TransferInput<>( - node, analysis, store.leastUpperBound(other.getRegularStore())); - } + /** + * Compute the least upper bound of two stores. + * + *

Important: This method must fulfill the same contract as {@code leastUpperBound} of + * {@link Store}. + * + * @param other a transfer input + * @return the least upper bound of this and {@code other} + */ + public TransferInput leastUpperBound(TransferInput other) { + if (store == null) { + S newThenStore = getThenStore().leastUpperBound(other.getThenStore()); + S newElseStore = getElseStore().leastUpperBound(other.getElseStore()); + return new TransferInput<>(node, analysis, newThenStore, newElseStore); + } else { + if (other.store == null) { + // make sure we do not lose precision and keep two stores if at + // least one of the two TransferInput's has two stores. + return other.leastUpperBound(this); + } + return new TransferInput<>(node, analysis, store.leastUpperBound(other.getRegularStore())); } + } - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof TransferInput) { - @SuppressWarnings("unchecked") - TransferInput other = (TransferInput) o; - if (containsTwoStores()) { - if (other.containsTwoStores()) { - return getThenStore().equals(other.getThenStore()) - && getElseStore().equals(other.getElseStore()); - } - } else { - if (!other.containsTwoStores()) { - return getRegularStore().equals(other.getRegularStore()); - } - } + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof TransferInput) { + @SuppressWarnings("unchecked") + TransferInput other = (TransferInput) o; + if (containsTwoStores()) { + if (other.containsTwoStores()) { + return getThenStore().equals(other.getThenStore()) + && getElseStore().equals(other.getElseStore()); + } + } else { + if (!other.containsTwoStores()) { + return getRegularStore().equals(other.getRegularStore()); } - return false; + } } + return false; + } - @Override - public int hashCode() { - return Objects.hash(this.analysis, this.node, this.store, this.thenStore, this.elseStore); - } + @Override + public int hashCode() { + return Objects.hash(this.analysis, this.node, this.store, this.thenStore, this.elseStore); + } - @Override - public String toString() { - if (store == null) { - return "[then=" - + StringsPlume.indentLinesExceptFirst(2, thenStore) - + "," - + System.lineSeparator() - + " else=" - + StringsPlume.indentLinesExceptFirst(2, elseStore) - + "]"; - } else { - return "[" + store + "]"; - } + @Override + public String toString() { + if (store == null) { + return "[then=" + + StringsPlume.indentLinesExceptFirst(2, thenStore) + + "," + + System.lineSeparator() + + " else=" + + StringsPlume.indentLinesExceptFirst(2, elseStore) + + "]"; + } else { + return "[" + store + "]"; } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java index 938a01a43ad..dab3046ae5e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java @@ -1,10 +1,8 @@ package org.checkerframework.dataflow.analysis; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Map; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@code TransferResult} is used as the result type of the individual transfer functions of a @@ -20,127 +18,126 @@ */ public abstract class TransferResult, S extends Store> { - /** - * The abstract value of the {@link org.checkerframework.dataflow.cfg.node.Node} associated with - * this {@link TransferResult}, or {@code null} if no value has been produced. - * - *

Is set by {@link #setResultValue}. - */ - protected @Nullable V resultValue; - - /** - * The stores in case the basic block throws an exception (or {@code null} if the corresponding - * {@link org.checkerframework.dataflow.cfg.node.Node} does not throw any exceptions). Does not - * necessarily contain a store for every exception, in which case the in-store will be used. - */ - protected final @Nullable Map exceptionalStores; - - /** - * Create a new TransferResult, given {@link #resultValue} and {@link #exceptionalStores}. - * - * @param resultValue the abstract value of the {@link - * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} - * @param exceptionalStores the stores in case the basic block throws an exception (or {@code - * null} if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} does not - * throw any exceptions) - */ - protected TransferResult( - @Nullable V resultValue, @Nullable Map exceptionalStores) { - this.resultValue = resultValue; - this.exceptionalStores = exceptionalStores; - } - - /** - * Returns the abstract value produced by the transfer function, {@code null} otherwise. - * - * @return the abstract value produced by the transfer function, {@code null} otherwise - */ - public @Nullable V getResultValue() { - return resultValue; - } - - /** - * Set the value of {@link #resultValue}. - * - * @param resultValue the abstract value of the {@link - * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} - */ - public void setResultValue(V resultValue) { - this.resultValue = resultValue; + /** + * The abstract value of the {@link org.checkerframework.dataflow.cfg.node.Node} associated with + * this {@link TransferResult}, or {@code null} if no value has been produced. + * + *

Is set by {@link #setResultValue}. + */ + protected @Nullable V resultValue; + + /** + * The stores in case the basic block throws an exception (or {@code null} if the corresponding + * {@link org.checkerframework.dataflow.cfg.node.Node} does not throw any exceptions). Does not + * necessarily contain a store for every exception, in which case the in-store will be used. + */ + protected final @Nullable Map exceptionalStores; + + /** + * Create a new TransferResult, given {@link #resultValue} and {@link #exceptionalStores}. + * + * @param resultValue the abstract value of the {@link + * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} + * @param exceptionalStores the stores in case the basic block throws an exception (or {@code + * null} if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} does not + * throw any exceptions) + */ + protected TransferResult( + @Nullable V resultValue, @Nullable Map exceptionalStores) { + this.resultValue = resultValue; + this.exceptionalStores = exceptionalStores; + } + + /** + * Returns the abstract value produced by the transfer function, {@code null} otherwise. + * + * @return the abstract value produced by the transfer function, {@code null} otherwise + */ + public @Nullable V getResultValue() { + return resultValue; + } + + /** + * Set the value of {@link #resultValue}. + * + * @param resultValue the abstract value of the {@link + * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} + */ + public void setResultValue(V resultValue) { + this.resultValue = resultValue; + } + + /** + * Returns the regular result store produced if no exception is thrown by the {@link + * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result. + * + * @return the regular result store produced if no exception is thrown by the {@link + * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result + */ + public abstract S getRegularStore(); + + /** + * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code true}. + * + * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code true} + */ + public abstract S getThenStore(); + + /** + * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code false}. + * + * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code false} + */ + public abstract S getElseStore(); + + /** + * Returns the store that flows along the outgoing exceptional edge labeled with {@code exception} + * (or {@code null} if no special handling is required for exceptional edges). + * + * @param exception an exception type + * @return the store that flows along the outgoing exceptional edge labeled with {@code exception} + * (or {@code null} if no special handling is required for exceptional edges) + */ + public @Nullable S getExceptionalStore(TypeMirror exception) { + if (exceptionalStores == null) { + return null; } - - /** - * Returns the regular result store produced if no exception is thrown by the {@link - * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result. - * - * @return the regular result store produced if no exception is thrown by the {@link - * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function - * result - */ - public abstract S getRegularStore(); - - /** - * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code true}. - * - * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code true} - */ - public abstract S getThenStore(); - - /** - * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code false}. - * - * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code false} - */ - public abstract S getElseStore(); - - /** - * Returns the store that flows along the outgoing exceptional edge labeled with {@code - * exception} (or {@code null} if no special handling is required for exceptional edges). - * - * @param exception an exception type - * @return the store that flows along the outgoing exceptional edge labeled with {@code - * exception} (or {@code null} if no special handling is required for exceptional edges) - */ - public @Nullable S getExceptionalStore(TypeMirror exception) { - if (exceptionalStores == null) { - return null; - } - return exceptionalStores.get(exception); - } - - /** - * Returns a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise. - * - * @return a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise - * @see TransferResult#getExceptionalStore(TypeMirror) - */ - public @Nullable Map getExceptionalStores() { - return exceptionalStores; - } - - /** - * Returns {@code true} if and only if this transfer result contains two stores that are - * potentially not equal. Note that the result {@code true} does not imply that {@code - * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates - * that {@code getThenStore} or {@code getElseStore} can be used to give more precise results. - * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, - * {@code getThenStore}, and {@code getElseStore} return equivalent stores. - * - * @return {@code true} if and only if this transfer result contains two stores that are - * potentially not equal - */ - public abstract boolean containsTwoStores(); - - /** - * Returns {@code true} if and only if the transfer function returning this transfer result - * changed the regularStore, elseStore, or thenStore. - * - * @return {@code true} if and only if the transfer function returning this transfer result - * changed the regularStore, elseStore, or thenStore - */ - public abstract boolean storeChanged(); + return exceptionalStores.get(exception); + } + + /** + * Returns a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise. + * + * @return a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise + * @see TransferResult#getExceptionalStore(TypeMirror) + */ + public @Nullable Map getExceptionalStores() { + return exceptionalStores; + } + + /** + * Returns {@code true} if and only if this transfer result contains two stores that are + * potentially not equal. Note that the result {@code true} does not imply that {@code + * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates that + * {@code getThenStore} or {@code getElseStore} can be used to give more precise results. + * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, + * {@code getThenStore}, and {@code getElseStore} return equivalent stores. + * + * @return {@code true} if and only if this transfer result contains two stores that are + * potentially not equal + */ + public abstract boolean containsTwoStores(); + + /** + * Returns {@code true} if and only if the transfer function returning this transfer result + * changed the regularStore, elseStore, or thenStore. + * + * @return {@code true} if and only if the transfer function returning this transfer result + * changed the regularStore, elseStore, or thenStore + */ + public abstract boolean storeChanged(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java index ff6088f5500..9c5bc2e2b0d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java @@ -11,13 +11,13 @@ */ public final class UnusedAbstractValue implements AbstractValue { - /** This class cannot be instantiated */ - private UnusedAbstractValue() { - throw new AssertionError("Class UnusedAbstractValue cannot be instantiated."); - } + /** This class cannot be instantiated */ + private UnusedAbstractValue() { + throw new AssertionError("Class UnusedAbstractValue cannot be instantiated."); + } - @Override - public UnusedAbstractValue leastUpperBound(UnusedAbstractValue other) { - throw new BugInCF("UnusedAbstractValue.leastUpperBound was called!"); - } + @Override + public UnusedAbstractValue leastUpperBound(UnusedAbstractValue other) { + throw new BugInCF("UnusedAbstractValue.leastUpperBound was called!"); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java index d66c72fb29b..314b062d560 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java @@ -1,5 +1,8 @@ package org.checkerframework.dataflow.busyexpr; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.BinaryOperationNode; @@ -8,136 +11,132 @@ import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.javacutil.BugInCF; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.StringJoiner; - /** A busy expression store contains a set of busy expressions represented by nodes. */ public class BusyExprStore implements Store { - /** A set of busy expression abstract values. */ - private final Set busyExprValueSet; - - /** - * Create a new BusyExprStore. - * - * @param busyExprValueSet a set of busy expression abstract values. The parameter is captured - * and the caller should not retain an alias. - */ - public BusyExprStore(Set busyExprValueSet) { - this.busyExprValueSet = busyExprValueSet; + /** A set of busy expression abstract values. */ + private final Set busyExprValueSet; + + /** + * Create a new BusyExprStore. + * + * @param busyExprValueSet a set of busy expression abstract values. The parameter is captured and + * the caller should not retain an alias. + */ + public BusyExprStore(Set busyExprValueSet) { + this.busyExprValueSet = busyExprValueSet; + } + + /** Create a new BusyExprStore. */ + public BusyExprStore() { + busyExprValueSet = new LinkedHashSet<>(); + } + + /** + * Kill expressions if they contain variable var. + * + * @param var a variable + */ + public void killBusyExpr(Node var) { + busyExprValueSet.removeIf( + busyExprValue -> exprContainsVariable(busyExprValue.busyExpression, var)); + } + + /** + * Return true if the expression contains variable var. Note that {@code .equals} is used in the + * return statement to verify value equality, as the statement decides whether the two nodes have + * the same value, not represent the same CFG node. + * + * @param expr the expression checked + * @param var the variable + * @return true if the expression contains the variable + */ + public boolean exprContainsVariable(Node expr, Node var) { + if (expr instanceof BinaryOperationNode) { + BinaryOperationNode binaryNode = (BinaryOperationNode) expr; + return exprContainsVariable(binaryNode.getLeftOperand(), var) + || exprContainsVariable(binaryNode.getRightOperand(), var); } - /** Create a new BusyExprStore. */ - public BusyExprStore() { - busyExprValueSet = new LinkedHashSet<>(); + return expr.equals(var); + } + + /** + * Add busy expression e to busy expression value set. + * + * @param e the busy expression to be added + */ + public void putBusyExpr(BusyExprValue e) { + busyExprValueSet.add(e); + } + + /** + * Add expressions to the store, add sub-expressions to the store recursively + * + * @param e the expression to be added + */ + public void addUseInExpression(Node e) { + if (e instanceof BinaryOperationNode) { + BinaryOperationNode binaryNode = (BinaryOperationNode) e; + putBusyExpr(new BusyExprValue(binaryNode)); + // recursively add expressions + addUseInExpression(binaryNode.getLeftOperand()); + addUseInExpression(binaryNode.getRightOperand()); } - - /** - * Kill expressions if they contain variable var. - * - * @param var a variable - */ - public void killBusyExpr(Node var) { - busyExprValueSet.removeIf( - busyExprValue -> exprContainsVariable(busyExprValue.busyExpression, var)); + } + + @Override + public BusyExprStore copy() { + return new BusyExprStore(new LinkedHashSet<>(busyExprValueSet)); + } + + @Override + public BusyExprStore leastUpperBound(BusyExprStore other) { + Set busyExprValueSetLub = new LinkedHashSet<>(this.busyExprValueSet); + busyExprValueSetLub.retainAll(other.busyExprValueSet); + + return new BusyExprStore(busyExprValueSetLub); + } + + @Override + public BusyExprStore widenedUpperBound(BusyExprStore previous) { + throw new BugInCF("BusyExprStore.widenedUpperBound was called!"); + } + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; + } + + @Override + public String visualize(CFGVisualizer viz) { + String key = "busy expressions"; + if (busyExprValueSet.isEmpty()) { + return viz.visualizeStoreKeyVal(key, "none"); } - - /** - * Return true if the expression contains variable var. Note that {@code .equals} is used in the - * return statement to verify value equality, as the statement decides whether the two nodes - * have the same value, not represent the same CFG node. - * - * @param expr the expression checked - * @param var the variable - * @return true if the expression contains the variable - */ - public boolean exprContainsVariable(Node expr, Node var) { - if (expr instanceof BinaryOperationNode) { - BinaryOperationNode binaryNode = (BinaryOperationNode) expr; - return exprContainsVariable(binaryNode.getLeftOperand(), var) - || exprContainsVariable(binaryNode.getRightOperand(), var); - } - - return expr.equals(var); - } - - /** - * Add busy expression e to busy expression value set. - * - * @param e the busy expression to be added - */ - public void putBusyExpr(BusyExprValue e) { - busyExprValueSet.add(e); - } - - /** - * Add expressions to the store, add sub-expressions to the store recursively - * - * @param e the expression to be added - */ - public void addUseInExpression(Node e) { - if (e instanceof BinaryOperationNode) { - BinaryOperationNode binaryNode = (BinaryOperationNode) e; - putBusyExpr(new BusyExprValue(binaryNode)); - // recursively add expressions - addUseInExpression(binaryNode.getLeftOperand()); - addUseInExpression(binaryNode.getRightOperand()); - } - } - - @Override - public BusyExprStore copy() { - return new BusyExprStore(new LinkedHashSet<>(busyExprValueSet)); + StringJoiner sjStoreVal = new StringJoiner(", "); + for (BusyExprValue busyExprValue : busyExprValueSet) { + sjStoreVal.add(busyExprValue.toString()); } - - @Override - public BusyExprStore leastUpperBound(BusyExprStore other) { - Set busyExprValueSetLub = new LinkedHashSet<>(this.busyExprValueSet); - busyExprValueSetLub.retainAll(other.busyExprValueSet); - - return new BusyExprStore(busyExprValueSetLub); - } - - @Override - public BusyExprStore widenedUpperBound(BusyExprStore previous) { - throw new BugInCF("BusyExprStore.widenedUpperBound was called!"); - } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; - } - - @Override - public String visualize(CFGVisualizer viz) { - String key = "busy expressions"; - if (busyExprValueSet.isEmpty()) { - return viz.visualizeStoreKeyVal(key, "none"); - } - StringJoiner sjStoreVal = new StringJoiner(", "); - for (BusyExprValue busyExprValue : busyExprValueSet) { - sjStoreVal.add(busyExprValue.toString()); - } - return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); - } - - @Override - public String toString() { - return busyExprValueSet.toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BusyExprStore)) { - return false; - } - BusyExprStore other = (BusyExprStore) obj; - return other.busyExprValueSet.equals(this.busyExprValueSet); - } - - @Override - public int hashCode() { - return this.busyExprValueSet.hashCode(); + return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); + } + + @Override + public String toString() { + return busyExprValueSet.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BusyExprStore)) { + return false; } + BusyExprStore other = (BusyExprStore) obj; + return other.busyExprValueSet.equals(this.busyExprValueSet); + } + + @Override + public int hashCode() { + return this.busyExprValueSet.hashCode(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java index ab9259c6184..45c0e30ccfb 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.dataflow.busyexpr; +import java.util.List; import org.checkerframework.dataflow.analysis.BackwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -13,80 +14,76 @@ import org.checkerframework.dataflow.cfg.node.ObjectCreationNode; import org.checkerframework.dataflow.cfg.node.ReturnNode; -import java.util.List; - /** A busy expression transfer function */ public class BusyExprTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements BackwardTransferFunction { + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements BackwardTransferFunction { - @Override - public BusyExprStore initialNormalExitStore( - UnderlyingAST underlyingAST, List returnNodes) { - return new BusyExprStore(); - } + @Override + public BusyExprStore initialNormalExitStore( + UnderlyingAST underlyingAST, List returnNodes) { + return new BusyExprStore(); + } - @Override - public BusyExprStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { - return new BusyExprStore(); - } + @Override + public BusyExprStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { + return new BusyExprStore(); + } - @Override - public RegularTransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } + @Override + public RegularTransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } - @Override - public RegularTransferResult visitAssignment( - AssignmentNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitAssignment(n, p); - BusyExprStore store = transferResult.getRegularStore(); - store.killBusyExpr(n.getTarget()); - store.addUseInExpression(n.getExpression()); - return transferResult; - } + @Override + public RegularTransferResult visitAssignment( + AssignmentNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitAssignment(n, p); + BusyExprStore store = transferResult.getRegularStore(); + store.killBusyExpr(n.getTarget()); + store.addUseInExpression(n.getExpression()); + return transferResult; + } - @Override - public RegularTransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitMethodInvocation(n, p); - BusyExprStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); - } - return transferResult; + @Override + public RegularTransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitMethodInvocation(n, p); + BusyExprStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); } + return transferResult; + } - @Override - public RegularTransferResult visitObjectCreation( - ObjectCreationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitObjectCreation(n, p); - BusyExprStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); - } - return transferResult; + @Override + public RegularTransferResult visitObjectCreation( + ObjectCreationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitObjectCreation(n, p); + BusyExprStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); } + return transferResult; + } - @Override - public RegularTransferResult visitReturn( - ReturnNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitReturn(n, p); - Node result = n.getResult(); - if (result != null) { - BusyExprStore store = transferResult.getRegularStore(); - store.addUseInExpression(result); - } - return transferResult; + @Override + public RegularTransferResult visitReturn( + ReturnNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitReturn(n, p); + Node result = n.getResult(); + if (result != null) { + BusyExprStore store = transferResult.getRegularStore(); + store.addUseInExpression(result); } + return transferResult; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java index 206e7ba0b8b..7d035ac5d48 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java @@ -12,38 +12,38 @@ */ public class BusyExprValue { - /** - * A busy expression is represented by a node, which can be a {@link - * org.checkerframework.dataflow.cfg.node.BinaryOperationNode} - */ - protected final BinaryOperationNode busyExpression; + /** + * A busy expression is represented by a node, which can be a {@link + * org.checkerframework.dataflow.cfg.node.BinaryOperationNode} + */ + protected final BinaryOperationNode busyExpression; - /** - * Create a new busy expression. - * - * @param n a node - */ - public BusyExprValue(BinaryOperationNode n) { - this.busyExpression = n; - } + /** + * Create a new busy expression. + * + * @param n a node + */ + public BusyExprValue(BinaryOperationNode n) { + this.busyExpression = n; + } - @Override - public String toString() { - return this.busyExpression.toString(); - } + @Override + public String toString() { + return this.busyExpression.toString(); + } - @Override - public int hashCode() { - return this.busyExpression.hashCode(); - } + @Override + public int hashCode() { + return this.busyExpression.hashCode(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BusyExprValue)) { - return false; - } - BusyExprValue other = (BusyExprValue) obj; - // Use `equals` to check equality rather than using `==`. - return this.busyExpression.equals(other.busyExpression); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BusyExprValue)) { + return false; } + BusyExprValue other = (BusyExprValue) obj; + // Use `equals` to check equality rather than using `==`. + return this.busyExpression.equals(other.busyExpression); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java index a35ebe8eb97..4b2115bb807 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java @@ -5,7 +5,10 @@ import com.sun.source.tree.MethodTree; import com.sun.source.util.TreePathScanner; import com.sun.tools.javac.util.Log; - +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -14,11 +17,6 @@ import org.checkerframework.javacutil.BasicTypeProcessor; import org.checkerframework.javacutil.TreeUtils; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; - /** * Generate the control flow graph of a given method in a given class. See {@link * org.checkerframework.dataflow.cfg.visualize.CFGVisualizeLauncher} for example usage. @@ -26,176 +24,174 @@ @SupportedAnnotationTypes("*") public class CFGProcessor extends BasicTypeProcessor { - /** - * Qualified name of a specified class which includes a specified method to generate the CFG - * for. - */ - private final String className; - - /** Name of a specified method to generate the CFG for. */ - private final String methodName; + /** + * Qualified name of a specified class which includes a specified method to generate the CFG for. + */ + private final String className; + + /** Name of a specified method to generate the CFG for. */ + private final String methodName; + + /** AST for source file. */ + private @Nullable CompilationUnitTree rootTree; + + /** AST node for the specified class. */ + private @Nullable ClassTree classTree; + + /** AST node for the specified method. */ + private @Nullable MethodTree methodTree; + + /** Result of CFG process; is set by {@link #typeProcessingOver}. */ + private @MonotonicNonNull CFGProcessResult result = null; + + /** + * Create a CFG processor. + * + * @param className the qualified name of class which includes the specified method to generate + * the CFG for + * @param methodName the name of the method to generate the CFG for + */ + public CFGProcessor(String className, String methodName) { + this.className = className; + this.methodName = methodName; + } + + /** + * Get the CFG process result. + * + * @return result of cfg process + */ + public final @Nullable CFGProcessResult getCFGProcessResult() { + return result; + } + + @Override + public void typeProcessingOver() { + if (rootTree == null) { + result = new CFGProcessResult("Root tree is null."); + } else if (classTree == null) { + result = new CFGProcessResult("Method tree is null."); + } else if (methodTree == null) { + result = new CFGProcessResult("Class tree is null."); + } else { + Log log = getCompilerLog(); + if (log.nerrors > 0) { + result = new CFGProcessResult("Compilation issued an error."); + } else { + ControlFlowGraph cfg = CFGBuilder.build(rootTree, methodTree, classTree, processingEnv); + result = new CFGProcessResult(cfg); + } + } + super.typeProcessingOver(); + } + + @Override + protected TreePathScanner createTreePathScanner(CompilationUnitTree root) { + rootTree = root; + return new TreePathScanner() { + @Override + public Void visitClass(ClassTree tree, Void p) { + TypeElement el = TreeUtils.elementFromDeclaration(tree); + if (el.getSimpleName().contentEquals(className)) { + classTree = tree; + } + return super.visitClass(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement el = TreeUtils.elementFromDeclaration(tree); + if (el.getSimpleName().contentEquals(methodName)) { + methodTree = tree; + // Stop execution by throwing an exception. This makes sure that compilation + // does not proceed, and thus the AST is not modified by further phases of the + // compilation (and we save the work to do the compilation). + throw new RuntimeException(); + } + return null; + } + }; + } - /** AST for source file. */ - private @Nullable CompilationUnitTree rootTree; + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } - /** AST node for the specified class. */ - private @Nullable ClassTree classTree; + /** The result of the CFG process, contains the control flow graph when successful. */ + public static class CFGProcessResult { + /** Control flow graph. */ + private final @Nullable ControlFlowGraph controlFlowGraph; - /** AST node for the specified method. */ - private @Nullable MethodTree methodTree; + /** Did the CFG process succeed? */ + private final boolean isSuccess; - /** Result of CFG process; is set by {@link #typeProcessingOver}. */ - private @MonotonicNonNull CFGProcessResult result = null; + /** Error message (when the CFG process failed). */ + private final @Nullable String errMsg; /** - * Create a CFG processor. + * Create the result of the CFG process. Only called if the CFG was built successfully. * - * @param className the qualified name of class which includes the specified method to generate - * the CFG for - * @param methodName the name of the method to generate the CFG for + * @param cfg control flow graph */ - public CFGProcessor(String className, String methodName) { - this.className = className; - this.methodName = methodName; + /*package-private*/ CFGProcessResult(ControlFlowGraph cfg) { + this(cfg, true, null); } /** - * Get the CFG process result. + * Create the result of the CFG process. Only called if the CFG was not built successfully. * - * @return result of cfg process + * @param errMsg the error message */ - public final @Nullable CFGProcessResult getCFGProcessResult() { - return result; + /*package-private*/ CFGProcessResult(String errMsg) { + this(null, false, errMsg); } - @Override - public void typeProcessingOver() { - if (rootTree == null) { - result = new CFGProcessResult("Root tree is null."); - } else if (classTree == null) { - result = new CFGProcessResult("Method tree is null."); - } else if (methodTree == null) { - result = new CFGProcessResult("Class tree is null."); - } else { - Log log = getCompilerLog(); - if (log.nerrors > 0) { - result = new CFGProcessResult("Compilation issued an error."); - } else { - ControlFlowGraph cfg = - CFGBuilder.build(rootTree, methodTree, classTree, processingEnv); - result = new CFGProcessResult(cfg); - } - } - super.typeProcessingOver(); + /** + * Create the result of CFG process. + * + * @param cfg the control flow graph + * @param isSuccess did the CFG process succeed? + * @param errMsg error message (when the CFG process failed) + */ + private CFGProcessResult( + @Nullable ControlFlowGraph cfg, boolean isSuccess, @Nullable String errMsg) { + this.controlFlowGraph = cfg; + this.isSuccess = isSuccess; + this.errMsg = errMsg; } - @Override - protected TreePathScanner createTreePathScanner(CompilationUnitTree root) { - rootTree = root; - return new TreePathScanner() { - @Override - public Void visitClass(ClassTree tree, Void p) { - TypeElement el = TreeUtils.elementFromDeclaration(tree); - if (el.getSimpleName().contentEquals(className)) { - classTree = tree; - } - return super.visitClass(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement el = TreeUtils.elementFromDeclaration(tree); - if (el.getSimpleName().contentEquals(methodName)) { - methodTree = tree; - // Stop execution by throwing an exception. This makes sure that compilation - // does not proceed, and thus the AST is not modified by further phases of the - // compilation (and we save the work to do the compilation). - throw new RuntimeException(); - } - return null; - } - }; + /** + * Check if the CFG process succeeded. + * + * @return true if the CFG process succeeded + */ + @Pure + @EnsuresNonNullIf(expression = "getCFG()", result = true) + @EnsuresNonNullIf(expression = "getErrMsg()", result = false) + @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") + public boolean isSuccess() { + return isSuccess; } - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); + /** + * Returns the generated control flow graph. + * + * @return the generated control flow graph + */ + @Pure + public @Nullable ControlFlowGraph getCFG() { + return controlFlowGraph; } - /** The result of the CFG process, contains the control flow graph when successful. */ - public static class CFGProcessResult { - /** Control flow graph. */ - private final @Nullable ControlFlowGraph controlFlowGraph; - - /** Did the CFG process succeed? */ - private final boolean isSuccess; - - /** Error message (when the CFG process failed). */ - private final @Nullable String errMsg; - - /** - * Create the result of the CFG process. Only called if the CFG was built successfully. - * - * @param cfg control flow graph - */ - /*package-private*/ CFGProcessResult(ControlFlowGraph cfg) { - this(cfg, true, null); - } - - /** - * Create the result of the CFG process. Only called if the CFG was not built successfully. - * - * @param errMsg the error message - */ - /*package-private*/ CFGProcessResult(String errMsg) { - this(null, false, errMsg); - } - - /** - * Create the result of CFG process. - * - * @param cfg the control flow graph - * @param isSuccess did the CFG process succeed? - * @param errMsg error message (when the CFG process failed) - */ - private CFGProcessResult( - @Nullable ControlFlowGraph cfg, boolean isSuccess, @Nullable String errMsg) { - this.controlFlowGraph = cfg; - this.isSuccess = isSuccess; - this.errMsg = errMsg; - } - - /** - * Check if the CFG process succeeded. - * - * @return true if the CFG process succeeded - */ - @Pure - @EnsuresNonNullIf(expression = "getCFG()", result = true) - @EnsuresNonNullIf(expression = "getErrMsg()", result = false) - @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") - public boolean isSuccess() { - return isSuccess; - } - - /** - * Returns the generated control flow graph. - * - * @return the generated control flow graph - */ - @Pure - public @Nullable ControlFlowGraph getCFG() { - return controlFlowGraph; - } - - /** - * Returns the error message. - * - * @return the error message - */ - @Pure - public @Nullable String getErrMsg() { - return errMsg; - } + /** + * Returns the error message. + * + * @return the error message + */ + @Pure + public @Nullable String getErrMsg() { + return errMsg; } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java index e6175f08460..d122d1a7dc7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java @@ -6,7 +6,22 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AnalysisResult; @@ -25,24 +40,6 @@ import org.plumelib.util.UniqueId; import org.plumelib.util.UnmodifiableIdentityHashMap; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; - -import javax.lang.model.type.TypeMirror; - /** * A control flow graph (CFG for short) of a single method. * @@ -53,436 +50,434 @@ */ public class ControlFlowGraph implements UniqueId { - /** The entry block of the control flow graph. */ - protected final SpecialBlock entryBlock; - - /** The regular exit block of the control flow graph. */ - protected final SpecialBlock regularExitBlock; - - /** The exceptional exit block of the control flow graph. */ - protected final SpecialBlock exceptionalExitBlock; - - /** The AST this CFG corresponds to. */ - public final UnderlyingAST underlyingAST; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid(@UnknownInitialization ControlFlowGraph this) { - return uid; - } - - /** - * Maps from AST {@link Tree}s to sets of {@link Node}s. - * - *

    - *
  • Most Trees that produce a value will have at least one corresponding Node. - *
  • Trees that undergo conversions, such as boxing or unboxing, can map to two distinct - * Nodes. The Node for the pre-conversion value is stored in {@link #treeLookup}, while - * the Node for the post-conversion value is stored in {@link #convertedTreeLookup}. - *
- * - * Some of the mapped-to nodes (in both {@link #treeLookup} and {@link #convertedTreeLookup}) do - * not appear in {@link #getAllNodes} because their blocks are not reachable in the control flow - * graph. Dataflow will not compute abstract values for these nodes. - */ - protected final IdentityHashMap> treeLookup; - - /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ - protected final IdentityHashMap> convertedTreeLookup; - - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the - * synthetic tree that is {@code v + 1} or {@code v - 1}. - */ - protected final IdentityHashMap postfixNodeLookup; - - /** - * All return nodes (if any) encountered. Only includes return statements that actually return - * something - */ - protected final List returnNodes; - - /** - * Class declarations that have been encountered when building the control-flow graph for a - * method. - */ - protected final List declaredClasses; - - /** - * Lambdas encountered when building the control-flow graph for a method, variable initializer, - * or initializer. - */ - protected final List declaredLambdas; - - public ControlFlowGraph( - SpecialBlock entryBlock, - SpecialBlockImpl regularExitBlock, - SpecialBlockImpl exceptionalExitBlock, - UnderlyingAST underlyingAST, - IdentityHashMap> treeLookup, - IdentityHashMap> convertedTreeLookup, - IdentityHashMap postfixNodeLookup, - List returnNodes, - List declaredClasses, - List declaredLambdas) { - super(); - this.entryBlock = entryBlock; - this.underlyingAST = underlyingAST; - this.treeLookup = treeLookup; - this.postfixNodeLookup = postfixNodeLookup; - this.convertedTreeLookup = convertedTreeLookup; - this.regularExitBlock = regularExitBlock; - this.exceptionalExitBlock = exceptionalExitBlock; - this.returnNodes = returnNodes; - this.declaredClasses = declaredClasses; - this.declaredLambdas = declaredLambdas; - } - - /** - * Verify that this is a complete and well-formed CFG, i.e. that all internal invariants hold. - * - * @throws IllegalStateException if some internal invariant is violated - */ - public void checkInvariants() { - // TODO: this is a big data structure with many more invariants... - for (Block b : getAllBlocks()) { - - // Each node in the block should have this block as its parent. - for (Node n : b.getNodes()) { - if (!Objects.equals(n.getBlock(), b)) { - throw new IllegalStateException( - "Node " - + n - + " in block " - + b - + " incorrectly believes it belongs to " - + n.getBlock()); - } - } - - // Each successor should have this block in its predecessors. - for (Block succ : b.getSuccessors()) { - if (!succ.getPredecessors().contains(b)) { - throw new IllegalStateException( - "Block " - + b - + " has successor " - + succ - + " but does not appear in that successor's predecessors"); - } - } - - // Each predecessor should have this block in its successors. - for (Block pred : b.getPredecessors()) { - if (!pred.getSuccessors().contains(b)) { - throw new IllegalStateException( - "Block " - + b - + " has predecessor " - + pred - + " but does not appear in that predecessor's successors"); - } - } + /** The entry block of the control flow graph. */ + protected final SpecialBlock entryBlock; + + /** The regular exit block of the control flow graph. */ + protected final SpecialBlock regularExitBlock; + + /** The exceptional exit block of the control flow graph. */ + protected final SpecialBlock exceptionalExitBlock; + + /** The AST this CFG corresponds to. */ + public final UnderlyingAST underlyingAST; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid(@UnknownInitialization ControlFlowGraph this) { + return uid; + } + + /** + * Maps from AST {@link Tree}s to sets of {@link Node}s. + * + *
    + *
  • Most Trees that produce a value will have at least one corresponding Node. + *
  • Trees that undergo conversions, such as boxing or unboxing, can map to two distinct + * Nodes. The Node for the pre-conversion value is stored in {@link #treeLookup}, while the + * Node for the post-conversion value is stored in {@link #convertedTreeLookup}. + *
+ * + * Some of the mapped-to nodes (in both {@link #treeLookup} and {@link #convertedTreeLookup}) do + * not appear in {@link #getAllNodes} because their blocks are not reachable in the control flow + * graph. Dataflow will not compute abstract values for these nodes. + */ + protected final IdentityHashMap> treeLookup; + + /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ + protected final IdentityHashMap> convertedTreeLookup; + + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic + * tree that is {@code v + 1} or {@code v - 1}. + */ + protected final IdentityHashMap postfixNodeLookup; + + /** + * All return nodes (if any) encountered. Only includes return statements that actually return + * something + */ + protected final List returnNodes; + + /** + * Class declarations that have been encountered when building the control-flow graph for a + * method. + */ + protected final List declaredClasses; + + /** + * Lambdas encountered when building the control-flow graph for a method, variable initializer, or + * initializer. + */ + protected final List declaredLambdas; + + public ControlFlowGraph( + SpecialBlock entryBlock, + SpecialBlockImpl regularExitBlock, + SpecialBlockImpl exceptionalExitBlock, + UnderlyingAST underlyingAST, + IdentityHashMap> treeLookup, + IdentityHashMap> convertedTreeLookup, + IdentityHashMap postfixNodeLookup, + List returnNodes, + List declaredClasses, + List declaredLambdas) { + super(); + this.entryBlock = entryBlock; + this.underlyingAST = underlyingAST; + this.treeLookup = treeLookup; + this.postfixNodeLookup = postfixNodeLookup; + this.convertedTreeLookup = convertedTreeLookup; + this.regularExitBlock = regularExitBlock; + this.exceptionalExitBlock = exceptionalExitBlock; + this.returnNodes = returnNodes; + this.declaredClasses = declaredClasses; + this.declaredLambdas = declaredLambdas; + } + + /** + * Verify that this is a complete and well-formed CFG, i.e. that all internal invariants hold. + * + * @throws IllegalStateException if some internal invariant is violated + */ + public void checkInvariants() { + // TODO: this is a big data structure with many more invariants... + for (Block b : getAllBlocks()) { + + // Each node in the block should have this block as its parent. + for (Node n : b.getNodes()) { + if (!Objects.equals(n.getBlock(), b)) { + throw new IllegalStateException( + "Node " + + n + + " in block " + + b + + " incorrectly believes it belongs to " + + n.getBlock()); } - } - - /** - * Returns the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for - * trees that don't produce a value. - * - * @param t a tree - * @return the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for - * trees that don't produce a value - */ - public @Nullable Set getNodesCorrespondingToTree(Tree t) { - if (convertedTreeLookup.containsKey(t)) { - return convertedTreeLookup.get(t); - } else { - return treeLookup.get(t); + } + + // Each successor should have this block in its predecessors. + for (Block succ : b.getSuccessors()) { + if (!succ.getPredecessors().contains(b)) { + throw new IllegalStateException( + "Block " + + b + + " has successor " + + succ + + " but does not appear in that successor's predecessors"); } - } - - /** - * Returns the entry block of the control flow graph. - * - * @return the entry block of the control flow graph - */ - public SpecialBlock getEntryBlock() { - return entryBlock; - } - - public List getReturnNodes() { - return returnNodes; - } - - public SpecialBlock getRegularExitBlock() { - return regularExitBlock; - } - - public SpecialBlock getExceptionalExitBlock() { - return exceptionalExitBlock; - } - - /** - * Returns the AST this CFG corresponds to. - * - * @return the AST this CFG corresponds to - */ - public UnderlyingAST getUnderlyingAST() { - return underlyingAST; - } - - /** - * Returns the set of all basic blocks in this control flow graph. - * - * @return the set of all basic blocks in this control flow graph - */ - public Set getAllBlocks( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { - Set visited = new LinkedHashSet<>(); - // worklist is always a subset of visited; any block in worklist is also in visited. - Queue worklist = new ArrayDeque<>(); - Block cur = entryBlock; - visited.add(entryBlock); - - // traverse the whole control flow graph - while (true) { - if (cur == null) { - break; - } - - for (Block b : cur.getSuccessors()) { - if (visited.add(b)) { - worklist.add(b); - } - } - - cur = worklist.poll(); + } + + // Each predecessor should have this block in its successors. + for (Block pred : b.getPredecessors()) { + if (!pred.getSuccessors().contains(b)) { + throw new IllegalStateException( + "Block " + + b + + " has predecessor " + + pred + + " but does not appear in that predecessor's successors"); } - - return visited; + } } - - /** - * Returns all nodes in this control flow graph. - * - * @return all nodes in this control flow graph - */ - public List getAllNodes( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { - List result = new ArrayList<>(); - for (Block b : getAllBlocks()) { - result.addAll(b.getNodes()); - } - return result; + } + + /** + * Returns the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for + * trees that don't produce a value. + * + * @param t a tree + * @return the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for + * trees that don't produce a value + */ + public @Nullable Set getNodesCorrespondingToTree(Tree t) { + if (convertedTreeLookup.containsKey(t)) { + return convertedTreeLookup.get(t); + } else { + return treeLookup.get(t); } - - /** - * Returns the set of all basic blocks in this control flow graph, except those that are - * only reachable via an exception whose type is ignored by parameter {@code - * shouldIgnoreException}. - * - * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be - * ignored - * @return the set of all basic blocks in this control flow graph, except those that are - * only reachable via an exception whose type is ignored by {@code shouldIgnoreException} - */ - public Set getAllBlocks( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, - Function shouldIgnoreException) { - // This is the return value of the method. - Set visited = new LinkedHashSet<>(); - // `worklist` is always a subset of `visited`; any block in `worklist` is also in `visited`. - Queue worklist = new ArrayDeque<>(); - Block cur = entryBlock; - visited.add(entryBlock); - - // Traverse the whole control flow graph. - while (cur != null) { - if (cur instanceof ExceptionBlock) { - for (Map.Entry> entry : - ((ExceptionBlock) cur).getExceptionalSuccessors().entrySet()) { - if (!shouldIgnoreException.apply(entry.getKey())) { - for (Block b : entry.getValue()) { - if (visited.add(b)) { - worklist.add(b); - } - } - } - } - Block b = ((SingleSuccessorBlockImpl) cur).getSuccessor(); - if (b != null && visited.add(b)) { - worklist.add(b); - } - - } else { - for (Block b : cur.getSuccessors()) { - if (visited.add(b)) { - worklist.add(b); - } - } - } - cur = worklist.poll(); + } + + /** + * Returns the entry block of the control flow graph. + * + * @return the entry block of the control flow graph + */ + public SpecialBlock getEntryBlock() { + return entryBlock; + } + + public List getReturnNodes() { + return returnNodes; + } + + public SpecialBlock getRegularExitBlock() { + return regularExitBlock; + } + + public SpecialBlock getExceptionalExitBlock() { + return exceptionalExitBlock; + } + + /** + * Returns the AST this CFG corresponds to. + * + * @return the AST this CFG corresponds to + */ + public UnderlyingAST getUnderlyingAST() { + return underlyingAST; + } + + /** + * Returns the set of all basic blocks in this control flow graph. + * + * @return the set of all basic blocks in this control flow graph + */ + public Set getAllBlocks( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { + Set visited = new LinkedHashSet<>(); + // worklist is always a subset of visited; any block in worklist is also in visited. + Queue worklist = new ArrayDeque<>(); + Block cur = entryBlock; + visited.add(entryBlock); + + // traverse the whole control flow graph + while (true) { + if (cur == null) { + break; + } + + for (Block b : cur.getSuccessors()) { + if (visited.add(b)) { + worklist.add(b); } + } - return visited; + cur = worklist.poll(); } - /** - * Returns the list of all nodes in this control flow graph, except those that are only - * reachable via an exception whose type is ignored by parameter {@code shouldIgnoreException}. - * - * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be - * ignored - * @return the list of all nodes in this control flow graph, except those that are only - * reachable via an exception whose type is ignored by {@code shouldIgnoreException} - */ - public List getAllNodes( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, - Function shouldIgnoreException) { - List result = new ArrayList<>(); - getAllBlocks(shouldIgnoreException).forEach(b -> result.addAll(b.getNodes())); - return result; + return visited; + } + + /** + * Returns all nodes in this control flow graph. + * + * @return all nodes in this control flow graph + */ + public List getAllNodes( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { + List result = new ArrayList<>(); + for (Block b : getAllBlocks()) { + result.addAll(b.getNodes()); } - - /** - * Returns all basic blocks in this control flow graph, in reversed depth-first postorder. - * Blocks may appear more than once in the sequence. - * - * @return the list of all basic block in this control flow graph in reversed depth-first - * postorder sequence - */ - public List getDepthFirstOrderedBlocks() { - List dfsOrderResult = new ArrayList<>(); - Set visited = new HashSet<>(); - // worklist can contain values that are not yet in visited. - Deque worklist = new ArrayDeque<>(); - worklist.add(entryBlock); - while (!worklist.isEmpty()) { - Block cur = worklist.getLast(); - if (visited.contains(cur)) { - dfsOrderResult.add(cur); - worklist.removeLast(); - } else { - visited.add(cur); - - for (Block b : cur.getSuccessors()) { - if (!visited.contains(b)) { - worklist.add(b); - } - } + return result; + } + + /** + * Returns the set of all basic blocks in this control flow graph, except those that are + * only reachable via an exception whose type is ignored by parameter {@code + * shouldIgnoreException}. + * + * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be + * ignored + * @return the set of all basic blocks in this control flow graph, except those that are + * only reachable via an exception whose type is ignored by {@code shouldIgnoreException} + */ + public Set getAllBlocks( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, + Function shouldIgnoreException) { + // This is the return value of the method. + Set visited = new LinkedHashSet<>(); + // `worklist` is always a subset of `visited`; any block in `worklist` is also in `visited`. + Queue worklist = new ArrayDeque<>(); + Block cur = entryBlock; + visited.add(entryBlock); + + // Traverse the whole control flow graph. + while (cur != null) { + if (cur instanceof ExceptionBlock) { + for (Map.Entry> entry : + ((ExceptionBlock) cur).getExceptionalSuccessors().entrySet()) { + if (!shouldIgnoreException.apply(entry.getKey())) { + for (Block b : entry.getValue()) { + if (visited.add(b)) { + worklist.add(b); + } } + } + } + Block b = ((SingleSuccessorBlockImpl) cur).getSuccessor(); + if (b != null && visited.add(b)) { + worklist.add(b); } - Collections.reverse(dfsOrderResult); - return dfsOrderResult; - } - - /** - * Returns an unmodifiable view of the tree-lookup map. Ignores convertedTreeLookup, though - * {@link #getNodesCorrespondingToTree} uses that field. - * - * @return the unmodifiable tree-lookup map - */ - public UnmodifiableIdentityHashMap> getTreeLookup() { - return UnmodifiableIdentityHashMap.wrap(treeLookup); - } - - /** - * Returns an unmodifiable view of the lookup-map of the binary tree for a postfix expression. - * - * @return the unmodifiable lookup-map of the binary tree for a postfix expression - */ - public UnmodifiableIdentityHashMap getPostfixNodeLookup() { - return UnmodifiableIdentityHashMap.wrap(postfixNodeLookup); - } - - /** - * Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in - * the CFG, or null otherwise. - * - * @param t a tree that might correspond to a node in the CFG - * @return the method that contains {@code t}'s Node, or null - */ - public @Nullable MethodTree getContainingMethod(Tree t) { - if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { - UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; - return cfgMethod.getMethod(); + } else { + for (Block b : cur.getSuccessors()) { + if (visited.add(b)) { + worklist.add(b); + } } - return null; + } + cur = worklist.poll(); } - /** - * Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in - * the CFG, or null otherwise. - * - * @param t a tree that might be within a class - * @return the class that contains the given tree, or null - */ - public @Nullable ClassTree getContainingClass(Tree t) { - if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { - UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; - return cfgMethod.getClassTree(); + return visited; + } + + /** + * Returns the list of all nodes in this control flow graph, except those that are only + * reachable via an exception whose type is ignored by parameter {@code shouldIgnoreException}. + * + * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be + * ignored + * @return the list of all nodes in this control flow graph, except those that are only + * reachable via an exception whose type is ignored by {@code shouldIgnoreException} + */ + public List getAllNodes( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, + Function shouldIgnoreException) { + List result = new ArrayList<>(); + getAllBlocks(shouldIgnoreException).forEach(b -> result.addAll(b.getNodes())); + return result; + } + + /** + * Returns all basic blocks in this control flow graph, in reversed depth-first postorder. Blocks + * may appear more than once in the sequence. + * + * @return the list of all basic block in this control flow graph in reversed depth-first + * postorder sequence + */ + public List getDepthFirstOrderedBlocks() { + List dfsOrderResult = new ArrayList<>(); + Set visited = new HashSet<>(); + // worklist can contain values that are not yet in visited. + Deque worklist = new ArrayDeque<>(); + worklist.add(entryBlock); + while (!worklist.isEmpty()) { + Block cur = worklist.getLast(); + if (visited.contains(cur)) { + dfsOrderResult.add(cur); + worklist.removeLast(); + } else { + visited.add(cur); + + for (Block b : cur.getSuccessors()) { + if (!visited.contains(b)) { + worklist.add(b); + } } - return null; + } } - public List getDeclaredClasses() { - return declaredClasses; + Collections.reverse(dfsOrderResult); + return dfsOrderResult; + } + + /** + * Returns an unmodifiable view of the tree-lookup map. Ignores convertedTreeLookup, though {@link + * #getNodesCorrespondingToTree} uses that field. + * + * @return the unmodifiable tree-lookup map + */ + public UnmodifiableIdentityHashMap> getTreeLookup() { + return UnmodifiableIdentityHashMap.wrap(treeLookup); + } + + /** + * Returns an unmodifiable view of the lookup-map of the binary tree for a postfix expression. + * + * @return the unmodifiable lookup-map of the binary tree for a postfix expression + */ + public UnmodifiableIdentityHashMap getPostfixNodeLookup() { + return UnmodifiableIdentityHashMap.wrap(postfixNodeLookup); + } + + /** + * Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in + * the CFG, or null otherwise. + * + * @param t a tree that might correspond to a node in the CFG + * @return the method that contains {@code t}'s Node, or null + */ + public @Nullable MethodTree getContainingMethod(Tree t) { + if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; + return cfgMethod.getMethod(); } - - public List getDeclaredLambdas() { - return declaredLambdas; + return null; + } + + /** + * Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in the + * CFG, or null otherwise. + * + * @param t a tree that might be within a class + * @return the class that contains the given tree, or null + */ + public @Nullable ClassTree getContainingClass(Tree t) { + if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; + return cfgMethod.getClassTree(); } - - @Override - public String toString() { - CFGVisualizer viz = new StringCFGVisualizer<>(); - viz.init(Collections.singletonMap("verbose", true)); - Map res = viz.visualize(this, this.getEntryBlock(), null); - viz.shutdown(); - if (res == null) { - return "unvisualizable " + getClass().getCanonicalName(); - } - String stringGraph = (String) res.get("stringGraph"); - return stringGraph == null - ? "unvisualizable " + getClass().getCanonicalName() - : stringGraph; + return null; + } + + public List getDeclaredClasses() { + return declaredClasses; + } + + public List getDeclaredLambdas() { + return declaredLambdas; + } + + @Override + public String toString() { + CFGVisualizer viz = new StringCFGVisualizer<>(); + viz.init(Collections.singletonMap("verbose", true)); + Map res = viz.visualize(this, this.getEntryBlock(), null); + viz.shutdown(); + if (res == null) { + return "unvisualizable " + getClass().getCanonicalName(); + } + String stringGraph = (String) res.get("stringGraph"); + return stringGraph == null ? "unvisualizable " + getClass().getCanonicalName() : stringGraph; + } + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public String toStringDebug() { + String className = this.getClass().getSimpleName(); + if (className.equals("ControlFlowGraph") && this.getClass() != ControlFlowGraph.class) { + className = this.getClass().getCanonicalName(); } - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public String toStringDebug() { - String className = this.getClass().getSimpleName(); - if (className.equals("ControlFlowGraph") && this.getClass() != ControlFlowGraph.class) { - className = this.getClass().getCanonicalName(); - } - - StringJoiner result = new StringJoiner(String.format("%n ")); - result.add(className + " #" + getUid() + " {"); - result.add("entryBlock=" + entryBlock); - result.add("regularExitBlock=" + regularExitBlock); - result.add("exceptionalExitBlock=" + exceptionalExitBlock); - String astString = underlyingAST.toString().replaceAll("\\s", " "); - if (astString.length() > 65) { - astString = "\"" + astString.substring(0, 60) + "\""; - } - result.add("underlyingAST=" + underlyingAST); - result.add("treeLookup=" + AnalysisResult.treeLookupToString(treeLookup)); - result.add("convertedTreeLookup=" + AnalysisResult.treeLookupToString(convertedTreeLookup)); - result.add("postfixLookup=" + postfixNodeLookup); - result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); - result.add("declaredClasses=" + declaredClasses); - result.add("declaredLambdas=" + declaredLambdas); - result.add("}"); - return result.toString(); + StringJoiner result = new StringJoiner(String.format("%n ")); + result.add(className + " #" + getUid() + " {"); + result.add("entryBlock=" + entryBlock); + result.add("regularExitBlock=" + regularExitBlock); + result.add("exceptionalExitBlock=" + exceptionalExitBlock); + String astString = underlyingAST.toString().replaceAll("\\s", " "); + if (astString.length() > 65) { + astString = "\"" + astString.substring(0, 60) + "\""; } + result.add("underlyingAST=" + underlyingAST); + result.add("treeLookup=" + AnalysisResult.treeLookupToString(treeLookup)); + result.add("convertedTreeLookup=" + AnalysisResult.treeLookupToString(convertedTreeLookup)); + result.add("postfixLookup=" + postfixNodeLookup); + result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); + result.add("declaredClasses=" + declaredClasses); + result.add("declaredLambdas=" + declaredLambdas); + result.add("}"); + return result.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java index d08e7174eb7..09f41866e10 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java @@ -4,247 +4,242 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; - +import java.util.concurrent.atomic.AtomicLong; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.plumelib.util.StringsPlume; import org.plumelib.util.UniqueId; -import java.util.concurrent.atomic.AtomicLong; - /** * Represents an abstract syntax tree of type {@link Tree} that underlies a given control flow * graph. */ public abstract class UnderlyingAST implements UniqueId { - /** The kinds of underlying ASTs. */ - public enum Kind { - /** The underlying code is a whole method. */ - METHOD, - /** The underlying code is a lambda expression. */ - LAMBDA, + /** The kinds of underlying ASTs. */ + public enum Kind { + /** The underlying code is a whole method. */ + METHOD, + /** The underlying code is a lambda expression. */ + LAMBDA, + + /** The underlying code is an arbitrary Java statement or expression. */ + ARBITRARY_CODE, + } + + /** The kind of the underlying AST. */ + protected final Kind kind; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid(@UnknownInitialization UnderlyingAST this) { + return uid; + } + + /** + * Creates an UnderlyingAST. + * + * @param kind the kind of the AST + */ + protected UnderlyingAST(Kind kind) { + this.kind = kind; + } + + /** + * Returns the code that corresponds to the CFG. For a method or lamdda, this returns the body. + * For other constructs, it returns the tree itself (a statement or expression). + * + * @return the code that corresponds to the CFG + */ + public abstract Tree getCode(); + + public Kind getKind() { + return kind; + } + + /** If the underlying AST is a method. */ + public static class CFGMethod extends UnderlyingAST { + + /** The method declaration. */ + protected final MethodTree method; + + /** The class tree this method belongs to. */ + protected final ClassTree classTree; + + public CFGMethod(MethodTree method, ClassTree classTree) { + super(Kind.METHOD); + this.method = method; + this.classTree = classTree; + } + + @Override + public Tree getCode() { + return method.getBody(); + } + + public MethodTree getMethod() { + return method; + } + + /** + * Returns the name of the method. + * + * @return the name of the method + */ + public String getMethodName() { + return method.getName().toString(); + } + + /** + * Returns the class tree this method belongs to. + * + * @return the class tree this method belongs to + */ + public ClassTree getClassTree() { + return classTree; + } + + /** + * Returns the simple name of the enclosing class. + * + * @return the simple name of the enclosing class + */ + public String getSimpleClassName() { + return classTree.getSimpleName().toString(); + } - /** The underlying code is an arbitrary Java statement or expression. */ - ARBITRARY_CODE, + @Override + public String toString() { + return StringsPlume.joinLines("CFGMethod(", method, ")"); } + } + + /** If the underlying AST is a lambda. */ + public static class CFGLambda extends UnderlyingAST { + + /** The lambda expression. */ + private final LambdaExpressionTree lambda; - /** The kind of the underlying AST. */ - protected final Kind kind; + /** The enclosing class of the lambda. */ + private final ClassTree classTree; - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); + /** The enclosing method of the lambda. */ + private final @Nullable MethodTree enclosingMethod; - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); + /** + * Create a new CFGLambda. + * + * @param lambda the lambda expression + * @param classTree the enclosing class of the lambda + * @param enclosingMethod the enclosing method of the lambda + */ + public CFGLambda( + LambdaExpressionTree lambda, ClassTree classTree, @Nullable MethodTree enclosingMethod) { + super(Kind.LAMBDA); + this.lambda = lambda; + this.enclosingMethod = enclosingMethod; + this.classTree = classTree; + } @Override - public long getUid(@UnknownInitialization UnderlyingAST this) { - return uid; + public Tree getCode() { + return lambda.getBody(); } /** - * Creates an UnderlyingAST. + * Returns the lambda expression tree. * - * @param kind the kind of the AST + * @return the lambda expression tree */ - protected UnderlyingAST(Kind kind) { - this.kind = kind; + public LambdaExpressionTree getLambdaTree() { + return lambda; } /** - * Returns the code that corresponds to the CFG. For a method or lamdda, this returns the body. - * For other constructs, it returns the tree itself (a statement or expression). + * Returns the enclosing class of the lambda. * - * @return the code that corresponds to the CFG + * @return the enclosing class of the lambda */ - public abstract Tree getCode(); - - public Kind getKind() { - return kind; - } - - /** If the underlying AST is a method. */ - public static class CFGMethod extends UnderlyingAST { - - /** The method declaration. */ - protected final MethodTree method; - - /** The class tree this method belongs to. */ - protected final ClassTree classTree; - - public CFGMethod(MethodTree method, ClassTree classTree) { - super(Kind.METHOD); - this.method = method; - this.classTree = classTree; - } - - @Override - public Tree getCode() { - return method.getBody(); - } - - public MethodTree getMethod() { - return method; - } - - /** - * Returns the name of the method. - * - * @return the name of the method - */ - public String getMethodName() { - return method.getName().toString(); - } - - /** - * Returns the class tree this method belongs to. - * - * @return the class tree this method belongs to - */ - public ClassTree getClassTree() { - return classTree; - } - - /** - * Returns the simple name of the enclosing class. - * - * @return the simple name of the enclosing class - */ - public String getSimpleClassName() { - return classTree.getSimpleName().toString(); - } - - @Override - public String toString() { - return StringsPlume.joinLines("CFGMethod(", method, ")"); - } - } - - /** If the underlying AST is a lambda. */ - public static class CFGLambda extends UnderlyingAST { - - /** The lambda expression. */ - private final LambdaExpressionTree lambda; - - /** The enclosing class of the lambda. */ - private final ClassTree classTree; - - /** The enclosing method of the lambda. */ - private final @Nullable MethodTree enclosingMethod; - - /** - * Create a new CFGLambda. - * - * @param lambda the lambda expression - * @param classTree the enclosing class of the lambda - * @param enclosingMethod the enclosing method of the lambda - */ - public CFGLambda( - LambdaExpressionTree lambda, - ClassTree classTree, - @Nullable MethodTree enclosingMethod) { - super(Kind.LAMBDA); - this.lambda = lambda; - this.enclosingMethod = enclosingMethod; - this.classTree = classTree; - } - - @Override - public Tree getCode() { - return lambda.getBody(); - } - - /** - * Returns the lambda expression tree. - * - * @return the lambda expression tree - */ - public LambdaExpressionTree getLambdaTree() { - return lambda; - } - - /** - * Returns the enclosing class of the lambda. - * - * @return the enclosing class of the lambda - */ - public ClassTree getClassTree() { - return classTree; - } - - /** - * Returns the simple name of the enclosing class. - * - * @return the simple name of the enclosing class - */ - public String getSimpleClassName() { - return classTree.getSimpleName().toString(); - } - - /** - * Returns the enclosing method of the lambda. - * - * @return the enclosing method of the lambda, or {@code null} if there is no enclosing - * method - */ - public @Nullable MethodTree getEnclosingMethod() { - return enclosingMethod; - } - - /** - * Returns the name of the enclosing method of the lambda. - * - * @return the name of the enclosing method of the lambda, or {@code null} if there is no - * enclosing method - */ - public @Nullable String getEnclosingMethodName() { - return enclosingMethod == null ? null : enclosingMethod.getName().toString(); - } - - @Override - public String toString() { - return StringsPlume.joinLines("CFGLambda(", lambda, ")"); - } + public ClassTree getClassTree() { + return classTree; } /** - * If the underlying AST is a statement or expression. This is for field definitions (with - * initializers) and initializer blocks. + * Returns the simple name of the enclosing class. + * + * @return the simple name of the enclosing class + */ + public String getSimpleClassName() { + return classTree.getSimpleName().toString(); + } + + /** + * Returns the enclosing method of the lambda. + * + * @return the enclosing method of the lambda, or {@code null} if there is no enclosing method + */ + public @Nullable MethodTree getEnclosingMethod() { + return enclosingMethod; + } + + /** + * Returns the name of the enclosing method of the lambda. + * + * @return the name of the enclosing method of the lambda, or {@code null} if there is no + * enclosing method + */ + public @Nullable String getEnclosingMethodName() { + return enclosingMethod == null ? null : enclosingMethod.getName().toString(); + } + + @Override + public String toString() { + return StringsPlume.joinLines("CFGLambda(", lambda, ")"); + } + } + + /** + * If the underlying AST is a statement or expression. This is for field definitions (with + * initializers) and initializer blocks. + */ + public static class CFGStatement extends UnderlyingAST { + + protected final Tree code; + + /** The class tree this method belongs to. */ + protected final ClassTree classTree; + + public CFGStatement(Tree code, ClassTree classTree) { + super(Kind.ARBITRARY_CODE); + this.code = code; + this.classTree = classTree; + } + + @Override + public Tree getCode() { + return code; + } + + public ClassTree getClassTree() { + return classTree; + } + + /** + * Returns the simple name of the enclosing class. + * + * @return the simple name of the enclosing class */ - public static class CFGStatement extends UnderlyingAST { - - protected final Tree code; - - /** The class tree this method belongs to. */ - protected final ClassTree classTree; - - public CFGStatement(Tree code, ClassTree classTree) { - super(Kind.ARBITRARY_CODE); - this.code = code; - this.classTree = classTree; - } - - @Override - public Tree getCode() { - return code; - } - - public ClassTree getClassTree() { - return classTree; - } - - /** - * Returns the simple name of the enclosing class. - * - * @return the simple name of the enclosing class - */ - public String getSimpleClassName() { - return classTree.getSimpleName().toString(); - } - - @Override - public String toString() { - return StringsPlume.joinLines("CFGStatement(", code, ")"); - } + public String getSimpleClassName() { + return classTree.getSimpleName().toString(); + } + + @Override + public String toString() { + return StringsPlume.joinLines("CFGStatement(", code, ")"); } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java index 007f48fe755..ed18d829acf 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java @@ -1,71 +1,70 @@ package org.checkerframework.dataflow.cfg.block; +import java.util.List; +import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.qual.Pure; import org.plumelib.util.UniqueId; -import java.util.List; -import java.util.Set; - /** Represents a basic block in a control flow graph. */ public interface Block extends UniqueId { - /** The types of basic blocks. */ - enum BlockType { + /** The types of basic blocks. */ + enum BlockType { - /** A regular basic block. */ - REGULAR_BLOCK, + /** A regular basic block. */ + REGULAR_BLOCK, - /** A conditional basic block. */ - CONDITIONAL_BLOCK, + /** A conditional basic block. */ + CONDITIONAL_BLOCK, - /** A special basic block. */ - SPECIAL_BLOCK, + /** A special basic block. */ + SPECIAL_BLOCK, - /** A basic block that can throw an exception. */ - EXCEPTION_BLOCK, - } + /** A basic block that can throw an exception. */ + EXCEPTION_BLOCK, + } - /** - * Returns the type of this basic block. - * - * @return the type of this basic block - */ - BlockType getType(); + /** + * Returns the type of this basic block. + * + * @return the type of this basic block + */ + BlockType getType(); - /** - * Returns the predecessors of this basic block. - * - * @return the predecessors of this basic block - */ - Set getPredecessors(); + /** + * Returns the predecessors of this basic block. + * + * @return the predecessors of this basic block + */ + Set getPredecessors(); - /** - * Returns the successors of this basic block. - * - * @return the successors of this basic block - */ - Set getSuccessors(); + /** + * Returns the successors of this basic block. + * + * @return the successors of this basic block + */ + Set getSuccessors(); - /** - * Returns the nodes contained within this basic block. The list may be empty. - * - *

The following invariant holds. - * - *

-     * forall n in getNodes() :: n.getBlock() == this
-     * 
- * - * @return the nodes contained within this basic block - */ - @Pure - List getNodes(); + /** + * Returns the nodes contained within this basic block. The list may be empty. + * + *

The following invariant holds. + * + *

+   * forall n in getNodes() :: n.getBlock() == this
+   * 
+ * + * @return the nodes contained within this basic block + */ + @Pure + List getNodes(); - /** - * Returns the last node of this block, or null if none. - * - * @return the last node of this block or {@code null} - */ - @Nullable Node getLastNode(); + /** + * Returns the last node of this block, or null if none. + * + * @return the last node of this block or {@code null} + */ + @Nullable Node getLastNode(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java index 3e007e49f01..8c2fadfeaab 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java @@ -1,59 +1,58 @@ package org.checkerframework.dataflow.cfg.block; -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.plumelib.util.ArraySet; - import java.util.Set; import java.util.concurrent.atomic.AtomicLong; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.plumelib.util.ArraySet; /** Base class of the {@link Block} implementation hierarchy. */ public abstract class BlockImpl implements Block { - /** The type of this basic block. */ - protected final BlockType type; - - /** The set of predecessors. */ - protected final Set predecessors; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid(@UnknownInitialization BlockImpl this) { - return uid; - } - - /** - * Create a new BlockImpl. - * - * @param type the type of this basic block - */ - protected BlockImpl(BlockType type) { - this.type = type; - // Most blocks have few predecessors. - this.predecessors = new ArraySet<>(2); - } - - @Override - public BlockType getType() { - return type; - } - - @Override - public Set getPredecessors() { - // Not "Collections.unmodifiableSet(predecessors)" which has nondeterministic iteration - // order. - return new ArraySet<>(predecessors); - } - - public void addPredecessor(BlockImpl pred) { - predecessors.add(pred); - } - - public void removePredecessor(BlockImpl pred) { - predecessors.remove(pred); - } + /** The type of this basic block. */ + protected final BlockType type; + + /** The set of predecessors. */ + protected final Set predecessors; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid(@UnknownInitialization BlockImpl this) { + return uid; + } + + /** + * Create a new BlockImpl. + * + * @param type the type of this basic block + */ + protected BlockImpl(BlockType type) { + this.type = type; + // Most blocks have few predecessors. + this.predecessors = new ArraySet<>(2); + } + + @Override + public BlockType getType() { + return type; + } + + @Override + public Set getPredecessors() { + // Not "Collections.unmodifiableSet(predecessors)" which has nondeterministic iteration + // order. + return new ArraySet<>(predecessors); + } + + public void addPredecessor(BlockImpl pred) { + predecessors.add(pred); + } + + public void removePredecessor(BlockImpl pred) { + predecessors.remove(pred); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java index 334b8f1ffce..6ee0d627905 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java @@ -9,45 +9,45 @@ /** Represents a conditional basic block. */ public interface ConditionalBlock extends Block { - /** - * Returns the entry block of the then branch. - * - * @return the entry block of the then branch - */ - Block getThenSuccessor(); - - /** - * Returns the entry block of the else branch. - * - * @return the entry block of the else branch - */ - Block getElseSuccessor(); - - /** - * Returns the flow rule for information flowing from this block to its then successor. - * - * @return the flow rule for information flowing from this block to its then successor - */ - FlowRule getThenFlowRule(); - - /** - * Returns the flow rule for information flowing from this block to its else successor. - * - * @return the flow rule for information flowing from this block to its else successor - */ - FlowRule getElseFlowRule(); - - /** - * Set the flow rule for information flowing from this block to its then successor. - * - * @param rule the new flow rule for information flowing from this block to its then successor - */ - void setThenFlowRule(FlowRule rule); - - /** - * Set the flow rule for information flowing from this block to its else successor. - * - * @param rule the new flow rule for information flowing from this block to its else successor - */ - void setElseFlowRule(FlowRule rule); + /** + * Returns the entry block of the then branch. + * + * @return the entry block of the then branch + */ + Block getThenSuccessor(); + + /** + * Returns the entry block of the else branch. + * + * @return the entry block of the else branch + */ + Block getElseSuccessor(); + + /** + * Returns the flow rule for information flowing from this block to its then successor. + * + * @return the flow rule for information flowing from this block to its then successor + */ + FlowRule getThenFlowRule(); + + /** + * Returns the flow rule for information flowing from this block to its else successor. + * + * @return the flow rule for information flowing from this block to its else successor + */ + FlowRule getElseFlowRule(); + + /** + * Set the flow rule for information flowing from this block to its then successor. + * + * @param rule the new flow rule for information flowing from this block to its then successor + */ + void setThenFlowRule(FlowRule rule); + + /** + * Set the flow rule for information flowing from this block to its else successor. + * + * @param rule the new flow rule for information flowing from this block to its else successor + */ + void setElseFlowRule(FlowRule rule); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java index 7e0c08f1af1..f7edfa36698 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java @@ -1,119 +1,116 @@ package org.checkerframework.dataflow.cfg.block; +import java.util.Collections; +import java.util.List; +import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store.FlowRule; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.ArraySet; -import java.util.Collections; -import java.util.List; -import java.util.Set; - /** Implementation of a conditional basic block. */ public class ConditionalBlockImpl extends BlockImpl implements ConditionalBlock { - /** Successor of the then branch. */ - protected @Nullable BlockImpl thenSuccessor; - - /** Successor of the else branch. */ - protected @Nullable BlockImpl elseSuccessor; - - /** - * The initial value says that the THEN store before a conditional block flows to BOTH of the - * stores of the then successor. - */ - protected FlowRule thenFlowRule = FlowRule.THEN_TO_BOTH; - - /** - * The initial value says that the ELSE store before a conditional block flows to BOTH of the - * stores of the else successor. - */ - protected FlowRule elseFlowRule = FlowRule.ELSE_TO_BOTH; - - /** - * Initialize an empty conditional basic block to be filled with contents and linked to other - * basic blocks later. - */ - public ConditionalBlockImpl() { - super(BlockType.CONDITIONAL_BLOCK); - } - - /** Set the then branch successor. */ - public void setThenSuccessor(BlockImpl b) { - thenSuccessor = b; - b.addPredecessor(this); - } - - /** Set the else branch successor. */ - public void setElseSuccessor(BlockImpl b) { - elseSuccessor = b; - b.addPredecessor(this); - } - - @Override - public Block getThenSuccessor() { - if (thenSuccessor == null) { - throw new BugInCF( - "Requested thenSuccessor for conditional block before initialization"); - } - return thenSuccessor; - } - - @Override - public Block getElseSuccessor() { - if (elseSuccessor == null) { - throw new BugInCF( - "Requested elseSuccessor for conditional block before initialization"); - } - return elseSuccessor; - } - - @Override - public Set getSuccessors() { - Set result = new ArraySet<>(2); - result.add(getThenSuccessor()); - result.add(getElseSuccessor()); - return result; - } - - @Override - public FlowRule getThenFlowRule() { - return thenFlowRule; - } - - @Override - public FlowRule getElseFlowRule() { - return elseFlowRule; - } - - @Override - public void setThenFlowRule(FlowRule rule) { - thenFlowRule = rule; - } - - @Override - public void setElseFlowRule(FlowRule rule) { - elseFlowRule = rule; - } - - /** - * {@inheritDoc} - * - *

This implementation returns an empty list. - */ - @Override - public List getNodes() { - return Collections.emptyList(); - } - - @Override - public @Nullable Node getLastNode() { - return null; + /** Successor of the then branch. */ + protected @Nullable BlockImpl thenSuccessor; + + /** Successor of the else branch. */ + protected @Nullable BlockImpl elseSuccessor; + + /** + * The initial value says that the THEN store before a conditional block flows to BOTH of the + * stores of the then successor. + */ + protected FlowRule thenFlowRule = FlowRule.THEN_TO_BOTH; + + /** + * The initial value says that the ELSE store before a conditional block flows to BOTH of the + * stores of the else successor. + */ + protected FlowRule elseFlowRule = FlowRule.ELSE_TO_BOTH; + + /** + * Initialize an empty conditional basic block to be filled with contents and linked to other + * basic blocks later. + */ + public ConditionalBlockImpl() { + super(BlockType.CONDITIONAL_BLOCK); + } + + /** Set the then branch successor. */ + public void setThenSuccessor(BlockImpl b) { + thenSuccessor = b; + b.addPredecessor(this); + } + + /** Set the else branch successor. */ + public void setElseSuccessor(BlockImpl b) { + elseSuccessor = b; + b.addPredecessor(this); + } + + @Override + public Block getThenSuccessor() { + if (thenSuccessor == null) { + throw new BugInCF("Requested thenSuccessor for conditional block before initialization"); } + return thenSuccessor; + } - @Override - public String toString() { - return "ConditionalBlock()"; + @Override + public Block getElseSuccessor() { + if (elseSuccessor == null) { + throw new BugInCF("Requested elseSuccessor for conditional block before initialization"); } + return elseSuccessor; + } + + @Override + public Set getSuccessors() { + Set result = new ArraySet<>(2); + result.add(getThenSuccessor()); + result.add(getElseSuccessor()); + return result; + } + + @Override + public FlowRule getThenFlowRule() { + return thenFlowRule; + } + + @Override + public FlowRule getElseFlowRule() { + return elseFlowRule; + } + + @Override + public void setThenFlowRule(FlowRule rule) { + thenFlowRule = rule; + } + + @Override + public void setElseFlowRule(FlowRule rule) { + elseFlowRule = rule; + } + + /** + * {@inheritDoc} + * + *

This implementation returns an empty list. + */ + @Override + public List getNodes() { + return Collections.emptyList(); + } + + @Override + public @Nullable Node getLastNode() { + return null; + } + + @Override + public String toString() { + return "ConditionalBlock()"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java index 83b028db751..cf9b3f3b4f4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java @@ -1,12 +1,10 @@ package org.checkerframework.dataflow.cfg.block; -import org.checkerframework.dataflow.cfg.node.Node; -import org.checkerframework.dataflow.qual.Pure; - import java.util.Map; import java.util.Set; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.qual.Pure; /** * Represents a basic block that contains exactly one {@link Node} which can throw an exception. @@ -20,19 +18,19 @@ */ public interface ExceptionBlock extends SingleSuccessorBlock { - /** - * Returns the node of this block. - * - * @return the node of this block - */ - @Pure - Node getNode(); + /** + * Returns the node of this block. + * + * @return the node of this block + */ + @Pure + Node getNode(); - /** - * Returns the list of exceptional successor blocks as an unmodifiable map. - * - * @return the list of exceptional successor blocks as an unmodifiable map - */ - @Pure - Map> getExceptionalSuccessors(); + /** + * Returns the list of exceptional successor blocks as an unmodifiable map. + * + * @return the list of exceptional successor blocks as an unmodifiable map + */ + @Pure + Map> getExceptionalSuccessors(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java index 7d1efd8aff3..1657661dec5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java @@ -1,5 +1,10 @@ package org.checkerframework.dataflow.cfg.block; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.javacutil.BugInCF; @@ -7,88 +12,81 @@ import org.plumelib.util.ArraySet; import org.plumelib.util.CollectionsPlume; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; - /** Implementation of {@link ExceptionBlock}. */ public class ExceptionBlockImpl extends SingleSuccessorBlockImpl implements ExceptionBlock { - /** The node of this block. */ - protected @Nullable Node node; + /** The node of this block. */ + protected @Nullable Node node; - /** Set of exceptional successors. */ - protected final Map> exceptionalSuccessors; + /** Set of exceptional successors. */ + protected final Map> exceptionalSuccessors; - /** Create an empty exceptional block. */ - public ExceptionBlockImpl() { - super(BlockType.EXCEPTION_BLOCK); - exceptionalSuccessors = new ArrayMap<>(2); - } + /** Create an empty exceptional block. */ + public ExceptionBlockImpl() { + super(BlockType.EXCEPTION_BLOCK); + exceptionalSuccessors = new ArrayMap<>(2); + } - /** Set the node. */ - public void setNode(Node c) { - node = c; - c.setBlock(this); - } + /** Set the node. */ + public void setNode(Node c) { + node = c; + c.setBlock(this); + } - @Override - public Node getNode() { - if (node == null) { - throw new BugInCF("Requested node for exception block before initialization"); - } - return node; + @Override + public Node getNode() { + if (node == null) { + throw new BugInCF("Requested node for exception block before initialization"); } + return node; + } - /** - * {@inheritDoc} - * - *

This implementation returns a singleton list. - */ - @Override - public List getNodes() { - return Collections.singletonList(getNode()); - } + /** + * {@inheritDoc} + * + *

This implementation returns a singleton list. + */ + @Override + public List getNodes() { + return Collections.singletonList(getNode()); + } - @Override - public @Nullable Node getLastNode() { - return getNode(); - } + @Override + public @Nullable Node getLastNode() { + return getNode(); + } - /** - * Add an exceptional successor. - * - * @param b the successor - * @param cause the exception type that leads to the given block - */ - public void addExceptionalSuccessor(BlockImpl b, TypeMirror cause) { - Set blocks = exceptionalSuccessors.computeIfAbsent(cause, __ -> new ArraySet<>(2)); - blocks.add(b); - b.addPredecessor(this); - } + /** + * Add an exceptional successor. + * + * @param b the successor + * @param cause the exception type that leads to the given block + */ + public void addExceptionalSuccessor(BlockImpl b, TypeMirror cause) { + Set blocks = exceptionalSuccessors.computeIfAbsent(cause, __ -> new ArraySet<>(2)); + blocks.add(b); + b.addPredecessor(this); + } - @Override - public Map> getExceptionalSuccessors() { - if (exceptionalSuccessors == null) { - return Collections.emptyMap(); - } - return Collections.unmodifiableMap(exceptionalSuccessors); + @Override + public Map> getExceptionalSuccessors() { + if (exceptionalSuccessors == null) { + return Collections.emptyMap(); } + return Collections.unmodifiableMap(exceptionalSuccessors); + } - @Override - public Set getSuccessors() { - Set result = new ArraySet<>(super.getSuccessors()); - for (Set blocks : getExceptionalSuccessors().values()) { - CollectionsPlume.adjoinAll(result, blocks); - } - return result; + @Override + public Set getSuccessors() { + Set result = new ArraySet<>(super.getSuccessors()); + for (Set blocks : getExceptionalSuccessors().values()) { + CollectionsPlume.adjoinAll(result, blocks); } + return result; + } - @Override - public String toString() { - return "ExceptionBlock(" + node + ")"; - } + @Override + public String toString() { + return "ExceptionBlock(" + node + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java index 91648ac8456..3883c45ab3e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java @@ -6,15 +6,15 @@ /** A regular basic block that contains a sequence of {@link Node}s. */ public interface RegularBlock extends SingleSuccessorBlock { - /** - * Returns the regular successor block. - * - * @return the regular successor block - */ - @Pure - @Nullable Block getRegularSuccessor(); + /** + * Returns the regular successor block. + * + * @return the regular successor block + */ + @Pure + @Nullable Block getRegularSuccessor(); - /** Is this block empty (i.e., does it not contain any contents). */ - @Pure - boolean isEmpty(); + /** Is this block empty (i.e., does it not contain any contents). */ + @Pure + boolean isEmpty(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java index 1197ac77301..27e8c8ba087 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java @@ -1,67 +1,66 @@ package org.checkerframework.dataflow.cfg.block; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.cfg.node.Node; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.cfg.node.Node; /** Implementation of a regular basic block. */ public class RegularBlockImpl extends SingleSuccessorBlockImpl implements RegularBlock { - /** Internal representation of the contents. */ - protected final List contents; + /** Internal representation of the contents. */ + protected final List contents; - /** - * Initialize an empty basic block to be filled with contents and linked to other basic blocks - * later. - */ - public RegularBlockImpl() { - super(BlockType.REGULAR_BLOCK); - contents = new ArrayList<>(); - } + /** + * Initialize an empty basic block to be filled with contents and linked to other basic blocks + * later. + */ + public RegularBlockImpl() { + super(BlockType.REGULAR_BLOCK); + contents = new ArrayList<>(); + } - /** Add a node to the contents of this basic block. */ - public void addNode(Node t) { - contents.add(t); - t.setBlock(this); - } + /** Add a node to the contents of this basic block. */ + public void addNode(Node t) { + contents.add(t); + t.setBlock(this); + } - /** Add multiple nodes to the contents of this basic block. */ - public void addNodes(List ts) { - for (Node t : ts) { - addNode(t); - } + /** Add multiple nodes to the contents of this basic block. */ + public void addNodes(List ts) { + for (Node t : ts) { + addNode(t); } + } - /** - * {@inheritDoc} - * - *

This implementation returns an non-empty list. - */ - @Override - public List getNodes() { - return Collections.unmodifiableList(contents); - } + /** + * {@inheritDoc} + * + *

This implementation returns an non-empty list. + */ + @Override + public List getNodes() { + return Collections.unmodifiableList(contents); + } - @Override - public @Nullable Node getLastNode() { - return contents.get(contents.size() - 1); - } + @Override + public @Nullable Node getLastNode() { + return contents.get(contents.size() - 1); + } - @Override - public @Nullable BlockImpl getRegularSuccessor() { - return successor; - } + @Override + public @Nullable BlockImpl getRegularSuccessor() { + return successor; + } - @Override - public String toString() { - return "RegularBlock(" + contents + ")"; - } + @Override + public String toString() { + return "RegularBlock(" + contents + ")"; + } - @Override - public boolean isEmpty() { - return contents.isEmpty(); - } + @Override + public boolean isEmpty() { + return contents.isEmpty(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java index b7dd3f23df3..057a78b949e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java @@ -7,28 +7,28 @@ /** A basic block that has exactly one non-exceptional successor. */ public interface SingleSuccessorBlock extends Block { - /** - * Returns the non-exceptional successor block, or {@code null} if there is no non-exceptional - * successor. - * - * @return the non-exceptional successor block, or {@code null} if there is no non-exceptional - * successor - */ - @Pure - @Nullable Block getSuccessor(); + /** + * Returns the non-exceptional successor block, or {@code null} if there is no non-exceptional + * successor. + * + * @return the non-exceptional successor block, or {@code null} if there is no non-exceptional + * successor + */ + @Pure + @Nullable Block getSuccessor(); - /** - * Returns the flow rule for information flowing from this block to its successor. - * - * @return the flow rule for information flowing from this block to its successor - */ - @Pure - FlowRule getFlowRule(); + /** + * Returns the flow rule for information flowing from this block to its successor. + * + * @return the flow rule for information flowing from this block to its successor + */ + @Pure + FlowRule getFlowRule(); - /** - * Set the flow rule for information flowing from this block to its successor. - * - * @param rule the new flow rule for information flowing from this block to its successor - */ - void setFlowRule(FlowRule rule); + /** + * Set the flow rule for information flowing from this block to its successor. + * + * @param rule the new flow rule for information flowing from this block to its successor + */ + void setFlowRule(FlowRule rule); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java index 429a17c14ce..996f92deea8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java @@ -1,10 +1,9 @@ package org.checkerframework.dataflow.cfg.block; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.analysis.Store.FlowRule; - import java.util.Collections; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.analysis.Store.FlowRule; /** * A basic block that has at most one successor. SpecialBlockImpl extends this, but exit blocks have @@ -12,59 +11,59 @@ */ public abstract class SingleSuccessorBlockImpl extends BlockImpl implements SingleSuccessorBlock { - /** - * Internal representation of the successor. - * - *

Is set by {@link #setSuccessor}. - */ - protected @Nullable BlockImpl successor; + /** + * Internal representation of the successor. + * + *

Is set by {@link #setSuccessor}. + */ + protected @Nullable BlockImpl successor; - /** - * The initial value for the rule below says that EACH store at the end of a single successor - * block flows to the corresponding store of the successor. - */ - protected FlowRule flowRule = FlowRule.EACH_TO_EACH; + /** + * The initial value for the rule below says that EACH store at the end of a single successor + * block flows to the corresponding store of the successor. + */ + protected FlowRule flowRule = FlowRule.EACH_TO_EACH; - /** - * Creates a new SingleSuccessorBlock. - * - * @param type the type of this basic block - */ - protected SingleSuccessorBlockImpl(BlockType type) { - super(type); - } + /** + * Creates a new SingleSuccessorBlock. + * + * @param type the type of this basic block + */ + protected SingleSuccessorBlockImpl(BlockType type) { + super(type); + } - @Override - public @Nullable Block getSuccessor() { - return successor; - } + @Override + public @Nullable Block getSuccessor() { + return successor; + } - @Override - public Set getSuccessors() { - if (successor == null) { - return Collections.emptySet(); - } else { - return Collections.singleton(successor); - } + @Override + public Set getSuccessors() { + if (successor == null) { + return Collections.emptySet(); + } else { + return Collections.singleton(successor); } + } - /** - * Set a basic block as the successor of this block. - * - * @param successor the block that will be the successor of this - */ - public void setSuccessor(BlockImpl successor) { - this.successor = successor; - successor.addPredecessor(this); - } + /** + * Set a basic block as the successor of this block. + * + * @param successor the block that will be the successor of this + */ + public void setSuccessor(BlockImpl successor) { + this.successor = successor; + successor.addPredecessor(this); + } - @Override - public FlowRule getFlowRule() { - return flowRule; - } + @Override + public FlowRule getFlowRule() { + return flowRule; + } - @Override - public void setFlowRule(FlowRule rule) { - flowRule = rule; - } + @Override + public void setFlowRule(FlowRule rule) { + flowRule = rule; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java index e3d26d7797b..ed564ec8116 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java @@ -11,23 +11,23 @@ */ public interface SpecialBlock extends SingleSuccessorBlock { - /** The types of special basic blocks. */ - public enum SpecialBlockType { + /** The types of special basic blocks. */ + public enum SpecialBlockType { - /** The entry block of a method. */ - ENTRY, + /** The entry block of a method. */ + ENTRY, - /** The exit block of a method. */ - EXIT, + /** The exit block of a method. */ + EXIT, - /** A special exit block of a method for exceptional termination. */ - EXCEPTIONAL_EXIT, - } + /** A special exit block of a method for exceptional termination. */ + EXCEPTIONAL_EXIT, + } - /** - * Returns the type of this special basic block. - * - * @return the type of this special basic block - */ - SpecialBlockType getSpecialType(); + /** + * Returns the type of this special basic block. + * + * @return the type of this special basic block + */ + SpecialBlockType getSpecialType(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java index cd461998c3e..9a3bf14cd68 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java @@ -1,44 +1,43 @@ package org.checkerframework.dataflow.cfg.block; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.cfg.node.Node; - import java.util.Collections; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.cfg.node.Node; /** The implementation of a {@link SpecialBlock}. */ public class SpecialBlockImpl extends SingleSuccessorBlockImpl implements SpecialBlock { - /** The type of this special basic block. */ - protected final SpecialBlockType specialType; - - public SpecialBlockImpl(SpecialBlockType type) { - super(BlockType.SPECIAL_BLOCK); - this.specialType = type; - } - - @Override - public SpecialBlockType getSpecialType() { - return specialType; - } - - /** - * {@inheritDoc} - * - *

This implementation returns an empty list. - */ - @Override - public List getNodes() { - return Collections.emptyList(); - } - - @Override - public @Nullable Node getLastNode() { - return null; - } - - @Override - public String toString() { - return "SpecialBlock(" + specialType + ")"; - } + /** The type of this special basic block. */ + protected final SpecialBlockType specialType; + + public SpecialBlockImpl(SpecialBlockType type) { + super(BlockType.SPECIAL_BLOCK); + this.specialType = type; + } + + @Override + public SpecialBlockType getSpecialType() { + return specialType; + } + + /** + * {@inheritDoc} + * + *

This implementation returns an empty list. + */ + @Override + public List getNodes() { + return Collections.emptyList(); + } + + @Override + public @Nullable Node getLastNode() { + return null; + } + + @Override + public String toString() { + return "SpecialBlock(" + specialType + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java index 57b3043c0b4..f258b08eac4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java @@ -4,7 +4,12 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; import com.sun.source.util.TreePath; - +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeMirror; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod; @@ -17,14 +22,6 @@ import org.checkerframework.javacutil.BasicAnnotationProvider; import org.checkerframework.javacutil.trees.TreeBuilder; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.type.TypeMirror; - /** * Builds the control flow graph of some Java code (either a method, or an arbitrary statement). * @@ -49,140 +46,129 @@ */ public abstract class CFGBuilder { - /** Creates a CFGBuilder. */ - protected CFGBuilder() {} + /** Creates a CFGBuilder. */ + protected CFGBuilder() {} - /** - * Build the control flow graph of some code. - * - * @param root the compilation unit - * @param underlyingAST the AST that underlies the control frow graph - * @param assumeAssertionsDisabled can assertions be assumed to be disabled? - * @param assumeAssertionsEnabled can assertions be assumed to be enabled? - * @param env annotation processing environment containing type utilities - * @return a control flow graph - */ - public static ControlFlowGraph build( - CompilationUnitTree root, - UnderlyingAST underlyingAST, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - TreeBuilder builder = new TreeBuilder(env); - AnnotationProvider annotationProvider = new BasicAnnotationProvider(); - PhaseOneResult phase1result = - new CFGTranslationPhaseOne( - builder, - annotationProvider, - assumeAssertionsEnabled, - assumeAssertionsDisabled, - env) - .process(root, underlyingAST); - ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); - ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); - return phase3result; - } + /** + * Build the control flow graph of some code. + * + * @param root the compilation unit + * @param underlyingAST the AST that underlies the control frow graph + * @param assumeAssertionsDisabled can assertions be assumed to be disabled? + * @param assumeAssertionsEnabled can assertions be assumed to be enabled? + * @param env annotation processing environment containing type utilities + * @return a control flow graph + */ + public static ControlFlowGraph build( + CompilationUnitTree root, + UnderlyingAST underlyingAST, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + TreeBuilder builder = new TreeBuilder(env); + AnnotationProvider annotationProvider = new BasicAnnotationProvider(); + PhaseOneResult phase1result = + new CFGTranslationPhaseOne( + builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env) + .process(root, underlyingAST); + ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); + return phase3result; + } - /** - * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the - * TreePath to the body of that code. - */ - public static ControlFlowGraph build( - TreePath bodyPath, - UnderlyingAST underlyingAST, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - TreeBuilder builder = new TreeBuilder(env); - AnnotationProvider annotationProvider = new BasicAnnotationProvider(); - PhaseOneResult phase1result = - new CFGTranslationPhaseOne( - builder, - annotationProvider, - assumeAssertionsEnabled, - assumeAssertionsDisabled, - env) - .process(bodyPath, underlyingAST); - ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); - ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); - return phase3result; - } + /** + * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the + * TreePath to the body of that code. + */ + public static ControlFlowGraph build( + TreePath bodyPath, + UnderlyingAST underlyingAST, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + TreeBuilder builder = new TreeBuilder(env); + AnnotationProvider annotationProvider = new BasicAnnotationProvider(); + PhaseOneResult phase1result = + new CFGTranslationPhaseOne( + builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env) + .process(bodyPath, underlyingAST); + ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); + return phase3result; + } - /** Build the control flow graph of some code. */ - public static ControlFlowGraph build( - CompilationUnitTree root, UnderlyingAST underlyingAST, ProcessingEnvironment env) { - return build(root, underlyingAST, false, false, env); - } + /** Build the control flow graph of some code. */ + public static ControlFlowGraph build( + CompilationUnitTree root, UnderlyingAST underlyingAST, ProcessingEnvironment env) { + return build(root, underlyingAST, false, false, env); + } - /** Build the control flow graph of a method. */ - public static ControlFlowGraph build( - CompilationUnitTree root, - MethodTree tree, - ClassTree classTree, - ProcessingEnvironment env) { - UnderlyingAST underlyingAST = new CFGMethod(tree, classTree); - return build(root, underlyingAST, false, false, env); - } + /** Build the control flow graph of a method. */ + public static ControlFlowGraph build( + CompilationUnitTree root, MethodTree tree, ClassTree classTree, ProcessingEnvironment env) { + UnderlyingAST underlyingAST = new CFGMethod(tree, classTree); + return build(root, underlyingAST, false, false, env); + } - /** - * Return a printed representation of a collection of extended nodes. - * - * @param nodes a collection of extended nodes to format - * @return a printed representation of the given collection - */ - public static String extendedNodeCollectionToStringDebug( - Collection nodes) { - StringJoiner result = new StringJoiner(", ", "[", "]"); - for (ExtendedNode n : nodes) { - result.add(n.toStringDebug()); - } - return result.toString(); + /** + * Return a printed representation of a collection of extended nodes. + * + * @param nodes a collection of extended nodes to format + * @return a printed representation of the given collection + */ + public static String extendedNodeCollectionToStringDebug( + Collection nodes) { + StringJoiner result = new StringJoiner(", ", "[", "]"); + for (ExtendedNode n : nodes) { + result.add(n.toStringDebug()); } + return result.toString(); + } - /* --------------------------------------------------------- */ - /* Utility routines for debugging CFG building */ - /* --------------------------------------------------------- */ + /* --------------------------------------------------------- */ + /* Utility routines for debugging CFG building */ + /* --------------------------------------------------------- */ - /** - * Print a set of {@link Block}s and the edges between them. This is useful for examining the - * results of phase two. - * - * @param blocks the blocks to print - */ - protected static void printBlocks(Set blocks) { - for (Block b : blocks) { - System.out.print(b.getUid() + ": " + b); - switch (b.getType()) { - case REGULAR_BLOCK: - case SPECIAL_BLOCK: - { - Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); - System.out.println(" -> " + (succ != null ? succ.getUid() : "||")); - break; - } - case EXCEPTION_BLOCK: - { - Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); - System.out.print(" -> " + (succ != null ? succ.getUid() : "||") + " {"); - for (Map.Entry> entry : - ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) { - System.out.print(entry.getKey() + " : " + entry.getValue() + ", "); - } - System.out.println("}"); - break; - } - case CONDITIONAL_BLOCK: - { - Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor(); - Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor(); - System.out.println( - " -> T " - + (tSucc != null ? tSucc.getUid() : "||") - + " F " - + (eSucc != null ? eSucc.getUid() : "||")); - break; - } + /** + * Print a set of {@link Block}s and the edges between them. This is useful for examining the + * results of phase two. + * + * @param blocks the blocks to print + */ + protected static void printBlocks(Set blocks) { + for (Block b : blocks) { + System.out.print(b.getUid() + ": " + b); + switch (b.getType()) { + case REGULAR_BLOCK: + case SPECIAL_BLOCK: + { + Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); + System.out.println(" -> " + (succ != null ? succ.getUid() : "||")); + break; + } + case EXCEPTION_BLOCK: + { + Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); + System.out.print(" -> " + (succ != null ? succ.getUid() : "||") + " {"); + for (Map.Entry> entry : + ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) { + System.out.print(entry.getKey() + " : " + entry.getValue() + ", "); } - } + System.out.println("}"); + break; + } + case CONDITIONAL_BLOCK: + { + Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor(); + Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor(); + System.out.println( + " -> T " + + (tSucc != null ? tSucc.getUid() : "||") + + " F " + + (eSucc != null ? eSucc.getUid() : "||")); + break; + } + } } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index 8f92a87c8ca..7f80592de1a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -58,7 +58,32 @@ import com.sun.source.util.TreeScanner; import com.sun.source.util.Trees; import com.sun.tools.javac.code.Type; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.ReferenceType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store.FlowRule; @@ -162,34 +187,6 @@ import org.plumelib.util.IPair; import org.plumelib.util.IdentityArraySet; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.ReferenceType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - /** * Class that performs phase one of the translation process. It generates the following information: * @@ -215,4390 +212,4269 @@ @SuppressWarnings("nullness") // TODO public class CFGTranslationPhaseOne extends TreeScanner { - /** Path to the tree currently being scanned. */ - private TreePath path; + /** Path to the tree currently being scanned. */ + private TreePath path; - /** Annotation processing environment. */ - protected final ProcessingEnvironment env; + /** Annotation processing environment. */ + protected final ProcessingEnvironment env; - /** Element utilities. */ - protected final Elements elements; + /** Element utilities. */ + protected final Elements elements; - /** Type utilities. */ - protected final Types types; + /** Type utilities. */ + protected final Types types; - /** Tree utilities. */ - protected final Trees trees; + /** Tree utilities. */ + protected final Trees trees; + + /** TreeBuilder instance. */ + protected final TreeBuilder treeBuilder; - /** TreeBuilder instance. */ - protected final TreeBuilder treeBuilder; + /** The annotation provider, e.g., a type factory. */ + protected final AnnotationProvider annotationProvider; - /** The annotation provider, e.g., a type factory. */ - protected final AnnotationProvider annotationProvider; + /** Can assertions be assumed to be disabled? */ + protected final boolean assumeAssertionsDisabled; - /** Can assertions be assumed to be disabled? */ - protected final boolean assumeAssertionsDisabled; + /** Can assertions be assumed to be enabled? */ + protected final boolean assumeAssertionsEnabled; - /** Can assertions be assumed to be enabled? */ - protected final boolean assumeAssertionsEnabled; + /* --------------------------------------------------------- */ + /* Extended Node Types and Labels */ + /* --------------------------------------------------------- */ - /* --------------------------------------------------------- */ - /* Extended Node Types and Labels */ - /* --------------------------------------------------------- */ + /** Special label to identify the regular exit. */ + private final Label regularExitLabel; - /** Special label to identify the regular exit. */ - private final Label regularExitLabel; + /** Special label to identify the exceptional exit. */ + private final Label exceptionalExitLabel; - /** Special label to identify the exceptional exit. */ - private final Label exceptionalExitLabel; + /** + * Current {@link LabelCell} to which a return statement should jump, or null if there is no valid + * destination. + */ + private @Nullable LabelCell returnTargetLC; - /** - * Current {@link LabelCell} to which a return statement should jump, or null if there is no - * valid destination. - */ - private @Nullable LabelCell returnTargetLC; + /** + * Current {@link LabelCell} to which a break statement with no label should jump, or null if + * there is no valid destination. + */ + private @Nullable LabelCell breakTargetLC; - /** - * Current {@link LabelCell} to which a break statement with no label should jump, or null if - * there is no valid destination. - */ - private @Nullable LabelCell breakTargetLC; + /** + * Map from AST label Names to CFG {@link Label}s for breaks. Each labeled statement creates two + * CFG {@link Label}s, one for break and one for continue. + */ + private Map breakLabels; - /** - * Map from AST label Names to CFG {@link Label}s for breaks. Each labeled statement creates two - * CFG {@link Label}s, one for break and one for continue. - */ - private Map breakLabels; + /** + * Current {@link LabelCell} to which a continue statement with no label should jump, or null if + * there is no valid destination. + */ + private @Nullable LabelCell continueTargetLC; - /** - * Current {@link LabelCell} to which a continue statement with no label should jump, or null if - * there is no valid destination. - */ - private @Nullable LabelCell continueTargetLC; + /** + * Map from AST label Names to CFG {@link Label}s for continues. Each labeled statement creates + * two CFG {@link Label}s, one for break and one for continue. + */ + private Map continueLabels; - /** - * Map from AST label Names to CFG {@link Label}s for continues. Each labeled statement creates - * two CFG {@link Label}s, one for break and one for continue. - */ - private Map continueLabels; + /** Nested scopes of try-catch blocks in force at the current program point. */ + private final TryStack tryStack; - /** Nested scopes of try-catch blocks in force at the current program point. */ - private final TryStack tryStack; + /** SwitchBuilder for the current switch. Used to match yield statements to enclosing switches. */ + private SwitchBuilder switchBuilder; - /** - * SwitchBuilder for the current switch. Used to match yield statements to enclosing switches. - */ - private SwitchBuilder switchBuilder; + /** + * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will + * have at least one corresponding Node. Trees that undergo conversions, such as boxing or + * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in the + * treeToCfgNodes, while the Node for the post-conversion value is stored in the + * treeToConvertedCfgNodes. + */ + private final IdentityHashMap> treeToCfgNodes; + + /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ + private final IdentityHashMap> treeToConvertedCfgNodes; + + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic + * tree that is {@code v + 1} or {@code v - 1}. + */ + private final IdentityHashMap postfixTreeToCfgNodes; + + /** The list of extended nodes. */ + private final ArrayList nodeList; + + /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ + private final Map bindings; + + /** The set of leaders (represented as indices into {@code nodeList}). */ + private final Set leaders; + + /** + * All return nodes (if any) encountered. Only includes return statements that actually return + * something. + */ + private final List returnNodes; + + /** + * Class declarations that have been encountered when building the control-flow graph for a + * method. + */ + private final List declaredClasses; + + /** + * Lambdas encountered when building the control-flow graph for a method, variable initializer, or + * initializer. + */ + private final List declaredLambdas; + + /** The ArithmeticException type. */ + protected final TypeMirror arithmeticExceptionType; + + /** The ArrayIndexOutOfBoundsException type. */ + protected final TypeMirror arrayIndexOutOfBoundsExceptionType; + + /** The AssertionError type. */ + protected final TypeMirror assertionErrorType; + + /** The ClassCastException type . */ + protected final TypeMirror classCastExceptionType; + + /** The Iterable type (erased). */ + protected final TypeMirror iterableType; + + /** The NegativeArraySizeException type. */ + protected final TypeMirror negativeArraySizeExceptionType; + + /** The NullPointerException type . */ + protected final TypeMirror nullPointerExceptionType; + + /** The OutOfMemoryError type. */ + protected final @Nullable TypeMirror outOfMemoryErrorType; + + /** The ClassCircularityError type. */ + protected final @Nullable TypeMirror classCircularityErrorType; + + /** The ClassFormatErrorType type. */ + protected final @Nullable TypeMirror classFormatErrorType; + + /** The NoClassDefFoundError type. */ + protected final @Nullable TypeMirror noClassDefFoundErrorType; + + /** The String type. */ + protected final TypeMirror stringType; + + /** The Throwable type. */ + protected final TypeMirror throwableType; + + /** + * Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code + * RuntimeException} and {@code Error}. + */ + protected final Set uncheckedExceptionTypes; + + /** + * Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the + * contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes + * from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions". + */ + protected final Set newArrayExceptionTypes; + + /** + * Creates {@link CFGTranslationPhaseOne}. + * + * @param treeBuilder builder for new AST nodes + * @param annotationProvider extracts annotations from AST nodes + * @param assumeAssertionsDisabled can assertions be assumed to be disabled? + * @param assumeAssertionsEnabled can assertions be assumed to be enabled? + * @param env annotation processing environment containing type utilities + */ + public CFGTranslationPhaseOne( + TreeBuilder treeBuilder, + AnnotationProvider annotationProvider, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + this.env = env; + this.treeBuilder = treeBuilder; + this.annotationProvider = annotationProvider; + + assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); + this.assumeAssertionsEnabled = assumeAssertionsEnabled; + this.assumeAssertionsDisabled = assumeAssertionsDisabled; + + elements = env.getElementUtils(); + types = env.getTypeUtils(); + trees = Trees.instance(env); + + // initialize lists and maps + treeToCfgNodes = new IdentityHashMap<>(); + treeToConvertedCfgNodes = new IdentityHashMap<>(); + postfixTreeToCfgNodes = new IdentityHashMap<>(); + nodeList = new ArrayList<>(); + bindings = new HashMap<>(); + leaders = new HashSet<>(); + + regularExitLabel = new Label(); + exceptionalExitLabel = new Label(); + tryStack = new TryStack(exceptionalExitLabel); + returnTargetLC = new LabelCell(regularExitLabel); + breakLabels = new HashMap<>(2); + continueLabels = new HashMap<>(2); + returnNodes = new ArrayList<>(); + declaredClasses = new ArrayList<>(); + declaredLambdas = new ArrayList<>(); + + arithmeticExceptionType = getTypeMirror(ArithmeticException.class); + arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class); + assertionErrorType = getTypeMirror(AssertionError.class); + classCastExceptionType = getTypeMirror(ClassCastException.class); + iterableType = types.erasure(getTypeMirror(Iterable.class)); + negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class); + nullPointerExceptionType = getTypeMirror(NullPointerException.class); + outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class); + classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class); + classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class); + noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class); + stringType = getTypeMirror(String.class); + throwableType = getTypeMirror(Throwable.class); + uncheckedExceptionTypes = new ArraySet<>(2); + uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class)); + uncheckedExceptionTypes.add(getTypeMirror(Error.class)); + newArrayExceptionTypes = new ArraySet<>(2); + newArrayExceptionTypes.add(negativeArraySizeExceptionType); + if (outOfMemoryErrorType != null) { + newArrayExceptionTypes.add(outOfMemoryErrorType); + } + } + + /** + * Performs the actual work of phase one: processing a single body (of a method, lambda, top-level + * block, etc.). + * + * @param bodyPath path to the body of the underlying AST's method + * @param underlyingAST the AST for which the CFG is to be built + * @return the result of phase one + */ + public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) { + + // Set class variables + this.path = bodyPath; + + // Traverse AST of the method body. + try { // "finally" clause is "this.path = null" + Node finalNode = scan(path.getLeaf(), null); + + // If we are building the CFG for a lambda with a single expression as the body, then + // add an extra node for the result of that lambda. + if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { + LambdaExpressionTree lambdaTree = ((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree(); + if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) { + Node resultNode = + new LambdaResultExpressionNode((ExpressionTree) lambdaTree.getBody(), finalNode); + extendWithNode(resultNode); + } + } + + // Add marker to indicate that the next block will be the exit block. + // Note: if there is a return statement earlier in the method (which is always the case + // for non-void methods), then this is not strictly necessary. However, it is also not a + // problem, as it will just generate a degenerate control graph case that will be + // removed in a later phase. + nodeList.add(new UnconditionalJump(regularExitLabel)); + + return new PhaseOneResult( + underlyingAST, + treeToCfgNodes, + treeToConvertedCfgNodes, + postfixTreeToCfgNodes, + nodeList, + bindings, + leaders, + returnNodes, + regularExitLabel, + exceptionalExitLabel, + declaredClasses, + declaredLambdas, + types); + } finally { + this.path = null; + } + } + + /** + * Process a single body within {@code root}. This method does not process the entire given + * CompilationUnitTree. Rather, it processes one body (of a method/lambda/etc.) within it, which + * corresponds to {@code underlyingAST}. + * + * @param root the compilation unit + * @param underlyingAST the AST corresponding to the body to process + * @return a PhaseOneResult + */ + public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) { + // TODO: Isn't this costly? Is there no cache we can reuse? + TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); + assert bodyPath != null; + return process(bodyPath, underlyingAST); + } + + /** + * Perform any actions required when CFG translation creates a new Tree that is not part of the + * original AST. + * + * @param tree the newly created Tree + */ + public void handleArtificialTree(Tree tree) {} + + /** + * Returns the current path for the tree currently being scanned. + * + * @return the current path + */ + public TreePath getCurrentPath() { + return path; + } + + // TODO: remove method and instead use JCP to add version-specific methods. + // Switch expressions first appeared in 12, standard in 14, so don't use 17. + // TODO: look into changing back to TreePathScanner and removing path/getCurrentPath. + @Override + public Node scan(Tree tree, Void p) { + if (tree == null) { + return null; + } - /** - * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will - * have at least one corresponding Node. Trees that undergo conversions, such as boxing or - * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in - * the treeToCfgNodes, while the Node for the post-conversion value is stored in the - * treeToConvertedCfgNodes. - */ - private final IdentityHashMap> treeToCfgNodes; + TreePath prev = path; + @SuppressWarnings("interning:not.interned") // Looking for exact match. + boolean treeIsLeaf = path.getLeaf() != tree; + if (treeIsLeaf) { + path = new TreePath(path, tree); + } + try { + // TODO: use JCP to add version-specific behavior + if (SystemUtil.jreVersion >= 14) { + // Must use String comparison to support compiling on JDK 11 and earlier. + // Features added between JDK 12 and JDK 17 inclusive. + switch (tree.getKind().name()) { + case "BINDING_PATTERN": + return visitBindingPattern17(path.getLeaf(), p); + case "SWITCH_EXPRESSION": + return visitSwitchExpression17(tree, p); + case "YIELD": + return visitYield17(tree, p); + case "DECONSTRUCTION_PATTERN": + return visitDeconstructionPattern21(tree, p); + default: + // fall through to generic behavior + } + } - /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ - private final IdentityHashMap> treeToConvertedCfgNodes; + return tree.accept(this, p); + } finally { + path = prev; + } + } + + /** + * Visit a SwitchExpressionTree. + * + * @param yieldTree a YieldTree, typed as Tree to be backward-compatible + * @param p parameter + * @return the result of visiting the switch expression tree + */ + public Node visitYield17(Tree yieldTree, Void p) { + ExpressionTree resultExpression = YieldUtils.getValue(yieldTree); + switchBuilder.buildSwitchExpressionResult(resultExpression); + return null; + } + + /** + * Visit a SwitchExpressionTree + * + * @param switchExpressionTree a SwitchExpressionTree, typed as Tree to be backward-compatible + * @param p parameter + * @return the result of visiting the switch expression tree + */ + public Node visitSwitchExpression17(Tree switchExpressionTree, Void p) { + SwitchBuilder oldSwitchBuilder = switchBuilder; + switchBuilder = new SwitchBuilder(switchExpressionTree); + Node result = switchBuilder.build(); + switchBuilder = oldSwitchBuilder; + return result; + } + + /** + * Visit a BindingPatternTree + * + * @param bindingPatternTree a BindingPatternTree, typed as Tree to be backward-compatible + * @param p parameter + * @return the result of visiting the binding pattern tree + */ + public Node visitBindingPattern17(Tree bindingPatternTree, Void p) { + ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); + TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass); + Node receiver = new ImplicitThisNode(classElem.asType()); + VariableTree varTree = BindingPatternUtils.getVariable(bindingPatternTree); + VariableDeclarationNode variableDeclarationNode = new VariableDeclarationNode(varTree); + extendWithNode(variableDeclarationNode); + LocalVariableNode varNode = new LocalVariableNode(varTree, receiver); + extendWithNode(varNode); + return varNode; + } + + /** + * Visit a DeconstructionPatternTree. + * + * @param deconstructionPatternTree a DeconstructionPatternTree, typed as Tree so the Checker + * Framework compiles under JDK 20 and earlier + * @param p an unused parameter + * @return the result of visiting the tree + */ + public Node visitDeconstructionPattern21(Tree deconstructionPatternTree, Void p) { + List nestedPatternTrees = + DeconstructionPatternUtils.getNestedPatterns(deconstructionPatternTree); + List nestedPatterns = new ArrayList<>(nestedPatternTrees.size()); + for (Tree pattern : nestedPatternTrees) { + nestedPatterns.add(scan(pattern, p)); + } - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the - * synthetic tree that is {@code v + 1} or {@code v - 1}. - */ - private final IdentityHashMap postfixTreeToCfgNodes; + DeconstructorPatternNode dcpN = + new DeconstructorPatternNode( + TreeUtils.typeOf(deconstructionPatternTree), deconstructionPatternTree, nestedPatterns); + extendWithNode(dcpN); + return dcpN; + } + + /* --------------------------------------------------------- */ + /* Nodes and Labels Management */ + /* --------------------------------------------------------- */ + + /** + * Add a node to the lookup map if it not already present. + * + * @param node the node to add to the lookup map + */ + protected void addToLookupMap(Node node) { + Tree tree = node.getTree(); + if (tree == null) { + return; + } + Set existing = treeToCfgNodes.get(tree); + if (existing == null) { + Set newSet = new IdentityArraySet(1); + newSet.add(node); + treeToCfgNodes.put(tree, newSet); + } else { + existing.add(node); + } + + Tree enclosingParens = parenMapping.get(tree); + while (enclosingParens != null) { + Set exp = + treeToCfgNodes.computeIfAbsent(enclosingParens, k -> new IdentityArraySet<>(1)); + // `node` could already be in set `exp`, but it's probably as fast to just add again + exp.add(node); + enclosingParens = parenMapping.get(enclosingParens); + } + } + + /** + * Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree + * should already be in the pre-conversion lookup map. This method is used to update the Tree-Node + * mapping with conversion nodes. + * + * @param node the node to add to the lookup map + */ + protected void addToConvertedLookupMap(Node node) { + Tree tree = node.getTree(); + addToConvertedLookupMap(tree, node); + } + + /** + * Add a node in the post-conversion lookup map. The tree argument should already be in the + * pre-conversion lookup map. This method is used to update the Tree-Node mapping with conversion + * nodes. + * + * @param tree the tree used as a key in the map + * @param node the node to add to the lookup map + */ + protected void addToConvertedLookupMap(Tree tree, Node node) { + assert tree != null; + assert treeToCfgNodes.containsKey(tree); + Set existing = treeToConvertedCfgNodes.get(tree); + if (existing == null) { + Set newSet = new IdentityArraySet<>(1); + newSet.add(node); + treeToConvertedCfgNodes.put(tree, newSet); + } else { + existing.add(node); + } + } + + /** + * Extend the list of extended nodes with a node. + * + * @param node the node to add + */ + protected void extendWithNode(Node node) { + addToLookupMap(node); + extendWithExtendedNode(new NodeHolder(node)); + } + + /** + * Extend the list of extended nodes with a node, where {@code node} might throw the exception + * {@code cause}. + * + * @param node the node to add + * @param cause an exception that the node might throw + * @return the node holder + */ + protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) { + addToLookupMap(node); + return extendWithNodeWithExceptions(node, Collections.singleton(cause)); + } + + /** + * Extend the list of extended nodes with a node, where {@code node} might throw any of the + * exceptions in {@code causes}. + * + * @param node the node to add + * @param causes set of exceptions that the node might throw + * @return the node holder + */ + protected NodeWithExceptionsHolder extendWithNodeWithExceptions( + Node node, Set causes) { + addToLookupMap(node); + Map> exceptions = new ArrayMap<>(causes.size()); + for (TypeMirror cause : causes) { + exceptions.put(cause, tryStack.possibleLabels(cause)); + } + NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); + extendWithExtendedNode(exNode); + return exNode; + } + + /** + * Extend a list of extended nodes with a ClassName node. + * + *

Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw one + * of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1). + * + * @param node the ClassName node to add + * @return the node holder + */ + protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) { + Set thrownSet = new ArraySet<>(4); + if (classCircularityErrorType != null) { + thrownSet.add(classCircularityErrorType); + } + if (classFormatErrorType != null) { + thrownSet.add(classFormatErrorType); + } + if (noClassDefFoundErrorType != null) { + thrownSet.add(noClassDefFoundErrorType); + } + if (outOfMemoryErrorType != null) { + thrownSet.add(outOfMemoryErrorType); + } + + return extendWithNodeWithExceptions(node, thrownSet); + } + + /** + * Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list if + * {@code pred} is not present. + * + * @param node the node to add + * @param pred the desired predecessor of node + * @return the node holder + */ + protected T insertNodeAfter(T node, Node pred) { + addToLookupMap(node); + insertExtendedNodeAfter(new NodeHolder(node), pred); + return node; + } + + /** + * Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in + * the list of extended nodes, or append to the list if {@code pred} is not present. + * + * @param node the node to add + * @param causes set of exceptions that the node might throw + * @param pred the desired predecessor of node + * @return the node holder + */ + protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter( + Node node, Set causes, Node pred) { + addToLookupMap(node); + Map> exceptions = new ArrayMap<>(causes.size()); + for (TypeMirror cause : causes) { + exceptions.put(cause, tryStack.possibleLabels(cause)); + } + NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); + insertExtendedNodeAfter(exNode, pred); + return exNode; + } + + /** + * Extend the list of extended nodes with an extended node. + * + * @param n the extended node + */ + protected void extendWithExtendedNode(ExtendedNode n) { + nodeList.add(n); + } + + /** + * Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code n} + * if {@code pred} is not present. + * + * @param n the extended node + * @param pred the desired predecessor + */ + @SuppressWarnings("ModifyCollectionInEnhancedForLoop") + protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) { + int index = -1; + for (int i = 0; i < nodeList.size(); i++) { + ExtendedNode inList = nodeList.get(i); + if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) { + if (inList.getNode() == pred) { + index = i; + break; + } + } + } + if (index != -1) { + nodeList.add(index + 1, n); + // update bindings + for (Map.Entry e : bindings.entrySet()) { + if (e.getValue() >= index + 1) { + bindings.put(e.getKey(), e.getValue() + 1); + } + } + // update leaders + Set oldLeaders = new HashSet<>(leaders); + leaders.clear(); + for (Integer l : oldLeaders) { + if (l >= index + 1) { + leaders.add(l + 1); + } else { + leaders.add(l); + } + } + } else { + nodeList.add(n); + } + } + + /** + * Add the label {@code l} to the extended node that will be placed next in the sequence. + * + * @param l the node to add to the forthcoming extended node + */ + protected void addLabelForNextNode(Label l) { + if (bindings.containsKey(l)) { + throw new BugInCF("bindings already contains key %s: %s", l, bindings); + } + leaders.add(nodeList.size()); + bindings.put(l, nodeList.size()); + } + + /* --------------------------------------------------------- */ + /* Utility Methods */ + /* --------------------------------------------------------- */ + + /** The UID for the next unique name. */ + protected long uid = 0; + + /** + * Returns a unique name starting with {@code prefix}. + * + * @param prefix the prefix of the unique name + * @return a unique name starting with {@code prefix} + */ + protected String uniqueName(String prefix) { + return prefix + "#num" + uid++; + } + + /** + * If the input node is an unboxed primitive type, insert a call to the appropriate valueOf + * method, otherwise leave it alone. + * + * @param node in input node + * @return a Node representing the boxed version of the input, which may simply be the input node + */ + protected Node box(Node node) { + // For boxing conversion, see JLS 5.1.7 + if (TypesUtils.isPrimitive(node.getType())) { + PrimitiveType primitive = types.getPrimitiveType(node.getType().getKind()); + TypeMirror boxedType = types.getDeclaredType(types.boxedClass(primitive)); + + TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); + IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement); + handleArtificialTree(classTree); + // No need to handle possible errors from evaluating a class literal here + // since this is synthetic code that can't fail. + ClassNameNode className = new ClassNameNode(classTree); + className.setInSource(false); + insertNodeAfter(className, node); + + MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree); + handleArtificialTree(valueOfSelect); + MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className); + valueOfAccess.setInSource(false); + insertNodeAfter(valueOfAccess, className); + + MethodInvocationTree valueOfCall = + treeBuilder.buildMethodInvocation(valueOfSelect, (ExpressionTree) node.getTree()); + handleArtificialTree(valueOfCall); + Node boxed = + new MethodInvocationNode( + valueOfCall, valueOfAccess, Collections.singletonList(node), getCurrentPath()); + boxed.setInSource(false); + // Add Throwable to account for unchecked exceptions + addToConvertedLookupMap(node.getTree(), boxed); + insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess); + return boxed; + } else { + return node; + } + } + + /** + * If the input node is a boxed type, unbox it, otherwise leave it alone. + * + * @param node in input node + * @return a Node representing the unboxed version of the input, which may simply be the input + * node + */ + protected Node unbox(Node node) { + if (TypesUtils.isBoxedPrimitive(node.getType())) { + + MemberSelectTree primValueSelect = treeBuilder.buildPrimValueMethodAccess(node.getTree()); + handleArtificialTree(primValueSelect); + MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node); + primValueAccess.setInSource(false); + // Method access may throw NullPointerException + insertNodeWithExceptionsAfter( + primValueAccess, Collections.singleton(nullPointerExceptionType), node); + + MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect); + handleArtificialTree(primValueCall); + Node unboxed = + new MethodInvocationNode( + primValueCall, primValueAccess, Collections.emptyList(), getCurrentPath()); + unboxed.setInSource(false); + + // Add Throwable to account for unchecked exceptions + addToConvertedLookupMap(node.getTree(), unboxed); + insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess); + return unboxed; + } else { + return node; + } + } + + private TreeInfo getTreeInfo(Tree tree) { + TypeMirror type = TreeUtils.typeOf(tree); + boolean boxed = TypesUtils.isBoxedPrimitive(type); + TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; + + boolean bool = TypesUtils.isBooleanType(type); + boolean numeric = TypesUtils.isNumeric(unboxedType); + + return new TreeInfo() { + @Override + public boolean isNumeric() { + return numeric; + } + + @Override + public boolean isBoxed() { + return boxed; + } + + @Override + public boolean isBoolean() { + return bool; + } + + @Override + public TypeMirror unboxedType() { + return unboxedType; + } + }; + } + + /** + * Returns the unboxed tree if necessary, as described in JLS 5.1.8. + * + * @return the unboxed tree if necessary, as described in JLS 5.1.8 + */ + private Node unboxAsNeeded(Node node, boolean boxed) { + return boxed ? unbox(node) : node; + } + + /** + * Convert the input node to String type, if it isn't already. + * + * @param node an input node + * @return a Node with the value promoted to String, which may be the input node + */ + protected Node stringConversion(Node node) { + // For string conversion, see JLS 5.1.11 + if (!TypesUtils.isString(node.getType())) { + Node converted = new StringConversionNode(node.getTree(), node, stringType); + addToConvertedLookupMap(converted); + insertNodeAfter(converted, node); + return converted; + } else { + return node; + } + } + + /** + * Perform unary numeric promotion on the input node. + * + * @param node a node producing a value of numeric primitive or boxed type + * @return a Node with the value promoted to the int, long, float, or double; may return be the + * input node + */ + protected Node unaryNumericPromotion(Node node) { + // For unary numeric promotion, see JLS 5.6.1 + node = unbox(node); + + switch (node.getType().getKind()) { + case BYTE: + case CHAR: + case SHORT: + { + TypeMirror intType = types.getPrimitiveType(TypeKind.INT); + Node widened = new WideningConversionNode(node.getTree(), node, intType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } + default: + // Nothing to do. + break; + } + + return node; + } + + /** + * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and false + * otherwise. + */ + protected boolean isNumericOrBoxed(TypeMirror type) { + if (TypesUtils.isBoxedPrimitive(type)) { + type = types.unboxedType(type); + } + return TypesUtils.isNumeric(type); + } + + /** + * Compute the type to which two numeric types must be promoted before performing a binary numeric + * operation on them. The input types must both be numeric and the output type is primitive. + * + * @param left the type of the left operand + * @param right the type of the right operand + * @return a TypeMirror representing the binary numeric promoted type + */ + protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { + if (TypesUtils.isBoxedPrimitive(left)) { + left = types.unboxedType(left); + } + if (TypesUtils.isBoxedPrimitive(right)) { + right = types.unboxedType(right); + } + TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right); + return types.getPrimitiveType(promotedTypeKind); + } + + /** + * Perform binary numeric promotion on the input node to make it match the expression type. + * + * @param node a node producing a value of numeric primitive or boxed type + * @param exprType the type to promote the value to + * @return a Node with the value promoted to the exprType, which may be the input node + */ + protected Node binaryNumericPromotion(Node node, TypeMirror exprType) { + // For binary numeric promotion, see JLS 5.6.2 + node = unbox(node); + + if (!types.isSameType(node.getType(), exprType)) { + Node widened = new WideningConversionNode(node.getTree(), node, exprType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } else { + return node; + } + } + + /** + * Perform widening primitive conversion on the input node to make it match the destination type. + * + * @param node a node producing a value of numeric primitive type + * @param destType the type to widen the value to + * @return a Node with the value widened to the exprType, which may be the input node + */ + protected Node widen(Node node, TypeMirror destType) { + // For widening conversion, see JLS 5.1.2 + assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) + : "widening must be applied to primitive types"; + if (types.isSubtype(node.getType(), destType) && !types.isSameType(node.getType(), destType)) { + Node widened = new WideningConversionNode(node.getTree(), node, destType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } else { + return node; + } + } + + /** + * Perform narrowing conversion on the input node to make it match the destination type. + * + * @param node a node producing a value of numeric primitive type + * @param destType the type to narrow the value to + * @return a Node with the value narrowed to the exprType, which may be the input node + */ + protected Node narrow(Node node, TypeMirror destType) { + // For narrowing conversion, see JLS 5.1.3 + assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) + : "narrowing must be applied to primitive types"; + if (types.isSubtype(destType, node.getType()) && !types.isSameType(destType, node.getType())) { + Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType); + addToConvertedLookupMap(narrowed); + insertNodeAfter(narrowed, node); + return narrowed; + } else { + return node; + } + } + + /** + * Perform narrowing conversion and optionally boxing conversion on the input node to make it + * match the destination type. + * + * @param node a node producing a value of numeric primitive type + * @param destType the type to narrow the value to (possibly boxed) + * @return a Node with the value narrowed and boxed to the destType, which may be the input node + */ + protected Node narrowAndBox(Node node, TypeMirror destType) { + if (TypesUtils.isBoxedPrimitive(destType)) { + return box(narrow(node, types.unboxedType(destType))); + } else { + return narrow(node, destType); + } + } + + /** + * Return whether a conversion from the type of the node to varType requires narrowing. + * + * @param varType the type of a variable (or general LHS) to be converted to + * @param node a node whose value is being converted + * @return whether this conversion requires narrowing to succeed + */ + protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { + // Narrowing is restricted to cases where the left hand side is byte, char, short or Byte, + // Char, Short and the right hand side is a constant. + TypeMirror unboxedVarType = + TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType; + TypeKind unboxedVarKind = unboxedVarType.getKind(); + boolean isLeftNarrowableTo = + unboxedVarKind == TypeKind.BYTE + || unboxedVarKind == TypeKind.SHORT + || unboxedVarKind == TypeKind.CHAR; + boolean isRightConstant = node instanceof ValueLiteralNode; + return isLeftNarrowableTo && isRightConstant; + } + + /** + * Assignment conversion and method invocation conversion are almost identical, except that + * assignment conversion allows narrowing. We factor out the common logic here. + * + * @param node a Node producing a value + * @param varType the type of a variable + * @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not + * (for method invocation conversion) + * @return a Node with the value converted to the type of the variable, which may be the input + * node itself + */ + protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) { + // For assignment conversion, see JLS 5.2 + // For method invocation conversion, see JLS 5.3 + + // Check for identical types or "identity conversion" + TypeMirror nodeType = node.getType(); + boolean isSameType = types.isSameType(nodeType, varType); + if (isSameType) { + return node; + } - /** The list of extended nodes. */ - private final ArrayList nodeList; + boolean isRightNumeric = TypesUtils.isNumeric(nodeType); + boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); + boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); + boolean isRightReference = nodeType instanceof ReferenceType; + boolean isLeftNumeric = TypesUtils.isNumeric(varType); + boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); + // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType); + boolean isLeftReference = varType instanceof ReferenceType; + boolean isSubtype = types.isSubtype(nodeType, varType); + + if (isRightNumeric && isLeftNumeric && isSubtype) { + node = widen(node, varType); + } else if (isRightReference && isLeftReference && isSubtype) { + // widening reference conversion is a no-op, but if it + // applies, then later conversions do not. + } else if (isRightPrimitive && isLeftReference) { + if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { + node = narrowAndBox(node, varType); + } else { + node = box(node); + } + } else if (isRightBoxed && isLeftPrimitive) { + node = unbox(node); + nodeType = node.getType(); + + if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) { + node = widen(node, varType); + } + } else if (isRightPrimitive && isLeftPrimitive) { + if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { + node = narrow(node, varType); + } + } + // `node` might have been re-assigned; if `nodeType` is needed, set it again. + // nodeType = node.getType(); + + // TODO: if checkers need to know about null references of + // a particular type, add logic for them here. + + return node; + } + + /** + * Perform assignment conversion so that it can be assigned to a variable of the given type. + * + * @param node a Node producing a value + * @param varType the type of a variable + * @return a Node with the value converted to the type of the variable, which may be the input + * node itself + */ + protected Node assignConvert(Node node, TypeMirror varType) { + return commonConvert(node, varType, true); + } + + /** + * Perform method invocation conversion so that the node can be passed as a formal parameter of + * the given type. + * + * @param node a Node producing a value + * @param formalType the type of a formal parameter + * @return a Node with the value converted to the type of the formal, which may be the input node + * itself + */ + protected Node methodInvocationConvert(Node node, TypeMirror formalType) { + return commonConvert(node, formalType, false); + } + + /** + * Given a method element, its type at the call site, and a list of argument expressions, return a + * list of {@link Node}s representing the arguments converted for a call of the method. This + * method applies to both method invocations and constructor calls. The argument of newClassTree + * is null when we visit {@link MethodInvocationTree}, and is non-null when we visit {@link + * NewClassTree}. + * + * @param tree the invocation tree for the call + * @param executable an ExecutableElement representing a method/constructor to be called + * @param executableType an ExecutableType representing the type of the method/constructor call; + * the type must be viewpoint-adapted to the call + * @param actualExprs a List of argument expressions to a call + * @param newClassTree the NewClassTree if the method is the invocation of a constructor + * @return a List of {@link Node}s representing arguments after conversions required by a call to + * this method + */ + protected List convertCallArguments( + ExpressionTree tree, + ExecutableElement executable, + ExecutableType executableType, + List actualExprs, + @Nullable NewClassTree newClassTree) { + // NOTE: It is important to convert one method argument before generating CFG nodes for the + // next argument, since label binding expects nodes to be generated in execution order. + // Therefore, this method first determines which conversions need to be applied and then + // iterates over the actual arguments. + List formals = executableType.getParameterTypes(); + int numFormals = formals.size(); + + ArrayList convertedNodes = new ArrayList<>(numFormals); + AssertMethodTuple assertMethodTuple = getAssertMethodTuple(executable); + + int numActuals = actualExprs.size(); + if (executable.isVarArgs()) { + // Create a new array argument if the actuals outnumber the formals, or if the last + // actual is not assignable to the last formal. + int lastArgIndex = numFormals - 1; + TypeMirror lastParamType = formals.get(lastArgIndex); + if (numActuals == numFormals + && types.isAssignable(TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) { + // Normal call with no array creation, apply method + // invocation conversion to all arguments. + for (int i = 0; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + if (i == assertMethodTuple.booleanParam) { + treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); + } + if (actualVal == null) { + throw new BugInCF( + "CFGBuilder: scan returned null for %s [%s]", + actualExprs.get(i), actualExprs.get(i).getClass()); + } + convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); + } + } else { + assert lastParamType instanceof ArrayType : "variable argument formal must be an array"; + // Handle anonymous constructors with an explicit enclosing expression. + // There is a mismatch between the number of parameters and arguments + // when the following conditions are met: + // 1. Java version >= 11, + // 2. the method is an anonymous constructor, + // 3. the executable element has varargs, + // 4. the constructor is invoked with an explicit enclosing expression. + // In this case, the parameters have an enclosing expression as its first parameter, + // while the arguments do not have such element. Hence, decrease the lastArgIndex + // to organize the arguments from the correct index later. + if (SystemUtil.jreVersion >= 11 + && newClassTree != null + && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( + executable, newClassTree)) { + lastArgIndex--; + } - /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ - private final Map bindings; + // Apply method invocation conversion to lastArgIndex arguments and use the + // remaining ones to initialize an array. + for (int i = 0; i < lastArgIndex; i++) { + Node actualVal = scan(actualExprs.get(i), null); + if (i == assertMethodTuple.booleanParam) { + treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); + } + convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); + } - /** The set of leaders (represented as indices into {@code nodeList}). */ - private final Set leaders; + TypeMirror elemType = ((ArrayType) lastParamType).getComponentType(); - /** - * All return nodes (if any) encountered. Only includes return statements that actually return - * something. - */ - private final List returnNodes; + List inits = new ArrayList<>(numActuals - lastArgIndex); + List initializers = new ArrayList<>(numActuals - lastArgIndex); + for (int i = lastArgIndex; i < numActuals; i++) { + inits.add(actualExprs.get(i)); + Node actualVal = scan(actualExprs.get(i), null); + initializers.add(assignConvert(actualVal, elemType)); + } + + NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits); + handleArtificialTree(wrappedVarargs); + + Node lastArgument = + new ArrayCreationNode( + wrappedVarargs, + lastParamType, + /* dimensions= */ Collections.emptyList(), + initializers); + extendWithNode(lastArgument); + + convertedNodes.add(lastArgument); + } + } else { + for (int i = 0; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + if (i == assertMethodTuple.booleanParam) { + treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); + } + convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); + } + } + + return convertedNodes; + } + + /** + * Returns the AssertMethodTuple for {@code method}. If {@code method} is not an assert method, + * then {@link AssertMethodTuple#NONE} is returned. + * + * @param method a method element that might be an assert method + * @return the AssertMethodTuple for {@code method} + */ + protected AssertMethodTuple getAssertMethodTuple(ExecutableElement method) { + AnnotationMirror assertMethodAnno = + annotationProvider.getDeclAnnotation(method, AssertMethod.class); + if (assertMethodAnno == null) { + return AssertMethodTuple.NONE; + } + + // Dataflow does not require checker-qual.jar to be on the users classpath, so + // AnnotationUtils.getElementValue(...) cannot be used. + + int booleanParam = + AnnotationUtils.getElementValueNotOnClasspath( + assertMethodAnno, "parameter", Integer.class, 1) + - 1; + + TypeMirror exceptionType = + AnnotationUtils.getElementValueNotOnClasspath( + assertMethodAnno, "value", Type.ClassType.class, (Type.ClassType) assertionErrorType); + boolean isAssertFalse = + AnnotationUtils.getElementValueNotOnClasspath( + assertMethodAnno, "isAssertFalse", Boolean.class, false); + return new AssertMethodTuple(booleanParam, exceptionType, isAssertFalse); + } + + /** Holds the elements of an {@link AssertMethod} annotation. */ + protected static class AssertMethodTuple { + + /** A tuple representing the lack of an {@link AssertMethodTuple}. */ + protected static final AssertMethodTuple NONE = new AssertMethodTuple(-1, null, false); /** - * Class declarations that have been encountered when building the control-flow graph for a - * method. + * 0-based index of the parameter of the expression that is tested by the assert method. (Or -1 + * if this isn't an assert method.) */ - private final List declaredClasses; + public final int booleanParam; + + /** The type of the exception thrown by the assert method. */ + public final TypeMirror exceptionType; + + /** Is this an assert false method? */ + public final boolean isAssertFalse; /** - * Lambdas encountered when building the control-flow graph for a method, variable initializer, - * or initializer. + * Creates an AssertMethodTuple. + * + * @param booleanParam 0-based index of the parameter of the expression that is tested by the + * assert method + * @param exceptionType the type of the exception thrown by the assert method + * @param isAssertFalse is this an assert false method */ - private final List declaredLambdas; + public AssertMethodTuple(int booleanParam, TypeMirror exceptionType, boolean isAssertFalse) { + this.booleanParam = booleanParam; + this.exceptionType = exceptionType; + this.isAssertFalse = isAssertFalse; + } + } + + /** + * Convert an operand of a conditional expression to the type of the whole expression. + * + * @param node a node occurring as the second or third operand of a conditional expression + * @param destType the type to promote the value to + * @return a Node with the value promoted to the destType, which may be the input node + */ + protected Node conditionalExprPromotion(Node node, TypeMirror destType) { + // For rules on converting operands of conditional expressions, + // JLS 15.25 + TypeMirror nodeType = node.getType(); + + // If the operand is already the same type as the whole + // expression, then do nothing. + if (types.isSameType(nodeType, destType)) { + return node; + } - /** The ArithmeticException type. */ - protected final TypeMirror arithmeticExceptionType; + // If the operand is a primitive and the whole expression is + // boxed, then apply boxing. + if (TypesUtils.isPrimitive(nodeType) && TypesUtils.isBoxedPrimitive(destType)) { + return box(node); + } - /** The ArrayIndexOutOfBoundsException type. */ - protected final TypeMirror arrayIndexOutOfBoundsExceptionType; + // If the operand is byte or Byte and the whole expression is + // short, then convert to short. + boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType); + TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType; + TypeMirror unboxedDestType = + TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType; + if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) { + if (unboxedNodeType.getKind() == TypeKind.BYTE && destType.getKind() == TypeKind.SHORT) { + if (isBoxedPrimitive) { + node = unbox(node); + } + return widen(node, destType); + } + + // If the operand is Byte, Short or Character and the whole expression + // is the unboxed version of it, then apply unboxing. + TypeKind destKind = destType.getKind(); + if (destKind == TypeKind.BYTE || destKind == TypeKind.CHAR || destKind == TypeKind.SHORT) { + if (isBoxedPrimitive) { + return unbox(node); + } else if (nodeType.getKind() == TypeKind.INT) { + return narrow(node, destType); + } + } - /** The AssertionError type. */ - protected final TypeMirror assertionErrorType; + return binaryNumericPromotion(node, destType); + } - /** The ClassCastException type . */ - protected final TypeMirror classCastExceptionType; + // For the final case in JLS 15.25, apply boxing but not lub. + if (TypesUtils.isPrimitive(nodeType) + && (destType.getKind() == TypeKind.DECLARED + || destType.getKind() == TypeKind.UNION + || destType.getKind() == TypeKind.INTERSECTION)) { + return box(node); + } - /** The Iterable type (erased). */ - protected final TypeMirror iterableType; + return node; + } + + /** + * Returns the label {@link Name} of the leaf in the argument path, or null if the leaf is not a + * labeled statement. + */ + protected @Nullable Name getLabel(TreePath path) { + if (path.getParentPath() != null) { + Tree parent = path.getParentPath().getLeaf(); + if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) { + return ((LabeledStatementTree) parent).getLabel(); + } + } + return null; + } + + /* --------------------------------------------------------- */ + /* Visitor Methods */ + /* --------------------------------------------------------- */ + + @Override + public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) { + return scan(tree.getUnderlyingType(), p); + } + + @Override + public Node visitAnnotation(AnnotationTree tree, Void p) { + throw new BugInCF("AnnotationTree is unexpected in AST to CFG translation"); + } + + @Override + public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { + + // see JLS 15.12.4 + + // First, compute the receiver, if any (15.12.4.1). + // Second, evaluate the actual arguments, left to right and possibly some arguments are + // stored into an array for varargs calls (15.12.4.2). + // Third, test the receiver, if any, for nullness (15.12.4.4). + // Fourth, convert the arguments to the type of the formal parameters (15.12.4.5). + // Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5). + ExecutableElement method = TreeUtils.elementFromUse(tree); + if (method == null) { + // The method wasn't found, e.g. because of a compilation error. + return null; + } - /** The NegativeArraySizeException type. */ - protected final TypeMirror negativeArraySizeExceptionType; + ExpressionTree methodSelect = tree.getMethodSelect(); + assert TreeUtils.isMethodAccess(methodSelect) + : "Expected a method access, but got: " + methodSelect; - /** The NullPointerException type . */ - protected final TypeMirror nullPointerExceptionType; + List actualExprs = tree.getArguments(); - /** The OutOfMemoryError type. */ - protected final @Nullable TypeMirror outOfMemoryErrorType; + // Look up method to invoke and possibly throw NullPointerException + Node receiver = getReceiver(methodSelect); - /** The ClassCircularityError type. */ - protected final @Nullable TypeMirror classCircularityErrorType; + MethodAccessNode target = new MethodAccessNode(methodSelect, method, receiver); - /** The ClassFormatErrorType type. */ - protected final @Nullable TypeMirror classFormatErrorType; + if (ElementUtils.isStatic(method) || receiver instanceof ThisNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(target); + } else { + extendWithNodeWithException(target, nullPointerExceptionType); + } - /** The NoClassDefFoundError type. */ - protected final @Nullable TypeMirror noClassDefFoundErrorType; + List arguments; + if (TreeUtils.isEnumSuperCall(tree)) { + // Don't convert arguments for enum super calls. The AST contains no actual arguments, + // while the method element expects two arguments, leading to an exception in + // convertCallArguments. + // Since no actual arguments are present in the AST that is being checked, it shouldn't + // cause any harm to omit the conversions. + // See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate. + arguments = Collections.emptyList(); + } else { + arguments = + convertCallArguments(tree, method, TreeUtils.typeFromUse(tree), actualExprs, null); + } - /** The String type. */ - protected final TypeMirror stringType; + // TODO: lock the receiver for synchronized methods - /** The Throwable type. */ - protected final TypeMirror throwableType; + MethodInvocationNode node = new MethodInvocationNode(tree, target, arguments, getCurrentPath()); - /** - * Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code - * RuntimeException} and {@code Error}. - */ - protected final Set uncheckedExceptionTypes; + ExtendedNode extendedNode = extendWithMethodInvocationNode(method, node); - /** - * Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the - * contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes - * from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions". - */ - protected final Set newArrayExceptionTypes; + /* Check for the TerminatesExecution annotation. */ + boolean terminatesExecution = + annotationProvider.getDeclAnnotation(method, TerminatesExecution.class) != null; + if (terminatesExecution) { + extendedNode.setTerminatesExecution(true); + } + return node; + } + + /** + * Extends the CFG with a MethodInvocationNode, accounting for potential exceptions thrown by the + * invocation. + * + * @param method the invoked method + * @param node the invocation + * @return an ExtendedNode representing the invocation and its possible thrown exceptions + */ + private ExtendedNode extendWithMethodInvocationNode( + ExecutableElement method, MethodInvocationNode node) { + List thrownTypes = method.getThrownTypes(); + Set thrownSet = + new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size()); + // Add exceptions explicitly mentioned in the throws clause. + thrownSet.addAll(thrownTypes); + // Add types to account for unchecked exceptions + thrownSet.addAll(uncheckedExceptionTypes); + + return extendWithNodeWithExceptions(node, thrownSet); + } + + @Override + public Node visitAssert(AssertTree tree, Void p) { + + // see JLS 14.10 + + // If assertions are enabled, then we can just translate the assertion. + if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) { + translateAssertWithAssertionsEnabled(tree); + return null; + } - /** - * Creates {@link CFGTranslationPhaseOne}. - * - * @param treeBuilder builder for new AST nodes - * @param annotationProvider extracts annotations from AST nodes - * @param assumeAssertionsDisabled can assertions be assumed to be disabled? - * @param assumeAssertionsEnabled can assertions be assumed to be enabled? - * @param env annotation processing environment containing type utilities - */ - public CFGTranslationPhaseOne( - TreeBuilder treeBuilder, - AnnotationProvider annotationProvider, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - this.env = env; - this.treeBuilder = treeBuilder; - this.annotationProvider = annotationProvider; - - assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); - this.assumeAssertionsEnabled = assumeAssertionsEnabled; - this.assumeAssertionsDisabled = assumeAssertionsDisabled; - - elements = env.getElementUtils(); - types = env.getTypeUtils(); - trees = Trees.instance(env); - - // initialize lists and maps - treeToCfgNodes = new IdentityHashMap<>(); - treeToConvertedCfgNodes = new IdentityHashMap<>(); - postfixTreeToCfgNodes = new IdentityHashMap<>(); - nodeList = new ArrayList<>(); - bindings = new HashMap<>(); - leaders = new HashSet<>(); - - regularExitLabel = new Label(); - exceptionalExitLabel = new Label(); - tryStack = new TryStack(exceptionalExitLabel); - returnTargetLC = new LabelCell(regularExitLabel); - breakLabels = new HashMap<>(2); - continueLabels = new HashMap<>(2); - returnNodes = new ArrayList<>(); - declaredClasses = new ArrayList<>(); - declaredLambdas = new ArrayList<>(); - - arithmeticExceptionType = getTypeMirror(ArithmeticException.class); - arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class); - assertionErrorType = getTypeMirror(AssertionError.class); - classCastExceptionType = getTypeMirror(ClassCastException.class); - iterableType = types.erasure(getTypeMirror(Iterable.class)); - negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class); - nullPointerExceptionType = getTypeMirror(NullPointerException.class); - outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class); - classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class); - classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class); - noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class); - stringType = getTypeMirror(String.class); - throwableType = getTypeMirror(Throwable.class); - uncheckedExceptionTypes = new ArraySet<>(2); - uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class)); - uncheckedExceptionTypes.add(getTypeMirror(Error.class)); - newArrayExceptionTypes = new ArraySet<>(2); - newArrayExceptionTypes.add(negativeArraySizeExceptionType); - if (outOfMemoryErrorType != null) { - newArrayExceptionTypes.add(outOfMemoryErrorType); - } + // If assertions are disabled, then nothing is executed. + if (assumeAssertionsDisabled) { + return null; } - /** - * Performs the actual work of phase one: processing a single body (of a method, lambda, - * top-level block, etc.). - * - * @param bodyPath path to the body of the underlying AST's method - * @param underlyingAST the AST for which the CFG is to be built - * @return the result of phase one - */ - public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) { - - // Set class variables - this.path = bodyPath; - - // Traverse AST of the method body. - try { // "finally" clause is "this.path = null" - Node finalNode = scan(path.getLeaf(), null); - - // If we are building the CFG for a lambda with a single expression as the body, then - // add an extra node for the result of that lambda. - if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { - LambdaExpressionTree lambdaTree = - ((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree(); - if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) { - Node resultNode = - new LambdaResultExpressionNode( - (ExpressionTree) lambdaTree.getBody(), finalNode); - extendWithNode(resultNode); - } - } + // Otherwise, we don't know if assertions are enabled, so we use a + // variable "ea" and case-split on it. One branch does execute the + // assertion, while the other assumes assertions are disabled. + VariableTree ea = getAssertionsEnabledVariable(); + + // all necessary labels + Label assertionEnabled = new Label(); + Label assertionDisabled = new Label(); + + extendWithNode(new LocalVariableNode(ea)); + extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled)); + + // 'then' branch (i.e. check the assertion) + addLabelForNextNode(assertionEnabled); + + translateAssertWithAssertionsEnabled(tree); + + // 'else' branch + addLabelForNextNode(assertionDisabled); + + return null; + } + + /** + * Should assertions be assumed to be executed for a given {@link AssertTree}? False by default. + */ + protected boolean assumeAssertionsEnabledFor(AssertTree tree) { + return false; + } + + /** The {@link VariableTree} that indicates whether assertions are enabled or not. */ + protected VariableTree ea = null; + + /** Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not. */ + protected VariableTree getAssertionsEnabledVariable() { + if (ea == null) { + String name = uniqueName("assertionsEnabled"); + Element owner = findOwner(); + ExpressionTree initializer = null; + ea = + treeBuilder.buildVariableDecl( + types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer); + handleArtificialTree(ea); + } + return ea; + } + + /** + * Find nearest owner element (Method or Class) which holds current tree. + * + * @return nearest owner element of current tree + */ + private Element findOwner() { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + if (enclosingMethod != null) { + return TreeUtils.elementFromDeclaration(enclosingMethod); + } else { + ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); + return TreeUtils.elementFromDeclaration(enclosingClass); + } + } + + /** + * Translates an assertion statement to the correct CFG nodes. The translation assumes that + * assertions are enabled. + */ + protected void translateAssertWithAssertionsEnabled(AssertTree tree) { + + // all necessary labels + Label assertEnd = new Label(); + Label elseEntry = new Label(); + + // basic block for the condition + Node condition = unbox(scan(tree.getCondition(), null)); + ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry); + extendWithExtendedNode(cjump); + + // else branch + Node detail = null; + addLabelForNextNode(elseEntry); + if (tree.getDetail() != null) { + detail = scan(tree.getDetail(), null); + } + AssertionErrorNode assertNode = + new AssertionErrorNode(tree, condition, detail, assertionErrorType); + extendWithNode(assertNode); + NodeWithExceptionsHolder exNode = + extendWithNodeWithException( + new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType); + exNode.setTerminatesExecution(true); + + // then branch (nothing happens) + addLabelForNextNode(assertEnd); + } + + /** + * Translates a method marked as {@link AssertMethod} into CFG nodes corresponding to an {@code + * assert} statement. + * + * @param tree the method invocation tree for a method marked as {@link AssertMethod} + * @param assertMethodTuple the assert method tuple for the method + * @param condition the boolean expression node for the argument that the method tests + */ + protected void treatMethodAsAssert( + MethodInvocationTree tree, AssertMethodTuple assertMethodTuple, Node condition) { + // all necessary labels + Label thenLabel = new Label(); + Label elseLabel = new Label(); + ConditionalJump cjump = new ConditionalJump(thenLabel, elseLabel); + extendWithExtendedNode(cjump); + + addLabelForNextNode(assertMethodTuple.isAssertFalse ? thenLabel : elseLabel); + AssertionErrorNode assertNode = + new AssertionErrorNode(tree, condition, null, assertMethodTuple.exceptionType); + extendWithNode(assertNode); + NodeWithExceptionsHolder exNode = + extendWithNodeWithException( + new ThrowNode(null, assertNode, env.getTypeUtils()), assertMethodTuple.exceptionType); + exNode.setTerminatesExecution(true); + + addLabelForNextNode(assertMethodTuple.isAssertFalse ? elseLabel : thenLabel); + } + + @Override + public Node visitAssignment(AssignmentTree tree, Void p) { + + // see JLS 15.26.1 + + AssignmentNode assignmentNode; + ExpressionTree variable = tree.getVariable(); + TypeMirror varType = TreeUtils.typeOf(variable); + + // case 1: lhs is field access + if (TreeUtils.isFieldAccess(variable)) { + // visit receiver + Node receiver = getReceiver(variable); + + // visit expression + Node expression = scan(tree.getExpression(), p); + expression = assignConvert(expression, varType); + + // visit field access (throws null-pointer exception) + FieldAccessNode target = new FieldAccessNode(variable, receiver); + target.setLValue(); + + Element element = TreeUtils.elementFromUse(variable); + if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(target); + } else { + extendWithNodeWithException(target, nullPointerExceptionType); + } + + // add assignment node + assignmentNode = new AssignmentNode(tree, target, expression); + extendWithNode(assignmentNode); + } - // Add marker to indicate that the next block will be the exit block. - // Note: if there is a return statement earlier in the method (which is always the case - // for non-void methods), then this is not strictly necessary. However, it is also not a - // problem, as it will just generate a degenerate control graph case that will be - // removed in a later phase. - nodeList.add(new UnconditionalJump(regularExitLabel)); - - return new PhaseOneResult( - underlyingAST, - treeToCfgNodes, - treeToConvertedCfgNodes, - postfixTreeToCfgNodes, - nodeList, - bindings, - leaders, - returnNodes, - regularExitLabel, - exceptionalExitLabel, - declaredClasses, - declaredLambdas, - types); - } finally { - this.path = null; - } + // case 2: lhs is not a field access + else { + Node target = scan(variable, p); + target.setLValue(); + + assignmentNode = translateAssignment(tree, target, tree.getExpression()); } - /** - * Process a single body within {@code root}. This method does not process the entire given - * CompilationUnitTree. Rather, it processes one body (of a method/lambda/etc.) within it, which - * corresponds to {@code underlyingAST}. - * - * @param root the compilation unit - * @param underlyingAST the AST corresponding to the body to process - * @return a PhaseOneResult - */ - public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) { - // TODO: Isn't this costly? Is there no cache we can reuse? - TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); - assert bodyPath != null; - return process(bodyPath, underlyingAST); + return assignmentNode; + } + + /** Translate an assignment. */ + protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) { + Node expression = scan(rhs, null); + return translateAssignment(tree, target, expression); + } + + /** Translate an assignment where the RHS has already been scanned. */ + protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) { + assert tree instanceof AssignmentTree || tree instanceof VariableTree; + target.setLValue(); + expression = assignConvert(expression, target.getType()); + AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression); + extendWithNode(assignmentNode); + return assignmentNode; + } + + /** + * Note 1: Requires {@code tree} to be a field or method access tree. + * + *

Note 2: Visits the receiver and adds all necessary blocks to the CFG. + * + * @param tree the field or method access tree containing the receiver: one of + * MethodInvocationTree, AssignmentTree, or IdentifierTree + * @return the receiver of the field or method access + */ + private Node getReceiver(ExpressionTree tree) { + assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree); + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + // `tree` has an explicit receiver. + MemberSelectTree mtree = (MemberSelectTree) tree; + return scan(mtree.getExpression(), null); } - /** - * Perform any actions required when CFG translation creates a new Tree that is not part of the - * original AST. - * - * @param tree the newly created Tree - */ - public void handleArtificialTree(Tree tree) {} + // Access through an implicit receiver + Element ele = TreeUtils.elementFromUse(tree); + TypeElement declClassElem = ElementUtils.enclosingTypeElement(ele); + TypeMirror declClassType = ElementUtils.getType(declClassElem); - /** - * Returns the current path for the tree currently being scanned. - * - * @return the current path - */ - public TreePath getCurrentPath() { - return path; + if (ElementUtils.isStatic(ele)) { + ClassNameNode node = new ClassNameNode(declClassType, declClassElem); + extendWithClassNameNode(node); + return node; } - // TODO: remove method and instead use JCP to add version-specific methods. - // Switch expressions first appeared in 12, standard in 14, so don't use 17. - // TODO: look into changing back to TreePathScanner and removing path/getCurrentPath. - @Override - public Node scan(Tree tree, Void p) { - if (tree == null) { - return null; - } + // Access through an implicit `this` + TreePath enclClassPath = TreePathUtil.pathTillClass(getCurrentPath()); + ClassTree enclClassTree = (ClassTree) enclClassPath.getLeaf(); + TypeElement enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); + TypeMirror enclClassType = enclClassElem.asType(); + while (!TypesUtils.isErasedSubtype(enclClassType, declClassType, types)) { + enclClassPath = TreePathUtil.pathTillClass(enclClassPath.getParentPath()); + if (enclClassPath == null) { + enclClassType = declClassType; + break; + } + enclClassTree = (ClassTree) enclClassPath.getLeaf(); + enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); + enclClassType = enclClassElem.asType(); + } + Node node = new ImplicitThisNode(enclClassType); + extendWithNode(node); + return node; + } + + /** + * Map an operation with assignment to the corresponding operation without assignment. + * + * @param kind a Tree.Kind representing an operation with assignment + * @return the Tree.Kind for the same operation without assignment + */ + protected Tree.Kind withoutAssignment(Tree.Kind kind) { + switch (kind) { + case DIVIDE_ASSIGNMENT: + return Tree.Kind.DIVIDE; + case MULTIPLY_ASSIGNMENT: + return Tree.Kind.MULTIPLY; + case REMAINDER_ASSIGNMENT: + return Tree.Kind.REMAINDER; + case MINUS_ASSIGNMENT: + return Tree.Kind.MINUS; + case PLUS_ASSIGNMENT: + return Tree.Kind.PLUS; + case LEFT_SHIFT_ASSIGNMENT: + return Tree.Kind.LEFT_SHIFT; + case RIGHT_SHIFT_ASSIGNMENT: + return Tree.Kind.RIGHT_SHIFT; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + return Tree.Kind.UNSIGNED_RIGHT_SHIFT; + case AND_ASSIGNMENT: + return Tree.Kind.AND; + case OR_ASSIGNMENT: + return Tree.Kind.OR; + case XOR_ASSIGNMENT: + return Tree.Kind.XOR; + default: + return Tree.Kind.ERRONEOUS; + } + } + + @Override + public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + // According the JLS 15.26.2, E1 op= E2 is equivalent to + // E1 = (T) ((E1) op (E2)), where T is the type of E1, + // except that E1 is evaluated only once. + // + + Tree.Kind kind = tree.getKind(); + switch (kind) { + case DIVIDE_ASSIGNMENT: + case MULTIPLY_ASSIGNMENT: + case REMAINDER_ASSIGNMENT: + { + // see JLS 15.17 and 15.26.2 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror exprType = TreeUtils.typeOf(tree); + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + + BinaryTree operTree = + treeBuilder.buildBinary( + promotedType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) { + operNode = new NumericalMultiplicationNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) { + if (TypesUtils.isIntegralPrimitive(exprType)) { + operNode = new IntegerDivisionNode(operTree, targetRHS, value); + + extendWithNodeWithException(operNode, arithmeticExceptionType); + } else { + operNode = new FloatingDivisionNode(operTree, targetRHS, value); + } + } else { + assert kind == Tree.Kind.REMAINDER_ASSIGNMENT; + if (TypesUtils.isIntegralPrimitive(exprType)) { + operNode = new IntegerRemainderNode(operTree, targetRHS, value); - TreePath prev = path; - @SuppressWarnings("interning:not.interned") // Looking for exact match. - boolean treeIsLeaf = path.getLeaf() != tree; - if (treeIsLeaf) { - path = new TreePath(path, tree); + extendWithNodeWithException(operNode, arithmeticExceptionType); + } else { + operNode = new FloatingRemainderNode(operTree, targetRHS, value); + } + } + extendWithNode(operNode); + + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; } - try { - // TODO: use JCP to add version-specific behavior - if (SystemUtil.jreVersion >= 14) { - // Must use String comparison to support compiling on JDK 11 and earlier. - // Features added between JDK 12 and JDK 17 inclusive. - switch (tree.getKind().name()) { - case "BINDING_PATTERN": - return visitBindingPattern17(path.getLeaf(), p); - case "SWITCH_EXPRESSION": - return visitSwitchExpression17(tree, p); - case "YIELD": - return visitYield17(tree, p); - case "DECONSTRUCTION_PATTERN": - return visitDeconstructionPattern21(tree, p); - default: - // fall through to generic behavior - } + + case MINUS_ASSIGNMENT: + case PLUS_ASSIGNMENT: + { + // see JLS 15.18 and 15.26.2 + + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); + + if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { + assert (kind == Tree.Kind.PLUS_ASSIGNMENT); + Node targetRHS = stringConversion(targetLHS); + value = stringConversion(value); + BinaryTree operTree = + treeBuilder.buildBinary( + leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode = new StringConcatenateNode(operTree, targetRHS, value); + extendWithNode(operNode); + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, operNode); + extendWithNode(assignNode); + return assignNode; + } else { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + + BinaryTree operTree = + treeBuilder.buildBinary( + promotedType, + withoutAssignment(kind), + tree.getVariable(), + tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.PLUS_ASSIGNMENT) { + operNode = new NumericalAdditionNode(operTree, targetRHS, value); + } else { + assert kind == Tree.Kind.MINUS_ASSIGNMENT; + operNode = new NumericalSubtractionNode(operTree, targetRHS, value); } + extendWithNode(operNode); + + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + // Map the compound assignment tree to an assignment node, which + // will have the correct type. + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + } + } - return tree.accept(this, p); - } finally { - path = prev; + case LEFT_SHIFT_ASSIGNMENT: + case RIGHT_SHIFT_ASSIGNMENT: + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + { + // see JLS 15.19 and 15.26.2 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + + Node targetRHS = unaryNumericPromotion(targetLHS); + value = unaryNumericPromotion(value); + + BinaryTree operTree = + treeBuilder.buildBinary( + leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) { + operNode = new LeftShiftNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) { + operNode = new SignedRightShiftNode(operTree, targetRHS, value); + } else { + assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; + operNode = new UnsignedRightShiftNode(operTree, targetRHS, value); + } + extendWithNode(operNode); + + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; } - } - /** - * Visit a SwitchExpressionTree. - * - * @param yieldTree a YieldTree, typed as Tree to be backward-compatible - * @param p parameter - * @return the result of visiting the switch expression tree - */ - public Node visitYield17(Tree yieldTree, Void p) { - ExpressionTree resultExpression = YieldUtils.getValue(yieldTree); - switchBuilder.buildSwitchExpressionResult(resultExpression); - return null; - } + case AND_ASSIGNMENT: + case OR_ASSIGNMENT: + case XOR_ASSIGNMENT: + // see JLS 15.22 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); + + Node targetRHS = null; + if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + } else if (TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType)) { + targetRHS = unbox(targetLHS); + value = unbox(value); + } else { + throw new BugInCF("Both arguments to logical operation must be numeric or boolean"); + } - /** - * Visit a SwitchExpressionTree - * - * @param switchExpressionTree a SwitchExpressionTree, typed as Tree to be backward-compatible - * @param p parameter - * @return the result of visiting the switch expression tree - */ - public Node visitSwitchExpression17(Tree switchExpressionTree, Void p) { - SwitchBuilder oldSwitchBuilder = switchBuilder; - switchBuilder = new SwitchBuilder(switchExpressionTree); - Node result = switchBuilder.build(); - switchBuilder = oldSwitchBuilder; - return result; - } + BinaryTree operTree = + treeBuilder.buildBinary( + leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.AND_ASSIGNMENT) { + operNode = new BitwiseAndNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.OR_ASSIGNMENT) { + operNode = new BitwiseOrNode(operTree, targetRHS, value); + } else { + assert kind == Tree.Kind.XOR_ASSIGNMENT; + operNode = new BitwiseXorNode(operTree, targetRHS, value); + } + extendWithNode(operNode); - /** - * Visit a BindingPatternTree - * - * @param bindingPatternTree a BindingPatternTree, typed as Tree to be backward-compatible - * @param p parameter - * @return the result of visiting the binding pattern tree - */ - public Node visitBindingPattern17(Tree bindingPatternTree, Void p) { - ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); - TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass); - Node receiver = new ImplicitThisNode(classElem.asType()); - VariableTree varTree = BindingPatternUtils.getVariable(bindingPatternTree); - VariableDeclarationNode variableDeclarationNode = new VariableDeclarationNode(varTree); - extendWithNode(variableDeclarationNode); - LocalVariableNode varNode = new LocalVariableNode(varTree, receiver); - extendWithNode(varNode); - return varNode; + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + default: + throw new BugInCF("unexpected compound assignment type"); } + } + + @Override + public Node visitBinary(BinaryTree tree, Void p) { + // Note that for binary operations it is important to perform any required promotion on the + // left operand before generating any Nodes for the right operand, because labels must be + // inserted AFTER ALL preceding Nodes and BEFORE ALL following Nodes. + Node r = null; + Tree leftTree = tree.getLeftOperand(); + Tree rightTree = tree.getRightOperand(); + + Tree.Kind kind = tree.getKind(); + switch (kind) { + case DIVIDE: + case MULTIPLY: + case REMAINDER: + { + // see JLS 15.17 + + TypeMirror exprType = TreeUtils.typeOf(tree); + TypeMirror leftType = TreeUtils.typeOf(leftTree); + TypeMirror rightType = TreeUtils.typeOf(rightTree); + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + if (kind == Tree.Kind.MULTIPLY) { + r = new NumericalMultiplicationNode(tree, left, right); + } else if (kind == Tree.Kind.DIVIDE) { + if (TypesUtils.isIntegralPrimitive(exprType)) { + r = new IntegerDivisionNode(tree, left, right); + + extendWithNodeWithException(r, arithmeticExceptionType); + } else { + r = new FloatingDivisionNode(tree, left, right); + } + } else { + assert kind == Tree.Kind.REMAINDER; + if (TypesUtils.isIntegralPrimitive(exprType)) { + r = new IntegerRemainderNode(tree, left, right); - /** - * Visit a DeconstructionPatternTree. - * - * @param deconstructionPatternTree a DeconstructionPatternTree, typed as Tree so the Checker - * Framework compiles under JDK 20 and earlier - * @param p an unused parameter - * @return the result of visiting the tree - */ - public Node visitDeconstructionPattern21(Tree deconstructionPatternTree, Void p) { - List nestedPatternTrees = - DeconstructionPatternUtils.getNestedPatterns(deconstructionPatternTree); - List nestedPatterns = new ArrayList<>(nestedPatternTrees.size()); - for (Tree pattern : nestedPatternTrees) { - nestedPatterns.add(scan(pattern, p)); + extendWithNodeWithException(r, arithmeticExceptionType); + } else { + r = new FloatingRemainderNode(tree, left, right); + } + } + break; } - DeconstructorPatternNode dcpN = - new DeconstructorPatternNode( - TreeUtils.typeOf(deconstructionPatternTree), - deconstructionPatternTree, - nestedPatterns); - extendWithNode(dcpN); - return dcpN; - } + case MINUS: + case PLUS: + { + // see JLS 15.18 + + // TypeMirror exprType = InternalUtils.typeOf(tree); + TypeMirror leftType = TreeUtils.typeOf(leftTree); + TypeMirror rightType = TreeUtils.typeOf(rightTree); + + if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { + assert (kind == Tree.Kind.PLUS); + Node left = stringConversion(scan(leftTree, p)); + Node right = stringConversion(scan(rightTree, p)); + r = new StringConcatenateNode(tree, left, right); + } else { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + // TODO: Decide whether to deal with floating-point value + // set conversion. + if (kind == Tree.Kind.PLUS) { + r = new NumericalAdditionNode(tree, left, right); + } else { + assert kind == Tree.Kind.MINUS; + r = new NumericalSubtractionNode(tree, left, right); + } + } + break; + } - /* --------------------------------------------------------- */ - /* Nodes and Labels Management */ - /* --------------------------------------------------------- */ + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + { + // see JLS 15.19 + + Node left = unaryNumericPromotion(scan(leftTree, p)); + Node right = unaryNumericPromotion(scan(rightTree, p)); + + if (kind == Tree.Kind.LEFT_SHIFT) { + r = new LeftShiftNode(tree, left, right); + } else if (kind == Tree.Kind.RIGHT_SHIFT) { + r = new SignedRightShiftNode(tree, left, right); + } else { + assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT; + r = new UnsignedRightShiftNode(tree, left, right); + } + break; + } - /** - * Add a node to the lookup map if it not already present. - * - * @param node the node to add to the lookup map - */ - protected void addToLookupMap(Node node) { - Tree tree = node.getTree(); - if (tree == null) { - return; + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case LESS_THAN: + case LESS_THAN_EQUAL: + { + // see JLS 15.20.1 + TypeMirror leftType = TreeUtils.typeOf(leftTree); + if (TypesUtils.isBoxedPrimitive(leftType)) { + leftType = types.unboxedType(leftType); + } + + TypeMirror rightType = TreeUtils.typeOf(rightTree); + if (TypesUtils.isBoxedPrimitive(rightType)) { + rightType = types.unboxedType(rightType); + } + + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + Node node; + if (kind == Tree.Kind.GREATER_THAN) { + node = new GreaterThanNode(tree, left, right); + } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) { + node = new GreaterThanOrEqualNode(tree, left, right); + } else if (kind == Tree.Kind.LESS_THAN) { + node = new LessThanNode(tree, left, right); + } else { + assert kind == Tree.Kind.LESS_THAN_EQUAL; + node = new LessThanOrEqualNode(tree, left, right); + } + + extendWithNode(node); + + return node; } - Set existing = treeToCfgNodes.get(tree); - if (existing == null) { - Set newSet = new IdentityArraySet(1); - newSet.add(node); - treeToCfgNodes.put(tree, newSet); - } else { - existing.add(node); + + case EQUAL_TO: + case NOT_EQUAL_TO: + { + // see JLS 15.21 + TreeInfo leftInfo = getTreeInfo(leftTree); + TreeInfo rightInfo = getTreeInfo(rightTree); + Node left = scan(leftTree, p); + Node right = scan(rightTree, p); + + if (leftInfo.isNumeric() + && rightInfo.isNumeric() + && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + // JLS 15.21.1 numerical equality + TypeMirror promotedType = + binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType()); + left = binaryNumericPromotion(left, promotedType); + right = binaryNumericPromotion(right, promotedType); + } else if (leftInfo.isBoolean() + && rightInfo.isBoolean() + && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + // JSL 15.21.2 boolean equality + left = unboxAsNeeded(left, leftInfo.isBoxed()); + right = unboxAsNeeded(right, rightInfo.isBoxed()); + } + + Node node; + if (kind == Tree.Kind.EQUAL_TO) { + node = new EqualToNode(tree, left, right); + } else { + assert kind == Tree.Kind.NOT_EQUAL_TO; + node = new NotEqualNode(tree, left, right); + } + extendWithNode(node); + + return node; + } + + case AND: + case OR: + case XOR: + { + // see JLS 15.22 + TypeMirror leftType = TreeUtils.typeOf(leftTree); + TypeMirror rightType = TreeUtils.typeOf(rightTree); + boolean isBooleanOp = + TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType); + + Node left; + Node right; + + if (isBooleanOp) { + left = unbox(scan(leftTree, p)); + right = unbox(scan(rightTree, p)); + } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + left = binaryNumericPromotion(scan(leftTree, p), promotedType); + right = binaryNumericPromotion(scan(rightTree, p), promotedType); + } else { + left = unbox(scan(leftTree, p)); + right = unbox(scan(rightTree, p)); + } + + Node node; + if (kind == Tree.Kind.AND) { + node = new BitwiseAndNode(tree, left, right); + } else if (kind == Tree.Kind.OR) { + node = new BitwiseOrNode(tree, left, right); + } else { + assert kind == Tree.Kind.XOR; + node = new BitwiseXorNode(tree, left, right); + } + + extendWithNode(node); + + return node; } - Tree enclosingParens = parenMapping.get(tree); - while (enclosingParens != null) { - Set exp = - treeToCfgNodes.computeIfAbsent(enclosingParens, k -> new IdentityArraySet<>(1)); - // `node` could already be in set `exp`, but it's probably as fast to just add again - exp.add(node); - enclosingParens = parenMapping.get(enclosingParens); + case CONDITIONAL_AND: + case CONDITIONAL_OR: + { + // see JLS 15.23 and 15.24 + + // all necessary labels + Label rightStartLabel = new Label(); + Label shortCircuitLabel = new Label(); + + // left-hand side + Node left = scan(leftTree, p); + + ConditionalJump cjump; + if (kind == Tree.Kind.CONDITIONAL_AND) { + cjump = new ConditionalJump(rightStartLabel, shortCircuitLabel); + cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE); + } else { + cjump = new ConditionalJump(shortCircuitLabel, rightStartLabel); + cjump.setTrueFlowRule(FlowRule.THEN_TO_THEN); + } + extendWithExtendedNode(cjump); + + // right-hand side + addLabelForNextNode(rightStartLabel); + Node right = scan(rightTree, p); + + // conditional expression itself + addLabelForNextNode(shortCircuitLabel); + Node node; + if (kind == Tree.Kind.CONDITIONAL_AND) { + node = new ConditionalAndNode(tree, left, right); + } else { + node = new ConditionalOrNode(tree, left, right); + } + extendWithNode(node); + return node; } + default: + throw new BugInCF("unexpected binary tree: " + kind); + } + assert r != null : "unexpected binary tree"; + extendWithNode(r); + return r; + } + + @Override + public Node visitBlock(BlockTree tree, Void p) { + for (StatementTree n : tree.getStatements()) { + scan(n, null); + } + return null; + } + + @Override + public Node visitBreak(BreakTree tree, Void p) { + Name label = tree.getLabel(); + if (label == null) { + assert breakTargetLC != null : "no target for break statement"; + + extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); + } else { + assert breakLabels.containsKey(label); + + extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label))); } + return null; + } + + // This visits a switch statement. + // Switch expressions are visited by visitSwitchExpression17. + @Override + public Node visitSwitch(SwitchTree tree, Void p) { + SwitchBuilder builder = new SwitchBuilder(tree); + builder.build(); + return null; + } + + /** + * Helper class for handling switch statements and switch expressions, including all their + * substatements such as case labels. + */ + private class SwitchBuilder { + /** - * Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree - * should already be in the pre-conversion lookup map. This method is used to update the - * Tree-Node mapping with conversion nodes. - * - * @param node the node to add to the lookup map + * The tree for the switch statement or switch expression. Its type may be {@link SwitchTree} + * (for a switch statement) or {@code SwitchExpressionTree}. */ - protected void addToConvertedLookupMap(Node node) { - Tree tree = node.getTree(); - addToConvertedLookupMap(tree, node); - } + private final Tree switchTree; + + /** The case trees of {@code switchTree} */ + private final List caseTrees; /** - * Add a node in the post-conversion lookup map. The tree argument should already be in the - * pre-conversion lookup map. This method is used to update the Tree-Node mapping with - * conversion nodes. + * The Tree for the selector expression. * - * @param tree the tree used as a key in the map - * @param node the node to add to the lookup map + *

+     *   switch ( selector expression ) { ... }
+     * 
*/ - protected void addToConvertedLookupMap(Tree tree, Node node) { - assert tree != null; - assert treeToCfgNodes.containsKey(tree); - Set existing = treeToConvertedCfgNodes.get(tree); - if (existing == null) { - Set newSet = new IdentityArraySet<>(1); - newSet.add(node); - treeToConvertedCfgNodes.put(tree, newSet); - } else { - existing.add(node); - } - } + private final ExpressionTree selectorExprTree; + + /** The labels for the case bodies. */ + private final Label[] caseBodyLabels; /** - * Extend the list of extended nodes with a node. - * - * @param node the node to add + * The Node for the assignment of the switch selector expression to a synthetic local variable. */ - protected void extendWithNode(Node node) { - addToLookupMap(node); - extendWithExtendedNode(new NodeHolder(node)); - } + private AssignmentNode selectorExprAssignment; /** - * Extend the list of extended nodes with a node, where {@code node} might throw the exception - * {@code cause}. - * - * @param node the node to add - * @param cause an exception that the node might throw - * @return the node holder + * If {@link #switchTree} is a switch expression, then this is a result variable: the synthetic + * variable that all results of {@code #switchTree} are assigned to. Otherwise, this is null. */ - protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) { - addToLookupMap(node); - return extendWithNodeWithExceptions(node, Collections.singleton(cause)); - } + private @Nullable VariableTree switchExprVarTree; /** - * Extend the list of extended nodes with a node, where {@code node} might throw any of the - * exceptions in {@code causes}. + * Construct a SwitchBuilder. * - * @param node the node to add - * @param causes set of exceptions that the node might throw - * @return the node holder + * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} */ - protected NodeWithExceptionsHolder extendWithNodeWithExceptions( - Node node, Set causes) { - addToLookupMap(node); - Map> exceptions = new ArrayMap<>(causes.size()); - for (TypeMirror cause : causes) { - exceptions.put(cause, tryStack.possibleLabels(cause)); - } - NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); - extendWithExtendedNode(exNode); - return exNode; + private SwitchBuilder(Tree switchTree) { + this.switchTree = switchTree; + if (TreeUtils.isSwitchStatement(switchTree)) { + SwitchTree switchStatementTree = (SwitchTree) switchTree; + this.caseTrees = switchStatementTree.getCases(); + this.selectorExprTree = switchStatementTree.getExpression(); + } else { + this.caseTrees = SwitchExpressionUtils.getCases(switchTree); + this.selectorExprTree = SwitchExpressionUtils.getExpression(switchTree); + } + // "+ 1" for the default case. If the switch has an explicit default case, then + // the last element of the array is never used. + this.caseBodyLabels = new Label[caseTrees.size() + 1]; } /** - * Extend a list of extended nodes with a ClassName node. - * - *

Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw - * one of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1). + * Build up the CFG for the switchTree. * - * @param node the ClassName node to add - * @return the node holder + * @return if the switch is a switch expression, then a {@link SwitchExpressionNode}; otherwise, + * null */ - protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) { - Set thrownSet = new ArraySet<>(4); - if (classCircularityErrorType != null) { - thrownSet.add(classCircularityErrorType); - } - if (classFormatErrorType != null) { - thrownSet.add(classFormatErrorType); - } - if (noClassDefFoundErrorType != null) { - thrownSet.add(noClassDefFoundErrorType); - } - if (outOfMemoryErrorType != null) { - thrownSet.add(outOfMemoryErrorType); - } - - return extendWithNodeWithExceptions(node, thrownSet); - } - - /** - * Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list - * if {@code pred} is not present. - * - * @param node the node to add - * @param pred the desired predecessor of node - * @return the node holder - */ - protected T insertNodeAfter(T node, Node pred) { - addToLookupMap(node); - insertExtendedNodeAfter(new NodeHolder(node), pred); - return node; - } - - /** - * Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in - * the list of extended nodes, or append to the list if {@code pred} is not present. - * - * @param node the node to add - * @param causes set of exceptions that the node might throw - * @param pred the desired predecessor of node - * @return the node holder - */ - protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter( - Node node, Set causes, Node pred) { - addToLookupMap(node); - Map> exceptions = new ArrayMap<>(causes.size()); - for (TypeMirror cause : causes) { - exceptions.put(cause, tryStack.possibleLabels(cause)); - } - NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); - insertExtendedNodeAfter(exNode, pred); - return exNode; - } - - /** - * Extend the list of extended nodes with an extended node. - * - * @param n the extended node - */ - protected void extendWithExtendedNode(ExtendedNode n) { - nodeList.add(n); - } - - /** - * Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code - * n} if {@code pred} is not present. - * - * @param n the extended node - * @param pred the desired predecessor - */ - @SuppressWarnings("ModifyCollectionInEnhancedForLoop") - protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) { - int index = -1; - for (int i = 0; i < nodeList.size(); i++) { - ExtendedNode inList = nodeList.get(i); - if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) { - if (inList.getNode() == pred) { - index = i; - break; - } - } - } - if (index != -1) { - nodeList.add(index + 1, n); - // update bindings - for (Map.Entry e : bindings.entrySet()) { - if (e.getValue() >= index + 1) { - bindings.put(e.getKey(), e.getValue() + 1); - } - } - // update leaders - Set oldLeaders = new HashSet<>(leaders); - leaders.clear(); - for (Integer l : oldLeaders) { - if (l >= index + 1) { - leaders.add(l + 1); - } else { - leaders.add(l); - } - } - } else { - nodeList.add(n); - } - } - - /** - * Add the label {@code l} to the extended node that will be placed next in the sequence. - * - * @param l the node to add to the forthcoming extended node - */ - protected void addLabelForNextNode(Label l) { - if (bindings.containsKey(l)) { - throw new BugInCF("bindings already contains key %s: %s", l, bindings); - } - leaders.add(nodeList.size()); - bindings.put(l, nodeList.size()); - } - - /* --------------------------------------------------------- */ - /* Utility Methods */ - /* --------------------------------------------------------- */ - - /** The UID for the next unique name. */ - protected long uid = 0; - - /** - * Returns a unique name starting with {@code prefix}. - * - * @param prefix the prefix of the unique name - * @return a unique name starting with {@code prefix} - */ - protected String uniqueName(String prefix) { - return prefix + "#num" + uid++; - } - - /** - * If the input node is an unboxed primitive type, insert a call to the appropriate valueOf - * method, otherwise leave it alone. - * - * @param node in input node - * @return a Node representing the boxed version of the input, which may simply be the input - * node - */ - protected Node box(Node node) { - // For boxing conversion, see JLS 5.1.7 - if (TypesUtils.isPrimitive(node.getType())) { - PrimitiveType primitive = types.getPrimitiveType(node.getType().getKind()); - TypeMirror boxedType = types.getDeclaredType(types.boxedClass(primitive)); - - TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); - IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement); - handleArtificialTree(classTree); - // No need to handle possible errors from evaluating a class literal here - // since this is synthetic code that can't fail. - ClassNameNode className = new ClassNameNode(classTree); - className.setInSource(false); - insertNodeAfter(className, node); - - MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree); - handleArtificialTree(valueOfSelect); - MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className); - valueOfAccess.setInSource(false); - insertNodeAfter(valueOfAccess, className); - - MethodInvocationTree valueOfCall = - treeBuilder.buildMethodInvocation( - valueOfSelect, (ExpressionTree) node.getTree()); - handleArtificialTree(valueOfCall); - Node boxed = - new MethodInvocationNode( - valueOfCall, - valueOfAccess, - Collections.singletonList(node), - getCurrentPath()); - boxed.setInSource(false); - // Add Throwable to account for unchecked exceptions - addToConvertedLookupMap(node.getTree(), boxed); - insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess); - return boxed; - } else { - return node; - } - } - - /** - * If the input node is a boxed type, unbox it, otherwise leave it alone. - * - * @param node in input node - * @return a Node representing the unboxed version of the input, which may simply be the input - * node - */ - protected Node unbox(Node node) { - if (TypesUtils.isBoxedPrimitive(node.getType())) { - - MemberSelectTree primValueSelect = - treeBuilder.buildPrimValueMethodAccess(node.getTree()); - handleArtificialTree(primValueSelect); - MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node); - primValueAccess.setInSource(false); - // Method access may throw NullPointerException - insertNodeWithExceptionsAfter( - primValueAccess, Collections.singleton(nullPointerExceptionType), node); - - MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect); - handleArtificialTree(primValueCall); - Node unboxed = - new MethodInvocationNode( - primValueCall, - primValueAccess, - Collections.emptyList(), - getCurrentPath()); - unboxed.setInSource(false); - - // Add Throwable to account for unchecked exceptions - addToConvertedLookupMap(node.getTree(), unboxed); - insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess); - return unboxed; - } else { - return node; - } - } - - private TreeInfo getTreeInfo(Tree tree) { - TypeMirror type = TreeUtils.typeOf(tree); - boolean boxed = TypesUtils.isBoxedPrimitive(type); - TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; - - boolean bool = TypesUtils.isBooleanType(type); - boolean numeric = TypesUtils.isNumeric(unboxedType); - - return new TreeInfo() { - @Override - public boolean isNumeric() { - return numeric; - } - - @Override - public boolean isBoxed() { - return boxed; - } - - @Override - public boolean isBoolean() { - return bool; - } - - @Override - public TypeMirror unboxedType() { - return unboxedType; - } - }; - } - - /** - * Returns the unboxed tree if necessary, as described in JLS 5.1.8. - * - * @return the unboxed tree if necessary, as described in JLS 5.1.8 - */ - private Node unboxAsNeeded(Node node, boolean boxed) { - return boxed ? unbox(node) : node; - } - - /** - * Convert the input node to String type, if it isn't already. - * - * @param node an input node - * @return a Node with the value promoted to String, which may be the input node - */ - protected Node stringConversion(Node node) { - // For string conversion, see JLS 5.1.11 - if (!TypesUtils.isString(node.getType())) { - Node converted = new StringConversionNode(node.getTree(), node, stringType); - addToConvertedLookupMap(converted); - insertNodeAfter(converted, node); - return converted; - } else { - return node; - } - } - - /** - * Perform unary numeric promotion on the input node. - * - * @param node a node producing a value of numeric primitive or boxed type - * @return a Node with the value promoted to the int, long, float, or double; may return be the - * input node - */ - protected Node unaryNumericPromotion(Node node) { - // For unary numeric promotion, see JLS 5.6.1 - node = unbox(node); - - switch (node.getType().getKind()) { - case BYTE: - case CHAR: - case SHORT: - { - TypeMirror intType = types.getPrimitiveType(TypeKind.INT); - Node widened = new WideningConversionNode(node.getTree(), node, intType); - addToConvertedLookupMap(widened); - insertNodeAfter(widened, node); - return widened; - } - default: - // Nothing to do. - break; - } - - return node; - } - - /** - * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and - * false otherwise. - */ - protected boolean isNumericOrBoxed(TypeMirror type) { - if (TypesUtils.isBoxedPrimitive(type)) { - type = types.unboxedType(type); - } - return TypesUtils.isNumeric(type); - } - - /** - * Compute the type to which two numeric types must be promoted before performing a binary - * numeric operation on them. The input types must both be numeric and the output type is - * primitive. - * - * @param left the type of the left operand - * @param right the type of the right operand - * @return a TypeMirror representing the binary numeric promoted type - */ - protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { - if (TypesUtils.isBoxedPrimitive(left)) { - left = types.unboxedType(left); - } - if (TypesUtils.isBoxedPrimitive(right)) { - right = types.unboxedType(right); - } - TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right); - return types.getPrimitiveType(promotedTypeKind); - } - - /** - * Perform binary numeric promotion on the input node to make it match the expression type. - * - * @param node a node producing a value of numeric primitive or boxed type - * @param exprType the type to promote the value to - * @return a Node with the value promoted to the exprType, which may be the input node - */ - protected Node binaryNumericPromotion(Node node, TypeMirror exprType) { - // For binary numeric promotion, see JLS 5.6.2 - node = unbox(node); - - if (!types.isSameType(node.getType(), exprType)) { - Node widened = new WideningConversionNode(node.getTree(), node, exprType); - addToConvertedLookupMap(widened); - insertNodeAfter(widened, node); - return widened; - } else { - return node; - } - } - - /** - * Perform widening primitive conversion on the input node to make it match the destination - * type. - * - * @param node a node producing a value of numeric primitive type - * @param destType the type to widen the value to - * @return a Node with the value widened to the exprType, which may be the input node - */ - protected Node widen(Node node, TypeMirror destType) { - // For widening conversion, see JLS 5.1.2 - assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) - : "widening must be applied to primitive types"; - if (types.isSubtype(node.getType(), destType) - && !types.isSameType(node.getType(), destType)) { - Node widened = new WideningConversionNode(node.getTree(), node, destType); - addToConvertedLookupMap(widened); - insertNodeAfter(widened, node); - return widened; - } else { - return node; - } - } - - /** - * Perform narrowing conversion on the input node to make it match the destination type. - * - * @param node a node producing a value of numeric primitive type - * @param destType the type to narrow the value to - * @return a Node with the value narrowed to the exprType, which may be the input node - */ - protected Node narrow(Node node, TypeMirror destType) { - // For narrowing conversion, see JLS 5.1.3 - assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) - : "narrowing must be applied to primitive types"; - if (types.isSubtype(destType, node.getType()) - && !types.isSameType(destType, node.getType())) { - Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType); - addToConvertedLookupMap(narrowed); - insertNodeAfter(narrowed, node); - return narrowed; - } else { - return node; - } - } - - /** - * Perform narrowing conversion and optionally boxing conversion on the input node to make it - * match the destination type. - * - * @param node a node producing a value of numeric primitive type - * @param destType the type to narrow the value to (possibly boxed) - * @return a Node with the value narrowed and boxed to the destType, which may be the input node - */ - protected Node narrowAndBox(Node node, TypeMirror destType) { - if (TypesUtils.isBoxedPrimitive(destType)) { - return box(narrow(node, types.unboxedType(destType))); - } else { - return narrow(node, destType); - } - } - - /** - * Return whether a conversion from the type of the node to varType requires narrowing. - * - * @param varType the type of a variable (or general LHS) to be converted to - * @param node a node whose value is being converted - * @return whether this conversion requires narrowing to succeed - */ - protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { - // Narrowing is restricted to cases where the left hand side is byte, char, short or Byte, - // Char, Short and the right hand side is a constant. - TypeMirror unboxedVarType = - TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType; - TypeKind unboxedVarKind = unboxedVarType.getKind(); - boolean isLeftNarrowableTo = - unboxedVarKind == TypeKind.BYTE - || unboxedVarKind == TypeKind.SHORT - || unboxedVarKind == TypeKind.CHAR; - boolean isRightConstant = node instanceof ValueLiteralNode; - return isLeftNarrowableTo && isRightConstant; - } - - /** - * Assignment conversion and method invocation conversion are almost identical, except that - * assignment conversion allows narrowing. We factor out the common logic here. - * - * @param node a Node producing a value - * @param varType the type of a variable - * @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not - * (for method invocation conversion) - * @return a Node with the value converted to the type of the variable, which may be the input - * node itself - */ - protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) { - // For assignment conversion, see JLS 5.2 - // For method invocation conversion, see JLS 5.3 - - // Check for identical types or "identity conversion" - TypeMirror nodeType = node.getType(); - boolean isSameType = types.isSameType(nodeType, varType); - if (isSameType) { - return node; - } - - boolean isRightNumeric = TypesUtils.isNumeric(nodeType); - boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); - boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); - boolean isRightReference = nodeType instanceof ReferenceType; - boolean isLeftNumeric = TypesUtils.isNumeric(varType); - boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); - // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType); - boolean isLeftReference = varType instanceof ReferenceType; - boolean isSubtype = types.isSubtype(nodeType, varType); - - if (isRightNumeric && isLeftNumeric && isSubtype) { - node = widen(node, varType); - } else if (isRightReference && isLeftReference && isSubtype) { - // widening reference conversion is a no-op, but if it - // applies, then later conversions do not. - } else if (isRightPrimitive && isLeftReference) { - if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { - node = narrowAndBox(node, varType); - } else { - node = box(node); - } - } else if (isRightBoxed && isLeftPrimitive) { - node = unbox(node); - nodeType = node.getType(); - - if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) { - node = widen(node, varType); - } - } else if (isRightPrimitive && isLeftPrimitive) { - if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { - node = narrow(node, varType); - } - } - // `node` might have been re-assigned; if `nodeType` is needed, set it again. - // nodeType = node.getType(); - - // TODO: if checkers need to know about null references of - // a particular type, add logic for them here. - - return node; - } - - /** - * Perform assignment conversion so that it can be assigned to a variable of the given type. - * - * @param node a Node producing a value - * @param varType the type of a variable - * @return a Node with the value converted to the type of the variable, which may be the input - * node itself - */ - protected Node assignConvert(Node node, TypeMirror varType) { - return commonConvert(node, varType, true); - } - - /** - * Perform method invocation conversion so that the node can be passed as a formal parameter of - * the given type. - * - * @param node a Node producing a value - * @param formalType the type of a formal parameter - * @return a Node with the value converted to the type of the formal, which may be the input - * node itself - */ - protected Node methodInvocationConvert(Node node, TypeMirror formalType) { - return commonConvert(node, formalType, false); - } + public @Nullable SwitchExpressionNode build() { + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(new Label()); + int numCases = caseTrees.size(); - /** - * Given a method element, its type at the call site, and a list of argument expressions, return - * a list of {@link Node}s representing the arguments converted for a call of the method. This - * method applies to both method invocations and constructor calls. The argument of newClassTree - * is null when we visit {@link MethodInvocationTree}, and is non-null when we visit {@link - * NewClassTree}. - * - * @param tree the invocation tree for the call - * @param executable an ExecutableElement representing a method/constructor to be called - * @param executableType an ExecutableType representing the type of the method/constructor call; - * the type must be viewpoint-adapted to the call - * @param actualExprs a List of argument expressions to a call - * @param newClassTree the NewClassTree if the method is the invocation of a constructor - * @return a List of {@link Node}s representing arguments after conversions required by a call - * to this method - */ - protected List convertCallArguments( - ExpressionTree tree, - ExecutableElement executable, - ExecutableType executableType, - List actualExprs, - @Nullable NewClassTree newClassTree) { - // NOTE: It is important to convert one method argument before generating CFG nodes for the - // next argument, since label binding expects nodes to be generated in execution order. - // Therefore, this method first determines which conversions need to be applied and then - // iterates over the actual arguments. - List formals = executableType.getParameterTypes(); - int numFormals = formals.size(); - - ArrayList convertedNodes = new ArrayList<>(numFormals); - AssertMethodTuple assertMethodTuple = getAssertMethodTuple(executable); - - int numActuals = actualExprs.size(); - if (executable.isVarArgs()) { - // Create a new array argument if the actuals outnumber the formals, or if the last - // actual is not assignable to the last formal. - int lastArgIndex = numFormals - 1; - TypeMirror lastParamType = formals.get(lastArgIndex); - if (numActuals == numFormals - && types.isAssignable( - TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) { - // Normal call with no array creation, apply method - // invocation conversion to all arguments. - for (int i = 0; i < numActuals; i++) { - Node actualVal = scan(actualExprs.get(i), null); - if (i == assertMethodTuple.booleanParam) { - treatMethodAsAssert( - (MethodInvocationTree) tree, assertMethodTuple, actualVal); - } - if (actualVal == null) { - throw new BugInCF( - "CFGBuilder: scan returned null for %s [%s]", - actualExprs.get(i), actualExprs.get(i).getClass()); - } - convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); - } - } else { - assert lastParamType instanceof ArrayType - : "variable argument formal must be an array"; - // Handle anonymous constructors with an explicit enclosing expression. - // There is a mismatch between the number of parameters and arguments - // when the following conditions are met: - // 1. Java version >= 11, - // 2. the method is an anonymous constructor, - // 3. the executable element has varargs, - // 4. the constructor is invoked with an explicit enclosing expression. - // In this case, the parameters have an enclosing expression as its first parameter, - // while the arguments do not have such element. Hence, decrease the lastArgIndex - // to organize the arguments from the correct index later. - if (SystemUtil.jreVersion >= 11 - && newClassTree != null - && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( - executable, newClassTree)) { - lastArgIndex--; - } - - // Apply method invocation conversion to lastArgIndex arguments and use the - // remaining ones to initialize an array. - for (int i = 0; i < lastArgIndex; i++) { - Node actualVal = scan(actualExprs.get(i), null); - if (i == assertMethodTuple.booleanParam) { - treatMethodAsAssert( - (MethodInvocationTree) tree, assertMethodTuple, actualVal); - } - convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); - } - - TypeMirror elemType = ((ArrayType) lastParamType).getComponentType(); - - List inits = new ArrayList<>(numActuals - lastArgIndex); - List initializers = new ArrayList<>(numActuals - lastArgIndex); - for (int i = lastArgIndex; i < numActuals; i++) { - inits.add(actualExprs.get(i)); - Node actualVal = scan(actualExprs.get(i), null); - initializers.add(assignConvert(actualVal, elemType)); - } - - NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits); - handleArtificialTree(wrappedVarargs); - - Node lastArgument = - new ArrayCreationNode( - wrappedVarargs, - lastParamType, - /* dimensions= */ Collections.emptyList(), - initializers); - extendWithNode(lastArgument); - - convertedNodes.add(lastArgument); - } - } else { - for (int i = 0; i < numActuals; i++) { - Node actualVal = scan(actualExprs.get(i), null); - if (i == assertMethodTuple.booleanParam) { - treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); - } - convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); - } - } - - return convertedNodes; - } - - /** - * Returns the AssertMethodTuple for {@code method}. If {@code method} is not an assert method, - * then {@link AssertMethodTuple#NONE} is returned. - * - * @param method a method element that might be an assert method - * @return the AssertMethodTuple for {@code method} - */ - protected AssertMethodTuple getAssertMethodTuple(ExecutableElement method) { - AnnotationMirror assertMethodAnno = - annotationProvider.getDeclAnnotation(method, AssertMethod.class); - if (assertMethodAnno == null) { - return AssertMethodTuple.NONE; - } - - // Dataflow does not require checker-qual.jar to be on the users classpath, so - // AnnotationUtils.getElementValue(...) cannot be used. - - int booleanParam = - AnnotationUtils.getElementValueNotOnClasspath( - assertMethodAnno, "parameter", Integer.class, 1) - - 1; - - TypeMirror exceptionType = - AnnotationUtils.getElementValueNotOnClasspath( - assertMethodAnno, - "value", - Type.ClassType.class, - (Type.ClassType) assertionErrorType); - boolean isAssertFalse = - AnnotationUtils.getElementValueNotOnClasspath( - assertMethodAnno, "isAssertFalse", Boolean.class, false); - return new AssertMethodTuple(booleanParam, exceptionType, isAssertFalse); - } - - /** Holds the elements of an {@link AssertMethod} annotation. */ - protected static class AssertMethodTuple { - - /** A tuple representing the lack of an {@link AssertMethodTuple}. */ - protected static final AssertMethodTuple NONE = new AssertMethodTuple(-1, null, false); - - /** - * 0-based index of the parameter of the expression that is tested by the assert method. (Or - * -1 if this isn't an assert method.) - */ - public final int booleanParam; - - /** The type of the exception thrown by the assert method. */ - public final TypeMirror exceptionType; - - /** Is this an assert false method? */ - public final boolean isAssertFalse; - - /** - * Creates an AssertMethodTuple. - * - * @param booleanParam 0-based index of the parameter of the expression that is tested by - * the assert method - * @param exceptionType the type of the exception thrown by the assert method - * @param isAssertFalse is this an assert false method - */ - public AssertMethodTuple( - int booleanParam, TypeMirror exceptionType, boolean isAssertFalse) { - this.booleanParam = booleanParam; - this.exceptionType = exceptionType; - this.isAssertFalse = isAssertFalse; - } - } - - /** - * Convert an operand of a conditional expression to the type of the whole expression. - * - * @param node a node occurring as the second or third operand of a conditional expression - * @param destType the type to promote the value to - * @return a Node with the value promoted to the destType, which may be the input node - */ - protected Node conditionalExprPromotion(Node node, TypeMirror destType) { - // For rules on converting operands of conditional expressions, - // JLS 15.25 - TypeMirror nodeType = node.getType(); - - // If the operand is already the same type as the whole - // expression, then do nothing. - if (types.isSameType(nodeType, destType)) { - return node; - } - - // If the operand is a primitive and the whole expression is - // boxed, then apply boxing. - if (TypesUtils.isPrimitive(nodeType) && TypesUtils.isBoxedPrimitive(destType)) { - return box(node); - } - - // If the operand is byte or Byte and the whole expression is - // short, then convert to short. - boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType); - TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType; - TypeMirror unboxedDestType = - TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType; - if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) { - if (unboxedNodeType.getKind() == TypeKind.BYTE - && destType.getKind() == TypeKind.SHORT) { - if (isBoxedPrimitive) { - node = unbox(node); - } - return widen(node, destType); - } - - // If the operand is Byte, Short or Character and the whole expression - // is the unboxed version of it, then apply unboxing. - TypeKind destKind = destType.getKind(); - if (destKind == TypeKind.BYTE - || destKind == TypeKind.CHAR - || destKind == TypeKind.SHORT) { - if (isBoxedPrimitive) { - return unbox(node); - } else if (nodeType.getKind() == TypeKind.INT) { - return narrow(node, destType); - } - } - - return binaryNumericPromotion(node, destType); - } - - // For the final case in JLS 15.25, apply boxing but not lub. - if (TypesUtils.isPrimitive(nodeType) - && (destType.getKind() == TypeKind.DECLARED - || destType.getKind() == TypeKind.UNION - || destType.getKind() == TypeKind.INTERSECTION)) { - return box(node); - } - - return node; - } - - /** - * Returns the label {@link Name} of the leaf in the argument path, or null if the leaf is not a - * labeled statement. - */ - protected @Nullable Name getLabel(TreePath path) { - if (path.getParentPath() != null) { - Tree parent = path.getParentPath().getLeaf(); - if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) { - return ((LabeledStatementTree) parent).getLabel(); - } - } - return null; - } - - /* --------------------------------------------------------- */ - /* Visitor Methods */ - /* --------------------------------------------------------- */ - - @Override - public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) { - return scan(tree.getUnderlyingType(), p); - } - - @Override - public Node visitAnnotation(AnnotationTree tree, Void p) { - throw new BugInCF("AnnotationTree is unexpected in AST to CFG translation"); - } - - @Override - public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { - - // see JLS 15.12.4 - - // First, compute the receiver, if any (15.12.4.1). - // Second, evaluate the actual arguments, left to right and possibly some arguments are - // stored into an array for varargs calls (15.12.4.2). - // Third, test the receiver, if any, for nullness (15.12.4.4). - // Fourth, convert the arguments to the type of the formal parameters (15.12.4.5). - // Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5). - ExecutableElement method = TreeUtils.elementFromUse(tree); - if (method == null) { - // The method wasn't found, e.g. because of a compilation error. - return null; - } - - ExpressionTree methodSelect = tree.getMethodSelect(); - assert TreeUtils.isMethodAccess(methodSelect) - : "Expected a method access, but got: " + methodSelect; - - List actualExprs = tree.getArguments(); - - // Look up method to invoke and possibly throw NullPointerException - Node receiver = getReceiver(methodSelect); - - MethodAccessNode target = new MethodAccessNode(methodSelect, method, receiver); - - if (ElementUtils.isStatic(method) || receiver instanceof ThisNode) { - // No NullPointerException can be thrown, use normal node - extendWithNode(target); - } else { - extendWithNodeWithException(target, nullPointerExceptionType); - } - - List arguments; - if (TreeUtils.isEnumSuperCall(tree)) { - // Don't convert arguments for enum super calls. The AST contains no actual arguments, - // while the method element expects two arguments, leading to an exception in - // convertCallArguments. - // Since no actual arguments are present in the AST that is being checked, it shouldn't - // cause any harm to omit the conversions. - // See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate. - arguments = Collections.emptyList(); - } else { - arguments = - convertCallArguments( - tree, method, TreeUtils.typeFromUse(tree), actualExprs, null); - } - - // TODO: lock the receiver for synchronized methods - - MethodInvocationNode node = - new MethodInvocationNode(tree, target, arguments, getCurrentPath()); - - ExtendedNode extendedNode = extendWithMethodInvocationNode(method, node); - - /* Check for the TerminatesExecution annotation. */ - boolean terminatesExecution = - annotationProvider.getDeclAnnotation(method, TerminatesExecution.class) != null; - if (terminatesExecution) { - extendedNode.setTerminatesExecution(true); - } - return node; - } - - /** - * Extends the CFG with a MethodInvocationNode, accounting for potential exceptions thrown by - * the invocation. - * - * @param method the invoked method - * @param node the invocation - * @return an ExtendedNode representing the invocation and its possible thrown exceptions - */ - private ExtendedNode extendWithMethodInvocationNode( - ExecutableElement method, MethodInvocationNode node) { - List thrownTypes = method.getThrownTypes(); - Set thrownSet = - new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size()); - // Add exceptions explicitly mentioned in the throws clause. - thrownSet.addAll(thrownTypes); - // Add types to account for unchecked exceptions - thrownSet.addAll(uncheckedExceptionTypes); - - return extendWithNodeWithExceptions(node, thrownSet); - } - - @Override - public Node visitAssert(AssertTree tree, Void p) { - - // see JLS 14.10 - - // If assertions are enabled, then we can just translate the assertion. - if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) { - translateAssertWithAssertionsEnabled(tree); - return null; - } - - // If assertions are disabled, then nothing is executed. - if (assumeAssertionsDisabled) { - return null; - } - - // Otherwise, we don't know if assertions are enabled, so we use a - // variable "ea" and case-split on it. One branch does execute the - // assertion, while the other assumes assertions are disabled. - VariableTree ea = getAssertionsEnabledVariable(); - - // all necessary labels - Label assertionEnabled = new Label(); - Label assertionDisabled = new Label(); - - extendWithNode(new LocalVariableNode(ea)); - extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled)); - - // 'then' branch (i.e. check the assertion) - addLabelForNextNode(assertionEnabled); - - translateAssertWithAssertionsEnabled(tree); - - // 'else' branch - addLabelForNextNode(assertionDisabled); - - return null; - } - - /** - * Should assertions be assumed to be executed for a given {@link AssertTree}? False by default. - */ - protected boolean assumeAssertionsEnabledFor(AssertTree tree) { - return false; - } - - /** The {@link VariableTree} that indicates whether assertions are enabled or not. */ - protected VariableTree ea = null; - - /** - * Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not. - */ - protected VariableTree getAssertionsEnabledVariable() { - if (ea == null) { - String name = uniqueName("assertionsEnabled"); - Element owner = findOwner(); - ExpressionTree initializer = null; - ea = - treeBuilder.buildVariableDecl( - types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer); - handleArtificialTree(ea); - } - return ea; - } - - /** - * Find nearest owner element (Method or Class) which holds current tree. - * - * @return nearest owner element of current tree - */ - private Element findOwner() { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - if (enclosingMethod != null) { - return TreeUtils.elementFromDeclaration(enclosingMethod); - } else { - ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); - return TreeUtils.elementFromDeclaration(enclosingClass); - } - } - - /** - * Translates an assertion statement to the correct CFG nodes. The translation assumes that - * assertions are enabled. - */ - protected void translateAssertWithAssertionsEnabled(AssertTree tree) { - - // all necessary labels - Label assertEnd = new Label(); - Label elseEntry = new Label(); - - // basic block for the condition - Node condition = unbox(scan(tree.getCondition(), null)); - ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry); - extendWithExtendedNode(cjump); - - // else branch - Node detail = null; - addLabelForNextNode(elseEntry); - if (tree.getDetail() != null) { - detail = scan(tree.getDetail(), null); - } - AssertionErrorNode assertNode = - new AssertionErrorNode(tree, condition, detail, assertionErrorType); - extendWithNode(assertNode); - NodeWithExceptionsHolder exNode = - extendWithNodeWithException( - new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType); - exNode.setTerminatesExecution(true); - - // then branch (nothing happens) - addLabelForNextNode(assertEnd); - } - - /** - * Translates a method marked as {@link AssertMethod} into CFG nodes corresponding to an {@code - * assert} statement. - * - * @param tree the method invocation tree for a method marked as {@link AssertMethod} - * @param assertMethodTuple the assert method tuple for the method - * @param condition the boolean expression node for the argument that the method tests - */ - protected void treatMethodAsAssert( - MethodInvocationTree tree, AssertMethodTuple assertMethodTuple, Node condition) { - // all necessary labels - Label thenLabel = new Label(); - Label elseLabel = new Label(); - ConditionalJump cjump = new ConditionalJump(thenLabel, elseLabel); - extendWithExtendedNode(cjump); - - addLabelForNextNode(assertMethodTuple.isAssertFalse ? thenLabel : elseLabel); - AssertionErrorNode assertNode = - new AssertionErrorNode(tree, condition, null, assertMethodTuple.exceptionType); - extendWithNode(assertNode); - NodeWithExceptionsHolder exNode = - extendWithNodeWithException( - new ThrowNode(null, assertNode, env.getTypeUtils()), - assertMethodTuple.exceptionType); - exNode.setTerminatesExecution(true); - - addLabelForNextNode(assertMethodTuple.isAssertFalse ? elseLabel : thenLabel); - } - - @Override - public Node visitAssignment(AssignmentTree tree, Void p) { - - // see JLS 15.26.1 - - AssignmentNode assignmentNode; - ExpressionTree variable = tree.getVariable(); - TypeMirror varType = TreeUtils.typeOf(variable); - - // case 1: lhs is field access - if (TreeUtils.isFieldAccess(variable)) { - // visit receiver - Node receiver = getReceiver(variable); - - // visit expression - Node expression = scan(tree.getExpression(), p); - expression = assignConvert(expression, varType); - - // visit field access (throws null-pointer exception) - FieldAccessNode target = new FieldAccessNode(variable, receiver); - target.setLValue(); - - Element element = TreeUtils.elementFromUse(variable); - if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) { - // No NullPointerException can be thrown, use normal node - extendWithNode(target); - } else { - extendWithNodeWithException(target, nullPointerExceptionType); - } - - // add assignment node - assignmentNode = new AssignmentNode(tree, target, expression); - extendWithNode(assignmentNode); - } - - // case 2: lhs is not a field access - else { - Node target = scan(variable, p); - target.setLValue(); - - assignmentNode = translateAssignment(tree, target, tree.getExpression()); - } - - return assignmentNode; - } - - /** Translate an assignment. */ - protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) { - Node expression = scan(rhs, null); - return translateAssignment(tree, target, expression); - } - - /** Translate an assignment where the RHS has already been scanned. */ - protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) { - assert tree instanceof AssignmentTree || tree instanceof VariableTree; - target.setLValue(); - expression = assignConvert(expression, target.getType()); - AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression); - extendWithNode(assignmentNode); - return assignmentNode; - } - - /** - * Note 1: Requires {@code tree} to be a field or method access tree. - * - *

Note 2: Visits the receiver and adds all necessary blocks to the CFG. - * - * @param tree the field or method access tree containing the receiver: one of - * MethodInvocationTree, AssignmentTree, or IdentifierTree - * @return the receiver of the field or method access - */ - private Node getReceiver(ExpressionTree tree) { - assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree); - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - // `tree` has an explicit receiver. - MemberSelectTree mtree = (MemberSelectTree) tree; - return scan(mtree.getExpression(), null); - } - - // Access through an implicit receiver - Element ele = TreeUtils.elementFromUse(tree); - TypeElement declClassElem = ElementUtils.enclosingTypeElement(ele); - TypeMirror declClassType = ElementUtils.getType(declClassElem); - - if (ElementUtils.isStatic(ele)) { - ClassNameNode node = new ClassNameNode(declClassType, declClassElem); - extendWithClassNameNode(node); - return node; - } - - // Access through an implicit `this` - TreePath enclClassPath = TreePathUtil.pathTillClass(getCurrentPath()); - ClassTree enclClassTree = (ClassTree) enclClassPath.getLeaf(); - TypeElement enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); - TypeMirror enclClassType = enclClassElem.asType(); - while (!TypesUtils.isErasedSubtype(enclClassType, declClassType, types)) { - enclClassPath = TreePathUtil.pathTillClass(enclClassPath.getParentPath()); - if (enclClassPath == null) { - enclClassType = declClassType; - break; - } - enclClassTree = (ClassTree) enclClassPath.getLeaf(); - enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); - enclClassType = enclClassElem.asType(); - } - Node node = new ImplicitThisNode(enclClassType); - extendWithNode(node); - return node; - } - - /** - * Map an operation with assignment to the corresponding operation without assignment. - * - * @param kind a Tree.Kind representing an operation with assignment - * @return the Tree.Kind for the same operation without assignment - */ - protected Tree.Kind withoutAssignment(Tree.Kind kind) { - switch (kind) { - case DIVIDE_ASSIGNMENT: - return Tree.Kind.DIVIDE; - case MULTIPLY_ASSIGNMENT: - return Tree.Kind.MULTIPLY; - case REMAINDER_ASSIGNMENT: - return Tree.Kind.REMAINDER; - case MINUS_ASSIGNMENT: - return Tree.Kind.MINUS; - case PLUS_ASSIGNMENT: - return Tree.Kind.PLUS; - case LEFT_SHIFT_ASSIGNMENT: - return Tree.Kind.LEFT_SHIFT; - case RIGHT_SHIFT_ASSIGNMENT: - return Tree.Kind.RIGHT_SHIFT; - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - return Tree.Kind.UNSIGNED_RIGHT_SHIFT; - case AND_ASSIGNMENT: - return Tree.Kind.AND; - case OR_ASSIGNMENT: - return Tree.Kind.OR; - case XOR_ASSIGNMENT: - return Tree.Kind.XOR; - default: - return Tree.Kind.ERRONEOUS; - } - } - - @Override - public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - // According the JLS 15.26.2, E1 op= E2 is equivalent to - // E1 = (T) ((E1) op (E2)), where T is the type of E1, - // except that E1 is evaluated only once. - // - - Tree.Kind kind = tree.getKind(); - switch (kind) { - case DIVIDE_ASSIGNMENT: - case MULTIPLY_ASSIGNMENT: - case REMAINDER_ASSIGNMENT: - { - // see JLS 15.17 and 15.26.2 - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror exprType = TreeUtils.typeOf(tree); - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); - value = binaryNumericPromotion(value, promotedType); - - BinaryTree operTree = - treeBuilder.buildBinary( - promotedType, - withoutAssignment(kind), - tree.getVariable(), - tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) { - operNode = new NumericalMultiplicationNode(operTree, targetRHS, value); - } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) { - if (TypesUtils.isIntegralPrimitive(exprType)) { - operNode = new IntegerDivisionNode(operTree, targetRHS, value); - - extendWithNodeWithException(operNode, arithmeticExceptionType); - } else { - operNode = new FloatingDivisionNode(operTree, targetRHS, value); - } - } else { - assert kind == Tree.Kind.REMAINDER_ASSIGNMENT; - if (TypesUtils.isIntegralPrimitive(exprType)) { - operNode = new IntegerRemainderNode(operTree, targetRHS, value); - - extendWithNodeWithException(operNode, arithmeticExceptionType); - } else { - operNode = new FloatingRemainderNode(operTree, targetRHS, value); - } - } - extendWithNode(operNode); - - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); - - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; - } - - case MINUS_ASSIGNMENT: - case PLUS_ASSIGNMENT: - { - // see JLS 15.18 and 15.26.2 - - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); - - if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { - assert (kind == Tree.Kind.PLUS_ASSIGNMENT); - Node targetRHS = stringConversion(targetLHS); - value = stringConversion(value); - BinaryTree operTree = - treeBuilder.buildBinary( - leftType, - withoutAssignment(kind), - tree.getVariable(), - tree.getExpression()); - handleArtificialTree(operTree); - Node operNode = new StringConcatenateNode(operTree, targetRHS, value); - extendWithNode(operNode); - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, operNode); - extendWithNode(assignNode); - return assignNode; - } else { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); - value = binaryNumericPromotion(value, promotedType); - - BinaryTree operTree = - treeBuilder.buildBinary( - promotedType, - withoutAssignment(kind), - tree.getVariable(), - tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.PLUS_ASSIGNMENT) { - operNode = new NumericalAdditionNode(operTree, targetRHS, value); - } else { - assert kind == Tree.Kind.MINUS_ASSIGNMENT; - operNode = new NumericalSubtractionNode(operTree, targetRHS, value); - } - extendWithNode(operNode); - - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = - new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); - - // Map the compound assignment tree to an assignment node, which - // will have the correct type. - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; - } - } - - case LEFT_SHIFT_ASSIGNMENT: - case RIGHT_SHIFT_ASSIGNMENT: - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - { - // see JLS 15.19 and 15.26.2 - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - - Node targetRHS = unaryNumericPromotion(targetLHS); - value = unaryNumericPromotion(value); - - BinaryTree operTree = - treeBuilder.buildBinary( - leftType, - withoutAssignment(kind), - tree.getVariable(), - tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) { - operNode = new LeftShiftNode(operTree, targetRHS, value); - } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) { - operNode = new SignedRightShiftNode(operTree, targetRHS, value); - } else { - assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; - operNode = new UnsignedRightShiftNode(operTree, targetRHS, value); - } - extendWithNode(operNode); - - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); - - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; - } - - case AND_ASSIGNMENT: - case OR_ASSIGNMENT: - case XOR_ASSIGNMENT: - // see JLS 15.22 - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); - - Node targetRHS = null; - if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - targetRHS = binaryNumericPromotion(targetLHS, promotedType); - value = binaryNumericPromotion(value, promotedType); - } else if (TypesUtils.isBooleanType(leftType) - && TypesUtils.isBooleanType(rightType)) { - targetRHS = unbox(targetLHS); - value = unbox(value); - } else { - throw new BugInCF( - "Both arguments to logical operation must be numeric or boolean"); - } - - BinaryTree operTree = - treeBuilder.buildBinary( - leftType, - withoutAssignment(kind), - tree.getVariable(), - tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.AND_ASSIGNMENT) { - operNode = new BitwiseAndNode(operTree, targetRHS, value); - } else if (kind == Tree.Kind.OR_ASSIGNMENT) { - operNode = new BitwiseOrNode(operTree, targetRHS, value); - } else { - assert kind == Tree.Kind.XOR_ASSIGNMENT; - operNode = new BitwiseXorNode(operTree, targetRHS, value); - } - extendWithNode(operNode); - - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); - - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; - default: - throw new BugInCF("unexpected compound assignment type"); - } - } - - @Override - public Node visitBinary(BinaryTree tree, Void p) { - // Note that for binary operations it is important to perform any required promotion on the - // left operand before generating any Nodes for the right operand, because labels must be - // inserted AFTER ALL preceding Nodes and BEFORE ALL following Nodes. - Node r = null; - Tree leftTree = tree.getLeftOperand(); - Tree rightTree = tree.getRightOperand(); - - Tree.Kind kind = tree.getKind(); - switch (kind) { - case DIVIDE: - case MULTIPLY: - case REMAINDER: - { - // see JLS 15.17 - - TypeMirror exprType = TreeUtils.typeOf(tree); - TypeMirror leftType = TreeUtils.typeOf(leftTree); - TypeMirror rightType = TreeUtils.typeOf(rightTree); - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - - Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); - Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); - - if (kind == Tree.Kind.MULTIPLY) { - r = new NumericalMultiplicationNode(tree, left, right); - } else if (kind == Tree.Kind.DIVIDE) { - if (TypesUtils.isIntegralPrimitive(exprType)) { - r = new IntegerDivisionNode(tree, left, right); - - extendWithNodeWithException(r, arithmeticExceptionType); - } else { - r = new FloatingDivisionNode(tree, left, right); - } - } else { - assert kind == Tree.Kind.REMAINDER; - if (TypesUtils.isIntegralPrimitive(exprType)) { - r = new IntegerRemainderNode(tree, left, right); - - extendWithNodeWithException(r, arithmeticExceptionType); - } else { - r = new FloatingRemainderNode(tree, left, right); - } - } - break; - } - - case MINUS: - case PLUS: - { - // see JLS 15.18 - - // TypeMirror exprType = InternalUtils.typeOf(tree); - TypeMirror leftType = TreeUtils.typeOf(leftTree); - TypeMirror rightType = TreeUtils.typeOf(rightTree); - - if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { - assert (kind == Tree.Kind.PLUS); - Node left = stringConversion(scan(leftTree, p)); - Node right = stringConversion(scan(rightTree, p)); - r = new StringConcatenateNode(tree, left, right); - } else { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); - Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); - - // TODO: Decide whether to deal with floating-point value - // set conversion. - if (kind == Tree.Kind.PLUS) { - r = new NumericalAdditionNode(tree, left, right); - } else { - assert kind == Tree.Kind.MINUS; - r = new NumericalSubtractionNode(tree, left, right); - } - } - break; - } - - case LEFT_SHIFT: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - { - // see JLS 15.19 - - Node left = unaryNumericPromotion(scan(leftTree, p)); - Node right = unaryNumericPromotion(scan(rightTree, p)); - - if (kind == Tree.Kind.LEFT_SHIFT) { - r = new LeftShiftNode(tree, left, right); - } else if (kind == Tree.Kind.RIGHT_SHIFT) { - r = new SignedRightShiftNode(tree, left, right); - } else { - assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT; - r = new UnsignedRightShiftNode(tree, left, right); - } - break; - } - - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case LESS_THAN: - case LESS_THAN_EQUAL: - { - // see JLS 15.20.1 - TypeMirror leftType = TreeUtils.typeOf(leftTree); - if (TypesUtils.isBoxedPrimitive(leftType)) { - leftType = types.unboxedType(leftType); - } - - TypeMirror rightType = TreeUtils.typeOf(rightTree); - if (TypesUtils.isBoxedPrimitive(rightType)) { - rightType = types.unboxedType(rightType); - } - - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); - Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); - - Node node; - if (kind == Tree.Kind.GREATER_THAN) { - node = new GreaterThanNode(tree, left, right); - } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) { - node = new GreaterThanOrEqualNode(tree, left, right); - } else if (kind == Tree.Kind.LESS_THAN) { - node = new LessThanNode(tree, left, right); - } else { - assert kind == Tree.Kind.LESS_THAN_EQUAL; - node = new LessThanOrEqualNode(tree, left, right); - } - - extendWithNode(node); - - return node; - } - - case EQUAL_TO: - case NOT_EQUAL_TO: - { - // see JLS 15.21 - TreeInfo leftInfo = getTreeInfo(leftTree); - TreeInfo rightInfo = getTreeInfo(rightTree); - Node left = scan(leftTree, p); - Node right = scan(rightTree, p); - - if (leftInfo.isNumeric() - && rightInfo.isNumeric() - && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { - // JLS 15.21.1 numerical equality - TypeMirror promotedType = - binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType()); - left = binaryNumericPromotion(left, promotedType); - right = binaryNumericPromotion(right, promotedType); - } else if (leftInfo.isBoolean() - && rightInfo.isBoolean() - && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { - // JSL 15.21.2 boolean equality - left = unboxAsNeeded(left, leftInfo.isBoxed()); - right = unboxAsNeeded(right, rightInfo.isBoxed()); - } - - Node node; - if (kind == Tree.Kind.EQUAL_TO) { - node = new EqualToNode(tree, left, right); - } else { - assert kind == Tree.Kind.NOT_EQUAL_TO; - node = new NotEqualNode(tree, left, right); - } - extendWithNode(node); - - return node; - } - - case AND: - case OR: - case XOR: - { - // see JLS 15.22 - TypeMirror leftType = TreeUtils.typeOf(leftTree); - TypeMirror rightType = TreeUtils.typeOf(rightTree); - boolean isBooleanOp = - TypesUtils.isBooleanType(leftType) - && TypesUtils.isBooleanType(rightType); - - Node left; - Node right; - - if (isBooleanOp) { - left = unbox(scan(leftTree, p)); - right = unbox(scan(rightTree, p)); - } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - left = binaryNumericPromotion(scan(leftTree, p), promotedType); - right = binaryNumericPromotion(scan(rightTree, p), promotedType); - } else { - left = unbox(scan(leftTree, p)); - right = unbox(scan(rightTree, p)); - } - - Node node; - if (kind == Tree.Kind.AND) { - node = new BitwiseAndNode(tree, left, right); - } else if (kind == Tree.Kind.OR) { - node = new BitwiseOrNode(tree, left, right); - } else { - assert kind == Tree.Kind.XOR; - node = new BitwiseXorNode(tree, left, right); - } - - extendWithNode(node); - - return node; - } - - case CONDITIONAL_AND: - case CONDITIONAL_OR: - { - // see JLS 15.23 and 15.24 - - // all necessary labels - Label rightStartLabel = new Label(); - Label shortCircuitLabel = new Label(); - - // left-hand side - Node left = scan(leftTree, p); - - ConditionalJump cjump; - if (kind == Tree.Kind.CONDITIONAL_AND) { - cjump = new ConditionalJump(rightStartLabel, shortCircuitLabel); - cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE); - } else { - cjump = new ConditionalJump(shortCircuitLabel, rightStartLabel); - cjump.setTrueFlowRule(FlowRule.THEN_TO_THEN); - } - extendWithExtendedNode(cjump); - - // right-hand side - addLabelForNextNode(rightStartLabel); - Node right = scan(rightTree, p); - - // conditional expression itself - addLabelForNextNode(shortCircuitLabel); - Node node; - if (kind == Tree.Kind.CONDITIONAL_AND) { - node = new ConditionalAndNode(tree, left, right); - } else { - node = new ConditionalOrNode(tree, left, right); - } - extendWithNode(node); - return node; - } - default: - throw new BugInCF("unexpected binary tree: " + kind); - } - assert r != null : "unexpected binary tree"; - extendWithNode(r); - return r; - } - - @Override - public Node visitBlock(BlockTree tree, Void p) { - for (StatementTree n : tree.getStatements()) { - scan(n, null); - } - return null; - } - - @Override - public Node visitBreak(BreakTree tree, Void p) { - Name label = tree.getLabel(); - if (label == null) { - assert breakTargetLC != null : "no target for break statement"; - - extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); - } else { - assert breakLabels.containsKey(label); - - extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label))); - } - - return null; - } - - // This visits a switch statement. - // Switch expressions are visited by visitSwitchExpression17. - @Override - public Node visitSwitch(SwitchTree tree, Void p) { - SwitchBuilder builder = new SwitchBuilder(tree); - builder.build(); - return null; - } - - /** - * Helper class for handling switch statements and switch expressions, including all their - * substatements such as case labels. - */ - private class SwitchBuilder { - - /** - * The tree for the switch statement or switch expression. Its type may be {@link - * SwitchTree} (for a switch statement) or {@code SwitchExpressionTree}. - */ - private final Tree switchTree; - - /** The case trees of {@code switchTree} */ - private final List caseTrees; - - /** - * The Tree for the selector expression. - * - *

-         *   switch ( selector expression ) { ... }
-         * 
- */ - private final ExpressionTree selectorExprTree; - - /** The labels for the case bodies. */ - private final Label[] caseBodyLabels; - - /** - * The Node for the assignment of the switch selector expression to a synthetic local - * variable. - */ - private AssignmentNode selectorExprAssignment; - - /** - * If {@link #switchTree} is a switch expression, then this is a result variable: the - * synthetic variable that all results of {@code #switchTree} are assigned to. Otherwise, - * this is null. - */ - private @Nullable VariableTree switchExprVarTree; - - /** - * Construct a SwitchBuilder. - * - * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} - */ - private SwitchBuilder(Tree switchTree) { - this.switchTree = switchTree; - if (TreeUtils.isSwitchStatement(switchTree)) { - SwitchTree switchStatementTree = (SwitchTree) switchTree; - this.caseTrees = switchStatementTree.getCases(); - this.selectorExprTree = switchStatementTree.getExpression(); - } else { - this.caseTrees = SwitchExpressionUtils.getCases(switchTree); - this.selectorExprTree = SwitchExpressionUtils.getExpression(switchTree); - } - // "+ 1" for the default case. If the switch has an explicit default case, then - // the last element of the array is never used. - this.caseBodyLabels = new Label[caseTrees.size() + 1]; - } - - /** - * Build up the CFG for the switchTree. - * - * @return if the switch is a switch expression, then a {@link SwitchExpressionNode}; - * otherwise, null - */ - public @Nullable SwitchExpressionNode build() { - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(new Label()); - int numCases = caseTrees.size(); - - for (int i = 0; i < numCases; ++i) { - caseBodyLabels[i] = new Label(); - } - caseBodyLabels[numCases] = breakTargetLC.peekLabel(); - - buildSelector(); - - buildSwitchExpressionVar(); - - if (TreeUtils.isSwitchStatement(switchTree)) { - // It's a switch statement, not a switch expression. - extendWithNode( - new MarkerNode( - switchTree, - "start of switch statement #" + TreeUtils.treeUids.get(switchTree), - env.getTypeUtils())); - } - - // JSL 14.11.2 - // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 - // states "For compatibility reasons, switch statements that are not enhanced switch - // statements are not required to be exhaustive". - // Switch expressions and enhanced switch statements are exhaustive. - boolean switchExprOrEnhanced = - !TreeUtils.isSwitchStatement(switchTree) - || TreeUtils.isEnhancedSwitchStatement((SwitchTree) switchTree); - // Build CFG for the cases. - int defaultIndex = -1; - for (int i = 0; i < numCases; ++i) { - CaseTree caseTree = caseTrees.get(i); - if (CaseUtils.isDefaultCaseTree(caseTree)) { - // Per the Java Language Specification, the checks of all cases must happen - // before the default case, no matter where `default:` is written. Therefore, - // build the default case last. - defaultIndex = i; - } else if (i == numCases - 1 && defaultIndex == -1) { - // This is the last case, and there is no default case. - // Switch expressions and enhanced switch statements are exhaustive. - buildCase(caseTree, i, switchExprOrEnhanced); - } else { - buildCase(caseTree, i, false); - } - } - - if (defaultIndex != -1) { - // The checks of all cases must happen before the default case, therefore we build - // the default case last. - // Fallthrough is still handled correctly with the caseBodyLabels. - buildCase(caseTrees.get(defaultIndex), defaultIndex, false); - } - - addLabelForNextNode(breakTargetLC.peekLabel()); - breakTargetLC = oldBreakTargetLC; - if (TreeUtils.isSwitchStatement(switchTree)) { - // It's a switch statement, not a switch expression. - extendWithNode( - new MarkerNode( - switchTree, - "end of switch statement #" + TreeUtils.treeUids.get(switchTree), - env.getTypeUtils())); - } - - if (!TreeUtils.isSwitchStatement(switchTree)) { - // It's a switch expression, not a switch statement. - IdentifierTree switchExprVarUseTree = - treeBuilder.buildVariableUse(switchExprVarTree); - handleArtificialTree(switchExprVarUseTree); - - LocalVariableNode switchExprVarUseNode = - new LocalVariableNode(switchExprVarUseTree); - switchExprVarUseNode.setInSource(false); - extendWithNode(switchExprVarUseNode); - SwitchExpressionNode switchExpressionNode = - new SwitchExpressionNode( - TreeUtils.typeOf(switchTree), switchTree, switchExprVarUseNode); - extendWithNode(switchExpressionNode); - return switchExpressionNode; - } else { - return null; - } - } - - /** - * Builds the CFG for the selector expression. It also creates a synthetic variable and - * assigns the selector expression to the variable. This assignment node is stored in {@link - * #selectorExprAssignment}. It can later be used to refine the selector expression in case - * bodies. - */ - private void buildSelector() { - // Create a synthetic variable to which the switch selector expression will be assigned - TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree); - VariableTree selectorVarTree = - treeBuilder.buildVariableDecl( - selectorExprType, uniqueName("switch"), findOwner(), null); - handleArtificialTree(selectorVarTree); - - VariableDeclarationNode selectorVarNode = new VariableDeclarationNode(selectorVarTree); - selectorVarNode.setInSource(false); - extendWithNode(selectorVarNode); - - IdentifierTree selectorVarUseTree = treeBuilder.buildVariableUse(selectorVarTree); - handleArtificialTree(selectorVarUseTree); - - LocalVariableNode selectorVarUseNode = new LocalVariableNode(selectorVarUseTree); - selectorVarUseNode.setInSource(false); - extendWithNode(selectorVarUseNode); - - Node selectorExprNode = unbox(scan(selectorExprTree, null)); - - AssignmentTree assign = - treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree); - handleArtificialTree(assign); - - selectorExprAssignment = - new AssignmentNode(assign, selectorVarUseNode, selectorExprNode); - selectorExprAssignment.setInSource(false); - extendWithNode(selectorExprAssignment); - } - - /** - * If {@link #switchTree} is a switch expression tree, this method creates a synthetic - * variable whose value is the value of the switch expression. - */ - private void buildSwitchExpressionVar() { - if (TreeUtils.isSwitchStatement(switchTree)) { - // A switch statement does not have a value, so do nothing. - return; - } - TypeMirror switchExprType = TreeUtils.typeOf(switchTree); - switchExprVarTree = - treeBuilder.buildVariableDecl( - switchExprType, uniqueName("switchExpr"), findOwner(), null); - handleArtificialTree(switchExprVarTree); - - VariableDeclarationNode switchExprVarNode = - new VariableDeclarationNode(switchExprVarTree); - switchExprVarNode.setInSource(false); - extendWithNode(switchExprVarNode); - } - - /** - * Build the CFG for the given case tree. - * - * @param caseTree a case tree whose CFG to build - * @param index the index of the case tree in {@link #caseBodyLabels} - * @param isLastCaseOfExhaustive true if this is the last case of an exhaustive switch - * statement, with no fallthrough to it. In other words, no test of the labels is - * necessary. - */ - private void buildCase(CaseTree caseTree, int index, boolean isLastCaseOfExhaustive) { - boolean isDefaultCase = CaseUtils.isDefaultCaseTree(caseTree); - // If true, no test of labels is necessary. - // Unfortunately, if isLastCaseOfExhaustive==TRUE, no flow-sensitive refinement occurs - // within the body of the CaseNode. In the future, that can be performed, but it - // requires addition of InfeasibleExitBlock, a new SpecialBlock in the CFG. - boolean isTerminalCase = isDefaultCase || isLastCaseOfExhaustive; - - Label thisBodyLabel = caseBodyLabels[index]; - Label nextBodyLabel = caseBodyLabels[index + 1]; - // `nextCaseLabel` is not used if isTerminalCase==FALSE. - Label nextCaseLabel = new Label(); - - // Handle the case expressions - if (!isTerminalCase) { - // A case expression exists, and it needs to be tested. - ArrayList exprs = new ArrayList<>(); - for (Tree exprTree : CaseUtils.getLabels(caseTree)) { - exprs.add(scan(exprTree, null)); - } - - ExpressionTree guardTree = CaseUtils.getGuard(caseTree); - Node guard = (guardTree == null) ? null : scan(guardTree, null); - - CaseNode test = - new CaseNode( - caseTree, selectorExprAssignment, exprs, guard, env.getTypeUtils()); - extendWithNode(test); - extendWithExtendedNode(new ConditionalJump(thisBodyLabel, nextCaseLabel)); - } + for (int i = 0; i < numCases; ++i) { + caseBodyLabels[i] = new Label(); + } + caseBodyLabels[numCases] = breakTargetLC.peekLabel(); - // Handle the case body - addLabelForNextNode(thisBodyLabel); - if (caseTree.getStatements() != null) { - // This is a switch labeled statement group. - // A "switch labeled statement group" is a "case L:" label along with its code. - // The code either ends with a "yield" statement, or it falls through. - for (StatementTree stmt : caseTree.getStatements()) { - scan(stmt, null); - } - // Handle possible fallthrough by adding jump to next body. - if (!isTerminalCase) { - extendWithExtendedNode(new UnconditionalJump(nextBodyLabel)); - } - } else { - // This is either the default case or a switch labeled rule (which appears in a - // switch expression). - // A "switch labeled rule" is a "case L ->" label along with its code. - Tree bodyTree = CaseUtils.getBody(caseTree); - if (!TreeUtils.isSwitchStatement(switchTree) - && bodyTree instanceof ExpressionTree) { - buildSwitchExpressionResult((ExpressionTree) bodyTree); - } else { - scan(bodyTree, null); - // Switch rules never fall through so add jump to the break target. - assert breakTargetLC != null : "no target for case statement"; - extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); - } - } + buildSelector(); - if (!isTerminalCase) { - addLabelForNextNode(nextCaseLabel); - } - } + buildSwitchExpressionVar(); - /** - * Does the following for the result expression of a switch expression, {@code - * resultExpression}: - * - *
    - *
  1. Builds the CFG for the switch expression result. - *
  2. Creates an assignment node for the assignment of {@code resultExpression} to {@code - * switchExprVarTree}. - *
  3. Adds an unconditional jump to {@link #breakTargetLC} (the end of the switch - * expression). - *
- * - * @param resultExpression the result of a switch expression; either from a yield or an - * expression in a case rule - */ - /*package-private*/ void buildSwitchExpressionResult(ExpressionTree resultExpression) { - IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); - handleArtificialTree(switchExprVarUseTree); - - LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); - switchExprVarUseNode.setInSource(false); - extendWithNode(switchExprVarUseNode); - - Node resultExprNode = scan(resultExpression, null); - - AssignmentTree assign = - treeBuilder.buildAssignment(switchExprVarUseTree, resultExpression); - handleArtificialTree(assign); - - AssignmentNode assignmentNode = - new AssignmentNode(assign, switchExprVarUseNode, resultExprNode); - assignmentNode.setInSource(false); - extendWithNode(assignmentNode); - // Switch rules never fall through so add jump to the break target. - assert breakTargetLC != null : "no target for case statement"; - extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); + if (TreeUtils.isSwitchStatement(switchTree)) { + // It's a switch statement, not a switch expression. + extendWithNode( + new MarkerNode( + switchTree, + "start of switch statement #" + TreeUtils.treeUids.get(switchTree), + env.getTypeUtils())); + } + + // JSL 14.11.2 + // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 + // states "For compatibility reasons, switch statements that are not enhanced switch + // statements are not required to be exhaustive". + // Switch expressions and enhanced switch statements are exhaustive. + boolean switchExprOrEnhanced = + !TreeUtils.isSwitchStatement(switchTree) + || TreeUtils.isEnhancedSwitchStatement((SwitchTree) switchTree); + // Build CFG for the cases. + int defaultIndex = -1; + for (int i = 0; i < numCases; ++i) { + CaseTree caseTree = caseTrees.get(i); + if (CaseUtils.isDefaultCaseTree(caseTree)) { + // Per the Java Language Specification, the checks of all cases must happen + // before the default case, no matter where `default:` is written. Therefore, + // build the default case last. + defaultIndex = i; + } else if (i == numCases - 1 && defaultIndex == -1) { + // This is the last case, and there is no default case. + // Switch expressions and enhanced switch statements are exhaustive. + buildCase(caseTree, i, switchExprOrEnhanced); + } else { + buildCase(caseTree, i, false); } + } + + if (defaultIndex != -1) { + // The checks of all cases must happen before the default case, therefore we build + // the default case last. + // Fallthrough is still handled correctly with the caseBodyLabels. + buildCase(caseTrees.get(defaultIndex), defaultIndex, false); + } + + addLabelForNextNode(breakTargetLC.peekLabel()); + breakTargetLC = oldBreakTargetLC; + if (TreeUtils.isSwitchStatement(switchTree)) { + // It's a switch statement, not a switch expression. + extendWithNode( + new MarkerNode( + switchTree, + "end of switch statement #" + TreeUtils.treeUids.get(switchTree), + env.getTypeUtils())); + } + + if (!TreeUtils.isSwitchStatement(switchTree)) { + // It's a switch expression, not a switch statement. + IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); + handleArtificialTree(switchExprVarUseTree); + + LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); + switchExprVarUseNode.setInSource(false); + extendWithNode(switchExprVarUseNode); + SwitchExpressionNode switchExpressionNode = + new SwitchExpressionNode( + TreeUtils.typeOf(switchTree), switchTree, switchExprVarUseNode); + extendWithNode(switchExpressionNode); + return switchExpressionNode; + } else { + return null; + } } - @Override - public Node visitCase(CaseTree tree, Void p) { - // This assertion assumes that `case` appears only within a switch statement, - throw new AssertionError("case visitor is implemented in SwitchBuilder"); - } + /** + * Builds the CFG for the selector expression. It also creates a synthetic variable and assigns + * the selector expression to the variable. This assignment node is stored in {@link + * #selectorExprAssignment}. It can later be used to refine the selector expression in case + * bodies. + */ + private void buildSelector() { + // Create a synthetic variable to which the switch selector expression will be assigned + TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree); + VariableTree selectorVarTree = + treeBuilder.buildVariableDecl(selectorExprType, uniqueName("switch"), findOwner(), null); + handleArtificialTree(selectorVarTree); - @Override - public Node visitCatch(CatchTree tree, Void p) { - scan(tree.getParameter(), p); - scan(tree.getBlock(), p); - return null; - } + VariableDeclarationNode selectorVarNode = new VariableDeclarationNode(selectorVarTree); + selectorVarNode.setInSource(false); + extendWithNode(selectorVarNode); - // This is not invoked for top-level classes. Maybe it is, for classes defined within method - // bodies. - @Override - public Node visitClass(ClassTree tree, Void p) { - declaredClasses.add(tree); - Node classbody = new ClassDeclarationNode(tree); - extendWithNode(classbody); - return classbody; - } + IdentifierTree selectorVarUseTree = treeBuilder.buildVariableUse(selectorVarTree); + handleArtificialTree(selectorVarUseTree); + + LocalVariableNode selectorVarUseNode = new LocalVariableNode(selectorVarUseTree); + selectorVarUseNode.setInSource(false); + extendWithNode(selectorVarUseNode); + + Node selectorExprNode = unbox(scan(selectorExprTree, null)); - @Override - public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - // see JLS 15.25 - TypeMirror exprType = TreeUtils.typeOf(tree); - - Label trueStart = new Label(); - Label falseStart = new Label(); - Label merge = new Label(); - - // create a synthetic variable for the value of the conditional expression - VariableTree condExprVarTree = - treeBuilder.buildVariableDecl(exprType, uniqueName("condExpr"), findOwner(), null); - handleArtificialTree(condExprVarTree); - VariableDeclarationNode condExprVarNode = new VariableDeclarationNode(condExprVarTree); - condExprVarNode.setInSource(false); - extendWithNode(condExprVarNode); - - Node condition = unbox(scan(tree.getCondition(), p)); - ConditionalJump cjump = new ConditionalJump(trueStart, falseStart); - extendWithExtendedNode(cjump); - - addLabelForNextNode(trueStart); - ExpressionTree trueExprTree = tree.getTrueExpression(); - Node trueExprNode = scan(trueExprTree, p); - trueExprNode = conditionalExprPromotion(trueExprNode, exprType); - extendWithAssignmentForConditionalExpr(condExprVarTree, trueExprTree, trueExprNode); - extendWithExtendedNode(new UnconditionalJump(merge)); - - addLabelForNextNode(falseStart); - ExpressionTree falseExprTree = tree.getFalseExpression(); - Node falseExprNode = scan(falseExprTree, p); - falseExprNode = conditionalExprPromotion(falseExprNode, exprType); - extendWithAssignmentForConditionalExpr(condExprVarTree, falseExprTree, falseExprNode); - extendWithExtendedNode(new UnconditionalJump(merge)); - - addLabelForNextNode(merge); - IPair treeAndLocalVarNode = - buildVarUseNode(condExprVarTree); - Node node = - new TernaryExpressionNode( - tree, condition, trueExprNode, falseExprNode, treeAndLocalVarNode.second); - extendWithNode(node); - - return node; + AssignmentTree assign = treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree); + handleArtificialTree(assign); + + selectorExprAssignment = new AssignmentNode(assign, selectorVarUseNode, selectorExprNode); + selectorExprAssignment.setInSource(false); + extendWithNode(selectorExprAssignment); } /** - * Extend the CFG with an assignment for either the true or false case of a conditional - * expression, assigning the value of the expression for the case to the synthetic variable for - * the conditional expression - * - * @param condExprVarTree tree for synthetic variable for conditional expression - * @param caseExprTree expression tree for the case - * @param caseExprNode node for the case + * If {@link #switchTree} is a switch expression tree, this method creates a synthetic variable + * whose value is the value of the switch expression. */ - private void extendWithAssignmentForConditionalExpr( - VariableTree condExprVarTree, ExpressionTree caseExprTree, Node caseExprNode) { - IPair treeAndLocalVarNode = - buildVarUseNode(condExprVarTree); - - AssignmentTree assign = - treeBuilder.buildAssignment(treeAndLocalVarNode.first, caseExprTree); - handleArtificialTree(assign); - - // Build a "synthetic" assignment node, allowing special handling in transfer functions - AssignmentNode assignmentNode = - new AssignmentNode(assign, treeAndLocalVarNode.second, caseExprNode, true); - assignmentNode.setInSource(false); - extendWithNode(assignmentNode); + private void buildSwitchExpressionVar() { + if (TreeUtils.isSwitchStatement(switchTree)) { + // A switch statement does not have a value, so do nothing. + return; + } + TypeMirror switchExprType = TreeUtils.typeOf(switchTree); + switchExprVarTree = + treeBuilder.buildVariableDecl( + switchExprType, uniqueName("switchExpr"), findOwner(), null); + handleArtificialTree(switchExprVarTree); + + VariableDeclarationNode switchExprVarNode = new VariableDeclarationNode(switchExprVarTree); + switchExprVarNode.setInSource(false); + extendWithNode(switchExprVarNode); } /** - * Build a pair of {@link IdentifierTree} and {@link LocalVariableNode} to represent a use of - * some variable. Does not add the node to the CFG. + * Build the CFG for the given case tree. * - * @param varTree tree for the variable - * @return a pair whose first element is the synthetic {@link IdentifierTree} for the use, and - * whose second element is the {@link LocalVariableNode} representing the use + * @param caseTree a case tree whose CFG to build + * @param index the index of the case tree in {@link #caseBodyLabels} + * @param isLastCaseOfExhaustive true if this is the last case of an exhaustive switch + * statement, with no fallthrough to it. In other words, no test of the labels is necessary. */ - private IPair buildVarUseNode(VariableTree varTree) { - IdentifierTree condExprVarUseTree = treeBuilder.buildVariableUse(varTree); - handleArtificialTree(condExprVarUseTree); - LocalVariableNode condExprVarUseNode = new LocalVariableNode(condExprVarUseTree); - condExprVarUseNode.setInSource(false); - // Do not actually add the node to the CFG. - return IPair.of(condExprVarUseTree, condExprVarUseNode); - } - - @Override - public Node visitContinue(ContinueTree tree, Void p) { - Name label = tree.getLabel(); - if (label == null) { - assert continueTargetLC != null : "no target for continue statement"; - - extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); - } else { - assert continueLabels.containsKey(label); - - extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label))); + private void buildCase(CaseTree caseTree, int index, boolean isLastCaseOfExhaustive) { + boolean isDefaultCase = CaseUtils.isDefaultCaseTree(caseTree); + // If true, no test of labels is necessary. + // Unfortunately, if isLastCaseOfExhaustive==TRUE, no flow-sensitive refinement occurs + // within the body of the CaseNode. In the future, that can be performed, but it + // requires addition of InfeasibleExitBlock, a new SpecialBlock in the CFG. + boolean isTerminalCase = isDefaultCase || isLastCaseOfExhaustive; + + Label thisBodyLabel = caseBodyLabels[index]; + Label nextBodyLabel = caseBodyLabels[index + 1]; + // `nextCaseLabel` is not used if isTerminalCase==FALSE. + Label nextCaseLabel = new Label(); + + // Handle the case expressions + if (!isTerminalCase) { + // A case expression exists, and it needs to be tested. + ArrayList exprs = new ArrayList<>(); + for (Tree exprTree : CaseUtils.getLabels(caseTree)) { + exprs.add(scan(exprTree, null)); } - return null; - } - - @Override - public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - Name parentLabel = getLabel(getCurrentPath()); - - Label loopEntry = new Label(); - Label loopExit = new Label(); - - // If the loop is a labeled statement, then its continue target is identical for continues - // with no label and continues with the loop's label. - Label conditionStart; - if (parentLabel != null) { - conditionStart = continueLabels.get(parentLabel); + ExpressionTree guardTree = CaseUtils.getGuard(caseTree); + Node guard = (guardTree == null) ? null : scan(guardTree, null); + + CaseNode test = + new CaseNode(caseTree, selectorExprAssignment, exprs, guard, env.getTypeUtils()); + extendWithNode(test); + extendWithExtendedNode(new ConditionalJump(thisBodyLabel, nextCaseLabel)); + } + + // Handle the case body + addLabelForNextNode(thisBodyLabel); + if (caseTree.getStatements() != null) { + // This is a switch labeled statement group. + // A "switch labeled statement group" is a "case L:" label along with its code. + // The code either ends with a "yield" statement, or it falls through. + for (StatementTree stmt : caseTree.getStatements()) { + scan(stmt, null); + } + // Handle possible fallthrough by adding jump to next body. + if (!isTerminalCase) { + extendWithExtendedNode(new UnconditionalJump(nextBodyLabel)); + } + } else { + // This is either the default case or a switch labeled rule (which appears in a + // switch expression). + // A "switch labeled rule" is a "case L ->" label along with its code. + Tree bodyTree = CaseUtils.getBody(caseTree); + if (!TreeUtils.isSwitchStatement(switchTree) && bodyTree instanceof ExpressionTree) { + buildSwitchExpressionResult((ExpressionTree) bodyTree); } else { - conditionStart = new Label(); + scan(bodyTree, null); + // Switch rules never fall through so add jump to the break target. + assert breakTargetLC != null : "no target for case statement"; + extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); } + } - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(loopExit); - - LabelCell oldContinueTargetLC = continueTargetLC; - continueTargetLC = new LabelCell(conditionStart); - - // Loop body - addLabelForNextNode(loopEntry); - assert tree.getStatement() != null; - scan(tree.getStatement(), p); - - // Condition - addLabelForNextNode(conditionStart); - assert tree.getCondition() != null; - unbox(scan(tree.getCondition(), p)); - ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); - extendWithExtendedNode(cjump); - - // Loop exit - addLabelForNextNode(loopExit); - - breakTargetLC = oldBreakTargetLC; - continueTargetLC = oldContinueTargetLC; - - return null; + if (!isTerminalCase) { + addLabelForNextNode(nextCaseLabel); + } } - @Override - public Node visitErroneous(ErroneousTree tree, Void p) { - throw new BugInCF("ErroneousTree is unexpected in AST to CFG translation: " + tree); + /** + * Does the following for the result expression of a switch expression, {@code + * resultExpression}: + * + *
    + *
  1. Builds the CFG for the switch expression result. + *
  2. Creates an assignment node for the assignment of {@code resultExpression} to {@code + * switchExprVarTree}. + *
  3. Adds an unconditional jump to {@link #breakTargetLC} (the end of the switch + * expression). + *
+ * + * @param resultExpression the result of a switch expression; either from a yield or an + * expression in a case rule + */ + /*package-private*/ void buildSwitchExpressionResult(ExpressionTree resultExpression) { + IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); + handleArtificialTree(switchExprVarUseTree); + + LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); + switchExprVarUseNode.setInSource(false); + extendWithNode(switchExprVarUseNode); + + Node resultExprNode = scan(resultExpression, null); + + AssignmentTree assign = treeBuilder.buildAssignment(switchExprVarUseTree, resultExpression); + handleArtificialTree(assign); + + AssignmentNode assignmentNode = + new AssignmentNode(assign, switchExprVarUseNode, resultExprNode); + assignmentNode.setInSource(false); + extendWithNode(assignmentNode); + // Switch rules never fall through so add jump to the break target. + assert breakTargetLC != null : "no target for case statement"; + extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); } - - @Override - public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) { - ExpressionTree exprTree = tree.getExpression(); - scan(exprTree, p); - extendWithNode(new ExpressionStatementNode(exprTree)); - return null; + } + + @Override + public Node visitCase(CaseTree tree, Void p) { + // This assertion assumes that `case` appears only within a switch statement, + throw new AssertionError("case visitor is implemented in SwitchBuilder"); + } + + @Override + public Node visitCatch(CatchTree tree, Void p) { + scan(tree.getParameter(), p); + scan(tree.getBlock(), p); + return null; + } + + // This is not invoked for top-level classes. Maybe it is, for classes defined within method + // bodies. + @Override + public Node visitClass(ClassTree tree, Void p) { + declaredClasses.add(tree); + Node classbody = new ClassDeclarationNode(tree); + extendWithNode(classbody); + return classbody; + } + + @Override + public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + // see JLS 15.25 + TypeMirror exprType = TreeUtils.typeOf(tree); + + Label trueStart = new Label(); + Label falseStart = new Label(); + Label merge = new Label(); + + // create a synthetic variable for the value of the conditional expression + VariableTree condExprVarTree = + treeBuilder.buildVariableDecl(exprType, uniqueName("condExpr"), findOwner(), null); + handleArtificialTree(condExprVarTree); + VariableDeclarationNode condExprVarNode = new VariableDeclarationNode(condExprVarTree); + condExprVarNode.setInSource(false); + extendWithNode(condExprVarNode); + + Node condition = unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(trueStart, falseStart); + extendWithExtendedNode(cjump); + + addLabelForNextNode(trueStart); + ExpressionTree trueExprTree = tree.getTrueExpression(); + Node trueExprNode = scan(trueExprTree, p); + trueExprNode = conditionalExprPromotion(trueExprNode, exprType); + extendWithAssignmentForConditionalExpr(condExprVarTree, trueExprTree, trueExprNode); + extendWithExtendedNode(new UnconditionalJump(merge)); + + addLabelForNextNode(falseStart); + ExpressionTree falseExprTree = tree.getFalseExpression(); + Node falseExprNode = scan(falseExprTree, p); + falseExprNode = conditionalExprPromotion(falseExprNode, exprType); + extendWithAssignmentForConditionalExpr(condExprVarTree, falseExprTree, falseExprNode); + extendWithExtendedNode(new UnconditionalJump(merge)); + + addLabelForNextNode(merge); + IPair treeAndLocalVarNode = buildVarUseNode(condExprVarTree); + Node node = + new TernaryExpressionNode( + tree, condition, trueExprNode, falseExprNode, treeAndLocalVarNode.second); + extendWithNode(node); + + return node; + } + + /** + * Extend the CFG with an assignment for either the true or false case of a conditional + * expression, assigning the value of the expression for the case to the synthetic variable for + * the conditional expression + * + * @param condExprVarTree tree for synthetic variable for conditional expression + * @param caseExprTree expression tree for the case + * @param caseExprNode node for the case + */ + private void extendWithAssignmentForConditionalExpr( + VariableTree condExprVarTree, ExpressionTree caseExprTree, Node caseExprNode) { + IPair treeAndLocalVarNode = buildVarUseNode(condExprVarTree); + + AssignmentTree assign = treeBuilder.buildAssignment(treeAndLocalVarNode.first, caseExprTree); + handleArtificialTree(assign); + + // Build a "synthetic" assignment node, allowing special handling in transfer functions + AssignmentNode assignmentNode = + new AssignmentNode(assign, treeAndLocalVarNode.second, caseExprNode, true); + assignmentNode.setInSource(false); + extendWithNode(assignmentNode); + } + + /** + * Build a pair of {@link IdentifierTree} and {@link LocalVariableNode} to represent a use of some + * variable. Does not add the node to the CFG. + * + * @param varTree tree for the variable + * @return a pair whose first element is the synthetic {@link IdentifierTree} for the use, and + * whose second element is the {@link LocalVariableNode} representing the use + */ + private IPair buildVarUseNode(VariableTree varTree) { + IdentifierTree condExprVarUseTree = treeBuilder.buildVariableUse(varTree); + handleArtificialTree(condExprVarUseTree); + LocalVariableNode condExprVarUseNode = new LocalVariableNode(condExprVarUseTree); + condExprVarUseNode.setInSource(false); + // Do not actually add the node to the CFG. + return IPair.of(condExprVarUseTree, condExprVarUseNode); + } + + @Override + public Node visitContinue(ContinueTree tree, Void p) { + Name label = tree.getLabel(); + if (label == null) { + assert continueTargetLC != null : "no target for continue statement"; + + extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); + } else { + assert continueLabels.containsKey(label); + + extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label))); } - @Override - public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - // see JLS 14.14.2 - Name parentLabel = getLabel(getCurrentPath()); - - Label conditionStart = new Label(); - Label loopEntry = new Label(); - Label loopExit = new Label(); - - // If the loop is a labeled statement, then its continue target is identical for continues - // with no label and continues with the loop's label. - Label updateStart; - if (parentLabel != null) { - updateStart = continueLabels.get(parentLabel); - } else { - updateStart = new Label(); - } - - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(loopExit); + return null; + } - LabelCell oldContinueTargetLC = continueTargetLC; - continueTargetLC = new LabelCell(updateStart); + @Override + public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + Name parentLabel = getLabel(getCurrentPath()); - // Distinguish loops over Iterables from loops over arrays. + Label loopEntry = new Label(); + Label loopExit = new Label(); - VariableTree variable = tree.getVariable(); - VariableElement variableElement = TreeUtils.elementFromDeclaration(variable); - ExpressionTree expression = tree.getExpression(); - StatementTree statement = tree.getStatement(); - - TypeMirror exprType = TreeUtils.typeOf(expression); - - if (types.isSubtype(exprType, iterableType)) { - // Take the upper bound of a type variable or wildcard - exprType = TypesUtils.upperBound(exprType); - - assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType"; - DeclaredType declaredExprType = (DeclaredType) exprType; - declaredExprType.getTypeArguments(); - - MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression); - handleArtificialTree(iteratorSelect); - - MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect); - handleArtificialTree(iteratorCall); - - VariableTree iteratorVariable = - createEnhancedForLoopIteratorVariable(iteratorCall, variableElement); - handleArtificialTree(iteratorVariable); - - VariableDeclarationNode iteratorVariableDecl = - new VariableDeclarationNode(iteratorVariable); - iteratorVariableDecl.setInSource(false); - - extendWithNode(iteratorVariableDecl); - - Node expressionNode = scan(expression, p); - - MethodAccessNode iteratorAccessNode = - new MethodAccessNode(iteratorSelect, expressionNode); - iteratorAccessNode.setInSource(false); - extendWithNode(iteratorAccessNode); - MethodInvocationNode iteratorCallNode = - new MethodInvocationNode( - iteratorCall, - iteratorAccessNode, - Collections.emptyList(), - getCurrentPath()); - iteratorCallNode.setInSource(false); - extendWithNode(iteratorCallNode); - - translateAssignment( - iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode); - - // Test the loop ending condition - addLabelForNextNode(conditionStart); - IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable); - handleArtificialTree(iteratorUse1); - - LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1); - iteratorReceiverNode.setInSource(false); - extendWithNode(iteratorReceiverNode); - - MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1); - handleArtificialTree(hasNextSelect); - - MethodAccessNode hasNextAccessNode = - new MethodAccessNode(hasNextSelect, iteratorReceiverNode); - hasNextAccessNode.setInSource(false); - extendWithNode(hasNextAccessNode); - - MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect); - handleArtificialTree(hasNextCall); - - MethodInvocationNode hasNextCallNode = - new MethodInvocationNode( - hasNextCall, - hasNextAccessNode, - Collections.emptyList(), - getCurrentPath()); - hasNextCallNode.setInSource(false); - extendWithNode(hasNextCallNode); - extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); - - // Loop body, starting with declaration of the loop iteration variable - addLabelForNextNode(loopEntry); - extendWithNode(new VariableDeclarationNode(variable)); - - IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable); - handleArtificialTree(iteratorUse2); - - LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2); - iteratorReceiverNode2.setInSource(false); - extendWithNode(iteratorReceiverNode2); - - MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2); - handleArtificialTree(nextSelect); - - MethodAccessNode nextAccessNode = - new MethodAccessNode(nextSelect, iteratorReceiverNode2); - nextAccessNode.setInSource(false); - extendWithNode(nextAccessNode); - - MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect); - handleArtificialTree(nextCall); - - MethodInvocationNode nextCallNode = - new MethodInvocationNode( - nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath()); - // If the type of iteratorVariable is a capture, its type tree may be missing - // annotations, so save the expression in the node so that the full type can be - // found later. - nextCallNode.setIterableExpression(expression); - nextCallNode.setInSource(false); - extendWithNode(nextCallNode); - - AssignmentNode assignNode = - translateAssignment(variable, new LocalVariableNode(variable), nextCall); - // translateAssignment() scans variable and creates new nodes, so set the expression - // there, too. - ((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression); - - assert statement != null; - scan(statement, p); - - // Loop back edge - addLabelForNextNode(updateStart); - extendWithExtendedNode(new UnconditionalJump(conditionStart)); - - } else { - // TODO: Shift any labels after the initialization of the - // temporary array variable. - - VariableTree arrayVariable = - createEnhancedForLoopArrayVariable(expression, variableElement); - handleArtificialTree(arrayVariable); - - VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable); - arrayVariableNode.setInSource(false); - extendWithNode(arrayVariableNode); - Node expressionNode = scan(expression, p); - - translateAssignment( - arrayVariable, new LocalVariableNode(arrayVariable), expressionNode); - - // Declare and initialize the loop index variable - TypeMirror intType = types.getPrimitiveType(TypeKind.INT); - - LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0)); - handleArtificialTree(zero); - - VariableTree indexVariable = - treeBuilder.buildVariableDecl( - intType, - uniqueName("index"), - variableElement.getEnclosingElement(), - zero); - handleArtificialTree(indexVariable); - VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable); - indexVariableNode.setInSource(false); - extendWithNode(indexVariableNode); - IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero); - extendWithNode(zeroNode); - - translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode); - - // Compare index to array length - addLabelForNextNode(conditionStart); - IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable); - handleArtificialTree(indexUse1); - LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1); - indexNode1.setInSource(false); - extendWithNode(indexNode1); - - IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable); - handleArtificialTree(arrayUse1); - LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1); - extendWithNode(arrayNode1); - - MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1); - handleArtificialTree(lengthSelect); - FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1); - lengthAccessNode.setInSource(false); - extendWithNode(lengthAccessNode); - - BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect); - handleArtificialTree(lessThan); - - LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode); - lessThanNode.setInSource(false); - extendWithNode(lessThanNode); - extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); - - // Loop body, starting with declaration of the loop iteration variable - addLabelForNextNode(loopEntry); - extendWithNode(new VariableDeclarationNode(variable)); - - IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable); - handleArtificialTree(arrayUse2); - LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2); - arrayNode2.setInSource(false); - extendWithNode(arrayNode2); - - IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable); - handleArtificialTree(indexUse2); - LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2); - indexNode2.setInSource(false); - extendWithNode(indexNode2); - - ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2); - handleArtificialTree(arrayAccess); - ArrayAccessNode arrayAccessNode = - new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2); - arrayAccessNode.setArrayExpression(expression); - arrayAccessNode.setInSource(false); - extendWithNode(arrayAccessNode); - AssignmentNode arrayAccessAssignNode = - translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode); - extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType); - // translateAssignment() scans variable and creates new nodes, so set the expression - // there, too. - Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression(); - if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) { - ((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression); - } else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) { - // If the array component type is a primitive, there may be a boxing or unboxing - // conversion. Treat that as an iterator. - MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr; - boxingNode.setIterableExpression(expression); - } + // If the loop is a labeled statement, then its continue target is identical for continues + // with no label and continues with the loop's label. + Label conditionStart; + if (parentLabel != null) { + conditionStart = continueLabels.get(parentLabel); + } else { + conditionStart = new Label(); + } - assert statement != null; - scan(statement, p); - - // Loop back edge - addLabelForNextNode(updateStart); - - IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable); - handleArtificialTree(indexUse3); - LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3); - indexNode3.setInSource(false); - extendWithNode(indexNode3); - - LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); - handleArtificialTree(oneTree); - Node one = new IntegerLiteralNode(oneTree); - one.setInSource(false); - extendWithNode(one); - - BinaryTree addOneTree = - treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree); - handleArtificialTree(addOneTree); - Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one); - addOneNode.setInSource(false); - extendWithNode(addOneNode); - - AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree); - handleArtificialTree(assignTree); - Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode); - assignNode.setInSource(false); - extendWithNode(assignNode); + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(loopExit); + + LabelCell oldContinueTargetLC = continueTargetLC; + continueTargetLC = new LabelCell(conditionStart); + + // Loop body + addLabelForNextNode(loopEntry); + assert tree.getStatement() != null; + scan(tree.getStatement(), p); + + // Condition + addLabelForNextNode(conditionStart); + assert tree.getCondition() != null; + unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); + extendWithExtendedNode(cjump); + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetLC = oldBreakTargetLC; + continueTargetLC = oldContinueTargetLC; + + return null; + } + + @Override + public Node visitErroneous(ErroneousTree tree, Void p) { + throw new BugInCF("ErroneousTree is unexpected in AST to CFG translation: " + tree); + } + + @Override + public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) { + ExpressionTree exprTree = tree.getExpression(); + scan(exprTree, p); + extendWithNode(new ExpressionStatementNode(exprTree)); + return null; + } + + @Override + public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + // see JLS 14.14.2 + Name parentLabel = getLabel(getCurrentPath()); + + Label conditionStart = new Label(); + Label loopEntry = new Label(); + Label loopExit = new Label(); + + // If the loop is a labeled statement, then its continue target is identical for continues + // with no label and continues with the loop's label. + Label updateStart; + if (parentLabel != null) { + updateStart = continueLabels.get(parentLabel); + } else { + updateStart = new Label(); + } - extendWithExtendedNode(new UnconditionalJump(conditionStart)); - } + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(loopExit); - // Loop exit - addLabelForNextNode(loopExit); + LabelCell oldContinueTargetLC = continueTargetLC; + continueTargetLC = new LabelCell(updateStart); - breakTargetLC = oldBreakTargetLC; - continueTargetLC = oldContinueTargetLC; + // Distinguish loops over Iterables from loops over arrays. - return null; - } + VariableTree variable = tree.getVariable(); + VariableElement variableElement = TreeUtils.elementFromDeclaration(variable); + ExpressionTree expression = tree.getExpression(); + StatementTree statement = tree.getStatement(); - protected VariableTree createEnhancedForLoopIteratorVariable( - MethodInvocationTree iteratorCall, VariableElement variableElement) { - TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall); + TypeMirror exprType = TreeUtils.typeOf(expression); - // Declare and initialize a new, unique iterator variable - VariableTree iteratorVariable = - treeBuilder.buildVariableDecl( - iteratorType, // annotatedIteratorTypeTree, - uniqueName("iter"), - variableElement.getEnclosingElement(), - iteratorCall); - return iteratorVariable; - } + if (types.isSubtype(exprType, iterableType)) { + // Take the upper bound of a type variable or wildcard + exprType = TypesUtils.upperBound(exprType); - protected VariableTree createEnhancedForLoopArrayVariable( - ExpressionTree expression, VariableElement variableElement) { - TypeMirror arrayType = TreeUtils.typeOf(expression); + assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType"; + DeclaredType declaredExprType = (DeclaredType) exprType; + declaredExprType.getTypeArguments(); - // Declare and initialize a temporary array variable - VariableTree arrayVariable = - treeBuilder.buildVariableDecl( - arrayType, - uniqueName("array"), - variableElement.getEnclosingElement(), - expression); - return arrayVariable; - } + MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression); + handleArtificialTree(iteratorSelect); - @Override - public Node visitForLoop(ForLoopTree tree, Void p) { - Name parentLabel = getLabel(getCurrentPath()); + MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect); + handleArtificialTree(iteratorCall); - Label conditionStart = new Label(); - Label loopEntry = new Label(); - Label loopExit = new Label(); + VariableTree iteratorVariable = + createEnhancedForLoopIteratorVariable(iteratorCall, variableElement); + handleArtificialTree(iteratorVariable); - // If the loop is a labeled statement, then its continue target is identical for continues - // with no label and continues with the loop's label. - Label updateStart; - if (parentLabel != null) { - updateStart = continueLabels.get(parentLabel); - } else { - updateStart = new Label(); - } + VariableDeclarationNode iteratorVariableDecl = new VariableDeclarationNode(iteratorVariable); + iteratorVariableDecl.setInSource(false); - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(loopExit); + extendWithNode(iteratorVariableDecl); - LabelCell oldContinueTargetLC = continueTargetLC; - continueTargetLC = new LabelCell(updateStart); + Node expressionNode = scan(expression, p); - // Initializer - for (StatementTree init : tree.getInitializer()) { - scan(init, p); - } + MethodAccessNode iteratorAccessNode = new MethodAccessNode(iteratorSelect, expressionNode); + iteratorAccessNode.setInSource(false); + extendWithNode(iteratorAccessNode); + MethodInvocationNode iteratorCallNode = + new MethodInvocationNode( + iteratorCall, iteratorAccessNode, Collections.emptyList(), getCurrentPath()); + iteratorCallNode.setInSource(false); + extendWithNode(iteratorCallNode); - // Condition - addLabelForNextNode(conditionStart); - if (tree.getCondition() != null) { - unbox(scan(tree.getCondition(), p)); - ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); - extendWithExtendedNode(cjump); - } + translateAssignment( + iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode); - // Loop body - addLabelForNextNode(loopEntry); - assert tree.getStatement() != null; - scan(tree.getStatement(), p); + // Test the loop ending condition + addLabelForNextNode(conditionStart); + IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable); + handleArtificialTree(iteratorUse1); - // Update - addLabelForNextNode(updateStart); - for (ExpressionStatementTree update : tree.getUpdate()) { - scan(update, p); - } + LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1); + iteratorReceiverNode.setInSource(false); + extendWithNode(iteratorReceiverNode); - extendWithExtendedNode(new UnconditionalJump(conditionStart)); + MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1); + handleArtificialTree(hasNextSelect); - // Loop exit - addLabelForNextNode(loopExit); + MethodAccessNode hasNextAccessNode = + new MethodAccessNode(hasNextSelect, iteratorReceiverNode); + hasNextAccessNode.setInSource(false); + extendWithNode(hasNextAccessNode); - breakTargetLC = oldBreakTargetLC; - continueTargetLC = oldContinueTargetLC; + MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect); + handleArtificialTree(hasNextCall); + + MethodInvocationNode hasNextCallNode = + new MethodInvocationNode( + hasNextCall, hasNextAccessNode, Collections.emptyList(), getCurrentPath()); + hasNextCallNode.setInSource(false); + extendWithNode(hasNextCallNode); + extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); + + // Loop body, starting with declaration of the loop iteration variable + addLabelForNextNode(loopEntry); + extendWithNode(new VariableDeclarationNode(variable)); + + IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable); + handleArtificialTree(iteratorUse2); - return null; + LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2); + iteratorReceiverNode2.setInSource(false); + extendWithNode(iteratorReceiverNode2); + + MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2); + handleArtificialTree(nextSelect); + + MethodAccessNode nextAccessNode = new MethodAccessNode(nextSelect, iteratorReceiverNode2); + nextAccessNode.setInSource(false); + extendWithNode(nextAccessNode); + + MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect); + handleArtificialTree(nextCall); + + MethodInvocationNode nextCallNode = + new MethodInvocationNode( + nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath()); + // If the type of iteratorVariable is a capture, its type tree may be missing + // annotations, so save the expression in the node so that the full type can be + // found later. + nextCallNode.setIterableExpression(expression); + nextCallNode.setInSource(false); + extendWithNode(nextCallNode); + + AssignmentNode assignNode = + translateAssignment(variable, new LocalVariableNode(variable), nextCall); + // translateAssignment() scans variable and creates new nodes, so set the expression + // there, too. + ((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression); + + assert statement != null; + scan(statement, p); + + // Loop back edge + addLabelForNextNode(updateStart); + extendWithExtendedNode(new UnconditionalJump(conditionStart)); + + } else { + // TODO: Shift any labels after the initialization of the + // temporary array variable. + + VariableTree arrayVariable = createEnhancedForLoopArrayVariable(expression, variableElement); + handleArtificialTree(arrayVariable); + + VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable); + arrayVariableNode.setInSource(false); + extendWithNode(arrayVariableNode); + Node expressionNode = scan(expression, p); + + translateAssignment(arrayVariable, new LocalVariableNode(arrayVariable), expressionNode); + + // Declare and initialize the loop index variable + TypeMirror intType = types.getPrimitiveType(TypeKind.INT); + + LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0)); + handleArtificialTree(zero); + + VariableTree indexVariable = + treeBuilder.buildVariableDecl( + intType, uniqueName("index"), variableElement.getEnclosingElement(), zero); + handleArtificialTree(indexVariable); + VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable); + indexVariableNode.setInSource(false); + extendWithNode(indexVariableNode); + IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero); + extendWithNode(zeroNode); + + translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode); + + // Compare index to array length + addLabelForNextNode(conditionStart); + IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse1); + LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1); + indexNode1.setInSource(false); + extendWithNode(indexNode1); + + IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable); + handleArtificialTree(arrayUse1); + LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1); + extendWithNode(arrayNode1); + + MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1); + handleArtificialTree(lengthSelect); + FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1); + lengthAccessNode.setInSource(false); + extendWithNode(lengthAccessNode); + + BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect); + handleArtificialTree(lessThan); + + LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode); + lessThanNode.setInSource(false); + extendWithNode(lessThanNode); + extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); + + // Loop body, starting with declaration of the loop iteration variable + addLabelForNextNode(loopEntry); + extendWithNode(new VariableDeclarationNode(variable)); + + IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable); + handleArtificialTree(arrayUse2); + LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2); + arrayNode2.setInSource(false); + extendWithNode(arrayNode2); + + IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse2); + LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2); + indexNode2.setInSource(false); + extendWithNode(indexNode2); + + ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2); + handleArtificialTree(arrayAccess); + ArrayAccessNode arrayAccessNode = new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2); + arrayAccessNode.setArrayExpression(expression); + arrayAccessNode.setInSource(false); + extendWithNode(arrayAccessNode); + AssignmentNode arrayAccessAssignNode = + translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode); + extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType); + // translateAssignment() scans variable and creates new nodes, so set the expression + // there, too. + Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression(); + if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) { + ((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression); + } else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) { + // If the array component type is a primitive, there may be a boxing or unboxing + // conversion. Treat that as an iterator. + MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr; + boxingNode.setIterableExpression(expression); + } + + assert statement != null; + scan(statement, p); + + // Loop back edge + addLabelForNextNode(updateStart); + + IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse3); + LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3); + indexNode3.setInSource(false); + extendWithNode(indexNode3); + + LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); + handleArtificialTree(oneTree); + Node one = new IntegerLiteralNode(oneTree); + one.setInSource(false); + extendWithNode(one); + + BinaryTree addOneTree = treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree); + handleArtificialTree(addOneTree); + Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one); + addOneNode.setInSource(false); + extendWithNode(addOneNode); + + AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree); + handleArtificialTree(assignTree); + Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode); + assignNode.setInSource(false); + extendWithNode(assignNode); + + extendWithExtendedNode(new UnconditionalJump(conditionStart)); } - @Override - public Node visitIdentifier(IdentifierTree tree, Void p) { - Node node; - if (TreeUtils.isFieldAccess(tree)) { - Node receiver = getReceiver(tree); - node = new FieldAccessNode(tree, receiver); - } else { - Element element = TreeUtils.elementFromUse(tree); - switch (element.getKind()) { - case FIELD: - // Note that "this"/"super" is a field, but not a field access. - if (element.getSimpleName().contentEquals("this")) { - node = new ExplicitThisNode(tree); - } else { - node = new SuperNode(tree); - } - break; - case EXCEPTION_PARAMETER: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case PARAMETER: - node = new LocalVariableNode(tree); - break; - case PACKAGE: - node = new PackageNameNode(tree); - break; - default: - if (ElementUtils.isTypeDeclaration(element)) { - node = new ClassNameNode(tree); - break; - } else if (ElementUtils.isBindingVariable(element)) { - // Note: BINDING_VARIABLE should be added as a direct case above when - // instanceof pattern matching and Java15 are supported. - node = new LocalVariableNode(tree); - break; - } - throw new BugInCF("bad element kind " + element.getKind()); - } - } - if (node instanceof ClassNameNode) { - extendWithClassNameNode((ClassNameNode) node); - } else { - extendWithNode(node); - } - return node; + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetLC = oldBreakTargetLC; + continueTargetLC = oldContinueTargetLC; + + return null; + } + + protected VariableTree createEnhancedForLoopIteratorVariable( + MethodInvocationTree iteratorCall, VariableElement variableElement) { + TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall); + + // Declare and initialize a new, unique iterator variable + VariableTree iteratorVariable = + treeBuilder.buildVariableDecl( + iteratorType, // annotatedIteratorTypeTree, + uniqueName("iter"), + variableElement.getEnclosingElement(), + iteratorCall); + return iteratorVariable; + } + + protected VariableTree createEnhancedForLoopArrayVariable( + ExpressionTree expression, VariableElement variableElement) { + TypeMirror arrayType = TreeUtils.typeOf(expression); + + // Declare and initialize a temporary array variable + VariableTree arrayVariable = + treeBuilder.buildVariableDecl( + arrayType, uniqueName("array"), variableElement.getEnclosingElement(), expression); + return arrayVariable; + } + + @Override + public Node visitForLoop(ForLoopTree tree, Void p) { + Name parentLabel = getLabel(getCurrentPath()); + + Label conditionStart = new Label(); + Label loopEntry = new Label(); + Label loopExit = new Label(); + + // If the loop is a labeled statement, then its continue target is identical for continues + // with no label and continues with the loop's label. + Label updateStart; + if (parentLabel != null) { + updateStart = continueLabels.get(parentLabel); + } else { + updateStart = new Label(); } - @Override - public Node visitIf(IfTree tree, Void p) { - // all necessary labels - Label thenEntry = new Label(); - Label elseEntry = new Label(); - Label endIf = new Label(); - - // basic block for the condition - unbox(scan(tree.getCondition(), p)); - - ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry); - extendWithExtendedNode(cjump); - - // then branch - addLabelForNextNode(thenEntry); - StatementTree thenStatement = tree.getThenStatement(); - scan(thenStatement, p); - extendWithExtendedNode(new UnconditionalJump(endIf)); - - // else branch - addLabelForNextNode(elseEntry); - StatementTree elseStatement = tree.getElseStatement(); - if (elseStatement != null) { - scan(elseStatement, p); - } - - // label the end of the if statement - addLabelForNextNode(endIf); + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(loopExit); - return null; - } + LabelCell oldContinueTargetLC = continueTargetLC; + continueTargetLC = new LabelCell(updateStart); - @Override - public Node visitImport(ImportTree tree, Void p) { - throw new BugInCF("ImportTree is unexpected in AST to CFG translation: " + tree); + // Initializer + for (StatementTree init : tree.getInitializer()) { + scan(init, p); } - @Override - public Node visitArrayAccess(ArrayAccessTree tree, Void p) { - Node array = scan(tree.getExpression(), p); - Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); - Node arrayAccess = new ArrayAccessNode(tree, array, index); - extendWithNode(arrayAccess); - extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType); - extendWithNodeWithException(arrayAccess, nullPointerExceptionType); - return arrayAccess; + // Condition + addLabelForNextNode(conditionStart); + if (tree.getCondition() != null) { + unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); + extendWithExtendedNode(cjump); } - @Override - public Node visitLabeledStatement(LabeledStatementTree tree, Void p) { - // This method can set the break target after generating all Nodes in the contained - // statement, but it can't set the continue target, which may be in the middle of a - // sequence of nodes. Labeled loops must look up and use the continue Labels. - Name labelName = tree.getLabel(); - - Label breakLabel = new Label(labelName + "_break"); - Label continueLabel = new Label(labelName + "_continue"); - - breakLabels.put(labelName, breakLabel); - continueLabels.put(labelName, continueLabel); - - scan(tree.getStatement(), p); + // Loop body + addLabelForNextNode(loopEntry); + assert tree.getStatement() != null; + scan(tree.getStatement(), p); - addLabelForNextNode(breakLabel); - - breakLabels.remove(labelName); - continueLabels.remove(labelName); - - return null; + // Update + addLabelForNextNode(updateStart); + for (ExpressionStatementTree update : tree.getUpdate()) { + scan(update, p); } - @Override - public Node visitLiteral(LiteralTree tree, Void p) { - Node r = null; - switch (tree.getKind()) { - case BOOLEAN_LITERAL: - r = new BooleanLiteralNode(tree); - break; - case CHAR_LITERAL: - r = new CharacterLiteralNode(tree); - break; - case DOUBLE_LITERAL: - r = new DoubleLiteralNode(tree); - break; - case FLOAT_LITERAL: - r = new FloatLiteralNode(tree); - break; - case INT_LITERAL: - r = new IntegerLiteralNode(tree); - break; - case LONG_LITERAL: - r = new LongLiteralNode(tree); - break; - case NULL_LITERAL: - r = new NullLiteralNode(tree); - break; - case STRING_LITERAL: - r = new StringLiteralNode(tree); - break; - default: - throw new BugInCF("unexpected literal tree: " + tree); - } - assert r != null : "unexpected literal tree"; - extendWithNode(r); - return r; + extendWithExtendedNode(new UnconditionalJump(conditionStart)); + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetLC = oldBreakTargetLC; + continueTargetLC = oldContinueTargetLC; + + return null; + } + + @Override + public Node visitIdentifier(IdentifierTree tree, Void p) { + Node node; + if (TreeUtils.isFieldAccess(tree)) { + Node receiver = getReceiver(tree); + node = new FieldAccessNode(tree, receiver); + } else { + Element element = TreeUtils.elementFromUse(tree); + switch (element.getKind()) { + case FIELD: + // Note that "this"/"super" is a field, but not a field access. + if (element.getSimpleName().contentEquals("this")) { + node = new ExplicitThisNode(tree); + } else { + node = new SuperNode(tree); + } + break; + case EXCEPTION_PARAMETER: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case PARAMETER: + node = new LocalVariableNode(tree); + break; + case PACKAGE: + node = new PackageNameNode(tree); + break; + default: + if (ElementUtils.isTypeDeclaration(element)) { + node = new ClassNameNode(tree); + break; + } else if (ElementUtils.isBindingVariable(element)) { + // Note: BINDING_VARIABLE should be added as a direct case above when + // instanceof pattern matching and Java15 are supported. + node = new LocalVariableNode(tree); + break; + } + throw new BugInCF("bad element kind " + element.getKind()); + } } - - @Override - public Node visitMethod(MethodTree tree, Void p) { - throw new BugInCF("MethodTree is unexpected in AST to CFG translation"); + if (node instanceof ClassNameNode) { + extendWithClassNameNode((ClassNameNode) node); + } else { + extendWithNode(node); } - - @Override - public Node visitModifiers(ModifiersTree tree, Void p) { - throw new BugInCF("ModifiersTree is unexpected in AST to CFG translation"); + return node; + } + + @Override + public Node visitIf(IfTree tree, Void p) { + // all necessary labels + Label thenEntry = new Label(); + Label elseEntry = new Label(); + Label endIf = new Label(); + + // basic block for the condition + unbox(scan(tree.getCondition(), p)); + + ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry); + extendWithExtendedNode(cjump); + + // then branch + addLabelForNextNode(thenEntry); + StatementTree thenStatement = tree.getThenStatement(); + scan(thenStatement, p); + extendWithExtendedNode(new UnconditionalJump(endIf)); + + // else branch + addLabelForNextNode(elseEntry); + StatementTree elseStatement = tree.getElseStatement(); + if (elseStatement != null) { + scan(elseStatement, p); } - @Override - public Node visitNewArray(NewArrayTree tree, Void p) { - // see JLS 15.10 - - ArrayType type = (ArrayType) TreeUtils.typeOf(tree); - TypeMirror elemType = type.getComponentType(); - - List dimensions = tree.getDimensions(); - List initializers = tree.getInitializers(); - assert dimensions != null; - - List dimensionNodes = - CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions); - - List initializerNodes; - if (initializers == null) { - initializerNodes = Collections.emptyList(); - } else { - initializerNodes = - CollectionsPlume.mapList( - init -> assignConvert(scan(init, p), elemType), initializers); - } - - Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes); - - extendWithNodeWithExceptions(node, newArrayExceptionTypes); - return node; + // label the end of the if statement + addLabelForNextNode(endIf); + + return null; + } + + @Override + public Node visitImport(ImportTree tree, Void p) { + throw new BugInCF("ImportTree is unexpected in AST to CFG translation: " + tree); + } + + @Override + public Node visitArrayAccess(ArrayAccessTree tree, Void p) { + Node array = scan(tree.getExpression(), p); + Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); + Node arrayAccess = new ArrayAccessNode(tree, array, index); + extendWithNode(arrayAccess); + extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType); + extendWithNodeWithException(arrayAccess, nullPointerExceptionType); + return arrayAccess; + } + + @Override + public Node visitLabeledStatement(LabeledStatementTree tree, Void p) { + // This method can set the break target after generating all Nodes in the contained + // statement, but it can't set the continue target, which may be in the middle of a + // sequence of nodes. Labeled loops must look up and use the continue Labels. + Name labelName = tree.getLabel(); + + Label breakLabel = new Label(labelName + "_break"); + Label continueLabel = new Label(labelName + "_continue"); + + breakLabels.put(labelName, breakLabel); + continueLabels.put(labelName, continueLabel); + + scan(tree.getStatement(), p); + + addLabelForNextNode(breakLabel); + + breakLabels.remove(labelName); + continueLabels.remove(labelName); + + return null; + } + + @Override + public Node visitLiteral(LiteralTree tree, Void p) { + Node r = null; + switch (tree.getKind()) { + case BOOLEAN_LITERAL: + r = new BooleanLiteralNode(tree); + break; + case CHAR_LITERAL: + r = new CharacterLiteralNode(tree); + break; + case DOUBLE_LITERAL: + r = new DoubleLiteralNode(tree); + break; + case FLOAT_LITERAL: + r = new FloatLiteralNode(tree); + break; + case INT_LITERAL: + r = new IntegerLiteralNode(tree); + break; + case LONG_LITERAL: + r = new LongLiteralNode(tree); + break; + case NULL_LITERAL: + r = new NullLiteralNode(tree); + break; + case STRING_LITERAL: + r = new StringLiteralNode(tree); + break; + default: + throw new BugInCF("unexpected literal tree: " + tree); } - - @Override - public Node visitNewClass(NewClassTree tree, Void p) { - // see JLS 15.9 - - DeclaredType classType = (DeclaredType) TreeUtils.typeOf(tree); - TypeMirror enclosingType = classType.getEnclosingType(); - Tree enclosingExpr = tree.getEnclosingExpression(); - Node enclosingExprNode; - if (enclosingExpr != null) { - enclosingExprNode = scan(enclosingExpr, p); - } else if (enclosingType.getKind() == TypeKind.DECLARED) { - // This is an inner class (instance nested class). - // As there is no explicit enclosing expression, create a node for the implicit this - // argument. - enclosingExprNode = new ImplicitThisNode(enclosingType); - extendWithNode(enclosingExprNode); - } else { - // For static nested classes, the kind would be Typekind.None. - - enclosingExprNode = null; - } - - // Convert constructor arguments - ExecutableElement constructor = TreeUtils.elementFromUse(tree); - - List actualExprs = tree.getArguments(); - - List arguments = - convertCallArguments( - tree, constructor, TreeUtils.typeFromUse(tree), actualExprs, tree); - - // TODO: for anonymous classes, don't use the identifier alone. - // See https://github.com/typetools/checker-framework/issues/890 . - Node constructorNode = scan(tree.getIdentifier(), p); - - // Handle anonymous classes in visitClass. - // Note that getClassBody() and therefore classbody can be null. - ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p); - - Node node = - new ObjectCreationNode( - tree, enclosingExprNode, constructorNode, arguments, classbody); - List thrownTypes = constructor.getThrownTypes(); - Set thrownSet = - ArraySet.newArraySetOrLinkedHashSet( - thrownTypes.size() + uncheckedExceptionTypes.size()); - // Add exceptions explicitly mentioned in the throws clause. - thrownSet.addAll(thrownTypes); - // Add types to account for unchecked exceptions - thrownSet.addAll(uncheckedExceptionTypes); - - extendWithNodeWithExceptions(node, thrownSet); - - return node; + assert r != null : "unexpected literal tree"; + extendWithNode(r); + return r; + } + + @Override + public Node visitMethod(MethodTree tree, Void p) { + throw new BugInCF("MethodTree is unexpected in AST to CFG translation"); + } + + @Override + public Node visitModifiers(ModifiersTree tree, Void p) { + throw new BugInCF("ModifiersTree is unexpected in AST to CFG translation"); + } + + @Override + public Node visitNewArray(NewArrayTree tree, Void p) { + // see JLS 15.10 + + ArrayType type = (ArrayType) TreeUtils.typeOf(tree); + TypeMirror elemType = type.getComponentType(); + + List dimensions = tree.getDimensions(); + List initializers = tree.getInitializers(); + assert dimensions != null; + + List dimensionNodes = + CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions); + + List initializerNodes; + if (initializers == null) { + initializerNodes = Collections.emptyList(); + } else { + initializerNodes = + CollectionsPlume.mapList(init -> assignConvert(scan(init, p), elemType), initializers); } - /** - * Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists. - * - *

This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a - * {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This - * map is necessary because dataflow does not create a {@code Node} for a {@code - * ParenthesizedTree}. - */ - private final Map parenMapping = new HashMap<>(); - - @Override - public Node visitParenthesized(ParenthesizedTree tree, Void p) { - parenMapping.put(tree.getExpression(), tree); - return scan(tree.getExpression(), p); + Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes); + + extendWithNodeWithExceptions(node, newArrayExceptionTypes); + return node; + } + + @Override + public Node visitNewClass(NewClassTree tree, Void p) { + // see JLS 15.9 + + DeclaredType classType = (DeclaredType) TreeUtils.typeOf(tree); + TypeMirror enclosingType = classType.getEnclosingType(); + Tree enclosingExpr = tree.getEnclosingExpression(); + Node enclosingExprNode; + if (enclosingExpr != null) { + enclosingExprNode = scan(enclosingExpr, p); + } else if (enclosingType.getKind() == TypeKind.DECLARED) { + // This is an inner class (instance nested class). + // As there is no explicit enclosing expression, create a node for the implicit this + // argument. + enclosingExprNode = new ImplicitThisNode(enclosingType); + extendWithNode(enclosingExprNode); + } else { + // For static nested classes, the kind would be Typekind.None. + + enclosingExprNode = null; } - @Override - public Node visitReturn(ReturnTree tree, Void p) { - ExpressionTree ret = tree.getExpression(); - // TODO: also have a return-node if nothing is returned - ReturnNode result = null; - if (ret != null) { - Node node = scan(ret, p); - result = new ReturnNode(tree, node, env.getTypeUtils()); - returnNodes.add(result); - extendWithNode(result); - } - - extendWithExtendedNode(new UnconditionalJump(this.returnTargetLC.accessLabel())); - - return result; + // Convert constructor arguments + ExecutableElement constructor = TreeUtils.elementFromUse(tree); + + List actualExprs = tree.getArguments(); + + List arguments = + convertCallArguments(tree, constructor, TreeUtils.typeFromUse(tree), actualExprs, tree); + + // TODO: for anonymous classes, don't use the identifier alone. + // See https://github.com/typetools/checker-framework/issues/890 . + Node constructorNode = scan(tree.getIdentifier(), p); + + // Handle anonymous classes in visitClass. + // Note that getClassBody() and therefore classbody can be null. + ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p); + + Node node = + new ObjectCreationNode(tree, enclosingExprNode, constructorNode, arguments, classbody); + List thrownTypes = constructor.getThrownTypes(); + Set thrownSet = + ArraySet.newArraySetOrLinkedHashSet(thrownTypes.size() + uncheckedExceptionTypes.size()); + // Add exceptions explicitly mentioned in the throws clause. + thrownSet.addAll(thrownTypes); + // Add types to account for unchecked exceptions + thrownSet.addAll(uncheckedExceptionTypes); + + extendWithNodeWithExceptions(node, thrownSet); + + return node; + } + + /** + * Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists. + * + *

This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a + * {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This + * map is necessary because dataflow does not create a {@code Node} for a {@code + * ParenthesizedTree}. + */ + private final Map parenMapping = new HashMap<>(); + + @Override + public Node visitParenthesized(ParenthesizedTree tree, Void p) { + parenMapping.put(tree.getExpression(), tree); + return scan(tree.getExpression(), p); + } + + @Override + public Node visitReturn(ReturnTree tree, Void p) { + ExpressionTree ret = tree.getExpression(); + // TODO: also have a return-node if nothing is returned + ReturnNode result = null; + if (ret != null) { + Node node = scan(ret, p); + result = new ReturnNode(tree, node, env.getTypeUtils()); + returnNodes.add(result); + extendWithNode(result); } - @Override - public Node visitMemberSelect(MemberSelectTree tree, Void p) { - Node expr = scan(tree.getExpression(), p); - if (!TreeUtils.isFieldAccess(tree)) { - // Could be a selector of a class or package - Element element = TreeUtils.elementFromUse(tree); - if (ElementUtils.isTypeElement(element)) { - ClassNameNode result = new ClassNameNode(tree, expr); - extendWithClassNameNode(result); - return result; - } else if (element.getKind() == ElementKind.PACKAGE) { - Node result = new PackageNameNode(tree, (PackageNameNode) expr); - extendWithNode(result); - return result; - } else { - throw new BugInCF("Unexpected element kind: " + element.getKind()); - } - } - - Node node = new FieldAccessNode(tree, expr); + extendWithExtendedNode(new UnconditionalJump(this.returnTargetLC.accessLabel())); - Element element = TreeUtils.elementFromUse(tree); - if (ElementUtils.isStatic(element) || expr instanceof ThisNode) { - // No NullPointerException can be thrown, use normal node - extendWithNode(node); - } else { - extendWithNodeWithException(node, nullPointerExceptionType); - } - - return node; - } + return result; + } - @Override - public Node visitEmptyStatement(EmptyStatementTree tree, Void p) { - return null; + @Override + public Node visitMemberSelect(MemberSelectTree tree, Void p) { + Node expr = scan(tree.getExpression(), p); + if (!TreeUtils.isFieldAccess(tree)) { + // Could be a selector of a class or package + Element element = TreeUtils.elementFromUse(tree); + if (ElementUtils.isTypeElement(element)) { + ClassNameNode result = new ClassNameNode(tree, expr); + extendWithClassNameNode(result); + return result; + } else if (element.getKind() == ElementKind.PACKAGE) { + Node result = new PackageNameNode(tree, (PackageNameNode) expr); + extendWithNode(result); + return result; + } else { + throw new BugInCF("Unexpected element kind: " + element.getKind()); + } } - @Override - public Node visitSynchronized(SynchronizedTree tree, Void p) { - // see JLS 14.19 + Node node = new FieldAccessNode(tree, expr); - Node synchronizedExpr = scan(tree.getExpression(), p); - SynchronizedNode synchronizedStartNode = - new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils()); - extendWithNode(synchronizedStartNode); - scan(tree.getBlock(), p); - SynchronizedNode synchronizedEndNode = - new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils()); - extendWithNode(synchronizedEndNode); - - return null; - } - - @Override - public Node visitThrow(ThrowTree tree, Void p) { - Node expression = scan(tree.getExpression(), p); - TypeMirror exception = expression.getType(); - ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils()); - NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception); - exNode.setTerminatesExecution(true); - return throwsNode; + Element element = TreeUtils.elementFromUse(tree); + if (ElementUtils.isStatic(element) || expr instanceof ThisNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(node); + } else { + extendWithNodeWithException(node, nullPointerExceptionType); } - @Override - public Node visitCompilationUnit(CompilationUnitTree tree, Void p) { - throw new BugInCF("CompilationUnitTree is unexpected in AST to CFG translation"); + return node; + } + + @Override + public Node visitEmptyStatement(EmptyStatementTree tree, Void p) { + return null; + } + + @Override + public Node visitSynchronized(SynchronizedTree tree, Void p) { + // see JLS 14.19 + + Node synchronizedExpr = scan(tree.getExpression(), p); + SynchronizedNode synchronizedStartNode = + new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils()); + extendWithNode(synchronizedStartNode); + scan(tree.getBlock(), p); + SynchronizedNode synchronizedEndNode = + new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils()); + extendWithNode(synchronizedEndNode); + + return null; + } + + @Override + public Node visitThrow(ThrowTree tree, Void p) { + Node expression = scan(tree.getExpression(), p); + TypeMirror exception = expression.getType(); + ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils()); + NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception); + exNode.setTerminatesExecution(true); + return throwsNode; + } + + @Override + public Node visitCompilationUnit(CompilationUnitTree tree, Void p) { + throw new BugInCF("CompilationUnitTree is unexpected in AST to CFG translation"); + } + + /** + * Return the first argument if it is non-null, otherwise return the second argument. Throws an + * exception if both arguments are null. + * + * @param the type of the arguments + * @param first a reference + * @param second a reference + * @return the first argument that is non-null + */ + private static A firstNonNull(A first, A second) { + if (first != null) { + return first; + } else if (second != null) { + return second; + } else { + throw new NullPointerException(); } + } - /** - * Return the first argument if it is non-null, otherwise return the second argument. Throws an - * exception if both arguments are null. - * - * @param the type of the arguments - * @param first a reference - * @param second a reference - * @return the first argument that is non-null - */ - private static A firstNonNull(A first, A second) { - if (first != null) { - return first; - } else if (second != null) { - return second; - } else { - throw new NullPointerException(); - } - } + @Override + public Node visitTry(TryTree tree, Void p) { + List catches = tree.getCatches(); + BlockTree finallyBlock = tree.getFinallyBlock(); - @Override - public Node visitTry(TryTree tree, Void p) { - List catches = tree.getCatches(); - BlockTree finallyBlock = tree.getFinallyBlock(); + extendWithNode( + new MarkerNode( + tree, "start of try statement #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); - extendWithNode( - new MarkerNode( - tree, - "start of try statement #" + TreeUtils.treeUids.get(tree), - env.getTypeUtils())); + List> catchLabels = + CollectionsPlume.mapList( + (CatchTree c) -> IPair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label()), + catches); - List> catchLabels = - CollectionsPlume.mapList( - (CatchTree c) -> - IPair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label()), - catches); + // Store return/break/continue labels, just in case we need them for a finally block. + LabelCell oldReturnTargetLC = returnTargetLC; + LabelCell oldBreakTargetLC = breakTargetLC; + Map oldBreakLabels = breakLabels; + LabelCell oldContinueTargetLC = continueTargetLC; + Map oldContinueLabels = continueLabels; - // Store return/break/continue labels, just in case we need them for a finally block. - LabelCell oldReturnTargetLC = returnTargetLC; - LabelCell oldBreakTargetLC = breakTargetLC; - Map oldBreakLabels = breakLabels; - LabelCell oldContinueTargetLC = continueTargetLC; - Map oldContinueLabels = continueLabels; + Label finallyLabel = null; + Label exceptionalFinallyLabel = null; - Label finallyLabel = null; - Label exceptionalFinallyLabel = null; + if (finallyBlock != null) { + finallyLabel = new Label(); - if (finallyBlock != null) { - finallyLabel = new Label(); + exceptionalFinallyLabel = new Label(); + tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); - exceptionalFinallyLabel = new Label(); - tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); + returnTargetLC = new LabelCell(); - returnTargetLC = new LabelCell(); + breakTargetLC = new LabelCell(); + breakLabels = new TryFinallyScopeMap(); - breakTargetLC = new LabelCell(); - breakLabels = new TryFinallyScopeMap(); + continueTargetLC = new LabelCell(); + continueLabels = new TryFinallyScopeMap(); + } - continueTargetLC = new LabelCell(); - continueLabels = new TryFinallyScopeMap(); - } + Label doneLabel = new Label(); - Label doneLabel = new Label(); + tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); - tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); + extendWithNode( + new MarkerNode( + tree, "start of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); - extendWithNode( - new MarkerNode( - tree, - "start of try block #" + TreeUtils.treeUids.get(tree), - env.getTypeUtils())); + handleTryResourcesAndBlock(tree, p, tree.getResources()); - handleTryResourcesAndBlock(tree, p, tree.getResources()); + extendWithNode( + new MarkerNode( + tree, "end of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); - extendWithNode( - new MarkerNode( - tree, - "end of try block #" + TreeUtils.treeUids.get(tree), - env.getTypeUtils())); - - extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); - - // This pops the try-catch frame - tryStack.popFrame(); - - int catchIndex = 0; - for (CatchTree c : catches) { - addLabelForNextNode(catchLabels.get(catchIndex).second); - TypeMirror catchType = TreeUtils.typeOf(c.getParameter().getType()); - extendWithNode(new CatchMarkerNode(tree, "start", catchType, env.getTypeUtils())); - scan(c, p); - extendWithNode(new CatchMarkerNode(tree, "end", catchType, env.getTypeUtils())); - - catchIndex++; - extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); - } + extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); - if (finallyLabel != null) { - handleFinally( - tree, - doneLabel, - finallyLabel, - exceptionalFinallyLabel, - () -> scan(finallyBlock, p), - oldReturnTargetLC, - oldBreakTargetLC, - oldBreakLabels, - oldContinueTargetLC, - oldContinueLabels); - } + // This pops the try-catch frame + tryStack.popFrame(); - addLabelForNextNode(doneLabel); + int catchIndex = 0; + for (CatchTree c : catches) { + addLabelForNextNode(catchLabels.get(catchIndex).second); + TypeMirror catchType = TreeUtils.typeOf(c.getParameter().getType()); + extendWithNode(new CatchMarkerNode(tree, "start", catchType, env.getTypeUtils())); + scan(c, p); + extendWithNode(new CatchMarkerNode(tree, "end", catchType, env.getTypeUtils())); - return null; + catchIndex++; + extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); } - /** - * A recursive helper method to handle the resource declarations (if any) in a {@link TryTree} - * and its main block. If the {@code resources} list is empty, the method scans the main block - * of the try statement and returns. Otherwise, the first resource declaration in {@code - * resources} is desugared, following the logic in JLS 14.20.3.1. A resource declaration - * r is desugared by adding the nodes for r itself to the CFG, followed by a - * synthetic nested {@code try} block and {@code finally} block. The synthetic {@code try} block - * contains any remaining resource declarations and the original try block (handled via - * recursion). The synthetic {@code finally} block contains a call to {@code close} for - * r, guaranteeing that on every path through the CFG, r is closed. - * - * @param tryTree the original try tree (with 0 or more resources) from the AST - * @param p the value to pass to calls to {@code scan} - * @param resources the remaining resource declarations to handle - */ - private void handleTryResourcesAndBlock( - TryTree tryTree, Void p, List resources) { - if (resources.isEmpty()) { - // Either `tryTree` was not a try-with-resources, or this method was called recursively - // and all the resources have been handled. Just scan the main try block. - scan(tryTree.getBlock(), p); - return; - } - - // Handle the first resource declaration in the list. The rest will be handled by a - // recursive call. - Tree resourceDeclarationTree = resources.get(0); - - extendWithNode( - new MarkerNode( - resourceDeclarationTree, - "start of try for resource #" - + TreeUtils.treeUids.get(resourceDeclarationTree), - env.getTypeUtils())); - - // Store return/break/continue labels. Generating a synthetic finally block for closing the - // resource requires creating fresh return/break/continue labels and then restoring the old - // labels afterward. - LabelCell oldReturnTargetLC = returnTargetLC; - LabelCell oldBreakTargetLC = breakTargetLC; - Map oldBreakLabels = breakLabels; - LabelCell oldContinueTargetLC = continueTargetLC; - Map oldContinueLabels = continueLabels; - - // Add nodes for the resource declaration to the CFG. NOTE: it is critical to add these - // nodes *before* pushing a TryFinallyFrame for the finally block that will close the - // resource. - // If any exception occurs due to code within the resource declaration, the corresponding - // variable or field is *not* automatically closed (as it was never assigned a value). - Node resourceCloseNode = scan(resourceDeclarationTree, p); - - // Now, set things up for our synthetic finally block that closes the resource. - Label doneLabel = new Label(); - Label finallyLabel = new Label(); - - Label exceptionalFinallyLabel = new Label(); - tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); - - returnTargetLC = new LabelCell(); - - breakTargetLC = new LabelCell(); - breakLabels = new TryFinallyScopeMap(); - - continueTargetLC = new LabelCell(); - continueLabels = new TryFinallyScopeMap(); - - extendWithNode( - new MarkerNode( - resourceDeclarationTree, - "start of try block for resource #" - + TreeUtils.treeUids.get(resourceDeclarationTree), - env.getTypeUtils())); - // Recursively handle any remaining resource declarations and the main block of the try - handleTryResourcesAndBlock(tryTree, p, resources.subList(1, resources.size())); - extendWithNode( - new MarkerNode( - resourceDeclarationTree, - "end of try block for resource #" - + TreeUtils.treeUids.get(resourceDeclarationTree), - env.getTypeUtils())); - - extendWithExtendedNode(new UnconditionalJump(finallyLabel)); - - // Generate the finally block that closes the resource - handleFinally( - resourceDeclarationTree, - doneLabel, - finallyLabel, - exceptionalFinallyLabel, - () -> addCloseCallForResource(resourceDeclarationTree, resourceCloseNode), - oldReturnTargetLC, - oldBreakTargetLC, - oldBreakLabels, - oldContinueTargetLC, - oldContinueLabels); - - addLabelForNextNode(doneLabel); + if (finallyLabel != null) { + handleFinally( + tree, + doneLabel, + finallyLabel, + exceptionalFinallyLabel, + () -> scan(finallyBlock, p), + oldReturnTargetLC, + oldBreakTargetLC, + oldBreakLabels, + oldContinueTargetLC, + oldContinueLabels); } - /** - * Adds a synthetic {@code close} call to the CFG to close some resource variable declared or - * used in a try-with-resources. - * - * @param resourceDeclarationTree the resource declaration - * @param resourceToCloseNode node represented the variable or field on which {@code close} - * should be invoked - */ - private void addCloseCallForResource(Tree resourceDeclarationTree, Node resourceToCloseNode) { - Tree receiverTree = resourceDeclarationTree; - if (receiverTree instanceof VariableTree) { - receiverTree = treeBuilder.buildVariableUse((VariableTree) receiverTree); - handleArtificialTree(receiverTree); - } - - MemberSelectTree closeSelect = - treeBuilder.buildCloseMethodAccess((ExpressionTree) receiverTree); - handleArtificialTree(closeSelect); - - MethodInvocationTree closeCall = treeBuilder.buildMethodInvocation(closeSelect); - handleArtificialTree(closeCall); - - Node receiverNode = resourceToCloseNode; - if (receiverNode instanceof AssignmentNode) { - // variable declaration; use the LHS - receiverNode = ((AssignmentNode) resourceToCloseNode).getTarget(); - } - // TODO do we need to insert some kind of node representing a use of receiverNode - // (which can be either a LocalVariableNode or a FieldAccessNode)? - MethodAccessNode closeAccessNode = new MethodAccessNode(closeSelect, receiverNode); - closeAccessNode.setInSource(false); - extendWithNode(closeAccessNode); - MethodInvocationNode closeCallNode = - new MethodInvocationNode( - closeCall, closeAccessNode, Collections.emptyList(), getCurrentPath()); - closeCallNode.setInSource(false); - extendWithMethodInvocationNode(TreeUtils.elementFromUse(closeCall), closeCallNode); + addLabelForNextNode(doneLabel); + + return null; + } + + /** + * A recursive helper method to handle the resource declarations (if any) in a {@link TryTree} and + * its main block. If the {@code resources} list is empty, the method scans the main block of the + * try statement and returns. Otherwise, the first resource declaration in {@code resources} is + * desugared, following the logic in JLS 14.20.3.1. A resource declaration r is desugared + * by adding the nodes for r itself to the CFG, followed by a synthetic nested {@code try} + * block and {@code finally} block. The synthetic {@code try} block contains any remaining + * resource declarations and the original try block (handled via recursion). The synthetic {@code + * finally} block contains a call to {@code close} for r, guaranteeing that on every path + * through the CFG, r is closed. + * + * @param tryTree the original try tree (with 0 or more resources) from the AST + * @param p the value to pass to calls to {@code scan} + * @param resources the remaining resource declarations to handle + */ + private void handleTryResourcesAndBlock(TryTree tryTree, Void p, List resources) { + if (resources.isEmpty()) { + // Either `tryTree` was not a try-with-resources, or this method was called recursively + // and all the resources have been handled. Just scan the main try block. + scan(tryTree.getBlock(), p); + return; } - /** - * Shared logic for CFG generation for a finally block. The block may correspond to a {@link - * TryTree} originally in the source code, or it may be a synthetic finally block used to model - * closing of a resource due to try-with-resources. - * - * @param markerTree tree to reference when creating {@link MarkerNode}s for the finally block - * @param doneLabel label for the normal successor of the try block (no exceptions, returns, - * breaks, or continues) - * @param finallyLabel label for the entry of the finally block for the normal case - * @param exceptionalFinallyLabel label for entry of the finally block for when the try block - * throws an exception - * @param finallyBlockCFGGenerator generates CFG nodes and edges for the finally block - * @param oldReturnTargetLC old return target label cell, which gets restored to {@link - * #returnTargetLC} while handling the finally block - * @param oldBreakTargetLC old break target label cell, which gets restored to {@link - * #breakTargetLC} while handling the finally block - * @param oldBreakLabels old break labels, which get restored to {@link #breakLabels} while - * handling the finally block - * @param oldContinueTargetLC old continue target label cell, which gets restored to {@link - * #continueTargetLC} while handling the finally block - * @param oldContinueLabels old continue labels, which get restored to {@link #continueLabels} - * while handling the finally block - */ - private void handleFinally( - Tree markerTree, - Label doneLabel, - Label finallyLabel, - Label exceptionalFinallyLabel, - Runnable finallyBlockCFGGenerator, - LabelCell oldReturnTargetLC, - LabelCell oldBreakTargetLC, - Map oldBreakLabels, - LabelCell oldContinueTargetLC, - Map oldContinueLabels) { - // Reset values before analyzing the finally block! - - tryStack.popFrame(); - - { // Scan 'finallyBlock' for only 'finallyLabel' (a successful path) - addLabelForNextNode(finallyLabel); - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block #" + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - finallyBlockCFGGenerator.run(); - extendWithNode( - new MarkerNode( - markerTree, - "end of finally block #" + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - extendWithExtendedNode(new UnconditionalJump(doneLabel)); - } - - if (hasExceptionalPath(exceptionalFinallyLabel)) { - // If an exceptional path exists, scan 'finallyBlock' for 'exceptionalFinallyLabel', - // and scan copied 'finallyBlock' for 'finallyLabel' (a successful path). If there - // is no successful path, it will be removed in later phase. - // TODO: Don't we need a separate finally block for each kind of exception? - addLabelForNextNode(exceptionalFinallyLabel); - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block for Throwable #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - - finallyBlockCFGGenerator.run(); - - NodeWithExceptionsHolder throwing = - extendWithNodeWithException( - new MarkerNode( - markerTree, - "end of finally block for Throwable #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils()), - throwableType); - - throwing.setTerminatesExecution(true); - } - - if (returnTargetLC.wasAccessed()) { - addLabelForNextNode(returnTargetLC.peekLabel()); - returnTargetLC = oldReturnTargetLC; - - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block for return #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - finallyBlockCFGGenerator.run(); - extendWithNode( - new MarkerNode( - markerTree, - "end of finally block for return #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - extendWithExtendedNode(new UnconditionalJump(returnTargetLC.accessLabel())); - } else { - returnTargetLC = oldReturnTargetLC; - } - - if (breakTargetLC.wasAccessed()) { - addLabelForNextNode(breakTargetLC.peekLabel()); - breakTargetLC = oldBreakTargetLC; - - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block for break #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - finallyBlockCFGGenerator.run(); - extendWithNode( - new MarkerNode( - markerTree, - "end of finally block for break #" + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); - } else { - breakTargetLC = oldBreakTargetLC; - } + // Handle the first resource declaration in the list. The rest will be handled by a + // recursive call. + Tree resourceDeclarationTree = resources.get(0); + + extendWithNode( + new MarkerNode( + resourceDeclarationTree, + "start of try for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), + env.getTypeUtils())); + + // Store return/break/continue labels. Generating a synthetic finally block for closing the + // resource requires creating fresh return/break/continue labels and then restoring the old + // labels afterward. + LabelCell oldReturnTargetLC = returnTargetLC; + LabelCell oldBreakTargetLC = breakTargetLC; + Map oldBreakLabels = breakLabels; + LabelCell oldContinueTargetLC = continueTargetLC; + Map oldContinueLabels = continueLabels; + + // Add nodes for the resource declaration to the CFG. NOTE: it is critical to add these + // nodes *before* pushing a TryFinallyFrame for the finally block that will close the + // resource. + // If any exception occurs due to code within the resource declaration, the corresponding + // variable or field is *not* automatically closed (as it was never assigned a value). + Node resourceCloseNode = scan(resourceDeclarationTree, p); + + // Now, set things up for our synthetic finally block that closes the resource. + Label doneLabel = new Label(); + Label finallyLabel = new Label(); + + Label exceptionalFinallyLabel = new Label(); + tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); + + returnTargetLC = new LabelCell(); + + breakTargetLC = new LabelCell(); + breakLabels = new TryFinallyScopeMap(); + + continueTargetLC = new LabelCell(); + continueLabels = new TryFinallyScopeMap(); + + extendWithNode( + new MarkerNode( + resourceDeclarationTree, + "start of try block for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), + env.getTypeUtils())); + // Recursively handle any remaining resource declarations and the main block of the try + handleTryResourcesAndBlock(tryTree, p, resources.subList(1, resources.size())); + extendWithNode( + new MarkerNode( + resourceDeclarationTree, + "end of try block for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), + env.getTypeUtils())); + + extendWithExtendedNode(new UnconditionalJump(finallyLabel)); + + // Generate the finally block that closes the resource + handleFinally( + resourceDeclarationTree, + doneLabel, + finallyLabel, + exceptionalFinallyLabel, + () -> addCloseCallForResource(resourceDeclarationTree, resourceCloseNode), + oldReturnTargetLC, + oldBreakTargetLC, + oldBreakLabels, + oldContinueTargetLC, + oldContinueLabels); + + addLabelForNextNode(doneLabel); + } + + /** + * Adds a synthetic {@code close} call to the CFG to close some resource variable declared or used + * in a try-with-resources. + * + * @param resourceDeclarationTree the resource declaration + * @param resourceToCloseNode node represented the variable or field on which {@code close} should + * be invoked + */ + private void addCloseCallForResource(Tree resourceDeclarationTree, Node resourceToCloseNode) { + Tree receiverTree = resourceDeclarationTree; + if (receiverTree instanceof VariableTree) { + receiverTree = treeBuilder.buildVariableUse((VariableTree) receiverTree); + handleArtificialTree(receiverTree); + } - Map accessedBreakLabels = - ((TryFinallyScopeMap) breakLabels).getAccessedNames(); - if (!accessedBreakLabels.isEmpty()) { - breakLabels = oldBreakLabels; - - for (Map.Entry access : accessedBreakLabels.entrySet()) { - addLabelForNextNode(access.getValue()); - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block for break label " - + access.getKey() - + " #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - finallyBlockCFGGenerator.run(); - extendWithNode( - new MarkerNode( - markerTree, - "end of finally block for break label " - + access.getKey() - + " #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - extendWithExtendedNode(new UnconditionalJump(breakLabels.get(access.getKey()))); - } - } else { - breakLabels = oldBreakLabels; - } + MemberSelectTree closeSelect = + treeBuilder.buildCloseMethodAccess((ExpressionTree) receiverTree); + handleArtificialTree(closeSelect); - if (continueTargetLC.wasAccessed()) { - addLabelForNextNode(continueTargetLC.peekLabel()); - continueTargetLC = oldContinueTargetLC; - - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block for continue #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - finallyBlockCFGGenerator.run(); - extendWithNode( - new MarkerNode( - markerTree, - "end of finally block for continue #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); - } else { - continueTargetLC = oldContinueTargetLC; - } + MethodInvocationTree closeCall = treeBuilder.buildMethodInvocation(closeSelect); + handleArtificialTree(closeCall); - Map accessedContinueLabels = - ((TryFinallyScopeMap) continueLabels).getAccessedNames(); - if (!accessedContinueLabels.isEmpty()) { - continueLabels = oldContinueLabels; - - for (Map.Entry access : accessedContinueLabels.entrySet()) { - addLabelForNextNode(access.getValue()); - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block for continue label " - + access.getKey() - + " #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - finallyBlockCFGGenerator.run(); - extendWithNode( - new MarkerNode( - markerTree, - "end of finally block for continue label " - + access.getKey() - + " #" - + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - extendWithExtendedNode(new UnconditionalJump(continueLabels.get(access.getKey()))); - } - } else { - continueLabels = oldContinueLabels; - } + Node receiverNode = resourceToCloseNode; + if (receiverNode instanceof AssignmentNode) { + // variable declaration; use the LHS + receiverNode = ((AssignmentNode) resourceToCloseNode).getTarget(); } - - /** - * Returns whether an exceptional node for {@code target} exists in {@link #nodeList} or not. - * - * @param target label for exception - * @return true when an exceptional node for {@code target} exists in {@link #nodeList} - */ - private boolean hasExceptionalPath(Label target) { - for (ExtendedNode node : nodeList) { - if (node instanceof NodeWithExceptionsHolder) { - NodeWithExceptionsHolder exceptionalNode = (NodeWithExceptionsHolder) node; - for (Set

This can be used to handle system types that are not present. For example, in Java code - * that is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are - * emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present. - * - * @param clazz a class, which must have a canonical name - * @return the TypeMirror for the class, or {@code null} if the type is not present - */ - protected @Nullable TypeMirror maybeGetTypeMirror(Class clazz) { - String name = clazz.getCanonicalName(); - assert name != null : clazz + " does not have a canonical name"; - TypeElement element = elements.getTypeElement(name); - if (element == null) { - return null; - } - return element.asType(); + Node node = new FunctionalInterfaceNode(tree); + extendWithNode(node); + + return node; + } + + @Override + public Node visitWildcard(WildcardTree tree, Void p) { + throw new BugInCF("WildcardTree is unexpected in AST to CFG translation"); + } + + @Override + public Node visitOther(Tree tree, Void p) { + throw new BugInCF("Unknown AST element encountered in AST to CFG translation."); + } + + /** + * Returns the TypeMirror for the given class. + * + * @param clazz a class + * @return the TypeMirror for the class + */ + protected TypeMirror getTypeMirror(Class clazz) { + return TypesUtils.typeFromClass(clazz, types, elements); + } + + /** + * Returns the TypeMirror for the given class, or {@code null} if the type is not present. + * + *

This can be used to handle system types that are not present. For example, in Java code that + * is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are + * emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present. + * + * @param clazz a class, which must have a canonical name + * @return the TypeMirror for the class, or {@code null} if the type is not present + */ + protected @Nullable TypeMirror maybeGetTypeMirror(Class clazz) { + String name = clazz.getCanonicalName(); + assert name != null : clazz + " does not have a canonical name"; + TypeElement element = elements.getTypeElement(name); + if (element == null) { + return null; } + return element.asType(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java index f874279870e..9fc0616e865 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java @@ -1,5 +1,10 @@ package org.checkerframework.dataflow.cfg.builder; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import javax.lang.model.type.TypeMirror; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.block.Block; import org.checkerframework.dataflow.cfg.block.Block.BlockType; @@ -10,13 +15,6 @@ import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl; import org.checkerframework.javacutil.BugInCF; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; - /** * Class that performs phase three of the translation process. In particular, the following * degenerate cases of basic blocks are removed: @@ -38,345 +36,343 @@ */ public class CFGTranslationPhaseThree { - /** A simple wrapper object that holds a basic block and allows to set one of its successors. */ - protected interface PredecessorHolder { - void setSuccessor(BlockImpl b); + /** A simple wrapper object that holds a basic block and allows to set one of its successors. */ + protected interface PredecessorHolder { + void setSuccessor(BlockImpl b); - BlockImpl getBlock(); - } + BlockImpl getBlock(); + } - /** - * Perform phase three on the control flow graph {@code cfg}. - * - * @param cfg the control flow graph. Ownership is transfered to this method and the caller is - * not allowed to read or modify {@code cfg} after the call to {@code process} any more. - * @return the resulting control flow graph - */ - @SuppressWarnings("nullness") // TODO: successors - public static ControlFlowGraph process(ControlFlowGraph cfg) { - Set worklist = cfg.getAllBlocks(); + /** + * Perform phase three on the control flow graph {@code cfg}. + * + * @param cfg the control flow graph. Ownership is transfered to this method and the caller is not + * allowed to read or modify {@code cfg} after the call to {@code process} any more. + * @return the resulting control flow graph + */ + @SuppressWarnings("nullness") // TODO: successors + public static ControlFlowGraph process(ControlFlowGraph cfg) { + Set worklist = cfg.getAllBlocks(); - // note: this method has to be careful when relinking basic blocks - // to not forget to adjust the predecessors, too + // note: this method has to be careful when relinking basic blocks + // to not forget to adjust the predecessors, too - // fix predecessor lists by removing any unreachable predecessors - for (Block c : worklist) { - BlockImpl cur = (BlockImpl) c; - for (Block pred : cur.getPredecessors()) { - if (!worklist.contains(pred)) { - cur.removePredecessor((BlockImpl) pred); - } - } + // fix predecessor lists by removing any unreachable predecessors + for (Block c : worklist) { + BlockImpl cur = (BlockImpl) c; + for (Block pred : cur.getPredecessors()) { + if (!worklist.contains(pred)) { + cur.removePredecessor((BlockImpl) pred); } + } + } - // remove empty blocks - Set dontVisit = new HashSet<>(); - for (Block cur : worklist) { - if (dontVisit.contains(cur)) { - continue; - } + // remove empty blocks + Set dontVisit = new HashSet<>(); + for (Block cur : worklist) { + if (dontVisit.contains(cur)) { + continue; + } - if (cur.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl b = (RegularBlockImpl) cur; - if (b.isEmpty()) { - Set emptyBlocks = new HashSet<>(); - Set predecessors = new LinkedHashSet<>(); - BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, emptyBlocks, predecessors); - for (RegularBlockImpl e : emptyBlocks) { - succ.removePredecessor(e); - dontVisit.add(e); - } - for (PredecessorHolder p : predecessors) { - BlockImpl block = p.getBlock(); - dontVisit.add(block); - succ.removePredecessor(block); - p.setSuccessor(succ); - } - } - } + if (cur.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl b = (RegularBlockImpl) cur; + if (b.isEmpty()) { + Set emptyBlocks = new HashSet<>(); + Set predecessors = new LinkedHashSet<>(); + BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, emptyBlocks, predecessors); + for (RegularBlockImpl e : emptyBlocks) { + succ.removePredecessor(e); + dontVisit.add(e); + } + for (PredecessorHolder p : predecessors) { + BlockImpl block = p.getBlock(); + dontVisit.add(block); + succ.removePredecessor(block); + p.setSuccessor(succ); + } } + } + } - // remove useless conditional blocks - /* Issue 3267 revealed that this is a dangerous optimization: - it merges a block that evaluates one condition onto an unrelated following block, - which can also be a condition. The then/else stores from the first block are still - set, leading to incorrect results for the then/else stores in the following block. - The correct result would be to merge the then/else stores from the previous block. - However, as this is late in the CFG construction, I didn't see how to add e.g. a - dummy variable declaration node in a dummy regular block, which would cause a merge. - So for now, let's not perform this optimization. - It would be interesting to know how large the impact of this optimization is. + // remove useless conditional blocks + /* Issue 3267 revealed that this is a dangerous optimization: + it merges a block that evaluates one condition onto an unrelated following block, + which can also be a condition. The then/else stores from the first block are still + set, leading to incorrect results for the then/else stores in the following block. + The correct result would be to merge the then/else stores from the previous block. + However, as this is late in the CFG construction, I didn't see how to add e.g. a + dummy variable declaration node in a dummy regular block, which would cause a merge. + So for now, let's not perform this optimization. + It would be interesting to know how large the impact of this optimization is. - worklist = cfg.getAllBlocks(); - for (Block c : worklist) { - BlockImpl cur = (BlockImpl) c; + worklist = cfg.getAllBlocks(); + for (Block c : worklist) { + BlockImpl cur = (BlockImpl) c; - if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { - ConditionalBlockImpl cb = (ConditionalBlockImpl) cur; - assert cb.getPredecessors().size() == 1; - if (cb.getThenSuccessor() == cb.getElseSuccessor()) { - BlockImpl pred = cb.getPredecessors().iterator().next(); - PredecessorHolder predecessorHolder = getPredecessorHolder(pred, cb); - BlockImpl succ = (BlockImpl) cb.getThenSuccessor(); - succ.removePredecessor(cb); - predecessorHolder.setSuccessor(succ); - } + if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { + ConditionalBlockImpl cb = (ConditionalBlockImpl) cur; + assert cb.getPredecessors().size() == 1; + if (cb.getThenSuccessor() == cb.getElseSuccessor()) { + BlockImpl pred = cb.getPredecessors().iterator().next(); + PredecessorHolder predecessorHolder = getPredecessorHolder(pred, cb); + BlockImpl succ = (BlockImpl) cb.getThenSuccessor(); + succ.removePredecessor(cb); + predecessorHolder.setSuccessor(succ); } } - */ - - mergeConsecutiveBlocks(cfg); - return cfg; } + */ - /** - * Simplify the CFG by merging consecutive single-successor blocks. - * - * @param cfg the control flow graph - */ - @SuppressWarnings({ - "interning:not.interned", // CFG node comparisons - "nullness" // TODO: successors - }) - protected static void mergeConsecutiveBlocks(ControlFlowGraph cfg) { - Set worklist = cfg.getAllBlocks(); + mergeConsecutiveBlocks(cfg); + return cfg; + } - // This transformation removes blocks from the CFG. If those blocks appear in `worklist` - // then we might visit a block AFTER it has been removed and its nodes have been moved - // somewhere else. When this happens the correct behavior is to just skip the removed - // block; to do so, we need to remember which blocks have been removed. - Set removedBlocks = new HashSet<>(); + /** + * Simplify the CFG by merging consecutive single-successor blocks. + * + * @param cfg the control flow graph + */ + @SuppressWarnings({ + "interning:not.interned", // CFG node comparisons + "nullness" // TODO: successors + }) + protected static void mergeConsecutiveBlocks(ControlFlowGraph cfg) { + Set worklist = cfg.getAllBlocks(); - for (Block cur : worklist) { - // Skip this block if it was already merged into another. - if (removedBlocks.contains(cur)) { - continue; - } + // This transformation removes blocks from the CFG. If those blocks appear in `worklist` + // then we might visit a block AFTER it has been removed and its nodes have been moved + // somewhere else. When this happens the correct behavior is to just skip the removed + // block; to do so, we need to remember which blocks have been removed. + Set removedBlocks = new HashSet<>(); - // There may be many blocks to merge in series. - // - // ... \ /> ... - // ... --> cur -> b2 -> b3 -> ... - // ... / \> ... - // - // This loop merges the successor into `cur` until it can't do so anymore. - boolean didMerge; - do { - didMerge = false; - if (cur.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl b = (RegularBlockImpl) cur; - Block succ = b.getRegularSuccessor(); - if (succ.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl rs = (RegularBlockImpl) succ; - if (rs.getRegularSuccessor() == rs) { - // An infinite loop, do not try to merge. - break; - } - if (rs.getPredecessors().size() == 1) { - b.setSuccessor(rs.getRegularSuccessor()); - b.addNodes(rs.getNodes()); - rs.getRegularSuccessor().removePredecessor(rs); - removedBlocks.add(rs); - didMerge = true; - } - } - } - } while (didMerge); + for (Block cur : worklist) { + // Skip this block if it was already merged into another. + if (removedBlocks.contains(cur)) { + continue; + } + + // There may be many blocks to merge in series. + // + // ... \ /> ... + // ... --> cur -> b2 -> b3 -> ... + // ... / \> ... + // + // This loop merges the successor into `cur` until it can't do so anymore. + boolean didMerge; + do { + didMerge = false; + if (cur.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl b = (RegularBlockImpl) cur; + Block succ = b.getRegularSuccessor(); + if (succ.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl rs = (RegularBlockImpl) succ; + if (rs.getRegularSuccessor() == rs) { + // An infinite loop, do not try to merge. + break; + } + if (rs.getPredecessors().size() == 1) { + b.setSuccessor(rs.getRegularSuccessor()); + b.addNodes(rs.getNodes()); + rs.getRegularSuccessor().removePredecessor(rs); + removedBlocks.add(rs); + didMerge = true; + } + } } + } while (didMerge); } + } - /** - * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} - * and going both forward and backwards. Furthermore, compute the predecessors of these empty - * blocks ({@code predecessors} ), and their single successor (return value). - * - * @param start the starting point of the search (an empty, regular basic block) - * @param emptyBlocks a set to be filled by this method with all empty basic blocks found - * (including {@code start}). - * @param predecessors a set to be filled by this method with all predecessors - * @return the single successor of the set of the empty basic blocks - */ - @SuppressWarnings({ - "interning:not.interned", // CFG node comparisons - "nullness" // successors - }) - protected static BlockImpl computeNeighborhoodOfEmptyBlock( - RegularBlockImpl start, - Set emptyBlocks, - Set predecessors) { + /** + * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} + * and going both forward and backwards. Furthermore, compute the predecessors of these empty + * blocks ({@code predecessors} ), and their single successor (return value). + * + * @param start the starting point of the search (an empty, regular basic block) + * @param emptyBlocks a set to be filled by this method with all empty basic blocks found + * (including {@code start}). + * @param predecessors a set to be filled by this method with all predecessors + * @return the single successor of the set of the empty basic blocks + */ + @SuppressWarnings({ + "interning:not.interned", // CFG node comparisons + "nullness" // successors + }) + protected static BlockImpl computeNeighborhoodOfEmptyBlock( + RegularBlockImpl start, + Set emptyBlocks, + Set predecessors) { - // get empty neighborhood that come before 'start' - computeNeighborhoodOfEmptyBlockBackwards(start, emptyBlocks, predecessors); + // get empty neighborhood that come before 'start' + computeNeighborhoodOfEmptyBlockBackwards(start, emptyBlocks, predecessors); - // go forward - BlockImpl succ = (BlockImpl) start.getSuccessor(); - while (succ.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl cur = (RegularBlockImpl) succ; - if (cur.isEmpty()) { - computeNeighborhoodOfEmptyBlockBackwards(cur, emptyBlocks, predecessors); - assert emptyBlocks.contains(cur) : "cur ought to be in emptyBlocks"; - succ = (BlockImpl) cur.getSuccessor(); - if (succ == cur) { - // An infinite loop, making exit block unreachable - break; - } - } else { - break; - } + // go forward + BlockImpl succ = (BlockImpl) start.getSuccessor(); + while (succ.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl cur = (RegularBlockImpl) succ; + if (cur.isEmpty()) { + computeNeighborhoodOfEmptyBlockBackwards(cur, emptyBlocks, predecessors); + assert emptyBlocks.contains(cur) : "cur ought to be in emptyBlocks"; + succ = (BlockImpl) cur.getSuccessor(); + if (succ == cur) { + // An infinite loop, making exit block unreachable + break; } - return succ; + } else { + break; + } } + return succ; + } - /** - * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} - * and looking only backwards in the control flow graph. Furthermore, compute the predecessors - * of these empty blocks ({@code predecessors}). - * - * @param start the starting point of the search (an empty, regular basic block) - * @param emptyBlocks a set to be filled by this method with all empty basic blocks found - * (including {@code start}). - * @param predecessors a set to be filled by this method with all predecessors - */ - protected static void computeNeighborhoodOfEmptyBlockBackwards( - RegularBlockImpl start, - Set emptyBlocks, - Set predecessors) { + /** + * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} + * and looking only backwards in the control flow graph. Furthermore, compute the predecessors of + * these empty blocks ({@code predecessors}). + * + * @param start the starting point of the search (an empty, regular basic block) + * @param emptyBlocks a set to be filled by this method with all empty basic blocks found + * (including {@code start}). + * @param predecessors a set to be filled by this method with all predecessors + */ + protected static void computeNeighborhoodOfEmptyBlockBackwards( + RegularBlockImpl start, + Set emptyBlocks, + Set predecessors) { - RegularBlockImpl cur = start; - emptyBlocks.add(cur); - for (Block p : cur.getPredecessors()) { - BlockImpl pred = (BlockImpl) p; - switch (pred.getType()) { - case SPECIAL_BLOCK: - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - break; - case CONDITIONAL_BLOCK: - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - break; - case EXCEPTION_BLOCK: - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - break; - case REGULAR_BLOCK: - RegularBlockImpl r = (RegularBlockImpl) pred; - if (r.isEmpty()) { - // recursively look backwards - if (!emptyBlocks.contains(r)) { - computeNeighborhoodOfEmptyBlockBackwards(r, emptyBlocks, predecessors); - } - } else { - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - } - break; + RegularBlockImpl cur = start; + emptyBlocks.add(cur); + for (Block p : cur.getPredecessors()) { + BlockImpl pred = (BlockImpl) p; + switch (pred.getType()) { + case SPECIAL_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case CONDITIONAL_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case EXCEPTION_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case REGULAR_BLOCK: + RegularBlockImpl r = (RegularBlockImpl) pred; + if (r.isEmpty()) { + // recursively look backwards + if (!emptyBlocks.contains(r)) { + computeNeighborhoodOfEmptyBlockBackwards(r, emptyBlocks, predecessors); } - } + } else { + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + } + break; + } } + } - /** - * Return a predecessor holder that can be used to set the successor of {@code pred} in the - * place where previously the edge pointed to {@code cur}. Additionally, the predecessor holder - * also takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} - * predecessors). - * - * @param pred a block whose successor should be set - * @param cur the previous successor of {@code pred} - * @return a predecessor holder to set the successor of {@code pred} - */ - @SuppressWarnings("interning:not.interned") // AST node comparisons - protected static PredecessorHolder getPredecessorHolder(BlockImpl pred, BlockImpl cur) { - switch (pred.getType()) { - case SPECIAL_BLOCK: - SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred; - return singleSuccessorHolder(s, cur); - case CONDITIONAL_BLOCK: - // add pred correctly to predecessor list - ConditionalBlockImpl c = (ConditionalBlockImpl) pred; - if (c.getThenSuccessor() == cur) { - return new PredecessorHolder() { - @Override - public void setSuccessor(BlockImpl b) { - c.setThenSuccessor(b); - cur.removePredecessor(pred); - } - - @Override - public BlockImpl getBlock() { - return c; - } - }; - } else { - assert c.getElseSuccessor() == cur; - return new PredecessorHolder() { - @Override - public void setSuccessor(BlockImpl b) { - c.setElseSuccessor(b); - cur.removePredecessor(pred); - } - - @Override - public BlockImpl getBlock() { - return c; - } - }; - } - case EXCEPTION_BLOCK: - // add pred correctly to predecessor list - ExceptionBlockImpl e = (ExceptionBlockImpl) pred; - if (e.getSuccessor() == cur) { - return singleSuccessorHolder(e, cur); - } else { - @SuppressWarnings("keyfor:assignment.type.incompatible") // ignore keyfor type - Set>> entrySet = - e.getExceptionalSuccessors().entrySet(); - for (Map.Entry> entry : entrySet) { - if (entry.getValue().contains(cur)) { - return new PredecessorHolder() { - @Override - public void setSuccessor(BlockImpl b) { - e.addExceptionalSuccessor(b, entry.getKey()); - cur.removePredecessor(pred); - } - - @Override - public BlockImpl getBlock() { - return e; - } - }; - } - } - } - throw new BugInCF("Unreachable"); - case REGULAR_BLOCK: - RegularBlockImpl r = (RegularBlockImpl) pred; - return singleSuccessorHolder(r, cur); - default: - throw new BugInCF("Unexpected block type " + pred.getType()); - } - } + /** + * Return a predecessor holder that can be used to set the successor of {@code pred} in the place + * where previously the edge pointed to {@code cur}. Additionally, the predecessor holder also + * takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} predecessors). + * + * @param pred a block whose successor should be set + * @param cur the previous successor of {@code pred} + * @return a predecessor holder to set the successor of {@code pred} + */ + @SuppressWarnings("interning:not.interned") // AST node comparisons + protected static PredecessorHolder getPredecessorHolder(BlockImpl pred, BlockImpl cur) { + switch (pred.getType()) { + case SPECIAL_BLOCK: + SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred; + return singleSuccessorHolder(s, cur); + case CONDITIONAL_BLOCK: + // add pred correctly to predecessor list + ConditionalBlockImpl c = (ConditionalBlockImpl) pred; + if (c.getThenSuccessor() == cur) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + c.setThenSuccessor(b); + cur.removePredecessor(pred); + } - /** - * Returns a {@link PredecessorHolder} that sets the successor of a single successor block - * {@code s}. - * - * @return a {@link PredecessorHolder} that sets the successor of a single successor block - * {@code s} - */ - protected static PredecessorHolder singleSuccessorHolder( - SingleSuccessorBlockImpl s, BlockImpl old) { - return new PredecessorHolder() { + @Override + public BlockImpl getBlock() { + return c; + } + }; + } else { + assert c.getElseSuccessor() == cur; + return new PredecessorHolder() { @Override public void setSuccessor(BlockImpl b) { - s.setSuccessor(b); - old.removePredecessor(s); + c.setElseSuccessor(b); + cur.removePredecessor(pred); } @Override public BlockImpl getBlock() { - return s; + return c; + } + }; + } + case EXCEPTION_BLOCK: + // add pred correctly to predecessor list + ExceptionBlockImpl e = (ExceptionBlockImpl) pred; + if (e.getSuccessor() == cur) { + return singleSuccessorHolder(e, cur); + } else { + @SuppressWarnings("keyfor:assignment.type.incompatible") // ignore keyfor type + Set>> entrySet = e.getExceptionalSuccessors().entrySet(); + for (Map.Entry> entry : entrySet) { + if (entry.getValue().contains(cur)) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + e.addExceptionalSuccessor(b, entry.getKey()); + cur.removePredecessor(pred); + } + + @Override + public BlockImpl getBlock() { + return e; + } + }; } - }; + } + } + throw new BugInCF("Unreachable"); + case REGULAR_BLOCK: + RegularBlockImpl r = (RegularBlockImpl) pred; + return singleSuccessorHolder(r, cur); + default: + throw new BugInCF("Unexpected block type " + pred.getType()); } + } + + /** + * Returns a {@link PredecessorHolder} that sets the successor of a single successor block {@code + * s}. + * + * @return a {@link PredecessorHolder} that sets the successor of a single successor block {@code + * s} + */ + protected static PredecessorHolder singleSuccessorHolder( + SingleSuccessorBlockImpl s, BlockImpl old) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + s.setSuccessor(b); + old.removePredecessor(s); + } + + @Override + public BlockImpl getBlock() { + return s; + } + }; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java index a1818b19101..faba847b1d5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java @@ -1,5 +1,10 @@ package org.checkerframework.dataflow.cfg.builder; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.block.BlockImpl; @@ -14,223 +19,215 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.ArraySet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; - /** Class that performs phase two of the translation process. */ @SuppressWarnings("nullness") // TODO public class CFGTranslationPhaseTwo { - private CFGTranslationPhaseTwo() {} - - /** - * Perform phase two of the translation. - * - * @param in the result of phase one - * @return a control flow graph that might still contain degenerate basic blocks (such as empty - * regular basic blocks or conditional blocks with the same block as 'then' and 'else' - * successor) - */ - @SuppressWarnings("interning:not.interned") // AST node comparisons - public static ControlFlowGraph process(PhaseOneResult in) { - - Map bindings = in.bindings; - List nodeList = in.nodeList; - // A leader is an extended node which will give rise to a basic block in phase two. - Set leaders = in.leaders; - - assert !in.nodeList.isEmpty(); - - // exit blocks - SpecialBlockImpl regularExitBlock = new SpecialBlockImpl(SpecialBlockType.EXIT); - SpecialBlockImpl exceptionalExitBlock = - new SpecialBlockImpl(SpecialBlockType.EXCEPTIONAL_EXIT); - - // record missing edges that will be added later - Set missingEdges = new ArraySet<>(1); - - // missing exceptional edges - Set missingExceptionalEdges = new LinkedHashSet<>(); - - // create start block - SpecialBlockImpl startBlock = new SpecialBlockImpl(SpecialBlockType.ENTRY); - missingEdges.add(new MissingEdge(startBlock, 0)); - - // Loop through all 'leaders' (while dynamically detecting the leaders). - @NonNull RegularBlockImpl block = new RegularBlockImpl(); // block being processed/built - int i = 0; - for (ExtendedNode node : nodeList) { - switch (node.getType()) { - case NODE: - if (leaders.contains(i)) { - RegularBlockImpl b = new RegularBlockImpl(); - block.setSuccessor(b); - block = b; - } - block.addNode(node.getNode()); - node.setBlock(block); - - // does this node end the execution (modeled as an edge to - // the exceptional exit block) - boolean terminatesExecution = node.getTerminatesExecution(); - if (terminatesExecution) { - block.setSuccessor(exceptionalExitBlock); - block = new RegularBlockImpl(); - } - break; - case CONDITIONAL_JUMP: - { - ConditionalJump cj = (ConditionalJump) node; - // Exception nodes may fall through to conditional jumps, so we set the - // block which is required for the insertion of missing edges. - node.setBlock(block); - assert block != null; - ConditionalBlockImpl cb = new ConditionalBlockImpl(); - if (cj.getTrueFlowRule() != null) { - cb.setThenFlowRule(cj.getTrueFlowRule()); - } - if (cj.getFalseFlowRule() != null) { - cb.setElseFlowRule(cj.getFalseFlowRule()); - } - block.setSuccessor(cb); - block = new RegularBlockImpl(); - - // use two anonymous SingleSuccessorBlockImpl that set the - // 'then' and 'else' successor of the conditional block - Label thenLabel = cj.getThenLabel(); - Label elseLabel = cj.getElseLabel(); - Integer target = bindings.get(thenLabel); - assert target != null; - missingEdges.add( - new MissingEdge( - new RegularBlockImpl() { - @Override - public void setSuccessor(BlockImpl successor) { - cb.setThenSuccessor(successor); - } - }, - target)); - target = bindings.get(elseLabel); - if (target == null) { - throw new BugInCF( - String.format( - "in conditional jump %s, no binding for elseLabel %s: %s", - cj, elseLabel, bindings)); - } - missingEdges.add( - new MissingEdge( - new RegularBlockImpl() { - @Override - public void setSuccessor(BlockImpl successor) { - cb.setElseSuccessor(successor); - } - }, - target)); - break; - } - case UNCONDITIONAL_JUMP: - UnconditionalJump uj = (UnconditionalJump) node; - if (leaders.contains(i)) { - RegularBlockImpl b = new RegularBlockImpl(); - block.setSuccessor(b); - block = b; - } - node.setBlock(block); - if (node.getLabel() == in.regularExitLabel) { - block.setSuccessor(regularExitBlock); - block.setFlowRule(uj.getFlowRule()); - } else if (node.getLabel() == in.exceptionalExitLabel) { - block.setSuccessor(exceptionalExitBlock); - block.setFlowRule(uj.getFlowRule()); - } else { - int target = bindings.get(node.getLabel()); - missingEdges.add(new MissingEdge(block, target, uj.getFlowRule())); - } - block = new RegularBlockImpl(); - break; - case EXCEPTION_NODE: - NodeWithExceptionsHolder en = (NodeWithExceptionsHolder) node; - // create new exception block and link with previous block - ExceptionBlockImpl e = new ExceptionBlockImpl(); - Node nn = en.getNode(); - e.setNode(nn); - node.setBlock(e); - block.setSuccessor(e); - block = new RegularBlockImpl(); - - // Ensure linking between e and next block (normal edge). - // Note: do not link to the next block for throw statements (these throw - // exceptions for sure). - if (!node.getTerminatesExecution()) { - missingEdges.add(new MissingEdge(e, i + 1)); - } - - // exceptional edges - for (Map.Entry> entry : en.getExceptions().entrySet()) { - TypeMirror cause = entry.getKey(); - for (Label label : entry.getValue()) { - Integer target = bindings.get(label); - // TODO: `target` is sometimes null; is this a problem? - // assert target != null; - missingExceptionalEdges.add(new MissingEdge(e, target, cause)); - } - } - break; + private CFGTranslationPhaseTwo() {} + + /** + * Perform phase two of the translation. + * + * @param in the result of phase one + * @return a control flow graph that might still contain degenerate basic blocks (such as empty + * regular basic blocks or conditional blocks with the same block as 'then' and 'else' + * successor) + */ + @SuppressWarnings("interning:not.interned") // AST node comparisons + public static ControlFlowGraph process(PhaseOneResult in) { + + Map bindings = in.bindings; + List nodeList = in.nodeList; + // A leader is an extended node which will give rise to a basic block in phase two. + Set leaders = in.leaders; + + assert !in.nodeList.isEmpty(); + + // exit blocks + SpecialBlockImpl regularExitBlock = new SpecialBlockImpl(SpecialBlockType.EXIT); + SpecialBlockImpl exceptionalExitBlock = new SpecialBlockImpl(SpecialBlockType.EXCEPTIONAL_EXIT); + + // record missing edges that will be added later + Set missingEdges = new ArraySet<>(1); + + // missing exceptional edges + Set missingExceptionalEdges = new LinkedHashSet<>(); + + // create start block + SpecialBlockImpl startBlock = new SpecialBlockImpl(SpecialBlockType.ENTRY); + missingEdges.add(new MissingEdge(startBlock, 0)); + + // Loop through all 'leaders' (while dynamically detecting the leaders). + @NonNull RegularBlockImpl block = new RegularBlockImpl(); // block being processed/built + int i = 0; + for (ExtendedNode node : nodeList) { + switch (node.getType()) { + case NODE: + if (leaders.contains(i)) { + RegularBlockImpl b = new RegularBlockImpl(); + block.setSuccessor(b); + block = b; + } + block.addNode(node.getNode()); + node.setBlock(block); + + // does this node end the execution (modeled as an edge to + // the exceptional exit block) + boolean terminatesExecution = node.getTerminatesExecution(); + if (terminatesExecution) { + block.setSuccessor(exceptionalExitBlock); + block = new RegularBlockImpl(); + } + break; + case CONDITIONAL_JUMP: + { + ConditionalJump cj = (ConditionalJump) node; + // Exception nodes may fall through to conditional jumps, so we set the + // block which is required for the insertion of missing edges. + node.setBlock(block); + assert block != null; + ConditionalBlockImpl cb = new ConditionalBlockImpl(); + if (cj.getTrueFlowRule() != null) { + cb.setThenFlowRule(cj.getTrueFlowRule()); } - i++; - } - - // add missing edges - for (MissingEdge p : missingEdges) { - Integer index = p.index; - assert index != null : "CFGBuilder: problem in CFG construction " + p.source; - ExtendedNode extendedNode = nodeList.get(index); - BlockImpl target = extendedNode.getBlock(); - SingleSuccessorBlockImpl source = p.source; - source.setSuccessor(target); - if (p.flowRule != null) { - source.setFlowRule(p.flowRule); + if (cj.getFalseFlowRule() != null) { + cb.setElseFlowRule(cj.getFalseFlowRule()); } - } - - // add missing exceptional edges - for (MissingEdge p : missingExceptionalEdges) { - Integer index = p.index; - TypeMirror cause = p.cause; - ExceptionBlockImpl source = (ExceptionBlockImpl) p.source; - if (index == null) { - // edge to exceptional exit - source.addExceptionalSuccessor(exceptionalExitBlock, cause); - } else { - // edge to specific target - ExtendedNode extendedNode = nodeList.get(index); - BlockImpl target = extendedNode.getBlock(); - List targetNodes = target.getNodes(); - Node firstNode = targetNodes.isEmpty() ? null : targetNodes.get(0); - if (firstNode instanceof CatchMarkerNode) { - TypeMirror catchType = ((CatchMarkerNode) firstNode).getCatchType(); - if (in.types.isSubtype(catchType, cause)) { - cause = catchType; - } - } - source.addExceptionalSuccessor(target, cause); + block.setSuccessor(cb); + block = new RegularBlockImpl(); + + // use two anonymous SingleSuccessorBlockImpl that set the + // 'then' and 'else' successor of the conditional block + Label thenLabel = cj.getThenLabel(); + Label elseLabel = cj.getElseLabel(); + Integer target = bindings.get(thenLabel); + assert target != null; + missingEdges.add( + new MissingEdge( + new RegularBlockImpl() { + @Override + public void setSuccessor(BlockImpl successor) { + cb.setThenSuccessor(successor); + } + }, + target)); + target = bindings.get(elseLabel); + if (target == null) { + throw new BugInCF( + String.format( + "in conditional jump %s, no binding for elseLabel %s: %s", + cj, elseLabel, bindings)); } - } + missingEdges.add( + new MissingEdge( + new RegularBlockImpl() { + @Override + public void setSuccessor(BlockImpl successor) { + cb.setElseSuccessor(successor); + } + }, + target)); + break; + } + case UNCONDITIONAL_JUMP: + UnconditionalJump uj = (UnconditionalJump) node; + if (leaders.contains(i)) { + RegularBlockImpl b = new RegularBlockImpl(); + block.setSuccessor(b); + block = b; + } + node.setBlock(block); + if (node.getLabel() == in.regularExitLabel) { + block.setSuccessor(regularExitBlock); + block.setFlowRule(uj.getFlowRule()); + } else if (node.getLabel() == in.exceptionalExitLabel) { + block.setSuccessor(exceptionalExitBlock); + block.setFlowRule(uj.getFlowRule()); + } else { + int target = bindings.get(node.getLabel()); + missingEdges.add(new MissingEdge(block, target, uj.getFlowRule())); + } + block = new RegularBlockImpl(); + break; + case EXCEPTION_NODE: + NodeWithExceptionsHolder en = (NodeWithExceptionsHolder) node; + // create new exception block and link with previous block + ExceptionBlockImpl e = new ExceptionBlockImpl(); + Node nn = en.getNode(); + e.setNode(nn); + node.setBlock(e); + block.setSuccessor(e); + block = new RegularBlockImpl(); + + // Ensure linking between e and next block (normal edge). + // Note: do not link to the next block for throw statements (these throw + // exceptions for sure). + if (!node.getTerminatesExecution()) { + missingEdges.add(new MissingEdge(e, i + 1)); + } + + // exceptional edges + for (Map.Entry> entry : en.getExceptions().entrySet()) { + TypeMirror cause = entry.getKey(); + for (Label label : entry.getValue()) { + Integer target = bindings.get(label); + // TODO: `target` is sometimes null; is this a problem? + // assert target != null; + missingExceptionalEdges.add(new MissingEdge(e, target, cause)); + } + } + break; + } + i++; + } - return new ControlFlowGraph( - startBlock, - regularExitBlock, - exceptionalExitBlock, - in.underlyingAST, - in.treeToCfgNodes, - in.treeToConvertedCfgNodes, - in.postfixTreeToCfgNodes, - in.returnNodes, - in.declaredClasses, - in.declaredLambdas); + // add missing edges + for (MissingEdge p : missingEdges) { + Integer index = p.index; + assert index != null : "CFGBuilder: problem in CFG construction " + p.source; + ExtendedNode extendedNode = nodeList.get(index); + BlockImpl target = extendedNode.getBlock(); + SingleSuccessorBlockImpl source = p.source; + source.setSuccessor(target); + if (p.flowRule != null) { + source.setFlowRule(p.flowRule); + } } + + // add missing exceptional edges + for (MissingEdge p : missingExceptionalEdges) { + Integer index = p.index; + TypeMirror cause = p.cause; + ExceptionBlockImpl source = (ExceptionBlockImpl) p.source; + if (index == null) { + // edge to exceptional exit + source.addExceptionalSuccessor(exceptionalExitBlock, cause); + } else { + // edge to specific target + ExtendedNode extendedNode = nodeList.get(index); + BlockImpl target = extendedNode.getBlock(); + List targetNodes = target.getNodes(); + Node firstNode = targetNodes.isEmpty() ? null : targetNodes.get(0); + if (firstNode instanceof CatchMarkerNode) { + TypeMirror catchType = ((CatchMarkerNode) firstNode).getCatchType(); + if (in.types.isSubtype(catchType, cause)) { + cause = catchType; + } + } + source.addExceptionalSuccessor(target, cause); + } + } + + return new ControlFlowGraph( + startBlock, + regularExitBlock, + exceptionalExitBlock, + in.underlyingAST, + in.treeToCfgNodes, + in.treeToConvertedCfgNodes, + in.postfixTreeToCfgNodes, + in.returnNodes, + in.declaredClasses, + in.declaredLambdas); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java index f7b152f89a9..5323f2c2f6e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java @@ -14,89 +14,89 @@ @SuppressWarnings("nullness") // TODO public class ConditionalJump extends ExtendedNode { - /** The true successor label. */ - protected final Label trueSucc; + /** The true successor label. */ + protected final Label trueSucc; - /** The false successor label. */ - protected final Label falseSucc; + /** The false successor label. */ + protected final Label falseSucc; - /** The true branch flow rule. */ - protected FlowRule trueFlowRule; + /** The true branch flow rule. */ + protected FlowRule trueFlowRule; - /** The false branch flow rule. */ - protected FlowRule falseFlowRule; + /** The false branch flow rule. */ + protected FlowRule falseFlowRule; - /** - * Construct a ConditionalJump. - * - * @param trueSucc true successor label - * @param falseSucc false successor label - */ - public ConditionalJump(Label trueSucc, Label falseSucc) { - super(ExtendedNodeType.CONDITIONAL_JUMP); - assert trueSucc != null; - this.trueSucc = trueSucc; - assert falseSucc != null; - this.falseSucc = falseSucc; - } + /** + * Construct a ConditionalJump. + * + * @param trueSucc true successor label + * @param falseSucc false successor label + */ + public ConditionalJump(Label trueSucc, Label falseSucc) { + super(ExtendedNodeType.CONDITIONAL_JUMP); + assert trueSucc != null; + this.trueSucc = trueSucc; + assert falseSucc != null; + this.falseSucc = falseSucc; + } - public Label getThenLabel() { - return trueSucc; - } + public Label getThenLabel() { + return trueSucc; + } - public Label getElseLabel() { - return falseSucc; - } + public Label getElseLabel() { + return falseSucc; + } - /** - * Returns the true branch flow rule. - * - * @return the true branch flow rule - */ - public FlowRule getTrueFlowRule() { - return trueFlowRule; - } + /** + * Returns the true branch flow rule. + * + * @return the true branch flow rule + */ + public FlowRule getTrueFlowRule() { + return trueFlowRule; + } - /** - * Returns the false branch flow rule. - * - * @return the false branch flow rule - */ - public FlowRule getFalseFlowRule() { - return falseFlowRule; - } + /** + * Returns the false branch flow rule. + * + * @return the false branch flow rule + */ + public FlowRule getFalseFlowRule() { + return falseFlowRule; + } - /** - * Sets the true branch flow rule. - * - * @param rule the new true branch flow rule - */ - public void setTrueFlowRule(FlowRule rule) { - trueFlowRule = rule; - } + /** + * Sets the true branch flow rule. + * + * @param rule the new true branch flow rule + */ + public void setTrueFlowRule(FlowRule rule) { + trueFlowRule = rule; + } - /** - * Sets the false branch flow rule. - * - * @param rule the new false branch flow rule - */ - public void setFalseFlowRule(FlowRule rule) { - falseFlowRule = rule; - } + /** + * Sets the false branch flow rule. + * + * @param rule the new false branch flow rule + */ + public void setFalseFlowRule(FlowRule rule) { + falseFlowRule = rule; + } - /** - * Produce a string representation. - * - * @return a string representation - * @see org.checkerframework.dataflow.cfg.builder.PhaseOneResult#nodeToString - */ - @Override - public String toString() { - return "TwoTargetConditionalJump(" + getThenLabel() + ", " + getElseLabel() + ")"; - } + /** + * Produce a string representation. + * + * @return a string representation + * @see org.checkerframework.dataflow.cfg.builder.PhaseOneResult#nodeToString + */ + @Override + public String toString() { + return "TwoTargetConditionalJump(" + getThenLabel() + ", " + getElseLabel() + ")"; + } - @Override - public String toStringDebug() { - return toString(); - } + @Override + public String toStringDebug() { + return toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java index 1c1c40f6e69..f72d4af9b84 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java @@ -25,83 +25,83 @@ @SuppressWarnings("nullness") // TODO public abstract class ExtendedNode { - /** The basic block this extended node belongs to (as determined in phase two). */ - protected BlockImpl block; - - /** Type of this node. */ - protected final ExtendedNodeType type; - - /** Does this node terminate the execution? (e.g., "System.exit()") */ - protected boolean terminatesExecution = false; - - /** - * Create a new ExtendedNode. - * - * @param type the type of this node - */ - protected ExtendedNode(ExtendedNodeType type) { - this.type = type; - } - - /** Extended node types (description see above). */ - public enum ExtendedNodeType { - NODE, - EXCEPTION_NODE, - UNCONDITIONAL_JUMP, - CONDITIONAL_JUMP - } - - public ExtendedNodeType getType() { - return type; - } - - public boolean getTerminatesExecution() { - return terminatesExecution; - } - - public void setTerminatesExecution(boolean terminatesExecution) { - this.terminatesExecution = terminatesExecution; - } - - /** - * Returns the node contained in this extended node (only applicable if the type is {@code NODE} - * or {@code EXCEPTION_NODE}). - * - * @return the node contained in this extended node (only applicable if the type is {@code NODE} - * or {@code EXCEPTION_NODE}) - */ - public Node getNode() { - throw new BugInCF("Do not call"); - } - - /** - * Returns the label associated with this extended node (only applicable if type is {@link - * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}). - * - * @return the label associated with this extended node (only applicable if type is {@link - * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}) - */ - public Label getLabel() { - throw new BugInCF("Do not call"); - } - - public BlockImpl getBlock() { - return block; - } - - public void setBlock(BlockImpl b) { - this.block = b; - } - - @Override - public String toString() { - throw new BugInCF("DO NOT CALL ExtendedNode.toString(). Write your own."); - } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public abstract String toStringDebug(); + /** The basic block this extended node belongs to (as determined in phase two). */ + protected BlockImpl block; + + /** Type of this node. */ + protected final ExtendedNodeType type; + + /** Does this node terminate the execution? (e.g., "System.exit()") */ + protected boolean terminatesExecution = false; + + /** + * Create a new ExtendedNode. + * + * @param type the type of this node + */ + protected ExtendedNode(ExtendedNodeType type) { + this.type = type; + } + + /** Extended node types (description see above). */ + public enum ExtendedNodeType { + NODE, + EXCEPTION_NODE, + UNCONDITIONAL_JUMP, + CONDITIONAL_JUMP + } + + public ExtendedNodeType getType() { + return type; + } + + public boolean getTerminatesExecution() { + return terminatesExecution; + } + + public void setTerminatesExecution(boolean terminatesExecution) { + this.terminatesExecution = terminatesExecution; + } + + /** + * Returns the node contained in this extended node (only applicable if the type is {@code NODE} + * or {@code EXCEPTION_NODE}). + * + * @return the node contained in this extended node (only applicable if the type is {@code NODE} + * or {@code EXCEPTION_NODE}) + */ + public Node getNode() { + throw new BugInCF("Do not call"); + } + + /** + * Returns the label associated with this extended node (only applicable if type is {@link + * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}). + * + * @return the label associated with this extended node (only applicable if type is {@link + * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}) + */ + public Label getLabel() { + throw new BugInCF("Do not call"); + } + + public BlockImpl getBlock() { + return block; + } + + public void setBlock(BlockImpl b) { + this.block = b; + } + + @Override + public String toString() { + throw new BugInCF("DO NOT CALL ExtendedNode.toString(). Write your own."); + } + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public abstract String toStringDebug(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java index 3ce9e3dcda3..22dacf0030a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java @@ -10,30 +10,30 @@ */ public class Label { - /** Unique id counter that incremented in {@code #uniqueName}. */ - private static int uid = 0; + /** Unique id counter that incremented in {@code #uniqueName}. */ + private static int uid = 0; - protected final String name; + protected final String name; - public Label(String name) { - this.name = name; - } + public Label(String name) { + this.name = name; + } - public Label() { - this.name = uniqueName(); - } + public Label() { + this.name = uniqueName(); + } - @Override - public String toString() { - return name; - } + @Override + public String toString() { + return name; + } - /** - * Return a new unique label name that cannot be confused with a Java source code label. - * - * @return a new unique label name - */ - private static String uniqueName() { - return "%L" + uid++; - } + /** + * Return a new unique label name that cannot be confused with a Java source code label. + * + * @return a new unique label name + */ + private static String uniqueName() { + return "%L" + uid++; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java index a948fcac774..90730482fe4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java @@ -5,44 +5,44 @@ /** Storage cell for a single Label, with tracking whether it was accessed. */ /*package-private*/ class LabelCell { - /** The label. If it is null, then it will be lazily set if {@link #accessLabel} is called. */ - private @MonotonicNonNull Label label; - - /** True if the label has been accessed. */ - private boolean accessed; - - /** Create a LabelCell with no label; the label will be lazily created if needed. */ - protected LabelCell() { - this.accessed = false; - } - - /** - * Create a LabelCell with the given label. - * - * @param label the label - */ - protected LabelCell(Label label) { - assert label != null; - this.label = label; - this.accessed = false; + /** The label. If it is null, then it will be lazily set if {@link #accessLabel} is called. */ + private @MonotonicNonNull Label label; + + /** True if the label has been accessed. */ + private boolean accessed; + + /** Create a LabelCell with no label; the label will be lazily created if needed. */ + protected LabelCell() { + this.accessed = false; + } + + /** + * Create a LabelCell with the given label. + * + * @param label the label + */ + protected LabelCell(Label label) { + assert label != null; + this.label = label; + this.accessed = false; + } + + public Label accessLabel() { + if (label == null) { + label = new Label(); } + accessed = true; + return label; + } - public Label accessLabel() { - if (label == null) { - label = new Label(); - } - accessed = true; - return label; + public Label peekLabel() { + if (label == null) { + throw new BugInCF("called peekLabel prematurely"); } + return label; + } - public Label peekLabel() { - if (label == null) { - throw new BugInCF("called peekLabel prematurely"); - } - return label; - } - - public boolean wasAccessed() { - return accessed; - } + public boolean wasAccessed() { + return accessed; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java index a49169ca59f..2dfbcb58ffb 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java @@ -1,84 +1,83 @@ package org.checkerframework.dataflow.cfg.builder; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store.FlowRule; import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl; -import javax.lang.model.type.TypeMirror; - /* --------------------------------------------------------- */ /* Phase Two */ /* --------------------------------------------------------- */ /** Represents a missing edge that will be added later. */ /*package-private*/ class MissingEdge { - /** The source of the edge. */ - /*package-private*/ final SingleSuccessorBlockImpl source; + /** The source of the edge. */ + /*package-private*/ final SingleSuccessorBlockImpl source; - /** The index (target?) of the edge. Null means go to exceptional exit. */ - /*package-private*/ final @Nullable Integer index; + /** The index (target?) of the edge. Null means go to exceptional exit. */ + /*package-private*/ final @Nullable Integer index; - /** The cause exception type, for an exceptional edge; otherwise null. */ - /*package-private*/ final @Nullable TypeMirror cause; + /** The cause exception type, for an exceptional edge; otherwise null. */ + /*package-private*/ final @Nullable TypeMirror cause; - /** The flow rule for this edge. */ - /*package-private*/ final @Nullable FlowRule flowRule; + /** The flow rule for this edge. */ + /*package-private*/ final @Nullable FlowRule flowRule; - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge - */ - public MissingEdge(SingleSuccessorBlockImpl source, int index) { - this(source, index, null, FlowRule.EACH_TO_EACH); - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge + */ + public MissingEdge(SingleSuccessorBlockImpl source, int index) { + this(source, index, null, FlowRule.EACH_TO_EACH); + } - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge - * @param flowRule the flow rule for this edge - */ - public MissingEdge(SingleSuccessorBlockImpl source, int index, FlowRule flowRule) { - this(source, index, null, flowRule); - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge + * @param flowRule the flow rule for this edge + */ + public MissingEdge(SingleSuccessorBlockImpl source, int index, FlowRule flowRule) { + this(source, index, null, flowRule); + } - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge; null means go to exceptional exit - * @param cause the cause exception type, for an exceptional edge; otherwise null - */ - public MissingEdge( - SingleSuccessorBlockImpl source, @Nullable Integer index, @Nullable TypeMirror cause) { - this(source, index, cause, FlowRule.EACH_TO_EACH); - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge; null means go to exceptional exit + * @param cause the cause exception type, for an exceptional edge; otherwise null + */ + public MissingEdge( + SingleSuccessorBlockImpl source, @Nullable Integer index, @Nullable TypeMirror cause) { + this(source, index, cause, FlowRule.EACH_TO_EACH); + } - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge; null means go to exceptional exit - * @param cause the cause exception type, for an exceptional edge; otherwise null - * @param flowRule the flow rule for this edge - */ - public MissingEdge( - SingleSuccessorBlockImpl source, - @Nullable Integer index, - @Nullable TypeMirror cause, - FlowRule flowRule) { - assert (index != null) || (cause != null); - this.source = source; - this.index = index; - this.cause = cause; - this.flowRule = flowRule; - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge; null means go to exceptional exit + * @param cause the cause exception type, for an exceptional edge; otherwise null + * @param flowRule the flow rule for this edge + */ + public MissingEdge( + SingleSuccessorBlockImpl source, + @Nullable Integer index, + @Nullable TypeMirror cause, + FlowRule flowRule) { + assert (index != null) || (cause != null); + this.source = source; + this.index = index; + this.cause = cause; + this.flowRule = flowRule; + } - @Override - public String toString() { - return "MissingEdge(" + source + ", " + index + ", " + cause + ")"; - } + @Override + public String toString() { + return "MissingEdge(" + source + ", " + index + ", " + cause + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java index 33dc0e97689..da7b7de241a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java @@ -6,31 +6,31 @@ /** An extended node of type {@code NODE}. */ /*package-private*/ class NodeHolder extends ExtendedNode { - /** The node to hold. */ - protected final Node node; + /** The node to hold. */ + protected final Node node; - /** - * Construct a NodeHolder for the given Node. - * - * @param node the node to hold - */ - public NodeHolder(Node node) { - super(ExtendedNodeType.NODE); - this.node = node; - } + /** + * Construct a NodeHolder for the given Node. + * + * @param node the node to hold + */ + public NodeHolder(Node node) { + super(ExtendedNodeType.NODE); + this.node = node; + } - @Override - public Node getNode() { - return node; - } + @Override + public Node getNode() { + return node; + } - @Override - public String toString() { - return "NodeHolder(" + node + ")"; - } + @Override + public String toString() { + return "NodeHolder(" + node + ")"; + } - @Override - public String toStringDebug() { - return "NodeHolder(" + node.toStringDebug() + ")"; - } + @Override + public String toStringDebug() { + return "NodeHolder(" + node.toStringDebug() + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java index c499126d428..e3f8f7ab406 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java @@ -1,70 +1,68 @@ package org.checkerframework.dataflow.cfg.builder; -import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; -import org.checkerframework.dataflow.cfg.node.Node; - import java.util.Map; import java.util.Set; import java.util.StringJoiner; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; +import org.checkerframework.dataflow.cfg.node.Node; /** An extended node of type {@code EXCEPTION_NODE}. */ /*package-private*/ class NodeWithExceptionsHolder extends ExtendedNode { - /** The node to hold. */ - protected final Node node; + /** The node to hold. */ + protected final Node node; - /** - * Map from exception type to labels of successors that may be reached as a result of that - * exception. - * - *

This map's keys are the exception types that a Java expression or statement is declared to - * throw -- say, in the {@code throws} clause of the declaration of a method being called. The - * expression might be within a {@code try} statement with {@code catch} blocks that are - * different (either finer-grained or coarser). - */ - protected final Map> exceptions; + /** + * Map from exception type to labels of successors that may be reached as a result of that + * exception. + * + *

This map's keys are the exception types that a Java expression or statement is declared to + * throw -- say, in the {@code throws} clause of the declaration of a method being called. The + * expression might be within a {@code try} statement with {@code catch} blocks that are different + * (either finer-grained or coarser). + */ + protected final Map> exceptions; - /** - * Construct a NodeWithExceptionsHolder for the given node and exceptions. - * - * @param node the node to hold - * @param exceptions the exceptions to hold - */ - public NodeWithExceptionsHolder(Node node, Map> exceptions) { - super(ExtendedNodeType.EXCEPTION_NODE); - this.node = node; - this.exceptions = exceptions; - } + /** + * Construct a NodeWithExceptionsHolder for the given node and exceptions. + * + * @param node the node to hold + * @param exceptions the exceptions to hold + */ + public NodeWithExceptionsHolder(Node node, Map> exceptions) { + super(ExtendedNodeType.EXCEPTION_NODE); + this.node = node; + this.exceptions = exceptions; + } - /** - * Get the exceptions for the node. - * - * @return exceptions for the node - */ - public Map> getExceptions() { - return exceptions; - } + /** + * Get the exceptions for the node. + * + * @return exceptions for the node + */ + public Map> getExceptions() { + return exceptions; + } - @Override - public Node getNode() { - return node; - } + @Override + public Node getNode() { + return node; + } - @Override - public String toString() { - return "NodeWithExceptionsHolder(" + node + ")"; - } + @Override + public String toString() { + return "NodeWithExceptionsHolder(" + node + ")"; + } - @Override - public String toStringDebug() { - StringJoiner sj = new StringJoiner(String.format("%n ")); - sj.add("NodeWithExceptionsHolder(" + node.toStringDebug() + ") {"); - for (Map.Entry> entry : exceptions.entrySet()) { - sj.add(entry.getKey() + " => " + entry.getValue()); - } - sj.add("}"); - return sj.toString(); + @Override + public String toStringDebug() { + StringJoiner sj = new StringJoiner(String.format("%n ")); + sj.add("NodeWithExceptionsHolder(" + node.toStringDebug() + ") {"); + for (Map.Entry> entry : exceptions.entrySet()) { + sj.add(entry.getKey() + " => " + entry.getValue()); } + sj.add("}"); + return sj.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java index e9a4537ac6d..87e2a738bad 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java @@ -5,204 +5,197 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - -import org.checkerframework.dataflow.cfg.UnderlyingAST; -import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; -import org.checkerframework.dataflow.cfg.node.Node; -import org.checkerframework.dataflow.cfg.node.ReturnNode; - import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringJoiner; - import javax.lang.model.util.Types; +import org.checkerframework.dataflow.cfg.UnderlyingAST; +import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.cfg.node.ReturnNode; /** A wrapper object to pass around the result of phase one. */ public class PhaseOneResult { - /** AST for which the CFG is to be built. */ - /*package-private*/ final UnderlyingAST underlyingAST; - - /** - * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will - * have at least one corresponding Node. Trees that undergo conversions, such as boxing or - * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in - * the treeToCfgNodes, while the Node for the post-conversion value is stored in the - * treeToConvertedCfgNodes. - */ - /*package-private*/ final IdentityHashMap> treeToCfgNodes; - - /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ - /*package-private*/ final IdentityHashMap> treeToConvertedCfgNodes; - - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the - * synthetic tree that is {@code v + 1} or {@code v - 1}. - */ - /*package-private*/ final IdentityHashMap postfixTreeToCfgNodes; - - /** The list of extended nodes. */ - /*package-private*/ final List nodeList; - - /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ - /*package-private*/ final Map bindings; - - /** The set of leaders (represented as indices into {@code nodeList}). */ - /*package-private*/ final Set leaders; - - /** - * All return nodes (if any) encountered. Only includes return statements that actually return - * something. - */ - /*package-private*/ final List returnNodes; - - /** Special label to identify the regular exit. */ - /*package-private*/ final Label regularExitLabel; - - /** Special label to identify the exceptional exit. */ - /*package-private*/ final Label exceptionalExitLabel; - - /** - * Class declarations that have been encountered when building the control-flow graph for a - * method. - */ - /*package-private*/ final List declaredClasses; - - /** - * Lambdas encountered when building the control-flow graph for a method, variable initializer, - * or initializer. - */ - /*package-private*/ final List declaredLambdas; - - /** The javac type utilities. */ - /*package-private*/ final Types types; - - /** - * Create a PhaseOneResult with the given data. - * - * @param underlyingAST the underlying AST - * @param treeToCfgNodes the tree to nodes mapping - * @param treeToConvertedCfgNodes the tree to converted nodes mapping - * @param postfixTreeToCfgNodes the postfix tree to nodes mapping - * @param nodeList the list of nodes - * @param bindings the label bindings - * @param leaders the leaders - * @param returnNodes the return nodes - * @param regularExitLabel the regular exit labels - * @param exceptionalExitLabel the exceptional exit labels - * @param declaredClasses the declared classes - * @param declaredLambdas the declared lambdas - * @param types the javac type utilities - */ - public PhaseOneResult( - UnderlyingAST underlyingAST, - IdentityHashMap> treeToCfgNodes, - IdentityHashMap> treeToConvertedCfgNodes, - IdentityHashMap postfixTreeToCfgNodes, - List nodeList, - Map bindings, - Set leaders, - List returnNodes, - Label regularExitLabel, - Label exceptionalExitLabel, - List declaredClasses, - List declaredLambdas, - Types types) { - this.underlyingAST = underlyingAST; - this.treeToCfgNodes = treeToCfgNodes; - this.treeToConvertedCfgNodes = treeToConvertedCfgNodes; - this.postfixTreeToCfgNodes = postfixTreeToCfgNodes; - this.nodeList = nodeList; - this.bindings = bindings; - this.leaders = leaders; - this.returnNodes = returnNodes; - this.regularExitLabel = regularExitLabel; - this.exceptionalExitLabel = exceptionalExitLabel; - this.declaredClasses = declaredClasses; - this.declaredLambdas = declaredLambdas; - this.types = types; - } - - @Override - public String toString() { - StringJoiner sj = new StringJoiner(System.lineSeparator()); - for (ExtendedNode n : nodeList) { - sj.add(nodeToString(n)); - } - return sj.toString(); + /** AST for which the CFG is to be built. */ + /*package-private*/ final UnderlyingAST underlyingAST; + + /** + * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will + * have at least one corresponding Node. Trees that undergo conversions, such as boxing or + * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in the + * treeToCfgNodes, while the Node for the post-conversion value is stored in the + * treeToConvertedCfgNodes. + */ + /*package-private*/ final IdentityHashMap> treeToCfgNodes; + + /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ + /*package-private*/ final IdentityHashMap> treeToConvertedCfgNodes; + + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic + * tree that is {@code v + 1} or {@code v - 1}. + */ + /*package-private*/ final IdentityHashMap postfixTreeToCfgNodes; + + /** The list of extended nodes. */ + /*package-private*/ final List nodeList; + + /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ + /*package-private*/ final Map bindings; + + /** The set of leaders (represented as indices into {@code nodeList}). */ + /*package-private*/ final Set leaders; + + /** + * All return nodes (if any) encountered. Only includes return statements that actually return + * something. + */ + /*package-private*/ final List returnNodes; + + /** Special label to identify the regular exit. */ + /*package-private*/ final Label regularExitLabel; + + /** Special label to identify the exceptional exit. */ + /*package-private*/ final Label exceptionalExitLabel; + + /** + * Class declarations that have been encountered when building the control-flow graph for a + * method. + */ + /*package-private*/ final List declaredClasses; + + /** + * Lambdas encountered when building the control-flow graph for a method, variable initializer, or + * initializer. + */ + /*package-private*/ final List declaredLambdas; + + /** The javac type utilities. */ + /*package-private*/ final Types types; + + /** + * Create a PhaseOneResult with the given data. + * + * @param underlyingAST the underlying AST + * @param treeToCfgNodes the tree to nodes mapping + * @param treeToConvertedCfgNodes the tree to converted nodes mapping + * @param postfixTreeToCfgNodes the postfix tree to nodes mapping + * @param nodeList the list of nodes + * @param bindings the label bindings + * @param leaders the leaders + * @param returnNodes the return nodes + * @param regularExitLabel the regular exit labels + * @param exceptionalExitLabel the exceptional exit labels + * @param declaredClasses the declared classes + * @param declaredLambdas the declared lambdas + * @param types the javac type utilities + */ + public PhaseOneResult( + UnderlyingAST underlyingAST, + IdentityHashMap> treeToCfgNodes, + IdentityHashMap> treeToConvertedCfgNodes, + IdentityHashMap postfixTreeToCfgNodes, + List nodeList, + Map bindings, + Set leaders, + List returnNodes, + Label regularExitLabel, + Label exceptionalExitLabel, + List declaredClasses, + List declaredLambdas, + Types types) { + this.underlyingAST = underlyingAST; + this.treeToCfgNodes = treeToCfgNodes; + this.treeToConvertedCfgNodes = treeToConvertedCfgNodes; + this.postfixTreeToCfgNodes = postfixTreeToCfgNodes; + this.nodeList = nodeList; + this.bindings = bindings; + this.leaders = leaders; + this.returnNodes = returnNodes; + this.regularExitLabel = regularExitLabel; + this.exceptionalExitLabel = exceptionalExitLabel; + this.declaredClasses = declaredClasses; + this.declaredLambdas = declaredLambdas; + this.types = types; + } + + @Override + public String toString() { + StringJoiner sj = new StringJoiner(System.lineSeparator()); + for (ExtendedNode n : nodeList) { + sj.add(nodeToString(n)); } - - protected String nodeToString(ExtendedNode n) { - if (n.getType() == ExtendedNodeType.CONDITIONAL_JUMP) { - ConditionalJump t = (ConditionalJump) n; - return "TwoTargetConditionalJump(" - + resolveLabel(t.getThenLabel()) - + ", " - + resolveLabel(t.getElseLabel()) - + ")"; - } else if (n.getType() == ExtendedNodeType.UNCONDITIONAL_JUMP) { - return "UnconditionalJump(" + resolveLabel(n.getLabel()) + ")"; - } else { - return n.toString(); - } + return sj.toString(); + } + + protected String nodeToString(ExtendedNode n) { + if (n.getType() == ExtendedNodeType.CONDITIONAL_JUMP) { + ConditionalJump t = (ConditionalJump) n; + return "TwoTargetConditionalJump(" + + resolveLabel(t.getThenLabel()) + + ", " + + resolveLabel(t.getElseLabel()) + + ")"; + } else if (n.getType() == ExtendedNodeType.UNCONDITIONAL_JUMP) { + return "UnconditionalJump(" + resolveLabel(n.getLabel()) + ")"; + } else { + return n.toString(); } + } - private String resolveLabel(Label label) { - Integer index = bindings.get(label); - if (index == null) { - return "unbound label: " + label; - } - return nodeToString(nodeList.get(index)); + private String resolveLabel(Label label) { + Integer index = bindings.get(label); + if (index == null) { + return "unbound label: " + label; } - - /** - * Returns a representation of a map, one entry per line. - * - * @param the key type of the map - * @param the value type of the map - * @param map a map - * @return a representation of a map, one entry per line - */ - private String mapToString(Map map) { - if (map.isEmpty()) { - return "{}"; - } - StringJoiner result = - new StringJoiner( - String.format("%n "), - String.format("{%n "), - String.format("%n }")); - for (Map.Entry entry : map.entrySet()) { - result.add(entry.getKey() + " => " + entry.getValue()); - } - return result.toString(); + return nodeToString(nodeList.get(index)); + } + + /** + * Returns a representation of a map, one entry per line. + * + * @param the key type of the map + * @param the value type of the map + * @param map a map + * @return a representation of a map, one entry per line + */ + private String mapToString(Map map) { + if (map.isEmpty()) { + return "{}"; } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public String toStringDebug() { - StringJoiner result = - new StringJoiner( - String.format("%n "), - String.format("PhaseOneResult{%n "), - String.format("%n }")); - result.add("treeToCfgNodes=" + mapToString(treeToCfgNodes)); - result.add("treeToConvertedCfgNodes=" + mapToString(treeToConvertedCfgNodes)); - result.add("postfixTreeToCfgNodes=" + mapToString(postfixTreeToCfgNodes)); - result.add("underlyingAST=" + underlyingAST); - result.add("bindings=" + bindings); - result.add("nodeList=" + CFGBuilder.extendedNodeCollectionToStringDebug(nodeList)); - result.add("leaders=" + leaders); - result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); - result.add("regularExitLabel=" + regularExitLabel); - result.add("exceptionalExitLabel=" + exceptionalExitLabel); - result.add("declaredClasses=" + declaredClasses); - result.add("declaredLambdas=" + declaredLambdas); - return result.toString(); + StringJoiner result = + new StringJoiner( + String.format("%n "), String.format("{%n "), String.format("%n }")); + for (Map.Entry entry : map.entrySet()) { + result.add(entry.getKey() + " => " + entry.getValue()); } + return result.toString(); + } + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public String toStringDebug() { + StringJoiner result = + new StringJoiner( + String.format("%n "), String.format("PhaseOneResult{%n "), String.format("%n }")); + result.add("treeToCfgNodes=" + mapToString(treeToCfgNodes)); + result.add("treeToConvertedCfgNodes=" + mapToString(treeToConvertedCfgNodes)); + result.add("postfixTreeToCfgNodes=" + mapToString(postfixTreeToCfgNodes)); + result.add("underlyingAST=" + underlyingAST); + result.add("bindings=" + bindings); + result.add("nodeList=" + CFGBuilder.extendedNodeCollectionToStringDebug(nodeList)); + result.add("leaders=" + leaders); + result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); + result.add("regularExitLabel=" + regularExitLabel); + result.add("exceptionalExitLabel=" + exceptionalExitLabel); + result.add("declaredClasses=" + declaredClasses); + result.add("declaredLambdas=" + declaredLambdas); + return result.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java index e4a0df09690..32953cdb80f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java @@ -4,31 +4,31 @@ /** A tuple with 4 named elements. */ /*package-private*/ interface TreeInfo { - /** - * Returns true if this is boxed. - * - * @return true if this is boxed - */ - boolean isBoxed(); + /** + * Returns true if this is boxed. + * + * @return true if this is boxed + */ + boolean isBoxed(); - /** - * Returns true if this is numeric. - * - * @return true if this is numeric - */ - boolean isNumeric(); + /** + * Returns true if this is numeric. + * + * @return true if this is numeric + */ + boolean isNumeric(); - /** - * Returns true if this is boolean. - * - * @return true if this is boolean - */ - boolean isBoolean(); + /** + * Returns true if this is boolean. + * + * @return true if this is boolean + */ + boolean isBoolean(); - /** - * Returns the unboxed type that this wraps. - * - * @return the unboxed type that this wraps - */ - TypeMirror unboxedType(); + /** + * Returns the unboxed type that this wraps. + * + * @return the unboxed type that this wraps + */ + TypeMirror unboxedType(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java index d390653a789..1151c5830d5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java @@ -1,121 +1,118 @@ package org.checkerframework.dataflow.cfg.builder; -import org.plumelib.util.IPair; - import java.util.List; import java.util.Set; import java.util.StringJoiner; - import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.UnionType; import javax.lang.model.util.Types; +import org.plumelib.util.IPair; /** * A TryCatchFrame contains an ordered list of catch labels that apply to exceptions with specific * types. */ /*package-private*/ class TryCatchFrame implements TryFrame { - /** The Types utilities. */ - protected final Types types; + /** The Types utilities. */ + protected final Types types; - /** An ordered list of pairs because catch blocks are ordered. */ - protected final List> catchLabels; + /** An ordered list of pairs because catch blocks are ordered. */ + protected final List> catchLabels; - /** - * Construct a TryCatchFrame. - * - * @param types the Types utilities - * @param catchLabels the catch labels - */ - public TryCatchFrame(Types types, List> catchLabels) { - this.types = types; - this.catchLabels = catchLabels; - } + /** + * Construct a TryCatchFrame. + * + * @param types the Types utilities + * @param catchLabels the catch labels + */ + public TryCatchFrame(Types types, List> catchLabels) { + this.types = types; + this.catchLabels = catchLabels; + } - @Override - public String toString() { - if (this.catchLabels.isEmpty()) { - return "TryCatchFrame: no catch labels."; - } else { - StringJoiner sb = new StringJoiner(System.lineSeparator(), "TryCatchFrame: ", ""); - for (IPair ptml : this.catchLabels) { - sb.add(ptml.first.toString() + " -> " + ptml.second.toString()); - } - return sb.toString(); - } + @Override + public String toString() { + if (this.catchLabels.isEmpty()) { + return "TryCatchFrame: no catch labels."; + } else { + StringJoiner sb = new StringJoiner(System.lineSeparator(), "TryCatchFrame: ", ""); + for (IPair ptml : this.catchLabels) { + sb.add(ptml.first.toString() + " -> " + ptml.second.toString()); + } + return sb.toString(); } + } - /** - * Given a type of thrown exception, add the set of possible control flow successor {@link - * Label}s to the argument set. Return true if the exception is known to be caught by one of - * those labels and false if it may propagate still further. - */ - @Override - public boolean possibleLabels(TypeMirror thrown, Set

Is set by {@link #setArrayExpression}. - */ - protected @Nullable ExpressionTree arrayExpression; - - /** - * Create an ArrayAccessNode. - * - * @param t tree for the array access - * @param array the node for the array expression being accessed - * @param index the node for the index used to access the array - */ - public ArrayAccessNode(ArrayAccessTree t, Node array, Node index) { - super(TreeUtils.typeOf(t)); - this.tree = t; - this.array = array; - this.index = index; - } - - /** - * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then - * return the expression in the for loop, e.g., {@code arr} in {@code for(Object o: arr}. - * Otherwise, return null. - * - * @return the array expression, or null if this is not an array desugared from an enhanced for - * loop - */ - public @Nullable ExpressionTree getArrayExpression() { - return arrayExpression; - } - - /** - * Set the array expression from a for loop. - * - * @param arrayExpression array expression - * @see #getArrayExpression() - */ - public void setArrayExpression(@Nullable ExpressionTree arrayExpression) { - this.arrayExpression = arrayExpression; - } - - /** - * Get the node that represents the array expression being accessed. - * - * @return the array expression node - */ - public Node getArray() { - return array; - } - - public Node getIndex() { - return index; - } - - @Override - public ArrayAccessTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitArrayAccess(this, p); - } - - @Override - public String toString() { - String base = getArray().toString() + "[" + getIndex() + "]"; - return base; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayAccessNode)) { - return false; - } - ArrayAccessNode other = (ArrayAccessNode) obj; - return getArray().equals(other.getArray()) && getIndex().equals(other.getIndex()); - } - - @Override - public int hashCode() { - return Objects.hash(getArray(), getIndex()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getArray(), getIndex()); + /** The corresponding ArrayAccessTree. */ + protected final ArrayAccessTree tree; + + /** The array expression being accessed. */ + protected final Node array; + + /** The index expresssion used to access the array. */ + protected final Node index; + + /** + * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then the + * {@code arrayExpression} field is the expression in the for loop, e.g., {@code arr} in {@code + * for(Object o: arr}. + * + *

Is set by {@link #setArrayExpression}. + */ + protected @Nullable ExpressionTree arrayExpression; + + /** + * Create an ArrayAccessNode. + * + * @param t tree for the array access + * @param array the node for the array expression being accessed + * @param index the node for the index used to access the array + */ + public ArrayAccessNode(ArrayAccessTree t, Node array, Node index) { + super(TreeUtils.typeOf(t)); + this.tree = t; + this.array = array; + this.index = index; + } + + /** + * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then return + * the expression in the for loop, e.g., {@code arr} in {@code for(Object o: arr}. Otherwise, + * return null. + * + * @return the array expression, or null if this is not an array desugared from an enhanced for + * loop + */ + public @Nullable ExpressionTree getArrayExpression() { + return arrayExpression; + } + + /** + * Set the array expression from a for loop. + * + * @param arrayExpression array expression + * @see #getArrayExpression() + */ + public void setArrayExpression(@Nullable ExpressionTree arrayExpression) { + this.arrayExpression = arrayExpression; + } + + /** + * Get the node that represents the array expression being accessed. + * + * @return the array expression node + */ + public Node getArray() { + return array; + } + + public Node getIndex() { + return index; + } + + @Override + public ArrayAccessTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitArrayAccess(this, p); + } + + @Override + public String toString() { + String base = getArray().toString() + "[" + getIndex() + "]"; + return base; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayAccessNode)) { + return false; } + ArrayAccessNode other = (ArrayAccessNode) obj; + return getArray().equals(other.getArray()) && getIndex().equals(other.getIndex()); + } + + @Override + public int hashCode() { + return Objects.hash(getArray(), getIndex()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getArray(), getIndex()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java index 9e4d33e905b..35684ab0e38 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java @@ -2,17 +2,14 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.plumelib.util.StringsPlume; - import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.plumelib.util.StringsPlume; /** * A node for new array creation. @@ -24,94 +21,94 @@ */ public class ArrayCreationNode extends Node { - /** The tree is null when an array is created for variable arity method calls. */ - protected final @Nullable NewArrayTree tree; - - /** - * The length of this list is the number of dimensions in the array. Each element is the size of - * the given dimension. It can be empty if initializers is non-empty, as in {@code new - * SomeType[] = { expr1, expr2, ... }}. - */ - protected final List dimensions; - - protected final List initializers; - - public ArrayCreationNode( - @Nullable NewArrayTree tree, - TypeMirror type, - List dimensions, - List initializers) { - super(type); - this.tree = tree; - this.dimensions = dimensions; - this.initializers = initializers; - } - - public List getDimensions() { - return dimensions; + /** The tree is null when an array is created for variable arity method calls. */ + protected final @Nullable NewArrayTree tree; + + /** + * The length of this list is the number of dimensions in the array. Each element is the size of + * the given dimension. It can be empty if initializers is non-empty, as in {@code new SomeType[] + * = { expr1, expr2, ... }}. + */ + protected final List dimensions; + + protected final List initializers; + + public ArrayCreationNode( + @Nullable NewArrayTree tree, + TypeMirror type, + List dimensions, + List initializers) { + super(type); + this.tree = tree; + this.dimensions = dimensions; + this.initializers = initializers; + } + + public List getDimensions() { + return dimensions; + } + + public Node getDimension(int i) { + return dimensions.get(i); + } + + public List getInitializers() { + return initializers; + } + + public Node getInitializer(int i) { + return initializers.get(i); + } + + @Override + public @Nullable Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitArrayCreation(this, p); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("new " + type); + if (!dimensions.isEmpty()) { + sb.append(" ("); + sb.append(StringsPlume.join(", ", dimensions)); + sb.append(")"); } - - public Node getDimension(int i) { - return dimensions.get(i); - } - - public List getInitializers() { - return initializers; - } - - public Node getInitializer(int i) { - return initializers.get(i); - } - - @Override - public @Nullable Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitArrayCreation(this, p); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("new " + type); - if (!dimensions.isEmpty()) { - sb.append(" ("); - sb.append(StringsPlume.join(", ", dimensions)); - sb.append(")"); - } - if (!initializers.isEmpty()) { - sb.append(" = {"); - sb.append(StringsPlume.join(", ", initializers)); - sb.append("}"); - } - return sb.toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayCreationNode)) { - return false; - } - ArrayCreationNode other = (ArrayCreationNode) obj; - - return getDimensions().equals(other.getDimensions()) - && getInitializers().equals(other.getInitializers()); - } - - @Override - public int hashCode() { - return Objects.hash(dimensions, initializers); + if (!initializers.isEmpty()) { + sb.append(" = {"); + sb.append(StringsPlume.join(", ", initializers)); + sb.append("}"); } + return sb.toString(); + } - @Override - @SideEffectFree - public Collection getOperands() { - ArrayList list = new ArrayList<>(dimensions.size() + initializers.size()); - list.addAll(dimensions); - list.addAll(initializers); - return list; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayCreationNode)) { + return false; } + ArrayCreationNode other = (ArrayCreationNode) obj; + + return getDimensions().equals(other.getDimensions()) + && getInitializers().equals(other.getInitializers()); + } + + @Override + public int hashCode() { + return Objects.hash(dimensions, initializers); + } + + @Override + @SideEffectFree + public Collection getOperands() { + ArrayList list = new ArrayList<>(dimensions.size() + initializers.size()); + list.addAll(dimensions); + list.addAll(initializers); + return list; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java index 088c84fbbaa..8d2bbd4bb99 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java @@ -2,16 +2,13 @@ import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node representing an array type used in an expression such as a field access. @@ -20,49 +17,49 @@ */ public class ArrayTypeNode extends Node { - protected final ArrayTypeTree tree; + protected final ArrayTypeTree tree; - /** For Types.isSameType. */ - protected final Types types; + /** For Types.isSameType. */ + protected final Types types; - public ArrayTypeNode(ArrayTypeTree tree, Types types) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.types = types; - } + public ArrayTypeNode(ArrayTypeTree tree, Types types) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.types = types; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitArrayType(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitArrayType(this, p); + } - @Override - public String toString() { - return tree.toString(); - } + @Override + public String toString() { + return tree.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayTypeNode)) { - return false; - } - ArrayTypeNode other = (ArrayTypeNode) obj; - return types.isSameType(getType(), other.getType()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayTypeNode)) { + return false; } + ArrayTypeNode other = (ArrayTypeNode) obj; + return types.isSameType(getType(), other.getType()); + } - @Override - public int hashCode() { - return Objects.hash(getType()); - } + @Override + public int hashCode() { + return Objects.hash(getType()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java index de2af1c76e4..27431a9bad0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java @@ -1,17 +1,14 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for the {@link AssertionError} when an assertion fails or when a method call marked {@link @@ -23,88 +20,88 @@ */ public class AssertionErrorNode extends Node { - /** Tree for the assert statement or assert method. */ - protected final Tree tree; - - /** The condition that if it is false, the assertion exception is thrown. */ - protected final Node condition; - - /** The node for the expression after {@code :} in the assert statement, or null. */ - protected final @Nullable Node detail; - - /** - * Creates an AssertionErrorNode. - * - * @param tree tree for the assert statement or assert method - * @param condition the node of the condition when if false the assertion exception is thrown - * @param detail node for the expression after {@code :} in the assert statement, or null - * @param type the type of the exception thrown - */ - public AssertionErrorNode(Tree tree, Node condition, @Nullable Node detail, TypeMirror type) { - // TODO: Find out the correct "type" for statements. - // Is it TypeKind.NONE? - super(type); - this.tree = tree; - this.condition = condition; - this.detail = detail; - } - - /** - * The node of the condition that if it is false, the assertion exception is thrown. - * - * @return the node of the condition that if it is false, the assertion exception is thrown - */ - @Pure - public Node getCondition() { - return condition; + /** Tree for the assert statement or assert method. */ + protected final Tree tree; + + /** The condition that if it is false, the assertion exception is thrown. */ + protected final Node condition; + + /** The node for the expression after {@code :} in the assert statement, or null. */ + protected final @Nullable Node detail; + + /** + * Creates an AssertionErrorNode. + * + * @param tree tree for the assert statement or assert method + * @param condition the node of the condition when if false the assertion exception is thrown + * @param detail node for the expression after {@code :} in the assert statement, or null + * @param type the type of the exception thrown + */ + public AssertionErrorNode(Tree tree, Node condition, @Nullable Node detail, TypeMirror type) { + // TODO: Find out the correct "type" for statements. + // Is it TypeKind.NONE? + super(type); + this.tree = tree; + this.condition = condition; + this.detail = detail; + } + + /** + * The node of the condition that if it is false, the assertion exception is thrown. + * + * @return the node of the condition that if it is false, the assertion exception is thrown + */ + @Pure + public Node getCondition() { + return condition; + } + + /** + * The node for the expression after {@code :} in the assert statement, or null. + * + * @return node for the expression after {@code :} in the assert statement, or null + */ + @Pure + public @Nullable Node getDetail() { + return detail; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitAssertionError(this, p); + } + + @Override + public String toString() { + return "AssertionError(" + getDetail() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof AssertionErrorNode)) { + return false; } - - /** - * The node for the expression after {@code :} in the assert statement, or null. - * - * @return node for the expression after {@code :} in the assert statement, or null - */ - @Pure - public @Nullable Node getDetail() { - return detail; - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitAssertionError(this, p); - } - - @Override - public String toString() { - return "AssertionError(" + getDetail() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof AssertionErrorNode)) { - return false; - } - AssertionErrorNode other = (AssertionErrorNode) obj; - return Objects.equals(getCondition(), other.getCondition()) - && Objects.equals(getDetail(), other.getDetail()); - } - - @Override - public int hashCode() { - return Objects.hash(getCondition(), getDetail()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - if (getDetail() == null) { - return Collections.singleton(getCondition()); - } - return Arrays.asList(getCondition(), getDetail()); + AssertionErrorNode other = (AssertionErrorNode) obj; + return Objects.equals(getCondition(), other.getCondition()) + && Objects.equals(getDetail(), other.getDetail()); + } + + @Override + public int hashCode() { + return Objects.hash(getCondition(), getDetail()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + if (getDetail() == null) { + return Collections.singleton(getCondition()); } + return Arrays.asList(getCondition(), getDetail()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java index 6cd525436df..63e9813e596 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java @@ -5,16 +5,14 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; - +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; -import java.util.Arrays; -import java.util.Collection; -import java.util.Objects; - /** * A node for an assignment: * @@ -37,120 +35,119 @@ */ public class AssignmentNode extends Node { - /** The underlying assignment tree. */ - protected final Tree tree; - - /** The node for the LHS of the assignment tree. */ - protected final Node lhs; - - /** The node for the RHS of the assignment tree. */ - protected final Node rhs; - - /** Whether the assignment node is synthetic */ - protected final boolean synthetic; - - /** - * Create a (non-synthetic) AssignmentNode. - * - * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} - * @param target the lhs of {@code tree} - * @param expression the rhs of {@code tree} - */ - public AssignmentNode(Tree tree, Node target, Node expression) { - this(tree, target, expression, false); - } - - /** - * Create an AssignmentNode. - * - * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} - * @param target the lhs of {@code tree} - * @param expression the rhs of {@code tree} - * @param synthetic whether the assignment node is synthetic - */ - public AssignmentNode(Tree tree, Node target, Node expression, boolean synthetic) { - super(TreeUtils.typeOf(tree)); - assert tree instanceof AssignmentTree - || tree instanceof VariableTree - || tree instanceof CompoundAssignmentTree - || tree instanceof UnaryTree; - assert target instanceof FieldAccessNode - || target instanceof LocalVariableNode - || target instanceof ArrayAccessNode; - this.tree = tree; - this.lhs = target; - this.rhs = expression; - this.synthetic = synthetic; - } - - /** - * Returns the left-hand-side of the assignment. - * - * @return the left-hand-side of the assignment - */ - @Pure - public Node getTarget() { - return lhs; - } - - /** - * Returns the right-hand-side of the assignment. - * - * @return the right-hand-side of the assignment - */ - @Pure - public Node getExpression() { - return rhs; - } - - @Override - @Pure - public Tree getTree() { - return tree; - } - - /** - * Check if the assignment node is synthetic, e.g. the synthetic assignment in a ternary - * expression. - * - * @return true if the assignment node is synthetic - */ - @Pure - public boolean isSynthetic() { - return synthetic; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitAssignment(this, p); - } - - @Override - @Pure - public String toString() { - return getTarget() + " = " + getExpression() + (synthetic ? " (synthetic)" : ""); - } - - @Override - @Pure - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof AssignmentNode)) { - return false; - } - AssignmentNode other = (AssignmentNode) obj; - return getTarget().equals(other.getTarget()) - && getExpression().equals(other.getExpression()); - } - - @Override - @Pure - public int hashCode() { - return Objects.hash(getTarget(), getExpression()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getTarget(), getExpression()); + /** The underlying assignment tree. */ + protected final Tree tree; + + /** The node for the LHS of the assignment tree. */ + protected final Node lhs; + + /** The node for the RHS of the assignment tree. */ + protected final Node rhs; + + /** Whether the assignment node is synthetic */ + protected final boolean synthetic; + + /** + * Create a (non-synthetic) AssignmentNode. + * + * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} + * @param target the lhs of {@code tree} + * @param expression the rhs of {@code tree} + */ + public AssignmentNode(Tree tree, Node target, Node expression) { + this(tree, target, expression, false); + } + + /** + * Create an AssignmentNode. + * + * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} + * @param target the lhs of {@code tree} + * @param expression the rhs of {@code tree} + * @param synthetic whether the assignment node is synthetic + */ + public AssignmentNode(Tree tree, Node target, Node expression, boolean synthetic) { + super(TreeUtils.typeOf(tree)); + assert tree instanceof AssignmentTree + || tree instanceof VariableTree + || tree instanceof CompoundAssignmentTree + || tree instanceof UnaryTree; + assert target instanceof FieldAccessNode + || target instanceof LocalVariableNode + || target instanceof ArrayAccessNode; + this.tree = tree; + this.lhs = target; + this.rhs = expression; + this.synthetic = synthetic; + } + + /** + * Returns the left-hand-side of the assignment. + * + * @return the left-hand-side of the assignment + */ + @Pure + public Node getTarget() { + return lhs; + } + + /** + * Returns the right-hand-side of the assignment. + * + * @return the right-hand-side of the assignment + */ + @Pure + public Node getExpression() { + return rhs; + } + + @Override + @Pure + public Tree getTree() { + return tree; + } + + /** + * Check if the assignment node is synthetic, e.g. the synthetic assignment in a ternary + * expression. + * + * @return true if the assignment node is synthetic + */ + @Pure + public boolean isSynthetic() { + return synthetic; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitAssignment(this, p); + } + + @Override + @Pure + public String toString() { + return getTarget() + " = " + getExpression() + (synthetic ? " (synthetic)" : ""); + } + + @Override + @Pure + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof AssignmentNode)) { + return false; } + AssignmentNode other = (AssignmentNode) obj; + return getTarget().equals(other.getTarget()) && getExpression().equals(other.getExpression()); + } + + @Override + @Pure + public int hashCode() { + return Objects.hash(getTarget(), getExpression()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getTarget(), getExpression()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java index d5ad0c2d3a7..8cadd084d8c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java @@ -1,12 +1,10 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.BinaryTree; - -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Arrays; import java.util.Collection; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a binary expression. @@ -19,33 +17,33 @@ */ public abstract class BinaryOperationNode extends Node { - protected final BinaryTree tree; - protected final Node left; - protected final Node right; - - protected BinaryOperationNode(BinaryTree tree, Node left, Node right) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.left = left; - this.right = right; - } - - public Node getLeftOperand() { - return left; - } - - public Node getRightOperand() { - return right; - } - - @Override - public BinaryTree getTree() { - return tree; - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getLeftOperand(), getRightOperand()); - } + protected final BinaryTree tree; + protected final Node left; + protected final Node right; + + protected BinaryOperationNode(BinaryTree tree, Node left, Node right) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public BinaryTree getTree() { + return tree; + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java index 35818d89410..3a0af45cf0e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the bitwise or logical (single bit) and operation: @@ -16,40 +14,40 @@ */ public class BitwiseAndNode extends BinaryOperationNode { - /** - * Constructs a {@link BitwiseAndNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public BitwiseAndNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.AND; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseAnd(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " & " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseAndNode)) { - return false; - } - BitwiseAndNode other = (BitwiseAndNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link BitwiseAndNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public BitwiseAndNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.AND; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseAnd(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " & " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseAndNode)) { + return false; } + BitwiseAndNode other = (BitwiseAndNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java index 527827f373f..56c56f6a579 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the bitwise complement operation: @@ -16,38 +14,38 @@ */ public class BitwiseComplementNode extends UnaryOperationNode { - /** - * Constructs a {@link BitwiseComplementNode}. - * - * @param tree the tree of the bitwise complement - * @param operand the operand of the bitwise complement - */ - public BitwiseComplementNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseComplement(this, p); - } - - @Override - public String toString() { - return "(~ " + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseComplementNode)) { - return false; - } - BitwiseComplementNode other = (BitwiseComplementNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(BitwiseComplementNode.class, getOperand()); + /** + * Constructs a {@link BitwiseComplementNode}. + * + * @param tree the tree of the bitwise complement + * @param operand the operand of the bitwise complement + */ + public BitwiseComplementNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseComplement(this, p); + } + + @Override + public String toString() { + return "(~ " + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseComplementNode)) { + return false; } + BitwiseComplementNode other = (BitwiseComplementNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(BitwiseComplementNode.class, getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java index d85a3522ecc..ad70c3ce32f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the bitwise or logical (single bit) or operation: @@ -16,40 +14,40 @@ */ public class BitwiseOrNode extends BinaryOperationNode { - /** - * Constructs a {@link BitwiseOrNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public BitwiseOrNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.OR; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseOr(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " | " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseOrNode)) { - return false; - } - BitwiseOrNode other = (BitwiseOrNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link BitwiseOrNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public BitwiseOrNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.OR; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseOr(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " | " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseOrNode)) { + return false; } + BitwiseOrNode other = (BitwiseOrNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java index 847e0aaac69..bd9206f42f5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the bitwise or logical (single bit) xor operation: @@ -16,40 +14,40 @@ */ public class BitwiseXorNode extends BinaryOperationNode { - /** - * Constructs a {@link BitwiseXorNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public BitwiseXorNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.XOR; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseXor(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " ^ " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseXorNode)) { - return false; - } - BitwiseXorNode other = (BitwiseXorNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link BitwiseXorNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public BitwiseXorNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.XOR; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseXor(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " ^ " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseXorNode)) { + return false; } + BitwiseXorNode other = (BitwiseXorNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java index 35edef75a06..9a812a343cd 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -15,33 +14,33 @@ */ public class BooleanLiteralNode extends ValueLiteralNode { - /** - * Create a new BooleanLiteralNode. - * - * @param t the tree for the literal value - */ - public BooleanLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.BOOLEAN_LITERAL; - } + /** + * Create a new BooleanLiteralNode. + * + * @param t the tree for the literal value + */ + public BooleanLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.BOOLEAN_LITERAL; + } - @Override - public Boolean getValue() { - return (Boolean) tree.getValue(); - } + @Override + public Boolean getValue() { + return (Boolean) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBooleanLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBooleanLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a BooleanLiteralNode - if (!(obj instanceof BooleanLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a BooleanLiteralNode + if (!(obj instanceof BooleanLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java index 540d37684ec..e3027fa44b9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java @@ -1,18 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.CaseTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.plumelib.util.StringsPlume; - import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; - import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.plumelib.util.StringsPlume; /** * A node for a case in a switch statement. Although a case has no abstract value, it can imply @@ -24,112 +21,112 @@ */ public class CaseNode extends Node { - /** The tree for this node. */ - protected final CaseTree tree; - - /** - * The Node for the assignment of the switch selector expression to a synthetic local variable. - */ - protected final AssignmentNode selectorExprAssignment; - - /** - * The case expressions to match the switch expression against: the operands of (possibly - * multiple) case labels. - */ - protected final List caseExprs; - - /** The guard (the expression in the {@code when} clause) for this case. */ - protected final @Nullable Node guard; - - /** - * Create a new CaseNode. - * - * @param tree the tree for this node - * @param selectorExprAssignment the Node for the assignment of the switch selector expression - * to a synthetic local variable - * @param caseExprs the case expression(s) to match the switch expression against - * @param guard the guard expression or null - * @param types a factory of utility methods for operating on types - */ - public CaseNode( - CaseTree tree, - AssignmentNode selectorExprAssignment, - List caseExprs, - @Nullable Node guard, - Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.selectorExprAssignment = selectorExprAssignment; - this.caseExprs = caseExprs; - this.guard = guard; - } - - /** - * The Node for the assignment of the switch selector expression to a synthetic local variable. - * This is used to refine the type of the switch selector expression in a case block. - * - * @return the assignment of the switch selector expression to a synthetic local variable - */ - public AssignmentNode getSwitchOperand() { - return selectorExprAssignment; - } - - /** - * Gets the nodes corresponding to the case expressions. There can be multiple expressions since - * Java 12. - * - * @return the nodes corresponding to the (potentially multiple) case expressions - */ - public List getCaseOperands() { - return caseExprs; - } - - /** - * Gets the node for the guard (the expression in the {@code when} clause). - * - * @return the node for the guard - */ - public @Nullable Node getGuard() { - return guard; - } - - @Override - public CaseTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitCase(this, p); - } - - @Override - public String toString() { - return "case " + StringsPlume.join(", ", getCaseOperands()) + ":"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof CaseNode)) { - return false; - } - CaseNode other = (CaseNode) obj; - return getSwitchOperand().equals(other.getSwitchOperand()) - && getCaseOperands().equals(other.getCaseOperands()); - } - - @Override - public int hashCode() { - return Objects.hash(getSwitchOperand(), getCaseOperands()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - List caseOperands = getCaseOperands(); - ArrayList operands = new ArrayList<>(caseOperands.size() + 1); - operands.add(getSwitchOperand()); - operands.addAll(caseOperands); - return operands; + /** The tree for this node. */ + protected final CaseTree tree; + + /** + * The Node for the assignment of the switch selector expression to a synthetic local variable. + */ + protected final AssignmentNode selectorExprAssignment; + + /** + * The case expressions to match the switch expression against: the operands of (possibly + * multiple) case labels. + */ + protected final List caseExprs; + + /** The guard (the expression in the {@code when} clause) for this case. */ + protected final @Nullable Node guard; + + /** + * Create a new CaseNode. + * + * @param tree the tree for this node + * @param selectorExprAssignment the Node for the assignment of the switch selector expression to + * a synthetic local variable + * @param caseExprs the case expression(s) to match the switch expression against + * @param guard the guard expression or null + * @param types a factory of utility methods for operating on types + */ + public CaseNode( + CaseTree tree, + AssignmentNode selectorExprAssignment, + List caseExprs, + @Nullable Node guard, + Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.selectorExprAssignment = selectorExprAssignment; + this.caseExprs = caseExprs; + this.guard = guard; + } + + /** + * The Node for the assignment of the switch selector expression to a synthetic local variable. + * This is used to refine the type of the switch selector expression in a case block. + * + * @return the assignment of the switch selector expression to a synthetic local variable + */ + public AssignmentNode getSwitchOperand() { + return selectorExprAssignment; + } + + /** + * Gets the nodes corresponding to the case expressions. There can be multiple expressions since + * Java 12. + * + * @return the nodes corresponding to the (potentially multiple) case expressions + */ + public List getCaseOperands() { + return caseExprs; + } + + /** + * Gets the node for the guard (the expression in the {@code when} clause). + * + * @return the node for the guard + */ + public @Nullable Node getGuard() { + return guard; + } + + @Override + public CaseTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitCase(this, p); + } + + @Override + public String toString() { + return "case " + StringsPlume.join(", ", getCaseOperands()) + ":"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CaseNode)) { + return false; } + CaseNode other = (CaseNode) obj; + return getSwitchOperand().equals(other.getSwitchOperand()) + && getCaseOperands().equals(other.getCaseOperands()); + } + + @Override + public int hashCode() { + return Objects.hash(getSwitchOperand(), getCaseOperands()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + List caseOperands = getCaseOperands(); + ArrayList operands = new ArrayList<>(caseOperands.size() + 1); + operands.add(getSwitchOperand()); + operands.addAll(caseOperands); + return operands; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java index 95d42cbe51d..0b7e8e85eef 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java @@ -1,67 +1,64 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; - import java.util.Objects; - import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; /** A CatchMarkerNode is a marker node for the beginning or end of a catch block. */ public class CatchMarkerNode extends MarkerNode { - /** The type of the exception parameter. */ - private final TypeMirror catchType; + /** The type of the exception parameter. */ + private final TypeMirror catchType; - /** The type utilities. */ - private final Types types; + /** The type utilities. */ + private final Types types; - /** - * Creates a new CatchMarkerNode. - * - * @param tree the tree - * @param startOrEnd {@code "start"} or {@code "end"} - * @param catchType the type of the exception parameter - * @param types the type utilities - */ - public CatchMarkerNode( - @Nullable Tree tree, String startOrEnd, TypeMirror catchType, Types types) { - super( - tree, - startOrEnd - + " of catch block for " - + TypesUtils.simpleTypeName(catchType) - + " #" - + (tree == null ? "null" : TreeUtils.treeUids.get(tree)), - types); - this.catchType = catchType; - this.types = types; - } + /** + * Creates a new CatchMarkerNode. + * + * @param tree the tree + * @param startOrEnd {@code "start"} or {@code "end"} + * @param catchType the type of the exception parameter + * @param types the type utilities + */ + public CatchMarkerNode( + @Nullable Tree tree, String startOrEnd, TypeMirror catchType, Types types) { + super( + tree, + startOrEnd + + " of catch block for " + + TypesUtils.simpleTypeName(catchType) + + " #" + + (tree == null ? "null" : TreeUtils.treeUids.get(tree)), + types); + this.catchType = catchType; + this.types = types; + } - /** - * Returns the type of the exception parameter. - * - * @return the type of the exception parameter - */ - public TypeMirror getCatchType() { - return catchType; - } + /** + * Returns the type of the exception parameter. + * + * @return the type of the exception parameter + */ + public TypeMirror getCatchType() { + return catchType; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof CatchMarkerNode)) { - return false; - } - CatchMarkerNode other = (CatchMarkerNode) obj; - return types.isSameType(getCatchType(), other.getCatchType()) && super.equals(other); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CatchMarkerNode)) { + return false; } + CatchMarkerNode other = (CatchMarkerNode) obj; + return types.isSameType(getCatchType(), other.getCatchType()) && super.equals(other); + } - @Override - public int hashCode() { - return Objects.hash(tree, getMessage(), catchType); - } + @Override + public int hashCode() { + return Objects.hash(tree, getMessage(), catchType); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java index 91c27e85ebb..868b5cf843c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -16,33 +15,33 @@ */ public class CharacterLiteralNode extends ValueLiteralNode { - /** - * Create a new CharacterLiteralNode. - * - * @param t the character literal - */ - public CharacterLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.CHAR_LITERAL; - } + /** + * Create a new CharacterLiteralNode. + * + * @param t the character literal + */ + public CharacterLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.CHAR_LITERAL; + } - @Override - public Character getValue() { - return (Character) tree.getValue(); - } + @Override + public Character getValue() { + return (Character) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitCharacterLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitCharacterLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a CharacterLiteralNode - if (!(obj instanceof CharacterLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a CharacterLiteralNode + if (!(obj instanceof CharacterLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java index 4a526b42e1d..0aa5e75b932 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java @@ -1,14 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ClassTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node representing a class declaration that occurs within a method, for example, an anonymous @@ -17,49 +15,49 @@ */ public class ClassDeclarationNode extends Node { - protected final ClassTree tree; + protected final ClassTree tree; - public ClassDeclarationNode(ClassTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } + public ClassDeclarationNode(ClassTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - @Override - public ClassTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitClassDeclaration(this, p); - } + @Override + public ClassTree getTree() { + return tree; + } - @Override - public String toString() { - return tree.toString(); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitClassDeclaration(this, p); + } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + @Override + public String toString() { + return tree.toString(); + } - ClassDeclarationNode that = (ClassDeclarationNode) o; - return Objects.equals(tree, that.tree); + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; } - - @Override - public int hashCode() { - return Objects.hash(tree); + if (o == null || getClass() != o.getClass()) { + return false; } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + ClassDeclarationNode that = (ClassDeclarationNode) o; + return Objects.equals(tree, that.tree); + } + + @Override + public int hashCode() { + return Objects.hash(tree); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java index b34299862fc..ebad7f80fc4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java @@ -4,19 +4,16 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node representing a class name used in an expression such as a static method invocation. @@ -25,103 +22,103 @@ */ public class ClassNameNode extends Node { - /** The tree for this node. */ - protected final @Nullable Tree tree; - - /** The class named by this node. Either a TypeElement or a TypeParameterElement. */ - protected final Element element; - - /** The parent name, if any. */ - protected final @Nullable Node parent; - - public ClassNameNode(IdentifierTree tree) { - super(TreeUtils.typeOf(tree)); - assert tree.getKind() == Tree.Kind.IDENTIFIER; - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - Element element = TreeUtils.elementFromUse(tree); - assert element instanceof TypeElement || element instanceof TypeParameterElement - : "@AssumeAssertion(nullness)"; - this.element = element; - this.parent = null; - } - - /** - * Create a new ClassNameNode. - * - * @param tree the class tree for this node - */ - public ClassNameNode(ClassTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.element = TreeUtils.elementFromDeclaration(tree); - this.parent = null; - } - - public ClassNameNode(MemberSelectTree tree, Node parent) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - Element element = TreeUtils.elementFromUse(tree); - assert element instanceof TypeElement || element instanceof TypeParameterElement - : "@AssumeAssertion(nullness)"; - this.element = element; - this.parent = parent; - } - - public ClassNameNode(TypeMirror type, Element element) { - super(type); - this.tree = null; - this.element = element; - assert element instanceof TypeElement || element instanceof TypeParameterElement; - this.parent = null; + /** The tree for this node. */ + protected final @Nullable Tree tree; + + /** The class named by this node. Either a TypeElement or a TypeParameterElement. */ + protected final Element element; + + /** The parent name, if any. */ + protected final @Nullable Node parent; + + public ClassNameNode(IdentifierTree tree) { + super(TreeUtils.typeOf(tree)); + assert tree.getKind() == Tree.Kind.IDENTIFIER; + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + Element element = TreeUtils.elementFromUse(tree); + assert element instanceof TypeElement || element instanceof TypeParameterElement + : "@AssumeAssertion(nullness)"; + this.element = element; + this.parent = null; + } + + /** + * Create a new ClassNameNode. + * + * @param tree the class tree for this node + */ + public ClassNameNode(ClassTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.element = TreeUtils.elementFromDeclaration(tree); + this.parent = null; + } + + public ClassNameNode(MemberSelectTree tree, Node parent) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + Element element = TreeUtils.elementFromUse(tree); + assert element instanceof TypeElement || element instanceof TypeParameterElement + : "@AssumeAssertion(nullness)"; + this.element = element; + this.parent = parent; + } + + public ClassNameNode(TypeMirror type, Element element) { + super(type); + this.tree = null; + this.element = element; + assert element instanceof TypeElement || element instanceof TypeParameterElement; + this.parent = null; + } + + public Element getElement() { + return element; + } + + /** The parent node of the current node. */ + public @Nullable Node getParent() { + return parent; + } + + @Override + public @Nullable Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitClassName(this, p); + } + + @Override + public String toString() { + return getElement().getSimpleName().toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ClassNameNode)) { + return false; } - - public Element getElement() { - return element; - } - - /** The parent node of the current node. */ - public @Nullable Node getParent() { - return parent; - } - - @Override - public @Nullable Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitClassName(this, p); - } - - @Override - public String toString() { - return getElement().getSimpleName().toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ClassNameNode)) { - return false; - } - ClassNameNode other = (ClassNameNode) obj; - return Objects.equals(getParent(), other.getParent()) - && getElement().equals(other.getElement()); - } - - @Override - public int hashCode() { - return Objects.hash(getElement(), getParent()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - if (parent == null) { - return Collections.emptyList(); - } - return Collections.singleton(parent); + ClassNameNode other = (ClassNameNode) obj; + return Objects.equals(getParent(), other.getParent()) + && getElement().equals(other.getElement()); + } + + @Override + public int hashCode() { + return Objects.hash(getElement(), getParent()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + if (parent == null) { + return Collections.emptyList(); } + return Collections.singleton(parent); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java index d87073ca647..2a5f1ef2e4a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for a conditional and expression: @@ -16,40 +14,40 @@ */ public class ConditionalAndNode extends BinaryOperationNode { - /** - * Create a new ConditionalAndNode. - * - * @param tree the conditional-and tree for this node - * @param left the first argument - * @param right the second argument - */ - public ConditionalAndNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.CONDITIONAL_AND; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitConditionalAnd(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " && " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ConditionalAndNode)) { - return false; - } - ConditionalAndNode other = (ConditionalAndNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Create a new ConditionalAndNode. + * + * @param tree the conditional-and tree for this node + * @param left the first argument + * @param right the second argument + */ + public ConditionalAndNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.CONDITIONAL_AND; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitConditionalAnd(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " && " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ConditionalAndNode)) { + return false; } + ConditionalAndNode other = (ConditionalAndNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java index f6940487813..07629bbfe39 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for a conditional not expression: @@ -16,38 +14,38 @@ */ public class ConditionalNotNode extends UnaryOperationNode { - /** - * Create a new ConditionalNotNode. - * - * @param tree the logical-complement tree for this node - * @param operand the boolean expression being negated - */ - public ConditionalNotNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.LOGICAL_COMPLEMENT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitConditionalNot(this, p); - } - - @Override - public String toString() { - return "(!" + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ConditionalNotNode)) { - return false; - } - ConditionalNotNode other = (ConditionalNotNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(ConditionalNotNode.class, getOperand()); + /** + * Create a new ConditionalNotNode. + * + * @param tree the logical-complement tree for this node + * @param operand the boolean expression being negated + */ + public ConditionalNotNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.LOGICAL_COMPLEMENT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitConditionalNot(this, p); + } + + @Override + public String toString() { + return "(!" + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ConditionalNotNode)) { + return false; } + ConditionalNotNode other = (ConditionalNotNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(ConditionalNotNode.class, getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java index 38704da607c..793264a6e87 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for a conditional or expression: @@ -16,40 +14,40 @@ */ public class ConditionalOrNode extends BinaryOperationNode { - /** - * Create a new ConditionalOrNode. - * - * @param tree the conditional-or tree for this node - * @param left the first argument - * @param right the second argument - */ - public ConditionalOrNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.CONDITIONAL_OR; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitConditionalOr(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " || " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ConditionalOrNode)) { - return false; - } - ConditionalOrNode other = (ConditionalOrNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Create a new ConditionalOrNode. + * + * @param tree the conditional-or tree for this node + * @param left the first argument + * @param right the second argument + */ + public ConditionalOrNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.CONDITIONAL_OR; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitConditionalOr(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " || " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ConditionalOrNode)) { + return false; } + ConditionalOrNode other = (ConditionalOrNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java index 33ae66cc71e..d8bfaf04110 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java @@ -1,98 +1,94 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; /** A node for a deconstrutor pattern. */ public class DeconstructorPatternNode extends Node { - /** - * The {@code DeconstructorPatternTree}, declared as {@link Tree} to permit this file to compile - * under JDK 20 and earlier. - */ - protected final Tree deconstructorPattern; + /** + * The {@code DeconstructorPatternTree}, declared as {@link Tree} to permit this file to compile + * under JDK 20 and earlier. + */ + protected final Tree deconstructorPattern; - /** A list of nested pattern nodes. */ - protected final List nestedPatterns; + /** A list of nested pattern nodes. */ + protected final List nestedPatterns; - /** - * Creates a {@code DeconstructorPatternNode}. - * - * @param type the type of the node - * @param deconstructorPattern the {@code DeconstructorPatternTree} - * @param nestedPatterns a list of nested pattern nodes - */ - public DeconstructorPatternNode( - TypeMirror type, Tree deconstructorPattern, List nestedPatterns) { - super(type); - this.deconstructorPattern = deconstructorPattern; - this.nestedPatterns = nestedPatterns; - } + /** + * Creates a {@code DeconstructorPatternNode}. + * + * @param type the type of the node + * @param deconstructorPattern the {@code DeconstructorPatternTree} + * @param nestedPatterns a list of nested pattern nodes + */ + public DeconstructorPatternNode( + TypeMirror type, Tree deconstructorPattern, List nestedPatterns) { + super(type); + this.deconstructorPattern = deconstructorPattern; + this.nestedPatterns = nestedPatterns; + } - @Override - @Pure - public @Nullable Tree getTree() { - return deconstructorPattern; - } + @Override + @Pure + public @Nullable Tree getTree() { + return deconstructorPattern; + } - /** - * Returns the nested patterns. - * - * @return the nested patterns - */ - @Pure - public List getNestedPatterns() { - return nestedPatterns; - } + /** + * Returns the nested patterns. + * + * @return the nested patterns + */ + @Pure + public List getNestedPatterns() { + return nestedPatterns; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitDeconstructorPattern(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitDeconstructorPattern(this, p); + } - @Override - @Pure - public Collection getOperands() { - return nestedPatterns; - } + @Override + @Pure + public Collection getOperands() { + return nestedPatterns; + } - /** - * A list of nested binding variables. This is lazily initialized and should only be accessed by - * {@link #getBindingVariables()}. - */ - protected @MonotonicNonNull List bindingVariables = null; + /** + * A list of nested binding variables. This is lazily initialized and should only be accessed by + * {@link #getBindingVariables()}. + */ + protected @MonotonicNonNull List bindingVariables = null; - /** - * Return all the binding variables in this pattern. - * - * @return all the binding variables in this pattern - */ - public List getBindingVariables() { - if (bindingVariables == null) { - if (nestedPatterns.isEmpty()) { - bindingVariables = Collections.emptyList(); - } else { - bindingVariables = new ArrayList<>(nestedPatterns.size()); - for (Node patternNode : nestedPatterns) { - if (patternNode instanceof LocalVariableNode) { - bindingVariables.add((LocalVariableNode) patternNode); - } else { - bindingVariables.addAll( - ((DeconstructorPatternNode) patternNode).getBindingVariables()); - } - } - bindingVariables = Collections.unmodifiableList(bindingVariables); - } + /** + * Return all the binding variables in this pattern. + * + * @return all the binding variables in this pattern + */ + public List getBindingVariables() { + if (bindingVariables == null) { + if (nestedPatterns.isEmpty()) { + bindingVariables = Collections.emptyList(); + } else { + bindingVariables = new ArrayList<>(nestedPatterns.size()); + for (Node patternNode : nestedPatterns) { + if (patternNode instanceof LocalVariableNode) { + bindingVariables.add((LocalVariableNode) patternNode); + } else { + bindingVariables.addAll(((DeconstructorPatternNode) patternNode).getBindingVariables()); + } } - return bindingVariables; + bindingVariables = Collections.unmodifiableList(bindingVariables); + } } + return bindingVariables; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java index 4907b234f40..7b014a27533 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -15,33 +14,33 @@ */ public class DoubleLiteralNode extends ValueLiteralNode { - /** - * Create a new DoubleLiteralNode. - * - * @param t the tree for the literal value - */ - public DoubleLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.DOUBLE_LITERAL; - } + /** + * Create a new DoubleLiteralNode. + * + * @param t the tree for the literal value + */ + public DoubleLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.DOUBLE_LITERAL; + } - @Override - public Double getValue() { - return (Double) tree.getValue(); - } + @Override + public Double getValue() { + return (Double) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitDoubleLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitDoubleLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a DoubleLiteralNode - if (!(obj instanceof DoubleLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a DoubleLiteralNode + if (!(obj instanceof DoubleLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java index 389da3c4fdd..0dab020afae 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for an equality check: @@ -16,40 +14,40 @@ */ public class EqualToNode extends BinaryOperationNode { - /** - * Create a new EqualToNode object. - * - * @param tree the tree for this node - * @param left the first argument - * @param right the second argument - */ - public EqualToNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.EQUAL_TO; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitEqualTo(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " == " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof EqualToNode)) { - return false; - } - EqualToNode other = (EqualToNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Create a new EqualToNode object. + * + * @param tree the tree for this node + * @param left the first argument + * @param right the second argument + */ + public EqualToNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.EQUAL_TO; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitEqualTo(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " == " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof EqualToNode)) { + return false; } + EqualToNode other = (EqualToNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java index 1176d970713..dba86498d56 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java @@ -1,7 +1,6 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.IdentifierTree; - import org.checkerframework.javacutil.TreeUtils; /** @@ -13,26 +12,26 @@ */ public class ExplicitThisNode extends ThisNode { - protected final IdentifierTree tree; + protected final IdentifierTree tree; - public ExplicitThisNode(IdentifierTree t) { - super(TreeUtils.typeOf(t)); - assert t.getName().contentEquals("this"); - tree = t; - } + public ExplicitThisNode(IdentifierTree t) { + super(TreeUtils.typeOf(t)); + assert t.getName().contentEquals("this"); + tree = t; + } - @Override - public IdentifierTree getTree() { - return tree; - } + @Override + public IdentifierTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitExplicitThis(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitExplicitThis(this, p); + } - @Override - public String toString() { - return "this"; - } + @Override + public String toString() { + return "this"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java index 60c12afeb70..14da7926d0d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java @@ -2,13 +2,11 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.TreeUtils; /** * A node for an expression that is used as a statement. @@ -22,46 +20,46 @@ * node. */ public class ExpressionStatementNode extends Node { - /** The expression constituting this ExpressionStatementNode. */ - protected final ExpressionTree tree; + /** The expression constituting this ExpressionStatementNode. */ + protected final ExpressionTree tree; - /** - * Construct a ExpressionStatementNode. - * - * @param t the expression constituting this ExpressionStatementNode - */ - public ExpressionStatementNode(ExpressionTree t) { - super(TreeUtils.typeOf(t)); - tree = t; - } + /** + * Construct a ExpressionStatementNode. + * + * @param t the expression constituting this ExpressionStatementNode + */ + public ExpressionStatementNode(ExpressionTree t) { + super(TreeUtils.typeOf(t)); + tree = t; + } - @Override - public @Nullable Tree getTree() { - return null; - } + @Override + public @Nullable Tree getTree() { + return null; + } - @Override - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + public Collection getOperands() { + return Collections.emptyList(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitExpressionStatement(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitExpressionStatement(this, p); + } - @Override - public String toString() { - return "expression statement " + tree.toString(); - } + @Override + public String toString() { + return "expression statement " + tree.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - return this == obj; - } + @Override + public boolean equals(@Nullable Object obj) { + return this == obj; + } - @Override - public int hashCode() { - return Objects.hash(toString()); - } + @Override + public int hashCode() { + return Objects.hash(toString()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java index 1cf3af49fe3..7ad59de9eef 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java @@ -3,19 +3,16 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; - +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; - -import javax.lang.model.element.VariableElement; - /** * A node for a field access, including a method accesses: * @@ -24,106 +21,105 @@ * */ public class FieldAccessNode extends Node { - /** The tree of the field access. */ - protected final Tree tree; - - /** The element of the accessed field. */ - protected final VariableElement element; - - /** The name of the accessed field. */ - protected final String field; - - /** The receiver node of the field access. */ - protected final Node receiver; - - /** - * Creates a new FieldAccessNode. - * - * @param tree the tree from which to create a FieldAccessNode - * @param receiver the receiver for the resulting FieldAccessNode - */ - public FieldAccessNode(Tree tree, Node receiver) { - super(TreeUtils.typeOf(tree)); - assert TreeUtils.isFieldAccess(tree); - this.tree = tree; - this.receiver = receiver; - this.field = TreeUtils.getFieldName(tree); - - if (tree instanceof MemberSelectTree) { - MemberSelectTree mstree = (MemberSelectTree) tree; - assert TreeUtils.isUseOfElement(mstree) : "@AssumeAssertion(nullness): tree kind"; - this.element = TreeUtils.variableElementFromUse(mstree); - } else if (tree instanceof IdentifierTree) { - IdentifierTree itree = (IdentifierTree) tree; - assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; - this.element = TreeUtils.variableElementFromUse(itree); - } else { - throw new BugInCF("unexpected tree %s [%s]", tree, tree.getClass()); - } - } - - public FieldAccessNode(Tree tree, VariableElement element, Node receiver) { - super(element.asType()); - this.tree = tree; - this.element = element; - this.receiver = receiver; - this.field = element.getSimpleName().toString(); - } - - public VariableElement getElement() { - return element; - } - - public Node getReceiver() { - return receiver; - } - - public String getFieldName() { - return field; - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFieldAccess(this, p); - } - - @Override - public String toString() { - return getReceiver() + "." + field; - } - - /** - * Determine whether the field is static or not. - * - * @return whether the field is static or not - */ - public boolean isStatic() { - return ElementUtils.isStatic(getElement()); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FieldAccessNode)) { - return false; - } - FieldAccessNode other = (FieldAccessNode) obj; - return getReceiver().equals(other.getReceiver()) - && getFieldName().equals(other.getFieldName()); - } - - @Override - public int hashCode() { - return Objects.hash(getReceiver(), getFieldName()); + /** The tree of the field access. */ + protected final Tree tree; + + /** The element of the accessed field. */ + protected final VariableElement element; + + /** The name of the accessed field. */ + protected final String field; + + /** The receiver node of the field access. */ + protected final Node receiver; + + /** + * Creates a new FieldAccessNode. + * + * @param tree the tree from which to create a FieldAccessNode + * @param receiver the receiver for the resulting FieldAccessNode + */ + public FieldAccessNode(Tree tree, Node receiver) { + super(TreeUtils.typeOf(tree)); + assert TreeUtils.isFieldAccess(tree); + this.tree = tree; + this.receiver = receiver; + this.field = TreeUtils.getFieldName(tree); + + if (tree instanceof MemberSelectTree) { + MemberSelectTree mstree = (MemberSelectTree) tree; + assert TreeUtils.isUseOfElement(mstree) : "@AssumeAssertion(nullness): tree kind"; + this.element = TreeUtils.variableElementFromUse(mstree); + } else if (tree instanceof IdentifierTree) { + IdentifierTree itree = (IdentifierTree) tree; + assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; + this.element = TreeUtils.variableElementFromUse(itree); + } else { + throw new BugInCF("unexpected tree %s [%s]", tree, tree.getClass()); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(receiver); + } + + public FieldAccessNode(Tree tree, VariableElement element, Node receiver) { + super(element.asType()); + this.tree = tree; + this.element = element; + this.receiver = receiver; + this.field = element.getSimpleName().toString(); + } + + public VariableElement getElement() { + return element; + } + + public Node getReceiver() { + return receiver; + } + + public String getFieldName() { + return field; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFieldAccess(this, p); + } + + @Override + public String toString() { + return getReceiver() + "." + field; + } + + /** + * Determine whether the field is static or not. + * + * @return whether the field is static or not + */ + public boolean isStatic() { + return ElementUtils.isStatic(getElement()); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FieldAccessNode)) { + return false; } + FieldAccessNode other = (FieldAccessNode) obj; + return getReceiver().equals(other.getReceiver()) && getFieldName().equals(other.getFieldName()); + } + + @Override + public int hashCode() { + return Objects.hash(getReceiver(), getFieldName()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(receiver); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java index a7ad2b4b44c..6f41a1c12ab 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -15,33 +14,33 @@ */ public class FloatLiteralNode extends ValueLiteralNode { - /** - * Create a new FloatLiteralNode. - * - * @param t the tree for the literal value - */ - public FloatLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.FLOAT_LITERAL; - } + /** + * Create a new FloatLiteralNode. + * + * @param t the tree for the literal value + */ + public FloatLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.FLOAT_LITERAL; + } - @Override - public Float getValue() { - return (Float) tree.getValue(); - } + @Override + public Float getValue() { + return (Float) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFloatLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFloatLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a FloatLiteralNode - if (!(obj instanceof FloatLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a FloatLiteralNode + if (!(obj instanceof FloatLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java index a9fcd2748ef..12e91bbdcfa 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the floating-point division: @@ -16,40 +14,40 @@ */ public class FloatingDivisionNode extends BinaryOperationNode { - /** - * Constructs a {@link FloatingDivisionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public FloatingDivisionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.DIVIDE; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFloatingDivision(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FloatingDivisionNode)) { - return false; - } - FloatingDivisionNode other = (FloatingDivisionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link FloatingDivisionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public FloatingDivisionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.DIVIDE; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFloatingDivision(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FloatingDivisionNode)) { + return false; } + FloatingDivisionNode other = (FloatingDivisionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java index fb3f25eca42..134d18caa54 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the floating-point remainder: @@ -16,40 +14,40 @@ */ public class FloatingRemainderNode extends BinaryOperationNode { - /** - * Constructs a {@link FloatingRemainderNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public FloatingRemainderNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.REMAINDER; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFloatingRemainder(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FloatingRemainderNode)) { - return false; - } - FloatingRemainderNode other = (FloatingRemainderNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link FloatingRemainderNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public FloatingRemainderNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.REMAINDER; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFloatingRemainder(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FloatingRemainderNode)) { + return false; } + FloatingRemainderNode other = (FloatingRemainderNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java index 8e5ce2340ed..830728c8d71 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java @@ -3,16 +3,14 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MemberReferenceTree; import com.sun.source.tree.Tree; - +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; - /** * A node for member references and lambdas. * @@ -33,62 +31,62 @@ */ public class FunctionalInterfaceNode extends Node { - protected final Tree tree; + protected final Tree tree; - public FunctionalInterfaceNode(MemberReferenceTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } + public FunctionalInterfaceNode(MemberReferenceTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - public FunctionalInterfaceNode(LambdaExpressionTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } + public FunctionalInterfaceNode(LambdaExpressionTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMemberReference(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMemberReference(this, p); + } - @Override - public String toString() { - if (tree instanceof LambdaExpressionTree) { - return "FunctionalInterfaceNode:" + ((LambdaExpressionTree) tree).getBodyKind(); - } else if (tree instanceof MemberReferenceTree) { - return "FunctionalInterfaceNode:" + ((MemberReferenceTree) tree).getName(); - } else { - // This should never happen. - throw new BugInCF("Invalid tree in FunctionalInterfaceNode"); - } + @Override + public String toString() { + if (tree instanceof LambdaExpressionTree) { + return "FunctionalInterfaceNode:" + ((LambdaExpressionTree) tree).getBodyKind(); + } else if (tree instanceof MemberReferenceTree) { + return "FunctionalInterfaceNode:" + ((MemberReferenceTree) tree).getName(); + } else { + // This should never happen. + throw new BugInCF("Invalid tree in FunctionalInterfaceNode"); } + } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - FunctionalInterfaceNode that = (FunctionalInterfaceNode) o; + FunctionalInterfaceNode that = (FunctionalInterfaceNode) o; - return tree != null ? tree.equals(that.tree) : that.tree == null; - } + return tree != null ? tree.equals(that.tree) : that.tree == null; + } - @Override - public int hashCode() { - return Objects.hash(tree); - } + @Override + public int hashCode() { + return Objects.hash(tree); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java index 5679d665fa4..771d2992d20 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the greater than comparison: @@ -16,40 +14,40 @@ */ public class GreaterThanNode extends BinaryOperationNode { - /** - * Constructs a {@link GreaterThanNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public GreaterThanNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.GREATER_THAN; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitGreaterThan(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " > " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof GreaterThanNode)) { - return false; - } - GreaterThanNode other = (GreaterThanNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link GreaterThanNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public GreaterThanNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.GREATER_THAN; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitGreaterThan(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " > " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof GreaterThanNode)) { + return false; } + GreaterThanNode other = (GreaterThanNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java index 3f76c748a5c..f6ecab0ab9d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the greater than or equal comparison: @@ -16,40 +14,40 @@ */ public class GreaterThanOrEqualNode extends BinaryOperationNode { - /** - * Constructs a {@link GreaterThanOrEqualNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public GreaterThanOrEqualNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.GREATER_THAN_EQUAL; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitGreaterThanOrEqual(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " >= " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof GreaterThanOrEqualNode)) { - return false; - } - GreaterThanOrEqualNode other = (GreaterThanOrEqualNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link GreaterThanOrEqualNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public GreaterThanOrEqualNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.GREATER_THAN_EQUAL; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitGreaterThanOrEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >= " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof GreaterThanOrEqualNode)) { + return false; } + GreaterThanOrEqualNode other = (GreaterThanOrEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java index 09b555d316c..751b50a8126 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java @@ -1,33 +1,31 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; /** A node to model the implicit {@code this}, e.g., in a field access. */ public class ImplicitThisNode extends ThisNode { - public ImplicitThisNode(TypeMirror type) { - super(type); - } - - @Override - public @Nullable Tree getTree() { - return null; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitImplicitThis(this, p); - } - - // In an inner class context, an implicit this may need to be represented as "Outer.this" rather - // than just as "this". This is context-dependent, and toString doesn't know if it is being - // used in an inner class context. - @Override - public String toString() { - return "(this)"; - } + public ImplicitThisNode(TypeMirror type) { + super(type); + } + + @Override + public @Nullable Tree getTree() { + return null; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitImplicitThis(this, p); + } + + // In an inner class context, an implicit this may need to be represented as "Outer.this" rather + // than just as "this". This is context-dependent, and toString doesn't know if it is being + // used in an inner class context. + @Override + public String toString() { + return "(this)"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java index c72bb91886b..9df3aac02b8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java @@ -1,20 +1,17 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.InstanceOfTree; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TypesUtils; - import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; - import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TypesUtils; /** * A node for the instanceof operator: @@ -23,156 +20,156 @@ */ public class InstanceOfNode extends Node { - /** The value being tested. */ - protected final Node operand; - - /** The reference type being tested against. */ - protected final TypeMirror refType; - - /** The tree associated with this node. */ - protected final InstanceOfTree tree; - - /** The node of the pattern if one exists. */ - protected final @Nullable Node patternNode; - - /** For Types.isSameType. */ - protected final Types types; - - /** - * Create an InstanceOfNode. - * - * @param tree instanceof tree - * @param operand the expression in the instanceof tree - * @param refType the type in the instanceof - * @param types types util - */ - public InstanceOfNode(InstanceOfTree tree, Node operand, TypeMirror refType, Types types) { - this(tree, operand, null, refType, types); - } - - /** - * Create an InstanceOfNode. - * - * @param tree instanceof tree - * @param operand the expression in the instanceof tree - * @param patternNode the pattern node or null if there is none - * @param refType the type in the instanceof - * @param types types util - */ - public InstanceOfNode( - InstanceOfTree tree, - Node operand, - @Nullable Node patternNode, - TypeMirror refType, - Types types) { - super(types.getPrimitiveType(TypeKind.BOOLEAN)); - this.tree = tree; - this.operand = operand; - this.refType = refType; - this.types = types; - this.patternNode = patternNode; - } - - public Node getOperand() { - return operand; - } - - /** - * Returns the binding variable for this instanceof, or null if one does not exist. - * - * @return the binding variable for this instanceof, or null if one does not exist - * @deprecated Use {@link #getPatternNode()} or {@link #getBindingVariables()} instead. - */ - @Deprecated // 2023-09-24 - public @Nullable LocalVariableNode getBindingVariable() { - if (patternNode instanceof LocalVariableNode) { - return (LocalVariableNode) patternNode; - } - return null; - } - - /** - * A list of all binding variables in this instanceof. This is lazily initialized, use {@link - * #getBindingVariables()}. - */ - protected @MonotonicNonNull List bindingVariables = null; - - /** - * Return all the binding variables in this instanceof. - * - * @return all the binding variables in this instanceof - */ - public List getBindingVariables() { - if (bindingVariables == null) { - if (patternNode instanceof DeconstructorPatternNode) { - bindingVariables = ((DeconstructorPatternNode) patternNode).getBindingVariables(); - } else if (patternNode instanceof LocalVariableNode) { - bindingVariables = Collections.singletonList((LocalVariableNode) patternNode); - } else { - bindingVariables = Collections.emptyList(); - } - } - return bindingVariables; + /** The value being tested. */ + protected final Node operand; + + /** The reference type being tested against. */ + protected final TypeMirror refType; + + /** The tree associated with this node. */ + protected final InstanceOfTree tree; + + /** The node of the pattern if one exists. */ + protected final @Nullable Node patternNode; + + /** For Types.isSameType. */ + protected final Types types; + + /** + * Create an InstanceOfNode. + * + * @param tree instanceof tree + * @param operand the expression in the instanceof tree + * @param refType the type in the instanceof + * @param types types util + */ + public InstanceOfNode(InstanceOfTree tree, Node operand, TypeMirror refType, Types types) { + this(tree, operand, null, refType, types); + } + + /** + * Create an InstanceOfNode. + * + * @param tree instanceof tree + * @param operand the expression in the instanceof tree + * @param patternNode the pattern node or null if there is none + * @param refType the type in the instanceof + * @param types types util + */ + public InstanceOfNode( + InstanceOfTree tree, + Node operand, + @Nullable Node patternNode, + TypeMirror refType, + Types types) { + super(types.getPrimitiveType(TypeKind.BOOLEAN)); + this.tree = tree; + this.operand = operand; + this.refType = refType; + this.types = types; + this.patternNode = patternNode; + } + + public Node getOperand() { + return operand; + } + + /** + * Returns the binding variable for this instanceof, or null if one does not exist. + * + * @return the binding variable for this instanceof, or null if one does not exist + * @deprecated Use {@link #getPatternNode()} or {@link #getBindingVariables()} instead. + */ + @Deprecated // 2023-09-24 + public @Nullable LocalVariableNode getBindingVariable() { + if (patternNode instanceof LocalVariableNode) { + return (LocalVariableNode) patternNode; } - - /** - * Returns the pattern for this instanceof, or null if one does not exist. - * - * @return the pattern for this instanceof, or null if one does not exist - */ - public @Nullable Node getPatternNode() { - return patternNode; - } - - /** - * The reference type being tested against. - * - * @return the reference type - */ - public TypeMirror getRefType() { - return refType; - } - - @Override - public InstanceOfTree getTree() { - return tree; + return null; + } + + /** + * A list of all binding variables in this instanceof. This is lazily initialized, use {@link + * #getBindingVariables()}. + */ + protected @MonotonicNonNull List bindingVariables = null; + + /** + * Return all the binding variables in this instanceof. + * + * @return all the binding variables in this instanceof + */ + public List getBindingVariables() { + if (bindingVariables == null) { + if (patternNode instanceof DeconstructorPatternNode) { + bindingVariables = ((DeconstructorPatternNode) patternNode).getBindingVariables(); + } else if (patternNode instanceof LocalVariableNode) { + bindingVariables = Collections.singletonList((LocalVariableNode) patternNode); + } else { + bindingVariables = Collections.emptyList(); + } } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitInstanceOf(this, p); - } - - @Override - public String toString() { - return "(" - + getOperand() - + " instanceof " - + TypesUtils.simpleTypeName(getRefType()) - + (patternNode == null ? "" : " " + getPatternNode()) - + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof InstanceOfNode)) { - return false; - } - InstanceOfNode other = (InstanceOfNode) obj; - // TODO: TypeMirror.equals may be too restrictive. - // Check whether Types.isSameType is the better comparison. - return getOperand().equals(other.getOperand()) - && types.isSameType(getRefType(), other.getRefType()); - } - - @Override - public int hashCode() { - return Objects.hash(InstanceOfNode.class, getOperand()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); + return bindingVariables; + } + + /** + * Returns the pattern for this instanceof, or null if one does not exist. + * + * @return the pattern for this instanceof, or null if one does not exist + */ + public @Nullable Node getPatternNode() { + return patternNode; + } + + /** + * The reference type being tested against. + * + * @return the reference type + */ + public TypeMirror getRefType() { + return refType; + } + + @Override + public InstanceOfTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitInstanceOf(this, p); + } + + @Override + public String toString() { + return "(" + + getOperand() + + " instanceof " + + TypesUtils.simpleTypeName(getRefType()) + + (patternNode == null ? "" : " " + getPatternNode()) + + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof InstanceOfNode)) { + return false; } + InstanceOfNode other = (InstanceOfNode) obj; + // TODO: TypeMirror.equals may be too restrictive. + // Check whether Types.isSameType is the better comparison. + return getOperand().equals(other.getOperand()) + && types.isSameType(getRefType(), other.getRefType()); + } + + @Override + public int hashCode() { + return Objects.hash(InstanceOfNode.class, getOperand()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java index 8307fc95edc..ff6e5bd23e3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the integer division: @@ -16,40 +14,40 @@ */ public class IntegerDivisionNode extends BinaryOperationNode { - /** - * Constructs an {@link IntegerDivisionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public IntegerDivisionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.DIVIDE; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitIntegerDivision(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof IntegerDivisionNode)) { - return false; - } - IntegerDivisionNode other = (IntegerDivisionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs an {@link IntegerDivisionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public IntegerDivisionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.DIVIDE; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitIntegerDivision(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof IntegerDivisionNode)) { + return false; } + IntegerDivisionNode other = (IntegerDivisionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java index b7c05eaa9d2..209852218ee 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,33 +13,33 @@ */ public class IntegerLiteralNode extends ValueLiteralNode { - /** - * Create a new IntegerLiteralNode. - * - * @param t the tree for the literal value - */ - public IntegerLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.INT_LITERAL; - } + /** + * Create a new IntegerLiteralNode. + * + * @param t the tree for the literal value + */ + public IntegerLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.INT_LITERAL; + } - @Override - public Integer getValue() { - return (Integer) tree.getValue(); - } + @Override + public Integer getValue() { + return (Integer) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitIntegerLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitIntegerLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a IntegerLiteralNode - if (!(obj instanceof IntegerLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a IntegerLiteralNode + if (!(obj instanceof IntegerLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java index d80fc1339a2..6a0a1b3f0f0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the integer remainder: @@ -16,40 +14,40 @@ */ public class IntegerRemainderNode extends BinaryOperationNode { - /** - * Constructs an {@link IntegerRemainderNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public IntegerRemainderNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.REMAINDER; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitIntegerRemainder(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof IntegerRemainderNode)) { - return false; - } - IntegerRemainderNode other = (IntegerRemainderNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs an {@link IntegerRemainderNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public IntegerRemainderNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.REMAINDER; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitIntegerRemainder(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof IntegerRemainderNode)) { + return false; } + IntegerRemainderNode other = (IntegerRemainderNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java index 27beb525617..48369f80034 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java @@ -1,89 +1,87 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ExpressionTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** A node for the single expression body of a single-expression lambda. */ public class LambdaResultExpressionNode extends Node { - /** Tree for the lambda expression body. */ - protected final ExpressionTree tree; + /** Tree for the lambda expression body. */ + protected final ExpressionTree tree; - /** Final CFG node corresponding to the lambda expression body. */ - protected final Node result; + /** Final CFG node corresponding to the lambda expression body. */ + protected final Node result; - /** - * Creates a LambdaResultExpressionNode. - * - * @param t tree for the lambda expression body - * @param result final CFG node corresponding to the lambda expression body - */ - public LambdaResultExpressionNode(ExpressionTree t, Node result) { - super(TreeUtils.typeOf(t)); - this.result = result; - tree = t; - } + /** + * Creates a LambdaResultExpressionNode. + * + * @param t tree for the lambda expression body + * @param result final CFG node corresponding to the lambda expression body + */ + public LambdaResultExpressionNode(ExpressionTree t, Node result) { + super(TreeUtils.typeOf(t)); + this.result = result; + tree = t; + } - /** - * Returns the final node of the CFG corresponding to the lambda expression body (see {@link - * #getTree()}). - * - * @return the final node of the CFG corresponding to the lambda expression body - */ - public Node getResult() { - return result; - } + /** + * Returns the final node of the CFG corresponding to the lambda expression body (see {@link + * #getTree()}). + * + * @return the final node of the CFG corresponding to the lambda expression body + */ + public Node getResult() { + return result; + } - /** - * Returns the {@link ExpressionTree} corresponding to the body of a lambda expression with an - * expression body (e.g. X for ({@code o -> X}) where X is an expression and not a {...} block). - * - * @return the {@link ExpressionTree} corresponding to the body of a lambda expression with an - * expression body - */ - @Override - public ExpressionTree getTree() { - return tree; - } + /** + * Returns the {@link ExpressionTree} corresponding to the body of a lambda expression with an + * expression body (e.g. X for ({@code o -> X}) where X is an expression and not a {...} block). + * + * @return the {@link ExpressionTree} corresponding to the body of a lambda expression with an + * expression body + */ + @Override + public ExpressionTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLambdaResultExpression(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLambdaResultExpression(this, p); + } - @Override - public String toString() { - return "-> " + result; - } + @Override + public String toString() { + return "-> " + result; + } - @Override - public boolean equals(@Nullable Object obj) { - // No need to compare tree, since in a well-formed LambdaResultExpressionNode, result will - // be the same only when tree is the same (this is similar to ReturnNode). - if (!(obj instanceof LambdaResultExpressionNode)) { - return false; - } - LambdaResultExpressionNode other = (LambdaResultExpressionNode) obj; - return Objects.equals(result, other.result); + @Override + public boolean equals(@Nullable Object obj) { + // No need to compare tree, since in a well-formed LambdaResultExpressionNode, result will + // be the same only when tree is the same (this is similar to ReturnNode). + if (!(obj instanceof LambdaResultExpressionNode)) { + return false; } + LambdaResultExpressionNode other = (LambdaResultExpressionNode) obj; + return Objects.equals(result, other.result); + } - @Override - public int hashCode() { - // No need to incorporate tree, since in a well-formed LambdaResultExpressionNode, result - // will be the same only when tree is the same (this is similar to ReturnNode). - return Objects.hash(LambdaResultExpressionNode.class, result); - } + @Override + public int hashCode() { + // No need to incorporate tree, since in a well-formed LambdaResultExpressionNode, result + // will be the same only when tree is the same (this is similar to ReturnNode). + return Objects.hash(LambdaResultExpressionNode.class, result); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(result); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(result); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java index 8ab462127e3..b6b38e85062 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for bitwise left shift operations: @@ -16,40 +14,40 @@ */ public class LeftShiftNode extends BinaryOperationNode { - /** - * Constructs a {@link LeftShiftNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public LeftShiftNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.LEFT_SHIFT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLeftShift(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " << " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LeftShiftNode)) { - return false; - } - LeftShiftNode other = (LeftShiftNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link LeftShiftNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public LeftShiftNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.LEFT_SHIFT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLeftShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " << " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LeftShiftNode)) { + return false; } + LeftShiftNode other = (LeftShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java index 5237ee99d2a..8b5595938f3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the less than comparison: @@ -18,40 +16,40 @@ */ public class LessThanNode extends BinaryOperationNode { - /** - * Constructs a {@link LessThanNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public LessThanNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.LESS_THAN; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLessThan(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " < " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LessThanNode)) { - return false; - } - LessThanNode other = (LessThanNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link LessThanNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public LessThanNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.LESS_THAN; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLessThan(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " < " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LessThanNode)) { + return false; } + LessThanNode other = (LessThanNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java index cb29c4adbcf..5f13d004455 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the less than or equal comparison: @@ -16,40 +14,40 @@ */ public class LessThanOrEqualNode extends BinaryOperationNode { - /** - * Constructs a {@link LessThanOrEqualNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public LessThanOrEqualNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.LESS_THAN_EQUAL; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLessThanOrEqual(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " <= " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LessThanOrEqualNode)) { - return false; - } - LessThanOrEqualNode other = (LessThanOrEqualNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link LessThanOrEqualNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public LessThanOrEqualNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.LESS_THAN_EQUAL; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLessThanOrEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " <= " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LessThanOrEqualNode)) { + return false; } + LessThanOrEqualNode other = (LessThanOrEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java index 6d477b7962a..ca171272bed 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java @@ -3,16 +3,13 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.element.VariableElement; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a local variable or a parameter: @@ -27,99 +24,99 @@ // TODO: don't use for parameters, as they don't have a tree public class LocalVariableNode extends Node { - /** The tree for the local variable. */ - protected final Tree tree; - - /** The receiver node for the local variable, {@code null} otherwise. */ - protected final @Nullable Node receiver; - - /** - * Create a new local variable node for the given tree. - * - * @param tree the tree for the local variable: a VariableTree or an IdentifierTree - */ - public LocalVariableNode(Tree tree) { - this(tree, null); + /** The tree for the local variable. */ + protected final Tree tree; + + /** The receiver node for the local variable, {@code null} otherwise. */ + protected final @Nullable Node receiver; + + /** + * Create a new local variable node for the given tree. + * + * @param tree the tree for the local variable: a VariableTree or an IdentifierTree + */ + public LocalVariableNode(Tree tree) { + this(tree, null); + } + + /** + * Create a new local variable node for the given tree and receiver. + * + * @param tree the tree for the local variable: a VariableTree or an IdentifierTree + * @param receiver the receiver for the local variable, or null if none + */ + public LocalVariableNode(Tree tree, @Nullable Node receiver) { + super(TreeUtils.typeOf(tree)); + // IdentifierTree for normal uses of the local variable or parameter, + // and VariableTree for declarations or the translation of an initializer block + assert tree != null; + assert tree instanceof IdentifierTree || tree instanceof VariableTree; + this.tree = tree; + this.receiver = receiver; + } + + /** + * Returns the element associated with this local variable. + * + * @return the element associated with this local variable + */ + public VariableElement getElement() { + VariableElement el; + if (tree instanceof IdentifierTree) { + IdentifierTree itree = (IdentifierTree) tree; + assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; + el = TreeUtils.variableElementFromUse(itree); + } else { + assert tree instanceof VariableTree; + el = TreeUtils.elementFromDeclaration((VariableTree) tree); } + return el; + } - /** - * Create a new local variable node for the given tree and receiver. - * - * @param tree the tree for the local variable: a VariableTree or an IdentifierTree - * @param receiver the receiver for the local variable, or null if none - */ - public LocalVariableNode(Tree tree, @Nullable Node receiver) { - super(TreeUtils.typeOf(tree)); - // IdentifierTree for normal uses of the local variable or parameter, - // and VariableTree for declarations or the translation of an initializer block - assert tree != null; - assert tree instanceof IdentifierTree || tree instanceof VariableTree; - this.tree = tree; - this.receiver = receiver; - } - - /** - * Returns the element associated with this local variable. - * - * @return the element associated with this local variable - */ - public VariableElement getElement() { - VariableElement el; - if (tree instanceof IdentifierTree) { - IdentifierTree itree = (IdentifierTree) tree; - assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; - el = TreeUtils.variableElementFromUse(itree); - } else { - assert tree instanceof VariableTree; - el = TreeUtils.elementFromDeclaration((VariableTree) tree); - } - return el; - } + /** The receiver node for the local variable, {@code null} otherwise. */ + public @Nullable Node getReceiver() { + return receiver; + } - /** The receiver node for the local variable, {@code null} otherwise. */ - public @Nullable Node getReceiver() { - return receiver; + public String getName() { + if (tree instanceof IdentifierTree) { + return ((IdentifierTree) tree).getName().toString(); } - - public String getName() { - if (tree instanceof IdentifierTree) { - return ((IdentifierTree) tree).getName().toString(); - } - return ((VariableTree) tree).getName().toString(); - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLocalVariable(this, p); - } - - @Override - public String toString() { - return getName().toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LocalVariableNode)) { - return false; - } - LocalVariableNode other = (LocalVariableNode) obj; - return getName().equals(other.getName()); - } - - @Override - public int hashCode() { - return Objects.hash(getName()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); + return ((VariableTree) tree).getName().toString(); + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLocalVariable(this, p); + } + + @Override + public String toString() { + return getName().toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LocalVariableNode)) { + return false; } + LocalVariableNode other = (LocalVariableNode) obj; + return getName().equals(other.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(getName()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java index 081633ead10..14818335eb8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -15,33 +14,33 @@ */ public class LongLiteralNode extends ValueLiteralNode { - /** - * Create a new LongLiteralNode. - * - * @param t the tree for the literal value - */ - public LongLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.LONG_LITERAL; - } + /** + * Create a new LongLiteralNode. + * + * @param t the tree for the literal value + */ + public LongLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.LONG_LITERAL; + } - @Override - public Long getValue() { - return (Long) tree.getValue(); - } + @Override + public Long getValue() { + return (Long) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLongLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLongLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a LongLiteralNode - if (!(obj instanceof LongLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a LongLiteralNode + if (!(obj instanceof LongLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java index 658551a458e..06a4fc8b0e3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * MarkerNodes are no-op Nodes used for debugging information. They can hold a Tree and a message, @@ -20,56 +17,55 @@ */ public class MarkerNode extends Node { - protected final @Nullable Tree tree; - protected final String message; + protected final @Nullable Tree tree; + protected final String message; - public MarkerNode(@Nullable Tree tree, String message, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.message = message; - } + public MarkerNode(@Nullable Tree tree, String message, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.message = message; + } - public String getMessage() { - return message; - } + public String getMessage() { + return message; + } - @Override - public @Nullable Tree getTree() { - return tree; - } + @Override + public @Nullable Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMarker(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMarker(this, p); + } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("marker ("); - sb.append(message); - sb.append(")"); - return sb.toString(); - } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("marker ("); + sb.append(message); + sb.append(")"); + return sb.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof MarkerNode)) { - return false; - } - MarkerNode other = (MarkerNode) obj; - return Objects.equals(getTree(), other.getTree()) - && getMessage().equals(other.getMessage()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof MarkerNode)) { + return false; } + MarkerNode other = (MarkerNode) obj; + return Objects.equals(getTree(), other.getTree()) && getMessage().equals(other.getMessage()); + } - @Override - public int hashCode() { - return Objects.hash(tree, getMessage()); - } + @Override + public int hashCode() { + return Objects.hash(tree, getMessage()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java index b35eabe3d04..276db20f5af 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java @@ -2,17 +2,14 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a method access, including a receiver: @@ -22,90 +19,90 @@ * */ public class MethodAccessNode extends Node { - /** The tree of the method access. */ - protected final ExpressionTree tree; - - /** The element of the accessed method. */ - protected final ExecutableElement method; - - /** The receiver node of the method access. */ - protected final Node receiver; - - /** - * Create a new MethodAccessNode. - * - * @param tree the expression that is a method access - * @param receiver the receiver - */ - public MethodAccessNode(ExpressionTree tree, Node receiver) { - this(tree, (ExecutableElement) TreeUtils.elementFromUse(tree), receiver); - } - - /** - * Create a new MethodAccessNode. - * - * @param tree the expression that is a method access - * @param method the element for the method - * @param receiver the receiver - */ - public MethodAccessNode(ExpressionTree tree, ExecutableElement method, Node receiver) { - super(TreeUtils.typeOf(tree)); - assert TreeUtils.isMethodAccess(tree); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - this.method = method; - this.receiver = receiver; - } - - public ExecutableElement getMethod() { - return method; - } - - public Node getReceiver() { - return receiver; - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMethodAccess(this, p); - } - - @Override - public String toString() { - return getReceiver() + "." + method.getSimpleName(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof MethodAccessNode)) { - return false; - } - MethodAccessNode other = (MethodAccessNode) obj; - return getReceiver().equals(other.getReceiver()) && getMethod().equals(other.getMethod()); - } - - @Override - public int hashCode() { - return Objects.hash(getReceiver(), getMethod()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(receiver); - } - - /** - * Determine whether the method is static or not. - * - * @return whether the method is static or not - */ - public boolean isStatic() { - return ElementUtils.isStatic(getMethod()); + /** The tree of the method access. */ + protected final ExpressionTree tree; + + /** The element of the accessed method. */ + protected final ExecutableElement method; + + /** The receiver node of the method access. */ + protected final Node receiver; + + /** + * Create a new MethodAccessNode. + * + * @param tree the expression that is a method access + * @param receiver the receiver + */ + public MethodAccessNode(ExpressionTree tree, Node receiver) { + this(tree, (ExecutableElement) TreeUtils.elementFromUse(tree), receiver); + } + + /** + * Create a new MethodAccessNode. + * + * @param tree the expression that is a method access + * @param method the element for the method + * @param receiver the receiver + */ + public MethodAccessNode(ExpressionTree tree, ExecutableElement method, Node receiver) { + super(TreeUtils.typeOf(tree)); + assert TreeUtils.isMethodAccess(tree); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + this.method = method; + this.receiver = receiver; + } + + public ExecutableElement getMethod() { + return method; + } + + public Node getReceiver() { + return receiver; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMethodAccess(this, p); + } + + @Override + public String toString() { + return getReceiver() + "." + method.getSimpleName(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof MethodAccessNode)) { + return false; } + MethodAccessNode other = (MethodAccessNode) obj; + return getReceiver().equals(other.getReceiver()) && getMethod().equals(other.getMethod()); + } + + @Override + public int hashCode() { + return Objects.hash(getReceiver(), getMethod()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(receiver); + } + + /** + * Determine whether the method is static or not. + * + * @return whether the method is static or not + */ + public boolean isStatic() { + return ElementUtils.isStatic(getMethod()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java index 5c444b2e4ec..212a0860db3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java @@ -4,17 +4,15 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; -import org.plumelib.util.StringsPlume; - import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; +import org.plumelib.util.StringsPlume; /** * A node for method invocation. @@ -28,128 +26,128 @@ */ public class MethodInvocationNode extends Node { - /** The tree for the method invocation. */ - protected final @Nullable MethodInvocationTree tree; - - /** - * The MethodAccessNode for the method being invoked. Includes the receiver if any. For a static - * method, the receiver may be a class name. - */ - protected final MethodAccessNode target; - - /** The arguments of the method invocation. */ - protected final List arguments; - - /** The tree path to the method invocation. */ - protected final TreePath treePath; - - /** - * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an - * enhanced for loop, then the {@code iterExpression} field is the expression in the for loop, - * e.g., {@code iter} in {@code for(Object o: iter}. - * - *

Is set by {@link #setIterableExpression}. - */ - protected @Nullable ExpressionTree iterableExpression; - - /** - * Create a MethodInvocationNode. - * - * @param tree for the method invocation - * @param target the MethodAccessNode for the method being invoked - * @param arguments arguments of the method invocation - * @param treePath path to the method invocation - */ - public MethodInvocationNode( - @Nullable MethodInvocationTree tree, - MethodAccessNode target, - List arguments, - TreePath treePath) { - super(tree != null ? TreeUtils.typeOf(tree) : target.getMethod().getReturnType()); - this.tree = tree; - this.target = target; - this.arguments = arguments; - this.treePath = treePath; - } - - public MethodInvocationNode(MethodAccessNode target, List arguments, TreePath treePath) { - this(null, target, arguments, treePath); - } - - public MethodAccessNode getTarget() { - return target; - } - - public List getArguments() { - return arguments; - } - - public Node getArgument(int i) { - return arguments.get(i); - } - - public TreePath getTreePath() { - return treePath; - } - - /** - * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an - * enhanced for loop, then return the expression in the for loop, e.g., {@code iter} in {@code - * for(Object o: iter}. Otherwise, return null. - * - * @return the iter expression, or null if this is not a {@link Iterator#next()} from an - * enhanced for loop - */ - public @Nullable ExpressionTree getIterableExpression() { - return iterableExpression; - } - - /** - * Set the iterable expression from a for loop. - * - * @param iterableExpression iterable expression - * @see #getIterableExpression() - */ - public void setIterableExpression(@Nullable ExpressionTree iterableExpression) { - this.iterableExpression = iterableExpression; - } - - @Override - public @Nullable MethodInvocationTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMethodInvocation(this, p); - } - - @Override - public String toString() { - return target + "(" + StringsPlume.join(", ", arguments) + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof MethodInvocationNode)) { - return false; - } - MethodInvocationNode other = (MethodInvocationNode) obj; - - return getTarget().equals(other.getTarget()) && getArguments().equals(other.getArguments()); - } - - @Override - public int hashCode() { - return Objects.hash(target, arguments); - } - - @Override - @SideEffectFree - public Collection getOperands() { - List list = new ArrayList<>(1 + arguments.size()); - list.add(target); - list.addAll(arguments); - return list; + /** The tree for the method invocation. */ + protected final @Nullable MethodInvocationTree tree; + + /** + * The MethodAccessNode for the method being invoked. Includes the receiver if any. For a static + * method, the receiver may be a class name. + */ + protected final MethodAccessNode target; + + /** The arguments of the method invocation. */ + protected final List arguments; + + /** The tree path to the method invocation. */ + protected final TreePath treePath; + + /** + * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an + * enhanced for loop, then the {@code iterExpression} field is the expression in the for loop, + * e.g., {@code iter} in {@code for(Object o: iter}. + * + *

Is set by {@link #setIterableExpression}. + */ + protected @Nullable ExpressionTree iterableExpression; + + /** + * Create a MethodInvocationNode. + * + * @param tree for the method invocation + * @param target the MethodAccessNode for the method being invoked + * @param arguments arguments of the method invocation + * @param treePath path to the method invocation + */ + public MethodInvocationNode( + @Nullable MethodInvocationTree tree, + MethodAccessNode target, + List arguments, + TreePath treePath) { + super(tree != null ? TreeUtils.typeOf(tree) : target.getMethod().getReturnType()); + this.tree = tree; + this.target = target; + this.arguments = arguments; + this.treePath = treePath; + } + + public MethodInvocationNode(MethodAccessNode target, List arguments, TreePath treePath) { + this(null, target, arguments, treePath); + } + + public MethodAccessNode getTarget() { + return target; + } + + public List getArguments() { + return arguments; + } + + public Node getArgument(int i) { + return arguments.get(i); + } + + public TreePath getTreePath() { + return treePath; + } + + /** + * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an + * enhanced for loop, then return the expression in the for loop, e.g., {@code iter} in {@code + * for(Object o: iter}. Otherwise, return null. + * + * @return the iter expression, or null if this is not a {@link Iterator#next()} from an enhanced + * for loop + */ + public @Nullable ExpressionTree getIterableExpression() { + return iterableExpression; + } + + /** + * Set the iterable expression from a for loop. + * + * @param iterableExpression iterable expression + * @see #getIterableExpression() + */ + public void setIterableExpression(@Nullable ExpressionTree iterableExpression) { + this.iterableExpression = iterableExpression; + } + + @Override + public @Nullable MethodInvocationTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMethodInvocation(this, p); + } + + @Override + public String toString() { + return target + "(" + StringsPlume.join(", ", arguments) + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof MethodInvocationNode)) { + return false; } + MethodInvocationNode other = (MethodInvocationNode) obj; + + return getTarget().equals(other.getTarget()) && getArguments().equals(other.getArguments()); + } + + @Override + public int hashCode() { + return Objects.hash(target, arguments); + } + + @Override + @SideEffectFree + public Collection getOperands() { + List list = new ArrayList<>(1 + arguments.size()); + list.add(target); + list.addAll(arguments); + return list; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java index 9f187c33426..68e601e45a7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TypesUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TypesUtils; /** * A node for the narrowing primitive conversion operation. See JLS 5.1.3 for the definition of @@ -22,53 +19,53 @@ */ public class NarrowingConversionNode extends Node { - protected final Tree tree; - protected final Node operand; + protected final Tree tree; + protected final Node operand; - public NarrowingConversionNode(Tree tree, Node operand, TypeMirror type) { - super(type); - assert TypesUtils.isPrimitive(type) : "non-primitive type in narrowing conversion"; - this.tree = tree; - this.operand = operand; - } + public NarrowingConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + assert TypesUtils.isPrimitive(type) : "non-primitive type in narrowing conversion"; + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNarrowingConversion(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNarrowingConversion(this, p); + } - @Override - public String toString() { - return "NarrowingConversion(" + getOperand() + ", " + type + ")"; - } + @Override + public String toString() { + return "NarrowingConversion(" + getOperand() + ", " + type + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NarrowingConversionNode)) { - return false; - } - NarrowingConversionNode other = (NarrowingConversionNode) obj; - return getOperand().equals(other.getOperand()) - && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NarrowingConversionNode)) { + return false; } + NarrowingConversionNode other = (NarrowingConversionNode) obj; + return getOperand().equals(other.getOperand()) + && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); + } - @Override - public int hashCode() { - return Objects.hash(NarrowingConversionNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(NarrowingConversionNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java index 0fdd5dfbe34..d249fa59344 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java @@ -1,7 +1,11 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.block.Block; @@ -10,13 +14,6 @@ import org.checkerframework.dataflow.qual.SideEffectFree; import org.plumelib.util.UniqueId; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicLong; - -import javax.lang.model.type.TypeMirror; - /** * A node in the abstract representation used for Java code inside a basic block. * @@ -38,179 +35,179 @@ */ public abstract class Node implements UniqueId { - /** - * The basic block this node belongs to. If null, this object represents a method formal - * parameter. - * - *

Is set by {@link #setBlock}. - */ - protected @Nullable Block block; - - /** - * Is this node an l-value? - * - *

Is set by {@link #setLValue}. - */ - protected boolean lvalue = false; - - /** - * Does this node represent a tree that appears in the source code (true) or one that the CFG - * builder added while desugaring (false). - * - *

Is set by {@link #setInSource}. - */ - protected boolean inSource = true; - - /** - * The type of this node. For {@link Node}s with {@link Tree}s, this type is the type of the - * {@link Tree}. Otherwise, it is the type is set by the {@link CFGBuilder}. - */ - protected final TypeMirror type; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - @Pure - public long getUid(@UnknownInitialization Node this) { - return uid; - } - - /** - * Creates a new Node. - * - * @param type the type of the node - */ - protected Node(TypeMirror type) { - assert type != null; - this.type = type; - } - - /** - * Returns the basic block this node belongs to (or {@code null} if it represents the parameter - * of a method). - * - * @return the basic block this node belongs to (or {@code null} if it represents the parameter - * of a method) - */ - @Pure - public @Nullable Block getBlock() { - return block; + /** + * The basic block this node belongs to. If null, this object represents a method formal + * parameter. + * + *

Is set by {@link #setBlock}. + */ + protected @Nullable Block block; + + /** + * Is this node an l-value? + * + *

Is set by {@link #setLValue}. + */ + protected boolean lvalue = false; + + /** + * Does this node represent a tree that appears in the source code (true) or one that the CFG + * builder added while desugaring (false). + * + *

Is set by {@link #setInSource}. + */ + protected boolean inSource = true; + + /** + * The type of this node. For {@link Node}s with {@link Tree}s, this type is the type of the + * {@link Tree}. Otherwise, it is the type is set by the {@link CFGBuilder}. + */ + protected final TypeMirror type; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + @Pure + public long getUid(@UnknownInitialization Node this) { + return uid; + } + + /** + * Creates a new Node. + * + * @param type the type of the node + */ + protected Node(TypeMirror type) { + assert type != null; + this.type = type; + } + + /** + * Returns the basic block this node belongs to (or {@code null} if it represents the parameter of + * a method). + * + * @return the basic block this node belongs to (or {@code null} if it represents the parameter of + * a method) + */ + @Pure + public @Nullable Block getBlock() { + return block; + } + + /** Set the basic block this node belongs to. */ + public void setBlock(Block b) { + block = b; + } + + /** + * Returns the {@link Tree} in the abstract syntax tree, or {@code null} if no corresponding tree + * exists. For instance, this is the case for an {@link ImplicitThisNode}. + * + * @return the corresponding {@link Tree} or {@code null} + */ + @Pure + public abstract @Nullable Tree getTree(); + + /** + * Returns a {@link TypeMirror} representing the type of a {@link Node}. A {@link Node} will + * always have a type even when it has no {@link Tree}. + * + * @return a {@link TypeMirror} representing the type of this {@link Node} + */ + @Pure + public TypeMirror getType() { + return type; + } + + /** + * Accept method of the visitor pattern. + * + * @param result type of the operation + * @param

parameter type + * @param visitor the visitor to be applied to this node + * @param p the parameter for this operation + */ + public abstract R accept(NodeVisitor visitor, P p); + + /** Is the node an lvalue or not? */ + @Pure + public boolean isLValue() { + return lvalue; + } + + /** Make this node an l-value. */ + public void setLValue() { + lvalue = true; + } + + /** + * Return whether this node represents a tree that appears in the source code (true) or one that + * the CFG or builder added while desugaring (false). + * + * @return whether this node represents a tree that appears in the source code + */ + @Pure + public boolean getInSource() { + return inSource; + } + + public void setInSource(boolean inSrc) { + inSource = inSrc; + } + + /** + * Returns a collection containing all of the operand {@link Node}s of this {@link Node}. + * + * @return a collection containing all of the operand {@link Node}s of this {@link Node} + */ + @SideEffectFree + public abstract Collection getOperands(); + + /** + * Returns a collection containing all of the operand {@link Node}s of this {@link Node}, as well + * as (transitively) the operands of its operands. + * + * @return a collection containing all of the operand {@link Node}s of this {@link Node}, as well + * as (transitively) the operands of its operands + */ + @Pure + public Collection getTransitiveOperands() { + ArrayDeque operands = new ArrayDeque<>(getOperands()); + ArrayDeque transitiveOperands = new ArrayDeque<>(operands.size()); + while (!operands.isEmpty()) { + Node next = operands.removeFirst(); + operands.addAll(next.getOperands()); + transitiveOperands.add(next); } - - /** Set the basic block this node belongs to. */ - public void setBlock(Block b) { - block = b; - } - - /** - * Returns the {@link Tree} in the abstract syntax tree, or {@code null} if no corresponding - * tree exists. For instance, this is the case for an {@link ImplicitThisNode}. - * - * @return the corresponding {@link Tree} or {@code null} - */ - @Pure - public abstract @Nullable Tree getTree(); - - /** - * Returns a {@link TypeMirror} representing the type of a {@link Node}. A {@link Node} will - * always have a type even when it has no {@link Tree}. - * - * @return a {@link TypeMirror} representing the type of this {@link Node} - */ - @Pure - public TypeMirror getType() { - return type; - } - - /** - * Accept method of the visitor pattern. - * - * @param result type of the operation - * @param

parameter type - * @param visitor the visitor to be applied to this node - * @param p the parameter for this operation - */ - public abstract R accept(NodeVisitor visitor, P p); - - /** Is the node an lvalue or not? */ - @Pure - public boolean isLValue() { - return lvalue; - } - - /** Make this node an l-value. */ - public void setLValue() { - lvalue = true; - } - - /** - * Return whether this node represents a tree that appears in the source code (true) or one that - * the CFG or builder added while desugaring (false). - * - * @return whether this node represents a tree that appears in the source code - */ - @Pure - public boolean getInSource() { - return inSource; - } - - public void setInSource(boolean inSrc) { - inSource = inSrc; - } - - /** - * Returns a collection containing all of the operand {@link Node}s of this {@link Node}. - * - * @return a collection containing all of the operand {@link Node}s of this {@link Node} - */ - @SideEffectFree - public abstract Collection getOperands(); - - /** - * Returns a collection containing all of the operand {@link Node}s of this {@link Node}, as - * well as (transitively) the operands of its operands. - * - * @return a collection containing all of the operand {@link Node}s of this {@link Node}, as - * well as (transitively) the operands of its operands - */ - @Pure - public Collection getTransitiveOperands() { - ArrayDeque operands = new ArrayDeque<>(getOperands()); - ArrayDeque transitiveOperands = new ArrayDeque<>(operands.size()); - while (!operands.isEmpty()) { - Node next = operands.removeFirst(); - operands.addAll(next.getOperands()); - transitiveOperands.add(next); - } - return transitiveOperands; - } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a printed representation of this - */ - @Pure - public String toStringDebug() { - return String.format("%s [%s]", this, this.getClassAndUid()); - } - - /** - * Returns a verbose string representation of a collection of nodes, useful for debugging.. - * - * @param nodes a collection of nodes to format - * @return a printed representation of the given collection - */ - @Pure - public static String nodeCollectionToString(Collection nodes) { - StringJoiner result = new StringJoiner(", ", "[", "]"); - for (Node n : nodes) { - result.add(n.toStringDebug()); - } - return result.toString(); + return transitiveOperands; + } + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a printed representation of this + */ + @Pure + public String toStringDebug() { + return String.format("%s [%s]", this, this.getClassAndUid()); + } + + /** + * Returns a verbose string representation of a collection of nodes, useful for debugging.. + * + * @param nodes a collection of nodes to format + * @return a printed representation of the given collection + */ + @Pure + public static String nodeCollectionToString(Collection nodes) { + StringJoiner result = new StringJoiner(", ", "[", "]"); + for (Node n : nodes) { + result.add(n.toStringDebug()); } + return result.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java index 20211a9619b..ad6be81daa9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java @@ -9,180 +9,180 @@ * parameter. */ public interface NodeVisitor { - // Literals - R visitShortLiteral(ShortLiteralNode n, P p); + // Literals + R visitShortLiteral(ShortLiteralNode n, P p); - R visitIntegerLiteral(IntegerLiteralNode n, P p); + R visitIntegerLiteral(IntegerLiteralNode n, P p); - R visitLongLiteral(LongLiteralNode n, P p); + R visitLongLiteral(LongLiteralNode n, P p); - R visitFloatLiteral(FloatLiteralNode n, P p); + R visitFloatLiteral(FloatLiteralNode n, P p); - R visitDoubleLiteral(DoubleLiteralNode n, P p); + R visitDoubleLiteral(DoubleLiteralNode n, P p); - R visitBooleanLiteral(BooleanLiteralNode n, P p); + R visitBooleanLiteral(BooleanLiteralNode n, P p); - R visitCharacterLiteral(CharacterLiteralNode n, P p); + R visitCharacterLiteral(CharacterLiteralNode n, P p); - R visitStringLiteral(StringLiteralNode n, P p); + R visitStringLiteral(StringLiteralNode n, P p); - R visitNullLiteral(NullLiteralNode n, P p); + R visitNullLiteral(NullLiteralNode n, P p); - // Unary operations - R visitNumericalMinus(NumericalMinusNode n, P p); + // Unary operations + R visitNumericalMinus(NumericalMinusNode n, P p); - R visitNumericalPlus(NumericalPlusNode n, P p); + R visitNumericalPlus(NumericalPlusNode n, P p); - R visitBitwiseComplement(BitwiseComplementNode n, P p); + R visitBitwiseComplement(BitwiseComplementNode n, P p); - R visitNullChk(NullChkNode n, P p); + R visitNullChk(NullChkNode n, P p); - // Binary operations - R visitStringConcatenate(StringConcatenateNode n, P p); + // Binary operations + R visitStringConcatenate(StringConcatenateNode n, P p); - R visitNumericalAddition(NumericalAdditionNode n, P p); + R visitNumericalAddition(NumericalAdditionNode n, P p); - R visitNumericalSubtraction(NumericalSubtractionNode n, P p); + R visitNumericalSubtraction(NumericalSubtractionNode n, P p); - R visitNumericalMultiplication(NumericalMultiplicationNode n, P p); + R visitNumericalMultiplication(NumericalMultiplicationNode n, P p); - R visitIntegerDivision(IntegerDivisionNode n, P p); + R visitIntegerDivision(IntegerDivisionNode n, P p); - R visitFloatingDivision(FloatingDivisionNode n, P p); + R visitFloatingDivision(FloatingDivisionNode n, P p); - R visitIntegerRemainder(IntegerRemainderNode n, P p); + R visitIntegerRemainder(IntegerRemainderNode n, P p); - R visitFloatingRemainder(FloatingRemainderNode n, P p); + R visitFloatingRemainder(FloatingRemainderNode n, P p); - R visitLeftShift(LeftShiftNode n, P p); + R visitLeftShift(LeftShiftNode n, P p); - R visitSignedRightShift(SignedRightShiftNode n, P p); + R visitSignedRightShift(SignedRightShiftNode n, P p); - R visitUnsignedRightShift(UnsignedRightShiftNode n, P p); + R visitUnsignedRightShift(UnsignedRightShiftNode n, P p); - R visitBitwiseAnd(BitwiseAndNode n, P p); + R visitBitwiseAnd(BitwiseAndNode n, P p); - R visitBitwiseOr(BitwiseOrNode n, P p); + R visitBitwiseOr(BitwiseOrNode n, P p); - R visitBitwiseXor(BitwiseXorNode n, P p); + R visitBitwiseXor(BitwiseXorNode n, P p); - // Comparison operations - R visitLessThan(LessThanNode n, P p); + // Comparison operations + R visitLessThan(LessThanNode n, P p); - R visitLessThanOrEqual(LessThanOrEqualNode n, P p); + R visitLessThanOrEqual(LessThanOrEqualNode n, P p); - R visitGreaterThan(GreaterThanNode n, P p); + R visitGreaterThan(GreaterThanNode n, P p); - R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p); + R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p); - R visitEqualTo(EqualToNode n, P p); + R visitEqualTo(EqualToNode n, P p); - R visitNotEqual(NotEqualNode n, P p); + R visitNotEqual(NotEqualNode n, P p); - // Conditional operations - R visitConditionalAnd(ConditionalAndNode n, P p); + // Conditional operations + R visitConditionalAnd(ConditionalAndNode n, P p); - R visitConditionalOr(ConditionalOrNode n, P p); + R visitConditionalOr(ConditionalOrNode n, P p); - R visitConditionalNot(ConditionalNotNode n, P p); + R visitConditionalNot(ConditionalNotNode n, P p); - R visitTernaryExpression(TernaryExpressionNode n, P p); + R visitTernaryExpression(TernaryExpressionNode n, P p); - R visitSwitchExpressionNode(SwitchExpressionNode n, P p); + R visitSwitchExpressionNode(SwitchExpressionNode n, P p); - R visitAssignment(AssignmentNode n, P p); + R visitAssignment(AssignmentNode n, P p); - R visitLocalVariable(LocalVariableNode n, P p); + R visitLocalVariable(LocalVariableNode n, P p); - R visitVariableDeclaration(VariableDeclarationNode n, P p); + R visitVariableDeclaration(VariableDeclarationNode n, P p); - R visitFieldAccess(FieldAccessNode n, P p); + R visitFieldAccess(FieldAccessNode n, P p); - R visitMethodAccess(MethodAccessNode n, P p); + R visitMethodAccess(MethodAccessNode n, P p); - R visitArrayAccess(ArrayAccessNode n, P p); + R visitArrayAccess(ArrayAccessNode n, P p); - R visitImplicitThis(ImplicitThisNode n, P p); + R visitImplicitThis(ImplicitThisNode n, P p); - R visitExplicitThis(ExplicitThisNode n, P p); + R visitExplicitThis(ExplicitThisNode n, P p); - R visitSuper(SuperNode n, P p); + R visitSuper(SuperNode n, P p); - R visitReturn(ReturnNode n, P p); + R visitReturn(ReturnNode n, P p); - R visitLambdaResultExpression(LambdaResultExpressionNode n, P p); + R visitLambdaResultExpression(LambdaResultExpressionNode n, P p); - R visitStringConversion(StringConversionNode n, P p); + R visitStringConversion(StringConversionNode n, P p); - R visitWideningConversion(WideningConversionNode n, P p); + R visitWideningConversion(WideningConversionNode n, P p); - R visitNarrowingConversion(NarrowingConversionNode n, P p); + R visitNarrowingConversion(NarrowingConversionNode n, P p); - R visitInstanceOf(InstanceOfNode n, P p); + R visitInstanceOf(InstanceOfNode n, P p); - R visitTypeCast(TypeCastNode n, P p); + R visitTypeCast(TypeCastNode n, P p); - // Blocks + // Blocks - R visitSynchronized(SynchronizedNode n, P p); + R visitSynchronized(SynchronizedNode n, P p); - // Statements - R visitAssertionError(AssertionErrorNode n, P p); + // Statements + R visitAssertionError(AssertionErrorNode n, P p); - R visitThrow(ThrowNode n, P p); + R visitThrow(ThrowNode n, P p); - // Cases - R visitCase(CaseNode n, P p); + // Cases + R visitCase(CaseNode n, P p); - // Method and constructor invocations - R visitMethodInvocation(MethodInvocationNode n, P p); + // Method and constructor invocations + R visitMethodInvocation(MethodInvocationNode n, P p); - R visitObjectCreation(ObjectCreationNode n, P p); + R visitObjectCreation(ObjectCreationNode n, P p); - R visitMemberReference(FunctionalInterfaceNode n, P p); + R visitMemberReference(FunctionalInterfaceNode n, P p); - R visitArrayCreation(ArrayCreationNode n, P p); + R visitArrayCreation(ArrayCreationNode n, P p); - // Type, package and class names - R visitArrayType(ArrayTypeNode n, P p); + // Type, package and class names + R visitArrayType(ArrayTypeNode n, P p); - R visitPrimitiveType(PrimitiveTypeNode n, P p); + R visitPrimitiveType(PrimitiveTypeNode n, P p); - R visitClassName(ClassNameNode n, P p); + R visitClassName(ClassNameNode n, P p); - R visitPackageName(PackageNameNode n, P p); + R visitPackageName(PackageNameNode n, P p); - // Parameterized types - R visitParameterizedType(ParameterizedTypeNode n, P p); + // Parameterized types + R visitParameterizedType(ParameterizedTypeNode n, P p); - // Marker nodes - R visitMarker(MarkerNode n, P p); + // Marker nodes + R visitMarker(MarkerNode n, P p); - /** - * Visits an anonymous/inner/nested class declaration within a method. - * - * @param classDeclarationNode the {@link ClassDeclarationNode} to be visited - * @param p the argument for the operation implemented by this visitor - * @return the return value of the operation implemented by this visitor - */ - R visitClassDeclaration(ClassDeclarationNode classDeclarationNode, P p); + /** + * Visits an anonymous/inner/nested class declaration within a method. + * + * @param classDeclarationNode the {@link ClassDeclarationNode} to be visited + * @param p the argument for the operation implemented by this visitor + * @return the return value of the operation implemented by this visitor + */ + R visitClassDeclaration(ClassDeclarationNode classDeclarationNode, P p); - /** - * Visits an expression that is used as a statement. This node is a marker after the expression - * node(s). - * - * @param n the {@link ExpressionStatementNode} to be visited - * @param p the argument for the operation implemented by this visitor - * @return the return value of the operation implemented by this visitor - */ - R visitExpressionStatement(ExpressionStatementNode n, P p); + /** + * Visits an expression that is used as a statement. This node is a marker after the expression + * node(s). + * + * @param n the {@link ExpressionStatementNode} to be visited + * @param p the argument for the operation implemented by this visitor + * @return the return value of the operation implemented by this visitor + */ + R visitExpressionStatement(ExpressionStatementNode n, P p); - /** - * Visits a deconstructor pattern node. - * - * @param n the {@link DeconstructorPatternNode} to be visited - * @param p the argument for the operation implemented by this visitor - * @return the return value of the operation implemented by this visitor - */ - R visitDeconstructorPattern(DeconstructorPatternNode n, P p); + /** + * Visits a deconstructor pattern node. + * + * @param n the {@link DeconstructorPatternNode} to be visited + * @param p the argument for the operation implemented by this visitor + * @return the return value of the operation implemented by this visitor + */ + R visitDeconstructorPattern(DeconstructorPatternNode n, P p); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java index b7c2f5ed0ec..ee0548c27aa 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the not equal comparison: @@ -16,40 +14,40 @@ */ public class NotEqualNode extends BinaryOperationNode { - /** - * Constructs a {@link NotEqualNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NotEqualNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.NOT_EQUAL_TO; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNotEqual(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " != " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NotEqualNode)) { - return false; - } - NotEqualNode other = (NotEqualNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link NotEqualNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NotEqualNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.NOT_EQUAL_TO; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNotEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " != " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NotEqualNode)) { + return false; } + NotEqualNode other = (NotEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java index 386bc977fd0..0a2dc570bc0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java @@ -1,14 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for the unary 'nullchk' operation (generated by the Java compiler): @@ -18,61 +16,61 @@ * */ public class NullChkNode extends Node { - /** The entire tree of the null check */ - protected final Tree tree; + /** The entire tree of the null check */ + protected final Tree tree; - /** The operand of the null check */ - protected final Node operand; + /** The operand of the null check */ + protected final Node operand; - /** - * Constructs a {@link NullChkNode}. - * - * @param tree the nullchk tree - * @param operand the operand of the null check - */ - public NullChkNode(Tree tree, Node operand) { - super(TreeUtils.typeOf(tree)); - assert tree.getKind() == Tree.Kind.OTHER; - this.tree = tree; - this.operand = operand; - } + /** + * Constructs a {@link NullChkNode}. + * + * @param tree the nullchk tree + * @param operand the operand of the null check + */ + public NullChkNode(Tree tree, Node operand) { + super(TreeUtils.typeOf(tree)); + assert tree.getKind() == Tree.Kind.OTHER; + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNullChk(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNullChk(this, p); + } - @Override - public String toString() { - return "(+ " + getOperand() + ")"; - } + @Override + public String toString() { + return "(+ " + getOperand() + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalPlusNode)) { - return false; - } - NumericalPlusNode other = (NumericalPlusNode) obj; - return getOperand().equals(other.getOperand()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalPlusNode)) { + return false; } + NumericalPlusNode other = (NumericalPlusNode) obj; + return getOperand().equals(other.getOperand()); + } - @Override - public int hashCode() { - return Objects.hash(NullChkNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(NullChkNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java index 574e1fbe8dd..f0866c47d77 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,35 +13,35 @@ */ public class NullLiteralNode extends ValueLiteralNode { - /** - * Create a new NullLiteralNode. - * - * @param t the tree for the literal value - */ - public NullLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.NULL_LITERAL; - } + /** + * Create a new NullLiteralNode. + * + * @param t the tree for the literal value + */ + public NullLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.NULL_LITERAL; + } - @Override - public Void getValue() { - return (Void) tree.getValue(); - } + @Override + public Void getValue() { + return (Void) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNullLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNullLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof NullLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof NullLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java index fd26ab1c3cc..6ca458abf79 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the numerical addition: @@ -16,40 +14,40 @@ */ public class NumericalAdditionNode extends BinaryOperationNode { - /** - * Constructs a {@link NumericalAdditionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NumericalAdditionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.PLUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalAddition(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalAdditionNode)) { - return false; - } - NumericalAdditionNode other = (NumericalAdditionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link NumericalAdditionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NumericalAdditionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.PLUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalAddition(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalAdditionNode)) { + return false; } + NumericalAdditionNode other = (NumericalAdditionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java index 0a7a02f5949..c5b2e5619f4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the unary minus operation: @@ -16,38 +14,38 @@ */ public class NumericalMinusNode extends UnaryOperationNode { - /** - * Constructs a {@link NumericalMinusNode}. - * - * @param tree the unary tree - * @param operand the operand of the unary minus - */ - public NumericalMinusNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.UNARY_MINUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalMinus(this, p); - } - - @Override - public String toString() { - return "(- " + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalMinusNode)) { - return false; - } - NumericalMinusNode other = (NumericalMinusNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(NumericalMinusNode.class, getOperand()); + /** + * Constructs a {@link NumericalMinusNode}. + * + * @param tree the unary tree + * @param operand the operand of the unary minus + */ + public NumericalMinusNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.UNARY_MINUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalMinus(this, p); + } + + @Override + public String toString() { + return "(- " + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalMinusNode)) { + return false; } + NumericalMinusNode other = (NumericalMinusNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(NumericalMinusNode.class, getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java index a1abe1c8403..3dabcaf1dd1 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the numerical multiplication: @@ -16,40 +14,40 @@ */ public class NumericalMultiplicationNode extends BinaryOperationNode { - /** - * Constructs a {@link NumericalMultiplicationNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NumericalMultiplicationNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.MULTIPLY; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalMultiplication(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " * " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalMultiplicationNode)) { - return false; - } - NumericalMultiplicationNode other = (NumericalMultiplicationNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link NumericalMultiplicationNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NumericalMultiplicationNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.MULTIPLY; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalMultiplication(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " * " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalMultiplicationNode)) { + return false; } + NumericalMultiplicationNode other = (NumericalMultiplicationNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java index 6f611d97cbe..2f6d2daa8de 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the unary plus operation: @@ -16,38 +14,38 @@ */ public class NumericalPlusNode extends UnaryOperationNode { - /** - * Constructs a {@link NumericalPlusNode}. - * - * @param tree the tree of the whole operation - * @param operand the operand of the operation - */ - public NumericalPlusNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.UNARY_PLUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalPlus(this, p); - } - - @Override - public String toString() { - return "(+ " + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalPlusNode)) { - return false; - } - NumericalPlusNode other = (NumericalPlusNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(NumericalPlusNode.class, getOperand()); + /** + * Constructs a {@link NumericalPlusNode}. + * + * @param tree the tree of the whole operation + * @param operand the operand of the operation + */ + public NumericalPlusNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.UNARY_PLUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalPlus(this, p); + } + + @Override + public String toString() { + return "(+ " + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalPlusNode)) { + return false; } + NumericalPlusNode other = (NumericalPlusNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(NumericalPlusNode.class, getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java index af72519ffe7..5d2576bb7b6 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for the numerical subtraction: @@ -16,40 +14,40 @@ */ public class NumericalSubtractionNode extends BinaryOperationNode { - /** - * Constructs a {@link NumericalSubtractionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NumericalSubtractionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.MINUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalSubtraction(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " - " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalSubtractionNode)) { - return false; - } - NumericalSubtractionNode other = (NumericalSubtractionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link NumericalSubtractionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NumericalSubtractionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.MINUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalSubtraction(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " - " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalSubtractionNode)) { + return false; } + NumericalSubtractionNode other = (NumericalSubtractionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java index a14856f89d0..7d7374d6ed6 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java @@ -1,18 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.NewClassTree; - +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.StringsPlume; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; - /** * A node for a new object creation. * @@ -33,183 +31,182 @@ */ public class ObjectCreationNode extends Node { - /** The tree for the object creation. */ - protected final NewClassTree tree; - - /** The enclosing expression of the object creation or null. */ - protected final @Nullable Node enclosingExpression; - - /** - * The type to instantiate node of the object creation. A non-generic typeToInstantiate node - * will refer to a {@link ClassNameNode}, while a generic typeToInstantiate node will refer to a - * {@link ParameterizedTypeNode}. - */ - protected final Node typeToInstantiate; - - /** The arguments of the object creation. */ - protected final List arguments; - - /** Class body for anonymous classes, otherwise null. */ - protected final @Nullable ClassDeclarationNode classbody; - - /** - * Constructs a {@link ObjectCreationNode}. - * - * @param tree the NewClassTree - * @param enclosingExpr the enclosing expression Node if it exists, or null - * @param typeToInstantiate the typeToInstantiate node - * @param arguments the passed arguments - * @param classbody the ClassDeclarationNode - */ - public ObjectCreationNode( - NewClassTree tree, - @Nullable Node enclosingExpr, - Node typeToInstantiate, - List arguments, - @Nullable ClassDeclarationNode classbody) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.enclosingExpression = enclosingExpr; - this.typeToInstantiate = typeToInstantiate; - this.arguments = arguments; - this.classbody = classbody; - } - - /** - * Returns the constructor node. - * - * @return the constructor node - * @deprecated use {@link #getTypeToInstantiate()} - */ - @Pure - @Deprecated // 2023-06-06 - public Node getConstructor() { - return typeToInstantiate; - } - - /** - * Returns the typeToInstantiate node. A non-generic typeToInstantiate node can refer to a - * {@link ClassNameNode}, while a generic typeToInstantiate node can refer to a {@link - * ParameterizedTypeNode}. - * - * @return the typeToInstantiate node - */ - @Pure - public Node getTypeToInstantiate() { - return typeToInstantiate; + /** The tree for the object creation. */ + protected final NewClassTree tree; + + /** The enclosing expression of the object creation or null. */ + protected final @Nullable Node enclosingExpression; + + /** + * The type to instantiate node of the object creation. A non-generic typeToInstantiate node will + * refer to a {@link ClassNameNode}, while a generic typeToInstantiate node will refer to a {@link + * ParameterizedTypeNode}. + */ + protected final Node typeToInstantiate; + + /** The arguments of the object creation. */ + protected final List arguments; + + /** Class body for anonymous classes, otherwise null. */ + protected final @Nullable ClassDeclarationNode classbody; + + /** + * Constructs a {@link ObjectCreationNode}. + * + * @param tree the NewClassTree + * @param enclosingExpr the enclosing expression Node if it exists, or null + * @param typeToInstantiate the typeToInstantiate node + * @param arguments the passed arguments + * @param classbody the ClassDeclarationNode + */ + public ObjectCreationNode( + NewClassTree tree, + @Nullable Node enclosingExpr, + Node typeToInstantiate, + List arguments, + @Nullable ClassDeclarationNode classbody) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.enclosingExpression = enclosingExpr; + this.typeToInstantiate = typeToInstantiate; + this.arguments = arguments; + this.classbody = classbody; + } + + /** + * Returns the constructor node. + * + * @return the constructor node + * @deprecated use {@link #getTypeToInstantiate()} + */ + @Pure + @Deprecated // 2023-06-06 + public Node getConstructor() { + return typeToInstantiate; + } + + /** + * Returns the typeToInstantiate node. A non-generic typeToInstantiate node can refer to a {@link + * ClassNameNode}, while a generic typeToInstantiate node can refer to a {@link + * ParameterizedTypeNode}. + * + * @return the typeToInstantiate node + */ + @Pure + public Node getTypeToInstantiate() { + return typeToInstantiate; + } + + /** + * Returns the explicit arguments to the object creation. + * + * @return the arguments + */ + @Pure + public List getArguments() { + return arguments; + } + + /** + * Returns the i-th explicit argument to the object creation. + * + * @param i the index of the argument + * @return the argument + */ + @Pure + public Node getArgument(int i) { + return arguments.get(i); + } + + /** + * Returns the enclosing expression node, which only exists if it is an inner class instantiation. + * + * @return the enclosing type expression node + */ + @Pure + public @Nullable Node getEnclosingExpression() { + return enclosingExpression; + } + + /** + * Returns the classbody. + * + * @return the classbody + */ + @Pure + public @Nullable Node getClassBody() { + return classbody; + } + + @Override + @Pure + public NewClassTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitObjectCreation(this, p); + } + + @Override + @SideEffectFree + public String toString() { + StringBuilder sb = new StringBuilder(); + if (enclosingExpression != null) { + sb.append(enclosingExpression + "."); } - - /** - * Returns the explicit arguments to the object creation. - * - * @return the arguments - */ - @Pure - public List getArguments() { - return arguments; - } - - /** - * Returns the i-th explicit argument to the object creation. - * - * @param i the index of the argument - * @return the argument - */ - @Pure - public Node getArgument(int i) { - return arguments.get(i); - } - - /** - * Returns the enclosing expression node, which only exists if it is an inner class - * instantiation. - * - * @return the enclosing type expression node - */ - @Pure - public @Nullable Node getEnclosingExpression() { - return enclosingExpression; - } - - /** - * Returns the classbody. - * - * @return the classbody - */ - @Pure - public @Nullable Node getClassBody() { - return classbody; + sb.append("new "); + if (!tree.getTypeArguments().isEmpty()) { + sb.append("<"); + sb.append(StringsPlume.join(", ", tree.getTypeArguments())); + sb.append(">"); } - - @Override - @Pure - public NewClassTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitObjectCreation(this, p); + sb.append(typeToInstantiate + "("); + sb.append(StringsPlume.join(", ", arguments)); + sb.append(")"); + if (classbody != null) { + // TODO: maybe this can be done nicer... + sb.append(" "); + sb.append(classbody.toString()); } - - @Override - @SideEffectFree - public String toString() { - StringBuilder sb = new StringBuilder(); - if (enclosingExpression != null) { - sb.append(enclosingExpression + "."); - } - sb.append("new "); - if (!tree.getTypeArguments().isEmpty()) { - sb.append("<"); - sb.append(StringsPlume.join(", ", tree.getTypeArguments())); - sb.append(">"); - } - sb.append(typeToInstantiate + "("); - sb.append(StringsPlume.join(", ", arguments)); - sb.append(")"); - if (classbody != null) { - // TODO: maybe this can be done nicer... - sb.append(" "); - sb.append(classbody.toString()); - } - return sb.toString(); + return sb.toString(); + } + + @Override + @Pure + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ObjectCreationNode)) { + return false; } - - @Override - @Pure - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ObjectCreationNode)) { - return false; - } - ObjectCreationNode other = (ObjectCreationNode) obj; - // TODO: See issue 470. - if (typeToInstantiate == null && other.getTypeToInstantiate() != null) { - return false; - } - - return getTypeToInstantiate().equals(other.getTypeToInstantiate()) - && getArguments().equals(other.getArguments()) - && (getEnclosingExpression() == null - ? null == other.getEnclosingExpression() - : getEnclosingExpression().equals(other.getEnclosingExpression())); - } - - @Override - @SideEffectFree - public int hashCode() { - return Objects.hash(enclosingExpression, typeToInstantiate, arguments); + ObjectCreationNode other = (ObjectCreationNode) obj; + // TODO: See issue 470. + if (typeToInstantiate == null && other.getTypeToInstantiate() != null) { + return false; } - @Override - @SideEffectFree - public Collection getOperands() { - ArrayList list = new ArrayList<>(2 + arguments.size()); - if (enclosingExpression != null) { - list.add(enclosingExpression); - } - list.add(typeToInstantiate); - list.addAll(arguments); - return list; + return getTypeToInstantiate().equals(other.getTypeToInstantiate()) + && getArguments().equals(other.getArguments()) + && (getEnclosingExpression() == null + ? null == other.getEnclosingExpression() + : getEnclosingExpression().equals(other.getEnclosingExpression())); + } + + @Override + @SideEffectFree + public int hashCode() { + return Objects.hash(enclosingExpression, typeToInstantiate, arguments); + } + + @Override + @SideEffectFree + public Collection getOperands() { + ArrayList list = new ArrayList<>(2 + arguments.size()); + if (enclosingExpression != null) { + list.add(enclosingExpression); } + list.add(typeToInstantiate); + list.addAll(arguments); + return list; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java index 6f8347a72cb..5874000a36a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java @@ -3,17 +3,14 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.element.PackageElement; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; /** * A node representing a package name used in an expression such as a constructor invocation. @@ -24,89 +21,89 @@ */ public class PackageNameNode extends Node { - /** The package name, which is an IdentifierTree or a MemberSelectTree. */ - protected final Tree tree; - - /** The package named by this node. */ - protected final PackageElement element; - - /** The parent name, if any. */ - protected final @Nullable PackageNameNode parent; + /** The package name, which is an IdentifierTree or a MemberSelectTree. */ + protected final Tree tree; - public PackageNameNode(IdentifierTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); - if (element == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); - } - this.element = element; - this.parent = null; - } + /** The package named by this node. */ + protected final PackageElement element; - public PackageNameNode(MemberSelectTree tree, PackageNameNode parent) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); - if (element == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); - } - this.element = element; - this.parent = parent; - } + /** The parent name, if any. */ + protected final @Nullable PackageNameNode parent; - /** - * Returns the element for this package. - * - * @return the element for this package - */ - public PackageElement getElement() { - return element; + public PackageNameNode(IdentifierTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); + if (element == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); } - - /** The package name node for the parent package, {@code null} otherwise. */ - public @Nullable PackageNameNode getParent() { - return parent; + this.element = element; + this.parent = null; + } + + public PackageNameNode(MemberSelectTree tree, PackageNameNode parent) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); + if (element == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); } - - @Override - public Tree getTree() { - return tree; + this.element = element; + this.parent = parent; + } + + /** + * Returns the element for this package. + * + * @return the element for this package + */ + public PackageElement getElement() { + return element; + } + + /** The package name node for the parent package, {@code null} otherwise. */ + public @Nullable PackageNameNode getParent() { + return parent; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitPackageName(this, p); + } + + @Override + public String toString() { + return getElement().getSimpleName().toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof PackageNameNode)) { + return false; } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitPackageName(this, p); - } - - @Override - public String toString() { - return getElement().getSimpleName().toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof PackageNameNode)) { - return false; - } - PackageNameNode other = (PackageNameNode) obj; - return Objects.equals(getParent(), other.getParent()) - && getElement().equals(other.getElement()); - } - - @Override - public int hashCode() { - return Objects.hash(getElement(), getParent()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - if (parent == null) { - return Collections.emptyList(); - } - return Collections.singleton((Node) parent); + PackageNameNode other = (PackageNameNode) obj; + return Objects.equals(getParent(), other.getParent()) + && getElement().equals(other.getElement()); + } + + @Override + public int hashCode() { + return Objects.hash(getElement(), getParent()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + if (parent == null) { + return Collections.emptyList(); } + return Collections.singleton((Node) parent); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java index b5931f6de7f..82cd3e23d46 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java @@ -1,14 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ParameterizedTypeTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a parameterized type occurring in an expression: @@ -23,45 +21,45 @@ */ public class ParameterizedTypeNode extends Node { - protected final ParameterizedTypeTree tree; + protected final ParameterizedTypeTree tree; - public ParameterizedTypeNode(ParameterizedTypeTree t) { - super(TreeUtils.typeOf(t)); - tree = t; - } + public ParameterizedTypeNode(ParameterizedTypeTree t) { + super(TreeUtils.typeOf(t)); + tree = t; + } - @Override - public ParameterizedTypeTree getTree() { - return tree; - } + @Override + public ParameterizedTypeTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitParameterizedType(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitParameterizedType(this, p); + } - @Override - public String toString() { - return getTree().toString(); - } + @Override + public String toString() { + return getTree().toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ParameterizedTypeNode)) { - return false; - } - ParameterizedTypeNode other = (ParameterizedTypeNode) obj; - return getTree().equals(other.getTree()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ParameterizedTypeNode)) { + return false; } + ParameterizedTypeNode other = (ParameterizedTypeNode) obj; + return getTree().equals(other.getTree()); + } - @Override - public int hashCode() { - return Objects.hash(getTree()); - } + @Override + public int hashCode() { + return Objects.hash(getTree()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java index a69081a99ab..bcb24e91a57 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.PrimitiveTypeTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node representing a primitive type used in an expression such as a field access. @@ -19,49 +16,49 @@ */ public class PrimitiveTypeNode extends Node { - protected final PrimitiveTypeTree tree; + protected final PrimitiveTypeTree tree; - /** For Types.isSameType. */ - protected final Types types; + /** For Types.isSameType. */ + protected final Types types; - public PrimitiveTypeNode(PrimitiveTypeTree tree, Types types) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.types = types; - } + public PrimitiveTypeNode(PrimitiveTypeTree tree, Types types) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.types = types; + } - @Override - public PrimitiveTypeTree getTree() { - return tree; - } + @Override + public PrimitiveTypeTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitPrimitiveType(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitPrimitiveType(this, p); + } - @Override - public String toString() { - return tree.toString(); - } + @Override + public String toString() { + return tree.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof PrimitiveTypeNode)) { - return false; - } - PrimitiveTypeNode other = (PrimitiveTypeNode) obj; - return types.isSameType(getType(), other.getType()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof PrimitiveTypeNode)) { + return false; } + PrimitiveTypeNode other = (PrimitiveTypeNode) obj; + return types.isSameType(getType(), other.getType()); + } - @Override - public int hashCode() { - return Objects.hash(getType()); - } + @Override + public int hashCode() { + return Objects.hash(getType()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java index 8ede588a75b..f84e746b6fc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ReturnTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for a return statement: @@ -24,69 +21,69 @@ */ public class ReturnNode extends Node { - /** The return tree. */ - protected final ReturnTree returnTree; + /** The return tree. */ + protected final ReturnTree returnTree; - /** The node of the returned expression. */ - protected final @Nullable Node result; + /** The node of the returned expression. */ + protected final @Nullable Node result; - /** - * Creates a node for the given return statement. - * - * @param returnTree return tree - * @param result the returned expression - * @param types types util - */ - public ReturnNode(ReturnTree returnTree, @Nullable Node result, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.result = result; - this.returnTree = returnTree; - } + /** + * Creates a node for the given return statement. + * + * @param returnTree return tree + * @param result the returned expression + * @param types types util + */ + public ReturnNode(ReturnTree returnTree, @Nullable Node result, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.result = result; + this.returnTree = returnTree; + } - /** The result of the return node, {@code null} otherwise. */ - public @Nullable Node getResult() { - return result; - } + /** The result of the return node, {@code null} otherwise. */ + public @Nullable Node getResult() { + return result; + } - @Override - public ReturnTree getTree() { - return returnTree; - } + @Override + public ReturnTree getTree() { + return returnTree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitReturn(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitReturn(this, p); + } - @Override - public String toString() { - if (result != null) { - return "return " + result; - } - return "return"; + @Override + public String toString() { + if (result != null) { + return "return " + result; } + return "return"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ReturnNode)) { - return false; - } - ReturnNode other = (ReturnNode) obj; - return Objects.equals(result, other.result); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ReturnNode)) { + return false; } + ReturnNode other = (ReturnNode) obj; + return Objects.equals(result, other.result); + } - @Override - public int hashCode() { - return Objects.hash(ReturnNode.class, result); - } + @Override + public int hashCode() { + return Objects.hash(ReturnNode.class, result); + } - @Override - @SideEffectFree - public Collection getOperands() { - if (result == null) { - return Collections.emptyList(); - } else { - return Collections.singletonList(result); - } + @Override + @SideEffectFree + public Collection getOperands() { + if (result == null) { + return Collections.emptyList(); + } else { + return Collections.singletonList(result); } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java index 8da1646c07a..f504e8b1321 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -19,33 +18,33 @@ // TODO: If we use explicit NarrowingConversionNodes, do we need ShortLiteralNodes too? public class ShortLiteralNode extends ValueLiteralNode { - /** - * Create a new ShortLiteralNode. - * - * @param t the tree for the literal value - */ - public ShortLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.INT_LITERAL; - } + /** + * Create a new ShortLiteralNode. + * + * @param t the tree for the literal value + */ + public ShortLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.INT_LITERAL; + } - @Override - public Short getValue() { - return (Short) tree.getValue(); - } + @Override + public Short getValue() { + return (Short) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitShortLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitShortLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a ShortLiteralNode - if (!(obj instanceof ShortLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a ShortLiteralNode + if (!(obj instanceof ShortLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java index 22710411850..849b7fafbfe 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for bitwise right shift operations with sign extension: @@ -16,40 +14,40 @@ */ public class SignedRightShiftNode extends BinaryOperationNode { - /** - * Constructs a {@link SignedRightShiftNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public SignedRightShiftNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.RIGHT_SHIFT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSignedRightShift(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " >> " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof SignedRightShiftNode)) { - return false; - } - SignedRightShiftNode other = (SignedRightShiftNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link SignedRightShiftNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public SignedRightShiftNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.RIGHT_SHIFT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSignedRightShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >> " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof SignedRightShiftNode)) { + return false; } + SignedRightShiftNode other = (SignedRightShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java index c6615ad5605..c7d0b2d0fb6 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for string concatenation: @@ -16,40 +14,40 @@ */ public class StringConcatenateNode extends BinaryOperationNode { - /** - * Constructs a {@link StringConcatenateNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public StringConcatenateNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.PLUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitStringConcatenate(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof StringConcatenateNode)) { - return false; - } - StringConcatenateNode other = (StringConcatenateNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs a {@link StringConcatenateNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public StringConcatenateNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.PLUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitStringConcatenate(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof StringConcatenateNode)) { + return false; } + StringConcatenateNode other = (StringConcatenateNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java index 4d55bb0171a..850edc43fc1 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java @@ -1,15 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for the string conversion operation. See JLS 5.1.11 for the definition of string @@ -25,55 +22,55 @@ */ public class StringConversionNode extends Node { - protected final Tree tree; - protected final Node operand; + protected final Tree tree; + protected final Node operand; - // TODO: The type of a string conversion should be a final - // TypeMirror representing java.lang.String. Currently we require - // the caller to pass in a TypeMirror instead of creating one - // through the javax.lang.model.type.Types interface. - public StringConversionNode(Tree tree, Node operand, TypeMirror type) { - super(type); - this.tree = tree; - this.operand = operand; - } + // TODO: The type of a string conversion should be a final + // TypeMirror representing java.lang.String. Currently we require + // the caller to pass in a TypeMirror instead of creating one + // through the javax.lang.model.type.Types interface. + public StringConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitStringConversion(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitStringConversion(this, p); + } - @Override - public String toString() { - return "StringConversion(" + getOperand() + ")"; - } + @Override + public String toString() { + return "StringConversion(" + getOperand() + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof StringConversionNode)) { - return false; - } - StringConversionNode other = (StringConversionNode) obj; - return getOperand().equals(other.getOperand()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof StringConversionNode)) { + return false; } + StringConversionNode other = (StringConversionNode) obj; + return getOperand().equals(other.getOperand()); + } - @Override - public int hashCode() { - return Objects.hash(StringConversionNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(StringConversionNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java index 7ba89c61b74..a44c39eaed3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java @@ -2,7 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,38 +13,38 @@ */ public class StringLiteralNode extends ValueLiteralNode { - /** - * Create a new StringLiteralNode. - * - * @param t the tree for the literal value - */ - public StringLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.STRING_LITERAL; - } - - @Override - public String getValue() { - return (String) tree.getValue(); - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitStringLiteral(this, p); - } - - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a StringLiteralNode - if (!(obj instanceof StringLiteralNode)) { - return false; - } - // super method compares values - return super.equals(obj); - } - - @Override - public String toString() { - return "\"" + super.toString() + "\""; + /** + * Create a new StringLiteralNode. + * + * @param t the tree for the literal value + */ + public StringLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.STRING_LITERAL; + } + + @Override + public String getValue() { + return (String) tree.getValue(); + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitStringLiteral(this, p); + } + + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a StringLiteralNode + if (!(obj instanceof StringLiteralNode)) { + return false; } + // super method compares values + return super.equals(obj); + } + + @Override + public String toString() { + return "\"" + super.toString() + "\""; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java index 05ef67c0eb4..c1fbf572286 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java @@ -1,14 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.IdentifierTree; - +import java.util.Collection; +import java.util.Collections; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collection; -import java.util.Collections; - /** * A node for a reference to 'super'. * @@ -18,42 +16,42 @@ */ public class SuperNode extends Node { - protected final IdentifierTree tree; - - public SuperNode(IdentifierTree t) { - super(TreeUtils.typeOf(t)); - assert t.getName().contentEquals("super"); - tree = t; - } - - @Override - public IdentifierTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSuper(this, p); - } - - @Override - public String toString() { - return "super"; - } - - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof SuperNode; - } - - @Override - public int hashCode() { - return 109801370; // Objects.hash("super"); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + protected final IdentifierTree tree; + + public SuperNode(IdentifierTree t) { + super(TreeUtils.typeOf(t)); + assert t.getName().contentEquals("super"); + tree = t; + } + + @Override + public IdentifierTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSuper(this, p); + } + + @Override + public String toString() { + return "super"; + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof SuperNode; + } + + @Override + public int hashCode() { + return 109801370; // Objects.hash("super"); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java index ef2a1dea1b9..725079c820e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java @@ -1,104 +1,101 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.SystemUtil; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.SystemUtil; /** A node for a switch expression. */ public class SwitchExpressionNode extends Node { - /** The {@code SwitchExpressionTree} corresponding to this node. */ - private final Tree switchExpressionTree; - - /** - * This is a variable created by dataflow to which each result expression of the switch - * expression is assigned. Its value should be used for the value of the switch expression. - */ - private final LocalVariableNode switchExpressionVar; - - /** - * Creates a new SwitchExpressionNode. - * - * @param type the type of the node - * @param switchExpressionTree the {@code SwitchExpressionTree} for this node - * @param switchExpressionVar a variable created by dataflow to which each result expression of - * the switch expression is assigned. Its value should be used for the value of the switch - * expression - */ - public SwitchExpressionNode( - TypeMirror type, Tree switchExpressionTree, LocalVariableNode switchExpressionVar) { - super(type); - - // TODO: use JCP to add version-specific behavior - if (SystemUtil.jreVersion < 14 - || !switchExpressionTree.getKind().name().equals("SWITCH_EXPRESSION")) { - throw new BugInCF( - "switchExpressionTree is not a SwitchExpressionTree found tree with kind %s" - + " instead.", - switchExpressionTree.getKind()); - } - - this.switchExpressionTree = switchExpressionTree; - this.switchExpressionVar = switchExpressionVar; - } - - @Override - public Tree getTree() { - return switchExpressionTree; - } - - /** - * This is a variable created by dataflow to which each result expression of the switch - * expression is assigned. Its value should be used for the value of the switch expression. - * - * @return the variable for this switch expression - */ - public LocalVariableNode getSwitchExpressionVar() { - return switchExpressionVar; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSwitchExpressionNode(this, p); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singleton(switchExpressionVar); - } - - @Override - public String toString() { - return "SwitchExpressionNode{" - + "switchExpressionTree=" - + switchExpressionTree - + ", switchExpressionVar=" - + switchExpressionVar - + '}'; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof SwitchExpressionNode)) { - return false; - } - SwitchExpressionNode other = (SwitchExpressionNode) obj; - return getTree().equals(other.getTree()) - && getSwitchExpressionVar().equals(other.getSwitchExpressionVar()); + /** The {@code SwitchExpressionTree} corresponding to this node. */ + private final Tree switchExpressionTree; + + /** + * This is a variable created by dataflow to which each result expression of the switch expression + * is assigned. Its value should be used for the value of the switch expression. + */ + private final LocalVariableNode switchExpressionVar; + + /** + * Creates a new SwitchExpressionNode. + * + * @param type the type of the node + * @param switchExpressionTree the {@code SwitchExpressionTree} for this node + * @param switchExpressionVar a variable created by dataflow to which each result expression of + * the switch expression is assigned. Its value should be used for the value of the switch + * expression + */ + public SwitchExpressionNode( + TypeMirror type, Tree switchExpressionTree, LocalVariableNode switchExpressionVar) { + super(type); + + // TODO: use JCP to add version-specific behavior + if (SystemUtil.jreVersion < 14 + || !switchExpressionTree.getKind().name().equals("SWITCH_EXPRESSION")) { + throw new BugInCF( + "switchExpressionTree is not a SwitchExpressionTree found tree with kind %s" + + " instead.", + switchExpressionTree.getKind()); } - @Override - public int hashCode() { - return Objects.hash(getTree(), getSwitchExpressionVar()); + this.switchExpressionTree = switchExpressionTree; + this.switchExpressionVar = switchExpressionVar; + } + + @Override + public Tree getTree() { + return switchExpressionTree; + } + + /** + * This is a variable created by dataflow to which each result expression of the switch expression + * is assigned. Its value should be used for the value of the switch expression. + * + * @return the variable for this switch expression + */ + public LocalVariableNode getSwitchExpressionVar() { + return switchExpressionVar; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSwitchExpressionNode(this, p); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singleton(switchExpressionVar); + } + + @Override + public String toString() { + return "SwitchExpressionNode{" + + "switchExpressionTree=" + + switchExpressionTree + + ", switchExpressionVar=" + + switchExpressionVar + + '}'; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof SwitchExpressionNode)) { + return false; } + SwitchExpressionNode other = (SwitchExpressionNode) obj; + return getTree().equals(other.getTree()) + && getSwitchExpressionVar().equals(other.getSwitchExpressionVar()); + } + + @Override + public int hashCode() { + return Objects.hash(getTree(), getSwitchExpressionVar()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java index fd636d47d65..be52c5d9647 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.SynchronizedTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * This represents the start and end of a synchronized code block. If startOfBlock == true it is the @@ -19,64 +16,64 @@ */ public class SynchronizedNode extends Node { - protected final SynchronizedTree tree; - protected final Node expression; - protected final boolean startOfBlock; + protected final SynchronizedTree tree; + protected final Node expression; + protected final boolean startOfBlock; - public SynchronizedNode( - SynchronizedTree tree, Node expression, boolean startOfBlock, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.expression = expression; - this.startOfBlock = startOfBlock; - } + public SynchronizedNode( + SynchronizedTree tree, Node expression, boolean startOfBlock, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.expression = expression; + this.startOfBlock = startOfBlock; + } - @Override - public SynchronizedTree getTree() { - return tree; - } + @Override + public SynchronizedTree getTree() { + return tree; + } - public Node getExpression() { - return expression; - } + public Node getExpression() { + return expression; + } - public boolean getIsStartOfBlock() { - return startOfBlock; - } + public boolean getIsStartOfBlock() { + return startOfBlock; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSynchronized(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSynchronized(this, p); + } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("synchronized ("); - sb.append(expression); - sb.append(")"); - return sb.toString(); - } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("synchronized ("); + sb.append(expression); + sb.append(")"); + return sb.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof SynchronizedNode)) { - return false; - } - SynchronizedNode other = (SynchronizedNode) obj; - return Objects.equals(getTree(), other.getTree()) - && getExpression().equals(other.getExpression()) - && startOfBlock == other.startOfBlock; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof SynchronizedNode)) { + return false; } + SynchronizedNode other = (SynchronizedNode) obj; + return Objects.equals(getTree(), other.getTree()) + && getExpression().equals(other.getExpression()) + && startOfBlock == other.startOfBlock; + } - @Override - public int hashCode() { - return Objects.hash(tree, startOfBlock, getExpression()); - } + @Override + public int hashCode() { + return Objects.hash(tree, startOfBlock, getExpression()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java index 8301cfd9460..2150640b98a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java @@ -1,14 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ConditionalExpressionTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Arrays; import java.util.Collection; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a conditional expression: @@ -19,126 +17,120 @@ */ public class TernaryExpressionNode extends Node { - /** The {@code ConditionalExpressionTree} corresponding to this node */ - protected final ConditionalExpressionTree tree; - - /** Node representing the condition checked by the expression */ - protected final Node condition; - - /** Node representing the "then" case of the expression */ - protected final Node thenOperand; - - /** Node representing the "else" case of the expression */ - protected final Node elseOperand; - - /** - * This is a variable created by dataflow to which each case expression of the ternary - * expression is assigned. Its value should be used for the value of the switch expression. - */ - private final LocalVariableNode ternaryExpressionVar; - - /** - * Creates a new TernaryExpressionNode. - * - * @param tree the {@code ConditionalExpressionTree} for the node - * @param condition node representing the condition checked by the expression - * @param thenOperand node representing the "then" case of the expression - * @param elseOperand node representing the "else" case of the expression - * @param ternaryExpressionVar a variable created by dataflow to which each case expression of - * the ternary expression is assigned. Its value should be used for the value of the switch - * expression. - */ - public TernaryExpressionNode( - ConditionalExpressionTree tree, - Node condition, - Node thenOperand, - Node elseOperand, - LocalVariableNode ternaryExpressionVar) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.condition = condition; - this.thenOperand = thenOperand; - this.elseOperand = elseOperand; - this.ternaryExpressionVar = ternaryExpressionVar; - } - - /** - * Gets the node representing the conditional operand for this node - * - * @return the condition operand node - */ - public Node getConditionOperand() { - return condition; - } - - /** - * Gets the node representing the "then" operand for this node - * - * @return the "then" operand node - */ - public Node getThenOperand() { - return thenOperand; - } - - /** - * Gets the node representing the "else" operand for this node - * - * @return the "else" operand node - */ - public Node getElseOperand() { - return elseOperand; - } - - /** - * This is a variable created by dataflow to which each case expression of the ternary - * expression is assigned. Its value should be used for the value of the switch expression. - * - * @return the variable for this ternary expression - */ - public LocalVariableNode getTernaryExpressionVar() { - return ternaryExpressionVar; - } - - @Override - public ConditionalExpressionTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitTernaryExpression(this, p); - } - - @Override - public String toString() { - return "(" - + getConditionOperand() - + " ? " - + getThenOperand() - + " : " - + getElseOperand() - + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof TernaryExpressionNode)) { - return false; - } - TernaryExpressionNode other = (TernaryExpressionNode) obj; - return getConditionOperand().equals(other.getConditionOperand()) - && getThenOperand().equals(other.getThenOperand()) - && getElseOperand().equals(other.getElseOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getConditionOperand(), getThenOperand(), getElseOperand()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getConditionOperand(), getThenOperand(), getElseOperand()); + /** The {@code ConditionalExpressionTree} corresponding to this node */ + protected final ConditionalExpressionTree tree; + + /** Node representing the condition checked by the expression */ + protected final Node condition; + + /** Node representing the "then" case of the expression */ + protected final Node thenOperand; + + /** Node representing the "else" case of the expression */ + protected final Node elseOperand; + + /** + * This is a variable created by dataflow to which each case expression of the ternary expression + * is assigned. Its value should be used for the value of the switch expression. + */ + private final LocalVariableNode ternaryExpressionVar; + + /** + * Creates a new TernaryExpressionNode. + * + * @param tree the {@code ConditionalExpressionTree} for the node + * @param condition node representing the condition checked by the expression + * @param thenOperand node representing the "then" case of the expression + * @param elseOperand node representing the "else" case of the expression + * @param ternaryExpressionVar a variable created by dataflow to which each case expression of the + * ternary expression is assigned. Its value should be used for the value of the switch + * expression. + */ + public TernaryExpressionNode( + ConditionalExpressionTree tree, + Node condition, + Node thenOperand, + Node elseOperand, + LocalVariableNode ternaryExpressionVar) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.condition = condition; + this.thenOperand = thenOperand; + this.elseOperand = elseOperand; + this.ternaryExpressionVar = ternaryExpressionVar; + } + + /** + * Gets the node representing the conditional operand for this node + * + * @return the condition operand node + */ + public Node getConditionOperand() { + return condition; + } + + /** + * Gets the node representing the "then" operand for this node + * + * @return the "then" operand node + */ + public Node getThenOperand() { + return thenOperand; + } + + /** + * Gets the node representing the "else" operand for this node + * + * @return the "else" operand node + */ + public Node getElseOperand() { + return elseOperand; + } + + /** + * This is a variable created by dataflow to which each case expression of the ternary expression + * is assigned. Its value should be used for the value of the switch expression. + * + * @return the variable for this ternary expression + */ + public LocalVariableNode getTernaryExpressionVar() { + return ternaryExpressionVar; + } + + @Override + public ConditionalExpressionTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitTernaryExpression(this, p); + } + + @Override + public String toString() { + return "(" + getConditionOperand() + " ? " + getThenOperand() + " : " + getElseOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TernaryExpressionNode)) { + return false; } + TernaryExpressionNode other = (TernaryExpressionNode) obj; + return getConditionOperand().equals(other.getConditionOperand()) + && getThenOperand().equals(other.getThenOperand()) + && getElseOperand().equals(other.getElseOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getConditionOperand(), getThenOperand(), getElseOperand()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getConditionOperand(), getThenOperand(), getElseOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java index e7d5f3c589b..307d0cee743 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java @@ -1,12 +1,10 @@ package org.checkerframework.dataflow.cfg.node; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; import java.util.Collections; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for a reference to 'this', either implicit or explicit. @@ -17,23 +15,23 @@ */ public abstract class ThisNode extends Node { - protected ThisNode(TypeMirror type) { - super(type); - } - - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ThisNode; - } - - @Override - public int hashCode() { - return 3559101; // Objects.hash("this"); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + protected ThisNode(TypeMirror type) { + super(type); + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ThisNode; + } + + @Override + public int hashCode() { + return 3559101; // Objects.hash("this"); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java index 3adc6307d06..a80adae19ee 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ThrowTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for exception throws: @@ -21,51 +18,51 @@ */ public class ThrowNode extends Node { - protected final ThrowTree tree; - protected final Node expression; + protected final ThrowTree tree; + protected final Node expression; - public ThrowNode(ThrowTree tree, Node expression, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.expression = expression; - } + public ThrowNode(ThrowTree tree, Node expression, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.expression = expression; + } - public Node getExpression() { - return expression; - } + public Node getExpression() { + return expression; + } - @Override - public ThrowTree getTree() { - return tree; - } + @Override + public ThrowTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitThrow(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitThrow(this, p); + } - @Override - public String toString() { - return "throw " + expression; - } + @Override + public String toString() { + return "throw " + expression; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ThrowNode)) { - return false; - } - ThrowNode other = (ThrowNode) obj; - return getExpression().equals(other.getExpression()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ThrowNode)) { + return false; } + ThrowNode other = (ThrowNode) obj; + return getExpression().equals(other.getExpression()); + } - @Override - public int hashCode() { - return Objects.hash(ThrowNode.class, expression); - } + @Override + public int hashCode() { + return Objects.hash(ThrowNode.class, expression); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(expression); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(expression); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java index 55e35949827..dfad710c0dc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.TypeCastTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for the cast operator: @@ -19,56 +16,55 @@ */ public class TypeCastNode extends Node { - protected final TypeCastTree tree; - protected final Node operand; + protected final TypeCastTree tree; + protected final Node operand; - /** For Types.isSameType. */ - protected final Types types; + /** For Types.isSameType. */ + protected final Types types; - public TypeCastNode(TypeCastTree tree, Node operand, TypeMirror type, Types types) { - super(type); - this.tree = tree; - this.operand = operand; - this.types = types; - } + public TypeCastNode(TypeCastTree tree, Node operand, TypeMirror type, Types types) { + super(type); + this.tree = tree; + this.operand = operand; + this.types = types; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public TypeCastTree getTree() { - return tree; - } + @Override + public TypeCastTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitTypeCast(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitTypeCast(this, p); + } - @Override - public String toString() { - return "(" + getType() + ")" + getOperand(); - } + @Override + public String toString() { + return "(" + getType() + ")" + getOperand(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof TypeCastNode)) { - return false; - } - TypeCastNode other = (TypeCastNode) obj; - return getOperand().equals(other.getOperand()) - && types.isSameType(getType(), other.getType()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TypeCastNode)) { + return false; } + TypeCastNode other = (TypeCastNode) obj; + return getOperand().equals(other.getOperand()) && types.isSameType(getType(), other.getType()); + } - @Override - public int hashCode() { - return Objects.hash(getType(), getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(getType(), getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java index 37921a06531..bfcdb161d70 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java @@ -1,12 +1,10 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.UnaryTree; - -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a postfix or an unary expression. @@ -21,27 +19,27 @@ */ public abstract class UnaryOperationNode extends Node { - protected final UnaryTree tree; - protected final Node operand; - - protected UnaryOperationNode(UnaryTree tree, Node operand) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.operand = operand; - } - - public Node getOperand() { - return this.operand; - } - - @Override - public UnaryTree getTree() { - return tree; - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + protected final UnaryTree tree; + protected final Node operand; + + protected UnaryOperationNode(UnaryTree tree, Node operand) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return this.operand; + } + + @Override + public UnaryTree getTree() { + return tree; + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java index 661de7e83a8..a87002eb16b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java @@ -2,10 +2,8 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A node for bitwise right shift operations with zero extension: @@ -16,40 +14,40 @@ */ public class UnsignedRightShiftNode extends BinaryOperationNode { - /** - * Constructs an {@link UnsignedRightShiftNode} - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public UnsignedRightShiftNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.UNSIGNED_RIGHT_SHIFT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitUnsignedRightShift(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " >>> " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof UnsignedRightShiftNode)) { - return false; - } - UnsignedRightShiftNode other = (UnsignedRightShiftNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); + /** + * Constructs an {@link UnsignedRightShiftNode} + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public UnsignedRightShiftNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.UNSIGNED_RIGHT_SHIFT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitUnsignedRightShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >>> " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof UnsignedRightShiftNode)) { + return false; } + UnsignedRightShiftNode other = (UnsignedRightShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java index 8a461a45264..f8570e8aed4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java @@ -1,14 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.LiteralTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a literals that have some form of value: @@ -26,54 +24,54 @@ */ public abstract class ValueLiteralNode extends Node { - /** The tree for the value literal. */ - protected final LiteralTree tree; + /** The tree for the value literal. */ + protected final LiteralTree tree; - /** - * Returns the value of the literal, null for the null literal. - * - * @return the value of the literal, null for the null literal - */ - public abstract @Nullable Object getValue(); + /** + * Returns the value of the literal, null for the null literal. + * + * @return the value of the literal, null for the null literal + */ + public abstract @Nullable Object getValue(); - protected ValueLiteralNode(LiteralTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } + protected ValueLiteralNode(LiteralTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - @Override - public LiteralTree getTree() { - return tree; - } + @Override + public LiteralTree getTree() { + return tree; + } - @Override - public String toString() { - return String.valueOf(getValue()); - } + @Override + public String toString() { + return String.valueOf(getValue()); + } - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof ValueLiteralNode)) { - return false; - } - ValueLiteralNode other = (ValueLiteralNode) obj; - Object val = getValue(); - Object otherVal = other.getValue(); - return Objects.equals(val, otherVal); + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; } - - @Override - public int hashCode() { - // value might be null - return Objects.hash(this.getClass(), getValue()); + if (!(obj instanceof ValueLiteralNode)) { + return false; } + ValueLiteralNode other = (ValueLiteralNode) obj; + Object val = getValue(); + Object otherVal = other.getValue(); + return Objects.equals(val, otherVal); + } - @Override - @SideEffectFree - public final Collection getOperands() { - return Collections.emptyList(); - } + @Override + public int hashCode() { + // value might be null + return Objects.hash(this.getClass(), getValue()); + } + + @Override + @SideEffectFree + public final Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java index 3fe7294cd57..8b39801d6a5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java @@ -1,14 +1,12 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.VariableTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; /** * A node for a variable declaration, including local variables and fields: @@ -22,53 +20,53 @@ */ public class VariableDeclarationNode extends Node { - protected final VariableTree tree; - protected final String name; + protected final VariableTree tree; + protected final String name; - // TODO: make modifier accessible + // TODO: make modifier accessible - public VariableDeclarationNode(VariableTree t) { - super(TreeUtils.typeOf(t)); - tree = t; - name = tree.getName().toString(); - } + public VariableDeclarationNode(VariableTree t) { + super(TreeUtils.typeOf(t)); + tree = t; + name = tree.getName().toString(); + } - public String getName() { - return name; - } + public String getName() { + return name; + } - @Override - public VariableTree getTree() { - return tree; - } + @Override + public VariableTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitVariableDeclaration(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitVariableDeclaration(this, p); + } - @Override - public String toString() { - return name; - } + @Override + public String toString() { + return name; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof VariableDeclarationNode)) { - return false; - } - VariableDeclarationNode other = (VariableDeclarationNode) obj; - return getName().equals(other.getName()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof VariableDeclarationNode)) { + return false; } + VariableDeclarationNode other = (VariableDeclarationNode) obj; + return getName().equals(other.getName()); + } - @Override - public int hashCode() { - return Objects.hash(getName()); - } + @Override + public int hashCode() { + return Objects.hash(getName()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java index 07e5e031c70..566afde3e47 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TypesUtils; - import java.util.Collection; import java.util.Collections; import java.util.Objects; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TypesUtils; /** * A node for the widening primitive conversion operation. See JLS 5.1.2 for the definition of @@ -22,53 +19,53 @@ */ public class WideningConversionNode extends Node { - protected final Tree tree; - protected final Node operand; + protected final Tree tree; + protected final Node operand; - public WideningConversionNode(Tree tree, Node operand, TypeMirror type) { - super(type); - assert TypesUtils.isPrimitive(type) : "non-primitive type in widening conversion"; - this.tree = tree; - this.operand = operand; - } + public WideningConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + assert TypesUtils.isPrimitive(type) : "non-primitive type in widening conversion"; + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitWideningConversion(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitWideningConversion(this, p); + } - @Override - public String toString() { - return "WideningConversion(" + getOperand() + ", " + type + ")"; - } + @Override + public String toString() { + return "WideningConversion(" + getOperand() + ", " + type + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof WideningConversionNode)) { - return false; - } - WideningConversionNode other = (WideningConversionNode) obj; - return getOperand().equals(other.getOperand()) - && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof WideningConversionNode)) { + return false; } + WideningConversionNode other = (WideningConversionNode) obj; + return getOperand().equals(other.getOperand()) + && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); + } - @Override - public int hashCode() { - return Objects.hash(WideningConversionNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(WideningConversionNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java index 005a4c60d5e..7139214675a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java @@ -14,25 +14,25 @@ */ public class BusyExpressionPlayground { - /** Class cannot be instantiated. */ - private BusyExpressionPlayground() { - throw new AssertionError("Class BusyExpressionPlayground cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private BusyExpressionPlayground() { + throw new AssertionError("Class BusyExpressionPlayground cannot be instantiated."); + } - /** - * Run busy expression analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run busy expression analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // Run the analysis and create a PDF file - BusyExprTransfer transfer = new BusyExprTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); - } + // Run the analysis and create a PDF file + BusyExprTransfer transfer = new BusyExprTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java index 9a901bf5fd0..fddf9760e9b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java @@ -11,25 +11,25 @@ /** The playground for constant propagation analysis. */ public class ConstantPropagationPlayground { - /** Class cannot be instantiated. */ - private ConstantPropagationPlayground() { - throw new AssertionError("Class ConstantPropagationPlayground cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private ConstantPropagationPlayground() { + throw new AssertionError("Class ConstantPropagationPlayground cannot be instantiated."); + } - /** - * Run constant propagation analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run constant propagation analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // run the analysis and create a PDF file - ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); - } + // run the analysis and create a PDF file + ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java index 80682f13291..10946fcae33 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java @@ -11,25 +11,25 @@ /** The playground of live variable analysis. */ public class LiveVariablePlayground { - /** Do not instantiate. */ - private LiveVariablePlayground() { - throw new Error("do not instantiate"); - } + /** Do not instantiate. */ + private LiveVariablePlayground() { + throw new Error("do not instantiate"); + } - /** - * Run live variable analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run live variable analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // Run the analysis and create a PDF file - LiveVarTransfer transfer = new LiveVarTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); - } + // Run the analysis and create a PDF file + LiveVarTransfer transfer = new LiveVarTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java index 04e52030dc5..de2672f8543 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java @@ -14,25 +14,25 @@ */ public class ReachingDefinitionPlayground { - /** Class cannot be instantiated. */ - private ReachingDefinitionPlayground() { - throw new AssertionError("Class ReachingDefinitionPlayground cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private ReachingDefinitionPlayground() { + throw new AssertionError("Class ReachingDefinitionPlayground cannot be instantiated."); + } - /** - * Run reaching definition analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run reaching definition analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // Run the analysis and create a PDF file - ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); - } + // Run the analysis and create a PDF file + ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java index 04a8ea20cdc..7036fde635f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java @@ -1,5 +1,15 @@ package org.checkerframework.dataflow.cfg.visualize; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.StringJoiner; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; @@ -19,18 +29,6 @@ import org.plumelib.util.StringsPlume; import org.plumelib.util.UniqueId; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.StringJoiner; - -import javax.lang.model.type.TypeMirror; - /** * This abstract class makes implementing a {@link CFGVisualizer} easier. Some of the methods in * {@link CFGVisualizer} are already implemented in this abstract class, but can be overridden if @@ -43,431 +41,424 @@ * @see StringCFGVisualizer */ public abstract class AbstractCFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> - implements CFGVisualizer { - - /** - * If {@code true}, {@link CFGVisualizer} returns more detailed information. - * - *

Initialized in {@link #init(Map)}. - */ - protected boolean verbose; - - /** The line separator. */ - protected static final String lineSeparator = System.lineSeparator(); - - /** The indentation for elements of the store. */ - protected static final String storeEntryIndent = " "; - - @Override - public void init(Map args) { - this.verbose = toBoolean(args.get("verbose")); + V extends AbstractValue, S extends Store, T extends TransferFunction> + implements CFGVisualizer { + + /** + * If {@code true}, {@link CFGVisualizer} returns more detailed information. + * + *

Initialized in {@link #init(Map)}. + */ + protected boolean verbose; + + /** The line separator. */ + protected static final String lineSeparator = System.lineSeparator(); + + /** The indentation for elements of the store. */ + protected static final String storeEntryIndent = " "; + + @Override + public void init(Map args) { + this.verbose = toBoolean(args.get("verbose")); + } + + /** + * Convert the value to boolean, by parsing a string or casting any other value. null converts to + * false. + * + * @param o an object to convert to boolean + * @return {@code o} converted to boolean + */ + private static boolean toBoolean(@Nullable Object o) { + if (o == null) { + return false; } - - /** - * Convert the value to boolean, by parsing a string or casting any other value. null converts - * to false. - * - * @param o an object to convert to boolean - * @return {@code o} converted to boolean - */ - private static boolean toBoolean(@Nullable Object o) { - if (o == null) { - return false; - } - if (o instanceof String) { - return Boolean.parseBoolean((String) o); - } - return (boolean) o; + if (o instanceof String) { + return Boolean.parseBoolean((String) o); } - - /** - * Visualize a control flow graph. - * - * @param cfg the current control flow graph - * @param entry the entry block of the control flow graph - * @param analysis the current analysis - * @return the representation of the control flow graph - */ - protected String visualizeGraph( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - return visualizeGraphHeader() - + visualizeGraphWithoutHeaderAndFooter(cfg, entry, analysis) - + visualizeGraphFooter(); + return (boolean) o; + } + + /** + * Visualize a control flow graph. + * + * @param cfg the current control flow graph + * @param entry the entry block of the control flow graph + * @param analysis the current analysis + * @return the representation of the control flow graph + */ + protected String visualizeGraph( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + return visualizeGraphHeader() + + visualizeGraphWithoutHeaderAndFooter(cfg, entry, analysis) + + visualizeGraphFooter(); + } + + /** + * Helper method to visualize a control flow graph, without outputting a header or footer. + * + * @param cfg the control flow graph + * @param entry the entry block of the control flow graph + * @param analysis the current analysis + * @return the String representation of the control flow graph + */ + protected String visualizeGraphWithoutHeaderAndFooter( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + Set visited = new LinkedHashSet<>(); + StringBuilder sbGraph = new StringBuilder(); + Queue workList = new ArrayDeque<>(); + Block cur = entry; + visited.add(entry); + while (cur != null) { + handleSuccessorsHelper(cur, visited, workList, sbGraph); + cur = workList.poll(); } - - /** - * Helper method to visualize a control flow graph, without outputting a header or footer. - * - * @param cfg the control flow graph - * @param entry the entry block of the control flow graph - * @param analysis the current analysis - * @return the String representation of the control flow graph - */ - protected String visualizeGraphWithoutHeaderAndFooter( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - Set visited = new LinkedHashSet<>(); - StringBuilder sbGraph = new StringBuilder(); - Queue workList = new ArrayDeque<>(); - Block cur = entry; - visited.add(entry); - while (cur != null) { - handleSuccessorsHelper(cur, visited, workList, sbGraph); - cur = workList.poll(); - } + sbGraph.append(lineSeparator); + sbGraph.append(visualizeNodes(visited, cfg, analysis)); + return sbGraph.toString(); + } + + /** + * Outputs, to sbGraph, a visualization of a block's edges, but not the block itself. (The block + * itself is output elsewhere.) Also adds the successors of the block to the work list and the + * visited blocks list. + * + * @param cur the current block + * @param visited the set of blocks that have already been visited or are in the work list; side + * effected by this method + * @param workList the queue of blocks to be processed; side effected by this method + * @param sbGraph the {@link StringBuilder} to store the graph; side effected by this method + */ + protected void handleSuccessorsHelper( + Block cur, Set visited, Queue workList, StringBuilder sbGraph) { + if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { + ConditionalBlock ccur = ((ConditionalBlock) cur); + Block thenSuccessor = ccur.getThenSuccessor(); + sbGraph.append( + visualizeEdge(ccur.getUid(), thenSuccessor.getUid(), ccur.getThenFlowRule().toString())); + sbGraph.append(lineSeparator); + addBlock(thenSuccessor, visited, workList); + Block elseSuccessor = ccur.getElseSuccessor(); + sbGraph.append( + visualizeEdge(ccur.getUid(), elseSuccessor.getUid(), ccur.getElseFlowRule().toString())); + sbGraph.append(lineSeparator); + addBlock(elseSuccessor, visited, workList); + } else { + SingleSuccessorBlock sscur = (SingleSuccessorBlock) cur; + Block succ = sscur.getSuccessor(); + if (succ != null) { + sbGraph.append(visualizeEdge(cur.getUid(), succ.getUid(), sscur.getFlowRule().name())); sbGraph.append(lineSeparator); - sbGraph.append(visualizeNodes(visited, cfg, analysis)); - return sbGraph.toString(); + addBlock(succ, visited, workList); + } } - - /** - * Outputs, to sbGraph, a visualization of a block's edges, but not the block itself. (The block - * itself is output elsewhere.) Also adds the successors of the block to the work list and the - * visited blocks list. - * - * @param cur the current block - * @param visited the set of blocks that have already been visited or are in the work list; side - * effected by this method - * @param workList the queue of blocks to be processed; side effected by this method - * @param sbGraph the {@link StringBuilder} to store the graph; side effected by this method - */ - protected void handleSuccessorsHelper( - Block cur, Set visited, Queue workList, StringBuilder sbGraph) { - if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { - ConditionalBlock ccur = ((ConditionalBlock) cur); - Block thenSuccessor = ccur.getThenSuccessor(); - sbGraph.append( - visualizeEdge( - ccur.getUid(), - thenSuccessor.getUid(), - ccur.getThenFlowRule().toString())); - sbGraph.append(lineSeparator); - addBlock(thenSuccessor, visited, workList); - Block elseSuccessor = ccur.getElseSuccessor(); - sbGraph.append( - visualizeEdge( - ccur.getUid(), - elseSuccessor.getUid(), - ccur.getElseFlowRule().toString())); - sbGraph.append(lineSeparator); - addBlock(elseSuccessor, visited, workList); - } else { - SingleSuccessorBlock sscur = (SingleSuccessorBlock) cur; - Block succ = sscur.getSuccessor(); - if (succ != null) { - sbGraph.append( - visualizeEdge(cur.getUid(), succ.getUid(), sscur.getFlowRule().name())); - sbGraph.append(lineSeparator); - addBlock(succ, visited, workList); - } + if (cur.getType() == Block.BlockType.EXCEPTION_BLOCK) { + ExceptionBlock ecur = (ExceptionBlock) cur; + for (Map.Entry> e : ecur.getExceptionalSuccessors().entrySet()) { + TypeMirror cause = e.getKey(); + String exception = cause.toString(); + if (exception.startsWith("java.lang.")) { + exception = exception.replace("java.lang.", ""); } - if (cur.getType() == Block.BlockType.EXCEPTION_BLOCK) { - ExceptionBlock ecur = (ExceptionBlock) cur; - for (Map.Entry> e : ecur.getExceptionalSuccessors().entrySet()) { - TypeMirror cause = e.getKey(); - String exception = cause.toString(); - if (exception.startsWith("java.lang.")) { - exception = exception.replace("java.lang.", ""); - } - for (Block b : e.getValue()) { - sbGraph.append(visualizeEdge(cur.getUid(), b.getUid(), exception)); - sbGraph.append(lineSeparator); - addBlock(b, visited, workList); - } - } + for (Block b : e.getValue()) { + sbGraph.append(visualizeEdge(cur.getUid(), b.getUid(), exception)); + sbGraph.append(lineSeparator); + addBlock(b, visited, workList); } + } } - - /** - * Checks whether a block exists in the visited blocks list, and, if not, adds it to the visited - * blocks list and the work list. - * - * @param b the block to check - * @param visited the set of blocks that have already been visited or are in the work list - * @param workList the queue of blocks to be processed - */ - protected void addBlock(Block b, Set visited, Queue workList) { - if (visited.add(b)) { - workList.add(b); - } + } + + /** + * Checks whether a block exists in the visited blocks list, and, if not, adds it to the visited + * blocks list and the work list. + * + * @param b the block to check + * @param visited the set of blocks that have already been visited or are in the work list + * @param workList the queue of blocks to be processed + */ + protected void addBlock(Block b, Set visited, Queue workList) { + if (visited.add(b)) { + workList.add(b); } - - /** - * Helper method to visualize a block. - * - *

NOTE: The output ends with a separator, only if an "after" store is visualized. The client - * {@link #visualizeBlock} should correct this if needed. - * - * @param bb the block - * @param analysis the current analysis - * @param separator the line separator. Examples: "\\l" for left justification in {@link - * DOTCFGVisualizer} (this is really a terminator, not a separator), "\n" to add a new line - * in {@link StringCFGVisualizer} - * @return the String representation of the block - */ - protected String visualizeBlockHelper( - Block bb, @Nullable Analysis analysis, String separator) { - StringBuilder sbBlock = new StringBuilder(); - String contents = loopOverBlockContents(bb, analysis, separator); - if (!contents.isEmpty()) { - sbBlock.append(contents); - } - if (sbBlock.length() == 0) { - // Nothing got appended; use default text for empty block - if (bb.getType() == Block.BlockType.SPECIAL_BLOCK) { - sbBlock.append(visualizeSpecialBlock((SpecialBlock) bb)); - } else if (bb.getType() == Block.BlockType.CONDITIONAL_BLOCK) { - sbBlock.append(visualizeConditionalBlock((ConditionalBlock) bb)); - } else { - sbBlock.append(""); - } - } - - // Visualize transfer input if necessary. - if (analysis != null) { - sbBlock.insert(0, visualizeBlockTransferInputBefore(bb, analysis) + separator); - if (verbose) { - Node lastNode = bb.getLastNode(); - if (lastNode != null) { - if (!sbBlock.toString().endsWith(separator)) { - sbBlock.append(separator); - } - sbBlock.append(visualizeBlockTransferInputAfter(bb, analysis) + separator); - } - } - } - return sbBlock.toString(); + } + + /** + * Helper method to visualize a block. + * + *

NOTE: The output ends with a separator, only if an "after" store is visualized. The client + * {@link #visualizeBlock} should correct this if needed. + * + * @param bb the block + * @param analysis the current analysis + * @param separator the line separator. Examples: "\\l" for left justification in {@link + * DOTCFGVisualizer} (this is really a terminator, not a separator), "\n" to add a new line in + * {@link StringCFGVisualizer} + * @return the String representation of the block + */ + protected String visualizeBlockHelper( + Block bb, @Nullable Analysis analysis, String separator) { + StringBuilder sbBlock = new StringBuilder(); + String contents = loopOverBlockContents(bb, analysis, separator); + if (!contents.isEmpty()) { + sbBlock.append(contents); + } + if (sbBlock.length() == 0) { + // Nothing got appended; use default text for empty block + if (bb.getType() == Block.BlockType.SPECIAL_BLOCK) { + sbBlock.append(visualizeSpecialBlock((SpecialBlock) bb)); + } else if (bb.getType() == Block.BlockType.CONDITIONAL_BLOCK) { + sbBlock.append(visualizeConditionalBlock((ConditionalBlock) bb)); + } else { + sbBlock.append(""); + } } - /** - * Iterates over the block content and visualizes all the nodes in it. - * - * @param bb the block - * @param analysis the current analysis - * @param separator the separator between the nodes of the block - * @return the String representation of the contents of the block - */ - protected String loopOverBlockContents( - Block bb, @Nullable Analysis analysis, String separator) { - - List contents = addBlockContent(bb); - StringJoiner sjBlockContents = new StringJoiner(separator); - for (Node t : contents) { - sjBlockContents.add(visualizeBlockNode(t, analysis)); + // Visualize transfer input if necessary. + if (analysis != null) { + sbBlock.insert(0, visualizeBlockTransferInputBefore(bb, analysis) + separator); + if (verbose) { + Node lastNode = bb.getLastNode(); + if (lastNode != null) { + if (!sbBlock.toString().endsWith(separator)) { + sbBlock.append(separator); + } + sbBlock.append(visualizeBlockTransferInputAfter(bb, analysis) + separator); } - return sjBlockContents.toString(); + } } - - /** - * Returns the contents of the block. - * - * @param bb the block - * @return the contents of the block, as a list of nodes - */ - protected List addBlockContent(Block bb) { - return bb.getNodes(); + return sbBlock.toString(); + } + + /** + * Iterates over the block content and visualizes all the nodes in it. + * + * @param bb the block + * @param analysis the current analysis + * @param separator the separator between the nodes of the block + * @return the String representation of the contents of the block + */ + protected String loopOverBlockContents( + Block bb, @Nullable Analysis analysis, String separator) { + + List contents = addBlockContent(bb); + StringJoiner sjBlockContents = new StringJoiner(separator); + for (Node t : contents) { + sjBlockContents.add(visualizeBlockNode(t, analysis)); } - - /** - * Format the given object as a String suitable for the output format, i.e. with format-specific - * characters escaped. - * - * @param obj an object - * @return the formatted String from the given object - */ - protected abstract String format(Object obj); - - @Override - public String visualizeBlockNode(Node t, @Nullable Analysis analysis) { - StringBuilder sbBlockNode = new StringBuilder(); - sbBlockNode.append(format(t)).append(" [ ").append(getNodeSimpleName(t)).append(" ]"); - if (analysis != null) { - V value = analysis.getValue(t); - if (value != null) { - sbBlockNode.append(" > ").append(format(value)); - } - } - return sbBlockNode.toString(); + return sjBlockContents.toString(); + } + + /** + * Returns the contents of the block. + * + * @param bb the block + * @return the contents of the block, as a list of nodes + */ + protected List addBlockContent(Block bb) { + return bb.getNodes(); + } + + /** + * Format the given object as a String suitable for the output format, i.e. with format-specific + * characters escaped. + * + * @param obj an object + * @return the formatted String from the given object + */ + protected abstract String format(Object obj); + + @Override + public String visualizeBlockNode(Node t, @Nullable Analysis analysis) { + StringBuilder sbBlockNode = new StringBuilder(); + sbBlockNode.append(format(t)).append(" [ ").append(getNodeSimpleName(t)).append(" ]"); + if (analysis != null) { + V value = analysis.getValue(t); + if (value != null) { + sbBlockNode.append(" > ").append(format(value)); + } } - - /** Whether to visualize before or after a block. */ - protected enum VisualizeWhere { - /** Visualize before the block. */ - BEFORE, - /** Visualize after the block. */ - AFTER + return sbBlockNode.toString(); + } + + /** Whether to visualize before or after a block. */ + protected enum VisualizeWhere { + /** Visualize before the block. */ + BEFORE, + /** Visualize after the block. */ + AFTER + } + + /** + * Visualize the transfer input before or after the given block. + * + * @param where either BEFORE or AFTER + * @param bb a block + * @param analysis the current analysis + * @param separator the line separator. Examples: "\\l" for left justification in {@link + * DOTCFGVisualizer} (which is actually a line TERMINATOR, not a separator!), "\n" to add a + * new line in {@link StringCFGVisualizer} + * @return the visualization of the transfer input before or after the given block + */ + protected String visualizeBlockTransferInputHelper( + VisualizeWhere where, Block bb, Analysis analysis, String separator) { + if (analysis == null) { + throw new BugInCF( + "analysis must be non-null when visualizing the transfer input of a block."); } - /** - * Visualize the transfer input before or after the given block. - * - * @param where either BEFORE or AFTER - * @param bb a block - * @param analysis the current analysis - * @param separator the line separator. Examples: "\\l" for left justification in {@link - * DOTCFGVisualizer} (which is actually a line TERMINATOR, not a separator!), "\n" to add a - * new line in {@link StringCFGVisualizer} - * @return the visualization of the transfer input before or after the given block - */ - protected String visualizeBlockTransferInputHelper( - VisualizeWhere where, Block bb, Analysis analysis, String separator) { - if (analysis == null) { - throw new BugInCF( - "analysis must be non-null when visualizing the transfer input of a block."); - } - - Direction analysisDirection = analysis.getDirection(); - - S regularStore; - S thenStore = null; - S elseStore = null; - boolean isTwoStores = false; - - UniqueId storesFrom; - - if (analysisDirection == Direction.FORWARD && where == VisualizeWhere.AFTER) { - regularStore = analysis.getResult().getStoreAfter(bb); - storesFrom = analysis.getResult(); - } else if (analysisDirection == Direction.BACKWARD && where == VisualizeWhere.BEFORE) { - regularStore = analysis.getResult().getStoreBefore(bb); - storesFrom = analysis.getResult(); - } else { - TransferInput input = analysis.getInput(bb); - assert input != null : "@AssumeAssertion(nullness): invariant"; - storesFrom = input; - isTwoStores = input.containsTwoStores(); - regularStore = input.getRegularStore(); - thenStore = input.getThenStore(); - elseStore = input.getElseStore(); - } - - StringBuilder sbStore = new StringBuilder(); - if (verbose) { - sbStore.append(storesFrom.getClassAndUid() + separator); - } - sbStore.append(where == VisualizeWhere.BEFORE ? "Before: " : "After: "); - - if (!isTwoStores) { - sbStore.append(visualizeStore(regularStore)); - } else { - assert thenStore != null : "@AssumeAssertion(nullness): invariant"; - assert elseStore != null : "@AssumeAssertion(nullness): invariant"; - sbStore.append("then="); - sbStore.append(visualizeStore(thenStore)); - sbStore.append(","); - sbStore.append(separator); - sbStore.append("else="); - sbStore.append(visualizeStore(elseStore)); - } - if (where == VisualizeWhere.BEFORE) { - sbStore.append(separator + "~~~~~~~~~"); - } else { - sbStore.insert(0, "~~~~~~~~~" + separator); - } - return sbStore.toString(); + Direction analysisDirection = analysis.getDirection(); + + S regularStore; + S thenStore = null; + S elseStore = null; + boolean isTwoStores = false; + + UniqueId storesFrom; + + if (analysisDirection == Direction.FORWARD && where == VisualizeWhere.AFTER) { + regularStore = analysis.getResult().getStoreAfter(bb); + storesFrom = analysis.getResult(); + } else if (analysisDirection == Direction.BACKWARD && where == VisualizeWhere.BEFORE) { + regularStore = analysis.getResult().getStoreBefore(bb); + storesFrom = analysis.getResult(); + } else { + TransferInput input = analysis.getInput(bb); + assert input != null : "@AssumeAssertion(nullness): invariant"; + storesFrom = input; + isTwoStores = input.containsTwoStores(); + regularStore = input.getRegularStore(); + thenStore = input.getThenStore(); + elseStore = input.getElseStore(); } - /** - * Visualize a special block. - * - * @param sbb the special block - * @return the String representation of the special block - */ - protected String visualizeSpecialBlockHelper(SpecialBlock sbb) { - switch (sbb.getSpecialType()) { - case ENTRY: - return ""; - case EXIT: - return ""; - case EXCEPTIONAL_EXIT: - return ""; - default: - throw new BugInCF("Unrecognized special block type: " + sbb.getType()); - } + StringBuilder sbStore = new StringBuilder(); + if (verbose) { + sbStore.append(storesFrom.getClassAndUid() + separator); } - - /** - * Generate the order of processing blocks. Because a block may appear more than once in {@link - * ControlFlowGraph#getDepthFirstOrderedBlocks()}, the orders of each block are stored in a - * separate array list. - * - * @param cfg the current control flow graph - * @return an IdentityHashMap that maps from blocks to their orders - */ - protected IdentityHashMap> getProcessOrder(ControlFlowGraph cfg) { - IdentityHashMap> depthFirstOrder = new IdentityHashMap<>(); - int count = 1; - for (Block b : cfg.getDepthFirstOrderedBlocks()) { - depthFirstOrder.computeIfAbsent(b, k -> new ArrayList<>()); - @SuppressWarnings( - "nullness:assignment.type.incompatible") // computeIfAbsent's function doesn't - // return null - @NonNull List blockIds = depthFirstOrder.get(b); - blockIds.add(count++); - } - return depthFirstOrder; + sbStore.append(where == VisualizeWhere.BEFORE ? "Before: " : "After: "); + + if (!isTwoStores) { + sbStore.append(visualizeStore(regularStore)); + } else { + assert thenStore != null : "@AssumeAssertion(nullness): invariant"; + assert elseStore != null : "@AssumeAssertion(nullness): invariant"; + sbStore.append("then="); + sbStore.append(visualizeStore(thenStore)); + sbStore.append(","); + sbStore.append(separator); + sbStore.append("else="); + sbStore.append(visualizeStore(elseStore)); } - - @Override - public String visualizeStore(S store) { - return store.visualize(this); + if (where == VisualizeWhere.BEFORE) { + sbStore.append(separator + "~~~~~~~~~"); + } else { + sbStore.insert(0, "~~~~~~~~~" + separator); } - - /** - * Generate the String representation of the nodes of a control flow graph. - * - * @param blocks the set of all the blocks in a control flow graph - * @param cfg the control flow graph - * @param analysis the current analysis - * @return the String representation of the nodes - */ - protected abstract String visualizeNodes( - Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis); - - /** - * Generate the String representation of an edge. - * - * @param sId a representation of the current block, such as its ID - * @param eId a representation of the successor block, such as its ID - * @param flowRule the content of the edge - * @return the String representation of the edge - */ - protected abstract String visualizeEdge(Object sId, Object eId, String flowRule); - - /** - * Return the header of the generated graph. - * - * @return the String representation of the header of the control flow graph - */ - protected abstract String visualizeGraphHeader(); - - /** - * Return the footer of the generated graph. - * - * @return the String representation of the footer of the control flow graph - */ - protected abstract String visualizeGraphFooter(); - - /** - * Given a list of process orders (integers), returns a string representation. - * - *

Examples: "Process order: 23", "Process order: 23,25". - * - * @param order a list of process orders - * @return a String representation of the given process orders - */ - protected String getProcessOrderSimpleString(List order) { - return "Process order: " + StringsPlume.join(",", order); + return sbStore.toString(); + } + + /** + * Visualize a special block. + * + * @param sbb the special block + * @return the String representation of the special block + */ + protected String visualizeSpecialBlockHelper(SpecialBlock sbb) { + switch (sbb.getSpecialType()) { + case ENTRY: + return ""; + case EXIT: + return ""; + case EXCEPTIONAL_EXIT: + return ""; + default: + throw new BugInCF("Unrecognized special block type: " + sbb.getType()); } - - /** - * Get the simple name of a node. - * - * @param t a node - * @return the node's simple name, without "Node" - */ - protected String getNodeSimpleName(Node t) { - String name = t.getClass().getSimpleName(); - return name.replace("Node", ""); + } + + /** + * Generate the order of processing blocks. Because a block may appear more than once in {@link + * ControlFlowGraph#getDepthFirstOrderedBlocks()}, the orders of each block are stored in a + * separate array list. + * + * @param cfg the current control flow graph + * @return an IdentityHashMap that maps from blocks to their orders + */ + protected IdentityHashMap> getProcessOrder(ControlFlowGraph cfg) { + IdentityHashMap> depthFirstOrder = new IdentityHashMap<>(); + int count = 1; + for (Block b : cfg.getDepthFirstOrderedBlocks()) { + depthFirstOrder.computeIfAbsent(b, k -> new ArrayList<>()); + @SuppressWarnings( + "nullness:assignment.type.incompatible") // computeIfAbsent's function doesn't + // return null + @NonNull List blockIds = depthFirstOrder.get(b); + blockIds.add(count++); } + return depthFirstOrder; + } + + @Override + public String visualizeStore(S store) { + return store.visualize(this); + } + + /** + * Generate the String representation of the nodes of a control flow graph. + * + * @param blocks the set of all the blocks in a control flow graph + * @param cfg the control flow graph + * @param analysis the current analysis + * @return the String representation of the nodes + */ + protected abstract String visualizeNodes( + Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis); + + /** + * Generate the String representation of an edge. + * + * @param sId a representation of the current block, such as its ID + * @param eId a representation of the successor block, such as its ID + * @param flowRule the content of the edge + * @return the String representation of the edge + */ + protected abstract String visualizeEdge(Object sId, Object eId, String flowRule); + + /** + * Return the header of the generated graph. + * + * @return the String representation of the header of the control flow graph + */ + protected abstract String visualizeGraphHeader(); + + /** + * Return the footer of the generated graph. + * + * @return the String representation of the footer of the control flow graph + */ + protected abstract String visualizeGraphFooter(); + + /** + * Given a list of process orders (integers), returns a string representation. + * + *

Examples: "Process order: 23", "Process order: 23,25". + * + * @param order a list of process orders + * @return a String representation of the given process orders + */ + protected String getProcessOrderSimpleString(List order) { + return "Process order: " + StringsPlume.join(",", order); + } + + /** + * Get the simple name of a node. + * + * @param t a node + * @return the node's simple name, without "Node" + */ + protected String getNodeSimpleName(Node t) { + String name = t.getClass().getSimpleName(); + return name.replace("Node", ""); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java index d0ff19faea2..f95f6d83fff 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java @@ -5,7 +5,14 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Options; - +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Collections; +import java.util.Map; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; @@ -17,16 +24,6 @@ import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.plumelib.util.ArrayMap; -import java.io.FileWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.util.Collections; -import java.util.Map; - -import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; - /** * Launcher to generate the DOT or String representation of the control flow graph of a given method * in a given class. @@ -38,335 +35,321 @@ */ public final class CFGVisualizeLauncher { - /** Class cannot be instantiated. */ - private CFGVisualizeLauncher() { - throw new AssertionError("Class CFGVisualizeLauncher cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private CFGVisualizeLauncher() { + throw new AssertionError("Class CFGVisualizeLauncher cannot be instantiated."); + } - /** - * The main entry point of CFGVisualizeLauncher. - * - * @param args command-line arguments - */ - public static void main(String[] args) { - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + /** + * The main entry point of CFGVisualizeLauncher. + * + * @param args command-line arguments + */ + public static void main(String[] args) { + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - performAnalysis(config, null); - } + performAnalysis(config, null); + } - /** - * Generate a visualization of the CFG of a method, with an optional analysis. - * - * @param the abstract value type of the analysis - * @param the store type of the analysis - * @param the transfer function type of the analysis - * @param config CFGVisualizeOptions that includes input file, output directory, method name, - * and class name - * @param analysis analysis to perform before the visualization (or {@code null} if no analysis - * is to be performed) - */ - public static , S extends Store, T extends TransferFunction> - void performAnalysis(CFGVisualizeOptions config, @Nullable Analysis analysis) { - if (!config.isString()) { - if (analysis == null) { - generateDOTofCFGWithoutAnalysis( - config.getInputFile(), - config.getOutputDirectory(), - config.getMethodName(), - config.getClassName(), - config.isPDF(), - config.isVerbose()); - } else { - generateDOTofCFG( - config.getInputFile(), - config.getOutputDirectory(), - config.getMethodName(), - config.getClassName(), - config.isPDF(), - config.isVerbose(), - analysis); - } + /** + * Generate a visualization of the CFG of a method, with an optional analysis. + * + * @param the abstract value type of the analysis + * @param the store type of the analysis + * @param the transfer function type of the analysis + * @param config CFGVisualizeOptions that includes input file, output directory, method name, and + * class name + * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is + * to be performed) + */ + public static , S extends Store, T extends TransferFunction> + void performAnalysis(CFGVisualizeOptions config, @Nullable Analysis analysis) { + if (!config.isString()) { + if (analysis == null) { + generateDOTofCFGWithoutAnalysis( + config.getInputFile(), + config.getOutputDirectory(), + config.getMethodName(), + config.getClassName(), + config.isPDF(), + config.isVerbose()); + } else { + generateDOTofCFG( + config.getInputFile(), + config.getOutputDirectory(), + config.getMethodName(), + config.getClassName(), + config.isPDF(), + config.isVerbose(), + analysis); + } + } else { + if (analysis == null) { + String stringGraph = + generateStringOfCFGWithoutAnalysis( + config.getInputFile(), + config.getMethodName(), + config.getClassName(), + config.isVerbose()); + System.out.println(stringGraph); + } else { + Map res = + generateStringOfCFG( + config.getInputFile(), + config.getMethodName(), + config.getClassName(), + config.isVerbose(), + analysis); + if (res != null) { + String stringGraph = (String) res.get("stringGraph"); + if (stringGraph == null) { + System.err.println( + "Unexpected output from generating string control flow graph, shouldn't be" + + " null. Result map: " + + res); + return; + } + System.out.println(stringGraph); } else { - if (analysis == null) { - String stringGraph = - generateStringOfCFGWithoutAnalysis( - config.getInputFile(), - config.getMethodName(), - config.getClassName(), - config.isVerbose()); - System.out.println(stringGraph); - } else { - Map res = - generateStringOfCFG( - config.getInputFile(), - config.getMethodName(), - config.getClassName(), - config.isVerbose(), - analysis); - if (res != null) { - String stringGraph = (String) res.get("stringGraph"); - if (stringGraph == null) { - System.err.println( - "Unexpected output from generating string control flow graph, shouldn't be" - + " null. Result map: " - + res); - return; - } - System.out.println(stringGraph); - } else { - System.err.println( - "Unexpected output from generating string control flow graph, shouldn't be" - + " null."); - } - } + System.err.println( + "Unexpected output from generating string control flow graph, shouldn't be" + + " null."); } + } } + } - /** - * Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis. - * - * @param inputFile a Java source file, used as input - * @param outputDir output directory - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param pdf also generate a PDF - * @param verbose show verbose information in CFG - */ - private static void generateDOTofCFGWithoutAnalysis( - String inputFile, - String outputDir, - String method, - String clas, - boolean pdf, - boolean verbose) { - generateDOTofCFG(inputFile, outputDir, method, clas, pdf, verbose, null); - } + /** + * Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis. + * + * @param inputFile a Java source file, used as input + * @param outputDir output directory + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param pdf also generate a PDF + * @param verbose show verbose information in CFG + */ + private static void generateDOTofCFGWithoutAnalysis( + String inputFile, + String outputDir, + String method, + String clas, + boolean pdf, + boolean verbose) { + generateDOTofCFG(inputFile, outputDir, method, clas, pdf, verbose, null); + } - /** - * Generate the String representation of the CFG for a method, only. Does no dataflow analysis. - * - * @param inputFile a Java source file, used as input - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param verbose show verbose information in CFG - * @return the String representation of the CFG - */ - private static String generateStringOfCFGWithoutAnalysis( - String inputFile, String method, String clas, boolean verbose) { - Map res = generateStringOfCFG(inputFile, method, clas, verbose, null); - if (res != null) { - String stringGraph = (String) res.get("stringGraph"); - if (stringGraph == null) { - return "Unexpected output from generating string control flow graph, shouldn't be" - + " null."; - } - return stringGraph; - } else { - return "Unexpected output from generating string control flow graph, shouldn't be" - + " null."; - } + /** + * Generate the String representation of the CFG for a method, only. Does no dataflow analysis. + * + * @param inputFile a Java source file, used as input + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param verbose show verbose information in CFG + * @return the String representation of the CFG + */ + private static String generateStringOfCFGWithoutAnalysis( + String inputFile, String method, String clas, boolean verbose) { + Map res = generateStringOfCFG(inputFile, method, clas, verbose, null); + if (res != null) { + String stringGraph = (String) res.get("stringGraph"); + if (stringGraph == null) { + return "Unexpected output from generating string control flow graph, shouldn't be" + + " null."; + } + return stringGraph; + } else { + return "Unexpected output from generating string control flow graph, shouldn't be" + " null."; } + } - /** - * Generate the DOT representation of the CFG for a method. - * - * @param the abstract value type to be tracked by the analysis - * @param the store type used in the analysis - * @param the transfer function type that is used to approximated runtime behavior - * @param inputFile a Java source file, used as input - * @param outputDir source output directory - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param pdf also generate a PDF - * @param verbose show verbose information in CFG - * @param analysis analysis to perform before the visualization (or {@code null} if no analysis - * is to be performed) - */ - private static < - V extends AbstractValue, - S extends Store, - T extends TransferFunction> - void generateDOTofCFG( - String inputFile, - String outputDir, - String method, - String clas, - boolean pdf, - boolean verbose, - @Nullable Analysis analysis) { - ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); - if (analysis != null) { - analysis.performAnalysis(cfg); - } + /** + * Generate the DOT representation of the CFG for a method. + * + * @param the abstract value type to be tracked by the analysis + * @param the store type used in the analysis + * @param the transfer function type that is used to approximated runtime behavior + * @param inputFile a Java source file, used as input + * @param outputDir source output directory + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param pdf also generate a PDF + * @param verbose show verbose information in CFG + * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is + * to be performed) + */ + private static , S extends Store, T extends TransferFunction> + void generateDOTofCFG( + String inputFile, + String outputDir, + String method, + String clas, + boolean pdf, + boolean verbose, + @Nullable Analysis analysis) { + ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); + if (analysis != null) { + analysis.performAnalysis(cfg); + } - Map args = new ArrayMap<>(2); - args.put("outdir", outputDir); - args.put("verbose", verbose); + Map args = new ArrayMap<>(2); + args.put("outdir", outputDir); + args.put("verbose", verbose); - CFGVisualizer viz = new DOTCFGVisualizer<>(); - viz.init(args); - Map res = viz.visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); - viz.shutdown(); + CFGVisualizer viz = new DOTCFGVisualizer<>(); + viz.init(args); + Map res = viz.visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); + viz.shutdown(); - if (pdf && res != null) { - assert res.get("dotFileName") != null : "@AssumeAssertion(nullness): specification"; - producePDF((String) res.get("dotFileName")); - } + if (pdf && res != null) { + assert res.get("dotFileName") != null : "@AssumeAssertion(nullness): specification"; + producePDF((String) res.get("dotFileName")); } + } - /** - * Generate the control flow graph of a method in a class. - * - * @param file a Java source file, used as input - * @param clas name of the class which includes the method to generate the CFG for - * @param method name of the method to generate the CFG for - * @return control flow graph of the specified method - */ - public static ControlFlowGraph generateMethodCFG(String file, String clas, String method) { - CFGProcessor cfgProcessor = new CFGProcessor(clas, method); - - Context context = new Context(); - Options.instance(context).put("compilePolicy", "ATTR_ONLY"); - JavaCompiler javac = new JavaCompiler(context); + /** + * Generate the control flow graph of a method in a class. + * + * @param file a Java source file, used as input + * @param clas name of the class which includes the method to generate the CFG for + * @param method name of the method to generate the CFG for + * @return control flow graph of the specified method + */ + public static ControlFlowGraph generateMethodCFG(String file, String clas, String method) { + CFGProcessor cfgProcessor = new CFGProcessor(clas, method); - JavaFileObject l; - try (@SuppressWarnings( - "mustcall:type.argument") // Context isn't annotated for the Must Call - // Checker. - JavacFileManager fileManager = - (JavacFileManager) context.get(JavaFileManager.class)) { - l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next(); - } catch (IOException e) { - throw new Error(e); - } + Context context = new Context(); + Options.instance(context).put("compilePolicy", "ATTR_ONLY"); + JavaCompiler javac = new JavaCompiler(context); - PrintStream err = System.err; - try { - // Redirect syserr to nothing (and prevent the compiler from issuing - // warnings about our exception). - @SuppressWarnings({ - "builder:required.method.not.called", - "mustcall:assignment" - }) // Won't be needed in JDK 11+ with use of "OutputStream.nullOutputStream()". - @MustCall() OutputStream nullOS = - // In JDK 11+, this can be just "OutputStream.nullOutputStream()". - new OutputStream() { - @Override - public void write(int b) throws IOException {} - }; - System.setErr(new PrintStream(nullOS)); - javac.compile(List.of(l), List.of(clas), List.of(cfgProcessor), List.nil()); - } catch (Throwable e) { - // ok - } finally { - System.setErr(err); - } + JavaFileObject l; + try (@SuppressWarnings("mustcall:type.argument") // Context isn't annotated for the Must Call + // Checker. + JavacFileManager fileManager = (JavacFileManager) context.get(JavaFileManager.class)) { + l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next(); + } catch (IOException e) { + throw new Error(e); + } - CFGProcessResult res = cfgProcessor.getCFGProcessResult(); + PrintStream err = System.err; + try { + // Redirect syserr to nothing (and prevent the compiler from issuing + // warnings about our exception). + @SuppressWarnings({ + "builder:required.method.not.called", + "mustcall:assignment" + }) // Won't be needed in JDK 11+ with use of "OutputStream.nullOutputStream()". + @MustCall() OutputStream nullOS = + // In JDK 11+, this can be just "OutputStream.nullOutputStream()". + new OutputStream() { + @Override + public void write(int b) throws IOException {} + }; + System.setErr(new PrintStream(nullOS)); + javac.compile(List.of(l), List.of(clas), List.of(cfgProcessor), List.nil()); + } catch (Throwable e) { + // ok + } finally { + System.setErr(err); + } - if (res == null) { - printError( - "internal error in type processor! method typeProcessOver() doesn't get" - + " called."); - System.exit(1); - } + CFGProcessResult res = cfgProcessor.getCFGProcessResult(); - if (!res.isSuccess()) { - printError(res.getErrMsg()); - System.exit(1); - } + if (res == null) { + printError( + "internal error in type processor! method typeProcessOver() doesn't get" + " called."); + System.exit(1); + } - return res.getCFG(); + if (!res.isSuccess()) { + printError(res.getErrMsg()); + System.exit(1); } - /** - * Write generated String representation of the CFG for a method to a file. - * - * @param inputFile a Java source file, used as input - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param outputFile source output file - * @param analysis instance of forward or backward analysis from specific dataflow test case - */ - @SuppressWarnings("CatchAndPrintStackTrace") // we want to use e.printStackTrace here. - public static void writeStringOfCFG( - String inputFile, - String method, - String clas, - String outputFile, - Analysis analysis) { - Map res = generateStringOfCFG(inputFile, method, clas, true, analysis); - try (FileWriter out = new FileWriter(outputFile)) { - if (res != null && res.get("stringGraph") != null) { - out.write(res.get("stringGraph").toString()); - } - out.write(System.lineSeparator()); - } catch (IOException e) { - e.printStackTrace(); - } + return res.getCFG(); + } + + /** + * Write generated String representation of the CFG for a method to a file. + * + * @param inputFile a Java source file, used as input + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param outputFile source output file + * @param analysis instance of forward or backward analysis from specific dataflow test case + */ + @SuppressWarnings("CatchAndPrintStackTrace") // we want to use e.printStackTrace here. + public static void writeStringOfCFG( + String inputFile, String method, String clas, String outputFile, Analysis analysis) { + Map res = generateStringOfCFG(inputFile, method, clas, true, analysis); + try (FileWriter out = new FileWriter(outputFile)) { + if (res != null && res.get("stringGraph") != null) { + out.write(res.get("stringGraph").toString()); + } + out.write(System.lineSeparator()); + } catch (IOException e) { + e.printStackTrace(); } + } - /** - * Invoke "dot" command to generate a PDF. - * - * @param file name of the dot file - */ - private static void producePDF(String file) { - try { - String command = "dot -Tpdf \"" + file + "\" -o \"" + file + ".pdf\""; - Process child = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command}); - child.waitFor(); - } catch (InterruptedException | IOException e) { - e.printStackTrace(); - System.exit(1); - } + /** + * Invoke "dot" command to generate a PDF. + * + * @param file name of the dot file + */ + private static void producePDF(String file) { + try { + String command = "dot -Tpdf \"" + file + "\" -o \"" + file + ".pdf\""; + Process child = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command}); + child.waitFor(); + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + System.exit(1); } + } - /** - * Generate the String representation of the CFG for a method. - * - * @param the abstract value type to be tracked by the analysis - * @param the store type used in the analysis - * @param the transfer function type that is used to approximated runtime behavior - * @param inputFile a Java source file, used as input - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param verbose show verbose information in CFG - * @param analysis analysis to perform before the visualization (or {@code null} if no analysis - * is to be performed) - * @return a map which includes a key "stringGraph" and the String representation of CFG as the - * value - */ - private static < - V extends AbstractValue, - S extends Store, - T extends TransferFunction> - @Nullable Map generateStringOfCFG( - String inputFile, - String method, - String clas, - boolean verbose, - @Nullable Analysis analysis) { - ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); - if (analysis != null) { - analysis.performAnalysis(cfg); - } + /** + * Generate the String representation of the CFG for a method. + * + * @param the abstract value type to be tracked by the analysis + * @param the store type used in the analysis + * @param the transfer function type that is used to approximated runtime behavior + * @param inputFile a Java source file, used as input + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param verbose show verbose information in CFG + * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is + * to be performed) + * @return a map which includes a key "stringGraph" and the String representation of CFG as the + * value + */ + private static , S extends Store, T extends TransferFunction> + @Nullable Map generateStringOfCFG( + String inputFile, + String method, + String clas, + boolean verbose, + @Nullable Analysis analysis) { + ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); + if (analysis != null) { + analysis.performAnalysis(cfg); + } - Map args = Collections.singletonMap("verbose", verbose); + Map args = Collections.singletonMap("verbose", verbose); - CFGVisualizer viz = new StringCFGVisualizer<>(); - viz.init(args); - Map res = viz.visualize(cfg, cfg.getEntryBlock(), analysis); - viz.shutdown(); - return res; - } + CFGVisualizer viz = new StringCFGVisualizer<>(); + viz.init(args); + Map res = viz.visualize(cfg, cfg.getEntryBlock(), analysis); + viz.shutdown(); + return res; + } - /** - * Print error message. - * - * @param string error message - */ - private static void printError(@Nullable String string) { - System.err.println("ERROR: " + string); - } + /** + * Print error message. + * + * @param string error message + */ + private static void printError(@Nullable String string) { + System.err.println("ERROR: " + string); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java index 39ba302c5ad..1a563a31aee 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java @@ -1,8 +1,7 @@ package org.checkerframework.dataflow.cfg.visualize; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.io.File; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Options for running analysis on files. @@ -14,242 +13,241 @@ */ public class CFGVisualizeOptions { - /** Default method name. */ - private static final String DEFAULT_METHOD = "test"; - - /** Default class name. */ - private static final String DEFAULT_CLASS = "Test"; - - /** Default output directory. */ - private static final String DEFAULT_OUTPUT_DIR = "."; - - /** The input file. */ - private final String input; - - /** The output directory. */ - private final String output; - - /** The method name. */ - private final String method; - - /** The class name. */ - private final String clas; - - /** True if the PDF should be generated. */ - private final boolean pdf; - - /** True if the verbose output should be generated. */ - private final boolean verbose; - - /** True if the string representation should be generated. */ - private final boolean string; - - /** - * Private constructor. - * - *

This constructor is private to ensure that the object is only created by calling {@link - * #parseArgs(String[])}. - * - * @param input the input file - * @param output the output directory - * @param method the method name - * @param clas the class name - * @param pdf true if the PDF should be generated - * @param verbose true if the verbose output should be generated - * @param string true if the string representation should be generated - */ - private CFGVisualizeOptions( - String input, - String output, - String method, - String clas, - boolean pdf, - boolean verbose, - boolean string) { - this.input = input; - this.output = output; - this.method = method; - this.clas = clas; - this.pdf = pdf; - this.verbose = verbose; - this.string = string; + /** Default method name. */ + private static final String DEFAULT_METHOD = "test"; + + /** Default class name. */ + private static final String DEFAULT_CLASS = "Test"; + + /** Default output directory. */ + private static final String DEFAULT_OUTPUT_DIR = "."; + + /** The input file. */ + private final String input; + + /** The output directory. */ + private final String output; + + /** The method name. */ + private final String method; + + /** The class name. */ + private final String clas; + + /** True if the PDF should be generated. */ + private final boolean pdf; + + /** True if the verbose output should be generated. */ + private final boolean verbose; + + /** True if the string representation should be generated. */ + private final boolean string; + + /** + * Private constructor. + * + *

This constructor is private to ensure that the object is only created by calling {@link + * #parseArgs(String[])}. + * + * @param input the input file + * @param output the output directory + * @param method the method name + * @param clas the class name + * @param pdf true if the PDF should be generated + * @param verbose true if the verbose output should be generated + * @param string true if the string representation should be generated + */ + private CFGVisualizeOptions( + String input, + String output, + String method, + String clas, + boolean pdf, + boolean verbose, + boolean string) { + this.input = input; + this.output = output; + this.method = method; + this.clas = clas; + this.pdf = pdf; + this.verbose = verbose; + this.string = string; + } + + /** + * Parse the command line arguments. + * + *

This method calls System.exit(1) if there are no arguments or if the input file cannot be + * read. + * + * @param args command-line arguments, see {@link #printUsage()} + * @return CFGVisualizeOptions object containing the parsed options + */ + public static CFGVisualizeOptions parseArgs(String[] args) { + if (args.length == 0) { + printUsage(); + System.exit(1); } - - /** - * Parse the command line arguments. - * - *

This method calls System.exit(1) if there are no arguments or if the input file cannot be - * read. - * - * @param args command-line arguments, see {@link #printUsage()} - * @return CFGVisualizeOptions object containing the parsed options - */ - public static CFGVisualizeOptions parseArgs(String[] args) { - if (args.length == 0) { - printUsage(); - System.exit(1); - } - String input = args[0]; - File file = new File(input); - if (!file.canRead()) { - printError("Cannot read input file: " + file.getAbsolutePath()); - printUsage(); - System.exit(1); - } - - String method = DEFAULT_METHOD; - String clas = DEFAULT_CLASS; - String output = DEFAULT_OUTPUT_DIR; - boolean pdf = false; - boolean error = false; - boolean verbose = false; - boolean string = false; - - for (int i = 1; i < args.length; i++) { - switch (args[i]) { - case "--outputdir": - if (i >= args.length - 1) { - printError("Did not find after --outputdir."); - continue; - } - i++; - output = args[i]; - break; - case "--pdf": - pdf = true; - break; - case "--method": - if (i >= args.length - 1) { - printError("Did not find after --method."); - continue; - } - i++; - method = args[i]; - break; - case "--class": - if (i >= args.length - 1) { - printError("Did not find after --class."); - continue; - } - i++; - clas = args[i]; - break; - case "--verbose": - verbose = true; - break; - case "--string": - string = true; - break; - default: - printError("Unknown command line argument: " + args[i]); - error = true; - break; - } - } - - if (error) { - System.exit(1); - } - - return new CFGVisualizeOptions(input, output, method, clas, pdf, verbose, string); + String input = args[0]; + File file = new File(input); + if (!file.canRead()) { + printError("Cannot read input file: " + file.getAbsolutePath()); + printUsage(); + System.exit(1); } - /** - * Getter for the input file. - * - * @return the input file - */ - public String getInputFile() { - return input; + String method = DEFAULT_METHOD; + String clas = DEFAULT_CLASS; + String output = DEFAULT_OUTPUT_DIR; + boolean pdf = false; + boolean error = false; + boolean verbose = false; + boolean string = false; + + for (int i = 1; i < args.length; i++) { + switch (args[i]) { + case "--outputdir": + if (i >= args.length - 1) { + printError("Did not find after --outputdir."); + continue; + } + i++; + output = args[i]; + break; + case "--pdf": + pdf = true; + break; + case "--method": + if (i >= args.length - 1) { + printError("Did not find after --method."); + continue; + } + i++; + method = args[i]; + break; + case "--class": + if (i >= args.length - 1) { + printError("Did not find after --class."); + continue; + } + i++; + clas = args[i]; + break; + case "--verbose": + verbose = true; + break; + case "--string": + string = true; + break; + default: + printError("Unknown command line argument: " + args[i]); + error = true; + break; + } } - /** - * Getter for the output directory. - * - * @return the output directory - */ - public String getOutputDirectory() { - return output; + if (error) { + System.exit(1); } - /** - * Getter for the method name. - * - * @return the method name - */ - public String getMethodName() { - return method; - } - - /** - * Getter for the class name. - * - * @return the class name - */ - public String getClassName() { - return clas; - } - - /** - * Getter for the PDF flag. - * - * @return true if the PDF should be generated - */ - public boolean isPDF() { - return pdf; - } - - /** - * Getter for the verbose flag. - * - * @return true if the verbose output should be generated - */ - public boolean isVerbose() { - return verbose; - } - - /** - * Getter for the string flag. - * - * @return true if the string representation should be generated - */ - public boolean isString() { - return string; - } - - /** - * Print usage information. - * - *

Sends the usage information to System.out. - */ - private static void printUsage() { - System.out.println( - "Generate the control flow graph of a Java method, represented as a DOT or String" - + " graph."); - System.out.println( - "Parameters: [--outputdir ] [--method ] [--class" - + " ] [--pdf] [--verbose] [--string]"); - System.out.println( - " --outputdir: The output directory for the generated files (defaults to '.')."); - System.out.println( - " --method: The method to generate the CFG for (defaults to 'test')."); - System.out.println( - " --class: The class in which to find the method (defaults to 'Test')."); - System.out.println(" --pdf: Also generate the PDF by invoking 'dot'."); - System.out.println(" --verbose: Show the verbose output (defaults to 'false')."); - System.out.println( - " --string: Print the string representation of the control flow graph" - + " (defaults to 'false')."); - } - - /** - * Print error message. - * - *

Sends the error message to System.err. - * - * @param string error message - */ - private static void printError(@Nullable String string) { - System.err.println("ERROR: " + string); - } + return new CFGVisualizeOptions(input, output, method, clas, pdf, verbose, string); + } + + /** + * Getter for the input file. + * + * @return the input file + */ + public String getInputFile() { + return input; + } + + /** + * Getter for the output directory. + * + * @return the output directory + */ + public String getOutputDirectory() { + return output; + } + + /** + * Getter for the method name. + * + * @return the method name + */ + public String getMethodName() { + return method; + } + + /** + * Getter for the class name. + * + * @return the class name + */ + public String getClassName() { + return clas; + } + + /** + * Getter for the PDF flag. + * + * @return true if the PDF should be generated + */ + public boolean isPDF() { + return pdf; + } + + /** + * Getter for the verbose flag. + * + * @return true if the verbose output should be generated + */ + public boolean isVerbose() { + return verbose; + } + + /** + * Getter for the string flag. + * + * @return true if the string representation should be generated + */ + public boolean isString() { + return string; + } + + /** + * Print usage information. + * + *

Sends the usage information to System.out. + */ + private static void printUsage() { + System.out.println( + "Generate the control flow graph of a Java method, represented as a DOT or String" + + " graph."); + System.out.println( + "Parameters: [--outputdir ] [--method ] [--class" + + " ] [--pdf] [--verbose] [--string]"); + System.out.println( + " --outputdir: The output directory for the generated files (defaults to '.')."); + System.out.println(" --method: The method to generate the CFG for (defaults to 'test')."); + System.out.println( + " --class: The class in which to find the method (defaults to 'Test')."); + System.out.println(" --pdf: Also generate the PDF by invoking 'dot'."); + System.out.println(" --verbose: Show the verbose output (defaults to 'false')."); + System.out.println( + " --string: Print the string representation of the control flow graph" + + " (defaults to 'false')."); + } + + /** + * Print error message. + * + *

Sends the error message to System.err. + * + * @param string error message + */ + private static void printError(@Nullable String string) { + System.err.println("ERROR: " + string); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java index d23a6eafbb9..8ca4378715b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java @@ -1,5 +1,6 @@ package org.checkerframework.dataflow.cfg.visualize; +import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; import org.checkerframework.dataflow.analysis.Analysis; @@ -16,8 +17,6 @@ import org.checkerframework.dataflow.expression.LocalVariable; import org.checkerframework.dataflow.expression.MethodCall; -import java.util.Map; - /** * Perform some visualization on a control flow graph. The particular operations depend on the * implementation. @@ -27,196 +26,196 @@ * @param the transfer function type that is used to approximate runtime behavior */ public interface CFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> { - /** - * Initialization method guaranteed to be called once before the first invocation of {@link - * #visualize} or {@link #visualizeWithAction}. - * - * @param args implementation-dependent options - */ - void init(Map args); - - /** - * Returns the separator for lines within a node's representation. - * - * @return the separator for lines within a node's representation - */ - public abstract String getSeparator(); - - /** - * Creates a visualization representing the control flow graph starting at {@code entry}. The - * keys and values in the returned map are implementation dependent. The method should not - * perform any actions. - * - *

An invocation {@code visualize(cfg, entry, null);} does not output stores at the beginning - * of basic blocks. - * - * @param cfg the CFG to visualize - * @param entry the entry node of the control flow graph to be represented - * @param analysis an analysis containing information about the program represented by the CFG. - * The information includes {@link Store}s that are valid at the beginning of basic blocks - * reachable from {@code entry} and per-node information for value producing {@link Node}s. - * Can also be {@code null} to indicate that this information should not be output. - * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a - * String representation of the CFG ({@link StringCFGVisualizer}) - * @see #visualizeWithAction(ControlFlowGraph, Block, Analysis) - */ - @Nullable Map visualize( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); - - /** - * Output a visualization representing the control flow graph starting at {@code entry}. The - * keys and values in the returned map are implementation dependent. The concrete actions are - * implementation dependent, and can include outputting information and producing files. - * - *

An invocation {@code visualizeWithAction(cfg, entry, null);} does not output stores at the - * beginning of basic blocks. - * - * @param cfg the CFG to visualize - * @param entry the entry node of the control flow graph to be represented - * @param analysis an analysis containing information about the program represented by the CFG. - * The information includes {@link Store}s that are valid at the beginning of basic blocks - * reachable from {@code entry} and per-node information for value producing {@link Node}s. - * Can also be {@code null} to indicate that this information should not be output. - * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a - * String representation of the CFG ({@link StringCFGVisualizer}) - * @see #visualize(ControlFlowGraph, Block, Analysis) - */ - @Nullable Map visualizeWithAction( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); - - /** - * Delegate the visualization responsibility to the passed {@link Store} instance, which will - * call back to this visualizer instance for sub-components. - * - * @param store the store to visualize - * @return the String representation of the given store - */ - String visualizeStore(S store); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize a local variable. - * - * @param localVar the local variable - * @param value the value of the local variable - * @return the String representation of the local variable - */ - String visualizeStoreLocalVar(LocalVariable localVar, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of the current - * object {@code this} in this Store. - * - * @param value the value of the current object {@code this} - * @return the String representation of {@code this} - */ - String visualizeStoreThisVal(V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one field - * collected by this Store. - * - * @param fieldAccess the field - * @param value the value of the field - * @return the String representation of the field - */ - String visualizeStoreFieldVal(FieldAccess fieldAccess, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one array - * collected by this Store. - * - * @param arrayValue the array - * @param value the value of the array - * @return the String representation of the array - */ - String visualizeStoreArrayVal(ArrayAccess arrayValue, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of pure method - * calls collected by this Store. - * - * @param methodCall the pure method call - * @param value the value of the pure method call - * @return the String representation of the pure method call - */ - String visualizeStoreMethodVals(MethodCall methodCall, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of class names - * collected by this Store. - * - * @param className the class name - * @param value the value of the class name - * @return the String representation of the class name - */ - String visualizeStoreClassVals(ClassName className, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the specific information - * collected according to the specific kind of Store. Currently, these Stores call this method: - * {@code LockStore}, {@code NullnessStore}, and {@code InitializationStore} to visualize - * additional information. - * - * @param keyName the name of the specific information to be visualized - * @param value the value of the specific information to be visualized - * @return the String representation of the specific information - */ - String visualizeStoreKeyVal(String keyName, Object value); - - /** - * Visualize a block based on the analysis. - * - * @param bb the block - * @param analysis the current analysis - * @return the String representation of the given block - */ - String visualizeBlock(Block bb, @Nullable Analysis analysis); - - /** - * Visualize a SpecialBlock. - * - * @param sbb the special block - * @return the String representation of the type of the special block {@code sbb}: entry, exit, - * or exceptional-exit - */ - String visualizeSpecialBlock(SpecialBlock sbb); - - /** - * Visualize a ConditionalBlock. - * - * @param cbb the conditional block - * @return the String representation of the conditional block - */ - String visualizeConditionalBlock(ConditionalBlock cbb); - - /** - * Visualize the transferInput before a Block based on the analysis. - * - * @param bb the block - * @param analysis the current analysis - * @return the String representation of the transferInput before the given block - */ - String visualizeBlockTransferInputBefore(Block bb, Analysis analysis); - - /** - * Visualize the transferInput after a Block based on the analysis. - * - * @param bb the block - * @param analysis the current analysis - * @return the String representation of the transferInput after the given block - */ - String visualizeBlockTransferInputAfter(Block bb, Analysis analysis); - - /** - * Visualize a Node based on the analysis. - * - * @param t the node - * @param analysis the current analysis - * @return the String representation of the given node - */ - String visualizeBlockNode(Node t, @Nullable Analysis analysis); - - /** Shutdown method called once from the shutdown hook of the {@code BaseTypeChecker}. */ - void shutdown(); + V extends AbstractValue, S extends Store, T extends TransferFunction> { + /** + * Initialization method guaranteed to be called once before the first invocation of {@link + * #visualize} or {@link #visualizeWithAction}. + * + * @param args implementation-dependent options + */ + void init(Map args); + + /** + * Returns the separator for lines within a node's representation. + * + * @return the separator for lines within a node's representation + */ + public abstract String getSeparator(); + + /** + * Creates a visualization representing the control flow graph starting at {@code entry}. The keys + * and values in the returned map are implementation dependent. The method should not perform any + * actions. + * + *

An invocation {@code visualize(cfg, entry, null);} does not output stores at the beginning + * of basic blocks. + * + * @param cfg the CFG to visualize + * @param entry the entry node of the control flow graph to be represented + * @param analysis an analysis containing information about the program represented by the CFG. + * The information includes {@link Store}s that are valid at the beginning of basic blocks + * reachable from {@code entry} and per-node information for value producing {@link Node}s. + * Can also be {@code null} to indicate that this information should not be output. + * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a String + * representation of the CFG ({@link StringCFGVisualizer}) + * @see #visualizeWithAction(ControlFlowGraph, Block, Analysis) + */ + @Nullable Map visualize( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); + + /** + * Output a visualization representing the control flow graph starting at {@code entry}. The keys + * and values in the returned map are implementation dependent. The concrete actions are + * implementation dependent, and can include outputting information and producing files. + * + *

An invocation {@code visualizeWithAction(cfg, entry, null);} does not output stores at the + * beginning of basic blocks. + * + * @param cfg the CFG to visualize + * @param entry the entry node of the control flow graph to be represented + * @param analysis an analysis containing information about the program represented by the CFG. + * The information includes {@link Store}s that are valid at the beginning of basic blocks + * reachable from {@code entry} and per-node information for value producing {@link Node}s. + * Can also be {@code null} to indicate that this information should not be output. + * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a String + * representation of the CFG ({@link StringCFGVisualizer}) + * @see #visualize(ControlFlowGraph, Block, Analysis) + */ + @Nullable Map visualizeWithAction( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); + + /** + * Delegate the visualization responsibility to the passed {@link Store} instance, which will call + * back to this visualizer instance for sub-components. + * + * @param store the store to visualize + * @return the String representation of the given store + */ + String visualizeStore(S store); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize a local variable. + * + * @param localVar the local variable + * @param value the value of the local variable + * @return the String representation of the local variable + */ + String visualizeStoreLocalVar(LocalVariable localVar, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of the current + * object {@code this} in this Store. + * + * @param value the value of the current object {@code this} + * @return the String representation of {@code this} + */ + String visualizeStoreThisVal(V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one field + * collected by this Store. + * + * @param fieldAccess the field + * @param value the value of the field + * @return the String representation of the field + */ + String visualizeStoreFieldVal(FieldAccess fieldAccess, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one array + * collected by this Store. + * + * @param arrayValue the array + * @param value the value of the array + * @return the String representation of the array + */ + String visualizeStoreArrayVal(ArrayAccess arrayValue, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of pure method + * calls collected by this Store. + * + * @param methodCall the pure method call + * @param value the value of the pure method call + * @return the String representation of the pure method call + */ + String visualizeStoreMethodVals(MethodCall methodCall, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of class names + * collected by this Store. + * + * @param className the class name + * @param value the value of the class name + * @return the String representation of the class name + */ + String visualizeStoreClassVals(ClassName className, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the specific information + * collected according to the specific kind of Store. Currently, these Stores call this method: + * {@code LockStore}, {@code NullnessStore}, and {@code InitializationStore} to visualize + * additional information. + * + * @param keyName the name of the specific information to be visualized + * @param value the value of the specific information to be visualized + * @return the String representation of the specific information + */ + String visualizeStoreKeyVal(String keyName, Object value); + + /** + * Visualize a block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + * @return the String representation of the given block + */ + String visualizeBlock(Block bb, @Nullable Analysis analysis); + + /** + * Visualize a SpecialBlock. + * + * @param sbb the special block + * @return the String representation of the type of the special block {@code sbb}: entry, exit, or + * exceptional-exit + */ + String visualizeSpecialBlock(SpecialBlock sbb); + + /** + * Visualize a ConditionalBlock. + * + * @param cbb the conditional block + * @return the String representation of the conditional block + */ + String visualizeConditionalBlock(ConditionalBlock cbb); + + /** + * Visualize the transferInput before a Block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + * @return the String representation of the transferInput before the given block + */ + String visualizeBlockTransferInputBefore(Block bb, Analysis analysis); + + /** + * Visualize the transferInput after a Block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + * @return the String representation of the transferInput after the given block + */ + String visualizeBlockTransferInputAfter(Block bb, Analysis analysis); + + /** + * Visualize a Node based on the analysis. + * + * @param t the node + * @param analysis the current analysis + * @return the String representation of the given node + */ + String visualizeBlockNode(Node t, @Nullable Analysis analysis); + + /** Shutdown method called once from the shutdown hook of the {@code BaseTypeChecker}. */ + void shutdown(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java index 88d26e95566..d8da5ae0487 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java @@ -3,7 +3,15 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.tree.JCTree; - +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -30,345 +38,331 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.UserError; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; - /** Generate a graph description in the DOT language of a control graph. */ public class DOTCFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> - extends AbstractCFGVisualizer { - - /** The output directory. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method - protected String outDir; - - /** The (optional) checker name. Used as a part of the name of the output dot file. */ - protected @Nullable String checkerName; - - /** Mapping from class/method representation to generated dot file. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method - protected Map generated; - - /** Terminator for lines that are left-justified. */ - protected static final String leftJustifiedTerminator = "\\l"; - - @Override - @SuppressWarnings("nullness") // assume arguments are set correctly - public void init(Map args) { - super.init(args); - this.outDir = (String) args.get("outdir"); - if (this.outDir == null) { - throw new BugInCF( - "outDir should never be null," - + " provide it in args when calling DOTCFGVisualizer.init(args)."); - } - this.checkerName = (String) args.get("checkerName"); - this.generated = new HashMap<>(); - } - - @Override - public String getSeparator() { - return leftJustifiedTerminator; + V extends AbstractValue, S extends Store, T extends TransferFunction> + extends AbstractCFGVisualizer { + + /** The output directory. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method + protected String outDir; + + /** The (optional) checker name. Used as a part of the name of the output dot file. */ + protected @Nullable String checkerName; + + /** Mapping from class/method representation to generated dot file. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method + protected Map generated; + + /** Terminator for lines that are left-justified. */ + protected static final String leftJustifiedTerminator = "\\l"; + + @Override + @SuppressWarnings("nullness") // assume arguments are set correctly + public void init(Map args) { + super.init(args); + this.outDir = (String) args.get("outdir"); + if (this.outDir == null) { + throw new BugInCF( + "outDir should never be null," + + " provide it in args when calling DOTCFGVisualizer.init(args)."); } - - @Override - public Map visualize( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - String dotGraph = visualizeGraph(cfg, entry, analysis); - - Map vis = new HashMap<>(2); - vis.put("dotGraph", dotGraph); - return vis; - } - - @Override - public Map visualizeWithAction( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - Map vis = visualize(cfg, entry, analysis); - String dotGraph = (String) vis.get("dotGraph"); - if (dotGraph == null) { - throw new BugInCF("dotGraph key missing in visualize result!"); - } - String dotFileName = dotOutputFileName(cfg.underlyingAST); - - try (BufferedWriter out = new BufferedWriter(new FileWriter(dotFileName))) { - out.write(dotGraph); - } catch (IOException e) { - throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e); - } - vis.put("dotFileName", dotFileName); - return vis; - } - - @SuppressWarnings("keyfor:enhancedfor.type.incompatible") - @Override - public String visualizeNodes( - Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { - - StringBuilder sbDotNodes = new StringBuilder(); - - IdentityHashMap> processOrder = getProcessOrder(cfg); - - // Definition of all nodes including their labels. - for (@KeyFor("processOrder") Block v : blocks) { - sbDotNodes.append(" ").append(v.getUid()).append(" ["); - if (v.getType() == BlockType.CONDITIONAL_BLOCK) { - sbDotNodes.append("shape=polygon sides=8 "); - } else if (v.getType() == BlockType.SPECIAL_BLOCK) { - sbDotNodes.append("shape=oval "); - } else { - sbDotNodes.append("shape=rectangle "); - } - sbDotNodes.append("label=\""); - if (verbose) { - sbDotNodes - .append(getProcessOrderSimpleString(processOrder.get(v))) - .append(getSeparator()); - } - String strBlock = visualizeBlock(v, analysis); - if (strBlock.length() == 0) { - if (v.getType() == BlockType.CONDITIONAL_BLOCK) { - // The footer of the conditional block. - sbDotNodes.append("\"];"); - } else { - // The footer of the block which has no content and is not a special or - // conditional block. - sbDotNodes.append("?? empty ??\"];"); - } - } else { - sbDotNodes.append(strBlock).append("\"];"); - } - sbDotNodes.append(System.lineSeparator()); - } - return sbDotNodes.toString(); - } - - @Override - protected String visualizeEdge(Object sId, Object eId, String flowRule) { - return " " + format(sId) + " -> " + format(eId) + " [label=\"" + flowRule + "\"];"; + this.checkerName = (String) args.get("checkerName"); + this.generated = new HashMap<>(); + } + + @Override + public String getSeparator() { + return leftJustifiedTerminator; + } + + @Override + public Map visualize( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + String dotGraph = visualizeGraph(cfg, entry, analysis); + + Map vis = new HashMap<>(2); + vis.put("dotGraph", dotGraph); + return vis; + } + + @Override + public Map visualizeWithAction( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + Map vis = visualize(cfg, entry, analysis); + String dotGraph = (String) vis.get("dotGraph"); + if (dotGraph == null) { + throw new BugInCF("dotGraph key missing in visualize result!"); } + String dotFileName = dotOutputFileName(cfg.underlyingAST); - @Override - public String visualizeBlock(Block bb, @Nullable Analysis analysis) { - return super.visualizeBlockHelper(bb, analysis, getSeparator()); + try (BufferedWriter out = new BufferedWriter(new FileWriter(dotFileName))) { + out.write(dotGraph); + } catch (IOException e) { + throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e); } - - @Override - public String visualizeSpecialBlock(SpecialBlock sbb) { - return super.visualizeSpecialBlockHelper(sbb); - } - - @Override - public String visualizeConditionalBlock(ConditionalBlock cbb) { - // No extra content in DOT output. - return ""; - } - - @Override - public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.BEFORE, bb, analysis, getSeparator()); - } - - @Override - public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.AFTER, bb, analysis, getSeparator()); - } - - /** - * Create a dot file and return its name. - * - * @param ast an abstract syntax tree - * @return the file name used for DOT output - */ - protected String dotOutputFileName(UnderlyingAST ast) { - StringBuilder srcLoc = new StringBuilder(); - StringBuilder outFile = new StringBuilder(); - - if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { - CFGStatement cfgStatement = (CFGStatement) ast; - String clsName = cfgStatement.getSimpleClassName(); - outFile.append(clsName); - outFile.append("-initializer-"); - outFile.append(ast.getUid()); - - srcLoc.append("<"); - srcLoc.append(clsName); - srcLoc.append("::initializer::"); - srcLoc.append(((JCTree) cfgStatement.getCode()).pos); - srcLoc.append(">"); - } else if (ast.getKind() == UnderlyingAST.Kind.METHOD) { - CFGMethod cfgMethod = (CFGMethod) ast; - String clsName = cfgMethod.getSimpleClassName(); - String methodName = cfgMethod.getMethodName(); - StringJoiner params = new StringJoiner(","); - for (VariableTree tree : cfgMethod.getMethod().getParameters()) { - params.add(tree.getType().toString()); - } - outFile.append(clsName); - outFile.append("-"); - outFile.append(methodName); - if (params.length() != 0) { - outFile.append("-"); - outFile.append(params); - } - - srcLoc.append("<"); - srcLoc.append(clsName); - srcLoc.append("::"); - srcLoc.append(methodName); - srcLoc.append("("); - srcLoc.append(params); - srcLoc.append(")::"); - srcLoc.append(((JCTree) cfgMethod.getMethod()).pos); - srcLoc.append(">"); - } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { - CFGLambda cfgLambda = (CFGLambda) ast; - String clsName = cfgLambda.getSimpleClassName(); - String enclosingMethodName = cfgLambda.getEnclosingMethodName(); - long uid = TreeUtils.treeUids.get(cfgLambda.getCode()); - outFile.append(clsName); - outFile.append("-"); - if (enclosingMethodName != null) { - outFile.append(enclosingMethodName); - outFile.append("-"); - } - outFile.append(uid); - - srcLoc.append("<"); - srcLoc.append(clsName); - if (enclosingMethodName != null) { - srcLoc.append("::"); - srcLoc.append(enclosingMethodName); - srcLoc.append("("); - @SuppressWarnings( - "nullness") // enclosingMethodName != null => getEnclosingMethod() != null - @NonNull MethodTree method = cfgLambda.getEnclosingMethod(); - srcLoc.append(method.getParameters()); - srcLoc.append(")"); - } - srcLoc.append("::"); - srcLoc.append(((JCTree) cfgLambda.getCode()).pos); - srcLoc.append(">"); + vis.put("dotFileName", dotFileName); + return vis; + } + + @SuppressWarnings("keyfor:enhancedfor.type.incompatible") + @Override + public String visualizeNodes( + Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { + + StringBuilder sbDotNodes = new StringBuilder(); + + IdentityHashMap> processOrder = getProcessOrder(cfg); + + // Definition of all nodes including their labels. + for (@KeyFor("processOrder") Block v : blocks) { + sbDotNodes.append(" ").append(v.getUid()).append(" ["); + if (v.getType() == BlockType.CONDITIONAL_BLOCK) { + sbDotNodes.append("shape=polygon sides=8 "); + } else if (v.getType() == BlockType.SPECIAL_BLOCK) { + sbDotNodes.append("shape=oval "); + } else { + sbDotNodes.append("shape=rectangle "); + } + sbDotNodes.append("label=\""); + if (verbose) { + sbDotNodes.append(getProcessOrderSimpleString(processOrder.get(v))).append(getSeparator()); + } + String strBlock = visualizeBlock(v, analysis); + if (strBlock.length() == 0) { + if (v.getType() == BlockType.CONDITIONAL_BLOCK) { + // The footer of the conditional block. + sbDotNodes.append("\"];"); } else { - throw new BugInCF("Unexpected AST kind: " + ast.getKind() + " value: " + ast); + // The footer of the block which has no content and is not a special or + // conditional block. + sbDotNodes.append("?? empty ??\"];"); } - if (checkerName != null && !checkerName.isEmpty()) { - outFile.append('-'); - outFile.append(checkerName); - } - outFile.append(".dot"); - - // make path safe for Linux - if (outFile.length() > 255) { - outFile.setLength(255); - } - // make path safe for Windows - String outFileBaseName = outFile.toString().replace("<", "_").replace(">", ""); - String outFileName = outDir + "/" + outFileBaseName; - - generated.put(srcLoc.toString(), outFileName); - - return outFileName; - } - - @Override - protected String format(Object obj) { - return escapeString(obj); - } - - @Override - public String visualizeStoreThisVal(V value) { - return storeEntryIndent + "this > " + escapeString(value); + } else { + sbDotNodes.append(strBlock).append("\"];"); + } + sbDotNodes.append(System.lineSeparator()); } - - @Override - public String visualizeStoreLocalVar(LocalVariable localVar, V value) { - return storeEntryIndent + localVar + " > " + escapeString(value); + return sbDotNodes.toString(); + } + + @Override + protected String visualizeEdge(Object sId, Object eId, String flowRule) { + return " " + format(sId) + " -> " + format(eId) + " [label=\"" + flowRule + "\"];"; + } + + @Override + public String visualizeBlock(Block bb, @Nullable Analysis analysis) { + return super.visualizeBlockHelper(bb, analysis, getSeparator()); + } + + @Override + public String visualizeSpecialBlock(SpecialBlock sbb) { + return super.visualizeSpecialBlockHelper(sbb); + } + + @Override + public String visualizeConditionalBlock(ConditionalBlock cbb) { + // No extra content in DOT output. + return ""; + } + + @Override + public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.BEFORE, bb, analysis, getSeparator()); + } + + @Override + public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.AFTER, bb, analysis, getSeparator()); + } + + /** + * Create a dot file and return its name. + * + * @param ast an abstract syntax tree + * @return the file name used for DOT output + */ + protected String dotOutputFileName(UnderlyingAST ast) { + StringBuilder srcLoc = new StringBuilder(); + StringBuilder outFile = new StringBuilder(); + + if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { + CFGStatement cfgStatement = (CFGStatement) ast; + String clsName = cfgStatement.getSimpleClassName(); + outFile.append(clsName); + outFile.append("-initializer-"); + outFile.append(ast.getUid()); + + srcLoc.append("<"); + srcLoc.append(clsName); + srcLoc.append("::initializer::"); + srcLoc.append(((JCTree) cfgStatement.getCode()).pos); + srcLoc.append(">"); + } else if (ast.getKind() == UnderlyingAST.Kind.METHOD) { + CFGMethod cfgMethod = (CFGMethod) ast; + String clsName = cfgMethod.getSimpleClassName(); + String methodName = cfgMethod.getMethodName(); + StringJoiner params = new StringJoiner(","); + for (VariableTree tree : cfgMethod.getMethod().getParameters()) { + params.add(tree.getType().toString()); + } + outFile.append(clsName); + outFile.append("-"); + outFile.append(methodName); + if (params.length() != 0) { + outFile.append("-"); + outFile.append(params); + } + + srcLoc.append("<"); + srcLoc.append(clsName); + srcLoc.append("::"); + srcLoc.append(methodName); + srcLoc.append("("); + srcLoc.append(params); + srcLoc.append(")::"); + srcLoc.append(((JCTree) cfgMethod.getMethod()).pos); + srcLoc.append(">"); + } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { + CFGLambda cfgLambda = (CFGLambda) ast; + String clsName = cfgLambda.getSimpleClassName(); + String enclosingMethodName = cfgLambda.getEnclosingMethodName(); + long uid = TreeUtils.treeUids.get(cfgLambda.getCode()); + outFile.append(clsName); + outFile.append("-"); + if (enclosingMethodName != null) { + outFile.append(enclosingMethodName); + outFile.append("-"); + } + outFile.append(uid); + + srcLoc.append("<"); + srcLoc.append(clsName); + if (enclosingMethodName != null) { + srcLoc.append("::"); + srcLoc.append(enclosingMethodName); + srcLoc.append("("); + @SuppressWarnings("nullness") // enclosingMethodName != null => getEnclosingMethod() != null + @NonNull MethodTree method = cfgLambda.getEnclosingMethod(); + srcLoc.append(method.getParameters()); + srcLoc.append(")"); + } + srcLoc.append("::"); + srcLoc.append(((JCTree) cfgLambda.getCode()).pos); + srcLoc.append(">"); + } else { + throw new BugInCF("Unexpected AST kind: " + ast.getKind() + " value: " + ast); } - - @Override - public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { - return storeEntryIndent + fieldAccess + " > " + escapeString(value); + if (checkerName != null && !checkerName.isEmpty()) { + outFile.append('-'); + outFile.append(checkerName); } + outFile.append(".dot"); - @Override - public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { - return storeEntryIndent + arrayValue + " > " + escapeString(value); + // make path safe for Linux + if (outFile.length() > 255) { + outFile.setLength(255); } - - @Override - public String visualizeStoreMethodVals(MethodCall methodCall, V value) { - return storeEntryIndent + escapeString(methodCall) + " > " + escapeString(value); + // make path safe for Windows + String outFileBaseName = outFile.toString().replace("<", "_").replace(">", ""); + String outFileName = outDir + "/" + outFileBaseName; + + generated.put(srcLoc.toString(), outFileName); + + return outFileName; + } + + @Override + protected String format(Object obj) { + return escapeString(obj); + } + + @Override + public String visualizeStoreThisVal(V value) { + return storeEntryIndent + "this > " + escapeString(value); + } + + @Override + public String visualizeStoreLocalVar(LocalVariable localVar, V value) { + return storeEntryIndent + localVar + " > " + escapeString(value); + } + + @Override + public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { + return storeEntryIndent + fieldAccess + " > " + escapeString(value); + } + + @Override + public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { + return storeEntryIndent + arrayValue + " > " + escapeString(value); + } + + @Override + public String visualizeStoreMethodVals(MethodCall methodCall, V value) { + return storeEntryIndent + escapeString(methodCall) + " > " + escapeString(value); + } + + @Override + public String visualizeStoreClassVals(ClassName className, V value) { + return storeEntryIndent + className + " > " + escapeString(value); + } + + @Override + public String visualizeStoreKeyVal(String keyName, Object value) { + return storeEntryIndent + keyName + " = " + value; + } + + /** + * Escape the input String. + * + * @param str the string to be escaped + * @return the escaped version of the string + */ + private static String escapeString(String str) { + return str.replace("\"", "\\\"").replace("\r", "\\\\r").replace("\n", "\\\\n"); + } + + /** + * Escape the double quotes from the string representation of the given object. + * + * @param obj an object + * @return an escaped version of the string representation of the object + */ + private static String escapeString(Object obj) { + return escapeString(String.valueOf(obj)); + } + + /** + * Write a file {@code methods.txt} that contains a mapping from source code location to generated + * dot file. + */ + @Override + public void shutdown() { + // Open for append, in case of multiple sub-checkers. + try (FileWriter fstream = new FileWriter(outDir + "/methods.txt", true); + BufferedWriter out = new BufferedWriter(fstream)) { + for (Map.Entry kv : generated.entrySet()) { + out.write(kv.getKey()); + out.append("\t"); + out.write(kv.getValue()); + out.append(lineSeparator); + } + } catch (IOException e) { + throw new UserError( + "Error creating methods.txt file in: " + outDir + "; ensure the path is valid", e); } + } - @Override - public String visualizeStoreClassVals(ClassName className, V value) { - return storeEntryIndent + className + " > " + escapeString(value); - } - - @Override - public String visualizeStoreKeyVal(String keyName, Object value) { - return storeEntryIndent + keyName + " = " + value; - } - - /** - * Escape the input String. - * - * @param str the string to be escaped - * @return the escaped version of the string - */ - private static String escapeString(String str) { - return str.replace("\"", "\\\"").replace("\r", "\\\\r").replace("\n", "\\\\n"); - } + @Override + protected String visualizeGraphHeader() { + return "digraph {" + lineSeparator; + } - /** - * Escape the double quotes from the string representation of the given object. - * - * @param obj an object - * @return an escaped version of the string representation of the object - */ - private static String escapeString(Object obj) { - return escapeString(String.valueOf(obj)); - } - - /** - * Write a file {@code methods.txt} that contains a mapping from source code location to - * generated dot file. - */ - @Override - public void shutdown() { - // Open for append, in case of multiple sub-checkers. - try (FileWriter fstream = new FileWriter(outDir + "/methods.txt", true); - BufferedWriter out = new BufferedWriter(fstream)) { - for (Map.Entry kv : generated.entrySet()) { - out.write(kv.getKey()); - out.append("\t"); - out.write(kv.getValue()); - out.append(lineSeparator); - } - } catch (IOException e) { - throw new UserError( - "Error creating methods.txt file in: " + outDir + "; ensure the path is valid", - e); - } - } - - @Override - protected String visualizeGraphHeader() { - return "digraph {" + lineSeparator; - } - - @Override - protected String visualizeGraphFooter() { - return "}"; - } + @Override + protected String visualizeGraphFooter() { + return "}"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java index 196e243759a..ca81680ec0f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java @@ -1,5 +1,12 @@ package org.checkerframework.dataflow.cfg.visualize; +import java.io.PrintStream; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; @@ -17,180 +24,172 @@ import org.checkerframework.dataflow.expression.LocalVariable; import org.checkerframework.dataflow.expression.MethodCall; -import java.io.PrintStream; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; - /** Generate the String representation of a control flow graph. */ public class StringCFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> - extends AbstractCFGVisualizer { - - /** Stream to output String representation to. */ - protected PrintStream out; - - /** Create a StringCFGVisualizer. */ - public StringCFGVisualizer() { - out = System.out; - } - - @Override - public void init(Map args) { - super.init(args); - PrintStream argout = (PrintStream) args.get("output"); - if (argout != null) { - out = argout; - } - } - - @Override - public String getSeparator() { - return "\n"; - } - - @Override - public Map visualize( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - String stringGraph = visualizeGraph(cfg, entry, analysis); - return Collections.singletonMap("stringGraph", stringGraph); - } - - @Override - public Map visualizeWithAction( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - Map vis = visualize(cfg, entry, analysis); - String stringGraph = (String) vis.get("stringGraph"); - out.println(stringGraph); - return vis; - } - - @SuppressWarnings("keyfor:enhancedfor.type.incompatible") - @Override - public String visualizeNodes( - Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { - StringJoiner sjStringNodes = new StringJoiner(lineSeparator); - IdentityHashMap> processOrder = getProcessOrder(cfg); - - // Generate all the Nodes. - for (@KeyFor("processOrder") Block v : blocks) { - sjStringNodes.add(v.getUid() + ":"); - if (verbose) { - sjStringNodes.add(getProcessOrderSimpleString(processOrder.get(v))); - } - sjStringNodes.add(visualizeBlock(v, analysis)); - sjStringNodes.add(""); - } - - return sjStringNodes.toString().trim(); - } - - @Override - protected String visualizeEdge(Object sId, Object eId, String flowRule) { - if (this.verbose) { - return sId + " -> " + eId + " " + flowRule; - } - return sId + " -> " + eId; - } - - @Override - public String visualizeBlock(Block bb, @Nullable Analysis analysis) { - return super.visualizeBlockHelper(bb, analysis, lineSeparator).trim(); - } - - @Override - public String visualizeSpecialBlock(SpecialBlock sbb) { - return super.visualizeSpecialBlockHelper(sbb); - } - - @Override - public String visualizeConditionalBlock(ConditionalBlock cbb) { - return "ConditionalBlock: then: " - + cbb.getThenSuccessor().getUid() - + ", else: " - + cbb.getElseSuccessor().getUid(); - } - - @Override - public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.BEFORE, bb, analysis, lineSeparator); - } - - @Override - public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.AFTER, bb, analysis, lineSeparator); - } - - @Override - protected String format(Object obj) { - return obj.toString(); - } - - @Override - public String visualizeStoreThisVal(V value) { - return storeEntryIndent + "this > " + value; - } - - @Override - public String visualizeStoreLocalVar(LocalVariable localVar, V value) { - return storeEntryIndent + localVar + " > " + value; - } - - @Override - public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { - return storeEntryIndent + fieldAccess + " > " + value; - } - - @Override - public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { - return storeEntryIndent + arrayValue + " > " + value; - } - - @Override - public String visualizeStoreMethodVals(MethodCall methodCall, V value) { - return storeEntryIndent + methodCall + " > " + value; - } - - @Override - public String visualizeStoreClassVals(ClassName className, V value) { - return storeEntryIndent + className + " > " + value; - } - - @Override - public String visualizeStoreKeyVal(String keyName, Object value) { - return storeEntryIndent + keyName + " = " + value; - } - - /** - * {@inheritDoc} - * - *

StringCFGVisualizer does not write into file, so left intentionally blank. - */ - @Override - public void shutdown() {} - - /** - * {@inheritDoc} - * - *

StringCFGVisualizer does not need a specific header, so just return an empty string. - */ - @Override - protected String visualizeGraphHeader() { - return ""; - } - - /** - * {@inheritDoc} - * - *

StringCFGVisualizer does not need a specific footer, so just return an empty string. - */ - @Override - protected String visualizeGraphFooter() { - return ""; - } + V extends AbstractValue, S extends Store, T extends TransferFunction> + extends AbstractCFGVisualizer { + + /** Stream to output String representation to. */ + protected PrintStream out; + + /** Create a StringCFGVisualizer. */ + public StringCFGVisualizer() { + out = System.out; + } + + @Override + public void init(Map args) { + super.init(args); + PrintStream argout = (PrintStream) args.get("output"); + if (argout != null) { + out = argout; + } + } + + @Override + public String getSeparator() { + return "\n"; + } + + @Override + public Map visualize( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + String stringGraph = visualizeGraph(cfg, entry, analysis); + return Collections.singletonMap("stringGraph", stringGraph); + } + + @Override + public Map visualizeWithAction( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + Map vis = visualize(cfg, entry, analysis); + String stringGraph = (String) vis.get("stringGraph"); + out.println(stringGraph); + return vis; + } + + @SuppressWarnings("keyfor:enhancedfor.type.incompatible") + @Override + public String visualizeNodes( + Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { + StringJoiner sjStringNodes = new StringJoiner(lineSeparator); + IdentityHashMap> processOrder = getProcessOrder(cfg); + + // Generate all the Nodes. + for (@KeyFor("processOrder") Block v : blocks) { + sjStringNodes.add(v.getUid() + ":"); + if (verbose) { + sjStringNodes.add(getProcessOrderSimpleString(processOrder.get(v))); + } + sjStringNodes.add(visualizeBlock(v, analysis)); + sjStringNodes.add(""); + } + + return sjStringNodes.toString().trim(); + } + + @Override + protected String visualizeEdge(Object sId, Object eId, String flowRule) { + if (this.verbose) { + return sId + " -> " + eId + " " + flowRule; + } + return sId + " -> " + eId; + } + + @Override + public String visualizeBlock(Block bb, @Nullable Analysis analysis) { + return super.visualizeBlockHelper(bb, analysis, lineSeparator).trim(); + } + + @Override + public String visualizeSpecialBlock(SpecialBlock sbb) { + return super.visualizeSpecialBlockHelper(sbb); + } + + @Override + public String visualizeConditionalBlock(ConditionalBlock cbb) { + return "ConditionalBlock: then: " + + cbb.getThenSuccessor().getUid() + + ", else: " + + cbb.getElseSuccessor().getUid(); + } + + @Override + public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.BEFORE, bb, analysis, lineSeparator); + } + + @Override + public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.AFTER, bb, analysis, lineSeparator); + } + + @Override + protected String format(Object obj) { + return obj.toString(); + } + + @Override + public String visualizeStoreThisVal(V value) { + return storeEntryIndent + "this > " + value; + } + + @Override + public String visualizeStoreLocalVar(LocalVariable localVar, V value) { + return storeEntryIndent + localVar + " > " + value; + } + + @Override + public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { + return storeEntryIndent + fieldAccess + " > " + value; + } + + @Override + public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { + return storeEntryIndent + arrayValue + " > " + value; + } + + @Override + public String visualizeStoreMethodVals(MethodCall methodCall, V value) { + return storeEntryIndent + methodCall + " > " + value; + } + + @Override + public String visualizeStoreClassVals(ClassName className, V value) { + return storeEntryIndent + className + " > " + value; + } + + @Override + public String visualizeStoreKeyVal(String keyName, Object value) { + return storeEntryIndent + keyName + " = " + value; + } + + /** + * {@inheritDoc} + * + *

StringCFGVisualizer does not write into file, so left intentionally blank. + */ + @Override + public void shutdown() {} + + /** + * {@inheritDoc} + * + *

StringCFGVisualizer does not need a specific header, so just return an empty string. + */ + @Override + protected String visualizeGraphHeader() { + return ""; + } + + /** + * {@inheritDoc} + * + *

StringCFGVisualizer does not need a specific footer, so just return an empty string. + */ + @Override + protected String visualizeGraphFooter() { + return ""; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java index 02b5e969ec9..b44f11c9818 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java @@ -1,126 +1,125 @@ package org.checkerframework.dataflow.constantpropagation; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; import org.checkerframework.javacutil.BugInCF; -import java.util.Objects; - public class Constant implements AbstractValue { - /** What kind of abstract value is this? */ - protected final Type type; - - /** The value of this abstract value (or null). */ - protected @Nullable Integer value; - - public enum Type { - CONSTANT, - TOP, - BOTTOM, + /** What kind of abstract value is this? */ + protected final Type type; + + /** The value of this abstract value (or null). */ + protected @Nullable Integer value; + + public enum Type { + CONSTANT, + TOP, + BOTTOM, + } + + /** Create a constant for {@code type}. */ + public Constant(Type type) { + assert type != Type.CONSTANT; + this.type = type; + } + + /** Create a constant for {@code value}. */ + public Constant(Integer value) { + this.type = Type.CONSTANT; + this.value = value; + } + + /** + * Returns whether or not the constant is TOP. + * + * @return whether or not the constant is TOP + */ + public boolean isTop() { + return type == Type.TOP; + } + + /** + * Returns whether or not the constant is BOTTOM. + * + * @return whether or not the constant is BOTTOM + */ + public boolean isBottom() { + return type == Type.BOTTOM; + } + + /** + * Returns whether or not the constant is CONSTANT. + * + * @return whether or not the constant is CONSTANT + */ + @EnsuresNonNullIf(result = true, expression = "value") + public boolean isConstant() { + return type == Type.CONSTANT && value != null; + } + + /** + * Returns the value. + * + * @return the value + */ + public Integer getValue() { + assert isConstant() : "@AssumeAssertion(nullness): inspection"; + return value; + } + + public Constant copy() { + if (isConstant()) { + return new Constant(value); } + return new Constant(type); + } - /** Create a constant for {@code type}. */ - public Constant(Type type) { - assert type != Type.CONSTANT; - this.type = type; + @Override + public Constant leastUpperBound(Constant other) { + if (other.isBottom()) { + return this.copy(); } - - /** Create a constant for {@code value}. */ - public Constant(Integer value) { - this.type = Type.CONSTANT; - this.value = value; + if (this.isBottom()) { + return other.copy(); } - - /** - * Returns whether or not the constant is TOP. - * - * @return whether or not the constant is TOP - */ - public boolean isTop() { - return type == Type.TOP; + if (other.isTop() || this.isTop()) { + return new Constant(Type.TOP); } - - /** - * Returns whether or not the constant is BOTTOM. - * - * @return whether or not the constant is BOTTOM - */ - public boolean isBottom() { - return type == Type.BOTTOM; - } - - /** - * Returns whether or not the constant is CONSTANT. - * - * @return whether or not the constant is CONSTANT - */ - @EnsuresNonNullIf(result = true, expression = "value") - public boolean isConstant() { - return type == Type.CONSTANT && value != null; + if (other.getValue().equals(getValue())) { + return this.copy(); } + return new Constant(Type.TOP); + } - /** - * Returns the value. - * - * @return the value - */ - public Integer getValue() { - assert isConstant() : "@AssumeAssertion(nullness): inspection"; - return value; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof Constant)) { + return false; } - - public Constant copy() { - if (isConstant()) { - return new Constant(value); - } - return new Constant(type); - } - - @Override - public Constant leastUpperBound(Constant other) { - if (other.isBottom()) { - return this.copy(); - } - if (this.isBottom()) { - return other.copy(); - } - if (other.isTop() || this.isTop()) { - return new Constant(Type.TOP); - } - if (other.getValue().equals(getValue())) { - return this.copy(); - } - return new Constant(Type.TOP); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof Constant)) { - return false; - } - Constant other = (Constant) obj; - return type == other.type && Objects.equals(value, other.value); - } - - @Override - public int hashCode() { - return Objects.hash(type, value); - } - - @Override - public String toString() { - switch (type) { - case TOP: - return "T"; - case BOTTOM: - return "-"; - case CONSTANT: - assert isConstant() : "@AssumeAssertion(nullness)"; - return value.toString(); - default: - throw new BugInCF("Unexpected type: " + type); - } + Constant other = (Constant) obj; + return type == other.type && Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(type, value); + } + + @Override + public String toString() { + switch (type) { + case TOP: + return "T"; + case BOTTOM: + return "-"; + case CONSTANT: + assert isConstant() : "@AssumeAssertion(nullness)"; + return value.toString(); + default: + throw new BugInCF("Unexpected type: " + type); } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java index 79189d6c5fc..f4c1efef9c8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java @@ -1,5 +1,7 @@ package org.checkerframework.dataflow.constantpropagation; +import java.util.LinkedHashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode; @@ -10,159 +12,156 @@ import org.plumelib.util.ArrayMap; import org.plumelib.util.CollectionsPlume; -import java.util.LinkedHashMap; -import java.util.Map; - /** A store that records information about constant values. */ public class ConstantPropagationStore implements Store { - /** Information about variables gathered so far. */ - private final Map contents; + /** Information about variables gathered so far. */ + private final Map contents; - /** Creates a new ConstantPropagationStore. */ - public ConstantPropagationStore() { - contents = new LinkedHashMap<>(); - } + /** Creates a new ConstantPropagationStore. */ + public ConstantPropagationStore() { + contents = new LinkedHashMap<>(); + } - protected ConstantPropagationStore(Map contents) { - this.contents = contents; - } + protected ConstantPropagationStore(Map contents) { + this.contents = contents; + } - public Constant getInformation(Node n) { - if (contents.containsKey(n)) { - return contents.get(n); - } - return new Constant(Constant.Type.TOP); + public Constant getInformation(Node n) { + if (contents.containsKey(n)) { + return contents.get(n); } - - public void mergeInformation(Node n, Constant val) { - Constant value; - if (contents.containsKey(n)) { - value = val.leastUpperBound(contents.get(n)); - } else { - value = val; - } - // TODO: remove (only two nodes supported atm) - assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; - contents.put(n, value); + return new Constant(Constant.Type.TOP); + } + + public void mergeInformation(Node n, Constant val) { + Constant value; + if (contents.containsKey(n)) { + value = val.leastUpperBound(contents.get(n)); + } else { + value = val; } - - public void setInformation(Node n, Constant val) { - // TODO: remove (only two nodes supported atm) - assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; - contents.put(n, val); + // TODO: remove (only two nodes supported atm) + assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; + contents.put(n, value); + } + + public void setInformation(Node n, Constant val) { + // TODO: remove (only two nodes supported atm) + assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; + contents.put(n, val); + } + + @Override + public ConstantPropagationStore copy() { + return new ConstantPropagationStore(new LinkedHashMap<>(contents)); + } + + @Override + public ConstantPropagationStore leastUpperBound(ConstantPropagationStore other) { + Map newContents = + ArrayMap.newArrayMapOrLinkedHashMap(contents.size() + other.contents.size()); + + // go through all of the information of the other class + for (Map.Entry e : other.contents.entrySet()) { + Node n = e.getKey(); + Constant otherVal = e.getValue(); + if (contents.containsKey(n)) { + // merge if both contain information about a variable + newContents.put(n, otherVal.leastUpperBound(contents.get(n))); + } else { + // add new information + newContents.put(n, otherVal); + } } - @Override - public ConstantPropagationStore copy() { - return new ConstantPropagationStore(new LinkedHashMap<>(contents)); + for (Map.Entry e : contents.entrySet()) { + Node n = e.getKey(); + Constant thisVal = e.getValue(); + if (!other.contents.containsKey(n)) { + // add new information + newContents.put(n, thisVal); + } } - @Override - public ConstantPropagationStore leastUpperBound(ConstantPropagationStore other) { - Map newContents = - ArrayMap.newArrayMapOrLinkedHashMap(contents.size() + other.contents.size()); - - // go through all of the information of the other class - for (Map.Entry e : other.contents.entrySet()) { - Node n = e.getKey(); - Constant otherVal = e.getValue(); - if (contents.containsKey(n)) { - // merge if both contain information about a variable - newContents.put(n, otherVal.leastUpperBound(contents.get(n))); - } else { - // add new information - newContents.put(n, otherVal); - } - } + return new ConstantPropagationStore(newContents); + } - for (Map.Entry e : contents.entrySet()) { - Node n = e.getKey(); - Constant thisVal = e.getValue(); - if (!other.contents.containsKey(n)) { - // add new information - newContents.put(n, thisVal); - } - } + @Override + public ConstantPropagationStore widenedUpperBound(ConstantPropagationStore previous) { + return leastUpperBound(previous); + } - return new ConstantPropagationStore(newContents); + @Override + public boolean equals(@Nullable Object o) { + if (o == null) { + return false; } - - @Override - public ConstantPropagationStore widenedUpperBound(ConstantPropagationStore previous) { - return leastUpperBound(previous); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == null) { - return false; - } - if (!(o instanceof ConstantPropagationStore)) { - return false; - } - ConstantPropagationStore other = (ConstantPropagationStore) o; - // go through all of the information of the other object - for (Map.Entry e : other.contents.entrySet()) { - Node n = e.getKey(); - Constant otherVal = e.getValue(); - if (otherVal.isBottom()) { - continue; // no information - } - if (contents.containsKey(n)) { - if (!otherVal.equals(contents.get(n))) { - return false; - } - } else { - return false; - } - } - // go through all of the information of the this object - for (Map.Entry e : contents.entrySet()) { - Node n = e.getKey(); - Constant thisVal = e.getValue(); - if (thisVal.isBottom()) { - continue; // no information - } - if (!other.contents.containsKey(n)) { - return false; - } - } - return true; + if (!(o instanceof ConstantPropagationStore)) { + return false; } - - @Override - public int hashCode() { - int s = 0; - for (Map.Entry e : contents.entrySet()) { - if (!e.getValue().isBottom()) { - s += e.hashCode(); - } + ConstantPropagationStore other = (ConstantPropagationStore) o; + // go through all of the information of the other object + for (Map.Entry e : other.contents.entrySet()) { + Node n = e.getKey(); + Constant otherVal = e.getValue(); + if (otherVal.isBottom()) { + continue; // no information + } + if (contents.containsKey(n)) { + if (!otherVal.equals(contents.get(n))) { + return false; } - return s; + } else { + return false; + } } - - @Override - public String toString() { - // Only output local variable information. - // This output is very terse, so a CFG containing it fits well in the manual. - Map contentsLocalVars = - new LinkedHashMap<>(CollectionsPlume.mapCapacity(contents)); - for (Map.Entry e : contents.entrySet()) { - if (e.getKey() instanceof LocalVariableNode) { - contentsLocalVars.put(e.getKey(), e.getValue()); - } - } - return contentsLocalVars.toString(); + // go through all of the information of the this object + for (Map.Entry e : contents.entrySet()) { + Node n = e.getKey(); + Constant thisVal = e.getValue(); + if (thisVal.isBottom()) { + continue; // no information + } + if (!other.contents.containsKey(n)) { + return false; + } } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; + return true; + } + + @Override + public int hashCode() { + int s = 0; + for (Map.Entry e : contents.entrySet()) { + if (!e.getValue().isBottom()) { + s += e.hashCode(); + } } - - @Override - public String visualize(CFGVisualizer viz) { - return viz.visualizeStoreKeyVal("constant propagation", toString()); + return s; + } + + @Override + public String toString() { + // Only output local variable information. + // This output is very terse, so a CFG containing it fits well in the manual. + Map contentsLocalVars = + new LinkedHashMap<>(CollectionsPlume.mapCapacity(contents)); + for (Map.Entry e : contents.entrySet()) { + if (e.getKey() instanceof LocalVariableNode) { + contentsLocalVars.put(e.getKey(), e.getValue()); + } } + return contentsLocalVars.toString(); + } + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; + } + + @Override + public String visualize(CFGVisualizer viz) { + return viz.visualizeStoreKeyVal("constant propagation", toString()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java index 2d75ee65bbb..0a850eda4de 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.dataflow.constantpropagation; +import java.util.List; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.ForwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; @@ -13,74 +14,72 @@ import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.dataflow.cfg.node.Node; -import java.util.List; - public class ConstantPropagationTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements ForwardTransferFunction { + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements ForwardTransferFunction { - @Override - public ConstantPropagationStore initialStore( - UnderlyingAST underlyingAST, List parameters) { - ConstantPropagationStore store = new ConstantPropagationStore(); - return store; - } + @Override + public ConstantPropagationStore initialStore( + UnderlyingAST underlyingAST, List parameters) { + ConstantPropagationStore store = new ConstantPropagationStore(); + return store; + } - @Override - public TransferResult visitLocalVariable( - LocalVariableNode node, TransferInput before) { - ConstantPropagationStore store = before.getRegularStore(); - Constant value = store.getInformation(node); - return new RegularTransferResult<>(value, store); - } + @Override + public TransferResult visitLocalVariable( + LocalVariableNode node, TransferInput before) { + ConstantPropagationStore store = before.getRegularStore(); + Constant value = store.getInformation(node); + return new RegularTransferResult<>(value, store); + } - @Override - public TransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } + @Override + public TransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } - @Override - public TransferResult visitAssignment( - AssignmentNode n, TransferInput pi) { - ConstantPropagationStore p = pi.getRegularStore(); - Node target = n.getTarget(); - Constant info = null; - if (target instanceof LocalVariableNode) { - LocalVariableNode t = (LocalVariableNode) target; - info = p.getInformation(n.getExpression()); - p.setInformation(t, info); - } - return new RegularTransferResult<>(info, p); + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput pi) { + ConstantPropagationStore p = pi.getRegularStore(); + Node target = n.getTarget(); + Constant info = null; + if (target instanceof LocalVariableNode) { + LocalVariableNode t = (LocalVariableNode) target; + info = p.getInformation(n.getExpression()); + p.setInformation(t, info); } + return new RegularTransferResult<>(info, p); + } - @Override - public TransferResult visitIntegerLiteral( - IntegerLiteralNode n, TransferInput pi) { - ConstantPropagationStore p = pi.getRegularStore(); - Constant c = new Constant(n.getValue()); - p.setInformation(n, c); - return new RegularTransferResult<>(c, p); - } + @Override + public TransferResult visitIntegerLiteral( + IntegerLiteralNode n, TransferInput pi) { + ConstantPropagationStore p = pi.getRegularStore(); + Constant c = new Constant(n.getValue()); + p.setInformation(n, c); + return new RegularTransferResult<>(c, p); + } - @Override - public TransferResult visitEqualTo( - EqualToNode n, TransferInput pi) { - ConstantPropagationStore p = pi.getRegularStore(); - ConstantPropagationStore old = p.copy(); - Node left = n.getLeftOperand(); - Node right = n.getRightOperand(); - process(p, left, right); - process(p, right, left); - return new ConditionalTransferResult<>(null, p, old); - } + @Override + public TransferResult visitEqualTo( + EqualToNode n, TransferInput pi) { + ConstantPropagationStore p = pi.getRegularStore(); + ConstantPropagationStore old = p.copy(); + Node left = n.getLeftOperand(); + Node right = n.getRightOperand(); + process(p, left, right); + process(p, right, left); + return new ConditionalTransferResult<>(null, p, old); + } - protected void process(ConstantPropagationStore p, Node a, Node b) { - Constant val = p.getInformation(a); - if (b instanceof LocalVariableNode && val.isConstant()) { - p.setInformation(b, val); - } + protected void process(ConstantPropagationStore p, Node a, Node b) { + Constant val = p.getInformation(a); + if (b instanceof LocalVariableNode && val.isConstant()) { + p.setInformation(b, val); } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java index ab245472636..cff72a52c44 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java @@ -1,124 +1,122 @@ package org.checkerframework.dataflow.expression; +import java.util.Objects; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.javacutil.AnnotationProvider; -import java.util.Objects; - -import javax.lang.model.type.TypeMirror; - /** An array access. */ public class ArrayAccess extends JavaExpression { - /** The array being accessed. */ - protected final JavaExpression array; - - /** The index; an expression of type int. */ - protected final JavaExpression index; - - /** - * Create a new ArrayAccess. - * - * @param type the type of the array access - * @param array the array being accessed - * @param index the index; an expression of type int - */ - public ArrayAccess(TypeMirror type, JavaExpression array, JavaExpression index) { - super(type); - this.array = array; - this.index = index; - } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; - } - if (array.containsOfClass(clazz)) { - return true; - } - return index.containsOfClass(clazz); - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return array.isDeterministic(provider) && index.isDeterministic(provider); + /** The array being accessed. */ + protected final JavaExpression array; + + /** The index; an expression of type int. */ + protected final JavaExpression index; + + /** + * Create a new ArrayAccess. + * + * @param type the type of the array access + * @param array the array being accessed + * @param index the index; an expression of type int + */ + public ArrayAccess(TypeMirror type, JavaExpression array, JavaExpression index) { + super(type); + this.array = array; + this.index = index; + } + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; } - - /** - * Returns the array being accessed. - * - * @return the array being accessed - */ - public JavaExpression getArray() { - return array; + if (array.containsOfClass(clazz)) { + return true; } - - public JavaExpression getIndex() { - return index; - } - - @Override - public boolean isUnassignableByOtherCode() { - return false; + return index.containsOfClass(clazz); + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return array.isDeterministic(provider) && index.isDeterministic(provider); + } + + /** + * Returns the array being accessed. + * + * @return the array being accessed + */ + public JavaExpression getArray() { + return array; + } + + public JavaExpression getIndex() { + return index; + } + + @Override + public boolean isUnassignableByOtherCode() { + return false; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof ArrayAccess)) { + return false; } - - @Override - public boolean isUnmodifiableByOtherCode() { - return false; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof ArrayAccess)) { - return false; - } - ArrayAccess other = (ArrayAccess) je; - return array.syntacticEquals(other.array) && index.syntacticEquals(other.index); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) - || array.containsSyntacticEqualJavaExpression(other) - || index.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - if (array.containsModifiableAliasOf(store, other)) { - return true; - } - return index.containsModifiableAliasOf(store, other); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayAccess)) { - return false; - } - ArrayAccess other = (ArrayAccess) obj; - return array.equals(other.array) && index.equals(other.index); - } - - @Override - public int hashCode() { - return Objects.hash(array, index); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append(array.toString()); - result.append("["); - result.append(index.toString()); - result.append("]"); - return result.toString(); + ArrayAccess other = (ArrayAccess) je; + return array.syntacticEquals(other.array) && index.syntacticEquals(other.index); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) + || array.containsSyntacticEqualJavaExpression(other) + || index.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + if (array.containsModifiableAliasOf(store, other)) { + return true; } + return index.containsModifiableAliasOf(store, other); + } - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitArrayAccess(this, p); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayAccess)) { + return false; } + ArrayAccess other = (ArrayAccess) obj; + return array.equals(other.array) && index.equals(other.index); + } + + @Override + public int hashCode() { + return Objects.hash(array, index); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(array.toString()); + result.append("["); + result.append(index.toString()); + result.append("]"); + return result.toString(); + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitArrayAccess(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java index bda0d3a406f..6d359535eaa 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java @@ -1,164 +1,161 @@ package org.checkerframework.dataflow.expression; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.StringsPlume; - import java.util.List; import java.util.Objects; - import javax.lang.model.type.ArrayType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.StringsPlume; /** JavaExpression for array creations. {@code new String[]()}. */ public class ArrayCreation extends JavaExpression { - /** - * List of dimensions expressions. A {code null} element means that there is no dimension - * expression for the given array level. - */ - protected final List<@Nullable JavaExpression> dimensions; - - /** List of initializers. */ - protected final List initializers; - - /** - * Creates an ArrayCreation object. - * - * @param type array type - * @param dimensions list of dimension expressions; a {@code null} element means that there is - * no dimension expression for the given array level - * @param initializers list of initializer expressions - */ - public ArrayCreation( - TypeMirror type, - List<@Nullable JavaExpression> dimensions, - List initializers) { - super(type); - assert type.getKind() == TypeKind.ARRAY; - this.dimensions = dimensions; - this.initializers = initializers; - } - - /** - * Returns a list representing the dimensions of this array creation. A {code null} element - * means that there is no dimension expression for the given array level. - * - * @return a list representing the dimensions of this array creation - */ - public List<@Nullable JavaExpression> getDimensions() { - return dimensions; + /** + * List of dimensions expressions. A {code null} element means that there is no dimension + * expression for the given array level. + */ + protected final List<@Nullable JavaExpression> dimensions; + + /** List of initializers. */ + protected final List initializers; + + /** + * Creates an ArrayCreation object. + * + * @param type array type + * @param dimensions list of dimension expressions; a {@code null} element means that there is no + * dimension expression for the given array level + * @param initializers list of initializer expressions + */ + public ArrayCreation( + TypeMirror type, + List<@Nullable JavaExpression> dimensions, + List initializers) { + super(type); + assert type.getKind() == TypeKind.ARRAY; + this.dimensions = dimensions; + this.initializers = initializers; + } + + /** + * Returns a list representing the dimensions of this array creation. A {code null} element means + * that there is no dimension expression for the given array level. + * + * @return a list representing the dimensions of this array creation + */ + public List<@Nullable JavaExpression> getDimensions() { + return dimensions; + } + + public List getInitializers() { + return initializers; + } + + @Override + public boolean containsOfClass(Class clazz) { + for (JavaExpression n : dimensions) { + if (n != null && n.getClass() == clazz) { + return true; + } } - - public List getInitializers() { - return initializers; + for (JavaExpression n : initializers) { + if (n.getClass() == clazz) { + return true; + } } - - @Override - public boolean containsOfClass(Class clazz) { - for (JavaExpression n : dimensions) { - if (n != null && n.getClass() == clazz) { - return true; - } - } - for (JavaExpression n : initializers) { - if (n.getClass() == clazz) { - return true; - } - } - return false; + return false; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return listIsDeterministic(dimensions, provider) && listIsDeterministic(initializers, provider); + } + + @Override + public boolean isUnassignableByOtherCode() { + return false; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public int hashCode() { + return Objects.hash(dimensions, initializers, getType().toString()); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayCreation)) { + return false; } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return listIsDeterministic(dimensions, provider) - && listIsDeterministic(initializers, provider); + ArrayCreation other = (ArrayCreation) obj; + return this.dimensions.equals(other.getDimensions()) + && this.initializers.equals(other.getInitializers()) + // It might be better to use Types.isSameType(getType(), other.getType()), but I + // don't have a Types object. + && getType().toString().equals(other.getType().toString()); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof ArrayCreation)) { + return false; } - - @Override - public boolean isUnassignableByOtherCode() { - return false; + ArrayCreation other = (ArrayCreation) je; + return JavaExpression.syntacticEqualsList(this.dimensions, other.dimensions) + && JavaExpression.syntacticEqualsList(this.initializers, other.initializers) + && getType().toString().equals(other.getType().toString()); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) + || JavaExpression.listContainsSyntacticEqualJavaExpression(dimensions, other) + || JavaExpression.listContainsSyntacticEqualJavaExpression(initializers, other); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (dimensions.isEmpty()) { + sb.append("new " + type); + } else { + sb.append("new " + TypesUtils.getInnermostComponentType((ArrayType) type)); + for (JavaExpression dim : dimensions) { + sb.append("["); + sb.append(dim == null ? "" : dim); + sb.append("]"); + } } - - @Override - public boolean isUnmodifiableByOtherCode() { - return false; - } - - @Override - public int hashCode() { - return Objects.hash(dimensions, initializers, getType().toString()); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayCreation)) { - return false; - } - ArrayCreation other = (ArrayCreation) obj; - return this.dimensions.equals(other.getDimensions()) - && this.initializers.equals(other.getInitializers()) - // It might be better to use Types.isSameType(getType(), other.getType()), but I - // don't have a Types object. - && getType().toString().equals(other.getType().toString()); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof ArrayCreation)) { - return false; - } - ArrayCreation other = (ArrayCreation) je; - return JavaExpression.syntacticEqualsList(this.dimensions, other.dimensions) - && JavaExpression.syntacticEqualsList(this.initializers, other.initializers) - && getType().toString().equals(other.getType().toString()); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) - || JavaExpression.listContainsSyntacticEqualJavaExpression(dimensions, other) - || JavaExpression.listContainsSyntacticEqualJavaExpression(initializers, other); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - if (dimensions.isEmpty()) { - sb.append("new " + type); - } else { - sb.append("new " + TypesUtils.getInnermostComponentType((ArrayType) type)); - for (JavaExpression dim : dimensions) { - sb.append("["); - sb.append(dim == null ? "" : dim); - sb.append("]"); - } - } - if (!initializers.isEmpty()) { - sb.append(" {"); - sb.append(StringsPlume.join(", ", initializers)); - sb.append("}"); - } - return sb.toString(); - } - - @Override - public String toStringDebug() { - return "\"" - + super.toStringDebug() - + "\"" - + " type=" - + type - + " dimensions=" - + dimensions - + " initializers=" - + initializers; - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitArrayCreation(this, p); + if (!initializers.isEmpty()) { + sb.append(" {"); + sb.append(StringsPlume.join(", ", initializers)); + sb.append("}"); } + return sb.toString(); + } + + @Override + public String toStringDebug() { + return "\"" + + super.toStringDebug() + + "\"" + + " type=" + + type + + " dimensions=" + + dimensions + + " initializers=" + + initializers; + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitArrayCreation(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java index 50b9018ba8d..1b2511d48c7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java @@ -1,235 +1,228 @@ package org.checkerframework.dataflow.expression; import com.sun.source.tree.Tree; - +import java.util.Objects; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.BinaryOperationNode; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; -import java.util.Objects; - -import javax.lang.model.type.TypeMirror; - /** JavaExpression for binary operations. */ public class BinaryOperation extends JavaExpression { - /** The binary operation kind. */ - protected final Tree.Kind operationKind; - - /** The left operand. */ - protected final JavaExpression left; - - /** The right operand. */ - protected final JavaExpression right; - - /** - * Create a binary operation. - * - * @param type the result type - * @param operationKind the operator - * @param left the left operand - * @param right the right operand - */ - public BinaryOperation( - TypeMirror type, Tree.Kind operationKind, JavaExpression left, JavaExpression right) { - super(type); - this.operationKind = operationKind; - this.left = left; - this.right = right; - } - - /** - * Create a binary operation. - * - * @param node the binary operation node - * @param left the left operand - * @param right the right operand - */ - public BinaryOperation(BinaryOperationNode node, JavaExpression left, JavaExpression right) { - this(node.getType(), node.getTree().getKind(), left, right); - } - - /** - * Returns the operator of this binary operation. - * - * @return the binary operation kind - */ - public Tree.Kind getOperationKind() { - return operationKind; - } - - /** - * Returns the left operand of this binary operation. - * - * @return the left operand - */ - public JavaExpression getLeft() { - return left; - } - - /** - * Returns the right operand of this binary operation. - * - * @return the right operand - */ - public JavaExpression getRight() { - return right; - } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; - } - return left.containsOfClass(clazz) || right.containsOfClass(clazz); - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return left.isDeterministic(provider) && right.isDeterministic(provider); - } - - @Override - public boolean isUnassignableByOtherCode() { - return left.isUnassignableByOtherCode() && right.isUnassignableByOtherCode(); - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return left.isUnmodifiableByOtherCode() && right.isUnmodifiableByOtherCode(); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof BinaryOperation)) { - return false; - } - BinaryOperation other = (BinaryOperation) je; - return operationKind == other.getOperationKind() - && left.syntacticEquals(other.left) - && right.syntacticEquals(other.right); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other) - || left.containsSyntacticEqualJavaExpression(other) - || right.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return left.containsModifiableAliasOf(store, other) - || right.containsModifiableAliasOf(store, other); - } - - @Override - public int hashCode() { - return Objects.hash(operationKind, left, right); - } - - @Override - public boolean equals(@Nullable Object other) { - if (!(other instanceof BinaryOperation)) { - return false; - } - BinaryOperation biOp = (BinaryOperation) other; - if (!(operationKind == biOp.getOperationKind())) { - return false; - } - if (isCommutative()) { - return (left.equals(biOp.left) && right.equals(biOp.right)) - || (left.equals(biOp.right) && right.equals(biOp.left)); - } - return left.equals(biOp.left) && right.equals(biOp.right); - } - - /** - * Returns true if the binary operation is commutative, e.g., x + y == y + x. - * - * @return true if the binary operation is commutative - */ - private boolean isCommutative() { - switch (operationKind) { - case PLUS: - case MULTIPLY: - case AND: - case OR: - case XOR: - case EQUAL_TO: - case NOT_EQUAL_TO: - case CONDITIONAL_AND: - case CONDITIONAL_OR: - return true; - default: - return false; - } - } - - @Override - public String toString() { - return left.toString() - + " " - + operationKindToString(operationKind) - + " " - + right.toString(); - } - - /** - * Return the Java source code representation of the given operation. - * - * @param operationKind an unary operation kind - * @return the Java source code representation of the given operation - */ - private String operationKindToString(Tree.Kind operationKind) { - switch (operationKind) { - case CONDITIONAL_AND: - return "&&"; - case AND: - return "&"; - case OR: - return "|"; - case DIVIDE: - return "/"; - case EQUAL_TO: - return "=="; - case GREATER_THAN: - return ">"; - case GREATER_THAN_EQUAL: - return ">="; - case LEFT_SHIFT: - return "<<"; - case LESS_THAN: - return "<"; - case LESS_THAN_EQUAL: - return "<="; - case MINUS: - return "-"; - case MULTIPLY: - return "*"; - case NOT_EQUAL_TO: - return "!="; - case CONDITIONAL_OR: - return "||"; - case PLUS: - return "+"; - case REMAINDER: - return "%"; - case RIGHT_SHIFT: - return ">>"; - case UNSIGNED_RIGHT_SHIFT: - return ">>>"; - case XOR: - return "^"; - default: - throw new BugInCF("unhandled " + operationKind); - } - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitBinaryOperation(this, p); - } + /** The binary operation kind. */ + protected final Tree.Kind operationKind; + + /** The left operand. */ + protected final JavaExpression left; + + /** The right operand. */ + protected final JavaExpression right; + + /** + * Create a binary operation. + * + * @param type the result type + * @param operationKind the operator + * @param left the left operand + * @param right the right operand + */ + public BinaryOperation( + TypeMirror type, Tree.Kind operationKind, JavaExpression left, JavaExpression right) { + super(type); + this.operationKind = operationKind; + this.left = left; + this.right = right; + } + + /** + * Create a binary operation. + * + * @param node the binary operation node + * @param left the left operand + * @param right the right operand + */ + public BinaryOperation(BinaryOperationNode node, JavaExpression left, JavaExpression right) { + this(node.getType(), node.getTree().getKind(), left, right); + } + + /** + * Returns the operator of this binary operation. + * + * @return the binary operation kind + */ + public Tree.Kind getOperationKind() { + return operationKind; + } + + /** + * Returns the left operand of this binary operation. + * + * @return the left operand + */ + public JavaExpression getLeft() { + return left; + } + + /** + * Returns the right operand of this binary operation. + * + * @return the right operand + */ + public JavaExpression getRight() { + return right; + } + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; + } + return left.containsOfClass(clazz) || right.containsOfClass(clazz); + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return left.isDeterministic(provider) && right.isDeterministic(provider); + } + + @Override + public boolean isUnassignableByOtherCode() { + return left.isUnassignableByOtherCode() && right.isUnassignableByOtherCode(); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return left.isUnmodifiableByOtherCode() && right.isUnmodifiableByOtherCode(); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof BinaryOperation)) { + return false; + } + BinaryOperation other = (BinaryOperation) je; + return operationKind == other.getOperationKind() + && left.syntacticEquals(other.left) + && right.syntacticEquals(other.right); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other) + || left.containsSyntacticEqualJavaExpression(other) + || right.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return left.containsModifiableAliasOf(store, other) + || right.containsModifiableAliasOf(store, other); + } + + @Override + public int hashCode() { + return Objects.hash(operationKind, left, right); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof BinaryOperation)) { + return false; + } + BinaryOperation biOp = (BinaryOperation) other; + if (!(operationKind == biOp.getOperationKind())) { + return false; + } + if (isCommutative()) { + return (left.equals(biOp.left) && right.equals(biOp.right)) + || (left.equals(biOp.right) && right.equals(biOp.left)); + } + return left.equals(biOp.left) && right.equals(biOp.right); + } + + /** + * Returns true if the binary operation is commutative, e.g., x + y == y + x. + * + * @return true if the binary operation is commutative + */ + private boolean isCommutative() { + switch (operationKind) { + case PLUS: + case MULTIPLY: + case AND: + case OR: + case XOR: + case EQUAL_TO: + case NOT_EQUAL_TO: + case CONDITIONAL_AND: + case CONDITIONAL_OR: + return true; + default: + return false; + } + } + + @Override + public String toString() { + return left.toString() + " " + operationKindToString(operationKind) + " " + right.toString(); + } + + /** + * Return the Java source code representation of the given operation. + * + * @param operationKind an unary operation kind + * @return the Java source code representation of the given operation + */ + private String operationKindToString(Tree.Kind operationKind) { + switch (operationKind) { + case CONDITIONAL_AND: + return "&&"; + case AND: + return "&"; + case OR: + return "|"; + case DIVIDE: + return "/"; + case EQUAL_TO: + return "=="; + case GREATER_THAN: + return ">"; + case GREATER_THAN_EQUAL: + return ">="; + case LEFT_SHIFT: + return "<<"; + case LESS_THAN: + return "<"; + case LESS_THAN_EQUAL: + return "<="; + case MINUS: + return "-"; + case MULTIPLY: + return "*"; + case NOT_EQUAL_TO: + return "!="; + case CONDITIONAL_OR: + return "||"; + case PLUS: + return "+"; + case REMAINDER: + return "%"; + case RIGHT_SHIFT: + return ">>"; + case UNSIGNED_RIGHT_SHIFT: + return ">>>"; + case XOR: + return "^"; + default: + throw new BugInCF("unhandled " + operationKind); + } + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitBinaryOperation(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java index 708bdaa6ee4..9e15dbdb84b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java @@ -1,98 +1,96 @@ package org.checkerframework.dataflow.expression; +import java.util.Objects; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.javacutil.AnnotationProvider; -import java.util.Objects; - -import javax.lang.model.type.TypeMirror; - /** * A ClassName represents either a class literal or the occurrence of a class as part of a static * field access or static method invocation. */ public class ClassName extends JavaExpression { - /** The string representation of the raw type of this. */ - private final String typeString; - - /** - * Creates a new ClassName object for the given type. - * - * @param type the type for the new ClassName. If it will represent a class literal, the type is - * declared primitive, void, or array of one of them. If it represents part of a static - * field access or static method invocation, the type is declared, type variable, or array - * (including array of primitive). - */ - public ClassName(TypeMirror type) { - super(type); - String typeString = type.toString(); - if (typeString.endsWith(">")) { - typeString = typeString.substring(0, typeString.indexOf("<")); - } - this.typeString = typeString; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ClassName)) { - return false; - } - ClassName other = (ClassName) obj; - return typeString.equals(other.typeString); - } - - @Override - public int hashCode() { - return Objects.hash(typeString); - } - - @Override - public String toString() { - return typeString + ".class"; + /** The string representation of the raw type of this. */ + private final String typeString; + + /** + * Creates a new ClassName object for the given type. + * + * @param type the type for the new ClassName. If it will represent a class literal, the type is + * declared primitive, void, or array of one of them. If it represents part of a static field + * access or static method invocation, the type is declared, type variable, or array + * (including array of primitive). + */ + public ClassName(TypeMirror type) { + super(type); + String typeString = type.toString(); + if (typeString.endsWith(">")) { + typeString = typeString.substring(0, typeString.indexOf("<")); } + this.typeString = typeString; + } - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ClassName)) { + return false; } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return true; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof ClassName)) { - return false; - } - ClassName other = (ClassName) je; - return typeString.equals(other.typeString); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return false; // not modifiable - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitClassName(this, p); + ClassName other = (ClassName) obj; + return typeString.equals(other.typeString); + } + + @Override + public int hashCode() { + return Objects.hash(typeString); + } + + @Override + public String toString() { + return typeString + ".class"; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof ClassName)) { + return false; } + ClassName other = (ClassName) je; + return typeString.equals(other.typeString); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return false; // not modifiable + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitClassName(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java index 9af3750d15f..e1bb0559fbe 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java @@ -1,7 +1,9 @@ package org.checkerframework.dataflow.expression; import com.sun.tools.javac.code.Symbol; - +import java.util.Objects; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.FieldAccessNode; @@ -10,169 +12,164 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.Objects; - -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - /** * A FieldAccess represents a field access. It does not represent a class literal such as {@code * SomeClass.class} or {@code int[].class}. */ public class FieldAccess extends JavaExpression { - /** The receiver of the field access. */ - protected final JavaExpression receiver; - - /** The field being accessed. */ - protected final VariableElement field; - - /** - * Returns the receiver. - * - * @return the receiver - */ - public JavaExpression getReceiver() { - return receiver; - } - - /** - * Returns the field. - * - * @return the field - */ - public VariableElement getField() { - return field; - } - - /** - * Create a {@code FieldAccess}. - * - * @param receiver receiver of the field access - * @param node the FieldAccessNode - */ - public FieldAccess(JavaExpression receiver, FieldAccessNode node) { - this(receiver, node.getType(), node.getElement()); - } - - /** - * Create a {@code FieldAccess}. - * - * @param receiver receiver of the field access - * @param fieldElement element of the field - */ - public FieldAccess(JavaExpression receiver, VariableElement fieldElement) { - this(receiver, fieldElement.asType(), fieldElement); - } - - /** - * Create a {@code FieldAccess}. - * - * @param receiver receiver of the field access - * @param type type of the field - * @param fieldElement element of the field - */ - public FieldAccess(JavaExpression receiver, TypeMirror type, VariableElement fieldElement) { - super(type); - this.receiver = receiver; - this.field = fieldElement; - String fieldName = fieldElement.toString(); - if (fieldName.equals("class") || fieldName.equals("this")) { - BugInCF e = - new BugInCF( - String.format( - "bad field name \"%s\" in new FieldAccess(%s, %s, %s)%n", - fieldName, receiver, type, fieldElement)); - e.printStackTrace(System.out); - e.printStackTrace(System.err); - throw e; - } - } - - public boolean isFinal() { - return ElementUtils.isFinal(field); - } - - public boolean isStatic() { - return ElementUtils.isStatic(field); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FieldAccess)) { - return false; - } - FieldAccess fa = (FieldAccess) obj; - return fa.getField().equals(getField()) && fa.getReceiver().equals(getReceiver()); - } - - @Override - public int hashCode() { - return Objects.hash(getField(), getReceiver()); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof FieldAccess)) { - return false; - } - FieldAccess other = (FieldAccess) je; - return this.receiver.syntacticEquals(other.receiver) && this.field.equals(other.field); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) || receiver.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return super.containsModifiableAliasOf(store, other) - || receiver.containsModifiableAliasOf(store, other); - } - - @Override - public String toString() { - if (receiver instanceof ClassName) { - return receiver.getType() + "." + field; - } else { - return receiver + "." + field; - } - } - - @Override - public String toStringDebug() { - return String.format( - "FieldAccess(type=%s, receiver=%s, field=%s [%s] [%s] owner=%s)", - type, - receiver.toStringDebug(), - field, - field.getClass().getSimpleName(), - System.identityHashCode(field), - ((Symbol) field).owner); - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz || receiver.containsOfClass(clazz); - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return receiver.isDeterministic(provider); - } - - @Override - public boolean isUnassignableByOtherCode() { - return isFinal() && getReceiver().isUnassignableByOtherCode(); - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return isUnassignableByOtherCode() && TypesUtils.isImmutableTypeInJdk(getReceiver().type); - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitFieldAccess(this, p); - } + /** The receiver of the field access. */ + protected final JavaExpression receiver; + + /** The field being accessed. */ + protected final VariableElement field; + + /** + * Returns the receiver. + * + * @return the receiver + */ + public JavaExpression getReceiver() { + return receiver; + } + + /** + * Returns the field. + * + * @return the field + */ + public VariableElement getField() { + return field; + } + + /** + * Create a {@code FieldAccess}. + * + * @param receiver receiver of the field access + * @param node the FieldAccessNode + */ + public FieldAccess(JavaExpression receiver, FieldAccessNode node) { + this(receiver, node.getType(), node.getElement()); + } + + /** + * Create a {@code FieldAccess}. + * + * @param receiver receiver of the field access + * @param fieldElement element of the field + */ + public FieldAccess(JavaExpression receiver, VariableElement fieldElement) { + this(receiver, fieldElement.asType(), fieldElement); + } + + /** + * Create a {@code FieldAccess}. + * + * @param receiver receiver of the field access + * @param type type of the field + * @param fieldElement element of the field + */ + public FieldAccess(JavaExpression receiver, TypeMirror type, VariableElement fieldElement) { + super(type); + this.receiver = receiver; + this.field = fieldElement; + String fieldName = fieldElement.toString(); + if (fieldName.equals("class") || fieldName.equals("this")) { + BugInCF e = + new BugInCF( + String.format( + "bad field name \"%s\" in new FieldAccess(%s, %s, %s)%n", + fieldName, receiver, type, fieldElement)); + e.printStackTrace(System.out); + e.printStackTrace(System.err); + throw e; + } + } + + public boolean isFinal() { + return ElementUtils.isFinal(field); + } + + public boolean isStatic() { + return ElementUtils.isStatic(field); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FieldAccess)) { + return false; + } + FieldAccess fa = (FieldAccess) obj; + return fa.getField().equals(getField()) && fa.getReceiver().equals(getReceiver()); + } + + @Override + public int hashCode() { + return Objects.hash(getField(), getReceiver()); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof FieldAccess)) { + return false; + } + FieldAccess other = (FieldAccess) je; + return this.receiver.syntacticEquals(other.receiver) && this.field.equals(other.field); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) || receiver.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return super.containsModifiableAliasOf(store, other) + || receiver.containsModifiableAliasOf(store, other); + } + + @Override + public String toString() { + if (receiver instanceof ClassName) { + return receiver.getType() + "." + field; + } else { + return receiver + "." + field; + } + } + + @Override + public String toStringDebug() { + return String.format( + "FieldAccess(type=%s, receiver=%s, field=%s [%s] [%s] owner=%s)", + type, + receiver.toStringDebug(), + field, + field.getClass().getSimpleName(), + System.identityHashCode(field), + ((Symbol) field).owner); + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz || receiver.containsOfClass(clazz); + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return receiver.isDeterministic(provider); + } + + @Override + public boolean isUnassignableByOtherCode() { + return isFinal() && getReceiver().isUnassignableByOtherCode(); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return isUnassignableByOtherCode() && TypesUtils.isImmutableTypeInJdk(getReceiver().type); + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitFieldAccess(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java index 1b5694925b3..6a058504bca 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java @@ -1,16 +1,13 @@ package org.checkerframework.dataflow.expression; import com.sun.tools.javac.code.Symbol.VarSymbol; - +import java.util.Objects; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypeAnnotationUtils; -import java.util.Objects; - -import javax.lang.model.element.VariableElement; - /** * A formal parameter, represented by its 1-based index. * @@ -18,113 +15,113 @@ */ public class FormalParameter extends JavaExpression { - /** The 1-based index. */ - protected final int index; - - /** The element for this formal parameter. */ - protected final VariableElement element; - - /** - * Creates a FormalParameter. - * - * @param index the 1-based index - * @param element the element for the formal parameter - */ - public FormalParameter(int index, VariableElement element) { - super(ElementUtils.getType(element)); - this.index = index; - this.element = element; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FormalParameter)) { - return false; - } - - FormalParameter other = (FormalParameter) obj; - return this.index == other.index && LocalVariable.sameElement(this.element, other.element); - } - - /** - * Returns the 1-based index of this formal parameter. - * - * @return the 1-based index of this formal parameter - */ - public int getIndex() { - return index; - } - - /** - * Returns the element for this variable. - * - * @return the element for this variable - */ - public VariableElement getElement() { - return element; - } - - @Override - public int hashCode() { - VarSymbol vs = (VarSymbol) element; - return Objects.hash( - index, - vs.name.toString(), - TypeAnnotationUtils.unannotatedType(vs.type).toString(), - vs.owner.toString()); - } - - @Override - public String toString() { - return "#" + index; - } - - @Override - public String toStringDebug() { - return super.toStringDebug() - + " [element=" - + element - + ", owner=" - + ((VarSymbol) element).owner - + "]"; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof FormalParameter)) { - return false; - } - FormalParameter other = (FormalParameter) je; - return index == other.index; - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other); - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return true; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; + /** The 1-based index. */ + protected final int index; + + /** The element for this formal parameter. */ + protected final VariableElement element; + + /** + * Creates a FormalParameter. + * + * @param index the 1-based index + * @param element the element for the formal parameter + */ + public FormalParameter(int index, VariableElement element) { + super(ElementUtils.getType(element)); + this.index = index; + this.element = element; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FormalParameter)) { + return false; } - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitFormalParameter(this, p); + FormalParameter other = (FormalParameter) obj; + return this.index == other.index && LocalVariable.sameElement(this.element, other.element); + } + + /** + * Returns the 1-based index of this formal parameter. + * + * @return the 1-based index of this formal parameter + */ + public int getIndex() { + return index; + } + + /** + * Returns the element for this variable. + * + * @return the element for this variable + */ + public VariableElement getElement() { + return element; + } + + @Override + public int hashCode() { + VarSymbol vs = (VarSymbol) element; + return Objects.hash( + index, + vs.name.toString(), + TypeAnnotationUtils.unannotatedType(vs.type).toString(), + vs.owner.toString()); + } + + @Override + public String toString() { + return "#" + index; + } + + @Override + public String toStringDebug() { + return super.toStringDebug() + + " [element=" + + element + + ", owner=" + + ((VarSymbol) element).owner + + "]"; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof FormalParameter)) { + return false; } + FormalParameter other = (FormalParameter) je; + return index == other.index; + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other); + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitFormalParameter(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java index d55fd6eefb2..8b4be624c93 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java @@ -14,7 +14,16 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -42,18 +51,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - // The Lock Checker also supports "" as a JavaExpression, but that is implemented in the Lock // Checker. // There are no special subclasses (AST nodes) for "". @@ -74,748 +71,736 @@ * Java expressions supported by the Checker Framework */ public abstract class JavaExpression { - /** The type of this expression. */ - protected final TypeMirror type; - - /** - * Create a JavaExpression. - * - * @param type the type of the expression - */ - protected JavaExpression(TypeMirror type) { - assert type != null; - this.type = type; - } - - public TypeMirror getType() { - return type; + /** The type of this expression. */ + protected final TypeMirror type; + + /** + * Create a JavaExpression. + * + * @param type the type of the expression + */ + protected JavaExpression(TypeMirror type) { + assert type != null; + this.type = type; + } + + public TypeMirror getType() { + return type; + } + + public abstract boolean containsOfClass(Class clazz); + + public boolean containsUnknown() { + return containsOfClass(Unknown.class); + } + + /** + * Returns true if the expression is deterministic. + * + * @param provider an annotation provider (a type factory) + * @return true if this expression is deterministic + */ + public abstract boolean isDeterministic(AnnotationProvider provider); + + /** + * Returns true if all the expressions in the list are deterministic. + * + * @param list the list whose elements to test + * @param provider an annotation provider (a type factory) + * @return true if all the expressions in the list are deterministic + */ + public static boolean listIsDeterministic( + List list, AnnotationProvider provider) { + return list.stream().allMatch(je -> je == null || je.isDeterministic(provider)); + } + + /** + * Returns true if and only if the value this expression stands for cannot be changed (with + * respect to ==) by a method call. This is the case for local variables, the self reference, + * final field accesses whose receiver is {@link #isUnassignableByOtherCode}, and operations whose + * operands are all {@link #isUnmodifiableByOtherCode}. + * + * @see #isUnmodifiableByOtherCode + */ + public abstract boolean isUnassignableByOtherCode(); + + /** + * Returns true if and only if the value this expression stands for cannot be changed by a method + * call, including changes to any of its fields. + * + *

Approximately, this returns true if the expression is {@link #isUnassignableByOtherCode} and + * its type is immutable. + * + * @see #isUnassignableByOtherCode + */ + public abstract boolean isUnmodifiableByOtherCode(); + + /** + * Returns true if and only if the two Java expressions are syntactically identical. + * + *

This exists for use by {@link #containsSyntacticEqualJavaExpression}. + * + * @param je the other Java expression to compare to this one + * @return true if and only if the two Java expressions are syntactically identical + */ + @EqualsMethod + public abstract boolean syntacticEquals(JavaExpression je); + + /** + * Returns true if the corresponding list elements satisfy {@link #syntacticEquals}. + * + * @param lst1 the first list to compare + * @param lst2 the second list to compare + * @return true if the corresponding list elements satisfy {@link #syntacticEquals} + */ + public static boolean syntacticEqualsList( + List lst1, + List lst2) { + if (lst1.size() != lst2.size()) { + return false; } - - public abstract boolean containsOfClass(Class clazz); - - public boolean containsUnknown() { - return containsOfClass(Unknown.class); - } - - /** - * Returns true if the expression is deterministic. - * - * @param provider an annotation provider (a type factory) - * @return true if this expression is deterministic - */ - public abstract boolean isDeterministic(AnnotationProvider provider); - - /** - * Returns true if all the expressions in the list are deterministic. - * - * @param list the list whose elements to test - * @param provider an annotation provider (a type factory) - * @return true if all the expressions in the list are deterministic - */ - public static boolean listIsDeterministic( - List list, AnnotationProvider provider) { - return list.stream().allMatch(je -> je == null || je.isDeterministic(provider)); - } - - /** - * Returns true if and only if the value this expression stands for cannot be changed (with - * respect to ==) by a method call. This is the case for local variables, the self reference, - * final field accesses whose receiver is {@link #isUnassignableByOtherCode}, and operations - * whose operands are all {@link #isUnmodifiableByOtherCode}. - * - * @see #isUnmodifiableByOtherCode - */ - public abstract boolean isUnassignableByOtherCode(); - - /** - * Returns true if and only if the value this expression stands for cannot be changed by a - * method call, including changes to any of its fields. - * - *

Approximately, this returns true if the expression is {@link #isUnassignableByOtherCode} - * and its type is immutable. - * - * @see #isUnassignableByOtherCode - */ - public abstract boolean isUnmodifiableByOtherCode(); - - /** - * Returns true if and only if the two Java expressions are syntactically identical. - * - *

This exists for use by {@link #containsSyntacticEqualJavaExpression}. - * - * @param je the other Java expression to compare to this one - * @return true if and only if the two Java expressions are syntactically identical - */ - @EqualsMethod - public abstract boolean syntacticEquals(JavaExpression je); - - /** - * Returns true if the corresponding list elements satisfy {@link #syntacticEquals}. - * - * @param lst1 the first list to compare - * @param lst2 the second list to compare - * @return true if the corresponding list elements satisfy {@link #syntacticEquals} - */ - public static boolean syntacticEqualsList( - List lst1, - List lst2) { - if (lst1.size() != lst2.size()) { - return false; - } - for (int i = 0; i < lst1.size(); i++) { - JavaExpression dim1 = lst1.get(i); - JavaExpression dim2 = lst2.get(i); - if (dim1 == null && dim2 == null) { - // Continue to next index. - } else if (dim1 == null || dim2 == null) { - return false; - } else { - if (!dim1.syntacticEquals(dim2)) { - return false; - } - } + for (int i = 0; i < lst1.size(); i++) { + JavaExpression dim1 = lst1.get(i); + JavaExpression dim2 = lst2.get(i); + if (dim1 == null && dim2 == null) { + // Continue to next index. + } else if (dim1 == null || dim2 == null) { + return false; + } else { + if (!dim1.syntacticEquals(dim2)) { + return false; } - return true; + } } - - /** - * Returns true if and only if this contains a JavaExpression that is syntactically equal to - * {@code other}. - * - * @param other the JavaExpression to search for - * @return true if and only if this contains a JavaExpression that is syntactically equal to - * {@code other} - */ - public abstract boolean containsSyntacticEqualJavaExpression(JavaExpression other); - - /** - * Returns true if the given list contains a JavaExpression that is syntactically equal to - * {@code other}. - * - * @param list the list in which to search for a match - * @param other the JavaExpression to search for - * @return true if and only if the list contains a JavaExpression that is syntactically equal to - * {@code other} - */ - public static boolean listContainsSyntacticEqualJavaExpression( - List list, JavaExpression other) { - return list.stream() - .anyMatch(je -> je != null && je.containsSyntacticEqualJavaExpression(other)); + return true; + } + + /** + * Returns true if and only if this contains a JavaExpression that is syntactically equal to + * {@code other}. + * + * @param other the JavaExpression to search for + * @return true if and only if this contains a JavaExpression that is syntactically equal to + * {@code other} + */ + public abstract boolean containsSyntacticEqualJavaExpression(JavaExpression other); + + /** + * Returns true if the given list contains a JavaExpression that is syntactically equal to {@code + * other}. + * + * @param list the list in which to search for a match + * @param other the JavaExpression to search for + * @return true if and only if the list contains a JavaExpression that is syntactically equal to + * {@code other} + */ + public static boolean listContainsSyntacticEqualJavaExpression( + List list, JavaExpression other) { + return list.stream() + .anyMatch(je -> je != null && je.containsSyntacticEqualJavaExpression(other)); + } + + /** + * Returns true if and only if {@code other} appears anywhere in this or an expression appears in + * this such that {@code other} might alias this expression, and that expression is modifiable. + * + *

This is always true, except for cases where the Java type information prevents aliasing and + * none of the subexpressions can alias 'other'. + */ + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return this.equals(other) || store.canAlias(this, other); + } + + /** + * Format this verbosely, for debugging. + * + * @return a verbose string representation of this + */ + public String toStringDebug() { + return String.format("%s(%s): %s", getClass().getSimpleName(), type, toString()); + } + + /// + /// Static methods + /// + + /** + * Returns the Java expression for a {@link FieldAccessNode}. The result may contain {@link + * Unknown} as receiver. + * + * @param node the FieldAccessNode to convert to a JavaExpression + * @return the {@link FieldAccess} or {@link ClassName} that corresponds to {@code node} + */ + public static JavaExpression fromNodeFieldAccess(FieldAccessNode node) { + Node receiverNode = node.getReceiver(); + String fieldName = node.getFieldName(); + if (fieldName.equals("this")) { + // The CFG represents "className.this" as a FieldAccessNode, but it isn't a field + // access. + return new ThisReference(receiverNode.getType()); + } else if (fieldName.equals("class")) { + // The CFG represents "className.class" as a FieldAccessNode; bit it is a class literal. + return new ClassName(receiverNode.getType()); } - - /** - * Returns true if and only if {@code other} appears anywhere in this or an expression appears - * in this such that {@code other} might alias this expression, and that expression is - * modifiable. - * - *

This is always true, except for cases where the Java type information prevents aliasing - * and none of the subexpressions can alias 'other'. - */ - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return this.equals(other) || store.canAlias(this, other); + JavaExpression receiver; + if (node.isStatic()) { + receiver = new ClassName(receiverNode.getType()); + } else { + receiver = fromNode(receiverNode); } - - /** - * Format this verbosely, for debugging. - * - * @return a verbose string representation of this - */ - public String toStringDebug() { - return String.format("%s(%s): %s", getClass().getSimpleName(), type, toString()); + return new FieldAccess(receiver, node); + } + + /** + * Returns the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. The + * result may contain {@link Unknown} as receiver. + * + * @param node the ArrayAccessNode to convert to a JavaExpression + * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. Can + * contain {@link Unknown} as receiver. + */ + public static ArrayAccess fromArrayAccess(ArrayAccessNode node) { + JavaExpression array = fromNode(node.getArray()); + JavaExpression index = fromNode(node.getIndex()); + return new ArrayAccess(node.getType(), array, index); + } + + /** + * We ignore operations such as widening and narrowing when computing the internal representation. + * + * @param receiverNode a node to convert to a JavaExpression + * @return the internal representation of the given node. Might contain {@link Unknown}. + */ + public static JavaExpression fromNode(Node receiverNode) { + JavaExpression result = null; + if (receiverNode instanceof FieldAccessNode) { + result = fromNodeFieldAccess((FieldAccessNode) receiverNode); + } else if (receiverNode instanceof ThisNode) { + result = new ThisReference(receiverNode.getType()); + } else if (receiverNode instanceof SuperNode) { + result = new ThisReference(receiverNode.getType()); + } else if (receiverNode instanceof LocalVariableNode) { + LocalVariableNode lv = (LocalVariableNode) receiverNode; + result = new LocalVariable(lv); + } else if (receiverNode instanceof ArrayAccessNode) { + ArrayAccessNode a = (ArrayAccessNode) receiverNode; + result = fromArrayAccess(a); + } else if (receiverNode instanceof StringConversionNode) { + // ignore string conversion + return fromNode(((StringConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof WideningConversionNode) { + // ignore widening + return fromNode(((WideningConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof NarrowingConversionNode) { + // ignore narrowing + return fromNode(((NarrowingConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof UnaryOperationNode) { + UnaryOperationNode uopn = (UnaryOperationNode) receiverNode; + return new UnaryOperation(uopn, fromNode(uopn.getOperand())); + } else if (receiverNode instanceof BinaryOperationNode) { + BinaryOperationNode bopn = (BinaryOperationNode) receiverNode; + return new BinaryOperation( + bopn, fromNode(bopn.getLeftOperand()), fromNode(bopn.getRightOperand())); + } else if (receiverNode instanceof ClassNameNode) { + ClassNameNode cn = (ClassNameNode) receiverNode; + result = new ClassName(cn.getType()); + } else if (receiverNode instanceof ValueLiteralNode) { + ValueLiteralNode vn = (ValueLiteralNode) receiverNode; + result = new ValueLiteral(vn.getType(), vn); + } else if (receiverNode instanceof ArrayCreationNode) { + ArrayCreationNode an = (ArrayCreationNode) receiverNode; + List<@Nullable JavaExpression> dimensions = + CollectionsPlume.mapList(JavaExpression::fromNode, an.getDimensions()); + List initializers = + CollectionsPlume.mapList(JavaExpression::fromNode, an.getInitializers()); + result = new ArrayCreation(an.getType(), dimensions, initializers); + } else if (receiverNode instanceof MethodInvocationNode) { + MethodInvocationNode mn = (MethodInvocationNode) receiverNode; + MethodInvocationTree t = mn.getTree(); + if (t == null) { + throw new BugInCF("Unexpected null tree for node: " + mn); + } + assert TreeUtils.isUseOfElement(t) : "@AssumeAssertion(nullness): tree kind"; + ExecutableElement invokedMethod = TreeUtils.elementFromUse(t); + + // Note that the method might be nondeterministic. + List parameters = + CollectionsPlume.mapList(JavaExpression::fromNode, mn.getArguments()); + JavaExpression methodReceiver; + if (ElementUtils.isStatic(invokedMethod)) { + methodReceiver = new ClassName(mn.getTarget().getReceiver().getType()); + } else { + methodReceiver = fromNode(mn.getTarget().getReceiver()); + } + result = new MethodCall(mn.getType(), invokedMethod, methodReceiver, parameters); } - /// - /// Static methods - /// - - /** - * Returns the Java expression for a {@link FieldAccessNode}. The result may contain {@link - * Unknown} as receiver. - * - * @param node the FieldAccessNode to convert to a JavaExpression - * @return the {@link FieldAccess} or {@link ClassName} that corresponds to {@code node} - */ - public static JavaExpression fromNodeFieldAccess(FieldAccessNode node) { - Node receiverNode = node.getReceiver(); - String fieldName = node.getFieldName(); - if (fieldName.equals("this")) { - // The CFG represents "className.this" as a FieldAccessNode, but it isn't a field - // access. - return new ThisReference(receiverNode.getType()); - } else if (fieldName.equals("class")) { - // The CFG represents "className.class" as a FieldAccessNode; bit it is a class literal. - return new ClassName(receiverNode.getType()); - } - JavaExpression receiver; - if (node.isStatic()) { - receiver = new ClassName(receiverNode.getType()); + if (result == null) { + result = new Unknown(receiverNode); + } + return result; + } + + /** + * Converts a javac {@link ExpressionTree} to a CF JavaExpression. The result might contain {@link + * Unknown}. + * + *

We ignore operations such as widening and narrowing when computing the JavaExpression. + * + * @param tree a javac tree + * @return a JavaExpression for the given javac tree + */ + public static JavaExpression fromTree(ExpressionTree tree) { + JavaExpression result; + switch (tree.getKind()) { + case ARRAY_ACCESS: + ArrayAccessTree a = (ArrayAccessTree) tree; + JavaExpression arrayAccessExpression = fromTree(a.getExpression()); + JavaExpression index = fromTree(a.getIndex()); + result = new ArrayAccess(TreeUtils.typeOf(a), arrayAccessExpression, index); + break; + + case BOOLEAN_LITERAL: + case CHAR_LITERAL: + case DOUBLE_LITERAL: + case FLOAT_LITERAL: + case INT_LITERAL: + case LONG_LITERAL: + case NULL_LITERAL: + case STRING_LITERAL: + LiteralTree vn = (LiteralTree) tree; + result = new ValueLiteral(TreeUtils.typeOf(tree), vn.getValue()); + break; + + case NEW_ARRAY: + NewArrayTree newArrayTree = (NewArrayTree) tree; + List<@Nullable JavaExpression> dimensions; + if (newArrayTree.getDimensions() == null) { + dimensions = Collections.emptyList(); } else { - receiver = fromNode(receiverNode); + dimensions = new ArrayList<>(newArrayTree.getDimensions().size()); + for (ExpressionTree dimension : newArrayTree.getDimensions()) { + dimensions.add(fromTree(dimension)); + } } - return new FieldAccess(receiver, node); - } - - /** - * Returns the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. - * The result may contain {@link Unknown} as receiver. - * - * @param node the ArrayAccessNode to convert to a JavaExpression - * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. - * Can contain {@link Unknown} as receiver. - */ - public static ArrayAccess fromArrayAccess(ArrayAccessNode node) { - JavaExpression array = fromNode(node.getArray()); - JavaExpression index = fromNode(node.getIndex()); - return new ArrayAccess(node.getType(), array, index); - } - - /** - * We ignore operations such as widening and narrowing when computing the internal - * representation. - * - * @param receiverNode a node to convert to a JavaExpression - * @return the internal representation of the given node. Might contain {@link Unknown}. - */ - public static JavaExpression fromNode(Node receiverNode) { - JavaExpression result = null; - if (receiverNode instanceof FieldAccessNode) { - result = fromNodeFieldAccess((FieldAccessNode) receiverNode); - } else if (receiverNode instanceof ThisNode) { - result = new ThisReference(receiverNode.getType()); - } else if (receiverNode instanceof SuperNode) { - result = new ThisReference(receiverNode.getType()); - } else if (receiverNode instanceof LocalVariableNode) { - LocalVariableNode lv = (LocalVariableNode) receiverNode; - result = new LocalVariable(lv); - } else if (receiverNode instanceof ArrayAccessNode) { - ArrayAccessNode a = (ArrayAccessNode) receiverNode; - result = fromArrayAccess(a); - } else if (receiverNode instanceof StringConversionNode) { - // ignore string conversion - return fromNode(((StringConversionNode) receiverNode).getOperand()); - } else if (receiverNode instanceof WideningConversionNode) { - // ignore widening - return fromNode(((WideningConversionNode) receiverNode).getOperand()); - } else if (receiverNode instanceof NarrowingConversionNode) { - // ignore narrowing - return fromNode(((NarrowingConversionNode) receiverNode).getOperand()); - } else if (receiverNode instanceof UnaryOperationNode) { - UnaryOperationNode uopn = (UnaryOperationNode) receiverNode; - return new UnaryOperation(uopn, fromNode(uopn.getOperand())); - } else if (receiverNode instanceof BinaryOperationNode) { - BinaryOperationNode bopn = (BinaryOperationNode) receiverNode; - return new BinaryOperation( - bopn, fromNode(bopn.getLeftOperand()), fromNode(bopn.getRightOperand())); - } else if (receiverNode instanceof ClassNameNode) { - ClassNameNode cn = (ClassNameNode) receiverNode; - result = new ClassName(cn.getType()); - } else if (receiverNode instanceof ValueLiteralNode) { - ValueLiteralNode vn = (ValueLiteralNode) receiverNode; - result = new ValueLiteral(vn.getType(), vn); - } else if (receiverNode instanceof ArrayCreationNode) { - ArrayCreationNode an = (ArrayCreationNode) receiverNode; - List<@Nullable JavaExpression> dimensions = - CollectionsPlume.mapList(JavaExpression::fromNode, an.getDimensions()); - List initializers = - CollectionsPlume.mapList(JavaExpression::fromNode, an.getInitializers()); - result = new ArrayCreation(an.getType(), dimensions, initializers); - } else if (receiverNode instanceof MethodInvocationNode) { - MethodInvocationNode mn = (MethodInvocationNode) receiverNode; - MethodInvocationTree t = mn.getTree(); - if (t == null) { - throw new BugInCF("Unexpected null tree for node: " + mn); - } - assert TreeUtils.isUseOfElement(t) : "@AssumeAssertion(nullness): tree kind"; - ExecutableElement invokedMethod = TreeUtils.elementFromUse(t); - - // Note that the method might be nondeterministic. - List parameters = - CollectionsPlume.mapList(JavaExpression::fromNode, mn.getArguments()); - JavaExpression methodReceiver; - if (ElementUtils.isStatic(invokedMethod)) { - methodReceiver = new ClassName(mn.getTarget().getReceiver().getType()); - } else { - methodReceiver = fromNode(mn.getTarget().getReceiver()); - } - result = new MethodCall(mn.getType(), invokedMethod, methodReceiver, parameters); + List initializers; + if (newArrayTree.getInitializers() == null) { + initializers = Collections.emptyList(); + } else { + initializers = new ArrayList<>(newArrayTree.getInitializers().size()); + for (ExpressionTree initializer : newArrayTree.getInitializers()) { + initializers.add(fromTree(initializer)); + } } - if (result == null) { - result = new Unknown(receiverNode); + result = new ArrayCreation(TreeUtils.typeOf(tree), dimensions, initializers); + break; + + case METHOD_INVOCATION: + MethodInvocationTree mn = (MethodInvocationTree) tree; + assert TreeUtils.isUseOfElement(mn) : "@AssumeAssertion(nullness): tree kind"; + ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn); + + // Note that the method might be nondeterministic. + List parameters = + CollectionsPlume.mapList(JavaExpression::fromTree, mn.getArguments()); + JavaExpression methodReceiver; + if (ElementUtils.isStatic(invokedMethod)) { + @SuppressWarnings("nullness:assignment" // enclosingTypeElement(ExecutableElement): + // @NonNull + ) + @NonNull TypeElement methodType = ElementUtils.enclosingTypeElement(invokedMethod); + methodReceiver = new ClassName(methodType.asType()); + } else { + methodReceiver = getReceiver(mn); } - return result; - } - - /** - * Converts a javac {@link ExpressionTree} to a CF JavaExpression. The result might contain - * {@link Unknown}. - * - *

We ignore operations such as widening and narrowing when computing the JavaExpression. - * - * @param tree a javac tree - * @return a JavaExpression for the given javac tree - */ - public static JavaExpression fromTree(ExpressionTree tree) { - JavaExpression result; - switch (tree.getKind()) { - case ARRAY_ACCESS: - ArrayAccessTree a = (ArrayAccessTree) tree; - JavaExpression arrayAccessExpression = fromTree(a.getExpression()); - JavaExpression index = fromTree(a.getIndex()); - result = new ArrayAccess(TreeUtils.typeOf(a), arrayAccessExpression, index); - break; - - case BOOLEAN_LITERAL: - case CHAR_LITERAL: - case DOUBLE_LITERAL: - case FLOAT_LITERAL: - case INT_LITERAL: - case LONG_LITERAL: - case NULL_LITERAL: - case STRING_LITERAL: - LiteralTree vn = (LiteralTree) tree; - result = new ValueLiteral(TreeUtils.typeOf(tree), vn.getValue()); - break; - - case NEW_ARRAY: - NewArrayTree newArrayTree = (NewArrayTree) tree; - List<@Nullable JavaExpression> dimensions; - if (newArrayTree.getDimensions() == null) { - dimensions = Collections.emptyList(); - } else { - dimensions = new ArrayList<>(newArrayTree.getDimensions().size()); - for (ExpressionTree dimension : newArrayTree.getDimensions()) { - dimensions.add(fromTree(dimension)); - } - } - List initializers; - if (newArrayTree.getInitializers() == null) { - initializers = Collections.emptyList(); - } else { - initializers = new ArrayList<>(newArrayTree.getInitializers().size()); - for (ExpressionTree initializer : newArrayTree.getInitializers()) { - initializers.add(fromTree(initializer)); - } - } - - result = new ArrayCreation(TreeUtils.typeOf(tree), dimensions, initializers); - break; - - case METHOD_INVOCATION: - MethodInvocationTree mn = (MethodInvocationTree) tree; - assert TreeUtils.isUseOfElement(mn) : "@AssumeAssertion(nullness): tree kind"; - ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn); - - // Note that the method might be nondeterministic. - List parameters = - CollectionsPlume.mapList(JavaExpression::fromTree, mn.getArguments()); - JavaExpression methodReceiver; - if (ElementUtils.isStatic(invokedMethod)) { - @SuppressWarnings( - "nullness:assignment" // enclosingTypeElement(ExecutableElement): - // @NonNull - ) - @NonNull TypeElement methodType = - ElementUtils.enclosingTypeElement(invokedMethod); - methodReceiver = new ClassName(methodType.asType()); - } else { - methodReceiver = getReceiver(mn); - } - TypeMirror resultType = TreeUtils.typeOf(mn); - result = new MethodCall(resultType, invokedMethod, methodReceiver, parameters); - break; - - case MEMBER_SELECT: - result = fromMemberSelect((MemberSelectTree) tree); - break; - - case IDENTIFIER: - IdentifierTree identifierTree = (IdentifierTree) tree; - TypeMirror typeOfId = TreeUtils.typeOf(identifierTree); - Name identifierName = identifierTree.getName(); - if (identifierName.contentEquals("this") || identifierName.contentEquals("super")) { - result = new ThisReference(typeOfId); - break; - } - assert TreeUtils.isUseOfElement(identifierTree) - : "@AssumeAssertion(nullness): tree kind"; - Element ele = TreeUtils.elementFromUse(identifierTree); - if (ele == null) { - result = null; - } else if (ElementUtils.isTypeElement(ele)) { - result = new ClassName(ele.asType()); - } else { - result = fromVariableElement(typeOfId, (VariableElement) ele, identifierTree); - } - break; - - case UNARY_PLUS: - return fromTree(((UnaryTree) tree).getExpression()); - case BITWISE_COMPLEMENT: - case LOGICAL_COMPLEMENT: - case POSTFIX_DECREMENT: - case POSTFIX_INCREMENT: - case PREFIX_DECREMENT: - case PREFIX_INCREMENT: - case UNARY_MINUS: - JavaExpression operand = fromTree(((UnaryTree) tree).getExpression()); - return new UnaryOperation(TreeUtils.typeOf(tree), tree.getKind(), operand); - - case CONDITIONAL_AND: - case CONDITIONAL_OR: - case DIVIDE: - case EQUAL_TO: - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case LEFT_SHIFT: - case LESS_THAN: - case LESS_THAN_EQUAL: - case MINUS: - case MULTIPLY: - case NOT_EQUAL_TO: - case OR: - case PLUS: - case REMAINDER: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - case XOR: - BinaryTree binaryTree = (BinaryTree) tree; - JavaExpression left = fromTree(binaryTree.getLeftOperand()); - JavaExpression right = fromTree(binaryTree.getRightOperand()); - return new BinaryOperation(TreeUtils.typeOf(tree), tree.getKind(), left, right); - - default: - result = null; + TypeMirror resultType = TreeUtils.typeOf(mn); + result = new MethodCall(resultType, invokedMethod, methodReceiver, parameters); + break; + + case MEMBER_SELECT: + result = fromMemberSelect((MemberSelectTree) tree); + break; + + case IDENTIFIER: + IdentifierTree identifierTree = (IdentifierTree) tree; + TypeMirror typeOfId = TreeUtils.typeOf(identifierTree); + Name identifierName = identifierTree.getName(); + if (identifierName.contentEquals("this") || identifierName.contentEquals("super")) { + result = new ThisReference(typeOfId); + break; } - - if (result == null) { - result = new Unknown(tree); + assert TreeUtils.isUseOfElement(identifierTree) : "@AssumeAssertion(nullness): tree kind"; + Element ele = TreeUtils.elementFromUse(identifierTree); + if (ele == null) { + result = null; + } else if (ElementUtils.isTypeElement(ele)) { + result = new ClassName(ele.asType()); + } else { + result = fromVariableElement(typeOfId, (VariableElement) ele, identifierTree); } - return result; + break; + + case UNARY_PLUS: + return fromTree(((UnaryTree) tree).getExpression()); + case BITWISE_COMPLEMENT: + case LOGICAL_COMPLEMENT: + case POSTFIX_DECREMENT: + case POSTFIX_INCREMENT: + case PREFIX_DECREMENT: + case PREFIX_INCREMENT: + case UNARY_MINUS: + JavaExpression operand = fromTree(((UnaryTree) tree).getExpression()); + return new UnaryOperation(TreeUtils.typeOf(tree), tree.getKind(), operand); + + case CONDITIONAL_AND: + case CONDITIONAL_OR: + case DIVIDE: + case EQUAL_TO: + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case LEFT_SHIFT: + case LESS_THAN: + case LESS_THAN_EQUAL: + case MINUS: + case MULTIPLY: + case NOT_EQUAL_TO: + case OR: + case PLUS: + case REMAINDER: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + case XOR: + BinaryTree binaryTree = (BinaryTree) tree; + JavaExpression left = fromTree(binaryTree.getLeftOperand()); + JavaExpression right = fromTree(binaryTree.getRightOperand()); + return new BinaryOperation(TreeUtils.typeOf(tree), tree.getKind(), left, right); + + default: + result = null; } - /** - * Returns the Java expression corresponding to the given variable tree {@code tree}. - * - * @param tree a variable tree - * @return a JavaExpression for {@code tree} - */ - public static JavaExpression fromVariableTree(VariableTree tree) { - return fromVariableElement( - TreeUtils.typeOf(tree), TreeUtils.elementFromDeclaration(tree), tree); + if (result == null) { + result = new Unknown(tree); } - - /** - * Returns the Java expression corresponding to the given variable element {@code ele}. - * - * @param typeOfEle the type of {@code ele} - * @param ele element whose JavaExpression is returned - * @param tree the tree for the variable - * @return the Java expression corresponding to the given variable element {@code ele} - */ - private static JavaExpression fromVariableElement( - TypeMirror typeOfEle, @Nullable VariableElement ele, Tree tree) { - if (ele == null) { - return new Unknown(tree); - } - switch (ele.getKind()) { - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - case PARAMETER: - return new LocalVariable(ele); - case FIELD: - case ENUM_CONSTANT: - // Implicit access expression, such as "this" or a class name - JavaExpression fieldAccessExpression; - @SuppressWarnings("nullness:dereference.of.nullable") // a field has enclosing class - TypeMirror enclosingTypeElement = ElementUtils.enclosingTypeElement(ele).asType(); - if (ElementUtils.isStatic(ele)) { - fieldAccessExpression = new ClassName(enclosingTypeElement); - } else { - fieldAccessExpression = new ThisReference(enclosingTypeElement); - } - return new FieldAccess(fieldAccessExpression, typeOfEle, ele); - default: - if (ElementUtils.isBindingVariable(ele)) { - return new LocalVariable(ele); - } - throw new BugInCF( - "Unexpected kind of VariableTree: kind: %s element: %s", - ele.getKind(), ele); - } + return result; + } + + /** + * Returns the Java expression corresponding to the given variable tree {@code tree}. + * + * @param tree a variable tree + * @return a JavaExpression for {@code tree} + */ + public static JavaExpression fromVariableTree(VariableTree tree) { + return fromVariableElement( + TreeUtils.typeOf(tree), TreeUtils.elementFromDeclaration(tree), tree); + } + + /** + * Returns the Java expression corresponding to the given variable element {@code ele}. + * + * @param typeOfEle the type of {@code ele} + * @param ele element whose JavaExpression is returned + * @param tree the tree for the variable + * @return the Java expression corresponding to the given variable element {@code ele} + */ + private static JavaExpression fromVariableElement( + TypeMirror typeOfEle, @Nullable VariableElement ele, Tree tree) { + if (ele == null) { + return new Unknown(tree); } - - /** - * Creates a JavaExpression from the {@code memberSelectTree}. - * - * @param memberSelectTree tree - * @return a JavaExpression for {@code memberSelectTree} - */ - private static JavaExpression fromMemberSelect(MemberSelectTree memberSelectTree) { - TypeMirror expressionType = TreeUtils.typeOf(memberSelectTree.getExpression()); - if (TreeUtils.isClassLiteral(memberSelectTree)) { - // the identifier is "class" - return new ClassName(expressionType); - } - if (TreeUtils.isExplicitThisDereference(memberSelectTree)) { - // the identifier is "class" - return new ThisReference(expressionType); - } - - assert TreeUtils.isUseOfElement(memberSelectTree) : "@AssumeAssertion(nullness): tree kind"; - Element ele = TreeUtils.elementFromUse(memberSelectTree); - if (ElementUtils.isTypeElement(ele)) { - // o instanceof MyClass.InnerClass - // o instanceof MyClass.InnerInterface - TypeMirror selectType = TreeUtils.typeOf(memberSelectTree); - return new ClassName(selectType); + switch (ele.getKind()) { + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + case PARAMETER: + return new LocalVariable(ele); + case FIELD: + case ENUM_CONSTANT: + // Implicit access expression, such as "this" or a class name + JavaExpression fieldAccessExpression; + @SuppressWarnings("nullness:dereference.of.nullable") // a field has enclosing class + TypeMirror enclosingTypeElement = ElementUtils.enclosingTypeElement(ele).asType(); + if (ElementUtils.isStatic(ele)) { + fieldAccessExpression = new ClassName(enclosingTypeElement); + } else { + fieldAccessExpression = new ThisReference(enclosingTypeElement); } - switch (ele.getKind()) { - case METHOD: - case CONSTRUCTOR: - return fromTree(memberSelectTree.getExpression()); - case ENUM_CONSTANT: - case FIELD: - TypeMirror fieldType = TreeUtils.typeOf(memberSelectTree); - JavaExpression je = fromTree(memberSelectTree.getExpression()); - return new FieldAccess(je, fieldType, (VariableElement) ele); - default: - throw new BugInCF("Unexpected element kind: %s element: %s", ele.getKind(), ele); + return new FieldAccess(fieldAccessExpression, typeOfEle, ele); + default: + if (ElementUtils.isBindingVariable(ele)) { + return new LocalVariable(ele); } + throw new BugInCF( + "Unexpected kind of VariableTree: kind: %s element: %s", ele.getKind(), ele); } - - /** - * Returns the parameters of {@code methodEle} as {@link LocalVariable}s. - * - * @param methodEle the method element - * @return list of parameters as {@link LocalVariable}s - */ - public static List getParametersAsLocalVariables(ExecutableElement methodEle) { - return CollectionsPlume.mapList(LocalVariable::new, methodEle.getParameters()); + } + + /** + * Creates a JavaExpression from the {@code memberSelectTree}. + * + * @param memberSelectTree tree + * @return a JavaExpression for {@code memberSelectTree} + */ + private static JavaExpression fromMemberSelect(MemberSelectTree memberSelectTree) { + TypeMirror expressionType = TreeUtils.typeOf(memberSelectTree.getExpression()); + if (TreeUtils.isClassLiteral(memberSelectTree)) { + // the identifier is "class" + return new ClassName(expressionType); } - - /** - * Returns the parameters of {@code methodEle} as {@link FormalParameter}s. - * - * @param methodEle the method element - * @return list of parameters as {@link FormalParameter}s - */ - public static List getFormalParameters(ExecutableElement methodEle) { - List parameters = new ArrayList<>(methodEle.getParameters().size()); - int oneBasedIndex = 1; - for (VariableElement variableElement : methodEle.getParameters()) { - parameters.add(new FormalParameter(oneBasedIndex, variableElement)); - oneBasedIndex++; - } - return parameters; + if (TreeUtils.isExplicitThisDereference(memberSelectTree)) { + // the identifier is "class" + return new ThisReference(expressionType); } - /// - /// Obtaining the receiver - /// - - /** - * Returns the receiver of the given invocation. - * - * @param accessTree a method or constructor invocation - * @return the receiver of the given invocation - */ - public static JavaExpression getReceiver(ExpressionTree accessTree) { - // TODO: Handle field accesses too? - assert accessTree instanceof MethodInvocationTree || accessTree instanceof NewClassTree; - ExpressionTree receiverTree = TreeUtils.getReceiverTree(accessTree); - if (receiverTree != null) { - return fromTree(receiverTree); - } else { - Element ele = TreeUtils.elementFromUse(accessTree); - if (ele == null) { - throw new BugInCF("TreeUtils.elementFromUse(" + accessTree + ") => null"); - } - return getImplicitReceiver(ele); - } + assert TreeUtils.isUseOfElement(memberSelectTree) : "@AssumeAssertion(nullness): tree kind"; + Element ele = TreeUtils.elementFromUse(memberSelectTree); + if (ElementUtils.isTypeElement(ele)) { + // o instanceof MyClass.InnerClass + // o instanceof MyClass.InnerInterface + TypeMirror selectType = TreeUtils.typeOf(memberSelectTree); + return new ClassName(selectType); } - - /** - * Returns the implicit receiver of ele. - * - *

Returns either a new ClassName or a new ThisReference depending on whether ele is static - * or not. The passed element must be a field, method, or class. - * - *

When this returns a ThisReference, its type is the class that declares {@code ele}, which - * is not necessarily the type of {@code this} at the invocation site. - * - * @param ele a field, method, or class - * @return either a new ClassName or a new ThisReference depending on whether ele is static or - * not - */ - public static JavaExpression getImplicitReceiver(Element ele) { - TypeElement enclosingTypeElement = ElementUtils.enclosingTypeElement(ele); - if (enclosingTypeElement == null) { - throw new BugInCF("getImplicitReceiver's arg has no enclosing type: " + ele); - } - TypeMirror enclosingType = enclosingTypeElement.asType(); - if (ElementUtils.isStatic(ele)) { - return new ClassName(enclosingType); - } else { - return new ThisReference(enclosingType); - } + switch (ele.getKind()) { + case METHOD: + case CONSTRUCTOR: + return fromTree(memberSelectTree.getExpression()); + case ENUM_CONSTANT: + case FIELD: + TypeMirror fieldType = TreeUtils.typeOf(memberSelectTree); + JavaExpression je = fromTree(memberSelectTree.getExpression()); + return new FieldAccess(je, fieldType, (VariableElement) ele); + default: + throw new BugInCF("Unexpected element kind: %s element: %s", ele.getKind(), ele); } - - /** - * Returns either a new ClassName or ThisReference JavaExpression object for the enclosingType. - * - *

The Tree should be an expression or a statement that does not have a receiver or an - * implicit receiver. For example, a local variable declaration. - * - * @param path a tree path - * @param enclosingType type of the enclosing type - * @return a new {@link ClassName} or {@link ThisReference} that is a JavaExpression object for - * the enclosingType - */ - public static JavaExpression getPseudoReceiver(TreePath path, TypeMirror enclosingType) { - if (TreePathUtil.isTreeInStaticScope(path)) { - return new ClassName(enclosingType); - } else { - return new ThisReference(enclosingType); - } + } + + /** + * Returns the parameters of {@code methodEle} as {@link LocalVariable}s. + * + * @param methodEle the method element + * @return list of parameters as {@link LocalVariable}s + */ + public static List getParametersAsLocalVariables(ExecutableElement methodEle) { + return CollectionsPlume.mapList(LocalVariable::new, methodEle.getParameters()); + } + + /** + * Returns the parameters of {@code methodEle} as {@link FormalParameter}s. + * + * @param methodEle the method element + * @return list of parameters as {@link FormalParameter}s + */ + public static List getFormalParameters(ExecutableElement methodEle) { + List parameters = new ArrayList<>(methodEle.getParameters().size()); + int oneBasedIndex = 1; + for (VariableElement variableElement : methodEle.getParameters()) { + parameters.add(new FormalParameter(oneBasedIndex, variableElement)); + oneBasedIndex++; } - - /** - * Accept method of the visitor pattern. - * - * @param visitor the visitor to be applied to this JavaExpression - * @param p the parameter for this operation - * @param result type of the operation - * @param

parameter type - * @return the result of visiting this - */ - public abstract R accept(JavaExpressionVisitor visitor, P p); - - /** - * Viewpoint-adapts {@code this} to a field access with receiver {@code receiver}. - * - * @param receiver receiver of the field access - * @return viewpoint-adapted version of this - */ - public JavaExpression atFieldAccess(JavaExpression receiver) { - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiver); + return parameters; + } + + /// + /// Obtaining the receiver + /// + + /** + * Returns the receiver of the given invocation. + * + * @param accessTree a method or constructor invocation + * @return the receiver of the given invocation + */ + public static JavaExpression getReceiver(ExpressionTree accessTree) { + // TODO: Handle field accesses too? + assert accessTree instanceof MethodInvocationTree || accessTree instanceof NewClassTree; + ExpressionTree receiverTree = TreeUtils.getReceiverTree(accessTree); + if (receiverTree != null) { + return fromTree(receiverTree); + } else { + Element ele = TreeUtils.elementFromUse(accessTree); + if (ele == null) { + throw new BugInCF("TreeUtils.elementFromUse(" + accessTree + ") => null"); + } + return getImplicitReceiver(ele); } - - /** - * Viewpoint-adapts {@code this} to the {@code methodTree} by converting any {@code - * FormalParameter} into {@code LocalVariable}s. - * - * @param methodTree method declaration tree - * @return viewpoint-adapted version of this - */ - public final JavaExpression atMethodBody(MethodTree methodTree) { - List parametersJe = - CollectionsPlume.mapList( - (VariableTree param) -> - new LocalVariable(TreeUtils.elementFromDeclaration(param)), - methodTree.getParameters()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, parametersJe); + } + + /** + * Returns the implicit receiver of ele. + * + *

Returns either a new ClassName or a new ThisReference depending on whether ele is static or + * not. The passed element must be a field, method, or class. + * + *

When this returns a ThisReference, its type is the class that declares {@code ele}, which is + * not necessarily the type of {@code this} at the invocation site. + * + * @param ele a field, method, or class + * @return either a new ClassName or a new ThisReference depending on whether ele is static or not + */ + public static JavaExpression getImplicitReceiver(Element ele) { + TypeElement enclosingTypeElement = ElementUtils.enclosingTypeElement(ele); + if (enclosingTypeElement == null) { + throw new BugInCF("getImplicitReceiver's arg has no enclosing type: " + ele); } - - /** - * Viewpoint-adapts {@code this} to the {@code methodInvocationTree}. - * - * @param methodInvocationTree method invocation - * @return viewpoint-adapted version of this - */ - public final JavaExpression atMethodInvocation(MethodInvocationTree methodInvocationTree) { - JavaExpression receiverJe = JavaExpression.getReceiver(methodInvocationTree); - List argumentsJe = - argumentTreesToJavaExpressions( - TreeUtils.elementFromUse(methodInvocationTree), - methodInvocationTree.getArguments()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); + TypeMirror enclosingType = enclosingTypeElement.asType(); + if (ElementUtils.isStatic(ele)) { + return new ClassName(enclosingType); + } else { + return new ThisReference(enclosingType); } - - /** - * Viewpoint-adapts {@code this} to the {@code invocationNode}. - * - * @param invocationNode method invocation - * @return viewpoint-adapted version of this - */ - public final JavaExpression atMethodInvocation(MethodInvocationNode invocationNode) { - JavaExpression receiverJe = - JavaExpression.fromNode(invocationNode.getTarget().getReceiver()); - List argumentsJe = - CollectionsPlume.mapList(JavaExpression::fromNode, invocationNode.getArguments()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); + } + + /** + * Returns either a new ClassName or ThisReference JavaExpression object for the enclosingType. + * + *

The Tree should be an expression or a statement that does not have a receiver or an implicit + * receiver. For example, a local variable declaration. + * + * @param path a tree path + * @param enclosingType type of the enclosing type + * @return a new {@link ClassName} or {@link ThisReference} that is a JavaExpression object for + * the enclosingType + */ + public static JavaExpression getPseudoReceiver(TreePath path, TypeMirror enclosingType) { + if (TreePathUtil.isTreeInStaticScope(path)) { + return new ClassName(enclosingType); + } else { + return new ThisReference(enclosingType); } - - /** - * Viewpoint-adapts {@code this} to the {@code newClassTree}. - * - * @param newClassTree constructor invocation - * @return viewpoint-adapted version of this - */ - public JavaExpression atConstructorInvocation(NewClassTree newClassTree) { - JavaExpression receiverJe = JavaExpression.getReceiver(newClassTree); - List argumentsJe = - argumentTreesToJavaExpressions( - TreeUtils.elementFromUse(newClassTree), newClassTree.getArguments()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); + } + + /** + * Accept method of the visitor pattern. + * + * @param visitor the visitor to be applied to this JavaExpression + * @param p the parameter for this operation + * @param result type of the operation + * @param

parameter type + * @return the result of visiting this + */ + public abstract R accept(JavaExpressionVisitor visitor, P p); + + /** + * Viewpoint-adapts {@code this} to a field access with receiver {@code receiver}. + * + * @param receiver receiver of the field access + * @return viewpoint-adapted version of this + */ + public JavaExpression atFieldAccess(JavaExpression receiver) { + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiver); + } + + /** + * Viewpoint-adapts {@code this} to the {@code methodTree} by converting any {@code + * FormalParameter} into {@code LocalVariable}s. + * + * @param methodTree method declaration tree + * @return viewpoint-adapted version of this + */ + public final JavaExpression atMethodBody(MethodTree methodTree) { + List parametersJe = + CollectionsPlume.mapList( + (VariableTree param) -> new LocalVariable(TreeUtils.elementFromDeclaration(param)), + methodTree.getParameters()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, parametersJe); + } + + /** + * Viewpoint-adapts {@code this} to the {@code methodInvocationTree}. + * + * @param methodInvocationTree method invocation + * @return viewpoint-adapted version of this + */ + public final JavaExpression atMethodInvocation(MethodInvocationTree methodInvocationTree) { + JavaExpression receiverJe = JavaExpression.getReceiver(methodInvocationTree); + List argumentsJe = + argumentTreesToJavaExpressions( + TreeUtils.elementFromUse(methodInvocationTree), methodInvocationTree.getArguments()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); + } + + /** + * Viewpoint-adapts {@code this} to the {@code invocationNode}. + * + * @param invocationNode method invocation + * @return viewpoint-adapted version of this + */ + public final JavaExpression atMethodInvocation(MethodInvocationNode invocationNode) { + JavaExpression receiverJe = JavaExpression.fromNode(invocationNode.getTarget().getReceiver()); + List argumentsJe = + CollectionsPlume.mapList(JavaExpression::fromNode, invocationNode.getArguments()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); + } + + /** + * Viewpoint-adapts {@code this} to the {@code newClassTree}. + * + * @param newClassTree constructor invocation + * @return viewpoint-adapted version of this + */ + public JavaExpression atConstructorInvocation(NewClassTree newClassTree) { + JavaExpression receiverJe = JavaExpression.getReceiver(newClassTree); + List argumentsJe = + argumentTreesToJavaExpressions( + TreeUtils.elementFromUse(newClassTree), newClassTree.getArguments()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); + } + + /** + * Converts method or constructor arguments from Trees to JavaExpressions, accounting for varargs. + * + * @param method the method or constructor being invoked + * @param argTrees the arguments to the method or constructor + * @return the arguments, as JavaExpressions + */ + private static List argumentTreesToJavaExpressions( + ExecutableElement method, List argTrees) { + if (isVarArgsInvocation(method, argTrees)) { + List result = new ArrayList<>(method.getParameters().size()); + for (int i = 0; i < method.getParameters().size() - 1; i++) { + result.add(JavaExpression.fromTree(argTrees.get(i))); + } + + List varargArgs = + new ArrayList<>(argTrees.size() - method.getParameters().size() + 1); + for (int i = method.getParameters().size() - 1; i < argTrees.size(); i++) { + varargArgs.add(JavaExpression.fromTree(argTrees.get(i))); + } + Element varargsElement = method.getParameters().get(method.getParameters().size() - 1); + TypeMirror tm = ElementUtils.getType(varargsElement); + result.add(new ArrayCreation(tm, Collections.emptyList(), varargArgs)); + + return result; } - /** - * Converts method or constructor arguments from Trees to JavaExpressions, accounting for - * varargs. - * - * @param method the method or constructor being invoked - * @param argTrees the arguments to the method or constructor - * @return the arguments, as JavaExpressions - */ - private static List argumentTreesToJavaExpressions( - ExecutableElement method, List argTrees) { - if (isVarArgsInvocation(method, argTrees)) { - List result = new ArrayList<>(method.getParameters().size()); - for (int i = 0; i < method.getParameters().size() - 1; i++) { - result.add(JavaExpression.fromTree(argTrees.get(i))); - } - - List varargArgs = - new ArrayList<>(argTrees.size() - method.getParameters().size() + 1); - for (int i = method.getParameters().size() - 1; i < argTrees.size(); i++) { - varargArgs.add(JavaExpression.fromTree(argTrees.get(i))); - } - Element varargsElement = method.getParameters().get(method.getParameters().size() - 1); - TypeMirror tm = ElementUtils.getType(varargsElement); - result.add(new ArrayCreation(tm, Collections.emptyList(), varargArgs)); - - return result; - } - - return CollectionsPlume.mapList(JavaExpression::fromTree, argTrees); + return CollectionsPlume.mapList(JavaExpression::fromTree, argTrees); + } + + /** + * Returns true if method is a varargs method or constructor and its varargs arguments are not + * passed in an array. + * + * @param method the method or constructor + * @param args the arguments at the call site + * @return true if method is a varargs method and its varargs arguments are not passed in an array + */ + private static boolean isVarArgsInvocation( + ExecutableElement method, List args) { + if (!method.isVarArgs()) { + return false; } - - /** - * Returns true if method is a varargs method or constructor and its varargs arguments are not - * passed in an array. - * - * @param method the method or constructor - * @param args the arguments at the call site - * @return true if method is a varargs method and its varargs arguments are not passed in an - * array - */ - private static boolean isVarArgsInvocation( - ExecutableElement method, List args) { - if (!method.isVarArgs()) { - return false; - } - if (method.getParameters().size() != args.size()) { - return true; - } - TypeMirror lastArgType = TreeUtils.typeOf(args.get(args.size() - 1)); - if (lastArgType.getKind() != TypeKind.ARRAY) { - return true; - } - List paramElts = method.getParameters(); - VariableElement lastParamElt = paramElts.get(paramElts.size() - 1); - return TypesUtils.getArrayDepth(ElementUtils.getType(lastParamElt)) - != TypesUtils.getArrayDepth(lastArgType); + if (method.getParameters().size() != args.size()) { + return true; + } + TypeMirror lastArgType = TreeUtils.typeOf(args.get(args.size() - 1)); + if (lastArgType.getKind() != TypeKind.ARRAY) { + return true; } + List paramElts = method.getParameters(); + VariableElement lastParamElt = paramElts.get(paramElts.size() - 1); + return TypesUtils.getArrayDepth(ElementUtils.getType(lastParamElt)) + != TypesUtils.getArrayDepth(lastArgType); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java index 7055aa24153..091228e9b9e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java @@ -1,11 +1,10 @@ package org.checkerframework.dataflow.expression; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.plumelib.util.CollectionsPlume; -import java.util.List; - /** * This class calls {@link #convert(JavaExpression)} on each subexpression of the {@link * JavaExpression} and returns a new {@code JavaExpression} built from the result of calling {@code @@ -18,104 +17,103 @@ */ public abstract class JavaExpressionConverter extends JavaExpressionVisitor { - /** - * Converts {@code javaExpr} and returns the resulting {@code JavaExpression}. - * - * @param javaExpr the expression to convert - * @return the converted expression - */ - public JavaExpression convert(JavaExpression javaExpr) { - return super.visit(javaExpr, null); - } - - /** - * Converts all the expressions in {@code list} and returns the resulting list. - * - * @param list the list of expressions to convert - * @return the list of converted expressions - */ - public List<@PolyNull JavaExpression> convert(List<@PolyNull JavaExpression> list) { - return CollectionsPlume.mapList( - (@PolyNull JavaExpression expression) -> { - // Can't use a ternary operator because of: - // https://github.com/typetools/checker-framework/issues/1170 - if (expression == null) { - return null; - } - return convert(expression); - }, - list); - } - - @Override - protected JavaExpression visitArrayAccess(ArrayAccess arrayAccessExpr, Void unused) { - JavaExpression array = convert(arrayAccessExpr.getArray()); - JavaExpression index = convert(arrayAccessExpr.getIndex()); - return new ArrayAccess(arrayAccessExpr.type, array, index); - } - - @Override - protected JavaExpression visitArrayCreation(ArrayCreation arrayCreationExpr, Void unused) { - List<@Nullable JavaExpression> dims = convert(arrayCreationExpr.getDimensions()); - List inits = convert(arrayCreationExpr.getInitializers()); - return new ArrayCreation(arrayCreationExpr.getType(), dims, inits); - } - - @Override - protected JavaExpression visitBinaryOperation(BinaryOperation binaryOpExpr, Void unused) { - JavaExpression left = convert(binaryOpExpr.getLeft()); - JavaExpression right = convert(binaryOpExpr.getRight()); - return new BinaryOperation( - binaryOpExpr.getType(), binaryOpExpr.getOperationKind(), left, right); - } - - @Override - protected JavaExpression visitClassName(ClassName classNameExpr, Void unused) { - return classNameExpr; - } - - @Override - protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { - JavaExpression receiver = convert(fieldAccessExpr.getReceiver()); - return new FieldAccess(receiver, fieldAccessExpr.getType(), fieldAccessExpr.getField()); - } - - @Override - protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { - return parameterExpr; - } - - @Override - protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { - return localVarExpr; - } - - @Override - protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { - JavaExpression receiver = convert(methodCallExpr.getReceiver()); - List args = convert(methodCallExpr.getArguments()); - return new MethodCall( - methodCallExpr.getType(), methodCallExpr.getElement(), receiver, args); - } - - @Override - protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { - return thisExpr; - } - - @Override - protected JavaExpression visitUnaryOperation(UnaryOperation unaryOpExpr, Void unused) { - JavaExpression operand = convert(unaryOpExpr.getOperand()); - return new UnaryOperation(unaryOpExpr.getType(), unaryOpExpr.getOperationKind(), operand); - } - - @Override - protected JavaExpression visitUnknown(Unknown unknownExpr, Void unused) { - return unknownExpr; - } - - @Override - protected JavaExpression visitValueLiteral(ValueLiteral literalExpr, Void unused) { - return literalExpr; - } + /** + * Converts {@code javaExpr} and returns the resulting {@code JavaExpression}. + * + * @param javaExpr the expression to convert + * @return the converted expression + */ + public JavaExpression convert(JavaExpression javaExpr) { + return super.visit(javaExpr, null); + } + + /** + * Converts all the expressions in {@code list} and returns the resulting list. + * + * @param list the list of expressions to convert + * @return the list of converted expressions + */ + public List<@PolyNull JavaExpression> convert(List<@PolyNull JavaExpression> list) { + return CollectionsPlume.mapList( + (@PolyNull JavaExpression expression) -> { + // Can't use a ternary operator because of: + // https://github.com/typetools/checker-framework/issues/1170 + if (expression == null) { + return null; + } + return convert(expression); + }, + list); + } + + @Override + protected JavaExpression visitArrayAccess(ArrayAccess arrayAccessExpr, Void unused) { + JavaExpression array = convert(arrayAccessExpr.getArray()); + JavaExpression index = convert(arrayAccessExpr.getIndex()); + return new ArrayAccess(arrayAccessExpr.type, array, index); + } + + @Override + protected JavaExpression visitArrayCreation(ArrayCreation arrayCreationExpr, Void unused) { + List<@Nullable JavaExpression> dims = convert(arrayCreationExpr.getDimensions()); + List inits = convert(arrayCreationExpr.getInitializers()); + return new ArrayCreation(arrayCreationExpr.getType(), dims, inits); + } + + @Override + protected JavaExpression visitBinaryOperation(BinaryOperation binaryOpExpr, Void unused) { + JavaExpression left = convert(binaryOpExpr.getLeft()); + JavaExpression right = convert(binaryOpExpr.getRight()); + return new BinaryOperation( + binaryOpExpr.getType(), binaryOpExpr.getOperationKind(), left, right); + } + + @Override + protected JavaExpression visitClassName(ClassName classNameExpr, Void unused) { + return classNameExpr; + } + + @Override + protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { + JavaExpression receiver = convert(fieldAccessExpr.getReceiver()); + return new FieldAccess(receiver, fieldAccessExpr.getType(), fieldAccessExpr.getField()); + } + + @Override + protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { + return parameterExpr; + } + + @Override + protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { + return localVarExpr; + } + + @Override + protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { + JavaExpression receiver = convert(methodCallExpr.getReceiver()); + List args = convert(methodCallExpr.getArguments()); + return new MethodCall(methodCallExpr.getType(), methodCallExpr.getElement(), receiver, args); + } + + @Override + protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { + return thisExpr; + } + + @Override + protected JavaExpression visitUnaryOperation(UnaryOperation unaryOpExpr, Void unused) { + JavaExpression operand = convert(unaryOpExpr.getOperand()); + return new UnaryOperation(unaryOpExpr.getType(), unaryOpExpr.getOperationKind(), operand); + } + + @Override + protected JavaExpression visitUnknown(Unknown unknownExpr, Void unused) { + return unknownExpr; + } + + @Override + protected JavaExpression visitValueLiteral(ValueLiteral literalExpr, Void unused) { + return literalExpr; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java index 11414a13bba..e282ce5a11d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java @@ -1,8 +1,7 @@ package org.checkerframework.dataflow.expression; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A simple scanner for {@link JavaExpression}. @@ -11,97 +10,97 @@ */ public abstract class JavaExpressionScanner

extends JavaExpressionVisitor { - /** - * Scans the JavaExpression. - * - * @param javaExpression the expression to scan - * @param p parameter to pass - */ - public void scan(JavaExpression javaExpression, P p) { - visit(javaExpression, p); - } - - /** - * Scans each JavaExpression in {@code expressions}. - * - * @param expressions a list of JavaExpressions to scan - * @param p pameter to pass - */ - public void scan(List expressions, P p) { - for (JavaExpression expression : expressions) { - if (expression != null) { - visit(expression, p); - } - } - } - - @Override - protected Void visitArrayAccess(ArrayAccess arrayAccessExpr, P p) { - visit(arrayAccessExpr.getArray(), p); - visit(arrayAccessExpr.getIndex(), p); - return null; - } - - @Override - protected Void visitArrayCreation(ArrayCreation arrayCreationExpr, P p) { - scan(arrayCreationExpr.getDimensions(), p); - scan(arrayCreationExpr.getInitializers(), p); - return null; - } - - @Override - protected Void visitBinaryOperation(BinaryOperation binaryOpExpr, P p) { - visit(binaryOpExpr.getLeft(), p); - visit(binaryOpExpr.getRight(), p); - return null; - } - - @Override - protected Void visitClassName(ClassName classNameExpr, P p) { - return null; - } - - @Override - protected Void visitFormalParameter(FormalParameter parameterExpr, P p) { - return null; - } - - @Override - protected Void visitFieldAccess(FieldAccess fieldAccessExpr, P p) { - visit(fieldAccessExpr.getReceiver(), p); - return null; - } - - @Override - protected Void visitLocalVariable(LocalVariable localVarExpr, P p) { - return null; - } - - @Override - protected Void visitMethodCall(MethodCall methodCallExpr, P p) { - visit(methodCallExpr.getReceiver(), p); - scan(methodCallExpr.getArguments(), p); - return null; - } - - @Override - protected Void visitThisReference(ThisReference thisExpr, P p) { - return null; - } - - @Override - protected Void visitUnaryOperation(UnaryOperation unaryOpExpr, P p) { - visit(unaryOpExpr.getOperand(), p); - return null; - } - - @Override - protected Void visitUnknown(Unknown unknownExpr, P p) { - return null; - } - - @Override - protected Void visitValueLiteral(ValueLiteral literalExpr, P p) { - return null; + /** + * Scans the JavaExpression. + * + * @param javaExpression the expression to scan + * @param p parameter to pass + */ + public void scan(JavaExpression javaExpression, P p) { + visit(javaExpression, p); + } + + /** + * Scans each JavaExpression in {@code expressions}. + * + * @param expressions a list of JavaExpressions to scan + * @param p pameter to pass + */ + public void scan(List expressions, P p) { + for (JavaExpression expression : expressions) { + if (expression != null) { + visit(expression, p); + } } + } + + @Override + protected Void visitArrayAccess(ArrayAccess arrayAccessExpr, P p) { + visit(arrayAccessExpr.getArray(), p); + visit(arrayAccessExpr.getIndex(), p); + return null; + } + + @Override + protected Void visitArrayCreation(ArrayCreation arrayCreationExpr, P p) { + scan(arrayCreationExpr.getDimensions(), p); + scan(arrayCreationExpr.getInitializers(), p); + return null; + } + + @Override + protected Void visitBinaryOperation(BinaryOperation binaryOpExpr, P p) { + visit(binaryOpExpr.getLeft(), p); + visit(binaryOpExpr.getRight(), p); + return null; + } + + @Override + protected Void visitClassName(ClassName classNameExpr, P p) { + return null; + } + + @Override + protected Void visitFormalParameter(FormalParameter parameterExpr, P p) { + return null; + } + + @Override + protected Void visitFieldAccess(FieldAccess fieldAccessExpr, P p) { + visit(fieldAccessExpr.getReceiver(), p); + return null; + } + + @Override + protected Void visitLocalVariable(LocalVariable localVarExpr, P p) { + return null; + } + + @Override + protected Void visitMethodCall(MethodCall methodCallExpr, P p) { + visit(methodCallExpr.getReceiver(), p); + scan(methodCallExpr.getArguments(), p); + return null; + } + + @Override + protected Void visitThisReference(ThisReference thisExpr, P p) { + return null; + } + + @Override + protected Void visitUnaryOperation(UnaryOperation unaryOpExpr, P p) { + visit(unaryOpExpr.getOperand(), p); + return null; + } + + @Override + protected Void visitUnknown(Unknown unknownExpr, P p) { + return null; + } + + @Override + protected Void visitValueLiteral(ValueLiteral literalExpr, P p) { + return null; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java index 024ec632ce1..1511dea1277 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java @@ -8,122 +8,122 @@ */ public abstract class JavaExpressionVisitor { - /** - * Visits the given {@code javaExpr}. - * - * @param javaExpr the expression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the expression - */ - public R visit(JavaExpression javaExpr, P p) { - return javaExpr.accept(this, p); - } + /** + * Visits the given {@code javaExpr}. + * + * @param javaExpr the expression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the expression + */ + public R visit(JavaExpression javaExpr, P p) { + return javaExpr.accept(this, p); + } - /** - * Visit an {@link ArrayAccess}. - * - * @param arrayAccessExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code arrayAccessExpr} - */ - protected abstract R visitArrayAccess(ArrayAccess arrayAccessExpr, P p); + /** + * Visit an {@link ArrayAccess}. + * + * @param arrayAccessExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code arrayAccessExpr} + */ + protected abstract R visitArrayAccess(ArrayAccess arrayAccessExpr, P p); - /** - * Visit an {@link ArrayCreation}. - * - * @param arrayCreationExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code arrayCreationExpr} - */ - protected abstract R visitArrayCreation(ArrayCreation arrayCreationExpr, P p); + /** + * Visit an {@link ArrayCreation}. + * + * @param arrayCreationExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code arrayCreationExpr} + */ + protected abstract R visitArrayCreation(ArrayCreation arrayCreationExpr, P p); - /** - * Visit a {@link BinaryOperation}. - * - * @param binaryOpExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code binaryOpExpr} - */ - protected abstract R visitBinaryOperation(BinaryOperation binaryOpExpr, P p); + /** + * Visit a {@link BinaryOperation}. + * + * @param binaryOpExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code binaryOpExpr} + */ + protected abstract R visitBinaryOperation(BinaryOperation binaryOpExpr, P p); - /** - * Visit a {@link ClassName}. - * - * @param classNameExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code classNameExpr} - */ - protected abstract R visitClassName(ClassName classNameExpr, P p); + /** + * Visit a {@link ClassName}. + * + * @param classNameExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code classNameExpr} + */ + protected abstract R visitClassName(ClassName classNameExpr, P p); - /** - * Visit a {@link FieldAccess}. - * - * @param fieldAccessExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code fieldAccessExpr} - */ - protected abstract R visitFieldAccess(FieldAccess fieldAccessExpr, P p); + /** + * Visit a {@link FieldAccess}. + * + * @param fieldAccessExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code fieldAccessExpr} + */ + protected abstract R visitFieldAccess(FieldAccess fieldAccessExpr, P p); - /** - * Visit a {@link FormalParameter}. - * - * @param parameterExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code parameterExpr} - */ - protected abstract R visitFormalParameter(FormalParameter parameterExpr, P p); + /** + * Visit a {@link FormalParameter}. + * + * @param parameterExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code parameterExpr} + */ + protected abstract R visitFormalParameter(FormalParameter parameterExpr, P p); - /** - * Visit a {@link LocalVariable}. - * - * @param localVarExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code localVarExpr} - */ - protected abstract R visitLocalVariable(LocalVariable localVarExpr, P p); + /** + * Visit a {@link LocalVariable}. + * + * @param localVarExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code localVarExpr} + */ + protected abstract R visitLocalVariable(LocalVariable localVarExpr, P p); - /** - * Visit a {@link MethodCall}. - * - * @param methodCallExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code methodCallExpr} - */ - protected abstract R visitMethodCall(MethodCall methodCallExpr, P p); + /** + * Visit a {@link MethodCall}. + * + * @param methodCallExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code methodCallExpr} + */ + protected abstract R visitMethodCall(MethodCall methodCallExpr, P p); - /** - * Visit a {@link ThisReference}. - * - * @param thisExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code thisExpr} - */ - protected abstract R visitThisReference(ThisReference thisExpr, P p); + /** + * Visit a {@link ThisReference}. + * + * @param thisExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code thisExpr} + */ + protected abstract R visitThisReference(ThisReference thisExpr, P p); - /** - * Visit an {@link UnaryOperation}. - * - * @param unaryOpExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code unaryOpExpr} - */ - protected abstract R visitUnaryOperation(UnaryOperation unaryOpExpr, P p); + /** + * Visit an {@link UnaryOperation}. + * + * @param unaryOpExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code unaryOpExpr} + */ + protected abstract R visitUnaryOperation(UnaryOperation unaryOpExpr, P p); - /** - * Visit an {@link Unknown}. - * - * @param unknownExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code unknownExpr} - */ - protected abstract R visitUnknown(Unknown unknownExpr, P p); + /** + * Visit an {@link Unknown}. + * + * @param unknownExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code unknownExpr} + */ + protected abstract R visitUnknown(Unknown unknownExpr, P p); - /** - * Visit a {@link ValueLiteral}. - * - * @param literalExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code literalExpr} - */ - protected abstract R visitValueLiteral(ValueLiteral literalExpr, P p); + /** + * Visit a {@link ValueLiteral}. + * + * @param literalExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code literalExpr} + */ + protected abstract R visitValueLiteral(ValueLiteral literalExpr, P p); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java index 0930d6bedc2..632d6c21bd3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java @@ -1,17 +1,14 @@ package org.checkerframework.dataflow.expression; import com.sun.tools.javac.code.Symbol.VarSymbol; - +import java.util.Objects; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.Objects; - -import javax.lang.model.element.VariableElement; - /** * A local variable. * @@ -19,118 +16,118 @@ * FormalParameter} represents a formal parameter expressed using the "#2" notation. */ public class LocalVariable extends JavaExpression { - /** The element for this local variable. */ - protected final VariableElement element; - - /** - * Creates a new LocalVariable. - * - * @param localVar a CFG local variable - */ - public LocalVariable(LocalVariableNode localVar) { - super(localVar.getType()); - this.element = localVar.getElement(); - } - - /** - * Creates a new LocalVariable. - * - * @param element the element for the local variable - */ - public LocalVariable(VariableElement element) { - super(ElementUtils.getType(element)); - this.element = element; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LocalVariable)) { - return false; - } - LocalVariable other = (LocalVariable) obj; - - return sameElement(element, other.element); - } - - /** - * Returns true if the two elements are the same. - * - * @param element1 the first element to compare - * @param element2 the second element to compare - * @return true if the two elements are the same - */ - protected static boolean sameElement(VariableElement element1, VariableElement element2) { - VarSymbol vs1 = (VarSymbol) element1; - VarSymbol vs2 = (VarSymbol) element2; - // If a LocalVariable is created via JavaExpressionParseUtil#parse, then `vs1.equals(vs2)` - // will not return true even if the elements represent the same local variable. - // The owner of a lambda parameter is the enclosing method, so a local variable and a lambda - // parameter might have the same name and the same owner. Use pos to differentiate this - // case. - return vs1.pos == vs2.pos && vs1.name == vs2.name && vs1.owner.equals(vs2.owner); - } - - /** - * Returns the element for this variable. - * - * @return the element for this variable - */ - public VariableElement getElement() { - return element; - } - - @Override - public int hashCode() { - VarSymbol vs = (VarSymbol) element; - return Objects.hash(vs.pos, vs.name, vs.owner); + /** The element for this local variable. */ + protected final VariableElement element; + + /** + * Creates a new LocalVariable. + * + * @param localVar a CFG local variable + */ + public LocalVariable(LocalVariableNode localVar) { + super(localVar.getType()); + this.element = localVar.getElement(); + } + + /** + * Creates a new LocalVariable. + * + * @param element the element for the local variable + */ + public LocalVariable(VariableElement element) { + super(ElementUtils.getType(element)); + this.element = element; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LocalVariable)) { + return false; } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public String toStringDebug() { - return super.toStringDebug() + " [owner=" + ((VarSymbol) element).owner + "]"; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof LocalVariable)) { - return false; - } - LocalVariable other = (LocalVariable) je; - return this.equals(other); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other); - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return TypesUtils.isImmutableTypeInJdk(((VarSymbol) element).type); - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitLocalVariable(this, p); + LocalVariable other = (LocalVariable) obj; + + return sameElement(element, other.element); + } + + /** + * Returns true if the two elements are the same. + * + * @param element1 the first element to compare + * @param element2 the second element to compare + * @return true if the two elements are the same + */ + protected static boolean sameElement(VariableElement element1, VariableElement element2) { + VarSymbol vs1 = (VarSymbol) element1; + VarSymbol vs2 = (VarSymbol) element2; + // If a LocalVariable is created via JavaExpressionParseUtil#parse, then `vs1.equals(vs2)` + // will not return true even if the elements represent the same local variable. + // The owner of a lambda parameter is the enclosing method, so a local variable and a lambda + // parameter might have the same name and the same owner. Use pos to differentiate this + // case. + return vs1.pos == vs2.pos && vs1.name == vs2.name && vs1.owner.equals(vs2.owner); + } + + /** + * Returns the element for this variable. + * + * @return the element for this variable + */ + public VariableElement getElement() { + return element; + } + + @Override + public int hashCode() { + VarSymbol vs = (VarSymbol) element; + return Objects.hash(vs.pos, vs.name, vs.owner); + } + + @Override + public String toString() { + return element.toString(); + } + + @Override + public String toStringDebug() { + return super.toStringDebug() + " [owner=" + ((VarSymbol) element).owner + "]"; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof LocalVariable)) { + return false; } + LocalVariable other = (LocalVariable) je; + return this.equals(other); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other); + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return TypesUtils.isImmutableTypeInJdk(((VarSymbol) element).type); + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitLocalVariable(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java index 4c50db53d22..7d42cad17ad 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java @@ -1,191 +1,189 @@ package org.checkerframework.dataflow.expression; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.analysis.Store; -import org.checkerframework.dataflow.util.PurityUtils; -import org.checkerframework.javacutil.AnnotationProvider; - import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.StringJoiner; - import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.util.PurityUtils; +import org.checkerframework.javacutil.AnnotationProvider; /** A call to a @Deterministic method. */ public class MethodCall extends JavaExpression { - /** The method being called. */ - protected final ExecutableElement method; - - /** The receiver argument. */ - protected final JavaExpression receiver; - - /** The arguments. */ - protected final List arguments; - - /** - * Creates a new MethodCall. - * - * @param type the type of the method call - * @param method the method being called - * @param receiver the receiver argument - * @param arguments the arguments - */ - public MethodCall( - TypeMirror type, - ExecutableElement method, - JavaExpression receiver, - List arguments) { - super(type); - this.receiver = receiver; - this.arguments = arguments; - this.method = method; + /** The method being called. */ + protected final ExecutableElement method; + + /** The receiver argument. */ + protected final JavaExpression receiver; + + /** The arguments. */ + protected final List arguments; + + /** + * Creates a new MethodCall. + * + * @param type the type of the method call + * @param method the method being called + * @param receiver the receiver argument + * @param arguments the arguments + */ + public MethodCall( + TypeMirror type, + ExecutableElement method, + JavaExpression receiver, + List arguments) { + super(type); + this.receiver = receiver; + this.arguments = arguments; + this.method = method; + } + + /** + * Returns the ExecutableElement for the method call. + * + * @return the ExecutableElement for the method call + */ + public ExecutableElement getElement() { + return method; + } + + /** + * Returns the method call receiver (for inspection only - do not modify). + * + * @return the method call receiver (for inspection only - do not modify) + */ + public JavaExpression getReceiver() { + return receiver; + } + + /** + * Returns the method call arguments (for inspection only - do not modify any of the arguments). + * + * @return the method call arguments (for inspection only - do not modify any of the arguments) + */ + public List getArguments() { + return Collections.unmodifiableList(arguments); + } + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; } - - /** - * Returns the ExecutableElement for the method call. - * - * @return the ExecutableElement for the method call - */ - public ExecutableElement getElement() { - return method; + if (receiver.containsOfClass(clazz)) { + return true; } - - /** - * Returns the method call receiver (for inspection only - do not modify). - * - * @return the method call receiver (for inspection only - do not modify) - */ - public JavaExpression getReceiver() { - return receiver; + for (JavaExpression p : arguments) { + if (p.containsOfClass(clazz)) { + return true; + } } - - /** - * Returns the method call arguments (for inspection only - do not modify any of the arguments). - * - * @return the method call arguments (for inspection only - do not modify any of the arguments) - */ - public List getArguments() { - return Collections.unmodifiableList(arguments); + return false; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return (PurityUtils.isDeterministic(provider, method) || provider.isDeterministic(method)) + && listIsDeterministic(arguments, provider); + } + + @Override + public boolean isUnassignableByOtherCode() { + // There is no need to check that the method is deterministic, because a MethodCall is + // only created for deterministic methods. + return receiver.isUnmodifiableByOtherCode() + && arguments.stream().allMatch(JavaExpression::isUnmodifiableByOtherCode); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return isUnassignableByOtherCode(); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof MethodCall)) { + return false; } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; - } - if (receiver.containsOfClass(clazz)) { - return true; - } - for (JavaExpression p : arguments) { - if (p.containsOfClass(clazz)) { - return true; - } - } - return false; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return (PurityUtils.isDeterministic(provider, method) || provider.isDeterministic(method)) - && listIsDeterministic(arguments, provider); + MethodCall other = (MethodCall) je; + return method.equals(other.method) + && this.receiver.syntacticEquals(other.receiver) + && JavaExpression.syntacticEqualsList(this.arguments, other.arguments); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) + || receiver.containsSyntacticEqualJavaExpression(other) + || JavaExpression.listContainsSyntacticEqualJavaExpression(arguments, other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + if (receiver.containsModifiableAliasOf(store, other)) { + return true; } - - @Override - public boolean isUnassignableByOtherCode() { - // There is no need to check that the method is deterministic, because a MethodCall is - // only created for deterministic methods. - return receiver.isUnmodifiableByOtherCode() - && arguments.stream().allMatch(JavaExpression::isUnmodifiableByOtherCode); - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return isUnassignableByOtherCode(); + for (JavaExpression p : arguments) { + if (p.containsModifiableAliasOf(store, other)) { + return true; + } } + return false; // the method call itself is not modifiable + } - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof MethodCall)) { - return false; - } - MethodCall other = (MethodCall) je; - return method.equals(other.method) - && this.receiver.syntacticEquals(other.receiver) - && JavaExpression.syntacticEqualsList(this.arguments, other.arguments); + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) - || receiver.containsSyntacticEqualJavaExpression(other) - || JavaExpression.listContainsSyntacticEqualJavaExpression(arguments, other); + if (!(obj instanceof MethodCall)) { + return false; } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - if (receiver.containsModifiableAliasOf(store, other)) { - return true; - } - for (JavaExpression p : arguments) { - if (p.containsModifiableAliasOf(store, other)) { - return true; - } - } - return false; // the method call itself is not modifiable + if (method.getKind() == ElementKind.CONSTRUCTOR) { + // No two constructor instances are equal. + return false; } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MethodCall)) { - return false; - } - if (method.getKind() == ElementKind.CONSTRUCTOR) { - // No two constructor instances are equal. - return false; - } - MethodCall other = (MethodCall) obj; - return method.equals(other.method) - && receiver.equals(other.receiver) - && arguments.equals(other.arguments); + MethodCall other = (MethodCall) obj; + return method.equals(other.method) + && receiver.equals(other.receiver) + && arguments.equals(other.arguments); + } + + @Override + public int hashCode() { + if (method.getKind() == ElementKind.CONSTRUCTOR) { + // No two constructor instances have the same hashcode. + return System.identityHashCode(this); } - - @Override - public int hashCode() { - if (method.getKind() == ElementKind.CONSTRUCTOR) { - // No two constructor instances have the same hashcode. - return System.identityHashCode(this); - } - return Objects.hash(method, receiver, arguments); + return Objects.hash(method, receiver, arguments); + } + + @Override + public String toString() { + StringBuilder preParen = new StringBuilder(); + if (receiver instanceof ClassName) { + preParen.append(receiver.getType()); + } else { + preParen.append(receiver); } - - @Override - public String toString() { - StringBuilder preParen = new StringBuilder(); - if (receiver instanceof ClassName) { - preParen.append(receiver.getType()); - } else { - preParen.append(receiver); - } - preParen.append("."); - String methodName = method.getSimpleName().toString(); - preParen.append(methodName); - preParen.append("("); - StringJoiner result = new StringJoiner(", ", preParen, ")"); - for (JavaExpression argument : arguments) { - result.add(argument.toString()); - } - return result.toString(); + preParen.append("."); + String methodName = method.getSimpleName().toString(); + preParen.append(methodName); + preParen.append("("); + StringJoiner result = new StringJoiner(", ", preParen, ")"); + for (JavaExpression argument : arguments) { + result.add(argument.toString()); } + return result.toString(); + } - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitMethodCall(this, p); - } + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitMethodCall(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java index 4ca54f224c0..dfb2374c058 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java @@ -1,75 +1,74 @@ package org.checkerframework.dataflow.expression; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.TypesUtils; -import javax.lang.model.type.TypeMirror; - /** A use of {@code this}. */ public class ThisReference extends JavaExpression { - /** - * Create a new ThisReference. - * - * @param type the type of the {@code this} reference - */ - public ThisReference(TypeMirror type) { - super(type); - } + /** + * Create a new ThisReference. + * + * @param type the type of the {@code this} reference + */ + public ThisReference(TypeMirror type) { + super(type); + } - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ThisReference; - } + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ThisReference; + } - @Override - public int hashCode() { - return 0; - } + @Override + public int hashCode() { + return 0; + } - @Override - public String toString() { - return "this"; - } + @Override + public String toString() { + return "this"; + } - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } - @Override - public boolean isUnassignableByOtherCode() { - return true; - } + @Override + public boolean isUnassignableByOtherCode() { + return true; + } - @Override - public boolean isUnmodifiableByOtherCode() { - return TypesUtils.isImmutableTypeInJdk(type); - } + @Override + public boolean isUnmodifiableByOtherCode() { + return TypesUtils.isImmutableTypeInJdk(type); + } - @Override - public boolean syntacticEquals(JavaExpression je) { - return je instanceof ThisReference; - } + @Override + public boolean syntacticEquals(JavaExpression je) { + return je instanceof ThisReference; + } - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return false; // 'this' is not modifiable - } + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return false; // 'this' is not modifiable + } - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitThisReference(this, p); - } + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitThisReference(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java index 7b61243c2b2..7365833b77c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java @@ -1,150 +1,147 @@ package org.checkerframework.dataflow.expression; import com.sun.source.tree.Tree; - +import java.util.Objects; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.UnaryOperationNode; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; -import java.util.Objects; - -import javax.lang.model.type.TypeMirror; - /** JavaExpression for unary operations. */ public class UnaryOperation extends JavaExpression { - /** The unary operation kind. */ - protected final Tree.Kind operationKind; - - /** The operand. */ - protected final JavaExpression operand; - - /** - * Create a unary operation. - * - * @param type the type of the result - * @param operationKind the operator - * @param operand the operand - */ - public UnaryOperation(TypeMirror type, Tree.Kind operationKind, JavaExpression operand) { - super(operand.type); - this.operationKind = operationKind; - this.operand = operand; - } - - /** - * Create a unary operation. - * - * @param node the unary operation node - * @param operand the operand - */ - public UnaryOperation(UnaryOperationNode node, JavaExpression operand) { - this(node.getType(), node.getTree().getKind(), operand); - } - - /** - * Returns the operator of this unary operation. - * - * @return the unary operation kind - */ - public Tree.Kind getOperationKind() { - return operationKind; - } - - /** - * Returns the operand of this unary operation. - * - * @return the operand - */ - public JavaExpression getOperand() { - return operand; + /** The unary operation kind. */ + protected final Tree.Kind operationKind; + + /** The operand. */ + protected final JavaExpression operand; + + /** + * Create a unary operation. + * + * @param type the type of the result + * @param operationKind the operator + * @param operand the operand + */ + public UnaryOperation(TypeMirror type, Tree.Kind operationKind, JavaExpression operand) { + super(operand.type); + this.operationKind = operationKind; + this.operand = operand; + } + + /** + * Create a unary operation. + * + * @param node the unary operation node + * @param operand the operand + */ + public UnaryOperation(UnaryOperationNode node, JavaExpression operand) { + this(node.getType(), node.getTree().getKind(), operand); + } + + /** + * Returns the operator of this unary operation. + * + * @return the unary operation kind + */ + public Tree.Kind getOperationKind() { + return operationKind; + } + + /** + * Returns the operand of this unary operation. + * + * @return the operand + */ + public JavaExpression getOperand() { + return operand; + } + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; - } - return operand.containsOfClass(clazz); + return operand.containsOfClass(clazz); + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return operand.isDeterministic(provider); + } + + @Override + public boolean isUnassignableByOtherCode() { + return operand.isUnassignableByOtherCode(); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return operand.isUnmodifiableByOtherCode(); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof UnaryOperation)) { + return false; } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return operand.isDeterministic(provider); + UnaryOperation other = (UnaryOperation) je; + return operationKind == other.getOperationKind() && operand.syntacticEquals(other.operand); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other) || operand.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return operand.containsModifiableAliasOf(store, other); + } + + @Override + public int hashCode() { + return Objects.hash(operationKind, operand); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof UnaryOperation)) { + return false; } - - @Override - public boolean isUnassignableByOtherCode() { - return operand.isUnassignableByOtherCode(); + UnaryOperation unOp = (UnaryOperation) other; + return operationKind == unOp.getOperationKind() && operand.equals(unOp.operand); + } + + @Override + public String toString() { + String operandString = operand.toString(); + switch (operationKind) { + case BITWISE_COMPLEMENT: + return "~" + operandString; + case LOGICAL_COMPLEMENT: + return "!" + operandString; + case POSTFIX_DECREMENT: + return operandString + "--"; + case POSTFIX_INCREMENT: + return operandString + "++"; + case PREFIX_DECREMENT: + return "--" + operandString; + case PREFIX_INCREMENT: + return "++" + operandString; + case UNARY_MINUS: + return "-" + operandString; + case UNARY_PLUS: + return "+" + operandString; + default: + throw new BugInCF("Unrecognized unary operation kind " + operationKind); } + } - @Override - public boolean isUnmodifiableByOtherCode() { - return operand.isUnmodifiableByOtherCode(); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof UnaryOperation)) { - return false; - } - UnaryOperation other = (UnaryOperation) je; - return operationKind == other.getOperationKind() && operand.syntacticEquals(other.operand); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other) || operand.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return operand.containsModifiableAliasOf(store, other); - } - - @Override - public int hashCode() { - return Objects.hash(operationKind, operand); - } - - @Override - public boolean equals(@Nullable Object other) { - if (!(other instanceof UnaryOperation)) { - return false; - } - UnaryOperation unOp = (UnaryOperation) other; - return operationKind == unOp.getOperationKind() && operand.equals(unOp.operand); - } - - @Override - public String toString() { - String operandString = operand.toString(); - switch (operationKind) { - case BITWISE_COMPLEMENT: - return "~" + operandString; - case LOGICAL_COMPLEMENT: - return "!" + operandString; - case POSTFIX_DECREMENT: - return operandString + "--"; - case POSTFIX_INCREMENT: - return operandString + "++"; - case PREFIX_DECREMENT: - return "--" + operandString; - case PREFIX_INCREMENT: - return "++" + operandString; - case UNARY_MINUS: - return "-" + operandString; - case UNARY_PLUS: - return "+" + operandString; - default: - throw new BugInCF("Unrecognized unary operation kind " + operationKind); - } - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitUnaryOperation(this, p); - } + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitUnaryOperation(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java index 50962765e26..309c1c25683 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java @@ -1,7 +1,7 @@ package org.checkerframework.dataflow.expression; import com.sun.source.tree.Tree; - +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.UsesObjectEquals; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; @@ -9,107 +9,105 @@ import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.type.TypeMirror; - /** Stands for any expression that the Dataflow Framework lacks explicit support for. */ @UsesObjectEquals public class Unknown extends JavaExpression { - /** String representation of the expression that has no corresponding {@code JavaExpression}. */ - private final String originalExpression; - - /** - * Create a new Unknown JavaExpression. - * - * @param type the Java type of this - */ - public Unknown(TypeMirror type) { - this(type, "?"); - } - - /** - * Create a new Unknown JavaExpression. - * - * @param type the Java type of this - * @param originalExpression a String representation of the expression that has no corresponding - * {@code JavaExpression} - */ - public Unknown(TypeMirror type, String originalExpression) { - super(type); - this.originalExpression = originalExpression; - } - - /** - * Create a new Unknown JavaExpression. - * - * @param tree a tree that does not have a corresponding {@code JavaExpression} - */ - public Unknown(Tree tree) { - this(TreeUtils.typeOf(tree), TreeUtils.toStringTruncated(tree, 40)); - } - - /** - * Create a new Unknown JavaExpression. - * - * @param node a node that does not have a corresponding {@code JavaExpression} - */ - public Unknown(Node node) { - this(node.getType(), node.toString()); - } - - @Override - public boolean equals(@Nullable Object obj) { - return obj == this; - } - - // Overridden to avoid an error "overrides equals, but does not override hashCode" - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public String toString() { - return originalExpression; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return false; - } - - @Override - public boolean isUnassignableByOtherCode() { - return false; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return false; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - return this == je; - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return true; - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitUnknown(this, p); - } + /** String representation of the expression that has no corresponding {@code JavaExpression}. */ + private final String originalExpression; + + /** + * Create a new Unknown JavaExpression. + * + * @param type the Java type of this + */ + public Unknown(TypeMirror type) { + this(type, "?"); + } + + /** + * Create a new Unknown JavaExpression. + * + * @param type the Java type of this + * @param originalExpression a String representation of the expression that has no corresponding + * {@code JavaExpression} + */ + public Unknown(TypeMirror type, String originalExpression) { + super(type); + this.originalExpression = originalExpression; + } + + /** + * Create a new Unknown JavaExpression. + * + * @param tree a tree that does not have a corresponding {@code JavaExpression} + */ + public Unknown(Tree tree) { + this(TreeUtils.typeOf(tree), TreeUtils.toStringTruncated(tree, 40)); + } + + /** + * Create a new Unknown JavaExpression. + * + * @param node a node that does not have a corresponding {@code JavaExpression} + */ + public Unknown(Node node) { + this(node.getType(), node.toString()); + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj == this; + } + + // Overridden to avoid an error "overrides equals, but does not override hashCode" + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return originalExpression; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return false; + } + + @Override + public boolean isUnassignableByOtherCode() { + return false; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + return this == je; + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return true; + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitUnknown(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java index 65c90bdb4ad..02940123b8b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java @@ -1,5 +1,9 @@ package org.checkerframework.dataflow.expression; +import java.math.BigInteger; +import java.util.Objects; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.ValueLiteralNode; @@ -7,168 +11,162 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; -import java.math.BigInteger; -import java.util.Objects; - -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** JavaExpression for literals. */ public class ValueLiteral extends JavaExpression { - /** The value of the literal. */ - protected final @Nullable Object value; - - /** The negative of Long.MIN_VALUE, which does not fit in a long. */ - private static final BigInteger NEGATIVE_LONG_MIN_VALUE = new BigInteger("9223372036854775808"); - - /** - * Creates a ValueLiteral from the node with the given type. - * - * @param type type of the literal - * @param node the literal represents by this {@link - * org.checkerframework.dataflow.expression.ValueLiteral} - */ - public ValueLiteral(TypeMirror type, ValueLiteralNode node) { - super(type); - value = node.getValue(); - } - - /** - * Creates a ValueLiteral where the value is {@code value} that has the given type. - * - * @param type type of the literal - * @param value the literal value - */ - public ValueLiteral(TypeMirror type, @Nullable Object value) { - super(type); - this.value = value; - } - - /** - * Returns the negation of this literal. Throws an exception if negation is not possible. - * - * @return the negation of this literal - */ - public ValueLiteral negate() { - if (TypesUtils.isIntegralPrimitive(type)) { - if (value == null) { - throw new BugInCF("null value of integral type " + type); - } - return new ValueLiteral(type, negateBoxedPrimitive(value)); - } - throw new BugInCF(String.format("cannot negate: %s type=%s", this, type)); - } - - /** - * Negate a boxed primitive. - * - * @param o a boxed primitive - * @return a boxed primitive that is the negation of the argument - */ - private Object negateBoxedPrimitive(Object o) { - if (value instanceof Byte) { - return (byte) -(Byte) value; - } - if (value instanceof Short) { - return (short) -(Short) value; - } - if (value instanceof Integer) { - return -(Integer) value; - } - if (value instanceof Long) { - return -(Long) value; - } - if (value instanceof Float) { - return -(Float) value; - } - if (value instanceof Double) { - return -(Double) value; - } - if (value instanceof BigInteger) { - assert value.equals(NEGATIVE_LONG_MIN_VALUE); - return Long.MIN_VALUE; - } - throw new BugInCF("Cannot be negated: " + o + " " + o.getClass()); - } - - /** - * Returns the value of this literal. - * - * @return the value of this literal - */ - public @Nullable Object getValue() { - return value; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return true; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - return this.equals(je); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return false; // not modifiable - } - - /// java.lang.Object methods - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ValueLiteral)) { - return false; - } - ValueLiteral other = (ValueLiteral) obj; - // TODO: Can this string comparison be cleaned up? - // Cannot use Types.isSameType(type, other.type) because we don't have a Types object. - return type.toString().equals(other.type.toString()) && Objects.equals(value, other.value); - } - - @Override - public String toString() { - if (TypesUtils.isString(type)) { - return "\"" + value + "\""; - } else if (type.getKind() == TypeKind.LONG) { - assert value != null : "@AssumeAssertion(nullness): invariant"; - return value.toString() + "L"; - } else if (type.getKind() == TypeKind.CHAR) { - return "\'" + value + "\'"; - } - return value == null ? "null" : value.toString(); - } - - @Override - public int hashCode() { - return Objects.hash(value, type.toString()); - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitValueLiteral(this, p); - } + /** The value of the literal. */ + protected final @Nullable Object value; + + /** The negative of Long.MIN_VALUE, which does not fit in a long. */ + private static final BigInteger NEGATIVE_LONG_MIN_VALUE = new BigInteger("9223372036854775808"); + + /** + * Creates a ValueLiteral from the node with the given type. + * + * @param type type of the literal + * @param node the literal represents by this {@link + * org.checkerframework.dataflow.expression.ValueLiteral} + */ + public ValueLiteral(TypeMirror type, ValueLiteralNode node) { + super(type); + value = node.getValue(); + } + + /** + * Creates a ValueLiteral where the value is {@code value} that has the given type. + * + * @param type type of the literal + * @param value the literal value + */ + public ValueLiteral(TypeMirror type, @Nullable Object value) { + super(type); + this.value = value; + } + + /** + * Returns the negation of this literal. Throws an exception if negation is not possible. + * + * @return the negation of this literal + */ + public ValueLiteral negate() { + if (TypesUtils.isIntegralPrimitive(type)) { + if (value == null) { + throw new BugInCF("null value of integral type " + type); + } + return new ValueLiteral(type, negateBoxedPrimitive(value)); + } + throw new BugInCF(String.format("cannot negate: %s type=%s", this, type)); + } + + /** + * Negate a boxed primitive. + * + * @param o a boxed primitive + * @return a boxed primitive that is the negation of the argument + */ + private Object negateBoxedPrimitive(Object o) { + if (value instanceof Byte) { + return (byte) -(Byte) value; + } + if (value instanceof Short) { + return (short) -(Short) value; + } + if (value instanceof Integer) { + return -(Integer) value; + } + if (value instanceof Long) { + return -(Long) value; + } + if (value instanceof Float) { + return -(Float) value; + } + if (value instanceof Double) { + return -(Double) value; + } + if (value instanceof BigInteger) { + assert value.equals(NEGATIVE_LONG_MIN_VALUE); + return Long.MIN_VALUE; + } + throw new BugInCF("Cannot be negated: " + o + " " + o.getClass()); + } + + /** + * Returns the value of this literal. + * + * @return the value of this literal + */ + public @Nullable Object getValue() { + return value; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + return this.equals(je); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return false; // not modifiable + } + + /// java.lang.Object methods + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ValueLiteral)) { + return false; + } + ValueLiteral other = (ValueLiteral) obj; + // TODO: Can this string comparison be cleaned up? + // Cannot use Types.isSameType(type, other.type) because we don't have a Types object. + return type.toString().equals(other.type.toString()) && Objects.equals(value, other.value); + } + + @Override + public String toString() { + if (TypesUtils.isString(type)) { + return "\"" + value + "\""; + } else if (type.getKind() == TypeKind.LONG) { + assert value != null : "@AssumeAssertion(nullness): invariant"; + return value.toString() + "L"; + } else if (type.getKind() == TypeKind.CHAR) { + return "\'" + value + "\'"; + } + return value == null ? "null" : value.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(value, type.toString()); + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitValueLiteral(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java index 26f129e5948..422a70ea847 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java @@ -1,8 +1,7 @@ package org.checkerframework.dataflow.expression; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; /** * This class has methods to viewpoint-adapt {@link JavaExpression} by replacing {@link @@ -10,95 +9,95 @@ */ public class ViewpointAdaptJavaExpression extends JavaExpressionConverter { - // Public static methods + // Public static methods - /** - * Replace {@link FormalParameter}s by {@code args} in {@code javaExpr}. ({@link ThisReference}s - * are not converted.) - * - * @param javaExpr the expression to viewpoint-adapt - * @param args the expressions that replace {@link FormalParameter}s; if null, {@link - * FormalParameter}s are not replaced - * @return the viewpoint-adapted expression - */ - public static JavaExpression viewpointAdapt( - JavaExpression javaExpr, @Nullable List args) { - return viewpointAdapt(javaExpr, null, args); - } + /** + * Replace {@link FormalParameter}s by {@code args} in {@code javaExpr}. ({@link ThisReference}s + * are not converted.) + * + * @param javaExpr the expression to viewpoint-adapt + * @param args the expressions that replace {@link FormalParameter}s; if null, {@link + * FormalParameter}s are not replaced + * @return the viewpoint-adapted expression + */ + public static JavaExpression viewpointAdapt( + JavaExpression javaExpr, @Nullable List args) { + return viewpointAdapt(javaExpr, null, args); + } - /** - * Replace {@link ThisReference} with {@code thisReference} in {@code javaExpr}. ({@link - * FormalParameter} are not replaced. - * - * @param javaExpr the expression to viewpoint-adapt - * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if - * null, {@link ThisReference}s are not replaced - * @return the viewpoint-adapted expression - */ - public static JavaExpression viewpointAdapt( - JavaExpression javaExpr, @Nullable JavaExpression thisReference) { - return viewpointAdapt(javaExpr, thisReference, null); - } + /** + * Replace {@link ThisReference} with {@code thisReference} in {@code javaExpr}. ({@link + * FormalParameter} are not replaced. + * + * @param javaExpr the expression to viewpoint-adapt + * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if + * null, {@link ThisReference}s are not replaced + * @return the viewpoint-adapted expression + */ + public static JavaExpression viewpointAdapt( + JavaExpression javaExpr, @Nullable JavaExpression thisReference) { + return viewpointAdapt(javaExpr, thisReference, null); + } - /** - * Replace {@link FormalParameter}s with {@code args} and {@link ThisReference} with {@code - * thisReference} in {@code javaExpr}. - * - * @param javaExpr the expression to viewpoint-adapt - * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if - * null, {@link ThisReference}s are not replaced - * @param args the expressions that replaces {@link FormalParameter}s; if null, {@link - * FormalParameter}s are not replaced - * @return the viewpoint-adapted expression - */ - public static JavaExpression viewpointAdapt( - JavaExpression javaExpr, - @Nullable JavaExpression thisReference, - @Nullable List args) { - return new ViewpointAdaptJavaExpression(thisReference, args).convert(javaExpr); - } + /** + * Replace {@link FormalParameter}s with {@code args} and {@link ThisReference} with {@code + * thisReference} in {@code javaExpr}. + * + * @param javaExpr the expression to viewpoint-adapt + * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if + * null, {@link ThisReference}s are not replaced + * @param args the expressions that replaces {@link FormalParameter}s; if null, {@link + * FormalParameter}s are not replaced + * @return the viewpoint-adapted expression + */ + public static JavaExpression viewpointAdapt( + JavaExpression javaExpr, + @Nullable JavaExpression thisReference, + @Nullable List args) { + return new ViewpointAdaptJavaExpression(thisReference, args).convert(javaExpr); + } - // Fields + // Fields - /** List of arguments used to replace occurrences {@link FormalParameter}s. */ - private final @Nullable List args; + /** List of arguments used to replace occurrences {@link FormalParameter}s. */ + private final @Nullable List args; - /** The expression to replace occurrences of {@link ThisReference}s. */ - private final @Nullable JavaExpression thisReference; + /** The expression to replace occurrences of {@link ThisReference}s. */ + private final @Nullable JavaExpression thisReference; - // Instance methods + // Instance methods - /** - * Creates a {@link JavaExpressionConverter} that viewpoint-adapts using the given {@code - * thisReference} and {@code args}. - * - * @param thisReference the expression that replaces occurrences of {@link ThisReference}; - * {@code null} means don't replace - * @param args list of arguments that replaces occurrences {@link FormalParameter}s; {@code - * null} means don't replace - */ - private ViewpointAdaptJavaExpression( - @Nullable JavaExpression thisReference, @Nullable List args) { - this.args = args; - this.thisReference = thisReference; - } + /** + * Creates a {@link JavaExpressionConverter} that viewpoint-adapts using the given {@code + * thisReference} and {@code args}. + * + * @param thisReference the expression that replaces occurrences of {@link ThisReference}; {@code + * null} means don't replace + * @param args list of arguments that replaces occurrences {@link FormalParameter}s; {@code null} + * means don't replace + */ + private ViewpointAdaptJavaExpression( + @Nullable JavaExpression thisReference, @Nullable List args) { + this.args = args; + this.thisReference = thisReference; + } - @Override - protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { - if (thisReference != null) { - return thisReference; - } - return super.visitThisReference(thisExpr, unused); + @Override + protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { + if (thisReference != null) { + return thisReference; } + return super.visitThisReference(thisExpr, unused); + } - @Override - protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { - if (args != null) { - int index = parameterExpr.getIndex() - 1; - if (index < args.size()) { - return args.get(index); - } - } - return super.visitFormalParameter(parameterExpr, unused); + @Override + protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { + if (args != null) { + int index = parameterExpr.getIndex() - 1; + if (index < args.size()) { + return args.get(index); + } } + return super.visitFormalParameter(parameterExpr, unused); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java index c40558fa312..42b28360e11 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java @@ -14,40 +14,40 @@ */ public class LiveVarNode { - /** - * A live variable is represented by a node, which can be a {@link - * org.checkerframework.dataflow.cfg.node.LocalVariableNode} or {@link - * org.checkerframework.dataflow.cfg.node.FieldAccessNode}. - */ - protected final Node liveVariable; + /** + * A live variable is represented by a node, which can be a {@link + * org.checkerframework.dataflow.cfg.node.LocalVariableNode} or {@link + * org.checkerframework.dataflow.cfg.node.FieldAccessNode}. + */ + protected final Node liveVariable; - /** - * Create a new live variable. - * - * @param n a node - */ - public LiveVarNode(Node n) { - assert n instanceof FieldAccessNode || n instanceof LocalVariableNode; - this.liveVariable = n; - } + /** + * Create a new live variable. + * + * @param n a node + */ + public LiveVarNode(Node n) { + assert n instanceof FieldAccessNode || n instanceof LocalVariableNode; + this.liveVariable = n; + } - @Override - public int hashCode() { - return this.liveVariable.hashCode(); - } + @Override + public int hashCode() { + return this.liveVariable.hashCode(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LiveVarNode)) { - return false; - } - LiveVarNode other = (LiveVarNode) obj; - // We use `.equals` instead of `==` here to compare value equality. - return this.liveVariable.equals(other.liveVariable); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LiveVarNode)) { + return false; } + LiveVarNode other = (LiveVarNode) obj; + // We use `.equals` instead of `==` here to compare value equality. + return this.liveVariable.equals(other.liveVariable); + } - @Override - public String toString() { - return this.liveVariable.toString(); - } + @Override + public String toString() { + return this.liveVariable.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java index a85f111c0ec..5c9de01162f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java @@ -1,5 +1,8 @@ package org.checkerframework.dataflow.livevariable; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.BinaryOperationNode; @@ -15,135 +18,131 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.ArraySet; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.StringJoiner; - /** A live variable store contains a set of live variables represented by nodes. */ public class LiveVarStore implements Store { - /** The set of live variables in this store */ - private final Set liveVarNodeSet; - - /** Create a new LiveVarStore. */ - public LiveVarStore() { - liveVarNodeSet = new LinkedHashSet<>(); - } - - /** - * Create a new LiveVarStore. - * - * @param liveVarNodeSet the set of live variable nodes. The parameter is captured and the - * caller should not retain an alias. - */ - public LiveVarStore(Set liveVarNodeSet) { - this.liveVarNodeSet = liveVarNodeSet; + /** The set of live variables in this store */ + private final Set liveVarNodeSet; + + /** Create a new LiveVarStore. */ + public LiveVarStore() { + liveVarNodeSet = new LinkedHashSet<>(); + } + + /** + * Create a new LiveVarStore. + * + * @param liveVarNodeSet the set of live variable nodes. The parameter is captured and the caller + * should not retain an alias. + */ + public LiveVarStore(Set liveVarNodeSet) { + this.liveVarNodeSet = liveVarNodeSet; + } + + /** + * Add the information of a live variable into the live variable set. + * + * @param variable a live variable + */ + public void putLiveVar(LiveVarNode variable) { + liveVarNodeSet.add(variable); + } + + /** + * Remove the information of a live variable from the live variable set. + * + * @param variable a live variable + */ + public void killLiveVar(LiveVarNode variable) { + liveVarNodeSet.remove(variable); + } + + /** + * Add the information of live variables in an expression to the live variable set. + * + * @param expression a node + */ + public void addUseInExpression(Node expression) { + // TODO Do we need a AbstractNodeScanner to do the following job? + if (expression instanceof LocalVariableNode || expression instanceof FieldAccessNode) { + LiveVarNode liveVarValue = new LiveVarNode(expression); + putLiveVar(liveVarValue); + } else if (expression instanceof UnaryOperationNode) { + UnaryOperationNode unaryNode = (UnaryOperationNode) expression; + addUseInExpression(unaryNode.getOperand()); + } else if (expression instanceof TernaryExpressionNode) { + TernaryExpressionNode ternaryNode = (TernaryExpressionNode) expression; + addUseInExpression(ternaryNode.getConditionOperand()); + addUseInExpression(ternaryNode.getThenOperand()); + addUseInExpression(ternaryNode.getElseOperand()); + } else if (expression instanceof TypeCastNode) { + TypeCastNode typeCastNode = (TypeCastNode) expression; + addUseInExpression(typeCastNode.getOperand()); + } else if (expression instanceof InstanceOfNode) { + InstanceOfNode instanceOfNode = (InstanceOfNode) expression; + addUseInExpression(instanceOfNode.getOperand()); + } else if (expression instanceof BinaryOperationNode) { + BinaryOperationNode binaryNode = (BinaryOperationNode) expression; + addUseInExpression(binaryNode.getLeftOperand()); + addUseInExpression(binaryNode.getRightOperand()); } + } - /** - * Add the information of a live variable into the live variable set. - * - * @param variable a live variable - */ - public void putLiveVar(LiveVarNode variable) { - liveVarNodeSet.add(variable); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LiveVarStore)) { + return false; } - - /** - * Remove the information of a live variable from the live variable set. - * - * @param variable a live variable - */ - public void killLiveVar(LiveVarNode variable) { - liveVarNodeSet.remove(variable); + LiveVarStore other = (LiveVarStore) obj; + return other.liveVarNodeSet.equals(this.liveVarNodeSet); + } + + @Override + public int hashCode() { + return this.liveVarNodeSet.hashCode(); + } + + @Override + public LiveVarStore copy() { + return new LiveVarStore(new LinkedHashSet<>(liveVarNodeSet)); + } + + @Override + public LiveVarStore leastUpperBound(LiveVarStore other) { + Set liveVarNodeSetLub = + ArraySet.newArraySetOrLinkedHashSet( + this.liveVarNodeSet.size() + other.liveVarNodeSet.size()); + liveVarNodeSetLub.addAll(this.liveVarNodeSet); + liveVarNodeSetLub.addAll(other.liveVarNodeSet); + return new LiveVarStore(liveVarNodeSetLub); + } + + /** It should not be called since it is not used by the backward analysis. */ + @Override + public LiveVarStore widenedUpperBound(LiveVarStore previous) { + throw new BugInCF("wub of LiveVarStore get called!"); + } + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; + } + + @Override + public String visualize(CFGVisualizer viz) { + String key = "live variables"; + if (liveVarNodeSet.isEmpty()) { + return viz.visualizeStoreKeyVal(key, "none"); } - - /** - * Add the information of live variables in an expression to the live variable set. - * - * @param expression a node - */ - public void addUseInExpression(Node expression) { - // TODO Do we need a AbstractNodeScanner to do the following job? - if (expression instanceof LocalVariableNode || expression instanceof FieldAccessNode) { - LiveVarNode liveVarValue = new LiveVarNode(expression); - putLiveVar(liveVarValue); - } else if (expression instanceof UnaryOperationNode) { - UnaryOperationNode unaryNode = (UnaryOperationNode) expression; - addUseInExpression(unaryNode.getOperand()); - } else if (expression instanceof TernaryExpressionNode) { - TernaryExpressionNode ternaryNode = (TernaryExpressionNode) expression; - addUseInExpression(ternaryNode.getConditionOperand()); - addUseInExpression(ternaryNode.getThenOperand()); - addUseInExpression(ternaryNode.getElseOperand()); - } else if (expression instanceof TypeCastNode) { - TypeCastNode typeCastNode = (TypeCastNode) expression; - addUseInExpression(typeCastNode.getOperand()); - } else if (expression instanceof InstanceOfNode) { - InstanceOfNode instanceOfNode = (InstanceOfNode) expression; - addUseInExpression(instanceOfNode.getOperand()); - } else if (expression instanceof BinaryOperationNode) { - BinaryOperationNode binaryNode = (BinaryOperationNode) expression; - addUseInExpression(binaryNode.getLeftOperand()); - addUseInExpression(binaryNode.getRightOperand()); - } - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LiveVarStore)) { - return false; - } - LiveVarStore other = (LiveVarStore) obj; - return other.liveVarNodeSet.equals(this.liveVarNodeSet); + StringJoiner sjStoreVal = new StringJoiner(", "); + for (LiveVarNode liveVar : liveVarNodeSet) { + sjStoreVal.add(liveVar.toString()); } + return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); + } - @Override - public int hashCode() { - return this.liveVarNodeSet.hashCode(); - } - - @Override - public LiveVarStore copy() { - return new LiveVarStore(new LinkedHashSet<>(liveVarNodeSet)); - } - - @Override - public LiveVarStore leastUpperBound(LiveVarStore other) { - Set liveVarNodeSetLub = - ArraySet.newArraySetOrLinkedHashSet( - this.liveVarNodeSet.size() + other.liveVarNodeSet.size()); - liveVarNodeSetLub.addAll(this.liveVarNodeSet); - liveVarNodeSetLub.addAll(other.liveVarNodeSet); - return new LiveVarStore(liveVarNodeSetLub); - } - - /** It should not be called since it is not used by the backward analysis. */ - @Override - public LiveVarStore widenedUpperBound(LiveVarStore previous) { - throw new BugInCF("wub of LiveVarStore get called!"); - } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; - } - - @Override - public String visualize(CFGVisualizer viz) { - String key = "live variables"; - if (liveVarNodeSet.isEmpty()) { - return viz.visualizeStoreKeyVal(key, "none"); - } - StringJoiner sjStoreVal = new StringJoiner(", "); - for (LiveVarNode liveVar : liveVarNodeSet) { - sjStoreVal.add(liveVar.toString()); - } - return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); - } - - @Override - public String toString() { - return liveVarNodeSet.toString(); - } + @Override + public String toString() { + return liveVarNodeSet.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java index dcc9557fda5..4bee886ffb9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.dataflow.livevariable; +import java.util.List; import org.checkerframework.dataflow.analysis.BackwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -14,95 +15,90 @@ import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.checkerframework.dataflow.qual.SideEffectFree; -import java.util.List; - /** A live variable transfer function. */ public class LiveVarTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements BackwardTransferFunction { + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements BackwardTransferFunction { - /** Creates a new LiveVarTransfer. */ - public LiveVarTransfer() {} + /** Creates a new LiveVarTransfer. */ + public LiveVarTransfer() {} - @Override - @SideEffectFree - public LiveVarStore initialNormalExitStore( - UnderlyingAST underlyingAST, List returnNodes) { - return new LiveVarStore(); - } + @Override + @SideEffectFree + public LiveVarStore initialNormalExitStore( + UnderlyingAST underlyingAST, List returnNodes) { + return new LiveVarStore(); + } - @Override - public LiveVarStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { - return new LiveVarStore(); - } + @Override + public LiveVarStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { + return new LiveVarStore(); + } - @Override - public RegularTransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } + @Override + public RegularTransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } - @Override - public RegularTransferResult visitAssignment( - AssignmentNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitAssignment(n, p); - processLiveVarInAssignment( - n.getTarget(), n.getExpression(), transferResult.getRegularStore()); - return transferResult; - } + @Override + public RegularTransferResult visitAssignment( + AssignmentNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitAssignment(n, p); + processLiveVarInAssignment(n.getTarget(), n.getExpression(), transferResult.getRegularStore()); + return transferResult; + } - @Override - public RegularTransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitMethodInvocation(n, p); - LiveVarStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); - } - return transferResult; + @Override + public RegularTransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitMethodInvocation(n, p); + LiveVarStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); } + return transferResult; + } - @Override - public RegularTransferResult visitObjectCreation( - ObjectCreationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitObjectCreation(n, p); - LiveVarStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); - } - return transferResult; + @Override + public RegularTransferResult visitObjectCreation( + ObjectCreationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitObjectCreation(n, p); + LiveVarStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); } + return transferResult; + } - @Override - public RegularTransferResult visitReturn( - ReturnNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitReturn(n, p); - Node result = n.getResult(); - if (result != null) { - LiveVarStore store = transferResult.getRegularStore(); - store.addUseInExpression(result); - } - return transferResult; + @Override + public RegularTransferResult visitReturn( + ReturnNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitReturn(n, p); + Node result = n.getResult(); + if (result != null) { + LiveVarStore store = transferResult.getRegularStore(); + store.addUseInExpression(result); } + return transferResult; + } - /** - * Update the information of live variables from an assignment statement. - * - * @param variable the variable that should be killed - * @param expression the expression in which the variables should be added - * @param store the live variable store - */ - private void processLiveVarInAssignment(Node variable, Node expression, LiveVarStore store) { - store.killLiveVar(new LiveVarNode(variable)); - store.addUseInExpression(expression); - } + /** + * Update the information of live variables from an assignment statement. + * + * @param variable the variable that should be killed + * @param expression the expression in which the variables should be added + * @param store the live variable store + */ + private void processLiveVarInAssignment(Node variable, Node expression, LiveVarStore store) { + store.killLiveVar(new LiveVarNode(variable)); + store.addUseInExpression(expression); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java index 4488f47ef4f..0b9ec2cb19b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java @@ -12,38 +12,38 @@ */ public class ReachingDefinitionNode { - /** - * A reaching definition is represented by a node, which can only be a {@link - * org.checkerframework.dataflow.cfg.node.AssignmentNode}. - */ - protected final AssignmentNode def; + /** + * A reaching definition is represented by a node, which can only be a {@link + * org.checkerframework.dataflow.cfg.node.AssignmentNode}. + */ + protected final AssignmentNode def; - /** - * Create a new reaching definition. - * - * @param n an assignment node - */ - public ReachingDefinitionNode(AssignmentNode n) { - this.def = n; - } + /** + * Create a new reaching definition. + * + * @param n an assignment node + */ + public ReachingDefinitionNode(AssignmentNode n) { + this.def = n; + } - @Override - public int hashCode() { - return this.def.hashCode(); - } + @Override + public int hashCode() { + return this.def.hashCode(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ReachingDefinitionNode)) { - return false; - } - ReachingDefinitionNode other = (ReachingDefinitionNode) obj; - // We use `.equals` instead of `==` here to compare value equality. - return this.def.equals(other.def); + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ReachingDefinitionNode)) { + return false; } + ReachingDefinitionNode other = (ReachingDefinitionNode) obj; + // We use `.equals` instead of `==` here to compare value equality. + return this.def.equals(other.def); + } - @Override - public String toString() { - return this.def.toString(); - } + @Override + public String toString() { + return this.def.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java index d56d7fc46db..865e8657033 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java @@ -1,5 +1,9 @@ package org.checkerframework.dataflow.reachingdef; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.Node; @@ -7,116 +11,111 @@ import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.javacutil.BugInCF; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.StringJoiner; - /** * A reaching definition store contains a set of reaching definitions represented by * ReachingDefinitionNode */ public class ReachingDefinitionStore implements Store { - /** The set of reaching definitions in this store */ - private final Set reachingDefSet; - - /** Create a new ReachDefinitionStore. */ - public ReachingDefinitionStore() { - reachingDefSet = new LinkedHashSet<>(); - } - - /** - * Create a new ReachDefinitionStore. - * - * @param reachingDefSet a set of reaching definition nodes. The parameter is captured and the - * caller should not retain an alias. - */ - public ReachingDefinitionStore(Set reachingDefSet) { - this.reachingDefSet = reachingDefSet; - } - - /** - * Remove the information of a reaching definition from the reaching definition set. - * - * @param defTarget target of a reaching definition - */ - public void killDef(Node defTarget) { - Iterator it = reachingDefSet.iterator(); - while (it.hasNext()) { - // We use `.equals` instead of `==` here to compare value equality - // rather than reference equality, because if two left-hand side node - // have same values, we need to kill the old one and replace with the - // new one. - ReachingDefinitionNode generatedDefNode = it.next(); - if (generatedDefNode.def.getTarget().equals(defTarget)) { - it.remove(); - } - } + /** The set of reaching definitions in this store */ + private final Set reachingDefSet; + + /** Create a new ReachDefinitionStore. */ + public ReachingDefinitionStore() { + reachingDefSet = new LinkedHashSet<>(); + } + + /** + * Create a new ReachDefinitionStore. + * + * @param reachingDefSet a set of reaching definition nodes. The parameter is captured and the + * caller should not retain an alias. + */ + public ReachingDefinitionStore(Set reachingDefSet) { + this.reachingDefSet = reachingDefSet; + } + + /** + * Remove the information of a reaching definition from the reaching definition set. + * + * @param defTarget target of a reaching definition + */ + public void killDef(Node defTarget) { + Iterator it = reachingDefSet.iterator(); + while (it.hasNext()) { + // We use `.equals` instead of `==` here to compare value equality + // rather than reference equality, because if two left-hand side node + // have same values, we need to kill the old one and replace with the + // new one. + ReachingDefinitionNode generatedDefNode = it.next(); + if (generatedDefNode.def.getTarget().equals(defTarget)) { + it.remove(); + } } - - /** - * Add a reaching definition to the reaching definition set. - * - * @param def a reaching definition - */ - public void putDef(ReachingDefinitionNode def) { - reachingDefSet.add(def); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ReachingDefinitionStore)) { - return false; - } - ReachingDefinitionStore other = (ReachingDefinitionStore) obj; - return other.reachingDefSet.equals(this.reachingDefSet); - } - - @Override - public int hashCode() { - return this.reachingDefSet.hashCode(); - } - - @Override - public ReachingDefinitionStore copy() { - return new ReachingDefinitionStore(new LinkedHashSet<>(reachingDefSet)); + } + + /** + * Add a reaching definition to the reaching definition set. + * + * @param def a reaching definition + */ + public void putDef(ReachingDefinitionNode def) { + reachingDefSet.add(def); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ReachingDefinitionStore)) { + return false; } - - @Override - public ReachingDefinitionStore leastUpperBound(ReachingDefinitionStore other) { - LinkedHashSet reachingDefSetLub = - new LinkedHashSet<>(this.reachingDefSet.size() + other.reachingDefSet.size()); - reachingDefSetLub.addAll(this.reachingDefSet); - reachingDefSetLub.addAll(other.reachingDefSet); - return new ReachingDefinitionStore(reachingDefSetLub); - } - - @Override - public ReachingDefinitionStore widenedUpperBound(ReachingDefinitionStore previous) { - throw new BugInCF("ReachingDefinitionStore.widenedUpperBound was called!"); + ReachingDefinitionStore other = (ReachingDefinitionStore) obj; + return other.reachingDefSet.equals(this.reachingDefSet); + } + + @Override + public int hashCode() { + return this.reachingDefSet.hashCode(); + } + + @Override + public ReachingDefinitionStore copy() { + return new ReachingDefinitionStore(new LinkedHashSet<>(reachingDefSet)); + } + + @Override + public ReachingDefinitionStore leastUpperBound(ReachingDefinitionStore other) { + LinkedHashSet reachingDefSetLub = + new LinkedHashSet<>(this.reachingDefSet.size() + other.reachingDefSet.size()); + reachingDefSetLub.addAll(this.reachingDefSet); + reachingDefSetLub.addAll(other.reachingDefSet); + return new ReachingDefinitionStore(reachingDefSetLub); + } + + @Override + public ReachingDefinitionStore widenedUpperBound(ReachingDefinitionStore previous) { + throw new BugInCF("ReachingDefinitionStore.widenedUpperBound was called!"); + } + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; + } + + @Override + public String visualize(CFGVisualizer viz) { + String key = "reaching definitions"; + if (reachingDefSet.isEmpty()) { + return viz.visualizeStoreKeyVal(key, "none"); } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; + StringJoiner sjStoreVal = new StringJoiner(", ", "{ ", " }"); + for (ReachingDefinitionNode reachDefNode : reachingDefSet) { + sjStoreVal.add(reachDefNode.toString()); } + return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); + } - @Override - public String visualize(CFGVisualizer viz) { - String key = "reaching definitions"; - if (reachingDefSet.isEmpty()) { - return viz.visualizeStoreKeyVal(key, "none"); - } - StringJoiner sjStoreVal = new StringJoiner(", ", "{ ", " }"); - for (ReachingDefinitionNode reachDefNode : reachingDefSet) { - sjStoreVal.add(reachDefNode.toString()); - } - return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); - } - - @Override - public String toString() { - return "ReachingDefinitionStore: " + reachingDefSet.toString(); - } + @Override + public String toString() { + return "ReachingDefinitionStore: " + reachingDefSet.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java index 2ed3b9e0b4f..d4ae76c7554 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java @@ -1,5 +1,6 @@ package org.checkerframework.dataflow.reachingdef; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.ForwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; @@ -12,52 +13,50 @@ import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.dataflow.cfg.node.Node; -import java.util.List; - /** * The reaching definition transfer function. The transfer function processes the * ReachingDefinitionNode in ReachingDefinitionStore, killing the node with same LHS and putting new * generated node into the store. See dataflow manual for more details. */ public class ReachingDefinitionTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements ForwardTransferFunction { - - /** Create a new ReachingDefinitionTransfer. */ - public ReachingDefinitionTransfer() {} - - @Override - public ReachingDefinitionStore initialStore( - UnderlyingAST underlyingAST, @Nullable List parameters) { - return new ReachingDefinitionStore(); - } - - @Override - public RegularTransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } - - @Override - public RegularTransferResult visitAssignment( - AssignmentNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitAssignment(n, p); - processDefinition(n, transferResult.getRegularStore()); - return transferResult; - } - - /** - * Update a reaching definition node in the store from an assignment statement. - * - * @param def the definition that should be put into the store - * @param store the reaching definition store - */ - private void processDefinition(AssignmentNode def, ReachingDefinitionStore store) { - store.killDef(def.getTarget()); - store.putDef(new ReachingDefinitionNode(def)); - } + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements ForwardTransferFunction { + + /** Create a new ReachingDefinitionTransfer. */ + public ReachingDefinitionTransfer() {} + + @Override + public ReachingDefinitionStore initialStore( + UnderlyingAST underlyingAST, @Nullable List parameters) { + return new ReachingDefinitionStore(); + } + + @Override + public RegularTransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } + + @Override + public RegularTransferResult visitAssignment( + AssignmentNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitAssignment(n, p); + processDefinition(n, transferResult.getRegularStore()); + return transferResult; + } + + /** + * Update a reaching definition node in the store from an assignment statement. + * + * @param def the definition that should be put into the store + * @param store the reaching definition store + */ + private void processDefinition(AssignmentNode def, ReachingDefinitionStore store) { + store.killDef(def.getTarget()); + store.putDef(new ReachingDefinitionNode(def)); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java index 81ca9f9970d..7dbf822d065 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java @@ -3,7 +3,9 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; - +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode; import org.checkerframework.dataflow.cfg.node.ConditionalNotNode; import org.checkerframework.dataflow.cfg.node.ConditionalOrNode; @@ -14,94 +16,90 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; - /** A utility class to operate on a given {@link Node}. */ public class NodeUtils { - /** - * Returns true iff {@code node} corresponds to a boolean typed expression (either the primitive - * type {@code boolean}, or class type {@link java.lang.Boolean}). - * - * @return true iff {@code node} corresponds to a boolean typed expression (either the primitive - * type {@code boolean}, or class type {@link java.lang.Boolean}) - */ - public static boolean isBooleanTypeNode(Node node) { - - if (node instanceof ConditionalOrNode) { - return true; - } + /** + * Returns true iff {@code node} corresponds to a boolean typed expression (either the primitive + * type {@code boolean}, or class type {@link java.lang.Boolean}). + * + * @return true iff {@code node} corresponds to a boolean typed expression (either the primitive + * type {@code boolean}, or class type {@link java.lang.Boolean}) + */ + public static boolean isBooleanTypeNode(Node node) { - // not all nodes have an associated tree, but those are all not of a boolean type. - Tree tree = node.getTree(); - if (tree == null) { - return false; - } + if (node instanceof ConditionalOrNode) { + return true; + } - Type type = ((JCTree) tree).type; - if (TypesUtils.isBooleanType(type)) { - return true; - } + // not all nodes have an associated tree, but those are all not of a boolean type. + Tree tree = node.getTree(); + if (tree == null) { + return false; + } - return false; + Type type = ((JCTree) tree).type; + if (TypesUtils.isBooleanType(type)) { + return true; } - /** - * Returns true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's - * length. - * - * @return true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's - * length - */ - public static boolean isArrayLengthFieldAccess(Node node) { - if (!(node instanceof FieldAccessNode)) { - return false; - } - FieldAccessNode fieldAccess = (FieldAccessNode) node; - return fieldAccess.getFieldName().equals("length") - && fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY; + return false; + } + + /** + * Returns true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's + * length. + * + * @return true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's + * length + */ + public static boolean isArrayLengthFieldAccess(Node node) { + if (!(node instanceof FieldAccessNode)) { + return false; } + FieldAccessNode fieldAccess = (FieldAccessNode) node; + return fieldAccess.getFieldName().equals("length") + && fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY; + } - /** Returns true iff {@code node} is an invocation of the given method. */ - public static boolean isMethodInvocation( - Node node, ExecutableElement method, ProcessingEnvironment env) { - if (!(node instanceof MethodInvocationNode)) { - return false; - } - ExecutableElement invoked = ((MethodInvocationNode) node).getTarget().getMethod(); - return ElementUtils.isMethod(invoked, method, env); + /** Returns true iff {@code node} is an invocation of the given method. */ + public static boolean isMethodInvocation( + Node node, ExecutableElement method, ProcessingEnvironment env) { + if (!(node instanceof MethodInvocationNode)) { + return false; } + ExecutableElement invoked = ((MethodInvocationNode) node).getTarget().getMethod(); + return ElementUtils.isMethod(invoked, method, env); + } - /** - * Returns true if the given node statically evaluates to {@code value} and has no side effects. - * - * @param n a node - * @param value the boolean value that the node is tested against - * @return true if the node is equivalent to a literal with value {@code value} - */ - public static boolean isConstantBoolean(Node n, boolean value) { - if (n instanceof BooleanLiteralNode) { - return ((BooleanLiteralNode) n).getValue() == value; - } else if (n instanceof ConditionalNotNode) { - return isConstantBoolean(((ConditionalNotNode) n).getOperand(), !value); - } else { - return false; - } + /** + * Returns true if the given node statically evaluates to {@code value} and has no side effects. + * + * @param n a node + * @param value the boolean value that the node is tested against + * @return true if the node is equivalent to a literal with value {@code value} + */ + public static boolean isConstantBoolean(Node n, boolean value) { + if (n instanceof BooleanLiteralNode) { + return ((BooleanLiteralNode) n).getValue() == value; + } else if (n instanceof ConditionalNotNode) { + return isConstantBoolean(((ConditionalNotNode) n).getOperand(), !value); + } else { + return false; } + } - /** - * Remove any {@link TypeCastNode}s wrapping a node, returning the operand nested within the - * type casts. - * - * @param node a node - * @return node, but with any surrounding typecasts removed - */ - public static Node removeCasts(Node node) { - while (node instanceof TypeCastNode) { - node = ((TypeCastNode) node).getOperand(); - } - return node; + /** + * Remove any {@link TypeCastNode}s wrapping a node, returning the operand nested within the type + * casts. + * + * @param node a node + * @return node, but with any surrounding typecasts removed + */ + public static Node removeCasts(Node node) { + while (node instanceof TypeCastNode) { + node = ((TypeCastNode) node).getOperand(); } + return node; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java index 9933fa0e7f7..e2b3f0d6517 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java @@ -13,7 +13,12 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; - +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; @@ -23,14 +28,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; - /** * A visitor that determines the purity (as defined by {@link * org.checkerframework.dataflow.qual.SideEffectFree}, {@link @@ -44,376 +41,375 @@ */ public class PurityChecker { + /** + * Compute whether the given statement is side-effect-free, deterministic, or both. Returns a + * result that can be queried. + * + * @param statement the statement to check + * @param annoProvider the annotation provider + * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree + * @param assumeDeterministic true if all methods should be assumed to be @Deterministic + * @param assumePureGetters true if all getter methods should be assumed to be @Pure + * @return information about whether the given statement is side-effect-free, deterministic, or + * both + */ + public static PurityResult checkPurity( + TreePath statement, + AnnotationProvider annoProvider, + boolean assumeSideEffectFree, + boolean assumeDeterministic, + boolean assumePureGetters) { + PurityCheckerHelper helper = + new PurityCheckerHelper( + annoProvider, assumeSideEffectFree, assumeDeterministic, assumePureGetters); + helper.scan(statement, null); + return helper.purityResult; + } + + /** + * Result of the {@link PurityChecker}. Can be queried regarding whether a given tree was + * side-effect-free, deterministic, or both; also gives reasons if the answer is "no". + */ + public static class PurityResult { + + /** Reasons that the referenced method is not side-effect-free. */ + protected final List> notSEFreeReasons = new ArrayList<>(1); + + /** Reasons that the referenced method is not deterministic. */ + protected final List> notDetReasons = new ArrayList<>(1); + + /** Reasons that the referenced method is not side-effect-free and deterministic. */ + protected final List> notBothReasons = new ArrayList<>(1); + + /** + * Contains all the varieties of purity that the expression has. Starts out with all varieties, + * and elements are removed from it as violations are found. + */ + protected EnumSet kinds = EnumSet.allOf(Pure.Kind.class); + /** - * Compute whether the given statement is side-effect-free, deterministic, or both. Returns a - * result that can be queried. + * Return the kinds of purity that the method has. * - * @param statement the statement to check - * @param annoProvider the annotation provider - * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree - * @param assumeDeterministic true if all methods should be assumed to be @Deterministic - * @param assumePureGetters true if all getter methods should be assumed to be @Pure - * @return information about whether the given statement is side-effect-free, deterministic, or - * both + * @return the kinds of purity that the method has */ - public static PurityResult checkPurity( - TreePath statement, - AnnotationProvider annoProvider, - boolean assumeSideEffectFree, - boolean assumeDeterministic, - boolean assumePureGetters) { - PurityCheckerHelper helper = - new PurityCheckerHelper( - annoProvider, assumeSideEffectFree, assumeDeterministic, assumePureGetters); - helper.scan(statement, null); - return helper.purityResult; + public EnumSet getKinds() { + return kinds; } /** - * Result of the {@link PurityChecker}. Can be queried regarding whether a given tree was - * side-effect-free, deterministic, or both; also gives reasons if the answer is "no". + * Is the method pure w.r.t. a given set of kinds? + * + * @param otherKinds the varieties of purity to check + * @return true if the method is pure with respect to all the given kinds */ - public static class PurityResult { - - /** Reasons that the referenced method is not side-effect-free. */ - protected final List> notSEFreeReasons = new ArrayList<>(1); - - /** Reasons that the referenced method is not deterministic. */ - protected final List> notDetReasons = new ArrayList<>(1); - - /** Reasons that the referenced method is not side-effect-free and deterministic. */ - protected final List> notBothReasons = new ArrayList<>(1); - - /** - * Contains all the varieties of purity that the expression has. Starts out with all - * varieties, and elements are removed from it as violations are found. - */ - protected EnumSet kinds = EnumSet.allOf(Pure.Kind.class); - - /** - * Return the kinds of purity that the method has. - * - * @return the kinds of purity that the method has - */ - public EnumSet getKinds() { - return kinds; - } - - /** - * Is the method pure w.r.t. a given set of kinds? - * - * @param otherKinds the varieties of purity to check - * @return true if the method is pure with respect to all the given kinds - */ - public boolean isPure(EnumSet otherKinds) { - return kinds.containsAll(otherKinds); - } + public boolean isPure(EnumSet otherKinds) { + return kinds.containsAll(otherKinds); + } - /** - * Get the reasons why the method is not side-effect-free. - * - * @return the reasons why the method is not side-effect-free - */ - public List> getNotSEFreeReasons() { - return notSEFreeReasons; - } + /** + * Get the reasons why the method is not side-effect-free. + * + * @return the reasons why the method is not side-effect-free + */ + public List> getNotSEFreeReasons() { + return notSEFreeReasons; + } - /** - * Add a reason why the method is not side-effect-free. - * - * @param t a tree - * @param msgId why the tree is not side-effect-free - */ - public void addNotSEFreeReason(Tree t, String msgId) { - notSEFreeReasons.add(IPair.of(t, msgId)); - kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); - } + /** + * Add a reason why the method is not side-effect-free. + * + * @param t a tree + * @param msgId why the tree is not side-effect-free + */ + public void addNotSEFreeReason(Tree t, String msgId) { + notSEFreeReasons.add(IPair.of(t, msgId)); + kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); + } - /** - * Get the reasons why the method is not deterministic. - * - * @return the reasons why the method is not deterministic - */ - public List> getNotDetReasons() { - return notDetReasons; - } + /** + * Get the reasons why the method is not deterministic. + * + * @return the reasons why the method is not deterministic + */ + public List> getNotDetReasons() { + return notDetReasons; + } - /** - * Add a reason why the method is not deterministic. - * - * @param t a tree - * @param msgId why the tree is not deterministic - */ - public void addNotDetReason(Tree t, String msgId) { - notDetReasons.add(IPair.of(t, msgId)); - kinds.remove(Pure.Kind.DETERMINISTIC); - } + /** + * Add a reason why the method is not deterministic. + * + * @param t a tree + * @param msgId why the tree is not deterministic + */ + public void addNotDetReason(Tree t, String msgId) { + notDetReasons.add(IPair.of(t, msgId)); + kinds.remove(Pure.Kind.DETERMINISTIC); + } - /** - * Get the reasons why the method is not both side-effect-free and deterministic. - * - * @return the reasons why the method is not both side-effect-free and deterministic - */ - public List> getNotBothReasons() { - return notBothReasons; - } + /** + * Get the reasons why the method is not both side-effect-free and deterministic. + * + * @return the reasons why the method is not both side-effect-free and deterministic + */ + public List> getNotBothReasons() { + return notBothReasons; + } - /** - * Add a reason why the method is not both side-effect-free and deterministic. - * - * @param t tree - * @param msgId why the tree is not deterministic and side-effect-free - */ - public void addNotBothReason(Tree t, String msgId) { - notBothReasons.add(IPair.of(t, msgId)); - kinds.remove(Pure.Kind.DETERMINISTIC); - kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); - } + /** + * Add a reason why the method is not both side-effect-free and deterministic. + * + * @param t tree + * @param msgId why the tree is not deterministic and side-effect-free + */ + public void addNotBothReason(Tree t, String msgId) { + notBothReasons.add(IPair.of(t, msgId)); + kinds.remove(Pure.Kind.DETERMINISTIC); + kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); + } - @Override - public String toString() { - return String.join( - System.lineSeparator(), - "PurityResult{", - " notSEF: " + notSEFreeReasons, - " notDet: " + notDetReasons, - " notBoth: " + notBothReasons, - "}"); - } + @Override + public String toString() { + return String.join( + System.lineSeparator(), + "PurityResult{", + " notSEF: " + notSEFreeReasons, + " notDet: " + notDetReasons, + " notBoth: " + notBothReasons, + "}"); } + } + + // TODO: It would be possible to improve efficiency by visiting fewer nodes. This would require + // overriding more visit* methods. I'm not sure whether such an optimization would be worth it. + + /** + * Helper class to keep {@link PurityChecker}'s interface clean. + * + *

The scanner is run on a single statement, not on a class or method. + */ + protected static class PurityCheckerHelper extends TreePathScanner { - // TODO: It would be possible to improve efficiency by visiting fewer nodes. This would require - // overriding more visit* methods. I'm not sure whether such an optimization would be worth it. + /** The purity result. */ + PurityResult purityResult = new PurityResult(); + + /** The annotation provider (typically an AnnotatedTypeFactory). */ + protected final AnnotationProvider annoProvider; /** - * Helper class to keep {@link PurityChecker}'s interface clean. + * True if all methods should be assumed to be @SideEffectFree, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeSideEffectFree; + + /** + * True if all methods should be assumed to be @Deterministic, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeDeterministic; + + /** + * True if all getter methods should be assumed to be @SideEffectFree and @Deterministic, for + * the purposes of org.checkerframework.dataflow analysis. + */ + private final boolean assumePureGetters; + + /** + * Create a PurityCheckerHelper. * - *

The scanner is run on a single statement, not on a class or method. + * @param annoProvider the annotation provider + * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree + * @param assumeDeterministic true if all methods should be assumed to be @Deterministic + * @param assumePureGetters true if getter methods should be assumed to be @Pure */ - protected static class PurityCheckerHelper extends TreePathScanner { - - /** The purity result. */ - PurityResult purityResult = new PurityResult(); - - /** The annotation provider (typically an AnnotatedTypeFactory). */ - protected final AnnotationProvider annoProvider; - - /** - * True if all methods should be assumed to be @SideEffectFree, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeSideEffectFree; - - /** - * True if all methods should be assumed to be @Deterministic, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeDeterministic; - - /** - * True if all getter methods should be assumed to be @SideEffectFree and @Deterministic, - * for the purposes of org.checkerframework.dataflow analysis. - */ - private final boolean assumePureGetters; - - /** - * Create a PurityCheckerHelper. - * - * @param annoProvider the annotation provider - * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree - * @param assumeDeterministic true if all methods should be assumed to be @Deterministic - * @param assumePureGetters true if getter methods should be assumed to be @Pure - */ - public PurityCheckerHelper( - AnnotationProvider annoProvider, - boolean assumeSideEffectFree, - boolean assumeDeterministic, - boolean assumePureGetters) { - this.annoProvider = annoProvider; - this.assumeSideEffectFree = assumeSideEffectFree; - this.assumeDeterministic = assumeDeterministic; - this.assumePureGetters = assumePureGetters; - } + public PurityCheckerHelper( + AnnotationProvider annoProvider, + boolean assumeSideEffectFree, + boolean assumeDeterministic, + boolean assumePureGetters) { + this.annoProvider = annoProvider; + this.assumeSideEffectFree = assumeSideEffectFree; + this.assumeDeterministic = assumeDeterministic; + this.assumePureGetters = assumePureGetters; + } - @Override - public Void visitCatch(CatchTree tree, Void ignore) { - purityResult.addNotDetReason(tree, "catch"); - return super.visitCatch(tree, ignore); - } + @Override + public Void visitCatch(CatchTree tree, Void ignore) { + purityResult.addNotDetReason(tree, "catch"); + return super.visitCatch(tree, ignore); + } - /** Represents a method that is both deterministic and side-effect free. */ - private static final EnumSet detAndSeFree = - EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void ignore) { - ExecutableElement elt = TreeUtils.elementFromUse(tree); - if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) { - purityResult.addNotBothReason(tree, "call"); - } else { - EnumSet purityKinds = - ((assumeDeterministic && assumeSideEffectFree) - || (assumePureGetters && ElementUtils.isGetter(elt))) - // Avoid computation if not necessary - ? detAndSeFree - : PurityUtils.getPurityKinds(annoProvider, elt); - boolean det = assumeDeterministic || purityKinds.contains(Pure.Kind.DETERMINISTIC); - boolean seFree = - assumeSideEffectFree || purityKinds.contains(Pure.Kind.SIDE_EFFECT_FREE); - if (!det && !seFree) { - purityResult.addNotBothReason(tree, "call"); - } else if (!det) { - purityResult.addNotDetReason(tree, "call"); - } else if (!seFree) { - purityResult.addNotSEFreeReason(tree, "call"); - } - } - return super.visitMethodInvocation(tree, ignore); + /** Represents a method that is both deterministic and side-effect free. */ + private static final EnumSet detAndSeFree = + EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void ignore) { + ExecutableElement elt = TreeUtils.elementFromUse(tree); + if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) { + purityResult.addNotBothReason(tree, "call"); + } else { + EnumSet purityKinds = + ((assumeDeterministic && assumeSideEffectFree) + || (assumePureGetters && ElementUtils.isGetter(elt))) + // Avoid computation if not necessary + ? detAndSeFree + : PurityUtils.getPurityKinds(annoProvider, elt); + boolean det = assumeDeterministic || purityKinds.contains(Pure.Kind.DETERMINISTIC); + boolean seFree = assumeSideEffectFree || purityKinds.contains(Pure.Kind.SIDE_EFFECT_FREE); + if (!det && !seFree) { + purityResult.addNotBothReason(tree, "call"); + } else if (!det) { + purityResult.addNotDetReason(tree, "call"); + } else if (!seFree) { + purityResult.addNotSEFreeReason(tree, "call"); } + } + return super.visitMethodInvocation(tree, ignore); + } - @Override - public Void visitNewClass(NewClassTree tree, Void ignore) { - // Ordinarily, "new MyClass()" is forbidden. It is permitted, however, when it is the - // expression in "throw EXPR;". (In the future, more expressions could be permitted.) - // - // The expression in "throw EXPR;" is allowed to be non-@Deterministic, so long as it is - // not within a catch block that could catch an exception that the statement throws. - // For example, EXPR can be object creation (a "new" expression) or can call a - // non-deterministic method. - // - // Coarse rule (currently implemented): - // * permit only "throw new SomeExpression(args)", where the constructor is - // @SideEffectFree and the args are pure, and forbid all enclosing try statements - // that have a catch clause. - // More precise rule: - // * permit other non-deterministic expresssions within throw (at which time move this - // logic to visitThrow()). - // * the only bad try statements are those with a catch block that is: - // * unchecked exceptions - // * checked = Exception or lower, but excluding RuntimeException and its - // subclasses - // * super- or sub-classes of the type of _expr_ - // * if _expr_ is exactly "new SomeException", this can be changed to just - // "superclasses of SomeException". - // * super- or sub-classes of exceptions declared to be thrown by any component of - // _expr_. - // * need to check every containing try statement, not just the nearest enclosing - // one. - - // Object creation is usually prohibited, but permit "throw new SomeException();" if it - // is not contained within any try statement that has a catch clause. (There is no need - // to check the latter condition, because the Purity Checker forbids all catch - // statements.) - Tree parent = getCurrentPath().getParentPath().getLeaf(); - boolean okThrowDeterministic = parent.getKind() == Tree.Kind.THROW; - - ExecutableElement ctorElement = TreeUtils.elementFromUse(tree); - boolean deterministic = - assumeDeterministic - || okThrowDeterministic - // No need to check assumePureGetters because a constructor is never a - // getter. - || PurityUtils.isDeterministic(annoProvider, ctorElement); - boolean sideEffectFree = - assumeSideEffectFree || PurityUtils.isSideEffectFree(annoProvider, ctorElement); - // This does not use "addNotBothReason" because the reasons are different: one is - // because the constructor is called at all, and the other is because the constuctor is - // not side-effect-free. - if (!deterministic) { - purityResult.addNotDetReason(tree, "object.creation"); - } - if (!sideEffectFree) { - purityResult.addNotSEFreeReason(tree, "call"); - } - - // TODO: if okThrowDeterministic, permit arguments to the newClass to be - // non-deterministic (don't add those to purityResult), but still don't permit them to - // have side effects. This should probably wait until a rewrite of the Purity Checker. - return super.visitNewClass(tree, ignore); - } + @Override + public Void visitNewClass(NewClassTree tree, Void ignore) { + // Ordinarily, "new MyClass()" is forbidden. It is permitted, however, when it is the + // expression in "throw EXPR;". (In the future, more expressions could be permitted.) + // + // The expression in "throw EXPR;" is allowed to be non-@Deterministic, so long as it is + // not within a catch block that could catch an exception that the statement throws. + // For example, EXPR can be object creation (a "new" expression) or can call a + // non-deterministic method. + // + // Coarse rule (currently implemented): + // * permit only "throw new SomeExpression(args)", where the constructor is + // @SideEffectFree and the args are pure, and forbid all enclosing try statements + // that have a catch clause. + // More precise rule: + // * permit other non-deterministic expresssions within throw (at which time move this + // logic to visitThrow()). + // * the only bad try statements are those with a catch block that is: + // * unchecked exceptions + // * checked = Exception or lower, but excluding RuntimeException and its + // subclasses + // * super- or sub-classes of the type of _expr_ + // * if _expr_ is exactly "new SomeException", this can be changed to just + // "superclasses of SomeException". + // * super- or sub-classes of exceptions declared to be thrown by any component of + // _expr_. + // * need to check every containing try statement, not just the nearest enclosing + // one. + + // Object creation is usually prohibited, but permit "throw new SomeException();" if it + // is not contained within any try statement that has a catch clause. (There is no need + // to check the latter condition, because the Purity Checker forbids all catch + // statements.) + Tree parent = getCurrentPath().getParentPath().getLeaf(); + boolean okThrowDeterministic = parent.getKind() == Tree.Kind.THROW; + + ExecutableElement ctorElement = TreeUtils.elementFromUse(tree); + boolean deterministic = + assumeDeterministic + || okThrowDeterministic + // No need to check assumePureGetters because a constructor is never a + // getter. + || PurityUtils.isDeterministic(annoProvider, ctorElement); + boolean sideEffectFree = + assumeSideEffectFree || PurityUtils.isSideEffectFree(annoProvider, ctorElement); + // This does not use "addNotBothReason" because the reasons are different: one is + // because the constructor is called at all, and the other is because the constuctor is + // not side-effect-free. + if (!deterministic) { + purityResult.addNotDetReason(tree, "object.creation"); + } + if (!sideEffectFree) { + purityResult.addNotSEFreeReason(tree, "call"); + } + + // TODO: if okThrowDeterministic, permit arguments to the newClass to be + // non-deterministic (don't add those to purityResult), but still don't permit them to + // have side effects. This should probably wait until a rewrite of the Purity Checker. + return super.visitNewClass(tree, ignore); + } - @Override - public Void visitAssignment(AssignmentTree tree, Void ignore) { - ExpressionTree variable = tree.getVariable(); - assignmentCheck(variable); - return super.visitAssignment(tree, ignore); - } + @Override + public Void visitAssignment(AssignmentTree tree, Void ignore) { + ExpressionTree variable = tree.getVariable(); + assignmentCheck(variable); + return super.visitAssignment(tree, ignore); + } - @Override - public Void visitUnary(UnaryTree tree, Void ignore) { - switch (tree.getKind()) { - case POSTFIX_DECREMENT: - case POSTFIX_INCREMENT: - case PREFIX_DECREMENT: - case PREFIX_INCREMENT: - ExpressionTree expression = tree.getExpression(); - assignmentCheck(expression); - break; - default: - // Nothing to do - break; - } - return super.visitUnary(tree, ignore); - } + @Override + public Void visitUnary(UnaryTree tree, Void ignore) { + switch (tree.getKind()) { + case POSTFIX_DECREMENT: + case POSTFIX_INCREMENT: + case PREFIX_DECREMENT: + case PREFIX_INCREMENT: + ExpressionTree expression = tree.getExpression(); + assignmentCheck(expression); + break; + default: + // Nothing to do + break; + } + return super.visitUnary(tree, ignore); + } - /** - * Check whether {@code variable} is permitted on the left-hand-side of an assignment. - * - * @param variable the lhs to check - */ - protected void assignmentCheck(ExpressionTree variable) { - variable = TreeUtils.withoutParens(variable); - VariableElement fieldElt = TreeUtils.asFieldAccess(variable); - if (fieldElt != null - && isFieldInCurrentClass(fieldElt) - && TreePathUtil.inConstructor(getCurrentPath())) { - // assigning a field in a constructor - // TODO: add a check for ArrayAccessTree too. - return; - } - if (TreeUtils.isFieldAccess(variable)) { - // lhs is a field access - purityResult.addNotBothReason(variable, "assign.field"); - } else if (variable instanceof ArrayAccessTree) { - // lhs is array access - purityResult.addNotBothReason(variable, "assign.array"); - } else { - // lhs is a local variable - assert isLocalVariable(variable); - } - } + /** + * Check whether {@code variable} is permitted on the left-hand-side of an assignment. + * + * @param variable the lhs to check + */ + protected void assignmentCheck(ExpressionTree variable) { + variable = TreeUtils.withoutParens(variable); + VariableElement fieldElt = TreeUtils.asFieldAccess(variable); + if (fieldElt != null + && isFieldInCurrentClass(fieldElt) + && TreePathUtil.inConstructor(getCurrentPath())) { + // assigning a field in a constructor + // TODO: add a check for ArrayAccessTree too. + return; + } + if (TreeUtils.isFieldAccess(variable)) { + // lhs is a field access + purityResult.addNotBothReason(variable, "assign.field"); + } else if (variable instanceof ArrayAccessTree) { + // lhs is array access + purityResult.addNotBothReason(variable, "assign.array"); + } else { + // lhs is a local variable + assert isLocalVariable(variable); + } + } - /** - * Returns true if the given field is defined by the current class. - * - * @param fieldElt a field - * @return true if the given field is defined by the current class - */ - private boolean isFieldInCurrentClass(VariableElement fieldElt) { - ClassTree currentTypeTree = TreePathUtil.enclosingClass(getCurrentPath()); - assert currentTypeTree != null : "@AssumeAssertion(nullness)"; - TypeElement currentType = TreeUtils.elementFromDeclaration(currentTypeTree); - assert currentType != null : "@AssumeAssertion(nullness)"; - TypeElement definesField = ElementUtils.enclosingTypeElement(fieldElt); - assert definesField != null : "@AssumeAssertion(nullness)"; - return currentType.equals(definesField); - } + /** + * Returns true if the given field is defined by the current class. + * + * @param fieldElt a field + * @return true if the given field is defined by the current class + */ + private boolean isFieldInCurrentClass(VariableElement fieldElt) { + ClassTree currentTypeTree = TreePathUtil.enclosingClass(getCurrentPath()); + assert currentTypeTree != null : "@AssumeAssertion(nullness)"; + TypeElement currentType = TreeUtils.elementFromDeclaration(currentTypeTree); + assert currentType != null : "@AssumeAssertion(nullness)"; + TypeElement definesField = ElementUtils.enclosingTypeElement(fieldElt); + assert definesField != null : "@AssumeAssertion(nullness)"; + return currentType.equals(definesField); + } - /** - * Checks if the argument is a local variable. - * - * @param variable the tree to check - * @return true if the argument is a local variable - */ - protected boolean isLocalVariable(ExpressionTree variable) { - return variable instanceof IdentifierTree && !TreeUtils.isFieldAccess(variable); - } + /** + * Checks if the argument is a local variable. + * + * @param variable the tree to check + * @return true if the argument is a local variable + */ + protected boolean isLocalVariable(ExpressionTree variable) { + return variable instanceof IdentifierTree && !TreeUtils.isFieldAccess(variable); + } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void ignore) { - ExpressionTree variable = tree.getVariable(); - assignmentCheck(variable); - return super.visitCompoundAssignment(tree, ignore); - } + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void ignore) { + ExpressionTree variable = tree.getVariable(); + assignmentCheck(variable); + return super.visitCompoundAssignment(tree, ignore); } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index ce61c39c76c..9af408c0bd0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -1,7 +1,9 @@ package org.checkerframework.dataflow.util; import com.sun.source.tree.MethodTree; - +import java.util.EnumSet; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; @@ -9,11 +11,6 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.EnumSet; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; - /** * A utility class for working with the {@link SideEffectFree}, {@link Deterministic}, and {@link * Pure} annotations. @@ -24,126 +21,125 @@ */ public class PurityUtils { - /** Do not instantiate. */ - private PurityUtils() { - throw new Error("Do not instantiate PurityUtils."); + /** Do not instantiate. */ + private PurityUtils() { + throw new Error("Do not instantiate PurityUtils."); + } + + /** Represents a method that is both deterministic and side-effect free. */ + private static final EnumSet detAndSeFree = + EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); + + /** + * Does the method {@code methodTree} have any purity annotation? + * + * @param provider how to get annotations + * @param methodTree a method to test + * @return whether the method has any purity annotations + */ + public static boolean hasPurityAnnotation(AnnotationProvider provider, MethodTree methodTree) { + return !getPurityKinds(provider, methodTree).isEmpty(); + } + + /** + * Does the method {@code methodElement} have any purity annotation? + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return whether the method has any purity annotations + */ + public static boolean hasPurityAnnotation( + AnnotationProvider provider, ExecutableElement methodElement) { + return !getPurityKinds(provider, methodElement).isEmpty(); + } + + /** + * Is the method {@code methodTree} deterministic? + * + * @param provider how to get annotations + * @param methodTree a method to test + * @return whether the method is deterministic + */ + public static boolean isDeterministic(AnnotationProvider provider, MethodTree methodTree) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); + return isDeterministic(provider, methodElement); + } + + /** + * Is the method {@code methodElement} deterministic? + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return whether the method is deterministic + */ + public static boolean isDeterministic( + AnnotationProvider provider, ExecutableElement methodElement) { + EnumSet kinds = getPurityKinds(provider, methodElement); + return kinds.contains(Pure.Kind.DETERMINISTIC); + } + + /** + * Is the method {@code methodElement} side-effect-free? + * + *

This method does not use, and has different semantics than, {@link + * AnnotationProvider#isSideEffectFree}. This method is concerned only with standard purity + * annotations. + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return whether the method is side-effect-free + */ + public static boolean isSideEffectFree( + AnnotationProvider provider, ExecutableElement methodElement) { + EnumSet kinds = getPurityKinds(provider, methodElement); + return kinds.contains(Pure.Kind.SIDE_EFFECT_FREE); + } + + /** + * Returns the purity annotations on the method {@code methodTree}. + * + * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and + * {@link AnnotationProvider#isDeterministic} methods are not used. + * @param methodTree a method to test + * @return the types of purity of the method {@code methodTree} + */ + public static EnumSet getPurityKinds( + AnnotationProvider provider, MethodTree methodTree) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); + return getPurityKinds(provider, methodElement); + } + + /** + * Returns the purity annotations on the method {@code methodElement}. + * + * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and + * {@link AnnotationProvider#isDeterministic} methods are not used. + * @param methodElement a method to test + * @return the types of purity of the method {@code methodElement} + */ + public static EnumSet getPurityKinds( + AnnotationProvider provider, ExecutableElement methodElement) { + // Special case for record accessors + if (ElementUtils.isRecordAccessor(methodElement) + && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { + return detAndSeFree; } - /** Represents a method that is both deterministic and side-effect free. */ - private static final EnumSet detAndSeFree = - EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); + AnnotationMirror pureAnnotation = provider.getDeclAnnotation(methodElement, Pure.class); + AnnotationMirror sefAnnotation = + provider.getDeclAnnotation(methodElement, SideEffectFree.class); + AnnotationMirror detAnnotation = provider.getDeclAnnotation(methodElement, Deterministic.class); - /** - * Does the method {@code methodTree} have any purity annotation? - * - * @param provider how to get annotations - * @param methodTree a method to test - * @return whether the method has any purity annotations - */ - public static boolean hasPurityAnnotation(AnnotationProvider provider, MethodTree methodTree) { - return !getPurityKinds(provider, methodTree).isEmpty(); + if (pureAnnotation != null) { + return detAndSeFree; } - - /** - * Does the method {@code methodElement} have any purity annotation? - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return whether the method has any purity annotations - */ - public static boolean hasPurityAnnotation( - AnnotationProvider provider, ExecutableElement methodElement) { - return !getPurityKinds(provider, methodElement).isEmpty(); + EnumSet result = EnumSet.noneOf(Pure.Kind.class); + if (sefAnnotation != null) { + result.add(Pure.Kind.SIDE_EFFECT_FREE); } - - /** - * Is the method {@code methodTree} deterministic? - * - * @param provider how to get annotations - * @param methodTree a method to test - * @return whether the method is deterministic - */ - public static boolean isDeterministic(AnnotationProvider provider, MethodTree methodTree) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); - return isDeterministic(provider, methodElement); - } - - /** - * Is the method {@code methodElement} deterministic? - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return whether the method is deterministic - */ - public static boolean isDeterministic( - AnnotationProvider provider, ExecutableElement methodElement) { - EnumSet kinds = getPurityKinds(provider, methodElement); - return kinds.contains(Pure.Kind.DETERMINISTIC); - } - - /** - * Is the method {@code methodElement} side-effect-free? - * - *

This method does not use, and has different semantics than, {@link - * AnnotationProvider#isSideEffectFree}. This method is concerned only with standard purity - * annotations. - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return whether the method is side-effect-free - */ - public static boolean isSideEffectFree( - AnnotationProvider provider, ExecutableElement methodElement) { - EnumSet kinds = getPurityKinds(provider, methodElement); - return kinds.contains(Pure.Kind.SIDE_EFFECT_FREE); - } - - /** - * Returns the purity annotations on the method {@code methodTree}. - * - * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and - * {@link AnnotationProvider#isDeterministic} methods are not used. - * @param methodTree a method to test - * @return the types of purity of the method {@code methodTree} - */ - public static EnumSet getPurityKinds( - AnnotationProvider provider, MethodTree methodTree) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); - return getPurityKinds(provider, methodElement); - } - - /** - * Returns the purity annotations on the method {@code methodElement}. - * - * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and - * {@link AnnotationProvider#isDeterministic} methods are not used. - * @param methodElement a method to test - * @return the types of purity of the method {@code methodElement} - */ - public static EnumSet getPurityKinds( - AnnotationProvider provider, ExecutableElement methodElement) { - // Special case for record accessors - if (ElementUtils.isRecordAccessor(methodElement) - && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { - return detAndSeFree; - } - - AnnotationMirror pureAnnotation = provider.getDeclAnnotation(methodElement, Pure.class); - AnnotationMirror sefAnnotation = - provider.getDeclAnnotation(methodElement, SideEffectFree.class); - AnnotationMirror detAnnotation = - provider.getDeclAnnotation(methodElement, Deterministic.class); - - if (pureAnnotation != null) { - return detAndSeFree; - } - EnumSet result = EnumSet.noneOf(Pure.Kind.class); - if (sefAnnotation != null) { - result.add(Pure.Kind.SIDE_EFFECT_FREE); - } - if (detAnnotation != null) { - result.add(Pure.Kind.DETERMINISTIC); - } - return result; + if (detAnnotation != null) { + result.add(Pure.Kind.DETERMINISTIC); } + return result; + } } diff --git a/dataflow/src/test/java/busyexpr/BusyExpression.java b/dataflow/src/test/java/busyexpr/BusyExpression.java index 00587aec969..619ec2dd1ad 100644 --- a/dataflow/src/test/java/busyexpr/BusyExpression.java +++ b/dataflow/src/test/java/busyexpr/BusyExpression.java @@ -9,22 +9,21 @@ /** Used in busyExpressionTest Gradle task to test the BusyExpression analysis. */ public class BusyExpression { - /** - * The main method expects to be run in dataflow/tests/busy-expression directory. - * - * @param args not used - */ - public static void main(String[] args) { + /** + * The main method expects to be run in dataflow/tests/busy-expression directory. + * + * @param args not used + */ + public static void main(String[] args) { - String inputFile = "Test.java"; // input file name; - String method = "test"; - String clazz = "Test"; - String outputFile = "Out.txt"; + String inputFile = "Test.java"; // input file name; + String method = "test"; + String clazz = "Test"; + String outputFile = "Out.txt"; - BusyExprTransfer transfer = new BusyExprTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG( - inputFile, method, clazz, outputFile, backwardAnalysis); - } + BusyExprTransfer transfer = new BusyExprTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clazz, outputFile, backwardAnalysis); + } } diff --git a/dataflow/src/test/java/cfgconstruction/CFGConstruction.java b/dataflow/src/test/java/cfgconstruction/CFGConstruction.java index e59df6a1339..ac598e6f86c 100644 --- a/dataflow/src/test/java/cfgconstruction/CFGConstruction.java +++ b/dataflow/src/test/java/cfgconstruction/CFGConstruction.java @@ -5,12 +5,12 @@ public class CFGConstruction { - public static void main(String[] args) { - String inputFile = "Test.java"; - String clazz = "Test"; - String method = "manyNestedTryFinallyBlocks"; + public static void main(String[] args) { + String inputFile = "Test.java"; + String clazz = "Test"; + String method = "manyNestedTryFinallyBlocks"; - ControlFlowGraph cfg = CFGVisualizeLauncher.generateMethodCFG(inputFile, clazz, method); - cfg.checkInvariants(); - } + ControlFlowGraph cfg = CFGVisualizeLauncher.generateMethodCFG(inputFile, clazz, method); + cfg.checkInvariants(); + } } diff --git a/dataflow/src/test/java/constantpropagation/ConstantPropagation.java b/dataflow/src/test/java/constantpropagation/ConstantPropagation.java index a83e8c37474..02e8fc25aab 100644 --- a/dataflow/src/test/java/constantpropagation/ConstantPropagation.java +++ b/dataflow/src/test/java/constantpropagation/ConstantPropagation.java @@ -9,21 +9,21 @@ public class ConstantPropagation { - /** - * The main method expects to be run in dataflow/tests/constant-propagation directory. - * - * @param args not used - */ - public static void main(String[] args) { + /** + * The main method expects to be run in dataflow/tests/constant-propagation directory. + * + * @param args not used + */ + public static void main(String[] args) { - String inputFile = "Test.java"; - String method = "test"; - String clas = "Test"; - String outputFile = "Out.txt"; + String inputFile = "Test.java"; + String method = "test"; + String clas = "Test"; + String outputFile = "Out.txt"; - ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); - } + ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); + } } diff --git a/dataflow/src/test/java/livevar/LiveVariable.java b/dataflow/src/test/java/livevar/LiveVariable.java index e30939fa213..2364829557c 100644 --- a/dataflow/src/test/java/livevar/LiveVariable.java +++ b/dataflow/src/test/java/livevar/LiveVariable.java @@ -10,21 +10,20 @@ /** Used in liveVariableTest Gradle task to test the LiveVariable analysis. */ public class LiveVariable { - /** - * The main method expects to be run in dataflow/tests/live-variable directory. - * - * @param args not used - */ - public static void main(String[] args) { - String inputFile = "Test.java"; - String method = "test"; - String clas = "Test"; - String outputFile = "Out.txt"; + /** + * The main method expects to be run in dataflow/tests/live-variable directory. + * + * @param args not used + */ + public static void main(String[] args) { + String inputFile = "Test.java"; + String method = "test"; + String clas = "Test"; + String outputFile = "Out.txt"; - LiveVarTransfer transfer = new LiveVarTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG( - inputFile, method, clas, outputFile, backwardAnalysis); - } + LiveVarTransfer transfer = new LiveVarTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, backwardAnalysis); + } } diff --git a/dataflow/src/test/java/reachingdef/ReachingDefinition.java b/dataflow/src/test/java/reachingdef/ReachingDefinition.java index d55ce717c64..23a30c41771 100644 --- a/dataflow/src/test/java/reachingdef/ReachingDefinition.java +++ b/dataflow/src/test/java/reachingdef/ReachingDefinition.java @@ -10,21 +10,21 @@ /** Used in reachingDefinitionsTest Gradle task to test the ReachingDefinition analysis. */ public class ReachingDefinition { - /** - * The main method expects to be run in dataflow/tests/reaching-definitions directory. - * - * @param args not used - */ - public static void main(String[] args) { + /** + * The main method expects to be run in dataflow/tests/reaching-definitions directory. + * + * @param args not used + */ + public static void main(String[] args) { - String inputFile = "Test.java"; - String method = "test"; - String clas = "Test"; - String outputFile = "Out.txt"; + String inputFile = "Test.java"; + String method = "test"; + String clas = "Test"; + String outputFile = "Out.txt"; - ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); - } + ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); + } } diff --git a/dataflow/tests/busyexpr/Test.java b/dataflow/tests/busyexpr/Test.java index 4e93b45962a..55dab9065a0 100644 --- a/dataflow/tests/busyexpr/Test.java +++ b/dataflow/tests/busyexpr/Test.java @@ -1,25 +1,25 @@ class Test { - Test(int x) {} + Test(int x) {} - public int test(int m) { - int a = 2, b = 3, x = 1, y; - if (a != b) { - x = b >> a; - new Test(a - b); // test object creation - y = a + b; - } else { - y = b >> a; - a = 0; - test(a - b); // test method invocation - } + public int test(int m) { + int a = 2, b = 3, x = 1, y; + if (a != b) { + x = b >> a; + new Test(a - b); // test object creation + y = a + b; + } else { + y = b >> a; + a = 0; + test(a - b); // test method invocation + } - // test exceptional exit block - int d; - try { - d = y / x; - } catch (ArithmeticException e) { - d = 10000000; - } - return d; + // test exceptional exit block + int d; + try { + d = y / x; + } catch (ArithmeticException e) { + d = 10000000; } + return d; + } } diff --git a/dataflow/tests/cfgconstruction/Test.java b/dataflow/tests/cfgconstruction/Test.java index 998fa64de4d..1d94da0c189 100644 --- a/dataflow/tests/cfgconstruction/Test.java +++ b/dataflow/tests/cfgconstruction/Test.java @@ -1,26 +1,26 @@ public class Test { - public void manyNestedTryFinallyBlocks() { + public void manyNestedTryFinallyBlocks() { + try { + System.out.println("!"); + } finally { + try { + System.out.println("!"); + } finally { try { - System.out.println("!"); + System.out.println("!"); } finally { + try { + System.out.println("!"); + } finally { try { - System.out.println("!"); + System.out.println("!"); } finally { - try { - System.out.println("!"); - } finally { - try { - System.out.println("!"); - } finally { - try { - System.out.println("!"); - } finally { - System.out.println("!"); - } - } - } + System.out.println("!"); } + } } + } } + } } diff --git a/dataflow/tests/constant-propagation/Test.java b/dataflow/tests/constant-propagation/Test.java index 62cb612e315..51e1159db35 100644 --- a/dataflow/tests/constant-propagation/Test.java +++ b/dataflow/tests/constant-propagation/Test.java @@ -1,11 +1,11 @@ public class Test { - public int test() { - int a = 0, b; - if (a > 5) { - b = a; - } else { - b = 4; - } - return b; + public int test() { + int a = 0, b; + if (a > 5) { + b = a; + } else { + b = 4; } + return b; + } } diff --git a/dataflow/tests/issue3447/Test.java b/dataflow/tests/issue3447/Test.java index 563d01a1d62..2ff93c9aeda 100644 --- a/dataflow/tests/issue3447/Test.java +++ b/dataflow/tests/issue3447/Test.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/3447 public class Test { - public void test() throws Exception { - try { - int[] myNumbers = {1}; - System.out.println(myNumbers[1]); - } catch (Exception e) { - } + public void test() throws Exception { + try { + int[] myNumbers = {1}; + System.out.println(myNumbers[1]); + } catch (Exception e) { } + } } diff --git a/dataflow/tests/live-variable/Test.java b/dataflow/tests/live-variable/Test.java index db2c8e4b9f3..0248330e8c5 100644 --- a/dataflow/tests/live-variable/Test.java +++ b/dataflow/tests/live-variable/Test.java @@ -1,19 +1,19 @@ // This file may not be renamed; it has to have the same filename as ../issue3447/Test.java . public class Test { - public int test() { - int a = 1, b = 2, c = 3; - if (a > 0) { - int d = a + c; - } else { - int e = a + b; - } - int f; - b = 0; - try { - f = 1 / a; - } catch (ArithmeticException e) { - f = b; - } - return a; + public int test() { + int a = 1, b = 2, c = 3; + if (a > 0) { + int d = a + c; + } else { + int e = a + b; } + int f; + b = 0; + try { + f = 1 / a; + } catch (ArithmeticException e) { + f = b; + } + return a; + } } diff --git a/dataflow/tests/reachingdef/Test.java b/dataflow/tests/reachingdef/Test.java index ea71b2fc492..d0e145c8f5f 100644 --- a/dataflow/tests/reachingdef/Test.java +++ b/dataflow/tests/reachingdef/Test.java @@ -1,15 +1,15 @@ public class Test { - public int test() { - int a = 1, b = 2, c = 3; - String x = "a", y = "b"; - if (a > 0) { - int d = a + c; - } else { - int e = a + b; - } - b = 0; - a = b; - x += y; - return a; + public int test() { + int a = 1, b = 2, c = 3; + String x = "a", y = "b"; + if (a > 0) { + int d = a + c; + } else { + int e = a + b; } + b = 0; + a = b; + x += y; + return a; + } } diff --git a/docs/examples/BazelExample/BazelExample.java b/docs/examples/BazelExample/BazelExample.java index 5e94c8b0e9d..5206e41fa11 100644 --- a/docs/examples/BazelExample/BazelExample.java +++ b/docs/examples/BazelExample/BazelExample.java @@ -1,10 +1,9 @@ +import java.util.HashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.HashMap; -import java.util.Map; - /** * If you run: * @@ -16,16 +15,16 @@ */ public class BazelExample { - public static @Nullable Object nullable = null; - public Map map = new HashMap<>(); + public static @Nullable Object nullable = null; + public Map map = new HashMap<>(); - public static void main(String[] args) { - System.out.println("Hello World!"); + public static void main(String[] args) { + System.out.println("Hello World!"); - @NonNull Object nn = null; // error on this line - System.out.println(nn.hashCode()); // NPE - } + @NonNull Object nn = null; // error on this line + System.out.println(nn.hashCode()); // NPE + } - // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. - void mapTest(@KeyFor("map") Object k) {} + // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. + void mapTest(@KeyFor("map") Object k) {} } diff --git a/docs/examples/InterningExample.java b/docs/examples/InterningExample.java index 5122db82dc3..64d73628859 100644 --- a/docs/examples/InterningExample.java +++ b/docs/examples/InterningExample.java @@ -9,16 +9,16 @@ */ public class InterningExample { - public void example() { + public void example() { - // These type annotations are redundant -- the Interning Checker will - // infer them, but they are written here in the example for emhpasis. - // In general, you do not have to annotate local variables. - @Interned String foo = "foo"; - @Interned String bar = "bar"; + // These type annotations are redundant -- the Interning Checker will + // infer them, but they are written here in the example for emhpasis. + // In general, you do not have to annotate local variables. + @Interned String foo = "foo"; + @Interned String bar = "bar"; - if (foo == bar) { - System.out.println("foo == bar"); - } + if (foo == bar) { + System.out.println("foo == bar"); } + } } diff --git a/docs/examples/InterningExampleWithWarnings.java b/docs/examples/InterningExampleWithWarnings.java index 174f6559ed5..5e68e1f73b3 100644 --- a/docs/examples/InterningExampleWithWarnings.java +++ b/docs/examples/InterningExampleWithWarnings.java @@ -10,16 +10,16 @@ */ public class InterningExampleWithWarnings { - public void example() { + public void example() { - // This type annotation is redundant -- the Interning Checker will - // infer it, but it is written here in the example for emhpasis. - // In general, you do not have to annotate local variables. - @Interned String foo = "foo"; - String bar = new String("bar"); + // This type annotation is redundant -- the Interning Checker will + // infer it, but it is written here in the example for emhpasis. + // In general, you do not have to annotate local variables. + @Interned String foo = "foo"; + String bar = new String("bar"); - if (foo == bar) { - System.out.println("foo == bar"); - } + if (foo == bar) { + System.out.println("foo == bar"); } + } } diff --git a/docs/examples/LockExample.java b/docs/examples/LockExample.java index a5765e27295..a74f4e13a95 100644 --- a/docs/examples/LockExample.java +++ b/docs/examples/LockExample.java @@ -1,70 +1,70 @@ import org.checkerframework.checker.lock.qual.*; class BankAccount { - int balance; + int balance; - void withdraw(@GuardSatisfied BankAccount this, int amount) { - this.balance = this.balance - amount; - } + void withdraw(@GuardSatisfied BankAccount this, int amount) { + this.balance = this.balance - amount; + } - void deposit(@GuardedBy("") BankAccount this, int amount) { - synchronized (this) { - this.balance = this.balance + amount; - } + void deposit(@GuardedBy("") BankAccount this, int amount) { + synchronized (this) { + this.balance = this.balance + amount; } + } } public class LockExample { - final @GuardedBy("") BankAccount myAccount; + final @GuardedBy("") BankAccount myAccount; - LockExample(@GuardedBy("") BankAccount in) { - this.myAccount = in; - } + LockExample(@GuardedBy("") BankAccount in) { + this.myAccount = in; + } - void demo1() { - myAccount.withdraw(100); // error! + void demo1() { + myAccount.withdraw(100); // error! - synchronized (myAccount) { - myAccount.withdraw(100); // OK - } + synchronized (myAccount) { + myAccount.withdraw(100); // OK } + } - @Holding("myAccount") - void demo1b() { - myAccount.withdraw(100); // OK - } + @Holding("myAccount") + void demo1b() { + myAccount.withdraw(100); // OK + } - void demo1c() { - demo1b(); // error! + void demo1c() { + demo1b(); // error! - synchronized (myAccount) { - demo1b(); - } + synchronized (myAccount) { + demo1b(); } + } - void demo2() { - myAccount.deposit(500); // OK - } + void demo2() { + myAccount.deposit(500); // OK + } - void demo3(Object someotherlock, @GuardedBy("someotherlock") BankAccount otherAccount) { - otherAccount.deposit(500); // error! - } + void demo3(Object someotherlock, @GuardedBy("someotherlock") BankAccount otherAccount) { + otherAccount.deposit(500); // error! + } - void demo3b(Object someotherlock, @GuardedBy("#1") BankAccount otherAccount) { - synchronized (someotherlock) { - otherAccount.deposit(500); // error! - } + void demo3b(Object someotherlock, @GuardedBy("#1") BankAccount otherAccount) { + synchronized (someotherlock) { + otherAccount.deposit(500); // error! } + } - void demo4() { - BankAccount spouseAccount = myAccount; // OK - spouseAccount.deposit(500); // OK + void demo4() { + BankAccount spouseAccount = myAccount; // OK + spouseAccount.deposit(500); // OK - synchronized (myAccount) { - spouseAccount.withdraw(100); // error! - } - synchronized (spouseAccount) { - spouseAccount.withdraw(200); // OK - } + synchronized (myAccount) { + spouseAccount.withdraw(100); // error! + } + synchronized (spouseAccount) { + spouseAccount.withdraw(200); // OK } + } } diff --git a/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java b/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java index 257e6136434..e7c50e7787d 100644 --- a/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java +++ b/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java @@ -14,14 +14,14 @@ */ public class MavenExample { - public static @IntVal(5) int five = 5; + public static @IntVal(5) int five = 5; - public static void main(final String[] args) { - System.out.println("Hello World!"); + public static void main(final String[] args) { + System.out.println("Hello World!"); - StrBuilder stb = new StrBuilder(); + StrBuilder stb = new StrBuilder(); - @IntVal(55) int l = five; // error on this line - System.out.println(l); - } + @IntVal(55) int l = five; // error on this line + System.out.println(l); + } } diff --git a/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java b/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java index 37ce755b12f..e5bc843ebde 100644 --- a/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java +++ b/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java @@ -1,13 +1,12 @@ package org.checkerframework.example; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.lang3.text.StrBuilder; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.HashMap; -import java.util.Map; - /** * If you run: * @@ -19,18 +18,18 @@ */ public class MavenExample { - public static @Nullable Object nullable = null; - public Map map = new HashMap<>(); + public static @Nullable Object nullable = null; + public Map map = new HashMap<>(); - public static void main(String[] args) { - System.out.println("Hello World!"); + public static void main(String[] args) { + System.out.println("Hello World!"); - StrBuilder stb = new StrBuilder(); + StrBuilder stb = new StrBuilder(); - @NonNull Object nn = nullable; // error on this line - System.out.println(nn); - } + @NonNull Object nn = nullable; // error on this line + System.out.println(nn); + } - // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. - void mapTest(@KeyFor("map") Object k) {} + // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. + void mapTest(@KeyFor("map") Object k) {} } diff --git a/docs/examples/NullnessExample.java b/docs/examples/NullnessExample.java index 16891bacc40..961695419cc 100644 --- a/docs/examples/NullnessExample.java +++ b/docs/examples/NullnessExample.java @@ -1,7 +1,6 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.LinkedList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; /** * This class illustrates use of nullness type annotations. The class doesn't do anything -- it is @@ -13,27 +12,27 @@ */ public class NullnessExample { - public void example() { + public void example() { - // In general, you do not have to annotate local variables, because the - // Nullness Checker infers such annotations. It is written here in the - // example for emhpasis. - @NonNull String foo = "foo"; - @NonNull String bar = "bar"; + // In general, you do not have to annotate local variables, because the + // Nullness Checker infers such annotations. It is written here in the + // example for emhpasis. + @NonNull String foo = "foo"; + @NonNull String bar = "bar"; - foo = bar; - bar = foo; - } + foo = bar; + bar = foo; + } - public @NonNull String exampleGenerics() { + public @NonNull String exampleGenerics() { - List<@NonNull String> foo = new LinkedList<@NonNull String>(); - List<@NonNull String> bar = foo; + List<@NonNull String> foo = new LinkedList<@NonNull String>(); + List<@NonNull String> bar = foo; - @NonNull String quux = "quux"; - foo.add(quux); - foo.add("quux"); - @NonNull String baz = foo.get(0); - return baz; - } + @NonNull String quux = "quux"; + foo.add(quux); + foo.add("quux"); + @NonNull String baz = foo.get(0); + return baz; + } } diff --git a/docs/examples/NullnessExampleWithWarnings.java b/docs/examples/NullnessExampleWithWarnings.java index be28113d0a2..b0c684ce885 100644 --- a/docs/examples/NullnessExampleWithWarnings.java +++ b/docs/examples/NullnessExampleWithWarnings.java @@ -1,7 +1,6 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.LinkedList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; /** * This class illustrates use of nullness type annotations. The class doesn't do anything -- it is @@ -13,27 +12,27 @@ */ public class NullnessExampleWithWarnings { - public void example() { + public void example() { - // In general, you do not have to annotate local variables, because the - // Nullness Checker infers such annotations. It is written here in the - // example for emhpasis. - @NonNull String foo = "foo"; - String bar = null; + // In general, you do not have to annotate local variables, because the + // Nullness Checker infers such annotations. It is written here in the + // example for emhpasis. + @NonNull String foo = "foo"; + String bar = null; - foo = bar; - bar = foo; - } + foo = bar; + bar = foo; + } - public String exampleGenerics() { + public String exampleGenerics() { - List<@NonNull String> foo = new LinkedList<@NonNull String>(); - List bar = foo; + List<@NonNull String> foo = new LinkedList<@NonNull String>(); + List bar = foo; - String quux = null; - foo.add(quux); - foo.add("quux"); - @NonNull String baz = foo.get(0); - return baz; - } + String quux = null; + foo.add(quux); + foo.add("quux"); + @NonNull String baz = foo.get(0); + return baz; + } } diff --git a/docs/examples/NullnessReleaseTests.java b/docs/examples/NullnessReleaseTests.java index b745fd4fe26..848d640ecc1 100644 --- a/docs/examples/NullnessReleaseTests.java +++ b/docs/examples/NullnessReleaseTests.java @@ -1,7 +1,6 @@ -import org.checkerframework.checker.nullness.qual.*; - import java.util.LinkedList; import java.util.List; +import org.checkerframework.checker.nullness.qual.*; /** * This class is based on NullnessExample. This version contains additional tests to ensure that a @@ -9,26 +8,26 @@ */ public class NullnessReleaseTests { - public void example() { - @NonNull String foo = "foo"; - @NonNull String bar = "bar"; + public void example() { + @NonNull String foo = "foo"; + @NonNull String bar = "bar"; - foo = bar; - bar = foo; - } + foo = bar; + bar = foo; + } - public @NonNull String exampleGenerics() { - List<@NonNull String> foo = new LinkedList<@NonNull String>(); - List<@NonNull String> bar = foo; + public @NonNull String exampleGenerics() { + List<@NonNull String> foo = new LinkedList<@NonNull String>(); + List<@NonNull String> bar = foo; - @NonNull String quux = "quux"; - foo.add(quux); - foo.add("quux"); - @NonNull String baz = foo.get(0); - return baz; - } + @NonNull String quux = "quux"; + foo.add(quux); + foo.add("quux"); + @NonNull String baz = foo.get(0); + return baz; + } - // For some reason this class causes an exception if the Checker - // Framework is compiled with JDK 7 and then executed on JDK 6. - class TestException extends Exception {} + // For some reason this class causes an exception if the Checker + // Framework is compiled with JDK 7 and then executed on JDK 6. + class TestException extends Exception {} } diff --git a/docs/examples/errorprone/src/main/java/com/example/Demo.java b/docs/examples/errorprone/src/main/java/com/example/Demo.java index cde00ff5489..85806b3908c 100644 --- a/docs/examples/errorprone/src/main/java/com/example/Demo.java +++ b/docs/examples/errorprone/src/main/java/com/example/Demo.java @@ -3,8 +3,8 @@ import java.util.Set; public class Demo { - void demo(Set s, short i) { - s.remove(i - 1); // Error Prone error - s.add(null); // Nullness Checker error - } + void demo(Set s, short i) { + s.remove(i - 1); // Error Prone error + s.add(null); // Nullness Checker error + } } diff --git a/docs/examples/fenum-extension/FenumDemo.java b/docs/examples/fenum-extension/FenumDemo.java index 027a807f948..d3113930126 100644 --- a/docs/examples/fenum-extension/FenumDemo.java +++ b/docs/examples/fenum-extension/FenumDemo.java @@ -1,83 +1,82 @@ import org.checkerframework.checker.fenum.qual.Fenum; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.framework.qual.DefaultQualifier; - import qual.MyFenum; @SuppressWarnings("fenum:assignment.type.incompatible") // initialization of fake enums class TestStatic { - public static final @Fenum("A") int ACONST1 = 1; - public static final @Fenum("A") int ACONST2 = 2; + public static final @Fenum("A") int ACONST1 = 1; + public static final @Fenum("A") int ACONST2 = 2; - public static final @Fenum("B") int BCONST1 = 4; - public static final @Fenum("B") int BCONST2 = 5; + public static final @Fenum("B") int BCONST1 = 4; + public static final @Fenum("B") int BCONST2 = 5; - public static final @MyFenum int CCONST1 = 5; - public static final @MyFenum int CCONST2 = 6; + public static final @MyFenum int CCONST1 = 5; + public static final @MyFenum int CCONST2 = 6; } public class FenumDemo { - @Fenum("A") int state1 = TestStatic.ACONST1; // ok + @Fenum("A") int state1 = TestStatic.ACONST1; // ok - @Fenum("B") int state2 = TestStatic.ACONST1; // Incompatible fenums forbidden! + @Fenum("B") int state2 = TestStatic.ACONST1; // Incompatible fenums forbidden! - @MyFenum int state3 = TestStatic.CCONST1; // ok + @MyFenum int state3 = TestStatic.CCONST1; // ok - short state4 = TestStatic.CCONST1; // ok, @MyFenum applies to short by default - @NonNegative short state5 = TestStatic.CCONST1; // ok, @MyFenum also applies to - // @NonNegative short by default (see issue #333) + short state4 = TestStatic.CCONST1; // ok, @MyFenum applies to short by default + @NonNegative short state5 = TestStatic.CCONST1; // ok, @MyFenum also applies to + // @NonNegative short by default (see issue #333) - int state6 = TestStatic.BCONST1; // Incompatible fenums forbidden! - @NonNegative int state7 = TestStatic.BCONST1; // Incompatible fenums forbidden! + int state6 = TestStatic.BCONST1; // Incompatible fenums forbidden! + @NonNegative int state7 = TestStatic.BCONST1; // Incompatible fenums forbidden! - void fenumArg(@Fenum("A") int p) {} + void fenumArg(@Fenum("A") int p) {} - void myFenumArg(@MyFenum int p) {} + void myFenumArg(@MyFenum int p) {} - void foo() { - state1 = 4; // Direct use of value forbidden! - state1 = TestStatic.BCONST1; // Incompatible fenums forbidden! - state1 = TestStatic.ACONST2; // ok + void foo() { + state1 = 4; // Direct use of value forbidden! + state1 = TestStatic.BCONST1; // Incompatible fenums forbidden! + state1 = TestStatic.ACONST2; // ok - fenumArg(5); // Direct use of value forbidden! - fenumArg(TestStatic.BCONST1); // Incompatible fenums forbidden! - fenumArg(TestStatic.ACONST1); // ok + fenumArg(5); // Direct use of value forbidden! + fenumArg(TestStatic.BCONST1); // Incompatible fenums forbidden! + fenumArg(TestStatic.ACONST1); // ok - state3 = 8; - state3 = TestStatic.ACONST2; // Incompatible fenums forbidden! - state3 = TestStatic.CCONST2; // ok + state3 = 8; + state3 = TestStatic.ACONST2; // Incompatible fenums forbidden! + state3 = TestStatic.CCONST2; // ok - myFenumArg(8); // Direct use of value forbidden! - myFenumArg(TestStatic.BCONST2); // Incompatible fenums forbidden! - myFenumArg(TestStatic.CCONST1); // ok - } + myFenumArg(8); // Direct use of value forbidden! + myFenumArg(TestStatic.BCONST2); // Incompatible fenums forbidden! + myFenumArg(TestStatic.CCONST1); // ok + } - @DefaultQualifier(MyFenum.class) - void bar() { - int int0 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method - @NonNegative int int1 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method - } + @DefaultQualifier(MyFenum.class) + void bar() { + int int0 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method + @NonNegative int int1 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method + } - void comparisons() { - if (TestStatic.ACONST1 < TestStatic.ACONST2) { - // ok - } - if (TestStatic.CCONST1 > TestStatic.CCONST2) { - // ok - } - - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 < TestStatic.BCONST2) {} - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 == TestStatic.BCONST2) {} - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 >= TestStatic.CCONST2) {} - - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 < 5) {} - // :: error: (binary.type.incompatible) - if (TestStatic.BCONST1 > 5) {} - // :: error: (binary.type.incompatible) - if (TestStatic.CCONST1 == 5) {} + void comparisons() { + if (TestStatic.ACONST1 < TestStatic.ACONST2) { + // ok } + if (TestStatic.CCONST1 > TestStatic.CCONST2) { + // ok + } + + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 < TestStatic.BCONST2) {} + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 == TestStatic.BCONST2) {} + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 >= TestStatic.CCONST2) {} + + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 < 5) {} + // :: error: (binary.type.incompatible) + if (TestStatic.BCONST1 > 5) {} + // :: error: (binary.type.incompatible) + if (TestStatic.CCONST1 == 5) {} + } } diff --git a/docs/examples/fenum-extension/qual/MyFenum.java b/docs/examples/fenum-extension/qual/MyFenum.java index 2c7df239c40..d32a024db7b 100644 --- a/docs/examples/fenum-extension/qual/MyFenum.java +++ b/docs/examples/fenum-extension/qual/MyFenum.java @@ -1,14 +1,13 @@ package qual; -import org.checkerframework.checker.fenum.qual.FenumTop; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.fenum.qual.FenumTop; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/docs/examples/lombok/src/main/java/lib/Foo.java b/docs/examples/lombok/src/main/java/lib/Foo.java index e9fd6dc0eae..bc81d1b10cf 100644 --- a/docs/examples/lombok/src/main/java/lib/Foo.java +++ b/docs/examples/lombok/src/main/java/lib/Foo.java @@ -1,16 +1,15 @@ package lib; import lombok.Builder; - import org.checkerframework.checker.nullness.qual.Nullable; @Builder public class Foo { - private @Nullable Integer x; - private Integer y; + private @Nullable Integer x; + private Integer y; - void demo() { - x = null; // ok - y = null; // error - } + void demo() { + x = null; // ok + y = null; // error + } } diff --git a/docs/examples/lombok/src/main/java/use/User.java b/docs/examples/lombok/src/main/java/use/User.java index 64f218ddc32..741ecf16d38 100644 --- a/docs/examples/lombok/src/main/java/use/User.java +++ b/docs/examples/lombok/src/main/java/use/User.java @@ -3,10 +3,10 @@ import lib.Foo; public class User { - Foo demo() { - return Foo.builder() - .x(null) // ok - .y(null) // error - .build(); - } + Foo demo() { + return Foo.builder() + .x(null) // ok + .y(null) // error + .build(); + } } diff --git a/docs/examples/subtyping-extension/Demo.java b/docs/examples/subtyping-extension/Demo.java index 4cc3a80805a..35994c9b640 100644 --- a/docs/examples/subtyping-extension/Demo.java +++ b/docs/examples/subtyping-extension/Demo.java @@ -1,39 +1,38 @@ import java.util.LinkedList; import java.util.List; - import qual.Encrypted; abstract class EncryptionDemo { - public @Encrypted String encrypt(String text) { - byte[] b = text.getBytes(); - for (int i = 0; i < b.length; b[i++]++) { - // side effect is in increment expression of for loop - } - // :: warning: (cast.unsafe) - return (@Encrypted String) new String(b); + public @Encrypted String encrypt(String text) { + byte[] b = text.getBytes(); + for (int i = 0; i < b.length; b[i++]++) { + // side effect is in increment expression of for loop } + // :: warning: (cast.unsafe) + return (@Encrypted String) new String(b); + } - // Only send encrypted data! - abstract void sendOverTheInternet(@Encrypted String msg); + // Only send encrypted data! + abstract void sendOverTheInternet(@Encrypted String msg); - void sendText() { - @Encrypted String s = encrypt("foo"); // valid - sendOverTheInternet(s); // valid + void sendText() { + @Encrypted String s = encrypt("foo"); // valid + sendOverTheInternet(s); // valid - String t = encrypt("bar"); // valid (subtype) - sendOverTheInternet(t); // valid (flow) + String t = encrypt("bar"); // valid (subtype) + sendOverTheInternet(t); // valid (flow) - List<@Encrypted String> lst = new LinkedList<@Encrypted String>(); - lst.add(s); - lst.add(t); + List<@Encrypted String> lst = new LinkedList<@Encrypted String>(); + lst.add(s); + lst.add(t); - for (String str : lst) // valid - sendOverTheInternet(str); - } + for (String str : lst) // valid + sendOverTheInternet(str); + } - void sendPassword() { - String password = "unencrypted"; - sendOverTheInternet(password); // invalid - } + void sendPassword() { + String password = "unencrypted"; + sendOverTheInternet(password); // invalid + } } diff --git a/docs/examples/subtyping-extension/qual/Encrypted.java b/docs/examples/subtyping-extension/qual/Encrypted.java index 21617420927..5f781b68405 100644 --- a/docs/examples/subtyping-extension/qual/Encrypted.java +++ b/docs/examples/subtyping-extension/qual/Encrypted.java @@ -1,14 +1,13 @@ package qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** Denotes that the representation of an object is encrypted. */ @Documented diff --git a/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java b/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java index 13cc4770a86..b5ce5cf00a2 100644 --- a/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java +++ b/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java @@ -1,13 +1,12 @@ package qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** Denotes that the representation of an object might not be encrypted. */ @Documented diff --git a/docs/examples/units-extension/UnitsExtensionDemo.java b/docs/examples/units-extension/UnitsExtensionDemo.java index 3c78eef43fb..10de7a1f464 100644 --- a/docs/examples/units-extension/UnitsExtensionDemo.java +++ b/docs/examples/units-extension/UnitsExtensionDemo.java @@ -1,70 +1,69 @@ import org.checkerframework.checker.units.qual.Prefix; import org.checkerframework.checker.units.qual.s; import org.checkerframework.checker.units.util.UnitsTools; - import qual.Frequency; import qual.Hz; import qual.kHz; public class UnitsExtensionDemo { - @Hz int frq; + @Hz int frq; - void bad() { - // Error! Unqualified value assigned to a @Hz value. - // :: error: (assignment.type.incompatible) - frq = 5; + void bad() { + // Error! Unqualified value assigned to a @Hz value. + // :: error: (assignment.type.incompatible) + frq = 5; - // suppress all warnings issued by the units checker for the d1 assignment statement - @SuppressWarnings("units") - @Hz int d1 = 9; + // suppress all warnings issued by the units checker for the d1 assignment statement + @SuppressWarnings("units") + @Hz int d1 = 9; - // specifically suppress warnings related to any frequency units for the d2 assigment - // statement - @SuppressWarnings("frequency") - @Hz int d2 = 10; - } + // specifically suppress warnings related to any frequency units for the d2 assigment + // statement + @SuppressWarnings("frequency") + @Hz int d2 = 10; + } - // specifically suppresses warnings for the hz annotation for the toHz method - @SuppressWarnings("hz") - static @Hz int toHz(int hz) { - return hz; - } + // specifically suppresses warnings for the hz annotation for the toHz method + @SuppressWarnings("hz") + static @Hz int toHz(int hz) { + return hz; + } - void good() { - frq = toHz(9); + void good() { + frq = toHz(9); - @s double time = 5 * UnitsTools.s; - @Hz double freq2 = 20 / time; - } + @s double time = 5 * UnitsTools.s; + @Hz double freq2 = 20 / time; + } - void auto(@s int time) { - // The @Hz annotation is automatically added to the result - // of the division, because we provide class FrequencyRelations. - frq = 99 / time; - } + void auto(@s int time) { + // The @Hz annotation is automatically added to the result + // of the division, because we provide class FrequencyRelations. + frq = 99 / time; + } - public static void main(String[] args) { - @Hz int hertz = toHz(20); - @s int seconds = 5 * UnitsTools.s; + public static void main(String[] args) { + @Hz int hertz = toHz(20); + @s int seconds = 5 * UnitsTools.s; - @SuppressWarnings("units") - @s(Prefix.milli) int millisec = 10; + @SuppressWarnings("units") + @s(Prefix.milli) int millisec = 10; - @SuppressWarnings("hz") - @kHz int kilohertz = 30; + @SuppressWarnings("hz") + @kHz int kilohertz = 30; - @Hz int resultHz = hertz + 20 / seconds; - System.out.println(resultHz); + @Hz int resultHz = hertz + 20 / seconds; + System.out.println(resultHz); - @kHz int resultkHz = kilohertz + 50 / millisec; - System.out.println(resultkHz); + @kHz int resultkHz = kilohertz + 50 / millisec; + System.out.println(resultkHz); - // this demonstrates the type hierarchy resolution: the common supertype of Hz and kHz is - // Frequency, so this statement will pass - @Frequency int okTernaryAssign = seconds > 10 ? hertz : kilohertz; + // this demonstrates the type hierarchy resolution: the common supertype of Hz and kHz is + // Frequency, so this statement will pass + @Frequency int okTernaryAssign = seconds > 10 ? hertz : kilohertz; - // on the other hand, this statement expects the right hand side to be a Hz, so it will fail - // :: error: (assignment.type.incompatible) - @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz; - } + // on the other hand, this statement expects the right hand side to be a Hz, so it will fail + // :: error: (assignment.type.incompatible) + @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz; + } } diff --git a/docs/examples/units-extension/qual/Frequency.java b/docs/examples/units-extension/qual/Frequency.java index 1ab6b088943..793acd1994f 100644 --- a/docs/examples/units-extension/qual/Frequency.java +++ b/docs/examples/units-extension/qual/Frequency.java @@ -1,14 +1,13 @@ package qual; -import org.checkerframework.checker.units.qual.UnitsRelations; -import org.checkerframework.checker.units.qual.UnknownUnits; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.units.qual.UnitsRelations; +import org.checkerframework.checker.units.qual.UnknownUnits; +import org.checkerframework.framework.qual.SubtypeOf; /** * Units of frequency, such as hertz (@{@link Hz}). diff --git a/docs/examples/units-extension/qual/FrequencyRelations.java b/docs/examples/units-extension/qual/FrequencyRelations.java index 5717d8110d7..9f60012b708 100644 --- a/docs/examples/units-extension/qual/FrequencyRelations.java +++ b/docs/examples/units-extension/qual/FrequencyRelations.java @@ -1,5 +1,8 @@ package qual; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.units.UnitsRelations; import org.checkerframework.checker.units.UnitsRelationsTools; @@ -7,53 +10,47 @@ import org.checkerframework.checker.units.qual.s; import org.checkerframework.framework.type.AnnotatedTypeMirror; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** Relations among units of frequency. */ public class FrequencyRelations implements UnitsRelations { - protected AnnotationMirror hertz, kilohertz, second, millisecond; - protected Elements elements; - - public UnitsRelations init(ProcessingEnvironment env) { - elements = env.getElementUtils(); - - // create Annotation Mirrors, each representing a particular Unit's Annotation - hertz = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, Hz.class); - kilohertz = - UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, Hz.class, Prefix.kilo); - second = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); - millisecond = - UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, s.class, Prefix.milli); - - return this; + protected AnnotationMirror hertz, kilohertz, second, millisecond; + protected Elements elements; + + public UnitsRelations init(ProcessingEnvironment env) { + elements = env.getElementUtils(); + + // create Annotation Mirrors, each representing a particular Unit's Annotation + hertz = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, Hz.class); + kilohertz = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, Hz.class, Prefix.kilo); + second = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); + millisecond = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, s.class, Prefix.milli); + + return this; + } + + /** No multiplications yield Hertz. */ + public @Nullable AnnotationMirror multiplication( + AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + // return null so the default units relations can process multiplcations of other units + return null; + } + + /** + * Division of a scalar by seconds yields Hertz. Division of a scalar by milliseconds yields + * Kilohertz. Other divisions yield an unannotated value. + */ + public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + if (UnitsRelationsTools.hasNoUnits(lht)) { + // scalar / millisecond => kilohertz + if (UnitsRelationsTools.hasSpecificUnit(rht, millisecond)) { + return kilohertz; + } + // scalar / second => hertz + else if (UnitsRelationsTools.hasSpecificUnit(rht, second)) { + return hertz; + } } - /** No multiplications yield Hertz. */ - public @Nullable AnnotationMirror multiplication( - AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - // return null so the default units relations can process multiplcations of other units - return null; - } - - /** - * Division of a scalar by seconds yields Hertz. Division of a scalar by milliseconds yields - * Kilohertz. Other divisions yield an unannotated value. - */ - public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - if (UnitsRelationsTools.hasNoUnits(lht)) { - // scalar / millisecond => kilohertz - if (UnitsRelationsTools.hasSpecificUnit(rht, millisecond)) { - return kilohertz; - } - // scalar / second => hertz - else if (UnitsRelationsTools.hasSpecificUnit(rht, second)) { - return hertz; - } - } - - return null; - } + return null; + } } diff --git a/docs/examples/units-extension/qual/Hz.java b/docs/examples/units-extension/qual/Hz.java index 34f549eb92b..055407831ca 100644 --- a/docs/examples/units-extension/qual/Hz.java +++ b/docs/examples/units-extension/qual/Hz.java @@ -1,14 +1,13 @@ package qual; -import org.checkerframework.checker.units.qual.Prefix; -import org.checkerframework.checker.units.qual.UnitsRelations; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.units.qual.Prefix; +import org.checkerframework.checker.units.qual.UnitsRelations; +import org.checkerframework.framework.qual.SubtypeOf; /** * Hertz (Hz), a unit of frequency. @@ -21,5 +20,5 @@ @SubtypeOf(Frequency.class) @UnitsRelations(FrequencyRelations.class) public @interface Hz { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/docs/examples/units-extension/qual/kHz.java b/docs/examples/units-extension/qual/kHz.java index 7ddfd8b8d9e..48ddab5c665 100644 --- a/docs/examples/units-extension/qual/kHz.java +++ b/docs/examples/units-extension/qual/kHz.java @@ -1,15 +1,14 @@ package qual; -import org.checkerframework.checker.units.qual.Prefix; -import org.checkerframework.checker.units.qual.UnitsMultiple; -import org.checkerframework.checker.units.qual.UnitsRelations; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.checker.units.qual.Prefix; +import org.checkerframework.checker.units.qual.UnitsMultiple; +import org.checkerframework.checker.units.qual.UnitsRelations; +import org.checkerframework.framework.qual.SubtypeOf; /** * Kilohertz (kHz), a unit of frequency, and an alias of @Hz(Prefix.kilo). diff --git a/docs/tutorial/src/NullnessExample.java b/docs/tutorial/src/NullnessExample.java index d5a29a6c2e3..909308a9ea1 100644 --- a/docs/tutorial/src/NullnessExample.java +++ b/docs/tutorial/src/NullnessExample.java @@ -1,10 +1,10 @@ public class NullnessExample { - public static void main(String[] args) { - Object myObject = null; + public static void main(String[] args) { + Object myObject = null; - if (args.length > 2) { - myObject = new Object(); - } - System.out.println(myObject.toString()); + if (args.length > 2) { + myObject = new Object(); } + System.out.println(myObject.toString()); + } } diff --git a/docs/tutorial/src/RegexExample.java b/docs/tutorial/src/RegexExample.java index 72c7a17de1d..824eaf00a6a 100644 --- a/docs/tutorial/src/RegexExample.java +++ b/docs/tutorial/src/RegexExample.java @@ -6,17 +6,17 @@ * text, from the string, that matches the first capturing group in the regular expression. */ public class RegexExample { - public static void main(String[] args) { - String regex = args[0]; - String content = args[1]; + public static void main(String[] args) { + String regex = args[0]; + String content = args[1]; - Pattern pat = Pattern.compile(regex); - Matcher mat = pat.matcher(content); + Pattern pat = Pattern.compile(regex); + Matcher mat = pat.matcher(content); - if (mat.matches()) { - System.out.println("Group 1: " + mat.group(1)); - } else { - System.out.println("No match!"); - } + if (mat.matches()) { + System.out.println("Group 1: " + mat.group(1)); + } else { + System.out.println("No match!"); } + } } diff --git a/docs/tutorial/src/encrypted/EncryptionDemo.java b/docs/tutorial/src/encrypted/EncryptionDemo.java index cd34d8a8bba..38feb8096c5 100644 --- a/docs/tutorial/src/encrypted/EncryptionDemo.java +++ b/docs/tutorial/src/encrypted/EncryptionDemo.java @@ -3,32 +3,32 @@ import myqual.Encrypted; public class EncryptionDemo { - private final int OFFSET = 13; + private final int OFFSET = 13; - public @Encrypted String encrypt(String text) { - @Encrypted String encryptedText = ""; - for (char character : text.toCharArray()) { - encryptedText += encryptCharacter(character); - } - return encryptedText; + public @Encrypted String encrypt(String text) { + @Encrypted String encryptedText = ""; + for (char character : text.toCharArray()) { + encryptedText += encryptCharacter(character); } + return encryptedText; + } - private @Encrypted char encryptCharacter(char character) { - @Encrypted int encryptInt = (character + OFFSET) % Character.MAX_VALUE; - return (@Encrypted char) encryptInt; - } + private @Encrypted char encryptCharacter(char character) { + @Encrypted int encryptInt = (character + OFFSET) % Character.MAX_VALUE; + return (@Encrypted char) encryptInt; + } - // Only send encrypted data! - public void sendOverInternet(@Encrypted String msg) { - // ... - } + // Only send encrypted data! + public void sendOverInternet(@Encrypted String msg) { + // ... + } - public void sendPassword() { - String password = getUserPassword(); - sendOverInternet(password); - } + public void sendPassword() { + String password = getUserPassword(); + sendOverInternet(password); + } - private String getUserPassword() { - return "!@#$Really Good Password**"; - } + private String getUserPassword() { + return "!@#$Really Good Password**"; + } } diff --git a/docs/tutorial/src/myqual/Encrypted.java b/docs/tutorial/src/myqual/Encrypted.java index ef7f561377f..2a5ff0c0758 100644 --- a/docs/tutorial/src/myqual/Encrypted.java +++ b/docs/tutorial/src/myqual/Encrypted.java @@ -1,11 +1,10 @@ package myqual; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; /** Denotes that the representation of an object is encrypted. */ @Documented diff --git a/docs/tutorial/src/myqual/PolyEncrypted.java b/docs/tutorial/src/myqual/PolyEncrypted.java index 0ecff27c024..5930cffdfe3 100644 --- a/docs/tutorial/src/myqual/PolyEncrypted.java +++ b/docs/tutorial/src/myqual/PolyEncrypted.java @@ -1,10 +1,9 @@ package myqual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** Denotes that the representation of an object is encrypted. */ @Documented diff --git a/docs/tutorial/src/myqual/PossiblyUnencrypted.java b/docs/tutorial/src/myqual/PossiblyUnencrypted.java index e64f5fd9434..33f1022aad2 100644 --- a/docs/tutorial/src/myqual/PossiblyUnencrypted.java +++ b/docs/tutorial/src/myqual/PossiblyUnencrypted.java @@ -1,11 +1,10 @@ package myqual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** Denotes that the representation of an object might not be encrypted. */ @Documented diff --git a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java index 10fce17677d..e1251bc8023 100644 --- a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java +++ b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java @@ -1,15 +1,10 @@ package net.eyde.personalblog.service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.checkerframework.checker.tainting.qual.Untainted; - import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.Properties; - import net.eyde.personalblog.beans.BlogProperty; import net.eyde.personalblog.beans.Comment; import net.eyde.personalblog.beans.Post; @@ -17,6 +12,9 @@ import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.cfg.Configuration; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.checkerframework.checker.tainting.qual.Untainted; /** * @author NEyde @@ -25,157 +23,157 @@ *

When a user selects a month, they will get all the posts for the month. */ public class PersonalBlogService { - // Installation State - public static final String INSTALLATION_STATE = "installation_state"; - public static final String STATE_UNDEFINED = "undefined"; - public static final String STATE_NO_HIBERNATE_FILE = "no_hibernate_file"; - public static final String STATE_DATABASE_OFF = "database_off"; - public static final String STATE_HIBERNATE_FILE_INVALID = "hibernate_file_invalid"; - public static final String STATE_TABLES_NOT_CREATED = "tables_not_created_yet"; - public static final String STATE_MISSING_PROPERTIES = "missing_properties"; - public static final String STATE_OK = "ok"; - private static Log log = LogFactory.getLog(PersonalBlogService.class); - private static PersonalBlogService service = null; - - // Property Name Constants - public static final String WEBLOG_TITLE = "weblog.title"; - public static final String WEBLOG_DESCRIPTION = "weblog.description"; - public static final String WEBLOG_PICTURE = "weblog.ownerpicture"; - public static final String WEBLOG_OWNER_NICK_NAME = "weblog.ownernickname"; - public static final String WEBLOG_URL = "weblog.url"; - public static final String WEBLOG_OWNER = "weblog.owner"; - public static final String WEBLOG_EMAIL = "weblog.email"; - public static final String LINK_POST = "links.post"; - public static final String EMOTICON_VALUES = "emoticon.values"; - public static final String EMOTICON_IMAGES = "emoticon.images"; - public static final String LOGON_ID = "logon.id"; - public static final String LOGON_PASSWORD = "logon.password"; - public static final String EDITOR = "weblog.editor"; - public static final String EMAIL_HOST = "mail.smtp.host"; - public static final String EMAIL_TRANSPORT = "mail.transport"; - public static final String EMAIL_USERNAME = "mail.username"; - public static final String EMAIL_PASSWORD = "mail.password"; - public static final String CATEGORY_TITLES = "category.titles"; - public static final String CATEGORY_VALUES = "category.values"; - public static final String CATEGORY_IMAGES = "category.images"; - Configuration cfg; - SessionFactory sf; - - int adjustHours; - PropertyManager pm; - CacheManager cache; - - // is really necessary when you are going to format it? - Locale myLocale = Locale.US; - String dburl; - String dbuser; - String dbpassword; - SimpleDateFormat qf = new SimpleDateFormat("yyyy-MM-dd", myLocale); - SimpleDateFormat monthNav = new SimpleDateFormat("yyyyMM", myLocale); - - /** Constructor for PersonalBlogService. */ - protected PersonalBlogService(Properties conn) throws InitializationException { - log.debug("initialization - constructor"); - - try { - cfg = - new Configuration() - .addClass(Post.class) - .addClass(Comment.class) - .addClass(Referrer.class) - .addClass(BlogProperty.class); - - if (conn != null) { - cfg.setProperties(conn); - pm = new PropertyManager(conn); - } else { - pm = new PropertyManager(); - } - - // I want to take it out of here, for these - sf = cfg.buildSessionFactory(); - } catch (Exception e) { - log.error("Error initializing PersonalBlog Service", e); - - throw new InitializationException(e); - } + // Installation State + public static final String INSTALLATION_STATE = "installation_state"; + public static final String STATE_UNDEFINED = "undefined"; + public static final String STATE_NO_HIBERNATE_FILE = "no_hibernate_file"; + public static final String STATE_DATABASE_OFF = "database_off"; + public static final String STATE_HIBERNATE_FILE_INVALID = "hibernate_file_invalid"; + public static final String STATE_TABLES_NOT_CREATED = "tables_not_created_yet"; + public static final String STATE_MISSING_PROPERTIES = "missing_properties"; + public static final String STATE_OK = "ok"; + private static Log log = LogFactory.getLog(PersonalBlogService.class); + private static PersonalBlogService service = null; + + // Property Name Constants + public static final String WEBLOG_TITLE = "weblog.title"; + public static final String WEBLOG_DESCRIPTION = "weblog.description"; + public static final String WEBLOG_PICTURE = "weblog.ownerpicture"; + public static final String WEBLOG_OWNER_NICK_NAME = "weblog.ownernickname"; + public static final String WEBLOG_URL = "weblog.url"; + public static final String WEBLOG_OWNER = "weblog.owner"; + public static final String WEBLOG_EMAIL = "weblog.email"; + public static final String LINK_POST = "links.post"; + public static final String EMOTICON_VALUES = "emoticon.values"; + public static final String EMOTICON_IMAGES = "emoticon.images"; + public static final String LOGON_ID = "logon.id"; + public static final String LOGON_PASSWORD = "logon.password"; + public static final String EDITOR = "weblog.editor"; + public static final String EMAIL_HOST = "mail.smtp.host"; + public static final String EMAIL_TRANSPORT = "mail.transport"; + public static final String EMAIL_USERNAME = "mail.username"; + public static final String EMAIL_PASSWORD = "mail.password"; + public static final String CATEGORY_TITLES = "category.titles"; + public static final String CATEGORY_VALUES = "category.values"; + public static final String CATEGORY_IMAGES = "category.images"; + Configuration cfg; + SessionFactory sf; + + int adjustHours; + PropertyManager pm; + CacheManager cache; + + // is really necessary when you are going to format it? + Locale myLocale = Locale.US; + String dburl; + String dbuser; + String dbpassword; + SimpleDateFormat qf = new SimpleDateFormat("yyyy-MM-dd", myLocale); + SimpleDateFormat monthNav = new SimpleDateFormat("yyyyMM", myLocale); + + /** Constructor for PersonalBlogService. */ + protected PersonalBlogService(Properties conn) throws InitializationException { + log.debug("initialization - constructor"); + + try { + cfg = + new Configuration() + .addClass(Post.class) + .addClass(Comment.class) + .addClass(Referrer.class) + .addClass(BlogProperty.class); + + if (conn != null) { + cfg.setProperties(conn); + pm = new PropertyManager(conn); + } else { + pm = new PropertyManager(); + } + + // I want to take it out of here, for these + sf = cfg.buildSessionFactory(); + } catch (Exception e) { + log.error("Error initializing PersonalBlog Service", e); + + throw new InitializationException(e); } - - /** Singleton getInstance method */ - public static PersonalBlogService getInstance() throws ServiceException { - if (service == null) { - try { - log.debug("Initializing PersonalBlog Service (WITHOUT CONNECTION PARMS)"); - service = new PersonalBlogService(null); - } catch (ServiceException e) { - log.error("Error getting instance of PersonalBlog Service", e); - - throw e; - } - } - - return service; - } - - public static PersonalBlogService getInstance(Properties conn) throws ServiceException { - if (service == null) { - try { - log.debug("Initializing PersonalBlog Service (WITH CONNECTION PARMS)"); - service = new PersonalBlogService(conn); - } catch (Exception e) { - log.error("Error getting instance of PersonalBlog Service", e); - } - } - - return service; + } + + /** Singleton getInstance method */ + public static PersonalBlogService getInstance() throws ServiceException { + if (service == null) { + try { + log.debug("Initializing PersonalBlog Service (WITHOUT CONNECTION PARMS)"); + service = new PersonalBlogService(null); + } catch (ServiceException e) { + log.error("Error getting instance of PersonalBlog Service", e); + + throw e; + } } - /* - * This method will return the most recent posts for today's date. This method - * will return a maximum of 25 total posts or three days worth of posts. - * - */ - public List getPosts() throws ServiceException { - List posts = null; - - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.MONTH, -1); - @SuppressWarnings("tainting") - String startdate = (@Untainted String) qf.format(cal.getTime()); - - posts = - executeQuery( - "from post in class net.eyde.personalblog.beans.Post " - + "where post.created > '" - + startdate - + "' order by post.created desc"); - - return posts; - } - - public List getPostsByCategory(String category) throws ServiceException { - List posts = null; - - posts = - executeQuery( - "from post in class net.eyde.personalblog.beans.Post " - + "where post.category like '%" - + category - + "%' order by post.created desc"); - - return posts; + return service; + } + + public static PersonalBlogService getInstance(Properties conn) throws ServiceException { + if (service == null) { + try { + log.debug("Initializing PersonalBlog Service (WITH CONNECTION PARMS)"); + service = new PersonalBlogService(conn); + } catch (Exception e) { + log.error("Error getting instance of PersonalBlog Service", e); + } } - private List executeQuery(@Untainted String query) { - try { - Session session = sf.openSession(); - @SuppressWarnings({"unchecked"}) - List lst = (List) session.find(query); - session.close(); - return lst; - } catch (Exception e) { - log.error("Error while importing data", e); - return null; - } + return service; + } + + /* + * This method will return the most recent posts for today's date. This method + * will return a maximum of 25 total posts or three days worth of posts. + * + */ + public List getPosts() throws ServiceException { + List posts = null; + + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MONTH, -1); + @SuppressWarnings("tainting") + String startdate = (@Untainted String) qf.format(cal.getTime()); + + posts = + executeQuery( + "from post in class net.eyde.personalblog.beans.Post " + + "where post.created > '" + + startdate + + "' order by post.created desc"); + + return posts; + } + + public List getPostsByCategory(String category) throws ServiceException { + List posts = null; + + posts = + executeQuery( + "from post in class net.eyde.personalblog.beans.Post " + + "where post.category like '%" + + category + + "%' order by post.created desc"); + + return posts; + } + + private List executeQuery(@Untainted String query) { + try { + Session session = sf.openSession(); + @SuppressWarnings({"unchecked"}) + List lst = (List) session.find(query); + session.close(); + return lst; + } catch (Exception e) { + log.error("Error while importing data", e); + return null; } + } } diff --git a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java index 07b7384fc9b..6d22737279d 100644 --- a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java +++ b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java @@ -1,5 +1,9 @@ package net.eyde.personalblog.struts.action; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.eyde.personalblog.service.PersonalBlogService; +import net.eyde.personalblog.service.ServiceException; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; @@ -8,12 +12,6 @@ import org.apache.struts.action.ActionMessages; import org.checkerframework.checker.tainting.qual.Untainted; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import net.eyde.personalblog.service.PersonalBlogService; -import net.eyde.personalblog.service.ServiceException; - /** * Description of the Class * @@ -21,79 +19,79 @@ * @created September 17, 2002 */ public final class ReadAction extends BlogGeneralAction { - /** - * Process the specified HTTP request, and create the corresponding HTTP response (or forward to - * another web component that will create it). Return an ActionForward instance describing where - * and how control should be forwarded, or null if the response has already been completed. - * - * @param mapping The ActionMapping used to select this instance - * @param request The HTTP request we are processing - * @param response The HTTP response we are creating - * @param form Description of the Parameter - * @return Description of the Return Value - * @exception IOException if an input/output error occurs - * @exception ServletException if a servlet exception occurs - */ - @Override - public ActionForward executeSub( - ActionMapping mapping, - ActionForm form, - HttpServletRequest request, - HttpServletResponse response) - throws Exception { - ActionErrors errors = new ActionErrors(); - String forward = "readposts"; + /** + * Process the specified HTTP request, and create the corresponding HTTP response (or forward to + * another web component that will create it). Return an ActionForward instance describing where + * and how control should be forwarded, or null if the response has already been completed. + * + * @param mapping The ActionMapping used to select this instance + * @param request The HTTP request we are processing + * @param response The HTTP response we are creating + * @param form Description of the Parameter + * @return Description of the Return Value + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet exception occurs + */ + @Override + public ActionForward executeSub( + ActionMapping mapping, + ActionForm form, + HttpServletRequest request, + HttpServletResponse response) + throws Exception { + ActionErrors errors = new ActionErrors(); + String forward = "readposts"; - // Get request parameters - String reqCategory = cleanNull(request.getParameter("cat")); + // Get request parameters + String reqCategory = cleanNull(request.getParameter("cat")); - // Get instance of PersonalBlog Service - PersonalBlogService pblog = PersonalBlogService.getInstance(); + // Get instance of PersonalBlog Service + PersonalBlogService pblog = PersonalBlogService.getInstance(); - // Set Request Parameters - // Depending on the parameters, call the appropriate method - try { - if (!reqCategory.equals("")) { - request.setAttribute("posts", pblog.getPostsByCategory(reqCategory)); - } else { - request.setAttribute("posts", pblog.getPosts()); - } + // Set Request Parameters + // Depending on the parameters, call the appropriate method + try { + if (!reqCategory.equals("")) { + request.setAttribute("posts", pblog.getPostsByCategory(reqCategory)); + } else { + request.setAttribute("posts", pblog.getPosts()); + } - } catch (ServiceException e) { - ActionMessages messages = new ActionMessages(); - ActionMessage message = new ActionMessage("exception.postdoesnotexist"); - messages.add(ActionMessages.GLOBAL_MESSAGE, message); + } catch (ServiceException e) { + ActionMessages messages = new ActionMessages(); + ActionMessage message = new ActionMessage("exception.postdoesnotexist"); + messages.add(ActionMessages.GLOBAL_MESSAGE, message); - errors.add(messages); - e.printStackTrace(); - } - - if (!errors.isEmpty()) { - saveErrors(request, errors); - } + errors.add(messages); + e.printStackTrace(); + } - return (mapping.findForward(forward)); + if (!errors.isEmpty()) { + saveErrors(request, errors); } - /** - * Validates userInput: verifies that it cannot be used for an attack. - * - *

A string is valid if it contains only letters, digits, and whitespace. - * - * @param userInput user input to be validated - * @return the input if it is valid - * @throws IllegalArgumentException if userInput is not valid - */ - @Untainted String validate(String userInput) { - for (int i = 0; i < userInput.length(); ++i) { - char ch = userInput.charAt(i); - if (!Character.isLetter(ch) && !Character.isDigit(ch) && !Character.isWhitespace(ch)) - throw new IllegalArgumentException("Illegal user input"); - } - @SuppressWarnings("tainting") - @Untainted String result = userInput; - return result; + return (mapping.findForward(forward)); + } + + /** + * Validates userInput: verifies that it cannot be used for an attack. + * + *

A string is valid if it contains only letters, digits, and whitespace. + * + * @param userInput user input to be validated + * @return the input if it is valid + * @throws IllegalArgumentException if userInput is not valid + */ + @Untainted String validate(String userInput) { + for (int i = 0; i < userInput.length(); ++i) { + char ch = userInput.charAt(i); + if (!Character.isLetter(ch) && !Character.isDigit(ch) && !Character.isWhitespace(ch)) + throw new IllegalArgumentException("Illegal user input"); } + @SuppressWarnings("tainting") + @Untainted String result = userInput; + return result; + } } /* To fix the bug, replace line 48 by: diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java index d40376aec3f..943d39f2421 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java @@ -2,7 +2,6 @@ import java.io.File; import java.util.List; - import javax.annotation.processing.AbstractProcessor; /** @@ -13,29 +12,29 @@ * after those inferences are taken into account. */ public abstract class AinferGeneratePerDirectoryTest extends CheckerFrameworkWPIPerDirectoryTest { - /** - * Creates a new checker test. Use this constructor when creating a generation test. - * - *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected AinferGeneratePerDirectoryTest( - List testFiles, - Class checker, - String testDir, - String... checkerOptions) { - super(testFiles, checker, testDir, checkerOptions); - // Do not typecheck the file all-systems/java8/memberref/Purity.java: it contains - // an expected error that will be issued as a warning, instead (because of -Awarns) if - // the test is executed by this test runner. - // Since it is part of the all-systems tests, it cannot be changed (that would break other - // checkers). Instead, a copy of the file with the expected warning (rather than error) - // has been added to the ainfer non-annotated suite. - doNotTypecheck("all-systems/java8/memberref/Purity.java"); - } + /** + * Creates a new checker test. Use this constructor when creating a generation test. + * + *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected AinferGeneratePerDirectoryTest( + List testFiles, + Class checker, + String testDir, + String... checkerOptions) { + super(testFiles, checker, testDir, checkerOptions); + // Do not typecheck the file all-systems/java8/memberref/Purity.java: it contains + // an expected error that will be issued as a warning, instead (because of -Awarns) if + // the test is executed by this test runner. + // Since it is part of the all-systems tests, it cannot be changed (that would break other + // checkers). Instead, a copy of the file with the expected warning (rather than error) + // has been added to the ainfer non-annotated suite. + doNotTypecheck("all-systems/java8/memberref/Purity.java"); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java index 458a8976fac..da221d9fca7 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java @@ -1,7 +1,5 @@ package org.checkerframework.framework.test; -import org.checkerframework.common.value.qual.StringVal; - import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -10,8 +8,8 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; - import javax.annotation.processing.AbstractProcessor; +import org.checkerframework.common.value.qual.StringVal; /** * A specialized variant of {@link CheckerFrameworkPerDirectoryTest} for testing the Whole Program @@ -22,159 +20,155 @@ */ public class AinferValidatePerDirectoryTest extends CheckerFrameworkWPIPerDirectoryTest { - /** The class of the corresponding generation test. */ - private final Class generationTest; + /** The class of the corresponding generation test. */ + private final Class generationTest; - /** - * The short name of the checker, as used in the naming conventions for files, e.g. "index" for - * the Index Checker or "testchecker" for the AInferTestChecker. - */ - private final String CHECKER_SHORT_NAME; + /** + * The short name of the checker, as used in the naming conventions for files, e.g. "index" for + * the Index Checker or "testchecker" for the AInferTestChecker. + */ + private final String CHECKER_SHORT_NAME; - /** - * The number of letters in ".java", used when stripping off the extension from the name of a - * file. - */ - private static final int DOT_JAVA_LETTER_COUNT = ".java".length(); + /** + * The number of letters in ".java", used when stripping off the extension from the name of a + * file. + */ + private static final int DOT_JAVA_LETTER_COUNT = ".java".length(); - /** - * Creates a new checker test. Use this constructor when creating a validation test. - * - *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @param testDir the path to the directory of test inputs - * @param generationTest the class of the test that must run before this test, if this is the - * second of a pair of tests - * @param checkerOptions options to pass to the compiler when running tests - */ - protected AinferValidatePerDirectoryTest( - List testFiles, - Class checker, - String checkerShortName, - String testDir, - Class generationTest, - String... checkerOptions) { - super(testFiles, checker, testDir, checkerOptions); - this.generationTest = generationTest; - this.CHECKER_SHORT_NAME = checkerShortName; - } + /** + * Creates a new checker test. Use this constructor when creating a validation test. + * + *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @param testDir the path to the directory of test inputs + * @param generationTest the class of the test that must run before this test, if this is the + * second of a pair of tests + * @param checkerOptions options to pass to the compiler when running tests + */ + protected AinferValidatePerDirectoryTest( + List testFiles, + Class checker, + String checkerShortName, + String testDir, + Class generationTest, + String... checkerOptions) { + super(testFiles, checker, testDir, checkerOptions); + this.generationTest = generationTest; + this.CHECKER_SHORT_NAME = checkerShortName; + } - /** - * The directory where inference output files are found. - * - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @return the (relative) directory in which the inference output files can be found - */ - private static String getInferenceBaseDir(String checkerShortName) { - return "tests/ainfer-" + checkerShortName + "/inference-output/"; - } + /** + * The directory where inference output files are found. + * + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @return the (relative) directory in which the inference output files can be found + */ + private static String getInferenceBaseDir(String checkerShortName) { + return "tests/ainfer-" + checkerShortName + "/inference-output/"; + } - /** - * Computes the -Aajava argument that corresponds to the test files. This method is necessary - * because the framework issues a warning if a .ajava file with no corresponding source file is - * specified. - * - *

Assumes that ajava files will be in the {@code #getInferenceBaseDir(String)} directory. - * - * @param sourceFiles the list of source files - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @return the appropriate -Aajava argument - */ - protected static String ajavaArgFromFiles(List sourceFiles, String checkerShortName) { - return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".ajava"); - } + /** + * Computes the -Aajava argument that corresponds to the test files. This method is necessary + * because the framework issues a warning if a .ajava file with no corresponding source file is + * specified. + * + *

Assumes that ajava files will be in the {@code #getInferenceBaseDir(String)} directory. + * + * @param sourceFiles the list of source files + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @return the appropriate -Aajava argument + */ + protected static String ajavaArgFromFiles(List sourceFiles, String checkerShortName) { + return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".ajava"); + } - /** - * Computes the -Astubs argument that corresponds to the test files. This method is necessary - * because the framework issues a warning if a .astub file with no corresponding source file is - * specified. - * - *

Assumes that astub files will be in the {@code #getInferenceBaseDir(String)} directory. - * - * @param sourceFiles the list of source files - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @return the appropriate -Astubs argument - */ - protected static String astubsArgFromFiles(List sourceFiles, String checkerShortName) { - return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".astub"); - } + /** + * Computes the -Astubs argument that corresponds to the test files. This method is necessary + * because the framework issues a warning if a .astub file with no corresponding source file is + * specified. + * + *

Assumes that astub files will be in the {@code #getInferenceBaseDir(String)} directory. + * + * @param sourceFiles the list of source files + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @return the appropriate -Astubs argument + */ + protected static String astubsArgFromFiles(List sourceFiles, String checkerShortName) { + return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".astub"); + } - /** - * Computes the list of ajava or stub files that correspond to the test files. This method is - * necessary because the framework issues a warning if a .ajava file or a stub file with no - * corresponding source file is specified. - * - *

Assumes that ajava/astub files will be in the {@code #getInferenceBaseDir(String)} - * directory. - * - * @param sourceFiles the list of source files - * @param inferenceBaseDir the base directory in which inference output is placed - * @param extension the extension to use: either .astub or .ajava - * @return the appropriate {@code -Aajava} or {@code -Astubs} argument - */ - private static String annotationArgFromFiles( - List sourceFiles, - String inferenceBaseDir, - @StringVal({".astub", ".ajava"}) String extension) { - String checkerArg = extension.equals(".astub") ? "-Astubs=" : "-Aajava="; - return checkerArg - + sourceFiles.stream() - .map(f -> annotationFilenameFromSourceFile(f, inferenceBaseDir, extension)) - .filter(s -> !s.isEmpty()) - .collect(Collectors.joining(":")); - } + /** + * Computes the list of ajava or stub files that correspond to the test files. This method is + * necessary because the framework issues a warning if a .ajava file or a stub file with no + * corresponding source file is specified. + * + *

Assumes that ajava/astub files will be in the {@code #getInferenceBaseDir(String)} + * directory. + * + * @param sourceFiles the list of source files + * @param inferenceBaseDir the base directory in which inference output is placed + * @param extension the extension to use: either .astub or .ajava + * @return the appropriate {@code -Aajava} or {@code -Astubs} argument + */ + private static String annotationArgFromFiles( + List sourceFiles, + String inferenceBaseDir, + @StringVal({".astub", ".ajava"}) String extension) { + String checkerArg = extension.equals(".astub") ? "-Astubs=" : "-Aajava="; + return checkerArg + + sourceFiles.stream() + .map(f -> annotationFilenameFromSourceFile(f, inferenceBaseDir, extension)) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(":")); + } - /** - * Generates the correct argument to the {@code -Aajava} or {@code -Astubs} CF option - * corresponding to the source file {@code sourceFile} and the WPI output type. - * - * @param sourceFile a java source file - * @param inferenceBaseDir the base directory in which inference output is placed - * @param extension the extension to use: either .astub or .ajava - * @return the ajava argument for the corresponding ajava files, if there are any - */ - private static String annotationFilenameFromSourceFile( - File sourceFile, - String inferenceBaseDir, - @StringVal({".astub", ".ajava"}) String extension) { - String fileBaseName = - sourceFile - .getName() - .substring(0, sourceFile.getName().length() - DOT_JAVA_LETTER_COUNT); - StringBuilder sb = new StringBuilder(); - // Find all the annotation files associated with this class name. This approach is necessary - // because (1) some tests are in packages, which will be included in the annotation file - // names, and (2) separate astub files are generated for inner classes. - try (DirectoryStream dirStream = - Files.newDirectoryStream( - Paths.get(inferenceBaseDir), "*" + fileBaseName + "{-,$}*" + extension)) { - dirStream.forEach(f -> sb.append(f).append(":")); - } catch (IOException ignored) { - // Ignore errors. - } - // remove the last ":" - if (sb.length() > 0) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); + /** + * Generates the correct argument to the {@code -Aajava} or {@code -Astubs} CF option + * corresponding to the source file {@code sourceFile} and the WPI output type. + * + * @param sourceFile a java source file + * @param inferenceBaseDir the base directory in which inference output is placed + * @param extension the extension to use: either .astub or .ajava + * @return the ajava argument for the corresponding ajava files, if there are any + */ + private static String annotationFilenameFromSourceFile( + File sourceFile, String inferenceBaseDir, @StringVal({".astub", ".ajava"}) String extension) { + String fileBaseName = + sourceFile.getName().substring(0, sourceFile.getName().length() - DOT_JAVA_LETTER_COUNT); + StringBuilder sb = new StringBuilder(); + // Find all the annotation files associated with this class name. This approach is necessary + // because (1) some tests are in packages, which will be included in the annotation file + // names, and (2) separate astub files are generated for inner classes. + try (DirectoryStream dirStream = + Files.newDirectoryStream( + Paths.get(inferenceBaseDir), "*" + fileBaseName + "{-,$}*" + extension)) { + dirStream.forEach(f -> sb.append(f).append(":")); + } catch (IOException ignored) { + // Ignore errors. + } + // remove the last ":" + if (sb.length() > 0) { + sb.deleteCharAt(sb.length() - 1); } + return sb.toString(); + } - @Override - public void run() { - // Only run if annotated files have been created. - // See ainferTest task. - if (generationTest != null - && !new File("tests/ainfer-" + CHECKER_SHORT_NAME + "/annotated/").exists()) { - throw new RuntimeException(generationTest + " must be run before this test."); - } - super.run(); + @Override + public void run() { + // Only run if annotated files have been created. + // See ainferTest task. + if (generationTest != null + && !new File("tests/ainfer-" + CHECKER_SHORT_NAME + "/annotated/").exists()) { + throw new RuntimeException(generationTest + " must be run before this test."); } + super.run(); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java index 47997912453..49bb7679e55 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java @@ -1,15 +1,13 @@ package org.checkerframework.framework.test; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.junit.Test; -import org.junit.runner.RunWith; - import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; - import javax.annotation.processing.AbstractProcessor; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.junit.Test; +import org.junit.runner.RunWith; /** * Compiles all test files in a test directory together. Use {@link CheckerFrameworkPerFileTest} to @@ -47,146 +45,145 @@ @RunWith(PerDirectorySuite.class) public abstract class CheckerFrameworkPerDirectoryTest extends CheckerFrameworkRootedTest { - /** The files containing test code, which will be type-checked. */ - protected final List testFiles; + /** The files containing test code, which will be type-checked. */ + protected final List testFiles; - /** The binary names of the checkers to run. */ - protected final List<@BinaryName String> checkerNames; + /** The binary names of the checkers to run. */ + protected final List<@BinaryName String> checkerNames; - /** - * The path, relative to the test root directory (see {@link - * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. - */ - protected final String testDir; + /** + * The path, relative to the test root directory (see {@link + * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. + */ + protected final String testDir; - /** Extra options to pass to javac when running the checker. */ - protected final List checkerOptions; + /** Extra options to pass to javac when running the checker. */ + protected final List checkerOptions; - /** Extra entries for the classpath. */ - protected final List classpathExtra; + /** Extra entries for the classpath. */ + protected final List classpathExtra; - /** - * Creates a new checker test. - * - *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkPerDirectoryTest( - List testFiles, - Class checker, - String testDir, - String... checkerOptions) { - this(testFiles, checker, testDir, Collections.emptyList(), checkerOptions); - } + /** + * Creates a new checker test. + * + *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkPerDirectoryTest( + List testFiles, + Class checker, + String testDir, + String... checkerOptions) { + this(testFiles, checker, testDir, Collections.emptyList(), checkerOptions); + } - /** - * Creates a new checker test. - * - *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param classpathExtra extra entries for the classpath, relative to a directory such as - * checker-framework/checker - * @param checkerOptions options to pass to the compiler when running tests - */ - @SuppressWarnings( - "signature:argument.type.incompatible" // for non-array non-primitive class, getName(): - // @BinaryName - ) - protected CheckerFrameworkPerDirectoryTest( - List testFiles, - Class checker, - String testDir, - List classpathExtra, - String... checkerOptions) { - this( - testFiles, - Collections.singletonList(checker.getName()), - testDir, - classpathExtra, - checkerOptions); - } + /** + * Creates a new checker test. + * + *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param classpathExtra extra entries for the classpath, relative to a directory such as + * checker-framework/checker + * @param checkerOptions options to pass to the compiler when running tests + */ + @SuppressWarnings( + "signature:argument.type.incompatible" // for non-array non-primitive class, getName(): + // @BinaryName + ) + protected CheckerFrameworkPerDirectoryTest( + List testFiles, + Class checker, + String testDir, + List classpathExtra, + String... checkerOptions) { + this( + testFiles, + Collections.singletonList(checker.getName()), + testDir, + classpathExtra, + checkerOptions); + } - /** - * Creates a new checker test. - * - *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checkerNames the binary names of the checkers to run - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param classpathExtra extra entries for the classpath, relative to a directory such as - * checker-framework/checker - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkPerDirectoryTest( - List testFiles, - List<@BinaryName String> checkerNames, - String testDir, - List classpathExtra, - String... checkerOptions) { - super(); - this.testFiles = testFiles; - this.checkerNames = checkerNames; - this.testDir = testDir; - this.classpathExtra = classpathExtra; - this.checkerOptions = Arrays.asList(checkerOptions); - } + /** + * Creates a new checker test. + * + *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checkerNames the binary names of the checkers to run + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param classpathExtra extra entries for the classpath, relative to a directory such as + * checker-framework/checker + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkPerDirectoryTest( + List testFiles, + List<@BinaryName String> checkerNames, + String testDir, + List classpathExtra, + String... checkerOptions) { + super(); + this.testFiles = testFiles; + this.checkerNames = checkerNames; + this.testDir = testDir; + this.classpathExtra = classpathExtra; + this.checkerOptions = Arrays.asList(checkerOptions); + } - /** Run the tests. */ - @Test - public void run() { - if (testFiles.isEmpty()) { - return; - } - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - new File(resolveTestDirectory(), testDir).getPath(), - testFiles, - classpathExtra, - checkerNames, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); - checkResult(adjustedTestResult); + /** Run the tests. */ + @Test + public void run() { + if (testFiles.isEmpty()) { + return; } + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + new File(resolveTestDirectory(), testDir).getPath(), + testFiles, + classpathExtra, + checkerNames, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); + checkResult(adjustedTestResult); + } - /** - * This method is called before issuing assertions about a TypecheckResult. Subclasses can - * override it to customize behavior. - * - * @param testResult a test result to possibly change - * @return a TypecheckResult to use instead, which may be the unmodified argument - */ - public TypecheckResult adjustTypecheckResult(TypecheckResult testResult) { - return testResult; - } + /** + * This method is called before issuing assertions about a TypecheckResult. Subclasses can + * override it to customize behavior. + * + * @param testResult a test result to possibly change + * @return a TypecheckResult to use instead, which may be the unmodified argument + */ + public TypecheckResult adjustTypecheckResult(TypecheckResult testResult) { + return testResult; + } - /** - * Override this method if you would like to supply a checker command-line option that depends - * on the Java files passed to the test. Those files are available in field {@link #testFiles}. - * - *

If you want to specify the same command-line option for all tests of a particular checker, - * then pass it to the {@link #CheckerFrameworkPerDirectoryTest} constructor. - * - * @param previousOptions the options specified in the constructor of the test previousOptions - * is unmodifiable - * @return a new list of options or the original passed through - */ - public List customizeOptions(List previousOptions) { - return previousOptions; - } + /** + * Override this method if you would like to supply a checker command-line option that depends on + * the Java files passed to the test. Those files are available in field {@link #testFiles}. + * + *

If you want to specify the same command-line option for all tests of a particular checker, + * then pass it to the {@link #CheckerFrameworkPerDirectoryTest} constructor. + * + * @param previousOptions the options specified in the constructor of the test previousOptions is + * unmodifiable + * @return a new list of options or the original passed through + */ + public List customizeOptions(List previousOptions) { + return previousOptions; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java index de1f9c7fb10..101ca081a55 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java @@ -1,15 +1,13 @@ package org.checkerframework.framework.test; -import org.junit.Test; -import org.junit.runner.RunWith; - import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; - import javax.annotation.processing.AbstractProcessor; +import org.junit.Test; +import org.junit.runner.RunWith; /** * Compiles all test files individually. Use {@link CheckerFrameworkPerDirectoryTest} to compile all @@ -49,72 +47,71 @@ @RunWith(PerFileSuite.class) public abstract class CheckerFrameworkPerFileTest extends CheckerFrameworkRootedTest { - /** The file containing test code, which will be type-checked. */ - protected final File testFile; + /** The file containing test code, which will be type-checked. */ + protected final File testFile; - /** The checker to use for tests. */ - protected final Class checker; + /** The checker to use for tests. */ + protected final Class checker; - /** - * The path, relative to the test root directory (see {@link - * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. - */ - protected final String testDir; + /** + * The path, relative to the test root directory (see {@link + * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. + */ + protected final String testDir; - /** Extra options to pass to javac when running the checker. */ - protected final List checkerOptions; + /** Extra options to pass to javac when running the checker. */ + protected final List checkerOptions; - /** - * Creates a new checker test. - * - *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFile the file containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkPerFileTest( - File testFile, - Class checker, - String testDir, - String... checkerOptions) { - super(); - this.testFile = testFile; - this.checker = checker; - this.testDir = testDir; - this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions)); - } + /** + * Creates a new checker test. + * + *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFile the file containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkPerFileTest( + File testFile, + Class checker, + String testDir, + String... checkerOptions) { + super(); + this.testFile = testFile; + this.checker = checker; + this.testDir = testDir; + this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions)); + } - @Test - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - new File(resolveTestDirectory(), testDir).getPath(), - testFile, - checker, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - checkResult(testResult); - } + @Test + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + new File(resolveTestDirectory(), testDir).getPath(), + testFile, + checker, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + checkResult(testResult); + } - /** - * Override this method if you would like to supply a checker command-line option that depends - * on the Java file passed to the test. That file name is available in field {@link #testFile}. - * - *

If you want to specify the same command-line option for all tests of a particular checker, - * then pass it to the {@link CheckerFrameworkPerFileTest} constructor. - * - * @param previousOptions the options specified in the constructor of the test previousOptions - * is unmodifiable - * @return a new list of options or the original passed through - */ - public List customizeOptions(List previousOptions) { - return previousOptions; - } + /** + * Override this method if you would like to supply a checker command-line option that depends on + * the Java file passed to the test. That file name is available in field {@link #testFile}. + * + *

If you want to specify the same command-line option for all tests of a particular checker, + * then pass it to the {@link CheckerFrameworkPerFileTest} constructor. + * + * @param previousOptions the options specified in the constructor of the test previousOptions is + * unmodifiable + * @return a new list of options or the original passed through + */ + public List customizeOptions(List previousOptions) { + return previousOptions; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java index 8d2f358a21f..ce02cbed2cb 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java @@ -5,29 +5,29 @@ /** Encapsulates the directory root to search within for test files to compile. */ abstract class CheckerFrameworkRootedTest { - /** Constructs a test that will assert that it can resolve its test root directory. */ - public CheckerFrameworkRootedTest() {} + /** Constructs a test that will assert that it can resolve its test root directory. */ + public CheckerFrameworkRootedTest() {} - /** - * Resolves the test root directory from the optional {@link TestRootDirectory} annotation or - * falls back to the default of {@code currentDir/tests}. - * - * @return the resolved directory - */ - protected File resolveTestDirectory() { - TestRootDirectory annotation = getClass().getAnnotation(TestRootDirectory.class); - if (annotation != null) { - return new File(annotation.value()); - } - return new File("tests"); + /** + * Resolves the test root directory from the optional {@link TestRootDirectory} annotation or + * falls back to the default of {@code currentDir/tests}. + * + * @return the resolved directory + */ + protected File resolveTestDirectory() { + TestRootDirectory annotation = getClass().getAnnotation(TestRootDirectory.class); + if (annotation != null) { + return new File(annotation.value()); } + return new File("tests"); + } - /** - * Check that the {@link TypecheckResult} did not fail. - * - * @param typecheckResult result to check - */ - public void checkResult(TypecheckResult typecheckResult) { - TestUtilities.assertTestDidNotFail(typecheckResult); - } + /** + * Check that the {@link TypecheckResult} did not fail. + * + * @param typecheckResult result to check + */ + public void checkResult(TypecheckResult typecheckResult) { + TestUtilities.assertTestDidNotFail(typecheckResult); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java index 22320f645c4..1c2304c53c7 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java @@ -1,15 +1,13 @@ package org.checkerframework.framework.test; -import org.checkerframework.checker.initialization.qual.UnderInitialization; -import org.junit.Assert; - import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import java.util.Scanner; - import javax.annotation.processing.AbstractProcessor; +import org.checkerframework.checker.initialization.qual.UnderInitialization; +import org.junit.Assert; /** * A specialized variant of {@link CheckerFrameworkPerDirectoryTest} for testing the Whole Program @@ -21,100 +19,100 @@ */ public abstract class CheckerFrameworkWPIPerDirectoryTest extends CheckerFrameworkPerDirectoryTest { - /** - * Creates a new checker test. Use this constructor when creating a generation test. - * - *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkWPIPerDirectoryTest( - List testFiles, - Class checker, - String testDir, - String... checkerOptions) { - super(testFiles, checker, testDir, checkerOptions); + /** + * Creates a new checker test. Use this constructor when creating a generation test. + * + *

{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkWPIPerDirectoryTest( + List testFiles, + Class checker, + String testDir, + String... checkerOptions) { + super(testFiles, checker, testDir, checkerOptions); - String skipComment; - if (this.checkerOptions.contains("-Ainfer=ajava")) { - skipComment = "@infer-ajava-skip-test"; - } else if (this.checkerOptions.contains("-Ainfer=jaifs")) { - skipComment = "@infer-jaifs-skip-test"; - } else if (this.checkerOptions.contains("-Ainfer=stubs")) { - skipComment = "@infer-stubs-skip-test"; - } else { - skipComment = null; - } - if (skipComment != null) { - List removeFiles = new ArrayList<>(); - for (File testFile : testFiles) { - if (hasSkipComment(testFile, skipComment)) { - removeFiles.add(testFile); - } - } - this.testFiles.removeAll(removeFiles); + String skipComment; + if (this.checkerOptions.contains("-Ainfer=ajava")) { + skipComment = "@infer-ajava-skip-test"; + } else if (this.checkerOptions.contains("-Ainfer=jaifs")) { + skipComment = "@infer-jaifs-skip-test"; + } else if (this.checkerOptions.contains("-Ainfer=stubs")) { + skipComment = "@infer-stubs-skip-test"; + } else { + skipComment = null; + } + if (skipComment != null) { + List removeFiles = new ArrayList<>(); + for (File testFile : testFiles) { + if (hasSkipComment(testFile, skipComment)) { + removeFiles.add(testFile); } + } + this.testFiles.removeAll(removeFiles); } + } - /** - * Do not typecheck any file ending with the given String. A subclass of - * CheckerFrameworkWPIPerDirectoryTest uses this routine to avoid typechecking files in the - * all-systems test suite that are problematic for one typechecker. For example, this routine is - * useful when running the all-systems tests using WPI, because some all-systems tests have - * expected errors that become warnings during a WPI run (because of {@code -Awarns}) and so - * must be excluded. - * - *

This code takes advantage of the mutability of the {@link #testFiles} field. - * - * @param endswith a string that the absolute path of the target file that should not be - * typechecked ends with. Usually, this takes the form "all-systems/ProblematicFile.java". - */ - protected void doNotTypecheck( - @UnderInitialization(CheckerFrameworkPerDirectoryTest.class) CheckerFrameworkWPIPerDirectoryTest this, - String endswith) { - int removeIndex = -1; - for (int i = 0; i < testFiles.size(); i++) { - File f = testFiles.get(i); - if (f.getAbsolutePath().endsWith(endswith)) { - if (removeIndex != -1) { - Assert.fail( - "When attempting to exclude a file, found more than one match in the" - + " test suite. Check the test code and use a more-specific removal" - + " key. Attempting to exclude: " - + endswith); - } - removeIndex = i; - } - } - // This test code can run for every subdirectory of the all-systems tests, so there is no - // guarantee that the file to be excluded will be found. + /** + * Do not typecheck any file ending with the given String. A subclass of + * CheckerFrameworkWPIPerDirectoryTest uses this routine to avoid typechecking files in the + * all-systems test suite that are problematic for one typechecker. For example, this routine is + * useful when running the all-systems tests using WPI, because some all-systems tests have + * expected errors that become warnings during a WPI run (because of {@code -Awarns}) and so must + * be excluded. + * + *

This code takes advantage of the mutability of the {@link #testFiles} field. + * + * @param endswith a string that the absolute path of the target file that should not be + * typechecked ends with. Usually, this takes the form "all-systems/ProblematicFile.java". + */ + protected void doNotTypecheck( + @UnderInitialization(CheckerFrameworkPerDirectoryTest.class) CheckerFrameworkWPIPerDirectoryTest this, + String endswith) { + int removeIndex = -1; + for (int i = 0; i < testFiles.size(); i++) { + File f = testFiles.get(i); + if (f.getAbsolutePath().endsWith(endswith)) { if (removeIndex != -1) { - testFiles.remove(removeIndex); + Assert.fail( + "When attempting to exclude a file, found more than one match in the" + + " test suite. Check the test code and use a more-specific removal" + + " key. Attempting to exclude: " + + endswith); } + removeIndex = i; + } + } + // This test code can run for every subdirectory of the all-systems tests, so there is no + // guarantee that the file to be excluded will be found. + if (removeIndex != -1) { + testFiles.remove(removeIndex); } + } - /** - * Whether {@code file} contains {@code skipComment}. - * - * @param file a java test file - * @param skipComment a comment that indicates that a test should be skipped - * @return whether {@code file} contains {@code skipComment} - */ - public static boolean hasSkipComment(File file, String skipComment) { - try (Scanner in = new Scanner(file)) { - while (in.hasNext()) { - String nextLine = in.nextLine(); - if (nextLine.contains(skipComment)) { - return true; - } - } - } catch (FileNotFoundException e) { - throw new RuntimeException(e); + /** + * Whether {@code file} contains {@code skipComment}. + * + * @param file a java test file + * @param skipComment a comment that indicates that a test should be skipped + * @return whether {@code file} contains {@code skipComment} + */ + public static boolean hasSkipComment(File file, String skipComment) { + try (Scanner in = new Scanner(file)) { + while (in.hasNext()) { + String nextLine = in.nextLine(); + if (nextLine.contains(skipComment)) { + return true; } - return false; + } + } catch (FileNotFoundException e) { + throw new RuntimeException(e); } + return false; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java index b6048cecf54..dd01819da95 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java @@ -2,61 +2,60 @@ import java.util.Collections; import java.util.List; - import javax.tools.Diagnostic; import javax.tools.JavaFileObject; /** CompilationResult represents the output of the compiler after it is run. */ public class CompilationResult { - private final boolean compiledWithoutError; - private final String javacOutput; - private final Iterable javaFileObjects; - private final List> diagnostics; - - CompilationResult( - boolean compiledWithoutError, - String javacOutput, - Iterable javaFileObjects, - List> diagnostics) { - this.compiledWithoutError = compiledWithoutError; - this.javacOutput = javacOutput; - this.javaFileObjects = javaFileObjects; - this.diagnostics = Collections.unmodifiableList(diagnostics); - } - - /** - * Returns whether or not compilation succeeded without errors or exceptions. - * - * @return whether or not compilation succeeded without errors or exceptions - */ - public boolean compiledWithoutError() { - return compiledWithoutError; - } - - /** - * Returns all of the output from the compiler. - * - * @return all of the output from the compiler - */ - public String getJavacOutput() { - return javacOutput; - } - - /** - * Returns the list of Java files passed to the compiler. - * - * @return the list of Java files passed to the compiler - */ - public Iterable getJavaFileObjects() { - return javaFileObjects; - } - - /** - * Returns the diagnostics reported by the compiler. - * - * @return the diagnostics reported by the compiler - */ - public List> getDiagnostics() { - return diagnostics; - } + private final boolean compiledWithoutError; + private final String javacOutput; + private final Iterable javaFileObjects; + private final List> diagnostics; + + CompilationResult( + boolean compiledWithoutError, + String javacOutput, + Iterable javaFileObjects, + List> diagnostics) { + this.compiledWithoutError = compiledWithoutError; + this.javacOutput = javacOutput; + this.javaFileObjects = javaFileObjects; + this.diagnostics = Collections.unmodifiableList(diagnostics); + } + + /** + * Returns whether or not compilation succeeded without errors or exceptions. + * + * @return whether or not compilation succeeded without errors or exceptions + */ + public boolean compiledWithoutError() { + return compiledWithoutError; + } + + /** + * Returns all of the output from the compiler. + * + * @return all of the output from the compiler + */ + public String getJavacOutput() { + return javacOutput; + } + + /** + * Returns the list of Java files passed to the compiler. + * + * @return the list of Java files passed to the compiler + */ + public Iterable getJavaFileObjects() { + return javaFileObjects; + } + + /** + * Returns the diagnostics reported by the compiler. + * + * @return the diagnostics reported by the compiler + */ + public List> getDiagnostics() { + return diagnostics; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java index 3105b42e217..6df18767861 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java @@ -1,15 +1,14 @@ package org.checkerframework.framework.test; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.plumelib.util.StringsPlume; - import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.plumelib.util.StringsPlume; /** * Represents all of the information needed to execute the Javac compiler for a given set of test @@ -17,98 +16,98 @@ */ public class ImmutableTestConfiguration implements TestConfiguration { - /** - * Options that should be passed to the compiler. This a {@code Map(optionName => - * optionArgumentIfAny)}. E.g., - * - *

{@code
-     * Map(
-     *     "-AprintAllQualifiers" => null
-     *     "-classpath" => "myDir1:myDir2"
-     * )
-     * }
- */ - private final Map options; - - /** - * These files contain diagnostics that should be returned by Javac. If this list is empty, the - * diagnostics are instead read from comments in the Java file itself - */ - private final List diagnosticFiles; - - /** - * The source files to compile. If the file is expected to emit errors on compilation, the file - * should contain expected error diagnostics OR should have a companion file with the same - * path/name but with the extension .out instead of .java if they - */ - private final List testSourceFiles; - - /** A list of AnnotationProcessors (usually checkers) to pass to the compiler for this test. */ - private final List<@BinaryName String> processors; - - /** The value of system property "emit.test.debug". */ - private final boolean shouldEmitDebugInfo; - - /** - * Create a new ImmutableTestConfiguration. - * - * @param diagnosticFiles files containing diagnostics that should be returned by javac - * @param testSourceFiles the source files to compile - * @param processors the annotation processors (usually checkers) to run - * @param options options that should be passed to the compiler - * @param shouldEmitDebugInfo the value of system property "emit.test.debug" - */ - public ImmutableTestConfiguration( - List diagnosticFiles, - List testSourceFiles, - List<@BinaryName String> processors, - Map options, - boolean shouldEmitDebugInfo) { - this.diagnosticFiles = Collections.unmodifiableList(diagnosticFiles); - this.testSourceFiles = Collections.unmodifiableList(new ArrayList<>(testSourceFiles)); - this.processors = new ArrayList<>(processors); - this.options = - Collections.unmodifiableMap(new LinkedHashMap(options)); - this.shouldEmitDebugInfo = shouldEmitDebugInfo; - } - - @Override - public List getTestSourceFiles() { - return testSourceFiles; - } - - @Override - public List getDiagnosticFiles() { - return diagnosticFiles; - } - - @Override - public List<@BinaryName String> getProcessors() { - return processors; - } - - @Override - public Map getOptions() { - return options; - } - - @Override - public List getFlatOptions() { - return TestUtilities.optionMapToList(options); - } - - @Override - public boolean shouldEmitDebugInfo() { - return shouldEmitDebugInfo; - } - - @Override - public String toString() { - return StringsPlume.joinLines( - "TestConfigurationBuilder:", - "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), - "processors=" + String.join(", ", processors), - "options=" + String.join(", ", getFlatOptions()), - "shouldEmitDebugInfo=" + shouldEmitDebugInfo); - } + /** + * Options that should be passed to the compiler. This a {@code Map(optionName => + * optionArgumentIfAny)}. E.g., + * + *
{@code
+   * Map(
+   *     "-AprintAllQualifiers" => null
+   *     "-classpath" => "myDir1:myDir2"
+   * )
+   * }
+ */ + private final Map options; + + /** + * These files contain diagnostics that should be returned by Javac. If this list is empty, the + * diagnostics are instead read from comments in the Java file itself + */ + private final List diagnosticFiles; + + /** + * The source files to compile. If the file is expected to emit errors on compilation, the file + * should contain expected error diagnostics OR should have a companion file with the same + * path/name but with the extension .out instead of .java if they + */ + private final List testSourceFiles; + + /** A list of AnnotationProcessors (usually checkers) to pass to the compiler for this test. */ + private final List<@BinaryName String> processors; + + /** The value of system property "emit.test.debug". */ + private final boolean shouldEmitDebugInfo; + + /** + * Create a new ImmutableTestConfiguration. + * + * @param diagnosticFiles files containing diagnostics that should be returned by javac + * @param testSourceFiles the source files to compile + * @param processors the annotation processors (usually checkers) to run + * @param options options that should be passed to the compiler + * @param shouldEmitDebugInfo the value of system property "emit.test.debug" + */ + public ImmutableTestConfiguration( + List diagnosticFiles, + List testSourceFiles, + List<@BinaryName String> processors, + Map options, + boolean shouldEmitDebugInfo) { + this.diagnosticFiles = Collections.unmodifiableList(diagnosticFiles); + this.testSourceFiles = Collections.unmodifiableList(new ArrayList<>(testSourceFiles)); + this.processors = new ArrayList<>(processors); + this.options = + Collections.unmodifiableMap(new LinkedHashMap(options)); + this.shouldEmitDebugInfo = shouldEmitDebugInfo; + } + + @Override + public List getTestSourceFiles() { + return testSourceFiles; + } + + @Override + public List getDiagnosticFiles() { + return diagnosticFiles; + } + + @Override + public List<@BinaryName String> getProcessors() { + return processors; + } + + @Override + public Map getOptions() { + return options; + } + + @Override + public List getFlatOptions() { + return TestUtilities.optionMapToList(options); + } + + @Override + public boolean shouldEmitDebugInfo() { + return shouldEmitDebugInfo; + } + + @Override + public String toString() { + return StringsPlume.joinLines( + "TestConfigurationBuilder:", + "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), + "processors=" + String.join(", ", processors), + "options=" + String.join(", ", getFlatOptions()), + "shouldEmitDebugInfo=" + shouldEmitDebugInfo); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java index 25c518e28e9..753d06c338a 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java @@ -1,15 +1,5 @@ package org.checkerframework.framework.test; -import org.checkerframework.javacutil.BugInCF; -import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.Parameterized.Parameters; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; - import java.io.File; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -20,6 +10,15 @@ import java.util.Collections; import java.util.List; import java.util.StringJoiner; +import org.checkerframework.javacutil.BugInCF; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; // TODO: large parts of this file are the same as PerFileSuite.java. // Reduce duplication by moving common parts to an abstract class. @@ -36,156 +35,155 @@ */ public class PerDirectorySuite extends RootedSuite { - /** Name */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public @interface Name {} + /** Name */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Name {} + + private final ArrayList runners = new ArrayList<>(); + + @Override + protected List getChildren() { + return runners; + } + + /** + * Only called reflectively. Do not use programmatically. + * + * @param klass the class whose tests to run + */ + @SuppressWarnings("nullness") // JUnit needs to be annotated + public PerDirectorySuite(Class klass) throws Throwable { + super(klass, Collections.emptyList()); + TestClass testClass = getTestClass(); + Class javaTestClass = testClass.getJavaClass(); + List> parametersList = getParametersList(testClass); + + for (List parameters : parametersList) { + runners.add(new PerParameterSetTestRunner(javaTestClass, parameters)); + } + } - private final ArrayList runners = new ArrayList<>(); + /** Returns a list of one-element arrays, each containing a Java File. */ + @SuppressWarnings("nullness") // JUnit needs to be annotated + private List> getParametersList(TestClass klass) throws Throwable { + FrameworkMethod method = getParametersMethod(klass); - @Override - protected List getChildren() { - return runners; + // We must have a method getTestDirs which returns String[], + // or getParametersMethod would fail. + if (method == null) { + throw new BugInCF("no method annotated with @Parameters"); } - - /** - * Only called reflectively. Do not use programmatically. - * - * @param klass the class whose tests to run - */ - @SuppressWarnings("nullness") // JUnit needs to be annotated - public PerDirectorySuite(Class klass) throws Throwable { - super(klass, Collections.emptyList()); - TestClass testClass = getTestClass(); - Class javaTestClass = testClass.getJavaClass(); - List> parametersList = getParametersList(testClass); - - for (List parameters : parametersList) { - runners.add(new PerParameterSetTestRunner(javaTestClass, parameters)); - } + if (!method.getReturnType().isArray()) { + throw new BugInCF( + "@Parameters annotation on method that does not return an array: " + method); } - - /** Returns a list of one-element arrays, each containing a Java File. */ - @SuppressWarnings("nullness") // JUnit needs to be annotated - private List> getParametersList(TestClass klass) throws Throwable { - FrameworkMethod method = getParametersMethod(klass); - - // We must have a method getTestDirs which returns String[], - // or getParametersMethod would fail. - if (method == null) { - throw new BugInCF("no method annotated with @Parameters"); + String[] dirs = (String[]) method.invokeExplosively(null); + return TestUtilities.findJavaFilesPerDirectory(resolveTestDirectory(), dirs); + } + + /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ + private FrameworkMethod getParametersMethod(TestClass testClass) { + List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); + if (parameterMethods.size() != 1) { + // Construct error message + + String methods; + if (parameterMethods.isEmpty()) { + methods = "[No methods specified]"; + } else { + StringJoiner sj = new StringJoiner(", "); + for (FrameworkMethod method : parameterMethods) { + sj.add(method.getName()); } - if (!method.getReturnType().isArray()) { - throw new BugInCF( - "@Parameters annotation on method that does not return an array: " + method); - } - String[] dirs = (String[]) method.invokeExplosively(null); - return TestUtilities.findJavaFilesPerDirectory(resolveTestDirectory(), dirs); + methods = sj.toString(); + } + + throw new BugInCF( + "Exactly one of the following methods should be declared:%n%s%n" + + "testClass=%s%n" + + "parameterMethods=%s", + requiredFormsMessage, testClass.getName(), methods); } - /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ - private FrameworkMethod getParametersMethod(TestClass testClass) { - List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); - if (parameterMethods.size() != 1) { - // Construct error message - - String methods; - if (parameterMethods.isEmpty()) { - methods = "[No methods specified]"; - } else { - StringJoiner sj = new StringJoiner(", "); - for (FrameworkMethod method : parameterMethods) { - sj.add(method.getName()); - } - methods = sj.toString(); - } - - throw new BugInCF( - "Exactly one of the following methods should be declared:%n%s%n" - + "testClass=%s%n" - + "parameterMethods=%s", - requiredFormsMessage, testClass.getName(), methods); - } - - FrameworkMethod method = parameterMethods.get(0); - - Class returnType = method.getReturnType(); - String methodName = method.getName(); - switch (methodName) { - case "getTestDirs": - if (!(returnType.isArray() && returnType.getComponentType() == String.class)) { - throw new RuntimeException( - "getTestDirs should return String[], found " + returnType); - } - break; - - default: - throw new RuntimeException( - requiredFormsMessage - + "%n" - + "testClass=" - + testClass.getName() - + "%n" - + "parameterMethods=" - + method); - } + FrameworkMethod method = parameterMethods.get(0); - int modifiers = method.getMethod().getModifiers(); - if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { - throw new RuntimeException( - "Parameter method (" + method.getName() + ") must be public and static"); + Class returnType = method.getReturnType(); + String methodName = method.getName(); + switch (methodName) { + case "getTestDirs": + if (!(returnType.isArray() && returnType.getComponentType() == String.class)) { + throw new RuntimeException("getTestDirs should return String[], found " + returnType); } + break; + + default: + throw new RuntimeException( + requiredFormsMessage + + "%n" + + "testClass=" + + testClass.getName() + + "%n" + + "parameterMethods=" + + method); + } - return method; + int modifiers = method.getMethod().getModifiers(); + if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { + throw new RuntimeException( + "Parameter method (" + method.getName() + ") must be public and static"); } - /** The message about the required getTestDirs method. */ - private static final String requiredFormsMessage = - "Parameter method must have the following form:" - + System.lineSeparator() - + "@Parameters String[] getTestDirs()"; + return method; + } - /** Runs the test class for the set of javaFiles passed in the constructor. */ - private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { - private final List javaFiles; + /** The message about the required getTestDirs method. */ + private static final String requiredFormsMessage = + "Parameter method must have the following form:" + + System.lineSeparator() + + "@Parameters String[] getTestDirs()"; - PerParameterSetTestRunner(Class type, List javaFiles) throws InitializationError { - super(type); - this.javaFiles = javaFiles; - } + /** Runs the test class for the set of javaFiles passed in the constructor. */ + private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { + private final List javaFiles; - @Override - public Object createTest() throws Exception { - Object[] arguments = Collections.singleton(javaFiles).toArray(); - return getTestClass().getOnlyConstructor().newInstance(arguments); - } + PerParameterSetTestRunner(Class type, List javaFiles) throws InitializationError { + super(type); + this.javaFiles = javaFiles; + } - String testCaseName() { - File file = javaFiles.get(0).getParentFile(); - if (file == null) { - throw new Error("root was passed? " + javaFiles.get(0)); - } - return file.getPath(); - } + @Override + public Object createTest() throws Exception { + Object[] arguments = Collections.singleton(javaFiles).toArray(); + return getTestClass().getOnlyConstructor().newInstance(arguments); + } - @Override - protected String getName() { - return String.format("[%s]", testCaseName()); - } + String testCaseName() { + File file = javaFiles.get(0).getParentFile(); + if (file == null) { + throw new Error("root was passed? " + javaFiles.get(0)); + } + return file.getPath(); + } - @Override - protected String testName(FrameworkMethod method) { - return String.format("%s[%s]", method.getName(), testCaseName()); - } + @Override + protected String getName() { + return String.format("[%s]", testCaseName()); + } - @Override - protected void validateZeroArgConstructor(List errors) { - // constructor should have args. - } + @Override + protected String testName(FrameworkMethod method) { + return String.format("%s[%s]", method.getName(), testCaseName()); + } - @Override - protected Statement classBlock(RunNotifier notifier) { - return childrenInvoker(notifier); - } + @Override + protected void validateZeroArgConstructor(List errors) { + // constructor should have args. + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); } + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java index 7466ff2cc27..84db8ceb825 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java @@ -1,16 +1,5 @@ package org.checkerframework.framework.test; -import org.checkerframework.javacutil.BugInCF; -import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.Parameterized.Parameters; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; -import org.plumelib.util.CollectionsPlume; - import java.io.File; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -21,6 +10,16 @@ import java.util.Collections; import java.util.List; import java.util.StringJoiner; +import org.checkerframework.javacutil.BugInCF; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; +import org.plumelib.util.CollectionsPlume; // TODO: large parts of this file are the same as PerDirectorySuite.java. // Reduce duplication by moving common parts to an abstract class. @@ -37,189 +36,186 @@ */ public class PerFileSuite extends RootedSuite { - /** Name */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public @interface Name {} - - private final ArrayList runners = new ArrayList<>(); - - @Override - protected List getChildren() { - return runners; + /** Name */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Name {} + + private final ArrayList runners = new ArrayList<>(); + + @Override + protected List getChildren() { + return runners; + } + + /** + * Only called reflectively. Do not use programmatically. + * + * @param klass the class whose tests to run + */ + @SuppressWarnings("nullness") // JUnit needs to be annotated + public PerFileSuite(Class klass) throws Throwable { + super(klass, Collections.emptyList()); + TestClass testClass = getTestClass(); + Class javaTestClass = testClass.getJavaClass(); + List parametersList = getParametersList(testClass); + + for (Object[] parameters : parametersList) { + runners.add(new PerParameterSetTestRunner(javaTestClass, resolveTestDirectory(), parameters)); } - - /** - * Only called reflectively. Do not use programmatically. - * - * @param klass the class whose tests to run - */ - @SuppressWarnings("nullness") // JUnit needs to be annotated - public PerFileSuite(Class klass) throws Throwable { - super(klass, Collections.emptyList()); - TestClass testClass = getTestClass(); - Class javaTestClass = testClass.getJavaClass(); - List parametersList = getParametersList(testClass); - - for (Object[] parameters : parametersList) { - runners.add( - new PerParameterSetTestRunner( - javaTestClass, resolveTestDirectory(), parameters)); - } + } + + /** Returns a list of one-element arrays, each containing a Java File. */ + @SuppressWarnings({ + "unchecked", + "nullness" // JUnit needs to be annotated + }) + private List getParametersList(TestClass klass) throws Throwable { + FrameworkMethod method = getParametersMethod(klass); + + List javaFiles; + // We will have either a method getTestDirs which returns String [] or getTestFiles + // which returns List or getParametersMethod would fail + if (method == null) { + throw new BugInCF("no method annotated with @Parameters"); + } + if (method.getReturnType().isArray()) { + String[] dirs = (String[]) method.invokeExplosively(null); + javaFiles = TestUtilities.findRelativeNestedJavaFiles(resolveTestDirectory(), dirs); + } else { + javaFiles = (List) method.invokeExplosively(null); } - /** Returns a list of one-element arrays, each containing a Java File. */ - @SuppressWarnings({ - "unchecked", - "nullness" // JUnit needs to be annotated - }) - private List getParametersList(TestClass klass) throws Throwable { - FrameworkMethod method = getParametersMethod(klass); - - List javaFiles; - // We will have either a method getTestDirs which returns String [] or getTestFiles - // which returns List or getParametersMethod would fail - if (method == null) { - throw new BugInCF("no method annotated with @Parameters"); - } - if (method.getReturnType().isArray()) { - String[] dirs = (String[]) method.invokeExplosively(null); - javaFiles = TestUtilities.findRelativeNestedJavaFiles(resolveTestDirectory(), dirs); - } else { - javaFiles = (List) method.invokeExplosively(null); + List argumentLists = + CollectionsPlume.mapList((File javaFile) -> new Object[] {javaFile}, javaFiles); + + return argumentLists; + } + + /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ + private FrameworkMethod getParametersMethod(TestClass testClass) { + List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); + if (parameterMethods.size() != 1) { + // Construct error message + + String methods; + if (parameterMethods.isEmpty()) { + methods = "[No methods specified]"; + } else { + StringJoiner sj = new StringJoiner(", "); + for (FrameworkMethod method : parameterMethods) { + sj.add(method.getName()); } + methods = sj.toString(); + } - List argumentLists = - CollectionsPlume.mapList((File javaFile) -> new Object[] {javaFile}, javaFiles); + throw new BugInCF(requiredFormsMessage, testClass.getName(), methods); + } // else - return argumentLists; - } + FrameworkMethod method = parameterMethods.get(0); - /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ - private FrameworkMethod getParametersMethod(TestClass testClass) { - List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); - if (parameterMethods.size() != 1) { - // Construct error message - - String methods; - if (parameterMethods.isEmpty()) { - methods = "[No methods specified]"; - } else { - StringJoiner sj = new StringJoiner(", "); - for (FrameworkMethod method : parameterMethods) { - sj.add(method.getName()); - } - methods = sj.toString(); - } - - throw new BugInCF(requiredFormsMessage, testClass.getName(), methods); - } // else - - FrameworkMethod method = parameterMethods.get(0); - - Class returnType = method.getReturnType(); - String methodName = method.getName(); - switch (methodName) { - case "getTestDirs": - if (returnType.isArray()) { - if (returnType.getComponentType() != String.class) { - throw new RuntimeException( - "Component type of getTestDirs must be java.lang.String, found " - + returnType.getComponentType().getCanonicalName()); - } - } - break; - - case "getTestFiles": - // We'll force people to return a List for now but enforcing exactly List or a - // subtype thereof is not easy. - if (!List.class.getCanonicalName().equals(returnType.getCanonicalName())) { - throw new RuntimeException( - "getTestFiles must return a List, found " + returnType); - } - break; - - default: - throw new BugInCF(requiredFormsMessage, testClass.getName(), method); + Class returnType = method.getReturnType(); + String methodName = method.getName(); + switch (methodName) { + case "getTestDirs": + if (returnType.isArray()) { + if (returnType.getComponentType() != String.class) { + throw new RuntimeException( + "Component type of getTestDirs must be java.lang.String, found " + + returnType.getComponentType().getCanonicalName()); + } } + break; - int modifiers = method.getMethod().getModifiers(); - if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { - throw new RuntimeException( - "Parameter method (" + method.getName() + ") must be public and static"); + case "getTestFiles": + // We'll force people to return a List for now but enforcing exactly List or a + // subtype thereof is not easy. + if (!List.class.getCanonicalName().equals(returnType.getCanonicalName())) { + throw new RuntimeException("getTestFiles must return a List, found " + returnType); } + break; - return method; + default: + throw new BugInCF(requiredFormsMessage, testClass.getName(), method); } - /** The message about the required getTestDirs or getTestFiles method. */ - private static final String requiredFormsMessage = - "Parameter method must have one of the following two forms:%n" - + "@Parameters String [] getTestDirs()%n" - + "@Parameters List getTestFiles()%n" - + "testClass=%s%n" - + "parameterMethods=%s"; - - /** Runs the test class for the set of parameters passed in the constructor. */ - private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { - /** A directory prefix to remove from the test name. */ - private final File directoryPrefix; - - /** The parameters, java source file(s) under {@link #directoryPrefix}. */ - private final Object[] parameters; - - /** - * Creates a PerParameterSetTestRunner for the test class {@code type}. - * - * @param type the test class - * @param directoryPrefix directory prefix to remove from the test name - * @param parameters java source file(s) under {@link #directoryPrefix} - * @throws InitializationError if the test class is malformed - */ - PerParameterSetTestRunner(Class type, File directoryPrefix, Object[] parameters) - throws InitializationError { - super(type); - this.directoryPrefix = directoryPrefix; - this.parameters = parameters; - } + int modifiers = method.getMethod().getModifiers(); + if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { + throw new RuntimeException( + "Parameter method (" + method.getName() + ") must be public and static"); + } - @Override - public Object createTest() throws Exception { - return getTestClass().getOnlyConstructor().newInstance(parameters); - } + return method; + } - /** - * Returns the test case's name. - * - * @return the test case's name - */ - String testCaseName() { - File file = (File) parameters[0]; - String name = - file.getPath() - .replace(".java", "") - .replace(directoryPrefix.getPath() + File.separator, ""); - return name; - } + /** The message about the required getTestDirs or getTestFiles method. */ + private static final String requiredFormsMessage = + "Parameter method must have one of the following two forms:%n" + + "@Parameters String [] getTestDirs()%n" + + "@Parameters List getTestFiles()%n" + + "testClass=%s%n" + + "parameterMethods=%s"; - @Override - protected String getName() { - return String.format("[%s]", testCaseName()); - } + /** Runs the test class for the set of parameters passed in the constructor. */ + private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { + /** A directory prefix to remove from the test name. */ + private final File directoryPrefix; - @Override - protected String testName(FrameworkMethod method) { - return String.format("%s[%s]", method.getName(), testCaseName()); - } + /** The parameters, java source file(s) under {@link #directoryPrefix}. */ + private final Object[] parameters; - @Override - protected void validateZeroArgConstructor(List errors) { - // constructor should have args. - } + /** + * Creates a PerParameterSetTestRunner for the test class {@code type}. + * + * @param type the test class + * @param directoryPrefix directory prefix to remove from the test name + * @param parameters java source file(s) under {@link #directoryPrefix} + * @throws InitializationError if the test class is malformed + */ + PerParameterSetTestRunner(Class type, File directoryPrefix, Object[] parameters) + throws InitializationError { + super(type); + this.directoryPrefix = directoryPrefix; + this.parameters = parameters; + } - @Override - protected Statement classBlock(RunNotifier notifier) { - return childrenInvoker(notifier); - } + @Override + public Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(parameters); + } + + /** + * Returns the test case's name. + * + * @return the test case's name + */ + String testCaseName() { + File file = (File) parameters[0]; + String name = + file.getPath() + .replace(".java", "") + .replace(directoryPrefix.getPath() + File.separator, ""); + return name; + } + + @Override + protected String getName() { + return String.format("[%s]", testCaseName()); + } + + @Override + protected String testName(FrameworkMethod method) { + return String.format("%s[%s]", method.getName(), testCaseName()); + } + + @Override + protected void validateZeroArgConstructor(List errors) { + // constructor should have args. + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); } + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java index 37e2be289ed..6fffafc2871 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java @@ -1,40 +1,38 @@ package org.checkerframework.framework.test; +import java.io.File; +import java.util.List; import org.junit.runner.Runner; import org.junit.runners.Suite; import org.junit.runners.model.InitializationError; -import java.io.File; -import java.util.List; - /** * Encapsulates the directory root to search within for test files to parameterise the test with. */ abstract class RootedSuite extends Suite { - /** - * Called by this class and subclasses once the runners making up the suite have been - * determined. - * - * @param klass root of the suite - * @param runners for each class in the suite, a {@link Runner} - * @throws InitializationError malformed test suite - */ - public RootedSuite(Class klass, List runners) throws InitializationError { - super(klass, runners); - } + /** + * Called by this class and subclasses once the runners making up the suite have been determined. + * + * @param klass root of the suite + * @param runners for each class in the suite, a {@link Runner} + * @throws InitializationError malformed test suite + */ + public RootedSuite(Class klass, List runners) throws InitializationError { + super(klass, runners); + } - /** - * Resolves the directory specified by {@link TestRootDirectory} or defaults to {@code - * currentDir/tests}. - * - * @return the resolved directory - */ - protected final File resolveTestDirectory() { - TestRootDirectory annotation = getTestClass().getAnnotation(TestRootDirectory.class); - if (annotation != null) { - return new File(annotation.value()); - } - return new File("tests"); + /** + * Resolves the directory specified by {@link TestRootDirectory} or defaults to {@code + * currentDir/tests}. + * + * @return the resolved directory + */ + protected final File resolveTestDirectory() { + TestRootDirectory annotation = getTestClass().getAnnotation(TestRootDirectory.class); + if (annotation != null) { + return new File(annotation.value()); } + return new File("tests"); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java index 58e6d9b540d..9ba608db3bb 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.test; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.io.File; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; /** * SimpleOptionMap is a very basic Option container. The keys of the Option container are the set of @@ -25,135 +24,135 @@ * needed to make this class usable from the command line. */ public class SimpleOptionMap { - /** A Map from optionName to arg, where arg is null if the option doesn't require any args. */ - private final Map options = new LinkedHashMap<>(); - - /** - * Clears the current set of options and copies the input options to this map. - * - * @param options the new options to use for this object - */ - public void setOptions(Map options) { - this.options.clear(); - this.options.putAll(options); - } - - /** - * A method to easily add Strings to an option that takes a filepath as an argument. - * - * @param key an option with an argument of the form "arg1[path-separator]arg2..." e.g., "-cp - * myDir:myDir2:myDir3" - * @param toAppend a string to append onto the path or, if the path is null/empty, the argument - * to the option indicated by key - */ - public void addToPathOption(String key, String toAppend) { - if (toAppend == null) { - throw new IllegalArgumentException("Null string appended to sourcePath."); - } - - String path = options.get(key); - - if (toAppend.startsWith(File.pathSeparator)) { - if (path == null || path.isEmpty()) { - path = toAppend.substring(1, toAppend.length()); - } else { - path += toAppend; - } - } else { - if (path == null || path.isEmpty()) { - path = toAppend; - } else { - path += File.pathSeparator + toAppend; - } - } - - addOption(key, path); - } - - /** - * Adds an option that takes no argument. - * - * @param option the no-argument option to add to this object - */ - public void addOption(String option) { - this.options.put(option, null); + /** A Map from optionName to arg, where arg is null if the option doesn't require any args. */ + private final Map options = new LinkedHashMap<>(); + + /** + * Clears the current set of options and copies the input options to this map. + * + * @param options the new options to use for this object + */ + public void setOptions(Map options) { + this.options.clear(); + this.options.putAll(options); + } + + /** + * A method to easily add Strings to an option that takes a filepath as an argument. + * + * @param key an option with an argument of the form "arg1[path-separator]arg2..." e.g., "-cp + * myDir:myDir2:myDir3" + * @param toAppend a string to append onto the path or, if the path is null/empty, the argument to + * the option indicated by key + */ + public void addToPathOption(String key, String toAppend) { + if (toAppend == null) { + throw new IllegalArgumentException("Null string appended to sourcePath."); } - /** - * Adds an option that takes an argument. - * - * @param option the option to add to this object - * @param value the argument to the option - */ - public void addOption(String option, String value) { - this.options.put(option, value); + String path = options.get(key); + + if (toAppend.startsWith(File.pathSeparator)) { + if (path == null || path.isEmpty()) { + path = toAppend.substring(1, toAppend.length()); + } else { + path += toAppend; + } + } else { + if (path == null || path.isEmpty()) { + path = toAppend; + } else { + path += File.pathSeparator + toAppend; + } } - /** - * Adds the option only if value is a non-null, non-empty String. - * - * @param option the option to add to this object - * @param value the argument to the option (or null) - */ - public void addOptionIfValueNonEmpty(String option, @Nullable String value) { - if (value != null && !value.isEmpty()) { - addOption(option, value); - } + addOption(key, path); + } + + /** + * Adds an option that takes no argument. + * + * @param option the no-argument option to add to this object + */ + public void addOption(String option) { + this.options.put(option, null); + } + + /** + * Adds an option that takes an argument. + * + * @param option the option to add to this object + * @param value the argument to the option + */ + public void addOption(String option, String value) { + this.options.put(option, value); + } + + /** + * Adds the option only if value is a non-null, non-empty String. + * + * @param option the option to add to this object + * @param value the argument to the option (or null) + */ + public void addOptionIfValueNonEmpty(String option, @Nullable String value) { + if (value != null && !value.isEmpty()) { + addOption(option, value); } - - /** - * Adds all of the options in the given map to this one. - * - * @param options the options to add to this object - */ - public void addOptions(Map options) { - this.options.putAll(options); - } - - public void addOptions(Iterable newOptions) { - Iterator optIter = newOptions.iterator(); - while (optIter.hasNext()) { - String opt = optIter.next(); - if (this.options.get(opt) != null) { - if (!optIter.hasNext()) { - throw new RuntimeException( - "Expected a value for option: " - + opt - + " in option list: " - + String.join(", ", newOptions)); - } - this.options.put(opt, optIter.next()); - - } else { - this.options.put(opt, null); - } + } + + /** + * Adds all of the options in the given map to this one. + * + * @param options the options to add to this object + */ + public void addOptions(Map options) { + this.options.putAll(options); + } + + public void addOptions(Iterable newOptions) { + Iterator optIter = newOptions.iterator(); + while (optIter.hasNext()) { + String opt = optIter.next(); + if (this.options.get(opt) != null) { + if (!optIter.hasNext()) { + throw new RuntimeException( + "Expected a value for option: " + + opt + + " in option list: " + + String.join(", ", newOptions)); } - } - - /** - * Removes the specified option. - * - * @param option the option to be removed from this object - */ - public void removeOption(String option) { - this.options.remove(option); - } - - /** - * Returns the map that backs this SimpleOptionMap. - * - * @return the options in this object - */ - public Map getOptions() { - return options; - } + this.options.put(opt, optIter.next()); - /** - * Creates a "flat" list representation of these options. - * - * @return a list of the string representations of the options in this object - */ - public List getOptionsAsList() { - return TestUtilities.optionMapToList(options); + } else { + this.options.put(opt, null); + } } + } + + /** + * Removes the specified option. + * + * @param option the option to be removed from this object + */ + public void removeOption(String option) { + this.options.remove(option); + } + + /** + * Returns the map that backs this SimpleOptionMap. + * + * @return the options in this object + */ + public Map getOptions() { + return options; + } + + /** + * Creates a "flat" list representation of these options. + * + * @return a list of the string representations of the options in this object + */ + public List getOptionsAsList() { + return TestUtilities.optionMapToList(options); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java index 9107215b629..a35dba5f29f 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java @@ -1,85 +1,84 @@ package org.checkerframework.framework.test; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; - import java.io.File; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; /** A configuration for running CheckerFrameworkTests or running the TypecheckExecutor. */ public interface TestConfiguration { - /** - * Returns a list of source files a CheckerFrameworkPerDirectoryTest should be run over. These - * source files will be passed to Javac when the test is run. These are NOT JUnit tests. - * - * @return a list of source files a CheckerFrameworkPerDirectoryTest should be run over - */ - List getTestSourceFiles(); + /** + * Returns a list of source files a CheckerFrameworkPerDirectoryTest should be run over. These + * source files will be passed to Javac when the test is run. These are NOT JUnit tests. + * + * @return a list of source files a CheckerFrameworkPerDirectoryTest should be run over + */ + List getTestSourceFiles(); - /** - * Diagnostic files consist of a set of lines that enumerate expected error/warning diagnostics. - * The lines are of the form: - * - *
fileName:lineNumber: diagnostKind: (messageKey)
- * - * e.g., - * - *
MethodInvocation.java:17: error: (method.invocation.invalid)
- * - * If getDiagnosticFiles does NOT return an empty list, then the only diagnostics expected by - * the TestExecutor will be the ones found in these files. If it does return an empty list, then - * the only diagnostics expected will be the ones found in comments in the input test files. - * - *

It is preferred that users write the errors in the test files and not in diagnostic files. - * - * @return a List of diagnostic files containing the error/warning messages expected to be - * output when Javac is run on the files returned by getTestSourceFiles. Return an empty - * list if these messages were specified within the source files. - */ - List getDiagnosticFiles(); + /** + * Diagnostic files consist of a set of lines that enumerate expected error/warning diagnostics. + * The lines are of the form: + * + *

fileName:lineNumber: diagnostKind: (messageKey)
+ * + * e.g., + * + *
MethodInvocation.java:17: error: (method.invocation.invalid)
+ * + * If getDiagnosticFiles does NOT return an empty list, then the only diagnostics expected by the + * TestExecutor will be the ones found in these files. If it does return an empty list, then the + * only diagnostics expected will be the ones found in comments in the input test files. + * + *

It is preferred that users write the errors in the test files and not in diagnostic files. + * + * @return a List of diagnostic files containing the error/warning messages expected to be output + * when Javac is run on the files returned by getTestSourceFiles. Return an empty list if + * these messages were specified within the source files. + */ + List getDiagnosticFiles(); - /** - * Returns a list of annotation processors (Checkers) passed to the Javac compiler. - * - * @return a list of annotation processors (Checkers) passed to the Javac compiler - */ - List<@BinaryName String> getProcessors(); + /** + * Returns a list of annotation processors (Checkers) passed to the Javac compiler. + * + * @return a list of annotation processors (Checkers) passed to the Javac compiler + */ + List<@BinaryName String> getProcessors(); - /** - * Some Javac command line arguments require arguments themselves (e.g. {@code -classpath} takes - * a path) getOptions returns a {@code Map(optionName => optionArgumentIfAny)}. If an option - * does not take an argument, pass null as the value. - * - *

E.g., - * - *

{@code
-     * Map(
-     *     "-AprintAllQualifiers" => null
-     *     "-classpath" => "myDir1:myDir2"
-     * )
-     * }
- * - * @return a Map representing all command-line options to Javac other than source files and - * processors - */ - Map getOptions(); + /** + * Some Javac command line arguments require arguments themselves (e.g. {@code -classpath} takes a + * path) getOptions returns a {@code Map(optionName => optionArgumentIfAny)}. If an option does + * not take an argument, pass null as the value. + * + *

E.g., + * + *

{@code
+   * Map(
+   *     "-AprintAllQualifiers" => null
+   *     "-classpath" => "myDir1:myDir2"
+   * )
+   * }
+ * + * @return a Map representing all command-line options to Javac other than source files and + * processors + */ + Map getOptions(); - /** - * Returns the map returned by {@link #getOptions}, flattened into a list. The entries will be - * added as followed: List(key1, value1, key2, value2, ..., keyN, valueN). If a value is NULL, - * then it will not appear in the list. - * - * @return the map returned {@link #getOptions}, but flattened into a list - */ - List getFlatOptions(); + /** + * Returns the map returned by {@link #getOptions}, flattened into a list. The entries will be + * added as followed: List(key1, value1, key2, value2, ..., keyN, valueN). If a value is NULL, + * then it will not appear in the list. + * + * @return the map returned {@link #getOptions}, but flattened into a list + */ + List getFlatOptions(); - /** - * Returns true if the TypecheckExecutor should emit debug information on system out, false - * otherwise. - * - * @return true if the TypecheckExecutor should emit debug information on system out, false - * otherwise - */ - boolean shouldEmitDebugInfo(); + /** + * Returns true if the TypecheckExecutor should emit debug information on system out, false + * otherwise. + * + * @return true if the TypecheckExecutor should emit debug information on system out, false + * otherwise + */ + boolean shouldEmitDebugInfo(); } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java index a564ff12c53..323eee4f98e 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.test; -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.javacutil.BugInCF; -import org.plumelib.util.StringsPlume; - import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -16,6 +9,12 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.javacutil.BugInCF; +import org.plumelib.util.StringsPlume; /** * Used to create an instance of TestConfiguration. TestConfigurationBuilder is fluent: it returns @@ -28,591 +27,590 @@ */ public class TestConfigurationBuilder { - // Presented first are static helper methods that reduce configuration building to a method - // call. - // However, if you need more complex configuration or custom configuration, use the - // constructors provided below. - - /** - * This creates a builder for the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the - * directory of Checker's tests - * @param outputClassDirectory the directory to place classes compiled for testing - * @param classPath the classpath to use for compilation - * @param testSourceFiles the Java files that compose the test - * @param processors the checkers or other annotation processors to run over the testSourceFiles - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return the builder that will create an immutable test configuration - */ - public static TestConfigurationBuilder getDefaultConfigurationBuilder( - String testSourcePath, - File outputClassDirectory, - String classPath, - Iterable testSourceFiles, - Iterable<@BinaryName String> processors, - List options, - boolean shouldEmitDebugInfo) { - - TestConfigurationBuilder configBuilder = - new TestConfigurationBuilder() - .setShouldEmitDebugInfo(shouldEmitDebugInfo) - .addProcessors(processors) - .addOption("-Xmaxerrs", "9999") - .addOption("-Xmaxwarns", "9999") - .addOption("-g") - .addOption("-Xlint:unchecked") - .addOption("-Xlint:deprecation") - .addOption("-XDrawDiagnostics") // use short javac diagnostics - .addOption("-ApermitMissingJdk") - .addOption("-AnoJreVersionCheck"); - - // -Anomsgtext is needed to ensure expected errors can be matched, which is the - // right thing for most test cases. - // Note that this will be removed if -Adetailedmsgtext is added to the configuration. - // Check `TestConfigurationBuilder#removeConflicts()` for more details. - configBuilder.addOption("-Anomsgtext"); - - // TODO: decide whether this would be useful - // configBuilder.addOption("-AajavaChecks"); - - if (outputClassDirectory != null) { - configBuilder.addOption("-d", outputClassDirectory.getAbsolutePath()); - } - - configBuilder - .addOptionIfValueNonEmpty("-sourcepath", testSourcePath) - .addOption("-implicit:class") - .addOption("-classpath", classPath); - - configBuilder.addOptions(options); - - configBuilder.addSourceFiles(testSourceFiles); - return configBuilder; - } - - /** - * This is the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the - * directory of Checker's tests - * @param testFile a single test Java file to compile - * @param processor a single checker to include in the processors field - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return a TestConfiguration with input parameters added plus the normal default options, - * compiler, and file manager used by Checker Framework tests - */ - @SuppressWarnings( - "signature:argument.type.incompatible" // for non-array non-primitive class, getName(): - // @BinaryName - ) - public static TestConfiguration buildDefaultConfiguration( - String testSourcePath, - File testFile, - Class processor, - List options, - boolean shouldEmitDebugInfo) { - return buildDefaultConfiguration( - testSourcePath, - Arrays.asList(testFile), - Collections.emptyList(), - Arrays.asList(processor.getName()), - options, - shouldEmitDebugInfo); - } - - /** - * This is the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the - * directory of Checker's tests - * @param testSourceFiles the Java files that compose the test - * @param processors the checkers or other annotation processors to run over the testSourceFiles - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return a TestConfiguration with input parameters added plus the normal default options, - * compiler, and file manager used by Checker Framework tests - */ - public static TestConfiguration buildDefaultConfiguration( - String testSourcePath, - Iterable testSourceFiles, - Iterable<@BinaryName String> processors, - List options, - boolean shouldEmitDebugInfo) { - return buildDefaultConfiguration( - testSourcePath, - testSourceFiles, - Collections.emptyList(), - processors, - options, - shouldEmitDebugInfo); - } - - /** - * This is the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the - * directory of Checker's tests - * @param testSourceFiles the Java files that compose the test - * @param classpathExtra extra entries for the classpath, needed to compile the source files - * @param processors the checkers or other annotation processors to run over the testSourceFiles - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return a TestConfiguration with input parameters added plus the normal default options, - * compiler, and file manager used by Checker Framework tests - */ - public static TestConfiguration buildDefaultConfiguration( - String testSourcePath, - Iterable testSourceFiles, - Collection classpathExtra, - Iterable<@BinaryName String> processors, - List options, - boolean shouldEmitDebugInfo) { - - String classPath = getDefaultClassPath(); - if (!classpathExtra.isEmpty()) { - classPath += - System.getProperty("path.separator") - + String.join(System.getProperty("path.separator"), classpathExtra); - } - - File outputDir = getOutputDirFromProperty(); - - TestConfigurationBuilder builder = - getDefaultConfigurationBuilder( - testSourcePath, - outputDir, - classPath, - testSourceFiles, - processors, - options, - shouldEmitDebugInfo); - return builder.validateThenBuild(true); - } - - /** The list of files that contain Java diagnostics to compare against. */ - private List diagnosticFiles; - - /** The set of Java files to test against. */ - private List testSourceFiles; - - /** The set of Checker Framework processors to test with. */ - private final Set<@BinaryName String> processors; - - /** The set of options to the Javac command line used to run the test. */ - private final SimpleOptionMap options; - - /** Should the Javac options be output before running the test. */ - private boolean shouldEmitDebugInfo; - - /** - * Note: There are static helper methods named buildConfiguration and buildConfigurationBuilder - * that can be used to create the most common types of configurations - */ - public TestConfigurationBuilder() { - diagnosticFiles = new ArrayList<>(); - testSourceFiles = new ArrayList<>(); - processors = new LinkedHashSet<>(); - options = new SimpleOptionMap(); - shouldEmitDebugInfo = false; - } - - /** - * Create a builder that has all of the options in initialConfig. - * - * @param initialConfig initial configuration for the newly-created builder - */ - public TestConfigurationBuilder(TestConfiguration initialConfig) { - this.diagnosticFiles = new ArrayList<>(initialConfig.getDiagnosticFiles()); - this.testSourceFiles = new ArrayList<>(initialConfig.getTestSourceFiles()); - this.processors = new LinkedHashSet<>(initialConfig.getProcessors()); - this.options = new SimpleOptionMap(); - this.addOptions(initialConfig.getOptions()); - - this.shouldEmitDebugInfo = initialConfig.shouldEmitDebugInfo(); - } - - /** - * Ensures that the minimum requirements for running a test are met. These requirements are: - * - *
    - *
  • There is at least one source file - *
  • There is at least one processor (if requireProcessors has been set to true) - *
  • There is an output directory specified for class files - *
  • There is no {@code -processor} option in the optionMap (it should be added by - * addProcessor instead) - *
  • There is no option with prefix "-J-" in the optionMap - *
- * - * @param requireProcessors whether or not to require that there is at least one processor - * @return a list of errors found while validating this configuration - */ - public List validate(boolean requireProcessors) { - List errors = new ArrayList<>(); - if (testSourceFiles == null || !testSourceFiles.iterator().hasNext()) { - errors.add("No source files specified!"); - } - - if (requireProcessors && !processors.iterator().hasNext()) { - errors.add("No processors were specified!"); - } - - Map optionMap = options.getOptions(); - if (!optionMap.containsKey("-d") || optionMap.get("-d") == null) { - errors.add("No output directory was specified."); - } - - if (optionMap.containsKey("-processor")) { - errors.add("Processors should not be added to the options list"); - } - - StringBuilder jvmOptionKeys = new StringBuilder(); - for (String optionKey : optionMap.keySet()) { - if (optionKey.startsWith("-J-")) { - jvmOptionKeys.append(optionKey).append('\n'); - } - } - if (jvmOptionKeys.length() > 0) { - errors.add( - "The following JVM options have no effects in a configuration.\n" - + jvmOptionKeys - + "If needed, please add them to your build file instead."); - } - - return errors; - } - - /** Ensures there are no options conflicting with each other. */ - protected void removeConflicts() { - final Map optionMap = options.getOptions(); - if (optionMap.containsKey("-Adetailedmsgtext")) { - // If `detailedmsgtext` is specified, remove `nomsgtext`. - options.removeOption("-Anomsgtext"); - } - } - - /** - * Adds the given path option to {@code this}. - * - * @param key the key to add - * @param toAppend the path to append - * @return the current object {@code this} - */ - public TestConfigurationBuilder adddToPathOption(String key, String toAppend) { - this.options.addToPathOption(key, toAppend); - return this; - } - - /** - * Adds the given diagnostics file to {@code this}. - * - * @param diagnostics the diagnostics file to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addDiagnosticFile(File diagnostics) { - this.diagnosticFiles.add(diagnostics); - return this; - } - - /** - * Adds the given diagnostics files to {@code this}. - * - * @param diagnostics diagnostics files to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addDiagnosticFiles(Iterable diagnostics) { - this.diagnosticFiles = catListAndIterable(diagnosticFiles, diagnostics); - return this; - } - - /** - * Sets the diagnostics files of {@code this}. - * - * @param diagnosticFiles diagnostics files to set on {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder setDiagnosticFiles(List diagnosticFiles) { - this.diagnosticFiles = new ArrayList<>(diagnosticFiles); - return this; - } - - /** - * Adds the given source file to {@code this}. - * - * @param sourceFile source file to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addSourceFile(File sourceFile) { - this.testSourceFiles.add(sourceFile); - return this; - } - - /** - * Adds the given source files to {@code this}. - * - * @param sourceFiles source files to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addSourceFiles(Iterable sourceFiles) { - this.testSourceFiles = catListAndIterable(testSourceFiles, sourceFiles); - return this; - } - - /** - * Sets the source files of {@code this}. - * - * @param sourceFiles source files to set on {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder setSourceFiles(List sourceFiles) { - this.testSourceFiles = new ArrayList<>(sourceFiles); - return this; - } - - /** - * Sets the given options on {@code this}. - * - * @param options options to set on {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder setOptions(Map options) { - this.options.setOptions(options); - return this; - } - - /** - * Adds the given option to {@code this}. - * - * @param option option to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOption(String option) { - this.options.addOption(option); - return this; - } - - /** - * Adds the given option and value to {@code this}. - * - * @param option option to add to {@code this} - * @param value value to add - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOption(String option, String value) { - this.options.addOption(option, value); - return this; - } - - /** - * Adds the given option to {@code this} if the value is non-empty. - * - * @param option option to add to {@code this} - * @param value value to add, iff it is non-empty - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOptionIfValueNonEmpty(String option, String value) { - if (value != null && !value.isEmpty()) { - return addOption(option, value); - } - - return this; - } - - /** - * Adds the given options to {@code this}. - * - * @param options options to add to {@code this} - * @return the current object {@code this} - */ - @SuppressWarnings("initialization:return.type.incompatible") // need @PolyInitialized annotation - @RequiresNonNull("this.options") - public TestConfigurationBuilder addOptions( - @UnknownInitialization(TestConfigurationBuilder.class) TestConfigurationBuilder this, - Map options) { - this.options.addOptions(options); - return this; - } - - /** - * Adds the given options to {@code this}. - * - * @param newOptions options to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOptions(Iterable newOptions) { - this.options.addOptions(newOptions); - return this; - } - - /** - * Set the processors. - * - * @param processors the processors to run - * @return this - */ - public TestConfigurationBuilder setProcessors(Iterable<@BinaryName String> processors) { - this.processors.clear(); - for (String proc : processors) { - this.processors.add(proc); - } - return this; - } - - /** - * Add a processor. - * - * @param processor a processor to run - * @return this - */ - public TestConfigurationBuilder addProcessor(@BinaryName String processor) { - this.processors.add(processor); - return this; - } - - /** - * Add processors. - * - * @param processors processors to run - * @return this - */ - public TestConfigurationBuilder addProcessors(Iterable<@BinaryName String> processors) { - for (String processor : processors) { - this.processors.add(processor); - } - - return this; - } - - /** - * Sets {@code this} to output debug info. - * - * @return the current object {@code this} - */ - public TestConfigurationBuilder emitDebugInfo() { - this.shouldEmitDebugInfo = true; - return this; - } - - /** - * Sets {@code this} to not output debug info. - * - * @return the current object {@code this} - */ - public TestConfigurationBuilder dontEmitDebugInfo() { - this.shouldEmitDebugInfo = false; - return this; - } - - /** - * Sets {@code this} to output debug info depending on the parameter. - * - * @param shouldEmitDebugInfo whether to emit debug info - * @return the current object {@code this} - */ - public TestConfigurationBuilder setShouldEmitDebugInfo(boolean shouldEmitDebugInfo) { - this.shouldEmitDebugInfo = shouldEmitDebugInfo; - return this; - } - - /** - * Creates a TestConfiguration using the settings in this builder. The settings are NOT - * validated first. - * - * @return a TestConfiguration using the settings in this builder - */ - public TestConfiguration build() { - return new ImmutableTestConfiguration( - diagnosticFiles, - testSourceFiles, - new ArrayList<>(processors), - options.getOptions(), - shouldEmitDebugInfo); - } - - /** - * Creates a TestConfiguration using the settings in this builder. The settings are first - * validated and a runtime exception is thrown if any errors are found - * - * @param requireProcessors whether or not there should be at least 1 processor specified, see - * method validate - * @return a TestConfiguration using the settings in this builder - */ - public TestConfiguration validateThenBuild(boolean requireProcessors) { - removeConflicts(); - List errors = validate(requireProcessors); - if (errors.isEmpty()) { - return build(); - } - - throw new BugInCF( - "Attempted to build invalid test configuration:%n" + "Errors:%n%s%n%s%n", - String.join("%n", errors), this); - } - - /** - * Returns the set of Javac options as a flat list. - * - * @return the set of Javac options as a flat list - */ - public List flatOptions() { - return options.getOptionsAsList(); - } - - @Override - public String toString() { - return StringsPlume.joinLines( - "TestConfigurationBuilder:", - "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), - "processors=" + String.join(", ", processors), - "options=" + String.join(", ", options.getOptionsAsList()), - "shouldEmitDebugInfo=" + shouldEmitDebugInfo); - } - - /** - * Returns a list that first has the items from parameter list then the items from iterable. - * - * @param the type of the elements in the resulting list - * @param list a list - * @param iterable an iterable - * @return a list that first has the items from parameter list then the items from iterable - */ - private static List catListAndIterable( - List list, Iterable iterable) { - List newList = new ArrayList<>(list); - - for (T iterObject : iterable) { - newList.add(iterObject); - } - - return newList; - } - - /** The output directory for tests. */ - public static final String TESTS_OUTPUTDIR = "tests.outputDir"; - - /** - * Determine the output directory from the {@code tests.outputDir} property. - * - * @return the output directory - */ - public static File getOutputDirFromProperty() { - return new File( - System.getProperty( - "tests.outputDir", - "tests" + File.separator + "build" + File.separator + "testclasses")); - } - - /** - * Determine the default classpath from the {@code tests.classpath} property. - * - * @return the default classpath - */ - public static String getDefaultClassPath() { - String classpath = - System.getProperty("tests.classpath", "tests" + File.separator + "build"); - String globalclasspath = System.getProperty("java.class.path", ""); - return classpath + File.pathSeparator + globalclasspath; - } + // Presented first are static helper methods that reduce configuration building to a method + // call. + // However, if you need more complex configuration or custom configuration, use the + // constructors provided below. + + /** + * This creates a builder for the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the directory + * of Checker's tests + * @param outputClassDirectory the directory to place classes compiled for testing + * @param classPath the classpath to use for compilation + * @param testSourceFiles the Java files that compose the test + * @param processors the checkers or other annotation processors to run over the testSourceFiles + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return the builder that will create an immutable test configuration + */ + public static TestConfigurationBuilder getDefaultConfigurationBuilder( + String testSourcePath, + File outputClassDirectory, + String classPath, + Iterable testSourceFiles, + Iterable<@BinaryName String> processors, + List options, + boolean shouldEmitDebugInfo) { + + TestConfigurationBuilder configBuilder = + new TestConfigurationBuilder() + .setShouldEmitDebugInfo(shouldEmitDebugInfo) + .addProcessors(processors) + .addOption("-Xmaxerrs", "9999") + .addOption("-Xmaxwarns", "9999") + .addOption("-g") + .addOption("-Xlint:unchecked") + .addOption("-Xlint:deprecation") + .addOption("-XDrawDiagnostics") // use short javac diagnostics + .addOption("-ApermitMissingJdk") + .addOption("-AnoJreVersionCheck"); + + // -Anomsgtext is needed to ensure expected errors can be matched, which is the + // right thing for most test cases. + // Note that this will be removed if -Adetailedmsgtext is added to the configuration. + // Check `TestConfigurationBuilder#removeConflicts()` for more details. + configBuilder.addOption("-Anomsgtext"); + + // TODO: decide whether this would be useful + // configBuilder.addOption("-AajavaChecks"); + + if (outputClassDirectory != null) { + configBuilder.addOption("-d", outputClassDirectory.getAbsolutePath()); + } + + configBuilder + .addOptionIfValueNonEmpty("-sourcepath", testSourcePath) + .addOption("-implicit:class") + .addOption("-classpath", classPath); + + configBuilder.addOptions(options); + + configBuilder.addSourceFiles(testSourceFiles); + return configBuilder; + } + + /** + * This is the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the directory + * of Checker's tests + * @param testFile a single test Java file to compile + * @param processor a single checker to include in the processors field + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return a TestConfiguration with input parameters added plus the normal default options, + * compiler, and file manager used by Checker Framework tests + */ + @SuppressWarnings( + "signature:argument.type.incompatible" // for non-array non-primitive class, getName(): + // @BinaryName + ) + public static TestConfiguration buildDefaultConfiguration( + String testSourcePath, + File testFile, + Class processor, + List options, + boolean shouldEmitDebugInfo) { + return buildDefaultConfiguration( + testSourcePath, + Arrays.asList(testFile), + Collections.emptyList(), + Arrays.asList(processor.getName()), + options, + shouldEmitDebugInfo); + } + + /** + * This is the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the directory + * of Checker's tests + * @param testSourceFiles the Java files that compose the test + * @param processors the checkers or other annotation processors to run over the testSourceFiles + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return a TestConfiguration with input parameters added plus the normal default options, + * compiler, and file manager used by Checker Framework tests + */ + public static TestConfiguration buildDefaultConfiguration( + String testSourcePath, + Iterable testSourceFiles, + Iterable<@BinaryName String> processors, + List options, + boolean shouldEmitDebugInfo) { + return buildDefaultConfiguration( + testSourcePath, + testSourceFiles, + Collections.emptyList(), + processors, + options, + shouldEmitDebugInfo); + } + + /** + * This is the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the directory + * of Checker's tests + * @param testSourceFiles the Java files that compose the test + * @param classpathExtra extra entries for the classpath, needed to compile the source files + * @param processors the checkers or other annotation processors to run over the testSourceFiles + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return a TestConfiguration with input parameters added plus the normal default options, + * compiler, and file manager used by Checker Framework tests + */ + public static TestConfiguration buildDefaultConfiguration( + String testSourcePath, + Iterable testSourceFiles, + Collection classpathExtra, + Iterable<@BinaryName String> processors, + List options, + boolean shouldEmitDebugInfo) { + + String classPath = getDefaultClassPath(); + if (!classpathExtra.isEmpty()) { + classPath += + System.getProperty("path.separator") + + String.join(System.getProperty("path.separator"), classpathExtra); + } + + File outputDir = getOutputDirFromProperty(); + + TestConfigurationBuilder builder = + getDefaultConfigurationBuilder( + testSourcePath, + outputDir, + classPath, + testSourceFiles, + processors, + options, + shouldEmitDebugInfo); + return builder.validateThenBuild(true); + } + + /** The list of files that contain Java diagnostics to compare against. */ + private List diagnosticFiles; + + /** The set of Java files to test against. */ + private List testSourceFiles; + + /** The set of Checker Framework processors to test with. */ + private final Set<@BinaryName String> processors; + + /** The set of options to the Javac command line used to run the test. */ + private final SimpleOptionMap options; + + /** Should the Javac options be output before running the test. */ + private boolean shouldEmitDebugInfo; + + /** + * Note: There are static helper methods named buildConfiguration and buildConfigurationBuilder + * that can be used to create the most common types of configurations + */ + public TestConfigurationBuilder() { + diagnosticFiles = new ArrayList<>(); + testSourceFiles = new ArrayList<>(); + processors = new LinkedHashSet<>(); + options = new SimpleOptionMap(); + shouldEmitDebugInfo = false; + } + + /** + * Create a builder that has all of the options in initialConfig. + * + * @param initialConfig initial configuration for the newly-created builder + */ + public TestConfigurationBuilder(TestConfiguration initialConfig) { + this.diagnosticFiles = new ArrayList<>(initialConfig.getDiagnosticFiles()); + this.testSourceFiles = new ArrayList<>(initialConfig.getTestSourceFiles()); + this.processors = new LinkedHashSet<>(initialConfig.getProcessors()); + this.options = new SimpleOptionMap(); + this.addOptions(initialConfig.getOptions()); + + this.shouldEmitDebugInfo = initialConfig.shouldEmitDebugInfo(); + } + + /** + * Ensures that the minimum requirements for running a test are met. These requirements are: + * + *
    + *
  • There is at least one source file + *
  • There is at least one processor (if requireProcessors has been set to true) + *
  • There is an output directory specified for class files + *
  • There is no {@code -processor} option in the optionMap (it should be added by + * addProcessor instead) + *
  • There is no option with prefix "-J-" in the optionMap + *
+ * + * @param requireProcessors whether or not to require that there is at least one processor + * @return a list of errors found while validating this configuration + */ + public List validate(boolean requireProcessors) { + List errors = new ArrayList<>(); + if (testSourceFiles == null || !testSourceFiles.iterator().hasNext()) { + errors.add("No source files specified!"); + } + + if (requireProcessors && !processors.iterator().hasNext()) { + errors.add("No processors were specified!"); + } + + Map optionMap = options.getOptions(); + if (!optionMap.containsKey("-d") || optionMap.get("-d") == null) { + errors.add("No output directory was specified."); + } + + if (optionMap.containsKey("-processor")) { + errors.add("Processors should not be added to the options list"); + } + + StringBuilder jvmOptionKeys = new StringBuilder(); + for (String optionKey : optionMap.keySet()) { + if (optionKey.startsWith("-J-")) { + jvmOptionKeys.append(optionKey).append('\n'); + } + } + if (jvmOptionKeys.length() > 0) { + errors.add( + "The following JVM options have no effects in a configuration.\n" + + jvmOptionKeys + + "If needed, please add them to your build file instead."); + } + + return errors; + } + + /** Ensures there are no options conflicting with each other. */ + protected void removeConflicts() { + final Map optionMap = options.getOptions(); + if (optionMap.containsKey("-Adetailedmsgtext")) { + // If `detailedmsgtext` is specified, remove `nomsgtext`. + options.removeOption("-Anomsgtext"); + } + } + + /** + * Adds the given path option to {@code this}. + * + * @param key the key to add + * @param toAppend the path to append + * @return the current object {@code this} + */ + public TestConfigurationBuilder adddToPathOption(String key, String toAppend) { + this.options.addToPathOption(key, toAppend); + return this; + } + + /** + * Adds the given diagnostics file to {@code this}. + * + * @param diagnostics the diagnostics file to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addDiagnosticFile(File diagnostics) { + this.diagnosticFiles.add(diagnostics); + return this; + } + + /** + * Adds the given diagnostics files to {@code this}. + * + * @param diagnostics diagnostics files to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addDiagnosticFiles(Iterable diagnostics) { + this.diagnosticFiles = catListAndIterable(diagnosticFiles, diagnostics); + return this; + } + + /** + * Sets the diagnostics files of {@code this}. + * + * @param diagnosticFiles diagnostics files to set on {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder setDiagnosticFiles(List diagnosticFiles) { + this.diagnosticFiles = new ArrayList<>(diagnosticFiles); + return this; + } + + /** + * Adds the given source file to {@code this}. + * + * @param sourceFile source file to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addSourceFile(File sourceFile) { + this.testSourceFiles.add(sourceFile); + return this; + } + + /** + * Adds the given source files to {@code this}. + * + * @param sourceFiles source files to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addSourceFiles(Iterable sourceFiles) { + this.testSourceFiles = catListAndIterable(testSourceFiles, sourceFiles); + return this; + } + + /** + * Sets the source files of {@code this}. + * + * @param sourceFiles source files to set on {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder setSourceFiles(List sourceFiles) { + this.testSourceFiles = new ArrayList<>(sourceFiles); + return this; + } + + /** + * Sets the given options on {@code this}. + * + * @param options options to set on {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder setOptions(Map options) { + this.options.setOptions(options); + return this; + } + + /** + * Adds the given option to {@code this}. + * + * @param option option to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOption(String option) { + this.options.addOption(option); + return this; + } + + /** + * Adds the given option and value to {@code this}. + * + * @param option option to add to {@code this} + * @param value value to add + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOption(String option, String value) { + this.options.addOption(option, value); + return this; + } + + /** + * Adds the given option to {@code this} if the value is non-empty. + * + * @param option option to add to {@code this} + * @param value value to add, iff it is non-empty + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOptionIfValueNonEmpty(String option, String value) { + if (value != null && !value.isEmpty()) { + return addOption(option, value); + } + + return this; + } + + /** + * Adds the given options to {@code this}. + * + * @param options options to add to {@code this} + * @return the current object {@code this} + */ + @SuppressWarnings("initialization:return.type.incompatible") // need @PolyInitialized annotation + @RequiresNonNull("this.options") + public TestConfigurationBuilder addOptions( + @UnknownInitialization(TestConfigurationBuilder.class) TestConfigurationBuilder this, + Map options) { + this.options.addOptions(options); + return this; + } + + /** + * Adds the given options to {@code this}. + * + * @param newOptions options to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOptions(Iterable newOptions) { + this.options.addOptions(newOptions); + return this; + } + + /** + * Set the processors. + * + * @param processors the processors to run + * @return this + */ + public TestConfigurationBuilder setProcessors(Iterable<@BinaryName String> processors) { + this.processors.clear(); + for (String proc : processors) { + this.processors.add(proc); + } + return this; + } + + /** + * Add a processor. + * + * @param processor a processor to run + * @return this + */ + public TestConfigurationBuilder addProcessor(@BinaryName String processor) { + this.processors.add(processor); + return this; + } + + /** + * Add processors. + * + * @param processors processors to run + * @return this + */ + public TestConfigurationBuilder addProcessors(Iterable<@BinaryName String> processors) { + for (String processor : processors) { + this.processors.add(processor); + } + + return this; + } + + /** + * Sets {@code this} to output debug info. + * + * @return the current object {@code this} + */ + public TestConfigurationBuilder emitDebugInfo() { + this.shouldEmitDebugInfo = true; + return this; + } + + /** + * Sets {@code this} to not output debug info. + * + * @return the current object {@code this} + */ + public TestConfigurationBuilder dontEmitDebugInfo() { + this.shouldEmitDebugInfo = false; + return this; + } + + /** + * Sets {@code this} to output debug info depending on the parameter. + * + * @param shouldEmitDebugInfo whether to emit debug info + * @return the current object {@code this} + */ + public TestConfigurationBuilder setShouldEmitDebugInfo(boolean shouldEmitDebugInfo) { + this.shouldEmitDebugInfo = shouldEmitDebugInfo; + return this; + } + + /** + * Creates a TestConfiguration using the settings in this builder. The settings are NOT validated + * first. + * + * @return a TestConfiguration using the settings in this builder + */ + public TestConfiguration build() { + return new ImmutableTestConfiguration( + diagnosticFiles, + testSourceFiles, + new ArrayList<>(processors), + options.getOptions(), + shouldEmitDebugInfo); + } + + /** + * Creates a TestConfiguration using the settings in this builder. The settings are first + * validated and a runtime exception is thrown if any errors are found + * + * @param requireProcessors whether or not there should be at least 1 processor specified, see + * method validate + * @return a TestConfiguration using the settings in this builder + */ + public TestConfiguration validateThenBuild(boolean requireProcessors) { + removeConflicts(); + List errors = validate(requireProcessors); + if (errors.isEmpty()) { + return build(); + } + + throw new BugInCF( + "Attempted to build invalid test configuration:%n" + "Errors:%n%s%n%s%n", + String.join("%n", errors), this); + } + + /** + * Returns the set of Javac options as a flat list. + * + * @return the set of Javac options as a flat list + */ + public List flatOptions() { + return options.getOptionsAsList(); + } + + @Override + public String toString() { + return StringsPlume.joinLines( + "TestConfigurationBuilder:", + "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), + "processors=" + String.join(", ", processors), + "options=" + String.join(", ", options.getOptionsAsList()), + "shouldEmitDebugInfo=" + shouldEmitDebugInfo); + } + + /** + * Returns a list that first has the items from parameter list then the items from iterable. + * + * @param the type of the elements in the resulting list + * @param list a list + * @param iterable an iterable + * @return a list that first has the items from parameter list then the items from iterable + */ + private static List catListAndIterable( + List list, Iterable iterable) { + List newList = new ArrayList<>(list); + + for (T iterObject : iterable) { + newList.add(iterObject); + } + + return newList; + } + + /** The output directory for tests. */ + public static final String TESTS_OUTPUTDIR = "tests.outputDir"; + + /** + * Determine the output directory from the {@code tests.outputDir} property. + * + * @return the output directory + */ + public static File getOutputDirFromProperty() { + return new File( + System.getProperty( + "tests.outputDir", + "tests" + File.separator + "build" + File.separator + "testclasses")); + } + + /** + * Determine the default classpath from the {@code tests.classpath} property. + * + * @return the default classpath + */ + public static String getDefaultClassPath() { + String classpath = System.getProperty("tests.classpath", "tests" + File.separator + "build"); + String globalclasspath = System.getProperty("java.class.path", ""); + return classpath + File.pathSeparator + globalclasspath; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java index b288bc7ac2b..9203aee1f8e 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java @@ -11,10 +11,10 @@ @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface TestRootDirectory { - /** - * Path, relative to the current/module directory, within which to search for test sources. - * - * @return tests root directory - */ - String value(); + /** + * Path, relative to the current/module directory, within which to search for test sources. + * + * @return tests root directory + */ + String value(); } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java index ab4cf985122..7288da58ade 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java @@ -1,14 +1,5 @@ package org.checkerframework.framework.test; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.SystemUtil; -import org.junit.Assert; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; -import org.plumelib.util.SystemPlume; - import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -30,548 +21,550 @@ import java.util.Scanner; import java.util.Set; import java.util.StringJoiner; - import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.SystemUtil; +import org.junit.Assert; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; +import org.plumelib.util.SystemPlume; /** Utilities for testing. */ public class TestUtilities { - /** True if the JVM is version 9 or above. */ - public static final boolean IS_AT_LEAST_9_JVM = SystemUtil.jreVersion >= 9; - - /** True if the JVM is version 10 or above. */ - public static final boolean IS_AT_LEAST_10_JVM = SystemUtil.jreVersion >= 10; - - /** True if the JVM is version 11 or above. */ - public static final boolean IS_AT_LEAST_11_JVM = SystemUtil.jreVersion >= 11; - - /** True if the JVM is version 11 or lower. */ - public static final boolean IS_AT_MOST_11_JVM = SystemUtil.jreVersion <= 11; - - /** True if the JVM is version 14 or above. */ - public static final boolean IS_AT_LEAST_14_JVM = SystemUtil.jreVersion >= 14; - - /** True if the JVM is version 14 or lower. */ - public static final boolean IS_AT_MOST_14_JVM = SystemUtil.jreVersion <= 14; - - /** True if the JVM is version 16 or above. */ - public static final boolean IS_AT_LEAST_16_JVM = SystemUtil.jreVersion >= 16; - - /** True if the JVM is version 16 or lower. */ - public static final boolean IS_AT_MOST_16_JVM = SystemUtil.jreVersion <= 16; - - /** True if the JVM is version 17 or above. */ - public static final boolean IS_AT_LEAST_17_JVM = SystemUtil.jreVersion >= 17; - - /** True if the JVM is version 17 or lower. */ - public static final boolean IS_AT_MOST_17_JVM = SystemUtil.jreVersion <= 17; - - /** True if the JVM is version 18 or above. */ - public static final boolean IS_AT_LEAST_18_JVM = SystemUtil.jreVersion >= 18; - - /** True if the JVM is version 18 or lower. */ - public static final boolean IS_AT_MOST_18_JVM = SystemUtil.jreVersion <= 18; - - /** True if the JVM is version 21 or above. */ - public static final boolean IS_AT_LEAST_21_JVM = SystemUtil.jreVersion >= 21; - - /** - * Find test java sources within currentDir/tests. - * - * @param dirNames subdirectories of currentDir/tests - * @return found files - */ - public static List findNestedJavaTestFiles(String... dirNames) { - return findRelativeNestedJavaFiles(new File("tests"), dirNames); + /** True if the JVM is version 9 or above. */ + public static final boolean IS_AT_LEAST_9_JVM = SystemUtil.jreVersion >= 9; + + /** True if the JVM is version 10 or above. */ + public static final boolean IS_AT_LEAST_10_JVM = SystemUtil.jreVersion >= 10; + + /** True if the JVM is version 11 or above. */ + public static final boolean IS_AT_LEAST_11_JVM = SystemUtil.jreVersion >= 11; + + /** True if the JVM is version 11 or lower. */ + public static final boolean IS_AT_MOST_11_JVM = SystemUtil.jreVersion <= 11; + + /** True if the JVM is version 14 or above. */ + public static final boolean IS_AT_LEAST_14_JVM = SystemUtil.jreVersion >= 14; + + /** True if the JVM is version 14 or lower. */ + public static final boolean IS_AT_MOST_14_JVM = SystemUtil.jreVersion <= 14; + + /** True if the JVM is version 16 or above. */ + public static final boolean IS_AT_LEAST_16_JVM = SystemUtil.jreVersion >= 16; + + /** True if the JVM is version 16 or lower. */ + public static final boolean IS_AT_MOST_16_JVM = SystemUtil.jreVersion <= 16; + + /** True if the JVM is version 17 or above. */ + public static final boolean IS_AT_LEAST_17_JVM = SystemUtil.jreVersion >= 17; + + /** True if the JVM is version 17 or lower. */ + public static final boolean IS_AT_MOST_17_JVM = SystemUtil.jreVersion <= 17; + + /** True if the JVM is version 18 or above. */ + public static final boolean IS_AT_LEAST_18_JVM = SystemUtil.jreVersion >= 18; + + /** True if the JVM is version 18 or lower. */ + public static final boolean IS_AT_MOST_18_JVM = SystemUtil.jreVersion <= 18; + + /** True if the JVM is version 21 or above. */ + public static final boolean IS_AT_LEAST_21_JVM = SystemUtil.jreVersion >= 21; + + /** + * Find test java sources within currentDir/tests. + * + * @param dirNames subdirectories of currentDir/tests + * @return found files + */ + public static List findNestedJavaTestFiles(String... dirNames) { + return findRelativeNestedJavaFiles(new File("tests"), dirNames); + } + + /** + * Find test java sources within {@code parent}. + * + * @param parent directory to search within + * @param dirNames subdirectories of {@code parent} + * @return found files + */ + public static List findRelativeNestedJavaFiles(String parent, String... dirNames) { + return findRelativeNestedJavaFiles(new File(parent), dirNames); + } + + /** + * Find test java sources within {@code parent}. + * + * @param parent directory to search within + * @param dirNames subdirectories of {@code parent} + * @return found files + */ + public static List findRelativeNestedJavaFiles(File parent, String... dirNames) { + File[] dirs = new File[dirNames.length]; + + int i = 0; + for (String dirName : dirNames) { + dirs[i] = new File(parent, dirName); + ++i; } - /** - * Find test java sources within {@code parent}. - * - * @param parent directory to search within - * @param dirNames subdirectories of {@code parent} - * @return found files - */ - public static List findRelativeNestedJavaFiles(String parent, String... dirNames) { - return findRelativeNestedJavaFiles(new File(parent), dirNames); + return getJavaFilesAsArgumentList(dirs); + } + + /** + * Returns a list where each item is a list of Java files, excluding any skip tests, for each + * directory given by dirName and also a list for any subdirectory. + * + * @param parent parent directory of the dirNames directories + * @param dirNames names of directories to search + * @return list where each item is a list of Java test files grouped by directory + */ + public static List> findJavaFilesPerDirectory(File parent, String... dirNames) { + if (!parent.exists()) { + throw new BugInCF( + "test parent directory does not exist: %s %s", parent, parent.getAbsoluteFile()); } - - /** - * Find test java sources within {@code parent}. - * - * @param parent directory to search within - * @param dirNames subdirectories of {@code parent} - * @return found files - */ - public static List findRelativeNestedJavaFiles(File parent, String... dirNames) { - File[] dirs = new File[dirNames.length]; - - int i = 0; - for (String dirName : dirNames) { - dirs[i] = new File(parent, dirName); - ++i; - } - - return getJavaFilesAsArgumentList(dirs); + if (!parent.isDirectory()) { + throw new BugInCF( + "test parent directory is not a directory: %s %s", parent, parent.getAbsoluteFile()); } - - /** - * Returns a list where each item is a list of Java files, excluding any skip tests, for each - * directory given by dirName and also a list for any subdirectory. - * - * @param parent parent directory of the dirNames directories - * @param dirNames names of directories to search - * @return list where each item is a list of Java test files grouped by directory - */ - public static List> findJavaFilesPerDirectory(File parent, String... dirNames) { - if (!parent.exists()) { - throw new BugInCF( - "test parent directory does not exist: %s %s", - parent, parent.getAbsoluteFile()); + List> filesPerDirectory = new ArrayList<>(); + + for (String dirName : dirNames) { + File dir = new File(parent, dirName).toPath().toAbsolutePath().normalize().toFile(); + if (dir.isDirectory()) { + filesPerDirectory.addAll(findJavaTestFilesInDirectory(dir)); + } else { + // `dir` is not an existing directory. + // If delombok does not yet work on a given JDK, this directory does not exist. + if (dir.getName().contains("delomboked")) { + continue; } - if (!parent.isDirectory()) { - throw new BugInCF( - "test parent directory is not a directory: %s %s", - parent, parent.getAbsoluteFile()); + // For "ainfer-*" tests, their sources do not necessarily + // exist yet but will be created by a test that runs earlier than they do. + if (dir.getName().equals("annotated") + && dir.getParentFile() != null + && dir.getParentFile().getName().startsWith("ainfer-")) { + continue; } - List> filesPerDirectory = new ArrayList<>(); - - for (String dirName : dirNames) { - File dir = new File(parent, dirName).toPath().toAbsolutePath().normalize().toFile(); - if (dir.isDirectory()) { - filesPerDirectory.addAll(findJavaTestFilesInDirectory(dir)); - } else { - // `dir` is not an existing directory. - // If delombok does not yet work on a given JDK, this directory does not exist. - if (dir.getName().contains("delomboked")) { - continue; - } - // For "ainfer-*" tests, their sources do not necessarily - // exist yet but will be created by a test that runs earlier than they do. - if (dir.getName().equals("annotated") - && dir.getParentFile() != null - && dir.getParentFile().getName().startsWith("ainfer-")) { - continue; - } - // When this reaches a sym-linked dir like all-system, Windows needs to explicitly - // read the content recorded in this file, which is the path to the real dir. - // Without this check Windows will treat the file as a meaningless one and skip it. - if (dir.isFile()) { - File p = dir; - try (BufferedReader br = new BufferedReader(new FileReader(dir))) { - String allSystemPath = br.readLine(); - if (allSystemPath == null) { - throw new BugInCF("test directory does not exist: %s", dir); - } - p = - new File(parent, allSystemPath.replace("/", File.separator)) - .toPath() - .toAbsolutePath() - .normalize() - .toFile(); - - } catch (IOException e) { - throw new BugInCF("file is not readable: %s", dir); - } - filesPerDirectory.addAll(findJavaTestFilesInDirectory(p)); - continue; - } - - throw new BugInCF("test directory does not exist: %s", dir); + // When this reaches a sym-linked dir like all-system, Windows needs to explicitly + // read the content recorded in this file, which is the path to the real dir. + // Without this check Windows will treat the file as a meaningless one and skip it. + if (dir.isFile()) { + File p = dir; + try (BufferedReader br = new BufferedReader(new FileReader(dir))) { + String allSystemPath = br.readLine(); + if (allSystemPath == null) { + throw new BugInCF("test directory does not exist: %s", dir); } + p = + new File(parent, allSystemPath.replace("/", File.separator)) + .toPath() + .toAbsolutePath() + .normalize() + .toFile(); + + } catch (IOException e) { + throw new BugInCF("file is not readable: %s", dir); + } + filesPerDirectory.addAll(findJavaTestFilesInDirectory(p)); + continue; } - return filesPerDirectory; + throw new BugInCF("test directory does not exist: %s", dir); + } } - /** - * Returns a list where each item is a list of Java files, excluding any skip tests. There is - * one list for {@code dir}, and one list for each subdirectory of {@code dir}. - * - * @param dir directory in which to search for Java files - * @return a list of list of Java test files - */ - private static List> findJavaTestFilesInDirectory(File dir) { - List> fileGroupedByDirectory = new ArrayList<>(); - List filesInDir = new ArrayList<>(); - - fileGroupedByDirectory.add(filesInDir); - String[] dirContents = dir.list(); - if (dirContents == null) { - throw new Error("Not a directory: " + dir); - } - Arrays.sort(dirContents); - for (String fileName : dirContents) { - File file = new File(dir, fileName); - if (file.isDirectory()) { - fileGroupedByDirectory.addAll(findJavaTestFilesInDirectory(file)); - } else if (isJavaTestFile(file)) { - filesInDir.add(file); - } - } - if (filesInDir.isEmpty()) { - fileGroupedByDirectory.remove(filesInDir); - } - return fileGroupedByDirectory; + return filesPerDirectory; + } + + /** + * Returns a list where each item is a list of Java files, excluding any skip tests. There is one + * list for {@code dir}, and one list for each subdirectory of {@code dir}. + * + * @param dir directory in which to search for Java files + * @return a list of list of Java test files + */ + private static List> findJavaTestFilesInDirectory(File dir) { + List> fileGroupedByDirectory = new ArrayList<>(); + List filesInDir = new ArrayList<>(); + + fileGroupedByDirectory.add(filesInDir); + String[] dirContents = dir.list(); + if (dirContents == null) { + throw new Error("Not a directory: " + dir); } - - /** - * Prepends a file to the beginning of each filename. - * - * @param parent a file to prepend to each filename - * @param fileNames file names - * @return the file names, each with {@code parent} prepended - */ - public static List findFilesInParent(File parent, String... fileNames) { - return CollectionsPlume.mapList( - (String fileName) -> new Object[] {new File(parent, fileName)}, fileNames); + Arrays.sort(dirContents); + for (String fileName : dirContents) { + File file = new File(dir, fileName); + if (file.isDirectory()) { + fileGroupedByDirectory.addAll(findJavaTestFilesInDirectory(file)); + } else if (isJavaTestFile(file)) { + filesInDir.add(file); + } } - - /** - * Traverses the directories listed looking for Java test files. - * - * @param dirs directories in which to search for Java test files - * @return a list of Java test files found in the directories - */ - public static List getJavaFilesAsArgumentList(File... dirs) { - List arguments = new ArrayList<>(); - for (File dir : dirs) { - arguments.addAll(deeplyEnclosedJavaTestFiles(dir)); - } - return arguments; + if (filesInDir.isEmpty()) { + fileGroupedByDirectory.remove(filesInDir); } - - /** - * Returns all the Java files that are descendants of the given directory. - * - * @param directory a directory - * @return all the Java files that are descendants of the given directory - */ - public static List deeplyEnclosedJavaTestFiles(File directory) { - if (!directory.exists()) { - throw new IllegalArgumentException( - "directory does not exist: " + directory + " " + directory.getAbsolutePath()); - } - if (!directory.isDirectory()) { - throw new IllegalArgumentException("found file instead of directory: " + directory); - } - - List javaFiles = new ArrayList<>(); - - @SuppressWarnings("nullness") // checked above that it's a directory - File @NonNull [] in = directory.listFiles(); - Arrays.sort(in, Comparator.comparing(File::getName)); - for (File file : in) { - if (file.isDirectory()) { - javaFiles.addAll(deeplyEnclosedJavaTestFiles(file)); - } else if (isJavaTestFile(file)) { - javaFiles.add(file); - } - } - - return javaFiles; + return fileGroupedByDirectory; + } + + /** + * Prepends a file to the beginning of each filename. + * + * @param parent a file to prepend to each filename + * @param fileNames file names + * @return the file names, each with {@code parent} prepended + */ + public static List findFilesInParent(File parent, String... fileNames) { + return CollectionsPlume.mapList( + (String fileName) -> new Object[] {new File(parent, fileName)}, fileNames); + } + + /** + * Traverses the directories listed looking for Java test files. + * + * @param dirs directories in which to search for Java test files + * @return a list of Java test files found in the directories + */ + public static List getJavaFilesAsArgumentList(File... dirs) { + List arguments = new ArrayList<>(); + for (File dir : dirs) { + arguments.addAll(deeplyEnclosedJavaTestFiles(dir)); } - - public static boolean isJavaFile(File file) { - return file.isFile() && file.getName().endsWith(".java"); + return arguments; + } + + /** + * Returns all the Java files that are descendants of the given directory. + * + * @param directory a directory + * @return all the Java files that are descendants of the given directory + */ + public static List deeplyEnclosedJavaTestFiles(File directory) { + if (!directory.exists()) { + throw new IllegalArgumentException( + "directory does not exist: " + directory + " " + directory.getAbsolutePath()); } - - public static boolean isJavaTestFile(File file) { - if (!isJavaFile(file)) { - return false; - } - - try (Scanner in = new Scanner(file)) { - while (in.hasNext()) { - String nextLine = in.nextLine(); - if (nextLine.contains("@skip-test") - || (!IS_AT_LEAST_9_JVM && nextLine.contains("@below-java9-jdk-skip-test")) - || (!IS_AT_LEAST_10_JVM && nextLine.contains("@below-java10-jdk-skip-test")) - || (!IS_AT_LEAST_11_JVM && nextLine.contains("@below-java11-jdk-skip-test")) - || (!IS_AT_MOST_11_JVM && nextLine.contains("@above-java11-jdk-skip-test")) - || (!IS_AT_LEAST_14_JVM && nextLine.contains("@below-java14-jdk-skip-test")) - || (!IS_AT_MOST_14_JVM && nextLine.contains("@above-java14-jdk-skip-test")) - || (!IS_AT_LEAST_16_JVM && nextLine.contains("@below-java16-jdk-skip-test")) - || (!IS_AT_MOST_16_JVM && nextLine.contains("@above-java16-jdk-skip-test")) - || (!IS_AT_LEAST_17_JVM && nextLine.contains("@below-java17-jdk-skip-test")) - || (!IS_AT_MOST_17_JVM && nextLine.contains("@above-java17-jdk-skip-test")) - || (!IS_AT_LEAST_18_JVM && nextLine.contains("@below-java18-jdk-skip-test")) - || (!IS_AT_MOST_18_JVM && nextLine.contains("@above-java18-jdk-skip-test")) - || (!IS_AT_LEAST_21_JVM - && nextLine.contains("@below-java21-jdk-skip-test"))) { - return false; - } - } - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - - return true; + if (!directory.isDirectory()) { + throw new IllegalArgumentException("found file instead of directory: " + directory); } - /** - * Convert a compiler diagnostic to a string. - * - * @param diagnostic the compiler diagnostic - * @param usingAnomsgtxt whether message text should be excluded or not - * @return the compiler message string or null for certain lint warnings - */ - public static @Nullable String diagnosticToString( - Diagnostic diagnostic, boolean usingAnomsgtxt) { - String result = diagnostic.toString().trim(); - - // suppress Xlint warnings - if (result.contains("uses unchecked or unsafe operations.") - || result.contains("Recompile with -Xlint:unchecked for details.") - || result.endsWith(" declares unsafe vararg methods.") - || result.contains("Recompile with -Xlint:varargs for details.")) { - return null; - } - - if (usingAnomsgtxt) { - // Lines with "unexpected Throwable" are stack traces - // and should be printed in full. - if (!result.contains("unexpected Throwable")) { - String firstLine; - int lineSepPos = result.indexOf(System.lineSeparator()); - if (lineSepPos != -1) { - firstLine = result.substring(0, lineSepPos); - } else { - firstLine = result; - } - int javaPos = firstLine.indexOf(".java:"); - if (javaPos != -1) { - firstLine = firstLine.substring(javaPos + 5).trim(); - } - result = firstLine; - } - } - - return result; + List javaFiles = new ArrayList<>(); + + @SuppressWarnings("nullness") // checked above that it's a directory + File @NonNull [] in = directory.listFiles(); + Arrays.sort(in, Comparator.comparing(File::getName)); + for (File file : in) { + if (file.isDirectory()) { + javaFiles.addAll(deeplyEnclosedJavaTestFiles(file)); + } else if (isJavaTestFile(file)) { + javaFiles.add(file); + } } - public static Set diagnosticsToStrings( - Iterable> actualDiagnostics, - boolean usingAnomsgtxt) { - Set actualDiagnosticsStr = new LinkedHashSet<>(); - for (Diagnostic diagnostic : actualDiagnostics) { - String diagnosticStr = TestUtilities.diagnosticToString(diagnostic, usingAnomsgtxt); - if (diagnosticStr != null) { - actualDiagnosticsStr.add(diagnosticStr); - } - } + return javaFiles; + } - return actualDiagnosticsStr; - } + public static boolean isJavaFile(File file) { + return file.isFile() && file.getName().endsWith(".java"); + } - /** - * Return the file absolute pathnames, separated by commas. - * - * @param javaFiles a list of Java files - * @return the file absolute pathnames, separated by commas - */ - public static String summarizeSourceFiles(List javaFiles) { - StringJoiner sj = new StringJoiner(", "); - for (File file : javaFiles) { - sj.add(file.getAbsolutePath()); - } - return sj.toString(); + public static boolean isJavaTestFile(File file) { + if (!isJavaFile(file)) { + return false; } - public static File getTestFile(String fileRelativeToTestsDir) { - return new File("tests", fileRelativeToTestsDir); + try (Scanner in = new Scanner(file)) { + while (in.hasNext()) { + String nextLine = in.nextLine(); + if (nextLine.contains("@skip-test") + || (!IS_AT_LEAST_9_JVM && nextLine.contains("@below-java9-jdk-skip-test")) + || (!IS_AT_LEAST_10_JVM && nextLine.contains("@below-java10-jdk-skip-test")) + || (!IS_AT_LEAST_11_JVM && nextLine.contains("@below-java11-jdk-skip-test")) + || (!IS_AT_MOST_11_JVM && nextLine.contains("@above-java11-jdk-skip-test")) + || (!IS_AT_LEAST_14_JVM && nextLine.contains("@below-java14-jdk-skip-test")) + || (!IS_AT_MOST_14_JVM && nextLine.contains("@above-java14-jdk-skip-test")) + || (!IS_AT_LEAST_16_JVM && nextLine.contains("@below-java16-jdk-skip-test")) + || (!IS_AT_MOST_16_JVM && nextLine.contains("@above-java16-jdk-skip-test")) + || (!IS_AT_LEAST_17_JVM && nextLine.contains("@below-java17-jdk-skip-test")) + || (!IS_AT_MOST_17_JVM && nextLine.contains("@above-java17-jdk-skip-test")) + || (!IS_AT_LEAST_18_JVM && nextLine.contains("@below-java18-jdk-skip-test")) + || (!IS_AT_MOST_18_JVM && nextLine.contains("@above-java18-jdk-skip-test")) + || (!IS_AT_LEAST_21_JVM && nextLine.contains("@below-java21-jdk-skip-test"))) { + return false; + } + } + } catch (FileNotFoundException e) { + throw new RuntimeException(e); } - public static File findComparisonFile(File testFile) { - File comparisonFile = - new File(testFile.getParent(), testFile.getName().replace(".java", ".out")); - return comparisonFile; + return true; + } + + /** + * Convert a compiler diagnostic to a string. + * + * @param diagnostic the compiler diagnostic + * @param usingAnomsgtxt whether message text should be excluded or not + * @return the compiler message string or null for certain lint warnings + */ + public static @Nullable String diagnosticToString( + Diagnostic diagnostic, boolean usingAnomsgtxt) { + String result = diagnostic.toString().trim(); + + // suppress Xlint warnings + if (result.contains("uses unchecked or unsafe operations.") + || result.contains("Recompile with -Xlint:unchecked for details.") + || result.endsWith(" declares unsafe vararg methods.") + || result.contains("Recompile with -Xlint:varargs for details.")) { + return null; } - /** - * Given an option map, return a list of option names. - * - * @param options an option map - * @return return a list of option names - */ - public static List optionMapToList(Map options) { - List optionList = new ArrayList<>(options.size() * 2); - - for (Map.Entry optEntry : options.entrySet()) { - optionList.add(optEntry.getKey()); - - if (optEntry.getValue() != null) { - optionList.add(optEntry.getValue()); - } + if (usingAnomsgtxt) { + // Lines with "unexpected Throwable" are stack traces + // and should be printed in full. + if (!result.contains("unexpected Throwable")) { + String firstLine; + int lineSepPos = result.indexOf(System.lineSeparator()); + if (lineSepPos != -1) { + firstLine = result.substring(0, lineSepPos); + } else { + firstLine = result; } - - return optionList; - } - - /** - * Write all the lines in the given Iterable to the given File. - * - * @param file where to write the lines - * @param lines what lines to write - */ - public static void writeLines(File file, Iterable lines) { - try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) { - Iterator iter = lines.iterator(); - while (iter.hasNext()) { - Object next = iter.next(); - if (next == null) { - bw.write(""); - } else { - bw.write(next.toString()); - } - bw.newLine(); - } - bw.flush(); - } catch (IOException io) { - throw new RuntimeException(io); + int javaPos = firstLine.indexOf(".java:"); + if (javaPos != -1) { + firstLine = firstLine.substring(javaPos + 5).trim(); } + result = firstLine; + } } - public static void writeDiagnostics( - File file, - File testFile, - List expected, - List actual, - List unexpected, - List missing, - boolean usingNoMsgText, - boolean testFailed) { - try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) { - pw.println("File: " + testFile.getAbsolutePath()); - pw.println("TestFailed: " + testFailed); - pw.println("Using nomsgtxt: " + usingNoMsgText); - pw.println("#Missing: " + missing.size() + " #Unexpected: " + unexpected.size()); - - pw.println("Expected:"); - pw.println(StringsPlume.joinLines(expected)); - pw.println(); - - pw.println("Actual:"); - pw.println(StringsPlume.joinLines(actual)); - pw.println(); - - pw.println("Missing:"); - pw.println(StringsPlume.joinLines(missing)); - pw.println(); - - pw.println("Unexpected:"); - pw.println(StringsPlume.joinLines(unexpected)); - pw.println(); - - pw.println(); - pw.println(); - pw.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } + return result; + } + + public static Set diagnosticsToStrings( + Iterable> actualDiagnostics, boolean usingAnomsgtxt) { + Set actualDiagnosticsStr = new LinkedHashSet<>(); + for (Diagnostic diagnostic : actualDiagnostics) { + String diagnosticStr = TestUtilities.diagnosticToString(diagnostic, usingAnomsgtxt); + if (diagnosticStr != null) { + actualDiagnosticsStr.add(diagnosticStr); + } } - /** - * Append a test configuration to the end of a file. - * - * @param file the file to write to - * @param config the configuration to append to the end of the file - */ - public static void writeTestConfiguration(File file, TestConfiguration config) { - try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) { - bw.write(config.toString()); - bw.newLine(); - bw.newLine(); - bw.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } + return actualDiagnosticsStr; + } + + /** + * Return the file absolute pathnames, separated by commas. + * + * @param javaFiles a list of Java files + * @return the file absolute pathnames, separated by commas + */ + public static String summarizeSourceFiles(List javaFiles) { + StringJoiner sj = new StringJoiner(", "); + for (File file : javaFiles) { + sj.add(file.getAbsolutePath()); } - - public static void writeJavacArguments( - File file, - Iterable files, - Iterable options, - Iterable processors) { - try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) { - pw.println("Files:"); - for (JavaFileObject f : files) { - pw.println(" " + f.getName()); - } - pw.println(); - - pw.println("Options:"); - for (String o : options) { - pw.println(" " + o); - } - pw.println(); - - pw.println("Processors:"); - for (String p : processors) { - pw.println(" " + p); - } - pw.println(); - pw.println(); - - pw.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } + return sj.toString(); + } + + public static File getTestFile(String fileRelativeToTestsDir) { + return new File("tests", fileRelativeToTestsDir); + } + + public static File findComparisonFile(File testFile) { + File comparisonFile = + new File(testFile.getParent(), testFile.getName().replace(".java", ".out")); + return comparisonFile; + } + + /** + * Given an option map, return a list of option names. + * + * @param options an option map + * @return return a list of option names + */ + public static List optionMapToList(Map options) { + List optionList = new ArrayList<>(options.size() * 2); + + for (Map.Entry optEntry : options.entrySet()) { + optionList.add(optEntry.getKey()); + + if (optEntry.getValue() != null) { + optionList.add(optEntry.getValue()); + } } - /** - * If the given TypecheckResult has unexpected or missing diagnostics, fail the running JUnit - * test. - * - * @param testResult the result of type-checking - */ - public static void assertTestDidNotFail(TypecheckResult testResult) { - if (testResult.didTestFail()) { - if (getShouldEmitDebugInfo()) { - System.out.println("---------------- start of javac ouput ----------------"); - System.out.println(testResult.getCompilationResult().getJavacOutput()); - System.out.println("---------------- end of javac ouput ----------------"); - } else { - System.out.println( - "To see the javac command line and output, run with: -Pemit.test.debug"); - } - Assert.fail(testResult.summarize()); + return optionList; + } + + /** + * Write all the lines in the given Iterable to the given File. + * + * @param file where to write the lines + * @param lines what lines to write + */ + public static void writeLines(File file, Iterable lines) { + try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) { + Iterator iter = lines.iterator(); + while (iter.hasNext()) { + Object next = iter.next(); + if (next == null) { + bw.write(""); + } else { + bw.write(next.toString()); } + bw.newLine(); + } + bw.flush(); + } catch (IOException io) { + throw new RuntimeException(io); } - - /** - * Create the directory (and its parents) if it does not exist. - * - * @param dir the directory to create - */ - public static void ensureDirectoryExists(String dir) { - try { - Files.createDirectories(Paths.get(dir)); - } catch (FileAlreadyExistsException e) { - // directory already exists - } catch (IOException e) { - throw new RuntimeException("Could not make directory: " + dir + ": " + e.getMessage()); - } + } + + public static void writeDiagnostics( + File file, + File testFile, + List expected, + List actual, + List unexpected, + List missing, + boolean usingNoMsgText, + boolean testFailed) { + try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) { + pw.println("File: " + testFile.getAbsolutePath()); + pw.println("TestFailed: " + testFailed); + pw.println("Using nomsgtxt: " + usingNoMsgText); + pw.println("#Missing: " + missing.size() + " #Unexpected: " + unexpected.size()); + + pw.println("Expected:"); + pw.println(StringsPlume.joinLines(expected)); + pw.println(); + + pw.println("Actual:"); + pw.println(StringsPlume.joinLines(actual)); + pw.println(); + + pw.println("Missing:"); + pw.println(StringsPlume.joinLines(missing)); + pw.println(); + + pw.println("Unexpected:"); + pw.println(StringsPlume.joinLines(unexpected)); + pw.println(); + + pw.println(); + pw.println(); + pw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); } - - /** - * Returns the value of system property "emit.test.debug". - * - * @return the value of system property "emit.test.debug" - */ - public static boolean getShouldEmitDebugInfo() { - return SystemPlume.getBooleanSystemProperty("emit.test.debug"); + } + + /** + * Append a test configuration to the end of a file. + * + * @param file the file to write to + * @param config the configuration to append to the end of the file + */ + public static void writeTestConfiguration(File file, TestConfiguration config) { + try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) { + bw.write(config.toString()); + bw.newLine(); + bw.newLine(); + bw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); } - - /** - * Adapt a string that uses Unix file and path separators to use the correct operating system - * separator. - * - * @param input a path with Unix file and path separators - * @return a path with the correct operating system separator - */ - public static String adapt(String input) { - return input.replace("/", File.separator).replace(":", File.pathSeparator); + } + + public static void writeJavacArguments( + File file, + Iterable files, + Iterable options, + Iterable processors) { + try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) { + pw.println("Files:"); + for (JavaFileObject f : files) { + pw.println(" " + f.getName()); + } + pw.println(); + + pw.println("Options:"); + for (String o : options) { + pw.println(" " + o); + } + pw.println(); + + pw.println("Processors:"); + for (String p : processors) { + pw.println(" " + p); + } + pw.println(); + pw.println(); + + pw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * If the given TypecheckResult has unexpected or missing diagnostics, fail the running JUnit + * test. + * + * @param testResult the result of type-checking + */ + public static void assertTestDidNotFail(TypecheckResult testResult) { + if (testResult.didTestFail()) { + if (getShouldEmitDebugInfo()) { + System.out.println("---------------- start of javac ouput ----------------"); + System.out.println(testResult.getCompilationResult().getJavacOutput()); + System.out.println("---------------- end of javac ouput ----------------"); + } else { + System.out.println("To see the javac command line and output, run with: -Pemit.test.debug"); + } + Assert.fail(testResult.summarize()); + } + } + + /** + * Create the directory (and its parents) if it does not exist. + * + * @param dir the directory to create + */ + public static void ensureDirectoryExists(String dir) { + try { + Files.createDirectories(Paths.get(dir)); + } catch (FileAlreadyExistsException e) { + // directory already exists + } catch (IOException e) { + throw new RuntimeException("Could not make directory: " + dir + ": " + e.getMessage()); } + } + + /** + * Returns the value of system property "emit.test.debug". + * + * @return the value of system property "emit.test.debug" + */ + public static boolean getShouldEmitDebugInfo() { + return SystemPlume.getBooleanSystemProperty("emit.test.debug"); + } + + /** + * Adapt a string that uses Unix file and path separators to use the correct operating system + * separator. + * + * @param input a path with Unix file and path separators + * @return a path with the correct operating system separator + */ + public static String adapt(String input) { + return input.replace("/", File.separator).replace(":", File.pathSeparator); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java index 01a3786ee0d..d8fff5c6663 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java @@ -1,144 +1,129 @@ package org.checkerframework.framework.test; -import org.checkerframework.framework.test.diagnostics.JavaDiagnosticReader; -import org.checkerframework.framework.test.diagnostics.TestDiagnostic; -import org.plumelib.util.StringsPlume; - import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; - import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; +import org.checkerframework.framework.test.diagnostics.JavaDiagnosticReader; +import org.checkerframework.framework.test.diagnostics.TestDiagnostic; +import org.plumelib.util.StringsPlume; /** Used by the Checker Framework test suite to run the framework and generate a test result. */ public class TypecheckExecutor { - /** Creates a new TypecheckExecutor. */ - public TypecheckExecutor() {} - - /** - * Runs a typechecking test using the given configuration and returns the test result. - * - * @param configuration the test configuration - * @return the test result - */ - public TypecheckResult runTest(TestConfiguration configuration) { - try { - CompilationResult result = compile(configuration); - return interpretResults(configuration, result); - } catch (OutOfMemoryError e) { - String message = - String.format( - "Max memory = %d, total memory = %d, free memory = %d.", - Runtime.getRuntime().maxMemory(), - Runtime.getRuntime().totalMemory(), - Runtime.getRuntime().freeMemory()); - System.out.println(message); - System.err.println(message); - throw new Error(message, e); - } + /** Creates a new TypecheckExecutor. */ + public TypecheckExecutor() {} + + /** + * Runs a typechecking test using the given configuration and returns the test result. + * + * @param configuration the test configuration + * @return the test result + */ + public TypecheckResult runTest(TestConfiguration configuration) { + try { + CompilationResult result = compile(configuration); + return interpretResults(configuration, result); + } catch (OutOfMemoryError e) { + String message = + String.format( + "Max memory = %d, total memory = %d, free memory = %d.", + Runtime.getRuntime().maxMemory(), + Runtime.getRuntime().totalMemory(), + Runtime.getRuntime().freeMemory()); + System.out.println(message); + System.err.println(message); + throw new Error(message, e); } - - /** - * Using the settings from the input configuration, compile all source files in the - * configuration, and return the result in a CompilationResult. - */ - public CompilationResult compile(TestConfiguration configuration) { - String dOption = configuration.getOptions().get("-d"); - if (dOption == null) { - throw new Error("-d not supplied"); - } - TestUtilities.ensureDirectoryExists(dOption); - - StringWriter javacOutput = new StringWriter(); - DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - try (StandardJavaFileManager fileManager = - compiler.getStandardFileManager(null, null, null)) { - Iterable javaFiles = - fileManager.getJavaFileObjects( - configuration.getTestSourceFiles().toArray(new File[] {})); - - // Even though the method compiler.getTask takes a list of processors, it fails if - // processors are passed this way with the message: - // error: Class names, 'org.checkerframework.checker.interning.InterningChecker', are - // only accepted if annotation processing is explicitly requested - // Therefore, we now add them to the beginning of the options list. - List options = new ArrayList<>(); - options.add("-processor"); - options.add(String.join(",", configuration.getProcessors())); - options.addAll(configuration.getFlatOptions()); - - if (configuration.shouldEmitDebugInfo()) { - System.out.println("Running test using the following invocation:"); - System.out.println( - "javac " - + String.join(" ", options) - + " " - + StringsPlume.join(" ", configuration.getTestSourceFiles())); - } - - JavaCompiler.CompilationTask task = - compiler.getTask( - javacOutput, - fileManager, - diagnostics, - options, - new ArrayList(), - javaFiles); - - /* - * In Eclipse, std out and std err for multiple tests appear as one - * long stream. When selecting a specific failed test, one sees the - * expected/unexpected messages, but not the std out/err messages from - * that particular test. Can we improve this somehow? - */ - Boolean compiledWithoutError = task.call(); - javacOutput.flush(); - return new CompilationResult( - compiledWithoutError, - javacOutput.toString(), - javaFiles, - diagnostics.getDiagnostics()); - } catch (IOException e) { - throw new Error(e); - } + } + + /** + * Using the settings from the input configuration, compile all source files in the configuration, + * and return the result in a CompilationResult. + */ + public CompilationResult compile(TestConfiguration configuration) { + String dOption = configuration.getOptions().get("-d"); + if (dOption == null) { + throw new Error("-d not supplied"); } - - /** - * Reads the expected diagnostics for the given configuration and creates a TypecheckResult - * which contains all of the missing and expected diagnostics. - */ - public TypecheckResult interpretResults( - TestConfiguration config, CompilationResult compilationResult) { - List expectedDiagnostics = readDiagnostics(config, compilationResult); - return TypecheckResult.fromCompilationResults( - config, compilationResult, expectedDiagnostics); + TestUtilities.ensureDirectoryExists(dOption); + + StringWriter javacOutput = new StringWriter(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { + Iterable javaFiles = + fileManager.getJavaFileObjects(configuration.getTestSourceFiles().toArray(new File[] {})); + + // Even though the method compiler.getTask takes a list of processors, it fails if + // processors are passed this way with the message: + // error: Class names, 'org.checkerframework.checker.interning.InterningChecker', are + // only accepted if annotation processing is explicitly requested + // Therefore, we now add them to the beginning of the options list. + List options = new ArrayList<>(); + options.add("-processor"); + options.add(String.join(",", configuration.getProcessors())); + options.addAll(configuration.getFlatOptions()); + + if (configuration.shouldEmitDebugInfo()) { + System.out.println("Running test using the following invocation:"); + System.out.println( + "javac " + + String.join(" ", options) + + " " + + StringsPlume.join(" ", configuration.getTestSourceFiles())); + } + + JavaCompiler.CompilationTask task = + compiler.getTask( + javacOutput, fileManager, diagnostics, options, new ArrayList(), javaFiles); + + /* + * In Eclipse, std out and std err for multiple tests appear as one + * long stream. When selecting a specific failed test, one sees the + * expected/unexpected messages, but not the std out/err messages from + * that particular test. Can we improve this somehow? + */ + Boolean compiledWithoutError = task.call(); + javacOutput.flush(); + return new CompilationResult( + compiledWithoutError, javacOutput.toString(), javaFiles, diagnostics.getDiagnostics()); + } catch (IOException e) { + throw new Error(e); } - - /** - * A subclass can override this to filter out errors or add new expected errors. This method is - * called immediately before results are checked. - */ - protected List readDiagnostics( - TestConfiguration config, CompilationResult compilationResult) { - List expectedDiagnostics; - if (config.getDiagnosticFiles() == null || config.getDiagnosticFiles().isEmpty()) { - expectedDiagnostics = - JavaDiagnosticReader.readJavaSourceFiles( - compilationResult.getJavaFileObjects()); - } else { - expectedDiagnostics = - JavaDiagnosticReader.readDiagnosticFiles(config.getDiagnosticFiles()); - } - - return expectedDiagnostics; + } + + /** + * Reads the expected diagnostics for the given configuration and creates a TypecheckResult which + * contains all of the missing and expected diagnostics. + */ + public TypecheckResult interpretResults( + TestConfiguration config, CompilationResult compilationResult) { + List expectedDiagnostics = readDiagnostics(config, compilationResult); + return TypecheckResult.fromCompilationResults(config, compilationResult, expectedDiagnostics); + } + + /** + * A subclass can override this to filter out errors or add new expected errors. This method is + * called immediately before results are checked. + */ + protected List readDiagnostics( + TestConfiguration config, CompilationResult compilationResult) { + List expectedDiagnostics; + if (config.getDiagnosticFiles() == null || config.getDiagnosticFiles().isEmpty()) { + expectedDiagnostics = + JavaDiagnosticReader.readJavaSourceFiles(compilationResult.getJavaFileObjects()); + } else { + expectedDiagnostics = JavaDiagnosticReader.readDiagnosticFiles(config.getDiagnosticFiles()); } + + return expectedDiagnostics; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java index d397031cea1..b686b62d1fd 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java @@ -1,165 +1,161 @@ package org.checkerframework.framework.test; -import org.checkerframework.framework.test.diagnostics.TestDiagnostic; -import org.checkerframework.framework.test.diagnostics.TestDiagnosticUtils; -import org.plumelib.util.StringsPlume; - import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.StringJoiner; - import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.checkerframework.framework.test.diagnostics.TestDiagnostic; +import org.checkerframework.framework.test.diagnostics.TestDiagnosticUtils; +import org.plumelib.util.StringsPlume; /** * Represents the test results from typechecking one or more Java files using the given * TestConfiguration. */ public class TypecheckResult { - private final TestConfiguration configuration; - private final CompilationResult compilationResult; - private final List expectedDiagnostics; - - private final List missingDiagnostics; - private final List unexpectedDiagnostics; - - protected TypecheckResult( - TestConfiguration configuration, - CompilationResult compilationResult, - List expectedDiagnostics, - List missingDiagnostics, - List unexpectedDiagnostics) { - this.configuration = configuration; - this.compilationResult = compilationResult; - this.expectedDiagnostics = expectedDiagnostics; - this.missingDiagnostics = missingDiagnostics; - this.unexpectedDiagnostics = unexpectedDiagnostics; - } - - public TestConfiguration getConfiguration() { - return configuration; - } - - public CompilationResult getCompilationResult() { - return compilationResult; - } - - public List> getActualDiagnostics() { - return compilationResult.getDiagnostics(); - } - - public List getExpectedDiagnostics() { - return expectedDiagnostics; + private final TestConfiguration configuration; + private final CompilationResult compilationResult; + private final List expectedDiagnostics; + + private final List missingDiagnostics; + private final List unexpectedDiagnostics; + + protected TypecheckResult( + TestConfiguration configuration, + CompilationResult compilationResult, + List expectedDiagnostics, + List missingDiagnostics, + List unexpectedDiagnostics) { + this.configuration = configuration; + this.compilationResult = compilationResult; + this.expectedDiagnostics = expectedDiagnostics; + this.missingDiagnostics = missingDiagnostics; + this.unexpectedDiagnostics = unexpectedDiagnostics; + } + + public TestConfiguration getConfiguration() { + return configuration; + } + + public CompilationResult getCompilationResult() { + return compilationResult; + } + + public List> getActualDiagnostics() { + return compilationResult.getDiagnostics(); + } + + public List getExpectedDiagnostics() { + return expectedDiagnostics; + } + + public boolean didTestFail() { + return !unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty(); + } + + public List getMissingDiagnostics() { + return missingDiagnostics; + } + + public List getUnexpectedDiagnostics() { + return unexpectedDiagnostics; + } + + public List getErrorHeaders() { + List errorHeaders = new ArrayList<>(); + + // none of these should be true if the test didn't fail + if (didTestFail()) { + if (compilationResult.compiledWithoutError() && !expectedDiagnostics.isEmpty()) { + errorHeaders.add("The test run was expected to issue errors/warnings, but it did not."); + + } else if (!compilationResult.compiledWithoutError() && expectedDiagnostics.isEmpty()) { + errorHeaders.add("The test run was not expected to issue errors/warnings, but it did."); + } + + if (!unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty()) { + int numExpected = expectedDiagnostics.size(); + int numFound = numExpected - missingDiagnostics.size(); + errorHeaders.add( + numFound + + " out of " + + StringsPlume.nplural(numExpected, "expected diagnostic") + + " " + + (numFound == 1 ? "was" : "were") + + " found."); + } } - public boolean didTestFail() { - return !unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty(); + return errorHeaders; + } + + /** + * Summarize unexpected and missing diagnostics. + * + * @return summary of failures + */ + public String summarize() { + if (!didTestFail()) { + return ""; } - - public List getMissingDiagnostics() { - return missingDiagnostics; + StringJoiner summaryBuilder = new StringJoiner(System.lineSeparator()); + summaryBuilder.add(StringsPlume.joinLines(getErrorHeaders())); + + if (!unexpectedDiagnostics.isEmpty()) { + int numUnexpected = unexpectedDiagnostics.size(); + if (numUnexpected == 1) { + summaryBuilder.add("1 unexpected diagnostic was found:"); + } else { + summaryBuilder.add(numUnexpected + " unexpected diagnostics were found:"); + } + + for (TestDiagnostic unexpected : unexpectedDiagnostics) { + summaryBuilder.add(" " + unexpected.toString()); + } } - public List getUnexpectedDiagnostics() { - return unexpectedDiagnostics; + if (!missingDiagnostics.isEmpty()) { + int numMissing = missingDiagnostics.size(); + summaryBuilder.add( + StringsPlume.nplural(numMissing, "expected diagnostic") + + " " + + (numMissing == 1 ? "was" : "were") + + " not found:"); + + for (TestDiagnostic missing : missingDiagnostics) { + summaryBuilder.add(" " + missing.toString()); + } } - public List getErrorHeaders() { - List errorHeaders = new ArrayList<>(); - - // none of these should be true if the test didn't fail - if (didTestFail()) { - if (compilationResult.compiledWithoutError() && !expectedDiagnostics.isEmpty()) { - errorHeaders.add( - "The test run was expected to issue errors/warnings, but it did not."); - - } else if (!compilationResult.compiledWithoutError() && expectedDiagnostics.isEmpty()) { - errorHeaders.add( - "The test run was not expected to issue errors/warnings, but it did."); - } - - if (!unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty()) { - int numExpected = expectedDiagnostics.size(); - int numFound = numExpected - missingDiagnostics.size(); - errorHeaders.add( - numFound - + " out of " - + StringsPlume.nplural(numExpected, "expected diagnostic") - + " " - + (numFound == 1 ? "was" : "were") - + " found."); - } - } - - return errorHeaders; - } - - /** - * Summarize unexpected and missing diagnostics. - * - * @return summary of failures - */ - public String summarize() { - if (!didTestFail()) { - return ""; - } - StringJoiner summaryBuilder = new StringJoiner(System.lineSeparator()); - summaryBuilder.add(StringsPlume.joinLines(getErrorHeaders())); - - if (!unexpectedDiagnostics.isEmpty()) { - int numUnexpected = unexpectedDiagnostics.size(); - if (numUnexpected == 1) { - summaryBuilder.add("1 unexpected diagnostic was found:"); - } else { - summaryBuilder.add(numUnexpected + " unexpected diagnostics were found:"); - } - - for (TestDiagnostic unexpected : unexpectedDiagnostics) { - summaryBuilder.add(" " + unexpected.toString()); - } - } - - if (!missingDiagnostics.isEmpty()) { - int numMissing = missingDiagnostics.size(); - summaryBuilder.add( - StringsPlume.nplural(numMissing, "expected diagnostic") - + " " - + (numMissing == 1 ? "was" : "were") - + " not found:"); - - for (TestDiagnostic missing : missingDiagnostics) { - summaryBuilder.add(" " + missing.toString()); - } - } - - summaryBuilder.add( - "While type-checking " - + TestUtilities.summarizeSourceFiles(configuration.getTestSourceFiles())); - return summaryBuilder.toString(); - } - - public static TypecheckResult fromCompilationResults( - TestConfiguration configuration, - CompilationResult result, - List expectedDiagnostics) { - - Set actualDiagnostics = - TestDiagnosticUtils.fromJavaxToolsDiagnosticList(result.getDiagnostics()); - - Set unexpectedDiagnostics = new LinkedHashSet<>(); - unexpectedDiagnostics.addAll(actualDiagnostics); - unexpectedDiagnostics.removeAll(expectedDiagnostics); - - List missingDiagnostics = new ArrayList<>(expectedDiagnostics); - missingDiagnostics.removeAll(actualDiagnostics); - - return new TypecheckResult( - configuration, - result, - expectedDiagnostics, - missingDiagnostics, - new ArrayList<>(unexpectedDiagnostics)); - } + summaryBuilder.add( + "While type-checking " + + TestUtilities.summarizeSourceFiles(configuration.getTestSourceFiles())); + return summaryBuilder.toString(); + } + + public static TypecheckResult fromCompilationResults( + TestConfiguration configuration, + CompilationResult result, + List expectedDiagnostics) { + + Set actualDiagnostics = + TestDiagnosticUtils.fromJavaxToolsDiagnosticList(result.getDiagnostics()); + + Set unexpectedDiagnostics = new LinkedHashSet<>(); + unexpectedDiagnostics.addAll(actualDiagnostics); + unexpectedDiagnostics.removeAll(expectedDiagnostics); + + List missingDiagnostics = new ArrayList<>(expectedDiagnostics); + missingDiagnostics.removeAll(actualDiagnostics); + + return new TypecheckResult( + configuration, + result, + expectedDiagnostics, + missingDiagnostics, + new ArrayList<>(unexpectedDiagnostics)); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java index 9acd3405fda..3edadf3d72d 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java @@ -1,11 +1,10 @@ package org.checkerframework.framework.test.diagnostics; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.StringJoiner; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents a detailed error/warning message reported by the Checker Framework when the {@code @@ -15,120 +14,119 @@ */ public class DetailedTestDiagnostic extends TestDiagnostic { - /** Additional tokens that are part of the diagnostic message. */ - protected final List additionalTokens; - - /** The start position of the diagnostic in the source file. */ - protected final long startPosition; - - /** The end position of the diagnostic in the source file. */ - protected final long endPosition; - - /** - * Create a new instance. - * - * @param file the file in which the diagnostic occurred - * @param lineNo the line number in the file at which the diagnostic occurred - * @param kind the kind of diagnostic (error or warning) - * @param messageKey a message key that usually appears between parentheses in diagnostic - * messages - * @param additionalTokens additional tokens that are part of the diagnostic message - * @param startPosition the start position of the diagnostic in the source file - * @param endPosition the end position of the diagnostic in the source file - * @param readableMessage a human-readable message describing the diagnostic - * @param isFixable whether the diagnostic is fixable - */ - public DetailedTestDiagnostic( - Path file, - long lineNo, - DiagnosticKind kind, - String messageKey, - List additionalTokens, - long startPosition, - long endPosition, - String readableMessage, - boolean isFixable) { - super(file, lineNo, kind, messageKey, readableMessage, isFixable); - - this.additionalTokens = additionalTokens; - this.startPosition = startPosition; - this.endPosition = endPosition; + /** Additional tokens that are part of the diagnostic message. */ + protected final List additionalTokens; + + /** The start position of the diagnostic in the source file. */ + protected final long startPosition; + + /** The end position of the diagnostic in the source file. */ + protected final long endPosition; + + /** + * Create a new instance. + * + * @param file the file in which the diagnostic occurred + * @param lineNo the line number in the file at which the diagnostic occurred + * @param kind the kind of diagnostic (error or warning) + * @param messageKey a message key that usually appears between parentheses in diagnostic messages + * @param additionalTokens additional tokens that are part of the diagnostic message + * @param startPosition the start position of the diagnostic in the source file + * @param endPosition the end position of the diagnostic in the source file + * @param readableMessage a human-readable message describing the diagnostic + * @param isFixable whether the diagnostic is fixable + */ + public DetailedTestDiagnostic( + Path file, + long lineNo, + DiagnosticKind kind, + String messageKey, + List additionalTokens, + long startPosition, + long endPosition, + String readableMessage, + boolean isFixable) { + super(file, lineNo, kind, messageKey, readableMessage, isFixable); + + this.additionalTokens = additionalTokens; + this.startPosition = startPosition; + this.endPosition = endPosition; + } + + /** + * The additional tokens that are part of the diagnostic message. + * + * @return the additional tokens + */ + public List getAdditionalTokens() { + return additionalTokens; + } + + /** + * The start position of the diagnostic in the source file. + * + * @return the start position + */ + public long getStartPosition() { + return startPosition; + } + + /** + * The end position of the diagnostic in the source file. + * + * @return the end position + */ + public long getEndPosition() { + return endPosition; + } + + /** + * Equality is compared without isFixable and messageKeyParens. + * + * @return true if this and otherObj are equal according to additionalTokens, startPosition, + * endPosition, and equality of the superclass. + */ + @Override + public boolean equals(@Nullable Object otherObj) { + if (!(otherObj instanceof DetailedTestDiagnostic)) { + return false; } - - /** - * The additional tokens that are part of the diagnostic message. - * - * @return the additional tokens - */ - public List getAdditionalTokens() { - return additionalTokens; - } - - /** - * The start position of the diagnostic in the source file. - * - * @return the start position - */ - public long getStartPosition() { - return startPosition; + DetailedTestDiagnostic other = (DetailedTestDiagnostic) otherObj; + return super.equals(other) + && additionalTokens.equals(other.additionalTokens) + && startPosition == other.startPosition + && endPosition == other.endPosition; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), additionalTokens, startPosition, endPosition); + } + + /** + * Returns a representation of this diagnostic as if it appeared as a detailed message. + * + * @return a representation of this diagnostic as if it appeared as a detailed message + * @see + * org.checkerframework.framework.source.SourceChecker#detailedMsgTextPrefix(Object,String,Object[]) + */ + @Override + public String toString() { + // Keep in sync with SourceChecker.DETAILS_SEPARATOR. + StringJoiner sj = new StringJoiner(" $$ "); + + sj.add("(" + messageKey + ")"); + if (additionalTokens != null) { + sj.add(Integer.toString(additionalTokens.size())); + for (String token : additionalTokens) { + sj.add(token); + } + } else { + sj.add("0"); } - /** - * The end position of the diagnostic in the source file. - * - * @return the end position - */ - public long getEndPosition() { - return endPosition; - } - - /** - * Equality is compared without isFixable and messageKeyParens. - * - * @return true if this and otherObj are equal according to additionalTokens, startPosition, - * endPosition, and equality of the superclass. - */ - @Override - public boolean equals(@Nullable Object otherObj) { - if (!(otherObj instanceof DetailedTestDiagnostic)) { - return false; - } - DetailedTestDiagnostic other = (DetailedTestDiagnostic) otherObj; - return super.equals(other) - && additionalTokens.equals(other.additionalTokens) - && startPosition == other.startPosition - && endPosition == other.endPosition; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), additionalTokens, startPosition, endPosition); - } - - /** - * Returns a representation of this diagnostic as if it appeared as a detailed message. - * - * @return a representation of this diagnostic as if it appeared as a detailed message - * @see - * org.checkerframework.framework.source.SourceChecker#detailedMsgTextPrefix(Object,String,Object[]) - */ - @Override - public String toString() { - // Keep in sync with SourceChecker.DETAILS_SEPARATOR. - StringJoiner sj = new StringJoiner(" $$ "); - - sj.add("(" + messageKey + ")"); - if (additionalTokens != null) { - sj.add(Integer.toString(additionalTokens.size())); - for (String token : additionalTokens) { - sj.add(token); - } - } else { - sj.add("0"); - } - - sj.add(String.format("( %d, %d )", startPosition, endPosition)); - sj.add(getMessage()); - return sj.toString(); - } + sj.add(String.format("( %d, %d )", startPosition, endPosition)); + sj.add(getMessage()); + return sj.toString(); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java index 2e914f98a26..e6ef75348d5 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java @@ -1,40 +1,37 @@ package org.checkerframework.framework.test.diagnostics; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.LinkedHashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; /** The kinds of errors that can be encountered during typechecking. */ public enum DiagnosticKind { - /** A warning. */ - Warning("warning"), - /** An error. */ - Error("error"), - /** A note. */ - Note("Note"), - /** Something else. */ - Other("other"); - - /** How this DiagnosticKind appears in error messages or source code. */ - public final String parseString; - - DiagnosticKind(String parseString) { - this.parseString = parseString; + /** A warning. */ + Warning("warning"), + /** An error. */ + Error("error"), + /** A note. */ + Note("Note"), + /** Something else. */ + Other("other"); + + /** How this DiagnosticKind appears in error messages or source code. */ + public final String parseString; + + DiagnosticKind(String parseString) { + this.parseString = parseString; + } + + private static final Map stringToCategory = new LinkedHashMap<>(); + + static { + for (DiagnosticKind cat : values()) { + stringToCategory.put(cat.parseString, cat); } + } - private static final Map stringToCategory = new LinkedHashMap<>(); - - static { - for (DiagnosticKind cat : values()) { - stringToCategory.put(cat.parseString, cat); - } - } - - /** - * Convert a string as it would appear in error messages or source code into a DiagnosticKind. - */ - public static @Nullable DiagnosticKind fromParseString(String parseStr) { - return stringToCategory.get(parseStr); - } + /** Convert a string as it would appear in error messages or source code into a DiagnosticKind. */ + public static @Nullable DiagnosticKind fromParseString(String parseStr) { + return stringToCategory.get(parseStr); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java index e95e9acf228..ed6058c5501 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java @@ -1,13 +1,5 @@ package org.checkerframework.framework.test.diagnostics; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.index.qual.GTENegativeOne; -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.checkerframework.checker.mustcall.qual.Owning; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.checkerframework.dataflow.qual.Pure; - import java.io.Closeable; import java.io.File; import java.io.FileReader; @@ -17,8 +9,14 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; - import javax.tools.JavaFileObject; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.index.qual.GTENegativeOne; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.mustcall.qual.Owning; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.dataflow.qual.Pure; /** * This class reads expected javac diagnostics from a single file. Its implementation is as an @@ -28,260 +26,254 @@ */ public class JavaDiagnosticReader implements Iterator, Closeable { - /// - /// This class begins with the public static methods that clients use to read diagnostics. - /// + /// + /// This class begins with the public static methods that clients use to read diagnostics. + /// - /** - * Returns all the diagnostics in any of the Java source files. - * - * @param files the Java files to read; each is a File or a JavaFileObject - * @return the TestDiagnostics from the input file - */ - // The argument has type Iterable because Java cannot resolve the overload - // of two versions that take Iterable and Iterable. - public static List readJavaSourceFiles(Iterable files) { - List result = new ArrayList<>(); - for (Object file : files) { - if (file instanceof JavaFileObject) { - try (JavaDiagnosticReader reader = - new JavaDiagnosticReader( - (JavaFileObject) file, TestDiagnosticUtils::fromJavaSourceLine)) { - readDiagnostics(result, reader); - } - } else if (file instanceof File) { - try (JavaDiagnosticReader reader = - new JavaDiagnosticReader( - (File) file, TestDiagnosticUtils::fromJavaSourceLine)) { - readDiagnostics(result, reader); - } - } else { - throw new IllegalArgumentException( - String.format( - "Elements of argument should be File or JavaFileObject, not %s: %s", - file.getClass(), file)); - } + /** + * Returns all the diagnostics in any of the Java source files. + * + * @param files the Java files to read; each is a File or a JavaFileObject + * @return the TestDiagnostics from the input file + */ + // The argument has type Iterable because Java cannot resolve the overload + // of two versions that take Iterable and Iterable. + public static List readJavaSourceFiles(Iterable files) { + List result = new ArrayList<>(); + for (Object file : files) { + if (file instanceof JavaFileObject) { + try (JavaDiagnosticReader reader = + new JavaDiagnosticReader( + (JavaFileObject) file, TestDiagnosticUtils::fromJavaSourceLine)) { + readDiagnostics(result, reader); + } + } else if (file instanceof File) { + try (JavaDiagnosticReader reader = + new JavaDiagnosticReader((File) file, TestDiagnosticUtils::fromJavaSourceLine)) { + readDiagnostics(result, reader); } - return result; + } else { + throw new IllegalArgumentException( + String.format( + "Elements of argument should be File or JavaFileObject, not %s: %s", + file.getClass(), file)); + } } + return result; + } - /** - * Reads diagnostics line-by-line from the input diagnostic files. - * - * @param files a set of diagnostic files - * @return the TestDiagnosticLines from the input files - */ - public static List readDiagnosticFiles(Iterable files) { - List result = new ArrayList<>(); - for (File file : files) { - try (JavaDiagnosticReader reader = - new JavaDiagnosticReader( - file, - (filename, line, lineNumber) -> - TestDiagnosticUtils.fromDiagnosticFileLine(line))) { - readDiagnostics(result, reader); - } - } - return result; + /** + * Reads diagnostics line-by-line from the input diagnostic files. + * + * @param files a set of diagnostic files + * @return the TestDiagnosticLines from the input files + */ + public static List readDiagnosticFiles(Iterable files) { + List result = new ArrayList<>(); + for (File file : files) { + try (JavaDiagnosticReader reader = + new JavaDiagnosticReader( + file, + (filename, line, lineNumber) -> TestDiagnosticUtils.fromDiagnosticFileLine(line))) { + readDiagnostics(result, reader); + } } + return result; + } - /// - /// End of public static methods, start of private static methods. - /// + /// + /// End of public static methods, start of private static methods. + /// - /** - * Reads all the diagnostics in the file. - * - * @param list where to put the diagnostics - * @param reader the file (Java or Diagnostics format) to read - */ - private static void readDiagnostics(List list, JavaDiagnosticReader reader) { - diagnosticLinesToDiagnostics(list, readDiagnosticLines(reader)); - } + /** + * Reads all the diagnostics in the file. + * + * @param list where to put the diagnostics + * @param reader the file (Java or Diagnostics format) to read + */ + private static void readDiagnostics(List list, JavaDiagnosticReader reader) { + diagnosticLinesToDiagnostics(list, readDiagnosticLines(reader)); + } - /** - * Reads the entire input file using the given codec and returns the resulting lines, filtering - * out empty ones produced by JavaDiagnosticReader. - * - * @param reader the file (Java or Diagnostics format) to read - * @return the List of TestDiagnosticLines from the input file - */ - private static List readDiagnosticLines(JavaDiagnosticReader reader) { - List diagnosticLines = new ArrayList<>(); - while (reader.hasNext()) { - TestDiagnosticLine line = reader.next(); - // A JavaDiagnosticReader can return a lot of empty diagnostics. Filter them out. - if (line.hasDiagnostics()) { - diagnosticLines.add(line); - } - } - return diagnosticLines; + /** + * Reads the entire input file using the given codec and returns the resulting lines, filtering + * out empty ones produced by JavaDiagnosticReader. + * + * @param reader the file (Java or Diagnostics format) to read + * @return the List of TestDiagnosticLines from the input file + */ + private static List readDiagnosticLines(JavaDiagnosticReader reader) { + List diagnosticLines = new ArrayList<>(); + while (reader.hasNext()) { + TestDiagnosticLine line = reader.next(); + // A JavaDiagnosticReader can return a lot of empty diagnostics. Filter them out. + if (line.hasDiagnostics()) { + diagnosticLines.add(line); + } } + return diagnosticLines; + } - /** - * Converts a list of TestDiagnosticLine into a list of TestDiagnostic. - * - * @param list where to put the result TestDiagnostics - * @param lines the TestDiagnosticLines - */ - private static void diagnosticLinesToDiagnostics( - List list, List lines) { - for (TestDiagnosticLine line : lines) { - list.addAll(line.getDiagnostics()); - } + /** + * Converts a list of TestDiagnosticLine into a list of TestDiagnostic. + * + * @param list where to put the result TestDiagnostics + * @param lines the TestDiagnosticLines + */ + private static void diagnosticLinesToDiagnostics( + List list, List lines) { + for (TestDiagnosticLine line : lines) { + list.addAll(line.getDiagnostics()); } + } + + /** + * StringToTestDiagnosticLine converts a line of a file into a TestDiagnosticLine. There are + * currently two possible formats: one for Java source code, and one for Diagnostic files. + * + *

No classes implement this interface. The methods TestDiagnosticUtils.fromJavaSourceLine and + * TestDiagnosticUtils.fromDiagnosticFileLine instantiate the method. + */ + private interface StringToTestDiagnosticLine { /** - * StringToTestDiagnosticLine converts a line of a file into a TestDiagnosticLine. There are - * currently two possible formats: one for Java source code, and one for Diagnostic files. + * Converts the specified line of the file into a {@link TestDiagnosticLine}. * - *

No classes implement this interface. The methods TestDiagnosticUtils.fromJavaSourceLine - * and TestDiagnosticUtils.fromDiagnosticFileLine instantiate the method. + * @param filename name of the file + * @param line the text of the line to convert to a TestDiagnosticLine + * @param lineNumber the line number of the line + * @return TestDiagnosticLine corresponding to {@code line} */ - private interface StringToTestDiagnosticLine { - - /** - * Converts the specified line of the file into a {@link TestDiagnosticLine}. - * - * @param filename name of the file - * @param line the text of the line to convert to a TestDiagnosticLine - * @param lineNumber the line number of the line - * @return TestDiagnosticLine corresponding to {@code line} - */ - TestDiagnosticLine createTestDiagnosticLine(String filename, String line, long lineNumber); - } + TestDiagnosticLine createTestDiagnosticLine(String filename, String line, long lineNumber); + } - /// - /// End of static methods, start of per-instance state. - /// + /// + /// End of static methods, start of per-instance state. + /// - /** Converts a file line into a TestDiagnosticLine. */ - private final StringToTestDiagnosticLine codec; + /** Converts a file line into a TestDiagnosticLine. */ + private final StringToTestDiagnosticLine codec; - /** The file name. */ - private final String filename; + /** The file name. */ + private final String filename; - /** The reader for the file. */ - private final @Owning LineNumberReader reader; + /** The reader for the file. */ + private final @Owning LineNumberReader reader; - /** The next line to be read, or null. */ - private @Nullable String nextLine = null; + /** The next line to be read, or null. */ + private @Nullable String nextLine = null; - /** The line number of the next line to be read, or -1. */ - private @GTENegativeOne int nextLineNumber = -1; + /** The line number of the next line to be read, or -1. */ + private @GTENegativeOne int nextLineNumber = -1; - /** - * Creates a JavaDiagnosticReader. - * - * @param toRead the file to read - * @param codec converts a file line into a TestDiagnosticLine - */ - private JavaDiagnosticReader(File toRead, StringToTestDiagnosticLine codec) { - this.codec = codec; - this.filename = toRead.getName(); - LineNumberReader reader = null; + /** + * Creates a JavaDiagnosticReader. + * + * @param toRead the file to read + * @param codec converts a file line into a TestDiagnosticLine + */ + private JavaDiagnosticReader(File toRead, StringToTestDiagnosticLine codec) { + this.codec = codec; + this.filename = toRead.getName(); + LineNumberReader reader = null; + try { + reader = new LineNumberReader(new FileReader(toRead)); + this.reader = reader; + advance(); + } catch (IOException e) { + if (reader != null) { try { - reader = new LineNumberReader(new FileReader(toRead)); - this.reader = reader; - advance(); - } catch (IOException e) { - if (reader != null) { - try { - reader.close(); - } catch (Exception exceptionOnClose) { - e.addSuppressed(exceptionOnClose); - } - } - throw new RuntimeException(e); + reader.close(); + } catch (Exception exceptionOnClose) { + e.addSuppressed(exceptionOnClose); } + } + throw new RuntimeException(e); } + } - /** - * Creates a JavaDiagnosticReader. - * - * @param toReadFileObject the file to read - * @param codec converts a file line into a TestDiagnosticLine - */ - private JavaDiagnosticReader( - JavaFileObject toReadFileObject, StringToTestDiagnosticLine codec) { - this.codec = codec; - this.filename = new File(toReadFileObject.getName()).getName(); - LineNumberReader reader = null; + /** + * Creates a JavaDiagnosticReader. + * + * @param toReadFileObject the file to read + * @param codec converts a file line into a TestDiagnosticLine + */ + private JavaDiagnosticReader(JavaFileObject toReadFileObject, StringToTestDiagnosticLine codec) { + this.codec = codec; + this.filename = new File(toReadFileObject.getName()).getName(); + LineNumberReader reader = null; + try { + reader = new LineNumberReader(toReadFileObject.openReader(true)); + this.reader = reader; + advance(); + } catch (IOException e) { + if (reader != null) { try { - reader = new LineNumberReader(toReadFileObject.openReader(true)); - this.reader = reader; - advance(); - } catch (IOException e) { - if (reader != null) { - try { - reader.close(); - } catch (Exception exceptionOnClose) { - e.addSuppressed(exceptionOnClose); - } - } - throw new RuntimeException(e); + reader.close(); + } catch (Exception exceptionOnClose) { + e.addSuppressed(exceptionOnClose); } + } + throw new RuntimeException(e); } + } - @Override - @Pure - public boolean hasNext() { - return nextLine != null; - } + @Override + @Pure + public boolean hasNext() { + return nextLine != null; + } - @Override - public void remove() { - throw new UnsupportedOperationException( - "Cannot remove elements using JavaDiagnosticFileReader."); + @Override + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove elements using JavaDiagnosticFileReader."); + } + + @Override + public TestDiagnosticLine next() { + if (nextLine == null) { + throw new NoSuchElementException(); } - @Override - public TestDiagnosticLine next() { - if (nextLine == null) { - throw new NoSuchElementException(); - } + String currentLine = nextLine; + int currentLineNumber = nextLineNumber; - String currentLine = nextLine; - int currentLineNumber = nextLineNumber; + try { + advance(); - try { - advance(); + currentLine = TestDiagnosticUtils.handleEndOfLineJavaDiagnostic(currentLine); - currentLine = TestDiagnosticUtils.handleEndOfLineJavaDiagnostic(currentLine); - - if (TestDiagnosticUtils.isJavaDiagnosticLineStart(currentLine)) { - while (TestDiagnosticUtils.isJavaDiagnosticLineContinuation(nextLine)) { - currentLine = - currentLine.trim() - + " " - + TestDiagnosticUtils.continuationPart(nextLine); - currentLineNumber = nextLineNumber; - advance(); - } - } - } catch (IOException e) { - throw new RuntimeException(e); + if (TestDiagnosticUtils.isJavaDiagnosticLineStart(currentLine)) { + while (TestDiagnosticUtils.isJavaDiagnosticLineContinuation(nextLine)) { + currentLine = currentLine.trim() + " " + TestDiagnosticUtils.continuationPart(nextLine); + currentLineNumber = nextLineNumber; + advance(); } - - return codec.createTestDiagnosticLine(filename, currentLine, currentLineNumber); + } + } catch (IOException e) { + throw new RuntimeException(e); } - @RequiresNonNull("reader") - protected void advance(@UnknownInitialization JavaDiagnosticReader this) throws IOException { - nextLine = reader.readLine(); - nextLineNumber = reader.getLineNumber(); - if (nextLine == null) { - reader.close(); - } + return codec.createTestDiagnosticLine(filename, currentLine, currentLineNumber); + } + + @RequiresNonNull("reader") + protected void advance(@UnknownInitialization JavaDiagnosticReader this) throws IOException { + nextLine = reader.readLine(); + nextLineNumber = reader.getLineNumber(); + if (nextLine == null) { + reader.close(); } + } - @Override - @EnsuresCalledMethods(value = "reader", methods = "close") - public void close() { - try { - reader.close(); - } catch (IOException e) { - throw new Error(e); - } + @Override + @EnsuresCalledMethods(value = "reader", methods = "close") + public void close() { + try { + reader.close(); + } catch (IOException e) { + throw new Error(e); } + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java index 9e961dbe8ab..b7432202a18 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.test.diagnostics; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.nio.file.Path; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents an expected error/warning message in a Java test file or an error/warning reported by @@ -17,247 +16,243 @@ */ public class TestDiagnostic { - /** The path to the test file. */ - protected final Path file; + /** The path to the test file. */ + protected final Path file; - /** The base file name of the test file. */ - protected final String filename; + /** The base file name of the test file. */ + protected final String filename; - /** The line number of the diagnostic output. */ - protected final long lineNumber; + /** The line number of the diagnostic output. */ + protected final long lineNumber; - /** The diagnostic kind of the output. */ - protected final DiagnosticKind kind; + /** The diagnostic kind of the output. */ + protected final DiagnosticKind kind; - /** The full diagnostic message. */ - protected final String message; + /** The full diagnostic message. */ + protected final String message; - /** - * The message key that usually appears between parentheses in diagnostic messages. Parentheses - * are removed and field messageKeyParens indicates whether they were present. - */ - protected final String messageKey; + /** + * The message key that usually appears between parentheses in diagnostic messages. Parentheses + * are removed and field messageKeyParens indicates whether they were present. + */ + protected final String messageKey; - /** Whether the message key had parentheses around it. */ - protected final boolean messageKeyParens; + /** Whether the message key had parentheses around it. */ + protected final boolean messageKeyParens; - /** Whether this diagnostic should no longer be reported after whole program inference. */ - protected final boolean isFixable; + /** Whether this diagnostic should no longer be reported after whole program inference. */ + protected final boolean isFixable; - /** - * Basic constructor that sets the immutable fields of this diagnostic. - * - * @param file the path to the test file - * @param lineNumber the line number of the diagnostic output - * @param kind the diagnostic kind of the output - * @param messageKey the message key - * @param message the full diagnostic message - * @param isFixable whether WPI can fix the test - */ - public TestDiagnostic( - Path file, - long lineNumber, - DiagnosticKind kind, - String messageKey, - String message, - boolean isFixable) { - this.file = file; - this.filename = - file.getFileName() != null ? file.getFileName().toString() : file.toString(); - this.lineNumber = lineNumber; - this.kind = kind; - this.message = message; - this.isFixable = isFixable; + /** + * Basic constructor that sets the immutable fields of this diagnostic. + * + * @param file the path to the test file + * @param lineNumber the line number of the diagnostic output + * @param kind the diagnostic kind of the output + * @param messageKey the message key + * @param message the full diagnostic message + * @param isFixable whether WPI can fix the test + */ + public TestDiagnostic( + Path file, + long lineNumber, + DiagnosticKind kind, + String messageKey, + String message, + boolean isFixable) { + this.file = file; + this.filename = file.getFileName() != null ? file.getFileName().toString() : file.toString(); + this.lineNumber = lineNumber; + this.kind = kind; + this.message = message; + this.isFixable = isFixable; - // Keep in sync with code below. - int open = messageKey.indexOf("("); - int close = messageKey.indexOf(")"); - if (open == 0 && close > open) { - this.messageKey = messageKey.substring(open + 1, close).trim(); - this.messageKeyParens = true; - } else { - this.messageKey = messageKey; - this.messageKeyParens = false; - } + // Keep in sync with code below. + int open = messageKey.indexOf("("); + int close = messageKey.indexOf(")"); + if (open == 0 && close > open) { + this.messageKey = messageKey.substring(open + 1, close).trim(); + this.messageKeyParens = true; + } else { + this.messageKey = messageKey; + this.messageKeyParens = false; } + } - /** - * Basic constructor that sets the immutable fields of this diagnostic. - * - * @param file the path to the test file - * @param lineNumber the line number of the diagnostic output - * @param kind the diagnostic kind of the output - * @param message the full diagnostic message - * @param isFixable whether WPI can fix the test - */ - public TestDiagnostic( - Path file, long lineNumber, DiagnosticKind kind, String message, boolean isFixable) { - this.file = file; - this.filename = - file.getFileName() != null ? file.getFileName().toString() : file.toString(); - this.lineNumber = lineNumber; - this.kind = kind; - this.message = message; - this.isFixable = isFixable; + /** + * Basic constructor that sets the immutable fields of this diagnostic. + * + * @param file the path to the test file + * @param lineNumber the line number of the diagnostic output + * @param kind the diagnostic kind of the output + * @param message the full diagnostic message + * @param isFixable whether WPI can fix the test + */ + public TestDiagnostic( + Path file, long lineNumber, DiagnosticKind kind, String message, boolean isFixable) { + this.file = file; + this.filename = file.getFileName() != null ? file.getFileName().toString() : file.toString(); + this.lineNumber = lineNumber; + this.kind = kind; + this.message = message; + this.isFixable = isFixable; - if (keepFullMessage(message)) { - this.messageKey = message; - this.messageKeyParens = false; - } else { - String firstline; - // There might be a mismatch between the System.lineSeparator() and the diagnostic - // message, so manually check both options. - int lineSepPos = this.message.indexOf("\r\n"); - if (lineSepPos == -1) { - lineSepPos = this.message.indexOf("\n"); - } - if (lineSepPos != -1) { - firstline = this.message.substring(0, lineSepPos).trim(); - } else { - firstline = this.message; - } + if (keepFullMessage(message)) { + this.messageKey = message; + this.messageKeyParens = false; + } else { + String firstline; + // There might be a mismatch between the System.lineSeparator() and the diagnostic + // message, so manually check both options. + int lineSepPos = this.message.indexOf("\r\n"); + if (lineSepPos == -1) { + lineSepPos = this.message.indexOf("\n"); + } + if (lineSepPos != -1) { + firstline = this.message.substring(0, lineSepPos).trim(); + } else { + firstline = this.message; + } - // Keep in sync with code above. - int open = firstline.indexOf("("); - int close = firstline.indexOf(")"); - if (open == 0 && close > open) { - this.messageKey = firstline.substring(open + 1, close).trim(); - this.messageKeyParens = true; - } else { - this.messageKey = firstline; - this.messageKeyParens = false; - } - } + // Keep in sync with code above. + int open = firstline.indexOf("("); + int close = firstline.indexOf(")"); + if (open == 0 && close > open) { + this.messageKey = firstline.substring(open + 1, close).trim(); + this.messageKeyParens = true; + } else { + this.messageKey = firstline; + this.messageKeyParens = false; + } } + } - /** - * Determine whether the full diagnostic message should be used as message key. This is useful - * to ensure e.g. stack traces are fully shown. - * - * @param message the full message - * @return whether the full diagnostic message should be used - */ - public static boolean keepFullMessage(String message) { - return message.contains("unexpected Throwable") - || message.contains("Compilation unit") - || message.contains("OutOfMemoryError"); - } + /** + * Determine whether the full diagnostic message should be used as message key. This is useful to + * ensure e.g. stack traces are fully shown. + * + * @param message the full message + * @return whether the full diagnostic message should be used + */ + public static boolean keepFullMessage(String message) { + return message.contains("unexpected Throwable") + || message.contains("Compilation unit") + || message.contains("OutOfMemoryError"); + } - /** - * The path to the test file. - * - * @return the path to the test file - */ - public Path getFile() { - return file; - } + /** + * The path to the test file. + * + * @return the path to the test file + */ + public Path getFile() { + return file; + } - /** - * The base file name of the test file. - * - * @return the base file name of the test file - */ - public String getFilename() { - return filename; - } + /** + * The base file name of the test file. + * + * @return the base file name of the test file + */ + public String getFilename() { + return filename; + } - /** - * The line number of the diagnostic output. - * - * @return the line number of the diagnostic output - */ - public long getLineNumber() { - return lineNumber; - } + /** + * The line number of the diagnostic output. + * + * @return the line number of the diagnostic output + */ + public long getLineNumber() { + return lineNumber; + } - /** - * The diagnostic kind of the output. - * - * @return the diagnostic kind of the output - */ - public DiagnosticKind getKind() { - return kind; - } + /** + * The diagnostic kind of the output. + * + * @return the diagnostic kind of the output + */ + public DiagnosticKind getKind() { + return kind; + } - /** - * The message key, without surrounding parentheses. - * - * @return the message key - */ - public String getMessageKey() { - return messageKey; - } - - /** - * The full diagnostic message. - * - * @return the full diagnostic message - */ - public String getMessage() { - return message; - } + /** + * The message key, without surrounding parentheses. + * + * @return the message key + */ + public String getMessageKey() { + return messageKey; + } - /** - * Whether WPI can fix the test. - * - * @return whether WPI can fix the test - */ - public boolean isFixable() { - return isFixable; - } + /** + * The full diagnostic message. + * + * @return the full diagnostic message + */ + public String getMessage() { + return message; + } - /** - * Equality is compared based the file name, not the full path, on the messageKey, not the full - * message, and without considering isFixable and messageKeyParens. - * - * @return true if this and otherObj are equal according to file, lineNumber, kind, and - * messageKey - */ - @Override - public boolean equals(@Nullable Object otherObj) { - if (otherObj == null || otherObj.getClass() != TestDiagnostic.class) { - return false; - } + /** + * Whether WPI can fix the test. + * + * @return whether WPI can fix the test + */ + public boolean isFixable() { + return isFixable; + } - TestDiagnostic other = (TestDiagnostic) otherObj; - return other.filename.equals(this.filename) - && other.lineNumber == lineNumber - && other.kind == this.kind - && other.messageKey.equals(this.messageKey); + /** + * Equality is compared based the file name, not the full path, on the messageKey, not the full + * message, and without considering isFixable and messageKeyParens. + * + * @return true if this and otherObj are equal according to file, lineNumber, kind, and messageKey + */ + @Override + public boolean equals(@Nullable Object otherObj) { + if (otherObj == null || otherObj.getClass() != TestDiagnostic.class) { + return false; } - @Override - public int hashCode() { - // Only filename, not file, and only messageKey, not message, not isFixable, not - // messageKeyParens. - return Objects.hash(filename, lineNumber, kind, messageKey); - } + TestDiagnostic other = (TestDiagnostic) otherObj; + return other.filename.equals(this.filename) + && other.lineNumber == lineNumber + && other.kind == this.kind + && other.messageKey.equals(this.messageKey); + } - /** - * Returns a representation of this diagnostic as if it appeared in a diagnostics file. This - * uses only the base file name, not the full path, and only the message key, not the full - * message. Field {@link #messageKeyParens} influences whether the message key is output in - * parentheses. - * - * @return a representation of this diagnostic as if it appeared in a diagnostics file - */ - @Override - public String toString() { - if (messageKeyParens) { - return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + messageKey + ")"; - } else { - return filename + ":" + lineNumber + ": " + kind.parseString + ": " + messageKey; - } - } + @Override + public int hashCode() { + // Only filename, not file, and only messageKey, not message, not isFixable, not + // messageKeyParens. + return Objects.hash(filename, lineNumber, kind, messageKey); + } - /** - * Returns the internal representation of this, formatted. - * - * @return the internal representation of this, formatted - */ - public String repr() { - return String.format( - "[TestDiagnostic: file=%s, lineNumber=%d, kind=%s, message=%s]", - file, lineNumber, kind, message); + /** + * Returns a representation of this diagnostic as if it appeared in a diagnostics file. This uses + * only the base file name, not the full path, and only the message key, not the full message. + * Field {@link #messageKeyParens} influences whether the message key is output in parentheses. + * + * @return a representation of this diagnostic as if it appeared in a diagnostics file + */ + @Override + public String toString() { + if (messageKeyParens) { + return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + messageKey + ")"; + } else { + return filename + ":" + lineNumber + ": " + kind.parseString + ": " + messageKey; } + } + + /** + * Returns the internal representation of this, formatted. + * + * @return the internal representation of this, formatted + */ + public String repr() { + return String.format( + "[TestDiagnostic: file=%s, lineNumber=%d, kind=%s, message=%s]", + file, lineNumber, kind, message); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java index dee08978bbb..7c12461db61 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java @@ -4,39 +4,36 @@ /** Represents a list of TestDiagnostics, which was read from a one line of a file. */ public class TestDiagnosticLine { - private final String filename; - private final long lineNumber; - private final String originalLine; - private final List diagnostics; - - public TestDiagnosticLine( - String filename, - long lineNumber, - String originalLine, - List diagnostics) { - this.filename = filename; - this.lineNumber = lineNumber; - this.originalLine = originalLine; - this.diagnostics = diagnostics; - } - - public String getFilename() { - return filename; - } - - public boolean hasDiagnostics() { - return !diagnostics.isEmpty(); - } - - public long getLineNumber() { - return lineNumber; - } - - public String getOriginalLine() { - return originalLine; - } - - public List getDiagnostics() { - return diagnostics; - } + private final String filename; + private final long lineNumber; + private final String originalLine; + private final List diagnostics; + + public TestDiagnosticLine( + String filename, long lineNumber, String originalLine, List diagnostics) { + this.filename = filename; + this.lineNumber = lineNumber; + this.originalLine = originalLine; + this.diagnostics = diagnostics; + } + + public String getFilename() { + return filename; + } + + public boolean hasDiagnostics() { + return !diagnostics.isEmpty(); + } + + public long getLineNumber() { + return lineNumber; + } + + public String getOriginalLine() { + return originalLine; + } + + public List getDiagnostics() { + return diagnostics; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java index 6a476746fc0..a132563cb44 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java @@ -1,10 +1,5 @@ package org.checkerframework.framework.test.diagnostics; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.IPair; - import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; @@ -14,437 +9,433 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.IPair; /** A set of utilities and factory methods useful for working with TestDiagnostics. */ public class TestDiagnosticUtils { - /** How the diagnostics appear in Java source files. */ - public static final String DIAGNOSTIC_IN_JAVA_REGEX = - "\\s*(?error|fixable-error|warning|fixable-warning|Note|other):\\s*(?[\\s\\S]*)"; - - /** Pattern compiled from {@link #DIAGNOSTIC_IN_JAVA_REGEX}. */ - public static final Pattern DIAGNOSTIC_IN_JAVA_PATTERN = - Pattern.compile(DIAGNOSTIC_IN_JAVA_REGEX); - - /** How the diagnostic warnings appear in Java source files. */ - public static final String DIAGNOSTIC_WARNING_IN_JAVA_REGEX = - "\\s*warning:\\s*(?[\\s\\S]*)"; - - /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_IN_JAVA_REGEX}. */ - public static final Pattern DIAGNOSTIC_WARNING_IN_JAVA_PATTERN = - Pattern.compile(DIAGNOSTIC_WARNING_IN_JAVA_REGEX); - - /** How the diagnostics appear in javax tools diagnostics from the compiler. */ - public static final String DIAGNOSTIC_REGEX = - "(?:(?\\d+):)?" + DIAGNOSTIC_IN_JAVA_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_REGEX}. */ - public static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile(DIAGNOSTIC_REGEX); - - /** How the diagnostic warnings appear in javax tools diagnostics from the compiler. */ - public static final String DIAGNOSTIC_WARNING_REGEX = - "(?:(?\\d+):)?" + DIAGNOSTIC_WARNING_IN_JAVA_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_REGEX}. */ - public static final Pattern DIAGNOSTIC_WARNING_PATTERN = - Pattern.compile(DIAGNOSTIC_WARNING_REGEX); - - /** How the diagnostics appear in diagnostic files (.out). */ - public static final String DIAGNOSTIC_FILE_REGEX = ".+\\.java" + DIAGNOSTIC_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_FILE_REGEX}. */ - public static final Pattern DIAGNOSTIC_FILE_PATTERN = Pattern.compile(DIAGNOSTIC_FILE_REGEX); - - /** How the diagnostic warnings appear in diagnostic files (.out). */ - public static final String DIAGNOSTIC_FILE_WARNING_REGEX = - ".+\\.java" + DIAGNOSTIC_WARNING_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_FILE_WARNING_REGEX}. */ - public static final Pattern DIAGNOSTIC_FILE_WARNING_PATTERN = - Pattern.compile(DIAGNOSTIC_FILE_WARNING_REGEX); - - /** - * Instantiate the diagnostic based on a string that would appear in diagnostic files (i.e. - * files that only contain line after line of expected diagnostics). - * - * @param stringFromDiagnosticFile a single diagnostic string to parse - * @return a new TestDiagnostic - */ - public static TestDiagnostic fromDiagnosticFileString(String stringFromDiagnosticFile) { - return fromPatternMatching( - DIAGNOSTIC_FILE_PATTERN, - DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, - // Important to use "" to make input of expected warnings easy. - Paths.get(""), - null, - stringFromDiagnosticFile); + /** How the diagnostics appear in Java source files. */ + public static final String DIAGNOSTIC_IN_JAVA_REGEX = + "\\s*(?error|fixable-error|warning|fixable-warning|Note|other):\\s*(?[\\s\\S]*)"; + + /** Pattern compiled from {@link #DIAGNOSTIC_IN_JAVA_REGEX}. */ + public static final Pattern DIAGNOSTIC_IN_JAVA_PATTERN = + Pattern.compile(DIAGNOSTIC_IN_JAVA_REGEX); + + /** How the diagnostic warnings appear in Java source files. */ + public static final String DIAGNOSTIC_WARNING_IN_JAVA_REGEX = + "\\s*warning:\\s*(?[\\s\\S]*)"; + + /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_IN_JAVA_REGEX}. */ + public static final Pattern DIAGNOSTIC_WARNING_IN_JAVA_PATTERN = + Pattern.compile(DIAGNOSTIC_WARNING_IN_JAVA_REGEX); + + /** How the diagnostics appear in javax tools diagnostics from the compiler. */ + public static final String DIAGNOSTIC_REGEX = + "(?:(?\\d+):)?" + DIAGNOSTIC_IN_JAVA_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_REGEX}. */ + public static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile(DIAGNOSTIC_REGEX); + + /** How the diagnostic warnings appear in javax tools diagnostics from the compiler. */ + public static final String DIAGNOSTIC_WARNING_REGEX = + "(?:(?\\d+):)?" + DIAGNOSTIC_WARNING_IN_JAVA_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_REGEX}. */ + public static final Pattern DIAGNOSTIC_WARNING_PATTERN = + Pattern.compile(DIAGNOSTIC_WARNING_REGEX); + + /** How the diagnostics appear in diagnostic files (.out). */ + public static final String DIAGNOSTIC_FILE_REGEX = ".+\\.java" + DIAGNOSTIC_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_FILE_REGEX}. */ + public static final Pattern DIAGNOSTIC_FILE_PATTERN = Pattern.compile(DIAGNOSTIC_FILE_REGEX); + + /** How the diagnostic warnings appear in diagnostic files (.out). */ + public static final String DIAGNOSTIC_FILE_WARNING_REGEX = ".+\\.java" + DIAGNOSTIC_WARNING_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_FILE_WARNING_REGEX}. */ + public static final Pattern DIAGNOSTIC_FILE_WARNING_PATTERN = + Pattern.compile(DIAGNOSTIC_FILE_WARNING_REGEX); + + /** + * Instantiate the diagnostic based on a string that would appear in diagnostic files (i.e. files + * that only contain line after line of expected diagnostics). + * + * @param stringFromDiagnosticFile a single diagnostic string to parse + * @return a new TestDiagnostic + */ + public static TestDiagnostic fromDiagnosticFileString(String stringFromDiagnosticFile) { + return fromPatternMatching( + DIAGNOSTIC_FILE_PATTERN, + DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, + // Important to use "" to make input of expected warnings easy. + Paths.get(""), + null, + stringFromDiagnosticFile); + } + + /** + * Instantiate the diagnostic from a string that would appear in a Java file, e.g.: "error: + * (message)" + * + * @param filename the file containing the diagnostic (and the error) + * @param lineNumber the line number of the line immediately below the diagnostic comment in the + * Java file + * @param stringFromJavaFile the string containing the diagnostic + * @return a new TestDiagnostic + */ + public static TestDiagnostic fromJavaFileComment( + String filename, long lineNumber, String stringFromJavaFile) { + return fromPatternMatching( + DIAGNOSTIC_IN_JAVA_PATTERN, + DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, + Paths.get(filename), + lineNumber, + stringFromJavaFile); + } + + /** + * Instantiate a diagnostic from output produced by the Java compiler. The resulting diagnostic is + * never fixable and always has parentheses. + * + * @param diagnosticString the compiler diagnostics string + * @return the corresponding test diagnostic + */ + public static TestDiagnostic fromJavaxToolsDiagnostic(String diagnosticString) { + // It would be nice not to parse this from the diagnostic string. + // However, diagnostic.toString() may contain "[unchecked]" even though getMessage() does + // not. + // Since we want to match the error messages reported by javac exactly, we must parse. + // diagnostic.getCode() returns "compiler.warn.prob.found.req" for "[unchecked]" messages, + // but not clear how to map from one to the other. + IPair trimmed = formatJavaxToolString(diagnosticString); + return fromPatternMatching( + DIAGNOSTIC_PATTERN, DIAGNOSTIC_WARNING_PATTERN, trimmed.second, null, trimmed.first); + } + + /** + * Instantiate the diagnostic via pattern-matching against patterns. + * + * @param diagnosticPattern a pattern that matches any diagnostic + * @param warningPattern a pattern that matches a warning diagnostic + * @param file the test file + * @param lineNumber the line number + * @param diagnosticString the string to parse + * @return a diagnostic parsed from the given string + */ + @SuppressWarnings("nullness") // TODO: regular expression group access + protected static TestDiagnostic fromPatternMatching( + Pattern diagnosticPattern, + Pattern warningPattern, + Path file, + @Nullable Long lineNumber, + String diagnosticString) { + final DiagnosticKind kind; + final String message; + final boolean isFixable; + long lineNo = -1; + + if (lineNumber != null) { + lineNo = lineNumber; } - /** - * Instantiate the diagnostic from a string that would appear in a Java file, e.g.: "error: - * (message)" - * - * @param filename the file containing the diagnostic (and the error) - * @param lineNumber the line number of the line immediately below the diagnostic comment in the - * Java file - * @param stringFromJavaFile the string containing the diagnostic - * @return a new TestDiagnostic - */ - public static TestDiagnostic fromJavaFileComment( - String filename, long lineNumber, String stringFromJavaFile) { - return fromPatternMatching( - DIAGNOSTIC_IN_JAVA_PATTERN, - DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, - Paths.get(filename), - lineNumber, - stringFromJavaFile); - } - - /** - * Instantiate a diagnostic from output produced by the Java compiler. The resulting diagnostic - * is never fixable and always has parentheses. - * - * @param diagnosticString the compiler diagnostics string - * @return the corresponding test diagnostic - */ - public static TestDiagnostic fromJavaxToolsDiagnostic(String diagnosticString) { - // It would be nice not to parse this from the diagnostic string. - // However, diagnostic.toString() may contain "[unchecked]" even though getMessage() does - // not. - // Since we want to match the error messages reported by javac exactly, we must parse. - // diagnostic.getCode() returns "compiler.warn.prob.found.req" for "[unchecked]" messages, - // but not clear how to map from one to the other. - IPair trimmed = formatJavaxToolString(diagnosticString); - return fromPatternMatching( - DIAGNOSTIC_PATTERN, - DIAGNOSTIC_WARNING_PATTERN, - trimmed.second, - null, - trimmed.first); - } - - /** - * Instantiate the diagnostic via pattern-matching against patterns. - * - * @param diagnosticPattern a pattern that matches any diagnostic - * @param warningPattern a pattern that matches a warning diagnostic - * @param file the test file - * @param lineNumber the line number - * @param diagnosticString the string to parse - * @return a diagnostic parsed from the given string - */ - @SuppressWarnings("nullness") // TODO: regular expression group access - protected static TestDiagnostic fromPatternMatching( - Pattern diagnosticPattern, - Pattern warningPattern, - Path file, - @Nullable Long lineNumber, - String diagnosticString) { - final DiagnosticKind kind; - final String message; - final boolean isFixable; - long lineNo = -1; - - if (lineNumber != null) { - lineNo = lineNumber; + Matcher diagnosticMatcher = diagnosticPattern.matcher(diagnosticString); + if (diagnosticMatcher.matches()) { + IPair categoryToFixable = + parseCategoryString(diagnosticMatcher.group("kind")); + kind = categoryToFixable.first; + isFixable = categoryToFixable.second; + message = diagnosticMatcher.group("message").trim(); + if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { + lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); + } + } else { + Matcher warningMatcher = warningPattern.matcher(diagnosticString); + if (warningMatcher.matches()) { + kind = DiagnosticKind.Warning; + isFixable = false; + message = warningMatcher.group("message").trim(); + if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { + lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); } - - Matcher diagnosticMatcher = diagnosticPattern.matcher(diagnosticString); - if (diagnosticMatcher.matches()) { - IPair categoryToFixable = - parseCategoryString(diagnosticMatcher.group("kind")); - kind = categoryToFixable.first; - isFixable = categoryToFixable.second; - message = diagnosticMatcher.group("message").trim(); - if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { - lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); - } + } else if (diagnosticString.startsWith("warning:")) { + kind = DiagnosticKind.Warning; + isFixable = false; + message = diagnosticString.substring("warning:".length()).trim(); + if (lineNumber != null) { + lineNo = lineNumber; } else { - Matcher warningMatcher = warningPattern.matcher(diagnosticString); - if (warningMatcher.matches()) { - kind = DiagnosticKind.Warning; - isFixable = false; - message = warningMatcher.group("message").trim(); - if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { - lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); - } - } else if (diagnosticString.startsWith("warning:")) { - kind = DiagnosticKind.Warning; - isFixable = false; - message = diagnosticString.substring("warning:".length()).trim(); - if (lineNumber != null) { - lineNo = lineNumber; - } else { - lineNo = 0; - } - } else { - kind = DiagnosticKind.Other; - isFixable = false; - message = diagnosticString; - // this should only happen if we are parsing a Java Diagnostic from the compiler - // that we did do not handle - if (lineNumber == null) { - lineNo = -1; - } - } + lineNo = 0; } - - // Check if the message matches detailed message format. - // Trim the message to remove leading/trailing whitespace. - // Keep separator in sync with SourceChecker.DETAILS_SEPARATOR. - String[] diagnosticStrings = - Arrays.stream(message.split(" \\$\\$ ")).map(String::trim).toArray(String[]::new); - if (diagnosticStrings.length > 1) { - // See SourceChecker.detailedMsgTextPrefix. - // The parts of the detailed message are: - - // (1) message key; - String messageKey = diagnosticStrings[0]; - - // (2) number of additional tokens, and those tokens; this depends on the error message, - // and an example is the found and expected types; - int numAdditionalTokens = Integer.parseInt(diagnosticStrings[1]); - int lastAdditionalToken = 2 + numAdditionalTokens; - List additionalTokens = - Arrays.asList(diagnosticStrings).subList(2, lastAdditionalToken); - - // (3) the diagnostic position, given by the format (startPosition, endPosition); - String pairParens = diagnosticStrings[lastAdditionalToken]; - // remove the leading and trailing parentheses and spaces - String pair = pairParens.substring(2, pairParens.length() - 2); - String[] diagPositionString = pair.split(", "); - long startPosition = Long.parseLong(diagPositionString[0]); - long endPosition = Long.parseLong(diagPositionString[1]); - - // (4) the human-readable diagnostic message. - String readableMessage = diagnosticStrings[lastAdditionalToken + 1]; - - return new DetailedTestDiagnostic( - file, - lineNo, - kind, - messageKey, - additionalTokens, - startPosition, - endPosition, - readableMessage, - isFixable); + } else { + kind = DiagnosticKind.Other; + isFixable = false; + message = diagnosticString; + // this should only happen if we are parsing a Java Diagnostic from the compiler + // that we did do not handle + if (lineNumber == null) { + lineNo = -1; } - - return new TestDiagnostic(file, lineNo, kind, message, isFixable); + } } - /** - * Given a javax diagnostic, return a pair of (trimmed, file), where "trimmed" is the message - * without the leading filename and the file path. As an example: "foo/bar/Baz.java:49: My error - * message" is turned into {@code IPair.of(":49: My error message", Path("foo/bar/Baz.java"))}. - * If the file path cannot be determined, it uses {@code ""}. This is necessary to make writing - * the expected warnings easy. - * - * @param original a javax diagnostic - * @return the diagnostic, split into message and file - */ - public static IPair formatJavaxToolString(String original) { - String firstline; - // In TestDiagnostic we manually check for "\r\n" and "\n". Here, we only use - // `firstline` to find the file name. Using the system line separator is not - // problem here, it seems. - int lineSepPos = original.indexOf(System.lineSeparator()); - if (lineSepPos != -1) { - firstline = original.substring(0, lineSepPos); - } else { - firstline = original; - } - - String trimmed; - Path file; - int extensionPos = firstline.indexOf(".java:"); - if (extensionPos != -1) { - file = Paths.get(firstline.substring(0, extensionPos + 5).trim()); - trimmed = original.substring(extensionPos + 5).trim(); - } else { - // Important to use "" to make input of expected warnings easy. - // For an example, see file - // ./checker/tests/nullness-stubfile/NullnessStubfileMerge.java - file = Paths.get(""); - trimmed = original; - } - - return IPair.of(trimmed, file); + // Check if the message matches detailed message format. + // Trim the message to remove leading/trailing whitespace. + // Keep separator in sync with SourceChecker.DETAILS_SEPARATOR. + String[] diagnosticStrings = + Arrays.stream(message.split(" \\$\\$ ")).map(String::trim).toArray(String[]::new); + if (diagnosticStrings.length > 1) { + // See SourceChecker.detailedMsgTextPrefix. + // The parts of the detailed message are: + + // (1) message key; + String messageKey = diagnosticStrings[0]; + + // (2) number of additional tokens, and those tokens; this depends on the error message, + // and an example is the found and expected types; + int numAdditionalTokens = Integer.parseInt(diagnosticStrings[1]); + int lastAdditionalToken = 2 + numAdditionalTokens; + List additionalTokens = + Arrays.asList(diagnosticStrings).subList(2, lastAdditionalToken); + + // (3) the diagnostic position, given by the format (startPosition, endPosition); + String pairParens = diagnosticStrings[lastAdditionalToken]; + // remove the leading and trailing parentheses and spaces + String pair = pairParens.substring(2, pairParens.length() - 2); + String[] diagPositionString = pair.split(", "); + long startPosition = Long.parseLong(diagPositionString[0]); + long endPosition = Long.parseLong(diagPositionString[1]); + + // (4) the human-readable diagnostic message. + String readableMessage = diagnosticStrings[lastAdditionalToken + 1]; + + return new DetailedTestDiagnostic( + file, + lineNo, + kind, + messageKey, + additionalTokens, + startPosition, + endPosition, + readableMessage, + isFixable); } - /** - * Given a category string that may be prepended with "fixable-", return the category enum that - * corresponds with the category and whether or not it is a isFixable error. - * - * @param category a category string - * @return the corresponding diagnostic kind and whether it is fixable - */ - private static IPair parseCategoryString(String category) { - String fixable = "fixable-"; - boolean isFixable = category.startsWith(fixable); - if (isFixable) { - category = category.substring(fixable.length()); - } - DiagnosticKind categoryEnum = DiagnosticKind.fromParseString(category); - if (categoryEnum == null) { - throw new Error("Unparsable category: " + category); - } - - return IPair.of(categoryEnum, isFixable); + return new TestDiagnostic(file, lineNo, kind, message, isFixable); + } + + /** + * Given a javax diagnostic, return a pair of (trimmed, file), where "trimmed" is the message + * without the leading filename and the file path. As an example: "foo/bar/Baz.java:49: My error + * message" is turned into {@code IPair.of(":49: My error message", Path("foo/bar/Baz.java"))}. If + * the file path cannot be determined, it uses {@code ""}. This is necessary to make writing the + * expected warnings easy. + * + * @param original a javax diagnostic + * @return the diagnostic, split into message and file + */ + public static IPair formatJavaxToolString(String original) { + String firstline; + // In TestDiagnostic we manually check for "\r\n" and "\n". Here, we only use + // `firstline` to find the file name. Using the system line separator is not + // problem here, it seems. + int lineSepPos = original.indexOf(System.lineSeparator()); + if (lineSepPos != -1) { + firstline = original.substring(0, lineSepPos); + } else { + firstline = original; } - /** - * Return true if this line in a Java file indicates an expected diagnostic that might be - * continued on the next line. - * - * @param originalLine the input line - * @return whether the diagnostic might be continued on the next line - */ - public static boolean isJavaDiagnosticLineStart(String originalLine) { - String trimmedLine = originalLine.trim(); - return trimmedLine.startsWith("// ::") || trimmedLine.startsWith("// warning:"); + String trimmed; + Path file; + int extensionPos = firstline.indexOf(".java:"); + if (extensionPos != -1) { + file = Paths.get(firstline.substring(0, extensionPos + 5).trim()); + trimmed = original.substring(extensionPos + 5).trim(); + } else { + // Important to use "" to make input of expected warnings easy. + // For an example, see file + // ./checker/tests/nullness-stubfile/NullnessStubfileMerge.java + file = Paths.get(""); + trimmed = original; } - /** - * Convert an end-of-line diagnostic message to a beginning-of-line one. Returns the argument - * unchanged if it does not contain an end-of-line diagnostic message. - * - *

Most diagnostics in Java files start at the beginning of a line. Occasionally, javac - * issues a warning about implicit code, such as an implicit constructor, on the line - * immediately after a curly brace. The only place to put the expected diagnostic - * message is on the line with the curly brace. - * - *

This implementation replaces "{ // ::" by "// ::", converting the end-of-line diagnostic - * message to a beginning-of-line one that the rest of the code can handle. It is rather - * specific (to avoid false positive matches, such as when "// ::" is commented out in source - * code). It could be extended in the future if such an extension is necessary. - */ - public static String handleEndOfLineJavaDiagnostic(String originalLine) { - int curlyIndex = originalLine.indexOf("{ // ::"); - if (curlyIndex == -1) { - return originalLine; - } else { - return originalLine.substring(curlyIndex + 2); - } + return IPair.of(trimmed, file); + } + + /** + * Given a category string that may be prepended with "fixable-", return the category enum that + * corresponds with the category and whether or not it is a isFixable error. + * + * @param category a category string + * @return the corresponding diagnostic kind and whether it is fixable + */ + private static IPair parseCategoryString(String category) { + String fixable = "fixable-"; + boolean isFixable = category.startsWith(fixable); + if (isFixable) { + category = category.substring(fixable.length()); } - - /** Return true if this line in a Java file continues an expected diagnostic. */ - @EnsuresNonNullIf(result = true, expression = "#1") - public static boolean isJavaDiagnosticLineContinuation(@Nullable String originalLine) { - if (originalLine == null) { - return false; - } - String trimmedLine = originalLine.trim(); - // Unlike with errors, there is no logic elsewhere for splitting multiple "warning:"s. So, - // avoid concatenating them. Also, each one must begin a line. They are allowed to wrap to - // the next line, though. - return trimmedLine.startsWith("// ") && !trimmedLine.startsWith("// warning:"); + DiagnosticKind categoryEnum = DiagnosticKind.fromParseString(category); + if (categoryEnum == null) { + throw new Error("Unparsable category: " + category); } - /** - * Return the continuation part. The argument is such that {@link - * #isJavaDiagnosticLineContinuation} returns true. - */ - public static String continuationPart(String originalLine) { - return originalLine.trim().substring(2).trim(); + return IPair.of(categoryEnum, isFixable); + } + + /** + * Return true if this line in a Java file indicates an expected diagnostic that might be + * continued on the next line. + * + * @param originalLine the input line + * @return whether the diagnostic might be continued on the next line + */ + public static boolean isJavaDiagnosticLineStart(String originalLine) { + String trimmedLine = originalLine.trim(); + return trimmedLine.startsWith("// ::") || trimmedLine.startsWith("// warning:"); + } + + /** + * Convert an end-of-line diagnostic message to a beginning-of-line one. Returns the argument + * unchanged if it does not contain an end-of-line diagnostic message. + * + *

Most diagnostics in Java files start at the beginning of a line. Occasionally, javac issues + * a warning about implicit code, such as an implicit constructor, on the line immediately + * after a curly brace. The only place to put the expected diagnostic message is on the line + * with the curly brace. + * + *

This implementation replaces "{ // ::" by "// ::", converting the end-of-line diagnostic + * message to a beginning-of-line one that the rest of the code can handle. It is rather specific + * (to avoid false positive matches, such as when "// ::" is commented out in source code). It + * could be extended in the future if such an extension is necessary. + */ + public static String handleEndOfLineJavaDiagnostic(String originalLine) { + int curlyIndex = originalLine.indexOf("{ // ::"); + if (curlyIndex == -1) { + return originalLine; + } else { + return originalLine.substring(curlyIndex + 2); } + } - /** - * Convert a line in a Java source file to a TestDiagnosticLine. - * - *

The input {@code line} is possibly the concatenation of multiple source lines, if the - * diagnostic was split across lines in the source code. - */ - public static TestDiagnosticLine fromJavaSourceLine( - String filename, String line, long lineNumber) { - String trimmedLine = line.trim(); - long errorLine = lineNumber + 1; - - if (trimmedLine.startsWith("// ::")) { - String restOfLine = trimmedLine.substring(5); // drop the "// ::" - String[] diagnosticStrs = restOfLine.split("::"); - List diagnostics = - CollectionsPlume.mapList( - (String diagnostic) -> - fromJavaFileComment(filename, errorLine, diagnostic), - diagnosticStrs); - return new TestDiagnosticLine( - filename, errorLine, line, Collections.unmodifiableList(diagnostics)); - } else if (trimmedLine.startsWith("// warning:")) { - // This special diagnostic does not expect a line number nor a file name - String diagnosticString = trimmedLine.substring(2); - TestDiagnostic diagnostic = fromJavaFileComment("", -1, diagnosticString); - return new TestDiagnosticLine("", -1, line, Collections.singletonList(diagnostic)); - } else if (trimmedLine.startsWith("//::")) { - TestDiagnostic diagnostic = - new TestDiagnostic( - Paths.get(filename), - lineNumber, - DiagnosticKind.Error, - "Use \"// ::\", not \"//::\"", - false); - return new TestDiagnosticLine( - filename, lineNumber, line, Collections.singletonList(diagnostic)); - } else { - // It's a bit gross to create empty diagnostics (returning null might be more - // efficient), but they will be filtered out later. - return new TestDiagnosticLine(filename, errorLine, line, Collections.emptyList()); - } + /** Return true if this line in a Java file continues an expected diagnostic. */ + @EnsuresNonNullIf(result = true, expression = "#1") + public static boolean isJavaDiagnosticLineContinuation(@Nullable String originalLine) { + if (originalLine == null) { + return false; } - - /** Convert a line in a DiagnosticFile to a TestDiagnosticLine. */ - public static TestDiagnosticLine fromDiagnosticFileLine(String diagnosticLine) { - String trimmedLine = diagnosticLine.trim(); - if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) { - return new TestDiagnosticLine("", -1, diagnosticLine, Collections.emptyList()); - } - - TestDiagnostic diagnostic = fromDiagnosticFileString(diagnosticLine); - return new TestDiagnosticLine( - "", diagnostic.getLineNumber(), diagnosticLine, Arrays.asList(diagnostic)); + String trimmedLine = originalLine.trim(); + // Unlike with errors, there is no logic elsewhere for splitting multiple "warning:"s. So, + // avoid concatenating them. Also, each one must begin a line. They are allowed to wrap to + // the next line, though. + return trimmedLine.startsWith("// ") && !trimmedLine.startsWith("// warning:"); + } + + /** + * Return the continuation part. The argument is such that {@link + * #isJavaDiagnosticLineContinuation} returns true. + */ + public static String continuationPart(String originalLine) { + return originalLine.trim().substring(2).trim(); + } + + /** + * Convert a line in a Java source file to a TestDiagnosticLine. + * + *

The input {@code line} is possibly the concatenation of multiple source lines, if the + * diagnostic was split across lines in the source code. + */ + public static TestDiagnosticLine fromJavaSourceLine( + String filename, String line, long lineNumber) { + String trimmedLine = line.trim(); + long errorLine = lineNumber + 1; + + if (trimmedLine.startsWith("// ::")) { + String restOfLine = trimmedLine.substring(5); // drop the "// ::" + String[] diagnosticStrs = restOfLine.split("::"); + List diagnostics = + CollectionsPlume.mapList( + (String diagnostic) -> fromJavaFileComment(filename, errorLine, diagnostic), + diagnosticStrs); + return new TestDiagnosticLine( + filename, errorLine, line, Collections.unmodifiableList(diagnostics)); + } else if (trimmedLine.startsWith("// warning:")) { + // This special diagnostic does not expect a line number nor a file name + String diagnosticString = trimmedLine.substring(2); + TestDiagnostic diagnostic = fromJavaFileComment("", -1, diagnosticString); + return new TestDiagnosticLine("", -1, line, Collections.singletonList(diagnostic)); + } else if (trimmedLine.startsWith("//::")) { + TestDiagnostic diagnostic = + new TestDiagnostic( + Paths.get(filename), + lineNumber, + DiagnosticKind.Error, + "Use \"// ::\", not \"//::\"", + false); + return new TestDiagnosticLine( + filename, lineNumber, line, Collections.singletonList(diagnostic)); + } else { + // It's a bit gross to create empty diagnostics (returning null might be more + // efficient), but they will be filtered out later. + return new TestDiagnosticLine(filename, errorLine, line, Collections.emptyList()); } + } - /** - * Convert a list of compiler diagnostics into test diagnostics. - * - * @param javaxDiagnostics the list of compiler diagnostics - * @return the corresponding test diagnostics - */ - public static Set fromJavaxToolsDiagnosticList( - List> javaxDiagnostics) { - Set diagnostics = new LinkedHashSet<>(javaxDiagnostics.size()); - - for (Diagnostic diagnostic : javaxDiagnostics) { - // See fromJavaxToolsDiagnostic as to why we use diagnostic.toString rather - // than convert from the diagnostic itself - String diagnosticString = diagnostic.toString(); - - // suppress Xlint warnings - if (diagnosticString.contains("uses unchecked or unsafe operations.") - || diagnosticString.contains("Recompile with -Xlint:unchecked for details.") - || diagnosticString.endsWith(" declares unsafe vararg methods.") - || diagnosticString.contains("Recompile with -Xlint:varargs for details.")) { - continue; - } - - diagnostics.add(fromJavaxToolsDiagnostic(diagnosticString)); - } - - return diagnostics; + /** Convert a line in a DiagnosticFile to a TestDiagnosticLine. */ + public static TestDiagnosticLine fromDiagnosticFileLine(String diagnosticLine) { + String trimmedLine = diagnosticLine.trim(); + if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) { + return new TestDiagnosticLine("", -1, diagnosticLine, Collections.emptyList()); } - /** - * Converts the given diagnostics to strings (as they would appear in a source file - * individually). - * - * @param diagnostics a list of diagnostics - * @return a list of the diagnastics as they would appear in a source file - */ - public static List diagnosticsToString(List diagnostics) { - return CollectionsPlume.mapList(TestDiagnostic::toString, diagnostics); + TestDiagnostic diagnostic = fromDiagnosticFileString(diagnosticLine); + return new TestDiagnosticLine( + "", diagnostic.getLineNumber(), diagnosticLine, Arrays.asList(diagnostic)); + } + + /** + * Convert a list of compiler diagnostics into test diagnostics. + * + * @param javaxDiagnostics the list of compiler diagnostics + * @return the corresponding test diagnostics + */ + public static Set fromJavaxToolsDiagnosticList( + List> javaxDiagnostics) { + Set diagnostics = new LinkedHashSet<>(javaxDiagnostics.size()); + + for (Diagnostic diagnostic : javaxDiagnostics) { + // See fromJavaxToolsDiagnostic as to why we use diagnostic.toString rather + // than convert from the diagnostic itself + String diagnosticString = diagnostic.toString(); + + // suppress Xlint warnings + if (diagnosticString.contains("uses unchecked or unsafe operations.") + || diagnosticString.contains("Recompile with -Xlint:unchecked for details.") + || diagnosticString.endsWith(" declares unsafe vararg methods.") + || diagnosticString.contains("Recompile with -Xlint:varargs for details.")) { + continue; + } + + diagnostics.add(fromJavaxToolsDiagnostic(diagnosticString)); } + + return diagnostics; + } + + /** + * Converts the given diagnostics to strings (as they would appear in a source file individually). + * + * @param diagnostics a list of diagnostics + * @return a list of the diagnastics as they would appear in a source file + */ + public static List diagnosticsToString(List diagnostics) { + return CollectionsPlume.mapList(TestDiagnostic::toString, diagnostics); + } } diff --git a/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java b/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java index b5b31bd34cb..eb89f02a97d 100644 --- a/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java +++ b/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java @@ -8,15 +8,12 @@ import com.sun.source.doctree.UnknownBlockTagTree; import com.sun.source.doctree.UnknownInlineTagTree; import com.sun.source.util.SimpleDocTreeVisitor; - -import jdk.javadoc.doclet.Taglet; - import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.StringJoiner; - import javax.lang.model.element.Element; +import jdk.javadoc.doclet.Taglet; /** * A taglet for processing the {@code @checker_framework.manual} javadoc block tag, which inserts @@ -33,97 +30,96 @@ */ public class ManualTaglet implements Taglet { - private static final String NAME = "checker_framework.manual"; - - @Override - public String getName() { - return NAME; + private static final String NAME = "checker_framework.manual"; + + @Override + public String getName() { + return NAME; + } + + private static final EnumSet allowedSet = EnumSet.allOf(Location.class); + + @Override + public Set getAllowedLocations() { + return allowedSet; + } + + @Override + public boolean isInlineTag() { + return false; + } + + /** + * Formats a link, given an array of tokens. + * + * @param parts the array of tokens + * @return a link to the manual top-level if the array size is one, or a link to a part of the + * manual if it's larger than one + */ + private String formatLink(String[] parts) { + String anchor, text; + if (parts.length < 2) { + anchor = ""; + text = "Checker Framework"; + } else { + anchor = parts[0]; + text = parts[1]; } - - private static final EnumSet allowedSet = EnumSet.allOf(Location.class); - - @Override - public Set getAllowedLocations() { - return allowedSet; + return String.format("%s", anchor, text); + } + + /** + * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag + * content. + * + * @param text the tag content + * @return the formatted tag + */ + private String formatHeader(String text) { + return String.format("

See the Checker Framework Manual:
%s
", text); + } + + @Override + public String toString(List tags, Element element) { + if (tags.isEmpty()) { + return ""; } - - @Override - public boolean isInlineTag() { - return false; + StringJoiner sb = new StringJoiner(", "); + for (DocTree t : tags) { + String text = getText(t); + String[] split = text.split(" ", 2); + sb.add(formatLink(split)); } - - /** - * Formats a link, given an array of tokens. - * - * @param parts the array of tokens - * @return a link to the manual top-level if the array size is one, or a link to a part of the - * manual if it's larger than one - */ - private String formatLink(String[] parts) { - String anchor, text; - if (parts.length < 2) { - anchor = ""; - text = "Checker Framework"; - } else { - anchor = parts[0]; - text = parts[1]; + return formatHeader(sb.toString()); + } + + static String getText(DocTree dt) { + return new SimpleDocTreeVisitor() { + @Override + public String visitUnknownBlockTag(UnknownBlockTagTree tree, Void p) { + for (DocTree dt : tree.getContent()) { + return dt.accept(this, null); } - return String.format( - "%s", anchor, text); - } - - /** - * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag - * content. - * - * @param text the tag content - * @return the formatted tag - */ - private String formatHeader(String text) { - return String.format("
See the Checker Framework Manual:
%s
", text); - } + return ""; + } - @Override - public String toString(List tags, Element element) { - if (tags.isEmpty()) { - return ""; + @Override + public String visitUnknownInlineTag(UnknownInlineTagTree tree, Void p) { + for (DocTree dt : tree.getContent()) { + return dt.accept(this, null); } - StringJoiner sb = new StringJoiner(", "); - for (DocTree t : tags) { - String text = getText(t); - String[] split = text.split(" ", 2); - sb.add(formatLink(split)); - } - return formatHeader(sb.toString()); - } - - static String getText(DocTree dt) { - return new SimpleDocTreeVisitor() { - @Override - public String visitUnknownBlockTag(UnknownBlockTagTree tree, Void p) { - for (DocTree dt : tree.getContent()) { - return dt.accept(this, null); - } - return ""; - } - - @Override - public String visitUnknownInlineTag(UnknownInlineTagTree tree, Void p) { - for (DocTree dt : tree.getContent()) { - return dt.accept(this, null); - } - return ""; - } - - @Override - public String visitText(TextTree tree, Void p) { - return tree.getBody(); - } - - @Override - protected String defaultAction(DocTree tree, Void p) { - return ""; - } - }.visit(dt, null); - } + return ""; + } + + @Override + public String visitText(TextTree tree, Void p) { + return tree.getBody(); + } + + @Override + protected String defaultAction(DocTree tree, Void p) { + return ""; + } + }.visit(dt, null); + } } diff --git a/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java b/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java index 726e2b1c2db..4252da48efa 100644 --- a/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java +++ b/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java @@ -5,7 +5,6 @@ import com.sun.javadoc.Tag; import com.sun.tools.doclets.Taglet; - import java.util.Map; import java.util.StringJoiner; @@ -24,104 +23,103 @@ */ public class ManualTaglet implements Taglet { - @Override - public String getName() { - return "checker_framework.manual"; - } - - @Override - public boolean inConstructor() { - return true; - } - - @Override - public boolean inField() { - return true; - } - - @Override - public boolean inMethod() { - return true; - } - - @Override - public boolean inOverview() { - return true; - } - - @Override - public boolean inPackage() { - return true; + @Override + public String getName() { + return "checker_framework.manual"; + } + + @Override + public boolean inConstructor() { + return true; + } + + @Override + public boolean inField() { + return true; + } + + @Override + public boolean inMethod() { + return true; + } + + @Override + public boolean inOverview() { + return true; + } + + @Override + public boolean inPackage() { + return true; + } + + @Override + public boolean inType() { + return true; + } + + @Override + public boolean isInlineTag() { + return false; + } + + /** + * Formats a link, given an array of tokens. + * + * @param parts the array of tokens + * @return a link to the manual top-level if the array size is one, or a link to a part of the + * manual if it's larger than one + */ + private String formatLink(String[] parts) { + String anchor, text; + if (parts.length < 2) { + anchor = ""; + text = "Checker Framework"; + } else { + anchor = parts[0]; + text = parts[1]; } - - @Override - public boolean inType() { - return true; - } - - @Override - public boolean isInlineTag() { - return false; + return String.format("%s", anchor, text); + } + + /** + * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag + * content. + * + * @param text the tag content + * @return the formatted tag + */ + private String formatHeader(String text) { + return String.format("
See the Checker Framework Manual:
%s
", text); + } + + @Override + public String toString(Tag tag) { + String[] split = tag.text().split(" ", 2); + return formatHeader(formatLink(split)); + } + + @Override + public String toString(Tag[] tags) { + if (tags.length == 0) { + return ""; } - - /** - * Formats a link, given an array of tokens. - * - * @param parts the array of tokens - * @return a link to the manual top-level if the array size is one, or a link to a part of the - * manual if it's larger than one - */ - private String formatLink(String[] parts) { - String anchor, text; - if (parts.length < 2) { - anchor = ""; - text = "Checker Framework"; - } else { - anchor = parts[0]; - text = parts[1]; - } - return String.format( - "%s", anchor, text); - } - - /** - * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag - * content. - * - * @param text the tag content - * @return the formatted tag - */ - private String formatHeader(String text) { - return String.format("
See the Checker Framework Manual:
%s
", text); + StringJoiner sb = new StringJoiner(", "); + for (Tag t : tags) { + String text = t.text(); + String[] split = text.split(" ", 2); + sb.add(formatLink(split)); } - - @Override - public String toString(Tag tag) { - String[] split = tag.text().split(" ", 2); - return formatHeader(formatLink(split)); - } - - @Override - public String toString(Tag[] tags) { - if (tags.length == 0) { - return ""; - } - StringJoiner sb = new StringJoiner(", "); - for (Tag t : tags) { - String text = t.text(); - String[] split = text.split(" ", 2); - sb.add(formatLink(split)); - } - return formatHeader(sb.toString()); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - public static void register(Map tagletMap) { - ManualTaglet tag = new ManualTaglet(); - Taglet t = (Taglet) tagletMap.get(tag.getName()); - if (t != null) { - tagletMap.remove(tag.getName()); - } - tagletMap.put(tag.getName(), tag); + return formatHeader(sb.toString()); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void register(Map tagletMap) { + ManualTaglet tag = new ManualTaglet(); + Taglet t = (Taglet) tagletMap.get(tag.getName()); + if (t != null) { + tagletMap.remove(tag.getName()); } + tagletMap.put(tag.getName(), tag); + } } diff --git a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java index 0c6fd0a7778..3907ac964eb 100644 --- a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java +++ b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java @@ -1,5 +1,7 @@ package org.checkerframework.framework.test.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestRootDirectory; @@ -9,60 +11,53 @@ import org.hamcrest.MatcherAssert; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests the explicit tests root configuration. */ @TestRootDirectory("tests-alt") public class AlternateTestRootPerDirTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AlternateTestRootPerDirTest(List testFiles) { - super(testFiles, ValueChecker.class, ""); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AlternateTestRootPerDirTest(List testFiles) { + super(testFiles, ValueChecker.class, ""); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"alt-dir-a", "alt-dir-b"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"alt-dir-a", "alt-dir-b"}; + } - @Override - public void checkResult(TypecheckResult typecheckResult) { - super.checkResult(typecheckResult); - MatcherAssert.assertThat( - "test check result has exactly one expected diagnostic", - typecheckResult.getExpectedDiagnostics().size(), - CoreMatchers.describedAs( - "singleton collection %0", - CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has the expected diagnostic of one of the test files", - typecheckResult.getExpectedDiagnostics(), - CoreMatchers.either( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125A.java", - 5, - "error: (assignment.type.incompatible)"))) - .or( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125B.java", - 5, - "error: (assignment.type.incompatible)")))); - MatcherAssert.assertThat( - "test check result has exactly zero unexpected diagnostics", - typecheckResult.getUnexpectedDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has exactly zero missing diagnostics", - typecheckResult.getMissingDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); - } + @Override + public void checkResult(TypecheckResult typecheckResult) { + super.checkResult(typecheckResult); + MatcherAssert.assertThat( + "test check result has exactly one expected diagnostic", + typecheckResult.getExpectedDiagnostics().size(), + CoreMatchers.describedAs( + "singleton collection %0", + CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has the expected diagnostic of one of the test files", + typecheckResult.getExpectedDiagnostics(), + CoreMatchers.either( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))) + .or( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125B.java", 5, "error: (assignment.type.incompatible)")))); + MatcherAssert.assertThat( + "test check result has exactly zero unexpected diagnostics", + typecheckResult.getUnexpectedDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has exactly zero missing diagnostics", + typecheckResult.getMissingDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); + } } diff --git a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java index 26c45913faa..228b1af810d 100644 --- a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java +++ b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java @@ -1,5 +1,6 @@ package org.checkerframework.framework.test.test.junit; +import java.io.File; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.checkerframework.framework.test.TestRootDirectory; @@ -9,59 +10,53 @@ import org.hamcrest.MatcherAssert; import org.junit.runners.Parameterized.Parameters; -import java.io.File; - /** Tests the explicit tests root configuration. */ @TestRootDirectory("tests-alt") public class AlternateTestRootPerFileWithDirsTest extends CheckerFrameworkPerFileTest { - /** - * @param testFile the files containing test code, which will be type-checked - */ - public AlternateTestRootPerFileWithDirsTest(File testFile) { - super(testFile, ValueChecker.class, ""); - } + /** + * @param testFile the files containing test code, which will be type-checked + */ + public AlternateTestRootPerFileWithDirsTest(File testFile) { + super(testFile, ValueChecker.class, ""); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"alt-dir-a", "alt-dir-b"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"alt-dir-a", "alt-dir-b"}; + } - @Override - public void checkResult(TypecheckResult typecheckResult) { - super.checkResult(typecheckResult); - MatcherAssert.assertThat( - "test check result has exactly one expected diagnostic", - typecheckResult.getExpectedDiagnostics().size(), - CoreMatchers.describedAs( - "singleton collection %0", - CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has the expected diagnostic of one of the test files", - typecheckResult.getExpectedDiagnostics(), - CoreMatchers.either( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125A.java", - 5, - "error: (assignment.type.incompatible)"))) - .or( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125B.java", - 5, - "error: (assignment.type.incompatible)")))); - MatcherAssert.assertThat( - "test check result has exactly zero unexpected diagnostics", - typecheckResult.getUnexpectedDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has exactly zero missing diagnostics", - typecheckResult.getMissingDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); - } + @Override + public void checkResult(TypecheckResult typecheckResult) { + super.checkResult(typecheckResult); + MatcherAssert.assertThat( + "test check result has exactly one expected diagnostic", + typecheckResult.getExpectedDiagnostics().size(), + CoreMatchers.describedAs( + "singleton collection %0", + CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has the expected diagnostic of one of the test files", + typecheckResult.getExpectedDiagnostics(), + CoreMatchers.either( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))) + .or( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125B.java", 5, "error: (assignment.type.incompatible)")))); + MatcherAssert.assertThat( + "test check result has exactly zero unexpected diagnostics", + typecheckResult.getUnexpectedDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has exactly zero missing diagnostics", + typecheckResult.getMissingDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); + } } diff --git a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java index c85dc0bcd54..ee8659ecf67 100644 --- a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java +++ b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java @@ -1,5 +1,7 @@ package org.checkerframework.framework.test.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.checkerframework.framework.test.TestRootDirectory; @@ -10,51 +12,48 @@ import org.hamcrest.MatcherAssert; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests the explicit tests root configuration. */ @TestRootDirectory("tests-alt") public class AlternateTestRootPerFileWithFilesTest extends CheckerFrameworkPerFileTest { - /** - * @param testFile the files containing test code, which will be type-checked - */ - public AlternateTestRootPerFileWithFilesTest(File testFile) { - super(testFile, ValueChecker.class, ""); - } + /** + * @param testFile the files containing test code, which will be type-checked + */ + public AlternateTestRootPerFileWithFilesTest(File testFile) { + super(testFile, ValueChecker.class, ""); + } - @Parameters - public static List getTestFiles() { - return TestUtilities.findRelativeNestedJavaFiles("tests-alt", "alt-dir-a"); - } + @Parameters + public static List getTestFiles() { + return TestUtilities.findRelativeNestedJavaFiles("tests-alt", "alt-dir-a"); + } - @Override - public void checkResult(TypecheckResult typecheckResult) { - super.checkResult(typecheckResult); - MatcherAssert.assertThat( - "test check result has exactly one expected diagnostic", - typecheckResult.getExpectedDiagnostics().size(), - CoreMatchers.describedAs( - "singleton collection %0", - CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has the expected diagnostic of the test file", - typecheckResult.getExpectedDiagnostics(), - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))); - MatcherAssert.assertThat( - "test check result has exactly zero unexpected diagnostics", - typecheckResult.getUnexpectedDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has exactly zero missing diagnostics", - typecheckResult.getMissingDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); - } + @Override + public void checkResult(TypecheckResult typecheckResult) { + super.checkResult(typecheckResult); + MatcherAssert.assertThat( + "test check result has exactly one expected diagnostic", + typecheckResult.getExpectedDiagnostics().size(), + CoreMatchers.describedAs( + "singleton collection %0", + CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has the expected diagnostic of the test file", + typecheckResult.getExpectedDiagnostics(), + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))); + MatcherAssert.assertThat( + "test check result has exactly zero unexpected diagnostics", + typecheckResult.getUnexpectedDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has exactly zero missing diagnostics", + typecheckResult.getMissingDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); + } } diff --git a/framework-test/tests-alt/alt-dir-a/Issue6125A.java b/framework-test/tests-alt/alt-dir-a/Issue6125A.java index b1df9696a06..5e063add1b0 100644 --- a/framework-test/tests-alt/alt-dir-a/Issue6125A.java +++ b/framework-test/tests-alt/alt-dir-a/Issue6125A.java @@ -1,6 +1,6 @@ import org.checkerframework.common.value.qual.StringVal; public class Issue6125A { - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "goodbye"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "goodbye"; } diff --git a/framework-test/tests-alt/alt-dir-b/Issue6125B.java b/framework-test/tests-alt/alt-dir-b/Issue6125B.java index 0a7aa611b5b..c2f3e0f2a65 100644 --- a/framework-test/tests-alt/alt-dir-b/Issue6125B.java +++ b/framework-test/tests-alt/alt-dir-b/Issue6125B.java @@ -1,6 +1,6 @@ import org.checkerframework.common.value.qual.StringVal; public class Issue6125B { - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "world"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "world"; } diff --git a/framework/jtreg/DOTCFGVisualizerForVarargsTest.java b/framework/jtreg/DOTCFGVisualizerForVarargsTest.java index 1f57315e077..845284f95bc 100644 --- a/framework/jtreg/DOTCFGVisualizerForVarargsTest.java +++ b/framework/jtreg/DOTCFGVisualizerForVarargsTest.java @@ -7,15 +7,15 @@ public class DOTCFGVisualizerForVarargsTest { - public DOTCFGVisualizerForVarargsTest(Object... objs) {} + public DOTCFGVisualizerForVarargsTest(Object... objs) {} - public static void method(Object... objs) {} + public static void method(Object... objs) {} - public void call() { - new DOTCFGVisualizerForVarargsTest(); - new DOTCFGVisualizerForVarargsTest(1, 2); + public void call() { + new DOTCFGVisualizerForVarargsTest(); + new DOTCFGVisualizerForVarargsTest(1, 2); - method(); - method("", null); - } + method(); + method("", null); + } } diff --git a/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java b/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java index 230348c6a09..01c980edc22 100644 --- a/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java +++ b/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java @@ -9,44 +9,43 @@ import static java.util.concurrent.TimeUnit.*; -import org.checkerframework.common.util.report.qual.*; - import java.util.concurrent.TimeUnit; +import org.checkerframework.common.util.report.qual.*; public class AnnotationFileParserEnumTest { - @SuppressWarnings("report") - enum MyTimeUnit { - NANOSECONDS, - MICROSECONDS, - @ReportReadWrite - MILLISECONDS, - @ReportReadWrite - SECONDS; - - @ReportCall - long toMicros(long d) { - return d; - } - } - - void readFromEnumInSource() { - MyTimeUnit u1 = MyTimeUnit.SECONDS; - MyTimeUnit u2 = MyTimeUnit.MILLISECONDS; - MyTimeUnit u3 = MyTimeUnit.MICROSECONDS; - MyTimeUnit u4 = MyTimeUnit.NANOSECONDS; - long sUS = MyTimeUnit.SECONDS.toMicros(10); - } - - void readFromEnumInStub() { - TimeUnit u1 = TimeUnit.SECONDS; - TimeUnit u2 = MILLISECONDS; - TimeUnit u3 = TimeUnit.MICROSECONDS; - TimeUnit u4 = NANOSECONDS; - long sUS = TimeUnit.SECONDS.toMicros(10); - long sNS = SECONDS.toNanos(10); - long msMS = TimeUnit.MILLISECONDS.toMillis(10); - long msUS = TimeUnit.MILLISECONDS.toMicros(10); - long msNS = MILLISECONDS.toNanos(10); + @SuppressWarnings("report") + enum MyTimeUnit { + NANOSECONDS, + MICROSECONDS, + @ReportReadWrite + MILLISECONDS, + @ReportReadWrite + SECONDS; + + @ReportCall + long toMicros(long d) { + return d; } + } + + void readFromEnumInSource() { + MyTimeUnit u1 = MyTimeUnit.SECONDS; + MyTimeUnit u2 = MyTimeUnit.MILLISECONDS; + MyTimeUnit u3 = MyTimeUnit.MICROSECONDS; + MyTimeUnit u4 = MyTimeUnit.NANOSECONDS; + long sUS = MyTimeUnit.SECONDS.toMicros(10); + } + + void readFromEnumInStub() { + TimeUnit u1 = TimeUnit.SECONDS; + TimeUnit u2 = MILLISECONDS; + TimeUnit u3 = TimeUnit.MICROSECONDS; + TimeUnit u4 = NANOSECONDS; + long sUS = TimeUnit.SECONDS.toMicros(10); + long sNS = SECONDS.toNanos(10); + long msMS = TimeUnit.MILLISECONDS.toMillis(10); + long msUS = TimeUnit.MILLISECONDS.toMicros(10); + long msNS = MILLISECONDS.toNanos(10); + } } diff --git a/framework/jtreg/issue845/checker/qual/NotInPackageTop.java b/framework/jtreg/issue845/checker/qual/NotInPackageTop.java index 7b4f8a9eb0c..54254994a65 100644 --- a/framework/jtreg/issue845/checker/qual/NotInPackageTop.java +++ b/framework/jtreg/issue845/checker/qual/NotInPackageTop.java @@ -1,10 +1,9 @@ package qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) diff --git a/framework/jtreg/variablenamedefault/lib/Test.java b/framework/jtreg/variablenamedefault/lib/Test.java index a676fe9bc37..68143680a5f 100644 --- a/framework/jtreg/variablenamedefault/lib/Test.java +++ b/framework/jtreg/variablenamedefault/lib/Test.java @@ -1,5 +1,5 @@ package lib; public class Test { - public static void method(int middle, int notmiddle) {} + public static void method(int middle, int notmiddle) {} } diff --git a/framework/jtreg/variablenamedefault/use/UseTest.java b/framework/jtreg/variablenamedefault/use/UseTest.java index c3e0eecf43a..1a2fb727896 100644 --- a/framework/jtreg/variablenamedefault/use/UseTest.java +++ b/framework/jtreg/variablenamedefault/use/UseTest.java @@ -1,11 +1,10 @@ package use; import lib.Test; - import org.checkerframework.framework.testchecker.variablenamedefault.quals.*; public class UseTest { - void testParameters(@VariableNameDefaultTop int t) { - Test.method(t, t); - } + void testParameters(@VariableNameDefaultTop int t) { + Test.method(t, t); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java index daa1705630a..90df4549afe 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java @@ -1,46 +1,45 @@ package org.checkerframework.common.accumulation; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.type.TypeMirror; - /** * This class only contains boilerplate code to permit AccumulationValue's accumulatedValues * functionality to interact with the rest of an accumulation type system. */ public class AccumulationAnalysis - extends CFAbstractAnalysis { + extends CFAbstractAnalysis { - /** - * Constructs an AccumulationAnalysis. - * - * @param checker the checker - * @param factory the type factory - */ - public AccumulationAnalysis(BaseTypeChecker checker, AccumulationAnnotatedTypeFactory factory) { - super(checker, factory); - } + /** + * Constructs an AccumulationAnalysis. + * + * @param checker the checker + * @param factory the type factory + */ + public AccumulationAnalysis(BaseTypeChecker checker, AccumulationAnnotatedTypeFactory factory) { + super(checker, factory); + } - @Override - public AccumulationStore createEmptyStore(boolean sequentialSemantics) { - return new AccumulationStore(this, sequentialSemantics); - } + @Override + public AccumulationStore createEmptyStore(boolean sequentialSemantics) { + return new AccumulationStore(this, sequentialSemantics); + } - @Override - public AccumulationStore createCopiedStore(AccumulationStore accumulationStore) { - return new AccumulationStore(accumulationStore); - } + @Override + public AccumulationStore createCopiedStore(AccumulationStore accumulationStore) { + return new AccumulationStore(accumulationStore); + } - @Override - public @Nullable AccumulationValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; - } - return new AccumulationValue(this, annotations, underlyingType); + @Override + public @Nullable AccumulationValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; } + return new AccumulationValue(this, annotations, underlyingType); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java index d1bd9234816..90c24749222 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java @@ -8,7 +8,16 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.accumulation.AccumulationChecker.AliasAnalysis; @@ -31,18 +40,6 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** * An annotated type factory for an accumulation checker. * @@ -51,666 +48,655 @@ * #postInit()}. */ public abstract class AccumulationAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory< - AccumulationValue, AccumulationStore, AccumulationTransfer, AccumulationAnalysis> { - - /** The typechecker associated with this factory. */ - public final AccumulationChecker accumulationChecker; - - /** - * The canonical top annotation for this accumulation checker: an instance of the accumulator - * annotation with no arguments. - */ - public final AnnotationMirror top; - - /** The canonical bottom annotation for this accumulation checker. */ - public final AnnotationMirror bottom; - - /** - * The annotation that accumulates things in this accumulation checker. Must be an annotation - * with exactly one field named "value" whose type is a String array. - */ - private final Class accumulator; - - /** - * The predicate annotation for this accumulation analysis, or null if predicates are not - * supported. A predicate annotation must have a single element named "value" of type String. - */ - private final @MonotonicNonNull Class predicate; - - /** - * Create an annotated type factory for an accumulation checker. - * - * @param checker the checker - * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single - * argument named "value" whose type is a String array. - * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code - * accumulator}. The bottom type should be an annotation with no arguments. - * @param predicate the predicate annotation. Either null (if predicates are not supported), or - * an annotation with a single element named "value" whose type is a String. - */ - protected AccumulationAnnotatedTypeFactory( - BaseTypeChecker checker, - Class accumulator, - Class bottom, - @Nullable Class predicate) { - super(checker); - if (!(checker instanceof AccumulationChecker)) { - throw new TypeSystemError( - "AccumulationAnnotatedTypeFactory cannot be used with a checker " - + "class that is not a subtype of AccumulationChecker. Found class: " - + checker.getClass()); - } - this.accumulationChecker = (AccumulationChecker) checker; - - this.accumulator = accumulator; - // Check that the requirements of the accumulator are met. - Method[] accDeclaredMethods = accumulator.getDeclaredMethods(); - if (accDeclaredMethods.length != 1) { - rejectMalformedAccumulator("have exactly one element"); - } - - Method accValue = accDeclaredMethods[0]; - if (accValue.getName() != "value") { // interned - rejectMalformedAccumulator("name its element \"value\""); - } - if (!accValue.getReturnType().isInstance(new String[0])) { - rejectMalformedAccumulator("have an element of type String[]"); - } - if (accValue.getDefaultValue() == null - || ((String[]) accValue.getDefaultValue()).length != 0) { - rejectMalformedAccumulator("have the empty String array {} as its default value"); - } - - this.predicate = predicate; - // If there is a predicate annotation, check that its requirements are met. - if (predicate != null) { - Method[] predDeclaredMethods = predicate.getDeclaredMethods(); - if (predDeclaredMethods.length != 1) { - rejectMalformedPredicate("have exactly one element"); - } - Method predValue = predDeclaredMethods[0]; - if (predValue.getName() != "value") { // interned - rejectMalformedPredicate("name its element \"value\""); - } - if (!predValue.getReturnType().isInstance("")) { - rejectMalformedPredicate("have an element of type String"); - } - } - - this.bottom = AnnotationBuilder.fromClass(elements, bottom); - this.top = createAccumulatorAnnotation(Collections.emptyList()); - - // Every subclass must call postInit! This does not do so. + extends GenericAnnotatedTypeFactory< + AccumulationValue, AccumulationStore, AccumulationTransfer, AccumulationAnalysis> { + + /** The typechecker associated with this factory. */ + public final AccumulationChecker accumulationChecker; + + /** + * The canonical top annotation for this accumulation checker: an instance of the accumulator + * annotation with no arguments. + */ + public final AnnotationMirror top; + + /** The canonical bottom annotation for this accumulation checker. */ + public final AnnotationMirror bottom; + + /** + * The annotation that accumulates things in this accumulation checker. Must be an annotation with + * exactly one field named "value" whose type is a String array. + */ + private final Class accumulator; + + /** + * The predicate annotation for this accumulation analysis, or null if predicates are not + * supported. A predicate annotation must have a single element named "value" of type String. + */ + private final @MonotonicNonNull Class predicate; + + /** + * Create an annotated type factory for an accumulation checker. + * + * @param checker the checker + * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single + * argument named "value" whose type is a String array. + * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code accumulator}. + * The bottom type should be an annotation with no arguments. + * @param predicate the predicate annotation. Either null (if predicates are not supported), or an + * annotation with a single element named "value" whose type is a String. + */ + protected AccumulationAnnotatedTypeFactory( + BaseTypeChecker checker, + Class accumulator, + Class bottom, + @Nullable Class predicate) { + super(checker); + if (!(checker instanceof AccumulationChecker)) { + throw new TypeSystemError( + "AccumulationAnnotatedTypeFactory cannot be used with a checker " + + "class that is not a subtype of AccumulationChecker. Found class: " + + checker.getClass()); } + this.accumulationChecker = (AccumulationChecker) checker; - /** - * Create an annotated type factory for an accumulation checker. - * - * @param checker the checker - * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single - * argument named "value" whose type is a String array. - * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code - * accumulator}. The bottom type should be an annotation with no arguments. - */ - protected AccumulationAnnotatedTypeFactory( - BaseTypeChecker checker, - Class accumulator, - Class bottom) { - this(checker, accumulator, bottom, null); + this.accumulator = accumulator; + // Check that the requirements of the accumulator are met. + Method[] accDeclaredMethods = accumulator.getDeclaredMethods(); + if (accDeclaredMethods.length != 1) { + rejectMalformedAccumulator("have exactly one element"); } - /** - * Common error message for malformed accumulator annotation. - * - * @param missing what is missing from the accumulator, suitable for use in this string to - * replace $MISSING$: "The accumulator annotation Foo must $MISSING$." - */ - private void rejectMalformedAccumulator(String missing) { - rejectMalformedAnno("accumulator", accumulator, missing); + Method accValue = accDeclaredMethods[0]; + if (accValue.getName() != "value") { // interned + rejectMalformedAccumulator("name its element \"value\""); } - - /** - * Common error message for malformed predicate annotation. - * - * @param missing what is missing from the predicate, suitable for use in this string to replace - * $MISSING$: "The predicate annotation Foo must $MISSING$." - */ - private void rejectMalformedPredicate(String missing) { - rejectMalformedAnno("predicate", predicate, missing); + if (!accValue.getReturnType().isInstance(new String[0])) { + rejectMalformedAccumulator("have an element of type String[]"); } - - /** - * Common error message implementation. Call rejectMalformedAccumulator or - * rejectMalformedPredicate as appropriate, rather than this method directly. - * - * @param annoTypeName the display name for the type of malformed annotation, such as - * "accumulator" - * @param anno the malformed annotation - * @param missing what is missing from the annotation, suitable for use in this string to - * replace $MISSING$: "The accumulator annotation Foo must $MISSING$." - */ - private void rejectMalformedAnno( - String annoTypeName, Class anno, String missing) { - throw new BugInCF("The " + annoTypeName + " annotation " + anno + " must " + missing + "."); + if (accValue.getDefaultValue() == null || ((String[]) accValue.getDefaultValue()).length != 0) { + rejectMalformedAccumulator("have the empty String array {} as its default value"); } - /** - * Creates a new instance of the accumulator annotation that contains the elements of {@code - * values}. - * - * @param values the arguments to the annotation. The values can contain duplicates and can be - * in any order. - * @return an annotation mirror representing the accumulator annotation with {@code values}'s - * arguments; this is top if {@code values} is empty - */ - public AnnotationMirror createAccumulatorAnnotation(List values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); - builder.setValue("value", CollectionsPlume.withoutDuplicatesSorted(values)); - return builder.build(); + this.predicate = predicate; + // If there is a predicate annotation, check that its requirements are met. + if (predicate != null) { + Method[] predDeclaredMethods = predicate.getDeclaredMethods(); + if (predDeclaredMethods.length != 1) { + rejectMalformedPredicate("have exactly one element"); + } + Method predValue = predDeclaredMethods[0]; + if (predValue.getName() != "value") { // interned + rejectMalformedPredicate("name its element \"value\""); + } + if (!predValue.getReturnType().isInstance("")) { + rejectMalformedPredicate("have an element of type String"); + } } - /** - * Creates a new instance of the accumulator annotation that contains exactly one value. - * - * @param value the argument to the annotation - * @return an annotation mirror representing the accumulator annotation with {@code value} as - * its argument - */ - public AnnotationMirror createAccumulatorAnnotation(String value) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); - builder.setValue("value", Collections.singletonList(value)); - return builder.build(); + this.bottom = AnnotationBuilder.fromClass(elements, bottom); + this.top = createAccumulatorAnnotation(Collections.emptyList()); + + // Every subclass must call postInit! This does not do so. + } + + /** + * Create an annotated type factory for an accumulation checker. + * + * @param checker the checker + * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single + * argument named "value" whose type is a String array. + * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code accumulator}. + * The bottom type should be an annotation with no arguments. + */ + protected AccumulationAnnotatedTypeFactory( + BaseTypeChecker checker, + Class accumulator, + Class bottom) { + this(checker, accumulator, bottom, null); + } + + /** + * Common error message for malformed accumulator annotation. + * + * @param missing what is missing from the accumulator, suitable for use in this string to replace + * $MISSING$: "The accumulator annotation Foo must $MISSING$." + */ + private void rejectMalformedAccumulator(String missing) { + rejectMalformedAnno("accumulator", accumulator, missing); + } + + /** + * Common error message for malformed predicate annotation. + * + * @param missing what is missing from the predicate, suitable for use in this string to replace + * $MISSING$: "The predicate annotation Foo must $MISSING$." + */ + private void rejectMalformedPredicate(String missing) { + rejectMalformedAnno("predicate", predicate, missing); + } + + /** + * Common error message implementation. Call rejectMalformedAccumulator or + * rejectMalformedPredicate as appropriate, rather than this method directly. + * + * @param annoTypeName the display name for the type of malformed annotation, such as + * "accumulator" + * @param anno the malformed annotation + * @param missing what is missing from the annotation, suitable for use in this string to replace + * $MISSING$: "The accumulator annotation Foo must $MISSING$." + */ + private void rejectMalformedAnno( + String annoTypeName, Class anno, String missing) { + throw new BugInCF("The " + annoTypeName + " annotation " + anno + " must " + missing + "."); + } + + /** + * Creates a new instance of the accumulator annotation that contains the elements of {@code + * values}. + * + * @param values the arguments to the annotation. The values can contain duplicates and can be in + * any order. + * @return an annotation mirror representing the accumulator annotation with {@code values}'s + * arguments; this is top if {@code values} is empty + */ + public AnnotationMirror createAccumulatorAnnotation(List values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); + builder.setValue("value", CollectionsPlume.withoutDuplicatesSorted(values)); + return builder.build(); + } + + /** + * Creates a new instance of the accumulator annotation that contains exactly one value. + * + * @param value the argument to the annotation + * @return an annotation mirror representing the accumulator annotation with {@code value} as its + * argument + */ + public AnnotationMirror createAccumulatorAnnotation(String value) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); + builder.setValue("value", Collections.singletonList(value)); + return builder.build(); + } + + /** + * Returns true if the return type of the given method invocation tree has an @This annotation + * from the Returns Receiver Checker. + * + * @param tree a method invocation tree + * @return true if the method being invoked returns its receiver + */ + public boolean returnsThis(MethodInvocationTree tree) { + if (!accumulationChecker.isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { + return false; } + // Must call `getTypeFactoryOfSubchecker` each time, not store and reuse. + ReturnsReceiverAnnotatedTypeFactory rrATF = + getTypeFactoryOfSubchecker(ReturnsReceiverChecker.class); + ExecutableElement methodEle = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType methodAtm = rrATF.getAnnotatedType(methodEle); + AnnotatedTypeMirror rrType = methodAtm.getReturnType(); + return rrType != null && rrType.hasAnnotation(This.class); + } + + /** + * Is the given annotation an accumulator annotation? Returns false if the argument is {@link + * #bottom}. + * + * @param anm an annotation mirror + * @return true if the annotation mirror is an instance of this factory's accumulator annotation + */ + public boolean isAccumulatorAnnotation(AnnotationMirror anm) { + return areSameByClass(anm, accumulator); + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new AccumulationTreeAnnotator(this)); + } + + /** + * This tree annotator implements the following rule(s): + * + *
+ *
RRA + *
If a method returns its receiver, and the receiver has an accumulation type, then the + * default type of the method's return value is the type of the receiver. + *
+ */ + protected class AccumulationTreeAnnotator extends TreeAnnotator { /** - * Returns true if the return type of the given method invocation tree has an @This annotation - * from the Returns Receiver Checker. + * Creates an instance of this tree annotator for the given type factory. * - * @param tree a method invocation tree - * @return true if the method being invoked returns its receiver + * @param factory the type factory */ - public boolean returnsThis(MethodInvocationTree tree) { - if (!accumulationChecker.isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { - return false; - } - // Must call `getTypeFactoryOfSubchecker` each time, not store and reuse. - ReturnsReceiverAnnotatedTypeFactory rrATF = - getTypeFactoryOfSubchecker(ReturnsReceiverChecker.class); - ExecutableElement methodEle = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType methodAtm = rrATF.getAnnotatedType(methodEle); - AnnotatedTypeMirror rrType = methodAtm.getReturnType(); - return rrType != null && rrType.hasAnnotation(This.class); + public AccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { + super(factory); } /** - * Is the given annotation an accumulator annotation? Returns false if the argument is {@link - * #bottom}. + * Implements rule RRA. * - * @param anm an annotation mirror - * @return true if the annotation mirror is an instance of this factory's accumulator annotation - */ - public boolean isAccumulatorAnnotation(AnnotationMirror anm) { - return areSameByClass(anm, accumulator); - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new AccumulationTreeAnnotator(this)); - } - - /** - * This tree annotator implements the following rule(s): + *

This implementation propagates types from the receiver to the return value, without + * change. Subclasses may override this method to also add additional properties, as + * appropriate. * - *

- *
RRA - *
If a method returns its receiver, and the receiver has an accumulation type, then the - * default type of the method's return value is the type of the receiver. - *
+ * @param tree a method invocation tree + * @param type the type of {@code tree} (i.e. the return type of the invoked method). Is + * (possibly) side-effected by this method. + * @return nothing, works by side-effect on {@code type} */ - protected class AccumulationTreeAnnotator extends TreeAnnotator { - - /** - * Creates an instance of this tree annotator for the given type factory. - * - * @param factory the type factory - */ - public AccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { - super(factory); - } - - /** - * Implements rule RRA. - * - *

This implementation propagates types from the receiver to the return value, without - * change. Subclasses may override this method to also add additional properties, as - * appropriate. - * - * @param tree a method invocation tree - * @param type the type of {@code tree} (i.e. the return type of the invoked method). Is - * (possibly) side-effected by this method. - * @return nothing, works by side-effect on {@code type} - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (returnsThis(tree)) { - // There is a @This annotation on the return type of the invoked method. - ExpressionTree receiverTree = TreeUtils.getReceiverTree(tree.getMethodSelect()); - AnnotationMirror returnAnno = type.getAnnotationInHierarchy(top); - AnnotationMirror glbAnno; - if (receiverTree == null) { - glbAnno = returnAnno; - } else { - AnnotatedTypeMirror receiverType = getAnnotatedType(receiverTree); - // The current type of the receiver, or top if none exists. - AnnotationMirror receiverAnno = receiverType.getAnnotationInHierarchy(top); - glbAnno = - qualHierarchy.greatestLowerBoundShallow( - returnAnno, - type.getUnderlyingType(), - receiverAnno, - receiverType.getUnderlyingType()); - } - - type.replaceAnnotation(glbAnno); - } - return super.visitMethodInvocation(tree, type); - } - } - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new AccumulationQualifierHierarchy(this.getSupportedTypeQualifiers(), this.elements); - } - - /** - * Returns all the values that anno has accumulated. - * - * @param anno an accumulator annotation; must not be bottom - * @return the list of values the annotation has accumulated; it is a new list, so it is safe - * for clients to side-effect - */ - public List getAccumulatedValues(AnnotationMirror anno) { - if (!isAccumulatorAnnotation(anno)) { - throw new BugInCF(anno + " isn't an accumulator annotation"); - } - List values = - AnnotationUtils.getElementValueArrayOrNull(anno, "value", String.class, false); - if (values == null) { - return Collections.emptyList(); + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (returnsThis(tree)) { + // There is a @This annotation on the return type of the invoked method. + ExpressionTree receiverTree = TreeUtils.getReceiverTree(tree.getMethodSelect()); + AnnotationMirror returnAnno = type.getAnnotationInHierarchy(top); + AnnotationMirror glbAnno; + if (receiverTree == null) { + glbAnno = returnAnno; } else { - return values; + AnnotatedTypeMirror receiverType = getAnnotatedType(receiverTree); + // The current type of the receiver, or top if none exists. + AnnotationMirror receiverAnno = receiverType.getAnnotationInHierarchy(top); + glbAnno = + qualHierarchy.greatestLowerBoundShallow( + returnAnno, + type.getUnderlyingType(), + receiverAnno, + receiverType.getUnderlyingType()); } - } - /** - * Returns the accumulated values on the given (expression, usually) tree. This differs from - * calling {@link #getAnnotatedType(Tree)}, because this version takes into account accumulated - * methods that are stored on the value. This is useful when dealing with accumulated facts on - * variables whose types are type variables (because type variable types cannot be refined - * directly, due to the quirks of subtyping between type variables and its interactions with the - * qualified type system). - * - *

The returned collection may be either a list or a set. - * - * @param tree a tree - * @return the accumulated values for the given tree, including those stored on the value - */ - public Collection getAccumulatedValues(Tree tree) { - AnnotatedTypeMirror type = getAnnotatedType(tree); - AnnotationMirror anno = type.getAnnotationInHierarchy(top); - if (anno != null && isAccumulatorAnnotation(anno)) { - return getAccumulatedValues(anno); - } else if (anno == null) { - // Handle type variables and wildcards. - AccumulationValue inferredValue = getInferredValueFor(tree); - if (inferredValue != null) { - Set accumulatedValues = inferredValue.getAccumulatedValues(); - if (accumulatedValues != null) { - return accumulatedValues; - } - } - } - return Collections.emptyList(); + type.replaceAnnotation(glbAnno); + } + return super.visitMethodInvocation(tree, type); } - - /** - * All accumulation analyses share a similar type hierarchy. This class implements the - * subtyping, LUB, and GLB for that hierarchy. The lattice looks like: - * - *

-     *       acc()
-     *      /   \
-     * acc(x)   acc(y) ...
-     *      \   /
-     *     acc(x,y) ...
-     *        |
-     *      bottom
-     * 
- * - * Predicate subtyping is defined as follows: - * - *
    - *
  • An accumulator is a subtype of a predicate if substitution from the accumulator to the - * predicate makes the predicate true. For example, {@code Acc(A)} is a subtype of {@code - * AccPred("A || B")}, because when A is replaced with {@code true} and B is replaced with - * {@code false}, the resulting boolean formula evaluates to true. - *
  • A predicate P is a subtype of an accumulator iff after converting the accumulator into - * a predicate representing the conjunction of its elements, P is a subtype of that - * predicate according to the rule for subtyping between two predicates defined below. - *
  • A predicate P is a subtype of another predicate Q iff P and Q are equal. An extension - * point ({@link #isPredicateSubtype(String, String)}) is provided to allow more complex - * subtyping behavior between predicates. (The "correct" subtyping rule is that P is a - * subtype of Q iff P implies Q. That rule would require an SMT solver in the general - * case, which is undesirable because it would require an external dependency. A user can - * override {@link #isPredicateSubtype(String, String)} if they require more precise - * subtyping; the check described here is overly conservative (and therefore sound), but - * not very precise.) - *
- */ - protected class AccumulationQualifierHierarchy extends ElementQualifierHierarchy { - - /** - * Creates a ElementQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - protected AccumulationQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, AccumulationAnnotatedTypeFactory.this); - } - - /** - * GLB in this type system is set union of the arguments of the two annotations, unless one - * of them is bottom, in which case the result is also bottom. - */ - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, bottom) || AnnotationUtils.areSame(a2, bottom)) { - return bottom; - } - - if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { - return a1; - } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { - return bottom; - } - - // If either is a predicate, then both should be converted to predicates and and-ed. - if (isPredicate(a1) || isPredicate(a2)) { - String a1Pred = convertToPredicate(a1); - String a2Pred = convertToPredicate(a2); - // check for top - if (a1Pred.isEmpty()) { - return a2; - } else if (a2Pred.isEmpty()) { - return a1; - } else { - return createPredicateAnnotation("(" + a1Pred + ") && (" + a2Pred + ")"); - } - } - - List a1Val = getAccumulatedValues(a1); - List a2Val = getAccumulatedValues(a2); - // Avoid creating new annotation objects in the common case. - if (a1Val.containsAll(a2Val)) { - return a1; - } - if (a2Val.containsAll(a1Val)) { - return a2; - } - a1Val.addAll(a2Val); // union - return createAccumulatorAnnotation(a1Val); - } - - /** - * LUB in this type system is set intersection of the arguments of the two annotations, - * unless one of them is bottom, in which case the result is the other annotation. - */ - @Override - public AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, bottom)) { - return a2; - } else if (AnnotationUtils.areSame(a2, bottom)) { - return a1; - } - - if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { - return a1; - } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { - return top; - } - - // If either is a predicate, then both should be converted to predicates and or-ed. - if (isPredicate(a1) || isPredicate(a2)) { - String a1Pred = convertToPredicate(a1); - String a2Pred = convertToPredicate(a2); - // check for top - if (a1Pred.isEmpty()) { - return a1; - } else if (a2Pred.isEmpty()) { - return a2; - } else { - return createPredicateAnnotation("(" + a1Pred + ") || (" + a2Pred + ")"); - } - } - - List a1Val = getAccumulatedValues(a1); - List a2Val = getAccumulatedValues(a2); - // Avoid creating new annotation objects in the common case. - if (a1Val.containsAll(a2Val)) { - return a2; - } - if (a2Val.containsAll(a1Val)) { - return a1; - } - a1Val.retainAll(a2Val); // intersection - return createAccumulatorAnnotation(a1Val); - } - - /** - * {@inheritDoc} - * - *

isSubtype in this type system is subset. - */ - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (AnnotationUtils.areSame(subAnno, bottom)) { - return true; - } else if (AnnotationUtils.areSame(superAnno, bottom)) { - return false; - } - - if (isPolymorphicQualifier(subAnno)) { - if (isPolymorphicQualifier(superAnno)) { - return true; - } else { - // Use this slightly more expensive conversion here because this is a rare code - // path and it's simpler to read than checking for both predicate and - // non-predicate forms of top. - return "".equals(convertToPredicate(superAnno)); - } - } else if (isPolymorphicQualifier(superAnno)) { - // Polymorphic annotations are only a supertype of other polymorphic annotations and - // the bottom type, both of which have already been checked above. - return false; - } - - if (isPredicate(subAnno)) { - return isPredicateSubtype( - convertToPredicate(subAnno), convertToPredicate(superAnno)); - } else if (isPredicate(superAnno)) { - return evaluatePredicate(subAnno, convertToPredicate(superAnno)); - } - - List subVal = getAccumulatedValues(subAnno); - List superVal = getAccumulatedValues(superAnno); - return subVal.containsAll(superVal); - } + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new AccumulationQualifierHierarchy(this.getSupportedTypeQualifiers(), this.elements); + } + + /** + * Returns all the values that anno has accumulated. + * + * @param anno an accumulator annotation; must not be bottom + * @return the list of values the annotation has accumulated; it is a new list, so it is safe for + * clients to side-effect + */ + public List getAccumulatedValues(AnnotationMirror anno) { + if (!isAccumulatorAnnotation(anno)) { + throw new BugInCF(anno + " isn't an accumulator annotation"); } - - /** - * Extension point for subtyping behavior between predicates. This implementation conservatively - * returns true only if the predicates are equal, or if the prospective supertype (q) is - * equivalent to top (that is, the empty string). - * - * @param p a predicate - * @param q another predicate - * @return true if p is a subtype of q - */ - protected boolean isPredicateSubtype(String p, String q) { - return "".equals(q) || p.equals(q); + List values = + AnnotationUtils.getElementValueArrayOrNull(anno, "value", String.class, false); + if (values == null) { + return Collections.emptyList(); + } else { + return values; } - - /** - * Evaluates whether the accumulator annotation {@code subAnno} makes the predicate {@code pred} - * true. - * - * @param subAnno an accumulator annotation - * @param pred a predicate - * @return whether the accumulator annotation satisfies the predicate - */ - protected boolean evaluatePredicate(AnnotationMirror subAnno, String pred) { - if (!isAccumulatorAnnotation(subAnno)) { - throw new BugInCF( - "tried to evaluate a predicate using an annotation that wasn't an accumulator: " - + subAnno); + } + + /** + * Returns the accumulated values on the given (expression, usually) tree. This differs from + * calling {@link #getAnnotatedType(Tree)}, because this version takes into account accumulated + * methods that are stored on the value. This is useful when dealing with accumulated facts on + * variables whose types are type variables (because type variable types cannot be refined + * directly, due to the quirks of subtyping between type variables and its interactions with the + * qualified type system). + * + *

The returned collection may be either a list or a set. + * + * @param tree a tree + * @return the accumulated values for the given tree, including those stored on the value + */ + public Collection getAccumulatedValues(Tree tree) { + AnnotatedTypeMirror type = getAnnotatedType(tree); + AnnotationMirror anno = type.getAnnotationInHierarchy(top); + if (anno != null && isAccumulatorAnnotation(anno)) { + return getAccumulatedValues(anno); + } else if (anno == null) { + // Handle type variables and wildcards. + AccumulationValue inferredValue = getInferredValueFor(tree); + if (inferredValue != null) { + Set accumulatedValues = inferredValue.getAccumulatedValues(); + if (accumulatedValues != null) { + return accumulatedValues; } - List trueVariables = getAccumulatedValues(subAnno); - return evaluatePredicate(trueVariables, pred); + } } + return Collections.emptyList(); + } + + /** + * All accumulation analyses share a similar type hierarchy. This class implements the subtyping, + * LUB, and GLB for that hierarchy. The lattice looks like: + * + *

+   *       acc()
+   *      /   \
+   * acc(x)   acc(y) ...
+   *      \   /
+   *     acc(x,y) ...
+   *        |
+   *      bottom
+   * 
+ * + * Predicate subtyping is defined as follows: + * + *
    + *
  • An accumulator is a subtype of a predicate if substitution from the accumulator to the + * predicate makes the predicate true. For example, {@code Acc(A)} is a subtype of {@code + * AccPred("A || B")}, because when A is replaced with {@code true} and B is replaced with + * {@code false}, the resulting boolean formula evaluates to true. + *
  • A predicate P is a subtype of an accumulator iff after converting the accumulator into a + * predicate representing the conjunction of its elements, P is a subtype of that predicate + * according to the rule for subtyping between two predicates defined below. + *
  • A predicate P is a subtype of another predicate Q iff P and Q are equal. An extension + * point ({@link #isPredicateSubtype(String, String)}) is provided to allow more complex + * subtyping behavior between predicates. (The "correct" subtyping rule is that P is a + * subtype of Q iff P implies Q. That rule would require an SMT solver in the general case, + * which is undesirable because it would require an external dependency. A user can override + * {@link #isPredicateSubtype(String, String)} if they require more precise subtyping; the + * check described here is overly conservative (and therefore sound), but not very precise.) + *
+ */ + protected class AccumulationQualifierHierarchy extends ElementQualifierHierarchy { /** - * Checks that the given annotation either: - * - *
    - *
  • does not contain a predicate, or - *
  • contains a parse-able predicate - *
- * - * Used by the visitor to throw "predicate.invalid" errors; thus must be package-private. + * Creates a ElementQualifierHierarchy from the given classes. * - * @param anm any annotation supported by this checker - * @return null if there is nothing wrong with the annotation, or an error message indicating - * the problem if it has an invalid predicate + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils */ - /*package-private*/ @Nullable String isValidPredicate(AnnotationMirror anm) { - String pred = convertToPredicate(anm); - try { - evaluatePredicate(Collections.emptyList(), pred); - } catch (UserError ue) { - return ue.getLocalizedMessage(); - } - return null; + protected AccumulationQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, AccumulationAnnotatedTypeFactory.this); } /** - * Evaluates whether treating the variables in {@code trueVariables} as {@code true} literals - * (and all other names as {@code false} literals) makes the predicate {@code pred} evaluate to - * true. - * - * @param trueVariables a list of names that should be replaced with {@code true} - * @param pred a predicate - * @return whether the true variables satisfy the predicate + * GLB in this type system is set union of the arguments of the two annotations, unless one of + * them is bottom, in which case the result is also bottom. */ - protected boolean evaluatePredicate(List trueVariables, String pred) { - Expression expression; - try { - expression = StaticJavaParser.parseExpression(pred); - } catch (ParseProblemException p) { - throw new UserError("unparsable predicate: " + pred + ". Parse exception: " + p); + @Override + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, bottom) || AnnotationUtils.areSame(a2, bottom)) { + return bottom; + } + + if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { + return a1; + } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { + return bottom; + } + + // If either is a predicate, then both should be converted to predicates and and-ed. + if (isPredicate(a1) || isPredicate(a2)) { + String a1Pred = convertToPredicate(a1); + String a2Pred = convertToPredicate(a2); + // check for top + if (a1Pred.isEmpty()) { + return a2; + } else if (a2Pred.isEmpty()) { + return a1; + } else { + return createPredicateAnnotation("(" + a1Pred + ") && (" + a2Pred + ")"); } - return evaluateBooleanExpression(expression, trueVariables); + } + + List a1Val = getAccumulatedValues(a1); + List a2Val = getAccumulatedValues(a2); + // Avoid creating new annotation objects in the common case. + if (a1Val.containsAll(a2Val)) { + return a1; + } + if (a2Val.containsAll(a1Val)) { + return a2; + } + a1Val.addAll(a2Val); // union + return createAccumulatorAnnotation(a1Val); } /** - * Evaluates a boolean expression, in JavaParser format, that contains only and, or, - * parentheses, logical complement, and boolean literal nodes. - * - * @param expression a JavaParser boolean expression - * @param trueVariables the names of the variables that should be considered "true"; all other - * literals are considered "false" - * @return the result of evaluating the expression + * LUB in this type system is set intersection of the arguments of the two annotations, unless + * one of them is bottom, in which case the result is the other annotation. */ - private boolean evaluateBooleanExpression(Expression expression, List trueVariables) { - if (expression.isNameExpr()) { - return trueVariables.contains(expression.asNameExpr().getNameAsString()); - } else if (expression.isBinaryExpr()) { - if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.OR) { - return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) - || evaluateBooleanExpression( - expression.asBinaryExpr().getRight(), trueVariables); - } else if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.AND) { - return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) - && evaluateBooleanExpression( - expression.asBinaryExpr().getRight(), trueVariables); - } - } else if (expression.isEnclosedExpr()) { - return evaluateBooleanExpression(expression.asEnclosedExpr().getInner(), trueVariables); - } else if (expression.isUnaryExpr()) { - if (expression.asUnaryExpr().getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return !evaluateBooleanExpression( - expression.asUnaryExpr().getExpression(), trueVariables); - } + @Override + public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, bottom)) { + return a2; + } else if (AnnotationUtils.areSame(a2, bottom)) { + return a1; + } + + if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { + return a1; + } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { + return top; + } + + // If either is a predicate, then both should be converted to predicates and or-ed. + if (isPredicate(a1) || isPredicate(a2)) { + String a1Pred = convertToPredicate(a1); + String a2Pred = convertToPredicate(a2); + // check for top + if (a1Pred.isEmpty()) { + return a1; + } else if (a2Pred.isEmpty()) { + return a2; + } else { + return createPredicateAnnotation("(" + a1Pred + ") || (" + a2Pred + ")"); } - // This could be a BugInCF if there is a bug in the code above. - throw new UserError( - "encountered an unexpected type of expression in a " - + "predicate expression: " - + expression - + " was of type " - + expression.getClass()); + } + + List a1Val = getAccumulatedValues(a1); + List a2Val = getAccumulatedValues(a2); + // Avoid creating new annotation objects in the common case. + if (a1Val.containsAll(a2Val)) { + return a2; + } + if (a2Val.containsAll(a1Val)) { + return a1; + } + a1Val.retainAll(a2Val); // intersection + return createAccumulatorAnnotation(a1Val); } /** - * Creates a new predicate annotation from the given string. + * {@inheritDoc} * - * @param p a valid predicate - * @return an annotation representing that predicate + *

isSubtype in this type system is subset. */ - protected AnnotationMirror createPredicateAnnotation(String p) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, predicate); - builder.setValue("value", p); - return builder.build(); - } - - /** - * Converts the given annotation mirror to a predicate. - * - * @param anno an annotation - * @return the predicate, as a String, that is equivalent to that annotation. May return the - * empty string. - */ - protected String convertToPredicate(AnnotationMirror anno) { - if (AnnotationUtils.areSame(anno, bottom)) { - return "false"; - } else if (isPredicate(anno)) { - String result = - AnnotationUtils.getElementValueOrNull(anno, "value", String.class, false); - return result == null ? "" : result; - } else if (isAccumulatorAnnotation(anno)) { - List values = getAccumulatedValues(anno); - StringJoiner sj = new StringJoiner(" && "); - for (String value : values) { - sj.add(value); - } - return sj.toString(); + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (AnnotationUtils.areSame(subAnno, bottom)) { + return true; + } else if (AnnotationUtils.areSame(superAnno, bottom)) { + return false; + } + + if (isPolymorphicQualifier(subAnno)) { + if (isPolymorphicQualifier(superAnno)) { + return true; } else { - throw new BugInCF("annotation is not bottom, a predicate, or an accumulator: " + anno); + // Use this slightly more expensive conversion here because this is a rare code + // path and it's simpler to read than checking for both predicate and + // non-predicate forms of top. + return "".equals(convertToPredicate(superAnno)); } + } else if (isPolymorphicQualifier(superAnno)) { + // Polymorphic annotations are only a supertype of other polymorphic annotations and + // the bottom type, both of which have already been checked above. + return false; + } + + if (isPredicate(subAnno)) { + return isPredicateSubtype(convertToPredicate(subAnno), convertToPredicate(superAnno)); + } else if (isPredicate(superAnno)) { + return evaluatePredicate(subAnno, convertToPredicate(superAnno)); + } + + List subVal = getAccumulatedValues(subAnno); + List superVal = getAccumulatedValues(superAnno); + return subVal.containsAll(superVal); } - - /** - * Returns true if anno is a predicate annotation. - * - * @param anno an annotation - * @return true if anno is a predicate annotation - */ - protected boolean isPredicate(AnnotationMirror anno) { - return predicate != null && areSameByClass(anno, predicate); + } + + /** + * Extension point for subtyping behavior between predicates. This implementation conservatively + * returns true only if the predicates are equal, or if the prospective supertype (q) is + * equivalent to top (that is, the empty string). + * + * @param p a predicate + * @param q another predicate + * @return true if p is a subtype of q + */ + protected boolean isPredicateSubtype(String p, String q) { + return "".equals(q) || p.equals(q); + } + + /** + * Evaluates whether the accumulator annotation {@code subAnno} makes the predicate {@code pred} + * true. + * + * @param subAnno an accumulator annotation + * @param pred a predicate + * @return whether the accumulator annotation satisfies the predicate + */ + protected boolean evaluatePredicate(AnnotationMirror subAnno, String pred) { + if (!isAccumulatorAnnotation(subAnno)) { + throw new BugInCF( + "tried to evaluate a predicate using an annotation that wasn't an accumulator: " + + subAnno); + } + List trueVariables = getAccumulatedValues(subAnno); + return evaluatePredicate(trueVariables, pred); + } + + /** + * Checks that the given annotation either: + * + *

    + *
  • does not contain a predicate, or + *
  • contains a parse-able predicate + *
+ * + * Used by the visitor to throw "predicate.invalid" errors; thus must be package-private. + * + * @param anm any annotation supported by this checker + * @return null if there is nothing wrong with the annotation, or an error message indicating the + * problem if it has an invalid predicate + */ + /*package-private*/ @Nullable String isValidPredicate(AnnotationMirror anm) { + String pred = convertToPredicate(anm); + try { + evaluatePredicate(Collections.emptyList(), pred); + } catch (UserError ue) { + return ue.getLocalizedMessage(); + } + return null; + } + + /** + * Evaluates whether treating the variables in {@code trueVariables} as {@code true} literals (and + * all other names as {@code false} literals) makes the predicate {@code pred} evaluate to true. + * + * @param trueVariables a list of names that should be replaced with {@code true} + * @param pred a predicate + * @return whether the true variables satisfy the predicate + */ + protected boolean evaluatePredicate(List trueVariables, String pred) { + Expression expression; + try { + expression = StaticJavaParser.parseExpression(pred); + } catch (ParseProblemException p) { + throw new UserError("unparsable predicate: " + pred + ". Parse exception: " + p); + } + return evaluateBooleanExpression(expression, trueVariables); + } + + /** + * Evaluates a boolean expression, in JavaParser format, that contains only and, or, parentheses, + * logical complement, and boolean literal nodes. + * + * @param expression a JavaParser boolean expression + * @param trueVariables the names of the variables that should be considered "true"; all other + * literals are considered "false" + * @return the result of evaluating the expression + */ + private boolean evaluateBooleanExpression(Expression expression, List trueVariables) { + if (expression.isNameExpr()) { + return trueVariables.contains(expression.asNameExpr().getNameAsString()); + } else if (expression.isBinaryExpr()) { + if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.OR) { + return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) + || evaluateBooleanExpression(expression.asBinaryExpr().getRight(), trueVariables); + } else if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.AND) { + return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) + && evaluateBooleanExpression(expression.asBinaryExpr().getRight(), trueVariables); + } + } else if (expression.isEnclosedExpr()) { + return evaluateBooleanExpression(expression.asEnclosedExpr().getInner(), trueVariables); + } else if (expression.isUnaryExpr()) { + if (expression.asUnaryExpr().getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return !evaluateBooleanExpression(expression.asUnaryExpr().getExpression(), trueVariables); + } + } + // This could be a BugInCF if there is a bug in the code above. + throw new UserError( + "encountered an unexpected type of expression in a " + + "predicate expression: " + + expression + + " was of type " + + expression.getClass()); + } + + /** + * Creates a new predicate annotation from the given string. + * + * @param p a valid predicate + * @return an annotation representing that predicate + */ + protected AnnotationMirror createPredicateAnnotation(String p) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, predicate); + builder.setValue("value", p); + return builder.build(); + } + + /** + * Converts the given annotation mirror to a predicate. + * + * @param anno an annotation + * @return the predicate, as a String, that is equivalent to that annotation. May return the empty + * string. + */ + protected String convertToPredicate(AnnotationMirror anno) { + if (AnnotationUtils.areSame(anno, bottom)) { + return "false"; + } else if (isPredicate(anno)) { + String result = AnnotationUtils.getElementValueOrNull(anno, "value", String.class, false); + return result == null ? "" : result; + } else if (isAccumulatorAnnotation(anno)) { + List values = getAccumulatedValues(anno); + StringJoiner sj = new StringJoiner(" && "); + for (String value : values) { + sj.add(value); + } + return sj.toString(); + } else { + throw new BugInCF("annotation is not bottom, a predicate, or an accumulator: " + anno); } + } + + /** + * Returns true if anno is a predicate annotation. + * + * @param anno an annotation + * @return true if anno is a predicate annotation + */ + protected boolean isPredicate(AnnotationMirror anno) { + return predicate != null && areSameByClass(anno, predicate); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java index b72562754af..d6b6cc5763c 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java @@ -1,12 +1,11 @@ package org.checkerframework.common.accumulation; +import java.util.EnumSet; +import java.util.Set; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; -import java.util.EnumSet; -import java.util.Set; - // TODO: This Javadoc comment should reference the Checker Framework manual, once the Accumulation // Checker chapter is uncommented in the manual's LaTeX source. /** @@ -24,54 +23,54 @@ */ public abstract class AccumulationChecker extends BaseTypeChecker { - /** Set of alias analyses that are enabled in this particular accumulation checker. */ - private final EnumSet aliasAnalyses; + /** Set of alias analyses that are enabled in this particular accumulation checker. */ + private final EnumSet aliasAnalyses; - /** Constructs a new AccumulationChecker. */ - protected AccumulationChecker() { - super(); - this.aliasAnalyses = createAliasAnalyses(); - } + /** Constructs a new AccumulationChecker. */ + protected AccumulationChecker() { + super(); + this.aliasAnalyses = createAliasAnalyses(); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - if (isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { - checkers.add(ReturnsReceiverChecker.class); - } - return checkers; + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + if (isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { + checkers.add(ReturnsReceiverChecker.class); } + return checkers; + } + /** + * The alias analyses that an accumulation checker can support. To add support for a new alias + * analysis, add a new item to this enum and then implement any functionality of the checker + * behind a call to {@link #isEnabled(AliasAnalysis)}. + */ + public enum AliasAnalysis { /** - * The alias analyses that an accumulation checker can support. To add support for a new alias - * analysis, add a new item to this enum and then implement any functionality of the checker - * behind a call to {@link #isEnabled(AliasAnalysis)}. + * An alias analysis that detects methods that always return their own receiver (i.e. whose + * return value and receiver are aliases). */ - public enum AliasAnalysis { - /** - * An alias analysis that detects methods that always return their own receiver (i.e. whose - * return value and receiver are aliases). - */ - RETURNS_RECEIVER - } + RETURNS_RECEIVER + } - /** - * Get the alias analyses that this checker should employ. - * - * @return the alias analyses - */ - protected EnumSet createAliasAnalyses( - @UnderInitialization AccumulationChecker this) { - return EnumSet.of(AliasAnalysis.RETURNS_RECEIVER); - } + /** + * Get the alias analyses that this checker should employ. + * + * @return the alias analyses + */ + protected EnumSet createAliasAnalyses( + @UnderInitialization AccumulationChecker this) { + return EnumSet.of(AliasAnalysis.RETURNS_RECEIVER); + } - /** - * Check whether the given alias analysis is enabled by this particular accumulation checker. - * - * @param aliasAnalysis the analysis to check - * @return true iff the analysis is enabled - */ - public boolean isEnabled(AliasAnalysis aliasAnalysis) { - return aliasAnalyses.contains(aliasAnalysis); - } + /** + * Check whether the given alias analysis is enabled by this particular accumulation checker. + * + * @param aliasAnalysis the analysis to check + * @return true iff the analysis is enabled + */ + public boolean isEnabled(AliasAnalysis aliasAnalysis) { + return aliasAnalyses.contains(aliasAnalysis); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java index eb2aff97ea1..f05d0169718 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java @@ -6,25 +6,25 @@ /** This class is boilerplate, to enable the logic in AccumulationValue. */ public class AccumulationStore extends CFAbstractStore { - /** - * Constructor matching super. - * - * @param analysis the analysis - * @param sequentialSemantics whether to use sequential semantics (true) or concurrent semantics - * (false) - */ - protected AccumulationStore( - CFAbstractAnalysis analysis, - boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - } + /** + * Constructor matching super. + * + * @param analysis the analysis + * @param sequentialSemantics whether to use sequential semantics (true) or concurrent semantics + * (false) + */ + protected AccumulationStore( + CFAbstractAnalysis analysis, + boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } - /** - * Constructor matching super's copy constructor. - * - * @param other another store - */ - protected AccumulationStore(CFAbstractStore other) { - super(other); - } + /** + * Constructor matching super's copy constructor. + * + * @param other another store + */ + protected AccumulationStore(CFAbstractStore other) { + super(other); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java index 3a7a884370f..00967e564f0 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java @@ -2,7 +2,11 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; @@ -12,13 +16,6 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.plumelib.util.CollectionsPlume; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; - /** * The default transfer function for an accumulation checker. * @@ -26,115 +23,112 @@ * method to add a string to the estimate at a particular program point. */ public class AccumulationTransfer - extends CFAbstractTransfer { + extends CFAbstractTransfer { - /** The type factory. */ - protected final AccumulationAnnotatedTypeFactory atypeFactory; + /** The type factory. */ + protected final AccumulationAnnotatedTypeFactory atypeFactory; - /** - * Build a new AccumulationTransfer for the given analysis. - * - * @param analysis the analysis - */ - public AccumulationTransfer(AccumulationAnalysis analysis) { - super(analysis); - atypeFactory = (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); - } + /** + * Build a new AccumulationTransfer for the given analysis. + * + * @param analysis the analysis + */ + public AccumulationTransfer(AccumulationAnalysis analysis) { + super(analysis); + atypeFactory = (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); + } - /** - * Updates the estimate of how many things {@code node} has accumulated. - * - *

If the node is an invocation of a method that returns its receiver, then its receiver's - * type will also be updated. In a chain of method calls, this process will continue backward as - * long as each receiver is itself a receiver-returning method invocation. - * - *

For example, suppose {@code node} is the expression {@code a.b().c()}, the new value - * (added by the accumulation analysis because of the {@code .c()} call) is "foo", and b and c - * return their receiver. This method will directly update the estimate of {@code a.b().c()} to - * include "foo". In addition, the estimates for the expressions {@code a.b()} and {@code a} - * would have their estimates updated to include "foo", because c and b (respectively) return - * their receivers. Note that due to what kind of values can be held in the store, this - * information is lost outside the method chain. That is, the returns-receiver propagated - * information is lost outside the expression in which the returns-receiver method invocations - * are nested. - * - *

As a concrete example, consider the Called Methods accumulation checker: if {@code build} - * requires a, b, and c to be called, then {@code foo.a().b().c().build();} will typecheck (they - * are in one fluent method chain), but {@code foo.a().b().c(); foo.build();} will not -- the - * store does not keep the information that a, b, and c have been called outside the chain. - * {@code foo}'s type will be {@code CalledMethods("a")}, because only {@code a()} was called - * directly on {@code foo}. For such code to typecheck, the Called Methods accumulation checker - * uses an additional rule: the return type of a receiver-returning method {@code rr()} is - * {@code CalledMethods("rr")}. This rule is implemented directly in the {@link - * org.checkerframework.framework.type.treeannotator.TreeAnnotator} subclass defined in the - * Called Methods type factory. - * - * @param node the node whose estimate should be expanded - * @param result the transfer result containing the store to be modified - * @param values the new accumulation values - */ - public void accumulate( - Node node, - TransferResult result, - String... values) { - List valuesAsList = Arrays.asList(values); - JavaExpression target = JavaExpression.fromNode(node); - if (CFAbstractStore.canInsertJavaExpression(target)) { - if (result.containsTwoStores()) { - updateValueAndInsertIntoStore(result.getThenStore(), target, valuesAsList); - updateValueAndInsertIntoStore(result.getElseStore(), target, valuesAsList); - } else { - updateValueAndInsertIntoStore(result.getRegularStore(), target, valuesAsList); - } - } + /** + * Updates the estimate of how many things {@code node} has accumulated. + * + *

If the node is an invocation of a method that returns its receiver, then its receiver's type + * will also be updated. In a chain of method calls, this process will continue backward as long + * as each receiver is itself a receiver-returning method invocation. + * + *

For example, suppose {@code node} is the expression {@code a.b().c()}, the new value (added + * by the accumulation analysis because of the {@code .c()} call) is "foo", and b and c return + * their receiver. This method will directly update the estimate of {@code a.b().c()} to include + * "foo". In addition, the estimates for the expressions {@code a.b()} and {@code a} would have + * their estimates updated to include "foo", because c and b (respectively) return their + * receivers. Note that due to what kind of values can be held in the store, this information is + * lost outside the method chain. That is, the returns-receiver propagated information is lost + * outside the expression in which the returns-receiver method invocations are nested. + * + *

As a concrete example, consider the Called Methods accumulation checker: if {@code build} + * requires a, b, and c to be called, then {@code foo.a().b().c().build();} will typecheck (they + * are in one fluent method chain), but {@code foo.a().b().c(); foo.build();} will not -- the + * store does not keep the information that a, b, and c have been called outside the chain. {@code + * foo}'s type will be {@code CalledMethods("a")}, because only {@code a()} was called directly on + * {@code foo}. For such code to typecheck, the Called Methods accumulation checker uses an + * additional rule: the return type of a receiver-returning method {@code rr()} is {@code + * CalledMethods("rr")}. This rule is implemented directly in the {@link + * org.checkerframework.framework.type.treeannotator.TreeAnnotator} subclass defined in the Called + * Methods type factory. + * + * @param node the node whose estimate should be expanded + * @param result the transfer result containing the store to be modified + * @param values the new accumulation values + */ + public void accumulate( + Node node, TransferResult result, String... values) { + List valuesAsList = Arrays.asList(values); + JavaExpression target = JavaExpression.fromNode(node); + if (CFAbstractStore.canInsertJavaExpression(target)) { + if (result.containsTwoStores()) { + updateValueAndInsertIntoStore(result.getThenStore(), target, valuesAsList); + updateValueAndInsertIntoStore(result.getElseStore(), target, valuesAsList); + } else { + updateValueAndInsertIntoStore(result.getRegularStore(), target, valuesAsList); + } + } - Tree tree = node.getTree(); - if (tree != null && tree.getKind() == Tree.Kind.METHOD_INVOCATION) { - Node receiver = ((MethodInvocationNode) node).getTarget().getReceiver(); - if (receiver != null && atypeFactory.returnsThis((MethodInvocationTree) tree)) { - accumulate(receiver, result, values); - } - } + Tree tree = node.getTree(); + if (tree != null && tree.getKind() == Tree.Kind.METHOD_INVOCATION) { + Node receiver = ((MethodInvocationNode) node).getTarget().getReceiver(); + if (receiver != null && atypeFactory.returnsThis((MethodInvocationTree) tree)) { + accumulate(receiver, result, values); + } } + } - /** - * Updates {@code target} in {@code store} so that {@code store}'s estimate includes the - * newly-accumulated values in {@code values}. If dataflow has already recorded information - * about the target, this method fetches it and integrates it into the list of values in the new - * annotation. - * - * @param store a store - * @param target an insertable JavaExpression ({@code canInsertJavaExpression(target)} should - * have returned true) - * @param values a list of newly-accumulated values - */ - private void updateValueAndInsertIntoStore( - AccumulationStore store, JavaExpression target, List values) { - // Make a modifiable copy of the list. - List valuesAsList = new ArrayList<>(values); - AccumulationValue flowValue = store.getValue(target); - if (flowValue != null) { - Set accumulatedValues = flowValue.getAccumulatedValues(); - if (accumulatedValues != null) { - valuesAsList = CollectionsPlume.concatenate(valuesAsList, accumulatedValues); - } else { - AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); - assert flowAnnos.size() <= 1; - for (AnnotationMirror anno : flowAnnos) { - if (atypeFactory.isAccumulatorAnnotation(anno)) { - List oldFlowValues = atypeFactory.getAccumulatedValues(anno); - if (!oldFlowValues.isEmpty()) { - // valuesAsList cannot have its length changed -- it is backed by an - // array -- but if oldFlowValues is not empty, it is a new, modifiable - // list. - oldFlowValues.addAll(valuesAsList); - valuesAsList = oldFlowValues; - } - } - } + /** + * Updates {@code target} in {@code store} so that {@code store}'s estimate includes the + * newly-accumulated values in {@code values}. If dataflow has already recorded information about + * the target, this method fetches it and integrates it into the list of values in the new + * annotation. + * + * @param store a store + * @param target an insertable JavaExpression ({@code canInsertJavaExpression(target)} should have + * returned true) + * @param values a list of newly-accumulated values + */ + private void updateValueAndInsertIntoStore( + AccumulationStore store, JavaExpression target, List values) { + // Make a modifiable copy of the list. + List valuesAsList = new ArrayList<>(values); + AccumulationValue flowValue = store.getValue(target); + if (flowValue != null) { + Set accumulatedValues = flowValue.getAccumulatedValues(); + if (accumulatedValues != null) { + valuesAsList = CollectionsPlume.concatenate(valuesAsList, accumulatedValues); + } else { + AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); + assert flowAnnos.size() <= 1; + for (AnnotationMirror anno : flowAnnos) { + if (atypeFactory.isAccumulatorAnnotation(anno)) { + List oldFlowValues = atypeFactory.getAccumulatedValues(anno); + if (!oldFlowValues.isEmpty()) { + // valuesAsList cannot have its length changed -- it is backed by an + // array -- but if oldFlowValues is not empty, it is a new, modifiable + // list. + oldFlowValues.addAll(valuesAsList); + valuesAsList = oldFlowValues; } + } } - AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); - store.insertValue(target, newAnno); + } } + AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); + store.insertValue(target, newAnno); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java index 43af2a3d2b0..12d51b25cff 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java @@ -1,5 +1,10 @@ package org.checkerframework.common.accumulation; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.Node; @@ -7,13 +12,6 @@ import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; -import java.util.HashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * AccumulationValue holds additional information about accumulated facts ("values", not to be * confused with "Value" in the name of this class) that cannot be stored in the accumulation type, @@ -35,132 +33,130 @@ */ public class AccumulationValue extends CFAbstractValue { - /** - * If the underlying type is a type variable or a wildcard, then this is a set of accumulated - * values. Otherwise, it is null. - */ - private @Nullable Set accumulatedValues = null; + /** + * If the underlying type is a type variable or a wildcard, then this is a set of accumulated + * values. Otherwise, it is null. + */ + private @Nullable Set accumulatedValues = null; - /** - * Creates a new CFAbstractValue. - * - * @param analysis the analysis class this value belongs to - * @param annotations the annotations in this abstract value - * @param underlyingType the underlying (Java) type in this abstract value - */ - protected AccumulationValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - if (underlyingType.getKind() == TypeKind.TYPEVAR - || underlyingType.getKind() == TypeKind.WILDCARD) { - AccumulationAnnotatedTypeFactory typeFactory = - (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); - AnnotationMirror accumulator = null; - for (AnnotationMirror anm : annotations) { - if (typeFactory.isAccumulatorAnnotation(anm)) { - accumulator = anm; - break; - } - } - if (accumulator != null) { - accumulatedValues = new HashSet<>(typeFactory.getAccumulatedValues(accumulator)); - } + /** + * Creates a new CFAbstractValue. + * + * @param analysis the analysis class this value belongs to + * @param annotations the annotations in this abstract value + * @param underlyingType the underlying (Java) type in this abstract value + */ + protected AccumulationValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + if (underlyingType.getKind() == TypeKind.TYPEVAR + || underlyingType.getKind() == TypeKind.WILDCARD) { + AccumulationAnnotatedTypeFactory typeFactory = + (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); + AnnotationMirror accumulator = null; + for (AnnotationMirror anm : annotations) { + if (typeFactory.isAccumulatorAnnotation(anm)) { + accumulator = anm; + break; } + } + if (accumulator != null) { + accumulatedValues = new HashSet<>(typeFactory.getAccumulatedValues(accumulator)); + } } + } - /** - * If the underlying type is a type variable or a wildcard, then this is a set of accumulated - * values. Otherwise, it is null. - * - * @return the set (this is not a copy of the set, but an alias) - */ - public @Nullable Set getAccumulatedValues() { - return accumulatedValues; - } + /** + * If the underlying type is a type variable or a wildcard, then this is a set of accumulated + * values. Otherwise, it is null. + * + * @return the set (this is not a copy of the set, but an alias) + */ + public @Nullable Set getAccumulatedValues() { + return accumulatedValues; + } - @Override - protected AccumulationValue upperBound( - @Nullable AccumulationValue other, - TypeMirror upperBoundTypeMirror, - boolean shouldWiden) { - AccumulationValue lub = super.upperBound(other, upperBoundTypeMirror, shouldWiden); + @Override + protected AccumulationValue upperBound( + @Nullable AccumulationValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { + AccumulationValue lub = super.upperBound(other, upperBoundTypeMirror, shouldWiden); - if (other == null || other.accumulatedValues == null || this.accumulatedValues == null) { - return lub; - } - // Lub the accumulatedValues by intersecting the lists as if they were sets. - lub.accumulatedValues = new HashSet<>(this.accumulatedValues.size()); - lub.accumulatedValues.addAll(this.accumulatedValues); - lub.accumulatedValues.retainAll(other.accumulatedValues); - if (lub.accumulatedValues.isEmpty()) { - lub.accumulatedValues = null; - } - return lub; + if (other == null || other.accumulatedValues == null || this.accumulatedValues == null) { + return lub; } + // Lub the accumulatedValues by intersecting the lists as if they were sets. + lub.accumulatedValues = new HashSet<>(this.accumulatedValues.size()); + lub.accumulatedValues.addAll(this.accumulatedValues); + lub.accumulatedValues.retainAll(other.accumulatedValues); + if (lub.accumulatedValues.isEmpty()) { + lub.accumulatedValues = null; + } + return lub; + } - @Override - public @Nullable AccumulationValue mostSpecific( - AccumulationValue other, AccumulationValue backup) { - if (other == null) { - return this; - } - AccumulationValue mostSpecific = super.mostSpecific(other, backup); - if (mostSpecific != null) { - mostSpecific.addAccumulatedValues(this.accumulatedValues); - mostSpecific.addAccumulatedValues(other.accumulatedValues); - return mostSpecific; - } - - // mostSpecific is null if the two types are not comparable. This is normally - // because one of this or other is a type variable and annotations is empty, but the - // other annotations are not empty. In this case, copy the accumulatedValues to the - // value with no annotations and return it as most specific. - if (other.getAnnotations().isEmpty()) { - mostSpecific = - new AccumulationValue( - analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); - mostSpecific.addAccumulatedValues(this.accumulatedValues); - mostSpecific.addAccumulatedValues(other.accumulatedValues); - return mostSpecific; - } else if (this.getAnnotations().isEmpty()) { - mostSpecific = - new AccumulationValue( - analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); - mostSpecific.addAccumulatedValues(this.accumulatedValues); - mostSpecific.addAccumulatedValues(other.accumulatedValues); - return mostSpecific; - } - return null; + @Override + public @Nullable AccumulationValue mostSpecific( + AccumulationValue other, AccumulationValue backup) { + if (other == null) { + return this; + } + AccumulationValue mostSpecific = super.mostSpecific(other, backup); + if (mostSpecific != null) { + mostSpecific.addAccumulatedValues(this.accumulatedValues); + mostSpecific.addAccumulatedValues(other.accumulatedValues); + return mostSpecific; } - /** - * Merges its argument into the {@link #accumulatedValues} field of this. - * - * @param newAccumulatedValues a new list of accumulated values - */ - private void addAccumulatedValues(Set newAccumulatedValues) { - if (newAccumulatedValues == null || newAccumulatedValues.isEmpty()) { - return; - } - if (accumulatedValues == null) { - accumulatedValues = new HashSet<>(newAccumulatedValues.size()); - } - accumulatedValues.addAll(newAccumulatedValues); + // mostSpecific is null if the two types are not comparable. This is normally + // because one of this or other is a type variable and annotations is empty, but the + // other annotations are not empty. In this case, copy the accumulatedValues to the + // value with no annotations and return it as most specific. + if (other.getAnnotations().isEmpty()) { + mostSpecific = + new AccumulationValue( + analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); + mostSpecific.addAccumulatedValues(this.accumulatedValues); + mostSpecific.addAccumulatedValues(other.accumulatedValues); + return mostSpecific; + } else if (this.getAnnotations().isEmpty()) { + mostSpecific = + new AccumulationValue( + analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); + mostSpecific.addAccumulatedValues(this.accumulatedValues); + mostSpecific.addAccumulatedValues(other.accumulatedValues); + return mostSpecific; } + return null; + } - @Override - public String toString() { - String superToString = super.toString(); - // remove last '}' - superToString = superToString.substring(0, superToString.length() - 1); - return superToString - + ", " - + "[" - + (accumulatedValues == null - ? "null" - : String.join(", ", accumulatedValues.toArray(new String[0]))) - + "] }"; + /** + * Merges its argument into the {@link #accumulatedValues} field of this. + * + * @param newAccumulatedValues a new list of accumulated values + */ + private void addAccumulatedValues(Set newAccumulatedValues) { + if (newAccumulatedValues == null || newAccumulatedValues.isEmpty()) { + return; + } + if (accumulatedValues == null) { + accumulatedValues = new HashSet<>(newAccumulatedValues.size()); } + accumulatedValues.addAll(newAccumulatedValues); + } + + @Override + public String toString() { + String superToString = super.toString(); + // remove last '}' + superToString = superToString.substring(0, superToString.length() - 1); + return superToString + + ", " + + "[" + + (accumulatedValues == null + ? "null" + : String.join(", ", accumulatedValues.toArray(new String[0]))) + + "] }"; + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java index 8f4b7e9c3cd..3cf870a4536 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java @@ -1,38 +1,36 @@ package org.checkerframework.common.accumulation; import com.sun.source.tree.AnnotationTree; - +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; - /** * The visitor for an accumulation checker. Issues predicate.invalid errors if the user writes an * invalid predicate. */ public class AccumulationVisitor extends BaseTypeVisitor { - /** - * Constructor matching super. - * - * @param checker the checker - */ - public AccumulationVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Constructor matching super. + * + * @param checker the checker + */ + public AccumulationVisitor(BaseTypeChecker checker) { + super(checker); + } - /** Checks each predicate annotation to make sure the predicate is well-formed. */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - if (atypeFactory.isPredicate(anno)) { - String errorMessage = atypeFactory.isValidPredicate(anno); - if (errorMessage != null) { - checker.reportError(tree, "predicate.invalid", errorMessage); - } - } - return super.visitAnnotation(tree, p); + /** Checks each predicate annotation to make sure the predicate is well-formed. */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + if (atypeFactory.isPredicate(anno)) { + String errorMessage = atypeFactory.isValidPredicate(anno); + if (errorMessage != null) { + checker.reportError(tree, "predicate.invalid", errorMessage); + } } + return super.visitAnnotation(tree, p); + } } diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java index 8627c37c0c8..9b24346f1fb 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java @@ -1,7 +1,11 @@ package org.checkerframework.common.aliasing; import com.sun.source.tree.NewArrayTree; - +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.common.aliasing.qual.LeakedToResult; import org.checkerframework.common.aliasing.qual.MaybeAliased; import org.checkerframework.common.aliasing.qual.MaybeLeaked; @@ -20,115 +24,108 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationBuilder; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** Annotated type factory for the Aliasing Checker. */ public class AliasingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** Aliasing annotations. */ - /** The @{@link MaybeAliased} annotation. */ - protected final AnnotationMirror MAYBE_ALIASED = - AnnotationBuilder.fromClass(elements, MaybeAliased.class); + /** Aliasing annotations. */ + /** The @{@link MaybeAliased} annotation. */ + protected final AnnotationMirror MAYBE_ALIASED = + AnnotationBuilder.fromClass(elements, MaybeAliased.class); - /** The @{@link NonLeaked} annotation. */ - protected final AnnotationMirror NON_LEAKED = - AnnotationBuilder.fromClass(elements, NonLeaked.class); + /** The @{@link NonLeaked} annotation. */ + protected final AnnotationMirror NON_LEAKED = + AnnotationBuilder.fromClass(elements, NonLeaked.class); - /** The @{@link Unique} annotation. */ - protected final AnnotationMirror UNIQUE = AnnotationBuilder.fromClass(elements, Unique.class); + /** The @{@link Unique} annotation. */ + protected final AnnotationMirror UNIQUE = AnnotationBuilder.fromClass(elements, Unique.class); - /** The @{@link MaybeLeaked} annotation. */ - protected final AnnotationMirror MAYBE_LEAKED = - AnnotationBuilder.fromClass(elements, MaybeLeaked.class); + /** The @{@link MaybeLeaked} annotation. */ + protected final AnnotationMirror MAYBE_LEAKED = + AnnotationBuilder.fromClass(elements, MaybeLeaked.class); - /** Create the type factory. */ - public AliasingAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - if (this.getClass() == AliasingAnnotatedTypeFactory.class) { - this.postInit(); - } + /** Create the type factory. */ + public AliasingAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + if (this.getClass() == AliasingAnnotatedTypeFactory.class) { + this.postInit(); } - - // @NonLeaked and @LeakedToResult are type qualifiers because of a checker framework limitation - // (Issue 383). Once the stub parser gets updated to read non-type-qualifiers annotations on - // stub files, this annotation won't be a type qualifier anymore. - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(MaybeLeaked.class); + } + + // @NonLeaked and @LeakedToResult are type qualifiers because of a checker framework limitation + // (Issue 383). Once the stub parser gets updated to read non-type-qualifiers annotations on + // stub files, this annotation won't be a type qualifier anymore. + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(MaybeLeaked.class); + } + + @Override + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + CFTransfer ret = new AliasingTransfer(analysis); + return ret; + } + + protected class AliasingTreeAnnotator extends TreeAnnotator { + + public AliasingTreeAnnotator(AliasingAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - CFTransfer ret = new AliasingTransfer(analysis); - return ret; + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(UNIQUE); + return super.visitNewArray(tree, type); } - - protected class AliasingTreeAnnotator extends TreeAnnotator { - - public AliasingTreeAnnotator(AliasingAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(UNIQUE); - return super.visitNewArray(tree, type); - } + } + + @Override + protected ListTreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new AliasingTreeAnnotator(this), super.createTreeAnnotator()); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new AliasingQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** AliasingQualifierHierarchy. */ + protected class AliasingQualifierHierarchy extends NoElementQualifierHierarchy { + + /** + * Create AliasingQualifierHierarchy. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + protected AliasingQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, AliasingAnnotatedTypeFactory.this); } - @Override - protected ListTreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new AliasingTreeAnnotator(this), super.createTreeAnnotator()); + /** + * Returns true is {@code anno} is annotation in the Leaked hierarchy. + * + * @param anno an annotation + * @return true is {@code anno} is annotation in the Leaked hierarchy + */ + private boolean isLeakedQualifier(AnnotationMirror anno) { + return areSameByClass(anno, MaybeLeaked.class) + || areSameByClass(anno, NonLeaked.class) + || areSameByClass(anno, LeakedToResult.class); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new AliasingQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** AliasingQualifierHierarchy. */ - protected class AliasingQualifierHierarchy extends NoElementQualifierHierarchy { - - /** - * Create AliasingQualifierHierarchy. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - protected AliasingQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, AliasingAnnotatedTypeFactory.this); - } - - /** - * Returns true is {@code anno} is annotation in the Leaked hierarchy. - * - * @param anno an annotation - * @return true is {@code anno} is annotation in the Leaked hierarchy - */ - private boolean isLeakedQualifier(AnnotationMirror anno) { - return areSameByClass(anno, MaybeLeaked.class) - || areSameByClass(anno, NonLeaked.class) - || areSameByClass(anno, LeakedToResult.class); - } - - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (isLeakedQualifier(superAnno) && isLeakedQualifier(subAnno)) { - // @LeakedToResult and @NonLeaked were supposed to be non-type-qualifiers - // annotations. - // Currently the stub parser does not support non-type-qualifier annotations on - // receiver parameters (Issue 383), therefore these annotations are implemented as - // type qualifiers but the warnings related to the hierarchy are ignored. - return true; - } - return super.isSubtypeQualifiers(subAnno, superAnno); - } + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (isLeakedQualifier(superAnno) && isLeakedQualifier(subAnno)) { + // @LeakedToResult and @NonLeaked were supposed to be non-type-qualifiers + // annotations. + // Currently the stub parser does not support non-type-qualifier annotations on + // receiver parameters (Issue 383), therefore these annotations are implemented as + // type qualifiers but the warnings related to the hierarchy are ignored. + return true; + } + return super.isSubtypeQualifiers(subAnno, superAnno); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java index a2e721206de..bd0aa7c43e0 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java @@ -3,7 +3,9 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; - +import java.util.List; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.common.aliasing.qual.LeakedToResult; import org.checkerframework.common.aliasing.qual.NonLeaked; import org.checkerframework.common.aliasing.qual.Unique; @@ -25,11 +27,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.javacutil.TreeUtils; -import java.util.List; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; - /** * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may lose * its type refinement, before the LHS is type-refined. @@ -47,136 +44,136 @@ */ public class AliasingTransfer extends CFTransfer { - /** The annotated type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * Create a new AliasingTransfer. - * - * @param analysis the CFAbstractAnalysis - */ - public AliasingTransfer(CFAbstractAnalysis analysis) { - super(analysis); - atypeFactory = analysis.getTypeFactory(); + /** The annotated type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * Create a new AliasingTransfer. + * + * @param analysis the CFAbstractAnalysis + */ + public AliasingTransfer(CFAbstractAnalysis analysis) { + super(analysis); + atypeFactory = analysis.getTypeFactory(); + } + + /** + * Case 1: For every assignment, the LHS is refined if the RHS has type {@literal @}Unique and is + * a method invocation or a new class instance. + */ + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput in) { + Node rhs = n.getExpression(); + Tree treeRhs = rhs.getTree(); + AnnotatedTypeMirror rhsType = atypeFactory.getAnnotatedType(treeRhs); + + if (rhsType.hasAnnotation(Unique.class) + && (rhs instanceof MethodInvocationNode || rhs instanceof ObjectCreationNode)) { + return super.visitAssignment(n, in); // Do normal refinement. } - - /** - * Case 1: For every assignment, the LHS is refined if the RHS has type {@literal @}Unique and - * is a method invocation or a new class instance. - */ - @Override - public TransferResult visitAssignment( - AssignmentNode n, TransferInput in) { - Node rhs = n.getExpression(); - Tree treeRhs = rhs.getTree(); - AnnotatedTypeMirror rhsType = atypeFactory.getAnnotatedType(treeRhs); - - if (rhsType.hasAnnotation(Unique.class) - && (rhs instanceof MethodInvocationNode || rhs instanceof ObjectCreationNode)) { - return super.visitAssignment(n, in); // Do normal refinement. - } - // Widen the type of the rhs if the RHS's declared type wasn't @Unique. - JavaExpression rhsExpr = JavaExpression.fromNode(rhs); - in.getRegularStore().clearValue(rhsExpr); - return new RegularTransferResult<>(null, in.getRegularStore()); + // Widen the type of the rhs if the RHS's declared type wasn't @Unique. + JavaExpression rhsExpr = JavaExpression.fromNode(rhs); + in.getRegularStore().clearValue(rhsExpr); + return new RegularTransferResult<>(null, in.getRegularStore()); + } + + /** + * Handling pseudo-assignments. Called by {@code CFAbstractTransfer.visitMethodInvocation()}. + * + *

Case 2: Given a method call, traverses all formal parameters of the method declaration, and + * if it doesn't have the {@literal @}NonLeaked or {@literal @}LeakedToResult annotations, we + * remove the node of the respective argument in the method call from the store. If parameter has + * {@literal @}LeakedToResult, {@code visitMethodInvocation()} handles it. + */ + @Override + protected void processPostconditions( + Node n, CFStore store, ExecutableElement executableElement, ExpressionTree tree) { + // TODO: Process ObjectCreationNode here after fixing issue: + // https://github.com/eisop/checker-framework/issues/400 + if (!(n instanceof MethodInvocationNode)) { + return; } - - /** - * Handling pseudo-assignments. Called by {@code CFAbstractTransfer.visitMethodInvocation()}. - * - *

Case 2: Given a method call, traverses all formal parameters of the method declaration, - * and if it doesn't have the {@literal @}NonLeaked or {@literal @}LeakedToResult annotations, - * we remove the node of the respective argument in the method call from the store. If parameter - * has {@literal @}LeakedToResult, {@code visitMethodInvocation()} handles it. - */ - @Override - protected void processPostconditions( - Node n, CFStore store, ExecutableElement executableElement, ExpressionTree tree) { - // TODO: Process ObjectCreationNode here after fixing issue: - // https://github.com/eisop/checker-framework/issues/400 - if (!(n instanceof MethodInvocationNode)) { - return; - } - super.processPostconditions(n, store, executableElement, tree); - if (TreeUtils.isEnumSuperCall((MethodInvocationTree) n.getTree())) { - // Skipping the init() method for enums. - return; - } - List args = ((MethodInvocationNode) n).getArguments(); - List params = executableElement.getParameters(); - assert (args.size() == params.size()) - : "Number of arguments in " - + "the method call " - + n - + " is different from the" - + " number of parameters for the method declaration: " - + executableElement.getSimpleName(); - - AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(executableElement); - List paramTypes = annotatedType.getParameterTypes(); - for (int i = 0; i < args.size(); i++) { - Node arg = args.get(i); - AnnotatedTypeMirror paramType = paramTypes.get(i); - if (!paramType.hasAnnotation(NonLeaked.class) - && !paramType.hasAnnotation(LeakedToResult.class)) { - store.clearValue(JavaExpression.fromNode(arg)); - } - } - - // Now, doing the same as above for the receiver parameter - Node receiver = ((MethodInvocationNode) n).getTarget().getReceiver(); - AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); - if (receiverType != null - && !receiverType.hasAnnotation(LeakedToResult.class) - && !receiverType.hasAnnotation(NonLeaked.class)) { - store.clearValue(JavaExpression.fromNode(receiver)); - } + super.processPostconditions(n, store, executableElement, tree); + if (TreeUtils.isEnumSuperCall((MethodInvocationTree) n.getTree())) { + // Skipping the init() method for enums. + return; + } + List args = ((MethodInvocationNode) n).getArguments(); + List params = executableElement.getParameters(); + assert (args.size() == params.size()) + : "Number of arguments in " + + "the method call " + + n + + " is different from the" + + " number of parameters for the method declaration: " + + executableElement.getSimpleName(); + + AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(executableElement); + List paramTypes = annotatedType.getParameterTypes(); + for (int i = 0; i < args.size(); i++) { + Node arg = args.get(i); + AnnotatedTypeMirror paramType = paramTypes.get(i); + if (!paramType.hasAnnotation(NonLeaked.class) + && !paramType.hasAnnotation(LeakedToResult.class)) { + store.clearValue(JavaExpression.fromNode(arg)); + } } - /** - * Case 3: Given a method invocation expression, if the parent of the expression is not a - * statement, check if there are any arguments of the method call annotated as - * {@literal @}LeakedToResult and remove it from the store, since it might be leaked. - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - Tree parent = n.getTreePath().getParentPath().getLeaf(); - boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; - - if (!parentIsStatement) { - - ExecutableElement methodElement = TreeUtils.elementFromUse(n.getTree()); - List args = n.getArguments(); - List params = methodElement.getParameters(); - assert (args.size() == params.size()) - : "Number of arguments in " - + "the method call " - + n - + " is different from the" - + " number of parameters for the method declaration: " - + methodElement.getSimpleName(); - CFStore store = in.getRegularStore(); - - for (int i = 0; i < args.size(); i++) { - Node arg = args.get(i); - VariableElement param = params.get(i); - if (atypeFactory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class)) { - // If argument can leak to result, and parent is not a - // single statement, remove that node from store. - store.clearValue(JavaExpression.fromNode(arg)); - } - } - - // Now, doing the same as above for the receiver parameter - Node receiver = n.getTarget().getReceiver(); - AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement); - AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); - if (receiverType != null && receiverType.hasAnnotation(LeakedToResult.class)) { - store.clearValue(JavaExpression.fromNode(receiver)); - } + // Now, doing the same as above for the receiver parameter + Node receiver = ((MethodInvocationNode) n).getTarget().getReceiver(); + AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); + if (receiverType != null + && !receiverType.hasAnnotation(LeakedToResult.class) + && !receiverType.hasAnnotation(NonLeaked.class)) { + store.clearValue(JavaExpression.fromNode(receiver)); + } + } + + /** + * Case 3: Given a method invocation expression, if the parent of the expression is not a + * statement, check if there are any arguments of the method call annotated as + * {@literal @}LeakedToResult and remove it from the store, since it might be leaked. + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + Tree parent = n.getTreePath().getParentPath().getLeaf(); + boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; + + if (!parentIsStatement) { + + ExecutableElement methodElement = TreeUtils.elementFromUse(n.getTree()); + List args = n.getArguments(); + List params = methodElement.getParameters(); + assert (args.size() == params.size()) + : "Number of arguments in " + + "the method call " + + n + + " is different from the" + + " number of parameters for the method declaration: " + + methodElement.getSimpleName(); + CFStore store = in.getRegularStore(); + + for (int i = 0; i < args.size(); i++) { + Node arg = args.get(i); + VariableElement param = params.get(i); + if (atypeFactory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class)) { + // If argument can leak to result, and parent is not a + // single statement, remove that node from store. + store.clearValue(JavaExpression.fromNode(arg)); } - // If parent is a statement, processPostconditions will handle the pseudo-assignments. - return super.visitMethodInvocation(n, in); + } + + // Now, doing the same as above for the receiver parameter + Node receiver = n.getTarget().getReceiver(); + AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement); + AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); + if (receiverType != null && receiverType.hasAnnotation(LeakedToResult.class)) { + store.clearValue(JavaExpression.fromNode(receiver)); + } } + // If parent is a statement, processPostconditions will handle the pseudo-assignments. + return super.visitMethodInvocation(n, in); + } } diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java index 60d31dc5756..5a01d3d1b5e 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java @@ -8,7 +8,10 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.common.aliasing.qual.LeakedToResult; @@ -24,12 +27,6 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; -import java.util.List; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; - /** * This visitor ensures that every constructor whose result is annotated as {@literal @}Unique does * not leak aliases. @@ -47,312 +44,308 @@ */ public class AliasingVisitor extends BaseTypeVisitor { - public AliasingVisitor(BaseTypeChecker checker) { - super(checker); - } + public AliasingVisitor(BaseTypeChecker checker) { + super(checker); + } - /** - * Checks that if a method call is being invoked inside a constructor with result type - * {@literal @}Unique, it must not leak the "this" reference. There are 3 ways to make sure that - * this is not happening: - * - *

    - *
  1. {@code this} is not an argument of the method call. - *
  2. {@code this} is an argument of the method call, but the respective parameter is - * annotated as {@literal @}NonLeaked. - *
  3. {@code this} is an argument of the method call, but the respective parameter is - * annotated as {@literal @}LeakedToResult AND the result of the method call is not being - * stored (the method call is a statement). - *
- * - * The private method {@code isUniqueCheck} handles cases 2 and 3. - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - // The check only needs to be done for constructors with result type - // @Unique. We also want to avoid visiting the method. - if (isInUniqueConstructor()) { - if (TreeUtils.isSuperConstructorCall(tree)) { - // Check if a call to super() might create an alias: that - // happens when the parent's respective constructor is not @Unique. - AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(tree); - if (!superResult.hasAnnotation(Unique.class)) { - checker.reportError(tree, "unique.leaked"); - } - } else { - // TODO: Currently the type of "this" doesn't always return the type of the - // constructor result, therefore we need this "else" block. Once constructors are - // implemented correctly we could remove that code below, since the type of "this" - // in a @Unique constructor will be @Unique. - Tree parent = getCurrentPath().getParentPath().getLeaf(); - boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; - ExecutableElement methodElement = TreeUtils.elementFromUse(tree); - List params = methodElement.getParameters(); - List args = tree.getArguments(); - assert (args.size() == params.size()) - : "Number of arguments in" - + " the method call " - + tree - + " is different from the " - + "number of parameters for the method declaration: " - + methodElement.getSimpleName(); - for (int i = 0; i < args.size(); i++) { - // Here we are traversing the arguments of the method call. - // For every argument we check if it is a reference to "this". - if (TreeUtils.isExplicitThisDereference(args.get(i))) { - // If it is a reference to "this", there is still hope that - // it is not being leaked (2. and 3. from the javadoc). - VariableElement param = params.get(i); - boolean hasNonLeaked = - atypeFactory.getAnnotatedType(param).hasAnnotation(NonLeaked.class); - boolean hasLeakedToResult = - atypeFactory - .getAnnotatedType(param) - .hasAnnotation(LeakedToResult.class); - isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); - } else { - // Not possible to leak reference here (case 1. from the javadoc). - } - } + /** + * Checks that if a method call is being invoked inside a constructor with result type + * {@literal @}Unique, it must not leak the "this" reference. There are 3 ways to make sure that + * this is not happening: + * + *
    + *
  1. {@code this} is not an argument of the method call. + *
  2. {@code this} is an argument of the method call, but the respective parameter is annotated + * as {@literal @}NonLeaked. + *
  3. {@code this} is an argument of the method call, but the respective parameter is annotated + * as {@literal @}LeakedToResult AND the result of the method call is not being stored (the + * method call is a statement). + *
+ * + * The private method {@code isUniqueCheck} handles cases 2 and 3. + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + // The check only needs to be done for constructors with result type + // @Unique. We also want to avoid visiting the method. + if (isInUniqueConstructor()) { + if (TreeUtils.isSuperConstructorCall(tree)) { + // Check if a call to super() might create an alias: that + // happens when the parent's respective constructor is not @Unique. + AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(tree); + if (!superResult.hasAnnotation(Unique.class)) { + checker.reportError(tree, "unique.leaked"); + } + } else { + // TODO: Currently the type of "this" doesn't always return the type of the + // constructor result, therefore we need this "else" block. Once constructors are + // implemented correctly we could remove that code below, since the type of "this" + // in a @Unique constructor will be @Unique. + Tree parent = getCurrentPath().getParentPath().getLeaf(); + boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; + ExecutableElement methodElement = TreeUtils.elementFromUse(tree); + List params = methodElement.getParameters(); + List args = tree.getArguments(); + assert (args.size() == params.size()) + : "Number of arguments in" + + " the method call " + + tree + + " is different from the " + + "number of parameters for the method declaration: " + + methodElement.getSimpleName(); + for (int i = 0; i < args.size(); i++) { + // Here we are traversing the arguments of the method call. + // For every argument we check if it is a reference to "this". + if (TreeUtils.isExplicitThisDereference(args.get(i))) { + // If it is a reference to "this", there is still hope that + // it is not being leaked (2. and 3. from the javadoc). + VariableElement param = params.get(i); + boolean hasNonLeaked = + atypeFactory.getAnnotatedType(param).hasAnnotation(NonLeaked.class); + boolean hasLeakedToResult = + atypeFactory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class); + isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); + } else { + // Not possible to leak reference here (case 1. from the javadoc). + } + } - // Now, doing the same as above for the receiver parameter - AnnotatedExecutableType annotatedType = - atypeFactory.getAnnotatedType(methodElement); - AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); - if (receiverType != null) { - boolean hasNonLeaked = receiverType.hasAnnotation(NonLeaked.class); - boolean hasLeakedToResult = receiverType.hasAnnotation(LeakedToResult.class); - isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); - } - } + // Now, doing the same as above for the receiver parameter + AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement); + AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); + if (receiverType != null) { + boolean hasNonLeaked = receiverType.hasAnnotation(NonLeaked.class); + boolean hasLeakedToResult = receiverType.hasAnnotation(LeakedToResult.class); + isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); } - return super.visitMethodInvocation(tree, p); + } } + return super.visitMethodInvocation(tree, p); + } - /** - * Possibly issue a "unique.leaked" error. - * - * @param tree a method invocation - * @param parentIsStatement is the parent of {@code tree} a statement? - * @param hasNonLeaked does the receiver have a {@code @NonLeaked} annotation? - * @param hasLeakedToResult does the receiver have a {@code @LeakedToResult} annotation? - */ - private void isUniqueCheck( - MethodInvocationTree tree, - boolean parentIsStatement, - boolean hasNonLeaked, - boolean hasLeakedToResult) { - if (hasNonLeaked || (hasLeakedToResult && parentIsStatement)) { - // Not leaked according to cases 2. and 3. from the javadoc of - // visitMethodInvocation. - } else { - // May be leaked, raise warning. - checker.reportError(tree, "unique.leaked"); - } + /** + * Possibly issue a "unique.leaked" error. + * + * @param tree a method invocation + * @param parentIsStatement is the parent of {@code tree} a statement? + * @param hasNonLeaked does the receiver have a {@code @NonLeaked} annotation? + * @param hasLeakedToResult does the receiver have a {@code @LeakedToResult} annotation? + */ + private void isUniqueCheck( + MethodInvocationTree tree, + boolean parentIsStatement, + boolean hasNonLeaked, + boolean hasLeakedToResult) { + if (hasNonLeaked || (hasLeakedToResult && parentIsStatement)) { + // Not leaked according to cases 2. and 3. from the javadoc of + // visitMethodInvocation. + } else { + // May be leaked, raise warning. + checker.reportError(tree, "unique.leaked"); } + } - // TODO: Merge that code in commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree - // valueExp, String errorKey, boolean isLocalVariableAssignment), because the method below - // isn't called for pseudo-assignments, but the mentioned one is. The issue of copy-pasting the - // code from this method to the other one is that a declaration such as: List<@Unique Object> - // will raise a unique.leaked warning, as there is a pseudo-assignment from @Unique to a - // @MaybeAliased object, if the @Unique annotation is not in the stubfile. TODO: Change the - // documentation in BaseTypeVisitor to point out that this isn't called for pseudo-assignments. - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); - if (isInUniqueConstructor() && TreeUtils.isExplicitThisDereference(valueExp)) { - // If an assignment occurs inside a constructor with result type @Unique, it will - // invalidate the @Unique property by using the "this" reference. - checker.reportError(valueExp, "unique.leaked"); - result = false; - } else if (canBeLeaked(valueExp)) { - checker.reportError(valueExp, "unique.leaked"); - result = false; - } - return result; + // TODO: Merge that code in commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree + // valueExp, String errorKey, boolean isLocalVariableAssignment), because the method below + // isn't called for pseudo-assignments, but the mentioned one is. The issue of copy-pasting the + // code from this method to the other one is that a declaration such as: List<@Unique Object> + // will raise a unique.leaked warning, as there is a pseudo-assignment from @Unique to a + // @MaybeAliased object, if the @Unique annotation is not in the stubfile. TODO: Change the + // documentation in BaseTypeVisitor to point out that this isn't called for pseudo-assignments. + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); + if (isInUniqueConstructor() && TreeUtils.isExplicitThisDereference(valueExp)) { + // If an assignment occurs inside a constructor with result type @Unique, it will + // invalidate the @Unique property by using the "this" reference. + checker.reportError(valueExp, "unique.leaked"); + result = false; + } else if (canBeLeaked(valueExp)) { + checker.reportError(valueExp, "unique.leaked"); + result = false; } + return result; + } - @Override - @FormatMethod - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + @Override + @FormatMethod + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - // If we are visiting a pseudo-assignment, visitorLeafKind is either - // Tree.Kind.NEW_CLASS or Tree.Kind.METHOD_INVOCATION. - TreePath path = getCurrentPath(); - if (path == null) { - return result; - } - Tree.Kind visitorLeafKind = path.getLeaf().getKind(); + // If we are visiting a pseudo-assignment, visitorLeafKind is either + // Tree.Kind.NEW_CLASS or Tree.Kind.METHOD_INVOCATION. + TreePath path = getCurrentPath(); + if (path == null) { + return result; + } + Tree.Kind visitorLeafKind = path.getLeaf().getKind(); - if (visitorLeafKind == Tree.Kind.NEW_CLASS - || visitorLeafKind == Tree.Kind.METHOD_INVOCATION) { - // Handling pseudo-assignments - if (canBeLeaked(valueTree)) { - Tree.Kind parentKind = getCurrentPath().getParentPath().getLeaf().getKind(); + if (visitorLeafKind == Tree.Kind.NEW_CLASS || visitorLeafKind == Tree.Kind.METHOD_INVOCATION) { + // Handling pseudo-assignments + if (canBeLeaked(valueTree)) { + Tree.Kind parentKind = getCurrentPath().getParentPath().getLeaf().getKind(); - if (!varType.hasAnnotation(NonLeaked.class) - && !(varType.hasAnnotation(LeakedToResult.class) - && parentKind == Tree.Kind.EXPRESSION_STATEMENT)) { - checker.reportError(valueTree, "unique.leaked"); - result = false; - } - } + if (!varType.hasAnnotation(NonLeaked.class) + && !(varType.hasAnnotation(LeakedToResult.class) + && parentKind == Tree.Kind.EXPRESSION_STATEMENT)) { + checker.reportError(valueTree, "unique.leaked"); + result = false; } - return result; + } } + return result; + } - @Override - public Void visitThrow(ThrowTree tree, Void p) { - // throw is also an escape mechanism. If an expression of type - // @Unique is thrown, it is not @Unique anymore. - ExpressionTree exp = tree.getExpression(); - if (canBeLeaked(exp)) { - checker.reportError(exp, "unique.leaked"); - } - return super.visitThrow(tree, p); + @Override + public Void visitThrow(ThrowTree tree, Void p) { + // throw is also an escape mechanism. If an expression of type + // @Unique is thrown, it is not @Unique anymore. + ExpressionTree exp = tree.getExpression(); + if (canBeLeaked(exp)) { + checker.reportError(exp, "unique.leaked"); } + return super.visitThrow(tree, p); + } - @Override - public Void visitVariable(VariableTree tree, Void p) { - // Component types are not allowed to have the @Unique annotation. - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(tree); - VariableElement elt = TreeUtils.elementFromDeclaration(tree); - if (elt.getKind().isField() && varType.hasExplicitAnnotation(Unique.class)) { + @Override + public Void visitVariable(VariableTree tree, Void p) { + // Component types are not allowed to have the @Unique annotation. + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(tree); + VariableElement elt = TreeUtils.elementFromDeclaration(tree); + if (elt.getKind().isField() && varType.hasExplicitAnnotation(Unique.class)) { + checker.reportError(tree, "unique.location.forbidden"); + } else if (tree.getType() != null) { + // VariableTree#getType returns null for binding variables from a + // DeconstructionPatternTree. + if (tree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { + AnnotatedArrayType arrayType = (AnnotatedArrayType) varType; + if (arrayType.getComponentType().hasAnnotation(Unique.class)) { + checker.reportError(tree, "unique.location.forbidden"); + } + } else if (tree.getType().getKind() == Tree.Kind.PARAMETERIZED_TYPE) { + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) varType; + for (AnnotatedTypeMirror atm : declaredType.getTypeArguments()) { + if (atm.hasAnnotation(Unique.class)) { checker.reportError(tree, "unique.location.forbidden"); - } else if (tree.getType() != null) { - // VariableTree#getType returns null for binding variables from a - // DeconstructionPatternTree. - if (tree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { - AnnotatedArrayType arrayType = (AnnotatedArrayType) varType; - if (arrayType.getComponentType().hasAnnotation(Unique.class)) { - checker.reportError(tree, "unique.location.forbidden"); - } - } else if (tree.getType().getKind() == Tree.Kind.PARAMETERIZED_TYPE) { - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) varType; - for (AnnotatedTypeMirror atm : declaredType.getTypeArguments()) { - if (atm.hasAnnotation(Unique.class)) { - checker.reportError(tree, "unique.location.forbidden"); - } - } - } + } } - return super.visitVariable(tree, p); + } } + return super.visitVariable(tree, p); + } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - List initializers = tree.getInitializers(); - if (initializers != null && !initializers.isEmpty()) { - for (ExpressionTree exp : initializers) { - if (canBeLeaked(exp)) { - checker.reportError(exp, "unique.leaked"); - } - } + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + List initializers = tree.getInitializers(); + if (initializers != null && !initializers.isEmpty()) { + for (ExpressionTree exp : initializers) { + if (canBeLeaked(exp)) { + checker.reportError(exp, "unique.leaked"); } - return super.visitNewArray(tree, p); + } } + return super.visitNewArray(tree, p); + } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // @Unique is verified, so don't check this. - AnnotatedTypeMirror returnType = constructorType.getReturnType(); - if (returnType.hasAnnotation(atypeFactory.UNIQUE)) { - return; - } - - // Don't issue warnings about @LeakedToResult or (implicit) @MaybeLeaked on constructor - // results. - if (!returnType.hasAnnotation(atypeFactory.NON_LEAKED)) { - // TODO: the visitor should not change qualifiers. - // Possible problem from aliasing of `returnType`, but all tests pass. - returnType.replaceAnnotation(atypeFactory.NON_LEAKED); - } - - super.checkConstructorResult(constructorType, constructorElement); + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // @Unique is verified, so don't check this. + AnnotatedTypeMirror returnType = constructorType.getReturnType(); + if (returnType.hasAnnotation(atypeFactory.UNIQUE)) { + return; } - @Override - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { - if (isInUniqueConstructor()) { - // Check if a call to super() might create an alias: that - // happens when the parent's respective constructor is not @Unique. - AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(superCall); - if (!superResult.hasAnnotation(Unique.class)) { - checker.reportError(superCall, "unique.leaked"); - } - } + // Don't issue warnings about @LeakedToResult or (implicit) @MaybeLeaked on constructor + // results. + if (!returnType.hasAnnotation(atypeFactory.NON_LEAKED)) { + // TODO: the visitor should not change qualifiers. + // Possible problem from aliasing of `returnType`, but all tests pass. + returnType.replaceAnnotation(atypeFactory.NON_LEAKED); } - /** - * Returns true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new - * class expression. It checks whether the tree expression is unique by either checking for an - * explicit annotation or checking whether the class of the tree expression {@code exp} has type - * {@code @Unique} - * - * @param exp the Tree to check - * @return true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new - * class expression - */ - private boolean canBeLeaked(Tree exp) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(exp); - boolean isMethodInvocation = exp.getKind() == Tree.Kind.METHOD_INVOCATION; - boolean isNewClass = exp.getKind() == Tree.Kind.NEW_CLASS; - boolean isUniqueType = isUniqueClass(type) || type.hasExplicitAnnotation(Unique.class); - return isUniqueType && !isMethodInvocation && !isNewClass; + super.checkConstructorResult(constructorType, constructorElement); + } + + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + if (isInUniqueConstructor()) { + // Check if a call to super() might create an alias: that + // happens when the parent's respective constructor is not @Unique. + AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(superCall); + if (!superResult.hasAnnotation(Unique.class)) { + checker.reportError(superCall, "unique.leaked"); + } } + } - /** - * Return true if the class declaration for annotated type {@code type} has annotation - * {@code @Unique}. - * - * @param type the annotated type whose class must be checked - * @return boolean true if class is unique and false otherwise - */ - private boolean isUniqueClass(AnnotatedTypeMirror type) { - Element el = types.asElement(type.getUnderlyingType()); - if (el == null) { - return false; - } - AnnotationMirrorSet annoMirrors = atypeFactory.getDeclAnnotations(el); - if (annoMirrors == null) { - return false; - } - if (atypeFactory.containsSameByClass(annoMirrors, Unique.class)) { - return true; - } - return false; + /** + * Returns true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new + * class expression. It checks whether the tree expression is unique by either checking for an + * explicit annotation or checking whether the class of the tree expression {@code exp} has type + * {@code @Unique} + * + * @param exp the Tree to check + * @return true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new + * class expression + */ + private boolean canBeLeaked(Tree exp) { + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(exp); + boolean isMethodInvocation = exp.getKind() == Tree.Kind.METHOD_INVOCATION; + boolean isNewClass = exp.getKind() == Tree.Kind.NEW_CLASS; + boolean isUniqueType = isUniqueClass(type) || type.hasExplicitAnnotation(Unique.class); + return isUniqueType && !isMethodInvocation && !isNewClass; + } + + /** + * Return true if the class declaration for annotated type {@code type} has annotation + * {@code @Unique}. + * + * @param type the annotated type whose class must be checked + * @return boolean true if class is unique and false otherwise + */ + private boolean isUniqueClass(AnnotatedTypeMirror type) { + Element el = types.asElement(type.getUnderlyingType()); + if (el == null) { + return false; + } + AnnotationMirrorSet annoMirrors = atypeFactory.getDeclAnnotations(el); + if (annoMirrors == null) { + return false; } + if (atypeFactory.containsSameByClass(annoMirrors, Unique.class)) { + return true; + } + return false; + } - /** - * Returns true if the enclosing method is a constructor whose return type is annotated as - * {@code @Unique}. - * - * @return true if the enclosing method is a constructor whose return type is annotated as - * {@code @Unique} - */ - private boolean isInUniqueConstructor() { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - if (enclosingMethod == null) { - return false; // No enclosing method. - } - return TreeUtils.isConstructor(enclosingMethod) - && atypeFactory - .getAnnotatedType(enclosingMethod) - .getReturnType() - .hasAnnotation(Unique.class); + /** + * Returns true if the enclosing method is a constructor whose return type is annotated as + * {@code @Unique}. + * + * @return true if the enclosing method is a constructor whose return type is annotated as + * {@code @Unique} + */ + private boolean isInUniqueConstructor() { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + if (enclosingMethod == null) { + return false; // No enclosing method. } + return TreeUtils.isConstructor(enclosingMethod) + && atypeFactory + .getAnnotatedType(enclosingMethod) + .getReturnType() + .hasAnnotation(Unique.class); + } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java index b027f0bd004..29b9bcfe6fc 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java @@ -11,23 +11,23 @@ * analysis as provided by {@link CFAnalysis}. */ public class BaseAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory { + extends GenericAnnotatedTypeFactory { - public BaseAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { - super(checker, useFlow); + public BaseAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { + super(checker, useFlow); - // Every subclass must call postInit! - if (this.getClass() == BaseAnnotatedTypeFactory.class) { - this.postInit(); - } + // Every subclass must call postInit! + if (this.getClass() == BaseAnnotatedTypeFactory.class) { + this.postInit(); } + } - public BaseAnnotatedTypeFactory(BaseTypeChecker checker) { - this(checker, flowByDefault); - } + public BaseAnnotatedTypeFactory(BaseTypeChecker checker) { + this(checker, flowByDefault); + } - @Override - protected CFAnalysis createFlowAnalysis() { - return new CFAnalysis(checker, this); - } + @Override + protected CFAnalysis createFlowAnalysis() { + return new CFAnalysis(checker, this); + } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 56353deb5e6..2c4b6e09bcc 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -7,7 +7,24 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; - +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -31,26 +48,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; - /** * An abstract {@link SourceChecker} that provides a simple {@link * org.checkerframework.framework.source.SourceVisitor} implementation that type-checks assignments, @@ -93,835 +90,826 @@ */ public abstract class BaseTypeChecker extends SourceChecker { - @Override - public void initChecker() { - // initialize all checkers and share options as necessary - for (BaseTypeChecker checker : getSubcheckers()) { - // We need to add all options that are activated for the set of subcheckers to - // the individual checkers. - checker.addOptions(super.getOptions()); - // Each checker should "support" all possible lint options - otherwise - // subchecker A would complain about a lint option for subchecker B. - checker.setSupportedLintOptions(this.getSupportedLintOptions()); - - // initChecker validates the passed options, so call it after setting supported options - // and lints. - checker.initChecker(); - } - - if (!getSubcheckers().isEmpty()) { - messageStore = new TreeSet<>(this::compareCheckerMessages); - } - - super.initChecker(); - - warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); - } - - /** - * The full list of subcheckers that need to be run prior to this one, in the order they need to - * be run in. This list will only be non-empty for the one checker that runs all other - * subcheckers. Do not read this field directly. Instead, retrieve it via {@link - * #getSubcheckers}. - * - *

If the list still null when {@link #getSubcheckers} is called, then {@code - * getSubcheckers()} will call {@link #instantiateSubcheckers}. However, if the current object - * was itself instantiated by a prior call to instantiateSubcheckers, this field will have been - * initialized to an empty list before {@code getSubcheckers()} is called, thereby ensuring that - * this list is non-empty only for one checker. - */ - private @MonotonicNonNull List subcheckers = null; - - /** - * The list of subcheckers that are direct dependencies of this checker. This list will be - * non-empty for any checker that has at least one subchecker. - * - *

Does not need to be initialized to null or an empty list because it is always initialized - * via calls to {@link #instantiateSubcheckers}. - */ - // Set to non-null when subcheckers is. - private @MonotonicNonNull List immediateSubcheckers = null; - - /** Supported options for this checker. */ - private @MonotonicNonNull Set supportedOptions = null; - - /** Options passed to this checker. */ - private @MonotonicNonNull Map options = null; - - /** - * The list of suppress warnings prefixes supported by this checker or any of its subcheckers - * (including indirect subcheckers). Do not access this field directly; instead, use {@link - * #getSuppressWarningsPrefixesOfSubcheckers}. - */ - private @MonotonicNonNull Collection suppressWarningsPrefixesOfSubcheckers = null; - - /** True if -AwarnUnneededSuppressions was supplied on the command line. */ - // Not final because it is set in `init()`. - private boolean warnUnneededSuppressions; - - @Override - protected void setRoot(CompilationUnitTree newRoot) { - super.setRoot(newRoot); - if (parentChecker == null) { - // Only clear the path cache if this is the main checker. - treePathCacher.clear(); - } - } - - /** - * Returns the set of subchecker classes on which this checker depends. Returns an empty set if - * this checker does not depend on any others. - * - *

Subclasses should override this method to specify subcheckers. If they do so, they should - * call the super implementation of this method and add dependencies to the returned set so that - * checkers required for reflection resolution are included if reflection resolution is - * requested. - * - *

Each subchecker of this checker may also depend on other checkers. If this checker and one - * of its subcheckers both depend on a third checker, that checker will only be instantiated - * once. - * - *

Though each checker is run on a whole compilation unit before the next checker is run, - * error and warning messages are collected and sorted based on the location in the source file - * before being printed. (See {@link #printOrStoreMessage(Diagnostic.Kind, String, Tree, - * CompilationUnitTree)}.) - * - *

WARNING: Circular dependencies are not supported nor do checkers verify that their - * dependencies are not circular. Make sure no circular dependencies are created when overriding - * this method. (In other words, if checker A depends on checker B, checker B cannot depend on - * checker A.) - * - *

This method is protected so it can be overridden, but it should only be called internally - * by the BaseTypeChecker. - * - *

The BaseTypeChecker will not modify the set returned by this method, but clients that - * override the method do modify the set. - * - * @return the subchecker classes on which this checker depends; will be modified by callees in - * overriding methods - */ - // This is never looked up in, but it is iterated over (and added to, which does a lookup). - protected Set> getImmediateSubcheckerClasses() { - // This must return a modifiable set because clients modify it. - // Most checkers have 1 or fewer subcheckers. - // Use a LinkedHashSet for deterministic ordering. - LinkedHashSet> result = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); - if (shouldResolveReflection()) { - result.add(MethodValChecker.class); - } + @Override + public void initChecker() { + // initialize all checkers and share options as necessary + for (BaseTypeChecker checker : getSubcheckers()) { + // We need to add all options that are activated for the set of subcheckers to + // the individual checkers. + checker.addOptions(super.getOptions()); + // Each checker should "support" all possible lint options - otherwise + // subchecker A would complain about a lint option for subchecker B. + checker.setSupportedLintOptions(this.getSupportedLintOptions()); + + // initChecker validates the passed options, so call it after setting supported options + // and lints. + checker.initChecker(); + } + + if (!getSubcheckers().isEmpty()) { + messageStore = new TreeSet<>(this::compareCheckerMessages); + } + + super.initChecker(); + + warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); + } + + /** + * The full list of subcheckers that need to be run prior to this one, in the order they need to + * be run in. This list will only be non-empty for the one checker that runs all other + * subcheckers. Do not read this field directly. Instead, retrieve it via {@link #getSubcheckers}. + * + *

If the list still null when {@link #getSubcheckers} is called, then {@code getSubcheckers()} + * will call {@link #instantiateSubcheckers}. However, if the current object was itself + * instantiated by a prior call to instantiateSubcheckers, this field will have been initialized + * to an empty list before {@code getSubcheckers()} is called, thereby ensuring that this list is + * non-empty only for one checker. + */ + private @MonotonicNonNull List subcheckers = null; + + /** + * The list of subcheckers that are direct dependencies of this checker. This list will be + * non-empty for any checker that has at least one subchecker. + * + *

Does not need to be initialized to null or an empty list because it is always initialized + * via calls to {@link #instantiateSubcheckers}. + */ + // Set to non-null when subcheckers is. + private @MonotonicNonNull List immediateSubcheckers = null; + + /** Supported options for this checker. */ + private @MonotonicNonNull Set supportedOptions = null; + + /** Options passed to this checker. */ + private @MonotonicNonNull Map options = null; + + /** + * The list of suppress warnings prefixes supported by this checker or any of its subcheckers + * (including indirect subcheckers). Do not access this field directly; instead, use {@link + * #getSuppressWarningsPrefixesOfSubcheckers}. + */ + private @MonotonicNonNull Collection suppressWarningsPrefixesOfSubcheckers = null; + + /** True if -AwarnUnneededSuppressions was supplied on the command line. */ + // Not final because it is set in `init()`. + private boolean warnUnneededSuppressions; + + @Override + protected void setRoot(CompilationUnitTree newRoot) { + super.setRoot(newRoot); + if (parentChecker == null) { + // Only clear the path cache if this is the main checker. + treePathCacher.clear(); + } + } + + /** + * Returns the set of subchecker classes on which this checker depends. Returns an empty set if + * this checker does not depend on any others. + * + *

Subclasses should override this method to specify subcheckers. If they do so, they should + * call the super implementation of this method and add dependencies to the returned set so that + * checkers required for reflection resolution are included if reflection resolution is requested. + * + *

Each subchecker of this checker may also depend on other checkers. If this checker and one + * of its subcheckers both depend on a third checker, that checker will only be instantiated once. + * + *

Though each checker is run on a whole compilation unit before the next checker is run, error + * and warning messages are collected and sorted based on the location in the source file before + * being printed. (See {@link #printOrStoreMessage(Diagnostic.Kind, String, Tree, + * CompilationUnitTree)}.) + * + *

WARNING: Circular dependencies are not supported nor do checkers verify that their + * dependencies are not circular. Make sure no circular dependencies are created when overriding + * this method. (In other words, if checker A depends on checker B, checker B cannot depend on + * checker A.) + * + *

This method is protected so it can be overridden, but it should only be called internally by + * the BaseTypeChecker. + * + *

The BaseTypeChecker will not modify the set returned by this method, but clients that + * override the method do modify the set. + * + * @return the subchecker classes on which this checker depends; will be modified by callees in + * overriding methods + */ + // This is never looked up in, but it is iterated over (and added to, which does a lookup). + protected Set> getImmediateSubcheckerClasses() { + // This must return a modifiable set because clients modify it. + // Most checkers have 1 or fewer subcheckers. + // Use a LinkedHashSet for deterministic ordering. + LinkedHashSet> result = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); + if (shouldResolveReflection()) { + result.add(MethodValChecker.class); + } + return result; + } + + /** + * Returns whether or not reflection should be resolved. + * + * @return true if reflection should be resolved + */ + public boolean shouldResolveReflection() { + return hasOptionNoSubcheckers("resolveReflection"); + } + + /** An array containing just {@code BaseTypeChecker.class}. */ + private static final Class[] baseTypeCheckerClassArray = + new Class[] {BaseTypeChecker.class}; + + /** + * Returns the appropriate visitor that type-checks the compilation unit according to the type + * system rules. + * + *

This implementation uses the checker naming convention to create the appropriate visitor. If + * no visitor is found, it returns an instance of {@link BaseTypeVisitor}. It reflectively invokes + * the constructor that accepts this checker and the compilation unit tree (in that order) as + * arguments. + * + *

Subclasses have to override this method to create the appropriate visitor if they do not + * follow the checker naming convention. + * + * @return the type-checking visitor + */ + @Override + protected BaseTypeVisitor createSourceVisitor() { + // Try to reflectively load the visitor. + Class checkerClass = this.getClass(); + Object[] thisArray = new Object[] {this}; + while (checkerClass != BaseTypeChecker.class) { + BaseTypeVisitor result = + invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"), + baseTypeCheckerClassArray, + thisArray); + if (result != null) { return result; - } - - /** - * Returns whether or not reflection should be resolved. - * - * @return true if reflection should be resolved - */ - public boolean shouldResolveReflection() { - return hasOptionNoSubcheckers("resolveReflection"); - } - - /** An array containing just {@code BaseTypeChecker.class}. */ - private static final Class[] baseTypeCheckerClassArray = - new Class[] {BaseTypeChecker.class}; - - /** - * Returns the appropriate visitor that type-checks the compilation unit according to the type - * system rules. - * - *

This implementation uses the checker naming convention to create the appropriate visitor. - * If no visitor is found, it returns an instance of {@link BaseTypeVisitor}. It reflectively - * invokes the constructor that accepts this checker and the compilation unit tree (in that - * order) as arguments. - * - *

Subclasses have to override this method to create the appropriate visitor if they do not - * follow the checker naming convention. - * - * @return the type-checking visitor - */ - @Override - protected BaseTypeVisitor createSourceVisitor() { - // Try to reflectively load the visitor. - Class checkerClass = this.getClass(); - Object[] thisArray = new Object[] {this}; - while (checkerClass != BaseTypeChecker.class) { - BaseTypeVisitor result = - invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"), - baseTypeCheckerClassArray, - thisArray); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If a visitor couldn't be loaded reflectively, return the default. - return new BaseTypeVisitor(this); - } - - /** - * A public variant of {@link #createSourceVisitor}. Only use this if you know what you are - * doing. - * - * @return the type-checking visitor - */ - public BaseTypeVisitor createSourceVisitorPublic() { - return createSourceVisitor(); - } - - /** - * Returns the name of a class related to a given one, by replacing "Checker" or "Subchecker" by - * {@code replacement}. - * - * @param checkerClass the checker class - * @param replacement the string to replace "Checker" or "Subchecker" by - * @return the name of the related class - */ - @SuppressWarnings("signature") // string manipulation of @ClassGetName string - public static @ClassGetName String getRelatedClassName( - Class checkerClass, String replacement) { - return checkerClass - .getName() - .replace("Checker", replacement) - .replace("Subchecker", replacement); - } - - // ********************************************************************** - // Misc. methods - // ********************************************************************** - - /** Specify supported lint options for all type-checkers. */ - @Override - public Set getSupportedLintOptions() { - Set lintSet = new HashSet<>(super.getSupportedLintOptions()); - lintSet.add("cast"); - lintSet.add("cast:redundant"); - lintSet.add("cast:unsafe"); - - for (BaseTypeChecker checker : getSubcheckers()) { - lintSet.addAll(checker.getSupportedLintOptions()); - } - - return Collections.unmodifiableSet(lintSet); - } - - /** - * Invokes the constructor belonging to the class named by {@code name} having the given - * parameter types on the given arguments. Returns {@code null} if the class cannot be found. - * Otherwise, throws an exception if there is trouble with the constructor invocation. - * - * @param the type to which the constructor belongs - * @param name the name of the class to which the constructor belongs - * @param paramTypes the types of the constructor's parameters - * @param args the arguments on which to invoke the constructor - * @return the result of the constructor invocation on {@code args}, or null if the class does - * not exist - */ - @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse - public static @Nullable T invokeConstructorFor( - @ClassGetName String name, Class[] paramTypes, Object[] args) { - - // Load the class. - Class cls = null; - try { - cls = (Class) Class.forName(name); - } catch (Exception e) { - // no class is found, simply return null - return null; - } - - assert cls != null : "reflectively loading " + name + " failed"; - - // Invoke the constructor. - try { - Constructor ctor = cls.getConstructor(paramTypes); - return ctor.newInstance(args); - } catch (Throwable t) { - if (t instanceof InvocationTargetException) { - Throwable err = t.getCause(); - if (err instanceof UserError || err instanceof TypeSystemError) { - // Don't add more information about the constructor invocation. - throw (RuntimeException) err; - } - } else if (t instanceof NoSuchMethodException) { - // Note: it's possible that NoSuchMethodException was caused by - // `ctor.newInstance(args)`, if - // the constructor itself uses reflection. But this case is unlikely. - throw new TypeSystemError( - "Could not find constructor %s(%s)", - name, StringsPlume.join(", ", paramTypes)); - } - - Throwable cause; - String causeMessage; - if (t instanceof InvocationTargetException) { - cause = t.getCause(); - if (cause == null || cause.getMessage() == null) { - causeMessage = t.getMessage(); - } else if (t.getMessage() == null) { - causeMessage = cause.getMessage(); - } else { - causeMessage = t.getMessage() + ": " + cause.getMessage(); - } - } else { - cause = t; - causeMessage = (cause == null) ? "null" : cause.getMessage(); - } - throw new BugInCF( - cause, - "Error when invoking constructor %s(%s) on args %s; cause: %s", - name, - StringsPlume.join(", ", paramTypes), - Arrays.toString(args), - causeMessage); - } - } - - @Override - public BaseTypeVisitor getVisitor() { - return (BaseTypeVisitor) super.getVisitor(); - } - - /** - * Return the type factory associated with this checker. - * - * @return the type factory associated with this checker - */ - public GenericAnnotatedTypeFactory getTypeFactory() { - BaseTypeVisitor visitor = getVisitor(); - // Avoid NPE if this method is called during initialization. - if (visitor == null) { - throw new TypeSystemError("Called getTypeFactory() before initialization was complete"); - } - return visitor.getTypeFactory(); - } - - @Override - public AnnotationProvider getAnnotationProvider() { - return getTypeFactory(); - } - - /** - * Returns the requested subchecker. A checker of a given class can only be run once, so this - * returns the only such checker, or null if none was found. The caller must know the exact - * checker class to request. - * - * @param the class of the subchecker to return - * @param checkerClass the class of the subchecker to return - * @return the requested subchecker or null if not found - */ - @SuppressWarnings("unchecked") - public @Nullable T getSubchecker(Class checkerClass) { - for (BaseTypeChecker checker : immediateSubcheckers) { - if (checker.getClass() == checkerClass) { - return (T) checker; - } - } - - return null; - } - - /** - * Returns the type factory used by a subchecker. Returns null if no matching subchecker was - * found or if the type factory is null. The caller must know the exact checker class to - * request. - * - *

Because the visitor state is copied, call this method each time a subfactory is needed - * rather than store the returned subfactory in a field. - * - * @param subCheckerClass the class of the subchecker - * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} - * @return the type factory of the requested subchecker or null if not found - */ - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public > - @Nullable T getTypeFactoryOfSubcheckerOrNull( - Class subCheckerClass) { - return getTypeFactory().getTypeFactoryOfSubcheckerOrNull(subCheckerClass); - } - - /** - * Returns the unmodifiable list of immediate subcheckers of this checker. - * - *

Performs a depth first search for all checkers this checker depends on. The depth first - * search ensures that the collection has the correct order the checkers need to be run in. - * - *

Modifies the alreadyInitializedSubcheckerMap map by adding all recursively newly - * instantiated subcheckers' class objects and instances. It is necessary to use a map that - * preserves the order in which entries were inserted, such as LinkedHashMap or ArrayMap. - * - * @param alreadyInitializedSubcheckerMap subcheckers that have already been instantiated. Is - * modified by this method. - * @return the unmodifiable list of immediate subcheckers of this checker - */ - private List instantiateSubcheckers( - Map, BaseTypeChecker> - alreadyInitializedSubcheckerMap) { - Set> classesOfImmediateSubcheckers = - getImmediateSubcheckerClasses(); - if (classesOfImmediateSubcheckers.isEmpty()) { - return Collections.emptyList(); - } - - ArrayList immediateSubcheckers = - new ArrayList<>(classesOfImmediateSubcheckers.size()); - - for (Class subcheckerClass : classesOfImmediateSubcheckers) { - BaseTypeChecker subchecker = alreadyInitializedSubcheckerMap.get(subcheckerClass); - if (subchecker != null) { - // Add the already initialized subchecker to the list of immediate subcheckers so - // that this checker can refer to it. - immediateSubcheckers.add(subchecker); - continue; - } - - BaseTypeChecker instance; - try { - instance = subcheckerClass.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new BugInCF("Could not create an instance of " + subcheckerClass, e); - } - - instance.setProcessingEnvironment(this.processingEnv); - instance.treePathCacher = this.getTreePathCacher(); - // Prevent the new checker from storing non-immediate subcheckers - instance.subcheckers = Collections.emptyList(); - immediateSubcheckers.add(instance); - instance.immediateSubcheckers = - instance.instantiateSubcheckers(alreadyInitializedSubcheckerMap); - instance.setParentChecker(this); - alreadyInitializedSubcheckerMap.put(subcheckerClass, instance); - } - - return Collections.unmodifiableList(immediateSubcheckers); - } - - /** - * Get the list of all subcheckers (if any). via the instantiateSubcheckers method. This list is - * only non-empty for the one checker that runs all other subcheckers. These are recursively - * instantiated via instantiateSubcheckers the first time the method is called if subcheckers is - * null. Assumes all checkers run on the same thread. - * - * @return the list of all subcheckers (if any) - */ - public List getSubcheckers() { - if (subcheckers == null) { - // Instantiate the checkers this one depends on, if any. - Map, BaseTypeChecker> checkerMap = - new ArrayMap, BaseTypeChecker>(2); - - immediateSubcheckers = instantiateSubcheckers(checkerMap); - - subcheckers = Collections.unmodifiableList(new ArrayList<>(checkerMap.values())); - } - - return subcheckers; - } - - // AbstractTypeProcessor delegation - @Override - public void typeProcess(TypeElement element, TreePath tree) { - if (!getSubcheckers().isEmpty()) { - // TODO: I expected this to only be necessary if (parentChecker == null). - // However, the NestedAggregateChecker fails otherwise. - messageStore.clear(); - } - - // Errors (or other messages) issued via - // SourceChecker#message(Diagnostic.Kind, Object, String, Object...) - // are stored in messageStore until all checkers have processed this compilation unit. - // All other messages are printed immediately. This includes errors issued because the - // checker threw an exception. - - // In order to run the next checker on this compilation unit even if the previous issued - // errors, the next checker's errsOnLastExit needs to include all errors issued by previous - // checkers. - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Log log = Log.instance(context); - - int nerrorsOfAllPreviousCheckers = this.errsOnLastExit; - for (BaseTypeChecker subchecker : getSubcheckers()) { - subchecker.errsOnLastExit = nerrorsOfAllPreviousCheckers; - subchecker.messageStore = messageStore; - int errorsBeforeTypeChecking = log.nerrors; - - subchecker.typeProcess(element, tree); - - int errorsAfterTypeChecking = log.nerrors; - nerrorsOfAllPreviousCheckers += errorsAfterTypeChecking - errorsBeforeTypeChecking; - } - - this.errsOnLastExit = nerrorsOfAllPreviousCheckers; - super.typeProcess(element, tree); - - if (!getSubcheckers().isEmpty()) { - printStoredMessages(tree.getCompilationUnit()); - // Update errsOnLastExit to reflect the errors issued. - this.errsOnLastExit = log.nerrors; - } - } - - /** - * Like {@link SourceChecker#getSuppressWarningsPrefixes()}, but includes all prefixes supported - * by this checker or any of its subcheckers. Does not guarantee that the result is in any - * particular order. The result is immutable. - * - * @return the suppress warnings prefixes supported by this checker or any of its subcheckers - */ - public Collection getSuppressWarningsPrefixesOfSubcheckers() { - if (this.suppressWarningsPrefixesOfSubcheckers == null) { - Collection prefixes = getSuppressWarningsPrefixes(); - for (BaseTypeChecker subchecker : getSubcheckers()) { - prefixes.addAll(subchecker.getSuppressWarningsPrefixes()); - } - this.suppressWarningsPrefixesOfSubcheckers = ImmutableSet.copyOf(prefixes); - } - return this.suppressWarningsPrefixesOfSubcheckers; - } - - /** A cache for {@link #getUltimateParentChecker}. */ - private @MonotonicNonNull BaseTypeChecker ultimateParentChecker; - - /** - * Finds the ultimate parent checker of this checker. The ultimate parent checker is the checker - * that the user actually requested, i.e. the one with no parent. The ultimate parent might be - * this checker itself. - * - * @return the first checker in the parent checker chain with no parent checker of its own, - * i.e., the ultimate parent checker - */ - public BaseTypeChecker getUltimateParentChecker() { - if (ultimateParentChecker == null) { - ultimateParentChecker = this; - while (ultimateParentChecker.getParentChecker() instanceof BaseTypeChecker) { - ultimateParentChecker = (BaseTypeChecker) ultimateParentChecker.getParentChecker(); - } - } - - return ultimateParentChecker; - } - - /** - * {@inheritDoc} - * - *

This implementation collects needed warning suppressions for all subcheckers. - */ - @Override - protected void warnUnneededSuppressions() { - if (parentChecker != null) { - return; - } - - if (!warnUnneededSuppressions) { - return; - } - Set elementsWithSuppressedWarnings = - new HashSet<>(this.elementsWithSuppressedWarnings); - this.elementsWithSuppressedWarnings.clear(); - - Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); - Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); - for (BaseTypeChecker subChecker : subcheckers) { - elementsWithSuppressedWarnings.addAll(subChecker.elementsWithSuppressedWarnings); - subChecker.elementsWithSuppressedWarnings.clear(); - prefixes.addAll(subChecker.getSuppressWarningsPrefixes()); - errorKeys.addAll(subChecker.messagesProperties.stringPropertyNames()); - subChecker.getVisitor().treesWithSuppressWarnings.clear(); - } - warnUnneededSuppressions(elementsWithSuppressedWarnings, prefixes, errorKeys); - - getVisitor().treesWithSuppressWarnings.clear(); - } - - /** - * Stores all messages issued by this checker and its subcheckers for the current compilation - * unit. The messages are printed after all checkers have processed the current compilation - * unit. The purpose is to sort messages, grouping together all messages about a particular line - * of code. - * - *

If this checker has no subcheckers and is not a subchecker for any other checker, then - * messageStore is null and messages will be printed as they are issued by this checker. - */ - private @MonotonicNonNull TreeSet messageStore; - - /** - * If this is a compound checker or a subchecker of a compound checker, then the message is - * stored until all messages from all checkers for the compilation unit are issued. - * - *

Otherwise, it prints the message. - */ - @Override - protected void printOrStoreMessage( - Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { - assert this.currentRoot == root; - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - if (messageStore == null) { - super.printOrStoreMessage(kind, message, source, root, trace); + } + checkerClass = checkerClass.getSuperclass(); + } + + // If a visitor couldn't be loaded reflectively, return the default. + return new BaseTypeVisitor(this); + } + + /** + * A public variant of {@link #createSourceVisitor}. Only use this if you know what you are doing. + * + * @return the type-checking visitor + */ + public BaseTypeVisitor createSourceVisitorPublic() { + return createSourceVisitor(); + } + + /** + * Returns the name of a class related to a given one, by replacing "Checker" or "Subchecker" by + * {@code replacement}. + * + * @param checkerClass the checker class + * @param replacement the string to replace "Checker" or "Subchecker" by + * @return the name of the related class + */ + @SuppressWarnings("signature") // string manipulation of @ClassGetName string + public static @ClassGetName String getRelatedClassName( + Class checkerClass, String replacement) { + return checkerClass + .getName() + .replace("Checker", replacement) + .replace("Subchecker", replacement); + } + + // ********************************************************************** + // Misc. methods + // ********************************************************************** + + /** Specify supported lint options for all type-checkers. */ + @Override + public Set getSupportedLintOptions() { + Set lintSet = new HashSet<>(super.getSupportedLintOptions()); + lintSet.add("cast"); + lintSet.add("cast:redundant"); + lintSet.add("cast:unsafe"); + + for (BaseTypeChecker checker : getSubcheckers()) { + lintSet.addAll(checker.getSupportedLintOptions()); + } + + return Collections.unmodifiableSet(lintSet); + } + + /** + * Invokes the constructor belonging to the class named by {@code name} having the given parameter + * types on the given arguments. Returns {@code null} if the class cannot be found. Otherwise, + * throws an exception if there is trouble with the constructor invocation. + * + * @param the type to which the constructor belongs + * @param name the name of the class to which the constructor belongs + * @param paramTypes the types of the constructor's parameters + * @param args the arguments on which to invoke the constructor + * @return the result of the constructor invocation on {@code args}, or null if the class does not + * exist + */ + @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse + public static @Nullable T invokeConstructorFor( + @ClassGetName String name, Class[] paramTypes, Object[] args) { + + // Load the class. + Class cls = null; + try { + cls = (Class) Class.forName(name); + } catch (Exception e) { + // no class is found, simply return null + return null; + } + + assert cls != null : "reflectively loading " + name + " failed"; + + // Invoke the constructor. + try { + Constructor ctor = cls.getConstructor(paramTypes); + return ctor.newInstance(args); + } catch (Throwable t) { + if (t instanceof InvocationTargetException) { + Throwable err = t.getCause(); + if (err instanceof UserError || err instanceof TypeSystemError) { + // Don't add more information about the constructor invocation. + throw (RuntimeException) err; + } + } else if (t instanceof NoSuchMethodException) { + // Note: it's possible that NoSuchMethodException was caused by + // `ctor.newInstance(args)`, if + // the constructor itself uses reflection. But this case is unlikely. + throw new TypeSystemError( + "Could not find constructor %s(%s)", name, StringsPlume.join(", ", paramTypes)); + } + + Throwable cause; + String causeMessage; + if (t instanceof InvocationTargetException) { + cause = t.getCause(); + if (cause == null || cause.getMessage() == null) { + causeMessage = t.getMessage(); + } else if (t.getMessage() == null) { + causeMessage = cause.getMessage(); } else { - CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this, trace); - messageStore.add(checkerMessage); - } - } + causeMessage = t.getMessage() + ": " + cause.getMessage(); + } + } else { + cause = t; + causeMessage = (cause == null) ? "null" : cause.getMessage(); + } + throw new BugInCF( + cause, + "Error when invoking constructor %s(%s) on args %s; cause: %s", + name, + StringsPlume.join(", ", paramTypes), + Arrays.toString(args), + causeMessage); + } + } + + @Override + public BaseTypeVisitor getVisitor() { + return (BaseTypeVisitor) super.getVisitor(); + } + + /** + * Return the type factory associated with this checker. + * + * @return the type factory associated with this checker + */ + public GenericAnnotatedTypeFactory getTypeFactory() { + BaseTypeVisitor visitor = getVisitor(); + // Avoid NPE if this method is called during initialization. + if (visitor == null) { + throw new TypeSystemError("Called getTypeFactory() before initialization was complete"); + } + return visitor.getTypeFactory(); + } + + @Override + public AnnotationProvider getAnnotationProvider() { + return getTypeFactory(); + } + + /** + * Returns the requested subchecker. A checker of a given class can only be run once, so this + * returns the only such checker, or null if none was found. The caller must know the exact + * checker class to request. + * + * @param the class of the subchecker to return + * @param checkerClass the class of the subchecker to return + * @return the requested subchecker or null if not found + */ + @SuppressWarnings("unchecked") + public @Nullable T getSubchecker(Class checkerClass) { + for (BaseTypeChecker checker : immediateSubcheckers) { + if (checker.getClass() == checkerClass) { + return (T) checker; + } + } + + return null; + } + + /** + * Returns the type factory used by a subchecker. Returns null if no matching subchecker was found + * or if the type factory is null. The caller must know the exact checker class to request. + * + *

Because the visitor state is copied, call this method each time a subfactory is needed + * rather than store the returned subfactory in a field. + * + * @param subCheckerClass the class of the subchecker + * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} + * @return the type factory of the requested subchecker or null if not found + */ + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public > + @Nullable T getTypeFactoryOfSubcheckerOrNull( + Class subCheckerClass) { + return getTypeFactory().getTypeFactoryOfSubcheckerOrNull(subCheckerClass); + } + + /** + * Returns the unmodifiable list of immediate subcheckers of this checker. + * + *

Performs a depth first search for all checkers this checker depends on. The depth first + * search ensures that the collection has the correct order the checkers need to be run in. + * + *

Modifies the alreadyInitializedSubcheckerMap map by adding all recursively newly + * instantiated subcheckers' class objects and instances. It is necessary to use a map that + * preserves the order in which entries were inserted, such as LinkedHashMap or ArrayMap. + * + * @param alreadyInitializedSubcheckerMap subcheckers that have already been instantiated. Is + * modified by this method. + * @return the unmodifiable list of immediate subcheckers of this checker + */ + private List instantiateSubcheckers( + Map, BaseTypeChecker> alreadyInitializedSubcheckerMap) { + Set> classesOfImmediateSubcheckers = + getImmediateSubcheckerClasses(); + if (classesOfImmediateSubcheckers.isEmpty()) { + return Collections.emptyList(); + } + + ArrayList immediateSubcheckers = + new ArrayList<>(classesOfImmediateSubcheckers.size()); + + for (Class subcheckerClass : classesOfImmediateSubcheckers) { + BaseTypeChecker subchecker = alreadyInitializedSubcheckerMap.get(subcheckerClass); + if (subchecker != null) { + // Add the already initialized subchecker to the list of immediate subcheckers so + // that this checker can refer to it. + immediateSubcheckers.add(subchecker); + continue; + } + + BaseTypeChecker instance; + try { + instance = subcheckerClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new BugInCF("Could not create an instance of " + subcheckerClass, e); + } + + instance.setProcessingEnvironment(this.processingEnv); + instance.treePathCacher = this.getTreePathCacher(); + // Prevent the new checker from storing non-immediate subcheckers + instance.subcheckers = Collections.emptyList(); + immediateSubcheckers.add(instance); + instance.immediateSubcheckers = + instance.instantiateSubcheckers(alreadyInitializedSubcheckerMap); + instance.setParentChecker(this); + alreadyInitializedSubcheckerMap.put(subcheckerClass, instance); + } + + return Collections.unmodifiableList(immediateSubcheckers); + } + + /** + * Get the list of all subcheckers (if any). via the instantiateSubcheckers method. This list is + * only non-empty for the one checker that runs all other subcheckers. These are recursively + * instantiated via instantiateSubcheckers the first time the method is called if subcheckers is + * null. Assumes all checkers run on the same thread. + * + * @return the list of all subcheckers (if any) + */ + public List getSubcheckers() { + if (subcheckers == null) { + // Instantiate the checkers this one depends on, if any. + Map, BaseTypeChecker> checkerMap = + new ArrayMap, BaseTypeChecker>(2); + + immediateSubcheckers = instantiateSubcheckers(checkerMap); + + subcheckers = Collections.unmodifiableList(new ArrayList<>(checkerMap.values())); + } + + return subcheckers; + } + + // AbstractTypeProcessor delegation + @Override + public void typeProcess(TypeElement element, TreePath tree) { + if (!getSubcheckers().isEmpty()) { + // TODO: I expected this to only be necessary if (parentChecker == null). + // However, the NestedAggregateChecker fails otherwise. + messageStore.clear(); + } + + // Errors (or other messages) issued via + // SourceChecker#message(Diagnostic.Kind, Object, String, Object...) + // are stored in messageStore until all checkers have processed this compilation unit. + // All other messages are printed immediately. This includes errors issued because the + // checker threw an exception. + + // In order to run the next checker on this compilation unit even if the previous issued + // errors, the next checker's errsOnLastExit needs to include all errors issued by previous + // checkers. + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Log log = Log.instance(context); + + int nerrorsOfAllPreviousCheckers = this.errsOnLastExit; + for (BaseTypeChecker subchecker : getSubcheckers()) { + subchecker.errsOnLastExit = nerrorsOfAllPreviousCheckers; + subchecker.messageStore = messageStore; + int errorsBeforeTypeChecking = log.nerrors; + + subchecker.typeProcess(element, tree); + + int errorsAfterTypeChecking = log.nerrors; + nerrorsOfAllPreviousCheckers += errorsAfterTypeChecking - errorsBeforeTypeChecking; + } + + this.errsOnLastExit = nerrorsOfAllPreviousCheckers; + super.typeProcess(element, tree); + + if (!getSubcheckers().isEmpty()) { + printStoredMessages(tree.getCompilationUnit()); + // Update errsOnLastExit to reflect the errors issued. + this.errsOnLastExit = log.nerrors; + } + } + + /** + * Like {@link SourceChecker#getSuppressWarningsPrefixes()}, but includes all prefixes supported + * by this checker or any of its subcheckers. Does not guarantee that the result is in any + * particular order. The result is immutable. + * + * @return the suppress warnings prefixes supported by this checker or any of its subcheckers + */ + public Collection getSuppressWarningsPrefixesOfSubcheckers() { + if (this.suppressWarningsPrefixesOfSubcheckers == null) { + Collection prefixes = getSuppressWarningsPrefixes(); + for (BaseTypeChecker subchecker : getSubcheckers()) { + prefixes.addAll(subchecker.getSuppressWarningsPrefixes()); + } + this.suppressWarningsPrefixesOfSubcheckers = ImmutableSet.copyOf(prefixes); + } + return this.suppressWarningsPrefixesOfSubcheckers; + } + + /** A cache for {@link #getUltimateParentChecker}. */ + private @MonotonicNonNull BaseTypeChecker ultimateParentChecker; + + /** + * Finds the ultimate parent checker of this checker. The ultimate parent checker is the checker + * that the user actually requested, i.e. the one with no parent. The ultimate parent might be + * this checker itself. + * + * @return the first checker in the parent checker chain with no parent checker of its own, i.e., + * the ultimate parent checker + */ + public BaseTypeChecker getUltimateParentChecker() { + if (ultimateParentChecker == null) { + ultimateParentChecker = this; + while (ultimateParentChecker.getParentChecker() instanceof BaseTypeChecker) { + ultimateParentChecker = (BaseTypeChecker) ultimateParentChecker.getParentChecker(); + } + } + + return ultimateParentChecker; + } + + /** + * {@inheritDoc} + * + *

This implementation collects needed warning suppressions for all subcheckers. + */ + @Override + protected void warnUnneededSuppressions() { + if (parentChecker != null) { + return; + } + + if (!warnUnneededSuppressions) { + return; + } + Set elementsWithSuppressedWarnings = + new HashSet<>(this.elementsWithSuppressedWarnings); + this.elementsWithSuppressedWarnings.clear(); + + Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); + Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); + for (BaseTypeChecker subChecker : subcheckers) { + elementsWithSuppressedWarnings.addAll(subChecker.elementsWithSuppressedWarnings); + subChecker.elementsWithSuppressedWarnings.clear(); + prefixes.addAll(subChecker.getSuppressWarningsPrefixes()); + errorKeys.addAll(subChecker.messagesProperties.stringPropertyNames()); + subChecker.getVisitor().treesWithSuppressWarnings.clear(); + } + warnUnneededSuppressions(elementsWithSuppressedWarnings, prefixes, errorKeys); + + getVisitor().treesWithSuppressWarnings.clear(); + } + + /** + * Stores all messages issued by this checker and its subcheckers for the current compilation + * unit. The messages are printed after all checkers have processed the current compilation unit. + * The purpose is to sort messages, grouping together all messages about a particular line of + * code. + * + *

If this checker has no subcheckers and is not a subchecker for any other checker, then + * messageStore is null and messages will be printed as they are issued by this checker. + */ + private @MonotonicNonNull TreeSet messageStore; + + /** + * If this is a compound checker or a subchecker of a compound checker, then the message is stored + * until all messages from all checkers for the compilation unit are issued. + * + *

Otherwise, it prints the message. + */ + @Override + protected void printOrStoreMessage( + Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { + assert this.currentRoot == root; + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + if (messageStore == null) { + super.printOrStoreMessage(kind, message, source, root, trace); + } else { + CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this, trace); + messageStore.add(checkerMessage); + } + } + + /** + * Prints error messages for this checker and all subcheckers such that the errors are ordered by + * line and column number and then by checker. (See {@link #compareCheckerMessages} for more + * precise order.) + * + * @param unit current compilation unit + */ + private void printStoredMessages(CompilationUnitTree unit) { + for (CheckerMessage msg : messageStore) { + super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit, msg.trace); + } + } + + /** Represents a message (e.g., an error message) issued by a checker. */ + private static class CheckerMessage { + /** The severity of the message. */ + final Diagnostic.Kind kind; + + /** The message itself. */ + final String message; + + /** The source code that the message is about. */ + final @InternedDistinct Tree source; + + /** Stores the stack trace when the message is created. */ + final StackTraceElement[] trace; /** - * Prints error messages for this checker and all subcheckers such that the errors are ordered - * by line and column number and then by checker. (See {@link #compareCheckerMessages} for more - * precise order.) - * - * @param unit current compilation unit + * The checker that issued this message. The compound checker that depends on this checker uses + * this to sort the messages. */ - private void printStoredMessages(CompilationUnitTree unit) { - for (CheckerMessage msg : messageStore) { - super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit, msg.trace); - } - } - - /** Represents a message (e.g., an error message) issued by a checker. */ - private static class CheckerMessage { - /** The severity of the message. */ - final Diagnostic.Kind kind; - - /** The message itself. */ - final String message; - - /** The source code that the message is about. */ - final @InternedDistinct Tree source; - - /** Stores the stack trace when the message is created. */ - final StackTraceElement[] trace; - - /** - * The checker that issued this message. The compound checker that depends on this checker - * uses this to sort the messages. - */ - final @InternedDistinct BaseTypeChecker checker; - - /** - * Create a new CheckerMessage. - * - * @param kind kind of diagnostic, for example, error or warning - * @param message error message that needs to be printed - * @param source tree element causing the error - * @param checker the type-checker in use - * @param trace the stack trace when the message is created - */ - private CheckerMessage( - Diagnostic.Kind kind, - String message, - @FindDistinct Tree source, - @FindDistinct BaseTypeChecker checker, - StackTraceElement[] trace) { - this.kind = kind; - this.message = message; - this.source = source; - this.checker = checker; - this.trace = trace; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - CheckerMessage that = (CheckerMessage) o; - return this.kind == that.kind - && this.message.equals(that.message) - && this.source == that.source - && this.checker == that.checker; - } - - @Override - public int hashCode() { - return Objects.hash(kind, message, source, checker); - } - - @Override - public String toString() { - return "CheckerMessage{" - + "kind=" - + kind - + ", checker=" - + checker.getClass().getSimpleName() - + ", message='" - + message - + '\'' - + ", source=" - + source - + '}'; - } - } + final @InternedDistinct BaseTypeChecker checker; /** - * Compares two {@link CheckerMessage}s. Compares first by position at which the error will be - * printed, then by kind of message, then by the message string, and finally by the order in - * which the checkers run. + * Create a new CheckerMessage. * - * @param o1 the first CheckerMessage - * @param o2 the second CheckerMessage - * @return a negative integer, zero, or a positive integer if the first CheckerMessage is less - * than, equal to, or greater than the second + * @param kind kind of diagnostic, for example, error or warning + * @param message error message that needs to be printed + * @param source tree element causing the error + * @param checker the type-checker in use + * @param trace the stack trace when the message is created */ - private int compareCheckerMessages(CheckerMessage o1, CheckerMessage o2) { - int byPos = InternalUtils.compareDiagnosticPosition(o1.source, o2.source); - if (byPos != 0) { - return byPos; - } - - int kind = o1.kind.compareTo(o2.kind); - if (kind != 0) { - return kind; - } - - int msgcmp = o1.message.compareTo(o2.message); - if (msgcmp == 0) { - // If the two messages are identical so far, it doesn't matter - // from which checker they came. - return 0; - } - - // Sort by order in which the checkers are run. (All the subcheckers, - // followed by the checker.) - List subcheckers = BaseTypeChecker.this.getSubcheckers(); - int o1Index = subcheckers.indexOf(o1.checker); - int o2Index = subcheckers.indexOf(o2.checker); - if (o1Index == -1) { - o1Index = subcheckers.size(); - } - if (o2Index == -1) { - o2Index = subcheckers.size(); - } - int checkercmp = Integer.compare(o1Index, o2Index); - if (checkercmp == 0) { - // If the two messages are from the same checker, sort by message. - return msgcmp; - } else { - return checkercmp; - } - } - - @Override - public void typeProcessingOver() { - for (BaseTypeChecker checker : getSubcheckers()) { - checker.typeProcessingOver(); - } - - super.typeProcessingOver(); - } - - @Override - public Set getSupportedOptions() { - if (supportedOptions == null) { - Set options = new HashSet<>(); - options.addAll(super.getSupportedOptions()); - - for (BaseTypeChecker checker : getSubcheckers()) { - options.addAll(checker.getSupportedOptions()); - } - - options.addAll( - expandCFOptions( - Arrays.asList(this.getClass()), - options.toArray(new String[options.size()]))); - - supportedOptions = Collections.unmodifiableSet(options); - } - return supportedOptions; + private CheckerMessage( + Diagnostic.Kind kind, + String message, + @FindDistinct Tree source, + @FindDistinct BaseTypeChecker checker, + StackTraceElement[] trace) { + this.kind = kind; + this.message = message; + this.source = source; + this.checker = checker; + this.trace = trace; } @Override - public Map getOptions() { - if (this.options == null) { - Map options = new HashMap<>(super.getOptions()); - - for (BaseTypeChecker checker : getSubcheckers()) { - options.putAll(checker.getOptions()); - } - this.options = Collections.unmodifiableMap(options); - } - - return this.options; - } - - /** - * Like {@link #getOptions}, but only includes options provided to this checker. Does not - * include those passed to subcheckers. - * - * @return the active options for this checker, not including those passed to subcheckers - */ - public Map getOptionsNoSubcheckers() { - return super.getOptions(); - } - - /** - * Like {@link #hasOption}, but checks whether the given option is provided to this checker. - * Does not consider those passed to subcheckers. - * - * @param name the name of the option to check - * @return true if the option name was provided to this checker, false otherwise - */ - public final boolean hasOptionNoSubcheckers(String name) { - return getOptionsNoSubcheckers().containsKey(name); - } + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - /** - * Return a list of additional stub files to be treated as if they had been written in a - * {@code @StubFiles} annotation. - * - * @return stub files to be treated as if they had been written in a {@code @StubFiles} - * annotation - */ - public List getExtraStubFiles() { - return Collections.emptyList(); + CheckerMessage that = (CheckerMessage) o; + return this.kind == that.kind + && this.message.equals(that.message) + && this.source == that.source + && this.checker == that.checker; } @Override - protected Object processErrorMessageArg(Object arg) { - if (arg instanceof Collection) { - Collection carg = (Collection) arg; - return CollectionsPlume.mapList(this::processErrorMessageArg, carg); - } else if (arg instanceof AnnotationMirror && getTypeFactory() != null) { - return getTypeFactory() - .getAnnotationFormatter() - .formatAnnotationMirror((AnnotationMirror) arg); - } else { - return super.processErrorMessageArg(arg); - } + public int hashCode() { + return Objects.hash(kind, message, source, checker); } @Override - protected boolean shouldAddShutdownHook() { - if (super.shouldAddShutdownHook() || getTypeFactory().getCFGVisualizer() != null) { - return true; - } - for (BaseTypeChecker checker : getSubcheckers()) { - if (checker.getTypeFactory().getCFGVisualizer() != null) { - return true; - } - } - return false; - } - - @Override - protected void shutdownHook() { - super.shutdownHook(); - - CFGVisualizer viz = getTypeFactory().getCFGVisualizer(); - if (viz != null) { - viz.shutdown(); - } - - for (BaseTypeChecker checker : getSubcheckers()) { - viz = checker.getTypeFactory().getCFGVisualizer(); - if (viz != null) { - viz.shutdown(); - } - } - } + public String toString() { + return "CheckerMessage{" + + "kind=" + + kind + + ", checker=" + + checker.getClass().getSimpleName() + + ", message='" + + message + + '\'' + + ", source=" + + source + + '}'; + } + } + + /** + * Compares two {@link CheckerMessage}s. Compares first by position at which the error will be + * printed, then by kind of message, then by the message string, and finally by the order in which + * the checkers run. + * + * @param o1 the first CheckerMessage + * @param o2 the second CheckerMessage + * @return a negative integer, zero, or a positive integer if the first CheckerMessage is less + * than, equal to, or greater than the second + */ + private int compareCheckerMessages(CheckerMessage o1, CheckerMessage o2) { + int byPos = InternalUtils.compareDiagnosticPosition(o1.source, o2.source); + if (byPos != 0) { + return byPos; + } + + int kind = o1.kind.compareTo(o2.kind); + if (kind != 0) { + return kind; + } + + int msgcmp = o1.message.compareTo(o2.message); + if (msgcmp == 0) { + // If the two messages are identical so far, it doesn't matter + // from which checker they came. + return 0; + } + + // Sort by order in which the checkers are run. (All the subcheckers, + // followed by the checker.) + List subcheckers = BaseTypeChecker.this.getSubcheckers(); + int o1Index = subcheckers.indexOf(o1.checker); + int o2Index = subcheckers.indexOf(o2.checker); + if (o1Index == -1) { + o1Index = subcheckers.size(); + } + if (o2Index == -1) { + o2Index = subcheckers.size(); + } + int checkercmp = Integer.compare(o1Index, o2Index); + if (checkercmp == 0) { + // If the two messages are from the same checker, sort by message. + return msgcmp; + } else { + return checkercmp; + } + } + + @Override + public void typeProcessingOver() { + for (BaseTypeChecker checker : getSubcheckers()) { + checker.typeProcessingOver(); + } + + super.typeProcessingOver(); + } + + @Override + public Set getSupportedOptions() { + if (supportedOptions == null) { + Set options = new HashSet<>(); + options.addAll(super.getSupportedOptions()); + + for (BaseTypeChecker checker : getSubcheckers()) { + options.addAll(checker.getSupportedOptions()); + } + + options.addAll( + expandCFOptions( + Arrays.asList(this.getClass()), options.toArray(new String[options.size()]))); + + supportedOptions = Collections.unmodifiableSet(options); + } + return supportedOptions; + } + + @Override + public Map getOptions() { + if (this.options == null) { + Map options = new HashMap<>(super.getOptions()); + + for (BaseTypeChecker checker : getSubcheckers()) { + options.putAll(checker.getOptions()); + } + this.options = Collections.unmodifiableMap(options); + } + + return this.options; + } + + /** + * Like {@link #getOptions}, but only includes options provided to this checker. Does not include + * those passed to subcheckers. + * + * @return the active options for this checker, not including those passed to subcheckers + */ + public Map getOptionsNoSubcheckers() { + return super.getOptions(); + } + + /** + * Like {@link #hasOption}, but checks whether the given option is provided to this checker. Does + * not consider those passed to subcheckers. + * + * @param name the name of the option to check + * @return true if the option name was provided to this checker, false otherwise + */ + public final boolean hasOptionNoSubcheckers(String name) { + return getOptionsNoSubcheckers().containsKey(name); + } + + /** + * Return a list of additional stub files to be treated as if they had been written in a + * {@code @StubFiles} annotation. + * + * @return stub files to be treated as if they had been written in a {@code @StubFiles} annotation + */ + public List getExtraStubFiles() { + return Collections.emptyList(); + } + + @Override + protected Object processErrorMessageArg(Object arg) { + if (arg instanceof Collection) { + Collection carg = (Collection) arg; + return CollectionsPlume.mapList(this::processErrorMessageArg, carg); + } else if (arg instanceof AnnotationMirror && getTypeFactory() != null) { + return getTypeFactory() + .getAnnotationFormatter() + .formatAnnotationMirror((AnnotationMirror) arg); + } else { + return super.processErrorMessageArg(arg); + } + } + + @Override + protected boolean shouldAddShutdownHook() { + if (super.shouldAddShutdownHook() || getTypeFactory().getCFGVisualizer() != null) { + return true; + } + for (BaseTypeChecker checker : getSubcheckers()) { + if (checker.getTypeFactory().getCFGVisualizer() != null) { + return true; + } + } + return false; + } + + @Override + protected void shutdownHook() { + super.shutdownHook(); + + CFGVisualizer viz = getTypeFactory().getCFGVisualizer(); + if (viz != null) { + viz.shutdown(); + } + + for (BaseTypeChecker checker : getSubcheckers()) { + viz = checker.getTypeFactory().getCFGVisualizer(); + if (viz != null) { + viz.shutdown(); + } + } + } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java index 5c609a25e4f..a8446ace691 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -10,7 +10,15 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; - +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.TypeUseLocation; @@ -37,17 +45,6 @@ import org.plumelib.util.ArrayMap; import org.plumelib.util.IPair; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; - /** * A visitor to validate the types in a tree. * @@ -63,733 +60,719 @@ * BaseTypeVisitor#visitVariable}. */ public class BaseTypeValidator extends AnnotatedTypeScanner implements TypeValidator { - /** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */ - protected boolean isValid = true; - - /** Should the primary annotation on the top level type be checked? */ - protected boolean checkTopLevelDeclaredOrPrimitiveType = true; - - /** BaseTypeChecker. */ - protected final BaseTypeChecker checker; - - /** BaseTypeVisitor. */ - protected final BaseTypeVisitor visitor; - - /** AnnotatedTypeFactory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The qualifer hierarchy. */ - protected final QualifierHierarchy qualHierarchy; - - // TODO: clean up coupling between components - public BaseTypeValidator( - BaseTypeChecker checker, - BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { - this.checker = checker; - this.visitor = visitor; - this.atypeFactory = atypeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - } - - /** - * Validate the type against the given tree. This method both issues error messages and also - * returns a boolean value. - * - *

This is the entry point to the type validator. Neither this method nor visit should be - * called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}. - * - *

This method is only called on top-level types, but it validates the entire type including - * components of a compound type. Subclasses should override this only if there is special-case - * behavior that should be performed only on top-level types. - * - * @param type the type to validate - * @param tree the tree from which the type originated. If the tree is a method tree, {@code - * type} is its return type. If the tree is a variable tree, {@code type} is the variable's - * type. - * @return true if the type is valid - */ - @Override - public boolean isValid(AnnotatedTypeMirror type, Tree tree) { - List diagMessages = isValidStructurally(type); - if (!diagMessages.isEmpty()) { - for (DiagMessage d : diagMessages) { - checker.report(tree, d); - } - return false; - } - this.isValid = true; - this.checkTopLevelDeclaredOrPrimitiveType = - shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); - visit(type, tree); - return this.isValid; - } - - /** - * Should the top-level declared or primitive type be checked? - * - *

If {@code type} is not a declared or primitive type, then this method returns true. - * - *

Top-level type is not checked if tree is a local variable or an expression tree. - * - * @param type the AnnotatedTypeMirror being validated - * @param tree a Tree whose type is {@code type} - * @return whether or not the top-level type should be checked, if {@code type} is a declared or - * primitive type. - */ - protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( - AnnotatedTypeMirror type, Tree tree) { - if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) { - return true; - } - return !TreeUtils.isLocalVariable(tree) - && (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree)); - } - - /** - * Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list - * of failures. If successful, returns an empty list. The method will never return failures for - * a valid type, but might not catch all invalid types. - * - *

This method ensures that the type is structurally or lexically well-formed, but it does - * not check whether the annotations are semantically sensible. Subclasses should generally - * override visit methods such as {@link #visitDeclared} rather than this method. - * - *

Currently, this implementation checks the following (subclasses can extend this behavior): - * - *

    - *
  1. There should not be multiple annotations from the same qualifier hierarchy. - *
  2. There should not be more annotations than the width of the QualifierHierarchy. - *
  3. If the type is not a type variable, then the number of annotations should be the same - * as the width of the QualifierHierarchy. - *
  4. These properties should also hold recursively for component types of arrays and for - * bounds of type variables and wildcards. - *
- * - * This does not test whether the Java type is relevant, because by the time this method is - * called, the type includes some non-programmer-written annotations. - * - * @param type the type to test - * @return list of reasons the type is invalid, or empty list if the type is valid - */ - protected List isValidStructurally(AnnotatedTypeMirror type) { - SimpleAnnotatedTypeScanner, Void> scanner = - new SimpleAnnotatedTypeScanner<>( - (atm, p) -> isTopLevelValidType(atm), - DiagMessage::mergeLists, - Collections.emptyList()); - return scanner.visit(type, null); - } - - /** - * Checks every property listed in {@link #isValidStructurally}, but only for the top level - * type. If successful, returns an empty list. If not successful, returns diagnostics. - * - * @param type the type to be checked - * @return the diagnostics indicating failure, or an empty list if successful - */ - // This method returns a singleton or empyty list. Its return type is List rather than - // DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects - // a list. - protected List isTopLevelValidType(AnnotatedTypeMirror type) { - // multiple annotations from the same hierarchy - AnnotationMirrorSet annotations = type.getAnnotations(); - AnnotationMirrorSet seenTops = new AnnotationMirrorSet(); - for (AnnotationMirror anno : annotations) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - if (AnnotationUtils.containsSame(seenTops, top)) { - return Collections.singletonList( - DiagMessage.error("type.invalid.conflicting.annos", annotations, type)); - } - seenTops.add(top); - } - - boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type); - - // wrong number of annotations - if (!canHaveEmptyAnnotationSet && seenTops.size() < qualHierarchy.getWidth()) { - return Collections.singletonList( - DiagMessage.error("type.invalid.too.few.annotations", annotations, type)); - } - - // success - return Collections.emptyList(); + /** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */ + protected boolean isValid = true; + + /** Should the primary annotation on the top level type be checked? */ + protected boolean checkTopLevelDeclaredOrPrimitiveType = true; + + /** BaseTypeChecker. */ + protected final BaseTypeChecker checker; + + /** BaseTypeVisitor. */ + protected final BaseTypeVisitor visitor; + + /** AnnotatedTypeFactory. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** The qualifer hierarchy. */ + protected final QualifierHierarchy qualHierarchy; + + // TODO: clean up coupling between components + public BaseTypeValidator( + BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { + this.checker = checker; + this.visitor = visitor; + this.atypeFactory = atypeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + } + + /** + * Validate the type against the given tree. This method both issues error messages and also + * returns a boolean value. + * + *

This is the entry point to the type validator. Neither this method nor visit should be + * called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}. + * + *

This method is only called on top-level types, but it validates the entire type including + * components of a compound type. Subclasses should override this only if there is special-case + * behavior that should be performed only on top-level types. + * + * @param type the type to validate + * @param tree the tree from which the type originated. If the tree is a method tree, {@code type} + * is its return type. If the tree is a variable tree, {@code type} is the variable's type. + * @return true if the type is valid + */ + @Override + public boolean isValid(AnnotatedTypeMirror type, Tree tree) { + List diagMessages = isValidStructurally(type); + if (!diagMessages.isEmpty()) { + for (DiagMessage d : diagMessages) { + checker.report(tree, d); + } + return false; } - - protected void reportValidityResult( - @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { - checker.reportError(p, errorType, type.getAnnotations(), type.toString()); - isValid = false; + this.isValid = true; + this.checkTopLevelDeclaredOrPrimitiveType = + shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); + visit(type, tree); + return this.isValid; + } + + /** + * Should the top-level declared or primitive type be checked? + * + *

If {@code type} is not a declared or primitive type, then this method returns true. + * + *

Top-level type is not checked if tree is a local variable or an expression tree. + * + * @param type the AnnotatedTypeMirror being validated + * @param tree a Tree whose type is {@code type} + * @return whether or not the top-level type should be checked, if {@code type} is a declared or + * primitive type. + */ + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( + AnnotatedTypeMirror type, Tree tree) { + if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) { + return true; + } + return !TreeUtils.isLocalVariable(tree) + && (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree)); + } + + /** + * Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list + * of failures. If successful, returns an empty list. The method will never return failures for a + * valid type, but might not catch all invalid types. + * + *

This method ensures that the type is structurally or lexically well-formed, but it does not + * check whether the annotations are semantically sensible. Subclasses should generally override + * visit methods such as {@link #visitDeclared} rather than this method. + * + *

Currently, this implementation checks the following (subclasses can extend this behavior): + * + *

    + *
  1. There should not be multiple annotations from the same qualifier hierarchy. + *
  2. There should not be more annotations than the width of the QualifierHierarchy. + *
  3. If the type is not a type variable, then the number of annotations should be the same as + * the width of the QualifierHierarchy. + *
  4. These properties should also hold recursively for component types of arrays and for + * bounds of type variables and wildcards. + *
+ * + * This does not test whether the Java type is relevant, because by the time this method is + * called, the type includes some non-programmer-written annotations. + * + * @param type the type to test + * @return list of reasons the type is invalid, or empty list if the type is valid + */ + protected List isValidStructurally(AnnotatedTypeMirror type) { + SimpleAnnotatedTypeScanner, Void> scanner = + new SimpleAnnotatedTypeScanner<>( + (atm, p) -> isTopLevelValidType(atm), DiagMessage::mergeLists, Collections.emptyList()); + return scanner.visit(type, null); + } + + /** + * Checks every property listed in {@link #isValidStructurally}, but only for the top level type. + * If successful, returns an empty list. If not successful, returns diagnostics. + * + * @param type the type to be checked + * @return the diagnostics indicating failure, or an empty list if successful + */ + // This method returns a singleton or empyty list. Its return type is List rather than + // DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects + // a list. + protected List isTopLevelValidType(AnnotatedTypeMirror type) { + // multiple annotations from the same hierarchy + AnnotationMirrorSet annotations = type.getAnnotations(); + AnnotationMirrorSet seenTops = new AnnotationMirrorSet(); + for (AnnotationMirror anno : annotations) { + AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); + if (AnnotationUtils.containsSame(seenTops, top)) { + return Collections.singletonList( + DiagMessage.error("type.invalid.conflicting.annos", annotations, type)); + } + seenTops.add(top); } - /** - * Like {@link #reportValidityResult}, but the type is printed in the error message without - * annotations. This method would print "annotation @NonNull is not permitted on type int", - * whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on - * type @NonNull int". In addition, when the underlying type is a compound type such as - * {@code @Bad List}, the erased type will be used, i.e., "{@code List}" will print - * instead of "{@code @Bad List}". - */ - protected void reportValidityResultOnUnannotatedType( - @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { - TypeMirror underlying = - TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType()); - checker.reportError(p, errorType, type.getAnnotations(), underlying.toString()); - isValid = false; - } - - /** - * Most errors reported by this class are of the form type.invalid. This method reports when the - * bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective - * annotations on the upper bound are supertypes of those on the lower bounds for all - * hierarchies. To ensure that this subtlety is not lost on users, we report - * "bound.type.incompatible" and print the bounds along with the invalid type rather than a - * "type.invalid". - * - * @param type the type with invalid bounds - * @param tree where to report the error - */ - protected void reportInvalidBounds(AnnotatedTypeMirror type, Tree tree) { - final String label; - final AnnotatedTypeMirror upperBound; - final AnnotatedTypeMirror lowerBound; - - switch (type.getKind()) { - case TYPEVAR: - label = "type parameter"; - upperBound = ((AnnotatedTypeVariable) type).getUpperBound(); - lowerBound = ((AnnotatedTypeVariable) type).getLowerBound(); - break; - - case WILDCARD: - label = "wildcard"; - upperBound = ((AnnotatedWildcardType) type).getExtendsBound(); - lowerBound = ((AnnotatedWildcardType) type).getSuperBound(); - break; - - default: - throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree); - } + boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type); - checker.reportError( - tree, - "bound.type.incompatible", - label, - type.toString(), - upperBound.toString(true), - lowerBound.toString(true)); - isValid = false; + // wrong number of annotations + if (!canHaveEmptyAnnotationSet && seenTops.size() < qualHierarchy.getWidth()) { + return Collections.singletonList( + DiagMessage.error("type.invalid.too.few.annotations", annotations, type)); } - protected void reportInvalidType(AnnotatedTypeMirror type, Tree p) { - reportValidityResult("type.invalid", type, p); + // success + return Collections.emptyList(); + } + + protected void reportValidityResult( + @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { + checker.reportError(p, errorType, type.getAnnotations(), type.toString()); + isValid = false; + } + + /** + * Like {@link #reportValidityResult}, but the type is printed in the error message without + * annotations. This method would print "annotation @NonNull is not permitted on type int", + * whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on + * type @NonNull int". In addition, when the underlying type is a compound type such as + * {@code @Bad List}, the erased type will be used, i.e., "{@code List}" will print + * instead of "{@code @Bad List}". + */ + protected void reportValidityResultOnUnannotatedType( + @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { + TypeMirror underlying = + TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType()); + checker.reportError(p, errorType, type.getAnnotations(), underlying.toString()); + isValid = false; + } + + /** + * Most errors reported by this class are of the form type.invalid. This method reports when the + * bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective + * annotations on the upper bound are supertypes of those on the lower bounds for all hierarchies. + * To ensure that this subtlety is not lost on users, we report "bound.type.incompatible" and + * print the bounds along with the invalid type rather than a "type.invalid". + * + * @param type the type with invalid bounds + * @param tree where to report the error + */ + protected void reportInvalidBounds(AnnotatedTypeMirror type, Tree tree) { + final String label; + final AnnotatedTypeMirror upperBound; + final AnnotatedTypeMirror lowerBound; + + switch (type.getKind()) { + case TYPEVAR: + label = "type parameter"; + upperBound = ((AnnotatedTypeVariable) type).getUpperBound(); + lowerBound = ((AnnotatedTypeVariable) type).getLowerBound(); + break; + + case WILDCARD: + label = "wildcard"; + upperBound = ((AnnotatedWildcardType) type).getExtendsBound(); + lowerBound = ((AnnotatedWildcardType) type).getSuperBound(); + break; + + default: + throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree); } - /** - * Report an "annotations.on.use" error for the given type and tree. - * - * @param type the type with invalid annotations - * @param p the tree where to report the error - */ - protected void reportInvalidAnnotationsOnUse(AnnotatedTypeMirror type, Tree p) { - reportValidityResultOnUnannotatedType("type.invalid.annotations.on.use", type, p); + checker.reportError( + tree, + "bound.type.incompatible", + label, + type.toString(), + upperBound.toString(true), + lowerBound.toString(true)); + isValid = false; + } + + protected void reportInvalidType(AnnotatedTypeMirror type, Tree p) { + reportValidityResult("type.invalid", type, p); + } + + /** + * Report an "annotations.on.use" error for the given type and tree. + * + * @param type the type with invalid annotations + * @param p the tree where to report the error + */ + protected void reportInvalidAnnotationsOnUse(AnnotatedTypeMirror type, Tree p) { + reportValidityResultOnUnannotatedType("type.invalid.annotations.on.use", type, p); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - - boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement()); - if (type.containsUninferredTypeArguments()) { - if (!atypeFactory.ignoreUninferredTypeArguments) { - // TODO: document the logic here. - isValid = true; - } - return null; - } - - if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) { - // Ensure that type use is a subtype of the element type - // isValidUse determines the erasure of the types. - - AnnotationMirrorSet bounds = - atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement()); + if (type.containsUninferredTypeArguments()) { + if (!atypeFactory.ignoreUninferredTypeArguments) { + // TODO: document the logic here. + isValid = true; + } + return null; + } - AnnotatedDeclaredType elemType = type.deepCopy(); - elemType.clearAnnotations(); - elemType.addAnnotations(bounds); + if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) { + // Ensure that type use is a subtype of the element type + // isValidUse determines the erasure of the types. - if (!visitor.isValidUse(elemType, type, tree)) { - reportInvalidAnnotationsOnUse(type, tree); - } - } - // Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called, - // the type isn't the top level, so always do the check. - checkTopLevelDeclaredOrPrimitiveType = true; - - if (TreeUtils.isClassTree(tree)) { - visitedNodes.put(type, null); - visitClassTypeParameters(type, (ClassTree) tree); - return null; - } + AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); - /* - * Try to reconstruct the ParameterizedTypeTree from the given tree. - * TODO: there has to be a nicer way to do this... - */ - IPair p = - extractParameterizedTypeTree(tree, type); - ParameterizedTypeTree typeArgTree = p.first; - type = p.second; - - if (typeArgTree == null) { - return super.visitDeclared(type, tree); - } // else - - // We put this here because we don't want to put it in visitedNodes before calling - // super (in the else branch) because that would cause the super implementation - // to detect that we've already visited type and to immediately return. - visitedNodes.put(type, null); - - // We have a ParameterizedTypeTree -> visit it. - - visitParameterizedType(type, typeArgTree); - - /* - * Instead of calling super with the unchanged "tree", adapt the - * second argument to be the corresponding type argument tree. This - * ensures that the first and second parameter to this method always - * correspond. visitDeclared is the only method that had this - * problem. - */ - List tatypes = type.getTypeArguments(); - - if (tatypes == null) { - return null; - } + AnnotatedDeclaredType elemType = type.deepCopy(); + elemType.clearAnnotations(); + elemType.addAnnotations(bounds); - // May be zero for a "diamond" (inferred type args in constructor invocation). - int numTypeArgs = typeArgTree.getTypeArguments().size(); - if (numTypeArgs != 0) { - // TODO: this should be an equality, but in the past it failed with: - // daikon/Debug.java; message: size mismatch for type arguments: - // @NonNull Object and Class - // but I didn't manage to reduce it to a test case. - assert tatypes.size() <= numTypeArgs || skipChecks - : "size mismatch for type arguments: " + type + " and " + typeArgTree; - - for (int i = 0; i < tatypes.size(); ++i) { - scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i)); - } - } + if (!visitor.isValidUse(elemType, type, tree)) { + reportInvalidAnnotationsOnUse(type, tree); + } + } + // Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called, + // the type isn't the top level, so always do the check. + checkTopLevelDeclaredOrPrimitiveType = true; + + if (TreeUtils.isClassTree(tree)) { + visitedNodes.put(type, null); + visitClassTypeParameters(type, (ClassTree) tree); + return null; + } - // Don't call the super version, because it creates a mismatch - // between the first and second parameters. - // return super.visitDeclared(type, tree); + /* + * Try to reconstruct the ParameterizedTypeTree from the given tree. + * TODO: there has to be a nicer way to do this... + */ + IPair p = + extractParameterizedTypeTree(tree, type); + ParameterizedTypeTree typeArgTree = p.first; + type = p.second; + + if (typeArgTree == null) { + return super.visitDeclared(type, tree); + } // else + + // We put this here because we don't want to put it in visitedNodes before calling + // super (in the else branch) because that would cause the super implementation + // to detect that we've already visited type and to immediately return. + visitedNodes.put(type, null); + + // We have a ParameterizedTypeTree -> visit it. + + visitParameterizedType(type, typeArgTree); + + /* + * Instead of calling super with the unchanged "tree", adapt the + * second argument to be the corresponding type argument tree. This + * ensures that the first and second parameter to this method always + * correspond. visitDeclared is the only method that had this + * problem. + */ + List tatypes = type.getTypeArguments(); - return null; + if (tatypes == null) { + return null; } - /** - * Visits the type parameters of a class tree. - * - * @param type type of {@code tree} - * @param tree a class tree - */ - protected void visitClassTypeParameters(AnnotatedDeclaredType type, ClassTree tree) { - for (int i = 0, size = type.getTypeArguments().size(); i < size; i++) { - AnnotatedTypeVariable typeParameter = - (AnnotatedTypeVariable) type.getTypeArguments().get(i); - TypeParameterTree typeParameterTree = tree.getTypeParameters().get(i); - scan(typeParameter, typeParameterTree); - } + // May be zero for a "diamond" (inferred type args in constructor invocation). + int numTypeArgs = typeArgTree.getTypeArguments().size(); + if (numTypeArgs != 0) { + // TODO: this should be an equality, but in the past it failed with: + // daikon/Debug.java; message: size mismatch for type arguments: + // @NonNull Object and Class + // but I didn't manage to reduce it to a test case. + assert tatypes.size() <= numTypeArgs || skipChecks + : "size mismatch for type arguments: " + type + " and " + typeArgTree; + + for (int i = 0; i < tatypes.size(); ++i) { + scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i)); + } } - /** - * Visits type parameter bounds. - * - * @param typeParameter type of {@code typeParameterTree} - * @param typeParameterTree a type parameter tree - */ - protected void visitTypeParameterBounds( - AnnotatedTypeVariable typeParameter, TypeParameterTree typeParameterTree) { - List boundTrees = typeParameterTree.getBounds(); - if (boundTrees.size() == 1) { - scan(typeParameter.getUpperBound(), boundTrees.get(0)); - } else if (boundTrees.size() == 0) { - // The upper bound is implicitly Object - scan(typeParameter.getUpperBound(), typeParameterTree); + // Don't call the super version, because it creates a mismatch + // between the first and second parameters. + // return super.visitDeclared(type, tree); + + return null; + } + + /** + * Visits the type parameters of a class tree. + * + * @param type type of {@code tree} + * @param tree a class tree + */ + protected void visitClassTypeParameters(AnnotatedDeclaredType type, ClassTree tree) { + for (int i = 0, size = type.getTypeArguments().size(); i < size; i++) { + AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type.getTypeArguments().get(i); + TypeParameterTree typeParameterTree = tree.getTypeParameters().get(i); + scan(typeParameter, typeParameterTree); + } + } + + /** + * Visits type parameter bounds. + * + * @param typeParameter type of {@code typeParameterTree} + * @param typeParameterTree a type parameter tree + */ + protected void visitTypeParameterBounds( + AnnotatedTypeVariable typeParameter, TypeParameterTree typeParameterTree) { + List boundTrees = typeParameterTree.getBounds(); + if (boundTrees.size() == 1) { + scan(typeParameter.getUpperBound(), boundTrees.get(0)); + } else if (boundTrees.size() == 0) { + // The upper bound is implicitly Object + scan(typeParameter.getUpperBound(), typeParameterTree); + } else { + AnnotatedIntersectionType intersectionType = + (AnnotatedIntersectionType) typeParameter.getUpperBound(); + for (int j = 0; j < intersectionType.getBounds().size(); j++) { + scan(intersectionType.getBounds().get(j), boundTrees.get(j)); + } + } + } + + /** + * If {@code tree} has a {@link ParameterizedTypeTree}, then the tree and its type is returned. + * Otherwise null and {@code type} are returned. + * + * @param tree tree to search + * @param type type to return if no {@code ParameterizedTypeTree} is found + * @return if {@code tree} has a {@code ParameterizedTypeTree}, then returns the tree and its + * type. Otherwise, returns null and {@code type}. + */ + private IPair<@Nullable ParameterizedTypeTree, AnnotatedDeclaredType> + extractParameterizedTypeTree(Tree tree, AnnotatedDeclaredType type) { + ParameterizedTypeTree typeargtree = null; + + switch (tree.getKind()) { + case VARIABLE: + Tree lt = ((VariableTree) tree).getType(); + if (lt instanceof ParameterizedTypeTree) { + typeargtree = (ParameterizedTypeTree) lt; } else { - AnnotatedIntersectionType intersectionType = - (AnnotatedIntersectionType) typeParameter.getUpperBound(); - for (int j = 0; j < intersectionType.getBounds().size(); j++) { - scan(intersectionType.getBounds().get(j), boundTrees.get(j)); - } - } + // System.out.println("Found a: " + lt); + } + break; + case PARAMETERIZED_TYPE: + typeargtree = (ParameterizedTypeTree) tree; + break; + case NEW_CLASS: + NewClassTree nct = (NewClassTree) tree; + ExpressionTree nctid = nct.getIdentifier(); + if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) { + typeargtree = (ParameterizedTypeTree) nctid; + /* + * This is quite tricky... for anonymous class instantiations, + * the type at this point has no type arguments. By doing the + * following, we get the type arguments again. + */ + type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree); + } + break; + case ANNOTATED_TYPE: + AnnotatedTypeTree tr = (AnnotatedTypeTree) tree; + ExpressionTree undtr = tr.getUnderlyingType(); + if (undtr instanceof ParameterizedTypeTree) { + typeargtree = (ParameterizedTypeTree) undtr; + } else if (undtr instanceof IdentifierTree) { + // @Something D -> Nothing to do + } else { + // TODO: add more test cases to ensure that nested types are + // handled correctly, + // e.g. @Nullable() List<@Nullable Object>[][] + IPair p = + extractParameterizedTypeTree(undtr, type); + typeargtree = p.first; + type = p.second; + } + break; + case IDENTIFIER: + case ARRAY_TYPE: + case NEW_ARRAY: + case MEMBER_SELECT: + case UNBOUNDED_WILDCARD: + case EXTENDS_WILDCARD: + case SUPER_WILDCARD: + case TYPE_PARAMETER: + // Nothing to do. + break; + case METHOD: + // If a MethodTree is passed, it's just the return type that is validated. + // See BaseTypeVisitor#validateTypeOf. + MethodTree methodTree = (MethodTree) tree; + if (methodTree.getReturnType() instanceof ParameterizedTypeTree) { + typeargtree = (ParameterizedTypeTree) methodTree.getReturnType(); + } + break; + default: + // The parameterized type is the result of some expression tree. + // No need to do anything further. + break; } - /** - * If {@code tree} has a {@link ParameterizedTypeTree}, then the tree and its type is returned. - * Otherwise null and {@code type} are returned. - * - * @param tree tree to search - * @param type type to return if no {@code ParameterizedTypeTree} is found - * @return if {@code tree} has a {@code ParameterizedTypeTree}, then returns the tree and its - * type. Otherwise, returns null and {@code type}. - */ - private IPair<@Nullable ParameterizedTypeTree, AnnotatedDeclaredType> - extractParameterizedTypeTree(Tree tree, AnnotatedDeclaredType type) { - ParameterizedTypeTree typeargtree = null; - - switch (tree.getKind()) { - case VARIABLE: - Tree lt = ((VariableTree) tree).getType(); - if (lt instanceof ParameterizedTypeTree) { - typeargtree = (ParameterizedTypeTree) lt; - } else { - // System.out.println("Found a: " + lt); - } - break; - case PARAMETERIZED_TYPE: - typeargtree = (ParameterizedTypeTree) tree; - break; - case NEW_CLASS: - NewClassTree nct = (NewClassTree) tree; - ExpressionTree nctid = nct.getIdentifier(); - if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) { - typeargtree = (ParameterizedTypeTree) nctid; - /* - * This is quite tricky... for anonymous class instantiations, - * the type at this point has no type arguments. By doing the - * following, we get the type arguments again. - */ - type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree); - } - break; - case ANNOTATED_TYPE: - AnnotatedTypeTree tr = (AnnotatedTypeTree) tree; - ExpressionTree undtr = tr.getUnderlyingType(); - if (undtr instanceof ParameterizedTypeTree) { - typeargtree = (ParameterizedTypeTree) undtr; - } else if (undtr instanceof IdentifierTree) { - // @Something D -> Nothing to do - } else { - // TODO: add more test cases to ensure that nested types are - // handled correctly, - // e.g. @Nullable() List<@Nullable Object>[][] - IPair p = - extractParameterizedTypeTree(undtr, type); - typeargtree = p.first; - type = p.second; - } - break; - case IDENTIFIER: - case ARRAY_TYPE: - case NEW_ARRAY: - case MEMBER_SELECT: - case UNBOUNDED_WILDCARD: - case EXTENDS_WILDCARD: - case SUPER_WILDCARD: - case TYPE_PARAMETER: - // Nothing to do. - break; - case METHOD: - // If a MethodTree is passed, it's just the return type that is validated. - // See BaseTypeVisitor#validateTypeOf. - MethodTree methodTree = (MethodTree) tree; - if (methodTree.getReturnType() instanceof ParameterizedTypeTree) { - typeargtree = (ParameterizedTypeTree) methodTree.getReturnType(); - } - break; - default: - // The parameterized type is the result of some expression tree. - // No need to do anything further. - break; - } + return IPair.of(typeargtree, type); + } - return IPair.of(typeargtree, type); + @Override + @SuppressWarnings( + "signature:argument.type.incompatible") // PrimitiveType.toString(): @PrimitiveType + public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { + if (!checkTopLevelDeclaredOrPrimitiveType + || checker.shouldSkipUses(type.getUnderlyingType().toString())) { + return super.visitPrimitive(type, tree); } - @Override - @SuppressWarnings( - "signature:argument.type.incompatible") // PrimitiveType.toString(): @PrimitiveType - public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { - if (!checkTopLevelDeclaredOrPrimitiveType - || checker.shouldSkipUses(type.getUnderlyingType().toString())) { - return super.visitPrimitive(type, tree); - } - - if (!visitor.isValidUse(type, tree)) { - reportInvalidAnnotationsOnUse(type, tree); - } - - return super.visitPrimitive(type, tree); + if (!visitor.isValidUse(type, tree)) { + reportInvalidAnnotationsOnUse(type, tree); } - @Override - public Void visitArray(AnnotatedArrayType type, Tree tree) { - // TODO: is there already or add a helper method - // to determine the non-array component type - AnnotatedTypeMirror comp = type; - do { - comp = ((AnnotatedArrayType) comp).getComponentType(); - } while (comp.getKind() == TypeKind.ARRAY); - - if (comp.getKind() == TypeKind.DECLARED - && checker.shouldSkipUses( - ((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) { - return super.visitArray(type, tree); - } - - if (!visitor.isValidUse(type, tree)) { - reportInvalidAnnotationsOnUse(type, tree); - } + return super.visitPrimitive(type, tree); + } + + @Override + public Void visitArray(AnnotatedArrayType type, Tree tree) { + // TODO: is there already or add a helper method + // to determine the non-array component type + AnnotatedTypeMirror comp = type; + do { + comp = ((AnnotatedArrayType) comp).getComponentType(); + } while (comp.getKind() == TypeKind.ARRAY); + + if (comp.getKind() == TypeKind.DECLARED + && checker.shouldSkipUses(((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) { + return super.visitArray(type, tree); + } - return super.visitArray(type, tree); + if (!visitor.isValidUse(type, tree)) { + reportInvalidAnnotationsOnUse(type, tree); } - /** - * Checks that the annotations on the type arguments supplied to a type or a method invocation - * are within the bounds of the type variables as declared, and issues the - * "type.argument.type.incompatible" error if they are not. - * - * @param type the type to check - * @param tree the type's tree - */ - protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) { - // System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s%n", type, - // tree); + return super.visitArray(type, tree); + } + + /** + * Checks that the annotations on the type arguments supplied to a type or a method invocation are + * within the bounds of the type variables as declared, and issues the + * "type.argument.type.incompatible" error if they are not. + * + * @param type the type to check + * @param tree the type's tree + */ + protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) { + // System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s%n", type, + // tree); + + if (TreeUtils.isDiamondTree(tree)) { + return null; + } - if (TreeUtils.isDiamondTree(tree)) { - return null; - } + TypeElement element = (TypeElement) type.getUnderlyingType().asElement(); + if (checker.shouldSkipUses(element)) { + return null; + } - TypeElement element = (TypeElement) type.getUnderlyingType().asElement(); - if (checker.shouldSkipUses(element)) { - return null; - } + AnnotatedDeclaredType capturedType = + (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type); + List bounds = + atypeFactory.typeVariablesFromUse(capturedType, element); - AnnotatedDeclaredType capturedType = - (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type); - List bounds = - atypeFactory.typeVariablesFromUse(capturedType, element); - - visitor.checkTypeArguments( - tree, - bounds, - capturedType.getTypeArguments(), - tree.getTypeArguments(), - element.getSimpleName(), - element.getTypeParameters()); - - @SuppressWarnings( - "interning:not.interned") // applyCaptureConversion returns the passed type if type - // does not have wildcards. - boolean hasCapturedTypeVariables = capturedType != type; - if (!hasCapturedTypeVariables) { - return null; - } + visitor.checkTypeArguments( + tree, + bounds, + capturedType.getTypeArguments(), + tree.getTypeArguments(), + element.getSimpleName(), + element.getTypeParameters()); - // Check that the extends bound of the captured type variable is a subtype of the - // extends bound of the wildcard. - int numTypeArgs = capturedType.getTypeArguments().size(); - // First create a mapping from captured type variable to its wildcard. - Map typeVarToWildcard = - ArrayMap.newArrayMapOrHashMap(numTypeArgs); - for (int i = 0; i < numTypeArgs; i++) { - AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); - if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType()) - && type.getTypeArguments().get(i).getKind() == TypeKind.WILDCARD) { - AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; - AnnotatedWildcardType wildcard = - (AnnotatedWildcardType) type.getTypeArguments().get(i); - typeVarToWildcard.put(capturedTypeVar.getUnderlyingType(), wildcard); - } - } + @SuppressWarnings( + "interning:not.interned") // applyCaptureConversion returns the passed type if type + // does not have wildcards. + boolean hasCapturedTypeVariables = capturedType != type; + if (!hasCapturedTypeVariables) { + return null; + } - for (int i = 0; i < numTypeArgs; i++) { - if (type.getTypeArguments().get(i).getKind() != TypeKind.WILDCARD) { - continue; - } - AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i); - if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())) { - AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; - // Substitute the captured type variables with their wildcards. Without - // this, the isSubtype check crashes because wildcards aren't comparable - // with type variables. - AnnotatedTypeMirror captureTypeVarUB = - atypeFactory - .getTypeVarSubstitutor() - .substituteWithoutCopyingTypeArguments( - typeVarToWildcard, capturedTypeVar.getUpperBound()); - if (!atypeFactory - .getTypeHierarchy() - .isSubtype(captureTypeVarUB, wildcard.getExtendsBound())) { - // For most captured type variables, this will trivially hold, as capturing - // incorporated the extends bound of the wildcard into the upper bound of the - // type variable. - // This will fail if the bound and the wildcard have generic types and there is - // no appropriate GLB. - // This issues an error for types that cannot be satisfied, because the two - // bounds have contradictory requirements. - checker.reportError( - tree.getTypeArguments().get(i), - "type.argument.type.incompatible", - element.getTypeParameters().get(i), - element.getSimpleName(), - wildcard.getExtendsBound(), - capturedTypeVar.getUpperBound()); - } - } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { - // If the super bound of the wildcard is the same as the upper bound of the - // type parameter, then javac uses the bound rather than creating a fresh - // type variable. - // (See https://bugs.openjdk.org/browse/JDK-8054309.) - // In this case, the Checker Framework uses the annotations on the super - // bound of the wildcard and ignores the annotations on the extends bound. - // For example, Set<@1 ? super @2 Object> will collapse into Set<@2 Object>. - // So, issue a warning if the annotations on the extends bound are not the - // same as the annotations on the super bound. - if (!(atypeFactory - .getTypeHierarchy() - .isSubtypeShallowEffective( - wildcard.getSuperBound(), wildcard.getExtendsBound()) - && atypeFactory - .getTypeHierarchy() - .isSubtypeShallowEffective( - wildcard.getExtendsBound(), wildcard.getSuperBound()))) { - checker.reportError( - tree.getTypeArguments().get(i), - "type.invalid.super.wildcard", - wildcard.getExtendsBound(), - wildcard.getSuperBound()); - } - } - } + // Check that the extends bound of the captured type variable is a subtype of the + // extends bound of the wildcard. + int numTypeArgs = capturedType.getTypeArguments().size(); + // First create a mapping from captured type variable to its wildcard. + Map typeVarToWildcard = + ArrayMap.newArrayMapOrHashMap(numTypeArgs); + for (int i = 0; i < numTypeArgs; i++) { + AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); + if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType()) + && type.getTypeArguments().get(i).getKind() == TypeKind.WILDCARD) { + AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; + AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i); + typeVarToWildcard.put(capturedTypeVar.getUnderlyingType(), wildcard); + } + } - return null; + for (int i = 0; i < numTypeArgs; i++) { + if (type.getTypeArguments().get(i).getKind() != TypeKind.WILDCARD) { + continue; + } + AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); + AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i); + if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())) { + AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; + // Substitute the captured type variables with their wildcards. Without + // this, the isSubtype check crashes because wildcards aren't comparable + // with type variables. + AnnotatedTypeMirror captureTypeVarUB = + atypeFactory + .getTypeVarSubstitutor() + .substituteWithoutCopyingTypeArguments( + typeVarToWildcard, capturedTypeVar.getUpperBound()); + if (!atypeFactory + .getTypeHierarchy() + .isSubtype(captureTypeVarUB, wildcard.getExtendsBound())) { + // For most captured type variables, this will trivially hold, as capturing + // incorporated the extends bound of the wildcard into the upper bound of the + // type variable. + // This will fail if the bound and the wildcard have generic types and there is + // no appropriate GLB. + // This issues an error for types that cannot be satisfied, because the two + // bounds have contradictory requirements. + checker.reportError( + tree.getTypeArguments().get(i), + "type.argument.type.incompatible", + element.getTypeParameters().get(i), + element.getSimpleName(), + wildcard.getExtendsBound(), + capturedTypeVar.getUpperBound()); + } + } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { + // If the super bound of the wildcard is the same as the upper bound of the + // type parameter, then javac uses the bound rather than creating a fresh + // type variable. + // (See https://bugs.openjdk.org/browse/JDK-8054309.) + // In this case, the Checker Framework uses the annotations on the super + // bound of the wildcard and ignores the annotations on the extends bound. + // For example, Set<@1 ? super @2 Object> will collapse into Set<@2 Object>. + // So, issue a warning if the annotations on the extends bound are not the + // same as the annotations on the super bound. + if (!(atypeFactory + .getTypeHierarchy() + .isSubtypeShallowEffective(wildcard.getSuperBound(), wildcard.getExtendsBound()) + && atypeFactory + .getTypeHierarchy() + .isSubtypeShallowEffective(wildcard.getExtendsBound(), wildcard.getSuperBound()))) { + checker.reportError( + tree.getTypeArguments().get(i), + "type.invalid.super.wildcard", + wildcard.getExtendsBound(), + wildcard.getSuperBound()); + } + } } - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } + return null; + } - if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) { - reportInvalidBounds(type, tree); - } - AnnotatedTypeVariable useOfTypeVar = type.asUse(); - if (tree instanceof TypeParameterTree) { - TypeParameterTree typeParameterTree = (TypeParameterTree) tree; - visitedNodes.put(useOfTypeVar, defaultResult); - visitTypeParameterBounds(useOfTypeVar, typeParameterTree); - visitedNodes.put(useOfTypeVar, defaultResult); - return null; - } - return super.visitTypeVariable(useOfTypeVar, tree); + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - @Override - public Void visitWildcard(AnnotatedWildcardType type, Tree tree) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - - if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) { - reportInvalidBounds(type, tree); - } + if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) { + reportInvalidBounds(type, tree); + } + AnnotatedTypeVariable useOfTypeVar = type.asUse(); + if (tree instanceof TypeParameterTree) { + TypeParameterTree typeParameterTree = (TypeParameterTree) tree; + visitedNodes.put(useOfTypeVar, defaultResult); + visitTypeParameterBounds(useOfTypeVar, typeParameterTree); + visitedNodes.put(useOfTypeVar, defaultResult); + return null; + } + return super.visitTypeVariable(useOfTypeVar, tree); + } - validateWildCardTargetLocation(type, tree); - return super.visitWildcard(type, tree); + @Override + public Void visitWildcard(AnnotatedWildcardType type, Tree tree) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - /** - * Returns true if the effective annotations on the upperBound are above (or equal to) those on - * the lowerBound. - * - * @param upperBound the upper bound to check - * @param lowerBound the lower bound to check - * @return true if the effective annotations on the upperBound are above (or equal to) those on - * the lowerBound - */ - public boolean areBoundsValid(AnnotatedTypeMirror upperBound, AnnotatedTypeMirror lowerBound) { - AnnotationMirrorSet upperBoundAnnos = - AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, upperBound); - AnnotationMirrorSet lowerBoundAnnos = - AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, lowerBound); - - if (upperBoundAnnos.size() == lowerBoundAnnos.size()) { - return atypeFactory - .getTypeHierarchy() - .isSubtypeShallowEffective(lowerBound, upperBound); - } else { - // When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will - // be reported as invalid. Therefore, we do not do any other comparisons nor do we - // report - // a bound. - return true; - } + if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) { + reportInvalidBounds(type, tree); } - /** - * Validate if qualifiers on wildcard are permitted by {@link - * org.checkerframework.framework.qual.TargetLocations}. Report an error if the actual use of - * this annotation is not listed in the declared TypeUseLocations in this meta-annotation. - * - * @param type the type to check - * @param tree the tree of this type - */ - protected void validateWildCardTargetLocation(AnnotatedWildcardType type, Tree tree) { - if (visitor.ignoreTargetLocations) { - return; - } + validateWildCardTargetLocation(type, tree); + return super.visitWildcard(type, tree); + } + + /** + * Returns true if the effective annotations on the upperBound are above (or equal to) those on + * the lowerBound. + * + * @param upperBound the upper bound to check + * @param lowerBound the lower bound to check + * @return true if the effective annotations on the upperBound are above (or equal to) those on + * the lowerBound + */ + public boolean areBoundsValid(AnnotatedTypeMirror upperBound, AnnotatedTypeMirror lowerBound) { + AnnotationMirrorSet upperBoundAnnos = + AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, upperBound); + AnnotationMirrorSet lowerBoundAnnos = + AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, lowerBound); + + if (upperBoundAnnos.size() == lowerBoundAnnos.size()) { + return atypeFactory.getTypeHierarchy().isSubtypeShallowEffective(lowerBound, upperBound); + } else { + // When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will + // be reported as invalid. Therefore, we do not do any other comparisons nor do we + // report + // a bound. + return true; + } + } + + /** + * Validate if qualifiers on wildcard are permitted by {@link + * org.checkerframework.framework.qual.TargetLocations}. Report an error if the actual use of this + * annotation is not listed in the declared TypeUseLocations in this meta-annotation. + * + * @param type the type to check + * @param tree the tree of this type + */ + protected void validateWildCardTargetLocation(AnnotatedWildcardType type, Tree tree) { + if (visitor.ignoreTargetLocations) { + return; + } - for (AnnotationMirror am : type.getSuperBound().getAnnotations()) { - List locations = - visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means - // that the qualifier can be written on any type use. - // Otherwise, for a valid use of qualifier on the super bound, that qualifier must - // declare one of these four type-use locations in the @TargetLocations meta-annotation. - List lowerLocations = - Arrays.asList( - TypeUseLocation.ALL, - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.IMPLICIT_LOWER_BOUND, - TypeUseLocation.EXPLICIT_LOWER_BOUND); - if (locations == null || locations.stream().anyMatch(lowerLocations::contains)) { - continue; - } - - checker.reportError( - tree, - "type.invalid.annotations.on.location", - type.getSuperBound().getAnnotations().toString(), - "SUPER_WILDCARD"); - } + for (AnnotationMirror am : type.getSuperBound().getAnnotations()) { + List locations = + visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means + // that the qualifier can be written on any type use. + // Otherwise, for a valid use of qualifier on the super bound, that qualifier must + // declare one of these four type-use locations in the @TargetLocations meta-annotation. + List lowerLocations = + Arrays.asList( + TypeUseLocation.ALL, + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.IMPLICIT_LOWER_BOUND, + TypeUseLocation.EXPLICIT_LOWER_BOUND); + if (locations == null || locations.stream().anyMatch(lowerLocations::contains)) { + continue; + } + + checker.reportError( + tree, + "type.invalid.annotations.on.location", + type.getSuperBound().getAnnotations().toString(), + "SUPER_WILDCARD"); + } - for (AnnotationMirror am : type.getExtendsBound().getAnnotations()) { - List locations = - visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - List upperLocations = - Arrays.asList( - TypeUseLocation.ALL, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.IMPLICIT_UPPER_BOUND, - TypeUseLocation.EXPLICIT_UPPER_BOUND); - if (locations == null || locations.stream().anyMatch(upperLocations::contains)) { - continue; - } - - checker.reportError( - tree, - "type.invalid.annotations.on.location", - type.getExtendsBound().getAnnotations().toString(), - "EXTENDS_WILDCARD"); - } + for (AnnotationMirror am : type.getExtendsBound().getAnnotations()) { + List locations = + visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + List upperLocations = + Arrays.asList( + TypeUseLocation.ALL, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.IMPLICIT_UPPER_BOUND, + TypeUseLocation.EXPLICIT_UPPER_BOUND); + if (locations == null || locations.stream().anyMatch(upperLocations::contains)) { + continue; + } + + checker.reportError( + tree, + "type.invalid.annotations.on.location", + type.getExtendsBound().getAnnotations().toString(), + "EXTENDS_WILDCARD"); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 74bdcb4b22b..40f50113837 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -43,7 +43,36 @@ import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.TreeInfo; - +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.Vector; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -122,38 +151,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.Vector; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; - /* NO-AFU import org.checkerframework.common.wholeprograminference.WholeProgramInference; */ @@ -193,5029 +190,4875 @@ * @see AnnotatedTypeFactory */ public class BaseTypeVisitor> - extends SourceVisitor { - - /** The {@link BaseTypeChecker} for error reporting. */ - protected final BaseTypeChecker checker; + extends SourceVisitor { - /** The factory to use for obtaining "parsed" version of annotations. */ - protected final Factory atypeFactory; + /** The {@link BaseTypeChecker} for error reporting. */ + protected final BaseTypeChecker checker; - /** The qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; + /** The factory to use for obtaining "parsed" version of annotations. */ + protected final Factory atypeFactory; - /** The Annotated Type Hierarchy. */ - protected final TypeHierarchy typeHierarchy; + /** The qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; - /** For obtaining line numbers in {@code -Ashowchecks} debugging output. */ - protected final SourcePositions positions; + /** The Annotated Type Hierarchy. */ + protected final TypeHierarchy typeHierarchy; - /** The element for java.util.Vector#copyInto. */ - private final ExecutableElement vectorCopyInto; + /** For obtaining line numbers in {@code -Ashowchecks} debugging output. */ + protected final SourcePositions positions; - /** The element for java.util.function.Function#apply. */ - private final ExecutableElement functionApply; + /** The element for java.util.Vector#copyInto. */ + private final ExecutableElement vectorCopyInto; - /** The type of java.util.Vector. */ - private final AnnotatedDeclaredType vectorType; + /** The element for java.util.function.Function#apply. */ + private final ExecutableElement functionApply; - /** The @java.lang.annotation.Target annotation. */ - protected final AnnotationMirror TARGET = - AnnotationBuilder.fromClass( - elements, - java.lang.annotation.Target.class, - AnnotationBuilder.elementNamesValues("value", new ElementType[0])); + /** The type of java.util.Vector. */ + private final AnnotatedDeclaredType vectorType; - /** The @{@link Deterministic} annotation. */ - protected final AnnotationMirror DETERMINISTIC = - AnnotationBuilder.fromClass(elements, Deterministic.class); + /** The @java.lang.annotation.Target annotation. */ + protected final AnnotationMirror TARGET = + AnnotationBuilder.fromClass( + elements, + java.lang.annotation.Target.class, + AnnotationBuilder.elementNamesValues("value", new ElementType[0])); - /** The @{@link SideEffectFree} annotation. */ - protected final AnnotationMirror SIDE_EFFECT_FREE = - AnnotationBuilder.fromClass(elements, SideEffectFree.class); - - /** The @{@link Pure} annotation. */ - protected final AnnotationMirror PURE = AnnotationBuilder.fromClass(elements, Pure.class); - - /** The @{@link Impure} annotation. */ - protected final AnnotationMirror IMPURE = AnnotationBuilder.fromClass(elements, Impure.class); - - /** The {@code value} element/field of the @java.lang.annotation.Target annotation. */ - protected final ExecutableElement targetValueElement; - - /** The {@code when} element/field of the @Unused annotation. */ - protected final ExecutableElement unusedWhenElement; - - /** True if "-Ashowchecks" was passed on the command line. */ - protected final boolean showchecks; - - /* NO-AFU True if "-Ainfer" was passed on the command line. */ + /** The @{@link Deterministic} annotation. */ + protected final AnnotationMirror DETERMINISTIC = + AnnotationBuilder.fromClass(elements, Deterministic.class); + + /** The @{@link SideEffectFree} annotation. */ + protected final AnnotationMirror SIDE_EFFECT_FREE = + AnnotationBuilder.fromClass(elements, SideEffectFree.class); + + /** The @{@link Pure} annotation. */ + protected final AnnotationMirror PURE = AnnotationBuilder.fromClass(elements, Pure.class); + + /** The @{@link Impure} annotation. */ + protected final AnnotationMirror IMPURE = AnnotationBuilder.fromClass(elements, Impure.class); + + /** The {@code value} element/field of the @java.lang.annotation.Target annotation. */ + protected final ExecutableElement targetValueElement; + + /** The {@code when} element/field of the @Unused annotation. */ + protected final ExecutableElement unusedWhenElement; + + /** True if "-Ashowchecks" was passed on the command line. */ + protected final boolean showchecks; + + /* NO-AFU True if "-Ainfer" was passed on the command line. */ + /* NO-AFU + private final boolean infer; + */ + + /** True if "-AsuggestPureMethods" or "-Ainfer" was passed on the command line. */ + private final boolean suggestPureMethods; + + /** + * True if "-AcheckPurityAnnotations" or "-AsuggestPureMethods" or "-Ainfer" was passed on the + * command line. + */ + private final boolean checkPurity; + + /** True if "-AajavaChecks" was passed on the command line. */ + private final boolean ajavaChecks; + + /** True if "-AassumeSideEffectFree" or "-AassumePure" was passed on the command line. */ + private final boolean assumeSideEffectFree; + + /** True if "-AassumeDeterministic" or "-AassumePure" was passed on the command line. */ + private final boolean assumeDeterministic; + + /** True if "-AassumePureGetters" was passed on the command line. */ + public final boolean assumePureGetters; + + /** True if "-AcheckCastElementType" was passed on the command line. */ + private final boolean checkCastElementType; + + /** True if "-AconservativeUninferredTypeArguments" was passed on the command line. */ + private final boolean conservativeUninferredTypeArguments; + + /** True if "-AwarnRedundantAnnotations" was passed on the command line. */ + private final boolean warnRedundantAnnotations; + + /** True if "-AignoreTargetLocations" was passed on the command line. */ + protected final boolean ignoreTargetLocations; + + /** True if "-AcheckEnclosingExpr" was passed on the command line. */ + private final boolean checkEnclosingExpr; + + /** The tree of the enclosing method that is currently being visited. */ + protected @Nullable MethodTree methodTree = null; + + /** + * Map from String (canonical name of the qualifier) to its type-use locations declared in {@link + * org.checkerframework.framework.qual.TargetLocations}. + */ + protected final Map<@CanonicalName String, List> qualAllowedLocations; + + /** + * Constructor for creating a BaseTypeVisitor. + * + * @param checker the type-checker associated with this visitor (for callbacks to {@link + * TypeHierarchy#isSubtype}) + */ + public BaseTypeVisitor(BaseTypeChecker checker) { + this(checker, null); + } + + /** + * Constructor for creating a BaseTypeVisitor. + * + * @param checker the type-checker associated with this visitor + * @param typeFactory the type factory, or null. If null, this calls {@link #createTypeFactory}. + */ + protected BaseTypeVisitor(BaseTypeChecker checker, @Nullable Factory typeFactory) { + super(checker); + + this.checker = checker; + this.atypeFactory = typeFactory == null ? createTypeFactory() : typeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + this.typeHierarchy = atypeFactory.getTypeHierarchy(); + this.positions = trees.getSourcePositions(); + this.typeValidator = createTypeValidator(); + ProcessingEnvironment env = checker.getProcessingEnvironment(); + this.vectorCopyInto = TreeUtils.getMethod("java.util.Vector", "copyInto", 1, env); + this.functionApply = TreeUtils.getMethod("java.util.function.Function", "apply", 1, env); + this.vectorType = + atypeFactory.fromElement(elements.getTypeElement(Vector.class.getCanonicalName())); + targetValueElement = TreeUtils.getMethod(Target.class, "value", 0, env); + unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, env); + showchecks = checker.hasOption("showchecks"); /* NO-AFU - private final boolean infer; + infer = checker.hasOption("infer"); */ + suggestPureMethods = checker.hasOption("suggestPureMethods"); // NO-AFU || infer; + checkPurity = checker.hasOption("checkPurityAnnotations") || suggestPureMethods; + warnRedundantAnnotations = checker.hasOption("warnRedundantAnnotations"); + ignoreTargetLocations = checker.hasOption("ignoreTargetLocations"); + qualAllowedLocations = createQualAllowedLocations(); + + checkEnclosingExpr = checker.hasOption("checkEnclosingExpr"); + ajavaChecks = checker.hasOption("ajavaChecks"); + assumeSideEffectFree = + checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); + assumeDeterministic = + checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); + assumePureGetters = checker.hasOption("assumePureGetters"); + checkCastElementType = checker.hasOption("checkCastElementType"); + conservativeUninferredTypeArguments = checker.hasOption("conservativeUninferredTypeArguments"); + } + + /** An array containing just {@code BaseTypeChecker.class}. */ + private static final Class[] baseTypeCheckerClassArray = + new Class[] {BaseTypeChecker.class}; + + /** + * Constructs an instance of the appropriate type factory for the implemented type system. + * + *

The default implementation uses the checker naming convention to create the appropriate type + * factory. If no factory is found, it returns {@link BaseAnnotatedTypeFactory}. It reflectively + * invokes the constructor that accepts this checker and compilation unit tree (in that order) as + * arguments. + * + *

Subclasses have to override this method to create the appropriate visitor if they do not + * follow the checker naming convention. + * + * @return the appropriate type factory + */ + @SuppressWarnings({ + "unchecked", // unchecked cast to type variable + }) + protected Factory createTypeFactory() { + // Try to reflectively load the type factory. + Class checkerClass = checker.getClass(); + Object[] checkerArray = new Object[] {checker}; + while (checkerClass != BaseTypeChecker.class) { + AnnotatedTypeFactory result = + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, "AnnotatedTypeFactory"), + baseTypeCheckerClassArray, + checkerArray); + if (result != null) { + return (Factory) result; + } + checkerClass = checkerClass.getSuperclass(); + } + try { + return (Factory) new BaseAnnotatedTypeFactory(checker); + } catch (Throwable t) { + throw new BugInCF( + "Unexpected " + + t.getClass().getSimpleName() + + " when invoking BaseAnnotatedTypeFactory for checker " + + checker.getClass().getSimpleName(), + t); + } + } + + public final Factory getTypeFactory() { + return atypeFactory; + } + + /** + * A public variant of {@link #createTypeFactory}. Only use this if you know what you are doing. + * + * @return the appropriate type factory + */ + public Factory createTypeFactoryPublic() { + return createTypeFactory(); + } + + // ********************************************************************** + // Responsible for updating the factory for the location (for performance) + // ********************************************************************** + + @Override + public void setRoot(CompilationUnitTree root) { + atypeFactory.setRoot(root); + super.setRoot(root); + testJointJavacJavaParserVisitor(); + testAnnotationInsertion(); + } + + @Override + public Void scan(@Nullable Tree tree, Void p) { + if (tree == null) { + return null; + } + if (getCurrentPath() != null) { + this.atypeFactory.setVisitorTreePath(new TreePath(getCurrentPath(), tree)); + } + // TODO: use JCP to add version-specific behavior + if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { + visitSwitchExpression17(tree); + return null; + } + return super.scan(tree, p); + } + + /** + * Test {@link org.checkerframework.framework.ajava.JointJavacJavaParserVisitor} if the checker + * has the "ajavaChecks" option. + * + *

Parse the current source file with JavaParser and check that the AST can be matched with the + * Tree produced by javac. Crash if not. + * + *

Subclasses may override this method to disable the test if even the option is provided. + */ + protected void testJointJavacJavaParserVisitor() { + if (root == null + || !ajavaChecks + // TODO: Make annotation insertion work for Java 21. + || root.getSourceFile().toUri().toString().contains("java21")) { + return; + } - /** True if "-AsuggestPureMethods" or "-Ainfer" was passed on the command line. */ - private final boolean suggestPureMethods; + Map treePairs = new HashMap<>(); + try (InputStream reader = root.getSourceFile().openInputStream()) { + CompilationUnit javaParserRoot = JavaParserUtil.parseCompilationUnit(reader); + JavaParserUtil.concatenateAddedStringLiterals(javaParserRoot); + new JointVisitorWithDefaultAction() { + @Override + public void defaultJointAction( + Tree javacTree, com.github.javaparser.ast.Node javaParserNode) { + treePairs.put(javacTree, javaParserNode); + } + }.visitCompilationUnit(root, javaParserRoot); + ExpectedTreesVisitor expectedTreesVisitor = new ExpectedTreesVisitor(); + expectedTreesVisitor.visitCompilationUnit(root, null); + for (Tree expected : expectedTreesVisitor.getTrees()) { + if (!treePairs.containsKey(expected)) { + throw new BugInCF( + "Javac tree not matched to JavaParser node: %s [%s @ %d], in file: %s", + expected, + expected.getClass(), + positions.getStartPosition(root, expected), + root.getSourceFile().getName()); + } + } + } catch (IOException e) { + throw new BugInCF("Error reading Java source file", e); + } + } + + /** + * Tests {@link org.checkerframework.framework.ajava.InsertAjavaAnnotations} if the checker has + * the "ajavaChecks" option. + * + *

    + *
  1. Parses the current file with JavaParser. + *
  2. Removes all annotations. + *
  3. Reinserts the annotations. + *
  4. Throws an exception if the ASTs are not the same. + *
+ * + *

Subclasses may override this method to disable the test even if the option is provided. + */ + protected void testAnnotationInsertion() { + if (root == null + || !ajavaChecks + // TODO: Make annotation insertion work for Java 21. + || root.getSourceFile().toUri().toString().contains("java21")) { + return; + } - /** - * True if "-AcheckPurityAnnotations" or "-AsuggestPureMethods" or "-Ainfer" was passed on the - * command line. - */ - private final boolean checkPurity; + CompilationUnit originalAst; + try (InputStream originalInputStream = root.getSourceFile().openInputStream()) { + originalAst = JavaParserUtil.parseCompilationUnit(originalInputStream); + } catch (IOException e) { + throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); + } - /** True if "-AajavaChecks" was passed on the command line. */ - private final boolean ajavaChecks; + CompilationUnit astWithoutAnnotations = originalAst.clone(); + JavaParserUtil.clearAnnotations(astWithoutAnnotations); + String withoutAnnotations = new DefaultPrettyPrinter().print(astWithoutAnnotations); + + String withAnnotations; + try (InputStream annotationInputStream = root.getSourceFile().openInputStream()) { + // This check only runs on files from the Checker Framework test suite, which should all + // use UNIX line separators. Using System.lineSeparator instead of "\n" could cause the + // test to fail on Mac or Windows. + withAnnotations = + new InsertAjavaAnnotations(elements) + .insertAnnotations(annotationInputStream, withoutAnnotations, "\n"); + } catch (IOException e) { + throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); + } - /** True if "-AassumeSideEffectFree" or "-AassumePure" was passed on the command line. */ - private final boolean assumeSideEffectFree; + CompilationUnit modifiedAst = null; + try { + modifiedAst = JavaParserUtil.parseCompilationUnit(withAnnotations); + } catch (ParseProblemException e) { + throw new BugInCF("Failed to parse code after annotation insertion:\n" + withAnnotations, e); + } - /** True if "-AassumeDeterministic" or "-AassumePure" was passed on the command line. */ - private final boolean assumeDeterministic; + AnnotationEqualityVisitor visitor = new AnnotationEqualityVisitor(); + originalAst.accept(visitor, modifiedAst); + if (!visitor.getAnnotationsMatch()) { + throw new BugInCF( + String.join( + System.lineSeparator(), + "Sanity check of erasing then reinserting annotations produced a" + " different AST.", + "File: " + root.getSourceFile(), + "Node class: " + visitor.getMismatchedNode1().getClass().getSimpleName(), + "Original node: " + oneLine(visitor.getMismatchedNode1()), + "Node with annotations re-inserted: " + oneLine(visitor.getMismatchedNode2()), + "Original annotations: " + visitor.getMismatchedNode1().getAnnotations(), + "Re-inserted annotations: " + visitor.getMismatchedNode2().getAnnotations(), + "Original AST:", + originalAst.toString(), + "Ast with annotations re-inserted: " + modifiedAst)); + } + } + + /** + * Replace newlines in the printed representation by spaces. + * + * @param arg an object to format + * @return the object's toString representation, on one line + */ + private String oneLine(Object arg) { + return arg.toString().replace(System.lineSeparator(), " "); + } + + /** + * Type-check classTree and skips classes specified by the skipDef option. Subclasses should + * override {@link #processClassTree(ClassTree)} instead of this method. + * + * @param classTree class to check + * @param p null + * @return null + */ + @Override + public final Void visitClass(ClassTree classTree, Void p) { + if (checker.shouldSkipDefs(classTree)) { + // Not "return super.visitClass(classTree, p);" because that would recursively call + // visitors on subtrees; we want to skip the class entirely. + return null; + } + atypeFactory.preProcessClassTree(classTree); - /** True if "-AassumePureGetters" was passed on the command line. */ - public final boolean assumePureGetters; + TreePath preTreePath = atypeFactory.getVisitorTreePath(); + MethodTree preMT = methodTree; - /** True if "-AcheckCastElementType" was passed on the command line. */ - private final boolean checkCastElementType; + // Don't use atypeFactory.getPath, because that depends on the visitor path. + atypeFactory.setVisitorTreePath(TreePath.getPath(root, classTree)); + methodTree = null; - /** True if "-AconservativeUninferredTypeArguments" was passed on the command line. */ - private final boolean conservativeUninferredTypeArguments; + try { + processClassTree(classTree); + atypeFactory.postProcessClassTree(classTree); + } finally { + atypeFactory.setVisitorTreePath(preTreePath); + methodTree = preMT; + } + return null; + } + + /** + * Type-check classTree. Subclasses should override this method instead of {@link + * #visitClass(ClassTree, Void)}. + * + * @param classTree class to check + */ + public void processClassTree(ClassTree classTree) { + checkFieldInvariantDeclarations(classTree); + if (!TreeUtils.hasExplicitConstructor(classTree)) { + checkDefaultConstructor(classTree); + } - /** True if "-AwarnRedundantAnnotations" was passed on the command line. */ - private final boolean warnRedundantAnnotations; + AnnotatedDeclaredType classType = atypeFactory.getAnnotatedType(classTree); + atypeFactory.getDependentTypesHelper().checkClassForErrorExpressions(classTree, classType); + validateType(classTree, classType); - /** True if "-AignoreTargetLocations" was passed on the command line. */ - protected final boolean ignoreTargetLocations; + Tree ext = classTree.getExtendsClause(); + if (ext != null) { + AnnotatedTypeMirror superClass = atypeFactory.getTypeOfExtendsImplements(ext); + validateType(ext, superClass); + } - /** True if "-AcheckEnclosingExpr" was passed on the command line. */ - private final boolean checkEnclosingExpr; + List impls = classTree.getImplementsClause(); + if (impls != null) { + for (Tree im : impls) { + AnnotatedTypeMirror superInterface = atypeFactory.getTypeOfExtendsImplements(im); + validateType(im, superInterface); + } + } - /** The tree of the enclosing method that is currently being visited. */ - protected @Nullable MethodTree methodTree = null; + checkForPolymorphicQualifiers(classTree); - /** - * Map from String (canonical name of the qualifier) to its type-use locations declared in - * {@link org.checkerframework.framework.qual.TargetLocations}. - */ - protected final Map<@CanonicalName String, List> qualAllowedLocations; + checkExtendsAndImplements(classTree); - /** - * Constructor for creating a BaseTypeVisitor. - * - * @param checker the type-checker associated with this visitor (for callbacks to {@link - * TypeHierarchy#isSubtype}) - */ - public BaseTypeVisitor(BaseTypeChecker checker) { - this(checker, null); - } + checkQualifierParameter(classTree); - /** - * Constructor for creating a BaseTypeVisitor. - * - * @param checker the type-checker associated with this visitor - * @param typeFactory the type factory, or null. If null, this calls {@link #createTypeFactory}. - */ - protected BaseTypeVisitor(BaseTypeChecker checker, @Nullable Factory typeFactory) { - super(checker); - - this.checker = checker; - this.atypeFactory = typeFactory == null ? createTypeFactory() : typeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - this.typeHierarchy = atypeFactory.getTypeHierarchy(); - this.positions = trees.getSourcePositions(); - this.typeValidator = createTypeValidator(); - ProcessingEnvironment env = checker.getProcessingEnvironment(); - this.vectorCopyInto = TreeUtils.getMethod("java.util.Vector", "copyInto", 1, env); - this.functionApply = TreeUtils.getMethod("java.util.function.Function", "apply", 1, env); - this.vectorType = - atypeFactory.fromElement(elements.getTypeElement(Vector.class.getCanonicalName())); - targetValueElement = TreeUtils.getMethod(Target.class, "value", 0, env); - unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, env); - showchecks = checker.hasOption("showchecks"); - /* NO-AFU - infer = checker.hasOption("infer"); - */ - suggestPureMethods = checker.hasOption("suggestPureMethods"); // NO-AFU || infer; - checkPurity = checker.hasOption("checkPurityAnnotations") || suggestPureMethods; - warnRedundantAnnotations = checker.hasOption("warnRedundantAnnotations"); - ignoreTargetLocations = checker.hasOption("ignoreTargetLocations"); - qualAllowedLocations = createQualAllowedLocations(); - - checkEnclosingExpr = checker.hasOption("checkEnclosingExpr"); - ajavaChecks = checker.hasOption("ajavaChecks"); - assumeSideEffectFree = - checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); - assumeDeterministic = - checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); - assumePureGetters = checker.hasOption("assumePureGetters"); - checkCastElementType = checker.hasOption("checkCastElementType"); - conservativeUninferredTypeArguments = - checker.hasOption("conservativeUninferredTypeArguments"); - } - - /** An array containing just {@code BaseTypeChecker.class}. */ - private static final Class[] baseTypeCheckerClassArray = - new Class[] {BaseTypeChecker.class}; + super.visitClass(classTree, null); + } - /** - * Constructs an instance of the appropriate type factory for the implemented type system. - * - *

The default implementation uses the checker naming convention to create the appropriate - * type factory. If no factory is found, it returns {@link BaseAnnotatedTypeFactory}. It - * reflectively invokes the constructor that accepts this checker and compilation unit tree (in - * that order) as arguments. - * - *

Subclasses have to override this method to create the appropriate visitor if they do not - * follow the checker naming convention. - * - * @return the appropriate type factory - */ - @SuppressWarnings({ - "unchecked", // unchecked cast to type variable - }) - protected Factory createTypeFactory() { - // Try to reflectively load the type factory. - Class checkerClass = checker.getClass(); - Object[] checkerArray = new Object[] {checker}; - while (checkerClass != BaseTypeChecker.class) { - AnnotatedTypeFactory result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName( - checkerClass, "AnnotatedTypeFactory"), - baseTypeCheckerClassArray, - checkerArray); - if (result != null) { - return (Factory) result; - } - checkerClass = checkerClass.getSuperclass(); - } - try { - return (Factory) new BaseAnnotatedTypeFactory(checker); - } catch (Throwable t) { - throw new BugInCF( - "Unexpected " - + t.getClass().getSimpleName() - + " when invoking BaseAnnotatedTypeFactory for checker " - + checker.getClass().getSimpleName(), - t); - } + /** + * A TreeScanner that issues an "invalid.polymorphic.qualifier" error for each {@link + * AnnotationTree} that is a polymorphic qualifier. The second parameter is added to the error + * message and should explain the location. + */ + private final TreeScanner polyTreeScanner = + new TreeScanner() { + @Override + public Void visitAnnotation(AnnotationTree annoTree, String location) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(annoTree); + if (atypeFactory.isSupportedQualifier(anno) + && qualHierarchy.isPolymorphicQualifier(anno)) { + checker.reportError(annoTree, "invalid.polymorphic.qualifier", anno, location); + } + return super.visitAnnotation(annoTree, location); + } + }; + + /** + * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the + * class declaration. + * + * @param classTree the class to check + */ + protected void checkForPolymorphicQualifiers(ClassTree classTree) { + if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { + // Anonymous class can have polymorphic annotations, so don't check them. + return; } - - public final Factory getTypeFactory() { - return atypeFactory; + classTree.getModifiers().accept(polyTreeScanner, "in a class declaration"); + if (classTree.getExtendsClause() != null) { + classTree.getExtendsClause().accept(polyTreeScanner, "in a class declaration"); } - - /** - * A public variant of {@link #createTypeFactory}. Only use this if you know what you are doing. - * - * @return the appropriate type factory - */ - public Factory createTypeFactoryPublic() { - return createTypeFactory(); + for (Tree tree : classTree.getImplementsClause()) { + tree.accept(polyTreeScanner, "in a class declaration"); + } + for (Tree tree : classTree.getTypeParameters()) { + tree.accept(polyTreeScanner, "in a class declaration"); } + } + + /** + * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the + * type parameters declaration. + * + * @param typeParameterTrees the type parameters to check + */ + protected void checkForPolymorphicQualifiers( + List typeParameterTrees) { + for (Tree tree : typeParameterTrees) { + tree.accept(polyTreeScanner, "in a type parameter"); + } + } + + /** + * Issues an error if {@code classTree} has polymorphic fields but is not annotated with + * {@code @HasQualifierParameter}. Always issue a warning if the type of a static field is + * annotated with a polymorphic qualifier. + * + *

Issues an error if {@code classTree} extends or implements a class/interface that has a + * qualifier parameter, but this class does not. + * + * @param classTree the ClassTree to check for polymorphic fields + */ + protected void checkQualifierParameter(ClassTree classTree) { + // Set of polymorphic qualifiers for hierarchies that do not have a qualifier parameter and + // therefor cannot appear on a field. + AnnotationMirrorSet illegalOnFieldsPolyQual = new AnnotationMirrorSet(); + // Set of polymorphic annotations for all hierarchies + AnnotationMirrorSet polys = new AnnotationMirrorSet(); + TypeElement classElement = TreeUtils.elementFromDeclaration(classTree); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); + if (poly != null) { + polys.add(poly); + } + // else { + // If there is no polymorphic qualifier in the hierarchy, it could still have a + // @HasQualifierParameter that must be checked. + // } + + if (atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) + && atypeFactory.hasExplicitNoQualifierParameterInHierarchy(classElement, top)) { + checker.reportError(classTree, "conflicting.qual.param", top); + } - // ********************************************************************** - // Responsible for updating the factory for the location (for performance) - // ********************************************************************** + if (atypeFactory.hasQualifierParameterInHierarchy(classElement, top)) { + continue; + } - @Override - public void setRoot(CompilationUnitTree root) { - atypeFactory.setRoot(root); - super.setRoot(root); - testJointJavacJavaParserVisitor(); - testAnnotationInsertion(); + if (poly != null) { + illegalOnFieldsPolyQual.add(poly); + } + Element extendsEle = TypesUtils.getTypeElement(classElement.getSuperclass()); + if (extendsEle != null && atypeFactory.hasQualifierParameterInHierarchy(extendsEle, top)) { + checker.reportError(classTree, "missing.has.qual.param", top); + } else { + for (TypeMirror interfaceType : classElement.getInterfaces()) { + Element interfaceEle = TypesUtils.getTypeElement(interfaceType); + if (atypeFactory.hasQualifierParameterInHierarchy(interfaceEle, top)) { + checker.reportError(classTree, "missing.has.qual.param", top); + break; // only issue error once + } + } + } } - @Override - public Void scan(@Nullable Tree tree, Void p) { - if (tree == null) { - return null; - } - if (getCurrentPath() != null) { - this.atypeFactory.setVisitorTreePath(new TreePath(getCurrentPath(), tree)); + for (Tree mem : classTree.getMembers()) { + if (mem.getKind() == Tree.Kind.VARIABLE) { + AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(mem); + List hasIllegalPoly; + if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration((VariableTree) mem))) { + // A polymorphic qualifier is not allowed on a static field even if the class + // has a qualifier parameter. + hasIllegalPoly = polyScanner.visit(fieldType, polys); + } else { + hasIllegalPoly = polyScanner.visit(fieldType, illegalOnFieldsPolyQual); } - // TODO: use JCP to add version-specific behavior - if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { - visitSwitchExpression17(tree); - return null; + for (DiagMessage dm : hasIllegalPoly) { + checker.report(mem, dm); } - return super.scan(tree, p); + } + } + } + + /** + * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use + * of one of the polymorphic qualifiers. + */ + private final PolyTypeScanner polyScanner = new PolyTypeScanner(); + + /** + * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use + * of one of the polymorphic qualifiers. + */ + static class PolyTypeScanner + extends SimpleAnnotatedTypeScanner, AnnotationMirrorSet> { + + /** Create PolyTypeScanner. */ + private PolyTypeScanner() { + super(DiagMessage::mergeLists, Collections.emptyList()); } - /** - * Test {@link org.checkerframework.framework.ajava.JointJavacJavaParserVisitor} if the checker - * has the "ajavaChecks" option. - * - *

Parse the current source file with JavaParser and check that the AST can be matched with - * the Tree produced by javac. Crash if not. - * - *

Subclasses may override this method to disable the test if even the option is provided. - */ - protected void testJointJavacJavaParserVisitor() { - if (root == null - || !ajavaChecks - // TODO: Make annotation insertion work for Java 21. - || root.getSourceFile().toUri().toString().contains("java21")) { - return; - } + @Override + protected List defaultAction(AnnotatedTypeMirror type, AnnotationMirrorSet polys) { + if (type == null) { + return Collections.emptyList(); + } - Map treePairs = new HashMap<>(); - try (InputStream reader = root.getSourceFile().openInputStream()) { - CompilationUnit javaParserRoot = JavaParserUtil.parseCompilationUnit(reader); - JavaParserUtil.concatenateAddedStringLiterals(javaParserRoot); - new JointVisitorWithDefaultAction() { - @Override - public void defaultJointAction( - Tree javacTree, com.github.javaparser.ast.Node javaParserNode) { - treePairs.put(javacTree, javaParserNode); - } - }.visitCompilationUnit(root, javaParserRoot); - ExpectedTreesVisitor expectedTreesVisitor = new ExpectedTreesVisitor(); - expectedTreesVisitor.visitCompilationUnit(root, null); - for (Tree expected : expectedTreesVisitor.getTrees()) { - if (!treePairs.containsKey(expected)) { - throw new BugInCF( - "Javac tree not matched to JavaParser node: %s [%s @ %d], in file: %s", - expected, - expected.getClass(), - positions.getStartPosition(root, expected), - root.getSourceFile().getName()); - } - } - } catch (IOException e) { - throw new BugInCF("Error reading Java source file", e); + for (AnnotationMirror poly : polys) { + if (type.hasAnnotation(poly)) { + return Collections.singletonList( + DiagMessage.error("invalid.polymorphic.qualifier.use", poly)); } + } + return Collections.emptyList(); + } + } + + /** + * In {@code @A class X extends @B Y implements @C Z {}}, enforce that {@code @A} must be a + * subtype of {@code @B} and {@code @C}. + * + *

Also validate the types of the extends and implements clauses. + * + * @param classTree class tree to check + */ + protected void checkExtendsAndImplements(ClassTree classTree) { + if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { + // Don't check extends clause on anonymous classes. + return; + } + if (classTree.getExtendsClause() == null && classTree.getImplementsClause().isEmpty()) { + // Nothing to do + return; } - /** - * Tests {@link org.checkerframework.framework.ajava.InsertAjavaAnnotations} if the checker has - * the "ajavaChecks" option. - * - *

    - *
  1. Parses the current file with JavaParser. - *
  2. Removes all annotations. - *
  3. Reinserts the annotations. - *
  4. Throws an exception if the ASTs are not the same. - *
- * - *

Subclasses may override this method to disable the test even if the option is provided. - */ - protected void testAnnotationInsertion() { - if (root == null - || !ajavaChecks - // TODO: Make annotation insertion work for Java 21. - || root.getSourceFile().toUri().toString().contains("java21")) { - return; - } + TypeMirror classType = TreeUtils.typeOf(classTree); + AnnotationMirrorSet classBounds = atypeFactory.getTypeDeclarationBounds(classType); + // If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A. + // classTree.getExtendsClause() is null when there is no explicitly-written extends clause, + // as in "class X {}". This is equivalent to writing "class X extends @Top Object {}", so + // there is no need to do any subtype checking. + if (classTree.getExtendsClause() != null) { + Tree boundClause = classTree.getExtendsClause(); + checkExtendsOrImplements(boundClause, classBounds, classType, true); + } + // Do the same check as above for implements clauses. + for (Tree boundClause : classTree.getImplementsClause()) { + checkExtendsOrImplements(boundClause, classBounds, classType, false); + } + } + + /** + * Helper for {@link #checkExtendsAndImplements} that checks one extends or implements clause. + * + * @param boundClause an extends or implements clause + * @param classBounds the type declarations bounds to check for consistency with {@code + * boundClause} + * @param classType the type being declared + * @param isExtends true for an extends clause, false for an implements clause + */ + protected void checkExtendsOrImplements( + Tree boundClause, AnnotationMirrorSet classBounds, TypeMirror classType, boolean isExtends) { + AnnotatedTypeMirror boundType = atypeFactory.getTypeOfExtendsImplements(boundClause); + TypeMirror boundTM = boundType.getUnderlyingType(); + for (AnnotationMirror classAnno : classBounds) { + AnnotationMirror boundAnno = boundType.getAnnotationInHierarchy(classAnno); + if (!qualHierarchy.isSubtypeShallow(classAnno, classType, boundAnno, boundTM)) { + checker.reportError( + boundClause, + (isExtends + ? "declaration.inconsistent.with.extends.clause" + : "declaration.inconsistent.with.implements.clause"), + classAnno, + boundAnno); + } + } + } + + /** + * Check that the field invariant declaration annotations meet the following requirements: + * + *

    + * + *
  1. If the superclass of {@code classTree} has a field invariant, then the field + * invariant for {@code classTree} must include all the fields in the superclass invariant + * and those fields' annotations must be a subtype (or equal) to the annotations for those + * fields in the superclass. + *
  2. The fields in the invariant must be a.) final and b.) declared in a superclass + * of {@code classTree}. + *
  3. The qualifier for each field must be a subtype of the annotation on the + * declaration of that field. + *
  4. The field invariant has an equal number of fields and qualifiers, or it has one + * qualifier and at least one field. + *
+ * + * @param classTree class that might have a field invariant + * @checker_framework.manual #field-invariants Field invariants + */ + protected void checkFieldInvariantDeclarations(ClassTree classTree) { + TypeElement elt = TreeUtils.elementFromDeclaration(classTree); + FieldInvariants invariants = atypeFactory.getFieldInvariants(elt); + if (invariants == null) { + // No invariants to check + return; + } - CompilationUnit originalAst; - try (InputStream originalInputStream = root.getSourceFile().openInputStream()) { - originalAst = JavaParserUtil.parseCompilationUnit(originalInputStream); - } catch (IOException e) { - throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); - } + // Where to issue an error, if any. + Tree errorTree = + atypeFactory.getFieldInvariantAnnotationTree(classTree.getModifiers().getAnnotations()); + if (errorTree == null) { + // If the annotation was inherited, then there is no annotation tree, so issue the + // error on the class. + errorTree = classTree; + } - CompilationUnit astWithoutAnnotations = originalAst.clone(); - JavaParserUtil.clearAnnotations(astWithoutAnnotations); - String withoutAnnotations = new DefaultPrettyPrinter().print(astWithoutAnnotations); - - String withAnnotations; - try (InputStream annotationInputStream = root.getSourceFile().openInputStream()) { - // This check only runs on files from the Checker Framework test suite, which should all - // use UNIX line separators. Using System.lineSeparator instead of "\n" could cause the - // test to fail on Mac or Windows. - withAnnotations = - new InsertAjavaAnnotations(elements) - .insertAnnotations(annotationInputStream, withoutAnnotations, "\n"); - } catch (IOException e) { - throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); - } + // Checks #4 (see method Javadoc) + if (!invariants.isWellFormed()) { + checker.reportError(errorTree, "field.invariant.not.wellformed"); + return; + } - CompilationUnit modifiedAst = null; - try { - modifiedAst = JavaParserUtil.parseCompilationUnit(withAnnotations); - } catch (ParseProblemException e) { - throw new BugInCF( - "Failed to parse code after annotation insertion:\n" + withAnnotations, e); - } + TypeMirror superClass = elt.getSuperclass(); + List fieldsNotFound = new ArrayList<>(invariants.getFields()); + Set fieldElts = + ElementUtils.findFieldsInTypeOrSuperType(superClass, fieldsNotFound); - AnnotationEqualityVisitor visitor = new AnnotationEqualityVisitor(); - originalAst.accept(visitor, modifiedAst); - if (!visitor.getAnnotationsMatch()) { - throw new BugInCF( - String.join( - System.lineSeparator(), - "Sanity check of erasing then reinserting annotations produced a" - + " different AST.", - "File: " + root.getSourceFile(), - "Node class: " - + visitor.getMismatchedNode1().getClass().getSimpleName(), - "Original node: " + oneLine(visitor.getMismatchedNode1()), - "Node with annotations re-inserted: " - + oneLine(visitor.getMismatchedNode2()), - "Original annotations: " - + visitor.getMismatchedNode1().getAnnotations(), - "Re-inserted annotations: " - + visitor.getMismatchedNode2().getAnnotations(), - "Original AST:", - originalAst.toString(), - "Ast with annotations re-inserted: " + modifiedAst)); - } + // Checks that fields are declared in super class. (#2b) + if (!fieldsNotFound.isEmpty()) { + String notFoundString = String.join(", ", fieldsNotFound); + checker.reportError(errorTree, "field.invariant.not.found", notFoundString); } - /** - * Replace newlines in the printed representation by spaces. - * - * @param arg an object to format - * @return the object's toString representation, on one line - */ - private String oneLine(Object arg) { - return arg.toString().replace(System.lineSeparator(), " "); + FieldInvariants superInvar = + atypeFactory.getFieldInvariants(TypesUtils.getTypeElement(superClass)); + if (superInvar != null) { + // Checks #3 (see method Javadoc) + DiagMessage superError = invariants.isStrongerThan(superInvar); + if (superError != null) { + checker.report(errorTree, superError); + } } - /** - * Type-check classTree and skips classes specified by the skipDef option. Subclasses should - * override {@link #processClassTree(ClassTree)} instead of this method. - * - * @param classTree class to check - * @param p null - * @return null - */ - @Override - public final Void visitClass(ClassTree classTree, Void p) { - if (checker.shouldSkipDefs(classTree)) { - // Not "return super.visitClass(classTree, p);" because that would recursively call - // visitors on subtrees; we want to skip the class entirely. - return null; + List notFinal = new ArrayList<>(fieldElts.size()); + for (VariableElement field : fieldElts) { + String fieldName = field.getSimpleName().toString(); + if (!ElementUtils.isFinal(field)) { + notFinal.add(fieldName); + } + AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(field); + + List annos = invariants.getQualifiersFor(field.getSimpleName()); + for (AnnotationMirror invariantAnno : annos) { + AnnotationMirror declaredAnno = fieldType.getEffectiveAnnotationInHierarchy(invariantAnno); + if (declaredAnno == null) { + // invariant anno isn't in this hierarchy + continue; } - atypeFactory.preProcessClassTree(classTree); - TreePath preTreePath = atypeFactory.getVisitorTreePath(); - MethodTree preMT = methodTree; + if (!typeHierarchy.isSubtypeShallowEffective(invariantAnno, fieldType)) { + // Checks #3 + checker.reportError( + errorTree, "field.invariant.not.subtype", fieldName, invariantAnno, declaredAnno); + } + } + } - // Don't use atypeFactory.getPath, because that depends on the visitor path. - atypeFactory.setVisitorTreePath(TreePath.getPath(root, classTree)); - methodTree = null; + // Checks #2a + if (!notFinal.isEmpty()) { + String notFinalString = String.join(", ", notFinal); + checker.reportError(errorTree, "field.invariant.not.final", notFinalString); + } + } + + /** + * Check the default constructor. + * + * @param tree a class declaration + */ + protected void checkDefaultConstructor(ClassTree tree) {} + + /** + * Checks that the method obeys override and subtype rules to all overridden methods. (Uses the + * pseudo-assignment logic to do so.) + * + *

The override rule specifies that a method, m1, may override a method m2 only if: + * + *

    + *
  • m1 return type is a subtype of m2 + *
  • m1 receiver type is a supertype of m2 + *
  • m1 parameters are supertypes of corresponding m2 parameters + *
+ * + * Also, it issues a "missing.this" error for static method annotated receivers. + */ + @Override + public Void visitMethod(MethodTree tree, Void p) { + // We copy the result from getAnnotatedType to ensure that circular types (e.g. K extends + // Comparable) are represented by circular AnnotatedTypeMirrors, which avoids problems + // with later checks. + // TODO: Find a cleaner way to ensure circular AnnotatedTypeMirrors. + AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); + MethodTree preMT = methodTree; + methodTree = tree; + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); + + warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); + + if (tree.getReturnType() != null) { + visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getReturnType()); + warnRedundantAnnotations(tree.getReturnType(), methodType.getReturnType()); + } else if (TreeUtils.isConstructor(tree)) { + maybeReportAnnoOnIrrelevant( + tree.getModifiers(), + methodType.getReturnType().getUnderlyingType(), + tree.getModifiers().getAnnotations()); + } - try { - processClassTree(classTree); - atypeFactory.postProcessClassTree(classTree); - } finally { - atypeFactory.setVisitorTreePath(preTreePath); - methodTree = preMT; - } + try { + if (TreeUtils.isAnonymousConstructor(tree)) { + // We shouldn't dig deeper return null; - } + } - /** - * Type-check classTree. Subclasses should override this method instead of {@link - * #visitClass(ClassTree, Void)}. - * - * @param classTree class to check - */ - public void processClassTree(ClassTree classTree) { - checkFieldInvariantDeclarations(classTree); - if (!TreeUtils.hasExplicitConstructor(classTree)) { - checkDefaultConstructor(classTree); - } + if (TreeUtils.isConstructor(tree)) { + checkConstructorResult(methodType, methodElement); + } + + checkPurity(tree); - AnnotatedDeclaredType classType = atypeFactory.getAnnotatedType(classTree); - atypeFactory.getDependentTypesHelper().checkClassForErrorExpressions(classTree, classType); - validateType(classTree, classType); + // Passing the whole method/constructor validates the return type + validateTypeOf(tree); - Tree ext = classTree.getExtendsClause(); - if (ext != null) { - AnnotatedTypeMirror superClass = atypeFactory.getTypeOfExtendsImplements(ext); - validateType(ext, superClass); + // Validate types in throws clauses + for (ExpressionTree thr : tree.getThrows()) { + validateTypeOf(thr); + } + + atypeFactory.getDependentTypesHelper().checkMethodForErrorExpressions(tree, methodType); + + // Check method overrides + AnnotatedDeclaredType enclosingType = + (AnnotatedDeclaredType) + atypeFactory.getAnnotatedType(methodElement.getEnclosingElement()); + + // Find which methods this method overrides + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedDeclaredType overriddenType = pair.getKey(); + ExecutableElement overriddenMethodElt = pair.getValue(); + AnnotatedExecutableType overriddenMethodType = + AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, overriddenMethodElt); + if (!checkOverride(tree, enclosingType, overriddenMethodType, overriddenType)) { + // Stop at the first mismatch; this makes a difference only if + // -Awarns is passed, in which case multiple warnings might be raised on + // the same method, not adding any value. See Issue 373. + break; } + } - List impls = classTree.getImplementsClause(); - if (impls != null) { - for (Tree im : impls) { - AnnotatedTypeMirror superInterface = atypeFactory.getTypeOfExtendsImplements(im); - validateType(im, superInterface); - } + // Check well-formedness of pre/postcondition + boolean abstractMethod = + methodElement.getModifiers().contains(Modifier.ABSTRACT) + || methodElement.getModifiers().contains(Modifier.NATIVE); + + List formalParamNames = + CollectionsPlume.mapList( + (VariableTree param) -> param.getName().toString(), tree.getParameters()); + checkContractsAtMethodDeclaration(tree, methodElement, formalParamNames, abstractMethod); + /* NO-AFU + // Infer postconditions + if (shouldPerformContractInference()) { + assert ElementUtils.isElementFromSourceCode(methodElement); + + // TODO: Infer conditional postconditions too. + CFAbstractStore store = atypeFactory.getRegularExitStore(tree); + // The store is null if the method has no normal exit, for example if its body is a + // throw statement. + if (store != null) { + atypeFactory + .getWholeProgramInference() + .updateContracts(Analysis.BeforeOrAfter.AFTER, methodElement, store); } + } + */ - checkForPolymorphicQualifiers(classTree); + checkForPolymorphicQualifiers(tree.getTypeParameters()); - checkExtendsAndImplements(classTree); + return super.visitMethod(tree, p); + } finally { + methodTree = preMT; + } + } + + /* NO-AFU + * Should Whole Program Inference attempt to infer contract annotations? Typically, the answer is + * "yes" whenever WPI is enabled, but this method exists to allow subclasses to customize that + * behavior. + * + * @return true if contract inference should be performed, false if it should be disabled (even + * when WPI is enabled) + */ + /* NO-AFU + protected boolean shouldPerformContractInference() { + return atypeFactory.getWholeProgramInference() != null; + } + */ + + /** + * Check method purity if needed. Note that overriding rules are checked as part of {@link + * #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType)}. + * + * @param tree the method tree to check + */ + protected void checkPurity(MethodTree tree) { + if (!checkPurity) { + return; + } - checkQualifierParameter(classTree); + if (!suggestPureMethods && !PurityUtils.hasPurityAnnotation(atypeFactory, tree)) { + // There is nothing to check. + return; + } - super.visitClass(classTree, null); + // check "no" purity + EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, tree); + // @Deterministic makes no sense for a void method or constructor + boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); + if (isDeterministic) { + if (TreeUtils.isConstructor(tree)) { + checker.reportWarning(tree, "purity.deterministic.constructor"); + } else if (TreeUtils.isVoidReturn(tree)) { + checker.reportWarning(tree, "purity.deterministic.void.method"); + } } - /** - * A TreeScanner that issues an "invalid.polymorphic.qualifier" error for each {@link - * AnnotationTree} that is a polymorphic qualifier. The second parameter is added to the error - * message and should explain the location. - */ - private final TreeScanner polyTreeScanner = - new TreeScanner() { - @Override - public Void visitAnnotation(AnnotationTree annoTree, String location) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(annoTree); - if (atypeFactory.isSupportedQualifier(anno) - && qualHierarchy.isPolymorphicQualifier(anno)) { - checker.reportError( - annoTree, "invalid.polymorphic.qualifier", anno, location); - } - return super.visitAnnotation(annoTree, location); - } - }; + TreePath body = atypeFactory.getPath(tree.getBody()); + PurityResult r; + if (body == null) { + r = new PurityResult(); + } else { + r = + PurityChecker.checkPurity( + body, atypeFactory, assumeSideEffectFree, assumeDeterministic, assumePureGetters); + } + if (!r.isPure(kinds)) { + reportPurityErrors(r, tree, kinds); + } - /** - * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on - * the class declaration. - * - * @param classTree the class to check - */ - protected void checkForPolymorphicQualifiers(ClassTree classTree) { - if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { - // Anonymous class can have polymorphic annotations, so don't check them. - return; + if (suggestPureMethods && !TreeUtils.isSynthetic(tree)) { + // Issue a warning if the method is pure, but not annotated as such. + EnumSet additionalKinds = r.getKinds().clone(); + /* NO-AFU + if (!infer) { + // During WPI, propagate all purity kinds, even those that are already + // present (because they were inferred in a previous WPI round). + */ + additionalKinds.removeAll(kinds); + /* NO-AFU + } + */ + if (TreeUtils.isConstructor(tree) || TreeUtils.isVoidReturn(tree)) { + additionalKinds.remove(Pure.Kind.DETERMINISTIC); + } + if (additionalKinds.isEmpty()) { + // No need to suggest @Impure, since it is equivalent to no annotation. + } else if (additionalKinds.size() == 2) { + checker.reportWarning(tree, "purity.more.pure", tree.getName()); + } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + checker.reportWarning(tree, "purity.more.sideeffectfree", tree.getName()); + } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { + checker.reportWarning(tree, "purity.more.deterministic", tree.getName()); + } else { + throw new BugInCF("Unexpected purity kind in " + additionalKinds); + } + /* NO-AFU + if (infer) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); + inferPurityAnno(additionalKinds, wpi, methodElt); + // The purity of overridden methods is impacted by the purity of this method. If a + // superclass method is pure, but an implementation in a subclass is not, WPI ought to treat + // **neither** as pure. The purity kind of the superclass method is the LUB of its own + // purity and the purity of all the methods that override it. Logically, this rule is the + // same as the WPI rule for overrides, but purity isn't a type system and therefore must be + // special-cased. + Set overriddenMethods = + ElementUtils.getOverriddenMethods(methodElt, types); + for (ExecutableElement overriddenElt : overriddenMethods) { + inferPurityAnno(additionalKinds, wpi, overriddenElt); + } + } + */ + } + } + + /* NO-AFU + * Infer a purity annotation for {@code elt} by converting {@code kinds} into a method annotation. + * + *

This method delegates to {@code WholeProgramInference.addMethodDeclarationAnnotation}, which + * special-cases purity annotations: that method lubs a purity argument with whatever purity + * annotation is already present on {@code elt}. + * + * @param kinds the set of purity kinds to use to infer the annotation + * @param wpi the whole program inference instance to use to do the inferring + * @param elt the element whose purity is being inferred + * + private void inferPurityAnno( + EnumSet kinds, WholeProgramInference wpi, ExecutableElement elt) { + if (kinds.size() == 2) { + wpi.addMethodDeclarationAnnotation(elt, PURE, true); + } else if (kinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + wpi.addMethodDeclarationAnnotation(elt, SIDE_EFFECT_FREE, true); + } else if (kinds.contains(Pure.Kind.DETERMINISTIC)) { + wpi.addMethodDeclarationAnnotation(elt, DETERMINISTIC, true); + } else { + assert kinds.isEmpty(); + wpi.addMethodDeclarationAnnotation(elt, IMPURE, true); + } + } + */ + + /** + * Issue a warning if the result type of the constructor declaration is not top. If it is a + * supertype of the class, then a type.invalid.conflicting.annos error will also be issued by + * {@link + * #isValidUse(AnnotatedTypeMirror.AnnotatedDeclaredType,AnnotatedTypeMirror.AnnotatedDeclaredType,Tree)}. + * + * @param constructorType the AnnotatedExecutableType for the constructor + * @param constructorElement the element that declares the constructor + */ + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + AnnotatedTypeMirror returnType = constructorType.getReturnType(); + AnnotationMirrorSet constructorAnnotations = returnType.getAnnotations(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + + for (AnnotationMirror top : tops) { + AnnotationMirror constructorAnno = + qualHierarchy.findAnnotationInHierarchy(constructorAnnotations, top); + if (!AnnotationUtils.areSame(top, constructorAnno)) { + checker.reportWarning( + constructorElement, "inconsistent.constructor.type", constructorAnno, top); + } + } + } + + /** + * Reports errors found during purity checking. + * + * @param result whether the method is deterministic and/or side-effect-free + * @param tree the method + * @param expectedKinds the expected purity for the method + */ + protected void reportPurityErrors( + PurityResult result, MethodTree tree, EnumSet expectedKinds) { + assert !result.isPure(expectedKinds); + EnumSet violations = EnumSet.copyOf(expectedKinds); + violations.removeAll(result.getKinds()); + if (violations.contains(Pure.Kind.DETERMINISTIC) + || violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + String msgKeyPrefix; + if (!violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + msgKeyPrefix = "purity.not.deterministic."; + } else if (!violations.contains(Pure.Kind.DETERMINISTIC)) { + msgKeyPrefix = "purity.not.sideeffectfree."; + } else { + msgKeyPrefix = "purity.not.deterministic.not.sideeffectfree."; + } + for (IPair r : result.getNotBothReasons()) { + reportPurityError(msgKeyPrefix, r); + } + if (violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + for (IPair r : result.getNotSEFreeReasons()) { + reportPurityError("purity.not.sideeffectfree.", r); } - classTree.getModifiers().accept(polyTreeScanner, "in a class declaration"); - if (classTree.getExtendsClause() != null) { - classTree.getExtendsClause().accept(polyTreeScanner, "in a class declaration"); + } + if (violations.contains(Pure.Kind.DETERMINISTIC)) { + for (IPair r : result.getNotDetReasons()) { + reportPurityError("purity.not.deterministic.", r); } - for (Tree tree : classTree.getImplementsClause()) { - tree.accept(polyTreeScanner, "in a class declaration"); + } + } + } + + /** + * Reports a single purity error. + * + * @param msgKeyPrefix the prefix of the message key to use when reporting + * @param r the result to report + */ + private void reportPurityError(String msgKeyPrefix, IPair r) { + String reason = r.second; + @SuppressWarnings("compilermessages") + @CompilerMessageKey String msgKey = msgKeyPrefix + reason; + if (reason.equals("call")) { + if (r.first.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree mitree = (MethodInvocationTree) r.first; + checker.reportError(r.first, msgKey, mitree.getMethodSelect()); + } else { + NewClassTree nctree = (NewClassTree) r.first; + checker.reportError(r.first, msgKey, nctree.getIdentifier()); + } + } else { + checker.reportError(r.first, msgKey); + } + } + + /** + * Check the contracts written on a method declaration. Ensures that the postconditions hold on + * exit, and that the contracts are well-formed. + * + * @param methodTree the method declaration + * @param methodElement the method element + * @param formalParamNames the formal parameter names + * @param abstractMethod whether the method is abstract + */ + private void checkContractsAtMethodDeclaration( + MethodTree methodTree, + ExecutableElement methodElement, + List formalParamNames, + boolean abstractMethod) { + Set contracts = atypeFactory.getContractsFromMethod().getContracts(methodElement); + + if (contracts.isEmpty()) { + return; + } + StringToJavaExpression stringToJavaExpr = + stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, methodTree, checker); + for (Contract contract : contracts) { + String expressionString = contract.expressionString; + AnnotationMirror annotation = + contract.viewpointAdaptDependentTypeAnnotation( + atypeFactory, stringToJavaExpr, methodTree); + + JavaExpression exprJe; + try { + exprJe = StringToJavaExpression.atMethodBody(expressionString, methodTree, checker); + } catch (JavaExpressionParseException e) { + DiagMessage diagMessage = e.getDiagMessage(); + if (diagMessage.getMessageKey().equals("flowexpr.parse.error")) { + String s = + String.format( + "'%s' in the %s %s on the declaration of method '%s': ", + expressionString, + contract.kind.errorKey, + contract.contractAnnotation.getAnnotationType().asElement().getSimpleName(), + methodTree.getName().toString()); + checker.reportError(methodTree, "flowexpr.parse.error", s + diagMessage.getArgs()[0]); + } else { + checker.report(methodTree, e.getDiagMessage()); } - for (Tree tree : classTree.getTypeParameters()) { - tree.accept(polyTreeScanner, "in a class declaration"); + continue; + } + if (!CFAbstractStore.canInsertJavaExpression(exprJe)) { + checker.reportError(methodTree, "flowexpr.parse.error", expressionString); + continue; + } + if (!abstractMethod && contract.kind != Contract.Kind.PRECONDITION) { + // Check the contract, which is a postcondition. + // Preconditions are checked at method invocations, not declarations. + + switch (contract.kind) { + case POSTCONDITION: + checkPostcondition(methodTree, annotation, exprJe); + break; + case CONDITIONALPOSTCONDITION: + checkConditionalPostcondition( + methodTree, annotation, exprJe, ((ConditionalPostcondition) contract).resultValue); + break; + default: + throw new BugInCF("Impossible: " + contract.kind); } - } + } - /** - * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on - * the type parameters declaration. - * - * @param typeParameterTrees the type parameters to check - */ - protected void checkForPolymorphicQualifiers( - List typeParameterTrees) { - for (Tree tree : typeParameterTrees) { - tree.accept(polyTreeScanner, "in a type parameter"); - } + if (formalParamNames != null && formalParamNames.contains(expressionString)) { + String locationOfExpression = + contract.kind.errorKey + + " " + + contract.contractAnnotation.getAnnotationType().asElement().getSimpleName() + + " on the declaration"; + checker.reportWarning( + methodTree, + "expression.parameter.name.shadows.field", + locationOfExpression, + methodTree.getName().toString(), + expressionString, + expressionString, + formalParamNames.indexOf(expressionString) + 1); + } + + checkParametersAreEffectivelyFinal(methodTree, exprJe); + } + } + + /** + * Scans a {@link JavaExpression} and adds all the parameters in the {@code JavaExpression} to the + * passed set. + */ + private final JavaExpressionScanner> findParameters = + new JavaExpressionScanner>() { + @Override + protected Void visitLocalVariable(LocalVariable localVarExpr, Set parameters) { + if (localVarExpr.getElement().getKind() == ElementKind.PARAMETER) { + parameters.add(localVarExpr.getElement()); + } + return super.visitLocalVariable(localVarExpr, parameters); + } + }; + + /** + * Check that the parameters used in {@code javaExpression} are effectively final for method + * {@code method}. + * + * @param methodDeclTree a method declaration + * @param javaExpression a Java expression + */ + private void checkParametersAreEffectivelyFinal( + MethodTree methodDeclTree, JavaExpression javaExpression) { + // check that all parameters used in the expression are + // effectively final, so that they cannot be modified + Set parameters = new ArraySet<>(2); + findParameters.scan(javaExpression, parameters); + for (Element parameter : parameters) { + if (!ElementUtils.isEffectivelyFinal(parameter)) { + checker.reportError( + methodDeclTree, + "flowexpr.parameter.not.final", + parameter.getSimpleName(), + javaExpression); + } + } + } + + /** + * Check that the expression's type is annotated with {@code annotation} at the regular exit + * store. + * + * @param methodTree declaration of the method + * @param annotation expression's type must have this annotation + * @param expression the expression that must have an annotation + */ + protected void checkPostcondition( + MethodTree methodTree, AnnotationMirror annotation, JavaExpression expression) { + CFAbstractStore exitStore = atypeFactory.getRegularExitStore(methodTree); + if (exitStore == null) { + // If there is no regular exitStore, then the method cannot reach the regular exit and + // there is no need to check anything. + } else { + CFAbstractValue value = exitStore.getValue(expression); + AnnotationMirror inferredAnno = null; + if (value != null) { + AnnotationMirrorSet annos = value.getAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); + } + if (!checkContract(expression, annotation, inferredAnno, exitStore)) { + checker.reportError( + methodTree, + "contracts.postcondition.not.satisfied", + methodTree.getName(), + contractExpressionAndType(expression.toString(), inferredAnno), + contractExpressionAndType(expression.toString(), annotation)); + } + } + } + + /** + * Returns a string representation of an expression and type qualifier. + * + * @param expression a Java expression + * @param qualifier the expression's type, or null if no information is available + * @return a string representation of the expression and type qualifier + */ + protected String contractExpressionAndType( + String expression, @Nullable AnnotationMirror qualifier) { + if (qualifier == null) { + return "no information about " + expression; + } else { + return expression + + " is " + + atypeFactory.getAnnotationFormatter().formatAnnotationMirror(qualifier); + } + } + + /** + * Check that the expression's type is annotated with {@code annotation} at every regular exit + * that returns {@code result}. + * + * @param methodTree tree of method with the postcondition + * @param annotation expression's type must have this annotation + * @param expression the expression that the postcondition concerns + * @param result result for which the postcondition is valid + */ + protected void checkConditionalPostcondition( + MethodTree methodTree, + AnnotationMirror annotation, + JavaExpression expression, + boolean result) { + boolean booleanReturnType = + TypesUtils.isBooleanType(TreeUtils.typeOf(methodTree.getReturnType())); + if (!booleanReturnType) { + checker.reportError(methodTree, "contracts.conditional.postcondition.invalid.returntype"); + // No reason to go ahead with further checking. The + // annotation is invalid. + return; } - /** - * Issues an error if {@code classTree} has polymorphic fields but is not annotated with - * {@code @HasQualifierParameter}. Always issue a warning if the type of a static field is - * annotated with a polymorphic qualifier. - * - *

Issues an error if {@code classTree} extends or implements a class/interface that has a - * qualifier parameter, but this class does not. - * - * @param classTree the ClassTree to check for polymorphic fields - */ - protected void checkQualifierParameter(ClassTree classTree) { - // Set of polymorphic qualifiers for hierarchies that do not have a qualifier parameter and - // therefor cannot appear on a field. - AnnotationMirrorSet illegalOnFieldsPolyQual = new AnnotationMirrorSet(); - // Set of polymorphic annotations for all hierarchies - AnnotationMirrorSet polys = new AnnotationMirrorSet(); - TypeElement classElement = TreeUtils.elementFromDeclaration(classTree); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); - if (poly != null) { - polys.add(poly); - } - // else { - // If there is no polymorphic qualifier in the hierarchy, it could still have a - // @HasQualifierParameter that must be checked. - // } - - if (atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) - && atypeFactory.hasExplicitNoQualifierParameterInHierarchy(classElement, top)) { - checker.reportError(classTree, "conflicting.qual.param", top); - } + for (IPair pair : atypeFactory.getReturnStatementStores(methodTree)) { + ReturnNode returnStmt = pair.first; - if (atypeFactory.hasQualifierParameterInHierarchy(classElement, top)) { - continue; - } + Node retValNode = returnStmt.getResult(); + Boolean retVal = + retValNode instanceof BooleanLiteralNode + ? ((BooleanLiteralNode) retValNode).getValue() + : null; - if (poly != null) { - illegalOnFieldsPolyQual.add(poly); - } - Element extendsEle = TypesUtils.getTypeElement(classElement.getSuperclass()); - if (extendsEle != null - && atypeFactory.hasQualifierParameterInHierarchy(extendsEle, top)) { - checker.reportError(classTree, "missing.has.qual.param", top); - } else { - for (TypeMirror interfaceType : classElement.getInterfaces()) { - Element interfaceEle = TypesUtils.getTypeElement(interfaceType); - if (atypeFactory.hasQualifierParameterInHierarchy(interfaceEle, top)) { - checker.reportError(classTree, "missing.has.qual.param", top); - break; // only issue error once - } - } - } - } - - for (Tree mem : classTree.getMembers()) { - if (mem.getKind() == Tree.Kind.VARIABLE) { - AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(mem); - List hasIllegalPoly; - if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration((VariableTree) mem))) { - // A polymorphic qualifier is not allowed on a static field even if the class - // has a qualifier parameter. - hasIllegalPoly = polyScanner.visit(fieldType, polys); - } else { - hasIllegalPoly = polyScanner.visit(fieldType, illegalOnFieldsPolyQual); - } - for (DiagMessage dm : hasIllegalPoly) { - checker.report(mem, dm); - } - } - } - } - - /** - * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a - * use of one of the polymorphic qualifiers. - */ - private final PolyTypeScanner polyScanner = new PolyTypeScanner(); - - /** - * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a - * use of one of the polymorphic qualifiers. - */ - static class PolyTypeScanner - extends SimpleAnnotatedTypeScanner, AnnotationMirrorSet> { - - /** Create PolyTypeScanner. */ - private PolyTypeScanner() { - super(DiagMessage::mergeLists, Collections.emptyList()); - } - - @Override - protected List defaultAction( - AnnotatedTypeMirror type, AnnotationMirrorSet polys) { - if (type == null) { - return Collections.emptyList(); - } - - for (AnnotationMirror poly : polys) { - if (type.hasAnnotation(poly)) { - return Collections.singletonList( - DiagMessage.error("invalid.polymorphic.qualifier.use", poly)); - } - } - return Collections.emptyList(); - } - } - - /** - * In {@code @A class X extends @B Y implements @C Z {}}, enforce that {@code @A} must be a - * subtype of {@code @B} and {@code @C}. - * - *

Also validate the types of the extends and implements clauses. - * - * @param classTree class tree to check - */ - protected void checkExtendsAndImplements(ClassTree classTree) { - if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { - // Don't check extends clause on anonymous classes. - return; - } - if (classTree.getExtendsClause() == null && classTree.getImplementsClause().isEmpty()) { - // Nothing to do - return; - } - - TypeMirror classType = TreeUtils.typeOf(classTree); - AnnotationMirrorSet classBounds = atypeFactory.getTypeDeclarationBounds(classType); - // If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A. - // classTree.getExtendsClause() is null when there is no explicitly-written extends clause, - // as in "class X {}". This is equivalent to writing "class X extends @Top Object {}", so - // there is no need to do any subtype checking. - if (classTree.getExtendsClause() != null) { - Tree boundClause = classTree.getExtendsClause(); - checkExtendsOrImplements(boundClause, classBounds, classType, true); - } - // Do the same check as above for implements clauses. - for (Tree boundClause : classTree.getImplementsClause()) { - checkExtendsOrImplements(boundClause, classBounds, classType, false); - } - } - - /** - * Helper for {@link #checkExtendsAndImplements} that checks one extends or implements clause. - * - * @param boundClause an extends or implements clause - * @param classBounds the type declarations bounds to check for consistency with {@code - * boundClause} - * @param classType the type being declared - * @param isExtends true for an extends clause, false for an implements clause - */ - protected void checkExtendsOrImplements( - Tree boundClause, - AnnotationMirrorSet classBounds, - TypeMirror classType, - boolean isExtends) { - AnnotatedTypeMirror boundType = atypeFactory.getTypeOfExtendsImplements(boundClause); - TypeMirror boundTM = boundType.getUnderlyingType(); - for (AnnotationMirror classAnno : classBounds) { - AnnotationMirror boundAnno = boundType.getAnnotationInHierarchy(classAnno); - if (!qualHierarchy.isSubtypeShallow(classAnno, classType, boundAnno, boundTM)) { - checker.reportError( - boundClause, - (isExtends - ? "declaration.inconsistent.with.extends.clause" - : "declaration.inconsistent.with.implements.clause"), - classAnno, - boundAnno); - } - } - } - - /** - * Check that the field invariant declaration annotations meet the following requirements: - * - *

    - * - *
  1. If the superclass of {@code classTree} has a field invariant, then the field - * invariant for {@code classTree} must include all the fields in the superclass invariant - * and those fields' annotations must be a subtype (or equal) to the annotations for those - * fields in the superclass. - *
  2. The fields in the invariant must be a.) final and b.) declared in a - * superclass of {@code classTree}. - *
  3. The qualifier for each field must be a subtype of the annotation on the - * declaration of that field. - *
  4. The field invariant has an equal number of fields and qualifiers, or it has - * one qualifier and at least one field. - *
- * - * @param classTree class that might have a field invariant - * @checker_framework.manual #field-invariants Field invariants - */ - protected void checkFieldInvariantDeclarations(ClassTree classTree) { - TypeElement elt = TreeUtils.elementFromDeclaration(classTree); - FieldInvariants invariants = atypeFactory.getFieldInvariants(elt); - if (invariants == null) { - // No invariants to check - return; - } - - // Where to issue an error, if any. - Tree errorTree = - atypeFactory.getFieldInvariantAnnotationTree( - classTree.getModifiers().getAnnotations()); - if (errorTree == null) { - // If the annotation was inherited, then there is no annotation tree, so issue the - // error on the class. - errorTree = classTree; - } - - // Checks #4 (see method Javadoc) - if (!invariants.isWellFormed()) { - checker.reportError(errorTree, "field.invariant.not.wellformed"); - return; - } - - TypeMirror superClass = elt.getSuperclass(); - List fieldsNotFound = new ArrayList<>(invariants.getFields()); - Set fieldElts = - ElementUtils.findFieldsInTypeOrSuperType(superClass, fieldsNotFound); - - // Checks that fields are declared in super class. (#2b) - if (!fieldsNotFound.isEmpty()) { - String notFoundString = String.join(", ", fieldsNotFound); - checker.reportError(errorTree, "field.invariant.not.found", notFoundString); - } - - FieldInvariants superInvar = - atypeFactory.getFieldInvariants(TypesUtils.getTypeElement(superClass)); - if (superInvar != null) { - // Checks #3 (see method Javadoc) - DiagMessage superError = invariants.isStrongerThan(superInvar); - if (superError != null) { - checker.report(errorTree, superError); - } - } - - List notFinal = new ArrayList<>(fieldElts.size()); - for (VariableElement field : fieldElts) { - String fieldName = field.getSimpleName().toString(); - if (!ElementUtils.isFinal(field)) { - notFinal.add(fieldName); - } - AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(field); - - List annos = invariants.getQualifiersFor(field.getSimpleName()); - for (AnnotationMirror invariantAnno : annos) { - AnnotationMirror declaredAnno = - fieldType.getEffectiveAnnotationInHierarchy(invariantAnno); - if (declaredAnno == null) { - // invariant anno isn't in this hierarchy - continue; - } - - if (!typeHierarchy.isSubtypeShallowEffective(invariantAnno, fieldType)) { - // Checks #3 - checker.reportError( - errorTree, - "field.invariant.not.subtype", - fieldName, - invariantAnno, - declaredAnno); - } - } - } - - // Checks #2a - if (!notFinal.isEmpty()) { - String notFinalString = String.join(", ", notFinal); - checker.reportError(errorTree, "field.invariant.not.final", notFinalString); - } - } - - /** - * Check the default constructor. - * - * @param tree a class declaration - */ - protected void checkDefaultConstructor(ClassTree tree) {} - - /** - * Checks that the method obeys override and subtype rules to all overridden methods. (Uses the - * pseudo-assignment logic to do so.) - * - *

The override rule specifies that a method, m1, may override a method m2 only if: - * - *

    - *
  • m1 return type is a subtype of m2 - *
  • m1 receiver type is a supertype of m2 - *
  • m1 parameters are supertypes of corresponding m2 parameters - *
- * - * Also, it issues a "missing.this" error for static method annotated receivers. - */ - @Override - public Void visitMethod(MethodTree tree, Void p) { - // We copy the result from getAnnotatedType to ensure that circular types (e.g. K extends - // Comparable) are represented by circular AnnotatedTypeMirrors, which avoids problems - // with later checks. - // TODO: Find a cleaner way to ensure circular AnnotatedTypeMirrors. - AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); - MethodTree preMT = methodTree; - methodTree = tree; - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); - - warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); - - if (tree.getReturnType() != null) { - visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getReturnType()); - warnRedundantAnnotations(tree.getReturnType(), methodType.getReturnType()); - } else if (TreeUtils.isConstructor(tree)) { - maybeReportAnnoOnIrrelevant( - tree.getModifiers(), - methodType.getReturnType().getUnderlyingType(), - tree.getModifiers().getAnnotations()); - } - - try { - if (TreeUtils.isAnonymousConstructor(tree)) { - // We shouldn't dig deeper - return null; - } - - if (TreeUtils.isConstructor(tree)) { - checkConstructorResult(methodType, methodElement); - } - - checkPurity(tree); - - // Passing the whole method/constructor validates the return type - validateTypeOf(tree); - - // Validate types in throws clauses - for (ExpressionTree thr : tree.getThrows()) { - validateTypeOf(thr); - } - - atypeFactory.getDependentTypesHelper().checkMethodForErrorExpressions(tree, methodType); - - // Check method overrides - AnnotatedDeclaredType enclosingType = - (AnnotatedDeclaredType) - atypeFactory.getAnnotatedType(methodElement.getEnclosingElement()); - - // Find which methods this method overrides - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedDeclaredType overriddenType = pair.getKey(); - ExecutableElement overriddenMethodElt = pair.getValue(); - AnnotatedExecutableType overriddenMethodType = - AnnotatedTypes.asMemberOf( - types, atypeFactory, overriddenType, overriddenMethodElt); - if (!checkOverride(tree, enclosingType, overriddenMethodType, overriddenType)) { - // Stop at the first mismatch; this makes a difference only if - // -Awarns is passed, in which case multiple warnings might be raised on - // the same method, not adding any value. See Issue 373. - break; - } - } - - // Check well-formedness of pre/postcondition - boolean abstractMethod = - methodElement.getModifiers().contains(Modifier.ABSTRACT) - || methodElement.getModifiers().contains(Modifier.NATIVE); - - List formalParamNames = - CollectionsPlume.mapList( - (VariableTree param) -> param.getName().toString(), - tree.getParameters()); - checkContractsAtMethodDeclaration( - tree, methodElement, formalParamNames, abstractMethod); - /* NO-AFU - // Infer postconditions - if (shouldPerformContractInference()) { - assert ElementUtils.isElementFromSourceCode(methodElement); - - // TODO: Infer conditional postconditions too. - CFAbstractStore store = atypeFactory.getRegularExitStore(tree); - // The store is null if the method has no normal exit, for example if its body is a - // throw statement. - if (store != null) { - atypeFactory - .getWholeProgramInference() - .updateContracts(Analysis.BeforeOrAfter.AFTER, methodElement, store); - } - } - */ - - checkForPolymorphicQualifiers(tree.getTypeParameters()); - - return super.visitMethod(tree, p); - } finally { - methodTree = preMT; - } - } - - /* NO-AFU - * Should Whole Program Inference attempt to infer contract annotations? Typically, the answer is - * "yes" whenever WPI is enabled, but this method exists to allow subclasses to customize that - * behavior. - * - * @return true if contract inference should be performed, false if it should be disabled (even - * when WPI is enabled) - */ - /* NO-AFU - protected boolean shouldPerformContractInference() { - return atypeFactory.getWholeProgramInference() != null; - } - */ - - /** - * Check method purity if needed. Note that overriding rules are checked as part of {@link - * #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType)}. - * - * @param tree the method tree to check - */ - protected void checkPurity(MethodTree tree) { - if (!checkPurity) { - return; - } - - if (!suggestPureMethods && !PurityUtils.hasPurityAnnotation(atypeFactory, tree)) { - // There is nothing to check. - return; - } - - // check "no" purity - EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, tree); - // @Deterministic makes no sense for a void method or constructor - boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); - if (isDeterministic) { - if (TreeUtils.isConstructor(tree)) { - checker.reportWarning(tree, "purity.deterministic.constructor"); - } else if (TreeUtils.isVoidReturn(tree)) { - checker.reportWarning(tree, "purity.deterministic.void.method"); - } - } - - TreePath body = atypeFactory.getPath(tree.getBody()); - PurityResult r; - if (body == null) { - r = new PurityResult(); - } else { - r = - PurityChecker.checkPurity( - body, - atypeFactory, - assumeSideEffectFree, - assumeDeterministic, - assumePureGetters); - } - if (!r.isPure(kinds)) { - reportPurityErrors(r, tree, kinds); - } - - if (suggestPureMethods && !TreeUtils.isSynthetic(tree)) { - // Issue a warning if the method is pure, but not annotated as such. - EnumSet additionalKinds = r.getKinds().clone(); - /* NO-AFU - if (!infer) { - // During WPI, propagate all purity kinds, even those that are already - // present (because they were inferred in a previous WPI round). - */ - additionalKinds.removeAll(kinds); - /* NO-AFU - } - */ - if (TreeUtils.isConstructor(tree) || TreeUtils.isVoidReturn(tree)) { - additionalKinds.remove(Pure.Kind.DETERMINISTIC); - } - if (additionalKinds.isEmpty()) { - // No need to suggest @Impure, since it is equivalent to no annotation. - } else if (additionalKinds.size() == 2) { - checker.reportWarning(tree, "purity.more.pure", tree.getName()); - } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - checker.reportWarning(tree, "purity.more.sideeffectfree", tree.getName()); - } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { - checker.reportWarning(tree, "purity.more.deterministic", tree.getName()); - } else { - throw new BugInCF("Unexpected purity kind in " + additionalKinds); - } - /* NO-AFU - if (infer) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); - inferPurityAnno(additionalKinds, wpi, methodElt); - // The purity of overridden methods is impacted by the purity of this method. If a - // superclass method is pure, but an implementation in a subclass is not, WPI ought to treat - // **neither** as pure. The purity kind of the superclass method is the LUB of its own - // purity and the purity of all the methods that override it. Logically, this rule is the - // same as the WPI rule for overrides, but purity isn't a type system and therefore must be - // special-cased. - Set overriddenMethods = - ElementUtils.getOverriddenMethods(methodElt, types); - for (ExecutableElement overriddenElt : overriddenMethods) { - inferPurityAnno(additionalKinds, wpi, overriddenElt); - } - } - */ - } - } - - /* NO-AFU - * Infer a purity annotation for {@code elt} by converting {@code kinds} into a method annotation. - * - *

This method delegates to {@code WholeProgramInference.addMethodDeclarationAnnotation}, which - * special-cases purity annotations: that method lubs a purity argument with whatever purity - * annotation is already present on {@code elt}. - * - * @param kinds the set of purity kinds to use to infer the annotation - * @param wpi the whole program inference instance to use to do the inferring - * @param elt the element whose purity is being inferred - * - private void inferPurityAnno( - EnumSet kinds, WholeProgramInference wpi, ExecutableElement elt) { - if (kinds.size() == 2) { - wpi.addMethodDeclarationAnnotation(elt, PURE, true); - } else if (kinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - wpi.addMethodDeclarationAnnotation(elt, SIDE_EFFECT_FREE, true); - } else if (kinds.contains(Pure.Kind.DETERMINISTIC)) { - wpi.addMethodDeclarationAnnotation(elt, DETERMINISTIC, true); - } else { - assert kinds.isEmpty(); - wpi.addMethodDeclarationAnnotation(elt, IMPURE, true); - } - } - */ - - /** - * Issue a warning if the result type of the constructor declaration is not top. If it is a - * supertype of the class, then a type.invalid.conflicting.annos error will also be issued by - * {@link - * #isValidUse(AnnotatedTypeMirror.AnnotatedDeclaredType,AnnotatedTypeMirror.AnnotatedDeclaredType,Tree)}. - * - * @param constructorType the AnnotatedExecutableType for the constructor - * @param constructorElement the element that declares the constructor - */ - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - AnnotatedTypeMirror returnType = constructorType.getReturnType(); - AnnotationMirrorSet constructorAnnotations = returnType.getAnnotations(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - - for (AnnotationMirror top : tops) { - AnnotationMirror constructorAnno = - qualHierarchy.findAnnotationInHierarchy(constructorAnnotations, top); - if (!AnnotationUtils.areSame(top, constructorAnno)) { - checker.reportWarning( - constructorElement, "inconsistent.constructor.type", constructorAnno, top); - } - } - } - - /** - * Reports errors found during purity checking. - * - * @param result whether the method is deterministic and/or side-effect-free - * @param tree the method - * @param expectedKinds the expected purity for the method - */ - protected void reportPurityErrors( - PurityResult result, MethodTree tree, EnumSet expectedKinds) { - assert !result.isPure(expectedKinds); - EnumSet violations = EnumSet.copyOf(expectedKinds); - violations.removeAll(result.getKinds()); - if (violations.contains(Pure.Kind.DETERMINISTIC) - || violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - String msgKeyPrefix; - if (!violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - msgKeyPrefix = "purity.not.deterministic."; - } else if (!violations.contains(Pure.Kind.DETERMINISTIC)) { - msgKeyPrefix = "purity.not.sideeffectfree."; - } else { - msgKeyPrefix = "purity.not.deterministic.not.sideeffectfree."; - } - for (IPair r : result.getNotBothReasons()) { - reportPurityError(msgKeyPrefix, r); - } - if (violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - for (IPair r : result.getNotSEFreeReasons()) { - reportPurityError("purity.not.sideeffectfree.", r); - } - } - if (violations.contains(Pure.Kind.DETERMINISTIC)) { - for (IPair r : result.getNotDetReasons()) { - reportPurityError("purity.not.deterministic.", r); - } - } - } - } - - /** - * Reports a single purity error. - * - * @param msgKeyPrefix the prefix of the message key to use when reporting - * @param r the result to report - */ - private void reportPurityError(String msgKeyPrefix, IPair r) { - String reason = r.second; - @SuppressWarnings("compilermessages") - @CompilerMessageKey String msgKey = msgKeyPrefix + reason; - if (reason.equals("call")) { - if (r.first.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree mitree = (MethodInvocationTree) r.first; - checker.reportError(r.first, msgKey, mitree.getMethodSelect()); - } else { - NewClassTree nctree = (NewClassTree) r.first; - checker.reportError(r.first, msgKey, nctree.getIdentifier()); - } - } else { - checker.reportError(r.first, msgKey); - } - } - - /** - * Check the contracts written on a method declaration. Ensures that the postconditions hold on - * exit, and that the contracts are well-formed. - * - * @param methodTree the method declaration - * @param methodElement the method element - * @param formalParamNames the formal parameter names - * @param abstractMethod whether the method is abstract - */ - private void checkContractsAtMethodDeclaration( - MethodTree methodTree, - ExecutableElement methodElement, - List formalParamNames, - boolean abstractMethod) { - Set contracts = atypeFactory.getContractsFromMethod().getContracts(methodElement); - - if (contracts.isEmpty()) { - return; - } - StringToJavaExpression stringToJavaExpr = - stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, methodTree, checker); - for (Contract contract : contracts) { - String expressionString = contract.expressionString; - AnnotationMirror annotation = - contract.viewpointAdaptDependentTypeAnnotation( - atypeFactory, stringToJavaExpr, methodTree); - - JavaExpression exprJe; - try { - exprJe = StringToJavaExpression.atMethodBody(expressionString, methodTree, checker); - } catch (JavaExpressionParseException e) { - DiagMessage diagMessage = e.getDiagMessage(); - if (diagMessage.getMessageKey().equals("flowexpr.parse.error")) { - String s = - String.format( - "'%s' in the %s %s on the declaration of method '%s': ", - expressionString, - contract.kind.errorKey, - contract.contractAnnotation - .getAnnotationType() - .asElement() - .getSimpleName(), - methodTree.getName().toString()); - checker.reportError( - methodTree, "flowexpr.parse.error", s + diagMessage.getArgs()[0]); - } else { - checker.report(methodTree, e.getDiagMessage()); - } - continue; - } - if (!CFAbstractStore.canInsertJavaExpression(exprJe)) { - checker.reportError(methodTree, "flowexpr.parse.error", expressionString); - continue; - } - if (!abstractMethod && contract.kind != Contract.Kind.PRECONDITION) { - // Check the contract, which is a postcondition. - // Preconditions are checked at method invocations, not declarations. - - switch (contract.kind) { - case POSTCONDITION: - checkPostcondition(methodTree, annotation, exprJe); - break; - case CONDITIONALPOSTCONDITION: - checkConditionalPostcondition( - methodTree, - annotation, - exprJe, - ((ConditionalPostcondition) contract).resultValue); - break; - default: - throw new BugInCF("Impossible: " + contract.kind); - } - } - - if (formalParamNames != null && formalParamNames.contains(expressionString)) { - String locationOfExpression = - contract.kind.errorKey - + " " - + contract.contractAnnotation - .getAnnotationType() - .asElement() - .getSimpleName() - + " on the declaration"; - checker.reportWarning( - methodTree, - "expression.parameter.name.shadows.field", - locationOfExpression, - methodTree.getName().toString(), - expressionString, - expressionString, - formalParamNames.indexOf(expressionString) + 1); - } - - checkParametersAreEffectivelyFinal(methodTree, exprJe); - } - } - - /** - * Scans a {@link JavaExpression} and adds all the parameters in the {@code JavaExpression} to - * the passed set. - */ - private final JavaExpressionScanner> findParameters = - new JavaExpressionScanner>() { - @Override - protected Void visitLocalVariable( - LocalVariable localVarExpr, Set parameters) { - if (localVarExpr.getElement().getKind() == ElementKind.PARAMETER) { - parameters.add(localVarExpr.getElement()); - } - return super.visitLocalVariable(localVarExpr, parameters); - } - }; - - /** - * Check that the parameters used in {@code javaExpression} are effectively final for method - * {@code method}. - * - * @param methodDeclTree a method declaration - * @param javaExpression a Java expression - */ - private void checkParametersAreEffectivelyFinal( - MethodTree methodDeclTree, JavaExpression javaExpression) { - // check that all parameters used in the expression are - // effectively final, so that they cannot be modified - Set parameters = new ArraySet<>(2); - findParameters.scan(javaExpression, parameters); - for (Element parameter : parameters) { - if (!ElementUtils.isEffectivelyFinal(parameter)) { - checker.reportError( - methodDeclTree, - "flowexpr.parameter.not.final", - parameter.getSimpleName(), - javaExpression); - } - } - } - - /** - * Check that the expression's type is annotated with {@code annotation} at the regular exit - * store. - * - * @param methodTree declaration of the method - * @param annotation expression's type must have this annotation - * @param expression the expression that must have an annotation - */ - protected void checkPostcondition( - MethodTree methodTree, AnnotationMirror annotation, JavaExpression expression) { - CFAbstractStore exitStore = atypeFactory.getRegularExitStore(methodTree); - if (exitStore == null) { - // If there is no regular exitStore, then the method cannot reach the regular exit and - // there is no need to check anything. - } else { - CFAbstractValue value = exitStore.getValue(expression); - AnnotationMirror inferredAnno = null; - if (value != null) { - AnnotationMirrorSet annos = value.getAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); - } - if (!checkContract(expression, annotation, inferredAnno, exitStore)) { - checker.reportError( - methodTree, - "contracts.postcondition.not.satisfied", - methodTree.getName(), - contractExpressionAndType(expression.toString(), inferredAnno), - contractExpressionAndType(expression.toString(), annotation)); - } - } - } - - /** - * Returns a string representation of an expression and type qualifier. - * - * @param expression a Java expression - * @param qualifier the expression's type, or null if no information is available - * @return a string representation of the expression and type qualifier - */ - protected String contractExpressionAndType( - String expression, @Nullable AnnotationMirror qualifier) { - if (qualifier == null) { - return "no information about " + expression; - } else { - return expression - + " is " - + atypeFactory.getAnnotationFormatter().formatAnnotationMirror(qualifier); - } - } - - /** - * Check that the expression's type is annotated with {@code annotation} at every regular exit - * that returns {@code result}. - * - * @param methodTree tree of method with the postcondition - * @param annotation expression's type must have this annotation - * @param expression the expression that the postcondition concerns - * @param result result for which the postcondition is valid - */ - protected void checkConditionalPostcondition( - MethodTree methodTree, - AnnotationMirror annotation, - JavaExpression expression, - boolean result) { - boolean booleanReturnType = - TypesUtils.isBooleanType(TreeUtils.typeOf(methodTree.getReturnType())); - if (!booleanReturnType) { - checker.reportError( - methodTree, "contracts.conditional.postcondition.invalid.returntype"); - // No reason to go ahead with further checking. The - // annotation is invalid. - return; - } - - for (IPair pair : atypeFactory.getReturnStatementStores(methodTree)) { - ReturnNode returnStmt = pair.first; - - Node retValNode = returnStmt.getResult(); - Boolean retVal = - retValNode instanceof BooleanLiteralNode - ? ((BooleanLiteralNode) retValNode).getValue() - : null; - - TransferResult transferResult = (TransferResult) pair.second; - if (transferResult == null) { - // Unreachable return statements have no stores, but there is no need to check them. - continue; - } - CFAbstractStore exitStore = - (CFAbstractStore) - (result - ? transferResult.getThenStore() - : transferResult.getElseStore()); - CFAbstractValue value = exitStore.getValue(expression); - - // don't check if return statement certainly does not match 'result'. at the moment, - // this means the result is a boolean literal - if (!(retVal == null || retVal == result)) { - continue; - } - AnnotationMirror inferredAnno = null; - if (value != null) { - AnnotationMirrorSet annos = value.getAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); - } - - if (!checkContract(expression, annotation, inferredAnno, exitStore)) { - checker.reportError( - returnStmt.getTree(), - "contracts.conditional.postcondition.not.satisfied", - methodTree.getName(), - result, - contractExpressionAndType(expression.toString(), inferredAnno), - contractExpressionAndType(expression.toString(), annotation)); - } - } - } - - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void p) { - if (tree.getBounds().size() > 1) { - // The upper bound of the type parameter is an intersection - AnnotatedTypeVariable type = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tree); - AnnotatedIntersectionType intersection = - (AnnotatedIntersectionType) type.getUpperBound(); - checkExplicitAnnotationsOnIntersectionBounds(intersection, tree.getBounds()); - } - validateTypeOf(tree); - - return super.visitTypeParameter(tree, p); - } - - /** - * Issues "explicit.annotation.ignored" warning if any explicit annotation on an intersection - * bound is not the same as the primary annotation of the given intersection type. - * - * @param intersection type to use - * @param boundTrees trees of {@code intersection} bounds - */ - protected void checkExplicitAnnotationsOnIntersectionBounds( - AnnotatedIntersectionType intersection, List boundTrees) { - for (Tree boundTree : boundTrees) { - if (boundTree.getKind() != Tree.Kind.ANNOTATED_TYPE) { - continue; - } - List explictAnnos = - TreeUtils.annotationsFromTree((AnnotatedTypeTree) boundTree); - for (AnnotationMirror explictAnno : explictAnnos) { - if (atypeFactory.isSupportedQualifier(explictAnno)) { - AnnotationMirror anno = intersection.getAnnotationInHierarchy(explictAnno); - if (!AnnotationUtils.areSame(anno, explictAnno)) { - checker.reportWarning( - boundTree, - "explicit.annotation.ignored", - explictAnno, - anno, - explictAnno, - anno); - } - } - } - } - } - - // ********************************************************************** - // Assignment checkers and pseudo-assignments - // ********************************************************************** - - @Override - public Void visitVariable(VariableTree tree, Void p) { - warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); - - // VariableTree#getType returns null for binding variables from a DeconstructionPatternTree. - if (tree.getType() != null) { - visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getType()); - } - - AnnotatedTypeMirror variableType; - if (getCurrentPath().getParentPath() != null - && getCurrentPath().getParentPath().getLeaf().getKind() - == Tree.Kind.LAMBDA_EXPRESSION) { - // Calling getAnnotatedTypeLhs on a lambda parameter tree is possibly expensive - // because caching is turned off. This should be fixed by #979. - // See https://github.com/typetools/checker-framework/issues/2853 for an example. - variableType = atypeFactory.getAnnotatedType(tree); - } else { - variableType = atypeFactory.getAnnotatedTypeLhs(tree); - } - - atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(variableType, tree); - Element varEle = TreeUtils.elementFromDeclaration(tree); - if (varEle.getKind() == ElementKind.ENUM_CONSTANT) { - commonAssignmentCheck( - tree, tree.getInitializer(), "enum.declaration.type.incompatible"); - } else if (tree.getInitializer() != null) { - if (!TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { - // If there is no assignment in this variable declaration or it is declared using - // `var`, skip it. - // For a `var` declaration, TypeFromMemberVisitor#visitVariable already uses the - // type of the initializer for the variable type, so it would be redundant to check - // for compatibility here. - commonAssignmentCheck(tree, tree.getInitializer(), "assignment.type.incompatible"); - } - } else { - // commonAssignmentCheck validates the type of `tree`, - // so only validate if commonAssignmentCheck wasn't called - validateTypeOf(tree); - } - validateVariablesTargetLocation(tree, variableType); - warnRedundantAnnotations(tree, variableType); - return super.visitVariable(tree, p); - } - - /** - * Validate if the annotations on the VariableTree are at the right locations, which is - * specified by the meta-annotation @TargetLocations. The difference of this method between - * {@link BaseTypeVisitor#validateTargetLocation(Tree, AnnotatedTypeMirror, TypeUseLocation)} is - * that this one is only used in {@link BaseTypeVisitor#visitVariable(VariableTree, Void)} - * - * @param tree annotations on this VariableTree will be validated - * @param type the type of the tree - */ - protected void validateVariablesTargetLocation(Tree tree, AnnotatedTypeMirror type) { - if (ignoreTargetLocations) { - return; - } - Element element = TreeUtils.elementFromTree(tree); - - if (element != null) { - ElementKind elemKind = element.getKind(); - // TypeUseLocation.java doesn't have ENUM type use location right now. - for (AnnotationMirror am : type.getAnnotations()) { - List locations = - qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - if (locations == null || locations.contains(TypeUseLocation.ALL)) { - continue; - } - boolean issueError = true; - switch (elemKind) { - case LOCAL_VARIABLE: - if (locations.contains(TypeUseLocation.LOCAL_VARIABLE)) issueError = false; - break; - case EXCEPTION_PARAMETER: - if (locations.contains(TypeUseLocation.EXCEPTION_PARAMETER)) - issueError = false; - break; - case PARAMETER: - if (((VariableTree) tree).getName().contentEquals("this")) { - if (locations.contains(TypeUseLocation.RECEIVER)) { - issueError = false; - } - } else { - if (locations.contains(TypeUseLocation.PARAMETER)) { - issueError = false; - } - } - break; - case RESOURCE_VARIABLE: - if (locations.contains(TypeUseLocation.RESOURCE_VARIABLE)) { - issueError = false; - } - break; - case FIELD: - if (locations.contains(TypeUseLocation.FIELD)) { - issueError = false; - } - break; - case ENUM_CONSTANT: - if (locations.contains(TypeUseLocation.FIELD) - || locations.contains(TypeUseLocation.CONSTRUCTOR_RESULT)) { - issueError = false; - } - break; - default: - throw new BugInCF("Location not matched"); - } - if (issueError) { - checker.reportError( - tree, - "type.invalid.annotations.on.location", - am.toString(), - element.getKind().name()); - } - } - } - } - - /** - * Validate if the annotations on the tree are at the right locations, which are specified by - * the meta-annotation @TargetLocations. - * - * @param tree annotations on this VariableTree will be validated - * @param type the type of the tree - * @param required if all of the TypeUseLocations in {@code required} are not present in the - * specification of the annotation (@TargetLocations), issue an error. - */ - protected void validateTargetLocation( - Tree tree, AnnotatedTypeMirror type, TypeUseLocation required) { - if (ignoreTargetLocations) { - return; - } - for (AnnotationMirror am : type.getAnnotations()) { - List locations = - qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - if (locations == null || locations.contains(TypeUseLocation.ALL)) { - continue; - } - boolean issueError = !locations.contains(required); - - if (issueError) { - checker.reportError( - tree, - "type.invalid.annotations.on.location", - am.toString(), - required.toString()); - } - } - } - - /** - * Create a new map, which is used for declared type-use locations lookup. - * - * @return a new mapping from strings of qualifier names to their declared type-use locations. - */ - protected Map<@CanonicalName String, List> createQualAllowedLocations() { - HashMap<@CanonicalName String, List> qualAllowedLocations = - new HashMap<>(); - for (String qual : atypeFactory.getSupportedTypeQualifierNames()) { - Element elem = elements.getTypeElement(qual); - TargetLocations tls = elem.getAnnotation(TargetLocations.class); - // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means that - // the qualifier can be written on any type use. - if (tls == null) { - qualAllowedLocations.put(qual, null); - continue; - } - List locations = Arrays.asList(tls.value()); - qualAllowedLocations.put(qual, locations); - } - return qualAllowedLocations; - } - - /** - * Issues a "redundant.anno" warning if the annotation written on the type is the same as the - * default annotation for this type and location. - * - * @param tree an AST node - * @param type get the explicit annotation on this type and compare it with the default one for - * this type and location. - */ - protected void warnRedundantAnnotations(Tree tree, AnnotatedTypeMirror type) { - // Type variable uses don't have default annotations. So, any explicit annotation is not - // redundant. - if (!warnRedundantAnnotations || (type.getKind() == TypeKind.TYPEVAR)) { - return; - } - AnnotationMirrorSet explicitAnnos = type.getExplicitAnnotations(); - if (explicitAnnos.isEmpty()) { - return; - } - if (tree == null) { - throw new BugInCF("unexpected null tree argument!"); - } - - AnnotatedTypeMirror defaultType = atypeFactory.getDefaultAnnotations(tree, type); - for (AnnotationMirror explicitAnno : explicitAnnos) { - AnnotationMirror defaultAM = defaultType.getAnnotationInHierarchy(explicitAnno); - if (AnnotationUtils.areSame(defaultAM, explicitAnno)) { - checker.reportWarning(tree, "redundant.anno", defaultAM); - } - } - } - - /** - * Warn if a type annotation is written before a modifier such as "public" or before a - * declaration annotation. - * - * @param tree a VariableTree or a MethodTree - * @param modifiersTree the modifiers sub-tree of tree - */ - private void warnAboutTypeAnnotationsTooEarly(Tree tree, ModifiersTree modifiersTree) { - - // Don't issue warnings about compiler-inserted modifiers. - // This simple code completely igonores enum constants and try-with-resources declarations. - // It could be made to catch some user errors in those locations, but it doesn't seem worth - // the effort to do so. - if (tree.getKind() == Tree.Kind.VARIABLE) { - ElementKind varKind = TreeUtils.elementFromDeclaration((VariableTree) tree).getKind(); - switch (varKind) { - case ENUM_CONSTANT: - // Enum constants are "public static final" by default, so the annotation always - // appears to be before "public". - return; - case RESOURCE_VARIABLE: - // Try-with-resources variables are "final" by default, so the annotation always - // appears to be before "final". - return; - default: - if (TreeUtils.isAutoGeneratedRecordMember(tree)) { - // Annotations can appear on record fields before the class body, so don't - // issue a warning about those. - return; - } - // Nothing to do - } - } - - Set modifierSet = modifiersTree.getFlags(); - List annotations = modifiersTree.getAnnotations(); - - if (annotations.isEmpty()) { - return; - } - - // Warn about type annotations written before modifiers such as "public". javac retains no - // information about modifier locations. So, this is a very partial check: Issue a warning - // if a type annotation is at the very beginning of the VariableTree, and a modifier follows - // it. - - // Check if a type annotation precedes a declaration annotation. - int lastDeclAnnoIndex = -1; - for (int i = annotations.size() - 1; i > 0; i--) { // no need to check index 0 - if (!isTypeAnnotation(annotations.get(i))) { - lastDeclAnnoIndex = i; - break; - } - } - if (lastDeclAnnoIndex != -1) { - // Usually, there are few bad invariant annotations. - List badTypeAnnos = new ArrayList<>(2); - for (int i = 0; i < lastDeclAnnoIndex; i++) { - AnnotationTree anno = annotations.get(i); - if (isTypeAnnotation(anno)) { - badTypeAnnos.add(anno); - } - } - if (!badTypeAnnos.isEmpty()) { - checker.reportWarning( - tree, - "type.anno.before.decl.anno", - badTypeAnnos, - annotations.get(lastDeclAnnoIndex)); - } - } - - // Determine the length of the text that ought to precede the first type annotation. - // If the type annotation appears before that text could appear, then warn that a - // modifier appears after the type annotation. - // TODO: in the future, account for the lengths of declaration annotations. Length of - // toString of the annotation isn't useful, as it might be different length than original - // input. Can use JCTree.getEndPosition(EndPosTable) and - // com.sun.tools.javac.tree.EndPosTable, but it requires -Xjcov. - AnnotationTree firstAnno = annotations.get(0); - if (!modifierSet.isEmpty() && isTypeAnnotation(firstAnno)) { - int precedingTextLength = 0; - for (Modifier m : modifierSet) { - precedingTextLength += m.toString().length() + 1; // +1 for the space - } - int annoStartPos = ((JCTree) firstAnno).getStartPosition(); - int varStartPos = ((JCTree) tree).getStartPosition(); - if (annoStartPos < varStartPos + precedingTextLength) { - checker.reportWarning(tree, "type.anno.before.modifier", firstAnno, modifierSet); - } - } - } - - /** - * Return true if the given annotation is a type annotation: that is, its definition is - * meta-annotated with {@code @Target({TYPE_USE,....})}. - */ - private boolean isTypeAnnotation(AnnotationTree anno) { - Tree annoType = anno.getAnnotationType(); - ClassSymbol annoSymbol; - switch (annoType.getKind()) { - case IDENTIFIER: - annoSymbol = (ClassSymbol) ((JCIdent) annoType).sym; - break; - case MEMBER_SELECT: - annoSymbol = (ClassSymbol) ((JCFieldAccess) annoType).sym; - break; - default: - throw new BugInCF("Unhandled kind: " + annoType.getKind() + " for " + anno); - } - for (AnnotationMirror metaAnno : annoSymbol.getAnnotationMirrors()) { - if (AnnotationUtils.areSameByName(metaAnno, TARGET)) { - AnnotationValue av = metaAnno.getElementValues().get(targetValueElement); - return AnnotationUtils.annotationValueContainsToString(av, "TYPE_USE"); - } - } - - return false; - } - - /** - * Performs two checks: subtyping and assignability checks, using {@link - * #commonAssignmentCheck(Tree, ExpressionTree, String, Object[])}. - * - *

If the subtype check fails, it issues an "assignment.type.incompatible" error. - */ - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - commonAssignmentCheck( - tree.getVariable(), tree.getExpression(), "assignment.type.incompatible"); - return super.visitAssignment(tree, p); - } - - /** - * Performs a subtype check, to test whether the tree expression iterable type is a subtype of - * the variable type in the enhanced for loop. - * - *

If the subtype check fails, it issues a "enhancedfor.type.incompatible" error. - */ - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(tree.getVariable()); - AnnotatedTypeMirror iteratedType = - atypeFactory.getIterableElementType(tree.getExpression()); - boolean valid = validateTypeOf(tree.getVariable()); - if (valid) { - commonAssignmentCheck( - var, iteratedType, tree.getExpression(), "enhancedfor.type.incompatible"); - } - return super.visitEnhancedForLoop(tree, p); - } - - /** - * Performs a method invocation check. - * - *

An invocation of a method, m, on the receiver, r is valid only if: - * - *

    - *
  • passed arguments are subtypes of corresponding m parameters - *
  • r is a subtype of m receiver type - *
  • if m is generic, passed type arguments are subtypes of m type variables - *
- */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - - // Skip calls to the Enum constructor (they're generated by javac and - // hard to check), also see CFGBuilder.visitMethodInvocation. - if (TreeUtils.elementFromUse(tree) == null || TreeUtils.isEnumSuperCall(tree)) { - return super.visitMethodInvocation(tree, p); - } - - if (shouldSkipUses(tree)) { - return super.visitMethodInvocation(tree, p); - } - - ParameterizedExecutableType mType = atypeFactory.methodFromUse(tree); - AnnotatedExecutableType invokedMethod = mType.executableType; - List typeargs = mType.typeArgs; - - if (!atypeFactory.ignoreUninferredTypeArguments) { - for (AnnotatedTypeMirror typearg : typeargs) { - if (typearg.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) typearg).isUninferredTypeArgument()) { - checker.reportError( - tree, - "type.arguments.not.inferred", - invokedMethod.getElement().getSimpleName()); - break; // only issue error once per method - } - } - } - - List paramBounds = - CollectionsPlume.mapList( - AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables()); - - ExecutableElement method = invokedMethod.getElement(); - CharSequence methodName = ElementUtils.getSimpleDescription(method); - try { - checkTypeArguments( - tree, - paramBounds, - typeargs, - tree.getTypeArguments(), - methodName, - invokedMethod.getTypeVariables()); - List params = invokedMethod.getParameterTypes(); - checkArguments(params, tree.getArguments(), methodName, method.getParameters()); - checkVarargs(invokedMethod, tree); - - if (ElementUtils.isMethod( - invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) { - typeCheckVectorCopyIntoArgument(tree, params); - } - - ExecutableElement invokedMethodElement = invokedMethod.getElement(); - if (!ElementUtils.isStatic(invokedMethodElement) - && !TreeUtils.isSuperConstructorCall(tree)) { - checkMethodInvocability(invokedMethod, tree); - } - - // check precondition annotations - checkPreconditions( - tree, - atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement)); - - if (TreeUtils.isSuperConstructorCall(tree)) { - checkSuperConstructorCall(tree); - } else if (TreeUtils.isThisConstructorCall(tree)) { - checkThisConstructorCall(tree); - } - } catch (RuntimeException t) { - // Sometimes the type arguments are inferred incorrectly, which causes crashes. Once - // #979 is fixed this should be removed and crashes should be reported normally. - if (tree.getTypeArguments().size() == typeargs.size()) { - // They type arguments were explicitly written. - throw t; - } - if (!atypeFactory.ignoreUninferredTypeArguments) { - checker.reportError( - tree, - "type.arguments.not.inferred", - invokedMethod.getElement().getSimpleName()); - } // else ignore the crash. - } - - // Do not call super, as that would observe the arguments without - // a set assignment context. - scan(tree.getMethodSelect(), p); - return null; // super.visitMethodInvocation(tree, p); - } - - /** - * Checks that the following rule is satisfied: The type on a constructor declaration must be a - * supertype of the return type of "this()" invocation within that constructor. - * - *

Subclasses can override this method to change the behavior for just "this" constructor - * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to - * change the behavior for "this" and "super" constructor calls. - * - * @param thisCall the AST node for the constructor call - */ - protected void checkThisConstructorCall(MethodInvocationTree thisCall) { - checkThisOrSuperConstructorCall(thisCall, "this.invocation.invalid"); - } - - /** - * Checks that the following rule is satisfied: The type on a constructor declaration must be a - * supertype of the return type of "super()" invocation within that constructor. - * - *

Subclasses can override this method to change the behavior for just "super" constructor - * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to - * change the behavior for "this" and "super" constructor calls. - * - * @param superCall the AST node for the super constructor call - */ - protected void checkSuperConstructorCall(MethodInvocationTree superCall) { - checkThisOrSuperConstructorCall(superCall, "super.invocation.invalid"); - } - - /** - * Checks that the following rule is satisfied: The type on a constructor declaration must be a - * supertype of the return type of "this()" or "super()" invocation within that constructor. - * - * @param call the AST node for the constructor call - * @param errorKey the error message key to use if the check fails - */ - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree call, @CompilerMessageKey String errorKey) { - TreePath path = atypeFactory.getPath(call); - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path); - AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(call); - AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); - AnnotatedTypeMirror returnType = constructorType.getReturnType(); - AnnotationMirrorSet topAnnotations = qualHierarchy.getTopAnnotations(); - for (AnnotationMirror topAnno : topAnnotations) { - if (!typeHierarchy.isSubtypeShallowEffective(superType, returnType, topAnno)) { - AnnotationMirror superAnno = superType.getAnnotationInHierarchy(topAnno); - AnnotationMirror constructorReturnAnno = - returnType.getAnnotationInHierarchy(topAnno); - checker.reportError(call, errorKey, constructorReturnAnno, call, superAnno); - } - } - } - - /** - * If the given invocation is a varargs invocation, check that the array type of actual varargs - * is a subtype of the corresponding formal parameter; issues "argument.invalid" error if not. - * - *

The caller must type-check for each element in varargs before or after calling this - * method. - * - * @see #checkArguments - * @param invokedMethod the method type to be invoked - * @param tree method or constructor invocation tree - */ - protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) { - if (!TreeUtils.isVarArgs(tree)) { - // If not a varargs invocation, type checking is already done in checkArguments. - return; - } - - // This is the varags type, an array. - AnnotatedArrayType lastParamAnnotatedType = invokedMethod.getVarargType(); - - AnnotatedTypeMirror wrappedVarargsType = atypeFactory.getAnnotatedTypeVarargsArray(tree); - - // When dataflow analysis is not enabled, it will be null and we can suppose there is no - // annotation to be checked for generated varargs array. - if (wrappedVarargsType == null) { - return; - } - - // The component type of wrappedVarargsType might not be a subtype of the component type of - // lastParamAnnotatedType due to the difference of type inference between for an expression - // and an invoked method element. We can consider that the component type of actual is same - // with formal one because type checking for elements will be done in checkArguments. This - // is also needed to avoid duplicating error message caused by elements in varargs. - if (wrappedVarargsType.getKind() == TypeKind.ARRAY) { - ((AnnotatedArrayType) wrappedVarargsType) - .setComponentType(lastParamAnnotatedType.getComponentType()); - } - - commonAssignmentCheck( - lastParamAnnotatedType, wrappedVarargsType, tree, "varargs.type.incompatible"); - } - - /** - * Checks that all the given {@code preconditions} hold true immediately prior to the method - * invocation or variable access at {@code tree}. - * - * @param tree the method invocation; immediately prior to it, the preconditions must hold true - * @param preconditions the preconditions to be checked - */ - protected void checkPreconditions(MethodInvocationTree tree, Set preconditions) { - // This check is needed for the GUI effects and Units Checkers tests to pass. - // TODO: Remove this check and investigate the root cause. - if (preconditions.isEmpty()) { - return; - } - - StringToJavaExpression stringToJavaExpr = - stringExpr -> StringToJavaExpression.atMethodInvocation(stringExpr, tree, checker); - for (Contract c : preconditions) { - Precondition p = (Precondition) c; - String expressionString = p.expressionString; - AnnotationMirror anno = - c.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, tree); - JavaExpression exprJe; - try { - exprJe = StringToJavaExpression.atMethodInvocation(expressionString, tree, checker); - } catch (JavaExpressionParseException e) { - // report errors here - checker.report(tree, e.getDiagMessage()); - return; - } - - CFAbstractStore store = atypeFactory.getStoreBefore(tree); - - AnnotationMirrorSet annos = - atypeFactory.getAnnotatedTypeBefore(exprJe, tree).getAnnotations(); - - AnnotationMirror inferredAnno = - qualHierarchy.findAnnotationInSameHierarchy(annos, anno); - - // If the expression is "this", then get the type of the method receiver. - // TODO: There are other expressions that can be converted to trees, "#1" for - // example. - if (expressionString.equals("this") - && qualHierarchy.getTopAnnotations().contains(inferredAnno)) { - AnnotatedTypeMirror atype = atypeFactory.getReceiverType(tree); - if (atype != null) { - annos = atype.getEffectiveAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, anno); - } - } - - if (!checkContract(exprJe, anno, inferredAnno, store)) { - if (exprJe != null) { - expressionString = exprJe.toString(); - } - checker.reportError( - tree, - "contracts.precondition.not.satisfied", - tree.getMethodSelect().toString(), - contractExpressionAndType(expressionString, inferredAnno), - contractExpressionAndType(expressionString, anno)); - } - } - } - - /** - * Returns true if and only if {@code inferredAnnotation} is valid for a given expression to - * match the {@code necessaryAnnotation}. - * - *

By default, {@code inferredAnnotation} must be a subtype of {@code necessaryAnnotation}, - * but subclasses might override this behavior. - */ - protected boolean checkContract( - JavaExpression expr, - AnnotationMirror necessaryAnnotation, - AnnotationMirror inferredAnnotation, - CFAbstractStore store) { - if (inferredAnnotation == null) { - return false; - } - TypeMirror exprTM = expr.getType(); - return qualHierarchy.isSubtypeShallow(inferredAnnotation, necessaryAnnotation, exprTM); - } - - /** - * Type checks the method arguments of {@code Vector.copyInto()}. - * - *

The Checker Framework special-cases the method invocation, as its type safety cannot be - * expressed by Java's type system. - * - *

For a Vector {@code v} of type {@code Vector}, the method invocation {@code - * v.copyInto(arr)} is type-safe iff {@code arr} is an array of type {@code T[]}, where {@code - * T} is a subtype of {@code E}. - * - *

In other words, this method checks that the type argument of the receiver method is a - * subtype of the component type of the passed array argument. - * - * @param tree a method invocation of {@code Vector.copyInto()} - * @param params the types of the parameters of {@code Vectory.copyInto()} - */ - protected void typeCheckVectorCopyIntoArgument( - MethodInvocationTree tree, List params) { - assert params.size() == 1 - : "invalid no. of parameters " + params + " found for method invocation " + tree; - assert tree.getArguments().size() == 1 - : "invalid no. of arguments in method invocation " + tree; - - AnnotatedTypeMirror passed = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedArrayType passedAsArray = (AnnotatedArrayType) passed; - - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - AnnotatedDeclaredType receiverAsVector = - AnnotatedTypes.asSuper(atypeFactory, receiver, vectorType); - if (receiverAsVector.getTypeArguments().isEmpty()) { - return; - } - - AnnotatedTypeMirror argComponent = passedAsArray.getComponentType(); - AnnotatedTypeMirror vectorTypeArg = receiverAsVector.getTypeArguments().get(0); - Tree errorLocation = tree.getArguments().get(0); - if (TypesUtils.isErasedSubtype( - vectorTypeArg.getUnderlyingType(), argComponent.getUnderlyingType(), types)) { - commonAssignmentCheck( - argComponent, - vectorTypeArg, - errorLocation, - "vector.copyinto.type.incompatible"); - } else { - checker.reportError( - errorLocation, - "vector.copyinto.type.incompatible", - vectorTypeArg, - argComponent); - } - } - - /** - * Performs a new class invocation check. - * - *

An invocation of a constructor, c, is valid only if: - * - *

    - *
  • passed arguments are subtypes of corresponding c parameters - *
  • if c is generic, passed type arguments are subtypes of c type variables - *
- */ - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - if (checker.shouldSkipUses(TreeUtils.elementFromUse(tree))) { - return super.visitNewClass(tree, p); - } - - ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(tree); - AnnotatedExecutableType constructorType = fromUse.executableType; - List typeargs = fromUse.typeArgs; - - // Type check inner class enclosing expr type - checkEnclosingExpr(tree, constructorType); - List passedArguments = tree.getArguments(); - List params = constructorType.getParameterTypes(); - - ExecutableElement constructor = constructorType.getElement(); - CharSequence constructorName = ElementUtils.getSimpleDescription(constructor); - - checkArguments(params, passedArguments, constructorName, constructor.getParameters()); - checkVarargs(constructorType, tree); - - List paramBounds = - CollectionsPlume.mapList( - AnnotatedTypeVariable::getBounds, constructorType.getTypeVariables()); - - checkTypeArguments( - tree, - paramBounds, - typeargs, - tree.getTypeArguments(), - constructorName, - constructor.getTypeParameters()); - - boolean valid = validateTypeOf(tree); - - if (valid) { - AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(tree); - atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(dt, tree); - checkConstructorInvocation(dt, constructorType, tree); - } - // Do not call super, as that would observe the arguments without - // a set assignment context. - scan(tree.getEnclosingExpression(), p); - scan(tree.getIdentifier(), p); - scan(tree.getClassBody(), p); - - return null; - } - - @Override - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - - AnnotatedExecutableType functionType = atypeFactory.getFunctionTypeFromTree(tree); - - if (tree.getBody().getKind() != Tree.Kind.BLOCK) { - // Check return type for single statement returns here. - AnnotatedTypeMirror ret = functionType.getReturnType(); - if (ret.getKind() != TypeKind.VOID) { - commonAssignmentCheck( - ret, (ExpressionTree) tree.getBody(), "return.type.incompatible"); - } - } - - // Check parameters - for (int i = 0; i < functionType.getParameterTypes().size(); ++i) { - AnnotatedTypeMirror lambdaParameter = - atypeFactory.getAnnotatedType(tree.getParameters().get(i)); - commonAssignmentCheck( - lambdaParameter, - functionType.getParameterTypes().get(i), - tree.getParameters().get(i), - "lambda.param.type.incompatible", - i); - } - - // TODO: Postconditions? - // https://github.com/typetools/checker-framework/issues/801 - - return super.visitLambdaExpression(tree, p); - } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void p) { - this.checkMethodReferenceAsOverride(tree, p); - return super.visitMemberReference(tree, p); - } - - /** A set containing {@code Tree.Kind.METHOD} and {@code Tree.Kind.LAMBDA_EXPRESSION}. */ - private final ArraySet methodAndLambdaExpression = - new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); - - /** - * Checks that the type of the return expression is a subtype of the enclosing method required - * return type. If not, it issues a "return.type.incompatible" error. - */ - @Override - public Void visitReturn(ReturnTree tree, Void p) { - // Don't try to check return expressions for void methods. - if (tree.getExpression() == null) { - return super.visitReturn(tree, p); - } - - Tree enclosing = TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); - - AnnotatedTypeMirror ret = null; - if (enclosing.getKind() == Tree.Kind.METHOD) { - - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - boolean valid = validateTypeOf(enclosing); - if (valid) { - ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); - } - } else { - AnnotatedExecutableType result = - atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); - ret = result.getReturnType(); - } - - if (ret != null) { - commonAssignmentCheck(ret, tree.getExpression(), "return.type.incompatible"); - } - return super.visitReturn(tree, p); - } - - /** - * Ensure that the annotation arguments comply to their declarations. This needs some special - * casing, as annotation arguments form special trees. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - List args = tree.getArguments(); - if (args.isEmpty()) { - // Nothing to do if there are no annotation arguments. - return null; - } - - TypeElement anno = (TypeElement) TreeInfo.symbol((JCTree) tree.getAnnotationType()); - - Name annoName = anno.getQualifiedName(); - if (annoName.contentEquals(DefaultQualifier.class.getName()) - || annoName.contentEquals(SuppressWarnings.class.getName())) { - // Skip these two annotations, as we don't care about the arguments to them. - return null; - } - - List methods = ElementFilter.methodsIn(anno.getEnclosedElements()); - // Mapping from argument simple name to its annotated type. - Map annoTypes = ArrayMap.newArrayMapOrHashMap(methods.size()); - for (ExecutableElement meth : methods) { - AnnotatedExecutableType exeatm = atypeFactory.getAnnotatedType(meth); - AnnotatedTypeMirror retty = exeatm.getReturnType(); - annoTypes.put(meth.getSimpleName().toString(), retty); - } - - for (ExpressionTree arg : args) { - if (!(arg instanceof AssignmentTree)) { - // TODO: when can this happen? - continue; - } - - AssignmentTree at = (AssignmentTree) arg; - // Ensure that we never ask for the annotated type of an annotation, because - // we don't have a type for annotations. - if (at.getExpression().getKind() == Tree.Kind.ANNOTATION) { - visitAnnotation((AnnotationTree) at.getExpression(), p); - continue; - } - if (at.getExpression().getKind() == Tree.Kind.NEW_ARRAY) { - NewArrayTree nat = (NewArrayTree) at.getExpression(); - boolean isAnno = false; - for (ExpressionTree init : nat.getInitializers()) { - if (init.getKind() == Tree.Kind.ANNOTATION) { - visitAnnotation((AnnotationTree) init, p); - isAnno = true; - } - } - if (isAnno) { - continue; - } - } + TransferResult transferResult = (TransferResult) pair.second; + if (transferResult == null) { + // Unreachable return statements have no stores, but there is no need to check them. + continue; + } + CFAbstractStore exitStore = + (CFAbstractStore) + (result ? transferResult.getThenStore() : transferResult.getElseStore()); + CFAbstractValue value = exitStore.getValue(expression); + + // don't check if return statement certainly does not match 'result'. at the moment, + // this means the result is a boolean literal + if (!(retVal == null || retVal == result)) { + continue; + } + AnnotationMirror inferredAnno = null; + if (value != null) { + AnnotationMirrorSet annos = value.getAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); + } - AnnotatedTypeMirror expected = annoTypes.get(at.getVariable().toString()); - AnnotatedTypeMirror actual = atypeFactory.getAnnotatedType(at.getExpression()); - if (expected.getKind() != TypeKind.ARRAY) { - // Expected is not an array -> direct comparison. - commonAssignmentCheck( - expected, actual, at.getExpression(), "annotation.type.incompatible"); - } else if (actual.getKind() == TypeKind.ARRAY) { - // Both actual and expected are arrays. - commonAssignmentCheck( - expected, actual, at.getExpression(), "annotation.type.incompatible"); - } else { - // The declaration is an array type, but just a single element is given. - commonAssignmentCheck( - ((AnnotatedArrayType) expected).getComponentType(), - actual, - at.getExpression(), - "annotation.type.incompatible"); - } - } - return null; + if (!checkContract(expression, annotation, inferredAnno, exitStore)) { + checker.reportError( + returnStmt.getTree(), + "contracts.conditional.postcondition.not.satisfied", + methodTree.getName(), + result, + contractExpressionAndType(expression.toString(), inferredAnno), + contractExpressionAndType(expression.toString(), annotation)); + } } - - /** - * If the computation of the type of the ConditionalExpressionTree in - * org.checkerframework.framework.type.TypeFromTree.TypeFromExpression.visitConditionalExpression(ConditionalExpressionTree, - * AnnotatedTypeFactory) is correct, the following checks are redundant. However, let's add - * another failsafe guard and do the checks. - */ - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - AnnotatedTypeMirror cond = atypeFactory.getAnnotatedType(tree); - this.commonAssignmentCheck(cond, tree.getTrueExpression(), "conditional.type.incompatible"); - this.commonAssignmentCheck( - cond, tree.getFalseExpression(), "conditional.type.incompatible"); - return super.visitConditionalExpression(tree, p); + } + + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void p) { + if (tree.getBounds().size() > 1) { + // The upper bound of the type parameter is an intersection + AnnotatedTypeVariable type = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tree); + AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type.getUpperBound(); + checkExplicitAnnotationsOnIntersectionBounds(intersection, tree.getBounds()); } - - /** - * This method validates the type of the switch expression. It issues an error if the type of a - * value that the switch expression can result is not a subtype of the switch type. - * - *

If a subclass overrides this method, it must call {@code super.scan(switchExpressionTree, - * null)} so that the blocks and statements in the cases are checked. - * - * @param switchExpressionTree a {@code SwitchExpressionTree} - */ - public void visitSwitchExpression17(Tree switchExpressionTree) { - boolean valid = validateTypeOf(switchExpressionTree); - if (valid) { - AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(switchExpressionTree); - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (ExpressionTree valueTree, Void unused) -> { - BaseTypeVisitor.this.commonAssignmentCheck( - switchType, - valueTree, - "switch.expression.type.incompatible"); - return null; - }, - (r1, r2) -> null); - - scanner.scanSwitchExpression(switchExpressionTree, null); + validateTypeOf(tree); + + return super.visitTypeParameter(tree, p); + } + + /** + * Issues "explicit.annotation.ignored" warning if any explicit annotation on an intersection + * bound is not the same as the primary annotation of the given intersection type. + * + * @param intersection type to use + * @param boundTrees trees of {@code intersection} bounds + */ + protected void checkExplicitAnnotationsOnIntersectionBounds( + AnnotatedIntersectionType intersection, List boundTrees) { + for (Tree boundTree : boundTrees) { + if (boundTree.getKind() != Tree.Kind.ANNOTATED_TYPE) { + continue; + } + List explictAnnos = + TreeUtils.annotationsFromTree((AnnotatedTypeTree) boundTree); + for (AnnotationMirror explictAnno : explictAnnos) { + if (atypeFactory.isSupportedQualifier(explictAnno)) { + AnnotationMirror anno = intersection.getAnnotationInHierarchy(explictAnno); + if (!AnnotationUtils.areSame(anno, explictAnno)) { + checker.reportWarning( + boundTree, "explicit.annotation.ignored", explictAnno, anno, explictAnno, anno); + } } - super.scan(switchExpressionTree, null); + } } + } - // ********************************************************************** - // Check for illegal re-assignment - // ********************************************************************** + // ********************************************************************** + // Assignment checkers and pseudo-assignments + // ********************************************************************** - /** Performs assignability check. */ - @Override - public Void visitUnary(UnaryTree tree, Void p) { - Tree.Kind treeKind = tree.getKind(); - if (treeKind == Tree.Kind.PREFIX_DECREMENT - || treeKind == Tree.Kind.PREFIX_INCREMENT - || treeKind == Tree.Kind.POSTFIX_DECREMENT - || treeKind == Tree.Kind.POSTFIX_INCREMENT) { - // Check the assignment that occurs at the increment/decrement. i.e.: - // exp = exp + 1 or exp = exp - 1 - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(tree.getExpression()); - AnnotatedTypeMirror valueType; - if (treeKind == Tree.Kind.POSTFIX_DECREMENT - || treeKind == Tree.Kind.POSTFIX_INCREMENT) { - // For postfixed increments or decrements, the type of the tree the type of the - // expression before 1 is added or subtracted. So, use a special method to get the - // type after 1 has been added or subtracted. - valueType = atypeFactory.getAnnotatedTypeRhsUnaryAssign(tree); - } else { - // For prefixed increments or decrements, the type of the tree the type of the - // expression after 1 is added or subtracted. So, its type can be found using the - // usual method. - valueType = atypeFactory.getAnnotatedType(tree); - } - String errorKey = - (treeKind == Tree.Kind.PREFIX_INCREMENT - || treeKind == Tree.Kind.POSTFIX_INCREMENT) - ? "unary.increment.type.incompatible" - : "unary.decrement.type.incompatible"; - commonAssignmentCheck(varType, valueType, tree, errorKey); - } - return super.visitUnary(tree, p); - } + @Override + public Void visitVariable(VariableTree tree, Void p) { + warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); - /** Performs assignability check. */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - // If tree is the tree representing the compounds assignment s += expr, - // Then this method should check whether s + expr can be assigned to s, - // but the "s + expr" tree does not exist. So instead, check that - // s += expr can be assigned to s. - commonAssignmentCheck(tree.getVariable(), tree, "compound.assignment.type.incompatible"); - return super.visitCompoundAssignment(tree, p); + // VariableTree#getType returns null for binding variables from a DeconstructionPatternTree. + if (tree.getType() != null) { + visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getType()); } - // ********************************************************************** - // Check for invalid types inserted by the user - // ********************************************************************** - - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - boolean valid = validateTypeOf(tree); - - if (valid && tree.getType() != null) { - AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(tree); - atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(arrayType, tree); - if (tree.getInitializers() != null) { - checkArrayInitialization(arrayType.getComponentType(), tree.getInitializers()); - } - } - - return super.visitNewArray(tree, p); + AnnotatedTypeMirror variableType; + if (getCurrentPath().getParentPath() != null + && getCurrentPath().getParentPath().getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + // Calling getAnnotatedTypeLhs on a lambda parameter tree is possibly expensive + // because caching is turned off. This should be fixed by #979. + // See https://github.com/typetools/checker-framework/issues/2853 for an example. + variableType = atypeFactory.getAnnotatedType(tree); + } else { + variableType = atypeFactory.getAnnotatedTypeLhs(tree); } - /** - * If the lint option "cast:redundant" is set, this method issues a warning if the cast is - * redundant. - */ - protected void checkTypecastRedundancy(TypeCastTree typeCastTree) { - if (!checker.getLintOption("cast:redundant", false)) { - return; - } - - AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); - - if (castType.equals(exprType)) { - checker.reportWarning(typeCastTree, "cast.redundant", castType); - } + atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(variableType, tree); + Element varEle = TreeUtils.elementFromDeclaration(tree); + if (varEle.getKind() == ElementKind.ENUM_CONSTANT) { + commonAssignmentCheck(tree, tree.getInitializer(), "enum.declaration.type.incompatible"); + } else if (tree.getInitializer() != null) { + if (!TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { + // If there is no assignment in this variable declaration or it is declared using + // `var`, skip it. + // For a `var` declaration, TypeFromMemberVisitor#visitVariable already uses the + // type of the initializer for the variable type, so it would be redundant to check + // for compatibility here. + commonAssignmentCheck(tree, tree.getInitializer(), "assignment.type.incompatible"); + } + } else { + // commonAssignmentCheck validates the type of `tree`, + // so only validate if commonAssignmentCheck wasn't called + validateTypeOf(tree); } - - /** - * Issues a warning if the given explicitly-written typecast is unsafe. Does nothing if the lint - * option "cast:unsafe" is not set. Only primary qualifiers are checked unless the command line - * option "checkCastElementType" is supplied. - * - * @param typeCastTree an explicitly-written typecast - */ - protected void checkTypecastSafety(TypeCastTree typeCastTree) { - if (!checker.getLintOption("cast:unsafe", true)) { - return; - } - AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); - boolean reported = false; - for (AnnotationMirror top : atypeFactory.getQualifierParameterHierarchies(castType)) { - if (!isTypeCastSafeInvariant(castType, exprType, top)) { - checker.reportError( - typeCastTree, - "invariant.cast.unsafe", - exprType.toString(true), - castType.toString(true)); - } - reported = true; // don't issue cast unsafe warning. - } - - // Don't call TypeHierarchy#isSubtype(exprType, castType) because the underlying Java types - // will not be in the correct subtyping relationship if this is a downcast. - if (!reported && !isTypeCastSafe(castType, exprType)) { - checker.reportWarning( - typeCastTree, "cast.unsafe", exprType.toString(true), castType.toString(true)); - } + validateVariablesTargetLocation(tree, variableType); + warnRedundantAnnotations(tree, variableType); + return super.visitVariable(tree, p); + } + + /** + * Validate if the annotations on the VariableTree are at the right locations, which is specified + * by the meta-annotation @TargetLocations. The difference of this method between {@link + * BaseTypeVisitor#validateTargetLocation(Tree, AnnotatedTypeMirror, TypeUseLocation)} is that + * this one is only used in {@link BaseTypeVisitor#visitVariable(VariableTree, Void)} + * + * @param tree annotations on this VariableTree will be validated + * @param type the type of the tree + */ + protected void validateVariablesTargetLocation(Tree tree, AnnotatedTypeMirror type) { + if (ignoreTargetLocations) { + return; } - - /** - * Returns true if the cast is safe. - * - *

Only primary qualifiers are checked unless the command line option "checkCastElementType" - * is supplied. - * - * @param castType annotated type of the cast - * @param exprType annotated type of the casted expression - * @return true if the type cast is safe, false otherwise - */ - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - TypeKind castTypeKind = castType.getKind(); - if (castTypeKind == TypeKind.DECLARED) { - // Don't issue an error if the annotations are equivalent to the qualifier upper bound - // of the type. - AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType; - AnnotationMirrorSet bounds = - atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType()); - - if (AnnotationUtils.areSame(castDeclared.getAnnotations(), bounds)) { - return true; - } - } - - AnnotationMirrorSet castAnnos; - AnnotatedTypeMirror newCastType; - TypeMirror newCastTM; - if (!checkCastElementType) { - // checkCastElementType option wasn't specified, so only check effective annotations. - castAnnos = castType.getEffectiveAnnotations(); - newCastType = castType; - newCastTM = newCastType.getUnderlyingType(); - } else { - if (castTypeKind == TypeKind.TYPEVAR) { - newCastType = ((AnnotatedTypeVariable) castType).getUpperBound(); - } else { - newCastType = castType; - } - newCastTM = newCastType.getUnderlyingType(); - AnnotatedTypeMirror newExprType; - if (exprType.getKind() == TypeKind.TYPEVAR) { - newExprType = ((AnnotatedTypeVariable) exprType).getUpperBound(); - } else { - newExprType = exprType; - } - TypeMirror newExprTM = newExprType.getUnderlyingType(); - - if (!typeHierarchy.isSubtype(newExprType, newCastType)) { - return false; - } - if (newCastType.getKind() == TypeKind.ARRAY - && newExprType.getKind() != TypeKind.ARRAY) { - // Always warn if the cast contains an array, but the expression - // doesn't, as in "(Object[]) o" where o is of type Object - return false; - } else if (newCastType.getKind() == TypeKind.DECLARED - && newExprType.getKind() == TypeKind.DECLARED) { - int castSize = ((AnnotatedDeclaredType) newCastType).getTypeArguments().size(); - int exprSize = ((AnnotatedDeclaredType) newExprType).getTypeArguments().size(); - - if (castSize != exprSize) { - // Always warn if the cast and expression contain a different number of type - // arguments, e.g. to catch a cast from "Object" to "List<@NonNull Object>". - // TODO: the same number of arguments actually doesn't guarantee anything. - return false; - } - } else if (castTypeKind == TypeKind.TYPEVAR && exprType.getKind() == TypeKind.TYPEVAR) { - // If both the cast type and the casted expression are type variables, then check - // the bounds. - AnnotationMirrorSet lowerBoundAnnotationsCast = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); - AnnotationMirrorSet lowerBoundAnnotationsExpr = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, exprType); - return qualHierarchy.isSubtypeShallow( - lowerBoundAnnotationsExpr, - newExprTM, - lowerBoundAnnotationsCast, - newCastTM) - && typeHierarchy.isSubtypeShallowEffective(exprType, castType); - } - if (castTypeKind == TypeKind.TYPEVAR) { - // If the cast type is a type var, but the expression is not, then check that the - // type of the expression is a subtype of the lower bound. - castAnnos = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); + Element element = TreeUtils.elementFromTree(tree); + + if (element != null) { + ElementKind elemKind = element.getKind(); + // TypeUseLocation.java doesn't have ENUM type use location right now. + for (AnnotationMirror am : type.getAnnotations()) { + List locations = + qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + if (locations == null || locations.contains(TypeUseLocation.ALL)) { + continue; + } + boolean issueError = true; + switch (elemKind) { + case LOCAL_VARIABLE: + if (locations.contains(TypeUseLocation.LOCAL_VARIABLE)) issueError = false; + break; + case EXCEPTION_PARAMETER: + if (locations.contains(TypeUseLocation.EXCEPTION_PARAMETER)) issueError = false; + break; + case PARAMETER: + if (((VariableTree) tree).getName().contentEquals("this")) { + if (locations.contains(TypeUseLocation.RECEIVER)) { + issueError = false; + } } else { - castAnnos = castType.getAnnotations(); + if (locations.contains(TypeUseLocation.PARAMETER)) { + issueError = false; + } } + break; + case RESOURCE_VARIABLE: + if (locations.contains(TypeUseLocation.RESOURCE_VARIABLE)) { + issueError = false; + } + break; + case FIELD: + if (locations.contains(TypeUseLocation.FIELD)) { + issueError = false; + } + break; + case ENUM_CONSTANT: + if (locations.contains(TypeUseLocation.FIELD) + || locations.contains(TypeUseLocation.CONSTRUCTOR_RESULT)) { + issueError = false; + } + break; + default: + throw new BugInCF("Location not matched"); + } + if (issueError) { + checker.reportError( + tree, + "type.invalid.annotations.on.location", + am.toString(), + element.getKind().name()); } - - AnnotatedTypeMirror exprTypeWidened = atypeFactory.getWidenedType(exprType, castType); - return qualHierarchy.isSubtypeShallow( - exprTypeWidened.getEffectiveAnnotations(), - exprTypeWidened.getUnderlyingType(), - castAnnos, - newCastTM); + } } - - /** - * Return whether casting the {@code exprType} to {@code castType}, a type with a qualifier - * parameter, is legal. - * - *

If {@code exprType} has qualifier parameter, the cast is legal if the qualifiers are - * invariant. Otherwise, the cast is legal is if the qualifier on both types is bottom. - * - * @param castType a type with a qualifier parameter - * @param exprType type of the expressions that is cast which may or may not have a qualifier - * parameter - * @param top the top qualifier of the hierarchy to check - * @return whether casting the {@code exprType} to {@code castType}, a type with a qualifier - * parameter, is legal. - */ - private boolean isTypeCastSafeInvariant( - AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType, AnnotationMirror top) { - if (!isTypeCastSafe(castType, exprType)) { - return false; - } - - if (atypeFactory.hasQualifierParameterInHierarchy(exprType, top)) { - // The isTypeCastSafe call above checked that the exprType is a subtype of castType, - // so just check the reverse to check that the qualifiers are equivalent. - return typeHierarchy.isSubtypeShallowEffective(castType, exprType, top); - } - AnnotationMirror castTypeAnno = castType.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror exprTypeAnno = exprType.getEffectiveAnnotationInHierarchy(top); - // Otherwise the cast is unsafe, unless the qualifiers on both cast and expr are bottom. - AnnotationMirror bottom = qualHierarchy.getBottomAnnotation(top); - return AnnotationUtils.areSame(castTypeAnno, bottom) - && AnnotationUtils.areSame(exprTypeAnno, bottom); + } + + /** + * Validate if the annotations on the tree are at the right locations, which are specified by the + * meta-annotation @TargetLocations. + * + * @param tree annotations on this VariableTree will be validated + * @param type the type of the tree + * @param required if all of the TypeUseLocations in {@code required} are not present in the + * specification of the annotation (@TargetLocations), issue an error. + */ + protected void validateTargetLocation( + Tree tree, AnnotatedTypeMirror type, TypeUseLocation required) { + if (ignoreTargetLocations) { + return; } + for (AnnotationMirror am : type.getAnnotations()) { + List locations = + qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + if (locations == null || locations.contains(TypeUseLocation.ALL)) { + continue; + } + boolean issueError = !locations.contains(required); - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - // validate "tree" instead of "tree.getType()" to prevent duplicate errors. - boolean valid = validateTypeOf(tree) && validateTypeOf(tree.getExpression()); - if (valid) { - checkTypecastSafety(tree); - checkTypecastRedundancy(tree); - } - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - - if (atypeFactory.getDependentTypesHelper().hasDependentAnnotations()) { - atypeFactory - .getDependentTypesHelper() - .checkTypeForErrorExpressions(type, tree.getType()); - } - - if (tree.getType().getKind() == Tree.Kind.INTERSECTION_TYPE) { - AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type; - checkExplicitAnnotationsOnIntersectionBounds( - intersection, ((IntersectionTypeTree) tree.getType()).getBounds()); - } - return super.visitTypeCast(tree, p); + if (issueError) { + checker.reportError( + tree, "type.invalid.annotations.on.location", am.toString(), required.toString()); + } } - - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - // The "reference type" is the type after "instanceof". - Tree patternTree = InstanceOfUtils.getPattern(tree); - if (patternTree != null) { - if (TreeUtils.isBindingPatternTree(patternTree)) { - VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); - validateTypeOf(variableTree); - if (variableTree.getModifiers() != null) { - AnnotatedTypeMirror variableType = atypeFactory.getAnnotatedType(variableTree); - AnnotatedTypeMirror expType = - atypeFactory.getAnnotatedType(tree.getExpression()); - if (!isTypeCastSafe(variableType, expType)) { - checker.reportWarning( - tree, "instanceof.pattern.unsafe", expType, variableTree); - } - } - } else { - // TODO: implement deconstructed patterns. - } - } else { - Tree refTypeTree = tree.getType(); - validateTypeOf(refTypeTree); - if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeMirror refType = atypeFactory.getAnnotatedType(refTypeTree); - AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(tree.getExpression()); - if (typeHierarchy.isSubtype(refType, expType) - && !refType.getAnnotations().equals(expType.getAnnotations())) { - checker.reportWarning(tree, "instanceof.unsafe", expType, refType); - } - } - } - - return super.visitInstanceOf(tree, p); + } + + /** + * Create a new map, which is used for declared type-use locations lookup. + * + * @return a new mapping from strings of qualifier names to their declared type-use locations. + */ + protected Map<@CanonicalName String, List> createQualAllowedLocations() { + HashMap<@CanonicalName String, List> qualAllowedLocations = new HashMap<>(); + for (String qual : atypeFactory.getSupportedTypeQualifierNames()) { + Element elem = elements.getTypeElement(qual); + TargetLocations tls = elem.getAnnotation(TargetLocations.class); + // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means that + // the qualifier can be written on any type use. + if (tls == null) { + qualAllowedLocations.put(qual, null); + continue; + } + List locations = Arrays.asList(tls.value()); + qualAllowedLocations.put(qual, locations); } - - /** - * Checks the type of the exception parameter. Subclasses should override {@link - * #checkExceptionParameter} rather than this method to change the behavior of this check. - */ - @Override - public Void visitCatch(CatchTree tree, Void p) { - checkExceptionParameter(tree); - return super.visitCatch(tree, p); + return qualAllowedLocations; + } + + /** + * Issues a "redundant.anno" warning if the annotation written on the type is the same as the + * default annotation for this type and location. + * + * @param tree an AST node + * @param type get the explicit annotation on this type and compare it with the default one for + * this type and location. + */ + protected void warnRedundantAnnotations(Tree tree, AnnotatedTypeMirror type) { + // Type variable uses don't have default annotations. So, any explicit annotation is not + // redundant. + if (!warnRedundantAnnotations || (type.getKind() == TypeKind.TYPEVAR)) { + return; } - - /** - * Checks the type of a thrown exception. Subclasses should override - * checkThrownExpression(ThrowTree tree) rather than this method to change the behavior of this - * check. - */ - @Override - public Void visitThrow(ThrowTree tree, Void p) { - checkThrownExpression(tree); - return super.visitThrow(tree, p); + AnnotationMirrorSet explicitAnnos = type.getExplicitAnnotations(); + if (explicitAnnos.isEmpty()) { + return; } - - /** - * Rather than overriding this method, clients should often override {@link - * #visitAnnotatedType(List,Tree)}. That method also handles the case of annotations at the - * beginning of a variable or method declaration. javac parses all those annotations as being on - * the variable or method declaration, even though the ones that are type annotations logically - * belong to the variable type or method return type. - */ - @Override - public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { - visitAnnotatedType(null, tree); - return super.visitAnnotatedType(tree, p); + if (tree == null) { + throw new BugInCF("unexpected null tree argument!"); } - /** - * Checks an annotated type. Invoked by {@link #visitAnnotatedType(AnnotatedTypeTree, Void)}, - * {@link #visitVariable}, and {@link #visitMethod}. Exists to prevent code duplication among - * the three. Checking in {@code visitVariable} and {@code visitMethod} is needed because there - * isn't an AnnotatedTypeTree within a variable declaration or for a method return type -- all - * the annotations are attached to the VariableTree or MethodTree, respectively. - * - * @param annoTrees annotations written before a variable/method declaration, if this type is - * from one; null otherwise. This might contain type annotations that the Java parser - * attached to the declaration rather than to the type. - * @param typeTree the type that any type annotations in annoTrees apply to - */ - public void visitAnnotatedType( - @Nullable List annoTrees, Tree typeTree) { - warnAboutIrrelevantJavaTypes(annoTrees, typeTree); + AnnotatedTypeMirror defaultType = atypeFactory.getDefaultAnnotations(tree, type); + for (AnnotationMirror explicitAnno : explicitAnnos) { + AnnotationMirror defaultAM = defaultType.getAnnotationInHierarchy(explicitAnno); + if (AnnotationUtils.areSame(defaultAM, explicitAnno)) { + checker.reportWarning(tree, "redundant.anno", defaultAM); + } } - - /** - * Warns if a type annotation is written on a Java type that is not listed in - * the @RelevantJavaTypes annotation. - * - * @param annoTrees annotations written before a variable/method declaration, if this type is - * from one; null otherwise. This might contain type annotations that the Java parser - * attached to the declaration rather than to the type. - * @param typeTree the type that any type annotations in annoTrees apply to - */ - public void warnAboutIrrelevantJavaTypes( - @Nullable List annoTrees, Tree typeTree) { - if (!shouldWarnAboutIrrelevantJavaTypes()) { + } + + /** + * Warn if a type annotation is written before a modifier such as "public" or before a declaration + * annotation. + * + * @param tree a VariableTree or a MethodTree + * @param modifiersTree the modifiers sub-tree of tree + */ + private void warnAboutTypeAnnotationsTooEarly(Tree tree, ModifiersTree modifiersTree) { + + // Don't issue warnings about compiler-inserted modifiers. + // This simple code completely igonores enum constants and try-with-resources declarations. + // It could be made to catch some user errors in those locations, but it doesn't seem worth + // the effort to do so. + if (tree.getKind() == Tree.Kind.VARIABLE) { + ElementKind varKind = TreeUtils.elementFromDeclaration((VariableTree) tree).getKind(); + switch (varKind) { + case ENUM_CONSTANT: + // Enum constants are "public static final" by default, so the annotation always + // appears to be before "public". + return; + case RESOURCE_VARIABLE: + // Try-with-resources variables are "final" by default, so the annotation always + // appears to be before "final". + return; + default: + if (TreeUtils.isAutoGeneratedRecordMember(tree)) { + // Annotations can appear on record fields before the class body, so don't + // issue a warning about those. return; - } - - Tree t = typeTree; - while (true) { - switch (t.getKind()) { - - // Recurse for compound types whose top level is not at the far left. - case ARRAY_TYPE: - t = ((ArrayTypeTree) t).getType(); - continue; - case MEMBER_SELECT: - t = ((MemberSelectTree) t).getExpression(); - continue; - case PARAMETERIZED_TYPE: - t = ((ParameterizedTypeTree) t).getType(); - continue; - - // Base cases - case PRIMITIVE_TYPE: - case IDENTIFIER: - maybeReportAnnoOnIrrelevant(t, TreeUtils.typeOf(t), annoTrees); - return; - case ANNOTATED_TYPE: - AnnotatedTypeTree at = (AnnotatedTypeTree) t; - ExpressionTree underlying = at.getUnderlyingType(); - maybeReportAnnoOnIrrelevant( - t, TreeUtils.typeOf(underlying), at.getAnnotations()); - return; - - default: - return; - } - } + } + // Nothing to do + } } - /** - * If the given Java basetype is not relevant, report an "anno.on.irrelevant" if it is - * annotated. This method does not necessarily issue an error, but it might. - * - * @param errorLocation where to repor the error - * @param type the Java basetype - * @param annos the annotation on the type - */ - private void maybeReportAnnoOnIrrelevant( - Tree errorLocation, TypeMirror type, List annos) { - List supportedAnnoTrees = supportedAnnoTrees(annos); - if (!supportedAnnoTrees.isEmpty() && !atypeFactory.isRelevant(type)) { - String extraInfo = atypeFactory.irrelevantExtraMessage(); - checker.reportError(errorLocation, "anno.on.irrelevant", annos, type, extraInfo); - } - } + Set modifierSet = modifiersTree.getFlags(); + List annotations = modifiersTree.getAnnotations(); - /** - * Returns true if the checker should issue warnings about irrelevant java types. - * - * @return true if the checker should issue warnings about irrelevant java types - */ - protected boolean shouldWarnAboutIrrelevantJavaTypes() { - return atypeFactory.relevantJavaTypes != null; + if (annotations.isEmpty()) { + return; } - /** - * Returns a new list containing only the supported annotations from its argument -- that is, - * those that are part of the current type system. - * - *

This method ignores aliases of supported annotations that are declaration annotations, - * because they may apply to inner types. - * - * @param annoTrees annotation trees - * @return a new list containing only the supported annotations from its argument - */ - private List supportedAnnoTrees(List annoTrees) { - List result = new ArrayList<>(1); - for (AnnotationTree at : annoTrees) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(at); - if (AnnotationUtils.isTypeUseAnnotation(anno) - && atypeFactory.isSupportedQualifier(anno)) { - result.add(at); - } - } - return result; + // Warn about type annotations written before modifiers such as "public". javac retains no + // information about modifier locations. So, this is a very partial check: Issue a warning + // if a type annotation is at the very beginning of the VariableTree, and a modifier follows + // it. + + // Check if a type annotation precedes a declaration annotation. + int lastDeclAnnoIndex = -1; + for (int i = annotations.size() - 1; i > 0; i--) { // no need to check index 0 + if (!isTypeAnnotation(annotations.get(i))) { + lastDeclAnnoIndex = i; + break; + } } - - // ********************************************************************** - // Helper methods to provide a single overriding point - // ********************************************************************** - - /** - * Cache to avoid calling {@link #getExceptionParameterLowerBoundAnnotations} more than once. - */ - private @MonotonicNonNull AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCache; - - /** - * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. The same - * as {@link #getExceptionParameterLowerBoundAnnotations}, but uses a cache. - * - * @return a set of AnnotationMirrors that is a lower bound for exception parameters - */ - private AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCached() { - if (getExceptionParameterLowerBoundAnnotationsCache == null) { - getExceptionParameterLowerBoundAnnotationsCache = - getExceptionParameterLowerBoundAnnotations(); + if (lastDeclAnnoIndex != -1) { + // Usually, there are few bad invariant annotations. + List badTypeAnnos = new ArrayList<>(2); + for (int i = 0; i < lastDeclAnnoIndex; i++) { + AnnotationTree anno = annotations.get(i); + if (isTypeAnnotation(anno)) { + badTypeAnnos.add(anno); } - return getExceptionParameterLowerBoundAnnotationsCache; + } + if (!badTypeAnnos.isEmpty()) { + checker.reportWarning( + tree, "type.anno.before.decl.anno", badTypeAnnos, annotations.get(lastDeclAnnoIndex)); + } } - /** - * Issue error if the exception parameter is not a supertype of the annotation specified by - * {@link #getExceptionParameterLowerBoundAnnotations()}, which is top by default. - * - *

Subclasses may override this method to change the behavior of this check. Subclasses - * wishing to enforce that exception parameter be annotated with other annotations can just - * override {@link #getExceptionParameterLowerBoundAnnotations()}. - * - * @param tree a CatchTree to check - */ - protected void checkExceptionParameter(CatchTree tree) { - AnnotationMirrorSet requiredAnnotations = - getExceptionParameterLowerBoundAnnotationsCached(); - VariableTree excParamTree = tree.getParameter(); - AnnotatedTypeMirror excParamType = atypeFactory.getAnnotatedType(excParamTree); - - for (AnnotationMirror required : requiredAnnotations) { - AnnotationMirror found = excParamType.getAnnotationInHierarchy(required); - assert found != null; - if (!typeHierarchy.isSubtypeShallowEffective(required, excParamType)) { - checker.reportError(excParamTree, "exception.parameter.invalid", found, required); - } - - if (excParamType.getKind() == TypeKind.UNION) { - AnnotatedUnionType aut = (AnnotatedUnionType) excParamType; - for (AnnotatedTypeMirror alternativeType : aut.getAlternatives()) { - if (!typeHierarchy.isSubtypeShallowEffective(required, alternativeType)) { - AnnotationMirror alternativeAnno = - alternativeType.getAnnotationInHierarchy(required); - checker.reportError( - excParamTree, - "exception.parameter.invalid", - alternativeAnno, - required); - } - } - } - } + // Determine the length of the text that ought to precede the first type annotation. + // If the type annotation appears before that text could appear, then warn that a + // modifier appears after the type annotation. + // TODO: in the future, account for the lengths of declaration annotations. Length of + // toString of the annotation isn't useful, as it might be different length than original + // input. Can use JCTree.getEndPosition(EndPosTable) and + // com.sun.tools.javac.tree.EndPosTable, but it requires -Xjcov. + AnnotationTree firstAnno = annotations.get(0); + if (!modifierSet.isEmpty() && isTypeAnnotation(firstAnno)) { + int precedingTextLength = 0; + for (Modifier m : modifierSet) { + precedingTextLength += m.toString().length() + 1; // +1 for the space + } + int annoStartPos = ((JCTree) firstAnno).getStartPosition(); + int varStartPos = ((JCTree) tree).getStartPosition(); + if (annoStartPos < varStartPos + precedingTextLength) { + checker.reportWarning(tree, "type.anno.before.modifier", firstAnno, modifierSet); + } } - - /** - * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. - * - *

This implementation returns top; subclasses can change this behavior. - * - *

Note: by default this method is called by {@link #getThrowUpperBoundAnnotations()}, so - * that this annotation is enforced. - * - * @return set of annotation mirrors, one per hierarchy, that form a lower bound of annotations - * that can be written on an exception parameter - */ - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return qualHierarchy.getTopAnnotations(); + } + + /** + * Return true if the given annotation is a type annotation: that is, its definition is + * meta-annotated with {@code @Target({TYPE_USE,....})}. + */ + private boolean isTypeAnnotation(AnnotationTree anno) { + Tree annoType = anno.getAnnotationType(); + ClassSymbol annoSymbol; + switch (annoType.getKind()) { + case IDENTIFIER: + annoSymbol = (ClassSymbol) ((JCIdent) annoType).sym; + break; + case MEMBER_SELECT: + annoSymbol = (ClassSymbol) ((JCFieldAccess) annoType).sym; + break; + default: + throw new BugInCF("Unhandled kind: " + annoType.getKind() + " for " + anno); } - - /** - * Checks the type of the thrown expression. - * - *

By default, this method checks that the thrown expression is a subtype of top. - * - *

Issue error if the thrown expression is not a sub type of the annotation given by {@link - * #getThrowUpperBoundAnnotations()}, the same as {@link - * #getExceptionParameterLowerBoundAnnotations()} by default. - * - *

Subclasses may override this method to change the behavior of this check. Subclasses - * wishing to enforce that the thrown expression be a subtype of a type besides {@link - * #getExceptionParameterLowerBoundAnnotations}, should override {@link - * #getThrowUpperBoundAnnotations()}. - * - * @param tree a ThrowTree to check - */ - protected void checkThrownExpression(ThrowTree tree) { - AnnotatedTypeMirror throwType = atypeFactory.getAnnotatedType(tree.getExpression()); - TypeMirror throwTM = throwType.getUnderlyingType(); - Set required = getThrowUpperBoundAnnotations(); - switch (throwType.getKind()) { - case NULL: - case DECLARED: - case TYPEVAR: - case WILDCARD: - if (!typeHierarchy.isSubtypeShallowEffective(throwType, required)) { - AnnotationMirrorSet found = throwType.getEffectiveAnnotations(); - checker.reportError( - tree.getExpression(), "throw.type.invalid", found, required); - } - break; - - case UNION: - AnnotatedUnionType unionType = (AnnotatedUnionType) throwType; - AnnotationMirrorSet foundPrimary = unionType.getAnnotations(); - if (!qualHierarchy.isSubtypeShallow(foundPrimary, required, throwTM)) { - checker.reportError( - tree.getExpression(), "throw.type.invalid", foundPrimary, required); - } - for (AnnotatedTypeMirror altern : unionType.getAlternatives()) { - TypeMirror alternTM = altern.getUnderlyingType(); - if (!qualHierarchy.isSubtypeShallow( - altern.getAnnotations(), required, alternTM)) { - checker.reportError( - tree.getExpression(), - "throw.type.invalid", - altern.getAnnotations(), - required); - } - } - break; - default: - throw new BugInCF("Unexpected throw expression type: " + throwType.getKind()); - } + for (AnnotationMirror metaAnno : annoSymbol.getAnnotationMirrors()) { + if (AnnotationUtils.areSameByName(metaAnno, TARGET)) { + AnnotationValue av = metaAnno.getElementValues().get(targetValueElement); + return AnnotationUtils.annotationValueContainsToString(av, "TYPE_USE"); + } } - /** - * Returns a set of AnnotationMirrors that is a upper bound for thrown exceptions. - * - *

Note: by default this method is returns by getExceptionParameterLowerBoundAnnotations(), - * so that this annotation is enforced. - * - *

(Default is top) - * - * @return set of annotation mirrors, one per hierarchy, that form an upper bound of thrown - * expressions - */ - protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { - return getExceptionParameterLowerBoundAnnotations(); + return false; + } + + /** + * Performs two checks: subtyping and assignability checks, using {@link + * #commonAssignmentCheck(Tree, ExpressionTree, String, Object[])}. + * + *

If the subtype check fails, it issues an "assignment.type.incompatible" error. + */ + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + commonAssignmentCheck(tree.getVariable(), tree.getExpression(), "assignment.type.incompatible"); + return super.visitAssignment(tree, p); + } + + /** + * Performs a subtype check, to test whether the tree expression iterable type is a subtype of the + * variable type in the enhanced for loop. + * + *

If the subtype check fails, it issues a "enhancedfor.type.incompatible" error. + */ + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(tree.getVariable()); + AnnotatedTypeMirror iteratedType = atypeFactory.getIterableElementType(tree.getExpression()); + boolean valid = validateTypeOf(tree.getVariable()); + if (valid) { + commonAssignmentCheck( + var, iteratedType, tree.getExpression(), "enhancedfor.type.incompatible"); + } + return super.visitEnhancedForLoop(tree, p); + } + + /** + * Performs a method invocation check. + * + *

An invocation of a method, m, on the receiver, r is valid only if: + * + *

    + *
  • passed arguments are subtypes of corresponding m parameters + *
  • r is a subtype of m receiver type + *
  • if m is generic, passed type arguments are subtypes of m type variables + *
+ */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + + // Skip calls to the Enum constructor (they're generated by javac and + // hard to check), also see CFGBuilder.visitMethodInvocation. + if (TreeUtils.elementFromUse(tree) == null || TreeUtils.isEnumSuperCall(tree)) { + return super.visitMethodInvocation(tree, p); } - /** - * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and - * emits an error message (through the compiler's messaging interface) if it is not valid. - * - * @param varTree the AST node for the lvalue (usually a variable) - * @param valueExpTree the AST node for the rvalue (the new value) - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(varTree); - assert varType != null : "no variable found for tree: " + varTree; - - if (!validateType(varTree, varType)) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because validateType() returned false", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return true; - } - - return commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); + if (shouldSkipUses(tree)) { + return super.visitMethodInvocation(tree, p); } - /** - * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and - * emits an error message (through the compiler's messaging interface) if it is not valid. - * - * @param varType the annotated type for the lvalue (usually a variable) - * @param valueExpTree the AST node for the rvalue (the new value) - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - if (shouldSkipUses(valueExpTree)) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because shouldSkipUses() returned true", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return true; - } - if (valueExpTree.getKind() == Tree.Kind.MEMBER_REFERENCE - || valueExpTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - // Member references and lambda expressions are type checked separately - // and do not need to be checked again as arguments. - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because member reference and lambda expression are type checked separately", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return true; - } - boolean result = true; - if (varType.getKind() == TypeKind.ARRAY - && valueExpTree instanceof NewArrayTree - && ((NewArrayTree) valueExpTree).getType() == null) { - AnnotatedTypeMirror compType = ((AnnotatedArrayType) varType).getComponentType(); - NewArrayTree arrayTree = (NewArrayTree) valueExpTree; - assert arrayTree.getInitializers() != null - : "array initializers are not expected to be null in: " + valueExpTree; - result = checkArrayInitialization(compType, arrayTree.getInitializers()) && result; - } - if (!validateTypeOf(valueExpTree)) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because validateType() returned false", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return result; + ParameterizedExecutableType mType = atypeFactory.methodFromUse(tree); + AnnotatedExecutableType invokedMethod = mType.executableType; + List typeargs = mType.typeArgs; + + if (!atypeFactory.ignoreUninferredTypeArguments) { + for (AnnotatedTypeMirror typearg : typeargs) { + if (typearg.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) typearg).isUninferredTypeArgument()) { + checker.reportError( + tree, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName()); + break; // only issue error once per method } - AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExpTree); - assert valueType != null : "null type for expression: " + valueExpTree; - result = - commonAssignmentCheck(varType, valueType, valueExpTree, errorKey, extraArgs) - && result; - return result; + } } - /** - * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and - * emits an error message (through the compiler's messaging interface) if it is not valid. - * - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - commonAssignmentCheckStartDiagnostic(varType, valueType, valueExpTree); - - AnnotatedTypeMirror widenedValueType = atypeFactory.getWidenedType(valueType, varType); - boolean result = typeHierarchy.isSubtype(widenedValueType, varType); - - // TODO: integrate with subtype test. - if (result) { - for (Class mono : - atypeFactory.getSupportedMonotonicTypeQualifiers()) { - if (valueType.hasAnnotation(mono) && varType.hasAnnotation(mono)) { - checker.reportError( - valueExpTree, - "monotonic.type.incompatible", - mono.getSimpleName(), - mono.getSimpleName(), - valueType.toString()); - result = false; - } - } - } else { - // `result` is false. - // Use an error key only if it's overridden by a checker. - reportCommonAssignmentError( - varType, widenedValueType, valueExpTree, errorKey, extraArgs); - } + List paramBounds = + CollectionsPlume.mapList( + AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables()); + + ExecutableElement method = invokedMethod.getElement(); + CharSequence methodName = ElementUtils.getSimpleDescription(method); + try { + checkTypeArguments( + tree, + paramBounds, + typeargs, + tree.getTypeArguments(), + methodName, + invokedMethod.getTypeVariables()); + List params = invokedMethod.getParameterTypes(); + checkArguments(params, tree.getArguments(), methodName, method.getParameters()); + checkVarargs(invokedMethod, tree); + + if (ElementUtils.isMethod( + invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) { + typeCheckVectorCopyIntoArgument(tree, params); + } - commonAssignmentCheckEndDiagnostic(result, null, varType, valueType, valueExpTree); + ExecutableElement invokedMethodElement = invokedMethod.getElement(); + if (!ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperConstructorCall(tree)) { + checkMethodInvocability(invokedMethod, tree); + } - return result; - } + // check precondition annotations + checkPreconditions( + tree, atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement)); - /** - * Report a common assignment error. Allows checkers to change how the message is output. - * - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueTree the location to use when reporting the error message - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - */ - protected void reportCommonAssignmentError( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - FoundRequired pair = FoundRequired.of(valueType, varType); - String valueTypeString = pair.found; - String varTypeString = pair.required; + if (TreeUtils.isSuperConstructorCall(tree)) { + checkSuperConstructorCall(tree); + } else if (TreeUtils.isThisConstructorCall(tree)) { + checkThisConstructorCall(tree); + } + } catch (RuntimeException t) { + // Sometimes the type arguments are inferred incorrectly, which causes crashes. Once + // #979 is fixed this should be removed and crashes should be reported normally. + if (tree.getTypeArguments().size() == typeargs.size()) { + // They type arguments were explicitly written. + throw t; + } + if (!atypeFactory.ignoreUninferredTypeArguments) { checker.reportError( - valueTree, - errorKey, - ArraysPlume.concatenate(extraArgs, valueTypeString, varTypeString)); + tree, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName()); + } // else ignore the crash. } - /** - * Prints a diagnostic about entering {@code commonAssignmentCheck()}, if the showchecks option - * was set. - * - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - */ - protected final void commonAssignmentCheckStartDiagnostic( - AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueExpTree) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "about to test whether actual is a subtype of expected", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - valueType.getKind(), - valueType.toString(), - varType.getKind(), - varType.toString()); - } + // Do not call super, as that would observe the arguments without + // a set assignment context. + scan(tree.getMethodSelect(), p); + return null; // super.visitMethodInvocation(tree, p); + } + + /** + * Checks that the following rule is satisfied: The type on a constructor declaration must be a + * supertype of the return type of "this()" invocation within that constructor. + * + *

Subclasses can override this method to change the behavior for just "this" constructor + * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to + * change the behavior for "this" and "super" constructor calls. + * + * @param thisCall the AST node for the constructor call + */ + protected void checkThisConstructorCall(MethodInvocationTree thisCall) { + checkThisOrSuperConstructorCall(thisCall, "this.invocation.invalid"); + } + + /** + * Checks that the following rule is satisfied: The type on a constructor declaration must be a + * supertype of the return type of "super()" invocation within that constructor. + * + *

Subclasses can override this method to change the behavior for just "super" constructor + * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to + * change the behavior for "this" and "super" constructor calls. + * + * @param superCall the AST node for the super constructor call + */ + protected void checkSuperConstructorCall(MethodInvocationTree superCall) { + checkThisOrSuperConstructorCall(superCall, "super.invocation.invalid"); + } + + /** + * Checks that the following rule is satisfied: The type on a constructor declaration must be a + * supertype of the return type of "this()" or "super()" invocation within that constructor. + * + * @param call the AST node for the constructor call + * @param errorKey the error message key to use if the check fails + */ + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree call, @CompilerMessageKey String errorKey) { + TreePath path = atypeFactory.getPath(call); + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path); + AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(call); + AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); + AnnotatedTypeMirror returnType = constructorType.getReturnType(); + AnnotationMirrorSet topAnnotations = qualHierarchy.getTopAnnotations(); + for (AnnotationMirror topAnno : topAnnotations) { + if (!typeHierarchy.isSubtypeShallowEffective(superType, returnType, topAnno)) { + AnnotationMirror superAnno = superType.getAnnotationInHierarchy(topAnno); + AnnotationMirror constructorReturnAnno = returnType.getAnnotationInHierarchy(topAnno); + checker.reportError(call, errorKey, constructorReturnAnno, call, superAnno); + } } - - /** - * Prints a diagnostic about exiting {@code commonAssignmentCheck()}, if the showchecks option - * was set. - * - * @param success whether the check succeeded or failed - * @param extraMessage information about why the result is what it is; may be null - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - */ - protected final void commonAssignmentCheckEndDiagnostic( - boolean success, - @Nullable String extraMessage, - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueExpTree) { - if (showchecks) { - commonAssignmentCheckEndDiagnostic( - (success - ? "success: actual is subtype of expected" - : "FAILURE: actual is not subtype of expected") - + (extraMessage == null ? "" : " because " + extraMessage), - varType, - valueType, - valueExpTree); - } + } + + /** + * If the given invocation is a varargs invocation, check that the array type of actual varargs is + * a subtype of the corresponding formal parameter; issues "argument.invalid" error if not. + * + *

The caller must type-check for each element in varargs before or after calling this method. + * + * @see #checkArguments + * @param invokedMethod the method type to be invoked + * @param tree method or constructor invocation tree + */ + protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) { + if (!TreeUtils.isVarArgs(tree)) { + // If not a varargs invocation, type checking is already done in checkArguments. + return; } - /** - * Helper method for printing a diagnostic about exiting {@code commonAssignmentCheck()}, if the - * showchecks option was set. - * - *

Most clients should call {@link #commonAssignmentCheckEndDiagnostic(boolean, String, - * AnnotatedTypeMirror, AnnotatedTypeMirror, Tree)}. The purpose of this method is to permit - * customizing the message that is printed. - * - * @param message the result, plus information about why the result is what it is - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - */ - protected final void commonAssignmentCheckEndDiagnostic( - String message, - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueExpTree) { - if (showchecks) { - System.out.printf( - " %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", - message, - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - valueType.getKind(), - valueType.toString(), - varType.getKind(), - varType.toString()); - } + // This is the varags type, an array. + AnnotatedArrayType lastParamAnnotatedType = invokedMethod.getVarargType(); + + AnnotatedTypeMirror wrappedVarargsType = atypeFactory.getAnnotatedTypeVarargsArray(tree); + + // When dataflow analysis is not enabled, it will be null and we can suppose there is no + // annotation to be checked for generated varargs array. + if (wrappedVarargsType == null) { + return; } - /** - * Returns "filename:linenumber:columnnumber" for the given tree. For brevity, the filename is - * given as a simple name, without any directory components. If the line and column numbers are - * unknown, they are omitted. - * - * @param tree a tree - * @return the location of the given tree in source code - */ - private String fileAndLineNumber(Tree tree) { - StringBuilder result = new StringBuilder(); - result.append(Paths.get(root.getSourceFile().getName()).getFileName().toString()); - long valuePos = positions.getStartPosition(root, tree); - LineMap lineMap = root.getLineMap(); - if (valuePos != -1 && lineMap != null) { - result.append(":"); - result.append(lineMap.getLineNumber(valuePos)); - result.append(":"); - result.append(lineMap.getColumnNumber(valuePos)); - } - return result.toString(); + // The component type of wrappedVarargsType might not be a subtype of the component type of + // lastParamAnnotatedType due to the difference of type inference between for an expression + // and an invoked method element. We can consider that the component type of actual is same + // with formal one because type checking for elements will be done in checkArguments. This + // is also needed to avoid duplicating error message caused by elements in varargs. + if (wrappedVarargsType.getKind() == TypeKind.ARRAY) { + ((AnnotatedArrayType) wrappedVarargsType) + .setComponentType(lastParamAnnotatedType.getComponentType()); } - /** - * Class that creates string representations of {@link AnnotatedTypeMirror}s which are only - * verbose if required to differentiate the two types. - */ - protected static class FoundRequired { + commonAssignmentCheck( + lastParamAnnotatedType, wrappedVarargsType, tree, "varargs.type.incompatible"); + } + + /** + * Checks that all the given {@code preconditions} hold true immediately prior to the method + * invocation or variable access at {@code tree}. + * + * @param tree the method invocation; immediately prior to it, the preconditions must hold true + * @param preconditions the preconditions to be checked + */ + protected void checkPreconditions(MethodInvocationTree tree, Set preconditions) { + // This check is needed for the GUI effects and Units Checkers tests to pass. + // TODO: Remove this check and investigate the root cause. + if (preconditions.isEmpty()) { + return; + } - /** The found type's string representation. */ - public final String found; + StringToJavaExpression stringToJavaExpr = + stringExpr -> StringToJavaExpression.atMethodInvocation(stringExpr, tree, checker); + for (Contract c : preconditions) { + Precondition p = (Precondition) c; + String expressionString = p.expressionString; + AnnotationMirror anno = + c.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, tree); + JavaExpression exprJe; + try { + exprJe = StringToJavaExpression.atMethodInvocation(expressionString, tree, checker); + } catch (JavaExpressionParseException e) { + // report errors here + checker.report(tree, e.getDiagMessage()); + return; + } - /** The required type's string representation. */ - public final String required; + CFAbstractStore store = atypeFactory.getStoreBefore(tree); - private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { - if (shouldPrintVerbose(found, required)) { - this.found = found.toString(true); - this.required = required.toString(true); - } else { - this.found = found.toString(); - this.required = required.toString(); - } - } + AnnotationMirrorSet annos = + atypeFactory.getAnnotatedTypeBefore(exprJe, tree).getAnnotations(); - /** Create a FoundRequired for a type and bounds. */ - private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { - if (shouldPrintVerbose(found, required)) { - this.found = found.toString(true); - this.required = required.toString(true); - } else { - this.found = found.toString(); - this.required = required.toString(); - } - } + AnnotationMirror inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, anno); - /** - * Creates string representations of {@link AnnotatedTypeMirror}s which are only verbose if - * required to differentiate the two types. - * - * @param found the found annotation - * @param required the required annotation - * @return a string representation of the two annotations - */ - public static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { - return new FoundRequired(found, required); + // If the expression is "this", then get the type of the method receiver. + // TODO: There are other expressions that can be converted to trees, "#1" for + // example. + if (expressionString.equals("this") + && qualHierarchy.getTopAnnotations().contains(inferredAnno)) { + AnnotatedTypeMirror atype = atypeFactory.getReceiverType(tree); + if (atype != null) { + annos = atype.getEffectiveAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, anno); } + } - /** - * Creates string representations of {@link AnnotatedTypeMirror} and {@link - * AnnotatedTypeParameterBounds}s which are only verbose if required to differentiate the - * two types. - * - * @param found the found annotation - * @param required the required annotation - * @return a string representation of the two annotations - */ - public static FoundRequired of( - AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { - return new FoundRequired(found, required); + if (!checkContract(exprJe, anno, inferredAnno, store)) { + if (exprJe != null) { + expressionString = exprJe.toString(); } + checker.reportError( + tree, + "contracts.precondition.not.satisfied", + tree.getMethodSelect().toString(), + contractExpressionAndType(expressionString, inferredAnno), + contractExpressionAndType(expressionString, anno)); + } + } + } + + /** + * Returns true if and only if {@code inferredAnnotation} is valid for a given expression to match + * the {@code necessaryAnnotation}. + * + *

By default, {@code inferredAnnotation} must be a subtype of {@code necessaryAnnotation}, but + * subclasses might override this behavior. + */ + protected boolean checkContract( + JavaExpression expr, + AnnotationMirror necessaryAnnotation, + AnnotationMirror inferredAnnotation, + CFAbstractStore store) { + if (inferredAnnotation == null) { + return false; + } + TypeMirror exprTM = expr.getType(); + return qualHierarchy.isSubtypeShallow(inferredAnnotation, necessaryAnnotation, exprTM); + } + + /** + * Type checks the method arguments of {@code Vector.copyInto()}. + * + *

The Checker Framework special-cases the method invocation, as its type safety cannot be + * expressed by Java's type system. + * + *

For a Vector {@code v} of type {@code Vector}, the method invocation {@code + * v.copyInto(arr)} is type-safe iff {@code arr} is an array of type {@code T[]}, where {@code T} + * is a subtype of {@code E}. + * + *

In other words, this method checks that the type argument of the receiver method is a + * subtype of the component type of the passed array argument. + * + * @param tree a method invocation of {@code Vector.copyInto()} + * @param params the types of the parameters of {@code Vectory.copyInto()} + */ + protected void typeCheckVectorCopyIntoArgument( + MethodInvocationTree tree, List params) { + assert params.size() == 1 + : "invalid no. of parameters " + params + " found for method invocation " + tree; + assert tree.getArguments().size() == 1 + : "invalid no. of arguments in method invocation " + tree; + + AnnotatedTypeMirror passed = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedArrayType passedAsArray = (AnnotatedArrayType) passed; + + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + AnnotatedDeclaredType receiverAsVector = + AnnotatedTypes.asSuper(atypeFactory, receiver, vectorType); + if (receiverAsVector.getTypeArguments().isEmpty()) { + return; } - /** - * Return whether or not the verbose toString should be used when printing the two annotated - * types. - * - * @param atm1 the first AnnotatedTypeMirror - * @param atm2 the second AnnotatedTypeMirror - * @return true iff neither argument contains "@", or there are two annotated types (in either - * ATM) such that their toStrings are the same but their verbose toStrings differ - */ - private static boolean shouldPrintVerbose(AnnotatedTypeMirror atm1, AnnotatedTypeMirror atm2) { - if (!atm1.toString().contains("@") && !atm2.toString().contains("@")) { - return true; - } - return containsSameToString(atm1, atm2); + AnnotatedTypeMirror argComponent = passedAsArray.getComponentType(); + AnnotatedTypeMirror vectorTypeArg = receiverAsVector.getTypeArguments().get(0); + Tree errorLocation = tree.getArguments().get(0); + if (TypesUtils.isErasedSubtype( + vectorTypeArg.getUnderlyingType(), argComponent.getUnderlyingType(), types)) { + commonAssignmentCheck( + argComponent, vectorTypeArg, errorLocation, "vector.copyinto.type.incompatible"); + } else { + checker.reportError( + errorLocation, "vector.copyinto.type.incompatible", vectorTypeArg, argComponent); + } + } + + /** + * Performs a new class invocation check. + * + *

An invocation of a constructor, c, is valid only if: + * + *

    + *
  • passed arguments are subtypes of corresponding c parameters + *
  • if c is generic, passed type arguments are subtypes of c type variables + *
+ */ + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + if (checker.shouldSkipUses(TreeUtils.elementFromUse(tree))) { + return super.visitNewClass(tree, p); } - /** - * Return whether or not the verbose toString should be used when printing the annotated type - * and the bounds it is not within. - * - * @param atm the type - * @param bounds the bounds - * @return true iff bounds does not contain "@", or there are two annotated types (in either - * argument) such that their toStrings are the same but their verbose toStrings differ - */ - private static boolean shouldPrintVerbose( - AnnotatedTypeMirror atm, AnnotatedTypeParameterBounds bounds) { - if (!atm.toString().contains("@") && !bounds.toString().contains("@")) { - return true; - } - return containsSameToString(atm, bounds.getUpperBound(), bounds.getLowerBound()); + ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(tree); + AnnotatedExecutableType constructorType = fromUse.executableType; + List typeargs = fromUse.typeArgs; + + // Type check inner class enclosing expr type + checkEnclosingExpr(tree, constructorType); + List passedArguments = tree.getArguments(); + List params = constructorType.getParameterTypes(); + + ExecutableElement constructor = constructorType.getElement(); + CharSequence constructorName = ElementUtils.getSimpleDescription(constructor); + + checkArguments(params, passedArguments, constructorName, constructor.getParameters()); + checkVarargs(constructorType, tree); + + List paramBounds = + CollectionsPlume.mapList( + AnnotatedTypeVariable::getBounds, constructorType.getTypeVariables()); + + checkTypeArguments( + tree, + paramBounds, + typeargs, + tree.getTypeArguments(), + constructorName, + constructor.getTypeParameters()); + + boolean valid = validateTypeOf(tree); + + if (valid) { + AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(tree); + atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(dt, tree); + checkConstructorInvocation(dt, constructorType, tree); } + // Do not call super, as that would observe the arguments without + // a set assignment context. + scan(tree.getEnclosingExpression(), p); + scan(tree.getIdentifier(), p); + scan(tree.getClassBody(), p); - /** - * A scanner that indicates whether any (component) types have the same toString but different - * verbose toString. If so, the Checker Framework prints types verbosely. - */ - private static final SimpleAnnotatedTypeScanner> - checkContainsSameToString = - new SimpleAnnotatedTypeScanner<>( - (AnnotatedTypeMirror type, Map map) -> { - if (type == null) { - return false; - } - String simple = type.toString(); - String verbose = map.get(simple); - if (verbose == null) { - map.put(simple, type.toString(true)); - return false; - } else { - return !verbose.equals(type.toString(true)); - } - }, - Boolean::logicalOr, - false); + return null; + } - /** - * Return true iff there are two annotated types (anywhere in any ATM) such that their toStrings - * are the same but their verbose toStrings differ. If so, the Checker Framework prints types - * verbosely. - * - * @param atms annotated type mirrors to compare - * @return true iff there are two annotated types (anywhere in any ATM) such that their - * toStrings are the same but their verbose toStrings differ - */ - private static boolean containsSameToString(AnnotatedTypeMirror... atms) { - Map simpleToVerbose = new HashMap<>(); - for (AnnotatedTypeMirror atm : atms) { - if (checkContainsSameToString.visit(atm, simpleToVerbose)) { - return true; - } - } + @Override + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - return false; + AnnotatedExecutableType functionType = atypeFactory.getFunctionTypeFromTree(tree); + + if (tree.getBody().getKind() != Tree.Kind.BLOCK) { + // Check return type for single statement returns here. + AnnotatedTypeMirror ret = functionType.getReturnType(); + if (ret.getKind() != TypeKind.VOID) { + commonAssignmentCheck(ret, (ExpressionTree) tree.getBody(), "return.type.incompatible"); + } } - /** - * Checks that the array initializers are consistent with the array type. - * - * @param type the array elemen type - * @param initializers the initializers - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean checkArrayInitialization( - AnnotatedTypeMirror type, List initializers) { - // TODO: set assignment context like for method arguments? - // Also in AbstractFlow. - boolean result = true; - for (ExpressionTree init : initializers) { - result = - commonAssignmentCheck(type, init, "array.initializer.type.incompatible") - && result; - } - return result; + // Check parameters + for (int i = 0; i < functionType.getParameterTypes().size(); ++i) { + AnnotatedTypeMirror lambdaParameter = + atypeFactory.getAnnotatedType(tree.getParameters().get(i)); + commonAssignmentCheck( + lambdaParameter, + functionType.getParameterTypes().get(i), + tree.getParameters().get(i), + "lambda.param.type.incompatible", + i); } - /** - * Checks that the annotations on the type arguments supplied to a type or a method invocation - * are within the bounds of the type variables as declared, and issues the - * "type.argument.type.incompatible" error if they are not. - * - * @param toptree the tree for error reporting, only used for inferred type arguments - * @param paramBounds the bounds of the type parameters from a class or method declaration - * @param typeargs the type arguments from the type or method invocation - * @param typeargTrees the type arguments as trees, used for error reporting - */ - protected void checkTypeArguments( - Tree toptree, - List paramBounds, - List typeargs, - List typeargTrees, - CharSequence typeOrMethodName, - List paramNames) { - - // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s%n", - // toptree, paramBounds, typeargs, typeargTrees); - - // If there are no type variables, do nothing. - if (paramBounds.isEmpty()) { - return; - } + // TODO: Postconditions? + // https://github.com/typetools/checker-framework/issues/801 + + return super.visitLambdaExpression(tree, p); + } + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void p) { + this.checkMethodReferenceAsOverride(tree, p); + return super.visitMemberReference(tree, p); + } + + /** A set containing {@code Tree.Kind.METHOD} and {@code Tree.Kind.LAMBDA_EXPRESSION}. */ + private final ArraySet methodAndLambdaExpression = + new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); + + /** + * Checks that the type of the return expression is a subtype of the enclosing method required + * return type. If not, it issues a "return.type.incompatible" error. + */ + @Override + public Void visitReturn(ReturnTree tree, Void p) { + // Don't try to check return expressions for void methods. + if (tree.getExpression() == null) { + return super.visitReturn(tree, p); + } - int size = paramBounds.size(); - assert size == typeargs.size() - : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " - + typeargs - + " and type parameter bounds" - + paramBounds; + Tree enclosing = TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); - for (int i = 0; i < size; i++) { + AnnotatedTypeMirror ret = null; + if (enclosing.getKind() == Tree.Kind.METHOD) { - AnnotatedTypeParameterBounds bounds = paramBounds.get(i); - AnnotatedTypeMirror typeArg = typeargs.get(i); + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + boolean valid = validateTypeOf(enclosing); + if (valid) { + ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); + } + } else { + AnnotatedExecutableType result = + atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); + ret = result.getReturnType(); + } - if (isIgnoredUninferredWildcard(bounds.getUpperBound())) { - continue; - } + if (ret != null) { + commonAssignmentCheck(ret, tree.getExpression(), "return.type.incompatible"); + } + return super.visitReturn(tree, p); + } + + /** + * Ensure that the annotation arguments comply to their declarations. This needs some special + * casing, as annotation arguments form special trees. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + List args = tree.getArguments(); + if (args.isEmpty()) { + // Nothing to do if there are no annotation arguments. + return null; + } - AnnotatedTypeMirror paramUpperBound = bounds.getUpperBound(); + TypeElement anno = (TypeElement) TreeInfo.symbol((JCTree) tree.getAnnotationType()); - Tree reportErrorToTree; - if (typeargTrees == null || typeargTrees.isEmpty()) { - // The type arguments were inferred, report the error on the method invocation. - reportErrorToTree = toptree; - } else { - reportErrorToTree = typeargTrees.get(i); - } + Name annoName = anno.getQualifiedName(); + if (annoName.contentEquals(DefaultQualifier.class.getName()) + || annoName.contentEquals(SuppressWarnings.class.getName())) { + // Skip these two annotations, as we don't care about the arguments to them. + return null; + } - checkHasQualifierParameterAsTypeArgument(typeArg, paramUpperBound, toptree); - commonAssignmentCheck( - paramUpperBound, - typeArg, - reportErrorToTree, - "type.argument.type.incompatible", - paramNames.get(i), - typeOrMethodName); - - if (!typeHierarchy.isSubtype(bounds.getLowerBound(), typeArg)) { - FoundRequired fr = FoundRequired.of(typeArg, bounds); - checker.reportError( - reportErrorToTree, - "type.argument.type.incompatible", - paramNames.get(i), - typeOrMethodName, - fr.found, - fr.required); - } - } + List methods = ElementFilter.methodsIn(anno.getEnclosedElements()); + // Mapping from argument simple name to its annotated type. + Map annoTypes = ArrayMap.newArrayMapOrHashMap(methods.size()); + for (ExecutableElement meth : methods) { + AnnotatedExecutableType exeatm = atypeFactory.getAnnotatedType(meth); + AnnotatedTypeMirror retty = exeatm.getReturnType(); + annoTypes.put(meth.getSimpleName().toString(), retty); } - /** - * Reports an error if the type argument has a qualifier parameter and the type parameter upper - * bound does not have a qualifier parameter. - * - * @param typeArgument type argument - * @param typeParameterUpperBound upper bound of the type parameter - * @param reportError where to report the error - */ - private void checkHasQualifierParameterAsTypeArgument( - AnnotatedTypeMirror typeArgument, - AnnotatedTypeMirror typeParameterUpperBound, - Tree reportError) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (atypeFactory.hasQualifierParameterInHierarchy(typeArgument, top) - && !getTypeFactory() - .hasQualifierParameterInHierarchy(typeParameterUpperBound, top)) { - checker.reportError(reportError, "type.argument.invalid.hasqualparam", top); - } + for (ExpressionTree arg : args) { + if (!(arg instanceof AssignmentTree)) { + // TODO: when can this happen? + continue; + } + + AssignmentTree at = (AssignmentTree) arg; + // Ensure that we never ask for the annotated type of an annotation, because + // we don't have a type for annotations. + if (at.getExpression().getKind() == Tree.Kind.ANNOTATION) { + visitAnnotation((AnnotationTree) at.getExpression(), p); + continue; + } + if (at.getExpression().getKind() == Tree.Kind.NEW_ARRAY) { + NewArrayTree nat = (NewArrayTree) at.getExpression(); + boolean isAnno = false; + for (ExpressionTree init : nat.getInitializers()) { + if (init.getKind() == Tree.Kind.ANNOTATION) { + visitAnnotation((AnnotationTree) init, p); + isAnno = true; + } + } + if (isAnno) { + continue; } + } + + AnnotatedTypeMirror expected = annoTypes.get(at.getVariable().toString()); + AnnotatedTypeMirror actual = atypeFactory.getAnnotatedType(at.getExpression()); + if (expected.getKind() != TypeKind.ARRAY) { + // Expected is not an array -> direct comparison. + commonAssignmentCheck(expected, actual, at.getExpression(), "annotation.type.incompatible"); + } else if (actual.getKind() == TypeKind.ARRAY) { + // Both actual and expected are arrays. + commonAssignmentCheck(expected, actual, at.getExpression(), "annotation.type.incompatible"); + } else { + // The declaration is an array type, but just a single element is given. + commonAssignmentCheck( + ((AnnotatedArrayType) expected).getComponentType(), + actual, + at.getExpression(), + "annotation.type.incompatible"); + } + } + return null; + } + + /** + * If the computation of the type of the ConditionalExpressionTree in + * org.checkerframework.framework.type.TypeFromTree.TypeFromExpression.visitConditionalExpression(ConditionalExpressionTree, + * AnnotatedTypeFactory) is correct, the following checks are redundant. However, let's add + * another failsafe guard and do the checks. + */ + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + AnnotatedTypeMirror cond = atypeFactory.getAnnotatedType(tree); + this.commonAssignmentCheck(cond, tree.getTrueExpression(), "conditional.type.incompatible"); + this.commonAssignmentCheck(cond, tree.getFalseExpression(), "conditional.type.incompatible"); + return super.visitConditionalExpression(tree, p); + } + + /** + * This method validates the type of the switch expression. It issues an error if the type of a + * value that the switch expression can result is not a subtype of the switch type. + * + *

If a subclass overrides this method, it must call {@code super.scan(switchExpressionTree, + * null)} so that the blocks and statements in the cases are checked. + * + * @param switchExpressionTree a {@code SwitchExpressionTree} + */ + public void visitSwitchExpression17(Tree switchExpressionTree) { + boolean valid = validateTypeOf(switchExpressionTree); + if (valid) { + AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(switchExpressionTree); + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree valueTree, Void unused) -> { + BaseTypeVisitor.this.commonAssignmentCheck( + switchType, valueTree, "switch.expression.type.incompatible"); + return null; + }, + (r1, r2) -> null); + + scanner.scanSwitchExpression(switchExpressionTree, null); + } + super.scan(switchExpressionTree, null); + } + + // ********************************************************************** + // Check for illegal re-assignment + // ********************************************************************** + + /** Performs assignability check. */ + @Override + public Void visitUnary(UnaryTree tree, Void p) { + Tree.Kind treeKind = tree.getKind(); + if (treeKind == Tree.Kind.PREFIX_DECREMENT + || treeKind == Tree.Kind.PREFIX_INCREMENT + || treeKind == Tree.Kind.POSTFIX_DECREMENT + || treeKind == Tree.Kind.POSTFIX_INCREMENT) { + // Check the assignment that occurs at the increment/decrement. i.e.: + // exp = exp + 1 or exp = exp - 1 + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(tree.getExpression()); + AnnotatedTypeMirror valueType; + if (treeKind == Tree.Kind.POSTFIX_DECREMENT || treeKind == Tree.Kind.POSTFIX_INCREMENT) { + // For postfixed increments or decrements, the type of the tree the type of the + // expression before 1 is added or subtracted. So, use a special method to get the + // type after 1 has been added or subtracted. + valueType = atypeFactory.getAnnotatedTypeRhsUnaryAssign(tree); + } else { + // For prefixed increments or decrements, the type of the tree the type of the + // expression after 1 is added or subtracted. So, its type can be found using the + // usual method. + valueType = atypeFactory.getAnnotatedType(tree); + } + String errorKey = + (treeKind == Tree.Kind.PREFIX_INCREMENT || treeKind == Tree.Kind.POSTFIX_INCREMENT) + ? "unary.increment.type.incompatible" + : "unary.decrement.type.incompatible"; + commonAssignmentCheck(varType, valueType, tree, errorKey); + } + return super.visitUnary(tree, p); + } + + /** Performs assignability check. */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + // If tree is the tree representing the compounds assignment s += expr, + // Then this method should check whether s + expr can be assigned to s, + // but the "s + expr" tree does not exist. So instead, check that + // s += expr can be assigned to s. + commonAssignmentCheck(tree.getVariable(), tree, "compound.assignment.type.incompatible"); + return super.visitCompoundAssignment(tree, p); + } + + // ********************************************************************** + // Check for invalid types inserted by the user + // ********************************************************************** + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + boolean valid = validateTypeOf(tree); + + if (valid && tree.getType() != null) { + AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(tree); + atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(arrayType, tree); + if (tree.getInitializers() != null) { + checkArrayInitialization(arrayType.getComponentType(), tree.getInitializers()); + } } - private boolean isIgnoredUninferredWildcard(AnnotatedTypeMirror type) { - return atypeFactory.ignoreUninferredTypeArguments - && type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument(); + return super.visitNewArray(tree, p); + } + + /** + * If the lint option "cast:redundant" is set, this method issues a warning if the cast is + * redundant. + */ + protected void checkTypecastRedundancy(TypeCastTree typeCastTree) { + if (!checker.getLintOption("cast:redundant", false)) { + return; } - /** - * Indicates whether to skip subtype checks on the receiver when checking method invocability. A - * visitor may, for example, allow a method to be invoked even if the receivers are siblings in - * a hierarchy, provided that some other condition (implemented by the visitor) is satisfied. - * - * @param tree the method invocation tree - * @param methodDefinitionReceiver the ATM of the receiver of the method definition - * @param methodCallReceiver the ATM of the receiver of the method call - * @return whether to skip subtype checks on the receiver - */ - protected boolean skipReceiverSubtypeCheck( - MethodInvocationTree tree, - AnnotatedTypeMirror methodDefinitionReceiver, - AnnotatedTypeMirror methodCallReceiver) { - return false; + AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); + + if (castType.equals(exprType)) { + checker.reportWarning(typeCastTree, "cast.redundant", castType); + } + } + + /** + * Issues a warning if the given explicitly-written typecast is unsafe. Does nothing if the lint + * option "cast:unsafe" is not set. Only primary qualifiers are checked unless the command line + * option "checkCastElementType" is supplied. + * + * @param typeCastTree an explicitly-written typecast + */ + protected void checkTypecastSafety(TypeCastTree typeCastTree) { + if (!checker.getLintOption("cast:unsafe", true)) { + return; + } + AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); + boolean reported = false; + for (AnnotationMirror top : atypeFactory.getQualifierParameterHierarchies(castType)) { + if (!isTypeCastSafeInvariant(castType, exprType, top)) { + checker.reportError( + typeCastTree, + "invariant.cast.unsafe", + exprType.toString(true), + castType.toString(true)); + } + reported = true; // don't issue cast unsafe warning. } - /** - * Tests whether the method can be invoked using the receiver of the 'tree' method invocation, - * and issues a "method.invocation.invalid" if the invocation is invalid. - * - *

This implementation tests whether the receiver in the method invocation is a subtype of - * the method receiver type. This behavior can be specialized by overriding - * skipReceiverSubtypeCheck. - * - * @param method the type of the invoked method - * @param tree the method invocation tree - */ - protected void checkMethodInvocability( - AnnotatedExecutableType method, MethodInvocationTree tree) { - if (method.getReceiverType() == null) { - // Static methods don't have a receiver to check. - return; - } - if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) { - // TODO: Explicit "this()" calls of constructors have an implicit passed - // from the enclosing constructor. We must not use the self type, but - // instead should find a way to determine the receiver of the enclosing constructor. - // rcv = - // ((AnnotatedExecutableType)atypeFactory.getAnnotatedType(atypeFactory.getEnclosingMethod(tree))).getReceiverType(); - return; - } + // Don't call TypeHierarchy#isSubtype(exprType, castType) because the underlying Java types + // will not be in the correct subtyping relationship if this is a downcast. + if (!reported && !isTypeCastSafe(castType, exprType)) { + checker.reportWarning( + typeCastTree, "cast.unsafe", exprType.toString(true), castType.toString(true)); + } + } + + /** + * Returns true if the cast is safe. + * + *

Only primary qualifiers are checked unless the command line option "checkCastElementType" is + * supplied. + * + * @param castType annotated type of the cast + * @param exprType annotated type of the casted expression + * @return true if the type cast is safe, false otherwise + */ + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + TypeKind castTypeKind = castType.getKind(); + if (castTypeKind == TypeKind.DECLARED) { + // Don't issue an error if the annotations are equivalent to the qualifier upper bound + // of the type. + AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType; + AnnotationMirrorSet bounds = + atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType()); + + if (AnnotationUtils.areSame(castDeclared.getAnnotations(), bounds)) { + return true; + } + } - AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); - AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); - AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); + AnnotationMirrorSet castAnnos; + AnnotatedTypeMirror newCastType; + TypeMirror newCastTM; + if (!checkCastElementType) { + // checkCastElementType option wasn't specified, so only check effective annotations. + castAnnos = castType.getEffectiveAnnotations(); + newCastType = castType; + newCastTM = newCastType.getUnderlyingType(); + } else { + if (castTypeKind == TypeKind.TYPEVAR) { + newCastType = ((AnnotatedTypeVariable) castType).getUpperBound(); + } else { + newCastType = castType; + } + newCastTM = newCastType.getUnderlyingType(); + AnnotatedTypeMirror newExprType; + if (exprType.getKind() == TypeKind.TYPEVAR) { + newExprType = ((AnnotatedTypeVariable) exprType).getUpperBound(); + } else { + newExprType = exprType; + } + TypeMirror newExprTM = newExprType.getUnderlyingType(); - treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); + if (!typeHierarchy.isSubtype(newExprType, newCastType)) { + return false; + } + if (newCastType.getKind() == TypeKind.ARRAY && newExprType.getKind() != TypeKind.ARRAY) { + // Always warn if the cast contains an array, but the expression + // doesn't, as in "(Object[]) o" where o is of type Object + return false; + } else if (newCastType.getKind() == TypeKind.DECLARED + && newExprType.getKind() == TypeKind.DECLARED) { + int castSize = ((AnnotatedDeclaredType) newCastType).getTypeArguments().size(); + int exprSize = ((AnnotatedDeclaredType) newExprType).getTypeArguments().size(); + + if (castSize != exprSize) { + // Always warn if the cast and expression contain a different number of type + // arguments, e.g. to catch a cast from "Object" to "List<@NonNull Object>". + // TODO: the same number of arguments actually doesn't guarantee anything. + return false; + } + } else if (castTypeKind == TypeKind.TYPEVAR && exprType.getKind() == TypeKind.TYPEVAR) { + // If both the cast type and the casted expression are type variables, then check + // the bounds. + AnnotationMirrorSet lowerBoundAnnotationsCast = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); + AnnotationMirrorSet lowerBoundAnnotationsExpr = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, exprType); + return qualHierarchy.isSubtypeShallow( + lowerBoundAnnotationsExpr, newExprTM, lowerBoundAnnotationsCast, newCastTM) + && typeHierarchy.isSubtypeShallowEffective(exprType, castType); + } + if (castTypeKind == TypeKind.TYPEVAR) { + // If the cast type is a type var, but the expression is not, then check that the + // type of the expression is a subtype of the lower bound. + castAnnos = AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); + } else { + castAnnos = castType.getAnnotations(); + } + } - if (!skipReceiverSubtypeCheck(tree, methodReceiver, rcv)) { - // The diagnostic can be a bit misleading because the check is of the receiver but - // `tree` is the entire method invocation (where the receiver might be implicit). - commonAssignmentCheckStartDiagnostic(methodReceiver, treeReceiver, tree); - boolean success = typeHierarchy.isSubtype(treeReceiver, methodReceiver); - commonAssignmentCheckEndDiagnostic(success, null, methodReceiver, treeReceiver, tree); - if (!success) { - reportMethodInvocabilityError(tree, treeReceiver, methodReceiver); - } - } + AnnotatedTypeMirror exprTypeWidened = atypeFactory.getWidenedType(exprType, castType); + return qualHierarchy.isSubtypeShallow( + exprTypeWidened.getEffectiveAnnotations(), + exprTypeWidened.getUnderlyingType(), + castAnnos, + newCastTM); + } + + /** + * Return whether casting the {@code exprType} to {@code castType}, a type with a qualifier + * parameter, is legal. + * + *

If {@code exprType} has qualifier parameter, the cast is legal if the qualifiers are + * invariant. Otherwise, the cast is legal is if the qualifier on both types is bottom. + * + * @param castType a type with a qualifier parameter + * @param exprType type of the expressions that is cast which may or may not have a qualifier + * parameter + * @param top the top qualifier of the hierarchy to check + * @return whether casting the {@code exprType} to {@code castType}, a type with a qualifier + * parameter, is legal. + */ + private boolean isTypeCastSafeInvariant( + AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType, AnnotationMirror top) { + if (!isTypeCastSafe(castType, exprType)) { + return false; } - /** - * Report a method invocability error. Allows checkers to change how the message is output. - * - * @param tree the AST node at which to report the error - * @param found the actual type of the receiver - * @param expected the expected type of the receiver - */ - protected void reportMethodInvocabilityError( - MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { - checker.reportError( - tree, - "method.invocation.invalid", - TreeUtils.elementFromUse(tree), - found.toString(), - expected.toString()); + if (atypeFactory.hasQualifierParameterInHierarchy(exprType, top)) { + // The isTypeCastSafe call above checked that the exprType is a subtype of castType, + // so just check the reverse to check that the qualifiers are equivalent. + return typeHierarchy.isSubtypeShallowEffective(castType, exprType, top); + } + AnnotationMirror castTypeAnno = castType.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror exprTypeAnno = exprType.getEffectiveAnnotationInHierarchy(top); + // Otherwise the cast is unsafe, unless the qualifiers on both cast and expr are bottom. + AnnotationMirror bottom = qualHierarchy.getBottomAnnotation(top); + return AnnotationUtils.areSame(castTypeAnno, bottom) + && AnnotationUtils.areSame(exprTypeAnno, bottom); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + // validate "tree" instead of "tree.getType()" to prevent duplicate errors. + boolean valid = validateTypeOf(tree) && validateTypeOf(tree.getExpression()); + if (valid) { + checkTypecastSafety(tree); + checkTypecastRedundancy(tree); } + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - /** - * Check that the (explicit) annotations on a new class tree are comparable to the result type - * of the constructor. Issue an error if not. - * - *

Issue a warning if the annotations on the constructor invocation is a subtype of the - * constructor result type. This is equivalent to down-casting. - * - *

For type checking of the enclosing expression of inner type instantiations, see {@link - * #checkEnclosingExpr(NewClassTree, AnnotatedTypeMirror.AnnotatedExecutableType)} - * - * @param invocation the AnnotatedDeclaredType of the constructor invocation - * @param constructor the AnnotatedExecutableType of the constructor declaration - * @param newClassTree the NewClassTree - */ - protected void checkConstructorInvocation( - AnnotatedDeclaredType invocation, - AnnotatedExecutableType constructor, - NewClassTree newClassTree) { - // Only check the primary annotations, the type arguments are checked elsewhere. - AnnotationMirrorSet explicitAnnos = atypeFactory.getExplicitNewClassAnnos(newClassTree); - if (explicitAnnos.isEmpty()) { - return; - } - for (AnnotationMirror explicit : explicitAnnos) { - // The return type of the constructor (resultAnnos) must be comparable to the - // annotations on the constructor invocation (explicitAnnos). - boolean resultIsSubtypeOfExplicit = - typeHierarchy.isSubtypeShallowEffective(constructor.getReturnType(), explicit); - if (!(typeHierarchy.isSubtypeShallowEffective(explicit, constructor.getReturnType()) - || resultIsSubtypeOfExplicit)) { - AnnotationMirror resultAnno = - constructor.getReturnType().getAnnotationInHierarchy(explicit); - checker.reportError( - newClassTree, - "constructor.invocation.invalid", - constructor.toString(), - explicit, - resultAnno); - return; - } else if (!resultIsSubtypeOfExplicit) { - AnnotationMirror resultAnno = - constructor.getReturnType().getAnnotationInHierarchy(explicit); - // Issue a warning if the annotations on the constructor invocation is a subtype of - // the constructor result type. This is equivalent to down-casting. - checker.reportWarning( - newClassTree, "cast.unsafe.constructor.invocation", resultAnno, explicit); - return; - } - } + if (atypeFactory.getDependentTypesHelper().hasDependentAnnotations()) { + atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(type, tree.getType()); } - /** - * Helper method to type check the enclosing expression of an inner class instantiation. - * - * @param node the NewClassTree - * @param constructorType the annotatedExecutableType of the constructor declaration - */ - protected void checkEnclosingExpr(NewClassTree node, AnnotatedExecutableType constructorType) { - if (!checkEnclosingExpr) { - return; + if (tree.getType().getKind() == Tree.Kind.INTERSECTION_TYPE) { + AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type; + checkExplicitAnnotationsOnIntersectionBounds( + intersection, ((IntersectionTypeTree) tree.getType()).getBounds()); + } + return super.visitTypeCast(tree, p); + } + + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + // The "reference type" is the type after "instanceof". + Tree patternTree = InstanceOfUtils.getPattern(tree); + if (patternTree != null) { + if (TreeUtils.isBindingPatternTree(patternTree)) { + VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); + validateTypeOf(variableTree); + if (variableTree.getModifiers() != null) { + AnnotatedTypeMirror variableType = atypeFactory.getAnnotatedType(variableTree); + AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(tree.getExpression()); + if (!isTypeCastSafe(variableType, expType)) { + checker.reportWarning(tree, "instanceof.pattern.unsafe", expType, variableTree); + } } - AnnotatedTypeMirror formalReceiverType = constructorType.getReceiverType(); - if (formalReceiverType != null) { - AnnotatedTypeMirror passedReceiverType = atypeFactory.getReceiverType(node); - commonAssignmentCheck( - formalReceiverType, - passedReceiverType, - node, - "enclosingexpr.type.incompatible"); + } else { + // TODO: implement deconstructed patterns. + } + } else { + Tree refTypeTree = tree.getType(); + validateTypeOf(refTypeTree); + if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeMirror refType = atypeFactory.getAnnotatedType(refTypeTree); + AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(tree.getExpression()); + if (typeHierarchy.isSubtype(refType, expType) + && !refType.getAnnotations().equals(expType.getAnnotations())) { + checker.reportWarning(tree, "instanceof.unsafe", expType, refType); } + } } - /** - * A helper method to check that each passed argument is a subtype of the corresponding required - * argument. Issues an "argument.invalid" error for each passed argument that not a subtype of - * the required one. - * - *

Note this method requires the lists to have the same length, as it does not handle cases - * like var args. - * - * @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree) - * @param requiredArgs the required types. This may differ from the formal parameter types, - * because it replaces a varargs parameter by multiple parameters with the vararg's element - * type. - * @param passedArgs the expressions passed to the corresponding types - * @param executableName the name of the method or constructor being called - * @param paramNames the names of the callee's formal parameters - */ - protected void checkArguments( - List requiredArgs, - List passedArgs, - CharSequence executableName, - List paramNames) { - int size = requiredArgs.size(); - assert size == passedArgs.size() - : "mismatch between required args (" - + requiredArgs - + ") and passed args (" - + passedArgs - + ")"; - int maxParamNamesIndex = paramNames.size() - 1; - // Rather weak assertion, due to how varargs parameters are treated. - assert size >= maxParamNamesIndex - : String.format( - "mismatched lengths %d %d %d checkArguments(%s, %s, %s, %s)", - size, - passedArgs.size(), - paramNames.size(), - listToString(requiredArgs), - listToString(passedArgs), - executableName, - listToString(paramNames)); - - for (int i = 0; i < size; ++i) { - commonAssignmentCheck( - requiredArgs.get(i), - passedArgs.get(i), - "argument.type.incompatible", - // TODO: for expanded varargs parameters, maybe adjust the name - paramNames.get(Math.min(i, maxParamNamesIndex)), - executableName); - // Also descend into the argument within the correct assignment context. - scan(passedArgs.get(i), null); - } + return super.visitInstanceOf(tree, p); + } + + /** + * Checks the type of the exception parameter. Subclasses should override {@link + * #checkExceptionParameter} rather than this method to change the behavior of this check. + */ + @Override + public Void visitCatch(CatchTree tree, Void p) { + checkExceptionParameter(tree); + return super.visitCatch(tree, p); + } + + /** + * Checks the type of a thrown exception. Subclasses should override + * checkThrownExpression(ThrowTree tree) rather than this method to change the behavior of this + * check. + */ + @Override + public Void visitThrow(ThrowTree tree, Void p) { + checkThrownExpression(tree); + return super.visitThrow(tree, p); + } + + /** + * Rather than overriding this method, clients should often override {@link + * #visitAnnotatedType(List,Tree)}. That method also handles the case of annotations at the + * beginning of a variable or method declaration. javac parses all those annotations as being on + * the variable or method declaration, even though the ones that are type annotations logically + * belong to the variable type or method return type. + */ + @Override + public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { + visitAnnotatedType(null, tree); + return super.visitAnnotatedType(tree, p); + } + + /** + * Checks an annotated type. Invoked by {@link #visitAnnotatedType(AnnotatedTypeTree, Void)}, + * {@link #visitVariable}, and {@link #visitMethod}. Exists to prevent code duplication among the + * three. Checking in {@code visitVariable} and {@code visitMethod} is needed because there isn't + * an AnnotatedTypeTree within a variable declaration or for a method return type -- all the + * annotations are attached to the VariableTree or MethodTree, respectively. + * + * @param annoTrees annotations written before a variable/method declaration, if this type is from + * one; null otherwise. This might contain type annotations that the Java parser attached to + * the declaration rather than to the type. + * @param typeTree the type that any type annotations in annoTrees apply to + */ + public void visitAnnotatedType( + @Nullable List annoTrees, Tree typeTree) { + warnAboutIrrelevantJavaTypes(annoTrees, typeTree); + } + + /** + * Warns if a type annotation is written on a Java type that is not listed in + * the @RelevantJavaTypes annotation. + * + * @param annoTrees annotations written before a variable/method declaration, if this type is from + * one; null otherwise. This might contain type annotations that the Java parser attached to + * the declaration rather than to the type. + * @param typeTree the type that any type annotations in annoTrees apply to + */ + public void warnAboutIrrelevantJavaTypes( + @Nullable List annoTrees, Tree typeTree) { + if (!shouldWarnAboutIrrelevantJavaTypes()) { + return; } - // com.sun.tools.javac.util.List has a toString that does not include surrounding "[...]", - // making it hard to interpret in messages. - /** - * Produce a printed representation of a list, in the standard format with surrounding "[...]". - * - * @param lst a list to format - * @return the printed representation of the list - */ - private String listToString(List lst) { - StringJoiner result = new StringJoiner(",", "[", "]"); - for (Object elt : lst) { - result.add(elt.toString()); - } - return result.toString(); + Tree t = typeTree; + while (true) { + switch (t.getKind()) { + + // Recurse for compound types whose top level is not at the far left. + case ARRAY_TYPE: + t = ((ArrayTypeTree) t).getType(); + continue; + case MEMBER_SELECT: + t = ((MemberSelectTree) t).getExpression(); + continue; + case PARAMETERIZED_TYPE: + t = ((ParameterizedTypeTree) t).getType(); + continue; + + // Base cases + case PRIMITIVE_TYPE: + case IDENTIFIER: + maybeReportAnnoOnIrrelevant(t, TreeUtils.typeOf(t), annoTrees); + return; + case ANNOTATED_TYPE: + AnnotatedTypeTree at = (AnnotatedTypeTree) t; + ExpressionTree underlying = at.getUnderlyingType(); + maybeReportAnnoOnIrrelevant(t, TreeUtils.typeOf(underlying), at.getAnnotations()); + return; + + default: + return; + } + } + } + + /** + * If the given Java basetype is not relevant, report an "anno.on.irrelevant" if it is annotated. + * This method does not necessarily issue an error, but it might. + * + * @param errorLocation where to repor the error + * @param type the Java basetype + * @param annos the annotation on the type + */ + private void maybeReportAnnoOnIrrelevant( + Tree errorLocation, TypeMirror type, List annos) { + List supportedAnnoTrees = supportedAnnoTrees(annos); + if (!supportedAnnoTrees.isEmpty() && !atypeFactory.isRelevant(type)) { + String extraInfo = atypeFactory.irrelevantExtraMessage(); + checker.reportError(errorLocation, "anno.on.irrelevant", annos, type, extraInfo); } + } + + /** + * Returns true if the checker should issue warnings about irrelevant java types. + * + * @return true if the checker should issue warnings about irrelevant java types + */ + protected boolean shouldWarnAboutIrrelevantJavaTypes() { + return atypeFactory.relevantJavaTypes != null; + } + + /** + * Returns a new list containing only the supported annotations from its argument -- that is, + * those that are part of the current type system. + * + *

This method ignores aliases of supported annotations that are declaration annotations, + * because they may apply to inner types. + * + * @param annoTrees annotation trees + * @return a new list containing only the supported annotations from its argument + */ + private List supportedAnnoTrees(List annoTrees) { + List result = new ArrayList<>(1); + for (AnnotationTree at : annoTrees) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(at); + if (AnnotationUtils.isTypeUseAnnotation(anno) && atypeFactory.isSupportedQualifier(anno)) { + result.add(at); + } + } + return result; + } + + // ********************************************************************** + // Helper methods to provide a single overriding point + // ********************************************************************** + + /** Cache to avoid calling {@link #getExceptionParameterLowerBoundAnnotations} more than once. */ + private @MonotonicNonNull AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCache; + + /** + * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. The same as + * {@link #getExceptionParameterLowerBoundAnnotations}, but uses a cache. + * + * @return a set of AnnotationMirrors that is a lower bound for exception parameters + */ + private AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCached() { + if (getExceptionParameterLowerBoundAnnotationsCache == null) { + getExceptionParameterLowerBoundAnnotationsCache = + getExceptionParameterLowerBoundAnnotations(); + } + return getExceptionParameterLowerBoundAnnotationsCache; + } + + /** + * Issue error if the exception parameter is not a supertype of the annotation specified by {@link + * #getExceptionParameterLowerBoundAnnotations()}, which is top by default. + * + *

Subclasses may override this method to change the behavior of this check. Subclasses wishing + * to enforce that exception parameter be annotated with other annotations can just override + * {@link #getExceptionParameterLowerBoundAnnotations()}. + * + * @param tree a CatchTree to check + */ + protected void checkExceptionParameter(CatchTree tree) { + AnnotationMirrorSet requiredAnnotations = getExceptionParameterLowerBoundAnnotationsCached(); + VariableTree excParamTree = tree.getParameter(); + AnnotatedTypeMirror excParamType = atypeFactory.getAnnotatedType(excParamTree); + + for (AnnotationMirror required : requiredAnnotations) { + AnnotationMirror found = excParamType.getAnnotationInHierarchy(required); + assert found != null; + if (!typeHierarchy.isSubtypeShallowEffective(required, excParamType)) { + checker.reportError(excParamTree, "exception.parameter.invalid", found, required); + } - /** - * Returns true if both types are type variables and outer contains inner. Outer contains inner - * implies: {@literal inner.upperBound <: outer.upperBound outer.lowerBound <: - * inner.lowerBound}. - * - * @return true if both types are type variables and outer contains inner - */ - protected boolean testTypevarContainment(AnnotatedTypeMirror inner, AnnotatedTypeMirror outer) { - if (inner.getKind() == TypeKind.TYPEVAR && outer.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable innerAtv = (AnnotatedTypeVariable) inner; - AnnotatedTypeVariable outerAtv = (AnnotatedTypeVariable) outer; - - if (AnnotatedTypes.areCorrespondingTypeVariables(elements, innerAtv, outerAtv)) { - return typeHierarchy.isSubtype(innerAtv.getUpperBound(), outerAtv.getUpperBound()) - && typeHierarchy.isSubtype( - outerAtv.getLowerBound(), innerAtv.getLowerBound()); - } + if (excParamType.getKind() == TypeKind.UNION) { + AnnotatedUnionType aut = (AnnotatedUnionType) excParamType; + for (AnnotatedTypeMirror alternativeType : aut.getAlternatives()) { + if (!typeHierarchy.isSubtypeShallowEffective(required, alternativeType)) { + AnnotationMirror alternativeAnno = alternativeType.getAnnotationInHierarchy(required); + checker.reportError( + excParamTree, "exception.parameter.invalid", alternativeAnno, required); + } } - - return false; + } + } + } + + /** + * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. + * + *

This implementation returns top; subclasses can change this behavior. + * + *

Note: by default this method is called by {@link #getThrowUpperBoundAnnotations()}, so that + * this annotation is enforced. + * + * @return set of annotation mirrors, one per hierarchy, that form a lower bound of annotations + * that can be written on an exception parameter + */ + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return qualHierarchy.getTopAnnotations(); + } + + /** + * Checks the type of the thrown expression. + * + *

By default, this method checks that the thrown expression is a subtype of top. + * + *

Issue error if the thrown expression is not a sub type of the annotation given by {@link + * #getThrowUpperBoundAnnotations()}, the same as {@link + * #getExceptionParameterLowerBoundAnnotations()} by default. + * + *

Subclasses may override this method to change the behavior of this check. Subclasses wishing + * to enforce that the thrown expression be a subtype of a type besides {@link + * #getExceptionParameterLowerBoundAnnotations}, should override {@link + * #getThrowUpperBoundAnnotations()}. + * + * @param tree a ThrowTree to check + */ + protected void checkThrownExpression(ThrowTree tree) { + AnnotatedTypeMirror throwType = atypeFactory.getAnnotatedType(tree.getExpression()); + TypeMirror throwTM = throwType.getUnderlyingType(); + Set required = getThrowUpperBoundAnnotations(); + switch (throwType.getKind()) { + case NULL: + case DECLARED: + case TYPEVAR: + case WILDCARD: + if (!typeHierarchy.isSubtypeShallowEffective(throwType, required)) { + AnnotationMirrorSet found = throwType.getEffectiveAnnotations(); + checker.reportError(tree.getExpression(), "throw.type.invalid", found, required); + } + break; + + case UNION: + AnnotatedUnionType unionType = (AnnotatedUnionType) throwType; + AnnotationMirrorSet foundPrimary = unionType.getAnnotations(); + if (!qualHierarchy.isSubtypeShallow(foundPrimary, required, throwTM)) { + checker.reportError(tree.getExpression(), "throw.type.invalid", foundPrimary, required); + } + for (AnnotatedTypeMirror altern : unionType.getAlternatives()) { + TypeMirror alternTM = altern.getUnderlyingType(); + if (!qualHierarchy.isSubtypeShallow(altern.getAnnotations(), required, alternTM)) { + checker.reportError( + tree.getExpression(), "throw.type.invalid", altern.getAnnotations(), required); + } + } + break; + default: + throw new BugInCF("Unexpected throw expression type: " + throwType.getKind()); + } + } + + /** + * Returns a set of AnnotationMirrors that is a upper bound for thrown exceptions. + * + *

Note: by default this method is returns by getExceptionParameterLowerBoundAnnotations(), so + * that this annotation is enforced. + * + *

(Default is top) + * + * @return set of annotation mirrors, one per hierarchy, that form an upper bound of thrown + * expressions + */ + protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { + return getExceptionParameterLowerBoundAnnotations(); + } + + /** + * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and + * emits an error message (through the compiler's messaging interface) if it is not valid. + * + * @param varTree the AST node for the lvalue (usually a variable) + * @param valueExpTree the AST node for the rvalue (the new value) + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(varTree); + assert varType != null : "no variable found for tree: " + varTree; + + if (!validateType(varTree, varType)) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because validateType() returned false", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return true; } - /** - * Create an OverrideChecker. - * - *

This exists so that subclasses can subclass OverrideChecker and use their subclass instead - * of using OverrideChecker itself. - * - * @param overriderTree the AST node of the overriding method or method reference - * @param overriderMethodType the type of the overriding method - * @param overriderType the type enclosing the overrider method, usually an - * AnnotatedDeclaredType; for Method References may be something else - * @param overriderReturnType the return type of the overriding method - * @param overriddenMethodType the type of the overridden method - * @param overriddenType the declared type enclosing the overridden method - * @param overriddenReturnType the return type of the overridden method - * @return an OverrideChecker - */ - protected OverrideChecker createOverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overriderMethodType, - AnnotatedTypeMirror overriderType, - AnnotatedTypeMirror overriderReturnType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - return new OverrideChecker( - overriderTree, - overriderMethodType, - overriderType, - overriderReturnType, - overriddenMethodType, - overriddenType, - overriddenReturnType); + return commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); + } + + /** + * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and + * emits an error message (through the compiler's messaging interface) if it is not valid. + * + * @param varType the annotated type for the lvalue (usually a variable) + * @param valueExpTree the AST node for the rvalue (the new value) + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + if (shouldSkipUses(valueExpTree)) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because shouldSkipUses() returned true", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return true; + } + if (valueExpTree.getKind() == Tree.Kind.MEMBER_REFERENCE + || valueExpTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + // Member references and lambda expressions are type checked separately + // and do not need to be checked again as arguments. + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because member reference and lambda expression are type checked separately", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return true; + } + boolean result = true; + if (varType.getKind() == TypeKind.ARRAY + && valueExpTree instanceof NewArrayTree + && ((NewArrayTree) valueExpTree).getType() == null) { + AnnotatedTypeMirror compType = ((AnnotatedArrayType) varType).getComponentType(); + NewArrayTree arrayTree = (NewArrayTree) valueExpTree; + assert arrayTree.getInitializers() != null + : "array initializers are not expected to be null in: " + valueExpTree; + result = checkArrayInitialization(compType, arrayTree.getInitializers()) && result; + } + if (!validateTypeOf(valueExpTree)) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because validateType() returned false", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return result; + } + AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExpTree); + assert valueType != null : "null type for expression: " + valueExpTree; + result = commonAssignmentCheck(varType, valueType, valueExpTree, errorKey, extraArgs) && result; + return result; + } + + /** + * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and + * emits an error message (through the compiler's messaging interface) if it is not valid. + * + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + commonAssignmentCheckStartDiagnostic(varType, valueType, valueExpTree); + + AnnotatedTypeMirror widenedValueType = atypeFactory.getWidenedType(valueType, varType); + boolean result = typeHierarchy.isSubtype(widenedValueType, varType); + + // TODO: integrate with subtype test. + if (result) { + for (Class mono : atypeFactory.getSupportedMonotonicTypeQualifiers()) { + if (valueType.hasAnnotation(mono) && varType.hasAnnotation(mono)) { + checker.reportError( + valueExpTree, + "monotonic.type.incompatible", + mono.getSimpleName(), + mono.getSimpleName(), + valueType.toString()); + result = false; + } + } + } else { + // `result` is false. + // Use an error key only if it's overridden by a checker. + reportCommonAssignmentError(varType, widenedValueType, valueExpTree, errorKey, extraArgs); } - /** - * Type checks that a method may override another method. Uses an OverrideChecker subclass as - * created by {@link #createOverrideChecker}. This version of the method uses the annotated type - * factory to get the annotated type of the overriding method, and does NOT expose that type. - * - * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType) - * @param overriderTree declaration tree of overriding method - * @param overriderType type of overriding class - * @param overriddenMethodType type of overridden method - * @param overriddenType type of overridden class - * @return true if the override is allowed - */ - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedDeclaredType overriderType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType) { + commonAssignmentCheckEndDiagnostic(result, null, varType, valueType, valueExpTree); + + return result; + } + + /** + * Report a common assignment error. Allows checkers to change how the message is output. + * + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueTree the location to use when reporting the error message + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + */ + protected void reportCommonAssignmentError( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + FoundRequired pair = FoundRequired.of(valueType, varType); + String valueTypeString = pair.found; + String varTypeString = pair.required; + checker.reportError( + valueTree, errorKey, ArraysPlume.concatenate(extraArgs, valueTypeString, varTypeString)); + } + + /** + * Prints a diagnostic about entering {@code commonAssignmentCheck()}, if the showchecks option + * was set. + * + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + */ + protected final void commonAssignmentCheckStartDiagnostic( + AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueExpTree) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "about to test whether actual is a subtype of expected", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + valueType.getKind(), + valueType.toString(), + varType.getKind(), + varType.toString()); + } + } + + /** + * Prints a diagnostic about exiting {@code commonAssignmentCheck()}, if the showchecks option was + * set. + * + * @param success whether the check succeeded or failed + * @param extraMessage information about why the result is what it is; may be null + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + */ + protected final void commonAssignmentCheckEndDiagnostic( + boolean success, + @Nullable String extraMessage, + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueExpTree) { + if (showchecks) { + commonAssignmentCheckEndDiagnostic( + (success + ? "success: actual is subtype of expected" + : "FAILURE: actual is not subtype of expected") + + (extraMessage == null ? "" : " because " + extraMessage), + varType, + valueType, + valueExpTree); + } + } + + /** + * Helper method for printing a diagnostic about exiting {@code commonAssignmentCheck()}, if the + * showchecks option was set. + * + *

Most clients should call {@link #commonAssignmentCheckEndDiagnostic(boolean, String, + * AnnotatedTypeMirror, AnnotatedTypeMirror, Tree)}. The purpose of this method is to permit + * customizing the message that is printed. + * + * @param message the result, plus information about why the result is what it is + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + */ + protected final void commonAssignmentCheckEndDiagnostic( + String message, + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueExpTree) { + if (showchecks) { + System.out.printf( + " %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", + message, + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + valueType.getKind(), + valueType.toString(), + varType.getKind(), + varType.toString()); + } + } + + /** + * Returns "filename:linenumber:columnnumber" for the given tree. For brevity, the filename is + * given as a simple name, without any directory components. If the line and column numbers are + * unknown, they are omitted. + * + * @param tree a tree + * @return the location of the given tree in source code + */ + private String fileAndLineNumber(Tree tree) { + StringBuilder result = new StringBuilder(); + result.append(Paths.get(root.getSourceFile().getName()).getFileName().toString()); + long valuePos = positions.getStartPosition(root, tree); + LineMap lineMap = root.getLineMap(); + if (valuePos != -1 && lineMap != null) { + result.append(":"); + result.append(lineMap.getLineNumber(valuePos)); + result.append(":"); + result.append(lineMap.getColumnNumber(valuePos)); + } + return result.toString(); + } - // Get the type of the overriding method. - AnnotatedExecutableType overriderMethodType = atypeFactory.getAnnotatedType(overriderTree); + /** + * Class that creates string representations of {@link AnnotatedTypeMirror}s which are only + * verbose if required to differentiate the two types. + */ + protected static class FoundRequired { - // Call the other version of the method, which takes overriderMethodType. Both versions - // exist to allow checkers to override one or the other depending on their needs. - return checkOverride( - overriderTree, - overriderMethodType, - overriderType, - overriddenMethodType, - overriddenType); - } + /** The found type's string representation. */ + public final String found; - /** - * Type checks that a method may override another method. Uses an OverrideChecker subclass as - * created by {@link #createOverrideChecker}. This version of the method exposes the - * AnnotatedExecutableType of the overriding method. Override this version of the method if you - * need to access that type. - * - * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedDeclaredType, - * AnnotatedTypeMirror.AnnotatedExecutableType, AnnotatedTypeMirror.AnnotatedDeclaredType) - * @param overriderTree declaration tree of overriding method - * @param overriderMethodType type of the overriding method - * @param overriderType type of overriding class - * @param overriddenMethodType type of overridden method - * @param overriddenType type of overridden class - * @return true if the override is allowed - */ - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedExecutableType overriderMethodType, - AnnotatedDeclaredType overriderType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType) { - - // This needs to be done before overriderMethodType.getReturnType() and - // overriddenMethodType.getReturnType() - if (overriderMethodType.getTypeVariables().isEmpty() - && !overriddenMethodType.getTypeVariables().isEmpty()) { - overriddenMethodType = overriddenMethodType.getErased(); - } + /** The required type's string representation. */ + public final String required; - OverrideChecker overrideChecker = - createOverrideChecker( - overriderTree, - overriderMethodType, - overriderType, - overriderMethodType.getReturnType(), - overriddenMethodType, - overriddenType, - overriddenMethodType.getReturnType()); + private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { + if (shouldPrintVerbose(found, required)) { + this.found = found.toString(true); + this.required = required.toString(true); + } else { + this.found = found.toString(); + this.required = required.toString(); + } + } - return overrideChecker.checkOverride(); + /** Create a FoundRequired for a type and bounds. */ + private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { + if (shouldPrintVerbose(found, required)) { + this.found = found.toString(true); + this.required = required.toString(true); + } else { + this.found = found.toString(); + this.required = required.toString(); + } } /** - * Check that a method reference is allowed. Uses the OverrideChecker class. + * Creates string representations of {@link AnnotatedTypeMirror}s which are only verbose if + * required to differentiate the two types. * - * @param memberReferenceTree the tree for the method reference - * @return true if the method reference is allowed + * @param found the found annotation + * @param required the required annotation + * @return a string representation of the two annotations */ - protected boolean checkMethodReferenceAsOverride( - MemberReferenceTree memberReferenceTree, Void p) { - - IPair result = - atypeFactory.getFnInterfaceFromTree(memberReferenceTree); - // The type to which the member reference is assigned -- also known as the target type of - // the reference. - AnnotatedTypeMirror functionalInterface = result.first; - // The type of the single method that is declared by the functional interface. - AnnotatedExecutableType functionType = result.second; - - // ========= Overriding Type ========= - // This doesn't get the correct type for a "MyOuter.super" based on the receiver of the - // enclosing method. - // That is handled separately in method receiver check. - - // The type of the expression or type use, ::method or ::method. - ExpressionTree qualifierExpression = memberReferenceTree.getQualifierExpression(); - MemberReferenceKind memRefKind = - MemberReferenceKind.getMemberReferenceKind(memberReferenceTree); - AnnotatedTypeMirror enclosingType; - if (memberReferenceTree.getMode() == ReferenceMode.NEW - || memRefKind == MemberReferenceKind.UNBOUND - || memRefKind == MemberReferenceKind.STATIC) { - // The "qualifier expression" is a type tree. - enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(qualifierExpression); - } else { - // The "qualifier expression" is an expression. - enclosingType = atypeFactory.getAnnotatedType(qualifierExpression); - } + public static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { + return new FoundRequired(found, required); + } - // ========= Overriding Executable ========= - // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference - ExecutableElement compileTimeDeclaration = TreeUtils.elementFromUse(memberReferenceTree); - if (enclosingType.getKind() == TypeKind.DECLARED - && ((AnnotatedDeclaredType) enclosingType).isUnderlyingTypeRaw()) { - if (memRefKind == MemberReferenceKind.UNBOUND) { - // The method reference is of the form: Type # instMethod and Type is a raw type. - // If the first parameter of the function type, p1, is a subtype of type, then type - // should be p1. This has the effect of "inferring" the class type parameter. - AnnotatedTypeMirror p1 = functionType.getParameterTypes().get(0); - TypeMirror asSuper = - TypesUtils.asSuper( - p1.getUnderlyingType(), - enclosingType.getUnderlyingType(), - atypeFactory.getProcessingEnv()); - if (asSuper != null) { - enclosingType = AnnotatedTypes.asSuper(atypeFactory, p1, enclosingType); + /** + * Creates string representations of {@link AnnotatedTypeMirror} and {@link + * AnnotatedTypeParameterBounds}s which are only verbose if required to differentiate the two + * types. + * + * @param found the found annotation + * @param required the required annotation + * @return a string representation of the two annotations + */ + public static FoundRequired of( + AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { + return new FoundRequired(found, required); + } + } + + /** + * Return whether or not the verbose toString should be used when printing the two annotated + * types. + * + * @param atm1 the first AnnotatedTypeMirror + * @param atm2 the second AnnotatedTypeMirror + * @return true iff neither argument contains "@", or there are two annotated types (in either + * ATM) such that their toStrings are the same but their verbose toStrings differ + */ + private static boolean shouldPrintVerbose(AnnotatedTypeMirror atm1, AnnotatedTypeMirror atm2) { + if (!atm1.toString().contains("@") && !atm2.toString().contains("@")) { + return true; + } + return containsSameToString(atm1, atm2); + } + + /** + * Return whether or not the verbose toString should be used when printing the annotated type and + * the bounds it is not within. + * + * @param atm the type + * @param bounds the bounds + * @return true iff bounds does not contain "@", or there are two annotated types (in either + * argument) such that their toStrings are the same but their verbose toStrings differ + */ + private static boolean shouldPrintVerbose( + AnnotatedTypeMirror atm, AnnotatedTypeParameterBounds bounds) { + if (!atm.toString().contains("@") && !bounds.toString().contains("@")) { + return true; + } + return containsSameToString(atm, bounds.getUpperBound(), bounds.getLowerBound()); + } + + /** + * A scanner that indicates whether any (component) types have the same toString but different + * verbose toString. If so, the Checker Framework prints types verbosely. + */ + private static final SimpleAnnotatedTypeScanner> + checkContainsSameToString = + new SimpleAnnotatedTypeScanner<>( + (AnnotatedTypeMirror type, Map map) -> { + if (type == null) { + return false; } - } - // else method reference is something like ArrayList::new - // TODO: Use diamond, <>, inference to infer the class type arguments. - // For now this case is skipped below in checkMethodReferenceInference. - } + String simple = type.toString(); + String verbose = map.get(simple); + if (verbose == null) { + map.put(simple, type.toString(true)); + return false; + } else { + return !verbose.equals(type.toString(true)); + } + }, + Boolean::logicalOr, + false); + + /** + * Return true iff there are two annotated types (anywhere in any ATM) such that their toStrings + * are the same but their verbose toStrings differ. If so, the Checker Framework prints types + * verbosely. + * + * @param atms annotated type mirrors to compare + * @return true iff there are two annotated types (anywhere in any ATM) such that their toStrings + * are the same but their verbose toStrings differ + */ + private static boolean containsSameToString(AnnotatedTypeMirror... atms) { + Map simpleToVerbose = new HashMap<>(); + for (AnnotatedTypeMirror atm : atms) { + if (checkContainsSameToString.visit(atm, simpleToVerbose)) { + return true; + } + } - // The type of the compileTimeDeclaration if it were invoked with a receiver expression - // of type {@code type} - AnnotatedExecutableType invocationType = - atypeFactory.methodFromUse( - memberReferenceTree, compileTimeDeclaration, enclosingType) - .executableType; - - if (checkMethodReferenceInference(memberReferenceTree, invocationType, enclosingType)) { - // Type argument inference is required, skip check. - // #checkMethodReferenceInference issued a warning. - return true; - } + return false; + } + + /** + * Checks that the array initializers are consistent with the array type. + * + * @param type the array elemen type + * @param initializers the initializers + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean checkArrayInitialization( + AnnotatedTypeMirror type, List initializers) { + // TODO: set assignment context like for method arguments? + // Also in AbstractFlow. + boolean result = true; + for (ExpressionTree init : initializers) { + result = commonAssignmentCheck(type, init, "array.initializer.type.incompatible") && result; + } + return result; + } + + /** + * Checks that the annotations on the type arguments supplied to a type or a method invocation are + * within the bounds of the type variables as declared, and issues the + * "type.argument.type.incompatible" error if they are not. + * + * @param toptree the tree for error reporting, only used for inferred type arguments + * @param paramBounds the bounds of the type parameters from a class or method declaration + * @param typeargs the type arguments from the type or method invocation + * @param typeargTrees the type arguments as trees, used for error reporting + */ + protected void checkTypeArguments( + Tree toptree, + List paramBounds, + List typeargs, + List typeargTrees, + CharSequence typeOrMethodName, + List paramNames) { + + // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s%n", + // toptree, paramBounds, typeargs, typeargTrees); + + // If there are no type variables, do nothing. + if (paramBounds.isEmpty()) { + return; + } - // This needs to be done before invocationType.getReturnType() and - // functionType.getReturnType() - if (invocationType.getTypeVariables().isEmpty() - && !functionType.getTypeVariables().isEmpty()) { - functionType = functionType.getErased(); - } + int size = paramBounds.size(); + assert size == typeargs.size() + : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " + + typeargs + + " and type parameter bounds" + + paramBounds; - // Use the function type's parameters to resolve polymorphic qualifiers. - QualifierPolymorphism poly = atypeFactory.getQualifierPolymorphism(); - poly.resolve(functionType, invocationType); + for (int i = 0; i < size; i++) { - AnnotatedTypeMirror invocationReturnType; - if (compileTimeDeclaration.getKind() == ElementKind.CONSTRUCTOR) { - if (enclosingType.getKind() == TypeKind.ARRAY) { - // Special casing for the return of array constructor - invocationReturnType = enclosingType; - } else { - invocationReturnType = - atypeFactory.getResultingTypeOfConstructorMemberReference( - memberReferenceTree, invocationType); - } - } else { - invocationReturnType = invocationType.getReturnType(); - } + AnnotatedTypeParameterBounds bounds = paramBounds.get(i); + AnnotatedTypeMirror typeArg = typeargs.get(i); - AnnotatedTypeMirror functionTypeReturnType = functionType.getReturnType(); - if (functionTypeReturnType.getKind() == TypeKind.VOID) { - // If the functional interface return type is void, the overriding return type doesn't - // matter. - functionTypeReturnType = invocationReturnType; - } + if (isIgnoredUninferredWildcard(bounds.getUpperBound())) { + continue; + } - if (functionalInterface.getKind() == TypeKind.DECLARED) { - // Check the member reference as if invocationType overrides functionType. - OverrideChecker overrideChecker = - createOverrideChecker( - memberReferenceTree, - invocationType, - enclosingType, - invocationReturnType, - functionType, - (AnnotatedDeclaredType) functionalInterface, - functionTypeReturnType); - return overrideChecker.checkOverride(); - } else { - // If the functionalInterface is not a declared type, it must be an uninferred wildcard. - // In that case, only return false if uninferred type arguments should not be ignored. - return !atypeFactory.ignoreUninferredTypeArguments; - } - } + AnnotatedTypeMirror paramUpperBound = bounds.getUpperBound(); - /** Check if method reference type argument inference is required. Issue an error if it is. */ - private boolean checkMethodReferenceInference( - MemberReferenceTree memberReferenceTree, - AnnotatedExecutableType invocationType, - AnnotatedTypeMirror type) { - // TODO: Issue #802 - // TODO: https://github.com/typetools/checker-framework/issues/802 - // TODO: Method type argument inference - // TODO: Enable checks for method reference with inferred type arguments. - // For now, error on mismatch of class or method type arguments. - boolean requiresInference = false; - // If the function to which the member reference refers is generic, but the member - // reference does not provide method type arguments, then Java 8 inference is required. - // Issue 979. - if (!invocationType.getTypeVariables().isEmpty() - && (memberReferenceTree.getTypeArguments() == null - || memberReferenceTree.getTypeArguments().isEmpty())) { - // Method type args - requiresInference = true; - } else if (memberReferenceTree.getMode() == ReferenceMode.NEW - || MemberReferenceKind.getMemberReferenceKind(memberReferenceTree).isUnbound()) { - if (type.getKind() == TypeKind.DECLARED - && ((AnnotatedDeclaredType) type).isUnderlyingTypeRaw()) { - // Class type args - requiresInference = true; - } - } - if (requiresInference) { - if (conservativeUninferredTypeArguments) { - checker.reportWarning(memberReferenceTree, "methodref.inference.unimplemented"); - } - return true; - } - return false; - } + Tree reportErrorToTree; + if (typeargTrees == null || typeargTrees.isEmpty()) { + // The type arguments were inferred, report the error on the method invocation. + reportErrorToTree = toptree; + } else { + reportErrorToTree = typeargTrees.get(i); + } - /** - * Class to perform method override and method reference checks. - * - *

Method references are checked similarly to method overrides, with the method reference - * viewed as overriding the functional interface's method. - * - *

Checks that an overriding method's return type, parameter types, and receiver type are - * correct with respect to the annotations on the overridden method's return type, parameter - * types, and receiver type. - * - *

Furthermore, any contracts on the method must satisfy behavioral subtyping, that is, - * postconditions must be at least as strong as the postcondition on the superclass, and - * preconditions must be at most as strong as the condition on the superclass. - * - *

This method returns the result of the check, but also emits error messages as a side - * effect. - */ - public class OverrideChecker { - - /** - * The declaration of an overriding method. Or, it could be a method reference that is being - * passed to a method. - */ - protected final Tree overriderTree; - - /** True if {@link #overriderTree} is a MEMBER_REFERENCE. */ - protected final boolean isMethodReference; - - /** The type of the overriding method. */ - protected final AnnotatedExecutableType overrider; - - /** The subtype that declares the overriding method. */ - protected final AnnotatedTypeMirror overriderType; - - /** The type of the overridden method. */ - protected final AnnotatedExecutableType overridden; - - /** The supertype that declares the overridden method. */ - protected final AnnotatedDeclaredType overriddenType; - - /** The teturn type of the overridden method. */ - protected final AnnotatedTypeMirror overriddenReturnType; - - /** The return type of the overriding method. */ - protected final AnnotatedTypeMirror overriderReturnType; - - /** - * Create an OverrideChecker. - * - *

Notice that the return types are passed in separately. This is to support some types - * of method references where the overrider's return type is not the appropriate type to - * check. - * - * @param overriderTree the AST node of the overriding method or method reference - * @param overrider the type of the overriding method - * @param overriderType the type enclosing the overrider method, usually an - * AnnotatedDeclaredType; for Method References may be something else - * @param overriderReturnType the return type of the overriding method - * @param overridden the type of the overridden method - * @param overriddenType the declared type enclosing the overridden method - * @param overriddenReturnType the return type of the overridden method - */ - public OverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overrider, - AnnotatedTypeMirror overriderType, - AnnotatedTypeMirror overriderReturnType, - AnnotatedExecutableType overridden, - AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - - this.overriderTree = overriderTree; - this.overrider = overrider; - this.overriderType = overriderType; - this.overriderReturnType = overriderReturnType; - this.overridden = overridden; - this.overriddenType = overriddenType; - this.overriddenReturnType = overriddenReturnType; - - this.isMethodReference = overriderTree.getKind() == Tree.Kind.MEMBER_REFERENCE; - } + checkHasQualifierParameterAsTypeArgument(typeArg, paramUpperBound, toptree); + commonAssignmentCheck( + paramUpperBound, + typeArg, + reportErrorToTree, + "type.argument.type.incompatible", + paramNames.get(i), + typeOrMethodName); + + if (!typeHierarchy.isSubtype(bounds.getLowerBound(), typeArg)) { + FoundRequired fr = FoundRequired.of(typeArg, bounds); + checker.reportError( + reportErrorToTree, + "type.argument.type.incompatible", + paramNames.get(i), + typeOrMethodName, + fr.found, + fr.required); + } + } + } + + /** + * Reports an error if the type argument has a qualifier parameter and the type parameter upper + * bound does not have a qualifier parameter. + * + * @param typeArgument type argument + * @param typeParameterUpperBound upper bound of the type parameter + * @param reportError where to report the error + */ + private void checkHasQualifierParameterAsTypeArgument( + AnnotatedTypeMirror typeArgument, + AnnotatedTypeMirror typeParameterUpperBound, + Tree reportError) { + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (atypeFactory.hasQualifierParameterInHierarchy(typeArgument, top) + && !getTypeFactory().hasQualifierParameterInHierarchy(typeParameterUpperBound, top)) { + checker.reportError(reportError, "type.argument.invalid.hasqualparam", top); + } + } + } + + private boolean isIgnoredUninferredWildcard(AnnotatedTypeMirror type) { + return atypeFactory.ignoreUninferredTypeArguments + && type.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type).isUninferredTypeArgument(); + } + + /** + * Indicates whether to skip subtype checks on the receiver when checking method invocability. A + * visitor may, for example, allow a method to be invoked even if the receivers are siblings in a + * hierarchy, provided that some other condition (implemented by the visitor) is satisfied. + * + * @param tree the method invocation tree + * @param methodDefinitionReceiver the ATM of the receiver of the method definition + * @param methodCallReceiver the ATM of the receiver of the method call + * @return whether to skip subtype checks on the receiver + */ + protected boolean skipReceiverSubtypeCheck( + MethodInvocationTree tree, + AnnotatedTypeMirror methodDefinitionReceiver, + AnnotatedTypeMirror methodCallReceiver) { + return false; + } + + /** + * Tests whether the method can be invoked using the receiver of the 'tree' method invocation, and + * issues a "method.invocation.invalid" if the invocation is invalid. + * + *

This implementation tests whether the receiver in the method invocation is a subtype of the + * method receiver type. This behavior can be specialized by overriding skipReceiverSubtypeCheck. + * + * @param method the type of the invoked method + * @param tree the method invocation tree + */ + protected void checkMethodInvocability( + AnnotatedExecutableType method, MethodInvocationTree tree) { + if (method.getReceiverType() == null) { + // Static methods don't have a receiver to check. + return; + } + if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) { + // TODO: Explicit "this()" calls of constructors have an implicit passed + // from the enclosing constructor. We must not use the self type, but + // instead should find a way to determine the receiver of the enclosing constructor. + // rcv = + // ((AnnotatedExecutableType)atypeFactory.getAnnotatedType(atypeFactory.getEnclosingMethod(tree))).getReceiverType(); + return; + } - /** - * Perform the check. - * - * @return true if the override is allowed - */ - public boolean checkOverride() { - if (checker.shouldSkipUses(overriddenType.getUnderlyingType().asElement())) { - return true; - } + AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); + AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); + AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); - boolean result = checkReturn(); - result &= checkParameters(); - if (isMethodReference) { - result &= checkMemberReferenceReceivers(); - } else { - result &= checkReceiverOverride(); - } - checkPreAndPostConditions(); - checkPurity(); + treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); - return result; - } + if (!skipReceiverSubtypeCheck(tree, methodReceiver, rcv)) { + // The diagnostic can be a bit misleading because the check is of the receiver but + // `tree` is the entire method invocation (where the receiver might be implicit). + commonAssignmentCheckStartDiagnostic(methodReceiver, treeReceiver, tree); + boolean success = typeHierarchy.isSubtype(treeReceiver, methodReceiver); + commonAssignmentCheckEndDiagnostic(success, null, methodReceiver, treeReceiver, tree); + if (!success) { + reportMethodInvocabilityError(tree, treeReceiver, methodReceiver); + } + } + } + + /** + * Report a method invocability error. Allows checkers to change how the message is output. + * + * @param tree the AST node at which to report the error + * @param found the actual type of the receiver + * @param expected the expected type of the receiver + */ + protected void reportMethodInvocabilityError( + MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { + checker.reportError( + tree, + "method.invocation.invalid", + TreeUtils.elementFromUse(tree), + found.toString(), + expected.toString()); + } + + /** + * Check that the (explicit) annotations on a new class tree are comparable to the result type of + * the constructor. Issue an error if not. + * + *

Issue a warning if the annotations on the constructor invocation is a subtype of the + * constructor result type. This is equivalent to down-casting. + * + *

For type checking of the enclosing expression of inner type instantiations, see {@link + * #checkEnclosingExpr(NewClassTree, AnnotatedTypeMirror.AnnotatedExecutableType)} + * + * @param invocation the AnnotatedDeclaredType of the constructor invocation + * @param constructor the AnnotatedExecutableType of the constructor declaration + * @param newClassTree the NewClassTree + */ + protected void checkConstructorInvocation( + AnnotatedDeclaredType invocation, + AnnotatedExecutableType constructor, + NewClassTree newClassTree) { + // Only check the primary annotations, the type arguments are checked elsewhere. + AnnotationMirrorSet explicitAnnos = atypeFactory.getExplicitNewClassAnnos(newClassTree); + if (explicitAnnos.isEmpty()) { + return; + } + for (AnnotationMirror explicit : explicitAnnos) { + // The return type of the constructor (resultAnnos) must be comparable to the + // annotations on the constructor invocation (explicitAnnos). + boolean resultIsSubtypeOfExplicit = + typeHierarchy.isSubtypeShallowEffective(constructor.getReturnType(), explicit); + if (!(typeHierarchy.isSubtypeShallowEffective(explicit, constructor.getReturnType()) + || resultIsSubtypeOfExplicit)) { + AnnotationMirror resultAnno = + constructor.getReturnType().getAnnotationInHierarchy(explicit); + checker.reportError( + newClassTree, + "constructor.invocation.invalid", + constructor.toString(), + explicit, + resultAnno); + return; + } else if (!resultIsSubtypeOfExplicit) { + AnnotationMirror resultAnno = + constructor.getReturnType().getAnnotationInHierarchy(explicit); + // Issue a warning if the annotations on the constructor invocation is a subtype of + // the constructor result type. This is equivalent to down-casting. + checker.reportWarning( + newClassTree, "cast.unsafe.constructor.invocation", resultAnno, explicit); + return; + } + } + } + + /** + * Helper method to type check the enclosing expression of an inner class instantiation. + * + * @param node the NewClassTree + * @param constructorType the annotatedExecutableType of the constructor declaration + */ + protected void checkEnclosingExpr(NewClassTree node, AnnotatedExecutableType constructorType) { + if (!checkEnclosingExpr) { + return; + } + AnnotatedTypeMirror formalReceiverType = constructorType.getReceiverType(); + if (formalReceiverType != null) { + AnnotatedTypeMirror passedReceiverType = atypeFactory.getReceiverType(node); + commonAssignmentCheck( + formalReceiverType, passedReceiverType, node, "enclosingexpr.type.incompatible"); + } + } + + /** + * A helper method to check that each passed argument is a subtype of the corresponding required + * argument. Issues an "argument.invalid" error for each passed argument that not a subtype of the + * required one. + * + *

Note this method requires the lists to have the same length, as it does not handle cases + * like var args. + * + * @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree) + * @param requiredArgs the required types. This may differ from the formal parameter types, + * because it replaces a varargs parameter by multiple parameters with the vararg's element + * type. + * @param passedArgs the expressions passed to the corresponding types + * @param executableName the name of the method or constructor being called + * @param paramNames the names of the callee's formal parameters + */ + protected void checkArguments( + List requiredArgs, + List passedArgs, + CharSequence executableName, + List paramNames) { + int size = requiredArgs.size(); + assert size == passedArgs.size() + : "mismatch between required args (" + + requiredArgs + + ") and passed args (" + + passedArgs + + ")"; + int maxParamNamesIndex = paramNames.size() - 1; + // Rather weak assertion, due to how varargs parameters are treated. + assert size >= maxParamNamesIndex + : String.format( + "mismatched lengths %d %d %d checkArguments(%s, %s, %s, %s)", + size, + passedArgs.size(), + paramNames.size(), + listToString(requiredArgs), + listToString(passedArgs), + executableName, + listToString(paramNames)); + + for (int i = 0; i < size; ++i) { + commonAssignmentCheck( + requiredArgs.get(i), + passedArgs.get(i), + "argument.type.incompatible", + // TODO: for expanded varargs parameters, maybe adjust the name + paramNames.get(Math.min(i, maxParamNamesIndex)), + executableName); + // Also descend into the argument within the correct assignment context. + scan(passedArgs.get(i), null); + } + } + + // com.sun.tools.javac.util.List has a toString that does not include surrounding "[...]", + // making it hard to interpret in messages. + /** + * Produce a printed representation of a list, in the standard format with surrounding "[...]". + * + * @param lst a list to format + * @return the printed representation of the list + */ + private String listToString(List lst) { + StringJoiner result = new StringJoiner(",", "[", "]"); + for (Object elt : lst) { + result.add(elt.toString()); + } + return result.toString(); + } + + /** + * Returns true if both types are type variables and outer contains inner. Outer contains inner + * implies: {@literal inner.upperBound <: outer.upperBound outer.lowerBound <: inner.lowerBound}. + * + * @return true if both types are type variables and outer contains inner + */ + protected boolean testTypevarContainment(AnnotatedTypeMirror inner, AnnotatedTypeMirror outer) { + if (inner.getKind() == TypeKind.TYPEVAR && outer.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable innerAtv = (AnnotatedTypeVariable) inner; + AnnotatedTypeVariable outerAtv = (AnnotatedTypeVariable) outer; + + if (AnnotatedTypes.areCorrespondingTypeVariables(elements, innerAtv, outerAtv)) { + return typeHierarchy.isSubtype(innerAtv.getUpperBound(), outerAtv.getUpperBound()) + && typeHierarchy.isSubtype(outerAtv.getLowerBound(), innerAtv.getLowerBound()); + } + } - /** Check that an override respects purity. */ - private void checkPurity() { - String msgKey = - isMethodReference ? "purity.invalid.methodref" : "purity.invalid.overriding"; - - // check purity annotations - EnumSet superPurity = - PurityUtils.getPurityKinds(atypeFactory, overridden.getElement()); - EnumSet subPurity = - PurityUtils.getPurityKinds(atypeFactory, overrider.getElement()); - if (!subPurity.containsAll(superPurity)) { - checker.reportError( - overriderTree, - msgKey, - overriderType, - overrider, - overriddenType, - overridden, - subPurity, - superPurity); - } - } + return false; + } + + /** + * Create an OverrideChecker. + * + *

This exists so that subclasses can subclass OverrideChecker and use their subclass instead + * of using OverrideChecker itself. + * + * @param overriderTree the AST node of the overriding method or method reference + * @param overriderMethodType the type of the overriding method + * @param overriderType the type enclosing the overrider method, usually an AnnotatedDeclaredType; + * for Method References may be something else + * @param overriderReturnType the return type of the overriding method + * @param overriddenMethodType the type of the overridden method + * @param overriddenType the declared type enclosing the overridden method + * @param overriddenReturnType the return type of the overridden method + * @return an OverrideChecker + */ + protected OverrideChecker createOverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overriderMethodType, + AnnotatedTypeMirror overriderType, + AnnotatedTypeMirror overriderReturnType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + return new OverrideChecker( + overriderTree, + overriderMethodType, + overriderType, + overriderReturnType, + overriddenMethodType, + overriddenType, + overriddenReturnType); + } + + /** + * Type checks that a method may override another method. Uses an OverrideChecker subclass as + * created by {@link #createOverrideChecker}. This version of the method uses the annotated type + * factory to get the annotated type of the overriding method, and does NOT expose that type. + * + * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType) + * @param overriderTree declaration tree of overriding method + * @param overriderType type of overriding class + * @param overriddenMethodType type of overridden method + * @param overriddenType type of overridden class + * @return true if the override is allowed + */ + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedDeclaredType overriderType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType) { + + // Get the type of the overriding method. + AnnotatedExecutableType overriderMethodType = atypeFactory.getAnnotatedType(overriderTree); + + // Call the other version of the method, which takes overriderMethodType. Both versions + // exist to allow checkers to override one or the other depending on their needs. + return checkOverride( + overriderTree, overriderMethodType, overriderType, overriddenMethodType, overriddenType); + } + + /** + * Type checks that a method may override another method. Uses an OverrideChecker subclass as + * created by {@link #createOverrideChecker}. This version of the method exposes the + * AnnotatedExecutableType of the overriding method. Override this version of the method if you + * need to access that type. + * + * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedDeclaredType, + * AnnotatedTypeMirror.AnnotatedExecutableType, AnnotatedTypeMirror.AnnotatedDeclaredType) + * @param overriderTree declaration tree of overriding method + * @param overriderMethodType type of the overriding method + * @param overriderType type of overriding class + * @param overriddenMethodType type of overridden method + * @param overriddenType type of overridden class + * @return true if the override is allowed + */ + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedExecutableType overriderMethodType, + AnnotatedDeclaredType overriderType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType) { + + // This needs to be done before overriderMethodType.getReturnType() and + // overriddenMethodType.getReturnType() + if (overriderMethodType.getTypeVariables().isEmpty() + && !overriddenMethodType.getTypeVariables().isEmpty()) { + overriddenMethodType = overriddenMethodType.getErased(); + } - /** - * Checks that overrides obey behavioral subtyping, that is, postconditions must be at least - * as strong as the postcondition on the superclass, and preconditions must be at most as - * strong as the condition on the superclass. - */ - private void checkPreAndPostConditions() { - String msgKey = isMethodReference ? "methodref" : "override"; - if (isMethodReference) { - // TODO: Support postconditions and method references. - // The parse context always expects instance methods, but method references can be - // static. - return; - } + OverrideChecker overrideChecker = + createOverrideChecker( + overriderTree, + overriderMethodType, + overriderType, + overriderMethodType.getReturnType(), + overriddenMethodType, + overriddenType, + overriddenMethodType.getReturnType()); + + return overrideChecker.checkOverride(); + } + + /** + * Check that a method reference is allowed. Uses the OverrideChecker class. + * + * @param memberReferenceTree the tree for the method reference + * @return true if the method reference is allowed + */ + protected boolean checkMethodReferenceAsOverride( + MemberReferenceTree memberReferenceTree, Void p) { + + IPair result = + atypeFactory.getFnInterfaceFromTree(memberReferenceTree); + // The type to which the member reference is assigned -- also known as the target type of + // the reference. + AnnotatedTypeMirror functionalInterface = result.first; + // The type of the single method that is declared by the functional interface. + AnnotatedExecutableType functionType = result.second; + + // ========= Overriding Type ========= + // This doesn't get the correct type for a "MyOuter.super" based on the receiver of the + // enclosing method. + // That is handled separately in method receiver check. + + // The type of the expression or type use, ::method or ::method. + ExpressionTree qualifierExpression = memberReferenceTree.getQualifierExpression(); + MemberReferenceKind memRefKind = + MemberReferenceKind.getMemberReferenceKind(memberReferenceTree); + AnnotatedTypeMirror enclosingType; + if (memberReferenceTree.getMode() == ReferenceMode.NEW + || memRefKind == MemberReferenceKind.UNBOUND + || memRefKind == MemberReferenceKind.STATIC) { + // The "qualifier expression" is a type tree. + enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(qualifierExpression); + } else { + // The "qualifier expression" is an expression. + enclosingType = atypeFactory.getAnnotatedType(qualifierExpression); + } - ContractsFromMethod contractsUtils = atypeFactory.getContractsFromMethod(); - - // Check preconditions - Set superPre = contractsUtils.getPreconditions(overridden.getElement()); - Set subPre = contractsUtils.getPreconditions(overrider.getElement()); - Set> superPre2 = - parseAndLocalizeContracts(superPre, overridden); - Set> subPre2 = - parseAndLocalizeContracts(subPre, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String premsg = "contracts.precondition." + msgKey + ".invalid"; - checkContractsSubset(overriderType, overriddenType, subPre2, superPre2, premsg); - - // Check postconditions - Set superPost = - contractsUtils.getPostconditions(overridden.getElement()); - Set subPost = contractsUtils.getPostconditions(overrider.getElement()); - Set> superPost2 = - parseAndLocalizeContracts(superPost, overridden); - Set> subPost2 = - parseAndLocalizeContracts(subPost, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String postmsg = "contracts.postcondition." + msgKey + ".invalid"; - checkContractsSubset(overriderType, overriddenType, superPost2, subPost2, postmsg); - - // Check conditional postconditions - Set superCPost = - contractsUtils.getConditionalPostconditions(overridden.getElement()); - Set subCPost = - contractsUtils.getConditionalPostconditions(overrider.getElement()); - // consider only 'true' postconditions - Set superCPostTrue = filterConditionalPostconditions(superCPost, true); - Set subCPostTrue = filterConditionalPostconditions(subCPost, true); - Set> superCPostTrue2 = - parseAndLocalizeContracts(superCPostTrue, overridden); - Set> subCPostTrue2 = - parseAndLocalizeContracts(subCPostTrue, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String posttruemsg = "contracts.conditional.postcondition.true." + msgKey + ".invalid"; - checkContractsSubset( - overriderType, overriddenType, superCPostTrue2, subCPostTrue2, posttruemsg); - - // consider only 'false' postconditions - Set superCPostFalse = filterConditionalPostconditions(superCPost, false); - Set subCPostFalse = filterConditionalPostconditions(subCPost, false); - Set> superCPostFalse2 = - parseAndLocalizeContracts(superCPostFalse, overridden); - Set> subCPostFalse2 = - parseAndLocalizeContracts(subCPostFalse, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String postfalsemsg = - "contracts.conditional.postcondition.false." + msgKey + ".invalid"; - checkContractsSubset( - overriderType, overriddenType, superCPostFalse2, subCPostFalse2, postfalsemsg); + // ========= Overriding Executable ========= + // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference + ExecutableElement compileTimeDeclaration = TreeUtils.elementFromUse(memberReferenceTree); + if (enclosingType.getKind() == TypeKind.DECLARED + && ((AnnotatedDeclaredType) enclosingType).isUnderlyingTypeRaw()) { + if (memRefKind == MemberReferenceKind.UNBOUND) { + // The method reference is of the form: Type # instMethod and Type is a raw type. + // If the first parameter of the function type, p1, is a subtype of type, then type + // should be p1. This has the effect of "inferring" the class type parameter. + AnnotatedTypeMirror p1 = functionType.getParameterTypes().get(0); + TypeMirror asSuper = + TypesUtils.asSuper( + p1.getUnderlyingType(), + enclosingType.getUnderlyingType(), + atypeFactory.getProcessingEnv()); + if (asSuper != null) { + enclosingType = AnnotatedTypes.asSuper(atypeFactory, p1, enclosingType); } + } + // else method reference is something like ArrayList::new + // TODO: Use diamond, <>, inference to infer the class type arguments. + // For now this case is skipped below in checkMethodReferenceInference. + } - /** - * Issue a "methodref.receiver.invalid" or "methodref.receiver.bound.invalid" error if the - * receiver for the method reference does not satify overriding rules. - * - * @return true if the override is legal - */ - protected boolean checkMemberReferenceReceivers() { - if (overriderType.getKind() == TypeKind.ARRAY) { - // Assume the receiver for all method on arrays are @Top. - // This simplifies some logic because an AnnotatedExecutableType for an array method - // (ie String[]::clone) has a receiver of "Array." The UNBOUND check would then - // have to compare "Array" to "String[]". - return true; - } - MemberReferenceTree memberTree = (MemberReferenceTree) overriderTree; - MemberReferenceKind methodRefKind = - MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree); - // These act like a traditional override - if (methodRefKind == MemberReferenceKind.UNBOUND) { - AnnotatedTypeMirror overriderReceiver = overrider.getReceiverType(); - AnnotatedTypeMirror overriddenReceiver = overridden.getParameterTypes().get(0); - boolean success = typeHierarchy.isSubtype(overriddenReceiver, overriderReceiver); - if (!success) { - checker.reportError( - overriderTree, - "methodref.receiver.invalid", - overriderReceiver, - overriddenReceiver, - overriderType, - overrider, - overriddenType, - overridden); - } - return success; - } - - // The rest act like method invocations - AnnotatedTypeMirror receiverDecl; - AnnotatedTypeMirror receiverArg; - switch (methodRefKind) { - case UNBOUND: - throw new BugInCF("Case UNBOUND should already be handled."); - case SUPER: - receiverDecl = overrider.getReceiverType(); - receiverArg = - atypeFactory.getAnnotatedType(memberTree.getQualifierExpression()); - - AnnotatedTypeMirror selfType = atypeFactory.getSelfType(memberTree); - receiverArg.replaceAnnotations(selfType.getAnnotations()); - break; - case BOUND: - receiverDecl = overrider.getReceiverType(); - receiverArg = overriderType; - break; - case IMPLICIT_INNER: - // JLS 15.13.1 "It is a compile-time error if the method reference expression is - // of the form ClassType :: [TypeArguments] new and a compile-time error would - // occur when determining an enclosing instance for ClassType as specified in - // 15.9.2 (treating the method reference expression as if it were an unqualified - // class instance creation expression)." - - // So a member reference can only refer to an inner class constructor if a type - // that encloses the inner class can be found. So either "this" is that - // enclosing type or "this" has an enclosing type that is that type. - receiverDecl = overrider.getReceiverType(); - receiverArg = atypeFactory.getSelfType(memberTree); - while (!TypesUtils.isErasedSubtype( - receiverArg.getUnderlyingType(), - receiverDecl.getUnderlyingType(), - types)) { - receiverArg = ((AnnotatedDeclaredType) receiverArg).getEnclosingType(); - } - - break; - case TOPLEVEL: - case STATIC: - case ARRAY_CTOR: - default: - // Intentional fallthrough - // These don't have receivers - return true; - } + // The type of the compileTimeDeclaration if it were invoked with a receiver expression + // of type {@code type} + AnnotatedExecutableType invocationType = + atypeFactory.methodFromUse(memberReferenceTree, compileTimeDeclaration, enclosingType) + .executableType; - boolean success = typeHierarchy.isSubtype(receiverArg, receiverDecl); - if (!success) { - checker.reportError( - overriderTree, - "methodref.receiver.bound.invalid", - receiverArg, - receiverDecl, - receiverArg, - overriderType, - overrider); - } + if (checkMethodReferenceInference(memberReferenceTree, invocationType, enclosingType)) { + // Type argument inference is required, skip check. + // #checkMethodReferenceInference issued a warning. + return true; + } - return success; - } + // This needs to be done before invocationType.getReturnType() and + // functionType.getReturnType() + if (invocationType.getTypeVariables().isEmpty() && !functionType.getTypeVariables().isEmpty()) { + functionType = functionType.getErased(); + } - /** - * Issue an "override.receiver.invalid" error if the receiver override is not valid. - * - * @return true if the override is legal - */ - protected boolean checkReceiverOverride() { - AnnotatedDeclaredType overriderReceiver = overrider.getReceiverType(); - AnnotatedDeclaredType overriddenReceiver = overridden.getReceiverType(); - // Check the receiver type. - // isSubtype() requires its arguments to be actual subtypes with respect to the JLS, but - // an overrider receiver is not a subtype of the overridden receiver. So, just check - // primary annotations. - // TODO: this will need to be improved for generic receivers. - if (!typeHierarchy.isSubtypeShallowEffective(overriddenReceiver, overriderReceiver)) { - AnnotationMirrorSet declaredAnnos = - atypeFactory.getTypeDeclarationBounds(overriderType.getUnderlyingType()); - if (typeHierarchy.isSubtypeShallowEffective(overriderReceiver, declaredAnnos) - && typeHierarchy.isSubtypeShallowEffective( - declaredAnnos, overriderReceiver)) { - // All the type of an object must be no higher than its upper bound. So if the - // receiver is annotated with the upper bound qualifiers, then the override is - // safe. - return true; - } - FoundRequired pair = FoundRequired.of(overriderReceiver, overriddenReceiver); - checker.reportError( - overriderTree, - "override.receiver.invalid", - pair.found, - pair.required, - overriderType, - overrider, - overriddenType, - overridden); - return false; - } - return true; - } + // Use the function type's parameters to resolve polymorphic qualifiers. + QualifierPolymorphism poly = atypeFactory.getQualifierPolymorphism(); + poly.resolve(functionType, invocationType); - private boolean checkParameters() { - List overriderParams = overrider.getParameterTypes(); - List overriddenParams = overridden.getParameterTypes(); - - // Fix up method reference parameters. - // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.13.1 - if (isMethodReference) { - // The functional interface of an unbound member reference has an extra parameter - // (the receiver). - if (MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree) - == MemberReferenceKind.UNBOUND) { - overriddenParams = new ArrayList<>(overriddenParams); - overriddenParams.remove(0); - } - // Deal with varargs - if (overrider.isVarArgs() && !overridden.isVarArgs()) { - overriderParams = - AnnotatedTypes.expandVarArgsParametersFromTypes( - overrider, overriddenParams); - } - } + AnnotatedTypeMirror invocationReturnType; + if (compileTimeDeclaration.getKind() == ElementKind.CONSTRUCTOR) { + if (enclosingType.getKind() == TypeKind.ARRAY) { + // Special casing for the return of array constructor + invocationReturnType = enclosingType; + } else { + invocationReturnType = + atypeFactory.getResultingTypeOfConstructorMemberReference( + memberReferenceTree, invocationType); + } + } else { + invocationReturnType = invocationType.getReturnType(); + } - boolean result = true; - for (int i = 0; i < overriderParams.size(); ++i) { - AnnotatedTypeMirror capturedParam = - atypeFactory.applyCaptureConversion(overriddenParams.get(i)); - boolean success = typeHierarchy.isSubtype(capturedParam, overriderParams.get(i)); - if (!success) { - success = - testTypevarContainment(overriddenParams.get(i), overriderParams.get(i)); - } + AnnotatedTypeMirror functionTypeReturnType = functionType.getReturnType(); + if (functionTypeReturnType.getKind() == TypeKind.VOID) { + // If the functional interface return type is void, the overriding return type doesn't + // matter. + functionTypeReturnType = invocationReturnType; + } - checkParametersMsg(success, i, overriderParams, overriddenParams); - result &= success; - } - return result; - } + if (functionalInterface.getKind() == TypeKind.DECLARED) { + // Check the member reference as if invocationType overrides functionType. + OverrideChecker overrideChecker = + createOverrideChecker( + memberReferenceTree, + invocationType, + enclosingType, + invocationReturnType, + functionType, + (AnnotatedDeclaredType) functionalInterface, + functionTypeReturnType); + return overrideChecker.checkOverride(); + } else { + // If the functionalInterface is not a declared type, it must be an uninferred wildcard. + // In that case, only return false if uninferred type arguments should not be ignored. + return !atypeFactory.ignoreUninferredTypeArguments; + } + } + + /** Check if method reference type argument inference is required. Issue an error if it is. */ + private boolean checkMethodReferenceInference( + MemberReferenceTree memberReferenceTree, + AnnotatedExecutableType invocationType, + AnnotatedTypeMirror type) { + // TODO: Issue #802 + // TODO: https://github.com/typetools/checker-framework/issues/802 + // TODO: Method type argument inference + // TODO: Enable checks for method reference with inferred type arguments. + // For now, error on mismatch of class or method type arguments. + boolean requiresInference = false; + // If the function to which the member reference refers is generic, but the member + // reference does not provide method type arguments, then Java 8 inference is required. + // Issue 979. + if (!invocationType.getTypeVariables().isEmpty() + && (memberReferenceTree.getTypeArguments() == null + || memberReferenceTree.getTypeArguments().isEmpty())) { + // Method type args + requiresInference = true; + } else if (memberReferenceTree.getMode() == ReferenceMode.NEW + || MemberReferenceKind.getMemberReferenceKind(memberReferenceTree).isUnbound()) { + if (type.getKind() == TypeKind.DECLARED + && ((AnnotatedDeclaredType) type).isUnderlyingTypeRaw()) { + // Class type args + requiresInference = true; + } + } + if (requiresInference) { + if (conservativeUninferredTypeArguments) { + checker.reportWarning(memberReferenceTree, "methodref.inference.unimplemented"); + } + return true; + } + return false; + } + + /** + * Class to perform method override and method reference checks. + * + *

Method references are checked similarly to method overrides, with the method reference + * viewed as overriding the functional interface's method. + * + *

Checks that an overriding method's return type, parameter types, and receiver type are + * correct with respect to the annotations on the overridden method's return type, parameter + * types, and receiver type. + * + *

Furthermore, any contracts on the method must satisfy behavioral subtyping, that is, + * postconditions must be at least as strong as the postcondition on the superclass, and + * preconditions must be at most as strong as the condition on the superclass. + * + *

This method returns the result of the check, but also emits error messages as a side effect. + */ + public class OverrideChecker { - private void checkParametersMsg( - boolean success, - int index, - List overriderParams, - List overriddenParams) { - if (success && !showchecks) { - return; - } + /** + * The declaration of an overriding method. Or, it could be a method reference that is being + * passed to a method. + */ + protected final Tree overriderTree; - String msgKey = - isMethodReference ? "methodref.param.invalid" : "override.param.invalid"; - Tree posTree = - overriderTree instanceof MethodTree - ? ((MethodTree) overriderTree).getParameters().get(index) - : overriderTree; - - if (showchecks) { - System.out.printf( - " %s (at %s):%n" - + " overrider: %s %s (parameter %d type %s)%n" - + " overridden: %s %s" - + " (parameter %d type %s)%n", - (success - ? "success: overridden parameter type is subtype of overriding" - : "FAILURE: overridden parameter type is not subtype of overriding"), - fileAndLineNumber(posTree), - overrider, - overriderType, - index, - overriderParams.get(index).toString(), - overridden, - overriddenType, - index, - overriddenParams.get(index).toString()); - } - if (!success) { - FoundRequired pair = - FoundRequired.of(overriderParams.get(index), overriddenParams.get(index)); - checker.reportError( - posTree, - msgKey, - overrider.getElement().getParameters().get(index).toString(), - pair.found, - pair.required, - overriderType, - overrider, - overriddenType, - overridden); - } - } + /** True if {@link #overriderTree} is a MEMBER_REFERENCE. */ + protected final boolean isMethodReference; - /** - * Returns true if the return type of the overridden method is a subtype of the return type - * of the overriding method. - * - * @return true if the return type is correct - */ - private boolean checkReturn() { - if ((overriderReturnType.getKind() == TypeKind.VOID)) { - // Nothing to check. - return true; - } - boolean success = typeHierarchy.isSubtype(overriderReturnType, overriddenReturnType); - if (!success) { - // If both the overridden method have type variables as return types and both - // types were defined in their respective methods then, they can be covariant or - // invariant use super/subtypes for the overrides locations - success = testTypevarContainment(overriderReturnType, overriddenReturnType); - } + /** The type of the overriding method. */ + protected final AnnotatedExecutableType overrider; - // Sometimes the overridden return type of a method reference becomes a captured - // type variable. This leads to defaulting that often makes the overriding return type - // invalid. We ignore these. This happens in Issue403/Issue404. - if (!success - && isMethodReference - && TypesUtils.isCapturedTypeVariable( - overriddenReturnType.getUnderlyingType())) { - if (ElementUtils.isMethod( - overridden.getElement(), functionApply, atypeFactory.getProcessingEnv())) { - success = - typeHierarchy.isSubtype( - overriderReturnType, - ((AnnotatedTypeVariable) overriddenReturnType).getUpperBound()); - } - } + /** The subtype that declares the overriding method. */ + protected final AnnotatedTypeMirror overriderType; - checkReturnMsg(success); - return success; - } + /** The type of the overridden method. */ + protected final AnnotatedExecutableType overridden; - /** - * Issue an error message or log message about checking an overriding return type. - * - * @param success whether the check succeeded or failed - */ - private void checkReturnMsg(boolean success) { - if (success && !showchecks) { - return; - } + /** The supertype that declares the overridden method. */ + protected final AnnotatedDeclaredType overriddenType; - String msgKey = - isMethodReference ? "methodref.return.invalid" : "override.return.invalid"; - Tree posTree = - overriderTree instanceof MethodTree - ? ((MethodTree) overriderTree).getReturnType() - : overriderTree; - // The return type of a MethodTree is null for a constructor. - if (posTree == null) { - posTree = overriderTree; - } + /** The teturn type of the overridden method. */ + protected final AnnotatedTypeMirror overriddenReturnType; - if (showchecks) { - System.out.printf( - " %s (at %s):%n" - + " overrider: %s %s (return type %s)%n" - + " overridden: %s %s (return type %s)%n", - (success - ? "success: overriding return type is subtype of overridden" - : "FAILURE: overriding return type is not subtype of overridden"), - fileAndLineNumber(posTree), - overrider, - overriderType, - overrider.getReturnType().toString(), - overridden, - overriddenType, - overridden.getReturnType().toString()); - } - if (!success) { - FoundRequired pair = FoundRequired.of(overriderReturnType, overriddenReturnType); - checker.reportError( - posTree, - msgKey, - pair.found, - pair.required, - overriderType, - overrider, - overriddenType, - overridden); - } - } - } + /** The return type of the overriding method. */ + protected final AnnotatedTypeMirror overriderReturnType; /** - * Filters the set of conditional postconditions to return only those whose annotation result - * value matches the value of the given boolean {@code b}. For example, if {@code b == true}, - * then the following {@code @EnsuresNonNullIf} conditional postcondition would match:
- * {@code @EnsuresNonNullIf(expression="#1", result=true)}
- * {@code boolean equals(@Nullable Object o)} + * Create an OverrideChecker. * - * @param conditionalPostconditions each is a ConditionalPostcondition - * @param b the value required for the {@code result} element - * @return all the given conditional postconditions whose {@code result} is {@code b} + *

Notice that the return types are passed in separately. This is to support some types of + * method references where the overrider's return type is not the appropriate type to check. + * + * @param overriderTree the AST node of the overriding method or method reference + * @param overrider the type of the overriding method + * @param overriderType the type enclosing the overrider method, usually an + * AnnotatedDeclaredType; for Method References may be something else + * @param overriderReturnType the return type of the overriding method + * @param overridden the type of the overridden method + * @param overriddenType the declared type enclosing the overridden method + * @param overriddenReturnType the return type of the overridden method */ - private Set filterConditionalPostconditions( - Set conditionalPostconditions, boolean b) { - if (conditionalPostconditions.isEmpty()) { - return Collections.emptySet(); - } - - Set result = - ArraySet.newArraySetOrLinkedHashSet(conditionalPostconditions.size()); - for (Contract c : conditionalPostconditions) { - ConditionalPostcondition p = (ConditionalPostcondition) c; - if (p.resultValue == b) { - result.add( - new Postcondition(p.expressionString, p.annotation, p.contractAnnotation)); - } - } - return result; + public OverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overrider, + AnnotatedTypeMirror overriderType, + AnnotatedTypeMirror overriderReturnType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + + this.overriderTree = overriderTree; + this.overrider = overrider; + this.overriderType = overriderType; + this.overriderReturnType = overriderReturnType; + this.overridden = overridden; + this.overriddenType = overriddenType; + this.overriddenReturnType = overriddenReturnType; + + this.isMethodReference = overriderTree.getKind() == Tree.Kind.MEMBER_REFERENCE; } /** - * Checks that {@code mustSubset} is a subset of {@code set} in the following sense: For every - * expression in {@code mustSubset} there must be the same expression in {@code set}, with the - * same (or a stronger) annotation. - * - *

This uses field {@link #methodTree} to determine where to issue an error message. + * Perform the check. * - * @param overriderType the subtype - * @param overriddenType the supertype - * @param mustSubset annotations that should be weaker - * @param set anontations that should be stronger - * @param messageKey message key for error messages + * @return true if the override is allowed */ - private void checkContractsSubset( - AnnotatedTypeMirror overriderType, - AnnotatedDeclaredType overriddenType, - Set> mustSubset, - Set> set, - @CompilerMessageKey String messageKey) { - for (IPair weak : mustSubset) { - JavaExpression jexpr = weak.first; - boolean found = false; - - for (IPair strong : set) { - // are we looking at a contract of the same receiver? - if (jexpr.equals(strong.first)) { - // check subtyping relationship of annotations - TypeMirror jexprTM = jexpr.getType(); - if (qualHierarchy.isSubtypeShallow( - strong.second, jexprTM, weak.second, jexprTM)) { - found = true; - break; - } - } - } - - if (!found) { - - String overriddenTypeString = - overriddenType.getUnderlyingType().asElement().toString(); - String overriderTypeString; - if (overriderType.getKind() == TypeKind.DECLARED) { - DeclaredType overriderTypeMirror = - ((AnnotatedDeclaredType) overriderType).getUnderlyingType(); - overriderTypeString = overriderTypeMirror.asElement().toString(); - } else { - overriderTypeString = overriderType.toString(); - } + public boolean checkOverride() { + if (checker.shouldSkipUses(overriddenType.getUnderlyingType().asElement())) { + return true; + } - // weak.second is the AnnotationMirror that is too strong. It might be from the - // precondition or the postcondition. + boolean result = checkReturn(); + result &= checkParameters(); + if (isMethodReference) { + result &= checkMemberReferenceReceivers(); + } else { + result &= checkReceiverOverride(); + } + checkPreAndPostConditions(); + checkPurity(); - // These are the annotations that are too weak. - StringJoiner strongRelevantAnnos = - new StringJoiner(" ").setEmptyValue("no information"); - for (IPair strong : set) { - if (jexpr.equals(strong.first)) { - strongRelevantAnnos.add(strong.second.toString()); - } - } + return result; + } - Object overriddenAnno; - Object overriderAnno; - if (messageKey.contains(".precondition.")) { - overriddenAnno = strongRelevantAnnos; - overriderAnno = weak.second; - } else { - overriddenAnno = weak.second; - overriderAnno = strongRelevantAnnos; - } + /** Check that an override respects purity. */ + private void checkPurity() { + String msgKey = isMethodReference ? "purity.invalid.methodref" : "purity.invalid.overriding"; - checker.reportError( - methodTree, - messageKey, - jexpr, - methodTree.getName(), - overriddenTypeString, - overriddenAnno, - overriderTypeString, - overriderAnno); - } - } + // check purity annotations + EnumSet superPurity = + PurityUtils.getPurityKinds(atypeFactory, overridden.getElement()); + EnumSet subPurity = + PurityUtils.getPurityKinds(atypeFactory, overrider.getElement()); + if (!subPurity.containsAll(superPurity)) { + checker.reportError( + overriderTree, + msgKey, + overriderType, + overrider, + overriddenType, + overridden, + subPurity, + superPurity); + } } /** - * Localizes some contracts -- that is, viewpoint-adapts them to some method body, according to - * the value of {@link #methodTree}. - * - *

The input is a set of {@link Contract}s, each of which contains an expression string and - * an annotation. In a {@link Contract}, Java expressions are exactly as written in source code, - * not standardized or viewpoint-adapted. - * - *

The output is a set of pairs of {@link JavaExpression} (parsed expression string) and - * standardized annotation (with respect to the path of {@link #methodTree}. This method - * discards any contract whose expression cannot be parsed into a JavaExpression. - * - * @param contractSet a set of contracts - * @param methodType the type of the method that the contracts are for - * @return pairs of (expression, AnnotationMirror), which are localized contracts + * Checks that overrides obey behavioral subtyping, that is, postconditions must be at least as + * strong as the postcondition on the superclass, and preconditions must be at most as strong as + * the condition on the superclass. */ - private Set> parseAndLocalizeContracts( - Set contractSet, AnnotatedExecutableType methodType) { - if (contractSet.isEmpty()) { - return Collections.emptySet(); - } + private void checkPreAndPostConditions() { + String msgKey = isMethodReference ? "methodref" : "override"; + if (isMethodReference) { + // TODO: Support postconditions and method references. + // The parse context always expects instance methods, but method references can be + // static. + return; + } - // This is the path to a place where the contract is being used, which might or might not be - // where the contract was defined. For example, methodTree might be an overriding - // definition, and the contract might be for a superclass. - MethodTree methodTree = this.methodTree; - - StringToJavaExpression stringToJavaExpr = - expression -> { - JavaExpression javaExpr = - StringToJavaExpression.atMethodDecl( - expression, methodType.getElement(), checker); - // methodType.getElement() is not necessarily the same method as methodTree, so - // viewpoint-adapt it to methodTree. - return javaExpr.atMethodBody(methodTree); - }; - - Set> result = - ArraySet.newArraySetOrHashSet(contractSet.size()); - for (Contract p : contractSet) { - String expressionString = p.expressionString; - AnnotationMirror annotation = - p.viewpointAdaptDependentTypeAnnotation( - atypeFactory, stringToJavaExpr, methodTree); - JavaExpression exprJe; - try { - // TODO: currently, these expressions are parsed many times. - // This could be optimized to store the result the first time. - // (same for other annotations) - exprJe = stringToJavaExpr.toJavaExpression(expressionString); - } catch (JavaExpressionParseException e) { - // report errors here - checker.report(methodTree, e.getDiagMessage()); - continue; - } - result.add(IPair.of(exprJe, annotation)); - } - return result; + ContractsFromMethod contractsUtils = atypeFactory.getContractsFromMethod(); + + // Check preconditions + Set superPre = contractsUtils.getPreconditions(overridden.getElement()); + Set subPre = contractsUtils.getPreconditions(overrider.getElement()); + Set> superPre2 = + parseAndLocalizeContracts(superPre, overridden); + Set> subPre2 = + parseAndLocalizeContracts(subPre, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String premsg = "contracts.precondition." + msgKey + ".invalid"; + checkContractsSubset(overriderType, overriddenType, subPre2, superPre2, premsg); + + // Check postconditions + Set superPost = contractsUtils.getPostconditions(overridden.getElement()); + Set subPost = contractsUtils.getPostconditions(overrider.getElement()); + Set> superPost2 = + parseAndLocalizeContracts(superPost, overridden); + Set> subPost2 = + parseAndLocalizeContracts(subPost, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String postmsg = "contracts.postcondition." + msgKey + ".invalid"; + checkContractsSubset(overriderType, overriddenType, superPost2, subPost2, postmsg); + + // Check conditional postconditions + Set superCPost = + contractsUtils.getConditionalPostconditions(overridden.getElement()); + Set subCPost = + contractsUtils.getConditionalPostconditions(overrider.getElement()); + // consider only 'true' postconditions + Set superCPostTrue = filterConditionalPostconditions(superCPost, true); + Set subCPostTrue = filterConditionalPostconditions(subCPost, true); + Set> superCPostTrue2 = + parseAndLocalizeContracts(superCPostTrue, overridden); + Set> subCPostTrue2 = + parseAndLocalizeContracts(subCPostTrue, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String posttruemsg = "contracts.conditional.postcondition.true." + msgKey + ".invalid"; + checkContractsSubset( + overriderType, overriddenType, superCPostTrue2, subCPostTrue2, posttruemsg); + + // consider only 'false' postconditions + Set superCPostFalse = filterConditionalPostconditions(superCPost, false); + Set subCPostFalse = filterConditionalPostconditions(subCPost, false); + Set> superCPostFalse2 = + parseAndLocalizeContracts(superCPostFalse, overridden); + Set> subCPostFalse2 = + parseAndLocalizeContracts(subCPostFalse, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String postfalsemsg = "contracts.conditional.postcondition.false." + msgKey + ".invalid"; + checkContractsSubset( + overriderType, overriddenType, superCPostFalse2, subCPostFalse2, postfalsemsg); } /** - * Call this only when the current path is an identifier. + * Issue a "methodref.receiver.invalid" or "methodref.receiver.bound.invalid" error if the + * receiver for the method reference does not satify overriding rules. * - * @return the enclosing member select, or null if the identifier is not the field in a member - * selection + * @return true if the override is legal */ - protected @Nullable MemberSelectTree enclosingMemberSelect() { - TreePath path = this.getCurrentPath(); - assert path.getLeaf().getKind() == Tree.Kind.IDENTIFIER - : "expected identifier, found: " + path.getLeaf(); - if (path.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { - return (MemberSelectTree) path.getParentPath().getLeaf(); - } else { - return null; - } + protected boolean checkMemberReferenceReceivers() { + if (overriderType.getKind() == TypeKind.ARRAY) { + // Assume the receiver for all method on arrays are @Top. + // This simplifies some logic because an AnnotatedExecutableType for an array method + // (ie String[]::clone) has a receiver of "Array." The UNBOUND check would then + // have to compare "Array" to "String[]". + return true; + } + MemberReferenceTree memberTree = (MemberReferenceTree) overriderTree; + MemberReferenceKind methodRefKind = + MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree); + // These act like a traditional override + if (methodRefKind == MemberReferenceKind.UNBOUND) { + AnnotatedTypeMirror overriderReceiver = overrider.getReceiverType(); + AnnotatedTypeMirror overriddenReceiver = overridden.getParameterTypes().get(0); + boolean success = typeHierarchy.isSubtype(overriddenReceiver, overriderReceiver); + if (!success) { + checker.reportError( + overriderTree, + "methodref.receiver.invalid", + overriderReceiver, + overriddenReceiver, + overriderType, + overrider, + overriddenType, + overridden); + } + return success; + } + + // The rest act like method invocations + AnnotatedTypeMirror receiverDecl; + AnnotatedTypeMirror receiverArg; + switch (methodRefKind) { + case UNBOUND: + throw new BugInCF("Case UNBOUND should already be handled."); + case SUPER: + receiverDecl = overrider.getReceiverType(); + receiverArg = atypeFactory.getAnnotatedType(memberTree.getQualifierExpression()); + + AnnotatedTypeMirror selfType = atypeFactory.getSelfType(memberTree); + receiverArg.replaceAnnotations(selfType.getAnnotations()); + break; + case BOUND: + receiverDecl = overrider.getReceiverType(); + receiverArg = overriderType; + break; + case IMPLICIT_INNER: + // JLS 15.13.1 "It is a compile-time error if the method reference expression is + // of the form ClassType :: [TypeArguments] new and a compile-time error would + // occur when determining an enclosing instance for ClassType as specified in + // 15.9.2 (treating the method reference expression as if it were an unqualified + // class instance creation expression)." + + // So a member reference can only refer to an inner class constructor if a type + // that encloses the inner class can be found. So either "this" is that + // enclosing type or "this" has an enclosing type that is that type. + receiverDecl = overrider.getReceiverType(); + receiverArg = atypeFactory.getSelfType(memberTree); + while (!TypesUtils.isErasedSubtype( + receiverArg.getUnderlyingType(), receiverDecl.getUnderlyingType(), types)) { + receiverArg = ((AnnotatedDeclaredType) receiverArg).getEnclosingType(); + } + + break; + case TOPLEVEL: + case STATIC: + case ARRAY_CTOR: + default: + // Intentional fallthrough + // These don't have receivers + return true; + } + + boolean success = typeHierarchy.isSubtype(receiverArg, receiverDecl); + if (!success) { + checker.reportError( + overriderTree, + "methodref.receiver.bound.invalid", + receiverArg, + receiverDecl, + receiverArg, + overriderType, + overrider); + } + + return success; } /** - * Returns the statement that encloses the given one. + * Issue an "override.receiver.invalid" error if the receiver override is not valid. * - * @param tree an AST node that is on the current path - * @return the statement that encloses the given one + * @return true if the override is legal */ - protected @Nullable Tree enclosingStatement(@FindDistinct Tree tree) { - TreePath path = this.getCurrentPath(); - while (path != null && path.getLeaf() != tree) { - path = path.getParentPath(); + protected boolean checkReceiverOverride() { + AnnotatedDeclaredType overriderReceiver = overrider.getReceiverType(); + AnnotatedDeclaredType overriddenReceiver = overridden.getReceiverType(); + // Check the receiver type. + // isSubtype() requires its arguments to be actual subtypes with respect to the JLS, but + // an overrider receiver is not a subtype of the overridden receiver. So, just check + // primary annotations. + // TODO: this will need to be improved for generic receivers. + if (!typeHierarchy.isSubtypeShallowEffective(overriddenReceiver, overriderReceiver)) { + AnnotationMirrorSet declaredAnnos = + atypeFactory.getTypeDeclarationBounds(overriderType.getUnderlyingType()); + if (typeHierarchy.isSubtypeShallowEffective(overriderReceiver, declaredAnnos) + && typeHierarchy.isSubtypeShallowEffective(declaredAnnos, overriderReceiver)) { + // All the type of an object must be no higher than its upper bound. So if the + // receiver is annotated with the upper bound qualifiers, then the override is + // safe. + return true; + } + FoundRequired pair = FoundRequired.of(overriderReceiver, overriddenReceiver); + checker.reportError( + overriderTree, + "override.receiver.invalid", + pair.found, + pair.required, + overriderType, + overrider, + overriddenType, + overridden); + return false; + } + return true; + } + + private boolean checkParameters() { + List overriderParams = overrider.getParameterTypes(); + List overriddenParams = overridden.getParameterTypes(); + + // Fix up method reference parameters. + // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.13.1 + if (isMethodReference) { + // The functional interface of an unbound member reference has an extra parameter + // (the receiver). + if (MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree) + == MemberReferenceKind.UNBOUND) { + overriddenParams = new ArrayList<>(overriddenParams); + overriddenParams.remove(0); + } + // Deal with varargs + if (overrider.isVarArgs() && !overridden.isVarArgs()) { + overriderParams = + AnnotatedTypes.expandVarArgsParametersFromTypes(overrider, overriddenParams); } + } - if (path != null) { - return path.getParentPath().getLeaf(); - } else { - return null; + boolean result = true; + for (int i = 0; i < overriderParams.size(); ++i) { + AnnotatedTypeMirror capturedParam = + atypeFactory.applyCaptureConversion(overriddenParams.get(i)); + boolean success = typeHierarchy.isSubtype(capturedParam, overriderParams.get(i)); + if (!success) { + success = testTypevarContainment(overriddenParams.get(i), overriderParams.get(i)); } + + checkParametersMsg(success, i, overriderParams, overriddenParams); + result &= success; + } + return result; } - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - checkAccess(tree, p); - return super.visitIdentifier(tree, p); + private void checkParametersMsg( + boolean success, + int index, + List overriderParams, + List overriddenParams) { + if (success && !showchecks) { + return; + } + + String msgKey = isMethodReference ? "methodref.param.invalid" : "override.param.invalid"; + Tree posTree = + overriderTree instanceof MethodTree + ? ((MethodTree) overriderTree).getParameters().get(index) + : overriderTree; + + if (showchecks) { + System.out.printf( + " %s (at %s):%n" + + " overrider: %s %s (parameter %d type %s)%n" + + " overridden: %s %s" + + " (parameter %d type %s)%n", + (success + ? "success: overridden parameter type is subtype of overriding" + : "FAILURE: overridden parameter type is not subtype of overriding"), + fileAndLineNumber(posTree), + overrider, + overriderType, + index, + overriderParams.get(index).toString(), + overridden, + overriddenType, + index, + overriddenParams.get(index).toString()); + } + if (!success) { + FoundRequired pair = + FoundRequired.of(overriderParams.get(index), overriddenParams.get(index)); + checker.reportError( + posTree, + msgKey, + overrider.getElement().getParameters().get(index).toString(), + pair.found, + pair.required, + overriderType, + overrider, + overriddenType, + overridden); + } } /** - * Issues an error if access is not allowed, based on an {@code @Unused} annotation. + * Returns true if the return type of the overridden method is a subtype of the return type of + * the overriding method. * - * @param identifierTree the identifier being accessed; the method does nothing if it is not a - * field - * @param p ignored + * @return true if the return type is correct */ - protected void checkAccess(IdentifierTree identifierTree, Void p) { - MemberSelectTree memberSel = enclosingMemberSelect(); - ExpressionTree tree; - Element elem; - - if (memberSel == null) { - tree = identifierTree; - elem = TreeUtils.elementFromUse(identifierTree); - } else { - tree = memberSel; - elem = TreeUtils.elementFromUse(memberSel); - } + private boolean checkReturn() { + if ((overriderReturnType.getKind() == TypeKind.VOID)) { + // Nothing to check. + return true; + } + boolean success = typeHierarchy.isSubtype(overriderReturnType, overriddenReturnType); + if (!success) { + // If both the overridden method have type variables as return types and both + // types were defined in their respective methods then, they can be covariant or + // invariant use super/subtypes for the overrides locations + success = testTypevarContainment(overriderReturnType, overriddenReturnType); + } - if (elem == null || !elem.getKind().isField()) { - return; + // Sometimes the overridden return type of a method reference becomes a captured + // type variable. This leads to defaulting that often makes the overriding return type + // invalid. We ignore these. This happens in Issue403/Issue404. + if (!success + && isMethodReference + && TypesUtils.isCapturedTypeVariable(overriddenReturnType.getUnderlyingType())) { + if (ElementUtils.isMethod( + overridden.getElement(), functionApply, atypeFactory.getProcessingEnv())) { + success = + typeHierarchy.isSubtype( + overriderReturnType, + ((AnnotatedTypeVariable) overriddenReturnType).getUpperBound()); } + } - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - - checkAccessAllowed(elem, receiver, tree); + checkReturnMsg(success); + return success; } /** - * Issues an error if access not allowed, based on an @Unused annotation. + * Issue an error message or log message about checking an overriding return type. * - * @param field the field to be accessed, whose declaration might be annotated by @Unused. It - * can also be (for example) {@code this}, in which case {@code receiverType} is null. - * @param receiverType the type of the expression whose field is accessed; null if the field is - * static - * @param accessTree the access expression + * @param success whether the check succeeded or failed */ - protected void checkAccessAllowed( - Element field, - @Nullable AnnotatedTypeMirror receiverType, - @FindDistinct ExpressionTree accessTree) { - AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class); - if (unused == null) { - return; - } + private void checkReturnMsg(boolean success) { + if (success && !showchecks) { + return; + } - String when = - AnnotationUtils.getElementValueClassName(unused, unusedWhenElement).toString(); + String msgKey = isMethodReference ? "methodref.return.invalid" : "override.return.invalid"; + Tree posTree = + overriderTree instanceof MethodTree + ? ((MethodTree) overriderTree).getReturnType() + : overriderTree; + // The return type of a MethodTree is null for a constructor. + if (posTree == null) { + posTree = overriderTree; + } - // TODO: Don't just look at the receiver type, but at the declaration annotations on the - // receiver. (That will enable handling type annotations that are not part of the type - // system being checked.) + if (showchecks) { + System.out.printf( + " %s (at %s):%n" + + " overrider: %s %s (return type %s)%n" + + " overridden: %s %s (return type %s)%n", + (success + ? "success: overriding return type is subtype of overridden" + : "FAILURE: overriding return type is not subtype of overridden"), + fileAndLineNumber(posTree), + overrider, + overriderType, + overrider.getReturnType().toString(), + overridden, + overriddenType, + overridden.getReturnType().toString()); + } + if (!success) { + FoundRequired pair = FoundRequired.of(overriderReturnType, overriddenReturnType); + checker.reportError( + posTree, + msgKey, + pair.found, + pair.required, + overriderType, + overrider, + overriddenType, + overridden); + } + } + } + + /** + * Filters the set of conditional postconditions to return only those whose annotation result + * value matches the value of the given boolean {@code b}. For example, if {@code b == true}, then + * the following {@code @EnsuresNonNullIf} conditional postcondition would match:
+ * {@code @EnsuresNonNullIf(expression="#1", result=true)}
+ * {@code boolean equals(@Nullable Object o)} + * + * @param conditionalPostconditions each is a ConditionalPostcondition + * @param b the value required for the {@code result} element + * @return all the given conditional postconditions whose {@code result} is {@code b} + */ + private Set filterConditionalPostconditions( + Set conditionalPostconditions, boolean b) { + if (conditionalPostconditions.isEmpty()) { + return Collections.emptySet(); + } - // TODO: This requires exactly the same type qualifier, but it should permit subqualifiers. - if (!AnnotationUtils.containsSameByName(receiverType.getAnnotations(), when)) { - return; + Set result = + ArraySet.newArraySetOrLinkedHashSet(conditionalPostconditions.size()); + for (Contract c : conditionalPostconditions) { + ConditionalPostcondition p = (ConditionalPostcondition) c; + if (p.resultValue == b) { + result.add(new Postcondition(p.expressionString, p.annotation, p.contractAnnotation)); + } + } + return result; + } + + /** + * Checks that {@code mustSubset} is a subset of {@code set} in the following sense: For every + * expression in {@code mustSubset} there must be the same expression in {@code set}, with the + * same (or a stronger) annotation. + * + *

This uses field {@link #methodTree} to determine where to issue an error message. + * + * @param overriderType the subtype + * @param overriddenType the supertype + * @param mustSubset annotations that should be weaker + * @param set anontations that should be stronger + * @param messageKey message key for error messages + */ + private void checkContractsSubset( + AnnotatedTypeMirror overriderType, + AnnotatedDeclaredType overriddenType, + Set> mustSubset, + Set> set, + @CompilerMessageKey String messageKey) { + for (IPair weak : mustSubset) { + JavaExpression jexpr = weak.first; + boolean found = false; + + for (IPair strong : set) { + // are we looking at a contract of the same receiver? + if (jexpr.equals(strong.first)) { + // check subtyping relationship of annotations + TypeMirror jexprTM = jexpr.getType(); + if (qualHierarchy.isSubtypeShallow(strong.second, jexprTM, weak.second, jexprTM)) { + found = true; + break; + } } + } - Tree tree = this.enclosingStatement(accessTree); + if (!found) { - if (tree != null - && tree.getKind() == Tree.Kind.ASSIGNMENT - && ((AssignmentTree) tree).getVariable() == accessTree - && ((AssignmentTree) tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL) { - // Assigning unused to null is OK. - return; + String overriddenTypeString = overriddenType.getUnderlyingType().asElement().toString(); + String overriderTypeString; + if (overriderType.getKind() == TypeKind.DECLARED) { + DeclaredType overriderTypeMirror = + ((AnnotatedDeclaredType) overriderType).getUnderlyingType(); + overriderTypeString = overriderTypeMirror.asElement().toString(); + } else { + overriderTypeString = overriderType.toString(); } - checker.reportError(accessTree, "unallowed.access", field, receiverType); - } + // weak.second is the AnnotationMirror that is too strong. It might be from the + // precondition or the postcondition. - /** - * Tests that the qualifiers present on {@code useType} are valid qualifiers, given the - * qualifiers on the declaration of the type, {@code declarationType}. - * - *

The check is shallow, as it does not descend into generic or array types (i.e. only - * performing the validity check on the raw type or outermost array dimension). {@link - * BaseTypeVisitor#validateTypeOf(Tree)} would call this for each type argument or array - * dimension separately. - * - *

In most cases, {@code useType} simply needs to be a subtype of {@code declarationType}. If - * a type system makes exceptions to this rule, its implementation should override this method. - * - *

This method is not called if {@link - * BaseTypeValidator#shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror, Tree)} - * returns false -- by default, it is not called on the top level for locals and expressions. To - * enforce a type validity property everywhere, override methods such as {@link - * BaseTypeValidator#visitDeclared} rather than this method. - * - * @param declarationType the type of the class (TypeElement) - * @param useType the use of the class (instance type) - * @param tree the tree where the type is used - * @return true if the useType is a valid use of elemType - */ - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // Don't use isSubtype(ATM, ATM) because it will return false if the types have qualifier - // parameters. - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - TypeMirror declarationTM = declarationType.getUnderlyingType(); - AnnotationMirrorSet upperBounds = atypeFactory.getTypeDeclarationBounds(declarationTM); - for (AnnotationMirror top : tops) { - AnnotationMirror upperBound = qualHierarchy.findAnnotationInHierarchy(upperBounds, top); - if (!typeHierarchy.isSubtypeShallowEffective(useType, upperBound)) { - return false; - } + // These are the annotations that are too weak. + StringJoiner strongRelevantAnnos = new StringJoiner(" ").setEmptyValue("no information"); + for (IPair strong : set) { + if (jexpr.equals(strong.first)) { + strongRelevantAnnos.add(strong.second.toString()); + } } - return true; + + Object overriddenAnno; + Object overriderAnno; + if (messageKey.contains(".precondition.")) { + overriddenAnno = strongRelevantAnnos; + overriderAnno = weak.second; + } else { + overriddenAnno = weak.second; + overriderAnno = strongRelevantAnnos; + } + + checker.reportError( + methodTree, + messageKey, + jexpr, + methodTree.getName(), + overriddenTypeString, + overriddenAnno, + overriderTypeString, + overriderAnno); + } + } + } + + /** + * Localizes some contracts -- that is, viewpoint-adapts them to some method body, according to + * the value of {@link #methodTree}. + * + *

The input is a set of {@link Contract}s, each of which contains an expression string and an + * annotation. In a {@link Contract}, Java expressions are exactly as written in source code, not + * standardized or viewpoint-adapted. + * + *

The output is a set of pairs of {@link JavaExpression} (parsed expression string) and + * standardized annotation (with respect to the path of {@link #methodTree}. This method discards + * any contract whose expression cannot be parsed into a JavaExpression. + * + * @param contractSet a set of contracts + * @param methodType the type of the method that the contracts are for + * @return pairs of (expression, AnnotationMirror), which are localized contracts + */ + private Set> parseAndLocalizeContracts( + Set contractSet, AnnotatedExecutableType methodType) { + if (contractSet.isEmpty()) { + return Collections.emptySet(); } - /** - * Tests that the qualifiers present on the primitive type are valid. - * - * @param type the use of the primitive type - * @param tree the tree where the type is used - * @return true if the type is a valid use of the primitive type - */ - public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { - AnnotationMirrorSet bounds = - atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); - return typeHierarchy.isSubtypeShallowEffective(type, bounds); + // This is the path to a place where the contract is being used, which might or might not be + // where the contract was defined. For example, methodTree might be an overriding + // definition, and the contract might be for a superclass. + MethodTree methodTree = this.methodTree; + + StringToJavaExpression stringToJavaExpr = + expression -> { + JavaExpression javaExpr = + StringToJavaExpression.atMethodDecl(expression, methodType.getElement(), checker); + // methodType.getElement() is not necessarily the same method as methodTree, so + // viewpoint-adapt it to methodTree. + return javaExpr.atMethodBody(methodTree); + }; + + Set> result = + ArraySet.newArraySetOrHashSet(contractSet.size()); + for (Contract p : contractSet) { + String expressionString = p.expressionString; + AnnotationMirror annotation = + p.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, methodTree); + JavaExpression exprJe; + try { + // TODO: currently, these expressions are parsed many times. + // This could be optimized to store the result the first time. + // (same for other annotations) + exprJe = stringToJavaExpr.toJavaExpression(expressionString); + } catch (JavaExpressionParseException e) { + // report errors here + checker.report(methodTree, e.getDiagMessage()); + continue; + } + result.add(IPair.of(exprJe, annotation)); + } + return result; + } + + /** + * Call this only when the current path is an identifier. + * + * @return the enclosing member select, or null if the identifier is not the field in a member + * selection + */ + protected @Nullable MemberSelectTree enclosingMemberSelect() { + TreePath path = this.getCurrentPath(); + assert path.getLeaf().getKind() == Tree.Kind.IDENTIFIER + : "expected identifier, found: " + path.getLeaf(); + if (path.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { + return (MemberSelectTree) path.getParentPath().getLeaf(); + } else { + return null; + } + } + + /** + * Returns the statement that encloses the given one. + * + * @param tree an AST node that is on the current path + * @return the statement that encloses the given one + */ + protected @Nullable Tree enclosingStatement(@FindDistinct Tree tree) { + TreePath path = this.getCurrentPath(); + while (path != null && path.getLeaf() != tree) { + path = path.getParentPath(); } - /** - * Tests that the qualifiers present on the array type are valid. This method will be invoked - * for each array level independently, i.e. this method only needs to check the top-level - * qualifiers of an array. - * - * @param type the array type use - * @param tree the tree where the type is used - * @return true if the type is a valid array type - */ - public boolean isValidUse(AnnotatedArrayType type, Tree tree) { - AnnotationMirrorSet bounds = - atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); - return typeHierarchy.isSubtypeShallowEffective(type, bounds); + if (path != null) { + return path.getParentPath().getLeaf(); + } else { + return null; + } + } + + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + checkAccess(tree, p); + return super.visitIdentifier(tree, p); + } + + /** + * Issues an error if access is not allowed, based on an {@code @Unused} annotation. + * + * @param identifierTree the identifier being accessed; the method does nothing if it is not a + * field + * @param p ignored + */ + protected void checkAccess(IdentifierTree identifierTree, Void p) { + MemberSelectTree memberSel = enclosingMemberSelect(); + ExpressionTree tree; + Element elem; + + if (memberSel == null) { + tree = identifierTree; + elem = TreeUtils.elementFromUse(identifierTree); + } else { + tree = memberSel; + elem = TreeUtils.elementFromUse(memberSel); } - /** - * Tests whether the tree expressed by the passed type tree is a valid type, and emits an error - * if that is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, - * check the return type. - * - * @param tree the AST type supplied by the user - * @return true if the tree is a valid type - */ - public boolean validateTypeOf(Tree tree) { - AnnotatedTypeMirror type; - // It's quite annoying that there is no TypeTree. - switch (tree.getKind()) { - case PRIMITIVE_TYPE: - case PARAMETERIZED_TYPE: - case ARRAY_TYPE: - case UNBOUNDED_WILDCARD: - case EXTENDS_WILDCARD: - case SUPER_WILDCARD: - case ANNOTATED_TYPE: - type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); - break; - case TYPE_PARAMETER: - type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); - validateTargetLocation( - tree, - ((AnnotatedTypeVariable) type).getUpperBound(), - TypeUseLocation.UPPER_BOUND); - validateTargetLocation( - tree, - ((AnnotatedTypeVariable) type).getLowerBound(), - TypeUseLocation.LOWER_BOUND); - break; - case METHOD: - type = atypeFactory.getMethodReturnType((MethodTree) tree); - if (type == null || type.getKind() == TypeKind.VOID) { - // Nothing to do for void methods. - // Note that for a constructor the AnnotatedExecutableType does - // not use void as return type. - return true; - } - if (TreeUtils.isConstructor((MethodTree) tree)) { - validateTargetLocation(tree, type, TypeUseLocation.CONSTRUCTOR_RESULT); - } else { - validateTargetLocation(tree, type, TypeUseLocation.RETURN); - } - break; - default: - type = atypeFactory.getAnnotatedType(tree); - } - return validateType(tree, type); + if (elem == null || !elem.getKind().isField()) { + return; } - /** - * Tests whether the type and corresponding type tree is a valid type, and emits an error if - * that is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, tests - * the return type. - * - * @param tree the type tree supplied by the user - * @param type the type corresponding to tree - * @return true if the type is valid - */ - protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { - return typeValidator.isValid(type, tree); + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + + checkAccessAllowed(elem, receiver, tree); + } + + /** + * Issues an error if access not allowed, based on an @Unused annotation. + * + * @param field the field to be accessed, whose declaration might be annotated by @Unused. It can + * also be (for example) {@code this}, in which case {@code receiverType} is null. + * @param receiverType the type of the expression whose field is accessed; null if the field is + * static + * @param accessTree the access expression + */ + protected void checkAccessAllowed( + Element field, + @Nullable AnnotatedTypeMirror receiverType, + @FindDistinct ExpressionTree accessTree) { + AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class); + if (unused == null) { + return; } - // This is a test to ensure that all types are valid - protected final TypeValidator typeValidator; + String when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement).toString(); - protected TypeValidator createTypeValidator() { - return new BaseTypeValidator(checker, this, atypeFactory); + // TODO: Don't just look at the receiver type, but at the declaration annotations on the + // receiver. (That will enable handling type annotations that are not part of the type + // system being checked.) + + // TODO: This requires exactly the same type qualifier, but it should permit subqualifiers. + if (!AnnotationUtils.containsSameByName(receiverType.getAnnotations(), when)) { + return; } - // ********************************************************************** - // Random helper methods - // ********************************************************************** + Tree tree = this.enclosingStatement(accessTree); - /** - * Tests whether the expression should not be checked because of the tree referring to - * unannotated classes, as specified in the {@code checker.skipUses} property. - * - *

It returns true if exprTree is a method invocation or a field access to a class whose - * qualified name matches the {@code checker.skipUses} property. - * - * @param exprTree any expression tree - * @return true if checker should not test exprTree - */ - protected final boolean shouldSkipUses(ExpressionTree exprTree) { - // System.out.printf("shouldSkipUses: %s: %s%n", exprTree.getClass(), exprTree); - // if (atypeFactory.isUnreachable(exprTree)) { - // return true; - // } - Element elm = TreeUtils.elementFromTree(exprTree); - return checker.shouldSkipUses(elm); + if (tree != null + && tree.getKind() == Tree.Kind.ASSIGNMENT + && ((AssignmentTree) tree).getVariable() == accessTree + && ((AssignmentTree) tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL) { + // Assigning unused to null is OK. + return; } - // ********************************************************************** - // Overriding to avoid visit part of the tree - // ********************************************************************** - - /** Override Compilation Unit so we won't visit package names or imports. */ - @Override - public Void visitCompilationUnit(CompilationUnitTree identifierTree, Void p) { - Void r = scan(identifierTree.getPackageAnnotations(), p); - // r = reduce(scan(identifierTree.getPackageName(), p), r); - // r = reduce(scan(identifierTree.getImports(), p), r); - r = reduce(scan(identifierTree.getTypeDecls(), p), r); - return r; + checker.reportError(accessTree, "unallowed.access", field, receiverType); + } + + /** + * Tests that the qualifiers present on {@code useType} are valid qualifiers, given the qualifiers + * on the declaration of the type, {@code declarationType}. + * + *

The check is shallow, as it does not descend into generic or array types (i.e. only + * performing the validity check on the raw type or outermost array dimension). {@link + * BaseTypeVisitor#validateTypeOf(Tree)} would call this for each type argument or array dimension + * separately. + * + *

In most cases, {@code useType} simply needs to be a subtype of {@code declarationType}. If a + * type system makes exceptions to this rule, its implementation should override this method. + * + *

This method is not called if {@link + * BaseTypeValidator#shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror, Tree)} + * returns false -- by default, it is not called on the top level for locals and expressions. To + * enforce a type validity property everywhere, override methods such as {@link + * BaseTypeValidator#visitDeclared} rather than this method. + * + * @param declarationType the type of the class (TypeElement) + * @param useType the use of the class (instance type) + * @param tree the tree where the type is used + * @return true if the useType is a valid use of elemType + */ + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // Don't use isSubtype(ATM, ATM) because it will return false if the types have qualifier + // parameters. + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + TypeMirror declarationTM = declarationType.getUnderlyingType(); + AnnotationMirrorSet upperBounds = atypeFactory.getTypeDeclarationBounds(declarationTM); + for (AnnotationMirror top : tops) { + AnnotationMirror upperBound = qualHierarchy.findAnnotationInHierarchy(upperBounds, top); + if (!typeHierarchy.isSubtypeShallowEffective(useType, upperBound)) { + return false; + } + } + return true; + } + + /** + * Tests that the qualifiers present on the primitive type are valid. + * + * @param type the use of the primitive type + * @param tree the tree where the type is used + * @return true if the type is a valid use of the primitive type + */ + public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { + AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + return typeHierarchy.isSubtypeShallowEffective(type, bounds); + } + + /** + * Tests that the qualifiers present on the array type are valid. This method will be invoked for + * each array level independently, i.e. this method only needs to check the top-level qualifiers + * of an array. + * + * @param type the array type use + * @param tree the tree where the type is used + * @return true if the type is a valid array type + */ + public boolean isValidUse(AnnotatedArrayType type, Tree tree) { + AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + return typeHierarchy.isSubtypeShallowEffective(type, bounds); + } + + /** + * Tests whether the tree expressed by the passed type tree is a valid type, and emits an error if + * that is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, check + * the return type. + * + * @param tree the AST type supplied by the user + * @return true if the tree is a valid type + */ + public boolean validateTypeOf(Tree tree) { + AnnotatedTypeMirror type; + // It's quite annoying that there is no TypeTree. + switch (tree.getKind()) { + case PRIMITIVE_TYPE: + case PARAMETERIZED_TYPE: + case ARRAY_TYPE: + case UNBOUNDED_WILDCARD: + case EXTENDS_WILDCARD: + case SUPER_WILDCARD: + case ANNOTATED_TYPE: + type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); + break; + case TYPE_PARAMETER: + type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); + validateTargetLocation( + tree, ((AnnotatedTypeVariable) type).getUpperBound(), TypeUseLocation.UPPER_BOUND); + validateTargetLocation( + tree, ((AnnotatedTypeVariable) type).getLowerBound(), TypeUseLocation.LOWER_BOUND); + break; + case METHOD: + type = atypeFactory.getMethodReturnType((MethodTree) tree); + if (type == null || type.getKind() == TypeKind.VOID) { + // Nothing to do for void methods. + // Note that for a constructor the AnnotatedExecutableType does + // not use void as return type. + return true; + } + if (TreeUtils.isConstructor((MethodTree) tree)) { + validateTargetLocation(tree, type, TypeUseLocation.CONSTRUCTOR_RESULT); + } else { + validateTargetLocation(tree, type, TypeUseLocation.RETURN); + } + break; + default: + type = atypeFactory.getAnnotatedType(tree); } + return validateType(tree, type); + } + + /** + * Tests whether the type and corresponding type tree is a valid type, and emits an error if that + * is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, tests the + * return type. + * + * @param tree the type tree supplied by the user + * @param type the type corresponding to tree + * @return true if the type is valid + */ + protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { + return typeValidator.isValid(type, tree); + } + + // This is a test to ensure that all types are valid + protected final TypeValidator typeValidator; + + protected TypeValidator createTypeValidator() { + return new BaseTypeValidator(checker, this, atypeFactory); + } + + // ********************************************************************** + // Random helper methods + // ********************************************************************** + + /** + * Tests whether the expression should not be checked because of the tree referring to unannotated + * classes, as specified in the {@code checker.skipUses} property. + * + *

It returns true if exprTree is a method invocation or a field access to a class whose + * qualified name matches the {@code checker.skipUses} property. + * + * @param exprTree any expression tree + * @return true if checker should not test exprTree + */ + protected final boolean shouldSkipUses(ExpressionTree exprTree) { + // System.out.printf("shouldSkipUses: %s: %s%n", exprTree.getClass(), exprTree); + // if (atypeFactory.isUnreachable(exprTree)) { + // return true; + // } + Element elm = TreeUtils.elementFromTree(exprTree); + return checker.shouldSkipUses(elm); + } + + // ********************************************************************** + // Overriding to avoid visit part of the tree + // ********************************************************************** + + /** Override Compilation Unit so we won't visit package names or imports. */ + @Override + public Void visitCompilationUnit(CompilationUnitTree identifierTree, Void p) { + Void r = scan(identifierTree.getPackageAnnotations(), p); + // r = reduce(scan(identifierTree.getPackageName(), p), r); + // r = reduce(scan(identifierTree.getImports(), p), r); + r = reduce(scan(identifierTree.getTypeDecls(), p), r); + return r; + } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java index 9c8404de2f6..db522cd4da8 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java @@ -1,7 +1,6 @@ package org.checkerframework.common.basetype; import com.sun.source.tree.Tree; - import org.checkerframework.framework.type.AnnotatedTypeMirror; /** @@ -10,13 +9,13 @@ */ public interface TypeValidator { - /** - * The entry point to the type validator. Validate the type against the given tree. - * - * @param type the type to validate - * @param tree the tree from which the type originated. If the tree is a method tree, then - * validate its return type. If the tree is a variable tree, then validate its field type. - * @return true, iff the type is valid - */ - public boolean isValid(AnnotatedTypeMirror type, Tree tree); + /** + * The entry point to the type validator. Validate the type against the given tree. + * + * @param type the type to validate + * @param tree the tree from which the type originated. If the tree is a method tree, then + * validate its return type. If the tree is a variable tree, then validate its field type. + * @return true, iff the type is valid + */ + public boolean isValid(AnnotatedTypeMirror type, Tree tree); } diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java index 18dd6e12e66..c3d25862b34 100644 --- a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java @@ -1,7 +1,16 @@ package org.checkerframework.common.initializedfields; import com.sun.source.tree.VariableTree; - +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.BinaryName; import org.checkerframework.common.accumulation.AccumulationAnalysis; @@ -20,237 +29,215 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.UserError; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; - /** The annotated type factory for the Initialized Fields Checker. */ public class InitializedFieldsAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory { - /** - * The type factories that determine whether the default value is consistent with the annotated - * type. If empty, warn about all uninitialized fields. - */ - private final List> defaultValueAtypeFactories; - - /** - * Creates a new InitializedFieldsAnnotatedTypeFactory. - * - * @param checker the checker - */ - public InitializedFieldsAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, InitializedFields.class, InitializedFieldsBottom.class); - - List checkerNames = getCheckerNames(); - - // There are usually few subcheckers. - defaultValueAtypeFactories = new ArrayList<>(2); - for (String checkerName : checkerNames) { - if (checkerName.equals(InitializedFieldsChecker.class.getCanonicalName())) { - continue; - } - @SuppressWarnings("signature:argument.type.incompatible") // -processor is a binary name - GenericAnnotatedTypeFactory atf = - createTypeFactoryForProcessor(checkerName); - if (atf != null) { - // Add all the subcheckers so that default values are checked for the subcheckers. - for (BaseTypeChecker subchecker : atf.getChecker().getSubcheckers()) { - defaultValueAtypeFactories.add(subchecker.getTypeFactory()); - } - defaultValueAtypeFactories.add(atf); - } + /** + * The type factories that determine whether the default value is consistent with the annotated + * type. If empty, warn about all uninitialized fields. + */ + private final List> defaultValueAtypeFactories; + + /** + * Creates a new InitializedFieldsAnnotatedTypeFactory. + * + * @param checker the checker + */ + public InitializedFieldsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, InitializedFields.class, InitializedFieldsBottom.class); + + List checkerNames = getCheckerNames(); + + // There are usually few subcheckers. + defaultValueAtypeFactories = new ArrayList<>(2); + for (String checkerName : checkerNames) { + if (checkerName.equals(InitializedFieldsChecker.class.getCanonicalName())) { + continue; + } + @SuppressWarnings("signature:argument.type.incompatible") // -processor is a binary name + GenericAnnotatedTypeFactory atf = createTypeFactoryForProcessor(checkerName); + if (atf != null) { + // Add all the subcheckers so that default values are checked for the subcheckers. + for (BaseTypeChecker subchecker : atf.getChecker().getSubcheckers()) { + defaultValueAtypeFactories.add(subchecker.getTypeFactory()); } - - this.postInit(); + defaultValueAtypeFactories.add(atf); + } } - /** - * Creates a new type factory for the given annotation processor, if it is a type-checker. This - * does NOT return an existing type factory. - * - * @param processorName the fully-qualified class name of an annotation processor - * @return the type factory for the given annotation processor, or null if it's not a checker - */ - private @Nullable GenericAnnotatedTypeFactory createTypeFactoryForProcessor( - @BinaryName String processorName) { - try { - Class checkerClass = Class.forName(processorName); - if (!BaseTypeChecker.class.isAssignableFrom(checkerClass)) { - return null; - } - @SuppressWarnings("unchecked") - BaseTypeChecker c = - ((Class) checkerClass) - .getDeclaredConstructor() - .newInstance(); - c.init(processingEnv); - c.initChecker(); - BaseTypeVisitor v = c.createSourceVisitorPublic(); - GenericAnnotatedTypeFactory atf = v.createTypeFactoryPublic(); - if (atf == null) { - throw new UserError( - "Cannot find %s; check the classpath or processorpath", processorName); - } - return atf; - } catch (ClassNotFoundException - | InstantiationException - | InvocationTargetException - | IllegalAccessException - | NoSuchMethodException e) { - throw new UserError("Problem instantiating " + processorName, e); - } - } - - @Override - public InitializedFieldsContractsFromMethod getContractsFromMethod() { - return new InitializedFieldsContractsFromMethod(this); + this.postInit(); + } + + /** + * Creates a new type factory for the given annotation processor, if it is a type-checker. This + * does NOT return an existing type factory. + * + * @param processorName the fully-qualified class name of an annotation processor + * @return the type factory for the given annotation processor, or null if it's not a checker + */ + private @Nullable GenericAnnotatedTypeFactory createTypeFactoryForProcessor( + @BinaryName String processorName) { + try { + Class checkerClass = Class.forName(processorName); + if (!BaseTypeChecker.class.isAssignableFrom(checkerClass)) { + return null; + } + @SuppressWarnings("unchecked") + BaseTypeChecker c = + ((Class) checkerClass).getDeclaredConstructor().newInstance(); + c.init(processingEnv); + c.initChecker(); + BaseTypeVisitor v = c.createSourceVisitorPublic(); + GenericAnnotatedTypeFactory atf = v.createTypeFactoryPublic(); + if (atf == null) { + throw new UserError("Cannot find %s; check the classpath or processorpath", processorName); + } + return atf; + } catch (ClassNotFoundException + | InstantiationException + | InvocationTargetException + | IllegalAccessException + | NoSuchMethodException e) { + throw new UserError("Problem instantiating " + processorName, e); } + } - /** An array consisting only of the string "this". */ - private static final String[] thisStringArray = new String[] {"this"}; - - /** - * A subclass of ContractsFromMethod that adds a postcondition contract to each constructor, - * requiring that it initializes all fields. - */ - private class InitializedFieldsContractsFromMethod extends DefaultContractsFromMethod { - /** - * Creates an InitializedFieldsContractsFromMethod for the given factory. - * - * @param factory the type factory associated with the newly-created ContractsFromMethod - */ - public InitializedFieldsContractsFromMethod( - GenericAnnotatedTypeFactory factory) { - super(factory); - } - - @Override - public Set getPostconditions(ExecutableElement executableElement) { - Set result = super.getPostconditions(executableElement); - - // Only process constructors defined in source code being type-checked. - if (declarationFromElement(executableElement) != null - && executableElement.getKind() == ElementKind.CONSTRUCTOR) { - String[] fieldsToInitialize = - fieldsToInitialize((TypeElement) executableElement.getEnclosingElement()); - if (fieldsToInitialize.length != 0) { - AnnotationMirror initializedFieldsAnno; - { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, InitializedFields.class); - builder.setValue("value", fieldsToInitialize); - initializedFieldsAnno = builder.build(); - } - AnnotationMirror ensuresAnno; - { - AnnotationBuilder builder = - new AnnotationBuilder( - processingEnv, EnsuresInitializedFields.class); - builder.setValue("value", thisStringArray); - builder.setValue("fields", fieldsToInitialize); - ensuresAnno = builder.build(); - } - Contract.Postcondition ensuresContract = - new Contract.Postcondition("this", initializedFieldsAnno, ensuresAnno); - result.add(ensuresContract); - } - } + @Override + public InitializedFieldsContractsFromMethod getContractsFromMethod() { + return new InitializedFieldsContractsFromMethod(this); + } - return result; - } - } + /** An array consisting only of the string "this". */ + private static final String[] thisStringArray = new String[] {"this"}; + /** + * A subclass of ContractsFromMethod that adds a postcondition contract to each constructor, + * requiring that it initializes all fields. + */ + private class InitializedFieldsContractsFromMethod extends DefaultContractsFromMethod { /** - * Returns the fields that the constructor must initialize. These are the fields F declared in - * this class that satisfy all of the following conditions: + * Creates an InitializedFieldsContractsFromMethod for the given factory. * - *

    - *
  • F is a non-final field (if final, Java will issue a warning, so we don't need to). - *
  • F's declaration has no initializer. - *
  • No initialization block or static initialization block sets the field. (This is handled - * automatically because dataflow visits (static) initialization blocks as part of the - * constructor.) - *
  • F's annotated type is not consistent with the default value (0, 0.0, false, or null) - *
- * - * @param type the type whose fields to list - * @return the fields whose type is not consistent with the default value, so the constructor - * must initialize them + * @param factory the type factory associated with the newly-created ContractsFromMethod */ - // It is a bit wasteful that this is recomputed for each constructor. - private String[] fieldsToInitialize(TypeElement type) { - List result = new ArrayList(); - - for (Element member : type.getEnclosedElements()) { - - if (member.getKind() != ElementKind.FIELD) { - continue; - } - - VariableElement field = (VariableElement) member; - if (ElementUtils.isFinal(field)) { - continue; - } - - VariableTree fieldTree = (VariableTree) declarationFromElement(field); - if (fieldTree.getInitializer() != null) { - continue; - } - - if (!defaultValueIsOK(field)) { - result.add(field.getSimpleName().toString()); - } - } - - return result.toArray(new String[result.size()]); + public InitializedFieldsContractsFromMethod(GenericAnnotatedTypeFactory factory) { + super(factory); } - /** - * Returns true if the default field value (0, 0.0, false, or null) is consistent with the - * field's declared type. - * - * @param field a field - * @return true if the default field value is consistent with the field's declared type - */ - private boolean defaultValueIsOK(VariableElement field) { - if (defaultValueAtypeFactories.isEmpty()) { - return false; + @Override + public Set getPostconditions(ExecutableElement executableElement) { + Set result = super.getPostconditions(executableElement); + + // Only process constructors defined in source code being type-checked. + if (declarationFromElement(executableElement) != null + && executableElement.getKind() == ElementKind.CONSTRUCTOR) { + String[] fieldsToInitialize = + fieldsToInitialize((TypeElement) executableElement.getEnclosingElement()); + if (fieldsToInitialize.length != 0) { + AnnotationMirror initializedFieldsAnno; + { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, InitializedFields.class); + builder.setValue("value", fieldsToInitialize); + initializedFieldsAnno = builder.build(); + } + AnnotationMirror ensuresAnno; + { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, EnsuresInitializedFields.class); + builder.setValue("value", thisStringArray); + builder.setValue("fields", fieldsToInitialize); + ensuresAnno = builder.build(); + } + Contract.Postcondition ensuresContract = + new Contract.Postcondition("this", initializedFieldsAnno, ensuresAnno); + result.add(ensuresContract); } + } - for (GenericAnnotatedTypeFactory defaultValueAtypeFactory : - defaultValueAtypeFactories) { - defaultValueAtypeFactory.setRoot(this.getRoot()); - // Set the root on all the subcheckers, too. - for (BaseTypeChecker subchecker : - defaultValueAtypeFactory.getChecker().getSubcheckers()) { - AnnotatedTypeFactory subATF = subchecker.getTypeFactory(); - subATF.setRoot(this.getRoot()); - } - AnnotatedTypeMirror fieldType = defaultValueAtypeFactory.getAnnotatedType(field); - AnnotatedTypeMirror defaultValueType = - defaultValueAtypeFactory.getDefaultValueAnnotatedType( - fieldType.getUnderlyingType()); - if (!defaultValueAtypeFactory - .getTypeHierarchy() - .isSubtype(defaultValueType, fieldType)) { - return false; - } - } + return result; + } + } + + /** + * Returns the fields that the constructor must initialize. These are the fields F declared in + * this class that satisfy all of the following conditions: + * + *
    + *
  • F is a non-final field (if final, Java will issue a warning, so we don't need to). + *
  • F's declaration has no initializer. + *
  • No initialization block or static initialization block sets the field. (This is handled + * automatically because dataflow visits (static) initialization blocks as part of the + * constructor.) + *
  • F's annotated type is not consistent with the default value (0, 0.0, false, or null) + *
+ * + * @param type the type whose fields to list + * @return the fields whose type is not consistent with the default value, so the constructor must + * initialize them + */ + // It is a bit wasteful that this is recomputed for each constructor. + private String[] fieldsToInitialize(TypeElement type) { + List result = new ArrayList(); + + for (Element member : type.getEnclosedElements()) { + + if (member.getKind() != ElementKind.FIELD) { + continue; + } + + VariableElement field = (VariableElement) member; + if (ElementUtils.isFinal(field)) { + continue; + } + + VariableTree fieldTree = (VariableTree) declarationFromElement(field); + if (fieldTree.getInitializer() != null) { + continue; + } + + if (!defaultValueIsOK(field)) { + result.add(field.getSimpleName().toString()); + } + } - return true; + return result.toArray(new String[result.size()]); + } + + /** + * Returns true if the default field value (0, 0.0, false, or null) is consistent with the field's + * declared type. + * + * @param field a field + * @return true if the default field value is consistent with the field's declared type + */ + private boolean defaultValueIsOK(VariableElement field) { + if (defaultValueAtypeFactories.isEmpty()) { + return false; } - // Overridden because there is no InitalizedFieldsAnalysis. - @Override - protected AccumulationAnalysis createFlowAnalysis() { - return new AccumulationAnalysis(this.getChecker(), this); + for (GenericAnnotatedTypeFactory defaultValueAtypeFactory : + defaultValueAtypeFactories) { + defaultValueAtypeFactory.setRoot(this.getRoot()); + // Set the root on all the subcheckers, too. + for (BaseTypeChecker subchecker : defaultValueAtypeFactory.getChecker().getSubcheckers()) { + AnnotatedTypeFactory subATF = subchecker.getTypeFactory(); + subATF.setRoot(this.getRoot()); + } + AnnotatedTypeMirror fieldType = defaultValueAtypeFactory.getAnnotatedType(field); + AnnotatedTypeMirror defaultValueType = + defaultValueAtypeFactory.getDefaultValueAnnotatedType(fieldType.getUnderlyingType()); + if (!defaultValueAtypeFactory.getTypeHierarchy().isSubtype(defaultValueType, fieldType)) { + return false; + } } + + return true; + } + + // Overridden because there is no InitalizedFieldsAnalysis. + @Override + protected AccumulationAnalysis createFlowAnalysis() { + return new AccumulationAnalysis(this.getChecker(), this); + } } diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java index c32e46a532b..921f1fd0020 100644 --- a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java @@ -13,25 +13,25 @@ /** Accumulates the names of fields that are initialized. */ public class InitializedFieldsTransfer extends AccumulationTransfer { - /** - * Create an InitializedFieldsTransfer. - * - * @param analysis the analysis - */ - public InitializedFieldsTransfer(AccumulationAnalysis analysis) { - super(analysis); - } + /** + * Create an InitializedFieldsTransfer. + * + * @param analysis the analysis + */ + public InitializedFieldsTransfer(AccumulationAnalysis analysis) { + super(analysis); + } - @Override - public TransferResult visitAssignment( - AssignmentNode node, TransferInput input) { - TransferResult result = - super.visitAssignment(node, input); - Node lhs = node.getTarget(); - if (lhs instanceof FieldAccessNode) { - FieldAccessNode fieldAccess = (FieldAccessNode) lhs; - accumulate(fieldAccess.getReceiver(), result, fieldAccess.getFieldName()); - } - return result; + @Override + public TransferResult visitAssignment( + AssignmentNode node, TransferInput input) { + TransferResult result = + super.visitAssignment(node, input); + Node lhs = node.getTarget(); + if (lhs instanceof FieldAccessNode) { + FieldAccessNode fieldAccess = (FieldAccessNode) lhs; + accumulate(fieldAccess.getReceiver(), result, fieldAccess.getFieldName()); } + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java index 0689e410b88..20cc4d6da52 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java @@ -7,7 +7,17 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.UnionClassType; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -33,362 +43,341 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; - /** A type factory for the @ClassVal and @ClassBound annotations. */ public class ClassValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - protected final AnnotationMirror CLASSVAL_TOP = - AnnotationBuilder.fromClass(elements, UnknownClass.class); + protected final AnnotationMirror CLASSVAL_TOP = + AnnotationBuilder.fromClass(elements, UnknownClass.class); - /** The ClassBound.value argument/element. */ - private final ExecutableElement classBoundValueElement = - TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); + /** The ClassBound.value argument/element. */ + private final ExecutableElement classBoundValueElement = + TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); - /** The ClassVal.value argument/element. */ - private final ExecutableElement classValValueElement = - TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); + /** The ClassVal.value argument/element. */ + private final ExecutableElement classValValueElement = + TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); - /** - * Create a new ClassValAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this factory - */ - public ClassValAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + /** + * Create a new ClassValAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this factory + */ + public ClassValAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); - if (this.getClass() == ClassValAnnotatedTypeFactory.class) { - this.postInit(); - } + if (this.getClass() == ClassValAnnotatedTypeFactory.class) { + this.postInit(); } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - UnknownClass.class, - ClassVal.class, - ClassBound.class, - ClassValBottom.class)); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList(UnknownClass.class, ClassVal.class, ClassBound.class, ClassValBottom.class)); + } + + /** + * Create a {@code @ClassVal} annotation with the given values. + * + * @param values the "value" field of the resulting {@code @ClassVal} annotation + * @return a {@code @ClassVal} annotation with the given values + */ + private AnnotationMirror createClassVal(List values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassVal.class); + builder.setValue("value", values); + return builder.build(); + } + + /** + * Create a {@code @ClassBound} annotation with the given values. + * + * @param values the "value" field of the resulting {@code @ClassBound} annotation + * @return a {@code @ClassBound} annotation with the given values + */ + private AnnotationMirror createClassBound(List values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassBound.class); + builder.setValue("value", values); + return builder.build(); + } + + /** + * Returns the list of classnames from {@code @ClassBound} or {@code @ClassVal} if anno is + * {@code @ClassBound} or {@code @ClassVal}, otherwise returns an empty list. + * + * @param anno any AnnotationMirror + * @return list of classnames in anno + */ + public List getClassNamesFromAnnotation(AnnotationMirror anno) { + if (areSameByClass(anno, ClassBound.class)) { + return AnnotationUtils.getElementValueArray(anno, classBoundValueElement, String.class); + } else if (areSameByClass(anno, ClassVal.class)) { + return AnnotationUtils.getElementValueArray(anno, classValValueElement, String.class); + } else { + return Collections.emptyList(); } + } - /** - * Create a {@code @ClassVal} annotation with the given values. - * - * @param values the "value" field of the resulting {@code @ClassVal} annotation - * @return a {@code @ClassVal} annotation with the given values - */ - private AnnotationMirror createClassVal(List values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassVal.class); - builder.setValue("value", values); - return builder.build(); - } + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new ClassValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** The qualifier hierarchy for the ClassVal type system. */ + protected class ClassValQualifierHierarchy extends ElementQualifierHierarchy { /** - * Create a {@code @ClassBound} annotation with the given values. + * Creates a ClassValQualifierHierarchy from the given classes. * - * @param values the "value" field of the resulting {@code @ClassBound} annotation - * @return a {@code @ClassBound} annotation with the given values + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils */ - private AnnotationMirror createClassBound(List values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassBound.class); - builder.setValue("value", values); - return builder.build(); + public ClassValQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, ClassValAnnotatedTypeFactory.this); } - /** - * Returns the list of classnames from {@code @ClassBound} or {@code @ClassVal} if anno is - * {@code @ClassBound} or {@code @ClassVal}, otherwise returns an empty list. - * - * @param anno any AnnotationMirror - * @return list of classnames in anno + /* + * Determines the least upper bound of a1 and a2. If both are ClassVal + * annotations, then the least upper bound is the set of elements + * obtained by combining the values of both annotations. */ - public List getClassNamesFromAnnotation(AnnotationMirror anno) { - if (areSameByClass(anno, ClassBound.class)) { - return AnnotationUtils.getElementValueArray(anno, classBoundValueElement, String.class); - } else if (areSameByClass(anno, ClassVal.class)) { - return AnnotationUtils.getElementValueArray(anno, classValValueElement, String.class); + @Override + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } else { + List a1ClassNames = getClassNamesFromAnnotation(a1); + List a2ClassNames = getClassNamesFromAnnotation(a2); + // There are usually few arguments/elements of @ClassBound and @ClassVal. + List lubClassNames = CollectionsPlume.listUnion(a1ClassNames, a2ClassNames); + + // If either annotation is a ClassBound, the lub must also be a class bound. + if (areSameByClass(a1, ClassBound.class) || areSameByClass(a2, ClassBound.class)) { + return createClassBound(lubClassNames); } else { - return Collections.emptyList(); + return createClassVal(lubClassNames); } + } } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new ClassValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + public @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } else { + List a1ClassNames = getClassNamesFromAnnotation(a1); + List a2ClassNames = getClassNamesFromAnnotation(a2); + List glbClassNames = CollectionsPlume.listIntersection(a1ClassNames, a2ClassNames); + + // If either annotation is a ClassVal, the glb must also be a ClassVal. + // For example: + // GLB( @ClassVal(a,b), @ClassBound(a,c)) is @ClassVal(a) + // because @ClassBound(a) is not a subtype of @ClassVal(a,b) + if (areSameByClass(a1, ClassVal.class) || areSameByClass(a2, ClassVal.class)) { + return createClassVal(glbClassNames); + } else { + return createClassBound(glbClassNames); + } + } } - /** The qualifier hierarchy for the ClassVal type system. */ - protected class ClassValQualifierHierarchy extends ElementQualifierHierarchy { - - /** - * Creates a ClassValQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - public ClassValQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, ClassValAnnotatedTypeFactory.this); - } + /* + * Computes subtyping as per the subtyping in the qualifier hierarchy + * structure unless both annotations are ClassVal. In this case, rhs is + * a subtype of lhs iff lhs contains every element of rhs. + */ + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (AnnotationUtils.areSame(subAnno, superAnno) + || areSameByClass(superAnno, UnknownClass.class) + || areSameByClass(subAnno, ClassValBottom.class)) { + return true; + } + if (areSameByClass(subAnno, UnknownClass.class) + || areSameByClass(superAnno, ClassValBottom.class)) { + return false; + } + if (areSameByClass(superAnno, ClassVal.class) && areSameByClass(subAnno, ClassBound.class)) { + return false; + } + + // if super: ClassVal && sub is ClassVal + // if super: ClassBound && (sub is ClassBound or ClassVal) + + List supValues = getClassNamesFromAnnotation(superAnno); + List subValues = getClassNamesFromAnnotation(subAnno); + + return supValues.containsAll(subValues); + } + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new ClassValTreeAnnotator(this), super.createTreeAnnotator()); + } + + /** + * Implements the following type inference rules. + * + *
+   * C.class:             @ClassVal(fully qualified name of C)
+   * Class.forName(name): @ClassVal("name")
+   * exp.getClass():      @ClassBound(fully qualified classname of exp)
+   * 
+ */ + protected class ClassValTreeAnnotator extends TreeAnnotator { + + protected ClassValTreeAnnotator(ClassValAnnotatedTypeFactory factory) { + super(factory); + } - /* - * Determines the least upper bound of a1 and a2. If both are ClassVal - * annotations, then the least upper bound is the set of elements - * obtained by combining the values of both annotations. - */ - @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } else { - List a1ClassNames = getClassNamesFromAnnotation(a1); - List a2ClassNames = getClassNamesFromAnnotation(a2); - // There are usually few arguments/elements of @ClassBound and @ClassVal. - List lubClassNames = CollectionsPlume.listUnion(a1ClassNames, a2ClassNames); - - // If either annotation is a ClassBound, the lub must also be a class bound. - if (areSameByClass(a1, ClassBound.class) || areSameByClass(a2, ClassBound.class)) { - return createClassBound(lubClassNames); - } else { - return createClassVal(lubClassNames); - } - } + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isClassLiteral(tree)) { + // Create annotations for Class literals + // C.class: @ClassVal(fully qualified name of C) + ExpressionTree etree = tree.getExpression(); + Type classType = (Type) TreeUtils.typeOf(etree); + String name = getClassNameFromType(classType); + if (name != null && !name.equals("void")) { + AnnotationMirror newQual = createClassVal(Arrays.asList(name)); + type.replaceAnnotation(newQual); } + } + return null; + } - @Override - public @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } else { - List a1ClassNames = getClassNamesFromAnnotation(a1); - List a2ClassNames = getClassNamesFromAnnotation(a2); - List glbClassNames = - CollectionsPlume.listIntersection(a1ClassNames, a2ClassNames); - - // If either annotation is a ClassVal, the glb must also be a ClassVal. - // For example: - // GLB( @ClassVal(a,b), @ClassBound(a,c)) is @ClassVal(a) - // because @ClassBound(a) is not a subtype of @ClassVal(a,b) - if (areSameByClass(a1, ClassVal.class) || areSameByClass(a2, ClassVal.class)) { - return createClassVal(glbClassNames); - } else { - return createClassBound(glbClassNames); - } - } + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + + if (isForNameMethodInvocation(tree)) { + // Class.forName(name): @ClassVal("name") + ExpressionTree arg = tree.getArguments().get(0); + List classNames = getStringValues(arg); + if (classNames != null) { + AnnotationMirror newQual = createClassVal(classNames); + type.replaceAnnotation(newQual); } - - /* - * Computes subtyping as per the subtyping in the qualifier hierarchy - * structure unless both annotations are ClassVal. In this case, rhs is - * a subtype of lhs iff lhs contains every element of rhs. - */ - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (AnnotationUtils.areSame(subAnno, superAnno) - || areSameByClass(superAnno, UnknownClass.class) - || areSameByClass(subAnno, ClassValBottom.class)) { - return true; - } - if (areSameByClass(subAnno, UnknownClass.class) - || areSameByClass(superAnno, ClassValBottom.class)) { - return false; - } - if (areSameByClass(superAnno, ClassVal.class) - && areSameByClass(subAnno, ClassBound.class)) { - return false; - } - - // if super: ClassVal && sub is ClassVal - // if super: ClassBound && (sub is ClassBound or ClassVal) - - List supValues = getClassNamesFromAnnotation(superAnno); - List subValues = getClassNamesFromAnnotation(subAnno); - - return supValues.containsAll(subValues); + } else if (isGetClassMethodInvocation(tree)) { + // exp.getClass(): @ClassBound(fully qualified class name of exp) + Type clType; + if (TreeUtils.getReceiverTree(tree) != null) { + clType = (Type) TreeUtils.typeOf(TreeUtils.getReceiverTree(tree)); + } else { // receiver is null, so it is implicitly "this" + ClassTree classTree = TreePathUtil.enclosingClass(getPath(tree)); + clType = (Type) TreeUtils.typeOf(classTree); } + String className = getClassNameFromType(clType); + AnnotationMirror newQual = createClassBound(Arrays.asList(className)); + type.replaceAnnotation(newQual); + } + return null; } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new ClassValTreeAnnotator(this), super.createTreeAnnotator()); + /** + * Return true if this is an invocation of a method annotated with @ForName. An example of such + * a method is {@link Class#forName}. + * + * @param tree a method invocation + * @return true if this is an invocation of a method annotated with @ForName + */ + private boolean isForNameMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), ForName.class) != null; } /** - * Implements the following type inference rules. + * Return true if this is an invocation of a method annotated with @GetClass. An example of such + * a method is {@link Object#getClass}. * - *
-     * C.class:             @ClassVal(fully qualified name of C)
-     * Class.forName(name): @ClassVal("name")
-     * exp.getClass():      @ClassBound(fully qualified classname of exp)
-     * 
+ * @param tree a method invocation + * @return true if this is an invocation of a method annotated with @GetClass */ - protected class ClassValTreeAnnotator extends TreeAnnotator { - - protected ClassValTreeAnnotator(ClassValAnnotatedTypeFactory factory) { - super(factory); - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isClassLiteral(tree)) { - // Create annotations for Class literals - // C.class: @ClassVal(fully qualified name of C) - ExpressionTree etree = tree.getExpression(); - Type classType = (Type) TreeUtils.typeOf(etree); - String name = getClassNameFromType(classType); - if (name != null && !name.equals("void")) { - AnnotationMirror newQual = createClassVal(Arrays.asList(name)); - type.replaceAnnotation(newQual); - } - } - return null; - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - - if (isForNameMethodInvocation(tree)) { - // Class.forName(name): @ClassVal("name") - ExpressionTree arg = tree.getArguments().get(0); - List classNames = getStringValues(arg); - if (classNames != null) { - AnnotationMirror newQual = createClassVal(classNames); - type.replaceAnnotation(newQual); - } - } else if (isGetClassMethodInvocation(tree)) { - // exp.getClass(): @ClassBound(fully qualified class name of exp) - Type clType; - if (TreeUtils.getReceiverTree(tree) != null) { - clType = (Type) TreeUtils.typeOf(TreeUtils.getReceiverTree(tree)); - } else { // receiver is null, so it is implicitly "this" - ClassTree classTree = TreePathUtil.enclosingClass(getPath(tree)); - clType = (Type) TreeUtils.typeOf(classTree); - } - String className = getClassNameFromType(clType); - AnnotationMirror newQual = createClassBound(Arrays.asList(className)); - type.replaceAnnotation(newQual); - } - return null; - } - - /** - * Return true if this is an invocation of a method annotated with @ForName. An example of - * such a method is {@link Class#forName}. - * - * @param tree a method invocation - * @return true if this is an invocation of a method annotated with @ForName - */ - private boolean isForNameMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), ForName.class) != null; - } - - /** - * Return true if this is an invocation of a method annotated with @GetClass. An example of - * such a method is {@link Object#getClass}. - * - * @param tree a method invocation - * @return true if this is an invocation of a method annotated with @GetClass - */ - private boolean isGetClassMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetClass.class) != null; - } + private boolean isGetClassMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetClass.class) != null; + } - private @Nullable List getStringValues(ExpressionTree arg) { - ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotationMirror annotation = valueATF.getAnnotationMirror(arg, StringVal.class); - if (annotation == null) { - return null; - } - return AnnotationUtils.getElementValueArray( - annotation, valueATF.stringValValueElement, String.class); - } + private @Nullable List getStringValues(ExpressionTree arg) { + ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotationMirror annotation = valueATF.getAnnotationMirror(arg, StringVal.class); + if (annotation == null) { + return null; + } + return AnnotationUtils.getElementValueArray( + annotation, valueATF.stringValValueElement, String.class); + } - // TODO: This looks like it returns a @BinaryName. Verify that fact and add a type - // qualifier. - /** - * Return String representation of class name. This will not return the correct name for - * anonymous classes. - */ - private String getClassNameFromType(Type classType) { - switch (classType.getKind()) { - case ARRAY: - String array = ""; - while (classType.getKind() == TypeKind.ARRAY) { - classType = ((ArrayType) classType).getComponentType(); - array += "[]"; - } - return getClassNameFromType(classType) + array; - case DECLARED: - StringBuilder className = - new StringBuilder( - TypesUtils.getQualifiedName((DeclaredType) classType)); - if (classType.getEnclosingType() != null) { - while (classType.getEnclosingType().getKind() != TypeKind.NONE) { - classType = classType.getEnclosingType(); - int last = className.lastIndexOf("."); - if (last > -1) { - className.replace(last, last + 1, "$"); - } - } - } - return className.toString(); - case INTERSECTION: - // This could be more precise - return "java.lang.Object"; - case NULL: - return "java.lang.Object"; - case UNION: - classType = ((UnionClassType) classType).getLub(); - return getClassNameFromType(classType); - case TYPEVAR: - case WILDCARD: - classType = classType.getUpperBound(); - return getClassNameFromType(classType); - case INT: - return int.class.getCanonicalName(); - case LONG: - return long.class.getCanonicalName(); - case SHORT: - return short.class.getCanonicalName(); - case BYTE: - return byte.class.getCanonicalName(); - case CHAR: - return char.class.getCanonicalName(); - case DOUBLE: - return double.class.getCanonicalName(); - case FLOAT: - return float.class.getCanonicalName(); - case BOOLEAN: - return boolean.class.getCanonicalName(); - case VOID: - return "void"; - default: - throw new BugInCF( - "ClassValAnnotatedTypeFactory.getClassname: did not expect " - + classType.getKind()); + // TODO: This looks like it returns a @BinaryName. Verify that fact and add a type + // qualifier. + /** + * Return String representation of class name. This will not return the correct name for + * anonymous classes. + */ + private String getClassNameFromType(Type classType) { + switch (classType.getKind()) { + case ARRAY: + String array = ""; + while (classType.getKind() == TypeKind.ARRAY) { + classType = ((ArrayType) classType).getComponentType(); + array += "[]"; + } + return getClassNameFromType(classType) + array; + case DECLARED: + StringBuilder className = + new StringBuilder(TypesUtils.getQualifiedName((DeclaredType) classType)); + if (classType.getEnclosingType() != null) { + while (classType.getEnclosingType().getKind() != TypeKind.NONE) { + classType = classType.getEnclosingType(); + int last = className.lastIndexOf("."); + if (last > -1) { + className.replace(last, last + 1, "$"); + } } - } + } + return className.toString(); + case INTERSECTION: + // This could be more precise + return "java.lang.Object"; + case NULL: + return "java.lang.Object"; + case UNION: + classType = ((UnionClassType) classType).getLub(); + return getClassNameFromType(classType); + case TYPEVAR: + case WILDCARD: + classType = classType.getUpperBound(); + return getClassNameFromType(classType); + case INT: + return int.class.getCanonicalName(); + case LONG: + return long.class.getCanonicalName(); + case SHORT: + return short.class.getCanonicalName(); + case BYTE: + return byte.class.getCanonicalName(); + case CHAR: + return char.class.getCanonicalName(); + case DOUBLE: + return double.class.getCanonicalName(); + case FLOAT: + return float.class.getCanonicalName(); + case BOOLEAN: + return boolean.class.getCanonicalName(); + case VOID: + return "void"; + default: + throw new BugInCF( + "ClassValAnnotatedTypeFactory.getClassname: did not expect " + classType.getKind()); + } } + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java index e3fc55fa96b..6afc4dc630d 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java @@ -1,13 +1,12 @@ package org.checkerframework.common.reflection; +import java.util.LinkedHashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; import org.plumelib.util.CollectionsPlume; -import java.util.LinkedHashSet; -import java.util.Set; - /** * The ClassVal Checker provides a sound estimate of the binary name of Class objects. * @@ -15,26 +14,26 @@ */ public class ClassValChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ClassValVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ClassValVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - // Don't call super otherwise MethodVal will be added as a subChecker - // which creates a circular dependency. - // Use the same Set implementation as super. - Set> subCheckers = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); - subCheckers.add(ValueChecker.class); - return subCheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Don't call super otherwise MethodVal will be added as a subChecker + // which creates a circular dependency. + // Use the same Set implementation as super. + Set> subCheckers = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); + subCheckers.add(ValueChecker.class); + return subCheckers; + } - @Override - public boolean shouldResolveReflection() { - // Because this checker is a subchecker of MethodVal, - // reflection can't be resolved. - return false; - } + @Override + public boolean shouldResolveReflection() { + // Because this checker is a subchecker of MethodVal, + // reflection can't be resolved. + return false; + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java index 4704948ba9d..ab138975a3e 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java @@ -1,7 +1,8 @@ package org.checkerframework.common.reflection; import com.sun.source.tree.Tree; - +import java.util.List; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -11,59 +12,52 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.plumelib.reflection.Signatures; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; - /** A visitor to verify validity of {@code @}{@link ClassVal} annotations. */ public class ClassValVisitor extends BaseTypeVisitor { - /** - * Create a new ClassValVisitor. - * - * @param checker the associated type-checker - */ - public ClassValVisitor(BaseTypeChecker checker) { - super(checker); - } - - @Override - protected ClassValAnnotatedTypeFactory createTypeFactory() { - return new ClassValAnnotatedTypeFactory(checker); - } - - @Override - protected BaseTypeValidator createTypeValidator() { - return new ClassNameValidator(checker, this, atypeFactory); - } + /** + * Create a new ClassValVisitor. + * + * @param checker the associated type-checker + */ + public ClassValVisitor(BaseTypeChecker checker) { + super(checker); + } + + @Override + protected ClassValAnnotatedTypeFactory createTypeFactory() { + return new ClassValAnnotatedTypeFactory(checker); + } + + @Override + protected BaseTypeValidator createTypeValidator() { + return new ClassNameValidator(checker, this, atypeFactory); + } } class ClassNameValidator extends BaseTypeValidator { - public ClassNameValidator( - BaseTypeChecker checker, - BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } - - /** - * This implementation reports an "illegal.classname" error if the type contains a @ClassVal - * annotation with a string that is not a valid class name. - */ - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - AnnotationMirror classVal = type.getAnnotation(ClassVal.class); - classVal = classVal == null ? type.getAnnotation(ClassBound.class) : classVal; - if (classVal != null) { - List classNames = - ((ClassValAnnotatedTypeFactory) atypeFactory) - .getClassNamesFromAnnotation(classVal); - for (String className : classNames) { - if (!Signatures.isFqBinaryName(className)) { - checker.reportError(tree, "illegal.classname", className, type); - } - } + public ClassNameValidator( + BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } + + /** + * This implementation reports an "illegal.classname" error if the type contains a @ClassVal + * annotation with a string that is not a valid class name. + */ + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + AnnotationMirror classVal = type.getAnnotation(ClassVal.class); + classVal = classVal == null ? type.getAnnotation(ClassBound.class) : classVal; + if (classVal != null) { + List classNames = + ((ClassValAnnotatedTypeFactory) atypeFactory).getClassNamesFromAnnotation(classVal); + for (String className : classNames) { + if (!Signatures.isFqBinaryName(className)) { + checker.reportError(tree, "illegal.classname", className, type); } - return super.visitDeclared(type, tree); + } } + return super.visitDeclared(type, tree); + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java index e30115d7ec4..7decc8dfea2 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java @@ -24,7 +24,21 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; - +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.reflection.qual.Invoke; @@ -42,23 +56,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; - /** * Default implementation of {@link ReflectionResolver}. It resolves calls to: * @@ -71,630 +68,613 @@ */ public class DefaultReflectionResolver implements ReflectionResolver { - /** Message prefix added to verbose reflection messages. */ - public static final String MSG_PREFEX_REFLECTION = "[Reflection] "; - - private final BaseTypeChecker checker; - private final AnnotationProvider provider; - private final ProcessingEnvironment processingEnv; - private final Trees trees; - private final boolean debug; - - public DefaultReflectionResolver( - BaseTypeChecker checker, - MethodValAnnotatedTypeFactory methodValProvider, - boolean debug) { - this.checker = checker; - this.provider = methodValProvider; - this.processingEnv = checker.getProcessingEnvironment(); - this.trees = Trees.instance(processingEnv); - this.debug = debug; + /** Message prefix added to verbose reflection messages. */ + public static final String MSG_PREFEX_REFLECTION = "[Reflection] "; + + private final BaseTypeChecker checker; + private final AnnotationProvider provider; + private final ProcessingEnvironment processingEnv; + private final Trees trees; + private final boolean debug; + + public DefaultReflectionResolver( + BaseTypeChecker checker, MethodValAnnotatedTypeFactory methodValProvider, boolean debug) { + this.checker = checker; + this.provider = methodValProvider; + this.processingEnv = checker.getProcessingEnvironment(); + this.trees = Trees.instance(processingEnv); + this.debug = debug; + } + + @Override + public boolean isReflectiveMethodInvocation(MethodInvocationTree tree) { + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + return (provider.getDeclAnnotation(methodElt, Invoke.class) != null + || provider.getDeclAnnotation(methodElt, NewInstance.class) != null); + } + + @Override + public ParameterizedExecutableType resolveReflectiveCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult) { + assert isReflectiveMethodInvocation(tree); + if (provider.getDeclAnnotation(TreeUtils.elementFromUse(tree), NewInstance.class) != null) { + return resolveConstructorCall(factory, tree, origResult); + } else { + return resolveMethodCall(factory, tree, origResult); } - - @Override - public boolean isReflectiveMethodInvocation(MethodInvocationTree tree) { - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - return (provider.getDeclAnnotation(methodElt, Invoke.class) != null - || provider.getDeclAnnotation(methodElt, NewInstance.class) != null); + } + + /** + * Resolves a call to {@link Method#invoke(Object, Object...)}. + * + * @param factory the {@link AnnotatedTypeFactory} of the underlying type system + * @param tree the method invocation tree that has to be resolved + * @param origResult the original result from {@code factory.methodFromUse} + * @return the resolved type of the call + */ + private ParameterizedExecutableType resolveMethodCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult) { + debugReflection("Try to resolve reflective method call: " + tree); + List possibleMethods = resolveReflectiveMethod(tree, factory); + + // Reflective method could not be resolved + if (possibleMethods.isEmpty()) { + return origResult; } - @Override - public ParameterizedExecutableType resolveReflectiveCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult) { - assert isReflectiveMethodInvocation(tree); - if (provider.getDeclAnnotation(TreeUtils.elementFromUse(tree), NewInstance.class) != null) { - return resolveConstructorCall(factory, tree, origResult); - } else { - return resolveMethodCall(factory, tree, origResult); + Set returnLub = null; + Set receiverGlb = null; + Set paramsGlb = null; + + // Iterate over all possible methods: lub return types, and glb receiver and parameter types + for (MethodInvocationTree resolvedTree : possibleMethods) { + debugReflection("Resolved method invocation: " + resolvedTree); + if (!checkMethodArguments(resolvedTree)) { + debugReflection("Spoofed tree's arguments did not match declaration" + resolvedTree); + // Calling methodFromUse on these sorts of trees will cause an assertion to fail in + // QualifierPolymorphism.PolyCollector.visitArray(...) + continue; + } + ParameterizedExecutableType resolvedResult = factory.methodFromUse(resolvedTree); + + AnnotatedTypeMirror returnType = resolvedResult.executableType.getReturnType(); + TypeMirror returnTM = returnType.getUnderlyingType(); + + // Lub return types + returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); + + // Glb receiver types (actual method receiver is passed as first + // argument to invoke(Object, Object[])) + // Check for static methods whose receiver is null + AnnotatedTypeMirror receiverType = resolvedResult.executableType.getReceiverType(); + if (receiverType == null) { + // If the method is static the first argument to Method.invoke isn't used, so assume + // top. + if (receiverGlb == null) { + receiverGlb = + new AnnotationMirrorSet(factory.getQualifierHierarchy().getTopAnnotations()); } + } else { + TypeMirror receiverTM = receiverType.getUnderlyingType(); + receiverGlb = + glb(receiverGlb, receiverTM, receiverType.getAnnotations(), receiverTM, factory); + } + + // Glb parameter types. All formal parameter types get combined together because + // Method#invoke takes as argument an array of parameter types, so there is no way to + // distinguish the types of different formal parameters. + for (AnnotatedTypeMirror mirror : resolvedResult.executableType.getParameterTypes()) { + TypeMirror mirrorTM = mirror.getUnderlyingType(); + paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); + } + } + + if (returnLub == null) { + // None of the spoofed tree's arguments matched the declared method + return origResult; } - /** - * Resolves a call to {@link Method#invoke(Object, Object...)}. - * - * @param factory the {@link AnnotatedTypeFactory} of the underlying type system - * @param tree the method invocation tree that has to be resolved - * @param origResult the original result from {@code factory.methodFromUse} - * @return the resolved type of the call + /* + * Clear all original (return, receiver, parameter type) annotations and + * set lub/glb annotations from resolved method(s) */ - private ParameterizedExecutableType resolveMethodCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult) { - debugReflection("Try to resolve reflective method call: " + tree); - List possibleMethods = resolveReflectiveMethod(tree, factory); - - // Reflective method could not be resolved - if (possibleMethods.isEmpty()) { - return origResult; - } - Set returnLub = null; - Set receiverGlb = null; - Set paramsGlb = null; - - // Iterate over all possible methods: lub return types, and glb receiver and parameter types - for (MethodInvocationTree resolvedTree : possibleMethods) { - debugReflection("Resolved method invocation: " + resolvedTree); - if (!checkMethodArguments(resolvedTree)) { - debugReflection( - "Spoofed tree's arguments did not match declaration" + resolvedTree); - // Calling methodFromUse on these sorts of trees will cause an assertion to fail in - // QualifierPolymorphism.PolyCollector.visitArray(...) - continue; - } - ParameterizedExecutableType resolvedResult = factory.methodFromUse(resolvedTree); - - AnnotatedTypeMirror returnType = resolvedResult.executableType.getReturnType(); - TypeMirror returnTM = returnType.getUnderlyingType(); - - // Lub return types - returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); - - // Glb receiver types (actual method receiver is passed as first - // argument to invoke(Object, Object[])) - // Check for static methods whose receiver is null - AnnotatedTypeMirror receiverType = resolvedResult.executableType.getReceiverType(); - if (receiverType == null) { - // If the method is static the first argument to Method.invoke isn't used, so assume - // top. - if (receiverGlb == null) { - receiverGlb = - new AnnotationMirrorSet( - factory.getQualifierHierarchy().getTopAnnotations()); - } - } else { - TypeMirror receiverTM = receiverType.getUnderlyingType(); - receiverGlb = - glb( - receiverGlb, - receiverTM, - receiverType.getAnnotations(), - receiverTM, - factory); - } - - // Glb parameter types. All formal parameter types get combined together because - // Method#invoke takes as argument an array of parameter types, so there is no way to - // distinguish the types of different formal parameters. - for (AnnotatedTypeMirror mirror : resolvedResult.executableType.getParameterTypes()) { - TypeMirror mirrorTM = mirror.getUnderlyingType(); - paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); - } - } + // return value + origResult.executableType.getReturnType().clearAnnotations(); + origResult.executableType.getReturnType().addAnnotations(returnLub); - if (returnLub == null) { - // None of the spoofed tree's arguments matched the declared method - return origResult; - } + // receiver type + origResult.executableType.getParameterTypes().get(0).clearAnnotations(); + origResult.executableType.getParameterTypes().get(0).addAnnotations(receiverGlb); - /* - * Clear all original (return, receiver, parameter type) annotations and - * set lub/glb annotations from resolved method(s) - */ - - // return value - origResult.executableType.getReturnType().clearAnnotations(); - origResult.executableType.getReturnType().addAnnotations(returnLub); - - // receiver type - origResult.executableType.getParameterTypes().get(0).clearAnnotations(); - origResult.executableType.getParameterTypes().get(0).addAnnotations(receiverGlb); - - // parameter types - if (paramsGlb != null) { - AnnotatedArrayType origArrayType = - (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(1); - origArrayType.getComponentType().clearAnnotations(); - origArrayType.getComponentType().addAnnotations(paramsGlb); - } + // parameter types + if (paramsGlb != null) { + AnnotatedArrayType origArrayType = + (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(1); + origArrayType.getComponentType().clearAnnotations(); + origArrayType.getComponentType().addAnnotations(paramsGlb); + } - debugReflection("Resolved annotations: " + origResult.executableType); - return origResult; + debugReflection("Resolved annotations: " + origResult.executableType); + return origResult; + } + + /** + * Checks that arguments of a method invocation are consistent with their corresponding + * parameters. + * + * @param resolvedTree a method invocation + * @return true if arguments are consistent with parameters + */ + private boolean checkMethodArguments(MethodInvocationTree resolvedTree) { + // type.getKind() == actualType.getKind() + ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); + return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); + } + + /** + * Checks that arguments of a constructor invocation are consistent with their corresponding + * parameters. + * + * @param resolvedTree a constructor invocation + * @return true if arguments are consistent with parameters + */ + private boolean checkNewClassArguments(NewClassTree resolvedTree) { + ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); + return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); + } + + /** + * Checks that argument are consistent with their corresponding parameter types. Common code used + * by {@link #checkMethodArguments} and {@link #checkNewClassArguments}. + * + * @param parameters formal parameters + * @param arguments actual arguments + * @return true if argument are consistent with their corresponding parameter types + */ + private boolean checkArguments( + List parameters, List arguments) { + if (parameters.size() != arguments.size()) { + return false; } - /** - * Checks that arguments of a method invocation are consistent with their corresponding - * parameters. - * - * @param resolvedTree a method invocation - * @return true if arguments are consistent with parameters - */ - private boolean checkMethodArguments(MethodInvocationTree resolvedTree) { - // type.getKind() == actualType.getKind() - ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); - return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); + for (int i = 0; i < parameters.size(); i++) { + VariableElement param = parameters.get(i); + ExpressionTree arg = arguments.get(i); + TypeMirror argType = TreeUtils.typeOf(arg); + TypeMirror paramType = param.asType(); + if (argType.getKind() == TypeKind.ARRAY && paramType.getKind() != argType.getKind()) { + return false; + } } - /** - * Checks that arguments of a constructor invocation are consistent with their corresponding - * parameters. - * - * @param resolvedTree a constructor invocation - * @return true if arguments are consistent with parameters - */ - private boolean checkNewClassArguments(NewClassTree resolvedTree) { - ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); - return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); + return true; + } + + /** + * Resolves a call to {@link Constructor#newInstance(Object...)}. + * + * @param factory the {@link AnnotatedTypeFactory} of the underlying type system + * @param tree the method invocation tree (representing a constructor call) that has to be + * resolved + * @param origResult the original result from {@code factory.methodFromUse} + * @return the resolved type of the call + */ + private ParameterizedExecutableType resolveConstructorCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult) { + debugReflection("Try to resolve reflective constructor call: " + tree); + List possibleConstructors = resolveReflectiveConstructor(tree, factory); + + // Reflective constructor could not be resolved + if (possibleConstructors.isEmpty()) { + return origResult; } - /** - * Checks that argument are consistent with their corresponding parameter types. Common code - * used by {@link #checkMethodArguments} and {@link #checkNewClassArguments}. - * - * @param parameters formal parameters - * @param arguments actual arguments - * @return true if argument are consistent with their corresponding parameter types + Set returnLub = null; + Set paramsGlb = null; + + // Iterate over all possible constructors: lub return types and glb parameter types + for (JCNewClass resolvedTree : possibleConstructors) { + debugReflection("Resolved constructor invocation: " + resolvedTree); + if (!checkNewClassArguments(resolvedTree)) { + debugReflection("Spoofed tree's arguments did not match declaration" + resolvedTree); + // Calling methodFromUse on these sorts of trees will cause an assertion to fail in + // QualifierPolymorphism.PolyCollector.visitArray(...) + continue; + } + ParameterizedExecutableType resolvedResult = factory.constructorFromUse(resolvedTree); + AnnotatedExecutableType executableType = resolvedResult.executableType; + AnnotatedTypeMirror returnType = executableType.getReturnType(); + TypeMirror returnTM = returnType.getUnderlyingType(); + + // Lub return types + returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); + + // Glb parameter types + for (AnnotatedTypeMirror mirror : executableType.getParameterTypes()) { + TypeMirror mirrorTM = mirror.getUnderlyingType(); + paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); + } + } + if (returnLub == null) { + // None of the spoofed tree's arguments matched the declared method + return origResult; + } + /* + * Clear all original (return, parameter type) annotations and set + * lub/glb annotations from resolved constructors. */ - private boolean checkArguments( - List parameters, List arguments) { - if (parameters.size() != arguments.size()) { - return false; - } - for (int i = 0; i < parameters.size(); i++) { - VariableElement param = parameters.get(i); - ExpressionTree arg = arguments.get(i); - TypeMirror argType = TreeUtils.typeOf(arg); - TypeMirror paramType = param.asType(); - if (argType.getKind() == TypeKind.ARRAY && paramType.getKind() != argType.getKind()) { - return false; - } - } + // return value + origResult.executableType.getReturnType().clearAnnotations(); + origResult.executableType.getReturnType().addAnnotations(returnLub); - return true; + // parameter types + if (paramsGlb != null) { + AnnotatedArrayType origArrayType = + (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(0); + origArrayType.getComponentType().clearAnnotations(); + origArrayType.getComponentType().addAnnotations(paramsGlb); } - /** - * Resolves a call to {@link Constructor#newInstance(Object...)}. - * - * @param factory the {@link AnnotatedTypeFactory} of the underlying type system - * @param tree the method invocation tree (representing a constructor call) that has to be - * resolved - * @param origResult the original result from {@code factory.methodFromUse} - * @return the resolved type of the call - */ - private ParameterizedExecutableType resolveConstructorCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult) { - debugReflection("Try to resolve reflective constructor call: " + tree); - List possibleConstructors = resolveReflectiveConstructor(tree, factory); - - // Reflective constructor could not be resolved - if (possibleConstructors.isEmpty()) { - return origResult; - } + debugReflection("Resolved annotations: " + origResult.executableType); + return origResult; + } + + /** + * Resolves a reflective method call and returns all possible corresponding method calls. + * + * @param tree the MethodInvocationTree AST node that is to be resolved (Method.invoke) + * @return a (potentially empty) list of all resolved MethodInvocationTrees + */ + private List resolveReflectiveMethod( + MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { + assert isReflectiveMethodInvocation(tree); + JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + TreeMaker make = TreeMaker.instance(context); + TreePath path = reflectionFactory.getPath(tree); + JavacScope scope = (JavacScope) trees.getScope(path); + Env env = scope.getEnv(); + + boolean unknown = isUnknownMethod(tree); + + AnnotationMirror estimate = getMethodVal(tree); + + if (estimate == null) { + debugReflection("MethodVal is unknown for: " + tree); + debugReflection("UnknownMethod annotation: " + unknown); + return Collections.emptyList(); + } - Set returnLub = null; - Set paramsGlb = null; - - // Iterate over all possible constructors: lub return types and glb parameter types - for (JCNewClass resolvedTree : possibleConstructors) { - debugReflection("Resolved constructor invocation: " + resolvedTree); - if (!checkNewClassArguments(resolvedTree)) { - debugReflection( - "Spoofed tree's arguments did not match declaration" + resolvedTree); - // Calling methodFromUse on these sorts of trees will cause an assertion to fail in - // QualifierPolymorphism.PolyCollector.visitArray(...) - continue; - } - ParameterizedExecutableType resolvedResult = factory.constructorFromUse(resolvedTree); - AnnotatedExecutableType executableType = resolvedResult.executableType; - AnnotatedTypeMirror returnType = executableType.getReturnType(); - TypeMirror returnTM = returnType.getUnderlyingType(); - - // Lub return types - returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); - - // Glb parameter types - for (AnnotatedTypeMirror mirror : executableType.getParameterTypes()) { - TypeMirror mirrorTM = mirror.getUnderlyingType(); - paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); - } + debugReflection("MethodVal type system annotations: " + estimate); + + List listClassNames = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValClassNameElement, String.class); + List listMethodNames = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValMethodNameElement, String.class); + List listParamLengths = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValParamsElement, Integer.class); + assert listClassNames.size() == listMethodNames.size() + && listClassNames.size() == listParamLengths.size(); + + List methodInvocations = new ArrayList<>(); + for (int i = 0; i < listClassNames.size(); ++i) { + String className = listClassNames.get(i); + String methodName = listMethodNames.get(i); + int paramLength = listParamLengths.get(i); + + // Get receiver, which is always the first argument of the invoke method + JCExpression receiver = methodInvocation.args.head; + // The remaining list contains the arguments + com.sun.tools.javac.util.List args = methodInvocation.args.tail; + + // Resolve the Symbol(s) for the current method + for (Symbol symbol : getMethodSymbolsfor(className, methodName, paramLength, env)) { + if (!processingEnv.getTypeUtils().isSubtype(receiver.type, symbol.owner.type)) { + continue; } - if (returnLub == null) { - // None of the spoofed tree's arguments matched the declared method - return origResult; - } - /* - * Clear all original (return, parameter type) annotations and set - * lub/glb annotations from resolved constructors. - */ - - // return value - origResult.executableType.getReturnType().clearAnnotations(); - origResult.executableType.getReturnType().addAnnotations(returnLub); - - // parameter types - if (paramsGlb != null) { - AnnotatedArrayType origArrayType = - (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(0); - origArrayType.getComponentType().clearAnnotations(); - origArrayType.getComponentType().addAnnotations(paramsGlb); + if ((symbol.flags() & Flags.PUBLIC) > 0) { + debugReflection("Resolved public method: " + symbol.owner + "." + symbol); + } else { + debugReflection("Resolved non-public method: " + symbol.owner + "." + symbol); } - debugReflection("Resolved annotations: " + origResult.executableType); - return origResult; - } - - /** - * Resolves a reflective method call and returns all possible corresponding method calls. - * - * @param tree the MethodInvocationTree AST node that is to be resolved (Method.invoke) - * @return a (potentially empty) list of all resolved MethodInvocationTrees - */ - private List resolveReflectiveMethod( - MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { - assert isReflectiveMethodInvocation(tree); - JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - TreeMaker make = TreeMaker.instance(context); - TreePath path = reflectionFactory.getPath(tree); - JavacScope scope = (JavacScope) trees.getScope(path); - Env env = scope.getEnv(); + JCExpression method = TreeUtils.Select(make, receiver, symbol); + args = getCorrectedArgs(symbol, args); + // Build method invocation tree depending on the number of + // parameters + JCMethodInvocation syntTree = paramLength > 0 ? make.App(method, args) : make.App(method); - boolean unknown = isUnknownMethod(tree); - - AnnotationMirror estimate = getMethodVal(tree); - - if (estimate == null) { - debugReflection("MethodVal is unknown for: " + tree); - debugReflection("UnknownMethod annotation: " + unknown); - return Collections.emptyList(); + // add method invocation tree to the list of possible method invocations + methodInvocations.add(syntTree); + } + } + return methodInvocations; + } + + private com.sun.tools.javac.util.List getCorrectedArgs( + Symbol symbol, com.sun.tools.javac.util.List args) { + if (symbol.getKind() == ElementKind.METHOD) { + MethodSymbol method = ((MethodSymbol) symbol); + // neg means too many arg, + // pos means to few args + int diff = method.getParameters().size() - args.size(); + if (diff > 0) { + // means too few args + int origArgSize = args.size(); + for (int i = 0; i < diff; i++) { + args = args.append(args.get(i % origArgSize)); } - - debugReflection("MethodVal type system annotations: " + estimate); - - List listClassNames = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValClassNameElement, String.class); - List listMethodNames = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValMethodNameElement, String.class); - List listParamLengths = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValParamsElement, Integer.class); - assert listClassNames.size() == listMethodNames.size() - && listClassNames.size() == listParamLengths.size(); - - List methodInvocations = new ArrayList<>(); - for (int i = 0; i < listClassNames.size(); ++i) { - String className = listClassNames.get(i); - String methodName = listMethodNames.get(i); - int paramLength = listParamLengths.get(i); - - // Get receiver, which is always the first argument of the invoke method - JCExpression receiver = methodInvocation.args.head; - // The remaining list contains the arguments - com.sun.tools.javac.util.List args = methodInvocation.args.tail; - - // Resolve the Symbol(s) for the current method - for (Symbol symbol : getMethodSymbolsfor(className, methodName, paramLength, env)) { - if (!processingEnv.getTypeUtils().isSubtype(receiver.type, symbol.owner.type)) { - continue; - } - if ((symbol.flags() & Flags.PUBLIC) > 0) { - debugReflection("Resolved public method: " + symbol.owner + "." + symbol); - } else { - debugReflection("Resolved non-public method: " + symbol.owner + "." + symbol); - } - - JCExpression method = TreeUtils.Select(make, receiver, symbol); - args = getCorrectedArgs(symbol, args); - // Build method invocation tree depending on the number of - // parameters - JCMethodInvocation syntTree = - paramLength > 0 ? make.App(method, args) : make.App(method); - - // add method invocation tree to the list of possible method invocations - methodInvocations.add(syntTree); - } + } else if (diff < 0) { + // means too many args + com.sun.tools.javac.util.List tmp = com.sun.tools.javac.util.List.nil(); + for (int i = 0; i < method.getParameters().size(); i++) { + tmp = tmp.append(args.get(i)); } - return methodInvocations; + args = tmp; + } } - - private com.sun.tools.javac.util.List getCorrectedArgs( - Symbol symbol, com.sun.tools.javac.util.List args) { - if (symbol.getKind() == ElementKind.METHOD) { - MethodSymbol method = ((MethodSymbol) symbol); - // neg means too many arg, - // pos means to few args - int diff = method.getParameters().size() - args.size(); - if (diff > 0) { - // means too few args - int origArgSize = args.size(); - for (int i = 0; i < diff; i++) { - args = args.append(args.get(i % origArgSize)); - } - } else if (diff < 0) { - // means too many args - com.sun.tools.javac.util.List tmp = - com.sun.tools.javac.util.List.nil(); - for (int i = 0; i < method.getParameters().size(); i++) { - tmp = tmp.append(args.get(i)); - } - args = tmp; - } - } - return args; + return args; + } + + /** + * Resolves a reflective constructor call and returns all possible corresponding constructor + * calls. + * + * @param tree the MethodInvocationTree AST node that is to be resolved (Constructor.newInstance) + * @return a (potentially empty) list of all resolved MethodInvocationTrees + */ + private List resolveReflectiveConstructor( + MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { + assert isReflectiveMethodInvocation(tree); + JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + TreeMaker make = TreeMaker.instance(context); + TreePath path = reflectionFactory.getPath(tree); + JavacScope scope = (JavacScope) trees.getScope(path); + Env env = scope.getEnv(); + + AnnotationMirror estimate = getMethodVal(tree); + + if (estimate == null) { + debugReflection("MethodVal is unknown for: " + tree); + debugReflection("UnknownMethod annotation: " + isUnknownMethod(tree)); + return Collections.emptyList(); } - /** - * Resolves a reflective constructor call and returns all possible corresponding constructor - * calls. - * - * @param tree the MethodInvocationTree AST node that is to be resolved - * (Constructor.newInstance) - * @return a (potentially empty) list of all resolved MethodInvocationTrees - */ - private List resolveReflectiveConstructor( - MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { - assert isReflectiveMethodInvocation(tree); - JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - TreeMaker make = TreeMaker.instance(context); - TreePath path = reflectionFactory.getPath(tree); - JavacScope scope = (JavacScope) trees.getScope(path); - Env env = scope.getEnv(); - - AnnotationMirror estimate = getMethodVal(tree); - - if (estimate == null) { - debugReflection("MethodVal is unknown for: " + tree); - debugReflection("UnknownMethod annotation: " + isUnknownMethod(tree)); - return Collections.emptyList(); - } + debugReflection("MethodVal type system annotations: " + estimate); - debugReflection("MethodVal type system annotations: " + estimate); + List listClassNames = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValClassNameElement, String.class); + List listParamLengths = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValParamsElement, Integer.class); + assert listClassNames.size() == listParamLengths.size(); - List listClassNames = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValClassNameElement, String.class); - List listParamLengths = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValParamsElement, Integer.class); - assert listClassNames.size() == listParamLengths.size(); + List constructorInvocations = new ArrayList<>(); + for (int i = 0; i < listClassNames.size(); ++i) { + String className = listClassNames.get(i); + int paramLength = listParamLengths.get(i); - List constructorInvocations = new ArrayList<>(); - for (int i = 0; i < listClassNames.size(); ++i) { - String className = listClassNames.get(i); - int paramLength = listParamLengths.get(i); + // Resolve the Symbol for the current constructor + for (Symbol symbol : getConstructorSymbolsfor(className, paramLength, env)) { + debugReflection("Resolved constructor: " + symbol.owner + "." + symbol); - // Resolve the Symbol for the current constructor - for (Symbol symbol : getConstructorSymbolsfor(className, paramLength, env)) { - debugReflection("Resolved constructor: " + symbol.owner + "." + symbol); + JCNewClass syntTree = (JCNewClass) make.Create(symbol, methodInvocation.args); - JCNewClass syntTree = (JCNewClass) make.Create(symbol, methodInvocation.args); + // add constructor invocation tree to the list of possible constructor invocations + constructorInvocations.add(syntTree); + } + } + return constructorInvocations; + } + + private AnnotationMirror getMethodVal(MethodInvocationTree tree) { + return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), MethodVal.class); + } + + /** Returns true if the receiver's type is @UnknownMethod. */ + private boolean isUnknownMethod(MethodInvocationTree tree) { + return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), UnknownMethod.class) + != null; + } + + /** + * Get set of MethodSymbols based on class name, method name, and parameter length. + * + * @param className the class that contains the method + * @param methodName the method's name + * @param paramLength the number of parameters + * @param env the environment + * @return the (potentially empty) set of corresponding method Symbol(s) + */ + private List getMethodSymbolsfor( + String className, String methodName, int paramLength, Env env) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Resolve resolve = Resolve.instance(context); + Names names = Names.instance(context); + + Symbol sym = getSymbol(className, env, names, resolve); + if (!sym.exists()) { + debugReflection("Unable to resolve class: " + className); + return Collections.emptyList(); + } - // add constructor invocation tree to the list of possible constructor invocations - constructorInvocations.add(syntTree); - } + // The common case is probably that `result` is a singleton at method exit. + List result = new ArrayList<>(); + ClassSymbol classSym = (ClassSymbol) sym; + while (classSym != null) { + for (Symbol s : getEnclosedElements(classSym)) { + // check all member methods + if (s.getKind() == ElementKind.METHOD) { + // Check for method name and number of arguments + if (names.fromString(methodName) == s.name + && ((MethodSymbol) s).getParameters().size() == paramLength) { + result.add(s); + } } - return constructorInvocations; + } + if (!result.isEmpty()) { + break; + } + Type t = classSym.getSuperclass(); + if (!t.hasTag(TypeTag.CLASS) || t.isErroneous()) { + break; + } + classSym = (ClassSymbol) t.tsym; } - - private AnnotationMirror getMethodVal(MethodInvocationTree tree) { - return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), MethodVal.class); + if (result.isEmpty()) { + debugReflection("Unable to resolve method: " + className + "@" + methodName); } - - /** Returns true if the receiver's type is @UnknownMethod. */ - private boolean isUnknownMethod(MethodInvocationTree tree) { - return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), UnknownMethod.class) - != null; + return result; + } + + /** + * Get set of Symbols for constructors based on class name and parameter length. + * + * @return the (potentially empty) set of corresponding constructor Symbol(s) + */ + private List getConstructorSymbolsfor( + String className, int paramLength, Env env) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Resolve resolve = Resolve.instance(context); + Names names = Names.instance(context); + + Symbol symClass = getSymbol(className, env, names, resolve); + if (!symClass.exists()) { + debugReflection("Unable to resolve class: " + className); + return Collections.emptyList(); } - /** - * Get set of MethodSymbols based on class name, method name, and parameter length. - * - * @param className the class that contains the method - * @param methodName the method's name - * @param paramLength the number of parameters - * @param env the environment - * @return the (potentially empty) set of corresponding method Symbol(s) - */ - private List getMethodSymbolsfor( - String className, String methodName, int paramLength, Env env) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Resolve resolve = Resolve.instance(context); - Names names = Names.instance(context); - - Symbol sym = getSymbol(className, env, names, resolve); - if (!sym.exists()) { - debugReflection("Unable to resolve class: " + className); - return Collections.emptyList(); - } - - // The common case is probably that `result` is a singleton at method exit. - List result = new ArrayList<>(); - ClassSymbol classSym = (ClassSymbol) sym; - while (classSym != null) { - for (Symbol s : getEnclosedElements(classSym)) { - // check all member methods - if (s.getKind() == ElementKind.METHOD) { - // Check for method name and number of arguments - if (names.fromString(methodName) == s.name - && ((MethodSymbol) s).getParameters().size() == paramLength) { - result.add(s); - } - } - } - if (!result.isEmpty()) { - break; - } - Type t = classSym.getSuperclass(); - if (!t.hasTag(TypeTag.CLASS) || t.isErroneous()) { - break; - } - classSym = (ClassSymbol) t.tsym; + // TODO: Should this be used instead of the below?? + ElementFilter.constructorsIn(getEnclosedElements(symClass)); + + // The common case is probably that there is one constructor of the given parameter length. + List result = new ArrayList<>(2); + for (Symbol s : getEnclosedElements(symClass)) { + // Check all constructors + if (s.getKind() == ElementKind.CONSTRUCTOR) { + // Check for number of parameters + if (((MethodSymbol) s).getParameters().size() == paramLength) { + result.add(s); } - if (result.isEmpty()) { - debugReflection("Unable to resolve method: " + className + "@" + methodName); - } - return result; + } } - - /** - * Get set of Symbols for constructors based on class name and parameter length. - * - * @return the (potentially empty) set of corresponding constructor Symbol(s) - */ - private List getConstructorSymbolsfor( - String className, int paramLength, Env env) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Resolve resolve = Resolve.instance(context); - Names names = Names.instance(context); - - Symbol symClass = getSymbol(className, env, names, resolve); - if (!symClass.exists()) { - debugReflection("Unable to resolve class: " + className); - return Collections.emptyList(); - } - - // TODO: Should this be used instead of the below?? - ElementFilter.constructorsIn(getEnclosedElements(symClass)); - - // The common case is probably that there is one constructor of the given parameter length. - List result = new ArrayList<>(2); - for (Symbol s : getEnclosedElements(symClass)) { - // Check all constructors - if (s.getKind() == ElementKind.CONSTRUCTOR) { - // Check for number of parameters - if (((MethodSymbol) s).getParameters().size() == paramLength) { - result.add(s); - } - } - } - if (result.isEmpty()) { - debugReflection("Unable to resolve constructor!"); - } - return result; + if (result.isEmpty()) { + debugReflection("Unable to resolve constructor!"); } - - private Symbol getSymbol(String className, Env env, Names names, Resolve resolve) { - Method loadClass; - try { - loadClass = - Resolve.class.getDeclaredMethod( - "loadClass", Env.class, Name.class, RecoveryLoadClass.class); - loadClass.setAccessible(true); - } catch (SecurityException | NoSuchMethodException | IllegalArgumentException e) { - // A problem with javac is serious and must be reported. - throw new BugInCF("Error in obtaining reflective method.", e); - } - try { - RecoveryLoadClass noRecovery = (e, n) -> null; - return (Symbol) loadClass.invoke(resolve, env, names.fromString(className), noRecovery); - } catch (SecurityException - | IllegalAccessException - | IllegalArgumentException - | InvocationTargetException e) { - // A problem with javac is serious and must be reported. - throw new BugInCF("Error in invoking reflective method.", e); - } + return result; + } + + private Symbol getSymbol(String className, Env env, Names names, Resolve resolve) { + Method loadClass; + try { + loadClass = + Resolve.class.getDeclaredMethod( + "loadClass", Env.class, Name.class, RecoveryLoadClass.class); + loadClass.setAccessible(true); + } catch (SecurityException | NoSuchMethodException | IllegalArgumentException e) { + // A problem with javac is serious and must be reported. + throw new BugInCF("Error in obtaining reflective method.", e); } - - /** - * Determine the enclosed elements for an element. This wrapper is useful to avoid a signature - * change in the called method. - * - * @param sym the element - * @return the enclosed elements - */ - @SuppressWarnings("ASTHelpersSuggestions") // Use local helper. - private static List getEnclosedElements(Symbol sym) { - return sym.getEnclosedElements(); + try { + RecoveryLoadClass noRecovery = (e, n) -> null; + return (Symbol) loadClass.invoke(resolve, env, names.fromString(className), noRecovery); + } catch (SecurityException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { + // A problem with javac is serious and must be reported. + throw new BugInCF("Error in invoking reflective method.", e); } - - /** - * Build lub of the two types (represented by sets {@code set1} and {@code set2}) using the - * provided AnnotatedTypeFactory. - * - *

If {@code set1} is {@code null} or empty, {@code set2} is returned. - * - * @param set1 the first type - * @param tm1 the type that is annotated by qualifier1 - * @param set2 the second type - * @param tm2 the type that is annotated by qualifier2 - * @param atypeFactory the type factory - * @return the lub of the two types - */ - private Set lub( - @Nullable Set set1, - TypeMirror tm1, - Set set2, - TypeMirror tm2, - AnnotatedTypeFactory atypeFactory) { - if (set1 == null || set1.isEmpty()) { - return set2; - } else { - return atypeFactory - .getQualifierHierarchy() - .leastUpperBoundsShallow(set1, tm1, set2, tm2); - } + } + + /** + * Determine the enclosed elements for an element. This wrapper is useful to avoid a signature + * change in the called method. + * + * @param sym the element + * @return the enclosed elements + */ + @SuppressWarnings("ASTHelpersSuggestions") // Use local helper. + private static List getEnclosedElements(Symbol sym) { + return sym.getEnclosedElements(); + } + + /** + * Build lub of the two types (represented by sets {@code set1} and {@code set2}) using the + * provided AnnotatedTypeFactory. + * + *

If {@code set1} is {@code null} or empty, {@code set2} is returned. + * + * @param set1 the first type + * @param tm1 the type that is annotated by qualifier1 + * @param set2 the second type + * @param tm2 the type that is annotated by qualifier2 + * @param atypeFactory the type factory + * @return the lub of the two types + */ + private Set lub( + @Nullable Set set1, + TypeMirror tm1, + Set set2, + TypeMirror tm2, + AnnotatedTypeFactory atypeFactory) { + if (set1 == null || set1.isEmpty()) { + return set2; + } else { + return atypeFactory.getQualifierHierarchy().leastUpperBoundsShallow(set1, tm1, set2, tm2); } - - /** - * Build glb of the two types (represented by sets {@code set1} and {@code set2}) using the - * provided AnnotatedTypeFactory. - * - *

If {@code set1} is {@code null} or empty, {@code set2} is returned. - * - * @param set1 the first type - * @param tm1 the type that is annotated by qualifier1 - * @param set2 the second type - * @param tm2 the type that is annotated by qualifier2 - * @param atypeFactory the type factory - * @return the glb of the two types - */ - private Set glb( - @Nullable Set set1, - TypeMirror tm1, - Set set2, - TypeMirror tm2, - AnnotatedTypeFactory atypeFactory) { - if (set1 == null || set1.isEmpty()) { - return set2; - } else { - return atypeFactory - .getQualifierHierarchy() - .greatestLowerBoundsShallow(set1, tm1, set2, tm2); - } + } + + /** + * Build glb of the two types (represented by sets {@code set1} and {@code set2}) using the + * provided AnnotatedTypeFactory. + * + *

If {@code set1} is {@code null} or empty, {@code set2} is returned. + * + * @param set1 the first type + * @param tm1 the type that is annotated by qualifier1 + * @param set2 the second type + * @param tm2 the type that is annotated by qualifier2 + * @param atypeFactory the type factory + * @return the glb of the two types + */ + private Set glb( + @Nullable Set set1, + TypeMirror tm1, + Set set2, + TypeMirror tm2, + AnnotatedTypeFactory atypeFactory) { + if (set1 == null || set1.isEmpty()) { + return set2; + } else { + return atypeFactory.getQualifierHierarchy().greatestLowerBoundsShallow(set1, tm1, set2, tm2); } - - /** - * Reports debug information about the reflection resolution iff the corresponding debug flag is - * set. - * - * @param msg the debug message - */ - private void debugReflection(String msg) { - if (debug) { - checker.message(javax.tools.Diagnostic.Kind.NOTE, MSG_PREFEX_REFLECTION + msg); - } + } + + /** + * Reports debug information about the reflection resolution iff the corresponding debug flag is + * set. + * + * @param msg the debug message + */ + private void debugReflection(String msg) { + if (debug) { + checker.message(javax.tools.Diagnostic.Kind.NOTE, MSG_PREFEX_REFLECTION + msg); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java index 14dfdbf6b7d..94d8a118cc1 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java @@ -2,7 +2,18 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -28,431 +39,406 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; - /** AnnotatedTypeFactory for the MethodVal Checker. */ public class MethodValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** {@link UnknownMethod} annotation mirror. */ - private final AnnotationMirror UNKNOWN_METHOD = - AnnotationBuilder.fromClass(elements, UnknownMethod.class); + /** {@link UnknownMethod} annotation mirror. */ + private final AnnotationMirror UNKNOWN_METHOD = + AnnotationBuilder.fromClass(elements, UnknownMethod.class); - /** An array length that represents that the length is unknown. */ - private static final int UNKNOWN_PARAM_LENGTH = -1; + /** An array length that represents that the length is unknown. */ + private static final int UNKNOWN_PARAM_LENGTH = -1; - /** A list containing just {@link #UNKNOWN_PARAM_LENGTH}. */ - private static final List UNKNOWN_PARAM_LENGTH_LIST = - Collections.singletonList(UNKNOWN_PARAM_LENGTH); + /** A list containing just {@link #UNKNOWN_PARAM_LENGTH}. */ + private static final List UNKNOWN_PARAM_LENGTH_LIST = + Collections.singletonList(UNKNOWN_PARAM_LENGTH); - /** A list containing just 0. */ - private static List ZERO_LIST = Collections.singletonList(0); + /** A list containing just 0. */ + private static List ZERO_LIST = Collections.singletonList(0); - /** A list containing just 1. */ - private static List ONE_LIST = Collections.singletonList(1); + /** A list containing just 1. */ + private static List ONE_LIST = Collections.singletonList(1); - /** An empty String list. */ - private static List EMPTY_STRING_LIST = Collections.emptyList(); + /** An empty String list. */ + private static List EMPTY_STRING_LIST = Collections.emptyList(); - /** The ArrayLen.value argument/element. */ - public final ExecutableElement arrayLenValueElement = - TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); + /** The ArrayLen.value argument/element. */ + public final ExecutableElement arrayLenValueElement = + TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); - /** The ClassBound.value argument/element. */ - public final ExecutableElement classBoundValueElement = - TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); + /** The ClassBound.value argument/element. */ + public final ExecutableElement classBoundValueElement = + TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); - /** The ClassVal.value argument/element. */ - public final ExecutableElement classValValueElement = - TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); + /** The ClassVal.value argument/element. */ + public final ExecutableElement classValValueElement = + TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); - /** The StringVal.value argument/element. */ - public final ExecutableElement stringValValueElement = - TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); + /** The StringVal.value argument/element. */ + public final ExecutableElement stringValValueElement = + TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); + + /** + * Create a new MethodValAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this factory + */ + public MethodValAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + if (this.getClass() == MethodValAnnotatedTypeFactory.class) { + this.postInit(); + } + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList(MethodVal.class, MethodValBottom.class, UnknownMethod.class)); + } + + @Override + protected void initializeReflectionResolution() { + boolean debug = "debug".equals(checker.getOption("resolveReflection")); + reflectionResolver = new DefaultReflectionResolver(checker, this, debug); + } + + /** + * Returns the methods that a {@code @MethodVal} represents. + * + * @param methodValAnno a {@code @MethodVal} annotation + * @return the methods that the given {@code @MethodVal} represents + */ + List getListOfMethodSignatures(AnnotationMirror methodValAnno) { + List methodNames = + AnnotationUtils.getElementValueArray( + methodValAnno, methodValMethodNameElement, String.class); + List classNames = + AnnotationUtils.getElementValueArray( + methodValAnno, methodValClassNameElement, String.class); + List params = + AnnotationUtils.getElementValueArray(methodValAnno, methodValParamsElement, Integer.class); + List list = new ArrayList<>(methodNames.size()); + for (int i = 0; i < methodNames.size(); i++) { + list.add(new MethodSignature(classNames.get(i), methodNames.get(i), params.get(i))); + } + return list; + } + + /** + * Creates a {@code @MethodVal} annotation. + * + * @param sigs the method signatures that the result should represent + * @return a {@code @MethodVal} annotation that represents {@code sigs} + */ + private AnnotationMirror createMethodVal(Collection sigs) { + int size = sigs.size(); + List classNames = new ArrayList<>(size); + List methodNames = new ArrayList<>(size); + List params = new ArrayList<>(size); + for (MethodSignature sig : sigs) { + classNames.add(sig.className); + methodNames.add(sig.methodName); + params.add(sig.params); + } + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MethodVal.class); + builder.setValue("className", classNames); + builder.setValue("methodName", methodNames); + builder.setValue("params", params); + return builder.build(); + } + + /** + * Returns a list of class names for the given tree using the Class Val Checker. + * + * @param tree an ExpressionTree whose class names are requested + * @param mustBeExact whether @ClassBound may be read to produce the result; if false, + * only @ClassVal may be read + * @return list of class names or the empty list if no class names were found + */ + private List getClassNamesFromClassValChecker(ExpressionTree tree, boolean mustBeExact) { + ClassValAnnotatedTypeFactory classValATF = getTypeFactoryOfSubchecker(ClassValChecker.class); + AnnotatedTypeMirror classAnno = classValATF.getAnnotatedType(tree); + + AnnotationMirror classValAnno = classAnno.getAnnotation(ClassVal.class); + if (classValAnno != null) { + return AnnotationUtils.getElementValueArray(classValAnno, classValValueElement, String.class); + } else if (mustBeExact) { + return Collections.emptyList(); + } + + AnnotationMirror classBoundAnno = classAnno.getAnnotation(ClassBound.class); + if (classBoundAnno != null) { + return AnnotationUtils.getElementValueArray( + classBoundAnno, classBoundValueElement, String.class); + } else { + return Collections.emptyList(); + } + } + + /** + * Returns the string values for the argument passed. The String Values are estimated using the + * Value Checker. + * + * @param arg an ExpressionTree whose string values are sought + * @return string values of arg or the empty list if no values were found + */ + private List getMethodNamesFromStringArg(ExpressionTree arg) { + ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(arg); + AnnotationMirror annotation = valueAnno.getAnnotation(StringVal.class); + if (annotation != null) { + return AnnotationUtils.getElementValueArray(annotation, stringValValueElement, String.class); + } else { + return EMPTY_STRING_LIST; + } + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new MethodValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** MethodValQualifierHierarchy. */ + protected class MethodValQualifierHierarchy extends ElementQualifierHierarchy { /** - * Create a new MethodValAnnotatedTypeFactory. + * Creates a MethodValQualifierHierarchy from the given classes. * - * @param checker the type-checker associated with this factory + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils */ - public MethodValAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - if (this.getClass() == MethodValAnnotatedTypeFactory.class) { - this.postInit(); - } + protected MethodValQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, MethodValAnnotatedTypeFactory.this); } + /* + * Determines the least upper bound of a1 and a2. If both are MethodVal + * annotations, then the least upper bound is the result of + * concatenating all value lists of a1 and a2. + */ @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList(MethodVal.class, MethodValBottom.class, UnknownMethod.class)); + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } else if (AnnotationUtils.areSameByName(a1, a2)) { + List a1Sigs = getListOfMethodSignatures(a1); + List a2Sigs = getListOfMethodSignatures(a2); + List lubSigs = CollectionsPlume.listUnion(a1Sigs, a2Sigs); + AnnotationMirror result = createMethodVal(lubSigs); + return result; + } + return null; } @Override - protected void initializeReflectionResolution() { - boolean debug = "debug".equals(checker.getOption("resolveReflection")); - reflectionResolver = new DefaultReflectionResolver(checker, this, debug); + public @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } else if (AnnotationUtils.areSameByName(a1, a2)) { + List a1Sigs = getListOfMethodSignatures(a1); + List a2Sigs = getListOfMethodSignatures(a2); + List lubSigs = CollectionsPlume.listIntersection(a1Sigs, a2Sigs); + AnnotationMirror result = createMethodVal(lubSigs); + return result; + } + return null; } - /** - * Returns the methods that a {@code @MethodVal} represents. - * - * @param methodValAnno a {@code @MethodVal} annotation - * @return the methods that the given {@code @MethodVal} represents + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (AnnotationUtils.areSame(subAnno, superAnno) + || areSameByClass(superAnno, UnknownMethod.class) + || areSameByClass(subAnno, MethodValBottom.class)) { + return true; + } + if (areSameByClass(subAnno, UnknownMethod.class) + || areSameByClass(superAnno, MethodValBottom.class)) { + return false; + } + assert areSameByClass(subAnno, MethodVal.class) && areSameByClass(superAnno, MethodVal.class) + : "Unexpected annotation in MethodVal"; + List subSignatures = getListOfMethodSignatures(subAnno); + List superSignatures = getListOfMethodSignatures(superAnno); + return superSignatures.containsAll(subSignatures); + } + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new MethodValTreeAnnotator(this), super.createTreeAnnotator()); + } + + /** TreeAnnotator with the visitMethodInvocation method overridden. */ + protected class MethodValTreeAnnotator extends TreeAnnotator { + + protected MethodValTreeAnnotator(MethodValAnnotatedTypeFactory factory) { + super(factory); + } + + /* + * Special handling of getMethod and getDeclaredMethod calls. Attempts + * to get the annotation on the Class object receiver to get the + * className, the annotation on the String argument to get the + * methodName, and the parameters argument to get the param, and then + * builds a new MethodVal annotation from these */ - List getListOfMethodSignatures(AnnotationMirror methodValAnno) { - List methodNames = - AnnotationUtils.getElementValueArray( - methodValAnno, methodValMethodNameElement, String.class); - List classNames = - AnnotationUtils.getElementValueArray( - methodValAnno, methodValClassNameElement, String.class); - List params = - AnnotationUtils.getElementValueArray( - methodValAnno, methodValParamsElement, Integer.class); - List list = new ArrayList<>(methodNames.size()); - for (int i = 0; i < methodNames.size(); i++) { - list.add(new MethodSignature(classNames.get(i), methodNames.get(i), params.get(i))); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + + List methodNames; + List params; + List classNames; + if (isGetConstructorMethodInvocation(tree)) { + // method name for constructors is always + methodNames = ReflectionResolver.INIT_LIST; + params = getConstructorParamsLen(tree.getArguments()); + classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), true); + + } else if (isGetMethodMethodInvocation(tree)) { + ExpressionTree methodNameArg = tree.getArguments().get(0); + methodNames = getMethodNamesFromStringArg(methodNameArg); + params = getMethodParamsLen(tree.getArguments()); + classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), false); + } else { + // Not a covered method invocation + return null; + } + + // Create MethodVal + if (methodNames.isEmpty() || classNames.isEmpty()) { + // No method name or classname is found, it could be any + // class or method, so, return @UnknownMethod. + type.replaceAnnotation(UNKNOWN_METHOD); + return null; + } + + Set methodSigs = + new HashSet<>( + CollectionsPlume.mapCapacity(methodNames.size() * classNames.size() * params.size())); + // The possible method signatures are the Cartesian product of all + // found class, method, and parameter lengths. + for (String methodName : methodNames) { + for (String className : classNames) { + for (Integer param : params) { + methodSigs.add(new MethodSignature(className, methodName, param)); + } } - return list; + } + + AnnotationMirror newQual = createMethodVal(methodSigs); + type.replaceAnnotation(newQual); + return null; } /** - * Creates a {@code @MethodVal} annotation. + * Returns true if the method being invoked is annotated with @GetConstructor. An example of + * such a method is {@link Class#getConstructor}. * - * @param sigs the method signatures that the result should represent - * @return a {@code @MethodVal} annotation that represents {@code sigs} + * @param tree a method invocation + * @return true if the method being invoked is annotated with @GetConstructor */ - private AnnotationMirror createMethodVal(Collection sigs) { - int size = sigs.size(); - List classNames = new ArrayList<>(size); - List methodNames = new ArrayList<>(size); - List params = new ArrayList<>(size); - for (MethodSignature sig : sigs) { - classNames.add(sig.className); - methodNames.add(sig.methodName); - params.add(sig.params); - } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MethodVal.class); - builder.setValue("className", classNames); - builder.setValue("methodName", methodNames); - builder.setValue("params", params); - return builder.build(); + private boolean isGetConstructorMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetConstructor.class) != null; } /** - * Returns a list of class names for the given tree using the Class Val Checker. + * Returns true if the method being invoked is annotated with @GetMethod. An example of such a + * method is {@link Class#getMethod}. * - * @param tree an ExpressionTree whose class names are requested - * @param mustBeExact whether @ClassBound may be read to produce the result; if false, - * only @ClassVal may be read - * @return list of class names or the empty list if no class names were found + * @param tree a method invocation + * @return true if the method being invoked is annotated with @GetMethod */ - private List getClassNamesFromClassValChecker( - ExpressionTree tree, boolean mustBeExact) { - ClassValAnnotatedTypeFactory classValATF = - getTypeFactoryOfSubchecker(ClassValChecker.class); - AnnotatedTypeMirror classAnno = classValATF.getAnnotatedType(tree); - - AnnotationMirror classValAnno = classAnno.getAnnotation(ClassVal.class); - if (classValAnno != null) { - return AnnotationUtils.getElementValueArray( - classValAnno, classValValueElement, String.class); - } else if (mustBeExact) { - return Collections.emptyList(); - } - - AnnotationMirror classBoundAnno = classAnno.getAnnotation(ClassBound.class); - if (classBoundAnno != null) { - return AnnotationUtils.getElementValueArray( - classBoundAnno, classBoundValueElement, String.class); - } else { - return Collections.emptyList(); - } + private boolean isGetMethodMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetMethod.class) != null; } /** - * Returns the string values for the argument passed. The String Values are estimated using the - * Value Checker. + * Returns a singleton list containing the number of parameters for a call to a method annotated + * with {@code @}{@link GetMethod}. * - * @param arg an ExpressionTree whose string values are sought - * @return string values of arg or the empty list if no values were found + * @param args arguments to a call to a method such as {@code getMethod} + * @return the number of parameters */ - private List getMethodNamesFromStringArg(ExpressionTree arg) { - ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(arg); - AnnotationMirror annotation = valueAnno.getAnnotation(StringVal.class); - if (annotation != null) { - return AnnotationUtils.getElementValueArray( - annotation, stringValValueElement, String.class); - } else { - return EMPTY_STRING_LIST; - } - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new MethodValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + private List getMethodParamsLen(List args) { + assert !args.isEmpty() : "getMethod must have at least one parameter"; + + // Number of parameters in the created method object + int numParams = args.size() - 1; + if (numParams == 1) { + return getNumberOfParameterOneArg(args.get(1)); + } + return Collections.singletonList(numParams); } - /** MethodValQualifierHierarchy. */ - protected class MethodValQualifierHierarchy extends ElementQualifierHierarchy { - - /** - * Creates a MethodValQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - protected MethodValQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, MethodValAnnotatedTypeFactory.this); - } - - /* - * Determines the least upper bound of a1 and a2. If both are MethodVal - * annotations, then the least upper bound is the result of - * concatenating all value lists of a1 and a2. - */ - @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } else if (AnnotationUtils.areSameByName(a1, a2)) { - List a1Sigs = getListOfMethodSignatures(a1); - List a2Sigs = getListOfMethodSignatures(a2); - List lubSigs = CollectionsPlume.listUnion(a1Sigs, a2Sigs); - AnnotationMirror result = createMethodVal(lubSigs); - return result; - } - return null; - } - - @Override - public @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } else if (AnnotationUtils.areSameByName(a1, a2)) { - List a1Sigs = getListOfMethodSignatures(a1); - List a2Sigs = getListOfMethodSignatures(a2); - List lubSigs = CollectionsPlume.listIntersection(a1Sigs, a2Sigs); - AnnotationMirror result = createMethodVal(lubSigs); - return result; - } - return null; - } - - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (AnnotationUtils.areSame(subAnno, superAnno) - || areSameByClass(superAnno, UnknownMethod.class) - || areSameByClass(subAnno, MethodValBottom.class)) { - return true; - } - if (areSameByClass(subAnno, UnknownMethod.class) - || areSameByClass(superAnno, MethodValBottom.class)) { - return false; - } - assert areSameByClass(subAnno, MethodVal.class) - && areSameByClass(superAnno, MethodVal.class) - : "Unexpected annotation in MethodVal"; - List subSignatures = getListOfMethodSignatures(subAnno); - List superSignatures = getListOfMethodSignatures(superAnno); - return superSignatures.containsAll(subSignatures); - } - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new MethodValTreeAnnotator(this), super.createTreeAnnotator()); + /** + * Returns a singleton list containing the number of parameters for a call to a method annotated + * with {@code @}{@link GetConstructor}. + * + * @param args arguments to a call to a method such as {@code getConstructor} + * @return the number of parameters + */ + private List getConstructorParamsLen(List args) { + // Number of parameters in the created method object + int numParams = args.size(); + if (numParams == 1) { + return getNumberOfParameterOneArg(args.get(0)); + } + return Collections.singletonList(numParams); } - /** TreeAnnotator with the visitMethodInvocation method overridden. */ - protected class MethodValTreeAnnotator extends TreeAnnotator { - - protected MethodValTreeAnnotator(MethodValAnnotatedTypeFactory factory) { - super(factory); - } - - /* - * Special handling of getMethod and getDeclaredMethod calls. Attempts - * to get the annotation on the Class object receiver to get the - * className, the annotation on the String argument to get the - * methodName, and the parameters argument to get the param, and then - * builds a new MethodVal annotation from these - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - - List methodNames; - List params; - List classNames; - if (isGetConstructorMethodInvocation(tree)) { - // method name for constructors is always - methodNames = ReflectionResolver.INIT_LIST; - params = getConstructorParamsLen(tree.getArguments()); - classNames = - getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), true); - - } else if (isGetMethodMethodInvocation(tree)) { - ExpressionTree methodNameArg = tree.getArguments().get(0); - methodNames = getMethodNamesFromStringArg(methodNameArg); - params = getMethodParamsLen(tree.getArguments()); - classNames = - getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), false); - } else { - // Not a covered method invocation - return null; - } - - // Create MethodVal - if (methodNames.isEmpty() || classNames.isEmpty()) { - // No method name or classname is found, it could be any - // class or method, so, return @UnknownMethod. - type.replaceAnnotation(UNKNOWN_METHOD); - return null; - } - - Set methodSigs = - new HashSet<>( - CollectionsPlume.mapCapacity( - methodNames.size() * classNames.size() * params.size())); - // The possible method signatures are the Cartesian product of all - // found class, method, and parameter lengths. - for (String methodName : methodNames) { - for (String className : classNames) { - for (Integer param : params) { - methodSigs.add(new MethodSignature(className, methodName, param)); - } - } - } - - AnnotationMirror newQual = createMethodVal(methodSigs); - type.replaceAnnotation(newQual); - return null; - } - - /** - * Returns true if the method being invoked is annotated with @GetConstructor. An example of - * such a method is {@link Class#getConstructor}. - * - * @param tree a method invocation - * @return true if the method being invoked is annotated with @GetConstructor - */ - private boolean isGetConstructorMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetConstructor.class) != null; - } - - /** - * Returns true if the method being invoked is annotated with @GetMethod. An example of such - * a method is {@link Class#getMethod}. - * - * @param tree a method invocation - * @return true if the method being invoked is annotated with @GetMethod - */ - private boolean isGetMethodMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetMethod.class) != null; - } - - /** - * Returns a singleton list containing the number of parameters for a call to a method - * annotated with {@code @}{@link GetMethod}. - * - * @param args arguments to a call to a method such as {@code getMethod} - * @return the number of parameters - */ - private List getMethodParamsLen(List args) { - assert !args.isEmpty() : "getMethod must have at least one parameter"; - - // Number of parameters in the created method object - int numParams = args.size() - 1; - if (numParams == 1) { - return getNumberOfParameterOneArg(args.get(1)); - } - return Collections.singletonList(numParams); - } - - /** - * Returns a singleton list containing the number of parameters for a call to a method - * annotated with {@code @}{@link GetConstructor}. - * - * @param args arguments to a call to a method such as {@code getConstructor} - * @return the number of parameters - */ - private List getConstructorParamsLen(List args) { - // Number of parameters in the created method object - int numParams = args.size(); - if (numParams == 1) { - return getNumberOfParameterOneArg(args.get(0)); - } - return Collections.singletonList(numParams); - } - - /** - * If getMethod(Object receiver, Object... params) or getConstructor(Object... params) have - * one argument for params, then the number of parameters in the underlying method or - * constructor must be: - * - *

    - *
  • 0: if the argument is null - *
  • x: if the argument is an array with @ArrayLen(x); note that x might be a set rather - * than a single value - *
  • UNKNOWN_PARAM_LENGTH: if the argument is an array with @UnknownVal - *
  • 1: otherwise - *
- * - * @param argument the single argument in a call to {@code getMethod} or {@code - * getConstrutor} - * @return a list, each of whose elementts is a possible the number of parameters; it is - * often, but not always, a singleton list - */ - private List getNumberOfParameterOneArg(ExpressionTree argument) { - AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(argument); - switch (atm.getKind()) { - case ARRAY: - ValueAnnotatedTypeFactory valueATF = - getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotatedTypeMirror valueType = valueATF.getAnnotatedType(argument); - AnnotationMirror arrayLenAnno = valueType.getAnnotation(ArrayLen.class); - if (arrayLenAnno != null) { - return AnnotationUtils.getElementValueArray( - arrayLenAnno, arrayLenValueElement, Integer.class); - } else if (valueType.getAnnotation(BottomVal.class) != null) { - // happens in this case: (Class[]) null - return ZERO_LIST; - } - // the argument is an array with unknown array length - return UNKNOWN_PARAM_LENGTH_LIST; - case NULL: - // null is treated as the empty list of parameters, so size is 0. - return ZERO_LIST; - default: - // The argument is not an array or null, so it must be a class. - return ONE_LIST; - } - } + /** + * If getMethod(Object receiver, Object... params) or getConstructor(Object... params) have one + * argument for params, then the number of parameters in the underlying method or constructor + * must be: + * + *
    + *
  • 0: if the argument is null + *
  • x: if the argument is an array with @ArrayLen(x); note that x might be a set rather + * than a single value + *
  • UNKNOWN_PARAM_LENGTH: if the argument is an array with @UnknownVal + *
  • 1: otherwise + *
+ * + * @param argument the single argument in a call to {@code getMethod} or {@code getConstrutor} + * @return a list, each of whose elementts is a possible the number of parameters; it is often, + * but not always, a singleton list + */ + private List getNumberOfParameterOneArg(ExpressionTree argument) { + AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(argument); + switch (atm.getKind()) { + case ARRAY: + ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotatedTypeMirror valueType = valueATF.getAnnotatedType(argument); + AnnotationMirror arrayLenAnno = valueType.getAnnotation(ArrayLen.class); + if (arrayLenAnno != null) { + return AnnotationUtils.getElementValueArray( + arrayLenAnno, arrayLenValueElement, Integer.class); + } else if (valueType.getAnnotation(BottomVal.class) != null) { + // happens in this case: (Class[]) null + return ZERO_LIST; + } + // the argument is an array with unknown array length + return UNKNOWN_PARAM_LENGTH_LIST; + case NULL: + // null is treated as the empty list of parameters, so size is 0. + return ZERO_LIST; + default: + // The argument is not an array or null, so it must be a class. + return ONE_LIST; + } } + } } /** @@ -460,61 +446,61 @@ private List getNumberOfParameterOneArg(ExpressionTree argument) { * name, method name, number of parameters). */ class MethodSignature { - String className; - String methodName; - int params; - - public MethodSignature(String className, String methodName, int params) { - this.className = className; - this.methodName = methodName; - this.params = params; + String className; + String methodName; + int params; + + public MethodSignature(String className, String methodName, int params) { + this.className = className; + this.methodName = methodName; + this.params = params; + } + + @Override + public int hashCode() { + return Objects.hash(className, methodName, params); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; } - - @Override - public int hashCode() { - return Objects.hash(className, methodName, params); + if (obj == null) { + return false; } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - MethodSignature other = (MethodSignature) obj; - if (className == null) { - if (other.className != null) { - return false; - } - } else if (!className.equals(other.className)) { - return false; - } - if (methodName == null) { - if (other.methodName != null) { - return false; - } - } else if (!methodName.equals(other.methodName)) { - return false; - } - if (params != other.params) { - return false; - } - return true; + if (getClass() != obj.getClass()) { + return false; } - - @Override - public String toString() { - return "MethodSignature [className=" - + className - + ", methodName=" - + methodName - + ", params=" - + params - + "]"; + MethodSignature other = (MethodSignature) obj; + if (className == null) { + if (other.className != null) { + return false; + } + } else if (!className.equals(other.className)) { + return false; + } + if (methodName == null) { + if (other.methodName != null) { + return false; + } + } else if (!methodName.equals(other.methodName)) { + return false; + } + if (params != other.params) { + return false; } + return true; + } + + @Override + public String toString() { + return "MethodSignature [className=" + + className + + ", methodName=" + + methodName + + ", params=" + + params + + "]"; + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java index 3a3c0d1173e..27e8054c880 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java @@ -1,33 +1,32 @@ package org.checkerframework.common.reflection; +import java.util.LinkedHashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; import org.plumelib.util.CollectionsPlume; -import java.util.LinkedHashSet; -import java.util.Set; - /** * The MethodVal Checker provides a sound estimate of the signature of Method objects. * * @checker_framework.manual #methodval-and-classval-checkers MethodVal Checker */ public class MethodValChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new MethodValVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new MethodValVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - // Don't call super otherwise MethodVal will be added as a subChecker - // which creates a circular dependency. - // Use the same Set implementation as super. - Set> subCheckers = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); - subCheckers.add(ValueChecker.class); - subCheckers.add(ClassValChecker.class); - return subCheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Don't call super otherwise MethodVal will be added as a subChecker + // which creates a circular dependency. + // Use the same Set implementation as super. + Set> subCheckers = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); + subCheckers.add(ValueChecker.class); + subCheckers.add(ClassValChecker.class); + return subCheckers; + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java index 772e170c610..971a7285d21 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java @@ -1,7 +1,8 @@ package org.checkerframework.common.reflection; import com.sun.source.tree.Tree; - +import java.util.List; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -10,79 +11,73 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.javacutil.AnnotationUtils; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; - public class MethodValVisitor extends BaseTypeVisitor { - public MethodValVisitor(BaseTypeChecker checker) { - super(checker); - } + public MethodValVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected MethodValAnnotatedTypeFactory createTypeFactory() { - return new MethodValAnnotatedTypeFactory(checker); - } + @Override + protected MethodValAnnotatedTypeFactory createTypeFactory() { + return new MethodValAnnotatedTypeFactory(checker); + } - @Override - protected BaseTypeValidator createTypeValidator() { - return new MethodNameValidator(checker, this, atypeFactory); - } + @Override + protected BaseTypeValidator createTypeValidator() { + return new MethodNameValidator(checker, this, atypeFactory); + } } class MethodNameValidator extends BaseTypeValidator { - public MethodNameValidator( - BaseTypeChecker checker, - BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } + public MethodNameValidator( + BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - AnnotationMirror methodVal = type.getAnnotation(MethodVal.class); - if (methodVal != null) { - AnnotatedTypeFactory atypeFactory = checker.getTypeFactory(); - List classNames = - AnnotationUtils.getElementValueArray( - methodVal, atypeFactory.methodValClassNameElement, String.class); - List methodNames = - AnnotationUtils.getElementValueArray( - methodVal, atypeFactory.methodValMethodNameElement, String.class); - List params = - AnnotationUtils.getElementValueArray( - methodVal, atypeFactory.methodValParamsElement, Integer.class); - if (!(classNames.size() == methodNames.size() && classNames.size() == params.size())) { - checker.reportError(tree, "invalid.methodval", methodVal); - } + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + AnnotationMirror methodVal = type.getAnnotation(MethodVal.class); + if (methodVal != null) { + AnnotatedTypeFactory atypeFactory = checker.getTypeFactory(); + List classNames = + AnnotationUtils.getElementValueArray( + methodVal, atypeFactory.methodValClassNameElement, String.class); + List methodNames = + AnnotationUtils.getElementValueArray( + methodVal, atypeFactory.methodValMethodNameElement, String.class); + List params = + AnnotationUtils.getElementValueArray( + methodVal, atypeFactory.methodValParamsElement, Integer.class); + if (!(classNames.size() == methodNames.size() && classNames.size() == params.size())) { + checker.reportError(tree, "invalid.methodval", methodVal); + } - for (String methodName : methodNames) { - if (!legalMethodName(methodName)) { - checker.reportError(tree, "illegal.methodname", methodName, type); - } - } + for (String methodName : methodNames) { + if (!legalMethodName(methodName)) { + checker.reportError(tree, "illegal.methodname", methodName, type); } - return super.visitDeclared(type, tree); + } } + return super.visitDeclared(type, tree); + } - private boolean legalMethodName(String methodName) { - if (methodName.equals(ReflectionResolver.INIT)) { - return true; - } - if (methodName.length() < 1) { - return false; - } - char[] methodNameChars = methodName.toCharArray(); - if (!Character.isJavaIdentifierStart(methodNameChars[0])) { - return false; - } - for (int i = 1; i < methodNameChars.length; i++) { - if (!Character.isJavaIdentifierPart(methodNameChars[i])) { - return false; - } - } - return true; + private boolean legalMethodName(String methodName) { + if (methodName.equals(ReflectionResolver.INIT)) { + return true; + } + if (methodName.length() < 1) { + return false; + } + char[] methodNameChars = methodName.toCharArray(); + if (!Character.isJavaIdentifierStart(methodNameChars[0])) { + return false; + } + for (int i = 1; i < methodNameChars.length; i++) { + if (!Character.isJavaIdentifierPart(methodNameChars[i])) { + return false; + } } + return true; + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java index 7d9282da79a..cd333be6d05 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java @@ -1,14 +1,12 @@ package org.checkerframework.common.reflection; import com.sun.source.tree.MethodInvocationTree; - -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; - import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; /** * Interface for reflection resolvers that handle reflective method calls such as {@link @@ -17,32 +15,32 @@ * @checker_framework.manual #reflection-resolution Reflection resolution */ public interface ReflectionResolver { - /** The "method name" of constructors. */ - public static final String INIT = ""; + /** The "method name" of constructors. */ + public static final String INIT = ""; - /** - * A list containing just the "method name" of constructors. Clients must not modify this list. - */ - public static final List INIT_LIST = Collections.singletonList(INIT); + /** + * A list containing just the "method name" of constructors. Clients must not modify this list. + */ + public static final List INIT_LIST = Collections.singletonList(INIT); - /** - * Returns true if the given tree represents a reflective method or constructor call. - * - * @return {@code true} iff tree is a reflective method invocation, {@code false} otherwise - */ - public boolean isReflectiveMethodInvocation(MethodInvocationTree tree); + /** + * Returns true if the given tree represents a reflective method or constructor call. + * + * @return {@code true} iff tree is a reflective method invocation, {@code false} otherwise + */ + public boolean isReflectiveMethodInvocation(MethodInvocationTree tree); - /** - * Resolve reflection and return the result of {@code factory.methodFromUse} for the actual, - * resolved method or constructor call. If the reflective method cannot be resolved the original - * result ({@code origResult}) is returned. - * - * @param factory the currently used AnnotatedTypeFactory - * @param tree the reflective invocation tree (m.invoke or c.newInstance) - * @param origResult the original result for the unresolved, reflective method call - */ - public ParameterizedExecutableType resolveReflectiveCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult); + /** + * Resolve reflection and return the result of {@code factory.methodFromUse} for the actual, + * resolved method or constructor call. If the reflective method cannot be resolved the original + * result ({@code origResult}) is returned. + * + * @param factory the currently used AnnotatedTypeFactory + * @param tree the reflective invocation tree (m.invoke or c.newInstance) + * @param origResult the original result for the unresolved, reflective method call + */ + public ParameterizedExecutableType resolveReflectiveCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult); } diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java index 16cd54eddea..f00f650bf39 100644 --- a/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java +++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java @@ -1,5 +1,10 @@ package org.checkerframework.common.returnsreceiver; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -7,12 +12,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * A utility class to support fluent API generators so the checker can add {@code @This} annotations * on method return types when a generator has been used. To check whether a method is created by @@ -21,120 +20,115 @@ */ public class FluentAPIGenerator { - /** - * Check if a method was generated by a known fluent API generator and returns its receiver. - * - * @param t the annotated type of the method signature - * @return {@code true} if the method was created by a generator and returns {@code this} - */ - public static boolean check(AnnotatedExecutableType t) { - for (FluentAPIGenerators fluentAPIGenerator : FluentAPIGenerators.values()) { - if (fluentAPIGenerator.returnsThis(t)) { - return true; - } - } - return false; + /** + * Check if a method was generated by a known fluent API generator and returns its receiver. + * + * @param t the annotated type of the method signature + * @return {@code true} if the method was created by a generator and returns {@code this} + */ + public static boolean check(AnnotatedExecutableType t) { + for (FluentAPIGenerators fluentAPIGenerator : FluentAPIGenerators.values()) { + if (fluentAPIGenerator.returnsThis(t)) { + return true; + } } + return false; + } + /** + * Enum of supported fluent API generators. For such generators, the checker can automatically + * add @This annotations on method return types in the generated code. + */ + private enum FluentAPIGenerators { /** - * Enum of supported fluent API generators. For such generators, the checker can automatically - * add @This annotations on method return types in the generated code. + * The AutoValue + * framework. */ - private enum FluentAPIGenerators { - /** - * The AutoValue - * framework. - */ - AUTO_VALUE { + AUTO_VALUE { - /** - * The qualified name of the AutoValue Builder annotation. This needs to be constructed - * dynamically due to a side effect of the shadow plugin. See {@link - * #getAutoValueBuilderCanonicalName()} for more information. - */ - private final String AUTO_VALUE_BUILDER = getAutoValueBuilderCanonicalName(); + /** + * The qualified name of the AutoValue Builder annotation. This needs to be constructed + * dynamically due to a side effect of the shadow plugin. See {@link + * #getAutoValueBuilderCanonicalName()} for more information. + */ + private final String AUTO_VALUE_BUILDER = getAutoValueBuilderCanonicalName(); - @Override - public boolean returnsThis(AnnotatedExecutableType t) { - ExecutableElement element = t.getElement(); - TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); - boolean inAutoValueBuilder = - AnnotationUtils.containsSameByName( - enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); + @Override + public boolean returnsThis(AnnotatedExecutableType t) { + ExecutableElement element = t.getElement(); + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + boolean inAutoValueBuilder = + AnnotationUtils.containsSameByName( + enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); - if (!inAutoValueBuilder) { - // see if superclass is an AutoValue Builder, to handle generated code - TypeMirror superclass = enclosingElement.getSuperclass(); - // if enclosingElement is an interface, the superclass has TypeKind NONE - if (superclass.getKind() != TypeKind.NONE) { - // update enclosingElement to be for the superclass for this case - enclosingElement = TypesUtils.getTypeElement(superclass); - inAutoValueBuilder = - AnnotationUtils.containsSameByName( - enclosingElement.getAnnotationMirrors(), - AUTO_VALUE_BUILDER); - } - } + if (!inAutoValueBuilder) { + // see if superclass is an AutoValue Builder, to handle generated code + TypeMirror superclass = enclosingElement.getSuperclass(); + // if enclosingElement is an interface, the superclass has TypeKind NONE + if (superclass.getKind() != TypeKind.NONE) { + // update enclosingElement to be for the superclass for this case + enclosingElement = TypesUtils.getTypeElement(superclass); + inAutoValueBuilder = + AnnotationUtils.containsSameByName( + enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); + } + } - if (inAutoValueBuilder) { - AnnotatedTypeMirror returnType = t.getReturnType(); - if (returnType == null) { - throw new TypeSystemError("Return type cannot be null: " + t); - } - return enclosingElement.equals( - TypesUtils.getTypeElement(returnType.getUnderlyingType())); - } - return false; - } + if (inAutoValueBuilder) { + AnnotatedTypeMirror returnType = t.getReturnType(); + if (returnType == null) { + throw new TypeSystemError("Return type cannot be null: " + t); + } + return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType())); + } + return false; + } - /** - * Get the qualified name of the AutoValue Builder annotation. This method constructs - * the String dynamically, to ensure it does not get rewritten due to relocation of the - * {@code "com.google"} package during the build process. - * - * @return {@code "com.google.auto.value.AutoValue.Builder"} - */ - private @CanonicalName String getAutoValueBuilderCanonicalName() { - String com = "com"; - @SuppressWarnings("signature:assignment.type.incompatible") // string concatenation - @CanonicalName String result = com + "." + "google.auto.value.AutoValue.Builder"; - return result; - } - }, - /** Project Lombok. */ - LOMBOK { - @Override - public boolean returnsThis(AnnotatedExecutableType t) { - ExecutableElement element = t.getElement(); - Element enclosingElement = element.getEnclosingElement(); - boolean inLombokBuilder = - (AnnotationUtils.containsSameByName( - enclosingElement.getAnnotationMirrors(), - "lombok.Generated") - || AnnotationUtils.containsSameByName( - element.getAnnotationMirrors(), "lombok.Generated")) - && enclosingElement.getSimpleName().toString().endsWith("Builder"); + /** + * Get the qualified name of the AutoValue Builder annotation. This method constructs the + * String dynamically, to ensure it does not get rewritten due to relocation of the {@code + * "com.google"} package during the build process. + * + * @return {@code "com.google.auto.value.AutoValue.Builder"} + */ + private @CanonicalName String getAutoValueBuilderCanonicalName() { + String com = "com"; + @SuppressWarnings("signature:assignment.type.incompatible") // string concatenation + @CanonicalName String result = com + "." + "google.auto.value.AutoValue.Builder"; + return result; + } + }, + /** Project Lombok. */ + LOMBOK { + @Override + public boolean returnsThis(AnnotatedExecutableType t) { + ExecutableElement element = t.getElement(); + Element enclosingElement = element.getEnclosingElement(); + boolean inLombokBuilder = + (AnnotationUtils.containsSameByName( + enclosingElement.getAnnotationMirrors(), "lombok.Generated") + || AnnotationUtils.containsSameByName( + element.getAnnotationMirrors(), "lombok.Generated")) + && enclosingElement.getSimpleName().toString().endsWith("Builder"); - if (inLombokBuilder) { - AnnotatedTypeMirror returnType = t.getReturnType(); - if (returnType == null) { - throw new TypeSystemError("Return type cannot be null: " + t); - } - return enclosingElement.equals( - TypesUtils.getTypeElement(returnType.getUnderlyingType())); - } - return false; - } - }; + if (inLombokBuilder) { + AnnotatedTypeMirror returnType = t.getReturnType(); + if (returnType == null) { + throw new TypeSystemError("Return type cannot be null: " + t); + } + return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType())); + } + return false; + } + }; - /** - * Returns {@code true} if the method was created by this generator and returns {@code - * this}. - * - * @param t the annotated type of the method signature - * @return {@code true} if the method was created by this generator and returns {@code this} - */ - protected abstract boolean returnsThis(AnnotatedExecutableType t); - } + /** + * Returns {@code true} if the method was created by this generator and returns {@code this}. + * + * @param t the annotated type of the method signature + * @return {@code true} if the method was created by this generator and returns {@code this} + */ + protected abstract boolean returnsThis(AnnotatedExecutableType t); + } } diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java index 5cb4df90afb..43b3b698962 100644 --- a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java @@ -1,5 +1,9 @@ package org.checkerframework.common.returnsreceiver; +import java.lang.annotation.Annotation; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.returnsreceiver.qual.BottomThis; @@ -12,83 +16,77 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -import java.lang.annotation.Annotation; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; - /** The type factory for the Returns Receiver Checker. */ public class ReturnsReceiverAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** - * The {@code @}{@link This} annotation. The field is package visible (i.e., "package private") - * due to a use in {@link ReturnsReceiverVisitor} - */ - /*package-private*/ final AnnotationMirror THIS_ANNOTATION; + /** + * The {@code @}{@link This} annotation. The field is package visible (i.e., "package private") + * due to a use in {@link ReturnsReceiverVisitor} + */ + /*package-private*/ final AnnotationMirror THIS_ANNOTATION; + + /** + * Create a new {@code ReturnsReceiverAnnotatedTypeFactory}. + * + * @param checker the type-checker associated with this factory + */ + public ReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + THIS_ANNOTATION = AnnotationBuilder.fromClass(elements, This.class); + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(BottomThis.class, UnknownThis.class, This.class); + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator( + new ReturnsReceiverTypeAnnotator(this), super.createTypeAnnotator()); + } + + /** A TypeAnnotator to add the {@code @}{@link This} annotation. */ + private class ReturnsReceiverTypeAnnotator extends TypeAnnotator { /** - * Create a new {@code ReturnsReceiverAnnotatedTypeFactory}. + * Create a new ReturnsReceiverTypeAnnotator. * - * @param checker the type-checker associated with this factory + * @param typeFactory the {@link AnnotatedTypeFactory} associated with this {@link + * TypeAnnotator} */ - public ReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - THIS_ANNOTATION = AnnotationBuilder.fromClass(elements, This.class); - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(BottomThis.class, UnknownThis.class, This.class); + public ReturnsReceiverTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); } @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator( - new ReturnsReceiverTypeAnnotator(this), super.createTypeAnnotator()); - } - - /** A TypeAnnotator to add the {@code @}{@link This} annotation. */ - private class ReturnsReceiverTypeAnnotator extends TypeAnnotator { - - /** - * Create a new ReturnsReceiverTypeAnnotator. - * - * @param typeFactory the {@link AnnotatedTypeFactory} associated with this {@link - * TypeAnnotator} - */ - public ReturnsReceiverTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - } - - @Override - public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { + public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { - // skip constructors, as we never need to add annotations to them - if (t.getElement().getKind() == ElementKind.CONSTRUCTOR) { - return super.visitExecutable(t, p); - } + // skip constructors, as we never need to add annotations to them + if (t.getElement().getKind() == ElementKind.CONSTRUCTOR) { + return super.visitExecutable(t, p); + } - AnnotatedTypeMirror returnType = t.getReturnType(); + AnnotatedTypeMirror returnType = t.getReturnType(); - // If any FluentAPIGenerator indicates the method returns this, - // add an @This annotation on the return type. - if (FluentAPIGenerator.check(t)) { - returnType.addMissingAnnotation(THIS_ANNOTATION); - } + // If any FluentAPIGenerator indicates the method returns this, + // add an @This annotation on the return type. + if (FluentAPIGenerator.check(t)) { + returnType.addMissingAnnotation(THIS_ANNOTATION); + } - // If return type is annotated with @This, add @This annotation to the receiver type. - // We cannot yet default all receivers to be @This due to - // https://github.com/typetools/checker-framework/issues/2931 - AnnotationMirror retAnnotation = returnType.getAnnotationInHierarchy(THIS_ANNOTATION); - if (retAnnotation != null && AnnotationUtils.areSame(retAnnotation, THIS_ANNOTATION)) { - AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = t.getReceiverType(); - if (receiverType != null) { - receiverType.addMissingAnnotation(THIS_ANNOTATION); - } - } - return super.visitExecutable(t, p); + // If return type is annotated with @This, add @This annotation to the receiver type. + // We cannot yet default all receivers to be @This due to + // https://github.com/typetools/checker-framework/issues/2931 + AnnotationMirror retAnnotation = returnType.getAnnotationInHierarchy(THIS_ANNOTATION); + if (retAnnotation != null && AnnotationUtils.areSame(retAnnotation, THIS_ANNOTATION)) { + AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = t.getReceiverType(); + if (receiverType != null) { + receiverType.addMissingAnnotation(THIS_ANNOTATION); } + } + return super.visitExecutable(t, p); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java index d5a3f14eef5..cb7a88ddb43 100644 --- a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java @@ -7,57 +7,53 @@ import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; - /** The visitor for the Returns Receiver Checker. */ public class ReturnsReceiverVisitor extends BaseTypeVisitor { - /** - * Create a new {@code ReturnsReceiverVisitor}. - * - * @param checker the type-checker associated with this visitor - */ - public ReturnsReceiverVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Create a new {@code ReturnsReceiverVisitor}. + * + * @param checker the type-checker associated with this visitor + */ + public ReturnsReceiverVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror annot = TreeUtils.annotationFromAnnotationTree(tree); - // Warn if a @This annotation is in an illegal location. - if (AnnotationUtils.areSame(annot, getTypeFactory().THIS_ANNOTATION)) { - TreePath parentPath = getCurrentPath().getParentPath(); - Tree parent = parentPath.getLeaf(); - Tree grandparent = parentPath.getParentPath().getLeaf(); - Tree greatGrandparent = parentPath.getParentPath().getParentPath().getLeaf(); - boolean isReturnAnnot = - grandparent instanceof MethodTree - && (parent.equals(((MethodTree) grandparent).getReturnType()) - || parent instanceof ModifiersTree); - boolean isReceiverAnnot = - greatGrandparent instanceof MethodTree - && grandparent.equals( - ((MethodTree) greatGrandparent).getReceiverParameter()) - && parent.equals(((VariableTree) grandparent).getModifiers()); - boolean isCastAnnot = - grandparent instanceof TypeCastTree - && parent.equals(((TypeCastTree) grandparent).getType()); - if (!(isReturnAnnot || isReceiverAnnot || isCastAnnot)) { - checker.reportError(tree, "type.invalid.this.location"); - } - if (isReturnAnnot - && ElementUtils.isStatic( - TreeUtils.elementFromDeclaration((MethodTree) grandparent))) { - checker.reportError(tree, "type.invalid.this.location"); - } - } - return super.visitAnnotation(tree, p); + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror annot = TreeUtils.annotationFromAnnotationTree(tree); + // Warn if a @This annotation is in an illegal location. + if (AnnotationUtils.areSame(annot, getTypeFactory().THIS_ANNOTATION)) { + TreePath parentPath = getCurrentPath().getParentPath(); + Tree parent = parentPath.getLeaf(); + Tree grandparent = parentPath.getParentPath().getLeaf(); + Tree greatGrandparent = parentPath.getParentPath().getParentPath().getLeaf(); + boolean isReturnAnnot = + grandparent instanceof MethodTree + && (parent.equals(((MethodTree) grandparent).getReturnType()) + || parent instanceof ModifiersTree); + boolean isReceiverAnnot = + greatGrandparent instanceof MethodTree + && grandparent.equals(((MethodTree) greatGrandparent).getReceiverParameter()) + && parent.equals(((VariableTree) grandparent).getModifiers()); + boolean isCastAnnot = + grandparent instanceof TypeCastTree + && parent.equals(((TypeCastTree) grandparent).getType()); + if (!(isReturnAnnot || isReceiverAnnot || isCastAnnot)) { + checker.reportError(tree, "type.invalid.this.location"); + } + if (isReturnAnnot + && ElementUtils.isStatic(TreeUtils.elementFromDeclaration((MethodTree) grandparent))) { + checker.reportError(tree, "type.invalid.this.location"); + } } + return super.visitAnnotation(tree, p); + } } diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java index ed298cb57a2..d663389fd1e 100644 --- a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java @@ -1,5 +1,11 @@ package org.checkerframework.common.subtyping; +import java.io.File; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.qual.Unqualified; @@ -13,118 +19,105 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; -import java.io.File; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; - /** Defines {@link #createSupportedTypeQualifiers}. */ public class SubtypingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public SubtypingAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + public SubtypingAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - protected AnnotationClassLoader createAnnotationClassLoader() { - return new SubtypingAnnotationClassLoader(checker); - } + @Override + protected AnnotationClassLoader createAnnotationClassLoader() { + return new SubtypingAnnotationClassLoader(checker); + } - @Override - protected Set> createSupportedTypeQualifiers() { - // Subtyping Checker doesn't have a qual directory, so we instantiate the loader here to - // load externally declared annotations - loader = createAnnotationClassLoader(); + @Override + protected Set> createSupportedTypeQualifiers() { + // Subtyping Checker doesn't have a qual directory, so we instantiate the loader here to + // load externally declared annotations + loader = createAnnotationClassLoader(); - Set> qualSet = new LinkedHashSet<>(); + Set> qualSet = new LinkedHashSet<>(); - // load individually named qualifiers - for (String qualName : checker.getStringsOption("quals", ',')) { - if (!Signatures.isBinaryName(qualName)) { - throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); - } - Class anno = loader.loadExternalAnnotationClass(qualName); - if (anno == null) { - throw new UserError("Qualifier specified in -Aquals not found: " + qualName); - } - qualSet.add(anno); - } + // load individually named qualifiers + for (String qualName : checker.getStringsOption("quals", ',')) { + if (!Signatures.isBinaryName(qualName)) { + throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); + } + Class anno = loader.loadExternalAnnotationClass(qualName); + if (anno == null) { + throw new UserError("Qualifier specified in -Aquals not found: " + qualName); + } + qualSet.add(anno); + } - // load directories of qualifiers - for (String dirName : checker.getStringsOption("qualDirs", ':')) { - if (!new File(dirName).exists()) { - throw new UserError( - "Directory specified in -AqualsDir does not exist: %s", dirName); - } - Set> annos = - loader.loadExternalAnnotationClassesFromDirectory(dirName); - if (annos.isEmpty()) { - throw new UserError( - "Directory specified in -AqualsDir contains no qualifiers: " + dirName); - } - qualSet.addAll(annos); - } + // load directories of qualifiers + for (String dirName : checker.getStringsOption("qualDirs", ':')) { + if (!new File(dirName).exists()) { + throw new UserError("Directory specified in -AqualsDir does not exist: %s", dirName); + } + Set> annos = + loader.loadExternalAnnotationClassesFromDirectory(dirName); + if (annos.isEmpty()) { + throw new UserError("Directory specified in -AqualsDir contains no qualifiers: " + dirName); + } + qualSet.addAll(annos); + } - if (qualSet.isEmpty()) { - throw new UserError( - "SubtypingChecker: no qualifiers specified via -Aquals or -AqualDirs"); - } + if (qualSet.isEmpty()) { + throw new UserError("SubtypingChecker: no qualifiers specified via -Aquals or -AqualDirs"); + } - // check for subtype meta-annotation - for (Class qual : qualSet) { - Annotation subtypeOfAnnotation = qual.getAnnotation(SubtypeOf.class); - if (subtypeOfAnnotation != null) { - for (Class superqual : - qual.getAnnotation(SubtypeOf.class).value()) { - if (!qualSet.contains(superqual)) { - throw new UserError( - "SubtypingChecker: qualifier " - + qual - + " was specified via -Aquals but its super-qualifier " - + superqual - + " was not"); - } - } - } + // check for subtype meta-annotation + for (Class qual : qualSet) { + Annotation subtypeOfAnnotation = qual.getAnnotation(SubtypeOf.class); + if (subtypeOfAnnotation != null) { + for (Class superqual : qual.getAnnotation(SubtypeOf.class).value()) { + if (!qualSet.contains(superqual)) { + throw new UserError( + "SubtypingChecker: qualifier " + + qual + + " was specified via -Aquals but its super-qualifier " + + superqual + + " was not"); + } } - - return qualSet; + } } - /** - * If necessary, make Unqualified the default qualifier. Keep most logic in sync with super. - * - * @see - * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addCheckedCodeDefaults(org.checkerframework.framework.util.defaults.QualifierDefaults) - */ - @Override - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - boolean foundOtherwise = false; - // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy - for (Class qual : getSupportedTypeQualifiers()) { - DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); - if (defaultFor != null) { - TypeUseLocation[] locations = defaultFor.value(); - defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); - foundOtherwise = - foundOtherwise - || Arrays.asList(locations).contains(TypeUseLocation.OTHERWISE); - } + return qualSet; + } - if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { - defs.addCheckedCodeDefault( - AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); - foundOtherwise = true; - } - } - // If Unqualified is a supported qualifier, make it the default. - AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); - if (!foundOtherwise && this.isSupportedQualifier(unqualified)) { - defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); - } + /** + * If necessary, make Unqualified the default qualifier. Keep most logic in sync with super. + * + * @see + * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addCheckedCodeDefaults(org.checkerframework.framework.util.defaults.QualifierDefaults) + */ + @Override + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + boolean foundOtherwise = false; + // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy + for (Class qual : getSupportedTypeQualifiers()) { + DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); + if (defaultFor != null) { + TypeUseLocation[] locations = defaultFor.value(); + defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); + foundOtherwise = + foundOtherwise || Arrays.asList(locations).contains(TypeUseLocation.OTHERWISE); + } + + if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { + defs.addCheckedCodeDefault( + AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); + foundOtherwise = true; + } + } + // If Unqualified is a supported qualifier, make it the default. + AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); + if (!foundOtherwise && this.isSupportedQualifier(unqualified)) { + defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java index f32e1ea3b32..9939ed82211 100644 --- a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java +++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java @@ -1,23 +1,21 @@ package org.checkerframework.common.subtyping; +import java.lang.annotation.Annotation; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.type.AnnotationClassLoader; -import java.lang.annotation.Annotation; - public class SubtypingAnnotationClassLoader extends AnnotationClassLoader { - public SubtypingAnnotationClassLoader(BaseTypeChecker checker) { - super(checker); - } + public SubtypingAnnotationClassLoader(BaseTypeChecker checker) { + super(checker); + } - // Unqualified is a supported annotation for the Subtyping Checker, and is loaded only if listed - // in -Aquals. It intentionally has an empty @Target meta-annotation. All other annotations used - // with the subtyping checker must have a well-defined @Target meta-annotation. - @Override - protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { - return super.hasWellDefinedTargetMetaAnnotation(annoClass) - || annoClass == Unqualified.class; - } + // Unqualified is a supported annotation for the Subtyping Checker, and is loaded only if listed + // in -Aquals. It intentionally has an empty @Target meta-annotation. All other annotations used + // with the subtyping checker must have a well-defined @Target meta-annotation. + @Override + protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { + return super.hasWellDefinedTargetMetaAnnotation(annoClass) || annoClass == Unqualified.class; + } } diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java index ad9a03ba98a..1a4aabe25c3 100644 --- a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java +++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java @@ -1,16 +1,14 @@ package org.checkerframework.common.subtyping; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.common.basetype.BaseTypeVisitor; -import org.checkerframework.framework.source.SourceVisitor; - import java.lang.annotation.Annotation; import java.util.Locale; import java.util.NavigableSet; import java.util.Set; import java.util.TreeSet; - import javax.annotation.processing.SupportedOptions; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.framework.source.SourceVisitor; /** * A checker for type qualifier systems that only checks subtyping relationships. @@ -28,40 +26,40 @@ @SupportedOptions({"quals", "qualDirs"}) public final class SubtypingChecker extends BaseTypeChecker { - /** - * Compute SuppressWarnings prefixes, based on the names of all the qualifiers. - * - *

Provided for the convenience of checkers that do not subclass {@code SubtypingChecker} - * (because it is final). Clients should call it like: - * - *

{@code
-     * SubtypingChecker.getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes());
-     * }
- * - * @param visitor the visitor - * @param superSupportedTypeQualifiers the result of super.getSuppressWarningsPrefixes(), as - * executed by checker - * @return SuppressWarnings prefixes, based on the names of all the qualifiers - */ - public static NavigableSet getSuppressWarningsPrefixes( - SourceVisitor visitor, NavigableSet superSupportedTypeQualifiers) { - TreeSet result = new TreeSet<>(superSupportedTypeQualifiers); - - // visitor can be null if there was an error when calling the visitor class's constructor -- - // that is, when there is a bug in a checker implementation. - if (visitor != null) { - Set> annos = - ((BaseTypeVisitor) visitor).getTypeFactory().getSupportedTypeQualifiers(); - for (Class anno : annos) { - result.add(anno.getSimpleName().toLowerCase(Locale.ROOT)); - } - } + /** + * Compute SuppressWarnings prefixes, based on the names of all the qualifiers. + * + *

Provided for the convenience of checkers that do not subclass {@code SubtypingChecker} + * (because it is final). Clients should call it like: + * + *

{@code
+   * SubtypingChecker.getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes());
+   * }
+ * + * @param visitor the visitor + * @param superSupportedTypeQualifiers the result of super.getSuppressWarningsPrefixes(), as + * executed by checker + * @return SuppressWarnings prefixes, based on the names of all the qualifiers + */ + public static NavigableSet getSuppressWarningsPrefixes( + SourceVisitor visitor, NavigableSet superSupportedTypeQualifiers) { + TreeSet result = new TreeSet<>(superSupportedTypeQualifiers); - return result; + // visitor can be null if there was an error when calling the visitor class's constructor -- + // that is, when there is a bug in a checker implementation. + if (visitor != null) { + Set> annos = + ((BaseTypeVisitor) visitor).getTypeFactory().getSupportedTypeQualifiers(); + for (Class anno : annos) { + result.add(anno.getSimpleName().toLowerCase(Locale.ROOT)); + } } - @Override - public NavigableSet getSuppressWarningsPrefixes() { - return getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes()); - } + return result; + } + + @Override + public NavigableSet getSuppressWarningsPrefixes() { + return getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes()); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java index 85d1cd226fb..a2e7d6667ef 100644 --- a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java +++ b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java @@ -1,5 +1,18 @@ package org.checkerframework.common.util; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; @@ -20,21 +33,6 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; - /** * TypeVisualizer prints AnnotatedTypeMirrors as a directed graph where each node is a type and an * arrow is a reference. Arrows are labeled with the relationship that reference represents (e.g. an @@ -54,560 +52,549 @@ */ public class TypeVisualizer { - /** - * Creates a dot file at dest that contains a digraph for the structure of {@code type}. - * - * @param dest the destination dot file - * @param type the type to be written - */ - public static void drawToDot(File dest, AnnotatedTypeMirror type) { - Drawing drawer = new Drawing("Type", type); - drawer.draw(dest); + /** + * Creates a dot file at dest that contains a digraph for the structure of {@code type}. + * + * @param dest the destination dot file + * @param type the type to be written + */ + public static void drawToDot(File dest, AnnotatedTypeMirror type) { + Drawing drawer = new Drawing("Type", type); + drawer.draw(dest); + } + + /** + * Creates a dot file at dest that contains a digraph for the structure of {@code type}. + * + * @param dest the destination dot file, this string will be directly passed to new File(dest) + * @param type the type to be written + */ + public static void drawToDot(String dest, AnnotatedTypeMirror type) { + drawToDot(new File(dest), type); + } + + /** + * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that + * file into a png at the location dest. This method will fail if a temp file can't be created. + * + * @param dest the destination png file + * @param type the type to be written + */ + public static void drawToPng(File dest, AnnotatedTypeMirror type) { + try { + File dotFile = File.createTempFile(dest.getName(), ".dot"); + drawToDot(dotFile, type); + execDotToPng(dotFile, dest); + + } catch (Exception exc) { + throw new RuntimeException(exc); + } + } + + /** + * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that + * file into a png at the location dest. This method will fail if a temp file can't be created. + * + * @param dest the destination png file, this string will be directly passed to new File(dest) + * @param type the type to be written + */ + public static void drawToPng(String dest, AnnotatedTypeMirror type) { + drawToPng(new File(dest), type); + } + + /** + * Converts the given dot file to a png file at the specified location. This method calls the + * program "dot" from Runtime.exec and will fail if "dot" is not on your class path. + * + * @param dotFile the dot file to convert + * @param pngFile the destination of the resultant png file + */ + public static void execDotToPng(File dotFile, File pngFile) { + String[] cmd = + new String[] {"dot", "-Tpng", dotFile.getAbsolutePath(), "-o", pngFile.getAbsolutePath()}; + System.out.println("Printing dotFile: " + dotFile + " to loc: " + pngFile); + System.out.flush(); + ExecUtil.execute(cmd, System.out, System.err); + } + + /** + * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to + * a dot file at {@code directory/varName}. + * + * @return true if the type variable was printed, otherwise false + */ + public static boolean printTypevarToDotIfMatches( + AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { + return printTypevarIfMatches(typeVariable, typeVarNames, directory, false); + } + + /** + * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to + * a png file at {@code directory/varName.png}. + * + * @return true if the type variable was printed, otherwise false + */ + public static boolean printTypevarToPngIfMatches( + AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { + return printTypevarIfMatches(typeVariable, typeVarNames, directory, true); + } + + private static boolean printTypevarIfMatches( + AnnotatedTypeVariable typeVariable, + List typeVarNames, + String directory, + boolean png) { + String dirPath = directory.endsWith(File.separator) ? directory : directory + File.separator; + String varName = typeVariable.getUnderlyingType().asElement().toString(); + + if (typeVarNames.contains(varName)) { + if (png) { + TypeVisualizer.drawToPng(dirPath + varName + ".png", typeVariable); + } else { + TypeVisualizer.drawToDot(dirPath + varName + ".dot", typeVariable); + } + return true; } + return false; + } + + /** + * This class exists because there is no LinkedIdentityHashMap. + * + *

Node is just a wrapper around type mirror that replaces .equals with referential equality. + * This is done to preserve the order types were traversed so that printing will occur in a + * hierarchical order. However, since there is no LinkedIdentityHashMap, it was easiest to just + * create a wrapper that performed referential equality on types and use a LinkedHashMap. + */ + private static class Node { + /** The delegate; that is, the wrapped value. */ + private final @InternedDistinct AnnotatedTypeMirror type; + /** - * Creates a dot file at dest that contains a digraph for the structure of {@code type}. + * Create a new Node that wraps the given type. * - * @param dest the destination dot file, this string will be directly passed to new File(dest) - * @param type the type to be written + * @param type the type that the newly-constructed Node represents */ - public static void drawToDot(String dest, AnnotatedTypeMirror type) { - drawToDot(new File(dest), type); + private Node(@FindDistinct AnnotatedTypeMirror type) { + this.type = type; } - /** - * Draws a dot file for type in a temporary directory then calls the "dot" program to convert - * that file into a png at the location dest. This method will fail if a temp file can't be - * created. - * - * @param dest the destination png file - * @param type the type to be written - */ - public static void drawToPng(File dest, AnnotatedTypeMirror type) { - try { - File dotFile = File.createTempFile(dest.getName(), ".dot"); - drawToDot(dotFile, type); - execDotToPng(dotFile, dest); - - } catch (Exception exc) { - throw new RuntimeException(exc); - } + @Override + public int hashCode() { + return type.hashCode(); } - /** - * Draws a dot file for type in a temporary directory then calls the "dot" program to convert - * that file into a png at the location dest. This method will fail if a temp file can't be - * created. - * - * @param dest the destination png file, this string will be directly passed to new File(dest) - * @param type the type to be written - */ - public static void drawToPng(String dest, AnnotatedTypeMirror type) { - drawToPng(new File(dest), type); + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof Node) { + return ((Node) obj).type == this.type; + } + + return false; } + } - /** - * Converts the given dot file to a png file at the specified location. This method calls the - * program "dot" from Runtime.exec and will fail if "dot" is not on your class path. - * - * @param dotFile the dot file to convert - * @param pngFile the destination of the resultant png file - */ - public static void execDotToPng(File dotFile, File pngFile) { - String[] cmd = - new String[] { - "dot", "-Tpng", dotFile.getAbsolutePath(), "-o", pngFile.getAbsolutePath() - }; - System.out.println("Printing dotFile: " + dotFile + " to loc: " + pngFile); - System.out.flush(); - ExecUtil.execute(cmd, System.out, System.err); + /** + * Drawing visits a type and writes a dot file to the location specified. It contains data + * structures to hold the intermediate dot information before printing. + */ + private static class Drawing { + /** A map from Node (type) to a dot string declaring that node. */ + private final Map nodes = new LinkedHashMap<>(); + + /** List of connections between nodes. Lines refer to identifiers in nodes.values(). */ + private final List lines = new ArrayList<>(); + + private final String graphName; + + /** The type being drawn. */ + private final AnnotatedTypeMirror type; + + /** Used to identify nodes uniquely. This field is monotonically increasing. */ + private int nextId = 0; + + public Drawing(String graphName, AnnotatedTypeMirror type) { + this.graphName = graphName; + this.type = type; } - /** - * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable - * to a dot file at {@code directory/varName}. - * - * @return true if the type variable was printed, otherwise false - */ - public static boolean printTypevarToDotIfMatches( - AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { - return printTypevarIfMatches(typeVariable, typeVarNames, directory, false); + public void draw(File file) { + addNodes(type); + addConnections(); + write(file); + } + + private void addNodes(AnnotatedTypeMirror type) { + new NodeDrawer().visit(type); + } + + private void addConnections() { + ConnectionDrawer connectionDrawer = new ConnectionDrawer(); + for (Node node : nodes.keySet()) { + connectionDrawer.visit(node.type); + } } /** - * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable - * to a png file at {@code directory/varName.png}. + * Write this to a file. * - * @return true if the type variable was printed, otherwise false + * @param file the file to write to */ - public static boolean printTypevarToPngIfMatches( - AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { - return printTypevarIfMatches(typeVariable, typeVarNames, directory, true); - } - - private static boolean printTypevarIfMatches( - AnnotatedTypeVariable typeVariable, - List typeVarNames, - String directory, - boolean png) { - String dirPath = - directory.endsWith(File.separator) ? directory : directory + File.separator; - String varName = typeVariable.getUnderlyingType().asElement().toString(); - - if (typeVarNames.contains(varName)) { - if (png) { - TypeVisualizer.drawToPng(dirPath + varName + ".png", typeVariable); - } else { - TypeVisualizer.drawToDot(dirPath + varName + ".dot", typeVariable); - } - return true; + private void write(File file) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + writer.write("digraph " + graphName + "{"); + writer.newLine(); + for (String line : lines) { + writer.write(line + ";"); + writer.newLine(); } - - return false; + writer.write("}"); + writer.flush(); + } catch (IOException exc) { + throw new BugInCF(exc, "Exception visualizing type:%nfile=%s%ntype=%s", file, type); + } } /** - * This class exists because there is no LinkedIdentityHashMap. - * - *

Node is just a wrapper around type mirror that replaces .equals with referential equality. - * This is done to preserve the order types were traversed so that printing will occur in a - * hierarchical order. However, since there is no LinkedIdentityHashMap, it was easiest to just - * create a wrapper that performed referential equality on types and use a LinkedHashMap. + * Connection drawer is used to add the connections between all the nodes created by the + * NodeDrawer. It is not a scanner and is called on every node in the nodes map. */ - private static class Node { - /** The delegate; that is, the wrapped value. */ - private final @InternedDistinct AnnotatedTypeMirror type; - - /** - * Create a new Node that wraps the given type. - * - * @param type the type that the newly-constructed Node represents - */ - private Node(@FindDistinct AnnotatedTypeMirror type) { - this.type = type; + private class ConnectionDrawer implements AnnotatedTypeVisitor { + + @Override + public Void visit(AnnotatedTypeMirror type) { + type.accept(this, null); + return null; + } + + @Override + public Void visit(AnnotatedTypeMirror type, Void aVoid) { + return visit(type); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + List typeArgs = type.getTypeArguments(); + for (int i = 0; i < typeArgs.size(); i++) { + lines.add(connect(type, typeArgs.get(i)) + " " + makeTypeArgLabel(i)); } + return null; + } + + @Override + public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { + List bounds = type.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + lines.add(connect(type, bounds.get(i)) + " " + makeLabel("&")); + } + return null; + } + + @Override + public Void visitUnion(AnnotatedUnionType type, Void aVoid) { + List alternatives = type.getAlternatives(); + for (int i = 0; i < alternatives.size(); i++) { + lines.add(connect(type, alternatives.get(i)) + " " + makeLabel("|")); + } + return null; + } + + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - @Override - public int hashCode() { - return type.hashCode(); + ExecutableElement methodElem = type.getElement(); + lines.add(connect(type, type.getReturnType()) + " " + makeLabel("returns")); + + List typeVarElems = methodElem.getTypeParameters(); + List typeVars = type.getTypeVariables(); + for (int i = 0; i < typeVars.size(); i++) { + String typeVarName = typeVarElems.get(i).getSimpleName().toString(); + lines.add(connect(type, typeVars.get(i)) + " " + makeMethodTypeArgLabel(typeVarName)); } - @Override - public boolean equals(@Nullable Object obj) { - if (obj == null) { - return false; - } - if (obj instanceof Node) { - return ((Node) obj).type == this.type; - } + if (type.getReceiverType() != null) { + lines.add(connect(type, type.getReceiverType()) + " " + makeLabel("receiver")); + } - return false; + List paramElems = methodElem.getParameters(); + List params = type.getParameterTypes(); + for (int i = 0; i < params.size(); i++) { + String paramName = paramElems.get(i).getSimpleName().toString(); + lines.add(connect(type, params.get(i)) + " " + makeParamLabel(paramName)); } + + List thrown = type.getThrownTypes(); + for (int i = 0; i < thrown.size(); i++) { + lines.add(connect(type, thrown.get(i)) + " " + makeThrownLabel(i)); + } + + return null; + } + + @Override + public Void visitArray(AnnotatedArrayType type, Void aVoid) { + lines.add(connect(type, type.getComponentType())); + return null; + } + + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { + lines.add(connect(type, type.getUpperBound()) + " " + makeLabel("extends")); + lines.add(connect(type, type.getLowerBound()) + " " + makeLabel("super")); + return null; + } + + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { + return null; + } + + @Override + public Void visitNoType(AnnotatedNoType type, Void aVoid) { + return null; + } + + @Override + public Void visitNull(AnnotatedNullType type, Void aVoid) { + return null; + } + + @Override + public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { + lines.add(connect(type, type.getExtendsBound()) + " " + makeLabel("extends")); + lines.add(connect(type, type.getSuperBound()) + " " + makeLabel("super")); + return null; + } + + private String connect(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + return nodes.get(new Node(from)) + " -> " + nodes.get(new Node(to)); + } + + private String makeLabel(String text) { + return "[label=\"" + text + "\"]"; + } + + private String makeTypeArgLabel(int argIndex) { + return makeLabel("<" + argIndex + ">"); + } + + private String makeMethodTypeArgLabel(String paramName) { + return makeLabel("<" + paramName + ">"); + } + + private String makeParamLabel(String paramName) { + return makeLabel(paramName); + } + + private String makeThrownLabel(int index) { + return makeLabel("throws: " + index); + } } + /** The default annotation formatter. */ + private static final DefaultAnnotationFormatter annoFormatter = + new DefaultAnnotationFormatter(); + /** - * Drawing visits a type and writes a dot file to the location specified. It contains data - * structures to hold the intermediate dot information before printing. + * Scans types and adds a mapping from type to dot node declaration representing that type in + * the enclosing drawing. */ - private static class Drawing { - /** A map from Node (type) to a dot string declaring that node. */ - private final Map nodes = new LinkedHashMap<>(); - - /** List of connections between nodes. Lines refer to identifiers in nodes.values(). */ - private final List lines = new ArrayList<>(); + private class NodeDrawer implements AnnotatedTypeVisitor { - private final String graphName; + /** Create a new NodeDrawer. */ + public NodeDrawer() {} - /** The type being drawn. */ - private final AnnotatedTypeMirror type; + private void visitAll(List types) { + for (AnnotatedTypeMirror type : types) { + visit(type); + } + } - /** Used to identify nodes uniquely. This field is monotonically increasing. */ - private int nextId = 0; + @Override + public Void visit(AnnotatedTypeMirror type) { + if (type != null) { + type.accept(this, null); + } - public Drawing(String graphName, AnnotatedTypeMirror type) { - this.graphName = graphName; - this.type = type; + return null; + } + + @Override + public Void visit(AnnotatedTypeMirror type, Void aVoid) { + return visit(type); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode( + type, + getAnnoStr(type) + + " " + + type.getUnderlyingType().asElement().getSimpleName() + + (type.getTypeArguments().isEmpty() ? "" : "<...>"), + "shape=box"); + visitAll(type.getTypeArguments()); + } + return null; + } + + @Override + public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " Intersection", "shape=octagon"); + visitAll(type.getBounds()); } - public void draw(File file) { - addNodes(type); - addConnections(); - write(file); + return null; + } + + @Override + public Void visitUnion(AnnotatedUnionType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " Union", "shape=doubleoctagon"); + visitAll(type.getAlternatives()); } + return null; + } - private void addNodes(AnnotatedTypeMirror type) { - new NodeDrawer().visit(type); + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, makeMethodLabel(type), "shape=box,peripheries=2"); + + visit(type.getReturnType()); + visitAll(type.getTypeVariables()); + + visit(type.getReceiverType()); + visitAll(type.getParameterTypes()); + + visitAll(type.getThrownTypes()); + + } else { + throw new BugInCF("Executable types should never be recursive%ntype=%s", type); + } + return null; + } + + @Override + public Void visitArray(AnnotatedArrayType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + "[]"); + visit(type.getComponentType()); + } + return null; + } + + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode( + type, + getAnnoStr(type) + " " + type.getUnderlyingType().asElement().getSimpleName(), + "shape=invtrapezium"); + visit(type.getUpperBound()); + visit(type.getLowerBound()); } + return null; + } - private void addConnections() { - ConnectionDrawer connectionDrawer = new ConnectionDrawer(); - for (Node node : nodes.keySet()) { - connectionDrawer.visit(node.type); - } + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " " + type.getKind()); } + return null; + } - /** - * Write this to a file. - * - * @param file the file to write to - */ - private void write(File file) { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { - writer.write("digraph " + graphName + "{"); - writer.newLine(); - for (String line : lines) { - writer.write(line + ";"); - writer.newLine(); - } - writer.write("}"); - writer.flush(); - } catch (IOException exc) { - throw new BugInCF(exc, "Exception visualizing type:%nfile=%s%ntype=%s", file, type); - } + @Override + public Void visitNoType(AnnotatedNoType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " None"); } + return null; + } - /** - * Connection drawer is used to add the connections between all the nodes created by the - * NodeDrawer. It is not a scanner and is called on every node in the nodes map. - */ - private class ConnectionDrawer implements AnnotatedTypeVisitor { - - @Override - public Void visit(AnnotatedTypeMirror type) { - type.accept(this, null); - return null; - } - - @Override - public Void visit(AnnotatedTypeMirror type, Void aVoid) { - return visit(type); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - List typeArgs = type.getTypeArguments(); - for (int i = 0; i < typeArgs.size(); i++) { - lines.add(connect(type, typeArgs.get(i)) + " " + makeTypeArgLabel(i)); - } - return null; - } - - @Override - public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { - List bounds = type.getBounds(); - for (int i = 0; i < bounds.size(); i++) { - lines.add(connect(type, bounds.get(i)) + " " + makeLabel("&")); - } - return null; - } - - @Override - public Void visitUnion(AnnotatedUnionType type, Void aVoid) { - List alternatives = type.getAlternatives(); - for (int i = 0; i < alternatives.size(); i++) { - lines.add(connect(type, alternatives.get(i)) + " " + makeLabel("|")); - } - return null; - } - - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - - ExecutableElement methodElem = type.getElement(); - lines.add(connect(type, type.getReturnType()) + " " + makeLabel("returns")); - - List typeVarElems = methodElem.getTypeParameters(); - List typeVars = type.getTypeVariables(); - for (int i = 0; i < typeVars.size(); i++) { - String typeVarName = typeVarElems.get(i).getSimpleName().toString(); - lines.add( - connect(type, typeVars.get(i)) - + " " - + makeMethodTypeArgLabel(typeVarName)); - } - - if (type.getReceiverType() != null) { - lines.add(connect(type, type.getReceiverType()) + " " + makeLabel("receiver")); - } - - List paramElems = methodElem.getParameters(); - List params = type.getParameterTypes(); - for (int i = 0; i < params.size(); i++) { - String paramName = paramElems.get(i).getSimpleName().toString(); - lines.add(connect(type, params.get(i)) + " " + makeParamLabel(paramName)); - } - - List thrown = type.getThrownTypes(); - for (int i = 0; i < thrown.size(); i++) { - lines.add(connect(type, thrown.get(i)) + " " + makeThrownLabel(i)); - } - - return null; - } - - @Override - public Void visitArray(AnnotatedArrayType type, Void aVoid) { - lines.add(connect(type, type.getComponentType())); - return null; - } - - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { - lines.add(connect(type, type.getUpperBound()) + " " + makeLabel("extends")); - lines.add(connect(type, type.getLowerBound()) + " " + makeLabel("super")); - return null; - } - - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - return null; - } - - @Override - public Void visitNoType(AnnotatedNoType type, Void aVoid) { - return null; - } - - @Override - public Void visitNull(AnnotatedNullType type, Void aVoid) { - return null; - } - - @Override - public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { - lines.add(connect(type, type.getExtendsBound()) + " " + makeLabel("extends")); - lines.add(connect(type, type.getSuperBound()) + " " + makeLabel("super")); - return null; - } - - private String connect(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - return nodes.get(new Node(from)) + " -> " + nodes.get(new Node(to)); - } - - private String makeLabel(String text) { - return "[label=\"" + text + "\"]"; - } - - private String makeTypeArgLabel(int argIndex) { - return makeLabel("<" + argIndex + ">"); - } - - private String makeMethodTypeArgLabel(String paramName) { - return makeLabel("<" + paramName + ">"); - } - - private String makeParamLabel(String paramName) { - return makeLabel(paramName); - } - - private String makeThrownLabel(int index) { - return makeLabel("throws: " + index); - } + @Override + public Void visitNull(AnnotatedNullType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " NullType"); } + return null; + } + + @Override + public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + "?", "shape=trapezium"); + visit(type.getExtendsBound()); + visit(type.getSuperBound()); + } + return null; + } + + /** + * Returns a string representation of the annotations on a type. + * + * @param atm an annotated type + * @return a string representation of the annotations on {@code atm} + */ + public String getAnnoStr(AnnotatedTypeMirror atm) { + StringJoiner sj = new StringJoiner(" "); + for (AnnotationMirror anno : atm.getAnnotations()) { + // TODO: More comprehensive escaping + sj.add(annoFormatter.formatAnnotationMirror(anno).replace("\"", "\\")); + } + return sj.toString(); + } - /** The default annotation formatter. */ - private static final DefaultAnnotationFormatter annoFormatter = - new DefaultAnnotationFormatter(); - - /** - * Scans types and adds a mapping from type to dot node declaration representing that type - * in the enclosing drawing. - */ - private class NodeDrawer implements AnnotatedTypeVisitor { - - /** Create a new NodeDrawer. */ - public NodeDrawer() {} - - private void visitAll(List types) { - for (AnnotatedTypeMirror type : types) { - visit(type); - } - } - - @Override - public Void visit(AnnotatedTypeMirror type) { - if (type != null) { - type.accept(this, null); - } - - return null; - } - - @Override - public Void visit(AnnotatedTypeMirror type, Void aVoid) { - return visit(type); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode( - type, - getAnnoStr(type) - + " " - + type.getUnderlyingType().asElement().getSimpleName() - + (type.getTypeArguments().isEmpty() ? "" : "<...>"), - "shape=box"); - visitAll(type.getTypeArguments()); - } - return null; - } - - @Override - public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " Intersection", "shape=octagon"); - visitAll(type.getBounds()); - } - - return null; - } - - @Override - public Void visitUnion(AnnotatedUnionType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " Union", "shape=doubleoctagon"); - visitAll(type.getAlternatives()); - } - return null; - } - - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, makeMethodLabel(type), "shape=box,peripheries=2"); - - visit(type.getReturnType()); - visitAll(type.getTypeVariables()); - - visit(type.getReceiverType()); - visitAll(type.getParameterTypes()); - - visitAll(type.getThrownTypes()); - - } else { - throw new BugInCF("Executable types should never be recursive%ntype=%s", type); - } - return null; - } - - @Override - public Void visitArray(AnnotatedArrayType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + "[]"); - visit(type.getComponentType()); - } - return null; - } - - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode( - type, - getAnnoStr(type) - + " " - + type.getUnderlyingType().asElement().getSimpleName(), - "shape=invtrapezium"); - visit(type.getUpperBound()); - visit(type.getLowerBound()); - } - return null; - } - - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " " + type.getKind()); - } - return null; - } - - @Override - public Void visitNoType(AnnotatedNoType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " None"); - } - return null; - } - - @Override - public Void visitNull(AnnotatedNullType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " NullType"); - } - return null; - } - - @Override - public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + "?", "shape=trapezium"); - visit(type.getExtendsBound()); - visit(type.getSuperBound()); - } - return null; - } - - /** - * Returns a string representation of the annotations on a type. - * - * @param atm an annotated type - * @return a string representation of the annotations on {@code atm} - */ - public String getAnnoStr(AnnotatedTypeMirror atm) { - StringJoiner sj = new StringJoiner(" "); - for (AnnotationMirror anno : atm.getAnnotations()) { - // TODO: More comprehensive escaping - sj.add(annoFormatter.formatAnnotationMirror(anno).replace("\"", "\\")); - } - return sj.toString(); - } - - public boolean checkOrAdd(AnnotatedTypeMirror atm) { - Node node = new Node(atm); - if (nodes.containsKey(node)) { - return false; - } - nodes.put(node, String.valueOf(nextId++)); - return true; - } - - public String makeLabeledNode(AnnotatedTypeMirror type, String label) { - return makeLabeledNode(type, label, null); - } - - public String makeLabeledNode( - AnnotatedTypeMirror type, String label, String attributes) { - String attr = (attributes != null) ? ", " + attributes : ""; - return nodes.get(new Node(type)) + " [label=\"" + label + "\"" + attr + "]"; - } - - public void addLabeledNode(AnnotatedTypeMirror type, String label) { - lines.add(makeLabeledNode(type, label)); - } - - public void addLabeledNode(AnnotatedTypeMirror type, String label, String attributes) { - lines.add(makeLabeledNode(type, label, attributes)); - } - - public String makeMethodLabel(AnnotatedExecutableType methodType) { - ExecutableElement methodElem = methodType.getElement(); - - StringBuilder builder = new StringBuilder(); - builder.append(methodElem.getReturnType().toString()); - builder.append(" <"); - - builder.append(StringsPlume.join(", ", methodElem.getTypeParameters())); - builder.append("> "); - - builder.append(methodElem.getSimpleName().toString()); - - builder.append("("); - builder.append(StringsPlume.join(",", methodElem.getParameters())); - builder.append(")"); - return builder.toString(); - } + public boolean checkOrAdd(AnnotatedTypeMirror atm) { + Node node = new Node(atm); + if (nodes.containsKey(node)) { + return false; } + nodes.put(node, String.valueOf(nextId++)); + return true; + } + + public String makeLabeledNode(AnnotatedTypeMirror type, String label) { + return makeLabeledNode(type, label, null); + } + + public String makeLabeledNode(AnnotatedTypeMirror type, String label, String attributes) { + String attr = (attributes != null) ? ", " + attributes : ""; + return nodes.get(new Node(type)) + " [label=\"" + label + "\"" + attr + "]"; + } + + public void addLabeledNode(AnnotatedTypeMirror type, String label) { + lines.add(makeLabeledNode(type, label)); + } + + public void addLabeledNode(AnnotatedTypeMirror type, String label, String attributes) { + lines.add(makeLabeledNode(type, label, attributes)); + } + + public String makeMethodLabel(AnnotatedExecutableType methodType) { + ExecutableElement methodElem = methodType.getElement(); + + StringBuilder builder = new StringBuilder(); + builder.append(methodElem.getReturnType().toString()); + builder.append(" <"); + + builder.append(StringsPlume.join(", ", methodElem.getTypeParameters())); + builder.append("> "); + + builder.append(methodElem.getSimpleName().toString()); + + builder.append("("); + builder.append(StringsPlume.join(",", methodElem.getParameters())); + builder.append(")"); + return builder.toString(); + } } + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java index c41e8b48979..67833460d1e 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java @@ -17,21 +17,18 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.util.Log; - -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.source.SourceVisitor; -import org.checkerframework.framework.source.SupportedOptions; -import org.checkerframework.javacutil.AnnotationProvider; - import java.util.HashMap; import java.util.Map; import java.util.StringJoiner; import java.util.TreeSet; - import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Name; import javax.tools.Diagnostic; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.source.SourceVisitor; +import org.checkerframework.framework.source.SupportedOptions; +import org.checkerframework.javacutil.AnnotationProvider; /** * An annotation processor for counting the annotations in a program and for listing the potential @@ -73,252 +70,252 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) public class AnnotationStatistics extends SourceChecker { - /** - * Map from annotation name (as the {@code toString()} of its Name representation) to number of - * times the annotation was written in source code. - */ - final Map annotationCount = new HashMap<>(); - - /** Creates an AnnotationStatistics. */ - public AnnotationStatistics() { - // This checker never issues any warnings, so don't warn about - // @SuppressWarnings("allcheckers:..."). - this.useAllcheckersPrefix = false; - } - - @Override - public void typeProcessingOver() { - Log log = getCompilerLog(); - String output; - if (log.nerrors != 0) { - output = "Not counting annotations, because compilation issued an error."; - } else if (annotationCount.isEmpty()) { - output = "No annotations found."; - } else { - StringJoiner sj = new StringJoiner(System.lineSeparator()); - sj.add("Found annotations: "); - // alphabetize the annotations - for (String key : new TreeSet<>(annotationCount.keySet())) { - sj.add(key + "\t" + annotationCount.get(key)); - } - output = sj.toString(); - } - if (hasOption("annotationserror")) { - // Issue annotation details a compiler warning rather than printed. This may be useful, - // for example, when Maven swallows non-warning output from the annotation processor. - getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.WARNING, output); - } else { - System.out.println(output); - } - super.typeProcessingOver(); + /** + * Map from annotation name (as the {@code toString()} of its Name representation) to number of + * times the annotation was written in source code. + */ + final Map annotationCount = new HashMap<>(); + + /** Creates an AnnotationStatistics. */ + public AnnotationStatistics() { + // This checker never issues any warnings, so don't warn about + // @SuppressWarnings("allcheckers:..."). + this.useAllcheckersPrefix = false; + } + + @Override + public void typeProcessingOver() { + Log log = getCompilerLog(); + String output; + if (log.nerrors != 0) { + output = "Not counting annotations, because compilation issued an error."; + } else if (annotationCount.isEmpty()) { + output = "No annotations found."; + } else { + StringJoiner sj = new StringJoiner(System.lineSeparator()); + sj.add("Found annotations: "); + // alphabetize the annotations + for (String key : new TreeSet<>(annotationCount.keySet())) { + sj.add(key + "\t" + annotationCount.get(key)); + } + output = sj.toString(); } - - /** Increment the number of times annotation with name {@code annoName} has appeared. */ - protected void incrementCount(Name annoName) { - String annoString = annoName.toString(); - if (!annotationCount.containsKey(annoString)) { - annotationCount.put(annoString, 1); - } else { - annotationCount.put(annoString, annotationCount.get(annoString) + 1); - } + if (hasOption("annotationserror")) { + // Issue annotation details a compiler warning rather than printed. This may be useful, + // for example, when Maven swallows non-warning output from the annotation processor. + getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.WARNING, output); + } else { + System.out.println(output); } - - @Override - protected SourceVisitor createSourceVisitor() { - return new Visitor(this); + super.typeProcessingOver(); + } + + /** Increment the number of times annotation with name {@code annoName} has appeared. */ + protected void incrementCount(Name annoName) { + String annoString = annoName.toString(); + if (!annotationCount.containsKey(annoString)) { + annotationCount.put(annoString, 1); + } else { + annotationCount.put(annoString, annotationCount.get(annoString) + 1); } + } - class Visitor extends SourceVisitor { + @Override + protected SourceVisitor createSourceVisitor() { + return new Visitor(this); + } - /** Whether annotation locations should be printed. */ - private final boolean locations; + class Visitor extends SourceVisitor { - /** Whether annotation details should be printed. */ - private final boolean annotations; + /** Whether annotation locations should be printed. */ + private final boolean locations; - /** Whether only a summary should be printed. */ - private final boolean annotationsummaryonly; + /** Whether annotation details should be printed. */ + private final boolean annotations; - /** - * Create a new Visitor. - * - * @param l the AnnotationStatistics object, used for obtaining command-line arguments - */ - public Visitor(AnnotationStatistics l) { - super(l); + /** Whether only a summary should be printed. */ + private final boolean annotationsummaryonly; - locations = !l.hasOption("nolocations"); - annotations = l.hasOption("annotations"); - annotationsummaryonly = l.hasOption("annotationsummaryonly"); - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - if (annotations) { - Name annoName = ((JCAnnotation) tree).annotationType.type.tsym.getQualifiedName(); - incrementCount(annoName); - - // An annotation is a body annotation if, while ascending the AST from the - // annotation to the root, we find a block immediately enclosed by a method. - // - // If an annotation is not a body annotation, it's a signature (declaration) - // annotation. + /** + * Create a new Visitor. + * + * @param l the AnnotationStatistics object, used for obtaining command-line arguments + */ + public Visitor(AnnotationStatistics l) { + super(l); - boolean isBodyAnnotation = false; - TreePath path = getCurrentPath(); - Tree prev = null; - for (Tree t : path) { - if (prev != null - && prev.getKind() == Tree.Kind.BLOCK - && t.getKind() == Tree.Kind.METHOD) { - isBodyAnnotation = true; - break; - } - prev = t; - } + locations = !l.hasOption("nolocations"); + annotations = l.hasOption("annotations"); + annotationsummaryonly = l.hasOption("annotationsummaryonly"); + } - if (!annotationsummaryonly) { - System.out.printf( - ":annotation %s %s %s %s%n", - tree.getAnnotationType(), - tree, - root.getSourceFile().getName(), - (isBodyAnnotation ? "body" : "sig")); - } - } - return super.visitAnnotation(tree, p); + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + if (annotations) { + Name annoName = ((JCAnnotation) tree).annotationType.type.tsym.getQualifiedName(); + incrementCount(annoName); + + // An annotation is a body annotation if, while ascending the AST from the + // annotation to the root, we find a block immediately enclosed by a method. + // + // If an annotation is not a body annotation, it's a signature (declaration) + // annotation. + + boolean isBodyAnnotation = false; + TreePath path = getCurrentPath(); + Tree prev = null; + for (Tree t : path) { + if (prev != null + && prev.getKind() == Tree.Kind.BLOCK + && t.getKind() == Tree.Kind.METHOD) { + isBodyAnnotation = true; + break; + } + prev = t; } - @Override - public Void visitArrayType(ArrayTypeTree tree, Void p) { - if (locations) { - System.out.println("array type"); - } - return super.visitArrayType(tree, p); + if (!annotationsummaryonly) { + System.out.printf( + ":annotation %s %s %s %s%n", + tree.getAnnotationType(), + tree, + root.getSourceFile().getName(), + (isBodyAnnotation ? "body" : "sig")); } + } + return super.visitAnnotation(tree, p); + } - @Override - public Void visitClass(ClassTree tree, Void p) { - if (shouldSkipDefs(tree)) { - // Not "return super.visitClass(classTree, p);" because that would recursively call - // visitors on subtrees; we want to skip the class entirely. - return null; - } - if (locations) { - System.out.println("class"); - if (tree.getExtendsClause() != null) { - System.out.println("class extends"); - } - for (@SuppressWarnings("unused") Tree t : tree.getImplementsClause()) { - System.out.println("class implements"); - } - } - return super.visitClass(tree, p); - } + @Override + public Void visitArrayType(ArrayTypeTree tree, Void p) { + if (locations) { + System.out.println("array type"); + } + return super.visitArrayType(tree, p); + } - @Override - public Void visitMethod(MethodTree tree, Void p) { - if (locations) { - System.out.println("method return"); - System.out.println("method receiver"); - for (@SuppressWarnings("unused") Tree t : tree.getThrows()) { - System.out.println("method throws"); - } - for (@SuppressWarnings("unused") Tree t : tree.getParameters()) { - System.out.println("method param"); - } - } - return super.visitMethod(tree, p); + @Override + public Void visitClass(ClassTree tree, Void p) { + if (shouldSkipDefs(tree)) { + // Not "return super.visitClass(classTree, p);" because that would recursively call + // visitors on subtrees; we want to skip the class entirely. + return null; + } + if (locations) { + System.out.println("class"); + if (tree.getExtendsClause() != null) { + System.out.println("class extends"); } - - @Override - public Void visitVariable(VariableTree tree, Void p) { - if (locations) { - System.out.println("variable"); - } - return super.visitVariable(tree, p); + for (@SuppressWarnings("unused") Tree t : tree.getImplementsClause()) { + System.out.println("class implements"); } + } + return super.visitClass(tree, p); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (locations) { - for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { - System.out.println("method invocation type argument"); - } - } - return super.visitMethodInvocation(tree, p); + @Override + public Void visitMethod(MethodTree tree, Void p) { + if (locations) { + System.out.println("method return"); + System.out.println("method receiver"); + for (@SuppressWarnings("unused") Tree t : tree.getThrows()) { + System.out.println("method throws"); } - - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - if (locations) { - System.out.println("new class"); - for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { - System.out.println("new class type argument"); - } - } - return super.visitNewClass(tree, p); + for (@SuppressWarnings("unused") Tree t : tree.getParameters()) { + System.out.println("method param"); } + } + return super.visitMethod(tree, p); + } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - if (locations) { - System.out.println("new array"); - for (@SuppressWarnings("unused") Tree t : tree.getDimensions()) { - System.out.println("new array dimension"); - } - } - return super.visitNewArray(tree, p); - } + @Override + public Void visitVariable(VariableTree tree, Void p) { + if (locations) { + System.out.println("variable"); + } + return super.visitVariable(tree, p); + } - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - if (locations) { - System.out.println("typecast"); - } - return super.visitTypeCast(tree, p); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (locations) { + for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { + System.out.println("method invocation type argument"); } + } + return super.visitMethodInvocation(tree, p); + } - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - if (locations) { - System.out.println("instanceof"); - } - return super.visitInstanceOf(tree, p); + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + if (locations) { + System.out.println("new class"); + for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { + System.out.println("new class type argument"); } + } + return super.visitNewClass(tree, p); + } - @Override - public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { - if (locations) { - for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { - System.out.println("parameterized type"); - } - } - return super.visitParameterizedType(tree, p); + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + if (locations) { + System.out.println("new array"); + for (@SuppressWarnings("unused") Tree t : tree.getDimensions()) { + System.out.println("new array dimension"); } + } + return super.visitNewArray(tree, p); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + if (locations) { + System.out.println("typecast"); + } + return super.visitTypeCast(tree, p); + } - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void p) { - if (locations) { - for (@SuppressWarnings("unused") Tree t : tree.getBounds()) { - System.out.println("type parameter bound"); - } - } - return super.visitTypeParameter(tree, p); + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + if (locations) { + System.out.println("instanceof"); + } + return super.visitInstanceOf(tree, p); + } + + @Override + public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { + if (locations) { + for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { + System.out.println("parameterized type"); } + } + return super.visitParameterizedType(tree, p); + } - @Override - public Void visitWildcard(WildcardTree tree, Void p) { - if (locations) { - System.out.println("wildcard"); - } - return super.visitWildcard(tree, p); + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void p) { + if (locations) { + for (@SuppressWarnings("unused") Tree t : tree.getBounds()) { + System.out.println("type parameter bound"); } + } + return super.visitTypeParameter(tree, p); } @Override - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for this class."); + public Void visitWildcard(WildcardTree tree, Void p) { + if (locations) { + System.out.println("wildcard"); + } + return super.visitWildcard(tree, p); } + } + + @Override + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for this class."); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java index 5eed8083588..ad3e5938e7c 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java @@ -12,19 +12,16 @@ import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.TypeCastTree; import com.sun.tools.javac.util.Log; - -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.source.SourceVisitor; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.TreeUtils; - import java.util.List; - import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.source.SourceVisitor; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TreeUtils; /** * An annotation processor for counting a few specific aspects about the size of Java code: @@ -47,160 +44,160 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) public class JavaCodeStatistics extends SourceChecker { - /** The number of type parameter declarations and uses. */ - int generics = 0; - - /** The number of array accesses and dimensions in array creations. */ - int arrayAccesses = 0; - - /** The number of type casts. */ - int typecasts = 0; - - String[] warningKeys = { - "index", "lowerbound", "samelen", "searchindex", "substringindex", "upperbound" - }; + /** The number of type parameter declarations and uses. */ + int generics = 0; + + /** The number of array accesses and dimensions in array creations. */ + int arrayAccesses = 0; + + /** The number of type casts. */ + int typecasts = 0; + + String[] warningKeys = { + "index", "lowerbound", "samelen", "searchindex", "substringindex", "upperbound" + }; + + /** + * The number of warning suppressions with at least one key that matches one of the Index Checker + * subcheckers. + */ + int numberOfIndexWarningSuppressions = 0; + + /** The SuppressWarnings.value field/element. */ + final ExecutableElement suppressWarningsValueElement = + TreeUtils.getMethod(SuppressWarnings.class, "value", 0, processingEnv); + + /** Creates a JavaCodeStatistics. */ + public JavaCodeStatistics() { + // This checker never issues any warnings, so don't warn about + // @SuppressWarnings("allcheckers:..."). + this.useAllcheckersPrefix = false; + } + + @Override + public void typeProcessingOver() { + Log log = getCompilerLog(); + if (log.nerrors != 0) { + System.out.printf("Not outputting statistics, because compilation issued an error.%n"); + } else { + System.out.printf("Found %d generic type uses.%n", generics); + System.out.printf("Found %d array accesses and creations.%n", arrayAccesses); + System.out.printf("Found %d typecasts.%n", typecasts); + System.out.printf( + "Found %d warning suppression annotations for the Index Checker.%n", + numberOfIndexWarningSuppressions); + } + super.typeProcessingOver(); + } - /** - * The number of warning suppressions with at least one key that matches one of the Index - * Checker subcheckers. - */ - int numberOfIndexWarningSuppressions = 0; + @Override + protected SourceVisitor createSourceVisitor() { + return new Visitor(this); + } - /** The SuppressWarnings.value field/element. */ - final ExecutableElement suppressWarningsValueElement = - TreeUtils.getMethod(SuppressWarnings.class, "value", 0, processingEnv); + class Visitor extends SourceVisitor { - /** Creates a JavaCodeStatistics. */ - public JavaCodeStatistics() { - // This checker never issues any warnings, so don't warn about - // @SuppressWarnings("allcheckers:..."). - this.useAllcheckersPrefix = false; + public Visitor(JavaCodeStatistics l) { + super(l); } @Override - public void typeProcessingOver() { - Log log = getCompilerLog(); - if (log.nerrors != 0) { - System.out.printf("Not outputting statistics, because compilation issued an error.%n"); - } else { - System.out.printf("Found %d generic type uses.%n", generics); - System.out.printf("Found %d array accesses and creations.%n", arrayAccesses); - System.out.printf("Found %d typecasts.%n", typecasts); - System.out.printf( - "Found %d warning suppression annotations for the Index Checker.%n", - numberOfIndexWarningSuppressions); + public Void visitAnnotation(AnnotationTree tree, Void aVoid) { + AnnotationMirror annotationMirror = TreeUtils.annotationFromAnnotationTree(tree); + if (AnnotationUtils.annotationName(annotationMirror) + .equals(SuppressWarnings.class.getCanonicalName())) { + List keys = + AnnotationUtils.getElementValueArray( + annotationMirror, suppressWarningsValueElement, String.class); + for (String foundKey : keys) { + for (String indexKey : warningKeys) { + if (foundKey.startsWith(indexKey)) { + numberOfIndexWarningSuppressions++; + return super.visitAnnotation(tree, aVoid); + } + } } - super.typeProcessingOver(); + } + return super.visitAnnotation(tree, aVoid); } @Override - protected SourceVisitor createSourceVisitor() { - return new Visitor(this); - } - - class Visitor extends SourceVisitor { - - public Visitor(JavaCodeStatistics l) { - super(l); - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void aVoid) { - AnnotationMirror annotationMirror = TreeUtils.annotationFromAnnotationTree(tree); - if (AnnotationUtils.annotationName(annotationMirror) - .equals(SuppressWarnings.class.getCanonicalName())) { - List keys = - AnnotationUtils.getElementValueArray( - annotationMirror, suppressWarningsValueElement, String.class); - for (String foundKey : keys) { - for (String indexKey : warningKeys) { - if (foundKey.startsWith(indexKey)) { - numberOfIndexWarningSuppressions++; - return super.visitAnnotation(tree, aVoid); - } - } - } - } - return super.visitAnnotation(tree, aVoid); - } - - @Override - public Void visitAssert(AssertTree tree, Void aVoid) { - ExpressionTree detail = tree.getDetail(); - if (detail != null) { - String msg = detail.toString(); - for (String indexKey : warningKeys) { - String key = "@AssumeAssertion(" + indexKey; - if (msg.contains(key)) { - numberOfIndexWarningSuppressions++; - return super.visitAssert(tree, aVoid); - } - } - } + public Void visitAssert(AssertTree tree, Void aVoid) { + ExpressionTree detail = tree.getDetail(); + if (detail != null) { + String msg = detail.toString(); + for (String indexKey : warningKeys) { + String key = "@AssumeAssertion(" + indexKey; + if (msg.contains(key)) { + numberOfIndexWarningSuppressions++; return super.visitAssert(tree, aVoid); + } } + } + return super.visitAssert(tree, aVoid); + } - @Override - public Void visitClass(ClassTree tree, Void p) { - if (shouldSkipDefs(tree)) { - // Not "return super.visitClass(classTree, p);" because that would recursively call - // visitors on subtrees; we want to skip the class entirely. - return null; - } - generics += tree.getTypeParameters().size(); - return super.visitClass(tree, p); - } - - @Override - public Void visitNewArray(NewArrayTree tree, Void aVoid) { - arrayAccesses += tree.getDimensions().size(); + @Override + public Void visitClass(ClassTree tree, Void p) { + if (shouldSkipDefs(tree)) { + // Not "return super.visitClass(classTree, p);" because that would recursively call + // visitors on subtrees; we want to skip the class entirely. + return null; + } + generics += tree.getTypeParameters().size(); + return super.visitClass(tree, p); + } - return super.visitNewArray(tree, aVoid); - } + @Override + public Void visitNewArray(NewArrayTree tree, Void aVoid) { + arrayAccesses += tree.getDimensions().size(); - @Override - public Void visitNewClass(NewClassTree tree, Void aVoid) { - if (TreeUtils.isDiamondTree(tree)) { - generics++; - } - generics += tree.getTypeArguments().size(); - return super.visitNewClass(tree, aVoid); - } + return super.visitNewArray(tree, aVoid); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void aVoid) { - generics += tree.getTypeArguments().size(); - return super.visitMethodInvocation(tree, aVoid); - } + @Override + public Void visitNewClass(NewClassTree tree, Void aVoid) { + if (TreeUtils.isDiamondTree(tree)) { + generics++; + } + generics += tree.getTypeArguments().size(); + return super.visitNewClass(tree, aVoid); + } - @Override - public Void visitMethod(MethodTree tree, Void aVoid) { - generics += tree.getTypeParameters().size(); - return super.visitMethod(tree, aVoid); - } + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void aVoid) { + generics += tree.getTypeArguments().size(); + return super.visitMethodInvocation(tree, aVoid); + } - @Override - public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { - generics += tree.getTypeArguments().size(); - return super.visitParameterizedType(tree, p); - } + @Override + public Void visitMethod(MethodTree tree, Void aVoid) { + generics += tree.getTypeParameters().size(); + return super.visitMethod(tree, aVoid); + } - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void aVoid) { - arrayAccesses++; - return super.visitArrayAccess(tree, aVoid); - } + @Override + public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { + generics += tree.getTypeArguments().size(); + return super.visitParameterizedType(tree, p); + } - @Override - public Void visitTypeCast(TypeCastTree tree, Void aVoid) { - typecasts++; - return super.visitTypeCast(tree, aVoid); - } + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void aVoid) { + arrayAccesses++; + return super.visitArrayAccess(tree, aVoid); } @Override - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for this class."); + public Void visitTypeCast(TypeCastTree tree, Void aVoid) { + typecasts++; + return super.visitTypeCast(tree, aVoid); } + } + + @Override + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for this class."); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java b/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java index ccb1459e44c..96c8f04ee38 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java @@ -1,7 +1,6 @@ package org.checkerframework.common.util.debug; import java.util.Set; - import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -17,16 +16,16 @@ @SupportedAnnotationTypes("*") public class DoNothingProcessor extends AbstractProcessor { - /** Creates a DoNothingProcessor. */ - public DoNothingProcessor() {} + /** Creates a DoNothingProcessor. */ + public DoNothingProcessor() {} - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - return false; - } + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + return false; + } - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java index e1ed6e3c36a..0ff6f1a4ce2 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java @@ -3,26 +3,9 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.source.SourceVisitor; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.javacutil.AbstractTypeProcessor; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.UserError; -import org.plumelib.reflection.Signatures; - import java.io.PrintStream; import java.lang.reflect.Constructor; import java.util.List; - import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; @@ -37,6 +20,20 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.AbstractElementVisitor8; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.source.SourceVisitor; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.javacutil.AbstractTypeProcessor; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.UserError; +import org.plumelib.reflection.Signatures; /** * Outputs the method signatures of a class with fully annotated types. @@ -74,292 +71,290 @@ @SupportedOptions("checker") public class SignaturePrinter extends AbstractTypeProcessor { - private SourceChecker checker; + private SourceChecker checker; + + ///////// Initialization ///////////// + /** + * Initialization. + * + * @param env the ProcessingEnvironment + * @param checkerName the name of the checker + */ + private void init(ProcessingEnvironment env, @Nullable @BinaryName String checkerName) { + if (checkerName != null) { + try { + Class checkerClass = Class.forName(checkerName); + Constructor cons = checkerClass.getConstructor(); + checker = (SourceChecker) cons.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + checker = + new SourceChecker() { + + @Override + protected SourceVisitor createSourceVisitor() { + return null; + } - ///////// Initialization ///////////// - /** - * Initialization. - * - * @param env the ProcessingEnvironment - * @param checkerName the name of the checker - */ - private void init(ProcessingEnvironment env, @Nullable @BinaryName String checkerName) { - if (checkerName != null) { - try { - Class checkerClass = Class.forName(checkerName); - Constructor cons = checkerClass.getConstructor(); - checker = (SourceChecker) cons.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); + @Override + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for this class."); } - } else { - checker = - new SourceChecker() { - - @Override - protected SourceVisitor createSourceVisitor() { - return null; - } - - @Override - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for this class."); - } - }; - } - checker.init(env); + }; } - - @Override - public void typeProcessingStart() { - super.typeProcessingStart(); - String checkerName = processingEnv.getOptions().get("checker"); - if (!Signatures.isBinaryName(checkerName)) { - throw new UserError("Malformed checker name \"%s\"", checkerName); - } - init(processingEnv, checkerName); + checker.init(env); + } + + @Override + public void typeProcessingStart() { + super.typeProcessingStart(); + String checkerName = processingEnv.getOptions().get("checker"); + if (!Signatures.isBinaryName(checkerName)) { + throw new UserError("Malformed checker name \"%s\"", checkerName); } - - @Override - public void typeProcess(TypeElement element, TreePath p) { - // TODO: fix this mess - // checker.currentPath = p; - // CompilationUnitTree root = p != null ? p.getCompilationUnit() : null; - // ElementPrinter printer = new ElementPrinter(checker.createTypeFactory(), System.out); - // printer.visit(element); + init(processingEnv, checkerName); + } + + @Override + public void typeProcess(TypeElement element, TreePath p) { + // TODO: fix this mess + // checker.currentPath = p; + // CompilationUnitTree root = p != null ? p.getCompilationUnit() : null; + // ElementPrinter printer = new ElementPrinter(checker.createTypeFactory(), System.out); + // printer.visit(element); + } + + ////////// Printer ////////// + /** Element printer. */ + static class ElementPrinter extends AbstractElementVisitor8 { + /** String used for indentation. */ + private static final String INDENTION = " "; + + private final PrintStream out; + private String indent = ""; + private final AnnotatedTypeFactory atypeFactory; + + public ElementPrinter(AnnotatedTypeFactory atypeFactory, PrintStream out) { + this.atypeFactory = atypeFactory; + this.out = out; } - ////////// Printer ////////// - /** Element printer. */ - static class ElementPrinter extends AbstractElementVisitor8 { - /** String used for indentation. */ - private static final String INDENTION = " "; - - private final PrintStream out; - private String indent = ""; - private final AnnotatedTypeFactory atypeFactory; - - public ElementPrinter(AnnotatedTypeFactory atypeFactory, PrintStream out) { - this.atypeFactory = atypeFactory; - this.out = out; - } - - public void printTypeParams(List params) { - if (params.isEmpty()) { - return; - } - - out.print("<"); - boolean isntFirst = false; - for (AnnotatedTypeMirror param : params) { - if (isntFirst) { - out.print(", "); - } - isntFirst = true; - out.print(param); - } - out.print("> "); - } + public void printTypeParams(List params) { + if (params.isEmpty()) { + return; + } - public void printParameters(AnnotatedExecutableType type) { - ExecutableElement elem = type.getElement(); - - out.print("("); - for (int i = 0; i < type.getParameterTypes().size(); ++i) { - if (i != 0) { - out.print(", "); - } - printVariable( - type.getParameterTypes().get(i), - elem.getParameters().get(i).getSimpleName()); - } - out.print(")"); + out.print("<"); + boolean isntFirst = false; + for (AnnotatedTypeMirror param : params) { + if (isntFirst) { + out.print(", "); } + isntFirst = true; + out.print(param); + } + out.print("> "); + } - public void printThrows(AnnotatedExecutableType type) { - if (type.getThrownTypes().isEmpty()) { - return; - } - - out.print(" throws "); + public void printParameters(AnnotatedExecutableType type) { + ExecutableElement elem = type.getElement(); - boolean isntFirst = false; - for (AnnotatedTypeMirror thrown : type.getThrownTypes()) { - if (isntFirst) { - out.print(", "); - } - isntFirst = true; - out.print(thrown); - } + out.print("("); + for (int i = 0; i < type.getParameterTypes().size(); ++i) { + if (i != 0) { + out.print(", "); } + printVariable(type.getParameterTypes().get(i), elem.getParameters().get(i).getSimpleName()); + } + out.print(")"); + } - public void printVariable(AnnotatedTypeMirror type, Name name, boolean isVarArg) { - out.print(type); - if (isVarArg) { - out.println("..."); - } - out.print(' '); - out.print(name); - } + public void printThrows(AnnotatedExecutableType type) { + if (type.getThrownTypes().isEmpty()) { + return; + } - public void printVariable(AnnotatedTypeMirror type, Name name) { - printVariable(type, name, false); - } + out.print(" throws "); - public void printType(AnnotatedTypeMirror type) { - out.print(type); - out.print(' '); + boolean isntFirst = false; + for (AnnotatedTypeMirror thrown : type.getThrownTypes()) { + if (isntFirst) { + out.print(", "); } + isntFirst = true; + out.print(thrown); + } + } - public void printName(CharSequence name) { - out.print(name); - } + public void printVariable(AnnotatedTypeMirror type, Name name, boolean isVarArg) { + out.print(type); + if (isVarArg) { + out.println("..."); + } + out.print(' '); + out.print(name); + } - @Override - public Void visitExecutable(ExecutableElement e, Void p) { - out.print(indent); + public void printVariable(AnnotatedTypeMirror type, Name name) { + printVariable(type, name, false); + } - AnnotatedExecutableType type = atypeFactory.getAnnotatedType(e); - printTypeParams(type.getTypeVariables()); - if (e.getKind() != ElementKind.CONSTRUCTOR) { - printType(type.getReturnType()); - } - printName(e.getSimpleName()); - printParameters(type); - printThrows(type); - out.println(';'); + public void printType(AnnotatedTypeMirror type) { + out.print(type); + out.print(' '); + } - return null; - } + public void printName(CharSequence name) { + out.print(name); + } - @Override - public Void visitPackage(PackageElement e, Void p) { - throw new IllegalArgumentException("Cannot process packages"); - } + @Override + public Void visitExecutable(ExecutableElement e, Void p) { + out.print(indent); + + AnnotatedExecutableType type = atypeFactory.getAnnotatedType(e); + printTypeParams(type.getTypeVariables()); + if (e.getKind() != ElementKind.CONSTRUCTOR) { + printType(type.getReturnType()); + } + printName(e.getSimpleName()); + printParameters(type); + printThrows(type); + out.println(';'); + + return null; + } - private String typeIdentifier(TypeElement e) { - switch (e.getKind()) { - case INTERFACE: - return "interface"; - case CLASS: - return "class"; - case ANNOTATION_TYPE: - return "@interface"; - case ENUM: - return "enum"; - default: - if (ElementUtils.isRecordElement(e)) { - return "record"; - } - throw new IllegalArgumentException("Not a type element: " + e.getKind()); - } - } + @Override + public Void visitPackage(PackageElement e, Void p) { + throw new IllegalArgumentException("Cannot process packages"); + } - @Override - public Void visitType(TypeElement e, Void p) { - String prevIndent = indent; + private String typeIdentifier(TypeElement e) { + switch (e.getKind()) { + case INTERFACE: + return "interface"; + case CLASS: + return "class"; + case ANNOTATION_TYPE: + return "@interface"; + case ENUM: + return "enum"; + default: + if (ElementUtils.isRecordElement(e)) { + return "record"; + } + throw new IllegalArgumentException("Not a type element: " + e.getKind()); + } + } - out.print(indent); - out.print(typeIdentifier(e)); - out.print(' '); - out.print(e.getSimpleName()); - out.print(' '); - AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(e); - printSupers(dt); - out.println("{"); + @Override + public Void visitType(TypeElement e, Void p) { + String prevIndent = indent; - indent += INDENTION; + out.print(indent); + out.print(typeIdentifier(e)); + out.print(' '); + out.print(e.getSimpleName()); + out.print(' '); + AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(e); + printSupers(dt); + out.println("{"); - for (Element enclosed : e.getEnclosedElements()) { - this.visit(enclosed); - } + indent += INDENTION; - indent = prevIndent; - out.print(indent); - out.println("}"); + for (Element enclosed : e.getEnclosedElements()) { + this.visit(enclosed); + } - return null; - } + indent = prevIndent; + out.print(indent); + out.println("}"); - /** - * Print the supertypes. - * - * @param dt the type whos supertypes to print - */ - private void printSupers(AnnotatedDeclaredType dt) { - if (dt.directSupertypes().isEmpty()) { - return; - } + return null; + } - out.print("extends "); + /** + * Print the supertypes. + * + * @param dt the type whos supertypes to print + */ + private void printSupers(AnnotatedDeclaredType dt) { + if (dt.directSupertypes().isEmpty()) { + return; + } - boolean isntFirst = false; - for (AnnotatedDeclaredType st : dt.directSupertypes()) { - if (isntFirst) { - out.print(", "); - } - isntFirst = true; - out.print(st); - } - out.print(' '); - } + out.print("extends "); - @Override - public Void visitTypeParameter(TypeParameterElement e, Void p) { - throw new IllegalStateException("Shouldn't visit any type parameters"); + boolean isntFirst = false; + for (AnnotatedDeclaredType st : dt.directSupertypes()) { + if (isntFirst) { + out.print(", "); } + isntFirst = true; + out.print(st); + } + out.print(' '); + } - @Override - public Void visitVariable(VariableElement e, Void p) { - if (!e.getKind().isField()) { - throw new IllegalStateException("can only process fields, received " + e.getKind()); - } + @Override + public Void visitTypeParameter(TypeParameterElement e, Void p) { + throw new IllegalStateException("Shouldn't visit any type parameters"); + } - out.print(indent); - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(e); - this.printVariable(type, e.getSimpleName()); - out.println(';'); + @Override + public Void visitVariable(VariableElement e, Void p) { + if (!e.getKind().isField()) { + throw new IllegalStateException("can only process fields, received " + e.getKind()); + } - return null; - } - } + out.print(indent); + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(e); + this.printVariable(type, e.getSimpleName()); + out.println(';'); - public static void printUsage() { - System.out.println(" Usage: java SignaturePrinter [-Achecker=] classname"); + return null; } + } - private static final String CHECKER_ARG = "-Achecker="; + public static void printUsage() { + System.out.println(" Usage: java SignaturePrinter [-Achecker=] classname"); + } - public static void main(String[] args) { - if (!(args.length == 1 && !args[0].startsWith(CHECKER_ARG)) - && !(args.length == 2 && args[0].startsWith(CHECKER_ARG))) { - printUsage(); - return; - } + private static final String CHECKER_ARG = "-Achecker="; - // process arguments - String checkerName = null; - if (args[0].startsWith(CHECKER_ARG)) { - checkerName = args[0].substring(CHECKER_ARG.length()); - if (!Signatures.isBinaryName(checkerName)) { - throw new UserError("Bad checker name \"%s\"", checkerName); - } - } + public static void main(String[] args) { + if (!(args.length == 1 && !args[0].startsWith(CHECKER_ARG)) + && !(args.length == 2 && args[0].startsWith(CHECKER_ARG))) { + printUsage(); + return; + } - // Setup compiler environment - Context context = new Context(); - JavacProcessingEnvironment env = JavacProcessingEnvironment.instance(context); - SignaturePrinter printer = new SignaturePrinter(); - printer.init(env, checkerName); - - String className = args[args.length - 1]; - TypeElement elem = env.getElementUtils().getTypeElement(className); - if (elem == null) { - System.err.println("Couldn't find class: " + className); - return; - } + // process arguments + String checkerName = null; + if (args[0].startsWith(CHECKER_ARG)) { + checkerName = args[0].substring(CHECKER_ARG.length()); + if (!Signatures.isBinaryName(checkerName)) { + throw new UserError("Bad checker name \"%s\"", checkerName); + } + } - printer.typeProcess(elem, null); + // Setup compiler environment + Context context = new Context(); + JavacProcessingEnvironment env = JavacProcessingEnvironment.instance(context); + SignaturePrinter printer = new SignaturePrinter(); + printer.init(env, checkerName); + + String className = args[args.length - 1]; + TypeElement elem = env.getElementUtils().getTypeElement(className); + if (elem == null) { + System.err.println("Couldn't find class: " + className); + return; } + + printer.typeProcess(elem, null); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java b/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java index 6a5939872b9..1c892ed6dd5 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java @@ -10,9 +10,7 @@ import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; import com.sun.tools.javac.tree.JCTree.JCNewArray; - import java.util.Set; - import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -40,91 +38,91 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) public class TreeDebug extends AbstractProcessor { - protected Visitor createSourceVisitor(CompilationUnitTree root) { - return new Visitor(); - } - - private static final String LINE_SEPARATOR = System.lineSeparator(); + protected Visitor createSourceVisitor(CompilationUnitTree root) { + return new Visitor(); + } - public static class Visitor extends TreePathScanner { + private static final String LINE_SEPARATOR = System.lineSeparator(); - private final StringBuilder buf; + public static class Visitor extends TreePathScanner { - public Visitor() { - buf = new StringBuilder(); - } + private final StringBuilder buf; - @Override - public Void scan(Tree tree, Void p) { + public Visitor() { + buf = new StringBuilder(); + } - // Indent according to subtrees. - if (getCurrentPath() != null) { - for (TreePath tp = getCurrentPath(); tp != null; tp = tp.getParentPath()) { - buf.append(" "); - } - } + @Override + public Void scan(Tree tree, Void p) { - // Add tree kind to the buffer. - if (tree == null) { - buf.append("null"); - } else { - buf.append(tree.getKind()); - } - buf.append(LINE_SEPARATOR); + // Indent according to subtrees. + if (getCurrentPath() != null) { + for (TreePath tp = getCurrentPath(); tp != null; tp = tp.getParentPath()) { + buf.append(" "); + } + } - // Visit subtrees. - super.scan(tree, p); + // Add tree kind to the buffer. + if (tree == null) { + buf.append("null"); + } else { + buf.append(tree.getKind()); + } + buf.append(LINE_SEPARATOR); - // Display and clear the buffer. - System.out.print(buf.toString()); - buf.setLength(0); + // Visit subtrees. + super.scan(tree, p); - return null; - } + // Display and clear the buffer. + System.out.print(buf.toString()); + buf.setLength(0); - /** - * Splices additional information for an AST node into the buffer. - * - * @param text additional information for the AST node - */ - private void insert(Object text) { - buf.insert(buf.length() - 1, " "); - buf.insert(buf.length() - 1, text); - } + return null; + } - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - insert(tree); - return super.visitIdentifier(tree, p); - } + /** + * Splices additional information for an AST node into the buffer. + * + * @param text additional information for the AST node + */ + private void insert(Object text) { + buf.insert(buf.length() - 1, " "); + buf.insert(buf.length() - 1, text); + } - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - insert(tree.getExpression() + "." + tree.getIdentifier()); - return super.visitMemberSelect(tree, p); - } + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + insert(tree); + return super.visitIdentifier(tree, p); + } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - insert(((JCNewArray) tree).annotations); - insert("|"); - insert(((JCNewArray) tree).dimAnnotations); - return super.visitNewArray(tree, p); - } + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + insert(tree.getExpression() + "." + tree.getIdentifier()); + return super.visitMemberSelect(tree, p); + } - @Override - public Void visitLiteral(LiteralTree tree, Void p) { - insert(tree.getValue()); - return super.visitLiteral(tree, p); - } + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + insert(((JCNewArray) tree).annotations); + insert("|"); + insert(((JCNewArray) tree).dimAnnotations); + return super.visitNewArray(tree, p); } @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - for (TypeElement element : ElementFilter.typesIn(roundEnv.getRootElements())) { - TreePath path = Trees.instance(processingEnv).getPath(element); - new Visitor().scan(path, null); - } - return false; + public Void visitLiteral(LiteralTree tree, Void p) { + insert(tree.getValue()); + return super.visitLiteral(tree, p); + } + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (TypeElement element : ElementFilter.typesIn(roundEnv.getRootElements())) { + TreePath path = Trees.instance(processingEnv).getPath(element); + new Visitor().scan(path, null); } + return false; + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java index 7a7f0a82e97..f39faf63dce 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java @@ -3,17 +3,14 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.Pretty; - -import org.checkerframework.javacutil.AbstractTypeProcessor; - import java.io.IOException; import java.io.StringWriter; import java.io.UncheckedIOException; - import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; +import org.checkerframework.javacutil.AbstractTypeProcessor; /** * A utility class for pretty-printing the AST of a program. @@ -42,25 +39,25 @@ @SupportedAnnotationTypes("*") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class TreePrinter extends AbstractTypeProcessor { - @Override - public void typeProcess(TypeElement element, TreePath tree) { - StringWriter out = new StringWriter(); - Pretty pretty = new Pretty(out, true); + @Override + public void typeProcess(TypeElement element, TreePath tree) { + StringWriter out = new StringWriter(); + Pretty pretty = new Pretty(out, true); - try { - pretty.printUnit((JCCompilationUnit) tree.getCompilationUnit(), null); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - System.out.println(out.toString()); + try { + pretty.printUnit((JCCompilationUnit) tree.getCompilationUnit(), null); + } catch (IOException e) { + throw new UncheckedIOException(e); } + System.out.println(out.toString()); + } - public static void main(String[] args) throws Exception { - String[] newArgs = new String[args.length + 3]; - newArgs[0] = "-processor"; - newArgs[1] = "org.checkerframework.common.util.debug.TreePrinter"; - newArgs[2] = "-proc:only"; - System.arraycopy(args, 0, newArgs, 3, args.length); - com.sun.tools.javac.Main.compile(newArgs); - } + public static void main(String[] args) throws Exception { + String[] newArgs = new String[args.length + 3]; + newArgs[0] = "-processor"; + newArgs[1] = "org.checkerframework.common.util.debug.TreePrinter"; + newArgs[2] = "-proc:only"; + System.arraycopy(args, 0, newArgs, 3, args.length); + com.sun.tools.javac.Main.compile(newArgs); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java index 397ca1af285..6b973b68588 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java @@ -5,7 +5,14 @@ import com.sun.source.tree.VariableTree; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; - +import java.util.Collection; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -18,16 +25,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; -import java.util.Collection; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.util.Elements; - /** * A testing class that can be used to test {@link TypeElement}. In particular it tests that the * types read from classfiles are the same to the ones from Java files. @@ -51,233 +48,222 @@ */ public class TypeOutputtingChecker extends BaseTypeChecker { + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new Visitor(this); + } + + /** Prints the types of the class and all of its enclosing fields, methods, and inner classes. */ + public static class Visitor extends BaseTypeVisitor> { + String currentClass; + + public Visitor(BaseTypeChecker checker) { + super(checker); + } + + // Print types of classes, methods, and fields @Override - protected BaseTypeVisitor createSourceVisitor() { - return new Visitor(this); + public void processClassTree(ClassTree tree) { + TypeElement element = TreeUtils.elementFromDeclaration(tree); + currentClass = element.getSimpleName().toString(); + + AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); + System.out.println(tree.getSimpleName() + "\t" + type + "\t" + type.directSupertypes()); + + super.processClassTree(tree); } - /** - * Prints the types of the class and all of its enclosing fields, methods, and inner classes. - */ - public static class Visitor extends BaseTypeVisitor> { - String currentClass; - - public Visitor(BaseTypeChecker checker) { - super(checker); - } - - // Print types of classes, methods, and fields - @Override - public void processClassTree(ClassTree tree) { - TypeElement element = TreeUtils.elementFromDeclaration(tree); - currentClass = element.getSimpleName().toString(); - - AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); - System.out.println(tree.getSimpleName() + "\t" + type + "\t" + type.directSupertypes()); - - super.processClassTree(tree); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement elem = TreeUtils.elementFromDeclaration(tree); - - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - System.out.println(currentClass + "." + elem + "\t\t" + type); - // Don't dig deeper - return null; - } - - @Override - public Void visitVariable(VariableTree tree, Void p) { - VariableElement elem = TreeUtils.elementFromDeclaration(tree); - if (elem.getKind().isField()) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - System.out.println(currentClass + "." + elem + "\t\t" + type); - } - - // Don't dig deeper - return null; - } + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement elem = TreeUtils.elementFromDeclaration(tree); + + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + System.out.println(currentClass + "." + elem + "\t\t" + type); + // Don't dig deeper + return null; } - /** - * Main entry point. - * - * @param args command-line arguments - */ - @SuppressWarnings("signature:argument.type.incompatible") // user-supplied input, uncheckable - public static void main(String[] args) { - new TypeOutputtingChecker().run(args); + @Override + public Void visitVariable(VariableTree tree, Void p) { + VariableElement elem = TreeUtils.elementFromDeclaration(tree); + if (elem.getKind().isField()) { + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + System.out.println(currentClass + "." + elem + "\t\t" + type); + } + + // Don't dig deeper + return null; + } + } + + /** + * Main entry point. + * + * @param args command-line arguments + */ + @SuppressWarnings("signature:argument.type.incompatible") // user-supplied input, uncheckable + public static void main(String[] args) { + new TypeOutputtingChecker().run(args); + } + + /** + * Run the test. + * + * @param args command-line arguments + */ + public void run(@CanonicalName String[] args) { + ProcessingEnvironment env = JavacProcessingEnvironment.instance(new Context()); + Elements elements = env.getElementUtils(); + + // TODO: Instead of using a GeneralAnnotatedTypeFactory, just use standard javac classes + // to print explicit annotations. + AnnotatedTypeFactory atypeFactory = new GeneralAnnotatedTypeFactory(this); + + for (String className : args) { + TypeElement typeElt = elements.getTypeElement(className); + printClassType(typeElt, atypeFactory); + } + } + + /** Prints the types of the class and all of its enclosing fields, methods, and inner classes. */ + protected static void printClassType(TypeElement typeElt, AnnotatedTypeFactory atypeFactory) { + assert typeElt != null; + + String simpleName = typeElt.getSimpleName().toString(); + // Output class info + AnnotatedDeclaredType type = atypeFactory.fromElement(typeElt); + System.out.println(simpleName + "\t" + type + "\t" + type.directSupertypes()); + + // output fields and methods + for (Element enclosedElt : typeElt.getEnclosedElements()) { + if (enclosedElt instanceof TypeElement) { + printClassType((TypeElement) enclosedElt, atypeFactory); + } + if (!enclosedElt.getKind().isField() && !(enclosedElt instanceof ExecutableElement)) { + continue; + } + AnnotatedTypeMirror memberType = atypeFactory.fromElement(enclosedElt); + System.out.println(simpleName + "." + enclosedElt + "\t\t" + memberType); + } + } + + /** + * Stores any explicit annotation in AnnotatedTypeMirrors. It doesn't have a qualifier hierarchy, + * so it violates most of the specifications for AnnotatedTypeMirrors and AnnotatedTypeFactorys, + * which may cause crashes and other unexpected behaviors. + */ + public static class GeneralAnnotatedTypeFactory extends AnnotatedTypeFactory { + + public GeneralAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); } - /** - * Run the test. - * - * @param args command-line arguments - */ - public void run(@CanonicalName String[] args) { - ProcessingEnvironment env = JavacProcessingEnvironment.instance(new Context()); - Elements elements = env.getElementUtils(); - - // TODO: Instead of using a GeneralAnnotatedTypeFactory, just use standard javac classes - // to print explicit annotations. - AnnotatedTypeFactory atypeFactory = new GeneralAnnotatedTypeFactory(this); - - for (String className : args) { - TypeElement typeElt = elements.getTypeElement(className); - printClassType(typeElt, atypeFactory); - } + @Override + public void postProcessClassTree(ClassTree tree) { + // Do not store the qualifiers determined by this factory. This factory adds + // declaration annotations as type annotations, because TypeFromElement needs to read + // declaration annotations and this factory blindly supports all annotations. + // When storing those annotation to bytecode, the compiler chokes. See testcase + // tests/nullness/GeneralATFStore.java } - /** - * Prints the types of the class and all of its enclosing fields, methods, and inner classes. - */ - protected static void printClassType(TypeElement typeElt, AnnotatedTypeFactory atypeFactory) { - assert typeElt != null; - - String simpleName = typeElt.getSimpleName().toString(); - // Output class info - AnnotatedDeclaredType type = atypeFactory.fromElement(typeElt); - System.out.println(simpleName + "\t" + type + "\t" + type.directSupertypes()); - - // output fields and methods - for (Element enclosedElt : typeElt.getEnclosedElements()) { - if (enclosedElt instanceof TypeElement) { - printClassType((TypeElement) enclosedElt, atypeFactory); - } - if (!enclosedElt.getKind().isField() && !(enclosedElt instanceof ExecutableElement)) { - continue; - } - AnnotatedTypeMirror memberType = atypeFactory.fromElement(enclosedElt); - System.out.println(simpleName + "." + enclosedElt + "\t\t" + memberType); - } + /** Return true to support any qualifier. No handling of aliases. */ + @Override + public boolean isSupportedQualifier(AnnotationMirror a) { + return true; + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new GeneralQualifierHierarchy(null); } /** - * Stores any explicit annotation in AnnotatedTypeMirrors. It doesn't have a qualifier - * hierarchy, so it violates most of the specifications for AnnotatedTypeMirrors and - * AnnotatedTypeFactorys, which may cause crashes and other unexpected behaviors. + * A very limited QualifierHierarchy that is used for access to qualifiers from different type + * systems. */ - public static class GeneralAnnotatedTypeFactory extends AnnotatedTypeFactory { - - public GeneralAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - @Override - public void postProcessClassTree(ClassTree tree) { - // Do not store the qualifiers determined by this factory. This factory adds - // declaration annotations as type annotations, because TypeFromElement needs to read - // declaration annotations and this factory blindly supports all annotations. - // When storing those annotation to bytecode, the compiler chokes. See testcase - // tests/nullness/GeneralATFStore.java - } - - /** Return true to support any qualifier. No handling of aliases. */ - @Override - public boolean isSupportedQualifier(AnnotationMirror a) { - return true; - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new GeneralQualifierHierarchy(null); - } - - /** - * A very limited QualifierHierarchy that is used for access to qualifiers from different - * type systems. - */ - static class GeneralQualifierHierarchy extends QualifierHierarchy { - - /** - * Creates a new GeneralQualifierHierarchy. - * - * @param atypeFactory the associated type factory - */ - public GeneralQualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - // Always return true - @Override - public boolean isValid() { - return true; - } - - // Return the qualifier itself instead of the top. - @Override - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - return start; - } - - // Return the qualifier itself instead of the bottom. - @Override - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - return start; - } - - // Never find a corresponding qualifier. - @Override - public AnnotationMirror findAnnotationInSameHierarchy( - Collection annotations, - AnnotationMirror annotationMirror) { - return null; - } - - // Not needed - raises error. - @Override - public AnnotationMirrorSet getTopAnnotations() { - throw new BugInCF( - "GeneralQualifierHierarchy:getTopAnnotations() shouldn't be called"); - } - - // Not needed - should raise error. Unfortunately, in inference we ask for bottom - // annotations. - // Return a dummy value that does no harm. - @Override - public AnnotationMirrorSet getBottomAnnotations() { - // throw new BugInCF("GeneralQualifierHierarchy.getBottomAnnotations() - // shouldn't be called"); - return new AnnotationMirrorSet(); - } - - // Not needed - raises error. - @Override - public boolean isSubtypeQualifiers( - AnnotationMirror subAnno, AnnotationMirror superAnno) { - throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called."); - } - - // Not needed - raises error. - @Override - public AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - throw new BugInCF( - "GeneralQualifierHierarchy.leastUpperBound() shouldn't be called."); - } - - // Not needed - raises error. - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - throw new BugInCF( - "GeneralQualifierHierarchy.greatestLowerBound() shouldn't be called."); - } - - @Override - public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { - throw new BugInCF( - "GeneralQualifierHierarchy.getPolymorphicAnnotation() shouldn't be" - + " called."); - } - - @Override - public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { - return false; - } - } + static class GeneralQualifierHierarchy extends QualifierHierarchy { + + /** + * Creates a new GeneralQualifierHierarchy. + * + * @param atypeFactory the associated type factory + */ + public GeneralQualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + // Always return true + @Override + public boolean isValid() { + return true; + } + + // Return the qualifier itself instead of the top. + @Override + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + return start; + } + + // Return the qualifier itself instead of the bottom. + @Override + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + return start; + } + + // Never find a corresponding qualifier. + @Override + public AnnotationMirror findAnnotationInSameHierarchy( + Collection annotations, AnnotationMirror annotationMirror) { + return null; + } + + // Not needed - raises error. + @Override + public AnnotationMirrorSet getTopAnnotations() { + throw new BugInCF("GeneralQualifierHierarchy:getTopAnnotations() shouldn't be called"); + } + + // Not needed - should raise error. Unfortunately, in inference we ask for bottom + // annotations. + // Return a dummy value that does no harm. + @Override + public AnnotationMirrorSet getBottomAnnotations() { + // throw new BugInCF("GeneralQualifierHierarchy.getBottomAnnotations() + // shouldn't be called"); + return new AnnotationMirrorSet(); + } + + // Not needed - raises error. + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called."); + } + + // Not needed - raises error. + @Override + public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + throw new BugInCF("GeneralQualifierHierarchy.leastUpperBound() shouldn't be called."); + } + + // Not needed - raises error. + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + throw new BugInCF("GeneralQualifierHierarchy.greatestLowerBound() shouldn't be called."); + } + + @Override + public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { + throw new BugInCF( + "GeneralQualifierHierarchy.getPolymorphicAnnotation() shouldn't be" + " called."); + } + + @Override + public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { + return false; + } } + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java index 93819c4f2d8..469373c9175 100644 --- a/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java +++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java @@ -1,8 +1,7 @@ package org.checkerframework.common.util.report; -import org.checkerframework.common.basetype.BaseTypeChecker; - import javax.annotation.processing.SupportedOptions; +import org.checkerframework.common.basetype.BaseTypeChecker; /** * The Report Checker performs semantic searches over a program, for example, to find all methods @@ -36,6 +35,6 @@ @SupportedOptions({"reportTreeKinds", "reportModifiers"}) public class ReportChecker extends BaseTypeChecker { - /** Creates a ReportChecker. */ - public ReportChecker() {} + /** Creates a ReportChecker. */ + public ReportChecker() {} } diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java index e0fa6c02272..ad8e15e3a2c 100644 --- a/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java @@ -13,7 +13,16 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; - +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -32,287 +41,269 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; - /** The visitor for the Report Checker. */ public class ReportVisitor extends BaseTypeVisitor { - /** The tree kinds that should be reported; may be null. */ - private final @Nullable EnumSet treeKinds; + /** The tree kinds that should be reported; may be null. */ + private final @Nullable EnumSet treeKinds; - /** The modifiers that should be reported; may be null. */ - private final @Nullable EnumSet modifiers; + /** The modifiers that should be reported; may be null. */ + private final @Nullable EnumSet modifiers; - public ReportVisitor(BaseTypeChecker checker) { - super(checker); - - EnumSet treeKindsTmp = EnumSet.noneOf(Tree.Kind.class); - for (String treeKind : checker.getStringsOption("reportTreeKinds", ',')) { - treeKindsTmp.add(Tree.Kind.valueOf(treeKind.toUpperCase(Locale.ROOT))); - } - treeKinds = treeKindsTmp.isEmpty() ? null : treeKindsTmp; + public ReportVisitor(BaseTypeChecker checker) { + super(checker); - EnumSet modifiersTmp = EnumSet.noneOf(Modifier.class); - for (String modifier : checker.getStringsOption("reportModifiers", ',')) { - modifiersTmp.add(Modifier.valueOf(modifier.toUpperCase(Locale.ROOT))); - } - modifiers = modifiersTmp.isEmpty() ? null : modifiersTmp; + EnumSet treeKindsTmp = EnumSet.noneOf(Tree.Kind.class); + for (String treeKind : checker.getStringsOption("reportTreeKinds", ',')) { + treeKindsTmp.add(Tree.Kind.valueOf(treeKind.toUpperCase(Locale.ROOT))); } + treeKinds = treeKindsTmp.isEmpty() ? null : treeKindsTmp; - @SuppressWarnings("compilermessages") // These warnings are not translated. - @Override - public Void scan(Tree tree, Void p) { - if ((tree != null) && (treeKinds != null) && treeKinds.contains(tree.getKind())) { - checker.reportError(tree, "Tree.Kind." + tree.getKind()); - } - return super.scan(tree, p); + EnumSet modifiersTmp = EnumSet.noneOf(Modifier.class); + for (String modifier : checker.getStringsOption("reportModifiers", ',')) { + modifiersTmp.add(Modifier.valueOf(modifier.toUpperCase(Locale.ROOT))); } - - /** - * Check for uses of the {@link ReportUse} annotation. This method has to be called for every - * explicit or implicit use of a type, most cases are simply covered by the type validator. - * - * @param tree the tree for error reporting only - * @param member the element from which to start looking - */ - private void checkReportUse(Tree tree, Element member) { - Element loop = member; - while (loop != null) { - boolean report = this.atypeFactory.getDeclAnnotation(loop, ReportUse.class) != null; - if (report) { - checker.reportError( - tree, - "usage", - tree, - ElementUtils.getQualifiedName(loop), - loop.getKind(), - ElementUtils.getQualifiedName(member), - member.getKind()); - break; - } else { - if (loop.getKind() == ElementKind.PACKAGE) { - loop = ElementUtils.parentPackage((PackageElement) loop, elements); - continue; - } - } - // Package will always be the last iteration. - loop = loop.getEnclosingElement(); + modifiers = modifiersTmp.isEmpty() ? null : modifiersTmp; + } + + @SuppressWarnings("compilermessages") // These warnings are not translated. + @Override + public Void scan(Tree tree, Void p) { + if ((tree != null) && (treeKinds != null) && treeKinds.contains(tree.getKind())) { + checker.reportError(tree, "Tree.Kind." + tree.getKind()); + } + return super.scan(tree, p); + } + + /** + * Check for uses of the {@link ReportUse} annotation. This method has to be called for every + * explicit or implicit use of a type, most cases are simply covered by the type validator. + * + * @param tree the tree for error reporting only + * @param member the element from which to start looking + */ + private void checkReportUse(Tree tree, Element member) { + Element loop = member; + while (loop != null) { + boolean report = this.atypeFactory.getDeclAnnotation(loop, ReportUse.class) != null; + if (report) { + checker.reportError( + tree, + "usage", + tree, + ElementUtils.getQualifiedName(loop), + loop.getKind(), + ElementUtils.getQualifiedName(member), + member.getKind()); + break; + } else { + if (loop.getKind() == ElementKind.PACKAGE) { + loop = ElementUtils.parentPackage((PackageElement) loop, elements); + continue; } + } + // Package will always be the last iteration. + loop = loop.getEnclosingElement(); } - - /* Would we want this? Seems redundant, as all uses of the imported - * package should already be reported. - * Also, how do we get an element for the import? - public Void visitImport(ImportTree tree, Void p) { - checkReportUse(tree, elem); + } + + /* Would we want this? Seems redundant, as all uses of the imported + * package should already be reported. + * Also, how do we get an element for the import? + public Void visitImport(ImportTree tree, Void p) { + checkReportUse(tree, elem); + } + */ + + @Override + public void processClassTree(ClassTree tree) { + TypeElement member = TreeUtils.elementFromDeclaration(tree); + boolean report = false; + // No need to check on the declaring class itself + // this.atypeFactory.getDeclAnnotation(member, ReportInherit.class) != null; + + // Check whether any superclass/interface had the ReportInherit annotation. + List suptypes = ElementUtils.getSuperTypes(member, elements); + for (TypeElement sup : suptypes) { + report = this.atypeFactory.getDeclAnnotation(sup, ReportInherit.class) != null; + if (report) { + checker.reportError(tree, "inherit", tree, ElementUtils.getQualifiedName(sup)); + } } - */ - - @Override - public void processClassTree(ClassTree tree) { - TypeElement member = TreeUtils.elementFromDeclaration(tree); - boolean report = false; - // No need to check on the declaring class itself - // this.atypeFactory.getDeclAnnotation(member, ReportInherit.class) != null; - - // Check whether any superclass/interface had the ReportInherit annotation. - List suptypes = ElementUtils.getSuperTypes(member, elements); - for (TypeElement sup : suptypes) { - report = this.atypeFactory.getDeclAnnotation(sup, ReportInherit.class) != null; - if (report) { - checker.reportError(tree, "inherit", tree, ElementUtils.getQualifiedName(sup)); - } - } - super.processClassTree(tree); + super.processClassTree(tree); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement method = TreeUtils.elementFromDeclaration(tree); + boolean report = false; + + // Check all overridden methods. + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); + for (Map.Entry pair : overriddenMethods.entrySet()) { + // AnnotatedDeclaredType overriddenType = pair.getKey(); + ExecutableElement exe = pair.getValue(); + report = this.atypeFactory.getDeclAnnotation(exe, ReportOverride.class) != null; + if (report) { + // Set method to report the right method, if found. + method = exe; + break; + } } - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement method = TreeUtils.elementFromDeclaration(tree); - boolean report = false; - - // Check all overridden methods. - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - // AnnotatedDeclaredType overriddenType = pair.getKey(); - ExecutableElement exe = pair.getValue(); - report = this.atypeFactory.getDeclAnnotation(exe, ReportOverride.class) != null; - if (report) { - // Set method to report the right method, if found. - method = exe; - break; - } - } - - if (report) { - checker.reportError(tree, "override", tree, ElementUtils.getQualifiedName(method)); - } - return super.visitMethod(tree, p); + if (report) { + checker.reportError(tree, "override", tree, ElementUtils.getQualifiedName(method)); } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - checkReportUse(tree, method); - boolean report = this.atypeFactory.getDeclAnnotation(method, ReportCall.class) != null; - - if (!report) { - // Find all methods that are overridden by the called method - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - // AnnotatedDeclaredType overriddenType = pair.getKey(); - ExecutableElement exe = pair.getValue(); - report = this.atypeFactory.getDeclAnnotation(exe, ReportCall.class) != null; - if (report) { - // Always report the element that has the annotation. - // Alternative would be to always report the initial element. - method = exe; - break; - } - } - } - + return super.visitMethod(tree, p); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + checkReportUse(tree, method); + boolean report = this.atypeFactory.getDeclAnnotation(method, ReportCall.class) != null; + + if (!report) { + // Find all methods that are overridden by the called method + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + // AnnotatedDeclaredType overriddenType = pair.getKey(); + ExecutableElement exe = pair.getValue(); + report = this.atypeFactory.getDeclAnnotation(exe, ReportCall.class) != null; if (report) { - checker.reportError(tree, "methodcall", tree, ElementUtils.getQualifiedName(method)); + // Always report the element that has the annotation. + // Alternative would be to always report the initial element. + method = exe; + break; } - return super.visitMethodInvocation(tree, p); + } } - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree); - checkReportUse(tree, member); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; - - if (report) { - checker.reportError( - tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); - } - return super.visitMemberSelect(tree, p); + if (report) { + checker.reportError(tree, "methodcall", tree, ElementUtils.getQualifiedName(method)); } + return super.visitMethodInvocation(tree, p); + } - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree); + checkReportUse(tree, member); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; - if (report) { - checker.reportError( - tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); - } - return super.visitIdentifier(tree, p); + if (report) { + checker.reportError(tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); } + return super.visitMemberSelect(tree, p); + } - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree.getVariable()); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportWrite.class) != null; + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; - if (report) { - checker.reportError(tree, "fieldwrite", tree, ElementUtils.getQualifiedName(member)); - } - return super.visitAssignment(tree, p); + if (report) { + checker.reportError(tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); } + return super.visitIdentifier(tree, p); + } - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - // TODO: should we introduce an annotation for this? - return super.visitArrayAccess(tree, p); - } - - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; - if (!report) { - // If the constructor is not annotated, check whether the class is. - member = member.getEnclosingElement(); - report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; - if (!report) { - // Check whether any superclass/interface had the ReportCreation annotation. - List suptypes = - ElementUtils.getSuperTypes((TypeElement) member, elements); - for (TypeElement sup : suptypes) { - report = this.atypeFactory.getDeclAnnotation(sup, ReportCreation.class) != null; - if (report) { - // Set member to report the right member if found - member = sup; - break; - } - } - } - } - - if (report) { - checker.reportError(tree, "creation", tree, ElementUtils.getQualifiedName(member)); - } - return super.visitNewClass(tree, p); - } + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree.getVariable()); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportWrite.class) != null; - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - // TODO Should we report this if the array type is @ReportCreation? - return super.visitNewArray(tree, p); + if (report) { + checker.reportError(tree, "fieldwrite", tree, ElementUtils.getQualifiedName(member)); } - - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - // TODO Is it worth adding a separate annotation for this? - return super.visitTypeCast(tree, p); + return super.visitAssignment(tree, p); + } + + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + // TODO: should we introduce an annotation for this? + return super.visitArrayAccess(tree, p); + } + + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; + if (!report) { + // If the constructor is not annotated, check whether the class is. + member = member.getEnclosingElement(); + report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; + if (!report) { + // Check whether any superclass/interface had the ReportCreation annotation. + List suptypes = ElementUtils.getSuperTypes((TypeElement) member, elements); + for (TypeElement sup : suptypes) { + report = this.atypeFactory.getDeclAnnotation(sup, ReportCreation.class) != null; + if (report) { + // Set member to report the right member if found + member = sup; + break; + } + } + } } - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - // TODO Is it worth adding a separate annotation for this? - return super.visitInstanceOf(tree, p); + if (report) { + checker.reportError(tree, "creation", tree, ElementUtils.getQualifiedName(member)); } - - @SuppressWarnings("compilermessages") // These warnings are not translated. - @Override - public Void visitModifiers(ModifiersTree tree, Void p) { - if (tree != null && modifiers != null) { - for (Modifier mod : tree.getFlags()) { - if (modifiers.contains(mod)) { - checker.reportError(tree, "Modifier." + mod); - } - } + return super.visitNewClass(tree, p); + } + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + // TODO Should we report this if the array type is @ReportCreation? + return super.visitNewArray(tree, p); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + // TODO Is it worth adding a separate annotation for this? + return super.visitTypeCast(tree, p); + } + + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + // TODO Is it worth adding a separate annotation for this? + return super.visitInstanceOf(tree, p); + } + + @SuppressWarnings("compilermessages") // These warnings are not translated. + @Override + public Void visitModifiers(ModifiersTree tree, Void p) { + if (tree != null && modifiers != null) { + for (Modifier mod : tree.getFlags()) { + if (modifiers.contains(mod)) { + checker.reportError(tree, "Modifier." + mod); } - return super.visitModifiers(tree, p); + } } - - @Override - protected BaseTypeValidator createTypeValidator() { - return new ReportTypeValidator(checker, this, atypeFactory); + return super.visitModifiers(tree, p); + } + + @Override + protected BaseTypeValidator createTypeValidator() { + return new ReportTypeValidator(checker, this, atypeFactory); + } + + protected class ReportTypeValidator extends BaseTypeValidator { + public ReportTypeValidator( + BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); } - protected class ReportTypeValidator extends BaseTypeValidator { - public ReportTypeValidator( - BaseTypeChecker checker, - BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - Element member = type.getUnderlyingType().asElement(); - checkReportUse(tree, member); + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + Element member = type.getUnderlyingType().asElement(); + checkReportUse(tree, member); - return super.visitDeclared(type, tree); - } + return super.visitDeclared(type, tree); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java b/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java index 4d5c1f2ca0f..ac80d9aa3f8 100644 --- a/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java +++ b/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java @@ -1,5 +1,8 @@ package org.checkerframework.common.value; +import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; import org.checkerframework.dataflow.expression.FieldAccess; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.dataflow.expression.JavaExpressionConverter; @@ -8,11 +11,6 @@ import org.checkerframework.dataflow.expression.ValueLiteral; import org.checkerframework.framework.type.AnnotatedTypeFactory; -import java.util.List; - -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; - /** * Optimize the given JavaExpression. If the supplied factory is a {@code * ValueAnnotatedTypeFactory}, this implementation replaces any expression that the factory has an @@ -21,62 +19,60 @@ */ public class JavaExpressionOptimizer extends JavaExpressionConverter { - /** - * Annotated type factory. If it is a {@code ValueAnnotatedTypeFactory}, then more optimizations - * are possible. - */ - private final AnnotatedTypeFactory atypeFactory; + /** + * Annotated type factory. If it is a {@code ValueAnnotatedTypeFactory}, then more optimizations + * are possible. + */ + private final AnnotatedTypeFactory atypeFactory; - /** - * Creates a JavaExpressionOptimizer. - * - * @param atypeFactory an annotated type factory - */ - public JavaExpressionOptimizer(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** + * Creates a JavaExpressionOptimizer. + * + * @param atypeFactory an annotated type factory + */ + public JavaExpressionOptimizer(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - @Override - protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { - // Replace references to compile-time constant fields by the constant itself. - if (fieldAccessExpr.isFinal()) { - Object constant = fieldAccessExpr.getField().getConstantValue(); - if (constant != null && !(constant instanceof String)) { - return new ValueLiteral(fieldAccessExpr.getType(), constant); - } - } - return super.visitFieldAccess(fieldAccessExpr, unused); + @Override + protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { + // Replace references to compile-time constant fields by the constant itself. + if (fieldAccessExpr.isFinal()) { + Object constant = fieldAccessExpr.getField().getConstantValue(); + if (constant != null && !(constant instanceof String)) { + return new ValueLiteral(fieldAccessExpr.getType(), constant); + } } + return super.visitFieldAccess(fieldAccessExpr, unused); + } - @Override - protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { - if (atypeFactory instanceof ValueAnnotatedTypeFactory) { - Element element = localVarExpr.getElement(); - Long exactValue = - ValueCheckerUtils.getExactValue( - element, (ValueAnnotatedTypeFactory) atypeFactory); - if (exactValue != null) { - return new ValueLiteral(localVarExpr.getType(), exactValue.intValue()); - } - } - return super.visitLocalVariable(localVarExpr, unused); + @Override + protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { + if (atypeFactory instanceof ValueAnnotatedTypeFactory) { + Element element = localVarExpr.getElement(); + Long exactValue = + ValueCheckerUtils.getExactValue(element, (ValueAnnotatedTypeFactory) atypeFactory); + if (exactValue != null) { + return new ValueLiteral(localVarExpr.getType(), exactValue.intValue()); + } } + return super.visitLocalVariable(localVarExpr, unused); + } - @Override - protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { - JavaExpression optReceiver = convert(methodCallExpr.getReceiver()); - List optArguments = convert(methodCallExpr.getArguments()); - // Length of string literal: convert it to an integer literal. - if (methodCallExpr.getElement().getSimpleName().contentEquals("length") - && optReceiver instanceof ValueLiteral) { - Object value = ((ValueLiteral) optReceiver).getValue(); - if (value instanceof String) { - return new ValueLiteral( - atypeFactory.types.getPrimitiveType(TypeKind.INT), - ((String) value).length()); - } - } - return new MethodCall( - methodCallExpr.getType(), methodCallExpr.getElement(), optReceiver, optArguments); + @Override + protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { + JavaExpression optReceiver = convert(methodCallExpr.getReceiver()); + List optArguments = convert(methodCallExpr.getArguments()); + // Length of string literal: convert it to an integer literal. + if (methodCallExpr.getElement().getSimpleName().contentEquals("length") + && optReceiver instanceof ValueLiteral) { + Object value = ((ValueLiteral) optReceiver).getValue(); + if (value instanceof String) { + return new ValueLiteral( + atypeFactory.types.getPrimitiveType(TypeKind.INT), ((String) value).length()); + } } + return new MethodCall( + methodCallExpr.getType(), methodCallExpr.getElement(), optReceiver, optArguments); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java b/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java index 1cef9b5690f..dd76ce607e5 100644 --- a/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java +++ b/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java @@ -1,5 +1,8 @@ package org.checkerframework.common.value; +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.ArrayLenRange; import org.checkerframework.common.value.qual.IntVal; @@ -8,11 +11,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; -import java.util.ArrayList; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; - /** * A mutable abstraction that can be either a range or a list of values that could come from an * {@link ArrayLen} or {@link IntVal}. This abstraction reduces the number of cases that {@link @@ -23,121 +21,121 @@ * to be used to reason about ArrayLen and ArrayLenRange values. */ class RangeOrListOfValues { - private Range range; - private List values; - private boolean isRange; + private Range range; + private List values; + private boolean isRange; - public RangeOrListOfValues(List values) { - this.values = new ArrayList<>(); - isRange = false; - addAll(values); - } + public RangeOrListOfValues(List values) { + this.values = new ArrayList<>(); + isRange = false; + addAll(values); + } - public RangeOrListOfValues(Range range) { - this.range = range; - isRange = true; - } + public RangeOrListOfValues(Range range) { + this.range = range; + isRange = true; + } - public void add(Range otherRange) { - if (isRange) { - range = range.union(otherRange); - } else { - convertToRange(); - add(otherRange); - } + public void add(Range otherRange) { + if (isRange) { + range = range.union(otherRange); + } else { + convertToRange(); + add(otherRange); } + } - /** - * If this is not a range, adds all members of newValues to the list. Otherwise, extends the - * range as appropriate based on the max and min of newValues. If adding newValues to a - * non-range would cause the list to become too large, converts this into a range. - * - *

If reading from an {@link org.checkerframework.common.value.qual.IntRange} annotation, - * {@link #convertLongsToInts(List)} should be called before calling this method. - * - * @param newValues values to add - */ - public void addAll(List newValues) { - if (isRange) { - range = range.union(Range.create(newValues)); - } else { - for (Integer i : newValues) { - if (!values.contains(i)) { - values.add(i); - } - } - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - convertToRange(); - } + /** + * If this is not a range, adds all members of newValues to the list. Otherwise, extends the range + * as appropriate based on the max and min of newValues. If adding newValues to a non-range would + * cause the list to become too large, converts this into a range. + * + *

If reading from an {@link org.checkerframework.common.value.qual.IntRange} annotation, + * {@link #convertLongsToInts(List)} should be called before calling this method. + * + * @param newValues values to add + */ + public void addAll(List newValues) { + if (isRange) { + range = range.union(Range.create(newValues)); + } else { + for (Integer i : newValues) { + if (!values.contains(i)) { + values.add(i); } + } + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + convertToRange(); + } } + } - /** - * Produces the most precise annotation that captures the information stored in this - * RangeOrListofValues. The result is either a {@link ArrayLen} or a {@link ArrayLenRange}. - * - * @param atypeFactory the type factory - * @return an annotation correspending to this RangeOrListofValues - */ - public AnnotationMirror createAnnotation(ValueAnnotatedTypeFactory atypeFactory) { - if (isRange) { - return atypeFactory.createArrayLenRangeAnnotation(range); - } else { - return atypeFactory.createArrayLenAnnotation(values); - } + /** + * Produces the most precise annotation that captures the information stored in this + * RangeOrListofValues. The result is either a {@link ArrayLen} or a {@link ArrayLenRange}. + * + * @param atypeFactory the type factory + * @return an annotation correspending to this RangeOrListofValues + */ + public AnnotationMirror createAnnotation(ValueAnnotatedTypeFactory atypeFactory) { + if (isRange) { + return atypeFactory.createArrayLenRangeAnnotation(range); + } else { + return atypeFactory.createArrayLenAnnotation(values); } + } - /** - * Converts a Long to an Integer by clipping it to the int range. - * - * @param l a Long integer - * @return the value clipped to the Integer range - */ - private static Integer convertLongToInt(Long l) { - if (l > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } else if (l < Integer.MIN_VALUE) { - return Integer.MIN_VALUE; - } else { - return l.intValue(); - } + /** + * Converts a Long to an Integer by clipping it to the int range. + * + * @param l a Long integer + * @return the value clipped to the Integer range + */ + private static Integer convertLongToInt(Long l) { + if (l > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else if (l < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } else { + return l.intValue(); } + } - /** - * To be called before addAll. Converts Longs to Integers by clipping them to the int range; - * meant to be used with ArrayLenRange (which only handles Ints). - * - * @param newValues a list of Long integers - * @return a list of Integers - */ - public static List convertLongsToInts(List newValues) { - return CollectionsPlume.mapList(RangeOrListOfValues::convertLongToInt, newValues); - } + /** + * To be called before addAll. Converts Longs to Integers by clipping them to the int range; meant + * to be used with ArrayLenRange (which only handles Ints). + * + * @param newValues a list of Long integers + * @return a list of Integers + */ + public static List convertLongsToInts(List newValues) { + return CollectionsPlume.mapList(RangeOrListOfValues::convertLongToInt, newValues); + } - /** - * Transforms this into a range. Fails if there are no values in the list. Has no effect if this - * is already a range. - */ - public void convertToRange() { - if (!isRange) { - isRange = true; - range = Range.create(values); - values = null; - } + /** + * Transforms this into a range. Fails if there are no values in the list. Has no effect if this + * is already a range. + */ + public void convertToRange() { + if (!isRange) { + isRange = true; + range = Range.create(values); + values = null; } + } - @Override - public String toString() { - if (isRange) { - return range.toString(); - } else { - if (values.isEmpty()) { - return "[]"; - } - String res = "["; - res += StringsPlume.join(", ", values); - res += "]"; - return res; - } + @Override + public String toString() { + if (isRange) { + return range.toString(); + } else { + if (values.isEmpty()) { + return "[]"; + } + String res = "["; + res += StringsPlume.join(", ", values); + res += "]"; + return res; } + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java b/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java index 3cde83c2690..bd95213a6b9 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java @@ -3,17 +3,6 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; -import org.checkerframework.checker.signature.qual.ClassGetName; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; - import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -22,11 +11,19 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; - import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; +import org.checkerframework.checker.signature.qual.ClassGetName; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; // The use of reflection in ReflectiveEvaluator is troubling. // A static analysis such as the Checker Framework should always use compiler APIs, never @@ -43,375 +40,357 @@ */ public class ReflectiveEvaluator { - /** The checker that is using this ReflectiveEvaluator. */ - private final BaseTypeChecker checker; + /** The checker that is using this ReflectiveEvaluator. */ + private final BaseTypeChecker checker; - /** - * Whether to report warnings about problems with evaluation. Controlled by the - * -AreportEvalWarns command-line option. - */ - private final boolean reportWarnings; + /** + * Whether to report warnings about problems with evaluation. Controlled by the -AreportEvalWarns + * command-line option. + */ + private final boolean reportWarnings; - /** - * Create a new ReflectiveEvaluator. - * - * @param checker the BaseTypeChecker - * @param factory the annotated type factory - * @param reportWarnings if true, report warnings about problems with evaluation - */ - public ReflectiveEvaluator( - BaseTypeChecker checker, ValueAnnotatedTypeFactory factory, boolean reportWarnings) { - this.checker = checker; - this.reportWarnings = reportWarnings; - } + /** + * Create a new ReflectiveEvaluator. + * + * @param checker the BaseTypeChecker + * @param factory the annotated type factory + * @param reportWarnings if true, report warnings about problems with evaluation + */ + public ReflectiveEvaluator( + BaseTypeChecker checker, ValueAnnotatedTypeFactory factory, boolean reportWarnings) { + this.checker = checker; + this.reportWarnings = reportWarnings; + } - /** - * Returns all possible values that the method may return, or null if the method could not be - * evaluated. - * - * @param allArgValues a list of lists where the first list corresponds to all possible values - * for the first argument. Pass null to indicate that the method has no arguments. - * @param receiverValues a list of possible receiver values. null indicates that the method has - * no receiver. - * @param tree location to report any errors - * @return all possible values that the method may return, or null if the method could not be - * evaluated - */ - public @Nullable List evaluateMethodCall( - @Nullable List> allArgValues, - @Nullable List receiverValues, - MethodInvocationTree tree) { - Method method = getMethodObject(tree); - if (method == null) { - return null; - } + /** + * Returns all possible values that the method may return, or null if the method could not be + * evaluated. + * + * @param allArgValues a list of lists where the first list corresponds to all possible values for + * the first argument. Pass null to indicate that the method has no arguments. + * @param receiverValues a list of possible receiver values. null indicates that the method has no + * receiver. + * @param tree location to report any errors + * @return all possible values that the method may return, or null if the method could not be + * evaluated + */ + public @Nullable List evaluateMethodCall( + @Nullable List> allArgValues, + @Nullable List receiverValues, + MethodInvocationTree tree) { + Method method = getMethodObject(tree); + if (method == null) { + return null; + } - if (receiverValues == null) { - // Method does not have a receiver - // the first parameter of Method.invoke should be null - receiverValues = Collections.singletonList(null); - } + if (receiverValues == null) { + // Method does not have a receiver + // the first parameter of Method.invoke should be null + receiverValues = Collections.singletonList(null); + } - List listOfArguments; - if (allArgValues == null) { - // Method does not have arguments - listOfArguments = Collections.singletonList(null); - } else { - // Find all possible argument sets - listOfArguments = cartesianProduct(allArgValues, allArgValues.size() - 1); - } + List listOfArguments; + if (allArgValues == null) { + // Method does not have arguments + listOfArguments = Collections.singletonList(null); + } else { + // Find all possible argument sets + listOfArguments = cartesianProduct(allArgValues, allArgValues.size() - 1); + } - if (method.isVarArgs()) { - int numberOfParameters = method.getParameterTypes().length; - listOfArguments = - CollectionsPlume.mapList( - (Object[] args) -> normalizeVararg(args, numberOfParameters), - listOfArguments); - } + if (method.isVarArgs()) { + int numberOfParameters = method.getParameterTypes().length; + listOfArguments = + CollectionsPlume.mapList( + (Object[] args) -> normalizeVararg(args, numberOfParameters), listOfArguments); + } - List results = new ArrayList<>(listOfArguments.size()); - for (Object[] arguments : listOfArguments) { - for (Object receiver : receiverValues) { - try { - results.add(method.invoke(receiver, arguments)); - } catch (InvocationTargetException e) { - if (reportWarnings) { - checker.reportWarning( - tree, - "method.evaluation.exception", - method, - e.getTargetException().toString()); - } - // Method evaluation will always fail, so don't bother - // trying again - return null; - } catch (ExceptionInInitializerError e) { - if (reportWarnings) { - checker.reportWarning( - tree, - "method.evaluation.exception", - method, - e.getCause().toString()); - } - return null; - } catch (IllegalArgumentException e) { - if (reportWarnings) { - String args = StringsPlume.join(", ", arguments); - checker.reportWarning( - tree, - "method.evaluation.exception", - method, - e.getLocalizedMessage() + ": " + args); - } - return null; - } catch (Throwable e) { - // Catch any exception thrown because they shouldn't crash the type checker. - if (reportWarnings) { - checker.reportWarning(tree, "method.evaluation.failed", method); - } - return null; - } - } + List results = new ArrayList<>(listOfArguments.size()); + for (Object[] arguments : listOfArguments) { + for (Object receiver : receiverValues) { + try { + results.add(method.invoke(receiver, arguments)); + } catch (InvocationTargetException e) { + if (reportWarnings) { + checker.reportWarning( + tree, "method.evaluation.exception", method, e.getTargetException().toString()); + } + // Method evaluation will always fail, so don't bother + // trying again + return null; + } catch (ExceptionInInitializerError e) { + if (reportWarnings) { + checker.reportWarning( + tree, "method.evaluation.exception", method, e.getCause().toString()); + } + return null; + } catch (IllegalArgumentException e) { + if (reportWarnings) { + String args = StringsPlume.join(", ", arguments); + checker.reportWarning( + tree, "method.evaluation.exception", method, e.getLocalizedMessage() + ": " + args); + } + return null; + } catch (Throwable e) { + // Catch any exception thrown because they shouldn't crash the type checker. + if (reportWarnings) { + checker.reportWarning(tree, "method.evaluation.failed", method); + } + return null; } - return results; + } } + return results; + } - /** An empty Object array. */ - private static final Object[] emptyObjectArray = new Object[] {}; + /** An empty Object array. */ + private static final Object[] emptyObjectArray = new Object[] {}; - /** - * This method normalizes an array of arguments to a varargs method by changing the arguments - * associated with the varargs parameter into an array. - * - * @param arguments an array of arguments for {@code method}. The length is at least {@code - * numberOfParameters - 1}. - * @param numberOfParameters number of parameters of the vararg method - * @return the length of the array is exactly {@code numberOfParameters} - */ - private Object[] normalizeVararg(Object[] arguments, int numberOfParameters) { + /** + * This method normalizes an array of arguments to a varargs method by changing the arguments + * associated with the varargs parameter into an array. + * + * @param arguments an array of arguments for {@code method}. The length is at least {@code + * numberOfParameters - 1}. + * @param numberOfParameters number of parameters of the vararg method + * @return the length of the array is exactly {@code numberOfParameters} + */ + private Object[] normalizeVararg(Object[] arguments, int numberOfParameters) { - if (arguments == null) { - // null means no arguments. For varargs no arguments is an empty array. - arguments = emptyObjectArray; - } - Object[] newArgs = new Object[numberOfParameters]; - Object[] varArgsArray; - int numOfVarArgs = arguments.length - numberOfParameters + 1; - if (numOfVarArgs > 0) { - System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); - varArgsArray = new Object[numOfVarArgs]; - System.arraycopy(arguments, numberOfParameters - 1, varArgsArray, 0, numOfVarArgs); - } else { - System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); - varArgsArray = emptyObjectArray; - } - newArgs[numberOfParameters - 1] = varArgsArray; - return newArgs; + if (arguments == null) { + // null means no arguments. For varargs no arguments is an empty array. + arguments = emptyObjectArray; } + Object[] newArgs = new Object[numberOfParameters]; + Object[] varArgsArray; + int numOfVarArgs = arguments.length - numberOfParameters + 1; + if (numOfVarArgs > 0) { + System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); + varArgsArray = new Object[numOfVarArgs]; + System.arraycopy(arguments, numberOfParameters - 1, varArgsArray, 0, numOfVarArgs); + } else { + System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); + varArgsArray = emptyObjectArray; + } + newArgs[numberOfParameters - 1] = varArgsArray; + return newArgs; + } - /** - * Method for reflectively obtaining a method object so it can (potentially) be statically - * executed by the checker for constant propagation. - * - * @param tree a method invocation tree - * @return the Method object corresponding to the method invocation tree - */ - private @Nullable Method getMethodObject(MethodInvocationTree tree) { - ExecutableElement ele = TreeUtils.elementFromUse(tree); - List> paramClasses = null; - try { - @CanonicalNameOrEmpty String className = - TypesUtils.getQualifiedName((DeclaredType) ele.getEnclosingElement().asType()); - paramClasses = getParameterClasses(ele); - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Class.toString - Class clazz = Class.forName(className.toString()); - Method method = - clazz.getMethod( - ele.getSimpleName().toString(), paramClasses.toArray(new Class[0])); - @SuppressWarnings("deprecation") // TODO: find alternative - boolean acc = method.isAccessible(); - if (!acc) { - method.setAccessible(true); - } - return method; - } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { - if (reportWarnings) { - checker.reportWarning(tree, "class.find.failed", ele.getEnclosingElement()); - } - return null; + /** + * Method for reflectively obtaining a method object so it can (potentially) be statically + * executed by the checker for constant propagation. + * + * @param tree a method invocation tree + * @return the Method object corresponding to the method invocation tree + */ + private @Nullable Method getMethodObject(MethodInvocationTree tree) { + ExecutableElement ele = TreeUtils.elementFromUse(tree); + List> paramClasses = null; + try { + @CanonicalNameOrEmpty String className = + TypesUtils.getQualifiedName((DeclaredType) ele.getEnclosingElement().asType()); + paramClasses = getParameterClasses(ele); + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Class.toString + Class clazz = Class.forName(className.toString()); + Method method = + clazz.getMethod(ele.getSimpleName().toString(), paramClasses.toArray(new Class[0])); + @SuppressWarnings("deprecation") // TODO: find alternative + boolean acc = method.isAccessible(); + if (!acc) { + method.setAccessible(true); + } + return method; + } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { + if (reportWarnings) { + checker.reportWarning(tree, "class.find.failed", ele.getEnclosingElement()); + } + return null; - } catch (Throwable e) { - // The class we attempted to getMethod from inside the call to getMethodObject. - Element classElem = ele.getEnclosingElement(); + } catch (Throwable e) { + // The class we attempted to getMethod from inside the call to getMethodObject. + Element classElem = ele.getEnclosingElement(); - if (classElem == null) { - if (reportWarnings) { - checker.reportWarning( - tree, "method.find.failed", ele.getSimpleName(), paramClasses); - } - } else { - if (reportWarnings) { - checker.reportWarning( - tree, - "method.find.failed.in.class", - ele.getSimpleName(), - paramClasses, - classElem); - } - } - return null; + if (classElem == null) { + if (reportWarnings) { + checker.reportWarning(tree, "method.find.failed", ele.getSimpleName(), paramClasses); } + } else { + if (reportWarnings) { + checker.reportWarning( + tree, "method.find.failed.in.class", ele.getSimpleName(), paramClasses, classElem); + } + } + return null; } + } - /** - * Returns the classes of the given method's formal parameters. - * - * @param ele a method or constructor - * @return the classes of the given method's formal parameters - * @throws ClassNotFoundException if the class cannot be found - */ - private List> getParameterClasses(ExecutableElement ele) - throws ClassNotFoundException { - return CollectionsPlume.mapList( - (Element e) -> TypesUtils.getClassFromType(ElementUtils.getType(e)), - ele.getParameters()); - } + /** + * Returns the classes of the given method's formal parameters. + * + * @param ele a method or constructor + * @return the classes of the given method's formal parameters + * @throws ClassNotFoundException if the class cannot be found + */ + private List> getParameterClasses(ExecutableElement ele) throws ClassNotFoundException { + return CollectionsPlume.mapList( + (Element e) -> TypesUtils.getClassFromType(ElementUtils.getType(e)), ele.getParameters()); + } - /** - * Returns all combinations of the elements of the given lists. - * - * @param allArgValues the lists whose cartesian product to form - * @param whichArg pass {@code allArgValues.size() - 1} - * @return all combinations of the elements of the given lists - */ - private List cartesianProduct(List> allArgValues, int whichArg) { - List argValues = allArgValues.get(whichArg); - List tuples = new ArrayList<>(argValues.size()); + /** + * Returns all combinations of the elements of the given lists. + * + * @param allArgValues the lists whose cartesian product to form + * @param whichArg pass {@code allArgValues.size() - 1} + * @return all combinations of the elements of the given lists + */ + private List cartesianProduct(List> allArgValues, int whichArg) { + List argValues = allArgValues.get(whichArg); + List tuples = new ArrayList<>(argValues.size()); - for (Object value : argValues) { - if (whichArg == 0) { - Object[] objects = new Object[allArgValues.size()]; - objects[0] = value; - tuples.add(objects); - } else { - List lastTuples = cartesianProduct(allArgValues, whichArg - 1); - List copies = copy(lastTuples); - for (Object[] copy : copies) { - copy[whichArg] = value; - } - tuples.addAll(copies); - } + for (Object value : argValues) { + if (whichArg == 0) { + Object[] objects = new Object[allArgValues.size()]; + objects[0] = value; + tuples.add(objects); + } else { + List lastTuples = cartesianProduct(allArgValues, whichArg - 1); + List copies = copy(lastTuples); + for (Object[] copy : copies) { + copy[whichArg] = value; } - return tuples; + tuples.addAll(copies); + } } + return tuples; + } - /** - * Returns a depth-2 copy of the given list. In the returned value, the list and the arrays in - * it are new, but the elements of the arrays are shared with the argument. - * - * @param lastTuples a list of arrays - * @return a depth-2 copy of the given list - */ - private List copy(List lastTuples) { - return CollectionsPlume.mapList( - (Object[] list) -> Arrays.copyOf(list, list.length), lastTuples); - } + /** + * Returns a depth-2 copy of the given list. In the returned value, the list and the arrays in it + * are new, but the elements of the arrays are shared with the argument. + * + * @param lastTuples a list of arrays + * @return a depth-2 copy of the given list + */ + private List copy(List lastTuples) { + return CollectionsPlume.mapList( + (Object[] list) -> Arrays.copyOf(list, list.length), lastTuples); + } - /** - * Return the value of a static field access. Return null if accessing the field reflectively - * fails. - * - * @param classname the class containing the field - * @param fieldName the name of the field - * @param tree the static field access in the program. It is a MemberSelectTree or an - * IdentifierTree and is used for diagnostics. - * @return the value of the static field access, or null if it cannot be determined - */ - public @Nullable Object evaluateStaticFieldAccess( - @ClassGetName String classname, String fieldName, ExpressionTree tree) { - try { - Class recClass = Class.forName(classname); - Field field = recClass.getField(fieldName); - return field.get(recClass); + /** + * Return the value of a static field access. Return null if accessing the field reflectively + * fails. + * + * @param classname the class containing the field + * @param fieldName the name of the field + * @param tree the static field access in the program. It is a MemberSelectTree or an + * IdentifierTree and is used for diagnostics. + * @return the value of the static field access, or null if it cannot be determined + */ + public @Nullable Object evaluateStaticFieldAccess( + @ClassGetName String classname, String fieldName, ExpressionTree tree) { + try { + Class recClass = Class.forName(classname); + Field field = recClass.getField(fieldName); + return field.get(recClass); - } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { - if (reportWarnings) { - checker.reportWarning( - tree, "class.find.failed", classname, e.getClass() + ": " + e.getMessage()); - } - return null; - } catch (Throwable e) { - // Catch all exceptions so that the checker doesn't crash. - if (reportWarnings) { - checker.reportWarning( - tree, - "field.access.failed", - fieldName, - classname, - e.getClass() + ": " + e.getMessage()); - } - return null; - } + } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { + if (reportWarnings) { + checker.reportWarning( + tree, "class.find.failed", classname, e.getClass() + ": " + e.getMessage()); + } + return null; + } catch (Throwable e) { + // Catch all exceptions so that the checker doesn't crash. + if (reportWarnings) { + checker.reportWarning( + tree, + "field.access.failed", + fieldName, + classname, + e.getClass() + ": " + e.getMessage()); + } + return null; } + } - public @Nullable List evaluteConstructorCall( - List> argValues, NewClassTree tree, TypeMirror typeToCreate) { - Constructor constructor; - try { - // get the constructor - constructor = getConstructorObject(tree, typeToCreate); - } catch (Throwable e) { - // Catch all exception so that the checker doesn't crash - if (reportWarnings) { - checker.reportWarning(tree, "constructor.invocation.failed"); - } - return null; - } - if (constructor == null) { - return null; - } + public @Nullable List evaluteConstructorCall( + List> argValues, NewClassTree tree, TypeMirror typeToCreate) { + Constructor constructor; + try { + // get the constructor + constructor = getConstructorObject(tree, typeToCreate); + } catch (Throwable e) { + // Catch all exception so that the checker doesn't crash + if (reportWarnings) { + checker.reportWarning(tree, "constructor.invocation.failed"); + } + return null; + } + if (constructor == null) { + return null; + } - List listOfArguments; - if (argValues == null) { - // Method does not have arguments - listOfArguments = Collections.singletonList(null); - } else { - // Find all possible argument sets - listOfArguments = cartesianProduct(argValues, argValues.size() - 1); - } + List listOfArguments; + if (argValues == null) { + // Method does not have arguments + listOfArguments = Collections.singletonList(null); + } else { + // Find all possible argument sets + listOfArguments = cartesianProduct(argValues, argValues.size() - 1); + } - List results = new ArrayList<>(listOfArguments.size()); - for (Object[] arguments : listOfArguments) { - try { - results.add(constructor.newInstance(arguments)); - } catch (Throwable e) { - if (reportWarnings) { - checker.reportWarning( - tree, - "constructor.evaluation.failed", - typeToCreate, - StringsPlume.join(", ", arguments)); - } - return null; - } + List results = new ArrayList<>(listOfArguments.size()); + for (Object[] arguments : listOfArguments) { + try { + results.add(constructor.newInstance(arguments)); + } catch (Throwable e) { + if (reportWarnings) { + checker.reportWarning( + tree, + "constructor.evaluation.failed", + typeToCreate, + StringsPlume.join(", ", arguments)); } - return results; + return null; + } } + return results; + } - private Constructor getConstructorObject(NewClassTree tree, TypeMirror typeToCreate) - throws ClassNotFoundException, NoSuchMethodException { - ExecutableElement ele = TreeUtils.elementFromUse(tree); - List> paramClasses = getParameterClasses(ele); - Class recClass = boxPrimitives(TypesUtils.getClassFromType(typeToCreate)); - Constructor constructor = recClass.getConstructor(paramClasses.toArray(new Class[0])); - return constructor; - } + private Constructor getConstructorObject(NewClassTree tree, TypeMirror typeToCreate) + throws ClassNotFoundException, NoSuchMethodException { + ExecutableElement ele = TreeUtils.elementFromUse(tree); + List> paramClasses = getParameterClasses(ele); + Class recClass = boxPrimitives(TypesUtils.getClassFromType(typeToCreate)); + Constructor constructor = recClass.getConstructor(paramClasses.toArray(new Class[0])); + return constructor; + } - /** - * Returns the boxed primitive type if the passed type is an (unboxed) primitive. Otherwise it - * returns the passed type. - * - * @param type a type to box or to return unchanged - * @return a boxed primitive type, if the argument was primitive; otherwise the argument - */ - private static Class boxPrimitives(Class type) { - if (type == byte.class) { - return Byte.class; - } else if (type == short.class) { - return Short.class; - } else if (type == int.class) { - return Integer.class; - } else if (type == long.class) { - return Long.class; - } else if (type == float.class) { - return Float.class; - } else if (type == double.class) { - return Double.class; - } else if (type == char.class) { - return Character.class; - } else if (type == boolean.class) { - return Boolean.class; - } - return type; + /** + * Returns the boxed primitive type if the passed type is an (unboxed) primitive. Otherwise it + * returns the passed type. + * + * @param type a type to box or to return unchanged + * @return a boxed primitive type, if the argument was primitive; otherwise the argument + */ + private static Class boxPrimitives(Class type) { + if (type == byte.class) { + return Byte.class; + } else if (type == short.class) { + return Short.class; + } else if (type == int.class) { + return Integer.class; + } else if (type == long.class) { + return Long.class; + } else if (type == float.class) { + return Float.class; + } else if (type == double.class) { + return Double.class; + } else if (type == char.class) { + return Character.class; + } else if (type == boolean.class) { + return Boolean.class; } + return type; + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index a8edb9e8bec..05be499f21e 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -4,7 +4,22 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; - +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.checker.regex.qual.Regex; @@ -63,1699 +78,1649 @@ import org.plumelib.util.ArraySet; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.regex.Pattern; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** AnnotatedTypeFactory for the Value type system. */ public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** Fully-qualified class name of {@link UnknownVal}. */ - public static final String UNKNOWN_NAME = "org.checkerframework.common.value.qual.UnknownVal"; - - /** Fully-qualified class name of {@link BottomVal}. */ - public static final String BOTTOMVAL_NAME = "org.checkerframework.common.value.qual.BottomVal"; + /** Fully-qualified class name of {@link UnknownVal}. */ + public static final String UNKNOWN_NAME = "org.checkerframework.common.value.qual.UnknownVal"; - /** Fully-qualified class name of {@link PolyValue}. */ - public static final String POLY_NAME = "org.checkerframework.common.value.qual.PolyValue"; + /** Fully-qualified class name of {@link BottomVal}. */ + public static final String BOTTOMVAL_NAME = "org.checkerframework.common.value.qual.BottomVal"; - /** Fully-qualified class name of {@link ArrayLen}. */ - public static final String ARRAYLEN_NAME = "org.checkerframework.common.value.qual.ArrayLen"; + /** Fully-qualified class name of {@link PolyValue}. */ + public static final String POLY_NAME = "org.checkerframework.common.value.qual.PolyValue"; - /** Fully-qualified class name of {@link BoolVal}. */ - public static final String BOOLVAL_NAME = "org.checkerframework.common.value.qual.BoolVal"; + /** Fully-qualified class name of {@link ArrayLen}. */ + public static final String ARRAYLEN_NAME = "org.checkerframework.common.value.qual.ArrayLen"; - /** Fully-qualified class name of {@link DoubleVal}. */ - public static final String DOUBLEVAL_NAME = "org.checkerframework.common.value.qual.DoubleVal"; + /** Fully-qualified class name of {@link BoolVal}. */ + public static final String BOOLVAL_NAME = "org.checkerframework.common.value.qual.BoolVal"; - /** Fully-qualified class name of {@link IntVal}. */ - public static final String INTVAL_NAME = "org.checkerframework.common.value.qual.IntVal"; + /** Fully-qualified class name of {@link DoubleVal}. */ + public static final String DOUBLEVAL_NAME = "org.checkerframework.common.value.qual.DoubleVal"; - /** Fully-qualified class name of {@link StringVal}. */ - public static final String STRINGVAL_NAME = "org.checkerframework.common.value.qual.StringVal"; + /** Fully-qualified class name of {@link IntVal}. */ + public static final String INTVAL_NAME = "org.checkerframework.common.value.qual.IntVal"; - /** Fully-qualified class name of {@link ArrayLenRange}. */ - public static final String ARRAYLENRANGE_NAME = - "org.checkerframework.common.value.qual.ArrayLenRange"; + /** Fully-qualified class name of {@link StringVal}. */ + public static final String STRINGVAL_NAME = "org.checkerframework.common.value.qual.StringVal"; - /** Fully-qualified class name of {@link IntRange}. */ - public static final String INTRANGE_NAME = "org.checkerframework.common.value.qual.IntRange"; + /** Fully-qualified class name of {@link ArrayLenRange}. */ + public static final String ARRAYLENRANGE_NAME = + "org.checkerframework.common.value.qual.ArrayLenRange"; - /** Fully-qualified class name of {@link IntRangeFromGTENegativeOne}. */ - public static final String INTRANGE_FROMGTENEGONE_NAME = - "org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne"; + /** Fully-qualified class name of {@link IntRange}. */ + public static final String INTRANGE_NAME = "org.checkerframework.common.value.qual.IntRange"; - /** Fully-qualified class name of {@link IntRangeFromNonNegative}. */ - public static final String INTRANGE_FROMNONNEG_NAME = - "org.checkerframework.common.value.qual.IntRangeFromNonNegative"; + /** Fully-qualified class name of {@link IntRangeFromGTENegativeOne}. */ + public static final String INTRANGE_FROMGTENEGONE_NAME = + "org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne"; - /** Fully-qualified class name of {@link IntRangeFromPositive}. */ - public static final String INTRANGE_FROMPOS_NAME = - "org.checkerframework.common.value.qual.IntRangeFromPositive"; + /** Fully-qualified class name of {@link IntRangeFromNonNegative}. */ + public static final String INTRANGE_FROMNONNEG_NAME = + "org.checkerframework.common.value.qual.IntRangeFromNonNegative"; - /** Fully-qualified class name of {@link MinLen}. */ - public static final String MINLEN_NAME = "org.checkerframework.common.value.qual.MinLen"; + /** Fully-qualified class name of {@link IntRangeFromPositive}. */ + public static final String INTRANGE_FROMPOS_NAME = + "org.checkerframework.common.value.qual.IntRangeFromPositive"; - /** Fully-qualified class name of {@link MatchesRegex}. */ - public static final String MATCHES_REGEX_NAME = - "org.checkerframework.common.value.qual.MatchesRegex"; + /** Fully-qualified class name of {@link MinLen}. */ + public static final String MINLEN_NAME = "org.checkerframework.common.value.qual.MinLen"; - /** Fully-qualified class name of {@link DoesNotMatchRegex}. */ - public static final String DOES_NOT_MATCH_REGEX_NAME = - "org.checkerframework.common.value.qual.DoesNotMatchRegex"; + /** Fully-qualified class name of {@link MatchesRegex}. */ + public static final String MATCHES_REGEX_NAME = + "org.checkerframework.common.value.qual.MatchesRegex"; - /** The maximum number of values allowed in an annotation's array. */ - protected static final int MAX_VALUES = 10; + /** Fully-qualified class name of {@link DoesNotMatchRegex}. */ + public static final String DOES_NOT_MATCH_REGEX_NAME = + "org.checkerframework.common.value.qual.DoesNotMatchRegex"; - /** The top type for this hierarchy. */ - protected final AnnotationMirror UNKNOWNVAL = - AnnotationBuilder.fromClass(elements, UnknownVal.class); + /** The maximum number of values allowed in an annotation's array. */ + protected static final int MAX_VALUES = 10; - /** The bottom type for this hierarchy. */ - protected final AnnotationMirror BOTTOMVAL = - AnnotationBuilder.fromClass(elements, BottomVal.class); + /** The top type for this hierarchy. */ + protected final AnnotationMirror UNKNOWNVAL = + AnnotationBuilder.fromClass(elements, UnknownVal.class); - /** The canonical @{@link PolyValue} annotation. */ - public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyValue.class); + /** The bottom type for this hierarchy. */ + protected final AnnotationMirror BOTTOMVAL = + AnnotationBuilder.fromClass(elements, BottomVal.class); - /** The canonical @{@link BoolVal}(true) annotation. */ - public final AnnotationMirror BOOLEAN_TRUE = - createBooleanAnnotation(Collections.singletonList(true)); + /** The canonical @{@link PolyValue} annotation. */ + public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyValue.class); - /** The canonical @{@link BoolVal}(false) annotation. */ - public final AnnotationMirror BOOLEAN_FALSE = - createBooleanAnnotation(Collections.singletonList(false)); - - /** The value() element/field of an @ArrayLen annotation. */ - protected final ExecutableElement arrayLenValueElement = - TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); - - /** The from() element/field of an @ArrayLenRange annotation. */ - protected final ExecutableElement arrayLenRangeFromElement = - TreeUtils.getMethod(ArrayLenRange.class, "from", 0, processingEnv); - - /** The to() element/field of an @ArrayLenRange annotation. */ - protected final ExecutableElement arrayLenRangeToElement = - TreeUtils.getMethod(ArrayLenRange.class, "to", 0, processingEnv); - - /** The value() element/field of a @BoolVal annotation. */ - protected final ExecutableElement boolValValueElement = - TreeUtils.getMethod(BoolVal.class, "value", 0, processingEnv); - - /** The value() element/field of a @DoubleVal annotation. */ - protected final ExecutableElement doubleValValueElement = - TreeUtils.getMethod(DoubleVal.class, "value", 0, processingEnv); - - /** The from() element/field of an @IntRange annotation. */ - protected final ExecutableElement intRangeFromElement = - TreeUtils.getMethod(IntRange.class, "from", 0, processingEnv); - - /** The to() element/field of an @IntRange annotation. */ - protected final ExecutableElement intRangeToElement = - TreeUtils.getMethod(IntRange.class, "to", 0, processingEnv); - - /** The value() element/field of a @IntVal annotation. */ - protected final ExecutableElement intValValueElement = - TreeUtils.getMethod(IntVal.class, "value", 0, processingEnv); - - /** The value() element/field of a @MatchesRegex annotation. */ - public final ExecutableElement matchesRegexValueElement = - TreeUtils.getMethod(MatchesRegex.class, "value", 0, processingEnv); - - /** The value() element/field of a @DoesNotMatchRegex annotation. */ - public final ExecutableElement doesNotMatchRegexValueElement = - TreeUtils.getMethod(DoesNotMatchRegex.class, "value", 0, processingEnv); - - /** The value() element/field of a @MinLen annotation. */ - protected final ExecutableElement minLenValueElement = - TreeUtils.getMethod(MinLen.class, "value", 0, processingEnv); - - /** The field() element/field of a @MinLenFieldInvariant annotation. */ - protected final ExecutableElement minLenFieldInvariantFieldElement = - TreeUtils.getMethod(MinLenFieldInvariant.class, "field", 0, processingEnv); - - /** The minLen() element/field of a @MinLenFieldInvariant annotation. */ - protected final ExecutableElement minLenFieldInvariantMinLenElement = - TreeUtils.getMethod(MinLenFieldInvariant.class, "minLen", 0, processingEnv); - - /** The value() element/field of a @StringVal annotation. */ - public final ExecutableElement stringValValueElement = - TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); - - /** Should this type factory report warnings? */ - private final boolean reportEvalWarnings; - - /** Helper class that evaluates statically executable methods, constructors, and fields. */ - // TODO: only used in ValueTreeAnnotator. Should it move there? - protected final ReflectiveEvaluator evaluator; - - /** Helper class that holds references to special methods. */ - private final ValueMethodIdentifier methods; - - @SuppressWarnings("StaticAssignmentInConstructor") // static Range.ignoreOverflow is gross - public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); - Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW); - evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); - - addAliasedTypeAnnotation("android.support.annotation.IntRange", IntRange.class, true); - - // The actual ArrayLenRange is created by - // {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}; - // this line just registers the alias. The BottomVal is never used. - addAliasedTypeAnnotation(MinLen.class, BOTTOMVAL); - - // @Positive is aliased here because @Positive provides useful - // information about @MinLen annotations. - // @NonNegative and @GTENegativeOne are aliased similarly so - // that it's possible to overwrite a function annotated to return - // @NonNegative with, for instance, a function that returns an @IntVal(0). - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.Positive", createIntRangeFromPositive()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.NonNegative", - createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.GTENegativeOne", - createIntRangeFromGTENegativeOne()); - // Must also alias any alias of three annotations above: - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.LengthOf", - createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.IndexFor", - createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.IndexOrHigh", - createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.IndexOrLow", - createIntRangeFromGTENegativeOne()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.SubstringIndexFor", - createIntRangeFromGTENegativeOne()); - - // PolyLength is syntactic sugar for both @PolySameLen and @PolyValue - addAliasedTypeAnnotation("org.checkerframework.checker.index.qual.PolyLength", POLY); - - // EnumVal is treated as StringVal internally by the checker. - addAliasedTypeAnnotation(EnumVal.class, StringVal.class, true); - - methods = new ValueMethodIdentifier(processingEnv); - - if (this.getClass() == ValueAnnotatedTypeFactory.class) { - this.postInit(); - } - } + /** The canonical @{@link BoolVal}(true) annotation. */ + public final AnnotationMirror BOOLEAN_TRUE = + createBooleanAnnotation(Collections.singletonList(true)); - /** Gets a helper object that holds references to methods with special handling. */ - ValueMethodIdentifier getMethodIdentifier() { - return methods; - } - - @Override - protected void applyInferredAnnotations(AnnotatedTypeMirror type, CFValue inferred) { - // Inference can widen an IntRange beyond the values possible for the Java type. Change the - // annotation here so it is no wider than is possible. - TypeMirror t = inferred.getUnderlyingType(); - AnnotationMirrorSet inferredAnnos = inferred.getAnnotations(); - AnnotationMirror intRange = - AnnotationUtils.getAnnotationByName(inferredAnnos, INTRANGE_NAME); - if (intRange != null && TypeKindUtils.primitiveOrBoxedToTypeKind(t) != null) { - Range range = getRange(intRange); - Range newRange = NumberUtils.castRange(t, range); - if (!newRange.equals(range)) { - inferredAnnos = AnnotationMirrorSet.singleton(createIntRangeAnnotation(newRange)); - } - } + /** The canonical @{@link BoolVal}(false) annotation. */ + public final AnnotationMirror BOOLEAN_FALSE = + createBooleanAnnotation(Collections.singletonList(false)); + + /** The value() element/field of an @ArrayLen annotation. */ + protected final ExecutableElement arrayLenValueElement = + TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); + + /** The from() element/field of an @ArrayLenRange annotation. */ + protected final ExecutableElement arrayLenRangeFromElement = + TreeUtils.getMethod(ArrayLenRange.class, "from", 0, processingEnv); + + /** The to() element/field of an @ArrayLenRange annotation. */ + protected final ExecutableElement arrayLenRangeToElement = + TreeUtils.getMethod(ArrayLenRange.class, "to", 0, processingEnv); + + /** The value() element/field of a @BoolVal annotation. */ + protected final ExecutableElement boolValValueElement = + TreeUtils.getMethod(BoolVal.class, "value", 0, processingEnv); + + /** The value() element/field of a @DoubleVal annotation. */ + protected final ExecutableElement doubleValValueElement = + TreeUtils.getMethod(DoubleVal.class, "value", 0, processingEnv); - DefaultInferredTypesApplier applier = - new DefaultInferredTypesApplier(getQualifierHierarchy(), this); - applier.applyInferredType(type, inferredAnnos, inferred.getUnderlyingType()); - } + /** The from() element/field of an @IntRange annotation. */ + protected final ExecutableElement intRangeFromElement = + TreeUtils.getMethod(IntRange.class, "from", 0, processingEnv); - @Override - public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { - if (AnnotationUtils.areSameByName(anno, MINLEN_NAME)) { - int from = getMinLenValue(anno); - return createArrayLenRangeAnnotation(from, Integer.MAX_VALUE); - } - - return super.canonicalAnnotation(anno); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Value Checker includes its own alias annotations, - // the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - ArrayLen.class, - ArrayLenRange.class, - IntVal.class, - IntRange.class, - BoolVal.class, - StringVal.class, - MatchesRegex.class, - DoesNotMatchRegex.class, - DoubleVal.class, - BottomVal.class, - UnknownVal.class, - IntRangeFromPositive.class, - IntRangeFromNonNegative.class, - IntRangeFromGTENegativeOne.class, - PolyValue.class)); - } - - @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new ValueTransfer(analysis); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new ValueQualifierHierarchy(this.getSupportedTypeQualifiers(), this); - } - - @Override - protected TypeHierarchy createTypeHierarchy() { - // This is a lot of code to replace annotations so that annotations that are equivalent - // qualifiers are the same annotation. - return new DefaultTypeHierarchy( - checker, - getQualifierHierarchy(), - checker.getBooleanOption("ignoreRawTypeArguments", true), - checker.hasOption("invariantArrays")) { - @Override - public StructuralEqualityComparer createEqualityComparer() { - return new StructuralEqualityComparer(areEqualVisitHistory) { - @Override - protected boolean arePrimaryAnnosEqual( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - type1.replaceAnnotation( - convertToUnknown( - convertSpecialIntRangeToStandardIntRange( - type1.getAnnotationInHierarchy(UNKNOWNVAL)))); - type2.replaceAnnotation( - convertToUnknown( - convertSpecialIntRangeToStandardIntRange( - type2.getAnnotationInHierarchy(UNKNOWNVAL)))); - - return super.arePrimaryAnnosEqual(type1, type2); - } - }; - } + /** The to() element/field of an @IntRange annotation. */ + protected final ExecutableElement intRangeToElement = + TreeUtils.getMethod(IntRange.class, "to", 0, processingEnv); + + /** The value() element/field of a @IntVal annotation. */ + protected final ExecutableElement intValValueElement = + TreeUtils.getMethod(IntVal.class, "value", 0, processingEnv); + + /** The value() element/field of a @MatchesRegex annotation. */ + public final ExecutableElement matchesRegexValueElement = + TreeUtils.getMethod(MatchesRegex.class, "value", 0, processingEnv); + + /** The value() element/field of a @DoesNotMatchRegex annotation. */ + public final ExecutableElement doesNotMatchRegexValueElement = + TreeUtils.getMethod(DoesNotMatchRegex.class, "value", 0, processingEnv); + + /** The value() element/field of a @MinLen annotation. */ + protected final ExecutableElement minLenValueElement = + TreeUtils.getMethod(MinLen.class, "value", 0, processingEnv); + + /** The field() element/field of a @MinLenFieldInvariant annotation. */ + protected final ExecutableElement minLenFieldInvariantFieldElement = + TreeUtils.getMethod(MinLenFieldInvariant.class, "field", 0, processingEnv); + + /** The minLen() element/field of a @MinLenFieldInvariant annotation. */ + protected final ExecutableElement minLenFieldInvariantMinLenElement = + TreeUtils.getMethod(MinLenFieldInvariant.class, "minLen", 0, processingEnv); + + /** The value() element/field of a @StringVal annotation. */ + public final ExecutableElement stringValValueElement = + TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); + + /** Should this type factory report warnings? */ + private final boolean reportEvalWarnings; + + /** Helper class that evaluates statically executable methods, constructors, and fields. */ + // TODO: only used in ValueTreeAnnotator. Should it move there? + protected final ReflectiveEvaluator evaluator; + + /** Helper class that holds references to special methods. */ + private final ValueMethodIdentifier methods; + + @SuppressWarnings("StaticAssignmentInConstructor") // static Range.ignoreOverflow is gross + public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); + Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW); + evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); + + addAliasedTypeAnnotation("android.support.annotation.IntRange", IntRange.class, true); + + // The actual ArrayLenRange is created by + // {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}; + // this line just registers the alias. The BottomVal is never used. + addAliasedTypeAnnotation(MinLen.class, BOTTOMVAL); + + // @Positive is aliased here because @Positive provides useful + // information about @MinLen annotations. + // @NonNegative and @GTENegativeOne are aliased similarly so + // that it's possible to overwrite a function annotated to return + // @NonNegative with, for instance, a function that returns an @IntVal(0). + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.Positive", createIntRangeFromPositive()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.NonNegative", createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.GTENegativeOne", + createIntRangeFromGTENegativeOne()); + // Must also alias any alias of three annotations above: + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.LengthOf", createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.IndexFor", createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.IndexOrHigh", createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.IndexOrLow", createIntRangeFromGTENegativeOne()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.SubstringIndexFor", + createIntRangeFromGTENegativeOne()); + + // PolyLength is syntactic sugar for both @PolySameLen and @PolyValue + addAliasedTypeAnnotation("org.checkerframework.checker.index.qual.PolyLength", POLY); + + // EnumVal is treated as StringVal internally by the checker. + addAliasedTypeAnnotation(EnumVal.class, StringVal.class, true); + + methods = new ValueMethodIdentifier(processingEnv); + + if (this.getClass() == ValueAnnotatedTypeFactory.class) { + this.postInit(); + } + } + + /** Gets a helper object that holds references to methods with special handling. */ + ValueMethodIdentifier getMethodIdentifier() { + return methods; + } + + @Override + protected void applyInferredAnnotations(AnnotatedTypeMirror type, CFValue inferred) { + // Inference can widen an IntRange beyond the values possible for the Java type. Change the + // annotation here so it is no wider than is possible. + TypeMirror t = inferred.getUnderlyingType(); + AnnotationMirrorSet inferredAnnos = inferred.getAnnotations(); + AnnotationMirror intRange = AnnotationUtils.getAnnotationByName(inferredAnnos, INTRANGE_NAME); + if (intRange != null && TypeKindUtils.primitiveOrBoxedToTypeKind(t) != null) { + Range range = getRange(intRange); + Range newRange = NumberUtils.castRange(t, range); + if (!newRange.equals(range)) { + inferredAnnos = AnnotationMirrorSet.singleton(createIntRangeAnnotation(newRange)); + } + } + + DefaultInferredTypesApplier applier = + new DefaultInferredTypesApplier(getQualifierHierarchy(), this); + applier.applyInferredType(type, inferredAnnos, inferred.getUnderlyingType()); + } + + @Override + public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { + if (AnnotationUtils.areSameByName(anno, MINLEN_NAME)) { + int from = getMinLenValue(anno); + return createArrayLenRangeAnnotation(from, Integer.MAX_VALUE); + } + + return super.canonicalAnnotation(anno); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Because the Value Checker includes its own alias annotations, + // the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList( + ArrayLen.class, + ArrayLenRange.class, + IntVal.class, + IntRange.class, + BoolVal.class, + StringVal.class, + MatchesRegex.class, + DoesNotMatchRegex.class, + DoubleVal.class, + BottomVal.class, + UnknownVal.class, + IntRangeFromPositive.class, + IntRangeFromNonNegative.class, + IntRangeFromGTENegativeOne.class, + PolyValue.class)); + } + + @Override + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new ValueTransfer(analysis); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new ValueQualifierHierarchy(this.getSupportedTypeQualifiers(), this); + } + + @Override + protected TypeHierarchy createTypeHierarchy() { + // This is a lot of code to replace annotations so that annotations that are equivalent + // qualifiers are the same annotation. + return new DefaultTypeHierarchy( + checker, + getQualifierHierarchy(), + checker.getBooleanOption("ignoreRawTypeArguments", true), + checker.hasOption("invariantArrays")) { + @Override + public StructuralEqualityComparer createEqualityComparer() { + return new StructuralEqualityComparer(areEqualVisitHistory) { + @Override + protected boolean arePrimaryAnnosEqual( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + type1.replaceAnnotation( + convertToUnknown( + convertSpecialIntRangeToStandardIntRange( + type1.getAnnotationInHierarchy(UNKNOWNVAL)))); + type2.replaceAnnotation( + convertToUnknown( + convertSpecialIntRangeToStandardIntRange( + type2.getAnnotationInHierarchy(UNKNOWNVAL)))); + + return super.arePrimaryAnnosEqual(type1, type2); + } }; - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(new ValueTypeAnnotator(this), super.createTypeAnnotator()); - } - - @Override - public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { - AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, MinLenFieldInvariant.class); - if (fieldInvarAnno == null) { - return null; - } - List fields = - AnnotationUtils.getElementValueArray( - fieldInvarAnno, minLenFieldInvariantFieldElement, String.class); - List minlens = - AnnotationUtils.getElementValueArray( - fieldInvarAnno, minLenFieldInvariantMinLenElement, Integer.class); - List qualifiers = - CollectionsPlume.mapList( - (Integer minlen) -> - createArrayLenRangeAnnotation(minlen, Integer.MAX_VALUE), - minlens); - - FieldInvariants superInvariants = super.getFieldInvariants(element); - return new FieldInvariants(superInvariants, fields, qualifiers, this); - } - - /** - * Computes the classes of field invariant annotations; a helper function for {@link - * #getFieldInvariantDeclarationAnnotations}. - * - * @return the classes of field invariant annotations - */ - private Set> computeFieldInvariantDeclarationAnnotations() { - // include FieldInvariant so that @MinLenBottom can be used. - Set> superResult = - super.getFieldInvariantDeclarationAnnotations(); - Set> set = - new HashSet<>(CollectionsPlume.mapCapacity(superResult.size() + 1)); - set.addAll(superResult); - set.add(MinLenFieldInvariant.class); - return set; - } - - /** The classes of field invariant annotations. */ - private final Set> fieldInvariantDeclarationAnnotations = - computeFieldInvariantDeclarationAnnotations(); - - @Override - protected Set> getFieldInvariantDeclarationAnnotations() { - return fieldInvariantDeclarationAnnotations; - } - - /** - * Creates array length annotations for the result of the Enum.values() method, which is the - * number of possible values of the enum. - */ - @Override - public ParameterizedExecutableType methodFromUse( - ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - - ParameterizedExecutableType superPair = super.methodFromUse(tree, methodElt, receiverType); - if (ElementUtils.matchesElement(methodElt, "values") - && methodElt.getEnclosingElement().getKind() == ElementKind.ENUM - && ElementUtils.isStatic(methodElt)) { - int count = - ElementUtils.getEnumConstants((TypeElement) methodElt.getEnclosingElement()) - .size(); - AnnotationMirror am = createArrayLenAnnotation(Collections.singletonList(count)); - superPair.executableType.getReturnType().replaceAnnotation(am); - } - return superPair; - } - - @Override - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - return AnnotationMirrorSet.singleton( - convertSpecialIntRangeToStandardIntRange(annos.first(), typeKind)); - } - - /** - * Finds the appropriate value for the {@code from} value of an annotated type mirror containing - * an {@code IntRange} annotation. - * - * @param atm an annotated type mirror that contains an {@code IntRange} annotation - * @return either the from value from the passed int range annotation, or the minimum value of - * the domain of the underlying type (i.e. Integer.MIN_VALUE if the underlying type is int) - */ - public long getFromValueFromIntRange(AnnotatedTypeMirror atm) { - TypeMirror type = atm.getUnderlyingType(); - long defaultValue = TypeKindUtils.minValue(toPrimitiveIntegralTypeKind(type)); - - AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); - return getIntRangeFromValue(intRangeAnno, defaultValue); - } - - /** - * Finds the appropriate value for the {@code to} value of an annotated type mirror containing - * an {@code IntRange} annotation. - * - * @param atm an annotated type mirror that contains an {@code IntRange} annotation - * @return either the to value from the passed int range annotation, or the maximum value of the - * domain of the underlying type (i.e. Integer.MAX_VALUE if the underlying type is int) - */ - public long getToValueFromIntRange(AnnotatedTypeMirror atm) { - TypeMirror type = atm.getUnderlyingType(); - long defaultValue = TypeKindUtils.maxValue(toPrimitiveIntegralTypeKind(type)); - - AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); - return getIntRangeToValue(intRangeAnno, defaultValue); - } - - /** - * Gets the from() element/field out of an IntRange annotation. The from() element/field must - * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @return its from() element/field - */ - protected long getIntRangeFromValue(AnnotationMirror intRangeAnno) { - return AnnotationUtils.getElementValueLong( - intRangeAnno, intRangeFromElement, Long.MIN_VALUE); - } - - /** - * Gets the from() element/field out of an IntRange annotation. The from() element/field must - * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @param defaultValue the value to return if there is no from() element/field - * @return its from() element/field - */ - protected long getIntRangeFromValue(AnnotationMirror intRangeAnno, long defaultValue) { - return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, defaultValue); - } - - /** - * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. - * Clients should call {@link #getToValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @param defaultValue the value to retur if there is no to() element/field - * @return its to() element/field - */ - protected long getIntRangeToValue(AnnotationMirror intRangeAnno, long defaultValue) { - return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, defaultValue); - } - - /** - * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. - * Clients should call {@link #getToValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @return its to() element/field - */ - protected long getIntRangeToValue(AnnotationMirror intRangeAnno) { - return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, Long.MAX_VALUE); - } - - /** - * Gets the from() element/field out of an ArrayLenRange annotation. - * - * @param anno an ArrayLenRange annotation - * @return its from() element/field - */ - protected int getArrayLenRangeFromValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValueInt(anno, arrayLenRangeFromElement, 0); - } - - /** - * Gets the to() element/field out of an ArrayLenRange annotation. - * - * @param anno an ArrayLenRange annotation - * @return its to() element/field - */ - protected int getArrayLenRangeToValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValueInt(anno, arrayLenRangeToElement, Integer.MAX_VALUE); - } - - /** - * Gets the value() element/field out of a MinLen annotation. - * - * @param anno a MinLen annotation - * @return its value() element/field - */ - protected int getMinLenValueValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValueInt(anno, minLenValueElement, 0); - } - - /** - * Determine the primitive integral TypeKind for the given integral type. - * - * @param type the type to convert, must be an integral type, boxed or primitive - * @return one of INT, SHORT, BYTE, CHAR, or LONG - */ - private static TypeKind toPrimitiveIntegralTypeKind(TypeMirror type) { - TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - if (typeKind != null && TypeKindUtils.isIntegral(typeKind)) { - return typeKind; - } - throw new TypeSystemError(type.toString() + " expected to be an integral type."); - } - - /** - * Gets the values stored in either an ArrayLen annotation (ints) or an IntVal/DoubleVal/etc. - * annotation (longs), and casts the result to a long. - * - * @param anno annotation mirror from which to get values - * @return the values in {@code anno} casted to longs - */ - /*package-private*/ List getArrayLenOrIntValue(AnnotationMirror anno) { - if (AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME)) { - return CollectionsPlume.mapList(Integer::longValue, getArrayLength(anno)); - } else { - return getIntValues(anno); - } - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because it includes the PropagationTreeAnnotator. - // Only use the PropagationTreeAnnotator for typing new arrays. The Value Checker - // computes types differently for all other trees normally typed by the - // PropagationTreeAnnotator. - TreeAnnotator arrayCreation = - new TreeAnnotator(this) { - PropagationTreeAnnotator propagationTreeAnnotator = - new PropagationTreeAnnotator(atypeFactory); - - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror mirror) { - return propagationTreeAnnotator.visitNewArray(tree, mirror); - } - }; - return new ListTreeAnnotator( - new ValueTreeAnnotator(this), - new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), - arrayCreation); - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm) { - return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @param primitiveKind a primitive TypeKind - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm, TypeKind primitiveKind) { - long max = Long.MAX_VALUE; - if (TypeKindUtils.isIntegral(primitiveKind)) { - Range maxRange = Range.create(primitiveKind); - max = maxRange.to; - } - return convertSpecialIntRangeToStandardIntRange(anm, max); - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @param typeMirror the Java type on which {@code anm} is written - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm, TypeMirror typeMirror) { - TypeKind primitiveKind; - if (TypesUtils.isPrimitive(typeMirror)) { - primitiveKind = typeMirror.getKind(); - } else if (TypesUtils.isBoxedPrimitive(typeMirror)) { - primitiveKind = types.unboxedType(typeMirror).getKind(); - } else { - primitiveKind = TypeKind.LONG; - } - - if (TypesUtils.isIntegralPrimitiveOrBoxed(typeMirror)) { - Range maxRange = Range.create(primitiveKind); - return convertSpecialIntRangeToStandardIntRange(anm, maxRange.to); - - } else { - return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); - } - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @param max the max value to use - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - private AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm, long max) { - if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMPOS_NAME)) { - return createIntRangeAnnotation(1, max); - } - - if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMNONNEG_NAME)) { - return createIntRangeAnnotation(0, max); - } - - if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMGTENEGONE_NAME)) { - return createIntRangeAnnotation(-1, max); - } - return anm; - } - - /** - * If {@code anno} is equivalent to UnknownVal, return UnknownVal; otherwise, return {@code - * anno}. - * - * @param anno any annotation mirror - * @return UnknownVal if {@code anno} is equivalent to it; otherwise, return {@code anno} - */ - /*package-private*/ AnnotationMirror convertToUnknown(AnnotationMirror anno) { - if (AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME)) { - Range range = getRange(anno); - if (range.from == 0 && range.to >= Integer.MAX_VALUE) { - return UNKNOWNVAL; - } - } else if (AnnotationUtils.areSameByName(anno, INTRANGE_NAME)) { - Range range = getRange(anno); - if (range.isLongEverything()) { - return UNKNOWNVAL; - } - } - return anno; - } - - /** - * Returns the estimate for the length of a string or array with whose annotated type is {@code - * type}. - * - * @param type annotated typed - * @return the estimate for the length of a string or array with whose annotated type is {@code - * type}. - */ - /*package-private*/ AnnotationMirror createArrayLengthResultAnnotation( - AnnotatedTypeMirror type) { - AnnotationMirror arrayAnno = type.getAnnotationInHierarchy(UNKNOWNVAL); - switch (AnnotationUtils.annotationName(arrayAnno)) { - case ARRAYLEN_NAME: - // array.length, where array : @ArrayLen(x) - List lengths = getArrayLength(arrayAnno); - return createNumberAnnotationMirror(new ArrayList<>(lengths)); - case ARRAYLENRANGE_NAME: - // array.length, where array : @ArrayLenRange(x) - Range range = getRange(arrayAnno); - return createIntRangeAnnotation(range); - case STRINGVAL_NAME: - List strings = getStringValues(arrayAnno); - List lengthsS = ValueCheckerUtils.getLengthsForStringValues(strings); - return createNumberAnnotationMirror(new ArrayList<>(lengthsS)); - default: - return createIntRangeAnnotation(0, Integer.MAX_VALUE); - } - } - - /** - * Returns a constant value annotation with the {@code value}. The class of the annotation - * reflects the {@code resultType} given. - * - * @param resultType used to select which kind of value annotation is returned - * @param value value to use - * @return a constant value annotation with the {@code value} - */ - /*package-private*/ AnnotationMirror createResultingAnnotation( - TypeMirror resultType, Object value) { - return createResultingAnnotation(resultType, Collections.singletonList(value)); - } - - /** - * Returns a constant value annotation with the {@code values}. The class of the annotation - * reflects the {@code resultType} given. - * - * @param resultType used to select which kind of value annotation is returned - * @param values must be a homogeneous list: every element of it has the same class - * @return a constant value annotation with the {@code values} - */ - /*package-private*/ AnnotationMirror createResultingAnnotation( - TypeMirror resultType, List values) { - if (values == null) { - return UNKNOWNVAL; - } - // For some reason null is included in the list of values, - // so remove it so that it does not cause a NPE elsewhere. - values.remove(null); - if (values.isEmpty()) { - return BOTTOMVAL; - } - - if (TypesUtils.isString(resultType)) { - List stringVals = CollectionsPlume.mapList((Object o) -> (String) o, values); - return createStringAnnotation(stringVals); - } else if (TypesUtils.getClassFromType(resultType) == char[].class) { - List stringVals = - CollectionsPlume.mapList( - (Object o) -> { - if (o instanceof char[]) { - return new String((char[]) o); - } else { - return o.toString(); - } - }, - values); - return createStringAnnotation(stringVals); - } - - TypeKind primitiveKind; - if (TypesUtils.isPrimitive(resultType)) { - primitiveKind = resultType.getKind(); - } else if (TypesUtils.isBoxedPrimitive(resultType)) { - primitiveKind = types.unboxedType(resultType).getKind(); - } else { - return UNKNOWNVAL; - } - - switch (primitiveKind) { - case BOOLEAN: - List boolVals = - CollectionsPlume.mapList((Object o) -> (Boolean) o, values); - return createBooleanAnnotation(boolVals); - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - case BYTE: - List numberVals = new ArrayList<>(values.size()); - List characterVals = new ArrayList<>(values.size()); - for (Object o : values) { - if (o instanceof Character) { - characterVals.add((Character) o); - } else { - numberVals.add((Number) o); - } - } - if (numberVals.isEmpty()) { - // Every value in the list is a Character. - return createCharAnnotation(characterVals); - } - return createNumberAnnotationMirror(new ArrayList<>(numberVals)); - case CHAR: - List charVals = new ArrayList<>(values.size()); - for (Object o : values) { - if (o instanceof Number) { - charVals.add((char) ((Number) o).intValue()); - } else { - charVals.add((char) o); - } + } + }; + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(new ValueTypeAnnotator(this), super.createTypeAnnotator()); + } + + @Override + public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { + AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, MinLenFieldInvariant.class); + if (fieldInvarAnno == null) { + return null; + } + List fields = + AnnotationUtils.getElementValueArray( + fieldInvarAnno, minLenFieldInvariantFieldElement, String.class); + List minlens = + AnnotationUtils.getElementValueArray( + fieldInvarAnno, minLenFieldInvariantMinLenElement, Integer.class); + List qualifiers = + CollectionsPlume.mapList( + (Integer minlen) -> createArrayLenRangeAnnotation(minlen, Integer.MAX_VALUE), minlens); + + FieldInvariants superInvariants = super.getFieldInvariants(element); + return new FieldInvariants(superInvariants, fields, qualifiers, this); + } + + /** + * Computes the classes of field invariant annotations; a helper function for {@link + * #getFieldInvariantDeclarationAnnotations}. + * + * @return the classes of field invariant annotations + */ + private Set> computeFieldInvariantDeclarationAnnotations() { + // include FieldInvariant so that @MinLenBottom can be used. + Set> superResult = super.getFieldInvariantDeclarationAnnotations(); + Set> set = + new HashSet<>(CollectionsPlume.mapCapacity(superResult.size() + 1)); + set.addAll(superResult); + set.add(MinLenFieldInvariant.class); + return set; + } + + /** The classes of field invariant annotations. */ + private final Set> fieldInvariantDeclarationAnnotations = + computeFieldInvariantDeclarationAnnotations(); + + @Override + protected Set> getFieldInvariantDeclarationAnnotations() { + return fieldInvariantDeclarationAnnotations; + } + + /** + * Creates array length annotations for the result of the Enum.values() method, which is the + * number of possible values of the enum. + */ + @Override + public ParameterizedExecutableType methodFromUse( + ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { + + ParameterizedExecutableType superPair = super.methodFromUse(tree, methodElt, receiverType); + if (ElementUtils.matchesElement(methodElt, "values") + && methodElt.getEnclosingElement().getKind() == ElementKind.ENUM + && ElementUtils.isStatic(methodElt)) { + int count = + ElementUtils.getEnumConstants((TypeElement) methodElt.getEnclosingElement()).size(); + AnnotationMirror am = createArrayLenAnnotation(Collections.singletonList(count)); + superPair.executableType.getReturnType().replaceAnnotation(am); + } + return superPair; + } + + @Override + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + return AnnotationMirrorSet.singleton( + convertSpecialIntRangeToStandardIntRange(annos.first(), typeKind)); + } + + /** + * Finds the appropriate value for the {@code from} value of an annotated type mirror containing + * an {@code IntRange} annotation. + * + * @param atm an annotated type mirror that contains an {@code IntRange} annotation + * @return either the from value from the passed int range annotation, or the minimum value of the + * domain of the underlying type (i.e. Integer.MIN_VALUE if the underlying type is int) + */ + public long getFromValueFromIntRange(AnnotatedTypeMirror atm) { + TypeMirror type = atm.getUnderlyingType(); + long defaultValue = TypeKindUtils.minValue(toPrimitiveIntegralTypeKind(type)); + + AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); + return getIntRangeFromValue(intRangeAnno, defaultValue); + } + + /** + * Finds the appropriate value for the {@code to} value of an annotated type mirror containing an + * {@code IntRange} annotation. + * + * @param atm an annotated type mirror that contains an {@code IntRange} annotation + * @return either the to value from the passed int range annotation, or the maximum value of the + * domain of the underlying type (i.e. Integer.MAX_VALUE if the underlying type is int) + */ + public long getToValueFromIntRange(AnnotatedTypeMirror atm) { + TypeMirror type = atm.getUnderlyingType(); + long defaultValue = TypeKindUtils.maxValue(toPrimitiveIntegralTypeKind(type)); + + AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); + return getIntRangeToValue(intRangeAnno, defaultValue); + } + + /** + * Gets the from() element/field out of an IntRange annotation. The from() element/field must + * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @return its from() element/field + */ + protected long getIntRangeFromValue(AnnotationMirror intRangeAnno) { + return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, Long.MIN_VALUE); + } + + /** + * Gets the from() element/field out of an IntRange annotation. The from() element/field must + * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @param defaultValue the value to return if there is no from() element/field + * @return its from() element/field + */ + protected long getIntRangeFromValue(AnnotationMirror intRangeAnno, long defaultValue) { + return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, defaultValue); + } + + /** + * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. + * Clients should call {@link #getToValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @param defaultValue the value to retur if there is no to() element/field + * @return its to() element/field + */ + protected long getIntRangeToValue(AnnotationMirror intRangeAnno, long defaultValue) { + return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, defaultValue); + } + + /** + * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. + * Clients should call {@link #getToValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @return its to() element/field + */ + protected long getIntRangeToValue(AnnotationMirror intRangeAnno) { + return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, Long.MAX_VALUE); + } + + /** + * Gets the from() element/field out of an ArrayLenRange annotation. + * + * @param anno an ArrayLenRange annotation + * @return its from() element/field + */ + protected int getArrayLenRangeFromValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValueInt(anno, arrayLenRangeFromElement, 0); + } + + /** + * Gets the to() element/field out of an ArrayLenRange annotation. + * + * @param anno an ArrayLenRange annotation + * @return its to() element/field + */ + protected int getArrayLenRangeToValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValueInt(anno, arrayLenRangeToElement, Integer.MAX_VALUE); + } + + /** + * Gets the value() element/field out of a MinLen annotation. + * + * @param anno a MinLen annotation + * @return its value() element/field + */ + protected int getMinLenValueValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValueInt(anno, minLenValueElement, 0); + } + + /** + * Determine the primitive integral TypeKind for the given integral type. + * + * @param type the type to convert, must be an integral type, boxed or primitive + * @return one of INT, SHORT, BYTE, CHAR, or LONG + */ + private static TypeKind toPrimitiveIntegralTypeKind(TypeMirror type) { + TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + if (typeKind != null && TypeKindUtils.isIntegral(typeKind)) { + return typeKind; + } + throw new TypeSystemError(type.toString() + " expected to be an integral type."); + } + + /** + * Gets the values stored in either an ArrayLen annotation (ints) or an IntVal/DoubleVal/etc. + * annotation (longs), and casts the result to a long. + * + * @param anno annotation mirror from which to get values + * @return the values in {@code anno} casted to longs + */ + /*package-private*/ List getArrayLenOrIntValue(AnnotationMirror anno) { + if (AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME)) { + return CollectionsPlume.mapList(Integer::longValue, getArrayLength(anno)); + } else { + return getIntValues(anno); + } + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because it includes the PropagationTreeAnnotator. + // Only use the PropagationTreeAnnotator for typing new arrays. The Value Checker + // computes types differently for all other trees normally typed by the + // PropagationTreeAnnotator. + TreeAnnotator arrayCreation = + new TreeAnnotator(this) { + PropagationTreeAnnotator propagationTreeAnnotator = + new PropagationTreeAnnotator(atypeFactory); + + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror mirror) { + return propagationTreeAnnotator.visitNewArray(tree, mirror); + } + }; + return new ListTreeAnnotator( + new ValueTreeAnnotator(this), + new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), + arrayCreation); + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm) { + return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @param primitiveKind a primitive TypeKind + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm, TypeKind primitiveKind) { + long max = Long.MAX_VALUE; + if (TypeKindUtils.isIntegral(primitiveKind)) { + Range maxRange = Range.create(primitiveKind); + max = maxRange.to; + } + return convertSpecialIntRangeToStandardIntRange(anm, max); + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @param typeMirror the Java type on which {@code anm} is written + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm, TypeMirror typeMirror) { + TypeKind primitiveKind; + if (TypesUtils.isPrimitive(typeMirror)) { + primitiveKind = typeMirror.getKind(); + } else if (TypesUtils.isBoxedPrimitive(typeMirror)) { + primitiveKind = types.unboxedType(typeMirror).getKind(); + } else { + primitiveKind = TypeKind.LONG; + } + + if (TypesUtils.isIntegralPrimitiveOrBoxed(typeMirror)) { + Range maxRange = Range.create(primitiveKind); + return convertSpecialIntRangeToStandardIntRange(anm, maxRange.to); + + } else { + return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); + } + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @param max the max value to use + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + private AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm, long max) { + if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMPOS_NAME)) { + return createIntRangeAnnotation(1, max); + } + + if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMNONNEG_NAME)) { + return createIntRangeAnnotation(0, max); + } + + if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMGTENEGONE_NAME)) { + return createIntRangeAnnotation(-1, max); + } + return anm; + } + + /** + * If {@code anno} is equivalent to UnknownVal, return UnknownVal; otherwise, return {@code anno}. + * + * @param anno any annotation mirror + * @return UnknownVal if {@code anno} is equivalent to it; otherwise, return {@code anno} + */ + /*package-private*/ AnnotationMirror convertToUnknown(AnnotationMirror anno) { + if (AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME)) { + Range range = getRange(anno); + if (range.from == 0 && range.to >= Integer.MAX_VALUE) { + return UNKNOWNVAL; + } + } else if (AnnotationUtils.areSameByName(anno, INTRANGE_NAME)) { + Range range = getRange(anno); + if (range.isLongEverything()) { + return UNKNOWNVAL; + } + } + return anno; + } + + /** + * Returns the estimate for the length of a string or array with whose annotated type is {@code + * type}. + * + * @param type annotated typed + * @return the estimate for the length of a string or array with whose annotated type is {@code + * type}. + */ + /*package-private*/ AnnotationMirror createArrayLengthResultAnnotation(AnnotatedTypeMirror type) { + AnnotationMirror arrayAnno = type.getAnnotationInHierarchy(UNKNOWNVAL); + switch (AnnotationUtils.annotationName(arrayAnno)) { + case ARRAYLEN_NAME: + // array.length, where array : @ArrayLen(x) + List lengths = getArrayLength(arrayAnno); + return createNumberAnnotationMirror(new ArrayList<>(lengths)); + case ARRAYLENRANGE_NAME: + // array.length, where array : @ArrayLenRange(x) + Range range = getRange(arrayAnno); + return createIntRangeAnnotation(range); + case STRINGVAL_NAME: + List strings = getStringValues(arrayAnno); + List lengthsS = ValueCheckerUtils.getLengthsForStringValues(strings); + return createNumberAnnotationMirror(new ArrayList<>(lengthsS)); + default: + return createIntRangeAnnotation(0, Integer.MAX_VALUE); + } + } + + /** + * Returns a constant value annotation with the {@code value}. The class of the annotation + * reflects the {@code resultType} given. + * + * @param resultType used to select which kind of value annotation is returned + * @param value value to use + * @return a constant value annotation with the {@code value} + */ + /*package-private*/ AnnotationMirror createResultingAnnotation( + TypeMirror resultType, Object value) { + return createResultingAnnotation(resultType, Collections.singletonList(value)); + } + + /** + * Returns a constant value annotation with the {@code values}. The class of the annotation + * reflects the {@code resultType} given. + * + * @param resultType used to select which kind of value annotation is returned + * @param values must be a homogeneous list: every element of it has the same class + * @return a constant value annotation with the {@code values} + */ + /*package-private*/ AnnotationMirror createResultingAnnotation( + TypeMirror resultType, List values) { + if (values == null) { + return UNKNOWNVAL; + } + // For some reason null is included in the list of values, + // so remove it so that it does not cause a NPE elsewhere. + values.remove(null); + if (values.isEmpty()) { + return BOTTOMVAL; + } + + if (TypesUtils.isString(resultType)) { + List stringVals = CollectionsPlume.mapList((Object o) -> (String) o, values); + return createStringAnnotation(stringVals); + } else if (TypesUtils.getClassFromType(resultType) == char[].class) { + List stringVals = + CollectionsPlume.mapList( + (Object o) -> { + if (o instanceof char[]) { + return new String((char[]) o); + } else { + return o.toString(); } - return createCharAnnotation(charVals); - default: - throw new UnsupportedOperationException("Unexpected kind:" + resultType); - } - } - - /** - * Returns a {@link IntVal} or {@link IntRange} annotation using the values. If {@code values} - * is null, then UnknownVal is returned; if {@code values} is empty, then bottom is returned. If - * the number of {@code values} is greater than MAX_VALUES, return an {@link IntRange}. In other - * cases, the values are sorted and duplicates are removed before an {@link IntVal} is created. - * - * @param values list of longs; duplicates are allowed and the values may be in any order - * @return an annotation depends on the values - */ - public AnnotationMirror createIntValAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - long valMin = values.get(0); - long valMax = values.get(values.size() - 1); - return createIntRangeAnnotation(valMin, valMax); - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Convert an {@code @IntRange} annotation to an {@code @IntVal} annotation, or to UNKNOWNVAL if - * the input is too wide to be represented as an {@code @IntVal}. - * - * @param intRangeAnno an {@code @IntRange} annotation - * @return an {@code @IntVal} annotation corresponding to the argument - */ - public AnnotationMirror convertIntRangeToIntVal(AnnotationMirror intRangeAnno) { - Range range = getRange(intRangeAnno); - List values = ValueCheckerUtils.getValuesFromRange(range, Long.class); - return createIntValAnnotation(values); - } - - /** - * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of doubles; duplicates are allowed and the values may be in any order - * @return a {@link DoubleVal} annotation using the values - */ - public AnnotationMirror createDoubleValAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoubleVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Convert an {@code @IntVal} annotation to a {@code @DoubleVal} annotation. - * - * @param intValAnno an {@code @IntVal} annotation - * @return a corresponding {@code @DoubleVal} annotation - */ - /*package-private*/ AnnotationMirror convertIntValToDoubleVal(AnnotationMirror intValAnno) { - List intValues = getIntValues(intValAnno); - return createDoubleValAnnotation(convertLongListToDoubleList(intValues)); - } - - /** - * Convert a {@code List} to a {@code List}. - * - * @param intValues a list of long integers - * @return a list of double floating-point values - */ - /*package-private*/ List convertLongListToDoubleList(List intValues) { - return CollectionsPlume.mapList(Long::doubleValue, intValues); - } - - /** - * Returns a {@link StringVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. If values is larger than - * the max number of values allowed (10 by default), then an {@link ArrayLen} or an {@link - * ArrayLenRange} annotation is returned. - * - * @param values list of strings; duplicates are allowed and the values may be in any order - * @return a {@link StringVal} annotation using the values - */ - public AnnotationMirror createStringAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - // Too many strings are replaced by their lengths - List lengths = ValueCheckerUtils.getLengthsForStringValues(values); - return createArrayLenAnnotation(lengths); - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, StringVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Returns a {@link ArrayLen} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. If values is larger than - * the max number of values allowed (10 by default), then an {@link ArrayLenRange} annotation is - * returned. - * - * @param values list of integers; duplicates are allowed and the values may be in any order - * @return a {@link ArrayLen} annotation using the values - */ - public AnnotationMirror createArrayLenAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.isEmpty() || Collections.min(values) < 0) { - return BOTTOMVAL; - } else if (values.size() > MAX_VALUES) { - return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLen.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Returns a {@link BoolVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of booleans; duplicates are allowed and the values may be in any order - * @return a {@link BoolVal} annotation using the values - */ - public AnnotationMirror createBooleanAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - // TODO: This seems wasteful. Why not create the 3 interesting AnnotationMirrors (with - // arguments {true}, {false}, and {true, false}, respectively) in advance and return one - // of them? (Maybe an advantage of this implementation is that it is identical to - // some other implementations and therefore might be less error-prone.) - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, BoolVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Returns a {@link IntVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of characters; duplicates are allowed and the values may be in any order - * @return a {@link IntVal} annotation using the values - */ - public AnnotationMirror createCharAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - List longValues = - CollectionsPlume.mapList((Character value) -> (long) value, values); - return createIntValAnnotation(longValues); - } - } - - /** - * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of doubleacters; duplicates are allowed and the values may be in any order - * @return a {@link IntVal} annotation using the values - */ - public AnnotationMirror createDoubleAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - return createDoubleValAnnotation(values); - } - } - - /** - * Returns an annotation that represents the given set of values. - * - * @param values a homogeneous list: every element of it has the same class. This method does - * not modify or store it. - * @return an annotation that represents the given set of values - */ - public AnnotationMirror createNumberAnnotationMirror(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } else if (values.isEmpty()) { - return BOTTOMVAL; - } - Number first = values.get(0); - if (first instanceof Integer - || first instanceof Short - || first instanceof Long - || first instanceof Byte) { - List intValues = CollectionsPlume.mapList(Number::longValue, values); - return createIntValAnnotation(intValues); - } else if (first instanceof Double || first instanceof Float) { - List doubleValues = CollectionsPlume.mapList(Number::doubleValue, values); - return createDoubleValAnnotation(doubleValues); - } - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: unexpected class: " + first.getClass()); - } - - /** - * Create an {@code @IntRange} annotation from the two (inclusive) bounds. Does not return - * BOTTOMVAL or UNKNOWNVAL. - * - * @param from the lower bound - * @param to the upper bound - * @return an {@code @IntRange} annotation - */ - /*package-private*/ AnnotationMirror createIntRangeAnnotation(long from, long to) { - assert from <= to; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRange.class); - builder.setValue("from", from); - builder.setValue("to", to); - return builder.build(); - } - - /** - * Create an {@code @IntRange} or {@code @IntVal} annotation from the range. May return - * BOTTOMVAL or UNKNOWNVAL. - */ - public AnnotationMirror createIntRangeAnnotation(Range range) { - if (range.isNothing()) { - return BOTTOMVAL; - } else if (range.isLongEverything()) { - return UNKNOWNVAL; - } else if (range.isWiderThan(MAX_VALUES)) { - return createIntRangeAnnotation(range.from, range.to); - } else { - List newValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); - return createIntValAnnotation(newValues); - } - } - - /** - * Creates the special {@link IntRangeFromPositive} annotation, which is only used as an alias - * for the Index Checker's {@link org.checkerframework.checker.index.qual.Positive} annotation. - * It is treated everywhere as an IntRange annotation, but is not checked when it appears as the - * left hand side of an assignment (because the Lower Bound Checker will check it). - */ - private AnnotationMirror createIntRangeFromPositive() { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, IntRangeFromPositive.class); - return builder.build(); - } - - /** - * Creates the special {@link IntRangeFromNonNegative} annotation, which is only used as an - * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.NonNegative} - * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it - * appears as the left hand side of an assignment (because the Lower Bound Checker will check - * it). - */ - private AnnotationMirror createIntRangeFromNonNegative() { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, IntRangeFromNonNegative.class); - return builder.build(); - } - - /** - * Creates the special {@link IntRangeFromGTENegativeOne} annotation, which is only used as an - * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.GTENegativeOne} - * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it - * appears as the left hand side of an assignment (because the Lower Bound Checker will check - * it). - */ - private AnnotationMirror createIntRangeFromGTENegativeOne() { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, IntRangeFromGTENegativeOne.class); - return builder.build(); - } - - /** - * Create an {@code @ArrayLenRange} annotation from the two (inclusive) bounds. Does not return - * BOTTOMVAL or UNKNOWNVAL. - */ - public AnnotationMirror createArrayLenRangeAnnotation(int from, int to) { - assert from <= to; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLenRange.class); - builder.setValue("from", from); - builder.setValue("to", to); - return builder.build(); - } - - /** - * Create an {@code @ArrayLenRange} annotation from the range. May return BOTTOMVAL or - * UNKNOWNVAL. - */ - public AnnotationMirror createArrayLenRangeAnnotation(Range range) { - if (range.isNothing()) { - return BOTTOMVAL; - } else if (range.isLongEverything() || !range.isWithinInteger()) { - return UNKNOWNVAL; - } else { - return createArrayLenRangeAnnotation( - Long.valueOf(range.from).intValue(), Long.valueOf(range.to).intValue()); - } - } - - /** - * Creates an {@code MatchesRegex} annotation for the given regular expressions. - * - * @param regexes a list of Java regular expressions - * @return a MatchesRegex annotation with those values - */ - public AnnotationMirror createMatchesRegexAnnotation(@Nullable List<@Regex String> regexes) { - if (regexes == null) { - return UNKNOWNVAL; - } - if (regexes.isEmpty()) { - return BOTTOMVAL; - } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MatchesRegex.class); - builder.setValue("value", regexes.toArray(new String[regexes.size()])); - return builder.build(); - } - - /** - * Creates an {@code DoesNotMatchRegex} annotation for the given regular expressions. - * - * @param regexes a list of Java regular expressions - * @return a DoesNotMatchRegex annotation with those values - */ - public AnnotationMirror createDoesNotMatchRegexAnnotation( - @Nullable List<@Regex String> regexes) { - if (regexes == null) { - return BOTTOMVAL; - } - if (regexes.isEmpty()) { - return UNKNOWNVAL; - } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoesNotMatchRegex.class); - builder.setValue("value", regexes.toArray(new String[regexes.size()])); - return builder.build(); - } - - /** - * Converts an {@code @StringVal} annotation to an {@code @ArrayLenRange} annotation. - * - * @param stringValAnno a StringVal annotation - * @return an ArrayLenRange annotation representing the possible lengths of the values of the - * given StringVal annotation - */ - /*package-private*/ AnnotationMirror convertStringValToArrayLenRange( - AnnotationMirror stringValAnno) { - List values = getStringValues(stringValAnno); - List lengths = ValueCheckerUtils.getLengthsForStringValues(values); - return createArrayLenRangeAnnotation(Collections.min(lengths), Collections.max(lengths)); - } - - /** - * Converts a {@code @StringVal} annotation to an {@code @ArrayLen} annotation. If the - * {@code @StringVal} annotation contains string values of more than MAX_VALUES distinct - * lengths, {@code @ArrayLenRange} annotation is returned instead. - * - * @param stringValAnno a {@code @StringVal} annotation - * @return a corresponding {@code @ArrayLen} annotation - */ - /*package-private*/ AnnotationMirror convertStringValToArrayLen( - AnnotationMirror stringValAnno) { - List values = getStringValues(stringValAnno); - return createArrayLenAnnotation(ValueCheckerUtils.getLengthsForStringValues(values)); - } - - /** - * Converts an {@code StringVal} annotation to an {@code MatchesRegex} annotation that matches - * exactly the string values listed in the {@code StringVal}. - * - * @param stringValAnno a StringVal annotation - * @return an equivalent MatchesReges annotation - */ - /*package-private*/ AnnotationMirror convertStringValToMatchesRegex( - AnnotationMirror stringValAnno) { - List values = getStringValues(stringValAnno); - List<@Regex String> valuesAsRegexes = CollectionsPlume.mapList(Pattern::quote, values); - return createMatchesRegexAnnotation(valuesAsRegexes); - } - - /** - * Converts an {@code @ArrayLen} annotation to an {@code @ArrayLenRange} annotation. - * - * @param arrayLenAnno an ArrayLen annotation - * @return an ArrayLenRange annotation representing the bounds of the given ArrayLen annotation - */ - public AnnotationMirror convertArrayLenToArrayLenRange(AnnotationMirror arrayLenAnno) { - List values = getArrayLength(arrayLenAnno); - return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); - } - - /** Converts an {@code @IntVal} annotation to an {@code @IntRange} annotation. */ - public AnnotationMirror convertIntValToIntRange(AnnotationMirror intValAnno) { - List intValues = getIntValues(intValAnno); - return createIntRangeAnnotation(Collections.min(intValues), Collections.max(intValues)); - } - - /** - * Returns a {@link Range} bounded by the values specified in the given {@code @Range} - * annotation. Also returns an appropriate range if an {@code @IntVal} annotation is passed. - * Returns {@code null} if the annotation is null or if the annotation is not an {@code - * IntRange}, {@code IntRangeFromPositive}, {@code IntVal}, or {@code ArrayLenRange}. - * - * @param rangeAnno a {@code @Range} annotation - * @return the {@link Range} that the annotation represents - */ - public @Nullable Range getRange(@Nullable AnnotationMirror rangeAnno) { - if (rangeAnno == null) { - return null; - } - switch (AnnotationUtils.annotationName(rangeAnno)) { - case INTRANGE_FROMPOS_NAME: - return Range.create(1, Integer.MAX_VALUE); - case INTRANGE_FROMNONNEG_NAME: - return Range.create(0, Integer.MAX_VALUE); - case INTRANGE_FROMGTENEGONE_NAME: - return Range.create(-1, Integer.MAX_VALUE); - case INTVAL_NAME: - return ValueCheckerUtils.getRangeFromValues(getIntValues(rangeAnno)); - case INTRANGE_NAME: - // Assume rangeAnno is well-formed, i.e., 'from' is less than or equal to 'to'. - return Range.create(getIntRangeFromValue(rangeAnno), getIntRangeToValue(rangeAnno)); - case ARRAYLENRANGE_NAME: - return Range.create( - getArrayLenRangeFromValue(rangeAnno), getArrayLenRangeToValue(rangeAnno)); - default: - return null; - } - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is no constant-value - * annotation so the argument is null. - * - *

The method returns a list of {@code Long} but is named {@code getIntValues} because it - * supports the {@code @IntVal} annotation. - * - * @param intAnno an {@code @IntVal} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getIntValues(@PolyNull AnnotationMirror intAnno) { - if (intAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is no constant-value - * annotation so the argument is null. - * - * @param doubleAnno a {@code @DoubleVal} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getDoubleValues(@PolyNull AnnotationMirror doubleAnno) { - if (doubleAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray( - doubleAnno, doubleValValueElement, Double.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible array lengths as a sorted list with no duplicate values. Returns - * the empty list if no values are possible (for dead code). Returns null if any value is - * possible -- that is, if no estimate can be made -- and this includes when there is no - * constant-value annotation so the argument is null. - * - * @param arrayAnno an {@code @ArrayLen} annotation, or null - * @return the possible array lengths, deduplicated and sorted - */ - public @PolyNull List getArrayLength(@PolyNull AnnotationMirror arrayAnno) { - if (arrayAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray( - arrayAnno, arrayLenValueElement, Integer.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is no constant-value - * annotation so the argument is null. - * - * @param intAnno an {@code @IntVal} annotation, or null - * @return the values represented by the given {@code @IntVal} annotation - */ - public @PolyNull List getCharValues(@PolyNull AnnotationMirror intAnno) { - if (intAnno == null) { - return Collections.emptyList(); - } - List intValues = - AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); - List charValues = - CollectionsPlume.mapList((Long i) -> (char) i.intValue(), intValues); - Collections.sort(charValues); - // TODO: Should this be an unmodifiable list? - return new ArrayList<>(charValues); - } - - /** - * Returns the single possible boolean value, or null if there is not exactly one possible - * value. - * - * @see #getBooleanValues - * @param boolAnno a {@code @BoolVal} annotation, or null - * @return the single possible boolean value, on null if that is not the case - */ - public @Nullable Boolean getBooleanValue(@Nullable AnnotationMirror boolAnno) { - if (boolAnno == null) { - return null; - } - List boolValues = - AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); - Set boolSet = new TreeSet<>(boolValues); - if (boolSet.size() == 1) { - return boolSet.iterator().next(); - } + }, + values); + return createStringAnnotation(stringVals); + } + + TypeKind primitiveKind; + if (TypesUtils.isPrimitive(resultType)) { + primitiveKind = resultType.getKind(); + } else if (TypesUtils.isBoxedPrimitive(resultType)) { + primitiveKind = types.unboxedType(resultType).getKind(); + } else { + return UNKNOWNVAL; + } + + switch (primitiveKind) { + case BOOLEAN: + List boolVals = CollectionsPlume.mapList((Object o) -> (Boolean) o, values); + return createBooleanAnnotation(boolVals); + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + case BYTE: + List numberVals = new ArrayList<>(values.size()); + List characterVals = new ArrayList<>(values.size()); + for (Object o : values) { + if (o instanceof Character) { + characterVals.add((Character) o); + } else { + numberVals.add((Number) o); + } + } + if (numberVals.isEmpty()) { + // Every value in the list is a Character. + return createCharAnnotation(characterVals); + } + return createNumberAnnotationMirror(new ArrayList<>(numberVals)); + case CHAR: + List charVals = new ArrayList<>(values.size()); + for (Object o : values) { + if (o instanceof Number) { + charVals.add((char) ((Number) o).intValue()); + } else { + charVals.add((char) o); + } + } + return createCharAnnotation(charVals); + default: + throw new UnsupportedOperationException("Unexpected kind:" + resultType); + } + } + + /** + * Returns a {@link IntVal} or {@link IntRange} annotation using the values. If {@code values} is + * null, then UnknownVal is returned; if {@code values} is empty, then bottom is returned. If the + * number of {@code values} is greater than MAX_VALUES, return an {@link IntRange}. In other + * cases, the values are sorted and duplicates are removed before an {@link IntVal} is created. + * + * @param values list of longs; duplicates are allowed and the values may be in any order + * @return an annotation depends on the values + */ + public AnnotationMirror createIntValAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + long valMin = values.get(0); + long valMax = values.get(values.size() - 1); + return createIntRangeAnnotation(valMin, valMax); + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Convert an {@code @IntRange} annotation to an {@code @IntVal} annotation, or to UNKNOWNVAL if + * the input is too wide to be represented as an {@code @IntVal}. + * + * @param intRangeAnno an {@code @IntRange} annotation + * @return an {@code @IntVal} annotation corresponding to the argument + */ + public AnnotationMirror convertIntRangeToIntVal(AnnotationMirror intRangeAnno) { + Range range = getRange(intRangeAnno); + List values = ValueCheckerUtils.getValuesFromRange(range, Long.class); + return createIntValAnnotation(values); + } + + /** + * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of doubles; duplicates are allowed and the values may be in any order + * @return a {@link DoubleVal} annotation using the values + */ + public AnnotationMirror createDoubleValAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoubleVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Convert an {@code @IntVal} annotation to a {@code @DoubleVal} annotation. + * + * @param intValAnno an {@code @IntVal} annotation + * @return a corresponding {@code @DoubleVal} annotation + */ + /*package-private*/ AnnotationMirror convertIntValToDoubleVal(AnnotationMirror intValAnno) { + List intValues = getIntValues(intValAnno); + return createDoubleValAnnotation(convertLongListToDoubleList(intValues)); + } + + /** + * Convert a {@code List} to a {@code List}. + * + * @param intValues a list of long integers + * @return a list of double floating-point values + */ + /*package-private*/ List convertLongListToDoubleList(List intValues) { + return CollectionsPlume.mapList(Long::doubleValue, intValues); + } + + /** + * Returns a {@link StringVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. If values is larger than + * the max number of values allowed (10 by default), then an {@link ArrayLen} or an {@link + * ArrayLenRange} annotation is returned. + * + * @param values list of strings; duplicates are allowed and the values may be in any order + * @return a {@link StringVal} annotation using the values + */ + public AnnotationMirror createStringAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + // Too many strings are replaced by their lengths + List lengths = ValueCheckerUtils.getLengthsForStringValues(values); + return createArrayLenAnnotation(lengths); + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, StringVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Returns a {@link ArrayLen} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. If values is larger than + * the max number of values allowed (10 by default), then an {@link ArrayLenRange} annotation is + * returned. + * + * @param values list of integers; duplicates are allowed and the values may be in any order + * @return a {@link ArrayLen} annotation using the values + */ + public AnnotationMirror createArrayLenAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.isEmpty() || Collections.min(values) < 0) { + return BOTTOMVAL; + } else if (values.size() > MAX_VALUES) { + return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLen.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Returns a {@link BoolVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of booleans; duplicates are allowed and the values may be in any order + * @return a {@link BoolVal} annotation using the values + */ + public AnnotationMirror createBooleanAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + // TODO: This seems wasteful. Why not create the 3 interesting AnnotationMirrors (with + // arguments {true}, {false}, and {true, false}, respectively) in advance and return one + // of them? (Maybe an advantage of this implementation is that it is identical to + // some other implementations and therefore might be less error-prone.) + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, BoolVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Returns a {@link IntVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of characters; duplicates are allowed and the values may be in any order + * @return a {@link IntVal} annotation using the values + */ + public AnnotationMirror createCharAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + List longValues = CollectionsPlume.mapList((Character value) -> (long) value, values); + return createIntValAnnotation(longValues); + } + } + + /** + * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of doubleacters; duplicates are allowed and the values may be in any order + * @return a {@link IntVal} annotation using the values + */ + public AnnotationMirror createDoubleAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + return createDoubleValAnnotation(values); + } + } + + /** + * Returns an annotation that represents the given set of values. + * + * @param values a homogeneous list: every element of it has the same class. This method does not + * modify or store it. + * @return an annotation that represents the given set of values + */ + public AnnotationMirror createNumberAnnotationMirror(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } else if (values.isEmpty()) { + return BOTTOMVAL; + } + Number first = values.get(0); + if (first instanceof Integer + || first instanceof Short + || first instanceof Long + || first instanceof Byte) { + List intValues = CollectionsPlume.mapList(Number::longValue, values); + return createIntValAnnotation(intValues); + } else if (first instanceof Double || first instanceof Float) { + List doubleValues = CollectionsPlume.mapList(Number::doubleValue, values); + return createDoubleValAnnotation(doubleValues); + } + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: unexpected class: " + first.getClass()); + } + + /** + * Create an {@code @IntRange} annotation from the two (inclusive) bounds. Does not return + * BOTTOMVAL or UNKNOWNVAL. + * + * @param from the lower bound + * @param to the upper bound + * @return an {@code @IntRange} annotation + */ + /*package-private*/ AnnotationMirror createIntRangeAnnotation(long from, long to) { + assert from <= to; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRange.class); + builder.setValue("from", from); + builder.setValue("to", to); + return builder.build(); + } + + /** + * Create an {@code @IntRange} or {@code @IntVal} annotation from the range. May return BOTTOMVAL + * or UNKNOWNVAL. + */ + public AnnotationMirror createIntRangeAnnotation(Range range) { + if (range.isNothing()) { + return BOTTOMVAL; + } else if (range.isLongEverything()) { + return UNKNOWNVAL; + } else if (range.isWiderThan(MAX_VALUES)) { + return createIntRangeAnnotation(range.from, range.to); + } else { + List newValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); + return createIntValAnnotation(newValues); + } + } + + /** + * Creates the special {@link IntRangeFromPositive} annotation, which is only used as an alias for + * the Index Checker's {@link org.checkerframework.checker.index.qual.Positive} annotation. It is + * treated everywhere as an IntRange annotation, but is not checked when it appears as the left + * hand side of an assignment (because the Lower Bound Checker will check it). + */ + private AnnotationMirror createIntRangeFromPositive() { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromPositive.class); + return builder.build(); + } + + /** + * Creates the special {@link IntRangeFromNonNegative} annotation, which is only used as an alias + * for the Index Checker's {@link org.checkerframework.checker.index.qual.NonNegative} annotation. + * It is treated everywhere as an IntRange annotation, but is not checked when it appears as the + * left hand side of an assignment (because the Lower Bound Checker will check it). + */ + private AnnotationMirror createIntRangeFromNonNegative() { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromNonNegative.class); + return builder.build(); + } + + /** + * Creates the special {@link IntRangeFromGTENegativeOne} annotation, which is only used as an + * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.GTENegativeOne} + * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it + * appears as the left hand side of an assignment (because the Lower Bound Checker will check it). + */ + private AnnotationMirror createIntRangeFromGTENegativeOne() { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, IntRangeFromGTENegativeOne.class); + return builder.build(); + } + + /** + * Create an {@code @ArrayLenRange} annotation from the two (inclusive) bounds. Does not return + * BOTTOMVAL or UNKNOWNVAL. + */ + public AnnotationMirror createArrayLenRangeAnnotation(int from, int to) { + assert from <= to; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLenRange.class); + builder.setValue("from", from); + builder.setValue("to", to); + return builder.build(); + } + + /** + * Create an {@code @ArrayLenRange} annotation from the range. May return BOTTOMVAL or UNKNOWNVAL. + */ + public AnnotationMirror createArrayLenRangeAnnotation(Range range) { + if (range.isNothing()) { + return BOTTOMVAL; + } else if (range.isLongEverything() || !range.isWithinInteger()) { + return UNKNOWNVAL; + } else { + return createArrayLenRangeAnnotation( + Long.valueOf(range.from).intValue(), Long.valueOf(range.to).intValue()); + } + } + + /** + * Creates an {@code MatchesRegex} annotation for the given regular expressions. + * + * @param regexes a list of Java regular expressions + * @return a MatchesRegex annotation with those values + */ + public AnnotationMirror createMatchesRegexAnnotation(@Nullable List<@Regex String> regexes) { + if (regexes == null) { + return UNKNOWNVAL; + } + if (regexes.isEmpty()) { + return BOTTOMVAL; + } + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MatchesRegex.class); + builder.setValue("value", regexes.toArray(new String[regexes.size()])); + return builder.build(); + } + + /** + * Creates an {@code DoesNotMatchRegex} annotation for the given regular expressions. + * + * @param regexes a list of Java regular expressions + * @return a DoesNotMatchRegex annotation with those values + */ + public AnnotationMirror createDoesNotMatchRegexAnnotation(@Nullable List<@Regex String> regexes) { + if (regexes == null) { + return BOTTOMVAL; + } + if (regexes.isEmpty()) { + return UNKNOWNVAL; + } + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoesNotMatchRegex.class); + builder.setValue("value", regexes.toArray(new String[regexes.size()])); + return builder.build(); + } + + /** + * Converts an {@code @StringVal} annotation to an {@code @ArrayLenRange} annotation. + * + * @param stringValAnno a StringVal annotation + * @return an ArrayLenRange annotation representing the possible lengths of the values of the + * given StringVal annotation + */ + /*package-private*/ AnnotationMirror convertStringValToArrayLenRange( + AnnotationMirror stringValAnno) { + List values = getStringValues(stringValAnno); + List lengths = ValueCheckerUtils.getLengthsForStringValues(values); + return createArrayLenRangeAnnotation(Collections.min(lengths), Collections.max(lengths)); + } + + /** + * Converts a {@code @StringVal} annotation to an {@code @ArrayLen} annotation. If the + * {@code @StringVal} annotation contains string values of more than MAX_VALUES distinct lengths, + * {@code @ArrayLenRange} annotation is returned instead. + * + * @param stringValAnno a {@code @StringVal} annotation + * @return a corresponding {@code @ArrayLen} annotation + */ + /*package-private*/ AnnotationMirror convertStringValToArrayLen(AnnotationMirror stringValAnno) { + List values = getStringValues(stringValAnno); + return createArrayLenAnnotation(ValueCheckerUtils.getLengthsForStringValues(values)); + } + + /** + * Converts an {@code StringVal} annotation to an {@code MatchesRegex} annotation that matches + * exactly the string values listed in the {@code StringVal}. + * + * @param stringValAnno a StringVal annotation + * @return an equivalent MatchesReges annotation + */ + /*package-private*/ AnnotationMirror convertStringValToMatchesRegex( + AnnotationMirror stringValAnno) { + List values = getStringValues(stringValAnno); + List<@Regex String> valuesAsRegexes = CollectionsPlume.mapList(Pattern::quote, values); + return createMatchesRegexAnnotation(valuesAsRegexes); + } + + /** + * Converts an {@code @ArrayLen} annotation to an {@code @ArrayLenRange} annotation. + * + * @param arrayLenAnno an ArrayLen annotation + * @return an ArrayLenRange annotation representing the bounds of the given ArrayLen annotation + */ + public AnnotationMirror convertArrayLenToArrayLenRange(AnnotationMirror arrayLenAnno) { + List values = getArrayLength(arrayLenAnno); + return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); + } + + /** Converts an {@code @IntVal} annotation to an {@code @IntRange} annotation. */ + public AnnotationMirror convertIntValToIntRange(AnnotationMirror intValAnno) { + List intValues = getIntValues(intValAnno); + return createIntRangeAnnotation(Collections.min(intValues), Collections.max(intValues)); + } + + /** + * Returns a {@link Range} bounded by the values specified in the given {@code @Range} annotation. + * Also returns an appropriate range if an {@code @IntVal} annotation is passed. Returns {@code + * null} if the annotation is null or if the annotation is not an {@code IntRange}, {@code + * IntRangeFromPositive}, {@code IntVal}, or {@code ArrayLenRange}. + * + * @param rangeAnno a {@code @Range} annotation + * @return the {@link Range} that the annotation represents + */ + public @Nullable Range getRange(@Nullable AnnotationMirror rangeAnno) { + if (rangeAnno == null) { + return null; + } + switch (AnnotationUtils.annotationName(rangeAnno)) { + case INTRANGE_FROMPOS_NAME: + return Range.create(1, Integer.MAX_VALUE); + case INTRANGE_FROMNONNEG_NAME: + return Range.create(0, Integer.MAX_VALUE); + case INTRANGE_FROMGTENEGONE_NAME: + return Range.create(-1, Integer.MAX_VALUE); + case INTVAL_NAME: + return ValueCheckerUtils.getRangeFromValues(getIntValues(rangeAnno)); + case INTRANGE_NAME: + // Assume rangeAnno is well-formed, i.e., 'from' is less than or equal to 'to'. + return Range.create(getIntRangeFromValue(rangeAnno), getIntRangeToValue(rangeAnno)); + case ARRAYLENRANGE_NAME: + return Range.create( + getArrayLenRangeFromValue(rangeAnno), getArrayLenRangeToValue(rangeAnno)); + default: return null; } - - /** - * Returns the set of possible boolean values as a sorted list with no duplicate values. Returns - * the empty list if no values are possible (for dead code). Returns null if any value is - * possible -- that is, if no estimate can be made -- and this includes when there is no - * constant-value annotation so the argument is null. - * - * @param boolAnno a {@code @BoolVal} annotation, or null - * @return a singleton or empty list of possible boolean values, or null - */ - public @Nullable List getBooleanValues(@Nullable AnnotationMirror boolAnno) { - if (boolAnno == null) { - return Collections.emptyList(); - } - List boolValues = - AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); - if (boolValues.size() < 2) { - return boolValues; - } - // Remove duplicates. - Set boolSet = new ArraySet<>(2); - boolSet.addAll(boolValues); - if (boolSet.size() > 1) { - // boolSet={true,false}; - return null; - } - return new ArrayList<>(boolSet); - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is no constant-value - * annotation so the argument is null. - * - * @param stringAnno a {@code @StringVal} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getStringValues(@PolyNull AnnotationMirror stringAnno) { - if (stringAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray( - stringAnno, stringValValueElement, String.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is no @MatchesRegex - * annotation so the argument is null. - * - * @param matchesRegexAnno a {@code @MatchesRegex} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getMatchesRegexValues( - @PolyNull AnnotationMirror matchesRegexAnno) { - if (matchesRegexAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray( - matchesRegexAnno, matchesRegexValueElement, String.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is - * no @DoesNotMatchRegex annotation so the argument is null. - * - * @param doesNotMatchRegexAnno a {@code @DoesNotMatchRegex} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getDoesNotMatchRegexValues( - @PolyNull AnnotationMirror doesNotMatchRegexAnno) { - if (doesNotMatchRegexAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray( - doesNotMatchRegexAnno, doesNotMatchRegexValueElement, String.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns true if {@link #isIntRange(AnnotationMirror)} returns true for any annotation in the - * given set. - * - * @param anmSet a set of annotations - * @return true if any annotation is {@link IntRange} or related - */ - public boolean isIntRange(AnnotationMirrorSet anmSet) { - for (AnnotationMirror anm : anmSet) { - if (isIntRange(anm)) { - return true; - } - } - return false; - } - - /** - * Returns true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link - * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne}. - * - * @param anno annotation mirror - * @return true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link - * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne} - */ - public boolean isIntRange(AnnotationMirror anno) { - String name = AnnotationUtils.annotationName(anno); - return name.equals(INTRANGE_NAME) - || name.equals(INTRANGE_FROMPOS_NAME) - || name.equals(INTRANGE_FROMNONNEG_NAME) - || name.equals(INTRANGE_FROMGTENEGONE_NAME); - } - - public int getMinLenValue(AnnotatedTypeMirror atm) { - return getMinLenValue(atm.getAnnotationInHierarchy(UNKNOWNVAL)); - } - - /** - * Used to find the maximum length of an array. Returns null if there is no minimum length - * known, or if the passed annotation is null. - */ - public @Nullable Integer getMaxLenValue(@Nullable AnnotationMirror annotation) { - if (annotation == null) { - return null; - } - switch (AnnotationUtils.annotationName(annotation)) { - case ARRAYLENRANGE_NAME: - return Long.valueOf(getRange(annotation).to).intValue(); - case ARRAYLEN_NAME: - return Collections.max(getArrayLength(annotation)); - case STRINGVAL_NAME: - return Collections.max( - ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); - default: - return null; - } - } - - /** - * Finds a minimum length of an array specified by the provided annotation. Returns null if - * there is no minimum length known, or if the passed annotation is null. - * - *

Note that this routine handles actual {@link MinLen} annotations, because it is called by - * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms - * {@link MinLen} annotations into {@link ArrayLenRange} annotations. - */ - private @Nullable Integer getSpecifiedMinLenValue(@Nullable AnnotationMirror annotation) { - if (annotation == null) { - return null; - } - switch (AnnotationUtils.annotationName(annotation)) { - case MINLEN_NAME: - return getMinLenValueValue(annotation); - case ARRAYLENRANGE_NAME: - return Long.valueOf(getRange(annotation).from).intValue(); - case ARRAYLEN_NAME: - return Collections.min(getArrayLength(annotation)); - case STRINGVAL_NAME: - return Collections.min( - ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); - default: - return null; - } - } - - /** - * Used to find the minimum length of an array, which is useful for array bounds checking. - * Returns 0 if there is no minimum length known, or if the passed annotation is null. - * - *

Note that this routine handles actual {@link MinLen} annotations, because it is called by - * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms - * {@link MinLen} annotations into {@link ArrayLenRange} annotations. - */ - public int getMinLenValue(@Nullable AnnotationMirror annotation) { - Integer minLen = getSpecifiedMinLenValue(annotation); - if (minLen == null || minLen < 0) { - return 0; - } else { - return minLen; - } - } - - /** - * Returns the minimum length of an array. - * - * @param annotations the annotations on the array expression - * @return the minimum length of an array - */ - public int getMinLenValue(AnnotationMirrorSet annotations) { - int result = 0; - for (AnnotationMirror annotation : annotations) { - Integer minLen = getSpecifiedMinLenValue(annotation); - if (minLen != null) { - result = Integer.min(result, minLen); - } - } - if (result < 0) { - return 0; - } else { - return result; - } - } - - /** - * Returns the smallest possible value that an integral annotation might take on. The passed - * {@code AnnotatedTypeMirror} should contain either an {@code @IntRange} annotation or an - * {@code @IntVal} annotation. Returns null if it does not. - * - * @param atm annotated type - * @return the smallest possible integral for which the {@code atm} could be the type - */ - public @Nullable Long getMinimumIntegralValue(AnnotatedTypeMirror atm) { - AnnotationMirror anm = atm.getAnnotationInHierarchy(UNKNOWNVAL); - if (AnnotationUtils.areSameByName(anm, INTVAL_NAME)) { - List possibleValues = getIntValues(anm); - return Collections.min(possibleValues); - } else if (isIntRange(anm)) { - Range range = getRange(anm); - return range.from; - } + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty + * list if no values are possible (for dead code). Returns null if any value is possible -- that + * is, if no estimate can be made -- and this includes when there is no constant-value annotation + * so the argument is null. + * + *

The method returns a list of {@code Long} but is named {@code getIntValues} because it + * supports the {@code @IntVal} annotation. + * + * @param intAnno an {@code @IntVal} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getIntValues(@PolyNull AnnotationMirror intAnno) { + if (intAnno == null) { + return null; + } + List list = AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty + * list if no values are possible (for dead code). Returns null if any value is possible -- that + * is, if no estimate can be made -- and this includes when there is no constant-value annotation + * so the argument is null. + * + * @param doubleAnno a {@code @DoubleVal} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getDoubleValues(@PolyNull AnnotationMirror doubleAnno) { + if (doubleAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray(doubleAnno, doubleValValueElement, Double.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible array lengths as a sorted list with no duplicate values. Returns + * the empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is no constant-value + * annotation so the argument is null. + * + * @param arrayAnno an {@code @ArrayLen} annotation, or null + * @return the possible array lengths, deduplicated and sorted + */ + public @PolyNull List getArrayLength(@PolyNull AnnotationMirror arrayAnno) { + if (arrayAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray(arrayAnno, arrayLenValueElement, Integer.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty + * list if no values are possible (for dead code). Returns null if any value is possible -- that + * is, if no estimate can be made -- and this includes when there is no constant-value annotation + * so the argument is null. + * + * @param intAnno an {@code @IntVal} annotation, or null + * @return the values represented by the given {@code @IntVal} annotation + */ + public @PolyNull List getCharValues(@PolyNull AnnotationMirror intAnno) { + if (intAnno == null) { + return Collections.emptyList(); + } + List intValues = + AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); + List charValues = + CollectionsPlume.mapList((Long i) -> (char) i.intValue(), intValues); + Collections.sort(charValues); + // TODO: Should this be an unmodifiable list? + return new ArrayList<>(charValues); + } + + /** + * Returns the single possible boolean value, or null if there is not exactly one possible value. + * + * @see #getBooleanValues + * @param boolAnno a {@code @BoolVal} annotation, or null + * @return the single possible boolean value, on null if that is not the case + */ + public @Nullable Boolean getBooleanValue(@Nullable AnnotationMirror boolAnno) { + if (boolAnno == null) { + return null; + } + List boolValues = + AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); + Set boolSet = new TreeSet<>(boolValues); + if (boolSet.size() == 1) { + return boolSet.iterator().next(); + } + return null; + } + + /** + * Returns the set of possible boolean values as a sorted list with no duplicate values. Returns + * the empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is no constant-value + * annotation so the argument is null. + * + * @param boolAnno a {@code @BoolVal} annotation, or null + * @return a singleton or empty list of possible boolean values, or null + */ + public @Nullable List getBooleanValues(@Nullable AnnotationMirror boolAnno) { + if (boolAnno == null) { + return Collections.emptyList(); + } + List boolValues = + AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); + if (boolValues.size() < 2) { + return boolValues; + } + // Remove duplicates. + Set boolSet = new ArraySet<>(2); + boolSet.addAll(boolValues); + if (boolSet.size() > 1) { + // boolSet={true,false}; + return null; + } + return new ArrayList<>(boolSet); + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty + * list if no values are possible (for dead code). Returns null if any value is possible -- that + * is, if no estimate can be made -- and this includes when there is no constant-value annotation + * so the argument is null. + * + * @param stringAnno a {@code @StringVal} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getStringValues(@PolyNull AnnotationMirror stringAnno) { + if (stringAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray(stringAnno, stringValValueElement, String.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible -- + * that is, if no estimate can be made -- and this includes when there is no @MatchesRegex + * annotation so the argument is null. + * + * @param matchesRegexAnno a {@code @MatchesRegex} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getMatchesRegexValues(@PolyNull AnnotationMirror matchesRegexAnno) { + if (matchesRegexAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray( + matchesRegexAnno, matchesRegexValueElement, String.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible -- + * that is, if no estimate can be made -- and this includes when there is no @DoesNotMatchRegex + * annotation so the argument is null. + * + * @param doesNotMatchRegexAnno a {@code @DoesNotMatchRegex} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getDoesNotMatchRegexValues( + @PolyNull AnnotationMirror doesNotMatchRegexAnno) { + if (doesNotMatchRegexAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray( + doesNotMatchRegexAnno, doesNotMatchRegexValueElement, String.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns true if {@link #isIntRange(AnnotationMirror)} returns true for any annotation in the + * given set. + * + * @param anmSet a set of annotations + * @return true if any annotation is {@link IntRange} or related + */ + public boolean isIntRange(AnnotationMirrorSet anmSet) { + for (AnnotationMirror anm : anmSet) { + if (isIntRange(anm)) { + return true; + } + } + return false; + } + + /** + * Returns true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link + * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne}. + * + * @param anno annotation mirror + * @return true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link + * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne} + */ + public boolean isIntRange(AnnotationMirror anno) { + String name = AnnotationUtils.annotationName(anno); + return name.equals(INTRANGE_NAME) + || name.equals(INTRANGE_FROMPOS_NAME) + || name.equals(INTRANGE_FROMNONNEG_NAME) + || name.equals(INTRANGE_FROMGTENEGONE_NAME); + } + + public int getMinLenValue(AnnotatedTypeMirror atm) { + return getMinLenValue(atm.getAnnotationInHierarchy(UNKNOWNVAL)); + } + + /** + * Used to find the maximum length of an array. Returns null if there is no minimum length known, + * or if the passed annotation is null. + */ + public @Nullable Integer getMaxLenValue(@Nullable AnnotationMirror annotation) { + if (annotation == null) { + return null; + } + switch (AnnotationUtils.annotationName(annotation)) { + case ARRAYLENRANGE_NAME: + return Long.valueOf(getRange(annotation).to).intValue(); + case ARRAYLEN_NAME: + return Collections.max(getArrayLength(annotation)); + case STRINGVAL_NAME: + return Collections.max( + ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); + default: return null; } - - /** - * Returns the minimum length of an array expression or 0 if the min length is unknown. - * - * @param sequenceExpression a Java expression - * @param tree expression tree or variable declaration - * @param currentPath path to local scope - * @return min length of sequenceExpression or 0 - */ - public int getMinLenFromString(String sequenceExpression, Tree tree, TreePath currentPath) { - AnnotationMirror lengthAnno; - JavaExpression expressionObj; - try { - expressionObj = parseJavaExpressionString(sequenceExpression, currentPath); - } catch (JavaExpressionParseException e) { - // ignore parse errors and return 0. - return 0; - } - - if (expressionObj instanceof ValueLiteral) { - ValueLiteral sequenceLiteral = (ValueLiteral) expressionObj; - Object sequenceLiteralValue = sequenceLiteral.getValue(); - if (sequenceLiteralValue instanceof String) { - return ((String) sequenceLiteralValue).length(); - } - } else if (expressionObj instanceof ArrayCreation) { - ArrayCreation arrayCreation = (ArrayCreation) expressionObj; - // This is only expected to support array creations in varargs methods - return arrayCreation.getInitializers().size(); - } else if (expressionObj instanceof ArrayAccess) { - List annoList = - expressionObj.getType().getAnnotationMirrors(); - for (AnnotationMirror anno : annoList) { - String ANNO_NAME = AnnotationUtils.annotationName(anno); - if (ANNO_NAME.equals(MINLEN_NAME)) { - return getMinLenValue(canonicalAnnotation(anno)); - } else if (ANNO_NAME.equals(ARRAYLEN_NAME) - || ANNO_NAME.equals(ARRAYLENRANGE_NAME)) { - return getMinLenValue(anno); - } - } - } - - lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLenRange.class); - if (lengthAnno == null) { - lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLen.class); - } - if (lengthAnno == null) { - lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, StringVal.class); - } - - if (lengthAnno == null) { - // Could not find a more precise type, so return 0; - return 0; - } - - return getMinLenValue(lengthAnno); - } - - /** - * Returns the annotation type mirror for the type of {@code expressionTree} with default - * annotations applied. - */ - @Override - public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { - TypeMirror type = TreeUtils.typeOf(expressionTree); - if (type.getKind() != TypeKind.VOID) { - AnnotatedTypeMirror atm = type(expressionTree); - addDefaultAnnotations(atm); - return atm; - } + } + + /** + * Finds a minimum length of an array specified by the provided annotation. Returns null if there + * is no minimum length known, or if the passed annotation is null. + * + *

Note that this routine handles actual {@link MinLen} annotations, because it is called by + * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms + * {@link MinLen} annotations into {@link ArrayLenRange} annotations. + */ + private @Nullable Integer getSpecifiedMinLenValue(@Nullable AnnotationMirror annotation) { + if (annotation == null) { + return null; + } + switch (AnnotationUtils.annotationName(annotation)) { + case MINLEN_NAME: + return getMinLenValueValue(annotation); + case ARRAYLENRANGE_NAME: + return Long.valueOf(getRange(annotation).from).intValue(); + case ARRAYLEN_NAME: + return Collections.min(getArrayLength(annotation)); + case STRINGVAL_NAME: + return Collections.min( + ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); + default: return null; } - - /** - * A fact about an array, such as its length, cannot be changed via side effects to the array. - */ - @Override - public boolean isImmutable(TypeMirror type) { - if (type.getKind() == TypeKind.ARRAY) { - return true; - } - return super.isImmutable(type); - } + } + + /** + * Used to find the minimum length of an array, which is useful for array bounds checking. Returns + * 0 if there is no minimum length known, or if the passed annotation is null. + * + *

Note that this routine handles actual {@link MinLen} annotations, because it is called by + * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms + * {@link MinLen} annotations into {@link ArrayLenRange} annotations. + */ + public int getMinLenValue(@Nullable AnnotationMirror annotation) { + Integer minLen = getSpecifiedMinLenValue(annotation); + if (minLen == null || minLen < 0) { + return 0; + } else { + return minLen; + } + } + + /** + * Returns the minimum length of an array. + * + * @param annotations the annotations on the array expression + * @return the minimum length of an array + */ + public int getMinLenValue(AnnotationMirrorSet annotations) { + int result = 0; + for (AnnotationMirror annotation : annotations) { + Integer minLen = getSpecifiedMinLenValue(annotation); + if (minLen != null) { + result = Integer.min(result, minLen); + } + } + if (result < 0) { + return 0; + } else { + return result; + } + } + + /** + * Returns the smallest possible value that an integral annotation might take on. The passed + * {@code AnnotatedTypeMirror} should contain either an {@code @IntRange} annotation or an + * {@code @IntVal} annotation. Returns null if it does not. + * + * @param atm annotated type + * @return the smallest possible integral for which the {@code atm} could be the type + */ + public @Nullable Long getMinimumIntegralValue(AnnotatedTypeMirror atm) { + AnnotationMirror anm = atm.getAnnotationInHierarchy(UNKNOWNVAL); + if (AnnotationUtils.areSameByName(anm, INTVAL_NAME)) { + List possibleValues = getIntValues(anm); + return Collections.min(possibleValues); + } else if (isIntRange(anm)) { + Range range = getRange(anm); + return range.from; + } + return null; + } + + /** + * Returns the minimum length of an array expression or 0 if the min length is unknown. + * + * @param sequenceExpression a Java expression + * @param tree expression tree or variable declaration + * @param currentPath path to local scope + * @return min length of sequenceExpression or 0 + */ + public int getMinLenFromString(String sequenceExpression, Tree tree, TreePath currentPath) { + AnnotationMirror lengthAnno; + JavaExpression expressionObj; + try { + expressionObj = parseJavaExpressionString(sequenceExpression, currentPath); + } catch (JavaExpressionParseException e) { + // ignore parse errors and return 0. + return 0; + } + + if (expressionObj instanceof ValueLiteral) { + ValueLiteral sequenceLiteral = (ValueLiteral) expressionObj; + Object sequenceLiteralValue = sequenceLiteral.getValue(); + if (sequenceLiteralValue instanceof String) { + return ((String) sequenceLiteralValue).length(); + } + } else if (expressionObj instanceof ArrayCreation) { + ArrayCreation arrayCreation = (ArrayCreation) expressionObj; + // This is only expected to support array creations in varargs methods + return arrayCreation.getInitializers().size(); + } else if (expressionObj instanceof ArrayAccess) { + List annoList = expressionObj.getType().getAnnotationMirrors(); + for (AnnotationMirror anno : annoList) { + String ANNO_NAME = AnnotationUtils.annotationName(anno); + if (ANNO_NAME.equals(MINLEN_NAME)) { + return getMinLenValue(canonicalAnnotation(anno)); + } else if (ANNO_NAME.equals(ARRAYLEN_NAME) || ANNO_NAME.equals(ARRAYLENRANGE_NAME)) { + return getMinLenValue(anno); + } + } + } + + lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLenRange.class); + if (lengthAnno == null) { + lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLen.class); + } + if (lengthAnno == null) { + lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, StringVal.class); + } + + if (lengthAnno == null) { + // Could not find a more precise type, so return 0; + return 0; + } + + return getMinLenValue(lengthAnno); + } + + /** + * Returns the annotation type mirror for the type of {@code expressionTree} with default + * annotations applied. + */ + @Override + public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { + TypeMirror type = TreeUtils.typeOf(expressionTree); + if (type.getKind() != TypeKind.VOID) { + AnnotatedTypeMirror atm = type(expressionTree); + addDefaultAnnotations(atm); + return atm; + } + return null; + } + + /** A fact about an array, such as its length, cannot be changed via side effects to the array. */ + @Override + public boolean isImmutable(TypeMirror type) { + if (type.getKind() == TypeKind.ARRAY) { + return true; + } + return super.isImmutable(type); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java index 1e3c911c23d..6a458091eda 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java @@ -1,13 +1,12 @@ package org.checkerframework.common.value; +import java.util.LinkedHashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.util.Range; import org.checkerframework.framework.source.SupportedOptions; -import java.util.LinkedHashSet; -import java.util.Set; - /** * The Constant Value Checker is a constant propagation analysis: for each variable, it determines * whether that variable's value can be known at compile time. @@ -20,47 +19,47 @@ * @checker_framework.manual #constant-value-checker Constant Value Checker */ @SupportedOptions({ - ValueChecker.REPORT_EVAL_WARNS, - ValueChecker.IGNORE_RANGE_OVERFLOW, - ValueChecker.NON_NULL_STRINGS_CONCATENATION + ValueChecker.REPORT_EVAL_WARNS, + ValueChecker.IGNORE_RANGE_OVERFLOW, + ValueChecker.NON_NULL_STRINGS_CONCATENATION }) public class ValueChecker extends BaseTypeChecker { - /** - * Command-line option to warn the user if a @StaticallyExecutable method can't load and run at - * compile time. - */ - public static final String REPORT_EVAL_WARNS = "reportEvalWarns"; + /** + * Command-line option to warn the user if a @StaticallyExecutable method can't load and run at + * compile time. + */ + public static final String REPORT_EVAL_WARNS = "reportEvalWarns"; - /** Command-line option to ignore the possibility of overflow for range annotations. */ - public static final String IGNORE_RANGE_OVERFLOW = "ignoreRangeOverflow"; + /** Command-line option to ignore the possibility of overflow for range annotations. */ + public static final String IGNORE_RANGE_OVERFLOW = "ignoreRangeOverflow"; - /** Command-line option that assumes most expressions in String concatenations can be null. */ - public static final String NON_NULL_STRINGS_CONCATENATION = "nonNullStringsConcatenation"; + /** Command-line option that assumes most expressions in String concatenations can be null. */ + public static final String NON_NULL_STRINGS_CONCATENATION = "nonNullStringsConcatenation"; - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ValueVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ValueVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - // Don't call super otherwise MethodVal will be added as a subChecker - // which creates a circular dependency. - // Use the same Set implementation as super. - return new LinkedHashSet<>(0); - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Don't call super otherwise MethodVal will be added as a subChecker + // which creates a circular dependency. + // Use the same Set implementation as super. + return new LinkedHashSet<>(0); + } - @Override - public boolean shouldResolveReflection() { - // Because this checker is a subchecker of MethodVal, - // reflection can't be resolved. - return false; - } + @Override + public boolean shouldResolveReflection() { + // Because this checker is a subchecker of MethodVal, + // reflection can't be resolved. + return false; + } - @Override - public void typeProcessingOver() { - // Reset ignore overflow. - Range.ignoreOverflow = false; - super.typeProcessingOver(); - } + @Override + public void typeProcessingOver() { + // Reset ignore overflow. + Range.ignoreOverflow = false; + super.typeProcessingOver(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java index 6fdd198236e..94c563d374f 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java @@ -1,7 +1,12 @@ package org.checkerframework.common.value; import com.sun.source.tree.Tree; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.mustcall.qual.MustCallUnknown; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.qual.IntRange; @@ -18,419 +23,407 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeMirror; - /** Utility methods for the Value Checker. */ public class ValueCheckerUtils { - /** Do not instantiate. */ - private ValueCheckerUtils() { - throw new TypeSystemError("do not instantiate"); - } + /** Do not instantiate. */ + private ValueCheckerUtils() { + throw new TypeSystemError("do not instantiate"); + } - /** - * Get a list of the values of an annotation, and then cast the values to a given type. - * - * @param anno the annotation that contains values - * @param castTo the type that is cast to - * @param atypeFactory the type factory - * @return a list of values after the casting - */ - public static List getValuesCastedToType( - AnnotationMirror anno, TypeMirror castTo, ValueAnnotatedTypeFactory atypeFactory) { - return getValuesCastedToType(anno, castTo, false, atypeFactory); - } + /** + * Get a list of the values of an annotation, and then cast the values to a given type. + * + * @param anno the annotation that contains values + * @param castTo the type that is cast to + * @param atypeFactory the type factory + * @return a list of values after the casting + */ + public static List getValuesCastedToType( + AnnotationMirror anno, TypeMirror castTo, ValueAnnotatedTypeFactory atypeFactory) { + return getValuesCastedToType(anno, castTo, false, atypeFactory); + } - /** - * Get a list of the values of an annotation, and then cast the values to a given type. - * - * @param anno the annotation that contains values - * @param castTo the unannotated type that is casted to - * @param isUnsigned true if the type being casted to is unsigned - * @param atypeFactory the type factory - * @return a list of values after the casting - */ - public static List getValuesCastedToType( - AnnotationMirror anno, - TypeMirror castTo, - boolean isUnsigned, - ValueAnnotatedTypeFactory atypeFactory) { - Class castType = TypesUtils.getClassFromType(castTo); - List values; - switch (AnnotationUtils.annotationName(anno)) { - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - values = convertDoubleVal(anno, castType, castTo, atypeFactory); - break; - case ValueAnnotatedTypeFactory.INTVAL_NAME: - List longs = atypeFactory.getIntValues(anno); - values = convertIntVal(longs, castType, castTo, isUnsigned); - break; - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - Range range = atypeFactory.getRange(anno); - List rangeValues = getValuesFromRange(range, Long.class); - values = convertIntVal(rangeValues, castType, castTo, isUnsigned); - break; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - values = convertStringVal(anno, castType, atypeFactory); - break; - case ValueAnnotatedTypeFactory.BOOLVAL_NAME: - values = convertBoolVal(anno, castType, atypeFactory); - break; - case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - values = Collections.emptyList(); - break; - default: - values = null; - } - return values; + /** + * Get a list of the values of an annotation, and then cast the values to a given type. + * + * @param anno the annotation that contains values + * @param castTo the unannotated type that is casted to + * @param isUnsigned true if the type being casted to is unsigned + * @param atypeFactory the type factory + * @return a list of values after the casting + */ + public static List getValuesCastedToType( + AnnotationMirror anno, + TypeMirror castTo, + boolean isUnsigned, + ValueAnnotatedTypeFactory atypeFactory) { + Class castType = TypesUtils.getClassFromType(castTo); + List values; + switch (AnnotationUtils.annotationName(anno)) { + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + values = convertDoubleVal(anno, castType, castTo, atypeFactory); + break; + case ValueAnnotatedTypeFactory.INTVAL_NAME: + List longs = atypeFactory.getIntValues(anno); + values = convertIntVal(longs, castType, castTo, isUnsigned); + break; + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + Range range = atypeFactory.getRange(anno); + List rangeValues = getValuesFromRange(range, Long.class); + values = convertIntVal(rangeValues, castType, castTo, isUnsigned); + break; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + values = convertStringVal(anno, castType, atypeFactory); + break; + case ValueAnnotatedTypeFactory.BOOLVAL_NAME: + values = convertBoolVal(anno, castType, atypeFactory); + break; + case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + values = Collections.emptyList(); + break; + default: + values = null; } + return values; + } - /** Get the minimum and maximum of a list and return a range bounded by them. */ - public static @Nullable Range getRangeFromValues(List values) { - if (values == null) { - return null; - } else if (values.isEmpty()) { - return Range.NOTHING; - } - return Range.create(values); + /** Get the minimum and maximum of a list and return a range bounded by them. */ + public static @Nullable Range getRangeFromValues(List values) { + if (values == null) { + return null; + } else if (values.isEmpty()) { + return Range.NOTHING; } + return Range.create(values); + } - /** - * Converts a long value to a boxed numeric type. - * - * @param value a long value - * @param expectedType the boxed numeric type of the result - * @return {@code value} converted to {@code expectedType} using standard conversion rules - */ - private static T convertLongToType(long value, Class expectedType) { - Object convertedValue; - if (expectedType == Integer.class) { - convertedValue = (int) value; - } else if (expectedType == Short.class) { - convertedValue = (short) value; - } else if (expectedType == Byte.class) { - convertedValue = (byte) value; - } else if (expectedType == Long.class) { - convertedValue = value; - } else if (expectedType == Double.class) { - convertedValue = (double) value; - } else if (expectedType == Float.class) { - convertedValue = (float) value; - } else if (expectedType == Character.class) { - convertedValue = (char) value; - } else { - throw new UnsupportedOperationException( - "ValueCheckerUtils: unexpected class: " + expectedType); - } - return expectedType.cast(convertedValue); + /** + * Converts a long value to a boxed numeric type. + * + * @param value a long value + * @param expectedType the boxed numeric type of the result + * @return {@code value} converted to {@code expectedType} using standard conversion rules + */ + private static T convertLongToType(long value, Class expectedType) { + Object convertedValue; + if (expectedType == Integer.class) { + convertedValue = (int) value; + } else if (expectedType == Short.class) { + convertedValue = (short) value; + } else if (expectedType == Byte.class) { + convertedValue = (byte) value; + } else if (expectedType == Long.class) { + convertedValue = value; + } else if (expectedType == Double.class) { + convertedValue = (double) value; + } else if (expectedType == Float.class) { + convertedValue = (float) value; + } else if (expectedType == Character.class) { + convertedValue = (char) value; + } else { + throw new UnsupportedOperationException( + "ValueCheckerUtils: unexpected class: " + expectedType); } + return expectedType.cast(convertedValue); + } - /** - * Get all possible values from the given type and cast them into a boxed primitive type. - * Returns null if the list would have length greater than {@link - * ValueAnnotatedTypeFactory#MAX_VALUES}. - * - *

{@code expectedType} must be a boxed type, not a primitive type, because primitive types - * cannot be stored in a list. - * - * @param the type of the values to obtain - * @param range the given range - * @param expectedType the expected type - * @return a list of all the values in the range, or null if there would be more than {@link - * ValueAnnotatedTypeFactory#MAX_VALUES} - */ - public static @Nullable List getValuesFromRange( - @Nullable Range range, Class expectedType) { - if (range == null || range.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - return null; - } - if (range.isNothing()) { - return Collections.emptyList(); - } + /** + * Get all possible values from the given type and cast them into a boxed primitive type. Returns + * null if the list would have length greater than {@link ValueAnnotatedTypeFactory#MAX_VALUES}. + * + *

{@code expectedType} must be a boxed type, not a primitive type, because primitive types + * cannot be stored in a list. + * + * @param the type of the values to obtain + * @param range the given range + * @param expectedType the expected type + * @return a list of all the values in the range, or null if there would be more than {@link + * ValueAnnotatedTypeFactory#MAX_VALUES} + */ + public static @Nullable List getValuesFromRange( + @Nullable Range range, Class expectedType) { + if (range == null || range.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + return null; + } + if (range.isNothing()) { + return Collections.emptyList(); + } - // The subtraction does not overflow, because the width has already been checked, so the - // bound difference is less than ValueAnnotatedTypeFactory.MAX_VALUES. - long boundDifference = range.to - range.from; + // The subtraction does not overflow, because the width has already been checked, so the + // bound difference is less than ValueAnnotatedTypeFactory.MAX_VALUES. + long boundDifference = range.to - range.from; - // Each value is computed as a sum of the first value and an offset within the range, - // to avoid having range.to as an upper bound of the loop. range.to can be Long.MAX_VALUE, - // in which case a comparison value <= range.to would be always true. - // boundDifference is always much smaller than Long.MAX_VALUE - List values = new ArrayList<>((int) boundDifference + 1); - for (long offset = 0; offset <= boundDifference; offset++) { - long value = range.from + offset; - values.add(convertLongToType(value, expectedType)); - } - return values; + // Each value is computed as a sum of the first value and an offset within the range, + // to avoid having range.to as an upper bound of the loop. range.to can be Long.MAX_VALUE, + // in which case a comparison value <= range.to would be always true. + // boundDifference is always much smaller than Long.MAX_VALUE + List values = new ArrayList<>((int) boundDifference + 1); + for (long offset = 0; offset <= boundDifference; offset++) { + long value = range.from + offset; + values.add(convertLongToType(value, expectedType)); } + return values; + } - /** - * Converts a list of objects to a list of their string representations. - * - * @param origValues the objects to format - * @return a list of the formatted objects - */ - private static @Nullable List convertToStringVal( - List origValues) { - if (origValues == null) { - return null; - } - return CollectionsPlume.mapList(Object::toString, origValues); + /** + * Converts a list of objects to a list of their string representations. + * + * @param origValues the objects to format + * @return a list of the formatted objects + */ + private static @Nullable List convertToStringVal( + List origValues) { + if (origValues == null) { + return null; } + return CollectionsPlume.mapList(Object::toString, origValues); + } - /** - * Convert the {@code value} argument/element of a @BoolVal annotation into a list. - * - * @param anno a @BoolVal annotation - * @param newClass if String.class, the returned list is a {@code List} - * @param atypeFactory the type factory, used for obtaining fields/elements from annotations - * @return the {@code value} of a @BoolVal annotation, as a {@code List} or a {@code - * List} - */ - private static List convertBoolVal( - AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { - List bools = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.boolValValueElement, Boolean.class); + /** + * Convert the {@code value} argument/element of a @BoolVal annotation into a list. + * + * @param anno a @BoolVal annotation + * @param newClass if String.class, the returned list is a {@code List} + * @param atypeFactory the type factory, used for obtaining fields/elements from annotations + * @return the {@code value} of a @BoolVal annotation, as a {@code List} or a {@code + * List} + */ + private static List convertBoolVal( + AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { + List bools = + AnnotationUtils.getElementValueArray(anno, atypeFactory.boolValValueElement, Boolean.class); - if (newClass == String.class) { - return convertToStringVal(bools); - } - return bools; + if (newClass == String.class) { + return convertToStringVal(bools); } + return bools; + } - /** - * Convert the {@code value} argument/element of a {@code @StringVal} annotation into a list. - * - * @param anno a {@code @StringVal} annotation - * @param newClass if char[].class, the returned list is a {@code List} - * @param atypeFactory the type factory, used for obtaining fields/elements from annotations - * @return the {@code value} of a {@code @StringVal} annotation, as a {@code List} or a - * {@code List} - */ - private static List convertStringVal( - AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { - List strings = atypeFactory.getStringValues(anno); - if (newClass == char[].class) { - return CollectionsPlume.mapList(String::toCharArray, strings); - } - return strings; + /** + * Convert the {@code value} argument/element of a {@code @StringVal} annotation into a list. + * + * @param anno a {@code @StringVal} annotation + * @param newClass if char[].class, the returned list is a {@code List} + * @param atypeFactory the type factory, used for obtaining fields/elements from annotations + * @return the {@code value} of a {@code @StringVal} annotation, as a {@code List} or a + * {@code List} + */ + private static List convertStringVal( + AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { + List strings = atypeFactory.getStringValues(anno); + if (newClass == char[].class) { + return CollectionsPlume.mapList(String::toCharArray, strings); } + return strings; + } - /** - * Convert a list of longs to a given type - * - * @param longs the integral values to convert - * @param newClass determines the type of the result - * @param newType the type to which to cast, if newClass is numeric - * @param isUnsigned if true, treat {@code newType} as unsigned - * @return the {@code value} of a {@code @IntVal} annotation, as a {@code List} or a - * {@code List} - */ - private static @Nullable List convertIntVal( - List longs, Class newClass, TypeMirror newType, boolean isUnsigned) { - if (longs == null) { - return null; - } - if (newClass == String.class) { - return convertToStringVal(longs); - } else if (newClass == Character.class || newClass == char.class) { - return CollectionsPlume.mapList((Long l) -> (char) l.longValue(), longs); - } else if (newClass == Boolean.class) { - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: can't convert integral type to boolean"); - } - return NumberUtils.castNumbers(newType, isUnsigned, longs); + /** + * Convert a list of longs to a given type + * + * @param longs the integral values to convert + * @param newClass determines the type of the result + * @param newType the type to which to cast, if newClass is numeric + * @param isUnsigned if true, treat {@code newType} as unsigned + * @return the {@code value} of a {@code @IntVal} annotation, as a {@code List} or a + * {@code List} + */ + private static @Nullable List convertIntVal( + List longs, Class newClass, TypeMirror newType, boolean isUnsigned) { + if (longs == null) { + return null; } - - /** - * Convert the {@code value} argument/element of a @StringVal annotation into a list. - * - * @param anno a {@code @DoubleVal} annotation - * @param newClass the component type for the returned list - * @param newType the component type for the returned list - * @param atypeFactory the type factory, used for obtaining fields/elements from annotations - * @return the {@code value} of a {@code @DoubleVal} annotation - */ - private static @Nullable List convertDoubleVal( - AnnotationMirror anno, - Class newClass, - TypeMirror newType, - ValueAnnotatedTypeFactory atypeFactory) { - List doubles = atypeFactory.getDoubleValues(anno); - if (doubles == null) { - return null; - } - if (newClass == String.class) { - return convertToStringVal(doubles); - } else if (newClass == Character.class || newClass == char.class) { - return CollectionsPlume.mapList((Double l) -> (char) l.doubleValue(), doubles); - } else if (newClass == Boolean.class) { - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: can't convert double to boolean"); - } - return NumberUtils.castNumbers(newType, doubles); + if (newClass == String.class) { + return convertToStringVal(longs); + } else if (newClass == Character.class || newClass == char.class) { + return CollectionsPlume.mapList((Long l) -> (char) l.longValue(), longs); + } else if (newClass == Boolean.class) { + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: can't convert integral type to boolean"); } + return NumberUtils.castNumbers(newType, isUnsigned, longs); + } - /** - * Gets a list of lengths for a list of string values. - * - * @param values list of string values - * @return list of unique lengths of strings in {@code values} - */ - public static List getLengthsForStringValues(List values) { - List lengths = CollectionsPlume.mapList(String::length, values); - return CollectionsPlume.withoutDuplicatesSorted(lengths); + /** + * Convert the {@code value} argument/element of a @StringVal annotation into a list. + * + * @param anno a {@code @DoubleVal} annotation + * @param newClass the component type for the returned list + * @param newType the component type for the returned list + * @param atypeFactory the type factory, used for obtaining fields/elements from annotations + * @return the {@code value} of a {@code @DoubleVal} annotation + */ + private static @Nullable List convertDoubleVal( + AnnotationMirror anno, + Class newClass, + TypeMirror newType, + ValueAnnotatedTypeFactory atypeFactory) { + List doubles = atypeFactory.getDoubleValues(anno); + if (doubles == null) { + return null; } - - /** - * Returns a range representing the possible integral values represented by the passed {@code - * AnnotatedTypeMirror}. If the passed {@code AnnotatedTypeMirror} does not contain an {@code - * IntRange} annotation or an {@code IntVal} annotation, returns null. - */ - public static @Nullable Range getPossibleValues( - AnnotatedTypeMirror valueType, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { - if (valueAnnotatedTypeFactory.isIntRange(valueType.getAnnotations())) { - return valueAnnotatedTypeFactory.getRange(valueType.getAnnotation(IntRange.class)); - } else { - List values = - valueAnnotatedTypeFactory.getIntValues(valueType.getAnnotation(IntVal.class)); - if (values != null) { - return Range.create(values); - } else { - return null; - } - } + if (newClass == String.class) { + return convertToStringVal(doubles); + } else if (newClass == Character.class || newClass == char.class) { + return CollectionsPlume.mapList((Double l) -> (char) l.doubleValue(), doubles); + } else if (newClass == Boolean.class) { + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: can't convert double to boolean"); } + return NumberUtils.castNumbers(newType, doubles); + } - /** - * Either returns the exact value of the given tree according to the Constant Value Checker, or - * null if the exact value is not known. This method should only be used by clients who need - * exactly one value -- such as the LBC's binary operator rules -- and not by those that need to - * know whether a valueType belongs to a particular qualifier. - */ - public static @Nullable Long getExactValue(Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null && possibleValues.from == possibleValues.to) { - return possibleValues.from; - } else { - return null; - } - } + /** + * Gets a list of lengths for a list of string values. + * + * @param values list of string values + * @return list of unique lengths of strings in {@code values} + */ + public static List getLengthsForStringValues(List values) { + List lengths = CollectionsPlume.mapList(String::length, values); + return CollectionsPlume.withoutDuplicatesSorted(lengths); + } - /** - * Returns the exact value of an annotated element according to the Constant Value Checker, or - * null if the exact value is not known. - * - * @param element the element to get the exact value from - * @param factory a ValueAnnotatedTypeFactory used for annotation accessing - * @return the exact value of the element if it is constant, or null otherwise - */ - public static @Nullable Long getExactValue(Element element, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(element); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null && possibleValues.from == possibleValues.to) { - return possibleValues.from; - } else { - return null; - } + /** + * Returns a range representing the possible integral values represented by the passed {@code + * AnnotatedTypeMirror}. If the passed {@code AnnotatedTypeMirror} does not contain an {@code + * IntRange} annotation or an {@code IntVal} annotation, returns null. + */ + public static @Nullable Range getPossibleValues( + AnnotatedTypeMirror valueType, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { + if (valueAnnotatedTypeFactory.isIntRange(valueType.getAnnotations())) { + return valueAnnotatedTypeFactory.getRange(valueType.getAnnotation(IntRange.class)); + } else { + List values = + valueAnnotatedTypeFactory.getIntValues(valueType.getAnnotation(IntVal.class)); + if (values != null) { + return Range.create(values); + } else { + return null; + } } + } - /** - * Either returns the exact string value of the given tree according to the Constant Value - * Checker, or null if the exact value is not known. This method should only be used by clients - * who need exactly one value and not by those that need to know whether a valueType belongs to - * a particular qualifier. - */ - public static @Nullable String getExactStringValue( - Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - if (valueType.hasAnnotation(StringVal.class)) { - AnnotationMirror valueAnno = valueType.getAnnotation(StringVal.class); - List possibleValues = - AnnotationUtils.getElementValueArray( - valueAnno, factory.stringValValueElement, String.class); - if (possibleValues.size() == 1) { - return possibleValues.get(0); - } - } - return null; + /** + * Either returns the exact value of the given tree according to the Constant Value Checker, or + * null if the exact value is not known. This method should only be used by clients who need + * exactly one value -- such as the LBC's binary operator rules -- and not by those that need to + * know whether a valueType belongs to a particular qualifier. + */ + public static @Nullable Long getExactValue(Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null && possibleValues.from == possibleValues.to) { + return possibleValues.from; + } else { + return null; } + } - /** - * Finds the minimum value in a Value Checker type. If there is no information (such as when the - * list of possible values is empty or null), returns null. Otherwise, returns the smallest - * value in the list of possible values. - */ - public static @Nullable Long getMinValue(Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null) { - return possibleValues.from; - } else { - return null; - } + /** + * Returns the exact value of an annotated element according to the Constant Value Checker, or + * null if the exact value is not known. + * + * @param element the element to get the exact value from + * @param factory a ValueAnnotatedTypeFactory used for annotation accessing + * @return the exact value of the element if it is constant, or null otherwise + */ + public static @Nullable Long getExactValue(Element element, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(element); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null && possibleValues.from == possibleValues.to) { + return possibleValues.from; + } else { + return null; } + } - /** - * Finds the maximum value in a Value Checker type. If there is no information (such as when the - * list of possible values is empty or null), returns null. Otherwise, returns the smallest - * value in the list of possible values. - */ - public static @Nullable Long getMaxValue(Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null) { - return possibleValues.to; - } else { - return null; - } + /** + * Either returns the exact string value of the given tree according to the Constant Value + * Checker, or null if the exact value is not known. This method should only be used by clients + * who need exactly one value and not by those that need to know whether a valueType belongs to a + * particular qualifier. + */ + public static @Nullable String getExactStringValue(Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + if (valueType.hasAnnotation(StringVal.class)) { + AnnotationMirror valueAnno = valueType.getAnnotation(StringVal.class); + List possibleValues = + AnnotationUtils.getElementValueArray( + valueAnno, factory.stringValValueElement, String.class); + if (possibleValues.size() == 1) { + return possibleValues.get(0); + } } + return null; + } - /** - * Looks up the minlen of a member select tree. The tree must be an access to a sequence length. - */ - public static @Nullable Integer getMinLenFromTree( - Tree tree, ValueAnnotatedTypeFactory valueATF) { - AnnotatedTypeMirror minLenType = valueATF.getAnnotatedType(tree); - Long min = valueATF.getMinimumIntegralValue(minLenType); - if (min == null) { - return null; - } - if (min < 0 || min > Integer.MAX_VALUE) { - min = 0L; - } - return min.intValue(); + /** + * Finds the minimum value in a Value Checker type. If there is no information (such as when the + * list of possible values is empty or null), returns null. Otherwise, returns the smallest value + * in the list of possible values. + */ + public static @Nullable Long getMinValue(Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null) { + return possibleValues.from; + } else { + return null; } + } - /** - * Queries the Value Checker to determine if there is a known minimum length for the array - * represented by {@code tree}. If not, returns 0. - */ - public static int getMinLen(Tree tree, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { - AnnotatedTypeMirror minLenType = valueAnnotatedTypeFactory.getAnnotatedType(tree); - return valueAnnotatedTypeFactory.getMinLenValue(minLenType); + /** + * Finds the maximum value in a Value Checker type. If there is no information (such as when the + * list of possible values is empty or null), returns null. Otherwise, returns the smallest value + * in the list of possible values. + */ + public static @Nullable Long getMaxValue(Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null) { + return possibleValues.to; + } else { + return null; } + } - /** - * Optimize the given JavaExpression. See {@link JavaExpressionOptimizer} for more details. - * - * @param je the expression to optimize - * @param factory the annotated type factory - * @return an optimized version of the argument - */ - public static JavaExpression optimize(JavaExpression je, AnnotatedTypeFactory factory) { - ValueAnnotatedTypeFactory vatf = - ((GenericAnnotatedTypeFactory) factory) - .getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); - return new JavaExpressionOptimizer(vatf == null ? factory : vatf).convert(je); + /** + * Looks up the minlen of a member select tree. The tree must be an access to a sequence length. + */ + public static @Nullable Integer getMinLenFromTree(Tree tree, ValueAnnotatedTypeFactory valueATF) { + AnnotatedTypeMirror minLenType = valueATF.getAnnotatedType(tree); + Long min = valueATF.getMinimumIntegralValue(minLenType); + if (min == null) { + return null; } + if (min < 0 || min > Integer.MAX_VALUE) { + min = 0L; + } + return min.intValue(); + } + + /** + * Queries the Value Checker to determine if there is a known minimum length for the array + * represented by {@code tree}. If not, returns 0. + */ + public static int getMinLen(Tree tree, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { + AnnotatedTypeMirror minLenType = valueAnnotatedTypeFactory.getAnnotatedType(tree); + return valueAnnotatedTypeFactory.getMinLenValue(minLenType); + } + + /** + * Optimize the given JavaExpression. See {@link JavaExpressionOptimizer} for more details. + * + * @param je the expression to optimize + * @param factory the annotated type factory + * @return an optimized version of the argument + */ + public static JavaExpression optimize(JavaExpression je, AnnotatedTypeFactory factory) { + ValueAnnotatedTypeFactory vatf = + ((GenericAnnotatedTypeFactory) factory) + .getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); + return new JavaExpressionOptimizer(vatf == null ? factory : vatf).convert(je); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java index 262ca839d7c..0338c2362cb 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java @@ -1,151 +1,147 @@ package org.checkerframework.common.value; import com.sun.source.tree.Tree; - -import org.checkerframework.javacutil.TreeUtils; - import java.util.List; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.javacutil.TreeUtils; /** Stores methods that have special handling in the value checker. */ class ValueMethodIdentifier { - /** String.length() method. */ - private final ExecutableElement lengthMethod; - - /** Array.getLength() method. */ - private final ExecutableElement getLengthMethod; - - /** String.startsWith(String) method. */ - private final ExecutableElement startsWithMethod; - - /** String.endsWith(String) method. */ - private final ExecutableElement endsWithMethod; - - /** The {@code java.lang.Math#min()} methods. */ - private final List mathMinMethods; - - /** The {@code java.lang.Math#max()} methods. */ - private final List mathMaxMethods; - - /** Arrays.copyOf() methods. */ - private final List copyOfMethods; - - /** - * Initialize elements with methods that have special handling in the value checker. - * - * @param processingEnv the processing environment - */ - public ValueMethodIdentifier(ProcessingEnvironment processingEnv) { - lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); - getLengthMethod = - TreeUtils.getMethod("java.lang.reflect.Array", "getLength", 1, processingEnv); - startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv); - endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv); - mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); - mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); - copyOfMethods = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv); - copyOfMethods.add(TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); - } - - /** - * Returns true iff the argument is an invocation of Math.min. - * - * @param methodTree a tree - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of Math.min - */ - public boolean isMathMin(Tree methodTree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); - } - - /** - * Returns true iff the argument is an invocation of Math.max. - * - * @param methodTree a tree - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of Math.max - */ - public boolean isMathMax(Tree methodTree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); - } - - /** - * Returns true if a tree is an invocation of the {@code String.length()} method. - * - * @param tree a tree - * @param processingEnv the processing environment - * @return true if a tree is an invocation of the {@code String.length()} method - */ - public boolean isStringLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, lengthMethod, processingEnv); - } - - /** - * Returns true if a tree is an invocation of the {@code Array.getLength()} method. - * - * @param tree tree to check - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of {@code Array.getLength()} method - */ - public boolean isArrayGetLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, getLengthMethod, processingEnv); - } - - /** - * Returns true if a method is the {@code String.length()} method. - * - * @param method the element to check - * @return true iff the argument methid is {@code String.length()} method - */ - public boolean isStringLengthMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(lengthMethod); - } - - /** - * Returns true if a method is the {@code Array.getLength()} method. - * - * @param method the element to check - * @return true iff the argument method is {@code Array.getLength()} method - */ - public boolean isArrayGetLengthMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(getLengthMethod); - } - - /** - * Returns true if a method is the {@code String.startsWith(String)} method. - * - * @param method the element to check - * @return true iff the argument method is {@code String.startsWith(String)} method - */ - public boolean isStartsWithMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(startsWithMethod); - } - - /** - * Returns true if a method is the {@code String.endsWith(String)} method. - * - * @param method a method - * @return true if the method is {@code String.endsWith(String)} - */ - public boolean isEndsWithMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(endsWithMethod); - } - - /** - * Returns true if a tree is an invocation of the {@code Arrays.copyOf()} method. - * - * @param tree tree to check - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of {@code Arrays.copyOf()} method - */ - public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv); - } + /** String.length() method. */ + private final ExecutableElement lengthMethod; + + /** Array.getLength() method. */ + private final ExecutableElement getLengthMethod; + + /** String.startsWith(String) method. */ + private final ExecutableElement startsWithMethod; + + /** String.endsWith(String) method. */ + private final ExecutableElement endsWithMethod; + + /** The {@code java.lang.Math#min()} methods. */ + private final List mathMinMethods; + + /** The {@code java.lang.Math#max()} methods. */ + private final List mathMaxMethods; + + /** Arrays.copyOf() methods. */ + private final List copyOfMethods; + + /** + * Initialize elements with methods that have special handling in the value checker. + * + * @param processingEnv the processing environment + */ + public ValueMethodIdentifier(ProcessingEnvironment processingEnv) { + lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); + getLengthMethod = TreeUtils.getMethod("java.lang.reflect.Array", "getLength", 1, processingEnv); + startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv); + endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv); + mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); + mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); + copyOfMethods = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv); + copyOfMethods.add(TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); + } + + /** + * Returns true iff the argument is an invocation of Math.min. + * + * @param methodTree a tree + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of Math.min + */ + public boolean isMathMin(Tree methodTree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); + } + + /** + * Returns true iff the argument is an invocation of Math.max. + * + * @param methodTree a tree + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of Math.max + */ + public boolean isMathMax(Tree methodTree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); + } + + /** + * Returns true if a tree is an invocation of the {@code String.length()} method. + * + * @param tree a tree + * @param processingEnv the processing environment + * @return true if a tree is an invocation of the {@code String.length()} method + */ + public boolean isStringLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, lengthMethod, processingEnv); + } + + /** + * Returns true if a tree is an invocation of the {@code Array.getLength()} method. + * + * @param tree tree to check + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of {@code Array.getLength()} method + */ + public boolean isArrayGetLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, getLengthMethod, processingEnv); + } + + /** + * Returns true if a method is the {@code String.length()} method. + * + * @param method the element to check + * @return true iff the argument methid is {@code String.length()} method + */ + public boolean isStringLengthMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(lengthMethod); + } + + /** + * Returns true if a method is the {@code Array.getLength()} method. + * + * @param method the element to check + * @return true iff the argument method is {@code Array.getLength()} method + */ + public boolean isArrayGetLengthMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(getLengthMethod); + } + + /** + * Returns true if a method is the {@code String.startsWith(String)} method. + * + * @param method the element to check + * @return true iff the argument method is {@code String.startsWith(String)} method + */ + public boolean isStartsWithMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(startsWithMethod); + } + + /** + * Returns true if a method is the {@code String.endsWith(String)} method. + * + * @param method a method + * @return true if the method is {@code String.endsWith(String)} + */ + public boolean isEndsWithMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(endsWithMethod); + } + + /** + * Returns true if a tree is an invocation of the {@code Arrays.copyOf()} method. + * + * @param tree tree to check + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of {@code Arrays.copyOf()} method + */ + public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java index 86d4b69eb9d..02e37050c99 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java @@ -1,5 +1,11 @@ package org.checkerframework.common.value; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.common.value.util.Range; @@ -9,593 +15,562 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.RegexUtil; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; - /** The qualifier hierarchy for the Value type system. */ final class ValueQualifierHierarchy extends ElementQualifierHierarchy { - // This shadows the same-named field in GenericAnnotatedTypeFactory, but has a more specific - // type. - /** The type factory to use. */ - @SuppressWarnings("HidingField") - private final ValueAnnotatedTypeFactory atypeFactory; - - /** - * Creates a ValueQualifierHierarchy from the given classes. - * - * @param atypeFactory a ValueAnnotatedTypeFactory - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @deprecated use {@link #ValueQualifierHierarchy(Collection, ValueAnnotatedTypeFactory)} which - * has the arguments in the other order - */ - @Deprecated // 2023-05-23 - ValueQualifierHierarchy( - ValueAnnotatedTypeFactory atypeFactory, - Collection> qualifierClasses) { - this(qualifierClasses, atypeFactory); + // This shadows the same-named field in GenericAnnotatedTypeFactory, but has a more specific + // type. + /** The type factory to use. */ + @SuppressWarnings("HidingField") + private final ValueAnnotatedTypeFactory atypeFactory; + + /** + * Creates a ValueQualifierHierarchy from the given classes. + * + * @param atypeFactory a ValueAnnotatedTypeFactory + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @deprecated use {@link #ValueQualifierHierarchy(Collection, ValueAnnotatedTypeFactory)} which + * has the arguments in the other order + */ + @Deprecated // 2023-05-23 + ValueQualifierHierarchy( + ValueAnnotatedTypeFactory atypeFactory, + Collection> qualifierClasses) { + this(qualifierClasses, atypeFactory); + } + + /** + * Creates a ValueQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param atypeFactory the associated type factory + */ + ValueQualifierHierarchy( + Collection> qualifierClasses, + ValueAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, atypeFactory.getElementUtils(), atypeFactory); + this.atypeFactory = atypeFactory; + } + + /** + * Computes greatest lower bound of a @StringVal annotation with another Value Checker annotation. + * + * @param stringValAnno annotation of type @StringVal + * @param otherAnno annotation from the value checker hierarchy + * @return greatest lower bound of {@code stringValAnno} and {@code otherAnno} + */ + private AnnotationMirror glbOfStringVal( + AnnotationMirror stringValAnno, AnnotationMirror otherAnno) { + List values = atypeFactory.getStringValues(stringValAnno); + switch (AnnotationUtils.annotationName(otherAnno)) { + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + // Intersection of value lists + List otherValues = atypeFactory.getStringValues(otherAnno); + values.retainAll(otherValues); + break; + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + // Retain strings of correct lengths + List otherLengths = atypeFactory.getArrayLength(otherAnno); + ArrayList result = new ArrayList<>(values.size()); + for (String s : values) { + if (otherLengths.contains(s.length())) { + result.add(s); + } + } + values = result; + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + // Retain strings of lengths from a range + Range otherRange = atypeFactory.getRange(otherAnno); + ArrayList range = new ArrayList<>(values.size()); + for (String s : values) { + if (otherRange.contains(s.length())) { + range.add(s); + } + } + values = range; + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + List<@Regex String> matchesRegexes = + AnnotationUtils.getElementValueArray( + otherAnno, atypeFactory.matchesRegexValueElement, String.class); + // Retain the @StringVal values such that one of the regexes matches it. + values = RegexUtil.matchesSomeRegex(values, matchesRegexes); + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + List<@Regex String> doesNotMatchRegexes = + AnnotationUtils.getElementValueArray( + otherAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); + // Retain the @StringVal values such that none of the regexes matches it. + values = RegexUtil.matchesNoRegex(values, doesNotMatchRegexes); + break; + default: + return atypeFactory.BOTTOMVAL; } - /** - * Creates a ValueQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param atypeFactory the associated type factory - */ - ValueQualifierHierarchy( - Collection> qualifierClasses, - ValueAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, atypeFactory.getElementUtils(), atypeFactory); - this.atypeFactory = atypeFactory; + return atypeFactory.createStringAnnotation(values); + } + + @Override + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } else { + + // Implementation of GLB where one of the annotations is StringVal is needed for + // length-based refinement of constant string values. Other cases of length-based + // refinement are handled by subtype check. + if (AnnotationUtils.areSameByName(a1, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { + return glbOfStringVal(a1, a2); + } else if (AnnotationUtils.areSameByName(a2, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { + return glbOfStringVal(a2, a1); + } + + // Simply return BOTTOMVAL in other cases. Refine this if we discover use cases + // that need a more precise GLB. + return atypeFactory.BOTTOMVAL; } - - /** - * Computes greatest lower bound of a @StringVal annotation with another Value Checker - * annotation. - * - * @param stringValAnno annotation of type @StringVal - * @param otherAnno annotation from the value checker hierarchy - * @return greatest lower bound of {@code stringValAnno} and {@code otherAnno} - */ - private AnnotationMirror glbOfStringVal( - AnnotationMirror stringValAnno, AnnotationMirror otherAnno) { - List values = atypeFactory.getStringValues(stringValAnno); - switch (AnnotationUtils.annotationName(otherAnno)) { - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - // Intersection of value lists - List otherValues = atypeFactory.getStringValues(otherAnno); - values.retainAll(otherValues); - break; - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - // Retain strings of correct lengths - List otherLengths = atypeFactory.getArrayLength(otherAnno); - ArrayList result = new ArrayList<>(values.size()); - for (String s : values) { - if (otherLengths.contains(s.length())) { - result.add(s); - } - } - values = result; - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - // Retain strings of lengths from a range - Range otherRange = atypeFactory.getRange(otherAnno); - ArrayList range = new ArrayList<>(values.size()); - for (String s : values) { - if (otherRange.contains(s.length())) { - range.add(s); - } - } - values = range; - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - List<@Regex String> matchesRegexes = - AnnotationUtils.getElementValueArray( - otherAnno, atypeFactory.matchesRegexValueElement, String.class); - // Retain the @StringVal values such that one of the regexes matches it. - values = RegexUtil.matchesSomeRegex(values, matchesRegexes); - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - List<@Regex String> doesNotMatchRegexes = - AnnotationUtils.getElementValueArray( - otherAnno, - atypeFactory.doesNotMatchRegexValueElement, - String.class); - // Retain the @StringVal values such that none of the regexes matches it. - values = RegexUtil.matchesNoRegex(values, doesNotMatchRegexes); - break; - default: - return atypeFactory.BOTTOMVAL; - } - - return atypeFactory.createStringAnnotation(values); + } + + @Override + public int numberOfIterationsBeforeWidening() { + return ValueAnnotatedTypeFactory.MAX_VALUES + 1; + } + + @Override + public AnnotationMirror widenedUpperBound( + AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { + AnnotationMirror lub = leastUpperBoundQualifiers(newQualifier, previousQualifier); + if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { + Range lubRange = atypeFactory.getRange(lub); + Range newRange = atypeFactory.getRange(newQualifier); + Range oldRange = atypeFactory.getRange(previousQualifier); + Range wubRange = widenedRange(newRange, oldRange, lubRange); + return atypeFactory.createIntRangeAnnotation(wubRange); + } else if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + Range lubRange = atypeFactory.getRange(lub); + Range newRange = atypeFactory.getRange(newQualifier); + Range oldRange = atypeFactory.getRange(previousQualifier); + Range wubRange = widenedRange(newRange, oldRange, lubRange); + return atypeFactory.createArrayLenRangeAnnotation(wubRange); + } else { + return lub; } - - @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } else { - - // Implementation of GLB where one of the annotations is StringVal is needed for - // length-based refinement of constant string values. Other cases of length-based - // refinement are handled by subtype check. - if (AnnotationUtils.areSameByName(a1, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { - return glbOfStringVal(a1, a2); - } else if (AnnotationUtils.areSameByName( - a2, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { - return glbOfStringVal(a2, a1); - } - - // Simply return BOTTOMVAL in other cases. Refine this if we discover use cases - // that need a more precise GLB. - return atypeFactory.BOTTOMVAL; - } + } + + /** + * Determine the widened range from other ranges. + * + * @param newRange the new range + * @param oldRange the old range + * @param lubRange the LUB range + * @return the widened range + */ + private Range widenedRange(Range newRange, Range oldRange, Range lubRange) { + if (newRange == null || oldRange == null || lubRange.equals(oldRange)) { + return lubRange; } - - @Override - public int numberOfIterationsBeforeWidening() { - return ValueAnnotatedTypeFactory.MAX_VALUES + 1; + // If both bounds of the new range are bigger than the old range, then returned range + // should use the lower bound of the new range and a MAX_VALUE. + if ((newRange.from >= oldRange.from && newRange.to >= oldRange.to)) { + long max = lubRange.to; + if (max < Byte.MAX_VALUE) { + max = Byte.MAX_VALUE; + } else if (max < Short.MAX_VALUE) { + max = Short.MAX_VALUE; + } else if (max < Integer.MAX_VALUE) { + max = Integer.MAX_VALUE; + } else { + max = Long.MAX_VALUE; + } + return Range.create(newRange.from, max); } - @Override - public AnnotationMirror widenedUpperBound( - AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { - AnnotationMirror lub = leastUpperBoundQualifiers(newQualifier, previousQualifier); - if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { - Range lubRange = atypeFactory.getRange(lub); - Range newRange = atypeFactory.getRange(newQualifier); - Range oldRange = atypeFactory.getRange(previousQualifier); - Range wubRange = widenedRange(newRange, oldRange, lubRange); - return atypeFactory.createIntRangeAnnotation(wubRange); - } else if (AnnotationUtils.areSameByName( - lub, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - Range lubRange = atypeFactory.getRange(lub); - Range newRange = atypeFactory.getRange(newQualifier); - Range oldRange = atypeFactory.getRange(previousQualifier); - Range wubRange = widenedRange(newRange, oldRange, lubRange); - return atypeFactory.createArrayLenRangeAnnotation(wubRange); - } else { - return lub; - } + // If both bounds of the old range are bigger than the new range, then returned range + // should use a MIN_VALUE and the upper bound of the new range. + if ((newRange.from <= oldRange.from && newRange.to <= oldRange.to)) { + long min = lubRange.from; + if (min > Byte.MIN_VALUE) { + min = Byte.MIN_VALUE; + } else if (min > Short.MIN_VALUE) { + min = Short.MIN_VALUE; + } else if (min > Integer.MIN_VALUE) { + min = Integer.MIN_VALUE; + } else { + min = Long.MIN_VALUE; + } + return Range.create(min, newRange.to); } - /** - * Determine the widened range from other ranges. - * - * @param newRange the new range - * @param oldRange the old range - * @param lubRange the LUB range - * @return the widened range - */ - private Range widenedRange(Range newRange, Range oldRange, Range lubRange) { - if (newRange == null || oldRange == null || lubRange.equals(oldRange)) { - return lubRange; - } - // If both bounds of the new range are bigger than the old range, then returned range - // should use the lower bound of the new range and a MAX_VALUE. - if ((newRange.from >= oldRange.from && newRange.to >= oldRange.to)) { - long max = lubRange.to; - if (max < Byte.MAX_VALUE) { - max = Byte.MAX_VALUE; - } else if (max < Short.MAX_VALUE) { - max = Short.MAX_VALUE; - } else if (max < Integer.MAX_VALUE) { - max = Integer.MAX_VALUE; - } else { - max = Long.MAX_VALUE; - } - return Range.create(newRange.from, max); - } - - // If both bounds of the old range are bigger than the new range, then returned range - // should use a MIN_VALUE and the upper bound of the new range. - if ((newRange.from <= oldRange.from && newRange.to <= oldRange.to)) { - long min = lubRange.from; - if (min > Byte.MIN_VALUE) { - min = Byte.MIN_VALUE; - } else if (min > Short.MIN_VALUE) { - min = Short.MIN_VALUE; - } else if (min > Integer.MIN_VALUE) { - min = Integer.MIN_VALUE; - } else { - min = Long.MIN_VALUE; - } - return Range.create(min, newRange.to); - } - - if (lubRange.isWithin(Byte.MIN_VALUE + 1, Byte.MAX_VALUE) - || lubRange.isWithin(Byte.MIN_VALUE, Byte.MAX_VALUE - 1)) { - return Range.BYTE_EVERYTHING; - } else if (lubRange.isWithin(Short.MIN_VALUE + 1, Short.MAX_VALUE) - || lubRange.isWithin(Short.MIN_VALUE, Short.MAX_VALUE - 1)) { - return Range.SHORT_EVERYTHING; - } else if (lubRange.isWithin(Long.MIN_VALUE + 1, Long.MAX_VALUE) - || lubRange.isWithin(Long.MIN_VALUE, Long.MAX_VALUE - 1)) { - return Range.INT_EVERYTHING; - } else { - return Range.EVERYTHING; - } + if (lubRange.isWithin(Byte.MIN_VALUE + 1, Byte.MAX_VALUE) + || lubRange.isWithin(Byte.MIN_VALUE, Byte.MAX_VALUE - 1)) { + return Range.BYTE_EVERYTHING; + } else if (lubRange.isWithin(Short.MIN_VALUE + 1, Short.MAX_VALUE) + || lubRange.isWithin(Short.MIN_VALUE, Short.MAX_VALUE - 1)) { + return Range.SHORT_EVERYTHING; + } else if (lubRange.isWithin(Long.MIN_VALUE + 1, Long.MAX_VALUE) + || lubRange.isWithin(Long.MIN_VALUE, Long.MAX_VALUE - 1)) { + return Range.INT_EVERYTHING; + } else { + return Range.EVERYTHING; + } + } + + /** + * Determines the least upper bound of a1 and a2, which contains the union of their sets of + * possible values. + * + * @return the least upper bound of a1 and a2 + */ + @Override + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + // The annotations are in different hierarchies + return null; } - /** - * Determines the least upper bound of a1 and a2, which contains the union of their sets of - * possible values. - * - * @return the least upper bound of a1 and a2 - */ - @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - // The annotations are in different hierarchies - return null; - } - - a1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a1); - a2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a2); - - if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } - String qual1 = AnnotationUtils.annotationName(a1); - String qual2 = AnnotationUtils.annotationName(a2); - - if (qual1.equals(qual2)) { - // If both are the same type, determine the type and merge - switch (qual1) { - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - // special handling for IntRange - Range intrange1 = atypeFactory.getRange(a1); - Range intrange2 = atypeFactory.getRange(a2); - return atypeFactory.createIntRangeAnnotation(intrange1.union(intrange2)); - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - // special handling for ArrayLenRange - Range range1 = atypeFactory.getRange(a1); - Range range2 = atypeFactory.getRange(a2); - return atypeFactory.createArrayLenRangeAnnotation(range1.union(range2)); - case ValueAnnotatedTypeFactory.INTVAL_NAME: - List longs = atypeFactory.getIntValues(a1); - CollectionsPlume.adjoinAll(longs, atypeFactory.getIntValues(a2)); - return atypeFactory.createIntValAnnotation(longs); - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - List arrayLens = atypeFactory.getArrayLength(a1); - CollectionsPlume.adjoinAll(arrayLens, atypeFactory.getArrayLength(a2)); - return atypeFactory.createArrayLenAnnotation(arrayLens); - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - List strings = atypeFactory.getStringValues(a1); - CollectionsPlume.adjoinAll(strings, atypeFactory.getStringValues(a2)); - return atypeFactory.createStringAnnotation(strings); - case ValueAnnotatedTypeFactory.BOOLVAL_NAME: - List bools = atypeFactory.getBooleanValues(a1); - CollectionsPlume.adjoinAll(bools, atypeFactory.getBooleanValues(a2)); - return atypeFactory.createBooleanAnnotation(bools); - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - List doubles = atypeFactory.getDoubleValues(a1); - CollectionsPlume.adjoinAll(doubles, atypeFactory.getDoubleValues(a2)); - return atypeFactory.createDoubleAnnotation(doubles); - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - List<@Regex String> regexes = atypeFactory.getMatchesRegexValues(a1); - CollectionsPlume.adjoinAll(regexes, atypeFactory.getMatchesRegexValues(a2)); - return atypeFactory.createMatchesRegexAnnotation(regexes); - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - // The LUB is the intersection of the sets. - List<@Regex String> regexes1 = atypeFactory.getDoesNotMatchRegexValues(a1); - List<@Regex String> regexes2 = atypeFactory.getDoesNotMatchRegexValues(a2); - regexes1.retainAll(regexes2); - return atypeFactory.createDoesNotMatchRegexAnnotation(regexes1); - default: - throw new TypeSystemError("default case: %s %s %s%n", qual1, a1, a2); - } - } - - // Special handling for dealing with the lub of two annotations that are distinct but - // convertible (e.g. a StringVal and a MatchesRegex, or an IntVal and an IntRange). - // Each of these variables is an annotation of the given type, or is null if neither of - // the arguments to leastUpperBound is of the given types. - AnnotationMirror arrayLenAnno = null; - AnnotationMirror arrayLenRangeAnno = null; - AnnotationMirror stringValAnno = null; - AnnotationMirror matchesRegexAnno = null; - AnnotationMirror doesNotMatchRegexAnno = null; - AnnotationMirror intValAnno = null; - AnnotationMirror intRangeAnno = null; - AnnotationMirror doubleValAnno = null; - - switch (qual1) { - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - arrayLenAnno = a1; - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - arrayLenRangeAnno = a1; - break; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - stringValAnno = a1; - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - matchesRegexAnno = a1; - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - doesNotMatchRegexAnno = a1; - break; - case ValueAnnotatedTypeFactory.INTVAL_NAME: - intValAnno = a1; - break; - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - intRangeAnno = a1; - break; - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - doubleValAnno = a1; - break; - default: - // Do nothing - } - - switch (qual2) { - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - arrayLenAnno = a2; - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - arrayLenRangeAnno = a2; - break; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - stringValAnno = a2; - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - matchesRegexAnno = a2; - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - doesNotMatchRegexAnno = a2; - break; - case ValueAnnotatedTypeFactory.INTVAL_NAME: - intValAnno = a2; - break; - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - intRangeAnno = a2; - break; - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - doubleValAnno = a2; - break; - default: - // Do nothing - } + a1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a1); + a2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a2); - // Special handling for dealing with the lub of an ArrayLenRange and an ArrayLen, - // a StringVal with one of them, or a StringVal and a MatchesRegex. - // Each of these converts one annotation to the other, then makes a recursive call. - if (arrayLenAnno != null && arrayLenRangeAnno != null) { - return leastUpperBoundQualifiers( - arrayLenRangeAnno, atypeFactory.convertArrayLenToArrayLenRange(arrayLenAnno)); - } else if (stringValAnno != null && arrayLenAnno != null) { - return leastUpperBoundQualifiers( - arrayLenAnno, atypeFactory.convertStringValToArrayLen(stringValAnno)); - } else if (stringValAnno != null && arrayLenRangeAnno != null) { - return leastUpperBoundQualifiers( - arrayLenRangeAnno, atypeFactory.convertStringValToArrayLenRange(stringValAnno)); - } else if (stringValAnno != null && matchesRegexAnno != null) { - return leastUpperBoundQualifiers( - matchesRegexAnno, atypeFactory.convertStringValToMatchesRegex(stringValAnno)); - } + if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } + String qual1 = AnnotationUtils.annotationName(a1); + String qual2 = AnnotationUtils.annotationName(a2); + + if (qual1.equals(qual2)) { + // If both are the same type, determine the type and merge + switch (qual1) { + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + // special handling for IntRange + Range intrange1 = atypeFactory.getRange(a1); + Range intrange2 = atypeFactory.getRange(a2); + return atypeFactory.createIntRangeAnnotation(intrange1.union(intrange2)); + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + // special handling for ArrayLenRange + Range range1 = atypeFactory.getRange(a1); + Range range2 = atypeFactory.getRange(a2); + return atypeFactory.createArrayLenRangeAnnotation(range1.union(range2)); + case ValueAnnotatedTypeFactory.INTVAL_NAME: + List longs = atypeFactory.getIntValues(a1); + CollectionsPlume.adjoinAll(longs, atypeFactory.getIntValues(a2)); + return atypeFactory.createIntValAnnotation(longs); + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + List arrayLens = atypeFactory.getArrayLength(a1); + CollectionsPlume.adjoinAll(arrayLens, atypeFactory.getArrayLength(a2)); + return atypeFactory.createArrayLenAnnotation(arrayLens); + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + List strings = atypeFactory.getStringValues(a1); + CollectionsPlume.adjoinAll(strings, atypeFactory.getStringValues(a2)); + return atypeFactory.createStringAnnotation(strings); + case ValueAnnotatedTypeFactory.BOOLVAL_NAME: + List bools = atypeFactory.getBooleanValues(a1); + CollectionsPlume.adjoinAll(bools, atypeFactory.getBooleanValues(a2)); + return atypeFactory.createBooleanAnnotation(bools); + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + List doubles = atypeFactory.getDoubleValues(a1); + CollectionsPlume.adjoinAll(doubles, atypeFactory.getDoubleValues(a2)); + return atypeFactory.createDoubleAnnotation(doubles); + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + List<@Regex String> regexes = atypeFactory.getMatchesRegexValues(a1); + CollectionsPlume.adjoinAll(regexes, atypeFactory.getMatchesRegexValues(a2)); + return atypeFactory.createMatchesRegexAnnotation(regexes); + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + // The LUB is the intersection of the sets. + List<@Regex String> regexes1 = atypeFactory.getDoesNotMatchRegexValues(a1); + List<@Regex String> regexes2 = atypeFactory.getDoesNotMatchRegexValues(a2); + regexes1.retainAll(regexes2); + return atypeFactory.createDoesNotMatchRegexAnnotation(regexes1); + default: + throw new TypeSystemError("default case: %s %s %s%n", qual1, a1, a2); + } + } - if (stringValAnno != null && doesNotMatchRegexAnno != null) { - // The lub is either doesNotMatchRegexAnno or UNKNOWNVAL. - List stringVals = atypeFactory.getStringValues(stringValAnno); - List<@Regex String> regexes = - AnnotationUtils.getElementValueArray( - doesNotMatchRegexAnno, - atypeFactory.doesNotMatchRegexValueElement, - String.class); - if (RegexUtil.everyStringMatchesSomeRegex(stringVals, regexes)) { - return atypeFactory.UNKNOWNVAL; - } - return doesNotMatchRegexAnno; - } + // Special handling for dealing with the lub of two annotations that are distinct but + // convertible (e.g. a StringVal and a MatchesRegex, or an IntVal and an IntRange). + // Each of these variables is an annotation of the given type, or is null if neither of + // the arguments to leastUpperBound is of the given types. + AnnotationMirror arrayLenAnno = null; + AnnotationMirror arrayLenRangeAnno = null; + AnnotationMirror stringValAnno = null; + AnnotationMirror matchesRegexAnno = null; + AnnotationMirror doesNotMatchRegexAnno = null; + AnnotationMirror intValAnno = null; + AnnotationMirror intRangeAnno = null; + AnnotationMirror doubleValAnno = null; + + switch (qual1) { + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + arrayLenAnno = a1; + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + arrayLenRangeAnno = a1; + break; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + stringValAnno = a1; + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + matchesRegexAnno = a1; + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + doesNotMatchRegexAnno = a1; + break; + case ValueAnnotatedTypeFactory.INTVAL_NAME: + intValAnno = a1; + break; + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + intRangeAnno = a1; + break; + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + doubleValAnno = a1; + break; + default: + // Do nothing + } - // Annotations are both in the same hierarchy, but they are not the same. - // If a1 and a2 are not the same type of *Value annotation, they may still be mergeable - // because some values can be implicitly cast as others. For example, if a1 and a2 are - // both in {DoubleVal, IntVal} then they will be converted upwards: IntVal -> DoubleVal - // to arrive at a common annotation type. + switch (qual2) { + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + arrayLenAnno = a2; + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + arrayLenRangeAnno = a2; + break; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + stringValAnno = a2; + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + matchesRegexAnno = a2; + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + doesNotMatchRegexAnno = a2; + break; + case ValueAnnotatedTypeFactory.INTVAL_NAME: + intValAnno = a2; + break; + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + intRangeAnno = a2; + break; + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + doubleValAnno = a2; + break; + default: + // Do nothing + } - if (doubleValAnno != null) { - if (intRangeAnno != null) { - intValAnno = atypeFactory.convertIntRangeToIntVal(intRangeAnno); - if (AnnotationUtils.areSameByName( - intValAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - intValAnno = null; - } - } - if (intValAnno != null) { - // Convert intValAnno to a @DoubleVal AnnotationMirror - AnnotationMirror doubleValAnno2 = atypeFactory.convertIntValToDoubleVal(intValAnno); - return leastUpperBoundQualifiers(doubleValAnno, doubleValAnno2); - } - return atypeFactory.UNKNOWNVAL; - } - if (intRangeAnno != null && intValAnno != null) { - // Convert intValAnno to an @IntRange AnnotationMirror - AnnotationMirror intRangeAnno2 = atypeFactory.convertIntValToIntRange(intValAnno); - return leastUpperBoundQualifiers(intRangeAnno, intRangeAnno2); - } + // Special handling for dealing with the lub of an ArrayLenRange and an ArrayLen, + // a StringVal with one of them, or a StringVal and a MatchesRegex. + // Each of these converts one annotation to the other, then makes a recursive call. + if (arrayLenAnno != null && arrayLenRangeAnno != null) { + return leastUpperBoundQualifiers( + arrayLenRangeAnno, atypeFactory.convertArrayLenToArrayLenRange(arrayLenAnno)); + } else if (stringValAnno != null && arrayLenAnno != null) { + return leastUpperBoundQualifiers( + arrayLenAnno, atypeFactory.convertStringValToArrayLen(stringValAnno)); + } else if (stringValAnno != null && arrayLenRangeAnno != null) { + return leastUpperBoundQualifiers( + arrayLenRangeAnno, atypeFactory.convertStringValToArrayLenRange(stringValAnno)); + } else if (stringValAnno != null && matchesRegexAnno != null) { + return leastUpperBoundQualifiers( + matchesRegexAnno, atypeFactory.convertStringValToMatchesRegex(stringValAnno)); + } - // In all other cases, the LUB is UnknownVal. + if (stringValAnno != null && doesNotMatchRegexAnno != null) { + // The lub is either doesNotMatchRegexAnno or UNKNOWNVAL. + List stringVals = atypeFactory.getStringValues(stringValAnno); + List<@Regex String> regexes = + AnnotationUtils.getElementValueArray( + doesNotMatchRegexAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); + if (RegexUtil.everyStringMatchesSomeRegex(stringVals, regexes)) { return atypeFactory.UNKNOWNVAL; + } + return doesNotMatchRegexAnno; } - @Override - public boolean isSubtypeShallow( - AnnotationMirror subQualifier, - TypeMirror subType, - AnnotationMirror superQualifier, - TypeMirror superType) { - subQualifier = atypeFactory.convertSpecialIntRangeToStandardIntRange(subQualifier, subType); - superQualifier = - atypeFactory.convertSpecialIntRangeToStandardIntRange(superQualifier, superType); - return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); + // Annotations are both in the same hierarchy, but they are not the same. + // If a1 and a2 are not the same type of *Value annotation, they may still be mergeable + // because some values can be implicitly cast as others. For example, if a1 and a2 are + // both in {DoubleVal, IntVal} then they will be converted upwards: IntVal -> DoubleVal + // to arrive at a common annotation type. + + if (doubleValAnno != null) { + if (intRangeAnno != null) { + intValAnno = atypeFactory.convertIntRangeToIntVal(intRangeAnno); + if (AnnotationUtils.areSameByName(intValAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + intValAnno = null; + } + } + if (intValAnno != null) { + // Convert intValAnno to a @DoubleVal AnnotationMirror + AnnotationMirror doubleValAnno2 = atypeFactory.convertIntValToDoubleVal(intValAnno); + return leastUpperBoundQualifiers(doubleValAnno, doubleValAnno2); + } + return atypeFactory.UNKNOWNVAL; } - - @Override - public @Nullable AnnotationMirror leastUpperBoundShallow( - AnnotationMirror qualifier1, - TypeMirror tm1, - AnnotationMirror qualifier2, - TypeMirror tm2) { - qualifier1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier1, tm1); - qualifier2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier2, tm2); - return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); + if (intRangeAnno != null && intValAnno != null) { + // Convert intValAnno to an @IntRange AnnotationMirror + AnnotationMirror intRangeAnno2 = atypeFactory.convertIntValToIntRange(intValAnno); + return leastUpperBoundQualifiers(intRangeAnno, intRangeAnno2); } - /** - * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both - * annotations are Value. In this case, subAnno is a subtype of superAnno iff superAnno contains - * at least every element of subAnno. - * - * @return true if subAnno is a subtype of superAnno, false otherwise - */ - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno); - superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno); - String subQualName = AnnotationUtils.annotationName(subAnno); - if (subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - superAnno = atypeFactory.convertToUnknown(superAnno); + // In all other cases, the LUB is UnknownVal. + return atypeFactory.UNKNOWNVAL; + } + + @Override + public boolean isSubtypeShallow( + AnnotationMirror subQualifier, + TypeMirror subType, + AnnotationMirror superQualifier, + TypeMirror superType) { + subQualifier = atypeFactory.convertSpecialIntRangeToStandardIntRange(subQualifier, subType); + superQualifier = + atypeFactory.convertSpecialIntRangeToStandardIntRange(superQualifier, superType); + return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); + } + + @Override + public @Nullable AnnotationMirror leastUpperBoundShallow( + AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { + qualifier1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier1, tm1); + qualifier2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier2, tm2); + return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); + } + + /** + * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both + * annotations are Value. In this case, subAnno is a subtype of superAnno iff superAnno contains + * at least every element of subAnno. + * + * @return true if subAnno is a subtype of superAnno, false otherwise + */ + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno); + superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno); + String subQualName = AnnotationUtils.annotationName(subAnno); + if (subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + superAnno = atypeFactory.convertToUnknown(superAnno); + } + String superQualName = AnnotationUtils.annotationName(superAnno); + if (superQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME) + || subQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return true; + } else if (superQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME) + || subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + return false; + } else if (superQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { + return subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME); + } else if (subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { + return false; + } else if (superQualName.equals(subQualName)) { + // Same annotation name, so might be subtype + if (subQualName.equals(ValueAnnotatedTypeFactory.INTRANGE_NAME) + || subQualName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + // Special case for range-based annotations + Range superRange = atypeFactory.getRange(superAnno); + Range subRange = atypeFactory.getRange(subAnno); + return superRange.contains(subRange); + } else if (subQualName.equals(ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { + List superValues = + AnnotationUtils.getElementValueArray( + superAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); + List subValues = + AnnotationUtils.getElementValueArray( + subAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); + return subValues.containsAll(superValues); + } else { + // The annotations have the same name, which is one of: + // ArrayLen, BoolVal, DoubleVal, EnumVal, StringVal, MatchesRegex. + @SuppressWarnings("deprecation") // concrete annotation class is not known + List superValues = + AnnotationUtils.getElementValueArray(superAnno, "value", Object.class, false); + @SuppressWarnings("deprecation") // concrete annotation class is not known + List subValues = + AnnotationUtils.getElementValueArray(subAnno, "value", Object.class, false); + return superValues.containsAll(subValues); + } + } + switch (subQualName + superQualName) { + case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + List superValues = atypeFactory.getDoubleValues(superAnno); + List subValues = + atypeFactory.convertLongListToDoubleList(atypeFactory.getIntValues(subAnno)); + return superValues.containsAll(subValues); + case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.INTRANGE_NAME: + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + Range superRange = atypeFactory.getRange(superAnno); + List subLongValues = atypeFactory.getArrayLenOrIntValue(subAnno); + Range subLongRange = Range.create(subLongValues); + return superRange.contains(subLongRange); + case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + Range subRange = atypeFactory.getRange(subAnno); + if (subRange.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + return false; } - String superQualName = AnnotationUtils.annotationName(superAnno); - if (superQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME) - || subQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return true; - } else if (superQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME) - || subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - return false; - } else if (superQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { - return subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME); - } else if (subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { + List superDoubleValues = atypeFactory.getDoubleValues(superAnno); + List subDoubleValues = ValueCheckerUtils.getValuesFromRange(subRange, Double.class); + return superDoubleValues.containsAll(subDoubleValues); + case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.INTVAL_NAME: + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + Range subRange2 = atypeFactory.getRange(subAnno); + if (subRange2.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + return false; + } + List superValues2 = atypeFactory.getArrayLenOrIntValue(superAnno); + List subValues2 = ValueCheckerUtils.getValuesFromRange(subRange2, Long.class); + return superValues2.containsAll(subValues2); + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME: + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME: + + // Allow @ArrayLen(0) to be converted to @StringVal("") + List superStringValues = atypeFactory.getStringValues(superAnno); + return superStringValues.contains("") && atypeFactory.getMaxLenValue(subAnno) == 0; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + { + List strings = atypeFactory.getStringValues(subAnno); + List regexes = + AnnotationUtils.getElementValueArray( + superAnno, atypeFactory.matchesRegexValueElement, String.class); + return RegexUtil.everyStringMatchesSomeRegex(strings, regexes); + } + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + + ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + { + List strings = atypeFactory.getStringValues(subAnno); + List regexes = + AnnotationUtils.getElementValueArray( + superAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); + return RegexUtil.noStringMatchesAnyRegex(strings, regexes); + } + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + // StringVal is a subtype of ArrayLen, if all the strings have one of the correct + // lengths. + List superIntValues = atypeFactory.getArrayLength(superAnno); + List subStringValues = atypeFactory.getStringValues(subAnno); + for (String value : subStringValues) { + if (!superIntValues.contains(value.length())) { return false; - } else if (superQualName.equals(subQualName)) { - // Same annotation name, so might be subtype - if (subQualName.equals(ValueAnnotatedTypeFactory.INTRANGE_NAME) - || subQualName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - // Special case for range-based annotations - Range superRange = atypeFactory.getRange(superAnno); - Range subRange = atypeFactory.getRange(subAnno); - return superRange.contains(subRange); - } else if (subQualName.equals(ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { - List superValues = - AnnotationUtils.getElementValueArray( - superAnno, - atypeFactory.doesNotMatchRegexValueElement, - String.class); - List subValues = - AnnotationUtils.getElementValueArray( - subAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); - return subValues.containsAll(superValues); - } else { - // The annotations have the same name, which is one of: - // ArrayLen, BoolVal, DoubleVal, EnumVal, StringVal, MatchesRegex. - @SuppressWarnings("deprecation") // concrete annotation class is not known - List superValues = - AnnotationUtils.getElementValueArray( - superAnno, "value", Object.class, false); - @SuppressWarnings("deprecation") // concrete annotation class is not known - List subValues = - AnnotationUtils.getElementValueArray(subAnno, "value", Object.class, false); - return superValues.containsAll(subValues); - } + } } - switch (subQualName + superQualName) { - case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - List superValues = atypeFactory.getDoubleValues(superAnno); - List subValues = - atypeFactory.convertLongListToDoubleList( - atypeFactory.getIntValues(subAnno)); - return superValues.containsAll(subValues); - case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.INTRANGE_NAME: - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME - + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - Range superRange = atypeFactory.getRange(superAnno); - List subLongValues = atypeFactory.getArrayLenOrIntValue(subAnno); - Range subLongRange = Range.create(subLongValues); - return superRange.contains(subLongRange); - case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - Range subRange = atypeFactory.getRange(subAnno); - if (subRange.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - return false; - } - List superDoubleValues = atypeFactory.getDoubleValues(superAnno); - List subDoubleValues = - ValueCheckerUtils.getValuesFromRange(subRange, Double.class); - return superDoubleValues.containsAll(subDoubleValues); - case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.INTVAL_NAME: - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME - + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - Range subRange2 = atypeFactory.getRange(subAnno); - if (subRange2.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - return false; - } - List superValues2 = atypeFactory.getArrayLenOrIntValue(superAnno); - List subValues2 = ValueCheckerUtils.getValuesFromRange(subRange2, Long.class); - return superValues2.containsAll(subValues2); - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME - + ValueAnnotatedTypeFactory.STRINGVAL_NAME: - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME: - - // Allow @ArrayLen(0) to be converted to @StringVal("") - List superStringValues = atypeFactory.getStringValues(superAnno); - return superStringValues.contains("") && atypeFactory.getMaxLenValue(subAnno) == 0; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME - + ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - { - List strings = atypeFactory.getStringValues(subAnno); - List regexes = - AnnotationUtils.getElementValueArray( - superAnno, atypeFactory.matchesRegexValueElement, String.class); - return RegexUtil.everyStringMatchesSomeRegex(strings, regexes); - } - case ValueAnnotatedTypeFactory.STRINGVAL_NAME - + ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - { - List strings = atypeFactory.getStringValues(subAnno); - List regexes = - AnnotationUtils.getElementValueArray( - superAnno, - atypeFactory.doesNotMatchRegexValueElement, - String.class); - return RegexUtil.noStringMatchesAnyRegex(strings, regexes); - } - case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - // StringVal is a subtype of ArrayLen, if all the strings have one of the correct - // lengths. - List superIntValues = atypeFactory.getArrayLength(superAnno); - List subStringValues = atypeFactory.getStringValues(subAnno); - for (String value : subStringValues) { - if (!superIntValues.contains(value.length())) { - return false; - } - } - return true; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME - + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - // StringVal is a subtype of ArrayLenRange, if all the strings have a length in the - // range. - Range superRange2 = atypeFactory.getRange(superAnno); - List subValues3 = atypeFactory.getStringValues(subAnno); - for (String value : subValues3) { - if (!superRange2.contains(value.length())) { - return false; - } - } - return true; - default: - return false; + return true; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + // StringVal is a subtype of ArrayLenRange, if all the strings have a length in the + // range. + Range superRange2 = atypeFactory.getRange(superAnno); + List subValues3 = atypeFactory.getStringValues(subAnno); + for (String value : subValues3) { + if (!superRange2.contains(value.length())) { + return false; + } } + return true; + default: + return false; } + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java index 6db49f108a6..4b19ed0da65 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java @@ -1,7 +1,15 @@ package org.checkerframework.common.value; import com.sun.source.tree.ExpressionTree; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.ArrayLenRange; @@ -61,1660 +69,1607 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** The transfer class for the Value Checker. */ public class ValueTransfer extends CFTransfer { - /** The Value type factory. */ - protected final ValueAnnotatedTypeFactory atypeFactory; - - /** The Value qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; - - /** True if -AnonNullStringsConcatenation was passed on the command line. */ - private final boolean nonNullStringsConcatenation; - - /** - * Create a new ValueTransfer. - * - * @param analysis the corresponding analysis - */ - public ValueTransfer(CFAbstractAnalysis analysis) { - super(analysis); - atypeFactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory(); - qualHierarchy = atypeFactory.getQualifierHierarchy(); - nonNullStringsConcatenation = - atypeFactory.getChecker().hasOption("nonNullStringsConcatenation"); - } - - /** Returns a range of possible lengths for an integer from a range, as casted to a String. */ - private Range getIntRangeStringLengthRange(Node subNode, TransferInput p) { - Range valueRange = getIntRange(subNode, p); - - // Get lengths of the bounds - int fromLength = Long.toString(valueRange.from).length(); - int toLength = Long.toString(valueRange.to).length(); - - int lowerLength = Math.min(fromLength, toLength); - // In case the range contains 0, the minimum length is 1 even if both bounds are longer - if (valueRange.contains(0)) { - lowerLength = 1; - } - - int upperLength = Math.max(fromLength, toLength); - - return Range.create(lowerLength, upperLength); - } - - /** - * Returns a range of possible lengths for {@code subNode}, as casted to a String. - * - * @param subNode some subnode of {@code p} - * @param p a TransferInput - * @return a range of possible lengths for {@code subNode}, as casted to a String - */ - private @Nullable Range getStringLengthRange(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - - AnnotationMirror anno = getValueAnnotation(value); - if (anno == null) { - return null; - } - String annoName = AnnotationUtils.annotationName(anno); - if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - return atypeFactory.getRange(anno); - } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Range.NOTHING; - } - - TypeKind subNodeTypeKind = subNode.getType().getKind(); - - // handle values converted to string (ints, longs, longs with @IntRange) - if (subNode instanceof StringConversionNode) { - return getStringLengthRange(((StringConversionNode) subNode).getOperand(), p); - } else if (isIntRange(subNode, p)) { - return getIntRangeStringLengthRange(subNode, p); - } else if (subNodeTypeKind == TypeKind.INT) { - // ints are between 1 and 11 characters long - return Range.create(1, 11); - } else if (subNodeTypeKind == TypeKind.LONG) { - // longs are between 1 and 20 characters long - return Range.create(1, 20); - } - - return Range.create(0, Integer.MAX_VALUE); - } - - /** - * Returns a list of possible lengths for {@code subNode}, as casted to a String. Returns null - * if {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is - * bottom. - */ - private @Nullable List getStringLengths( - Node subNode, TransferInput p) { - - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror anno = getValueAnnotation(value); - if (anno == null) { - return null; - } - String annoName = AnnotationUtils.annotationName(anno); - if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { - return atypeFactory.getArrayLength(anno); - } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Collections.emptyList(); - } - - TypeKind subNodeTypeKind = subNode.getType().getKind(); - - // handle values converted to string (characters, bytes, shorts, ints with @IntRange) - if (subNode instanceof StringConversionNode) { - return getStringLengths(((StringConversionNode) subNode).getOperand(), p); - } else if (subNodeTypeKind == TypeKind.CHAR) { - // characters always have length 1 - return Collections.singletonList(1); - } else if (isIntRange(subNode, p)) { - // Try to get a list of lengths from a range of integer values converted to string. - // @IntVal is not checked for, because if it is present, we would already have the - // actual string values. - Range lengthRange = getIntRangeStringLengthRange(subNode, p); - return ValueCheckerUtils.getValuesFromRange(lengthRange, Integer.class); - } else if (subNodeTypeKind == TypeKind.BYTE) { - // bytes are between 1 and 4 characters long - return ValueCheckerUtils.getValuesFromRange(Range.create(1, 4), Integer.class); - } else if (subNodeTypeKind == TypeKind.SHORT) { - // shorts are between 1 and 6 characters long - return ValueCheckerUtils.getValuesFromRange(Range.create(1, 6), Integer.class); - } else { - return null; - } - } - - /** - * Returns a list of possible values for {@code subNode}, as casted to a String. Returns null if - * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is - * bottom. - * - * @param subNode a subNode of p - * @param p a TransferInput - * @return a list of possible values for {@code subNode} or null - */ - private @Nullable List getStringValues( - Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror anno = getValueAnnotation(value); - if (anno == null) { - return null; - } - String annoName = AnnotationUtils.annotationName(anno); - switch (annoName) { - case ValueAnnotatedTypeFactory.UNKNOWN_NAME: - return null; - case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: - return Collections.emptyList(); - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - return atypeFactory.getStringValues(anno); - default: - // Do nothing. - } - - // @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string) - List values; - if (annoName.equals(ValueAnnotatedTypeFactory.BOOLVAL_NAME)) { - values = getBooleanValues(subNode, p); - } else if (subNode.getType().getKind() == TypeKind.CHAR) { - values = getCharValues(subNode, p); - } else if (subNode instanceof StringConversionNode) { - return getStringValues(((StringConversionNode) subNode).getOperand(), p); - } else if (isIntRange(subNode, p)) { - Range range = getIntRange(subNode, p); - List longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); - values = NumberUtils.castNumbers(subNode.getType(), longValues); - } else { - values = getNumericalValues(subNode, p); - } - if (values == null) { - return null; - } - List stringValues = CollectionsPlume.mapList(Object::toString, values); - // Empty list means bottom value - return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues; - } - - /** - * Create a @BoolVal CFValue for the given boolean value. - * - * @param value the value for the @BoolVal annotation - * @return a @BoolVal CFValue for the given boolean value - */ - private CFValue createBooleanCFValue(boolean value) { - return analysis.createSingleAnnotationValue( - value ? atypeFactory.BOOLEAN_TRUE : atypeFactory.BOOLEAN_FALSE, - atypeFactory.types.getPrimitiveType(TypeKind.BOOLEAN)); - } - - /** - * Get the unique possible boolean value from @BoolVal. Returns null if that is not the case - * (including if the CFValue is not @BoolVal). - * - * @param value a CFValue - * @return theboolean if {@code value} represents a single boolean value; otherwise null - */ - private @Nullable Boolean getBooleanValue(CFValue value) { - AnnotationMirror boolAnno = - AnnotationUtils.getAnnotationByName( - value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); - return atypeFactory.getBooleanValue(boolAnno); - } - - /** - * Get possible boolean values for a node. Returns null if there is no estimate, because the - * node's value is not @BoolVal. - * - * @param subNode the node whose value to obtain - * @param p the transfer input in which to look up values - * @return the possible boolean values for the node - */ - private @Nullable List getBooleanValues( - Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror intAnno = - AnnotationUtils.getAnnotationByName( - value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); - return atypeFactory.getBooleanValues(intAnno); - } - - /** Get possible char values from annotation @IntRange or @IntVal. */ - private List getCharValues(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror intAnno; - - intAnno = - AnnotationUtils.getAnnotationByName( - value.getAnnotations(), ValueAnnotatedTypeFactory.INTVAL_NAME); - if (intAnno != null) { - return atypeFactory.getCharValues(intAnno); - } - - if (atypeFactory.isIntRange(value.getAnnotations())) { - intAnno = - qualHierarchy.findAnnotationInHierarchy( - value.getAnnotations(), atypeFactory.UNKNOWNVAL); - Range range = atypeFactory.getRange(intAnno); - return ValueCheckerUtils.getValuesFromRange(range, Character.class); - } - + /** The Value type factory. */ + protected final ValueAnnotatedTypeFactory atypeFactory; + + /** The Value qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; + + /** True if -AnonNullStringsConcatenation was passed on the command line. */ + private final boolean nonNullStringsConcatenation; + + /** + * Create a new ValueTransfer. + * + * @param analysis the corresponding analysis + */ + public ValueTransfer(CFAbstractAnalysis analysis) { + super(analysis); + atypeFactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory(); + qualHierarchy = atypeFactory.getQualifierHierarchy(); + nonNullStringsConcatenation = + atypeFactory.getChecker().hasOption("nonNullStringsConcatenation"); + } + + /** Returns a range of possible lengths for an integer from a range, as casted to a String. */ + private Range getIntRangeStringLengthRange(Node subNode, TransferInput p) { + Range valueRange = getIntRange(subNode, p); + + // Get lengths of the bounds + int fromLength = Long.toString(valueRange.from).length(); + int toLength = Long.toString(valueRange.to).length(); + + int lowerLength = Math.min(fromLength, toLength); + // In case the range contains 0, the minimum length is 1 even if both bounds are longer + if (valueRange.contains(0)) { + lowerLength = 1; + } + + int upperLength = Math.max(fromLength, toLength); + + return Range.create(lowerLength, upperLength); + } + + /** + * Returns a range of possible lengths for {@code subNode}, as casted to a String. + * + * @param subNode some subnode of {@code p} + * @param p a TransferInput + * @return a range of possible lengths for {@code subNode}, as casted to a String + */ + private @Nullable Range getStringLengthRange(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + + AnnotationMirror anno = getValueAnnotation(value); + if (anno == null) { + return null; + } + String annoName = AnnotationUtils.annotationName(anno); + if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + return atypeFactory.getRange(anno); + } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Range.NOTHING; + } + + TypeKind subNodeTypeKind = subNode.getType().getKind(); + + // handle values converted to string (ints, longs, longs with @IntRange) + if (subNode instanceof StringConversionNode) { + return getStringLengthRange(((StringConversionNode) subNode).getOperand(), p); + } else if (isIntRange(subNode, p)) { + return getIntRangeStringLengthRange(subNode, p); + } else if (subNodeTypeKind == TypeKind.INT) { + // ints are between 1 and 11 characters long + return Range.create(1, 11); + } else if (subNodeTypeKind == TypeKind.LONG) { + // longs are between 1 and 20 characters long + return Range.create(1, 20); + } + + return Range.create(0, Integer.MAX_VALUE); + } + + /** + * Returns a list of possible lengths for {@code subNode}, as casted to a String. Returns null if + * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is + * bottom. + */ + private @Nullable List getStringLengths( + Node subNode, TransferInput p) { + + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror anno = getValueAnnotation(value); + if (anno == null) { + return null; + } + String annoName = AnnotationUtils.annotationName(anno); + if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { + return atypeFactory.getArrayLength(anno); + } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Collections.emptyList(); + } + + TypeKind subNodeTypeKind = subNode.getType().getKind(); + + // handle values converted to string (characters, bytes, shorts, ints with @IntRange) + if (subNode instanceof StringConversionNode) { + return getStringLengths(((StringConversionNode) subNode).getOperand(), p); + } else if (subNodeTypeKind == TypeKind.CHAR) { + // characters always have length 1 + return Collections.singletonList(1); + } else if (isIntRange(subNode, p)) { + // Try to get a list of lengths from a range of integer values converted to string. + // @IntVal is not checked for, because if it is present, we would already have the + // actual string values. + Range lengthRange = getIntRangeStringLengthRange(subNode, p); + return ValueCheckerUtils.getValuesFromRange(lengthRange, Integer.class); + } else if (subNodeTypeKind == TypeKind.BYTE) { + // bytes are between 1 and 4 characters long + return ValueCheckerUtils.getValuesFromRange(Range.create(1, 4), Integer.class); + } else if (subNodeTypeKind == TypeKind.SHORT) { + // shorts are between 1 and 6 characters long + return ValueCheckerUtils.getValuesFromRange(Range.create(1, 6), Integer.class); + } else { + return null; + } + } + + /** + * Returns a list of possible values for {@code subNode}, as casted to a String. Returns null if + * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is + * bottom. + * + * @param subNode a subNode of p + * @param p a TransferInput + * @return a list of possible values for {@code subNode} or null + */ + private @Nullable List getStringValues(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror anno = getValueAnnotation(value); + if (anno == null) { + return null; + } + String annoName = AnnotationUtils.annotationName(anno); + switch (annoName) { + case ValueAnnotatedTypeFactory.UNKNOWN_NAME: + return null; + case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: return Collections.emptyList(); - } - - private AnnotationMirror getValueAnnotation(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - return getValueAnnotation(value); - } - - /** - * Extract the Value Checker annotation from a CFValue object. - * - * @param cfValue a CFValue object - * @return the Value Checker annotation within cfValue - */ - private AnnotationMirror getValueAnnotation(CFValue cfValue) { - return qualHierarchy.findAnnotationInHierarchy( - cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL); - } - - /** - * Returns a list of possible values, or null if no estimate is available and any value is - * possible. - */ - private @Nullable List getNumericalValues( - Node subNode, TransferInput p) { - AnnotationMirror valueAnno = getValueAnnotation(subNode, p); - return getNumericalValues(subNode, valueAnno); - } - - /** - * Returns the numerical values in valueAnno casted to the type of subNode. - * - * @param subNode node - * @param valueAnno annotation mirror - * @return the numerical values in valueAnno casted to the type of subNode - */ - private @Nullable List getNumericalValues( - Node subNode, AnnotationMirror valueAnno) { - - if (valueAnno == null - || AnnotationUtils.areSameByName( - valueAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - return null; - } else if (AnnotationUtils.areSameByName( - valueAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Collections.emptyList(); - } - List values; - if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - values = atypeFactory.getIntValues(valueAnno); - } else if (AnnotationUtils.areSameByName( - valueAnno, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { - values = atypeFactory.getDoubleValues(valueAnno); - } else { - return null; - } - return NumberUtils.castNumbers(subNode.getType(), values); - } - - /** Get possible integer range from annotation. */ - private Range getIntRange(Node subNode, TransferInput p) { - AnnotationMirror val = getValueAnnotation(subNode, p); - return getIntRangeFromAnnotation(subNode, val); - } - - /** - * Returns the {@link Range} object corresponding to the annotation {@code val} casted to the - * type of {@code node}. - * - * @param node a node - * @param val annotation mirror - * @return the {@link Range} object corresponding to the annotation {@code val} casted to the - * type of {@code node}. - */ - private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) { - Range range; - if (val == null - || AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - range = Range.EVERYTHING; - } else if (atypeFactory.isIntRange(val)) { - range = atypeFactory.getRange(val); - } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - List values = atypeFactory.getIntValues(val); - range = ValueCheckerUtils.getRangeFromValues(values); - } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { - List values = atypeFactory.getDoubleValues(val); - range = ValueCheckerUtils.getRangeFromValues(values); - } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Range.NOTHING; - } else { - range = Range.EVERYTHING; - } - return NumberUtils.castRange(node.getType(), range); - } - - /** - * Returns true if subNode is annotated with {@code @IntRange}. - * - * @param subNode subNode of {@code p} - * @param p a TransferInput - * @return true if this subNode is annotated with {@code @IntRange} - */ - private boolean isIntRange(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - return atypeFactory.isIntRange(value.getAnnotations()); - } - - /** - * Returns true if {@code node} an integral type and is {@code anno} is {@code @UnknownVal}. - * - * @param node a node - * @param anno annotation mirror - * @return true if node is annotated with {@code @UnknownVal} and it is an integral type - */ - private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) { - return AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.UNKNOWN_NAME) - && TypesUtils.isIntegralPrimitive(node.getType()); - } - - /** - * Returns true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}. - * - * @param node the node to inspect - * @param p storage - * @return true if this node is annotated with {@code @IntRange} or {@code @UnknownVal} - */ - private boolean isIntRangeOrIntegralUnknownVal(Node node, TransferInput p) { - if (isIntRange(node, p)) { - return true; - } - return isIntegralUnknownVal(node, getValueAnnotation(p.getValueOfSubNode(node))); - } - - /** - * Create a new transfer result based on the original result and the new annotation. - * - * @param result the original result - * @param resultAnno the new annotation - * @return the new transfer result - */ - private TransferResult createNewResult( - TransferResult result, AnnotationMirror resultAnno) { - CFValue newResultValue = - analysis.createSingleAnnotationValue( - resultAnno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - - /** Create a boolean transfer result. */ - private TransferResult createNewResultBoolean( - CFStore thenStore, - CFStore elseStore, - List resultValues, - TypeMirror underlyingType) { - AnnotationMirror boolVal = atypeFactory.createBooleanAnnotation(resultValues); - CFValue newResultValue = analysis.createSingleAnnotationValue(boolVal, underlyingType); - if (elseStore != null) { - return new ConditionalTransferResult<>(newResultValue, thenStore, elseStore); - } else { - return new RegularTransferResult<>(newResultValue, thenStore); - } - } - - @Override - public TransferResult visitFieldAccess( - FieldAccessNode node, TransferInput in) { - - TransferResult result = super.visitFieldAccess(node, in); - refineArrayAtLengthAccess(node, result.getRegularStore()); - return result; - } - - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput p) { - TransferResult result = super.visitMethodInvocation(n, p); - refineAtLengthInvocation(n, result.getRegularStore()); - return result; - } - - /** - * If array.length is encountered, transform its @IntVal annotation into an @ArrayLen annotation - * for array. - */ - private void refineArrayAtLengthAccess(FieldAccessNode arrayLengthNode, CFStore store) { - if (!NodeUtils.isArrayLengthFieldAccess(arrayLengthNode)) { - return; - } - - refineAtLengthAccess(arrayLengthNode, arrayLengthNode.getReceiver(), store); - } - - /** - * If length method is invoked for a sequence, transform its @IntVal annotation into - * an @ArrayLen annotation. - * - * @param lengthNode the length method invocation node - * @param store the Checker Framework store - */ - private void refineAtLengthInvocation(MethodInvocationNode lengthNode, CFStore store) { - if (atypeFactory - .getMethodIdentifier() - .isStringLengthMethod(lengthNode.getTarget().getMethod())) { - MethodAccessNode methodAccessNode = lengthNode.getTarget(); - refineAtLengthAccess(lengthNode, methodAccessNode.getReceiver(), store); - } else if (atypeFactory - .getMethodIdentifier() - .isArrayGetLengthMethod(lengthNode.getTarget().getMethod())) { - Node node = lengthNode.getArguments().get(0); - refineAtLengthAccess(lengthNode, node, store); + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + return atypeFactory.getStringValues(anno); + default: + // Do nothing. + } + + // @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string) + List values; + if (annoName.equals(ValueAnnotatedTypeFactory.BOOLVAL_NAME)) { + values = getBooleanValues(subNode, p); + } else if (subNode.getType().getKind() == TypeKind.CHAR) { + values = getCharValues(subNode, p); + } else if (subNode instanceof StringConversionNode) { + return getStringValues(((StringConversionNode) subNode).getOperand(), p); + } else if (isIntRange(subNode, p)) { + Range range = getIntRange(subNode, p); + List longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); + values = NumberUtils.castNumbers(subNode.getType(), longValues); + } else { + values = getNumericalValues(subNode, p); + } + if (values == null) { + return null; + } + List stringValues = CollectionsPlume.mapList(Object::toString, values); + // Empty list means bottom value + return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues; + } + + /** + * Create a @BoolVal CFValue for the given boolean value. + * + * @param value the value for the @BoolVal annotation + * @return a @BoolVal CFValue for the given boolean value + */ + private CFValue createBooleanCFValue(boolean value) { + return analysis.createSingleAnnotationValue( + value ? atypeFactory.BOOLEAN_TRUE : atypeFactory.BOOLEAN_FALSE, + atypeFactory.types.getPrimitiveType(TypeKind.BOOLEAN)); + } + + /** + * Get the unique possible boolean value from @BoolVal. Returns null if that is not the case + * (including if the CFValue is not @BoolVal). + * + * @param value a CFValue + * @return theboolean if {@code value} represents a single boolean value; otherwise null + */ + private @Nullable Boolean getBooleanValue(CFValue value) { + AnnotationMirror boolAnno = + AnnotationUtils.getAnnotationByName( + value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); + return atypeFactory.getBooleanValue(boolAnno); + } + + /** + * Get possible boolean values for a node. Returns null if there is no estimate, because the + * node's value is not @BoolVal. + * + * @param subNode the node whose value to obtain + * @param p the transfer input in which to look up values + * @return the possible boolean values for the node + */ + private @Nullable List getBooleanValues( + Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror intAnno = + AnnotationUtils.getAnnotationByName( + value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); + return atypeFactory.getBooleanValues(intAnno); + } + + /** Get possible char values from annotation @IntRange or @IntVal. */ + private List getCharValues(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror intAnno; + + intAnno = + AnnotationUtils.getAnnotationByName( + value.getAnnotations(), ValueAnnotatedTypeFactory.INTVAL_NAME); + if (intAnno != null) { + return atypeFactory.getCharValues(intAnno); + } + + if (atypeFactory.isIntRange(value.getAnnotations())) { + intAnno = + qualHierarchy.findAnnotationInHierarchy(value.getAnnotations(), atypeFactory.UNKNOWNVAL); + Range range = atypeFactory.getRange(intAnno); + return ValueCheckerUtils.getValuesFromRange(range, Character.class); + } + + return Collections.emptyList(); + } + + private AnnotationMirror getValueAnnotation(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + return getValueAnnotation(value); + } + + /** + * Extract the Value Checker annotation from a CFValue object. + * + * @param cfValue a CFValue object + * @return the Value Checker annotation within cfValue + */ + private AnnotationMirror getValueAnnotation(CFValue cfValue) { + return qualHierarchy.findAnnotationInHierarchy( + cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL); + } + + /** + * Returns a list of possible values, or null if no estimate is available and any value is + * possible. + */ + private @Nullable List getNumericalValues( + Node subNode, TransferInput p) { + AnnotationMirror valueAnno = getValueAnnotation(subNode, p); + return getNumericalValues(subNode, valueAnno); + } + + /** + * Returns the numerical values in valueAnno casted to the type of subNode. + * + * @param subNode node + * @param valueAnno annotation mirror + * @return the numerical values in valueAnno casted to the type of subNode + */ + private @Nullable List getNumericalValues( + Node subNode, AnnotationMirror valueAnno) { + + if (valueAnno == null + || AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + return null; + } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Collections.emptyList(); + } + List values; + if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + values = atypeFactory.getIntValues(valueAnno); + } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { + values = atypeFactory.getDoubleValues(valueAnno); + } else { + return null; + } + return NumberUtils.castNumbers(subNode.getType(), values); + } + + /** Get possible integer range from annotation. */ + private Range getIntRange(Node subNode, TransferInput p) { + AnnotationMirror val = getValueAnnotation(subNode, p); + return getIntRangeFromAnnotation(subNode, val); + } + + /** + * Returns the {@link Range} object corresponding to the annotation {@code val} casted to the type + * of {@code node}. + * + * @param node a node + * @param val annotation mirror + * @return the {@link Range} object corresponding to the annotation {@code val} casted to the type + * of {@code node}. + */ + private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) { + Range range; + if (val == null || AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + range = Range.EVERYTHING; + } else if (atypeFactory.isIntRange(val)) { + range = atypeFactory.getRange(val); + } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + List values = atypeFactory.getIntValues(val); + range = ValueCheckerUtils.getRangeFromValues(values); + } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { + List values = atypeFactory.getDoubleValues(val); + range = ValueCheckerUtils.getRangeFromValues(values); + } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Range.NOTHING; + } else { + range = Range.EVERYTHING; + } + return NumberUtils.castRange(node.getType(), range); + } + + /** + * Returns true if subNode is annotated with {@code @IntRange}. + * + * @param subNode subNode of {@code p} + * @param p a TransferInput + * @return true if this subNode is annotated with {@code @IntRange} + */ + private boolean isIntRange(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + return atypeFactory.isIntRange(value.getAnnotations()); + } + + /** + * Returns true if {@code node} an integral type and is {@code anno} is {@code @UnknownVal}. + * + * @param node a node + * @param anno annotation mirror + * @return true if node is annotated with {@code @UnknownVal} and it is an integral type + */ + private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) { + return AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.UNKNOWN_NAME) + && TypesUtils.isIntegralPrimitive(node.getType()); + } + + /** + * Returns true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}. + * + * @param node the node to inspect + * @param p storage + * @return true if this node is annotated with {@code @IntRange} or {@code @UnknownVal} + */ + private boolean isIntRangeOrIntegralUnknownVal(Node node, TransferInput p) { + if (isIntRange(node, p)) { + return true; + } + return isIntegralUnknownVal(node, getValueAnnotation(p.getValueOfSubNode(node))); + } + + /** + * Create a new transfer result based on the original result and the new annotation. + * + * @param result the original result + * @param resultAnno the new annotation + * @return the new transfer result + */ + private TransferResult createNewResult( + TransferResult result, AnnotationMirror resultAnno) { + CFValue newResultValue = + analysis.createSingleAnnotationValue( + resultAnno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } + + /** Create a boolean transfer result. */ + private TransferResult createNewResultBoolean( + CFStore thenStore, CFStore elseStore, List resultValues, TypeMirror underlyingType) { + AnnotationMirror boolVal = atypeFactory.createBooleanAnnotation(resultValues); + CFValue newResultValue = analysis.createSingleAnnotationValue(boolVal, underlyingType); + if (elseStore != null) { + return new ConditionalTransferResult<>(newResultValue, thenStore, elseStore); + } else { + return new RegularTransferResult<>(newResultValue, thenStore); + } + } + + @Override + public TransferResult visitFieldAccess( + FieldAccessNode node, TransferInput in) { + + TransferResult result = super.visitFieldAccess(node, in); + refineArrayAtLengthAccess(node, result.getRegularStore()); + return result; + } + + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput p) { + TransferResult result = super.visitMethodInvocation(n, p); + refineAtLengthInvocation(n, result.getRegularStore()); + return result; + } + + /** + * If array.length is encountered, transform its @IntVal annotation into an @ArrayLen annotation + * for array. + */ + private void refineArrayAtLengthAccess(FieldAccessNode arrayLengthNode, CFStore store) { + if (!NodeUtils.isArrayLengthFieldAccess(arrayLengthNode)) { + return; + } + + refineAtLengthAccess(arrayLengthNode, arrayLengthNode.getReceiver(), store); + } + + /** + * If length method is invoked for a sequence, transform its @IntVal annotation into an @ArrayLen + * annotation. + * + * @param lengthNode the length method invocation node + * @param store the Checker Framework store + */ + private void refineAtLengthInvocation(MethodInvocationNode lengthNode, CFStore store) { + if (atypeFactory + .getMethodIdentifier() + .isStringLengthMethod(lengthNode.getTarget().getMethod())) { + MethodAccessNode methodAccessNode = lengthNode.getTarget(); + refineAtLengthAccess(lengthNode, methodAccessNode.getReceiver(), store); + } else if (atypeFactory + .getMethodIdentifier() + .isArrayGetLengthMethod(lengthNode.getTarget().getMethod())) { + Node node = lengthNode.getArguments().get(0); + refineAtLengthAccess(lengthNode, node, store); + } + } + + /** + * Gets a value checker annotation relevant for an array or a string. + * + * @param arrayOrStringNode the node whose annotation to return + * @return the value checker annotation for the array or a string + */ + private AnnotationMirror getArrayOrStringAnnotation(Node arrayOrStringNode) { + AnnotationMirror arrayOrStringAnno = + atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), StringVal.class); + if (arrayOrStringAnno == null) { + arrayOrStringAnno = + atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLen.class); + } + if (arrayOrStringAnno == null) { + arrayOrStringAnno = + atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLenRange.class); + } + + return arrayOrStringAnno; + } + + /** + * Transform @IntVal or @IntRange annotations of an array or string length into an @ArrayLen + * or @ArrayLenRange annotation for the array or string. + * + * @param lengthNode an invocation of method {@code length} or an access of the {@code length} + * field + * @param receiverNode the receiver of {@code lengthNode} + * @param store the store to update + */ + private void refineAtLengthAccess(Node lengthNode, Node receiverNode, CFStore store) { + JavaExpression lengthExpr = JavaExpression.fromNode(lengthNode); + + // If the expression is not representable (for example if String.length() for some reason is + // not marked @Pure, then do not refine. + if (lengthExpr instanceof Unknown) { + return; + } + + CFValue value = store.getValue(lengthExpr); + if (value == null) { + return; + } + + AnnotationMirror lengthAnno = getValueAnnotation(value); + if (lengthAnno == null) { + return; + } + JavaExpression receiverJE = JavaExpression.fromNode(receiverNode); + if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + // If the length is bottom, then this is dead code, so the receiver type + // should also be bottom. + store.insertValue(receiverJE, lengthAnno); + return; + } + + RangeOrListOfValues rolv; + if (atypeFactory.isIntRange(lengthAnno)) { + rolv = new RangeOrListOfValues(atypeFactory.getRange(lengthAnno)); + } else if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + List lengthValues = atypeFactory.getIntValues(lengthAnno); + rolv = new RangeOrListOfValues(RangeOrListOfValues.convertLongsToInts(lengthValues)); + } else { + return; + } + AnnotationMirror newRecAnno = rolv.createAnnotation(atypeFactory); + AnnotationMirror oldRecAnno = getArrayOrStringAnnotation(receiverNode); + + AnnotationMirror combinedRecAnno; + // If the receiver doesn't have an @ArrayLen annotation, use the new annotation. + // If it does have an annotation, combine the facts known about the receiver + // with the facts known about its length using GLB. + if (oldRecAnno == null) { + combinedRecAnno = newRecAnno; + } else { + TypeMirror receiverTM = receiverJE.getType(); + combinedRecAnno = + qualHierarchy.greatestLowerBoundShallow(oldRecAnno, receiverTM, newRecAnno, receiverTM); + } + store.insertValue(receiverJE, combinedRecAnno); + } + + @Override + public TransferResult visitStringConcatenate( + StringConcatenateNode n, TransferInput p) { + TransferResult result = super.visitStringConcatenate(n, p); + return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result); + } + + /** + * Calculates possible lengths of a result of string concatenation of strings with known lengths. + */ + private List calculateLengthAddition( + List leftLengths, List rightLengths) { + List result = new ArrayList<>(leftLengths.size() * rightLengths.size()); + + for (int left : leftLengths) { + for (int right : rightLengths) { + long resultLength = (long) left + right; + // Lengths not fitting into int are not allowed + if (resultLength <= Integer.MAX_VALUE) { + result.add((int) resultLength); } - } - - /** - * Gets a value checker annotation relevant for an array or a string. - * - * @param arrayOrStringNode the node whose annotation to return - * @return the value checker annotation for the array or a string - */ - private AnnotationMirror getArrayOrStringAnnotation(Node arrayOrStringNode) { - AnnotationMirror arrayOrStringAnno = - atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), StringVal.class); - if (arrayOrStringAnno == null) { - arrayOrStringAnno = - atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLen.class); + } + } + + return result; + } + + /** + * Calculates a range of possible lengths of a result of string concatenation of strings with + * known ranges of lengths. + */ + private Range calculateLengthRangeAddition(Range leftLengths, Range rightLengths) { + return leftLengths.plus(rightLengths).intersect(Range.INT_EVERYTHING); + } + + /** + * Checks whether or not the passed node is nullable. This superficial check assumes that every + * node is nullable unless it is a primitive, String literal, or compile-time constant. + * + * @return false if the node's run-time can't be null; true if the node's run-time value may be + * null, or if this method is not precise enough + */ + private boolean isNullable(Node node) { + if (node instanceof StringConversionNode) { + if (((StringConversionNode) node).getOperand().getType().getKind().isPrimitive()) { + return false; + } + } else if (node instanceof StringLiteralNode) { + return false; + } else if (node instanceof StringConcatenateNode) { + return false; + } + + Element element = TreeUtils.elementFromTree((ExpressionTree) node.getTree()); + return !ElementUtils.isCompileTimeConstant(element); + } + + /** Creates an annotation for a result of string concatenation. */ + private AnnotationMirror createAnnotationForStringConcatenation( + Node leftOperand, Node rightOperand, TransferInput p) { + + // Try using sets of string values + List leftValues = getStringValues(leftOperand, p); + List rightValues = getStringValues(rightOperand, p); + + if (leftValues != null && rightValues != null) { + // Both operands have known string values, compute set of results + if (!nonNullStringsConcatenation) { + if (isNullable(leftOperand)) { + leftValues = CollectionsPlume.append(leftValues, "null"); } - if (arrayOrStringAnno == null) { - arrayOrStringAnno = - atypeFactory.getAnnotationMirror( - arrayOrStringNode.getTree(), ArrayLenRange.class); + if (isNullable(rightOperand)) { + rightValues = CollectionsPlume.append(rightValues, "null"); } - - return arrayOrStringAnno; - } - - /** - * Transform @IntVal or @IntRange annotations of an array or string length into an @ArrayLen - * or @ArrayLenRange annotation for the array or string. - * - * @param lengthNode an invocation of method {@code length} or an access of the {@code length} - * field - * @param receiverNode the receiver of {@code lengthNode} - * @param store the store to update - */ - private void refineAtLengthAccess(Node lengthNode, Node receiverNode, CFStore store) { - JavaExpression lengthExpr = JavaExpression.fromNode(lengthNode); - - // If the expression is not representable (for example if String.length() for some reason is - // not marked @Pure, then do not refine. - if (lengthExpr instanceof Unknown) { - return; - } - - CFValue value = store.getValue(lengthExpr); - if (value == null) { - return; - } - - AnnotationMirror lengthAnno = getValueAnnotation(value); - if (lengthAnno == null) { - return; - } - JavaExpression receiverJE = JavaExpression.fromNode(receiverNode); - if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - // If the length is bottom, then this is dead code, so the receiver type - // should also be bottom. - store.insertValue(receiverJE, lengthAnno); - return; - } - - RangeOrListOfValues rolv; - if (atypeFactory.isIntRange(lengthAnno)) { - rolv = new RangeOrListOfValues(atypeFactory.getRange(lengthAnno)); - } else if (AnnotationUtils.areSameByName( - lengthAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - List lengthValues = atypeFactory.getIntValues(lengthAnno); - rolv = new RangeOrListOfValues(RangeOrListOfValues.convertLongsToInts(lengthValues)); - } else { - return; - } - AnnotationMirror newRecAnno = rolv.createAnnotation(atypeFactory); - AnnotationMirror oldRecAnno = getArrayOrStringAnnotation(receiverNode); - - AnnotationMirror combinedRecAnno; - // If the receiver doesn't have an @ArrayLen annotation, use the new annotation. - // If it does have an annotation, combine the facts known about the receiver - // with the facts known about its length using GLB. - if (oldRecAnno == null) { - combinedRecAnno = newRecAnno; - } else { - TypeMirror receiverTM = receiverJE.getType(); - combinedRecAnno = - qualHierarchy.greatestLowerBoundShallow( - oldRecAnno, receiverTM, newRecAnno, receiverTM); + } else { + if (leftOperand instanceof StringConversionNode) { + if (((StringConversionNode) leftOperand).getOperand().getType().getKind() + == TypeKind.NULL) { + leftValues = CollectionsPlume.append(leftValues, "null"); + } } - store.insertValue(receiverJE, combinedRecAnno); - } - - @Override - public TransferResult visitStringConcatenate( - StringConcatenateNode n, TransferInput p) { - TransferResult result = super.visitStringConcatenate(n, p); - return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result); - } - - /** - * Calculates possible lengths of a result of string concatenation of strings with known - * lengths. - */ - private List calculateLengthAddition( - List leftLengths, List rightLengths) { - List result = new ArrayList<>(leftLengths.size() * rightLengths.size()); - - for (int left : leftLengths) { - for (int right : rightLengths) { - long resultLength = (long) left + right; - // Lengths not fitting into int are not allowed - if (resultLength <= Integer.MAX_VALUE) { - result.add((int) resultLength); - } - } + if (rightOperand instanceof StringConversionNode) { + if (((StringConversionNode) rightOperand).getOperand().getType().getKind() + == TypeKind.NULL) { + rightValues = CollectionsPlume.append(rightValues, "null"); + } } + } - return result; - } - - /** - * Calculates a range of possible lengths of a result of string concatenation of strings with - * known ranges of lengths. - */ - private Range calculateLengthRangeAddition(Range leftLengths, Range rightLengths) { - return leftLengths.plus(rightLengths).intersect(Range.INT_EVERYTHING); - } - - /** - * Checks whether or not the passed node is nullable. This superficial check assumes that every - * node is nullable unless it is a primitive, String literal, or compile-time constant. - * - * @return false if the node's run-time can't be null; true if the node's run-time value may be - * null, or if this method is not precise enough - */ - private boolean isNullable(Node node) { - if (node instanceof StringConversionNode) { - if (((StringConversionNode) node).getOperand().getType().getKind().isPrimitive()) { - return false; - } - } else if (node instanceof StringLiteralNode) { - return false; - } else if (node instanceof StringConcatenateNode) { - return false; + List concatValues = new ArrayList<>(leftValues.size() * rightValues.size()); + for (String left : leftValues) { + for (String right : rightValues) { + concatValues.add(left + right); } - - Element element = TreeUtils.elementFromTree((ExpressionTree) node.getTree()); - return !ElementUtils.isCompileTimeConstant(element); - } - - /** Creates an annotation for a result of string concatenation. */ - private AnnotationMirror createAnnotationForStringConcatenation( - Node leftOperand, Node rightOperand, TransferInput p) { - - // Try using sets of string values - List leftValues = getStringValues(leftOperand, p); - List rightValues = getStringValues(rightOperand, p); - - if (leftValues != null && rightValues != null) { - // Both operands have known string values, compute set of results - if (!nonNullStringsConcatenation) { - if (isNullable(leftOperand)) { - leftValues = CollectionsPlume.append(leftValues, "null"); - } - if (isNullable(rightOperand)) { - rightValues = CollectionsPlume.append(rightValues, "null"); - } - } else { - if (leftOperand instanceof StringConversionNode) { - if (((StringConversionNode) leftOperand).getOperand().getType().getKind() - == TypeKind.NULL) { - leftValues = CollectionsPlume.append(leftValues, "null"); - } - } - if (rightOperand instanceof StringConversionNode) { - if (((StringConversionNode) rightOperand).getOperand().getType().getKind() - == TypeKind.NULL) { - rightValues = CollectionsPlume.append(rightValues, "null"); - } - } - } - - List concatValues = new ArrayList<>(leftValues.size() * rightValues.size()); - for (String left : leftValues) { - for (String right : rightValues) { - concatValues.add(left + right); - } - } - return atypeFactory.createStringAnnotation(concatValues); + } + return atypeFactory.createStringAnnotation(concatValues); + } + + // Try using sets of lengths + List leftLengths = + leftValues != null + ? ValueCheckerUtils.getLengthsForStringValues(leftValues) + : getStringLengths(leftOperand, p); + List rightLengths = + rightValues != null + ? ValueCheckerUtils.getLengthsForStringValues(rightValues) + : getStringLengths(rightOperand, p); + + if (leftLengths != null && rightLengths != null) { + // Both operands have known lengths, compute set of result lengths + if (!nonNullStringsConcatenation) { + if (isNullable(leftOperand)) { + leftLengths = new ArrayList<>(leftLengths); + leftLengths.add(4); // "null" } - - // Try using sets of lengths - List leftLengths = - leftValues != null - ? ValueCheckerUtils.getLengthsForStringValues(leftValues) - : getStringLengths(leftOperand, p); - List rightLengths = - rightValues != null - ? ValueCheckerUtils.getLengthsForStringValues(rightValues) - : getStringLengths(rightOperand, p); - - if (leftLengths != null && rightLengths != null) { - // Both operands have known lengths, compute set of result lengths - if (!nonNullStringsConcatenation) { - if (isNullable(leftOperand)) { - leftLengths = new ArrayList<>(leftLengths); - leftLengths.add(4); // "null" - } - if (isNullable(rightOperand)) { - rightLengths = new ArrayList<>(rightLengths); - rightLengths.add(4); // "null" - } - } - List concatLengths = calculateLengthAddition(leftLengths, rightLengths); - return atypeFactory.createArrayLenAnnotation(concatLengths); + if (isNullable(rightOperand)) { + rightLengths = new ArrayList<>(rightLengths); + rightLengths.add(4); // "null" } - - // Try using ranges of lengths - Range leftLengthRange = - leftLengths != null - ? ValueCheckerUtils.getRangeFromValues(leftLengths) - : getStringLengthRange(leftOperand, p); - Range rightLengthRange = - rightLengths != null - ? ValueCheckerUtils.getRangeFromValues(rightLengths) - : getStringLengthRange(rightOperand, p); - - if (leftLengthRange != null && rightLengthRange != null) { - // Both operands have a length from a known range, compute a range of result lengths - if (!nonNullStringsConcatenation) { - if (isNullable(leftOperand)) { - leftLengthRange = leftLengthRange.union(Range.create(4, 4)); // "null" - } - if (isNullable(rightOperand)) { - rightLengthRange = rightLengthRange.union(Range.create(4, 4)); // "null" - } - } - Range concatLengthRange = - calculateLengthRangeAddition(leftLengthRange, rightLengthRange); - return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange); + } + List concatLengths = calculateLengthAddition(leftLengths, rightLengths); + return atypeFactory.createArrayLenAnnotation(concatLengths); + } + + // Try using ranges of lengths + Range leftLengthRange = + leftLengths != null + ? ValueCheckerUtils.getRangeFromValues(leftLengths) + : getStringLengthRange(leftOperand, p); + Range rightLengthRange = + rightLengths != null + ? ValueCheckerUtils.getRangeFromValues(rightLengths) + : getStringLengthRange(rightOperand, p); + + if (leftLengthRange != null && rightLengthRange != null) { + // Both operands have a length from a known range, compute a range of result lengths + if (!nonNullStringsConcatenation) { + if (isNullable(leftOperand)) { + leftLengthRange = leftLengthRange.union(Range.create(4, 4)); // "null" } - - return atypeFactory.UNKNOWNVAL; - } - - public TransferResult stringConcatenation( - Node leftOperand, - Node rightOperand, - TransferInput p, - TransferResult result) { - - AnnotationMirror resultAnno = - createAnnotationForStringConcatenation(leftOperand, rightOperand, p); - - TypeMirror underlyingType = result.getResultValue().getUnderlyingType(); - CFValue newResultValue = analysis.createSingleAnnotationValue(resultAnno, underlyingType); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - - /** Binary operations that are analyzed by the value checker. */ - enum NumericalBinaryOps { - ADDITION, - SUBTRACTION, - DIVISION, - REMAINDER, - MULTIPLICATION, - SHIFT_LEFT, - SIGNED_SHIFT_RIGHT, - UNSIGNED_SHIFT_RIGHT, - BITWISE_AND, - BITWISE_OR, - BITWISE_XOR; - } - - /** - * Get the refined annotation after a numerical binary operation. - * - * @param leftNode the node that represents the left operand - * @param rightNode the node that represents the right operand - * @param op the operator type - * @param p the transfer input - * @return the result annotation mirror - */ - private AnnotationMirror calculateNumericalBinaryOp( - Node leftNode, - Node rightNode, - NumericalBinaryOps op, - TransferInput p) { - if (!isIntRangeOrIntegralUnknownVal(leftNode, p) - && !isIntRangeOrIntegralUnknownVal(rightNode, p)) { - List resultValues = calculateValuesBinaryOp(leftNode, rightNode, op, p); - return atypeFactory.createNumberAnnotationMirror(resultValues); - } else { - Range resultRange = calculateRangeBinaryOp(leftNode, rightNode, op, p); - return atypeFactory.createIntRangeAnnotation(resultRange); + if (isNullable(rightOperand)) { + rightLengthRange = rightLengthRange.union(Range.create(4, 4)); // "null" } - } - - /** Calculate the result range after a binary operation between two numerical type nodes. */ - private Range calculateRangeBinaryOp( - Node leftNode, - Node rightNode, - NumericalBinaryOps op, - TransferInput p) { - if (TypesUtils.isIntegralPrimitive(leftNode.getType()) - && TypesUtils.isIntegralPrimitive(rightNode.getType())) { - Range leftRange = getIntRange(leftNode, p); - Range rightRange = getIntRange(rightNode, p); - Range resultRange; - switch (op) { - case ADDITION: - resultRange = leftRange.plus(rightRange); - break; - case SUBTRACTION: - resultRange = leftRange.minus(rightRange); - break; - case MULTIPLICATION: - resultRange = leftRange.times(rightRange); - break; - case DIVISION: - resultRange = leftRange.divide(rightRange); - break; - case REMAINDER: - resultRange = leftRange.remainder(rightRange); - break; - case SHIFT_LEFT: - resultRange = leftRange.shiftLeft(rightRange); - break; - case SIGNED_SHIFT_RIGHT: - resultRange = leftRange.signedShiftRight(rightRange); - break; - case UNSIGNED_SHIFT_RIGHT: - resultRange = leftRange.unsignedShiftRight(rightRange); - break; - case BITWISE_AND: - resultRange = leftRange.bitwiseAnd(rightRange); - break; - case BITWISE_OR: - resultRange = leftRange.bitwiseOr(rightRange); - break; - case BITWISE_XOR: - resultRange = leftRange.bitwiseXor(rightRange); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + Range concatLengthRange = calculateLengthRangeAddition(leftLengthRange, rightLengthRange); + return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange); + } + + return atypeFactory.UNKNOWNVAL; + } + + public TransferResult stringConcatenation( + Node leftOperand, + Node rightOperand, + TransferInput p, + TransferResult result) { + + AnnotationMirror resultAnno = + createAnnotationForStringConcatenation(leftOperand, rightOperand, p); + + TypeMirror underlyingType = result.getResultValue().getUnderlyingType(); + CFValue newResultValue = analysis.createSingleAnnotationValue(resultAnno, underlyingType); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } + + /** Binary operations that are analyzed by the value checker. */ + enum NumericalBinaryOps { + ADDITION, + SUBTRACTION, + DIVISION, + REMAINDER, + MULTIPLICATION, + SHIFT_LEFT, + SIGNED_SHIFT_RIGHT, + UNSIGNED_SHIFT_RIGHT, + BITWISE_AND, + BITWISE_OR, + BITWISE_XOR; + } + + /** + * Get the refined annotation after a numerical binary operation. + * + * @param leftNode the node that represents the left operand + * @param rightNode the node that represents the right operand + * @param op the operator type + * @param p the transfer input + * @return the result annotation mirror + */ + private AnnotationMirror calculateNumericalBinaryOp( + Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) { + if (!isIntRangeOrIntegralUnknownVal(leftNode, p) + && !isIntRangeOrIntegralUnknownVal(rightNode, p)) { + List resultValues = calculateValuesBinaryOp(leftNode, rightNode, op, p); + return atypeFactory.createNumberAnnotationMirror(resultValues); + } else { + Range resultRange = calculateRangeBinaryOp(leftNode, rightNode, op, p); + return atypeFactory.createIntRangeAnnotation(resultRange); + } + } + + /** Calculate the result range after a binary operation between two numerical type nodes. */ + private Range calculateRangeBinaryOp( + Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) { + if (TypesUtils.isIntegralPrimitive(leftNode.getType()) + && TypesUtils.isIntegralPrimitive(rightNode.getType())) { + Range leftRange = getIntRange(leftNode, p); + Range rightRange = getIntRange(rightNode, p); + Range resultRange; + switch (op) { + case ADDITION: + resultRange = leftRange.plus(rightRange); + break; + case SUBTRACTION: + resultRange = leftRange.minus(rightRange); + break; + case MULTIPLICATION: + resultRange = leftRange.times(rightRange); + break; + case DIVISION: + resultRange = leftRange.divide(rightRange); + break; + case REMAINDER: + resultRange = leftRange.remainder(rightRange); + break; + case SHIFT_LEFT: + resultRange = leftRange.shiftLeft(rightRange); + break; + case SIGNED_SHIFT_RIGHT: + resultRange = leftRange.signedShiftRight(rightRange); + break; + case UNSIGNED_SHIFT_RIGHT: + resultRange = leftRange.unsignedShiftRight(rightRange); + break; + case BITWISE_AND: + resultRange = leftRange.bitwiseAnd(rightRange); + break; + case BITWISE_OR: + resultRange = leftRange.bitwiseOr(rightRange); + break; + case BITWISE_XOR: + resultRange = leftRange.bitwiseXor(rightRange); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + // Any integral type with less than 32 bits would be promoted to 32-bit int type during + // operations. + return leftNode.getType().getKind() == TypeKind.LONG + || rightNode.getType().getKind() == TypeKind.LONG + ? resultRange + : resultRange.intRange(); + } else { + return Range.EVERYTHING; + } + } + + /** Calculate the possible values after a binary operation between two numerical type nodes. */ + private @Nullable List calculateValuesBinaryOp( + Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) { + List lefts = getNumericalValues(leftNode, p); + List rights = getNumericalValues(rightNode, p); + if (lefts == null || rights == null) { + return null; + } + List resultValues = new ArrayList<>(lefts.size() * rights.size()); + for (Number left : lefts) { + NumberMath nmLeft = NumberMath.getNumberMath(left); + for (Number right : rights) { + switch (op) { + case ADDITION: + resultValues.add(nmLeft.plus(right)); + break; + case DIVISION: + Number result = nmLeft.divide(right); + if (result != null) { + resultValues.add(result); } - // Any integral type with less than 32 bits would be promoted to 32-bit int type during - // operations. - return leftNode.getType().getKind() == TypeKind.LONG - || rightNode.getType().getKind() == TypeKind.LONG - ? resultRange - : resultRange.intRange(); - } else { - return Range.EVERYTHING; - } - } - - /** Calculate the possible values after a binary operation between two numerical type nodes. */ - private @Nullable List calculateValuesBinaryOp( - Node leftNode, - Node rightNode, - NumericalBinaryOps op, - TransferInput p) { - List lefts = getNumericalValues(leftNode, p); - List rights = getNumericalValues(rightNode, p); - if (lefts == null || rights == null) { - return null; - } - List resultValues = new ArrayList<>(lefts.size() * rights.size()); - for (Number left : lefts) { - NumberMath nmLeft = NumberMath.getNumberMath(left); - for (Number right : rights) { - switch (op) { - case ADDITION: - resultValues.add(nmLeft.plus(right)); - break; - case DIVISION: - Number result = nmLeft.divide(right); - if (result != null) { - resultValues.add(result); - } - break; - case MULTIPLICATION: - resultValues.add(nmLeft.times(right)); - break; - case REMAINDER: - Number resultR = nmLeft.remainder(right); - if (resultR != null) { - resultValues.add(resultR); - } - break; - case SUBTRACTION: - resultValues.add(nmLeft.minus(right)); - break; - case SHIFT_LEFT: - resultValues.add(nmLeft.shiftLeft(right)); - break; - case SIGNED_SHIFT_RIGHT: - resultValues.add(nmLeft.signedShiftRight(right)); - break; - case UNSIGNED_SHIFT_RIGHT: - resultValues.add(nmLeft.unsignedShiftRight(right)); - break; - case BITWISE_AND: - resultValues.add(nmLeft.bitwiseAnd(right)); - break; - case BITWISE_OR: - resultValues.add(nmLeft.bitwiseOr(right)); - break; - case BITWISE_XOR: - resultValues.add(nmLeft.bitwiseXor(right)); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } + break; + case MULTIPLICATION: + resultValues.add(nmLeft.times(right)); + break; + case REMAINDER: + Number resultR = nmLeft.remainder(right); + if (resultR != null) { + resultValues.add(resultR); } + break; + case SUBTRACTION: + resultValues.add(nmLeft.minus(right)); + break; + case SHIFT_LEFT: + resultValues.add(nmLeft.shiftLeft(right)); + break; + case SIGNED_SHIFT_RIGHT: + resultValues.add(nmLeft.signedShiftRight(right)); + break; + case UNSIGNED_SHIFT_RIGHT: + resultValues.add(nmLeft.unsignedShiftRight(right)); + break; + case BITWISE_AND: + resultValues.add(nmLeft.bitwiseAnd(right)); + break; + case BITWISE_OR: + resultValues.add(nmLeft.bitwiseOr(right)); + break; + case BITWISE_XOR: + resultValues.add(nmLeft.bitwiseXor(right)); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); } - return resultValues; - } - - @Override - public TransferResult visitNumericalAddition( - NumericalAdditionNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalAddition(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalSubtraction(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitNumericalMultiplication( - NumericalMultiplicationNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalMultiplication(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), - n.getRightOperand(), - NumericalBinaryOps.MULTIPLICATION, - p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitIntegerDivision( - IntegerDivisionNode n, TransferInput p) { - TransferResult transferResult = super.visitIntegerDivision(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitFloatingDivision( - FloatingDivisionNode n, TransferInput p) { - TransferResult transferResult = super.visitFloatingDivision(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitIntegerRemainder( - IntegerRemainderNode n, TransferInput p) { - TransferResult transferResult = super.visitIntegerRemainder(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitFloatingRemainder( - FloatingRemainderNode n, TransferInput p) { - TransferResult transferResult = super.visitFloatingRemainder(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitLeftShift( - LeftShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitLeftShift(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitSignedRightShift( - SignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitSignedRightShift(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), - n.getRightOperand(), - NumericalBinaryOps.SIGNED_SHIFT_RIGHT, - p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitUnsignedRightShift( - UnsignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitUnsignedRightShift(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), - n.getRightOperand(), - NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, - p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseAnd( - BitwiseAndNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseAnd(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseOr( - BitwiseOrNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseOr(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p); - return createNewResult(transferResult, resultAnno); + } + } + return resultValues; + } + + @Override + public TransferResult visitNumericalAddition( + NumericalAdditionNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalAddition(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalSubtraction(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitNumericalMultiplication( + NumericalMultiplicationNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalMultiplication(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.MULTIPLICATION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitIntegerDivision( + IntegerDivisionNode n, TransferInput p) { + TransferResult transferResult = super.visitIntegerDivision(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitFloatingDivision( + FloatingDivisionNode n, TransferInput p) { + TransferResult transferResult = super.visitFloatingDivision(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitIntegerRemainder( + IntegerRemainderNode n, TransferInput p) { + TransferResult transferResult = super.visitIntegerRemainder(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitFloatingRemainder( + FloatingRemainderNode n, TransferInput p) { + TransferResult transferResult = super.visitFloatingRemainder(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitLeftShift( + LeftShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitLeftShift(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitSignedRightShift( + SignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitSignedRightShift(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SIGNED_SHIFT_RIGHT, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitUnsignedRightShift( + UnsignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitUnsignedRightShift(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseAnd( + BitwiseAndNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseAnd(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseOr( + BitwiseOrNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseOr(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseXor( + BitwiseXorNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseXor(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_XOR, p); + return createNewResult(transferResult, resultAnno); + } + + /** Unary operations that are analyzed by the value checker. */ + enum NumericalUnaryOps { + PLUS, + MINUS, + BITWISE_COMPLEMENT; + } + + /** + * Get the refined annotation after a numerical unary operation. + * + * @param operand the node that represents the operand + * @param op the operator type + * @param p the transfer input + * @return the result annotation mirror + */ + private AnnotationMirror calculateNumericalUnaryOp( + Node operand, NumericalUnaryOps op, TransferInput p) { + if (!isIntRange(operand, p)) { + List resultValues = calculateValuesUnaryOp(operand, op, p); + return atypeFactory.createNumberAnnotationMirror(resultValues); + } else { + Range resultRange = calculateRangeUnaryOp(operand, op, p); + return atypeFactory.createIntRangeAnnotation(resultRange); + } + } + + /** + * Calculate the result range after a unary operation of a numerical type node. + * + * @param operand the node that represents the operand + * @param op the operator type + * @param p the transfer input + * @return the result annotation mirror + */ + private Range calculateRangeUnaryOp( + Node operand, NumericalUnaryOps op, TransferInput p) { + if (TypesUtils.isIntegralPrimitive(operand.getType())) { + Range range = getIntRange(operand, p); + Range resultRange; + switch (op) { + case PLUS: + resultRange = range.unaryPlus(); + break; + case MINUS: + resultRange = range.unaryMinus(); + break; + case BITWISE_COMPLEMENT: + resultRange = range.bitwiseComplement(); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + // Any integral type with less than 32 bits would be promoted to 32-bit int type during + // operations. + return operand.getType().getKind() == TypeKind.LONG ? resultRange : resultRange.intRange(); + } else { + return Range.EVERYTHING; + } + } + + /** Calculate the possible values after a unary operation of a numerical type node. */ + private @Nullable List calculateValuesUnaryOp( + Node operand, NumericalUnaryOps op, TransferInput p) { + List lefts = getNumericalValues(operand, p); + if (lefts == null) { + return null; + } + List resultValues = new ArrayList<>(lefts.size()); + for (Number left : lefts) { + NumberMath nmLeft = NumberMath.getNumberMath(left); + switch (op) { + case PLUS: + resultValues.add(nmLeft.unaryPlus()); + break; + case MINUS: + resultValues.add(nmLeft.unaryMinus()); + break; + case BITWISE_COMPLEMENT: + resultValues.add(nmLeft.bitwiseComplement()); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + } + return resultValues; + } + + @Override + public TransferResult visitNumericalMinus( + NumericalMinusNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalMinus(n, p); + AnnotationMirror resultAnno = + calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitNumericalPlus( + NumericalPlusNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalPlus(n, p); + AnnotationMirror resultAnno = + calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseComplement( + BitwiseComplementNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseComplement(n, p); + AnnotationMirror resultAnno = + calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.BITWISE_COMPLEMENT, p); + return createNewResult(transferResult, resultAnno); + } + + enum ComparisonOperators { + EQUAL, + NOT_EQUAL, + GREATER_THAN, + GREATER_THAN_EQ, + LESS_THAN, + LESS_THAN_EQ; + } + + private @Nullable List calculateBinaryComparison( + Node leftNode, + CFValue leftValue, + Node rightNode, + CFValue rightValue, + ComparisonOperators op, + CFStore thenStore, + CFStore elseStore) { + AnnotationMirror leftAnno = getValueAnnotation(leftValue); + AnnotationMirror rightAnno = getValueAnnotation(rightValue); + + if (atypeFactory.isIntRange(leftAnno) + || atypeFactory.isIntRange(rightAnno) + || isIntegralUnknownVal(rightNode, rightAnno) + || isIntegralUnknownVal(leftNode, leftAnno)) { + // If either is @UnknownVal, then refineIntRanges will treat it as the max range and + // thus refine it if possible. Also, if either is an @IntVal, then it will be converted + // to a range. This is less precise in some cases, but avoids the complexity of + // comparing a list of values to a range. (This could be implemented in the future.) + return refineIntRanges(leftNode, leftAnno, rightNode, rightAnno, op, thenStore, elseStore); + } + + List lefts = getNumericalValues(leftNode, leftAnno); + List rights = getNumericalValues(rightNode, rightAnno); + + if (lefts == null || rights == null) { + // Appropriately handle bottom when something is compared to bottom. + if (AnnotationUtils.areSame(leftAnno, atypeFactory.BOTTOMVAL) + || AnnotationUtils.areSame(rightAnno, atypeFactory.BOTTOMVAL)) { + return Collections.emptyList(); + } + return null; } - @Override - public TransferResult visitBitwiseXor( - BitwiseXorNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseXor(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_XOR, p); - return createNewResult(transferResult, resultAnno); - } + // This is a list of all the values that the expression can evaluate to. + int numResultValues = lefts.size() * rights.size(); + List resultValues = new ArrayList<>(numResultValues); - /** Unary operations that are analyzed by the value checker. */ - enum NumericalUnaryOps { - PLUS, - MINUS, - BITWISE_COMPLEMENT; - } + // These lists are used to refine the values in the store based on the results of the + // comparison. + List thenLeftVals = new ArrayList<>(numResultValues); + List elseLeftVals = new ArrayList<>(numResultValues); + List thenRightVals = new ArrayList<>(numResultValues); + List elseRightVals = new ArrayList<>(numResultValues); - /** - * Get the refined annotation after a numerical unary operation. - * - * @param operand the node that represents the operand - * @param op the operator type - * @param p the transfer input - * @return the result annotation mirror - */ - private AnnotationMirror calculateNumericalUnaryOp( - Node operand, NumericalUnaryOps op, TransferInput p) { - if (!isIntRange(operand, p)) { - List resultValues = calculateValuesUnaryOp(operand, op, p); - return atypeFactory.createNumberAnnotationMirror(resultValues); - } else { - Range resultRange = calculateRangeUnaryOp(operand, op, p); - return atypeFactory.createIntRangeAnnotation(resultRange); + for (Number left : lefts) { + NumberMath nmLeft = NumberMath.getNumberMath(left); + for (Number right : rights) { + Boolean result; + switch (op) { + case EQUAL: + result = nmLeft.equalTo(right); + break; + case GREATER_THAN: + result = nmLeft.greaterThan(right); + break; + case GREATER_THAN_EQ: + result = nmLeft.greaterThanEq(right); + break; + case LESS_THAN: + result = nmLeft.lessThan(right); + break; + case LESS_THAN_EQ: + result = nmLeft.lessThanEq(right); + break; + case NOT_EQUAL: + result = nmLeft.notEqualTo(right); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); } - } - - /** - * Calculate the result range after a unary operation of a numerical type node. - * - * @param operand the node that represents the operand - * @param op the operator type - * @param p the transfer input - * @return the result annotation mirror - */ - private Range calculateRangeUnaryOp( - Node operand, NumericalUnaryOps op, TransferInput p) { - if (TypesUtils.isIntegralPrimitive(operand.getType())) { - Range range = getIntRange(operand, p); - Range resultRange; - switch (op) { - case PLUS: - resultRange = range.unaryPlus(); - break; - case MINUS: - resultRange = range.unaryMinus(); - break; - case BITWISE_COMPLEMENT: - resultRange = range.bitwiseComplement(); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - // Any integral type with less than 32 bits would be promoted to 32-bit int type during - // operations. - return operand.getType().getKind() == TypeKind.LONG - ? resultRange - : resultRange.intRange(); + resultValues.add(result); + if (result) { + thenLeftVals.add(left); + thenRightVals.add(right); } else { - return Range.EVERYTHING; + elseLeftVals.add(left); + elseRightVals.add(right); } + } + } + + createAnnotationFromResultsAndAddToStore(thenStore, thenLeftVals, leftNode); + createAnnotationFromResultsAndAddToStore(elseStore, elseLeftVals, leftNode); + createAnnotationFromResultsAndAddToStore(thenStore, thenRightVals, rightNode); + createAnnotationFromResultsAndAddToStore(elseStore, elseRightVals, rightNode); + + return resultValues; + } + + /** + * Calculates the result of a binary comparison on a pair of intRange annotations, and refines + * annotations appropriately. + */ + private @Nullable List refineIntRanges( + Node leftNode, + AnnotationMirror leftAnno, + Node rightNode, + AnnotationMirror rightAnno, + ComparisonOperators op, + CFStore thenStore, + CFStore elseStore) { + + Range leftRange = getIntRangeFromAnnotation(leftNode, leftAnno); + Range rightRange = getIntRangeFromAnnotation(rightNode, rightAnno); + + final Range thenRightRange; + final Range thenLeftRange; + final Range elseRightRange; + final Range elseLeftRange; + + switch (op) { + case EQUAL: + thenRightRange = rightRange.refineEqualTo(leftRange); + thenLeftRange = thenRightRange; // Only needs to be computed once. + elseRightRange = rightRange.refineNotEqualTo(leftRange); + elseLeftRange = leftRange.refineNotEqualTo(rightRange); + break; + case GREATER_THAN: + thenLeftRange = leftRange.refineGreaterThan(rightRange); + thenRightRange = rightRange.refineLessThan(leftRange); + elseRightRange = rightRange.refineGreaterThanEq(leftRange); + elseLeftRange = leftRange.refineLessThanEq(rightRange); + break; + case GREATER_THAN_EQ: + thenRightRange = rightRange.refineLessThanEq(leftRange); + thenLeftRange = leftRange.refineGreaterThanEq(rightRange); + elseLeftRange = leftRange.refineLessThan(rightRange); + elseRightRange = rightRange.refineGreaterThan(leftRange); + break; + case LESS_THAN: + thenLeftRange = leftRange.refineLessThan(rightRange); + thenRightRange = rightRange.refineGreaterThan(leftRange); + elseRightRange = rightRange.refineLessThanEq(leftRange); + elseLeftRange = leftRange.refineGreaterThanEq(rightRange); + break; + case LESS_THAN_EQ: + thenRightRange = rightRange.refineGreaterThanEq(leftRange); + thenLeftRange = leftRange.refineLessThanEq(rightRange); + elseLeftRange = leftRange.refineGreaterThan(rightRange); + elseRightRange = rightRange.refineLessThan(leftRange); + break; + case NOT_EQUAL: + thenRightRange = rightRange.refineNotEqualTo(leftRange); + thenLeftRange = leftRange.refineNotEqualTo(rightRange); + elseRightRange = rightRange.refineEqualTo(leftRange); + elseLeftRange = elseRightRange; // Equality only needs to be computed once. + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); } - /** Calculate the possible values after a unary operation of a numerical type node. */ - private @Nullable List calculateValuesUnaryOp( - Node operand, NumericalUnaryOps op, TransferInput p) { - List lefts = getNumericalValues(operand, p); - if (lefts == null) { - return null; - } - List resultValues = new ArrayList<>(lefts.size()); - for (Number left : lefts) { - NumberMath nmLeft = NumberMath.getNumberMath(left); - switch (op) { - case PLUS: - resultValues.add(nmLeft.unaryPlus()); - break; - case MINUS: - resultValues.add(nmLeft.unaryMinus()); - break; - case BITWISE_COMPLEMENT: - resultValues.add(nmLeft.bitwiseComplement()); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } + createAnnotationFromRangeAndAddToStore(thenStore, thenRightRange, rightNode); + createAnnotationFromRangeAndAddToStore(thenStore, thenLeftRange, leftNode); + createAnnotationFromRangeAndAddToStore(elseStore, elseRightRange, rightNode); + createAnnotationFromRangeAndAddToStore(elseStore, elseLeftRange, leftNode); + + // TODO: Refine the type of the comparison. + return null; + } + + /** + * Takes a list of result values (i.e. the values possible after the comparison) and creates the + * appropriate annotation from them, then combines that annotation with the existing annotation on + * the node. The resulting annotation is inserted into the store. + * + * @param store the store + * @param results the result values + * @param node the node whose existing annotation to refine + */ + private void createAnnotationFromResultsAndAddToStore(CFStore store, List results, Node node) { + AnnotationMirror anno = atypeFactory.createResultingAnnotation(node.getType(), results); + addAnnotationToStore(store, anno, node); + } + + /** + * Takes a range and creates the appropriate annotation from it, then combines that annotation + * with the existing annotation on the node. The resulting annotation is inserted into the store. + * + * @param store the store + * @param range the range to create an annotation for + * @param node the node whose existing annotation to refine + */ + private void createAnnotationFromRangeAndAddToStore(CFStore store, Range range, Node node) { + AnnotationMirror anno = atypeFactory.createIntRangeAnnotation(range); + addAnnotationToStore(store, anno, node); + } + + private void addAnnotationToStore(CFStore store, AnnotationMirror anno, Node node) { + // If node is assignment, iterate over lhs and rhs; otherwise, iterator contains just node. + for (Node internal : splitAssignments(node)) { + JavaExpression je = JavaExpression.fromNode(internal); + CFValue currentValueFromStore; + if (CFAbstractStore.canInsertJavaExpression(je)) { + currentValueFromStore = store.getValue(je); + } else { + // Don't just `continue;` which would skip the calls to refine{Array,String}... + currentValueFromStore = null; + } + AnnotationMirror currentAnno = + (currentValueFromStore == null + ? atypeFactory.UNKNOWNVAL + : getValueAnnotation(currentValueFromStore)); + // Combine the new annotations based on the results of the comparison with the existing + // type. + AnnotationMirror newAnno = + qualHierarchy.greatestLowerBoundShallow(anno, je.getType(), currentAnno, je.getType()); + store.insertValue(je, newAnno); + + if (node instanceof FieldAccessNode) { + refineArrayAtLengthAccess((FieldAccessNode) internal, store); + } else if (node instanceof MethodInvocationNode) { + MethodInvocationNode miNode = (MethodInvocationNode) node; + refineAtLengthInvocation(miNode, store); + } + } + } + + @Override + public TransferResult visitLessThan( + LessThanNode n, TransferInput p) { + TransferResult transferResult = super.visitLessThan(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.LESS_THAN, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + public TransferResult visitLessThanOrEqual( + LessThanOrEqualNode n, TransferInput p) { + TransferResult transferResult = super.visitLessThanOrEqual(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.LESS_THAN_EQ, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + public TransferResult visitGreaterThan( + GreaterThanNode n, TransferInput p) { + TransferResult transferResult = super.visitGreaterThan(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.GREATER_THAN, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + public TransferResult visitGreaterThanOrEqual( + GreaterThanOrEqualNode n, TransferInput p) { + TransferResult transferResult = super.visitGreaterThanOrEqual(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.GREATER_THAN_EQ, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult transferResult, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + if (firstValue == null) { + return transferResult; + } + if (TypesUtils.isNumeric(firstNode.getType()) || TypesUtils.isNumeric(secondNode.getType())) { + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + // At least one must be a primitive otherwise reference equality is used. + List resultValues = + calculateBinaryComparison( + firstNode, + firstValue, + secondNode, + secondValue, + notEqualTo ? ComparisonOperators.NOT_EQUAL : ComparisonOperators.EQUAL, + thenStore, + elseStore); + if (transferResult.getResultValue() == null) { + // Happens for case labels + return transferResult; + } + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + return super.strengthenAnnotationOfEqualTo( + transferResult, firstNode, secondNode, firstValue, secondValue, notEqualTo); + } + + @Override + protected void processConditionalPostconditions( + MethodInvocationNode n, + ExecutableElement methodElement, + ExpressionTree tree, + CFStore thenStore, + CFStore elseStore) { + // For String.startsWith(String) and String.endsWith(String), refine the minimum length + // of the receiver to the minimum length of the argument. + ValueMethodIdentifier methodIdentifier = atypeFactory.getMethodIdentifier(); + if (methodIdentifier.isStartsWithMethod(methodElement) + || methodIdentifier.isEndsWithMethod(methodElement)) { + + Node argumentNode = n.getArgument(0); + AnnotationMirror argumentAnno = getArrayOrStringAnnotation(argumentNode); + int minLength = atypeFactory.getMinLenValue(argumentAnno); + // Update the annotation of the receiver + if (minLength != 0) { + JavaExpression receiver = JavaExpression.fromNode(n.getTarget().getReceiver()); + + AnnotationMirror minLenAnno = + atypeFactory.createArrayLenRangeAnnotation(minLength, Integer.MAX_VALUE); + thenStore.insertValuePermitNondeterministic(receiver, minLenAnno); + } + } + + super.processConditionalPostconditions(n, methodElement, tree, thenStore, elseStore); + } + + enum ConditionalOperators { + NOT, + OR, + AND; + } + + private static final List ALL_BOOLEANS = + Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE}); + + private List calculateConditionalOperator( + Node leftNode, Node rightNode, ConditionalOperators op, TransferInput p) { + List lefts = getBooleanValues(leftNode, p); + if (lefts == null) { + lefts = ALL_BOOLEANS; + } + List rights = null; + if (rightNode != null) { + rights = getBooleanValues(rightNode, p); + if (rights == null) { + rights = ALL_BOOLEANS; + } + } + // This list can contain duplicates. It is deduplicated later by createBooleanAnnotation. + List resultValues = new ArrayList<>(2); + switch (op) { + case NOT: + return CollectionsPlume.mapList((Boolean left) -> !left, lefts); + case OR: + for (Boolean left : lefts) { + for (Boolean right : rights) { + resultValues.add(left || right); + } } return resultValues; - } - - @Override - public TransferResult visitNumericalMinus( - NumericalMinusNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalMinus(n, p); - AnnotationMirror resultAnno = - calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitNumericalPlus( - NumericalPlusNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalPlus(n, p); - AnnotationMirror resultAnno = - calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseComplement( - BitwiseComplementNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseComplement(n, p); - AnnotationMirror resultAnno = - calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.BITWISE_COMPLEMENT, p); - return createNewResult(transferResult, resultAnno); - } - - enum ComparisonOperators { - EQUAL, - NOT_EQUAL, - GREATER_THAN, - GREATER_THAN_EQ, - LESS_THAN, - LESS_THAN_EQ; - } - - private @Nullable List calculateBinaryComparison( - Node leftNode, - CFValue leftValue, - Node rightNode, - CFValue rightValue, - ComparisonOperators op, - CFStore thenStore, - CFStore elseStore) { - AnnotationMirror leftAnno = getValueAnnotation(leftValue); - AnnotationMirror rightAnno = getValueAnnotation(rightValue); - - if (atypeFactory.isIntRange(leftAnno) - || atypeFactory.isIntRange(rightAnno) - || isIntegralUnknownVal(rightNode, rightAnno) - || isIntegralUnknownVal(leftNode, leftAnno)) { - // If either is @UnknownVal, then refineIntRanges will treat it as the max range and - // thus refine it if possible. Also, if either is an @IntVal, then it will be converted - // to a range. This is less precise in some cases, but avoids the complexity of - // comparing a list of values to a range. (This could be implemented in the future.) - return refineIntRanges( - leftNode, leftAnno, rightNode, rightAnno, op, thenStore, elseStore); + case AND: + for (Boolean left : lefts) { + for (Boolean right : rights) { + resultValues.add(left && right); + } } - - List lefts = getNumericalValues(leftNode, leftAnno); - List rights = getNumericalValues(rightNode, rightAnno); - - if (lefts == null || rights == null) { - // Appropriately handle bottom when something is compared to bottom. - if (AnnotationUtils.areSame(leftAnno, atypeFactory.BOTTOMVAL) - || AnnotationUtils.areSame(rightAnno, atypeFactory.BOTTOMVAL)) { - return Collections.emptyList(); - } - return null; - } - - // This is a list of all the values that the expression can evaluate to. - int numResultValues = lefts.size() * rights.size(); - List resultValues = new ArrayList<>(numResultValues); - - // These lists are used to refine the values in the store based on the results of the - // comparison. - List thenLeftVals = new ArrayList<>(numResultValues); - List elseLeftVals = new ArrayList<>(numResultValues); - List thenRightVals = new ArrayList<>(numResultValues); - List elseRightVals = new ArrayList<>(numResultValues); - - for (Number left : lefts) { - NumberMath nmLeft = NumberMath.getNumberMath(left); - for (Number right : rights) { - Boolean result; - switch (op) { - case EQUAL: - result = nmLeft.equalTo(right); - break; - case GREATER_THAN: - result = nmLeft.greaterThan(right); - break; - case GREATER_THAN_EQ: - result = nmLeft.greaterThanEq(right); - break; - case LESS_THAN: - result = nmLeft.lessThan(right); - break; - case LESS_THAN_EQ: - result = nmLeft.lessThanEq(right); - break; - case NOT_EQUAL: - result = nmLeft.notEqualTo(right); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - resultValues.add(result); - if (result) { - thenLeftVals.add(left); - thenRightVals.add(right); - } else { - elseLeftVals.add(left); - elseRightVals.add(right); - } - } - } - - createAnnotationFromResultsAndAddToStore(thenStore, thenLeftVals, leftNode); - createAnnotationFromResultsAndAddToStore(elseStore, elseLeftVals, leftNode); - createAnnotationFromResultsAndAddToStore(thenStore, thenRightVals, rightNode); - createAnnotationFromResultsAndAddToStore(elseStore, elseRightVals, rightNode); - return resultValues; } - - /** - * Calculates the result of a binary comparison on a pair of intRange annotations, and refines - * annotations appropriately. - */ - private @Nullable List refineIntRanges( - Node leftNode, - AnnotationMirror leftAnno, - Node rightNode, - AnnotationMirror rightAnno, - ComparisonOperators op, - CFStore thenStore, - CFStore elseStore) { - - Range leftRange = getIntRangeFromAnnotation(leftNode, leftAnno); - Range rightRange = getIntRangeFromAnnotation(rightNode, rightAnno); - - final Range thenRightRange; - final Range thenLeftRange; - final Range elseRightRange; - final Range elseLeftRange; - - switch (op) { - case EQUAL: - thenRightRange = rightRange.refineEqualTo(leftRange); - thenLeftRange = thenRightRange; // Only needs to be computed once. - elseRightRange = rightRange.refineNotEqualTo(leftRange); - elseLeftRange = leftRange.refineNotEqualTo(rightRange); - break; - case GREATER_THAN: - thenLeftRange = leftRange.refineGreaterThan(rightRange); - thenRightRange = rightRange.refineLessThan(leftRange); - elseRightRange = rightRange.refineGreaterThanEq(leftRange); - elseLeftRange = leftRange.refineLessThanEq(rightRange); - break; - case GREATER_THAN_EQ: - thenRightRange = rightRange.refineLessThanEq(leftRange); - thenLeftRange = leftRange.refineGreaterThanEq(rightRange); - elseLeftRange = leftRange.refineLessThan(rightRange); - elseRightRange = rightRange.refineGreaterThan(leftRange); - break; - case LESS_THAN: - thenLeftRange = leftRange.refineLessThan(rightRange); - thenRightRange = rightRange.refineGreaterThan(leftRange); - elseRightRange = rightRange.refineLessThanEq(leftRange); - elseLeftRange = leftRange.refineGreaterThanEq(rightRange); - break; - case LESS_THAN_EQ: - thenRightRange = rightRange.refineGreaterThanEq(leftRange); - thenLeftRange = leftRange.refineLessThanEq(rightRange); - elseLeftRange = leftRange.refineGreaterThan(rightRange); - elseRightRange = rightRange.refineLessThan(leftRange); - break; - case NOT_EQUAL: - thenRightRange = rightRange.refineNotEqualTo(leftRange); - thenLeftRange = leftRange.refineNotEqualTo(rightRange); - elseRightRange = rightRange.refineEqualTo(leftRange); - elseLeftRange = elseRightRange; // Equality only needs to be computed once. - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - - createAnnotationFromRangeAndAddToStore(thenStore, thenRightRange, rightNode); - createAnnotationFromRangeAndAddToStore(thenStore, thenLeftRange, leftNode); - createAnnotationFromRangeAndAddToStore(elseStore, elseRightRange, rightNode); - createAnnotationFromRangeAndAddToStore(elseStore, elseLeftRange, leftNode); - - // TODO: Refine the type of the comparison. - return null; - } - - /** - * Takes a list of result values (i.e. the values possible after the comparison) and creates the - * appropriate annotation from them, then combines that annotation with the existing annotation - * on the node. The resulting annotation is inserted into the store. - * - * @param store the store - * @param results the result values - * @param node the node whose existing annotation to refine - */ - private void createAnnotationFromResultsAndAddToStore( - CFStore store, List results, Node node) { - AnnotationMirror anno = atypeFactory.createResultingAnnotation(node.getType(), results); - addAnnotationToStore(store, anno, node); - } - - /** - * Takes a range and creates the appropriate annotation from it, then combines that annotation - * with the existing annotation on the node. The resulting annotation is inserted into the - * store. - * - * @param store the store - * @param range the range to create an annotation for - * @param node the node whose existing annotation to refine - */ - private void createAnnotationFromRangeAndAddToStore(CFStore store, Range range, Node node) { - AnnotationMirror anno = atypeFactory.createIntRangeAnnotation(range); - addAnnotationToStore(store, anno, node); - } - - private void addAnnotationToStore(CFStore store, AnnotationMirror anno, Node node) { - // If node is assignment, iterate over lhs and rhs; otherwise, iterator contains just node. - for (Node internal : splitAssignments(node)) { - JavaExpression je = JavaExpression.fromNode(internal); - CFValue currentValueFromStore; - if (CFAbstractStore.canInsertJavaExpression(je)) { - currentValueFromStore = store.getValue(je); - } else { - // Don't just `continue;` which would skip the calls to refine{Array,String}... - currentValueFromStore = null; - } - AnnotationMirror currentAnno = - (currentValueFromStore == null - ? atypeFactory.UNKNOWNVAL - : getValueAnnotation(currentValueFromStore)); - // Combine the new annotations based on the results of the comparison with the existing - // type. - AnnotationMirror newAnno = - qualHierarchy.greatestLowerBoundShallow( - anno, je.getType(), currentAnno, je.getType()); - store.insertValue(je, newAnno); - - if (node instanceof FieldAccessNode) { - refineArrayAtLengthAccess((FieldAccessNode) internal, store); - } else if (node instanceof MethodInvocationNode) { - MethodInvocationNode miNode = (MethodInvocationNode) node; - refineAtLengthInvocation(miNode, store); - } - } - } - - @Override - public TransferResult visitLessThan( - LessThanNode n, TransferInput p) { - TransferResult transferResult = super.visitLessThan(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.LESS_THAN, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - public TransferResult visitLessThanOrEqual( - LessThanOrEqualNode n, TransferInput p) { - TransferResult transferResult = super.visitLessThanOrEqual(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.LESS_THAN_EQ, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - public TransferResult visitGreaterThan( - GreaterThanNode n, TransferInput p) { - TransferResult transferResult = super.visitGreaterThan(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.GREATER_THAN, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - public TransferResult visitGreaterThanOrEqual( - GreaterThanOrEqualNode n, TransferInput p) { - TransferResult transferResult = super.visitGreaterThanOrEqual(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.GREATER_THAN_EQ, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult transferResult, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - if (firstValue == null) { - return transferResult; - } - if (TypesUtils.isNumeric(firstNode.getType()) - || TypesUtils.isNumeric(secondNode.getType())) { - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - // At least one must be a primitive otherwise reference equality is used. - List resultValues = - calculateBinaryComparison( - firstNode, - firstValue, - secondNode, - secondValue, - notEqualTo ? ComparisonOperators.NOT_EQUAL : ComparisonOperators.EQUAL, - thenStore, - elseStore); - if (transferResult.getResultValue() == null) { - // Happens for case labels - return transferResult; - } - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - return super.strengthenAnnotationOfEqualTo( - transferResult, firstNode, secondNode, firstValue, secondValue, notEqualTo); - } - - @Override - protected void processConditionalPostconditions( - MethodInvocationNode n, - ExecutableElement methodElement, - ExpressionTree tree, - CFStore thenStore, - CFStore elseStore) { - // For String.startsWith(String) and String.endsWith(String), refine the minimum length - // of the receiver to the minimum length of the argument. - ValueMethodIdentifier methodIdentifier = atypeFactory.getMethodIdentifier(); - if (methodIdentifier.isStartsWithMethod(methodElement) - || methodIdentifier.isEndsWithMethod(methodElement)) { - - Node argumentNode = n.getArgument(0); - AnnotationMirror argumentAnno = getArrayOrStringAnnotation(argumentNode); - int minLength = atypeFactory.getMinLenValue(argumentAnno); - // Update the annotation of the receiver - if (minLength != 0) { - JavaExpression receiver = JavaExpression.fromNode(n.getTarget().getReceiver()); - - AnnotationMirror minLenAnno = - atypeFactory.createArrayLenRangeAnnotation(minLength, Integer.MAX_VALUE); - thenStore.insertValuePermitNondeterministic(receiver, minLenAnno); - } - } - - super.processConditionalPostconditions(n, methodElement, tree, thenStore, elseStore); - } - - enum ConditionalOperators { - NOT, - OR, - AND; - } - - private static final List ALL_BOOLEANS = - Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE}); - - private List calculateConditionalOperator( - Node leftNode, - Node rightNode, - ConditionalOperators op, - TransferInput p) { - List lefts = getBooleanValues(leftNode, p); - if (lefts == null) { - lefts = ALL_BOOLEANS; - } - List rights = null; - if (rightNode != null) { - rights = getBooleanValues(rightNode, p); - if (rights == null) { - rights = ALL_BOOLEANS; - } - } - // This list can contain duplicates. It is deduplicated later by createBooleanAnnotation. - List resultValues = new ArrayList<>(2); - switch (op) { - case NOT: - return CollectionsPlume.mapList((Boolean left) -> !left, lefts); - case OR: - for (Boolean left : lefts) { - for (Boolean right : rights) { - resultValues.add(left || right); - } - } - return resultValues; - case AND: - for (Boolean left : lefts) { - for (Boolean right : rights) { - resultValues.add(left && right); - } - } - return resultValues; - } - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - - @Override - public TransferResult visitEqualTo( - EqualToNode n, TransferInput p) { - TransferResult res = super.visitEqualTo(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - CFValue leftV = p.getValueOfSubNode(leftN); - CFValue rightV = p.getValueOfSubNode(rightN); - - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); - - Boolean leftBoolean = getBooleanValue(leftV); - if (leftBoolean != null) { - CFValue notLeftV = createBooleanCFValue(!leftBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, true); - } - Boolean rightBoolean = getBooleanValue(rightV); - if (rightBoolean != null) { - CFValue notRightV = createBooleanCFValue(!rightBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, true); - } - - return res; - } - - @Override - public TransferResult visitNotEqual( - NotEqualNode n, TransferInput p) { - TransferResult res = super.visitNotEqual(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - CFValue leftV = p.getValueOfSubNode(leftN); - CFValue rightV = p.getValueOfSubNode(rightN); - - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); - - Boolean leftBoolean = getBooleanValue(leftV); - if (leftBoolean != null) { - CFValue notLeftV = createBooleanCFValue(!leftBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, false); - } - Boolean rightBoolean = getBooleanValue(rightV); - if (rightBoolean != null) { - CFValue notRightV = createBooleanCFValue(!rightBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, false); - } - - return res; - } - - @Override - public TransferResult visitConditionalNot( - ConditionalNotNode n, TransferInput p) { - TransferResult transferResult = super.visitConditionalNot(n, p); - List resultValues = - calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p); - return createNewResultBoolean( - transferResult.getThenStore(), - transferResult.getElseStore(), - resultValues, - transferResult.getResultValue().getUnderlyingType()); - } - - @Override - public TransferResult visitConditionalAnd( - ConditionalAndNode n, TransferInput p) { - TransferResult transferResult = super.visitConditionalAnd(n, p); - List resultValues = - calculateConditionalOperator( - n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p); - return createNewResultBoolean( - transferResult.getThenStore(), - transferResult.getElseStore(), - resultValues, - transferResult.getResultValue().getUnderlyingType()); - } - - @Override - public TransferResult visitConditionalOr( - ConditionalOrNode n, TransferInput p) { - TransferResult transferResult = super.visitConditionalOr(n, p); - List resultValues = - calculateConditionalOperator( - n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p); - return createNewResultBoolean( - transferResult.getThenStore(), - transferResult.getElseStore(), - resultValues, - transferResult.getResultValue().getUnderlyingType()); - } + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + + @Override + public TransferResult visitEqualTo( + EqualToNode n, TransferInput p) { + TransferResult res = super.visitEqualTo(n, p); + + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + CFValue leftV = p.getValueOfSubNode(leftN); + CFValue rightV = p.getValueOfSubNode(rightN); + + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); + + Boolean leftBoolean = getBooleanValue(leftV); + if (leftBoolean != null) { + CFValue notLeftV = createBooleanCFValue(!leftBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, true); + } + Boolean rightBoolean = getBooleanValue(rightV); + if (rightBoolean != null) { + CFValue notRightV = createBooleanCFValue(!rightBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, true); + } + + return res; + } + + @Override + public TransferResult visitNotEqual( + NotEqualNode n, TransferInput p) { + TransferResult res = super.visitNotEqual(n, p); + + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + CFValue leftV = p.getValueOfSubNode(leftN); + CFValue rightV = p.getValueOfSubNode(rightN); + + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); + + Boolean leftBoolean = getBooleanValue(leftV); + if (leftBoolean != null) { + CFValue notLeftV = createBooleanCFValue(!leftBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, false); + } + Boolean rightBoolean = getBooleanValue(rightV); + if (rightBoolean != null) { + CFValue notRightV = createBooleanCFValue(!rightBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, false); + } + + return res; + } + + @Override + public TransferResult visitConditionalNot( + ConditionalNotNode n, TransferInput p) { + TransferResult transferResult = super.visitConditionalNot(n, p); + List resultValues = + calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p); + return createNewResultBoolean( + transferResult.getThenStore(), + transferResult.getElseStore(), + resultValues, + transferResult.getResultValue().getUnderlyingType()); + } + + @Override + public TransferResult visitConditionalAnd( + ConditionalAndNode n, TransferInput p) { + TransferResult transferResult = super.visitConditionalAnd(n, p); + List resultValues = + calculateConditionalOperator( + n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p); + return createNewResultBoolean( + transferResult.getThenStore(), + transferResult.getElseStore(), + resultValues, + transferResult.getResultValue().getUnderlyingType()); + } + + @Override + public TransferResult visitConditionalOr( + ConditionalOrNode n, TransferInput p) { + TransferResult transferResult = super.visitConditionalOr(n, p); + List resultValues = + calculateConditionalOperator( + n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p); + return createNewResultBoolean( + transferResult.getThenStore(), + transferResult.getElseStore(), + resultValues, + transferResult.getResultValue().getUnderlyingType()); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java index 826b9d3a71d..78f57f6aa4c 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java @@ -11,7 +11,19 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Name; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.BinaryName; import org.checkerframework.checker.signature.qual.Identifier; @@ -28,673 +40,631 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.Name; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** The TreeAnnotator for this AnnotatedTypeFactory. It adds/replaces annotations. */ class ValueTreeAnnotator extends TreeAnnotator { - /** The type factory to use. Shadows the field from the superclass with a more specific type. */ - @SuppressWarnings("HidingField") - protected final ValueAnnotatedTypeFactory atypeFactory; - - /** - * The domain of the Constant Value Checker: the types for which it estimates possible values. - */ - protected static final Set COVERED_CLASS_STRINGS = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - "int", - "java.lang.Integer", - "double", - "java.lang.Double", - "byte", - "java.lang.Byte", - "java.lang.String", - "char", - "java.lang.Character", - "float", - "java.lang.Float", - "boolean", - "java.lang.Boolean", - "long", - "java.lang.Long", - "short", - "java.lang.Short", - "char[]"))); - - /** - * Create a ValueTreeAnnotator. - * - * @param atypeFactory the ValueAnnotatedTypeFactory to use - */ - public ValueTreeAnnotator(ValueAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - this.atypeFactory = atypeFactory; + /** The type factory to use. Shadows the field from the superclass with a more specific type. */ + @SuppressWarnings("HidingField") + protected final ValueAnnotatedTypeFactory atypeFactory; + + /** The domain of the Constant Value Checker: the types for which it estimates possible values. */ + protected static final Set COVERED_CLASS_STRINGS = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + "int", + "java.lang.Integer", + "double", + "java.lang.Double", + "byte", + "java.lang.Byte", + "java.lang.String", + "char", + "java.lang.Character", + "float", + "java.lang.Float", + "boolean", + "java.lang.Boolean", + "long", + "java.lang.Long", + "short", + "java.lang.Short", + "char[]"))); + + /** + * Create a ValueTreeAnnotator. + * + * @param atypeFactory the ValueAnnotatedTypeFactory to use + */ + public ValueTreeAnnotator(ValueAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + this.atypeFactory = atypeFactory; + } + + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + + List dimensions = tree.getDimensions(); + List initializers = tree.getInitializers(); + + // Array construction can provide dimensions or use an initializer. + + // Dimensions provided + if (!dimensions.isEmpty()) { + handleDimensions(dimensions, (AnnotatedTypeMirror.AnnotatedArrayType) type); + } else { + // Initializer used + handleInitializers(initializers, (AnnotatedTypeMirror.AnnotatedArrayType) type); + + AnnotationMirror newQual; + Class clazz = TypesUtils.getClassFromType(type.getUnderlyingType()); + String stringVal = null; + if (clazz == char[].class) { + stringVal = getCharArrayStringVal(initializers); + } + + if (stringVal != null) { + newQual = atypeFactory.createStringAnnotation(Collections.singletonList(stringVal)); + type.replaceAnnotation(newQual); + } } - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - - List dimensions = tree.getDimensions(); - List initializers = tree.getInitializers(); - - // Array construction can provide dimensions or use an initializer. + return null; + } + + /** + * Recursive method to handle array initializations. Recursively descends the initializer to find + * each dimension's size and create the appropriate annotation for it. + * + *

If the annotation of the dimension is {@code @IntVal}, create an {@code @ArrayLen} with the + * same set of possible values. If the annotation is {@code @IntRange}, create an + * {@code @ArrayLenRange}. If the annotation is {@code @BottomVal}, create an {@code @BottomVal} + * instead. In other cases, no annotations are created. + * + * @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of the + * size of that dimension + * @param type the AnnotatedTypeMirror of the array, which is side-effected by this method + */ + private void handleDimensions( + List dimensions, AnnotatedTypeMirror.AnnotatedArrayType type) { + if (dimensions.size() > 1) { + handleDimensions( + dimensions.subList(1, dimensions.size()), + (AnnotatedTypeMirror.AnnotatedArrayType) type.getComponentType()); + } + AnnotationMirror dimType = + atypeFactory + .getAnnotatedType(dimensions.get(0)) + .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + + if (AnnotationUtils.areSameByName(dimType, atypeFactory.BOTTOMVAL)) { + type.replaceAnnotation(atypeFactory.BOTTOMVAL); + } else { + RangeOrListOfValues rolv = null; + if (atypeFactory.isIntRange(dimType)) { + rolv = new RangeOrListOfValues(atypeFactory.getRange(dimType)); + } else if (AnnotationUtils.areSameByName(dimType, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + rolv = + new RangeOrListOfValues( + RangeOrListOfValues.convertLongsToInts(atypeFactory.getIntValues(dimType))); + } + if (rolv != null) { + AnnotationMirror newQual = rolv.createAnnotation(atypeFactory); + type.replaceAnnotation(newQual); + } + } + } + + /** + * Adds the ArrayLen/ArrayLenRange annotation from the array initializers to {@code type}. + * + *

If type is a multi-dimensional array, the initializers might also contain arrays, so this + * method adds the annotations for those initializers, too. + * + * @param initializers initializer trees + * @param type array type to which annotations are added + */ + private void handleInitializers( + List initializers, AnnotatedTypeMirror.AnnotatedArrayType type) { + + type.replaceAnnotation( + atypeFactory.createArrayLenAnnotation(Collections.singletonList(initializers.size()))); + + if (type.getComponentType().getKind() != TypeKind.ARRAY) { + return; + } - // Dimensions provided - if (!dimensions.isEmpty()) { - handleDimensions(dimensions, (AnnotatedTypeMirror.AnnotatedArrayType) type); + // A list of arrayLens. arrayLenOfDimensions.get(i) is the array lengths for the ith + // dimension. + List arrayLenOfDimensions = new ArrayList<>(); + for (ExpressionTree init : initializers) { + AnnotatedTypeMirror componentType = atypeFactory.getAnnotatedType(init); + int dimension = 0; + while (componentType.getKind() == TypeKind.ARRAY) { + RangeOrListOfValues rolv = null; + if (dimension < arrayLenOfDimensions.size()) { + rolv = arrayLenOfDimensions.get(dimension); + } + AnnotationMirror arrayLen = componentType.getAnnotation(ArrayLen.class); + if (arrayLen != null) { + List currentLengths = atypeFactory.getArrayLength(arrayLen); + if (rolv != null) { + rolv.addAll(currentLengths); + } else { + arrayLenOfDimensions.add(new RangeOrListOfValues(currentLengths)); + } } else { - // Initializer used - handleInitializers(initializers, (AnnotatedTypeMirror.AnnotatedArrayType) type); - - AnnotationMirror newQual; - Class clazz = TypesUtils.getClassFromType(type.getUnderlyingType()); - String stringVal = null; - if (clazz == char[].class) { - stringVal = getCharArrayStringVal(initializers); - } - - if (stringVal != null) { - newQual = atypeFactory.createStringAnnotation(Collections.singletonList(stringVal)); - type.replaceAnnotation(newQual); - } + // Check for an arrayLenRange annotation + AnnotationMirror arrayLenRangeAnno = componentType.getAnnotation(ArrayLenRange.class); + Range range; + if (arrayLenRangeAnno != null) { + range = atypeFactory.getRange(arrayLenRangeAnno); + } else { + range = Range.EVERYTHING; + } + if (rolv != null) { + rolv.add(range); + } else { + arrayLenOfDimensions.add(new RangeOrListOfValues(range)); + } } - return null; + dimension++; + componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); + } } - /** - * Recursive method to handle array initializations. Recursively descends the initializer to - * find each dimension's size and create the appropriate annotation for it. - * - *

If the annotation of the dimension is {@code @IntVal}, create an {@code @ArrayLen} with - * the same set of possible values. If the annotation is {@code @IntRange}, create an - * {@code @ArrayLenRange}. If the annotation is {@code @BottomVal}, create an {@code @BottomVal} - * instead. In other cases, no annotations are created. - * - * @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of the - * size of that dimension - * @param type the AnnotatedTypeMirror of the array, which is side-effected by this method - */ - private void handleDimensions( - List dimensions, - AnnotatedTypeMirror.AnnotatedArrayType type) { - if (dimensions.size() > 1) { - handleDimensions( - dimensions.subList(1, dimensions.size()), - (AnnotatedTypeMirror.AnnotatedArrayType) type.getComponentType()); - } - AnnotationMirror dimType = - atypeFactory - .getAnnotatedType(dimensions.get(0)) - .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - - if (AnnotationUtils.areSameByName(dimType, atypeFactory.BOTTOMVAL)) { - type.replaceAnnotation(atypeFactory.BOTTOMVAL); + AnnotatedTypeMirror componentType = type.getComponentType(); + int i = 0; + while (componentType.getKind() == TypeKind.ARRAY && i < arrayLenOfDimensions.size()) { + RangeOrListOfValues rolv = arrayLenOfDimensions.get(i); + componentType.addAnnotation(rolv.createAnnotation(atypeFactory)); + componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); + i++; + } + } + + /** Convert a char array to a String. Return null if unable to convert. */ + private @Nullable String getCharArrayStringVal(List initializers) { + boolean allLiterals = true; + StringBuilder stringVal = new StringBuilder(); + for (ExpressionTree e : initializers) { + Range range = + atypeFactory.getRange( + atypeFactory.getAnnotatedType(e).getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + if (range != null && range.from == range.to) { + char charVal = (char) range.from; + stringVal.append(charVal); + } else { + allLiterals = false; + break; + } + } + if (allLiterals) { + return stringVal.toString(); + } + // If any part of the initializer isn't known, + // the stringval isn't known. + return null; + } + + // Side-effects the `atm` formal parameter. + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) { + if (handledByValueChecker(atm)) { + AnnotationMirror oldAnno = + atypeFactory + .getAnnotatedType(tree.getExpression()) + .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + if (oldAnno == null) { + return null; + } + + // I would like to call ((AnnotatedTypeTree) castTree).hasAnnotation(Unsigned.class), + // but `Unsigned` is in the checker package and this code is in the common package. + List annoTrees = + TreeUtils.getExplicitAnnotationTrees(null, tree.getType()); + List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); + boolean isUnsigned = + AnnotationUtils.containsSameByName( + annos, "org.checkerframework.checker.signedness.qual.Unsigned"); + + TypeMirror newType = atm.getUnderlyingType(); + AnnotationMirror newAnno; + Range range; + + if (TypesUtils.isString(newType) || newType.getKind() == TypeKind.ARRAY) { + // Strings and arrays do not allow conversions + newAnno = oldAnno; + } else if (atypeFactory.isIntRange(oldAnno) + && (range = atypeFactory.getRange(oldAnno)) + .isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + Class newClass = TypesUtils.getClassFromType(newType); + if (newClass == String.class) { + newAnno = atypeFactory.UNKNOWNVAL; + } else if (newClass == Boolean.class || newClass == boolean.class) { + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: can't convert int to boolean"); } else { - RangeOrListOfValues rolv = null; - if (atypeFactory.isIntRange(dimType)) { - rolv = new RangeOrListOfValues(atypeFactory.getRange(dimType)); - } else if (AnnotationUtils.areSameByName( - dimType, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - rolv = - new RangeOrListOfValues( - RangeOrListOfValues.convertLongsToInts( - atypeFactory.getIntValues(dimType))); - } - if (rolv != null) { - AnnotationMirror newQual = rolv.createAnnotation(atypeFactory); - type.replaceAnnotation(newQual); - } + newAnno = atypeFactory.createIntRangeAnnotation(NumberUtils.castRange(newType, range)); } + } else { + List values = + ValueCheckerUtils.getValuesCastedToType(oldAnno, newType, isUnsigned, atypeFactory); + newAnno = atypeFactory.createResultingAnnotation(atm.getUnderlyingType(), values); + } + atm.addMissingAnnotations(Collections.singleton(newAnno)); + } else if (atm.getKind() == TypeKind.ARRAY) { + if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { + atm.addMissingAnnotations(Collections.singleton(atypeFactory.BOTTOMVAL)); + } } + return null; + } + + /** + * Get the "value" element/field of the annotation on {@code type}, casted to the given type. + * Empty list means no value is possible (dead code). Null means no information is known -- any + * value is possible. + * + * @param type the type with a Value Checker annotation + * @param castTo the type to cast to + * @return the Value Checker annotation's value, casted to the given type + */ + private @Nullable List getValues(AnnotatedTypeMirror type, TypeMirror castTo) { + return getValues(type, castTo, false); + } + + /** + * Get the "value" element/field of the annotation on {@code type}, casted to the given type. + * Empty list means no value is possible (dead code). Null means no information is known -- any + * value is possible. + * + * @param type the type with a Value Checker annotation + * @param castTo the type to cast to + * @param isUnsigned if true, treat {@code castTo} as unsigned + * @return the Value Checker annotation's value, casted to the given type + */ + private @Nullable List getValues( + AnnotatedTypeMirror type, TypeMirror castTo, boolean isUnsigned) { + AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + if (anno == null) { + // If type is an AnnotatedTypeVariable (or other type without a primary annotation) + // then anno will be null. It would be safe to use the annotation on the upper + // bound; however, unless the upper bound was explicitly annotated, it will be + // unknown. AnnotatedTypes.findEffectiveAnnotationInHierarchy(, toSearch, top) + return null; + } + return ValueCheckerUtils.getValuesCastedToType(anno, castTo, isUnsigned, atypeFactory); + } - /** - * Adds the ArrayLen/ArrayLenRange annotation from the array initializers to {@code type}. - * - *

If type is a multi-dimensional array, the initializers might also contain arrays, so this - * method adds the annotations for those initializers, too. - * - * @param initializers initializer trees - * @param type array type to which annotations are added - */ - private void handleInitializers( - List initializers, - AnnotatedTypeMirror.AnnotatedArrayType type) { - - type.replaceAnnotation( - atypeFactory.createArrayLenAnnotation( - Collections.singletonList(initializers.size()))); - - if (type.getComponentType().getKind() != TypeKind.ARRAY) { - return; - } - - // A list of arrayLens. arrayLenOfDimensions.get(i) is the array lengths for the ith - // dimension. - List arrayLenOfDimensions = new ArrayList<>(); - for (ExpressionTree init : initializers) { - AnnotatedTypeMirror componentType = atypeFactory.getAnnotatedType(init); - int dimension = 0; - while (componentType.getKind() == TypeKind.ARRAY) { - RangeOrListOfValues rolv = null; - if (dimension < arrayLenOfDimensions.size()) { - rolv = arrayLenOfDimensions.get(dimension); - } - AnnotationMirror arrayLen = componentType.getAnnotation(ArrayLen.class); - if (arrayLen != null) { - List currentLengths = atypeFactory.getArrayLength(arrayLen); - if (rolv != null) { - rolv.addAll(currentLengths); - } else { - arrayLenOfDimensions.add(new RangeOrListOfValues(currentLengths)); - } - } else { - // Check for an arrayLenRange annotation - AnnotationMirror arrayLenRangeAnno = - componentType.getAnnotation(ArrayLenRange.class); - Range range; - if (arrayLenRangeAnno != null) { - range = atypeFactory.getRange(arrayLenRangeAnno); - } else { - range = Range.EVERYTHING; - } - if (rolv != null) { - rolv.add(range); - } else { - arrayLenOfDimensions.add(new RangeOrListOfValues(range)); - } - } - - dimension++; - componentType = - ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); - } - } - - AnnotatedTypeMirror componentType = type.getComponentType(); - int i = 0; - while (componentType.getKind() == TypeKind.ARRAY && i < arrayLenOfDimensions.size()) { - RangeOrListOfValues rolv = arrayLenOfDimensions.get(i); - componentType.addAnnotation(rolv.createAnnotation(atypeFactory)); - componentType = - ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); - i++; - } + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!handledByValueChecker(type)) { + return null; } + Object value = tree.getValue(); + switch (tree.getKind()) { + case BOOLEAN_LITERAL: + AnnotationMirror boolAnno = + atypeFactory.createBooleanAnnotation(Collections.singletonList((Boolean) value)); + type.replaceAnnotation(boolAnno); + return null; - /** Convert a char array to a String. Return null if unable to convert. */ - private @Nullable String getCharArrayStringVal(List initializers) { - boolean allLiterals = true; - StringBuilder stringVal = new StringBuilder(); - for (ExpressionTree e : initializers) { - Range range = - atypeFactory.getRange( - atypeFactory - .getAnnotatedType(e) - .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - if (range != null && range.from == range.to) { - char charVal = (char) range.from; - stringVal.append(charVal); - } else { - allLiterals = false; - break; - } - } - if (allLiterals) { - return stringVal.toString(); - } - // If any part of the initializer isn't known, - // the stringval isn't known. + case CHAR_LITERAL: + AnnotationMirror charAnno = + atypeFactory.createCharAnnotation(Collections.singletonList((Character) value)); + type.replaceAnnotation(charAnno); return null; - } - // Side-effects the `atm` formal parameter. - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) { - if (handledByValueChecker(atm)) { - AnnotationMirror oldAnno = - atypeFactory - .getAnnotatedType(tree.getExpression()) - .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - if (oldAnno == null) { - return null; - } - - // I would like to call ((AnnotatedTypeTree) castTree).hasAnnotation(Unsigned.class), - // but `Unsigned` is in the checker package and this code is in the common package. - List annoTrees = - TreeUtils.getExplicitAnnotationTrees(null, tree.getType()); - List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); - boolean isUnsigned = - AnnotationUtils.containsSameByName( - annos, "org.checkerframework.checker.signedness.qual.Unsigned"); - - TypeMirror newType = atm.getUnderlyingType(); - AnnotationMirror newAnno; - Range range; - - if (TypesUtils.isString(newType) || newType.getKind() == TypeKind.ARRAY) { - // Strings and arrays do not allow conversions - newAnno = oldAnno; - } else if (atypeFactory.isIntRange(oldAnno) - && (range = atypeFactory.getRange(oldAnno)) - .isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - Class newClass = TypesUtils.getClassFromType(newType); - if (newClass == String.class) { - newAnno = atypeFactory.UNKNOWNVAL; - } else if (newClass == Boolean.class || newClass == boolean.class) { - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: can't convert int to boolean"); - } else { - newAnno = - atypeFactory.createIntRangeAnnotation( - NumberUtils.castRange(newType, range)); - } - } else { - List values = - ValueCheckerUtils.getValuesCastedToType( - oldAnno, newType, isUnsigned, atypeFactory); - newAnno = atypeFactory.createResultingAnnotation(atm.getUnderlyingType(), values); - } - atm.addMissingAnnotations(Collections.singleton(newAnno)); - } else if (atm.getKind() == TypeKind.ARRAY) { - if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { - atm.addMissingAnnotations(Collections.singleton(atypeFactory.BOTTOMVAL)); - } - } + case DOUBLE_LITERAL: + case FLOAT_LITERAL: + case INT_LITERAL: + case LONG_LITERAL: + AnnotationMirror numberAnno = + atypeFactory.createNumberAnnotationMirror(Collections.singletonList((Number) value)); + type.replaceAnnotation(numberAnno); + return null; + case STRING_LITERAL: + AnnotationMirror stringAnno = + atypeFactory.createStringAnnotation(Collections.singletonList((String) value)); + type.replaceAnnotation(stringAnno); + return null; + default: return null; } - - /** - * Get the "value" element/field of the annotation on {@code type}, casted to the given type. - * Empty list means no value is possible (dead code). Null means no information is known -- any - * value is possible. - * - * @param type the type with a Value Checker annotation - * @param castTo the type to cast to - * @return the Value Checker annotation's value, casted to the given type - */ - private @Nullable List getValues(AnnotatedTypeMirror type, TypeMirror castTo) { - return getValues(type, castTo, false); + } + + /** + * Given a MemberSelectTree representing a method call, return true if the method's declaration is + * annotated with {@code @StaticallyExecutable}. + */ + private boolean methodIsStaticallyExecutable(Element method) { + return atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null; + } + + /** + * Returns the Range of the Math.min or Math.max method, or null if the argument is none of these + * methods or their arguments are not annotated in ValueChecker hierarchy. + * + * @return the Range of the Math.min or Math.max method, or null if the argument is none of these + * methods or their arguments are not annotated in ValueChecker hierarchy + */ + private @Nullable Range getRangeForMathMinMax(MethodInvocationTree tree) { + if (atypeFactory.getMethodIdentifier().isMathMin(tree, atypeFactory.getProcessingEnv())) { + AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); + Range rangeArg1 = + atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + Range rangeArg2 = + atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + if (rangeArg1 != null && rangeArg2 != null) { + return rangeArg1.min(rangeArg2); + } + } else if (atypeFactory + .getMethodIdentifier() + .isMathMax(tree, atypeFactory.getProcessingEnv())) { + AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); + Range rangeArg1 = + atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + Range rangeArg2 = + atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + if (rangeArg1 != null && rangeArg2 != null) { + return rangeArg1.max(rangeArg2); + } } - - /** - * Get the "value" element/field of the annotation on {@code type}, casted to the given type. - * Empty list means no value is possible (dead code). Null means no information is known -- any - * value is possible. - * - * @param type the type with a Value Checker annotation - * @param castTo the type to cast to - * @param isUnsigned if true, treat {@code castTo} as unsigned - * @return the Value Checker annotation's value, casted to the given type - */ - private @Nullable List getValues( - AnnotatedTypeMirror type, TypeMirror castTo, boolean isUnsigned) { - AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - if (anno == null) { - // If type is an AnnotatedTypeVariable (or other type without a primary annotation) - // then anno will be null. It would be safe to use the annotation on the upper - // bound; however, unless the upper bound was explicitly annotated, it will be - // unknown. AnnotatedTypes.findEffectiveAnnotationInHierarchy(, toSearch, top) - return null; - } - return ValueCheckerUtils.getValuesCastedToType(anno, castTo, isUnsigned, atypeFactory); + return null; + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (type.hasAnnotation(atypeFactory.UNKNOWNVAL)) { + Range range = getRangeForMathMinMax(tree); + if (range != null) { + type.replaceAnnotation(atypeFactory.createIntRangeAnnotation(range)); + } } - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!handledByValueChecker(type)) { - return null; - } - Object value = tree.getValue(); - switch (tree.getKind()) { - case BOOLEAN_LITERAL: - AnnotationMirror boolAnno = - atypeFactory.createBooleanAnnotation( - Collections.singletonList((Boolean) value)); - type.replaceAnnotation(boolAnno); - return null; - - case CHAR_LITERAL: - AnnotationMirror charAnno = - atypeFactory.createCharAnnotation( - Collections.singletonList((Character) value)); - type.replaceAnnotation(charAnno); - return null; - - case DOUBLE_LITERAL: - case FLOAT_LITERAL: - case INT_LITERAL: - case LONG_LITERAL: - AnnotationMirror numberAnno = - atypeFactory.createNumberAnnotationMirror( - Collections.singletonList((Number) value)); - type.replaceAnnotation(numberAnno); - return null; - case STRING_LITERAL: - AnnotationMirror stringAnno = - atypeFactory.createStringAnnotation( - Collections.singletonList((String) value)); - type.replaceAnnotation(stringAnno); - return null; - default: - return null; - } + if (atypeFactory + .getMethodIdentifier() + .isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) { + List args = tree.getArguments(); + Range range = + ValueCheckerUtils.getPossibleValues( + atypeFactory.getAnnotatedType(args.get(1)), atypeFactory); + if (range != null) { + type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range)); + } } - /** - * Given a MemberSelectTree representing a method call, return true if the method's declaration - * is annotated with {@code @StaticallyExecutable}. - */ - private boolean methodIsStaticallyExecutable(Element method) { - return atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null; + if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) + || !handledByValueChecker(type)) { + return null; } - /** - * Returns the Range of the Math.min or Math.max method, or null if the argument is none of - * these methods or their arguments are not annotated in ValueChecker hierarchy. - * - * @return the Range of the Math.min or Math.max method, or null if the argument is none of - * these methods or their arguments are not annotated in ValueChecker hierarchy - */ - private @Nullable Range getRangeForMathMinMax(MethodInvocationTree tree) { - if (atypeFactory.getMethodIdentifier().isMathMin(tree, atypeFactory.getProcessingEnv())) { - AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); - Range rangeArg1 = - atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - Range rangeArg2 = - atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - if (rangeArg1 != null && rangeArg2 != null) { - return rangeArg1.min(rangeArg2); - } - } else if (atypeFactory - .getMethodIdentifier() - .isMathMax(tree, atypeFactory.getProcessingEnv())) { - AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); - Range rangeArg1 = - atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - Range rangeArg2 = - atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - if (rangeArg1 != null && rangeArg2 != null) { - return rangeArg1.max(rangeArg2); - } - } - return null; + if (atypeFactory + .getMethodIdentifier() + .isStringLengthInvocation(tree, atypeFactory.getProcessingEnv())) { + AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); + AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType); + if (resultAnno != null) { + type.replaceAnnotation(resultAnno); + } + return null; } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (type.hasAnnotation(atypeFactory.UNKNOWNVAL)) { - Range range = getRangeForMathMinMax(tree); - if (range != null) { - type.replaceAnnotation(atypeFactory.createIntRangeAnnotation(range)); - } - } - - if (atypeFactory - .getMethodIdentifier() - .isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) { - List args = tree.getArguments(); - Range range = - ValueCheckerUtils.getPossibleValues( - atypeFactory.getAnnotatedType(args.get(1)), atypeFactory); - if (range != null) { - type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range)); - } - } - - if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) - || !handledByValueChecker(type)) { - return null; - } - - if (atypeFactory - .getMethodIdentifier() - .isStringLengthInvocation(tree, atypeFactory.getProcessingEnv())) { - AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); - AnnotationMirror resultAnno = - atypeFactory.createArrayLengthResultAnnotation(receiverType); - if (resultAnno != null) { - type.replaceAnnotation(resultAnno); - } - return null; - } - - if (atypeFactory - .getMethodIdentifier() - .isArrayGetLengthInvocation(tree, atypeFactory.getProcessingEnv())) { - List args = tree.getArguments(); - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(args.get(0)); - AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(argType); - if (resultAnno != null) { - type.replaceAnnotation(resultAnno); - } - return null; - } - - // Get argument values - List arguments = tree.getArguments(); - ArrayList> argValues; - if (arguments.isEmpty()) { - argValues = null; - } else { - argValues = new ArrayList<>(arguments.size()); - for (ExpressionTree argument : arguments) { - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); - List values = getValues(argType, argType.getUnderlyingType()); - if (values == null || values.isEmpty()) { - // Values aren't known, so don't try to evaluate the method. - return null; - } - argValues.add(values); - } - } - - // Get receiver values - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - List receiverValues; - - if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { - receiverValues = getValues(receiver, receiver.getUnderlyingType()); - if (receiverValues == null || receiverValues.isEmpty()) { - // Values aren't known, so don't try to evaluate the method. - return null; - } - } else { - receiverValues = null; - } - - // Evaluate method - List returnValues = - atypeFactory.evaluator.evaluateMethodCall(argValues, receiverValues, tree); - if (returnValues == null) { - return null; - } - AnnotationMirror returnType = - atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); - type.replaceAnnotation(returnType); - - return null; + if (atypeFactory + .getMethodIdentifier() + .isArrayGetLengthInvocation(tree, atypeFactory.getProcessingEnv())) { + List args = tree.getArguments(); + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(args.get(0)); + AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(argType); + if (resultAnno != null) { + type.replaceAnnotation(resultAnno); + } + return null; } - @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) - || !handledByValueChecker(type)) { - return null; - } - - // get argument values - List arguments = tree.getArguments(); - ArrayList> argValues; - if (arguments.isEmpty()) { - argValues = null; - } else { - argValues = new ArrayList<>(arguments.size()); - for (ExpressionTree argument : arguments) { - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); - List values = getValues(argType, argType.getUnderlyingType()); - if (values == null || values.isEmpty()) { - // Values aren't known, so don't try to evaluate the method. - return null; - } - argValues.add(values); - } + // Get argument values + List arguments = tree.getArguments(); + ArrayList> argValues; + if (arguments.isEmpty()) { + argValues = null; + } else { + argValues = new ArrayList<>(arguments.size()); + for (ExpressionTree argument : arguments) { + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); + List values = getValues(argType, argType.getUnderlyingType()); + if (values == null || values.isEmpty()) { + // Values aren't known, so don't try to evaluate the method. + return null; } + argValues.add(values); + } + } - // Evaluate method - List returnValues = - atypeFactory.evaluator.evaluteConstructorCall( - argValues, tree, type.getUnderlyingType()); - if (returnValues == null) { - return null; - } - AnnotationMirror returnType = - atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); - type.replaceAnnotation(returnType); + // Get receiver values + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + List receiverValues; + if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { + receiverValues = getValues(receiver, receiver.getUnderlyingType()); + if (receiverValues == null || receiverValues.isEmpty()) { + // Values aren't known, so don't try to evaluate the method. return null; + } + } else { + receiverValues = null; } - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - visitFieldAccess(tree, type); - visitEnumConstant(tree, type); - - if (TreeUtils.isArrayLengthAccess(tree)) { - // The field access is to the length field, as in "someArrayExpression.length" - AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(tree.getExpression()); - if (receiverType.getKind() == TypeKind.ARRAY) { - AnnotationMirror resultAnno = - atypeFactory.createArrayLengthResultAnnotation(receiverType); - if (resultAnno != null) { - type.replaceAnnotation(resultAnno); - } - } - } - return null; + // Evaluate method + List returnValues = + atypeFactory.evaluator.evaluateMethodCall(argValues, receiverValues, tree); + if (returnValues == null) { + return null; + } + AnnotationMirror returnType = + atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); + type.replaceAnnotation(returnType); + + return null; + } + + @Override + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) + || !handledByValueChecker(type)) { + return null; } - /** - * Visit a tree that might be a field access. - * - * @param tree a tree that might be a field access. It is either a MemberSelectTree or an - * IdentifierTree (if the programmer omitted the leading `this.`). - * @param type its type - */ - private void visitFieldAccess(ExpressionTree tree, AnnotatedTypeMirror type) { - if (!TreeUtils.isFieldAccess(tree) || !handledByValueChecker(type)) { - return; - } - - VariableElement fieldElement = TreeUtils.variableElementFromTree(tree); - Object value = fieldElement.getConstantValue(); - if (value != null) { - // The field is a compile-time constant. - type.replaceAnnotation( - atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value)); - return; - } - if (ElementUtils.isStatic(fieldElement) && ElementUtils.isFinal(fieldElement)) { - // The field is static and final, but its declaration does not initialize it to a - // compile-time constant. Obtain its value reflectively. - Element classElement = fieldElement.getEnclosingElement(); - if (classElement != null) { - @SuppressWarnings("signature" // TODO: bug in ValueAnnotatedTypeFactory. - // evaluateStaticFieldAccess requires a @ClassGetName but this passes a - // @FullyQualifiedName. They differ for inner classes. - ) - @BinaryName String classname = ElementUtils.getQualifiedClassName(classElement).toString(); - @SuppressWarnings( - "signature") // https://tinyurl.com/cfissue/658 for Name.toString() - @Identifier String fieldName = fieldElement.getSimpleName().toString(); - value = - atypeFactory.evaluator.evaluateStaticFieldAccess( - classname, fieldName, tree); - if (value != null) { - type.replaceAnnotation( - atypeFactory.createResultingAnnotation( - type.getUnderlyingType(), value)); - } - return; - } + // get argument values + List arguments = tree.getArguments(); + ArrayList> argValues; + if (arguments.isEmpty()) { + argValues = null; + } else { + argValues = new ArrayList<>(arguments.size()); + for (ExpressionTree argument : arguments) { + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); + List values = getValues(argType, argType.getUnderlyingType()); + if (values == null || values.isEmpty()) { + // Values aren't known, so don't try to evaluate the method. + return null; } + argValues.add(values); + } } - /** Returns true iff the given type is in the domain of the Constant Value Checker. */ - private boolean handledByValueChecker(AnnotatedTypeMirror type) { - TypeMirror tm = type.getUnderlyingType(); - /* TODO: compare performance to the more readable. - return TypesUtils.isPrimitive(tm) - || TypesUtils.isBoxedPrimitive(tm) - || TypesUtils.isString(tm) - || tm.toString().equals("char[]"); // Why? - */ - return COVERED_CLASS_STRINGS.contains(tm.toString()); + // Evaluate method + List returnValues = + atypeFactory.evaluator.evaluteConstructorCall(argValues, tree, type.getUnderlyingType()); + if (returnValues == null) { + return null; } - - @Override - public Void visitConditionalExpression( - ConditionalExpressionTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - // Work around for https://github.com/typetools/checker-framework/issues/602. - annotatedTypeMirror.replaceAnnotation(atypeFactory.UNKNOWNVAL); - return null; + AnnotationMirror returnType = + atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); + type.replaceAnnotation(returnType); + + return null; + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + visitFieldAccess(tree, type); + visitEnumConstant(tree, type); + + if (TreeUtils.isArrayLengthAccess(tree)) { + // The field access is to the length field, as in "someArrayExpression.length" + AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(tree.getExpression()); + if (receiverType.getKind() == TypeKind.ARRAY) { + AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType); + if (resultAnno != null) { + type.replaceAnnotation(resultAnno); + } + } } - - // An IdentifierTree can be a local variable (including formals, exception parameters, etc.) or - // an implicit field access (where `this.` is omitted). - // A field access is always an IdentifierTree or MemberSelectTree. - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - visitFieldAccess(tree, type); - visitEnumConstant(tree, type); - return null; + return null; + } + + /** + * Visit a tree that might be a field access. + * + * @param tree a tree that might be a field access. It is either a MemberSelectTree or an + * IdentifierTree (if the programmer omitted the leading `this.`). + * @param type its type + */ + private void visitFieldAccess(ExpressionTree tree, AnnotatedTypeMirror type) { + if (!TreeUtils.isFieldAccess(tree) || !handledByValueChecker(type)) { + return; } - /** - * Default the type of an enum constant {@code E.V} to {@code @StringVal("V")}. Does nothing if - * the argument is not an enum constant. - * - * @param tree an Identifier or MemberSelect tree that might be an enum - * @param type the type of that tree - */ - private void visitEnumConstant(ExpressionTree tree, AnnotatedTypeMirror type) { - Element decl = TreeUtils.elementFromUse(tree); - if (decl.getKind() != ElementKind.ENUM_CONSTANT) { - return; + VariableElement fieldElement = TreeUtils.variableElementFromTree(tree); + Object value = fieldElement.getConstantValue(); + if (value != null) { + // The field is a compile-time constant. + type.replaceAnnotation( + atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value)); + return; + } + if (ElementUtils.isStatic(fieldElement) && ElementUtils.isFinal(fieldElement)) { + // The field is static and final, but its declaration does not initialize it to a + // compile-time constant. Obtain its value reflectively. + Element classElement = fieldElement.getEnclosingElement(); + if (classElement != null) { + @SuppressWarnings("signature" // TODO: bug in ValueAnnotatedTypeFactory. + // evaluateStaticFieldAccess requires a @ClassGetName but this passes a + // @FullyQualifiedName. They differ for inner classes. + ) + @BinaryName String classname = ElementUtils.getQualifiedClassName(classElement).toString(); + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Name.toString() + @Identifier String fieldName = fieldElement.getSimpleName().toString(); + value = atypeFactory.evaluator.evaluateStaticFieldAccess(classname, fieldName, tree); + if (value != null) { + type.replaceAnnotation( + atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value)); } + return; + } + } + } + + /** Returns true iff the given type is in the domain of the Constant Value Checker. */ + private boolean handledByValueChecker(AnnotatedTypeMirror type) { + TypeMirror tm = type.getUnderlyingType(); + /* TODO: compare performance to the more readable. + return TypesUtils.isPrimitive(tm) + || TypesUtils.isBoxedPrimitive(tm) + || TypesUtils.isString(tm) + || tm.toString().equals("char[]"); // Why? + */ + return COVERED_CLASS_STRINGS.contains(tm.toString()); + } + + @Override + public Void visitConditionalExpression( + ConditionalExpressionTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + // Work around for https://github.com/typetools/checker-framework/issues/602. + annotatedTypeMirror.replaceAnnotation(atypeFactory.UNKNOWNVAL); + return null; + } + + // An IdentifierTree can be a local variable (including formals, exception parameters, etc.) or + // an implicit field access (where `this.` is omitted). + // A field access is always an IdentifierTree or MemberSelectTree. + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + visitFieldAccess(tree, type); + visitEnumConstant(tree, type); + return null; + } + + /** + * Default the type of an enum constant {@code E.V} to {@code @StringVal("V")}. Does nothing if + * the argument is not an enum constant. + * + * @param tree an Identifier or MemberSelect tree that might be an enum + * @param type the type of that tree + */ + private void visitEnumConstant(ExpressionTree tree, AnnotatedTypeMirror type) { + Element decl = TreeUtils.elementFromUse(tree); + if (decl.getKind() != ElementKind.ENUM_CONSTANT) { + return; + } - Name id; - switch (tree.getKind()) { - case MEMBER_SELECT: - id = ((MemberSelectTree) tree).getIdentifier(); - break; - case IDENTIFIER: - id = ((IdentifierTree) tree).getName(); - break; - default: - throw new TypeSystemError( - "unexpected kind of enum constant use tree: " + tree.getKind()); - } - AnnotationMirror stringVal = - atypeFactory.createStringAnnotation(Collections.singletonList(id.toString())); - type.replaceAnnotation(stringVal); + Name id; + switch (tree.getKind()) { + case MEMBER_SELECT: + id = ((MemberSelectTree) tree).getIdentifier(); + break; + case IDENTIFIER: + id = ((IdentifierTree) tree).getName(); + break; + default: + throw new TypeSystemError("unexpected kind of enum constant use tree: " + tree.getKind()); } + AnnotationMirror stringVal = + atypeFactory.createStringAnnotation(Collections.singletonList(id.toString())); + type.replaceAnnotation(stringVal); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java index 03766b95c4f..c0b40b5c156 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java @@ -1,20 +1,18 @@ package org.checkerframework.common.value; -import org.checkerframework.common.value.util.Range; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.typeannotator.TypeAnnotator; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.TypeKindUtils; -import org.checkerframework.javacutil.TypesUtils; - import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.common.value.util.Range; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.typeannotator.TypeAnnotator; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TypeKindUtils; +import org.checkerframework.javacutil.TypesUtils; /** * Performs pre-processing on annotations written by users, replacing illegal annotations by legal @@ -22,167 +20,164 @@ */ class ValueTypeAnnotator extends TypeAnnotator { - /** The type factory to use. Shadows the field from the superclass with a more specific type. */ - @SuppressWarnings("HidingField") - protected final ValueAnnotatedTypeFactory typeFactory; + /** The type factory to use. Shadows the field from the superclass with a more specific type. */ + @SuppressWarnings("HidingField") + protected final ValueAnnotatedTypeFactory typeFactory; - /** - * Construct a new ValueTypeAnnotator. - * - * @param typeFactory the type factory to use - */ - protected ValueTypeAnnotator(ValueAnnotatedTypeFactory typeFactory) { - super(typeFactory); - this.typeFactory = typeFactory; - } + /** + * Construct a new ValueTypeAnnotator. + * + * @param typeFactory the type factory to use + */ + protected ValueTypeAnnotator(ValueAnnotatedTypeFactory typeFactory) { + super(typeFactory); + this.typeFactory = typeFactory; + } - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - replaceWithNewAnnoInSpecialCases(type); - return super.scan(type, aVoid); - } + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + replaceWithNewAnnoInSpecialCases(type); + return super.scan(type, aVoid); + } - /** - * This method performs pre-processing on annotations written by users. - * - *

If any *Val annotation has > MAX_VALUES number of values provided, replaces the - * annotation by @IntRange for integral types, @ArrayLenRange for arrays, @ArrayLen - * or @ArrayLenRange for strings, and @UnknownVal for all other types. Works together with - * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} which issues - * warnings to users in these cases. - * - *

If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value - * "from" is greater than the value "to", replaces the annotation by {@code @BottomVal}. The - * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} raises an - * error to users if the annotation was user-written. - * - *

If any @ArrayLen annotation has a negative number, replaces the annotation by {@code - * BottomVal}. The {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, - * Void)} raises an error to users if the annotation was user-written. - * - *

If a user only writes one side of an {@code IntRange} annotation, this method also - * computes an appropriate default based on the underlying type for the other side of the range. - * For instance, if the user writes {@code @IntRange(from = 1) short x;} then this method will - * translate the annotation to {@code @IntRange(from = 1, to = Short.MAX_VALUE}. - */ - private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) { - AnnotationMirror anno = atm.getAnnotationInHierarchy(typeFactory.UNKNOWNVAL); - if (anno == null || anno.getElementValues().isEmpty()) { - return; - } + /** + * This method performs pre-processing on annotations written by users. + * + *

If any *Val annotation has > MAX_VALUES number of values provided, replaces the + * annotation by @IntRange for integral types, @ArrayLenRange for arrays, @ArrayLen + * or @ArrayLenRange for strings, and @UnknownVal for all other types. Works together with {@link + * ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} which issues warnings + * to users in these cases. + * + *

If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value + * "from" is greater than the value "to", replaces the annotation by {@code @BottomVal}. The + * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} raises an error + * to users if the annotation was user-written. + * + *

If any @ArrayLen annotation has a negative number, replaces the annotation by {@code + * BottomVal}. The {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} + * raises an error to users if the annotation was user-written. + * + *

If a user only writes one side of an {@code IntRange} annotation, this method also computes + * an appropriate default based on the underlying type for the other side of the range. For + * instance, if the user writes {@code @IntRange(from = 1) short x;} then this method will + * translate the annotation to {@code @IntRange(from = 1, to = Short.MAX_VALUE}. + */ + private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) { + AnnotationMirror anno = atm.getAnnotationInHierarchy(typeFactory.UNKNOWNVAL); + if (anno == null || anno.getElementValues().isEmpty()) { + return; + } - if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - List values = typeFactory.getIntValues(anno); - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(Range.create(values))); - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { - List values = typeFactory.getArrayLength(anno); - if (values.isEmpty()) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else if (Collections.min(values) < 0) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - atm.replaceAnnotation( - typeFactory.createArrayLenRangeAnnotation(Range.create(values))); - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { - TypeMirror underlyingType = atm.getUnderlyingType(); - // If the underlying type is neither a primitive integral type nor boxed integral type, - // return without making changes. TypesUtils.isIntegralPrimitiveOrBoxed fails if passed - // a non-primitive type that is not a declared type, so it cannot be called directly. - if (!TypeKindUtils.isIntegral(underlyingType.getKind()) - && (underlyingType.getKind() != TypeKind.DECLARED - || !TypesUtils.isIntegralPrimitiveOrBoxed(underlyingType))) { - return; - } + if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + List values = typeFactory.getIntValues(anno); + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(Range.create(values))); + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { + List values = typeFactory.getArrayLength(anno); + if (values.isEmpty()) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else if (Collections.min(values) < 0) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(Range.create(values))); + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { + TypeMirror underlyingType = atm.getUnderlyingType(); + // If the underlying type is neither a primitive integral type nor boxed integral type, + // return without making changes. TypesUtils.isIntegralPrimitiveOrBoxed fails if passed + // a non-primitive type that is not a declared type, so it cannot be called directly. + if (!TypeKindUtils.isIntegral(underlyingType.getKind()) + && (underlyingType.getKind() != TypeKind.DECLARED + || !TypesUtils.isIntegralPrimitiveOrBoxed(underlyingType))) { + return; + } - // Compute appropriate defaults for integral ranges. - long from = typeFactory.getFromValueFromIntRange(atm); - long to = typeFactory.getToValueFromIntRange(atm); + // Compute appropriate defaults for integral ranges. + long from = typeFactory.getFromValueFromIntRange(atm); + long to = typeFactory.getToValueFromIntRange(atm); - if (from > to) { - // `from > to` either indicates a user error when writing an annotation or an error - // in the checker's implementation. `-from` should always be <= to. - // ValueVisitor#validateType will issue an error. - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else { - // Always do a replacement of the annotation here so that the defaults calculated - // above are correctly added to the annotation (assuming the annotation is - // well-formed). - atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(from, to)); - } - } else if (AnnotationUtils.areSameByName( - anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - int from = typeFactory.getArrayLenRangeFromValue(anno); - int to = typeFactory.getArrayLenRangeToValue(anno); - if (from > to) { - // `from > to` either indicates a user error when writing an annotation or an error - // in the checker's implementation `-from` should always be <= to. - // ValueVisitor#validateType will issue an error. - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else if (from < 0) { - // No array can have a length less than 0. Any time the type includes a from - // less than zero, it must indicate imprecision in the checker. - atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(0, to)); - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { - // The annotation is StringVal. If there are too many elements, - // ArrayLen or ArrayLenRange is used. - List values = typeFactory.getStringValues(anno); + if (from > to) { + // `from > to` either indicates a user error when writing an annotation or an error + // in the checker's implementation. `-from` should always be <= to. + // ValueVisitor#validateType will issue an error. + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else { + // Always do a replacement of the annotation here so that the defaults calculated + // above are correctly added to the annotation (assuming the annotation is + // well-formed). + atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(from, to)); + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + int from = typeFactory.getArrayLenRangeFromValue(anno); + int to = typeFactory.getArrayLenRangeToValue(anno); + if (from > to) { + // `from > to` either indicates a user error when writing an annotation or an error + // in the checker's implementation `-from` should always be <= to. + // ValueVisitor#validateType will issue an error. + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else if (from < 0) { + // No array can have a length less than 0. Any time the type includes a from + // less than zero, it must indicate imprecision in the checker. + atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(0, to)); + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { + // The annotation is StringVal. If there are too many elements, + // ArrayLen or ArrayLenRange is used. + List values = typeFactory.getStringValues(anno); - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - List lengths = ValueCheckerUtils.getLengthsForStringValues(values); - atm.replaceAnnotation(typeFactory.createArrayLenAnnotation(lengths)); - } + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + List lengths = ValueCheckerUtils.getLengthsForStringValues(values); + atm.replaceAnnotation(typeFactory.createArrayLenAnnotation(lengths)); + } - } else if (AnnotationUtils.areSameByName( - anno, ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME)) { - // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor - // will issue a warning where the annotation was written. - List regexes = - AnnotationUtils.getElementValueArray( - anno, typeFactory.matchesRegexValueElement, String.class); - if (!allRegexesCompile(regexes)) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } - } else if (AnnotationUtils.areSameByName( - anno, ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { - // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor - // will issue a warning where the annotation was written. - List regexes = - AnnotationUtils.getElementValueArray( - anno, typeFactory.doesNotMatchRegexValueElement, String.class); - if (!allRegexesCompile(regexes)) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } - } else { - // In here the annotation is @*Val where (*) is not Int, String but other types - // (Bool, Double, or Enum). - // Therefore we extract its values in a generic way to check its size. - @SuppressWarnings("deprecation") // concrete annotation class is not known - List values = - AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - atm.replaceAnnotation(typeFactory.UNKNOWNVAL); - } - } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME)) { + // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor + // will issue a warning where the annotation was written. + List regexes = + AnnotationUtils.getElementValueArray( + anno, typeFactory.matchesRegexValueElement, String.class); + if (!allRegexesCompile(regexes)) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } + } else if (AnnotationUtils.areSameByName( + anno, ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { + // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor + // will issue a warning where the annotation was written. + List regexes = + AnnotationUtils.getElementValueArray( + anno, typeFactory.doesNotMatchRegexValueElement, String.class); + if (!allRegexesCompile(regexes)) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } + } else { + // In here the annotation is @*Val where (*) is not Int, String but other types + // (Bool, Double, or Enum). + // Therefore we extract its values in a generic way to check its size. + @SuppressWarnings("deprecation") // concrete annotation class is not known + List values = + AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + atm.replaceAnnotation(typeFactory.UNKNOWNVAL); + } } + } - /** - * Returns true if all the given strings are valid regexes. - * - * @param regexes a list of strings that might all be regexes - * @return true if all the given strings are valid regexes - */ - private boolean allRegexesCompile(List regexes) { - for (String regex : regexes) { - try { - Pattern.compile(regex); - } catch (PatternSyntaxException e) { - return false; - } - } - return true; + /** + * Returns true if all the given strings are valid regexes. + * + * @param regexes a list of strings that might all be regexes + * @return true if all the given strings are valid regexes + */ + private boolean allRegexesCompile(List regexes) { + for (String regex : regexes) { + try { + Pattern.compile(regex); + } catch (PatternSyntaxException e) { + return false; + } } + return true; + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java index 20833b218f7..00171b8d4c3 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java @@ -5,7 +5,17 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; - +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -29,527 +39,489 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.util.Collections; -import java.util.List; -import java.util.TreeSet; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** Visitor for the Constant Value type system. */ public class ValueVisitor extends BaseTypeVisitor { - public ValueVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * ValueVisitor overrides this method so that it does not have to check variables annotated with - * the {@link IntRangeFromPositive} annotation, the {@link IntRangeFromNonNegative} annotation, - * or the {@link IntRangeFromGTENegativeOne} annotation. This annotation is only introduced by - * the Index Checker's lower bound annotations. It is safe to defer checking of these values to - * the Index Checker because this is only introduced for explicitly-written {@code - * org.checkerframework.checker.index.qual.Positive}, explicitly-written {@code - * org.checkerframework.checker.index.qual.NonNegative}, and explicitly-written {@code - * org.checkerframework.checker.index.qual.GTENegativeOne} annotations, which must be checked by - * the Lower Bound Checker. - * - * @param varType the annotated type of the lvalue (usually a variable) - * @param valueExp the AST node for the rvalue (the new value) - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - replaceSpecialIntRangeAnnotations(varType); - return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); + public ValueVisitor(BaseTypeChecker checker) { + super(checker); + } + + /** + * ValueVisitor overrides this method so that it does not have to check variables annotated with + * the {@link IntRangeFromPositive} annotation, the {@link IntRangeFromNonNegative} annotation, or + * the {@link IntRangeFromGTENegativeOne} annotation. This annotation is only introduced by the + * Index Checker's lower bound annotations. It is safe to defer checking of these values to the + * Index Checker because this is only introduced for explicitly-written {@code + * org.checkerframework.checker.index.qual.Positive}, explicitly-written {@code + * org.checkerframework.checker.index.qual.NonNegative}, and explicitly-written {@code + * org.checkerframework.checker.index.qual.GTENegativeOne} annotations, which must be checked by + * the Lower Bound Checker. + * + * @param varType the annotated type of the lvalue (usually a variable) + * @param valueExp the AST node for the rvalue (the new value) + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + replaceSpecialIntRangeAnnotations(varType); + return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); + } + + @Override + @FormatMethod + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + replaceSpecialIntRangeAnnotations(varType); + + if (valueType.getKind() == TypeKind.CHAR + && valueType.hasAnnotation(getTypeFactory().UNKNOWNVAL)) { + valueType.addAnnotation(getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING)); } - @Override - @FormatMethod - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - replaceSpecialIntRangeAnnotations(varType); - - if (valueType.getKind() == TypeKind.CHAR - && valueType.hasAnnotation(getTypeFactory().UNKNOWNVAL)) { - valueType.addAnnotation( - getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING)); - } - - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - } - - /** - * Return types for methods that are annotated with {@code @IntRangeFromX} annotations need to - * be replaced with {@code @UnknownVal}. See the documentation on {@link - * #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String, Object[]) - * commonAssignmentCheck}. - * - *

A separate override is necessary because checkOverride doesn't actually use the - * commonAssignmentCheck. - */ - @Override - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedTypeMirror.AnnotatedExecutableType overrider, - AnnotatedTypeMirror.AnnotatedDeclaredType overridingType, - AnnotatedTypeMirror.AnnotatedExecutableType overridden, - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType) { - - replaceSpecialIntRangeAnnotations(overrider); - replaceSpecialIntRangeAnnotations(overridden); - - return super.checkOverride( - overriderTree, overrider, overridingType, overridden, overriddenType); - } - - /** - * Replaces any {@code IntRangeFromX} annotations with {@code @UnknownVal}. This is used to - * prevent these annotations from being required on the left hand side of assignments. - * - * @param varType an annotated type mirror that may contain IntRangeFromX annotations, which - * will be used on the lhs of an assignment or pseudo-assignment - */ - private void replaceSpecialIntRangeAnnotations(AnnotatedTypeMirror varType) { - AnnotatedTypeScanner replaceSpecialIntRangeAnnotations = - new AnnotatedTypeScanner() { - @Override - protected Void scan(AnnotatedTypeMirror type, Void p) { - if (type.hasAnnotation(IntRangeFromPositive.class) - || type.hasAnnotation(IntRangeFromNonNegative.class) - || type.hasAnnotation(IntRangeFromGTENegativeOne.class)) { - type.replaceAnnotation(atypeFactory.UNKNOWNVAL); - } - return super.scan(type, p); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void p) { - // Don't call super so that the type arguments are not visited. - if (type.getEnclosingType() != null) { - scan(type.getEnclosingType(), p); - } - - return null; - } - }; - replaceSpecialIntRangeAnnotations.visit(varType); - } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } + + /** + * Return types for methods that are annotated with {@code @IntRangeFromX} annotations need to be + * replaced with {@code @UnknownVal}. See the documentation on {@link + * #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String, Object[]) + * commonAssignmentCheck}. + * + *

A separate override is necessary because checkOverride doesn't actually use the + * commonAssignmentCheck. + */ + @Override + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedTypeMirror.AnnotatedExecutableType overrider, + AnnotatedTypeMirror.AnnotatedDeclaredType overridingType, + AnnotatedTypeMirror.AnnotatedExecutableType overridden, + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType) { + + replaceSpecialIntRangeAnnotations(overrider); + replaceSpecialIntRangeAnnotations(overridden); + + return super.checkOverride( + overriderTree, overrider, overridingType, overridden, overriddenType); + } + + /** + * Replaces any {@code IntRangeFromX} annotations with {@code @UnknownVal}. This is used to + * prevent these annotations from being required on the left hand side of assignments. + * + * @param varType an annotated type mirror that may contain IntRangeFromX annotations, which will + * be used on the lhs of an assignment or pseudo-assignment + */ + private void replaceSpecialIntRangeAnnotations(AnnotatedTypeMirror varType) { + AnnotatedTypeScanner replaceSpecialIntRangeAnnotations = + new AnnotatedTypeScanner() { + @Override + protected Void scan(AnnotatedTypeMirror type, Void p) { + if (type.hasAnnotation(IntRangeFromPositive.class) + || type.hasAnnotation(IntRangeFromNonNegative.class) + || type.hasAnnotation(IntRangeFromGTENegativeOne.class)) { + type.replaceAnnotation(atypeFactory.UNKNOWNVAL); + } + return super.scan(type, p); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void p) { + // Don't call super so that the type arguments are not visited. + if (type.getEnclosingType() != null) { + scan(type.getEnclosingType(), p); + } - @Override - protected ValueAnnotatedTypeFactory createTypeFactory() { - return new ValueAnnotatedTypeFactory(checker); + return null; + } + }; + replaceSpecialIntRangeAnnotations.visit(varType); + } + + @Override + protected ValueAnnotatedTypeFactory createTypeFactory() { + return new ValueAnnotatedTypeFactory(checker); + } + + /** + * Warns about malformed constant-value annotations. + * + *

Issues an error if any @IntRange annotation has its 'from' value greater than 'to' value. + * + *

Issues an error if any constant-value annotation has no arguments. + * + *

Issues a warning if any constant-value annotation has > MAX_VALUES arguments. + * + *

Issues a warning if any @ArrayLen/@ArrayLenRange annotations contain a negative array + * length. + * + *

Issues a warning if any {@literal @}MatchesRegex or {@literal @}DoesNotMatchRegex annotation + * contains an invalid regular expression. + */ + /* Implementation note: the ValueTypeAnnotator replaces such invalid annotations with valid ones. + * Therefore, the usual validation in #validateType cannot perform this validation. + * These warnings cannot be issued in the ValueAnnotatedTypeFactory, because the conversions + * might happen multiple times. + * On the other hand, not all validations can happen here, because only the annotations are + * available, not the full types. + * Therefore, some validation is still done in #validateType below. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + List args = tree.getArguments(); + + if (args.isEmpty()) { + // Nothing to do if there are no annotation arguments. + return super.visitAnnotation(tree, p); } - /** - * Warns about malformed constant-value annotations. - * - *

Issues an error if any @IntRange annotation has its 'from' value greater than 'to' value. - * - *

Issues an error if any constant-value annotation has no arguments. - * - *

Issues a warning if any constant-value annotation has > MAX_VALUES arguments. - * - *

Issues a warning if any @ArrayLen/@ArrayLenRange annotations contain a negative array - * length. - * - *

Issues a warning if any {@literal @}MatchesRegex or {@literal @}DoesNotMatchRegex - * annotation contains an invalid regular expression. - */ - /* Implementation note: the ValueTypeAnnotator replaces such invalid annotations with valid ones. - * Therefore, the usual validation in #validateType cannot perform this validation. - * These warnings cannot be issued in the ValueAnnotatedTypeFactory, because the conversions - * might happen multiple times. - * On the other hand, not all validations can happen here, because only the annotations are - * available, not the full types. - * Therefore, some validation is still done in #validateType below. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - List args = tree.getArguments(); - - if (args.isEmpty()) { - // Nothing to do if there are no annotation arguments. - return super.visitAnnotation(tree, p); - } - - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - switch (AnnotationUtils.annotationName(anno)) { - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - // If there are 2 arguments, issue an error if from.greater.than.to. - // If there are fewer than 2 arguments, we needn't worry about this problem because - // the other argument will be defaulted to Long.MIN_VALUE or Long.MAX_VALUE - // accordingly. - if (args.size() == 2) { - long from = getTypeFactory().getIntRangeFromValue(anno); - long to = getTypeFactory().getIntRangeToValue(anno); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return null; - } - } - break; - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - case ValueAnnotatedTypeFactory.BOOLVAL_NAME: - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - case ValueAnnotatedTypeFactory.INTVAL_NAME: - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - @SuppressWarnings("deprecation") // concrete annotation class is not known - List values = - AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); - - if (values.isEmpty()) { - checker.reportWarning(tree, "no.values.given"); - return null; - } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - checker.reportWarning( - tree, - (AnnotationUtils.areSameByName( - anno, ValueAnnotatedTypeFactory.INTVAL_NAME) - ? "too.many.values.given.int" - : "too.many.values.given"), - ValueAnnotatedTypeFactory.MAX_VALUES); - return null; - } else if (AnnotationUtils.areSameByName( - anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { - List arrayLens = getTypeFactory().getArrayLength(anno); - if (Collections.min(arrayLens) < 0) { - checker.reportWarning( - tree, "negative.arraylen", Collections.min(arrayLens)); - return null; - } - } - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - long from = getTypeFactory().getArrayLenRangeFromValue(anno); - long to = getTypeFactory().getArrayLenRangeToValue(anno); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return null; - } else if (from < 0) { - checker.reportWarning(tree, "negative.arraylen", from); - return null; - } - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - List matchesRegexes = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.matchesRegexValueElement, String.class); - for (String regex : matchesRegexes) { - try { - Pattern.compile(regex); - } catch (PatternSyntaxException pse) { - checker.reportWarning(tree, "invalid.matches.regex", pse.getMessage()); - } - } - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - List doesNotMatchRegexes = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.doesNotMatchRegexValueElement, String.class); - for (String regex : doesNotMatchRegexes) { - try { - Pattern.compile(regex); - } catch (PatternSyntaxException pse) { - checker.reportWarning(tree, "invalid.doesnotmatch.regex", pse.getMessage()); - } - } - break; - default: - // Do nothing. + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + switch (AnnotationUtils.annotationName(anno)) { + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + // If there are 2 arguments, issue an error if from.greater.than.to. + // If there are fewer than 2 arguments, we needn't worry about this problem because + // the other argument will be defaulted to Long.MIN_VALUE or Long.MAX_VALUE + // accordingly. + if (args.size() == 2) { + long from = getTypeFactory().getIntRangeFromValue(anno); + long to = getTypeFactory().getIntRangeToValue(anno); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return null; + } } - - return super.visitAnnotation(tree, p); - } - - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { + break; + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + case ValueAnnotatedTypeFactory.BOOLVAL_NAME: + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + case ValueAnnotatedTypeFactory.INTVAL_NAME: + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + @SuppressWarnings("deprecation") // concrete annotation class is not known + List values = + AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); + + if (values.isEmpty()) { + checker.reportWarning(tree, "no.values.given"); + return null; + } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + checker.reportWarning( + tree, + (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME) + ? "too.many.values.given.int" + : "too.many.values.given"), + ValueAnnotatedTypeFactory.MAX_VALUES); + return null; + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { + List arrayLens = getTypeFactory().getArrayLength(anno); + if (Collections.min(arrayLens) < 0) { + checker.reportWarning(tree, "negative.arraylen", Collections.min(arrayLens)); return null; + } } - - AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(tree); - AnnotationMirror castAnno = castType.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - AnnotationMirror exprAnno = - atypeFactory - .getAnnotatedType(tree.getExpression()) - .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - - // It is always legal to cast to an IntRange type that includes all values - // of the underlying type. Do not warn about such casts. - // I.e. do not warn if an @IntRange(...) int is casted - // to a @IntRange(from = Byte.MIN_VALUE, to = Byte.MAX_VALUE byte). - if (castAnno != null - && exprAnno != null - && atypeFactory.isIntRange(castAnno) - && atypeFactory.isIntRange(exprAnno)) { - Range castRange = atypeFactory.getRange(castAnno); - TypeKind castTypeKind = castType.getKind(); - if (castTypeKind == TypeKind.BYTE && castRange.isByteEverything()) { - return p; - } - if (castTypeKind == TypeKind.CHAR && castRange.isCharEverything()) { - return p; - } - if (castTypeKind == TypeKind.SHORT && castRange.isShortEverything()) { - return p; - } - if (castTypeKind == TypeKind.INT && castRange.isIntEverything()) { - return p; - } - if (castTypeKind == TypeKind.LONG && castRange.isLongEverything()) { - return p; - } - if (Range.ignoreOverflow) { - // Range.ignoreOverflow is only set if this checker is ignoring overflow. - // In that case, do not warn if the range of the expression encompasses - // the whole type being casted to (i.e. the warning is actually about overflow). - Range exprRange = atypeFactory.getRange(exprAnno); - if (castTypeKind == TypeKind.BYTE - || castTypeKind == TypeKind.CHAR - || castTypeKind == TypeKind.SHORT - || castTypeKind == TypeKind.INT) { - exprRange = NumberUtils.castRange(castType.getUnderlyingType(), exprRange); - } - if (castRange.equals(exprRange)) { - return p; - } - } + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + long from = getTypeFactory().getArrayLenRangeFromValue(anno); + long to = getTypeFactory().getArrayLenRangeToValue(anno); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return null; + } else if (from < 0) { + checker.reportWarning(tree, "negative.arraylen", from); + return null; + } + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + List matchesRegexes = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.matchesRegexValueElement, String.class); + for (String regex : matchesRegexes) { + try { + Pattern.compile(regex); + } catch (PatternSyntaxException pse) { + checker.reportWarning(tree, "invalid.matches.regex", pse.getMessage()); + } + } + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + List doesNotMatchRegexes = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.doesNotMatchRegexValueElement, String.class); + for (String regex : doesNotMatchRegexes) { + try { + Pattern.compile(regex); + } catch (PatternSyntaxException pse) { + checker.reportWarning(tree, "invalid.doesnotmatch.regex", pse.getMessage()); + } } - return super.visitTypeCast(tree, p); + break; + default: + // Do nothing. } - // At this point, types are like: (@IntVal(-1) byte, @IntVal(255) int) and knowledge of - // signedness is gone. So, use castType's underlying type to infer correctness of the cast. - // This method returns true for (@IntVal(-1), @IntVal(255)) if the underlying type is `byte`, - // but not for any other underlying type. - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - TypeKind castTypeKind = - TypeKindUtils.primitiveOrBoxedToTypeKind(castType.getUnderlyingType()); - TypeKind exprTypeKind = - TypeKindUtils.primitiveOrBoxedToTypeKind(exprType.getUnderlyingType()); - if (castTypeKind != null - && exprTypeKind != null - && TypeKindUtils.isIntegral(castTypeKind) - && TypeKindUtils.isIntegral(exprTypeKind)) { - AnnotationMirrorSet castAnnos = castType.getAnnotations(); - AnnotationMirrorSet exprAnnos = exprType.getAnnotations(); - if (castAnnos.equals(exprAnnos)) { - return true; - } - assert castAnnos.size() == 1; - assert exprAnnos.size() == 1; - AnnotationMirror castAnno = castAnnos.first(); - AnnotationMirror exprAnno = exprAnnos.first(); - boolean castAnnoIsIntVal = atypeFactory.areSameByClass(castAnno, IntVal.class); - boolean exprAnnoIsIntVal = atypeFactory.areSameByClass(exprAnno, IntVal.class); - if (castAnnoIsIntVal && exprAnnoIsIntVal) { - List castValues = atypeFactory.getIntValues(castAnno); - List exprValues = atypeFactory.getIntValues(exprAnno); - if (castValues.size() == 1 && exprValues.size() == 1) { - // Special-case singleton sets for speed. - switch (castTypeKind) { - case BYTE: - return castValues.get(0).byteValue() == exprValues.get(0).byteValue(); - case INT: - return castValues.get(0).intValue() == exprValues.get(0).intValue(); - case SHORT: - return castValues.get(0).shortValue() == exprValues.get(0).shortValue(); - default: - return castValues.get(0).longValue() == exprValues.get(0).longValue(); - } - } else { - switch (castTypeKind) { - case BYTE: - { - TreeSet castValuesTree = - new TreeSet( - CollectionsPlume.mapList( - Number::byteValue, castValues)); - TreeSet exprValuesTree = - new TreeSet( - CollectionsPlume.mapList( - Number::byteValue, exprValues)); - return CollectionsPlume.sortedSetContainsAll( - castValuesTree, exprValuesTree); - } - case INT: - { - TreeSet castValuesTree = - new TreeSet( - CollectionsPlume.mapList( - Number::intValue, castValues)); - TreeSet exprValuesTree = - new TreeSet( - CollectionsPlume.mapList( - Number::intValue, exprValues)); - return CollectionsPlume.sortedSetContainsAll( - castValuesTree, exprValuesTree); - } - case SHORT: - { - TreeSet castValuesTree = - new TreeSet( - CollectionsPlume.mapList( - Number::shortValue, castValues)); - TreeSet exprValuesTree = - new TreeSet( - CollectionsPlume.mapList( - Number::shortValue, exprValues)); - return CollectionsPlume.sortedSetContainsAll( - castValuesTree, exprValuesTree); - } - default: - { - TreeSet castValuesTree = new TreeSet<>(castValues); - TreeSet exprValuesTree = new TreeSet<>(exprValues); - return CollectionsPlume.sortedSetContainsAll( - castValuesTree, exprValuesTree); - } - } - } - } - } + return super.visitAnnotation(tree, p); + } - return super.isTypeCastSafe(castType, exprType); + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { + return null; } - /** - * Overridden to issue errors at the appropriate place if an {@code IntRange} or {@code - * ArrayLenRange} annotation has {@code from > to}. {@code from > to} either indicates a user - * error when writing an annotation or an error in the checker's implementation, as {@code from} - * should always be {@code <= to}. Note that additional checks are performed in {@link - * #visitAnnotation(AnnotationTree, Void)}. - * - * @see #visitAnnotation(AnnotationTree, Void) - */ - @Override - public boolean validateType(Tree tree, AnnotatedTypeMirror type) { - replaceSpecialIntRangeAnnotations(type); - if (!super.validateType(tree, type)) { - return false; - } - - AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - if (anno == null) { - return false; + AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(tree); + AnnotationMirror castAnno = castType.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + AnnotationMirror exprAnno = + atypeFactory + .getAnnotatedType(tree.getExpression()) + .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + + // It is always legal to cast to an IntRange type that includes all values + // of the underlying type. Do not warn about such casts. + // I.e. do not warn if an @IntRange(...) int is casted + // to a @IntRange(from = Byte.MIN_VALUE, to = Byte.MAX_VALUE byte). + if (castAnno != null + && exprAnno != null + && atypeFactory.isIntRange(castAnno) + && atypeFactory.isIntRange(exprAnno)) { + Range castRange = atypeFactory.getRange(castAnno); + TypeKind castTypeKind = castType.getKind(); + if (castTypeKind == TypeKind.BYTE && castRange.isByteEverything()) { + return p; + } + if (castTypeKind == TypeKind.CHAR && castRange.isCharEverything()) { + return p; + } + if (castTypeKind == TypeKind.SHORT && castRange.isShortEverything()) { + return p; + } + if (castTypeKind == TypeKind.INT && castRange.isIntEverything()) { + return p; + } + if (castTypeKind == TypeKind.LONG && castRange.isLongEverything()) { + return p; + } + if (Range.ignoreOverflow) { + // Range.ignoreOverflow is only set if this checker is ignoring overflow. + // In that case, do not warn if the range of the expression encompasses + // the whole type being casted to (i.e. the warning is actually about overflow). + Range exprRange = atypeFactory.getRange(exprAnno); + if (castTypeKind == TypeKind.BYTE + || castTypeKind == TypeKind.CHAR + || castTypeKind == TypeKind.SHORT + || castTypeKind == TypeKind.INT) { + exprRange = NumberUtils.castRange(castType.getUnderlyingType(), exprRange); } - - if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { - if (TypesUtils.isIntegralPrimitiveOrBoxed(type.getUnderlyingType())) { - long from = atypeFactory.getFromValueFromIntRange(type); - long to = atypeFactory.getToValueFromIntRange(type); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return false; - } - } else { - TypeMirror utype = type.getUnderlyingType(); - if (!TypesUtils.isObject(utype) - && !TypesUtils.isDeclaredOfName(utype, "java.lang.Number") - && !TypesUtils.isFloatingPoint(utype)) { - checker.reportError(tree, "annotation.intrange.on.noninteger"); - return false; - } - } - } else if (AnnotationUtils.areSameByName( - anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - long from = getTypeFactory().getArrayLenRangeFromValue(anno); - long to = getTypeFactory().getArrayLenRangeToValue(anno); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return false; - } + if (castRange.equals(exprRange)) { + return p; } - + } + } + return super.visitTypeCast(tree, p); + } + + // At this point, types are like: (@IntVal(-1) byte, @IntVal(255) int) and knowledge of + // signedness is gone. So, use castType's underlying type to infer correctness of the cast. + // This method returns true for (@IntVal(-1), @IntVal(255)) if the underlying type is `byte`, + // but not for any other underlying type. + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + TypeKind castTypeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(castType.getUnderlyingType()); + TypeKind exprTypeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(exprType.getUnderlyingType()); + if (castTypeKind != null + && exprTypeKind != null + && TypeKindUtils.isIntegral(castTypeKind) + && TypeKindUtils.isIntegral(exprTypeKind)) { + AnnotationMirrorSet castAnnos = castType.getAnnotations(); + AnnotationMirrorSet exprAnnos = exprType.getAnnotations(); + if (castAnnos.equals(exprAnnos)) { return true; + } + assert castAnnos.size() == 1; + assert exprAnnos.size() == 1; + AnnotationMirror castAnno = castAnnos.first(); + AnnotationMirror exprAnno = exprAnnos.first(); + boolean castAnnoIsIntVal = atypeFactory.areSameByClass(castAnno, IntVal.class); + boolean exprAnnoIsIntVal = atypeFactory.areSameByClass(exprAnno, IntVal.class); + if (castAnnoIsIntVal && exprAnnoIsIntVal) { + List castValues = atypeFactory.getIntValues(castAnno); + List exprValues = atypeFactory.getIntValues(exprAnno); + if (castValues.size() == 1 && exprValues.size() == 1) { + // Special-case singleton sets for speed. + switch (castTypeKind) { + case BYTE: + return castValues.get(0).byteValue() == exprValues.get(0).byteValue(); + case INT: + return castValues.get(0).intValue() == exprValues.get(0).intValue(); + case SHORT: + return castValues.get(0).shortValue() == exprValues.get(0).shortValue(); + default: + return castValues.get(0).longValue() == exprValues.get(0).longValue(); + } + } else { + switch (castTypeKind) { + case BYTE: + { + TreeSet castValuesTree = + new TreeSet(CollectionsPlume.mapList(Number::byteValue, castValues)); + TreeSet exprValuesTree = + new TreeSet(CollectionsPlume.mapList(Number::byteValue, exprValues)); + return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); + } + case INT: + { + TreeSet castValuesTree = + new TreeSet(CollectionsPlume.mapList(Number::intValue, castValues)); + TreeSet exprValuesTree = + new TreeSet(CollectionsPlume.mapList(Number::intValue, exprValues)); + return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); + } + case SHORT: + { + TreeSet castValuesTree = + new TreeSet(CollectionsPlume.mapList(Number::shortValue, castValues)); + TreeSet exprValuesTree = + new TreeSet(CollectionsPlume.mapList(Number::shortValue, exprValues)); + return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); + } + default: + { + TreeSet castValuesTree = new TreeSet<>(castValues); + TreeSet exprValuesTree = new TreeSet<>(exprValues); + return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); + } + } + } + } } - /** - * Returns true if an expression of the given type can be a compile-time constant value. - * - * @param tm a type - * @return true if an expression of the given type can be a compile-time constant value - */ - private boolean canBeConstant(TypeMirror tm) { - return TypesUtils.isPrimitive(tm) - || TypesUtils.isBoxedPrimitive(tm) - || TypesUtils.isString(tm) - || (tm.getKind() == TypeKind.ARRAY - && canBeConstant(((ArrayType) tm).getComponentType())); + return super.isTypeCastSafe(castType, exprType); + } + + /** + * Overridden to issue errors at the appropriate place if an {@code IntRange} or {@code + * ArrayLenRange} annotation has {@code from > to}. {@code from > to} either indicates a user + * error when writing an annotation or an error in the checker's implementation, as {@code from} + * should always be {@code <= to}. Note that additional checks are performed in {@link + * #visitAnnotation(AnnotationTree, Void)}. + * + * @see #visitAnnotation(AnnotationTree, Void) + */ + @Override + public boolean validateType(Tree tree, AnnotatedTypeMirror type) { + replaceSpecialIntRangeAnnotations(type); + if (!super.validateType(tree, type)) { + return false; } - @Override - public Void visitMethod(MethodTree tree, Void p) { - super.visitMethod(tree, p); - - ExecutableElement method = TreeUtils.elementFromDeclaration(tree); - if (atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null) { - // The method is annotated as @StaticallyExecutable. - if (atypeFactory.getDeclAnnotation(method, Pure.class) == null) { - checker.reportWarning(tree, "statically.executable.not.pure"); - } - TypeMirror returnType = method.getReturnType(); - if (returnType.getKind() != TypeKind.VOID && !canBeConstant(returnType)) { - checker.reportError( - tree, "statically.executable.nonconstant.return.type", returnType); - } + AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + if (anno == null) { + return false; + } - // Ways to determine the receiver type. - // 1. This definition of receiverType is null when receiver is implicit and method has - // class com.sun.tools.javac.code.Symbol$MethodSymbol. WHY? - // TypeMirror receiverType = method.getReceiverType(); - // The same is true of TreeUtils.elementFromDeclaration(tree).getReceiverType() - // which seems to conflict with ExecutableType's documentation. - // 2. Can't use the tree, because the receiver might not be explicit. - // 3. Check whether method is static and use the declaring class. Doesn't handle all - // cases, but handles the most common ones. - TypeMirror receiverType = method.getReceiverType(); - // If the method is static, issue no warning. This is incorrect in the case of a - // constructor or a static method in an inner class. - if (!ElementUtils.isStatic(method)) { - receiverType = ElementUtils.getType(ElementUtils.enclosingTypeElement(method)); - } - if (receiverType != null - && receiverType.getKind() != TypeKind.NONE - && !canBeConstant(receiverType)) { - checker.reportError( - tree, - "statically.executable.nonconstant.parameter.type", - "this (the receiver)", - returnType); - } + if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { + if (TypesUtils.isIntegralPrimitiveOrBoxed(type.getUnderlyingType())) { + long from = atypeFactory.getFromValueFromIntRange(type); + long to = atypeFactory.getToValueFromIntRange(type); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return false; + } + } else { + TypeMirror utype = type.getUnderlyingType(); + if (!TypesUtils.isObject(utype) + && !TypesUtils.isDeclaredOfName(utype, "java.lang.Number") + && !TypesUtils.isFloatingPoint(utype)) { + checker.reportError(tree, "annotation.intrange.on.noninteger"); + return false; + } + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + long from = getTypeFactory().getArrayLenRangeFromValue(anno); + long to = getTypeFactory().getArrayLenRangeToValue(anno); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return false; + } + } - for (VariableElement param : method.getParameters()) { - TypeMirror paramType = param.asType(); - if (paramType.getKind() != TypeKind.NONE && !canBeConstant(paramType)) { - checker.reportError( - tree, - "statically.executable.nonconstant.parameter.type", - param.getSimpleName().toString(), - returnType); - } - } + return true; + } + + /** + * Returns true if an expression of the given type can be a compile-time constant value. + * + * @param tm a type + * @return true if an expression of the given type can be a compile-time constant value + */ + private boolean canBeConstant(TypeMirror tm) { + return TypesUtils.isPrimitive(tm) + || TypesUtils.isBoxedPrimitive(tm) + || TypesUtils.isString(tm) + || (tm.getKind() == TypeKind.ARRAY && canBeConstant(((ArrayType) tm).getComponentType())); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + super.visitMethod(tree, p); + + ExecutableElement method = TreeUtils.elementFromDeclaration(tree); + if (atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null) { + // The method is annotated as @StaticallyExecutable. + if (atypeFactory.getDeclAnnotation(method, Pure.class) == null) { + checker.reportWarning(tree, "statically.executable.not.pure"); + } + TypeMirror returnType = method.getReturnType(); + if (returnType.getKind() != TypeKind.VOID && !canBeConstant(returnType)) { + checker.reportError(tree, "statically.executable.nonconstant.return.type", returnType); + } + + // Ways to determine the receiver type. + // 1. This definition of receiverType is null when receiver is implicit and method has + // class com.sun.tools.javac.code.Symbol$MethodSymbol. WHY? + // TypeMirror receiverType = method.getReceiverType(); + // The same is true of TreeUtils.elementFromDeclaration(tree).getReceiverType() + // which seems to conflict with ExecutableType's documentation. + // 2. Can't use the tree, because the receiver might not be explicit. + // 3. Check whether method is static and use the declaring class. Doesn't handle all + // cases, but handles the most common ones. + TypeMirror receiverType = method.getReceiverType(); + // If the method is static, issue no warning. This is incorrect in the case of a + // constructor or a static method in an inner class. + if (!ElementUtils.isStatic(method)) { + receiverType = ElementUtils.getType(ElementUtils.enclosingTypeElement(method)); + } + if (receiverType != null + && receiverType.getKind() != TypeKind.NONE + && !canBeConstant(receiverType)) { + checker.reportError( + tree, + "statically.executable.nonconstant.parameter.type", + "this (the receiver)", + returnType); + } + + for (VariableElement param : method.getParameters()) { + TypeMirror paramType = param.asType(); + if (paramType.getKind() != TypeKind.NONE && !canBeConstant(paramType)) { + checker.reportError( + tree, + "statically.executable.nonconstant.parameter.type", + param.getSimpleName().toString(), + returnType); } - return null; + } } + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java b/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java index 9818aac69df..1f1a951a93f 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ByteMath extends NumberMath { - byte number; + byte number; - public ByteMath(byte i) { - number = i; - } + public ByteMath(byte i) { + number = i; + } - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); - } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); } - - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number + right.doubleValue(); } - - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number + right.floatValue(); } - - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); - } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); - } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); - } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); - } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); - } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryPlus() { - return +number; + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryMinus() { - return -number; + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseComplement() { - return ~number; + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); + } + + @Override + public Number unaryPlus() { + return +number; + } + + @Override + public Number unaryMinus() { + return -number; + } - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number bitwiseComplement() { + return ~number; + } + + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); - } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); - } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); - } - if (right instanceof Float) { - return number <= right.floatValue(); - } - if (right instanceof Integer) { - return number <= right.intValue(); - } - if (right instanceof Long) { - return number <= right.longValue(); - } - if (right instanceof Short) { - return number <= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); } + throw new UnsupportedOperationException(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java b/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java index 38d7c9f0a2f..f4dbd76d136 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java @@ -1,307 +1,307 @@ package org.checkerframework.common.value.util; public class DoubleMath extends NumberMath { - double number; + double number; - public DoubleMath(double i) { - number = i; - } + public DoubleMath(double i) { + number = i; + } - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); - } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); } - - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number + right.doubleValue(); } - - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number + right.floatValue(); } - - @Override - public Number divide(Number right) { - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number + right.intValue(); } - - @Override - public Number remainder(Number right) { - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Long) { + return number + right.longValue(); } - - @Override - public Number shiftLeft(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Short) { + return number + right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number signedShiftRight(Number right) { - throw new UnsupportedOperationException(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); } - - @Override - public Number unsignedShiftRight(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number - right.doubleValue(); } - - @Override - public Number bitwiseAnd(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number - right.floatValue(); } - - @Override - public Number bitwiseXor(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number - right.intValue(); } - - @Override - public Number bitwiseOr(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryPlus() { - return +number; + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryMinus() { - return -number; + @Override + public Number divide(Number right) { + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseComplement() { - throw new UnsupportedOperationException(); + @Override + public Number remainder(Number right) { + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); + } + + @Override + public Number shiftLeft(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number signedShiftRight(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number unsignedShiftRight(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number bitwiseAnd(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number bitwiseXor(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number bitwiseOr(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number unaryPlus() { + return +number; + } - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number unaryMinus() { + return -number; + } + + @Override + public Number bitwiseComplement() { + throw new UnsupportedOperationException(); + } + + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); - } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); - } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); - } - if (right instanceof Float) { - return number <= right.floatValue(); - } - if (right instanceof Integer) { - return number <= right.intValue(); - } - if (right instanceof Long) { - return number <= right.longValue(); - } - if (right instanceof Short) { - return number <= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); } + throw new UnsupportedOperationException(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java b/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java index 1fea5819a33..7e132137564 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java @@ -1,307 +1,307 @@ package org.checkerframework.common.value.util; public class FloatMath extends NumberMath { - float number; + float number; - public FloatMath(float i) { - number = i; - } + public FloatMath(float i) { + number = i; + } - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); - } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); } - - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number + right.doubleValue(); } - - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number + right.floatValue(); } - - @Override - public Number divide(Number right) { - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number + right.intValue(); } - - @Override - public Number remainder(Number right) { - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Long) { + return number + right.longValue(); } - - @Override - public Number shiftLeft(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Short) { + return number + right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number signedShiftRight(Number right) { - throw new UnsupportedOperationException(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); } - - @Override - public Number unsignedShiftRight(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number - right.doubleValue(); } - - @Override - public Number bitwiseAnd(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number - right.floatValue(); } - - @Override - public Number bitwiseXor(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number - right.intValue(); } - - @Override - public Number bitwiseOr(Number right) { - throw new UnsupportedOperationException(); + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryPlus() { - return +number; + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryMinus() { - return -number; + @Override + public Number divide(Number right) { + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseComplement() { - throw new UnsupportedOperationException(); + @Override + public Number remainder(Number right) { + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); + } + + @Override + public Number shiftLeft(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number signedShiftRight(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number unsignedShiftRight(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number bitwiseAnd(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number bitwiseXor(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number bitwiseOr(Number right) { + throw new UnsupportedOperationException(); + } + + @Override + public Number unaryPlus() { + return +number; + } - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number unaryMinus() { + return -number; + } + + @Override + public Number bitwiseComplement() { + throw new UnsupportedOperationException(); + } + + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); - } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); - } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); - } - if (right instanceof Float) { - return number <= right.floatValue(); - } - if (right instanceof Integer) { - return number <= right.intValue(); - } - if (right instanceof Long) { - return number <= right.longValue(); - } - if (right instanceof Short) { - return number <= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); } + throw new UnsupportedOperationException(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java b/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java index b1ef0e3a9a2..e6f208293d4 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class IntegerMath extends NumberMath { - int number; + int number; - public IntegerMath(int i) { - number = i; - } + public IntegerMath(int i) { + number = i; + } - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); - } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); } - - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number + right.doubleValue(); } - - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number + right.floatValue(); } - - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); - } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); - } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); - } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); - } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); - } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryPlus() { - return +number; + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryMinus() { - return -number; + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseComplement() { - return ~number; + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); + } + + @Override + public Number unaryPlus() { + return +number; + } + + @Override + public Number unaryMinus() { + return -number; + } - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number bitwiseComplement() { + return ~number; + } + + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); - } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); - } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); - } - if (right instanceof Float) { - return number <= right.floatValue(); - } - if (right instanceof Integer) { - return number <= right.intValue(); - } - if (right instanceof Long) { - return number <= right.longValue(); - } - if (right instanceof Short) { - return number <= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); } + throw new UnsupportedOperationException(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java b/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java index 15ebfb3e257..a746fcc0462 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class LongMath extends NumberMath { - long number; + long number; - public LongMath(long i) { - number = i; - } + public LongMath(long i) { + number = i; + } - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); - } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); } - - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number + right.doubleValue(); } - - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number + right.floatValue(); } - - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); - } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); - } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); - } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); - } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); - } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryPlus() { - return +number; + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryMinus() { - return -number; + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseComplement() { - return ~number; + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); + } + + @Override + public Number unaryPlus() { + return +number; + } + + @Override + public Number unaryMinus() { + return -number; + } - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number bitwiseComplement() { + return ~number; + } + + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); - } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); - } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); - } - if (right instanceof Float) { - return number <= right.floatValue(); - } - if (right instanceof Integer) { - return number <= right.intValue(); - } - if (right instanceof Long) { - return number <= right.longValue(); - } - if (right instanceof Short) { - return number <= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); } + throw new UnsupportedOperationException(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java b/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java index 3d50a0633ad..742331ceced 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java @@ -3,86 +3,86 @@ import org.checkerframework.checker.nullness.qual.Nullable; public abstract class NumberMath { - public static @Nullable NumberMath getNumberMath(Number number) { - if (number instanceof Byte) { - return new ByteMath(number.byteValue()); - } - if (number instanceof Double) { - return new DoubleMath(number.doubleValue()); - } - if (number instanceof Float) { - return new FloatMath(number.floatValue()); - } - if (number instanceof Integer) { - return new IntegerMath(number.intValue()); - } - if (number instanceof Long) { - return new LongMath(number.longValue()); - } - if (number instanceof Short) { - return new ShortMath(number.shortValue()); - } - return null; + public static @Nullable NumberMath getNumberMath(Number number) { + if (number instanceof Byte) { + return new ByteMath(number.byteValue()); } + if (number instanceof Double) { + return new DoubleMath(number.doubleValue()); + } + if (number instanceof Float) { + return new FloatMath(number.floatValue()); + } + if (number instanceof Integer) { + return new IntegerMath(number.intValue()); + } + if (number instanceof Long) { + return new LongMath(number.longValue()); + } + if (number instanceof Short) { + return new ShortMath(number.shortValue()); + } + return null; + } - public abstract Number plus(Number right); + public abstract Number plus(Number right); - public abstract Number minus(Number right); + public abstract Number minus(Number right); - public abstract Number times(Number right); + public abstract Number times(Number right); - /** - * Returns the result of dividing the {@code this} by {@code right}. If {@code right} is zero - * and this is an integer division, {@code null} is returned. - */ - public abstract @Nullable Number divide(Number right); + /** + * Returns the result of dividing the {@code this} by {@code right}. If {@code right} is zero and + * this is an integer division, {@code null} is returned. + */ + public abstract @Nullable Number divide(Number right); - /** - * Returns the result of {@code this % right}. If {@code right} is zero and this is an integer - * remainder, {@code null} is returned. - */ - public abstract @Nullable Number remainder(Number right); + /** + * Returns the result of {@code this % right}. If {@code right} is zero and this is an integer + * remainder, {@code null} is returned. + */ + public abstract @Nullable Number remainder(Number right); - public abstract Number shiftLeft(Number right); + public abstract Number shiftLeft(Number right); - public abstract Number signedShiftRight(Number right); + public abstract Number signedShiftRight(Number right); - public abstract Number unsignedShiftRight(Number right); + public abstract Number unsignedShiftRight(Number right); - public abstract Number bitwiseAnd(Number right); + public abstract Number bitwiseAnd(Number right); - public abstract Number bitwiseOr(Number right); + public abstract Number bitwiseOr(Number right); - public abstract Number bitwiseXor(Number right); + public abstract Number bitwiseXor(Number right); - public abstract Number unaryPlus(); + public abstract Number unaryPlus(); - public abstract Number unaryMinus(); + public abstract Number unaryMinus(); - public abstract Number bitwiseComplement(); + public abstract Number bitwiseComplement(); - public abstract Boolean equalTo(Number right); + public abstract Boolean equalTo(Number right); - public abstract Boolean notEqualTo(Number right); + public abstract Boolean notEqualTo(Number right); - public abstract Boolean greaterThan(Number right); + public abstract Boolean greaterThan(Number right); - public abstract Boolean greaterThanEq(Number right); + public abstract Boolean greaterThanEq(Number right); - public abstract Boolean lessThan(Number right); + public abstract Boolean lessThan(Number right); - public abstract Boolean lessThanEq(Number right); + public abstract Boolean lessThanEq(Number right); - public static boolean isIntegralZero(Number number) { - if (number instanceof Byte) { - return number.byteValue() == 0; - } else if (number instanceof Integer) { - return number.intValue() == 0; - } else if (number instanceof Long) { - return number.longValue() == 0; - } else if (number instanceof Short) { - return number.shortValue() == 0; - } - return false; + public static boolean isIntegralZero(Number number) { + if (number instanceof Byte) { + return number.byteValue() == 0; + } else if (number instanceof Integer) { + return number.intValue() == 0; + } else if (number instanceof Long) { + return number.longValue() == 0; + } else if (number instanceof Short) { + return number.shortValue() == 0; } + return false; + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java b/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java index 9c0b32fec3c..b2e2d3fc33d 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java @@ -1,157 +1,155 @@ package org.checkerframework.common.value.util; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.TypeKindUtils; -import org.plumelib.util.CollectionsPlume; - import java.util.List; - import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.TypeKindUtils; +import org.plumelib.util.CollectionsPlume; /** Utility routines for manipulating numbers. */ public class NumberUtils { - /** Do not instantiate. */ - private NumberUtils() { - throw new Error("Do not instantiate"); - } + /** Do not instantiate. */ + private NumberUtils() { + throw new Error("Do not instantiate"); + } - /** - * Converts a {@code List} to a {@code List}, where A and B are numeric types. - * - * @param type the type to cast to - * @param numbers the numbers to cast to the given type - * @return a list of numbers of the given type - */ - public static List castNumbers( - TypeMirror type, List numbers) { - return castNumbers(type, false, numbers); - } + /** + * Converts a {@code List} to a {@code List}, where A and B are numeric types. + * + * @param type the type to cast to + * @param numbers the numbers to cast to the given type + * @return a list of numbers of the given type + */ + public static List castNumbers( + TypeMirror type, List numbers) { + return castNumbers(type, false, numbers); + } - /** - * Converts a {@code List} to a {@code List}, where A and B are numeric types. - * - * @param type the type to cast to - * @param isUnsigned if true, treat {@code type} as unsigned - * @param numbers the numbers to cast to the given type - * @return a list of numbers of the given type - */ - @SuppressWarnings("unchecked") - public static @Nullable List castNumbers( - TypeMirror type, boolean isUnsigned, List numbers) { - if (numbers == null) { - return null; + /** + * Converts a {@code List} to a {@code List}, where A and B are numeric types. + * + * @param type the type to cast to + * @param isUnsigned if true, treat {@code type} as unsigned + * @param numbers the numbers to cast to the given type + * @return a list of numbers of the given type + */ + @SuppressWarnings("unchecked") + public static @Nullable List castNumbers( + TypeMirror type, boolean isUnsigned, List numbers) { + if (numbers == null) { + return null; + } + TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + if (typeKind == null) { + throw new UnsupportedOperationException(type.toString()); + } + switch (typeKind) { + case BYTE: + if (isUnsigned) { + return CollectionsPlume.mapList( + NumberUtils::byteValueUnsigned, (Iterable) numbers); + } else { + return CollectionsPlume.mapList(Number::byteValue, numbers); } - TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - if (typeKind == null) { - throw new UnsupportedOperationException(type.toString()); + case CHAR: + return CollectionsPlume.mapList(Number::intValue, numbers); + case DOUBLE: + return CollectionsPlume.mapList(Number::doubleValue, numbers); + case FLOAT: + return CollectionsPlume.mapList(Number::floatValue, numbers); + case INT: + if (isUnsigned) { + return CollectionsPlume.mapList( + NumberUtils::intValueUnsigned, (Iterable) numbers); + } else { + return CollectionsPlume.mapList(Number::intValue, numbers); } - switch (typeKind) { - case BYTE: - if (isUnsigned) { - return CollectionsPlume.mapList( - NumberUtils::byteValueUnsigned, (Iterable) numbers); - } else { - return CollectionsPlume.mapList(Number::byteValue, numbers); - } - case CHAR: - return CollectionsPlume.mapList(Number::intValue, numbers); - case DOUBLE: - return CollectionsPlume.mapList(Number::doubleValue, numbers); - case FLOAT: - return CollectionsPlume.mapList(Number::floatValue, numbers); - case INT: - if (isUnsigned) { - return CollectionsPlume.mapList( - NumberUtils::intValueUnsigned, (Iterable) numbers); - } else { - return CollectionsPlume.mapList(Number::intValue, numbers); - } - case LONG: - return CollectionsPlume.mapList(Number::longValue, numbers); - case SHORT: - if (isUnsigned) { - return CollectionsPlume.mapList( - NumberUtils::shortValueUnsigned, (Iterable) numbers); - } else { - return CollectionsPlume.mapList(Number::shortValue, numbers); - } - default: - throw new UnsupportedOperationException(typeKind + ": " + type); + case LONG: + return CollectionsPlume.mapList(Number::longValue, numbers); + case SHORT: + if (isUnsigned) { + return CollectionsPlume.mapList( + NumberUtils::shortValueUnsigned, (Iterable) numbers); + } else { + return CollectionsPlume.mapList(Number::shortValue, numbers); } + default: + throw new UnsupportedOperationException(typeKind + ": " + type); } + } - /** - * Returns the given number, casted to unsigned byte. - * - * @param n a number - * @return the given number, casted to unsigned byte - */ - private static Short byteValueUnsigned(Number n) { - short result = n.byteValue(); - if (result < 0) { - result = (short) (result + 256); - } - return result; + /** + * Returns the given number, casted to unsigned byte. + * + * @param n a number + * @return the given number, casted to unsigned byte + */ + private static Short byteValueUnsigned(Number n) { + short result = n.byteValue(); + if (result < 0) { + result = (short) (result + 256); } + return result; + } - /** - * Returns the given number, casted to unsigned short. - * - * @param n a number - * @return the given number, casted to unsigned short - */ - private static Integer shortValueUnsigned(Number n) { - int result = n.shortValue(); - if (result < 0) { - result = result + 65536; - } - return result; + /** + * Returns the given number, casted to unsigned short. + * + * @param n a number + * @return the given number, casted to unsigned short + */ + private static Integer shortValueUnsigned(Number n) { + int result = n.shortValue(); + if (result < 0) { + result = result + 65536; } + return result; + } - /** - * Returns the given number, casted to unsigned int. - * - * @param n a number - * @return the given number, casted to unsigned int - */ - private static Long intValueUnsigned(Number n) { - long result = n.intValue(); - if (result < 0) { - result = result + 4294967296L; - } - return result; + /** + * Returns the given number, casted to unsigned int. + * + * @param n a number + * @return the given number, casted to unsigned int + */ + private static Long intValueUnsigned(Number n) { + long result = n.intValue(); + if (result < 0) { + result = result + 4294967296L; } + return result; + } - /** - * Return a range that restricts the given range to the given type. That is, return the range - * resulting from casting a value with the given range. - * - * @param type the type for the cast; the result will be within it - * @param range the original range; the result will be within it - * @return the intersection of the given range and the possible values of the given type - */ - public static Range castRange(TypeMirror type, Range range) { - TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - if (typeKind == null) { - throw new UnsupportedOperationException(type.toString()); - } - switch (typeKind) { - case BYTE: - return range.byteRange(); - case CHAR: - return range.charRange(); - case SHORT: - return range.shortRange(); - case INT: - return range.intRange(); - case LONG: - case FLOAT: - case DOUBLE: - return range; - default: - throw new UnsupportedOperationException(typeKind + ": " + type); - } + /** + * Return a range that restricts the given range to the given type. That is, return the range + * resulting from casting a value with the given range. + * + * @param type the type for the cast; the result will be within it + * @param range the original range; the result will be within it + * @return the intersection of the given range and the possible values of the given type + */ + public static Range castRange(TypeMirror type, Range range) { + TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + if (typeKind == null) { + throw new UnsupportedOperationException(type.toString()); + } + switch (typeKind) { + case BYTE: + return range.byteRange(); + case CHAR: + return range.charRange(); + case SHORT: + return range.shortRange(); + case INT: + return range.intRange(); + case LONG: + case FLOAT: + case DOUBLE: + return range; + default: + throw new UnsupportedOperationException(typeKind + ": " + type); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/Range.java b/framework/src/main/java/org/checkerframework/common/value/util/Range.java index 2d490e276a5..9f6f2a748ef 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/Range.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/Range.java @@ -1,16 +1,14 @@ package org.checkerframework.common.value.util; -import org.checkerframework.checker.interning.qual.InternedDistinct; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.math.BigInteger; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; - import javax.lang.model.type.TypeKind; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.checker.nullness.qual.Nullable; /** * The Range class models a 64-bit two's-complement integral interval, such as all integers between @@ -20,1242 +18,1237 @@ */ public class Range { - /** The lower bound of the interval, inclusive. */ - public final long from; - - /** The upper bound of the interval, inclusive. */ - public final long to; - - /** - * Should ranges take overflow into account or ignore it? - * - *
    - *
  • If {@code ignoreOverflow} is true, then operations that would result in more than the - * max value are clipped to the max value (and similarly for the min). - *
  • If {@code ignoreOverflow} is false, then operations that would result in more than the - * max wrap around according to the rules of twos-complement arithmetic and produce a - * smaller value (and similarly for the min). - *
- * - *

Any checker that uses this library should set this field. By default, this field is set to - * false (meaning overflow is taken into account), but a previous checker might have set it to - * true. - * - *

A static field is used because passing an instance field throughout the class bloats the - * code. - */ - public static boolean ignoreOverflow = false; - - /** A range containing all possible 64-bit values. */ - public static final Range LONG_EVERYTHING = create(Long.MIN_VALUE, Long.MAX_VALUE); - - /** Long.MIN_VALUE, as a BigInteger. */ - private static final BigInteger BIG_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE); - - /** Long.MAX_VALUE, as a BigInteger. */ - private static final BigInteger BIG_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE); - - /** The number of Long values, as a BigInteger. */ - private static final BigInteger BIG_LONG_WIDTH = - BIG_LONG_MAX_VALUE.subtract(BIG_LONG_MIN_VALUE).add(BigInteger.ONE); - - /** A range containing all possible 32-bit values. */ - public static final Range INT_EVERYTHING = create(Integer.MIN_VALUE, Integer.MAX_VALUE); - - /** The number of values representable in 32 bits: 2^32 or {@code 1<<32}. */ - private static final long INT_WIDTH = INT_EVERYTHING.width(); - - /** A range containing all possible 16-bit values. */ - public static final Range SHORT_EVERYTHING = create(Short.MIN_VALUE, Short.MAX_VALUE); - - /** The number of values representable in 16 bits: 2^16 or 1<<16. */ - private static final long SHORT_WIDTH = SHORT_EVERYTHING.width(); - - /** A range containing all possible char values. */ - public static final Range CHAR_EVERYTHING = create(Character.MIN_VALUE, Character.MAX_VALUE); - - /** The number of values representable in char: */ - private static final long CHAR_WIDTH = CHAR_EVERYTHING.width(); - - /** A range containing all possible 8-bit values. */ - public static final Range BYTE_EVERYTHING = create(Byte.MIN_VALUE, Byte.MAX_VALUE); - - /** The number of values representable in 8 bits: 2^8 or 1<<8. */ - private static final long BYTE_WIDTH = BYTE_EVERYTHING.width(); - - /** The empty range. This is the only Range object that contains nothing */ - @SuppressWarnings( - "interning:assignment.type.incompatible") // no other constructor call makes this - public static final @InternedDistinct Range NOTHING = new Range(Long.MAX_VALUE, Long.MIN_VALUE); - - /** An alias to the range containing all possible 64-bit values. */ - public static final Range EVERYTHING = LONG_EVERYTHING; - - /** - * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. - * - *

This is a private constructor that does no validation of arguments, so special instances - * (e.g., {@link #NOTHING}) can be created through it. - * - * @param from the lower bound (inclusive) - * @param to the upper bound (inclusive) - */ - private Range(long from, long to) { - this.from = from; - this.to = to; + /** The lower bound of the interval, inclusive. */ + public final long from; + + /** The upper bound of the interval, inclusive. */ + public final long to; + + /** + * Should ranges take overflow into account or ignore it? + * + *

    + *
  • If {@code ignoreOverflow} is true, then operations that would result in more than the max + * value are clipped to the max value (and similarly for the min). + *
  • If {@code ignoreOverflow} is false, then operations that would result in more than the + * max wrap around according to the rules of twos-complement arithmetic and produce a + * smaller value (and similarly for the min). + *
+ * + *

Any checker that uses this library should set this field. By default, this field is set to + * false (meaning overflow is taken into account), but a previous checker might have set it to + * true. + * + *

A static field is used because passing an instance field throughout the class bloats the + * code. + */ + public static boolean ignoreOverflow = false; + + /** A range containing all possible 64-bit values. */ + public static final Range LONG_EVERYTHING = create(Long.MIN_VALUE, Long.MAX_VALUE); + + /** Long.MIN_VALUE, as a BigInteger. */ + private static final BigInteger BIG_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE); + + /** Long.MAX_VALUE, as a BigInteger. */ + private static final BigInteger BIG_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE); + + /** The number of Long values, as a BigInteger. */ + private static final BigInteger BIG_LONG_WIDTH = + BIG_LONG_MAX_VALUE.subtract(BIG_LONG_MIN_VALUE).add(BigInteger.ONE); + + /** A range containing all possible 32-bit values. */ + public static final Range INT_EVERYTHING = create(Integer.MIN_VALUE, Integer.MAX_VALUE); + + /** The number of values representable in 32 bits: 2^32 or {@code 1<<32}. */ + private static final long INT_WIDTH = INT_EVERYTHING.width(); + + /** A range containing all possible 16-bit values. */ + public static final Range SHORT_EVERYTHING = create(Short.MIN_VALUE, Short.MAX_VALUE); + + /** The number of values representable in 16 bits: 2^16 or 1<<16. */ + private static final long SHORT_WIDTH = SHORT_EVERYTHING.width(); + + /** A range containing all possible char values. */ + public static final Range CHAR_EVERYTHING = create(Character.MIN_VALUE, Character.MAX_VALUE); + + /** The number of values representable in char: */ + private static final long CHAR_WIDTH = CHAR_EVERYTHING.width(); + + /** A range containing all possible 8-bit values. */ + public static final Range BYTE_EVERYTHING = create(Byte.MIN_VALUE, Byte.MAX_VALUE); + + /** The number of values representable in 8 bits: 2^8 or 1<<8. */ + private static final long BYTE_WIDTH = BYTE_EVERYTHING.width(); + + /** The empty range. This is the only Range object that contains nothing */ + @SuppressWarnings( + "interning:assignment.type.incompatible") // no other constructor call makes this + public static final @InternedDistinct Range NOTHING = new Range(Long.MAX_VALUE, Long.MIN_VALUE); + + /** An alias to the range containing all possible 64-bit values. */ + public static final Range EVERYTHING = LONG_EVERYTHING; + + /** + * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. + * + *

This is a private constructor that does no validation of arguments, so special instances + * (e.g., {@link #NOTHING}) can be created through it. + * + * @param from the lower bound (inclusive) + * @param to the upper bound (inclusive) + */ + private Range(long from, long to) { + this.from = from; + this.to = to; + } + + /** + * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. + * Requires {@code from <= to}. + * + * @param from the lower bound (inclusive) + * @param to the upper bound (inclusive) + * @return the Range [from..to] + */ + public static Range create(long from, long to) { + if (!(from <= to)) { + throw new IllegalArgumentException(String.format("Invalid Range: %s %s", from, to)); } - - /** - * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. - * Requires {@code from <= to}. - * - * @param from the lower bound (inclusive) - * @param to the upper bound (inclusive) - * @return the Range [from..to] - */ - public static Range create(long from, long to) { - if (!(from <= to)) { - throw new IllegalArgumentException(String.format("Invalid Range: %s %s", from, to)); - } - return new Range(from, to); + return new Range(from, to); + } + + /** + * Create a Range from a collection of Numbers. + * + * @param values collection whose min and max values will be used as the range's from and to + * values + * @return a range that encompasses all the argument's values ({@link #NOTHING} if the argument is + * an empty collection) + */ + public static Range create(Collection values) { + if (values.isEmpty()) { + return NOTHING; } - - /** - * Create a Range from a collection of Numbers. - * - * @param values collection whose min and max values will be used as the range's from and to - * values - * @return a range that encompasses all the argument's values ({@link #NOTHING} if the argument - * is an empty collection) - */ - public static Range create(Collection values) { - if (values.isEmpty()) { - return NOTHING; - } - long min = values.iterator().next().longValue(); - long max = min; - for (Number value : values) { - long current = value.longValue(); - if (min > current) min = current; - if (max < current) max = current; - } - return create(min, max); + long min = values.iterator().next().longValue(); + long max = min; + for (Number value : values) { + long current = value.longValue(); + if (min > current) min = current; + if (max < current) max = current; } - - /** - * Returns a Range representing all possible values for the given primitive type. - * - * @param typeKind one of INT, SHORT, BYTE, CHAR, or LONG - * @return the range for the given primitive type - */ - public static Range create(TypeKind typeKind) { - switch (typeKind) { - case INT: - return INT_EVERYTHING; - case SHORT: - return SHORT_EVERYTHING; - case BYTE: - return BYTE_EVERYTHING; - case CHAR: - return CHAR_EVERYTHING; - case LONG: - return LONG_EVERYTHING; - default: - throw new IllegalArgumentException( - "Invalid TypeKind for Range: expected INT, SHORT, BYTE, CHAR, or LONG, got " - + typeKind); - } + return create(min, max); + } + + /** + * Returns a Range representing all possible values for the given primitive type. + * + * @param typeKind one of INT, SHORT, BYTE, CHAR, or LONG + * @return the range for the given primitive type + */ + public static Range create(TypeKind typeKind) { + switch (typeKind) { + case INT: + return INT_EVERYTHING; + case SHORT: + return SHORT_EVERYTHING; + case BYTE: + return BYTE_EVERYTHING; + case CHAR: + return CHAR_EVERYTHING; + case LONG: + return LONG_EVERYTHING; + default: + throw new IllegalArgumentException( + "Invalid TypeKind for Range: expected INT, SHORT, BYTE, CHAR, or LONG, got " + + typeKind); } - - /** - * Creates a range using BigInteger type bounds. - * - *

If the BigInteger range is wider than the full range of the Long class, return EVERYTHING. - * - *

If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is - * false, convert the bounds to Long type in accordance with Java twos-complement overflow - * rules, e.g., Long.MAX_VALUE + 1 is converted to Long.MIN_VALUE. - * - *

If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is - * true, convert the bound that is outside Long's range to max/min value of a Long. - * - * @param bigFrom the lower bound of the BigInteger range - * @param bigTo the upper bound of the BigInteger range - * @return a range with Long type bounds converted from the BigInteger range - */ - private static Range create(BigInteger bigFrom, BigInteger bigTo) { - if (ignoreOverflow) { - bigFrom = bigFrom.max(BIG_LONG_MIN_VALUE); - bigTo = bigTo.min(BIG_LONG_MAX_VALUE); - } else { - BigInteger bigWidth = bigTo.subtract(bigFrom).add(BigInteger.ONE); - if (bigWidth.compareTo(BIG_LONG_WIDTH) > 0) { - return EVERYTHING; - } - } - long longFrom = bigFrom.longValue(); - long longTo = bigTo.longValue(); - return createOrElse(longFrom, longTo, EVERYTHING); + } + + /** + * Creates a range using BigInteger type bounds. + * + *

If the BigInteger range is wider than the full range of the Long class, return EVERYTHING. + * + *

If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is false, + * convert the bounds to Long type in accordance with Java twos-complement overflow rules, e.g., + * Long.MAX_VALUE + 1 is converted to Long.MIN_VALUE. + * + *

If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is true, + * convert the bound that is outside Long's range to max/min value of a Long. + * + * @param bigFrom the lower bound of the BigInteger range + * @param bigTo the upper bound of the BigInteger range + * @return a range with Long type bounds converted from the BigInteger range + */ + private static Range create(BigInteger bigFrom, BigInteger bigTo) { + if (ignoreOverflow) { + bigFrom = bigFrom.max(BIG_LONG_MIN_VALUE); + bigTo = bigTo.min(BIG_LONG_MAX_VALUE); + } else { + BigInteger bigWidth = bigTo.subtract(bigFrom).add(BigInteger.ONE); + if (bigWidth.compareTo(BIG_LONG_WIDTH) > 0) { + return EVERYTHING; + } } - - /** - * Creates a Range if {@code from<=to}; otherwise returns the given Range value. - * - * @param from lower bound for the range - * @param to upper bound for the range - * @param alternate what to return if {@code from > to} - * @return a new Range [from..to], or {@code alternate} - */ - private static Range createOrElse(long from, long to, Range alternate) { - if (from <= to) { - return new Range(from, to); - } else { - return alternate; - } + long longFrom = bigFrom.longValue(); + long longTo = bigTo.longValue(); + return createOrElse(longFrom, longTo, EVERYTHING); + } + + /** + * Creates a Range if {@code from<=to}; otherwise returns the given Range value. + * + * @param from lower bound for the range + * @param to upper bound for the range + * @param alternate what to return if {@code from > to} + * @return a new Range [from..to], or {@code alternate} + */ + private static Range createOrElse(long from, long to, Range alternate) { + if (from <= to) { + return new Range(from, to); + } else { + return alternate; } - - /** - * Returns a range with its bounds specified by two parameters, {@code from} and {@code to}. If - * {@code from} is greater than {@code to}, returns {@link #NOTHING}. - * - * @param from the lower bound (inclusive) - * @param to the upper bound (inclusive) - * @return newly-created Range or NOTHING - */ - private static Range createOrNothing(long from, long to) { - return createOrElse(from, to, NOTHING); + } + + /** + * Returns a range with its bounds specified by two parameters, {@code from} and {@code to}. If + * {@code from} is greater than {@code to}, returns {@link #NOTHING}. + * + * @param from the lower bound (inclusive) + * @param to the upper bound (inclusive) + * @return newly-created Range or NOTHING + */ + private static Range createOrNothing(long from, long to) { + return createOrElse(from, to, NOTHING); + } + + /** + * Returns the number of values in this range. + * + * @return how many values are in the range + */ + private long width() { + return to - from + 1; + } + + @Override + public String toString() { + if (this.isNothing()) { + return "[]"; + } else { + return String.format("[%s..%s]", from, to); } + } - /** - * Returns the number of values in this range. - * - * @return how many values are in the range - */ - private long width() { - return to - from + 1; + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; } - - @Override - public String toString() { - if (this.isNothing()) { - return "[]"; - } else { - return String.format("[%s..%s]", from, to); - } + if (obj instanceof Range) { + return equalsRange((Range) obj); } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj instanceof Range) { - return equalsRange((Range) obj); - } - return false; + return false; + } + + @Override + public int hashCode() { + return Objects.hash(from, to); + } + + /** + * Compare two ranges in a type safe manner for equality without incurring the cost of an + * instanceof check such as equals(Object) does. + * + * @param range to compare against + * @return true for ranges that match from and to respectively + */ + private boolean equalsRange(Range range) { + return from == range.from && to == range.to; + } + + /** Return true if this range contains every {@code long} value. */ + public boolean isLongEverything() { + return equalsRange(LONG_EVERYTHING); + } + + /** Return true if this range contains every {@code int} value. */ + public boolean isIntEverything() { + return equalsRange(INT_EVERYTHING); + } + + /** Return true if this range contains every {@code short} value. */ + public boolean isShortEverything() { + return equalsRange(SHORT_EVERYTHING); + } + + /** Return true if this range contains every {@code char} value. */ + public boolean isCharEverything() { + return equalsRange(CHAR_EVERYTHING); + } + + /** Return true if this range contains every {@code byte} value. */ + public boolean isByteEverything() { + return equalsRange(BYTE_EVERYTHING); + } + + /** Return true if this range contains no values. */ + public boolean isNothing() { + return this == NOTHING; + } + + /** + * Converts this range to a 32-bit integral range. + * + *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Integer range, then + * that bound is set to the bound of the Integer range. + * + *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Integer class, return INT_EVERYTHING. + * + *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 32-bit integers, convert the bounds to Integer type in accordance with Java twos-complement + * overflow rules, e.g., Integer.MAX_VALUE + 1 is converted to Integer.MIN_VALUE. + * + * @return this range, converted to a 32-bit integral range + */ + @SuppressWarnings("UnnecessaryLongToIntConversion") + public Range intRange() { + if (this.isNothing()) { + return this; } - - @Override - public int hashCode() { - return Objects.hash(from, to); + if (INT_EVERYTHING.contains(this)) { + return this; } - - /** - * Compare two ranges in a type safe manner for equality without incurring the cost of an - * instanceof check such as equals(Object) does. - * - * @param range to compare against - * @return true for ranges that match from and to respectively - */ - private boolean equalsRange(Range range) { - return from == range.from && to == range.to; + if (ignoreOverflow) { + return create(clipToRange(from, INT_EVERYTHING), clipToRange(to, INT_EVERYTHING)); } - - /** Return true if this range contains every {@code long} value. */ - public boolean isLongEverything() { - return equalsRange(LONG_EVERYTHING); + if (this.isWiderThan(INT_WIDTH)) { + return INT_EVERYTHING; } - - /** Return true if this range contains every {@code int} value. */ - public boolean isIntEverything() { - return equalsRange(INT_EVERYTHING); + return createOrElse((int) this.from, (int) this.to, INT_EVERYTHING); + } + + /** + * Converts a this range to a 16-bit short range. + * + *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Short range, then + * that bound is set to the bound of the Short range. + * + *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Short class, return SHORT_EVERYTHING. + * + *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 16-bit integers, convert the bounds to Short type in accordance with Java twos-complement + * overflow rules, e.g., Short.MAX_VALUE + 1 is converted to Short.MIN_VALUE. + * + * @return this range, converted to a 16-bit short range + */ + public Range shortRange() { + if (this.isNothing()) { + return this; } - - /** Return true if this range contains every {@code short} value. */ - public boolean isShortEverything() { - return equalsRange(SHORT_EVERYTHING); + if (SHORT_EVERYTHING.contains(this)) { + return this; } - - /** Return true if this range contains every {@code char} value. */ - public boolean isCharEverything() { - return equalsRange(CHAR_EVERYTHING); + if (ignoreOverflow) { + return create(clipToRange(from, SHORT_EVERYTHING), clipToRange(to, SHORT_EVERYTHING)); } - - /** Return true if this range contains every {@code byte} value. */ - public boolean isByteEverything() { - return equalsRange(BYTE_EVERYTHING); + if (this.isWiderThan(SHORT_WIDTH)) { + // short is promoted to int before the operation so no need for explicit casting + return SHORT_EVERYTHING; } - - /** Return true if this range contains no values. */ - public boolean isNothing() { - return this == NOTHING; + return createOrElse((short) this.from, (short) this.to, SHORT_EVERYTHING); + } + + /** + * Converts this range to a char range. + * + *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Character range, + * then that bound is set to the bound of the Character range. + * + *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Character class, return CHAR_EVERYTHING. + * + *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 8-bit integers, convert the bounds to Character type in accordance with Java overflow rules + * (twos-complement), e.g., Character.MAX_VALUE + 1 is converted to Character.MIN_VALUE. + */ + public Range charRange() { + if (this.isNothing()) { + return this; } - - /** - * Converts this range to a 32-bit integral range. - * - *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Integer range, - * then that bound is set to the bound of the Integer range. - * - *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Integer class, return INT_EVERYTHING. - * - *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 32-bit integers, convert the bounds to Integer type in accordance with Java twos-complement - * overflow rules, e.g., Integer.MAX_VALUE + 1 is converted to Integer.MIN_VALUE. - * - * @return this range, converted to a 32-bit integral range - */ - @SuppressWarnings("UnnecessaryLongToIntConversion") - public Range intRange() { - if (this.isNothing()) { - return this; - } - if (INT_EVERYTHING.contains(this)) { - return this; - } - if (ignoreOverflow) { - return create(clipToRange(from, INT_EVERYTHING), clipToRange(to, INT_EVERYTHING)); - } - if (this.isWiderThan(INT_WIDTH)) { - return INT_EVERYTHING; - } - return createOrElse((int) this.from, (int) this.to, INT_EVERYTHING); + if (CHAR_EVERYTHING.contains(this)) { + return this; } - - /** - * Converts a this range to a 16-bit short range. - * - *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Short range, then - * that bound is set to the bound of the Short range. - * - *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Short class, return SHORT_EVERYTHING. - * - *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 16-bit integers, convert the bounds to Short type in accordance with Java twos-complement - * overflow rules, e.g., Short.MAX_VALUE + 1 is converted to Short.MIN_VALUE. - * - * @return this range, converted to a 16-bit short range - */ - public Range shortRange() { - if (this.isNothing()) { - return this; - } - if (SHORT_EVERYTHING.contains(this)) { - return this; - } - if (ignoreOverflow) { - return create(clipToRange(from, SHORT_EVERYTHING), clipToRange(to, SHORT_EVERYTHING)); - } - if (this.isWiderThan(SHORT_WIDTH)) { - // short is promoted to int before the operation so no need for explicit casting - return SHORT_EVERYTHING; - } - return createOrElse((short) this.from, (short) this.to, SHORT_EVERYTHING); + if (ignoreOverflow) { + return create(clipToRange(from, CHAR_EVERYTHING), clipToRange(to, CHAR_EVERYTHING)); } - - /** - * Converts this range to a char range. - * - *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Character range, - * then that bound is set to the bound of the Character range. - * - *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Character class, return CHAR_EVERYTHING. - * - *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 8-bit integers, convert the bounds to Character type in accordance with Java overflow rules - * (twos-complement), e.g., Character.MAX_VALUE + 1 is converted to Character.MIN_VALUE. - */ - public Range charRange() { - if (this.isNothing()) { - return this; - } - if (CHAR_EVERYTHING.contains(this)) { - return this; - } - if (ignoreOverflow) { - return create(clipToRange(from, CHAR_EVERYTHING), clipToRange(to, CHAR_EVERYTHING)); - } - if (this.isWiderThan(CHAR_WIDTH)) { - // char is promoted to int before the operation so no need for explicit casting - return CHAR_EVERYTHING; - } - return createOrElse((char) this.from, (char) this.to, CHAR_EVERYTHING); + if (this.isWiderThan(CHAR_WIDTH)) { + // char is promoted to int before the operation so no need for explicit casting + return CHAR_EVERYTHING; } - - /** - * Converts this range to an 8-bit byte range. - * - *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Byte range, then - * that bound is set to the bound of the Byte range. - * - *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Byte class, return BYTE_EVERYTHING. - * - *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 8-bit integers, convert the bounds to Byte type in accordance with Java twos-complement - * overflow rules, e.g., Byte.MAX_VALUE + 1 is converted to Byte.MIN_VALUE. - * - * @return this range, converted to an 8-bit byte range - */ - public Range byteRange() { - if (this.isNothing()) { - return this; - } - if (BYTE_EVERYTHING.contains(this)) { - return this; - } - if (ignoreOverflow) { - return create(clipToRange(from, BYTE_EVERYTHING), clipToRange(to, BYTE_EVERYTHING)); - } - if (this.isWiderThan(BYTE_WIDTH)) { - // byte is promoted to int before the operation so no need for explicit casting - return BYTE_EVERYTHING; - } - return createOrElse((byte) this.from, (byte) this.to, BYTE_EVERYTHING); + return createOrElse((char) this.from, (char) this.to, CHAR_EVERYTHING); + } + + /** + * Converts this range to an 8-bit byte range. + * + *

If {@link #ignoreOverflow} is true and one of the bounds is outside the Byte range, then + * that bound is set to the bound of the Byte range. + * + *

If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Byte class, return BYTE_EVERYTHING. + * + *

If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 8-bit integers, convert the bounds to Byte type in accordance with Java twos-complement + * overflow rules, e.g., Byte.MAX_VALUE + 1 is converted to Byte.MIN_VALUE. + * + * @return this range, converted to an 8-bit byte range + */ + public Range byteRange() { + if (this.isNothing()) { + return this; } - - /** - * Return x clipped to the given range; out-of-range values become extremal values. Appropriate - * only when {@link #ignoreOverflow} is true. - * - * @param x a value - * @param r a range - * @return a value within the range; if x is outside r, returns the min or max of r - */ - private long clipToRange(long x, Range r) { - if (x < r.from) { - return r.from; - } else if (x > r.to) { - return r.to; - } else { - return x; - } + if (BYTE_EVERYTHING.contains(this)) { + return this; } - - /** - * Returns true if the element is contained in this range. - * - * @param element the value to seek - * @return true if {@code element} is in this range - */ - public boolean contains(long element) { - return from <= element && element <= to; + if (ignoreOverflow) { + return create(clipToRange(from, BYTE_EVERYTHING), clipToRange(to, BYTE_EVERYTHING)); } - - /** - * Returns true if the other range is contained in this range. - * - * @param other the range that might be within this one - * @return true if {@code other} is within this range - */ - public boolean contains(Range other) { - return other.isWithin(from, to); + if (this.isWiderThan(BYTE_WIDTH)) { + // byte is promoted to int before the operation so no need for explicit casting + return BYTE_EVERYTHING; } - - /** - * Returns the smallest range that includes all values contained in either of the two ranges. We - * call this the union of two ranges. - * - * @param right a range to union with this range - * @return a range resulting from the union of the specified range and this range - */ - public Range union(Range right) { - if (this.isNothing()) { - return right; - } else if (right.isNothing()) { - return this; - } - - long resultFrom = Math.min(from, right.from); - long resultTo = Math.max(to, right.to); - return create(resultFrom, resultTo); + return createOrElse((byte) this.from, (byte) this.to, BYTE_EVERYTHING); + } + + /** + * Return x clipped to the given range; out-of-range values become extremal values. Appropriate + * only when {@link #ignoreOverflow} is true. + * + * @param x a value + * @param r a range + * @return a value within the range; if x is outside r, returns the min or max of r + */ + private long clipToRange(long x, Range r) { + if (x < r.from) { + return r.from; + } else if (x > r.to) { + return r.to; + } else { + return x; } - - /** - * Returns the smallest range that includes all values contained in both of the two ranges. We - * call this the intersection of two ranges. If there is no overlap between the two ranges, - * returns an empty range. - * - * @param right the range to intersect with this range - * @return a range resulting from the intersection of the specified range and this range - */ - public Range intersect(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - long resultFrom = Math.max(from, right.from); - long resultTo = Math.min(to, right.to); - return createOrNothing(resultFrom, resultTo); + } + + /** + * Returns true if the element is contained in this range. + * + * @param element the value to seek + * @return true if {@code element} is in this range + */ + public boolean contains(long element) { + return from <= element && element <= to; + } + + /** + * Returns true if the other range is contained in this range. + * + * @param other the range that might be within this one + * @return true if {@code other} is within this range + */ + public boolean contains(Range other) { + return other.isWithin(from, to); + } + + /** + * Returns the smallest range that includes all values contained in either of the two ranges. We + * call this the union of two ranges. + * + * @param right a range to union with this range + * @return a range resulting from the union of the specified range and this range + */ + public Range union(Range right) { + if (this.isNothing()) { + return right; + } else if (right.isNothing()) { + return this; } - /** - * Returns the range with the lowest to and from values of this range and the passed range. - * - * @param other the range to compare - * @return the range with the lowest to and from values of this range and the passed range - */ - public Range min(Range other) { - return create(Math.min(this.from, other.from), Math.min(this.to, other.to)); + long resultFrom = Math.min(from, right.from); + long resultTo = Math.max(to, right.to); + return create(resultFrom, resultTo); + } + + /** + * Returns the smallest range that includes all values contained in both of the two ranges. We + * call this the intersection of two ranges. If there is no overlap between the two ranges, + * returns an empty range. + * + * @param right the range to intersect with this range + * @return a range resulting from the intersection of the specified range and this range + */ + public Range intersect(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns the range with the highest to and from values of this range and the passed range. - * - * @param other the range to compare - * @return the range with the highest to and from values of this range and the passed range - */ - public Range max(Range other) { - return create(Math.max(this.from, other.from), Math.max(this.to, other.to)); + long resultFrom = Math.max(from, right.from); + long resultTo = Math.min(to, right.to); + return createOrNothing(resultFrom, resultTo); + } + + /** + * Returns the range with the lowest to and from values of this range and the passed range. + * + * @param other the range to compare + * @return the range with the lowest to and from values of this range and the passed range + */ + public Range min(Range other) { + return create(Math.min(this.from, other.from), Math.min(this.to, other.to)); + } + + /** + * Returns the range with the highest to and from values of this range and the passed range. + * + * @param other the range to compare + * @return the range with the highest to and from values of this range and the passed range + */ + public Range max(Range other) { + return create(Math.max(this.from, other.from), Math.max(this.to, other.to)); + } + + /** + * Returns the smallest range that includes all possible values resulting from adding an arbitrary + * value in the specified range to an arbitrary value in this range. We call this the addition of + * two ranges. + * + * @param right a range to be added to this range + * @return the range resulting from the addition of the specified range and this range + */ + public Range plus(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns the smallest range that includes all possible values resulting from adding an - * arbitrary value in the specified range to an arbitrary value in this range. We call this the - * addition of two ranges. - * - * @param right a range to be added to this range - * @return the range resulting from the addition of the specified range and this range - */ - public Range plus(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - if (this.isWithinHalfLong() && right.isWithinHalfLong()) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from + right.from; - long resultTo = to + right.to; - if (from > to) { - return Range.EVERYTHING; - } else { - return create(resultFrom, resultTo); - } - } else { - BigInteger bigFrom = BigInteger.valueOf(from).add(BigInteger.valueOf(right.from)); - BigInteger bigTo = BigInteger.valueOf(to).add(BigInteger.valueOf(right.to)); - return create(bigFrom, bigTo); - } + if (this.isWithinHalfLong() && right.isWithinHalfLong()) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from + right.from; + long resultTo = to + right.to; + if (from > to) { + return Range.EVERYTHING; + } else { + return create(resultFrom, resultTo); + } + } else { + BigInteger bigFrom = BigInteger.valueOf(from).add(BigInteger.valueOf(right.from)); + BigInteger bigTo = BigInteger.valueOf(to).add(BigInteger.valueOf(right.to)); + return create(bigFrom, bigTo); } - - /** - * Returns the smallest range that includes all possible values resulting from subtracting an - * arbitrary value in the specified range from an arbitrary value in this range. We call this - * the subtraction of two ranges. - * - * @param right the range to be subtracted from this range - * @return the range resulting from subtracting the specified range from this range - */ - public Range minus(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - if (this.isWithinHalfLong() && right.isWithinHalfLong()) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from - right.to; - long resultTo = to - right.from; - return create(resultFrom, resultTo); - } else { - BigInteger bigFrom = BigInteger.valueOf(from).subtract(BigInteger.valueOf(right.to)); - BigInteger bigTo = BigInteger.valueOf(to).subtract(BigInteger.valueOf(right.from)); - return create(bigFrom, bigTo); - } + } + + /** + * Returns the smallest range that includes all possible values resulting from subtracting an + * arbitrary value in the specified range from an arbitrary value in this range. We call this the + * subtraction of two ranges. + * + * @param right the range to be subtracted from this range + * @return the range resulting from subtracting the specified range from this range + */ + public Range minus(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns the smallest range that includes all possible values resulting from multiplying an - * arbitrary value in the specified range by an arbitrary value in this range. We call this the - * multiplication of two ranges. - * - * @param right the specified range to be multiplied by this range - * @return the range resulting from multiplying the specified range by this range - */ - public Range times(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - // These bounds are adequate: Integer.MAX_VALUE^2 is still a bit less than Long.MAX_VALUE. - if (this.isWithinInteger() && right.isWithinInteger()) { - List possibleValues = - Arrays.asList( - from * right.from, from * right.to, to * right.from, to * right.to); - return create(possibleValues); - } else { - BigInteger bigLeftFrom = BigInteger.valueOf(from); - BigInteger bigRightFrom = BigInteger.valueOf(right.from); - BigInteger bigRightTo = BigInteger.valueOf(right.to); - BigInteger bigLeftTo = BigInteger.valueOf(to); - List bigPossibleValues = - Arrays.asList( - bigLeftFrom.multiply(bigRightFrom), - bigLeftFrom.multiply(bigRightTo), - bigLeftTo.multiply(bigRightFrom), - bigLeftTo.multiply(bigRightTo)); - return create(Collections.min(bigPossibleValues), Collections.max(bigPossibleValues)); - } + if (this.isWithinHalfLong() && right.isWithinHalfLong()) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from - right.to; + long resultTo = to - right.from; + return create(resultFrom, resultTo); + } else { + BigInteger bigFrom = BigInteger.valueOf(from).subtract(BigInteger.valueOf(right.to)); + BigInteger bigTo = BigInteger.valueOf(to).subtract(BigInteger.valueOf(right.from)); + return create(bigFrom, bigTo); } - - /** - * Returns the smallest range that includes all possible values resulting from dividing (integer - * division) an arbitrary value in this range by an arbitrary value in the specified range. We - * call this the division of two ranges. - * - * @param right the specified range by which this range is divided - * @return the range resulting from dividing this range by the specified range - */ - public Range divide(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - if (right.from == 0 && right.to == 0) { - return NOTHING; - } - // Special cases that involve overflow. - // The only overflow in integer division is Long.MIN_VALUE / -1 == Long.MIN_VALUE. - if (from == Long.MIN_VALUE && right.contains(-1)) { - // The values in the right range are all negative because right does not contain 0 but - // does contain 1. - if (from != to) { - // Special case 1: - // This range contains Long.MIN_VALUE and Long.MIN_VALUE + 1, which makes the - // result range EVERYTHING. - return EVERYTHING; - } else if (right.from != right.to) { - // Special case 2: - // This range contains only Long.MIN_VALUE, and the right range contains at least -1 - // and -2. The result range is from Long.MIN_VALUE to Long.MIN_VALUE / -2. - return create(Long.MIN_VALUE, Long.MIN_VALUE / -2); - } else { - // Special case 3: - // This range contains only Long.MIN_VALUE, and right contains only -1. - return create(Long.MIN_VALUE, Long.MIN_VALUE); - } - } - // We needn't worry about the overflow issue starting from here. - - // There are 9 different cases: - // (note: pos=positive, neg=negative, unk=unknown sign, np=non-positive, nn=non-negative) - long resultFrom; - long resultTo; - if (from > 0) { // this range is positive - if (right.from >= 0) { - // 1. right: nn - resultFrom = from / Math.max(right.to, 1); - resultTo = to / Math.max(right.from, 1); - } else if (right.to <= 0) { - // 2. right: np - resultFrom = to / Math.min(right.to, -1); - resultTo = from / Math.min(right.from, -1); - } else { - // 3. right: unk; values include -1 and 1 - resultFrom = -to; - resultTo = to; - } - } else if (to < 0) { // this range is negative - if (right.from >= 0) { - // 4. right: nn - resultFrom = from / Math.max(right.from, 1); - resultTo = to / Math.max(right.to, 1); - } else if (right.to <= 0) { - // 5. right: np - resultFrom = to / Math.min(right.from, -1); - resultTo = from / Math.min(right.to, -1); - } else { - // 6. right: unk; values include -1 and 1 - resultFrom = from; - resultTo = -from; - } - } else { // this range spans both signs - if (right.from >= 0) { - // 7. right: nn - resultFrom = from / Math.max(right.from, 1); - resultTo = to / Math.max(right.from, 1); - } else if (right.to <= 0) { - // 8. right: np - resultFrom = to / Math.min(right.to, -1); - resultTo = from / Math.min(right.to, -1); - } else { - // 9. right: unk; values include -1 and 1 - resultFrom = Math.min(from, -to); - resultTo = Math.max(-from, to); - } - } - return create(resultFrom, resultTo); + } + + /** + * Returns the smallest range that includes all possible values resulting from multiplying an + * arbitrary value in the specified range by an arbitrary value in this range. We call this the + * multiplication of two ranges. + * + * @param right the specified range to be multiplied by this range + * @return the range resulting from multiplying the specified range by this range + */ + public Range times(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns a range that includes all possible values of the remainder of dividing an arbitrary - * value in this range by an arbitrary value in the specified range. - * - *

In the current implementation, the result might not be the smallest range that includes - * all the possible values. - * - * @param right the specified range by which this range is divided - * @return the range of the remainder of dividing this range by the specified range - */ - public Range remainder(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - if (right.from == 0 && right.to == 0) { - return NOTHING; - } - // Special cases that would cause overflow if we use the general method below - if (right.from == Long.MIN_VALUE) { - Range range; - // The value Long.MIN_VALUE as a divisor needs special handling as follows: - if (from == Long.MIN_VALUE) { - if (to == Long.MIN_VALUE) { - // This range only contains Long.MIN_VALUE, so the result range is {0}. - range = create(0, 0); - } else { // (to > Long.MIN_VALUE) - // When this range contains Long.MIN_VALUE, which would have a remainder of 0 if - // divided by Long.MIN_VALUE, the result range is {0} unioned with [from + 1, - // to]. - range = create(from + 1, to).union(create(0, 0)); - } - } else { // (from > Long.MIN_VALUE) - // When this range doesn't contain Long.MIN_VALUE, the remainder of each value - // in this range divided by Long.MIN_VALUE is this value itself. Therefore the - // result range is this range itself. - range = this; - } - // If right.to > Long.MIN_VALUE, union the previous result with the result of range - // [right.from + 1, right.to] divided by this range, which can be calculated using - // the general method (see below) - if (right.to > Long.MIN_VALUE) { - Range rangeAdditional = this.remainder(create(right.from + 1, right.to)); - range = range.union(rangeAdditional); - } - return range; - } - // General method: - // Calculate range1: the result range of this range divided by EVERYTHING. For example, - // if this range is [3, 5], then the result range would be [0, 5]. If this range is [-3, 4], - // then the result range would be [-3, 4]. In general, the result range is {0} union with - // this range excluding the value Long.MIN_VALUE. - Range range1 = - create(Math.max(Long.MIN_VALUE + 1, from), Math.max(Long.MIN_VALUE + 1, to)) - .union(create(0, 0)); - // Calculate range2: the result range of range EVERYTHING divided by the right range. For - // example, if the right range is [-5, 3], then the result range would be [-4, 4]. If the - // right range is [3, 6], then the result range would be [-5, 5]. In general, the result - // range is calculated as following: - long maxAbsolute = Math.max(Math.abs(right.from), Math.abs(right.to)); - Range range2 = create(-maxAbsolute + 1, maxAbsolute - 1); - // Since range1 and range2 are both super sets of the minimal result range, we return the - // intersection of range1 and range2, which is correct (super set) and precise enough. - return range1.intersect(range2); + // These bounds are adequate: Integer.MAX_VALUE^2 is still a bit less than Long.MAX_VALUE. + if (this.isWithinInteger() && right.isWithinInteger()) { + List possibleValues = + Arrays.asList(from * right.from, from * right.to, to * right.from, to * right.to); + return create(possibleValues); + } else { + BigInteger bigLeftFrom = BigInteger.valueOf(from); + BigInteger bigRightFrom = BigInteger.valueOf(right.from); + BigInteger bigRightTo = BigInteger.valueOf(right.to); + BigInteger bigLeftTo = BigInteger.valueOf(to); + List bigPossibleValues = + Arrays.asList( + bigLeftFrom.multiply(bigRightFrom), + bigLeftFrom.multiply(bigRightTo), + bigLeftTo.multiply(bigRightFrom), + bigLeftTo.multiply(bigRightTo)); + return create(Collections.min(bigPossibleValues), Collections.max(bigPossibleValues)); } - - /** - * Returns a range that includes all possible values resulting from left shifting an arbitrary - * value in this range by an arbitrary number of bits in the specified range. We call this the - * left shift of a range. - * - * @param right the range of bits by which this range is left shifted - * @return the range resulting from left shifting this range by the specified range - */ - public Range shiftLeft(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - // Shifting operations in Java are depending on the type of the left-hand operand: - // If the left-hand operand is int type, only the 5 lowest-order bits of the right-hand - // operand are used. - // If the left-hand operand is long type, only the 6 lowest-order bits of the right-hand - // operand are used. - // For example, while 1 << -1== 1 << 31, 1L << -1 == 1L << 63. - // For ths reason, we restrict the shift-bits to analyze in [0. 31] and give up the analysis - // when out of this range. - // - // Other possible solutions: - // 1. create different methods for int type and long type and use them accordingly - // 2. add an additional boolean parameter to indicate the type of the left-hand operand - // - // see https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.19 for more - // detail. - if (right.isWithin(0, 31)) { - if (this.isWithinInteger()) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from << (from >= 0 ? right.from : right.to); - long resultTo = to << (to >= 0 ? right.to : right.from); - return create(resultFrom, resultTo); - } else { - BigInteger bigFrom = - BigInteger.valueOf(from) - .shiftLeft(from >= 0 ? (int) right.from : (int) right.to); - BigInteger bigTo = - BigInteger.valueOf(to) - .shiftLeft(to >= 0 ? (int) right.to : (int) right.from); - return create(bigFrom, bigTo); - } - } else { - // In other cases, we give up on the calculation and return EVERYTHING (rare in - // practice). - return EVERYTHING; - } + } + + /** + * Returns the smallest range that includes all possible values resulting from dividing (integer + * division) an arbitrary value in this range by an arbitrary value in the specified range. We + * call this the division of two ranges. + * + * @param right the specified range by which this range is divided + * @return the range resulting from dividing this range by the specified range + */ + public Range divide(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - - /** - * Returns a range that includes all possible values resulting from signed right shifting an - * arbitrary value in this range by an arbitrary number of bits in the specified range. We call - * this the signed right shift operation of a range. - * - * @param right the range of bits by which this range is signed right shifted - * @return the range resulting from signed right shifting this range by the specified range - */ - public Range signedShiftRight(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - if (this.isWithinInteger() && right.isWithin(0, 31)) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from >> (from >= 0 ? right.to : right.from); - long resultTo = to >> (to >= 0 ? right.from : right.to); - return create(resultFrom, resultTo); - } else { - // Signed shift right operation for long type cannot be simulated with BigInteger. - // Give up on the calculation and return EVERYTHING instead. - return EVERYTHING; - } + if (right.from == 0 && right.to == 0) { + return NOTHING; } - - /** - * When this range only contains non-negative values, the refined result should be the same as - * {@link #signedShiftRight(Range)}. We give up the analysis when this range contains negative - * value(s). - */ - public Range unsignedShiftRight(Range right) { - if (this.from >= 0) { - return signedShiftRight(right); - } - - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - + // Special cases that involve overflow. + // The only overflow in integer division is Long.MIN_VALUE / -1 == Long.MIN_VALUE. + if (from == Long.MIN_VALUE && right.contains(-1)) { + // The values in the right range are all negative because right does not contain 0 but + // does contain 1. + if (from != to) { + // Special case 1: + // This range contains Long.MIN_VALUE and Long.MIN_VALUE + 1, which makes the + // result range EVERYTHING. return EVERYTHING; + } else if (right.from != right.to) { + // Special case 2: + // This range contains only Long.MIN_VALUE, and the right range contains at least -1 + // and -2. The result range is from Long.MIN_VALUE to Long.MIN_VALUE / -2. + return create(Long.MIN_VALUE, Long.MIN_VALUE / -2); + } else { + // Special case 3: + // This range contains only Long.MIN_VALUE, and right contains only -1. + return create(Long.MIN_VALUE, Long.MIN_VALUE); + } } - - /** - * Returns a range that includes all possible values resulting from performing the bitwise and - * operation on a value in this range by a mask in the specified range. We call this the bitwise - * and operation of a range. - * - *

The current implementation is conservative: it only refines the cases where the range of - * mask represents a constant. In other cases, it gives up on the refinement and returns {@code - * EVERYTHING} instead. - * - * @param right the range of mask of the bitwise and operation - * @return the range resulting from the bitwise and operation of this range and the specified - * range of mask - */ - public Range bitwiseAnd(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - // We only refine the cases where the range of mask represent a constant. - // Recall these two's-complement facts: - // 11111111 represents -1 - // 10000000 represents MIN_VALUE - - Range constant = null; - Range variable = null; - if (right.isConstant()) { - constant = right; - variable = this; - } else if (this.isConstant()) { - constant = this; - variable = right; - } - - if (constant != null) { - long mask = constant.from; - if (mask >= 0) { - // Sign bit of mask is 0. The elements in the result range must be positive, and - // the result range is upper-bounded by the mask. - if (variable.from >= 0) { - // Case 1.1: The result range is upper-bounded by the upper bound of this range. - return create(0, Math.min(mask, variable.to)); - } else if (variable.to < 0) { - // Case 1.2: The result range is upper-bounded by the upper bound of this range - // after ignoring the sign bit. The upper bound of this range has the most bits - // (of the highest place values) set to 1. - return create(0, Math.min(mask, noSignBit(variable.to))); - } else { - // Case 1.3: Since this range contains -1, the upper bound of this range after - // ignoring the sign bit is Long.MAX_VALUE and thus doesn't contribute to - // further refinement. - return create(0, mask); - } - } else { - // Sign bit of mask is 1. - if (variable.from >= 0) { - // Case 2.1: Similar to case 1.1 except that the sign bit of the mask can be - // ignored. - return create(0, Math.min(noSignBit(mask), variable.to)); - } else if (variable.to < 0) { - // Case 2.2: The sign bit of the elements in the result range must be 1. - // Therefore the lower bound of the result range is Long.MIN_VALUE (when all - // 1-bits are mismatched between the mask and the element in this range). The - // result range is also upper-bounded by this mask itself and the upper bound of - // this range. (Because more set bits means a larger number -- still negative, - // but closer to 0.) - return create(Long.MIN_VALUE, Math.min(mask, variable.to)); - } else { - // Case 2.3: Similar to case 2.2 except that the elements in this range could - // be positive, and thus the result range is upper-bounded by the upper bound - // of this range and the mask after ignoring the sign bit. - return create(Long.MIN_VALUE, Math.min(noSignBit(mask), variable.to)); - } - } - } - - return EVERYTHING; + // We needn't worry about the overflow issue starting from here. + + // There are 9 different cases: + // (note: pos=positive, neg=negative, unk=unknown sign, np=non-positive, nn=non-negative) + long resultFrom; + long resultTo; + if (from > 0) { // this range is positive + if (right.from >= 0) { + // 1. right: nn + resultFrom = from / Math.max(right.to, 1); + resultTo = to / Math.max(right.from, 1); + } else if (right.to <= 0) { + // 2. right: np + resultFrom = to / Math.min(right.to, -1); + resultTo = from / Math.min(right.from, -1); + } else { + // 3. right: unk; values include -1 and 1 + resultFrom = -to; + resultTo = to; + } + } else if (to < 0) { // this range is negative + if (right.from >= 0) { + // 4. right: nn + resultFrom = from / Math.max(right.from, 1); + resultTo = to / Math.max(right.to, 1); + } else if (right.to <= 0) { + // 5. right: np + resultFrom = to / Math.min(right.from, -1); + resultTo = from / Math.min(right.to, -1); + } else { + // 6. right: unk; values include -1 and 1 + resultFrom = from; + resultTo = -from; + } + } else { // this range spans both signs + if (right.from >= 0) { + // 7. right: nn + resultFrom = from / Math.max(right.from, 1); + resultTo = to / Math.max(right.from, 1); + } else if (right.to <= 0) { + // 8. right: np + resultFrom = to / Math.min(right.to, -1); + resultTo = from / Math.min(right.to, -1); + } else { + // 9. right: unk; values include -1 and 1 + resultFrom = Math.min(from, -to); + resultTo = Math.max(-from, to); + } } - - /** Return the argument, with its sign bit zeroed out. */ - private long noSignBit(Long mask) { - return mask & (-1L >>> 1); + return create(resultFrom, resultTo); + } + + /** + * Returns a range that includes all possible values of the remainder of dividing an arbitrary + * value in this range by an arbitrary value in the specified range. + * + *

In the current implementation, the result might not be the smallest range that includes all + * the possible values. + * + * @param right the specified range by which this range is divided + * @return the range of the remainder of dividing this range by the specified range + */ + public Range remainder(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - - /** We give up the analysis for bitwise OR operation. */ - public Range bitwiseOr(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } - - return EVERYTHING; + if (right.from == 0 && right.to == 0) { + return NOTHING; } - - /** We give up the analysis for bitwise XOR operation. */ - public Range bitwiseXor(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + // Special cases that would cause overflow if we use the general method below + if (right.from == Long.MIN_VALUE) { + Range range; + // The value Long.MIN_VALUE as a divisor needs special handling as follows: + if (from == Long.MIN_VALUE) { + if (to == Long.MIN_VALUE) { + // This range only contains Long.MIN_VALUE, so the result range is {0}. + range = create(0, 0); + } else { // (to > Long.MIN_VALUE) + // When this range contains Long.MIN_VALUE, which would have a remainder of 0 if + // divided by Long.MIN_VALUE, the result range is {0} unioned with [from + 1, + // to]. + range = create(from + 1, to).union(create(0, 0)); } - - return EVERYTHING; + } else { // (from > Long.MIN_VALUE) + // When this range doesn't contain Long.MIN_VALUE, the remainder of each value + // in this range divided by Long.MIN_VALUE is this value itself. Therefore the + // result range is this range itself. + range = this; + } + // If right.to > Long.MIN_VALUE, union the previous result with the result of range + // [right.from + 1, right.to] divided by this range, which can be calculated using + // the general method (see below) + if (right.to > Long.MIN_VALUE) { + Range rangeAdditional = this.remainder(create(right.from + 1, right.to)); + range = range.union(rangeAdditional); + } + return range; } - - /** - * Returns the range of a variable that falls within this range after applying the unary plus - * operation (which is a no-op). - * - * @return this range - */ - public Range unaryPlus() { - return this; + // General method: + // Calculate range1: the result range of this range divided by EVERYTHING. For example, + // if this range is [3, 5], then the result range would be [0, 5]. If this range is [-3, 4], + // then the result range would be [-3, 4]. In general, the result range is {0} union with + // this range excluding the value Long.MIN_VALUE. + Range range1 = + create(Math.max(Long.MIN_VALUE + 1, from), Math.max(Long.MIN_VALUE + 1, to)) + .union(create(0, 0)); + // Calculate range2: the result range of range EVERYTHING divided by the right range. For + // example, if the right range is [-5, 3], then the result range would be [-4, 4]. If the + // right range is [3, 6], then the result range would be [-5, 5]. In general, the result + // range is calculated as following: + long maxAbsolute = Math.max(Math.abs(right.from), Math.abs(right.to)); + Range range2 = create(-maxAbsolute + 1, maxAbsolute - 1); + // Since range1 and range2 are both super sets of the minimal result range, we return the + // intersection of range1 and range2, which is correct (super set) and precise enough. + return range1.intersect(range2); + } + + /** + * Returns a range that includes all possible values resulting from left shifting an arbitrary + * value in this range by an arbitrary number of bits in the specified range. We call this the + * left shift of a range. + * + * @param right the range of bits by which this range is left shifted + * @return the range resulting from left shifting this range by the specified range + */ + public Range shiftLeft(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns the range of a variable that falls within this range after applying the unary minus - * operation. - * - * @return the resulted range of applying unary minus on an arbitrary value in this range - */ - public Range unaryMinus() { - if (this.isNothing()) { - return NOTHING; - } + // Shifting operations in Java are depending on the type of the left-hand operand: + // If the left-hand operand is int type, only the 5 lowest-order bits of the right-hand + // operand are used. + // If the left-hand operand is long type, only the 6 lowest-order bits of the right-hand + // operand are used. + // For example, while 1 << -1== 1 << 31, 1L << -1 == 1L << 63. + // For ths reason, we restrict the shift-bits to analyze in [0. 31] and give up the analysis + // when out of this range. + // + // Other possible solutions: + // 1. create different methods for int type and long type and use them accordingly + // 2. add an additional boolean parameter to indicate the type of the left-hand operand + // + // see https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.19 for more + // detail. + if (right.isWithin(0, 31)) { + if (this.isWithinInteger()) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from << (from >= 0 ? right.from : right.to); + long resultTo = to << (to >= 0 ? right.to : right.from); + return create(resultFrom, resultTo); + } else { + BigInteger bigFrom = + BigInteger.valueOf(from).shiftLeft(from >= 0 ? (int) right.from : (int) right.to); + BigInteger bigTo = + BigInteger.valueOf(to).shiftLeft(to >= 0 ? (int) right.to : (int) right.from); + return create(bigFrom, bigTo); + } + } else { + // In other cases, we give up on the calculation and return EVERYTHING (rare in + // practice). + return EVERYTHING; + } + } + + /** + * Returns a range that includes all possible values resulting from signed right shifting an + * arbitrary value in this range by an arbitrary number of bits in the specified range. We call + * this the signed right shift operation of a range. + * + * @param right the range of bits by which this range is signed right shifted + * @return the range resulting from signed right shifting this range by the specified range + */ + public Range signedShiftRight(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } - if (from == Long.MIN_VALUE && from != to) { - // the only case that needs special handling because of overflow - return EVERYTHING; - } + if (this.isWithinInteger() && right.isWithin(0, 31)) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from >> (from >= 0 ? right.to : right.from); + long resultTo = to >> (to >= 0 ? right.from : right.to); + return create(resultFrom, resultTo); + } else { + // Signed shift right operation for long type cannot be simulated with BigInteger. + // Give up on the calculation and return EVERYTHING instead. + return EVERYTHING; + } + } + + /** + * When this range only contains non-negative values, the refined result should be the same as + * {@link #signedShiftRight(Range)}. We give up the analysis when this range contains negative + * value(s). + */ + public Range unsignedShiftRight(Range right) { + if (this.from >= 0) { + return signedShiftRight(right); + } - return create(-to, -from); + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns the range of a variable that falls within this range after applying the bitwise - * complement operation. - * - * @return the resulting range of applying bitwise complement on an arbitrary value in this - * range - */ - public Range bitwiseComplement() { - if (this.isNothing()) { - return NOTHING; - } + return EVERYTHING; + } + + /** + * Returns a range that includes all possible values resulting from performing the bitwise and + * operation on a value in this range by a mask in the specified range. We call this the bitwise + * and operation of a range. + * + *

The current implementation is conservative: it only refines the cases where the range of + * mask represents a constant. In other cases, it gives up on the refinement and returns {@code + * EVERYTHING} instead. + * + * @param right the range of mask of the bitwise and operation + * @return the range resulting from the bitwise and operation of this range and the specified + * range of mask + */ + public Range bitwiseAnd(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } - return create(~to, ~from); + // We only refine the cases where the range of mask represent a constant. + // Recall these two's-complement facts: + // 11111111 represents -1 + // 10000000 represents MIN_VALUE + + Range constant = null; + Range variable = null; + if (right.isConstant()) { + constant = right; + variable = this; + } else if (this.isConstant()) { + constant = this; + variable = right; } - /** - * Refines this range to reflect that some value in it can be less than a value in the given - * range. This is used for calculating the control-flow-refined result of the < operator. For - * example: - * - *

-     * 
-     *    {@literal @}IntRange(from = 0, to = 10) int a;
-     *    {@literal @}IntRange(from = 3, to = 7) int b;
-     *     ...
-     *     if (a < b) {
-     *         // range of a is now refined to [0, 6] because a value in range [7, 10]
-     *         // cannot be smaller than variable b with range [3, 7].
-     *         ...
-     *     }
-     * 
-     * 
- * - * Use the {@link #refineGreaterThanEq(Range)} method if you are also interested in refining the - * range of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineLessThan(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + if (constant != null) { + long mask = constant.from; + if (mask >= 0) { + // Sign bit of mask is 0. The elements in the result range must be positive, and + // the result range is upper-bounded by the mask. + if (variable.from >= 0) { + // Case 1.1: The result range is upper-bounded by the upper bound of this range. + return create(0, Math.min(mask, variable.to)); + } else if (variable.to < 0) { + // Case 1.2: The result range is upper-bounded by the upper bound of this range + // after ignoring the sign bit. The upper bound of this range has the most bits + // (of the highest place values) set to 1. + return create(0, Math.min(mask, noSignBit(variable.to))); + } else { + // Case 1.3: Since this range contains -1, the upper bound of this range after + // ignoring the sign bit is Long.MAX_VALUE and thus doesn't contribute to + // further refinement. + return create(0, mask); } - - if (right.to == Long.MIN_VALUE) { - return NOTHING; + } else { + // Sign bit of mask is 1. + if (variable.from >= 0) { + // Case 2.1: Similar to case 1.1 except that the sign bit of the mask can be + // ignored. + return create(0, Math.min(noSignBit(mask), variable.to)); + } else if (variable.to < 0) { + // Case 2.2: The sign bit of the elements in the result range must be 1. + // Therefore the lower bound of the result range is Long.MIN_VALUE (when all + // 1-bits are mismatched between the mask and the element in this range). The + // result range is also upper-bounded by this mask itself and the upper bound of + // this range. (Because more set bits means a larger number -- still negative, + // but closer to 0.) + return create(Long.MIN_VALUE, Math.min(mask, variable.to)); + } else { + // Case 2.3: Similar to case 2.2 except that the elements in this range could + // be positive, and thus the result range is upper-bounded by the upper bound + // of this range and the mask after ignoring the sign bit. + return create(Long.MIN_VALUE, Math.min(noSignBit(mask), variable.to)); } - - long resultTo = Math.min(to, right.to - 1); - return createOrNothing(from, resultTo); + } } - /** - * Refines this range to reflect that some value in it can be less than or equal to a value in - * the given range. This is used for calculating the control-flow-refined result of the <= - * operator. For example: - * - *
-     * 
-     *    {@literal @}IntRange(from = 0, to = 10) int a;
-     *    {@literal @}IntRange(from = 3, to = 7) int b;
-     *     ...
-     *     if (a <= b) {
-     *         // range of a is now refined to [0, 7] because a value in range [8, 10]
-     *         // cannot be less than or equal to variable b with range [3, 7].
-     *         ...
-     *     }
-     * 
-     * 
- * - * Use the {@link #refineGreaterThan(Range)} method if you are also interested in refining the - * range of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineLessThanEq(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } + return EVERYTHING; + } + + /** Return the argument, with its sign bit zeroed out. */ + private long noSignBit(Long mask) { + return mask & (-1L >>> 1); + } - long resultTo = Math.min(to, right.to); - return createOrNothing(from, resultTo); + /** We give up the analysis for bitwise OR operation. */ + public Range bitwiseOr(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Refines this range to reflect that some value in it can be greater than a value in the given - * range. This is used for calculating the control-flow-refined result of the > operator. For - * example: - * - *
-     * 
-     *    {@literal @}IntRange(from = 0, to = 10) int a;
-     *    {@literal @}IntRange(from = 3, to = 7) int b;
-     *     ...
-     *     if (a > b) {
-     *         // range of a is now refined to [4, 10] because a value in range [0, 3]
-     *         // cannot be greater than variable b with range [3, 7].
-     *         ...
-     *     }
-     * 
-     * 
- * - * Use the {@link #refineLessThanEq(Range)} method if you are also interested in refining the - * range of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineGreaterThan(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } + return EVERYTHING; + } - if (right.from == Long.MAX_VALUE) { - return NOTHING; - } + /** We give up the analysis for bitwise XOR operation. */ + public Range bitwiseXor(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } - long resultFrom = Math.max(from, right.from + 1); - return createOrNothing(resultFrom, to); + return EVERYTHING; + } + + /** + * Returns the range of a variable that falls within this range after applying the unary plus + * operation (which is a no-op). + * + * @return this range + */ + public Range unaryPlus() { + return this; + } + + /** + * Returns the range of a variable that falls within this range after applying the unary minus + * operation. + * + * @return the resulted range of applying unary minus on an arbitrary value in this range + */ + public Range unaryMinus() { + if (this.isNothing()) { + return NOTHING; } - /** - * Refines this range to reflect that some value in it can be greater than or equal to a value - * in the given range. This is used for calculating the control-flow-refined result of the >= - * operator. For example: - * - *
-     * 
-     *    {@literal @}IntRange(from = 0, to = 10) int a;
-     *    {@literal @}IntRange(from = 3, to = 7) int b;
-     *     ...
-     *     if (a >= b) {
-     *         // range of a is now refined to [3, 10] because a value in range [0, 2]
-     *         // cannot be greater than or equal to variable b with range [3, 7].
-     *         ...
-     *     }
-     * 
-     * 
- * - * Use the {@link #refineLessThan(Range)} method if you are also interested in refining the - * range of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineGreaterThanEq(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } + if (from == Long.MIN_VALUE && from != to) { + // the only case that needs special handling because of overflow + return EVERYTHING; + } - long resultFrom = Math.max(from, right.from); - return createOrNothing(resultFrom, to); + return create(-to, -from); + } + + /** + * Returns the range of a variable that falls within this range after applying the bitwise + * complement operation. + * + * @return the resulting range of applying bitwise complement on an arbitrary value in this range + */ + public Range bitwiseComplement() { + if (this.isNothing()) { + return NOTHING; } - /** - * Refines this range to reflect that some value in it can be equal to a value in the given - * range. This is used for calculating the control-flow-refined result of the == operator. For - * example: - * - *
-     * 
-     *    {@literal @}IntRange(from = 0, to = 10) int a;
-     *    {@literal @}IntRange(from = 3, to = 15) int b;
-     *     ...
-     *     if (a == b) {
-     *         // range of a is now refined to [3, 10] because a value in range [0, 2]
-     *         // cannot be equal to variable b with range [3, 15].
-     *         ...
-     *     }
-     * 
-     * 
- * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineEqualTo(Range right) { - return this.intersect(right); + return create(~to, ~from); + } + + /** + * Refines this range to reflect that some value in it can be less than a value in the given + * range. This is used for calculating the control-flow-refined result of the < operator. For + * example: + * + *
+   * 
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a < b) {
+   *         // range of a is now refined to [0, 6] because a value in range [7, 10]
+   *         // cannot be smaller than variable b with range [3, 7].
+   *         ...
+   *     }
+   * 
+   * 
+ * + * Use the {@link #refineGreaterThanEq(Range)} method if you are also interested in refining the + * range of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineLessThan(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Refines this range to reflect that some value in it must not be equal to a value in the given - * range. This only changes the range if the given range (right) contains exactly one integer, - * and that integer is one of the bounds of this range. This is used for calculating the - * control-flow-refined result of the != operator. For example: - * - *
-     * 
-     *    {@literal @}IntRange(from = 0, to = 10) int a;
-     *    {@literal @}IntRange(from = 0, to = 0) int b;
-     *     ...
-     *     if (a != b) {
-     *         // range of a is now refined to [1, 10] because it cannot
-     *         // be zero.
-     *         ...
-     *     }
-     * 
-     * 
- * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineNotEqualTo(Range right) { - if (right.isConstant()) { - if (this.to == right.to) { - return create(this.from, this.to - 1); - } else if (this.from == right.from) { - return create(this.from + 1, this.to); - } - } - return this; + if (right.to == Long.MIN_VALUE) { + return NOTHING; } - /** - * Returns true if the range is wider than a given value, i.e., if the number of possible values - * enclosed by this range is more than the given value. - * - * @param value the value to compare with - * @return true if wider than the given value - */ - public boolean isWiderThan(long value) { - if (this.isWithin((Long.MIN_VALUE >> 1) + 1, Long.MAX_VALUE >> 1)) { - // This bound is adequate to guarantee no overflow when using long to evaluate. - // Long.MIN_VALUE >> 1 + 1 = -4611686018427387903 - // Long.MAX_VALUE >> 1 = 4611686018427387903 - return width() > value; - } else { - return BigInteger.valueOf(to) - .subtract(BigInteger.valueOf(from)) - .add(BigInteger.ONE) - .compareTo(BigInteger.valueOf(value)) - > 0; - } + long resultTo = Math.min(to, right.to - 1); + return createOrNothing(from, resultTo); + } + + /** + * Refines this range to reflect that some value in it can be less than or equal to a value in the + * given range. This is used for calculating the control-flow-refined result of the <= + * operator. For example: + * + *
+   * 
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a <= b) {
+   *         // range of a is now refined to [0, 7] because a value in range [8, 10]
+   *         // cannot be less than or equal to variable b with range [3, 7].
+   *         ...
+   *     }
+   * 
+   * 
+ * + * Use the {@link #refineGreaterThan(Range)} method if you are also interested in refining the + * range of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineLessThanEq(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns true if this range represents a constant value. - * - * @return true if this range represents a constant value - */ - public boolean isConstant() { - return from == to; + long resultTo = Math.min(to, right.to); + return createOrNothing(from, resultTo); + } + + /** + * Refines this range to reflect that some value in it can be greater than a value in the given + * range. This is used for calculating the control-flow-refined result of the > operator. For + * example: + * + *
+   * 
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a > b) {
+   *         // range of a is now refined to [4, 10] because a value in range [0, 3]
+   *         // cannot be greater than variable b with range [3, 7].
+   *         ...
+   *     }
+   * 
+   * 
+ * + * Use the {@link #refineLessThanEq(Range)} method if you are also interested in refining the + * range of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineGreaterThan(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns true if this range is completely contained in the range specified by the given lower - * bound inclusive and upper bound inclusive. - * - * @param lb lower bound for the range that might contain this one - * @param ub upper bound for the range that might contain this one - * @return true if this range is within the given bounds - */ - public boolean isWithin(long lb, long ub) { - assert lb <= ub; - return lb <= from && to <= ub; + if (right.from == Long.MAX_VALUE) { + return NOTHING; } - /** - * Returns true if this range is contained inclusively between Long.MIN_VALUE/2 and - * Long.MAX_VALUE/2. Note: Long.MIN_VALUE/2 != -Long.MAX_VALUE/2 - */ - private boolean isWithinHalfLong() { - return isWithin(Long.MIN_VALUE >> 1, Long.MAX_VALUE >> 1); + long resultFrom = Math.max(from, right.from + 1); + return createOrNothing(resultFrom, to); + } + + /** + * Refines this range to reflect that some value in it can be greater than or equal to a value in + * the given range. This is used for calculating the control-flow-refined result of the >= + * operator. For example: + * + *
+   * 
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 7) int b;
+   *     ...
+   *     if (a >= b) {
+   *         // range of a is now refined to [3, 10] because a value in range [0, 2]
+   *         // cannot be greater than or equal to variable b with range [3, 7].
+   *         ...
+   *     }
+   * 
+   * 
+ * + * Use the {@link #refineLessThan(Range)} method if you are also interested in refining the range + * of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineGreaterThanEq(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - /** - * Returns true if this range is completely contained in the scope of the Integer type. - * - * @return true if the range is contained within the Integer range inclusive - */ - public boolean isWithinInteger() { - return isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE); + long resultFrom = Math.max(from, right.from); + return createOrNothing(resultFrom, to); + } + + /** + * Refines this range to reflect that some value in it can be equal to a value in the given range. + * This is used for calculating the control-flow-refined result of the == operator. For example: + * + *
+   * 
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 3, to = 15) int b;
+   *     ...
+   *     if (a == b) {
+   *         // range of a is now refined to [3, 10] because a value in range [0, 2]
+   *         // cannot be equal to variable b with range [3, 15].
+   *         ...
+   *     }
+   * 
+   * 
+ * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineEqualTo(Range right) { + return this.intersect(right); + } + + /** + * Refines this range to reflect that some value in it must not be equal to a value in the given + * range. This only changes the range if the given range (right) contains exactly one integer, and + * that integer is one of the bounds of this range. This is used for calculating the + * control-flow-refined result of the != operator. For example: + * + *
+   * 
+   *    {@literal @}IntRange(from = 0, to = 10) int a;
+   *    {@literal @}IntRange(from = 0, to = 0) int b;
+   *     ...
+   *     if (a != b) {
+   *         // range of a is now refined to [1, 10] because it cannot
+   *         // be zero.
+   *         ...
+   *     }
+   * 
+   * 
+ * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineNotEqualTo(Range right) { + if (right.isConstant()) { + if (this.to == right.to) { + return create(this.from, this.to - 1); + } else if (this.from == right.from) { + return create(this.from + 1, this.to); + } + } + return this; + } + + /** + * Returns true if the range is wider than a given value, i.e., if the number of possible values + * enclosed by this range is more than the given value. + * + * @param value the value to compare with + * @return true if wider than the given value + */ + public boolean isWiderThan(long value) { + if (this.isWithin((Long.MIN_VALUE >> 1) + 1, Long.MAX_VALUE >> 1)) { + // This bound is adequate to guarantee no overflow when using long to evaluate. + // Long.MIN_VALUE >> 1 + 1 = -4611686018427387903 + // Long.MAX_VALUE >> 1 = 4611686018427387903 + return width() > value; + } else { + return BigInteger.valueOf(to) + .subtract(BigInteger.valueOf(from)) + .add(BigInteger.ONE) + .compareTo(BigInteger.valueOf(value)) + > 0; } + } + + /** + * Returns true if this range represents a constant value. + * + * @return true if this range represents a constant value + */ + public boolean isConstant() { + return from == to; + } + + /** + * Returns true if this range is completely contained in the range specified by the given lower + * bound inclusive and upper bound inclusive. + * + * @param lb lower bound for the range that might contain this one + * @param ub upper bound for the range that might contain this one + * @return true if this range is within the given bounds + */ + public boolean isWithin(long lb, long ub) { + assert lb <= ub; + return lb <= from && to <= ub; + } + + /** + * Returns true if this range is contained inclusively between Long.MIN_VALUE/2 and + * Long.MAX_VALUE/2. Note: Long.MIN_VALUE/2 != -Long.MAX_VALUE/2 + */ + private boolean isWithinHalfLong() { + return isWithin(Long.MIN_VALUE >> 1, Long.MAX_VALUE >> 1); + } + + /** + * Returns true if this range is completely contained in the scope of the Integer type. + * + * @return true if the range is contained within the Integer range inclusive + */ + public boolean isWithinInteger() { + return isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java b/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java index bc70f02a989..7c3bdc71e25 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ShortMath extends NumberMath { - int number; + int number; - public ShortMath(int i) { - number = i; - } + public ShortMath(int i) { + number = i; + } - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); - } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); } - - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Double) { + return number + right.doubleValue(); } - - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Float) { + return number + right.floatValue(); } - - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); - } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); - } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); - } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); - } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); - } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryPlus() { - return +number; + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); } + throw new UnsupportedOperationException(); + } - @Override - public Number unaryMinus() { - return -number; + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Number bitwiseComplement() { - return ~number; + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); + } + + @Override + public Number unaryPlus() { + return +number; + } + + @Override + public Number unaryMinus() { + return -number; + } - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Number bitwiseComplement() { + return ~number; + } + + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); - } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); - } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); + } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); - } - if (right instanceof Float) { - return number <= right.floatValue(); - } - if (right instanceof Integer) { - return number <= right.intValue(); - } - if (right instanceof Long) { - return number <= right.longValue(); - } - if (right instanceof Short) { - return number <= right.shortValue(); - } - throw new UnsupportedOperationException(); + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); } + throw new UnsupportedOperationException(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java index b74e7799e3c..d88a4c27443 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java @@ -1,7 +1,16 @@ package org.checkerframework.common.wholeprograminference; import com.sun.tools.javac.code.Type.ArrayType; - +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AnnotationDef; import org.checkerframework.afu.scenelib.annotations.field.AnnotationFieldType; @@ -19,18 +28,6 @@ import org.plumelib.util.ArrayMap; import org.plumelib.util.CollectionsPlume; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; - /** * This class contains static methods that convert between {@link * org.checkerframework.afu.scenelib.annotations.Annotation} and {@link @@ -38,181 +35,181 @@ */ public class AnnotationConverter { - /** Creates a new AnnotationConverter. */ - AnnotationConverter() {} + /** Creates a new AnnotationConverter. */ + AnnotationConverter() {} - /** - * Converts an {@link javax.lang.model.element.AnnotationMirror} into an {@link - * org.checkerframework.afu.scenelib.annotations.Annotation}. - * - * @param am the AnnotationMirror - * @return the Annotation - */ - public static Annotation annotationMirrorToAnnotation(AnnotationMirror am) { - @SuppressWarnings("signature:argument") // TODO: bug for inner classes - AnnotationDef def = - new AnnotationDef( - AnnotationUtils.annotationName(am), - String.format( - "annotationMirrorToAnnotation %s [%s] keyset=%s", - am, am.getClass(), am.getElementValues().keySet())); - Map fieldTypes = new ArrayMap<>(am.getElementValues().size()); - // Handling cases where there are fields in annotations. - for (ExecutableElement ee : am.getElementValues().keySet()) { - AnnotationFieldType aft = getAnnotationFieldType(ee); - fieldTypes.put(ee.getSimpleName().toString(), aft); - } - def.setFieldTypes(fieldTypes); - - // Now, we handle the values of those types below - Map values = am.getElementValues(); - Map newValues = new HashMap<>(values.size()); - for (ExecutableElement ee : values.keySet()) { - Object value = values.get(ee).getValue(); - if (value instanceof List) { - // If we have a List here, then it is a List of AnnotationValue. - // Convert each AnnotationValue to its respective Java type. - @SuppressWarnings("unchecked") - List valueList = (List) value; - value = CollectionsPlume.mapList(AnnotationValue::getValue, valueList); - } else if (value instanceof TypeMirror) { - try { - value = Class.forName(TypesUtils.binaryName((TypeMirror) value)); - } catch (ClassNotFoundException e) { - throw new BugInCF(e, "value = %s [%s]", value, value.getClass()); - } - } - newValues.put(ee.getSimpleName().toString(), value); - } - Annotation out = new Annotation(def, newValues); - return out; + /** + * Converts an {@link javax.lang.model.element.AnnotationMirror} into an {@link + * org.checkerframework.afu.scenelib.annotations.Annotation}. + * + * @param am the AnnotationMirror + * @return the Annotation + */ + public static Annotation annotationMirrorToAnnotation(AnnotationMirror am) { + @SuppressWarnings("signature:argument") // TODO: bug for inner classes + AnnotationDef def = + new AnnotationDef( + AnnotationUtils.annotationName(am), + String.format( + "annotationMirrorToAnnotation %s [%s] keyset=%s", + am, am.getClass(), am.getElementValues().keySet())); + Map fieldTypes = new ArrayMap<>(am.getElementValues().size()); + // Handling cases where there are fields in annotations. + for (ExecutableElement ee : am.getElementValues().keySet()) { + AnnotationFieldType aft = getAnnotationFieldType(ee); + fieldTypes.put(ee.getSimpleName().toString(), aft); } + def.setFieldTypes(fieldTypes); - /** - * Converts an {@link org.checkerframework.afu.scenelib.annotations.Annotation} into an {@link - * javax.lang.model.element.AnnotationMirror}. - * - * @param anno the Annotation - * @param processingEnv the ProcessingEnvironment - * @return the AnnotationMirror - */ - protected static AnnotationMirror annotationToAnnotationMirror( - Annotation anno, ProcessingEnvironment processingEnv) { - AnnotationBuilder builder = - new AnnotationBuilder( - processingEnv, Signatures.binaryNameToFullyQualified(anno.def().name)); - for (String fieldKey : anno.fieldValues.keySet()) { - addFieldToAnnotationBuilder(fieldKey, anno.fieldValues.get(fieldKey), builder); + // Now, we handle the values of those types below + Map values = am.getElementValues(); + Map newValues = new HashMap<>(values.size()); + for (ExecutableElement ee : values.keySet()) { + Object value = values.get(ee).getValue(); + if (value instanceof List) { + // If we have a List here, then it is a List of AnnotationValue. + // Convert each AnnotationValue to its respective Java type. + @SuppressWarnings("unchecked") + List valueList = (List) value; + value = CollectionsPlume.mapList(AnnotationValue::getValue, valueList); + } else if (value instanceof TypeMirror) { + try { + value = Class.forName(TypesUtils.binaryName((TypeMirror) value)); + } catch (ClassNotFoundException e) { + throw new BugInCF(e, "value = %s [%s]", value, value.getClass()); } - return builder.build(); + } + newValues.put(ee.getSimpleName().toString(), value); } + Annotation out = new Annotation(def, newValues); + return out; + } - /** - * Returns the type of an element (that is, a field) of an annotation. - * - * @param ee an element (that is, a field) of an annotation - * @return the type of the given annotation field - */ - protected static @Nullable AnnotationFieldType getAnnotationFieldType(ExecutableElement ee) { - return typeMirrorToAnnotationFieldType(ee.getReturnType()); + /** + * Converts an {@link org.checkerframework.afu.scenelib.annotations.Annotation} into an {@link + * javax.lang.model.element.AnnotationMirror}. + * + * @param anno the Annotation + * @param processingEnv the ProcessingEnvironment + * @return the AnnotationMirror + */ + protected static AnnotationMirror annotationToAnnotationMirror( + Annotation anno, ProcessingEnvironment processingEnv) { + AnnotationBuilder builder = + new AnnotationBuilder( + processingEnv, Signatures.binaryNameToFullyQualified(anno.def().name)); + for (String fieldKey : anno.fieldValues.keySet()) { + addFieldToAnnotationBuilder(fieldKey, anno.fieldValues.get(fieldKey), builder); } + return builder.build(); + } - /** - * Converts a TypeMirror to an AnnotationFieldType. - * - * @param tm a type for an annotation element/field: primitive, String, class, enum constant, or - * array thereof - * @return an AnnotationFieldType corresponding to the argument - */ - protected static AnnotationFieldType typeMirrorToAnnotationFieldType(TypeMirror tm) { - switch (tm.getKind()) { - case BOOLEAN: - return BasicAFT.forType(boolean.class); - // Primitives - case BYTE: - return BasicAFT.forType(byte.class); - case CHAR: - return BasicAFT.forType(char.class); - case DOUBLE: - return BasicAFT.forType(double.class); - case FLOAT: - return BasicAFT.forType(float.class); - case INT: - return BasicAFT.forType(int.class); - case LONG: - return BasicAFT.forType(long.class); - case SHORT: - return BasicAFT.forType(short.class); + /** + * Returns the type of an element (that is, a field) of an annotation. + * + * @param ee an element (that is, a field) of an annotation + * @return the type of the given annotation field + */ + protected static @Nullable AnnotationFieldType getAnnotationFieldType(ExecutableElement ee) { + return typeMirrorToAnnotationFieldType(ee.getReturnType()); + } - case ARRAY: - TypeMirror componentType = ((ArrayType) tm).getComponentType(); - AnnotationFieldType componentAFT = typeMirrorToAnnotationFieldType(componentType); - return new ArrayAFT((ScalarAFT) componentAFT); + /** + * Converts a TypeMirror to an AnnotationFieldType. + * + * @param tm a type for an annotation element/field: primitive, String, class, enum constant, or + * array thereof + * @return an AnnotationFieldType corresponding to the argument + */ + protected static AnnotationFieldType typeMirrorToAnnotationFieldType(TypeMirror tm) { + switch (tm.getKind()) { + case BOOLEAN: + return BasicAFT.forType(boolean.class); + // Primitives + case BYTE: + return BasicAFT.forType(byte.class); + case CHAR: + return BasicAFT.forType(char.class); + case DOUBLE: + return BasicAFT.forType(double.class); + case FLOAT: + return BasicAFT.forType(float.class); + case INT: + return BasicAFT.forType(int.class); + case LONG: + return BasicAFT.forType(long.class); + case SHORT: + return BasicAFT.forType(short.class); - case DECLARED: - String className = TypesUtils.getQualifiedName((DeclaredType) tm); - if (className.equals("java.lang.String")) { - return BasicAFT.forType(String.class); - } else if (className.equals("java.lang.Class")) { - return ClassTokenAFT.ctaft; - } else { - // This must be an enum constant. - return new EnumAFT(className); - } + case ARRAY: + TypeMirror componentType = ((ArrayType) tm).getComponentType(); + AnnotationFieldType componentAFT = typeMirrorToAnnotationFieldType(componentType); + return new ArrayAFT((ScalarAFT) componentAFT); - default: - throw new BugInCF( - "typeMirrorToAnnotationFieldType: unexpected argument %s [%s %s]", - tm, tm.getKind(), tm.getClass()); + case DECLARED: + String className = TypesUtils.getQualifiedName((DeclaredType) tm); + if (className.equals("java.lang.String")) { + return BasicAFT.forType(String.class); + } else if (className.equals("java.lang.Class")) { + return ClassTokenAFT.ctaft; + } else { + // This must be an enum constant. + return new EnumAFT(className); } + + default: + throw new BugInCF( + "typeMirrorToAnnotationFieldType: unexpected argument %s [%s %s]", + tm, tm.getKind(), tm.getClass()); } + } - /** - * Adds a field to an AnnotationBuilder. - * - * @param fieldKey is the name of the field - * @param obj is the value of the field - * @param builder is the AnnotationBuilder - */ - @SuppressWarnings("unchecked") // This is actually checked in the first instanceOf call below. - protected static void addFieldToAnnotationBuilder( - String fieldKey, Object obj, AnnotationBuilder builder) { - if (obj instanceof List) { - builder.setValue(fieldKey, (List) obj); - } else if (obj instanceof String) { - builder.setValue(fieldKey, (String) obj); - } else if (obj instanceof Integer) { - builder.setValue(fieldKey, (Integer) obj); - } else if (obj instanceof Float) { - builder.setValue(fieldKey, (Float) obj); - } else if (obj instanceof Long) { - builder.setValue(fieldKey, (Long) obj); - } else if (obj instanceof Boolean) { - builder.setValue(fieldKey, (Boolean) obj); - } else if (obj instanceof Character) { - builder.setValue(fieldKey, (Character) obj); - } else if (obj instanceof Class) { - builder.setValue(fieldKey, (Class) obj); - } else if (obj instanceof Double) { - builder.setValue(fieldKey, (Double) obj); - } else if (obj instanceof Enum) { - builder.setValue(fieldKey, (Enum) obj); - } else if (obj instanceof Enum[]) { - builder.setValue(fieldKey, (Enum[]) obj); - } else if (obj instanceof AnnotationMirror) { - builder.setValue(fieldKey, (AnnotationMirror) obj); - } else if (obj instanceof Object[]) { - builder.setValue(fieldKey, (Object[]) obj); - } else if (obj instanceof TypeMirror) { - builder.setValue(fieldKey, (TypeMirror) obj); - } else if (obj instanceof Short) { - builder.setValue(fieldKey, (Short) obj); - } else if (obj instanceof VariableElement) { - builder.setValue(fieldKey, (VariableElement) obj); - } else if (obj instanceof VariableElement[]) { - builder.setValue(fieldKey, (VariableElement[]) obj); - } else { - throw new BugInCF("Unrecognized type: " + obj.getClass()); - } + /** + * Adds a field to an AnnotationBuilder. + * + * @param fieldKey is the name of the field + * @param obj is the value of the field + * @param builder is the AnnotationBuilder + */ + @SuppressWarnings("unchecked") // This is actually checked in the first instanceOf call below. + protected static void addFieldToAnnotationBuilder( + String fieldKey, Object obj, AnnotationBuilder builder) { + if (obj instanceof List) { + builder.setValue(fieldKey, (List) obj); + } else if (obj instanceof String) { + builder.setValue(fieldKey, (String) obj); + } else if (obj instanceof Integer) { + builder.setValue(fieldKey, (Integer) obj); + } else if (obj instanceof Float) { + builder.setValue(fieldKey, (Float) obj); + } else if (obj instanceof Long) { + builder.setValue(fieldKey, (Long) obj); + } else if (obj instanceof Boolean) { + builder.setValue(fieldKey, (Boolean) obj); + } else if (obj instanceof Character) { + builder.setValue(fieldKey, (Character) obj); + } else if (obj instanceof Class) { + builder.setValue(fieldKey, (Class) obj); + } else if (obj instanceof Double) { + builder.setValue(fieldKey, (Double) obj); + } else if (obj instanceof Enum) { + builder.setValue(fieldKey, (Enum) obj); + } else if (obj instanceof Enum[]) { + builder.setValue(fieldKey, (Enum[]) obj); + } else if (obj instanceof AnnotationMirror) { + builder.setValue(fieldKey, (AnnotationMirror) obj); + } else if (obj instanceof Object[]) { + builder.setValue(fieldKey, (Object[]) obj); + } else if (obj instanceof TypeMirror) { + builder.setValue(fieldKey, (TypeMirror) obj); + } else if (obj instanceof Short) { + builder.setValue(fieldKey, (Short) obj); + } else if (obj instanceof VariableElement) { + builder.setValue(fieldKey, (VariableElement) obj); + } else if (obj instanceof VariableElement[]) { + builder.setValue(fieldKey, (VariableElement[]) obj); + } else { + throw new BugInCF("Unrecognized type: " + obj.getClass()); } + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java index 45946c1e01e..8519c4e3bab 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java @@ -1,7 +1,25 @@ package org.checkerframework.common.wholeprograminference; import com.google.common.collect.ComparisonChain; - +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.regex.Pattern; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AClass; import org.checkerframework.afu.scenelib.annotations.el.AField; @@ -24,27 +42,6 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; -import java.util.regex.Pattern; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - // In this file, "base name" means "type without its package part in binary name format". // For example, "Outer$Inner" is a base name. @@ -69,861 +66,850 @@ */ public final class SceneToStubWriter { - /** - * The entry point to this class is {@link #write}. - * - *

This is a utility class with only static methods. It is not instantiable. - */ - private SceneToStubWriter() { - throw new BugInCF("Do not instantiate"); + /** + * The entry point to this class is {@link #write}. + * + *

This is a utility class with only static methods. It is not instantiable. + */ + private SceneToStubWriter() { + throw new BugInCF("Do not instantiate"); + } + + /** + * A pattern matching the name of an anonymous inner class, a local class, or a class nested + * within one of these types of classes. An anonymous inner class has a basename like Outer$1 and + * a local class has a basename like Outer$1Inner. See Java Language + * Specification, section 13.1. + */ + private static final Pattern anonymousInnerClassOrLocalClassPattern = Pattern.compile("\\$\\d+"); + + /** How far to indent when writing members of a stub file. */ + private static final String INDENT = " "; + + /** + * Writes the annotations in {@code scene} to {@code out} in stub file format. + * + * @param scene the scene to write out + * @param filename the name of the file to write (must end with .astub) + * @param checker the checker, for computing preconditions and postconditions + */ + public static void write(ASceneWrapper scene, String filename, BaseTypeChecker checker) { + writeImpl(scene, filename, checker); + } + + /** + * Returns the part of a binary name that specifies the package. + * + * @param className the binary name of a class + * @return the part of the name referring to the package, or null if there is no package name + */ + @SuppressWarnings("signature") // a valid non-empty package name is a dot separated identifier + private static @Nullable @DotSeparatedIdentifiers String packagePart( + @BinaryName String className) { + int lastdot = className.lastIndexOf('.'); + return (lastdot == -1) ? null : className.substring(0, lastdot); + } + + /** + * Returns the part of a binary name that specifies the basename of the class. + * + * @param className a binary name + * @return the part of the name representing the class's name without its package + */ + @SuppressWarnings("signature:return") // A binary name without its package is still a binary name + private static @BinaryName String basenamePart(@BinaryName String className) { + int lastdot = className.lastIndexOf('.'); + return className.substring(lastdot + 1); + } + + /** + * Returns the String representation of an annotation in Java source format. + * + * @param a the annotation to print + * @return the formatted annotation + */ + public static String formatAnnotation(Annotation a) { + StringBuilder sb = new StringBuilder(); + formatAnnotation(sb, a); + return sb.toString(); + } + + /** + * Formats an annotation in Java source format. + * + * @param sb where to format the annotation to + * @param a the annotation to print + */ + public static void formatAnnotation(StringBuilder sb, Annotation a) { + String fullAnnoName = a.def().name; + String simpleAnnoName = fullAnnoName.substring(fullAnnoName.lastIndexOf('.') + 1); + sb.append("@"); + sb.append(simpleAnnoName); + if (a.fieldValues.isEmpty()) { + return; + } else { + sb.append("("); + if (a.fieldValues.size() == 1 && a.fieldValues.containsKey("value")) { + AnnotationFieldType aft = a.def().fieldTypes.get("value"); + aft.format(sb, a.fieldValues.get("value")); + } else { + // This simulates: new StringJoiner(", ", "@" + simpleAnnoName + "(", ")") + for (Map.Entry f : a.fieldValues.entrySet()) { + AnnotationFieldType aft = a.def().fieldTypes.get(f.getKey()); + sb.append(f.getKey()); + sb.append("="); + aft.format(sb, f.getValue()); + sb.append(", "); + } + sb.delete(sb.length() - 2, sb.length()); + } + sb.append(")"); } - - /** - * A pattern matching the name of an anonymous inner class, a local class, or a class nested - * within one of these types of classes. An anonymous inner class has a basename like Outer$1 - * and a local class has a basename like Outer$1Inner. See Java Language - * Specification, section 13.1. - */ - private static final Pattern anonymousInnerClassOrLocalClassPattern = - Pattern.compile("\\$\\d+"); - - /** How far to indent when writing members of a stub file. */ - private static final String INDENT = " "; - - /** - * Writes the annotations in {@code scene} to {@code out} in stub file format. - * - * @param scene the scene to write out - * @param filename the name of the file to write (must end with .astub) - * @param checker the checker, for computing preconditions and postconditions - */ - public static void write(ASceneWrapper scene, String filename, BaseTypeChecker checker) { - writeImpl(scene, filename, checker); + } + + /** + * Returns all annotations in {@code annos} in a form suitable to be printed as Java source code. + * + *

Each annotation is followed by a space, to separate it from following Java code. + * + * @param annos the annotations to format + * @return all annotations in {@code annos}, each followed by a space, in a form suitable to be + * printed as Java source code + */ + private static String formatAnnotations(Collection annos) { + StringBuilder sb = new StringBuilder(); + formatAnnotations(sb, annos); + return sb.toString(); + } + + /** + * Prints all annotations in {@code annos} to {@code sb} in a form suitable to be printed as Java + * source code. + * + *

Each annotation is followed by a space, to separate it from following Java code. + * + * @param sb where to write the formatted annotations + * @param annos the annotations to format + */ + private static void formatAnnotations(StringBuilder sb, Collection annos) { + for (Annotation tla : annos) { + if (!isInternalJDKAnnotation(tla.def.name)) { + formatAnnotation(sb, tla); + sb.append(" "); + } } - - /** - * Returns the part of a binary name that specifies the package. - * - * @param className the binary name of a class - * @return the part of the name referring to the package, or null if there is no package name - */ - @SuppressWarnings("signature") // a valid non-empty package name is a dot separated identifier - private static @Nullable @DotSeparatedIdentifiers String packagePart( - @BinaryName String className) { - int lastdot = className.lastIndexOf('.'); - return (lastdot == -1) ? null : className.substring(0, lastdot); + } + + /** + * Formats the type of an array so that it is printable in Java source code, with the annotations + * from the scenelib representation added in appropriate places. The result includes a trailing + * space. + * + * @param sb where to format the array type to + * @param scenelibRep the array's scenelib type element + * @param javacRep the representation of the array's type used by javac + */ + private static void formatArrayType( + StringBuilder sb, ATypeElement scenelibRep, ArrayType javacRep) { + TypeMirror componentType = javacRep.getComponentType(); + ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep); + while (componentType.getKind() == TypeKind.ARRAY) { + componentType = ((ArrayType) componentType).getComponentType(); + scenelibComponent = getNextArrayLevel(scenelibComponent); } - - /** - * Returns the part of a binary name that specifies the basename of the class. - * - * @param className a binary name - * @return the part of the name representing the class's name without its package - */ - @SuppressWarnings( - "signature:return") // A binary name without its package is still a binary name - private static @BinaryName String basenamePart(@BinaryName String className) { - int lastdot = className.lastIndexOf('.'); - return className.substring(lastdot + 1); + formatType(sb, scenelibComponent, componentType); + formatArrayTypeImpl(sb, scenelibRep, javacRep); + } + + /** + * Formats the type of an array to be printable in Java source code, with the annotations from the + * scenelib representation added. This method formats only the "array" parts of an array type; it + * does not format (or attempt to format) the ultimate component type (that is, the non-array part + * of the array type). + * + * @param sb where to format the array type to + * @param scenelibRep the scene-lib representation + * @param javacRep the javac representation of the array type + */ + private static void formatArrayTypeImpl( + StringBuilder sb, ATypeElement scenelibRep, ArrayType javacRep) { + TypeMirror javacComponent = javacRep.getComponentType(); + ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep); + List explicitAnnos = javacRep.getAnnotationMirrors(); + for (AnnotationMirror explicitAnno : explicitAnnos) { + sb.append(explicitAnno.toString()); + sb.append(" "); } - - /** - * Returns the String representation of an annotation in Java source format. - * - * @param a the annotation to print - * @return the formatted annotation - */ - public static String formatAnnotation(Annotation a) { - StringBuilder sb = new StringBuilder(); - formatAnnotation(sb, a); - return sb.toString(); + if (explicitAnnos.isEmpty() && scenelibRep != null) { + formatAnnotations(sb, scenelibRep.tlAnnotationsHere); } - - /** - * Formats an annotation in Java source format. - * - * @param sb where to format the annotation to - * @param a the annotation to print - */ - public static void formatAnnotation(StringBuilder sb, Annotation a) { - String fullAnnoName = a.def().name; - String simpleAnnoName = fullAnnoName.substring(fullAnnoName.lastIndexOf('.') + 1); - sb.append("@"); - sb.append(simpleAnnoName); - if (a.fieldValues.isEmpty()) { - return; - } else { - sb.append("("); - if (a.fieldValues.size() == 1 && a.fieldValues.containsKey("value")) { - AnnotationFieldType aft = a.def().fieldTypes.get("value"); - aft.format(sb, a.fieldValues.get("value")); - } else { - // This simulates: new StringJoiner(", ", "@" + simpleAnnoName + "(", ")") - for (Map.Entry f : a.fieldValues.entrySet()) { - AnnotationFieldType aft = a.def().fieldTypes.get(f.getKey()); - sb.append(f.getKey()); - sb.append("="); - aft.format(sb, f.getValue()); - sb.append(", "); - } - sb.delete(sb.length() - 2, sb.length()); - } - sb.append(")"); - } + sb.append("[] "); + if (javacComponent.getKind() == TypeKind.ARRAY) { + formatArrayTypeImpl(sb, scenelibComponent, (ArrayType) javacComponent); } - - /** - * Returns all annotations in {@code annos} in a form suitable to be printed as Java source - * code. - * - *

Each annotation is followed by a space, to separate it from following Java code. - * - * @param annos the annotations to format - * @return all annotations in {@code annos}, each followed by a space, in a form suitable to be - * printed as Java source code - */ - private static String formatAnnotations(Collection annos) { - StringBuilder sb = new StringBuilder(); - formatAnnotations(sb, annos); - return sb.toString(); + } + + /** Static mutable variable to improve performance of getNextArrayLevel. */ + private static List location; + + /** + * Gets the outermost array level (or the component if not an array) from the given type element, + * or null if scene-lib is not storing any more information about this array (for example, when + * the component type is unannotated). + * + * @param e the array type element; can be null + * @return the next level of the array, if scene-lib stores information on it. null if the input + * is null or scene-lib is not storing more information. + */ + private static @Nullable ATypeElement getNextArrayLevel(@Nullable ATypeElement e) { + if (e == null) { + return null; } - /** - * Prints all annotations in {@code annos} to {@code sb} in a form suitable to be printed as - * Java source code. - * - *

Each annotation is followed by a space, to separate it from following Java code. - * - * @param sb where to write the formatted annotations - * @param annos the annotations to format - */ - private static void formatAnnotations( - StringBuilder sb, Collection annos) { - for (Annotation tla : annos) { - if (!isInternalJDKAnnotation(tla.def.name)) { - formatAnnotation(sb, tla); - sb.append(" "); - } - } + for (Map.Entry, ATypeElement> ite : e.innerTypes.entrySet()) { + location = ite.getKey(); + if (location.contains(TypePathEntry.ARRAY_ELEMENT)) { + return ite.getValue(); + } + } + return null; + } + + /** + * Formats a single formal parameter declaration. + * + * @param param the AField that represents the parameter + * @param parameterName the name of the parameter to display in the stub file. Stub files + * disregard formal parameter names, so this is aesthetic in almost all cases. The exception + * is the receiver parameter, whose name must be "this". + * @param basename the type name to use for the receiver parameter. Only used when the previous + * argument is exactly the String "this". + * @return the formatted formal parameter, as if it were written in Java source code + */ + private static String formatParameter(AField param, String parameterName, String basename) { + StringBuilder sb = new StringBuilder(); + formatParameter(sb, param, parameterName, basename); + return sb.toString(); + } + + /** + * Formats a single formal parameter declaration, as if it were written in Java source code. + * + * @param sb where to format the formal parameter to + * @param param the AField that represents the parameter + * @param parameterName the name of the parameter to display in the stub file. Stub files + * disregard formal parameter names, so this is aesthetic in almost all cases. The exception + * is the receiver parameter, whose name must be "this". + * @param basename the type name to use for the receiver parameter. Only used when the previous + * argument is exactly the String "this". + */ + private static void formatParameter( + StringBuilder sb, AField param, String parameterName, String basename) { + if (!param.tlAnnotationsHere.isEmpty()) { + for (Annotation declAnno : param.tlAnnotationsHere) { + formatAnnotation(sb, declAnno); + sb.append(" "); + } + sb.delete(sb.length() - 1, sb.length()); + } + formatAFieldImpl(sb, param, parameterName, basename); + } + + /** + * Formats a field declaration or formal parameter so that it can be printed in a stub. + * + *

This method does not add a trailing semicolon or comma. + * + *

Usually, {@link #formatParameter(AField, String, String)} should be called to format method + * parameters, and {@link #printField(AField, String, PrintWriter, String)} should be called to + * print field declarations. Both use this method as their underlying implementation. + * + * @param aField the field declaration or formal parameter declaration to format; should not + * represent a local variable + * @param fieldName the name to use for the declaration in the stub file. This doesn't matter for + * parameters (except the "this" receiver parameter), but must be correct for fields. + * @param className the simple name of the enclosing class. This is only used for printing the + * type of an explicit receiver parameter (i.e., a parameter named "this"). + * @return a String suitable to print in a stub file + */ + private static String formatAFieldImpl(AField aField, String fieldName, String className) { + StringBuilder sb = new StringBuilder(); + formatAFieldImpl(sb, aField, fieldName, className); + return sb.toString(); + } + + /** + * Formats a field declaration or formal parameter so that it can be printed in a stub file. + * + *

This method does not add a trailing semicolon or comma. + * + *

Usually, {@link #formatParameter(AField, String, String)} should be called to format method + * parameters, and {@link #printField(AField, String, PrintWriter, String)} should be called to + * print field declarations. Both use this method as their underlying implementation. + * + * @param sb where to write the formatted declaration to + * @param aField the field declaration or formal parameter declaration to format; should not + * represent a local variable + * @param fieldName the name to use for the declaration in the stub file. This doesn't matter for + * parameters (except the "this" receiver parameter), but must be correct for fields. + * @param className the simple name of the enclosing class. This is only used for printing the + * type of an explicit receiver parameter (i.e., a parameter named "this"). + */ + private static void formatAFieldImpl( + StringBuilder sb, AField aField, String fieldName, String className) { + if ("this".equals(fieldName)) { + formatType(sb, aField.type, null, className); + } else { + formatType(sb, aField.type, aField.getTypeMirror()); + } + sb.append(fieldName); + } + + /** + * Formats the given type for printing in Java source code. + * + * @param aType the scene-lib representation of the type, or null if only the unannotated type is + * to be printed + * @param javacType the javac representation of the type + * @return the type as it would appear in Java source code, followed by a trailing space + */ + private static String formatType(@Nullable ATypeElement aType, TypeMirror javacType) { + StringBuilder sb = new StringBuilder(); + formatType(sb, aType, javacType); + return sb.toString(); + } + + /** + * Formats the given type as it would appear in Java source code, followed by a trailing space. + * + * @param sb where to format the type to + * @param aType the scene-lib representation of the type, or null if only the unannotated type is + * to be printed + * @param javacType the javac representation of the type + */ + private static void formatType( + StringBuilder sb, @Nullable ATypeElement aType, TypeMirror javacType) { + // TypeMirror#toString prints multiple annotations on a single type + // separated by commas rather than by whitespace, as is required in source code. + String basetypeToPrint = javacType.toString().replaceAll(",@", " @"); + + // We must not print annotations in the default package that conflict with + // imported annotation names. + for (AnnotationMirror anm : javacType.getAnnotationMirrors()) { + String annotationName = AnnotationUtils.annotationName(anm); + String simpleName = annotationName.substring(annotationName.lastIndexOf('.') + 1); + // This checks if it is in the default package. + if (simpleName.equals(annotationName)) { + // In that case, do not print any annotations with the type, to + // avoid needing to parse an annotation string to remove it. + // TypeMirror does not provide any methods to remove annotations. + // This code relies on unannotated Java types not including spaces. + basetypeToPrint = basetypeToPrint.substring(basetypeToPrint.lastIndexOf(' ') + 1); + } + } + formatType(sb, aType, javacType, basetypeToPrint); + } + + /** + * Formats the given type as it would appear in Java source code, followed by a trailing space. + * + *

This overloaded version of this method exists only for receiver parameters, which are + * printed using the name of the class as {@code basetypeToPrint} instead of the javac type. The + * other version of this method should be preferred in every other case. + * + * @param sb where to formate the type to + * @param aType the scene-lib representation of the type, or null if only the unannotated type is + * to be printed + * @param javacType the javac representation of the type, or null if this is a receiver parameter + * @param basetypeToPrint the string representation of the type + */ + private static void formatType( + StringBuilder sb, + @Nullable ATypeElement aType, + @Nullable TypeMirror javacType, + String basetypeToPrint) { + // anonymous static classes shouldn't be printed with the "anonymous" tag that the AScene + // library uses + if (basetypeToPrint.startsWith(" 0) { + pos++; + if (basetypeToPrint.charAt(pos) == '<') { + openCount++; + } + if (basetypeToPrint.charAt(pos) == '>') { + openCount--; + } + } + basetypeToPrint = + basetypeToPrint.substring(0, basetypeToPrint.indexOf('<')) + + basetypeToPrint.substring(pos + 1); } - /** - * Formats the type of an array to be printable in Java source code, with the annotations from - * the scenelib representation added. This method formats only the "array" parts of an array - * type; it does not format (or attempt to format) the ultimate component type (that is, the - * non-array part of the array type). - * - * @param sb where to format the array type to - * @param scenelibRep the scene-lib representation - * @param javacRep the javac representation of the array type - */ - private static void formatArrayTypeImpl( - StringBuilder sb, ATypeElement scenelibRep, ArrayType javacRep) { - TypeMirror javacComponent = javacRep.getComponentType(); - ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep); - List explicitAnnos = javacRep.getAnnotationMirrors(); - for (AnnotationMirror explicitAnno : explicitAnnos) { - sb.append(explicitAnno.toString()); - sb.append(" "); - } - if (explicitAnnos.isEmpty() && scenelibRep != null) { - formatAnnotations(sb, scenelibRep.tlAnnotationsHere); - } - sb.append("[] "); - if (javacComponent.getKind() == TypeKind.ARRAY) { - formatArrayTypeImpl(sb, scenelibComponent, (ArrayType) javacComponent); - } + // An array is not a receiver, so using the javacType to check for arrays is safe. + if (javacType != null && javacType.getKind() == TypeKind.ARRAY) { + formatArrayType(sb, aType, (ArrayType) javacType); + return; } - /** Static mutable variable to improve performance of getNextArrayLevel. */ - private static List location; + if (aType != null) { + formatAnnotations(sb, aType.tlAnnotationsHere); + } + sb.append(basetypeToPrint); + sb.append(" "); + } - /** - * Gets the outermost array level (or the component if not an array) from the given type - * element, or null if scene-lib is not storing any more information about this array (for - * example, when the component type is unannotated). - * - * @param e the array type element; can be null - * @return the next level of the array, if scene-lib stores information on it. null if the input - * is null or scene-lib is not storing more information. - */ - private static @Nullable ATypeElement getNextArrayLevel(@Nullable ATypeElement e) { - if (e == null) { - return null; - } + /** Writes an import statement for each annotation used in an {@link AScene}. */ + private static class ImportDefWriter extends DefCollector { - for (Map.Entry, ATypeElement> ite : e.innerTypes.entrySet()) { - location = ite.getKey(); - if (location.contains(TypePathEntry.ARRAY_ELEMENT)) { - return ite.getValue(); - } - } - return null; - } + /** The writer onto which to write the import statements. */ + private final PrintWriter printWriter; /** - * Formats a single formal parameter declaration. + * Constructs a new ImportDefWriter, which will run on the given AScene when its {@code visit} + * method is called. * - * @param param the AField that represents the parameter - * @param parameterName the name of the parameter to display in the stub file. Stub files - * disregard formal parameter names, so this is aesthetic in almost all cases. The exception - * is the receiver parameter, whose name must be "this". - * @param basename the type name to use for the receiver parameter. Only used when the previous - * argument is exactly the String "this". - * @return the formatted formal parameter, as if it were written in Java source code + * @param scene the scene whose imported annotations should be printed + * @param printWriter the writer onto which to write the import statements + * @throws DefException if the DefCollector does not succeed */ - private static String formatParameter(AField param, String parameterName, String basename) { - StringBuilder sb = new StringBuilder(); - formatParameter(sb, param, parameterName, basename); - return sb.toString(); + ImportDefWriter(ASceneWrapper scene, PrintWriter printWriter) throws DefException { + super(scene.getAScene()); + this.printWriter = printWriter; } /** - * Formats a single formal parameter declaration, as if it were written in Java source code. + * Write an import statement for a given AnnotationDef. This is only called once per annotation + * used in the scene. * - * @param sb where to format the formal parameter to - * @param param the AField that represents the parameter - * @param parameterName the name of the parameter to display in the stub file. Stub files - * disregard formal parameter names, so this is aesthetic in almost all cases. The exception - * is the receiver parameter, whose name must be "this". - * @param basename the type name to use for the receiver parameter. Only used when the previous - * argument is exactly the String "this". + * @param d the annotation definition to print an import for */ - private static void formatParameter( - StringBuilder sb, AField param, String parameterName, String basename) { - if (!param.tlAnnotationsHere.isEmpty()) { - for (Annotation declAnno : param.tlAnnotationsHere) { - formatAnnotation(sb, declAnno); - sb.append(" "); - } - sb.delete(sb.length() - 1, sb.length()); - } - formatAFieldImpl(sb, param, parameterName, basename); + @Override + protected void visitAnnotationDef(AnnotationDef d) { + if (!isInternalJDKAnnotation(d.name)) { + printWriter.println("import " + d.name + ";"); + } } - - /** - * Formats a field declaration or formal parameter so that it can be printed in a stub. - * - *

This method does not add a trailing semicolon or comma. - * - *

Usually, {@link #formatParameter(AField, String, String)} should be called to format - * method parameters, and {@link #printField(AField, String, PrintWriter, String)} should be - * called to print field declarations. Both use this method as their underlying implementation. - * - * @param aField the field declaration or formal parameter declaration to format; should not - * represent a local variable - * @param fieldName the name to use for the declaration in the stub file. This doesn't matter - * for parameters (except the "this" receiver parameter), but must be correct for fields. - * @param className the simple name of the enclosing class. This is only used for printing the - * type of an explicit receiver parameter (i.e., a parameter named "this"). - * @return a String suitable to print in a stub file - */ - private static String formatAFieldImpl(AField aField, String fieldName, String className) { - StringBuilder sb = new StringBuilder(); - formatAFieldImpl(sb, aField, fieldName, className); - return sb.toString(); + } + + /** + * Return true if the given annotation is an internal JDK annotation, whose name includes '+'. + * + * @param annotationName the name of the annotation + * @return true iff this is an internal JDK annotation + */ + private static boolean isInternalJDKAnnotation(String annotationName) { + return annotationName.contains("+"); + } + + /** + * Print the hierarchy of outer classes up to and including the given class, and return the number + * of curly braces to close with. The classes are printed with appropriate opening curly braces, + * in standard Java style. + * + *

In an AScene, an inner class name is a binary name like "Outer$Inner". In a stub file, inner + * classes must be nested, as in Java source code. + * + * @param basename the binary name of the class without the package part + * @param aClass the AClass for {@code basename} + * @param printWriter the writer where the class definition should be printed + * @param checker the type-checker whose annotations are being written + * @return the number of outer classes within which this class is nested + */ + private static int printClassDefinitions( + String basename, AClass aClass, PrintWriter printWriter, BaseTypeChecker checker) { + String[] classNames = basename.split("\\$"); + TypeElement innermostTypeElt = aClass.getTypeElement(); + if (innermostTypeElt == null) { + throw new BugInCF("typeElement was unexpectedly null in this aClass: " + aClass); } - - /** - * Formats a field declaration or formal parameter so that it can be printed in a stub file. - * - *

This method does not add a trailing semicolon or comma. - * - *

Usually, {@link #formatParameter(AField, String, String)} should be called to format - * method parameters, and {@link #printField(AField, String, PrintWriter, String)} should be - * called to print field declarations. Both use this method as their underlying implementation. - * - * @param sb where to write the formatted declaration to - * @param aField the field declaration or formal parameter declaration to format; should not - * represent a local variable - * @param fieldName the name to use for the declaration in the stub file. This doesn't matter - * for parameters (except the "this" receiver parameter), but must be correct for fields. - * @param className the simple name of the enclosing class. This is only used for printing the - * type of an explicit receiver parameter (i.e., a parameter named "this"). - */ - private static void formatAFieldImpl( - StringBuilder sb, AField aField, String fieldName, String className) { - if ("this".equals(fieldName)) { - formatType(sb, aField.type, null, className); - } else { - formatType(sb, aField.type, aField.getTypeMirror()); - } - sb.append(fieldName); + TypeElement[] typeElements = getTypeElementsForClasses(innermostTypeElt, classNames); + + for (int i = 0; i < classNames.length; i++) { + String nameToPrint = classNames[i]; + if (i == classNames.length - 1) { + printWriter.print(indents(i)); + printWriter.println("@AnnotatedFor(\"" + checker.getClass().getCanonicalName() + "\")"); + } + printWriter.print(indents(i)); + if (i == classNames.length - 1) { + // Only print class annotations on the innermost class, which corresponds to aClass. + // If there should be class annotations on another class, it will have its own stub + // file, which will eventually be merged with this one. + printWriter.print(formatAnnotations(aClass.getAnnotations())); + } + if (aClass.isAnnotation(nameToPrint)) { + printWriter.print("@interface "); + } else if (aClass.isEnum(nameToPrint)) { + printWriter.print("enum "); + } else if (aClass.isInterface(nameToPrint)) { + printWriter.print("interface "); + } else if (aClass.isRecord(nameToPrint)) { + printWriter.print("record "); + } else { + printWriter.print("class "); + } + printWriter.print(nameToPrint); + printTypeParameters(typeElements[i], printWriter); + printWriter.println(" {"); + if (aClass.isEnum(nameToPrint) && i != classNames.length - 1) { + // Print a blank set of enum constants if this is an outer enum. + printWriter.println(indents(i + 1) + "/* omitted enum constants */ ;"); + } + printWriter.println(); } - - /** - * Formats the given type for printing in Java source code. - * - * @param aType the scene-lib representation of the type, or null if only the unannotated type - * is to be printed - * @param javacType the javac representation of the type - * @return the type as it would appear in Java source code, followed by a trailing space - */ - private static String formatType(@Nullable ATypeElement aType, TypeMirror javacType) { - StringBuilder sb = new StringBuilder(); - formatType(sb, aType, javacType); - return sb.toString(); + return classNames.length; + } + + /** + * Constructs an array of TypeElements corresponding to the list of classes. + * + * @param innermostTypeElt the innermost type element: either an inner class or an outer class + * without any inner classes that should be printed + * @param classNames the names of the containing classes, from outer to inner + * @return an array of TypeElements whose entry at a given index represents the type named at that + * index in {@code classNames} + */ + private static TypeElement @SameLen("#2") [] getTypeElementsForClasses( + TypeElement innermostTypeElt, String @MinLen(1) [] classNames) { + TypeElement[] result = new TypeElement[classNames.length]; + result[classNames.length - 1] = innermostTypeElt; + Element elt = innermostTypeElt; + for (int i = classNames.length - 2; i >= 0; i--) { + elt = elt.getEnclosingElement(); + result[i] = (TypeElement) elt; } - - /** - * Formats the given type as it would appear in Java source code, followed by a trailing space. - * - * @param sb where to format the type to - * @param aType the scene-lib representation of the type, or null if only the unannotated type - * is to be printed - * @param javacType the javac representation of the type - */ - private static void formatType( - StringBuilder sb, @Nullable ATypeElement aType, TypeMirror javacType) { - // TypeMirror#toString prints multiple annotations on a single type - // separated by commas rather than by whitespace, as is required in source code. - String basetypeToPrint = javacType.toString().replaceAll(",@", " @"); - - // We must not print annotations in the default package that conflict with - // imported annotation names. - for (AnnotationMirror anm : javacType.getAnnotationMirrors()) { - String annotationName = AnnotationUtils.annotationName(anm); - String simpleName = annotationName.substring(annotationName.lastIndexOf('.') + 1); - // This checks if it is in the default package. - if (simpleName.equals(annotationName)) { - // In that case, do not print any annotations with the type, to - // avoid needing to parse an annotation string to remove it. - // TypeMirror does not provide any methods to remove annotations. - // This code relies on unannotated Java types not including spaces. - basetypeToPrint = basetypeToPrint.substring(basetypeToPrint.lastIndexOf(' ') + 1); - } - } - formatType(sb, aType, javacType, basetypeToPrint); + return result; + } + + /** + * Prints all the fields of a given class. + * + * @param aClass the class whose fields should be printed + * @param printWriter the writer on which to print the fields + * @param indentLevel the indent string + */ + private static void printFields(AClass aClass, PrintWriter printWriter, String indentLevel) { + + if (aClass.getFields().isEmpty()) { + return; } - /** - * Formats the given type as it would appear in Java source code, followed by a trailing space. - * - *

This overloaded version of this method exists only for receiver parameters, which are - * printed using the name of the class as {@code basetypeToPrint} instead of the javac type. The - * other version of this method should be preferred in every other case. - * - * @param sb where to formate the type to - * @param aType the scene-lib representation of the type, or null if only the unannotated type - * is to be printed - * @param javacType the javac representation of the type, or null if this is a receiver - * parameter - * @param basetypeToPrint the string representation of the type - */ - private static void formatType( - StringBuilder sb, - @Nullable ATypeElement aType, - @Nullable TypeMirror javacType, - String basetypeToPrint) { - // anonymous static classes shouldn't be printed with the "anonymous" tag that the AScene - // library uses - if (basetypeToPrint.startsWith(" 0) { - pos++; - if (basetypeToPrint.charAt(pos) == '<') { - openCount++; - } - if (basetypeToPrint.charAt(pos) == '>') { - openCount--; - } - } - basetypeToPrint = - basetypeToPrint.substring(0, basetypeToPrint.indexOf('<')) - + basetypeToPrint.substring(pos + 1); - } - - // An array is not a receiver, so using the javacType to check for arrays is safe. - if (javacType != null && javacType.getKind() == TypeKind.ARRAY) { - formatArrayType(sb, aType, (ArrayType) javacType); - return; - } - - if (aType != null) { - formatAnnotations(sb, aType.tlAnnotationsHere); - } - sb.append(basetypeToPrint); - sb.append(" "); + printWriter.println(indentLevel + "// fields:"); + printWriter.println(); + for (Map.Entry fieldEntry : aClass.getFields().entrySet()) { + String fieldName = fieldEntry.getKey(); + AField aField = fieldEntry.getValue(); + printField(aField, fieldName, printWriter, indentLevel); } - - /** Writes an import statement for each annotation used in an {@link AScene}. */ - private static class ImportDefWriter extends DefCollector { - - /** The writer onto which to write the import statements. */ - private final PrintWriter printWriter; - - /** - * Constructs a new ImportDefWriter, which will run on the given AScene when its {@code - * visit} method is called. - * - * @param scene the scene whose imported annotations should be printed - * @param printWriter the writer onto which to write the import statements - * @throws DefException if the DefCollector does not succeed - */ - ImportDefWriter(ASceneWrapper scene, PrintWriter printWriter) throws DefException { - super(scene.getAScene()); - this.printWriter = printWriter; - } - - /** - * Write an import statement for a given AnnotationDef. This is only called once per - * annotation used in the scene. - * - * @param d the annotation definition to print an import for - */ - @Override - protected void visitAnnotationDef(AnnotationDef d) { - if (!isInternalJDKAnnotation(d.name)) { - printWriter.println("import " + d.name + ";"); - } - } + } + + /** + * Prints a field declaration, including a trailing semicolon and a newline. + * + * @param aField the field declaration + * @param fieldName the name of the field + * @param printWriter the writer on which to print + * @param indentLevel the indent string + */ + private static void printField( + AField aField, String fieldName, PrintWriter printWriter, String indentLevel) { + if (aField.getTypeMirror() == null) { + // aField has no type mirror, so there are no inferred annotations and the field need + // not be printed. + return; } - /** - * Return true if the given annotation is an internal JDK annotation, whose name includes '+'. - * - * @param annotationName the name of the annotation - * @return true iff this is an internal JDK annotation - */ - private static boolean isInternalJDKAnnotation(String annotationName) { - return annotationName.contains("+"); + for (Annotation declAnno : aField.tlAnnotationsHere) { + printWriter.print(indentLevel); + printWriter.println(formatAnnotation(declAnno)); } - /** - * Print the hierarchy of outer classes up to and including the given class, and return the - * number of curly braces to close with. The classes are printed with appropriate opening curly - * braces, in standard Java style. - * - *

In an AScene, an inner class name is a binary name like "Outer$Inner". In a stub file, - * inner classes must be nested, as in Java source code. - * - * @param basename the binary name of the class without the package part - * @param aClass the AClass for {@code basename} - * @param printWriter the writer where the class definition should be printed - * @param checker the type-checker whose annotations are being written - * @return the number of outer classes within which this class is nested - */ - private static int printClassDefinitions( - String basename, AClass aClass, PrintWriter printWriter, BaseTypeChecker checker) { - String[] classNames = basename.split("\\$"); - TypeElement innermostTypeElt = aClass.getTypeElement(); - if (innermostTypeElt == null) { - throw new BugInCF("typeElement was unexpectedly null in this aClass: " + aClass); - } - TypeElement[] typeElements = getTypeElementsForClasses(innermostTypeElt, classNames); - - for (int i = 0; i < classNames.length; i++) { - String nameToPrint = classNames[i]; - if (i == classNames.length - 1) { - printWriter.print(indents(i)); - printWriter.println( - "@AnnotatedFor(\"" + checker.getClass().getCanonicalName() + "\")"); - } - printWriter.print(indents(i)); - if (i == classNames.length - 1) { - // Only print class annotations on the innermost class, which corresponds to aClass. - // If there should be class annotations on another class, it will have its own stub - // file, which will eventually be merged with this one. - printWriter.print(formatAnnotations(aClass.getAnnotations())); - } - if (aClass.isAnnotation(nameToPrint)) { - printWriter.print("@interface "); - } else if (aClass.isEnum(nameToPrint)) { - printWriter.print("enum "); - } else if (aClass.isInterface(nameToPrint)) { - printWriter.print("interface "); - } else if (aClass.isRecord(nameToPrint)) { - printWriter.print("record "); - } else { - printWriter.print("class "); - } - printWriter.print(nameToPrint); - printTypeParameters(typeElements[i], printWriter); - printWriter.println(" {"); - if (aClass.isEnum(nameToPrint) && i != classNames.length - 1) { - // Print a blank set of enum constants if this is an outer enum. - printWriter.println(indents(i + 1) + "/* omitted enum constants */ ;"); - } - printWriter.println(); - } - return classNames.length; + printWriter.print(indentLevel); + printWriter.print(formatAFieldImpl(aField, fieldName, /*enclosing class=*/ null)); + printWriter.println(";"); + printWriter.println(); + } + + /** + * Prints a method declaration in stub file format (i.e., without a method body). + * + * @param aMethod the method to print + * @param simplename the simple name of the enclosing class, for receiver parameters and + * constructor names + * @param printWriter where to print the method signature + * @param atf the type factory, for computing preconditions and postconditions + * @param indentLevel the indent string + */ + private static void printMethodDeclaration( + AMethod aMethod, + String simplename, + PrintWriter printWriter, + String indentLevel, + GenericAnnotatedTypeFactory atf) { + + if (aMethod.getTypeParameters() == null) { + // aMethod.setFieldsFromMethodElement has not been called + return; } - /** - * Constructs an array of TypeElements corresponding to the list of classes. - * - * @param innermostTypeElt the innermost type element: either an inner class or an outer class - * without any inner classes that should be printed - * @param classNames the names of the containing classes, from outer to inner - * @return an array of TypeElements whose entry at a given index represents the type named at - * that index in {@code classNames} - */ - private static TypeElement @SameLen("#2") [] getTypeElementsForClasses( - TypeElement innermostTypeElt, String @MinLen(1) [] classNames) { - TypeElement[] result = new TypeElement[classNames.length]; - result[classNames.length - 1] = innermostTypeElt; - Element elt = innermostTypeElt; - for (int i = classNames.length - 2; i >= 0; i--) { - elt = elt.getEnclosingElement(); - result[i] = (TypeElement) elt; - } - return result; + for (Annotation declAnno : aMethod.tlAnnotationsHere) { + printWriter.print(indentLevel); + printWriter.println(formatAnnotation(declAnno)); } - /** - * Prints all the fields of a given class. - * - * @param aClass the class whose fields should be printed - * @param printWriter the writer on which to print the fields - * @param indentLevel the indent string - */ - private static void printFields(AClass aClass, PrintWriter printWriter, String indentLevel) { - - if (aClass.getFields().isEmpty()) { - return; - } - - printWriter.println(indentLevel + "// fields:"); - printWriter.println(); - for (Map.Entry fieldEntry : aClass.getFields().entrySet()) { - String fieldName = fieldEntry.getKey(); - AField aField = fieldEntry.getValue(); - printField(aField, fieldName, printWriter, indentLevel); - } + for (AnnotationMirror contractAnno : atf.getContractAnnotations(aMethod)) { + printWriter.print(indentLevel); + printWriter.println(contractAnno); } - /** - * Prints a field declaration, including a trailing semicolon and a newline. - * - * @param aField the field declaration - * @param fieldName the name of the field - * @param printWriter the writer on which to print - * @param indentLevel the indent string - */ - private static void printField( - AField aField, String fieldName, PrintWriter printWriter, String indentLevel) { - if (aField.getTypeMirror() == null) { - // aField has no type mirror, so there are no inferred annotations and the field need - // not be printed. - return; - } + printWriter.print(indentLevel); - for (Annotation declAnno : aField.tlAnnotationsHere) { - printWriter.print(indentLevel); - printWriter.println(formatAnnotation(declAnno)); - } + printTypeParameters(aMethod.getTypeParameters(), printWriter); - printWriter.print(indentLevel); - printWriter.print(formatAFieldImpl(aField, fieldName, /*enclosing class=*/ null)); - printWriter.println(";"); - printWriter.println(); + String methodName = aMethod.getMethodName(); + // Use Java syntax for constructors. + if ("".equals(methodName)) { + // Set methodName, but don't output a return type. + methodName = simplename; + } else { + printWriter.print(formatType(aMethod.returnType, aMethod.getReturnTypeMirror())); } + printWriter.print(methodName); + printWriter.print("("); - /** - * Prints a method declaration in stub file format (i.e., without a method body). - * - * @param aMethod the method to print - * @param simplename the simple name of the enclosing class, for receiver parameters and - * constructor names - * @param printWriter where to print the method signature - * @param atf the type factory, for computing preconditions and postconditions - * @param indentLevel the indent string - */ - private static void printMethodDeclaration( - AMethod aMethod, - String simplename, - PrintWriter printWriter, - String indentLevel, - GenericAnnotatedTypeFactory atf) { - - if (aMethod.getTypeParameters() == null) { - // aMethod.setFieldsFromMethodElement has not been called - return; - } - - for (Annotation declAnno : aMethod.tlAnnotationsHere) { - printWriter.print(indentLevel); - printWriter.println(formatAnnotation(declAnno)); - } - - for (AnnotationMirror contractAnno : atf.getContractAnnotations(aMethod)) { - printWriter.print(indentLevel); - printWriter.println(contractAnno); - } - - printWriter.print(indentLevel); - - printTypeParameters(aMethod.getTypeParameters(), printWriter); - - String methodName = aMethod.getMethodName(); - // Use Java syntax for constructors. - if ("".equals(methodName)) { - // Set methodName, but don't output a return type. - methodName = simplename; - } else { - printWriter.print(formatType(aMethod.returnType, aMethod.getReturnTypeMirror())); - } - printWriter.print(methodName); - printWriter.print("("); - - StringJoiner parameters = new StringJoiner(", "); - if (!aMethod.receiver.type.tlAnnotationsHere.isEmpty()) { - // Only output the receiver if it has an annotation. - parameters.add(formatParameter(aMethod.receiver, "this", simplename)); - } - for (Integer index : aMethod.getParameters().keySet()) { - AField param = aMethod.getParameters().get(index); - parameters.add(formatParameter(param, param.getName(), simplename)); - } - printWriter.print(parameters.toString()); - printWriter.println(");"); - printWriter.println(); + StringJoiner parameters = new StringJoiner(", "); + if (!aMethod.receiver.type.tlAnnotationsHere.isEmpty()) { + // Only output the receiver if it has an annotation. + parameters.add(formatParameter(aMethod.receiver, "this", simplename)); } - - /** - * The implementation of {@link #write}. Prints imports, classes, method signatures, and fields - * in stub file format, all with appropriate annotations. - * - * @param scene the scene to write - * @param filename the name of the file to write (must end in .astub) - * @param checker the checker, for computing preconditions - */ - private static void writeImpl(ASceneWrapper scene, String filename, BaseTypeChecker checker) { - // Sort by package name first so that output is deterministic and default package - // comes first; within package sort by class name. - @SuppressWarnings("signature") // scene-lib bytecode lacks signature annotations - List<@BinaryName String> classes = new ArrayList<>(scene.getAScene().getClasses().keySet()); - Collections.sort( - classes, - (o1, o2) -> - ComparisonChain.start() - .compare( - packagePart(o1), - packagePart(o2), - Comparator.nullsFirst(Comparator.naturalOrder())) - .compare(basenamePart(o1), basenamePart(o2)) - .result()); - - boolean anyClassPrintable = false; - - // The writer is not initialized until it is certain that at - // least one class can be written, to avoid empty stub files. - // An alternate approach would be to delete the file after it is closed, if the file is - // empty. - // It's not worth rewriting this code, since .stub files are obsolescent. - - FileWriter fileWriter = null; - PrintWriter printWriter = null; - try { - - // For each class - for (String clazz : classes) { - if (isPrintable(clazz, scene.getAScene().getClasses().get(clazz))) { - if (!anyClassPrintable) { - try { - if (fileWriter != null || printWriter != null) { - throw new Error("This can't happen"); - } - fileWriter = new FileWriter(filename); - printWriter = new PrintWriter(fileWriter); - } catch (IOException e) { - throw new BugInCF("error writing file during WPI: " + filename); - } - - // Write out all imports - ImportDefWriter importDefWriter; - try { - importDefWriter = new ImportDefWriter(scene, printWriter); - } catch (DefException e) { - throw new BugInCF(e); - } - importDefWriter.visit(); - printWriter.println( - "import org.checkerframework.framework.qual.AnnotatedFor;"); - printWriter.println(); - anyClassPrintable = true; - } - printClass( - clazz, scene.getAScene().getClasses().get(clazz), checker, printWriter); - } - } - } finally { - if (printWriter != null) { - printWriter.close(); // does not throw IOException - } + for (Integer index : aMethod.getParameters().keySet()) { + AField param = aMethod.getParameters().get(index); + parameters.add(formatParameter(param, param.getName(), simplename)); + } + printWriter.print(parameters.toString()); + printWriter.println(");"); + printWriter.println(); + } + + /** + * The implementation of {@link #write}. Prints imports, classes, method signatures, and fields in + * stub file format, all with appropriate annotations. + * + * @param scene the scene to write + * @param filename the name of the file to write (must end in .astub) + * @param checker the checker, for computing preconditions + */ + private static void writeImpl(ASceneWrapper scene, String filename, BaseTypeChecker checker) { + // Sort by package name first so that output is deterministic and default package + // comes first; within package sort by class name. + @SuppressWarnings("signature") // scene-lib bytecode lacks signature annotations + List<@BinaryName String> classes = new ArrayList<>(scene.getAScene().getClasses().keySet()); + Collections.sort( + classes, + (o1, o2) -> + ComparisonChain.start() + .compare( + packagePart(o1), + packagePart(o2), + Comparator.nullsFirst(Comparator.naturalOrder())) + .compare(basenamePart(o1), basenamePart(o2)) + .result()); + + boolean anyClassPrintable = false; + + // The writer is not initialized until it is certain that at + // least one class can be written, to avoid empty stub files. + // An alternate approach would be to delete the file after it is closed, if the file is + // empty. + // It's not worth rewriting this code, since .stub files are obsolescent. + + FileWriter fileWriter = null; + PrintWriter printWriter = null; + try { + + // For each class + for (String clazz : classes) { + if (isPrintable(clazz, scene.getAScene().getClasses().get(clazz))) { + if (!anyClassPrintable) { try { - if (fileWriter != null) { - fileWriter.close(); - } + if (fileWriter != null || printWriter != null) { + throw new Error("This can't happen"); + } + fileWriter = new FileWriter(filename); + printWriter = new PrintWriter(fileWriter); } catch (IOException e) { - // Nothing to do since exceptions thrown from a finally block have no effect. + throw new BugInCF("error writing file during WPI: " + filename); } - } - } - - /** - * Returns true if the class is printable in a stub file. A printable class is a class or enum - * (not a package or module) and is not anonymous. - * - * @param classname the class name - * @param aClass the representation of the class - * @return whether the class is printable, by the definition above - */ - private static boolean isPrintable(@BinaryName String classname, AClass aClass) { - String basename = basenamePart(classname); - - if ("package-info".equals(basename) || "module-info".equals(basename)) { - return false; - } - // Do not attempt to print stubs for anonymous inner classes, local classes, or their inner - // classes, because the stub parser cannot read them. - if (anonymousInnerClassOrLocalClassPattern.matcher(basename).find()) { - return false; - } - - if (aClass.getTypeElement() == null) { - throw new BugInCF( - "Tried printing an unprintable class to a stub file during WPI: " - + aClass.className); - } - - return true; + // Write out all imports + ImportDefWriter importDefWriter; + try { + importDefWriter = new ImportDefWriter(scene, printWriter); + } catch (DefException e) { + throw new BugInCF(e); + } + importDefWriter.visit(); + printWriter.println("import org.checkerframework.framework.qual.AnnotatedFor;"); + printWriter.println(); + anyClassPrintable = true; + } + printClass(clazz, scene.getAScene().getClasses().get(clazz), checker, printWriter); + } + } + } finally { + if (printWriter != null) { + printWriter.close(); // does not throw IOException + } + try { + if (fileWriter != null) { + fileWriter.close(); + } + } catch (IOException e) { + // Nothing to do since exceptions thrown from a finally block have no effect. + } + } + } + + /** + * Returns true if the class is printable in a stub file. A printable class is a class or enum + * (not a package or module) and is not anonymous. + * + * @param classname the class name + * @param aClass the representation of the class + * @return whether the class is printable, by the definition above + */ + private static boolean isPrintable(@BinaryName String classname, AClass aClass) { + String basename = basenamePart(classname); + + if ("package-info".equals(basename) || "module-info".equals(basename)) { + return false; } - /** - * Print the class body, or nothing if this is an anonymous inner class. Call {@link - * #isPrintable(String, AClass)} and check that it returns true before calling this method. - * - * @param classname the class name - * @param aClass the representation of the class - * @param checker the checker, for computing preconditions - * @param printWriter the writer on which to print - */ - private static void printClass( - @BinaryName String classname, - AClass aClass, - BaseTypeChecker checker, - PrintWriter printWriter) { - - String basename = basenamePart(classname); - String innermostClassname = - basename.contains("$") - ? basename.substring(basename.lastIndexOf('$') + 1) - : basename; - String pkg = packagePart(classname); - - if (pkg != null) { - printWriter.println("package " + pkg + ";"); - } + // Do not attempt to print stubs for anonymous inner classes, local classes, or their inner + // classes, because the stub parser cannot read them. + if (anonymousInnerClassOrLocalClassPattern.matcher(basename).find()) { + return false; + } - int curlyCount = printClassDefinitions(basename, aClass, printWriter, checker); + if (aClass.getTypeElement() == null) { + throw new BugInCF( + "Tried printing an unprintable class to a stub file during WPI: " + aClass.className); + } - String indentLevel = indents(curlyCount); + return true; + } + + /** + * Print the class body, or nothing if this is an anonymous inner class. Call {@link + * #isPrintable(String, AClass)} and check that it returns true before calling this method. + * + * @param classname the class name + * @param aClass the representation of the class + * @param checker the checker, for computing preconditions + * @param printWriter the writer on which to print + */ + private static void printClass( + @BinaryName String classname, + AClass aClass, + BaseTypeChecker checker, + PrintWriter printWriter) { + + String basename = basenamePart(classname); + String innermostClassname = + basename.contains("$") ? basename.substring(basename.lastIndexOf('$') + 1) : basename; + String pkg = packagePart(classname); + + if (pkg != null) { + printWriter.println("package " + pkg + ";"); + } - List enumConstants = aClass.getEnumConstants(); - if (enumConstants != null) { - StringJoiner sj = new StringJoiner(", "); - for (VariableElement enumConstant : enumConstants) { - sj.add(enumConstant.getSimpleName()); - } + int curlyCount = printClassDefinitions(basename, aClass, printWriter, checker); - printWriter.println(indentLevel + "// enum constants:"); - printWriter.println(); - printWriter.println(indentLevel + sj.toString() + ";"); - printWriter.println(); - } + String indentLevel = indents(curlyCount); - printFields(aClass, printWriter, indentLevel); + List enumConstants = aClass.getEnumConstants(); + if (enumConstants != null) { + StringJoiner sj = new StringJoiner(", "); + for (VariableElement enumConstant : enumConstants) { + sj.add(enumConstant.getSimpleName()); + } - if (!aClass.getMethods().isEmpty()) { - // print method signatures - printWriter.println(indentLevel + "// methods:"); - printWriter.println(); - for (Map.Entry methodEntry : aClass.getMethods().entrySet()) { - printMethodDeclaration( - methodEntry.getValue(), - innermostClassname, - printWriter, - indentLevel, - checker.getTypeFactory()); - } - } - for (int i = 0; i < curlyCount; i++) { - printWriter.println(indents(curlyCount - i - 1) + "}"); - } + printWriter.println(indentLevel + "// enum constants:"); + printWriter.println(); + printWriter.println(indentLevel + sj.toString() + ";"); + printWriter.println(); } - /** - * Return a string containing n indents. - * - * @param n the number of indents - * @return a string containing that many indents - */ - private static String indents(int n) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < n; i++) { - sb.append(INDENT); - } - return sb.toString(); + printFields(aClass, printWriter, indentLevel); + + if (!aClass.getMethods().isEmpty()) { + // print method signatures + printWriter.println(indentLevel + "// methods:"); + printWriter.println(); + for (Map.Entry methodEntry : aClass.getMethods().entrySet()) { + printMethodDeclaration( + methodEntry.getValue(), + innermostClassname, + printWriter, + indentLevel, + checker.getTypeFactory()); + } } - - /** - * Prints the type parameters of the given class, enclosed in {@code <...>}. - * - * @param type the TypeElement representing the class whose type parameters should be printed - * @param printWriter where to print the type parameters - */ - private static void printTypeParameters(TypeElement type, PrintWriter printWriter) { - List typeParameters = type.getTypeParameters(); - printTypeParameters(typeParameters, printWriter); + for (int i = 0; i < curlyCount; i++) { + printWriter.println(indents(curlyCount - i - 1) + "}"); } - - /** - * Prints the given type parameters. - * - * @param typeParameters the type element to print - * @param printWriter where to print the type parameters - */ - private static void printTypeParameters( - List typeParameters, PrintWriter printWriter) { - if (typeParameters.isEmpty()) { - return; - } - StringJoiner sj = new StringJoiner(", ", "<", ">"); - for (TypeParameterElement t : typeParameters) { - sj.add(t.getSimpleName().toString()); - } - printWriter.print(sj.toString()); + } + + /** + * Return a string containing n indents. + * + * @param n the number of indents + * @return a string containing that many indents + */ + private static String indents(int n) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) { + sb.append(INDENT); + } + return sb.toString(); + } + + /** + * Prints the type parameters of the given class, enclosed in {@code <...>}. + * + * @param type the TypeElement representing the class whose type parameters should be printed + * @param printWriter where to print the type parameters + */ + private static void printTypeParameters(TypeElement type, PrintWriter printWriter) { + List typeParameters = type.getTypeParameters(); + printTypeParameters(typeParameters, printWriter); + } + + /** + * Prints the given type parameters. + * + * @param typeParameters the type element to print + * @param printWriter where to print the type parameters + */ + private static void printTypeParameters( + List typeParameters, PrintWriter printWriter) { + if (typeParameters.isEmpty()) { + return; + } + StringJoiner sj = new StringJoiner(", ", "<", ">"); + for (TypeParameterElement t : typeParameters) { + sj.add(t.getSimpleName().toString()); } + printWriter.print(sj.toString()); + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java index 66b65bec6d8..c5eaf1840ae 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java @@ -4,7 +4,12 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol.ClassSymbol; - +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.analysis.Analysis; @@ -19,14 +24,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import java.util.Map; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; - /** * Interface for recording facts at (pseudo-)assignments. It is used by the -Ainfer command-line * argument. The -Ainfer command-line argument is used by the whole-program-inference loop, but this @@ -41,249 +38,244 @@ */ public interface WholeProgramInference { - /** - * Updates the parameter types of the constructor {@code constructorElt} based on the arguments - * in {@code objectCreationNode}. - * - *

For each parameter in constructorElt: - * - *

    - *
  • If there is no stored annotated type for that parameter, then use the type of the - * corresponding argument in the object creation call objectCreationNode. - *
  • If there was a stored annotated type for that parameter, then its new type will be the - * LUB between the previous type and the type of the corresponding argument in the object - * creation call. - *
- * - * @param objectCreationNode the Node that invokes the constructor - * @param constructorElt the Element of the constructor - * @param store the store just before the call - */ - void updateFromObjectCreation( - ObjectCreationNode objectCreationNode, - ExecutableElement constructorElt, - CFAbstractStore store); + /** + * Updates the parameter types of the constructor {@code constructorElt} based on the arguments in + * {@code objectCreationNode}. + * + *

For each parameter in constructorElt: + * + *

    + *
  • If there is no stored annotated type for that parameter, then use the type of the + * corresponding argument in the object creation call objectCreationNode. + *
  • If there was a stored annotated type for that parameter, then its new type will be the + * LUB between the previous type and the type of the corresponding argument in the object + * creation call. + *
+ * + * @param objectCreationNode the Node that invokes the constructor + * @param constructorElt the Element of the constructor + * @param store the store just before the call + */ + void updateFromObjectCreation( + ObjectCreationNode objectCreationNode, + ExecutableElement constructorElt, + CFAbstractStore store); - /** - * Updates the parameter types of the method {@code methodElt} based on the arguments in the - * method invocation {@code methodInvNode}. - * - *

For each formal parameter in methodElt (including the receiver): - * - *

    - *
  • If there is no stored annotated type for that parameter, then use the type of the - * corresponding argument in the method call methodInvNode. - *
  • If there was a stored annotated type for that parameter, then its new type will be the - * LUB between the previous type and the type of the corresponding argument in the method - * call. - *
- * - * @param methodInvNode the node representing a method invocation - * @param methodElt the element of the method being invoked - * @param store the store before the method call, used for inferring method preconditions - */ - void updateFromMethodInvocation( - MethodInvocationNode methodInvNode, - ExecutableElement methodElt, - CFAbstractStore store); + /** + * Updates the parameter types of the method {@code methodElt} based on the arguments in the + * method invocation {@code methodInvNode}. + * + *

For each formal parameter in methodElt (including the receiver): + * + *

    + *
  • If there is no stored annotated type for that parameter, then use the type of the + * corresponding argument in the method call methodInvNode. + *
  • If there was a stored annotated type for that parameter, then its new type will be the + * LUB between the previous type and the type of the corresponding argument in the method + * call. + *
+ * + * @param methodInvNode the node representing a method invocation + * @param methodElt the element of the method being invoked + * @param store the store before the method call, used for inferring method preconditions + */ + void updateFromMethodInvocation( + MethodInvocationNode methodInvNode, ExecutableElement methodElt, CFAbstractStore store); - /** - * Updates the parameter types (including the receiver) of the method {@code methodTree} based - * on the parameter types of the overridden method {@code overriddenMethod}. - * - *

For each formal parameter in methodElt: - * - *

    - *
  • If there is no stored annotated type for that parameter, then use the type of the - * corresponding parameter on the overridden method. - *
  • If there is a stored annotated type for that parameter, then its new type will be the - * LUB between the previous type and the type of the corresponding parameter on the - * overridden method. - *
- * - * @param methodTree the tree of the method that contains the parameter(s) - * @param methodElt the element of the method - * @param overriddenMethod the AnnotatedExecutableType of the overridden method - */ - void updateFromOverride( - MethodTree methodTree, - ExecutableElement methodElt, - AnnotatedExecutableType overriddenMethod); + /** + * Updates the parameter types (including the receiver) of the method {@code methodTree} based on + * the parameter types of the overridden method {@code overriddenMethod}. + * + *

For each formal parameter in methodElt: + * + *

    + *
  • If there is no stored annotated type for that parameter, then use the type of the + * corresponding parameter on the overridden method. + *
  • If there is a stored annotated type for that parameter, then its new type will be the LUB + * between the previous type and the type of the corresponding parameter on the overridden + * method. + *
+ * + * @param methodTree the tree of the method that contains the parameter(s) + * @param methodElt the element of the method + * @param overriddenMethod the AnnotatedExecutableType of the overridden method + */ + void updateFromOverride( + MethodTree methodTree, ExecutableElement methodElt, AnnotatedExecutableType overriddenMethod); - /** - * Updates the type of {@code lhs} based on an assignment of {@code rhs} to {@code lhs}. - * - *
    - *
  • If there is no stored annotated type for lhs, then use the type of the corresponding - * argument in the method call methodInvNode. - *
  • If there is a stored annotated type for lhs, then its new type will be the LUB between - * the previous type and the type of the corresponding argument in the method call. - *
- * - * @param lhs the node representing the formal parameter - * @param rhs the node being assigned to the parameter in the method body - * @param paramElt the formal parameter - */ - void updateFromFormalParameterAssignment( - LocalVariableNode lhs, Node rhs, VariableElement paramElt); + /** + * Updates the type of {@code lhs} based on an assignment of {@code rhs} to {@code lhs}. + * + *
    + *
  • If there is no stored annotated type for lhs, then use the type of the corresponding + * argument in the method call methodInvNode. + *
  • If there is a stored annotated type for lhs, then its new type will be the LUB between + * the previous type and the type of the corresponding argument in the method call. + *
+ * + * @param lhs the node representing the formal parameter + * @param rhs the node being assigned to the parameter in the method body + * @param paramElt the formal parameter + */ + void updateFromFormalParameterAssignment( + LocalVariableNode lhs, Node rhs, VariableElement paramElt); - /** - * Updates the type of {@code field} based on an assignment of {@code rhs} to {@code field}. If - * the field has a declaration annotation with the {@link IgnoreInWholeProgramInference} - * meta-annotation, no type annotation will be inferred for that field. - * - *

If there is no stored entry for the field lhs, the entry will be created and its type will - * be the type of rhs. If there is a stored entry/type for lhs, its new type will be the LUB - * between the previous type and the type of rhs. - * - * @param field the field whose type will be refined. Must be either a FieldAccessNode or a - * LocalVariableNode whose element kind is FIELD. - * @param rhs the expression being assigned to the field - */ - void updateFromFieldAssignment(Node field, Node rhs); + /** + * Updates the type of {@code field} based on an assignment of {@code rhs} to {@code field}. If + * the field has a declaration annotation with the {@link IgnoreInWholeProgramInference} + * meta-annotation, no type annotation will be inferred for that field. + * + *

If there is no stored entry for the field lhs, the entry will be created and its type will + * be the type of rhs. If there is a stored entry/type for lhs, its new type will be the LUB + * between the previous type and the type of rhs. + * + * @param field the field whose type will be refined. Must be either a FieldAccessNode or a + * LocalVariableNode whose element kind is FIELD. + * @param rhs the expression being assigned to the field + */ + void updateFromFieldAssignment(Node field, Node rhs); - /** - * Updates the type of {@code field} based on an assignment whose right-hand side has type - * {@code rhsATM}. See more details at {@link #updateFromFieldAssignment}. - * - * @param lhsTree the tree for the field whose type will be refined - * @param element the element for the field whose type will be refined - * @param fieldName the name of the field whose type will be refined - * @param rhsATM the type of the expression being assigned to the field - */ - void updateFieldFromType( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM); + /** + * Updates the type of {@code field} based on an assignment whose right-hand side has type {@code + * rhsATM}. See more details at {@link #updateFromFieldAssignment}. + * + * @param lhsTree the tree for the field whose type will be refined + * @param element the element for the field whose type will be refined + * @param fieldName the name of the field whose type will be refined + * @param rhsATM the type of the expression being assigned to the field + */ + void updateFieldFromType( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM); - /** - * Updates the return type of the method {@code methodTree} based on {@code returnedExpression}. - * Also updates the return types of any methods that this method overrides that are available as - * source code. - * - *

If there is no stored annotated return type for the method methodTree, then the type of - * the return expression will be added to the return type of that method. If there is a stored - * annotated return type for the method methodTree, its new type will be the LUB between the - * previous type and the type of the return expression. - * - * @param retNode the node that contains the expression returned - * @param classSymbol the symbol of the class that contains the method - * @param methodTree the tree of the method whose return type may be updated - * @param overriddenMethods the methods that the given method return overrides, indexed by the - * annotated type of the superclass in which each method is defined - */ - void updateFromReturn( - ReturnNode retNode, - ClassSymbol classSymbol, - MethodTree methodTree, - Map overriddenMethods); + /** + * Updates the return type of the method {@code methodTree} based on {@code returnedExpression}. + * Also updates the return types of any methods that this method overrides that are available as + * source code. + * + *

If there is no stored annotated return type for the method methodTree, then the type of the + * return expression will be added to the return type of that method. If there is a stored + * annotated return type for the method methodTree, its new type will be the LUB between the + * previous type and the type of the return expression. + * + * @param retNode the node that contains the expression returned + * @param classSymbol the symbol of the class that contains the method + * @param methodTree the tree of the method whose return type may be updated + * @param overriddenMethods the methods that the given method return overrides, indexed by the + * annotated type of the superclass in which each method is defined + */ + void updateFromReturn( + ReturnNode retNode, + ClassSymbol classSymbol, + MethodTree methodTree, + Map overriddenMethods); - /** - * Updates the preconditions or postconditions of the current method, from a store. - * - * @param methodElement the method or constructor whose preconditions or postconditions to - * update - * @param preOrPost whether to update preconditions or postconditions - * @param store the store at the method's entry or normal exit, for reading types of expressions - */ - void updateContracts( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - CFAbstractStore store); + /** + * Updates the preconditions or postconditions of the current method, from a store. + * + * @param methodElement the method or constructor whose preconditions or postconditions to update + * @param preOrPost whether to update preconditions or postconditions + * @param store the store at the method's entry or normal exit, for reading types of expressions + */ + void updateContracts( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + CFAbstractStore store); - // TODO: This Javadoc should explain why this method is in WholeProgramInference and not in some - // AnnotatedTypeMirror related class. - /** - * Updates sourceCodeATM to contain the LUB between sourceCodeATM and ajavaATM, ignoring missing - * AnnotationMirrors from ajavaATM -- it considers the LUB between an AnnotationMirror am and a - * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. - * - * @param sourceCodeATM the annotated type on the source code; side effected by this method - * @param ajavaATM the annotated type on the annotation file - */ - public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM); + // TODO: This Javadoc should explain why this method is in WholeProgramInference and not in some + // AnnotatedTypeMirror related class. + /** + * Updates sourceCodeATM to contain the LUB between sourceCodeATM and ajavaATM, ignoring missing + * AnnotationMirrors from ajavaATM -- it considers the LUB between an AnnotationMirror am and a + * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. + * + * @param sourceCodeATM the annotated type on the source code; side effected by this method + * @param ajavaATM the annotated type on the annotation file + */ + public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM); - /** - * Updates a method to add a declaration annotation. - * - * @param methodElt the method to annotate - * @param anno the declaration annotation to add to the method - */ - void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); + /** + * Updates a method to add a declaration annotation. + * + * @param methodElt the method to annotate + * @param anno the declaration annotation to add to the method + */ + void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); - /** - * Updates a method to add a declaration annotation. Optionally, may replace the current purity - * annotation on {@code elt} with the logical least upper bound between that purity annotation - * and {@code anno}, if {@code anno} is also a purity annotation. - * - * @param elt the method to annotate - * @param anno the declaration annotation to add to the method - * @param lubPurity if true and {@code anno} is a purity annotation, replaces the current purity - * annotation with a least upper bound rather than just adding {@code anno} - */ - void addMethodDeclarationAnnotation( - ExecutableElement elt, AnnotationMirror anno, boolean lubPurity); + /** + * Updates a method to add a declaration annotation. Optionally, may replace the current purity + * annotation on {@code elt} with the logical least upper bound between that purity annotation and + * {@code anno}, if {@code anno} is also a purity annotation. + * + * @param elt the method to annotate + * @param anno the declaration annotation to add to the method + * @param lubPurity if true and {@code anno} is a purity annotation, replaces the current purity + * annotation with a least upper bound rather than just adding {@code anno} + */ + void addMethodDeclarationAnnotation( + ExecutableElement elt, AnnotationMirror anno, boolean lubPurity); - /** - * Updates a field to add a declaration annotation. - * - * @param fieldElt the field to annotate - * @param anno the declaration annotation to add to the field - */ - void addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); + /** + * Updates a field to add a declaration annotation. + * + * @param fieldElt the field to annotate + * @param anno the declaration annotation to add to the field + */ + void addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); - /** - * Adds a declaration annotation to a formal parameter. - * - * @param methodElt the method whose formal parameter will be annotated - * @param index_1based the index of the parameter (1-indexed) - * @param anno the annotation to add - */ - void addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); + /** + * Adds a declaration annotation to a formal parameter. + * + * @param methodElt the method whose formal parameter will be annotated + * @param index_1based the index of the parameter (1-indexed) + * @param anno the annotation to add + */ + void addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); + + /** + * Adds an annotation to a class declaration. + * + * @param classElt the class declaration to annotate + * @param anno the annotation to add + */ + void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); + /** + * Writes the inferred results to a file. Ideally, it should be called at the end of the + * type-checking process. In practice, it is called after each class, because we don't know which + * class will be the last one in the type-checking process. + * + * @param format the file format in which to write the results + * @param checker the checker from which this method is called, for naming annotation files + */ + void writeResultsToFile(OutputFormat format, BaseTypeChecker checker); + + /** + * Performs any preparation required for inference on Elements of a class. Should be called on + * each toplevel class declaration in a compilation unit before processing it. + * + * @param classTree the class to preprocess + */ + void preprocessClassTree(ClassTree classTree); + + /** The kinds of output that whole-program inference can produce. */ + enum OutputFormat { /** - * Adds an annotation to a class declaration. - * - * @param classElt the class declaration to annotate - * @param anno the annotation to add + * Output the results of whole-program inference as a stub file that can be parsed back into the + * Checker Framework by the Stub Parser. */ - void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); + STUB(), /** - * Writes the inferred results to a file. Ideally, it should be called at the end of the - * type-checking process. In practice, it is called after each class, because we don't know - * which class will be the last one in the type-checking process. - * - * @param format the file format in which to write the results - * @param checker the checker from which this method is called, for naming annotation files + * Output the results of whole-program inference as a Java annotation index file. The Annotation + * File Utilities project contains code for reading and writing .jaif files. */ - void writeResultsToFile(OutputFormat format, BaseTypeChecker checker); + JAIF(), /** - * Performs any preparation required for inference on Elements of a class. Should be called on - * each toplevel class declaration in a compilation unit before processing it. - * - * @param classTree the class to preprocess + * Output the results of whole-program inference as an ajava file that can be read in using the + * {@code -Aajava} option. */ - void preprocessClassTree(ClassTree classTree); - - /** The kinds of output that whole-program inference can produce. */ - enum OutputFormat { - /** - * Output the results of whole-program inference as a stub file that can be parsed back into - * the Checker Framework by the Stub Parser. - */ - STUB(), - - /** - * Output the results of whole-program inference as a Java annotation index file. The - * Annotation File Utilities project contains code for reading and writing .jaif files. - */ - JAIF(), - - /** - * Output the results of whole-program inference as an ajava file that can be read in using - * the {@code -Aajava} option. - */ - AJAVA(), - } + AJAVA(), + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index cb27095f4d5..a1061b458d2 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -5,7 +5,17 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol.ClassSymbol; - +import java.util.List; +import java.util.Map; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import org.checkerframework.afu.scenelib.util.JVMNames; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.Nullable; @@ -51,19 +61,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; -import java.util.List; -import java.util.Map; - -import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; - /** * This is the primary implementation of {@link * org.checkerframework.common.wholeprograminference.WholeProgramInference}. It uses an instance of @@ -108,1083 +105,1050 @@ // can yield different results (order of annotations). public class WholeProgramInferenceImplementation implements WholeProgramInference { - /** The type factory associated with this. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Whether to print debugging information when an inference is attempted, but cannot be - * completed. An inference can be attempted without success for example because the current - * storage system does not support placing annotation in the location for which an annotation - * was inferred. - */ - private final boolean showWpiFailedInferences; - - /** The storage for the inferred annotations. */ - private final WholeProgramInferenceStorage storage; - - /** Whether to ignore assignments where the rhs is null. */ - private final boolean ignoreNullAssignments; - - /** The @{@link Deterministic} annotation. */ - private final AnnotationMirror DETERMINISTIC; - - /** The @{@link SideEffectFree} annotation. */ - private final AnnotationMirror SIDE_EFFECT_FREE; - - /** The @{@link Pure} annotation. */ - private final AnnotationMirror PURE; - - /** The @{@link Impure} annotation. */ - private final AnnotationMirror IMPURE; - - /** The fully-qualified name of the @{@link Deterministic} class. */ - private final String DETERMINISTIC_NAME = "org.checkerframework.dataflow.qual.Deterministic"; - - /** The fully-qualified name of the @{@link SideEffectFree} class. */ - private final String SIDE_EFFECT_FREE_NAME = - "org.checkerframework.dataflow.qual.SideEffectFree"; - - /** The fully-qualified name of the @{@link Pure} class. */ - private final String PURE_NAME = "org.checkerframework.dataflow.qual.Pure"; - - /** The fully-qualified name of the @{@link Impure} class. */ - private final String IMPURE_NAME = "org.checkerframework.dataflow.qual.Impure"; - - /** - * Constructs a new {@code WholeProgramInferenceImplementation} that has not yet inferred any - * annotations. - * - * @param atypeFactory the associated type factory - * @param storage the storage used for inferred annotations and for writing output files - * @param showWpiFailedInferences whether the {@code -AshowWpiFailedInferences} argument was - * passed to the checker, and therefore whether to print debugging messages when inference - * fails - */ - public WholeProgramInferenceImplementation( - AnnotatedTypeFactory atypeFactory, - WholeProgramInferenceStorage storage, - boolean showWpiFailedInferences) { - this.atypeFactory = atypeFactory; - this.storage = storage; - boolean isNullness = - atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); - this.ignoreNullAssignments = !isNullness; - this.showWpiFailedInferences = showWpiFailedInferences; - DETERMINISTIC = - AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Deterministic.class); - SIDE_EFFECT_FREE = - AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), SideEffectFree.class); - PURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Pure.class); - IMPURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Impure.class); + /** The type factory associated with this. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** + * Whether to print debugging information when an inference is attempted, but cannot be completed. + * An inference can be attempted without success for example because the current storage system + * does not support placing annotation in the location for which an annotation was inferred. + */ + private final boolean showWpiFailedInferences; + + /** The storage for the inferred annotations. */ + private final WholeProgramInferenceStorage storage; + + /** Whether to ignore assignments where the rhs is null. */ + private final boolean ignoreNullAssignments; + + /** The @{@link Deterministic} annotation. */ + private final AnnotationMirror DETERMINISTIC; + + /** The @{@link SideEffectFree} annotation. */ + private final AnnotationMirror SIDE_EFFECT_FREE; + + /** The @{@link Pure} annotation. */ + private final AnnotationMirror PURE; + + /** The @{@link Impure} annotation. */ + private final AnnotationMirror IMPURE; + + /** The fully-qualified name of the @{@link Deterministic} class. */ + private final String DETERMINISTIC_NAME = "org.checkerframework.dataflow.qual.Deterministic"; + + /** The fully-qualified name of the @{@link SideEffectFree} class. */ + private final String SIDE_EFFECT_FREE_NAME = "org.checkerframework.dataflow.qual.SideEffectFree"; + + /** The fully-qualified name of the @{@link Pure} class. */ + private final String PURE_NAME = "org.checkerframework.dataflow.qual.Pure"; + + /** The fully-qualified name of the @{@link Impure} class. */ + private final String IMPURE_NAME = "org.checkerframework.dataflow.qual.Impure"; + + /** + * Constructs a new {@code WholeProgramInferenceImplementation} that has not yet inferred any + * annotations. + * + * @param atypeFactory the associated type factory + * @param storage the storage used for inferred annotations and for writing output files + * @param showWpiFailedInferences whether the {@code -AshowWpiFailedInferences} argument was + * passed to the checker, and therefore whether to print debugging messages when inference + * fails + */ + public WholeProgramInferenceImplementation( + AnnotatedTypeFactory atypeFactory, + WholeProgramInferenceStorage storage, + boolean showWpiFailedInferences) { + this.atypeFactory = atypeFactory; + this.storage = storage; + boolean isNullness = + atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); + this.ignoreNullAssignments = !isNullness; + this.showWpiFailedInferences = showWpiFailedInferences; + DETERMINISTIC = + AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Deterministic.class); + SIDE_EFFECT_FREE = + AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), SideEffectFree.class); + PURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Pure.class); + IMPURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Impure.class); + } + + /** + * Returns the storage for inferred annotations. + * + * @return the storage for the inferred annotations + */ + public WholeProgramInferenceStorage getStorage() { + return storage; + } + + @Override + public void updateFromObjectCreation( + ObjectCreationNode objectCreationNode, + ExecutableElement constructorElt, + CFAbstractStore store) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(constructorElt)) { + return; } - /** - * Returns the storage for inferred annotations. - * - * @return the storage for the inferred annotations - */ - public WholeProgramInferenceStorage getStorage() { - return storage; + // Don't infer types for code that can't be annotated anyway. + if (!storage.hasStorageLocationForMethod(constructorElt)) { + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "WPI could not store information" + + "about this constructor: " + + JVMNames.getJVMMethodSignature(constructorElt)); + } + return; } - @Override - public void updateFromObjectCreation( - ObjectCreationNode objectCreationNode, - ExecutableElement constructorElt, - CFAbstractStore store) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(constructorElt)) { - return; - } - - // Don't infer types for code that can't be annotated anyway. - if (!storage.hasStorageLocationForMethod(constructorElt)) { - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "WPI could not store information" - + "about this constructor: " - + JVMNames.getJVMMethodSignature(constructorElt)); - } - return; - } - - List arguments = objectCreationNode.getArguments(); - updateInferredExecutableParameterTypes( - constructorElt, arguments, null, objectCreationNode.getTree()); - updateContracts(Analysis.BeforeOrAfter.BEFORE, constructorElt, store); + List arguments = objectCreationNode.getArguments(); + updateInferredExecutableParameterTypes( + constructorElt, arguments, null, objectCreationNode.getTree()); + updateContracts(Analysis.BeforeOrAfter.BEFORE, constructorElt, store); + } + + @Override + public void updateFromMethodInvocation( + MethodInvocationNode methodInvNode, + ExecutableElement methodElt, + CFAbstractStore store) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; } - @Override - public void updateFromMethodInvocation( - MethodInvocationNode methodInvNode, - ExecutableElement methodElt, - CFAbstractStore store) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } - - if (!storage.hasStorageLocationForMethod(methodElt)) { - return; - } - - // Don't infer formal parameter types from recursive calls. - // - // When performing WPI on a library, if there are no external calls (only recursive calls), - // then each iteration of WPI would make the formal parameter types more restrictive, - // leading to an infinite (or very long) loop. - // - // Consider - // void myMethod(int x) { ... myMethod(x-1) ... }` - // On one iteration, if x has type IntRange(to=100), the recursive call's argument has type - // IntRange(to=99). If that is the only call to `MyMethod`, then the formal parameter type - // would be updated. On the next iteration it would be refined again to @IntRange(to=98), - // and so forth. A recursive call should never restrict a formal parameter type. - if (isRecursiveCall(methodInvNode)) { - return; - } - - List arguments = methodInvNode.getArguments(); - Node receiver = methodInvNode.getTarget().getReceiver(); - // Static methods have a "receiver" that is a class name rather than an expression. - // Do not attempt to use the class name as a receiver expression for inference - // purposes. - if (receiver instanceof ClassNameNode) { - receiver = null; - } - updateInferredExecutableParameterTypes( - methodElt, arguments, receiver, methodInvNode.getTree()); - updateContracts(Analysis.BeforeOrAfter.BEFORE, methodElt, store); + if (!storage.hasStorageLocationForMethod(methodElt)) { + return; } - /** - * Returns true if the given call is a recursive call. - * - * @param methodInvNode a method invocation - * @return true if the given call is a recursive call - */ - private boolean isRecursiveCall(MethodInvocationNode methodInvNode) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(methodInvNode.getTreePath()); - if (enclosingMethod == null) { - return false; - } - ExecutableElement methodInvocEle = TreeUtils.elementFromUse(methodInvNode.getTree()); - ExecutableElement methodDeclEle = TreeUtils.elementFromDeclaration(enclosingMethod); - return methodDeclEle.equals(methodInvocEle); + // Don't infer formal parameter types from recursive calls. + // + // When performing WPI on a library, if there are no external calls (only recursive calls), + // then each iteration of WPI would make the formal parameter types more restrictive, + // leading to an infinite (or very long) loop. + // + // Consider + // void myMethod(int x) { ... myMethod(x-1) ... }` + // On one iteration, if x has type IntRange(to=100), the recursive call's argument has type + // IntRange(to=99). If that is the only call to `MyMethod`, then the formal parameter type + // would be updated. On the next iteration it would be refined again to @IntRange(to=98), + // and so forth. A recursive call should never restrict a formal parameter type. + if (isRecursiveCall(methodInvNode)) { + return; } - /** - * Updates inferred parameter types based on a call to a method or constructor. - * - * @param methodElt the element of the method or constructor being invoked - * @param arguments the arguments of the invocation - * @param receiver the receiver node, if there is one; null if there is not - * @param invocationTree the method or constructor invocation, used to viewpoint adapt any - * dependent types when storing newly-inferred annotations - */ - private void updateInferredExecutableParameterTypes( - ExecutableElement methodElt, - List arguments, - @Nullable Node receiver, - ExpressionTree invocationTree) { - - String file = storage.getFileForElement(methodElt); - // Need to check both that receiver is non-null and that this is not a constructor - // invocation: despite updateFromObjectCreation always passes null, it's possible - // for updateFromMethodInvocation to actually be a constructor invocation with a - // receiver: for example, when calling an inner class's constructor, the receiver - // can be an instance of the enclosing class. Constructor invocations should never - // have information inferred about their receivers. - if (receiver != null - && atypeFactory.wpiShouldInferTypesForReceivers() - && !methodElt.getSimpleName().contentEquals("")) { - AnnotatedTypeMirror receiverArgATM = atypeFactory.getReceiverType(invocationTree); - AnnotatedExecutableType methodDeclType = atypeFactory.getAnnotatedType(methodElt); - AnnotatedTypeMirror receiverParamATM = methodDeclType.getReceiverType(); - // update the set of annotations for the receiver type if it is not null. - if (receiverParamATM != null) { - atypeFactory.wpiAdjustForUpdateNonField(receiverArgATM); - T receiverAnnotations = - storage.getReceiverAnnotations(methodElt, receiverParamATM, atypeFactory); - if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { - ((GenericAnnotatedTypeFactory) this.atypeFactory) - .getDependentTypesHelper() - .delocalizeAtCallsite( - receiverArgATM, invocationTree, arguments, receiver, methodElt); - } - updateAnnotationSet( - receiverAnnotations, - TypeUseLocation.RECEIVER, - receiverArgATM, - receiverParamATM, - file); - } - } - - int numArguments = arguments.size(); - for (int i = 0; i < numArguments; i++) { - Node arg = arguments.get(i); - Tree argTree = arg.getTree(); - - VariableElement ve; - boolean varargsParam = - i >= methodElt.getParameters().size() - 1 && methodElt.isVarArgs(); - if (varargsParam && this.atypeFactory.wpiOutputFormat == OutputFormat.JAIF) { - // The AFU's org.checkerframework.afu.annotator.Main produces a non-compilable - // source file when JAIF-based WPI tries to output an annotated varargs parameter, - // such as when running the test - // checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java. - // Until that bug is fixed, do not attempt to infer information about varargs - // parameters in JAIF mode. - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Annotations cannot be placed on varargs parameters in -Ainfer=jaifs mode, because" - + " the JAIF format does not correctly support it.\n" - + "The signature of the method whose varargs parameter was not annotated is: " - + JVMNames.getJVMMethodSignature(methodElt)); - } - return; - } - List params = methodElt.getParameters(); - if (varargsParam) { - ve = params.get(params.size() - 1); - } else { - ve = params.get(i); - } - AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); - AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(argTree); - if (varargsParam) { - // Check whether argATM needs to be turned into an array type, so that the type - // structure matches paramATM. - boolean expandArgATM = false; - if (argATM.getKind() == TypeKind.ARRAY) { - int argATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) argATM); - // This unchecked cast is safe because the declared type of a varargs parameter - // is guaranteed to be an array of some kind. - int paramATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) paramATM); - if (paramATMDepth != argATMDepth) { - assert argATMDepth + 1 == paramATMDepth; - expandArgATM = true; - } - } else { - expandArgATM = true; - } - if (expandArgATM) { - if (argATM.getKind() == TypeKind.WILDCARD) { - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Javac cannot create an array type " - + "from a wildcard, so WPI did not attempt to infer a type for an array " - + "parameter.\n" - + "The signature of the method whose parameter had inference skipped is: " - + JVMNames.getJVMMethodSignature(methodElt)); - } - return; - } - AnnotatedTypeMirror argArray = - AnnotatedTypeMirror.createType( - TypesUtils.createArrayType( - argATM.getUnderlyingType(), atypeFactory.types), - atypeFactory, - false); - ((AnnotatedArrayType) argArray).setComponentType(argATM); - argATM = argArray; - } - } - atypeFactory.wpiAdjustForUpdateNonField(argATM); - // If storage.getParameterAnnotations receives an index that's larger than the size - // of the parameter list, scenes-backed inference can create duplicate entries - // for the varargs parameter (it indexes inferred annotations by the parameter number). - int paramIndex = varargsParam ? methodElt.getParameters().size() : i + 1; - T paramAnnotations = - storage.getParameterAnnotations( - methodElt, paramIndex, paramATM, ve, atypeFactory); - if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { - ((GenericAnnotatedTypeFactory) this.atypeFactory) - .getDependentTypesHelper() - .delocalizeAtCallsite( - argATM, invocationTree, arguments, receiver, methodElt); - } - updateAnnotationSet( - paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); - } + List arguments = methodInvNode.getArguments(); + Node receiver = methodInvNode.getTarget().getReceiver(); + // Static methods have a "receiver" that is a class name rather than an expression. + // Do not attempt to use the class name as a receiver expression for inference + // purposes. + if (receiver instanceof ClassNameNode) { + receiver = null; } - - @Override - public void updateContracts( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElt, - CFAbstractStore store) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } - - if (store == null) { - throw new BugInCF( - "updateContracts(%s, %s, null) for %s", - preOrPost, methodElt, atypeFactory.getClass().getSimpleName()); - } - - if (!storage.hasStorageLocationForMethod(methodElt)) { - return; + updateInferredExecutableParameterTypes(methodElt, arguments, receiver, methodInvNode.getTree()); + updateContracts(Analysis.BeforeOrAfter.BEFORE, methodElt, store); + } + + /** + * Returns true if the given call is a recursive call. + * + * @param methodInvNode a method invocation + * @return true if the given call is a recursive call + */ + private boolean isRecursiveCall(MethodInvocationNode methodInvNode) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(methodInvNode.getTreePath()); + if (enclosingMethod == null) { + return false; + } + ExecutableElement methodInvocEle = TreeUtils.elementFromUse(methodInvNode.getTree()); + ExecutableElement methodDeclEle = TreeUtils.elementFromDeclaration(enclosingMethod); + return methodDeclEle.equals(methodInvocEle); + } + + /** + * Updates inferred parameter types based on a call to a method or constructor. + * + * @param methodElt the element of the method or constructor being invoked + * @param arguments the arguments of the invocation + * @param receiver the receiver node, if there is one; null if there is not + * @param invocationTree the method or constructor invocation, used to viewpoint adapt any + * dependent types when storing newly-inferred annotations + */ + private void updateInferredExecutableParameterTypes( + ExecutableElement methodElt, + List arguments, + @Nullable Node receiver, + ExpressionTree invocationTree) { + + String file = storage.getFileForElement(methodElt); + // Need to check both that receiver is non-null and that this is not a constructor + // invocation: despite updateFromObjectCreation always passes null, it's possible + // for updateFromMethodInvocation to actually be a constructor invocation with a + // receiver: for example, when calling an inner class's constructor, the receiver + // can be an instance of the enclosing class. Constructor invocations should never + // have information inferred about their receivers. + if (receiver != null + && atypeFactory.wpiShouldInferTypesForReceivers() + && !methodElt.getSimpleName().contentEquals("")) { + AnnotatedTypeMirror receiverArgATM = atypeFactory.getReceiverType(invocationTree); + AnnotatedExecutableType methodDeclType = atypeFactory.getAnnotatedType(methodElt); + AnnotatedTypeMirror receiverParamATM = methodDeclType.getReceiverType(); + // update the set of annotations for the receiver type if it is not null. + if (receiverParamATM != null) { + atypeFactory.wpiAdjustForUpdateNonField(receiverArgATM); + T receiverAnnotations = + storage.getReceiverAnnotations(methodElt, receiverParamATM, atypeFactory); + if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { + ((GenericAnnotatedTypeFactory) this.atypeFactory) + .getDependentTypesHelper() + .delocalizeAtCallsite(receiverArgATM, invocationTree, arguments, receiver, methodElt); } + updateAnnotationSet( + receiverAnnotations, TypeUseLocation.RECEIVER, receiverArgATM, receiverParamATM, file); + } + } - // TODO: Probably move some part of this into the AnnotatedTypeFactory. - - // This code handles fields of "this" and method parameters (including the receiver - // parameter "this"), for now. In the future, extend it to other expressions. - TypeElement containingClass = (TypeElement) methodElt.getEnclosingElement(); - ThisReference thisReference = new ThisReference(containingClass.asType()); - ClassName classNameReceiver = new ClassName(containingClass.asType()); - // Fields of "this": - for (VariableElement fieldElement : - ElementFilter.fieldsIn(containingClass.getEnclosedElements())) { - if (atypeFactory.wpiOutputFormat == OutputFormat.JAIF - && containingClass.getNestingKind().isNested()) { - // Don't infer facts about fields of inner classes, because IndexFileWriter - // places the annotations incorrectly on the class declarations. - continue; - } - if (ElementUtils.isStatic(methodElt) && !ElementUtils.isStatic(fieldElement)) { - // A static method can't have precondition annotations about instance fields. - continue; - } - FieldAccess fa = - new FieldAccess( - (ElementUtils.isStatic(fieldElement) - ? classNameReceiver - : thisReference), - fieldElement.asType(), - fieldElement); - CFAbstractValue v = store.getFieldValue(fa); - AnnotatedTypeMirror fieldDeclType = atypeFactory.getAnnotatedType(fieldElement); - AnnotatedTypeMirror inferredType; - if (v != null) { - // This field is in the store. - inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, fieldDeclType); - atypeFactory.wpiAdjustForUpdateNonField(inferredType); - } else { - // This field is not in the store. Use the declared type. - inferredType = fieldDeclType; - } - T preOrPostConditionAnnos = - storage.getPreOrPostconditions( - preOrPost, methodElt, fa.toString(), fieldDeclType, atypeFactory); - if (preOrPostConditionAnnos == null) { - continue; - } - String file = storage.getFileForElement(methodElt); - updateAnnotationSet( - preOrPostConditionAnnos, - TypeUseLocation.FIELD, - inferredType, - fieldDeclType, - file, - false); + int numArguments = arguments.size(); + for (int i = 0; i < numArguments; i++) { + Node arg = arguments.get(i); + Tree argTree = arg.getTree(); + + VariableElement ve; + boolean varargsParam = i >= methodElt.getParameters().size() - 1 && methodElt.isVarArgs(); + if (varargsParam && this.atypeFactory.wpiOutputFormat == OutputFormat.JAIF) { + // The AFU's org.checkerframework.afu.annotator.Main produces a non-compilable + // source file when JAIF-based WPI tries to output an annotated varargs parameter, + // such as when running the test + // checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java. + // Until that bug is fixed, do not attempt to infer information about varargs + // parameters in JAIF mode. + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Annotations cannot be placed on varargs parameters in -Ainfer=jaifs mode, because" + + " the JAIF format does not correctly support it.\n" + + "The signature of the method whose varargs parameter was not annotated is: " + + JVMNames.getJVMMethodSignature(methodElt)); } - // Method parameters (other than the receiver parameter "this"): - // This loop is 1-indexed to match the syntax used in annotation arguments. - for (int index = 1; index <= methodElt.getParameters().size(); index++) { - VariableElement paramElt = methodElt.getParameters().get(index - 1); - - // Do not infer information about non-effectively-final method parameters, to avoid - // spurious flowexpr.parameter.not.final warnings. - if (!ElementUtils.isEffectivelyFinal(paramElt)) { - continue; - } - LocalVariable param = new LocalVariable(paramElt); - CFAbstractValue v = store.getValue(param); - AnnotatedTypeMirror declType = atypeFactory.getAnnotatedType(paramElt); - AnnotatedTypeMirror inferredType; - if (v != null) { - // This parameter is in the store. - inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, declType); - atypeFactory.wpiAdjustForUpdateNonField(inferredType); - } else { - // The parameter is not in the store, so don't attempt to create a postcondition for - // it, since anything other than its default type would not be verifiable. (Only - // postconditions are supported for parameters.) - continue; - } - T preOrPostConditionAnnos = - storage.getPreOrPostconditions( - preOrPost, methodElt, "#" + index, declType, atypeFactory); - if (preOrPostConditionAnnos != null) { - String file = storage.getFileForElement(methodElt); - updateAnnotationSet( - preOrPostConditionAnnos, - TypeUseLocation.PARAMETER, - inferredType, - declType, - file, - false); - } + return; + } + List params = methodElt.getParameters(); + if (varargsParam) { + ve = params.get(params.size() - 1); + } else { + ve = params.get(i); + } + AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); + AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(argTree); + if (varargsParam) { + // Check whether argATM needs to be turned into an array type, so that the type + // structure matches paramATM. + boolean expandArgATM = false; + if (argATM.getKind() == TypeKind.ARRAY) { + int argATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) argATM); + // This unchecked cast is safe because the declared type of a varargs parameter + // is guaranteed to be an array of some kind. + int paramATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) paramATM); + if (paramATMDepth != argATMDepth) { + assert argATMDepth + 1 == paramATMDepth; + expandArgATM = true; + } + } else { + expandArgATM = true; } - // Receiver parameter ("this"): - if (!ElementUtils.isStatic(methodElt)) { // Static methods do not have a receiver. - CFAbstractValue v = store.getValue(thisReference); - if (v != null) { - // This parameter is in the store. - AnnotatedTypeMirror declaredType = - atypeFactory.getAnnotatedType(methodElt).getReceiverType(); - if (declaredType == null) { - // declaredType is null when the method being analyzed is a constructor (which - // doesn't have a receiver). - return; - } - AnnotatedTypeMirror inferredType = - AnnotatedTypeMirror.createType( - declaredType.getUnderlyingType(), atypeFactory, false); - inferredType.replaceAnnotations(v.getAnnotations()); - atypeFactory.wpiAdjustForUpdateNonField(inferredType); - T preOrPostConditionAnnos = - storage.getPreOrPostconditions( - preOrPost, methodElt, "this", declaredType, atypeFactory); - if (preOrPostConditionAnnos != null) { - String file = storage.getFileForElement(methodElt); - updateAnnotationSet( - preOrPostConditionAnnos, - TypeUseLocation.PARAMETER, - inferredType, - declaredType, - file, - false); - } + if (expandArgATM) { + if (argATM.getKind() == TypeKind.WILDCARD) { + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Javac cannot create an array type " + + "from a wildcard, so WPI did not attempt to infer a type for an array " + + "parameter.\n" + + "The signature of the method whose parameter had inference skipped is: " + + JVMNames.getJVMMethodSignature(methodElt)); } + return; + } + AnnotatedTypeMirror argArray = + AnnotatedTypeMirror.createType( + TypesUtils.createArrayType(argATM.getUnderlyingType(), atypeFactory.types), + atypeFactory, + false); + ((AnnotatedArrayType) argArray).setComponentType(argATM); + argATM = argArray; } + } + atypeFactory.wpiAdjustForUpdateNonField(argATM); + // If storage.getParameterAnnotations receives an index that's larger than the size + // of the parameter list, scenes-backed inference can create duplicate entries + // for the varargs parameter (it indexes inferred annotations by the parameter number). + int paramIndex = varargsParam ? methodElt.getParameters().size() : i + 1; + T paramAnnotations = + storage.getParameterAnnotations(methodElt, paramIndex, paramATM, ve, atypeFactory); + if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { + ((GenericAnnotatedTypeFactory) this.atypeFactory) + .getDependentTypesHelper() + .delocalizeAtCallsite(argATM, invocationTree, arguments, receiver, methodElt); + } + updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); + } + } + + @Override + public void updateContracts( + Analysis.BeforeOrAfter preOrPost, ExecutableElement methodElt, CFAbstractStore store) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; } - /** - * Converts a CFAbstractValue to an AnnotatedTypeMirror. - * - * @param v a value to convert to an AnnotatedTypeMirror - * @param fieldType an {@code AnnotatedTypeMirror} with the same underlying type as {@code v} - * that is copied, then the copy is updated to use {@code v}'s annotations - * @return a copy of {@code fieldType} with {@code v}'s annotations - */ - private AnnotatedTypeMirror convertCFAbstractValueToAnnotatedTypeMirror( - CFAbstractValue v, AnnotatedTypeMirror fieldType) { - AnnotatedTypeMirror result = fieldType.deepCopy(); - result.replaceAnnotations(v.getAnnotations()); - return result; + if (store == null) { + throw new BugInCF( + "updateContracts(%s, %s, null) for %s", + preOrPost, methodElt, atypeFactory.getClass().getSimpleName()); } - @Override - public void updateFromOverride( - MethodTree methodTree, - ExecutableElement methodElt, - AnnotatedExecutableType overriddenMethod) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } + if (!storage.hasStorageLocationForMethod(methodElt)) { + return; + } + // TODO: Probably move some part of this into the AnnotatedTypeFactory. + + // This code handles fields of "this" and method parameters (including the receiver + // parameter "this"), for now. In the future, extend it to other expressions. + TypeElement containingClass = (TypeElement) methodElt.getEnclosingElement(); + ThisReference thisReference = new ThisReference(containingClass.asType()); + ClassName classNameReceiver = new ClassName(containingClass.asType()); + // Fields of "this": + for (VariableElement fieldElement : + ElementFilter.fieldsIn(containingClass.getEnclosedElements())) { + if (atypeFactory.wpiOutputFormat == OutputFormat.JAIF + && containingClass.getNestingKind().isNested()) { + // Don't infer facts about fields of inner classes, because IndexFileWriter + // places the annotations incorrectly on the class declarations. + continue; + } + if (ElementUtils.isStatic(methodElt) && !ElementUtils.isStatic(fieldElement)) { + // A static method can't have precondition annotations about instance fields. + continue; + } + FieldAccess fa = + new FieldAccess( + (ElementUtils.isStatic(fieldElement) ? classNameReceiver : thisReference), + fieldElement.asType(), + fieldElement); + CFAbstractValue v = store.getFieldValue(fa); + AnnotatedTypeMirror fieldDeclType = atypeFactory.getAnnotatedType(fieldElement); + AnnotatedTypeMirror inferredType; + if (v != null) { + // This field is in the store. + inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, fieldDeclType); + atypeFactory.wpiAdjustForUpdateNonField(inferredType); + } else { + // This field is not in the store. Use the declared type. + inferredType = fieldDeclType; + } + T preOrPostConditionAnnos = + storage.getPreOrPostconditions( + preOrPost, methodElt, fa.toString(), fieldDeclType, atypeFactory); + if (preOrPostConditionAnnos == null) { + continue; + } + String file = storage.getFileForElement(methodElt); + updateAnnotationSet( + preOrPostConditionAnnos, TypeUseLocation.FIELD, inferredType, fieldDeclType, file, false); + } + // Method parameters (other than the receiver parameter "this"): + // This loop is 1-indexed to match the syntax used in annotation arguments. + for (int index = 1; index <= methodElt.getParameters().size(); index++) { + VariableElement paramElt = methodElt.getParameters().get(index - 1); + + // Do not infer information about non-effectively-final method parameters, to avoid + // spurious flowexpr.parameter.not.final warnings. + if (!ElementUtils.isEffectivelyFinal(paramElt)) { + continue; + } + LocalVariable param = new LocalVariable(paramElt); + CFAbstractValue v = store.getValue(param); + AnnotatedTypeMirror declType = atypeFactory.getAnnotatedType(paramElt); + AnnotatedTypeMirror inferredType; + if (v != null) { + // This parameter is in the store. + inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, declType); + atypeFactory.wpiAdjustForUpdateNonField(inferredType); + } else { + // The parameter is not in the store, so don't attempt to create a postcondition for + // it, since anything other than its default type would not be verifiable. (Only + // postconditions are supported for parameters.) + continue; + } + T preOrPostConditionAnnos = + storage.getPreOrPostconditions(preOrPost, methodElt, "#" + index, declType, atypeFactory); + if (preOrPostConditionAnnos != null) { String file = storage.getFileForElement(methodElt); - - int numParams = overriddenMethod.getParameterTypes().size(); - for (int i = 0; i < numParams; i++) { - VariableElement ve = methodElt.getParameters().get(i); - AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); - AnnotatedTypeMirror argATM = overriddenMethod.getParameterTypes().get(i); - atypeFactory.wpiAdjustForUpdateNonField(argATM); - T paramAnnotations = - storage.getParameterAnnotations(methodElt, i + 1, paramATM, ve, atypeFactory); - updateAnnotationSet( - paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); - } - - AnnotatedDeclaredType argADT = overriddenMethod.getReceiverType(); - if (argADT != null) { - AnnotatedTypeMirror paramATM = - atypeFactory.getAnnotatedType(methodTree).getReceiverType(); - if (paramATM != null) { - T receiver = storage.getReceiverAnnotations(methodElt, paramATM, atypeFactory); - updateAnnotationSet(receiver, TypeUseLocation.RECEIVER, argADT, paramATM, file); - } - } + updateAnnotationSet( + preOrPostConditionAnnos, + TypeUseLocation.PARAMETER, + inferredType, + declType, + file, + false); + } } - - @Override - public void updateFromFormalParameterAssignment( - LocalVariableNode lhs, Node rhs, VariableElement paramElt) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(lhs.getElement())) { - return; - } - - Tree rhsTree = rhs.getTree(); - if (rhsTree == null) { - // TODO: Handle variable-length list as parameter. - // An ArrayCreationNode with a null tree is created when the - // parameter is a variable-length list. We are ignoring it for now. - // See Issue 682: https://github.com/typetools/checker-framework/issues/682 - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Could not update from formal parameter " - + "assignment, because an ArrayCreationNode with a null tree is created when " - + "the parameter is a variable-length list.\nParameter: " - + paramElt); - } - return; + // Receiver parameter ("this"): + if (!ElementUtils.isStatic(methodElt)) { // Static methods do not have a receiver. + CFAbstractValue v = store.getValue(thisReference); + if (v != null) { + // This parameter is in the store. + AnnotatedTypeMirror declaredType = + atypeFactory.getAnnotatedType(methodElt).getReceiverType(); + if (declaredType == null) { + // declaredType is null when the method being analyzed is a constructor (which + // doesn't have a receiver). + return; } - - ExecutableElement methodElt = (ExecutableElement) paramElt.getEnclosingElement(); - - int index_1based = methodElt.getParameters().indexOf(paramElt) + 1; - if (index_1based == 0) { - // When paramElt is the parameter of a lambda contained in another - // method body, the enclosing element is the outer method body - // rather than the lambda itself (which has no element). WPI - // does not support inferring types for lambda parameters, so - // ignore it. - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Could not update from formal " - + "parameter assignment inside a lambda expression, because lambda parameters " - + "cannot be annotated.\nParameter: " - + paramElt); - } - return; + AnnotatedTypeMirror inferredType = + AnnotatedTypeMirror.createType(declaredType.getUnderlyingType(), atypeFactory, false); + inferredType.replaceAnnotations(v.getAnnotations()); + atypeFactory.wpiAdjustForUpdateNonField(inferredType); + T preOrPostConditionAnnos = + storage.getPreOrPostconditions( + preOrPost, methodElt, "this", declaredType, atypeFactory); + if (preOrPostConditionAnnos != null) { + String file = storage.getFileForElement(methodElt); + updateAnnotationSet( + preOrPostConditionAnnos, + TypeUseLocation.PARAMETER, + inferredType, + declaredType, + file, + false); } - - AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(paramElt); - AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(rhsTree); - atypeFactory.wpiAdjustForUpdateNonField(argATM); - T paramAnnotations = - storage.getParameterAnnotations( - methodElt, index_1based, paramATM, paramElt, atypeFactory); - String file = storage.getFileForElement(methodElt); - updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); + } + } + } + + /** + * Converts a CFAbstractValue to an AnnotatedTypeMirror. + * + * @param v a value to convert to an AnnotatedTypeMirror + * @param fieldType an {@code AnnotatedTypeMirror} with the same underlying type as {@code v} that + * is copied, then the copy is updated to use {@code v}'s annotations + * @return a copy of {@code fieldType} with {@code v}'s annotations + */ + private AnnotatedTypeMirror convertCFAbstractValueToAnnotatedTypeMirror( + CFAbstractValue v, AnnotatedTypeMirror fieldType) { + AnnotatedTypeMirror result = fieldType.deepCopy(); + result.replaceAnnotations(v.getAnnotations()); + return result; + } + + @Override + public void updateFromOverride( + MethodTree methodTree, + ExecutableElement methodElt, + AnnotatedExecutableType overriddenMethod) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; } - @Override - public void updateFromFieldAssignment(Node lhs, Node rhs) { - - Element element; - String fieldName; - if (lhs instanceof FieldAccessNode) { - element = ((FieldAccessNode) lhs).getElement(); - fieldName = ((FieldAccessNode) lhs).getFieldName(); - } else if (lhs instanceof LocalVariableNode) { - element = ((LocalVariableNode) lhs).getElement(); - fieldName = ((LocalVariableNode) lhs).getName(); - } else { - throw new BugInCF( - "updateFromFieldAssignment received an unexpected node type: " - + lhs.getClass()); - } - - // TODO: For a primitive such as long, this is yielding just @GuardedBy rather than - // @GuardedBy({}). - AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(rhs.getTree()); - atypeFactory.wpiAdjustForUpdateField(lhs.getTree(), element, fieldName, rhsATM); + String file = storage.getFileForElement(methodElt); + + int numParams = overriddenMethod.getParameterTypes().size(); + for (int i = 0; i < numParams; i++) { + VariableElement ve = methodElt.getParameters().get(i); + AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); + AnnotatedTypeMirror argATM = overriddenMethod.getParameterTypes().get(i); + atypeFactory.wpiAdjustForUpdateNonField(argATM); + T paramAnnotations = + storage.getParameterAnnotations(methodElt, i + 1, paramATM, ve, atypeFactory); + updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); + } - updateFieldFromType(lhs.getTree(), element, fieldName, rhsATM); + AnnotatedDeclaredType argADT = overriddenMethod.getReceiverType(); + if (argADT != null) { + AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(methodTree).getReceiverType(); + if (paramATM != null) { + T receiver = storage.getReceiverAnnotations(methodElt, paramATM, atypeFactory); + updateAnnotationSet(receiver, TypeUseLocation.RECEIVER, argADT, paramATM, file); + } + } + } + + @Override + public void updateFromFormalParameterAssignment( + LocalVariableNode lhs, Node rhs, VariableElement paramElt) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(lhs.getElement())) { + return; } - @Override - public void updateFieldFromType( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { + Tree rhsTree = rhs.getTree(); + if (rhsTree == null) { + // TODO: Handle variable-length list as parameter. + // An ArrayCreationNode with a null tree is created when the + // parameter is a variable-length list. We are ignoring it for now. + // See Issue 682: https://github.com/typetools/checker-framework/issues/682 + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Could not update from formal parameter " + + "assignment, because an ArrayCreationNode with a null tree is created when " + + "the parameter is a variable-length list.\nParameter: " + + paramElt); + } + return; + } - if (ignoreFieldInWPI(element, fieldName)) { - return; - } + ExecutableElement methodElt = (ExecutableElement) paramElt.getEnclosingElement(); + + int index_1based = methodElt.getParameters().indexOf(paramElt) + 1; + if (index_1based == 0) { + // When paramElt is the parameter of a lambda contained in another + // method body, the enclosing element is the outer method body + // rather than the lambda itself (which has no element). WPI + // does not support inferring types for lambda parameters, so + // ignore it. + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Could not update from formal " + + "parameter assignment inside a lambda expression, because lambda parameters " + + "cannot be annotated.\nParameter: " + + paramElt); + } + return; + } - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(element)) { - return; - } + AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(paramElt); + AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(rhsTree); + atypeFactory.wpiAdjustForUpdateNonField(argATM); + T paramAnnotations = + storage.getParameterAnnotations(methodElt, index_1based, paramATM, paramElt, atypeFactory); + String file = storage.getFileForElement(methodElt); + updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); + } + + @Override + public void updateFromFieldAssignment(Node lhs, Node rhs) { + + Element element; + String fieldName; + if (lhs instanceof FieldAccessNode) { + element = ((FieldAccessNode) lhs).getElement(); + fieldName = ((FieldAccessNode) lhs).getFieldName(); + } else if (lhs instanceof LocalVariableNode) { + element = ((LocalVariableNode) lhs).getElement(); + fieldName = ((LocalVariableNode) lhs).getName(); + } else { + throw new BugInCF( + "updateFromFieldAssignment received an unexpected node type: " + lhs.getClass()); + } - String file = storage.getFileForElement(element); + // TODO: For a primitive such as long, this is yielding just @GuardedBy rather than + // @GuardedBy({}). + AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(rhs.getTree()); + atypeFactory.wpiAdjustForUpdateField(lhs.getTree(), element, fieldName, rhsATM); - AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(lhsTree); - T fieldAnnotations = storage.getFieldAnnotations(element, fieldName, lhsATM, atypeFactory); + updateFieldFromType(lhs.getTree(), element, fieldName, rhsATM); + } - if (fieldAnnotations == null) { - return; - } + @Override + public void updateFieldFromType( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { - updateAnnotationSet(fieldAnnotations, TypeUseLocation.FIELD, rhsATM, lhsATM, file); + if (ignoreFieldInWPI(element, fieldName)) { + return; } - /** - * Returns true if an assignment to the given field should be ignored by WPI. - * - * @param element the field's element - * @param fieldName the field's name - * @return true if an assignment to the given field should be ignored by WPI - */ - protected boolean ignoreFieldInWPI(Element element, String fieldName) { - // Do not attempt to infer types for fields that do not have valid names. For example, - // compiler-generated temporary variables will have invalid names. Recording facts about - // fields with invalid names causes jaif-based WPI to crash when reading the .jaif file, - // and stub-based WPI to generate unparsable stub files. See - // https://github.com/typetools/checker-framework/issues/3442 - if (!SourceVersion.isIdentifier(fieldName)) { - return true; - } + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(element)) { + return; + } - // Don't infer types if the inferred field has a declaration annotation with the - // @IgnoreInWholeProgramInference meta-annotation. - if (atypeFactory.getDeclAnnotation(element, IgnoreInWholeProgramInference.class) != null - || atypeFactory - .getDeclAnnotationWithMetaAnnotation( - element, IgnoreInWholeProgramInference.class) - .size() - > 0) { - return true; - } + String file = storage.getFileForElement(element); - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(element)) { - return true; - } + AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(lhsTree); + T fieldAnnotations = storage.getFieldAnnotations(element, fieldName, lhsATM, atypeFactory); - return false; + if (fieldAnnotations == null) { + return; } - @Override - public void updateFromReturn( - ReturnNode retNode, - ClassSymbol classSymbol, - MethodTree methodDeclTree, - Map overriddenMethods) { - // Don't infer types for code that isn't presented as source. - if (methodDeclTree == null - || !ElementUtils.isElementFromSourceCode( - TreeUtils.elementFromDeclaration(methodDeclTree))) { - return; - } - - // Whole-program inference ignores some locations. See Issue 682: - // https://github.com/typetools/checker-framework/issues/682 - if (classSymbol == null) { // TODO: Handle anonymous classes. - return; - } + updateAnnotationSet(fieldAnnotations, TypeUseLocation.FIELD, rhsATM, lhsATM, file); + } + + /** + * Returns true if an assignment to the given field should be ignored by WPI. + * + * @param element the field's element + * @param fieldName the field's name + * @return true if an assignment to the given field should be ignored by WPI + */ + protected boolean ignoreFieldInWPI(Element element, String fieldName) { + // Do not attempt to infer types for fields that do not have valid names. For example, + // compiler-generated temporary variables will have invalid names. Recording facts about + // fields with invalid names causes jaif-based WPI to crash when reading the .jaif file, + // and stub-based WPI to generate unparsable stub files. See + // https://github.com/typetools/checker-framework/issues/3442 + if (!SourceVersion.isIdentifier(fieldName)) { + return true; + } - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(methodDeclTree); - String file = storage.getFileForElement(methodElt); + // Don't infer types if the inferred field has a declaration annotation with the + // @IgnoreInWholeProgramInference meta-annotation. + if (atypeFactory.getDeclAnnotation(element, IgnoreInWholeProgramInference.class) != null + || atypeFactory + .getDeclAnnotationWithMetaAnnotation(element, IgnoreInWholeProgramInference.class) + .size() + > 0) { + return true; + } - AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(methodDeclTree).getReturnType(); - // Type of the expression returned - AnnotatedTypeMirror rhsATM = - atypeFactory.getAnnotatedType(retNode.getTree().getExpression()); - atypeFactory.wpiAdjustForUpdateNonField(rhsATM); - DependentTypesHelper dependentTypesHelper = - ((GenericAnnotatedTypeFactory) atypeFactory).getDependentTypesHelper(); - dependentTypesHelper.delocalize(rhsATM, methodDeclTree); - T returnTypeAnnos = storage.getReturnAnnotations(methodElt, lhsATM, atypeFactory); - updateAnnotationSet(returnTypeAnnos, TypeUseLocation.RETURN, rhsATM, lhsATM, file); - - // Now, update return types of overridden methods based on the implementation we just saw. - // This inference is similar to the inference procedure for method parameters: both are - // updated based only on the implementations (in this case) or call-sites (for method - // parameters) that are available to WPI. - // - // An alternative implementation would be to: - // * update only the method (not overridden methods) - // * when finished, propagate the final result to overridden methods - // - for (Map.Entry pair : - overriddenMethods.entrySet()) { - ExecutableElement overriddenMethodElement = pair.getValue(); - - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(overriddenMethodElement)) { - continue; - } + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(element)) { + return true; + } - AnnotatedExecutableType overriddenMethod = - atypeFactory.getAnnotatedType(overriddenMethodElement); - String superClassFile = storage.getFileForElement(overriddenMethodElement); - AnnotatedTypeMirror overriddenMethodReturnType = overriddenMethod.getReturnType(); - T storedOverriddenMethodReturnTypeAnnotations = - storage.getReturnAnnotations( - overriddenMethodElement, overriddenMethodReturnType, atypeFactory); - - updateAnnotationSet( - storedOverriddenMethodReturnTypeAnnotations, - TypeUseLocation.RETURN, - rhsATM, - overriddenMethodReturnType, - superClassFile); - } + return false; + } + + @Override + public void updateFromReturn( + ReturnNode retNode, + ClassSymbol classSymbol, + MethodTree methodDeclTree, + Map overriddenMethods) { + // Don't infer types for code that isn't presented as source. + if (methodDeclTree == null + || !ElementUtils.isElementFromSourceCode( + TreeUtils.elementFromDeclaration(methodDeclTree))) { + return; } - @Override - public void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno) { - this.addMethodDeclarationAnnotation(methodElt, anno, false); + // Whole-program inference ignores some locations. See Issue 682: + // https://github.com/typetools/checker-framework/issues/682 + if (classSymbol == null) { // TODO: Handle anonymous classes. + return; } - @Override - public void addMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno, boolean lubPurity) { + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(methodDeclTree); + String file = storage.getFileForElement(methodElt); + + AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(methodDeclTree).getReturnType(); + // Type of the expression returned + AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(retNode.getTree().getExpression()); + atypeFactory.wpiAdjustForUpdateNonField(rhsATM); + DependentTypesHelper dependentTypesHelper = + ((GenericAnnotatedTypeFactory) atypeFactory).getDependentTypesHelper(); + dependentTypesHelper.delocalize(rhsATM, methodDeclTree); + T returnTypeAnnos = storage.getReturnAnnotations(methodElt, lhsATM, atypeFactory); + updateAnnotationSet(returnTypeAnnos, TypeUseLocation.RETURN, rhsATM, lhsATM, file); + + // Now, update return types of overridden methods based on the implementation we just saw. + // This inference is similar to the inference procedure for method parameters: both are + // updated based only on the implementations (in this case) or call-sites (for method + // parameters) that are available to WPI. + // + // An alternative implementation would be to: + // * update only the method (not overridden methods) + // * when finished, propagate the final result to overridden methods + // + for (Map.Entry pair : overriddenMethods.entrySet()) { + ExecutableElement overriddenMethodElement = pair.getValue(); + + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(overriddenMethodElement)) { + continue; + } + + AnnotatedExecutableType overriddenMethod = + atypeFactory.getAnnotatedType(overriddenMethodElement); + String superClassFile = storage.getFileForElement(overriddenMethodElement); + AnnotatedTypeMirror overriddenMethodReturnType = overriddenMethod.getReturnType(); + T storedOverriddenMethodReturnTypeAnnotations = + storage.getReturnAnnotations( + overriddenMethodElement, overriddenMethodReturnType, atypeFactory); + + updateAnnotationSet( + storedOverriddenMethodReturnTypeAnnotations, + TypeUseLocation.RETURN, + rhsATM, + overriddenMethodReturnType, + superClassFile); + } + } - // Do not infer types for library code, only for type-checked source code. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } + @Override + public void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno) { + this.addMethodDeclarationAnnotation(methodElt, anno, false); + } - // Special-case handling for purity annotations. - AnnotationMirror annoToAdd; - if (!(lubPurity && isPurityAnno(anno))) { - annoToAdd = anno; - } else { - // It's a purity annotation and `lubPurity` is true. Do a "least upper bound" between - // the current purity annotation inferred for the method and anno. This is necessary to - // avoid WPI inferring incompatible purity annotations on methods that override methods - // from their superclass. TODO: this would be unnecessary if purity was implemented as a - // type system. - AnnotationMirror currentPurityAnno = getPurityAnnotation(methodElt); - if (currentPurityAnno == null) { - annoToAdd = anno; - } else { - // Clear the current purity annotation, because at this point a new one is - // definitely going to be inferred. - storage.removeMethodDeclarationAnnotation(methodElt, currentPurityAnno); - annoToAdd = lubPurityAnnotations(anno, currentPurityAnno); - } - } + @Override + public void addMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno, boolean lubPurity) { - String file = storage.getFileForElement(methodElt); - boolean isNewAnnotation = storage.addMethodDeclarationAnnotation(methodElt, annoToAdd); - if (isNewAnnotation) { - storage.setFileModified(file); - } + // Do not infer types for library code, only for type-checked source code. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; } - /** - * Computes a "least upper bound" between two purity annotations (an annotation is a purity - * annotation if and only if {@link #isPurityAnno(AnnotationMirror)} returns true). In the - * "lattice", Impure is the top, SideEffectFree and Deterministic are siblings below it, and - * Pure is the bottom, below them. Note that this routine is "fail-safe": Impure is returned if - * either of the input annotations is not actually a purity annotation. - * - * @param anno1 a purity annotation - * @param anno2 another purity annotation - * @return the "least upper bound" between anno1 and anno2, as described above - */ - private AnnotationMirror lubPurityAnnotations(AnnotationMirror anno1, AnnotationMirror anno2) { - // TODO: is this the best way to do this? Would it be easier to just write a real subtype - // routine for purity? Do we have code to handle this already somewhere? - - boolean anno1IsDet = - AnnotationUtils.areSameByName(anno1, PURE_NAME) - || AnnotationUtils.areSameByName(anno1, DETERMINISTIC_NAME); - boolean anno1IsSEF = - AnnotationUtils.areSameByName(anno1, PURE_NAME) - || AnnotationUtils.areSameByName(anno1, SIDE_EFFECT_FREE_NAME); - - boolean anno2IsDet = - AnnotationUtils.areSameByName(anno2, PURE_NAME) - || AnnotationUtils.areSameByName(anno2, DETERMINISTIC_NAME); - boolean anno2IsSEF = - AnnotationUtils.areSameByName(anno2, PURE_NAME) - || AnnotationUtils.areSameByName(anno2, SIDE_EFFECT_FREE_NAME); - - if (anno2IsSEF && anno2IsDet && anno1IsSEF && anno1IsDet) { - return PURE; - } else if (anno2IsSEF && anno1IsSEF) { - return SIDE_EFFECT_FREE; - } else if (anno2IsDet && anno1IsDet) { - return DETERMINISTIC; - } else { - return IMPURE; - } + // Special-case handling for purity annotations. + AnnotationMirror annoToAdd; + if (!(lubPurity && isPurityAnno(anno))) { + annoToAdd = anno; + } else { + // It's a purity annotation and `lubPurity` is true. Do a "least upper bound" between + // the current purity annotation inferred for the method and anno. This is necessary to + // avoid WPI inferring incompatible purity annotations on methods that override methods + // from their superclass. TODO: this would be unnecessary if purity was implemented as a + // type system. + AnnotationMirror currentPurityAnno = getPurityAnnotation(methodElt); + if (currentPurityAnno == null) { + annoToAdd = anno; + } else { + // Clear the current purity annotation, because at this point a new one is + // definitely going to be inferred. + storage.removeMethodDeclarationAnnotation(methodElt, currentPurityAnno); + annoToAdd = lubPurityAnnotations(anno, currentPurityAnno); + } } - /** - * Returns the purity annotation ({@link Pure}, {@link SideEffectFree}, {@link Deterministic}, - * or {@link Impure}) currently associated with the given executable element in this round of - * inference, if there is one. Invariant: no more than one purity annotation should ever be - * present on a given executable element at a time. - * - * @param methodElt a method element - * @return the purity annotation, or null if none has yet been inferred - */ - private @Nullable AnnotationMirror getPurityAnnotation(ExecutableElement methodElt) { - AnnotationMirrorSet declAnnos = storage.getMethodDeclarationAnnotations(methodElt); - if (declAnnos.isEmpty()) { - return null; - } - for (AnnotationMirror declAnno : declAnnos) { - if (isPurityAnno(declAnno)) { - return declAnno; - } - } - return null; + String file = storage.getFileForElement(methodElt); + boolean isNewAnnotation = storage.addMethodDeclarationAnnotation(methodElt, annoToAdd); + if (isNewAnnotation) { + storage.setFileModified(file); } - - /** - * Returns true if the given annotation is {@link Pure}, {@link SideEffectFree}, {@link - * Deterministic}, or {@link Impure}. Returns false otherwise. - * - * @param anno an annotation - * @return true iff the annotation is a purity annotation - */ - private boolean isPurityAnno(AnnotationMirror anno) { - return AnnotationUtils.areSameByName(anno, PURE_NAME) - || AnnotationUtils.areSameByName(anno, SIDE_EFFECT_FREE_NAME) - || AnnotationUtils.areSameByName(anno, DETERMINISTIC_NAME) - || AnnotationUtils.areSameByName(anno, IMPURE_NAME); + } + + /** + * Computes a "least upper bound" between two purity annotations (an annotation is a purity + * annotation if and only if {@link #isPurityAnno(AnnotationMirror)} returns true). In the + * "lattice", Impure is the top, SideEffectFree and Deterministic are siblings below it, and Pure + * is the bottom, below them. Note that this routine is "fail-safe": Impure is returned if either + * of the input annotations is not actually a purity annotation. + * + * @param anno1 a purity annotation + * @param anno2 another purity annotation + * @return the "least upper bound" between anno1 and anno2, as described above + */ + private AnnotationMirror lubPurityAnnotations(AnnotationMirror anno1, AnnotationMirror anno2) { + // TODO: is this the best way to do this? Would it be easier to just write a real subtype + // routine for purity? Do we have code to handle this already somewhere? + + boolean anno1IsDet = + AnnotationUtils.areSameByName(anno1, PURE_NAME) + || AnnotationUtils.areSameByName(anno1, DETERMINISTIC_NAME); + boolean anno1IsSEF = + AnnotationUtils.areSameByName(anno1, PURE_NAME) + || AnnotationUtils.areSameByName(anno1, SIDE_EFFECT_FREE_NAME); + + boolean anno2IsDet = + AnnotationUtils.areSameByName(anno2, PURE_NAME) + || AnnotationUtils.areSameByName(anno2, DETERMINISTIC_NAME); + boolean anno2IsSEF = + AnnotationUtils.areSameByName(anno2, PURE_NAME) + || AnnotationUtils.areSameByName(anno2, SIDE_EFFECT_FREE_NAME); + + if (anno2IsSEF && anno2IsDet && anno1IsSEF && anno1IsDet) { + return PURE; + } else if (anno2IsSEF && anno1IsSEF) { + return SIDE_EFFECT_FREE; + } else if (anno2IsDet && anno1IsDet) { + return DETERMINISTIC; + } else { + return IMPURE; } - - @Override - public void addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(field)) { - return; - } - - String file = storage.getFileForElement(field); - boolean isNewAnnotation = storage.addFieldDeclarationAnnotation(field, anno); - if (isNewAnnotation) { - storage.setFileModified(file); - } + } + + /** + * Returns the purity annotation ({@link Pure}, {@link SideEffectFree}, {@link Deterministic}, or + * {@link Impure}) currently associated with the given executable element in this round of + * inference, if there is one. Invariant: no more than one purity annotation should ever be + * present on a given executable element at a time. + * + * @param methodElt a method element + * @return the purity annotation, or null if none has yet been inferred + */ + private @Nullable AnnotationMirror getPurityAnnotation(ExecutableElement methodElt) { + AnnotationMirrorSet declAnnos = storage.getMethodDeclarationAnnotations(methodElt); + if (declAnnos.isEmpty()) { + return null; } - - @Override - public void addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } - - String file = storage.getFileForElement(methodElt); - boolean isNewAnnotation = - storage.addDeclarationAnnotationToFormalParameter(methodElt, index_1based, anno); - if (isNewAnnotation) { - storage.setFileModified(file); - } + for (AnnotationMirror declAnno : declAnnos) { + if (isPurityAnno(declAnno)) { + return declAnno; + } } - - @Override - public void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(classElt)) { - return; - } - - String file = storage.getFileForElement(classElt); - boolean isNewAnnotation = storage.addClassDeclarationAnnotation(classElt, anno); - if (isNewAnnotation) { - storage.setFileModified(file); - } + return null; + } + + /** + * Returns true if the given annotation is {@link Pure}, {@link SideEffectFree}, {@link + * Deterministic}, or {@link Impure}. Returns false otherwise. + * + * @param anno an annotation + * @return true iff the annotation is a purity annotation + */ + private boolean isPurityAnno(AnnotationMirror anno) { + return AnnotationUtils.areSameByName(anno, PURE_NAME) + || AnnotationUtils.areSameByName(anno, SIDE_EFFECT_FREE_NAME) + || AnnotationUtils.areSameByName(anno, DETERMINISTIC_NAME) + || AnnotationUtils.areSameByName(anno, IMPURE_NAME); + } + + @Override + public void addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(field)) { + return; } - /** - * Updates the set of annotations in a location in a program. - * - *

    - *
  • If there was no previous annotation for that location, then the updated set will be the - * annotations in rhsATM. - *
  • If there was a previous annotation, the updated set will be the LUB between the - * previous annotation and rhsATM. - *
- * - *

Subclasses can customize this behavior. - * - * @param annotationsToUpdate the type whose annotations are modified by this method - * @param defLoc the location where the annotation will be added - * @param rhsATM the RHS of the annotated type on the source code - * @param lhsATM the LHS of the annotated type on the source code - * @param file the annotation file containing the executable; used for marking the scene as - * modified (needing to be written to disk) - */ - protected void updateAnnotationSet( - T annotationsToUpdate, - TypeUseLocation defLoc, - AnnotatedTypeMirror rhsATM, - AnnotatedTypeMirror lhsATM, - String file) { - updateAnnotationSet(annotationsToUpdate, defLoc, rhsATM, lhsATM, file, true); + String file = storage.getFileForElement(field); + boolean isNewAnnotation = storage.addFieldDeclarationAnnotation(field, anno); + if (isNewAnnotation) { + storage.setFileModified(file); } - - /** - * Updates the set of annotations in a location in a program. - * - *

    - *
  • If there was no previous annotation for that location, then the updated set will be the - * annotations in rhsATM. - *
  • If there was a previous annotation, the updated set will be the LUB between the - * previous annotation and rhsATM. - *
- * - *

Subclasses can customize this behavior. - * - * @param annotationsToUpdate the type whose annotations are modified by this method - * @param defLoc the location where the annotation will be added - * @param rhsATM the RHS of the annotated type on the source code - * @param lhsATM the LHS of the annotated type on the source code - * @param file annotation file containing the executable; used for marking the scene as modified - * (needing to be written to disk) - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - protected void updateAnnotationSet( - T annotationsToUpdate, - TypeUseLocation defLoc, - AnnotatedTypeMirror rhsATM, - AnnotatedTypeMirror lhsATM, - String file, - boolean ignoreIfAnnotated) { - if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { - return; - } - - AnnotatedTypeMirror atmFromStorage = - storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); - updateAtmWithLub(rhsATM, atmFromStorage); - - // For type variables, infer primary annotations for field type use locations, but - // for other locations only infer primary annotations if they are a super type of the upper - // bound of declaration of the type variable. - if (defLoc != TypeUseLocation.FIELD && lhsATM instanceof AnnotatedTypeVariable) { - AnnotatedTypeVariable lhsTV = (AnnotatedTypeVariable) lhsATM; - AnnotatedTypeMirror decl = - atypeFactory.getAnnotatedType(lhsTV.getUnderlyingType().asElement()); - AnnotationMirrorSet upperAnnos = decl.getEffectiveAnnotations(); - - // If the inferred type is a subtype of the upper bounds of the - // current type in the source code, do nothing. - TypeMirror rhsTM = rhsATM.getUnderlyingType(); - TypeMirror declTM = decl.getUnderlyingType(); - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - for (AnnotationMirror anno : rhsATM.getAnnotations()) { - AnnotationMirror upperAnno = - qualHierarchy.findAnnotationInSameHierarchy(upperAnnos, anno); - if (qualHierarchy.isSubtypeShallow(anno, rhsTM, upperAnno, declTM)) { - rhsATM.removeAnnotation(anno); - } - } - if (rhsATM.getAnnotations().isEmpty()) { - return; - } - } - storage.updateStorageLocationFromAtm( - rhsATM, lhsATM, annotationsToUpdate, defLoc, ignoreIfAnnotated); - storage.setFileModified(file); + } + + @Override + public void addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; } - /** - * Prints a debugging message about a failed inference. Must only be called after {@link - * #showWpiFailedInferences} has been checked, to avoid constructing the debugging message - * eagerly. - * - * @param reason a message describing the reason an inference was unsuccessful, which will be - * displayed to the user - */ - private void printFailedInferenceDebugMessage(String reason) { - assert showWpiFailedInferences; - // TODO: it would be nice if this message also included a line number - // for the file being analyzed, but I don't know how to get that information - // here, given that this message is called from places where only the annotated - // type mirrors for the LHS and RHS of some pseduo-assignment are available. - System.out.println("WPI failed to make an inference: " + reason); + String file = storage.getFileForElement(methodElt); + boolean isNewAnnotation = + storage.addDeclarationAnnotationToFormalParameter(methodElt, index_1based, anno); + if (isNewAnnotation) { + storage.setFileModified(file); } + } - @Override - public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) { - - if (sourceCodeATM.getKind() != ajavaATM.getKind()) { - // Ignore null types: passing them to asSuper causes a crash, as they cannot be - // substituted for type variables. If sourceCodeATM is a null type, only the primary - // annotation will be considered anyway, so there is no danger of recursing into - // typevar bounds. - if (sourceCodeATM.getKind() != TypeKind.NULL) { - // This can happen e.g. when recursing into the bounds of a type variable: - // the bound on sourceCodeATM might be a declared type (such as T), while - // the ajavaATM might be a typevar (such as S extends T), or vice-versa. In - // that case, use asSuper to make the two ATMs fully-compatible. - sourceCodeATM = AnnotatedTypes.asSuper(this.atypeFactory, sourceCodeATM, ajavaATM); - } - } + @Override + public void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(classElt)) { + return; + } - switch (sourceCodeATM.getKind()) { - case TYPEVAR: - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), - ((AnnotatedTypeVariable) ajavaATM).getLowerBound()); - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), - ((AnnotatedTypeVariable) ajavaATM).getUpperBound()); - break; - case WILDCARD: - break; - // throw new BugInCF("This can't happen"); - // TODO: This comment is wrong: the wildcard case does get entered. - // Because inferring type arguments is not supported, wildcards won't be - // encountered. - // updateATMWithLUB( - // atf, - // ((AnnotatedWildcardType) sourceCodeATM).getExtendsBound(), - // ((AnnotatedWildcardType) ajavaATM).getExtendsBound()); - // updateATMWithLUB( - // atf, - // ((AnnotatedWildcardType) sourceCodeATM).getSuperBound(), - // ((AnnotatedWildcardType) ajavaATM).getSuperBound()); - // break; - case ARRAY: - AnnotatedTypeMirror sourceCodeComponent = - ((AnnotatedArrayType) sourceCodeATM).getComponentType(); - AnnotatedTypeMirror ajavaComponent = - ((AnnotatedArrayType) ajavaATM).getComponentType(); - if (sourceCodeComponent.getKind() == ajavaComponent.getKind()) { - updateAtmWithLub(sourceCodeComponent, ajavaComponent); - } else { - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "attempted to update the component type of an array type, but found an unexpected" - + " difference in type structure.\n" - + "LHS kind: " - + sourceCodeComponent.getKind() - + "\nRHS kind: " - + ajavaComponent.getKind()); - break; - } - } - break; - // case DECLARED: - // Inferring annotations on type arguments is not supported, so no need to recur on - // generic types. If this was ever implemented, this method would need a - // VisitHistory object to prevent infinite recursion on types such as T extends - // List. - default: - // ATM only has primary annotations - break; - } + String file = storage.getFileForElement(classElt); + boolean isNewAnnotation = storage.addClassDeclarationAnnotation(classElt, anno); + if (isNewAnnotation) { + storage.setFileModified(file); + } + } + + /** + * Updates the set of annotations in a location in a program. + * + *

    + *
  • If there was no previous annotation for that location, then the updated set will be the + * annotations in rhsATM. + *
  • If there was a previous annotation, the updated set will be the LUB between the previous + * annotation and rhsATM. + *
+ * + *

Subclasses can customize this behavior. + * + * @param annotationsToUpdate the type whose annotations are modified by this method + * @param defLoc the location where the annotation will be added + * @param rhsATM the RHS of the annotated type on the source code + * @param lhsATM the LHS of the annotated type on the source code + * @param file the annotation file containing the executable; used for marking the scene as + * modified (needing to be written to disk) + */ + protected void updateAnnotationSet( + T annotationsToUpdate, + TypeUseLocation defLoc, + AnnotatedTypeMirror rhsATM, + AnnotatedTypeMirror lhsATM, + String file) { + updateAnnotationSet(annotationsToUpdate, defLoc, rhsATM, lhsATM, file, true); + } + + /** + * Updates the set of annotations in a location in a program. + * + *

    + *
  • If there was no previous annotation for that location, then the updated set will be the + * annotations in rhsATM. + *
  • If there was a previous annotation, the updated set will be the LUB between the previous + * annotation and rhsATM. + *
+ * + *

Subclasses can customize this behavior. + * + * @param annotationsToUpdate the type whose annotations are modified by this method + * @param defLoc the location where the annotation will be added + * @param rhsATM the RHS of the annotated type on the source code + * @param lhsATM the LHS of the annotated type on the source code + * @param file annotation file containing the executable; used for marking the scene as modified + * (needing to be written to disk) + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + protected void updateAnnotationSet( + T annotationsToUpdate, + TypeUseLocation defLoc, + AnnotatedTypeMirror rhsATM, + AnnotatedTypeMirror lhsATM, + String file, + boolean ignoreIfAnnotated) { + if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { + return; + } - // LUB primary annotations - AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); - for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { - AnnotationMirror amAjava = ajavaATM.getAnnotationInHierarchy(amSource); - // amAjava only contains annotations from the ajava file, so it might be missing - // an annotation in the hierarchy. - if (amAjava != null) { - amSource = - atypeFactory - .getQualifierHierarchy() - .leastUpperBoundShallow( - amSource, - sourceCodeATM.getUnderlyingType(), - amAjava, - ajavaATM.getUnderlyingType()); - } - annosToReplace.add(amSource); + AnnotatedTypeMirror atmFromStorage = + storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); + updateAtmWithLub(rhsATM, atmFromStorage); + + // For type variables, infer primary annotations for field type use locations, but + // for other locations only infer primary annotations if they are a super type of the upper + // bound of declaration of the type variable. + if (defLoc != TypeUseLocation.FIELD && lhsATM instanceof AnnotatedTypeVariable) { + AnnotatedTypeVariable lhsTV = (AnnotatedTypeVariable) lhsATM; + AnnotatedTypeMirror decl = + atypeFactory.getAnnotatedType(lhsTV.getUnderlyingType().asElement()); + AnnotationMirrorSet upperAnnos = decl.getEffectiveAnnotations(); + + // If the inferred type is a subtype of the upper bounds of the + // current type in the source code, do nothing. + TypeMirror rhsTM = rhsATM.getUnderlyingType(); + TypeMirror declTM = decl.getUnderlyingType(); + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + for (AnnotationMirror anno : rhsATM.getAnnotations()) { + AnnotationMirror upperAnno = qualHierarchy.findAnnotationInSameHierarchy(upperAnnos, anno); + if (qualHierarchy.isSubtypeShallow(anno, rhsTM, upperAnno, declTM)) { + rhsATM.removeAnnotation(anno); } - sourceCodeATM.replaceAnnotations(annosToReplace); + } + if (rhsATM.getAnnotations().isEmpty()) { + return; + } + } + storage.updateStorageLocationFromAtm( + rhsATM, lhsATM, annotationsToUpdate, defLoc, ignoreIfAnnotated); + storage.setFileModified(file); + } + + /** + * Prints a debugging message about a failed inference. Must only be called after {@link + * #showWpiFailedInferences} has been checked, to avoid constructing the debugging message + * eagerly. + * + * @param reason a message describing the reason an inference was unsuccessful, which will be + * displayed to the user + */ + private void printFailedInferenceDebugMessage(String reason) { + assert showWpiFailedInferences; + // TODO: it would be nice if this message also included a line number + // for the file being analyzed, but I don't know how to get that information + // here, given that this message is called from places where only the annotated + // type mirrors for the LHS and RHS of some pseduo-assignment are available. + System.out.println("WPI failed to make an inference: " + reason); + } + + @Override + public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) { + + if (sourceCodeATM.getKind() != ajavaATM.getKind()) { + // Ignore null types: passing them to asSuper causes a crash, as they cannot be + // substituted for type variables. If sourceCodeATM is a null type, only the primary + // annotation will be considered anyway, so there is no danger of recursing into + // typevar bounds. + if (sourceCodeATM.getKind() != TypeKind.NULL) { + // This can happen e.g. when recursing into the bounds of a type variable: + // the bound on sourceCodeATM might be a declared type (such as T), while + // the ajavaATM might be a typevar (such as S extends T), or vice-versa. In + // that case, use asSuper to make the two ATMs fully-compatible. + sourceCodeATM = AnnotatedTypes.asSuper(this.atypeFactory, sourceCodeATM, ajavaATM); + } } - @Override - public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { - storage.writeResultsToFile(outputFormat, checker); + switch (sourceCodeATM.getKind()) { + case TYPEVAR: + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), + ((AnnotatedTypeVariable) ajavaATM).getLowerBound()); + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), + ((AnnotatedTypeVariable) ajavaATM).getUpperBound()); + break; + case WILDCARD: + break; + // throw new BugInCF("This can't happen"); + // TODO: This comment is wrong: the wildcard case does get entered. + // Because inferring type arguments is not supported, wildcards won't be + // encountered. + // updateATMWithLUB( + // atf, + // ((AnnotatedWildcardType) sourceCodeATM).getExtendsBound(), + // ((AnnotatedWildcardType) ajavaATM).getExtendsBound()); + // updateATMWithLUB( + // atf, + // ((AnnotatedWildcardType) sourceCodeATM).getSuperBound(), + // ((AnnotatedWildcardType) ajavaATM).getSuperBound()); + // break; + case ARRAY: + AnnotatedTypeMirror sourceCodeComponent = + ((AnnotatedArrayType) sourceCodeATM).getComponentType(); + AnnotatedTypeMirror ajavaComponent = ((AnnotatedArrayType) ajavaATM).getComponentType(); + if (sourceCodeComponent.getKind() == ajavaComponent.getKind()) { + updateAtmWithLub(sourceCodeComponent, ajavaComponent); + } else { + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "attempted to update the component type of an array type, but found an unexpected" + + " difference in type structure.\n" + + "LHS kind: " + + sourceCodeComponent.getKind() + + "\nRHS kind: " + + ajavaComponent.getKind()); + break; + } + } + break; + // case DECLARED: + // Inferring annotations on type arguments is not supported, so no need to recur on + // generic types. If this was ever implemented, this method would need a + // VisitHistory object to prevent infinite recursion on types such as T extends + // List. + default: + // ATM only has primary annotations + break; } - @Override - public void preprocessClassTree(ClassTree classTree) { - storage.preprocessClassTree(classTree); + // LUB primary annotations + AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); + for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { + AnnotationMirror amAjava = ajavaATM.getAnnotationInHierarchy(amSource); + // amAjava only contains annotations from the ajava file, so it might be missing + // an annotation in the hierarchy. + if (amAjava != null) { + amSource = + atypeFactory + .getQualifierHierarchy() + .leastUpperBoundShallow( + amSource, + sourceCodeATM.getUnderlyingType(), + amAjava, + ajavaATM.getUnderlyingType()); + } + annosToReplace.add(amSource); } + sourceCodeATM.replaceAnnotations(annosToReplace); + } + + @Override + public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { + storage.writeResultsToFile(outputFormat, checker); + } + + @Override + public void preprocessClassTree(ClassTree classTree) { + storage.preprocessClassTree(classTree); + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index 77d3baa422d..82b9bc1824a 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -36,37 +36,6 @@ import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; - -import org.checkerframework.afu.scenelib.util.JVMNames; -import org.checkerframework.checker.index.qual.Positive; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.common.wholeprograminference.WholeProgramInference.OutputFormat; -import org.checkerframework.dataflow.analysis.Analysis; -import org.checkerframework.framework.ajava.AnnotationMirrorToAnnotationExprConversion; -import org.checkerframework.framework.ajava.AnnotationTransferVisitor; -import org.checkerframework.framework.ajava.DefaultJointVisitor; -import org.checkerframework.framework.ajava.JointJavacJavaParserVisitor; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.TypeUseLocation; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.util.JavaParserUtil; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypeSystemError; -import org.plumelib.util.ArraySet; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.DeepCopyable; -import org.plumelib.util.IPair; -import org.plumelib.util.UtilPlume; - import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; @@ -89,7 +58,6 @@ import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Collectors; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -99,6 +67,35 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; +import org.checkerframework.afu.scenelib.util.JVMNames; +import org.checkerframework.checker.index.qual.Positive; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.wholeprograminference.WholeProgramInference.OutputFormat; +import org.checkerframework.dataflow.analysis.Analysis; +import org.checkerframework.framework.ajava.AnnotationMirrorToAnnotationExprConversion; +import org.checkerframework.framework.ajava.AnnotationTransferVisitor; +import org.checkerframework.framework.ajava.DefaultJointVisitor; +import org.checkerframework.framework.ajava.JointJavacJavaParserVisitor; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.TypeUseLocation; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.util.JavaParserUtil; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypeSystemError; +import org.plumelib.util.ArraySet; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.DeepCopyable; +import org.plumelib.util.IPair; +import org.plumelib.util.UtilPlume; /** * This is an implementation of {@link WholeProgramInferenceStorage} that stores annotations @@ -106,1905 +103,1853 @@ * files. */ public class WholeProgramInferenceJavaParserStorage - implements WholeProgramInferenceStorage { + implements WholeProgramInferenceStorage { + + /** + * Directory where .ajava files will be written to and read from. This directory is relative to + * where the javac command is executed. + */ + public static final File AJAVA_FILES_PATH = new File("build", "whole-program-inference"); + + /** The type factory associated with this. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** The element utilities for {@code atypeFactory}. */ + protected final Elements elements; + + /** + * Maps from binary class name to the wrapper containing the class. Contains all classes in Java + * source files containing an Element for which an annotation has been inferred. + */ + private Map<@BinaryName String, ClassOrInterfaceAnnos> classToAnnos = new HashMap<>(); + + /** Maps from binary class name to binary names of all supertypes. */ + private Map<@BinaryName String, Set<@BinaryName String>> supertypesMap = new HashMap<>(); + + /** Maps from binary class name to binary names of all known subtypes. */ + private Map<@BinaryName String, Set<@BinaryName String>> subtypesMap = new HashMap<>(); + + /** + * Files containing classes for which an annotation has been inferred since the last time files + * were written to disk. + */ + private Set modifiedFiles = new HashSet<>(); + + /** Mapping from source file to the wrapper for the compilation unit parsed from that file. */ + private Map sourceToAnnos = new HashMap<>(); + + /** Maps from binary class name to the source file that contains it. */ + private Map classToSource = new HashMap<>(); + + /** Whether the {@code -AinferOutputOriginal} option was supplied to the checker. */ + private final boolean inferOutputOriginal; + + /** + * Returns the names of all qualifiers that are marked with {@link InvisibleQualifier}, and that + * are supported by the given type factory. + * + * @param atypeFactory a type factory + * @return the names of every invisible qualifier supported by {@code atypeFactory} + */ + public static Set getInvisibleQualifierNames(AnnotatedTypeFactory atypeFactory) { + return atypeFactory.getSupportedTypeQualifiers().stream() + .filter(WholeProgramInferenceJavaParserStorage::isInvisible) + .map(Class::getCanonicalName) + .collect(Collectors.toSet()); + } + + /** + * Is the definition of the given annotation class annotated with {@link InvisibleQualifier}? + * + * @param qual an annotation class + * @return true iff {@code qual} is meta-annotated with {@link InvisibleQualifier} + */ + public static boolean isInvisible(Class qual) { + return Arrays.stream(qual.getAnnotations()) + .anyMatch(anno -> anno.annotationType() == InvisibleQualifier.class); + } + + /** + * Constructs a new {@code WholeProgramInferenceJavaParser} that has not yet inferred any + * annotations. + * + * @param atypeFactory the associated type factory + * @param inferOutputOriginal whether the -AinferOutputOriginal option was supplied to the checker + */ + public WholeProgramInferenceJavaParserStorage( + AnnotatedTypeFactory atypeFactory, boolean inferOutputOriginal) { + this.atypeFactory = atypeFactory; + this.elements = atypeFactory.getElementUtils(); + this.inferOutputOriginal = inferOutputOriginal; + } + + @Override + public String getFileForElement(Element elt) { + return addClassesForElement(elt); + } + + @Override + public void setFileModified(String path) { + modifiedFiles.add(path); + } + + /** + * Set the source file as modified, for the given class. + * + * @param className the binary name of a class that should be written to disk + */ + private void setClassModified(@Nullable @BinaryName String className) { + if (className == null) { + return; + } + String path = classToSource.get(className); + if (path != null) { + setFileModified(path); + } + } + + /** + * Set the source files as modified, for all the given classes. + * + * @param classNames the binary names of classes that should be written to disk + */ + private void setClassesModified(@Nullable Collection<@BinaryName String> classNames) { + if (classNames == null) { + return; + } + for (String className : classNames) { + setClassModified(className); + } + } + + /** + * For every modified file, consider its subclasses and superclasses modified, too. The reason is + * that an annotation change in a class might require annotations in its superclasses and + * supclasses to be modified, in order to preserve behavioral subtyping. Setting it modified will + * cause it to be written out, and while writing out, the annotations will be made consistent + * across the class hierarchy by {@link #wpiPrepareCompilationUnitForWriting}. + */ + public void setSupertypesAndSubtypesModified() { + // Copy into a list to avoid a ConcurrentModificationException. + for (String path : new ArrayList<>(modifiedFiles)) { + CompilationUnitAnnos cuAnnos = sourceToAnnos.get(path); + for (ClassOrInterfaceAnnos classAnnos : cuAnnos.types) { + String className = classAnnos.className; + setClassesModified(supertypesMap.get(className)); + setClassesModified(subtypesMap.get(className)); + } + } + } + + /// + /// Reading stored annotations + /// + + @Override + public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { + return getMethodAnnos(methodElt) != null; + } + + @Override + public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement methodElt) { + String className = ElementUtils.getEnclosingClassName(methodElt); + // Read in classes for the element. + getFileForElement(methodElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return AnnotationMirrorSet.emptySet(); + } + CallableDeclarationAnnos methodAnnos = + classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); + if (methodAnnos == null) { + return AnnotationMirrorSet.emptySet(); + } + return methodAnnos.getDeclarationAnnotations(); + } + + /** + * Get the annotations for a method or constructor. + * + * @param methodElt the method or constructor + * @return the annotations for a method or constructor + */ + private @Nullable CallableDeclarationAnnos getMethodAnnos(ExecutableElement methodElt) { + String className = ElementUtils.getEnclosingClassName(methodElt); + // Read in classes for the element. + getFileForElement(methodElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return null; + } + CallableDeclarationAnnos methodAnnos = + classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); + return methodAnnos; + } + + /** + * Get the annotations for a field. + * + * @param fieldElt a field + * @return the annotations for a field + */ + private @Nullable FieldAnnos getFieldAnnos(VariableElement fieldElt) { + String className = ElementUtils.getEnclosingClassName(fieldElt); + // Read in classes for the element. + getFileForElement(fieldElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return null; + } + FieldAnnos fieldAnnos = classAnnos.fields.get(fieldElt.getSimpleName().toString()); + return fieldAnnos; + } + + @Override + public AnnotatedTypeMirror getParameterAnnotations( + ExecutableElement methodElt, + @Positive int index_1based, + AnnotatedTypeMirror paramATM, + VariableElement ve, + AnnotatedTypeFactory atypeFactory) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // When processing anonymous inner classes outside their compilation units, + // it might not have been possible to create an appropriate CallableDeclarationAnnos: + // no element would have been available, causing the computed method signature to + // be incorrect. In this case, abort looking up annotations -- inference will fail, + // because even if WPI inferred something, it couldn't be printed. + return paramATM; + } + return methodAnnos.getParameterTypeInitialized(paramATM, index_1based, atypeFactory); + } + + @Override + public AnnotatedTypeMirror getReceiverAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror paramATM, + AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return paramATM; + } + return methodAnnos.getReceiverType(paramATM, atypeFactory); + } + + @Override + public AnnotatedTypeMirror getReturnAnnotations( + ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return atm; + } + return methodAnnos.getReturnType(atm, atypeFactory); + } + + @Override + public @Nullable AnnotatedTypeMirror getFieldAnnotations( + Element element, + String fieldName, + AnnotatedTypeMirror lhsATM, + AnnotatedTypeFactory atypeFactory) { + ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); + // Read in classes for the element. + getFileForElement(element); + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @BinaryName String className = enclosingClass.flatname.toString(); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return null; + } + // If it's an enum constant it won't appear as a field + // and it won't have extra annotations, so just return the basic type: + if (classAnnos.enumConstants.contains(fieldName)) { + return lhsATM; + } else if (classAnnos.fields.get(fieldName) == null) { + // There might not be a corresponding entry for the field name + // in an anonymous class, if the field and class were defined in + // another compilation unit (for the same reason that a method + // might not have an entry, as in #getParameterAnnotations, above). + return null; + } else { + return classAnnos.fields.get(fieldName).getType(lhsATM, atypeFactory); + } + } + + @Override + public AnnotatedTypeMirror getPreOrPostconditions( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + switch (preOrPost) { + case BEFORE: + return getPreconditionsForExpression(methodElement, expression, declaredType, atypeFactory); + case AFTER: + return getPostconditionsForExpression( + methodElement, expression, declaredType, atypeFactory); + default: + throw new BugInCF("Unexpected " + preOrPost); + } + } + + /** + * Returns the precondition annotations for the given expression. + * + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @param atypeFactory the type factory + * @return the precondition annotations for a field + */ + private AnnotatedTypeMirror getPreconditionsForExpression( + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return declaredType; + } + return methodAnnos.getPreconditionsForExpression(expression, declaredType, atypeFactory); + } + + /** + * Returns the postcondition annotations for an expression. + * + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @param atypeFactory the type factory + * @return the postcondition annotations for a field + */ + private AnnotatedTypeMirror getPostconditionsForExpression( + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return declaredType; + } + return methodAnnos.getPostconditionsForExpression(expression, declaredType, atypeFactory); + } - /** - * Directory where .ajava files will be written to and read from. This directory is relative to - * where the javac command is executed. - */ - public static final File AJAVA_FILES_PATH = new File("build", "whole-program-inference"); + @Override + public boolean addMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno) { + + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = methodAnnos.addDeclarationAnnotation(anno); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(methodElt)); + } + return isNewAnnotation; + } + + @Override + public boolean removeMethodDeclarationAnnotation(ExecutableElement elt, AnnotationMirror anno) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(elt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + return methodAnnos.removeDeclarationAnnotation(anno); + } + + @Override + public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { + FieldAnnos fieldAnnos = getFieldAnnos(field); + if (fieldAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = fieldAnnos != null && fieldAnnos.addDeclarationAnnotation(anno); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(field)); + } + return isNewAnnotation; + } + + @Override + public boolean addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = + methodAnnos.addDeclarationAnnotationToFormalParameter(anno, index_1based); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(methodElt)); + } + return isNewAnnotation; + } + + @Override + public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { + String className = ElementUtils.getBinaryName(classElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = classAnnos.addAnnotationToClassDeclaration(anno); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(classElt)); + } + return isNewAnnotation; + } + + @Override + public AnnotatedTypeMirror atmFromStorageLocation( + TypeMirror typeMirror, AnnotatedTypeMirror storageLocation) { + if (typeMirror.getKind() == TypeKind.TYPEVAR) { + // Only copy the primary annotation, because we don't currently have + // support for inferring type bounds. This avoids accidentally substituting the + // use of the type variable for its declaration when inferring annotations on + // fields with a type variable as their type. + AnnotatedTypeMirror asExpectedType = + AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); + asExpectedType.replaceAnnotations(storageLocation.getAnnotations()); + return asExpectedType; + } else { + return storageLocation; + } + } + + @Override + public void updateStorageLocationFromAtm( + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + AnnotatedTypeMirror typeToUpdate, + TypeUseLocation defLoc, + boolean ignoreIfAnnotated) { + // Only update the AnnotatedTypeMirror if there are no explicit annotations + if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { + for (AnnotationMirror am : newATM.getAnnotations()) { + typeToUpdate.replaceAnnotation(am); + } + } else if (curATM.getKind() == TypeKind.TYPEVAR) { + // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly + // annotated. So instead, only insert the annotation if there is not primary annotation + // of the same hierarchy. + for (AnnotationMirror am : newATM.getAnnotations()) { + if (curATM.getAnnotationInHierarchy(am) != null) { + // Don't insert if the type is already has a primary annotation + // in the same hierarchy. + break; + } + typeToUpdate.replaceAnnotation(am); + } + } - /** The type factory associated with this. */ - protected final AnnotatedTypeFactory atypeFactory; + // Need to check both newATM and curATM, because one might be a declared type + // even if the other is an array: it is permitted to assign e.g., a String[] + // to a location with static type Object **and vice-versa** (if a cast is used). + if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; + AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; + AnnotatedArrayType aatToUpdate = (AnnotatedArrayType) typeToUpdate; + updateStorageLocationFromAtm( + newAAT.getComponentType(), + oldAAT.getComponentType(), + aatToUpdate.getComponentType(), + defLoc, + ignoreIfAnnotated); + } + } + + /// + /// Reading in files + /// + + @Override + public void preprocessClassTree(ClassTree classTree) { + addClassTree(classTree); + } + + /** + * Reads in the source file containing {@code tree} and creates wrappers around all classes in the + * file. Stores the wrapper for the compilation unit in {@link #sourceToAnnos} and stores the + * wrappers of all classes in the file in {@link #classToAnnos}. + * + * @param tree tree for class to add + */ + private void addClassTree(ClassTree tree) { + TypeElement element = TreeUtils.elementFromDeclaration(tree); + if (element == null) { + // TODO: There should be an element here, or there is nowhere to store inferences about + // `tree`. + return; + } + String className = ElementUtils.getBinaryName(element); + if (classToAnnos.containsKey(className)) { + return; + } - /** The element utilities for {@code atypeFactory}. */ - protected final Elements elements; + TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); + String path = ElementUtils.getSourceFilePath(toplevelClass); + addSourceFile(path); + CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); + TypeDeclaration javaParserNode = + sourceAnnos.getClassOrInterfaceDeclarationByName(toplevelClass.getSimpleName().toString()); + ClassTree toplevelClassTree = atypeFactory.getTreeUtils().getTree(toplevelClass); + createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); + } + + /** + * Reads in the file at {@code path} and creates a wrapper around its compilation unit. Stores the + * wrapper in {@link #sourceToAnnos}, but doesn't create wrappers around any classes in the file. + * + * @param path path to source file to read + */ + private void addSourceFile(String path) { + if (sourceToAnnos.containsKey(path)) { + return; + } - /** - * Maps from binary class name to the wrapper containing the class. Contains all classes in Java - * source files containing an Element for which an annotation has been inferred. - */ - private Map<@BinaryName String, ClassOrInterfaceAnnos> classToAnnos = new HashMap<>(); + CompilationUnit root; + try { + root = JavaParserUtil.parseCompilationUnit(new File(path)); + } catch (FileNotFoundException e) { + throw new BugInCF("Failed to read Java file " + path, e); + } + JavaParserUtil.concatenateAddedStringLiterals(root); + CompilationUnitAnnos sourceAnnos = new CompilationUnitAnnos(root); + sourceToAnnos.put(path, sourceAnnos); + + Optional oPackageDecl = root.getPackageDeclaration(); + String prefix = oPackageDecl.isPresent() ? oPackageDecl.get().getName().asString() + "." : ""; + List<@BinaryName String> typeNames = new ArrayList<>(); + for (TypeDeclaration type : root.getTypes()) { + addDeclaredTypes(type, prefix, typeNames); + } + for (String typeName : typeNames) { + classToSource.put(typeName, path); + } + } + + /** + * Computes the binary names of a type and all nested types. + * + * @param td a type declaration + * @param prefix the package, or package+outerclass, prefix in a binary name + * @param result a list to which to add the binary names of all classes defined in the compilation + * unit + */ + private static void addDeclaredTypes( + TypeDeclaration td, String prefix, List<@BinaryName String> result) { + @SuppressWarnings("signature:assignment") // string concatenation + @BinaryName String typeName = prefix + td.getName().asString(); + result.add(typeName); + for (BodyDeclaration member : td.getMembers()) { + if (member.isTypeDeclaration()) { + addDeclaredTypes(member.asTypeDeclaration(), typeName + "$", result); + } + } + } + + /** + * The first two arguments are a javac tree and a JavaParser node representing the same class. + * This method creates wrappers around all the classes, fields, and methods in that class, and + * stores those wrappers in {@code sourceAnnos}. + * + * @param javacClass javac tree for class + * @param javaParserClass a JavaParser node corresponding to the same class as {@code javacClass} + * @param sourceAnnos compilation unit wrapper to add new wrappers to + */ + private void createWrappersForClass( + ClassTree javacClass, TypeDeclaration javaParserClass, CompilationUnitAnnos sourceAnnos) { + JointJavacJavaParserVisitor visitor = + new DefaultJointVisitor() { + + /** + * The number of inner classes encountered, for use in computing their names as keys to + * various maps. This is an estimate only: an error might lead to inaccurate annotations + * being emitted, but that is ok: WPI should never be run without running the checker + * again afterwards to check the results. This field is only used when no element for the + * inner class is available, such as when it comes from another compilation unit. + */ + private int innerClassCount = 0; + + @Override + public void processClass( + ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { + addClass(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { + addClass(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) { + addClass(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) { + // TODO: consider supporting inferring annotations on annotation + // declarations. + // addClass(javacTree, javaParserNode); + } + + @Override + public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) { + ClassTree body = javacTree.getClassBody(); + if (body != null) { + addClass(body, null); + } + } + + /** + * Creates a wrapper around the class for {@code tree} and stores it in {@code + * sourceAnnos}. + * + *

This method computes the name of the class when the element corresponding to tree is + * null and uses it as the key for {@code classToAnnos} + * + * @param tree tree to add. Its corresponding name is used as the key for {@code + * classToAnnos}. + * @param javaParserNode the node corresponding to the declaration, which is used to place + * annotations on the class itself. Can be null, e.g. for an anonymous class. + */ + private void addClass(ClassTree tree, @Nullable TypeDeclaration javaParserNode) { + String className; + // elementFromDeclaration returns null instead of crashing when no element + // exists for the class tree, which can happen for certain kinds of + // anonymous classes, such as classes, such as Ordering$1 in + // PolyCollectorTypeVar.java in the all-systems test suite. + TypeElement classElt = TreeUtils.elementFromDeclaration(tree); + if (classElt == null) { + // If such an element does not exist, compute the name of the class, + // instead. This method of computing the name is not 100% guaranteed to + // be reliable, but it should be sufficient for WPI's purposes here: if + // the wrong name is computed, the worst outcome is a false positive + // because WPI inferred an untrue annotation. + Optional ofqn = javaParserClass.getFullyQualifiedName(); + if (ofqn.isEmpty()) { + throw new BugInCF("Missing getFullyQualifiedName() for " + javaParserClass); + } + if ("".contentEquals(tree.getSimpleName())) { + @SuppressWarnings("signature:assignment") // computed from string + // concatenation + @BinaryName String computedName = ofqn.get() + "$" + ++innerClassCount; + className = computedName; + } else { + @SuppressWarnings("signature:assignment") // computed from string + // concatenation + @BinaryName String computedName = ofqn.get() + "$" + tree.getSimpleName().toString(); + className = computedName; + } + } else { + className = ElementUtils.getBinaryName(classElt); + for (TypeElement supertypeElement : ElementUtils.getSuperTypes(classElt, elements)) { + String supertypeName = ElementUtils.getBinaryName(supertypeElement); + Set<@BinaryName String> supertypeSet = + supertypesMap.computeIfAbsent( + className, k -> new TreeSet<@BinaryName String>()); + supertypeSet.add(supertypeName); + Set<@BinaryName String> subtypeSet = + subtypesMap.computeIfAbsent( + supertypeName, k -> new TreeSet<@BinaryName String>()); + subtypeSet.add(className); + } + } - /** Maps from binary class name to binary names of all supertypes. */ - private Map<@BinaryName String, Set<@BinaryName String>> supertypesMap = new HashMap<>(); + ClassOrInterfaceAnnos typeWrapper = + new ClassOrInterfaceAnnos(className, javaParserNode); + if (!classToAnnos.containsKey(className)) { + classToAnnos.put(className, typeWrapper); + } - /** Maps from binary class name to binary names of all known subtypes. */ - private Map<@BinaryName String, Set<@BinaryName String>> subtypesMap = new HashMap<>(); + sourceAnnos.types.add(typeWrapper); + } + + @Override + public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) { + addCallableDeclaration(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) { + addCallableDeclaration(javacTree, javaParserNode); + } + + /** + * Creates a wrapper around {@code javacTree} with the corresponding declaration {@code + * javaParserNode} and stores it in {@code sourceAnnos}. + * + * @param javacTree javac tree for declaration to add + * @param javaParserNode a JavaParser node for the same class as {@code javacTree} + */ + private void addCallableDeclaration( + MethodTree javacTree, CallableDeclaration javaParserNode) { + ExecutableElement element = TreeUtils.elementFromDeclaration(javacTree); + if (element == null) { + // element can be null if there is no element corresponding to the + // method, which happens for certain kinds of anonymous classes, + // such as Ordering$1 in PolyCollectorTypeVar.java in the + // all-systems test suite. + return; + } + String className = ElementUtils.getEnclosingClassName(element); + ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(className); + String executableSignature = JVMNames.getJVMMethodSignature(javacTree); + if (!enclosingClass.callableDeclarations.containsKey(executableSignature)) { + enclosingClass.callableDeclarations.put( + executableSignature, new CallableDeclarationAnnos(javaParserNode)); + } + } + + @Override + public void processVariable( + VariableTree javacTree, EnumConstantDeclaration javaParserNode) { + VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); + if (!elt.getKind().isField()) { + throw new BugInCF(elt + " is not a field but a " + elt.getKind()); + } - /** - * Files containing classes for which an annotation has been inferred since the last time files - * were written to disk. - */ - private Set modifiedFiles = new HashSet<>(); + String enclosingClassName = ElementUtils.getEnclosingClassName(elt); + ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); + String fieldName = javacTree.getName().toString(); + enclosingClass.enumConstants.add(fieldName); + + // Ensure that if an enum constant defines a class, that class gets + // registered properly. See e.g. + // https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.9.1 + // for the specification of an enum constant, which does permit it to + // define an anonymous class. + NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); + ClassTree constructorClassBody = constructor.getClassBody(); + if (constructorClassBody != null) { + // addClass assumes there is an element for its argument, but that is + // not always true! + if (TreeUtils.elementFromDeclaration(constructorClassBody) != null) { + addClass(constructorClassBody, null); + } + } + } - /** Mapping from source file to the wrapper for the compilation unit parsed from that file. */ - private Map sourceToAnnos = new HashMap<>(); + @Override + public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) { + VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); + if (!elt.getKind().isField()) { + return; + } - /** Maps from binary class name to the source file that contains it. */ - private Map classToSource = new HashMap<>(); + String enclosingClassName = ElementUtils.getEnclosingClassName(elt); + ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); + String fieldName = javacTree.getName().toString(); + if (!enclosingClass.fields.containsKey(fieldName)) { + enclosingClass.fields.put(fieldName, new FieldAnnos(javaParserNode)); + } + } + }; + visitor.visitClass(javacClass, javaParserClass); + } + + /** + * Calls {@link #addSourceFile(String)} for the file containing the given element. + * + * @param element the element for the source file to add + * @return path of the file containing {@code element} + */ + private String addClassesForElement(Element element) { + if (!ElementUtils.isElementFromSourceCode(element)) { + throw new BugInCF("Called addClassesForElement for non-source element: " + element); + } - /** Whether the {@code -AinferOutputOriginal} option was supplied to the checker. */ - private final boolean inferOutputOriginal; + TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); + String path = ElementUtils.getSourceFilePath(toplevelClass); + if (toplevelClass.getKind() == ElementKind.ANNOTATION_TYPE) { + // Inferring annotations on elements of annotation declarations is not supported. + // One issue with supporting inference on annotation declaration elements is that + // AnnotatedTypeFactory#declarationFromElement returns null for annotation declarations + // quite commonly (because Trees#getTree, which it delegates to, does as well). + // In this case, we return path here without actually attempting to create the wrappers + // for the annotation declaration. The rest of WholeProgramInferenceJavaParserStorage + // already needs to handle classes without entries in the various tables (because of the + // possibility of classes outside the current compilation unit), so this is safe. + return path; + } + if (classToAnnos.containsKey(ElementUtils.getBinaryName(toplevelClass))) { + return path; + } - /** - * Returns the names of all qualifiers that are marked with {@link InvisibleQualifier}, and that - * are supported by the given type factory. - * - * @param atypeFactory a type factory - * @return the names of every invisible qualifier supported by {@code atypeFactory} - */ - public static Set getInvisibleQualifierNames(AnnotatedTypeFactory atypeFactory) { - return atypeFactory.getSupportedTypeQualifiers().stream() - .filter(WholeProgramInferenceJavaParserStorage::isInvisible) - .map(Class::getCanonicalName) - .collect(Collectors.toSet()); + addSourceFile(path); + CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); + ClassTree toplevelClassTree = (ClassTree) atypeFactory.declarationFromElement(toplevelClass); + TypeDeclaration javaParserNode = + sourceAnnos.getClassOrInterfaceDeclarationByName(toplevelClass.getSimpleName().toString()); + createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); + return path; + } + + /// + /// Writing to a file + /// + + // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because + // a scene may be modifed and written at any time, including before or after + // postProcessClassTree is called. + + /** + * Side-effects the compilation unit annotations to make any desired changes before writing to a + * file. + * + * @param compilationUnitAnnos the compilation unit annotations to modify + */ + public void wpiPrepareCompilationUnitForWriting(CompilationUnitAnnos compilationUnitAnnos) { + for (ClassOrInterfaceAnnos type : compilationUnitAnnos.types) { + wpiPrepareClassForWriting( + type, supertypesMap.get(type.className), subtypesMap.get(type.className)); + } + } + + /** + * Side-effects the class annotations to make any desired changes before writing to a file. + * + *

Because of the side effect, clients may want to pass a copy into this method. + * + * @param classAnnos the class annotations to modify + * @param supertypes the binary names of all supertypes; not side-effected + * @param subtypes the binary names of all subtypes; not side-effected + */ + public void wpiPrepareClassForWriting( + ClassOrInterfaceAnnos classAnnos, + Collection<@BinaryName String> supertypes, + Collection<@BinaryName String> subtypes) { + if (classAnnos.callableDeclarations.isEmpty()) { + return; + } + + for (Map.Entry methodEntry : + classAnnos.callableDeclarations.entrySet()) { + String jvmSignature = methodEntry.getKey(); + List inSupertypes = + findOverrides(jvmSignature, supertypesMap.get(classAnnos.className)); + List inSubtypes = + findOverrides(jvmSignature, subtypesMap.get(classAnnos.className)); + + wpiPrepareMethodForWriting(methodEntry.getValue(), inSupertypes, inSubtypes); + } + } + + /** + * Return all the CallableDeclarationAnnos for the given signature. + * + * @param jvmSignature the JVM signature + * @param typeNames a collection of type names + * @return the CallableDeclarationAnnos for the given signature, in all of the types + */ + private List findOverrides( + String jvmSignature, @Nullable Collection<@BinaryName String> typeNames) { + if (typeNames == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (String typeName : typeNames) { + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(typeName); + if (classAnnos != null) { + CallableDeclarationAnnos callableAnnos = classAnnos.callableDeclarations.get(jvmSignature); + if (callableAnnos != null) { + result.add(callableAnnos); + } + } + } + return result; + } + + /** + * Side-effects the method or constructor annotations to make any desired changes before writing + * to a file. For example, this method may make inferred annotations consistent with one another + * between superclasses and subclasses. + * + * @param methodAnnos the method or constructor annotations to modify + * @param inSupertypes the method or constructor annotations for all overridden methods; not + * side-effected + * @param inSubtypes the method or constructor annotations for all overriding methods; not + * side-effected + */ + // TODO: Inferred annotations must be consistent both with one another and with + // programmer-written annotations. The latter are stored in elements and, with the given formal + // parameter list, are not accessible to this method. In the future, the annotations stored in + // elements should also be passed to this method (or maybe they are already available to the + // type + // factory?). I'm leaving that enhancement until later. + public void wpiPrepareMethodForWriting( + CallableDeclarationAnnos methodAnnos, + Collection inSupertypes, + Collection inSubtypes) { + atypeFactory.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); + } + + @Override + public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { + if (outputFormat != OutputFormat.AJAVA) { + throw new BugInCF("WholeProgramInferenceJavaParser used with output format " + outputFormat); + } + + File outputDir = AJAVA_FILES_PATH; + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + setSupertypesAndSubtypesModified(); + + for (String path : modifiedFiles) { + // This calls deepCopy() because wpiPrepareCompilationUnitForWriting performs side + // effects that we don't want to be persistent. + CompilationUnitAnnos root = sourceToAnnos.get(path).deepCopy(); + wpiPrepareCompilationUnitForWriting(root); + File packageDir; + if (!root.compilationUnit.getPackageDeclaration().isPresent()) { + packageDir = AJAVA_FILES_PATH; + } else { + packageDir = + new File( + AJAVA_FILES_PATH, + root.compilationUnit + .getPackageDeclaration() + .get() + .getNameAsString() + .replaceAll("\\.", File.separator)); + } + + if (!packageDir.exists()) { + packageDir.mkdirs(); + } + + String name = new File(path).getName(); + if (name.endsWith(".java")) { + name = name.substring(0, name.length() - ".java".length()); + } + + String nameWithChecker = name + "-" + checker.getClass().getCanonicalName() + ".ajava"; + File outputPath = new File(packageDir, nameWithChecker); + if (this.inferOutputOriginal) { + File outputPathNoCheckerName = new File(packageDir, name + ".ajava"); + // Avoid re-writing this file for each checker that was run. + if (Files.notExists(outputPathNoCheckerName.toPath())) { + writeAjavaFile(outputPathNoCheckerName, root); + } + } + root.transferAnnotations(checker); + writeAjavaFile(outputPath, root); + } + + modifiedFiles.clear(); + } + + /** + * Write an ajava file to disk. + * + * @param outputPath the path to which the ajava file should be written + * @param root the compilation unit to be written + */ + private void writeAjavaFile(File outputPath, CompilationUnitAnnos root) { + try (Writer writer = new BufferedWriter(new FileWriter(outputPath))) { + + // JavaParser can output using lexical preserving printing, which writes the file such + // that its formatting is close to the original source file it was parsed from as + // possible. Currently, this feature is very buggy and crashes when adding annotations + // in certain locations. This implementation could be used instead if it's fixed in + // JavaParser.LexicalPreservingPrinter.print(root.declaration, writer); + + // Do not print invisible qualifiers, to avoid cluttering the output. + Set invisibleQualifierNames = getInvisibleQualifierNames(this.atypeFactory); + DefaultPrettyPrinter prettyPrinter = + new DefaultPrettyPrinter() { + @Override + public String print(Node node) { + VoidVisitor visitor = + new DefaultPrettyPrinterVisitor(getConfiguration()) { + @Override + public void visit(MarkerAnnotationExpr n, Void arg) { + if (invisibleQualifierNames.contains(n.getName().toString())) { + return; + } + super.visit(n, arg); + } + + @Override + public void visit(SingleMemberAnnotationExpr n, Void arg) { + if (invisibleQualifierNames.contains(n.getName().toString())) { + return; + } + super.visit(n, arg); + } + + @Override + public void visit(NormalAnnotationExpr n, Void arg) { + if (invisibleQualifierNames.contains(n.getName().toString())) { + return; + } + super.visit(n, arg); + } + }; + node.accept(visitor, null); + return visitor.toString(); + } + }; + + writer.write(prettyPrinter.print(root.compilationUnit)); + } catch (IOException e) { + throw new BugInCF("Error while writing ajava file " + outputPath, e); + } + } + + /** + * Adds an explicit receiver type to a JavaParser method declaration. + * + * @param methodDeclaration declaration to add a receiver to + */ + private static void addExplicitReceiver(MethodDeclaration methodDeclaration) { + if (methodDeclaration.getReceiverParameter().isPresent()) { + return; + } + + com.github.javaparser.ast.Node parent = methodDeclaration.getParentNode().get(); + if (!(parent instanceof TypeDeclaration)) { + return; + } + + TypeDeclaration parentDecl = (TypeDeclaration) parent; + ClassOrInterfaceType receiver = new ClassOrInterfaceType(); + receiver.setName(parentDecl.getName()); + if (parentDecl.isClassOrInterfaceDeclaration()) { + ClassOrInterfaceDeclaration parentClassDecl = parentDecl.asClassOrInterfaceDeclaration(); + if (!parentClassDecl.getTypeParameters().isEmpty()) { + NodeList typeArgs = new NodeList<>(); + for (TypeParameter typeParam : parentClassDecl.getTypeParameters()) { + ClassOrInterfaceType typeArg = new ClassOrInterfaceType(); + typeArg.setName(typeParam.getNameAsString()); + typeArgs.add(typeArg); + } + + receiver.setTypeArguments(typeArgs); + } + } + + methodDeclaration.setReceiverParameter(new ReceiverParameter(receiver, "this")); + } + + /** + * Transfers all annotations for {@code annotatedType} and its nested types to {@code target}, + * which is the JavaParser node representing the same type. Does nothing if {@code annotatedType} + * is null (this may occur if there are no inferred annotations for the type). + * + * @param annotatedType type to transfer annotations from + * @param target the JavaParser type to transfer annotation to; must represent the same type as + * {@code annotatedType} + */ + private static void transferAnnotations( + @Nullable AnnotatedTypeMirror annotatedType, Type target) { + if (annotatedType == null) { + return; } + target.accept(new AnnotationTransferVisitor(), annotatedType); + } + + /// + /// Storing annotations + /// + + /** + * Stores the JavaParser node for a compilation unit and the list of wrappers for the classes and + * interfaces in that compilation unit. + */ + private static class CompilationUnitAnnos implements DeepCopyable { + /** Compilation unit being wrapped. */ + public final CompilationUnit compilationUnit; + + /** Wrappers for classes and interfaces in {@code compilationUnit}. */ + public final List types; + /** - * Is the definition of the given annotation class annotated with {@link InvisibleQualifier}? + * Constructs a wrapper around the given compilation unit. * - * @param qual an annotation class - * @return true iff {@code qual} is meta-annotated with {@link InvisibleQualifier} + * @param compilationUnit compilation unit to wrap */ - public static boolean isInvisible(Class qual) { - return Arrays.stream(qual.getAnnotations()) - .anyMatch(anno -> anno.annotationType() == InvisibleQualifier.class); + public CompilationUnitAnnos(CompilationUnit compilationUnit) { + this.compilationUnit = compilationUnit; + this.types = new ArrayList<>(); } /** - * Constructs a new {@code WholeProgramInferenceJavaParser} that has not yet inferred any - * annotations. + * Private constructor for use by deepCopy(). * - * @param atypeFactory the associated type factory - * @param inferOutputOriginal whether the -AinferOutputOriginal option was supplied to the - * checker + * @param compilationUnit compilation unit to wrap + * @param types wrappers for classes and interfaces in {@code compilationUnit} */ - public WholeProgramInferenceJavaParserStorage( - AnnotatedTypeFactory atypeFactory, boolean inferOutputOriginal) { - this.atypeFactory = atypeFactory; - this.elements = atypeFactory.getElementUtils(); - this.inferOutputOriginal = inferOutputOriginal; + private CompilationUnitAnnos( + CompilationUnit compilationUnit, List types) { + this.compilationUnit = compilationUnit; + this.types = types; } @Override - public String getFileForElement(Element elt) { - return addClassesForElement(elt); + public CompilationUnitAnnos deepCopy() { + return new CompilationUnitAnnos(compilationUnit, CollectionsPlume.deepCopy(types)); } - @Override - public void setFileModified(String path) { - modifiedFiles.add(path); + /** + * Transfers all annotations inferred by whole program inference for the wrapped compilation + * unit to their corresponding JavaParser locations. + * + * @param checker the checker who's name to include in the @AnnotatedFor annotation + */ + public void transferAnnotations(BaseTypeChecker checker) { + JavaParserUtil.clearAnnotations(compilationUnit); + for (TypeDeclaration typeDecl : compilationUnit.getTypes()) { + typeDecl.addSingleMemberAnnotation( + "org.checkerframework.framework.qual.AnnotatedFor", + "\"" + checker.getClass().getCanonicalName() + "\""); + } + + for (ClassOrInterfaceAnnos typeAnnos : types) { + typeAnnos.transferAnnotations(); + } } /** - * Set the source file as modified, for the given class. + * Returns the top-level type declaration named {@code name} in the compilation unit. * - * @param className the binary name of a class that should be written to disk + * @param name name of type declaration + * @return the type declaration named {@code name} in the wrapped compilation unit */ - private void setClassModified(@Nullable @BinaryName String className) { - if (className == null) { - return; - } - String path = classToSource.get(className); - if (path != null) { - setFileModified(path); - } + public TypeDeclaration getClassOrInterfaceDeclarationByName(String name) { + return JavaParserUtil.getTypeDeclarationByName(compilationUnit, name); } /** - * Set the source files as modified, for all the given classes. + * Returns a verbose printed representation of this. * - * @param classNames the binary names of classes that should be written to disk + * @return a verbose printed representation of this */ - private void setClassesModified(@Nullable Collection<@BinaryName String> classNames) { - if (classNames == null) { - return; - } - for (String className : classNames) { - setClassModified(className); - } + @SuppressWarnings("UnusedMethod") + public String toStringVerbose() { + StringJoiner sb = new StringJoiner(System.lineSeparator()); + sb.add("CompilationUnitAnnos:"); + for (ClassOrInterfaceAnnos type : types) { + sb.add(type.toStringVerbose()); + } + return sb.toString(); } + } + /** + * Stores wrappers for the locations where annotations may be inferred in a class or interface. + */ + private static class ClassOrInterfaceAnnos implements DeepCopyable { /** - * For every modified file, consider its subclasses and superclasses modified, too. The reason - * is that an annotation change in a class might require annotations in its superclasses and - * supclasses to be modified, in order to preserve behavioral subtyping. Setting it modified - * will cause it to be written out, and while writing out, the annotations will be made - * consistent across the class hierarchy by {@link #wpiPrepareCompilationUnitForWriting}. + * Mapping from JVM method signatures to the wrapper containing the corresponding executable. */ - public void setSupertypesAndSubtypesModified() { - // Copy into a list to avoid a ConcurrentModificationException. - for (String path : new ArrayList<>(modifiedFiles)) { - CompilationUnitAnnos cuAnnos = sourceToAnnos.get(path); - for (ClassOrInterfaceAnnos classAnnos : cuAnnos.types) { - String className = classAnnos.className; - setClassesModified(supertypesMap.get(className)); - setClassesModified(subtypesMap.get(className)); - } - } - } + public Map callableDeclarations = new HashMap<>(); - /// - /// Reading stored annotations - /// + /** Mapping from field names to wrappers for those fields. */ + public Map fields = new HashMap<>(2); - @Override - public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { - return getMethodAnnos(methodElt) != null; - } + /** Collection of declared enum constants (empty if not an enum). */ + public Set enumConstants = new HashSet<>(2); - @Override - public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement methodElt) { - String className = ElementUtils.getEnclosingClassName(methodElt); - // Read in classes for the element. - getFileForElement(methodElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return AnnotationMirrorSet.emptySet(); - } - CallableDeclarationAnnos methodAnnos = - classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); - if (methodAnnos == null) { - return AnnotationMirrorSet.emptySet(); - } - return methodAnnos.getDeclarationAnnotations(); - } + /** + * Annotations on the declaration of the class (note that despite the name, these can also be + * type annotations). + */ + private @MonotonicNonNull AnnotationMirrorSet classAnnotations = null; /** - * Get the annotations for a method or constructor. - * - * @param methodElt the method or constructor - * @return the annotations for a method or constructor + * The JavaParser TypeDeclaration representing the class's declaration. Used for placing + * annotations inferred on the class declaration itself. */ - private @Nullable CallableDeclarationAnnos getMethodAnnos(ExecutableElement methodElt) { - String className = ElementUtils.getEnclosingClassName(methodElt); - // Read in classes for the element. - getFileForElement(methodElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return null; - } - CallableDeclarationAnnos methodAnnos = - classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); - return methodAnnos; - } + private @MonotonicNonNull TypeDeclaration classDeclaration; + + /** The binary name of the class. */ + private @BinaryName String className; /** - * Get the annotations for a field. + * Create a new ClassOrInterfaceAnnos. * - * @param fieldElt a field - * @return the annotations for a field + * @param className the binary name of the class + * @param javaParserNode the JavaParser node corresponding to the class declaration, which is + * used for placing annotations on the class declaration */ - private @Nullable FieldAnnos getFieldAnnos(VariableElement fieldElt) { - String className = ElementUtils.getEnclosingClassName(fieldElt); - // Read in classes for the element. - getFileForElement(fieldElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return null; - } - FieldAnnos fieldAnnos = classAnnos.fields.get(fieldElt.getSimpleName().toString()); - return fieldAnnos; + public ClassOrInterfaceAnnos( + @BinaryName String className, @Nullable TypeDeclaration javaParserNode) { + this.classDeclaration = javaParserNode; + this.className = className; } @Override - public AnnotatedTypeMirror getParameterAnnotations( - ExecutableElement methodElt, - @Positive int index_1based, - AnnotatedTypeMirror paramATM, - VariableElement ve, - AnnotatedTypeFactory atypeFactory) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // When processing anonymous inner classes outside their compilation units, - // it might not have been possible to create an appropriate CallableDeclarationAnnos: - // no element would have been available, causing the computed method signature to - // be incorrect. In this case, abort looking up annotations -- inference will fail, - // because even if WPI inferred something, it couldn't be printed. - return paramATM; - } - return methodAnnos.getParameterTypeInitialized(paramATM, index_1based, atypeFactory); + public ClassOrInterfaceAnnos deepCopy() { + ClassOrInterfaceAnnos result = new ClassOrInterfaceAnnos(className, classDeclaration); + result.callableDeclarations = CollectionsPlume.deepCopyValues(callableDeclarations); + result.fields = CollectionsPlume.deepCopyValues(fields); + result.enumConstants = UtilPlume.clone(enumConstants); // no deep copy: elements are strings + if (classAnnotations != null) { + result.classAnnotations = classAnnotations.deepCopy(); + } + // no need to change classDeclaration + return result; } - @Override - public AnnotatedTypeMirror getReceiverAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror paramATM, - AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return paramATM; - } - return methodAnnos.getReceiverType(paramATM, atypeFactory); - } + /** + * Adds {@code annotation} to the set of annotations on the declaration of this class. + * + * @param annotation an annotation (can be declaration or type) + * @return true if this is a new annotation for this class + */ + public boolean addAnnotationToClassDeclaration(AnnotationMirror annotation) { + if (classAnnotations == null) { + classAnnotations = new AnnotationMirrorSet(); + } - @Override - public AnnotatedTypeMirror getReturnAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror atm, - AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return atm; - } - return methodAnnos.getReturnType(atm, atypeFactory); + return classAnnotations.add(annotation); } - @Override - public @Nullable AnnotatedTypeMirror getFieldAnnotations( - Element element, - String fieldName, - AnnotatedTypeMirror lhsATM, - AnnotatedTypeFactory atypeFactory) { - ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); - // Read in classes for the element. - getFileForElement(element); - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @BinaryName String className = enclosingClass.flatname.toString(); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return null; - } - // If it's an enum constant it won't appear as a field - // and it won't have extra annotations, so just return the basic type: - if (classAnnos.enumConstants.contains(fieldName)) { - return lhsATM; - } else if (classAnnos.fields.get(fieldName) == null) { - // There might not be a corresponding entry for the field name - // in an anonymous class, if the field and class were defined in - // another compilation unit (for the same reason that a method - // might not have an entry, as in #getParameterAnnotations, above). - return null; - } else { - return classAnnos.fields.get(fieldName).getType(lhsATM, atypeFactory); - } + /** + * Transfers all annotations inferred by whole program inference for the methods and fields in + * the wrapper class or interface to their corresponding JavaParser locations. + */ + public void transferAnnotations() { + for (CallableDeclarationAnnos callableAnnos : callableDeclarations.values()) { + callableAnnos.transferAnnotations(); + } + + if (classAnnotations != null && classDeclaration != null) { + for (AnnotationMirror annotation : classAnnotations) { + classDeclaration.addAnnotation( + AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( + annotation)); + } + } + + for (FieldAnnos field : fields.values()) { + field.transferAnnotations(); + } } @Override - public AnnotatedTypeMirror getPreOrPostconditions( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - switch (preOrPost) { - case BEFORE: - return getPreconditionsForExpression( - methodElement, expression, declaredType, atypeFactory); - case AFTER: - return getPostconditionsForExpression( - methodElement, expression, declaredType, atypeFactory); - default: - throw new BugInCF("Unexpected " + preOrPost); - } + public String toString() { + String fieldsString = fields.toString(); + if (fieldsString.length() > 100) { + // The quoting increases the likelihood that all delimiters are balanced in the + // result. + // That makes it easier to manipulate the result (such as skipping over it) in an + // editor. The quoting also makes clear that the value is truncated. + fieldsString = "\"" + fieldsString.substring(0, 95) + "...\""; + } + + return "ClassOrInterfaceAnnos [" + + (classDeclaration == null ? "unnamed" : classDeclaration.getName()) + + ": callableDeclarations=" + // For deterministic output + + new TreeMap<>(callableDeclarations) + + ", fields=" + + fieldsString + + "]"; } /** - * Returns the precondition annotations for the given expression. + * Returns a verbose printed representation of this. * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @param atypeFactory the type factory - * @return the precondition annotations for a field + * @return a verbose printed representation of this */ - private AnnotatedTypeMirror getPreconditionsForExpression( - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return declaredType; - } - return methodAnnos.getPreconditionsForExpression(expression, declaredType, atypeFactory); + public String toStringVerbose() { + return toString(); } + } + + /** + * Stores the JavaParser node for a method or constructor and the annotations that have been + * inferred about its parameters and return type. + */ + public class CallableDeclarationAnnos implements DeepCopyable { + /** Wrapped method or constructor declaration. */ + public final CallableDeclaration declaration; /** - * Returns the postcondition annotations for an expression. - * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @param atypeFactory the type factory - * @return the postcondition annotations for a field + * Inferred annotations for the return type, if the declaration represents a method. Initialized + * on first usage. */ - private AnnotatedTypeMirror getPostconditionsForExpression( - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return declaredType; - } - return methodAnnos.getPostconditionsForExpression(expression, declaredType, atypeFactory); - } - - @Override - public boolean addMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno) { + private @MonotonicNonNull AnnotatedTypeMirror returnType = null; - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = methodAnnos.addDeclarationAnnotation(anno); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(methodElt)); - } - return isNewAnnotation; - } - - @Override - public boolean removeMethodDeclarationAnnotation(ExecutableElement elt, AnnotationMirror anno) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(elt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - return methodAnnos.removeDeclarationAnnotation(anno); - } + /** + * Inferred annotations for the receiver type, if the declaration represents a method. + * Initialized on first usage. + */ + private @MonotonicNonNull AnnotatedTypeMirror receiverType = null; - @Override - public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { - FieldAnnos fieldAnnos = getFieldAnnos(field); - if (fieldAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = fieldAnnos != null && fieldAnnos.addDeclarationAnnotation(anno); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(field)); - } - return isNewAnnotation; - } + /** + * Inferred annotations for parameter types. The list is initialized the first time any + * parameter is accessed, and each parameter is initialized the first time it's accessed. + */ + private @MonotonicNonNull List<@Nullable AnnotatedTypeMirror> parameterTypes = null; - @Override - public boolean addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = - methodAnnos.addDeclarationAnnotationToFormalParameter(anno, index_1based); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(methodElt)); - } - return isNewAnnotation; - } + /** Declaration annotations on the parameters. */ + private @MonotonicNonNull Set> paramsDeclAnnos = null; - @Override - public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { - String className = ElementUtils.getBinaryName(classElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = classAnnos.addAnnotationToClassDeclaration(anno); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(classElt)); - } - return isNewAnnotation; - } + /** + * Annotations on the callable declaration. This does not include preconditions and + * postconditions. + */ + private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; - @Override - public AnnotatedTypeMirror atmFromStorageLocation( - TypeMirror typeMirror, AnnotatedTypeMirror storageLocation) { - if (typeMirror.getKind() == TypeKind.TYPEVAR) { - // Only copy the primary annotation, because we don't currently have - // support for inferring type bounds. This avoids accidentally substituting the - // use of the type variable for its declaration when inferring annotations on - // fields with a type variable as their type. - AnnotatedTypeMirror asExpectedType = - AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); - asExpectedType.replaceAnnotations(storageLocation.getAnnotations()); - return asExpectedType; - } else { - return storageLocation; - } - } + /** + * Mapping from expression strings to pairs of (inferred precondition, declared type). The keys + * are strings representing JavaExpressions, using the same format as a user would in an {@link + * org.checkerframework.framework.qual.RequiresQualifier} annotation. + */ + private @MonotonicNonNull Map> + preconditions = null; - @Override - public void updateStorageLocationFromAtm( - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - AnnotatedTypeMirror typeToUpdate, - TypeUseLocation defLoc, - boolean ignoreIfAnnotated) { - // Only update the AnnotatedTypeMirror if there are no explicit annotations - if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { - for (AnnotationMirror am : newATM.getAnnotations()) { - typeToUpdate.replaceAnnotation(am); - } - } else if (curATM.getKind() == TypeKind.TYPEVAR) { - // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly - // annotated. So instead, only insert the annotation if there is not primary annotation - // of the same hierarchy. - for (AnnotationMirror am : newATM.getAnnotations()) { - if (curATM.getAnnotationInHierarchy(am) != null) { - // Don't insert if the type is already has a primary annotation - // in the same hierarchy. - break; - } - typeToUpdate.replaceAnnotation(am); - } - } + /** + * Mapping from expression strings to pairs of (inferred postcondition, declared type). The + * okeys are strings representing JavaExpressions, using the same format as a user would in an + * {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation. + */ + private @MonotonicNonNull Map> + postconditions = null; - // Need to check both newATM and curATM, because one might be a declared type - // even if the other is an array: it is permitted to assign e.g., a String[] - // to a location with static type Object **and vice-versa** (if a cast is used). - if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; - AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; - AnnotatedArrayType aatToUpdate = (AnnotatedArrayType) typeToUpdate; - updateStorageLocationFromAtm( - newAAT.getComponentType(), - oldAAT.getComponentType(), - aatToUpdate.getComponentType(), - defLoc, - ignoreIfAnnotated); - } + /** + * Creates a wrapper for the given method or constructor declaration. + * + * @param declaration method or constructor declaration to wrap + */ + public CallableDeclarationAnnos(CallableDeclaration declaration) { + this.declaration = declaration; } - /// - /// Reading in files - /// - @Override - public void preprocessClassTree(ClassTree classTree) { - addClassTree(classTree); + public CallableDeclarationAnnos deepCopy() { + CallableDeclarationAnnos result = new CallableDeclarationAnnos(declaration); + result.returnType = DeepCopyable.deepCopyOrNull(this.returnType); + result.receiverType = DeepCopyable.deepCopyOrNull(this.receiverType); + if (parameterTypes != null) { + result.parameterTypes = CollectionsPlume.deepCopy(this.parameterTypes); + } + result.declarationAnnotations = DeepCopyable.deepCopyOrNull(this.declarationAnnotations); + + if (this.paramsDeclAnnos != null) { + result.paramsDeclAnnos = new ArraySet<>(this.paramsDeclAnnos); + } + result.preconditions = deepCopyMapOfStringToPair(this.preconditions); + result.postconditions = deepCopyMapOfStringToPair(this.postconditions); + return result; } /** - * Reads in the source file containing {@code tree} and creates wrappers around all classes in - * the file. Stores the wrapper for the compilation unit in {@link #sourceToAnnos} and stores - * the wrappers of all classes in the file in {@link #classToAnnos}. + * Returns the inferred type for the parameter at the given index. If necessary, initializes the + * {@code AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper + * around the base type for the parameter. * - * @param tree tree for class to add + * @param type type for the parameter at {@code index}, used for initializing the returned + * {@code AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will be + * used + * @param index_1based index of the parameter to return the inferred annotations of (1-based) + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the parameter + * at the given index */ - private void addClassTree(ClassTree tree) { - TypeElement element = TreeUtils.elementFromDeclaration(tree); - if (element == null) { - // TODO: There should be an element here, or there is nowhere to store inferences about - // `tree`. - return; - } - String className = ElementUtils.getBinaryName(element); - if (classToAnnos.containsKey(className)) { - return; - } + public AnnotatedTypeMirror getParameterTypeInitialized( + AnnotatedTypeMirror type, @Positive int index_1based, AnnotatedTypeFactory atf) { + // 0-based index + int i = index_1based - 1; + + if (parameterTypes == null) { + parameterTypes = + new ArrayList<>(Collections.nCopies(declaration.getParameters().size(), null)); + } + + if (parameterTypes.get(i) == null) { + parameterTypes.set(i, AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false)); + } - TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); - String path = ElementUtils.getSourceFilePath(toplevelClass); - addSourceFile(path); - CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); - TypeDeclaration javaParserNode = - sourceAnnos.getClassOrInterfaceDeclarationByName( - toplevelClass.getSimpleName().toString()); - ClassTree toplevelClassTree = atypeFactory.getTreeUtils().getTree(toplevelClass); - createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); + return parameterTypes.get(i); } /** - * Reads in the file at {@code path} and creates a wrapper around its compilation unit. Stores - * the wrapper in {@link #sourceToAnnos}, but doesn't create wrappers around any classes in the - * file. + * Returns the inferred type for the parameter at the given index, or null if there's no + * parameter at the given index or there's no inferred type for that parameter. * - * @param path path to source file to read + * @param index index of the parameter to return the inferred annotations of + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the parameter + * at the given index, or null if there's no parameter at {@code index} or if there's not + * inferred annotations for that parameter */ - private void addSourceFile(String path) { - if (sourceToAnnos.containsKey(path)) { - return; - } + public @Nullable AnnotatedTypeMirror getParameterType(int index) { + if (parameterTypes == null || index < 0 || index >= parameterTypes.size()) { + return null; + } - CompilationUnit root; - try { - root = JavaParserUtil.parseCompilationUnit(new File(path)); - } catch (FileNotFoundException e) { - throw new BugInCF("Failed to read Java file " + path, e); - } - JavaParserUtil.concatenateAddedStringLiterals(root); - CompilationUnitAnnos sourceAnnos = new CompilationUnitAnnos(root); - sourceToAnnos.put(path, sourceAnnos); - - Optional oPackageDecl = root.getPackageDeclaration(); - String prefix = - oPackageDecl.isPresent() ? oPackageDecl.get().getName().asString() + "." : ""; - List<@BinaryName String> typeNames = new ArrayList<>(); - for (TypeDeclaration type : root.getTypes()) { - addDeclaredTypes(type, prefix, typeNames); - } - for (String typeName : typeNames) { - classToSource.put(typeName, path); - } + return parameterTypes.get(index); } /** - * Computes the binary names of a type and all nested types. + * Adds a declaration annotation to this parameter and returns whether it was a new annotation. * - * @param td a type declaration - * @param prefix the package, or package+outerclass, prefix in a binary name - * @param result a list to which to add the binary names of all classes defined in the - * compilation unit + * @param annotation the declaration annotation to add + * @param index_1based index of the parameter (1-indexed) + * @return true if {@code annotation} wasn't previously stored for this parameter */ - private static void addDeclaredTypes( - TypeDeclaration td, String prefix, List<@BinaryName String> result) { - @SuppressWarnings("signature:assignment") // string concatenation - @BinaryName String typeName = prefix + td.getName().asString(); - result.add(typeName); - for (BodyDeclaration member : td.getMembers()) { - if (member.isTypeDeclaration()) { - addDeclaredTypes(member.asTypeDeclaration(), typeName + "$", result); - } - } + public boolean addDeclarationAnnotationToFormalParameter( + AnnotationMirror annotation, @Positive int index_1based) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + if (paramsDeclAnnos == null) { + // There are usually few formal parameters. + paramsDeclAnnos = new ArraySet<>(4); + } + + return paramsDeclAnnos.add(IPair.of(index_1based, annotation)); } /** - * The first two arguments are a javac tree and a JavaParser node representing the same class. - * This method creates wrappers around all the classes, fields, and methods in that class, and - * stores those wrappers in {@code sourceAnnos}. + * If this wrapper holds a method, returns the inferred type of the receiver. If necessary, + * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code + * atf} to a wrapper around the base type for the receiver type. * - * @param javacClass javac tree for class - * @param javaParserClass a JavaParser node corresponding to the same class as {@code - * javacClass} - * @param sourceAnnos compilation unit wrapper to add new wrappers to + * @param type base type for the receiver type, used for initializing the returned {@code + * AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will be + * used + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the receiver + * type */ - private void createWrappersForClass( - ClassTree javacClass, - TypeDeclaration javaParserClass, - CompilationUnitAnnos sourceAnnos) { - JointJavacJavaParserVisitor visitor = - new DefaultJointVisitor() { - - /** - * The number of inner classes encountered, for use in computing their names as - * keys to various maps. This is an estimate only: an error might lead to - * inaccurate annotations being emitted, but that is ok: WPI should never be run - * without running the checker again afterwards to check the results. This field - * is only used when no element for the inner class is available, such as when - * it comes from another compilation unit. - */ - private int innerClassCount = 0; - - @Override - public void processClass( - ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { - addClass(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { - addClass(javacTree, javaParserNode); - } - - @Override - public void processClass( - ClassTree javacTree, RecordDeclaration javaParserNode) { - addClass(javacTree, javaParserNode); - } - - @Override - public void processClass( - ClassTree javacTree, AnnotationDeclaration javaParserNode) { - // TODO: consider supporting inferring annotations on annotation - // declarations. - // addClass(javacTree, javaParserNode); - } - - @Override - public void processNewClass( - NewClassTree javacTree, ObjectCreationExpr javaParserNode) { - ClassTree body = javacTree.getClassBody(); - if (body != null) { - addClass(body, null); - } - } - - /** - * Creates a wrapper around the class for {@code tree} and stores it in {@code - * sourceAnnos}. - * - *

This method computes the name of the class when the element corresponding - * to tree is null and uses it as the key for {@code classToAnnos} - * - * @param tree tree to add. Its corresponding name is used as the key for {@code - * classToAnnos}. - * @param javaParserNode the node corresponding to the declaration, which is - * used to place annotations on the class itself. Can be null, e.g. for an - * anonymous class. - */ - private void addClass( - ClassTree tree, @Nullable TypeDeclaration javaParserNode) { - String className; - // elementFromDeclaration returns null instead of crashing when no element - // exists for the class tree, which can happen for certain kinds of - // anonymous classes, such as classes, such as Ordering$1 in - // PolyCollectorTypeVar.java in the all-systems test suite. - TypeElement classElt = TreeUtils.elementFromDeclaration(tree); - if (classElt == null) { - // If such an element does not exist, compute the name of the class, - // instead. This method of computing the name is not 100% guaranteed to - // be reliable, but it should be sufficient for WPI's purposes here: if - // the wrong name is computed, the worst outcome is a false positive - // because WPI inferred an untrue annotation. - Optional ofqn = javaParserClass.getFullyQualifiedName(); - if (ofqn.isEmpty()) { - throw new BugInCF( - "Missing getFullyQualifiedName() for " + javaParserClass); - } - if ("".contentEquals(tree.getSimpleName())) { - @SuppressWarnings("signature:assignment") // computed from string - // concatenation - @BinaryName String computedName = ofqn.get() + "$" + ++innerClassCount; - className = computedName; - } else { - @SuppressWarnings("signature:assignment") // computed from string - // concatenation - @BinaryName String computedName = - ofqn.get() + "$" + tree.getSimpleName().toString(); - className = computedName; - } - } else { - className = ElementUtils.getBinaryName(classElt); - for (TypeElement supertypeElement : - ElementUtils.getSuperTypes(classElt, elements)) { - String supertypeName = ElementUtils.getBinaryName(supertypeElement); - Set<@BinaryName String> supertypeSet = - supertypesMap.computeIfAbsent( - className, k -> new TreeSet<@BinaryName String>()); - supertypeSet.add(supertypeName); - Set<@BinaryName String> subtypeSet = - subtypesMap.computeIfAbsent( - supertypeName, - k -> new TreeSet<@BinaryName String>()); - subtypeSet.add(className); - } - } - - ClassOrInterfaceAnnos typeWrapper = - new ClassOrInterfaceAnnos(className, javaParserNode); - if (!classToAnnos.containsKey(className)) { - classToAnnos.put(className, typeWrapper); - } - - sourceAnnos.types.add(typeWrapper); - } - - @Override - public void processMethod( - MethodTree javacTree, MethodDeclaration javaParserNode) { - addCallableDeclaration(javacTree, javaParserNode); - } - - @Override - public void processMethod( - MethodTree javacTree, ConstructorDeclaration javaParserNode) { - addCallableDeclaration(javacTree, javaParserNode); - } + public AnnotatedTypeMirror getReceiverType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { + if (receiverType == null) { + receiverType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); + } - /** - * Creates a wrapper around {@code javacTree} with the corresponding declaration - * {@code javaParserNode} and stores it in {@code sourceAnnos}. - * - * @param javacTree javac tree for declaration to add - * @param javaParserNode a JavaParser node for the same class as {@code - * javacTree} - */ - private void addCallableDeclaration( - MethodTree javacTree, CallableDeclaration javaParserNode) { - ExecutableElement element = TreeUtils.elementFromDeclaration(javacTree); - if (element == null) { - // element can be null if there is no element corresponding to the - // method, which happens for certain kinds of anonymous classes, - // such as Ordering$1 in PolyCollectorTypeVar.java in the - // all-systems test suite. - return; - } - String className = ElementUtils.getEnclosingClassName(element); - ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(className); - String executableSignature = JVMNames.getJVMMethodSignature(javacTree); - if (!enclosingClass.callableDeclarations.containsKey(executableSignature)) { - enclosingClass.callableDeclarations.put( - executableSignature, - new CallableDeclarationAnnos(javaParserNode)); - } - } - - @Override - public void processVariable( - VariableTree javacTree, EnumConstantDeclaration javaParserNode) { - VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); - if (!elt.getKind().isField()) { - throw new BugInCF(elt + " is not a field but a " + elt.getKind()); - } - - String enclosingClassName = ElementUtils.getEnclosingClassName(elt); - ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); - String fieldName = javacTree.getName().toString(); - enclosingClass.enumConstants.add(fieldName); - - // Ensure that if an enum constant defines a class, that class gets - // registered properly. See e.g. - // https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.9.1 - // for the specification of an enum constant, which does permit it to - // define an anonymous class. - NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); - ClassTree constructorClassBody = constructor.getClassBody(); - if (constructorClassBody != null) { - // addClass assumes there is an element for its argument, but that is - // not always true! - if (TreeUtils.elementFromDeclaration(constructorClassBody) != null) { - addClass(constructorClassBody, null); - } - } - } - - @Override - public void processVariable( - VariableTree javacTree, VariableDeclarator javaParserNode) { - VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); - if (!elt.getKind().isField()) { - return; - } - - String enclosingClassName = ElementUtils.getEnclosingClassName(elt); - ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); - String fieldName = javacTree.getName().toString(); - if (!enclosingClass.fields.containsKey(fieldName)) { - enclosingClass.fields.put(fieldName, new FieldAnnos(javaParserNode)); - } - } - }; - visitor.visitClass(javacClass, javaParserClass); + return receiverType; } /** - * Calls {@link #addSourceFile(String)} for the file containing the given element. + * If this wrapper holds a method, returns the inferred type of the return type. If necessary, + * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code + * atf} to a wrapper around the base type for the return type. * - * @param element the element for the source file to add - * @return path of the file containing {@code element} + * @param type base type for the return type, used for initializing the returned {@code + * AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will be + * used + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the return + * type */ - private String addClassesForElement(Element element) { - if (!ElementUtils.isElementFromSourceCode(element)) { - throw new BugInCF("Called addClassesForElement for non-source element: " + element); - } + public AnnotatedTypeMirror getReturnType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { + if (returnType == null) { + returnType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); + } - TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); - String path = ElementUtils.getSourceFilePath(toplevelClass); - if (toplevelClass.getKind() == ElementKind.ANNOTATION_TYPE) { - // Inferring annotations on elements of annotation declarations is not supported. - // One issue with supporting inference on annotation declaration elements is that - // AnnotatedTypeFactory#declarationFromElement returns null for annotation declarations - // quite commonly (because Trees#getTree, which it delegates to, does as well). - // In this case, we return path here without actually attempting to create the wrappers - // for the annotation declaration. The rest of WholeProgramInferenceJavaParserStorage - // already needs to handle classes without entries in the various tables (because of the - // possibility of classes outside the current compilation unit), so this is safe. - return path; - } - if (classToAnnos.containsKey(ElementUtils.getBinaryName(toplevelClass))) { - return path; - } - - addSourceFile(path); - CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); - ClassTree toplevelClassTree = - (ClassTree) atypeFactory.declarationFromElement(toplevelClass); - TypeDeclaration javaParserNode = - sourceAnnos.getClassOrInterfaceDeclarationByName( - toplevelClass.getSimpleName().toString()); - createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); - return path; + return returnType; } - /// - /// Writing to a file - /// - - // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because - // a scene may be modifed and written at any time, including before or after - // postProcessClassTree is called. - /** - * Side-effects the compilation unit annotations to make any desired changes before writing to a - * file. + * Returns the inferred declaration annotations on this executable. Returns an empty set if + * there are no annotations. * - * @param compilationUnitAnnos the compilation unit annotations to modify + * @return the declaration annotations for this callable declaration */ - public void wpiPrepareCompilationUnitForWriting(CompilationUnitAnnos compilationUnitAnnos) { - for (ClassOrInterfaceAnnos type : compilationUnitAnnos.types) { - wpiPrepareClassForWriting( - type, supertypesMap.get(type.className), subtypesMap.get(type.className)); - } + public AnnotationMirrorSet getDeclarationAnnotations() { + if (declarationAnnotations == null) { + return AnnotationMirrorSet.emptySet(); + } + + return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); } /** - * Side-effects the class annotations to make any desired changes before writing to a file. - * - *

Because of the side effect, clients may want to pass a copy into this method. + * Adds a declaration annotation to this callable declaration and returns whether it was a new + * annotation. * - * @param classAnnos the class annotations to modify - * @param supertypes the binary names of all supertypes; not side-effected - * @param subtypes the binary names of all subtypes; not side-effected + * @param annotation the declaration annotation to add + * @return true if {@code annotation} wasn't previously stored for this callable declaration */ - public void wpiPrepareClassForWriting( - ClassOrInterfaceAnnos classAnnos, - Collection<@BinaryName String> supertypes, - Collection<@BinaryName String> subtypes) { - if (classAnnos.callableDeclarations.isEmpty()) { - return; - } - - for (Map.Entry methodEntry : - classAnnos.callableDeclarations.entrySet()) { - String jvmSignature = methodEntry.getKey(); - List inSupertypes = - findOverrides(jvmSignature, supertypesMap.get(classAnnos.className)); - List inSubtypes = - findOverrides(jvmSignature, subtypesMap.get(classAnnos.className)); + public boolean addDeclarationAnnotation(AnnotationMirror annotation) { + if (declarationAnnotations == null) { + declarationAnnotations = new AnnotationMirrorSet(); + } - wpiPrepareMethodForWriting(methodEntry.getValue(), inSupertypes, inSubtypes); - } + return declarationAnnotations.add(annotation); } /** - * Return all the CallableDeclarationAnnos for the given signature. + * Attempts to remove the given declaration annotation from this callable declaration and + * returns whether an annotation was successfully removed. * - * @param jvmSignature the JVM signature - * @param typeNames a collection of type names - * @return the CallableDeclarationAnnos for the given signature, in all of the types + * @param anno an annotation + * @return true if {@code anno} was removed; false if it was not present or otherwise couldn't + * be removed */ - private List findOverrides( - String jvmSignature, @Nullable Collection<@BinaryName String> typeNames) { - if (typeNames == null) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); - for (String typeName : typeNames) { - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(typeName); - if (classAnnos != null) { - CallableDeclarationAnnos callableAnnos = - classAnnos.callableDeclarations.get(jvmSignature); - if (callableAnnos != null) { - result.add(callableAnnos); - } - } - } - return result; + /*package-private*/ boolean removeDeclarationAnnotation(AnnotationMirror anno) { + if (declarationAnnotations == null) { + return false; + } + return declarationAnnotations.remove(anno); } /** - * Side-effects the method or constructor annotations to make any desired changes before writing - * to a file. For example, this method may make inferred annotations consistent with one another - * between superclasses and subclasses. + * Returns the inferred preconditions for this callable declaration. The keys of the returned + * map use the same string formatting as the {@link + * org.checkerframework.framework.qual.RequiresQualifier} annotation, e.g. "#1" for the first + * parameter. * - * @param methodAnnos the method or constructor annotations to modify - * @param inSupertypes the method or constructor annotations for all overridden methods; not - * side-effected - * @param inSubtypes the method or constructor annotations for all overriding methods; not - * side-effected + *

Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and + * such changes will be reflected in the receiver CallableDeclarationAnnos object. + * + * @return a mapping from Java expression string to pairs of (inferred precondition for the + * expression, declared type of the expression) + * @see #getPreconditionsForExpression */ - // TODO: Inferred annotations must be consistent both with one another and with - // programmer-written annotations. The latter are stored in elements and, with the given formal - // parameter list, are not accessible to this method. In the future, the annotations stored in - // elements should also be passed to this method (or maybe they are already available to the - // type - // factory?). I'm leaving that enhancement until later. - public void wpiPrepareMethodForWriting( - CallableDeclarationAnnos methodAnnos, - Collection inSupertypes, - Collection inSubtypes) { - atypeFactory.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); - } - - @Override - public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { - if (outputFormat != OutputFormat.AJAVA) { - throw new BugInCF( - "WholeProgramInferenceJavaParser used with output format " + outputFormat); - } - - File outputDir = AJAVA_FILES_PATH; - if (!outputDir.exists()) { - outputDir.mkdirs(); - } - - setSupertypesAndSubtypesModified(); - - for (String path : modifiedFiles) { - // This calls deepCopy() because wpiPrepareCompilationUnitForWriting performs side - // effects that we don't want to be persistent. - CompilationUnitAnnos root = sourceToAnnos.get(path).deepCopy(); - wpiPrepareCompilationUnitForWriting(root); - File packageDir; - if (!root.compilationUnit.getPackageDeclaration().isPresent()) { - packageDir = AJAVA_FILES_PATH; - } else { - packageDir = - new File( - AJAVA_FILES_PATH, - root.compilationUnit - .getPackageDeclaration() - .get() - .getNameAsString() - .replaceAll("\\.", File.separator)); - } - - if (!packageDir.exists()) { - packageDir.mkdirs(); - } - - String name = new File(path).getName(); - if (name.endsWith(".java")) { - name = name.substring(0, name.length() - ".java".length()); - } - - String nameWithChecker = name + "-" + checker.getClass().getCanonicalName() + ".ajava"; - File outputPath = new File(packageDir, nameWithChecker); - if (this.inferOutputOriginal) { - File outputPathNoCheckerName = new File(packageDir, name + ".ajava"); - // Avoid re-writing this file for each checker that was run. - if (Files.notExists(outputPathNoCheckerName.toPath())) { - writeAjavaFile(outputPathNoCheckerName, root); - } - } - root.transferAnnotations(checker); - writeAjavaFile(outputPath, root); - } - - modifiedFiles.clear(); + public Map> getPreconditions() { + if (preconditions == null) { + return Collections.emptyMap(); + } else { + return Collections.unmodifiableMap(preconditions); + } } /** - * Write an ajava file to disk. + * Returns the inferred postconditions for this callable declaration. The keys of the returned + * map use the same string formatting as the {@link + * org.checkerframework.framework.qual.EnsuresQualifier} annotation, e.g. "#1" for the first + * parameter. + * + *

Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and + * such changes will be reflected in the receiver CallableDeclarationAnnos object. * - * @param outputPath the path to which the ajava file should be written - * @param root the compilation unit to be written + * @return a mapping from Java expression string to pairs of (inferred postcondition for the + * expression, declared type of the expression) + * @see #getPostconditionsForExpression */ - private void writeAjavaFile(File outputPath, CompilationUnitAnnos root) { - try (Writer writer = new BufferedWriter(new FileWriter(outputPath))) { - - // JavaParser can output using lexical preserving printing, which writes the file such - // that its formatting is close to the original source file it was parsed from as - // possible. Currently, this feature is very buggy and crashes when adding annotations - // in certain locations. This implementation could be used instead if it's fixed in - // JavaParser.LexicalPreservingPrinter.print(root.declaration, writer); - - // Do not print invisible qualifiers, to avoid cluttering the output. - Set invisibleQualifierNames = getInvisibleQualifierNames(this.atypeFactory); - DefaultPrettyPrinter prettyPrinter = - new DefaultPrettyPrinter() { - @Override - public String print(Node node) { - VoidVisitor visitor = - new DefaultPrettyPrinterVisitor(getConfiguration()) { - @Override - public void visit(MarkerAnnotationExpr n, Void arg) { - if (invisibleQualifierNames.contains( - n.getName().toString())) { - return; - } - super.visit(n, arg); - } - - @Override - public void visit(SingleMemberAnnotationExpr n, Void arg) { - if (invisibleQualifierNames.contains( - n.getName().toString())) { - return; - } - super.visit(n, arg); - } - - @Override - public void visit(NormalAnnotationExpr n, Void arg) { - if (invisibleQualifierNames.contains( - n.getName().toString())) { - return; - } - super.visit(n, arg); - } - }; - node.accept(visitor, null); - return visitor.toString(); - } - }; - - writer.write(prettyPrinter.print(root.compilationUnit)); - } catch (IOException e) { - throw new BugInCF("Error while writing ajava file " + outputPath, e); - } + public Map> getPostconditions() { + if (postconditions == null) { + return Collections.emptyMap(); + } + + return Collections.unmodifiableMap(postconditions); } /** - * Adds an explicit receiver type to a JavaParser method declaration. + * Returns an AnnotatedTypeMirror containing the preconditions for the given expression. Changes + * to the returned AnnotatedTypeMirror are reflected in this CallableDeclarationAnnos. * - * @param methodDeclaration declaration to add a receiver to + * @param expression a string representing a Java expression, in the same format as the argument + * to a {@link org.checkerframework.framework.qual.RequiresQualifier} annotation + * @param declaredType the declared type of {@code expression} + * @param atf the annotated type factory of a given type system, whose type hierarchy will be + * used + * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred + * preconditions for the given expression */ - private static void addExplicitReceiver(MethodDeclaration methodDeclaration) { - if (methodDeclaration.getReceiverParameter().isPresent()) { - return; - } - - com.github.javaparser.ast.Node parent = methodDeclaration.getParentNode().get(); - if (!(parent instanceof TypeDeclaration)) { - return; - } - - TypeDeclaration parentDecl = (TypeDeclaration) parent; - ClassOrInterfaceType receiver = new ClassOrInterfaceType(); - receiver.setName(parentDecl.getName()); - if (parentDecl.isClassOrInterfaceDeclaration()) { - ClassOrInterfaceDeclaration parentClassDecl = - parentDecl.asClassOrInterfaceDeclaration(); - if (!parentClassDecl.getTypeParameters().isEmpty()) { - NodeList typeArgs = new NodeList<>(); - for (TypeParameter typeParam : parentClassDecl.getTypeParameters()) { - ClassOrInterfaceType typeArg = new ClassOrInterfaceType(); - typeArg.setName(typeParam.getNameAsString()); - typeArgs.add(typeArg); - } - - receiver.setTypeArguments(typeArgs); - } - } - - methodDeclaration.setReceiverParameter(new ReceiverParameter(receiver, "this")); + public AnnotatedTypeMirror getPreconditionsForExpression( + String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { + if (preconditions == null) { + preconditions = new HashMap<>(1); + } + + if (!preconditions.containsKey(expression)) { + AnnotatedTypeMirror preconditionsType = + AnnotatedTypeMirror.createType(declaredType.getUnderlyingType(), atf, false); + preconditions.put(expression, IPair.of(preconditionsType, declaredType)); + } + + return preconditions.get(expression).first; } /** - * Transfers all annotations for {@code annotatedType} and its nested types to {@code target}, - * which is the JavaParser node representing the same type. Does nothing if {@code - * annotatedType} is null (this may occur if there are no inferred annotations for the type). + * Returns an AnnotatedTypeMirror containing the postconditions for the given expression. + * Changes to the returned AnnotatedTypeMirror are reflected in this CallableDeclarationAnnos. * - * @param annotatedType type to transfer annotations from - * @param target the JavaParser type to transfer annotation to; must represent the same type as - * {@code annotatedType} + * @param expression a string representing a Java expression, in the same format as the argument + * to a {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation + * @param declaredType the declared type of {@code expression} + * @param atf the annotated type factory of a given type system, whose type hierarchy will be + * used + * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred + * postconditions for the given expression */ - private static void transferAnnotations( - @Nullable AnnotatedTypeMirror annotatedType, Type target) { - if (annotatedType == null) { - return; - } - - target.accept(new AnnotationTransferVisitor(), annotatedType); + public AnnotatedTypeMirror getPostconditionsForExpression( + String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { + if (postconditions == null) { + postconditions = new HashMap<>(1); + } + + if (!postconditions.containsKey(expression)) { + AnnotatedTypeMirror postconditionsType = + AnnotatedTypeMirror.createType(declaredType.getUnderlyingType(), atf, false); + postconditions.put(expression, IPair.of(postconditionsType, declaredType)); + } + + return postconditions.get(expression).first; } - /// - /// Storing annotations - /// - /** - * Stores the JavaParser node for a compilation unit and the list of wrappers for the classes - * and interfaces in that compilation unit. + * Transfers all annotations inferred by whole program inference for the return type, receiver + * type, and parameter types for the wrapped declaration to their corresponding JavaParser + * locations. */ - private static class CompilationUnitAnnos implements DeepCopyable { - /** Compilation unit being wrapped. */ - public final CompilationUnit compilationUnit; - - /** Wrappers for classes and interfaces in {@code compilationUnit}. */ - public final List types; - - /** - * Constructs a wrapper around the given compilation unit. - * - * @param compilationUnit compilation unit to wrap - */ - public CompilationUnitAnnos(CompilationUnit compilationUnit) { - this.compilationUnit = compilationUnit; - this.types = new ArrayList<>(); + public void transferAnnotations() { + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + GenericAnnotatedTypeFactory genericAtf = + (GenericAnnotatedTypeFactory) atypeFactory; + for (AnnotationMirror contractAnno : genericAtf.getContractAnnotations(this)) { + declaration.addAnnotation( + AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( + contractAnno)); + } + } + + if (declarationAnnotations != null && declaration != null) { + for (AnnotationMirror annotation : declarationAnnotations) { + declaration.addAnnotation( + AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( + annotation)); + } + } + + if (paramsDeclAnnos != null) { + for (IPair pair : paramsDeclAnnos) { + Parameter param = declaration.getParameter(pair.first - 1); + param.addAnnotation( + AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( + pair.second)); + } + } + + if (returnType != null) { + // If a return type exists, then the declaration must be a method, not a + // constructor. + WholeProgramInferenceJavaParserStorage.transferAnnotations( + returnType, declaration.asMethodDeclaration().getType()); + } + + if (receiverType != null) { + addExplicitReceiver(declaration.asMethodDeclaration()); + // The receiver won't be present for an anonymous class. + if (declaration.getReceiverParameter().isPresent()) { + WholeProgramInferenceJavaParserStorage.transferAnnotations( + receiverType, declaration.getReceiverParameter().get().getType()); + } + } + + if (parameterTypes == null) { + return; + } + + for (int i = 0; i < parameterTypes.size(); i++) { + AnnotatedTypeMirror inferredType = parameterTypes.get(i); + if (inferredType == null) { + // Can occur if the only places that this method was called were + // outside the compilation unit. + continue; + } + Parameter param = declaration.getParameter(i); + Type javaParserType = param.getType(); + if (param.isVarArgs()) { + NodeList varArgsAnnoExprs = + AnnotationMirrorToAnnotationExprConversion.annotationMirrorSetToAnnotationExprList( + inferredType.getAnnotations()); + param.setVarArgsAnnotations(varArgsAnnoExprs); + + AnnotatedTypeMirror inferredComponentType = + ((AnnotatedArrayType) inferredType).getComponentType(); + WholeProgramInferenceJavaParserStorage.transferAnnotations( + inferredComponentType, javaParserType); + } else { + WholeProgramInferenceJavaParserStorage.transferAnnotations(inferredType, javaParserType); } + } + } - /** - * Private constructor for use by deepCopy(). - * - * @param compilationUnit compilation unit to wrap - * @param types wrappers for classes and interfaces in {@code compilationUnit} - */ - private CompilationUnitAnnos( - CompilationUnit compilationUnit, List types) { - this.compilationUnit = compilationUnit; - this.types = types; - } + @Override + public String toString() { + return "CallableDeclarationAnnos [declaration=" + + declaration + + ", parameterTypes=" + + parameterTypes + + ", receiverType=" + + receiverType + + ", returnType=" + + returnType + + "]"; + } + } + + /** + * Deep copy (according to the {@code DeepCopyable} interface) a pre- or post-condition map. + * + * @param orig the map to copy + * @return a deep copy of the map + */ + private static @Nullable Map> + deepCopyMapOfStringToPair( + @Nullable Map> orig) { + if (orig == null) { + return null; + } + Map> result = + new HashMap<>(CollectionsPlume.mapCapacity(orig.size())); + result.clear(); + for (Map.Entry> entry : + orig.entrySet()) { + String javaExpression = entry.getKey(); + IPair atms = entry.getValue(); + result.put(javaExpression, IPair.deepCopy(atms)); + } + return result; + } - @Override - public CompilationUnitAnnos deepCopy() { - return new CompilationUnitAnnos(compilationUnit, CollectionsPlume.deepCopy(types)); - } + /** Stores the JavaParser node for a field and the annotations that have been inferred for it. */ + private static class FieldAnnos implements DeepCopyable { + /** Wrapped field declaration. */ + public final VariableDeclarator declaration; - /** - * Transfers all annotations inferred by whole program inference for the wrapped compilation - * unit to their corresponding JavaParser locations. - * - * @param checker the checker who's name to include in the @AnnotatedFor annotation - */ - public void transferAnnotations(BaseTypeChecker checker) { - JavaParserUtil.clearAnnotations(compilationUnit); - for (TypeDeclaration typeDecl : compilationUnit.getTypes()) { - typeDecl.addSingleMemberAnnotation( - "org.checkerframework.framework.qual.AnnotatedFor", - "\"" + checker.getClass().getCanonicalName() + "\""); - } + /** Inferred type for field, initialized the first time it's accessed. */ + private @MonotonicNonNull AnnotatedTypeMirror type = null; - for (ClassOrInterfaceAnnos typeAnnos : types) { - typeAnnos.transferAnnotations(); - } - } + /** Annotations on the field declaration. */ + private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; - /** - * Returns the top-level type declaration named {@code name} in the compilation unit. - * - * @param name name of type declaration - * @return the type declaration named {@code name} in the wrapped compilation unit - */ - public TypeDeclaration getClassOrInterfaceDeclarationByName(String name) { - return JavaParserUtil.getTypeDeclarationByName(compilationUnit, name); - } + /** + * Creates a wrapper for the given field declaration. + * + * @param declaration field declaration to wrap + */ + public FieldAnnos(VariableDeclarator declaration) { + this.declaration = declaration; + } - /** - * Returns a verbose printed representation of this. - * - * @return a verbose printed representation of this - */ - @SuppressWarnings("UnusedMethod") - public String toStringVerbose() { - StringJoiner sb = new StringJoiner(System.lineSeparator()); - sb.add("CompilationUnitAnnos:"); - for (ClassOrInterfaceAnnos type : types) { - sb.add(type.toStringVerbose()); - } - return sb.toString(); - } + @Override + public FieldAnnos deepCopy() { + FieldAnnos result = new FieldAnnos(declaration); + result.type = DeepCopyable.deepCopyOrNull(this.type); + result.declarationAnnotations = DeepCopyable.deepCopyOrNull(this.declarationAnnotations); + return result; } /** - * Stores wrappers for the locations where annotations may be inferred in a class or interface. + * Returns the inferred type of the field. If necessary, initializes the {@code + * AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper around + * the base type for the field. + * + * @param type base type for the field, used for initializing the returned {@code + * AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will be + * used + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the field */ - private static class ClassOrInterfaceAnnos implements DeepCopyable { - /** - * Mapping from JVM method signatures to the wrapper containing the corresponding - * executable. - */ - public Map callableDeclarations = new HashMap<>(); - - /** Mapping from field names to wrappers for those fields. */ - public Map fields = new HashMap<>(2); - - /** Collection of declared enum constants (empty if not an enum). */ - public Set enumConstants = new HashSet<>(2); - - /** - * Annotations on the declaration of the class (note that despite the name, these can also - * be type annotations). - */ - private @MonotonicNonNull AnnotationMirrorSet classAnnotations = null; - - /** - * The JavaParser TypeDeclaration representing the class's declaration. Used for placing - * annotations inferred on the class declaration itself. - */ - private @MonotonicNonNull TypeDeclaration classDeclaration; - - /** The binary name of the class. */ - private @BinaryName String className; - - /** - * Create a new ClassOrInterfaceAnnos. - * - * @param className the binary name of the class - * @param javaParserNode the JavaParser node corresponding to the class declaration, which - * is used for placing annotations on the class declaration - */ - public ClassOrInterfaceAnnos( - @BinaryName String className, @Nullable TypeDeclaration javaParserNode) { - this.classDeclaration = javaParserNode; - this.className = className; - } - - @Override - public ClassOrInterfaceAnnos deepCopy() { - ClassOrInterfaceAnnos result = new ClassOrInterfaceAnnos(className, classDeclaration); - result.callableDeclarations = CollectionsPlume.deepCopyValues(callableDeclarations); - result.fields = CollectionsPlume.deepCopyValues(fields); - result.enumConstants = - UtilPlume.clone(enumConstants); // no deep copy: elements are strings - if (classAnnotations != null) { - result.classAnnotations = classAnnotations.deepCopy(); - } - // no need to change classDeclaration - return result; - } - - /** - * Adds {@code annotation} to the set of annotations on the declaration of this class. - * - * @param annotation an annotation (can be declaration or type) - * @return true if this is a new annotation for this class - */ - public boolean addAnnotationToClassDeclaration(AnnotationMirror annotation) { - if (classAnnotations == null) { - classAnnotations = new AnnotationMirrorSet(); - } - - return classAnnotations.add(annotation); - } - - /** - * Transfers all annotations inferred by whole program inference for the methods and fields - * in the wrapper class or interface to their corresponding JavaParser locations. - */ - public void transferAnnotations() { - for (CallableDeclarationAnnos callableAnnos : callableDeclarations.values()) { - callableAnnos.transferAnnotations(); - } - - if (classAnnotations != null && classDeclaration != null) { - for (AnnotationMirror annotation : classAnnotations) { - classDeclaration.addAnnotation( - AnnotationMirrorToAnnotationExprConversion - .annotationMirrorToAnnotationExpr(annotation)); - } - } - - for (FieldAnnos field : fields.values()) { - field.transferAnnotations(); - } - } - - @Override - public String toString() { - String fieldsString = fields.toString(); - if (fieldsString.length() > 100) { - // The quoting increases the likelihood that all delimiters are balanced in the - // result. - // That makes it easier to manipulate the result (such as skipping over it) in an - // editor. The quoting also makes clear that the value is truncated. - fieldsString = "\"" + fieldsString.substring(0, 95) + "...\""; - } - - return "ClassOrInterfaceAnnos [" - + (classDeclaration == null ? "unnamed" : classDeclaration.getName()) - + ": callableDeclarations=" - // For deterministic output - + new TreeMap<>(callableDeclarations) - + ", fields=" - + fieldsString - + "]"; - } + public AnnotatedTypeMirror getType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { + if (this.type == null) { + this.type = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); + } - /** - * Returns a verbose printed representation of this. - * - * @return a verbose printed representation of this - */ - public String toStringVerbose() { - return toString(); - } + return this.type; } /** - * Stores the JavaParser node for a method or constructor and the annotations that have been - * inferred about its parameters and return type. + * Returns the inferred declaration annotations on this field, or an empty set if there are no + * annotations. + * + * @return the declaration annotations for this field declaration */ - public class CallableDeclarationAnnos implements DeepCopyable { - /** Wrapped method or constructor declaration. */ - public final CallableDeclaration declaration; - - /** - * Inferred annotations for the return type, if the declaration represents a method. - * Initialized on first usage. - */ - private @MonotonicNonNull AnnotatedTypeMirror returnType = null; - - /** - * Inferred annotations for the receiver type, if the declaration represents a method. - * Initialized on first usage. - */ - private @MonotonicNonNull AnnotatedTypeMirror receiverType = null; - - /** - * Inferred annotations for parameter types. The list is initialized the first time any - * parameter is accessed, and each parameter is initialized the first time it's accessed. - */ - private @MonotonicNonNull List<@Nullable AnnotatedTypeMirror> parameterTypes = null; - - /** Declaration annotations on the parameters. */ - private @MonotonicNonNull Set> paramsDeclAnnos = null; - - /** - * Annotations on the callable declaration. This does not include preconditions and - * postconditions. - */ - private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; - - /** - * Mapping from expression strings to pairs of (inferred precondition, declared type). The - * keys are strings representing JavaExpressions, using the same format as a user would in - * an {@link org.checkerframework.framework.qual.RequiresQualifier} annotation. - */ - private @MonotonicNonNull Map> - preconditions = null; - - /** - * Mapping from expression strings to pairs of (inferred postcondition, declared type). The - * okeys are strings representing JavaExpressions, using the same format as a user would in - * an {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation. - */ - private @MonotonicNonNull Map> - postconditions = null; - - /** - * Creates a wrapper for the given method or constructor declaration. - * - * @param declaration method or constructor declaration to wrap - */ - public CallableDeclarationAnnos(CallableDeclaration declaration) { - this.declaration = declaration; - } - - @Override - public CallableDeclarationAnnos deepCopy() { - CallableDeclarationAnnos result = new CallableDeclarationAnnos(declaration); - result.returnType = DeepCopyable.deepCopyOrNull(this.returnType); - result.receiverType = DeepCopyable.deepCopyOrNull(this.receiverType); - if (parameterTypes != null) { - result.parameterTypes = CollectionsPlume.deepCopy(this.parameterTypes); - } - result.declarationAnnotations = - DeepCopyable.deepCopyOrNull(this.declarationAnnotations); - - if (this.paramsDeclAnnos != null) { - result.paramsDeclAnnos = new ArraySet<>(this.paramsDeclAnnos); - } - result.preconditions = deepCopyMapOfStringToPair(this.preconditions); - result.postconditions = deepCopyMapOfStringToPair(this.postconditions); - return result; - } - - /** - * Returns the inferred type for the parameter at the given index. If necessary, initializes - * the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a - * wrapper around the base type for the parameter. - * - * @param type type for the parameter at {@code index}, used for initializing the returned - * {@code AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will - * be used - * @param index_1based index of the parameter to return the inferred annotations of - * (1-based) - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the - * parameter at the given index - */ - public AnnotatedTypeMirror getParameterTypeInitialized( - AnnotatedTypeMirror type, @Positive int index_1based, AnnotatedTypeFactory atf) { - // 0-based index - int i = index_1based - 1; - - if (parameterTypes == null) { - parameterTypes = - new ArrayList<>( - Collections.nCopies(declaration.getParameters().size(), null)); - } - - if (parameterTypes.get(i) == null) { - parameterTypes.set( - i, AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false)); - } - - return parameterTypes.get(i); - } - - /** - * Returns the inferred type for the parameter at the given index, or null if there's no - * parameter at the given index or there's no inferred type for that parameter. - * - * @param index index of the parameter to return the inferred annotations of - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the - * parameter at the given index, or null if there's no parameter at {@code index} or if - * there's not inferred annotations for that parameter - */ - public @Nullable AnnotatedTypeMirror getParameterType(int index) { - if (parameterTypes == null || index < 0 || index >= parameterTypes.size()) { - return null; - } - - return parameterTypes.get(index); - } - - /** - * Adds a declaration annotation to this parameter and returns whether it was a new - * annotation. - * - * @param annotation the declaration annotation to add - * @param index_1based index of the parameter (1-indexed) - * @return true if {@code annotation} wasn't previously stored for this parameter - */ - public boolean addDeclarationAnnotationToFormalParameter( - AnnotationMirror annotation, @Positive int index_1based) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - if (paramsDeclAnnos == null) { - // There are usually few formal parameters. - paramsDeclAnnos = new ArraySet<>(4); - } - - return paramsDeclAnnos.add(IPair.of(index_1based, annotation)); - } - - /** - * If this wrapper holds a method, returns the inferred type of the receiver. If necessary, - * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and - * {@code atf} to a wrapper around the base type for the receiver type. - * - * @param type base type for the receiver type, used for initializing the returned {@code - * AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will - * be used - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the - * receiver type - */ - public AnnotatedTypeMirror getReceiverType( - AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { - if (receiverType == null) { - receiverType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); - } - - return receiverType; - } - - /** - * If this wrapper holds a method, returns the inferred type of the return type. If - * necessary, initializes the {@code AnnotatedTypeMirror} for that location using {@code - * type} and {@code atf} to a wrapper around the base type for the return type. - * - * @param type base type for the return type, used for initializing the returned {@code - * AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will - * be used - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the return - * type - */ - public AnnotatedTypeMirror getReturnType( - AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { - if (returnType == null) { - returnType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); - } - - return returnType; - } - - /** - * Returns the inferred declaration annotations on this executable. Returns an empty set if - * there are no annotations. - * - * @return the declaration annotations for this callable declaration - */ - public AnnotationMirrorSet getDeclarationAnnotations() { - if (declarationAnnotations == null) { - return AnnotationMirrorSet.emptySet(); - } - - return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); - } - - /** - * Adds a declaration annotation to this callable declaration and returns whether it was a - * new annotation. - * - * @param annotation the declaration annotation to add - * @return true if {@code annotation} wasn't previously stored for this callable declaration - */ - public boolean addDeclarationAnnotation(AnnotationMirror annotation) { - if (declarationAnnotations == null) { - declarationAnnotations = new AnnotationMirrorSet(); - } - - return declarationAnnotations.add(annotation); - } - - /** - * Attempts to remove the given declaration annotation from this callable declaration and - * returns whether an annotation was successfully removed. - * - * @param anno an annotation - * @return true if {@code anno} was removed; false if it was not present or otherwise - * couldn't be removed - */ - /*package-private*/ boolean removeDeclarationAnnotation(AnnotationMirror anno) { - if (declarationAnnotations == null) { - return false; - } - return declarationAnnotations.remove(anno); - } - - /** - * Returns the inferred preconditions for this callable declaration. The keys of the - * returned map use the same string formatting as the {@link - * org.checkerframework.framework.qual.RequiresQualifier} annotation, e.g. "#1" for the - * first parameter. - * - *

Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and - * such changes will be reflected in the receiver CallableDeclarationAnnos object. - * - * @return a mapping from Java expression string to pairs of (inferred precondition for the - * expression, declared type of the expression) - * @see #getPreconditionsForExpression - */ - public Map> getPreconditions() { - if (preconditions == null) { - return Collections.emptyMap(); - } else { - return Collections.unmodifiableMap(preconditions); - } - } - - /** - * Returns the inferred postconditions for this callable declaration. The keys of the - * returned map use the same string formatting as the {@link - * org.checkerframework.framework.qual.EnsuresQualifier} annotation, e.g. "#1" for the first - * parameter. - * - *

Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and - * such changes will be reflected in the receiver CallableDeclarationAnnos object. - * - * @return a mapping from Java expression string to pairs of (inferred postcondition for the - * expression, declared type of the expression) - * @see #getPostconditionsForExpression - */ - public Map> getPostconditions() { - if (postconditions == null) { - return Collections.emptyMap(); - } - - return Collections.unmodifiableMap(postconditions); - } - - /** - * Returns an AnnotatedTypeMirror containing the preconditions for the given expression. - * Changes to the returned AnnotatedTypeMirror are reflected in this - * CallableDeclarationAnnos. - * - * @param expression a string representing a Java expression, in the same format as the - * argument to a {@link org.checkerframework.framework.qual.RequiresQualifier} - * annotation - * @param declaredType the declared type of {@code expression} - * @param atf the annotated type factory of a given type system, whose type hierarchy will - * be used - * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred - * preconditions for the given expression - */ - public AnnotatedTypeMirror getPreconditionsForExpression( - String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { - if (preconditions == null) { - preconditions = new HashMap<>(1); - } - - if (!preconditions.containsKey(expression)) { - AnnotatedTypeMirror preconditionsType = - AnnotatedTypeMirror.createType( - declaredType.getUnderlyingType(), atf, false); - preconditions.put(expression, IPair.of(preconditionsType, declaredType)); - } - - return preconditions.get(expression).first; - } - - /** - * Returns an AnnotatedTypeMirror containing the postconditions for the given expression. - * Changes to the returned AnnotatedTypeMirror are reflected in this - * CallableDeclarationAnnos. - * - * @param expression a string representing a Java expression, in the same format as the - * argument to a {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation - * @param declaredType the declared type of {@code expression} - * @param atf the annotated type factory of a given type system, whose type hierarchy will - * be used - * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred - * postconditions for the given expression - */ - public AnnotatedTypeMirror getPostconditionsForExpression( - String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { - if (postconditions == null) { - postconditions = new HashMap<>(1); - } - - if (!postconditions.containsKey(expression)) { - AnnotatedTypeMirror postconditionsType = - AnnotatedTypeMirror.createType( - declaredType.getUnderlyingType(), atf, false); - postconditions.put(expression, IPair.of(postconditionsType, declaredType)); - } - - return postconditions.get(expression).first; - } - - /** - * Transfers all annotations inferred by whole program inference for the return type, - * receiver type, and parameter types for the wrapped declaration to their corresponding - * JavaParser locations. - */ - public void transferAnnotations() { - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory genericAtf = - (GenericAnnotatedTypeFactory) atypeFactory; - for (AnnotationMirror contractAnno : genericAtf.getContractAnnotations(this)) { - declaration.addAnnotation( - AnnotationMirrorToAnnotationExprConversion - .annotationMirrorToAnnotationExpr(contractAnno)); - } - } - - if (declarationAnnotations != null && declaration != null) { - for (AnnotationMirror annotation : declarationAnnotations) { - declaration.addAnnotation( - AnnotationMirrorToAnnotationExprConversion - .annotationMirrorToAnnotationExpr(annotation)); - } - } - - if (paramsDeclAnnos != null) { - for (IPair pair : paramsDeclAnnos) { - Parameter param = declaration.getParameter(pair.first - 1); - param.addAnnotation( - AnnotationMirrorToAnnotationExprConversion - .annotationMirrorToAnnotationExpr(pair.second)); - } - } - - if (returnType != null) { - // If a return type exists, then the declaration must be a method, not a - // constructor. - WholeProgramInferenceJavaParserStorage.transferAnnotations( - returnType, declaration.asMethodDeclaration().getType()); - } - - if (receiverType != null) { - addExplicitReceiver(declaration.asMethodDeclaration()); - // The receiver won't be present for an anonymous class. - if (declaration.getReceiverParameter().isPresent()) { - WholeProgramInferenceJavaParserStorage.transferAnnotations( - receiverType, declaration.getReceiverParameter().get().getType()); - } - } - - if (parameterTypes == null) { - return; - } - - for (int i = 0; i < parameterTypes.size(); i++) { - AnnotatedTypeMirror inferredType = parameterTypes.get(i); - if (inferredType == null) { - // Can occur if the only places that this method was called were - // outside the compilation unit. - continue; - } - Parameter param = declaration.getParameter(i); - Type javaParserType = param.getType(); - if (param.isVarArgs()) { - NodeList varArgsAnnoExprs = - AnnotationMirrorToAnnotationExprConversion - .annotationMirrorSetToAnnotationExprList( - inferredType.getAnnotations()); - param.setVarArgsAnnotations(varArgsAnnoExprs); - - AnnotatedTypeMirror inferredComponentType = - ((AnnotatedArrayType) inferredType).getComponentType(); - WholeProgramInferenceJavaParserStorage.transferAnnotations( - inferredComponentType, javaParserType); - } else { - WholeProgramInferenceJavaParserStorage.transferAnnotations( - inferredType, javaParserType); - } - } - } + @SuppressWarnings("UnusedMethod") + public AnnotationMirrorSet getDeclarationAnnotations() { + if (declarationAnnotations == null) { + return AnnotationMirrorSet.emptySet(); + } - @Override - public String toString() { - return "CallableDeclarationAnnos [declaration=" - + declaration - + ", parameterTypes=" - + parameterTypes - + ", receiverType=" - + receiverType - + ", returnType=" - + returnType - + "]"; - } + return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); } /** - * Deep copy (according to the {@code DeepCopyable} interface) a pre- or post-condition map. + * Adds a declaration annotation to this field declaration and returns whether it was a new + * annotation. * - * @param orig the map to copy - * @return a deep copy of the map + * @param annotation declaration annotation to add + * @return true if {@code annotation} wasn't previously stored for this field declaration */ - private static @Nullable Map> - deepCopyMapOfStringToPair( - @Nullable Map> orig) { - if (orig == null) { - return null; - } - Map> result = - new HashMap<>(CollectionsPlume.mapCapacity(orig.size())); - result.clear(); - for (Map.Entry> entry : - orig.entrySet()) { - String javaExpression = entry.getKey(); - IPair atms = entry.getValue(); - result.put(javaExpression, IPair.deepCopy(atms)); - } - return result; + public boolean addDeclarationAnnotation(AnnotationMirror annotation) { + if (declarationAnnotations == null) { + declarationAnnotations = new AnnotationMirrorSet(); + } + + return declarationAnnotations.add(annotation); } /** - * Stores the JavaParser node for a field and the annotations that have been inferred for it. + * Transfers all annotations inferred by whole program inference on this field to the JavaParser + * nodes for that field. */ - private static class FieldAnnos implements DeepCopyable { - /** Wrapped field declaration. */ - public final VariableDeclarator declaration; - - /** Inferred type for field, initialized the first time it's accessed. */ - private @MonotonicNonNull AnnotatedTypeMirror type = null; - - /** Annotations on the field declaration. */ - private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; - - /** - * Creates a wrapper for the given field declaration. - * - * @param declaration field declaration to wrap - */ - public FieldAnnos(VariableDeclarator declaration) { - this.declaration = declaration; - } - - @Override - public FieldAnnos deepCopy() { - FieldAnnos result = new FieldAnnos(declaration); - result.type = DeepCopyable.deepCopyOrNull(this.type); - result.declarationAnnotations = - DeepCopyable.deepCopyOrNull(this.declarationAnnotations); - return result; - } - - /** - * Returns the inferred type of the field. If necessary, initializes the {@code - * AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper - * around the base type for the field. - * - * @param type base type for the field, used for initializing the returned {@code - * AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will - * be used - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the field - */ - public AnnotatedTypeMirror getType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { - if (this.type == null) { - this.type = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); - } - - return this.type; - } - - /** - * Returns the inferred declaration annotations on this field, or an empty set if there are - * no annotations. - * - * @return the declaration annotations for this field declaration - */ - @SuppressWarnings("UnusedMethod") - public AnnotationMirrorSet getDeclarationAnnotations() { - if (declarationAnnotations == null) { - return AnnotationMirrorSet.emptySet(); - } - - return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); - } - - /** - * Adds a declaration annotation to this field declaration and returns whether it was a new - * annotation. - * - * @param annotation declaration annotation to add - * @return true if {@code annotation} wasn't previously stored for this field declaration - */ - public boolean addDeclarationAnnotation(AnnotationMirror annotation) { - if (declarationAnnotations == null) { - declarationAnnotations = new AnnotationMirrorSet(); - } - - return declarationAnnotations.add(annotation); - } - - /** - * Transfers all annotations inferred by whole program inference on this field to the - * JavaParser nodes for that field. - */ - public void transferAnnotations() { - if (declarationAnnotations != null) { - // Don't add directly to the type of the variable declarator, - // because declaration annotations need to be attached to the FieldDeclaration - // node instead. - Node declParent = declaration.getParentNode().orElse(null); - if (declParent instanceof FieldDeclaration) { - FieldDeclaration decl = (FieldDeclaration) declParent; - for (AnnotationMirror annotation : declarationAnnotations) { - decl.addAnnotation( - AnnotationMirrorToAnnotationExprConversion - .annotationMirrorToAnnotationExpr(annotation)); - } - } - } - - // Don't transfer type annotations to variable declarators with sibling - // variable declarators, because they're printed incorrectly (as "???"). - // (A variable declarator can have siblings if it's part of a declaration - // like "int x, y, z;", which is bad style but legal Java.) - // In any event, WPI doesn't consider the LUB of the types of the siblings, - // so any inferred type is likely to be wrong. - // TODO: avoid inferring these types at all, or take the LUB of all assignments - // to the siblings. Unfortunately, VariableElements don't track whether they have - // siblings, and there's no other information about the declaration for - // WholeProgramInferenceImplementation to use: to determine that there are siblings, - // a parse tree is needed. - boolean foundVariableDeclarator = false; - for (Node child : this.declaration.getParentNode().get().getChildNodes()) { - if (child instanceof VariableDeclarator) { - if (foundVariableDeclarator) { - // This is the second VariableDeclarator that was found. - return; - } - foundVariableDeclarator = true; - } - } - Type newType = (Type) declaration.getType().accept(new CloneVisitor(), null); - WholeProgramInferenceJavaParserStorage.transferAnnotations(type, newType); - declaration.setType(newType); + public void transferAnnotations() { + if (declarationAnnotations != null) { + // Don't add directly to the type of the variable declarator, + // because declaration annotations need to be attached to the FieldDeclaration + // node instead. + Node declParent = declaration.getParentNode().orElse(null); + if (declParent instanceof FieldDeclaration) { + FieldDeclaration decl = (FieldDeclaration) declParent; + for (AnnotationMirror annotation : declarationAnnotations) { + decl.addAnnotation( + AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( + annotation)); + } + } + } + + // Don't transfer type annotations to variable declarators with sibling + // variable declarators, because they're printed incorrectly (as "???"). + // (A variable declarator can have siblings if it's part of a declaration + // like "int x, y, z;", which is bad style but legal Java.) + // In any event, WPI doesn't consider the LUB of the types of the siblings, + // so any inferred type is likely to be wrong. + // TODO: avoid inferring these types at all, or take the LUB of all assignments + // to the siblings. Unfortunately, VariableElements don't track whether they have + // siblings, and there's no other information about the declaration for + // WholeProgramInferenceImplementation to use: to determine that there are siblings, + // a parse tree is needed. + boolean foundVariableDeclarator = false; + for (Node child : this.declaration.getParentNode().get().getChildNodes()) { + if (child instanceof VariableDeclarator) { + if (foundVariableDeclarator) { + // This is the second VariableDeclarator that was found. + return; + } + foundVariableDeclarator = true; } + } + Type newType = (Type) declaration.getType().accept(new CloneVisitor(), null); + WholeProgramInferenceJavaParserStorage.transferAnnotations(type, newType); + declaration.setType(newType); + } - @Override - public String toString() { - return "FieldAnnos [declaration=" + declaration + ", type=" + type + "]"; - } + @Override + public String toString() { + return "FieldAnnos [declaration=" + declaration + ", type=" + type + "]"; } + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java index d41961853ea..cbe362f65cd 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java @@ -4,7 +4,22 @@ import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; - +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Target; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.afu.scenelib.Annotation; import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AClass; @@ -50,24 +65,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; -import java.io.File; -import java.io.IOException; -import java.lang.annotation.Target; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * This class stores annotations using scenelib objects. * @@ -84,956 +81,940 @@ * will be better. {@link #writeScenes} rewrites the initial .jaif files and may create new ones. */ public class WholeProgramInferenceScenesStorage - implements WholeProgramInferenceStorage { - - /** - * Directory where .jaif files will be written to and read from. This directory is relative to - * where the CF's javac command is executed. - */ - public static final String JAIF_FILES_PATH = - "build" + File.separator + "whole-program-inference" + File.separator; - - /** The type factory associated with this WholeProgramInferenceScenesStorage. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** Annotations that should not be output to a .jaif or stub file. */ - private final AnnotationsInContexts annosToIgnore = new AnnotationsInContexts(); - - /** - * If true, assignments where the rhs is null are be ignored. - * - *

If all assignments to a variable are null (because inference is being done with respect to - * a limited set of uses) then the variable is inferred to have bottom type. That inference is - * unlikely to be correct. To avoid that inference, set this variable to true. When the variable - * is true, if all assignments are null, then none are recorded, no inference is done, and the - * variable remains at its default type. - */ - private final boolean ignoreNullAssignments; - - /** Maps .jaif file paths (Strings) to Scenes. Relative to JAIF_FILES_PATH. */ - public final Map scenes = new HashMap<>(); - - /** - * Scenes that were modified since the last time all Scenes were written into .jaif files. Each - * String element of this set is a path (relative to JAIF_FILES_PATH) to the .jaif file of the - * corresponding Scene in the set. It is obtained by passing a class name as argument to the - * {@link #getJaifPath} method. - * - *

Modifying a Scene means adding (or changing) a type annotation for a field, method return - * type, or method parameter type in the Scene. (Scenes are modified by the method {@link - * #updateAnnotationSetInScene}.) - */ - public final Set modifiedScenes = new HashSet<>(); - - /** - * This map relates inferred preconditions to the declared types of the expressions to which the - * precondition applies. It is necessary to keep this map here because the AFU does not have a - * dependency on the CF itself, where AnnotatedTypeMirror exists. - * - *

The keys are the concatenation of the string representation of the method signature as - * stored by {@link AMethod} to which the precondition applies and the expression to which the - * precondition applies. - */ - private final Map preconditionsToDeclaredTypes = new HashMap<>(); - - /** - * This map relates inferred postconditions to the declared types of the expressions to which - * the postcondition applies. It is necessary to keep this map here because the AFU does not - * have a dependency on the CF itself, where AnnotatedTypeMirror exists. - * - *

The keys are the concatenation of the string representation of the method signature as - * stored by {@link AMethod} to which the postcondition applies and the expression to which the - * postcondition applies. - */ - private final Map postconditionsToDeclaredTypes = new HashMap<>(); - - /** - * Default constructor. - * - * @param atypeFactory the type factory associated with this WholeProgramInferenceScenesStorage - */ - public WholeProgramInferenceScenesStorage(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - boolean isNullness = - atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); - this.ignoreNullAssignments = !isNullness; - } - - @Override - public String getFileForElement(Element elt) { - String className; - switch (elt.getKind()) { - case CONSTRUCTOR: - case METHOD: - className = ElementUtils.getEnclosingClassName((ExecutableElement) elt); - break; - case LOCAL_VARIABLE: - className = getEnclosingClassName((LocalVariableNode) elt); - break; - case FIELD: - case ENUM_CONSTANT: - ClassSymbol enclosingClass = ((VarSymbol) elt).enclClass(); - className = enclosingClass.flatname.toString(); - break; - case CLASS: - className = ElementUtils.getBinaryName((TypeElement) elt); - break; - case PARAMETER: - className = ElementUtils.getEnclosingClassName((VariableElement) elt); - break; - default: - throw new BugInCF("What element? %s %s", elt.getKind(), elt); - } - String file = getJaifPath(className); - return file; - } - - /** - * Get the annotations for a class. - * - * @param className the name of the class, in binary form - * @param file the path to the file that represents the class - * @param classSymbol optionally, the ClassSymbol representing the class - * @return the annotations for the class - */ - private AClass getClassAnnos( - @BinaryName String className, String file, @Nullable ClassSymbol classSymbol) { - // Possibly reads .jaif file to obtain a Scene. - ASceneWrapper scene = getScene(file); - AClass aClass = scene.getAScene().classes.getVivify(className); - scene.updateSymbolInformation(aClass, classSymbol); - return aClass; - } - - /** - * Get the annotations for a method or constructor. - * - * @param methodElt the method or constructor - * @return the annotations for a method or constructor - */ - private AMethod getMethodAnnos(ExecutableElement methodElt) { - String className = ElementUtils.getEnclosingClassName(methodElt); - String file = getFileForElement(methodElt); - AClass classAnnos = getClassAnnos(className, file, ((MethodSymbol) methodElt).enclClass()); - AMethod methodAnnos = - classAnnos.methods.getVivify(JVMNames.getJVMMethodSignature(methodElt)); - methodAnnos.setFieldsFromMethodElement(methodElt); - return methodAnnos; - } - - /** - * Get the annotations for a field. - * - * @param fieldElt the field - * @return the annotations for a field - */ - private AField getFieldAnnos(VariableElement fieldElt) { - String className = ElementUtils.getEnclosingClassName(fieldElt); - String file = getFileForElement(fieldElt); - AClass classAnnos = getClassAnnos(className, file, ((VarSymbol) fieldElt).enclClass()); - AField fieldAnnos = classAnnos.fields.getVivify(fieldElt.getSimpleName().toString()); - return fieldAnnos; - } - - @Override - public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { - // The only location that the scenes implementation cannot store an annotation is on - // a member of an annotation type, which it cannot distinguish from a normal interface's - // method. Without this, the scenes implementation will attempt to annotate annotation - // elements, which is an error. - Element enclosingType = ElementUtils.enclosingTypeElement(methodElt); - return enclosingType == null || enclosingType.getKind() != ElementKind.ANNOTATION_TYPE; - } - - @Override - public ATypeElement getParameterAnnotations( - ExecutableElement methodElt, - @Positive int index_1based, - AnnotatedTypeMirror paramATM, - VariableElement ve, - AnnotatedTypeFactory atypeFactory) { - if (index_1based == 0) { - throw new TypeSystemError("0 is illegal as index argument to getParameterAnnotations"); - } - AMethod methodAnnos = getMethodAnnos(methodElt); - AField param = - methodAnnos.vivifyAndAddTypeMirrorToParameter( - index_1based - 1, paramATM.getUnderlyingType(), ve.getSimpleName()); - return param.type; - } - - @Override - public ATypeElement getReceiverAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror paramATM, - AnnotatedTypeFactory atypeFactory) { - AMethod methodAnnos = getMethodAnnos(methodElt); - return methodAnnos.receiver.type; - } - - @Override - public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt) { - AMethod methodAnnos = getMethodAnnos(elt); - Set annos = methodAnnos.tlAnnotationsHere; - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (Annotation anno : annos) { - result.add( - AnnotationConverter.annotationToAnnotationMirror( - anno, atypeFactory.getProcessingEnv())); - } - return result; - } - - @Override - public boolean removeMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno) { - AMethod methodAnnos = getMethodAnnos(methodElt); - return methodAnnos.tlAnnotationsHere.remove( - AnnotationConverter.annotationMirrorToAnnotation(anno)); - } - - @Override - public ATypeElement getReturnAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror atm, - AnnotatedTypeFactory atypeFactory) { - AMethod methodAnnos = getMethodAnnos(methodElt); - return methodAnnos.returnType; - } - - @Override - public ATypeElement getFieldAnnotations( - Element element, - String fieldName, - AnnotatedTypeMirror lhsATM, - AnnotatedTypeFactory atypeFactory) { - ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); - String file = getFileForElement(element); - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @BinaryName String className = enclosingClass.flatname.toString(); - AClass classAnnos = getClassAnnos(className, file, enclosingClass); - AField field = classAnnos.fields.getVivify(fieldName); - field.setTypeMirror(lhsATM.getUnderlyingType()); - return field.type; - } - - @Override - public ATypeElement getPreOrPostconditions( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - switch (preOrPost) { - case BEFORE: - return getPreconditionsForExpression(methodElement, expression, declaredType); - case AFTER: - return getPostconditionsForExpression(methodElement, expression, declaredType); - default: - throw new BugInCF("Unexpected " + preOrPost); - } - } - - /** - * Returns the precondition annotations for a Java expression. - * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @return the precondition annotations for a Java expression - */ - private ATypeElement getPreconditionsForExpression( - ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { - AMethod methodAnnos = getMethodAnnos(methodElement); - preconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); - return methodAnnos.vivifyAndAddTypeMirrorToPrecondition( - expression, declaredType.getUnderlyingType()) - .type; - } - - /** - * Returns the postcondition annotations for a Java expression. - * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @return the postcondition annotations for a Java expression - */ - private ATypeElement getPostconditionsForExpression( - ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { - AMethod methodAnnos = getMethodAnnos(methodElement); - postconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); - return methodAnnos.vivifyAndAddTypeMirrorToPostcondition( - expression, declaredType.getUnderlyingType()) - .type; - } - - /** - * Fetches the declared type of an expression for which a precondition was inferred, for the - * given AMethod. - * - * @param m a method - * @param expression the expression - * @return the declared type - */ - public AnnotatedTypeMirror getPreconditionDeclaredType(AMethod m, String expression) { - String key = m.methodSignature + expression; - if (!preconditionsToDeclaredTypes.containsKey(key)) { - throw new BugInCF( - "attempted to retrieve the declared type of a precondition expression for which" - + "nothing was inferred: " - + key); - } - return preconditionsToDeclaredTypes.get(key); - } - - /** - * Fetches the declared type of an expression for which a postcondition was inferred, for the - * given AMethod. - * - * @param m a method - * @param expression the expression - * @return the declared type - */ - public AnnotatedTypeMirror getPostconditionDeclaredType(AMethod m, String expression) { - String key = m.methodSignature + expression; - if (!postconditionsToDeclaredTypes.containsKey(key)) { - throw new BugInCF( - "attempted to retrieve the declared type of a postcondition expression for which" - + "nothing was inferred: " - + key); - } - return postconditionsToDeclaredTypes.get(key); - } - - @Override - public boolean addMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno) { - - // Do not infer types for library code, only for type-checked source code. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return false; - } - - AMethod methodAnnos = getMethodAnnos(methodElt); - - org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = - AnnotationConverter.annotationMirrorToAnnotation(anno); - boolean isNewAnnotation = methodAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - @Override - public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(field)) { - return false; - } - - AField fieldAnnos = getFieldAnnos(field); - - org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = - AnnotationConverter.annotationMirrorToAnnotation(anno); - - boolean isNewAnnotation = fieldAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - @Override - public boolean addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return false; - } - - VariableElement paramElt = methodElt.getParameters().get(index_1based - 1); - AnnotatedTypeMirror paramAType = atypeFactory.getAnnotatedType(paramElt); - ATypeElement paramAnnos = - getParameterAnnotations( - methodElt, index_1based, paramAType, paramElt, atypeFactory); - Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); - - boolean isNewAnnotation = paramAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - @Override - public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(classElt)) { - return false; - } - - AClass classAnnos = - getClassAnnos( - ElementUtils.getBinaryName(classElt), - getFileForElement(classElt), - (ClassSymbol) classElt); - - Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); - - boolean isNewAnnotation = classAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - /** - * Write all modified scenes into files. (Scenes are modified by the method {@link - * #updateAnnotationSetInScene}.) - * - * @param outputFormat the output format to use when writing files - * @param checker the checker from which this method is called, for naming stub files - */ - public void writeScenes(OutputFormat outputFormat, BaseTypeChecker checker) { - // Create WPI directory if it doesn't exist already. - File jaifDir = new File(JAIF_FILES_PATH); - if (!jaifDir.exists()) { - jaifDir.mkdirs(); - } - // Write scenes into files. - for (String jaifPath : modifiedScenes) { - scenes.get(jaifPath).writeToFile(jaifPath, annosToIgnore, outputFormat, checker); - } - modifiedScenes.clear(); - } - - /** - * Returns the String representing the .jaif path of a class given its name. - * - * @param className the simple name of a class - * @return the path to the .jaif file - */ - protected String getJaifPath(String className) { - String jaifPath = JAIF_FILES_PATH + className + ".jaif"; - return jaifPath; - } - - /** - * Reads a Scene from the given .jaif file, or returns an empty Scene if the file does not - * exist. - * - * @param jaifPath the .jaif file - * @return the Scene read from the file, or an empty Scene if the file does not exist - */ - private ASceneWrapper getScene(String jaifPath) { - AScene scene; - if (!scenes.containsKey(jaifPath)) { - File jaifFile = new File(jaifPath); - scene = new AScene(); - if (jaifFile.exists()) { - try { - IndexFileParser.parseFile(jaifPath, scene); - } catch (IOException e) { - throw new UserError("Problem while reading %s: %s", jaifPath, e.getMessage()); - } - } - ASceneWrapper wrapper = new ASceneWrapper(scene); - scenes.put(jaifPath, wrapper); - return wrapper; - } else { - return scenes.get(jaifPath); - } - } - - /** - * Returns the scene-lib representation of the given className in the scene identified by the - * given jaifPath. - * - * @param className the name of the class to get, in binary form - * @param jaifPath the path to the jaif file that would represent that class (must end in - * ".jaif") - * @param classSymbol optionally, the ClassSymbol representing the class. Used to set the symbol - * information stored on an AClass. - * @return a version of the scene-lib representation of the class, augmented with symbol - * information if {@code classSymbol} was non-null - */ - protected AClass getAClass( - @BinaryName String className, String jaifPath, @Nullable ClassSymbol classSymbol) { - // Possibly reads .jaif file to obtain a Scene. - ASceneWrapper scene = getScene(jaifPath); - AClass aClass = scene.getAScene().classes.getVivify(className); - scene.updateSymbolInformation(aClass, classSymbol); - return aClass; - } - - /** - * Returns the scene-lib representation of the given className in the scene identified by the - * given jaifPath. - * - * @param className the name of the class to get, in binary form - * @param jaifPath the path to the jaif file that would represent that class (must end in - * ".jaif") - * @return the scene-lib representation of the class, possibly augmented with symbol information - * if {@link #getAClass(String, String, com.sun.tools.javac.code.Symbol.ClassSymbol)} has - * already been called with a non-null third argument - */ - protected AClass getAClass(@BinaryName String className, String jaifPath) { - return getAClass(className, jaifPath, null); - } - - /** - * Updates the set of annotations in a location of a Scene, as the result of a - * pseudo-assignment. - * - *

    - *
  • If there was no previous annotation for that location, then the updated set will be the - * annotations in rhsATM. - *
  • If there was a previous annotation, the updated set will be the LUB between the - * previous annotation and rhsATM. - *
- * - * @param type the ATypeElement of the Scene which will be modified - * @param jaifPath path to a .jaif file for a Scene; used for marking the scene as modified - * (needing to be written to disk) - * @param rhsATM the RHS of the annotated type on the source code - * @param lhsATM the LHS of the annotated type on the source code - * @param defLoc the location where the annotation will be added - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - protected void updateAnnotationSetInScene( - ATypeElement type, - TypeUseLocation defLoc, - AnnotatedTypeMirror rhsATM, - AnnotatedTypeMirror lhsATM, - String jaifPath, - boolean ignoreIfAnnotated) { - if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { - return; - } - TypeMirror rhsTM = rhsATM.getUnderlyingType(); - AnnotatedTypeMirror atmFromScene = atmFromStorageLocation(rhsTM, type); - updateAtmWithLub(rhsATM, atmFromScene); - if (lhsATM instanceof AnnotatedTypeVariable) { - AnnotationMirrorSet upperAnnos = - ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations(); - // If the inferred type is a subtype of the upper bounds of the - // current type on the source code, halt. - if (upperAnnos.size() == rhsATM.getAnnotations().size() - && atypeFactory - .getQualifierHierarchy() - .isSubtypeShallow( - rhsATM.getAnnotations(), - rhsTM, - upperAnnos, - lhsATM.getUnderlyingType())) { - return; - } - } - updateTypeElementFromATM(type, defLoc, rhsATM, lhsATM, ignoreIfAnnotated); - modifiedScenes.add(jaifPath); - } - - /** - * Updates sourceCodeATM to contain the LUB between sourceCodeATM and jaifATM, ignoring missing - * AnnotationMirrors from jaifATM -- it considers the LUB between an AnnotationMirror am and a - * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. - * - * @param sourceCodeATM the annotated type on the source code - * @param jaifATM the annotated type on the .jaif file - */ - private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror jaifATM) { - - switch (sourceCodeATM.getKind()) { - case TYPEVAR: - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), - ((AnnotatedTypeVariable) jaifATM).getLowerBound()); - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), - ((AnnotatedTypeVariable) jaifATM).getUpperBound()); - break; - // case WILDCARD: - // Because inferring type arguments is not supported, wildcards won't be encoutered - // updateAtmWithLub(((AnnotatedWildcardType) - // sourceCodeATM).getExtendsBound(), - // ((AnnotatedWildcardType) - // jaifATM).getExtendsBound()); - // updateAtmWithLub(((AnnotatedWildcardType) - // sourceCodeATM).getSuperBound(), - // ((AnnotatedWildcardType) jaifATM).getSuperBound()); - // break; - case ARRAY: - updateAtmWithLub( - ((AnnotatedArrayType) sourceCodeATM).getComponentType(), - ((AnnotatedArrayType) jaifATM).getComponentType()); - break; - // case DECLARED: - // inferring annotations on type arguments is not supported, so no need to recur on - // generic types. If this was every implemented, this method would need VisitHistory - // object to prevent infinite recursion on types such as T extends List. - default: - // ATM only has primary annotations - break; - } - - // LUB primary annotations - AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); - for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { - AnnotationMirror amJaif = jaifATM.getAnnotationInHierarchy(amSource); - // amJaif only contains annotations from the jaif, so it might be missing - // an annotation in the hierarchy - if (amJaif != null) { - amSource = - atypeFactory - .getQualifierHierarchy() - .leastUpperBoundShallow( - amSource, - sourceCodeATM.getUnderlyingType(), - amJaif, - jaifATM.getUnderlyingType()); - } - annosToReplace.add(amSource); - } - sourceCodeATM.replaceAnnotations(annosToReplace); - } - - /** - * Returns true if {@code am} should not be inserted in source code, for example {@link - * org.checkerframework.common.value.qual.BottomVal}. This happens when {@code am} cannot be - * inserted in source code or is the default for the location passed as argument. - * - *

Invisible qualifiers, which are annotations that contain the {@link - * org.checkerframework.framework.qual.InvisibleQualifier} meta-annotation, also return true. - * - *

TODO: Merge functionality somewhere else with {@link - * org.checkerframework.framework.util.defaults.QualifierDefaults}. Look into the - * createQualifierDefaults method in {@link GenericAnnotatedTypeFactory} (which uses the - * QualifierDefaults class linked above) before changing anything here. See - * https://github.com/typetools/checker-framework/issues/683 . - * - * @param am an annotation to test for whether it should be inserted into source code - * @param location where the location would be inserted; used to determine if {@code am} is the - * default for that location - * @param atm its kind is used to determine if {@code am} is the default for that kind - * @return true if am should not be inserted into source code, or if am is invisible - */ - private boolean shouldIgnore( - AnnotationMirror am, TypeUseLocation location, AnnotatedTypeMirror atm) { - Element elt = am.getAnnotationType().asElement(); - // Checks if am is an implementation detail (a type qualifier used - // internally by the type system and not meant to be seen by the user). - Target target = elt.getAnnotation(Target.class); - if (target != null && target.value().length == 0) { - return true; - } - if (elt.getAnnotation(InvisibleQualifier.class) != null) { - return true; - } - - // Checks if am is default - if (elt.getAnnotation(DefaultQualifierInHierarchy.class) != null) { - return true; - } - DefaultQualifier defaultQual = elt.getAnnotation(DefaultQualifier.class); - if (defaultQual != null) { - for (TypeUseLocation loc : defaultQual.locations()) { - if (loc == TypeUseLocation.ALL || loc == location) { - return true; - } - } - } - DefaultFor defaultQualForLocation = elt.getAnnotation(DefaultFor.class); - if (defaultQualForLocation != null) { - for (TypeUseLocation loc : defaultQualForLocation.value()) { - if (loc == TypeUseLocation.ALL || loc == location) { - return true; - } - } - } - - // Checks if am is a default annotation. - // This case checks if it is meta-annotated with @DefaultFor. - // TODO: Handle cases of annotations added via an - // org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator. - DefaultFor defaultFor = elt.getAnnotation(DefaultFor.class); - if (defaultFor != null) { - org.checkerframework.framework.qual.TypeKind[] types = defaultFor.typeKinds(); - TypeKind atmKind = atm.getUnderlyingType().getKind(); - if (hasMatchingTypeKind(atmKind, types)) { - return true; - } - } - - return false; - } - - /** Returns true, iff a matching TypeKind is found. */ - private boolean hasMatchingTypeKind( - TypeKind atmKind, org.checkerframework.framework.qual.TypeKind[] types) { - for (org.checkerframework.framework.qual.TypeKind tk : types) { - if (tk.name().equals(atmKind.name())) { - return true; - } - } - return false; - } - - /** - * Returns a subset of annosSet, consisting of the annotations supported by the type factory - * associated with this. These are not necessarily legal annotations: they have the right name, - * but they may lack elements (fields). - * - * @param annosSet a set of annotations - * @return the annoattions supported by this object's AnnotatedTypeFactory - */ - private Set getSupportedAnnosInSet(Set annosSet) { - Set output = new HashSet<>(1); - Set> supportedAnnos = - atypeFactory.getSupportedTypeQualifiers(); - for (Annotation anno : annosSet) { - for (Class clazz : supportedAnnos) { - // TODO: Remove comparison by name, and make this routine more efficient. - if (clazz.getName().equals(anno.def.name)) { - output.add(anno); - } - } - } - return output; - } - - @Override - public AnnotatedTypeMirror atmFromStorageLocation( - TypeMirror typeMirror, ATypeElement storageLocation) { - AnnotatedTypeMirror result = - AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); - updateAtmFromATypeElement(result, storageLocation); - return result; - } - - /** - * Updates an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} to contain the - * {@link org.checkerframework.afu.scenelib.annotations.Annotation}s of an {@link - * org.checkerframework.afu.scenelib.annotations.el.ATypeElement}. - * - * @param result the AnnotatedTypeMirror to be modified - * @param storageLocation the {@link - * org.checkerframework.afu.scenelib.annotations.el.ATypeElement} used - */ - private void updateAtmFromATypeElement( - AnnotatedTypeMirror result, ATypeElement storageLocation) { - Set annos = getSupportedAnnosInSet(storageLocation.tlAnnotationsHere); - for (Annotation anno : annos) { - AnnotationMirror am = - AnnotationConverter.annotationToAnnotationMirror( - anno, atypeFactory.getProcessingEnv()); - result.addAnnotation(am); - } - if (result.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aat = (AnnotatedArrayType) result; - for (ATypeElement innerType : storageLocation.innerTypes.values()) { - updateAtmFromATypeElement(aat.getComponentType(), innerType); - } - } - if (result.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) result; - for (ATypeElement innerType : storageLocation.innerTypes.values()) { - updateAtmFromATypeElement(atv.getUpperBound(), innerType); - } - } - } - - @Override - public void updateStorageLocationFromAtm( - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - ATypeElement typeToUpdate, - TypeUseLocation defLoc, - boolean ignoreIfAnnotated) { - updateTypeElementFromATM(typeToUpdate, defLoc, newATM, curATM, ignoreIfAnnotated); - } - - /// - /// Writing to a file - /// - - // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because - // a scene may be modifed and written at any time, including before or after - // postProcessClassTree is called. - - /** - * Side-effects the compilation unit annotations to make any desired changes before writing to a - * file. - * - * @param compilationUnitAnnos the compilation unit annotations to modify - */ - public void prepareSceneForWriting(AScene compilationUnitAnnos) { - for (Map.Entry classEntry : compilationUnitAnnos.classes.entrySet()) { - wpiPrepareClassForWriting(classEntry.getValue()); - } - } - - /** - * Side-effects the class annotations to make any desired changes before writing to a file. - * - * @param classAnnos the class annotations to modify - */ - public void wpiPrepareClassForWriting(AClass classAnnos) { - for (Map.Entry methodEntry : classAnnos.methods.entrySet()) { - wpiPrepareMethodForWriting(methodEntry.getValue()); - } - } - - /** - * Side-effects the method or constructor annotations to make any desired changes before writing - * to a file. - * - * @param methodAnnos the method or constructor annotations to modify - */ - public void wpiPrepareMethodForWriting(AMethod methodAnnos) { - atypeFactory.wpiPrepareMethodForWriting(methodAnnos); - } - - @Override - public void writeResultsToFile( - WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker) { - if (outputFormat == OutputFormat.AJAVA) { - throw new BugInCF("WholeProgramInferenceScenes used with format " + outputFormat); - } - - for (String file : modifiedScenes) { - ASceneWrapper scene = scenes.get(file); - prepareSceneForWriting(scene.getAScene()); - } - - writeScenes(outputFormat, checker); - } - - @Override - public void setFileModified(String path) { - modifiedScenes.add(path); - } - - @Override - public void preprocessClassTree(ClassTree classTree) { - // This implementation does nothing. - } - - /** - * Updates an {@link org.checkerframework.afu.scenelib.annotations.el.ATypeElement} to have the - * annotations of an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} passed as - * argument. Annotations in the original set that should be ignored (see {@link #shouldIgnore}) - * are not added to the resulting set. This method also checks if the AnnotatedTypeMirror has - * explicit annotations in source code, and if that is the case no annotations are added for - * that location. - * - *

This method removes from the ATypeElement all annotations supported by this object's - * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is - * called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. Therefore, - * it is not a problem to remove all annotations before inserting the new annotations. - * - * @param typeToUpdate the ATypeElement that will be updated - * @param defLoc the location where the annotation will be added - * @param newATM the AnnotatedTypeMirror whose annotations will be added to the ATypeElement - * @param curATM used to check if the element which will be updated has explicit annotations in - * source code - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - private void updateTypeElementFromATM( - ATypeElement typeToUpdate, - TypeUseLocation defLoc, - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - boolean ignoreIfAnnotated) { - // Clears only the annotations that are supported by the relevant AnnotatedTypeFactory. - // The others stay intact. - Set annosToRemove = getSupportedAnnosInSet(typeToUpdate.tlAnnotationsHere); - // This method may be called consecutive times for the same ATypeElement. Each time it is - // called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. - // Therefore, it is not a problem to remove all annotations before inserting the new - // annotations. - typeToUpdate.tlAnnotationsHere.removeAll(annosToRemove); - - // Only update the ATypeElement if there are no explicit annotations. - if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { - for (AnnotationMirror am : newATM.getAnnotations()) { - addAnnotationsToATypeElement( - newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); - } - } else if (curATM.getKind() == TypeKind.TYPEVAR) { - // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly - // annotated. So instead, only insert the annotation if there is not primary annotation - // of the same hierarchy. #shouldIgnore prevent annotations that are subtypes of type - // vars upper bound from being inserted. - for (AnnotationMirror am : newATM.getAnnotations()) { - if (curATM.getAnnotationInHierarchy(am) != null) { - // Don't insert if the type is already has a primary annotation - // in the same hierarchy. - break; - } - addAnnotationsToATypeElement( - newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); - } - } - - // Recursively update compound type and type variable type if they exist. - if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; - AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; - updateTypeElementFromATM( - typeToUpdate.innerTypes.getVivify( - TypePathEntry.getTypePathEntryListFromBinary( - Collections.nCopies(2, 0))), - defLoc, - newAAT.getComponentType(), - oldAAT.getComponentType(), - ignoreIfAnnotated); - } - } - - private void addAnnotationsToATypeElement( - AnnotatedTypeMirror newATM, - ATypeElement typeToUpdate, - TypeUseLocation defLoc, - AnnotationMirror am, - boolean isEffectiveAnnotation) { - Annotation anno = AnnotationConverter.annotationMirrorToAnnotation(am); - typeToUpdate.tlAnnotationsHere.add(anno); - if (isEffectiveAnnotation || shouldIgnore(am, defLoc, newATM)) { - // firstKey works as a unique identifier for each annotation - // that should not be inserted in source code - String firstKey = aTypeElementToString(typeToUpdate); - IPair key = IPair.of(firstKey, defLoc); - Set annosIgnored = annosToIgnore.get(key); - if (annosIgnored == null) { - annosIgnored = new HashSet<>(CollectionsPlume.mapCapacity(1)); - annosToIgnore.put(key, annosIgnored); - } - annosIgnored.add(anno.def().toString()); - } - } - - /** - * Returns a string representation of an ATypeElement, for use as part of a key in {@link - * AnnotationsInContexts}. - * - * @param aType an ATypeElement to convert to a string representation - * @return a string representation of the argument - */ - public static String aTypeElementToString(ATypeElement aType) { - // return aType.description.toString() + aType.tlAnnotationsHere; - return aType.description.toString(); - } - - /** - * Maps the {@link #aTypeElementToString} representation of an ATypeElement and its - * TypeUseLocation to a set of names of annotations. - */ - public static class AnnotationsInContexts - extends HashMap, Set> { - private static final long serialVersionUID = 20200321L; - } - - /** - * Returns the "flatname" of the class enclosing {@code localVariableNode}. - * - * @param localVariableNode the {@link LocalVariableNode} - * @return the "flatname" of the class enclosing {@code localVariableNode} - */ - private static @BinaryName String getEnclosingClassName(LocalVariableNode localVariableNode) { - return ElementUtils.getBinaryName( - ElementUtils.enclosingTypeElement(localVariableNode.getElement())); - } + implements WholeProgramInferenceStorage { + + /** + * Directory where .jaif files will be written to and read from. This directory is relative to + * where the CF's javac command is executed. + */ + public static final String JAIF_FILES_PATH = + "build" + File.separator + "whole-program-inference" + File.separator; + + /** The type factory associated with this WholeProgramInferenceScenesStorage. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** Annotations that should not be output to a .jaif or stub file. */ + private final AnnotationsInContexts annosToIgnore = new AnnotationsInContexts(); + + /** + * If true, assignments where the rhs is null are be ignored. + * + *

If all assignments to a variable are null (because inference is being done with respect to a + * limited set of uses) then the variable is inferred to have bottom type. That inference is + * unlikely to be correct. To avoid that inference, set this variable to true. When the variable + * is true, if all assignments are null, then none are recorded, no inference is done, and the + * variable remains at its default type. + */ + private final boolean ignoreNullAssignments; + + /** Maps .jaif file paths (Strings) to Scenes. Relative to JAIF_FILES_PATH. */ + public final Map scenes = new HashMap<>(); + + /** + * Scenes that were modified since the last time all Scenes were written into .jaif files. Each + * String element of this set is a path (relative to JAIF_FILES_PATH) to the .jaif file of the + * corresponding Scene in the set. It is obtained by passing a class name as argument to the + * {@link #getJaifPath} method. + * + *

Modifying a Scene means adding (or changing) a type annotation for a field, method return + * type, or method parameter type in the Scene. (Scenes are modified by the method {@link + * #updateAnnotationSetInScene}.) + */ + public final Set modifiedScenes = new HashSet<>(); + + /** + * This map relates inferred preconditions to the declared types of the expressions to which the + * precondition applies. It is necessary to keep this map here because the AFU does not have a + * dependency on the CF itself, where AnnotatedTypeMirror exists. + * + *

The keys are the concatenation of the string representation of the method signature as + * stored by {@link AMethod} to which the precondition applies and the expression to which the + * precondition applies. + */ + private final Map preconditionsToDeclaredTypes = new HashMap<>(); + + /** + * This map relates inferred postconditions to the declared types of the expressions to which the + * postcondition applies. It is necessary to keep this map here because the AFU does not have a + * dependency on the CF itself, where AnnotatedTypeMirror exists. + * + *

The keys are the concatenation of the string representation of the method signature as + * stored by {@link AMethod} to which the postcondition applies and the expression to which the + * postcondition applies. + */ + private final Map postconditionsToDeclaredTypes = new HashMap<>(); + + /** + * Default constructor. + * + * @param atypeFactory the type factory associated with this WholeProgramInferenceScenesStorage + */ + public WholeProgramInferenceScenesStorage(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + boolean isNullness = + atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); + this.ignoreNullAssignments = !isNullness; + } + + @Override + public String getFileForElement(Element elt) { + String className; + switch (elt.getKind()) { + case CONSTRUCTOR: + case METHOD: + className = ElementUtils.getEnclosingClassName((ExecutableElement) elt); + break; + case LOCAL_VARIABLE: + className = getEnclosingClassName((LocalVariableNode) elt); + break; + case FIELD: + case ENUM_CONSTANT: + ClassSymbol enclosingClass = ((VarSymbol) elt).enclClass(); + className = enclosingClass.flatname.toString(); + break; + case CLASS: + className = ElementUtils.getBinaryName((TypeElement) elt); + break; + case PARAMETER: + className = ElementUtils.getEnclosingClassName((VariableElement) elt); + break; + default: + throw new BugInCF("What element? %s %s", elt.getKind(), elt); + } + String file = getJaifPath(className); + return file; + } + + /** + * Get the annotations for a class. + * + * @param className the name of the class, in binary form + * @param file the path to the file that represents the class + * @param classSymbol optionally, the ClassSymbol representing the class + * @return the annotations for the class + */ + private AClass getClassAnnos( + @BinaryName String className, String file, @Nullable ClassSymbol classSymbol) { + // Possibly reads .jaif file to obtain a Scene. + ASceneWrapper scene = getScene(file); + AClass aClass = scene.getAScene().classes.getVivify(className); + scene.updateSymbolInformation(aClass, classSymbol); + return aClass; + } + + /** + * Get the annotations for a method or constructor. + * + * @param methodElt the method or constructor + * @return the annotations for a method or constructor + */ + private AMethod getMethodAnnos(ExecutableElement methodElt) { + String className = ElementUtils.getEnclosingClassName(methodElt); + String file = getFileForElement(methodElt); + AClass classAnnos = getClassAnnos(className, file, ((MethodSymbol) methodElt).enclClass()); + AMethod methodAnnos = classAnnos.methods.getVivify(JVMNames.getJVMMethodSignature(methodElt)); + methodAnnos.setFieldsFromMethodElement(methodElt); + return methodAnnos; + } + + /** + * Get the annotations for a field. + * + * @param fieldElt the field + * @return the annotations for a field + */ + private AField getFieldAnnos(VariableElement fieldElt) { + String className = ElementUtils.getEnclosingClassName(fieldElt); + String file = getFileForElement(fieldElt); + AClass classAnnos = getClassAnnos(className, file, ((VarSymbol) fieldElt).enclClass()); + AField fieldAnnos = classAnnos.fields.getVivify(fieldElt.getSimpleName().toString()); + return fieldAnnos; + } + + @Override + public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { + // The only location that the scenes implementation cannot store an annotation is on + // a member of an annotation type, which it cannot distinguish from a normal interface's + // method. Without this, the scenes implementation will attempt to annotate annotation + // elements, which is an error. + Element enclosingType = ElementUtils.enclosingTypeElement(methodElt); + return enclosingType == null || enclosingType.getKind() != ElementKind.ANNOTATION_TYPE; + } + + @Override + public ATypeElement getParameterAnnotations( + ExecutableElement methodElt, + @Positive int index_1based, + AnnotatedTypeMirror paramATM, + VariableElement ve, + AnnotatedTypeFactory atypeFactory) { + if (index_1based == 0) { + throw new TypeSystemError("0 is illegal as index argument to getParameterAnnotations"); + } + AMethod methodAnnos = getMethodAnnos(methodElt); + AField param = + methodAnnos.vivifyAndAddTypeMirrorToParameter( + index_1based - 1, paramATM.getUnderlyingType(), ve.getSimpleName()); + return param.type; + } + + @Override + public ATypeElement getReceiverAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror paramATM, + AnnotatedTypeFactory atypeFactory) { + AMethod methodAnnos = getMethodAnnos(methodElt); + return methodAnnos.receiver.type; + } + + @Override + public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt) { + AMethod methodAnnos = getMethodAnnos(elt); + Set annos = methodAnnos.tlAnnotationsHere; + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (Annotation anno : annos) { + result.add( + AnnotationConverter.annotationToAnnotationMirror(anno, atypeFactory.getProcessingEnv())); + } + return result; + } + + @Override + public boolean removeMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno) { + AMethod methodAnnos = getMethodAnnos(methodElt); + return methodAnnos.tlAnnotationsHere.remove( + AnnotationConverter.annotationMirrorToAnnotation(anno)); + } + + @Override + public ATypeElement getReturnAnnotations( + ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory) { + AMethod methodAnnos = getMethodAnnos(methodElt); + return methodAnnos.returnType; + } + + @Override + public ATypeElement getFieldAnnotations( + Element element, + String fieldName, + AnnotatedTypeMirror lhsATM, + AnnotatedTypeFactory atypeFactory) { + ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); + String file = getFileForElement(element); + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @BinaryName String className = enclosingClass.flatname.toString(); + AClass classAnnos = getClassAnnos(className, file, enclosingClass); + AField field = classAnnos.fields.getVivify(fieldName); + field.setTypeMirror(lhsATM.getUnderlyingType()); + return field.type; + } + + @Override + public ATypeElement getPreOrPostconditions( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + switch (preOrPost) { + case BEFORE: + return getPreconditionsForExpression(methodElement, expression, declaredType); + case AFTER: + return getPostconditionsForExpression(methodElement, expression, declaredType); + default: + throw new BugInCF("Unexpected " + preOrPost); + } + } + + /** + * Returns the precondition annotations for a Java expression. + * + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @return the precondition annotations for a Java expression + */ + private ATypeElement getPreconditionsForExpression( + ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { + AMethod methodAnnos = getMethodAnnos(methodElement); + preconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); + return methodAnnos.vivifyAndAddTypeMirrorToPrecondition( + expression, declaredType.getUnderlyingType()) + .type; + } + + /** + * Returns the postcondition annotations for a Java expression. + * + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @return the postcondition annotations for a Java expression + */ + private ATypeElement getPostconditionsForExpression( + ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { + AMethod methodAnnos = getMethodAnnos(methodElement); + postconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); + return methodAnnos.vivifyAndAddTypeMirrorToPostcondition( + expression, declaredType.getUnderlyingType()) + .type; + } + + /** + * Fetches the declared type of an expression for which a precondition was inferred, for the given + * AMethod. + * + * @param m a method + * @param expression the expression + * @return the declared type + */ + public AnnotatedTypeMirror getPreconditionDeclaredType(AMethod m, String expression) { + String key = m.methodSignature + expression; + if (!preconditionsToDeclaredTypes.containsKey(key)) { + throw new BugInCF( + "attempted to retrieve the declared type of a precondition expression for which" + + "nothing was inferred: " + + key); + } + return preconditionsToDeclaredTypes.get(key); + } + + /** + * Fetches the declared type of an expression for which a postcondition was inferred, for the + * given AMethod. + * + * @param m a method + * @param expression the expression + * @return the declared type + */ + public AnnotatedTypeMirror getPostconditionDeclaredType(AMethod m, String expression) { + String key = m.methodSignature + expression; + if (!postconditionsToDeclaredTypes.containsKey(key)) { + throw new BugInCF( + "attempted to retrieve the declared type of a postcondition expression for which" + + "nothing was inferred: " + + key); + } + return postconditionsToDeclaredTypes.get(key); + } + + @Override + public boolean addMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno) { + + // Do not infer types for library code, only for type-checked source code. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return false; + } + + AMethod methodAnnos = getMethodAnnos(methodElt); + + org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = + AnnotationConverter.annotationMirrorToAnnotation(anno); + boolean isNewAnnotation = methodAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + @Override + public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(field)) { + return false; + } + + AField fieldAnnos = getFieldAnnos(field); + + org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = + AnnotationConverter.annotationMirrorToAnnotation(anno); + + boolean isNewAnnotation = fieldAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + @Override + public boolean addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return false; + } + + VariableElement paramElt = methodElt.getParameters().get(index_1based - 1); + AnnotatedTypeMirror paramAType = atypeFactory.getAnnotatedType(paramElt); + ATypeElement paramAnnos = + getParameterAnnotations(methodElt, index_1based, paramAType, paramElt, atypeFactory); + Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); + + boolean isNewAnnotation = paramAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + @Override + public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(classElt)) { + return false; + } + + AClass classAnnos = + getClassAnnos( + ElementUtils.getBinaryName(classElt), + getFileForElement(classElt), + (ClassSymbol) classElt); + + Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); + + boolean isNewAnnotation = classAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + /** + * Write all modified scenes into files. (Scenes are modified by the method {@link + * #updateAnnotationSetInScene}.) + * + * @param outputFormat the output format to use when writing files + * @param checker the checker from which this method is called, for naming stub files + */ + public void writeScenes(OutputFormat outputFormat, BaseTypeChecker checker) { + // Create WPI directory if it doesn't exist already. + File jaifDir = new File(JAIF_FILES_PATH); + if (!jaifDir.exists()) { + jaifDir.mkdirs(); + } + // Write scenes into files. + for (String jaifPath : modifiedScenes) { + scenes.get(jaifPath).writeToFile(jaifPath, annosToIgnore, outputFormat, checker); + } + modifiedScenes.clear(); + } + + /** + * Returns the String representing the .jaif path of a class given its name. + * + * @param className the simple name of a class + * @return the path to the .jaif file + */ + protected String getJaifPath(String className) { + String jaifPath = JAIF_FILES_PATH + className + ".jaif"; + return jaifPath; + } + + /** + * Reads a Scene from the given .jaif file, or returns an empty Scene if the file does not exist. + * + * @param jaifPath the .jaif file + * @return the Scene read from the file, or an empty Scene if the file does not exist + */ + private ASceneWrapper getScene(String jaifPath) { + AScene scene; + if (!scenes.containsKey(jaifPath)) { + File jaifFile = new File(jaifPath); + scene = new AScene(); + if (jaifFile.exists()) { + try { + IndexFileParser.parseFile(jaifPath, scene); + } catch (IOException e) { + throw new UserError("Problem while reading %s: %s", jaifPath, e.getMessage()); + } + } + ASceneWrapper wrapper = new ASceneWrapper(scene); + scenes.put(jaifPath, wrapper); + return wrapper; + } else { + return scenes.get(jaifPath); + } + } + + /** + * Returns the scene-lib representation of the given className in the scene identified by the + * given jaifPath. + * + * @param className the name of the class to get, in binary form + * @param jaifPath the path to the jaif file that would represent that class (must end in ".jaif") + * @param classSymbol optionally, the ClassSymbol representing the class. Used to set the symbol + * information stored on an AClass. + * @return a version of the scene-lib representation of the class, augmented with symbol + * information if {@code classSymbol} was non-null + */ + protected AClass getAClass( + @BinaryName String className, String jaifPath, @Nullable ClassSymbol classSymbol) { + // Possibly reads .jaif file to obtain a Scene. + ASceneWrapper scene = getScene(jaifPath); + AClass aClass = scene.getAScene().classes.getVivify(className); + scene.updateSymbolInformation(aClass, classSymbol); + return aClass; + } + + /** + * Returns the scene-lib representation of the given className in the scene identified by the + * given jaifPath. + * + * @param className the name of the class to get, in binary form + * @param jaifPath the path to the jaif file that would represent that class (must end in ".jaif") + * @return the scene-lib representation of the class, possibly augmented with symbol information + * if {@link #getAClass(String, String, com.sun.tools.javac.code.Symbol.ClassSymbol)} has + * already been called with a non-null third argument + */ + protected AClass getAClass(@BinaryName String className, String jaifPath) { + return getAClass(className, jaifPath, null); + } + + /** + * Updates the set of annotations in a location of a Scene, as the result of a pseudo-assignment. + * + *

    + *
  • If there was no previous annotation for that location, then the updated set will be the + * annotations in rhsATM. + *
  • If there was a previous annotation, the updated set will be the LUB between the previous + * annotation and rhsATM. + *
+ * + * @param type the ATypeElement of the Scene which will be modified + * @param jaifPath path to a .jaif file for a Scene; used for marking the scene as modified + * (needing to be written to disk) + * @param rhsATM the RHS of the annotated type on the source code + * @param lhsATM the LHS of the annotated type on the source code + * @param defLoc the location where the annotation will be added + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + protected void updateAnnotationSetInScene( + ATypeElement type, + TypeUseLocation defLoc, + AnnotatedTypeMirror rhsATM, + AnnotatedTypeMirror lhsATM, + String jaifPath, + boolean ignoreIfAnnotated) { + if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { + return; + } + TypeMirror rhsTM = rhsATM.getUnderlyingType(); + AnnotatedTypeMirror atmFromScene = atmFromStorageLocation(rhsTM, type); + updateAtmWithLub(rhsATM, atmFromScene); + if (lhsATM instanceof AnnotatedTypeVariable) { + AnnotationMirrorSet upperAnnos = + ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations(); + // If the inferred type is a subtype of the upper bounds of the + // current type on the source code, halt. + if (upperAnnos.size() == rhsATM.getAnnotations().size() + && atypeFactory + .getQualifierHierarchy() + .isSubtypeShallow( + rhsATM.getAnnotations(), rhsTM, upperAnnos, lhsATM.getUnderlyingType())) { + return; + } + } + updateTypeElementFromATM(type, defLoc, rhsATM, lhsATM, ignoreIfAnnotated); + modifiedScenes.add(jaifPath); + } + + /** + * Updates sourceCodeATM to contain the LUB between sourceCodeATM and jaifATM, ignoring missing + * AnnotationMirrors from jaifATM -- it considers the LUB between an AnnotationMirror am and a + * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. + * + * @param sourceCodeATM the annotated type on the source code + * @param jaifATM the annotated type on the .jaif file + */ + private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror jaifATM) { + + switch (sourceCodeATM.getKind()) { + case TYPEVAR: + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), + ((AnnotatedTypeVariable) jaifATM).getLowerBound()); + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), + ((AnnotatedTypeVariable) jaifATM).getUpperBound()); + break; + // case WILDCARD: + // Because inferring type arguments is not supported, wildcards won't be encoutered + // updateAtmWithLub(((AnnotatedWildcardType) + // sourceCodeATM).getExtendsBound(), + // ((AnnotatedWildcardType) + // jaifATM).getExtendsBound()); + // updateAtmWithLub(((AnnotatedWildcardType) + // sourceCodeATM).getSuperBound(), + // ((AnnotatedWildcardType) jaifATM).getSuperBound()); + // break; + case ARRAY: + updateAtmWithLub( + ((AnnotatedArrayType) sourceCodeATM).getComponentType(), + ((AnnotatedArrayType) jaifATM).getComponentType()); + break; + // case DECLARED: + // inferring annotations on type arguments is not supported, so no need to recur on + // generic types. If this was every implemented, this method would need VisitHistory + // object to prevent infinite recursion on types such as T extends List. + default: + // ATM only has primary annotations + break; + } + + // LUB primary annotations + AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); + for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { + AnnotationMirror amJaif = jaifATM.getAnnotationInHierarchy(amSource); + // amJaif only contains annotations from the jaif, so it might be missing + // an annotation in the hierarchy + if (amJaif != null) { + amSource = + atypeFactory + .getQualifierHierarchy() + .leastUpperBoundShallow( + amSource, + sourceCodeATM.getUnderlyingType(), + amJaif, + jaifATM.getUnderlyingType()); + } + annosToReplace.add(amSource); + } + sourceCodeATM.replaceAnnotations(annosToReplace); + } + + /** + * Returns true if {@code am} should not be inserted in source code, for example {@link + * org.checkerframework.common.value.qual.BottomVal}. This happens when {@code am} cannot be + * inserted in source code or is the default for the location passed as argument. + * + *

Invisible qualifiers, which are annotations that contain the {@link + * org.checkerframework.framework.qual.InvisibleQualifier} meta-annotation, also return true. + * + *

TODO: Merge functionality somewhere else with {@link + * org.checkerframework.framework.util.defaults.QualifierDefaults}. Look into the + * createQualifierDefaults method in {@link GenericAnnotatedTypeFactory} (which uses the + * QualifierDefaults class linked above) before changing anything here. See + * https://github.com/typetools/checker-framework/issues/683 . + * + * @param am an annotation to test for whether it should be inserted into source code + * @param location where the location would be inserted; used to determine if {@code am} is the + * default for that location + * @param atm its kind is used to determine if {@code am} is the default for that kind + * @return true if am should not be inserted into source code, or if am is invisible + */ + private boolean shouldIgnore( + AnnotationMirror am, TypeUseLocation location, AnnotatedTypeMirror atm) { + Element elt = am.getAnnotationType().asElement(); + // Checks if am is an implementation detail (a type qualifier used + // internally by the type system and not meant to be seen by the user). + Target target = elt.getAnnotation(Target.class); + if (target != null && target.value().length == 0) { + return true; + } + if (elt.getAnnotation(InvisibleQualifier.class) != null) { + return true; + } + + // Checks if am is default + if (elt.getAnnotation(DefaultQualifierInHierarchy.class) != null) { + return true; + } + DefaultQualifier defaultQual = elt.getAnnotation(DefaultQualifier.class); + if (defaultQual != null) { + for (TypeUseLocation loc : defaultQual.locations()) { + if (loc == TypeUseLocation.ALL || loc == location) { + return true; + } + } + } + DefaultFor defaultQualForLocation = elt.getAnnotation(DefaultFor.class); + if (defaultQualForLocation != null) { + for (TypeUseLocation loc : defaultQualForLocation.value()) { + if (loc == TypeUseLocation.ALL || loc == location) { + return true; + } + } + } + + // Checks if am is a default annotation. + // This case checks if it is meta-annotated with @DefaultFor. + // TODO: Handle cases of annotations added via an + // org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator. + DefaultFor defaultFor = elt.getAnnotation(DefaultFor.class); + if (defaultFor != null) { + org.checkerframework.framework.qual.TypeKind[] types = defaultFor.typeKinds(); + TypeKind atmKind = atm.getUnderlyingType().getKind(); + if (hasMatchingTypeKind(atmKind, types)) { + return true; + } + } + + return false; + } + + /** Returns true, iff a matching TypeKind is found. */ + private boolean hasMatchingTypeKind( + TypeKind atmKind, org.checkerframework.framework.qual.TypeKind[] types) { + for (org.checkerframework.framework.qual.TypeKind tk : types) { + if (tk.name().equals(atmKind.name())) { + return true; + } + } + return false; + } + + /** + * Returns a subset of annosSet, consisting of the annotations supported by the type factory + * associated with this. These are not necessarily legal annotations: they have the right name, + * but they may lack elements (fields). + * + * @param annosSet a set of annotations + * @return the annoattions supported by this object's AnnotatedTypeFactory + */ + private Set getSupportedAnnosInSet(Set annosSet) { + Set output = new HashSet<>(1); + Set> supportedAnnos = + atypeFactory.getSupportedTypeQualifiers(); + for (Annotation anno : annosSet) { + for (Class clazz : supportedAnnos) { + // TODO: Remove comparison by name, and make this routine more efficient. + if (clazz.getName().equals(anno.def.name)) { + output.add(anno); + } + } + } + return output; + } + + @Override + public AnnotatedTypeMirror atmFromStorageLocation( + TypeMirror typeMirror, ATypeElement storageLocation) { + AnnotatedTypeMirror result = AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); + updateAtmFromATypeElement(result, storageLocation); + return result; + } + + /** + * Updates an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} to contain the + * {@link org.checkerframework.afu.scenelib.annotations.Annotation}s of an {@link + * org.checkerframework.afu.scenelib.annotations.el.ATypeElement}. + * + * @param result the AnnotatedTypeMirror to be modified + * @param storageLocation the {@link + * org.checkerframework.afu.scenelib.annotations.el.ATypeElement} used + */ + private void updateAtmFromATypeElement(AnnotatedTypeMirror result, ATypeElement storageLocation) { + Set annos = getSupportedAnnosInSet(storageLocation.tlAnnotationsHere); + for (Annotation anno : annos) { + AnnotationMirror am = + AnnotationConverter.annotationToAnnotationMirror(anno, atypeFactory.getProcessingEnv()); + result.addAnnotation(am); + } + if (result.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aat = (AnnotatedArrayType) result; + for (ATypeElement innerType : storageLocation.innerTypes.values()) { + updateAtmFromATypeElement(aat.getComponentType(), innerType); + } + } + if (result.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) result; + for (ATypeElement innerType : storageLocation.innerTypes.values()) { + updateAtmFromATypeElement(atv.getUpperBound(), innerType); + } + } + } + + @Override + public void updateStorageLocationFromAtm( + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + ATypeElement typeToUpdate, + TypeUseLocation defLoc, + boolean ignoreIfAnnotated) { + updateTypeElementFromATM(typeToUpdate, defLoc, newATM, curATM, ignoreIfAnnotated); + } + + /// + /// Writing to a file + /// + + // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because + // a scene may be modifed and written at any time, including before or after + // postProcessClassTree is called. + + /** + * Side-effects the compilation unit annotations to make any desired changes before writing to a + * file. + * + * @param compilationUnitAnnos the compilation unit annotations to modify + */ + public void prepareSceneForWriting(AScene compilationUnitAnnos) { + for (Map.Entry classEntry : compilationUnitAnnos.classes.entrySet()) { + wpiPrepareClassForWriting(classEntry.getValue()); + } + } + + /** + * Side-effects the class annotations to make any desired changes before writing to a file. + * + * @param classAnnos the class annotations to modify + */ + public void wpiPrepareClassForWriting(AClass classAnnos) { + for (Map.Entry methodEntry : classAnnos.methods.entrySet()) { + wpiPrepareMethodForWriting(methodEntry.getValue()); + } + } + + /** + * Side-effects the method or constructor annotations to make any desired changes before writing + * to a file. + * + * @param methodAnnos the method or constructor annotations to modify + */ + public void wpiPrepareMethodForWriting(AMethod methodAnnos) { + atypeFactory.wpiPrepareMethodForWriting(methodAnnos); + } + + @Override + public void writeResultsToFile( + WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker) { + if (outputFormat == OutputFormat.AJAVA) { + throw new BugInCF("WholeProgramInferenceScenes used with format " + outputFormat); + } + + for (String file : modifiedScenes) { + ASceneWrapper scene = scenes.get(file); + prepareSceneForWriting(scene.getAScene()); + } + + writeScenes(outputFormat, checker); + } + + @Override + public void setFileModified(String path) { + modifiedScenes.add(path); + } + + @Override + public void preprocessClassTree(ClassTree classTree) { + // This implementation does nothing. + } + + /** + * Updates an {@link org.checkerframework.afu.scenelib.annotations.el.ATypeElement} to have the + * annotations of an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} passed as + * argument. Annotations in the original set that should be ignored (see {@link #shouldIgnore}) + * are not added to the resulting set. This method also checks if the AnnotatedTypeMirror has + * explicit annotations in source code, and if that is the case no annotations are added for that + * location. + * + *

This method removes from the ATypeElement all annotations supported by this object's + * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is + * called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. Therefore, it + * is not a problem to remove all annotations before inserting the new annotations. + * + * @param typeToUpdate the ATypeElement that will be updated + * @param defLoc the location where the annotation will be added + * @param newATM the AnnotatedTypeMirror whose annotations will be added to the ATypeElement + * @param curATM used to check if the element which will be updated has explicit annotations in + * source code + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + private void updateTypeElementFromATM( + ATypeElement typeToUpdate, + TypeUseLocation defLoc, + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + boolean ignoreIfAnnotated) { + // Clears only the annotations that are supported by the relevant AnnotatedTypeFactory. + // The others stay intact. + Set annosToRemove = getSupportedAnnosInSet(typeToUpdate.tlAnnotationsHere); + // This method may be called consecutive times for the same ATypeElement. Each time it is + // called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. + // Therefore, it is not a problem to remove all annotations before inserting the new + // annotations. + typeToUpdate.tlAnnotationsHere.removeAll(annosToRemove); + + // Only update the ATypeElement if there are no explicit annotations. + if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { + for (AnnotationMirror am : newATM.getAnnotations()) { + addAnnotationsToATypeElement( + newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); + } + } else if (curATM.getKind() == TypeKind.TYPEVAR) { + // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly + // annotated. So instead, only insert the annotation if there is not primary annotation + // of the same hierarchy. #shouldIgnore prevent annotations that are subtypes of type + // vars upper bound from being inserted. + for (AnnotationMirror am : newATM.getAnnotations()) { + if (curATM.getAnnotationInHierarchy(am) != null) { + // Don't insert if the type is already has a primary annotation + // in the same hierarchy. + break; + } + addAnnotationsToATypeElement( + newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); + } + } + + // Recursively update compound type and type variable type if they exist. + if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; + AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; + updateTypeElementFromATM( + typeToUpdate.innerTypes.getVivify( + TypePathEntry.getTypePathEntryListFromBinary(Collections.nCopies(2, 0))), + defLoc, + newAAT.getComponentType(), + oldAAT.getComponentType(), + ignoreIfAnnotated); + } + } + + private void addAnnotationsToATypeElement( + AnnotatedTypeMirror newATM, + ATypeElement typeToUpdate, + TypeUseLocation defLoc, + AnnotationMirror am, + boolean isEffectiveAnnotation) { + Annotation anno = AnnotationConverter.annotationMirrorToAnnotation(am); + typeToUpdate.tlAnnotationsHere.add(anno); + if (isEffectiveAnnotation || shouldIgnore(am, defLoc, newATM)) { + // firstKey works as a unique identifier for each annotation + // that should not be inserted in source code + String firstKey = aTypeElementToString(typeToUpdate); + IPair key = IPair.of(firstKey, defLoc); + Set annosIgnored = annosToIgnore.get(key); + if (annosIgnored == null) { + annosIgnored = new HashSet<>(CollectionsPlume.mapCapacity(1)); + annosToIgnore.put(key, annosIgnored); + } + annosIgnored.add(anno.def().toString()); + } + } + + /** + * Returns a string representation of an ATypeElement, for use as part of a key in {@link + * AnnotationsInContexts}. + * + * @param aType an ATypeElement to convert to a string representation + * @return a string representation of the argument + */ + public static String aTypeElementToString(ATypeElement aType) { + // return aType.description.toString() + aType.tlAnnotationsHere; + return aType.description.toString(); + } + + /** + * Maps the {@link #aTypeElementToString} representation of an ATypeElement and its + * TypeUseLocation to a set of names of annotations. + */ + public static class AnnotationsInContexts + extends HashMap, Set> { + private static final long serialVersionUID = 20200321L; + } + + /** + * Returns the "flatname" of the class enclosing {@code localVariableNode}. + * + * @param localVariableNode the {@link LocalVariableNode} + * @return the "flatname" of the class enclosing {@code localVariableNode} + */ + private static @BinaryName String getEnclosingClassName(LocalVariableNode localVariableNode) { + return ElementUtils.getBinaryName( + ElementUtils.enclosingTypeElement(localVariableNode.getElement())); + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java index 88dea76e226..8a00fac866f 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java @@ -1,7 +1,12 @@ package org.checkerframework.common.wholeprograminference; import com.sun.source.tree.ClassTree; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -11,13 +16,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - /** * Stores annotations from whole-program inference. For a given location such as a field or method, * an object can be obtained containing the inferred annotations for that object. @@ -30,241 +28,235 @@ * a storage location. */ public interface WholeProgramInferenceStorage { - /** - * Returns the file corresponding to the given element. This may side-effect the storage to load - * the file if it hasn't been read yet. - * - * @param elt an element - * @return the path to the file where inference results for the element will be written - */ - public String getFileForElement(Element elt); + /** + * Returns the file corresponding to the given element. This may side-effect the storage to load + * the file if it hasn't been read yet. + * + * @param elt an element + * @return the path to the file where inference results for the element will be written + */ + public String getFileForElement(Element elt); - /** - * Given an ExecutableElement in a compilation unit that has already been read into storage, - * returns whether there exists a stored method matching {@code elt}. - * - *

An implementation is permitted to return false if {@code elt} represents a method that was - * synthetically added by javac, such as zero-argument constructors or valueOf(String) methods - * for enum types. - * - * @param methodElt a method or constructor Element - * @return true if the storage has a method corresponding to {@code elt} - */ - public boolean hasStorageLocationForMethod(ExecutableElement methodElt); + /** + * Given an ExecutableElement in a compilation unit that has already been read into storage, + * returns whether there exists a stored method matching {@code elt}. + * + *

An implementation is permitted to return false if {@code elt} represents a method that was + * synthetically added by javac, such as zero-argument constructors or valueOf(String) methods for + * enum types. + * + * @param methodElt a method or constructor Element + * @return true if the storage has a method corresponding to {@code elt} + */ + public boolean hasStorageLocationForMethod(ExecutableElement methodElt); - /** - * Get the annotations for a formal parameter type. - * - * @param methodElt the method or constructor Element - * @param index_1based the parameter index (1-based) - * @param paramATM the parameter type - * @param ve the parameter variable - * @param atypeFactory the type factory - * @return the annotations for a formal parameter type - */ - public T getParameterAnnotations( - ExecutableElement methodElt, - @Positive int index_1based, - AnnotatedTypeMirror paramATM, - VariableElement ve, - AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for a formal parameter type. + * + * @param methodElt the method or constructor Element + * @param index_1based the parameter index (1-based) + * @param paramATM the parameter type + * @param ve the parameter variable + * @param atypeFactory the type factory + * @return the annotations for a formal parameter type + */ + public T getParameterAnnotations( + ExecutableElement methodElt, + @Positive int index_1based, + AnnotatedTypeMirror paramATM, + VariableElement ve, + AnnotatedTypeFactory atypeFactory); - /** - * Get the annotations for the receiver type. - * - * @param methodElt the method or constructor Element - * @param paramATM the receiver type - * @param atypeFactory the type factory - * @return the annotations for the receiver type - */ - public T getReceiverAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror paramATM, - AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for the receiver type. + * + * @param methodElt the method or constructor Element + * @param paramATM the receiver type + * @param atypeFactory the type factory + * @return the annotations for the receiver type + */ + public T getReceiverAnnotations( + ExecutableElement methodElt, AnnotatedTypeMirror paramATM, AnnotatedTypeFactory atypeFactory); - /** - * Get the annotations for the return type. - * - * @param methodElt the method or constructor Element - * @param atm the return type - * @param atypeFactory the type factory - * @return the annotations for the return type - */ - public T getReturnAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror atm, - AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for the return type. + * + * @param methodElt the method or constructor Element + * @param atm the return type + * @param atypeFactory the type factory + * @return the annotations for the return type + */ + public T getReturnAnnotations( + ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory); - /** - * Get the annotations for a field type. - * - * @param element the element for the field - * @param fieldName the simple field name - * @param lhsATM the field type - * @param atypeFactory the annotated type factory - * @return the annotations for a field type - */ - public T getFieldAnnotations( - Element element, - String fieldName, - AnnotatedTypeMirror lhsATM, - AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for a field type. + * + * @param element the element for the field + * @param fieldName the simple field name + * @param lhsATM the field type + * @param atypeFactory the annotated type factory + * @return the annotations for a field type + */ + public T getFieldAnnotations( + Element element, + String fieldName, + AnnotatedTypeMirror lhsATM, + AnnotatedTypeFactory atypeFactory); - /** - * Returns the pre- or postcondition annotations for an expression. The format of the expression - * is the same as a programmer would write in a {@link - * org.checkerframework.framework.qual.RequiresQualifier} or {@link - * org.checkerframework.framework.qual.EnsuresQualifier} annotation. - * - *

This method may return null if the given expression is not a supported expression type. - * Currently, the supported expression types are: fields of "this" (e.g. "this.f", pre- and - * postconditions), "this" (postconditions only), and method parameters (e.g. "#1", "#2", - * postconditions only). - * - * @param preOrPost whether to get the precondition or postcondition - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @param atypeFactory the type factory - * @return the pre- or postcondition annotations for an expression, or null if the given - * expression is not a supported expression type - */ - public @Nullable T getPreOrPostconditions( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory); + /** + * Returns the pre- or postcondition annotations for an expression. The format of the expression + * is the same as a programmer would write in a {@link + * org.checkerframework.framework.qual.RequiresQualifier} or {@link + * org.checkerframework.framework.qual.EnsuresQualifier} annotation. + * + *

This method may return null if the given expression is not a supported expression type. + * Currently, the supported expression types are: fields of "this" (e.g. "this.f", pre- and + * postconditions), "this" (postconditions only), and method parameters (e.g. "#1", "#2", + * postconditions only). + * + * @param preOrPost whether to get the precondition or postcondition + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @param atypeFactory the type factory + * @return the pre- or postcondition annotations for an expression, or null if the given + * expression is not a supported expression type + */ + public @Nullable T getPreOrPostconditions( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory); - /** - * Updates a method to add a declaration annotation. - * - * @param methodElt the method to annotate - * @param anno the declaration annotation to add to the method - * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false - * otherwise - */ - public boolean addMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno); + /** + * Updates a method to add a declaration annotation. + * + * @param methodElt the method to annotate + * @param anno the declaration annotation to add to the method + * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false + * otherwise + */ + public boolean addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); - /** - * Updates a field to add a declaration annotation. - * - * @param fieldElt the field - * @param anno the declaration annotation to add to the field - * @return true if {@code anno} is a new declaration annotation for {@code fieldElt}, false - * otherwise - */ - public boolean addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); + /** + * Updates a field to add a declaration annotation. + * + * @param fieldElt the field + * @param anno the declaration annotation to add to the field + * @return true if {@code anno} is a new declaration annotation for {@code fieldElt}, false + * otherwise + */ + public boolean addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); - /** - * Adds a declaration annotation to a formal parameter. - * - * @param methodElt the method whose formal parameter will be annotated - * @param index_1based the index of the parameter (1-indexed) - * @param anno the annotation to add - * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false - * otherwise - */ - public boolean addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); + /** + * Adds a declaration annotation to a formal parameter. + * + * @param methodElt the method whose formal parameter will be annotated + * @param index_1based the index of the parameter (1-indexed) + * @param anno the annotation to add + * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false + * otherwise + */ + public boolean addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); - /** - * Adds an annotation to a class declaration. - * - * @param classElt the class declaration to annotate - * @param anno the annotation to add - * @return true if {@code anno} is a new declaration annotation for {@code classElt}, false - * otherwise - */ - public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); + /** + * Adds an annotation to a class declaration. + * + * @param classElt the class declaration to annotate + * @param anno the annotation to add + * @return true if {@code anno} is a new declaration annotation for {@code classElt}, false + * otherwise + */ + public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); - /** - * Return the list of declaration annotations inferred on the given method so far in this round - * of WPI. - * - * @param elt a method - * @return the declaration annotations inferred on elt so far (may be empty) - */ - AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt); + /** + * Return the list of declaration annotations inferred on the given method so far in this round of + * WPI. + * + * @param elt a method + * @return the declaration annotations inferred on elt so far (may be empty) + */ + AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt); - /** - * Removes the given annotation from the given method element's inferred declaration annotation. - * If the given annotation was not in the list of inferred declaration annotations on the given - * method, calling this method is a no-op. - * - * @param methodElt a method element - * @param anno a declaration annotation to remove - * @return true if the annotation was successfully removed, false if not (e.g., if it wasn't - * present) - */ - boolean removeMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); + /** + * Removes the given annotation from the given method element's inferred declaration annotation. + * If the given annotation was not in the list of inferred declaration annotations on the given + * method, calling this method is a no-op. + * + * @param methodElt a method element + * @param anno a declaration annotation to remove + * @return true if the annotation was successfully removed, false if not (e.g., if it wasn't + * present) + */ + boolean removeMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); - /** - * Obtain the type from a storage location. - * - * @param typeMirror the underlying type for the result - * @param storageLocation the storage location from which to obtain annotations - * @return an annotated type mirror with underlying type {@code typeMirror} and annotations from - * {@code storageLocation} - */ - public AnnotatedTypeMirror atmFromStorageLocation(TypeMirror typeMirror, T storageLocation); + /** + * Obtain the type from a storage location. + * + * @param typeMirror the underlying type for the result + * @param storageLocation the storage location from which to obtain annotations + * @return an annotated type mirror with underlying type {@code typeMirror} and annotations from + * {@code storageLocation} + */ + public AnnotatedTypeMirror atmFromStorageLocation(TypeMirror typeMirror, T storageLocation); - /** - * Updates a storage location to have the annotations of the given {@code AnnotatedTypeMirror}. - * Annotations in the original set that should be ignored are not added to the resulting set. If - * {@code ignoreIfAnnotated} is true, doesn't add annotations for locations with explicit - * annotations in source code. - * - *

This method removes from the storage location all annotations supported by the - * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is - * called, the new {@code AnnotatedTypeMirror} has a better type estimate for the given - * location. Therefore, it is not a problem to remove all annotations before inserting the new - * annotations. - * - *

The {@code update*} methods in {@link WholeProgramInference} perform LUB. This one just - * does replacement. (Thus, the naming may be a bit confusing.) - * - * @param newATM the type whose annotations will be added to the {@code AnnotatedTypeMirror} - * @param curATM the annotations currently stored at the location, used to check if the element - * that will be updated has explicit annotations in source code - * @param storageLocationToUpdate the storage location that will be updated - * @param defLoc the location where the annotation will be added - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - public void updateStorageLocationFromAtm( - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - T storageLocationToUpdate, - TypeUseLocation defLoc, - boolean ignoreIfAnnotated); + /** + * Updates a storage location to have the annotations of the given {@code AnnotatedTypeMirror}. + * Annotations in the original set that should be ignored are not added to the resulting set. If + * {@code ignoreIfAnnotated} is true, doesn't add annotations for locations with explicit + * annotations in source code. + * + *

This method removes from the storage location all annotations supported by the + * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is + * called, the new {@code AnnotatedTypeMirror} has a better type estimate for the given location. + * Therefore, it is not a problem to remove all annotations before inserting the new annotations. + * + *

The {@code update*} methods in {@link WholeProgramInference} perform LUB. This one just does + * replacement. (Thus, the naming may be a bit confusing.) + * + * @param newATM the type whose annotations will be added to the {@code AnnotatedTypeMirror} + * @param curATM the annotations currently stored at the location, used to check if the element + * that will be updated has explicit annotations in source code + * @param storageLocationToUpdate the storage location that will be updated + * @param defLoc the location where the annotation will be added + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + public void updateStorageLocationFromAtm( + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + T storageLocationToUpdate, + TypeUseLocation defLoc, + boolean ignoreIfAnnotated); - /** - * Writes the inferred results to a file. Ideally, it should be called at the end of the - * type-checking process. In practice, it is called after each class, because we don't know - * which class will be the last one in the type-checking process. - * - * @param outputFormat the file format in which to write the results - * @param checker the checker from which this method is called, for naming annotation files - */ - public void writeResultsToFile( - WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker); + /** + * Writes the inferred results to a file. Ideally, it should be called at the end of the + * type-checking process. In practice, it is called after each class, because we don't know which + * class will be the last one in the type-checking process. + * + * @param outputFormat the file format in which to write the results + * @param checker the checker from which this method is called, for naming annotation files + */ + public void writeResultsToFile( + WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker); - /** - * Indicates that inferred annotations for the file at {@code path} have changed since last - * written. This causes output files for {@code path} to be written out next time {@link - * #writeResultsToFile} is called. - * - * @param path path to the file with annotations that have been modified - */ - public void setFileModified(String path); + /** + * Indicates that inferred annotations for the file at {@code path} have changed since last + * written. This causes output files for {@code path} to be written out next time {@link + * #writeResultsToFile} is called. + * + * @param path path to the file with annotations that have been modified + */ + public void setFileModified(String path); - /** - * Performs any preparation required for inference on the elements of a class. Should be called - * on each top-level class declaration in a compilation unit before processing it. - * - * @param classTree the class to preprocess - */ - void preprocessClassTree(ClassTree classTree); + /** + * Performs any preparation required for inference on the elements of a class. Should be called on + * each top-level class declaration in a compilation unit before processing it. + * + * @param classTree the class to preprocess + */ + void preprocessClassTree(ClassTree classTree); } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java index b5011bb8f2d..31f8311629d 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java @@ -1,7 +1,17 @@ package org.checkerframework.common.wholeprograminference.scenelib; import com.sun.tools.javac.code.Symbol.ClassSymbol; - +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.afu.scenelib.Annotation; import org.checkerframework.afu.scenelib.el.AClass; import org.checkerframework.afu.scenelib.el.AField; @@ -25,19 +35,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; - /** * scene-lib (from the Annotation File Utilities) doesn't provide enough information to usefully * print stub files: it lacks information about what is and is not an enum, about the base types of @@ -51,212 +48,208 @@ */ public class ASceneWrapper { - /** The AScene being wrapped. */ - private final AScene theScene; + /** The AScene being wrapped. */ + private final AScene theScene; - /** - * Constructor. Pass the AScene to wrap. - * - * @param theScene the scene to wrap - */ - public ASceneWrapper(AScene theScene) { - this.theScene = theScene; - } + /** + * Constructor. Pass the AScene to wrap. + * + * @param theScene the scene to wrap + */ + public ASceneWrapper(AScene theScene) { + this.theScene = theScene; + } - /** - * Removes the specified annotations from an AScene. - * - * @param scene the scene from which to remove annotations - * @param annosToRemove annotations that should not be added to .jaif or stub files - */ - private void removeAnnosFromScene(AScene scene, AnnotationsInContexts annosToRemove) { - for (AClass aclass : scene.classes.values()) { - for (AField field : aclass.fields.values()) { - removeAnnosFromATypeElement(field.type, TypeUseLocation.FIELD, annosToRemove); - } - for (AMethod method : aclass.methods.values()) { - removeAnnosFromATypeElement( - method.returnType, TypeUseLocation.RETURN, annosToRemove); - removeAnnosFromATypeElement( - method.receiver.type, TypeUseLocation.RECEIVER, annosToRemove); - for (AField param : method.parameters.values()) { - removeAnnosFromATypeElement( - param.type, TypeUseLocation.PARAMETER, annosToRemove); - } - } + /** + * Removes the specified annotations from an AScene. + * + * @param scene the scene from which to remove annotations + * @param annosToRemove annotations that should not be added to .jaif or stub files + */ + private void removeAnnosFromScene(AScene scene, AnnotationsInContexts annosToRemove) { + for (AClass aclass : scene.classes.values()) { + for (AField field : aclass.fields.values()) { + removeAnnosFromATypeElement(field.type, TypeUseLocation.FIELD, annosToRemove); + } + for (AMethod method : aclass.methods.values()) { + removeAnnosFromATypeElement(method.returnType, TypeUseLocation.RETURN, annosToRemove); + removeAnnosFromATypeElement(method.receiver.type, TypeUseLocation.RECEIVER, annosToRemove); + for (AField param : method.parameters.values()) { + removeAnnosFromATypeElement(param.type, TypeUseLocation.PARAMETER, annosToRemove); } + } } + } - /** - * Removes the specified annotations from an ATypeElement. - * - * @param typeElt the type element from which to remove annotations - * @param loc the location where typeElt is used - * @param annosToRemove annotations that should not be added to .jaif or stub files - */ - private void removeAnnosFromATypeElement( - ATypeElement typeElt, TypeUseLocation loc, AnnotationsInContexts annosToRemove) { - String annosToRemoveKey = WholeProgramInferenceScenesStorage.aTypeElementToString(typeElt); - Set annosToRemoveForLocation = annosToRemove.get(IPair.of(annosToRemoveKey, loc)); - if (annosToRemoveForLocation != null) { - Set annosToRemoveHere = - ArraySet.newArraySetOrHashSet(annosToRemoveForLocation.size()); - for (Annotation anno : typeElt.tlAnnotationsHere) { - if (annosToRemoveForLocation.contains(anno.def().toString())) { - annosToRemoveHere.add(anno); - } - } - typeElt.tlAnnotationsHere.removeAll(annosToRemoveHere); + /** + * Removes the specified annotations from an ATypeElement. + * + * @param typeElt the type element from which to remove annotations + * @param loc the location where typeElt is used + * @param annosToRemove annotations that should not be added to .jaif or stub files + */ + private void removeAnnosFromATypeElement( + ATypeElement typeElt, TypeUseLocation loc, AnnotationsInContexts annosToRemove) { + String annosToRemoveKey = WholeProgramInferenceScenesStorage.aTypeElementToString(typeElt); + Set annosToRemoveForLocation = annosToRemove.get(IPair.of(annosToRemoveKey, loc)); + if (annosToRemoveForLocation != null) { + Set annosToRemoveHere = + ArraySet.newArraySetOrHashSet(annosToRemoveForLocation.size()); + for (Annotation anno : typeElt.tlAnnotationsHere) { + if (annosToRemoveForLocation.contains(anno.def().toString())) { + annosToRemoveHere.add(anno); } + } + typeElt.tlAnnotationsHere.removeAll(annosToRemoveHere); + } - // Recursively remove annotations from inner types - for (ATypeElement innerType : typeElt.innerTypes.values()) { - removeAnnosFromATypeElement(innerType, loc, annosToRemove); - } + // Recursively remove annotations from inner types + for (ATypeElement innerType : typeElt.innerTypes.values()) { + removeAnnosFromATypeElement(innerType, loc, annosToRemove); } + } - /** - * Write the scene wrapped by this object to a file at the given path. - * - * @param jaifPath the path of the file to be written, but ending in ".jaif". If {@code - * outputformat} is not {@code JAIF}, the path will be modified to match. - * @param annosToIgnore which annotations should be ignored in which contexts - * @param outputFormat the output format to use - * @param checker the checker from which this method is called, for naming stub files - */ - public void writeToFile( - String jaifPath, - AnnotationsInContexts annosToIgnore, - OutputFormat outputFormat, - BaseTypeChecker checker) { - assert jaifPath.endsWith(".jaif"); - AScene scene = theScene.clone(); - removeAnnosFromScene(scene, annosToIgnore); - scene.prune(); - String filepath; + /** + * Write the scene wrapped by this object to a file at the given path. + * + * @param jaifPath the path of the file to be written, but ending in ".jaif". If {@code + * outputformat} is not {@code JAIF}, the path will be modified to match. + * @param annosToIgnore which annotations should be ignored in which contexts + * @param outputFormat the output format to use + * @param checker the checker from which this method is called, for naming stub files + */ + public void writeToFile( + String jaifPath, + AnnotationsInContexts annosToIgnore, + OutputFormat outputFormat, + BaseTypeChecker checker) { + assert jaifPath.endsWith(".jaif"); + AScene scene = theScene.clone(); + removeAnnosFromScene(scene, annosToIgnore); + scene.prune(); + String filepath; + switch (outputFormat) { + case JAIF: + filepath = jaifPath; + break; + case STUB: + String astubWithChecker = "-" + checker.getClass().getCanonicalName() + ".astub"; + filepath = jaifPath.replace(".jaif", astubWithChecker); + break; + default: + throw new BugInCF("Unhandled outputFormat " + outputFormat); + } + new File(filepath).delete(); + // Only write non-empty scenes into files. + if (!scene.isEmpty()) { + try { switch (outputFormat) { - case JAIF: - filepath = jaifPath; - break; - case STUB: - String astubWithChecker = "-" + checker.getClass().getCanonicalName() + ".astub"; - filepath = jaifPath.replace(".jaif", astubWithChecker); - break; - default: - throw new BugInCF("Unhandled outputFormat " + outputFormat); - } - new File(filepath).delete(); - // Only write non-empty scenes into files. - if (!scene.isEmpty()) { - try { - switch (outputFormat) { - case STUB: - // For stub files, pass in the checker to compute contracts on the fly; - // precomputing yields incorrect annotations, most likely due to nested - // classes. - SceneToStubWriter.write(this, filepath, checker); - break; - case JAIF: - // For .jaif files, precompute contracts because the Annotation File - // Utilities knows nothing about (and cannot depend on) the Checker - // Framework. - for (Map.Entry classEntry : scene.classes.entrySet()) { - AClass aClass = classEntry.getValue(); - for (Map.Entry methodEntry : - aClass.getMethods().entrySet()) { - AMethod aMethod = methodEntry.getValue(); - List contractAnnotationMirrors = - checker.getTypeFactory().getContractAnnotations(aMethod); - List contractAnnotations = - CollectionsPlume.mapList( - AnnotationConverter::annotationMirrorToAnnotation, - contractAnnotationMirrors); - aMethod.contracts = contractAnnotations; - } - } - try (FileWriter fw = new FileWriter(filepath)) { - IndexFileWriter.write(scene, fw); - } - break; - default: - throw new BugInCF("Unhandled outputFormat " + outputFormat); - } - } catch (IOException e) { - throw new UserError("Problem while writing %s: %s", filepath, e.getMessage()); - } catch (DefException e) { - throw new BugInCF(e); + case STUB: + // For stub files, pass in the checker to compute contracts on the fly; + // precomputing yields incorrect annotations, most likely due to nested + // classes. + SceneToStubWriter.write(this, filepath, checker); + break; + case JAIF: + // For .jaif files, precompute contracts because the Annotation File + // Utilities knows nothing about (and cannot depend on) the Checker + // Framework. + for (Map.Entry classEntry : scene.classes.entrySet()) { + AClass aClass = classEntry.getValue(); + for (Map.Entry methodEntry : aClass.getMethods().entrySet()) { + AMethod aMethod = methodEntry.getValue(); + List contractAnnotationMirrors = + checker.getTypeFactory().getContractAnnotations(aMethod); + List contractAnnotations = + CollectionsPlume.mapList( + AnnotationConverter::annotationMirrorToAnnotation, + contractAnnotationMirrors); + aMethod.contracts = contractAnnotations; + } + } + try (FileWriter fw = new FileWriter(filepath)) { + IndexFileWriter.write(scene, fw); } + break; + default: + throw new BugInCF("Unhandled outputFormat " + outputFormat); } + } catch (IOException e) { + throw new UserError("Problem while writing %s: %s", filepath, e.getMessage()); + } catch (DefException e) { + throw new BugInCF(e); + } } + } - /** - * Updates the symbol information stored in AClass for the given class. May be called multiple - * times (and needs to be if the second parameter was null the first time it was called; only - * some calls provide the symbol information). - * - * @param aClass the class representation in which the symbol information is to be updated - * @param classSymbol the source of the symbol information; may be null, in which case this - * method does nothing - */ - public void updateSymbolInformation(AClass aClass, @Nullable ClassSymbol classSymbol) { - if (classSymbol == null) { - return; + /** + * Updates the symbol information stored in AClass for the given class. May be called multiple + * times (and needs to be if the second parameter was null the first time it was called; only some + * calls provide the symbol information). + * + * @param aClass the class representation in which the symbol information is to be updated + * @param classSymbol the source of the symbol information; may be null, in which case this method + * does nothing + */ + public void updateSymbolInformation(AClass aClass, @Nullable ClassSymbol classSymbol) { + if (classSymbol == null) { + return; + } + if (classSymbol.isEnum()) { + List enumConstants = ElementUtils.getEnumConstants(classSymbol); + if (!aClass.isEnum(classSymbol.getSimpleName().toString())) { + aClass.setEnumConstants(enumConstants); + } else { + // Verify that the existing value is consistent. + List existingEnumConstants = aClass.getEnumConstants(); + if (existingEnumConstants.size() != enumConstants.size()) { + throw new BugInCF( + "inconsistent enum constants in WPI for class " + + classSymbol.getQualifiedName().toString()); } - if (classSymbol.isEnum()) { - List enumConstants = ElementUtils.getEnumConstants(classSymbol); - if (!aClass.isEnum(classSymbol.getSimpleName().toString())) { - aClass.setEnumConstants(enumConstants); - } else { - // Verify that the existing value is consistent. - List existingEnumConstants = aClass.getEnumConstants(); - if (existingEnumConstants.size() != enumConstants.size()) { - throw new BugInCF( - "inconsistent enum constants in WPI for class " - + classSymbol.getQualifiedName().toString()); - } - for (int i = 0; i < enumConstants.size(); i++) { - if (!existingEnumConstants.get(i).equals(enumConstants.get(i))) { - throw new BugInCF( - "inconsistent enum constants in WPI for class " - + classSymbol.getQualifiedName().toString()); - } - } - } + for (int i = 0; i < enumConstants.size(); i++) { + if (!existingEnumConstants.get(i).equals(enumConstants.get(i))) { + throw new BugInCF( + "inconsistent enum constants in WPI for class " + + classSymbol.getQualifiedName().toString()); + } } + } + } - ClassSymbol outerClass = classSymbol; - ClassSymbol previous = classSymbol; - do { - if (outerClass.getKind() == ElementKind.ANNOTATION_TYPE) { - aClass.markAsAnnotation(outerClass.getSimpleName().toString()); - } else if (outerClass.isEnum()) { - aClass.markAsEnum(outerClass.getSimpleName().toString()); - } else if (outerClass.isInterface()) { - aClass.markAsInterface(outerClass.getSimpleName().toString()); - // } else if (outerClass.isRecord()) { - // aClass.markAsRecord(outerClass.getSimpleName().toString()); - } - Element element = classSymbol.getEnclosingElement(); - if (element == null || element.getKind() == ElementKind.PACKAGE) { - break; - } - TypeElement t = ElementUtils.enclosingTypeElement(element); - previous = outerClass; - outerClass = (ClassSymbol) t; - // It is necessary to check that previous isn't equal to outer class because - // otherwise this loop will sometimes run forever. - } while (outerClass != null && !previous.equals(outerClass)); + ClassSymbol outerClass = classSymbol; + ClassSymbol previous = classSymbol; + do { + if (outerClass.getKind() == ElementKind.ANNOTATION_TYPE) { + aClass.markAsAnnotation(outerClass.getSimpleName().toString()); + } else if (outerClass.isEnum()) { + aClass.markAsEnum(outerClass.getSimpleName().toString()); + } else if (outerClass.isInterface()) { + aClass.markAsInterface(outerClass.getSimpleName().toString()); + // } else if (outerClass.isRecord()) { + // aClass.markAsRecord(outerClass.getSimpleName().toString()); + } + Element element = classSymbol.getEnclosingElement(); + if (element == null || element.getKind() == ElementKind.PACKAGE) { + break; + } + TypeElement t = ElementUtils.enclosingTypeElement(element); + previous = outerClass; + outerClass = (ClassSymbol) t; + // It is necessary to check that previous isn't equal to outer class because + // otherwise this loop will sometimes run forever. + } while (outerClass != null && !previous.equals(outerClass)); - aClass.setTypeElement(classSymbol); - } + aClass.setTypeElement(classSymbol); + } - /** - * Avoid using this if possible; use the other methods of this class unless you absolutely need - * an AScene. - * - * @return the AScene representation of this - */ - public AScene getAScene() { - return theScene; - } + /** + * Avoid using this if possible; use the other methods of this class unless you absolutely need an + * AScene. + * + * @return the AScene representation of this + */ + public AScene getAScene() { + return theScene; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java index d78a97bd1ca..b572da4fc89 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java @@ -4,12 +4,10 @@ import com.github.javaparser.ast.comments.Comment; import com.github.javaparser.ast.expr.AnnotationExpr; import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; - +import java.util.List; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.List; - /** * Given two ASTs representing the same Java file that may differ in annotations, tests if they have * the same annotations. @@ -18,80 +16,79 @@ * the second argument. Then, check {@link #getAnnotationsMatch}. */ public class AnnotationEqualityVisitor extends DoubleJavaParserVisitor { - /** Whether or not a node with mismatched annotations has been seen. */ - private boolean annotationsMatch; + /** Whether or not a node with mismatched annotations has been seen. */ + private boolean annotationsMatch; - /** If a node with mismatched annotations has been seen, stores the node from the first AST. */ - private @MonotonicNonNull NodeWithAnnotations mismatchedNode1; + /** If a node with mismatched annotations has been seen, stores the node from the first AST. */ + private @MonotonicNonNull NodeWithAnnotations mismatchedNode1; - /** If a node with mismatched annotations has been seen, stores the node from the second AST. */ - private @MonotonicNonNull NodeWithAnnotations mismatchedNode2; + /** If a node with mismatched annotations has been seen, stores the node from the second AST. */ + private @MonotonicNonNull NodeWithAnnotations mismatchedNode2; - /** Constructs an {@code AnnotationEqualityVisitor}. */ - public AnnotationEqualityVisitor() { - annotationsMatch = true; - mismatchedNode1 = null; - mismatchedNode2 = null; - } + /** Constructs an {@code AnnotationEqualityVisitor}. */ + public AnnotationEqualityVisitor() { + annotationsMatch = true; + mismatchedNode1 = null; + mismatchedNode2 = null; + } - /** - * Returns whether a visited pair of nodes differed in annotations. - * - * @return true if some visited pair of nodes differed in annotations - */ - public boolean getAnnotationsMatch() { - return annotationsMatch; - } + /** + * Returns whether a visited pair of nodes differed in annotations. + * + * @return true if some visited pair of nodes differed in annotations + */ + public boolean getAnnotationsMatch() { + return annotationsMatch; + } - /** - * If a visited pair of nodes has had mismatched annotations, returns the node from the first - * AST where annotations differed, or null otherwise. - * - * @return the node from the first AST with differing annotations or null - */ - public @Nullable NodeWithAnnotations getMismatchedNode1() { - return mismatchedNode1; - } + /** + * If a visited pair of nodes has had mismatched annotations, returns the node from the first AST + * where annotations differed, or null otherwise. + * + * @return the node from the first AST with differing annotations or null + */ + public @Nullable NodeWithAnnotations getMismatchedNode1() { + return mismatchedNode1; + } - /** - * If a visited pair of nodes has had mismatched annotations, returns the node from the second - * AST where annotations differed, or null otherwise. - * - * @return the node from the second AST with differing annotations or null - */ - public @Nullable NodeWithAnnotations getMismatchedNode2() { - return mismatchedNode2; - } + /** + * If a visited pair of nodes has had mismatched annotations, returns the node from the second AST + * where annotations differed, or null otherwise. + * + * @return the node from the second AST with differing annotations or null + */ + public @Nullable NodeWithAnnotations getMismatchedNode2() { + return mismatchedNode2; + } - @Override - public void defaultAction(T node1, T node2) { - if (!(node1 instanceof NodeWithAnnotations) - || !(node2 instanceof NodeWithAnnotations)) { - return; - } + @Override + public void defaultAction(T node1, T node2) { + if (!(node1 instanceof NodeWithAnnotations) || !(node2 instanceof NodeWithAnnotations)) { + return; + } - // Comparing annotations with "equals" considers comments in the AST attached to the - // annotations. These should be ignored because two valid ASTs for the same file may differ - // in where comments appear, or whether they appear at all. So, to check if two nodes have - // the same annotations we create copies with all comments removed and compare their lists - // of annotations directly. - Node node1Copy = node1.clone(); - Node node2Copy = node2.clone(); + // Comparing annotations with "equals" considers comments in the AST attached to the + // annotations. These should be ignored because two valid ASTs for the same file may differ + // in where comments appear, or whether they appear at all. So, to check if two nodes have + // the same annotations we create copies with all comments removed and compare their lists + // of annotations directly. + Node node1Copy = node1.clone(); + Node node2Copy = node2.clone(); - for (Comment comment : node1Copy.getAllContainedComments()) { - comment.remove(); - } - for (Comment comment : node2Copy.getAllContainedComments()) { - comment.remove(); - } + for (Comment comment : node1Copy.getAllContainedComments()) { + comment.remove(); + } + for (Comment comment : node2Copy.getAllContainedComments()) { + comment.remove(); + } - List node1annos = ((NodeWithAnnotations) node1Copy).getAnnotations(); - List node2annos = ((NodeWithAnnotations) node2Copy).getAnnotations(); + List node1annos = ((NodeWithAnnotations) node1Copy).getAnnotations(); + List node2annos = ((NodeWithAnnotations) node2Copy).getAnnotations(); - if (!node1annos.equals(node2annos)) { - annotationsMatch = false; - mismatchedNode1 = (NodeWithAnnotations) node1; - mismatchedNode2 = (NodeWithAnnotations) node2; - } + if (!node1annos.equals(node2annos)) { + annotationsMatch = false; + mismatchedNode1 = (NodeWithAnnotations) node1; + mismatchedNode2 = (NodeWithAnnotations) node2; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java index 5b3a2c790e9..1de6be9e75d 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java @@ -2,10 +2,6 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.TypeDeclaration; - -import org.checkerframework.framework.util.JavaParserUtil; -import org.checkerframework.javacutil.BugInCF; - import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -13,68 +9,70 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.checkerframework.framework.util.JavaParserUtil; +import org.checkerframework.javacutil.BugInCF; /** * Stores a collection of annotation files. Given a type name, can return a list of paths to stored * annotation files corresponding to that type name. */ public class AnnotationFileStore { - /** - * Mapping from a fully qualified class name to the paths to annotation files that contain that - * type. - */ - private final Map> annotationFiles; + /** + * Mapping from a fully qualified class name to the paths to annotation files that contain that + * type. + */ + private final Map> annotationFiles; - /** Constructs an {@code AnnotationFileStore}. */ - public AnnotationFileStore() { - annotationFiles = new HashMap<>(); - } + /** Constructs an {@code AnnotationFileStore}. */ + public AnnotationFileStore() { + annotationFiles = new HashMap<>(); + } - /** - * If {@code location} is a file, stores it in this as an annotation file. If {@code location} - * is a directory, stores all annotation files contained in it. - * - * @param location an annotation file or a directory containing annotation files - */ - public void addFileOrDirectory(File location) { - if (location.isDirectory()) { - for (File child : location.listFiles()) { - addFileOrDirectory(child); - } + /** + * If {@code location} is a file, stores it in this as an annotation file. If {@code location} is + * a directory, stores all annotation files contained in it. + * + * @param location an annotation file or a directory containing annotation files + */ + public void addFileOrDirectory(File location) { + if (location.isDirectory()) { + for (File child : location.listFiles()) { + addFileOrDirectory(child); + } - return; - } + return; + } - if (location.isFile() && location.getName().endsWith(".ajava")) { - try { - CompilationUnit root = JavaParserUtil.parseCompilationUnit(location); - for (TypeDeclaration type : root.getTypes()) { - String name = JavaParserUtil.getFullyQualifiedName(type, root); + if (location.isFile() && location.getName().endsWith(".ajava")) { + try { + CompilationUnit root = JavaParserUtil.parseCompilationUnit(location); + for (TypeDeclaration type : root.getTypes()) { + String name = JavaParserUtil.getFullyQualifiedName(type, root); - if (!annotationFiles.containsKey(name)) { - annotationFiles.put(name, new ArrayList<>()); - } + if (!annotationFiles.containsKey(name)) { + annotationFiles.put(name, new ArrayList<>()); + } - annotationFiles.get(name).add(location.getPath()); - } - } catch (FileNotFoundException e) { - throw new BugInCF("Unable to open annotation file: " + location.getPath(), e); - } + annotationFiles.get(name).add(location.getPath()); } + } catch (FileNotFoundException e) { + throw new BugInCF("Unable to open annotation file: " + location.getPath(), e); + } } + } - /** - * Given a fully qualified type name, returns a List of paths to annotation files containing - * annotations for the type. - * - * @param typeName fully qualified name of a type - * @return a list of paths to annotation files with annotations for {@code typeName} - */ - public List getAnnotationFileForType(String typeName) { - if (!annotationFiles.containsKey(typeName)) { - return Collections.emptyList(); - } - - return Collections.unmodifiableList(annotationFiles.get(typeName)); + /** + * Given a fully qualified type name, returns a List of paths to annotation files containing + * annotations for the type. + * + * @param typeName fully qualified name of a type + * @return a list of paths to annotation files with annotations for {@code typeName} + */ + public List getAnnotationFileForType(String typeName) { + if (!annotationFiles.containsKey(typeName)) { + return Collections.emptyList(); } + + return Collections.unmodifiableList(annotationFiles.get(typeName)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java index 90509739378..026532cb02d 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java @@ -23,16 +23,8 @@ import com.github.javaparser.ast.expr.UnaryExpr; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.utils.StringEscapeUtils; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypesUtils; - import java.util.List; import java.util.Map; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValueVisitor; @@ -42,231 +34,231 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypesUtils; /** * Methods for converting a {@code AnnotationMirror} into a JavaParser {@code AnnotationExpr}, * namely {@code annotationMirrorToAnnotationExpr}. */ public class AnnotationMirrorToAnnotationExprConversion { - /** - * Converts an AnnotationMirror into a JavaParser {@code AnnotationExpr}. - * - * @param annotation the annotation to convert - * @return a JavaParser {@code AnnotationExpr} representing the same annotation with the same - * element values. The converted annotation will contain the annotation's fully qualified - * name. - */ - public static AnnotationExpr annotationMirrorToAnnotationExpr(AnnotationMirror annotation) { - Map values = - annotation.getElementValues(); - Name name = createQualifiedName(AnnotationUtils.annotationName(annotation)); - if (values.isEmpty()) { - return new MarkerAnnotationExpr(name); - } - - NodeList convertedValues = convertAnnotationValues(values); - if (convertedValues.size() == 1 - && convertedValues.get(0).getName().asString().equals("value")) { - return new SingleMemberAnnotationExpr(name, convertedValues.get(0).getValue()); - } - - return new NormalAnnotationExpr(name, convertedValues); + /** + * Converts an AnnotationMirror into a JavaParser {@code AnnotationExpr}. + * + * @param annotation the annotation to convert + * @return a JavaParser {@code AnnotationExpr} representing the same annotation with the same + * element values. The converted annotation will contain the annotation's fully qualified + * name. + */ + public static AnnotationExpr annotationMirrorToAnnotationExpr(AnnotationMirror annotation) { + Map values = + annotation.getElementValues(); + Name name = createQualifiedName(AnnotationUtils.annotationName(annotation)); + if (values.isEmpty()) { + return new MarkerAnnotationExpr(name); } - /** - * Converts a Set of AnnotationMirror into List of JavaParser {@code AnnotationExpr}. - * - * @param annotationMirrors the annotations to convert - * @return a list of JavaParser {@code AnnotationExpr}s representing the same annotations - * @see #annotationMirrorToAnnotationExpr - */ - public static NodeList annotationMirrorSetToAnnotationExprList( - AnnotationMirrorSet annotationMirrors) { - NodeList result = new NodeList<>(); - for (AnnotationMirror am : annotationMirrors) { - result.add(annotationMirrorToAnnotationExpr(am)); - } - return result; + NodeList convertedValues = convertAnnotationValues(values); + if (convertedValues.size() == 1 + && convertedValues.get(0).getName().asString().equals("value")) { + return new SingleMemberAnnotationExpr(name, convertedValues.get(0).getValue()); } - /** - * Converts a mapping of (annotation element → value) into a list of key-value pairs - * containing the JavaParser representations of the same values. - * - * @param values mapping of element values from an {@code AnnotationMirror} - * @return a list of the key-value pairs in {@code values} converted to their JavaParser - * representations - */ - private static NodeList convertAnnotationValues( - Map values) { - NodeList convertedValues = new NodeList<>(); - AnnotationValueConverterVisitor converter = new AnnotationValueConverterVisitor(); - for (Map.Entry entry : - values.entrySet()) { - AnnotationValue value = entry.getValue(); - convertedValues.add( - new MemberValuePair( - entry.getKey().getSimpleName().toString(), - value.accept(converter, null))); - } - - return convertedValues; + return new NormalAnnotationExpr(name, convertedValues); + } + + /** + * Converts a Set of AnnotationMirror into List of JavaParser {@code AnnotationExpr}. + * + * @param annotationMirrors the annotations to convert + * @return a list of JavaParser {@code AnnotationExpr}s representing the same annotations + * @see #annotationMirrorToAnnotationExpr + */ + public static NodeList annotationMirrorSetToAnnotationExprList( + AnnotationMirrorSet annotationMirrors) { + NodeList result = new NodeList<>(); + for (AnnotationMirror am : annotationMirrors) { + result.add(annotationMirrorToAnnotationExpr(am)); } - - /** - * Given a fully qualified name, creates a JavaParser {@code Name} structure representing the - * same name. - * - * @param name the fully qualified name to convert - * @return a JavaParser {@code Name} holding {@code name} - */ - private static Name createQualifiedName(String name) { - String[] components = name.split("\\."); - Name result = new Name(components[0]); - for (int i = 1; i < components.length; i++) { - result = new Name(result, components[i]); - } - - return result; + return result; + } + + /** + * Converts a mapping of (annotation element → value) into a list of key-value pairs + * containing the JavaParser representations of the same values. + * + * @param values mapping of element values from an {@code AnnotationMirror} + * @return a list of the key-value pairs in {@code values} converted to their JavaParser + * representations + */ + private static NodeList convertAnnotationValues( + Map values) { + NodeList convertedValues = new NodeList<>(); + AnnotationValueConverterVisitor converter = new AnnotationValueConverterVisitor(); + for (Map.Entry entry : + values.entrySet()) { + AnnotationValue value = entry.getValue(); + convertedValues.add( + new MemberValuePair( + entry.getKey().getSimpleName().toString(), value.accept(converter, null))); } - /** - * A visitor that converts an annotation value from an {@code AnnotationMirror} to a JavaParser - * node that can appear in an {@code AnnotationExpr}. - */ - private static class AnnotationValueConverterVisitor - implements AnnotationValueVisitor { - @Override - public Expression visit(AnnotationValue value, Void p) { - // This is called only if the value couldn't be dispatched to any known type, which - // should never happen. - throw new BugInCF("Unknown annotation value type: " + value); - } - - @Override - public Expression visitAnnotation(AnnotationMirror value, Void p) { - return AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( - value); - } - - @Override - public Expression visitArray(List value, Void p) { - NodeList valueExpressions = new NodeList<>(); - for (AnnotationValue arrayValue : value) { - valueExpressions.add(arrayValue.accept(this, null)); - } + return convertedValues; + } + + /** + * Given a fully qualified name, creates a JavaParser {@code Name} structure representing the same + * name. + * + * @param name the fully qualified name to convert + * @return a JavaParser {@code Name} holding {@code name} + */ + private static Name createQualifiedName(String name) { + String[] components = name.split("\\."); + Name result = new Name(components[0]); + for (int i = 1; i < components.length; i++) { + result = new Name(result, components[i]); + } - return new ArrayInitializerExpr(valueExpressions); - } + return result; + } + + /** + * A visitor that converts an annotation value from an {@code AnnotationMirror} to a JavaParser + * node that can appear in an {@code AnnotationExpr}. + */ + private static class AnnotationValueConverterVisitor + implements AnnotationValueVisitor { + @Override + public Expression visit(AnnotationValue value, Void p) { + // This is called only if the value couldn't be dispatched to any known type, which + // should never happen. + throw new BugInCF("Unknown annotation value type: " + value); + } - @Override - public Expression visitBoolean(boolean value, Void p) { - return new BooleanLiteralExpr(value); - } + @Override + public Expression visitAnnotation(AnnotationMirror value, Void p) { + return AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(value); + } - @Override - public Expression visitByte(byte value, Void p) { - // Annotation byte values are automatically cast to the correct type, so using an - // integer literal here works. - return toIntegerLiteralExpr(value); - } + @Override + public Expression visitArray(List value, Void p) { + NodeList valueExpressions = new NodeList<>(); + for (AnnotationValue arrayValue : value) { + valueExpressions.add(arrayValue.accept(this, null)); + } - @Override - public Expression visitChar(char value, Void p) { - return new CharLiteralExpr(value); - } + return new ArrayInitializerExpr(valueExpressions); + } - @Override - public Expression visitDouble(double value, Void p) { - return new DoubleLiteralExpr(value); - } + @Override + public Expression visitBoolean(boolean value, Void p) { + return new BooleanLiteralExpr(value); + } - @Override - public Expression visitEnumConstant(VariableElement value, Void p) { - // The enclosing element of an enum constant is the enum type itself. - TypeElement enumElt = (TypeElement) value.getEnclosingElement(); - String[] components = enumElt.getQualifiedName().toString().split("\\."); - Expression enumName = new NameExpr(components[0]); - for (int i = 1; i < components.length; i++) { - enumName = new FieldAccessExpr(enumName, components[i]); - } + @Override + public Expression visitByte(byte value, Void p) { + // Annotation byte values are automatically cast to the correct type, so using an + // integer literal here works. + return toIntegerLiteralExpr(value); + } - return new FieldAccessExpr(enumName, value.getSimpleName().toString()); - } + @Override + public Expression visitChar(char value, Void p) { + return new CharLiteralExpr(value); + } - @Override - public Expression visitFloat(float value, Void p) { - return new DoubleLiteralExpr(value + "f"); - } + @Override + public Expression visitDouble(double value, Void p) { + return new DoubleLiteralExpr(value); + } - @Override - public Expression visitInt(int value, Void p) { - return toIntegerLiteralExpr(value); - } + @Override + public Expression visitEnumConstant(VariableElement value, Void p) { + // The enclosing element of an enum constant is the enum type itself. + TypeElement enumElt = (TypeElement) value.getEnclosingElement(); + String[] components = enumElt.getQualifiedName().toString().split("\\."); + Expression enumName = new NameExpr(components[0]); + for (int i = 1; i < components.length; i++) { + enumName = new FieldAccessExpr(enumName, components[i]); + } + + return new FieldAccessExpr(enumName, value.getSimpleName().toString()); + } - @Override - public Expression visitLong(long value, Void p) { - if (value < 0) { - return new UnaryExpr( - new LongLiteralExpr(Long.toString(-value) + "L"), UnaryExpr.Operator.MINUS); - } + @Override + public Expression visitFloat(float value, Void p) { + return new DoubleLiteralExpr(value + "f"); + } - return new LongLiteralExpr(Long.toString(value) + "L"); - } + @Override + public Expression visitInt(int value, Void p) { + return toIntegerLiteralExpr(value); + } - @Override - public Expression visitShort(short value, Void p) { - // Annotation short values are automatically cast to the correct type, so using an - // integer literal here works. - return toIntegerLiteralExpr(value); - } + @Override + public Expression visitLong(long value, Void p) { + if (value < 0) { + return new UnaryExpr( + new LongLiteralExpr(Long.toString(-value) + "L"), UnaryExpr.Operator.MINUS); + } - @Override - public Expression visitString(String value, Void p) { - return new StringLiteralExpr(StringEscapeUtils.escapeJava(value)); - } + return new LongLiteralExpr(Long.toString(value) + "L"); + } - @Override - public Expression visitType(TypeMirror value, Void p) { - if (value.getKind() != TypeKind.DECLARED) { - throw new BugInCF("Unexpected type for class expression: " + value); - } + @Override + public Expression visitShort(short value, Void p) { + // Annotation short values are automatically cast to the correct type, so using an + // integer literal here works. + return toIntegerLiteralExpr(value); + } - DeclaredType type = (DeclaredType) value; - ClassOrInterfaceType parsedType; - try { - parsedType = - StaticJavaParser.parseClassOrInterfaceType( - TypesUtils.getQualifiedName(type)); - } catch (ParseProblemException e) { - throw new BugInCF("Invalid class or interface name: " + value, e); - } + @Override + public Expression visitString(String value, Void p) { + return new StringLiteralExpr(StringEscapeUtils.escapeJava(value)); + } - return new ClassExpr(parsedType); - } + @Override + public Expression visitType(TypeMirror value, Void p) { + if (value.getKind() != TypeKind.DECLARED) { + throw new BugInCF("Unexpected type for class expression: " + value); + } + + DeclaredType type = (DeclaredType) value; + ClassOrInterfaceType parsedType; + try { + parsedType = StaticJavaParser.parseClassOrInterfaceType(TypesUtils.getQualifiedName(type)); + } catch (ParseProblemException e) { + throw new BugInCF("Invalid class or interface name: " + value, e); + } + + return new ClassExpr(parsedType); + } - @Override - public @Nullable Expression visitUnknown(AnnotationValue value, Void p) { - return null; - } + @Override + public @Nullable Expression visitUnknown(AnnotationValue value, Void p) { + return null; + } - /** - * Creates a JavaParser expression node representing a literal with the given value. - * - *

JavaParser represents a negative literal with a {@code UnaryExpr} containing a {@code - * IntegerLiteralExpr}, so this method won't necessarily return an {@code - * IntegerLiteralExpr}. - * - * @param value the value for the literal - * @return a JavaParser expression representing {@code value} - */ - private Expression toIntegerLiteralExpr(int value) { - if (value < 0) { - return new UnaryExpr( - new IntegerLiteralExpr(Integer.toString(-value)), UnaryExpr.Operator.MINUS); - } + /** + * Creates a JavaParser expression node representing a literal with the given value. + * + *

JavaParser represents a negative literal with a {@code UnaryExpr} containing a {@code + * IntegerLiteralExpr}, so this method won't necessarily return an {@code IntegerLiteralExpr}. + * + * @param value the value for the literal + * @return a JavaParser expression representing {@code value} + */ + private Expression toIntegerLiteralExpr(int value) { + if (value < 0) { + return new UnaryExpr( + new IntegerLiteralExpr(Integer.toString(-value)), UnaryExpr.Operator.MINUS); + } - return new IntegerLiteralExpr(Integer.toString(value)); - } + return new IntegerLiteralExpr(Integer.toString(value)); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java index a2df56b8664..781b5365064 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java @@ -9,16 +9,14 @@ import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.type.TypeParameter; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; - /** * A visitor that adds all annotations from a {@code AnnotatedTypeMirror} to the corresponding * JavaParser type, including nested types like array components. @@ -26,63 +24,62 @@ *

The {@code AnnotatedTypeMirror} is passed as the secondary parameter to the visit methods. */ public class AnnotationTransferVisitor extends VoidVisitorAdapter { - @Override - public void visit(ArrayType target, AnnotatedTypeMirror type) { - // type can also be a wildcard, in which case don't try to transfer component - // annotations (there can't be any) - if (type.getKind() == TypeKind.ARRAY) { - target.getComponentType().accept(this, ((AnnotatedArrayType) type).getComponentType()); - } - transferAnnotations(type, target); + @Override + public void visit(ArrayType target, AnnotatedTypeMirror type) { + // type can also be a wildcard, in which case don't try to transfer component + // annotations (there can't be any) + if (type.getKind() == TypeKind.ARRAY) { + target.getComponentType().accept(this, ((AnnotatedArrayType) type).getComponentType()); } + transferAnnotations(type, target); + } - @Override - public void visit(ClassOrInterfaceType target, AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; - if (target.getTypeArguments().isPresent()) { - NodeList types = target.getTypeArguments().get(); - for (int i = 0; i < types.size(); i++) { - types.get(i).accept(this, declaredType.getTypeArguments().get(i)); - } - } + @Override + public void visit(ClassOrInterfaceType target, AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; + if (target.getTypeArguments().isPresent()) { + NodeList types = target.getTypeArguments().get(); + for (int i = 0; i < types.size(); i++) { + types.get(i).accept(this, declaredType.getTypeArguments().get(i)); } - - transferAnnotations(type, target); + } } - @Override - public void visit(PrimitiveType target, AnnotatedTypeMirror type) { - transferAnnotations(type, target); - } + transferAnnotations(type, target); + } - @Override - public void visit(TypeParameter target, AnnotatedTypeMirror type) { - AnnotatedTypeVariable annotatedTypeVar = (AnnotatedTypeVariable) type; - NodeList bounds = target.getTypeBound(); - if (bounds.size() == 1) { - bounds.get(0).accept(this, annotatedTypeVar.getUpperBound()); - } + @Override + public void visit(PrimitiveType target, AnnotatedTypeMirror type) { + transferAnnotations(type, target); + } + + @Override + public void visit(TypeParameter target, AnnotatedTypeMirror type) { + AnnotatedTypeVariable annotatedTypeVar = (AnnotatedTypeVariable) type; + NodeList bounds = target.getTypeBound(); + if (bounds.size() == 1) { + bounds.get(0).accept(this, annotatedTypeVar.getUpperBound()); } + } - /** - * Transfers annotations from {@code annotatedType} to {@code target}. Does nothing if {@code - * annotatedType} is null. - * - * @param annotatedType type with annotations to transfer - * @param target a JavaParser node representing the type to transfer annotations to - */ - private void transferAnnotations( - @Nullable AnnotatedTypeMirror annotatedType, NodeWithAnnotations target) { - if (annotatedType == null) { - return; - } + /** + * Transfers annotations from {@code annotatedType} to {@code target}. Does nothing if {@code + * annotatedType} is null. + * + * @param annotatedType type with annotations to transfer + * @param target a JavaParser node representing the type to transfer annotations to + */ + private void transferAnnotations( + @Nullable AnnotatedTypeMirror annotatedType, NodeWithAnnotations target) { + if (annotatedType == null) { + return; + } - for (AnnotationMirror annotation : annotatedType.getAnnotations()) { - AnnotationExpr convertedAnnotation = - AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( - annotation); - target.addAnnotation(convertedAnnotation); - } + for (AnnotationMirror annotation : annotatedType.getAnnotations()) { + AnnotationExpr convertedAnnotation = + AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(annotation); + target.addAnnotation(convertedAnnotation); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java index a40bce364f4..2278a284c34 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java @@ -139,271 +139,269 @@ * this class, extend it and override a {@code process} method. */ public class DefaultJointVisitor extends JointJavacJavaParserVisitor { - @Override - public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) {} + @Override + public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) {} - @Override - public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) {} + @Override + public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) {} - @Override - public void processAnnotation( - AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) {} + @Override + public void processAnnotation( + AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) {} - @Override - public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) {} + @Override + public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) {} - @Override - public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) {} + @Override + public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) {} - @Override - public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) {} + @Override + public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) {} - @Override - public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) {} + @Override + public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) {} - @Override - public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) {} + @Override + public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) {} - @Override - public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) {} + @Override + public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) {} - @Override - public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) {} + @Override + public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) {} - @Override - public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) {} + @Override + public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) {} - @Override - public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) {} + @Override + public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) {} - @Override - public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) {} + @Override + public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) {} - @Override - public void processCatch(CatchTree javacTree, CatchClause javaParserNode) {} + @Override + public void processCatch(CatchTree javacTree, CatchClause javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) {} - @Override - public void processCompilationUnit( - CompilationUnitTree javacTree, CompilationUnit javaParserNode) {} + @Override + public void processCompilationUnit( + CompilationUnitTree javacTree, CompilationUnit javaParserNode) {} - @Override - public void processConditionalExpression( - ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) {} + @Override + public void processConditionalExpression( + ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) {} - @Override - public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) {} + @Override + public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) {} - @Override - public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) {} + @Override + public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) {} - @Override - public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) {} + @Override + public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) {} - @Override - public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) {} + @Override + public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) {} - @Override - public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) {} + @Override + public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) {} - @Override - public void processExpressionStatemen( - ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) {} + @Override + public void processExpressionStatemen( + ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) {} - @Override - public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) {} + @Override + public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) {} - @Override - public void processIf(IfTree javacTree, IfStmt javaParserNode) {} + @Override + public void processIf(IfTree javacTree, IfStmt javaParserNode) {} - @Override - public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) {} + @Override + public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) {} - @Override - public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) {} + @Override + public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) {} - @Override - public void processIntersectionType( - IntersectionTypeTree javacTree, IntersectionType javaParserNode) {} + @Override + public void processIntersectionType( + IntersectionTypeTree javacTree, IntersectionType javaParserNode) {} - @Override - public void processLabeledStatement( - LabeledStatementTree javacTree, LabeledStmt javaParserNode) {} + @Override + public void processLabeledStatement(LabeledStatementTree javacTree, LabeledStmt javaParserNode) {} - @Override - public void processLambdaExpression( - LambdaExpressionTree javacTree, LambdaExpr javaParserNode) {} + @Override + public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) {} - @Override - public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) {} + @Override + public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) {} - @Override - public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) {} + @Override + public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) {} - @Override - public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) {} + @Override + public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) {} - @Override - public void processMemberReference( - MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) {} + @Override + public void processMemberReference( + MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) {} - @Override - public void processMemberSelect( - MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) {} + @Override + public void processMemberSelect( + MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) {} - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) {} + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) {} - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, MethodCallExpr javaParserNode) {} + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, MethodCallExpr javaParserNode) {} - @Override - public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) {} + @Override + public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) {} - @Override - public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) {} + @Override + public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) {} - @Override - public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) {} + @Override + public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) {} - @Override - public void processOther(Tree javacTree, Node javaParserNode) {} + @Override + public void processOther(Tree javacTree, Node javaParserNode) {} - @Override - public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) {} + @Override + public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) {} - @Override - public void processParameterizedType( - ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) {} + @Override + public void processParameterizedType( + ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) {} - @Override - public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) {} + @Override + public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) {} - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) {} + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) {} - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) {} + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) {} - @Override - public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) {} + @Override + public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) {} - @Override - public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) {} + @Override + public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) {} - @Override - public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) {} + @Override + public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) {} - @Override - public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) {} + @Override + public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) {} - @Override - public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) {} + @Override + public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) {} - @Override - public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) {} + @Override + public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) {} - @Override - public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) {} + @Override + public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) {} - @Override - public void processTry(TryTree javacTree, TryStmt javaParserNode) {} + @Override + public void processTry(TryTree javacTree, TryStmt javaParserNode) {} - @Override - public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) {} + @Override + public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) {} - @Override - public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) {} + @Override + public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) {} - @Override - public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) {} + @Override + public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) {} - @Override - public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) {} + @Override + public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) {} - @Override - public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) {} + @Override + public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, Parameter javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, Parameter javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) {} - @Override - public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) {} + @Override + public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) {} - @Override - public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) {} + @Override + public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) {} - @Override - public void processYield(Tree javacTree, YieldStmt javaParserNode) {} + @Override + public void processYield(Tree javacTree, YieldStmt javaParserNode) {} - @Override - public void processCompoundAssignment( - CompoundAssignmentTree javacTree, AssignExpr javaParserNode) {} + @Override + public void processCompoundAssignment( + CompoundAssignmentTree javacTree, AssignExpr javaParserNode) {} } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java index 3eb60ecccf4..971fc8af9b2 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java @@ -100,7 +100,6 @@ import com.github.javaparser.ast.type.VoidType; import com.github.javaparser.ast.type.WildcardType; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - import java.util.List; /** @@ -118,804 +117,801 @@ @SuppressWarnings("optional:method.invocation") // parallel structure of two data structures public abstract class DoubleJavaParserVisitor extends VoidVisitorAdapter { - /** Create a DoubleJavaParserVisitor. */ - public DoubleJavaParserVisitor() {} - - /** - * Default action performed on all pairs of nodes from matching ASTs. - * - * @param node1 first node in pair - * @param node2 second node in pair - * @param the Node type of {@code node1} and {@code node2} - */ - public abstract void defaultAction(T node1, T node2); - - /** - * Given two lists with the same size where corresponding elements represent nodes with - * almost-identical ASTs as specified in this class's description, visits each pair of - * corresponding elements in order. - * - * @param list1 first list of nodes - * @param list2 second list of nodes, which has the same size as the first list - */ - private void visitLists(List list1, List list2) { - if (list1.size() != list2.size()) { - throw new Error( - String.format( - "%s.visitLists(%s [size %d], %s [size %d])", - this.getClass().getCanonicalName(), - list1, - list1.size(), - list2, - list2.size())); - } - for (int i = 0; i < list1.size(); i++) { - list1.get(i).accept(this, list2.get(i)); - } - } - - @Override - public void visit(AnnotationDeclaration node1, Node other) { - AnnotationDeclaration node2 = (AnnotationDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(AnnotationMemberDeclaration node1, Node other) { - AnnotationMemberDeclaration node2 = (AnnotationMemberDeclaration) other; - defaultAction(node1, node2); - node1.getDefaultValue().ifPresent(dv -> dv.accept(this, node2.getDefaultValue().get())); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(ArrayAccessExpr node1, Node other) { - ArrayAccessExpr node2 = (ArrayAccessExpr) other; - defaultAction(node1, node2); - node1.getIndex().accept(this, node2.getIndex()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ArrayCreationExpr node1, Node other) { - ArrayCreationExpr node2 = (ArrayCreationExpr) other; - defaultAction(node1, node2); - node1.getElementType().accept(this, node2.getElementType()); - node1.getInitializer().ifPresent(init -> init.accept(this, node2.getInitializer().get())); - visitLists(node1.getLevels(), node2.getLevels()); - } - - @Override - public void visit(ArrayInitializerExpr node1, Node other) { - ArrayInitializerExpr node2 = (ArrayInitializerExpr) other; - defaultAction(node1, node2); - visitLists(node1.getValues(), node2.getValues()); - } - - @Override - public void visit(AssertStmt node1, Node other) { - AssertStmt node2 = (AssertStmt) other; - defaultAction(node1, node2); - node1.getCheck().accept(this, node2.getCheck()); - node1.getMessage().ifPresent(m -> m.accept(this, node2.getMessage().get())); - } - - @Override - public void visit(AssignExpr node1, Node other) { - AssignExpr node2 = (AssignExpr) other; - defaultAction(node1, node2); - node1.getTarget().accept(this, node2.getTarget()); - node1.getValue().accept(this, node2.getValue()); - } - - @Override - public void visit(BinaryExpr node1, Node other) { - BinaryExpr node2 = (BinaryExpr) other; - defaultAction(node1, node2); - node1.getLeft().accept(this, node2.getLeft()); - node1.getRight().accept(this, node2.getRight()); - } - - @Override - public void visit(BlockComment node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(BlockStmt node1, Node other) { - BlockStmt node2 = (BlockStmt) other; - defaultAction(node1, node2); - visitLists(node1.getStatements(), node2.getStatements()); - } - - @Override - public void visit(BooleanLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(BreakStmt node1, Node other) { - BreakStmt node2 = (BreakStmt) other; - defaultAction(node1, node2); - node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); - } - - @Override - public void visit(CastExpr node1, Node other) { - CastExpr node2 = (CastExpr) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(CatchClause node1, Node other) { - CatchClause node2 = (CatchClause) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getParameter().accept(this, node2.getParameter()); - } - - @Override - public void visit(CharLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ClassExpr node1, Node other) { - ClassExpr node2 = (ClassExpr) other; - defaultAction(node1, node2); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(ClassOrInterfaceDeclaration node1, Node other) { - ClassOrInterfaceDeclaration node2 = (ClassOrInterfaceDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getExtendedTypes(), node2.getExtendedTypes()); - visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ClassOrInterfaceType node1, Node other) { - ClassOrInterfaceType node2 = (ClassOrInterfaceType) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getScope().ifPresent(s -> s.accept(this, node2.getScope().get())); - node1.getTypeArguments() - .ifPresent(targs -> visitLists(targs, node2.getTypeArguments().get())); - } - - @Override - public void visit(CompilationUnit node1, Node other) { - CompilationUnit node2 = (CompilationUnit) other; - defaultAction(node1, node2); - node1.getModule().ifPresent(m -> m.accept(this, node2.getModule().get())); - node1.getPackageDeclaration() - .ifPresent(pd -> pd.accept(this, node2.getPackageDeclaration().get())); - visitLists(node1.getTypes(), node2.getTypes()); - } - - @Override - public void visit(ConditionalExpr node1, Node other) { - ConditionalExpr node2 = (ConditionalExpr) other; - defaultAction(node1, node2); - node1.getCondition().accept(this, node2.getCondition()); - node1.getElseExpr().accept(this, node2.getElseExpr()); - node1.getThenExpr().accept(this, node2.getThenExpr()); - } - - @Override - public void visit(ConstructorDeclaration node1, Node other) { - ConstructorDeclaration node2 = (ConstructorDeclaration) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - visitLists(node1.getParameters(), node2.getParameters()); - if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { - node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); - } - - visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - } - - @Override - public void visit(CompactConstructorDeclaration node1, Node other) { - CompactConstructorDeclaration node2 = (CompactConstructorDeclaration) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - - visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - } - - @Override - public void visit(ContinueStmt node1, Node other) { - ContinueStmt node2 = (ContinueStmt) other; - defaultAction(node1, node2); - node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); - } - - @Override - public void visit(DoStmt node1, Node other) { - DoStmt node2 = (DoStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getCondition().accept(this, node2.getCondition()); - } - - @Override - public void visit(DoubleLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(EmptyStmt node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(EnclosedExpr node1, Node other) { - EnclosedExpr node2 = (EnclosedExpr) other; - defaultAction(node1, node2); - node1.getInner().accept(this, node2.getInner()); - } - - @Override - public void visit(EnumConstantDeclaration node1, Node other) { - EnumConstantDeclaration node2 = (EnumConstantDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getArguments(), node2.getArguments()); - visitLists(node1.getClassBody(), node2.getClassBody()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(EnumDeclaration node1, Node other) { - EnumDeclaration node2 = (EnumDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getEntries(), node2.getEntries()); - visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ExplicitConstructorInvocationStmt node1, Node other) { - ExplicitConstructorInvocationStmt node2 = (ExplicitConstructorInvocationStmt) other; - defaultAction(node1, node2); - visitLists(node1.getArguments(), node2.getArguments()); - node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(ExpressionStmt node1, Node other) { - ExpressionStmt node2 = (ExpressionStmt) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(FieldAccessExpr node1, Node other) { - FieldAccessExpr node2 = (FieldAccessExpr) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getScope().accept(this, node2.getScope()); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(FieldDeclaration node1, Node other) { - FieldDeclaration node2 = (FieldDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - visitLists(node1.getVariables(), node2.getVariables()); - } - - @Override - public void visit(ForEachStmt node1, Node other) { - ForEachStmt node2 = (ForEachStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getIterable().accept(this, node2.getIterable()); - node1.getVariable().accept(this, node2.getVariable()); - } - - @Override - public void visit(ForStmt node1, Node other) { - ForStmt node2 = (ForStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getCompare().ifPresent(l -> l.accept(this, node2.getCompare().get())); - visitLists(node1.getInitialization(), node2.getInitialization()); - visitLists(node1.getUpdate(), node2.getUpdate()); - } - - @Override - public void visit(IfStmt node1, Node other) { - IfStmt node2 = (IfStmt) other; - defaultAction(node1, node2); - node1.getCondition().accept(this, node2.getCondition()); - node1.getElseStmt().ifPresent(l -> l.accept(this, node2.getElseStmt().get())); - node1.getThenStmt().accept(this, node2.getThenStmt()); - } - - @Override - public void visit(InitializerDeclaration node1, Node other) { - InitializerDeclaration node2 = (InitializerDeclaration) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - } - - @Override - public void visit(InstanceOfExpr node1, Node other) { - InstanceOfExpr node2 = (InstanceOfExpr) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(IntegerLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(JavadocComment node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(LabeledStmt node1, Node other) { - LabeledStmt node2 = (LabeledStmt) other; - defaultAction(node1, node2); - node1.getLabel().accept(this, node2.getLabel()); - node1.getStatement().accept(this, node2.getStatement()); - } - - @Override - public void visit(LineComment node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(LongLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(MarkerAnnotationExpr node1, Node other) { - MarkerAnnotationExpr node2 = (MarkerAnnotationExpr) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(MemberValuePair node1, Node other) { - MemberValuePair node2 = (MemberValuePair) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getValue().accept(this, node2.getName()); - } - - @Override - public void visit(MethodCallExpr node1, Node other) { - MethodCallExpr node2 = (MethodCallExpr) other; - defaultAction(node1, node2); - visitLists(node1.getArguments(), node2.getArguments()); - node1.getName().accept(this, node2.getName()); - node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(MethodDeclaration node1, Node other) { - MethodDeclaration node2 = (MethodDeclaration) other; - defaultAction(node1, node2); - node1.getBody().ifPresent(l -> l.accept(this, node2.getBody().get())); - node1.getType().accept(this, node2.getType()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - visitLists(node1.getParameters(), node2.getParameters()); - if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { - node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); - } - - visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - } - - @Override - public void visit(NameExpr node1, Node other) { - NameExpr node2 = (NameExpr) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(NormalAnnotationExpr node1, Node other) { - NormalAnnotationExpr node2 = (NormalAnnotationExpr) other; - defaultAction(node1, node2); - visitLists(node1.getPairs(), node2.getPairs()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(NullLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ObjectCreationExpr node1, Node other) { - ObjectCreationExpr node2 = (ObjectCreationExpr) other; - defaultAction(node1, node2); - node1.getAnonymousClassBody() - .ifPresent(l -> visitLists(l, node2.getAnonymousClassBody().get())); - visitLists(node1.getArguments(), node2.getArguments()); - node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); - node1.getType().accept(this, node2.getType()); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(PackageDeclaration node1, Node other) { - PackageDeclaration node2 = (PackageDeclaration) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(Parameter node1, Node other) { - Parameter node2 = (Parameter) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(PrimitiveType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(Name node1, Node other) { - Name node2 = (Name) other; - defaultAction(node1, node2); - node1.getQualifier().ifPresent(l -> l.accept(this, node2.getQualifier().get())); - } - - @Override - public void visit(SimpleName node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ArrayType node1, Node other) { - ArrayType node2 = (ArrayType) other; - defaultAction(node1, node2); - node1.getComponentType().accept(this, node2.getComponentType()); - } - - @Override - public void visit(ArrayCreationLevel node1, Node other) { - ArrayCreationLevel node2 = (ArrayCreationLevel) other; - defaultAction(node1, node2); - node1.getDimension().ifPresent(l -> l.accept(this, node2.getDimension().get())); - } - - @Override - public void visit(IntersectionType node1, Node other) { - IntersectionType node2 = (IntersectionType) other; - defaultAction(node1, node2); - visitLists(node1.getElements(), node2.getElements()); - } - - @Override - public void visit(UnionType node1, Node other) { - UnionType node2 = (UnionType) other; - defaultAction(node1, node2); - visitLists(node1.getElements(), node2.getElements()); - } - - @Override - public void visit(RecordDeclaration node1, Node other) { - RecordDeclaration node2 = (RecordDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - visitLists(node1.getParameters(), node2.getParameters()); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { - node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); - } - } - - @Override - public void visit(ReturnStmt node1, Node other) { - ReturnStmt node2 = (ReturnStmt) other; - defaultAction(node1, node2); - node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); - } - - @Override - public void visit(SingleMemberAnnotationExpr node1, Node other) { - SingleMemberAnnotationExpr node2 = (SingleMemberAnnotationExpr) other; - defaultAction(node1, node2); - node1.getMemberValue().accept(this, node2.getMemberValue()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(StringLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(SuperExpr node1, Node other) { - SuperExpr node2 = (SuperExpr) other; - defaultAction(node1, node2); - node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); - } - - @Override - public void visit(SwitchEntry node1, Node other) { - SwitchEntry node2 = (SwitchEntry) other; - defaultAction(node1, node2); - visitLists(node1.getLabels(), node2.getLabels()); - visitLists(node1.getStatements(), node2.getStatements()); - } - - @Override - public void visit(SwitchStmt node1, Node other) { - SwitchStmt node2 = (SwitchStmt) other; - defaultAction(node1, node2); - visitLists(node1.getEntries(), node2.getEntries()); - node1.getSelector().accept(this, node2.getSelector()); - } - - @Override - public void visit(SynchronizedStmt node1, Node other) { - SynchronizedStmt node2 = (SynchronizedStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(ThisExpr node1, Node other) { - ThisExpr node2 = (ThisExpr) other; - defaultAction(node1, node2); - node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); - } - - @Override - public void visit(ThrowStmt node1, Node other) { - ThrowStmt node2 = (ThrowStmt) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(TryStmt node1, Node other) { - TryStmt node2 = (TryStmt) other; - defaultAction(node1, node2); - visitLists(node1.getCatchClauses(), node2.getCatchClauses()); - node1.getFinallyBlock().ifPresent(l -> l.accept(this, node2.getFinallyBlock().get())); - visitLists(node1.getResources(), node2.getResources()); - node1.getTryBlock().accept(this, node2.getTryBlock()); - } - - @Override - public void visit(LocalClassDeclarationStmt node1, Node other) { - LocalClassDeclarationStmt node2 = (LocalClassDeclarationStmt) other; - defaultAction(node1, node2); - node1.getClassDeclaration().accept(this, node2.getClassDeclaration()); - } - - @Override - public void visit(LocalRecordDeclarationStmt node1, Node other) { - LocalRecordDeclarationStmt node2 = (LocalRecordDeclarationStmt) other; - defaultAction(node1, node2); - node1.getRecordDeclaration().accept(this, node2.getRecordDeclaration()); - } - - @Override - public void visit(TypeParameter node1, Node other) { - TypeParameter node2 = (TypeParameter) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - // Since ajava files and its corresponding Java file may differ in whether they contain a - // type bound, only visit type bounds if they're present in both nodes. - if (node1.getTypeBound().isEmpty() == node2.getTypeBound().isEmpty()) { - visitLists(node1.getTypeBound(), node2.getTypeBound()); - } - } - - @Override - public void visit(UnaryExpr node1, Node other) { - UnaryExpr node2 = (UnaryExpr) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(UnknownType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(VariableDeclarationExpr node1, Node other) { - VariableDeclarationExpr node2 = (VariableDeclarationExpr) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - visitLists(node1.getVariables(), node2.getVariables()); - } - - @Override - public void visit(VariableDeclarator node1, Node other) { - VariableDeclarator node2 = (VariableDeclarator) other; - defaultAction(node1, node2); - node1.getInitializer().ifPresent(l -> l.accept(this, node2.getInitializer().get())); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(VoidType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(WhileStmt node1, Node other) { - WhileStmt node2 = (WhileStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getCondition().accept(this, node2.getCondition()); - } - - @Override - public void visit(WildcardType node1, Node other) { - WildcardType node2 = (WildcardType) other; - defaultAction(node1, node2); - node1.getExtendedType().ifPresent(l -> l.accept(this, node2.getExtendedType().get())); - node1.getSuperType().ifPresent(l -> l.accept(this, node2.getSuperType().get())); - } - - @Override - public void visit(LambdaExpr node1, Node other) { - LambdaExpr node2 = (LambdaExpr) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - visitLists(node1.getParameters(), node2.getParameters()); - } - - @Override - public void visit(MethodReferenceExpr node1, Node other) { - MethodReferenceExpr node2 = (MethodReferenceExpr) other; - defaultAction(node1, node2); - node1.getScope().accept(this, node2.getScope()); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(TypeExpr node1, Node other) { - TypeExpr node2 = (TypeExpr) other; - defaultAction(node1, node2); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(ImportDeclaration node1, Node other) { - ImportDeclaration node2 = (ImportDeclaration) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleDeclaration node1, Node other) { - ModuleDeclaration node2 = (ModuleDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getDirectives(), node2.getDirectives()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleRequiresDirective node1, Node other) { - ModuleRequiresDirective node2 = (ModuleRequiresDirective) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleExportsDirective node1, Node other) { - ModuleExportsDirective node2 = (ModuleExportsDirective) other; - defaultAction(node1, node2); - visitLists(node1.getModuleNames(), node2.getModuleNames()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleProvidesDirective node1, Node other) { - ModuleProvidesDirective node2 = (ModuleProvidesDirective) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - visitLists(node1.getWith(), node2.getWith()); - } - - @Override - public void visit(ModuleUsesDirective node1, Node other) { - ModuleUsesDirective node2 = (ModuleUsesDirective) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleOpensDirective node1, Node other) { - ModuleOpensDirective node2 = (ModuleOpensDirective) other; - defaultAction(node1, node2); - visitLists(node1.getModuleNames(), node2.getModuleNames()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(UnparsableStmt node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ReceiverParameter node1, Node other) { - ReceiverParameter node2 = (ReceiverParameter) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(VarType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(Modifier node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(SwitchExpr node1, Node other) { - SwitchExpr node2 = (SwitchExpr) other; - defaultAction(node1, node2); - visitLists(node1.getEntries(), node2.getEntries()); - node1.getSelector().accept(this, node2.getSelector()); - } - - @Override - public void visit(TextBlockLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(YieldStmt node1, Node other) { - YieldStmt node2 = (YieldStmt) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } + /** Create a DoubleJavaParserVisitor. */ + public DoubleJavaParserVisitor() {} + + /** + * Default action performed on all pairs of nodes from matching ASTs. + * + * @param node1 first node in pair + * @param node2 second node in pair + * @param the Node type of {@code node1} and {@code node2} + */ + public abstract void defaultAction(T node1, T node2); + + /** + * Given two lists with the same size where corresponding elements represent nodes with + * almost-identical ASTs as specified in this class's description, visits each pair of + * corresponding elements in order. + * + * @param list1 first list of nodes + * @param list2 second list of nodes, which has the same size as the first list + */ + private void visitLists(List list1, List list2) { + if (list1.size() != list2.size()) { + throw new Error( + String.format( + "%s.visitLists(%s [size %d], %s [size %d])", + this.getClass().getCanonicalName(), list1, list1.size(), list2, list2.size())); + } + for (int i = 0; i < list1.size(); i++) { + list1.get(i).accept(this, list2.get(i)); + } + } + + @Override + public void visit(AnnotationDeclaration node1, Node other) { + AnnotationDeclaration node2 = (AnnotationDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(AnnotationMemberDeclaration node1, Node other) { + AnnotationMemberDeclaration node2 = (AnnotationMemberDeclaration) other; + defaultAction(node1, node2); + node1.getDefaultValue().ifPresent(dv -> dv.accept(this, node2.getDefaultValue().get())); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(ArrayAccessExpr node1, Node other) { + ArrayAccessExpr node2 = (ArrayAccessExpr) other; + defaultAction(node1, node2); + node1.getIndex().accept(this, node2.getIndex()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ArrayCreationExpr node1, Node other) { + ArrayCreationExpr node2 = (ArrayCreationExpr) other; + defaultAction(node1, node2); + node1.getElementType().accept(this, node2.getElementType()); + node1.getInitializer().ifPresent(init -> init.accept(this, node2.getInitializer().get())); + visitLists(node1.getLevels(), node2.getLevels()); + } + + @Override + public void visit(ArrayInitializerExpr node1, Node other) { + ArrayInitializerExpr node2 = (ArrayInitializerExpr) other; + defaultAction(node1, node2); + visitLists(node1.getValues(), node2.getValues()); + } + + @Override + public void visit(AssertStmt node1, Node other) { + AssertStmt node2 = (AssertStmt) other; + defaultAction(node1, node2); + node1.getCheck().accept(this, node2.getCheck()); + node1.getMessage().ifPresent(m -> m.accept(this, node2.getMessage().get())); + } + + @Override + public void visit(AssignExpr node1, Node other) { + AssignExpr node2 = (AssignExpr) other; + defaultAction(node1, node2); + node1.getTarget().accept(this, node2.getTarget()); + node1.getValue().accept(this, node2.getValue()); + } + + @Override + public void visit(BinaryExpr node1, Node other) { + BinaryExpr node2 = (BinaryExpr) other; + defaultAction(node1, node2); + node1.getLeft().accept(this, node2.getLeft()); + node1.getRight().accept(this, node2.getRight()); + } + + @Override + public void visit(BlockComment node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(BlockStmt node1, Node other) { + BlockStmt node2 = (BlockStmt) other; + defaultAction(node1, node2); + visitLists(node1.getStatements(), node2.getStatements()); + } + + @Override + public void visit(BooleanLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(BreakStmt node1, Node other) { + BreakStmt node2 = (BreakStmt) other; + defaultAction(node1, node2); + node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); + } + + @Override + public void visit(CastExpr node1, Node other) { + CastExpr node2 = (CastExpr) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(CatchClause node1, Node other) { + CatchClause node2 = (CatchClause) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getParameter().accept(this, node2.getParameter()); + } + + @Override + public void visit(CharLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ClassExpr node1, Node other) { + ClassExpr node2 = (ClassExpr) other; + defaultAction(node1, node2); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(ClassOrInterfaceDeclaration node1, Node other) { + ClassOrInterfaceDeclaration node2 = (ClassOrInterfaceDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getExtendedTypes(), node2.getExtendedTypes()); + visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ClassOrInterfaceType node1, Node other) { + ClassOrInterfaceType node2 = (ClassOrInterfaceType) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getScope().ifPresent(s -> s.accept(this, node2.getScope().get())); + node1.getTypeArguments().ifPresent(targs -> visitLists(targs, node2.getTypeArguments().get())); + } + + @Override + public void visit(CompilationUnit node1, Node other) { + CompilationUnit node2 = (CompilationUnit) other; + defaultAction(node1, node2); + node1.getModule().ifPresent(m -> m.accept(this, node2.getModule().get())); + node1 + .getPackageDeclaration() + .ifPresent(pd -> pd.accept(this, node2.getPackageDeclaration().get())); + visitLists(node1.getTypes(), node2.getTypes()); + } + + @Override + public void visit(ConditionalExpr node1, Node other) { + ConditionalExpr node2 = (ConditionalExpr) other; + defaultAction(node1, node2); + node1.getCondition().accept(this, node2.getCondition()); + node1.getElseExpr().accept(this, node2.getElseExpr()); + node1.getThenExpr().accept(this, node2.getThenExpr()); + } + + @Override + public void visit(ConstructorDeclaration node1, Node other) { + ConstructorDeclaration node2 = (ConstructorDeclaration) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + visitLists(node1.getParameters(), node2.getParameters()); + if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { + node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); + } + + visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + } + + @Override + public void visit(CompactConstructorDeclaration node1, Node other) { + CompactConstructorDeclaration node2 = (CompactConstructorDeclaration) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + + visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + } + + @Override + public void visit(ContinueStmt node1, Node other) { + ContinueStmt node2 = (ContinueStmt) other; + defaultAction(node1, node2); + node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); + } + + @Override + public void visit(DoStmt node1, Node other) { + DoStmt node2 = (DoStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getCondition().accept(this, node2.getCondition()); + } + + @Override + public void visit(DoubleLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(EmptyStmt node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(EnclosedExpr node1, Node other) { + EnclosedExpr node2 = (EnclosedExpr) other; + defaultAction(node1, node2); + node1.getInner().accept(this, node2.getInner()); + } + + @Override + public void visit(EnumConstantDeclaration node1, Node other) { + EnumConstantDeclaration node2 = (EnumConstantDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getArguments(), node2.getArguments()); + visitLists(node1.getClassBody(), node2.getClassBody()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(EnumDeclaration node1, Node other) { + EnumDeclaration node2 = (EnumDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getEntries(), node2.getEntries()); + visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ExplicitConstructorInvocationStmt node1, Node other) { + ExplicitConstructorInvocationStmt node2 = (ExplicitConstructorInvocationStmt) other; + defaultAction(node1, node2); + visitLists(node1.getArguments(), node2.getArguments()); + node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(ExpressionStmt node1, Node other) { + ExpressionStmt node2 = (ExpressionStmt) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(FieldAccessExpr node1, Node other) { + FieldAccessExpr node2 = (FieldAccessExpr) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getScope().accept(this, node2.getScope()); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(FieldDeclaration node1, Node other) { + FieldDeclaration node2 = (FieldDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + visitLists(node1.getVariables(), node2.getVariables()); + } + + @Override + public void visit(ForEachStmt node1, Node other) { + ForEachStmt node2 = (ForEachStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getIterable().accept(this, node2.getIterable()); + node1.getVariable().accept(this, node2.getVariable()); + } + + @Override + public void visit(ForStmt node1, Node other) { + ForStmt node2 = (ForStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getCompare().ifPresent(l -> l.accept(this, node2.getCompare().get())); + visitLists(node1.getInitialization(), node2.getInitialization()); + visitLists(node1.getUpdate(), node2.getUpdate()); + } + + @Override + public void visit(IfStmt node1, Node other) { + IfStmt node2 = (IfStmt) other; + defaultAction(node1, node2); + node1.getCondition().accept(this, node2.getCondition()); + node1.getElseStmt().ifPresent(l -> l.accept(this, node2.getElseStmt().get())); + node1.getThenStmt().accept(this, node2.getThenStmt()); + } + + @Override + public void visit(InitializerDeclaration node1, Node other) { + InitializerDeclaration node2 = (InitializerDeclaration) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + } + + @Override + public void visit(InstanceOfExpr node1, Node other) { + InstanceOfExpr node2 = (InstanceOfExpr) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(IntegerLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(JavadocComment node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(LabeledStmt node1, Node other) { + LabeledStmt node2 = (LabeledStmt) other; + defaultAction(node1, node2); + node1.getLabel().accept(this, node2.getLabel()); + node1.getStatement().accept(this, node2.getStatement()); + } + + @Override + public void visit(LineComment node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(LongLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(MarkerAnnotationExpr node1, Node other) { + MarkerAnnotationExpr node2 = (MarkerAnnotationExpr) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(MemberValuePair node1, Node other) { + MemberValuePair node2 = (MemberValuePair) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getValue().accept(this, node2.getName()); + } + + @Override + public void visit(MethodCallExpr node1, Node other) { + MethodCallExpr node2 = (MethodCallExpr) other; + defaultAction(node1, node2); + visitLists(node1.getArguments(), node2.getArguments()); + node1.getName().accept(this, node2.getName()); + node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(MethodDeclaration node1, Node other) { + MethodDeclaration node2 = (MethodDeclaration) other; + defaultAction(node1, node2); + node1.getBody().ifPresent(l -> l.accept(this, node2.getBody().get())); + node1.getType().accept(this, node2.getType()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + visitLists(node1.getParameters(), node2.getParameters()); + if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { + node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); + } + + visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + } + + @Override + public void visit(NameExpr node1, Node other) { + NameExpr node2 = (NameExpr) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(NormalAnnotationExpr node1, Node other) { + NormalAnnotationExpr node2 = (NormalAnnotationExpr) other; + defaultAction(node1, node2); + visitLists(node1.getPairs(), node2.getPairs()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(NullLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ObjectCreationExpr node1, Node other) { + ObjectCreationExpr node2 = (ObjectCreationExpr) other; + defaultAction(node1, node2); + node1 + .getAnonymousClassBody() + .ifPresent(l -> visitLists(l, node2.getAnonymousClassBody().get())); + visitLists(node1.getArguments(), node2.getArguments()); + node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); + node1.getType().accept(this, node2.getType()); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(PackageDeclaration node1, Node other) { + PackageDeclaration node2 = (PackageDeclaration) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(Parameter node1, Node other) { + Parameter node2 = (Parameter) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(PrimitiveType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(Name node1, Node other) { + Name node2 = (Name) other; + defaultAction(node1, node2); + node1.getQualifier().ifPresent(l -> l.accept(this, node2.getQualifier().get())); + } + + @Override + public void visit(SimpleName node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ArrayType node1, Node other) { + ArrayType node2 = (ArrayType) other; + defaultAction(node1, node2); + node1.getComponentType().accept(this, node2.getComponentType()); + } + + @Override + public void visit(ArrayCreationLevel node1, Node other) { + ArrayCreationLevel node2 = (ArrayCreationLevel) other; + defaultAction(node1, node2); + node1.getDimension().ifPresent(l -> l.accept(this, node2.getDimension().get())); + } + + @Override + public void visit(IntersectionType node1, Node other) { + IntersectionType node2 = (IntersectionType) other; + defaultAction(node1, node2); + visitLists(node1.getElements(), node2.getElements()); + } + + @Override + public void visit(UnionType node1, Node other) { + UnionType node2 = (UnionType) other; + defaultAction(node1, node2); + visitLists(node1.getElements(), node2.getElements()); + } + + @Override + public void visit(RecordDeclaration node1, Node other) { + RecordDeclaration node2 = (RecordDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + visitLists(node1.getParameters(), node2.getParameters()); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { + node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); + } + } + + @Override + public void visit(ReturnStmt node1, Node other) { + ReturnStmt node2 = (ReturnStmt) other; + defaultAction(node1, node2); + node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); + } + + @Override + public void visit(SingleMemberAnnotationExpr node1, Node other) { + SingleMemberAnnotationExpr node2 = (SingleMemberAnnotationExpr) other; + defaultAction(node1, node2); + node1.getMemberValue().accept(this, node2.getMemberValue()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(StringLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(SuperExpr node1, Node other) { + SuperExpr node2 = (SuperExpr) other; + defaultAction(node1, node2); + node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); + } + + @Override + public void visit(SwitchEntry node1, Node other) { + SwitchEntry node2 = (SwitchEntry) other; + defaultAction(node1, node2); + visitLists(node1.getLabels(), node2.getLabels()); + visitLists(node1.getStatements(), node2.getStatements()); + } + + @Override + public void visit(SwitchStmt node1, Node other) { + SwitchStmt node2 = (SwitchStmt) other; + defaultAction(node1, node2); + visitLists(node1.getEntries(), node2.getEntries()); + node1.getSelector().accept(this, node2.getSelector()); + } + + @Override + public void visit(SynchronizedStmt node1, Node other) { + SynchronizedStmt node2 = (SynchronizedStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(ThisExpr node1, Node other) { + ThisExpr node2 = (ThisExpr) other; + defaultAction(node1, node2); + node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); + } + + @Override + public void visit(ThrowStmt node1, Node other) { + ThrowStmt node2 = (ThrowStmt) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(TryStmt node1, Node other) { + TryStmt node2 = (TryStmt) other; + defaultAction(node1, node2); + visitLists(node1.getCatchClauses(), node2.getCatchClauses()); + node1.getFinallyBlock().ifPresent(l -> l.accept(this, node2.getFinallyBlock().get())); + visitLists(node1.getResources(), node2.getResources()); + node1.getTryBlock().accept(this, node2.getTryBlock()); + } + + @Override + public void visit(LocalClassDeclarationStmt node1, Node other) { + LocalClassDeclarationStmt node2 = (LocalClassDeclarationStmt) other; + defaultAction(node1, node2); + node1.getClassDeclaration().accept(this, node2.getClassDeclaration()); + } + + @Override + public void visit(LocalRecordDeclarationStmt node1, Node other) { + LocalRecordDeclarationStmt node2 = (LocalRecordDeclarationStmt) other; + defaultAction(node1, node2); + node1.getRecordDeclaration().accept(this, node2.getRecordDeclaration()); + } + + @Override + public void visit(TypeParameter node1, Node other) { + TypeParameter node2 = (TypeParameter) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + // Since ajava files and its corresponding Java file may differ in whether they contain a + // type bound, only visit type bounds if they're present in both nodes. + if (node1.getTypeBound().isEmpty() == node2.getTypeBound().isEmpty()) { + visitLists(node1.getTypeBound(), node2.getTypeBound()); + } + } + + @Override + public void visit(UnaryExpr node1, Node other) { + UnaryExpr node2 = (UnaryExpr) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(UnknownType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(VariableDeclarationExpr node1, Node other) { + VariableDeclarationExpr node2 = (VariableDeclarationExpr) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + visitLists(node1.getVariables(), node2.getVariables()); + } + + @Override + public void visit(VariableDeclarator node1, Node other) { + VariableDeclarator node2 = (VariableDeclarator) other; + defaultAction(node1, node2); + node1.getInitializer().ifPresent(l -> l.accept(this, node2.getInitializer().get())); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(VoidType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(WhileStmt node1, Node other) { + WhileStmt node2 = (WhileStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getCondition().accept(this, node2.getCondition()); + } + + @Override + public void visit(WildcardType node1, Node other) { + WildcardType node2 = (WildcardType) other; + defaultAction(node1, node2); + node1.getExtendedType().ifPresent(l -> l.accept(this, node2.getExtendedType().get())); + node1.getSuperType().ifPresent(l -> l.accept(this, node2.getSuperType().get())); + } + + @Override + public void visit(LambdaExpr node1, Node other) { + LambdaExpr node2 = (LambdaExpr) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + visitLists(node1.getParameters(), node2.getParameters()); + } + + @Override + public void visit(MethodReferenceExpr node1, Node other) { + MethodReferenceExpr node2 = (MethodReferenceExpr) other; + defaultAction(node1, node2); + node1.getScope().accept(this, node2.getScope()); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(TypeExpr node1, Node other) { + TypeExpr node2 = (TypeExpr) other; + defaultAction(node1, node2); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(ImportDeclaration node1, Node other) { + ImportDeclaration node2 = (ImportDeclaration) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleDeclaration node1, Node other) { + ModuleDeclaration node2 = (ModuleDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getDirectives(), node2.getDirectives()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleRequiresDirective node1, Node other) { + ModuleRequiresDirective node2 = (ModuleRequiresDirective) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleExportsDirective node1, Node other) { + ModuleExportsDirective node2 = (ModuleExportsDirective) other; + defaultAction(node1, node2); + visitLists(node1.getModuleNames(), node2.getModuleNames()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleProvidesDirective node1, Node other) { + ModuleProvidesDirective node2 = (ModuleProvidesDirective) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + visitLists(node1.getWith(), node2.getWith()); + } + + @Override + public void visit(ModuleUsesDirective node1, Node other) { + ModuleUsesDirective node2 = (ModuleUsesDirective) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleOpensDirective node1, Node other) { + ModuleOpensDirective node2 = (ModuleOpensDirective) other; + defaultAction(node1, node2); + visitLists(node1.getModuleNames(), node2.getModuleNames()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(UnparsableStmt node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ReceiverParameter node1, Node other) { + ReceiverParameter node2 = (ReceiverParameter) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(VarType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(Modifier node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(SwitchExpr node1, Node other) { + SwitchExpr node2 = (SwitchExpr) other; + defaultAction(node1, node2); + visitLists(node1.getEntries(), node2.getEntries()); + node1.getSelector().accept(this, node2.getSelector()); + } + + @Override + public void visit(TextBlockLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(YieldStmt node1, Node other) { + YieldStmt node2 = (YieldStmt) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java index 6c3ada58877..68216b7302b 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java @@ -23,14 +23,12 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.tree.WhileLoopTree; - +import java.util.HashSet; +import java.util.Set; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; -import java.util.HashSet; -import java.util.Set; - /** * After this visitor visits a tree, {@link #getTrees} returns all the trees that should match with * some JavaParser node. Some trees shouldn't be matched with a JavaParser node because there isn't @@ -41,363 +39,363 @@ * so the trees this class stores can be used to test if the entirety of the javac tree was visited. */ public class ExpectedTreesVisitor extends TreeScannerWithDefaults { - /** The set of trees that should be matched to a JavaParser node when visiting both. */ - private Set trees = new HashSet<>(); - - /** - * Returns the visited trees that should match to some JavaParser node. - * - * @return the visited trees that should match to some JavaParser node - */ - public Set getTrees() { - return trees; - } - - /** - * Records that {@code tree} should have a corresponding JavaParser node. - * - * @param tree the tree to record - */ - @Override - public void defaultAction(Tree tree) { - trees.add(tree); - } + /** The set of trees that should be matched to a JavaParser node when visiting both. */ + private Set trees = new HashSet<>(); + + /** + * Returns the visited trees that should match to some JavaParser node. + * + * @return the visited trees that should match to some JavaParser node + */ + public Set getTrees() { + return trees; + } + + /** + * Records that {@code tree} should have a corresponding JavaParser node. + * + * @param tree the tree to record + */ + @Override + public void defaultAction(Tree tree) { + trees.add(tree); + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + // Skip annotations because ajava files are not required to have the same annotations as + // their corresponding Java files. + return null; + } + + @Override + public Void visitBindingPattern17(Tree tree, Void p) { + super.visitBindingPattern17(tree, p); + // JavaParser doesn't have a node for the VariableTree. + trees.remove(BindingPatternUtils.getVariable(tree)); + return null; + } + + @Override + public Void visitClass(ClassTree tree, Void p) { + defaultAction(tree); + scan(tree.getModifiers(), p); + scan(tree.getTypeParameters(), p); + scan(tree.getExtendsClause(), p); + scan(tree.getImplementsClause(), p); + if (tree.getKind() == Tree.Kind.ENUM) { + // Enum constants expand to a VariableTree like + // public static final MY_ENUM_CONSTANT = new MyEnum(args ...) + // The constructor invocation in the initializer has no corresponding JavaParser node, + // so this removes those invocations. This doesn't remove any trees that should be + // matched to a JavaParser node, because it's illegal to explicitly construct an + // instance of an enum. + for (Tree member : tree.getMembers()) { + member.accept(this, p); + if (member.getKind() != Tree.Kind.VARIABLE) { + continue; + } - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - // Skip annotations because ajava files are not required to have the same annotations as - // their corresponding Java files. - return null; - } + VariableTree variable = (VariableTree) member; + ExpressionTree initializer = variable.getInitializer(); + if (initializer == null || initializer.getKind() != Tree.Kind.NEW_CLASS) { + continue; + } - @Override - public Void visitBindingPattern17(Tree tree, Void p) { - super.visitBindingPattern17(tree, p); - // JavaParser doesn't have a node for the VariableTree. - trees.remove(BindingPatternUtils.getVariable(tree)); - return null; - } + NewClassTree constructor = (NewClassTree) initializer; + if (constructor.getIdentifier().getKind() != Tree.Kind.IDENTIFIER) { + continue; + } - @Override - public Void visitClass(ClassTree tree, Void p) { - defaultAction(tree); - scan(tree.getModifiers(), p); - scan(tree.getTypeParameters(), p); - scan(tree.getExtendsClause(), p); - scan(tree.getImplementsClause(), p); - if (tree.getKind() == Tree.Kind.ENUM) { - // Enum constants expand to a VariableTree like - // public static final MY_ENUM_CONSTANT = new MyEnum(args ...) - // The constructor invocation in the initializer has no corresponding JavaParser node, - // so this removes those invocations. This doesn't remove any trees that should be - // matched to a JavaParser node, because it's illegal to explicitly construct an - // instance of an enum. - for (Tree member : tree.getMembers()) { - member.accept(this, p); - if (member.getKind() != Tree.Kind.VARIABLE) { - continue; - } - - VariableTree variable = (VariableTree) member; - ExpressionTree initializer = variable.getInitializer(); - if (initializer == null || initializer.getKind() != Tree.Kind.NEW_CLASS) { - continue; - } - - NewClassTree constructor = (NewClassTree) initializer; - if (constructor.getIdentifier().getKind() != Tree.Kind.IDENTIFIER) { - continue; - } - - IdentifierTree name = (IdentifierTree) constructor.getIdentifier(); - if (name.getName().contentEquals(tree.getSimpleName())) { - trees.remove(variable.getType()); - trees.remove(constructor); - trees.remove(constructor.getIdentifier()); - } - } - } else if (TreeUtils.isRecordTree(tree)) { - // A record like: - // record MyRec(String myField) {} - // will be expanded by javac to: - // class MyRec { - // MyRec(String myField) { - // super(); - // } - // private final String myField; - // } - // So the constructor and the field declarations have no matching trees in the - // JavaParser AST node, and we must remove those trees (and their subtrees) from the - // `trees` field. - TreeScannerWithDefaults removeAllVisitor = - new TreeScannerWithDefaults() { - @Override - public void defaultAction(Tree tree) { - trees.remove(tree); - } - }; - for (Tree member : tree.getMembers()) { - scan(member, p); - if (TreeUtils.isAutoGeneratedRecordMember(member)) { - member.accept(removeAllVisitor, null); - } else { - // If the user declares a compact canonical constructor, javac will - // automatically fill in the parameters. - // These trees also don't have a match: - if (member.getKind() == Tree.Kind.METHOD) { - MethodTree methodTree = (MethodTree) member; - if (TreeUtils.isCompactCanonicalRecordConstructor(methodTree)) { - for (VariableTree canonicalParameter : methodTree.getParameters()) { - canonicalParameter.accept(removeAllVisitor, null); - } - } - } - } + IdentifierTree name = (IdentifierTree) constructor.getIdentifier(); + if (name.getName().contentEquals(tree.getSimpleName())) { + trees.remove(variable.getType()); + trees.remove(constructor); + trees.remove(constructor.getIdentifier()); + } + } + } else if (TreeUtils.isRecordTree(tree)) { + // A record like: + // record MyRec(String myField) {} + // will be expanded by javac to: + // class MyRec { + // MyRec(String myField) { + // super(); + // } + // private final String myField; + // } + // So the constructor and the field declarations have no matching trees in the + // JavaParser AST node, and we must remove those trees (and their subtrees) from the + // `trees` field. + TreeScannerWithDefaults removeAllVisitor = + new TreeScannerWithDefaults() { + @Override + public void defaultAction(Tree tree) { + trees.remove(tree); } + }; + for (Tree member : tree.getMembers()) { + scan(member, p); + if (TreeUtils.isAutoGeneratedRecordMember(member)) { + member.accept(removeAllVisitor, null); } else { - scan(tree.getMembers(), p); + // If the user declares a compact canonical constructor, javac will + // automatically fill in the parameters. + // These trees also don't have a match: + if (member.getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) member; + if (TreeUtils.isCompactCanonicalRecordConstructor(methodTree)) { + for (VariableTree canonicalParameter : methodTree.getParameters()) { + canonicalParameter.accept(removeAllVisitor, null); + } + } + } } - - return null; + } + } else { + scan(tree.getMembers(), p); } - @Override - public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { - // Javac inserts calls to super() at the start of constructors with no this or super call. - // These don't have matching JavaParser nodes. - if (JointJavacJavaParserVisitor.isDefaultSuperConstructorCall(tree)) { - return null; - } - - // Whereas synthetic constructors should be skipped, regular super() and this() should still - // be added. JavaParser has no expression statement surrounding these, so remove the - // expression statement itself. - Void result = super.visitExpressionStatement(tree, p); - if (tree.getExpression().getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree invocation = (MethodInvocationTree) tree.getExpression(); - if (invocation.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { - IdentifierTree identifier = (IdentifierTree) invocation.getMethodSelect(); - if (identifier.getName().contentEquals("this") - || identifier.getName().contentEquals("super")) { - trees.remove(tree); - trees.remove(identifier); - } - } - } + return null; + } - return result; + @Override + public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { + // Javac inserts calls to super() at the start of constructors with no this or super call. + // These don't have matching JavaParser nodes. + if (JointJavacJavaParserVisitor.isDefaultSuperConstructorCall(tree)) { + return null; } - @Override - public Void visitForLoop(ForLoopTree tree, Void p) { - // Javac nests a for loop's updates in expression statements but JavaParser stores the - // statements directly, so remove the expression statements. - Void result = super.visitForLoop(tree, p); - for (StatementTree initializer : tree.getInitializer()) { - trees.remove(initializer); + // Whereas synthetic constructors should be skipped, regular super() and this() should still + // be added. JavaParser has no expression statement surrounding these, so remove the + // expression statement itself. + Void result = super.visitExpressionStatement(tree, p); + if (tree.getExpression().getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree invocation = (MethodInvocationTree) tree.getExpression(); + if (invocation.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { + IdentifierTree identifier = (IdentifierTree) invocation.getMethodSelect(); + if (identifier.getName().contentEquals("this") + || identifier.getName().contentEquals("super")) { + trees.remove(tree); + trees.remove(identifier); } + } + } - for (ExpressionStatementTree update : tree.getUpdate()) { - trees.remove(update); - } + return result; + } - return result; + @Override + public Void visitForLoop(ForLoopTree tree, Void p) { + // Javac nests a for loop's updates in expression statements but JavaParser stores the + // statements directly, so remove the expression statements. + Void result = super.visitForLoop(tree, p); + for (StatementTree initializer : tree.getInitializer()) { + trees.remove(initializer); } - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - super.visitSwitch(tree, p); - // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getExpression()); - return null; + for (ExpressionStatementTree update : tree.getUpdate()) { + trees.remove(update); } - @Override - public Void visitSwitchExpression17(Tree tree, Void p) { - super.visitSwitchExpression17(tree, p); - // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. - trees.remove(SwitchExpressionUtils.getExpression(tree)); + return result; + } + + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + super.visitSwitch(tree, p); + // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getExpression()); + return null; + } + + @Override + public Void visitSwitchExpression17(Tree tree, Void p) { + super.visitSwitchExpression17(tree, p); + // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. + trees.remove(SwitchExpressionUtils.getExpression(tree)); + return null; + } + + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + super.visitSynchronized(tree, p); + // javac surrounds synchronized expressions in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getExpression()); + return null; + } + + @Override + public Void visitIf(IfTree tree, Void p) { + // In an if statement, javac stores the condition as a parenthesized expression, which has + // no corresponding JavaParserNode, so remove the parenthesized expression, but not its + // child. + Void result = super.visitIf(tree, p); + trees.remove(tree.getCondition()); + return result; + } + + @Override + public Void visitImport(ImportTree tree, Void p) { + // Javac stores an import like a.* as a member select, but JavaParser just stores "a", so + // don't add the member select in that case. + if (tree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree memberSelect = (MemberSelectTree) tree.getQualifiedIdentifier(); + if (memberSelect.getIdentifier().contentEquals("*")) { + memberSelect.getExpression().accept(this, p); return null; + } } - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - super.visitSynchronized(tree, p); - // javac surrounds synchronized expressions in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getExpression()); - return null; - } + return super.visitImport(tree, p); + } - @Override - public Void visitIf(IfTree tree, Void p) { - // In an if statement, javac stores the condition as a parenthesized expression, which has - // no corresponding JavaParserNode, so remove the parenthesized expression, but not its - // child. - Void result = super.visitIf(tree, p); - trees.remove(tree.getCondition()); - return result; + @Override + public Void visitMethod(MethodTree tree, Void p) { + // Synthetic default constructors don't have matching JavaParser nodes. Conservatively skip + // nullary (no-argument) constructor calls, even if they may not be synthetic. + if (JointJavacJavaParserVisitor.isNoArgumentConstructor(tree)) { + return null; } - @Override - public Void visitImport(ImportTree tree, Void p) { - // Javac stores an import like a.* as a member select, but JavaParser just stores "a", so - // don't add the member select in that case. - if (tree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree memberSelect = (MemberSelectTree) tree.getQualifiedIdentifier(); - if (memberSelect.getIdentifier().contentEquals("*")) { - memberSelect.getExpression().accept(this, p); - return null; - } + Void result = super.visitMethod(tree, p); + // A varargs parameter like String... is converted to String[], where the array type doesn't + // have a corresponding JavaParser AST node. Conservatively skip the array type (but not the + // component type) if it's the last argument. + if (!tree.getParameters().isEmpty()) { + VariableTree last = tree.getParameters().get(tree.getParameters().size() - 1); + if (last.getType().getKind() == Tree.Kind.ARRAY_TYPE) { + trees.remove(last.getType()); + } + + if (last.getType().getKind() == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) last.getType(); + if (annotatedType.getUnderlyingType().getKind() == Tree.Kind.ARRAY_TYPE) { + trees.remove(annotatedType); + trees.remove(annotatedType.getUnderlyingType()); } - - return super.visitImport(tree, p); + } } - @Override - public Void visitMethod(MethodTree tree, Void p) { - // Synthetic default constructors don't have matching JavaParser nodes. Conservatively skip - // nullary (no-argument) constructor calls, even if they may not be synthetic. - if (JointJavacJavaParserVisitor.isNoArgumentConstructor(tree)) { - return null; - } + return result; + } - Void result = super.visitMethod(tree, p); - // A varargs parameter like String... is converted to String[], where the array type doesn't - // have a corresponding JavaParser AST node. Conservatively skip the array type (but not the - // component type) if it's the last argument. - if (!tree.getParameters().isEmpty()) { - VariableTree last = tree.getParameters().get(tree.getParameters().size() - 1); - if (last.getType().getKind() == Tree.Kind.ARRAY_TYPE) { - trees.remove(last.getType()); - } + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + Void result = super.visitMethodInvocation(tree, p); + // In a method invocation like myObject.myMethod(), the method invocation stores + // myObject.myMethod as its own MemberSelectTree which has no corresponding JavaParserNode. + if (tree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { + trees.remove(tree.getMethodSelect()); + } - if (last.getType().getKind() == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) last.getType(); - if (annotatedType.getUnderlyingType().getKind() == Tree.Kind.ARRAY_TYPE) { - trees.remove(annotatedType); - trees.remove(annotatedType.getUnderlyingType()); - } - } - } + return result; + } - return result; - } + @Override + public Void visitModifiers(ModifiersTree tree, Void p) { + // Don't add ModifierTrees or children because they have no corresponding JavaParser node. + return null; + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - Void result = super.visitMethodInvocation(tree, p); - // In a method invocation like myObject.myMethod(), the method invocation stores - // myObject.myMethod as its own MemberSelectTree which has no corresponding JavaParserNode. - if (tree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { - trees.remove(tree.getMethodSelect()); - } + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + // Skip array initialization because it's not implemented yet. + return null; + } - return result; - } + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + defaultAction(tree); - @Override - public Void visitModifiers(ModifiersTree tree, Void p) { - // Don't add ModifierTrees or children because they have no corresponding JavaParser node. - return null; + if (tree.getEnclosingExpression() != null) { + tree.getEnclosingExpression().accept(this, p); } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - // Skip array initialization because it's not implemented yet. - return null; + tree.getIdentifier().accept(this, p); + for (Tree typeArgument : tree.getTypeArguments()) { + typeArgument.accept(this, p); } - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - defaultAction(tree); - - if (tree.getEnclosingExpression() != null) { - tree.getEnclosingExpression().accept(this, p); - } - - tree.getIdentifier().accept(this, p); - for (Tree typeArgument : tree.getTypeArguments()) { - typeArgument.accept(this, p); - } - - for (Tree arg : tree.getTypeArguments()) { - arg.accept(this, p); - } - - if (tree.getClassBody() == null) { - return null; - } - - // Anonymous class bodies require special handling. There isn't a corresponding JavaParser - // node, and synthetic constructors must be skipped. - ClassTree body = tree.getClassBody(); - scan(body.getModifiers(), p); - scan(body.getTypeParameters(), p); - scan(body.getImplementsClause(), p); - for (Tree member : body.getMembers()) { - // Constructors cannot be declared in an anonymous class, so don't add them. - if (member.getKind() == Tree.Kind.METHOD) { - MethodTree methodTree = (MethodTree) member; - if (methodTree.getName().contentEquals("")) { - continue; - } - } - - member.accept(this, p); - } + for (Tree arg : tree.getTypeArguments()) { + arg.accept(this, p); + } - return null; + if (tree.getClassBody() == null) { + return null; } - @Override - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - for (VariableTree parameter : tree.getParameters()) { - // Programmers may omit parameter types for lambda expressions. When not specified, - // javac infers them but JavaParser uses UnknownType. Conservatively, don't add - // parameter types for lambda expressions. - scan(parameter.getModifiers(), p); - scan(parameter.getNameExpression(), p); - assert parameter.getInitializer() == null; + // Anonymous class bodies require special handling. There isn't a corresponding JavaParser + // node, and synthetic constructors must be skipped. + ClassTree body = tree.getClassBody(); + scan(body.getModifiers(), p); + scan(body.getTypeParameters(), p); + scan(body.getImplementsClause(), p); + for (Tree member : body.getMembers()) { + // Constructors cannot be declared in an anonymous class, so don't add them. + if (member.getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) member; + if (methodTree.getName().contentEquals("")) { + continue; } + } - scan(tree.getBody(), p); - return null; + member.accept(this, p); } - @Override - public Void visitWhileLoop(WhileLoopTree tree, Void p) { - super.visitWhileLoop(tree, p); - // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getCondition()); - return null; + return null; + } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + for (VariableTree parameter : tree.getParameters()) { + // Programmers may omit parameter types for lambda expressions. When not specified, + // javac infers them but JavaParser uses UnknownType. Conservatively, don't add + // parameter types for lambda expressions. + scan(parameter.getModifiers(), p); + scan(parameter.getNameExpression(), p); + assert parameter.getInitializer() == null; } - @Override - public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - super.visitDoWhileLoop(tree, p); - // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getCondition()); - return null; + scan(tree.getBody(), p); + return null; + } + + @Override + public Void visitWhileLoop(WhileLoopTree tree, Void p) { + super.visitWhileLoop(tree, p); + // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getCondition()); + return null; + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + super.visitDoWhileLoop(tree, p); + // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getCondition()); + return null; + } + + @Override + public Void visitVariable(VariableTree tree, Void p) { + // Javac expands the keyword "var" in a variable declaration to its inferred type. + // JavaParser has a special "var" construct, so they won't match. If a javac type was + // generated this way, then it won't have a position in source code so in that case we don't + // add it. + if (TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { + return null; } - @Override - public Void visitVariable(VariableTree tree, Void p) { - // Javac expands the keyword "var" in a variable declaration to its inferred type. - // JavaParser has a special "var" construct, so they won't match. If a javac type was - // generated this way, then it won't have a position in source code so in that case we don't - // add it. - if (TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { - return null; - } - - return super.visitVariable(tree, p); - } + return super.visitVariable(tree, p); + } - @Override - public Void visitYield17(Tree tree, Void p) { - // JavaParser does not parse yields correctly: - // https://github.com/javaparser/javaparser/issues/3364 - // So skip yields. - return null; - } + @Override + public Void visitYield17(Tree tree, Void p) { + // JavaParser does not parse yields correctly: + // https://github.com/javaparser/javaparser/issues/3364 + // So skip yields. + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java index 32758b086a1..08272f6c5b7 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java @@ -16,15 +16,6 @@ import com.github.javaparser.printer.DefaultPrettyPrinter; import com.github.javaparser.utils.Pair; import com.sun.source.util.JavacTask; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.framework.stub.AnnotationFileParser; -import org.checkerframework.framework.util.JavaParserUtil; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.FilesPlume; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -46,7 +37,6 @@ import java.util.Map; import java.util.Set; import java.util.StringJoiner; - import javax.lang.model.element.ElementKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; @@ -57,576 +47,576 @@ import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.framework.stub.AnnotationFileParser; +import org.checkerframework.framework.util.JavaParserUtil; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.FilesPlume; /** This program inserts annotations from an ajava file into a Java file. See {@link #main}. */ public class InsertAjavaAnnotations { - /** Element utilities. */ - private final Elements elements; + /** Element utilities. */ + private final Elements elements; + + /** + * Constructs an {@code InsertAjavaAnnotations} using the given {@code Elements} instance. + * + * @param elements an instance of {@code Elements} + */ + public InsertAjavaAnnotations(Elements elements) { + this.elements = elements; + } + + /** + * Gets an instance of {@code Elements} from the current Java compiler. + * + * @return the Element utilities + */ + private static Elements createElements() { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + System.err.println("Could not get compiler instance"); + System.exit(1); + } + + DiagnosticCollector diagnostics = new DiagnosticCollector(); + try (JavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { + if (fileManager == null) { + System.err.println("Could not get file manager"); + System.exit(1); + } + + CompilationTask cTask = + compiler.getTask( + null, + fileManager, + diagnostics, + Collections.emptyList(), + null, + Collections.emptyList()); + if (!(cTask instanceof JavacTask)) { + System.err.println("Could not get a valid JavacTask: " + cTask.getClass()); + System.exit(1); + } + + return ((JavacTask) cTask).getElements(); + } catch (IOException e) { + throw new Error(e); + } + } + + /** Represents some text to be inserted at a file and its location. */ + private static class Insertion { + /** Offset of the insertion in the file, measured in characters from the beginning. */ + public final int position; + + /** The contents of the insertion. */ + public final String contents; + + /** Whether the insertion should be on its own separate line. */ + public final boolean ownLine; /** - * Constructs an {@code InsertAjavaAnnotations} using the given {@code Elements} instance. + * Constructs an insertion with the given position and contents. * - * @param elements an instance of {@code Elements} + * @param position offset of the insertion in the file + * @param contents contents of the insertion */ - public InsertAjavaAnnotations(Elements elements) { - this.elements = elements; + public Insertion(int position, String contents) { + this(position, contents, false); } /** - * Gets an instance of {@code Elements} from the current Java compiler. + * Constructs an insertion with the given position and contents. * - * @return the Element utilities + * @param position offset of the insertion in the file + * @param contents contents of the insertion + * @param ownLine true if this insertion should appear on its own separate line (doesn't affect + * the contents of the insertion) */ - private static Elements createElements() { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - if (compiler == null) { - System.err.println("Could not get compiler instance"); - System.exit(1); - } + public Insertion(int position, String contents, boolean ownLine) { + this.position = position; + this.contents = contents; + this.ownLine = ownLine; + } - DiagnosticCollector diagnostics = new DiagnosticCollector(); - try (JavaFileManager fileManager = - compiler.getStandardFileManager(diagnostics, null, null)) { - if (fileManager == null) { - System.err.println("Could not get file manager"); - System.exit(1); - } + @Override + public String toString() { + return "Insertion [contents=" + contents + ", position=" + position + "]"; + } + } + + /** + * Given two JavaParser ASTs representing the same Java file but with differing annotations, + * stores a list of {@link Insertion}s. The {@link Insertion}s represent how to textually modify + * the file of the second AST to insert all annotations in the first AST into the second AST, but + * this class doesn't modify the second AST itself. To use this class, call {@link + * #visit(CompilationUnit, Node)} on a pair of ASTs and then use the contents of {@link + * #insertions}. + */ + private class BuildInsertionsVisitor extends DoubleJavaParserVisitor { + /** + * The set of annotations found in the file. Keys are both fully-qualified and simple names. + * Contains an entry for the fully qualified name of each annotation and, if it was imported, + * its simple name. + * + *

The map is populated from import statements and also when parsing a file that uses the + * fully qualified name of an annotation it doesn't import. + */ + private @MonotonicNonNull Map allAnnotations = null; - CompilationTask cTask = - compiler.getTask( - null, - fileManager, - diagnostics, - Collections.emptyList(), - null, - Collections.emptyList()); - if (!(cTask instanceof JavacTask)) { - System.err.println("Could not get a valid JavacTask: " + cTask.getClass()); - System.exit(1); - } + /** The annotation insertions seen so far. */ + public final List insertions = new ArrayList<>(); - return ((JavacTask) cTask).getElements(); - } catch (IOException e) { - throw new Error(e); - } - } + /** A printer for annotations. */ + private final DefaultPrettyPrinter printer = new DefaultPrettyPrinter(); - /** Represents some text to be inserted at a file and its location. */ - private static class Insertion { - /** Offset of the insertion in the file, measured in characters from the beginning. */ - public final int position; - - /** The contents of the insertion. */ - public final String contents; - - /** Whether the insertion should be on its own separate line. */ - public final boolean ownLine; - - /** - * Constructs an insertion with the given position and contents. - * - * @param position offset of the insertion in the file - * @param contents contents of the insertion - */ - public Insertion(int position, String contents) { - this(position, contents, false); - } + /** The lines of the String representation of the second AST. */ + private final List lines; - /** - * Constructs an insertion with the given position and contents. - * - * @param position offset of the insertion in the file - * @param contents contents of the insertion - * @param ownLine true if this insertion should appear on its own separate line (doesn't - * affect the contents of the insertion) - */ - public Insertion(int position, String contents, boolean ownLine) { - this.position = position; - this.contents = contents; - this.ownLine = ownLine; - } + /** The line separator used in the text the second AST was parsed from */ + private final String lineSeparator; - @Override - public String toString() { - return "Insertion [contents=" + contents + ", position=" + position + "]"; - } - } + /** + * Stores the offsets of the lines in the string representation of the second AST. At index i, + * stores the number of characters from the start of the file to the beginning of the ith line. + */ + private final List cumulativeLineSizes; /** - * Given two JavaParser ASTs representing the same Java file but with differing annotations, - * stores a list of {@link Insertion}s. The {@link Insertion}s represent how to textually modify - * the file of the second AST to insert all annotations in the first AST into the second AST, - * but this class doesn't modify the second AST itself. To use this class, call {@link - * #visit(CompilationUnit, Node)} on a pair of ASTs and then use the contents of {@link - * #insertions}. + * Constructs a {@code BuildInsertionsVisitor} where {@code destFileContents} is the String + * representation of the AST to insert annotation into, that uses the given line separator. When + * visiting a node pair, the second node must always be from an AST generated from this String. + * + * @param destFileContents the String the second vistide AST was parsed from + * @param lineSeparator the line separator that {@code destFileContents} uses */ - private class BuildInsertionsVisitor extends DoubleJavaParserVisitor { - /** - * The set of annotations found in the file. Keys are both fully-qualified and simple names. - * Contains an entry for the fully qualified name of each annotation and, if it was - * imported, its simple name. - * - *

The map is populated from import statements and also when parsing a file that uses the - * fully qualified name of an annotation it doesn't import. - */ - private @MonotonicNonNull Map allAnnotations = null; - - /** The annotation insertions seen so far. */ - public final List insertions = new ArrayList<>(); - - /** A printer for annotations. */ - private final DefaultPrettyPrinter printer = new DefaultPrettyPrinter(); - - /** The lines of the String representation of the second AST. */ - private final List lines; - - /** The line separator used in the text the second AST was parsed from */ - private final String lineSeparator; - - /** - * Stores the offsets of the lines in the string representation of the second AST. At index - * i, stores the number of characters from the start of the file to the beginning of the ith - * line. - */ - private final List cumulativeLineSizes; - - /** - * Constructs a {@code BuildInsertionsVisitor} where {@code destFileContents} is the String - * representation of the AST to insert annotation into, that uses the given line separator. - * When visiting a node pair, the second node must always be from an AST generated from this - * String. - * - * @param destFileContents the String the second vistide AST was parsed from - * @param lineSeparator the line separator that {@code destFileContents} uses - */ - public BuildInsertionsVisitor(String destFileContents, String lineSeparator) { - allAnnotations = null; - String[] lines = destFileContents.split(lineSeparator); - this.lines = Arrays.asList(lines); - this.lineSeparator = lineSeparator; - cumulativeLineSizes = new ArrayList<>(lines.length); - cumulativeLineSizes.add(0); - for (int i = 1; i < lines.length; i++) { - int lastSize = cumulativeLineSizes.get(i - 1); - int lastLineLength = lines[i - 1].length() + lineSeparator.length(); - cumulativeLineSizes.add(lastSize + lastLineLength); - } - } + public BuildInsertionsVisitor(String destFileContents, String lineSeparator) { + allAnnotations = null; + String[] lines = destFileContents.split(lineSeparator); + this.lines = Arrays.asList(lines); + this.lineSeparator = lineSeparator; + cumulativeLineSizes = new ArrayList<>(lines.length); + cumulativeLineSizes.add(0); + for (int i = 1; i < lines.length; i++) { + int lastSize = cumulativeLineSizes.get(i - 1); + int lastLineLength = lines[i - 1].length() + lineSeparator.length(); + cumulativeLineSizes.add(lastSize + lastLineLength); + } + } - @Override - public void defaultAction(Node src, Node dest) { - if (!(src instanceof NodeWithAnnotations)) { - return; - } - NodeWithAnnotations srcWithAnnos = (NodeWithAnnotations) src; - - // If `src` is a declaration, its annotations are declaration annotations. - if (src instanceof MethodDeclaration) { - addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); - return; - } else if (src instanceof FieldDeclaration) { - addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); - return; - } + @Override + public void defaultAction(Node src, Node dest) { + if (!(src instanceof NodeWithAnnotations)) { + return; + } + NodeWithAnnotations srcWithAnnos = (NodeWithAnnotations) src; + + // If `src` is a declaration, its annotations are declaration annotations. + if (src instanceof MethodDeclaration) { + addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); + return; + } else if (src instanceof FieldDeclaration) { + addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); + return; + } + + // `src`'s annotations are type annotations. + Position position; + if (dest instanceof ClassOrInterfaceType) { + // In a multi-part name like my.package.MyClass, type annotations go directly in + // front of MyClass instead of the full name. + position = ((ClassOrInterfaceType) dest).getName().getBegin().get(); + } else { + position = dest.getBegin().get(); + } + addAnnotations(position, srcWithAnnos.getAnnotations(), 0, false); + } - // `src`'s annotations are type annotations. - Position position; - if (dest instanceof ClassOrInterfaceType) { - // In a multi-part name like my.package.MyClass, type annotations go directly in - // front of MyClass instead of the full name. - position = ((ClassOrInterfaceType) dest).getName().getBegin().get(); - } else { - position = dest.getBegin().get(); - } - addAnnotations(position, srcWithAnnos.getAnnotations(), 0, false); - } + @Override + public void visit(ArrayType src, Node other) { + ArrayType dest = (ArrayType) other; + // The second component of this pair contains a list of ArrayBracketPairs from left to + // right. For example, if src contains String[][], then the list will contain the + // types String[] and String[][]. To insert array annotations in the correct location, + // we insert them directly to the right of the end of the previous element. + Pair> srcArrayTypes = ArrayType.unwrapArrayTypes(src); + Pair> destArrayTypes = + ArrayType.unwrapArrayTypes(dest); + // The first annotations go directly after the element type. + Position firstPosition = destArrayTypes.a.getEnd().get(); + addAnnotations(firstPosition, srcArrayTypes.b.get(0).getAnnotations(), 1, false); + for (int i = 1; i < srcArrayTypes.b.size(); i++) { + Position position = destArrayTypes.b.get(i - 1).getTokenRange().get().toRange().get().end; + addAnnotations(position, srcArrayTypes.b.get(i).getAnnotations(), 1, true); + } + + // Visit the component type. + srcArrayTypes.a.accept(this, destArrayTypes.a); + } - @Override - public void visit(ArrayType src, Node other) { - ArrayType dest = (ArrayType) other; - // The second component of this pair contains a list of ArrayBracketPairs from left to - // right. For example, if src contains String[][], then the list will contain the - // types String[] and String[][]. To insert array annotations in the correct location, - // we insert them directly to the right of the end of the previous element. - Pair> srcArrayTypes = - ArrayType.unwrapArrayTypes(src); - Pair> destArrayTypes = - ArrayType.unwrapArrayTypes(dest); - // The first annotations go directly after the element type. - Position firstPosition = destArrayTypes.a.getEnd().get(); - addAnnotations(firstPosition, srcArrayTypes.b.get(0).getAnnotations(), 1, false); - for (int i = 1; i < srcArrayTypes.b.size(); i++) { - Position position = - destArrayTypes.b.get(i - 1).getTokenRange().get().toRange().get().end; - addAnnotations(position, srcArrayTypes.b.get(i).getAnnotations(), 1, true); - } + @Override + @SuppressWarnings("optional:method.invocation") // parallel structure of two data structures + public void visit(CompilationUnit src, Node other) { + CompilationUnit dest = (CompilationUnit) other; + defaultAction(src, dest); - // Visit the component type. - srcArrayTypes.a.accept(this, destArrayTypes.a); - } + // Gather annotations used in the ajava file. + allAnnotations = getImportedAnnotations(src); - @Override - @SuppressWarnings("optional:method.invocation") // parallel structure of two data structures - public void visit(CompilationUnit src, Node other) { - CompilationUnit dest = (CompilationUnit) other; - defaultAction(src, dest); - - // Gather annotations used in the ajava file. - allAnnotations = getImportedAnnotations(src); - - // Move any annotations that JavaParser puts in the declaration position but belong only - // in the type position. - src.accept(new TypeAnnotationMover(allAnnotations, elements), null); - - // Transfer import statements from the ajava file to the Java file. - - List newImports; - { // set `newImports` - NodeList destImports = dest.getImports(); - Set existingImports = - new HashSet<>(CollectionsPlume.mapCapacity(destImports.size())); - for (ImportDeclaration importDecl : destImports) { - existingImports.add(printer.print(importDecl)); - } - - newImports = new ArrayList<>(); - for (ImportDeclaration importDecl : src.getImports()) { - String importString = printer.print(importDecl); - if (!existingImports.contains(importString)) { - newImports.add(importString); - } - } - } + // Move any annotations that JavaParser puts in the declaration position but belong only + // in the type position. + src.accept(new TypeAnnotationMover(allAnnotations, elements), null); - if (!newImports.isEmpty()) { - int position; - int lineBreaksBeforeFirstImport; - if (!dest.getImports().isEmpty()) { - Position lastImportPosition = - dest.getImports().get(dest.getImports().size() - 1).getEnd().get(); - position = getFilePosition(lastImportPosition) + 1; - lineBreaksBeforeFirstImport = 1; - } else if (dest.getPackageDeclaration().isPresent()) { - Position packagePosition = dest.getPackageDeclaration().get().getEnd().get(); - position = getFilePosition(packagePosition) + 1; - lineBreaksBeforeFirstImport = 2; - } else { - position = 0; - lineBreaksBeforeFirstImport = 0; - } - - String insertionContent = ""; - // In Java 11, use String::repeat. - for (int i = 0; i < lineBreaksBeforeFirstImport; i++) { - insertionContent += lineSeparator; - } - insertionContent += String.join("", newImports); - - insertions.add(new Insertion(position, insertionContent)); - } + // Transfer import statements from the ajava file to the Java file. - src.getModule().ifPresent(m -> m.accept(this, dest.getModule().get())); - src.getPackageDeclaration() - .ifPresent(pd -> pd.accept(this, dest.getPackageDeclaration().get())); - int numTypes = src.getTypes().size(); - for (int i = 0; i < numTypes; i++) { - src.getTypes().get(i).accept(this, dest.getTypes().get(i)); - } + List newImports; + { // set `newImports` + NodeList destImports = dest.getImports(); + Set existingImports = + new HashSet<>(CollectionsPlume.mapCapacity(destImports.size())); + for (ImportDeclaration importDecl : destImports) { + existingImports.add(printer.print(importDecl)); } - /** - * Creates an insertion for a collection of annotations and adds it to {@link #insertions}. - * The annotations will appear on their own line (unless any non-whitespace characters - * precede the insertion position on its own line). - * - * @param position the position of the insertion - * @param annotations list of annotations to insert - */ - private void addAnnotationOnOwnLine(Position position, List annotations) { - String line = lines.get(position.line - 1); - int insertionColumn = position.column - 1; - boolean ownLine = true; - for (int i = 0; i < insertionColumn; i++) { - if (line.charAt(i) != ' ' && line.charAt(i) != '\t') { - ownLine = false; - break; - } - } - - if (ownLine) { - StringJoiner insertionContent = new StringJoiner(" "); - for (AnnotationExpr annotation : annotations) { - insertionContent.add(printer.print(annotation)); - } - - if (insertionContent.length() == 0) { - return; - } - - String leadingWhitespace = line.substring(0, insertionColumn); - int filePosition = getFilePosition(position); - insertions.add( - new Insertion( - filePosition, - insertionContent.toString() + lineSeparator + leadingWhitespace, - true)); - } else { - addAnnotations(position, annotations, 0, false); - } + newImports = new ArrayList<>(); + for (ImportDeclaration importDecl : src.getImports()) { + String importString = printer.print(importDecl); + if (!existingImports.contains(importString)) { + newImports.add(importString); + } } - - /** - * Creates an insertion for a collection of annotations at {@code position} + {@code offset} - * and adds it to {@link #insertions}. - * - * @param position the position of the insertion - * @param annotations list of annotations to insert - * @param offset additional offset of the insertion after {@code position} - * @param addSpaceBefore if true, the insertion content will start with a space - */ - private void addAnnotations( - Position position, - Iterable annotations, - int offset, - boolean addSpaceBefore) { - StringBuilder insertionContent = new StringBuilder(); - for (AnnotationExpr annotation : annotations) { - insertionContent.append(printer.print(annotation)); - insertionContent.append(" "); - } - - // Can't test `annotations.isEmpty()` earlier because `annotations` has type `Iterable`. - if (insertionContent.length() == 0) { - return; - } - - if (addSpaceBefore) { - insertionContent.insert(0, " "); - } - - int filePosition = getFilePosition(position) + offset; - insertions.add(new Insertion(filePosition, insertionContent.toString())); + } + + if (!newImports.isEmpty()) { + int position; + int lineBreaksBeforeFirstImport; + if (!dest.getImports().isEmpty()) { + Position lastImportPosition = + dest.getImports().get(dest.getImports().size() - 1).getEnd().get(); + position = getFilePosition(lastImportPosition) + 1; + lineBreaksBeforeFirstImport = 1; + } else if (dest.getPackageDeclaration().isPresent()) { + Position packagePosition = dest.getPackageDeclaration().get().getEnd().get(); + position = getFilePosition(packagePosition) + 1; + lineBreaksBeforeFirstImport = 2; + } else { + position = 0; + lineBreaksBeforeFirstImport = 0; } - /** - * Converts a Position (which contains a line and column) to an offset from the start of the - * file, in characters. - * - * @param position a Position - * @return the total offset of the position from the start of the file - */ - private int getFilePosition(Position position) { - return cumulativeLineSizes.get(position.line - 1) + (position.column - 1); + String insertionContent = ""; + // In Java 11, use String::repeat. + for (int i = 0; i < lineBreaksBeforeFirstImport; i++) { + insertionContent += lineSeparator; } + insertionContent += String.join("", newImports); + + insertions.add(new Insertion(position, insertionContent)); + } + + src.getModule().ifPresent(m -> m.accept(this, dest.getModule().get())); + src.getPackageDeclaration() + .ifPresent(pd -> pd.accept(this, dest.getPackageDeclaration().get())); + int numTypes = src.getTypes().size(); + for (int i = 0; i < numTypes; i++) { + src.getTypes().get(i).accept(this, dest.getTypes().get(i)); + } } /** - * Returns all annotations imported by the annotation file as a mapping from simple and - * qualified names to TypeElements. + * Creates an insertion for a collection of annotations and adds it to {@link #insertions}. The + * annotations will appear on their own line (unless any non-whitespace characters precede the + * insertion position on its own line). * - * @param cu compilation unit to extract imports from - * @return a map from names to TypeElement, for all annotations imported by the annotation file. - * Two entries for each annotation: one for the simple name and another for the - * fully-qualified name, with the same value. + * @param position the position of the insertion + * @param annotations list of annotations to insert */ - private Map getImportedAnnotations(CompilationUnit cu) { - if (cu.getImports() == null) { - return Collections.emptyMap(); + private void addAnnotationOnOwnLine(Position position, List annotations) { + String line = lines.get(position.line - 1); + int insertionColumn = position.column - 1; + boolean ownLine = true; + for (int i = 0; i < insertionColumn; i++) { + if (line.charAt(i) != ' ' && line.charAt(i) != '\t') { + ownLine = false; + break; } + } - Map result = new HashMap<>(); - for (ImportDeclaration importDecl : cu.getImports()) { - if (importDecl.isAsterisk()) { - @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: - // com.github.javaparser.ast.expr.Name inherits toString, - // so there can be no annotation for it - ) - @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); - if (importDecl.isStatic()) { - // Wildcard import of members of a type (class or interface) - TypeElement element = elements.getTypeElement(imported); - if (element != null) { - // Find nested annotations - result.putAll(AnnotationFileParser.annosInType(element)); - } - - } else { - // Wildcard import of members of a package - PackageElement element = elements.getPackageElement(imported); - if (element != null) { - result.putAll(AnnotationFileParser.annosInPackage(element)); - } - } - } else { - @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is - // @FullyQualifiedName - ) - @FullyQualifiedName String imported = importDecl.getNameAsString(); - TypeElement importType = elements.getTypeElement(imported); - if (importType != null && importType.getKind() == ElementKind.ANNOTATION_TYPE) { - TypeElement annoElt = elements.getTypeElement(imported); - if (annoElt != null) { - result.put(annoElt.getSimpleName().toString(), annoElt); - } - } - } + if (ownLine) { + StringJoiner insertionContent = new StringJoiner(" "); + for (AnnotationExpr annotation : annotations) { + insertionContent.add(printer.print(annotation)); } - return result; + + if (insertionContent.length() == 0) { + return; + } + + String leadingWhitespace = line.substring(0, insertionColumn); + int filePosition = getFilePosition(position); + insertions.add( + new Insertion( + filePosition, + insertionContent.toString() + lineSeparator + leadingWhitespace, + true)); + } else { + addAnnotations(position, annotations, 0, false); + } } /** - * Inserts all annotations from the ajava file read from {@code annotationFile} into a Java file - * with contents {@code javaFileContents} that uses the given line separator and returns the - * resulting String. + * Creates an insertion for a collection of annotations at {@code position} + {@code offset} and + * adds it to {@link #insertions}. * - * @param annotationFile input stream for an ajava file for {@code javaFileContents} - * @param javaFileContents contents of a Java file to insert annotations into - * @param lineSeparator the line separator {@code javaFileContents} uses - * @return a modified {@code javaFileContents} with annotations from {@code annotationFile} - * inserted + * @param position the position of the insertion + * @param annotations list of annotations to insert + * @param offset additional offset of the insertion after {@code position} + * @param addSpaceBefore if true, the insertion content will start with a space */ - public String insertAnnotations( - InputStream annotationFile, String javaFileContents, String lineSeparator) { - CompilationUnit annotationCu = JavaParserUtil.parseCompilationUnit(annotationFile); - CompilationUnit javaCu = JavaParserUtil.parseCompilationUnit(javaFileContents); - BuildInsertionsVisitor insertionVisitor = - new BuildInsertionsVisitor(javaFileContents, lineSeparator); - annotationCu.accept(insertionVisitor, javaCu); - List insertions = insertionVisitor.insertions; - insertions.sort(InsertAjavaAnnotations::compareInsertions); - - StringBuilder result = new StringBuilder(javaFileContents); - for (Insertion insertion : insertions) { - result.insert(insertion.position, insertion.contents); - } - return result.toString(); + private void addAnnotations( + Position position, + Iterable annotations, + int offset, + boolean addSpaceBefore) { + StringBuilder insertionContent = new StringBuilder(); + for (AnnotationExpr annotation : annotations) { + insertionContent.append(printer.print(annotation)); + insertionContent.append(" "); + } + + // Can't test `annotations.isEmpty()` earlier because `annotations` has type `Iterable`. + if (insertionContent.length() == 0) { + return; + } + + if (addSpaceBefore) { + insertionContent.insert(0, " "); + } + + int filePosition = getFilePosition(position) + offset; + insertions.add(new Insertion(filePosition, insertionContent.toString())); } /** - * Compares two insertions in the reverse order of where their content should appear in the - * file. Making an insertion changes the offset values of all content after the insertion, so - * performing the insertions in reverse order of appearance removes the need to recalculate the - * positions of other insertions. - * - *

The order in which insertions should appear is determined first by their absolute position - * in the file, and second by whether they have their own line. In a method like - * {@code @Pure @Tainting String myMethod()} both annotations should be inserted at the same - * location (right before "String"), but {@code @Pure} should always come first because it - * belongs on its own line. + * Converts a Position (which contains a line and column) to an offset from the start of the + * file, in characters. * - * @param insertion1 the first insertion - * @param insertion2 the second insertion - * @return a negative integer, zero, or a positive integer if {@code insertion1} belongs before, - * at the same position, or after {@code insertion2} respectively in the above ordering + * @param position a Position + * @return the total offset of the position from the start of the file */ - private static int compareInsertions(Insertion insertion1, Insertion insertion2) { - int cmp = Integer.compare(insertion1.position, insertion2.position); - if (cmp == 0 && (insertion1.ownLine != insertion2.ownLine)) { - if (insertion1.ownLine) { - cmp = -1; - } else { - cmp = 1; - } + private int getFilePosition(Position position) { + return cumulativeLineSizes.get(position.line - 1) + (position.column - 1); + } + } + + /** + * Returns all annotations imported by the annotation file as a mapping from simple and qualified + * names to TypeElements. + * + * @param cu compilation unit to extract imports from + * @return a map from names to TypeElement, for all annotations imported by the annotation file. + * Two entries for each annotation: one for the simple name and another for the + * fully-qualified name, with the same value. + */ + private Map getImportedAnnotations(CompilationUnit cu) { + if (cu.getImports() == null) { + return Collections.emptyMap(); + } + + Map result = new HashMap<>(); + for (ImportDeclaration importDecl : cu.getImports()) { + if (importDecl.isAsterisk()) { + @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: + // com.github.javaparser.ast.expr.Name inherits toString, + // so there can be no annotation for it + ) + @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); + if (importDecl.isStatic()) { + // Wildcard import of members of a type (class or interface) + TypeElement element = elements.getTypeElement(imported); + if (element != null) { + // Find nested annotations + result.putAll(AnnotationFileParser.annosInType(element)); + } + + } else { + // Wildcard import of members of a package + PackageElement element = elements.getPackageElement(imported); + if (element != null) { + result.putAll(AnnotationFileParser.annosInPackage(element)); + } } + } else { + @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is + // @FullyQualifiedName + ) + @FullyQualifiedName String imported = importDecl.getNameAsString(); + TypeElement importType = elements.getTypeElement(imported); + if (importType != null && importType.getKind() == ElementKind.ANNOTATION_TYPE) { + TypeElement annoElt = elements.getTypeElement(imported); + if (annoElt != null) { + result.put(annoElt.getSimpleName().toString(), annoElt); + } + } + } + } + return result; + } + + /** + * Inserts all annotations from the ajava file read from {@code annotationFile} into a Java file + * with contents {@code javaFileContents} that uses the given line separator and returns the + * resulting String. + * + * @param annotationFile input stream for an ajava file for {@code javaFileContents} + * @param javaFileContents contents of a Java file to insert annotations into + * @param lineSeparator the line separator {@code javaFileContents} uses + * @return a modified {@code javaFileContents} with annotations from {@code annotationFile} + * inserted + */ + public String insertAnnotations( + InputStream annotationFile, String javaFileContents, String lineSeparator) { + CompilationUnit annotationCu = JavaParserUtil.parseCompilationUnit(annotationFile); + CompilationUnit javaCu = JavaParserUtil.parseCompilationUnit(javaFileContents); + BuildInsertionsVisitor insertionVisitor = + new BuildInsertionsVisitor(javaFileContents, lineSeparator); + annotationCu.accept(insertionVisitor, javaCu); + List insertions = insertionVisitor.insertions; + insertions.sort(InsertAjavaAnnotations::compareInsertions); + + StringBuilder result = new StringBuilder(javaFileContents); + for (Insertion insertion : insertions) { + result.insert(insertion.position, insertion.contents); + } + return result.toString(); + } + + /** + * Compares two insertions in the reverse order of where their content should appear in the file. + * Making an insertion changes the offset values of all content after the insertion, so performing + * the insertions in reverse order of appearance removes the need to recalculate the positions of + * other insertions. + * + *

The order in which insertions should appear is determined first by their absolute position + * in the file, and second by whether they have their own line. In a method like + * {@code @Pure @Tainting String myMethod()} both annotations should be inserted at the same + * location (right before "String"), but {@code @Pure} should always come first because it belongs + * on its own line. + * + * @param insertion1 the first insertion + * @param insertion2 the second insertion + * @return a negative integer, zero, or a positive integer if {@code insertion1} belongs before, + * at the same position, or after {@code insertion2} respectively in the above ordering + */ + private static int compareInsertions(Insertion insertion1, Insertion insertion2) { + int cmp = Integer.compare(insertion1.position, insertion2.position); + if (cmp == 0 && (insertion1.ownLine != insertion2.ownLine)) { + if (insertion1.ownLine) { + cmp = -1; + } else { + cmp = 1; + } + } - return -cmp; + return -cmp; + } + + /** + * Inserts all annotations from the ajava file at {@code annotationFilePath} into {@code + * javaFilePath}. + * + * @param annotationFilePath path to an ajava file + * @param javaFilePath path to a Java file to insert annotation into + */ + public void insertAnnotations(String annotationFilePath, String javaFilePath) { + try { + File javaFile = new File(javaFilePath); + String fileContents = FilesPlume.readFile(javaFile); + String lineSeparator = FilesPlume.inferLineSeparator(annotationFilePath); + try (FileInputStream annotationInputStream = new FileInputStream(annotationFilePath)) { + String result = insertAnnotations(annotationInputStream, fileContents, lineSeparator); + FilesPlume.writeFile(javaFile, result); + } + } catch (IOException e) { + System.err.println( + "Failed to insert annotations from file " + + annotationFilePath + + " into file " + + javaFilePath); + System.exit(1); + } + } + + /** + * Inserts annotations from ajava files into Java files in place. + * + *

The first argument is an ajava file or a directory containing ajava files. + * + *

The second argument is a Java file or a directory containing Java files to insert + * annotations into. + * + *

For each Java file, checks if any ajava files from the first argument match it. For each + * such ajava file, inserts all its annotations into the Java file. + * + * @param args command line arguments: the first element should be a path to ajava files and the + * second should be the directory containing Java files to insert into + */ + public static void main(String[] args) { + if (args.length != 2) { + System.out.println( + "Usage: java InsertAjavaAnnotations " + + " insertionVisitor = + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + if (!path.getFileName().toString().endsWith(".java")) { + return FileVisitResult.CONTINUE; } - } catch (IOException e) { - System.err.println( - "Failed to insert annotations from file " - + annotationFilePath - + " into file " - + javaFilePath); - System.exit(1); - } - } - /** - * Inserts annotations from ajava files into Java files in place. - * - *

The first argument is an ajava file or a directory containing ajava files. - * - *

The second argument is a Java file or a directory containing Java files to insert - * annotations into. - * - *

For each Java file, checks if any ajava files from the first argument match it. For each - * such ajava file, inserts all its annotations into the Java file. - * - * @param args command line arguments: the first element should be a path to ajava files and the - * second should be the directory containing Java files to insert into - */ - public static void main(String[] args) { - if (args.length != 2) { - System.out.println( - "Usage: java InsertAjavaAnnotations " - + " insertionVisitor = - new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { - if (!path.getFileName().toString().endsWith(".java")) { - return FileVisitResult.CONTINUE; - } - - CompilationUnit root = null; - try { - root = JavaParserUtil.parseCompilationUnit(path.toFile()); - } catch (IOException e) { - System.err.println("Failed to read file: " + path); - System.exit(1); - } - - List> rootTypes = root.getTypes(); - // Estimate of size. - Set annotationFilesForRoot = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(rootTypes.size())); - for (TypeDeclaration type : rootTypes) { - String name = JavaParserUtil.getFullyQualifiedName(type, root); - annotationFilesForRoot.addAll( - annotationFiles.getAnnotationFileForType(name)); - } - - for (String annotationFile : annotationFilesForRoot) { - inserter.insertAnnotations(annotationFile, path.toString()); - } - - return FileVisitResult.CONTINUE; - } - }; - - try { - Files.walkFileTree(Paths.get(javaSourceDir), insertionVisitor); - } catch (IOException e) { - System.out.println("Error while adding annotations to: " + javaSourceDir); - e.printStackTrace(); - System.exit(1); - } + List> rootTypes = root.getTypes(); + // Estimate of size. + Set annotationFilesForRoot = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(rootTypes.size())); + for (TypeDeclaration type : rootTypes) { + String name = JavaParserUtil.getFullyQualifiedName(type, root); + annotationFilesForRoot.addAll(annotationFiles.getAnnotationFileForType(name)); + } + + for (String annotationFile : annotationFilesForRoot) { + inserter.insertAnnotations(annotationFile, path.toString()); + } + + return FileVisitResult.CONTINUE; + } + }; + + try { + Files.walkFileTree(Paths.get(javaSourceDir), insertionVisitor); + } catch (IOException e) { + System.out.println("Error while adding annotations to: " + javaSourceDir); + e.printStackTrace(); + System.exit(1); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java index cbf7729ce3e..e7d6451ffd6 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java @@ -154,7 +154,10 @@ import com.sun.source.tree.WhileLoopTree; import com.sun.source.tree.WildcardTree; import com.sun.source.util.SimpleTreeVisitor; - +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; @@ -164,11 +167,6 @@ import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; - /** * A visitor that processes javac trees and JavaParser nodes simultaneously, matching corresponding * nodes. @@ -188,2240 +186,2204 @@ * called before its children. */ public abstract class JointJavacJavaParserVisitor extends SimpleTreeVisitor { - @Override - public Void visitAnnotation(AnnotationTree javacTree, Node javaParserNode) { - // javac stores annotation arguments as assignments, so @MyAnno("myArg") is stored the same - // as @MyAnno(value="myArg") which has a single element argument list with an assignment. - if (javaParserNode instanceof MarkerAnnotationExpr) { - processAnnotation(javacTree, (MarkerAnnotationExpr) javaParserNode); - } else if (javaParserNode instanceof SingleMemberAnnotationExpr) { - SingleMemberAnnotationExpr node = (SingleMemberAnnotationExpr) javaParserNode; - processAnnotation(javacTree, node); - assert javacTree.getArguments().size() == 1; - ExpressionTree value = javacTree.getArguments().get(0); - assert value instanceof AssignmentTree; - AssignmentTree assignment = (AssignmentTree) value; - assert assignment.getVariable().getKind() == Tree.Kind.IDENTIFIER; - assert ((IdentifierTree) assignment.getVariable()).getName().contentEquals("value"); - assignment.getExpression().accept(this, node.getMemberValue()); - } else if (javaParserNode instanceof NormalAnnotationExpr) { - NormalAnnotationExpr node = (NormalAnnotationExpr) javaParserNode; - processAnnotation(javacTree, node); - assert javacTree.getArguments().size() == node.getPairs().size(); - Iterator argIter = node.getPairs().iterator(); - for (ExpressionTree arg : javacTree.getArguments()) { - assert arg instanceof AssignmentTree; - AssignmentTree assignment = (AssignmentTree) arg; - IdentifierTree memberName = (IdentifierTree) assignment.getVariable(); - MemberValuePair javaParserArg = argIter.next(); - assert memberName.getName().contentEquals(javaParserArg.getNameAsString()); - assignment.getExpression().accept(this, javaParserArg.getValue()); - } - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { - castNode(NodeWithAnnotations.class, javaParserNode, javacTree); - processAnnotatedType(javacTree, javaParserNode); - javacTree.getUnderlyingType().accept(this, javaParserNode); - return null; - } - - @Override - public Void visitArrayAccess(ArrayAccessTree javacTree, Node javaParserNode) { - ArrayAccessExpr node = castNode(ArrayAccessExpr.class, javaParserNode, javacTree); - processArrayAccess(javacTree, node); - javacTree.getExpression().accept(this, node.getName()); - javacTree.getIndex().accept(this, node.getIndex()); - return null; - } - - @Override - public Void visitArrayType(ArrayTypeTree javacTree, Node javaParserNode) { - ArrayType node = castNode(ArrayType.class, javaParserNode, javacTree); - processArrayType(javacTree, node); - javacTree.getType().accept(this, node.getComponentType()); - return null; - } - - @Override - public Void visitAssert(AssertTree javacTree, Node javaParserNode) { - AssertStmt node = castNode(AssertStmt.class, javaParserNode, javacTree); - processAssert(javacTree, node); - javacTree.getCondition().accept(this, node.getCheck()); - visitOptional(javacTree.getDetail(), node.getMessage()); - - return null; - } - - @Override - public Void visitAssignment(AssignmentTree javacTree, Node javaParserNode) { - AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); - processAssignment(javacTree, node); - javacTree.getVariable().accept(this, node.getTarget()); - javacTree.getExpression().accept(this, node.getValue()); - return null; - } - - @Override - public Void visitBinary(BinaryTree javacTree, Node javaParserNode) { - BinaryExpr node = castNode(BinaryExpr.class, javaParserNode, javacTree); - processBinary(javacTree, node); - javacTree.getLeftOperand().accept(this, node.getLeft()); - javacTree.getRightOperand().accept(this, node.getRight()); - return null; - } - - /** - * Visit a BindingPatternTree. - * - * @param javacTree a BindingPatternTree, typed as Tree to be backward-compatible - * @param javaParserNode a PatternExpr - * @return nothing - */ - @SuppressWarnings("UnusedVariable") - public Void visitBindingPattern17(Tree javacTree, Node javaParserNode) { - PatternExpr patternExpr = castNode(PatternExpr.class, javaParserNode, javacTree); - processBindingPattern(javacTree, patternExpr); - VariableTree variableTree = BindingPatternUtils.getVariable(javacTree); - // The name expression can be null, even when a name exists. - if (variableTree.getNameExpression() != null) { - variableTree.getNameExpression().accept(this, patternExpr.getName()); - } - - assert variableTree.getInitializer() == null; - variableTree.getType().accept(this, patternExpr.getType()); - - return null; - } - - @Override - public Void visitBlock(BlockTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof InitializerDeclaration) { - return javacTree.accept(this, ((InitializerDeclaration) javaParserNode).getBody()); - } - - BlockStmt node = castNode(BlockStmt.class, javaParserNode, javacTree); - processBlock(javacTree, node); - processStatements(javacTree.getStatements(), node.getStatements()); - return null; - } - - /** - * Given a matching sequence of statements for a block, visits each javac statement with its - * corresponding JavaParser statement, excluding synthetic javac trees like no-argument - * constructors. - * - * @param javacStatements sequence of javac trees for statements - * @param javaParserStatements sequence of JavaParser statements representing the same block as - * {@code javacStatements} - */ - private void processStatements( - Iterable javacStatements, - Iterable javaParserStatements) { - PeekingIterator javacIter = - Iterators.peekingIterator(javacStatements.iterator()); - PeekingIterator javaParserIter = - Iterators.peekingIterator(javaParserStatements.iterator()); - - while (javacIter.hasNext() || javaParserIter.hasNext()) { - // Skip synthetic javac super() calls by checking if the JavaParser statement matches. - if (javacIter.hasNext() - && isDefaultSuperConstructorCall(javacIter.peek()) - && (!javaParserIter.hasNext() - || !isDefaultSuperConstructorCall(javaParserIter.peek()))) { - javacIter.next(); - continue; - } - - // In javac, a line like "int i = 0, j = 0" is expanded as two sibling VariableTree - // instances. In javaParser this is one VariableDeclarationExpr with two nested - // VariableDeclarators. Match the declarators with the VariableTrees. - if (javaParserIter.hasNext() - && javacIter.peek().getKind() == Tree.Kind.VARIABLE - && javaParserIter.peek().isExpressionStmt() - && javaParserIter - .peek() - .asExpressionStmt() - .getExpression() - .isVariableDeclarationExpr()) { - for (VariableDeclarator decl : - javaParserIter - .next() - .asExpressionStmt() - .getExpression() - .asVariableDeclarationExpr() - .getVariables()) { - assert javacIter.hasNext(); - javacIter.next().accept(this, decl); - } - - continue; - } - - assert javacIter.hasNext(); - assert javaParserIter.hasNext(); - javacIter.next().accept(this, javaParserIter.next()); - } - - assert !javacIter.hasNext(); - assert !javaParserIter.hasNext(); - } - - /** - * Returns whether a javac statement represents a method call {@code super()}. - * - * @param statement the javac statement to check - * @return true if statement is a method invocation named "super" with no arguments, false - * otherwise - */ - public static boolean isDefaultSuperConstructorCall(StatementTree statement) { - if (statement.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { - return false; - } - - ExpressionStatementTree expressionStatement = (ExpressionStatementTree) statement; - if (expressionStatement.getExpression().getKind() != Tree.Kind.METHOD_INVOCATION) { - return false; - } - - MethodInvocationTree invocation = - (MethodInvocationTree) expressionStatement.getExpression(); - if (invocation.getMethodSelect().getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - - if (!((IdentifierTree) invocation.getMethodSelect()).getName().contentEquals("super")) { - return false; - } - - return invocation.getArguments().isEmpty(); - } - - /** - * Returns whether a JavaParser statement represents a method call {@code super()}. - * - * @param statement the JavaParser statement to check - * @return true if statement is an explicit super constructor invocation with no arguments - */ - private boolean isDefaultSuperConstructorCall(Statement statement) { - if (!statement.isExplicitConstructorInvocationStmt()) { - return false; + @Override + public Void visitAnnotation(AnnotationTree javacTree, Node javaParserNode) { + // javac stores annotation arguments as assignments, so @MyAnno("myArg") is stored the same + // as @MyAnno(value="myArg") which has a single element argument list with an assignment. + if (javaParserNode instanceof MarkerAnnotationExpr) { + processAnnotation(javacTree, (MarkerAnnotationExpr) javaParserNode); + } else if (javaParserNode instanceof SingleMemberAnnotationExpr) { + SingleMemberAnnotationExpr node = (SingleMemberAnnotationExpr) javaParserNode; + processAnnotation(javacTree, node); + assert javacTree.getArguments().size() == 1; + ExpressionTree value = javacTree.getArguments().get(0); + assert value instanceof AssignmentTree; + AssignmentTree assignment = (AssignmentTree) value; + assert assignment.getVariable().getKind() == Tree.Kind.IDENTIFIER; + assert ((IdentifierTree) assignment.getVariable()).getName().contentEquals("value"); + assignment.getExpression().accept(this, node.getMemberValue()); + } else if (javaParserNode instanceof NormalAnnotationExpr) { + NormalAnnotationExpr node = (NormalAnnotationExpr) javaParserNode; + processAnnotation(javacTree, node); + assert javacTree.getArguments().size() == node.getPairs().size(); + Iterator argIter = node.getPairs().iterator(); + for (ExpressionTree arg : javacTree.getArguments()) { + assert arg instanceof AssignmentTree; + AssignmentTree assignment = (AssignmentTree) arg; + IdentifierTree memberName = (IdentifierTree) assignment.getVariable(); + MemberValuePair javaParserArg = argIter.next(); + assert memberName.getName().contentEquals(javaParserArg.getNameAsString()); + assignment.getExpression().accept(this, javaParserArg.getValue()); + } + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { + castNode(NodeWithAnnotations.class, javaParserNode, javacTree); + processAnnotatedType(javacTree, javaParserNode); + javacTree.getUnderlyingType().accept(this, javaParserNode); + return null; + } + + @Override + public Void visitArrayAccess(ArrayAccessTree javacTree, Node javaParserNode) { + ArrayAccessExpr node = castNode(ArrayAccessExpr.class, javaParserNode, javacTree); + processArrayAccess(javacTree, node); + javacTree.getExpression().accept(this, node.getName()); + javacTree.getIndex().accept(this, node.getIndex()); + return null; + } + + @Override + public Void visitArrayType(ArrayTypeTree javacTree, Node javaParserNode) { + ArrayType node = castNode(ArrayType.class, javaParserNode, javacTree); + processArrayType(javacTree, node); + javacTree.getType().accept(this, node.getComponentType()); + return null; + } + + @Override + public Void visitAssert(AssertTree javacTree, Node javaParserNode) { + AssertStmt node = castNode(AssertStmt.class, javaParserNode, javacTree); + processAssert(javacTree, node); + javacTree.getCondition().accept(this, node.getCheck()); + visitOptional(javacTree.getDetail(), node.getMessage()); + + return null; + } + + @Override + public Void visitAssignment(AssignmentTree javacTree, Node javaParserNode) { + AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); + processAssignment(javacTree, node); + javacTree.getVariable().accept(this, node.getTarget()); + javacTree.getExpression().accept(this, node.getValue()); + return null; + } + + @Override + public Void visitBinary(BinaryTree javacTree, Node javaParserNode) { + BinaryExpr node = castNode(BinaryExpr.class, javaParserNode, javacTree); + processBinary(javacTree, node); + javacTree.getLeftOperand().accept(this, node.getLeft()); + javacTree.getRightOperand().accept(this, node.getRight()); + return null; + } + + /** + * Visit a BindingPatternTree. + * + * @param javacTree a BindingPatternTree, typed as Tree to be backward-compatible + * @param javaParserNode a PatternExpr + * @return nothing + */ + @SuppressWarnings("UnusedVariable") + public Void visitBindingPattern17(Tree javacTree, Node javaParserNode) { + PatternExpr patternExpr = castNode(PatternExpr.class, javaParserNode, javacTree); + processBindingPattern(javacTree, patternExpr); + VariableTree variableTree = BindingPatternUtils.getVariable(javacTree); + // The name expression can be null, even when a name exists. + if (variableTree.getNameExpression() != null) { + variableTree.getNameExpression().accept(this, patternExpr.getName()); + } + + assert variableTree.getInitializer() == null; + variableTree.getType().accept(this, patternExpr.getType()); + + return null; + } + + @Override + public Void visitBlock(BlockTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof InitializerDeclaration) { + return javacTree.accept(this, ((InitializerDeclaration) javaParserNode).getBody()); + } + + BlockStmt node = castNode(BlockStmt.class, javaParserNode, javacTree); + processBlock(javacTree, node); + processStatements(javacTree.getStatements(), node.getStatements()); + return null; + } + + /** + * Given a matching sequence of statements for a block, visits each javac statement with its + * corresponding JavaParser statement, excluding synthetic javac trees like no-argument + * constructors. + * + * @param javacStatements sequence of javac trees for statements + * @param javaParserStatements sequence of JavaParser statements representing the same block as + * {@code javacStatements} + */ + private void processStatements( + Iterable javacStatements, Iterable javaParserStatements) { + PeekingIterator javacIter = + Iterators.peekingIterator(javacStatements.iterator()); + PeekingIterator javaParserIter = + Iterators.peekingIterator(javaParserStatements.iterator()); + + while (javacIter.hasNext() || javaParserIter.hasNext()) { + // Skip synthetic javac super() calls by checking if the JavaParser statement matches. + if (javacIter.hasNext() + && isDefaultSuperConstructorCall(javacIter.peek()) + && (!javaParserIter.hasNext() || !isDefaultSuperConstructorCall(javaParserIter.peek()))) { + javacIter.next(); + continue; + } + + // In javac, a line like "int i = 0, j = 0" is expanded as two sibling VariableTree + // instances. In javaParser this is one VariableDeclarationExpr with two nested + // VariableDeclarators. Match the declarators with the VariableTrees. + if (javaParserIter.hasNext() + && javacIter.peek().getKind() == Tree.Kind.VARIABLE + && javaParserIter.peek().isExpressionStmt() + && javaParserIter.peek().asExpressionStmt().getExpression().isVariableDeclarationExpr()) { + for (VariableDeclarator decl : + javaParserIter + .next() + .asExpressionStmt() + .getExpression() + .asVariableDeclarationExpr() + .getVariables()) { + assert javacIter.hasNext(); + javacIter.next().accept(this, decl); } - ExplicitConstructorInvocationStmt invocation = - statement.asExplicitConstructorInvocationStmt(); - boolean isSuper = !invocation.isThis(); - return isSuper && invocation.getArguments().isEmpty(); - } - - @Override - public Void visitBreak(BreakTree javacTree, Node javaParserNode) { - BreakStmt node = castNode(BreakStmt.class, javaParserNode, javacTree); - processBreak(javacTree, node); - return null; - } - - @Override - public Void visitCase(CaseTree javacTree, Node javaParserNode) { - SwitchEntry node = castNode(SwitchEntry.class, javaParserNode, javacTree); - processCase(javacTree, node); - // Java 12 introduced multiple label cases: - List labels = node.getLabels(); - List treeExpressions = CaseUtils.getExpressions(javacTree); - assert node.getLabels().size() == treeExpressions.size() - : String.format( - "node.getLabels() = %s, treeExpressions = %s", - node.getLabels(), treeExpressions); - for (int i = 0; i < treeExpressions.size(); i++) { - treeExpressions.get(i).accept(this, labels.get(i)); - } - if (javacTree.getStatements() == null) { - Tree javacBody = CaseUtils.getBody(javacTree); - Statement nodeBody = node.getStatement(0); - if (javacBody.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { - javacBody.accept(this, node.getStatement(0)); - } else if (nodeBody.isExpressionStmt()) { - javacBody.accept(this, nodeBody.asExpressionStmt().getExpression()); - } else { - javacBody.accept(this, nodeBody); - } + continue; + } + + assert javacIter.hasNext(); + assert javaParserIter.hasNext(); + javacIter.next().accept(this, javaParserIter.next()); + } + + assert !javacIter.hasNext(); + assert !javaParserIter.hasNext(); + } + + /** + * Returns whether a javac statement represents a method call {@code super()}. + * + * @param statement the javac statement to check + * @return true if statement is a method invocation named "super" with no arguments, false + * otherwise + */ + public static boolean isDefaultSuperConstructorCall(StatementTree statement) { + if (statement.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { + return false; + } + + ExpressionStatementTree expressionStatement = (ExpressionStatementTree) statement; + if (expressionStatement.getExpression().getKind() != Tree.Kind.METHOD_INVOCATION) { + return false; + } + + MethodInvocationTree invocation = (MethodInvocationTree) expressionStatement.getExpression(); + if (invocation.getMethodSelect().getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + + if (!((IdentifierTree) invocation.getMethodSelect()).getName().contentEquals("super")) { + return false; + } + + return invocation.getArguments().isEmpty(); + } + + /** + * Returns whether a JavaParser statement represents a method call {@code super()}. + * + * @param statement the JavaParser statement to check + * @return true if statement is an explicit super constructor invocation with no arguments + */ + private boolean isDefaultSuperConstructorCall(Statement statement) { + if (!statement.isExplicitConstructorInvocationStmt()) { + return false; + } + + ExplicitConstructorInvocationStmt invocation = statement.asExplicitConstructorInvocationStmt(); + boolean isSuper = !invocation.isThis(); + return isSuper && invocation.getArguments().isEmpty(); + } + + @Override + public Void visitBreak(BreakTree javacTree, Node javaParserNode) { + BreakStmt node = castNode(BreakStmt.class, javaParserNode, javacTree); + processBreak(javacTree, node); + return null; + } + + @Override + public Void visitCase(CaseTree javacTree, Node javaParserNode) { + SwitchEntry node = castNode(SwitchEntry.class, javaParserNode, javacTree); + processCase(javacTree, node); + // Java 12 introduced multiple label cases: + List labels = node.getLabels(); + List treeExpressions = CaseUtils.getExpressions(javacTree); + assert node.getLabels().size() == treeExpressions.size() + : String.format( + "node.getLabels() = %s, treeExpressions = %s", node.getLabels(), treeExpressions); + for (int i = 0; i < treeExpressions.size(); i++) { + treeExpressions.get(i).accept(this, labels.get(i)); + } + if (javacTree.getStatements() == null) { + Tree javacBody = CaseUtils.getBody(javacTree); + Statement nodeBody = node.getStatement(0); + if (javacBody.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { + javacBody.accept(this, node.getStatement(0)); + } else if (nodeBody.isExpressionStmt()) { + javacBody.accept(this, nodeBody.asExpressionStmt().getExpression()); + } else { + javacBody.accept(this, nodeBody); + } + } else { + processStatements(javacTree.getStatements(), node.getStatements()); + } + + return null; + } + + @Override + public Void visitCatch(CatchTree javacTree, Node javaParserNode) { + CatchClause node = castNode(CatchClause.class, javaParserNode, javacTree); + processCatch(javacTree, node); + javacTree.getParameter().accept(this, node.getParameter()); + javacTree.getBlock().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitClass(ClassTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof ClassOrInterfaceDeclaration) { + ClassOrInterfaceDeclaration node = (ClassOrInterfaceDeclaration) javaParserNode; + processClass(javacTree, node); + visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); + + if (javacTree.getKind() == Tree.Kind.CLASS) { + if (javacTree.getExtendsClause() == null) { + assert node.getExtendedTypes().isEmpty(); } else { - processStatements(javacTree.getStatements(), node.getStatements()); + assert node.getExtendedTypes().size() == 1; + javacTree.getExtendsClause().accept(this, node.getExtendedTypes().get(0)); } - return null; - } - - @Override - public Void visitCatch(CatchTree javacTree, Node javaParserNode) { - CatchClause node = castNode(CatchClause.class, javaParserNode, javacTree); - processCatch(javacTree, node); - javacTree.getParameter().accept(this, node.getParameter()); - javacTree.getBlock().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitClass(ClassTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof ClassOrInterfaceDeclaration) { - ClassOrInterfaceDeclaration node = (ClassOrInterfaceDeclaration) javaParserNode; - processClass(javacTree, node); - visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); - - if (javacTree.getKind() == Tree.Kind.CLASS) { - if (javacTree.getExtendsClause() == null) { - assert node.getExtendedTypes().isEmpty(); - } else { - assert node.getExtendedTypes().size() == 1; - javacTree.getExtendsClause().accept(this, node.getExtendedTypes().get(0)); - } - - visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); - } else if (javacTree.getKind() == Tree.Kind.INTERFACE) { - visitLists(javacTree.getImplementsClause(), node.getExtendedTypes()); - } - - visitClassMembers(javacTree.getMembers(), node.getMembers()); - } else if (javaParserNode instanceof RecordDeclaration) { - RecordDeclaration node = (RecordDeclaration) javaParserNode; - processClass(javacTree, node); - visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); - visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); - List membersWithoutAutoGenerated = - Lists.newArrayList( - Iterables.filter( - javacTree.getMembers(), - (Predicate) - (Tree m) -> { - // Filter out all auto-generated items: - return !TreeUtils.isAutoGeneratedRecordMember(m); - })); - visitClassMembers(membersWithoutAutoGenerated, node.getMembers()); - } else if (javaParserNode instanceof AnnotationDeclaration) { - AnnotationDeclaration node = (AnnotationDeclaration) javaParserNode; - processClass(javacTree, node); - visitClassMembers(javacTree.getMembers(), node.getMembers()); - } else if (javaParserNode instanceof LocalClassDeclarationStmt) { - javacTree.accept( - this, ((LocalClassDeclarationStmt) javaParserNode).getClassDeclaration()); - } else if (javaParserNode instanceof LocalRecordDeclarationStmt) { - javacTree.accept( - this, ((LocalRecordDeclarationStmt) javaParserNode).getRecordDeclaration()); - } else if (javaParserNode instanceof EnumDeclaration) { - EnumDeclaration node = (EnumDeclaration) javaParserNode; - processClass(javacTree, node); - visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); - // In an enum declaration, javac stores the enum constants expanded as constant variable - // members, whereas JavaParser stores them as one object. Need to match them. - assert javacTree.getKind() == Tree.Kind.ENUM; - List javacMembers = new ArrayList<>(javacTree.getMembers()); - // Discard a synthetic constructor if it exists. If there are any constants in this - // enum, then they will show up as the first members of the javac tree, except for - // possibly a synthetic constructor. - if (!node.getEntries().isEmpty()) { - while (!javacMembers.isEmpty() - && javacMembers.get(0).getKind() != Tree.Kind.VARIABLE) { - javacMembers.remove(0); - } - } - - for (EnumConstantDeclaration entry : node.getEntries()) { - assert !javacMembers.isEmpty(); - javacMembers.get(0).accept(this, entry); - javacMembers.remove(0); - } - - visitClassMembers(javacMembers, node.getMembers()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); + visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); + } else if (javacTree.getKind() == Tree.Kind.INTERFACE) { + visitLists(javacTree.getImplementsClause(), node.getExtendedTypes()); + } + + visitClassMembers(javacTree.getMembers(), node.getMembers()); + } else if (javaParserNode instanceof RecordDeclaration) { + RecordDeclaration node = (RecordDeclaration) javaParserNode; + processClass(javacTree, node); + visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); + visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); + List membersWithoutAutoGenerated = + Lists.newArrayList( + Iterables.filter( + javacTree.getMembers(), + (Predicate) + (Tree m) -> { + // Filter out all auto-generated items: + return !TreeUtils.isAutoGeneratedRecordMember(m); + })); + visitClassMembers(membersWithoutAutoGenerated, node.getMembers()); + } else if (javaParserNode instanceof AnnotationDeclaration) { + AnnotationDeclaration node = (AnnotationDeclaration) javaParserNode; + processClass(javacTree, node); + visitClassMembers(javacTree.getMembers(), node.getMembers()); + } else if (javaParserNode instanceof LocalClassDeclarationStmt) { + javacTree.accept(this, ((LocalClassDeclarationStmt) javaParserNode).getClassDeclaration()); + } else if (javaParserNode instanceof LocalRecordDeclarationStmt) { + javacTree.accept(this, ((LocalRecordDeclarationStmt) javaParserNode).getRecordDeclaration()); + } else if (javaParserNode instanceof EnumDeclaration) { + EnumDeclaration node = (EnumDeclaration) javaParserNode; + processClass(javacTree, node); + visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); + // In an enum declaration, javac stores the enum constants expanded as constant variable + // members, whereas JavaParser stores them as one object. Need to match them. + assert javacTree.getKind() == Tree.Kind.ENUM; + List javacMembers = new ArrayList<>(javacTree.getMembers()); + // Discard a synthetic constructor if it exists. If there are any constants in this + // enum, then they will show up as the first members of the javac tree, except for + // possibly a synthetic constructor. + if (!node.getEntries().isEmpty()) { + while (!javacMembers.isEmpty() && javacMembers.get(0).getKind() != Tree.Kind.VARIABLE) { + javacMembers.remove(0); } - - return null; - } - - /** - * Given a list of class members for javac and JavaParser, visits each javac member with its - * corresponding JavaParser member. Skips synthetic javac members. - * - * @param javacMembers a list of trees forming the members of a javac {@code ClassTree} - * @param javaParserMembers a list of nodes forming the members of a JavaParser {@code - * ClassOrInterfaceDeclaration} or an {@code ObjectCreationExpr} with an anonymous class - * body that corresponds to {@code javacMembers} - */ - private void visitClassMembers( - List javacMembers, List> javaParserMembers) { - PeekingIterator javacIter = Iterators.peekingIterator(javacMembers.iterator()); - PeekingIterator> javaParserIter = - Iterators.peekingIterator(javaParserMembers.iterator()); - while (javacIter.hasNext() || javaParserIter.hasNext()) { - // Skip javac's synthetic no-argument constructors. - if (javacIter.hasNext() - && isNoArgumentConstructor(javacIter.peek()) - && (!javaParserIter.hasNext() - || !isNoArgumentConstructor(javaParserIter.peek()))) { - javacIter.next(); - continue; - } - - // In javac, a line like int i = 0, j = 0 is expanded as two sibling VariableTree - // instances. In JavaParser this is one FieldDeclaration with two nested - // VariableDeclarators. Match the declarators with the VariableTrees. - if (javaParserIter.hasNext() && javaParserIter.peek().isFieldDeclaration()) { - for (VariableDeclarator decl : - javaParserIter.next().asFieldDeclaration().getVariables()) { - assert javacIter.hasNext(); - assert javacIter.peek().getKind() == Tree.Kind.VARIABLE; - javacIter.next().accept(this, decl); - } - - continue; - } - - assert javacIter.hasNext(); - assert javaParserIter.hasNext(); - javacIter.next().accept(this, javaParserIter.next()); - } - - assert !javacIter.hasNext(); - assert !javaParserIter.hasNext(); - } - - /** - * Visits the members of an anonymous class body. - * - *

In normal classes, javac inserts a synthetic no-argument constructor if no constructor is - * explicitly defined, which is skipped when visiting members. Anonymous class bodies may - * introduce constructors that take arguments if the constructor invocation that created them - * was passed arguments. For example, if {@code MyClass} has a constructor taking a single - * integer argument, then writing {@code new MyClass(5) { }} expands to the javac tree - * - *

{@code
-     * new MyClass(5) {
-     *     (int arg) {
-     *         super(arg);
-     *     }
-     * }
-     * }
- * - *

This method skips these synthetic constructors. - * - * @param javacBody body of an anonymous class body - * @param javaParserMembers list of members for the anonymous class body of an {@code - * ObjectCreationExpr} - */ - public void visitAnonymousClassBody( - ClassTree javacBody, List> javaParserMembers) { - List javacMembers = new ArrayList<>(javacBody.getMembers()); - if (!javacMembers.isEmpty()) { - Tree member = javacMembers.get(0); - if (member.getKind() == Tree.Kind.METHOD) { - MethodTree methodTree = (MethodTree) member; - if (methodTree.getName().contentEquals("")) { - javacMembers.remove(0); - } - } - } - - visitClassMembers(javacMembers, javaParserMembers); - } - - /** - * Returns whether {@code member} is a javac constructor declaration that takes no arguments. - * - * @param member the javac tree to check - * @return true if {@code member} is a method declaration with name {@code } that takes no - * arguments - */ - public static boolean isNoArgumentConstructor(Tree member) { - if (member.getKind() != Tree.Kind.METHOD) { - return false; + } + + for (EnumConstantDeclaration entry : node.getEntries()) { + assert !javacMembers.isEmpty(); + javacMembers.get(0).accept(this, entry); + javacMembers.remove(0); + } + + visitClassMembers(javacMembers, node.getMembers()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + /** + * Given a list of class members for javac and JavaParser, visits each javac member with its + * corresponding JavaParser member. Skips synthetic javac members. + * + * @param javacMembers a list of trees forming the members of a javac {@code ClassTree} + * @param javaParserMembers a list of nodes forming the members of a JavaParser {@code + * ClassOrInterfaceDeclaration} or an {@code ObjectCreationExpr} with an anonymous class body + * that corresponds to {@code javacMembers} + */ + private void visitClassMembers( + List javacMembers, List> javaParserMembers) { + PeekingIterator javacIter = Iterators.peekingIterator(javacMembers.iterator()); + PeekingIterator> javaParserIter = + Iterators.peekingIterator(javaParserMembers.iterator()); + while (javacIter.hasNext() || javaParserIter.hasNext()) { + // Skip javac's synthetic no-argument constructors. + if (javacIter.hasNext() + && isNoArgumentConstructor(javacIter.peek()) + && (!javaParserIter.hasNext() || !isNoArgumentConstructor(javaParserIter.peek()))) { + javacIter.next(); + continue; + } + + // In javac, a line like int i = 0, j = 0 is expanded as two sibling VariableTree + // instances. In JavaParser this is one FieldDeclaration with two nested + // VariableDeclarators. Match the declarators with the VariableTrees. + if (javaParserIter.hasNext() && javaParserIter.peek().isFieldDeclaration()) { + for (VariableDeclarator decl : javaParserIter.next().asFieldDeclaration().getVariables()) { + assert javacIter.hasNext(); + assert javacIter.peek().getKind() == Tree.Kind.VARIABLE; + javacIter.next().accept(this, decl); } + continue; + } + + assert javacIter.hasNext(); + assert javaParserIter.hasNext(); + javacIter.next().accept(this, javaParserIter.next()); + } + + assert !javacIter.hasNext(); + assert !javaParserIter.hasNext(); + } + + /** + * Visits the members of an anonymous class body. + * + *

In normal classes, javac inserts a synthetic no-argument constructor if no constructor is + * explicitly defined, which is skipped when visiting members. Anonymous class bodies may + * introduce constructors that take arguments if the constructor invocation that created them was + * passed arguments. For example, if {@code MyClass} has a constructor taking a single integer + * argument, then writing {@code new MyClass(5) { }} expands to the javac tree + * + *

{@code
+   * new MyClass(5) {
+   *     (int arg) {
+   *         super(arg);
+   *     }
+   * }
+   * }
+ * + *

This method skips these synthetic constructors. + * + * @param javacBody body of an anonymous class body + * @param javaParserMembers list of members for the anonymous class body of an {@code + * ObjectCreationExpr} + */ + public void visitAnonymousClassBody( + ClassTree javacBody, List> javaParserMembers) { + List javacMembers = new ArrayList<>(javacBody.getMembers()); + if (!javacMembers.isEmpty()) { + Tree member = javacMembers.get(0); + if (member.getKind() == Tree.Kind.METHOD) { MethodTree methodTree = (MethodTree) member; - return methodTree.getName().contentEquals("") && methodTree.getParameters().isEmpty(); - } - - /** - * Returns whether {@code member} is a JavaParser constructor declaration that takes no - * arguments. - * - * @param member the JavaParser body declaration to check - * @return true if {@code member} is a constructor declaration that takes no arguments - */ - private boolean isNoArgumentConstructor(BodyDeclaration member) { - return member.isConstructorDeclaration() - && member.asConstructorDeclaration().getParameters().isEmpty(); - } - - @Override - public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { - CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); - processCompilationUnit(javacTree, node); - visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); - visitLists(javacTree.getImports(), node.getImports()); - visitLists(javacTree.getTypeDecls(), node.getTypes()); - return null; - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree javacTree, Node javaParserNode) { - AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); - processCompoundAssignment(javacTree, node); - javacTree.getVariable().accept(this, node.getTarget()); - javacTree.getExpression().accept(this, node.getValue()); - return null; - } - - @Override - public Void visitConditionalExpression( - ConditionalExpressionTree javacTree, Node javaParserNode) { - ConditionalExpr node = castNode(ConditionalExpr.class, javaParserNode, javacTree); - processConditionalExpression(javacTree, node); - javacTree.getCondition().accept(this, node.getCondition()); - javacTree.getTrueExpression().accept(this, node.getThenExpr()); - javacTree.getFalseExpression().accept(this, node.getElseExpr()); - return null; - } - - @Override - public Void visitContinue(ContinueTree javacTree, Node javaParserNode) { - ContinueStmt node = castNode(ContinueStmt.class, javaParserNode, javacTree); - processContinue(javacTree, node); - return null; - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree javacTree, Node javaParserNode) { - DoStmt node = castNode(DoStmt.class, javaParserNode, javacTree); - processDoWhileLoop(javacTree, node); - // In javac the condition is parenthesized but not in JavaParser. - ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); - condition.accept(this, node.getCondition()); - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitEmptyStatement(EmptyStatementTree javacTree, Node javaParserNode) { - EmptyStmt node = castNode(EmptyStmt.class, javaParserNode, javacTree); - processEmptyStatement(javacTree, node); - return null; - } - - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree javacTree, Node javaParserNode) { - ForEachStmt node = castNode(ForEachStmt.class, javaParserNode, javacTree); - processEnhancedForLoop(javacTree, node); - javacTree.getVariable().accept(this, node.getVariableDeclarator()); - javacTree.getExpression().accept(this, node.getIterable()); - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitErroneous(ErroneousTree javacTree, Node javaParserNode) { - // An erroneous tree is a malformed expression, so skip. - return null; - } - - @Override - public Void visitExports(ExportsTree javacTree, Node javaParserNode) { - ModuleExportsDirective node = - castNode(ModuleExportsDirective.class, javaParserNode, javacTree); - processExports(javacTree, node); - visitLists(javacTree.getModuleNames(), node.getModuleNames()); - javacTree.getPackageName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitExpressionStatement(ExpressionStatementTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof ExpressionStmt) { - ExpressionStmt node = (ExpressionStmt) javaParserNode; - processExpressionStatemen(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { - // In this case the javac expression will be a MethodTree. Since JavaParser doesn't - // surround explicit constructor invocations in an expression statement, we match - // javaParserNode to the javac expression rather than the javac expression statement. - javacTree.getExpression().accept(this, javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitForLoop(ForLoopTree javacTree, Node javaParserNode) { - ForStmt node = castNode(ForStmt.class, javaParserNode, javacTree); - processForLoop(javacTree, node); - Iterator javacInitializers = javacTree.getInitializer().iterator(); - for (Expression initializer : node.getInitialization()) { - if (initializer.isVariableDeclarationExpr()) { - for (VariableDeclarator declarator : - initializer.asVariableDeclarationExpr().getVariables()) { - assert javacInitializers.hasNext(); - javacInitializers.next().accept(this, declarator); - } - } else if (initializer.isAssignExpr()) { - ExpressionStatementTree statement = - (ExpressionStatementTree) javacInitializers.next(); - statement.getExpression().accept(this, initializer); - } else { - assert javacInitializers.hasNext(); - StatementTree javacInitializer = javacInitializers.next(); - if (javacInitializer.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { - // JavaParser doesn't wrap other kinds of expressions in an expression - // statement, but javac does. For example, suppose that the initializer is - // "index++", as in the test all-systems/LightWeightCache.java. - ((ExpressionStatementTree) javacInitializer) - .getExpression() - .accept(this, initializer); - } else { - // This is likely to lead to a crash, if it ever happens: javacInitializer - // is a StatementTree of some kind, but initializer is a raw expression (not - // wrapped in a statement). - javacInitializer.accept(this, initializer); - } - } - } - assert !javacInitializers.hasNext(); - - visitOptional(javacTree.getCondition(), node.getCompare()); - - // Javac stores a list of expression statements and JavaParser stores a list of statements, - // the javac statements must be unwrapped. - assert javacTree.getUpdate().size() == node.getUpdate().size(); - Iterator javaParserUpdates = node.getUpdate().iterator(); - for (ExpressionStatementTree javacUpdate : javacTree.getUpdate()) { - // Match the inner javac expression with the JavaParser expression. - javacUpdate.getExpression().accept(this, javaParserUpdates.next()); - } - - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitIdentifier(IdentifierTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof ClassOrInterfaceType) { - processIdentifier(javacTree, (ClassOrInterfaceType) javaParserNode); - } else if (javaParserNode instanceof Name) { - processIdentifier(javacTree, (Name) javaParserNode); - } else if (javaParserNode instanceof NameExpr) { - processIdentifier(javacTree, (NameExpr) javaParserNode); - } else if (javaParserNode instanceof SimpleName) { - processIdentifier(javacTree, (SimpleName) javaParserNode); - } else if (javaParserNode instanceof ThisExpr) { - processIdentifier(javacTree, (ThisExpr) javaParserNode); - } else if (javaParserNode instanceof SuperExpr) { - processIdentifier(javacTree, (SuperExpr) javaParserNode); - } else if (javaParserNode instanceof TypeExpr) { - // This occurs in a member reference like MyClass::myMember. The MyClass is wrapped in a - // TypeExpr. - javacTree.accept(this, ((TypeExpr) javaParserNode).getType()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); + if (methodTree.getName().contentEquals("")) { + javacMembers.remove(0); } - - return null; - } - - @Override - public Void visitIf(IfTree javacTree, Node javaParserNode) { - IfStmt node = castNode(IfStmt.class, javaParserNode, javacTree); - processIf(javacTree, node); - assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; - ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); - condition.accept(this, node.getCondition()); - javacTree.getThenStatement().accept(this, node.getThenStmt()); - visitOptional(javacTree.getElseStatement(), node.getElseStmt()); - - return null; - } - - @Override - public Void visitImport(ImportTree javacTree, Node javaParserNode) { - ImportDeclaration node = castNode(ImportDeclaration.class, javaParserNode, javacTree); - processImport(javacTree, node); - // In javac trees, a name like "a.*" is stored as a member select, but JavaParser just - // stores "a" and records that the name ends in an asterisk. - if (node.isAsterisk()) { - assert javacTree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT; - MemberSelectTree identifier = (MemberSelectTree) javacTree.getQualifiedIdentifier(); - identifier.getExpression().accept(this, node.getName()); - } else { - javacTree.getQualifiedIdentifier().accept(this, node.getName()); + } + } + + visitClassMembers(javacMembers, javaParserMembers); + } + + /** + * Returns whether {@code member} is a javac constructor declaration that takes no arguments. + * + * @param member the javac tree to check + * @return true if {@code member} is a method declaration with name {@code } that takes no + * arguments + */ + public static boolean isNoArgumentConstructor(Tree member) { + if (member.getKind() != Tree.Kind.METHOD) { + return false; + } + + MethodTree methodTree = (MethodTree) member; + return methodTree.getName().contentEquals("") && methodTree.getParameters().isEmpty(); + } + + /** + * Returns whether {@code member} is a JavaParser constructor declaration that takes no arguments. + * + * @param member the JavaParser body declaration to check + * @return true if {@code member} is a constructor declaration that takes no arguments + */ + private boolean isNoArgumentConstructor(BodyDeclaration member) { + return member.isConstructorDeclaration() + && member.asConstructorDeclaration().getParameters().isEmpty(); + } + + @Override + public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { + CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); + processCompilationUnit(javacTree, node); + visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); + visitLists(javacTree.getImports(), node.getImports()); + visitLists(javacTree.getTypeDecls(), node.getTypes()); + return null; + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree javacTree, Node javaParserNode) { + AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); + processCompoundAssignment(javacTree, node); + javacTree.getVariable().accept(this, node.getTarget()); + javacTree.getExpression().accept(this, node.getValue()); + return null; + } + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree javacTree, Node javaParserNode) { + ConditionalExpr node = castNode(ConditionalExpr.class, javaParserNode, javacTree); + processConditionalExpression(javacTree, node); + javacTree.getCondition().accept(this, node.getCondition()); + javacTree.getTrueExpression().accept(this, node.getThenExpr()); + javacTree.getFalseExpression().accept(this, node.getElseExpr()); + return null; + } + + @Override + public Void visitContinue(ContinueTree javacTree, Node javaParserNode) { + ContinueStmt node = castNode(ContinueStmt.class, javaParserNode, javacTree); + processContinue(javacTree, node); + return null; + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree javacTree, Node javaParserNode) { + DoStmt node = castNode(DoStmt.class, javaParserNode, javacTree); + processDoWhileLoop(javacTree, node); + // In javac the condition is parenthesized but not in JavaParser. + ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); + condition.accept(this, node.getCondition()); + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitEmptyStatement(EmptyStatementTree javacTree, Node javaParserNode) { + EmptyStmt node = castNode(EmptyStmt.class, javaParserNode, javacTree); + processEmptyStatement(javacTree, node); + return null; + } + + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree javacTree, Node javaParserNode) { + ForEachStmt node = castNode(ForEachStmt.class, javaParserNode, javacTree); + processEnhancedForLoop(javacTree, node); + javacTree.getVariable().accept(this, node.getVariableDeclarator()); + javacTree.getExpression().accept(this, node.getIterable()); + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitErroneous(ErroneousTree javacTree, Node javaParserNode) { + // An erroneous tree is a malformed expression, so skip. + return null; + } + + @Override + public Void visitExports(ExportsTree javacTree, Node javaParserNode) { + ModuleExportsDirective node = castNode(ModuleExportsDirective.class, javaParserNode, javacTree); + processExports(javacTree, node); + visitLists(javacTree.getModuleNames(), node.getModuleNames()); + javacTree.getPackageName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitExpressionStatement(ExpressionStatementTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof ExpressionStmt) { + ExpressionStmt node = (ExpressionStmt) javaParserNode; + processExpressionStatemen(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { + // In this case the javac expression will be a MethodTree. Since JavaParser doesn't + // surround explicit constructor invocations in an expression statement, we match + // javaParserNode to the javac expression rather than the javac expression statement. + javacTree.getExpression().accept(this, javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitForLoop(ForLoopTree javacTree, Node javaParserNode) { + ForStmt node = castNode(ForStmt.class, javaParserNode, javacTree); + processForLoop(javacTree, node); + Iterator javacInitializers = javacTree.getInitializer().iterator(); + for (Expression initializer : node.getInitialization()) { + if (initializer.isVariableDeclarationExpr()) { + for (VariableDeclarator declarator : + initializer.asVariableDeclarationExpr().getVariables()) { + assert javacInitializers.hasNext(); + javacInitializers.next().accept(this, declarator); } - - return null; - } - - @Override - public Void visitInstanceOf(InstanceOfTree javacTree, Node javaParserNode) { - InstanceOfExpr node = castNode(InstanceOfExpr.class, javaParserNode, javacTree); - processInstanceOf(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - if (node.getPattern().isPresent()) { - Tree bindingPattern = InstanceOfUtils.getPattern(javacTree); - visitBindingPattern17(bindingPattern, node.getPattern().get()); + } else if (initializer.isAssignExpr()) { + ExpressionStatementTree statement = (ExpressionStatementTree) javacInitializers.next(); + statement.getExpression().accept(this, initializer); + } else { + assert javacInitializers.hasNext(); + StatementTree javacInitializer = javacInitializers.next(); + if (javacInitializer.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { + // JavaParser doesn't wrap other kinds of expressions in an expression + // statement, but javac does. For example, suppose that the initializer is + // "index++", as in the test all-systems/LightWeightCache.java. + ((ExpressionStatementTree) javacInitializer).getExpression().accept(this, initializer); } else { - javacTree.getType().accept(this, node.getType()); - } - return null; - } - - @Override - public Void visitIntersectionType(IntersectionTypeTree javacTree, Node javaParserNode) { - IntersectionType node = castNode(IntersectionType.class, javaParserNode, javacTree); - processIntersectionType(javacTree, node); - visitLists(javacTree.getBounds(), node.getElements()); - return null; - } - - @Override - public Void visitLabeledStatement(LabeledStatementTree javacTree, Node javaParserNode) { - LabeledStmt node = castNode(LabeledStmt.class, javaParserNode, javacTree); - processLabeledStatement(javacTree, node); - javacTree.getStatement().accept(this, node.getStatement()); - return null; - } - - @Override - public Void visitLambdaExpression(LambdaExpressionTree javacTree, Node javaParserNode) { - LambdaExpr node = castNode(LambdaExpr.class, javaParserNode, javacTree); - processLambdaExpression(javacTree, node); - visitLists(javacTree.getParameters(), node.getParameters()); - switch (javacTree.getBodyKind()) { - case EXPRESSION: - assert node.getBody() instanceof ExpressionStmt; - ExpressionStmt body = (ExpressionStmt) node.getBody(); - javacTree.getBody().accept(this, body.getExpression()); - break; - case STATEMENT: - javacTree.getBody().accept(this, node.getBody()); - break; + // This is likely to lead to a crash, if it ever happens: javacInitializer + // is a StatementTree of some kind, but initializer is a raw expression (not + // wrapped in a statement). + javacInitializer.accept(this, initializer); } - - return null; - } - - @Override - public Void visitLiteral(LiteralTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof LiteralExpr) { - processLiteral(javacTree, (LiteralExpr) javaParserNode); - } else if (javaParserNode instanceof UnaryExpr) { - // Occurs for negative literals such as -7. - processLiteral(javacTree, (UnaryExpr) javaParserNode); - } else if (javaParserNode instanceof BinaryExpr) { - // Occurs for expression like "a" + "b" where javac compresses them to "ab" but - // JavaParser doesn't. - processLiteral(javacTree, (BinaryExpr) javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); + } + } + assert !javacInitializers.hasNext(); + + visitOptional(javacTree.getCondition(), node.getCompare()); + + // Javac stores a list of expression statements and JavaParser stores a list of statements, + // the javac statements must be unwrapped. + assert javacTree.getUpdate().size() == node.getUpdate().size(); + Iterator javaParserUpdates = node.getUpdate().iterator(); + for (ExpressionStatementTree javacUpdate : javacTree.getUpdate()) { + // Match the inner javac expression with the JavaParser expression. + javacUpdate.getExpression().accept(this, javaParserUpdates.next()); + } + + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitIdentifier(IdentifierTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof ClassOrInterfaceType) { + processIdentifier(javacTree, (ClassOrInterfaceType) javaParserNode); + } else if (javaParserNode instanceof Name) { + processIdentifier(javacTree, (Name) javaParserNode); + } else if (javaParserNode instanceof NameExpr) { + processIdentifier(javacTree, (NameExpr) javaParserNode); + } else if (javaParserNode instanceof SimpleName) { + processIdentifier(javacTree, (SimpleName) javaParserNode); + } else if (javaParserNode instanceof ThisExpr) { + processIdentifier(javacTree, (ThisExpr) javaParserNode); + } else if (javaParserNode instanceof SuperExpr) { + processIdentifier(javacTree, (SuperExpr) javaParserNode); + } else if (javaParserNode instanceof TypeExpr) { + // This occurs in a member reference like MyClass::myMember. The MyClass is wrapped in a + // TypeExpr. + javacTree.accept(this, ((TypeExpr) javaParserNode).getType()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitIf(IfTree javacTree, Node javaParserNode) { + IfStmt node = castNode(IfStmt.class, javaParserNode, javacTree); + processIf(javacTree, node); + assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; + ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); + condition.accept(this, node.getCondition()); + javacTree.getThenStatement().accept(this, node.getThenStmt()); + visitOptional(javacTree.getElseStatement(), node.getElseStmt()); + + return null; + } + + @Override + public Void visitImport(ImportTree javacTree, Node javaParserNode) { + ImportDeclaration node = castNode(ImportDeclaration.class, javaParserNode, javacTree); + processImport(javacTree, node); + // In javac trees, a name like "a.*" is stored as a member select, but JavaParser just + // stores "a" and records that the name ends in an asterisk. + if (node.isAsterisk()) { + assert javacTree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT; + MemberSelectTree identifier = (MemberSelectTree) javacTree.getQualifiedIdentifier(); + identifier.getExpression().accept(this, node.getName()); + } else { + javacTree.getQualifiedIdentifier().accept(this, node.getName()); + } + + return null; + } + + @Override + public Void visitInstanceOf(InstanceOfTree javacTree, Node javaParserNode) { + InstanceOfExpr node = castNode(InstanceOfExpr.class, javaParserNode, javacTree); + processInstanceOf(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + if (node.getPattern().isPresent()) { + Tree bindingPattern = InstanceOfUtils.getPattern(javacTree); + visitBindingPattern17(bindingPattern, node.getPattern().get()); + } else { + javacTree.getType().accept(this, node.getType()); + } + return null; + } + + @Override + public Void visitIntersectionType(IntersectionTypeTree javacTree, Node javaParserNode) { + IntersectionType node = castNode(IntersectionType.class, javaParserNode, javacTree); + processIntersectionType(javacTree, node); + visitLists(javacTree.getBounds(), node.getElements()); + return null; + } + + @Override + public Void visitLabeledStatement(LabeledStatementTree javacTree, Node javaParserNode) { + LabeledStmt node = castNode(LabeledStmt.class, javaParserNode, javacTree); + processLabeledStatement(javacTree, node); + javacTree.getStatement().accept(this, node.getStatement()); + return null; + } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree javacTree, Node javaParserNode) { + LambdaExpr node = castNode(LambdaExpr.class, javaParserNode, javacTree); + processLambdaExpression(javacTree, node); + visitLists(javacTree.getParameters(), node.getParameters()); + switch (javacTree.getBodyKind()) { + case EXPRESSION: + assert node.getBody() instanceof ExpressionStmt; + ExpressionStmt body = (ExpressionStmt) node.getBody(); + javacTree.getBody().accept(this, body.getExpression()); + break; + case STATEMENT: + javacTree.getBody().accept(this, node.getBody()); + break; + } + + return null; + } + + @Override + public Void visitLiteral(LiteralTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof LiteralExpr) { + processLiteral(javacTree, (LiteralExpr) javaParserNode); + } else if (javaParserNode instanceof UnaryExpr) { + // Occurs for negative literals such as -7. + processLiteral(javacTree, (UnaryExpr) javaParserNode); + } else if (javaParserNode instanceof BinaryExpr) { + // Occurs for expression like "a" + "b" where javac compresses them to "ab" but + // JavaParser doesn't. + processLiteral(javacTree, (BinaryExpr) javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitMemberReference(MemberReferenceTree javacTree, Node javaParserNode) { + MethodReferenceExpr node = castNode(MethodReferenceExpr.class, javaParserNode, javacTree); + processMemberReference(javacTree, node); + if (node.getScope().isTypeExpr()) { + javacTree.getQualifierExpression().accept(this, node.getScope().asTypeExpr().getType()); + } else { + javacTree.getQualifierExpression().accept(this, node.getScope()); + } + + assert (javacTree.getTypeArguments() != null) == node.getTypeArguments().isPresent(); + if (node.getTypeArguments().isPresent()) { + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + return null; + } + + @Override + public Void visitMemberSelect(MemberSelectTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof FieldAccessExpr) { + FieldAccessExpr node = (FieldAccessExpr) javaParserNode; + processMemberSelect(javacTree, node); + javacTree.getExpression().accept(this, node.getScope()); + } else if (javaParserNode instanceof Name) { + Name node = (Name) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getQualifier().isPresent(); + javacTree.getExpression().accept(this, node.getQualifier().get()); + } else if (javaParserNode instanceof ClassOrInterfaceType) { + ClassOrInterfaceType node = (ClassOrInterfaceType) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getScope().isPresent(); + javacTree.getExpression().accept(this, node.getScope().get()); + } else if (javaParserNode instanceof ClassExpr) { + ClassExpr node = (ClassExpr) javaParserNode; + processMemberSelect(javacTree, node); + javacTree.getExpression().accept(this, node.getType()); + } else if (javaParserNode instanceof ThisExpr) { + ThisExpr node = (ThisExpr) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getTypeName().isPresent(); + javacTree.getExpression().accept(this, node.getTypeName().get()); + } else if (javaParserNode instanceof SuperExpr) { + SuperExpr node = (SuperExpr) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getTypeName().isPresent(); + javacTree.getExpression().accept(this, node.getTypeName().get()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitMethod(MethodTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof MethodDeclaration) { + visitMethodForMethodDeclaration(javacTree, (MethodDeclaration) javaParserNode); + } else if (javaParserNode instanceof ConstructorDeclaration) { + visitMethodForConstructorDeclaration(javacTree, (ConstructorDeclaration) javaParserNode); + } else if (javaParserNode instanceof CompactConstructorDeclaration) { + visitMethodForConstructorDeclaration( + javacTree, (CompactConstructorDeclaration) javaParserNode); + } else if (javaParserNode instanceof AnnotationMemberDeclaration) { + visitMethodForAnnotationMemberDeclaration( + javacTree, (AnnotationMemberDeclaration) javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + throw new BugInCF("unreachable"); + } + return null; + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * MethodDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser method declaration + */ + private void visitMethodForMethodDeclaration( + MethodTree javacTree, MethodDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + // TODO: Handle modifiers. In javac this is a ModifiersTree but in JavaParser it's a list of + // modifiers. This is a problem because a ModifiersTree has separate accessors to + // annotations and other modifiers, so the order doesn't match. It might be that for + // JavaParser, the annotations and other modifiers are also accessed separately. + if (javacTree.getReturnType() != null) { + javacTree.getReturnType().accept(this, javaParserNode.getType()); + } + // Unlike other javac constructs, the javac list is non-null even if no type parameters are + // present. + visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); + // JavaParser sometimes inserts a receiver parameter that is not present in the source code. + // (Example: on an explicitly-written toString for an enum class.) + if (javacTree.getReceiverParameter() != null + && javaParserNode.getReceiverParameter().isPresent()) { + javacTree.getReceiverParameter().accept(this, javaParserNode.getReceiverParameter().get()); + } + + visitLists(javacTree.getParameters(), javaParserNode.getParameters()); + + visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); + visitOptional(javacTree.getBody(), javaParserNode.getBody()); + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * ConstructorDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser constructor declaration + */ + private void visitMethodForConstructorDeclaration( + MethodTree javacTree, ConstructorDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); + visitOptional(javacTree.getReceiverParameter(), javaParserNode.getReceiverParameter()); + visitLists(javacTree.getParameters(), javaParserNode.getParameters()); + visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); + javacTree.getBody().accept(this, javaParserNode.getBody()); + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * CompactConstructorDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser constructor declaration + */ + private void visitMethodForConstructorDeclaration( + MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); + visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); + javacTree.getBody().accept(this, javaParserNode.getBody()); + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * AnnotationMemberDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser annotation member declaration + */ + private void visitMethodForAnnotationMemberDeclaration( + MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + javacTree.getReturnType().accept(this, javaParserNode.getType()); + visitOptional(javacTree.getDefaultValue(), javaParserNode.getDefaultValue()); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof MethodCallExpr) { + MethodCallExpr node = (MethodCallExpr) javaParserNode; + processMethodInvocation(javacTree, node); + // In javac, the type arguments will be empty if no type arguments are specified, but in + // JavaParser the type arguments will have the none Optional value. + assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); + if (!javacTree.getTypeArguments().isEmpty()) { + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + // In JavaParser, the method name itself and receiver are stored as fields of the + // invocation itself, but in javac they might be combined into one MemberSelectTree. + // That member select may also be a single IdentifierTree if no receiver was written. + // This requires one layer of unnesting. + ExpressionTree methodSelect = javacTree.getMethodSelect(); + if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { + methodSelect.accept(this, node.getName()); + } else if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree selection = (MemberSelectTree) methodSelect; + assert node.getScope().isPresent(); + selection.getExpression().accept(this, node.getScope().get()); + } else { + throw new BugInCF("Unexpected method selection type: %s", methodSelect); + } + + visitLists(javacTree.getArguments(), node.getArguments()); + } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { + ExplicitConstructorInvocationStmt node = (ExplicitConstructorInvocationStmt) javaParserNode; + processMethodInvocation(javacTree, node); + assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); + if (!javacTree.getTypeArguments().isEmpty()) { + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + visitLists(javacTree.getArguments(), node.getArguments()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitModifiers(ModifiersTree arg0, Node arg1) { + // TODO How to handle this? I don't think there's a corresponding JavaParser class, maybe + // the NodeWithModifiers interface? + return null; + } + + @Override + public Void visitModule(ModuleTree javacTree, Node javaParserNode) { + ModuleDeclaration node = castNode(ModuleDeclaration.class, javaParserNode, javacTree); + processModule(javacTree, node); + javacTree.getName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitNewArray(NewArrayTree javacTree, Node javaParserNode) { + // TODO: Implement this. + // + // Some notes: + // - javacTree.getAnnotations() seems to always return empty, any annotations on the + // base type seem to go on the type itself in javacTree.getType(). The JavaParser version + // doesn't even have a corresponding getAnnotations method. + // - When there are no initializers, both systems use similar representations. The + // dimensions line up. + // - When there is an initializer, they differ greatly for multi-dimensional arrays. Javac + // turns an expression like new int[][]{{1, 2}, {3, 4}} into a single NewArray tree with + // type int[] and two initializer elements {1, 2} and {3, 4}. However, for each of the + // sub-initializers, it creates an implicit NewArray tree with a null component type. + // JavaParser keeps the whole expression as one ArrayCreationExpr with multiple dimensions + // and the initializer stored in special ArrayInitializerExpr type. + return null; + } + + @Override + public Void visitNewClass(NewClassTree javacTree, Node javaParserNode) { + ObjectCreationExpr node = castNode(ObjectCreationExpr.class, javaParserNode, javacTree); + processNewClass(javacTree, node); + // When using Java 11 javac, an expression like this.new MyInnerClass() would store "this" + // as the enclosing expression. In Java 8 javac, this would be stored as new + // MyInnerClass(this). So, we only traverse the enclosing expression if present in both. + if (javacTree.getEnclosingExpression() != null && node.getScope().isPresent()) { + javacTree.getEnclosingExpression().accept(this, node.getScope().get()); + } + + javacTree.getIdentifier().accept(this, node.getType()); + if (javacTree.getTypeArguments().isEmpty()) { + assert !node.getTypeArguments().isPresent(); + } else { + assert node.getTypeArguments().isPresent(); + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + // Remove synthetic javac argument. When using Java 11, an expression like this.new + // MyInnerClass() would store "this" as the enclosing expression. In Java 8, this would be + // stored as new MyInnerClass(this). So, for the argument lists to match, we may have to + // remove the first argument. + List javacArgs = new ArrayList<>(javacTree.getArguments()); + if (javacArgs.size() > node.getArguments().size()) { + javacArgs.remove(0); + } + + visitLists(javacArgs, node.getArguments()); + assert (javacTree.getClassBody() != null) == node.getAnonymousClassBody().isPresent(); + if (javacTree.getClassBody() != null) { + visitAnonymousClassBody(javacTree.getClassBody(), node.getAnonymousClassBody().get()); + } + + return null; + } + + @Override + public Void visitOpens(OpensTree javacTree, Node javaParserNode) { + ModuleOpensDirective node = castNode(ModuleOpensDirective.class, javaParserNode, javacTree); + processOpens(javacTree, node); + javacTree.getPackageName().accept(this, node.getName()); + visitLists(javacTree.getModuleNames(), node.getModuleNames()); + return null; + } + + @Override + public Void visitOther(Tree javacTree, Node javaParserNode) { + processOther(javacTree, javaParserNode); + return null; + } + + @Override + public Void visitPackage(PackageTree javacTree, Node javaParserNode) { + PackageDeclaration node = castNode(PackageDeclaration.class, javaParserNode, javacTree); + processPackage(javacTree, node); + // visitLists(javacTree.getAnnotations(), node.getAnnotations()); + javacTree.getPackageName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitParameterizedType(ParameterizedTypeTree javacTree, Node javaParserNode) { + ClassOrInterfaceType node = castNode(ClassOrInterfaceType.class, javaParserNode, javacTree); + processParameterizedType(javacTree, node); + javacTree.getType().accept(this, node); + // TODO: In a parameterized type, will the first branch ever run? + if (javacTree.getTypeArguments().isEmpty()) { + assert !node.getTypeArguments().isPresent() || node.getTypeArguments().get().isEmpty(); + } else { + assert node.getTypeArguments().isPresent(); + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + return null; + } + + @Override + public Void visitParenthesized(ParenthesizedTree javacTree, Node javaParserNode) { + EnclosedExpr node = castNode(EnclosedExpr.class, javaParserNode, javacTree); + processParenthesized(javacTree, node); + javacTree.getExpression().accept(this, node.getInner()); + return null; + } + + @Override + public Void visitPrimitiveType(PrimitiveTypeTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof PrimitiveType) { + processPrimitiveType(javacTree, (PrimitiveType) javaParserNode); + } else if (javaParserNode instanceof VoidType) { + processPrimitiveType(javacTree, (VoidType) javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitProvides(ProvidesTree javacTree, Node javaParserNode) { + ModuleProvidesDirective node = + castNode(ModuleProvidesDirective.class, javaParserNode, javacTree); + processProvides(javacTree, node); + javacTree.getServiceName().accept(this, node.getName()); + visitLists(javacTree.getImplementationNames(), node.getWith()); + return null; + } + + @Override + public Void visitRequires(RequiresTree javacTree, Node javaParserNode) { + ModuleRequiresDirective node = + castNode(ModuleRequiresDirective.class, javaParserNode, javacTree); + processRequires(javacTree, node); + javacTree.getModuleName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitReturn(ReturnTree javacTree, Node javaParserNode) { + ReturnStmt node = castNode(ReturnStmt.class, javaParserNode, javacTree); + processReturn(javacTree, node); + visitOptional(javacTree.getExpression(), node.getExpression()); + + return null; + } + + @Override + public Void visitSwitch(SwitchTree javacTree, Node javaParserNode) { + SwitchStmt node = castNode(SwitchStmt.class, javaParserNode, javacTree); + processSwitch(javacTree, node); + // Switch expressions are always parenthesized in javac but never in JavaParser. + ExpressionTree expression = ((ParenthesizedTree) javacTree.getExpression()).getExpression(); + expression.accept(this, node.getSelector()); + visitLists(javacTree.getCases(), node.getEntries()); + return null; + } + + /** + * Visit a switch expression. + * + * @param javacTree switch expression tree + * @param javaParserNode a JavaParser node + * @return null + */ + public Void visitSwitchExpression17(Tree javacTree, Node javaParserNode) { + SwitchExpr node = castNode(SwitchExpr.class, javaParserNode, javacTree); + processSwitchExpression(javacTree, node); + + // Switch expressions are always parenthesized in javac but never in JavaParser. + ExpressionTree expression = + ((ParenthesizedTree) SwitchExpressionUtils.getExpression(javacTree)).getExpression(); + expression.accept(this, node.getSelector()); + + visitLists(SwitchExpressionUtils.getCases(javacTree), node.getEntries()); + return null; + } + + @Override + public Void visitSynchronized(SynchronizedTree javacTree, Node javaParserNode) { + SynchronizedStmt node = castNode(SynchronizedStmt.class, javaParserNode, javacTree); + processSynchronized(javacTree, node); + ((ParenthesizedTree) javacTree.getExpression()) + .getExpression() + .accept(this, node.getExpression()); + javacTree.getBlock().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitThrow(ThrowTree javacTree, Node javaParserNode) { + ThrowStmt node = castNode(ThrowStmt.class, javaParserNode, javacTree); + processThrow(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + return null; + } + + @Override + public Void visitTry(TryTree javacTree, Node javaParserNode) { + TryStmt node = castNode(TryStmt.class, javaParserNode, javacTree); + processTry(javacTree, node); + Iterator javacResources = javacTree.getResources().iterator(); + for (Expression resource : node.getResources()) { + if (resource.isVariableDeclarationExpr()) { + for (VariableDeclarator declarator : resource.asVariableDeclarationExpr().getVariables()) { + assert javacResources.hasNext(); + javacResources.next().accept(this, declarator); } - - return null; - } - - @Override - public Void visitMemberReference(MemberReferenceTree javacTree, Node javaParserNode) { - MethodReferenceExpr node = castNode(MethodReferenceExpr.class, javaParserNode, javacTree); - processMemberReference(javacTree, node); - if (node.getScope().isTypeExpr()) { - javacTree.getQualifierExpression().accept(this, node.getScope().asTypeExpr().getType()); - } else { - javacTree.getQualifierExpression().accept(this, node.getScope()); - } - - assert (javacTree.getTypeArguments() != null) == node.getTypeArguments().isPresent(); - if (node.getTypeArguments().isPresent()) { - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - return null; - } - - @Override - public Void visitMemberSelect(MemberSelectTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof FieldAccessExpr) { - FieldAccessExpr node = (FieldAccessExpr) javaParserNode; - processMemberSelect(javacTree, node); - javacTree.getExpression().accept(this, node.getScope()); - } else if (javaParserNode instanceof Name) { - Name node = (Name) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getQualifier().isPresent(); - javacTree.getExpression().accept(this, node.getQualifier().get()); - } else if (javaParserNode instanceof ClassOrInterfaceType) { - ClassOrInterfaceType node = (ClassOrInterfaceType) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getScope().isPresent(); - javacTree.getExpression().accept(this, node.getScope().get()); - } else if (javaParserNode instanceof ClassExpr) { - ClassExpr node = (ClassExpr) javaParserNode; - processMemberSelect(javacTree, node); - javacTree.getExpression().accept(this, node.getType()); - } else if (javaParserNode instanceof ThisExpr) { - ThisExpr node = (ThisExpr) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getTypeName().isPresent(); - javacTree.getExpression().accept(this, node.getTypeName().get()); - } else if (javaParserNode instanceof SuperExpr) { - SuperExpr node = (SuperExpr) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getTypeName().isPresent(); - javacTree.getExpression().accept(this, node.getTypeName().get()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitMethod(MethodTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof MethodDeclaration) { - visitMethodForMethodDeclaration(javacTree, (MethodDeclaration) javaParserNode); - } else if (javaParserNode instanceof ConstructorDeclaration) { - visitMethodForConstructorDeclaration( - javacTree, (ConstructorDeclaration) javaParserNode); - } else if (javaParserNode instanceof CompactConstructorDeclaration) { - visitMethodForConstructorDeclaration( - javacTree, (CompactConstructorDeclaration) javaParserNode); - } else if (javaParserNode instanceof AnnotationMemberDeclaration) { - visitMethodForAnnotationMemberDeclaration( - javacTree, (AnnotationMemberDeclaration) javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - throw new BugInCF("unreachable"); - } - return null; - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * MethodDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser method declaration - */ - private void visitMethodForMethodDeclaration( - MethodTree javacTree, MethodDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - // TODO: Handle modifiers. In javac this is a ModifiersTree but in JavaParser it's a list of - // modifiers. This is a problem because a ModifiersTree has separate accessors to - // annotations and other modifiers, so the order doesn't match. It might be that for - // JavaParser, the annotations and other modifiers are also accessed separately. - if (javacTree.getReturnType() != null) { - javacTree.getReturnType().accept(this, javaParserNode.getType()); - } - // Unlike other javac constructs, the javac list is non-null even if no type parameters are - // present. - visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); - // JavaParser sometimes inserts a receiver parameter that is not present in the source code. - // (Example: on an explicitly-written toString for an enum class.) - if (javacTree.getReceiverParameter() != null - && javaParserNode.getReceiverParameter().isPresent()) { - javacTree - .getReceiverParameter() - .accept(this, javaParserNode.getReceiverParameter().get()); - } - - visitLists(javacTree.getParameters(), javaParserNode.getParameters()); - - visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); - visitOptional(javacTree.getBody(), javaParserNode.getBody()); - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * ConstructorDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser constructor declaration - */ - private void visitMethodForConstructorDeclaration( - MethodTree javacTree, ConstructorDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); - visitOptional(javacTree.getReceiverParameter(), javaParserNode.getReceiverParameter()); - visitLists(javacTree.getParameters(), javaParserNode.getParameters()); - visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); - javacTree.getBody().accept(this, javaParserNode.getBody()); - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * CompactConstructorDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser constructor declaration - */ - private void visitMethodForConstructorDeclaration( - MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); - visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); - javacTree.getBody().accept(this, javaParserNode.getBody()); - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * AnnotationMemberDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser annotation member declaration - */ - private void visitMethodForAnnotationMemberDeclaration( - MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - javacTree.getReturnType().accept(this, javaParserNode.getType()); - visitOptional(javacTree.getDefaultValue(), javaParserNode.getDefaultValue()); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof MethodCallExpr) { - MethodCallExpr node = (MethodCallExpr) javaParserNode; - processMethodInvocation(javacTree, node); - // In javac, the type arguments will be empty if no type arguments are specified, but in - // JavaParser the type arguments will have the none Optional value. - assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); - if (!javacTree.getTypeArguments().isEmpty()) { - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - // In JavaParser, the method name itself and receiver are stored as fields of the - // invocation itself, but in javac they might be combined into one MemberSelectTree. - // That member select may also be a single IdentifierTree if no receiver was written. - // This requires one layer of unnesting. - ExpressionTree methodSelect = javacTree.getMethodSelect(); - if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { - methodSelect.accept(this, node.getName()); - } else if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree selection = (MemberSelectTree) methodSelect; - assert node.getScope().isPresent(); - selection.getExpression().accept(this, node.getScope().get()); - } else { - throw new BugInCF("Unexpected method selection type: %s", methodSelect); - } - - visitLists(javacTree.getArguments(), node.getArguments()); - } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { - ExplicitConstructorInvocationStmt node = - (ExplicitConstructorInvocationStmt) javaParserNode; - processMethodInvocation(javacTree, node); - assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); - if (!javacTree.getTypeArguments().isEmpty()) { - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - visitLists(javacTree.getArguments(), node.getArguments()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitModifiers(ModifiersTree arg0, Node arg1) { - // TODO How to handle this? I don't think there's a corresponding JavaParser class, maybe - // the NodeWithModifiers interface? - return null; - } - - @Override - public Void visitModule(ModuleTree javacTree, Node javaParserNode) { - ModuleDeclaration node = castNode(ModuleDeclaration.class, javaParserNode, javacTree); - processModule(javacTree, node); - javacTree.getName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitNewArray(NewArrayTree javacTree, Node javaParserNode) { - // TODO: Implement this. - // - // Some notes: - // - javacTree.getAnnotations() seems to always return empty, any annotations on the - // base type seem to go on the type itself in javacTree.getType(). The JavaParser version - // doesn't even have a corresponding getAnnotations method. - // - When there are no initializers, both systems use similar representations. The - // dimensions line up. - // - When there is an initializer, they differ greatly for multi-dimensional arrays. Javac - // turns an expression like new int[][]{{1, 2}, {3, 4}} into a single NewArray tree with - // type int[] and two initializer elements {1, 2} and {3, 4}. However, for each of the - // sub-initializers, it creates an implicit NewArray tree with a null component type. - // JavaParser keeps the whole expression as one ArrayCreationExpr with multiple dimensions - // and the initializer stored in special ArrayInitializerExpr type. - return null; - } - - @Override - public Void visitNewClass(NewClassTree javacTree, Node javaParserNode) { - ObjectCreationExpr node = castNode(ObjectCreationExpr.class, javaParserNode, javacTree); - processNewClass(javacTree, node); - // When using Java 11 javac, an expression like this.new MyInnerClass() would store "this" - // as the enclosing expression. In Java 8 javac, this would be stored as new - // MyInnerClass(this). So, we only traverse the enclosing expression if present in both. - if (javacTree.getEnclosingExpression() != null && node.getScope().isPresent()) { - javacTree.getEnclosingExpression().accept(this, node.getScope().get()); - } - - javacTree.getIdentifier().accept(this, node.getType()); - if (javacTree.getTypeArguments().isEmpty()) { - assert !node.getTypeArguments().isPresent(); - } else { - assert node.getTypeArguments().isPresent(); - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - // Remove synthetic javac argument. When using Java 11, an expression like this.new - // MyInnerClass() would store "this" as the enclosing expression. In Java 8, this would be - // stored as new MyInnerClass(this). So, for the argument lists to match, we may have to - // remove the first argument. - List javacArgs = new ArrayList<>(javacTree.getArguments()); - if (javacArgs.size() > node.getArguments().size()) { - javacArgs.remove(0); - } - - visitLists(javacArgs, node.getArguments()); - assert (javacTree.getClassBody() != null) == node.getAnonymousClassBody().isPresent(); - if (javacTree.getClassBody() != null) { - visitAnonymousClassBody(javacTree.getClassBody(), node.getAnonymousClassBody().get()); - } - - return null; - } - - @Override - public Void visitOpens(OpensTree javacTree, Node javaParserNode) { - ModuleOpensDirective node = castNode(ModuleOpensDirective.class, javaParserNode, javacTree); - processOpens(javacTree, node); - javacTree.getPackageName().accept(this, node.getName()); - visitLists(javacTree.getModuleNames(), node.getModuleNames()); - return null; - } - - @Override - public Void visitOther(Tree javacTree, Node javaParserNode) { - processOther(javacTree, javaParserNode); - return null; - } - - @Override - public Void visitPackage(PackageTree javacTree, Node javaParserNode) { - PackageDeclaration node = castNode(PackageDeclaration.class, javaParserNode, javacTree); - processPackage(javacTree, node); - // visitLists(javacTree.getAnnotations(), node.getAnnotations()); - javacTree.getPackageName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitParameterizedType(ParameterizedTypeTree javacTree, Node javaParserNode) { - ClassOrInterfaceType node = castNode(ClassOrInterfaceType.class, javaParserNode, javacTree); - processParameterizedType(javacTree, node); - javacTree.getType().accept(this, node); - // TODO: In a parameterized type, will the first branch ever run? - if (javacTree.getTypeArguments().isEmpty()) { - assert !node.getTypeArguments().isPresent() || node.getTypeArguments().get().isEmpty(); - } else { - assert node.getTypeArguments().isPresent(); - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - return null; - } - - @Override - public Void visitParenthesized(ParenthesizedTree javacTree, Node javaParserNode) { - EnclosedExpr node = castNode(EnclosedExpr.class, javaParserNode, javacTree); - processParenthesized(javacTree, node); - javacTree.getExpression().accept(this, node.getInner()); - return null; - } - - @Override - public Void visitPrimitiveType(PrimitiveTypeTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof PrimitiveType) { - processPrimitiveType(javacTree, (PrimitiveType) javaParserNode); - } else if (javaParserNode instanceof VoidType) { - processPrimitiveType(javacTree, (VoidType) javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitProvides(ProvidesTree javacTree, Node javaParserNode) { - ModuleProvidesDirective node = - castNode(ModuleProvidesDirective.class, javaParserNode, javacTree); - processProvides(javacTree, node); - javacTree.getServiceName().accept(this, node.getName()); - visitLists(javacTree.getImplementationNames(), node.getWith()); - return null; - } - - @Override - public Void visitRequires(RequiresTree javacTree, Node javaParserNode) { - ModuleRequiresDirective node = - castNode(ModuleRequiresDirective.class, javaParserNode, javacTree); - processRequires(javacTree, node); - javacTree.getModuleName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitReturn(ReturnTree javacTree, Node javaParserNode) { - ReturnStmt node = castNode(ReturnStmt.class, javaParserNode, javacTree); - processReturn(javacTree, node); - visitOptional(javacTree.getExpression(), node.getExpression()); - - return null; - } - - @Override - public Void visitSwitch(SwitchTree javacTree, Node javaParserNode) { - SwitchStmt node = castNode(SwitchStmt.class, javaParserNode, javacTree); - processSwitch(javacTree, node); - // Switch expressions are always parenthesized in javac but never in JavaParser. - ExpressionTree expression = ((ParenthesizedTree) javacTree.getExpression()).getExpression(); - expression.accept(this, node.getSelector()); - visitLists(javacTree.getCases(), node.getEntries()); - return null; - } - - /** - * Visit a switch expression. - * - * @param javacTree switch expression tree - * @param javaParserNode a JavaParser node - * @return null - */ - public Void visitSwitchExpression17(Tree javacTree, Node javaParserNode) { - SwitchExpr node = castNode(SwitchExpr.class, javaParserNode, javacTree); - processSwitchExpression(javacTree, node); - - // Switch expressions are always parenthesized in javac but never in JavaParser. - ExpressionTree expression = - ((ParenthesizedTree) SwitchExpressionUtils.getExpression(javacTree)) - .getExpression(); - expression.accept(this, node.getSelector()); - - visitLists(SwitchExpressionUtils.getCases(javacTree), node.getEntries()); - return null; - } - - @Override - public Void visitSynchronized(SynchronizedTree javacTree, Node javaParserNode) { - SynchronizedStmt node = castNode(SynchronizedStmt.class, javaParserNode, javacTree); - processSynchronized(javacTree, node); - ((ParenthesizedTree) javacTree.getExpression()) - .getExpression() - .accept(this, node.getExpression()); - javacTree.getBlock().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitThrow(ThrowTree javacTree, Node javaParserNode) { - ThrowStmt node = castNode(ThrowStmt.class, javaParserNode, javacTree); - processThrow(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - return null; - } - - @Override - public Void visitTry(TryTree javacTree, Node javaParserNode) { - TryStmt node = castNode(TryStmt.class, javaParserNode, javacTree); - processTry(javacTree, node); - Iterator javacResources = javacTree.getResources().iterator(); - for (Expression resource : node.getResources()) { - if (resource.isVariableDeclarationExpr()) { - for (VariableDeclarator declarator : - resource.asVariableDeclarationExpr().getVariables()) { - assert javacResources.hasNext(); - javacResources.next().accept(this, declarator); - } - } else { - assert javacResources.hasNext(); - javacResources.next().accept(this, resource); - } - } - - javacTree.getBlock().accept(this, node.getTryBlock()); - visitLists(javacTree.getCatches(), node.getCatchClauses()); - visitOptional(javacTree.getFinallyBlock(), node.getFinallyBlock()); - - return null; - } - - @Override - public Void visitTypeCast(TypeCastTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof MethodReferenceExpr) { - // Work around https://github.com/javaparser/javaparser/issues/3855 - return null; - } - CastExpr node = castNode(CastExpr.class, javaParserNode, javacTree); - processTypeCast(javacTree, node); + } else { + assert javacResources.hasNext(); + javacResources.next().accept(this, resource); + } + } + + javacTree.getBlock().accept(this, node.getTryBlock()); + visitLists(javacTree.getCatches(), node.getCatchClauses()); + visitOptional(javacTree.getFinallyBlock(), node.getFinallyBlock()); + + return null; + } + + @Override + public Void visitTypeCast(TypeCastTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof MethodReferenceExpr) { + // Work around https://github.com/javaparser/javaparser/issues/3855 + return null; + } + CastExpr node = castNode(CastExpr.class, javaParserNode, javacTree); + processTypeCast(javacTree, node); + javacTree.getType().accept(this, node.getType()); + javacTree.getExpression().accept(this, node.getExpression()); + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterTree javacTree, Node javaParserNode) { + TypeParameter node = castNode(TypeParameter.class, javaParserNode, javacTree); + processTypeParameter(javacTree, node); + visitLists(javacTree.getBounds(), node.getTypeBound()); + return null; + } + + @Override + public Void visitUnary(UnaryTree javacTree, Node javaParserNode) { + UnaryExpr node = castNode(UnaryExpr.class, javaParserNode, javacTree); + processUnary(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + return null; + } + + @Override + public Void visitUnionType(UnionTypeTree javacTree, Node javaParserNode) { + UnionType node = castNode(UnionType.class, javaParserNode, javacTree); + processUnionType(javacTree, node); + visitLists(javacTree.getTypeAlternatives(), node.getElements()); + return null; + } + + @Override + public Void visitUses(UsesTree javacTree, Node javaParserNode) { + ModuleUsesDirective node = castNode(ModuleUsesDirective.class, javaParserNode, javacTree); + processUses(javacTree, node); + javacTree.getServiceName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitVariable(VariableTree javacTree, Node javaParserNode) { + // Javac uses the class VariableTree to represent multiple syntactic concepts such as + // variable declarations, parameters, and fields. + if (javaParserNode instanceof VariableDeclarator) { + // JavaParser uses VariableDeclarator as parts of other declaration types like + // VariableDeclarationExpr when multiple variables may be declared. + VariableDeclarator node = (VariableDeclarator) javaParserNode; + processVariable(javacTree, node); + // Don't process the variable type when it's the Java keyword "var". + if (!node.getType().isVarType() + && (!node.getType().isClassOrInterfaceType() + || !node.getType().asClassOrInterfaceType().getName().asString().equals("var"))) { javacTree.getType().accept(this, node.getType()); - javacTree.getExpression().accept(this, node.getExpression()); - return null; - } - - @Override - public Void visitTypeParameter(TypeParameterTree javacTree, Node javaParserNode) { - TypeParameter node = castNode(TypeParameter.class, javaParserNode, javacTree); - processTypeParameter(javacTree, node); - visitLists(javacTree.getBounds(), node.getTypeBound()); - return null; - } - - @Override - public Void visitUnary(UnaryTree javacTree, Node javaParserNode) { - UnaryExpr node = castNode(UnaryExpr.class, javaParserNode, javacTree); - processUnary(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - return null; - } - - @Override - public Void visitUnionType(UnionTypeTree javacTree, Node javaParserNode) { - UnionType node = castNode(UnionType.class, javaParserNode, javacTree); - processUnionType(javacTree, node); - visitLists(javacTree.getTypeAlternatives(), node.getElements()); - return null; - } - - @Override - public Void visitUses(UsesTree javacTree, Node javaParserNode) { - ModuleUsesDirective node = castNode(ModuleUsesDirective.class, javaParserNode, javacTree); - processUses(javacTree, node); - javacTree.getServiceName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitVariable(VariableTree javacTree, Node javaParserNode) { - // Javac uses the class VariableTree to represent multiple syntactic concepts such as - // variable declarations, parameters, and fields. - if (javaParserNode instanceof VariableDeclarator) { - // JavaParser uses VariableDeclarator as parts of other declaration types like - // VariableDeclarationExpr when multiple variables may be declared. - VariableDeclarator node = (VariableDeclarator) javaParserNode; - processVariable(javacTree, node); - // Don't process the variable type when it's the Java keyword "var". - if (!node.getType().isVarType() - && (!node.getType().isClassOrInterfaceType() - || !node.getType() - .asClassOrInterfaceType() - .getName() - .asString() - .equals("var"))) { - javacTree.getType().accept(this, node.getType()); - } - - // The name expression can be null, even when a name exists. - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - visitOptional(javacTree.getInitializer(), node.getInitializer()); - } else if (javaParserNode instanceof Parameter) { - Parameter node = (Parameter) javaParserNode; - processVariable(javacTree, node); - if (node.isVarArgs()) { - ArrayTypeTree arrayType; - // A varargs parameter's type will either be an ArrayTypeTree or an - // AnnotatedType depending on whether it has an annotation. - if (javacTree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { - arrayType = (ArrayTypeTree) javacTree.getType(); - } else { - AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) javacTree.getType(); - arrayType = (ArrayTypeTree) annotatedType.getUnderlyingType(); - } - - arrayType.getType().accept(this, node.getType()); - } else { - // Types for lambda parameters without explicit types don't have JavaParser nodes, - // don't process them. - if (!node.getType().isUnknownType()) { - javacTree.getType().accept(this, node.getType()); - } - } - - // The name expression can be null, even when a name exists. - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - assert javacTree.getInitializer() == null; - } else if (javaParserNode instanceof ReceiverParameter) { - ReceiverParameter node = (ReceiverParameter) javaParserNode; - processVariable(javacTree, node); - javacTree.getType().accept(this, node.getType()); - // The name expression can be null, even when a name exists. - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - assert javacTree.getInitializer() == null; - } else if (javaParserNode instanceof EnumConstantDeclaration) { - // In javac, an enum constant is expanded as a variable declaration initialized to a - // constuctor call. - EnumConstantDeclaration node = (EnumConstantDeclaration) javaParserNode; - processVariable(javacTree, node); - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - assert javacTree.getInitializer().getKind() == Tree.Kind.NEW_CLASS; - NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); - visitLists(constructor.getArguments(), node.getArguments()); - if (constructor.getClassBody() != null) { - visitAnonymousClassBody(constructor.getClassBody(), node.getClassBody()); - } else { - assert node.getClassBody().isEmpty(); - } + } + + // The name expression can be null, even when a name exists. + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + visitOptional(javacTree.getInitializer(), node.getInitializer()); + } else if (javaParserNode instanceof Parameter) { + Parameter node = (Parameter) javaParserNode; + processVariable(javacTree, node); + if (node.isVarArgs()) { + ArrayTypeTree arrayType; + // A varargs parameter's type will either be an ArrayTypeTree or an + // AnnotatedType depending on whether it has an annotation. + if (javacTree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { + arrayType = (ArrayTypeTree) javacTree.getType(); } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitWhileLoop(WhileLoopTree javacTree, Node javaParserNode) { - WhileStmt node = castNode(WhileStmt.class, javaParserNode, javacTree); - processWhileLoop(javacTree, node); - // While loop conditions are always parenthesized in javac but never in JavaParser. - assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; - ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); - condition.accept(this, node.getCondition()); - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitWildcard(WildcardTree javacTree, Node javaParserNode) { - WildcardType node = castNode(WildcardType.class, javaParserNode, javacTree); - processWildcard(javacTree, node); - // In javac, whether the bound is an extends or super clause depends on the kind of the - // tree. - assert (javacTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) - == node.getExtendedType().isPresent(); - assert (javacTree.getKind() == Tree.Kind.SUPER_WILDCARD) == node.getSuperType().isPresent(); - switch (javacTree.getKind()) { - case UNBOUNDED_WILDCARD: - break; - case EXTENDS_WILDCARD: - javacTree.getBound().accept(this, node.getExtendedType().get()); - break; - case SUPER_WILDCARD: - javacTree.getBound().accept(this, node.getSuperType().get()); - break; - default: - throw new BugInCF("Unexpected wildcard kind: %s", javacTree); - } - - return null; - } - - /** - * Visit a YieldTree - * - * @param tree a YieldTree, typed as Tree to be backward-compatible - * @param node a YieldStmt, typed as Node to be backward-compatible - * @return nothing - */ - public Void visitYield17(Tree tree, Node node) { - if (node instanceof YieldStmt) { - YieldStmt yieldStmt = castNode(YieldStmt.class, node, tree); - processYield(tree, yieldStmt); - - YieldUtils.getValue(tree).accept(this, yieldStmt.getExpression()); - return null; - } - // JavaParser does not parse yields correctly: - // https://github.com/javaparser/javaparser/issues/3364 - // So skip yields that aren't matched with a YieldStmt. - return null; - } - - /** - * Process an {@code AnnotationTree} with multiple key-value pairs like {@code @MyAnno(a=5, - * b=10)}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotation( - AnnotationTree javacTree, NormalAnnotationExpr javaParserNode); - - /** - * Process an {@code AnnotationTree} with no arguments like {@code @MyAnno}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotation( - AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode); - - /** - * Process an {@code AnnotationTree} with a single argument like {@code MyAnno(5)}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotation( - AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode); - - /** - * Process an {@code AnnotatedTypeTree}. - * - *

In javac, a type with an annotation is represented as an {@code AnnotatedTypeTree} with a - * nested tree for the base type whereas in JavaParser the annotations are store directly on the - * node for the base type. As a result, the JavaParser base type node will be processed twice, - * once with the {@code AnnotatedTypeTree} and once with the tree for the base type. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode); - - /** - * Process an {@code ArrayAccessTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processArrayAccess( - ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode); - - /** - * Process an {@code ArrayTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode); - - /** - * Process an {@code AssertTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAssert(AssertTree javacTree, AssertStmt javaParserNode); - - /** - * Process an {@code AssignmentTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode); - - /** - * Process a {@code BinaryTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode); - - /** - * Process a {@code BindingPatternTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBindingPattern(Tree javacTree, PatternExpr javaParserNode); - - /** - * Process a {@code BlockTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBlock(BlockTree javacTree, BlockStmt javaParserNode); - - /** - * Process a {@code BreakTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBreak(BreakTree javacTree, BreakStmt javaParserNode); - - /** - * Process a {@code CaseTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCase(CaseTree javacTree, SwitchEntry javaParserNode); - - /** - * Process a {@code CatchTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCatch(CatchTree javacTree, CatchClause javaParserNode); - - /** - * Process a {@code ClassTree} representing an annotation declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode); - - /** - * Process a {@code ClassTree} representing a class or interface declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass( - ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode); - - /** - * Process a {@code ClassTree} representing a record declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass(ClassTree javacTree, RecordDeclaration javaParserNode); - - /** - * Process a {@code ClassTree} representing an enum declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass(ClassTree javacTree, EnumDeclaration javaParserNode); - - /** - * Process a {@code CompilationUnitTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCompilationUnit( - CompilationUnitTree javacTree, CompilationUnit javaParserNode); - - /** - * Process a {@code ConditionalExpressionTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processConditionalExpression( - ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode); - - /** - * Process a {@code ContinueTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode); - - /** - * Process a {@code DoWhileLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode); - - /** - * Process an {@code EmptyStatementTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processEmptyStatement( - EmptyStatementTree javacTree, EmptyStmt javaParserNode); - - /** - * Process an {@code EnhancedForLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processEnhancedForLoop( - EnhancedForLoopTree javacTree, ForEachStmt javaParserNode); - - /** - * Process an {@code ExportsTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processExports( - ExportsTree javacTree, ModuleExportsDirective javaParserNode); - - /** - * Process an {@code ExpressionStatementTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processExpressionStatemen( - ExpressionStatementTree javacTree, ExpressionStmt javaParserNode); - - /** - * Process a {@code ForLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a class or interface type. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier( - IdentifierTree javacTree, ClassOrInterfaceType javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a name that may contain dots. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, Name javaParserNode); - - /** - * Process an {@code IdentifierTree} representing an expression that evaluates to the value of a - * variable. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a name without dots. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a {@code super} expression like the {@code - * super} in {@code super.myMethod()} or {@code MyClass.super.myMethod()}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a {@code this} expression like the {@code - * this} in {@code MyClass = this}, {@code this.myMethod()}, or {@code MyClass.this.myMethod()}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode); - - /** - * Process an {@code IfTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIf(IfTree javacTree, IfStmt javaParserNode); - - /** - * Process an {@code ImportTree}. - * - *

Wildcards are stored differently between the two. In a statement like {@code import a.*;}, - * the name is stored as a {@code MemberSelectTree} with {@code a} and {@code *}. In JavaParser - * this is just stored as {@code a} but with a method that returns whether it has a wildcard. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processImport(ImportTree javacTree, ImportDeclaration javaParserNode); - - /** - * Process an {@code InstanceOfTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode); - - /** - * Process an {@code IntersectionType}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIntersectionType( - IntersectionTypeTree javacTree, IntersectionType javaParserNode); - - /** - * Process a {@code LabeledStatement}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLabeledStatement( - LabeledStatementTree javacTree, LabeledStmt javaParserNode); - - /** - * Process a {@code LambdaExpressionTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLambdaExpression( - LambdaExpressionTree javacTree, LambdaExpr javaParserNode); - - /** - * Process a {@code LiteralTree} for a String literal defined using concatenation. - * - *

For an expression like {@code "a" + "b"}, javac stores a single String literal {@code - * "ab"} but JavaParser stores it as an operation with two operands. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode); - - /** - * Process a {@code LiteralTree} for a literal expression prefixed with {@code +} or {@code -} - * like {@code +5} or {@code -2}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode); - - /** - * Process a {@code LiteralTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode); - - /** - * Process a {@code MemberReferenceTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberReference( - MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a class expression like {@code MyClass.class}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a type with a name containing dots, like {@code - * mypackage.MyClass}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect( - MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a field access expression like {@code myObj.myField}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect( - MemberSelectTree javacTree, FieldAccessExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a name that contains dots. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a this expression with a class like {@code - * MyClass.this}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a super expression with a class like {@code - * super.MyClass}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode); - - /** - * Process a {@code MethodTree} representing a regular method declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode); - - /** - * Process a {@code MethodTree} representing a constructor declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode); - - /** - * Process a {@code MethodTree} representing a compact constructor declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod( - MethodTree javacTree, CompactConstructorDeclaration javaParserNode); - - /** - * Process a {@code MethodTree} representing a value field for an annotation. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod( - MethodTree javacTree, AnnotationMemberDeclaration javaParserNode); - - /** - * Process a {@code MethodInvocationTree} representing a constructor invocation. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethodInvocation( - MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode); - - /** - * Process a {@code MethodInvocationTree} representing a regular method invocation. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethodInvocation( - MethodInvocationTree javacTree, MethodCallExpr javaParserNode); - - /** - * Process a {@code ModuleTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode); - - /** - * Process a {@code NewClassTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode); - - /** - * Process an {@code OpensTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode); - - /** - * Process a {@code Tree} that isn't an instance of any specific tree class. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processOther(Tree javacTree, Node javaParserNode); - - /** - * Process a {@code PackageTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode); - - /** - * Process a {@code ParameterizedTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processParameterizedType( - ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode); - - /** - * Process a {@code ParenthesizedTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processParenthesized( - ParenthesizedTree javacTree, EnclosedExpr javaParserNode); - - /** - * Process a {@code PrimitiveTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processPrimitiveType( - PrimitiveTypeTree javacTree, PrimitiveType javaParserNode); - - /** - * Process a {@code PrimitiveTypeTree} representing a void type. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode); - - /** - * Process a {@code ProvidesTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processProvides( - ProvidesTree javacTree, ModuleProvidesDirective javaParserNode); - - /** - * Process a {@code RequiresTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processRequires( - RequiresTree javacTree, ModuleRequiresDirective javaParserNode); - - /** - * Process a {@code RetrunTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode); - - /** - * Process a {@code SwitchTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode); - - /** - * Process a {@code SwitchExpressionTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode); - - /** - * Process a {@code SynchronizedTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processSynchronized( - SynchronizedTree javacTree, SynchronizedStmt javaParserNode); - - /** - * Process a {@code ThrowTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode); - - /** - * Process a {@code TryTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processTry(TryTree javacTree, TryStmt javaParserNode); - - /** - * Process a {@code TypeCastTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode); - - /** - * Process a {@code TypeParameterTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processTypeParameter( - TypeParameterTree javacTree, TypeParameter javaParserNode); - - /** - * Process a {@code UnaryTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode); - - /** - * Process a {@code UnionTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode); - - /** - * Process a {@code UsesTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode); - - /** - * Process a {@code VariableTree} representing an enum constant declaration. In an enum like - * {@code enum MyEnum { MY_CONSTANT }}, javac expands {@code MY_CONSTANT} as a constant - * variable. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable( - VariableTree javacTree, EnumConstantDeclaration javaParserNode); - - /** - * Process a {@code VariableTree} representing a parameter to a method or constructor. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable(VariableTree javacTree, Parameter javaParserNode); - - /** - * Process a {@code VariableTree} representing the receiver parameter of a method. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode); - - /** - * Process a {@code VariableTree} representing a regular variable declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode); - - /** - * Process a {@code WhileLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode); - - /** - * Process a {@code WhileLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processWildcard(WildcardTree javacTree, WildcardType javaParserNode); - - /** - * Process a {@code YieldTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding Javaparser node - */ - public abstract void processYield(Tree javacTree, YieldStmt javaParserNode); - - /** - * Process a {@code CompoundAssignmentTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCompoundAssignment( - CompoundAssignmentTree javacTree, AssignExpr javaParserNode); - - /** - * Given a list of javac trees and a list of JavaParser nodes, where the elements of the lists - * correspond to each other, visit each javac tree along with its corresponding JavaParser node. - * - *

The two lists must be of the same length and elements at corresponding positions must - * match. - * - * @param javacTrees list of trees - * @param javaParserNodes list of corresponding JavaParser nodes - */ - protected void visitLists( - List javacTrees, List javaParserNodes) { - if (javacTrees.size() != javaParserNodes.size()) { - throw new BugInCF( - "%s.visitLists(%s [size %d], %s [size %d])", - this.getClass().getCanonicalName(), - javacTrees, - javacTrees.size(), - javaParserNodes, - javaParserNodes.size()); - } - Iterator nodeIter = javaParserNodes.iterator(); - for (Tree tree : javacTrees) { - tree.accept(this, nodeIter.next()); - } - } - - /** - * Visit an optional syntax construct. Iff the javac tree is non-null, the JavaParser optional - * is present. - * - * @param javacTree a javac tree or null - * @param javaParserNode an optional JavaParser node, which might not be present - */ - @SuppressWarnings("optional:optional.parameter") // interface with JavaParser - protected void visitOptional( - @Nullable Tree javacTree, Optional javaParserNode) { - assert javacTree != null == javaParserNode.isPresent() - : String.format("visitOptional(%s, %s)", javacTree, javaParserNode); - if (javacTree != null) { - javacTree.accept(this, javaParserNode.get()); - } - } - - /** - * Cast {@code javaParserNode} to type {@code type} and return it. - * - * @param the type of {@code type} - * @param type the type to cast to - * @param javaParserNode the object to cast - * @param javacTree the javac tree that corresponds to {@code javaParserNode}; used only for - * error reporting - * @return javaParserNode, casted to {@code type} - */ - public T castNode(Class type, Node javaParserNode, Tree javacTree) { - if (type.isInstance(javaParserNode)) { - return type.cast(javaParserNode); + AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) javacTree.getType(); + arrayType = (ArrayTypeTree) annotatedType.getUnderlyingType(); } - throwUnexpectedNodeType(javacTree, javaParserNode, type); - throw new BugInCF("unreachable"); - } - - /** - * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other, - * throws an exception indicating that the visiting process failed for those nodes. - * - * @param javacTree a tree that was visited - * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but - * which was not of the correct type for that tree - * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the - * visitng process at {@code javacTree} and {@code javaParserNode} - */ - private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode) { - throw new BugInCF( - "desynced trees: %s [%s], %s [%s] %s", - javacTree, - javacTree.getClass(), - javaParserNode, - javaParserNode.getClass(), - // There is also XmlPrinter. - new YamlPrinter(true).output(javaParserNode)); - } - /** - * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other, - * throws an exception indicating that the visiting process failed for those nodes because - * {@code javaParserNode} was expected to be of type {@code expectedType}. - * - * @param javacTree a tree that was visited - * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but - * which was not of the correct type for that tree - * @param expectedType the type {@code javaParserNode} was expected to be based on {@code - * javacTree} - * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the - * visitng process at {@code javacTree} and {@code javaParserNode} - */ - private void throwUnexpectedNodeType( - Tree javacTree, Node javaParserNode, Class expectedType) { - throw new BugInCF( - "desynced trees: %s [%s], %s [%s (expected %s)] %s", - javacTree, - javacTree.getClass(), - javaParserNode, - javaParserNode.getClass(), - expectedType, - new YamlPrinter(true).output(javaParserNode)); - } - - /** - * The default action for this visitor. This is inherited from SimpleTreeVisitor, but is only - * called for those methods which do not have an override of the visitXXX method in this class. - * Ultimately, those are the methods added post Java 11, such as for switch-expressions. - * - * @param tree the Javac tree - * @param node the Javaparser node - * @return nothing - */ - @Override - protected Void defaultAction(Tree tree, Node node) { - // Features added between JDK 12 and JDK 17 inclusive. - // Must use String comparison to support compiling on JDK 11 and earlier: - switch (tree.getKind().name()) { - case "BINDING_PATTERN": - return visitBindingPattern17(tree, node); - case "SWITCH_EXPRESSION": - return visitSwitchExpression17(tree, node); - case "YIELD": - return visitYield17(tree, node); + arrayType.getType().accept(this, node.getType()); + } else { + // Types for lambda parameters without explicit types don't have JavaParser nodes, + // don't process them. + if (!node.getType().isUnknownType()) { + javacTree.getType().accept(this, node.getType()); } - - return super.defaultAction(tree, node); - } + } + + // The name expression can be null, even when a name exists. + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + assert javacTree.getInitializer() == null; + } else if (javaParserNode instanceof ReceiverParameter) { + ReceiverParameter node = (ReceiverParameter) javaParserNode; + processVariable(javacTree, node); + javacTree.getType().accept(this, node.getType()); + // The name expression can be null, even when a name exists. + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + assert javacTree.getInitializer() == null; + } else if (javaParserNode instanceof EnumConstantDeclaration) { + // In javac, an enum constant is expanded as a variable declaration initialized to a + // constuctor call. + EnumConstantDeclaration node = (EnumConstantDeclaration) javaParserNode; + processVariable(javacTree, node); + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + assert javacTree.getInitializer().getKind() == Tree.Kind.NEW_CLASS; + NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); + visitLists(constructor.getArguments(), node.getArguments()); + if (constructor.getClassBody() != null) { + visitAnonymousClassBody(constructor.getClassBody(), node.getClassBody()); + } else { + assert node.getClassBody().isEmpty(); + } + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitWhileLoop(WhileLoopTree javacTree, Node javaParserNode) { + WhileStmt node = castNode(WhileStmt.class, javaParserNode, javacTree); + processWhileLoop(javacTree, node); + // While loop conditions are always parenthesized in javac but never in JavaParser. + assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; + ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); + condition.accept(this, node.getCondition()); + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitWildcard(WildcardTree javacTree, Node javaParserNode) { + WildcardType node = castNode(WildcardType.class, javaParserNode, javacTree); + processWildcard(javacTree, node); + // In javac, whether the bound is an extends or super clause depends on the kind of the + // tree. + assert (javacTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) + == node.getExtendedType().isPresent(); + assert (javacTree.getKind() == Tree.Kind.SUPER_WILDCARD) == node.getSuperType().isPresent(); + switch (javacTree.getKind()) { + case UNBOUNDED_WILDCARD: + break; + case EXTENDS_WILDCARD: + javacTree.getBound().accept(this, node.getExtendedType().get()); + break; + case SUPER_WILDCARD: + javacTree.getBound().accept(this, node.getSuperType().get()); + break; + default: + throw new BugInCF("Unexpected wildcard kind: %s", javacTree); + } + + return null; + } + + /** + * Visit a YieldTree + * + * @param tree a YieldTree, typed as Tree to be backward-compatible + * @param node a YieldStmt, typed as Node to be backward-compatible + * @return nothing + */ + public Void visitYield17(Tree tree, Node node) { + if (node instanceof YieldStmt) { + YieldStmt yieldStmt = castNode(YieldStmt.class, node, tree); + processYield(tree, yieldStmt); + + YieldUtils.getValue(tree).accept(this, yieldStmt.getExpression()); + return null; + } + // JavaParser does not parse yields correctly: + // https://github.com/javaparser/javaparser/issues/3364 + // So skip yields that aren't matched with a YieldStmt. + return null; + } + + /** + * Process an {@code AnnotationTree} with multiple key-value pairs like {@code @MyAnno(a=5, + * b=10)}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotation( + AnnotationTree javacTree, NormalAnnotationExpr javaParserNode); + + /** + * Process an {@code AnnotationTree} with no arguments like {@code @MyAnno}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotation( + AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode); + + /** + * Process an {@code AnnotationTree} with a single argument like {@code MyAnno(5)}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotation( + AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode); + + /** + * Process an {@code AnnotatedTypeTree}. + * + *

In javac, a type with an annotation is represented as an {@code AnnotatedTypeTree} with a + * nested tree for the base type whereas in JavaParser the annotations are store directly on the + * node for the base type. As a result, the JavaParser base type node will be processed twice, + * once with the {@code AnnotatedTypeTree} and once with the tree for the base type. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode); + + /** + * Process an {@code ArrayAccessTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processArrayAccess( + ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode); + + /** + * Process an {@code ArrayTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode); + + /** + * Process an {@code AssertTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAssert(AssertTree javacTree, AssertStmt javaParserNode); + + /** + * Process an {@code AssignmentTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode); + + /** + * Process a {@code BinaryTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode); + + /** + * Process a {@code BindingPatternTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBindingPattern(Tree javacTree, PatternExpr javaParserNode); + + /** + * Process a {@code BlockTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBlock(BlockTree javacTree, BlockStmt javaParserNode); + + /** + * Process a {@code BreakTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBreak(BreakTree javacTree, BreakStmt javaParserNode); + + /** + * Process a {@code CaseTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCase(CaseTree javacTree, SwitchEntry javaParserNode); + + /** + * Process a {@code CatchTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCatch(CatchTree javacTree, CatchClause javaParserNode); + + /** + * Process a {@code ClassTree} representing an annotation declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode); + + /** + * Process a {@code ClassTree} representing a class or interface declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass( + ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode); + + /** + * Process a {@code ClassTree} representing a record declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass(ClassTree javacTree, RecordDeclaration javaParserNode); + + /** + * Process a {@code ClassTree} representing an enum declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass(ClassTree javacTree, EnumDeclaration javaParserNode); + + /** + * Process a {@code CompilationUnitTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCompilationUnit( + CompilationUnitTree javacTree, CompilationUnit javaParserNode); + + /** + * Process a {@code ConditionalExpressionTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processConditionalExpression( + ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode); + + /** + * Process a {@code ContinueTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode); + + /** + * Process a {@code DoWhileLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode); + + /** + * Process an {@code EmptyStatementTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processEmptyStatement( + EmptyStatementTree javacTree, EmptyStmt javaParserNode); + + /** + * Process an {@code EnhancedForLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processEnhancedForLoop( + EnhancedForLoopTree javacTree, ForEachStmt javaParserNode); + + /** + * Process an {@code ExportsTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode); + + /** + * Process an {@code ExpressionStatementTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processExpressionStatemen( + ExpressionStatementTree javacTree, ExpressionStmt javaParserNode); + + /** + * Process a {@code ForLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a class or interface type. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier( + IdentifierTree javacTree, ClassOrInterfaceType javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a name that may contain dots. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, Name javaParserNode); + + /** + * Process an {@code IdentifierTree} representing an expression that evaluates to the value of a + * variable. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a name without dots. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a {@code super} expression like the {@code + * super} in {@code super.myMethod()} or {@code MyClass.super.myMethod()}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a {@code this} expression like the {@code this} + * in {@code MyClass = this}, {@code this.myMethod()}, or {@code MyClass.this.myMethod()}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode); + + /** + * Process an {@code IfTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIf(IfTree javacTree, IfStmt javaParserNode); + + /** + * Process an {@code ImportTree}. + * + *

Wildcards are stored differently between the two. In a statement like {@code import a.*;}, + * the name is stored as a {@code MemberSelectTree} with {@code a} and {@code *}. In JavaParser + * this is just stored as {@code a} but with a method that returns whether it has a wildcard. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processImport(ImportTree javacTree, ImportDeclaration javaParserNode); + + /** + * Process an {@code InstanceOfTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode); + + /** + * Process an {@code IntersectionType}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIntersectionType( + IntersectionTypeTree javacTree, IntersectionType javaParserNode); + + /** + * Process a {@code LabeledStatement}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLabeledStatement( + LabeledStatementTree javacTree, LabeledStmt javaParserNode); + + /** + * Process a {@code LambdaExpressionTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLambdaExpression( + LambdaExpressionTree javacTree, LambdaExpr javaParserNode); + + /** + * Process a {@code LiteralTree} for a String literal defined using concatenation. + * + *

For an expression like {@code "a" + "b"}, javac stores a single String literal {@code "ab"} + * but JavaParser stores it as an operation with two operands. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode); + + /** + * Process a {@code LiteralTree} for a literal expression prefixed with {@code +} or {@code -} + * like {@code +5} or {@code -2}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode); + + /** + * Process a {@code LiteralTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode); + + /** + * Process a {@code MemberReferenceTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberReference( + MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a class expression like {@code MyClass.class}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a type with a name containing dots, like {@code + * mypackage.MyClass}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect( + MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a field access expression like {@code myObj.myField}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect( + MemberSelectTree javacTree, FieldAccessExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a name that contains dots. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a this expression with a class like {@code + * MyClass.this}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a super expression with a class like {@code + * super.MyClass}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode); + + /** + * Process a {@code MethodTree} representing a regular method declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode); + + /** + * Process a {@code MethodTree} representing a constructor declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode); + + /** + * Process a {@code MethodTree} representing a compact constructor declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod( + MethodTree javacTree, CompactConstructorDeclaration javaParserNode); + + /** + * Process a {@code MethodTree} representing a value field for an annotation. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod( + MethodTree javacTree, AnnotationMemberDeclaration javaParserNode); + + /** + * Process a {@code MethodInvocationTree} representing a constructor invocation. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethodInvocation( + MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode); + + /** + * Process a {@code MethodInvocationTree} representing a regular method invocation. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethodInvocation( + MethodInvocationTree javacTree, MethodCallExpr javaParserNode); + + /** + * Process a {@code ModuleTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode); + + /** + * Process a {@code NewClassTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode); + + /** + * Process an {@code OpensTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode); + + /** + * Process a {@code Tree} that isn't an instance of any specific tree class. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processOther(Tree javacTree, Node javaParserNode); + + /** + * Process a {@code PackageTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode); + + /** + * Process a {@code ParameterizedTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processParameterizedType( + ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode); + + /** + * Process a {@code ParenthesizedTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processParenthesized( + ParenthesizedTree javacTree, EnclosedExpr javaParserNode); + + /** + * Process a {@code PrimitiveTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processPrimitiveType( + PrimitiveTypeTree javacTree, PrimitiveType javaParserNode); + + /** + * Process a {@code PrimitiveTypeTree} representing a void type. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode); + + /** + * Process a {@code ProvidesTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processProvides( + ProvidesTree javacTree, ModuleProvidesDirective javaParserNode); + + /** + * Process a {@code RequiresTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processRequires( + RequiresTree javacTree, ModuleRequiresDirective javaParserNode); + + /** + * Process a {@code RetrunTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode); + + /** + * Process a {@code SwitchTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode); + + /** + * Process a {@code SwitchExpressionTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode); + + /** + * Process a {@code SynchronizedTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processSynchronized( + SynchronizedTree javacTree, SynchronizedStmt javaParserNode); + + /** + * Process a {@code ThrowTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode); + + /** + * Process a {@code TryTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processTry(TryTree javacTree, TryStmt javaParserNode); + + /** + * Process a {@code TypeCastTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode); + + /** + * Process a {@code TypeParameterTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processTypeParameter( + TypeParameterTree javacTree, TypeParameter javaParserNode); + + /** + * Process a {@code UnaryTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode); + + /** + * Process a {@code UnionTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode); + + /** + * Process a {@code UsesTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode); + + /** + * Process a {@code VariableTree} representing an enum constant declaration. In an enum like + * {@code enum MyEnum { MY_CONSTANT }}, javac expands {@code MY_CONSTANT} as a constant variable. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable( + VariableTree javacTree, EnumConstantDeclaration javaParserNode); + + /** + * Process a {@code VariableTree} representing a parameter to a method or constructor. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable(VariableTree javacTree, Parameter javaParserNode); + + /** + * Process a {@code VariableTree} representing the receiver parameter of a method. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode); + + /** + * Process a {@code VariableTree} representing a regular variable declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode); + + /** + * Process a {@code WhileLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode); + + /** + * Process a {@code WhileLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processWildcard(WildcardTree javacTree, WildcardType javaParserNode); + + /** + * Process a {@code YieldTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding Javaparser node + */ + public abstract void processYield(Tree javacTree, YieldStmt javaParserNode); + + /** + * Process a {@code CompoundAssignmentTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCompoundAssignment( + CompoundAssignmentTree javacTree, AssignExpr javaParserNode); + + /** + * Given a list of javac trees and a list of JavaParser nodes, where the elements of the lists + * correspond to each other, visit each javac tree along with its corresponding JavaParser node. + * + *

The two lists must be of the same length and elements at corresponding positions must match. + * + * @param javacTrees list of trees + * @param javaParserNodes list of corresponding JavaParser nodes + */ + protected void visitLists(List javacTrees, List javaParserNodes) { + if (javacTrees.size() != javaParserNodes.size()) { + throw new BugInCF( + "%s.visitLists(%s [size %d], %s [size %d])", + this.getClass().getCanonicalName(), + javacTrees, + javacTrees.size(), + javaParserNodes, + javaParserNodes.size()); + } + Iterator nodeIter = javaParserNodes.iterator(); + for (Tree tree : javacTrees) { + tree.accept(this, nodeIter.next()); + } + } + + /** + * Visit an optional syntax construct. Iff the javac tree is non-null, the JavaParser optional is + * present. + * + * @param javacTree a javac tree or null + * @param javaParserNode an optional JavaParser node, which might not be present + */ + @SuppressWarnings("optional:optional.parameter") // interface with JavaParser + protected void visitOptional(@Nullable Tree javacTree, Optional javaParserNode) { + assert javacTree != null == javaParserNode.isPresent() + : String.format("visitOptional(%s, %s)", javacTree, javaParserNode); + if (javacTree != null) { + javacTree.accept(this, javaParserNode.get()); + } + } + + /** + * Cast {@code javaParserNode} to type {@code type} and return it. + * + * @param the type of {@code type} + * @param type the type to cast to + * @param javaParserNode the object to cast + * @param javacTree the javac tree that corresponds to {@code javaParserNode}; used only for error + * reporting + * @return javaParserNode, casted to {@code type} + */ + public T castNode(Class type, Node javaParserNode, Tree javacTree) { + if (type.isInstance(javaParserNode)) { + return type.cast(javaParserNode); + } + throwUnexpectedNodeType(javacTree, javaParserNode, type); + throw new BugInCF("unreachable"); + } + + /** + * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other, + * throws an exception indicating that the visiting process failed for those nodes. + * + * @param javacTree a tree that was visited + * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but which + * was not of the correct type for that tree + * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the + * visitng process at {@code javacTree} and {@code javaParserNode} + */ + private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode) { + throw new BugInCF( + "desynced trees: %s [%s], %s [%s] %s", + javacTree, + javacTree.getClass(), + javaParserNode, + javaParserNode.getClass(), + // There is also XmlPrinter. + new YamlPrinter(true).output(javaParserNode)); + } + + /** + * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other, + * throws an exception indicating that the visiting process failed for those nodes because {@code + * javaParserNode} was expected to be of type {@code expectedType}. + * + * @param javacTree a tree that was visited + * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but which + * was not of the correct type for that tree + * @param expectedType the type {@code javaParserNode} was expected to be based on {@code + * javacTree} + * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the + * visitng process at {@code javacTree} and {@code javaParserNode} + */ + private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode, Class expectedType) { + throw new BugInCF( + "desynced trees: %s [%s], %s [%s (expected %s)] %s", + javacTree, + javacTree.getClass(), + javaParserNode, + javaParserNode.getClass(), + expectedType, + new YamlPrinter(true).output(javaParserNode)); + } + + /** + * The default action for this visitor. This is inherited from SimpleTreeVisitor, but is only + * called for those methods which do not have an override of the visitXXX method in this class. + * Ultimately, those are the methods added post Java 11, such as for switch-expressions. + * + * @param tree the Javac tree + * @param node the Javaparser node + * @return nothing + */ + @Override + protected Void defaultAction(Tree tree, Node node) { + // Features added between JDK 12 and JDK 17 inclusive. + // Must use String comparison to support compiling on JDK 11 and earlier: + switch (tree.getKind().name()) { + case "BINDING_PATTERN": + return visitBindingPattern17(tree, node); + case "SWITCH_EXPRESSION": + return visitSwitchExpression17(tree, node); + case "YIELD": + return visitYield17(tree, node); + } + + return super.defaultAction(tree, node); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java index eb221e216be..cf3007c1235 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java @@ -141,448 +141,446 @@ *

To use this class, override {@code defaultJointAction}. */ public abstract class JointVisitorWithDefaultAction extends JointJavacJavaParserVisitor { - /** - * Action performed on each javac tree and JavaParser node pair. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void defaultJointAction(Tree javacTree, Node javaParserNode); - - @Override - public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAnnotation( - AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCatch(CatchTree javacTree, CatchClause javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCompilationUnit( - CompilationUnitTree javacTree, CompilationUnit javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processConditionalExpression( - ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processExpressionStatemen( - ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIf(IfTree javacTree, IfStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIntersectionType( - IntersectionTypeTree javacTree, IntersectionType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLabeledStatement( - LabeledStatementTree javacTree, LabeledStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberReference( - MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect( - MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, MethodCallExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processOther(Tree javacTree, Node javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processParameterizedType( - ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processTry(TryTree javacTree, TryStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, Parameter javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processYield(Tree javacTree, YieldStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCompoundAssignment( - CompoundAssignmentTree javacTree, AssignExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } + /** + * Action performed on each javac tree and JavaParser node pair. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void defaultJointAction(Tree javacTree, Node javaParserNode); + + @Override + public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAnnotation( + AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCatch(CatchTree javacTree, CatchClause javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCompilationUnit( + CompilationUnitTree javacTree, CompilationUnit javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processConditionalExpression( + ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processExpressionStatemen( + ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIf(IfTree javacTree, IfStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIntersectionType( + IntersectionTypeTree javacTree, IntersectionType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLabeledStatement(LabeledStatementTree javacTree, LabeledStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberReference( + MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, MethodCallExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processOther(Tree javacTree, Node javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processParameterizedType( + ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processTry(TryTree javacTree, TryStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, Parameter javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processYield(Tree javacTree, YieldStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCompoundAssignment( + CompoundAssignmentTree javacTree, AssignExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java b/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java index 82eece358cf..fec65b35635 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java @@ -61,7 +61,6 @@ import com.sun.source.tree.WhileLoopTree; import com.sun.source.tree.WildcardTree; import com.sun.source.util.TreeScanner; - import org.checkerframework.javacutil.SystemUtil; /** @@ -70,425 +69,425 @@ */ public abstract class TreeScannerWithDefaults extends TreeScanner { - /** - * Action performed on each visited tree. - * - * @param tree tree to perform action on - */ - public abstract void defaultAction(Tree tree); - - // TODO: use JCP to add version-specific behavior - @Override - public Void scan(Tree tree, Void unused) { - if (tree != null && SystemUtil.jreVersion >= 14) { - switch (tree.getKind().name()) { - case "SWITCH_EXPRESSION": - visitSwitchExpression17(tree, unused); - return null; - case "YIELD": - visitYield17(tree, unused); - return null; - case "BINDING_PATTERN": - visitBindingPattern17(tree, unused); - return null; - } - } - return super.scan(tree, unused); - } - - @Override - public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { - defaultAction(tree); - return super.visitAnnotatedType(tree, p); - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - defaultAction(tree); - return super.visitAnnotation(tree, p); - } - - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - defaultAction(tree); - return super.visitArrayAccess(tree, p); - } - - @Override - public Void visitArrayType(ArrayTypeTree tree, Void p) { - defaultAction(tree); - return super.visitArrayType(tree, p); - } - - @Override - public Void visitAssert(AssertTree tree, Void p) { - defaultAction(tree); - return super.visitAssert(tree, p); - } - - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - defaultAction(tree); - return super.visitAssignment(tree, p); - } - - @Override - public Void visitBinary(BinaryTree tree, Void p) { - defaultAction(tree); - return super.visitBinary(tree, p); - } - - /** - * Visit a binding pattern tree. - * - * @param tree a binding pattern tree - * @param p null - * @return null - */ - public Void visitBindingPattern17(Tree tree, Void p) { - defaultAction(tree); - return super.scan(tree, p); - } - - @Override - public Void visitBlock(BlockTree tree, Void p) { - defaultAction(tree); - return super.visitBlock(tree, p); - } - - @Override - public Void visitBreak(BreakTree tree, Void p) { - defaultAction(tree); - return super.visitBreak(tree, p); - } - - @Override - public Void visitCase(CaseTree tree, Void p) { - defaultAction(tree); - return super.visitCase(tree, p); - } - - @Override - public Void visitCatch(CatchTree tree, Void p) { - defaultAction(tree); - return super.visitCatch(tree, p); - } - - @Override - public Void visitClass(ClassTree tree, Void p) { - defaultAction(tree); - return super.visitClass(tree, p); - } - - @Override - public Void visitCompilationUnit(CompilationUnitTree tree, Void p) { - defaultAction(tree); - return super.visitCompilationUnit(tree, p); - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - defaultAction(tree); - return super.visitCompoundAssignment(tree, p); - } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - defaultAction(tree); - return super.visitConditionalExpression(tree, p); - } - - @Override - public Void visitContinue(ContinueTree tree, Void p) { - defaultAction(tree); - return super.visitContinue(tree, p); - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - defaultAction(tree); - return super.visitDoWhileLoop(tree, p); - } - - @Override - public Void visitEmptyStatement(EmptyStatementTree tree, Void p) { - defaultAction(tree); - return super.visitEmptyStatement(tree, p); - } - - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - defaultAction(tree); - return super.visitEnhancedForLoop(tree, p); - } - - @Override - public Void visitErroneous(ErroneousTree tree, Void p) { - defaultAction(tree); - return super.visitErroneous(tree, p); - } - - @Override - public Void visitExports(ExportsTree tree, Void p) { - defaultAction(tree); - return super.visitExports(tree, p); - } - - @Override - public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { - defaultAction(tree); - return super.visitExpressionStatement(tree, p); - } - - @Override - public Void visitForLoop(ForLoopTree tree, Void p) { - defaultAction(tree); - return super.visitForLoop(tree, p); - } - - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - defaultAction(tree); - return super.visitIdentifier(tree, p); - } - - @Override - public Void visitIf(IfTree tree, Void p) { - defaultAction(tree); - return super.visitIf(tree, p); - } - - @Override - public Void visitImport(ImportTree tree, Void p) { - defaultAction(tree); - return super.visitImport(tree, p); - } - - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - defaultAction(tree); - return super.visitInstanceOf(tree, p); - } - - @Override - public Void visitIntersectionType(IntersectionTypeTree tree, Void p) { - defaultAction(tree); - return super.visitIntersectionType(tree, p); - } - - @Override - public Void visitLabeledStatement(LabeledStatementTree tree, Void p) { - defaultAction(tree); - return super.visitLabeledStatement(tree, p); - } - - @Override - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - defaultAction(tree); - return super.visitLambdaExpression(tree, p); - } - - @Override - public Void visitLiteral(LiteralTree tree, Void p) { - defaultAction(tree); - return super.visitLiteral(tree, p); - } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void p) { - defaultAction(tree); - return super.visitMemberReference(tree, p); - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - defaultAction(tree); - return super.visitMemberSelect(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - defaultAction(tree); - return super.visitMethod(tree, p); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - defaultAction(tree); - return super.visitMethodInvocation(tree, p); - } - - @Override - public Void visitModifiers(ModifiersTree tree, Void p) { - defaultAction(tree); - return super.visitModifiers(tree, p); - } - - @Override - public Void visitModule(ModuleTree tree, Void p) { - defaultAction(tree); - return super.visitModule(tree, p); - } - - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - defaultAction(tree); - return super.visitNewArray(tree, p); - } - - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - defaultAction(tree); - return super.visitNewClass(tree, p); - } - - @Override - public Void visitOpens(OpensTree tree, Void p) { - defaultAction(tree); - return super.visitOpens(tree, p); - } - - @Override - public Void visitOther(Tree tree, Void p) { - defaultAction(tree); - return super.visitOther(tree, p); - } - - @Override - public Void visitPackage(PackageTree tree, Void p) { - defaultAction(tree); - return super.visitPackage(tree, p); - } - - @Override - public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { - defaultAction(tree); - return super.visitParameterizedType(tree, p); - } - - @Override - public Void visitParenthesized(ParenthesizedTree tree, Void p) { - defaultAction(tree); - return super.visitParenthesized(tree, p); - } - - @Override - public Void visitPrimitiveType(PrimitiveTypeTree tree, Void p) { - defaultAction(tree); - return super.visitPrimitiveType(tree, p); - } - - @Override - public Void visitProvides(ProvidesTree tree, Void p) { - defaultAction(tree); - return super.visitProvides(tree, p); - } - - @Override - public Void visitRequires(RequiresTree tree, Void p) { - defaultAction(tree); - return super.visitRequires(tree, p); - } - - @Override - public Void visitReturn(ReturnTree tree, Void p) { - defaultAction(tree); - return super.visitReturn(tree, p); - } - - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - defaultAction(tree); - return super.visitSwitch(tree, p); - } - - /** - * Visit a switch expression tree. - * - * @param tree switch expression tree - * @param p null - * @return null - */ - public Void visitSwitchExpression17(Tree tree, Void p) { - defaultAction(tree); - return super.scan(tree, p); - } - - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - defaultAction(tree); - return super.visitSynchronized(tree, p); - } - - @Override - public Void visitThrow(ThrowTree tree, Void p) { - defaultAction(tree); - return super.visitThrow(tree, p); - } - - @Override - public Void visitTry(TryTree tree, Void p) { - defaultAction(tree); - return super.visitTry(tree, p); - } - - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - defaultAction(tree); - return super.visitTypeCast(tree, p); - } - - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void p) { - defaultAction(tree); - return super.visitTypeParameter(tree, p); - } - - @Override - public Void visitUnary(UnaryTree tree, Void p) { - defaultAction(tree); - return super.visitUnary(tree, p); - } - - @Override - public Void visitUnionType(UnionTypeTree tree, Void p) { - defaultAction(tree); - return super.visitUnionType(tree, p); - } - - @Override - public Void visitUses(UsesTree tree, Void p) { - defaultAction(tree); - return super.visitUses(tree, p); - } - - @Override - public Void visitVariable(VariableTree tree, Void p) { - defaultAction(tree); - return super.visitVariable(tree, p); - } - - @Override - public Void visitWhileLoop(WhileLoopTree tree, Void p) { - defaultAction(tree); - return super.visitWhileLoop(tree, p); - } - - @Override - public Void visitWildcard(WildcardTree tree, Void p) { - defaultAction(tree); - return super.visitWildcard(tree, p); - } - - /** - * Visit a yield tree. - * - * @param tree a yield tree - * @param p null - * @return null - */ - public Void visitYield17(Tree tree, Void p) { - defaultAction(tree); - return super.scan(tree, p); - } + /** + * Action performed on each visited tree. + * + * @param tree tree to perform action on + */ + public abstract void defaultAction(Tree tree); + + // TODO: use JCP to add version-specific behavior + @Override + public Void scan(Tree tree, Void unused) { + if (tree != null && SystemUtil.jreVersion >= 14) { + switch (tree.getKind().name()) { + case "SWITCH_EXPRESSION": + visitSwitchExpression17(tree, unused); + return null; + case "YIELD": + visitYield17(tree, unused); + return null; + case "BINDING_PATTERN": + visitBindingPattern17(tree, unused); + return null; + } + } + return super.scan(tree, unused); + } + + @Override + public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { + defaultAction(tree); + return super.visitAnnotatedType(tree, p); + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + defaultAction(tree); + return super.visitAnnotation(tree, p); + } + + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + defaultAction(tree); + return super.visitArrayAccess(tree, p); + } + + @Override + public Void visitArrayType(ArrayTypeTree tree, Void p) { + defaultAction(tree); + return super.visitArrayType(tree, p); + } + + @Override + public Void visitAssert(AssertTree tree, Void p) { + defaultAction(tree); + return super.visitAssert(tree, p); + } + + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + defaultAction(tree); + return super.visitAssignment(tree, p); + } + + @Override + public Void visitBinary(BinaryTree tree, Void p) { + defaultAction(tree); + return super.visitBinary(tree, p); + } + + /** + * Visit a binding pattern tree. + * + * @param tree a binding pattern tree + * @param p null + * @return null + */ + public Void visitBindingPattern17(Tree tree, Void p) { + defaultAction(tree); + return super.scan(tree, p); + } + + @Override + public Void visitBlock(BlockTree tree, Void p) { + defaultAction(tree); + return super.visitBlock(tree, p); + } + + @Override + public Void visitBreak(BreakTree tree, Void p) { + defaultAction(tree); + return super.visitBreak(tree, p); + } + + @Override + public Void visitCase(CaseTree tree, Void p) { + defaultAction(tree); + return super.visitCase(tree, p); + } + + @Override + public Void visitCatch(CatchTree tree, Void p) { + defaultAction(tree); + return super.visitCatch(tree, p); + } + + @Override + public Void visitClass(ClassTree tree, Void p) { + defaultAction(tree); + return super.visitClass(tree, p); + } + + @Override + public Void visitCompilationUnit(CompilationUnitTree tree, Void p) { + defaultAction(tree); + return super.visitCompilationUnit(tree, p); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + defaultAction(tree); + return super.visitCompoundAssignment(tree, p); + } + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + defaultAction(tree); + return super.visitConditionalExpression(tree, p); + } + + @Override + public Void visitContinue(ContinueTree tree, Void p) { + defaultAction(tree); + return super.visitContinue(tree, p); + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + defaultAction(tree); + return super.visitDoWhileLoop(tree, p); + } + + @Override + public Void visitEmptyStatement(EmptyStatementTree tree, Void p) { + defaultAction(tree); + return super.visitEmptyStatement(tree, p); + } + + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + defaultAction(tree); + return super.visitEnhancedForLoop(tree, p); + } + + @Override + public Void visitErroneous(ErroneousTree tree, Void p) { + defaultAction(tree); + return super.visitErroneous(tree, p); + } + + @Override + public Void visitExports(ExportsTree tree, Void p) { + defaultAction(tree); + return super.visitExports(tree, p); + } + + @Override + public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { + defaultAction(tree); + return super.visitExpressionStatement(tree, p); + } + + @Override + public Void visitForLoop(ForLoopTree tree, Void p) { + defaultAction(tree); + return super.visitForLoop(tree, p); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + defaultAction(tree); + return super.visitIdentifier(tree, p); + } + + @Override + public Void visitIf(IfTree tree, Void p) { + defaultAction(tree); + return super.visitIf(tree, p); + } + + @Override + public Void visitImport(ImportTree tree, Void p) { + defaultAction(tree); + return super.visitImport(tree, p); + } + + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + defaultAction(tree); + return super.visitInstanceOf(tree, p); + } + + @Override + public Void visitIntersectionType(IntersectionTypeTree tree, Void p) { + defaultAction(tree); + return super.visitIntersectionType(tree, p); + } + + @Override + public Void visitLabeledStatement(LabeledStatementTree tree, Void p) { + defaultAction(tree); + return super.visitLabeledStatement(tree, p); + } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + defaultAction(tree); + return super.visitLambdaExpression(tree, p); + } + + @Override + public Void visitLiteral(LiteralTree tree, Void p) { + defaultAction(tree); + return super.visitLiteral(tree, p); + } + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void p) { + defaultAction(tree); + return super.visitMemberReference(tree, p); + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + defaultAction(tree); + return super.visitMemberSelect(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + defaultAction(tree); + return super.visitMethod(tree, p); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + defaultAction(tree); + return super.visitMethodInvocation(tree, p); + } + + @Override + public Void visitModifiers(ModifiersTree tree, Void p) { + defaultAction(tree); + return super.visitModifiers(tree, p); + } + + @Override + public Void visitModule(ModuleTree tree, Void p) { + defaultAction(tree); + return super.visitModule(tree, p); + } + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + defaultAction(tree); + return super.visitNewArray(tree, p); + } + + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + defaultAction(tree); + return super.visitNewClass(tree, p); + } + + @Override + public Void visitOpens(OpensTree tree, Void p) { + defaultAction(tree); + return super.visitOpens(tree, p); + } + + @Override + public Void visitOther(Tree tree, Void p) { + defaultAction(tree); + return super.visitOther(tree, p); + } + + @Override + public Void visitPackage(PackageTree tree, Void p) { + defaultAction(tree); + return super.visitPackage(tree, p); + } + + @Override + public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { + defaultAction(tree); + return super.visitParameterizedType(tree, p); + } + + @Override + public Void visitParenthesized(ParenthesizedTree tree, Void p) { + defaultAction(tree); + return super.visitParenthesized(tree, p); + } + + @Override + public Void visitPrimitiveType(PrimitiveTypeTree tree, Void p) { + defaultAction(tree); + return super.visitPrimitiveType(tree, p); + } + + @Override + public Void visitProvides(ProvidesTree tree, Void p) { + defaultAction(tree); + return super.visitProvides(tree, p); + } + + @Override + public Void visitRequires(RequiresTree tree, Void p) { + defaultAction(tree); + return super.visitRequires(tree, p); + } + + @Override + public Void visitReturn(ReturnTree tree, Void p) { + defaultAction(tree); + return super.visitReturn(tree, p); + } + + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + defaultAction(tree); + return super.visitSwitch(tree, p); + } + + /** + * Visit a switch expression tree. + * + * @param tree switch expression tree + * @param p null + * @return null + */ + public Void visitSwitchExpression17(Tree tree, Void p) { + defaultAction(tree); + return super.scan(tree, p); + } + + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + defaultAction(tree); + return super.visitSynchronized(tree, p); + } + + @Override + public Void visitThrow(ThrowTree tree, Void p) { + defaultAction(tree); + return super.visitThrow(tree, p); + } + + @Override + public Void visitTry(TryTree tree, Void p) { + defaultAction(tree); + return super.visitTry(tree, p); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + defaultAction(tree); + return super.visitTypeCast(tree, p); + } + + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void p) { + defaultAction(tree); + return super.visitTypeParameter(tree, p); + } + + @Override + public Void visitUnary(UnaryTree tree, Void p) { + defaultAction(tree); + return super.visitUnary(tree, p); + } + + @Override + public Void visitUnionType(UnionTypeTree tree, Void p) { + defaultAction(tree); + return super.visitUnionType(tree, p); + } + + @Override + public Void visitUses(UsesTree tree, Void p) { + defaultAction(tree); + return super.visitUses(tree, p); + } + + @Override + public Void visitVariable(VariableTree tree, Void p) { + defaultAction(tree); + return super.visitVariable(tree, p); + } + + @Override + public Void visitWhileLoop(WhileLoopTree tree, Void p) { + defaultAction(tree); + return super.visitWhileLoop(tree, p); + } + + @Override + public Void visitWildcard(WildcardTree tree, Void p) { + defaultAction(tree); + return super.visitWildcard(tree, p); + } + + /** + * Visit a yield tree. + * + * @param tree a yield tree + * @param p null + * @return null + */ + public Void visitYield17(Tree tree, Void p) { + defaultAction(tree); + return super.scan(tree, p); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java b/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java index a3a9c82fdb9..9e5dd19da01 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java @@ -6,11 +6,6 @@ import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.framework.stub.AnnotationFileParser; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.util.ArrayList; @@ -18,9 +13,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.framework.stub.AnnotationFileParser; /** * Moves annotations in a JavaParser AST from declaration position onto the types they correspond @@ -34,197 +31,193 @@ * declaration target. */ public class TypeAnnotationMover extends VoidVisitorAdapter { - /** - * Annotations imported by the file, stored as a mapping from names to the TypeElements for the - * annotations. Contains entries for the simple and fully qualified names of each annotation. - */ - private final Map allAnnotations; - - /** Element utilities. */ - private final Elements elements; - - /** - * Constructs a {@code TypeAnnotationMover}. - * - * @param allAnnotations the annotations imported by the file, as a mapping from annotation name - * to TypeElement. There should be two entries for each annotation: the annotation's simple - * name and its fully-qualified name both mapped to its TypeElement. - * @param elements the Element utilities - */ - public TypeAnnotationMover(Map allAnnotations, Elements elements) { - this.allAnnotations = new HashMap<>(allAnnotations); - this.elements = elements; + /** + * Annotations imported by the file, stored as a mapping from names to the TypeElements for the + * annotations. Contains entries for the simple and fully qualified names of each annotation. + */ + private final Map allAnnotations; + + /** Element utilities. */ + private final Elements elements; + + /** + * Constructs a {@code TypeAnnotationMover}. + * + * @param allAnnotations the annotations imported by the file, as a mapping from annotation name + * to TypeElement. There should be two entries for each annotation: the annotation's simple + * name and its fully-qualified name both mapped to its TypeElement. + * @param elements the Element utilities + */ + public TypeAnnotationMover(Map allAnnotations, Elements elements) { + this.allAnnotations = new HashMap<>(allAnnotations); + this.elements = elements; + } + + @Override + public void visit(FieldDeclaration node, Void p) { + // Use the type of the first declared variable in the field declaration. + Type type = node.getVariable(0).getType(); + if (!type.isClassOrInterfaceType()) { + return; + } + + if (isMultiPartName(type)) { + return; } - @Override - public void visit(FieldDeclaration node, Void p) { - // Use the type of the first declared variable in the field declaration. - Type type = node.getVariable(0).getType(); - if (!type.isClassOrInterfaceType()) { - return; - } - - if (isMultiPartName(type)) { - return; - } - - List annosToMove = getAnnotationsToMove(node, ElementType.FIELD); - if (annosToMove.isEmpty()) { - return; - } - - node.getAnnotations().removeAll(annosToMove); - annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); + List annosToMove = getAnnotationsToMove(node, ElementType.FIELD); + if (annosToMove.isEmpty()) { + return; } - @Override - public void visit(MethodDeclaration node, Void p) { - Type type = node.getType(); - if (!type.isClassOrInterfaceType()) { - return; - } + node.getAnnotations().removeAll(annosToMove); + annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); + } + + @Override + public void visit(MethodDeclaration node, Void p) { + Type type = node.getType(); + if (!type.isClassOrInterfaceType()) { + return; + } - if (isMultiPartName(type)) { - return; - } + if (isMultiPartName(type)) { + return; + } - List annosToMove = getAnnotationsToMove(node, ElementType.METHOD); - if (annosToMove.isEmpty()) { - return; - } + List annosToMove = getAnnotationsToMove(node, ElementType.METHOD); + if (annosToMove.isEmpty()) { + return; + } - node.getAnnotations().removeAll(annosToMove); - annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); + node.getAnnotations().removeAll(annosToMove); + annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); + } + + /** + * Given a declaration, returns a List of annotations currently in declaration position that can't + * possibly be declaration annotations for that type of declaration. + * + * @param node a JavaParser node for declaration + * @param declarationType the type of declaration {@code node} represents; always FIELD or METHOD + * @return a list of annotations in declaration position that should be on the declaration's type + */ + private List getAnnotationsToMove( + NodeWithAnnotations node, ElementType declarationType) { + // There are usually no annotations that need to be moved. + List annosToMove = new ArrayList<>(0); + for (AnnotationExpr annotation : node.getAnnotations()) { + if (!isPossiblyDeclarationAnnotation(annotation, declarationType)) { + annosToMove.add(annotation); + } } - /** - * Given a declaration, returns a List of annotations currently in declaration position that - * can't possibly be declaration annotations for that type of declaration. - * - * @param node a JavaParser node for declaration - * @param declarationType the type of declaration {@code node} represents; always FIELD or - * METHOD - * @return a list of annotations in declaration position that should be on the declaration's - * type - */ - private List getAnnotationsToMove( - NodeWithAnnotations node, ElementType declarationType) { - // There are usually no annotations that need to be moved. - List annosToMove = new ArrayList<>(0); - for (AnnotationExpr annotation : node.getAnnotations()) { - if (!isPossiblyDeclarationAnnotation(annotation, declarationType)) { - annosToMove.add(annotation); - } - } - - return annosToMove; + return annosToMove; + } + + /** + * Returns the TypeElement for an annotation, or null if it cannot be found. + * + * @param annotation a JavaParser annotation + * @return the TypeElement for {@code annotation}, or null if it cannot be found + */ + private @Nullable TypeElement getAnnotationDeclaration(AnnotationExpr annotation) { + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); + TypeElement annoTypeElt = allAnnotations.get(annoNameFq); + if (annoTypeElt == null) { + annoTypeElt = elements.getTypeElement(annoNameFq); + if (annoTypeElt == null) { + // Not a supported annotation. + return null; + } + AnnotationFileParser.putAllNew( + allAnnotations, + AnnotationFileParser.createNameToAnnotationMap(Collections.singletonList(annoTypeElt))); } - /** - * Returns the TypeElement for an annotation, or null if it cannot be found. - * - * @param annotation a JavaParser annotation - * @return the TypeElement for {@code annotation}, or null if it cannot be found - */ - private @Nullable TypeElement getAnnotationDeclaration(AnnotationExpr annotation) { - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); - TypeElement annoTypeElt = allAnnotations.get(annoNameFq); - if (annoTypeElt == null) { - annoTypeElt = elements.getTypeElement(annoNameFq); - if (annoTypeElt == null) { - // Not a supported annotation. - return null; - } - AnnotationFileParser.putAllNew( - allAnnotations, - AnnotationFileParser.createNameToAnnotationMap( - Collections.singletonList(annoTypeElt))); - } - - return annoTypeElt; + return annoTypeElt; + } + + /** + * Returns if {@code annotation} could be a declaration annotation for {@code declarationType}. + * This would be the case if the annotation isn't recognized at all, or if it has no + * {@code @Target} meta-annotation, or if it has {@code declarationType} as one of its targets. + * + * @param annotation a JavaParser annotation expression + * @param declarationType the declaration type to check if {@code annotation} might be a + * declaration annotation for + * @return true unless {@code annotation} definitely cannot be a declaration annotation for {@code + * declarationType} + */ + private boolean isPossiblyDeclarationAnnotation( + AnnotationExpr annotation, ElementType declarationType) { + TypeElement annotationType = getAnnotationDeclaration(annotation); + if (annotationType == null) { + return true; } - /** - * Returns if {@code annotation} could be a declaration annotation for {@code declarationType}. - * This would be the case if the annotation isn't recognized at all, or if it has no - * {@code @Target} meta-annotation, or if it has {@code declarationType} as one of its targets. - * - * @param annotation a JavaParser annotation expression - * @param declarationType the declaration type to check if {@code annotation} might be a - * declaration annotation for - * @return true unless {@code annotation} definitely cannot be a declaration annotation for - * {@code declarationType} - */ - private boolean isPossiblyDeclarationAnnotation( - AnnotationExpr annotation, ElementType declarationType) { - TypeElement annotationType = getAnnotationDeclaration(annotation); - if (annotationType == null) { - return true; - } - - return isDeclarationAnnotation(annotationType, declarationType); + return isDeclarationAnnotation(annotationType, declarationType); + } + + /** + * Returns whether the annotation represented by {@code annotationDeclaration} might be a + * declaration annotation for {@code declarationType}. This holds if the TypeElement has no + * {@code @Target} meta-annotation, or if {@code declarationType} is a target of the annotation. + * + * @param annotationDeclaration declaration for an annotation + * @param declarationType the declaration type to check if the annotation might be a declaration + * annotation for + * @return true if {@code annotationDeclaration} contains {@code declarationType} as a target or + * doesn't contain {@code ElementType.TYPE_USE} as a target + */ + private boolean isDeclarationAnnotation( + TypeElement annotationDeclaration, ElementType declarationType) { + Target target = annotationDeclaration.getAnnotation(Target.class); + if (target == null) { + return true; } - /** - * Returns whether the annotation represented by {@code annotationDeclaration} might be a - * declaration annotation for {@code declarationType}. This holds if the TypeElement has no - * {@code @Target} meta-annotation, or if {@code declarationType} is a target of the annotation. - * - * @param annotationDeclaration declaration for an annotation - * @param declarationType the declaration type to check if the annotation might be a declaration - * annotation for - * @return true if {@code annotationDeclaration} contains {@code declarationType} as a target or - * doesn't contain {@code ElementType.TYPE_USE} as a target - */ - private boolean isDeclarationAnnotation( - TypeElement annotationDeclaration, ElementType declarationType) { - Target target = annotationDeclaration.getAnnotation(Target.class); - if (target == null) { - return true; - } - - boolean hasTypeUse = false; - for (ElementType elementType : target.value()) { - if (elementType == declarationType) { - return true; - } - - if (elementType == ElementType.TYPE_USE) { - hasTypeUse = true; - } - } - - if (!hasTypeUse) { - throw new Error( - String.format( - "Annotation %s cannot be used on declaration with type %s", - annotationDeclaration.getQualifiedName(), declarationType)); - } - return false; + boolean hasTypeUse = false; + for (ElementType elementType : target.value()) { + if (elementType == declarationType) { + return true; + } + + if (elementType == ElementType.TYPE_USE) { + hasTypeUse = true; + } } - /** - * Returns whether {@code type} has a name containing multiple parts separated by dots, e.g. - * "java.lang.String" or "Outer.Inner". - * - *

Annotations should not be moved onto a Type for which this method returns true. A type - * like {@code @Anno java.lang.String} is illegal since the annotation should go directly next - * to the rightmost part of the fully qualified name, like {@code java.lang. @Anno String}. So - * if a file contains a declaration like {@code @Anno java.lang.String myField}, the annotation - * must belong to the declaration and not the type. - * - *

If a declaration contains an inner class type like {@code @Anno Outer.Inner myField}, it - * may be the case that {@code @Anno} belongs to the type {@code Outer}, not the declaration, - * and should be moved, but it's impossible to distinguish this from the above case using only - * the JavaParser AST for a file. To be safe, the annotation still shouldn't be moved, but this - * may lead to suboptimal formatting placing {@code @Anno} on its own line. - * - * @param type a JavaParser type node - * @return true if {@code type} has a multi-part name - */ - private boolean isMultiPartName(Type type) { - return type.isClassOrInterfaceType() - && type.asClassOrInterfaceType().getScope().isPresent(); + if (!hasTypeUse) { + throw new Error( + String.format( + "Annotation %s cannot be used on declaration with type %s", + annotationDeclaration.getQualifiedName(), declarationType)); } + return false; + } + + /** + * Returns whether {@code type} has a name containing multiple parts separated by dots, e.g. + * "java.lang.String" or "Outer.Inner". + * + *

Annotations should not be moved onto a Type for which this method returns true. A type like + * {@code @Anno java.lang.String} is illegal since the annotation should go directly next to the + * rightmost part of the fully qualified name, like {@code java.lang. @Anno String}. So if a file + * contains a declaration like {@code @Anno java.lang.String myField}, the annotation must belong + * to the declaration and not the type. + * + *

If a declaration contains an inner class type like {@code @Anno Outer.Inner myField}, it may + * be the case that {@code @Anno} belongs to the type {@code Outer}, not the declaration, and + * should be moved, but it's impossible to distinguish this from the above case using only the + * JavaParser AST for a file. To be safe, the annotation still shouldn't be moved, but this may + * lead to suboptimal formatting placing {@code @Anno} on its own line. + * + * @param type a JavaParser type node + * @return true if {@code type} has a multi-part name + */ + private boolean isMultiPartName(Type type) { + return type.isClassOrInterfaceType() && type.asClassOrInterfaceType().getScope().isPresent(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java index f98260e8876..aabc1d8f89e 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.flow; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.analysis.ForwardAnalysisImpl; @@ -17,15 +24,6 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.TypesUtils; -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; - /** * {@link CFAbstractAnalysis} is an extensible org.checkerframework.dataflow analysis for the * Checker Framework that tracks the annotations using a flow-sensitive analysis. It uses an {@link @@ -38,235 +36,234 @@ * stores to access the {@link AnnotatedTypeFactory}, the qualifier hierarchy, etc. */ public abstract class CFAbstractAnalysis< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> - extends ForwardAnalysisImpl { - /** The qualifier hierarchy for which to track annotations. */ - protected final QualifierHierarchy qualHierarchy; + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> + extends ForwardAnalysisImpl { + /** The qualifier hierarchy for which to track annotations. */ + protected final QualifierHierarchy qualHierarchy; - /** The type hierarchy. */ - protected final TypeHierarchy typeHierarchy; + /** The type hierarchy. */ + protected final TypeHierarchy typeHierarchy; - /** - * The dependent type helper used to standardize both annotations belonging to the type - * hierarchy, and contract expressions. - */ - protected final DependentTypesHelper dependentTypesHelper; + /** + * The dependent type helper used to standardize both annotations belonging to the type hierarchy, + * and contract expressions. + */ + protected final DependentTypesHelper dependentTypesHelper; - /** A type factory that can provide static type annotations for AST Trees. */ - protected final GenericAnnotatedTypeFactory> - atypeFactory; + /** A type factory that can provide static type annotations for AST Trees. */ + protected final GenericAnnotatedTypeFactory> + atypeFactory; - /** A checker that contains command-line arguments and other information. */ - protected final SourceChecker checker; + /** A checker that contains command-line arguments and other information. */ + protected final SourceChecker checker; - /** - * A triple of field, value corresponding to the annotations on its declared type, value of its - * initializer. The value of the initializer is {@code null} if the field does not have one. - * - * @param type of value - */ - public static class FieldInitialValue> { + /** + * A triple of field, value corresponding to the annotations on its declared type, value of its + * initializer. The value of the initializer is {@code null} if the field does not have one. + * + * @param type of value + */ + public static class FieldInitialValue> { - /** A field access that corresponds to the declaration of a field. */ - public final FieldAccess fieldDecl; + /** A field access that corresponds to the declaration of a field. */ + public final FieldAccess fieldDecl; - /** The value corresponding to the annotations on the declared type of the field. */ - public final V declared; + /** The value corresponding to the annotations on the declared type of the field. */ + public final V declared; - /** The value of the initializer of the field, or null if no initializer exists. */ - public final @Nullable V initializer; + /** The value of the initializer of the field, or null if no initializer exists. */ + public final @Nullable V initializer; - /** - * Creates a new FieldInitialValue. - * - * @param fieldDecl a field access that corresponds to the declaration of a field - * @param declared value corresponding to the annotations on the declared type of {@code - * field} - * @param initializer value of the initializer of {@code field}, or null if no initializer - * exists - */ - public FieldInitialValue(FieldAccess fieldDecl, V declared, @Nullable V initializer) { - this.fieldDecl = fieldDecl; - this.declared = declared; - this.initializer = initializer; - } + /** + * Creates a new FieldInitialValue. + * + * @param fieldDecl a field access that corresponds to the declaration of a field + * @param declared value corresponding to the annotations on the declared type of {@code field} + * @param initializer value of the initializer of {@code field}, or null if no initializer + * exists + */ + public FieldInitialValue(FieldAccess fieldDecl, V declared, @Nullable V initializer) { + this.fieldDecl = fieldDecl; + this.declared = declared; + this.initializer = initializer; } + } - /** Initial abstract types for fields. */ - protected final List> fieldValues; + /** Initial abstract types for fields. */ + protected final List> fieldValues; - /** The associated processing environment. */ - protected final ProcessingEnvironment env; + /** The associated processing environment. */ + protected final ProcessingEnvironment env; - /** Instance of the types utility. */ - protected final Types types; + /** Instance of the types utility. */ + protected final Types types; - /** - * Create a CFAbstractAnalysis. - * - * @param checker a checker that contains command-line arguments and other information - * @param factory an annotated type factory to introduce type and dataflow rules - * @param maxCountBeforeWidening number of times a block can be analyzed before widening - */ - protected CFAbstractAnalysis( - BaseTypeChecker checker, - GenericAnnotatedTypeFactory> factory, - int maxCountBeforeWidening) { - super(maxCountBeforeWidening); - env = checker.getProcessingEnvironment(); - types = env.getTypeUtils(); - qualHierarchy = factory.getQualifierHierarchy(); - typeHierarchy = factory.getTypeHierarchy(); - dependentTypesHelper = factory.getDependentTypesHelper(); - this.atypeFactory = factory; - this.checker = checker; - this.transferFunction = createTransferFunction(); - this.fieldValues = new ArrayList<>(); - } + /** + * Create a CFAbstractAnalysis. + * + * @param checker a checker that contains command-line arguments and other information + * @param factory an annotated type factory to introduce type and dataflow rules + * @param maxCountBeforeWidening number of times a block can be analyzed before widening + */ + protected CFAbstractAnalysis( + BaseTypeChecker checker, + GenericAnnotatedTypeFactory> factory, + int maxCountBeforeWidening) { + super(maxCountBeforeWidening); + env = checker.getProcessingEnvironment(); + types = env.getTypeUtils(); + qualHierarchy = factory.getQualifierHierarchy(); + typeHierarchy = factory.getTypeHierarchy(); + dependentTypesHelper = factory.getDependentTypesHelper(); + this.atypeFactory = factory; + this.checker = checker; + this.transferFunction = createTransferFunction(); + this.fieldValues = new ArrayList<>(); + } - /** - * Create a CFAbstractAnalysis. - * - * @param checker a checker that contains command-line arguments and other information - * @param factory an annotated type factory to introduce type and dataflow rules - */ - protected CFAbstractAnalysis( - BaseTypeChecker checker, - GenericAnnotatedTypeFactory> factory) { - this(checker, factory, factory.getQualifierHierarchy().numberOfIterationsBeforeWidening()); - } + /** + * Create a CFAbstractAnalysis. + * + * @param checker a checker that contains command-line arguments and other information + * @param factory an annotated type factory to introduce type and dataflow rules + */ + protected CFAbstractAnalysis( + BaseTypeChecker checker, + GenericAnnotatedTypeFactory> factory) { + this(checker, factory, factory.getQualifierHierarchy().numberOfIterationsBeforeWidening()); + } - /** - * Analyze the given control flow graph. - * - * @param cfg control flow graph to analyze - * @param fieldValues initial values of the fields - */ - public void performAnalysis(ControlFlowGraph cfg, List> fieldValues) { - this.fieldValues.clear(); - this.fieldValues.addAll(fieldValues); - super.performAnalysis(cfg); - } + /** + * Analyze the given control flow graph. + * + * @param cfg control flow graph to analyze + * @param fieldValues initial values of the fields + */ + public void performAnalysis(ControlFlowGraph cfg, List> fieldValues) { + this.fieldValues.clear(); + this.fieldValues.addAll(fieldValues); + super.performAnalysis(cfg); + } - /** - * A list of initial abstract values for the fields. - * - * @return a list of initial abstract values for the fields - */ - public List> getFieldInitialValues() { - return fieldValues; - } + /** + * A list of initial abstract values for the fields. + * + * @return a list of initial abstract values for the fields + */ + public List> getFieldInitialValues() { + return fieldValues; + } - /** - * Returns the transfer function to be used by the analysis. - * - * @return the transfer function to be used by the analysis - */ - public T createTransferFunction() { - return atypeFactory.createFlowTransferFunction(this); - } + /** + * Returns the transfer function to be used by the analysis. + * + * @return the transfer function to be used by the analysis + */ + public T createTransferFunction() { + return atypeFactory.createFlowTransferFunction(this); + } - /** - * Returns an empty store of the appropriate type. - * - * @return an empty store of the appropriate type - */ - public abstract S createEmptyStore(boolean sequentialSemantics); + /** + * Returns an empty store of the appropriate type. + * + * @return an empty store of the appropriate type + */ + public abstract S createEmptyStore(boolean sequentialSemantics); - /** - * Returns an identical copy of the store {@code s}. - * - * @return an identical copy of the store {@code s} - */ - public abstract S createCopiedStore(S s); + /** + * Returns an identical copy of the store {@code s}. + * + * @return an identical copy of the store {@code s} + */ + public abstract S createCopiedStore(S s); - /** - * Creates an abstract value from the annotated type mirror. The value contains the set of - * primary annotations on the type, unless the type is an AnnotatedWildcardType. For an - * AnnotatedWildcardType, the annotations in the created value are the primary annotations on - * the extends bound. See {@link CFAbstractValue} for an explanation. - * - * @param type the type to convert into an abstract value - * @return an abstract value containing the given annotated {@code type} - */ - public @Nullable V createAbstractValue(AnnotatedTypeMirror type) { - AnnotationMirrorSet annos; - if (type.getKind() == TypeKind.WILDCARD) { - annos = ((AnnotatedWildcardType) type).getExtendsBound().getAnnotations(); - } else if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { - annos = ((AnnotatedTypeVariable) type).getUpperBound().getAnnotations(); - } else { - annos = type.getAnnotations(); - } - return createAbstractValue(annos, type.getUnderlyingType()); + /** + * Creates an abstract value from the annotated type mirror. The value contains the set of primary + * annotations on the type, unless the type is an AnnotatedWildcardType. For an + * AnnotatedWildcardType, the annotations in the created value are the primary annotations on the + * extends bound. See {@link CFAbstractValue} for an explanation. + * + * @param type the type to convert into an abstract value + * @return an abstract value containing the given annotated {@code type} + */ + public @Nullable V createAbstractValue(AnnotatedTypeMirror type) { + AnnotationMirrorSet annos; + if (type.getKind() == TypeKind.WILDCARD) { + annos = ((AnnotatedWildcardType) type).getExtendsBound().getAnnotations(); + } else if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { + annos = ((AnnotatedTypeVariable) type).getUpperBound().getAnnotations(); + } else { + annos = type.getAnnotations(); } + return createAbstractValue(annos, type.getUnderlyingType()); + } - /** - * Returns an abstract value containing the given {@code annotations} and {@code - * underlyingType}. Returns null if the annotation set has missing annotations. - * - * @param annotations the annotations for the result annotated type - * @param underlyingType the unannotated type for the result annotated type - * @return an abstract value containing the given {@code annotations} and {@code underlyingType} - */ - public abstract @Nullable V createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType); + /** + * Returns an abstract value containing the given {@code annotations} and {@code underlyingType}. + * Returns null if the annotation set has missing annotations. + * + * @param annotations the annotations for the result annotated type + * @param underlyingType the unannotated type for the result annotated type + * @return an abstract value containing the given {@code annotations} and {@code underlyingType} + */ + public abstract @Nullable V createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType); - /** Default implementation for {@link #createAbstractValue(AnnotationMirrorSet, TypeMirror)}. */ - public @Nullable CFValue defaultCreateAbstractValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; - } - return new CFValue(analysis, annotations, underlyingType); + /** Default implementation for {@link #createAbstractValue(AnnotationMirrorSet, TypeMirror)}. */ + public @Nullable CFValue defaultCreateAbstractValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; } + return new CFValue(analysis, annotations, underlyingType); + } - public TypeHierarchy getTypeHierarchy() { - return typeHierarchy; - } + public TypeHierarchy getTypeHierarchy() { + return typeHierarchy; + } - public GenericAnnotatedTypeFactory> - getTypeFactory() { - return atypeFactory; - } + public GenericAnnotatedTypeFactory> + getTypeFactory() { + return atypeFactory; + } - /** - * Returns an abstract value containing an annotated type with the annotation {@code anno}, and - * 'top' for all other hierarchies. The underlying type is {@code underlyingType}. - * - * @param anno the annotation for the result annotated type - * @param underlyingType the unannotated type for the result annotated type - * @return an abstract value with {@code anno} and {@code underlyingType} - */ - public V createSingleAnnotationValue(AnnotationMirror anno, TypeMirror underlyingType) { - QualifierHierarchy hierarchy = getTypeFactory().getQualifierHierarchy(); - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - annos.addAll(hierarchy.getTopAnnotations()); - AnnotationMirror f = hierarchy.findAnnotationInSameHierarchy(annos, anno); - annos.remove(f); - annos.add(anno); - return createAbstractValue(annos, underlyingType); - } + /** + * Returns an abstract value containing an annotated type with the annotation {@code anno}, and + * 'top' for all other hierarchies. The underlying type is {@code underlyingType}. + * + * @param anno the annotation for the result annotated type + * @param underlyingType the unannotated type for the result annotated type + * @return an abstract value with {@code anno} and {@code underlyingType} + */ + public V createSingleAnnotationValue(AnnotationMirror anno, TypeMirror underlyingType) { + QualifierHierarchy hierarchy = getTypeFactory().getQualifierHierarchy(); + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + annos.addAll(hierarchy.getTopAnnotations()); + AnnotationMirror f = hierarchy.findAnnotationInSameHierarchy(annos, anno); + annos.remove(f); + annos.add(anno); + return createAbstractValue(annos, underlyingType); + } - /** - * Get the types utility. - * - * @return {@link #types} - */ - public Types getTypes() { - return types; - } + /** + * Get the types utility. + * + * @return {@link #types} + */ + public Types getTypes() { + return types; + } - /** - * Get the processing environment. - * - * @return {@link #env} - */ - public ProcessingEnvironment getEnv() { - return env; - } + /** + * Get the processing environment. + * + * @return {@link #env} + */ + public ProcessingEnvironment getEnv() { + return env; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 519e83b59bf..d00696dd1e6 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,5 +1,21 @@ package org.checkerframework.framework.flow; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BinaryOperator; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; @@ -31,24 +47,6 @@ import org.plumelib.util.ToStringComparator; import org.plumelib.util.UniqueId; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.BinaryOperator; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; - /** * A store for the Checker Framework analysis. It tracks the annotations of memory locations such as * local variables and fields. @@ -65,1303 +63,1286 @@ // TODO: Split this class into two parts: one that is reusable generally and // one that is specific to the Checker Framework. public abstract class CFAbstractStore, S extends CFAbstractStore> - implements Store, UniqueId { - - /** The analysis class this store belongs to. */ - protected final CFAbstractAnalysis analysis; - - /** Information collected about local variables (including method parameters). */ - protected final Map localVariableValues; - - /** Information collected about the current object. */ - protected V thisValue; - - /** - * Information collected about fields, using the internal representation {@link FieldAccess}. - */ - protected Map fieldValues; - - /** - * Returns information about fields. Clients should not side-effect the returned value, which is - * aliased to internal state. - * - * @return information about fields - */ - public Map getFieldValues() { - return fieldValues; + implements Store, UniqueId { + + /** The analysis class this store belongs to. */ + protected final CFAbstractAnalysis analysis; + + /** Information collected about local variables (including method parameters). */ + protected final Map localVariableValues; + + /** Information collected about the current object. */ + protected V thisValue; + + /** Information collected about fields, using the internal representation {@link FieldAccess}. */ + protected Map fieldValues; + + /** + * Returns information about fields. Clients should not side-effect the returned value, which is + * aliased to internal state. + * + * @return information about fields + */ + public Map getFieldValues() { + return fieldValues; + } + + /** + * Information collected about array elements, using the internal representation {@link + * ArrayAccess}. + */ + protected final Map arrayValues; + + /** + * Information collected about method calls, using the internal representation {@link MethodCall}. + */ + protected final Map methodValues; + + /** + * Information collected about classname.class values, using the internal representation + * {@link ClassName}. + */ + protected final Map classValues; + + /** + * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running + * at all times)? + */ + protected final boolean sequentialSemantics; + + /** True if -AassumeSideEffectFree or -AassumePure was passed on the command line. */ + private final boolean assumeSideEffectFree; + + /** True if -AassumePureGetters was passed on the command line. */ + private final boolean assumePureGetters; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid() { + return uid; + } + + /* --------------------------------------------------------- */ + /* Initialization */ + /* --------------------------------------------------------- */ + + /** + * Creates a new CFAbstractStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics? + */ + protected CFAbstractStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { + this.analysis = analysis; + localVariableValues = new HashMap<>(); + thisValue = null; + fieldValues = new HashMap<>(); + methodValues = new HashMap<>(); + arrayValues = new HashMap<>(); + classValues = new HashMap<>(); + this.sequentialSemantics = sequentialSemantics; + assumeSideEffectFree = + analysis.checker.hasOption("assumeSideEffectFree") + || analysis.checker.hasOption("assumePure"); + assumePureGetters = analysis.checker.hasOption("assumePureGetters"); + } + + /** + * Copy constructor. + * + * @param other a CFAbstractStore to copy into this + */ + protected CFAbstractStore(CFAbstractStore other) { + this.analysis = other.analysis; + localVariableValues = new HashMap<>(other.localVariableValues); + thisValue = other.thisValue; + fieldValues = new HashMap<>(other.fieldValues); + methodValues = new HashMap<>(other.methodValues); + arrayValues = new HashMap<>(other.arrayValues); + classValues = new HashMap<>(other.classValues); + sequentialSemantics = other.sequentialSemantics; + assumeSideEffectFree = other.assumeSideEffectFree; + assumePureGetters = other.assumePureGetters; + } + + /** + * Set the abstract value of a method parameter (only adds the information to the store, does not + * remove any other knowledge). Any previous information is erased; this method should only be + * used to initialize the abstract value. + */ + public void initializeMethodParameter(LocalVariableNode p, @Nullable V value) { + if (value != null) { + localVariableValues.put(new LocalVariable(p.getElement()), value); } - - /** - * Information collected about array elements, using the internal representation {@link - * ArrayAccess}. - */ - protected final Map arrayValues; - - /** - * Information collected about method calls, using the internal representation {@link - * MethodCall}. - */ - protected final Map methodValues; - - /** - * Information collected about classname.class values, using the internal representation - * {@link ClassName}. - */ - protected final Map classValues; - - /** - * Should the analysis use sequential Java semantics (i.e., assume that only one thread is - * running at all times)? - */ - protected final boolean sequentialSemantics; - - /** True if -AassumeSideEffectFree or -AassumePure was passed on the command line. */ - private final boolean assumeSideEffectFree; - - /** True if -AassumePureGetters was passed on the command line. */ - private final boolean assumePureGetters; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid() { - return uid; + } + + /** + * Set the value of the current object. Any previous information is erased; this method should + * only be used to initialize the value. + */ + public void initializeThisValue(AnnotationMirror a, TypeMirror underlyingType) { + if (a != null) { + thisValue = analysis.createSingleAnnotationValue(a, underlyingType); } - - /* --------------------------------------------------------- */ - /* Initialization */ - /* --------------------------------------------------------- */ - - /** - * Creates a new CFAbstractStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics? - */ - protected CFAbstractStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { - this.analysis = analysis; - localVariableValues = new HashMap<>(); + } + + /* --------------------------------------------------------- */ + /* Handling of fields */ + /* --------------------------------------------------------- */ + + /** + * Remove any information that might not be valid any more after a method call, and add + * information guaranteed by the method. + * + *

    + *
  1. If the method is side-effect-free (as indicated by {@link + * org.checkerframework.dataflow.qual.SideEffectFree} or {@link + * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. + *
  2. Otherwise, all information about field accesses {@code a.f} needs to be removed, except + * if the method {@code n} cannot modify {@code a.f}. This unmodifiability property holds if + * {@code a} is a local variable or {@code this}, and {@code f} is final, or if {@code a.f} + * has a {@link MonotonicQualifier} in the current store. Subclasses can change this + * behavior by overriding {@link #newFieldValueAfterMethodCall(FieldAccess, + * GenericAnnotatedTypeFactory, CFAbstractValue)}. + *
  3. Furthermore, if the field has a monotonic annotation, then its information can also be + * kept. + *
+ * + * Furthermore, if the method is deterministic, we store its result {@code val} in the store. + * + * @param methodInvocationNode method whose information is being updated + * @param atypeFactory the type factory of the associated checker + * @param val abstract value of the method call + */ + public void updateForMethodCall( + MethodInvocationNode methodInvocationNode, + GenericAnnotatedTypeFactory atypeFactory, + V val) { + ExecutableElement method = methodInvocationNode.getTarget().getMethod(); + + // Case 1: The method is side-effect-free. + boolean hasSideEffect = + !(assumeSideEffectFree + || (assumePureGetters && ElementUtils.isGetter(method)) + || atypeFactory.isSideEffectFree(method)); + if (hasSideEffect) { + boolean sideEffectsUnrefineAliases = atypeFactory.sideEffectsUnrefineAliases; + + // update local variables + // TODO: Also remove if any element/argument to the annotation is not + // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). + if (sideEffectsUnrefineAliases) { + localVariableValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + } + + // update this value + if (sideEffectsUnrefineAliases) { thisValue = null; - fieldValues = new HashMap<>(); - methodValues = new HashMap<>(); - arrayValues = new HashMap<>(); - classValues = new HashMap<>(); - this.sequentialSemantics = sequentialSemantics; - assumeSideEffectFree = - analysis.checker.hasOption("assumeSideEffectFree") - || analysis.checker.hasOption("assumePure"); - assumePureGetters = analysis.checker.hasOption("assumePureGetters"); - } + } - /** - * Copy constructor. - * - * @param other a CFAbstractStore to copy into this - */ - protected CFAbstractStore(CFAbstractStore other) { - this.analysis = other.analysis; - localVariableValues = new HashMap<>(other.localVariableValues); - thisValue = other.thisValue; - fieldValues = new HashMap<>(other.fieldValues); - methodValues = new HashMap<>(other.methodValues); - arrayValues = new HashMap<>(other.arrayValues); - classValues = new HashMap<>(other.classValues); - sequentialSemantics = other.sequentialSemantics; - assumeSideEffectFree = other.assumeSideEffectFree; - assumePureGetters = other.assumePureGetters; - } + // update field values + if (sideEffectsUnrefineAliases) { + fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + } else { + // Case 2 (unassignable fields) and case 3 (monotonic fields) + updateFieldValuesForMethodCall(atypeFactory); + } - /** - * Set the abstract value of a method parameter (only adds the information to the store, does - * not remove any other knowledge). Any previous information is erased; this method should only - * be used to initialize the abstract value. - */ - public void initializeMethodParameter(LocalVariableNode p, @Nullable V value) { - if (value != null) { - localVariableValues.put(new LocalVariable(p.getElement()), value); - } - } + // update array values + arrayValues.clear(); - /** - * Set the value of the current object. Any previous information is erased; this method should - * only be used to initialize the value. - */ - public void initializeThisValue(AnnotationMirror a, TypeMirror underlyingType) { - if (a != null) { - thisValue = analysis.createSingleAnnotationValue(a, underlyingType); - } + // update method values + methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); } - /* --------------------------------------------------------- */ - /* Handling of fields */ - /* --------------------------------------------------------- */ - - /** - * Remove any information that might not be valid any more after a method call, and add - * information guaranteed by the method. - * - *
    - *
  1. If the method is side-effect-free (as indicated by {@link - * org.checkerframework.dataflow.qual.SideEffectFree} or {@link - * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. - *
  2. Otherwise, all information about field accesses {@code a.f} needs to be removed, except - * if the method {@code n} cannot modify {@code a.f}. This unmodifiability property holds - * if {@code a} is a local variable or {@code this}, and {@code f} is final, or if {@code - * a.f} has a {@link MonotonicQualifier} in the current store. Subclasses can change this - * behavior by overriding {@link #newFieldValueAfterMethodCall(FieldAccess, - * GenericAnnotatedTypeFactory, CFAbstractValue)}. - *
  3. Furthermore, if the field has a monotonic annotation, then its information can also be - * kept. - *
- * - * Furthermore, if the method is deterministic, we store its result {@code val} in the store. - * - * @param methodInvocationNode method whose information is being updated - * @param atypeFactory the type factory of the associated checker - * @param val abstract value of the method call - */ - public void updateForMethodCall( - MethodInvocationNode methodInvocationNode, - GenericAnnotatedTypeFactory atypeFactory, - V val) { - ExecutableElement method = methodInvocationNode.getTarget().getMethod(); - - // Case 1: The method is side-effect-free. - boolean hasSideEffect = - !(assumeSideEffectFree - || (assumePureGetters && ElementUtils.isGetter(method)) - || atypeFactory.isSideEffectFree(method)); - if (hasSideEffect) { - boolean sideEffectsUnrefineAliases = atypeFactory.sideEffectsUnrefineAliases; - - // update local variables - // TODO: Also remove if any element/argument to the annotation is not - // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). - if (sideEffectsUnrefineAliases) { - localVariableValues - .entrySet() - .removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); - } - - // update this value - if (sideEffectsUnrefineAliases) { - thisValue = null; - } - - // update field values - if (sideEffectsUnrefineAliases) { - fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); - } else { - // Case 2 (unassignable fields) and case 3 (monotonic fields) - updateFieldValuesForMethodCall(atypeFactory); - } - - // update array values - arrayValues.clear(); - - // update method values - methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - } - - // store information about method call if possible - JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); - replaceValue(methodCall, val); + // store information about method call if possible + JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); + replaceValue(methodCall, val); + } + + /** + * Returns the new value of a field after a method call, or {@code null} if the field should be + * removed from the store. + * + *

In this default implementation, the field's value is preserved if it is either unassignable + * (see {@link FieldAccess#isUnassignableByOtherCode()}) or has a monotonic qualifier (see {@link + * #newMonotonicFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory, + * CFAbstractValue)}). Otherwise, it is removed from the store. + * + * @param fieldAccess the field whose value to update + * @param atypeFactory AnnotatedTypeFactory of the associated checker + * @param value the field's value before the method call + * @return the field's value after the method call, or {@code null} if the field should be removed + * from the store + */ + protected V newFieldValueAfterMethodCall( + FieldAccess fieldAccess, GenericAnnotatedTypeFactory atypeFactory, V value) { + // Handle unassignable fields. + if (fieldAccess.isUnassignableByOtherCode()) { + return value; } - /** - * Returns the new value of a field after a method call, or {@code null} if the field should be - * removed from the store. - * - *

In this default implementation, the field's value is preserved if it is either - * unassignable (see {@link FieldAccess#isUnassignableByOtherCode()}) or has a monotonic - * qualifier (see {@link #newMonotonicFieldValueAfterMethodCall(FieldAccess, - * GenericAnnotatedTypeFactory, CFAbstractValue)}). Otherwise, it is removed from the store. - * - * @param fieldAccess the field whose value to update - * @param atypeFactory AnnotatedTypeFactory of the associated checker - * @param value the field's value before the method call - * @return the field's value after the method call, or {@code null} if the field should be - * removed from the store - */ - protected V newFieldValueAfterMethodCall( - FieldAccess fieldAccess, - GenericAnnotatedTypeFactory atypeFactory, - V value) { - // Handle unassignable fields. - if (fieldAccess.isUnassignableByOtherCode()) { - return value; - } - - // Handle fields with monotonic annotations. - return newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + // Handle fields with monotonic annotations. + return newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + } + + /** + * Computes the value of a field whose declaration has a monotonic annotation, or returns {@code + * null} if the field has no monotonic annotation. + * + *

Used by {@link #newFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory, + * CFAbstractValue)} to handle fields with monotonic annotations. + * + * @param fieldAccess the field whose value to compute + * @param atypeFactory AnnotatedTypeFactory of the associated checker + * @param value the field's value before the method call + * @return the field's value after the method call, or {@code null} if the field has no monotonic + * annotation + */ + protected V newMonotonicFieldValueAfterMethodCall( + FieldAccess fieldAccess, GenericAnnotatedTypeFactory atypeFactory, V value) { + // case 3: the field has a monotonic annotation + if (atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { + return null; } - /** - * Computes the value of a field whose declaration has a monotonic annotation, or returns {@code - * null} if the field has no monotonic annotation. - * - *

Used by {@link #newFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory, - * CFAbstractValue)} to handle fields with monotonic annotations. - * - * @param fieldAccess the field whose value to compute - * @param atypeFactory AnnotatedTypeFactory of the associated checker - * @param value the field's value before the method call - * @return the field's value after the method call, or {@code null} if the field has no - * monotonic annotation - */ - protected V newMonotonicFieldValueAfterMethodCall( - FieldAccess fieldAccess, - GenericAnnotatedTypeFactory atypeFactory, - V value) { - // case 3: the field has a monotonic annotation - if (atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { - return null; - } - - List> fieldAnnotationPairs = - atypeFactory.getAnnotationWithMetaAnnotation( - fieldAccess.getField(), MonotonicQualifier.class); - List metaAnnotations = - CollectionsPlume.withoutDuplicates( - CollectionsPlume.mapList(pair -> pair.second, fieldAnnotationPairs)); - List monotonicAnnotations = new ArrayList<>(metaAnnotations.size()); - for (AnnotationMirror metaAnnotation : metaAnnotations) { - @SuppressWarnings("deprecation") // permitted for use in the framework - Name annoName = - AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); - monotonicAnnotations.add( - AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName)); - } - Collection valueAnnos = value.getAnnotations(); - V newValue = null; - for (AnnotationMirror monotonicAnnotation : monotonicAnnotations) { - // Make sure the target annotation is present. - if (AnnotationUtils.containsSame(valueAnnos, monotonicAnnotation)) { - newValue = - analysis.createSingleAnnotationValue( - monotonicAnnotation, value.getUnderlyingType()) - .mostSpecific(newValue, null); - } - } - return newValue; + List> fieldAnnotationPairs = + atypeFactory.getAnnotationWithMetaAnnotation( + fieldAccess.getField(), MonotonicQualifier.class); + List metaAnnotations = + CollectionsPlume.withoutDuplicates( + CollectionsPlume.mapList(pair -> pair.second, fieldAnnotationPairs)); + List monotonicAnnotations = new ArrayList<>(metaAnnotations.size()); + for (AnnotationMirror metaAnnotation : metaAnnotations) { + @SuppressWarnings("deprecation") // permitted for use in the framework + Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); + monotonicAnnotations.add( + AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName)); } - - /** - * Helper for {@link #updateForMethodCall(MethodInvocationNode, GenericAnnotatedTypeFactory, - * CFAbstractValue)}. Remove any information about field values that might not be valid any more - * after a method call, and add information guaranteed by the method. - * - *

More specifically, remove all information about fields except for unassignable fields and - * fields that have a monotonic annotation. - * - * @param atypeFactory AnnotatedTypeFactory of the associated checker - */ - private void updateFieldValuesForMethodCall( - GenericAnnotatedTypeFactory atypeFactory) { - Map newFieldValues = - new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); - for (Map.Entry e : fieldValues.entrySet()) { - FieldAccess fieldAccess = e.getKey(); - V value = e.getValue(); - - V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); - if (newValue != null) { - // Keep information for all hierarchies where we had a monotonic annotation. - newFieldValues.put(fieldAccess, newValue); - } - } - fieldValues = newFieldValues; + Collection valueAnnos = value.getAnnotations(); + V newValue = null; + for (AnnotationMirror monotonicAnnotation : monotonicAnnotations) { + // Make sure the target annotation is present. + if (AnnotationUtils.containsSame(valueAnnos, monotonicAnnotation)) { + newValue = + analysis + .createSingleAnnotationValue(monotonicAnnotation, value.getUnderlyingType()) + .mostSpecific(newValue, null); + } } - - /** - * Add the annotation {@code a} for the expression {@code expr} (correctly deciding where to - * store the information depending on the type of the expression {@code expr}). - * - *

This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - * - *

If there is already a value {@code v} present for {@code expr}, then the stronger of the - * new and old value are taken (according to the lattice). Note that this happens per hierarchy, - * and if the store already contains information about a hierarchy other than {@code a}s - * hierarchy, that information is preserved. - * - *

If {@code expr} is nondeterministic, this method does not insert {@code value} into the - * store. - * - * @param expr an expression - * @param a an annotation for the expression - */ - public void insertValue(JavaExpression expr, AnnotationMirror a) { - insertValue(expr, analysis.createSingleAnnotationValue(a, expr.getType())); + return newValue; + } + + /** + * Helper for {@link #updateForMethodCall(MethodInvocationNode, GenericAnnotatedTypeFactory, + * CFAbstractValue)}. Remove any information about field values that might not be valid any more + * after a method call, and add information guaranteed by the method. + * + *

More specifically, remove all information about fields except for unassignable fields and + * fields that have a monotonic annotation. + * + * @param atypeFactory AnnotatedTypeFactory of the associated checker + */ + private void updateFieldValuesForMethodCall( + GenericAnnotatedTypeFactory atypeFactory) { + Map newFieldValues = new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); + for (Map.Entry e : fieldValues.entrySet()) { + FieldAccess fieldAccess = e.getKey(); + V value = e.getValue(); + + V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + if (newValue != null) { + // Keep information for all hierarchies where we had a monotonic annotation. + newFieldValues.put(fieldAccess, newValue); + } } - - /** - * Like {@link #insertValue(JavaExpression, AnnotationMirror)}, but permits nondeterministic - * expressions to be stored. - * - *

For an explanation of when to permit nondeterministic expressions, see {@link - * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. - * - * @param expr an expression - * @param a an annotation for the expression - */ - public void insertValuePermitNondeterministic(JavaExpression expr, AnnotationMirror a) { - insertValuePermitNondeterministic( - expr, analysis.createSingleAnnotationValue(a, expr.getType())); + fieldValues = newFieldValues; + } + + /** + * Add the annotation {@code a} for the expression {@code expr} (correctly deciding where to store + * the information depending on the type of the expression {@code expr}). + * + *

This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + * + *

If there is already a value {@code v} present for {@code expr}, then the stronger of the new + * and old value are taken (according to the lattice). Note that this happens per hierarchy, and + * if the store already contains information about a hierarchy other than {@code a}s hierarchy, + * that information is preserved. + * + *

If {@code expr} is nondeterministic, this method does not insert {@code value} into the + * store. + * + * @param expr an expression + * @param a an annotation for the expression + */ + public void insertValue(JavaExpression expr, AnnotationMirror a) { + insertValue(expr, analysis.createSingleAnnotationValue(a, expr.getType())); + } + + /** + * Like {@link #insertValue(JavaExpression, AnnotationMirror)}, but permits nondeterministic + * expressions to be stored. + * + *

For an explanation of when to permit nondeterministic expressions, see {@link + * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. + * + * @param expr an expression + * @param a an annotation for the expression + */ + public void insertValuePermitNondeterministic(JavaExpression expr, AnnotationMirror a) { + insertValuePermitNondeterministic( + expr, analysis.createSingleAnnotationValue(a, expr.getType())); + } + + /** + * Add the annotation {@code newAnno} for the expression {@code expr} (correctly deciding where to + * store the information depending on the type of the expression {@code expr}). + * + *

This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + * + *

If there is already a value {@code v} present for {@code expr}, then the greatest lower + * bound of the new and old value is inserted into the store. + * + *

Note that this happens per hierarchy, and if the store already contains information about a + * hierarchy other than {@code newAnno}'s hierarchy, that information is preserved. + * + *

If {@code expr} is nondeterministic, this method does not insert {@code value} into the + * store. + * + * @param expr an expression + * @param newAnno the expression's annotation + */ + public final void insertOrRefine(JavaExpression expr, AnnotationMirror newAnno) { + insertOrRefine(expr, newAnno, false); + } + + /** + * Like {@link #insertOrRefine(JavaExpression, AnnotationMirror)}, but permits nondeterministic + * expressions to be inserted. + * + *

For an explanation of when to permit nondeterministic expressions, see {@link + * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. + * + * @param expr an expression + * @param newAnno the expression's annotation + */ + public final void insertOrRefinePermitNondeterministic( + JavaExpression expr, AnnotationMirror newAnno) { + insertOrRefine(expr, newAnno, true); + } + + /** + * Helper function for {@link #insertOrRefine(JavaExpression, AnnotationMirror)} and {@link + * #insertOrRefinePermitNondeterministic}. + * + * @param expr an expression + * @param newAnno the expression's annotation + * @param permitNondeterministic whether nondeterministic expressions may be inserted into the + * store + */ + protected void insertOrRefine( + JavaExpression expr, AnnotationMirror newAnno, boolean permitNondeterministic) { + if (!canInsertJavaExpression(expr)) { + return; } - - /** - * Add the annotation {@code newAnno} for the expression {@code expr} (correctly deciding where - * to store the information depending on the type of the expression {@code expr}). - * - *

This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - * - *

If there is already a value {@code v} present for {@code expr}, then the greatest lower - * bound of the new and old value is inserted into the store. - * - *

Note that this happens per hierarchy, and if the store already contains information about - * a hierarchy other than {@code newAnno}'s hierarchy, that information is preserved. - * - *

If {@code expr} is nondeterministic, this method does not insert {@code value} into the - * store. - * - * @param expr an expression - * @param newAnno the expression's annotation - */ - public final void insertOrRefine(JavaExpression expr, AnnotationMirror newAnno) { - insertOrRefine(expr, newAnno, false); + if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { + return; } - /** - * Like {@link #insertOrRefine(JavaExpression, AnnotationMirror)}, but permits nondeterministic - * expressions to be inserted. - * - *

For an explanation of when to permit nondeterministic expressions, see {@link - * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. - * - * @param expr an expression - * @param newAnno the expression's annotation - */ - public final void insertOrRefinePermitNondeterministic( - JavaExpression expr, AnnotationMirror newAnno) { - insertOrRefine(expr, newAnno, true); + V newValue = analysis.createSingleAnnotationValue(newAnno, expr.getType()); + V oldValue = getValue(expr); + if (oldValue == null) { + insertValue( + expr, + analysis.createSingleAnnotationValue(newAnno, expr.getType()), + permitNondeterministic); + return; } - - /** - * Helper function for {@link #insertOrRefine(JavaExpression, AnnotationMirror)} and {@link - * #insertOrRefinePermitNondeterministic}. - * - * @param expr an expression - * @param newAnno the expression's annotation - * @param permitNondeterministic whether nondeterministic expressions may be inserted into the - * store - */ - protected void insertOrRefine( - JavaExpression expr, AnnotationMirror newAnno, boolean permitNondeterministic) { - if (!canInsertJavaExpression(expr)) { - return; - } - if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { - return; - } - - V newValue = analysis.createSingleAnnotationValue(newAnno, expr.getType()); - V oldValue = getValue(expr); - if (oldValue == null) { - insertValue( - expr, - analysis.createSingleAnnotationValue(newAnno, expr.getType()), - permitNondeterministic); - return; - } - computeNewValueAndInsert( - expr, newValue, CFAbstractValue::greatestLowerBound, permitNondeterministic); + computeNewValueAndInsert( + expr, newValue, CFAbstractValue::greatestLowerBound, permitNondeterministic); + } + + /** Returns true if {@code expr} can be stored in this store. */ + public static boolean canInsertJavaExpression(JavaExpression expr) { + if (expr instanceof FieldAccess + || expr instanceof ThisReference + || expr instanceof LocalVariable + || expr instanceof MethodCall + || expr instanceof ArrayAccess + || expr instanceof ClassName) { + return !expr.containsUnknown(); } - - /** Returns true if {@code expr} can be stored in this store. */ - public static boolean canInsertJavaExpression(JavaExpression expr) { - if (expr instanceof FieldAccess - || expr instanceof ThisReference - || expr instanceof LocalVariable - || expr instanceof MethodCall - || expr instanceof ArrayAccess - || expr instanceof ClassName) { - return !expr.containsUnknown(); - } - return false; + return false; + } + + /** + * Add the abstract value {@code value} for the expression {@code expr} (correctly deciding where + * to store the information depending on the type of the expression {@code expr}). + * + *

This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + * + *

If there is already a value {@code v} present for {@code expr}, then the stronger of the new + * and old value are taken (according to the lattice). Note that this happens per hierarchy, and + * if the store already contains information about a hierarchy for which {@code value} does not + * contain information, then that information is preserved. + * + *

If {@code expr} is nondeterministic, this method does not insert {@code value} into the + * store. + * + * @param expr the expression to insert in the store + * @param value the value of the expression + */ + public final void insertValue(JavaExpression expr, @Nullable V value) { + insertValue(expr, value, false); + } + + /** + * Like {@link #insertValue(JavaExpression, CFAbstractValue)}, but updates the store even if + * {@code expr} is nondeterministic. + * + *

Usually, nondeterministic JavaExpressions should not be stored in a Store. For example, in + * the body of {@code if (nondet() == 3) {...}}, the store should not record that the value of + * {@code nondet()} is 3, because it might not be 3 the next time {@code nondet()} is executed. + * + *

However, contracts can mention a nondeterministic JavaExpression. For example, a contract + * might have a postcondition that {@code nondet()} is odd. This means that the next call to + * {@code nondet()} will return odd. Such a postcondition may be evicted from the store by calling + * a side-effecting method. + * + * @param expr the expression to insert in the store + * @param value the value of the expression + */ + public final void insertValuePermitNondeterministic(JavaExpression expr, @Nullable V value) { + insertValue(expr, value, true); + } + + /** + * Returns true if the given (expression, value) pair can be inserted in the store, namely if the + * value is non-null and the expression does not contain unknown or a nondeterministic expression. + * + *

This method returning true does not guarantee that the value will be inserted; the + * implementation of {@link #insertValue( JavaExpression, CFAbstractValue, boolean)} might still + * not insert it. + * + * @param expr the expression to insert in the store + * @param value the value of the expression + * @param permitNondeterministic if false, returns false if {@code expr} is nondeterministic; if + * true, permits nondeterministic expressions to be placed in the store + * @return true if the given (expression, value) pair can be inserted in the store + */ + @EnsuresNonNullIf(expression = "#2", result = true) + protected boolean shouldInsert( + JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { + if (value == null) { + // No need to insert a null abstract value because it represents + // top and top is also the default value. + return false; } - - /** - * Add the abstract value {@code value} for the expression {@code expr} (correctly deciding - * where to store the information depending on the type of the expression {@code expr}). - * - *

This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - * - *

If there is already a value {@code v} present for {@code expr}, then the stronger of the - * new and old value are taken (according to the lattice). Note that this happens per hierarchy, - * and if the store already contains information about a hierarchy for which {@code value} does - * not contain information, then that information is preserved. - * - *

If {@code expr} is nondeterministic, this method does not insert {@code value} into the - * store. - * - * @param expr the expression to insert in the store - * @param value the value of the expression - */ - public final void insertValue(JavaExpression expr, @Nullable V value) { - insertValue(expr, value, false); + if (expr.containsUnknown()) { + // Expressions containing unknown expressions are not stored. + return false; } - - /** - * Like {@link #insertValue(JavaExpression, CFAbstractValue)}, but updates the store even if - * {@code expr} is nondeterministic. - * - *

Usually, nondeterministic JavaExpressions should not be stored in a Store. For example, in - * the body of {@code if (nondet() == 3) {...}}, the store should not record that the value of - * {@code nondet()} is 3, because it might not be 3 the next time {@code nondet()} is executed. - * - *

However, contracts can mention a nondeterministic JavaExpression. For example, a contract - * might have a postcondition that {@code nondet()} is odd. This means that the next call to - * {@code nondet()} will return odd. Such a postcondition may be evicted from the store by - * calling a side-effecting method. - * - * @param expr the expression to insert in the store - * @param value the value of the expression - */ - public final void insertValuePermitNondeterministic(JavaExpression expr, @Nullable V value) { - insertValue(expr, value, true); + if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { + // Nondeterministic expressions may not be stored. + // (They are likely to be quickly evicted, as soon as a side-effecting method is + // called.) + return false; } - - /** - * Returns true if the given (expression, value) pair can be inserted in the store, namely if - * the value is non-null and the expression does not contain unknown or a nondeterministic - * expression. - * - *

This method returning true does not guarantee that the value will be inserted; the - * implementation of {@link #insertValue( JavaExpression, CFAbstractValue, boolean)} might still - * not insert it. - * - * @param expr the expression to insert in the store - * @param value the value of the expression - * @param permitNondeterministic if false, returns false if {@code expr} is nondeterministic; if - * true, permits nondeterministic expressions to be placed in the store - * @return true if the given (expression, value) pair can be inserted in the store - */ - @EnsuresNonNullIf(expression = "#2", result = true) - protected boolean shouldInsert( - JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { - if (value == null) { - // No need to insert a null abstract value because it represents - // top and top is also the default value. - return false; - } - if (expr.containsUnknown()) { - // Expressions containing unknown expressions are not stored. - return false; - } - if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { - // Nondeterministic expressions may not be stored. - // (They are likely to be quickly evicted, as soon as a side-effecting method is - // called.) - return false; - } - return true; - } - - /** - * Helper method for {@link #insertValue(JavaExpression, CFAbstractValue)} and {@link - * #insertValuePermitNondeterministic}. - * - *

Every overriding implementation should start with - * - *

{@code
-     * if (!shouldInsert) {
-     *   return;
-     * }
-     * }
- * - * @param expr the expression to insert in the store - * @param value the value of the expression - * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if - * true, permits nondeterministic expressions to be placed in the store - */ - protected void insertValue( - JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { - computeNewValueAndInsert( - expr, - value, - (old, newValue) -> newValue.mostSpecific(old, null), - permitNondeterministic); - } - - /** - * Inserts the result of applying {@code merger} to {@code value} and the previous value for - * {@code expr}. - * - * @param expr the JavaExpression - * @param value the value of the JavaExpression - * @param merger the function used to merge {@code value} and the previous value of {@code expr} - * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if - * true, permits nondeterministic expressions to be placed in the store - */ - protected void computeNewValueAndInsert( - JavaExpression expr, - @Nullable V value, - BinaryOperator merger, - boolean permitNondeterministic) { - if (!shouldInsert(expr, value, permitNondeterministic)) { - return; - } - - if (expr instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) expr; - V oldValue = localVariableValues.get(localVar); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - localVariableValues.put(localVar, newValue); - } - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - // Only store information about final fields (where the receiver is - // also fixed) if concurrent semantics are enabled. - boolean isMonotonic = isMonotonicUpdate(fieldAcc, value); - if (sequentialSemantics || isMonotonic || fieldAcc.isUnassignableByOtherCode()) { - V oldValue = fieldValues.get(fieldAcc); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - fieldValues.put(fieldAcc, newValue); - } - } - } else if (expr instanceof MethodCall) { - MethodCall method = (MethodCall) expr; - // Don't store any information if concurrent semantics are enabled. - if (sequentialSemantics) { - V oldValue = methodValues.get(method); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - methodValues.put(method, newValue); - } - } - } else if (expr instanceof ArrayAccess) { - ArrayAccess arrayAccess = (ArrayAccess) expr; - if (sequentialSemantics) { - V oldValue = arrayValues.get(arrayAccess); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - arrayValues.put(arrayAccess, newValue); - } - } - } else if (expr instanceof ThisReference) { - ThisReference thisRef = (ThisReference) expr; - if (sequentialSemantics || thisRef.isUnassignableByOtherCode()) { - V oldValue = thisValue; - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - thisValue = newValue; - } - } - } else if (expr instanceof ClassName) { - ClassName className = (ClassName) expr; - if (sequentialSemantics || className.isUnassignableByOtherCode()) { - V oldValue = classValues.get(className); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - classValues.put(className, newValue); - } - } - } else { - // No other types of expressions need to be stored. - } + return true; + } + + /** + * Helper method for {@link #insertValue(JavaExpression, CFAbstractValue)} and {@link + * #insertValuePermitNondeterministic}. + * + *

Every overriding implementation should start with + * + *

{@code
+   * if (!shouldInsert) {
+   *   return;
+   * }
+   * }
+ * + * @param expr the expression to insert in the store + * @param value the value of the expression + * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if + * true, permits nondeterministic expressions to be placed in the store + */ + protected void insertValue( + JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { + computeNewValueAndInsert( + expr, value, (old, newValue) -> newValue.mostSpecific(old, null), permitNondeterministic); + } + + /** + * Inserts the result of applying {@code merger} to {@code value} and the previous value for + * {@code expr}. + * + * @param expr the JavaExpression + * @param value the value of the JavaExpression + * @param merger the function used to merge {@code value} and the previous value of {@code expr} + * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if + * true, permits nondeterministic expressions to be placed in the store + */ + protected void computeNewValueAndInsert( + JavaExpression expr, + @Nullable V value, + BinaryOperator merger, + boolean permitNondeterministic) { + if (!shouldInsert(expr, value, permitNondeterministic)) { + return; } - /** - * Return true if fieldAcc is an update of a monotonic qualifier to its target qualifier. - * (e.g. @MonotonicNonNull to @NonNull). Always returns false if {@code sequentialSemantics} is - * true. - * - * @return true if fieldAcc is an update of a monotonic qualifier to its target qualifier - * (e.g. @MonotonicNonNull to @NonNull) - */ - protected boolean isMonotonicUpdate(FieldAccess fieldAcc, V value) { - if (analysis.atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { - return false; + if (expr instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) expr; + V oldValue = localVariableValues.get(localVar); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + localVariableValues.put(localVar, newValue); + } + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + // Only store information about final fields (where the receiver is + // also fixed) if concurrent semantics are enabled. + boolean isMonotonic = isMonotonicUpdate(fieldAcc, value); + if (sequentialSemantics || isMonotonic || fieldAcc.isUnassignableByOtherCode()) { + V oldValue = fieldValues.get(fieldAcc); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + fieldValues.put(fieldAcc, newValue); } - boolean isMonotonic = false; - // TODO: This check for !sequentialSemantics is an optimization that breaks the contract of - // the method, since the method name and documentation say nothing about sequential - // semantics. This check should be performed by callers of this method when needed. - // TODO: Update the javadoc of this method when the above to-do item is addressed. - if (!sequentialSemantics) { // only compute if necessary - AnnotatedTypeFactory atypeFactory = this.analysis.atypeFactory; - List> fieldAnnotations = - atypeFactory.getAnnotationWithMetaAnnotation( - fieldAcc.getField(), MonotonicQualifier.class); - for (IPair fieldAnnotation : fieldAnnotations) { - AnnotationMirror metaAnnotation = fieldAnnotation.second; - @SuppressWarnings("deprecation") // permitted for use in the framework - Name annoName = - AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); - AnnotationMirror monotonicAnnotation = - AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); - // Make sure the 'target' annotation is present. - if (AnnotationUtils.containsSame(value.getAnnotations(), monotonicAnnotation)) { - isMonotonic = true; - break; - } - } + } + } else if (expr instanceof MethodCall) { + MethodCall method = (MethodCall) expr; + // Don't store any information if concurrent semantics are enabled. + if (sequentialSemantics) { + V oldValue = methodValues.get(method); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + methodValues.put(method, newValue); } - return isMonotonic; - } - - public void insertThisValue(AnnotationMirror a, TypeMirror underlyingType) { - if (a == null) { - return; + } + } else if (expr instanceof ArrayAccess) { + ArrayAccess arrayAccess = (ArrayAccess) expr; + if (sequentialSemantics) { + V oldValue = arrayValues.get(arrayAccess); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + arrayValues.put(arrayAccess, newValue); } - - V value = analysis.createSingleAnnotationValue(a, underlyingType); - + } + } else if (expr instanceof ThisReference) { + ThisReference thisRef = (ThisReference) expr; + if (sequentialSemantics || thisRef.isUnassignableByOtherCode()) { V oldValue = thisValue; - V newValue = value.mostSpecific(oldValue, null); + V newValue = merger.apply(oldValue, value); if (newValue != null) { - thisValue = newValue; + thisValue = newValue; } + } + } else if (expr instanceof ClassName) { + ClassName className = (ClassName) expr; + if (sequentialSemantics || className.isUnassignableByOtherCode()) { + V oldValue = classValues.get(className); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + classValues.put(className, newValue); + } + } + } else { + // No other types of expressions need to be stored. } - - /** - * Completely replaces the abstract value {@code value} for the expression {@code expr} - * (correctly deciding where to store the information depending on the type of the expression - * {@code expr}). Any previous information is discarded. - * - *

This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - */ - public void replaceValue(JavaExpression expr, @Nullable V value) { - clearValue(expr); - insertValue(expr, value); + } + + /** + * Return true if fieldAcc is an update of a monotonic qualifier to its target qualifier. + * (e.g. @MonotonicNonNull to @NonNull). Always returns false if {@code sequentialSemantics} is + * true. + * + * @return true if fieldAcc is an update of a monotonic qualifier to its target qualifier + * (e.g. @MonotonicNonNull to @NonNull) + */ + protected boolean isMonotonicUpdate(FieldAccess fieldAcc, V value) { + if (analysis.atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { + return false; } - - /** - * Remove any knowledge about the expression {@code expr} (correctly deciding where to remove - * the information depending on the type of the expression {@code expr}). - */ - public void clearValue(JavaExpression expr) { - if (expr.containsUnknown()) { - // Expressions containing unknown expressions are not stored. - return; - } - if (expr instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) expr; - localVariableValues.remove(localVar); - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - fieldValues.remove(fieldAcc); - } else if (expr instanceof MethodCall) { - MethodCall method = (MethodCall) expr; - methodValues.remove(method); - } else if (expr instanceof ArrayAccess) { - ArrayAccess a = (ArrayAccess) expr; - arrayValues.remove(a); - } else if (expr instanceof ClassName) { - ClassName c = (ClassName) expr; - classValues.remove(c); - } else { // thisValue ... - // No other types of expressions are stored. + boolean isMonotonic = false; + // TODO: This check for !sequentialSemantics is an optimization that breaks the contract of + // the method, since the method name and documentation say nothing about sequential + // semantics. This check should be performed by callers of this method when needed. + // TODO: Update the javadoc of this method when the above to-do item is addressed. + if (!sequentialSemantics) { // only compute if necessary + AnnotatedTypeFactory atypeFactory = this.analysis.atypeFactory; + List> fieldAnnotations = + atypeFactory.getAnnotationWithMetaAnnotation( + fieldAcc.getField(), MonotonicQualifier.class); + for (IPair fieldAnnotation : fieldAnnotations) { + AnnotationMirror metaAnnotation = fieldAnnotation.second; + @SuppressWarnings("deprecation") // permitted for use in the framework + Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); + AnnotationMirror monotonicAnnotation = + AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); + // Make sure the 'target' annotation is present. + if (AnnotationUtils.containsSame(value.getAnnotations(), monotonicAnnotation)) { + isMonotonic = true; + break; } + } } + return isMonotonic; + } - /** - * Returns the current abstract value of a Java expression, or {@code null} if no information is - * available. - * - * @return the current abstract value of a Java expression, or {@code null} if no information is - * available - */ - public @Nullable V getValue(JavaExpression expr) { - if (expr instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) expr; - return localVariableValues.get(localVar); - } else if (expr instanceof ThisReference) { - return thisValue; - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - return fieldValues.get(fieldAcc); - } else if (expr instanceof MethodCall) { - MethodCall method = (MethodCall) expr; - return methodValues.get(method); - } else if (expr instanceof ArrayAccess) { - ArrayAccess a = (ArrayAccess) expr; - return arrayValues.get(a); - } else if (expr instanceof ClassName) { - ClassName c = (ClassName) expr; - return classValues.get(c); - } else { - throw new BugInCF("Unexpected JavaExpression: " + expr + " (" + expr.getClass() + ")"); - } + public void insertThisValue(AnnotationMirror a, TypeMirror underlyingType) { + if (a == null) { + return; } - /** - * Returns the current abstract value of a field access, or {@code null} if no information is - * available. - * - * @param n the node whose abstract value to return - * @return the current abstract value of a field access, or {@code null} if no information is - * available - */ - public @Nullable V getValue(FieldAccessNode n) { - JavaExpression je = JavaExpression.fromNodeFieldAccess(n); - if (je instanceof FieldAccess) { - return fieldValues.get((FieldAccess) je); - } else if (je instanceof ClassName) { - return classValues.get((ClassName) je); - } else if (je instanceof ThisReference) { - // "return thisValue" is wrong, because the node refers to an outer this. - // So, return null for now. TODO: improve. - return null; - } else { - throw new BugInCF( - "Unexpected JavaExpression %s %s for FieldAccessNode %s", - je.getClass().getSimpleName(), je, n); - } - } + V value = analysis.createSingleAnnotationValue(a, underlyingType); - /** - * Returns the current abstract value of a field access, or {@code null} if no information is - * available. - * - * @param fieldAccess the field access to look up in this store - * @return current abstract value of a field access, or {@code null} if no information is - * available - */ - public @Nullable V getFieldValue(FieldAccess fieldAccess) { - return fieldValues.get(fieldAccess); + V oldValue = thisValue; + V newValue = value.mostSpecific(oldValue, null); + if (newValue != null) { + thisValue = newValue; } - - /** - * Returns the current abstract value of a method call, or {@code null} if no information is - * available. - * - * @param n a method call - * @return the current abstract value of a method call, or {@code null} if no information is - * available - */ - public @Nullable V getValue(MethodInvocationNode n) { - JavaExpression method = JavaExpression.fromNode(n); - if (method == null) { - return null; - } - return methodValues.get(method); + } + + /** + * Completely replaces the abstract value {@code value} for the expression {@code expr} (correctly + * deciding where to store the information depending on the type of the expression {@code expr}). + * Any previous information is discarded. + * + *

This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + */ + public void replaceValue(JavaExpression expr, @Nullable V value) { + clearValue(expr); + insertValue(expr, value); + } + + /** + * Remove any knowledge about the expression {@code expr} (correctly deciding where to remove the + * information depending on the type of the expression {@code expr}). + */ + public void clearValue(JavaExpression expr) { + if (expr.containsUnknown()) { + // Expressions containing unknown expressions are not stored. + return; } - - /** - * Returns the current abstract value of a field access, or {@code null} if no information is - * available. - * - * @param n the node whose abstract value to return - * @return the current abstract value of a field access, or {@code null} if no information is - * available - */ - public @Nullable V getValue(ArrayAccessNode n) { - ArrayAccess arrayAccess = JavaExpression.fromArrayAccess(n); - return arrayValues.get(arrayAccess); + if (expr instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) expr; + localVariableValues.remove(localVar); + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + fieldValues.remove(fieldAcc); + } else if (expr instanceof MethodCall) { + MethodCall method = (MethodCall) expr; + methodValues.remove(method); + } else if (expr instanceof ArrayAccess) { + ArrayAccess a = (ArrayAccess) expr; + arrayValues.remove(a); + } else if (expr instanceof ClassName) { + ClassName c = (ClassName) expr; + classValues.remove(c); + } else { // thisValue ... + // No other types of expressions are stored. } - - /** - * Update the information in the store by considering an assignment with target {@code n}. - * - * @param n the left-hand side of an assignment - * @param val the right-hand value of an assignment - */ - public void updateForAssignment(Node n, @Nullable V val) { - JavaExpression je = JavaExpression.fromNode(n); - if (je instanceof ArrayAccess) { - updateForArrayAssignment((ArrayAccess) je, val); - } else if (je instanceof FieldAccess) { - updateForFieldAccessAssignment((FieldAccess) je, val); - } else if (je instanceof LocalVariable) { - updateForLocalVariableAssignment((LocalVariable) je, val); - } else { - throw new BugInCF("Unexpected je of class " + je.getClass()); - } + } + + /** + * Returns the current abstract value of a Java expression, or {@code null} if no information is + * available. + * + * @return the current abstract value of a Java expression, or {@code null} if no information is + * available + */ + public @Nullable V getValue(JavaExpression expr) { + if (expr instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) expr; + return localVariableValues.get(localVar); + } else if (expr instanceof ThisReference) { + return thisValue; + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + return fieldValues.get(fieldAcc); + } else if (expr instanceof MethodCall) { + MethodCall method = (MethodCall) expr; + return methodValues.get(method); + } else if (expr instanceof ArrayAccess) { + ArrayAccess a = (ArrayAccess) expr; + return arrayValues.get(a); + } else if (expr instanceof ClassName) { + ClassName c = (ClassName) expr; + return classValues.get(c); + } else { + throw new BugInCF("Unexpected JavaExpression: " + expr + " (" + expr.getClass() + ")"); } - - /** - * Update the information in the store by considering a field assignment with target {@code n}, - * where the right hand side has the abstract value {@code val}. - * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void updateForFieldAccessAssignment(FieldAccess fieldAccess, @Nullable V val) { - removeConflicting(fieldAccess, val); - if (!fieldAccess.containsUnknown() && val != null) { - // Only store information about final fields (where the receiver is - // also fixed) if concurrent semantics are enabled. - if (sequentialSemantics - || isMonotonicUpdate(fieldAccess, val) - || fieldAccess.isUnassignableByOtherCode()) { - fieldValues.put(fieldAccess, val); - } - } + } + + /** + * Returns the current abstract value of a field access, or {@code null} if no information is + * available. + * + * @param n the node whose abstract value to return + * @return the current abstract value of a field access, or {@code null} if no information is + * available + */ + public @Nullable V getValue(FieldAccessNode n) { + JavaExpression je = JavaExpression.fromNodeFieldAccess(n); + if (je instanceof FieldAccess) { + return fieldValues.get((FieldAccess) je); + } else if (je instanceof ClassName) { + return classValues.get((ClassName) je); + } else if (je instanceof ThisReference) { + // "return thisValue" is wrong, because the node refers to an outer this. + // So, return null for now. TODO: improve. + return null; + } else { + throw new BugInCF( + "Unexpected JavaExpression %s %s for FieldAccessNode %s", + je.getClass().getSimpleName(), je, n); } - - /** - * Update the information in the store by considering an assignment with target {@code n}, where - * the target is an array access. - * - *

See {@link #removeConflicting(ArrayAccess,CFAbstractValue)}, as it is called first by this - * method. - */ - protected void updateForArrayAssignment(ArrayAccess arrayAccess, @Nullable V val) { - removeConflicting(arrayAccess, val); - if (!arrayAccess.containsUnknown() && val != null) { - // Only store information about final fields (where the receiver is - // also fixed) if concurrent semantics are enabled. - if (sequentialSemantics) { - arrayValues.put(arrayAccess, val); - } - } + } + + /** + * Returns the current abstract value of a field access, or {@code null} if no information is + * available. + * + * @param fieldAccess the field access to look up in this store + * @return current abstract value of a field access, or {@code null} if no information is + * available + */ + public @Nullable V getFieldValue(FieldAccess fieldAccess) { + return fieldValues.get(fieldAccess); + } + + /** + * Returns the current abstract value of a method call, or {@code null} if no information is + * available. + * + * @param n a method call + * @return the current abstract value of a method call, or {@code null} if no information is + * available + */ + public @Nullable V getValue(MethodInvocationNode n) { + JavaExpression method = JavaExpression.fromNode(n); + if (method == null) { + return null; } - - /** - * Set the abstract value of a local variable in the store. Overwrites any value that might have - * been available previously. - * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void updateForLocalVariableAssignment(LocalVariable receiver, @Nullable V val) { - removeConflicting(receiver); - if (val != null) { - localVariableValues.put(receiver, val); - } + return methodValues.get(method); + } + + /** + * Returns the current abstract value of a field access, or {@code null} if no information is + * available. + * + * @param n the node whose abstract value to return + * @return the current abstract value of a field access, or {@code null} if no information is + * available + */ + public @Nullable V getValue(ArrayAccessNode n) { + ArrayAccess arrayAccess = JavaExpression.fromArrayAccess(n); + return arrayValues.get(arrayAccess); + } + + /** + * Update the information in the store by considering an assignment with target {@code n}. + * + * @param n the left-hand side of an assignment + * @param val the right-hand value of an assignment + */ + public void updateForAssignment(Node n, @Nullable V val) { + JavaExpression je = JavaExpression.fromNode(n); + if (je instanceof ArrayAccess) { + updateForArrayAssignment((ArrayAccess) je, val); + } else if (je instanceof FieldAccess) { + updateForFieldAccessAssignment((FieldAccess) je, val); + } else if (je instanceof LocalVariable) { + updateForLocalVariableAssignment((LocalVariable) je, val); + } else { + throw new BugInCF("Unexpected je of class " + je.getClass()); } - - /** - * Remove any information in this store that might not be true any more after {@code - * fieldAccess} has been assigned a new value (with the abstract value {@code val}). This - * includes the following steps (assume that {@code fieldAccess} is of the form a.f for - * some a. - * - *

    - *
  1. Update the abstract value of other field accesses b.g where the - * field is equal (that is, f=g), and the receiver b might alias the - * receiver of {@code fieldAccess}, a. This update will raise the abstract value - * for such field accesses to at least {@code val} (or the old value, if that was less - * precise). However, this is only necessary if the field g is not final. - *
  2. Remove any abstract values for field accesses b.g where {@code - * fieldAccess} might alias any expression in the receiver b. - *
  3. Remove any information about method calls. - *
  4. Remove any abstract values an array access b[i] where {@code - * fieldAccess} might alias any expression in the receiver a or index i. - *
- * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void removeConflicting(FieldAccess fieldAccess, @Nullable V val) { - Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); - while (fieldValuesIterator.hasNext()) { - Map.Entry entry = fieldValuesIterator.next(); - FieldAccess otherFieldAccess = entry.getKey(); - V otherVal = entry.getValue(); - // case 2: - if (otherFieldAccess.getReceiver().containsModifiableAliasOf(this, fieldAccess)) { - fieldValuesIterator.remove(); // remove information completely - } - // case 1: - else if (fieldAccess.getField().equals(otherFieldAccess.getField())) { - if (canAlias(fieldAccess.getReceiver(), otherFieldAccess.getReceiver())) { - if (!otherFieldAccess.isFinal()) { - if (val != null) { - V newVal = val.leastUpperBound(otherVal); - entry.setValue(newVal); - } else { - // remove information completely - fieldValuesIterator.remove(); - } - } - } - } - } - - Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); - while (arrayValuesIterator.hasNext()) { - Map.Entry entry = arrayValuesIterator.next(); - ArrayAccess otherArrayAccess = entry.getKey(); - if (otherArrayAccess.containsModifiableAliasOf(this, fieldAccess)) { - // remove information completely - arrayValuesIterator.remove(); - } - } - - // case 3: - methodValues.clear(); + } + + /** + * Update the information in the store by considering a field assignment with target {@code n}, + * where the right hand side has the abstract value {@code val}. + * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void updateForFieldAccessAssignment(FieldAccess fieldAccess, @Nullable V val) { + removeConflicting(fieldAccess, val); + if (!fieldAccess.containsUnknown() && val != null) { + // Only store information about final fields (where the receiver is + // also fixed) if concurrent semantics are enabled. + if (sequentialSemantics + || isMonotonicUpdate(fieldAccess, val) + || fieldAccess.isUnassignableByOtherCode()) { + fieldValues.put(fieldAccess, val); + } } - - /** - * Remove any information in the store that might not be true any more after {@code arrayAccess} - * has been assigned a new value (with the abstract value {@code val}). This includes the - * following steps (assume that {@code arrayAccess} is of the form a[i] for some - * a. - * - *
    - *
  1. Remove any abstract value for other array access b[j] where - * a and b can be aliases, or where either b or j - * contains a modifiable alias of a[i]. - *
  2. Remove any abstract values for field accesses b.g where - * a[i] might alias any expression in the receiver b and there is an - * array expression somewhere in the receiver. - *
  3. Remove any information about method calls. - *
- * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) { - Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); - while (arrayValuesIterator.hasNext()) { - Map.Entry entry = arrayValuesIterator.next(); - ArrayAccess otherArrayAccess = entry.getKey(); - // case 1: - if (otherArrayAccess.containsModifiableAliasOf(this, arrayAccess)) { - arrayValuesIterator.remove(); // remove information completely - } else if (canAlias(arrayAccess.getArray(), otherArrayAccess.getArray())) { - // TODO: one could be less strict here, and only raise the abstract - // value for all array expressions with potentially aliasing receivers. - arrayValuesIterator.remove(); // remove information completely - } - } - - // case 2: - Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); - while (fieldValuesIterator.hasNext()) { - Map.Entry entry = fieldValuesIterator.next(); - FieldAccess otherFieldAccess = entry.getKey(); - JavaExpression otherReceiver = otherFieldAccess.getReceiver(); - if (otherReceiver.containsModifiableAliasOf(this, arrayAccess) - && otherReceiver.containsOfClass(ArrayAccess.class)) { - // remove information completely - fieldValuesIterator.remove(); - } - } - - // case 3: - methodValues.clear(); + } + + /** + * Update the information in the store by considering an assignment with target {@code n}, where + * the target is an array access. + * + *

See {@link #removeConflicting(ArrayAccess,CFAbstractValue)}, as it is called first by this + * method. + */ + protected void updateForArrayAssignment(ArrayAccess arrayAccess, @Nullable V val) { + removeConflicting(arrayAccess, val); + if (!arrayAccess.containsUnknown() && val != null) { + // Only store information about final fields (where the receiver is + // also fixed) if concurrent semantics are enabled. + if (sequentialSemantics) { + arrayValues.put(arrayAccess, val); + } } - - /** - * Remove any information in this store that might not be true any more after {@code localVar} - * has been assigned a new value. This includes the following steps: - * - *

    - *
  1. Remove any abstract values for field accesses b.g where {@code - * localVar} might alias any expression in the receiver b. - *
  2. Remove any abstract values for array accesses a[i] where {@code - * localVar} might alias the receiver a. - *
  3. Remove any information about method calls where the receiver or any of the - * parameters contains {@code localVar}. - *
- */ - protected void removeConflicting(LocalVariable var) { - Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); - while (fieldValuesIterator.hasNext()) { - Map.Entry entry = fieldValuesIterator.next(); - FieldAccess otherFieldAccess = entry.getKey(); - // case 1: - if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) { - fieldValuesIterator.remove(); - } - } - - Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); - while (arrayValuesIterator.hasNext()) { - Map.Entry entry = arrayValuesIterator.next(); - ArrayAccess otherArrayAccess = entry.getKey(); - // case 2: - if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) { - arrayValuesIterator.remove(); - } - } - - Iterator> methodValuesIterator = - methodValues.entrySet().iterator(); - while (methodValuesIterator.hasNext()) { - Map.Entry entry = methodValuesIterator.next(); - MethodCall otherMethodAccess = entry.getKey(); - // case 3: - if (otherMethodAccess.containsSyntacticEqualJavaExpression(var)) { - methodValuesIterator.remove(); + } + + /** + * Set the abstract value of a local variable in the store. Overwrites any value that might have + * been available previously. + * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void updateForLocalVariableAssignment(LocalVariable receiver, @Nullable V val) { + removeConflicting(receiver); + if (val != null) { + localVariableValues.put(receiver, val); + } + } + + /** + * Remove any information in this store that might not be true any more after {@code fieldAccess} + * has been assigned a new value (with the abstract value {@code val}). This includes the + * following steps (assume that {@code fieldAccess} is of the form a.f for some + * a. + * + *
    + *
  1. Update the abstract value of other field accesses b.g where the field + * is equal (that is, f=g), and the receiver b might alias the receiver of + * {@code fieldAccess}, a. This update will raise the abstract value for such field + * accesses to at least {@code val} (or the old value, if that was less precise). However, + * this is only necessary if the field g is not final. + *
  2. Remove any abstract values for field accesses b.g where {@code + * fieldAccess} might alias any expression in the receiver b. + *
  3. Remove any information about method calls. + *
  4. Remove any abstract values an array access b[i] where {@code + * fieldAccess} might alias any expression in the receiver a or index i. + *
+ * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void removeConflicting(FieldAccess fieldAccess, @Nullable V val) { + Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); + while (fieldValuesIterator.hasNext()) { + Map.Entry entry = fieldValuesIterator.next(); + FieldAccess otherFieldAccess = entry.getKey(); + V otherVal = entry.getValue(); + // case 2: + if (otherFieldAccess.getReceiver().containsModifiableAliasOf(this, fieldAccess)) { + fieldValuesIterator.remove(); // remove information completely + } + // case 1: + else if (fieldAccess.getField().equals(otherFieldAccess.getField())) { + if (canAlias(fieldAccess.getReceiver(), otherFieldAccess.getReceiver())) { + if (!otherFieldAccess.isFinal()) { + if (val != null) { + V newVal = val.leastUpperBound(otherVal); + entry.setValue(newVal); + } else { + // remove information completely + fieldValuesIterator.remove(); } + } } + } } - /** - * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., - * returns {@code true} if not enough information is available to determine aliasing). - */ - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - TypeMirror tb = b.getType(); - TypeMirror ta = a.getType(); - Types types = analysis.getTypes(); - return types.isSubtype(ta, tb) || types.isSubtype(tb, ta); + Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); + while (arrayValuesIterator.hasNext()) { + Map.Entry entry = arrayValuesIterator.next(); + ArrayAccess otherArrayAccess = entry.getKey(); + if (otherArrayAccess.containsModifiableAliasOf(this, fieldAccess)) { + // remove information completely + arrayValuesIterator.remove(); + } } - /* --------------------------------------------------------- */ - /* Handling of local variables */ - /* --------------------------------------------------------- */ - - /** - * Returns the current abstract value of a local variable, or {@code null} if no information is - * available. - * - * @param n the local variable - * @return the current abstract value of a local variable, or {@code null} if no information is - * available - */ - public @Nullable V getValue(LocalVariableNode n) { - VariableElement el = n.getElement(); - return localVariableValues.get(new LocalVariable(el)); + // case 3: + methodValues.clear(); + } + + /** + * Remove any information in the store that might not be true any more after {@code arrayAccess} + * has been assigned a new value (with the abstract value {@code val}). This includes the + * following steps (assume that {@code arrayAccess} is of the form a[i] for some + * a. + * + *
    + *
  1. Remove any abstract value for other array access b[j] where a + * and b can be aliases, or where either b or j contains a + * modifiable alias of a[i]. + *
  2. Remove any abstract values for field accesses b.g where a[i] + * might alias any expression in the receiver b and there is an array expression + * somewhere in the receiver. + *
  3. Remove any information about method calls. + *
+ * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) { + Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); + while (arrayValuesIterator.hasNext()) { + Map.Entry entry = arrayValuesIterator.next(); + ArrayAccess otherArrayAccess = entry.getKey(); + // case 1: + if (otherArrayAccess.containsModifiableAliasOf(this, arrayAccess)) { + arrayValuesIterator.remove(); // remove information completely + } else if (canAlias(arrayAccess.getArray(), otherArrayAccess.getArray())) { + // TODO: one could be less strict here, and only raise the abstract + // value for all array expressions with potentially aliasing receivers. + arrayValuesIterator.remove(); // remove information completely + } } - /* --------------------------------------------------------- */ - /* Handling of the current object */ - /* --------------------------------------------------------- */ - - /** - * Returns the current abstract value of the current object, or {@code null} if no information - * is available. - * - * @param n a reference to "this" - * @return the current abstract value of the current object, or {@code null} if no information - * is available - */ - public @Nullable V getValue(ThisNode n) { - return thisValue; + // case 2: + Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); + while (fieldValuesIterator.hasNext()) { + Map.Entry entry = fieldValuesIterator.next(); + FieldAccess otherFieldAccess = entry.getKey(); + JavaExpression otherReceiver = otherFieldAccess.getReceiver(); + if (otherReceiver.containsModifiableAliasOf(this, arrayAccess) + && otherReceiver.containsOfClass(ArrayAccess.class)) { + // remove information completely + fieldValuesIterator.remove(); + } } - /* --------------------------------------------------------- */ - /* Helper and miscellaneous methods */ - /* --------------------------------------------------------- */ - - @SuppressWarnings("unchecked") - @Override - public S copy() { - return analysis.createCopiedStore((S) this); + // case 3: + methodValues.clear(); + } + + /** + * Remove any information in this store that might not be true any more after {@code localVar} has + * been assigned a new value. This includes the following steps: + * + *
    + *
  1. Remove any abstract values for field accesses b.g where {@code + * localVar} might alias any expression in the receiver b. + *
  2. Remove any abstract values for array accesses a[i] where {@code + * localVar} might alias the receiver a. + *
  3. Remove any information about method calls where the receiver or any of the + * parameters contains {@code localVar}. + *
+ */ + protected void removeConflicting(LocalVariable var) { + Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); + while (fieldValuesIterator.hasNext()) { + Map.Entry entry = fieldValuesIterator.next(); + FieldAccess otherFieldAccess = entry.getKey(); + // case 1: + if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) { + fieldValuesIterator.remove(); + } } - @Override - public S leastUpperBound(S other) { - return upperBound(other, false); + Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); + while (arrayValuesIterator.hasNext()) { + Map.Entry entry = arrayValuesIterator.next(); + ArrayAccess otherArrayAccess = entry.getKey(); + // case 2: + if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) { + arrayValuesIterator.remove(); + } } - @Override - public S widenedUpperBound(S previous) { - return upperBound(previous, true); + Iterator> methodValuesIterator = methodValues.entrySet().iterator(); + while (methodValuesIterator.hasNext()) { + Map.Entry entry = methodValuesIterator.next(); + MethodCall otherMethodAccess = entry.getKey(); + // case 3: + if (otherMethodAccess.containsSyntacticEqualJavaExpression(var)) { + methodValuesIterator.remove(); + } } - - private S upperBound(S other, boolean shouldWiden) { - S newStore = analysis.createEmptyStore(sequentialSemantics); - - for (Map.Entry e : other.localVariableValues.entrySet()) { - // local variables that are only part of one store, but not the other are discarded, as - // one of store implicitly contains 'top' for that variable. - LocalVariable localVar = e.getKey(); - V thisVal = localVariableValues.get(localVar); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - - if (mergedVal != null) { - newStore.localVariableValues.put(localVar, mergedVal); - } - } + } + + /** + * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., + * returns {@code true} if not enough information is available to determine aliasing). + */ + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + TypeMirror tb = b.getType(); + TypeMirror ta = a.getType(); + Types types = analysis.getTypes(); + return types.isSubtype(ta, tb) || types.isSubtype(tb, ta); + } + + /* --------------------------------------------------------- */ + /* Handling of local variables */ + /* --------------------------------------------------------- */ + + /** + * Returns the current abstract value of a local variable, or {@code null} if no information is + * available. + * + * @param n the local variable + * @return the current abstract value of a local variable, or {@code null} if no information is + * available + */ + public @Nullable V getValue(LocalVariableNode n) { + VariableElement el = n.getElement(); + return localVariableValues.get(new LocalVariable(el)); + } + + /* --------------------------------------------------------- */ + /* Handling of the current object */ + /* --------------------------------------------------------- */ + + /** + * Returns the current abstract value of the current object, or {@code null} if no information is + * available. + * + * @param n a reference to "this" + * @return the current abstract value of the current object, or {@code null} if no information is + * available + */ + public @Nullable V getValue(ThisNode n) { + return thisValue; + } + + /* --------------------------------------------------------- */ + /* Helper and miscellaneous methods */ + /* --------------------------------------------------------- */ + + @SuppressWarnings("unchecked") + @Override + public S copy() { + return analysis.createCopiedStore((S) this); + } + + @Override + public S leastUpperBound(S other) { + return upperBound(other, false); + } + + @Override + public S widenedUpperBound(S previous) { + return upperBound(previous, true); + } + + private S upperBound(S other, boolean shouldWiden) { + S newStore = analysis.createEmptyStore(sequentialSemantics); + + for (Map.Entry e : other.localVariableValues.entrySet()) { + // local variables that are only part of one store, but not the other are discarded, as + // one of store implicitly contains 'top' for that variable. + LocalVariable localVar = e.getKey(); + V thisVal = localVariableValues.get(localVar); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + + if (mergedVal != null) { + newStore.localVariableValues.put(localVar, mergedVal); } - - // information about the current object - { - V otherVal = other.thisValue; - V myVal = thisValue; - V mergedVal = myVal == null ? null : upperBoundOfValues(otherVal, myVal, shouldWiden); - if (mergedVal != null) { - newStore.thisValue = mergedVal; - } - } - - for (Map.Entry e : other.fieldValues.entrySet()) { - // information about fields that are only part of one store, but not the other are - // discarded, as one store implicitly contains 'top' for that field. - FieldAccess el = e.getKey(); - V thisVal = fieldValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.fieldValues.put(el, mergedVal); - } - } - } - for (Map.Entry e : other.arrayValues.entrySet()) { - // information about arrays that are only part of one store, but not the other are - // discarded, as one store implicitly contains 'top' for that array access. - ArrayAccess el = e.getKey(); - V thisVal = arrayValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.arrayValues.put(el, mergedVal); - } - } - } - for (Map.Entry e : other.methodValues.entrySet()) { - // information about methods that are only part of one store, but not the other are - // discarded, as one store implicitly contains 'top' for that field. - MethodCall el = e.getKey(); - V thisVal = methodValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.methodValues.put(el, mergedVal); - } - } - } - for (Map.Entry e : other.classValues.entrySet()) { - ClassName el = e.getKey(); - V thisVal = classValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.classValues.put(el, mergedVal); - } - } - } - return newStore; + } } - private V upperBoundOfValues(V otherVal, V thisVal, boolean shouldWiden) { - return shouldWiden ? thisVal.widenUpperBound(otherVal) : thisVal.leastUpperBound(otherVal); + // information about the current object + { + V otherVal = other.thisValue; + V myVal = thisValue; + V mergedVal = myVal == null ? null : upperBoundOfValues(otherVal, myVal, shouldWiden); + if (mergedVal != null) { + newStore.thisValue = mergedVal; + } } - /** - * Returns true iff this {@link CFAbstractStore} contains a superset of the map entries of the - * argument {@link CFAbstractStore}. Note that we test the entry keys and values by Java - * equality, not by any subtype relationship. This method is used primarily to simplify the - * equals predicate. - */ - protected boolean supersetOf(CFAbstractStore other) { - for (Map.Entry e : other.localVariableValues.entrySet()) { - LocalVariable key = e.getKey(); - V value = localVariableValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } - } - if (!Objects.equals(thisValue, other.thisValue)) { - return false; + for (Map.Entry e : other.fieldValues.entrySet()) { + // information about fields that are only part of one store, but not the other are + // discarded, as one store implicitly contains 'top' for that field. + FieldAccess el = e.getKey(); + V thisVal = fieldValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.fieldValues.put(el, mergedVal); } - for (Map.Entry e : other.fieldValues.entrySet()) { - FieldAccess key = e.getKey(); - V value = fieldValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } - } - for (Map.Entry e : other.arrayValues.entrySet()) { - ArrayAccess key = e.getKey(); - V value = arrayValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } - } - for (Map.Entry e : other.methodValues.entrySet()) { - MethodCall key = e.getKey(); - V value = methodValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } + } + } + for (Map.Entry e : other.arrayValues.entrySet()) { + // information about arrays that are only part of one store, but not the other are + // discarded, as one store implicitly contains 'top' for that array access. + ArrayAccess el = e.getKey(); + V thisVal = arrayValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.arrayValues.put(el, mergedVal); } - for (Map.Entry e : other.classValues.entrySet()) { - ClassName key = e.getKey(); - V value = classValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } + } + } + for (Map.Entry e : other.methodValues.entrySet()) { + // information about methods that are only part of one store, but not the other are + // discarded, as one store implicitly contains 'top' for that field. + MethodCall el = e.getKey(); + V thisVal = methodValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.methodValues.put(el, mergedVal); } - return true; + } } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof CFAbstractStore) { - @SuppressWarnings("unchecked") - CFAbstractStore other = (CFAbstractStore) o; - return this.supersetOf(other) && other.supersetOf(this); - } else { - return false; + for (Map.Entry e : other.classValues.entrySet()) { + ClassName el = e.getKey(); + V thisVal = classValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.classValues.put(el, mergedVal); } + } } - - @Override - public int hashCode() { - // What is a good hash code to use? - return 22; + return newStore; + } + + private V upperBoundOfValues(V otherVal, V thisVal, boolean shouldWiden) { + return shouldWiden ? thisVal.widenUpperBound(otherVal) : thisVal.leastUpperBound(otherVal); + } + + /** + * Returns true iff this {@link CFAbstractStore} contains a superset of the map entries of the + * argument {@link CFAbstractStore}. Note that we test the entry keys and values by Java equality, + * not by any subtype relationship. This method is used primarily to simplify the equals + * predicate. + */ + protected boolean supersetOf(CFAbstractStore other) { + for (Map.Entry e : other.localVariableValues.entrySet()) { + LocalVariable key = e.getKey(); + V value = localVariableValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } } - - @SideEffectFree - @Override - public String toString() { - return visualize(new StringCFGVisualizer<>()); + if (!Objects.equals(thisValue, other.thisValue)) { + return false; } - - @Override - public String visualize(CFGVisualizer viz) { - /* This cast is guaranteed to be safe, as long as the CFGVisualizer is created by - * CFGVisualizer createCFGVisualizer() of GenericAnnotatedTypeFactory */ - @SuppressWarnings("unchecked") - CFGVisualizer castedViz = (CFGVisualizer) viz; - String internal = internalVisualize(castedViz); - if (internal.trim().isEmpty()) { - return this.getClassAndUid() + "()"; - } else { - return this.getClassAndUid() + "(" + viz.getSeparator() + internal + ")"; - } + for (Map.Entry e : other.fieldValues.entrySet()) { + FieldAccess key = e.getKey(); + V value = fieldValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } } - - /** - * Adds a representation of the internal information of this Store to visualizer {@code viz}. - * - * @param viz the visualizer - * @return a representation of the internal information of this {@link Store} - */ - protected String internalVisualize(CFGVisualizer viz) { - StringJoiner res = new StringJoiner(viz.getSeparator()); - for (LocalVariable lv : ToStringComparator.sorted(localVariableValues.keySet())) { - res.add(viz.visualizeStoreLocalVar(lv, localVariableValues.get(lv))); - } - if (thisValue != null) { - res.add(viz.visualizeStoreThisVal(thisValue)); - } - for (FieldAccess fa : ToStringComparator.sorted(fieldValues.keySet())) { - res.add(viz.visualizeStoreFieldVal(fa, fieldValues.get(fa))); - } - for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) { - res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa))); - } - for (MethodCall fa : ToStringComparator.sorted(methodValues.keySet())) { - res.add(viz.visualizeStoreMethodVals(fa, methodValues.get(fa))); - } - for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) { - res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa))); - } - return res.toString(); + for (Map.Entry e : other.arrayValues.entrySet()) { + ArrayAccess key = e.getKey(); + V value = arrayValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + for (Map.Entry e : other.methodValues.entrySet()) { + MethodCall key = e.getKey(); + V value = methodValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + for (Map.Entry e : other.classValues.entrySet()) { + ClassName key = e.getKey(); + V value = classValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + return true; + } + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof CFAbstractStore) { + @SuppressWarnings("unchecked") + CFAbstractStore other = (CFAbstractStore) o; + return this.supersetOf(other) && other.supersetOf(this); + } else { + return false; + } + } + + @Override + public int hashCode() { + // What is a good hash code to use? + return 22; + } + + @SideEffectFree + @Override + public String toString() { + return visualize(new StringCFGVisualizer<>()); + } + + @Override + public String visualize(CFGVisualizer viz) { + /* This cast is guaranteed to be safe, as long as the CFGVisualizer is created by + * CFGVisualizer createCFGVisualizer() of GenericAnnotatedTypeFactory */ + @SuppressWarnings("unchecked") + CFGVisualizer castedViz = (CFGVisualizer) viz; + String internal = internalVisualize(castedViz); + if (internal.trim().isEmpty()) { + return this.getClassAndUid() + "()"; + } else { + return this.getClassAndUid() + "(" + viz.getSeparator() + internal + ")"; + } + } + + /** + * Adds a representation of the internal information of this Store to visualizer {@code viz}. + * + * @param viz the visualizer + * @return a representation of the internal information of this {@link Store} + */ + protected String internalVisualize(CFGVisualizer viz) { + StringJoiner res = new StringJoiner(viz.getSeparator()); + for (LocalVariable lv : ToStringComparator.sorted(localVariableValues.keySet())) { + res.add(viz.visualizeStoreLocalVar(lv, localVariableValues.get(lv))); + } + if (thisValue != null) { + res.add(viz.visualizeStoreThisVal(thisValue)); + } + for (FieldAccess fa : ToStringComparator.sorted(fieldValues.keySet())) { + res.add(viz.visualizeStoreFieldVal(fa, fieldValues.get(fa))); + } + for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) { + res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa))); + } + for (MethodCall fa : ToStringComparator.sorted(methodValues.keySet())) { + res.add(viz.visualizeStoreMethodVals(fa, methodValues.get(fa))); + } + for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) { + res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa))); } + return res.toString(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java index 8130f4aff65..de74ab45690 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -6,7 +6,19 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; @@ -67,21 +79,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - /** * The default analysis transfer function for the Checker Framework. It propagates information * through assignments. It uses the {@link AnnotatedTypeFactory} to provide checker-specific logic @@ -95,1336 +92,1305 @@ * function logic themselves. */ public abstract class CFAbstractTransfer< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> - extends AbstractNodeVisitor, TransferInput> - implements ForwardTransferFunction { - - /** The analysis used by this transfer function. */ - protected final CFAbstractAnalysis analysis; - - /** - * Should the analysis use sequential Java semantics (i.e., assume that only one thread is - * running at all times)? - */ - protected final boolean sequentialSemantics; - - /* NO-AFU Indicates that the whole-program inference is on. */ + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> + extends AbstractNodeVisitor, TransferInput> + implements ForwardTransferFunction { + + /** The analysis used by this transfer function. */ + protected final CFAbstractAnalysis analysis; + + /** + * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running + * at all times)? + */ + protected final boolean sequentialSemantics; + + /* NO-AFU Indicates that the whole-program inference is on. */ + /* NO-AFU + private final boolean infer; + */ + + /** + * Create a CFAbstractTransfer. + * + * @param analysis the analysis used by this transfer function + */ + protected CFAbstractTransfer(CFAbstractAnalysis analysis) { + this(analysis, false); + } + + /** + * Constructor that allows forcing concurrent semantics to be on for this instance of + * CFAbstractTransfer. + * + * @param analysis the analysis used by this transfer function + * @param forceConcurrentSemantics whether concurrent semantics should be forced to be on. If + * false, concurrent semantics are turned off by default, but the user can still turn them on + * via {@code -AconcurrentSemantics}. If true, the user cannot turn off concurrent semantics. + */ + protected CFAbstractTransfer( + CFAbstractAnalysis analysis, boolean forceConcurrentSemantics) { + this.analysis = analysis; + this.sequentialSemantics = + !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics")); /* NO-AFU - private final boolean infer; + this.infer = analysis.checker.hasOption("infer"); */ - - /** - * Create a CFAbstractTransfer. - * - * @param analysis the analysis used by this transfer function - */ - protected CFAbstractTransfer(CFAbstractAnalysis analysis) { - this(analysis, false); - } - - /** - * Constructor that allows forcing concurrent semantics to be on for this instance of - * CFAbstractTransfer. - * - * @param analysis the analysis used by this transfer function - * @param forceConcurrentSemantics whether concurrent semantics should be forced to be on. If - * false, concurrent semantics are turned off by default, but the user can still turn them - * on via {@code -AconcurrentSemantics}. If true, the user cannot turn off concurrent - * semantics. - */ - protected CFAbstractTransfer( - CFAbstractAnalysis analysis, boolean forceConcurrentSemantics) { - this.analysis = analysis; - this.sequentialSemantics = - !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics")); - /* NO-AFU - this.infer = analysis.checker.hasOption("infer"); - */ - } - - /** - * Returns true if the transfer function uses sequential semantics, false if it uses concurrent - * semantics. Useful when creating an empty store, since a store makes different decisions - * depending on whether sequential or concurrent semantics are used. - * - * @return true if the transfer function uses sequential semantics, false if it uses concurrent - * semantics - */ - @Pure - public boolean usesSequentialSemantics() { - return sequentialSemantics; - } - - /** - * A hook for subclasses to modify the result of the transfer function. This method is called - * before returning the abstract value {@code value} as the result of the transfer function. - * - *

If a subclass overrides this method, the subclass should also override {@link - * #finishValue(CFAbstractValue,CFAbstractStore,CFAbstractStore)}. - * - * @param value a value to possibly modify - * @param store the store - * @return the possibly-modified value - */ - @SideEffectFree - protected @Nullable V finishValue(@Nullable V value, S store) { - return value; - } - - /** - * A hook for subclasses to modify the result of the transfer function. This method is called - * before returning the abstract value {@code value} as the result of the transfer function. - * - *

If a subclass overrides this method, the subclass should also override {@link - * #finishValue(CFAbstractValue,CFAbstractStore)}. - * - * @param value the value to finish - * @param thenStore the "then" store - * @param elseStore the "else" store - * @return the possibly-modified value - */ - @SideEffectFree - protected @Nullable V finishValue(@Nullable V value, S thenStore, S elseStore) { - return value; - } - - /** - * Returns the abstract value of a non-leaf tree {@code tree}, as computed by the {@link - * AnnotatedTypeFactory}. - * - * @return the abstract value of a non-leaf tree {@code tree}, as computed by the {@link - * AnnotatedTypeFactory} - */ - protected V getValueFromFactory(Tree tree, Node node) { - GenericAnnotatedTypeFactory> factory = - analysis.atypeFactory; - Tree preTree = analysis.getCurrentTree(); - analysis.setCurrentTree(tree); - AnnotatedTypeMirror at; - if (node instanceof MethodInvocationNode - && ((MethodInvocationNode) node).getIterableExpression() != null) { - ExpressionTree iter = ((MethodInvocationNode) node).getIterableExpression(); - at = factory.getIterableElementType(iter); - } else if (node instanceof ArrayAccessNode - && ((ArrayAccessNode) node).getArrayExpression() != null) { - ExpressionTree array = ((ArrayAccessNode) node).getArrayExpression(); - at = factory.getIterableElementType(array); - } else { - at = factory.getAnnotatedType(tree); - } - analysis.setCurrentTree(preTree); - return analysis.createAbstractValue(at); + } + + /** + * Returns true if the transfer function uses sequential semantics, false if it uses concurrent + * semantics. Useful when creating an empty store, since a store makes different decisions + * depending on whether sequential or concurrent semantics are used. + * + * @return true if the transfer function uses sequential semantics, false if it uses concurrent + * semantics + */ + @Pure + public boolean usesSequentialSemantics() { + return sequentialSemantics; + } + + /** + * A hook for subclasses to modify the result of the transfer function. This method is called + * before returning the abstract value {@code value} as the result of the transfer function. + * + *

If a subclass overrides this method, the subclass should also override {@link + * #finishValue(CFAbstractValue,CFAbstractStore,CFAbstractStore)}. + * + * @param value a value to possibly modify + * @param store the store + * @return the possibly-modified value + */ + @SideEffectFree + protected @Nullable V finishValue(@Nullable V value, S store) { + return value; + } + + /** + * A hook for subclasses to modify the result of the transfer function. This method is called + * before returning the abstract value {@code value} as the result of the transfer function. + * + *

If a subclass overrides this method, the subclass should also override {@link + * #finishValue(CFAbstractValue,CFAbstractStore)}. + * + * @param value the value to finish + * @param thenStore the "then" store + * @param elseStore the "else" store + * @return the possibly-modified value + */ + @SideEffectFree + protected @Nullable V finishValue(@Nullable V value, S thenStore, S elseStore) { + return value; + } + + /** + * Returns the abstract value of a non-leaf tree {@code tree}, as computed by the {@link + * AnnotatedTypeFactory}. + * + * @return the abstract value of a non-leaf tree {@code tree}, as computed by the {@link + * AnnotatedTypeFactory} + */ + protected V getValueFromFactory(Tree tree, Node node) { + GenericAnnotatedTypeFactory> factory = + analysis.atypeFactory; + Tree preTree = analysis.getCurrentTree(); + analysis.setCurrentTree(tree); + AnnotatedTypeMirror at; + if (node instanceof MethodInvocationNode + && ((MethodInvocationNode) node).getIterableExpression() != null) { + ExpressionTree iter = ((MethodInvocationNode) node).getIterableExpression(); + at = factory.getIterableElementType(iter); + } else if (node instanceof ArrayAccessNode + && ((ArrayAccessNode) node).getArrayExpression() != null) { + ExpressionTree array = ((ArrayAccessNode) node).getArrayExpression(); + at = factory.getIterableElementType(array); + } else { + at = factory.getAnnotatedType(tree); } - - /** The fixed initial store. */ - private @Nullable S fixedInitialStore = null; - - /** - * Set a fixed initial Store. - * - * @param s initial store; possible null - */ - public void setFixedInitialStore(@Nullable S s) { - fixedInitialStore = s; + analysis.setCurrentTree(preTree); + return analysis.createAbstractValue(at); + } + + /** The fixed initial store. */ + private @Nullable S fixedInitialStore = null; + + /** + * Set a fixed initial Store. + * + * @param s initial store; possible null + */ + public void setFixedInitialStore(@Nullable S s) { + fixedInitialStore = s; + } + + /** The initial store maps method formal parameters to their currently most refined type. */ + @Override + public S initialStore(UnderlyingAST underlyingAST, List parameters) { + if (underlyingAST.getKind() != UnderlyingAST.Kind.LAMBDA + && underlyingAST.getKind() != UnderlyingAST.Kind.METHOD) { + if (fixedInitialStore != null) { + return fixedInitialStore; + } else { + return analysis.createEmptyStore(sequentialSemantics); + } } - /** The initial store maps method formal parameters to their currently most refined type. */ - @Override - public S initialStore(UnderlyingAST underlyingAST, List parameters) { - if (underlyingAST.getKind() != UnderlyingAST.Kind.LAMBDA - && underlyingAST.getKind() != UnderlyingAST.Kind.METHOD) { - if (fixedInitialStore != null) { - return fixedInitialStore; - } else { - return analysis.createEmptyStore(sequentialSemantics); - } - } - - S store; - AnnotatedTypeFactory atypeFactory = analysis.getTypeFactory(); - - if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { - if (fixedInitialStore != null) { - // copy knowledge - store = analysis.createCopiedStore(fixedInitialStore); - } else { - store = analysis.createEmptyStore(sequentialSemantics); - } - - for (LocalVariableNode p : parameters) { - AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); - store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); - } + S store; + AnnotatedTypeFactory atypeFactory = analysis.getTypeFactory(); - // add properties known through precondition - CFGMethod method = (CFGMethod) underlyingAST; - MethodTree methodDeclTree = method.getMethod(); - ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree); - addInformationFromPreconditions( - store, atypeFactory, method, methodDeclTree, methodElem); - - addInitialFieldValues(store, method.getClassTree(), methodDeclTree); - - addFinalLocalValues(store, methodElem); - - /* NO-AFU - if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) { - Map overriddenMethods = - AnnotatedTypes.overriddenMethods( - atypeFactory.getElementUtils(), atypeFactory, methodElem); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedExecutableType overriddenMethod = - AnnotatedTypes.asMemberOf( - atypeFactory.getProcessingEnv().getTypeUtils(), - atypeFactory, - pair.getKey(), - pair.getValue()); - - // Infers parameter and receiver types of the method based - // on the overridden method. - atypeFactory - .getWholeProgramInference() - .updateFromOverride(methodDeclTree, methodElem, overriddenMethod); - } - } - */ - - } else if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { - if (fixedInitialStore != null) { - // Create a copy and keep only the field values (nothing else applies). - store = analysis.createCopiedStore(fixedInitialStore); - // Allow that local variables are retained; they are effectively final, - // otherwise Java wouldn't allow access from within the lambda. - // TODO: what about the other information? Can code further down be simplified? - // store.localVariableValues.clear(); - store.classValues.clear(); - store.arrayValues.clear(); - store.methodValues.clear(); - } else { - store = analysis.createEmptyStore(sequentialSemantics); - } - - for (LocalVariableNode p : parameters) { - AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); - store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); - } + if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + if (fixedInitialStore != null) { + // copy knowledge + store = analysis.createCopiedStore(fixedInitialStore); + } else { + store = analysis.createEmptyStore(sequentialSemantics); + } - CFGLambda lambda = (CFGLambda) underlyingAST; - @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests - @InternedDistinct Tree enclosingTree = - TreePathUtil.enclosingOfKind( - atypeFactory.getPath(lambda.getLambdaTree()), - TreeUtils.classAndMethodTreeKinds()); - - Element enclosingElement = null; - if (enclosingTree.getKind() == Tree.Kind.METHOD) { - // If it is in an initializer, we need to use locals from the initializer. - enclosingElement = TreeUtils.elementFromDeclaration((MethodTree) enclosingTree); - - } else if (TreeUtils.isClassTree(enclosingTree)) { - - // Try to find an enclosing initializer block. - // Would love to know if there was a better way. - // Find any enclosing element of the lambda (using trees). - // Then go up the elements to find an initializer element (which can't be found with - // the tree). - TreePath loopTree = atypeFactory.getPath(lambda.getLambdaTree()).getParentPath(); - Element anEnclosingElement = null; - while (loopTree.getLeaf() != enclosingTree) { - Element sym = TreeUtils.elementFromTree(loopTree.getLeaf()); - if (sym != null) { - anEnclosingElement = sym; - break; - } - loopTree = loopTree.getParentPath(); - } - while (anEnclosingElement != null - && !anEnclosingElement.equals(TreeUtils.elementFromTree(enclosingTree))) { - if (anEnclosingElement.getKind() == ElementKind.INSTANCE_INIT - || anEnclosingElement.getKind() == ElementKind.STATIC_INIT) { - enclosingElement = anEnclosingElement; - break; - } - anEnclosingElement = anEnclosingElement.getEnclosingElement(); - } - } - if (enclosingElement != null) { - addFinalLocalValues(store, enclosingElement); - } + for (LocalVariableNode p : parameters) { + AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); + store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); + } - // We want the initialization stuff, but need to throw out any refinements. - Map fieldValuesClone = new HashMap<>(store.fieldValues); - for (Map.Entry fieldValue : fieldValuesClone.entrySet()) { - AnnotatedTypeMirror declaredType = - atypeFactory.getAnnotatedType(fieldValue.getKey().getField()); - V lubbedValue = - analysis.createAbstractValue(declaredType) - .leastUpperBound(fieldValue.getValue()); - store.fieldValues.put(fieldValue.getKey(), lubbedValue); - } - } else { - assert false : "Unexpected tree: " + underlyingAST; - store = null; + // add properties known through precondition + CFGMethod method = (CFGMethod) underlyingAST; + MethodTree methodDeclTree = method.getMethod(); + ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree); + addInformationFromPreconditions(store, atypeFactory, method, methodDeclTree, methodElem); + + addInitialFieldValues(store, method.getClassTree(), methodDeclTree); + + addFinalLocalValues(store, methodElem); + + /* NO-AFU + if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) { + Map overriddenMethods = + AnnotatedTypes.overriddenMethods( + atypeFactory.getElementUtils(), atypeFactory, methodElem); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf( + atypeFactory.getProcessingEnv().getTypeUtils(), + atypeFactory, + pair.getKey(), + pair.getValue()); + + // Infers parameter and receiver types of the method based + // on the overridden method. + atypeFactory + .getWholeProgramInference() + .updateFromOverride(methodDeclTree, methodElem, overriddenMethod); } + } + */ + + } else if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { + if (fixedInitialStore != null) { + // Create a copy and keep only the field values (nothing else applies). + store = analysis.createCopiedStore(fixedInitialStore); + // Allow that local variables are retained; they are effectively final, + // otherwise Java wouldn't allow access from within the lambda. + // TODO: what about the other information? Can code further down be simplified? + // store.localVariableValues.clear(); + store.classValues.clear(); + store.arrayValues.clear(); + store.methodValues.clear(); + } else { + store = analysis.createEmptyStore(sequentialSemantics); + } - return store; - } - - /** - * Add field values to the initial store before {@code methodTree}. - * - *

The initializer value is inserted into {@code store} if the field is final and the field - * type is immutable, as defined by {@link AnnotatedTypeFactory#isImmutable(TypeMirror)}. - * - *

The declared value is inserted into {@code store} if: - * - *

    - *
  • {@code methodTree} is a constructor and the field has an initializer. (Use the - * declaration type rather than the initializer because an initialization block might have - * re-set it.) - *
  • {@code methodTree} is not a constructor and the receiver is fully initialized as - * determined by {@link #isNotFullyInitializedReceiver(MethodTree)}. - *
- * - * @param store initial store into which field values are inserted; it may not be empty - * @param classTree the class that contains {@code methodTree} - * @param methodTree the method or constructor tree - */ - // TODO: should field visibility matter? An access from outside the class might observe - // the declared type instead of a refined type. Issue a warning to alert users? - private void addInitialFieldValues(S store, ClassTree classTree, MethodTree methodTree) { - boolean isConstructor = TreeUtils.isConstructor(methodTree); - boolean isStaticMethod = - ElementUtils.isStatic(TreeUtils.elementFromDeclaration(methodTree)); - TypeElement classEle = TreeUtils.elementFromDeclaration(classTree); - for (FieldInitialValue fieldInitialValue : analysis.getFieldInitialValues()) { - VariableElement varEle = fieldInitialValue.fieldDecl.getField(); - boolean isStaticField = ElementUtils.isStatic(varEle); - if (isStaticMethod && !isStaticField) { - continue; - } - // Insert the value from the initializer of private final fields. - if (fieldInitialValue.initializer != null - // && varEle.getModifiers().contains(Modifier.PRIVATE) - && ElementUtils.isFinal(varEle) - && analysis.atypeFactory.isImmutable(ElementUtils.getType(varEle))) { - store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.initializer); - // The type from the initializer is always more specific than (or equal to) the - // declared type of the field. - // So, if there is an initializer, there is no point in inserting the declared type - // below. - continue; - } + for (LocalVariableNode p : parameters) { + AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); + store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); + } - boolean isFieldOfCurrentClass = varEle.getEnclosingElement().equals(classEle); - // Maybe insert the declared type: - if (!isConstructor) { - // If it's not a constructor, use the declared type if the receiver of the method is - // fully initialized. - boolean isInitializedReceiver = !isNotFullyInitializedReceiver(methodTree); - if (isInitializedReceiver && isFieldOfCurrentClass) { - store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); - } - } else { - // If it is a constructor, then only use the declared type if the field has been - // initialized. - if (fieldInitialValue.initializer != null && isFieldOfCurrentClass) { - store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); - } - } + CFGLambda lambda = (CFGLambda) underlyingAST; + @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests + @InternedDistinct Tree enclosingTree = + TreePathUtil.enclosingOfKind( + atypeFactory.getPath(lambda.getLambdaTree()), TreeUtils.classAndMethodTreeKinds()); + + Element enclosingElement = null; + if (enclosingTree.getKind() == Tree.Kind.METHOD) { + // If it is in an initializer, we need to use locals from the initializer. + enclosingElement = TreeUtils.elementFromDeclaration((MethodTree) enclosingTree); + + } else if (TreeUtils.isClassTree(enclosingTree)) { + + // Try to find an enclosing initializer block. + // Would love to know if there was a better way. + // Find any enclosing element of the lambda (using trees). + // Then go up the elements to find an initializer element (which can't be found with + // the tree). + TreePath loopTree = atypeFactory.getPath(lambda.getLambdaTree()).getParentPath(); + Element anEnclosingElement = null; + while (loopTree.getLeaf() != enclosingTree) { + Element sym = TreeUtils.elementFromTree(loopTree.getLeaf()); + if (sym != null) { + anEnclosingElement = sym; + break; + } + loopTree = loopTree.getParentPath(); } - } - - /** - * Adds information about effectively final variables (from outer scopes) - * - * @param store the store to add to - * @param enclosingElement the enclosing element of the code we are analyzing - */ - private void addFinalLocalValues(S store, Element enclosingElement) { - // add information about effectively final variables (from outer scopes) - for (Map.Entry e : - analysis.atypeFactory.getFinalLocalValues().entrySet()) { - - VariableElement elem = e.getKey(); - - // TODO: There is a design flaw where the values of final local values leaks - // into other methods of the same class. For example, in - // class a { void b() {...} void c() {...} } - // final local values from b() would be visible in the store for c(), - // even though they should only be visible in b() and in classes - // defined inside the method body of b(). - // This is partly because GenericAnnotatedTypeFactory.performFlowAnalysis does not call - // itself recursively to analyze inner classes, but instead pops classes off of a queue, - // and the information about known final local values is stored by - // GenericAnnotatedTypeFactory.analyze in GenericAnnotatedTypeFactory.flowResult, which - // is visible to all classes in the queue regardless of their level of recursion. - - // We work around this here by ensuring that we only add a final local value to a - // method's store if that method is enclosed by the method where the local variables - // were declared. - - // Find the enclosing method of the element - Element enclosingMethodOfVariableDeclaration = elem.getEnclosingElement(); - - if (enclosingMethodOfVariableDeclaration != null) { - - // Now find all the enclosing methods of the code we are analyzing. If any one of - // them matches the above, then the final local variable value applies. - Element enclosingMethodOfCurrentMethod = enclosingElement; - - while (enclosingMethodOfCurrentMethod != null) { - if (enclosingMethodOfVariableDeclaration.equals( - enclosingMethodOfCurrentMethod)) { - LocalVariable l = new LocalVariable(elem); - store.insertValue(l, e.getValue()); - break; - } - - enclosingMethodOfCurrentMethod = - enclosingMethodOfCurrentMethod.getEnclosingElement(); - } - } + while (anEnclosingElement != null + && !anEnclosingElement.equals(TreeUtils.elementFromTree(enclosingTree))) { + if (anEnclosingElement.getKind() == ElementKind.INSTANCE_INIT + || anEnclosingElement.getKind() == ElementKind.STATIC_INIT) { + enclosingElement = anEnclosingElement; + break; + } + anEnclosingElement = anEnclosingElement.getEnclosingElement(); } - } + } + if (enclosingElement != null) { + addFinalLocalValues(store, enclosingElement); + } - /** - * Returns true if the receiver of a method or constructor might not be fully initialized - * according to {@code analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree)}. - * - * @param methodDeclTree the declaration of the method or constructor - * @return true if the receiver of a method or constructor might not be fully initialized - * @see GenericAnnotatedTypeFactory#isNotFullyInitializedReceiver(MethodTree) - */ - @Pure - protected final boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { - return analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree); + // We want the initialization stuff, but need to throw out any refinements. + Map fieldValuesClone = new HashMap<>(store.fieldValues); + for (Map.Entry fieldValue : fieldValuesClone.entrySet()) { + AnnotatedTypeMirror declaredType = + atypeFactory.getAnnotatedType(fieldValue.getKey().getField()); + V lubbedValue = + analysis.createAbstractValue(declaredType).leastUpperBound(fieldValue.getValue()); + store.fieldValues.put(fieldValue.getKey(), lubbedValue); + } + } else { + assert false : "Unexpected tree: " + underlyingAST; + store = null; } - /** - * Add the information from all the preconditions of a method to the initial store in the method - * body. - * - * @param initialStore the initial store for the method body - * @param factory the type factory - * @param methodAst the AST for a method declaration - * @param methodDeclTree the declaration of the method; is a field of {@code methodAst} - * @param methodElement the element for the method - */ - protected void addInformationFromPreconditions( - S initialStore, - AnnotatedTypeFactory factory, - CFGMethod methodAst, - MethodTree methodDeclTree, - ExecutableElement methodElement) { - ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); - Set preconditions = contractsUtils.getPreconditions(methodElement); - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody( - stringExpr, methodDeclTree, analysis.checker); - for (Precondition p : preconditions) { - String stringExpr = p.expressionString; - AnnotationMirror annotation = - p.viewpointAdaptDependentTypeAnnotation( - analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); - JavaExpression exprJe; - try { - // TODO: currently, these expressions are parsed at the declaration (i.e. here) and - // for every use. this could be optimized to store the result the first time. - // (same for other annotations) - exprJe = - StringToJavaExpression.atMethodBody( - stringExpr, methodDeclTree, analysis.checker); - } catch (JavaExpressionParseException e) { - // Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration(). - continue; - } - initialStore.insertValuePermitNondeterministic(exprJe, annotation); - } - } + return store; + } + + /** + * Add field values to the initial store before {@code methodTree}. + * + *

The initializer value is inserted into {@code store} if the field is final and the field + * type is immutable, as defined by {@link AnnotatedTypeFactory#isImmutable(TypeMirror)}. + * + *

The declared value is inserted into {@code store} if: + * + *

    + *
  • {@code methodTree} is a constructor and the field has an initializer. (Use the + * declaration type rather than the initializer because an initialization block might have + * re-set it.) + *
  • {@code methodTree} is not a constructor and the receiver is fully initialized as + * determined by {@link #isNotFullyInitializedReceiver(MethodTree)}. + *
+ * + * @param store initial store into which field values are inserted; it may not be empty + * @param classTree the class that contains {@code methodTree} + * @param methodTree the method or constructor tree + */ + // TODO: should field visibility matter? An access from outside the class might observe + // the declared type instead of a refined type. Issue a warning to alert users? + private void addInitialFieldValues(S store, ClassTree classTree, MethodTree methodTree) { + boolean isConstructor = TreeUtils.isConstructor(methodTree); + boolean isStaticMethod = ElementUtils.isStatic(TreeUtils.elementFromDeclaration(methodTree)); + TypeElement classEle = TreeUtils.elementFromDeclaration(classTree); + for (FieldInitialValue fieldInitialValue : analysis.getFieldInitialValues()) { + VariableElement varEle = fieldInitialValue.fieldDecl.getField(); + boolean isStaticField = ElementUtils.isStatic(varEle); + if (isStaticMethod && !isStaticField) { + continue; + } + // Insert the value from the initializer of private final fields. + if (fieldInitialValue.initializer != null + // && varEle.getModifiers().contains(Modifier.PRIVATE) + && ElementUtils.isFinal(varEle) + && analysis.atypeFactory.isImmutable(ElementUtils.getType(varEle))) { + store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.initializer); + // The type from the initializer is always more specific than (or equal to) the + // declared type of the field. + // So, if there is an initializer, there is no point in inserting the declared type + // below. + continue; + } - /** - * The default visitor returns the input information unchanged, or in the case of conditional - * input information, merged. - */ - @Override - public TransferResult visitNode(Node n, TransferInput in) { - V value = null; - - // TODO: handle implicit/explicit this and go to correct factory method - Tree tree = n.getTree(); - if (tree != null) { - if (TreeUtils.canHaveTypeAnnotation(tree)) { - value = getValueFromFactory(tree, n); - } + boolean isFieldOfCurrentClass = varEle.getEnclosingElement().equals(classEle); + // Maybe insert the declared type: + if (!isConstructor) { + // If it's not a constructor, use the declared type if the receiver of the method is + // fully initialized. + boolean isInitializedReceiver = !isNotFullyInitializedReceiver(methodTree); + if (isInitializedReceiver && isFieldOfCurrentClass) { + store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); } - - return createTransferResult(value, in); - } - - /** - * Creates a TransferResult. - * - *

This default implementation returns the input information unchanged, or in the case of - * conditional input information, merged. - * - * @param value the value; possibly null - * @param in the transfer input - * @return the input information, as a TransferResult - */ - @SideEffectFree - protected TransferResult createTransferResult(@Nullable V value, TransferInput in) { - if (in.containsTwoStores()) { - S thenStore = in.getThenStore(); - S elseStore = in.getElseStore(); - return new ConditionalTransferResult<>( - finishValue(value, thenStore, elseStore), thenStore, elseStore); - } else { - S store = in.getRegularStore(); - return new RegularTransferResult<>(finishValue(value, store), store); + } else { + // If it is a constructor, then only use the declared type if the field has been + // initialized. + if (fieldInitialValue.initializer != null && isFieldOfCurrentClass) { + store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); } + } } + } + + /** + * Adds information about effectively final variables (from outer scopes) + * + * @param store the store to add to + * @param enclosingElement the enclosing element of the code we are analyzing + */ + private void addFinalLocalValues(S store, Element enclosingElement) { + // add information about effectively final variables (from outer scopes) + for (Map.Entry e : analysis.atypeFactory.getFinalLocalValues().entrySet()) { + + VariableElement elem = e.getKey(); + + // TODO: There is a design flaw where the values of final local values leaks + // into other methods of the same class. For example, in + // class a { void b() {...} void c() {...} } + // final local values from b() would be visible in the store for c(), + // even though they should only be visible in b() and in classes + // defined inside the method body of b(). + // This is partly because GenericAnnotatedTypeFactory.performFlowAnalysis does not call + // itself recursively to analyze inner classes, but instead pops classes off of a queue, + // and the information about known final local values is stored by + // GenericAnnotatedTypeFactory.analyze in GenericAnnotatedTypeFactory.flowResult, which + // is visible to all classes in the queue regardless of their level of recursion. + + // We work around this here by ensuring that we only add a final local value to a + // method's store if that method is enclosed by the method where the local variables + // were declared. + + // Find the enclosing method of the element + Element enclosingMethodOfVariableDeclaration = elem.getEnclosingElement(); + + if (enclosingMethodOfVariableDeclaration != null) { + + // Now find all the enclosing methods of the code we are analyzing. If any one of + // them matches the above, then the final local variable value applies. + Element enclosingMethodOfCurrentMethod = enclosingElement; + + while (enclosingMethodOfCurrentMethod != null) { + if (enclosingMethodOfVariableDeclaration.equals(enclosingMethodOfCurrentMethod)) { + LocalVariable l = new LocalVariable(elem); + store.insertValue(l, e.getValue()); + break; + } - /** - * Creates a TransferResult just like the given one, but with the given value. - * - *

This default implementation returns the input information unchanged, or in the case of - * conditional input information, merged. - * - * @param value the value; possibly null - * @param in the TransferResult to copy - * @return the input information - */ - @SideEffectFree - protected TransferResult recreateTransferResult( - @Nullable V value, TransferResult in) { - if (in.containsTwoStores()) { - S thenStore = in.getThenStore(); - S elseStore = in.getElseStore(); - return new ConditionalTransferResult<>( - finishValue(value, thenStore, elseStore), thenStore, elseStore); - } else { - S store = in.getRegularStore(); - return new RegularTransferResult<>(finishValue(value, store), store); + enclosingMethodOfCurrentMethod = enclosingMethodOfCurrentMethod.getEnclosingElement(); } + } } - - @Override - public TransferResult visitClassName(ClassNameNode n, TransferInput in) { - // The tree underlying a class name is a type tree. - V value = null; - - Tree tree = n.getTree(); - if (tree != null) { - if (TreeUtils.canHaveTypeAnnotation(tree)) { - GenericAnnotatedTypeFactory> - factory = analysis.atypeFactory; - analysis.setCurrentTree(tree); - AnnotatedTypeMirror at = factory.getAnnotatedTypeFromTypeTree(tree); - analysis.setCurrentTree(null); - value = analysis.createAbstractValue(at); - } - } - - return createTransferResult(value, in); + } + + /** + * Returns true if the receiver of a method or constructor might not be fully initialized + * according to {@code analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree)}. + * + * @param methodDeclTree the declaration of the method or constructor + * @return true if the receiver of a method or constructor might not be fully initialized + * @see GenericAnnotatedTypeFactory#isNotFullyInitializedReceiver(MethodTree) + */ + @Pure + protected final boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { + return analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree); + } + + /** + * Add the information from all the preconditions of a method to the initial store in the method + * body. + * + * @param initialStore the initial store for the method body + * @param factory the type factory + * @param methodAst the AST for a method declaration + * @param methodDeclTree the declaration of the method; is a field of {@code methodAst} + * @param methodElement the element for the method + */ + protected void addInformationFromPreconditions( + S initialStore, + AnnotatedTypeFactory factory, + CFGMethod methodAst, + MethodTree methodDeclTree, + ExecutableElement methodElement) { + ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); + Set preconditions = contractsUtils.getPreconditions(methodElement); + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker); + for (Precondition p : preconditions) { + String stringExpr = p.expressionString; + AnnotationMirror annotation = + p.viewpointAdaptDependentTypeAnnotation( + analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); + JavaExpression exprJe; + try { + // TODO: currently, these expressions are parsed at the declaration (i.e. here) and + // for every use. this could be optimized to store the result the first time. + // (same for other annotations) + exprJe = StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker); + } catch (JavaExpressionParseException e) { + // Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration(). + continue; + } + initialStore.insertValuePermitNondeterministic(exprJe, annotation); } - - @Override - public TransferResult visitFieldAccess(FieldAccessNode n, TransferInput p) { - S store = p.getRegularStore(); - V storeValue = store.getValue(n); - // look up value in factory, and take the more specific one - // TODO: handle cases, where this is not allowed (e.g. constructors in non-null type - // systems) - V factoryValue = getValueFromFactory(n.getTree(), n); - V value = moreSpecificValue(factoryValue, storeValue); - return new RegularTransferResult<>(finishValue(value, store), store); + } + + /** + * The default visitor returns the input information unchanged, or in the case of conditional + * input information, merged. + */ + @Override + public TransferResult visitNode(Node n, TransferInput in) { + V value = null; + + // TODO: handle implicit/explicit this and go to correct factory method + Tree tree = n.getTree(); + if (tree != null) { + if (TreeUtils.canHaveTypeAnnotation(tree)) { + value = getValueFromFactory(tree, n); + } } - @Override - public TransferResult visitArrayAccess(ArrayAccessNode n, TransferInput p) { - S store = p.getRegularStore(); - V storeValue = store.getValue(n); - // look up value in factory, and take the more specific one - V factoryValue = getValueFromFactory(n.getTree(), n); - V value = moreSpecificValue(factoryValue, storeValue); - return new RegularTransferResult<>(finishValue(value, store), store); + return createTransferResult(value, in); + } + + /** + * Creates a TransferResult. + * + *

This default implementation returns the input information unchanged, or in the case of + * conditional input information, merged. + * + * @param value the value; possibly null + * @param in the transfer input + * @return the input information, as a TransferResult + */ + @SideEffectFree + protected TransferResult createTransferResult(@Nullable V value, TransferInput in) { + if (in.containsTwoStores()) { + S thenStore = in.getThenStore(); + S elseStore = in.getElseStore(); + return new ConditionalTransferResult<>( + finishValue(value, thenStore, elseStore), thenStore, elseStore); + } else { + S store = in.getRegularStore(); + return new RegularTransferResult<>(finishValue(value, store), store); } - - /** Use the most specific type information available according to the store. */ - @Override - public TransferResult visitLocalVariable(LocalVariableNode n, TransferInput in) { - S store = in.getRegularStore(); - V valueFromStore = store.getValue(n); - V valueFromFactory = getValueFromFactory(n.getTree(), n); - V value = moreSpecificValue(valueFromFactory, valueFromStore); - return new RegularTransferResult<>(finishValue(value, store), store); + } + + /** + * Creates a TransferResult just like the given one, but with the given value. + * + *

This default implementation returns the input information unchanged, or in the case of + * conditional input information, merged. + * + * @param value the value; possibly null + * @param in the TransferResult to copy + * @return the input information + */ + @SideEffectFree + protected TransferResult recreateTransferResult( + @Nullable V value, TransferResult in) { + if (in.containsTwoStores()) { + S thenStore = in.getThenStore(); + S elseStore = in.getElseStore(); + return new ConditionalTransferResult<>( + finishValue(value, thenStore, elseStore), thenStore, elseStore); + } else { + S store = in.getRegularStore(); + return new RegularTransferResult<>(finishValue(value, store), store); } + } - @Override - public TransferResult visitThis(ThisNode n, TransferInput in) { - S store = in.getRegularStore(); - V valueFromStore = store.getValue(n); + @Override + public TransferResult visitClassName(ClassNameNode n, TransferInput in) { + // The tree underlying a class name is a type tree. + V value = null; - V valueFromFactory = null; - V value = null; - Tree tree = n.getTree(); - if (tree != null && TreeUtils.canHaveTypeAnnotation(tree)) { - valueFromFactory = getValueFromFactory(tree, n); - } - - if (valueFromFactory == null) { - value = valueFromStore; - } else { - value = moreSpecificValue(valueFromFactory, valueFromStore); - } - - return new RegularTransferResult<>(finishValue(value, store), store); + Tree tree = n.getTree(); + if (tree != null) { + if (TreeUtils.canHaveTypeAnnotation(tree)) { + GenericAnnotatedTypeFactory> factory = + analysis.atypeFactory; + analysis.setCurrentTree(tree); + AnnotatedTypeMirror at = factory.getAnnotatedTypeFromTypeTree(tree); + analysis.setCurrentTree(null); + value = analysis.createAbstractValue(at); + } } - @Override - public TransferResult visitTernaryExpression( - TernaryExpressionNode n, TransferInput p) { - TransferResult result = super.visitTernaryExpression(n, p); - S thenStore = result.getThenStore(); - S elseStore = result.getElseStore(); - - V thenValue = p.getValueOfSubNode(n.getThenOperand()); - V elseValue = p.getValueOfSubNode(n.getElseOperand()); - V resultValue = null; - if (thenValue != null && elseValue != null) { - // If a conditional expression is a poly expression, then its Java type is the type of - // its context. (For example, the type of the conditional expression in `Object o = b ? - // "" : "";` is `Object`, not `String`.) - // So, use the Java type of the conditional expression and the annotations for each - // branch. - TypeMirror conditionalType = TreeUtils.typeOf(n.getTree()); - // The resulting abstract value is the merge of the 'then' and 'else' branch. - resultValue = thenValue.leastUpperBound(elseValue, conditionalType); - } - V finishedValue = finishValue(resultValue, thenStore, elseStore); - return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore); + return createTransferResult(value, in); + } + + @Override + public TransferResult visitFieldAccess(FieldAccessNode n, TransferInput p) { + S store = p.getRegularStore(); + V storeValue = store.getValue(n); + // look up value in factory, and take the more specific one + // TODO: handle cases, where this is not allowed (e.g. constructors in non-null type + // systems) + V factoryValue = getValueFromFactory(n.getTree(), n); + V value = moreSpecificValue(factoryValue, storeValue); + return new RegularTransferResult<>(finishValue(value, store), store); + } + + @Override + public TransferResult visitArrayAccess(ArrayAccessNode n, TransferInput p) { + S store = p.getRegularStore(); + V storeValue = store.getValue(n); + // look up value in factory, and take the more specific one + V factoryValue = getValueFromFactory(n.getTree(), n); + V value = moreSpecificValue(factoryValue, storeValue); + return new RegularTransferResult<>(finishValue(value, store), store); + } + + /** Use the most specific type information available according to the store. */ + @Override + public TransferResult visitLocalVariable(LocalVariableNode n, TransferInput in) { + S store = in.getRegularStore(); + V valueFromStore = store.getValue(n); + V valueFromFactory = getValueFromFactory(n.getTree(), n); + V value = moreSpecificValue(valueFromFactory, valueFromStore); + return new RegularTransferResult<>(finishValue(value, store), store); + } + + @Override + public TransferResult visitThis(ThisNode n, TransferInput in) { + S store = in.getRegularStore(); + V valueFromStore = store.getValue(n); + + V valueFromFactory = null; + V value = null; + Tree tree = n.getTree(); + if (tree != null && TreeUtils.canHaveTypeAnnotation(tree)) { + valueFromFactory = getValueFromFactory(tree, n); } - @Override - public TransferResult visitSwitchExpressionNode( - SwitchExpressionNode n, TransferInput vsTransferInput) { - return visitLocalVariable(n.getSwitchExpressionVar(), vsTransferInput); + if (valueFromFactory == null) { + value = valueFromStore; + } else { + value = moreSpecificValue(valueFromFactory, valueFromStore); } - /** Reverse the role of the 'thenStore' and 'elseStore'. */ - @Override - public TransferResult visitConditionalNot(ConditionalNotNode n, TransferInput p) { - TransferResult result = super.visitConditionalNot(n, p); - S thenStore = result.getThenStore(); - S elseStore = result.getElseStore(); - return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore); + return new RegularTransferResult<>(finishValue(value, store), store); + } + + @Override + public TransferResult visitTernaryExpression( + TernaryExpressionNode n, TransferInput p) { + TransferResult result = super.visitTernaryExpression(n, p); + S thenStore = result.getThenStore(); + S elseStore = result.getElseStore(); + + V thenValue = p.getValueOfSubNode(n.getThenOperand()); + V elseValue = p.getValueOfSubNode(n.getElseOperand()); + V resultValue = null; + if (thenValue != null && elseValue != null) { + // If a conditional expression is a poly expression, then its Java type is the type of + // its context. (For example, the type of the conditional expression in `Object o = b ? + // "" : "";` is `Object`, not `String`.) + // So, use the Java type of the conditional expression and the annotations for each + // branch. + TypeMirror conditionalType = TreeUtils.typeOf(n.getTree()); + // The resulting abstract value is the merge of the 'then' and 'else' branch. + resultValue = thenValue.leastUpperBound(elseValue, conditionalType); } - - @Override - public TransferResult visitEqualTo(EqualToNode n, TransferInput p) { - TransferResult res = super.visitEqualTo(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - V leftV = p.getValueOfSubNode(leftN); - V rightV = p.getValueOfSubNode(rightN); - - if (res.containsTwoStores() - && (NodeUtils.isConstantBoolean(leftN, false) - || NodeUtils.isConstantBoolean(rightN, false))) { - S thenStore = res.getElseStore(); - S elseStore = res.getThenStore(); - res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); - } - - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); - return res; + V finishedValue = finishValue(resultValue, thenStore, elseStore); + return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore); + } + + @Override + public TransferResult visitSwitchExpressionNode( + SwitchExpressionNode n, TransferInput vsTransferInput) { + return visitLocalVariable(n.getSwitchExpressionVar(), vsTransferInput); + } + + /** Reverse the role of the 'thenStore' and 'elseStore'. */ + @Override + public TransferResult visitConditionalNot(ConditionalNotNode n, TransferInput p) { + TransferResult result = super.visitConditionalNot(n, p); + S thenStore = result.getThenStore(); + S elseStore = result.getElseStore(); + return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore); + } + + @Override + public TransferResult visitEqualTo(EqualToNode n, TransferInput p) { + TransferResult res = super.visitEqualTo(n, p); + + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + V leftV = p.getValueOfSubNode(leftN); + V rightV = p.getValueOfSubNode(rightN); + + if (res.containsTwoStores() + && (NodeUtils.isConstantBoolean(leftN, false) + || NodeUtils.isConstantBoolean(rightN, false))) { + S thenStore = res.getElseStore(); + S elseStore = res.getThenStore(); + res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); } - @Override - public TransferResult visitNotEqual(NotEqualNode n, TransferInput p) { - TransferResult res = super.visitNotEqual(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - V leftV = p.getValueOfSubNode(leftN); - V rightV = p.getValueOfSubNode(rightN); - - if (res.containsTwoStores() - && (NodeUtils.isConstantBoolean(leftN, true) - || NodeUtils.isConstantBoolean(rightN, true))) { - S thenStore = res.getElseStore(); - S elseStore = res.getThenStore(); - res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); - } - - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); - - return res; + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); + return res; + } + + @Override + public TransferResult visitNotEqual(NotEqualNode n, TransferInput p) { + TransferResult res = super.visitNotEqual(n, p); + + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + V leftV = p.getValueOfSubNode(leftN); + V rightV = p.getValueOfSubNode(rightN); + + if (res.containsTwoStores() + && (NodeUtils.isConstantBoolean(leftN, true) + || NodeUtils.isConstantBoolean(rightN, true))) { + S thenStore = res.getElseStore(); + S elseStore = res.getThenStore(); + res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); } - /** - * Refine the annotation of {@code secondNode} if the annotation {@code secondValue} is less - * precise than {@code firstValue}. This is possible, if {@code secondNode} is an expression - * that is tracked by the store (e.g., a local variable or a field). Clients usually call this - * twice with {@code firstNode} and {@code secondNode} reversed, to refine each of them. - * - *

Note that when overriding this method, when a new type is inserted into the store, {@link - * #splitAssignments} should be called, and the new type should be inserted into the store for - * each of the resulting nodes. - * - * @param firstNode the node that might be more precise - * @param secondNode the node whose type to possibly refine - * @param firstValue the abstract value that might be more precise - * @param secondValue the abstract value that might be less precise - * @param res the previous result - * @param notEqualTo if true, indicates that the logic is flipped (i.e., the information is - * added to the {@code elseStore} instead of the {@code thenStore}) for a not-equal - * comparison. - * @return the conditional transfer result (if information has been added), or {@code res} - */ - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult res, - Node firstNode, - Node secondNode, - V firstValue, - V secondValue, - boolean notEqualTo) { - if (firstValue != null) { - // Only need to insert if the second value is actually different. - if (!firstValue.equals(secondValue)) { - List secondParts = splitAssignments(secondNode); - for (Node secondPart : secondParts) { - JavaExpression secondInternal = JavaExpression.fromNode(secondPart); - if (!secondInternal.isDeterministic(analysis.atypeFactory)) { - continue; - } - if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { - S thenStore = res.getThenStore(); - S elseStore = res.getElseStore(); - if (notEqualTo) { - elseStore.insertValue(secondInternal, firstValue); - } else { - thenStore.insertValue(secondInternal, firstValue); - } - // To handle `(a = b = c) == x`, repeat for all insertable receivers of - // splitted assignments instead of returning. - res = - new ConditionalTransferResult<>( - res.getResultValue(), thenStore, elseStore); - } - } + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); + + return res; + } + + /** + * Refine the annotation of {@code secondNode} if the annotation {@code secondValue} is less + * precise than {@code firstValue}. This is possible, if {@code secondNode} is an expression that + * is tracked by the store (e.g., a local variable or a field). Clients usually call this twice + * with {@code firstNode} and {@code secondNode} reversed, to refine each of them. + * + *

Note that when overriding this method, when a new type is inserted into the store, {@link + * #splitAssignments} should be called, and the new type should be inserted into the store for + * each of the resulting nodes. + * + * @param firstNode the node that might be more precise + * @param secondNode the node whose type to possibly refine + * @param firstValue the abstract value that might be more precise + * @param secondValue the abstract value that might be less precise + * @param res the previous result + * @param notEqualTo if true, indicates that the logic is flipped (i.e., the information is added + * to the {@code elseStore} instead of the {@code thenStore}) for a not-equal comparison. + * @return the conditional transfer result (if information has been added), or {@code res} + */ + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult res, + Node firstNode, + Node secondNode, + V firstValue, + V secondValue, + boolean notEqualTo) { + if (firstValue != null) { + // Only need to insert if the second value is actually different. + if (!firstValue.equals(secondValue)) { + List secondParts = splitAssignments(secondNode); + for (Node secondPart : secondParts) { + JavaExpression secondInternal = JavaExpression.fromNode(secondPart); + if (!secondInternal.isDeterministic(analysis.atypeFactory)) { + continue; + } + if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { + S thenStore = res.getThenStore(); + S elseStore = res.getElseStore(); + if (notEqualTo) { + elseStore.insertValue(secondInternal, firstValue); + } else { + thenStore.insertValue(secondInternal, firstValue); } + // To handle `(a = b = c) == x`, repeat for all insertable receivers of + // splitted assignments instead of returning. + res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); + } } - return res; + } } - - /** - * Takes a node, and either returns the node itself again (as a singleton list), or if the node - * is an assignment node, returns the lhs and rhs (where splitAssignments is applied recursively - * to the rhs -- that is, it is possible that the rhs does not appear in the result, but rather - * its lhs and rhs do). - * - * @param node possibly an assignment node - * @return a list containing all the right- and left-hand sides in the given assignment node; it - * contains just the node itself if it is not an assignment) - */ - @SideEffectFree - protected List splitAssignments(Node node) { - if (node instanceof AssignmentNode) { - List result = new ArrayList<>(2); - AssignmentNode a = (AssignmentNode) node; - result.add(a.getTarget()); - result.addAll(splitAssignments(a.getExpression())); - return result; - } else { - return Collections.singletonList(node); - } + return res; + } + + /** + * Takes a node, and either returns the node itself again (as a singleton list), or if the node is + * an assignment node, returns the lhs and rhs (where splitAssignments is applied recursively to + * the rhs -- that is, it is possible that the rhs does not appear in the result, but rather its + * lhs and rhs do). + * + * @param node possibly an assignment node + * @return a list containing all the right- and left-hand sides in the given assignment node; it + * contains just the node itself if it is not an assignment) + */ + @SideEffectFree + protected List splitAssignments(Node node) { + if (node instanceof AssignmentNode) { + List result = new ArrayList<>(2); + AssignmentNode a = (AssignmentNode) node; + result.add(a.getTarget()); + result.addAll(splitAssignments(a.getExpression())); + return result; + } else { + return Collections.singletonList(node); } + } - @Override - public TransferResult visitAssignment(AssignmentNode n, TransferInput in) { - Node lhs = n.getTarget(); - Node rhs = n.getExpression(); - - V rhsValue = in.getValueOfSubNode(rhs); - - /* NO-AFU - if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) { - // Fields defined in interfaces are LocalVariableNodes with ElementKind of FIELD. - if (lhs instanceof FieldAccessNode - || (lhs instanceof LocalVariableNode - && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.FIELD)) { - // Updates inferred field type - analysis.atypeFactory.getWholeProgramInference().updateFromFieldAssignment(lhs, rhs); - } else if (lhs instanceof LocalVariableNode - && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) { - // lhs is a formal parameter of some method - VariableElement param = ((LocalVariableNode) lhs).getElement(); - analysis - .atypeFactory - .getWholeProgramInference() - .updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param); - } - */ - - if (n.isSynthetic() && in.containsTwoStores()) { - // This is a synthetic assignment node created for a ternary expression. In this case - // the `then` and `else` store are not merged. - S thenStore = in.getThenStore(); - S elseStore = in.getElseStore(); - processCommonAssignment(in, lhs, rhs, thenStore, rhsValue); - processCommonAssignment(in, lhs, rhs, elseStore, rhsValue); - return new ConditionalTransferResult<>( - finishValue(rhsValue, thenStore, elseStore), thenStore, elseStore); - } else { - S store = in.getRegularStore(); - processCommonAssignment(in, lhs, rhs, store, rhsValue); - return new RegularTransferResult<>(finishValue(rhsValue, store), store); - } - } + @Override + public TransferResult visitAssignment(AssignmentNode n, TransferInput in) { + Node lhs = n.getTarget(); + Node rhs = n.getExpression(); - @Override - public TransferResult visitReturn(ReturnNode n, TransferInput p) { - TransferResult result = super.visitReturn(n, p); - - /* NO-AFU - if (shouldPerformWholeProgramInference(n.getTree())) { - // Retrieves class containing the method - ClassTree classTree = analysis.getContainingClass(n.getTree()); - // classTree is null e.g. if this is a return statement in a lambda. - if (classTree == null) { - return result; - } - ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromDeclaration(classTree); + V rhsValue = in.getValueOfSubNode(rhs); - ExecutableElement methodElem = - TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree())); + /* NO-AFU + if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) { + // Fields defined in interfaces are LocalVariableNodes with ElementKind of FIELD. + if (lhs instanceof FieldAccessNode + || (lhs instanceof LocalVariableNode + && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.FIELD)) { + // Updates inferred field type + analysis.atypeFactory.getWholeProgramInference().updateFromFieldAssignment(lhs, rhs); + } else if (lhs instanceof LocalVariableNode + && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) { + // lhs is a formal parameter of some method + VariableElement param = ((LocalVariableNode) lhs).getElement(); + analysis + .atypeFactory + .getWholeProgramInference() + .updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param); + } + */ - Map overriddenMethods = - AnnotatedTypes.overriddenMethods( - analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem); + if (n.isSynthetic() && in.containsTwoStores()) { + // This is a synthetic assignment node created for a ternary expression. In this case + // the `then` and `else` store are not merged. + S thenStore = in.getThenStore(); + S elseStore = in.getElseStore(); + processCommonAssignment(in, lhs, rhs, thenStore, rhsValue); + processCommonAssignment(in, lhs, rhs, elseStore, rhsValue); + return new ConditionalTransferResult<>( + finishValue(rhsValue, thenStore, elseStore), thenStore, elseStore); + } else { + S store = in.getRegularStore(); + processCommonAssignment(in, lhs, rhs, store, rhsValue); + return new RegularTransferResult<>(finishValue(rhsValue, store), store); + } + } - // Updates the inferred return type of the method - analysis - .atypeFactory - .getWholeProgramInference() - .updateFromReturn( - n, classSymbol, analysis.getContainingMethod(n.getTree()), overriddenMethods); - } - */ + @Override + public TransferResult visitReturn(ReturnNode n, TransferInput p) { + TransferResult result = super.visitReturn(n, p); + /* NO-AFU + if (shouldPerformWholeProgramInference(n.getTree())) { + // Retrieves class containing the method + ClassTree classTree = analysis.getContainingClass(n.getTree()); + // classTree is null e.g. if this is a return statement in a lambda. + if (classTree == null) { return result; - } + } + ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromDeclaration(classTree); - @Override - public TransferResult visitLambdaResultExpression( - LambdaResultExpressionNode n, TransferInput in) { - return n.getResult().accept(this, in); - } + ExecutableElement methodElem = + TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree())); + + Map overriddenMethods = + AnnotatedTypes.overriddenMethods( + analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem); - /** - * Determine abstract value of right-hand side and update the store accordingly. - * - * @param in the store(s) before the assignment - * @param lhs the left-hand side of the assignment - * @param rhs the right-hand side of the assignment - * @param store the regular input store (from {@code in}) - * @param rhsValue the value of the right-hand side of the assignment - */ - protected void processCommonAssignment( - TransferInput in, Node lhs, Node rhs, S store, V rhsValue) { - - // update information in the store - store.updateForAssignment(lhs, rhsValue); + // Updates the inferred return type of the method + analysis + .atypeFactory + .getWholeProgramInference() + .updateFromReturn( + n, classSymbol, analysis.getContainingMethod(n.getTree()), overriddenMethods); } + */ - @Override - public TransferResult visitObjectCreation(ObjectCreationNode n, TransferInput p) { - /* NO-AFU - if (shouldPerformWholeProgramInference(n.getTree())) { - NewClassTree newClassTree = n.getTree(); - // Can't infer annotations on an anonymous constructor, so use the super constructor. - ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); - if (newClassTree.getClassBody() == null || !TreeUtils.hasSyntheticArgument(newClassTree)) { - // TODO: WPI could be changed to handle the synthetic argument, but for now just - // don't infer annotations for those new class trees. - analysis - .atypeFactory - .getWholeProgramInference() - .updateFromObjectCreation(n, constructorElt, p.getRegularStore()); - } - } - */ - NewClassTree newClassTree = n.getTree(); - ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); - S store = p.getRegularStore(); - // add new information based on postcondition - processPostconditions(n, store, constructorElt, newClassTree); - return super.visitObjectCreation(n, p); + return result; + } + + @Override + public TransferResult visitLambdaResultExpression( + LambdaResultExpressionNode n, TransferInput in) { + return n.getResult().accept(this, in); + } + + /** + * Determine abstract value of right-hand side and update the store accordingly. + * + * @param in the store(s) before the assignment + * @param lhs the left-hand side of the assignment + * @param rhs the right-hand side of the assignment + * @param store the regular input store (from {@code in}) + * @param rhsValue the value of the right-hand side of the assignment + */ + protected void processCommonAssignment( + TransferInput in, Node lhs, Node rhs, S store, V rhsValue) { + + // update information in the store + store.updateForAssignment(lhs, rhsValue); + } + + @Override + public TransferResult visitObjectCreation(ObjectCreationNode n, TransferInput p) { + /* NO-AFU + if (shouldPerformWholeProgramInference(n.getTree())) { + NewClassTree newClassTree = n.getTree(); + // Can't infer annotations on an anonymous constructor, so use the super constructor. + ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); + if (newClassTree.getClassBody() == null || !TreeUtils.hasSyntheticArgument(newClassTree)) { + // TODO: WPI could be changed to handle the synthetic argument, but for now just + // don't infer annotations for those new class trees. + analysis + .atypeFactory + .getWholeProgramInference() + .updateFromObjectCreation(n, constructorElt, p.getRegularStore()); + } } + */ + NewClassTree newClassTree = n.getTree(); + ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); + S store = p.getRegularStore(); + // add new information based on postcondition + processPostconditions(n, store, constructorElt, newClassTree); + return super.visitObjectCreation(n, p); + } - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { - S store = in.getRegularStore(); - ExecutableElement method = n.getTarget().getMethod(); + S store = in.getRegularStore(); + ExecutableElement method = n.getTarget().getMethod(); - /* NO-AFU - // Perform WPI before the store has been side-effected. - if (shouldPerformWholeProgramInference(n.getTree(), method)) { - // Updates the inferred parameter types of the invoked method. - analysis.atypeFactory - .getWholeProgramInference() - .updateFromMethodInvocation(n, method, store); - } - */ + /* NO-AFU + // Perform WPI before the store has been side-effected. + if (shouldPerformWholeProgramInference(n.getTree(), method)) { + // Updates the inferred parameter types of the invoked method. + analysis.atypeFactory + .getWholeProgramInference() + .updateFromMethodInvocation(n, method, store); + } + */ - ExpressionTree invocationTree = n.getTree(); + ExpressionTree invocationTree = n.getTree(); - // Determine the abstract value for the method call. - // look up the call's value from factory - V factoryValue = (invocationTree == null) ? null : getValueFromFactory(invocationTree, n); - // look up the call's value in the store (if possible) - V storeValue = store.getValue(n); - V resValue = moreSpecificValue(factoryValue, storeValue); + // Determine the abstract value for the method call. + // look up the call's value from factory + V factoryValue = (invocationTree == null) ? null : getValueFromFactory(invocationTree, n); + // look up the call's value in the store (if possible) + V storeValue = store.getValue(n); + V resValue = moreSpecificValue(factoryValue, storeValue); - store.updateForMethodCall(n, analysis.atypeFactory, resValue); + store.updateForMethodCall(n, analysis.atypeFactory, resValue); - // add new information based on postcondition - processPostconditions(n, store, method, invocationTree); + // add new information based on postcondition + processPostconditions(n, store, method, invocationTree); - if (TypesUtils.isBooleanType(method.getReturnType())) { - S thenStore = store; - S elseStore = thenStore.copy(); + if (TypesUtils.isBooleanType(method.getReturnType())) { + S thenStore = store; + S elseStore = thenStore.copy(); - // add new information based on conditional postcondition - processConditionalPostconditions(n, method, invocationTree, thenStore, elseStore); + // add new information based on conditional postcondition + processConditionalPostconditions(n, method, invocationTree, thenStore, elseStore); - return new ConditionalTransferResult<>( - finishValue(resValue, thenStore, elseStore), thenStore, elseStore); - } else { - return new RegularTransferResult<>(finishValue(resValue, store), store); - } + return new ConditionalTransferResult<>( + finishValue(resValue, thenStore, elseStore), thenStore, elseStore); + } else { + return new RegularTransferResult<>(finishValue(resValue, store), store); } - - @Override - public TransferResult visitDeconstructorPattern( - DeconstructorPatternNode n, TransferInput in) { - // TODO: Implement getting the type of a DeconstructorPatternTree. - V value = null; - return createTransferResult(value, in); + } + + @Override + public TransferResult visitDeconstructorPattern( + DeconstructorPatternNode n, TransferInput in) { + // TODO: Implement getting the type of a DeconstructorPatternTree. + V value = null; + return createTransferResult(value, in); + } + + @Override + public TransferResult visitInstanceOf(InstanceOfNode node, TransferInput in) { + TransferResult result = super.visitInstanceOf(node, in); + for (LocalVariableNode bindingVar : node.getBindingVariables()) { + JavaExpression expr = JavaExpression.fromNode(bindingVar); + AnnotatedTypeMirror expType = + analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); + for (AnnotationMirror anno : expType.getAnnotations()) { + in.getRegularStore().insertOrRefine(expr, anno); + } } - @Override - public TransferResult visitInstanceOf(InstanceOfNode node, TransferInput in) { - TransferResult result = super.visitInstanceOf(node, in); - for (LocalVariableNode bindingVar : node.getBindingVariables()) { - JavaExpression expr = JavaExpression.fromNode(bindingVar); - AnnotatedTypeMirror expType = - analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); - for (AnnotationMirror anno : expType.getAnnotations()) { - in.getRegularStore().insertOrRefine(expr, anno); - } - } - - // The "reference type" is the type after "instanceof". - Tree refTypeTree = node.getTree().getType(); - if (refTypeTree != null && refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeMirror refType = analysis.atypeFactory.getAnnotatedType(refTypeTree); - AnnotatedTypeMirror expType = - analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); - if (analysis.atypeFactory.getTypeHierarchy().isSubtype(refType, expType) - && !refType.getAnnotations().equals(expType.getAnnotations()) - && !expType.getAnnotations().isEmpty()) { - JavaExpression expr = JavaExpression.fromTree(node.getTree().getExpression()); - for (AnnotationMirror anno : refType.getAnnotations()) { - in.getRegularStore().insertOrRefine(expr, anno); - } - return new RegularTransferResult<>(result.getResultValue(), in.getRegularStore()); - } + // The "reference type" is the type after "instanceof". + Tree refTypeTree = node.getTree().getType(); + if (refTypeTree != null && refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeMirror refType = analysis.atypeFactory.getAnnotatedType(refTypeTree); + AnnotatedTypeMirror expType = + analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); + if (analysis.atypeFactory.getTypeHierarchy().isSubtype(refType, expType) + && !refType.getAnnotations().equals(expType.getAnnotations()) + && !expType.getAnnotations().isEmpty()) { + JavaExpression expr = JavaExpression.fromTree(node.getTree().getExpression()); + for (AnnotationMirror anno : refType.getAnnotations()) { + in.getRegularStore().insertOrRefine(expr, anno); } - return result; - } - - /* NO-AFU - * Returns true if whole-program inference should be performed. If the tree is in the scope of - * a @SuppressWarnings, then this method returns false. - * - * @param tree a tree - * @return whether to perform whole-program inference on the tree - */ - /* NO-AFU - protected boolean shouldPerformWholeProgramInference(Tree tree) { - TreePath path = this.analysis.atypeFactory.getPath(tree); - return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(path, "")); - } - */ - - /* NO-AFU - * Returns true if whole-program inference should be performed. If the expressionTree or lhsTree - * is in the scope of a @SuppressWarnings, then this method returns false. - * - * @param expressionTree the right-hand side of an assignment - * @param lhsTree the left-hand side of an assignment - * @return whether to perform whole-program inference - */ - /* NO-AFU - protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { - // Check that infer is true and the tree isn't in scope of a @SuppressWarnings - // before calling InternalUtils.symbol(lhs). - if (!shouldPerformWholeProgramInference(expressionTree)) { - return false; + return new RegularTransferResult<>(result.getResultValue(), in.getRegularStore()); } - Element elt = TreeUtils.elementFromTree(lhsTree); - return !analysis.checker.shouldSuppressWarnings(elt, ""); - } - */ - - /* NO-AFU - * Returns true if whole-program inference should be performed. If the tree or element is in the - * scope of a @SuppressWarnings, then this method returns false. - * - * @param tree a tree - * @param elt its element - * @return whether to perform whole-program inference - */ - /* NO-AFU - private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) { - return shouldPerformWholeProgramInference(tree) - && !analysis.checker.shouldSuppressWarnings(elt, ""); } - */ - - /** - * Add information from the postconditions of a method to the store after an invocation. - * - * @param invocationNode a method call or an object creation - * @param store a store; is side-effected by this method - * @param executableElement the method or constructor being called - * @param invocationTree the tree for the method call or for the object creation - */ - protected void processPostconditions( - Node invocationNode, - S store, - ExecutableElement executableElement, - ExpressionTree invocationTree) { - ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); - Set postconditions = contractsUtils.getPostconditions(executableElement); - processPostconditionsAndConditionalPostconditions( - invocationNode, invocationTree, store, null, postconditions); + return result; + } + + /* NO-AFU + * Returns true if whole-program inference should be performed. If the tree is in the scope of + * a @SuppressWarnings, then this method returns false. + * + * @param tree a tree + * @return whether to perform whole-program inference on the tree + */ + /* NO-AFU + protected boolean shouldPerformWholeProgramInference(Tree tree) { + TreePath path = this.analysis.atypeFactory.getPath(tree); + return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(path, "")); + } + */ + + /* NO-AFU + * Returns true if whole-program inference should be performed. If the expressionTree or lhsTree + * is in the scope of a @SuppressWarnings, then this method returns false. + * + * @param expressionTree the right-hand side of an assignment + * @param lhsTree the left-hand side of an assignment + * @return whether to perform whole-program inference + */ + /* NO-AFU + protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { + // Check that infer is true and the tree isn't in scope of a @SuppressWarnings + // before calling InternalUtils.symbol(lhs). + if (!shouldPerformWholeProgramInference(expressionTree)) { + return false; } - - /** - * Add information from the conditional postconditions of a method to the stores after an - * invocation. - * - * @param invocationNode a method call - * @param methodElement the method being called - * @param invocationTree the tree for the method call - * @param thenStore the "then" store; is side-effected by this method - * @param elseStore the "else" store; is side-effected by this method - */ - protected void processConditionalPostconditions( - MethodInvocationNode invocationNode, - ExecutableElement methodElement, - ExpressionTree invocationTree, - S thenStore, - S elseStore) { - ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); - Set conditionalPostconditions = - contractsUtils.getConditionalPostconditions(methodElement); - processPostconditionsAndConditionalPostconditions( - invocationNode, invocationTree, thenStore, elseStore, conditionalPostconditions); + Element elt = TreeUtils.elementFromTree(lhsTree); + return !analysis.checker.shouldSuppressWarnings(elt, ""); + } + */ + + /* NO-AFU + * Returns true if whole-program inference should be performed. If the tree or element is in the + * scope of a @SuppressWarnings, then this method returns false. + * + * @param tree a tree + * @param elt its element + * @return whether to perform whole-program inference + */ + /* NO-AFU + private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) { + return shouldPerformWholeProgramInference(tree) + && !analysis.checker.shouldSuppressWarnings(elt, ""); + } + */ + + /** + * Add information from the postconditions of a method to the store after an invocation. + * + * @param invocationNode a method call or an object creation + * @param store a store; is side-effected by this method + * @param executableElement the method or constructor being called + * @param invocationTree the tree for the method call or for the object creation + */ + protected void processPostconditions( + Node invocationNode, + S store, + ExecutableElement executableElement, + ExpressionTree invocationTree) { + ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); + Set postconditions = contractsUtils.getPostconditions(executableElement); + processPostconditionsAndConditionalPostconditions( + invocationNode, invocationTree, store, null, postconditions); + } + + /** + * Add information from the conditional postconditions of a method to the stores after an + * invocation. + * + * @param invocationNode a method call + * @param methodElement the method being called + * @param invocationTree the tree for the method call + * @param thenStore the "then" store; is side-effected by this method + * @param elseStore the "else" store; is side-effected by this method + */ + protected void processConditionalPostconditions( + MethodInvocationNode invocationNode, + ExecutableElement methodElement, + ExpressionTree invocationTree, + S thenStore, + S elseStore) { + ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); + Set conditionalPostconditions = + contractsUtils.getConditionalPostconditions(methodElement); + processPostconditionsAndConditionalPostconditions( + invocationNode, invocationTree, thenStore, elseStore, conditionalPostconditions); + } + + /** + * Add information from the postconditions and conditional postconditions of a method to the + * stores after an invocation. + * + * @param invocationNode a method call node or an object creation node + * @param invocationTree the tree for the method call or for the object creation + * @param thenStore the "then" store; is side-effected by this method + * @param elseStore the "else" store; is side-effected by this method + * @param postconditions the postconditions + */ + private void processPostconditionsAndConditionalPostconditions( + Node invocationNode, + ExpressionTree invocationTree, + S thenStore, + S elseStore, + Set postconditions) { + + StringToJavaExpression stringToJavaExpr = null; + if (invocationNode instanceof MethodInvocationNode) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodInvocation( + stringExpr, (MethodInvocationNode) invocationNode, analysis.checker); + } else if (invocationNode instanceof ObjectCreationNode) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atConstructorInvocation( + stringExpr, (NewClassTree) invocationTree, analysis.checker); + } else { + throw new BugInCF( + "CFAbstractTransfer.processPostconditionsAndConditionalPostconditions" + + " expects a MethodInvocationNode or ObjectCreationNode argument;" + + " received a " + + invocationNode.getClass().getSimpleName()); } - /** - * Add information from the postconditions and conditional postconditions of a method to the - * stores after an invocation. - * - * @param invocationNode a method call node or an object creation node - * @param invocationTree the tree for the method call or for the object creation - * @param thenStore the "then" store; is side-effected by this method - * @param elseStore the "else" store; is side-effected by this method - * @param postconditions the postconditions - */ - private void processPostconditionsAndConditionalPostconditions( - Node invocationNode, - ExpressionTree invocationTree, - S thenStore, - S elseStore, - Set postconditions) { - - StringToJavaExpression stringToJavaExpr = null; - if (invocationNode instanceof MethodInvocationNode) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodInvocation( - stringExpr, - (MethodInvocationNode) invocationNode, - analysis.checker); - } else if (invocationNode instanceof ObjectCreationNode) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atConstructorInvocation( - stringExpr, (NewClassTree) invocationTree, analysis.checker); + for (Contract p : postconditions) { + // Viewpoint-adapt to the method use (the call site). + AnnotationMirror anno = + p.viewpointAdaptDependentTypeAnnotation( + analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); + + String expressionString = p.expressionString; + try { + JavaExpression je = stringToJavaExpr.toJavaExpression(expressionString); + + // "insertOrRefine" is called so that the postcondition information is added to any + // existing information rather than replacing it. If the called method is not + // side-effect-free, then the values that might have been changed by the method call + // are removed from the store before this method is called. + if (p.kind == Contract.Kind.CONDITIONALPOSTCONDITION) { + if (((ConditionalPostcondition) p).resultValue) { + thenStore.insertOrRefinePermitNondeterministic(je, anno); + } else { + elseStore.insertOrRefinePermitNondeterministic(je, anno); + } } else { - throw new BugInCF( - "CFAbstractTransfer.processPostconditionsAndConditionalPostconditions" - + " expects a MethodInvocationNode or ObjectCreationNode argument;" - + " received a " - + invocationNode.getClass().getSimpleName()); - } - - for (Contract p : postconditions) { - // Viewpoint-adapt to the method use (the call site). - AnnotationMirror anno = - p.viewpointAdaptDependentTypeAnnotation( - analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); - - String expressionString = p.expressionString; - try { - JavaExpression je = stringToJavaExpr.toJavaExpression(expressionString); - - // "insertOrRefine" is called so that the postcondition information is added to any - // existing information rather than replacing it. If the called method is not - // side-effect-free, then the values that might have been changed by the method call - // are removed from the store before this method is called. - if (p.kind == Contract.Kind.CONDITIONALPOSTCONDITION) { - if (((ConditionalPostcondition) p).resultValue) { - thenStore.insertOrRefinePermitNondeterministic(je, anno); - } else { - elseStore.insertOrRefinePermitNondeterministic(je, anno); - } - } else { - thenStore.insertOrRefinePermitNondeterministic(je, anno); - } - } catch (JavaExpressionParseException e) { - // report errors here - if (e.isFlowParseError()) { - Object[] args = new Object[e.args.length + 1]; - args[0] = - ElementUtils.getSimpleSignature( - (ExecutableElement) TreeUtils.elementFromUse(invocationTree)); - System.arraycopy(e.args, 0, args, 1, e.args.length); - analysis.checker.reportError( - invocationTree, "flowexpr.parse.error.postcondition", args); - } else { - analysis.checker.report(invocationTree, e.getDiagMessage()); - } - } + thenStore.insertOrRefinePermitNondeterministic(je, anno); } - } - - /** A case produces no value, but it may imply some facts about switch selector expression. */ - @Override - public TransferResult visitCase(CaseNode n, TransferInput in) { - S store = in.getRegularStore(); - TransferResult lubResult = null; - // Case operands are the case constants. For example, A, B and C in case A, B, C:. - // This method refines the type of the selector expression and the synthetic variable that - // represents the selector expression to the type of the case constant if it is more - // precise. - // If there are multiple case constants then a new store is created for each case constant - // and then they are lubbed. This method returns the lubbed result. - for (Node caseOperand : n.getCaseOperands()) { - TransferResult result = - new ConditionalTransferResult<>( - finishValue(null, store), - in.getThenStore().copy(), - in.getElseStore().copy(), - false); - V caseValue = in.getValueOfSubNode(caseOperand); - AssignmentNode assign = n.getSwitchOperand(); - V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget())); - result = - strengthenAnnotationOfEqualTo( - result, - caseOperand, - assign.getExpression(), - caseValue, - switchValue, - false); - // Update value of switch temporary variable - result = - strengthenAnnotationOfEqualTo( - result, caseOperand, assign.getTarget(), caseValue, switchValue, false); - - // Lub the result of one case label constant with the result of the others. - if (lubResult == null) { - lubResult = result; - } else { - S thenStore = lubResult.getThenStore().leastUpperBound(result.getThenStore()); - S elseStore = lubResult.getElseStore().leastUpperBound(result.getElseStore()); - lubResult = - new ConditionalTransferResult<>( - null, - thenStore, - elseStore, - lubResult.storeChanged() || result.storeChanged()); - } - } - return lubResult; - } - - /** - * In a cast {@code (@A C) e} of some expression {@code e} to a new type {@code @A C}, we - * usually take the annotation of the type {@code C} (here {@code @A}). However, if the inferred - * annotation of {@code e} is more precise, we keep that one. - */ - // @Override - // public TransferResult visitTypeCast(TypeCastNode n, - // TransferInput p) { - // TransferResult result = super.visitTypeCast(n, p); - // V value = result.getResultValue(); - // V operandValue = p.getValueOfSubNode(n.getOperand()); - // // Normally we take the value of the type cast node. However, if the old - // // flow-refined value was more precise, we keep that value. - // V resultValue = moreSpecificValue(value, operandValue); - // result.setResultValue(resultValue); - // return result; - // } - - /** - * Returns the abstract value of {@code (value1, value2)} that is more specific. If the two are - * incomparable, then {@code value1} is returned. - * - * @param value1 an abstract value - * @param value2 another abstract value - * @return the more specific value of the two parameters, or, if they are incomparable, {@code - * value1} - */ - @Pure - public V moreSpecificValue(V value1, V value2) { - if (value1 == null) { - return value2; - } - if (value2 == null) { - return value1; + } catch (JavaExpressionParseException e) { + // report errors here + if (e.isFlowParseError()) { + Object[] args = new Object[e.args.length + 1]; + args[0] = + ElementUtils.getSimpleSignature( + (ExecutableElement) TreeUtils.elementFromUse(invocationTree)); + System.arraycopy(e.args, 0, args, 1, e.args.length); + analysis.checker.reportError(invocationTree, "flowexpr.parse.error.postcondition", args); + } else { + analysis.checker.report(invocationTree, e.getDiagMessage()); } - return value1.mostSpecific(value2, value1); - } - - @Override - public TransferResult visitVariableDeclaration( - VariableDeclarationNode n, TransferInput p) { - S store = p.getRegularStore(); - return new RegularTransferResult<>(finishValue(null, store), store); - } - - @Override - public TransferResult visitWideningConversion( - WideningConversionNode n, TransferInput p) { - TransferResult result = super.visitWideningConversion(n, p); - // Combine annotations from the operand with the wide type - V operandValue = p.getValueOfSubNode(n.getOperand()); - V widenedValue = getWidenedValue(n.getType(), operandValue); - result.setResultValue(widenedValue); - return result; + } } - - /** - * Returns an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}, adapted for narrowing. This is only called at a narrowing conversion. - * - * @param type the type to narrow to - * @param annotatedValue the type to narrow from - * @return an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}; returns null if {@code annotatedValue} is null - */ - @SideEffectFree - protected @PolyNull V getNarrowedValue(TypeMirror type, @PolyNull V annotatedValue) { - if (annotatedValue == null) { - return null; - } - AnnotationMirrorSet narrowedAnnos = - analysis.atypeFactory.getNarrowedAnnotations( - annotatedValue.getAnnotations(), - annotatedValue.getUnderlyingType().getKind(), - type.getKind()); - - return analysis.createAbstractValue(narrowedAnnos, type); + } + + /** A case produces no value, but it may imply some facts about switch selector expression. */ + @Override + public TransferResult visitCase(CaseNode n, TransferInput in) { + S store = in.getRegularStore(); + TransferResult lubResult = null; + // Case operands are the case constants. For example, A, B and C in case A, B, C:. + // This method refines the type of the selector expression and the synthetic variable that + // represents the selector expression to the type of the case constant if it is more + // precise. + // If there are multiple case constants then a new store is created for each case constant + // and then they are lubbed. This method returns the lubbed result. + for (Node caseOperand : n.getCaseOperands()) { + TransferResult result = + new ConditionalTransferResult<>( + finishValue(null, store), in.getThenStore().copy(), in.getElseStore().copy(), false); + V caseValue = in.getValueOfSubNode(caseOperand); + AssignmentNode assign = n.getSwitchOperand(); + V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget())); + result = + strengthenAnnotationOfEqualTo( + result, caseOperand, assign.getExpression(), caseValue, switchValue, false); + // Update value of switch temporary variable + result = + strengthenAnnotationOfEqualTo( + result, caseOperand, assign.getTarget(), caseValue, switchValue, false); + + // Lub the result of one case label constant with the result of the others. + if (lubResult == null) { + lubResult = result; + } else { + S thenStore = lubResult.getThenStore().leastUpperBound(result.getThenStore()); + S elseStore = lubResult.getElseStore().leastUpperBound(result.getElseStore()); + lubResult = + new ConditionalTransferResult<>( + null, thenStore, elseStore, lubResult.storeChanged() || result.storeChanged()); + } } - - /** - * Returns an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}, adapted for widening. This is only called at a widening conversion. - * - * @param type the type to widen to - * @param annotatedValue the type to widen from - * @return an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}; returns null if {@code annotatedValue} is null - */ - @SideEffectFree - protected @PolyNull V getWidenedValue(TypeMirror type, @PolyNull V annotatedValue) { - if (annotatedValue == null) { - return null; - } - AnnotationMirrorSet widenedAnnos = - analysis.atypeFactory.getWidenedAnnotations( - annotatedValue.getAnnotations(), - annotatedValue.getUnderlyingType().getKind(), - type.getKind()); - - return analysis.createAbstractValue(widenedAnnos, type); + return lubResult; + } + + /** + * In a cast {@code (@A C) e} of some expression {@code e} to a new type {@code @A C}, we usually + * take the annotation of the type {@code C} (here {@code @A}). However, if the inferred + * annotation of {@code e} is more precise, we keep that one. + */ + // @Override + // public TransferResult visitTypeCast(TypeCastNode n, + // TransferInput p) { + // TransferResult result = super.visitTypeCast(n, p); + // V value = result.getResultValue(); + // V operandValue = p.getValueOfSubNode(n.getOperand()); + // // Normally we take the value of the type cast node. However, if the old + // // flow-refined value was more precise, we keep that value. + // V resultValue = moreSpecificValue(value, operandValue); + // result.setResultValue(resultValue); + // return result; + // } + + /** + * Returns the abstract value of {@code (value1, value2)} that is more specific. If the two are + * incomparable, then {@code value1} is returned. + * + * @param value1 an abstract value + * @param value2 another abstract value + * @return the more specific value of the two parameters, or, if they are incomparable, {@code + * value1} + */ + @Pure + public V moreSpecificValue(V value1, V value2) { + if (value1 == null) { + return value2; } - - @Override - public TransferResult visitNarrowingConversion( - NarrowingConversionNode n, TransferInput p) { - TransferResult result = super.visitNarrowingConversion(n, p); - // Combine annotations from the operand with the narrow type - V operandValue = p.getValueOfSubNode(n.getOperand()); - V narrowedValue = getNarrowedValue(n.getType(), operandValue); - result.setResultValue(narrowedValue); - return result; + if (value2 == null) { + return value1; } - - @Override - public TransferResult visitStringConversion( - StringConversionNode n, TransferInput p) { - TransferResult result = super.visitStringConversion(n, p); - result.setResultValue(p.getValueOfSubNode(n.getOperand())); - return result; + return value1.mostSpecific(value2, value1); + } + + @Override + public TransferResult visitVariableDeclaration( + VariableDeclarationNode n, TransferInput p) { + S store = p.getRegularStore(); + return new RegularTransferResult<>(finishValue(null, store), store); + } + + @Override + public TransferResult visitWideningConversion( + WideningConversionNode n, TransferInput p) { + TransferResult result = super.visitWideningConversion(n, p); + // Combine annotations from the operand with the wide type + V operandValue = p.getValueOfSubNode(n.getOperand()); + V widenedValue = getWidenedValue(n.getType(), operandValue); + result.setResultValue(widenedValue); + return result; + } + + /** + * Returns an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}, adapted for narrowing. This is only called at a narrowing conversion. + * + * @param type the type to narrow to + * @param annotatedValue the type to narrow from + * @return an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}; returns null if {@code annotatedValue} is null + */ + @SideEffectFree + protected @PolyNull V getNarrowedValue(TypeMirror type, @PolyNull V annotatedValue) { + if (annotatedValue == null) { + return null; } - - @Override - public TransferResult visitExpressionStatement( - ExpressionStatementNode n, TransferInput vsTransferInput) { - // Merge the input - S info = vsTransferInput.getRegularStore(); - return new RegularTransferResult<>(finishValue(null, info), info); + AnnotationMirrorSet narrowedAnnos = + analysis.atypeFactory.getNarrowedAnnotations( + annotatedValue.getAnnotations(), + annotatedValue.getUnderlyingType().getKind(), + type.getKind()); + + return analysis.createAbstractValue(narrowedAnnos, type); + } + + /** + * Returns an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}, adapted for widening. This is only called at a widening conversion. + * + * @param type the type to widen to + * @param annotatedValue the type to widen from + * @return an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}; returns null if {@code annotatedValue} is null + */ + @SideEffectFree + protected @PolyNull V getWidenedValue(TypeMirror type, @PolyNull V annotatedValue) { + if (annotatedValue == null) { + return null; } - - /** - * Inserts newAnno as the value into all stores (conditional or not) in the result for node. - * This is a utility method for subclasses. - * - * @param result the TransferResult holding the stores to modify - * @param target the receiver whose value should be modified - * @param newAnno the new value - */ - protected static void insertIntoStores( - TransferResult result, - JavaExpression target, - AnnotationMirror newAnno) { - if (result.containsTwoStores()) { - result.getThenStore().insertValue(target, newAnno); - result.getElseStore().insertValue(target, newAnno); - } else { - result.getRegularStore().insertValue(target, newAnno); - } + AnnotationMirrorSet widenedAnnos = + analysis.atypeFactory.getWidenedAnnotations( + annotatedValue.getAnnotations(), + annotatedValue.getUnderlyingType().getKind(), + type.getKind()); + + return analysis.createAbstractValue(widenedAnnos, type); + } + + @Override + public TransferResult visitNarrowingConversion( + NarrowingConversionNode n, TransferInput p) { + TransferResult result = super.visitNarrowingConversion(n, p); + // Combine annotations from the operand with the narrow type + V operandValue = p.getValueOfSubNode(n.getOperand()); + V narrowedValue = getNarrowedValue(n.getType(), operandValue); + result.setResultValue(narrowedValue); + return result; + } + + @Override + public TransferResult visitStringConversion(StringConversionNode n, TransferInput p) { + TransferResult result = super.visitStringConversion(n, p); + result.setResultValue(p.getValueOfSubNode(n.getOperand())); + return result; + } + + @Override + public TransferResult visitExpressionStatement( + ExpressionStatementNode n, TransferInput vsTransferInput) { + // Merge the input + S info = vsTransferInput.getRegularStore(); + return new RegularTransferResult<>(finishValue(null, info), info); + } + + /** + * Inserts newAnno as the value into all stores (conditional or not) in the result for node. This + * is a utility method for subclasses. + * + * @param result the TransferResult holding the stores to modify + * @param target the receiver whose value should be modified + * @param newAnno the new value + */ + protected static void insertIntoStores( + TransferResult result, JavaExpression target, AnnotationMirror newAnno) { + if (result.containsTwoStores()) { + result.getThenStore().insertValue(target, newAnno); + result.getElseStore().insertValue(target, newAnno); + } else { + result.getRegularStore().insertValue(target, newAnno); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java index 0a640839916..ad5c1bf499a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java @@ -1,5 +1,13 @@ package org.checkerframework.framework.flow; +import java.util.Objects; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; import org.checkerframework.dataflow.analysis.Analysis; @@ -17,16 +25,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.StringsPlume; -import java.util.Objects; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; -import javax.lang.model.util.Types; - /** * An implementation of an abstract value used by the Checker Framework * org.checkerframework.dataflow analysis. @@ -55,848 +53,821 @@ */ public abstract class CFAbstractValue> implements AbstractValue { - /** The analysis class this value belongs to. */ - protected final CFAbstractAnalysis analysis; - - /** The underlying (Java) type in this abstract value. */ - protected final TypeMirror underlyingType; - - /** The annotations in this abstract value. */ - protected final AnnotationMirrorSet annotations; - - /** - * Creates a new CFAbstractValue. - * - * @param analysis the analysis class this value belongs to - * @param annotations the annotations in this abstract value - * @param underlyingType the underlying (Java) type in this abstract value - */ - protected CFAbstractValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - this.analysis = analysis; - this.annotations = annotations; - this.underlyingType = underlyingType; - - assert validateSet( - this.getAnnotations(), this.getUnderlyingType(), analysis.getTypeFactory()) - : "Encountered invalid type: " - + underlyingType - + " annotations: " - + StringsPlume.join(", ", annotations); + /** The analysis class this value belongs to. */ + protected final CFAbstractAnalysis analysis; + + /** The underlying (Java) type in this abstract value. */ + protected final TypeMirror underlyingType; + + /** The annotations in this abstract value. */ + protected final AnnotationMirrorSet annotations; + + /** + * Creates a new CFAbstractValue. + * + * @param analysis the analysis class this value belongs to + * @param annotations the annotations in this abstract value + * @param underlyingType the underlying (Java) type in this abstract value + */ + protected CFAbstractValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + this.analysis = analysis; + this.annotations = annotations; + this.underlyingType = underlyingType; + + assert validateSet(this.getAnnotations(), this.getUnderlyingType(), analysis.getTypeFactory()) + : "Encountered invalid type: " + + underlyingType + + " annotations: " + + StringsPlume.join(", ", annotations); + } + + /** + * Returns true if the set has an annotation from every hierarchy (or if it doesn't need to); + * returns false if the set is missing an annotation from some hierarchy. + * + * @param annos set of annotations + * @param typeMirror where the annotations are written + * @param atypeFactory the type factory + * @return true if no annotations are missing + */ + public static boolean validateSet( + AnnotationMirrorSet annos, TypeMirror typeMirror, AnnotatedTypeFactory atypeFactory) { + + boolean canBeMissing = canBeMissingAnnotations(typeMirror); + if (canBeMissing) { + return true; } - /** - * Returns true if the set has an annotation from every hierarchy (or if it doesn't need to); - * returns false if the set is missing an annotation from some hierarchy. - * - * @param annos set of annotations - * @param typeMirror where the annotations are written - * @param atypeFactory the type factory - * @return true if no annotations are missing - */ - public static boolean validateSet( - AnnotationMirrorSet annos, TypeMirror typeMirror, AnnotatedTypeFactory atypeFactory) { - - boolean canBeMissing = canBeMissingAnnotations(typeMirror); - if (canBeMissing) { - return true; - } - - // TODO: temporarily disable stricter size comparison. - // if (annos.size() != hierarchy.getTopAnnotations().size()) { - // return false; - // } - - // The size of the set matches, but maybe it contains multiple annos of one - // hierarchy and none of another. - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror anno = qualHierarchy.findAnnotationInHierarchy(annos, top); - if (anno == null) { - return false; - } - } - - return true; + // TODO: temporarily disable stricter size comparison. + // if (annos.size() != hierarchy.getTopAnnotations().size()) { + // return false; + // } + + // The size of the set matches, but maybe it contains multiple annos of one + // hierarchy and none of another. + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror anno = qualHierarchy.findAnnotationInHierarchy(annos, top); + if (anno == null) { + return false; + } } - /** - * Returns whether or not the set of annotations can be missing an annotation for any hierarchy. - * - * @return whether or not the set of annotations can be missing an annotation - */ - public boolean canBeMissingAnnotations() { - return canBeMissingAnnotations(underlyingType); + return true; + } + + /** + * Returns whether or not the set of annotations can be missing an annotation for any hierarchy. + * + * @return whether or not the set of annotations can be missing an annotation + */ + public boolean canBeMissingAnnotations() { + return canBeMissingAnnotations(underlyingType); + } + + /** + * Returns true if it is OK for the given type mirror not to be annotated, such as for VOID, NONE, + * PACKAGE, TYPEVAR, and some WILDCARD. + * + * @param typeMirror a type + * @return true if it is OK for the given type mirror not to be annotated + */ + private static boolean canBeMissingAnnotations(TypeMirror typeMirror) { + if (typeMirror == null) { + return false; } - - /** - * Returns true if it is OK for the given type mirror not to be annotated, such as for VOID, - * NONE, PACKAGE, TYPEVAR, and some WILDCARD. - * - * @param typeMirror a type - * @return true if it is OK for the given type mirror not to be annotated - */ - private static boolean canBeMissingAnnotations(TypeMirror typeMirror) { - if (typeMirror == null) { - return false; - } - if (typeMirror.getKind() == TypeKind.VOID - || typeMirror.getKind() == TypeKind.NONE - || typeMirror.getKind() == TypeKind.PACKAGE) { - return true; - } - if (typeMirror.getKind() == TypeKind.WILDCARD) { - return canBeMissingAnnotations(((WildcardType) typeMirror).getExtendsBound()); - } - return typeMirror.getKind() == TypeKind.TYPEVAR; + if (typeMirror.getKind() == TypeKind.VOID + || typeMirror.getKind() == TypeKind.NONE + || typeMirror.getKind() == TypeKind.PACKAGE) { + return true; } - - /** - * Returns a set of annotations. If {@link #canBeMissingAnnotations()} returns true, then the - * set of annotations may not have an annotation for every hierarchy. - * - *

To get the single annotation in a particular hierarchy, use {@link - * QualifierHierarchy#findAnnotationInHierarchy}. - * - * @return a set of annotations - */ - @Pure - public AnnotationMirrorSet getAnnotations() { - return annotations; + if (typeMirror.getKind() == TypeKind.WILDCARD) { + return canBeMissingAnnotations(((WildcardType) typeMirror).getExtendsBound()); } - - @Pure - public TypeMirror getUnderlyingType() { - return underlyingType; + return typeMirror.getKind() == TypeKind.TYPEVAR; + } + + /** + * Returns a set of annotations. If {@link #canBeMissingAnnotations()} returns true, then the set + * of annotations may not have an annotation for every hierarchy. + * + *

To get the single annotation in a particular hierarchy, use {@link + * QualifierHierarchy#findAnnotationInHierarchy}. + * + * @return a set of annotations + */ + @Pure + public AnnotationMirrorSet getAnnotations() { + return annotations; + } + + @Pure + public TypeMirror getUnderlyingType() { + return underlyingType; + } + + @SuppressWarnings("interning:not.interned") // efficiency pre-test + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CFAbstractValue)) { + return false; } - @SuppressWarnings("interning:not.interned") // efficiency pre-test - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof CFAbstractValue)) { - return false; - } - - CFAbstractValue other = (CFAbstractValue) obj; - if (this.getUnderlyingType() != other.getUnderlyingType() - && !analysis.getTypes() - .isSameType(this.getUnderlyingType(), other.getUnderlyingType())) { - return false; - } - return AnnotationUtils.areSame(this.getAnnotations(), other.getAnnotations()); + CFAbstractValue other = (CFAbstractValue) obj; + if (this.getUnderlyingType() != other.getUnderlyingType() + && !analysis.getTypes().isSameType(this.getUnderlyingType(), other.getUnderlyingType())) { + return false; + } + return AnnotationUtils.areSame(this.getAnnotations(), other.getAnnotations()); + } + + @Pure + @Override + public int hashCode() { + return Objects.hash(getAnnotations(), underlyingType); + } + + /** + * Returns the string representation, using fully-qualified names. + * + * @return the string representation, using fully-qualified names + */ + @SideEffectFree + public String toStringFullyQualified() { + return "CFAV{" + annotations + ", " + underlyingType + '}'; + } + + /** + * Returns the string representation, using simple (not fully-qualified) names. + * + * @return the string representation, using simple (not fully-qualified) names + */ + @SideEffectFree + public String toStringSimple() { + return "CFAV{" + + AnnotationUtils.toStringSimple(annotations) + + ", " + + TypesUtils.simpleTypeName(underlyingType) + + '}'; + } + + /** + * Returns the string representation. + * + * @return the string representation + */ + @SideEffectFree + @Override + public String toString() { + return toStringSimple(); + } + + /** + * Returns the more specific of two values {@code this} and {@code other}. If they do not contain + * information for all hierarchies, then it is possible that information from both {@code this} + * and {@code other} are taken. + * + *

If neither of the two is more specific for one of the hierarchies (i.e., if the two are + * incomparable as determined by {@link QualifierHierarchy#isSubtypeShallow(AnnotationMirror, + * TypeMirror, AnnotationMirror, TypeMirror)}, then the respective value from {@code backup} is + * used. + * + * @param other the other value to obtain information from + * @param backup the value to use if {@code this} and {@code other} are incomparable + * @return the more specific of two values {@code this} and {@code other} + */ + public V mostSpecific(@Nullable V other, @Nullable V backup) { + if (other == null) { + @SuppressWarnings("unchecked") + V v = (V) this; + return v; + } + Types types = analysis.getTypes(); + TypeMirror mostSpecifTypeMirror; + if (types.isAssignable(this.getUnderlyingType(), other.getUnderlyingType())) { + mostSpecifTypeMirror = this.getUnderlyingType(); + } else if (types.isAssignable(other.getUnderlyingType(), this.getUnderlyingType())) { + mostSpecifTypeMirror = other.getUnderlyingType(); + } else if (TypesUtils.isErasedSubtype( + this.getUnderlyingType(), other.getUnderlyingType(), types)) { + mostSpecifTypeMirror = this.getUnderlyingType(); + } else if (TypesUtils.isErasedSubtype( + other.getUnderlyingType(), this.getUnderlyingType(), types)) { + mostSpecifTypeMirror = other.getUnderlyingType(); + } else { + mostSpecifTypeMirror = this.getUnderlyingType(); } - @Pure - @Override - public int hashCode() { - return Objects.hash(getAnnotations(), underlyingType); + MostSpecificVisitor ms = new MostSpecificVisitor(backup); + AnnotationMirrorSet mostSpecific = + ms.combineSets( + this.getUnderlyingType(), + this.getAnnotations(), + other.getUnderlyingType(), + other.getAnnotations(), + canBeMissingAnnotations(mostSpecifTypeMirror)); + if (ms.error) { + // return null because `ms.error` is only set to true when `backup` is null. + return null; } + return analysis.createAbstractValue(mostSpecific, mostSpecifTypeMirror); + } + + /** Computes the most specific annotations. */ + private class MostSpecificVisitor extends AnnotationSetCombiner { + /** If set to true, then this visitor was unable to find a most specific annotation. */ + boolean error = false; + + /** Set of annotations to use if a most specific value cannot be found. */ + final @Nullable AnnotationMirrorSet backupAMSet; /** - * Returns the string representation, using fully-qualified names. + * Create a {@link MostSpecificVisitor}. * - * @return the string representation, using fully-qualified names + * @param backup value to use if no most specific value is found */ - @SideEffectFree - public String toStringFullyQualified() { - return "CFAV{" + annotations + ", " + underlyingType + '}'; + public MostSpecificVisitor(@Nullable V backup) { + if (backup != null) { + this.backupAMSet = backup.getAnnotations(); + } else { + this.backupAMSet = null; + } } /** - * Returns the string representation, using simple (not fully-qualified) names. + * Returns the backup annotation that is in the same hierarchy as {@code top}. * - * @return the string representation, using simple (not fully-qualified) names + * @param top an annotation + * @return the backup annotation that is in the same hierarchy as {@code top} */ - @SideEffectFree - public String toStringSimple() { - return "CFAV{" - + AnnotationUtils.toStringSimple(annotations) - + ", " - + TypesUtils.simpleTypeName(underlyingType) - + '}'; + private @Nullable AnnotationMirror getBackupAnnoIn(AnnotationMirror top) { + if (backupAMSet == null) { + // If there is no backup value, but one is required, then the resulting set will + // not be the most specific. Indicate this with the error. + error = true; + return null; + } + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + return qualHierarchy.findAnnotationInHierarchy(backupAMSet, top); } - /** - * Returns the string representation. - * - * @return the string representation - */ - @SideEffectFree @Override - public String toString() { - return toStringSimple(); + protected @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top) { + if (aTypeMirror == null) { + throw new NullPointerException("combineTwoAnnotations: aTypeMirror==null"); + } + if (bTypeMirror == null) { + throw new NullPointerException("combineTwoAnnotations: bTypeMirror==null"); + } + GenericAnnotatedTypeFactory gatf = analysis.getTypeFactory(); + QualifierHierarchy qualHierarchy = gatf.getQualifierHierarchy(); + if (gatf.hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(aTypeMirror), top) + && gatf.hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(bTypeMirror), top)) { + // Both types have qualifier parameters, so they are related by invariance rather + // than subtyping. + if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror) + && qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { + return b; + } + } else if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror)) { + // `a` may not be a subtype of `b`, if one of the type mirrors isn't relevant, + // so return the lower of the two. + return lowestQualifier(a, b); + } else if (qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { + // `b` may not be a subtype of `a`, if one of the type mirrors isn't relevant, + // so return the lower of the two. + return lowestQualifier(a, b); + } + return getBackupAnnoIn(top); } /** - * Returns the more specific of two values {@code this} and {@code other}. If they do not - * contain information for all hierarchies, then it is possible that information from both - * {@code this} and {@code other} are taken. + * Returns the qualifier that is the lowest in the hierarchy. If the two qualifiers are not + * comparable, then returns the qualifier that is ordered first by {@link + * AnnotationUtils#compareAnnotationMirrors(AnnotationMirror, AnnotationMirror)}. * - *

If neither of the two is more specific for one of the hierarchies (i.e., if the two are - * incomparable as determined by {@link QualifierHierarchy#isSubtypeShallow(AnnotationMirror, - * TypeMirror, AnnotationMirror, TypeMirror)}, then the respective value from {@code backup} is - * used. + *

This is similar to glb, but one of the given qualifiers is always returned. * - * @param other the other value to obtain information from - * @param backup the value to use if {@code this} and {@code other} are incomparable - * @return the more specific of two values {@code this} and {@code other} + * @param qual1 a qualifier + * @param qual2 a qualifier + * @return the qualifier that is the lowest in the hierarchy */ - public V mostSpecific(@Nullable V other, @Nullable V backup) { - if (other == null) { - @SuppressWarnings("unchecked") - V v = (V) this; - return v; - } - Types types = analysis.getTypes(); - TypeMirror mostSpecifTypeMirror; - if (types.isAssignable(this.getUnderlyingType(), other.getUnderlyingType())) { - mostSpecifTypeMirror = this.getUnderlyingType(); - } else if (types.isAssignable(other.getUnderlyingType(), this.getUnderlyingType())) { - mostSpecifTypeMirror = other.getUnderlyingType(); - } else if (TypesUtils.isErasedSubtype( - this.getUnderlyingType(), other.getUnderlyingType(), types)) { - mostSpecifTypeMirror = this.getUnderlyingType(); - } else if (TypesUtils.isErasedSubtype( - other.getUnderlyingType(), this.getUnderlyingType(), types)) { - mostSpecifTypeMirror = other.getUnderlyingType(); + private final AnnotationMirror lowestQualifier(AnnotationMirror qual1, AnnotationMirror qual2) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + if (qualHierarchy.isSubtypeQualifiersOnly(qual1, qual2)) { + return qual1; + } else if (qualHierarchy.isSubtypeQualifiersOnly(qual2, qual1)) { + return qual2; + } else { + int i = AnnotationUtils.compareAnnotationMirrors(qual1, qual2); + if (i > 0) { + return qual2; } else { - mostSpecifTypeMirror = this.getUnderlyingType(); - } - - MostSpecificVisitor ms = new MostSpecificVisitor(backup); - AnnotationMirrorSet mostSpecific = - ms.combineSets( - this.getUnderlyingType(), - this.getAnnotations(), - other.getUnderlyingType(), - other.getAnnotations(), - canBeMissingAnnotations(mostSpecifTypeMirror)); - if (ms.error) { - // return null because `ms.error` is only set to true when `backup` is null. - return null; + return qual1; } - return analysis.createAbstractValue(mostSpecific, mostSpecifTypeMirror); + } } - /** Computes the most specific annotations. */ - private class MostSpecificVisitor extends AnnotationSetCombiner { - /** If set to true, then this visitor was unable to find a most specific annotation. */ - boolean error = false; - - /** Set of annotations to use if a most specific value cannot be found. */ - final @Nullable AnnotationMirrorSet backupAMSet; - - /** - * Create a {@link MostSpecificVisitor}. - * - * @param backup value to use if no most specific value is found - */ - public MostSpecificVisitor(@Nullable V backup) { - if (backup != null) { - this.backupAMSet = backup.getAnnotations(); - } else { - this.backupAMSet = null; - } - } - - /** - * Returns the backup annotation that is in the same hierarchy as {@code top}. - * - * @param top an annotation - * @return the backup annotation that is in the same hierarchy as {@code top} - */ - private @Nullable AnnotationMirror getBackupAnnoIn(AnnotationMirror top) { - if (backupAMSet == null) { - // If there is no backup value, but one is required, then the resulting set will - // not be the most specific. Indicate this with the error. - error = true; - return null; - } - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - return qualHierarchy.findAnnotationInHierarchy(backupAMSet, top); - } - - @Override - protected @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top) { - if (aTypeMirror == null) { - throw new NullPointerException("combineTwoAnnotations: aTypeMirror==null"); - } - if (bTypeMirror == null) { - throw new NullPointerException("combineTwoAnnotations: bTypeMirror==null"); - } - GenericAnnotatedTypeFactory gatf = analysis.getTypeFactory(); - QualifierHierarchy qualHierarchy = gatf.getQualifierHierarchy(); - if (gatf.hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(aTypeMirror), top) - && gatf.hasQualifierParameterInHierarchy( - TypesUtils.getTypeElement(bTypeMirror), top)) { - // Both types have qualifier parameters, so they are related by invariance rather - // than subtyping. - if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror) - && qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { - return b; - } - } else if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror)) { - // `a` may not be a subtype of `b`, if one of the type mirrors isn't relevant, - // so return the lower of the two. - return lowestQualifier(a, b); - } else if (qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { - // `b` may not be a subtype of `a`, if one of the type mirrors isn't relevant, - // so return the lower of the two. - return lowestQualifier(a, b); - } - return getBackupAnnoIn(top); - } - - /** - * Returns the qualifier that is the lowest in the hierarchy. If the two qualifiers are not - * comparable, then returns the qualifier that is ordered first by {@link - * AnnotationUtils#compareAnnotationMirrors(AnnotationMirror, AnnotationMirror)}. - * - *

This is similar to glb, but one of the given qualifiers is always returned. - * - * @param qual1 a qualifier - * @param qual2 a qualifier - * @return the qualifier that is the lowest in the hierarchy - */ - private final AnnotationMirror lowestQualifier( - AnnotationMirror qual1, AnnotationMirror qual2) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - if (qualHierarchy.isSubtypeQualifiersOnly(qual1, qual2)) { - return qual1; - } else if (qualHierarchy.isSubtypeQualifiersOnly(qual2, qual1)) { - return qual2; - } else { - int i = AnnotationUtils.compareAnnotationMirrors(qual1, qual2); - if (i > 0) { - return qual2; - } else { - return qual1; - } - } - } - - @Override - protected @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - if (canCombinedSetBeMissingAnnos) { - return null; - } else { - AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); - TypeMirror aTM = aAtv.getUnderlyingType(); - TypeMirror bTM = bAtv.getUnderlyingType(); - return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); - } - } - - @Override - protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - - AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); - TypeMirror upperBoundTM = typeVar.getUpperBound().getUnderlyingType(); - - if (!canCombinedSetBeMissingAnnos) { - TypeVariable typeVarTM = typeVar.getUnderlyingType(); - return combineTwoAnnotations(annotation, typeVarTM, upperBound, typeVarTM, top); - } - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - AnnotationMirrorSet lBSet = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); - AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); - TypeMirror lowerBoundTM = typeVar.getLowerBound().getUnderlyingType(); - - TypeMirror typeVarTM = typeVar.getUnderlyingType(); - if (qualHierarchy.isSubtypeShallow(upperBound, upperBoundTM, annotation, typeVarTM)) { - // no anno is more specific than anno - return null; - } else if (qualHierarchy.isSubtypeShallow( - annotation, typeVarTM, lowerBound, lowerBoundTM)) { - return lowestQualifier(annotation, lowerBound); - } else { - return getBackupAnnoIn(top); - } - } + @Override + protected @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + if (canCombinedSetBeMissingAnnos) { + return null; + } else { + AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); + TypeMirror aTM = aAtv.getUnderlyingType(); + TypeMirror bTM = bAtv.getUnderlyingType(); + return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); + } } - /** - * {@inheritDoc} - * - *

Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} - * instead of this method. - */ @Override - public final V leastUpperBound(@Nullable V other) { - return upperBound(other, false); + protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + + AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); + TypeMirror upperBoundTM = typeVar.getUpperBound().getUnderlyingType(); + + if (!canCombinedSetBeMissingAnnos) { + TypeVariable typeVarTM = typeVar.getUnderlyingType(); + return combineTwoAnnotations(annotation, typeVarTM, upperBound, typeVarTM, top); + } + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + AnnotationMirrorSet lBSet = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); + AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); + TypeMirror lowerBoundTM = typeVar.getLowerBound().getUnderlyingType(); + + TypeMirror typeVarTM = typeVar.getUnderlyingType(); + if (qualHierarchy.isSubtypeShallow(upperBound, upperBoundTM, annotation, typeVarTM)) { + // no anno is more specific than anno + return null; + } else if (qualHierarchy.isSubtypeShallow(annotation, typeVarTM, lowerBound, lowerBoundTM)) { + return lowestQualifier(annotation, lowerBound); + } else { + return getBackupAnnoIn(top); + } } + } + + /** + * {@inheritDoc} + * + *

Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead + * of this method. + */ + @Override + public final V leastUpperBound(@Nullable V other) { + return upperBound(other, false); + } + + /** + * Compute the least upper bound of two abstract values. The returned value has a Java type of + * {@code typeMirror}. {@code typeMirror} should be an upper bound of the Java types of {@code + * this} an {@code other}, but it does not have be to the least upper bound. + * + *

Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead + * of this method. + * + * @param other another value + * @param typeMirror the underlying Java type of the returned value, which may or may not be the + * least upper bound + * @return the least upper bound of two abstract values + */ + public final V leastUpperBound(@Nullable V other, TypeMirror typeMirror) { + return upperBound(other, typeMirror, false); + } + + /** + * Compute an upper bound of two values that is wider than the least upper bound of the two + * values. Used to jump to a higher abstraction to allow faster termination of the fixed point + * computations in {@link Analysis}. + * + *

A particular analysis might not require widening and should implement this method by calling + * leastUpperBound. + * + *

Important: This method must fulfill the following contract: + * + *

    + *
  • Does not change {@code this}. + *
  • Does not change {@code previous}. + *
  • Returns a fresh object which is not aliased yet. + *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
  • Is commutative. + *
+ * + * Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead of + * this method. + * + * @param previous must be the previous value + * @return an upper bound of two values that is wider than the least upper bound of the two values + */ + public final V widenUpperBound(@Nullable V previous) { + return upperBound(previous, true); + } + + /** + * Returns the least upper bound of this and {@code other}. + * + * @param other an abstract value + * @param shouldWiden true if the lub should perform widening + * @return the least upper bound of this and {@code other} + */ + private V upperBound(@Nullable V other, boolean shouldWiden) { + if (other == null) { + @SuppressWarnings("unchecked") + V v = (V) this; + return v; + } + ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); + TypeMirror lubTypeMirror = + TypesUtils.leastUpperBound( + this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); + return upperBound(other, lubTypeMirror, shouldWiden); + } + + /** + * Returns an upper bound of {@code this} and {@code other}. The underlying type of the value + * returned is {@code upperBoundTypeMirror}. If {@code shouldWiden} is false, this method returns + * the least upper bound of {@code this} and {@code other}. + * + *

This is the implementation of {@link #leastUpperBound(CFAbstractValue, TypeMirror)}, {@link + * #leastUpperBound(CFAbstractValue)}, {@link #widenUpperBound(CFAbstractValue)}, and {@link + * #upperBound(CFAbstractValue, boolean)}. Subclasses may override it. + * + * @param other an abstract value + * @param upperBoundTypeMirror the underlying type of the returned value + * @param shouldWiden true if the method should perform widening + * @return an upper bound of this and {@code other} + */ + protected V upperBound(@Nullable V other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { + ValueLub valueLub = new ValueLub(shouldWiden); + AnnotationMirrorSet lub = + valueLub.combineSets( + this.getUnderlyingType(), + this.getAnnotations(), + other.getUnderlyingType(), + other.getAnnotations(), + canBeMissingAnnotations(upperBoundTypeMirror)); + return analysis.createAbstractValue(lub, upperBoundTypeMirror); + } + + /** + * Computes the least upper bound or, if {@code shouldWiden} is true, an upper bounds of two sets + * of annotations. The computation accounts for sets that are missing annotations in hierarchies. + */ + protected class ValueLub extends AnnotationSetCombiner { /** - * Compute the least upper bound of two abstract values. The returned value has a Java type of - * {@code typeMirror}. {@code typeMirror} should be an upper bound of the Java types of {@code - * this} an {@code other}, but it does not have be to the least upper bound. - * - *

Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} - * instead of this method. - * - * @param other another value - * @param typeMirror the underlying Java type of the returned value, which may or may not be the - * least upper bound - * @return the least upper bound of two abstract values + * If true, this class computes an upper bound; if false, this class computes the least upper + * bound. */ - public final V leastUpperBound(@Nullable V other, TypeMirror typeMirror) { - return upperBound(other, typeMirror, false); - } + private final boolean widen; /** - * Compute an upper bound of two values that is wider than the least upper bound of the two - * values. Used to jump to a higher abstraction to allow faster termination of the fixed point - * computations in {@link Analysis}. - * - *

A particular analysis might not require widening and should implement this method by - * calling leastUpperBound. - * - *

Important: This method must fulfill the following contract: + * Creates a {@link ValueLub}. * - *

    - *
  • Does not change {@code this}. - *
  • Does not change {@code previous}. - *
  • Returns a fresh object which is not aliased yet. - *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
  • Is commutative. - *
- * - * Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead - * of this method. - * - * @param previous must be the previous value - * @return an upper bound of two values that is wider than the least upper bound of the two - * values + * @param shouldWiden if true, this class computes an upper bound */ - public final V widenUpperBound(@Nullable V previous) { - return upperBound(previous, true); + public ValueLub(boolean shouldWiden) { + this.widen = shouldWiden; } - /** - * Returns the least upper bound of this and {@code other}. - * - * @param other an abstract value - * @param shouldWiden true if the lub should perform widening - * @return the least upper bound of this and {@code other} - */ - private V upperBound(@Nullable V other, boolean shouldWiden) { - if (other == null) { - @SuppressWarnings("unchecked") - V v = (V) this; - return v; - } - ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); - TypeMirror lubTypeMirror = - TypesUtils.leastUpperBound( - this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); - return upperBound(other, lubTypeMirror, shouldWiden); + @Override + protected @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + if (widen) { + return qualHierarchy.widenedUpperBound(a, b); + } else { + return qualHierarchy.leastUpperBoundShallow(a, aTypeMirror, b, bTypeMirror); + } } - /** - * Returns an upper bound of {@code this} and {@code other}. The underlying type of the value - * returned is {@code upperBoundTypeMirror}. If {@code shouldWiden} is false, this method - * returns the least upper bound of {@code this} and {@code other}. - * - *

This is the implementation of {@link #leastUpperBound(CFAbstractValue, TypeMirror)}, - * {@link #leastUpperBound(CFAbstractValue)}, {@link #widenUpperBound(CFAbstractValue)}, and - * {@link #upperBound(CFAbstractValue, boolean)}. Subclasses may override it. - * - * @param other an abstract value - * @param upperBoundTypeMirror the underlying type of the returned value - * @param shouldWiden true if the method should perform widening - * @return an upper bound of this and {@code other} - */ - protected V upperBound( - @Nullable V other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { - ValueLub valueLub = new ValueLub(shouldWiden); - AnnotationMirrorSet lub = - valueLub.combineSets( - this.getUnderlyingType(), - this.getAnnotations(), - other.getUnderlyingType(), - other.getAnnotations(), - canBeMissingAnnotations(upperBoundTypeMirror)); - return analysis.createAbstractValue(lub, upperBoundTypeMirror); + @Override + protected @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + if (canCombinedSetBeMissingAnnos) { + // don't add an annotation + return null; + } else { + AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); + return combineTwoAnnotations( + aUB, aAtv.getUnderlyingType(), bUB, bAtv.getUnderlyingType(), top); + } } - /** - * Computes the least upper bound or, if {@code shouldWiden} is true, an upper bounds of two - * sets of annotations. The computation accounts for sets that are missing annotations in - * hierarchies. - */ - protected class ValueLub extends AnnotationSetCombiner { - - /** - * If true, this class computes an upper bound; if false, this class computes the least - * upper bound. - */ - private final boolean widen; - - /** - * Creates a {@link ValueLub}. - * - * @param shouldWiden if true, this class computes an upper bound - */ - public ValueLub(boolean shouldWiden) { - this.widen = shouldWiden; + @Override + protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + TypeMirror typeVarTM = typeVar.getUnderlyingType(); + if (canCombinedSetBeMissingAnnos) { + // anno is the primary annotation on the use of a type variable. typeVar is a use of + // the same type variable that does not have a primary annotation. The lub of the + // two type variables is computed as follows. If anno is a subtype (or equal) to the + // annotation on the lower bound of typeVar, then typeVar is the lub, so no + // annotation is added to lubset. + // If anno is a supertype of the annotation on the lower bound of typeVar, then the + // lub is typeVar with a primary annotation of lub(anno, upperBound), where + // upperBound is the annotation on the upper bound of typeVar. + AnnotationMirrorSet lBSet = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); + AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); + if (qualHierarchy.isSubtypeQualifiersOnly(annotation, lowerBound)) { + return null; + } else { + return combineTwoAnnotations( + annotation, + typeVarTM, + typeVar.getEffectiveAnnotationInHierarchy(top), + typeVarTM, + top); } + } else { + return combineTwoAnnotations( + annotation, typeVarTM, typeVar.getEffectiveAnnotationInHierarchy(top), typeVarTM, top); + } + } + } + + /** + * Compute the greatest lower bound of two values. + * + *

Important: This method must fulfill the following contract: + * + *

    + *
  • Does not change {@code this}. + *
  • Does not change {@code other}. + *
  • Returns a fresh object which is not aliased yet. + *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
  • Is commutative. + *
+ * + * @param other another value + * @return the greatest lower bound of two values + */ + public V greatestLowerBound(@Nullable V other) { + if (other == null) { + @SuppressWarnings("unchecked") + V v = (V) this; + return v; + } + ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); + TypeMirror glbTypeMirror = + TypesUtils.greatestLowerBound( + this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); + + ValueGlb valueGlb = new ValueGlb(); + AnnotationMirrorSet glb = + valueGlb.combineSets( + this.getUnderlyingType(), + this.getAnnotations(), + other.getUnderlyingType(), + other.getAnnotations(), + canBeMissingAnnotations(glbTypeMirror)); + return analysis.createAbstractValue(glb, glbTypeMirror); + } + + /** + * Computes the GLB of two sets of annotations. The computation accounts for sets that are missing + * annotations in hierarchies. + */ + protected class ValueGlb extends AnnotationSetCombiner { - @Override - protected @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - if (widen) { - return qualHierarchy.widenedUpperBound(a, b); - } else { - return qualHierarchy.leastUpperBoundShallow(a, aTypeMirror, b, bTypeMirror); - } - } + @Override + protected @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + return qualHierarchy.greatestLowerBoundShallow(a, aTypeMirror, b, bTypeMirror); + } - @Override - protected @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - if (canCombinedSetBeMissingAnnos) { - // don't add an annotation - return null; - } else { - AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); - return combineTwoAnnotations( - aUB, aAtv.getUnderlyingType(), bUB, bAtv.getUnderlyingType(), top); - } - } + @Override + protected @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + if (canCombinedSetBeMissingAnnos) { + // don't add an annotation + return null; + } else { + AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); + TypeMirror aTM = aAtv.getUnderlyingType(); + TypeMirror bTM = bAtv.getUnderlyingType(); + return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); + } + } - @Override - protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - TypeMirror typeVarTM = typeVar.getUnderlyingType(); - if (canCombinedSetBeMissingAnnos) { - // anno is the primary annotation on the use of a type variable. typeVar is a use of - // the same type variable that does not have a primary annotation. The lub of the - // two type variables is computed as follows. If anno is a subtype (or equal) to the - // annotation on the lower bound of typeVar, then typeVar is the lub, so no - // annotation is added to lubset. - // If anno is a supertype of the annotation on the lower bound of typeVar, then the - // lub is typeVar with a primary annotation of lub(anno, upperBound), where - // upperBound is the annotation on the upper bound of typeVar. - AnnotationMirrorSet lBSet = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); - AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); - if (qualHierarchy.isSubtypeQualifiersOnly(annotation, lowerBound)) { - return null; - } else { - return combineTwoAnnotations( - annotation, - typeVarTM, - typeVar.getEffectiveAnnotationInHierarchy(top), - typeVarTM, - top); - } - } else { - return combineTwoAnnotations( - annotation, - typeVarTM, - typeVar.getEffectiveAnnotationInHierarchy(top), - typeVarTM, - top); - } + @Override + protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + TypeMirror typeVarTM = typeVar.getUnderlyingType(); + if (canCombinedSetBeMissingAnnos) { + // anno is the primary annotation on the use of a type variable. typeVar is a use of + // the same type variable that does not have a primary annotation. The glb of the + // two type variables is computed as follows. If anno is a supertype (or equal) to + // the annotation on the upper bound of typeVar, then typeVar is the glb, so no + // annotation is added to glbset. + // If anno is a subtype of the annotation on the upper bound of typeVar, then the + // glb is typeVar with a primary annotation of glb(anno, lowerBound), where + // lowerBound is the annotation on the lower bound of typeVar. + AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + if (qualHierarchy.isSubtypeQualifiersOnly(upperBound, annotation)) { + return null; + } else { + AnnotationMirrorSet lBSet = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); + AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); + return combineTwoAnnotations(annotation, typeVarTM, lowerBound, typeVarTM, top); } + } else { + return combineTwoAnnotations( + annotation, typeVarTM, typeVar.getEffectiveAnnotationInHierarchy(top), typeVarTM, top); + } } + } + + /** + * Combines two sets of AnnotationMirrors by hierarchy. + * + *

Subclasses must define how to combine sets by implementing the following methods: + * + *

    + *
  1. {@link #combineTwoAnnotations} + *
  2. {@link #combineAnnotationWithTypeVar} + *
  3. {@link #combineTwoTypeVars} + *
+ * + * If a set is missing an annotation in a hierarchy, and if the combined set can be missing an + * annotation, then there must be a TypeVariable for the set that can be used to find annotations + * on its bounds. + */ + protected abstract class AnnotationSetCombiner { /** - * Compute the greatest lower bound of two values. - * - *

Important: This method must fulfill the following contract: - * - *

    - *
  • Does not change {@code this}. - *
  • Does not change {@code other}. - *
  • Returns a fresh object which is not aliased yet. - *
  • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
  • Is commutative. - *
+ * Combines the two sets. * - * @param other another value - * @return the greatest lower bound of two values + * @param aTypeMirror the type mirror associated with {@code aSet} + * @param aSet a set of annotation mirrors + * @param bTypeMirror the type mirror associated with {@code bSet} + * @param bSet a set of annotation mirrors + * @param canCombinedSetBeMissingAnnos whether or not the combined set can be missing + * annotations + * @return the combined sets */ - public V greatestLowerBound(@Nullable V other) { - if (other == null) { - @SuppressWarnings("unchecked") - V v = (V) this; - return v; + protected AnnotationMirrorSet combineSets( + TypeMirror aTypeMirror, + AnnotationMirrorSet aSet, + TypeMirror bTypeMirror, + AnnotationMirrorSet bSet, + boolean canCombinedSetBeMissingAnnos) { + if (aTypeMirror == null) { + throw new NullPointerException("combineSets: aTypeMirror==null"); + } + if (bTypeMirror == null) { + throw new NullPointerException("combineSets: bTypeMirror==null"); + } + + AnnotatedTypeVariable aAtv = getEffectiveTypeVar(aTypeMirror); + AnnotatedTypeVariable bAtv = getEffectiveTypeVar(bTypeMirror); + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet combinedSets = new AnnotationMirrorSet(); + for (AnnotationMirror top : tops) { + AnnotationMirror a = qualHierarchy.findAnnotationInHierarchy(aSet, top); + AnnotationMirror b = qualHierarchy.findAnnotationInHierarchy(bSet, top); + AnnotationMirror result; + if (a != null && b != null) { + result = combineTwoAnnotations(a, aTypeMirror, b, bTypeMirror, top); + } else if (a != null) { + result = combineAnnotationWithTypeVar(a, bAtv, top, canCombinedSetBeMissingAnnos); + } else if (b != null) { + result = combineAnnotationWithTypeVar(b, aAtv, top, canCombinedSetBeMissingAnnos); + } else { + result = combineTwoTypeVars(aAtv, bAtv, top, canCombinedSetBeMissingAnnos); + } + if (result != null) { + combinedSets.add(result); } - ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); - TypeMirror glbTypeMirror = - TypesUtils.greatestLowerBound( - this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); - - ValueGlb valueGlb = new ValueGlb(); - AnnotationMirrorSet glb = - valueGlb.combineSets( - this.getUnderlyingType(), - this.getAnnotations(), - other.getUnderlyingType(), - other.getAnnotations(), - canBeMissingAnnotations(glbTypeMirror)); - return analysis.createAbstractValue(glb, glbTypeMirror); + } + return combinedSets; } /** - * Computes the GLB of two sets of annotations. The computation accounts for sets that are - * missing annotations in hierarchies. + * Returns the result of combining the two annotations. This method is called when an annotation + * exists in both sets for the hierarchy whose top is {@code top}. + * + * @param a an annotation in the hierarchy + * @param aTypeMirror the type that is annotated by {@code a} + * @param b an annotation in the hierarchy + * @param bTypeMirror the type that is annotated by {@code b} + * @param top the top annotation in the hierarchy + * @return the result of combining the two annotations or null if no combination exists */ - protected class ValueGlb extends AnnotationSetCombiner { - - @Override - protected @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - return qualHierarchy.greatestLowerBoundShallow(a, aTypeMirror, b, bTypeMirror); - } - - @Override - protected @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - if (canCombinedSetBeMissingAnnos) { - // don't add an annotation - return null; - } else { - AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); - TypeMirror aTM = aAtv.getUnderlyingType(); - TypeMirror bTM = bAtv.getUnderlyingType(); - return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); - } - } - - @Override - protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - TypeMirror typeVarTM = typeVar.getUnderlyingType(); - if (canCombinedSetBeMissingAnnos) { - // anno is the primary annotation on the use of a type variable. typeVar is a use of - // the same type variable that does not have a primary annotation. The glb of the - // two type variables is computed as follows. If anno is a supertype (or equal) to - // the annotation on the upper bound of typeVar, then typeVar is the glb, so no - // annotation is added to glbset. - // If anno is a subtype of the annotation on the upper bound of typeVar, then the - // glb is typeVar with a primary annotation of glb(anno, lowerBound), where - // lowerBound is the annotation on the lower bound of typeVar. - AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); - QualifierHierarchy qualHierarchy = - analysis.getTypeFactory().getQualifierHierarchy(); - if (qualHierarchy.isSubtypeQualifiersOnly(upperBound, annotation)) { - return null; - } else { - AnnotationMirrorSet lBSet = - AnnotatedTypes.findEffectiveLowerBoundAnnotations( - qualHierarchy, typeVar); - AnnotationMirror lowerBound = - qualHierarchy.findAnnotationInHierarchy(lBSet, top); - return combineTwoAnnotations(annotation, typeVarTM, lowerBound, typeVarTM, top); - } - } else { - return combineTwoAnnotations( - annotation, - typeVarTM, - typeVar.getEffectiveAnnotationInHierarchy(top), - typeVarTM, - top); - } - } - } + protected abstract @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top); /** - * Combines two sets of AnnotationMirrors by hierarchy. - * - *

Subclasses must define how to combine sets by implementing the following methods: + * Returns the primary annotation that result from of combining the two {@link + * AnnotatedTypeVariable}. If the result has no primary annotation, {@code null} is returned. + * This method is called when no annotation exists in either sets for the hierarchy whose top is + * {@code top}. * - *

    - *
  1. {@link #combineTwoAnnotations} - *
  2. {@link #combineAnnotationWithTypeVar} - *
  3. {@link #combineTwoTypeVars} - *
- * - * If a set is missing an annotation in a hierarchy, and if the combined set can be missing an - * annotation, then there must be a TypeVariable for the set that can be used to find - * annotations on its bounds. + * @param aAtv a type variable that does not have a primary annotation in {@code top} hierarchy + * @param bAtv a type variable that does not have a primary annotation in {@code top} hierarchy + * @param top the top annotation in the hierarchy + * @param canCombinedSetBeMissingAnnos whether or not + * @return the result of combining the two type variables, which may be null */ - protected abstract class AnnotationSetCombiner { - - /** - * Combines the two sets. - * - * @param aTypeMirror the type mirror associated with {@code aSet} - * @param aSet a set of annotation mirrors - * @param bTypeMirror the type mirror associated with {@code bSet} - * @param bSet a set of annotation mirrors - * @param canCombinedSetBeMissingAnnos whether or not the combined set can be missing - * annotations - * @return the combined sets - */ - protected AnnotationMirrorSet combineSets( - TypeMirror aTypeMirror, - AnnotationMirrorSet aSet, - TypeMirror bTypeMirror, - AnnotationMirrorSet bSet, - boolean canCombinedSetBeMissingAnnos) { - if (aTypeMirror == null) { - throw new NullPointerException("combineSets: aTypeMirror==null"); - } - if (bTypeMirror == null) { - throw new NullPointerException("combineSets: bTypeMirror==null"); - } - - AnnotatedTypeVariable aAtv = getEffectiveTypeVar(aTypeMirror); - AnnotatedTypeVariable bAtv = getEffectiveTypeVar(bTypeMirror); - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet combinedSets = new AnnotationMirrorSet(); - for (AnnotationMirror top : tops) { - AnnotationMirror a = qualHierarchy.findAnnotationInHierarchy(aSet, top); - AnnotationMirror b = qualHierarchy.findAnnotationInHierarchy(bSet, top); - AnnotationMirror result; - if (a != null && b != null) { - result = combineTwoAnnotations(a, aTypeMirror, b, bTypeMirror, top); - } else if (a != null) { - result = - combineAnnotationWithTypeVar( - a, bAtv, top, canCombinedSetBeMissingAnnos); - } else if (b != null) { - result = - combineAnnotationWithTypeVar( - b, aAtv, top, canCombinedSetBeMissingAnnos); - } else { - result = combineTwoTypeVars(aAtv, bAtv, top, canCombinedSetBeMissingAnnos); - } - if (result != null) { - combinedSets.add(result); - } - } - return combinedSets; - } - - /** - * Returns the result of combining the two annotations. This method is called when an - * annotation exists in both sets for the hierarchy whose top is {@code top}. - * - * @param a an annotation in the hierarchy - * @param aTypeMirror the type that is annotated by {@code a} - * @param b an annotation in the hierarchy - * @param bTypeMirror the type that is annotated by {@code b} - * @param top the top annotation in the hierarchy - * @return the result of combining the two annotations or null if no combination exists - */ - protected abstract @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top); - - /** - * Returns the primary annotation that result from of combining the two {@link - * AnnotatedTypeVariable}. If the result has no primary annotation, {@code null} is - * returned. This method is called when no annotation exists in either sets for the - * hierarchy whose top is {@code top}. - * - * @param aAtv a type variable that does not have a primary annotation in {@code top} - * hierarchy - * @param bAtv a type variable that does not have a primary annotation in {@code top} - * hierarchy - * @param top the top annotation in the hierarchy - * @param canCombinedSetBeMissingAnnos whether or not - * @return the result of combining the two type variables, which may be null - */ - protected abstract @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos); - - /** - * Returns the result of combining {@code annotation} with {@code typeVar}. - * - *

This is called when an annotation exists for the hierarchy in one set, but not the - * other. - * - * @param annotation an annotation - * @param typeVar a type variable that does not have a primary annotation in the hierarchy - * @param top the top annotation of the hierarchy - * @param canCombinedSetBeMissingAnnos whether or not - * @return the result of combining {@code annotation} with {@code typeVar} - */ - protected abstract @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos); - } + protected abstract @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos); /** - * Returns the AnnotatedTypeVariable associated with the given TypeMirror or null. + * Returns the result of combining {@code annotation} with {@code typeVar}. * - *

If {@code typeMirror} is a type variable, then the {@link AnnotatedTypeVariable} of its - * declaration is returned. If {@code typeMirror} is a wildcard whose extends bounds is a type - * variable, then the {@link AnnotatedTypeVariable} for its declaration is returned. Otherwise, - * {@code null} is returned. + *

This is called when an annotation exists for the hierarchy in one set, but not the other. * - * @param typeMirror a type mirror - * @return the AnnotatedTypeVariable associated with the given TypeMirror or null + * @param annotation an annotation + * @param typeVar a type variable that does not have a primary annotation in the hierarchy + * @param top the top annotation of the hierarchy + * @param canCombinedSetBeMissingAnnos whether or not + * @return the result of combining {@code annotation} with {@code typeVar} */ - private @Nullable AnnotatedTypeVariable getEffectiveTypeVar(@Nullable TypeMirror typeMirror) { - if (typeMirror == null) { - return null; - } else if (typeMirror.getKind() == TypeKind.WILDCARD) { - return getEffectiveTypeVar(((WildcardType) typeMirror).getExtendsBound()); - - } else if (typeMirror.getKind() == TypeKind.TYPEVAR) { - TypeVariable typevar = ((TypeVariable) typeMirror); - AnnotatedTypeMirror atm = - analysis.getTypeFactory().getAnnotatedType(typevar.asElement()); - return (AnnotatedTypeVariable) atm; - } else { - return null; - } + protected abstract @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos); + } + + /** + * Returns the AnnotatedTypeVariable associated with the given TypeMirror or null. + * + *

If {@code typeMirror} is a type variable, then the {@link AnnotatedTypeVariable} of its + * declaration is returned. If {@code typeMirror} is a wildcard whose extends bounds is a type + * variable, then the {@link AnnotatedTypeVariable} for its declaration is returned. Otherwise, + * {@code null} is returned. + * + * @param typeMirror a type mirror + * @return the AnnotatedTypeVariable associated with the given TypeMirror or null + */ + private @Nullable AnnotatedTypeVariable getEffectiveTypeVar(@Nullable TypeMirror typeMirror) { + if (typeMirror == null) { + return null; + } else if (typeMirror.getKind() == TypeKind.WILDCARD) { + return getEffectiveTypeVar(((WildcardType) typeMirror).getExtendsBound()); + + } else if (typeMirror.getKind() == TypeKind.TYPEVAR) { + TypeVariable typevar = ((TypeVariable) typeMirror); + AnnotatedTypeMirror atm = analysis.getTypeFactory().getAnnotatedType(typevar.asElement()); + return (AnnotatedTypeVariable) atm; + } else { + return null; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java index 05a86465320..d0cea9a06b9 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java @@ -1,38 +1,37 @@ package org.checkerframework.framework.flow; +import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.javacutil.AnnotationMirrorSet; -import javax.lang.model.type.TypeMirror; - /** The default org.checkerframework.dataflow analysis used in the Checker Framework. */ public class CFAnalysis extends CFAbstractAnalysis { - /** - * Creates a new {@code CFAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public CFAnalysis( - BaseTypeChecker checker, - GenericAnnotatedTypeFactory factory) { - super(checker, factory); - } + /** + * Creates a new {@code CFAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public CFAnalysis( + BaseTypeChecker checker, + GenericAnnotatedTypeFactory factory) { + super(checker, factory); + } - @Override - public CFStore createEmptyStore(boolean sequentialSemantics) { - return new CFStore(this, sequentialSemantics); - } + @Override + public CFStore createEmptyStore(boolean sequentialSemantics) { + return new CFStore(this, sequentialSemantics); + } - @Override - public CFStore createCopiedStore(CFStore s) { - return new CFStore(s); - } + @Override + public CFStore createCopiedStore(CFStore s) { + return new CFStore(s); + } - @Override - public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { - return defaultCreateAbstractValue(this, annotations, underlyingType); - } + @Override + public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { + return defaultCreateAbstractValue(this, annotations, underlyingType); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java b/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java index 9a4b064933b..9ca70e887e7 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java @@ -7,7 +7,13 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.Collection; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.UnderlyingAST; @@ -23,207 +29,197 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.UserError; -import java.util.Collection; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * A control-flow graph builder (see {@link CFGBuilder}) that knows about the Checker Framework * annotations and their representation as {@link AnnotatedTypeMirror}s. */ public class CFCFGBuilder extends CFGBuilder { - /** This class should never be instantiated. Protected to still allow subclasses. */ - protected CFCFGBuilder() {} - - /** Build the control flow graph of some code. */ - public static ControlFlowGraph build( - CompilationUnitTree root, - UnderlyingAST underlyingAST, - BaseTypeChecker checker, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment env) { - boolean assumeAssertionsEnabled = checker.hasOption("assumeAssertionsAreEnabled"); - boolean assumeAssertionsDisabled = checker.hasOption("assumeAssertionsAreDisabled"); - if (assumeAssertionsEnabled && assumeAssertionsDisabled) { - throw new UserError( - "Assertions cannot be assumed to be enabled and disabled at the same time."); - } + /** This class should never be instantiated. Protected to still allow subclasses. */ + protected CFCFGBuilder() {} + + /** Build the control flow graph of some code. */ + public static ControlFlowGraph build( + CompilationUnitTree root, + UnderlyingAST underlyingAST, + BaseTypeChecker checker, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment env) { + boolean assumeAssertionsEnabled = checker.hasOption("assumeAssertionsAreEnabled"); + boolean assumeAssertionsDisabled = checker.hasOption("assumeAssertionsAreDisabled"); + if (assumeAssertionsEnabled && assumeAssertionsDisabled) { + throw new UserError( + "Assertions cannot be assumed to be enabled and disabled at the same time."); + } - // Subcheckers with dataflow share control-flow graph structure to - // allow a super-checker to query the stores of a subchecker. - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory asGATF = - (GenericAnnotatedTypeFactory) atypeFactory; - if (asGATF.hasOrIsSubchecker) { - ControlFlowGraph sharedCFG = asGATF.getSharedCFGForTree(underlyingAST.getCode()); - if (sharedCFG != null) { - return sharedCFG; - } - } + // Subcheckers with dataflow share control-flow graph structure to + // allow a super-checker to query the stores of a subchecker. + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + GenericAnnotatedTypeFactory asGATF = + (GenericAnnotatedTypeFactory) atypeFactory; + if (asGATF.hasOrIsSubchecker) { + ControlFlowGraph sharedCFG = asGATF.getSharedCFGForTree(underlyingAST.getCode()); + if (sharedCFG != null) { + return sharedCFG; } + } + } - CFTreeBuilder builder = new CFTreeBuilder(env); - PhaseOneResult phase1result = - new CFCFGTranslationPhaseOne( - builder, - checker, - atypeFactory, - assumeAssertionsEnabled, - assumeAssertionsDisabled, - env) - .process(root, underlyingAST); - ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); - ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory asGATF = - (GenericAnnotatedTypeFactory) atypeFactory; - if (asGATF.hasOrIsSubchecker) { - asGATF.addSharedCFGForTree(underlyingAST.getCode(), phase3result); - } + CFTreeBuilder builder = new CFTreeBuilder(env); + PhaseOneResult phase1result = + new CFCFGTranslationPhaseOne( + builder, + checker, + atypeFactory, + assumeAssertionsEnabled, + assumeAssertionsDisabled, + env) + .process(root, underlyingAST); + ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + GenericAnnotatedTypeFactory asGATF = + (GenericAnnotatedTypeFactory) atypeFactory; + if (asGATF.hasOrIsSubchecker) { + asGATF.addSharedCFGForTree(underlyingAST.getCode(), phase3result); + } + } + return phase3result; + } + + /** + * Given a SourceChecker and an AssertTree, returns whether the AssertTree uses + * an @AssumeAssertion string that is relevant to the SourceChecker. + * + * @param checker the checker + * @param tree an assert tree + * @return true if the assert tree contains an @AssumeAssertion(checker) message string for any + * subchecker of the given checker's ultimate parent checker + */ + public static boolean assumeAssertionsActivatedForAssertTree( + BaseTypeChecker checker, AssertTree tree) { + ExpressionTree detail = tree.getDetail(); + if (detail != null) { + String msg = detail.toString(); + BaseTypeChecker ultimateParent = checker.getUltimateParentChecker(); + Collection prefixes = ultimateParent.getSuppressWarningsPrefixesOfSubcheckers(); + for (String prefix : prefixes) { + String assumeAssert = "@AssumeAssertion(" + prefix + ")"; + if (msg.contains(assumeAssert)) { + return true; } - return phase3result; + } } - /** - * Given a SourceChecker and an AssertTree, returns whether the AssertTree uses - * an @AssumeAssertion string that is relevant to the SourceChecker. - * - * @param checker the checker - * @param tree an assert tree - * @return true if the assert tree contains an @AssumeAssertion(checker) message string for any - * subchecker of the given checker's ultimate parent checker - */ - public static boolean assumeAssertionsActivatedForAssertTree( - BaseTypeChecker checker, AssertTree tree) { - ExpressionTree detail = tree.getDetail(); - if (detail != null) { - String msg = detail.toString(); - BaseTypeChecker ultimateParent = checker.getUltimateParentChecker(); - Collection prefixes = ultimateParent.getSuppressWarningsPrefixesOfSubcheckers(); - for (String prefix : prefixes) { - String assumeAssert = "@AssumeAssertion(" + prefix + ")"; - if (msg.contains(assumeAssert)) { - return true; - } - } - } + return false; + } + + /** + * A specialized phase-one CFG builder, with a few modifications that make use of the type + * factory. It is responsible for: 1) translating foreach loops so that the declarations of their + * iteration variables have the right annotations, 2) registering the containing elements of + * artificial trees with the relevant type factories, and 3) generating appropriate assertion CFG + * structure in the presence of @AssumeAssertion assertion strings which mention the checker or + * its supercheckers. + */ + protected static class CFCFGTranslationPhaseOne extends CFGTranslationPhaseOne { + /** The associated checker. */ + protected final BaseTypeChecker checker; + + /** Type factory to provide types used during CFG building. */ + protected final AnnotatedTypeFactory atypeFactory; + + public CFCFGTranslationPhaseOne( + CFTreeBuilder builder, + BaseTypeChecker checker, + AnnotatedTypeFactory atypeFactory, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + super(builder, atypeFactory, assumeAssertionsEnabled, assumeAssertionsDisabled, env); + this.checker = checker; + this.atypeFactory = atypeFactory; + } - return false; + @Override + protected boolean assumeAssertionsEnabledFor(AssertTree tree) { + if (assumeAssertionsActivatedForAssertTree(checker, tree)) { + return true; + } + return super.assumeAssertionsEnabledFor(tree); } /** - * A specialized phase-one CFG builder, with a few modifications that make use of the type - * factory. It is responsible for: 1) translating foreach loops so that the declarations of - * their iteration variables have the right annotations, 2) registering the containing elements - * of artificial trees with the relevant type factories, and 3) generating appropriate assertion - * CFG structure in the presence of @AssumeAssertion assertion strings which mention the checker - * or its supercheckers. + * {@inheritDoc} + * + *

Assigns a path to the artificial tree. + * + * @param tree the newly created Tree */ - protected static class CFCFGTranslationPhaseOne extends CFGTranslationPhaseOne { - /** The associated checker. */ - protected final BaseTypeChecker checker; - - /** Type factory to provide types used during CFG building. */ - protected final AnnotatedTypeFactory atypeFactory; - - public CFCFGTranslationPhaseOne( - CFTreeBuilder builder, - BaseTypeChecker checker, - AnnotatedTypeFactory atypeFactory, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - super(builder, atypeFactory, assumeAssertionsEnabled, assumeAssertionsDisabled, env); - this.checker = checker; - this.atypeFactory = atypeFactory; - } - - @Override - protected boolean assumeAssertionsEnabledFor(AssertTree tree) { - if (assumeAssertionsActivatedForAssertTree(checker, tree)) { - return true; - } - return super.assumeAssertionsEnabledFor(tree); - } - - /** - * {@inheritDoc} - * - *

Assigns a path to the artificial tree. - * - * @param tree the newly created Tree - */ - @Override - public void handleArtificialTree(Tree tree) { - // Create a new child of the current path and assign to the artificial tree. - // Although intuitively, using the sibling of the current path as the artificial tree - // path makes more sense, it has the risk of improperly changing the defaulting scope - // of the artificial tree. - TreePath artificialPath = new TreePath(getCurrentPath(), tree); - atypeFactory.setPathForArtificialTree(tree, artificialPath); - } + @Override + public void handleArtificialTree(Tree tree) { + // Create a new child of the current path and assign to the artificial tree. + // Although intuitively, using the sibling of the current path as the artificial tree + // path makes more sense, it has the risk of improperly changing the defaulting scope + // of the artificial tree. + TreePath artificialPath = new TreePath(getCurrentPath(), tree); + atypeFactory.setPathForArtificialTree(tree, artificialPath); + } - @Override - protected VariableTree createEnhancedForLoopIteratorVariable( - MethodInvocationTree iteratorCall, VariableElement variableElement) { - Tree annotatedIteratorTypeTree = - ((CFTreeBuilder) treeBuilder) - .buildAnnotatedType(TreeUtils.typeOf(iteratorCall)); - handleArtificialTree(annotatedIteratorTypeTree); - - // Declare and initialize a new, unique iterator variable - VariableTree iteratorVariable = - treeBuilder.buildVariableDecl( - annotatedIteratorTypeTree, - uniqueName("iter"), - variableElement.getEnclosingElement(), - iteratorCall); - return iteratorVariable; - } + @Override + protected VariableTree createEnhancedForLoopIteratorVariable( + MethodInvocationTree iteratorCall, VariableElement variableElement) { + Tree annotatedIteratorTypeTree = + ((CFTreeBuilder) treeBuilder).buildAnnotatedType(TreeUtils.typeOf(iteratorCall)); + handleArtificialTree(annotatedIteratorTypeTree); + + // Declare and initialize a new, unique iterator variable + VariableTree iteratorVariable = + treeBuilder.buildVariableDecl( + annotatedIteratorTypeTree, + uniqueName("iter"), + variableElement.getEnclosingElement(), + iteratorCall); + return iteratorVariable; + } - @Override - protected VariableTree createEnhancedForLoopArrayVariable( - ExpressionTree expression, VariableElement variableElement) { - - TypeMirror type = null; - if (TreeUtils.isLocalVariable(expression)) { - // It is necessary to get the elt because just getting the type of expression - // directly (via TreeUtils.typeOf) doesn't include annotations on the declarations - // of local variables, for some reason. - Element elt = TreeUtils.elementFromTree(expression); - if (elt != null) { - type = ElementUtils.getType(elt); - } - } - - // In all other cases, instead get the type of the expression. This case is - // also triggered when the type from the element is not an array, which can occur - // if the declaration of the local is a generic, such as in - // framework/tests/all-systems/java8inference/Issue1775.java. - // Getting the type from the expression itself guarantees the result will be an array. - if (type == null || type.getKind() != TypeKind.ARRAY) { - TypeMirror expressionType = TreeUtils.typeOf(expression); - type = expressionType; - } - - assert (type instanceof ArrayType) : "array types must be represented by ArrayType"; - - Tree annotatedArrayTypeTree = ((CFTreeBuilder) treeBuilder).buildAnnotatedType(type); - handleArtificialTree(annotatedArrayTypeTree); - - // Declare and initialize a temporary array variable - VariableTree arrayVariable = - treeBuilder.buildVariableDecl( - annotatedArrayTypeTree, - uniqueName("array"), - variableElement.getEnclosingElement(), - expression); - return arrayVariable; + @Override + protected VariableTree createEnhancedForLoopArrayVariable( + ExpressionTree expression, VariableElement variableElement) { + + TypeMirror type = null; + if (TreeUtils.isLocalVariable(expression)) { + // It is necessary to get the elt because just getting the type of expression + // directly (via TreeUtils.typeOf) doesn't include annotations on the declarations + // of local variables, for some reason. + Element elt = TreeUtils.elementFromTree(expression); + if (elt != null) { + type = ElementUtils.getType(elt); } + } + + // In all other cases, instead get the type of the expression. This case is + // also triggered when the type from the element is not an array, which can occur + // if the declaration of the local is a generic, such as in + // framework/tests/all-systems/java8inference/Issue1775.java. + // Getting the type from the expression itself guarantees the result will be an array. + if (type == null || type.getKind() != TypeKind.ARRAY) { + TypeMirror expressionType = TreeUtils.typeOf(expression); + type = expressionType; + } + + assert (type instanceof ArrayType) : "array types must be represented by ArrayType"; + + Tree annotatedArrayTypeTree = ((CFTreeBuilder) treeBuilder).buildAnnotatedType(type); + handleArtificialTree(annotatedArrayTypeTree); + + // Declare and initialize a temporary array variable + VariableTree arrayVariable = + treeBuilder.buildVariableDecl( + annotatedArrayTypeTree, + uniqueName("array"), + variableElement.getEnclosingElement(), + expression); + return arrayVariable; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java index a2c6c17e7ed..b8024425894 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java @@ -3,16 +3,16 @@ /** The default store used in the Checker Framework. */ public class CFStore extends CFAbstractStore { - public CFStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - } + public CFStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } - /** - * Copy constructor. - * - * @param other the CFStore to copy - */ - public CFStore(CFAbstractStore other) { - super(other); - } + /** + * Copy constructor. + * + * @param other the CFStore to copy + */ + public CFStore(CFAbstractStore other) { + super(other); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java index d463d43362a..55905ac0adc 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java @@ -3,7 +3,7 @@ /** The default transfer function used in the Checker Framework. */ public class CFTransfer extends CFAbstractTransfer { - public CFTransfer(CFAbstractAnalysis analysis) { - super(analysis); - } + public CFTransfer(CFAbstractAnalysis analysis) { + super(analysis); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java b/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java index 8a9ff638724..15a7952d72a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java @@ -12,14 +12,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.util.List; - -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.trees.TreeBuilder; - import java.util.Collection; import java.util.HashSet; import java.util.Set; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.ArrayType; @@ -28,6 +23,8 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; +import org.checkerframework.javacutil.TypeAnnotationUtils; +import org.checkerframework.javacutil.trees.TreeBuilder; /** * The TreeBuilder permits the creation of new AST Trees using the non-public Java compiler API @@ -36,178 +33,173 @@ */ public class CFTreeBuilder extends TreeBuilder { - /** - * To avoid infinite recursions, record each wildcard that has been converted to a tree. This - * set is cleared each time {@link #buildAnnotatedType(TypeMirror)} is called. - */ - private final Set visitedWildcards = new HashSet<>(); - - /** - * Creates a {@code CFTreeBuilder}. - * - * @param env environment - */ - public CFTreeBuilder(ProcessingEnvironment env) { - super(env); - } - - /** - * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. - * - * @param type the type - * @return a Tree representing the type - */ - public Tree buildAnnotatedType(TypeMirror type) { - visitedWildcards.clear(); - return createAnnotatedType(type); + /** + * To avoid infinite recursions, record each wildcard that has been converted to a tree. This set + * is cleared each time {@link #buildAnnotatedType(TypeMirror)} is called. + */ + private final Set visitedWildcards = new HashSet<>(); + + /** + * Creates a {@code CFTreeBuilder}. + * + * @param env environment + */ + public CFTreeBuilder(ProcessingEnvironment env) { + super(env); + } + + /** + * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. + * + * @param type the type + * @return a Tree representing the type + */ + public Tree buildAnnotatedType(TypeMirror type) { + visitedWildcards.clear(); + return createAnnotatedType(type); + } + + /** + * Converts a list of AnnotationMirrors to the a corresponding list of new AnnotationTrees. + * + * @param annotations the annotations + * @return new annotation trees representing the annotations + */ + private List convertAnnotationMirrorsToAnnotationTrees( + Collection annotations) { + List annotationTrees = List.nil(); + + for (AnnotationMirror am : annotations) { + // TODO: what TypeAnnotationPosition should be used? + Attribute.TypeCompound typeCompound = + TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( + am, TypeAnnotationUtils.unknownTAPosition(), env); + JCAnnotation annotationTree = maker.Annotation(typeCompound); + JCAnnotation typeAnnotationTree = + maker.TypeAnnotation(annotationTree.getAnnotationType(), annotationTree.getArguments()); + + typeAnnotationTree.attribute = typeCompound; + + annotationTrees = annotationTrees.append(typeAnnotationTree); } - - /** - * Converts a list of AnnotationMirrors to the a corresponding list of new AnnotationTrees. - * - * @param annotations the annotations - * @return new annotation trees representing the annotations - */ - private List convertAnnotationMirrorsToAnnotationTrees( - Collection annotations) { - List annotationTrees = List.nil(); - - for (AnnotationMirror am : annotations) { - // TODO: what TypeAnnotationPosition should be used? - Attribute.TypeCompound typeCompound = - TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( - am, TypeAnnotationUtils.unknownTAPosition(), env); - JCAnnotation annotationTree = maker.Annotation(typeCompound); - JCAnnotation typeAnnotationTree = - maker.TypeAnnotation( - annotationTree.getAnnotationType(), annotationTree.getArguments()); - - typeAnnotationTree.attribute = typeCompound; - - annotationTrees = annotationTrees.append(typeAnnotationTree); + return annotationTrees; + } + + /** + * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. This + * internal method differs from the public {@link #buildAnnotatedType(TypeMirror)} only in that it + * does not reset the list of visited wildcards. + * + * @param type the type for which to create a tree + * @return a Tree representing the type + */ + private Tree createAnnotatedType(TypeMirror type) { + // Implementation based on com.sun.tools.javac.tree.TreeMaker.Type + + // Convert the annotations from a set of AnnotationMirrors + // to a list of AnnotationTrees. + java.util.List annotations = type.getAnnotationMirrors(); + List annotationTrees = convertAnnotationMirrorsToAnnotationTrees(annotations); + + // Convert the underlying type from a TypeMirror to an ExpressionTree and combine with the + // AnnotationTrees to form a ClassTree of kind ANNOTATION_TYPE. + JCExpression typeTree; + switch (type.getKind()) { + case BYTE: + typeTree = maker.TypeIdent(TypeTag.BYTE); + break; + case CHAR: + typeTree = maker.TypeIdent(TypeTag.CHAR); + break; + case SHORT: + typeTree = maker.TypeIdent(TypeTag.SHORT); + break; + case INT: + typeTree = maker.TypeIdent(TypeTag.INT); + break; + case LONG: + typeTree = maker.TypeIdent(TypeTag.LONG); + break; + case FLOAT: + typeTree = maker.TypeIdent(TypeTag.FLOAT); + break; + case DOUBLE: + typeTree = maker.TypeIdent(TypeTag.DOUBLE); + break; + case BOOLEAN: + typeTree = maker.TypeIdent(TypeTag.BOOLEAN); + break; + case VOID: + typeTree = maker.TypeIdent(TypeTag.VOID); + break; + case TYPEVAR: + // No recursive annotations. + TypeVariable underlyingTypeVar = (TypeVariable) type; + typeTree = maker.Ident((TypeSymbol) underlyingTypeVar.asElement()); + break; + case WILDCARD: + WildcardType wildcardType = (WildcardType) type; + boolean visitedBefore = !visitedWildcards.add(wildcardType); + if (!visitedBefore && wildcardType.getExtendsBound() != null) { + Tree annotatedExtendsBound = createAnnotatedType(wildcardType.getExtendsBound()); + typeTree = + maker.Wildcard( + maker.TypeBoundKind(BoundKind.EXTENDS), (JCTree) annotatedExtendsBound); + } else if (!visitedBefore && wildcardType.getSuperBound() != null) { + Tree annotatedSuperBound = createAnnotatedType(wildcardType.getSuperBound()); + typeTree = + maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), (JCTree) annotatedSuperBound); + } else { + typeTree = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); } - return annotationTrees; - } - - /** - * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. This - * internal method differs from the public {@link #buildAnnotatedType(TypeMirror)} only in that - * it does not reset the list of visited wildcards. - * - * @param type the type for which to create a tree - * @return a Tree representing the type - */ - private Tree createAnnotatedType(TypeMirror type) { - // Implementation based on com.sun.tools.javac.tree.TreeMaker.Type - - // Convert the annotations from a set of AnnotationMirrors - // to a list of AnnotationTrees. - java.util.List annotations = type.getAnnotationMirrors(); - List annotationTrees = convertAnnotationMirrorsToAnnotationTrees(annotations); - - // Convert the underlying type from a TypeMirror to an ExpressionTree and combine with the - // AnnotationTrees to form a ClassTree of kind ANNOTATION_TYPE. - JCExpression typeTree; - switch (type.getKind()) { - case BYTE: - typeTree = maker.TypeIdent(TypeTag.BYTE); - break; - case CHAR: - typeTree = maker.TypeIdent(TypeTag.CHAR); - break; - case SHORT: - typeTree = maker.TypeIdent(TypeTag.SHORT); - break; - case INT: - typeTree = maker.TypeIdent(TypeTag.INT); - break; - case LONG: - typeTree = maker.TypeIdent(TypeTag.LONG); - break; - case FLOAT: - typeTree = maker.TypeIdent(TypeTag.FLOAT); - break; - case DOUBLE: - typeTree = maker.TypeIdent(TypeTag.DOUBLE); - break; - case BOOLEAN: - typeTree = maker.TypeIdent(TypeTag.BOOLEAN); - break; - case VOID: - typeTree = maker.TypeIdent(TypeTag.VOID); - break; - case TYPEVAR: - // No recursive annotations. - TypeVariable underlyingTypeVar = (TypeVariable) type; - typeTree = maker.Ident((TypeSymbol) underlyingTypeVar.asElement()); - break; - case WILDCARD: - WildcardType wildcardType = (WildcardType) type; - boolean visitedBefore = !visitedWildcards.add(wildcardType); - if (!visitedBefore && wildcardType.getExtendsBound() != null) { - Tree annotatedExtendsBound = - createAnnotatedType(wildcardType.getExtendsBound()); - typeTree = - maker.Wildcard( - maker.TypeBoundKind(BoundKind.EXTENDS), - (JCTree) annotatedExtendsBound); - } else if (!visitedBefore && wildcardType.getSuperBound() != null) { - Tree annotatedSuperBound = createAnnotatedType(wildcardType.getSuperBound()); - typeTree = - maker.Wildcard( - maker.TypeBoundKind(BoundKind.SUPER), - (JCTree) annotatedSuperBound); - } else { - typeTree = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); - } - break; - case INTERSECTION: - IntersectionType intersectionType = (IntersectionType) type; - List components = List.nil(); - for (TypeMirror bound : intersectionType.getBounds()) { - components = components.append((JCExpression) createAnnotatedType(bound)); - } - typeTree = maker.TypeIntersection(components); - break; - // case UNION: - // TODO: case UNION similar to INTERSECTION, but write test first. - case DECLARED: - typeTree = maker.Type((Type) type); - - if (typeTree instanceof JCTypeApply) { - // Replace the type parameters with annotated versions. - DeclaredType annotatedDeclaredType = (DeclaredType) type; - List typeArgTrees = List.nil(); - for (TypeMirror arg : annotatedDeclaredType.getTypeArguments()) { - typeArgTrees = typeArgTrees.append((JCExpression) createAnnotatedType(arg)); - } - JCExpression clazz = (JCExpression) ((JCTypeApply) typeTree).getType(); - typeTree = maker.TypeApply(clazz, typeArgTrees); - } - break; - case ARRAY: - ArrayType arrayType = (ArrayType) type; - Tree componentTree = createAnnotatedType(arrayType.getComponentType()); - typeTree = maker.TypeArray((JCExpression) componentTree); - break; - case ERROR: - typeTree = maker.TypeIdent(TypeTag.ERROR); - break; - default: - assert false : "unexpected type: " + type; - typeTree = null; - break; + break; + case INTERSECTION: + IntersectionType intersectionType = (IntersectionType) type; + List components = List.nil(); + for (TypeMirror bound : intersectionType.getBounds()) { + components = components.append((JCExpression) createAnnotatedType(bound)); } - - typeTree.setType((Type) type); - - if (annotationTrees.isEmpty()) { - return typeTree; + typeTree = maker.TypeIntersection(components); + break; + // case UNION: + // TODO: case UNION similar to INTERSECTION, but write test first. + case DECLARED: + typeTree = maker.Type((Type) type); + + if (typeTree instanceof JCTypeApply) { + // Replace the type parameters with annotated versions. + DeclaredType annotatedDeclaredType = (DeclaredType) type; + List typeArgTrees = List.nil(); + for (TypeMirror arg : annotatedDeclaredType.getTypeArguments()) { + typeArgTrees = typeArgTrees.append((JCExpression) createAnnotatedType(arg)); + } + JCExpression clazz = (JCExpression) ((JCTypeApply) typeTree).getType(); + typeTree = maker.TypeApply(clazz, typeArgTrees); } + break; + case ARRAY: + ArrayType arrayType = (ArrayType) type; + Tree componentTree = createAnnotatedType(arrayType.getComponentType()); + typeTree = maker.TypeArray((JCExpression) componentTree); + break; + case ERROR: + typeTree = maker.TypeIdent(TypeTag.ERROR); + break; + default: + assert false : "unexpected type: " + type; + typeTree = null; + break; + } - JCAnnotatedType annotatedTypeTree = maker.AnnotatedType(annotationTrees, typeTree); - annotatedTypeTree.setType((Type) type); + typeTree.setType((Type) type); - return annotatedTypeTree; + if (annotationTrees.isEmpty()) { + return typeTree; } + + JCAnnotatedType annotatedTypeTree = maker.AnnotatedType(annotationTrees, typeTree); + annotatedTypeTree.setType((Type) type); + + return annotatedTypeTree; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java index fe75cdfda6a..a268e23b09c 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java @@ -1,8 +1,7 @@ package org.checkerframework.framework.flow; -import org.checkerframework.javacutil.AnnotationMirrorSet; - import javax.lang.model.type.TypeMirror; +import org.checkerframework.javacutil.AnnotationMirrorSet; // TODO: CFAbstractValue is also a set of annotations and a TypeMirror. // This documentation does not clarify how this class is different. @@ -11,17 +10,17 @@ */ public class CFValue extends CFAbstractValue { - /** - * Creates a new CFValue. - * - * @param analysis the analysis - * @param annotations the annotations - * @param underlyingType the underlying type - */ - public CFValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - } + /** + * Creates a new CFValue. + * + * @param analysis the analysis + * @param annotations the annotations + * @param underlyingType the underlying type + */ + public CFValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java index e8303662b3d..c90ce4fcdad 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java @@ -4,9 +4,6 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -16,10 +13,10 @@ import java.util.List; import java.util.Map; import java.util.Set; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * An aggregate checker that packages multiple checkers together. The resulting checker invokes the @@ -36,163 +33,160 @@ */ public abstract class AggregateChecker extends SourceChecker { - protected final List checkers; - - /** - * Returns the list of supported checkers to be run together. Subclasses need to override this - * method. - * - * @return the list of checkers to be run - */ - protected abstract Collection> getSupportedCheckers(); - - /** Supported options for this checker. */ - private @MonotonicNonNull Set supportedOptions = null; - - /** Options passed to this checker. */ - private @MonotonicNonNull Map options = null; - - /** Create a new AggregateChecker. */ - protected AggregateChecker() { - Collection> checkerClasses = getSupportedCheckers(); - - checkers = new ArrayList<>(checkerClasses.size()); - for (Class checkerClass : checkerClasses) { - try { - SourceChecker instance = checkerClass.getDeclaredConstructor().newInstance(); - instance.setParentChecker(this); - checkers.add(instance); - } catch (Exception e) { - message( - Diagnostic.Kind.ERROR, - "Couldn't instantiate an instance of " + checkerClass); - } - } + protected final List checkers; + + /** + * Returns the list of supported checkers to be run together. Subclasses need to override this + * method. + * + * @return the list of checkers to be run + */ + protected abstract Collection> getSupportedCheckers(); + + /** Supported options for this checker. */ + private @MonotonicNonNull Set supportedOptions = null; + + /** Options passed to this checker. */ + private @MonotonicNonNull Map options = null; + + /** Create a new AggregateChecker. */ + protected AggregateChecker() { + Collection> checkerClasses = getSupportedCheckers(); + + checkers = new ArrayList<>(checkerClasses.size()); + for (Class checkerClass : checkerClasses) { + try { + SourceChecker instance = checkerClass.getDeclaredConstructor().newInstance(); + instance.setParentChecker(this); + checkers.add(instance); + } catch (Exception e) { + message(Diagnostic.Kind.ERROR, "Couldn't instantiate an instance of " + checkerClass); + } } - - /** - * {@code processingEnv} needs to be set on each checker since we are not calling init on the - * checker, which leaves it null. If one of checkers is an AggregateChecker, its visitors will - * try use checker's processing env which should not be null. - */ - @Override - protected void setProcessingEnvironment(ProcessingEnvironment env) { - super.setProcessingEnvironment(env); - for (SourceChecker checker : checkers) { - checker.setProcessingEnvironment(env); - } + } + + /** + * {@code processingEnv} needs to be set on each checker since we are not calling init on the + * checker, which leaves it null. If one of checkers is an AggregateChecker, its visitors will try + * use checker's processing env which should not be null. + */ + @Override + protected void setProcessingEnvironment(ProcessingEnvironment env) { + super.setProcessingEnvironment(env); + for (SourceChecker checker : checkers) { + checker.setProcessingEnvironment(env); } - - @Override - public void initChecker() { - // No need to call super, it might result in reflective instantiations - // of visitor/factory classes. - // super.initChecker(); - // To prevent the warning that initChecker wasn't called. - messager = processingEnv.getMessager(); - - // first initialize all checkers - for (SourceChecker checker : checkers) { - checker.initChecker(); - } - // then share options as necessary - for (SourceChecker checker : checkers) { - // We need to add all options that are activated for the aggregate to - // the individual checkers. - checker.addOptions(super.getOptions()); - // Each checker should "support" all possible lint options - otherwise - // subchecker A would complain about a lint option for subchecker B. - checker.setSupportedLintOptions(this.getSupportedLintOptions()); - } - allCheckersInited = true; + } + + @Override + public void initChecker() { + // No need to call super, it might result in reflective instantiations + // of visitor/factory classes. + // super.initChecker(); + // To prevent the warning that initChecker wasn't called. + messager = processingEnv.getMessager(); + + // first initialize all checkers + for (SourceChecker checker : checkers) { + checker.initChecker(); } - - // Whether all checkers were successfully initialized. - private boolean allCheckersInited = false; - - // AbstractTypeProcessor delegation - @Override - public final void typeProcess(TypeElement element, TreePath tree) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Log log = Log.instance(context); - if (log.nerrors > this.errsOnLastExit) { - // If there is a Java error, do not perform any of the component type checks, but come - // back for the next compilation unit. - this.errsOnLastExit = log.nerrors; - return; - } - if (!allCheckersInited) { - // If there was an initialization problem, an - // error was already output. Just quit. - return; - } - for (SourceChecker checker : checkers) { - checker.errsOnLastExit = this.errsOnLastExit; - checker.typeProcess(element, tree); - if (checker.javacErrored) { - this.javacErrored = true; - return; - } - this.errsOnLastExit = checker.errsOnLastExit; - } + // then share options as necessary + for (SourceChecker checker : checkers) { + // We need to add all options that are activated for the aggregate to + // the individual checkers. + checker.addOptions(super.getOptions()); + // Each checker should "support" all possible lint options - otherwise + // subchecker A would complain about a lint option for subchecker B. + checker.setSupportedLintOptions(this.getSupportedLintOptions()); } - - @Override - public void typeProcessingOver() { - for (SourceChecker checker : checkers) { - checker.typeProcessingOver(); - } - super.typeProcessingOver(); + allCheckersInited = true; + } + + // Whether all checkers were successfully initialized. + private boolean allCheckersInited = false; + + // AbstractTypeProcessor delegation + @Override + public final void typeProcess(TypeElement element, TreePath tree) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Log log = Log.instance(context); + if (log.nerrors > this.errsOnLastExit) { + // If there is a Java error, do not perform any of the component type checks, but come + // back for the next compilation unit. + this.errsOnLastExit = log.nerrors; + return; } - - @Override - public final Set getSupportedOptions() { - if (this.supportedOptions == null) { - Set options = new HashSet<>(); - for (SourceChecker checker : checkers) { - options.addAll(checker.getSupportedOptions()); - } - options.addAll( - expandCFOptions( - Arrays.asList(this.getClass()), - options.toArray(new String[options.size()]))); - this.supportedOptions = options; - } - return this.supportedOptions; + if (!allCheckersInited) { + // If there was an initialization problem, an + // error was already output. Just quit. + return; } - - @Override - public final Map getOptions() { - if (this.options == null) { - Map options = new HashMap<>(super.getOptions()); - for (SourceChecker checker : checkers) { - options.putAll(checker.getOptions()); - } - this.options = Collections.unmodifiableMap(options); - } - return this.options; + for (SourceChecker checker : checkers) { + checker.errsOnLastExit = this.errsOnLastExit; + checker.typeProcess(element, tree); + if (checker.javacErrored) { + this.javacErrored = true; + return; + } + this.errsOnLastExit = checker.errsOnLastExit; } + } - @Override - public final Set getSupportedLintOptions() { - Set lints = new HashSet<>(); - for (SourceChecker checker : checkers) { - lints.addAll(checker.getSupportedLintOptions()); - } - return lints; + @Override + public void typeProcessingOver() { + for (SourceChecker checker : checkers) { + checker.typeProcessingOver(); } - - @Override - protected SourceVisitor createSourceVisitor() { - return new SourceVisitor(this) { - // Aggregate checkers do not visit source, - // the checkers in the aggregate checker do. - }; + super.typeProcessingOver(); + } + + @Override + public final Set getSupportedOptions() { + if (this.supportedOptions == null) { + Set options = new HashSet<>(); + for (SourceChecker checker : checkers) { + options.addAll(checker.getSupportedOptions()); + } + options.addAll( + expandCFOptions( + Arrays.asList(this.getClass()), options.toArray(new String[options.size()]))); + this.supportedOptions = options; } - - // TODO some methods in a component checker should behave differently if they - // are part of an aggregate, e.g. getSuppressWarningKeys should additionally - // return the name of the aggregate checker. - // We could add a query method in SourceChecker that refers to the aggregate, if present. - // At the moment, all the component checkers manually need to add the name of the aggregate. + return this.supportedOptions; + } + + @Override + public final Map getOptions() { + if (this.options == null) { + Map options = new HashMap<>(super.getOptions()); + for (SourceChecker checker : checkers) { + options.putAll(checker.getOptions()); + } + this.options = Collections.unmodifiableMap(options); + } + return this.options; + } + + @Override + public final Set getSupportedLintOptions() { + Set lints = new HashSet<>(); + for (SourceChecker checker : checkers) { + lints.addAll(checker.getSupportedLintOptions()); + } + return lints; + } + + @Override + protected SourceVisitor createSourceVisitor() { + return new SourceVisitor(this) { + // Aggregate checkers do not visit source, + // the checkers in the aggregate checker do. + }; + } + + // TODO some methods in a component checker should behave differently if they + // are part of an aggregate, e.g. getSuppressWarningKeys should additionally + // return the name of the aggregate checker. + // We could add a query method in SourceChecker that refers to the aggregate, if present. + // At the moment, all the component checkers manually need to add the name of the aggregate. } diff --git a/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java index 80312064795..00412d95f84 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java +++ b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java @@ -1,17 +1,15 @@ package org.checkerframework.framework.source; -import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.qual.AnnotatedFor; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; - import javax.tools.Diagnostic; +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.qual.AnnotatedFor; /** * A {@code DiagMessage} is a kind, a message key, and arguments. The message key will be expanded @@ -21,118 +19,117 @@ */ @AnnotatedFor("nullness") public class DiagMessage { - /** The kind of message. */ - private final Diagnostic.Kind kind; - - /** The message key. */ - private final @CompilerMessageKey String messageKey; - - /** The arguments that will be interpolated into the localized message. */ - private final Object[] args; - - /** - * Create a DiagMessage. - * - * @param kind the kind of message - * @param messageKey the message key - * @param args the arguments that will be interpolated into the localized message - */ - public DiagMessage( - Diagnostic.Kind kind, @CompilerMessageKey String messageKey, Object... args) { - this.kind = kind; - this.messageKey = messageKey; - if (args == null) { - this.args = new Object[0]; /*null->nn*/ - } else { - this.args = Arrays.copyOf(args, args.length); - } + /** The kind of message. */ + private final Diagnostic.Kind kind; + + /** The message key. */ + private final @CompilerMessageKey String messageKey; + + /** The arguments that will be interpolated into the localized message. */ + private final Object[] args; + + /** + * Create a DiagMessage. + * + * @param kind the kind of message + * @param messageKey the message key + * @param args the arguments that will be interpolated into the localized message + */ + public DiagMessage(Diagnostic.Kind kind, @CompilerMessageKey String messageKey, Object... args) { + this.kind = kind; + this.messageKey = messageKey; + if (args == null) { + this.args = new Object[0]; /*null->nn*/ + } else { + this.args = Arrays.copyOf(args, args.length); } - - /** - * Create a DiagMessage with kind ERROR. - * - * @param messageKey the message key - * @param args the arguments that will be interpolated into the localized message - * @return a new DiagMessage - */ - public static DiagMessage error(@CompilerMessageKey String messageKey, Object... args) { - return new DiagMessage(Diagnostic.Kind.ERROR, messageKey, args); - } - - /** - * Returns the kind of this DiagMessage. - * - * @return the kind of this DiagMessage - */ - public Diagnostic.Kind getKind() { - return this.kind; + } + + /** + * Create a DiagMessage with kind ERROR. + * + * @param messageKey the message key + * @param args the arguments that will be interpolated into the localized message + * @return a new DiagMessage + */ + public static DiagMessage error(@CompilerMessageKey String messageKey, Object... args) { + return new DiagMessage(Diagnostic.Kind.ERROR, messageKey, args); + } + + /** + * Returns the kind of this DiagMessage. + * + * @return the kind of this DiagMessage + */ + public Diagnostic.Kind getKind() { + return this.kind; + } + + /** + * Returns the message key of this DiagMessage. + * + * @return the message key of this DiagMessage + */ + public @CompilerMessageKey String getMessageKey() { + return this.messageKey; + } + + /** + * Returns the customized optional arguments for the message. + * + * @return the customized optional arguments for the message + */ + public Object[] getArgs() { + return this.args; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof DiagMessage)) { + return false; } - /** - * Returns the message key of this DiagMessage. - * - * @return the message key of this DiagMessage - */ - public @CompilerMessageKey String getMessageKey() { - return this.messageKey; - } - - /** - * Returns the customized optional arguments for the message. - * - * @return the customized optional arguments for the message - */ - public Object[] getArgs() { - return this.args; - } + DiagMessage other = (DiagMessage) obj; - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof DiagMessage)) { - return false; - } - - DiagMessage other = (DiagMessage) obj; - - return (kind == other.kind - && messageKey.equals(other.messageKey) - && Arrays.equals(args, other.args)); - } - - @Pure - @Override - public int hashCode() { - return Objects.hash(kind, messageKey, Arrays.hashCode(args)); - } + return (kind == other.kind + && messageKey.equals(other.messageKey) + && Arrays.equals(args, other.args)); + } - @SideEffectFree - @Override - public String toString() { - if (args.length == 0) { - return messageKey; - } + @Pure + @Override + public int hashCode() { + return Objects.hash(kind, messageKey, Arrays.hashCode(args)); + } - return kind + messageKey + " : " + Arrays.toString(args); + @SideEffectFree + @Override + public String toString() { + if (args.length == 0) { + return messageKey; } - /** - * Returns the concatenation of the lists. - * - * @param list1 a list of DiagMessage, or null - * @param list2 a list of DiagMessage, or null - * @return the concatenation of the lists - */ - public static @Nullable List mergeLists( - @Nullable List list1, @Nullable List list2) { - if (list1 == null || list1.isEmpty()) { - return list2; - } else if (list2 == null || list2.isEmpty()) { - return list1; - } else { - List result = new ArrayList<>(list1.size() + list2.size()); - result.addAll(list1); - result.addAll(list2); - return result; - } + return kind + messageKey + " : " + Arrays.toString(args); + } + + /** + * Returns the concatenation of the lists. + * + * @param list1 a list of DiagMessage, or null + * @param list2 a list of DiagMessage, or null + * @return the concatenation of the lists + */ + public static @Nullable List mergeLists( + @Nullable List list1, @Nullable List list2) { + if (list1 == null || list1.isEmpty()) { + return list2; + } else if (list2 == null || list2.isEmpty()) { + return list1; + } else { + List result = new ArrayList<>(list1.size() + list2.size()); + result.addAll(list1); + result.addAll(list2); + return result; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index d4edf72c042..8712ff5e2d3 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -18,36 +18,6 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Position; - -import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; -import org.checkerframework.checker.formatter.qual.FormatMethod; -import org.checkerframework.checker.interning.qual.InternedDistinct; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.PolyNull; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.util.CheckerMain; -import org.checkerframework.framework.util.OptionConfiguration; -import org.checkerframework.framework.util.TreePathCacher; -import org.checkerframework.javacutil.AbstractTypeProcessor; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.TreePathUtil; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypeSystemError; -import org.checkerframework.javacutil.UserError; -import org.plumelib.util.ArraySet; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.SystemPlume; -import org.plumelib.util.UtilPlume; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -76,7 +46,6 @@ import java.util.TreeSet; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; - import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; @@ -90,6 +59,34 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.formatter.qual.FormatMethod; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.util.CheckerMain; +import org.checkerframework.framework.util.OptionConfiguration; +import org.checkerframework.framework.util.TreePathCacher; +import org.checkerframework.javacutil.AbstractTypeProcessor; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypeSystemError; +import org.checkerframework.javacutil.UserError; +import org.plumelib.util.ArraySet; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.SystemPlume; +import org.plumelib.util.UtilPlume; // import io.github.classgraph.ClassGraph; @@ -105,3006 +102,2957 @@ * AbstractProcessor} (or even this class). */ @SupportedOptions({ - // When adding a new standard option: - // 1. Add a brief blurb here about the use case - // and a pointer to one prominent use of the option. - // 2. Update the Checker Framework manual: - // * docs/manual/introduction.tex contains a list of all options, - // which should be in the same order as this source code file. - // * a specific section should contain a detailed discussion. - - /// - /// Unsound checking: ignore some errors - /// - - // A comma-separated list of warnings to suppress - // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings - "suppressWarnings", - - // Set inclusion/exclusion of type uses or definitions - // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar - "skipUses", - "onlyUses", - "skipDefs", - "onlyDefs", - - // Unsoundly assume all methods have no side effects, are deterministic, or both. - "assumeSideEffectFree", - "assumeDeterministic", - "assumePure", - // Unsoundly assume getter methods have no side effects and are deterministic. - "assumePureGetters", - - // Whether to assume that assertions are enabled or disabled - // org.checkerframework.framework.flow.CFCFGBuilder.CFCFGBuilder - "assumeAssertionsAreEnabled", - "assumeAssertionsAreDisabled", - - // Whether to ignore all subtype tests for type arguments that - // were inferred for a raw type. Defaults to true. - // org.checkerframework.framework.type.TypeHierarchy.isSubtypeTypeArguments - "ignoreRawTypeArguments", - - // Do not validate meta-annotation @TargetLocations - "ignoreTargetLocations", - - // Treat checker errors as warnings - // org.checkerframework.framework.source.SourceChecker.report - "warns", - - /// - /// More sound (strict checking): enable errors that are disabled by default - /// - - // The next ones *increase* rather than *decrease* soundness. They will eventually be replaced - // by their complements (except -AconcurrentSemantics) and moved into the above section. - - // TODO: Checking of bodies of @SideEffectFree, @Deterministic, and - // @Pure methods is temporarily disabled unless -AcheckPurityAnnotations is - // supplied on the command line. - // Re-enable it after making the analysis more precise. - // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) - "checkPurityAnnotations", - - // TODO: Temporary option to make array subtyping invariant, - // which will be the new default soon. - "invariantArrays", - - // TODO: Temporary option to make casts stricter, in particular when - // casting to an array or generic type. This will be the new default soon. - "checkCastElementType", - - // Whether to type check the enclosing expression of an inner class instantiation. - "checkEnclosingExpr", - - // Whether to use conservative defaults for bytecode and/or source code. - // This option takes arguments "source" and/or "bytecode". - // The default is "-source,-bytecode" (eventually this will be changed to "-source,bytecode"). - // Note, in source code, conservative defaults are never - // applied to code in the scope of an @AnnotatedFor. - // See the "Compiling partially-annotated libraries" and - // "Default qualifiers for \<.class> files (conservative library defaults)" - // sections in the manual for more details - // org.checkerframework.framework.source.SourceChecker.useConservativeDefault - "useConservativeDefaultsForUncheckedCode", - - // Whether to assume sound concurrent semantics or - // simplified sequential semantics - // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics - "concurrentSemantics", - - // Whether to use a conservative value for type arguments that could not be inferred. - // See Issue 979. - "conservativeUninferredTypeArguments", - - // Issues a "redundant.anno" warning if the annotation explicitly written on the type is - // the same as the default annotation for this type and location. - "warnRedundantAnnotations", - - /// - /// Type-checking modes: enable/disable functionality - /// - - // Lint options - // org.checkerframework.framework.source.SourceChecker.getSupportedLintOptions() and similar - "lint", - - // Whether to suggest methods that could be marked @SideEffectFree, - // @Deterministic, or @Pure - // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) - "suggestPureMethods", - - // Whether to resolve reflective method invocations. - // "-AresolveReflection=debug" causes debugging information - // to be output. - "resolveReflection", - - // Whether to use whole-program inference. Takes an argument to specify the output format: - // "-Ainfer=stubs" or "-Ainfer=jaifs". - "infer", - - // Whether to output a copy of each file for which annotations were inferred, formatted - // as an ajava file. Can only be used with -Ainfer=ajava - "inferOutputOriginal", - - // With each warning, in addition to the concrete error key, - // output the SuppressWarnings strings that can be used to - // suppress that warning. - "showSuppressWarningsStrings", - - // Warn about @SuppressWarnings annotations that do not suppress any warnings. - // org.checkerframework.common.basetype.BaseTypeChecker.warnUnneededSuppressions - // org.checkerframework.framework.source.SourceChecker.warnUnneededSuppressions - // org.checkerframework.framework.source.SourceChecker.shouldSuppressWarnings(javax.lang.model.element.Element, java.lang.String) - // org.checkerframework.framework.source.SourceVisitor.checkForSuppressWarningsAnno - "warnUnneededSuppressions", - - // Exceptions to -AwarnUnneededSuppressions. - "warnUnneededSuppressionsExceptions", - - // Require that warning suppression annotations contain a checker key as a prefix in order for - // the warning to be suppressed. - // org.checkerframework.framework.source.SourceChecker.checkSuppressWarnings(java.lang.String[], - // java.lang.String) - "requirePrefixInWarningSuppressions", - - // Print a checker key as a prefix to each typechecking diagnostic. - // org.checkerframework.framework.source.SourceChecker.suppressWarningsString(java.lang.String) - "showPrefixInWarningMessages", - - // Ignore annotations in bytecode that have invalid annotation locations. - // See https://github.com/typetools/checker-framework/issues/2173 - // org.checkerframework.framework.type.ElementAnnotationApplier.apply - "ignoreInvalidAnnotationLocations", - - /// - /// Compatibility options - /// - - // Additional type and declaration annotation aliases - // -AaliasedTypeAnnos={aliases} or -AaliasedDeclAnnos={aliases} - // where `aliases` is in the format - // `FQN.canonical.Qualifier1:FQN.alias1.Qual1,FQN.alias2.Qual1;FQN.canonical.Qualifier2:FQN.alias1.Qual2` - // org.checkerframework.framework.type.AnnotatedTypeFactory - "aliasedTypeAnnos", - "aliasedDeclAnnos", - - /// - /// Partially-annotated libraries - /// - - // Additional stub files to use - // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() - "stubs", - - // Additional ajava files to use - // org.checkerframework.framework.type.AnnotatedTypeFactory.parserAjavaFiles() - "ajava", - - // Whether to print warnings about types/members in a stub file - // that were not found on the class path - // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFound - "stubWarnIfNotFound", - "stubNoWarnIfNotFound", - - // Whether to ignore missing classes even when warnIfNotFound is set to true and other classes - // from the same package are present (useful if a package spans more than one jar). - // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFoundIgnoresClasses - "stubWarnIfNotFoundIgnoresClasses", - - // Whether to print warnings about stub files that overwrite annotations from bytecode. - "stubWarnIfOverwritesBytecode", - - // Whether to print warnings about stub files that are redundant with the annotations from - // bytecode. - "stubWarnIfRedundantWithBytecode", - - // Whether to issue a NOTE rather than a WARNING for -AstubWarn* command-line options - "stubWarnNote", - - // With this option, annotations in stub files are used EVEN IF THE SOURCE FILE IS - // PRESENT. Only use this option when you intend to store types in stub files rather than - // directly in source code, such as during whole-program inference. The annotations in the - // stub files will be glb'd with those in the source code before local inference begins. - "mergeStubsWithSource", - - // Already listed above, but worth noting again in this section: - // "useConservativeDefaultsForUncheckedCode" - - /// - /// Debugging - /// - - /// Amount of detail in messages - - // Print the version of the Checker Framework - "version", - - // Print info about git repository from which the Checker Framework was compiled - "printGitProperties", - - // Whether to print @InvisibleQualifier marked annotations - // org.checkerframework.framework.type.AnnotatedTypeMirror.toString() - "printAllQualifiers", - - // Whether to print [] around a set of type parameters in order to clearly see where they end - // e.g. - // without this option the E is printed: E extends F extends Object - // with this option: E [ extends F [ extends Object super Void ] super Void - // ] - // when multiple type variables are used this becomes useful very quickly - "printVerboseGenerics", - - // Whether to NOT output a stack trace for each framework error. - // org.checkerframework.framework.source.SourceChecker.logBugInCF - "noPrintErrorStack", - - // If true, issue a NOTE rather than a WARNING when performance is impeded by memory - // constraints. - "noWarnMemoryConstraints", - - // Only output error code, useful for testing framework. - // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) - "nomsgtext", - - // Do not perform a JRE version check. - "noJreVersionCheck", - - /// Format of messages - - // Output detailed message in simple-to-parse format, useful - // for tools parsing Checker Framework output. - // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) - "detailedmsgtext", - - /// Stub and JDK libraries - - // Ignore the standard jdk.astub file; primarily for testing or debugging. - // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() - "ignorejdkastub", - - // Whether to check that the annotated JDK is correctly provided - // org.checkerframework.common.basetype.BaseTypeVisitor.checkForAnnotatedJdk() - "permitMissingJdk", - - // Parse all JDK files at startup rather than as needed. - // org.checkerframework.framework.stub.AnnotationFileElementTypes.AnnotationFileElementTypes - "parseAllJdk", - - // Whether to print debugging messages while processing the stub files - // org.checkerframework.framework.stub.AnnotationFileParser.debugAnnotationFileParser - "stubDebug", - - /// Progress tracing - - // Output file names before checking - // org.checkerframework.framework.source.SourceChecker.typeProcess() - "filenames", - - // Output all subtyping checks - // org.checkerframework.common.basetype.BaseTypeVisitor - "showchecks", - - // Output information about intermediate steps in method type argument inference - // org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference - "showInferenceSteps", - - // Output a stack trace when reporting errors or warnings - // org.checkerframework.common.basetype.SourceChecker.printStackTrace() - "dumpOnErrors", - - /// Visualizing the CFG + // When adding a new standard option: + // 1. Add a brief blurb here about the use case + // and a pointer to one prominent use of the option. + // 2. Update the Checker Framework manual: + // * docs/manual/introduction.tex contains a list of all options, + // which should be in the same order as this source code file. + // * a specific section should contain a detailed discussion. + + /// + /// Unsound checking: ignore some errors + /// + + // A comma-separated list of warnings to suppress + // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings + "suppressWarnings", + + // Set inclusion/exclusion of type uses or definitions + // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar + "skipUses", + "onlyUses", + "skipDefs", + "onlyDefs", + + // Unsoundly assume all methods have no side effects, are deterministic, or both. + "assumeSideEffectFree", + "assumeDeterministic", + "assumePure", + // Unsoundly assume getter methods have no side effects and are deterministic. + "assumePureGetters", + + // Whether to assume that assertions are enabled or disabled + // org.checkerframework.framework.flow.CFCFGBuilder.CFCFGBuilder + "assumeAssertionsAreEnabled", + "assumeAssertionsAreDisabled", + + // Whether to ignore all subtype tests for type arguments that + // were inferred for a raw type. Defaults to true. + // org.checkerframework.framework.type.TypeHierarchy.isSubtypeTypeArguments + "ignoreRawTypeArguments", + + // Do not validate meta-annotation @TargetLocations + "ignoreTargetLocations", + + // Treat checker errors as warnings + // org.checkerframework.framework.source.SourceChecker.report + "warns", + + /// + /// More sound (strict checking): enable errors that are disabled by default + /// + + // The next ones *increase* rather than *decrease* soundness. They will eventually be replaced + // by their complements (except -AconcurrentSemantics) and moved into the above section. + + // TODO: Checking of bodies of @SideEffectFree, @Deterministic, and + // @Pure methods is temporarily disabled unless -AcheckPurityAnnotations is + // supplied on the command line. + // Re-enable it after making the analysis more precise. + // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) + "checkPurityAnnotations", + + // TODO: Temporary option to make array subtyping invariant, + // which will be the new default soon. + "invariantArrays", + + // TODO: Temporary option to make casts stricter, in particular when + // casting to an array or generic type. This will be the new default soon. + "checkCastElementType", + + // Whether to type check the enclosing expression of an inner class instantiation. + "checkEnclosingExpr", + + // Whether to use conservative defaults for bytecode and/or source code. + // This option takes arguments "source" and/or "bytecode". + // The default is "-source,-bytecode" (eventually this will be changed to "-source,bytecode"). + // Note, in source code, conservative defaults are never + // applied to code in the scope of an @AnnotatedFor. + // See the "Compiling partially-annotated libraries" and + // "Default qualifiers for \<.class> files (conservative library defaults)" + // sections in the manual for more details + // org.checkerframework.framework.source.SourceChecker.useConservativeDefault + "useConservativeDefaultsForUncheckedCode", + + // Whether to assume sound concurrent semantics or + // simplified sequential semantics + // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics + "concurrentSemantics", + + // Whether to use a conservative value for type arguments that could not be inferred. + // See Issue 979. + "conservativeUninferredTypeArguments", + + // Issues a "redundant.anno" warning if the annotation explicitly written on the type is + // the same as the default annotation for this type and location. + "warnRedundantAnnotations", + + /// + /// Type-checking modes: enable/disable functionality + /// + + // Lint options + // org.checkerframework.framework.source.SourceChecker.getSupportedLintOptions() and similar + "lint", + + // Whether to suggest methods that could be marked @SideEffectFree, + // @Deterministic, or @Pure + // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) + "suggestPureMethods", + + // Whether to resolve reflective method invocations. + // "-AresolveReflection=debug" causes debugging information + // to be output. + "resolveReflection", + + // Whether to use whole-program inference. Takes an argument to specify the output format: + // "-Ainfer=stubs" or "-Ainfer=jaifs". + "infer", + + // Whether to output a copy of each file for which annotations were inferred, formatted + // as an ajava file. Can only be used with -Ainfer=ajava + "inferOutputOriginal", + + // With each warning, in addition to the concrete error key, + // output the SuppressWarnings strings that can be used to + // suppress that warning. + "showSuppressWarningsStrings", + + // Warn about @SuppressWarnings annotations that do not suppress any warnings. + // org.checkerframework.common.basetype.BaseTypeChecker.warnUnneededSuppressions + // org.checkerframework.framework.source.SourceChecker.warnUnneededSuppressions + // org.checkerframework.framework.source.SourceChecker.shouldSuppressWarnings(javax.lang.model.element.Element, java.lang.String) + // org.checkerframework.framework.source.SourceVisitor.checkForSuppressWarningsAnno + "warnUnneededSuppressions", + + // Exceptions to -AwarnUnneededSuppressions. + "warnUnneededSuppressionsExceptions", + + // Require that warning suppression annotations contain a checker key as a prefix in order for + // the warning to be suppressed. + // org.checkerframework.framework.source.SourceChecker.checkSuppressWarnings(java.lang.String[], + // java.lang.String) + "requirePrefixInWarningSuppressions", + + // Print a checker key as a prefix to each typechecking diagnostic. + // org.checkerframework.framework.source.SourceChecker.suppressWarningsString(java.lang.String) + "showPrefixInWarningMessages", + + // Ignore annotations in bytecode that have invalid annotation locations. + // See https://github.com/typetools/checker-framework/issues/2173 + // org.checkerframework.framework.type.ElementAnnotationApplier.apply + "ignoreInvalidAnnotationLocations", + + /// + /// Compatibility options + /// + + // Additional type and declaration annotation aliases + // -AaliasedTypeAnnos={aliases} or -AaliasedDeclAnnos={aliases} + // where `aliases` is in the format + // `FQN.canonical.Qualifier1:FQN.alias1.Qual1,FQN.alias2.Qual1;FQN.canonical.Qualifier2:FQN.alias1.Qual2` + // org.checkerframework.framework.type.AnnotatedTypeFactory + "aliasedTypeAnnos", + "aliasedDeclAnnos", + + /// + /// Partially-annotated libraries + /// + + // Additional stub files to use + // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() + "stubs", + + // Additional ajava files to use + // org.checkerframework.framework.type.AnnotatedTypeFactory.parserAjavaFiles() + "ajava", + + // Whether to print warnings about types/members in a stub file + // that were not found on the class path + // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFound + "stubWarnIfNotFound", + "stubNoWarnIfNotFound", + + // Whether to ignore missing classes even when warnIfNotFound is set to true and other classes + // from the same package are present (useful if a package spans more than one jar). + // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFoundIgnoresClasses + "stubWarnIfNotFoundIgnoresClasses", + + // Whether to print warnings about stub files that overwrite annotations from bytecode. + "stubWarnIfOverwritesBytecode", + + // Whether to print warnings about stub files that are redundant with the annotations from + // bytecode. + "stubWarnIfRedundantWithBytecode", + + // Whether to issue a NOTE rather than a WARNING for -AstubWarn* command-line options + "stubWarnNote", + + // With this option, annotations in stub files are used EVEN IF THE SOURCE FILE IS + // PRESENT. Only use this option when you intend to store types in stub files rather than + // directly in source code, such as during whole-program inference. The annotations in the + // stub files will be glb'd with those in the source code before local inference begins. + "mergeStubsWithSource", + + // Already listed above, but worth noting again in this section: + // "useConservativeDefaultsForUncheckedCode" + + /// + /// Debugging + /// + + /// Amount of detail in messages + + // Print the version of the Checker Framework + "version", + + // Print info about git repository from which the Checker Framework was compiled + "printGitProperties", + + // Whether to print @InvisibleQualifier marked annotations + // org.checkerframework.framework.type.AnnotatedTypeMirror.toString() + "printAllQualifiers", + + // Whether to print [] around a set of type parameters in order to clearly see where they end + // e.g. + // without this option the E is printed: E extends F extends Object + // with this option: E [ extends F [ extends Object super Void ] super Void + // ] + // when multiple type variables are used this becomes useful very quickly + "printVerboseGenerics", + + // Whether to NOT output a stack trace for each framework error. + // org.checkerframework.framework.source.SourceChecker.logBugInCF + "noPrintErrorStack", + + // If true, issue a NOTE rather than a WARNING when performance is impeded by memory + // constraints. + "noWarnMemoryConstraints", + + // Only output error code, useful for testing framework. + // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) + "nomsgtext", + + // Do not perform a JRE version check. + "noJreVersionCheck", + + /// Format of messages + + // Output detailed message in simple-to-parse format, useful + // for tools parsing Checker Framework output. + // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) + "detailedmsgtext", + + /// Stub and JDK libraries + + // Ignore the standard jdk.astub file; primarily for testing or debugging. + // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() + "ignorejdkastub", + + // Whether to check that the annotated JDK is correctly provided + // org.checkerframework.common.basetype.BaseTypeVisitor.checkForAnnotatedJdk() + "permitMissingJdk", + + // Parse all JDK files at startup rather than as needed. + // org.checkerframework.framework.stub.AnnotationFileElementTypes.AnnotationFileElementTypes + "parseAllJdk", + + // Whether to print debugging messages while processing the stub files + // org.checkerframework.framework.stub.AnnotationFileParser.debugAnnotationFileParser + "stubDebug", + + /// Progress tracing + + // Output file names before checking + // org.checkerframework.framework.source.SourceChecker.typeProcess() + "filenames", + + // Output all subtyping checks + // org.checkerframework.common.basetype.BaseTypeVisitor + "showchecks", + + // Output information about intermediate steps in method type argument inference + // org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference + "showInferenceSteps", + + // Output a stack trace when reporting errors or warnings + // org.checkerframework.common.basetype.SourceChecker.printStackTrace() + "dumpOnErrors", + + /// Visualizing the CFG - // Implemented in the wrapper rather than this file, but worth noting here. - // -AoutputArgsToFile + // Implemented in the wrapper rather than this file, but worth noting here. + // -AoutputArgsToFile - // Mechanism to visualize the control flow graph (CFG). - // The argument is a sequence of values or key-value pairs. - // The first argument has to be the fully-qualified name of the - // org.checkerframework.dataflow.cfg.CFGVisualizer implementation that should be used. The - // remaining values or key-value pairs are passed to CFGVisualizer.init. - // For example: - // -Acfgviz=MyViz,a,b=c,d - // instantiates class MyViz and calls CFGVisualizer.init - // with {"a" -> true, "b" -> "c", "d" -> true}. - "cfgviz", - - // Directory for .dot files generated from the CFG visualization in - // org.checkerframework.dataflow.cfg.DOTCFGVisualizer - // as initialized by - // org.checkerframework.framework.type.GenericAnnotatedTypeFactory.createCFGVisualizer() - // -Aflowdotdir=xyz - // is short-hand for - // -Acfgviz=org.checkerframework.dataflow.cfg.DOTCFGVisualizer,outdir=xyz - "flowdotdir", - - // Enable additional output in the CFG visualization. - // -Averbosecfg - // is short-hand for - // -Acfgviz=MyClass,verbose - "verbosecfg", - - /// Caches - - // Set the cache size for caches in AnnotatedTypeFactory - "atfCacheSize", - - // Sets AnnotatedTypeFactory shouldCache to false - "atfDoNotCache", - - /// Language Server Protocol(LSP) Support + // Mechanism to visualize the control flow graph (CFG). + // The argument is a sequence of values or key-value pairs. + // The first argument has to be the fully-qualified name of the + // org.checkerframework.dataflow.cfg.CFGVisualizer implementation that should be used. The + // remaining values or key-value pairs are passed to CFGVisualizer.init. + // For example: + // -Acfgviz=MyViz,a,b=c,d + // instantiates class MyViz and calls CFGVisualizer.init + // with {"a" -> true, "b" -> "c", "d" -> true}. + "cfgviz", + + // Directory for .dot files generated from the CFG visualization in + // org.checkerframework.dataflow.cfg.DOTCFGVisualizer + // as initialized by + // org.checkerframework.framework.type.GenericAnnotatedTypeFactory.createCFGVisualizer() + // -Aflowdotdir=xyz + // is short-hand for + // -Acfgviz=org.checkerframework.dataflow.cfg.DOTCFGVisualizer,outdir=xyz + "flowdotdir", + + // Enable additional output in the CFG visualization. + // -Averbosecfg + // is short-hand for + // -Acfgviz=MyClass,verbose + "verbosecfg", + + /// Caches + + // Set the cache size for caches in AnnotatedTypeFactory + "atfCacheSize", + + // Sets AnnotatedTypeFactory shouldCache to false + "atfDoNotCache", + + /// Language Server Protocol(LSP) Support - // Output detailed type information for nodes in AST - // org.checkerframework.framework.type.AnnotatedTypeFactory - "lspTypeInfo", + // Output detailed type information for nodes in AST + // org.checkerframework.framework.type.AnnotatedTypeFactory + "lspTypeInfo", - /// Miscellaneous debugging options - - // Whether to output resource statistics at JVM shutdown - // org.checkerframework.framework.source.SourceChecker.shutdownHook() - "resourceStats", - - // Run checks that test ajava files. - // - // Whenever processing a source file, parse it with JavaParser and check that the AST can be - // matched with javac's tree. Crash if not. For testing the class JointJavacJavaParserVisitor. - // - // Also checks that annotations can be inserted. For each Java file, clears all annotations and - // reinserts them, then checks if the original and modified ASTs are equivalent. - "ajavaChecks", + /// Miscellaneous debugging options + + // Whether to output resource statistics at JVM shutdown + // org.checkerframework.framework.source.SourceChecker.shutdownHook() + "resourceStats", + + // Run checks that test ajava files. + // + // Whenever processing a source file, parse it with JavaParser and check that the AST can be + // matched with javac's tree. Crash if not. For testing the class JointJavacJavaParserVisitor. + // + // Also checks that annotations can be inserted. For each Java file, clears all annotations and + // reinserts them, then checks if the original and modified ASTs are equivalent. + "ajavaChecks", }) public abstract class SourceChecker extends AbstractTypeProcessor implements OptionConfiguration { - // TODO A checker should export itself through a separate interface, and maybe have an interface - // for all the methods for which it's safe to override. - - /** The message key that will suppress all warnings (it matches any message key). */ - public static final String SUPPRESS_ALL_MESSAGE_KEY = "all"; - - /** The SuppressWarnings prefix that will suppress warnings for all checkers. */ - public static final String SUPPRESS_ALL_PREFIX = "allcheckers"; - - /** The message key emitted when an unused warning suppression is found. */ - public static final @CompilerMessageKey String UNNEEDED_SUPPRESSION_KEY = - "unneeded.suppression"; - - /** File name of the localized messages. */ - protected static final String MSGS_FILE = "messages.properties"; - - /** True if the Checker Framework version number has already been printed. */ - private static boolean printedVersion = false; - - /** - * Maps error keys to localized/custom error messages. Do not use directly; call {@link - * #fullMessageOf} or {@link #processErrorMessageArg}. Is set in {@link #initChecker}. - */ - protected Properties messagesProperties; - - /** - * Used to report error messages and warnings via the compiler. Is set in {@link - * #typeProcessingStart}. - */ - protected Messager messager; - - /** Element utilities. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() - protected Elements elements; - - /** Tree utilities; used as a helper for the {@link SourceVisitor}. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() - protected Trees trees; - - /** Type utilities. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() - protected Types types; - - /** The source tree that is being scanned. Is set in {@link #setRoot}. */ - protected @MonotonicNonNull @InternedDistinct CompilationUnitTree currentRoot; - - /** The visitor to use. */ - protected SourceVisitor visitor; - - /** - * Exceptions to {@code -AwarnUnneededSuppressions} processing. No warning about unneeded - * suppressions is issued if the SuppressWarnings string matches this pattern. - */ - private @Nullable Pattern warnUnneededSuppressionsExceptions; - - /** - * SuppressWarnings strings supplied via the -AsuppressWarnings option. Do not use directly, - * call {@link #getSuppressWarningsStringsFromOption()}. - */ - private String @MonotonicNonNull [] suppressWarningsStringsFromOption; - - /** True if {@link #suppressWarningsStringsFromOption} has been computed. */ - private boolean computedSuppressWarningsStringsFromOption = false; - - /** - * If true, use the "allcheckers:" warning string prefix. - * - *

Checkers that never issue any error messages should set this to false. That prevents - * {@code -AwarnUnneededSuppressions} from issuing warnings about - * {@code @SuppressWarnings("allcheckers:...")}. - */ - protected boolean useAllcheckersPrefix = true; - - /** - * Regular expression pattern to specify Java classes that are not annotated, so warnings about - * uses of them should be suppressed. - * - *

It contains the pattern specified by the user, through the option {@code - * checkers.skipUses}; otherwise it contains a pattern that can match no class. - */ - private @MonotonicNonNull Pattern skipUsesPattern; - - /** - * Regular expression pattern to specify Java classes that are annotated, so warnings about them - * should be issued but warnings about all other classes should be suppressed. - * - *

It contains the pattern specified by the user, through the option {@code - * checkers.onlyUses}; otherwise it contains a pattern that matches every class. - */ - private @MonotonicNonNull Pattern onlyUsesPattern; - - /** - * Regular expression pattern to specify Java classes whose definition should not be checked. - * - *

It contains the pattern specified by the user, through the option {@code - * checkers.skipDefs}; otherwise it contains a pattern that can match no class. - */ - private @MonotonicNonNull Pattern skipDefsPattern; - - /** - * Regular expression pattern to specify Java classes whose definition should be checked. - * - *

It contains the pattern specified by the user, through the option {@code - * checkers.onlyDefs}; otherwise it contains a pattern that matches every class. - */ - private @MonotonicNonNull Pattern onlyDefsPattern; - - /** The supported lint options. */ - private @MonotonicNonNull Set supportedLints; - - /** The enabled lint options. Is set in {@link #initChecker}. */ - private Set activeLints; - - /** - * The active options for this checker. This is a processed version of {@link - * ProcessingEnvironment#getOptions()}: If the option is of the form "-ACheckerName_key=value" - * and the current checker class, or one of its superclasses, is named "CheckerName", then add - * key → value. If the option is of the form "-ACheckerName_key=value" and the current - * checker class, and none of its superclasses, is named "CheckerName", then do not add key - * → value. If the option is of the form "-Akey=value", then add key → value. - * - *

Both the simple and the canonical name of the checker can be used. Superclasses of the - * current checker are also considered. - */ - private @MonotonicNonNull Map activeOptions; - - /** - * The string that separates the checker name from the option name in a "-A" command-line - * argument. This string may only consist of valid Java identifier part characters, because it - * will be used within the key of an option. - */ - protected static final String OPTION_SEPARATOR = "_"; - - /** - * The checker that called this one, whether that be a BaseTypeChecker (used as a compound - * checker) or an AggregateChecker. Null if this is the checker that calls all others. Note that - * in the case of a compound checker, the compound checker is the parent, not the checker that - * was run prior to this one by the compound checker. - */ - protected @Nullable SourceChecker parentChecker; - - /** List of upstream checker names. Includes the current checker. */ - protected @MonotonicNonNull List<@FullyQualifiedName String> upstreamCheckerNames; - - /** - * TreePathCacher to share between instances. Initialized in getTreePathCacher (which is also - * called from {@link BaseTypeChecker#instantiateSubcheckers(Map)}). - */ - protected TreePathCacher treePathCacher = null; - - /** Default constructor. */ - protected SourceChecker() {} - - /** True if the -Afilenames command-line argument was passed. */ - private boolean printFilenames; - - /** True if the -Awarns command-line argument was passed. */ - private boolean warns; - - /** True if the -AshowSuppressWarningsStrings command-line argument was passed. */ - private boolean showSuppressWarningsStrings; - - /** True if the -ArequirePrefixInWarningSuppressions command-line argument was passed. */ - private boolean requirePrefixInWarningSuppressions; - - /** True if the -AshowPrefixInWarningMessages command-line argument was passed. */ - private boolean showPrefixInWarningMessages; - - /** True if the -AwarnUnneededSuppressions command-line argument was passed. */ - private boolean warnUnneededSuppressions; - - // Also see initChecker(). - @Override - public final synchronized void init(ProcessingEnvironment env) { - ProcessingEnvironment unwrappedEnv = unwrapProcessingEnvironment(env); - super.init(unwrappedEnv); - // The processingEnvironment field will be set by the superclass's init method. - // This is used to trigger AggregateChecker's setProcessingEnvironment. - setProcessingEnvironment(unwrappedEnv); - - // Keep in sync with check in checker-framework/build.gradle . - int jreVersion = SystemUtil.jreVersion; - if (!hasOption("noJreVersionCheck") - && jreVersion != 8 - && jreVersion != 11 - && jreVersion != 17 - && jreVersion != 21) { - message( - Diagnostic.Kind.NOTE, - "The Checker Framework is tested with JDK 8, 11, 17, and 21." - + " You are using version %d.", - jreVersion); - } - - if (!hasOption("warnUnneededSuppressionsExceptions")) { - warnUnneededSuppressionsExceptions = null; - } else { - String warnUnneededSuppressionsExceptionsString = - getOption("warnUnneededSuppressionsExceptions"); - if (warnUnneededSuppressionsExceptionsString == null) { - throw new UserError( - "Must supply an argument to -AwarnUnneededSuppressionsExceptions"); - } - try { - warnUnneededSuppressionsExceptions = - Pattern.compile(warnUnneededSuppressionsExceptionsString); - } catch (PatternSyntaxException e) { - throw new UserError( - "Argument to -AwarnUnneededSuppressionsExceptions is not a regular" - + " expression: " - + e.getMessage()); - } - } - - if (hasOption("printGitProperties")) { - printGitProperties(); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Getters and setters - /// - - /** - * Returns the {@link ProcessingEnvironment} that was supplied to this checker. - * - * @return the {@link ProcessingEnvironment} that was supplied to this checker - */ - public ProcessingEnvironment getProcessingEnvironment() { - return this.processingEnv; - } - - /** - * Set the processing environment of the current checker. - * - * @param env the new processing environment - */ - // This method is protected only to allow the AggregateChecker and BaseTypeChecker to call it. - protected void setProcessingEnvironment(ProcessingEnvironment env) { - this.processingEnv = env; - this.elements = processingEnv.getElementUtils(); - this.trees = Trees.instance(processingEnv); - this.types = processingEnv.getTypeUtils(); - } - - /** Set the parent checker of the current checker. */ - protected void setParentChecker(SourceChecker parentChecker) { - this.parentChecker = parentChecker; - } - - /** - * Returns the immediate parent checker of the current checker. - * - * @return the immediate parent checker of the current checker, or null if there is none - */ - public @Nullable SourceChecker getParentChecker() { - return this.parentChecker; - } - - /** - * Invoked when the current compilation unit root changes. - * - * @param newRoot the new compilation unit root - */ - @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests - protected void setRoot(CompilationUnitTree newRoot) { - this.currentRoot = newRoot; - visitor.setRoot(currentRoot); - - if (parentChecker == null) { - // Only clear the path cache if this is the main checker. - treePathCacher.clear(); - } - } - - /** - * Returns a list containing this checker name and all checkers it is a part of (that is, - * checkers that called it). - * - * @return a list containing this checker name and all checkers it is a part of (that is, - * checkers that called it) - */ - public List<@FullyQualifiedName String> getUpstreamCheckerNames() { - if (upstreamCheckerNames == null) { - upstreamCheckerNames = new ArrayList<>(); - - SourceChecker checker = this; - - while (checker != null) { - String className = checker.getClass().getCanonicalName(); - assert className != null : "@AssumeAssertion(nullness): checker classes have names"; - upstreamCheckerNames.add(className); - checker = checker.parentChecker; - } - } - - return upstreamCheckerNames; - } - - /** - * Returns the OptionConfiguration associated with this. - * - * @return the OptionConfiguration associated with this - */ - public OptionConfiguration getOptionConfiguration() { - return this; - } - - /** - * Returns the element utilities associated with this. - * - * @return the element utilities associated with this - */ - public Elements getElementUtils() { - return elements; - } - - /** - * Returns the type utilities associated with this. - * - * @return the type utilities associated with this - */ - public Types getTypeUtils() { - return types; - } - - /** - * Returns the tree utilities associated with this. - * - * @return the tree utilities associated with this - */ - public Trees getTreeUtils() { - return trees; - } - - /** - * Returns the SourceVisitor associated with this. - * - * @return the SourceVisitor associated with this - */ - public SourceVisitor getVisitor() { - return this.visitor; - } - - /** - * Provides the {@link SourceVisitor} that the checker should use to scan input source trees. - * - * @return a {@link SourceVisitor} to use to scan source trees - */ - protected abstract SourceVisitor createSourceVisitor(); - - /** - * Returns the AnnotationProvider (the type factory) associated with this. - * - * @return the AnnotationProvider (the type factory) associated with this - */ - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for " - + this.getClass().getSimpleName() - + "."); - } - - /** - * Provides a mapping of error keys to custom error messages. - * - *

As a default, this implementation builds a {@link Properties} out of file {@code - * messages.properties}. It accumulates all the properties files in the Java class hierarchy - * from the checker up to {@code SourceChecker}. This permits subclasses to inherit default - * messages while being able to override them. - * - * @return a {@link Properties} that maps error keys to error message text - */ - public Properties getMessagesProperties() { - if (messagesProperties != null) { - return messagesProperties; - } - - messagesProperties = new Properties(); - - ArrayDeque> checkers = new ArrayDeque<>(); - Class currClass = this.getClass(); - while (currClass != AbstractTypeProcessor.class) { - checkers.addFirst(currClass); - currClass = currClass.getSuperclass(); - assert currClass != null : "@AssumeAssertion(nullness): won't encounter Object.class"; - } - - for (Class checker : checkers) { - messagesProperties.putAll(getProperties(checker, MSGS_FILE, true)); - } - return messagesProperties; - } - - /** - * Get the shared TreePathCacher instance. - * - * @return the shared TreePathCacher instance. - */ - public TreePathCacher getTreePathCacher() { - if (treePathCacher == null) { - // In case it wasn't already set in instantiateSubcheckers. - treePathCacher = new TreePathCacher(); - } - return treePathCacher; - } - - /** - * Return the given skip pattern if supplied by the user, or else a pattern that matches - * nothing. - * - * @param patternName "skipUses" or "skipDefs" - * @param options the command-line options - * @return the user-supplied regex for the given pattern, or a regex that matches nothing - */ - private Pattern getSkipPattern(String patternName, Map options) { - // Default is an illegal Java identifier substring - // so that it won't match anything. - // Note that AnnotatedType's toString output format contains characters such as "():{}". - return getPattern(patternName, options, "\\]'\"\\]"); - } - - /** - * Return the given only pattern if supplied by the user, or else a pattern that matches - * everything. - * - * @param patternName "onlyUses" or "onlyDefs" - * @param options the command-line options - * @return the user-supplied regex for the given pattern, or a regex that matches everything - */ - private Pattern getOnlyPattern(String patternName, Map options) { - // default matches everything - return getPattern(patternName, options, "."); - } - - private Pattern getPattern( - String patternName, Map options, String defaultPattern) { - String pattern; - if (options.containsKey(patternName)) { - pattern = options.get(patternName); - if (pattern == null) { - throw new UserError( - "The " + patternName + " property is empty; please fix your command line"); - } - } else { - pattern = System.getProperty("checkers." + patternName); - if (pattern == null) { - pattern = System.getenv(patternName); - } - if (pattern == null) { - pattern = ""; - } - } - - if (pattern.indexOf("/") != -1) { - throw new UserError( - "The " - + patternName - + " property contains \"/\", which will never match a class name: " - + pattern); - } - - if (pattern.isEmpty()) { - pattern = defaultPattern; - } - - try { - return Pattern.compile(pattern); - } catch (PatternSyntaxException e) { - throw new UserError( - "The " + patternName + " property is not a regular expression: " + pattern); - } - } - - private Pattern getSkipUsesPattern(Map options) { - return getSkipPattern("skipUses", options); - } - - private Pattern getOnlyUsesPattern(Map options) { - return getOnlyPattern("onlyUses", options); - } - - private Pattern getSkipDefsPattern(Map options) { - return getSkipPattern("skipDefs", options); - } - - private Pattern getOnlyDefsPattern(Map options) { - return getOnlyPattern("onlyDefs", options); - } - - /////////////////////////////////////////////////////////////////////////// - /// Type-checking - /// - - /** - * {@inheritDoc} - * - *

Type-checkers are not supposed to override this. Instead override initChecker. This allows - * us to handle BugInCF only here and doesn't require all overriding implementations to be aware - * of BugInCF. - * - * @see AbstractProcessor#init(ProcessingEnvironment) - * @see SourceChecker#initChecker() - */ - @Override - public void typeProcessingStart() { - try { - super.typeProcessingStart(); - initChecker(); - if (this.messager == null) { - messager = processingEnv.getMessager(); - messager.printMessage( - Diagnostic.Kind.WARNING, - "You have forgotten to call super.initChecker in your " - + "subclass of SourceChecker, " - + this.getClass() - + "! Please ensure your checker is properly initialized."); - } - if (shouldAddShutdownHook()) { - Runtime.getRuntime() - .addShutdownHook( - new Thread() { - @Override - public void run() { - shutdownHook(); - } - }); - } - if (!printedVersion && hasOption("version")) { - messager.printMessage( - Diagnostic.Kind.NOTE, "Checker Framework " + getCheckerVersion()); - printedVersion = true; - } - } catch (UserError ce) { - logUserError(ce); - } catch (TypeSystemError ce) { - logTypeSystemError(ce); - } catch (BugInCF ce) { - logBugInCF(ce); - } catch (Throwable t) { - logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcessingStart", t, null)); - } - } - - /** - * Initialize the checker. - * - * @see AbstractProcessor#init(ProcessingEnvironment) - */ - public void initChecker() { - // Grab the Trees and Messager instances now; other utilities - // (like Types and Elements) can be retrieved by subclasses. - Trees trees = Trees.instance(processingEnv); - assert trees != null; - this.trees = trees; - - this.messager = processingEnv.getMessager(); - this.messagesProperties = getMessagesProperties(); - - this.visitor = createSourceVisitor(); - - // Validate the lint flags, if they haven't been used already. - if (this.activeLints == null) { - this.activeLints = createActiveLints(getOptions()); - } - - printFilenames = hasOption("filenames"); - warns = hasOption("warns"); - showSuppressWarningsStrings = hasOption("showSuppressWarningsStrings"); - requirePrefixInWarningSuppressions = hasOption("requirePrefixInWarningSuppressions"); - showPrefixInWarningMessages = hasOption("showPrefixInWarningMessages"); - warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); - } - - /** Output the warning about source level at most once. */ - private boolean warnedAboutSourceLevel = false; - - /** - * If true, javac failed to compile the code or a previously-run annotation processor issued an - * error. - */ - protected boolean javacErrored = false; - - /** Output the warning about memory at most once. */ - private boolean warnedAboutGarbageCollection = false; - - /** - * The number of errors at the last exit of the type processor. At entry to the type processor - * we check whether the current error count is higher and then don't process the file, as it - * contains some Java errors. Needs to be protected to allow access from AggregateChecker and - * BaseTypeChecker. - */ - protected int errsOnLastExit = 0; - - /** - * Type-check the code using this checker's visitor. - * - * @see Processor#process(Set, RoundEnvironment) - */ - @Override - public void typeProcess(TypeElement e, TreePath p) { - if (javacErrored) { - return; - } - - // Cannot use BugInCF here because it is outside of the try/catch for BugInCF. - if (e == null) { - messager.printMessage(Diagnostic.Kind.ERROR, "Refusing to process empty TypeElement"); - return; - } - if (p == null) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "Refusing to process empty TreePath in TypeElement: " + e); - return; - } - - if (!warnedAboutGarbageCollection) { - String gcUsageMessage = SystemPlume.gcUsageMessage(.25, 60); - if (gcUsageMessage != null) { - boolean noWarnMemoryConstraints = - (processingEnv != null - && processingEnv.getOptions() != null - && processingEnv - .getOptions() - .containsKey("noWarnMemoryConstraints")); - Diagnostic.Kind kind = - noWarnMemoryConstraints ? Diagnostic.Kind.NOTE : Diagnostic.Kind.WARNING; - messager.printMessage(kind, gcUsageMessage); - warnedAboutGarbageCollection = true; - } - } - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Source source = Source.instance(context); - // Don't use source.allowTypeAnnotations() because that API changed after 9. - // Also the enum constant Source.JDK1_8 was renamed at some point... - if (!warnedAboutSourceLevel && source.compareTo(Source.lookup("8")) < 0) { - messager.printMessage( - Diagnostic.Kind.WARNING, - "-source " + source.name + " does not support type annotations"); - warnedAboutSourceLevel = true; - } - - Log log = Log.instance(context); - if (log.nerrors > this.errsOnLastExit) { - this.errsOnLastExit = log.nerrors; - javacErrored = true; - return; - } - - if (visitor == null) { - // typeProcessingStart invokes initChecker, which should have set the visitor. If the - // field is still null, an exception occurred during initialization, which was already - // logged there. Don't also cause a NPE here. - return; - } - if (p.getCompilationUnit() != currentRoot) { - setRoot(p.getCompilationUnit()); - if (printFilenames) { - // TODO: Have a command-line option to turn the timestamps on/off too, because - // they are nondeterministic across runs. - - // Add timestamp to indicate how long operations are taking. - // Duplicate messages are suppressed, so this might not appear in front of every - // " is type-checking " message (when a file takes less than a second to - // type-check). - message(Diagnostic.Kind.NOTE, Instant.now().toString()); - message( - Diagnostic.Kind.NOTE, - "%s is type-checking %s", - (Object) this.getClass().getSimpleName(), - currentRoot.getSourceFile().getName()); - } - } - - // Visit the attributed tree. - try { - visitor.visit(p); - warnUnneededSuppressions(); - } catch (UserError ce) { - logUserError(ce); - } catch (TypeSystemError ce) { - logTypeSystemError(ce); - } catch (BugInCF ce) { - logBugInCF(ce); - } catch (Throwable t) { - logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcess", t, p)); - } finally { - // Also add possibly deferred diagnostics, which will get published back in - // AbstractTypeProcessor. - this.errsOnLastExit = log.nerrors; - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Reporting type-checking errors; most clients use reportError() or reportWarning() - /// - - /** - * Reports an error. By default, prints it to the screen via the compiler's internal messager. - * - * @param source the source position information; may be an Element, a Tree, or null - * @param messageKey the message key - * @param args arguments for interpolation in the string corresponding to the given message key - */ - public void reportError( - @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { - report(source, Diagnostic.Kind.ERROR, messageKey, args); - } - - /** - * Reports a warning. By default, prints it to the screen via the compiler's internal messager. - * - * @param source the source position information; may be an Element, a Tree, or null - * @param messageKey the message key - * @param args arguments for interpolation in the string corresponding to the given message key - */ - public void reportWarning( - @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { - report(source, Diagnostic.Kind.MANDATORY_WARNING, messageKey, args); - } - - /** - * Reports a diagnostic message. By default, prints it to the screen via the compiler's internal - * messager. - * - *

It is rare to use this method. Most clients should use {@link #reportError} or {@link - * #reportWarning}. - * - * @param source the source position information; may be an Element, a Tree, or null - * @param d the diagnostic message - */ - public void report(@Nullable Object source, DiagMessage d) { - report(source, d.getKind(), d.getMessageKey(), d.getArgs()); - } - - /** - * Reports a diagnostic message. By default, it prints it to the screen via the compiler's - * internal messager; however, it might also store it for later output. - * - * @param source the source position information; may be an Element, a Tree, or null - * @param kind the type of message - * @param messageKey the message key - * @param args arguments for interpolation in the string corresponding to the given message key - */ - // Not a format method. However, messageKey should be either a format string for `args`, or a - // property key that maps to a format string for `args`. - // @FormatMethod - @SuppressWarnings("formatter:format.string.invalid") // arg is a format string or a property key - private void report( - @Nullable Object source, - Diagnostic.Kind kind, - @CompilerMessageKey String messageKey, - Object... args) { - assert messagesProperties != null : "null messagesProperties"; - - if (shouldSuppressWarnings(source, messageKey)) { - return; - } - Object preciseSource = getSourceWithPrecisePosition(source); - - if (args != null) { - for (int i = 0; i < args.length; ++i) { - args[i] = processErrorMessageArg(args[i]); - } - } - - if (kind == Diagnostic.Kind.NOTE) { - System.err.println("(NOTE) " + String.format(messageKey, args)); - return; - } - - String defaultFormat = "(" + messageKey + ")"; - String fmtString; - if (this.processingEnv.getOptions() != null /*nnbug*/ - && this.processingEnv.getOptions().containsKey("nomsgtext")) { - fmtString = defaultFormat; - } else if (this.processingEnv.getOptions() != null /*nnbug*/ - && this.processingEnv.getOptions().containsKey("detailedmsgtext")) { - // The -Adetailedmsgtext command-line option was given, so output - // a stylized error message for easy parsing by a tool. - fmtString = - detailedMsgTextPrefix(preciseSource, defaultFormat, args) - + fullMessageOf(messageKey, defaultFormat); - } else { - fmtString = - "[" - + suppressWarningsString(messageKey) - + "] " - + fullMessageOf(messageKey, defaultFormat); - } - String messageText; - try { - messageText = String.format(fmtString, args); - } catch (Exception e) { - throw new BugInCF( - "Invalid format string: \"" + fmtString + "\" args: " + Arrays.toString(args), - e); - } - - if (kind == Diagnostic.Kind.ERROR && warns) { - kind = Diagnostic.Kind.MANDATORY_WARNING; - } - - if (preciseSource instanceof Element) { - messager.printMessage(kind, messageText, (Element) preciseSource); - } else if (preciseSource instanceof Tree) { - printOrStoreMessage(kind, messageText, (Tree) preciseSource, currentRoot); - } else { - throw new BugInCF("invalid position source, class=" + preciseSource.getClass()); - } - } - - /** - * This method improves the source position information for message reporting. If the given - * {@code source} does not have a precise location, it will try to return an object with a - * precise location that encloses the {@code source}; otherwise, it simply returns the {@code - * source}. - * - *

Currently, missing position only happens to artificial trees generated by the compiler. - * For example, a lambda expression "s -> ..." in the source code can be de-sugared into - * "(Type s) -> ..." in the corresponding {@link Tree}, where the "Type" {@link Tree} is an - * artificial tree. - * - * @param source the original source position information; may be an Element, a Tree, or null - * @return a source that may have more precise position information - */ - private @PolyNull Object getSourceWithPrecisePosition(@PolyNull Object source) { - if (!(source instanceof JCTree)) { - return source; - } - - JCTree tree = (JCTree) source; - if (tree.getPreferredPosition() != Position.NOPOS) { - return tree; - } - - TreePath path = getTreePathCacher().getPath(currentRoot, tree); - if (path == null) { - return tree; - } - - // find the first parent in AST path that has a precise preferred position - while (((JCTree) path.getLeaf()).getPreferredPosition() == Position.NOPOS - && path.getParentPath() != null) { - path = path.getParentPath(); - } - return path.getLeaf(); - } - - /** - * Print a non-localized message using the javac messager. This is preferable to using - * System.out or System.err, but should only be used for exceptional cases that don't happen in - * correct usage. Localized messages should be raised using {@link #reportError}, {@link - * #reportWarning}, etc. - * - * @param kind the kind of message to print - * @param msg the message text - * @param args optional arguments to substitute in the message - * @see SourceChecker#report(Object, DiagMessage) - */ - @FormatMethod - public void message(Diagnostic.Kind kind, String msg, Object... args) { - message(kind, String.format(msg, args)); - } - - /** - * Print a non-localized message using the javac messager. This is preferable to using - * System.out or System.err, but should only be used for exceptional cases that don't happen in - * correct usage. Localized messages should be raised using {@link #reportError}, {@link - * #reportWarning}, etc. - * - * @param kind the kind of message to print - * @param msg the message text - * @see SourceChecker#report(Object, DiagMessage) - */ - public void message(javax.tools.Diagnostic.Kind kind, String msg) { - if (messager == null) { - // If this method is called before initChecker() sets the field - messager = processingEnv.getMessager(); - } - messager.printMessage(kind, msg); - } - - /** - * Print the given message. - * - * @param msg the message to print x - */ - private void printMessage(String msg) { - if (messager == null) { - // If this method is called before initChecker() sets the field - messager = processingEnv.getMessager(); - } - messager.printMessage(Diagnostic.Kind.ERROR, msg); - } - - /** - * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. - * - *

This method exists so that the BaseTypeChecker can override it. For compound checkers, it - * stores all messages and sorts them by location before outputting them. - * - * @param kind the kind of message to print - * @param message the message text - * @param source the source code position of the diagnostic message - * @param root the compilation unit - */ - protected void printOrStoreMessage( - javax.tools.Diagnostic.Kind kind, - String message, - Tree source, - CompilationUnitTree root) { - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - printOrStoreMessage(kind, message, source, root, trace); - } - - /** - * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. - * - *

This method exists so that the BaseTypeChecker can override it. For compound checkers, it - * stores all messages and sorts them by location before outputting them. - * - * @param kind the kind of message to print - * @param message the message text - * @param source the source code position of the diagnostic message - * @param root the compilation unit - * @param trace the stack trace where the checker encountered an error. It is printed when the - * dumpOnErrors option is enabled. - */ - protected void printOrStoreMessage( - javax.tools.Diagnostic.Kind kind, - String message, - Tree source, - CompilationUnitTree root, - StackTraceElement[] trace) { - Trees.instance(processingEnv).printMessage(kind, message, source, root); - printStackTrace(trace); - } - - /** - * Output the given stack trace if the "dumpOnErrors" option is enabled. - * - * @param trace stack trace when the checker encountered a warning/error - */ - private void printStackTrace(StackTraceElement[] trace) { - if (hasOption("dumpOnErrors")) { - StringBuilder msg = new StringBuilder(); - for (StackTraceElement elem : trace) { - msg.append("\tat " + elem + "\n"); - } - message(Diagnostic.Kind.NOTE, msg.toString()); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Diagnostic message formatting - /// - - /** - * Returns the localized long message corresponding to this key. If not found, tries suffixes of - * this key, stripping off dot-separated prefixes. If still not found, returns {@code - * defaultValue}. - * - * @param messageKey a message key - * @param defaultValue a default value to use if {@code messageKey} is not a message key - * @return the localized long message corresponding to this key or a suffix, or {@code - * defaultValue} - */ - protected String fullMessageOf(String messageKey, String defaultValue) { - String key = messageKey; - - do { - String messageForKey = messagesProperties.getProperty(key); - if (messageForKey != null) { - return messageForKey; - } - - int dot = key.indexOf('.'); - if (dot < 0) { - return defaultValue; - } - key = key.substring(dot + 1); - } while (true); - } - - /** - * Process an argument to an error message before it is passed to String.format. - * - *

This implementation expands the argument if it is exactly a message key. - * - *

By contrast, {@link #fullMessageOf} processes the message key itself but not the - * arguments, and tries suffixes. - * - * @param arg the argument - * @return the result after processing - */ - protected Object processErrorMessageArg(Object arg) { - // Check to see if the argument itself is a property to be expanded - if (arg instanceof String) { - return messagesProperties.getProperty((String) arg, (String) arg); - } else { - return arg; - } - } - - /** Separates parts of a "detailed message", to permit easier parsing. */ - public static final String DETAILS_SEPARATOR = " $$ "; - - /** - * Returns all but the message key part of the message format output by {@code - * -Adetailedmsgtext}. - * - * @param source the object from which to obtain source position information; may be an Element, - * a Tree, or null - * @param defaultFormat the message key, in parentheses - * @param args arguments for interpolation in the string corresponding to the given message key - * @return the first part of the message format output by {@code -Adetailedmsgtext} - */ - private String detailedMsgTextPrefix( - @Nullable Object source, String defaultFormat, Object[] args) { - StringJoiner sj = new StringJoiner(DETAILS_SEPARATOR); - - // The parts, separated by " $$ " (DETAILS_SEPARATOR), are: - - // (1) error key - sj.add(defaultFormat); - - // (2) number of additional tokens, and those tokens; this depends on the error message, and - // an example is the found and expected types - if (args != null) { - sj.add(Integer.toString(args.length)); - for (Object arg : args) { - sj.add(Objects.toString(arg)); - } - } else { - // Output 0 for null arguments. - sj.add(Integer.toString(0)); - } - - // (3) The error position, as starting and ending characters in the source file. - sj.add(detailedMsgTextPositionString(sourceToTree(source), currentRoot)); - - // (4) The human-readable error message will be added by the caller. - sj.add(""); // Add DETAILS_SEPARATOR at the end. - return sj.toString(); - } - - /** - * Returns the most specific warning suppression string for the warning/error being printed. - * - *

    - *
  • If {@code -AshowSuppressWarningsStrings} was supplied on the command line, this is - * {@code [checkername1, checkername2]:msg}, where each {@code checkername} is a checker - * name or "allcheckers". - *
  • If {@code -ArequirePrefixInWarningSuppressions} or {@code - * -AshowPrefixInWarningMessages} was supplied on the command line, this is {@code - * checkername:msg} (where {@code checkername} may be "allcheckers"). - *
  • Otherwise, it is just {@code msg}. - *
- * - * @param messageKey the simple, checker-specific error message key - * @return the most specific SuppressWarnings string for the warning/error being printed - */ - protected String suppressWarningsString(String messageKey) { - Collection prefixes = this.getSuppressWarningsPrefixes(); - prefixes.remove(SUPPRESS_ALL_PREFIX); - if (showSuppressWarningsStrings) { - List list = new ArrayList<>(prefixes); - // Make sure "allcheckers" is at the end of the list. - if (useAllcheckersPrefix) { - list.add(SUPPRESS_ALL_PREFIX); - } - return list + ":" + messageKey; - } else if (requirePrefixInWarningSuppressions || showPrefixInWarningMessages) { - // If the warning key must be prefixed with a prefix (a checker name), then add that to - // the SuppressWarnings string that is printed. - return getWarningMessagePrefix() + ":" + messageKey; - } else { - return messageKey; - } - } - - /** - * Convert a Tree, Element, or null, into a Tree or null. - * - * @param source the object from which to obtain source position information; may be an Element, - * a Tree, or null - * @return the tree associated with the given source object, or null if none - */ - private @Nullable Tree sourceToTree(@Nullable Object source) { - if (source instanceof Element) { - return trees.getTree((Element) source); - } else if (source instanceof Tree) { - return (Tree) source; - } else if (source == null) { - return null; - } else { - throw new BugInCF("Unexpected source %s [%s]", source, source.getClass()); - } - } - - /** - * For the given tree, compute the source positions for that tree. Return a "tuple"-like string - * (e.g. "( 1, 200 )" ) that contains the start and end position of the tree in the current - * compilation unit. Used only by the -Adetailedmsgtext output format. - * - * @param tree tree to locate within the current compilation unit - * @param currentRoot the current compilation unit - * @return a tuple string representing the range of characters that tree occupies in the source - * file, or the empty string if {@code tree} is null - */ - private String detailedMsgTextPositionString( - @Nullable Tree tree, CompilationUnitTree currentRoot) { - if (tree == null) { - return ""; - } - - SourcePositions sourcePositions = trees.getSourcePositions(); - long start = sourcePositions.getStartPosition(currentRoot, tree); - long end = sourcePositions.getEndPosition(currentRoot, tree); - - return "( " + start + ", " + end + " )"; - } - - /////////////////////////////////////////////////////////////////////////// - /// Lint options ("-Alint:xxxx" and "-Alint:-xxxx") - /// - - /** - * Determine which lint options are artive. - * - * @param options the command-line options - * @return the active lint options - */ - private Set createActiveLints(Map options) { - if (!options.containsKey("lint")) { - return Collections.emptySet(); - } - - String lintString = options.get("lint"); - if (lintString == null) { - return Collections.singleton("all"); - } - - List lintStrings = SystemUtil.COMMA_SPLITTER.splitToList(lintString); - Set activeLint = ArraySet.newArraySetOrHashSet(lintStrings.size()); - for (String s : lintStrings) { - if (!this.getSupportedLintOptions().contains(s) - && !(s.charAt(0) == '-' - && this.getSupportedLintOptions().contains(s.substring(1))) - && !s.equals("all") - && !s.equals("none")) { - this.messager.printMessage( - Diagnostic.Kind.WARNING, - "Unsupported lint option: " - + s - + "; All options: " - + this.getSupportedLintOptions()); - } - - activeLint.add(s); - if (s.equals("none")) { - activeLint.add("-all"); - } - } - - return Collections.unmodifiableSet(activeLint); - } - - /** - * Determines the value of the lint option with the given name. Just as javac uses - * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint - * options are enabled with "-Alint:xxx" and disabled with "-Alint:-xxx". - * - * @throws IllegalArgumentException if the option name is not recognized via the {@link - * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} - * method - * @param name the name of the lint option to check for - * @return true if the lint option was given, false if it was not given or was given prepended - * with a "-" - * @see SourceChecker#getLintOption(String, boolean) - */ - public final boolean getLintOption(String name) { - return getLintOption(name, false); - } - - /** - * Determines the value of the lint option with the given name. Just as javac - * uses "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint - * options are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". - * - * @throws IllegalArgumentException if the option name is not recognized via the {@link - * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} - * method - * @param name the name of the lint option to check for - * @param def the default option value, returned if the option was not given - * @return true if the lint option was given, false if it was given prepended with a "-", or - * {@code def} if it was not given at all - * @see SourceChecker#getLintOption(String) - * @see SourceChecker#getOption(String) - */ - public final boolean getLintOption(String name, boolean def) { - if (!this.getSupportedLintOptions().contains(name)) { - throw new UserError("Illegal lint option: " + name); - } - - // This is only needed if initChecker() has not yet been called. - if (activeLints == null) { - activeLints = createActiveLints(getOptions()); - } - - if (activeLints.isEmpty()) { - return def; - } - + // TODO A checker should export itself through a separate interface, and maybe have an interface + // for all the methods for which it's safe to override. + + /** The message key that will suppress all warnings (it matches any message key). */ + public static final String SUPPRESS_ALL_MESSAGE_KEY = "all"; + + /** The SuppressWarnings prefix that will suppress warnings for all checkers. */ + public static final String SUPPRESS_ALL_PREFIX = "allcheckers"; + + /** The message key emitted when an unused warning suppression is found. */ + public static final @CompilerMessageKey String UNNEEDED_SUPPRESSION_KEY = "unneeded.suppression"; + + /** File name of the localized messages. */ + protected static final String MSGS_FILE = "messages.properties"; + + /** True if the Checker Framework version number has already been printed. */ + private static boolean printedVersion = false; + + /** + * Maps error keys to localized/custom error messages. Do not use directly; call {@link + * #fullMessageOf} or {@link #processErrorMessageArg}. Is set in {@link #initChecker}. + */ + protected Properties messagesProperties; + + /** + * Used to report error messages and warnings via the compiler. Is set in {@link + * #typeProcessingStart}. + */ + protected Messager messager; + + /** Element utilities. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() + protected Elements elements; + + /** Tree utilities; used as a helper for the {@link SourceVisitor}. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() + protected Trees trees; + + /** Type utilities. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() + protected Types types; + + /** The source tree that is being scanned. Is set in {@link #setRoot}. */ + protected @MonotonicNonNull @InternedDistinct CompilationUnitTree currentRoot; + + /** The visitor to use. */ + protected SourceVisitor visitor; + + /** + * Exceptions to {@code -AwarnUnneededSuppressions} processing. No warning about unneeded + * suppressions is issued if the SuppressWarnings string matches this pattern. + */ + private @Nullable Pattern warnUnneededSuppressionsExceptions; + + /** + * SuppressWarnings strings supplied via the -AsuppressWarnings option. Do not use directly, call + * {@link #getSuppressWarningsStringsFromOption()}. + */ + private String @MonotonicNonNull [] suppressWarningsStringsFromOption; + + /** True if {@link #suppressWarningsStringsFromOption} has been computed. */ + private boolean computedSuppressWarningsStringsFromOption = false; + + /** + * If true, use the "allcheckers:" warning string prefix. + * + *

Checkers that never issue any error messages should set this to false. That prevents {@code + * -AwarnUnneededSuppressions} from issuing warnings about + * {@code @SuppressWarnings("allcheckers:...")}. + */ + protected boolean useAllcheckersPrefix = true; + + /** + * Regular expression pattern to specify Java classes that are not annotated, so warnings about + * uses of them should be suppressed. + * + *

It contains the pattern specified by the user, through the option {@code checkers.skipUses}; + * otherwise it contains a pattern that can match no class. + */ + private @MonotonicNonNull Pattern skipUsesPattern; + + /** + * Regular expression pattern to specify Java classes that are annotated, so warnings about them + * should be issued but warnings about all other classes should be suppressed. + * + *

It contains the pattern specified by the user, through the option {@code checkers.onlyUses}; + * otherwise it contains a pattern that matches every class. + */ + private @MonotonicNonNull Pattern onlyUsesPattern; + + /** + * Regular expression pattern to specify Java classes whose definition should not be checked. + * + *

It contains the pattern specified by the user, through the option {@code checkers.skipDefs}; + * otherwise it contains a pattern that can match no class. + */ + private @MonotonicNonNull Pattern skipDefsPattern; + + /** + * Regular expression pattern to specify Java classes whose definition should be checked. + * + *

It contains the pattern specified by the user, through the option {@code checkers.onlyDefs}; + * otherwise it contains a pattern that matches every class. + */ + private @MonotonicNonNull Pattern onlyDefsPattern; + + /** The supported lint options. */ + private @MonotonicNonNull Set supportedLints; + + /** The enabled lint options. Is set in {@link #initChecker}. */ + private Set activeLints; + + /** + * The active options for this checker. This is a processed version of {@link + * ProcessingEnvironment#getOptions()}: If the option is of the form "-ACheckerName_key=value" and + * the current checker class, or one of its superclasses, is named "CheckerName", then add key + * → value. If the option is of the form "-ACheckerName_key=value" and the current checker + * class, and none of its superclasses, is named "CheckerName", then do not add key → value. + * If the option is of the form "-Akey=value", then add key → value. + * + *

Both the simple and the canonical name of the checker can be used. Superclasses of the + * current checker are also considered. + */ + private @MonotonicNonNull Map activeOptions; + + /** + * The string that separates the checker name from the option name in a "-A" command-line + * argument. This string may only consist of valid Java identifier part characters, because it + * will be used within the key of an option. + */ + protected static final String OPTION_SEPARATOR = "_"; + + /** + * The checker that called this one, whether that be a BaseTypeChecker (used as a compound + * checker) or an AggregateChecker. Null if this is the checker that calls all others. Note that + * in the case of a compound checker, the compound checker is the parent, not the checker that was + * run prior to this one by the compound checker. + */ + protected @Nullable SourceChecker parentChecker; + + /** List of upstream checker names. Includes the current checker. */ + protected @MonotonicNonNull List<@FullyQualifiedName String> upstreamCheckerNames; + + /** + * TreePathCacher to share between instances. Initialized in getTreePathCacher (which is also + * called from {@link BaseTypeChecker#instantiateSubcheckers(Map)}). + */ + protected TreePathCacher treePathCacher = null; + + /** Default constructor. */ + protected SourceChecker() {} + + /** True if the -Afilenames command-line argument was passed. */ + private boolean printFilenames; + + /** True if the -Awarns command-line argument was passed. */ + private boolean warns; + + /** True if the -AshowSuppressWarningsStrings command-line argument was passed. */ + private boolean showSuppressWarningsStrings; + + /** True if the -ArequirePrefixInWarningSuppressions command-line argument was passed. */ + private boolean requirePrefixInWarningSuppressions; + + /** True if the -AshowPrefixInWarningMessages command-line argument was passed. */ + private boolean showPrefixInWarningMessages; + + /** True if the -AwarnUnneededSuppressions command-line argument was passed. */ + private boolean warnUnneededSuppressions; + + // Also see initChecker(). + @Override + public final synchronized void init(ProcessingEnvironment env) { + ProcessingEnvironment unwrappedEnv = unwrapProcessingEnvironment(env); + super.init(unwrappedEnv); + // The processingEnvironment field will be set by the superclass's init method. + // This is used to trigger AggregateChecker's setProcessingEnvironment. + setProcessingEnvironment(unwrappedEnv); + + // Keep in sync with check in checker-framework/build.gradle . + int jreVersion = SystemUtil.jreVersion; + if (!hasOption("noJreVersionCheck") + && jreVersion != 8 + && jreVersion != 11 + && jreVersion != 17 + && jreVersion != 21) { + message( + Diagnostic.Kind.NOTE, + "The Checker Framework is tested with JDK 8, 11, 17, and 21." + + " You are using version %d.", + jreVersion); + } + + if (!hasOption("warnUnneededSuppressionsExceptions")) { + warnUnneededSuppressionsExceptions = null; + } else { + String warnUnneededSuppressionsExceptionsString = + getOption("warnUnneededSuppressionsExceptions"); + if (warnUnneededSuppressionsExceptionsString == null) { + throw new UserError("Must supply an argument to -AwarnUnneededSuppressionsExceptions"); + } + try { + warnUnneededSuppressionsExceptions = + Pattern.compile(warnUnneededSuppressionsExceptionsString); + } catch (PatternSyntaxException e) { + throw new UserError( + "Argument to -AwarnUnneededSuppressionsExceptions is not a regular" + + " expression: " + + e.getMessage()); + } + } + + if (hasOption("printGitProperties")) { + printGitProperties(); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Getters and setters + /// + + /** + * Returns the {@link ProcessingEnvironment} that was supplied to this checker. + * + * @return the {@link ProcessingEnvironment} that was supplied to this checker + */ + public ProcessingEnvironment getProcessingEnvironment() { + return this.processingEnv; + } + + /** + * Set the processing environment of the current checker. + * + * @param env the new processing environment + */ + // This method is protected only to allow the AggregateChecker and BaseTypeChecker to call it. + protected void setProcessingEnvironment(ProcessingEnvironment env) { + this.processingEnv = env; + this.elements = processingEnv.getElementUtils(); + this.trees = Trees.instance(processingEnv); + this.types = processingEnv.getTypeUtils(); + } + + /** Set the parent checker of the current checker. */ + protected void setParentChecker(SourceChecker parentChecker) { + this.parentChecker = parentChecker; + } + + /** + * Returns the immediate parent checker of the current checker. + * + * @return the immediate parent checker of the current checker, or null if there is none + */ + public @Nullable SourceChecker getParentChecker() { + return this.parentChecker; + } + + /** + * Invoked when the current compilation unit root changes. + * + * @param newRoot the new compilation unit root + */ + @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests + protected void setRoot(CompilationUnitTree newRoot) { + this.currentRoot = newRoot; + visitor.setRoot(currentRoot); + + if (parentChecker == null) { + // Only clear the path cache if this is the main checker. + treePathCacher.clear(); + } + } + + /** + * Returns a list containing this checker name and all checkers it is a part of (that is, checkers + * that called it). + * + * @return a list containing this checker name and all checkers it is a part of (that is, checkers + * that called it) + */ + public List<@FullyQualifiedName String> getUpstreamCheckerNames() { + if (upstreamCheckerNames == null) { + upstreamCheckerNames = new ArrayList<>(); + + SourceChecker checker = this; + + while (checker != null) { + String className = checker.getClass().getCanonicalName(); + assert className != null : "@AssumeAssertion(nullness): checker classes have names"; + upstreamCheckerNames.add(className); + checker = checker.parentChecker; + } + } + + return upstreamCheckerNames; + } + + /** + * Returns the OptionConfiguration associated with this. + * + * @return the OptionConfiguration associated with this + */ + public OptionConfiguration getOptionConfiguration() { + return this; + } + + /** + * Returns the element utilities associated with this. + * + * @return the element utilities associated with this + */ + public Elements getElementUtils() { + return elements; + } + + /** + * Returns the type utilities associated with this. + * + * @return the type utilities associated with this + */ + public Types getTypeUtils() { + return types; + } + + /** + * Returns the tree utilities associated with this. + * + * @return the tree utilities associated with this + */ + public Trees getTreeUtils() { + return trees; + } + + /** + * Returns the SourceVisitor associated with this. + * + * @return the SourceVisitor associated with this + */ + public SourceVisitor getVisitor() { + return this.visitor; + } + + /** + * Provides the {@link SourceVisitor} that the checker should use to scan input source trees. + * + * @return a {@link SourceVisitor} to use to scan source trees + */ + protected abstract SourceVisitor createSourceVisitor(); + + /** + * Returns the AnnotationProvider (the type factory) associated with this. + * + * @return the AnnotationProvider (the type factory) associated with this + */ + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for " + this.getClass().getSimpleName() + "."); + } + + /** + * Provides a mapping of error keys to custom error messages. + * + *

As a default, this implementation builds a {@link Properties} out of file {@code + * messages.properties}. It accumulates all the properties files in the Java class hierarchy from + * the checker up to {@code SourceChecker}. This permits subclasses to inherit default messages + * while being able to override them. + * + * @return a {@link Properties} that maps error keys to error message text + */ + public Properties getMessagesProperties() { + if (messagesProperties != null) { + return messagesProperties; + } + + messagesProperties = new Properties(); + + ArrayDeque> checkers = new ArrayDeque<>(); + Class currClass = this.getClass(); + while (currClass != AbstractTypeProcessor.class) { + checkers.addFirst(currClass); + currClass = currClass.getSuperclass(); + assert currClass != null : "@AssumeAssertion(nullness): won't encounter Object.class"; + } + + for (Class checker : checkers) { + messagesProperties.putAll(getProperties(checker, MSGS_FILE, true)); + } + return messagesProperties; + } + + /** + * Get the shared TreePathCacher instance. + * + * @return the shared TreePathCacher instance. + */ + public TreePathCacher getTreePathCacher() { + if (treePathCacher == null) { + // In case it wasn't already set in instantiateSubcheckers. + treePathCacher = new TreePathCacher(); + } + return treePathCacher; + } + + /** + * Return the given skip pattern if supplied by the user, or else a pattern that matches nothing. + * + * @param patternName "skipUses" or "skipDefs" + * @param options the command-line options + * @return the user-supplied regex for the given pattern, or a regex that matches nothing + */ + private Pattern getSkipPattern(String patternName, Map options) { + // Default is an illegal Java identifier substring + // so that it won't match anything. + // Note that AnnotatedType's toString output format contains characters such as "():{}". + return getPattern(patternName, options, "\\]'\"\\]"); + } + + /** + * Return the given only pattern if supplied by the user, or else a pattern that matches + * everything. + * + * @param patternName "onlyUses" or "onlyDefs" + * @param options the command-line options + * @return the user-supplied regex for the given pattern, or a regex that matches everything + */ + private Pattern getOnlyPattern(String patternName, Map options) { + // default matches everything + return getPattern(patternName, options, "."); + } + + private Pattern getPattern( + String patternName, Map options, String defaultPattern) { + String pattern; + if (options.containsKey(patternName)) { + pattern = options.get(patternName); + if (pattern == null) { + throw new UserError( + "The " + patternName + " property is empty; please fix your command line"); + } + } else { + pattern = System.getProperty("checkers." + patternName); + if (pattern == null) { + pattern = System.getenv(patternName); + } + if (pattern == null) { + pattern = ""; + } + } + + if (pattern.indexOf("/") != -1) { + throw new UserError( + "The " + + patternName + + " property contains \"/\", which will never match a class name: " + + pattern); + } + + if (pattern.isEmpty()) { + pattern = defaultPattern; + } + + try { + return Pattern.compile(pattern); + } catch (PatternSyntaxException e) { + throw new UserError( + "The " + patternName + " property is not a regular expression: " + pattern); + } + } + + private Pattern getSkipUsesPattern(Map options) { + return getSkipPattern("skipUses", options); + } + + private Pattern getOnlyUsesPattern(Map options) { + return getOnlyPattern("onlyUses", options); + } + + private Pattern getSkipDefsPattern(Map options) { + return getSkipPattern("skipDefs", options); + } + + private Pattern getOnlyDefsPattern(Map options) { + return getOnlyPattern("onlyDefs", options); + } + + /////////////////////////////////////////////////////////////////////////// + /// Type-checking + /// + + /** + * {@inheritDoc} + * + *

Type-checkers are not supposed to override this. Instead override initChecker. This allows + * us to handle BugInCF only here and doesn't require all overriding implementations to be aware + * of BugInCF. + * + * @see AbstractProcessor#init(ProcessingEnvironment) + * @see SourceChecker#initChecker() + */ + @Override + public void typeProcessingStart() { + try { + super.typeProcessingStart(); + initChecker(); + if (this.messager == null) { + messager = processingEnv.getMessager(); + messager.printMessage( + Diagnostic.Kind.WARNING, + "You have forgotten to call super.initChecker in your " + + "subclass of SourceChecker, " + + this.getClass() + + "! Please ensure your checker is properly initialized."); + } + if (shouldAddShutdownHook()) { + Runtime.getRuntime() + .addShutdownHook( + new Thread() { + @Override + public void run() { + shutdownHook(); + } + }); + } + if (!printedVersion && hasOption("version")) { + messager.printMessage(Diagnostic.Kind.NOTE, "Checker Framework " + getCheckerVersion()); + printedVersion = true; + } + } catch (UserError ce) { + logUserError(ce); + } catch (TypeSystemError ce) { + logTypeSystemError(ce); + } catch (BugInCF ce) { + logBugInCF(ce); + } catch (Throwable t) { + logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcessingStart", t, null)); + } + } + + /** + * Initialize the checker. + * + * @see AbstractProcessor#init(ProcessingEnvironment) + */ + public void initChecker() { + // Grab the Trees and Messager instances now; other utilities + // (like Types and Elements) can be retrieved by subclasses. + Trees trees = Trees.instance(processingEnv); + assert trees != null; + this.trees = trees; + + this.messager = processingEnv.getMessager(); + this.messagesProperties = getMessagesProperties(); + + this.visitor = createSourceVisitor(); + + // Validate the lint flags, if they haven't been used already. + if (this.activeLints == null) { + this.activeLints = createActiveLints(getOptions()); + } + + printFilenames = hasOption("filenames"); + warns = hasOption("warns"); + showSuppressWarningsStrings = hasOption("showSuppressWarningsStrings"); + requirePrefixInWarningSuppressions = hasOption("requirePrefixInWarningSuppressions"); + showPrefixInWarningMessages = hasOption("showPrefixInWarningMessages"); + warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); + } + + /** Output the warning about source level at most once. */ + private boolean warnedAboutSourceLevel = false; + + /** + * If true, javac failed to compile the code or a previously-run annotation processor issued an + * error. + */ + protected boolean javacErrored = false; + + /** Output the warning about memory at most once. */ + private boolean warnedAboutGarbageCollection = false; + + /** + * The number of errors at the last exit of the type processor. At entry to the type processor we + * check whether the current error count is higher and then don't process the file, as it contains + * some Java errors. Needs to be protected to allow access from AggregateChecker and + * BaseTypeChecker. + */ + protected int errsOnLastExit = 0; + + /** + * Type-check the code using this checker's visitor. + * + * @see Processor#process(Set, RoundEnvironment) + */ + @Override + public void typeProcess(TypeElement e, TreePath p) { + if (javacErrored) { + return; + } + + // Cannot use BugInCF here because it is outside of the try/catch for BugInCF. + if (e == null) { + messager.printMessage(Diagnostic.Kind.ERROR, "Refusing to process empty TypeElement"); + return; + } + if (p == null) { + messager.printMessage( + Diagnostic.Kind.ERROR, "Refusing to process empty TreePath in TypeElement: " + e); + return; + } + + if (!warnedAboutGarbageCollection) { + String gcUsageMessage = SystemPlume.gcUsageMessage(.25, 60); + if (gcUsageMessage != null) { + boolean noWarnMemoryConstraints = + (processingEnv != null + && processingEnv.getOptions() != null + && processingEnv.getOptions().containsKey("noWarnMemoryConstraints")); + Diagnostic.Kind kind = + noWarnMemoryConstraints ? Diagnostic.Kind.NOTE : Diagnostic.Kind.WARNING; + messager.printMessage(kind, gcUsageMessage); + warnedAboutGarbageCollection = true; + } + } + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Source source = Source.instance(context); + // Don't use source.allowTypeAnnotations() because that API changed after 9. + // Also the enum constant Source.JDK1_8 was renamed at some point... + if (!warnedAboutSourceLevel && source.compareTo(Source.lookup("8")) < 0) { + messager.printMessage( + Diagnostic.Kind.WARNING, "-source " + source.name + " does not support type annotations"); + warnedAboutSourceLevel = true; + } + + Log log = Log.instance(context); + if (log.nerrors > this.errsOnLastExit) { + this.errsOnLastExit = log.nerrors; + javacErrored = true; + return; + } + + if (visitor == null) { + // typeProcessingStart invokes initChecker, which should have set the visitor. If the + // field is still null, an exception occurred during initialization, which was already + // logged there. Don't also cause a NPE here. + return; + } + if (p.getCompilationUnit() != currentRoot) { + setRoot(p.getCompilationUnit()); + if (printFilenames) { + // TODO: Have a command-line option to turn the timestamps on/off too, because + // they are nondeterministic across runs. + + // Add timestamp to indicate how long operations are taking. + // Duplicate messages are suppressed, so this might not appear in front of every + // " is type-checking " message (when a file takes less than a second to + // type-check). + message(Diagnostic.Kind.NOTE, Instant.now().toString()); + message( + Diagnostic.Kind.NOTE, + "%s is type-checking %s", + (Object) this.getClass().getSimpleName(), + currentRoot.getSourceFile().getName()); + } + } + + // Visit the attributed tree. + try { + visitor.visit(p); + warnUnneededSuppressions(); + } catch (UserError ce) { + logUserError(ce); + } catch (TypeSystemError ce) { + logTypeSystemError(ce); + } catch (BugInCF ce) { + logBugInCF(ce); + } catch (Throwable t) { + logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcess", t, p)); + } finally { + // Also add possibly deferred diagnostics, which will get published back in + // AbstractTypeProcessor. + this.errsOnLastExit = log.nerrors; + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Reporting type-checking errors; most clients use reportError() or reportWarning() + /// + + /** + * Reports an error. By default, prints it to the screen via the compiler's internal messager. + * + * @param source the source position information; may be an Element, a Tree, or null + * @param messageKey the message key + * @param args arguments for interpolation in the string corresponding to the given message key + */ + public void reportError( + @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { + report(source, Diagnostic.Kind.ERROR, messageKey, args); + } + + /** + * Reports a warning. By default, prints it to the screen via the compiler's internal messager. + * + * @param source the source position information; may be an Element, a Tree, or null + * @param messageKey the message key + * @param args arguments for interpolation in the string corresponding to the given message key + */ + public void reportWarning( + @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { + report(source, Diagnostic.Kind.MANDATORY_WARNING, messageKey, args); + } + + /** + * Reports a diagnostic message. By default, prints it to the screen via the compiler's internal + * messager. + * + *

It is rare to use this method. Most clients should use {@link #reportError} or {@link + * #reportWarning}. + * + * @param source the source position information; may be an Element, a Tree, or null + * @param d the diagnostic message + */ + public void report(@Nullable Object source, DiagMessage d) { + report(source, d.getKind(), d.getMessageKey(), d.getArgs()); + } + + /** + * Reports a diagnostic message. By default, it prints it to the screen via the compiler's + * internal messager; however, it might also store it for later output. + * + * @param source the source position information; may be an Element, a Tree, or null + * @param kind the type of message + * @param messageKey the message key + * @param args arguments for interpolation in the string corresponding to the given message key + */ + // Not a format method. However, messageKey should be either a format string for `args`, or a + // property key that maps to a format string for `args`. + // @FormatMethod + @SuppressWarnings("formatter:format.string.invalid") // arg is a format string or a property key + private void report( + @Nullable Object source, + Diagnostic.Kind kind, + @CompilerMessageKey String messageKey, + Object... args) { + assert messagesProperties != null : "null messagesProperties"; + + if (shouldSuppressWarnings(source, messageKey)) { + return; + } + Object preciseSource = getSourceWithPrecisePosition(source); + + if (args != null) { + for (int i = 0; i < args.length; ++i) { + args[i] = processErrorMessageArg(args[i]); + } + } + + if (kind == Diagnostic.Kind.NOTE) { + System.err.println("(NOTE) " + String.format(messageKey, args)); + return; + } + + String defaultFormat = "(" + messageKey + ")"; + String fmtString; + if (this.processingEnv.getOptions() != null /*nnbug*/ + && this.processingEnv.getOptions().containsKey("nomsgtext")) { + fmtString = defaultFormat; + } else if (this.processingEnv.getOptions() != null /*nnbug*/ + && this.processingEnv.getOptions().containsKey("detailedmsgtext")) { + // The -Adetailedmsgtext command-line option was given, so output + // a stylized error message for easy parsing by a tool. + fmtString = + detailedMsgTextPrefix(preciseSource, defaultFormat, args) + + fullMessageOf(messageKey, defaultFormat); + } else { + fmtString = + "[" + + suppressWarningsString(messageKey) + + "] " + + fullMessageOf(messageKey, defaultFormat); + } + String messageText; + try { + messageText = String.format(fmtString, args); + } catch (Exception e) { + throw new BugInCF( + "Invalid format string: \"" + fmtString + "\" args: " + Arrays.toString(args), e); + } + + if (kind == Diagnostic.Kind.ERROR && warns) { + kind = Diagnostic.Kind.MANDATORY_WARNING; + } + + if (preciseSource instanceof Element) { + messager.printMessage(kind, messageText, (Element) preciseSource); + } else if (preciseSource instanceof Tree) { + printOrStoreMessage(kind, messageText, (Tree) preciseSource, currentRoot); + } else { + throw new BugInCF("invalid position source, class=" + preciseSource.getClass()); + } + } + + /** + * This method improves the source position information for message reporting. If the given {@code + * source} does not have a precise location, it will try to return an object with a precise + * location that encloses the {@code source}; otherwise, it simply returns the {@code source}. + * + *

Currently, missing position only happens to artificial trees generated by the compiler. For + * example, a lambda expression "s -> ..." in the source code can be de-sugared into "(Type s) + * -> ..." in the corresponding {@link Tree}, where the "Type" {@link Tree} is an artificial + * tree. + * + * @param source the original source position information; may be an Element, a Tree, or null + * @return a source that may have more precise position information + */ + private @PolyNull Object getSourceWithPrecisePosition(@PolyNull Object source) { + if (!(source instanceof JCTree)) { + return source; + } + + JCTree tree = (JCTree) source; + if (tree.getPreferredPosition() != Position.NOPOS) { + return tree; + } + + TreePath path = getTreePathCacher().getPath(currentRoot, tree); + if (path == null) { + return tree; + } + + // find the first parent in AST path that has a precise preferred position + while (((JCTree) path.getLeaf()).getPreferredPosition() == Position.NOPOS + && path.getParentPath() != null) { + path = path.getParentPath(); + } + return path.getLeaf(); + } + + /** + * Print a non-localized message using the javac messager. This is preferable to using System.out + * or System.err, but should only be used for exceptional cases that don't happen in correct + * usage. Localized messages should be raised using {@link #reportError}, {@link #reportWarning}, + * etc. + * + * @param kind the kind of message to print + * @param msg the message text + * @param args optional arguments to substitute in the message + * @see SourceChecker#report(Object, DiagMessage) + */ + @FormatMethod + public void message(Diagnostic.Kind kind, String msg, Object... args) { + message(kind, String.format(msg, args)); + } + + /** + * Print a non-localized message using the javac messager. This is preferable to using System.out + * or System.err, but should only be used for exceptional cases that don't happen in correct + * usage. Localized messages should be raised using {@link #reportError}, {@link #reportWarning}, + * etc. + * + * @param kind the kind of message to print + * @param msg the message text + * @see SourceChecker#report(Object, DiagMessage) + */ + public void message(javax.tools.Diagnostic.Kind kind, String msg) { + if (messager == null) { + // If this method is called before initChecker() sets the field + messager = processingEnv.getMessager(); + } + messager.printMessage(kind, msg); + } + + /** + * Print the given message. + * + * @param msg the message to print x + */ + private void printMessage(String msg) { + if (messager == null) { + // If this method is called before initChecker() sets the field + messager = processingEnv.getMessager(); + } + messager.printMessage(Diagnostic.Kind.ERROR, msg); + } + + /** + * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. + * + *

This method exists so that the BaseTypeChecker can override it. For compound checkers, it + * stores all messages and sorts them by location before outputting them. + * + * @param kind the kind of message to print + * @param message the message text + * @param source the source code position of the diagnostic message + * @param root the compilation unit + */ + protected void printOrStoreMessage( + javax.tools.Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + printOrStoreMessage(kind, message, source, root, trace); + } + + /** + * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. + * + *

This method exists so that the BaseTypeChecker can override it. For compound checkers, it + * stores all messages and sorts them by location before outputting them. + * + * @param kind the kind of message to print + * @param message the message text + * @param source the source code position of the diagnostic message + * @param root the compilation unit + * @param trace the stack trace where the checker encountered an error. It is printed when the + * dumpOnErrors option is enabled. + */ + protected void printOrStoreMessage( + javax.tools.Diagnostic.Kind kind, + String message, + Tree source, + CompilationUnitTree root, + StackTraceElement[] trace) { + Trees.instance(processingEnv).printMessage(kind, message, source, root); + printStackTrace(trace); + } + + /** + * Output the given stack trace if the "dumpOnErrors" option is enabled. + * + * @param trace stack trace when the checker encountered a warning/error + */ + private void printStackTrace(StackTraceElement[] trace) { + if (hasOption("dumpOnErrors")) { + StringBuilder msg = new StringBuilder(); + for (StackTraceElement elem : trace) { + msg.append("\tat " + elem + "\n"); + } + message(Diagnostic.Kind.NOTE, msg.toString()); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Diagnostic message formatting + /// + + /** + * Returns the localized long message corresponding to this key. If not found, tries suffixes of + * this key, stripping off dot-separated prefixes. If still not found, returns {@code + * defaultValue}. + * + * @param messageKey a message key + * @param defaultValue a default value to use if {@code messageKey} is not a message key + * @return the localized long message corresponding to this key or a suffix, or {@code + * defaultValue} + */ + protected String fullMessageOf(String messageKey, String defaultValue) { + String key = messageKey; + + do { + String messageForKey = messagesProperties.getProperty(key); + if (messageForKey != null) { + return messageForKey; + } + + int dot = key.indexOf('.'); + if (dot < 0) { + return defaultValue; + } + key = key.substring(dot + 1); + } while (true); + } + + /** + * Process an argument to an error message before it is passed to String.format. + * + *

This implementation expands the argument if it is exactly a message key. + * + *

By contrast, {@link #fullMessageOf} processes the message key itself but not the arguments, + * and tries suffixes. + * + * @param arg the argument + * @return the result after processing + */ + protected Object processErrorMessageArg(Object arg) { + // Check to see if the argument itself is a property to be expanded + if (arg instanceof String) { + return messagesProperties.getProperty((String) arg, (String) arg); + } else { + return arg; + } + } + + /** Separates parts of a "detailed message", to permit easier parsing. */ + public static final String DETAILS_SEPARATOR = " $$ "; + + /** + * Returns all but the message key part of the message format output by {@code -Adetailedmsgtext}. + * + * @param source the object from which to obtain source position information; may be an Element, a + * Tree, or null + * @param defaultFormat the message key, in parentheses + * @param args arguments for interpolation in the string corresponding to the given message key + * @return the first part of the message format output by {@code -Adetailedmsgtext} + */ + private String detailedMsgTextPrefix( + @Nullable Object source, String defaultFormat, Object[] args) { + StringJoiner sj = new StringJoiner(DETAILS_SEPARATOR); + + // The parts, separated by " $$ " (DETAILS_SEPARATOR), are: + + // (1) error key + sj.add(defaultFormat); + + // (2) number of additional tokens, and those tokens; this depends on the error message, and + // an example is the found and expected types + if (args != null) { + sj.add(Integer.toString(args.length)); + for (Object arg : args) { + sj.add(Objects.toString(arg)); + } + } else { + // Output 0 for null arguments. + sj.add(Integer.toString(0)); + } + + // (3) The error position, as starting and ending characters in the source file. + sj.add(detailedMsgTextPositionString(sourceToTree(source), currentRoot)); + + // (4) The human-readable error message will be added by the caller. + sj.add(""); // Add DETAILS_SEPARATOR at the end. + return sj.toString(); + } + + /** + * Returns the most specific warning suppression string for the warning/error being printed. + * + *

    + *
  • If {@code -AshowSuppressWarningsStrings} was supplied on the command line, this is {@code + * [checkername1, checkername2]:msg}, where each {@code checkername} is a checker name or + * "allcheckers". + *
  • If {@code -ArequirePrefixInWarningSuppressions} or {@code -AshowPrefixInWarningMessages} + * was supplied on the command line, this is {@code checkername:msg} (where {@code + * checkername} may be "allcheckers"). + *
  • Otherwise, it is just {@code msg}. + *
+ * + * @param messageKey the simple, checker-specific error message key + * @return the most specific SuppressWarnings string for the warning/error being printed + */ + protected String suppressWarningsString(String messageKey) { + Collection prefixes = this.getSuppressWarningsPrefixes(); + prefixes.remove(SUPPRESS_ALL_PREFIX); + if (showSuppressWarningsStrings) { + List list = new ArrayList<>(prefixes); + // Make sure "allcheckers" is at the end of the list. + if (useAllcheckersPrefix) { + list.add(SUPPRESS_ALL_PREFIX); + } + return list + ":" + messageKey; + } else if (requirePrefixInWarningSuppressions || showPrefixInWarningMessages) { + // If the warning key must be prefixed with a prefix (a checker name), then add that to + // the SuppressWarnings string that is printed. + return getWarningMessagePrefix() + ":" + messageKey; + } else { + return messageKey; + } + } + + /** + * Convert a Tree, Element, or null, into a Tree or null. + * + * @param source the object from which to obtain source position information; may be an Element, a + * Tree, or null + * @return the tree associated with the given source object, or null if none + */ + private @Nullable Tree sourceToTree(@Nullable Object source) { + if (source instanceof Element) { + return trees.getTree((Element) source); + } else if (source instanceof Tree) { + return (Tree) source; + } else if (source == null) { + return null; + } else { + throw new BugInCF("Unexpected source %s [%s]", source, source.getClass()); + } + } + + /** + * For the given tree, compute the source positions for that tree. Return a "tuple"-like string + * (e.g. "( 1, 200 )" ) that contains the start and end position of the tree in the current + * compilation unit. Used only by the -Adetailedmsgtext output format. + * + * @param tree tree to locate within the current compilation unit + * @param currentRoot the current compilation unit + * @return a tuple string representing the range of characters that tree occupies in the source + * file, or the empty string if {@code tree} is null + */ + private String detailedMsgTextPositionString( + @Nullable Tree tree, CompilationUnitTree currentRoot) { + if (tree == null) { + return ""; + } + + SourcePositions sourcePositions = trees.getSourcePositions(); + long start = sourcePositions.getStartPosition(currentRoot, tree); + long end = sourcePositions.getEndPosition(currentRoot, tree); + + return "( " + start + ", " + end + " )"; + } + + /////////////////////////////////////////////////////////////////////////// + /// Lint options ("-Alint:xxxx" and "-Alint:-xxxx") + /// + + /** + * Determine which lint options are artive. + * + * @param options the command-line options + * @return the active lint options + */ + private Set createActiveLints(Map options) { + if (!options.containsKey("lint")) { + return Collections.emptySet(); + } + + String lintString = options.get("lint"); + if (lintString == null) { + return Collections.singleton("all"); + } + + List lintStrings = SystemUtil.COMMA_SPLITTER.splitToList(lintString); + Set activeLint = ArraySet.newArraySetOrHashSet(lintStrings.size()); + for (String s : lintStrings) { + if (!this.getSupportedLintOptions().contains(s) + && !(s.charAt(0) == '-' && this.getSupportedLintOptions().contains(s.substring(1))) + && !s.equals("all") + && !s.equals("none")) { + this.messager.printMessage( + Diagnostic.Kind.WARNING, + "Unsupported lint option: " + s + "; All options: " + this.getSupportedLintOptions()); + } + + activeLint.add(s); + if (s.equals("none")) { + activeLint.add("-all"); + } + } + + return Collections.unmodifiableSet(activeLint); + } + + /** + * Determines the value of the lint option with the given name. Just as javac uses + * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options + * are enabled with "-Alint:xxx" and disabled with "-Alint:-xxx". + * + * @throws IllegalArgumentException if the option name is not recognized via the {@link + * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} + * method + * @param name the name of the lint option to check for + * @return true if the lint option was given, false if it was not given or was given prepended + * with a "-" + * @see SourceChecker#getLintOption(String, boolean) + */ + public final boolean getLintOption(String name) { + return getLintOption(name, false); + } + + /** + * Determines the value of the lint option with the given name. Just as javac uses + * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options + * are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". + * + * @throws IllegalArgumentException if the option name is not recognized via the {@link + * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} + * method + * @param name the name of the lint option to check for + * @param def the default option value, returned if the option was not given + * @return true if the lint option was given, false if it was given prepended with a "-", or + * {@code def} if it was not given at all + * @see SourceChecker#getLintOption(String) + * @see SourceChecker#getOption(String) + */ + public final boolean getLintOption(String name, boolean def) { + if (!this.getSupportedLintOptions().contains(name)) { + throw new UserError("Illegal lint option: " + name); + } + + // This is only needed if initChecker() has not yet been called. + if (activeLints == null) { + activeLints = createActiveLints(getOptions()); + } + + if (activeLints.isEmpty()) { + return def; + } + + String tofind = name; + while (tofind != null) { + if (activeLints.contains(tofind)) { + return true; + } else if (activeLints.contains(String.format("-%s", tofind))) { + return false; + } + + tofind = parentOfOption(tofind); + } + + return def; + } + + /** + * Set the value of the lint option with the given name. Just as javac uses + * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options + * are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". This method can be used by + * subclasses to enforce having certain lint options enabled/disabled. + * + * @throws IllegalArgumentException if the option name is not recognized via the {@link + * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} + * method + * @param name the name of the lint option to set + * @param val the option value + * @see SourceChecker#getLintOption(String) + * @see SourceChecker#getLintOption(String,boolean) + */ + protected final void setLintOption(String name, boolean val) { + if (!this.getSupportedLintOptions().contains(name)) { + throw new UserError("Illegal lint option: " + name); + } + + /* TODO: warn if the option is also provided on the command line(?) + boolean exists = false; + if (!activeLints.isEmpty()) { String tofind = name; while (tofind != null) { - if (activeLints.contains(tofind)) { - return true; - } else if (activeLints.contains(String.format("-%s", tofind))) { - return false; + if (activeLints.contains(tofind) || // direct + activeLints.contains(String.format("-%s", tofind)) || // negation + activeLints.contains(tofind.substring(1))) { // name was negation + exists = true; } - tofind = parentOfOption(tofind); } - - return def; - } - - /** - * Set the value of the lint option with the given name. Just as javac - * uses "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint - * options are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". This method can be - * used by subclasses to enforce having certain lint options enabled/disabled. - * - * @throws IllegalArgumentException if the option name is not recognized via the {@link - * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} - * method - * @param name the name of the lint option to set - * @param val the option value - * @see SourceChecker#getLintOption(String) - * @see SourceChecker#getLintOption(String,boolean) - */ - protected final void setLintOption(String name, boolean val) { - if (!this.getSupportedLintOptions().contains(name)) { - throw new UserError("Illegal lint option: " + name); - } - - /* TODO: warn if the option is also provided on the command line(?) - boolean exists = false; - if (!activeLints.isEmpty()) { - String tofind = name; - while (tofind != null) { - if (activeLints.contains(tofind) || // direct - activeLints.contains(String.format("-%s", tofind)) || // negation - activeLints.contains(tofind.substring(1))) { // name was negation - exists = true; - } - tofind = parentOfOption(tofind); - } - } - - if (exists) { - // TODO: Issue warning? - } - TODO: assert that name doesn't start with '-' - */ - - Set newlints = ArraySet.newArraySetOrHashSet(activeLints.size() + 1); - newlints.addAll(activeLints); - if (val) { - newlints.add(name); - } else { - newlints.add(String.format("-%s", name)); - } - activeLints = Collections.unmodifiableSet(newlints); - } - - /** - * Helper method to find the parent of a lint key. The lint hierarchy level is denoted by a - * colon ':'. 'all' is the root for all hierarchy. - * - *
-     * Example
-     *    cast:unsafe → cast
-     *    cast        → all
-     *    all         → {@code null}
-     * 
- * - * @param name the lint key whose parest to find - * @return the parent of the lint key - */ - private @Nullable String parentOfOption(String name) { - if (name.equals("all")) { - return null; - } - int colonIndex = name.lastIndexOf(':'); - if (colonIndex != -1) { - return name.substring(0, colonIndex); - } else { - return "all"; - } - } - - /** - * Returns the lint options recognized by this checker. Lint options are those which can be - * checked for via {@link SourceChecker#getLintOption}. - * - * @return an unmodifiable {@link Set} of the lint options recognized by this checker - */ - public Set getSupportedLintOptions() { - if (supportedLints == null) { - supportedLints = createSupportedLintOptions(); - } - return supportedLints; } - /** Compute the set of supported lint options. */ - protected Set createSupportedLintOptions() { - SupportedLintOptions sl = this.getClass().getAnnotation(SupportedLintOptions.class); - - if (sl == null) { - return Collections.emptySet(); - } - - @Nullable String @Nullable [] slValue = sl.value(); - assert slValue != null; - - @Nullable String[] lintArray = slValue; - Set lintSet = new HashSet<>(lintArray.length); - lintSet.addAll(Arrays.asList(lintArray)); - return Collections.unmodifiableSet(lintSet); - } - - /** - * Set the supported lint options. Use of this method should be limited to the AggregateChecker, - * who needs to set the lint options to the union of all subcheckers. Also, e.g. the - * NullnessSubchecker need to use this method, as one is created by the other. - * - * @param newLints the new supported lint options, which replace any existing ones - */ - protected void setSupportedLintOptions(Set newLints) { - supportedLints = newLints; - } - - /////////////////////////////////////////////////////////////////////////// - /// Regular (non-lint) options ("-Axxxx") - /// - - /** - * Determine which options are active. - * - * @param options all provided options - * @return a value for {@link #activeOptions} - */ - private Map createActiveOptions(Map options) { - if (options.isEmpty()) { - return Collections.emptyMap(); - } - - Map activeOpts = new HashMap<>(CollectionsPlume.mapCapacity(options)); - - forEveryOption: - for (Map.Entry opt : options.entrySet()) { - String key = opt.getKey(); - String value = opt.getValue(); - - String[] split = key.split(OPTION_SEPARATOR); - - switch (split.length) { - case 1: - // No separator, option always active. - activeOpts.put(key, value); - break; - case 2: - Class clazz = this.getClass(); - - do { - if (clazz.getCanonicalName().equals(split[0]) - || clazz.getSimpleName().equals(split[0])) { - // Valid class-option pair. - activeOpts.put(split[1], value); - continue forEveryOption; - } - - clazz = clazz.getSuperclass(); - } while (clazz != null - && !clazz.getName() - .equals(AbstractTypeProcessor.class.getCanonicalName())); - // Didn't find a matching class. Option might be for another processor. Add - // option anyways. javac will warn if no processor supports the option. - activeOpts.put(key, value); - break; - default: - // Too many separators. Option might be for another processor. Add option - // anyways. javac will warn if no processor supports the option. - activeOpts.put(key, value); + if (exists) { + // TODO: Issue warning? + } + TODO: assert that name doesn't start with '-' + */ + + Set newlints = ArraySet.newArraySetOrHashSet(activeLints.size() + 1); + newlints.addAll(activeLints); + if (val) { + newlints.add(name); + } else { + newlints.add(String.format("-%s", name)); + } + activeLints = Collections.unmodifiableSet(newlints); + } + + /** + * Helper method to find the parent of a lint key. The lint hierarchy level is denoted by a colon + * ':'. 'all' is the root for all hierarchy. + * + *
+   * Example
+   *    cast:unsafe → cast
+   *    cast        → all
+   *    all         → {@code null}
+   * 
+ * + * @param name the lint key whose parest to find + * @return the parent of the lint key + */ + private @Nullable String parentOfOption(String name) { + if (name.equals("all")) { + return null; + } + int colonIndex = name.lastIndexOf(':'); + if (colonIndex != -1) { + return name.substring(0, colonIndex); + } else { + return "all"; + } + } + + /** + * Returns the lint options recognized by this checker. Lint options are those which can be + * checked for via {@link SourceChecker#getLintOption}. + * + * @return an unmodifiable {@link Set} of the lint options recognized by this checker + */ + public Set getSupportedLintOptions() { + if (supportedLints == null) { + supportedLints = createSupportedLintOptions(); + } + return supportedLints; + } + + /** Compute the set of supported lint options. */ + protected Set createSupportedLintOptions() { + SupportedLintOptions sl = this.getClass().getAnnotation(SupportedLintOptions.class); + + if (sl == null) { + return Collections.emptySet(); + } + + @Nullable String @Nullable [] slValue = sl.value(); + assert slValue != null; + + @Nullable String[] lintArray = slValue; + Set lintSet = new HashSet<>(lintArray.length); + lintSet.addAll(Arrays.asList(lintArray)); + return Collections.unmodifiableSet(lintSet); + } + + /** + * Set the supported lint options. Use of this method should be limited to the AggregateChecker, + * who needs to set the lint options to the union of all subcheckers. Also, e.g. the + * NullnessSubchecker need to use this method, as one is created by the other. + * + * @param newLints the new supported lint options, which replace any existing ones + */ + protected void setSupportedLintOptions(Set newLints) { + supportedLints = newLints; + } + + /////////////////////////////////////////////////////////////////////////// + /// Regular (non-lint) options ("-Axxxx") + /// + + /** + * Determine which options are active. + * + * @param options all provided options + * @return a value for {@link #activeOptions} + */ + private Map createActiveOptions(Map options) { + if (options.isEmpty()) { + return Collections.emptyMap(); + } + + Map activeOpts = new HashMap<>(CollectionsPlume.mapCapacity(options)); + + forEveryOption: + for (Map.Entry opt : options.entrySet()) { + String key = opt.getKey(); + String value = opt.getValue(); + + String[] split = key.split(OPTION_SEPARATOR); + + switch (split.length) { + case 1: + // No separator, option always active. + activeOpts.put(key, value); + break; + case 2: + Class clazz = this.getClass(); + + do { + if (clazz.getCanonicalName().equals(split[0]) + || clazz.getSimpleName().equals(split[0])) { + // Valid class-option pair. + activeOpts.put(split[1], value); + continue forEveryOption; } - // Don't add code here, there is a `continue` in the switch above. - } - return Collections.unmodifiableMap(activeOpts); - } - - /** - * Add additional active options. Use of this method should be limited to the AggregateChecker, - * who needs to set the active options to the union of all subcheckers. - * - * @param moreOpts the active options to add - */ - protected void addOptions(Map moreOpts) { - Map activeOpts = new HashMap<>(getOptions()); - activeOpts.putAll(moreOpts); - activeOptions = Collections.unmodifiableMap(activeOpts); - } - @Override - public Map getOptions() { - if (activeOptions == null) { - activeOptions = createActiveOptions(processingEnv.getOptions()); - } - return activeOptions; - } - - @Override - public final boolean hasOption(String name) { - return getOptions().containsKey(name); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final String getOption(String name) { - return getOption(name, null); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final String getOption(String name, String defaultValue) { - // TODO: Should supportedOptions be cached? - Set supportedOptions = this.getSupportedOptions(); - if (!supportedOptions.contains(name)) { - throw new UserError( - "Illegal option: " - + name - + "; supported options = " - + String.join(",", supportedOptions)); - } - - if (activeOptions == null) { - activeOptions = createActiveOptions(processingEnv.getOptions()); - } - - if (activeOptions.isEmpty()) { - return defaultValue; - } - - if (activeOptions.containsKey(name)) { - return activeOptions.get(name); - } else { - return defaultValue; - } - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final boolean getBooleanOption(String name) { - return getBooleanOption(name, false); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final boolean getBooleanOption(String name, boolean defaultValue) { - String value = getOption(name); - if (value == null) { - return defaultValue; - } - if (value.equals("true")) { - return true; - } - if (value.equals("false")) { - return false; - } - throw new UserError( - String.format( - "Value of %s option should be a boolean, but is \"%s\".", name, value)); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final List getStringsOption( - String name, char separator, List defaultValue) { - String value = getOption(name); - if (value == null) { - return defaultValue; - } - return Splitter.on(separator).omitEmptyStrings().splitToList(value); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final List getStringsOption( - String name, String separator, List defaultValue) { - String value = getOption(name); - if (value == null) { - return defaultValue; - } - return Splitter.on(separator).omitEmptyStrings().splitToList(value); - } - - /** - * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation - * provided version {@link javax.annotation.processing.SupportedOptions}. - */ - @Override - public Set getSupportedOptions() { - Set options = new HashSet<>(); - - // Support all options provided with the standard {@link - // javax.annotation.processing.SupportedOptions} annotation. - options.addAll(super.getSupportedOptions()); - - // For the Checker Framework annotation - // {@link org.checkerframework.framework.source.SupportedOptions} - // we additionally add - Class clazz = this.getClass(); - List> clazzPrefixes = new ArrayList<>(); - - do { - clazzPrefixes.add(clazz); - - SupportedOptions so = clazz.getAnnotation(SupportedOptions.class); - if (so != null) { - options.addAll(expandCFOptions(clazzPrefixes, so.value())); - } clazz = clazz.getSuperclass(); - } while (clazz != null - && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); - - return Collections.unmodifiableSet(options); - } - - /** - * Generate the possible command-line option names by prefixing each class name from {@code - * classPrefixes} to {@code options}, separated by {@link #OPTION_SEPARATOR}. - * - * @param clazzPrefixes the classes to prefix - * @param options the option names - * @return the possible combinations that should be supported - */ - protected Collection expandCFOptions( - List> clazzPrefixes, String[] options) { - Set res = - new HashSet<>( - CollectionsPlume.mapCapacity(options.length * (1 + clazzPrefixes.size()))); - for (String option : options) { - res.add(option); - for (Class clazz : clazzPrefixes) { - res.add(clazz.getCanonicalName() + OPTION_SEPARATOR + option); - res.add(clazz.getSimpleName() + OPTION_SEPARATOR + option); - } - } - return res; - } - - /** - * Overrides the default implementation to always return a singleton set containing only "*". - * - *

javac uses this list to determine which classes process; javac only runs an annotation - * processor on classes that contain at least one of the mentioned annotations. Thus, the effect - * of returning "*" is as if the checker were annotated by - * {@code @SupportedAnnotationTypes("*")}: javac runs the checker on every class mentioned on - * the javac command line. This method also checks that subclasses do not contain a {@link - * SupportedAnnotationTypes} annotation. - * - *

To specify the annotations that a checker recognizes as type qualifiers, see {@link - * AnnotatedTypeFactory#createSupportedTypeQualifiers()}. - * - * @throws Error if a subclass is annotated with {@link SupportedAnnotationTypes} - */ - @Override - public final Set getSupportedAnnotationTypes() { - SupportedAnnotationTypes supported = - this.getClass().getAnnotation(SupportedAnnotationTypes.class); - if (supported != null) { - throw new BugInCF( - "@SupportedAnnotationTypes should not be written on any checker;" - + " supported annotation types are inherited from SourceChecker."); - } - return Collections.singleton("*"); - } - - /////////////////////////////////////////////////////////////////////////// - /// Warning suppression and unneeded warnings - /// - - /** - * Returns the argument to {@code -AsuppressWarnings}, split on commas, or null if no such - * argument. Only ever called once; the value is cached in field {@link - * #suppressWarningsStringsFromOption}. - * - * @return the argument to {@code -AsuppressWarnings}, split on commas, or null if no such - * argument - */ - private String @Nullable [] getSuppressWarningsStringsFromOption() { - if (!computedSuppressWarningsStringsFromOption) { - computedSuppressWarningsStringsFromOption = true; - Map options = getOptions(); - if (options.containsKey("suppressWarnings")) { - String swStrings = options.get("suppressWarnings"); - if (swStrings != null) { - this.suppressWarningsStringsFromOption = swStrings.split(","); - } - } - } - - return this.suppressWarningsStringsFromOption; - } - - /** - * Issues a warning about any {@code @SuppressWarnings} that didn't suppress a warning, but - * starts with this checker name or "allcheckers". - */ - protected void warnUnneededSuppressions() { - if (!warnUnneededSuppressions) { - return; - } - - Set elementsSuppress = new HashSet<>(this.elementsWithSuppressedWarnings); - this.elementsWithSuppressedWarnings.clear(); - Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); - Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); - warnUnneededSuppressions(elementsSuppress, prefixes, errorKeys); - getVisitor().treesWithSuppressWarnings.clear(); - } - - /** - * Issues a warning about any {@code @SuppressWarnings} string that didn't suppress a warning, - * but starts with one of the given prefixes (checker names). Does nothing if the string doesn't - * start with a checker name. - * - * @param elementsSuppress elements with a {@code @SuppressWarnings} that actually suppressed a - * warning - * @param prefixes the SuppressWarnings prefixes that suppress all warnings from this checker - * @param allErrorKeys all error keys that can be issued by this checker - */ - protected void warnUnneededSuppressions( - Set elementsSuppress, Set prefixes, Set allErrorKeys) { - for (Tree tree : getVisitor().treesWithSuppressWarnings) { - Element elt = TreeUtils.elementFromTree(tree); - // TODO: This test is too coarse. The fact that this @SuppressWarnings suppressed - // *some* warning doesn't mean that every value in it did so. - if (elementsSuppress.contains(elt)) { - continue; - } - // tree has a @SuppressWarnings annotation that didn't suppress any warnings. - SuppressWarnings suppressAnno = elt.getAnnotation(SuppressWarnings.class); - String[] suppressWarningsStrings = suppressAnno.value(); - for (String suppressWarningsString : suppressWarningsStrings) { - if (warnUnneededSuppressionsExceptions != null - && warnUnneededSuppressionsExceptions - .matcher(suppressWarningsString) - .find(0)) { - continue; - } - for (String prefix : prefixes) { - if (suppressWarningsString.equals(prefix) - || (suppressWarningsString.startsWith(prefix + ":") - && !suppressWarningsString.equals( - prefix + ":unneeded.suppression"))) { - reportUnneededSuppression(tree, suppressWarningsString); - break; // Don't report the same warning string more than once. - } - } - } - } - } - - /** - * Issues a warning that the string in a {@code @SuppressWarnings} on {@code tree} isn't needed. - * - * @param tree has unneeded {@code @SuppressWarnings} - * @param suppressWarningsString the SuppressWarnings string that isn't needed - */ - private void reportUnneededSuppression(Tree tree, String suppressWarningsString) { - Tree swTree = findSuppressWarningsAnnotationTree(tree); - report( - swTree, - Diagnostic.Kind.MANDATORY_WARNING, - SourceChecker.UNNEEDED_SUPPRESSION_KEY, - "\"" + suppressWarningsString + "\"", - getClass().getSimpleName()); - } - - /** The name of the @SuppressWarnings annotation. */ - private static final @CanonicalName String suppressWarningsClassName = - SuppressWarnings.class.getCanonicalName(); - - /** - * Finds the tree that is a {@code @SuppressWarnings} annotation. - * - * @param tree a class, method, or variable tree annotated with {@code @SuppressWarnings} - * @return tree for {@code @SuppressWarnings} or {@code default} if one isn't found - */ - private Tree findSuppressWarningsAnnotationTree(Tree tree) { - List annotations; - if (TreeUtils.isClassTree(tree)) { - annotations = ((ClassTree) tree).getModifiers().getAnnotations(); - } else if (tree.getKind() == Tree.Kind.METHOD) { - annotations = ((MethodTree) tree).getModifiers().getAnnotations(); - } else { - annotations = ((VariableTree) tree).getModifiers().getAnnotations(); - } - - for (AnnotationTree annotationTree : annotations) { - if (AnnotationUtils.areSameByName( - TreeUtils.annotationFromAnnotationTree(annotationTree), - suppressWarningsClassName)) { - return annotationTree; - } - } - throw new BugInCF("Did not find @SuppressWarnings: " + tree); - } - - /** - * Returns true if all the warnings pertaining to the given source should be suppressed. This - * implementation just delegates to an overloaded, more specific version of {@code - * shouldSuppressWarnings()}. - * - * @param src the position object to test; may be an Element, a Tree, or null - * @param errKey the error key the checker is emitting - * @return true if all warnings pertaining to the given source should be suppressed - * @see #shouldSuppressWarnings(Element, String) - * @see #shouldSuppressWarnings(Tree, String) - */ - private boolean shouldSuppressWarnings(@Nullable Object src, String errKey) { - if (src instanceof Element) { - return shouldSuppressWarnings((Element) src, errKey); - } else if (src instanceof Tree) { - return shouldSuppressWarnings((Tree) src, errKey); - } else if (src == null) { - return false; - } else { - throw new BugInCF("Unexpected source " + src); - } - } - - /** - * Returns true if all the warnings pertaining to a given tree should be suppressed. Returns - * true if the tree is within the scope of a @SuppressWarnings annotation, one of whose values - * suppresses the checker's warning. Also, returns true if the {@code errKey} matches a string - * in {@code -AsuppressWarnings}. - * - * @param tree the tree that might be a source of a warning - * @param errKey the error key the checker is emitting - * @return true if no warning should be emitted for the given tree because it is contained by a - * declaration with an appropriately-valued {@literal @}SuppressWarnings annotation; false - * otherwise - */ - public boolean shouldSuppressWarnings(Tree tree, String errKey) { - Collection prefixes = getSuppressWarningsPrefixes(); - if (prefixes.isEmpty() - || (prefixes.contains(SUPPRESS_ALL_PREFIX) && prefixes.size() == 1)) { - throw new BugInCF( - "Checker must provide a SuppressWarnings prefix." - + " SourceChecker#getSuppressWarningsPrefixes was not overridden" - + " correctly."); - } - - if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { - // If the error key matches a warning string in the -AsuppressWarnings, then suppress - // the warning. + } while (clazz != null + && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); + // Didn't find a matching class. Option might be for another processor. Add + // option anyways. javac will warn if no processor supports the option. + activeOpts.put(key, value); + break; + default: + // Too many separators. Option might be for another processor. Add option + // anyways. javac will warn if no processor supports the option. + activeOpts.put(key, value); + } + // Don't add code here, there is a `continue` in the switch above. + } + return Collections.unmodifiableMap(activeOpts); + } + + /** + * Add additional active options. Use of this method should be limited to the AggregateChecker, + * who needs to set the active options to the union of all subcheckers. + * + * @param moreOpts the active options to add + */ + protected void addOptions(Map moreOpts) { + Map activeOpts = new HashMap<>(getOptions()); + activeOpts.putAll(moreOpts); + activeOptions = Collections.unmodifiableMap(activeOpts); + } + + @Override + public Map getOptions() { + if (activeOptions == null) { + activeOptions = createActiveOptions(processingEnv.getOptions()); + } + return activeOptions; + } + + @Override + public final boolean hasOption(String name) { + return getOptions().containsKey(name); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final String getOption(String name) { + return getOption(name, null); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final String getOption(String name, String defaultValue) { + // TODO: Should supportedOptions be cached? + Set supportedOptions = this.getSupportedOptions(); + if (!supportedOptions.contains(name)) { + throw new UserError( + "Illegal option: " + + name + + "; supported options = " + + String.join(",", supportedOptions)); + } + + if (activeOptions == null) { + activeOptions = createActiveOptions(processingEnv.getOptions()); + } + + if (activeOptions.isEmpty()) { + return defaultValue; + } + + if (activeOptions.containsKey(name)) { + return activeOptions.get(name); + } else { + return defaultValue; + } + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final boolean getBooleanOption(String name) { + return getBooleanOption(name, false); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final boolean getBooleanOption(String name, boolean defaultValue) { + String value = getOption(name); + if (value == null) { + return defaultValue; + } + if (value.equals("true")) { + return true; + } + if (value.equals("false")) { + return false; + } + throw new UserError( + String.format("Value of %s option should be a boolean, but is \"%s\".", name, value)); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final List getStringsOption( + String name, char separator, List defaultValue) { + String value = getOption(name); + if (value == null) { + return defaultValue; + } + return Splitter.on(separator).omitEmptyStrings().splitToList(value); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final List getStringsOption( + String name, String separator, List defaultValue) { + String value = getOption(name); + if (value == null) { + return defaultValue; + } + return Splitter.on(separator).omitEmptyStrings().splitToList(value); + } + + /** + * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation + * provided version {@link javax.annotation.processing.SupportedOptions}. + */ + @Override + public Set getSupportedOptions() { + Set options = new HashSet<>(); + + // Support all options provided with the standard {@link + // javax.annotation.processing.SupportedOptions} annotation. + options.addAll(super.getSupportedOptions()); + + // For the Checker Framework annotation + // {@link org.checkerframework.framework.source.SupportedOptions} + // we additionally add + Class clazz = this.getClass(); + List> clazzPrefixes = new ArrayList<>(); + + do { + clazzPrefixes.add(clazz); + + SupportedOptions so = clazz.getAnnotation(SupportedOptions.class); + if (so != null) { + options.addAll(expandCFOptions(clazzPrefixes, so.value())); + } + clazz = clazz.getSuperclass(); + } while (clazz != null + && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); + + return Collections.unmodifiableSet(options); + } + + /** + * Generate the possible command-line option names by prefixing each class name from {@code + * classPrefixes} to {@code options}, separated by {@link #OPTION_SEPARATOR}. + * + * @param clazzPrefixes the classes to prefix + * @param options the option names + * @return the possible combinations that should be supported + */ + protected Collection expandCFOptions( + List> clazzPrefixes, String[] options) { + Set res = + new HashSet<>(CollectionsPlume.mapCapacity(options.length * (1 + clazzPrefixes.size()))); + for (String option : options) { + res.add(option); + for (Class clazz : clazzPrefixes) { + res.add(clazz.getCanonicalName() + OPTION_SEPARATOR + option); + res.add(clazz.getSimpleName() + OPTION_SEPARATOR + option); + } + } + return res; + } + + /** + * Overrides the default implementation to always return a singleton set containing only "*". + * + *

javac uses this list to determine which classes process; javac only runs an annotation + * processor on classes that contain at least one of the mentioned annotations. Thus, the effect + * of returning "*" is as if the checker were annotated by {@code @SupportedAnnotationTypes("*")}: + * javac runs the checker on every class mentioned on the javac command line. This method also + * checks that subclasses do not contain a {@link SupportedAnnotationTypes} annotation. + * + *

To specify the annotations that a checker recognizes as type qualifiers, see {@link + * AnnotatedTypeFactory#createSupportedTypeQualifiers()}. + * + * @throws Error if a subclass is annotated with {@link SupportedAnnotationTypes} + */ + @Override + public final Set getSupportedAnnotationTypes() { + SupportedAnnotationTypes supported = + this.getClass().getAnnotation(SupportedAnnotationTypes.class); + if (supported != null) { + throw new BugInCF( + "@SupportedAnnotationTypes should not be written on any checker;" + + " supported annotation types are inherited from SourceChecker."); + } + return Collections.singleton("*"); + } + + /////////////////////////////////////////////////////////////////////////// + /// Warning suppression and unneeded warnings + /// + + /** + * Returns the argument to {@code -AsuppressWarnings}, split on commas, or null if no such + * argument. Only ever called once; the value is cached in field {@link + * #suppressWarningsStringsFromOption}. + * + * @return the argument to {@code -AsuppressWarnings}, split on commas, or null if no such + * argument + */ + private String @Nullable [] getSuppressWarningsStringsFromOption() { + if (!computedSuppressWarningsStringsFromOption) { + computedSuppressWarningsStringsFromOption = true; + Map options = getOptions(); + if (options.containsKey("suppressWarnings")) { + String swStrings = options.get("suppressWarnings"); + if (swStrings != null) { + this.suppressWarningsStringsFromOption = swStrings.split(","); + } + } + } + + return this.suppressWarningsStringsFromOption; + } + + /** + * Issues a warning about any {@code @SuppressWarnings} that didn't suppress a warning, but starts + * with this checker name or "allcheckers". + */ + protected void warnUnneededSuppressions() { + if (!warnUnneededSuppressions) { + return; + } + + Set elementsSuppress = new HashSet<>(this.elementsWithSuppressedWarnings); + this.elementsWithSuppressedWarnings.clear(); + Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); + Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); + warnUnneededSuppressions(elementsSuppress, prefixes, errorKeys); + getVisitor().treesWithSuppressWarnings.clear(); + } + + /** + * Issues a warning about any {@code @SuppressWarnings} string that didn't suppress a warning, but + * starts with one of the given prefixes (checker names). Does nothing if the string doesn't start + * with a checker name. + * + * @param elementsSuppress elements with a {@code @SuppressWarnings} that actually suppressed a + * warning + * @param prefixes the SuppressWarnings prefixes that suppress all warnings from this checker + * @param allErrorKeys all error keys that can be issued by this checker + */ + protected void warnUnneededSuppressions( + Set elementsSuppress, Set prefixes, Set allErrorKeys) { + for (Tree tree : getVisitor().treesWithSuppressWarnings) { + Element elt = TreeUtils.elementFromTree(tree); + // TODO: This test is too coarse. The fact that this @SuppressWarnings suppressed + // *some* warning doesn't mean that every value in it did so. + if (elementsSuppress.contains(elt)) { + continue; + } + // tree has a @SuppressWarnings annotation that didn't suppress any warnings. + SuppressWarnings suppressAnno = elt.getAnnotation(SuppressWarnings.class); + String[] suppressWarningsStrings = suppressAnno.value(); + for (String suppressWarningsString : suppressWarningsStrings) { + if (warnUnneededSuppressionsExceptions != null + && warnUnneededSuppressionsExceptions.matcher(suppressWarningsString).find(0)) { + continue; + } + for (String prefix : prefixes) { + if (suppressWarningsString.equals(prefix) + || (suppressWarningsString.startsWith(prefix + ":") + && !suppressWarningsString.equals(prefix + ":unneeded.suppression"))) { + reportUnneededSuppression(tree, suppressWarningsString); + break; // Don't report the same warning string more than once. + } + } + } + } + } + + /** + * Issues a warning that the string in a {@code @SuppressWarnings} on {@code tree} isn't needed. + * + * @param tree has unneeded {@code @SuppressWarnings} + * @param suppressWarningsString the SuppressWarnings string that isn't needed + */ + private void reportUnneededSuppression(Tree tree, String suppressWarningsString) { + Tree swTree = findSuppressWarningsAnnotationTree(tree); + report( + swTree, + Diagnostic.Kind.MANDATORY_WARNING, + SourceChecker.UNNEEDED_SUPPRESSION_KEY, + "\"" + suppressWarningsString + "\"", + getClass().getSimpleName()); + } + + /** The name of the @SuppressWarnings annotation. */ + private static final @CanonicalName String suppressWarningsClassName = + SuppressWarnings.class.getCanonicalName(); + + /** + * Finds the tree that is a {@code @SuppressWarnings} annotation. + * + * @param tree a class, method, or variable tree annotated with {@code @SuppressWarnings} + * @return tree for {@code @SuppressWarnings} or {@code default} if one isn't found + */ + private Tree findSuppressWarningsAnnotationTree(Tree tree) { + List annotations; + if (TreeUtils.isClassTree(tree)) { + annotations = ((ClassTree) tree).getModifiers().getAnnotations(); + } else if (tree.getKind() == Tree.Kind.METHOD) { + annotations = ((MethodTree) tree).getModifiers().getAnnotations(); + } else { + annotations = ((VariableTree) tree).getModifiers().getAnnotations(); + } + + for (AnnotationTree annotationTree : annotations) { + if (AnnotationUtils.areSameByName( + TreeUtils.annotationFromAnnotationTree(annotationTree), suppressWarningsClassName)) { + return annotationTree; + } + } + throw new BugInCF("Did not find @SuppressWarnings: " + tree); + } + + /** + * Returns true if all the warnings pertaining to the given source should be suppressed. This + * implementation just delegates to an overloaded, more specific version of {@code + * shouldSuppressWarnings()}. + * + * @param src the position object to test; may be an Element, a Tree, or null + * @param errKey the error key the checker is emitting + * @return true if all warnings pertaining to the given source should be suppressed + * @see #shouldSuppressWarnings(Element, String) + * @see #shouldSuppressWarnings(Tree, String) + */ + private boolean shouldSuppressWarnings(@Nullable Object src, String errKey) { + if (src instanceof Element) { + return shouldSuppressWarnings((Element) src, errKey); + } else if (src instanceof Tree) { + return shouldSuppressWarnings((Tree) src, errKey); + } else if (src == null) { + return false; + } else { + throw new BugInCF("Unexpected source " + src); + } + } + + /** + * Returns true if all the warnings pertaining to a given tree should be suppressed. Returns true + * if the tree is within the scope of a @SuppressWarnings annotation, one of whose values + * suppresses the checker's warning. Also, returns true if the {@code errKey} matches a string in + * {@code -AsuppressWarnings}. + * + * @param tree the tree that might be a source of a warning + * @param errKey the error key the checker is emitting + * @return true if no warning should be emitted for the given tree because it is contained by a + * declaration with an appropriately-valued {@literal @}SuppressWarnings annotation; false + * otherwise + */ + public boolean shouldSuppressWarnings(Tree tree, String errKey) { + Collection prefixes = getSuppressWarningsPrefixes(); + if (prefixes.isEmpty() || (prefixes.contains(SUPPRESS_ALL_PREFIX) && prefixes.size() == 1)) { + throw new BugInCF( + "Checker must provide a SuppressWarnings prefix." + + " SourceChecker#getSuppressWarningsPrefixes was not overridden" + + " correctly."); + } + + if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { + // If the error key matches a warning string in the -AsuppressWarnings, then suppress + // the warning. + return true; + } + + TreePath path = getTreePathCacher().getPath(currentRoot, tree); + return shouldSuppressWarnings(path, errKey); + } + + /** + * Returns true if all the warnings pertaining to a given tree path should be suppressed. Returns + * true if the path is within the scope of a @SuppressWarnings annotation, one of whose values + * suppresses the checker's warning. + * + * @param path the TreePath that might be a source of, or related to, a warning + * @param errKey the error key the checker is emitting + * @return true if no warning should be emitted for the given path because it is contained by a + * declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false + * otherwise + */ + public boolean shouldSuppressWarnings(@Nullable TreePath path, String errKey) { + if (path == null) { + return false; + } + + // iterate through the path; continue until path contains no declarations + for (TreePath declPath = TreePathUtil.enclosingDeclarationPath(path); + declPath != null; + declPath = TreePathUtil.enclosingDeclarationPath(declPath.getParentPath())) { + + Tree decl = declPath.getLeaf(); + + if (decl.getKind() == Tree.Kind.VARIABLE) { + Element elt = TreeUtils.elementFromDeclaration((VariableTree) decl); + if (shouldSuppressWarnings(elt, errKey)) { + return true; + } + } else if (decl.getKind() == Tree.Kind.METHOD) { + Element elt = TreeUtils.elementFromDeclaration((MethodTree) decl); + if (shouldSuppressWarnings(elt, errKey)) { + return true; + } + + if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + // Return false immediately. Do NOT check for AnnotatedFor in the enclosing + // elements as the closest AnnotatedFor is already found. + return false; + } + } else if (TreeUtils.classTreeKinds().contains(decl.getKind())) { + // A class tree + Element elt = TreeUtils.elementFromDeclaration((ClassTree) decl); + if (shouldSuppressWarnings(elt, errKey)) { + return true; + } + + if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + // Return false immediately. Do NOT check for AnnotatedFor in the enclosing + // elements as the closest AnnotatedFor is already found. + return false; + } + Element packageElement = elt.getEnclosingElement(); + if (packageElement != null && packageElement.getKind() == ElementKind.PACKAGE) { + if (shouldSuppressWarnings(packageElement, errKey)) { return true; - } - - TreePath path = getTreePathCacher().getPath(currentRoot, tree); - return shouldSuppressWarnings(path, errKey); - } - - /** - * Returns true if all the warnings pertaining to a given tree path should be suppressed. - * Returns true if the path is within the scope of a @SuppressWarnings annotation, one of whose - * values suppresses the checker's warning. - * - * @param path the TreePath that might be a source of, or related to, a warning - * @param errKey the error key the checker is emitting - * @return true if no warning should be emitted for the given path because it is contained by a - * declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false - * otherwise - */ - public boolean shouldSuppressWarnings(@Nullable TreePath path, String errKey) { - if (path == null) { + } + if (isAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { + // Return false immediately. Do NOT check for AnnotatedFor in the enclosing + // elements as the closest AnnotatedFor is already found. return false; - } - - // iterate through the path; continue until path contains no declarations - for (TreePath declPath = TreePathUtil.enclosingDeclarationPath(path); - declPath != null; - declPath = TreePathUtil.enclosingDeclarationPath(declPath.getParentPath())) { - - Tree decl = declPath.getLeaf(); - - if (decl.getKind() == Tree.Kind.VARIABLE) { - Element elt = TreeUtils.elementFromDeclaration((VariableTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { - return true; - } - } else if (decl.getKind() == Tree.Kind.METHOD) { - Element elt = TreeUtils.elementFromDeclaration((MethodTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { - return true; - } - - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; - } - } else if (TreeUtils.classTreeKinds().contains(decl.getKind())) { - // A class tree - Element elt = TreeUtils.elementFromDeclaration((ClassTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { - return true; - } - - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; - } - Element packageElement = elt.getEnclosingElement(); - if (packageElement != null && packageElement.getKind() == ElementKind.PACKAGE) { - if (shouldSuppressWarnings(packageElement, errKey)) { - return true; - } - if (isAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; - } - } - } else { - throw new BugInCF("Unexpected declaration kind: " + decl.getKind() + " " + decl); - } - } - - if (useConservativeDefault("source")) { - // If we got this far without hitting an @AnnotatedFor and returning - // false, we DO suppress the warning. - return true; - } - + } + } + } else { + throw new BugInCF("Unexpected declaration kind: " + decl.getKind() + " " + decl); + } + } + + if (useConservativeDefault("source")) { + // If we got this far without hitting an @AnnotatedFor and returning + // false, we DO suppress the warning. + return true; + } + + return false; + } + + /** + * Should conservative defaults be used for the kind of unchecked code indicated by the parameter? + * + * @param kindOfCode source or bytecode + * @return whether conservative defaults should be used + */ + public boolean useConservativeDefault(String kindOfCode) { + boolean useUncheckedDefaultsForSource = false; + boolean useUncheckedDefaultsForByteCode = false; + for (String arg : this.getStringsOption("useConservativeDefaultsForUncheckedCode", ',')) { + boolean value = arg.indexOf("-") != 0; + arg = value ? arg : arg.substring(1); + if (arg.equals(kindOfCode)) { + return value; + } + } + if (kindOfCode.equals("source")) { + return useUncheckedDefaultsForSource; + } else if (kindOfCode.equals("bytecode")) { + return useUncheckedDefaultsForByteCode; + } else { + throw new UserError( + "SourceChecker: unexpected argument to useConservativeDefault: " + kindOfCode); + } + } + + /** + * Elements with a {@code @SuppressWarnings} that actually suppressed a warning for this checker. + */ + protected final Set elementsWithSuppressedWarnings = new HashSet<>(); + + /** + * Returns true if all the warnings pertaining to a given element should be suppressed. Returns + * true if the element is within the scope of a @SuppressWarnings annotation, one of whose values + * suppresses all the checker's warnings. + * + * @param elt the Element that might be a source of, or related to, a warning + * @param errKey the error key the checker is emitting + * @return true if no warning should be emitted for the given Element because it is contained by a + * declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false + * otherwise + */ + public boolean shouldSuppressWarnings(@Nullable Element elt, String errKey) { + if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { + return true; + } + + for (Element currElt = elt; currElt != null; currElt = currElt.getEnclosingElement()) { + SuppressWarnings suppressWarningsAnno = currElt.getAnnotation(SuppressWarnings.class); + if (suppressWarningsAnno != null) { + String[] suppressWarningsStrings = suppressWarningsAnno.value(); + if (shouldSuppress(suppressWarningsStrings, errKey)) { + if (warnUnneededSuppressions) { + elementsWithSuppressedWarnings.add(currElt); + } + return true; + } + } + if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + // Return false immediately. Do NOT check for AnnotatedFor in the + // enclosing elements, because they may not have an @AnnotatedFor. return false; - } - - /** - * Should conservative defaults be used for the kind of unchecked code indicated by the - * parameter? - * - * @param kindOfCode source or bytecode - * @return whether conservative defaults should be used - */ - public boolean useConservativeDefault(String kindOfCode) { - boolean useUncheckedDefaultsForSource = false; - boolean useUncheckedDefaultsForByteCode = false; - for (String arg : this.getStringsOption("useConservativeDefaultsForUncheckedCode", ',')) { - boolean value = arg.indexOf("-") != 0; - arg = value ? arg : arg.substring(1); - if (arg.equals(kindOfCode)) { - return value; - } - } - if (kindOfCode.equals("source")) { - return useUncheckedDefaultsForSource; - } else if (kindOfCode.equals("bytecode")) { - return useUncheckedDefaultsForByteCode; - } else { - throw new UserError( - "SourceChecker: unexpected argument to useConservativeDefault: " + kindOfCode); - } - } - - /** - * Elements with a {@code @SuppressWarnings} that actually suppressed a warning for this - * checker. - */ - protected final Set elementsWithSuppressedWarnings = new HashSet<>(); - - /** - * Returns true if all the warnings pertaining to a given element should be suppressed. Returns - * true if the element is within the scope of a @SuppressWarnings annotation, one of whose - * values suppresses all the checker's warnings. - * - * @param elt the Element that might be a source of, or related to, a warning - * @param errKey the error key the checker is emitting - * @return true if no warning should be emitted for the given Element because it is contained by - * a declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false - * otherwise - */ - public boolean shouldSuppressWarnings(@Nullable Element elt, String errKey) { - if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { - return true; - } - - for (Element currElt = elt; currElt != null; currElt = currElt.getEnclosingElement()) { - SuppressWarnings suppressWarningsAnno = currElt.getAnnotation(SuppressWarnings.class); - if (suppressWarningsAnno != null) { - String[] suppressWarningsStrings = suppressWarningsAnno.value(); - if (shouldSuppress(suppressWarningsStrings, errKey)) { - if (warnUnneededSuppressions) { - elementsWithSuppressedWarnings.add(currElt); - } - return true; - } - } - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the - // enclosing elements, because they may not have an @AnnotatedFor. - return false; - } - } - return false; - } - - /** - * Returns true if an error (whose message key is {@code messageKey}) should be suppressed. It - * is suppressed if any of the given SuppressWarnings strings suppresses it. - * - *

A SuppressWarnings string may be of the following pattern: - * - *

    - *
  1. {@code "prefix"}, where prefix is a SuppressWarnings prefix, as specified by {@link - * #getSuppressWarningsPrefixes()}. For example, {@code "nullness"} and {@code - * "initialization"} for the Nullness Checker, {@code "regex"} for the Regex Checker. - *
  2. {@code "partial-message-key"}, where partial-message-key is a prefix or suffix of the - * message key that it may suppress. So "generic.argument" would suppress any errors whose - * message key contains "generic.argument". - *
  3. {@code "prefix:partial-message-key}, where the prefix and partial-message-key is as - * above. So "nullness:generic.argument", would suppress any errors in the Nullness - * Checker with a message key that contains "generic.argument". - *
- * - * {@code "allcheckers"} is a prefix that suppresses a warning from any checker. {@code "all"} - * is a partial-message-key that suppresses a warning with any message key. - * - *

If the {@code -ArequirePrefixInWarningSuppressions} command-line argument was supplied, - * then {@code "partial-message-key"} has no effect; {@code "prefix"} and {@code - * "prefix:partial-message-key"} are the only SuppressWarnings strings that have an effect. - * - * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, - * in which case this method returns false. - * @param messageKey the message key of the error the checker is emitting; a lowercase string, - * without any "checkername:" prefix - * @return true if an element of {@code suppressWarningsInEffect} suppresses the error - */ - private boolean shouldSuppress( - String @Nullable [] suppressWarningsInEffect, String messageKey) { - Set prefixes = this.getSuppressWarningsPrefixes(); - return shouldSuppress(prefixes, suppressWarningsInEffect, messageKey); - } - - /** - * Helper method for {@link #shouldSuppress(String[], String)}. - * - * @param prefixes the SuppressWarnings prefixes used by this checker - * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, - * in which case this method returns false. - * @param messageKey the message key of the error the checker is emitting; a lowercase string, - * without any "checkername:" prefix - * @return true if one of the {@code suppressWarningsInEffect} suppresses the error - */ - private boolean shouldSuppress( - Set prefixes, String @Nullable [] suppressWarningsInEffect, String messageKey) { - if (suppressWarningsInEffect == null) { - return false; - } - - for (String currentSuppressWarningsInEffect : suppressWarningsInEffect) { - int colonPos = currentSuppressWarningsInEffect.indexOf(":"); - String messageKeyInSuppressWarningsString; - if (colonPos == -1) { - // The SuppressWarnings string has no colon, so it is not of the form - // prefix:partial-message-key. - if (prefixes.contains(currentSuppressWarningsInEffect)) { - // The value in the @SuppressWarnings is exactly a prefix. - // Suppress the warning unless its message key is "unneeded.suppression". - boolean result = - !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); - return result; - } else if (requirePrefixInWarningSuppressions) { - // A prefix is required, but this SuppressWarnings string does not have a - // prefix; check the next SuppressWarnings string. - continue; - } else if (currentSuppressWarningsInEffect.equals(SUPPRESS_ALL_MESSAGE_KEY)) { - // Prefixes aren't required and the SuppressWarnings string is "all". - // Suppress the warning unless its message key is "unneeded.suppression". - boolean result = - !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); - return result; - } - // The currentSuppressWarningsInEffect is not a prefix or a prefix:message-key, so - // it might be a message key. - messageKeyInSuppressWarningsString = currentSuppressWarningsInEffect; - } else { - // The SuppressWarnings string has a colon; that is, it has a prefix. - String currentSuppressWarningsPrefix = - currentSuppressWarningsInEffect.substring(0, colonPos); - if (!prefixes.contains(currentSuppressWarningsPrefix)) { - // The prefix of this SuppressWarnings string is a not a prefix supported by - // this checker. Proceed to the next SuppressWarnings string. - continue; - } - messageKeyInSuppressWarningsString = - currentSuppressWarningsInEffect.substring(colonPos + 1); - } - // Check if the message key in the warning suppression is part of the message key that - // the checker is emiting. - if (messageKeyMatches(messageKey, messageKeyInSuppressWarningsString)) { - return true; - } - } - - // None of the SuppressWarnings strings suppresses this error. - return false; - } - - /** - * Does the given messageKey match a messageKey that appears in a SuppressWarnings? Subclasses - * should override this method if they need additional logic to compare message keys. - * - * @param messageKey the message key - * @param messageKeyInSuppressWarningsString the message key in a SuppressWarnings - * @return true if the arguments match - */ - protected boolean messageKeyMatches( - String messageKey, String messageKeyInSuppressWarningsString) { - return messageKey.equals(messageKeyInSuppressWarningsString) - || messageKey.startsWith(messageKeyInSuppressWarningsString + ".") - || messageKey.endsWith("." + messageKeyInSuppressWarningsString) - || messageKey.contains("." + messageKeyInSuppressWarningsString + "."); - } - - /** - * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an - * upstream checker that called this one. - * - * @param elt the source code element to check, or null - * @return true if the element is annotated for this checker or an upstream checker - */ - private boolean isAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { - if (elt == null || !useConservativeDefault("source")) { - return false; - } - - AnnotatedFor anno = elt.getAnnotation(AnnotatedFor.class); - - String[] userAnnotatedFors = (anno == null ? null : anno.value()); - - if (userAnnotatedFors != null) { - List<@FullyQualifiedName String> upstreamCheckerNames = getUpstreamCheckerNames(); - - for (String userAnnotatedFor : userAnnotatedFors) { - if (CheckerMain.matchesCheckerOrSubcheckerFromList( - userAnnotatedFor, upstreamCheckerNames)) { - return true; - } - } - } - - return false; - } - - /** - * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings - * strings. - * - *

The collection must not be empty and must not contain only {@link #SUPPRESS_ALL_PREFIX}. - * - * @return non-empty modifiable set of lower-case prefixes for SuppressWarnings strings - */ - public NavigableSet getSuppressWarningsPrefixes() { - return getStandardSuppressWarningsPrefixes(); - } - - /** - * Returns a sorted set of SuppressWarnings prefixes read from the {@link - * SuppressWarningsPrefix} meta-annotation on the checker class. Or if no {@link - * SuppressWarningsPrefix} is used, the checker name is used. {@link #SUPPRESS_ALL_PREFIX} is - * also added, at the end, unless {@link #useAllcheckersPrefix} is false. - * - * @return a sorted set of SuppressWarnings prefixes - */ - protected final NavigableSet getStandardSuppressWarningsPrefixes() { - NavigableSet prefixes = new TreeSet<>(); - if (useAllcheckersPrefix) { - prefixes.add(SUPPRESS_ALL_PREFIX); - } - SuppressWarningsPrefix prefixMetaAnno = - this.getClass().getAnnotation(SuppressWarningsPrefix.class); - if (prefixMetaAnno != null) { - for (String prefix : prefixMetaAnno.value()) { - prefixes.add(prefix); - } - return prefixes; - } - - // No @SuppressWarningsPrefixes annotation, by default infer key from class name. - String defaultPrefix = getDefaultSuppressWarningsPrefix(); - prefixes.add(defaultPrefix); - return prefixes; - } - - /** - * Returns the default SuppressWarnings prefix for this checker based on the checker name. - * - * @return the default SuppressWarnings prefix for this checker based on the checker name - */ - private String getDefaultSuppressWarningsPrefix() { - String className = this.getClass().getSimpleName(); - int indexOfChecker = className.lastIndexOf("Checker"); - if (indexOfChecker == -1) { - indexOfChecker = className.lastIndexOf("Subchecker"); - } - String result = (indexOfChecker == -1) ? className : className.substring(0, indexOfChecker); - return result.toLowerCase(Locale.ROOT); - } - - /** - * Returns the prefix that should be added when issuing an error or warning if the {@code - * -AshowPrefixInWarningMessages} command-line option was passed. - * - *

The default implementation uses the default prefix based on the class name if that default - * prefix is contained in {@link #getSuppressWarningsPrefixes()}. Otherwise, it uses the first - * element of {@link #getSuppressWarningsPrefixes()}. - * - * @return the prefix that should be added when issuing an error or warning if the * {@code - * -AshowPrefixInWarningMessages} command-line option was passed - */ - protected String getWarningMessagePrefix() { - Collection prefixes = this.getSuppressWarningsPrefixes(); - prefixes.remove(SUPPRESS_ALL_PREFIX); - String defaultPrefix = getDefaultSuppressWarningsPrefix(); - if (prefixes.contains(defaultPrefix)) { - return defaultPrefix; - } else { - String firstKey = prefixes.iterator().next(); - return firstKey; - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Skipping uses and defs - /// - - /** - * Tests whether the class owner of the passed element is an unannotated class and matches the - * pattern specified in the {@code checker.skipUses} property. - * - * @param element an element - * @return true iff the enclosing class of element should be skipped - */ - public final boolean shouldSkipUses(@Nullable Element element) { - if (element == null) { - return false; - } - TypeElement typeElement = ElementUtils.enclosingTypeElement(element); - if (typeElement == null) { - throw new BugInCF( - "enclosingTypeElement(%s [%s]) => null%n", element, element.getClass()); - } - @SuppressWarnings("signature:assignment.type.incompatible" // TypeElement.toString(): - // @FullyQualifiedName - ) - @FullyQualifiedName String name = typeElement.toString(); - return shouldSkipUses(name); - } - - /** - * Tests whether the class owner of the passed type matches the pattern specified in the {@code - * checker.skipUses} property. In contrast to {@link #shouldSkipUses(Element)} this version can - * also be used from primitive types, which don't have an element. - * - *

Checkers that require their annotations not to be checked on certain JDK classes may - * override this method to skip them. They shall call {@code super.shouldSkipUses(typeName)} to - * also skip the classes matching the pattern. - * - * @param typeName the fully-qualified name of a type - * @return true iff the enclosing class of element should be skipped - */ - public boolean shouldSkipUses(@FullyQualifiedName String typeName) { - // System.out.printf("shouldSkipUses(%s) %s%nskipUses %s%nonlyUses %s%nresult %s%n", - // element, - // name, - // skipUsesPattern.matcher(name).find(), - // onlyUsesPattern.matcher(name).find(), - // (skipUsesPattern.matcher(name).find() - // || ! onlyUsesPattern.matcher(name).find())); - // StackTraceElement[] stea = new Throwable().getStackTrace(); - // for (int i=0; i<3; i++) { - // System.out.println(" " + stea[i]); - // } - // System.out.println(); - if (skipUsesPattern == null) { - skipUsesPattern = getSkipUsesPattern(getOptions()); - } - if (onlyUsesPattern == null) { - onlyUsesPattern = getOnlyUsesPattern(getOptions()); - } - return skipUsesPattern.matcher(typeName).find() - || !onlyUsesPattern.matcher(typeName).find(); - } - - /** - * Tests whether the class definition should not be checked because it matches the {@code - * checker.skipDefs} property. - * - * @param tree class to potentially skip - * @return true if checker should not test {@code tree} - */ - public boolean shouldSkipDefs(ClassTree tree) { - String qualifiedName = TreeUtils.typeOf(tree).toString(); - // System.out.printf("shouldSkipDefs(%s) %s%nskipDefs %s%nonlyDefs %s%nresult %s%n%n", - // tree, - // qualifiedName, - // skipDefsPattern.matcher(qualifiedName).find(), - // onlyDefsPattern.matcher(qualifiedName).find(), - // (skipDefsPattern.matcher(qualifiedName).find() - // || ! onlyDefsPattern.matcher(qualifiedName).find())); - if (skipDefsPattern == null) { - skipDefsPattern = getSkipDefsPattern(getOptions()); - } - if (onlyDefsPattern == null) { - onlyDefsPattern = getOnlyDefsPattern(getOptions()); - } - - return skipDefsPattern.matcher(qualifiedName).find() - || !onlyDefsPattern.matcher(qualifiedName).find(); - } - - /** - * Tests whether the method definition should not be checked because it matches the {@code - * checker.skipDefs} property. - * - *

TODO: currently only uses the class definition. Refine pattern. Same for skipUses. - * - * @param cls class to potentially skip - * @param meth method to potentially skip - * @return true if checker should not test {@code meth} - */ - public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { - return shouldSkipDefs(cls); - } - - /////////////////////////////////////////////////////////////////////////// - /// Errors other than type-checking errors - /// - - /** - * Log (that is, print) a user error. - * - * @param ce the user error to output - */ - private void logUserError(UserError ce) { - String msg = ce.getMessage(); - printMessage(msg); - } - - /** - * Log (that is, print) a type system error. - * - * @param ce the type system error to output - */ - private void logTypeSystemError(TypeSystemError ce) { - logBug( - ce, - "A type system implementation is buggy. Please report the crash to the maintainer."); - } - - /** - * Log (that is, print) an internal error in the framework or a checker. - * - * @param ce the internal error to output - */ - private void logBugInCF(BugInCF ce) { - String checkerVersion; - checkerVersion = getCheckerVersion(); - String msg = "The Checker Framework crashed. Please report the crash. "; - if (checkerVersion != null) { - msg += String.format("Version: Checker Framework %s. ", checkerVersion); - } - logBug(ce, msg); - } - - /** - * Log (that is, print) an internal error in the framework or a checker. - * - * @param ce the internal error to output - * @param culprit a message to print about the cause - */ - private void logBug(Throwable ce, String culprit) { - StringJoiner msg = new StringJoiner(System.lineSeparator()); - if (ce.getCause() != null && ce.getCause() instanceof OutOfMemoryError) { - msg.add( - String.format( - "OutOfMemoryError (max memory = %d, total memory = %d, free memory =" - + " %d)", - Runtime.getRuntime().maxMemory(), - Runtime.getRuntime().totalMemory(), - Runtime.getRuntime().freeMemory())); + } + } + return false; + } + + /** + * Returns true if an error (whose message key is {@code messageKey}) should be suppressed. It is + * suppressed if any of the given SuppressWarnings strings suppresses it. + * + *

A SuppressWarnings string may be of the following pattern: + * + *

    + *
  1. {@code "prefix"}, where prefix is a SuppressWarnings prefix, as specified by {@link + * #getSuppressWarningsPrefixes()}. For example, {@code "nullness"} and {@code + * "initialization"} for the Nullness Checker, {@code "regex"} for the Regex Checker. + *
  2. {@code "partial-message-key"}, where partial-message-key is a prefix or suffix of the + * message key that it may suppress. So "generic.argument" would suppress any errors whose + * message key contains "generic.argument". + *
  3. {@code "prefix:partial-message-key}, where the prefix and partial-message-key is as + * above. So "nullness:generic.argument", would suppress any errors in the Nullness Checker + * with a message key that contains "generic.argument". + *
+ * + * {@code "allcheckers"} is a prefix that suppresses a warning from any checker. {@code "all"} is + * a partial-message-key that suppresses a warning with any message key. + * + *

If the {@code -ArequirePrefixInWarningSuppressions} command-line argument was supplied, then + * {@code "partial-message-key"} has no effect; {@code "prefix"} and {@code + * "prefix:partial-message-key"} are the only SuppressWarnings strings that have an effect. + * + * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, + * in which case this method returns false. + * @param messageKey the message key of the error the checker is emitting; a lowercase string, + * without any "checkername:" prefix + * @return true if an element of {@code suppressWarningsInEffect} suppresses the error + */ + private boolean shouldSuppress(String @Nullable [] suppressWarningsInEffect, String messageKey) { + Set prefixes = this.getSuppressWarningsPrefixes(); + return shouldSuppress(prefixes, suppressWarningsInEffect, messageKey); + } + + /** + * Helper method for {@link #shouldSuppress(String[], String)}. + * + * @param prefixes the SuppressWarnings prefixes used by this checker + * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, + * in which case this method returns false. + * @param messageKey the message key of the error the checker is emitting; a lowercase string, + * without any "checkername:" prefix + * @return true if one of the {@code suppressWarningsInEffect} suppresses the error + */ + private boolean shouldSuppress( + Set prefixes, String @Nullable [] suppressWarningsInEffect, String messageKey) { + if (suppressWarningsInEffect == null) { + return false; + } + + for (String currentSuppressWarningsInEffect : suppressWarningsInEffect) { + int colonPos = currentSuppressWarningsInEffect.indexOf(":"); + String messageKeyInSuppressWarningsString; + if (colonPos == -1) { + // The SuppressWarnings string has no colon, so it is not of the form + // prefix:partial-message-key. + if (prefixes.contains(currentSuppressWarningsInEffect)) { + // The value in the @SuppressWarnings is exactly a prefix. + // Suppress the warning unless its message key is "unneeded.suppression". + boolean result = !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); + return result; + } else if (requirePrefixInWarningSuppressions) { + // A prefix is required, but this SuppressWarnings string does not have a + // prefix; check the next SuppressWarnings string. + continue; + } else if (currentSuppressWarningsInEffect.equals(SUPPRESS_ALL_MESSAGE_KEY)) { + // Prefixes aren't required and the SuppressWarnings string is "all". + // Suppress the warning unless its message key is "unneeded.suppression". + boolean result = !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); + return result; + } + // The currentSuppressWarningsInEffect is not a prefix or a prefix:message-key, so + // it might be a message key. + messageKeyInSuppressWarningsString = currentSuppressWarningsInEffect; + } else { + // The SuppressWarnings string has a colon; that is, it has a prefix. + String currentSuppressWarningsPrefix = + currentSuppressWarningsInEffect.substring(0, colonPos); + if (!prefixes.contains(currentSuppressWarningsPrefix)) { + // The prefix of this SuppressWarnings string is a not a prefix supported by + // this checker. Proceed to the next SuppressWarnings string. + continue; + } + messageKeyInSuppressWarningsString = + currentSuppressWarningsInEffect.substring(colonPos + 1); + } + // Check if the message key in the warning suppression is part of the message key that + // the checker is emiting. + if (messageKeyMatches(messageKey, messageKeyInSuppressWarningsString)) { + return true; + } + } + + // None of the SuppressWarnings strings suppresses this error. + return false; + } + + /** + * Does the given messageKey match a messageKey that appears in a SuppressWarnings? Subclasses + * should override this method if they need additional logic to compare message keys. + * + * @param messageKey the message key + * @param messageKeyInSuppressWarningsString the message key in a SuppressWarnings + * @return true if the arguments match + */ + protected boolean messageKeyMatches( + String messageKey, String messageKeyInSuppressWarningsString) { + return messageKey.equals(messageKeyInSuppressWarningsString) + || messageKey.startsWith(messageKeyInSuppressWarningsString + ".") + || messageKey.endsWith("." + messageKeyInSuppressWarningsString) + || messageKey.contains("." + messageKeyInSuppressWarningsString + "."); + } + + /** + * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an + * upstream checker that called this one. + * + * @param elt the source code element to check, or null + * @return true if the element is annotated for this checker or an upstream checker + */ + private boolean isAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { + if (elt == null || !useConservativeDefault("source")) { + return false; + } + + AnnotatedFor anno = elt.getAnnotation(AnnotatedFor.class); + + String[] userAnnotatedFors = (anno == null ? null : anno.value()); + + if (userAnnotatedFors != null) { + List<@FullyQualifiedName String> upstreamCheckerNames = getUpstreamCheckerNames(); + + for (String userAnnotatedFor : userAnnotatedFors) { + if (CheckerMain.matchesCheckerOrSubcheckerFromList( + userAnnotatedFor, upstreamCheckerNames)) { + return true; + } + } + } + + return false; + } + + /** + * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings strings. + * + *

The collection must not be empty and must not contain only {@link #SUPPRESS_ALL_PREFIX}. + * + * @return non-empty modifiable set of lower-case prefixes for SuppressWarnings strings + */ + public NavigableSet getSuppressWarningsPrefixes() { + return getStandardSuppressWarningsPrefixes(); + } + + /** + * Returns a sorted set of SuppressWarnings prefixes read from the {@link SuppressWarningsPrefix} + * meta-annotation on the checker class. Or if no {@link SuppressWarningsPrefix} is used, the + * checker name is used. {@link #SUPPRESS_ALL_PREFIX} is also added, at the end, unless {@link + * #useAllcheckersPrefix} is false. + * + * @return a sorted set of SuppressWarnings prefixes + */ + protected final NavigableSet getStandardSuppressWarningsPrefixes() { + NavigableSet prefixes = new TreeSet<>(); + if (useAllcheckersPrefix) { + prefixes.add(SUPPRESS_ALL_PREFIX); + } + SuppressWarningsPrefix prefixMetaAnno = + this.getClass().getAnnotation(SuppressWarningsPrefix.class); + if (prefixMetaAnno != null) { + for (String prefix : prefixMetaAnno.value()) { + prefixes.add(prefix); + } + return prefixes; + } + + // No @SuppressWarningsPrefixes annotation, by default infer key from class name. + String defaultPrefix = getDefaultSuppressWarningsPrefix(); + prefixes.add(defaultPrefix); + return prefixes; + } + + /** + * Returns the default SuppressWarnings prefix for this checker based on the checker name. + * + * @return the default SuppressWarnings prefix for this checker based on the checker name + */ + private String getDefaultSuppressWarningsPrefix() { + String className = this.getClass().getSimpleName(); + int indexOfChecker = className.lastIndexOf("Checker"); + if (indexOfChecker == -1) { + indexOfChecker = className.lastIndexOf("Subchecker"); + } + String result = (indexOfChecker == -1) ? className : className.substring(0, indexOfChecker); + return result.toLowerCase(Locale.ROOT); + } + + /** + * Returns the prefix that should be added when issuing an error or warning if the {@code + * -AshowPrefixInWarningMessages} command-line option was passed. + * + *

The default implementation uses the default prefix based on the class name if that default + * prefix is contained in {@link #getSuppressWarningsPrefixes()}. Otherwise, it uses the first + * element of {@link #getSuppressWarningsPrefixes()}. + * + * @return the prefix that should be added when issuing an error or warning if the * {@code + * -AshowPrefixInWarningMessages} command-line option was passed + */ + protected String getWarningMessagePrefix() { + Collection prefixes = this.getSuppressWarningsPrefixes(); + prefixes.remove(SUPPRESS_ALL_PREFIX); + String defaultPrefix = getDefaultSuppressWarningsPrefix(); + if (prefixes.contains(defaultPrefix)) { + return defaultPrefix; + } else { + String firstKey = prefixes.iterator().next(); + return firstKey; + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Skipping uses and defs + /// + + /** + * Tests whether the class owner of the passed element is an unannotated class and matches the + * pattern specified in the {@code checker.skipUses} property. + * + * @param element an element + * @return true iff the enclosing class of element should be skipped + */ + public final boolean shouldSkipUses(@Nullable Element element) { + if (element == null) { + return false; + } + TypeElement typeElement = ElementUtils.enclosingTypeElement(element); + if (typeElement == null) { + throw new BugInCF("enclosingTypeElement(%s [%s]) => null%n", element, element.getClass()); + } + @SuppressWarnings("signature:assignment.type.incompatible" // TypeElement.toString(): + // @FullyQualifiedName + ) + @FullyQualifiedName String name = typeElement.toString(); + return shouldSkipUses(name); + } + + /** + * Tests whether the class owner of the passed type matches the pattern specified in the {@code + * checker.skipUses} property. In contrast to {@link #shouldSkipUses(Element)} this version can + * also be used from primitive types, which don't have an element. + * + *

Checkers that require their annotations not to be checked on certain JDK classes may + * override this method to skip them. They shall call {@code super.shouldSkipUses(typeName)} to + * also skip the classes matching the pattern. + * + * @param typeName the fully-qualified name of a type + * @return true iff the enclosing class of element should be skipped + */ + public boolean shouldSkipUses(@FullyQualifiedName String typeName) { + // System.out.printf("shouldSkipUses(%s) %s%nskipUses %s%nonlyUses %s%nresult %s%n", + // element, + // name, + // skipUsesPattern.matcher(name).find(), + // onlyUsesPattern.matcher(name).find(), + // (skipUsesPattern.matcher(name).find() + // || ! onlyUsesPattern.matcher(name).find())); + // StackTraceElement[] stea = new Throwable().getStackTrace(); + // for (int i=0; i<3; i++) { + // System.out.println(" " + stea[i]); + // } + // System.out.println(); + if (skipUsesPattern == null) { + skipUsesPattern = getSkipUsesPattern(getOptions()); + } + if (onlyUsesPattern == null) { + onlyUsesPattern = getOnlyUsesPattern(getOptions()); + } + return skipUsesPattern.matcher(typeName).find() || !onlyUsesPattern.matcher(typeName).find(); + } + + /** + * Tests whether the class definition should not be checked because it matches the {@code + * checker.skipDefs} property. + * + * @param tree class to potentially skip + * @return true if checker should not test {@code tree} + */ + public boolean shouldSkipDefs(ClassTree tree) { + String qualifiedName = TreeUtils.typeOf(tree).toString(); + // System.out.printf("shouldSkipDefs(%s) %s%nskipDefs %s%nonlyDefs %s%nresult %s%n%n", + // tree, + // qualifiedName, + // skipDefsPattern.matcher(qualifiedName).find(), + // onlyDefsPattern.matcher(qualifiedName).find(), + // (skipDefsPattern.matcher(qualifiedName).find() + // || ! onlyDefsPattern.matcher(qualifiedName).find())); + if (skipDefsPattern == null) { + skipDefsPattern = getSkipDefsPattern(getOptions()); + } + if (onlyDefsPattern == null) { + onlyDefsPattern = getOnlyDefsPattern(getOptions()); + } + + return skipDefsPattern.matcher(qualifiedName).find() + || !onlyDefsPattern.matcher(qualifiedName).find(); + } + + /** + * Tests whether the method definition should not be checked because it matches the {@code + * checker.skipDefs} property. + * + *

TODO: currently only uses the class definition. Refine pattern. Same for skipUses. + * + * @param cls class to potentially skip + * @param meth method to potentially skip + * @return true if checker should not test {@code meth} + */ + public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { + return shouldSkipDefs(cls); + } + + /////////////////////////////////////////////////////////////////////////// + /// Errors other than type-checking errors + /// + + /** + * Log (that is, print) a user error. + * + * @param ce the user error to output + */ + private void logUserError(UserError ce) { + String msg = ce.getMessage(); + printMessage(msg); + } + + /** + * Log (that is, print) a type system error. + * + * @param ce the type system error to output + */ + private void logTypeSystemError(TypeSystemError ce) { + logBug( + ce, "A type system implementation is buggy. Please report the crash to the maintainer."); + } + + /** + * Log (that is, print) an internal error in the framework or a checker. + * + * @param ce the internal error to output + */ + private void logBugInCF(BugInCF ce) { + String checkerVersion; + checkerVersion = getCheckerVersion(); + String msg = "The Checker Framework crashed. Please report the crash. "; + if (checkerVersion != null) { + msg += String.format("Version: Checker Framework %s. ", checkerVersion); + } + logBug(ce, msg); + } + + /** + * Log (that is, print) an internal error in the framework or a checker. + * + * @param ce the internal error to output + * @param culprit a message to print about the cause + */ + private void logBug(Throwable ce, String culprit) { + StringJoiner msg = new StringJoiner(System.lineSeparator()); + if (ce.getCause() != null && ce.getCause() instanceof OutOfMemoryError) { + msg.add( + String.format( + "OutOfMemoryError (max memory = %d, total memory = %d, free memory =" + " %d)", + Runtime.getRuntime().maxMemory(), + Runtime.getRuntime().totalMemory(), + Runtime.getRuntime().freeMemory())); + } else { + msg.add(ce.getMessage()); + boolean noPrintErrorStack = + (processingEnv != null + && processingEnv.getOptions() != null + && processingEnv.getOptions().containsKey("noPrintErrorStack")); + + msg.add("; " + culprit); + if (noPrintErrorStack) { + msg.add( + " To see the full stack trace, don't invoke the compiler with" + + " -AnoPrintErrorStack"); + } else { + msg.add("Checker: " + this.getClass()); + if (this.visitor != null) { + msg.add("Visitor: " + this.visitor.getClass()); + } + if (this.currentRoot != null && this.currentRoot.getSourceFile() != null) { + msg.add("Compilation unit: " + this.currentRoot.getSourceFile().getName()); + } + + if (this.visitor != null) { + DiagnosticPosition pos = (DiagnosticPosition) this.visitor.lastVisited; + if (pos != null) { + DiagnosticSource source = new DiagnosticSource(this.currentRoot.getSourceFile(), null); + int linenr = source.getLineNumber(pos.getStartPosition()); + int col = source.getColumnNumber(pos.getStartPosition(), true); + String line = source.getLine(pos.getStartPosition()); + + msg.add("Last visited tree at line " + linenr + " column " + col + ":"); + msg.add(line); + } + } + + Throwable forStackTrace = ce.getCause() != null ? ce.getCause() : ce; + if (forStackTrace != null) { + msg.add( + "Exception: " + forStackTrace + "; " + UtilPlume.stackTraceToString(forStackTrace)); + boolean printClasspath = forStackTrace instanceof NoClassDefFoundError; + Throwable cause = forStackTrace.getCause(); + while (cause != null) { + msg.add("Underlying Exception: " + cause + "; " + UtilPlume.stackTraceToString(cause)); + printClasspath |= cause instanceof NoClassDefFoundError; + cause = cause.getCause(); + } + + if (printClasspath) { + msg.add("Inspect your classpath, as there was a NoClassDefFoundError."); + /* + msg.add("Classpath:"); + for (URI uri : new ClassGraph().getClasspathURIs()) { + msg.add(uri.toString()); + } + */ + } + } + } + } + + printMessage(msg.toString()); + } + + /** + * Converts a throwable to a BugInCF. + * + * @param methodName the method that caught the exception (redundant with stack trace) + * @param t the throwable to be converted to a BugInCF + * @param p what source code was being processed + * @return a BugInCF that wraps the given throwable + */ + private BugInCF wrapThrowableAsBugInCF(String methodName, Throwable t, @Nullable TreePath p) { + return new BugInCF( + methodName + + ": unexpected Throwable (" + + t.getClass().getSimpleName() + + ")" + + ((p == null) + ? "" + : " while processing " + p.getCompilationUnit().getSourceFile().getName()) + + (t.getMessage() == null ? "" : "; message: " + t.getMessage()), + t); + } + + /////////////////////////////////////////////////////////////////////////// + /// Shutdown + /// + + /** + * Return true to indicate that method {@link #shutdownHook} should be added as a shutdownHook of + * the JVM. + * + * @return true to add {@link #shutdownHook} as a shutdown hook of the JVM + */ + protected boolean shouldAddShutdownHook() { + return hasOption("resourceStats"); + } + + /** + * Method that gets called exactly once at shutdown time of the JVM. Checkers can override this + * method to customize the behavior. + * + *

If you override this, you must also override {@link #shouldAddShutdownHook} to return true. + */ + protected void shutdownHook() { + if (hasOption("resourceStats")) { + // Check for the "resourceStats" option and don't call shouldAddShutdownHook + // to allow subclasses to override shouldXXX and shutdownHook and simply + // call the super implementations. + printStats(); + } + } + + /** Print resource usage statistics. */ + protected void printStats() { + List memoryPools = ManagementFactory.getMemoryPoolMXBeans(); + for (MemoryPoolMXBean memoryPool : memoryPools) { + System.out.println("Memory pool " + memoryPool.getName() + " statistics"); + System.out.println(" Pool type: " + memoryPool.getType()); + System.out.println(" Peak usage: " + memoryPool.getPeakUsage()); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Miscellaneous + /// + + /** + * A helper function to parse a Properties file. + * + * @param cls the class whose location is the base of the file path + * @param filePath the name/path of the file to be read + * @param permitNonExisting if true, return an empty Properties if the file does not exist or + * cannot be parsed; if false, issue an error + * @return the properties + */ + protected Properties getProperties(Class cls, String filePath, boolean permitNonExisting) { + Properties prop = new Properties(); + try (InputStream base = cls.getResourceAsStream(filePath)) { + if (base == null) { + // The property file was not found. + if (permitNonExisting) { + return prop; } else { - msg.add(ce.getMessage()); - boolean noPrintErrorStack = - (processingEnv != null - && processingEnv.getOptions() != null - && processingEnv.getOptions().containsKey("noPrintErrorStack")); - - msg.add("; " + culprit); - if (noPrintErrorStack) { - msg.add( - " To see the full stack trace, don't invoke the compiler with" - + " -AnoPrintErrorStack"); - } else { - msg.add("Checker: " + this.getClass()); - if (this.visitor != null) { - msg.add("Visitor: " + this.visitor.getClass()); - } - if (this.currentRoot != null && this.currentRoot.getSourceFile() != null) { - msg.add("Compilation unit: " + this.currentRoot.getSourceFile().getName()); - } - - if (this.visitor != null) { - DiagnosticPosition pos = (DiagnosticPosition) this.visitor.lastVisited; - if (pos != null) { - DiagnosticSource source = - new DiagnosticSource(this.currentRoot.getSourceFile(), null); - int linenr = source.getLineNumber(pos.getStartPosition()); - int col = source.getColumnNumber(pos.getStartPosition(), true); - String line = source.getLine(pos.getStartPosition()); - - msg.add("Last visited tree at line " + linenr + " column " + col + ":"); - msg.add(line); - } - } - - Throwable forStackTrace = ce.getCause() != null ? ce.getCause() : ce; - if (forStackTrace != null) { - msg.add( - "Exception: " - + forStackTrace - + "; " - + UtilPlume.stackTraceToString(forStackTrace)); - boolean printClasspath = forStackTrace instanceof NoClassDefFoundError; - Throwable cause = forStackTrace.getCause(); - while (cause != null) { - msg.add( - "Underlying Exception: " - + cause - + "; " - + UtilPlume.stackTraceToString(cause)); - printClasspath |= cause instanceof NoClassDefFoundError; - cause = cause.getCause(); - } - - if (printClasspath) { - msg.add("Inspect your classpath, as there was a NoClassDefFoundError."); - /* - msg.add("Classpath:"); - for (URI uri : new ClassGraph().getClasspathURIs()) { - msg.add(uri.toString()); - } - */ - } - } - } - } - - printMessage(msg.toString()); - } - - /** - * Converts a throwable to a BugInCF. - * - * @param methodName the method that caught the exception (redundant with stack trace) - * @param t the throwable to be converted to a BugInCF - * @param p what source code was being processed - * @return a BugInCF that wraps the given throwable - */ - private BugInCF wrapThrowableAsBugInCF(String methodName, Throwable t, @Nullable TreePath p) { - return new BugInCF( - methodName - + ": unexpected Throwable (" - + t.getClass().getSimpleName() - + ")" - + ((p == null) - ? "" - : " while processing " - + p.getCompilationUnit().getSourceFile().getName()) - + (t.getMessage() == null ? "" : "; message: " + t.getMessage()), - t); - } - - /////////////////////////////////////////////////////////////////////////// - /// Shutdown - /// - - /** - * Return true to indicate that method {@link #shutdownHook} should be added as a shutdownHook - * of the JVM. - * - * @return true to add {@link #shutdownHook} as a shutdown hook of the JVM - */ - protected boolean shouldAddShutdownHook() { - return hasOption("resourceStats"); - } - - /** - * Method that gets called exactly once at shutdown time of the JVM. Checkers can override this - * method to customize the behavior. - * - *

If you override this, you must also override {@link #shouldAddShutdownHook} to return - * true. - */ - protected void shutdownHook() { - if (hasOption("resourceStats")) { - // Check for the "resourceStats" option and don't call shouldAddShutdownHook - // to allow subclasses to override shouldXXX and shutdownHook and simply - // call the super implementations. - printStats(); - } - } - - /** Print resource usage statistics. */ - protected void printStats() { - List memoryPools = ManagementFactory.getMemoryPoolMXBeans(); - for (MemoryPoolMXBean memoryPool : memoryPools) { - System.out.println("Memory pool " + memoryPool.getName() + " statistics"); - System.out.println(" Pool type: " + memoryPool.getType()); - System.out.println(" Peak usage: " + memoryPool.getPeakUsage()); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Miscellaneous - /// - - /** - * A helper function to parse a Properties file. - * - * @param cls the class whose location is the base of the file path - * @param filePath the name/path of the file to be read - * @param permitNonExisting if true, return an empty Properties if the file does not exist or - * cannot be parsed; if false, issue an error - * @return the properties - */ - protected Properties getProperties(Class cls, String filePath, boolean permitNonExisting) { - Properties prop = new Properties(); - try (InputStream base = cls.getResourceAsStream(filePath)) { - if (base == null) { - // The property file was not found. - if (permitNonExisting) { - return prop; - } else { - throw new BugInCF("Couldn't locate properties file " + filePath); - } - } - - prop.load(base); - } catch (IOException e) { - throw new BugInCF("Couldn't parse properties file: " + filePath, e); - } - return prop; - } - - @Override - public final SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } - - /** True if the git.properties file has been printed. */ - private static boolean gitPropertiesPrinted = false; - - /** Print information about the git repository from which the Checker Framework was compiled. */ - private void printGitProperties() { - if (gitPropertiesPrinted) { - return; - } - gitPropertiesPrinted = true; - - try (InputStream in = getClass().getResourceAsStream("/git.properties"); - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); ) { - String line; - while ((line = reader.readLine()) != null) { - System.out.println(line); - } - } catch (IOException e) { - System.out.println("IOException while reading git.properties: " + e.getMessage()); - } - } - - /** - * Returns the version of the Checker Framework. - * - * @return the Checker Framework version, or null if not available - */ - private @Nullable String getCheckerVersion() { - String version; - try { - Properties gitProperties = getProperties(getClass(), "/git.properties", false); - version = gitProperties.getProperty("git.build.version"); - if (version == null) { - throw new BugInCF("Could not find the version in git.properties"); - } - String branch = gitProperties.getProperty("git.branch"); - // git.dirty indicates modified tracked files and staged changes. Untracked content - // doesn't count, so not being dirty doesn't mean that exactly the printed commit is - // being run. - String dirty = gitProperties.getProperty("git.dirty"); - if (version.endsWith("-SNAPSHOT") || !branch.equals("master")) { - // Sometimes the branch is HEAD, which is not informative. - // How does that happen, and how can I fix it? - version += ", branch " + branch; - // For brevity, only date but not time of day. - version += ", " + gitProperties.getProperty("git.commit.time").substring(0, 10); - version += ", commit " + gitProperties.getProperty("git.commit.id.abbrev"); - if (dirty.equals("true")) { - version += ", dirty=true"; - } - } - } catch (Exception ex) { - // throws an exception when invoked during JUnit tests. - version = null; - } - return version; - } - - /** - * Gradle and IntelliJ wrap the processing environment to gather information about modifications - * done by annotation processor during incremental compilation. But the Checker Framework calls - * methods from javac that require the processing environment to be {@code - * com.sun.tools.javac.processing.JavacProcessingEnvironment}. They fail if given a proxy. This - * method unwraps a proxy if one is used. - * - * @param env a processing environment - * @return unwrapped environment if the argument is a proxy created by IntelliJ or Gradle; - * original value (the argument) if the argument is a javac processing environment - * @throws BugInCF if method fails to retrieve {@code - * com.sun.tools.javac.processing.JavacProcessingEnvironment} - */ - private static ProcessingEnvironment unwrapProcessingEnvironment(ProcessingEnvironment env) { - if (env.getClass().getName() - == "com.sun.tools.javac.processing.JavacProcessingEnvironment") { // interned - return env; - } - // IntelliJ >2020.3 wraps the processing environment in a dynamic proxy. - ProcessingEnvironment unwrappedIntelliJ = unwrapIntelliJ(env); - if (unwrappedIntelliJ != null) { - return unwrapProcessingEnvironment(unwrappedIntelliJ); - } - // Gradle incremental build also wraps the processing environment. - for (Class envClass = env.getClass(); - envClass != null; - envClass = envClass.getSuperclass()) { - ProcessingEnvironment unwrappedGradle = unwrapGradle(envClass, env); - if (unwrappedGradle != null) { - return unwrapProcessingEnvironment(unwrappedGradle); - } - } - throw new BugInCF("Unexpected processing environment: %s %s", env, env.getClass()); - } - - /** - * Tries to unwrap ProcessingEnvironment from proxy in IntelliJ 2020.3 or later. - * - * @param env possibly a dynamic proxy wrapping processing environment - * @return unwrapped processing environment, null if not successful - */ - private static @Nullable ProcessingEnvironment unwrapIntelliJ(ProcessingEnvironment env) { - if (!Proxy.isProxyClass(env.getClass())) { - return null; - } - InvocationHandler handler = Proxy.getInvocationHandler(env); - try { - Field field = handler.getClass().getDeclaredField("val$delegateTo"); - field.setAccessible(true); - Object o = field.get(handler); - if (o instanceof ProcessingEnvironment) { - return (ProcessingEnvironment) o; - } - return null; - } catch (NoSuchFieldException | IllegalAccessException e) { - return null; - } - } - - /** - * Tries to unwrap processing environment in Gradle incremental processing. Inspired by project - * Lombok. - * - * @param delegateClass a class in which to find a {@code delegate} field - * @param env a processing environment wrapper - * @return unwrapped processing environment, null if not successful - */ - private static @Nullable ProcessingEnvironment unwrapGradle( - Class delegateClass, ProcessingEnvironment env) { - try { - Field field = delegateClass.getDeclaredField("delegate"); - field.setAccessible(true); - Object o = field.get(env); - if (o instanceof ProcessingEnvironment) { - return (ProcessingEnvironment) o; - } - return null; - } catch (NoSuchFieldException | IllegalAccessException e) { - return null; - } - } - - /** - * Return the path to the current compilation unit. - * - * @return path to the current compilation unit - */ - public TreePath getPathToCompilationUnit() { - return getTreePathCacher().getPath(currentRoot, currentRoot); - } + throw new BugInCF("Couldn't locate properties file " + filePath); + } + } + + prop.load(base); + } catch (IOException e) { + throw new BugInCF("Couldn't parse properties file: " + filePath, e); + } + return prop; + } + + @Override + public final SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + /** True if the git.properties file has been printed. */ + private static boolean gitPropertiesPrinted = false; + + /** Print information about the git repository from which the Checker Framework was compiled. */ + private void printGitProperties() { + if (gitPropertiesPrinted) { + return; + } + gitPropertiesPrinted = true; + + try (InputStream in = getClass().getResourceAsStream("/git.properties"); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); ) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } catch (IOException e) { + System.out.println("IOException while reading git.properties: " + e.getMessage()); + } + } + + /** + * Returns the version of the Checker Framework. + * + * @return the Checker Framework version, or null if not available + */ + private @Nullable String getCheckerVersion() { + String version; + try { + Properties gitProperties = getProperties(getClass(), "/git.properties", false); + version = gitProperties.getProperty("git.build.version"); + if (version == null) { + throw new BugInCF("Could not find the version in git.properties"); + } + String branch = gitProperties.getProperty("git.branch"); + // git.dirty indicates modified tracked files and staged changes. Untracked content + // doesn't count, so not being dirty doesn't mean that exactly the printed commit is + // being run. + String dirty = gitProperties.getProperty("git.dirty"); + if (version.endsWith("-SNAPSHOT") || !branch.equals("master")) { + // Sometimes the branch is HEAD, which is not informative. + // How does that happen, and how can I fix it? + version += ", branch " + branch; + // For brevity, only date but not time of day. + version += ", " + gitProperties.getProperty("git.commit.time").substring(0, 10); + version += ", commit " + gitProperties.getProperty("git.commit.id.abbrev"); + if (dirty.equals("true")) { + version += ", dirty=true"; + } + } + } catch (Exception ex) { + // throws an exception when invoked during JUnit tests. + version = null; + } + return version; + } + + /** + * Gradle and IntelliJ wrap the processing environment to gather information about modifications + * done by annotation processor during incremental compilation. But the Checker Framework calls + * methods from javac that require the processing environment to be {@code + * com.sun.tools.javac.processing.JavacProcessingEnvironment}. They fail if given a proxy. This + * method unwraps a proxy if one is used. + * + * @param env a processing environment + * @return unwrapped environment if the argument is a proxy created by IntelliJ or Gradle; + * original value (the argument) if the argument is a javac processing environment + * @throws BugInCF if method fails to retrieve {@code + * com.sun.tools.javac.processing.JavacProcessingEnvironment} + */ + private static ProcessingEnvironment unwrapProcessingEnvironment(ProcessingEnvironment env) { + if (env.getClass().getName() + == "com.sun.tools.javac.processing.JavacProcessingEnvironment") { // interned + return env; + } + // IntelliJ >2020.3 wraps the processing environment in a dynamic proxy. + ProcessingEnvironment unwrappedIntelliJ = unwrapIntelliJ(env); + if (unwrappedIntelliJ != null) { + return unwrapProcessingEnvironment(unwrappedIntelliJ); + } + // Gradle incremental build also wraps the processing environment. + for (Class envClass = env.getClass(); + envClass != null; + envClass = envClass.getSuperclass()) { + ProcessingEnvironment unwrappedGradle = unwrapGradle(envClass, env); + if (unwrappedGradle != null) { + return unwrapProcessingEnvironment(unwrappedGradle); + } + } + throw new BugInCF("Unexpected processing environment: %s %s", env, env.getClass()); + } + + /** + * Tries to unwrap ProcessingEnvironment from proxy in IntelliJ 2020.3 or later. + * + * @param env possibly a dynamic proxy wrapping processing environment + * @return unwrapped processing environment, null if not successful + */ + private static @Nullable ProcessingEnvironment unwrapIntelliJ(ProcessingEnvironment env) { + if (!Proxy.isProxyClass(env.getClass())) { + return null; + } + InvocationHandler handler = Proxy.getInvocationHandler(env); + try { + Field field = handler.getClass().getDeclaredField("val$delegateTo"); + field.setAccessible(true); + Object o = field.get(handler); + if (o instanceof ProcessingEnvironment) { + return (ProcessingEnvironment) o; + } + return null; + } catch (NoSuchFieldException | IllegalAccessException e) { + return null; + } + } + + /** + * Tries to unwrap processing environment in Gradle incremental processing. Inspired by project + * Lombok. + * + * @param delegateClass a class in which to find a {@code delegate} field + * @param env a processing environment wrapper + * @return unwrapped processing environment, null if not successful + */ + private static @Nullable ProcessingEnvironment unwrapGradle( + Class delegateClass, ProcessingEnvironment env) { + try { + Field field = delegateClass.getDeclaredField("delegate"); + field.setAccessible(true); + Object o = field.get(env); + if (o instanceof ProcessingEnvironment) { + return (ProcessingEnvironment) o; + } + return null; + } catch (NoSuchFieldException | IllegalAccessException e) { + return null; + } + } + + /** + * Return the path to the current compilation unit. + * + * @return path to the current compilation unit + */ + public TreePath getPathToCompilationUnit() { + return getTreePathCacher().getPath(currentRoot, currentRoot); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java b/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java index 8320d7b22b4..6d3e1740c3d 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java @@ -8,17 +8,14 @@ import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; - -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; - import java.util.ArrayList; import java.util.List; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; /** * An AST visitor that provides a variety of compiler utilities and interfaces to facilitate @@ -26,105 +23,105 @@ */ public abstract class SourceVisitor extends TreePathScanner { - /** The {@link Trees} instance to use for scanning. */ - protected final Trees trees; - - /** The {@link Elements} helper to use when scanning. */ - protected final Elements elements; - - /** The {@link Types} helper to use when scanning. */ - protected final Types types; - - /** - * The root of the AST that this {@link SourceVisitor} will scan. - * - *

Is set by {@link #setRoot}. - */ - protected CompilationUnitTree root; - - /** The trees that are annotated with {@code @SuppressWarnings}. */ - public final List treesWithSuppressWarnings; - - /** Whether or not a warning should be issued for unneeded warning suppressions. */ - private final boolean warnUnneededSuppressions; - - /** - * Creates a {@link SourceVisitor} to use for scanning a source tree. - * - * @param checker the checker to invoke on the input source tree - */ - protected SourceVisitor(SourceChecker checker) { - // Use the checker's processing environment to get the helpers we need. - ProcessingEnvironment env = checker.getProcessingEnvironment(); - - this.trees = Trees.instance(env); - this.elements = env.getElementUtils(); - this.types = env.getTypeUtils(); - - this.treesWithSuppressWarnings = new ArrayList<>(); - this.warnUnneededSuppressions = checker.hasOption("warnUnneededSuppressions"); - } - - /** - * Set the CompilationUnitTree to be used during any visits. For any later calls of {@code - * com.sun.source.util.TreePathScanner.scan(TreePath, P)}, the CompilationUnitTree of the - * TreePath has to be equal to {@code root}. - */ - public void setRoot(CompilationUnitTree root) { - this.root = root; - } - - /** - * Store the last Tree visited by the SourceVisitor. This is necessary because the finally - * blocks in {@link com.sun.source.util.TreePathScanner#scan(TreePath, Object)} and {@link - * com.sun.source.util.TreePathScanner#scan(Tree, Object)} set the visited Path to null. This - * field is used to report a rough location for the error in {@link - * org.checkerframework.framework.source.SourceChecker#logBugInCF(BugInCF)}. - */ - /*package-private*/ Tree lastVisited; - - /** Entry point for a type processor: the TreePath leaf is a top-level type tree within root. */ - public void visit(TreePath path) { - lastVisited = path.getLeaf(); - this.scan(path, null); - } - - @Override - public R scan(Tree tree, P p) { - lastVisited = tree; - return super.scan(tree, p); + /** The {@link Trees} instance to use for scanning. */ + protected final Trees trees; + + /** The {@link Elements} helper to use when scanning. */ + protected final Elements elements; + + /** The {@link Types} helper to use when scanning. */ + protected final Types types; + + /** + * The root of the AST that this {@link SourceVisitor} will scan. + * + *

Is set by {@link #setRoot}. + */ + protected CompilationUnitTree root; + + /** The trees that are annotated with {@code @SuppressWarnings}. */ + public final List treesWithSuppressWarnings; + + /** Whether or not a warning should be issued for unneeded warning suppressions. */ + private final boolean warnUnneededSuppressions; + + /** + * Creates a {@link SourceVisitor} to use for scanning a source tree. + * + * @param checker the checker to invoke on the input source tree + */ + protected SourceVisitor(SourceChecker checker) { + // Use the checker's processing environment to get the helpers we need. + ProcessingEnvironment env = checker.getProcessingEnvironment(); + + this.trees = Trees.instance(env); + this.elements = env.getElementUtils(); + this.types = env.getTypeUtils(); + + this.treesWithSuppressWarnings = new ArrayList<>(); + this.warnUnneededSuppressions = checker.hasOption("warnUnneededSuppressions"); + } + + /** + * Set the CompilationUnitTree to be used during any visits. For any later calls of {@code + * com.sun.source.util.TreePathScanner.scan(TreePath, P)}, the CompilationUnitTree of the TreePath + * has to be equal to {@code root}. + */ + public void setRoot(CompilationUnitTree root) { + this.root = root; + } + + /** + * Store the last Tree visited by the SourceVisitor. This is necessary because the finally blocks + * in {@link com.sun.source.util.TreePathScanner#scan(TreePath, Object)} and {@link + * com.sun.source.util.TreePathScanner#scan(Tree, Object)} set the visited Path to null. This + * field is used to report a rough location for the error in {@link + * org.checkerframework.framework.source.SourceChecker#logBugInCF(BugInCF)}. + */ + /*package-private*/ Tree lastVisited; + + /** Entry point for a type processor: the TreePath leaf is a top-level type tree within root. */ + public void visit(TreePath path) { + lastVisited = path.getLeaf(); + this.scan(path, null); + } + + @Override + public R scan(Tree tree, P p) { + lastVisited = tree; + return super.scan(tree, p); + } + + @Override + public R visitClass(ClassTree classTree, P p) { + storeSuppressWarningsAnno(classTree); + return super.visitClass(classTree, p); + } + + @Override + public R visitVariable(VariableTree variableTree, P p) { + storeSuppressWarningsAnno(variableTree); + return super.visitVariable(variableTree, p); + } + + @Override + public R visitMethod(MethodTree tree, P p) { + storeSuppressWarningsAnno(tree); + return super.visitMethod(tree, p); + } + + /** + * If {@code tree} has a {@code @SuppressWarnings} add it to treesWithSuppressWarnings. + * + * @param tree a declaration on which a {@code @SuppressWarnings} annotation may be placed + */ + private void storeSuppressWarningsAnno(Tree tree) { + if (!warnUnneededSuppressions) { + return; } - - @Override - public R visitClass(ClassTree classTree, P p) { - storeSuppressWarningsAnno(classTree); - return super.visitClass(classTree, p); - } - - @Override - public R visitVariable(VariableTree variableTree, P p) { - storeSuppressWarningsAnno(variableTree); - return super.visitVariable(variableTree, p); - } - - @Override - public R visitMethod(MethodTree tree, P p) { - storeSuppressWarningsAnno(tree); - return super.visitMethod(tree, p); - } - - /** - * If {@code tree} has a {@code @SuppressWarnings} add it to treesWithSuppressWarnings. - * - * @param tree a declaration on which a {@code @SuppressWarnings} annotation may be placed - */ - private void storeSuppressWarningsAnno(Tree tree) { - if (!warnUnneededSuppressions) { - return; - } - Element elt = TreeUtils.elementFromTree(tree); - if (elt.getAnnotation(SuppressWarnings.class) != null) { - treesWithSuppressWarnings.add(tree); - } + Element elt = TreeUtils.elementFromTree(tree); + if (elt.getAnnotation(SuppressWarnings.class) != null) { + treesWithSuppressWarnings.add(tree); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java b/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java index af2e3da8726..238b7c6a151 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java @@ -28,5 +28,5 @@ @Target(ElementType.TYPE) @Inherited public @interface SupportedLintOptions { - String[] value(); + String[] value(); } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java b/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java index d8a8edc2b91..2353aeaad5c 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java @@ -23,5 +23,5 @@ @Target(ElementType.TYPE) @Inherited public @interface SupportedOptions { - String[] value(); + String[] value(); } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java index 51604b60911..18b2805fe8f 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java @@ -30,12 +30,12 @@ // of prefix. public @interface SuppressWarningsPrefix { - /** - * Returns array of strings, any one of which causes this checker to suppress a warning when - * passed as the argument of {@literal @}{@link SuppressWarnings}. - * - * @return array of strings, any one of which causes this checker to suppress a warning when - * passed as the argument of {@literal @}{@link SuppressWarnings} - */ - String[] value(); + /** + * Returns array of strings, any one of which causes this checker to suppress a warning when + * passed as the argument of {@literal @}{@link SuppressWarnings}. + * + * @return array of strings, any one of which causes this checker to suppress a warning when + * passed as the argument of {@literal @}{@link SuppressWarnings} + */ + String[] value(); } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java index 8a42fb1cdf5..8489ff852cd 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java @@ -1,5 +1,17 @@ package org.checkerframework.framework.stub; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.checkerframework.afu.scenelib.Annotation; import org.checkerframework.afu.scenelib.Annotations; import org.checkerframework.afu.scenelib.el.ABlock; @@ -23,240 +35,222 @@ import org.checkerframework.checker.signature.qual.BinaryName; import org.plumelib.util.ArraySet; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.LineNumberReader; -import java.io.PrintWriter; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - /** * Utility that generates {@code @AnnotatedFor} class annotations. The {@link #main} method acts as * a filter: it reads a JAIF from standard input and writes an augmented JAIF to standard output. */ public class AddAnnotatedFor { - /** Definition of {@code @AnnotatedFor} annotation. */ - private static final AnnotationDef adAnnotatedFor; + /** Definition of {@code @AnnotatedFor} annotation. */ + private static final AnnotationDef adAnnotatedFor; - static { - Class annotatedFor = org.checkerframework.framework.qual.AnnotatedFor.class; - Set annotatedForMetaAnnotations = new HashSet<>(2); - annotatedForMetaAnnotations.add(Annotations.aRetentionSource); - annotatedForMetaAnnotations.add( - Annotations.createValueAnnotation( - Annotations.adTarget, - Arrays.asList("TYPE", "METHOD", "CONSTRUCTOR", "PACKAGE"))); - @SuppressWarnings( - "signature") // TODO bug: AnnotationDef requires @BinaryName, gets CanonicalName - @BinaryName String name = annotatedFor.getCanonicalName(); - adAnnotatedFor = - new AnnotationDef( - name, - annotatedForMetaAnnotations, - Collections.singletonMap( - "value", new ArrayAFT(BasicAFT.forType(String.class))), - "AddAnnotatedFor."); - } + static { + Class annotatedFor = org.checkerframework.framework.qual.AnnotatedFor.class; + Set annotatedForMetaAnnotations = new HashSet<>(2); + annotatedForMetaAnnotations.add(Annotations.aRetentionSource); + annotatedForMetaAnnotations.add( + Annotations.createValueAnnotation( + Annotations.adTarget, Arrays.asList("TYPE", "METHOD", "CONSTRUCTOR", "PACKAGE"))); + @SuppressWarnings( + "signature") // TODO bug: AnnotationDef requires @BinaryName, gets CanonicalName + @BinaryName String name = annotatedFor.getCanonicalName(); + adAnnotatedFor = + new AnnotationDef( + name, + annotatedForMetaAnnotations, + Collections.singletonMap("value", new ArrayAFT(BasicAFT.forType(String.class))), + "AddAnnotatedFor."); + } - /** Do not instantiate. */ - private AddAnnotatedFor() { - throw new Error("Do not instantiate"); - } + /** Do not instantiate. */ + private AddAnnotatedFor() { + throw new Error("Do not instantiate"); + } - /** - * Reads JAIF from the file indicated by the first element, or standard input if the argument - * array is empty; inserts any appropriate {@code @AnnotatedFor} annotations, based on the - * annotations defined in the input JAIF; and writes the augmented JAIF to standard output. - * - * @param args one jaif file, or empty to read from standard input - * @throws IOException if there is trouble reading or writing a file - * @throws DefException if two definitions cannot be unified - * @throws ParseException if the file is malformed - */ - public static void main(String[] args) throws IOException, DefException, ParseException { - if (args.length > 1) { - System.err.println("Supply 0 or 1 command-line arguments."); - System.exit(1); - } - AScene scene = new AScene(); - boolean useFile = args.length == 1; - String filename = useFile ? args[0] : "System.in"; - try (Reader r = useFile ? new FileReader(filename) : new InputStreamReader(System.in)) { - IndexFileParser.parse(new LineNumberReader(r), filename, scene); - } - scene.prune(); - addAnnotatedFor(scene); - IndexFileWriter.write(scene, new PrintWriter(System.out, true)); + /** + * Reads JAIF from the file indicated by the first element, or standard input if the argument + * array is empty; inserts any appropriate {@code @AnnotatedFor} annotations, based on the + * annotations defined in the input JAIF; and writes the augmented JAIF to standard output. + * + * @param args one jaif file, or empty to read from standard input + * @throws IOException if there is trouble reading or writing a file + * @throws DefException if two definitions cannot be unified + * @throws ParseException if the file is malformed + */ + public static void main(String[] args) throws IOException, DefException, ParseException { + if (args.length > 1) { + System.err.println("Supply 0 or 1 command-line arguments."); + System.exit(1); + } + AScene scene = new AScene(); + boolean useFile = args.length == 1; + String filename = useFile ? args[0] : "System.in"; + try (Reader r = useFile ? new FileReader(filename) : new InputStreamReader(System.in)) { + IndexFileParser.parse(new LineNumberReader(r), filename, scene); } + scene.prune(); + addAnnotatedFor(scene); + IndexFileWriter.write(scene, new PrintWriter(System.out, true)); + } - /** - * Add {@code @AnnotatedFor} annotations to each class in the given scene. - * - * @param scene an {@code @AnnotatedFor} annotation is added to each class in this scene - */ - public static void addAnnotatedFor(AScene scene) { - for (AClass clazz : new HashSet<>(scene.classes.values())) { - Set annotatedFor = - new ArraySet<>(2); // usually few @AnnotatedFor are applicable - clazz.accept(annotatedForVisitor, annotatedFor); - if (!annotatedFor.isEmpty()) { - // Set eliminates duplicates, but it must be converted to List; for whatever reason, - // IndexFileWriter recognizes array arguments only in List form. - List annotatedForList = new ArrayList<>(annotatedFor); - clazz.tlAnnotationsHere.add( - new Annotation( - adAnnotatedFor, Annotations.valueFieldOnly(annotatedForList))); - } - } + /** + * Add {@code @AnnotatedFor} annotations to each class in the given scene. + * + * @param scene an {@code @AnnotatedFor} annotation is added to each class in this scene + */ + public static void addAnnotatedFor(AScene scene) { + for (AClass clazz : new HashSet<>(scene.classes.values())) { + Set annotatedFor = new ArraySet<>(2); // usually few @AnnotatedFor are applicable + clazz.accept(annotatedForVisitor, annotatedFor); + if (!annotatedFor.isEmpty()) { + // Set eliminates duplicates, but it must be converted to List; for whatever reason, + // IndexFileWriter recognizes array arguments only in List form. + List annotatedForList = new ArrayList<>(annotatedFor); + clazz.tlAnnotationsHere.add( + new Annotation(adAnnotatedFor, Annotations.valueFieldOnly(annotatedForList))); + } } + } - /** - * This visitor collects the names of all the type systems, one of whose annotations is written. - * These need to be the arguments to an {@code AnnotatedFor} annotation on the class, so that - * all of the given type systems are run. - */ - private static final ElementVisitor> annotatedForVisitor = - new ElementVisitor>() { - @Override - public Void visitAnnotationDef(AnnotationDef el, Set annotatedFor) { - return null; - } + /** + * This visitor collects the names of all the type systems, one of whose annotations is written. + * These need to be the arguments to an {@code AnnotatedFor} annotation on the class, so that all + * of the given type systems are run. + */ + private static final ElementVisitor> annotatedForVisitor = + new ElementVisitor>() { + @Override + public Void visitAnnotationDef(AnnotationDef el, Set annotatedFor) { + return null; + } - @Override - public Void visitBlock(ABlock el, Set annotatedFor) { - for (AField e : el.locals.values()) { - e.accept(this, annotatedFor); - } - return visitExpression(el, annotatedFor); - } + @Override + public Void visitBlock(ABlock el, Set annotatedFor) { + for (AField e : el.locals.values()) { + e.accept(this, annotatedFor); + } + return visitExpression(el, annotatedFor); + } - @Override - public Void visitClass(AClass el, Set annotatedFor) { - for (ATypeElement e : el.bounds.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.extendsImplements.values()) { - e.accept(this, annotatedFor); - } - for (AExpression e : el.fieldInits.values()) { - e.accept(this, annotatedFor); - } - for (AField e : el.fields.values()) { - e.accept(this, annotatedFor); - } - for (ABlock e : el.instanceInits.values()) { - e.accept(this, annotatedFor); - } - for (AMethod e : el.methods.values()) { - e.accept(this, annotatedFor); - } - for (ABlock e : el.staticInits.values()) { - e.accept(this, annotatedFor); - } - return visitDeclaration(el, annotatedFor); - } + @Override + public Void visitClass(AClass el, Set annotatedFor) { + for (ATypeElement e : el.bounds.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.extendsImplements.values()) { + e.accept(this, annotatedFor); + } + for (AExpression e : el.fieldInits.values()) { + e.accept(this, annotatedFor); + } + for (AField e : el.fields.values()) { + e.accept(this, annotatedFor); + } + for (ABlock e : el.instanceInits.values()) { + e.accept(this, annotatedFor); + } + for (AMethod e : el.methods.values()) { + e.accept(this, annotatedFor); + } + for (ABlock e : el.staticInits.values()) { + e.accept(this, annotatedFor); + } + return visitDeclaration(el, annotatedFor); + } - @Override - public Void visitDeclaration(ADeclaration el, Set annotatedFor) { - for (ATypeElement e : el.insertAnnotations.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElementWithType e : el.insertTypecasts.values()) { - e.accept(this, annotatedFor); - } - return visitElement(el, annotatedFor); - } + @Override + public Void visitDeclaration(ADeclaration el, Set annotatedFor) { + for (ATypeElement e : el.insertAnnotations.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElementWithType e : el.insertTypecasts.values()) { + e.accept(this, annotatedFor); + } + return visitElement(el, annotatedFor); + } - @Override - public Void visitExpression(AExpression el, Set annotatedFor) { - for (ATypeElement e : el.calls.values()) { - e.accept(this, annotatedFor); - } - for (AMethod e : el.funs.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.instanceofs.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.news.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.refs.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.typecasts.values()) { - e.accept(this, annotatedFor); - } - return visitElement(el, annotatedFor); - } + @Override + public Void visitExpression(AExpression el, Set annotatedFor) { + for (ATypeElement e : el.calls.values()) { + e.accept(this, annotatedFor); + } + for (AMethod e : el.funs.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.instanceofs.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.news.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.refs.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.typecasts.values()) { + e.accept(this, annotatedFor); + } + return visitElement(el, annotatedFor); + } - @Override - public Void visitField(AField el, Set annotatedFor) { - if (el.init != null) { - el.init.accept(this, annotatedFor); - } - return visitDeclaration(el, annotatedFor); - } + @Override + public Void visitField(AField el, Set annotatedFor) { + if (el.init != null) { + el.init.accept(this, annotatedFor); + } + return visitDeclaration(el, annotatedFor); + } - @Override - public Void visitMethod(AMethod el, Set annotatedFor) { - if (el.body != null) { - el.body.accept(this, annotatedFor); - } - if (el.receiver != null) { - el.receiver.accept(this, annotatedFor); - } - if (el.returnType != null) { - el.returnType.accept(this, annotatedFor); - } - for (ATypeElement e : el.bounds.values()) { - e.accept(this, annotatedFor); - } - for (AField e : el.parameters.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.throwsException.values()) { - e.accept(this, annotatedFor); - } - return visitDeclaration(el, annotatedFor); - } + @Override + public Void visitMethod(AMethod el, Set annotatedFor) { + if (el.body != null) { + el.body.accept(this, annotatedFor); + } + if (el.receiver != null) { + el.receiver.accept(this, annotatedFor); + } + if (el.returnType != null) { + el.returnType.accept(this, annotatedFor); + } + for (ATypeElement e : el.bounds.values()) { + e.accept(this, annotatedFor); + } + for (AField e : el.parameters.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.throwsException.values()) { + e.accept(this, annotatedFor); + } + return visitDeclaration(el, annotatedFor); + } - @Override - public Void visitTypeElement(ATypeElement el, Set annotatedFor) { - for (ATypeElement e : el.innerTypes.values()) { - e.accept(this, annotatedFor); - } - return visitElement(el, annotatedFor); - } + @Override + public Void visitTypeElement(ATypeElement el, Set annotatedFor) { + for (ATypeElement e : el.innerTypes.values()) { + e.accept(this, annotatedFor); + } + return visitElement(el, annotatedFor); + } - @Override - public Void visitTypeElementWithType( - ATypeElementWithType el, Set annotatedFor) { - return visitTypeElement(el, annotatedFor); - } + @Override + public Void visitTypeElementWithType(ATypeElementWithType el, Set annotatedFor) { + return visitTypeElement(el, annotatedFor); + } - @Override - public Void visitElement(AElement el, Set annotatedFor) { - for (Annotation a : el.tlAnnotationsHere) { - String s = a.def().name; - int j = s.indexOf(".qual."); - if (j > 0) { - int i = s.lastIndexOf('.', j - 1); - if (i > 0 && j - i > 1) { - annotatedFor.add(s.substring(i + 1, j)); - } - } - } - if (el.type != null) { - el.type.accept(this, annotatedFor); - } - return null; - } - }; + @Override + public Void visitElement(AElement el, Set annotatedFor) { + for (Annotation a : el.tlAnnotationsHere) { + String s = a.def().name; + int j = s.indexOf(".qual."); + if (j > 0) { + int i = s.lastIndexOf('.', j - 1); + if (i > 0 && j - i > 1) { + annotatedFor.add(s.substring(i + 1, j)); + } + } + } + if (el.type != null) { + el.type.accept(this, annotatedFor); + } + return null; + } + }; } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java index 9dacf299664..4d318840afa 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java @@ -1,27 +1,6 @@ package org.checkerframework.framework.stub; import com.sun.source.tree.CompilationUnitTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.qual.StubFiles; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.stub.AnnotationFileParser.AnnotationFileAnnotations; -import org.checkerframework.framework.stub.AnnotationFileParser.RecordComponentStub; -import org.checkerframework.framework.stub.AnnotationFileUtil.AnnotationFileType; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.IPair; -import org.plumelib.util.SystemPlume; - import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -48,7 +27,6 @@ import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.stream.Stream; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -57,6 +35,25 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import javax.tools.Diagnostic; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.StubFiles; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.stub.AnnotationFileParser.AnnotationFileAnnotations; +import org.checkerframework.framework.stub.AnnotationFileParser.RecordComponentStub; +import org.checkerframework.framework.stub.AnnotationFileUtil.AnnotationFileType; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.IPair; +import org.plumelib.util.SystemPlume; // import io.github.classgraph.ClassGraph; @@ -65,1015 +62,982 @@ * using an ajava file, only holds information on public elements as with stub files. */ public class AnnotationFileElementTypes { - /** Annotations from annotation files (but not from annotated JDK files). */ - private final AnnotationFileAnnotations annotationFileAnnos; - - /** The number of ongoing parsing tasks. */ - private int parsingCount; - - /** AnnotatedTypeFactory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * Mapping from fully-qualified class name to corresponding JDK stub file from the file system - * that have not yet been read. When a file is read, its mapping is removed from this map. - * - *

By contrast, {@link #remainingJdkStubFilesJar} contains JDK stub files from checker.jar. - */ - private final Map remainingJdkStubFiles = new HashMap<>(); - - /** - * Mapping from fully-qualified class name to corresponding JDK stub files from checker.jar that - * have not yet been read. When a file is read, its mapping is removed from this map. - * - *

By contrast, {@link #remainingJdkStubFiles} contains JDK stub files from the file system. - */ - private final Map remainingJdkStubFilesJar = new HashMap<>(); - - /** Which version number of the annotated JDK should be used? */ - private final String annotatedJdkVersion; - - /** Should the JDK be parsed? */ - private final boolean shouldParseJdk; - - /** Parse all JDK files at startup rather than as needed. */ - private final boolean parseAllJdkFiles; - - /** True if -ApermitMissingJdk was passed on the command line. */ - private final boolean permitMissingJdk; - - /** True if -Aignorejdkastub was passed on the command line. */ - private final boolean ignorejdkastub; - - /** True if -AstubDebug was passed on the command line. */ - private final boolean stubDebug; - - /** - * Stores the fully qualified name of top-level classes (from any type of stub file) that are - * currently being parsed. This can stop recursively parsing an annotated JDK class that is - * currently being processed, which prevents conflicts of definition and infinite loops. - */ - private final Set processingClasses = new LinkedHashSet<>(); - - /** - * Creates an empty annotation source. - * - * @param atypeFactory a type factory - */ - public AnnotationFileElementTypes(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.annotationFileAnnos = new AnnotationFileAnnotations(); - this.parsingCount = 0; - String release = SystemUtil.getReleaseValue(atypeFactory.getProcessingEnv()); - this.annotatedJdkVersion = - release != null ? release : String.valueOf(SystemUtil.jreVersion); - - SourceChecker checker = atypeFactory.getChecker(); - this.ignorejdkastub = checker.hasOption("ignorejdkastub"); - this.shouldParseJdk = !ignorejdkastub; - this.parseAllJdkFiles = checker.hasOption("parseAllJdk"); - this.permitMissingJdk = checker.hasOption("permitMissingJdk"); - this.stubDebug = checker.hasOption("stubDebug"); + /** Annotations from annotation files (but not from annotated JDK files). */ + private final AnnotationFileAnnotations annotationFileAnnos; + + /** The number of ongoing parsing tasks. */ + private int parsingCount; + + /** AnnotatedTypeFactory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * Mapping from fully-qualified class name to corresponding JDK stub file from the file system + * that have not yet been read. When a file is read, its mapping is removed from this map. + * + *

By contrast, {@link #remainingJdkStubFilesJar} contains JDK stub files from checker.jar. + */ + private final Map remainingJdkStubFiles = new HashMap<>(); + + /** + * Mapping from fully-qualified class name to corresponding JDK stub files from checker.jar that + * have not yet been read. When a file is read, its mapping is removed from this map. + * + *

By contrast, {@link #remainingJdkStubFiles} contains JDK stub files from the file system. + */ + private final Map remainingJdkStubFilesJar = new HashMap<>(); + + /** Which version number of the annotated JDK should be used? */ + private final String annotatedJdkVersion; + + /** Should the JDK be parsed? */ + private final boolean shouldParseJdk; + + /** Parse all JDK files at startup rather than as needed. */ + private final boolean parseAllJdkFiles; + + /** True if -ApermitMissingJdk was passed on the command line. */ + private final boolean permitMissingJdk; + + /** True if -Aignorejdkastub was passed on the command line. */ + private final boolean ignorejdkastub; + + /** True if -AstubDebug was passed on the command line. */ + private final boolean stubDebug; + + /** + * Stores the fully qualified name of top-level classes (from any type of stub file) that are + * currently being parsed. This can stop recursively parsing an annotated JDK class that is + * currently being processed, which prevents conflicts of definition and infinite loops. + */ + private final Set processingClasses = new LinkedHashSet<>(); + + /** + * Creates an empty annotation source. + * + * @param atypeFactory a type factory + */ + public AnnotationFileElementTypes(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.annotationFileAnnos = new AnnotationFileAnnotations(); + this.parsingCount = 0; + String release = SystemUtil.getReleaseValue(atypeFactory.getProcessingEnv()); + this.annotatedJdkVersion = release != null ? release : String.valueOf(SystemUtil.jreVersion); + + SourceChecker checker = atypeFactory.getChecker(); + this.ignorejdkastub = checker.hasOption("ignorejdkastub"); + this.shouldParseJdk = !ignorejdkastub; + this.parseAllJdkFiles = checker.hasOption("parseAllJdk"); + this.permitMissingJdk = checker.hasOption("permitMissingJdk"); + this.stubDebug = checker.hasOption("stubDebug"); + } + + /** + * Returns true if files are currently being parsed; otherwise, false. + * + * @return true if files are currently being parsed; otherwise, false + */ + public boolean isParsing() { + return parsingCount > 0; + } + + /** + * Parses the stub files in the following order: + * + *

    + *
  1. {@code jdk.astub} in the same directory as the checker, if it exists and the {@code + * ignorejdkastub} option is not supplied; + *
  2. {@code jdkN.astub} (where N is the current Java version or any higher value) in the same + * directory as the checker, if it exists and the {@code ignorejdkastub} option is not + * supplied; + *
  3. If parsing the annotated JDK as stub files, all {@code package-info.java} files under the + * {@code jdk/} directory; + *
  4. Stub files listed in a {@code @StubFiles} annotation on the checker; these files must be + * in same directory as the checker; + *
  5. Stub files returned by {@link BaseTypeChecker#getExtraStubFiles} (treated like those + * listed in a {@code @StubFiles} annotation on the checker); + *
  6. Stub files provided via the {@code -Astubs} compiler option. + *
+ * + *

If a type is annotated with a qualifier from the same hierarchy in more than one stub file, + * the qualifier in the last stub file is applied. + * + *

If using JDK 11, then the JDK stub files are only parsed if a type or declaration annotation + * is requested from a class in that file. + */ + // TODO: it's unclear for what Java versions a jdkN.astub is parsed. + public void parseStubFiles() { + assert parsingCount == 0; + ++parsingCount; + BaseTypeChecker checker = atypeFactory.getChecker(); + if (stubDebug) { + System.out.printf( + "entered parseStubFiles() for %s, ignorejdkastub=%s%n", + atypeFactory.getClass().getSimpleName(), ignorejdkastub); } - - /** - * Returns true if files are currently being parsed; otherwise, false. - * - * @return true if files are currently being parsed; otherwise, false - */ - public boolean isParsing() { - return parsingCount > 0; + if (!ignorejdkastub) { + // 1. Annotated JDK + // This preps but does not parse the JDK files (except package-info.java files). + // The JDK source code files will be parsed later, on demand. + prepJdkStubs(); + + // 2. jdk.astub + // Only look in .jar files, and parse it right away. + String[] jdkVersions = {"", annotatedJdkVersion}; + for (String jdkVersion : jdkVersions) { + String jdkVersionStub = "jdk" + jdkVersion + ".astub"; + parseOneStubFile(this.getClass(), jdkVersionStub); + parseOneStubFile(checker.getClass(), jdkVersionStub); + } + // This needs to be special-cased for every jdkX.astub for which files exist. :-( + // TODO: not clear what this is supposed to mean - if we are on Java 8, why parse Java + // 11 stub files? + // It would make more sense to parse this if we're e.g. on Java 12. + if (annotatedJdkVersion.equals("8")) { + String jdk11Stub = "jdk11.astub"; + parseOneStubFile(this.getClass(), jdk11Stub); + parseOneStubFile(checker.getClass(), jdk11Stub); + } } - /** - * Parses the stub files in the following order: - * - *

    - *
  1. {@code jdk.astub} in the same directory as the checker, if it exists and the {@code - * ignorejdkastub} option is not supplied; - *
  2. {@code jdkN.astub} (where N is the current Java version or any higher value) in the - * same directory as the checker, if it exists and the {@code ignorejdkastub} option is - * not supplied; - *
  3. If parsing the annotated JDK as stub files, all {@code package-info.java} files under - * the {@code jdk/} directory; - *
  4. Stub files listed in a {@code @StubFiles} annotation on the checker; these files must - * be in same directory as the checker; - *
  5. Stub files returned by {@link BaseTypeChecker#getExtraStubFiles} (treated like those - * listed in a {@code @StubFiles} annotation on the checker); - *
  6. Stub files provided via the {@code -Astubs} compiler option. - *
- * - *

If a type is annotated with a qualifier from the same hierarchy in more than one stub - * file, the qualifier in the last stub file is applied. - * - *

If using JDK 11, then the JDK stub files are only parsed if a type or declaration - * annotation is requested from a class in that file. - */ - // TODO: it's unclear for what Java versions a jdkN.astub is parsed. - public void parseStubFiles() { - assert parsingCount == 0; - ++parsingCount; - BaseTypeChecker checker = atypeFactory.getChecker(); - if (stubDebug) { - System.out.printf( - "entered parseStubFiles() for %s, ignorejdkastub=%s%n", - atypeFactory.getClass().getSimpleName(), ignorejdkastub); - } - if (!ignorejdkastub) { - // 1. Annotated JDK - // This preps but does not parse the JDK files (except package-info.java files). - // The JDK source code files will be parsed later, on demand. - prepJdkStubs(); - - // 2. jdk.astub - // Only look in .jar files, and parse it right away. - String[] jdkVersions = {"", annotatedJdkVersion}; - for (String jdkVersion : jdkVersions) { - String jdkVersionStub = "jdk" + jdkVersion + ".astub"; - parseOneStubFile(this.getClass(), jdkVersionStub); - parseOneStubFile(checker.getClass(), jdkVersionStub); - } - // This needs to be special-cased for every jdkX.astub for which files exist. :-( - // TODO: not clear what this is supposed to mean - if we are on Java 8, why parse Java - // 11 stub files? - // It would make more sense to parse this if we're e.g. on Java 12. - if (annotatedJdkVersion.equals("8")) { - String jdk11Stub = "jdk11.astub"; - parseOneStubFile(this.getClass(), jdk11Stub); - parseOneStubFile(checker.getClass(), jdk11Stub); - } - } - - // 3. Stub files listed in @StubFiles annotation on the checker - StubFiles stubFilesAnnotation = checker.getClass().getAnnotation(StubFiles.class); - if (stubFilesAnnotation != null) { - parseAnnotationFiles( - Arrays.asList(stubFilesAnnotation.value()), AnnotationFileType.BUILTIN_STUB); - } - - // 4. Stub files returned by the `getExtraStubFiles()` method - parseAnnotationFiles(checker.getExtraStubFiles(), AnnotationFileType.BUILTIN_STUB); - - // 5. Stub files provided via -Astubs command-line option - String stubsOption = checker.getOption("stubs"); - if (stubsOption != null) { - parseAnnotationFiles( - SystemUtil.PATH_SEPARATOR_SPLITTER.splitToList(stubsOption), - AnnotationFileType.COMMAND_LINE_STUB); - } - - --parsingCount; - assert parsingCount == 0; - - if (stubDebug) { - System.out.printf( - "exited parseStubFiles() for %s%n", atypeFactory.getClass().getSimpleName()); - } + // 3. Stub files listed in @StubFiles annotation on the checker + StubFiles stubFilesAnnotation = checker.getClass().getAnnotation(StubFiles.class); + if (stubFilesAnnotation != null) { + parseAnnotationFiles( + Arrays.asList(stubFilesAnnotation.value()), AnnotationFileType.BUILTIN_STUB); } - /** - * Parse one .astub file. - * - * @param checkerClass the location of the resource in the checker.jar file - * @param stubFileName the basename of the .astub file - */ - private void parseOneStubFile(Class checkerClass, String stubFileName) { - BaseTypeChecker checker = atypeFactory.getChecker(); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - try (InputStream jdkVersionStubIn = checkerClass.getResourceAsStream(stubFileName)) { - if (jdkVersionStubIn != null) { - if (stubDebug) { - AnnotationFileParser.stubDebugStatic( - processingEnv, - "parseOneStubFile(%s, %s): jdkVersionStubIn = %s%n", - checkerClass.getSimpleName(), - stubFileName, - jdkVersionStubIn); - } - AnnotationFileParser.parseStubFile( - checkerClass.getResource(stubFileName).toString(), - jdkVersionStubIn, - atypeFactory, - processingEnv, - annotationFileAnnos, - AnnotationFileType.BUILTIN_STUB, - this); - } - } catch (IOException e) { - checker.message( - Diagnostic.Kind.NOTE, - "Could not read annotation resource from " - + checkerClass - + ": " - + stubFileName); - } - } + // 4. Stub files returned by the `getExtraStubFiles()` method + parseAnnotationFiles(checker.getExtraStubFiles(), AnnotationFileType.BUILTIN_STUB); - /** Parses the ajava files passed through the -Aajava command-line option. */ - public void parseAjavaFiles() { - assert parsingCount == 0; - ++parsingCount; - // TODO: Error if this is called more than once? - SourceChecker checker = atypeFactory.getChecker(); - List ajavaFiles = checker.getStringsOption("ajava", File.pathSeparator); - - parseAnnotationFiles(ajavaFiles, AnnotationFileType.AJAVA); - --parsingCount; - assert parsingCount == 0; + // 5. Stub files provided via -Astubs command-line option + String stubsOption = checker.getOption("stubs"); + if (stubsOption != null) { + parseAnnotationFiles( + SystemUtil.PATH_SEPARATOR_SPLITTER.splitToList(stubsOption), + AnnotationFileType.COMMAND_LINE_STUB); } - /** - * Parses the ajava file at {@code ajavaPath} assuming {@code root} represents the compilation - * unit of that file. Uses {@code root} to get information from javac on specific elements of - * {@code ajavaPath}, enabling storage of more detailed annotation information than with just - * the ajava file. - * - * @param ajavaPath path to an ajava file - * @param root javac tree for the compilation unit stored in {@code ajavaFile} - */ - public void parseAjavaFileWithTree(String ajavaPath, CompilationUnitTree root) { - assert parsingCount == 0; - ++parsingCount; - SourceChecker checker = atypeFactory.getChecker(); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - try (InputStream in = new FileInputStream(ajavaPath)) { - if (stubDebug) { - AnnotationFileParser.stubDebugStatic( - processingEnv, - "parseAjavaFileWithTree(%s, %s): checker = %s, in = %s%n", - ajavaPath, - System.identityHashCode(root), - checker.getClass().getSimpleName(), - in); - } - AnnotationFileParser.parseAjavaFile( - ajavaPath, in, root, atypeFactory, processingEnv, annotationFileAnnos, this); - } catch (IOException e) { - checker.message(Diagnostic.Kind.NOTE, "Could not read ajava file: " + ajavaPath); - } + --parsingCount; + assert parsingCount == 0; - --parsingCount; - assert parsingCount == 0; + if (stubDebug) { + System.out.printf( + "exited parseStubFiles() for %s%n", atypeFactory.getClass().getSimpleName()); } - - /** - * Parses the files in {@code annotationFiles} of the given file type. This includes files - * listed directly in {@code annotationFiles} and for each listed directory, also includes all - * files located in that directory (recursively). - * - * @param annotationFiles list of files and directories to parse - * @param fileType the file type of files to parse - */ - @SuppressWarnings("builder:required.method.not.called" // `allFiles` may contain multiple - // JarEntryAnnotationFileResource. Each of those references a zip file entry resource, which - // itself references a ZipFile resource -- the same ZipFile for multiple zip file entries. - // Closing any one of the zip file entries will close the ZipFile, which invalidates the - // other zipfile entries. Therefore, this code does not close any of them. This code may - // leak resources. - ) - private void parseAnnotationFiles(List annotationFiles, AnnotationFileType fileType) { - SourceChecker checker = atypeFactory.getChecker(); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + } + + /** + * Parse one .astub file. + * + * @param checkerClass the location of the resource in the checker.jar file + * @param stubFileName the basename of the .astub file + */ + private void parseOneStubFile(Class checkerClass, String stubFileName) { + BaseTypeChecker checker = atypeFactory.getChecker(); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + try (InputStream jdkVersionStubIn = checkerClass.getResourceAsStream(stubFileName)) { + if (jdkVersionStubIn != null) { if (stubDebug) { - AnnotationFileParser.stubDebugStatic( - processingEnv, "AFET.parseAnnotationFiles(%s, %s)", annotationFiles, fileType); - } - for (String path : annotationFiles) { - // Special case when running in jtreg. - String base = System.getProperty("test.src"); - String fullPath = (base == null) ? path : base + "/" + path; - - List allFiles = - AnnotationFileUtil.allAnnotationFiles(fullPath, fileType); - if (allFiles != null) { - for (AnnotationFileResource resource : allFiles) { - // See note with the SuppressWarnings on this method for why this is not a - // try-with-resources. - BufferedInputStream annotationFileStream; - try { - annotationFileStream = new BufferedInputStream(resource.getInputStream()); - } catch (IOException e) { - checker.message( - Diagnostic.Kind.NOTE, - "Could not read annotation resource: " + resource.getDescription()); - continue; - } - // We use parseStubFile here even for ajava files because at this stage - // ajava files are parsed as stub files. The extra annotation data in an - // ajava file is parsed when type-checking the ajava file's corresponding - // Java file. - AnnotationFileParser.parseStubFile( - resource.getDescription(), - annotationFileStream, - atypeFactory, - processingEnv, - annotationFileAnnos, - fileType == AnnotationFileType.AJAVA - ? AnnotationFileType.AJAVA_AS_STUB - : fileType, - this); - } - } else { - // We didn't find the files. - // If the file has a prefix of "checker.jar/" then look for the file in the top - // level directory of the jar that contains the checker. - if (path.startsWith("checker.jar/")) { - // Note the missing `/` here - this makes sure that `path` starts with `/`. - path = path.substring("checker.jar".length()); - } - boolean issueWarning; - try (InputStream in = checker.getClass().getResourceAsStream(path)) { - if (in != null) { - AnnotationFileParser.parseStubFile( - path, - in, - atypeFactory, - processingEnv, - annotationFileAnnos, - fileType, - this); - issueWarning = false; - } else { - issueWarning = true; - } - } catch (IOException e) { - issueWarning = true; - checker.message( - Diagnostic.Kind.NOTE, "Could not read annotation resource: " + path); - } - - if (issueWarning) { - // Didn't find the file. Possibly issue a warning. - - // When using a compound checker, the target file may be found by the - // current checker's parent checkers. Also check this to avoid a false - // warning. Currently, only the original checker will try to parse the - // target file, the parent checkers are only used to reduce false - // warnings. - SourceChecker currentChecker = checker; - boolean findByParentCheckers = false; - while (currentChecker != null) { - URL normalResource = currentChecker.getClass().getResource(path); - if (normalResource != null) { - // If the parent checker supports the stub file, there is no need - // for a warning. - findByParentCheckers = true; - break; - } - // See whether the stub file is mis-placed and issue a helpful warning. - URL topLevelResource = currentChecker.getClass().getResource("/" + path); - if (topLevelResource != null) { - currentChecker.message( - Diagnostic.Kind.WARNING, - path - + " should be in the same directory as " - + currentChecker.getClass().getSimpleName() - + ".class, but is at the top level of a jar file: " - + topLevelResource); - findByParentCheckers = true; - break; - } else { - currentChecker = currentChecker.getParentChecker(); - } - } - // If there exists one parent checker that can find this file, don't report - // a warning. - if (!findByParentCheckers) { - File parentPath = new File(path).getParentFile(); - String parentPathDescription = - (parentPath == null - ? "current directory" - : "directory " + parentPath.getAbsolutePath()); - String msg = - checker.getClass().getSimpleName() - + " did not find annotation file or directory " - + path - + " on classpath or within " - + parentPathDescription - + (fullPath.equals(path) ? "" : (" or at " + fullPath)); - StringJoiner sj = new StringJoiner(System.lineSeparator() + " "); - sj.add(msg); - /* - sj.add("Classpath:"); - for (URI uri : new ClassGraph().getClasspathURIs()) { - sj.add(uri.toString()); - } - */ - checker.message(Diagnostic.Kind.WARNING, sj.toString()); - } - } - } + AnnotationFileParser.stubDebugStatic( + processingEnv, + "parseOneStubFile(%s, %s): jdkVersionStubIn = %s%n", + checkerClass.getSimpleName(), + stubFileName, + jdkVersionStubIn); } + AnnotationFileParser.parseStubFile( + checkerClass.getResource(stubFileName).toString(), + jdkVersionStubIn, + atypeFactory, + processingEnv, + annotationFileAnnos, + AnnotationFileType.BUILTIN_STUB, + this); + } + } catch (IOException e) { + checker.message( + Diagnostic.Kind.NOTE, + "Could not read annotation resource from " + checkerClass + ": " + stubFileName); } - - /** - * Returns the annotated type for {@code e} containing only annotations explicitly written in an - * annotation file. Returns {@code null} if {@code e} does not appear in an annotation file. - * - * @param e an Element whose type is returned - * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written - * in the annotation file and in the element. Returns {@code null} if {@code element} does - * not appear in an annotation file. - */ - public @Nullable AnnotatedTypeMirror getAnnotatedTypeMirror(Element e) { - maybeParseEnclosingJdkClass(e); - AnnotatedTypeMirror type = annotationFileAnnos.atypes.get(e); - return type == null ? null : type.deepCopy(); + } + + /** Parses the ajava files passed through the -Aajava command-line option. */ + public void parseAjavaFiles() { + assert parsingCount == 0; + ++parsingCount; + // TODO: Error if this is called more than once? + SourceChecker checker = atypeFactory.getChecker(); + List ajavaFiles = checker.getStringsOption("ajava", File.pathSeparator); + + parseAnnotationFiles(ajavaFiles, AnnotationFileType.AJAVA); + --parsingCount; + assert parsingCount == 0; + } + + /** + * Parses the ajava file at {@code ajavaPath} assuming {@code root} represents the compilation + * unit of that file. Uses {@code root} to get information from javac on specific elements of + * {@code ajavaPath}, enabling storage of more detailed annotation information than with just the + * ajava file. + * + * @param ajavaPath path to an ajava file + * @param root javac tree for the compilation unit stored in {@code ajavaFile} + */ + public void parseAjavaFileWithTree(String ajavaPath, CompilationUnitTree root) { + assert parsingCount == 0; + ++parsingCount; + SourceChecker checker = atypeFactory.getChecker(); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + try (InputStream in = new FileInputStream(ajavaPath)) { + if (stubDebug) { + AnnotationFileParser.stubDebugStatic( + processingEnv, + "parseAjavaFileWithTree(%s, %s): checker = %s, in = %s%n", + ajavaPath, + System.identityHashCode(root), + checker.getClass().getSimpleName(), + in); + } + AnnotationFileParser.parseAjavaFile( + ajavaPath, in, root, atypeFactory, processingEnv, annotationFileAnnos, this); + } catch (IOException e) { + checker.message(Diagnostic.Kind.NOTE, "Could not read ajava file: " + ajavaPath); } - /** - * Returns the set of declaration annotations for {@code e} containing only annotations - * explicitly written in an annotation file or the empty set if {@code e} does not appear in an - * annotation file. - * - * @param elt element for which annotations are returned - * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written - * in the annotation file and in the element. {@code null} is returned if {@code element} - * does not appear in an annotation file. - */ - public @Nullable AnnotationMirrorSet getDeclAnnotations(Element elt) { - if (stubDebug) { - if (isParsing()) { - System.out.printf("AFET.getDeclAnnotations(%s [%s])%n", elt, elt.getClass()); - } else { - System.out.printf( - "AFET.getDeclAnnotations(%s [%s]) IS NOT PARSING%n", elt, elt.getClass()); - } + --parsingCount; + assert parsingCount == 0; + } + + /** + * Parses the files in {@code annotationFiles} of the given file type. This includes files listed + * directly in {@code annotationFiles} and for each listed directory, also includes all files + * located in that directory (recursively). + * + * @param annotationFiles list of files and directories to parse + * @param fileType the file type of files to parse + */ + @SuppressWarnings("builder:required.method.not.called" // `allFiles` may contain multiple + // JarEntryAnnotationFileResource. Each of those references a zip file entry resource, which + // itself references a ZipFile resource -- the same ZipFile for multiple zip file entries. + // Closing any one of the zip file entries will close the ZipFile, which invalidates the + // other zipfile entries. Therefore, this code does not close any of them. This code may + // leak resources. + ) + private void parseAnnotationFiles(List annotationFiles, AnnotationFileType fileType) { + SourceChecker checker = atypeFactory.getChecker(); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + if (stubDebug) { + AnnotationFileParser.stubDebugStatic( + processingEnv, "AFET.parseAnnotationFiles(%s, %s)", annotationFiles, fileType); + } + for (String path : annotationFiles) { + // Special case when running in jtreg. + String base = System.getProperty("test.src"); + String fullPath = (base == null) ? path : base + "/" + path; + + List allFiles = + AnnotationFileUtil.allAnnotationFiles(fullPath, fileType); + if (allFiles != null) { + for (AnnotationFileResource resource : allFiles) { + // See note with the SuppressWarnings on this method for why this is not a + // try-with-resources. + BufferedInputStream annotationFileStream; + try { + annotationFileStream = new BufferedInputStream(resource.getInputStream()); + } catch (IOException e) { + checker.message( + Diagnostic.Kind.NOTE, + "Could not read annotation resource: " + resource.getDescription()); + continue; + } + // We use parseStubFile here even for ajava files because at this stage + // ajava files are parsed as stub files. The extra annotation data in an + // ajava file is parsed when type-checking the ajava file's corresponding + // Java file. + AnnotationFileParser.parseStubFile( + resource.getDescription(), + annotationFileStream, + atypeFactory, + processingEnv, + annotationFileAnnos, + fileType == AnnotationFileType.AJAVA ? AnnotationFileType.AJAVA_AS_STUB : fileType, + this); } - - maybeParseEnclosingJdkClass(elt); - String eltName = ElementUtils.getQualifiedName(elt); - if (annotationFileAnnos.declAnnos.containsKey(eltName)) { - return annotationFileAnnos.declAnnos.get(eltName); - } else { - // Handle annotations on record declarations. - boolean canTransferAnnotationsToSameName; - Element enclosingType; // Do nothing unless this element is a record. - switch (elt.getKind()) { - case METHOD: - // Annotations transfer to zero-arg accessor methods of same name: - canTransferAnnotationsToSameName = - ((ExecutableElement) elt).getParameters().isEmpty(); - enclosingType = elt.getEnclosingElement(); - break; - case FIELD: - // Annotations transfer to fields of same name: - canTransferAnnotationsToSameName = true; - enclosingType = elt.getEnclosingElement(); - break; - case PARAMETER: - // Annotations transfer to compact canonical constructor parameter of same name: - canTransferAnnotationsToSameName = - ElementUtils.isCompactCanonicalRecordConstructor( - elt.getEnclosingElement()) - && elt.getEnclosingElement().getKind() - == ElementKind.CONSTRUCTOR; - enclosingType = elt.getEnclosingElement().getEnclosingElement(); - break; - default: - canTransferAnnotationsToSameName = false; - enclosingType = null; - break; - } - - if (canTransferAnnotationsToSameName && ElementUtils.isRecordElement(enclosingType)) { - AnnotationFileParser.RecordStub recordStub = - annotationFileAnnos.records.get(enclosingType.getSimpleName().toString()); - if (recordStub != null - && recordStub.componentsByName.containsKey( - elt.getSimpleName().toString())) { - RecordComponentStub recordComponentStub = - recordStub.componentsByName.get(elt.getSimpleName().toString()); - return recordComponentStub.getAnnotationsForTarget(elt.getKind()); - } - } + } else { + // We didn't find the files. + // If the file has a prefix of "checker.jar/" then look for the file in the top + // level directory of the jar that contains the checker. + if (path.startsWith("checker.jar/")) { + // Note the missing `/` here - this makes sure that `path` starts with `/`. + path = path.substring("checker.jar".length()); } - return AnnotationMirrorSet.emptySet(); - } - - /** - * Adds annotations from stub files for the corresponding record components (if the given - * constructor/method is the canonical constructor or a record accessor). Such transfer is - * automatically done by javac usually, but not from stubs. - * - * @param types a Types instance used for checking type equivalence - * @param elt a member. This method does nothing if it's not a method or constructor. - * @param memberType the type corresponding to the element elt; side-effected by this method - */ - public void injectRecordComponentType( - Types types, Element elt, AnnotatedExecutableType memberType) { - if (isParsing()) { - throw new BugInCF("parsing while calling injectRecordComponentType"); + boolean issueWarning; + try (InputStream in = checker.getClass().getResourceAsStream(path)) { + if (in != null) { + AnnotationFileParser.parseStubFile( + path, in, atypeFactory, processingEnv, annotationFileAnnos, fileType, this); + issueWarning = false; + } else { + issueWarning = true; + } + } catch (IOException e) { + issueWarning = true; + checker.message(Diagnostic.Kind.NOTE, "Could not read annotation resource: " + path); } - if (elt.getKind() == ElementKind.METHOD) { - if (((ExecutableElement) elt).getParameters().isEmpty()) { - String recordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); - AnnotationFileParser.RecordStub recordComponentType = - annotationFileAnnos.records.get(recordName); - if (recordComponentType != null) { - // If the record component has an annotation in the stub, the component - // annotation replaces any from the same hierarchy on the accessor method, - // unless there is an accessor in the stubs file (which may or may not have an - // annotation in the same hierarchy; the user may want to specify the annotation - // or deliberately not annotate the accessor). - // We thus only replace the method annotation with the component annotation - // if there is no accessor in the stubs file: - RecordComponentStub recordComponentStub = - recordComponentType.componentsByName.get( - elt.getSimpleName().toString()); - if (recordComponentStub != null && !recordComponentStub.hasAccessorInStubs()) { - memberType - .getReturnType() - .replaceAnnotations(recordComponentStub.type.getAnnotations()); - } - } + if (issueWarning) { + // Didn't find the file. Possibly issue a warning. + + // When using a compound checker, the target file may be found by the + // current checker's parent checkers. Also check this to avoid a false + // warning. Currently, only the original checker will try to parse the + // target file, the parent checkers are only used to reduce false + // warnings. + SourceChecker currentChecker = checker; + boolean findByParentCheckers = false; + while (currentChecker != null) { + URL normalResource = currentChecker.getClass().getResource(path); + if (normalResource != null) { + // If the parent checker supports the stub file, there is no need + // for a warning. + findByParentCheckers = true; + break; } - } else if (elt.getKind() == ElementKind.CONSTRUCTOR) { - if (AnnotationFileUtil.isCanonicalConstructor((ExecutableElement) elt, types)) { - TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); - AnnotationFileParser.RecordStub recordComponentType = - annotationFileAnnos.records.get(enclosing.getQualifiedName().toString()); - if (recordComponentType != null) { - List componentsInCanonicalConstructor = - recordComponentType.getComponentsInCanonicalConstructor(); - if (componentsInCanonicalConstructor != null) { - for (int i = 0; i < componentsInCanonicalConstructor.size(); i++) { - memberType - .getParameterTypes() - .get(i) - .replaceAnnotations( - componentsInCanonicalConstructor - .get(i) - .getAnnotations()); - } - } - } + // See whether the stub file is mis-placed and issue a helpful warning. + URL topLevelResource = currentChecker.getClass().getResource("/" + path); + if (topLevelResource != null) { + currentChecker.message( + Diagnostic.Kind.WARNING, + path + + " should be in the same directory as " + + currentChecker.getClass().getSimpleName() + + ".class, but is at the top level of a jar file: " + + topLevelResource); + findByParentCheckers = true; + break; + } else { + currentChecker = currentChecker.getParentChecker(); } + } + // If there exists one parent checker that can find this file, don't report + // a warning. + if (!findByParentCheckers) { + File parentPath = new File(path).getParentFile(); + String parentPathDescription = + (parentPath == null + ? "current directory" + : "directory " + parentPath.getAbsolutePath()); + String msg = + checker.getClass().getSimpleName() + + " did not find annotation file or directory " + + path + + " on classpath or within " + + parentPathDescription + + (fullPath.equals(path) ? "" : (" or at " + fullPath)); + StringJoiner sj = new StringJoiner(System.lineSeparator() + " "); + sj.add(msg); + /* + sj.add("Classpath:"); + for (URI uri : new ClassGraph().getClasspathURIs()) { + sj.add(uri.toString()); + } + */ + checker.message(Diagnostic.Kind.WARNING, sj.toString()); + } } + } + } + } + + /** + * Returns the annotated type for {@code e} containing only annotations explicitly written in an + * annotation file. Returns {@code null} if {@code e} does not appear in an annotation file. + * + * @param e an Element whose type is returned + * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in + * the annotation file and in the element. Returns {@code null} if {@code element} does not + * appear in an annotation file. + */ + public @Nullable AnnotatedTypeMirror getAnnotatedTypeMirror(Element e) { + maybeParseEnclosingJdkClass(e); + AnnotatedTypeMirror type = annotationFileAnnos.atypes.get(e); + return type == null ? null : type.deepCopy(); + } + + /** + * Returns the set of declaration annotations for {@code e} containing only annotations explicitly + * written in an annotation file or the empty set if {@code e} does not appear in an annotation + * file. + * + * @param elt element for which annotations are returned + * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in + * the annotation file and in the element. {@code null} is returned if {@code element} does + * not appear in an annotation file. + */ + public @Nullable AnnotationMirrorSet getDeclAnnotations(Element elt) { + if (stubDebug) { + if (isParsing()) { + System.out.printf("AFET.getDeclAnnotations(%s [%s])%n", elt, elt.getClass()); + } else { + System.out.printf("AFET.getDeclAnnotations(%s [%s]) IS NOT PARSING%n", elt, elt.getClass()); + } } - /** - * Returns the method type of the most specific fake override for the given element, when used - * as a member of the given type. - * - * @param elt element for which annotations are returned - * @param receiverType the type of the class that contains member (or a subtype of it) - * @return the most specific AnnotatedTypeMirror for {@code elt} that is a fake override, or - * null if there are no fake overrides - */ - public @Nullable AnnotatedExecutableType getFakeOverride( - Element elt, AnnotatedTypeMirror receiverType) { - if (isParsing()) { - throw new BugInCF("parsing while calling getFakeOverride"); - } - - if (elt.getKind() != ElementKind.METHOD) { - return null; + maybeParseEnclosingJdkClass(elt); + String eltName = ElementUtils.getQualifiedName(elt); + if (annotationFileAnnos.declAnnos.containsKey(eltName)) { + return annotationFileAnnos.declAnnos.get(eltName); + } else { + // Handle annotations on record declarations. + boolean canTransferAnnotationsToSameName; + Element enclosingType; // Do nothing unless this element is a record. + switch (elt.getKind()) { + case METHOD: + // Annotations transfer to zero-arg accessor methods of same name: + canTransferAnnotationsToSameName = ((ExecutableElement) elt).getParameters().isEmpty(); + enclosingType = elt.getEnclosingElement(); + break; + case FIELD: + // Annotations transfer to fields of same name: + canTransferAnnotationsToSameName = true; + enclosingType = elt.getEnclosingElement(); + break; + case PARAMETER: + // Annotations transfer to compact canonical constructor parameter of same name: + canTransferAnnotationsToSameName = + ElementUtils.isCompactCanonicalRecordConstructor(elt.getEnclosingElement()) + && elt.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR; + enclosingType = elt.getEnclosingElement().getEnclosingElement(); + break; + default: + canTransferAnnotationsToSameName = false; + enclosingType = null; + break; + } + + if (canTransferAnnotationsToSameName && ElementUtils.isRecordElement(enclosingType)) { + AnnotationFileParser.RecordStub recordStub = + annotationFileAnnos.records.get(enclosingType.getSimpleName().toString()); + if (recordStub != null + && recordStub.componentsByName.containsKey(elt.getSimpleName().toString())) { + RecordComponentStub recordComponentStub = + recordStub.componentsByName.get(elt.getSimpleName().toString()); + return recordComponentStub.getAnnotationsForTarget(elt.getKind()); } + } + } + return AnnotationMirrorSet.emptySet(); + } + + /** + * Adds annotations from stub files for the corresponding record components (if the given + * constructor/method is the canonical constructor or a record accessor). Such transfer is + * automatically done by javac usually, but not from stubs. + * + * @param types a Types instance used for checking type equivalence + * @param elt a member. This method does nothing if it's not a method or constructor. + * @param memberType the type corresponding to the element elt; side-effected by this method + */ + public void injectRecordComponentType( + Types types, Element elt, AnnotatedExecutableType memberType) { + if (isParsing()) { + throw new BugInCF("parsing while calling injectRecordComponentType"); + } - ExecutableElement method = (ExecutableElement) elt; - - // This is a list of pairs of (where defined, method type) for fake overrides. The second - // element of each pair is currently always an AnnotatedExecutableType. - List> candidates = - annotationFileAnnos.fakeOverrides.get(method); - - if (candidates == null || candidates.isEmpty()) { - return null; + if (elt.getKind() == ElementKind.METHOD) { + if (((ExecutableElement) elt).getParameters().isEmpty()) { + String recordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); + AnnotationFileParser.RecordStub recordComponentType = + annotationFileAnnos.records.get(recordName); + if (recordComponentType != null) { + // If the record component has an annotation in the stub, the component + // annotation replaces any from the same hierarchy on the accessor method, + // unless there is an accessor in the stubs file (which may or may not have an + // annotation in the same hierarchy; the user may want to specify the annotation + // or deliberately not annotate the accessor). + // We thus only replace the method annotation with the component annotation + // if there is no accessor in the stubs file: + RecordComponentStub recordComponentStub = + recordComponentType.componentsByName.get(elt.getSimpleName().toString()); + if (recordComponentStub != null && !recordComponentStub.hasAccessorInStubs()) { + memberType + .getReturnType() + .replaceAnnotations(recordComponentStub.type.getAnnotations()); + } } - - TypeMirror receiverTypeMirror = receiverType.getUnderlyingType(); - - // A list of fake receiver types. - List applicableClasses = new ArrayList<>(); - List applicableInterfaces = new ArrayList<>(); - for (IPair candidatePair : candidates) { - TypeMirror fakeLocation = candidatePair.first; - AnnotatedExecutableType candidate = (AnnotatedExecutableType) candidatePair.second; - if (atypeFactory.types.isSameType(receiverTypeMirror, fakeLocation)) { - return candidate; - } else if (atypeFactory.types.isSubtype(receiverTypeMirror, fakeLocation)) { - TypeElement fakeElement = TypesUtils.getTypeElement(fakeLocation); - switch (fakeElement.getKind()) { - case CLASS: - case ENUM: - applicableClasses.add(fakeLocation); - break; - case INTERFACE: - case ANNOTATION_TYPE: - applicableInterfaces.add(fakeLocation); - break; - default: - throw new BugInCF( - "What type? %s %s %s", - fakeElement.getKind(), fakeElement.getClass(), fakeElement); - } + } + } else if (elt.getKind() == ElementKind.CONSTRUCTOR) { + if (AnnotationFileUtil.isCanonicalConstructor((ExecutableElement) elt, types)) { + TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); + AnnotationFileParser.RecordStub recordComponentType = + annotationFileAnnos.records.get(enclosing.getQualifiedName().toString()); + if (recordComponentType != null) { + List componentsInCanonicalConstructor = + recordComponentType.getComponentsInCanonicalConstructor(); + if (componentsInCanonicalConstructor != null) { + for (int i = 0; i < componentsInCanonicalConstructor.size(); i++) { + memberType + .getParameterTypes() + .get(i) + .replaceAnnotations(componentsInCanonicalConstructor.get(i).getAnnotations()); } + } } + } + } + } + + /** + * Returns the method type of the most specific fake override for the given element, when used as + * a member of the given type. + * + * @param elt element for which annotations are returned + * @param receiverType the type of the class that contains member (or a subtype of it) + * @return the most specific AnnotatedTypeMirror for {@code elt} that is a fake override, or null + * if there are no fake overrides + */ + public @Nullable AnnotatedExecutableType getFakeOverride( + Element elt, AnnotatedTypeMirror receiverType) { + if (isParsing()) { + throw new BugInCF("parsing while calling getFakeOverride"); + } - if (applicableClasses.isEmpty() && applicableInterfaces.isEmpty()) { - return null; - } - TypeMirror fakeReceiverType = - TypesUtils.mostSpecific( - !applicableClasses.isEmpty() ? applicableClasses : applicableInterfaces, - atypeFactory.getProcessingEnv()); - if (fakeReceiverType == null) { - StringJoiner message = new StringJoiner(System.lineSeparator()); - message.add( - String.format( - "No most specific fake override found for %s with receiver %s." - + " These fake overrides are applicable:", - elt, receiverTypeMirror)); - for (TypeMirror candidate : applicableClasses) { - message.add(" class candidate: " + candidate); - } - for (TypeMirror candidate : applicableInterfaces) { - message.add(" interface candidate: " + candidate); - } - throw new BugInCF(message.toString()); - } - - for (IPair candidatePair : candidates) { - TypeMirror candidateReceiverType = candidatePair.first; - if (atypeFactory.types.isSameType(fakeReceiverType, candidateReceiverType)) { - return (AnnotatedExecutableType) candidatePair.second; - } - } - - throw new BugInCF( - "No match for %s in %s %s %s", - fakeReceiverType, candidates, applicableClasses, applicableInterfaces); + if (elt.getKind() != ElementKind.METHOD) { + return null; } - /// - /// End of public methods, private helper methods follow - /// - - /** - * Parses the outermost enclosing class of {@code e} if it is in the annotated JDK and it has - * not already been parsed. - * - * @param e element whose outermost enclosing class might be parsed, if it is in the JDK and has - * not already been parsed - */ - private void maybeParseEnclosingJdkClass(Element e) { - if (stubDebug) { - System.out.printf( - "maybeParseEnclosingJdkClass(%s encloses %s), shouldParseJdk=%s%n", - getOutermostEnclosingClass(e), e, shouldParseJdk); - } + ExecutableElement method = (ExecutableElement) elt; - if (!shouldParseJdk - || e.getKind() == ElementKind.PACKAGE - || e.getKind() == ElementKind.MODULE) { - return; - } + // This is a list of pairs of (where defined, method type) for fake overrides. The second + // element of each pair is currently always an AnnotatedExecutableType. + List> candidates = + annotationFileAnnos.fakeOverrides.get(method); - String className = getOutermostEnclosingClass(e); - // `className` can be null if `e` is a package or module element. - if (className == null || className.isEmpty()) { - // TODO: maybe investigate other situations where the enclosing class is missing - // if (e.getKind() != ElementKind.PACKAGE && e.getKind() != - // ElementKind.MODULE) { - // atypeFactory.getChecker().reportWarning(e, "unexpected element " + e + - // " of - // kind " + e.getKind()); - // } - - return; - } + if (candidates == null || candidates.isEmpty()) { + return null; + } - if (processingClasses.contains(className)) { - // TODO: some declaration annotations in the enclosing class may still - // be missing, we can revisit this part if it's causing issues - return; + TypeMirror receiverTypeMirror = receiverType.getUnderlyingType(); + + // A list of fake receiver types. + List applicableClasses = new ArrayList<>(); + List applicableInterfaces = new ArrayList<>(); + for (IPair candidatePair : candidates) { + TypeMirror fakeLocation = candidatePair.first; + AnnotatedExecutableType candidate = (AnnotatedExecutableType) candidatePair.second; + if (atypeFactory.types.isSameType(receiverTypeMirror, fakeLocation)) { + return candidate; + } else if (atypeFactory.types.isSubtype(receiverTypeMirror, fakeLocation)) { + TypeElement fakeElement = TypesUtils.getTypeElement(fakeLocation); + switch (fakeElement.getKind()) { + case CLASS: + case ENUM: + applicableClasses.add(fakeLocation); + break; + case INTERFACE: + case ANNOTATION_TYPE: + applicableInterfaces.add(fakeLocation); + break; + default: + throw new BugInCF( + "What type? %s %s %s", fakeElement.getKind(), fakeElement.getClass(), fakeElement); } + } + } - if (remainingJdkStubFiles.containsKey(className)) { - parseJdkStubFile(remainingJdkStubFiles.remove(className)); - } else if (remainingJdkStubFilesJar.containsKey(className)) { - parseJdkJarEntry(remainingJdkStubFilesJar.remove(className)); - } else { - if (stubDebug) { - System.out.printf(" not in remaining JDK stub files: %s%n", className); - } - } + if (applicableClasses.isEmpty() && applicableInterfaces.isEmpty()) { + return null; + } + TypeMirror fakeReceiverType = + TypesUtils.mostSpecific( + !applicableClasses.isEmpty() ? applicableClasses : applicableInterfaces, + atypeFactory.getProcessingEnv()); + if (fakeReceiverType == null) { + StringJoiner message = new StringJoiner(System.lineSeparator()); + message.add( + String.format( + "No most specific fake override found for %s with receiver %s." + + " These fake overrides are applicable:", + elt, receiverTypeMirror)); + for (TypeMirror candidate : applicableClasses) { + message.add(" class candidate: " + candidate); + } + for (TypeMirror candidate : applicableInterfaces) { + message.add(" interface candidate: " + candidate); + } + throw new BugInCF(message.toString()); } - /** - * Returns the fully qualified name of the outermost enclosing class of {@code e} or {@code - * null} if no such class exists for {@code e}, such as when {@code e} is a package or module - * element. - * - * @param e an element whose outermost enclosing class to return - * @return the canonical name of the outermost enclosing class of {@code e} or {@code null} if - * no class encloses {@code e} - */ - private @Nullable @CanonicalNameOrEmpty String getOutermostEnclosingClass(Element e) { - TypeElement enclosingClass = ElementUtils.enclosingTypeElement(e); - if (enclosingClass == null) { - return null; - } - while (true) { - Element element = enclosingClass.getEnclosingElement(); - if (element == null || element.getKind() == ElementKind.PACKAGE) { - break; - } - TypeElement t = ElementUtils.enclosingTypeElement(element); - if (t == null) { - break; - } - enclosingClass = t; - } - @SuppressWarnings( - "signature:assignment.type.incompatible" // https://tinyurl.com/cfissue/658: - // Name.toString should be @PolySignature - ) - @CanonicalNameOrEmpty String result = enclosingClass.getQualifiedName().toString(); - return result; + for (IPair candidatePair : candidates) { + TypeMirror candidateReceiverType = candidatePair.first; + if (atypeFactory.types.isSameType(fakeReceiverType, candidateReceiverType)) { + return (AnnotatedExecutableType) candidatePair.second; + } } - /** - * Parses the stub file in {@code path}. - * - * @param path path to file to parse - */ - private void parseJdkStubFile(Path path) { - ++parsingCount; - try (FileInputStream jdkStub = new FileInputStream(path.toFile())) { - AnnotationFileParser.parseJdkFileAsStub( - path.toFile().getName(), - jdkStub, - atypeFactory, - atypeFactory.getProcessingEnv(), - annotationFileAnnos, - this); - } catch (IOException e) { - throw new BugInCF("cannot open the jdk stub file " + path, e); - } finally { - --parsingCount; - } + throw new BugInCF( + "No match for %s in %s %s %s", + fakeReceiverType, candidates, applicableClasses, applicableInterfaces); + } + + /// + /// End of public methods, private helper methods follow + /// + + /** + * Parses the outermost enclosing class of {@code e} if it is in the annotated JDK and it has not + * already been parsed. + * + * @param e element whose outermost enclosing class might be parsed, if it is in the JDK and has + * not already been parsed + */ + private void maybeParseEnclosingJdkClass(Element e) { + if (stubDebug) { + System.out.printf( + "maybeParseEnclosingJdkClass(%s encloses %s), shouldParseJdk=%s%n", + getOutermostEnclosingClass(e), e, shouldParseJdk); } - /** - * Parses the stub file in the given jar entry. - * - * @param jarEntryName name of the jar entry to parse - */ - private void parseJdkJarEntry(String jarEntryName) { - if (stubDebug) { - System.out.printf("entered parseJdkJarEntry(%s)%n", jarEntryName); - } + if (!shouldParseJdk + || e.getKind() == ElementKind.PACKAGE + || e.getKind() == ElementKind.MODULE) { + return; + } - JarURLConnection connection = getJarURLConnectionToJdk(); - ++parsingCount; - try (JarFile jarFile = connection.getJarFile()) { - try (InputStream jdkStub = jarFile.getInputStream(jarFile.getJarEntry(jarEntryName))) { - AnnotationFileParser.parseJdkFileAsStub( - jarEntryName, - jdkStub, - atypeFactory, - atypeFactory.getProcessingEnv(), - annotationFileAnnos, - this); - } catch (IOException e) { - throw new BugInCF("cannot open the jdk stub file " + jarEntryName, e); - } - } catch (IOException e) { - throw new BugInCF("cannot open the Jar file " + connection.getEntryName(), e); - } catch (BugInCF e) { - throw new BugInCF("Exception while parsing " + jarEntryName + ": " + e.getMessage(), e); - } finally { - --parsingCount; - } + String className = getOutermostEnclosingClass(e); + // `className` can be null if `e` is a package or module element. + if (className == null || className.isEmpty()) { + // TODO: maybe investigate other situations where the enclosing class is missing + // if (e.getKind() != ElementKind.PACKAGE && e.getKind() != + // ElementKind.MODULE) { + // atypeFactory.getChecker().reportWarning(e, "unexpected element " + e + + // " of + // kind " + e.getKind()); + // } + + return; + } - if (stubDebug) { - System.out.printf("exited parseJdkJarEntry(%s)%n", jarEntryName); - } + if (processingClasses.contains(className)) { + // TODO: some declaration annotations in the enclosing class may still + // be missing, we can revisit this part if it's causing issues + return; } - /** - * Returns a JarURLConnection to "/jdk*". - * - * @return a JarURLConnection to "/jdk*" - */ - private JarURLConnection getJarURLConnectionToJdk() { - URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); - JarURLConnection connection; - try { - connection = (JarURLConnection) resourceURL.openConnection(); + if (remainingJdkStubFiles.containsKey(className)) { + parseJdkStubFile(remainingJdkStubFiles.remove(className)); + } else if (remainingJdkStubFilesJar.containsKey(className)) { + parseJdkJarEntry(remainingJdkStubFilesJar.remove(className)); + } else { + if (stubDebug) { + System.out.printf(" not in remaining JDK stub files: %s%n", className); + } + } + } + + /** + * Returns the fully qualified name of the outermost enclosing class of {@code e} or {@code null} + * if no such class exists for {@code e}, such as when {@code e} is a package or module element. + * + * @param e an element whose outermost enclosing class to return + * @return the canonical name of the outermost enclosing class of {@code e} or {@code null} if no + * class encloses {@code e} + */ + private @Nullable @CanonicalNameOrEmpty String getOutermostEnclosingClass(Element e) { + TypeElement enclosingClass = ElementUtils.enclosingTypeElement(e); + if (enclosingClass == null) { + return null; + } + while (true) { + Element element = enclosingClass.getEnclosingElement(); + if (element == null || element.getKind() == ElementKind.PACKAGE) { + break; + } + TypeElement t = ElementUtils.enclosingTypeElement(element); + if (t == null) { + break; + } + enclosingClass = t; + } + @SuppressWarnings("signature:assignment.type.incompatible" // https://tinyurl.com/cfissue/658: + // Name.toString should be @PolySignature + ) + @CanonicalNameOrEmpty String result = enclosingClass.getQualifiedName().toString(); + return result; + } + + /** + * Parses the stub file in {@code path}. + * + * @param path path to file to parse + */ + private void parseJdkStubFile(Path path) { + ++parsingCount; + try (FileInputStream jdkStub = new FileInputStream(path.toFile())) { + AnnotationFileParser.parseJdkFileAsStub( + path.toFile().getName(), + jdkStub, + atypeFactory, + atypeFactory.getProcessingEnv(), + annotationFileAnnos, + this); + } catch (IOException e) { + throw new BugInCF("cannot open the jdk stub file " + path, e); + } finally { + --parsingCount; + } + } + + /** + * Parses the stub file in the given jar entry. + * + * @param jarEntryName name of the jar entry to parse + */ + private void parseJdkJarEntry(String jarEntryName) { + if (stubDebug) { + System.out.printf("entered parseJdkJarEntry(%s)%n", jarEntryName); + } - // disable caching / connection sharing of the low level URLConnection to the Jarfile - connection.setDefaultUseCaches(false); - connection.setUseCaches(false); + JarURLConnection connection = getJarURLConnectionToJdk(); + ++parsingCount; + try (JarFile jarFile = connection.getJarFile()) { + try (InputStream jdkStub = jarFile.getInputStream(jarFile.getJarEntry(jarEntryName))) { + AnnotationFileParser.parseJdkFileAsStub( + jarEntryName, + jdkStub, + atypeFactory, + atypeFactory.getProcessingEnv(), + annotationFileAnnos, + this); + } catch (IOException e) { + throw new BugInCF("cannot open the jdk stub file " + jarEntryName, e); + } + } catch (IOException e) { + throw new BugInCF("cannot open the Jar file " + connection.getEntryName(), e); + } catch (BugInCF e) { + throw new BugInCF("Exception while parsing " + jarEntryName + ": " + e.getMessage(), e); + } finally { + --parsingCount; + } - connection.connect(); - } catch (IOException e) { - throw new BugInCF( - "cannot open a connection to the Jar file " + resourceURL.getFile(), e); - } - return connection; + if (stubDebug) { + System.out.printf("exited parseJdkJarEntry(%s)%n", jarEntryName); + } + } + + /** + * Returns a JarURLConnection to "/jdk*". + * + * @return a JarURLConnection to "/jdk*" + */ + private JarURLConnection getJarURLConnectionToJdk() { + URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); + JarURLConnection connection; + try { + connection = (JarURLConnection) resourceURL.openConnection(); + + // disable caching / connection sharing of the low level URLConnection to the Jarfile + connection.setDefaultUseCaches(false); + connection.setUseCaches(false); + + connection.connect(); + } catch (IOException e) { + throw new BugInCF("cannot open a connection to the Jar file " + resourceURL.getFile(), e); + } + return connection; + } + + /** + * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from file + * name to the class contained with in it. Also, parses all package-info.java files. + */ + private void prepJdkStubs() { + if (!shouldParseJdk) { + return; + } + URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); + if (stubDebug) { + System.out.printf( + "Loading JDK from class %s and url: %s%n", atypeFactory.getClass(), resourceURL); + } + if (resourceURL == null) { + if (permitMissingJdk) { + return; + } + throw new BugInCF( + "JDK not found for type factory " + atypeFactory.getClass().getSimpleName()); + } else if (resourceURL.getProtocol().contentEquals("jar")) { + prepJdkFromJar(resourceURL); + } else if (resourceURL.getProtocol().contentEquals("file")) { + prepJdkFromFile(resourceURL); + } else { + if (permitMissingJdk) { + return; + } + throw new BugInCF( + "JDK not found in " + + resourceURL + + ". Unsupported protocol: " + + resourceURL.getProtocol()); + } + } + + /** + * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from file + * name to the class contained with in it. Also, parses all package-info.java files. + * + * @param jdkDirectory the URL pointing to the JDK directory + */ + private void prepJdkFromFile(URL jdkDirectory) { + Path root; + try { + root = Paths.get(jdkDirectory.toURI()); + } catch (URISyntaxException e) { + throw new BugInCF("Cannot parse URL: " + jdkDirectory.toString(), e); } - /** - * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from - * file name to the class contained with in it. Also, parses all package-info.java files. - */ - private void prepJdkStubs() { - if (!shouldParseJdk) { - return; + try (Stream walk = Files.walk(root)) { + List paths = + walk.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java")) + .collect(Collectors.toList()); + paths.sort(Path::compareTo); + for (Path path : paths) { + if (path.getFileName().toString().equals("package-info.java")) { + parseJdkStubFile(path); + continue; } - URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); - if (stubDebug) { - System.out.printf( - "Loading JDK from class %s and url: %s%n", - atypeFactory.getClass(), resourceURL); + if (path.getFileName().toString().equals("module-info.java")) { + // JavaParser can't parse module-info files, so skip them. + continue; } - if (resourceURL == null) { - if (permitMissingJdk) { - return; - } - throw new BugInCF( - "JDK not found for type factory " + atypeFactory.getClass().getSimpleName()); - } else if (resourceURL.getProtocol().contentEquals("jar")) { - prepJdkFromJar(resourceURL); - } else if (resourceURL.getProtocol().contentEquals("file")) { - prepJdkFromFile(resourceURL); - } else { - if (permitMissingJdk) { - return; - } - throw new BugInCF( - "JDK not found in " - + resourceURL - + ". Unsupported protocol: " - + resourceURL.getProtocol()); + if (parseAllJdkFiles) { + parseJdkStubFile(path); + continue; } + Path relativePath = root.relativize(path); + // The number 4 is to strip off "/src//share/classes". + Path savepath = relativePath.subpath(4, relativePath.getNameCount()); + String savepathString = savepath.toString(); + // The number 5 is to remove trailing ".java". + String savepathWithoutExtension = savepathString.substring(0, savepathString.length() - 5); + String fqName = savepathWithoutExtension.replace(File.separatorChar, '.'); + remainingJdkStubFiles.put(fqName, path); + } + if (stubDebug) { + System.out.printf( + "Contents of remainingJdkStubFiles for %s from %s:%n", + atypeFactory.getClass().getSimpleName(), jdkDirectory); + printSortedIndented(remainingJdkStubFiles.keySet()); + System.out.printf( + "End of remainingJdkStubFiles for %s from %s.%n", + atypeFactory.getClass().getSimpleName(), jdkDirectory); + } + } catch (IOException e) { + throw new BugInCF("prepJdkFromFile(" + jdkDirectory + ")", e); } - - /** - * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from - * file name to the class contained with in it. Also, parses all package-info.java files. - * - * @param jdkDirectory the URL pointing to the JDK directory - */ - private void prepJdkFromFile(URL jdkDirectory) { - Path root; - try { - root = Paths.get(jdkDirectory.toURI()); - } catch (URISyntaxException e) { - throw new BugInCF("Cannot parse URL: " + jdkDirectory.toString(), e); + } + + /** + * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFilesJar}, from + * file name to the class contained with in it. Also, parses all package-info.java files. + * + * @param jdkJarfile the URL pointing to the JDK jarfile + */ + private void prepJdkFromJar(@SuppressWarnings("UnusedVariable") URL jdkJarfile) { + JarURLConnection connection = getJarURLConnectionToJdk(); + + try (JarFile jarFile = connection.getJarFile()) { + ArrayList entries = CollectionsPlume.makeArrayList(jarFile.entries()); + entries.sort(Comparator.comparing(Object::toString)); + for (JarEntry jarEntry : entries) { + // filter out directories and non-Java files + if (jarEntry.isDirectory()) { + continue; } - - try (Stream walk = Files.walk(root)) { - List paths = - walk.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java")) - .collect(Collectors.toList()); - paths.sort(Path::compareTo); - for (Path path : paths) { - if (path.getFileName().toString().equals("package-info.java")) { - parseJdkStubFile(path); - continue; - } - if (path.getFileName().toString().equals("module-info.java")) { - // JavaParser can't parse module-info files, so skip them. - continue; - } - if (parseAllJdkFiles) { - parseJdkStubFile(path); - continue; - } - Path relativePath = root.relativize(path); - // The number 4 is to strip off "/src//share/classes". - Path savepath = relativePath.subpath(4, relativePath.getNameCount()); - String savepathString = savepath.toString(); - // The number 5 is to remove trailing ".java". - String savepathWithoutExtension = - savepathString.substring(0, savepathString.length() - 5); - String fqName = savepathWithoutExtension.replace(File.separatorChar, '.'); - remainingJdkStubFiles.put(fqName, path); - } - if (stubDebug) { - System.out.printf( - "Contents of remainingJdkStubFiles for %s from %s:%n", - atypeFactory.getClass().getSimpleName(), jdkDirectory); - printSortedIndented(remainingJdkStubFiles.keySet()); - System.out.printf( - "End of remainingJdkStubFiles for %s from %s.%n", - atypeFactory.getClass().getSimpleName(), jdkDirectory); - } - } catch (IOException e) { - throw new BugInCF("prepJdkFromFile(" + jdkDirectory + ")", e); + String jarEntryName = jarEntry.getName(); + if (!(jarEntryName.startsWith("annotated-jdk") && jarEntryName.endsWith(".java")) + // JavaParser can't parse module-info files, so skip them. + || jarEntryName.endsWith("module-info.java")) { + continue; } - } - - /** - * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFilesJar}, from - * file name to the class contained with in it. Also, parses all package-info.java files. - * - * @param jdkJarfile the URL pointing to the JDK jarfile - */ - private void prepJdkFromJar(@SuppressWarnings("UnusedVariable") URL jdkJarfile) { - JarURLConnection connection = getJarURLConnectionToJdk(); - - try (JarFile jarFile = connection.getJarFile()) { - ArrayList entries = CollectionsPlume.makeArrayList(jarFile.entries()); - entries.sort(Comparator.comparing(Object::toString)); - for (JarEntry jarEntry : entries) { - // filter out directories and non-Java files - if (jarEntry.isDirectory()) { - continue; - } - String jarEntryName = jarEntry.getName(); - if (!(jarEntryName.startsWith("annotated-jdk") && jarEntryName.endsWith(".java")) - // JavaParser can't parse module-info files, so skip them. - || jarEntryName.endsWith("module-info.java")) { - continue; - } - if (parseAllJdkFiles || jarEntryName.endsWith("package-info.java")) { - parseJdkJarEntry(jarEntryName); - continue; - } - int index = jarEntryName.indexOf("/share/classes/") + "/share/classes/".length(); - // "-5" is to remove ".java" from end of file name - String fqClassName = - jarEntryName.substring(index, jarEntryName.length() - 5).replace('/', '.'); - remainingJdkStubFilesJar.put(fqClassName, jarEntryName); - } - if (stubDebug) { - String factoryClass = atypeFactory.getClass().getSimpleName().toString(); - String jarFileURL = connection.getJarFileURL().toString(); - System.out.printf( - "Contents of remainingJdkStubFilesJar for %s from %s:%n", - factoryClass, jarFileURL); - printSortedIndented(remainingJdkStubFilesJar.keySet()); - System.out.printf( - "End of remainingJdkStubFilesJar for %s from %s.%n", - factoryClass, jarFileURL); - - System.out.printf("Contents of %s:%n", jarFileURL); - assert jarFileURL.startsWith("file:"); - ProcessBuilder pb = - new ProcessBuilder( - "/bin/sh", - "-c", - "jar tf '" + jarFileURL.substring(5) + "' | LC_ALL=C sort"); - pb.redirectOutput(Redirect.INHERIT); - pb.redirectError(Redirect.INHERIT); - Process p = pb.start(); - try { - p.waitFor(); - } catch (InterruptedException e) { - // do nothing - } - System.out.flush(); - SystemPlume.sleep(1); - System.out.printf("End of %s.%n", jarFileURL); - } - } catch (IOException e) { - throw new BugInCF("Cannot open the jar file " + connection.getJarFileURL(), e); + if (parseAllJdkFiles || jarEntryName.endsWith("package-info.java")) { + parseJdkJarEntry(jarEntryName); + continue; } - } - - /** - * This method is invoked each time before {@link AnnotationFileParser} processes a top-level - * type. - * - * @param typeName the fully qualified name of the top-level type - */ - void preProcessTopLevelType(String typeName) { - boolean added = processingClasses.add(typeName); - if (!added) { - throw new BugInCF( - "Trying to process type " + typeName + " which is already being processed."); + int index = jarEntryName.indexOf("/share/classes/") + "/share/classes/".length(); + // "-5" is to remove ".java" from end of file name + String fqClassName = + jarEntryName.substring(index, jarEntryName.length() - 5).replace('/', '.'); + remainingJdkStubFilesJar.put(fqClassName, jarEntryName); + } + if (stubDebug) { + String factoryClass = atypeFactory.getClass().getSimpleName().toString(); + String jarFileURL = connection.getJarFileURL().toString(); + System.out.printf( + "Contents of remainingJdkStubFilesJar for %s from %s:%n", factoryClass, jarFileURL); + printSortedIndented(remainingJdkStubFilesJar.keySet()); + System.out.printf( + "End of remainingJdkStubFilesJar for %s from %s.%n", factoryClass, jarFileURL); + + System.out.printf("Contents of %s:%n", jarFileURL); + assert jarFileURL.startsWith("file:"); + ProcessBuilder pb = + new ProcessBuilder( + "/bin/sh", "-c", "jar tf '" + jarFileURL.substring(5) + "' | LC_ALL=C sort"); + pb.redirectOutput(Redirect.INHERIT); + pb.redirectError(Redirect.INHERIT); + Process p = pb.start(); + try { + p.waitFor(); + } catch (InterruptedException e) { + // do nothing } + System.out.flush(); + SystemPlume.sleep(1); + System.out.printf("End of %s.%n", jarFileURL); + } + } catch (IOException e) { + throw new BugInCF("Cannot open the jar file " + connection.getJarFileURL(), e); } - - /** - * This method is invoked each time after {@link AnnotationFileParser} processes a top-level - * type. - * - * @param typeName the fully qualified name of the top-level type - */ - void postProcessTopLevelType(String typeName) { - boolean removed = processingClasses.remove(typeName); - if (!removed) { - throw new BugInCF("Cannot find the processing record for type " + typeName); - } + } + + /** + * This method is invoked each time before {@link AnnotationFileParser} processes a top-level + * type. + * + * @param typeName the fully qualified name of the top-level type + */ + void preProcessTopLevelType(String typeName) { + boolean added = processingClasses.add(typeName); + if (!added) { + throw new BugInCF( + "Trying to process type " + typeName + " which is already being processed."); } - - /** - * Print the strings, in order, each on its own line, indented by two spaces. - * - * @param strings a collection of strings - */ - private void printSortedIndented(Collection strings) { - List stringList = new ArrayList<>(strings); - stringList.sort(String::compareTo); - for (String s : stringList) { - System.out.printf(" %s%n", s); - } + } + + /** + * This method is invoked each time after {@link AnnotationFileParser} processes a top-level type. + * + * @param typeName the fully qualified name of the top-level type + */ + void postProcessTopLevelType(String typeName) { + boolean removed = processingClasses.remove(typeName); + if (!removed) { + throw new BugInCF("Cannot find the processing record for type " + typeName); + } + } + + /** + * Print the strings, in order, each on its own line, indented by two spaces. + * + * @param strings a collection of strings + */ + private void printSortedIndented(Collection strings) { + List stringList = new ArrayList<>(strings); + stringList.sort(String::compareTo); + for (String s : stringList) { + System.out.printf(" %s%n", s); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java index 6b013084681..243dfdebbb4 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java @@ -56,7 +56,36 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; - +import java.io.File; +import java.io.InputStream; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -89,38 +118,6 @@ import org.plumelib.util.IPair; import org.plumelib.util.SystemPlume; -import java.io.File; -import java.io.InputStream; -import java.lang.annotation.Target; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - // From an implementation perspective, this class represents a single annotation file (stub file or // ajava file), notably its annotated types and its declaration annotations. // From a client perspective, it has static methods as described below in the Javadoc. @@ -145,3316 +142,3203 @@ */ public class AnnotationFileParser { - /** - * The type of file being parsed: stub file or ajava file. Also indicates its source, such as - * from the JDK, built in, or from the command line. - * - *

Non-JDK stub files override JDK stub files. (Ordinarily, if two stubs are provided, they - * are merged.) - * - *

For a built-in stub file, - * - *

    - *
  • private declarations are ignored, - *
  • some warning messages are not issued, and - *
- */ - private final AnnotationFileType fileType; - - /** - * If parsing an ajava file, represents the javac tree for the compilation root of the file - * being parsed. - */ - private CompilationUnitTree root; - - /** - * Whether to print warnings about types/members that were not found. The warning states that a - * class/field in the file is not found on the user's real classpath. Since the file may contain - * packages that are not on the classpath, this can be OK, so default to false. - */ - private final boolean warnIfNotFound; + /** + * The type of file being parsed: stub file or ajava file. Also indicates its source, such as from + * the JDK, built in, or from the command line. + * + *

Non-JDK stub files override JDK stub files. (Ordinarily, if two stubs are provided, they are + * merged.) + * + *

For a built-in stub file, + * + *

    + *
  • private declarations are ignored, + *
  • some warning messages are not issued, and + *
+ */ + private final AnnotationFileType fileType; + + /** + * If parsing an ajava file, represents the javac tree for the compilation root of the file being + * parsed. + */ + private CompilationUnitTree root; + + /** + * Whether to print warnings about types/members that were not found. The warning states that a + * class/field in the file is not found on the user's real classpath. Since the file may contain + * packages that are not on the classpath, this can be OK, so default to false. + */ + private final boolean warnIfNotFound; + + /** + * Whether to ignore missing classes even when warnIfNotFound is set to true. This allows the + * files to contain classes not in the classpath (even if another class in the classpath has the + * same package), but still warn if members of the class (methods, fields) are missing. This + * option does nothing unless warnIfNotFound is also set. + */ + private final boolean warnIfNotFoundIgnoresClasses; + + /** Whether to print warnings about stub files that overwrite annotations from bytecode. */ + private final boolean warnIfStubOverwritesBytecode; + + /** + * Whether to print warnings about stub files that are redundant with annotations from bytecode. + */ + private final boolean warnIfStubRedundantWithBytecode; + + /** The diagnostic kind for stub file warnings: NOTE or WARNING. */ + private final Diagnostic.Kind stubWarnDiagnosticKind; + + /** Whether to print verbose debugging messages. */ + private final boolean debugAnnotationFileParser; + + /** The name of the file being processed; used only for diagnostic messages. */ + private final String filename; + + /** + * The AST of the parsed file that this class is processing. May be null if there was a problem + * parsing the file. (TODO: Should the Checker Framework just halt in that case?) + */ + // Not final in order to accommodate a default value. + private @Nullable StubUnit stubUnit; + + /** The processing environment */ + private final ProcessingEnvironment processingEnv; + + /** The type factory */ + private final AnnotatedTypeFactory atypeFactory; + + /** The element utilities */ + private final Elements elements; + + /** The manager that controls the stub file parsing process. */ + private final AnnotationFileElementTypes fileElementTypes; + + /** + * The set of annotations found in the file. Keys are both fully-qualified and simple names. There + * are two entries for each annotation: the annotation's simple name and its fully-qualified name. + * + *

The map is populated from import statements and also by {@link #getAnnotation( + * AnnotationExpr, Map)} for annotations that are used fully-qualified. + * + * @see #getImportedAnnotations + */ + private Map allAnnotations; - /** - * Whether to ignore missing classes even when warnIfNotFound is set to true. This allows the - * files to contain classes not in the classpath (even if another class in the classpath has the - * same package), but still warn if members of the class (methods, fields) are missing. This - * option does nothing unless warnIfNotFound is also set. - */ - private final boolean warnIfNotFoundIgnoresClasses; + /** + * A list of the fully-qualified names of enum constants and static fields with constant values + * that have been imported. + */ + private final List<@FullyQualifiedName String> importedConstants = new ArrayList<>(); - /** Whether to print warnings about stub files that overwrite annotations from bytecode. */ - private final boolean warnIfStubOverwritesBytecode; + /** A map of imported fully-qualified type names to type elements. */ + private final Map importedTypes = new HashMap<>(); - /** - * Whether to print warnings about stub files that are redundant with annotations from bytecode. - */ - private final boolean warnIfStubRedundantWithBytecode; + /** The annotation {@code @FromStubFile}. */ + private final AnnotationMirror fromStubFileAnno; - /** The diagnostic kind for stub file warnings: NOTE or WARNING. */ - private final Diagnostic.Kind stubWarnDiagnosticKind; + /** + * List of AnnotatedTypeMirrors for class or method type parameters that are in scope of the + * elements currently parsed. + */ + private final List typeParameters = new ArrayList<>(); - /** Whether to print verbose debugging messages. */ - private final boolean debugAnnotationFileParser; + /** + * The annotations on the declared package of the complation unit being processed. Contains null + * if not processing a compilation unit or if the file has no declared package. + */ + private @Nullable List<@Nullable AnnotationExpr> packageAnnos; - /** The name of the file being processed; used only for diagnostic messages. */ - private final String filename; + // The following variables are stored in the AnnotationFileParser because otherwise they would + // need to be passed through everywhere, which would be verbose. - /** - * The AST of the parsed file that this class is processing. May be null if there was a problem - * parsing the file. (TODO: Should the Checker Framework just halt in that case?) - */ - // Not final in order to accommodate a default value. - private @Nullable StubUnit stubUnit; + /** + * The name of the type that is currently being parsed. After processing a package declaration but + * before processing a type declaration, the type part of this may be null. + * + *

It is used both for resolving symbols and for error messages. + */ + private FqName typeBeingParsed; - /** The processing environment */ - private final ProcessingEnvironment processingEnv; + /** + * Contains the annotations of the file currently being processed, or null if not currently + * processing a file. The {@code process*} methods side-effect this data structure. + */ + private @Nullable AnnotationFileAnnotations annotationFileAnnos; - /** The type factory */ - private final AnnotatedTypeFactory atypeFactory; + /** The line separator. */ + private static final String LINE_SEPARATOR = System.lineSeparator().intern(); - /** The element utilities */ - private final Elements elements; + /** Whether or not the {@code -AmergeStubsWithSource} command-line argument was passed. */ + private final boolean mergeStubsWithSource; - /** The manager that controls the stub file parsing process. */ - private final AnnotationFileElementTypes fileElementTypes; + /** + * The result of calling AnnotationFileParser.parse: the annotated types and declaration + * annotations from the file. + */ + public static class AnnotationFileAnnotations { /** - * The set of annotations found in the file. Keys are both fully-qualified and simple names. - * There are two entries for each annotation: the annotation's simple name and its - * fully-qualified name. + * Map from element to its type as declared in the annotation file. * - *

The map is populated from import statements and also by {@link #getAnnotation( - * AnnotationExpr, Map)} for annotations that are used fully-qualified. - * - * @see #getImportedAnnotations - */ - private Map allAnnotations; - - /** - * A list of the fully-qualified names of enum constants and static fields with constant values - * that have been imported. - */ - private final List<@FullyQualifiedName String> importedConstants = new ArrayList<>(); - - /** A map of imported fully-qualified type names to type elements. */ - private final Map importedTypes = new HashMap<>(); - - /** The annotation {@code @FromStubFile}. */ - private final AnnotationMirror fromStubFileAnno; - - /** - * List of AnnotatedTypeMirrors for class or method type parameters that are in scope of the - * elements currently parsed. - */ - private final List typeParameters = new ArrayList<>(); - - /** - * The annotations on the declared package of the complation unit being processed. Contains null - * if not processing a compilation unit or if the file has no declared package. + *

This is a fine-grained mapping that contains all sorts of elements; contrast with {@link + * #fakeOverrides}. */ - private @Nullable List<@Nullable AnnotationExpr> packageAnnos; - - // The following variables are stored in the AnnotationFileParser because otherwise they would - // need to be passed through everywhere, which would be verbose. + public final Map atypes = new HashMap<>(); /** - * The name of the type that is currently being parsed. After processing a package declaration - * but before processing a type declaration, the type part of this may be null. + * Map from a name (actually declaration element string) to the set of declaration annotations + * on it, as written in the annotation file. * - *

It is used both for resolving symbols and for error messages. + *

Map keys cannot be Element, because a different Element appears in the annotation files + * than in the real files. So, map keys are the verbose element name, as returned by + * ElementUtils.getQualifiedName. */ - private FqName typeBeingParsed; + public final Map declAnnos = new HashMap<>(1); /** - * Contains the annotations of the file currently being processed, or null if not currently - * processing a file. The {@code process*} methods side-effect this data structure. + * Map from a method element to all the fake overrides of it. Given a key {@code ee}, the fake + * overrides are always in subtypes of {@code ee.getEnclosingElement()}, which is the same as + * {@code ee.getReceiverType()}. */ - private @Nullable AnnotationFileAnnotations annotationFileAnnos; - - /** The line separator. */ - private static final String LINE_SEPARATOR = System.lineSeparator().intern(); + public final Map>> + fakeOverrides = new HashMap<>(1); - /** Whether or not the {@code -AmergeStubsWithSource} command-line argument was passed. */ - private final boolean mergeStubsWithSource; + /** Maps fully qualified record name to information in the stub file. */ + public final Map records = new HashMap<>(); + } + /** Information about a record from a stub file. */ + public static class RecordStub { /** - * The result of calling AnnotationFileParser.parse: the annotated types and declaration - * annotations from the file. + * A map from name to record component. It must have deterministic insertion/iteration order: + * the order that they are declared in the record header. */ - public static class AnnotationFileAnnotations { - - /** - * Map from element to its type as declared in the annotation file. - * - *

This is a fine-grained mapping that contains all sorts of elements; contrast with - * {@link #fakeOverrides}. - */ - public final Map atypes = new HashMap<>(); - - /** - * Map from a name (actually declaration element string) to the set of declaration - * annotations on it, as written in the annotation file. - * - *

Map keys cannot be Element, because a different Element appears in the annotation - * files than in the real files. So, map keys are the verbose element name, as returned by - * ElementUtils.getQualifiedName. - */ - public final Map declAnnos = new HashMap<>(1); - - /** - * Map from a method element to all the fake overrides of it. Given a key {@code ee}, the - * fake overrides are always in subtypes of {@code ee.getEnclosingElement()}, which is the - * same as {@code ee.getReceiverType()}. - */ - public final Map>> - fakeOverrides = new HashMap<>(1); - - /** Maps fully qualified record name to information in the stub file. */ - public final Map records = new HashMap<>(); - } - - /** Information about a record from a stub file. */ - public static class RecordStub { - /** - * A map from name to record component. It must have deterministic insertion/iteration - * order: the order that they are declared in the record header. - */ - public final Map componentsByName; - - /** - * If the canonical constructor is given in the stubs, the annotated types (in component - * declaration order) for the constructor. Null if not present in the stubs. - */ - public @MonotonicNonNull List componentsInCanonicalConstructor; - - /** - * Creates a new RecordStub. - * - * @param componentsByName a map from name to record component. It must have deterministic - * insertion/iteration order: the order that they are declared in the record header. - */ - public RecordStub(Map componentsByName) { - this.componentsByName = componentsByName; - } - - /** - * Returns the annotated types for the parameters to the canonical constructor. This is - * either from explicit annotations on the constructor in the stubs, otherwise it's taken - * from the annotations on the record components in the stubs. - * - * @return the annotated types for the parameters to the canonical constructor - */ - public List getComponentsInCanonicalConstructor() { - if (componentsInCanonicalConstructor != null) { - return componentsInCanonicalConstructor; - } else { - return CollectionsPlume.mapList(c -> c.type, componentsByName.values()); - } - } - } + public final Map componentsByName; /** - * Information about a record component: its type, and whether there was an accessor in the - * stubs for that component. That is, for a component "foo" was there a method named exactly - * "foo()" in the stubs. If so, annotations on that accessor will take precedence over - * annotations that would otherwise be copied from the component in the stubs to the acessor. + * If the canonical constructor is given in the stubs, the annotated types (in component + * declaration order) for the constructor. Null if not present in the stubs. */ - public static class RecordComponentStub { - /** The type of the record component. */ - public final AnnotatedTypeMirror type; - - /** - * The set of all annotations on the declaration of the record component. If applicable - * these will be copied to the corresponding field, accessor method, and compact canonical - * constructor parameter. - */ - private final AnnotationMirrorSet allAnnotations; - - /** Whether this component has an accessor of exactly the same name in the stubs file. */ - private boolean hasAccessorInStubs = false; - - /** - * Creates a new RecordComponentStub with the given type. - * - * @param type the type of the record component - * @param allAnnotations the declaration annotations on the component - */ - public RecordComponentStub(AnnotatedTypeMirror type, AnnotationMirrorSet allAnnotations) { - this.type = type; - this.allAnnotations = allAnnotations; - } - - /** - * Get the record component annotations that are applicable to the given element kind. - * - * @param elementKind the element kind to apply to (e.g., FIELD, METHOD) - * @return the set of annotations from the component that apply - */ - public AnnotationMirrorSet getAnnotationsForTarget(ElementKind elementKind) { - AnnotationMirrorSet filtered = new AnnotationMirrorSet(); - for (AnnotationMirror annoMirror : allAnnotations) { - Target target = - annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); - // Only add the declaration annotation if the annotation applies to the element. - if (AnnotationUtils.getElementKindsForTarget(target).contains(elementKind)) { - // `annoMirror` is applicable to `elt` - filtered.add(annoMirror); - } - } - return filtered; - } - - /** - * Returns whether there is an accessor in a stub file. - * - * @return true if some stub file contains an accessor for this record component - */ - public boolean hasAccessorInStubs() { - return hasAccessorInStubs; - } - } + public @MonotonicNonNull List componentsInCanonicalConstructor; /** - * Create a new AnnotationFileParser object, which will parse and extract annotations from the - * given file. + * Creates a new RecordStub. * - * @param filename name of annotation file, used only for diagnostic messages - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param fileType the type of file being parsed (stub file or ajava file) and its source - * @param fileElementTypes the manager that controls the stub file parsing process + * @param componentsByName a map from name to record component. It must have deterministic + * insertion/iteration order: the order that they are declared in the record header. */ - private AnnotationFileParser( - String filename, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileType fileType, - AnnotationFileElementTypes fileElementTypes) { - this.filename = filename; - this.atypeFactory = atypeFactory; - this.processingEnv = processingEnv; - this.elements = processingEnv.getElementUtils(); - this.fileType = fileType; - this.root = null; - this.fileElementTypes = fileElementTypes; - - // TODO: This should use SourceChecker.getOptions() to allow - // setting these flags per checker. - Map options = processingEnv.getOptions(); - boolean stubWarnIfNotFoundOption = options.containsKey("stubWarnIfNotFound"); - boolean stubNoWarnIfNotFoundOption = options.containsKey("stubNoWarnIfNotFound"); - if (stubWarnIfNotFoundOption && stubNoWarnIfNotFoundOption) { - throw new UserError( - "Do not supply both -AstubWarnIfNotFound and -AstubNoWarnIfNotFound."); - } - this.warnIfNotFound = - stubWarnIfNotFoundOption - || (fileType.isCommandLine() && !stubNoWarnIfNotFoundOption); - - this.warnIfNotFoundIgnoresClasses = options.containsKey("stubWarnIfNotFoundIgnoresClasses"); - this.warnIfStubOverwritesBytecode = options.containsKey("stubWarnIfOverwritesBytecode"); - this.warnIfStubRedundantWithBytecode = - options.containsKey("stubWarnIfRedundantWithBytecode") - && atypeFactory.shouldWarnIfStubRedundantWithBytecode(); - this.stubWarnDiagnosticKind = - options.containsKey("stubWarnNote") - ? Diagnostic.Kind.NOTE - : Diagnostic.Kind.WARNING; - this.debugAnnotationFileParser = options.containsKey("stubDebug"); - - this.fromStubFileAnno = AnnotationBuilder.fromClass(elements, FromStubFile.class); - - this.mergeStubsWithSource = atypeFactory.getChecker().hasOption("mergeStubsWithSource"); + public RecordStub(Map componentsByName) { + this.componentsByName = componentsByName; } /** - * Sets the root of the file currently being parsed to {@code root}. + * Returns the annotated types for the parameters to the canonical constructor. This is either + * from explicit annotations on the constructor in the stubs, otherwise it's taken from the + * annotations on the record components in the stubs. * - * @param root compilation unit for the file being parsed + * @return the annotated types for the parameters to the canonical constructor */ - private void setRoot(CompilationUnitTree root) { - this.root = root; + public List getComponentsInCanonicalConstructor() { + if (componentsInCanonicalConstructor != null) { + return componentsInCanonicalConstructor; + } else { + return CollectionsPlume.mapList(c -> c.type, componentsByName.values()); + } } + } - /** - * All annotations defined in the package (but not those nested within classes in the package). - * Keys are both fully-qualified and simple names. - * - * @param packageElement a package - * @return a map from annotation name to TypeElement - */ - public static Map annosInPackage(PackageElement packageElement) { - return createNameToAnnotationMap( - ElementFilter.typesIn(packageElement.getEnclosedElements())); - } + /** + * Information about a record component: its type, and whether there was an accessor in the stubs + * for that component. That is, for a component "foo" was there a method named exactly "foo()" in + * the stubs. If so, annotations on that accessor will take precedence over annotations that would + * otherwise be copied from the component in the stubs to the acessor. + */ + public static class RecordComponentStub { + /** The type of the record component. */ + public final AnnotatedTypeMirror type; /** - * All annotations declared (directly) within a class. Keys are both fully-qualified and simple - * names. - * - * @param typeElement a type - * @return a map from annotation name to TypeElement + * The set of all annotations on the declaration of the record component. If applicable these + * will be copied to the corresponding field, accessor method, and compact canonical constructor + * parameter. */ - public static Map annosInType(TypeElement typeElement) { - return createNameToAnnotationMap(ElementFilter.typesIn(typeElement.getEnclosedElements())); - } + private final AnnotationMirrorSet allAnnotations; - /** - * All annotations declared within any of the given elements. - * - * @param typeElements the elements whose annotations to retrieve - * @return a map from annotation names (both fully-qualified and simple names) to TypeElement - */ - public static Map createNameToAnnotationMap( - List typeElements) { - Map result = new HashMap<>(); - for (TypeElement typeElm : typeElements) { - if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) { - putIfAbsent(result, typeElm.getSimpleName().toString(), typeElm); - putIfAbsent(result, typeElm.getQualifiedName().toString(), typeElm); - } - } - return result; - } + /** Whether this component has an accessor of exactly the same name in the stubs file. */ + private boolean hasAccessorInStubs = false; /** - * Get all members of a Type that are importable in an annotation file. Currently these are - * values of enums, or compile time constants. + * Creates a new RecordComponentStub with the given type. * - * @param typeElement the type whose members to return - * @return a list of fully-qualified member names + * @param type the type of the record component + * @param allAnnotations the declaration annotations on the component */ - private static List<@FullyQualifiedName String> getImportableMembers(TypeElement typeElement) { - List memberElements = - ElementFilter.fieldsIn(typeElement.getEnclosedElements()); - List<@FullyQualifiedName String> result = new ArrayList<>(); - for (VariableElement varElement : memberElements) { - if (varElement.getConstantValue() != null - || varElement.getKind() == ElementKind.ENUM_CONSTANT) { - @SuppressWarnings("signature") // string concatenation - @FullyQualifiedName String fqName = - typeElement.getQualifiedName().toString() - + "." - + varElement.getSimpleName().toString(); - result.add(fqName); - } - } - return result; + public RecordComponentStub(AnnotatedTypeMirror type, AnnotationMirrorSet allAnnotations) { + this.type = type; + this.allAnnotations = allAnnotations; } /** - * Returns all annotations imported by the annotation file, as a value for {@link - * #allAnnotations}. Note that this also modifies {@link #importedConstants} and {@link - * #importedTypes}. + * Get the record component annotations that are applicable to the given element kind. * - *

This method misses annotations that are not imported. The {@link #getAnnotation} method - * compensates for this deficiency by adding any fully-qualified annotation that it encounters. - * - * @return a map from names to TypeElement, for all annotations imported by the annotation file. - * Two entries for each annotation: one for the simple name and another for the - * fully-qualified name, with the same value. - * @see #allAnnotations + * @param elementKind the element kind to apply to (e.g., FIELD, METHOD) + * @return the set of annotations from the component that apply */ - private Map getImportedAnnotations() { - Map result = new HashMap<>(); - - // TODO: The size can be greater than 1, but this ignores all but the first element. - assert !stubUnit.getCompilationUnits().isEmpty(); - CompilationUnit cu = stubUnit.getCompilationUnits().get(0); - - if (cu.getImports() == null) { - return result; + public AnnotationMirrorSet getAnnotationsForTarget(ElementKind elementKind) { + AnnotationMirrorSet filtered = new AnnotationMirrorSet(); + for (AnnotationMirror annoMirror : allAnnotations) { + Target target = annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); + // Only add the declaration annotation if the annotation applies to the element. + if (AnnotationUtils.getElementKindsForTarget(target).contains(elementKind)) { + // `annoMirror` is applicable to `elt` + filtered.add(annoMirror); } - - for (ImportDeclaration importDecl : cu.getImports()) { - try { - if (importDecl.isAsterisk()) { - @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: - // com.github.javaparser.ast.expr.Name inherits toString, - // so there can be no annotation for it - ) - @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); - if (importDecl.isStatic()) { - // Wildcard import of members of a type (class or interface) - TypeElement element = - getTypeElement(imported, "imported type not found", importDecl); - if (element != null) { - // Find nested annotations - // Find compile time constant fields, or values of an enum - putAllNew(result, annosInType(element)); - importedConstants.addAll(getImportableMembers(element)); - addEnclosingTypesToImportedTypes(element); - } - - } else { - // Wildcard import of members of a package - PackageElement element = findPackage(imported, importDecl); - if (element != null) { - putAllNew(result, annosInPackage(element)); - addEnclosingTypesToImportedTypes(element); - } - } - } else { - // A single (non-wildcard) import. - @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is - // @FullyQualifiedName - ) - @FullyQualifiedName String imported = importDecl.getNameAsString(); - - TypeElement importType = elements.getTypeElement(imported); - if (importType == null && !importDecl.isStatic()) { - // Class or nested class (according to JSL), but we can't resolve - - stubWarnNotFound(importDecl, "imported type not found: " + imported); - } else if (importType == null) { - // static import of field or method. - - IPair<@FullyQualifiedName String, String> typeParts = - AnnotationFileUtil.partitionQualifiedName(imported); - String type = typeParts.first; - String fieldName = typeParts.second; - TypeElement enclType = - getTypeElement( - type, - String.format( - "enclosing type of static field %s not found", - fieldName), - importDecl); - - if (enclType != null) { - // Don't use findFieldElement(enclType, fieldName), because we don't - // want a warning, imported might be a method. - for (VariableElement field : - ElementUtils.getAllFieldsIn(enclType, elements)) { - // field.getSimpleName() is a CharSequence, not a String - if (fieldName.equals(field.getSimpleName().toString())) { - importedConstants.add(imported); - } - } - } - - } else if (importType.getKind() == ElementKind.ANNOTATION_TYPE) { - // Single annotation or nested annotation - TypeElement annoElt = elements.getTypeElement(imported); - if (annoElt != null) { - putIfAbsent(result, annoElt.getSimpleName().toString(), annoElt); - importedTypes.put(annoElt.getSimpleName().toString(), annoElt); - } else { - stubWarnNotFound(importDecl, "could not load import: " + imported); - } - } else { - // Class or nested class - // TODO: Is this needed? - importedConstants.add(imported); - TypeElement element = - getTypeElement(imported, "imported type not found", importDecl); - importedTypes.put(element.getSimpleName().toString(), element); - } + } + return filtered; + } + + /** + * Returns whether there is an accessor in a stub file. + * + * @return true if some stub file contains an accessor for this record component + */ + public boolean hasAccessorInStubs() { + return hasAccessorInStubs; + } + } + + /** + * Create a new AnnotationFileParser object, which will parse and extract annotations from the + * given file. + * + * @param filename name of annotation file, used only for diagnostic messages + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param fileType the type of file being parsed (stub file or ajava file) and its source + * @param fileElementTypes the manager that controls the stub file parsing process + */ + private AnnotationFileParser( + String filename, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileType fileType, + AnnotationFileElementTypes fileElementTypes) { + this.filename = filename; + this.atypeFactory = atypeFactory; + this.processingEnv = processingEnv; + this.elements = processingEnv.getElementUtils(); + this.fileType = fileType; + this.root = null; + this.fileElementTypes = fileElementTypes; + + // TODO: This should use SourceChecker.getOptions() to allow + // setting these flags per checker. + Map options = processingEnv.getOptions(); + boolean stubWarnIfNotFoundOption = options.containsKey("stubWarnIfNotFound"); + boolean stubNoWarnIfNotFoundOption = options.containsKey("stubNoWarnIfNotFound"); + if (stubWarnIfNotFoundOption && stubNoWarnIfNotFoundOption) { + throw new UserError("Do not supply both -AstubWarnIfNotFound and -AstubNoWarnIfNotFound."); + } + this.warnIfNotFound = + stubWarnIfNotFoundOption || (fileType.isCommandLine() && !stubNoWarnIfNotFoundOption); + + this.warnIfNotFoundIgnoresClasses = options.containsKey("stubWarnIfNotFoundIgnoresClasses"); + this.warnIfStubOverwritesBytecode = options.containsKey("stubWarnIfOverwritesBytecode"); + this.warnIfStubRedundantWithBytecode = + options.containsKey("stubWarnIfRedundantWithBytecode") + && atypeFactory.shouldWarnIfStubRedundantWithBytecode(); + this.stubWarnDiagnosticKind = + options.containsKey("stubWarnNote") ? Diagnostic.Kind.NOTE : Diagnostic.Kind.WARNING; + this.debugAnnotationFileParser = options.containsKey("stubDebug"); + + this.fromStubFileAnno = AnnotationBuilder.fromClass(elements, FromStubFile.class); + + this.mergeStubsWithSource = atypeFactory.getChecker().hasOption("mergeStubsWithSource"); + } + + /** + * Sets the root of the file currently being parsed to {@code root}. + * + * @param root compilation unit for the file being parsed + */ + private void setRoot(CompilationUnitTree root) { + this.root = root; + } + + /** + * All annotations defined in the package (but not those nested within classes in the package). + * Keys are both fully-qualified and simple names. + * + * @param packageElement a package + * @return a map from annotation name to TypeElement + */ + public static Map annosInPackage(PackageElement packageElement) { + return createNameToAnnotationMap(ElementFilter.typesIn(packageElement.getEnclosedElements())); + } + + /** + * All annotations declared (directly) within a class. Keys are both fully-qualified and simple + * names. + * + * @param typeElement a type + * @return a map from annotation name to TypeElement + */ + public static Map annosInType(TypeElement typeElement) { + return createNameToAnnotationMap(ElementFilter.typesIn(typeElement.getEnclosedElements())); + } + + /** + * All annotations declared within any of the given elements. + * + * @param typeElements the elements whose annotations to retrieve + * @return a map from annotation names (both fully-qualified and simple names) to TypeElement + */ + public static Map createNameToAnnotationMap(List typeElements) { + Map result = new HashMap<>(); + for (TypeElement typeElm : typeElements) { + if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) { + putIfAbsent(result, typeElm.getSimpleName().toString(), typeElm); + putIfAbsent(result, typeElm.getQualifiedName().toString(), typeElm); + } + } + return result; + } + + /** + * Get all members of a Type that are importable in an annotation file. Currently these are values + * of enums, or compile time constants. + * + * @param typeElement the type whose members to return + * @return a list of fully-qualified member names + */ + private static List<@FullyQualifiedName String> getImportableMembers(TypeElement typeElement) { + List memberElements = + ElementFilter.fieldsIn(typeElement.getEnclosedElements()); + List<@FullyQualifiedName String> result = new ArrayList<>(); + for (VariableElement varElement : memberElements) { + if (varElement.getConstantValue() != null + || varElement.getKind() == ElementKind.ENUM_CONSTANT) { + @SuppressWarnings("signature") // string concatenation + @FullyQualifiedName String fqName = + typeElement.getQualifiedName().toString() + "." + varElement.getSimpleName().toString(); + result.add(fqName); + } + } + return result; + } + + /** + * Returns all annotations imported by the annotation file, as a value for {@link + * #allAnnotations}. Note that this also modifies {@link #importedConstants} and {@link + * #importedTypes}. + * + *

This method misses annotations that are not imported. The {@link #getAnnotation} method + * compensates for this deficiency by adding any fully-qualified annotation that it encounters. + * + * @return a map from names to TypeElement, for all annotations imported by the annotation file. + * Two entries for each annotation: one for the simple name and another for the + * fully-qualified name, with the same value. + * @see #allAnnotations + */ + private Map getImportedAnnotations() { + Map result = new HashMap<>(); + + // TODO: The size can be greater than 1, but this ignores all but the first element. + assert !stubUnit.getCompilationUnits().isEmpty(); + CompilationUnit cu = stubUnit.getCompilationUnits().get(0); + + if (cu.getImports() == null) { + return result; + } + + for (ImportDeclaration importDecl : cu.getImports()) { + try { + if (importDecl.isAsterisk()) { + @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: + // com.github.javaparser.ast.expr.Name inherits toString, + // so there can be no annotation for it + ) + @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); + if (importDecl.isStatic()) { + // Wildcard import of members of a type (class or interface) + TypeElement element = getTypeElement(imported, "imported type not found", importDecl); + if (element != null) { + // Find nested annotations + // Find compile time constant fields, or values of an enum + putAllNew(result, annosInType(element)); + importedConstants.addAll(getImportableMembers(element)); + addEnclosingTypesToImportedTypes(element); + } + + } else { + // Wildcard import of members of a package + PackageElement element = findPackage(imported, importDecl); + if (element != null) { + putAllNew(result, annosInPackage(element)); + addEnclosingTypesToImportedTypes(element); + } + } + } else { + // A single (non-wildcard) import. + @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is + // @FullyQualifiedName + ) + @FullyQualifiedName String imported = importDecl.getNameAsString(); + + TypeElement importType = elements.getTypeElement(imported); + if (importType == null && !importDecl.isStatic()) { + // Class or nested class (according to JSL), but we can't resolve + + stubWarnNotFound(importDecl, "imported type not found: " + imported); + } else if (importType == null) { + // static import of field or method. + + IPair<@FullyQualifiedName String, String> typeParts = + AnnotationFileUtil.partitionQualifiedName(imported); + String type = typeParts.first; + String fieldName = typeParts.second; + TypeElement enclType = + getTypeElement( + type, + String.format("enclosing type of static field %s not found", fieldName), + importDecl); + + if (enclType != null) { + // Don't use findFieldElement(enclType, fieldName), because we don't + // want a warning, imported might be a method. + for (VariableElement field : ElementUtils.getAllFieldsIn(enclType, elements)) { + // field.getSimpleName() is a CharSequence, not a String + if (fieldName.equals(field.getSimpleName().toString())) { + importedConstants.add(imported); } - } catch (AssertionError error) { - stubWarnNotFound(importDecl, error.toString()); + } } - } + + } else if (importType.getKind() == ElementKind.ANNOTATION_TYPE) { + // Single annotation or nested annotation + TypeElement annoElt = elements.getTypeElement(imported); + if (annoElt != null) { + putIfAbsent(result, annoElt.getSimpleName().toString(), annoElt); + importedTypes.put(annoElt.getSimpleName().toString(), annoElt); + } else { + stubWarnNotFound(importDecl, "could not load import: " + imported); + } + } else { + // Class or nested class + // TODO: Is this needed? + importedConstants.add(imported); + TypeElement element = getTypeElement(imported, "imported type not found", importDecl); + importedTypes.put(element.getSimpleName().toString(), element); + } + } + } catch (AssertionError error) { + stubWarnNotFound(importDecl, error.toString()); + } + } + return result; + } + + // If a member is imported, then consider every containing class to also be imported. + private void addEnclosingTypesToImportedTypes(Element element) { + for (Element enclosedEle : element.getEnclosedElements()) { + if (enclosedEle.getKind().isClass()) { + importedTypes.put(enclosedEle.getSimpleName().toString(), (TypeElement) enclosedEle); + } + } + } + + /** + * The main entry point. Parse a stub file and side-effects the {@code annotationFileAnnos} + * argument. + * + * @param filename name of stub file, used only for diagnostic messages + * @param inputStream of stub file to parse + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param annotationFileAnnos annotations from the annotation file; side-effected by this method + * @param fileType the annotation file type and source + * @param fileElementTypes the manager that controls the stub file parsing process + */ + public static void parseStubFile( + String filename, + InputStream inputStream, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileAnnotations annotationFileAnnos, + AnnotationFileType fileType, + AnnotationFileElementTypes fileElementTypes) { + AnnotationFileParser afp = + new AnnotationFileParser(filename, atypeFactory, processingEnv, fileType, fileElementTypes); + try { + afp.parseStubUnit(inputStream); + afp.process(annotationFileAnnos); + } catch (ParseProblemException e) { + for (Problem p : e.getProblems()) { + afp.warn(null, p.getVerboseMessage()); + } + } catch (Throwable t) { + afp.warn(null, "Parse problem: " + t); + } + } + + /** + * The main entry point when parsing an ajava file. Parses an ajava file and side-effects the last + * two arguments. + * + * @param filename name of ajava file, used only for diagnostic messages + * @param inputStream of ajava file to parse + * @param root javac tree for the file to be parsed + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param ajavaAnnos annotations from the ajava file; side-effected by this method + * @param fileElementTypes the manager that controls the stub file parsing process + */ + public static void parseAjavaFile( + String filename, + InputStream inputStream, + CompilationUnitTree root, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileAnnotations ajavaAnnos, + AnnotationFileElementTypes fileElementTypes) { + AnnotationFileParser afp = + new AnnotationFileParser( + filename, atypeFactory, processingEnv, AnnotationFileType.AJAVA, fileElementTypes); + try { + afp.parseStubUnit(inputStream); + JavaParserUtil.concatenateAddedStringLiterals(afp.stubUnit); + afp.setRoot(root); + afp.process(ajavaAnnos); + } catch (ParseProblemException e) { + for (Problem p : e.getProblems()) { + afp.warn(null, filename + ": " + p.getVerboseMessage()); + } + } catch (Throwable t) { + afp.warn(null, "Parse problem: " + t); + } + } + + /** + * Parse a stub file that is a part of the annotated JDK and side-effects the {@code stubAnnos} + * argument. + * + * @param filename name of stub file, used only for diagnostic messages + * @param inputStream of stub file to parse + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param stubAnnos annotations from the stub file; side-effected by this method + * @param fileElementTypes the manager that controls the stub file parsing process + */ + public static void parseJdkFileAsStub( + String filename, + InputStream inputStream, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileAnnotations stubAnnos, + AnnotationFileElementTypes fileElementTypes) { + Map options = processingEnv.getOptions(); + boolean debugAnnotationFileParser = options.containsKey("stubDebug"); + if (debugAnnotationFileParser) { + stubDebugStatic( + processingEnv, + "parseJdkFileAsStub(%s, _, %s, _, _)%n", + filename, + atypeFactory.getClass().getSimpleName()); + } + + parseStubFile( + filename, + inputStream, + atypeFactory, + processingEnv, + stubAnnos, + AnnotationFileType.JDK_STUB, + fileElementTypes); + } + + /** + * Delegate to the Stub Parser to parse the annotation file to an AST, and save it in {@link + * #stubUnit}. Also sets {@link #allAnnotations}. Does not copy annotations out of {@link + * #stubUnit}; that is done by the {@code process*} methods. + * + *

Subsequently, all work uses the AST. + * + * @param inputStream the stream from which to read an annotation file + */ + private void parseStubUnit(InputStream inputStream) { + stubDebug( + "started parsing annotation file %s for %s", + filename, atypeFactory.getClass().getSimpleName()); + stubUnit = JavaParserUtil.parseStubUnit(inputStream); + + // getImportedAnnotations() also modifies importedConstants and importedTypes. This should + // be refactored to be nicer. + allAnnotations = getImportedAnnotations(); + if (allAnnotations.isEmpty() + && fileType.isStub() + && fileType != AnnotationFileType.AJAVA_AS_STUB) { + // Issue a warning if the stub file contains no import statements. The warning is + // incorrect if the stub file contains fully-qualified annotations. + stubWarnNotFound( + null, + String.format( + "No supported annotations found! Does stub file %s import them?", filename)); + } + // Annotations in java.lang might be used without an import statement, so add them in case. + allAnnotations.putAll(annosInPackage(findPackage("java.lang", null))); + + if (debugAnnotationFileParser) { + stubDebug( + "finished parsing annotation file %s for %s", + filename, atypeFactory.getClass().getSimpleName()); + } + } + + /** + * Process {@link #stubUnit}, which is the AST produced by {@link #parseStubUnit}. Processing + * means copying annotations from Stub Parser data structures to {@code #annotationFileAnnos}. + * + * @param annotationFileAnnos annotations from the file; side-effected by this method + */ + private void process(AnnotationFileAnnotations annotationFileAnnos) { + this.annotationFileAnnos = annotationFileAnnos; + processStubUnit(this.stubUnit); + this.annotationFileAnnos = null; + } + + /** + * Process the given StubUnit: copy its annotations to {@code #annotationFileAnnos}. + * + * @param su the StubUnit to process + */ + private void processStubUnit(StubUnit su) { + for (CompilationUnit cu : su.getCompilationUnits()) { + processCompilationUnit(cu); + } + } + + /** + * Process the given CompilationUnit: copy its annotations to {@code #annotationFileAnnos}. + * + * @param cu the CompilationUnit to process + */ + private void processCompilationUnit(CompilationUnit cu) { + + if (cu.getPackageDeclaration().isPresent()) { + PackageDeclaration pDecl = cu.getPackageDeclaration().get(); + packageAnnos = pDecl.getAnnotations(); + if (debugAnnotationFileParser + || (!warnIfNotFoundIgnoresClasses && !hasNoAnnotationFileParserWarning(packageAnnos))) { + String packageName = pDecl.getName().toString(); + if (elements.getPackageElement(packageName) == null) { + stubWarnNotFound(pDecl, "package not found: " + packageName); + } + } + processPackage(pDecl); + } else { + packageAnnos = null; + typeBeingParsed = new FqName(null, null); + } + + if (fileType.isStub()) { + if (cu.getTypes() != null) { + for (TypeDeclaration typeDeclaration : cu.getTypes()) { + Optional typeDeclName = typeDeclaration.getFullyQualifiedName(); + + typeDeclName.ifPresent(fileElementTypes::preProcessTopLevelType); + try { + // Not processing an ajava file, so ignore the return value. + processTypeDecl(typeDeclaration, null, null); + } finally { + typeDeclName.ifPresent(fileElementTypes::postProcessTopLevelType); + } + } + } + } else { + root.accept(new AjavaAnnotationCollectorVisitor(), cu); + } + + packageAnnos = null; + } + + /** + * Process the given package declaration: copy its annotations to {@code #annotationFileAnnos}. + * + * @param packDecl the package declaration to process + */ + private void processPackage(PackageDeclaration packDecl) { + assert (packDecl != null); + if (!isAnnotatedForThisChecker(packDecl.getAnnotations())) { + return; + } + String packageName = packDecl.getNameAsString(); + typeBeingParsed = new FqName(packageName, null); + Element elem = elements.getPackageElement(packageName); + // If the element lookup fails (that is, elem == null), it's because we have an annotation + // for a package that isn't on the classpath, which is fine. + if (elem != null) { + recordDeclAnnotation(elem, packDecl.getAnnotations(), packDecl); + } + // TODO: Handle atypes??? + } + + /** + * Returns true if the given program construct need not be read: it is private and one of the + * following is true: + * + *

    + *
  • It is in the annotated JDK. Private constructs can't be referenced outside of the JDK and + * might refer to types that are not accessible. + *
  • It is not an ajava file and {@code -AmergeStubsWithSource} was not supplied. As described + * at https://eisop.github.io/cf/manual/#stub-multiple-specifications, source files take + * precedence over stub files unless {@code -AmergeStubsWithSource} is supplied. As + * described at https://eisop.github.io/cf/manual/#ajava-using, source files do not take + * precedence over ajava files (when reading an ajava file, it is as if {@code + * -AmergeStubsWithSource} were supplied). + *
+ * + * @param node a declaration + * @return true if the given program construct is in the annotated JDK and is private + */ + private boolean skipNode(NodeWithAccessModifiers node) { + // Must include everything with no access modifier, because stub files are allowed to omit + // the access modifier. Also, interface methods have no access modifier, but they are still + // public. + // Must include protected JDK methods. For example, Object.clone is protected, but it + // contains annotations that apply to calls like `super.clone()` and `myArray.clone()`. + return (fileType == AnnotationFileType.BUILTIN_STUB + || (fileType.isStub() + && fileType != AnnotationFileType.AJAVA_AS_STUB + && !mergeStubsWithSource)) + && node.getModifiers().contains(Modifier.privateModifier()); + } + + /** + * Returns the string representation of {@code n}, one one line, truncated to {@code length} + * characters. + * + * @param n a JavaParser node + * @param length the maximum length of the string representation + * @return the truncated string representation of {@code n} + */ + private String javaParserNodeToStringTruncated(Node n, int length) { + String oneLine = + n.toString() + .replace("\t", " ") + .replace("\n", " ") + .replace("\r", " ") + .replaceAll(" +", " "); + if (oneLine.length() <= length) { + return oneLine; + } else { + return oneLine.substring(0, length - 3) + "..."; + } + } + + /** + * Process a type declaration: copy its annotations to {@code #annotationFileAnnos}. + * + *

This method stores the declaration's type parameters in {@link #typeParameters}. When + * processing an ajava file, where traversal is handled externaly by a {@link + * org.checkerframework.framework.ajava.JointJavacJavaParserVisitor}, these type variables must be + * removed after processing the type's members. Otherwise, this method removes them. + * + * @param typeDecl the type declaration to process + * @param outerTypeName the name of the containing class, when processing a nested class; + * otherwise null + * @param classTree the tree corresponding to typeDecl if processing an ajava file, null otherwise + * @return a list of types variables for {@code typeDecl}. Only non-null if processing an ajava + * file, in which case the contents should be removed from {@link #typeParameters} after + * processing the type declaration's members + */ + private @Nullable List processTypeDecl( + TypeDeclaration typeDecl, @Nullable String outerTypeName, @Nullable ClassTree classTree) { + assert typeBeingParsed != null; + if (skipNode(typeDecl)) { + return null; + } + String innerName; + @FullyQualifiedName String fqTypeName; + TypeElement typeElt; + if (classTree != null) { + typeElt = TreeUtils.elementFromDeclaration(classTree); + innerName = typeElt.getQualifiedName().toString(); + typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); + fqTypeName = typeBeingParsed.toString(); + } else { + String packagePrefix = outerTypeName == null ? "" : outerTypeName + "."; + innerName = packagePrefix + typeDecl.getNameAsString(); + typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); + fqTypeName = typeBeingParsed.toString(); + typeElt = elements.getTypeElement(fqTypeName); + } + + if (!isAnnotatedForThisChecker(typeDecl.getAnnotations())) { + return null; + } + if (typeElt == null) { + if (debugAnnotationFileParser + || (!warnIfNotFoundIgnoresClasses + && !hasNoAnnotationFileParserWarning(typeDecl.getAnnotations()) + && !hasNoAnnotationFileParserWarning(packageAnnos))) { + if (elements.getAllTypeElements(fqTypeName).isEmpty()) { + stubWarnNotFound(typeDecl, "type not found: " + fqTypeName); + } else { + stubWarnNotFound( + typeDecl, + "type not found uniquely: " + + fqTypeName + + " : " + + elements.getAllTypeElements(fqTypeName)); + } + } + return null; + } + + List typeDeclTypeParameters = null; + if (typeElt.getKind() == ElementKind.ENUM) { + if (!(typeDecl instanceof EnumDeclaration)) { + warn( + typeDecl, + innerName + + " is an enum, but stub file declared it as " + + javaParserNodeToStringTruncated(typeDecl, 100)); + return null; + } + typeDeclTypeParameters = processEnum((EnumDeclaration) typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } else if (typeElt.getKind() == ElementKind.ANNOTATION_TYPE) { + if (!(typeDecl instanceof AnnotationDeclaration)) { + warn( + typeDecl, + innerName + + " is an annotation, but stub file declared it as " + + javaParserNodeToStringTruncated(typeDecl, 100)); + return null; + } + typeDeclTypeParameters = processType(typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } else if (typeDecl instanceof ClassOrInterfaceDeclaration) { + // TODO: This test is never satisfied, because it is the opposite of that on the line + // above. + if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) { + warn( + typeDecl, + innerName + + " is a class or interface, but stub file declared it as " + + javaParserNodeToStringTruncated(typeDecl, 100)); + return null; + } + typeDeclTypeParameters = processType(typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } else if (typeDecl instanceof RecordDeclaration) { + typeDeclTypeParameters = processType(typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } // else it's an EmptyTypeDeclaration. TODO: An EmptyTypeDeclaration can have + // annotations, right? + + // If processing an ajava file, then traversal is handled by a visitor, rather than the rest + // of this method. + if (fileType == AnnotationFileType.AJAVA) { + return typeDeclTypeParameters; + } + + if (typeDecl instanceof RecordDeclaration) { + RecordDeclaration recordDecl = (RecordDeclaration) typeDecl; + NodeList recordMembers = recordDecl.getParameters(); + Map byName = + ArrayMap.newArrayMapOrLinkedHashMap(recordMembers.size()); + for (Parameter recordMember : recordMembers) { + RecordComponentStub stub = + processRecordField( + recordMember, + findFieldElement(typeElt, recordMember.getNameAsString(), recordMember)); + byName.put(recordMember.getNameAsString(), stub); + } + annotationFileAnnos.records.put( + recordDecl.getFullyQualifiedName().get(), new RecordStub(byName)); + } + + IPair>, Map>>> members = + getMembers(typeDecl, typeElt, typeDecl); + for (Map.Entry> entry : members.first.entrySet()) { + Element elt = entry.getKey(); + BodyDeclaration decl = entry.getValue(); + switch (elt.getKind()) { + case FIELD: + processField((FieldDeclaration) decl, (VariableElement) elt); + break; + case ENUM_CONSTANT: + // Enum constants can occur as fields in stubs files when their + // type has an annotation on it, e.g. see DeviceTypeTest which ends up with + // the TRACKER enum constant annotated with DefaultType: + if (decl instanceof FieldDeclaration) { + processField((FieldDeclaration) decl, (VariableElement) elt); + } else if (decl instanceof EnumConstantDeclaration) { + processEnumConstant((EnumConstantDeclaration) decl, (VariableElement) elt); + } else { + throw new BugInCF( + "unexpected decl type " + + decl.getClass() + + " for ENUM_CONSTANT kind, original: " + + decl); + } + break; + case CONSTRUCTOR: + case METHOD: + processCallableDeclaration((CallableDeclaration) decl, (ExecutableElement) elt); + break; + case CLASS: + case INTERFACE: + // Not processing an ajava file, so ignore the return value. + processTypeDecl((ClassOrInterfaceDeclaration) decl, innerName, null); + break; + case ENUM: + // Not processing an ajava file, so ignore the return value. + processTypeDecl((EnumDeclaration) decl, innerName, null); + break; + default: + /* do nothing */ + stubWarnNotFound(decl, "AnnotationFileParser ignoring: " + elt); + break; + } + } + for (Map.Entry>> entry : members.second.entrySet()) { + ExecutableElement fakeOverridden = (ExecutableElement) entry.getKey(); + List> fakeOverrideDecls = entry.getValue(); + for (BodyDeclaration bodyDecl : fakeOverrideDecls) { + processFakeOverride(fakeOverridden, (CallableDeclaration) bodyDecl, typeElt); + } + } + + if (typeDeclTypeParameters != null) { + typeParameters.removeAll(typeDeclTypeParameters); + } + + return null; + } + + /** + * Returns true if the argument contains {@code @NoAnnotationFileParserWarning}. + * + * @param aexprs collection of annotation expressions + * @return true if {@code aexprs} contains {@code @NoAnnotationFileParserWarning} + */ + private boolean hasNoAnnotationFileParserWarning(Iterable aexprs) { + if (aexprs == null) { + return false; + } + for (AnnotationExpr anno : aexprs) { + if (anno.getNameAsString().equals("NoAnnotationFileParserWarning")) { + return true; + } + } + return false; + } + + /** + * Process the type's declaration: copy its annotations to {@code #annotationFileAnnos}. Does not + * process any of its members. Returns the type's type parameter declarations. + * + * @param decl a type declaration + * @param elt the type's element + * @return the type's type parameter declarations + */ + private List processType(TypeDeclaration decl, TypeElement elt) { + + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + AnnotatedDeclaredType type = atypeFactory.fromElement(elt); + annotate(type, decl.getAnnotations(), decl); + + List typeArguments = type.getTypeArguments(); + List typeParameters; + if (decl instanceof NodeWithTypeParameters) { + typeParameters = ((NodeWithTypeParameters) decl).getTypeParameters(); + } else { + typeParameters = Collections.emptyList(); + } + + // It can be the case that args=[] and params=null, so don't crash in that case. + // if ((typeParameters == null) != (typeArguments == null)) { + // throw new Error(String.format("parseType (%s, %s): inconsistent nullness for args and + // params%n args = %s%n params = %s%n", decl, elt, typeArguments, typeParameters)); + // } + + if (debugAnnotationFileParser) { + int numParams = (typeParameters == null ? 0 : typeParameters.size()); + int numArgs = (typeArguments == null ? 0 : typeArguments.size()); + if (numParams != numArgs) { + stubDebug( + "parseType: mismatched sizes for typeParameters=%s (size %d)" + + " and typeArguments=%s (size %d);" + + " decl=%s; elt=%s (%s); type=%s (%s); typeBeingParsed=%s", + typeParameters, + numParams, + typeArguments, + numArgs, + decl.toString().replace(LINE_SEPARATOR, " "), + elt.toString().replace(LINE_SEPARATOR, " "), + elt.getClass(), + type, + type.getClass(), + typeBeingParsed); + stubDebug("proceeding despite mismatched sizes"); + } + } + + annotateTypeParameters(decl, elt, typeArguments, typeParameters); + if (decl instanceof ClassOrInterfaceDeclaration) { + annotateSupertypes((ClassOrInterfaceDeclaration) decl, type); + } + putMerge(annotationFileAnnos.atypes, elt, type); + List typeVariables = new ArrayList<>(type.getTypeArguments().size()); + for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { + if (typeV.getKind() != TypeKind.TYPEVAR) { + warn( + decl, + "expected an AnnotatedTypeVariable but found type kind " + + typeV.getKind() + + ": " + + typeV); + } else { + typeVariables.add((AnnotatedTypeVariable) typeV); + } + } + return typeVariables; + } + + /** + * Process an enum: copy its annotations to {@code #annotationFileAnnos}. Returns the enum's type + * parameter declarations. + * + * @param decl enum declaration + * @param elt element representing enum + * @return the enum's type parameter declarations + */ + private List processEnum(EnumDeclaration decl, TypeElement elt) { + + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + AnnotatedDeclaredType type = atypeFactory.fromElement(elt); + annotate(type, decl.getAnnotations(), decl); + + putMerge(annotationFileAnnos.atypes, elt, type); + List typeVariables = new ArrayList<>(type.getTypeArguments().size()); + for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { + if (typeV.getKind() != TypeKind.TYPEVAR) { + warn( + decl, + "expected an AnnotatedTypeVariable but found type kind " + + typeV.getKind() + + ": " + + typeV); + } else { + typeVariables.add((AnnotatedTypeVariable) typeV); + } + } + return typeVariables; + } + + private void annotateSupertypes( + ClassOrInterfaceDeclaration typeDecl, AnnotatedDeclaredType type) { + if (typeDecl.getExtendedTypes() != null) { + for (ClassOrInterfaceType supertype : typeDecl.getExtendedTypes()) { + AnnotatedDeclaredType annotatedSupertype = + findAnnotatedType(supertype, type.directSupertypes(), typeDecl); + if (annotatedSupertype == null) { + warn( + typeDecl, + "stub file does not match bytecode: " + + "could not find direct superclass " + + supertype + + " from type " + + type); + } else { + annotate(annotatedSupertype, supertype, null, typeDecl); + } + } + } + if (typeDecl.getImplementedTypes() != null) { + for (ClassOrInterfaceType supertype : typeDecl.getImplementedTypes()) { + AnnotatedDeclaredType annotatedSupertype = + findAnnotatedType(supertype, type.directSupertypes(), typeDecl); + if (annotatedSupertype == null) { + warn( + typeDecl, + "stub file does not match bytecode: " + + "could not find direct superinterface " + + supertype + + " from type " + + type); + } else { + annotate(annotatedSupertype, supertype, null, typeDecl); + } + } + } + } + + /** + * Process a method or constructor declaration: copy its annotations to {@code + * #annotationFileAnnos}. + * + * @param decl a method or constructor declaration, as read from an annotation file + * @param elt the method or constructor's element + * @return type variables for the method + */ + private @Nullable List processCallableDeclaration( + CallableDeclaration decl, ExecutableElement elt) { + if (!isAnnotatedForThisChecker(decl.getAnnotations())) { + return null; + } + // Declaration annotations + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + if (decl.isMethodDeclaration()) { + // AnnotationFileParser parses all annotations in type annotation position as type + // annotations. + recordDeclAnnotation(elt, ((MethodDeclaration) decl).getType().getAnnotations(), decl); + } + markAsFromStubFile(elt); + + AnnotatedExecutableType methodType; + try { + methodType = atypeFactory.fromElement(elt); + } catch (ErrorTypeKindException e) { + stubWarnNotFound(decl, "Error type kind occurred: " + e.getLocalizedMessage()); + return Collections.emptyList(); + } + + AnnotatedExecutableType origMethodType = + warnIfStubRedundantWithBytecode ? methodType.deepCopy() : null; + + // Type Parameters + annotateTypeParameters(decl, elt, methodType.getTypeVariables(), decl.getTypeParameters()); + typeParameters.addAll(methodType.getTypeVariables()); + + // Return type, from declaration annotations on the method or constructor + if (decl.isMethodDeclaration()) { + MethodDeclaration methodDeclaration = (MethodDeclaration) decl; + if (methodDeclaration.getParameters().isEmpty()) { + String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); + RecordStub recordStub = annotationFileAnnos.records.get(qualRecordName); + if (recordStub != null) { + RecordComponentStub recordComponentStub = + recordStub.componentsByName.get(methodDeclaration.getNameAsString()); + if (recordComponentStub != null) { + recordComponentStub.hasAccessorInStubs = true; + } + } + } + + try { + annotate( + methodType.getReturnType(), methodDeclaration.getType(), decl.getAnnotations(), decl); + } catch (ErrorTypeKindException e) { + // See https://github.com/typetools/checker-framework/issues/244 . + // Issue a warning, to enable fixes to the classpath. + stubWarnNotFound(decl, "Error type kind occurred: " + e); + } + } else { + assert decl.isConstructorDeclaration(); + if (AnnotationFileUtil.isCanonicalConstructor(elt, atypeFactory.types)) { + // If this is the (user-written) canonical constructor, record that the component + // annotations should not be automatically transferred: + String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); + if (annotationFileAnnos.records.containsKey(qualRecordName)) { + List parameters = elt.getParameters(); + ArrayList annotatedParameters = new ArrayList<>(parameters.size()); + for (int i = 0; i < parameters.size(); i++) { + VariableElement parameter = parameters.get(i); + AnnotatedTypeMirror atm = + AnnotatedTypeMirror.createType(parameter.asType(), atypeFactory, false); + annotate(atm, decl.getParameter(i).getAnnotations(), decl.getParameter(i)); + annotatedParameters.add(atm); + } + annotationFileAnnos.records.get(qualRecordName).componentsInCanonicalConstructor = + annotatedParameters; + } + } + annotate(methodType.getReturnType(), decl.getAnnotations(), decl); + } + + // Parameters + processParameters(decl, elt, methodType); + + // Receiver + if (decl.getReceiverParameter().isPresent()) { + ReceiverParameter receiverParameter = decl.getReceiverParameter().get(); + if (methodType.getReceiverType() == null) { + if (decl.isConstructorDeclaration()) { + warn( + receiverParameter, + "parseParameter: constructor %s of a top-level class" + + " cannot have receiver annotations %s", + methodType, + receiverParameter.getAnnotations()); + } else { + warn( + receiverParameter, + "parseParameter: static method %s cannot have receiver annotations %s", + methodType, + receiverParameter.getAnnotations()); + } + } else { + // Add declaration annotations. + annotate( + methodType.getReceiverType(), receiverParameter.getAnnotations(), receiverParameter); + // Add type annotations. + annotate( + methodType.getReceiverType(), + receiverParameter.getType(), + receiverParameter.getAnnotations(), + receiverParameter); + } + } + + if (warnIfStubRedundantWithBytecode + && methodType.toString().equals(origMethodType.toString()) + && fileType != AnnotationFileType.BUILTIN_STUB) { + warn( + decl, + String.format( + "stub file specification is same as bytecode for %s", + ElementUtils.getQualifiedName(elt))); + } + + // Store the type. + putMerge(annotationFileAnnos.atypes, elt, methodType); + if (fileType.isStub()) { + typeParameters.removeAll(methodType.getTypeVariables()); + } + + return methodType.getTypeVariables(); + } + + /** + * Process the parameters of a method or constructor declaration: copy their annotations to {@code + * #annotationFileAnnos}. + * + * @param method a method or constructor declaration + * @param elt the element for {@code method} + * @param methodType the annotated type of {@code method} + */ + private void processParameters( + CallableDeclaration method, ExecutableElement elt, AnnotatedExecutableType methodType) { + List params = method.getParameters(); + List paramElts = elt.getParameters(); + List paramTypes = methodType.getParameterTypes(); + + for (int i = 0; i < methodType.getParameterTypes().size(); ++i) { + VariableElement paramElt = paramElts.get(i); + AnnotatedTypeMirror paramType = paramTypes.get(i); + Parameter param = params.get(i); + + recordDeclAnnotation(paramElt, param.getAnnotations(), param); + recordDeclAnnotation(paramElt, param.getType().getAnnotations(), param); + + if (param.isVarArgs()) { + assert paramType.getKind() == TypeKind.ARRAY; + // The "type" of param is actually the component type of the vararg. + // For example, in "Object..." the type would be "Object". + annotate( + ((AnnotatedArrayType) paramType).getComponentType(), + param.getType(), + param.getAnnotations(), + param); + // The "VarArgsAnnotations" are those just before "...". + annotate(paramType, param.getVarArgsAnnotations(), param); + } else { + annotate(paramType, param.getType(), param.getAnnotations(), param); + putMerge(annotationFileAnnos.atypes, paramElt, paramType); + } + } + } + + /** + * Clear (remove) existing annotations on the type. + * + *

Stub files override annotations read from .class files. Using {@code replaceAnnotation} + * usually achieves this; however, for annotations on type variables, it is sometimes necessary to + * remove an existing annotation, leaving no annotation on the type variable. This method does so. + * + * @param atype the type to modify + * @param typeDef the type from the annotation file, used only for diagnostic messages + */ + @SuppressWarnings("unused") // for disabled warning message + private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) { + // TODO: only produce output if the removed annotation isn't the top or default + // annotation in the type hierarchy. See https://tinyurl.com/cfissue/2759 . + /* + if (!atype.getAnnotations().isEmpty()) { + stubWarnOverwritesBytecode( + String.format( + "in file %s at line %s removed existing annotations on type: %s", + filename.substring(filename.lastIndexOf('/') + 1), + typeDef.getBegin().get().line, + atype.toString(true))); + } + */ + // Clear existing annotations, which only makes a difference for + // type variables, but doesn't hurt in other cases. + atype.clearAnnotations(); + } + + /** + * Add the annotations from {@code type} to {@code atype}. Type annotations that parsed as + * declaration annotations (i.e., type annotations in {@code declAnnos}) are applied to the + * innermost component type. + * + * @param atype annotated type to which to add annotations + * @param type parsed type + * @param declAnnos annotations stored on the declaration of the variable with this type or null + * @param astNode where to report errors + */ + private void annotateAsArray( + AnnotatedArrayType atype, + ReferenceType type, + @Nullable NodeList declAnnos, + NodeWithRange astNode) { + annotateInnermostComponentType(atype, declAnnos, astNode); + Type typeDef = type; + AnnotatedTypeMirror currentAtype = atype; + while (typeDef.isArrayType()) { + if (currentAtype.getKind() != TypeKind.ARRAY) { + warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); + return; + } + + // handle generic type + clearAnnotations(currentAtype, typeDef); + + List annotations = typeDef.getAnnotations(); + if (annotations != null) { + annotate(currentAtype, annotations, astNode); + } + typeDef = ((com.github.javaparser.ast.type.ArrayType) typeDef).getComponentType(); + currentAtype = ((AnnotatedArrayType) currentAtype).getComponentType(); + } + if (currentAtype.getKind() == TypeKind.ARRAY) { + warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); + } + } + + private @Nullable ClassOrInterfaceType unwrapDeclaredType(Type type) { + if (type instanceof ClassOrInterfaceType) { + return (ClassOrInterfaceType) type; + } else if (type instanceof ReferenceType && type.getArrayLevel() == 0) { + return unwrapDeclaredType(type.getElementType()); + } else { + return null; + } + } + + /** + * Add to formal parameter {@code atype}: + * + *

    + *
  1. the annotations from {@code typeDef}, and + *
  2. any type annotations that parsed as declaration annotations (i.e., type annotations in + * {@code declAnnos}). + *
+ * + * @param atype annotated type to which to add annotations + * @param typeDef parsed type + * @param declAnnos annotations stored on the declaration of the variable with this type, or null + * @param astNode where to report errors + */ + private void annotate( + AnnotatedTypeMirror atype, + Type typeDef, + @Nullable NodeList declAnnos, + NodeWithRange astNode) { + if (atype.getKind() == TypeKind.ARRAY) { + if (typeDef instanceof ReferenceType) { + annotateAsArray((AnnotatedArrayType) atype, (ReferenceType) typeDef, declAnnos, astNode); + } else { + warn(astNode, "expected ReferenceType but found: " + typeDef); + } + return; + } + + clearAnnotations(atype, typeDef); + + // Primary annotations for the type of a variable declaration are not stored in typeDef, but + // rather as declaration annotations (passed as declAnnos to this method). But, if typeDef + // is not the type of a variable, then the primary annotations are stored in typeDef. + NodeList primaryAnnotations; + if (typeDef.getAnnotations().isEmpty() && declAnnos != null) { + primaryAnnotations = declAnnos; + } else { + primaryAnnotations = typeDef.getAnnotations(); + } + if (atype.getKind() != TypeKind.WILDCARD) { + // The primary annotation on a wildcard applies to the super or extends bound and + // are added below. + annotate(atype, primaryAnnotations, astNode); + } + + switch (atype.getKind()) { + case DECLARED: + ClassOrInterfaceType declType = unwrapDeclaredType(typeDef); + if (declType == null) { + break; + } + AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType) atype; + // Process type arguments. + @SuppressWarnings("optional:optional.collection") // JavaParser uses Optional + Optional> oDeclTypeArgs = declType.getTypeArguments(); + List adeclTypeArgs = adeclType.getTypeArguments(); + if (oDeclTypeArgs.isPresent() + && !oDeclTypeArgs.get().isEmpty() + && !adeclTypeArgs.isEmpty()) { + NodeList declTypeArgs = oDeclTypeArgs.get(); + if (declTypeArgs.size() != adeclTypeArgs.size()) { + warn( + astNode, + String.format( + "Mismatch in type argument size between %s (%d) and %s (%d)", + declType, declTypeArgs.size(), adeclType, adeclTypeArgs.size())); + break; + } + for (int i = 0; i < declTypeArgs.size(); ++i) { + annotate(adeclTypeArgs.get(i), declTypeArgs.get(i), null, astNode); + } + } + break; + case WILDCARD: + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atype; + // Ensure that the file also has a wildcard type, report an error otherwise + if (!typeDef.isWildcardType()) { + // We throw an error here, as otherwise we are just getting a generic cast error + // on the very next line. + warn( + astNode, + "wildcard type <" + + atype + + "> does not match type in stubs file" + + filename + + ": <" + + typeDef + + ">" + + " while parsing " + + typeBeingParsed); + return; + } + WildcardType wildcardDef = (WildcardType) typeDef; + if (wildcardDef.getExtendedType().isPresent()) { + annotate( + wildcardType.getExtendsBound(), wildcardDef.getExtendedType().get(), null, astNode); + annotate(wildcardType.getSuperBound(), primaryAnnotations, astNode); + } else if (wildcardDef.getSuperType().isPresent()) { + annotate(wildcardType.getSuperBound(), wildcardDef.getSuperType().get(), null, astNode); + annotate(wildcardType.getExtendsBound(), primaryAnnotations, astNode); + } else if (primaryAnnotations.isEmpty()) { + // Unannotated unbounded wildcard "?": remove any existing annotations and + // add the annotations from the type variable corresponding to the wildcard. + wildcardType.getExtendsBound().clearAnnotations(); + wildcardType.getSuperBound().clearAnnotations(); + AnnotatedTypeVariable atv = + (AnnotatedTypeVariable) + atypeFactory.getAnnotatedType(wildcardType.getTypeVariable().asElement()); + wildcardType.getExtendsBound().addAnnotations(atv.getUpperBound().getAnnotations()); + wildcardType.getSuperBound().addAnnotations(atv.getLowerBound().getAnnotations()); + } else { + // Annotated unbounded wildcard "@A ?": use annotations. + annotate(atype, primaryAnnotations, astNode); + } + break; + case TYPEVAR: + // Add annotations from the declaration of the TypeVariable + AnnotatedTypeVariable typeVarUse = (AnnotatedTypeVariable) atype; + Types typeUtils = processingEnv.getTypeUtils(); + for (AnnotatedTypeVariable typePar : typeParameters) { + if (typeUtils.isSameType(typePar.getUnderlyingType(), atype.getUnderlyingType())) { + atypeFactory.replaceAnnotations(typePar.getUpperBound(), typeVarUse.getUpperBound()); + atypeFactory.replaceAnnotations(typePar.getLowerBound(), typeVarUse.getLowerBound()); + } + } + break; + default: + // No additional annotations to add. + } + } + + /** + * Process the field declaration in decl: copy its annotations to {@code #annotationFileAnnos}. + * + * @param decl the declaration in the annotation file + * @param elt the element representing that same declaration + */ + private void processField(FieldDeclaration decl, VariableElement elt) { + if (skipNode(decl)) { + // Don't process private fields of the JDK. They can't be referenced outside of the JDK + // and might refer to types that are not accessible. + return; + } + markAsFromStubFile(elt); + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + // AnnotationFileParser parses all annotations in type annotation position as type + // annotations + recordDeclAnnotation(elt, decl.getElementType().getAnnotations(), decl); + AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); + + VariableDeclarator fieldVarDecl = null; + String eltName = elt.getSimpleName().toString(); + for (VariableDeclarator var : decl.getVariables()) { + if (var.getName().toString().equals(eltName)) { + fieldVarDecl = var; + break; + } + } + assert fieldVarDecl != null; + annotate(fieldType, fieldVarDecl.getType(), decl.getAnnotations(), fieldVarDecl); + putMerge(annotationFileAnnos.atypes, elt, fieldType); + } + + /** + * Processes a parameter in a record header (i.e., a record component). + * + * @param decl the parameter in the record header + * @param elt the corresponding variable declaration element + * @return a representation of the record component in the stub file + */ + private RecordComponentStub processRecordField(Parameter decl, VariableElement elt) { + markAsFromStubFile(elt); + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + // AnnotationFileParser parses all annotations in type annotation position as type + // annotations. + recordDeclAnnotation(elt, decl.getType().getAnnotations(), decl); + AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); + + annotate(fieldType, decl.getType(), decl.getAnnotations(), decl); + putMerge(annotationFileAnnos.atypes, elt, fieldType); + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + for (AnnotationExpr annotation : decl.getAnnotations()) { + AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); + annos.add(annoMirror); + } + return new RecordComponentStub(fieldType, annos); + } + + /** + * Adds the annotations present on the declaration of an enum constant to the ATM of that + * constant. + * + * @param decl the enum constant, in Javaparser AST form (the source of annotations) + * @param elt the enum constant declaration, as an element (the destination for annotations) + */ + private void processEnumConstant(EnumConstantDeclaration decl, VariableElement elt) { + markAsFromStubFile(elt); + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + AnnotatedTypeMirror enumConstType = atypeFactory.fromElement(elt); + annotate(enumConstType, decl.getAnnotations(), decl); + putMerge(annotationFileAnnos.atypes, elt, enumConstType); + } + + /** + * Returns the innermost component type of {@code type}. + * + * @param type array type + * @return the innermost component type of {@code type} + */ + private AnnotatedTypeMirror innermostComponentType(AnnotatedArrayType type) { + AnnotatedTypeMirror componentType = type; + while (componentType.getKind() == TypeKind.ARRAY) { + componentType = ((AnnotatedArrayType) componentType).getComponentType(); + } + return componentType; + } + + /** + * Adds {@code annotations} to the innermost component type of {@code type}. + * + * @param type array type + * @param annotations annotations to add + * @param astNode where to report errors + */ + private void annotateInnermostComponentType( + AnnotatedArrayType type, List annotations, NodeWithRange astNode) { + annotate(innermostComponentType(type), annotations, astNode); + } + + /** + * Annotate the type with the given type annotations, removing any existing annotations from the + * same qualifier hierarchies. + * + * @param type the type to annotate + * @param annotations the new annotations for the type; if null, nothing is done + * @param astNode where to report errors + */ + private void annotate( + AnnotatedTypeMirror type, + @Nullable List annotations, + NodeWithRange astNode) { + if (annotations == null) { + return; + } + for (AnnotationExpr annotation : annotations) { + AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); + if (annoMirror != null) { + type.replaceAnnotation(annoMirror); + } else { + // TODO: Maybe always warn here. It's so easy to forget an import statement and + // have an annotation silently ignored. + stubWarnNotFound(astNode, "unknown annotation " + annotation); + } + } + } + + /** + * Adds to {@code annotationFileAnnos} all the annotations in {@code annotations} that are + * applicable to {@code elt}'s location. For example, if an annotation is a type annotation but + * {@code elt} is a field declaration, the type annotation will be ignored. + * + * @param elt the element to be annotated + * @param annotations set of annotations that may be applicable to elt + * @param astNode where to report errors + */ + private void recordDeclAnnotation( + Element elt, List annotations, NodeWithRange astNode) { + if (annotations == null || annotations.isEmpty()) { + return; + } + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + for (AnnotationExpr annotation : annotations) { + AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); + if (annoMirror != null) { + // The @Target annotation on `annotation`/`annoMirror` + Target target = annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); + // Only add the declaration annotation if the annotation applies to the element. + if (AnnotationUtils.getElementKindsForTarget(target).contains(elt.getKind())) { + // `annoMirror` is applicable to `elt` + annos.add(annoMirror); + } + } else { + // TODO: Maybe always warn here. It's so easy to forget an import statement and + // have an annotation silently ignored. + stubWarnNotFound(astNode, String.format("unknown annotation %s", annotation)); + } + } + String eltName = ElementUtils.getQualifiedName(elt); + putOrAddToDeclAnnos(eltName, annos); + } + + /** + * Adds the declaration annotation {@code @FromStubFile} to {@link #annotationFileAnnos}, unless + * we are parsing the JDK as a stub file. + * + * @param elt an element to be annotated as {@code @FromStubFile} + */ + private void markAsFromStubFile(Element elt) { + if (fileType == AnnotationFileType.AJAVA || fileType == AnnotationFileType.JDK_STUB) { + return; + } + putOrAddToDeclAnnos( + ElementUtils.getQualifiedName(elt), AnnotationMirrorSet.singleton(fromStubFileAnno)); + } + + private void annotateTypeParameters( + BodyDeclaration decl, // for debugging + Object elt, // for debugging; TypeElement or ExecutableElement + List typeArguments, + List typeParameters) { + if (typeParameters == null) { + return; + } + + if (typeParameters.size() != typeArguments.size()) { + String msg = + String.format( + "annotateTypeParameters: mismatched sizes:" + + " typeParameters (size %d)=%s;" + + " typeArguments (size %d)=%s;" + + " decl=%s; elt=%s (%s).", + typeParameters.size(), + typeParameters, + typeArguments.size(), + typeArguments, + decl.toString().replace(LINE_SEPARATOR, " "), + elt.toString().replace(LINE_SEPARATOR, " "), + elt.getClass()); + if (!debugAnnotationFileParser) { + msg = msg + "; for more details, run with -AstubDebug"; + } + warn(decl, msg); + return; + } + for (int i = 0; i < typeParameters.size(); ++i) { + TypeParameter param = typeParameters.get(i); + AnnotatedTypeVariable paramType = (AnnotatedTypeVariable) typeArguments.get(i); + + // Handle type bounds + if (param.getTypeBound() == null || param.getTypeBound().isEmpty()) { + // No type bound, so annotations are both lower and upper bounds. + annotate(paramType, param.getAnnotations(), param); + } else if (param.getTypeBound() != null && !param.getTypeBound().isEmpty()) { + annotate(paramType.getLowerBound(), param.getAnnotations(), param); + if (param.getTypeBound().size() == 1) { + // The additional declAnnos (third argument) is always null in this call to + // `annotate`, but the type bound (second argument) might have annotations. + annotate(paramType.getUpperBound(), param.getTypeBound().get(0), null, param); + } else { + // param.getTypeBound().size() > 1 + ArrayList typeBoundsWithAnotations = + new ArrayList<>(param.getTypeBound().size()); + for (ClassOrInterfaceType typeBound : param.getTypeBound()) { + if (!typeBound.getAnnotations().isEmpty()) { + typeBoundsWithAnotations.add(typeBound); + } + } + int numBounds = typeBoundsWithAnotations.size(); + if (numBounds == 0) { + // nothing to do + } else if (numBounds == 1) { + annotate(paramType.getUpperBound(), typeBoundsWithAnotations.get(0), null, param); + } else { + // TODO: add support for intersection types + // One problem is that `annotate()` removes any existing annotations from + // the same qualifier hierarchies, so paramType.getLowerBound() would end up + // with the annotations of only the last type bound. + + // String msg = + // String.format( + // "annotateTypeParameters: multiple type bounds: + // typeParameters=%s; " + // + "param #%d=%s; bounds=%s; decl=%s; elt=%s (%s).", + // typeParameters, + // i, + // param, + // param.getTypeBound(), + // decl.toString().replace(LINE_SEPARATOR, " "), + // elt.toString().replace(LINE_SEPARATOR, " "), + // elt.getClass()); + // warn(decl, msg); + + stubWarnNotFound( + param, "annotations on intersection types are not yet supported: " + param); + } + } + if (param.getTypeBound().size() == 1 + && param.getTypeBound().get(0).getAnnotations().isEmpty() + && TypesUtils.isObject(paramType.getUpperBound().getUnderlyingType())) { + // If there is an explicit "T extends Object" type parameter bound, + // treat it like an explicit use of "Object" in code. + AnnotatedTypeMirror ub = atypeFactory.getAnnotatedType(Object.class); + paramType.getUpperBound().replaceAnnotations(ub.getAnnotations()); + } + } + + putMerge(annotationFileAnnos.atypes, paramType.getUnderlyingType().asElement(), paramType); + } + } + + /** + * Returns a pair of mappings. For each member declaration of the JavaParser type declaration + * {@code typeDecl}: + * + *
    + *
  • If {@code typeElt} contains a member element for it, the first mapping maps the member + * element to it. + *
  • If it is a fake override, the second mapping maps each element it overrides to it. + *
  • Otherwise, does nothing. + *
+ * + * This method does not read or write the field {@link #annotationFileAnnos}. + * + * @param typeDecl a JavaParser type declaration + * @param typeElt the javac element for {@code typeDecl} + * @return two mappings: from javac elements to their JavaParser declaration, and from javac + * elements to fake overrides of them + * @param astNode where to report errors + */ + private IPair>, Map>>> + getMembers(TypeDeclaration typeDecl, TypeElement typeElt, NodeWithRange astNode) { + assert (typeElt.getSimpleName().contentEquals(typeDecl.getNameAsString()) + || typeDecl.getNameAsString().endsWith("$" + typeElt.getSimpleName())) + : String.format("%s %s", typeElt.getSimpleName(), typeDecl.getName()); + + Map> elementsToDecl = new LinkedHashMap<>(); + Map>> fakeOverrideDecls = new LinkedHashMap<>(); + + for (BodyDeclaration member : typeDecl.getMembers()) { + putNewElement( + elementsToDecl, fakeOverrideDecls, typeElt, member, typeDecl.getNameAsString(), astNode); + } + // For an enum type declaration, also add the enum constants + if (typeDecl instanceof EnumDeclaration) { + EnumDeclaration enumDecl = (EnumDeclaration) typeDecl; + // getEntries() gives the list of enum constant declarations + for (BodyDeclaration member : enumDecl.getEntries()) { + putNewElement( + elementsToDecl, + fakeOverrideDecls, + typeElt, + member, + typeDecl.getNameAsString(), + astNode); + } + } + + return IPair.of(elementsToDecl, fakeOverrideDecls); + } + + // Used only by getMembers(). + /** + * If {@code typeElt} contains an element for {@code member}, adds to {@code elementsToDecl} a + * mapping from member's element to member. Does nothing if a mapping already exists. + * + *

Otherwise (if there is no element for {@code member}), adds to {@code fakeOverrideDecls} + * zero or more mappings. Each mapping is from an element that {@code member} would override to + * {@code member}. + * + *

This method does not read or write field {@link #annotationFileAnnos}. + * + * @param elementsToDecl the mapping that is side-effected by this method + * @param fakeOverrideDecls fake overrides, also side-effected by this method + * @param typeElt the class in which {@code member} is declared + * @param member the stub file declaration of a method + * @param typeDeclName used only for debugging + * @param astNode where to report errors + */ + private void putNewElement( + Map> elementsToDecl, + Map>> fakeOverrideDecls, + TypeElement typeElt, + BodyDeclaration member, + String typeDeclName, + NodeWithRange astNode) { + if (member instanceof MethodDeclaration) { + MethodDeclaration method = (MethodDeclaration) member; + Element elt = findElement(typeElt, method, /* noWarn= */ true); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, method); + } else { + ExecutableElement overriddenMethod = fakeOverriddenMethod(typeElt, method); + if (overriddenMethod == null) { + // Didn't find the element and it isn't a fake override. Issue a warning. + findElement(typeElt, method, /* noWarn= */ false); + } else { + List> l = + fakeOverrideDecls.computeIfAbsent(overriddenMethod, __ -> new ArrayList<>(1)); + l.add(member); + } + } + } else if (member instanceof ConstructorDeclaration) { + Element elt = findElement(typeElt, (ConstructorDeclaration) member); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else if (member instanceof FieldDeclaration) { + FieldDeclaration fieldDecl = (FieldDeclaration) member; + for (VariableDeclarator var : fieldDecl.getVariables()) { + Element varelt = findElement(typeElt, var); + if (varelt != null) { + putIfAbsent(elementsToDecl, varelt, fieldDecl); + } + } + } else if (member instanceof EnumConstantDeclaration) { + Element elt = findElement(typeElt, (EnumConstantDeclaration) member, astNode); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else if (member instanceof ClassOrInterfaceDeclaration) { + Element elt = findElement(typeElt, (ClassOrInterfaceDeclaration) member); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else if (member instanceof EnumDeclaration) { + Element elt = findElement(typeElt, (EnumDeclaration) member); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else { + stubDebug("ignoring element of type %s in %s", member.getClass(), typeDeclName); + } + } + + /** + * Given a method declaration that does not correspond to an element, returns the method it + * directly overrides or implements. As Java does, this prefers a method in a superclass to one in + * an interface. + * + *

As with regular overrides, the parameter types must be exact matches; contravariance is not + * permitted. + * + * @param typeElt the type in which the method appears + * @param methodDecl the method declaration that does not correspond to an element + * @return the methods that the given method declaration would override, or null if none + */ + private @Nullable ExecutableElement fakeOverriddenMethod( + TypeElement typeElt, MethodDeclaration methodDecl) { + for (Element elt : typeElt.getEnclosedElements()) { + if (elt.getKind() != ElementKind.METHOD) { + continue; + } + ExecutableElement candidate = (ExecutableElement) elt; + if (!candidate.getSimpleName().contentEquals(methodDecl.getName().getIdentifier())) { + continue; + } + List candidateParams = candidate.getParameters(); + if (sameTypes(candidateParams, methodDecl.getParameters())) { + return candidate; + } + } + + TypeElement superType = ElementUtils.getSuperClass(typeElt); + if (superType != null) { + ExecutableElement result = fakeOverriddenMethod(superType, methodDecl); + if (result != null) { return result; + } } - // If a member is imported, then consider every containing class to also be imported. - private void addEnclosingTypesToImportedTypes(Element element) { - for (Element enclosedEle : element.getEnclosedElements()) { - if (enclosedEle.getKind().isClass()) { - importedTypes.put( - enclosedEle.getSimpleName().toString(), (TypeElement) enclosedEle); - } + for (TypeMirror interfaceTypeMirror : typeElt.getInterfaces()) { + TypeElement interfaceElement = (TypeElement) ((DeclaredType) interfaceTypeMirror).asElement(); + ExecutableElement result = fakeOverriddenMethod(interfaceElement, methodDecl); + if (result != null) { + return result; + } + } + + return null; + } + + /** + * Returns true if the two signatures (represented as lists of formal parameters) are the same. No + * contravariance is permitted. + * + * @param javacParams parameter list in javac form + * @param javaParserParams parameter list in JavaParser form + * @return true if the two signatures are the same + */ + private boolean sameTypes( + List javacParams, NodeList javaParserParams) { + if (javacParams.size() != javaParserParams.size()) { + return false; + } + for (int i = 0; i < javacParams.size(); i++) { + TypeMirror javacType = javacParams.get(i).asType(); + Parameter javaParserParam = javaParserParams.get(i); + Type javaParserType = javaParserParam.getType(); + if (javacType.getKind() == TypeKind.TYPEVAR) { + // TODO: Hack, need to viewpoint-adapt. + javacType = ((TypeVariable) javacType).getUpperBound(); + } + if (!sameType(javacType, javaParserType)) { + return false; + } + } + return true; + } + + /** + * Returns true if the two types are the same. + * + * @param javacType type in javac form + * @param javaParserType type in JavaParser form + * @return true if the two types are the same + */ + private boolean sameType(TypeMirror javacType, Type javaParserType) { + + switch (javacType.getKind()) { + case BOOLEAN: + return javaParserType.equals(PrimitiveType.booleanType()); + case BYTE: + return javaParserType.equals(PrimitiveType.byteType()); + case CHAR: + return javaParserType.equals(PrimitiveType.charType()); + case DOUBLE: + return javaParserType.equals(PrimitiveType.doubleType()); + case FLOAT: + return javaParserType.equals(PrimitiveType.floatType()); + case INT: + return javaParserType.equals(PrimitiveType.intType()); + case LONG: + return javaParserType.equals(PrimitiveType.longType()); + case SHORT: + return javaParserType.equals(PrimitiveType.shortType()); + + case DECLARED: + case TYPEVAR: + if (!(javaParserType instanceof ClassOrInterfaceType)) { + return false; + } + com.sun.tools.javac.code.Type javacTypeInternal = (com.sun.tools.javac.code.Type) javacType; + ClassOrInterfaceType javaParserClassType = (ClassOrInterfaceType) javaParserType; + + // Use asString() because toString() includes annotations. + String javaParserString = javaParserClassType.asString(); + Element javacElement = javacTypeInternal.asElement(); + // Check both fully-qualified name and simple name. + return javacElement.toString().equals(javaParserString) + || javacElement.getSimpleName().contentEquals(javaParserString); + + case ARRAY: + return javaParserType.isArrayType() + && sameType( + ((ArrayType) javacType).getComponentType(), + javaParserType.asArrayType().getComponentType()); + + default: + throw new BugInCF("unhandled type %s of kind %s", javacType, javacType.getKind()); + } + } + + /** + * Process a fake override: copy its annotations to the fake overrides part of {@code + * #annotationFileAnnos}. + * + * @param element a real element + * @param decl a fake override of the element + * @param fakeLocation where the fake override was defined + */ + private void processFakeOverride( + ExecutableElement element, CallableDeclaration decl, TypeElement fakeLocation) { + // This is a fresh type, which this code may side-effect. + AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(element); + + // Here is a hacky solution that does not use the visitor. It just handles the return type. + // TODO: Walk the type and the declaration, copying annotations from the declaration to the + // element. I think PR #3977 has a visitor that does that, which I should use after it is + // merged. + + // The annotations on the method. These include type annotations on the return type. + NodeList annotations = decl.getAnnotations(); + annotate(methodType.getReturnType(), ((MethodDeclaration) decl).getType(), annotations, decl); + + List> l = + annotationFileAnnos.fakeOverrides.computeIfAbsent(element, __ -> new ArrayList<>(1)); + l.add(IPair.of(fakeLocation.asType(), methodType)); + } + + /** + * Return the annotated type corresponding to {@code type}, or null if none exists. More + * specifically, returns the element of {@code types} whose name matches {@code type}. + * + * @param type the type to search for + * @param types the list of AnnotatedDeclaredTypes to search in + * @param astNode where to report errors + * @return the annotated type in {@code types} corresponding to {@code type}, or null if none + * exists + */ + private @Nullable AnnotatedDeclaredType findAnnotatedType( + ClassOrInterfaceType type, List types, NodeWithRange astNode) { + String typeString = type.getNameAsString(); + for (AnnotatedDeclaredType supertype : types) { + if (supertype.getUnderlyingType().asElement().getSimpleName().contentEquals(typeString)) { + return supertype; + } + } + stubWarnNotFound(astNode, "direct supertype " + typeString + " not found"); + if (debugAnnotationFileParser) { + stubDebug("direct supertypes that were searched:"); + for (AnnotatedDeclaredType supertype : types) { + stubDebug(" %s", supertype); + } + } + return null; + } + + /** + * Looks for the nested type element in the typeElt and returns it if the element has the same + * name as provided class or interface declaration. In case nested element is not found it returns + * null. + * + * @param typeElt an element where nested type element should be looked for + * @param ciDecl class or interface declaration which name should be found among nested elements + * of the typeElt + * @return nested in typeElt element with the name of the class or interface, or null if nested + * element is not found + */ + private @Nullable Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) { + String wantedClassOrInterfaceName = ciDecl.getNameAsString(); + for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { + if (wantedClassOrInterfaceName.equals(typeElement.getSimpleName().toString())) { + return typeElement; + } + } + + stubWarnNotFound( + ciDecl, "class/interface " + wantedClassOrInterfaceName + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + stubDebug(" type declarations of %s:", typeElt); + for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + return null; + } + + /** + * Looks for the nested enum element in the typeElt and returns it if the element has the same + * name as provided enum declaration. In case nested element is not found it returns null. + * + * @param typeElt an element where nested enum element should be looked for + * @param enumDecl enum declaration which name should be found among nested elements of the + * typeElt + * @return nested in typeElt enum element with the name of the provided enum, or null if nested + * element is not found + */ + private @Nullable Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) { + String wantedEnumName = enumDecl.getNameAsString(); + for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { + if (wantedEnumName.equals(typeElement.getSimpleName().toString())) { + return typeElement; + } + } + + stubWarnNotFound(enumDecl, "enum " + wantedEnumName + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + stubDebug(" type declarations of %s:", typeElt); + for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + return null; + } + + /** + * Looks for an enum constant element in the typeElt and returns it if the element has the same + * name as provided. In case enum constant element is not found it returns null. + * + * @param typeElt type element where enum constant element should be looked for + * @param enumConstDecl the declaration of the enum constant + * @param astNode where to report errors + * @return enum constant element in typeElt with the provided name, or null if enum constant + * element is not found + */ + private @Nullable VariableElement findElement( + TypeElement typeElt, EnumConstantDeclaration enumConstDecl, NodeWithRange astNode) { + String enumConstName = enumConstDecl.getNameAsString(); + return findFieldElement(typeElt, enumConstName, astNode); + } + + /** + * Looks for a method element in {@code typeElt} that has the same name and formal parameter types + * as {@code methodDecl}. Returns null, and possibly issues a warning, if no such method element + * is found. + * + * @param typeElt type element where method element should be looked for + * @param methodDecl method declaration with signature that should be found among methods in the + * typeElt + * @param noWarn if true, don't issue a warning if the element is not found + * @return method element in typeElt with the same signature as the provided method declaration or + * null if method element is not found + */ + private @Nullable ExecutableElement findElement( + TypeElement typeElt, MethodDeclaration methodDecl, boolean noWarn) { + if (skipNode(methodDecl)) { + return null; + } + String wantedMethodName = methodDecl.getNameAsString(); + int wantedMethodParams = + (methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size(); + String wantedMethodString = AnnotationFileUtil.toString(methodDecl); + for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (wantedMethodParams == method.getParameters().size() + && wantedMethodName.contentEquals(method.getSimpleName().toString()) + && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { + return method; + } + } + if (!noWarn) { + if (methodDecl.getAccessSpecifier() == AccessSpecifier.NONE) { + // This might be a false positive warning. The stub parser permits a stub file to + // omit the access specifier, but package-private methods aren't in the TypeElement. + stubWarnNotFound( + methodDecl, + "package-private method " + + wantedMethodString + + " not found in type " + + typeElt + + System.lineSeparator() + + "If the method is not package-private," + + " add an access specifier in the stub file" + + " and use -AstubDebug to receive a more useful error message."); + } else { + stubWarnNotFound( + methodDecl, "method " + wantedMethodString + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + stubDebug(" methods of %s:", typeElt); + for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + } + } + return null; + } + + /** + * Looks for a constructor element in the typeElt and returns it if the element has the same + * signature as provided constructor declaration. In case constructor element is not found it + * returns null. + * + * @param typeElt type element where constructor element should be looked for + * @param constructorDecl constructor declaration with signature that should be found among + * constructors in the typeElt + * @return constructor element in typeElt with the same signature as the provided constructor + * declaration or null if constructor element is not found + */ + private @Nullable ExecutableElement findElement( + TypeElement typeElt, ConstructorDeclaration constructorDecl) { + if (skipNode(constructorDecl)) { + return null; + } + int wantedMethodParams = + (constructorDecl.getParameters() == null) ? 0 : constructorDecl.getParameters().size(); + String wantedMethodString = AnnotationFileUtil.toString(constructorDecl); + for (ExecutableElement method : ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { + if (wantedMethodParams == method.getParameters().size() + && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { + return method; + } + } + + stubWarnNotFound( + constructorDecl, "constructor " + wantedMethodString + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + for (ExecutableElement method : ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + return null; + } + + /** + * Returns the element for the given variable. + * + * @param typeElt the type in which the variable is contained + * @param variable the variable whose element to return + * @return the element for the given variable + */ + private VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) { + String fieldName = variable.getNameAsString(); + return findFieldElement(typeElt, fieldName, variable); + } + + /** + * Looks for a field element in the typeElt and returns it if the element has the same name as + * provided. In case field element is not found it returns null. + * + * @param typeElt type element where field element should be looked for + * @param fieldName field name that should be found + * @param astNode where to report errors + * @return field element in typeElt with the provided name or null if field element is not found + */ + private @Nullable VariableElement findFieldElement( + TypeElement typeElt, String fieldName, NodeWithRange astNode) { + for (VariableElement field : ElementUtils.getAllFieldsIn(typeElt, elements)) { + // field.getSimpleName() is a CharSequence, not a String + if (fieldName.equals(field.getSimpleName().toString())) { + return field; + } + } + + stubWarnNotFound(astNode, "field " + fieldName + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", field); + } + } + return null; + } + + /** + * Given a fully-qualified type name, return a TypeElement for it, or null if none exists. Also + * cache in importedTypes. + * + * @param name a fully-qualified type name + * @return a TypeElement for the name, or null + */ + private @Nullable TypeElement getTypeElementOrNull(@FullyQualifiedName String name) { + TypeElement typeElement = elements.getTypeElement(name); + if (typeElement != null) { + importedTypes.put(name, typeElement); + } + // for debugging: warn("getTypeElementOrNull(%s) => %s", name, typeElement); + return typeElement; + } + + /** + * Get the type element for the given fully-qualified type name. If none is found, issue a warning + * and return null. + * + * @param typeName a type name + * @param msg a warning message to issue if the type element for {@code typeName} cannot be found + * @param astNode where to report errors + * @return the type element for the given fully-qualified type name, or null + */ + private @Nullable TypeElement getTypeElement( + @FullyQualifiedName String typeName, String msg, NodeWithRange astNode) { + TypeElement classElement = elements.getTypeElement(typeName); + if (classElement == null) { + stubWarnNotFound(astNode, msg + ": " + typeName); + } + return classElement; + } + + /** + * Returns the element for the given package. + * + * @param packageName the package's name + * @param astNode where to report errors + * @return the element for the given package + */ + private PackageElement findPackage(String packageName, NodeWithRange astNode) { + PackageElement packageElement = elements.getPackageElement(packageName); + if (packageElement == null) { + stubWarnNotFound(astNode, "imported package not found: " + packageName); + } + return packageElement; + } + + /** + * Returns true if one of the annotations is {@link AnnotatedFor} and this checker is in its list + * of checkers. If none of the annotations are {@code AnnotatedFor}, then also return true. + * + * @param annotations a list of JavaParser annotations + * @return true if one of the annotations is {@link AnnotatedFor} and its list of checkers does + * not contain this checker + */ + private boolean isAnnotatedForThisChecker(List annotations) { + if (fileType == AnnotationFileType.JDK_STUB) { + // The JDK stubs have purity annotations that should be read for all checkers. + // TODO: Parse the JDK stubs, but only save the declaration annotations. + return true; + } + for (AnnotationExpr ae : annotations) { + if (ae.getNameAsString().equals("AnnotatedFor") + || ae.getNameAsString().equals("org.checkerframework.framework.qual.AnnotatedFor")) { + AnnotationMirror af = getAnnotation(ae, allAnnotations); + if (atypeFactory.areSameByClass(af, AnnotatedFor.class)) { + return atypeFactory.doesAnnotatedForApplyToThisChecker(af); + } + } + } + return true; + } + + /** + * Convert {@code annotation} into an AnnotationMirror. Returns null if the annotation isn't + * supported by the checker or if some error occurred while converting it. + * + * @param annotation syntax tree for an annotation + * @param allAnnotations map from simple name to annotation definition; side-effected by this + * method + * @return the AnnotationMirror for the annotation, or null if it cannot be built + */ + private @Nullable AnnotationMirror getAnnotation( + AnnotationExpr annotation, Map allAnnotations) { + + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); + TypeElement annoTypeElt = allAnnotations.get(annoNameFq); + if (annoTypeElt == null) { + // If the annotation was not imported, then #getImportedAnnotations did not add it to + // the allAnnotations field. This code adds the annotation when it is encountered (i.e. + // here). + // Note that this does not call AnnotationFileParser#getTypeElement to avoid a spurious + // diagnostic if the annotation is actually unknown. + annoTypeElt = elements.getTypeElement(annoNameFq); + if (annoTypeElt == null) { + // Not a supported annotation -> ignore + return null; + } + putAllNew(allAnnotations, createNameToAnnotationMap(Collections.singletonList(annoTypeElt))); + } + @SuppressWarnings("signature") // not anonymous, so name is not empty + @CanonicalName String annoName = annoTypeElt.getQualifiedName().toString(); + + if (annotation instanceof MarkerAnnotationExpr) { + return AnnotationBuilder.fromName(elements, annoName); + } else if (annotation instanceof NormalAnnotationExpr) { + NormalAnnotationExpr nrmanno = (NormalAnnotationExpr) annotation; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); + List pairs = nrmanno.getPairs(); + if (pairs != null) { + for (MemberValuePair mvp : pairs) { + String member = mvp.getNameAsString(); + Expression exp = mvp.getValue(); + try { + builderAddElement(builder, member, exp); + } catch (AnnotationFileParserException e) { + warn( + exp, + "for annotation %s, could not add %s=%s because %s", + annotation, + member, + exp, + e.getMessage()); + return null; + } + } + } + return builder.build(); + } else if (annotation instanceof SingleMemberAnnotationExpr) { + SingleMemberAnnotationExpr sglanno = (SingleMemberAnnotationExpr) annotation; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); + Expression valExpr = sglanno.getMemberValue(); + try { + builderAddElement(builder, "value", valExpr); + } catch (AnnotationFileParserException e) { + warn( + valExpr, + "for annotation %s, could not add value=%s because %s", + annotation, + valExpr, + e.getMessage()); + return null; + } + return builder.build(); + } else { + throw new BugInCF("AnnotationFileParser: unknown annotation type: " + annotation); + } + } + + /** + * Returns the value of {@code expr}. + * + * @param name the name of an annotation element/argument, used for diagnostic messages + * @param expr the expression to determine the value of + * @param valueKind the type of the result + * @return the value of {@code expr} + * @throws AnnotationFileParserException if a problem occurred getting the value + */ + private Object getValueOfExpressionInAnnotation(String name, Expression expr, TypeKind valueKind) + throws AnnotationFileParserException { + if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) { + VariableElement elem; + if (expr instanceof NameExpr) { + elem = findVariableElement((NameExpr) expr); + } else { + elem = findVariableElement((FieldAccessExpr) expr); + } + if (elem == null) { + throw new AnnotationFileParserException(String.format("variable %s not found", expr)); + } + Object value = elem.getConstantValue() != null ? elem.getConstantValue() : elem; + if (value instanceof Number) { + return convert((Number) value, valueKind); + } else { + return value; + } + } else if (expr instanceof StringLiteralExpr) { + return ((StringLiteralExpr) expr).asString(); + } else if (expr instanceof BooleanLiteralExpr) { + return ((BooleanLiteralExpr) expr).getValue(); + } else if (expr instanceof CharLiteralExpr) { + return convert((int) ((CharLiteralExpr) expr).asChar(), valueKind); + } else if (expr instanceof DoubleLiteralExpr) { + // No conversion needed if the expression is a double, the annotation value must be a + // double, too. + return ((DoubleLiteralExpr) expr).asDouble(); + } else if (expr instanceof IntegerLiteralExpr) { + return convert(((IntegerLiteralExpr) expr).asNumber(), valueKind); + } else if (expr instanceof LongLiteralExpr) { + return convert(((LongLiteralExpr) expr).asNumber(), valueKind); + } else if (expr instanceof UnaryExpr) { + switch (expr.toString()) { + // Special-case the minimum values. Separately parsing a "-" and a value + // doesn't correctly handle the minimum values, because the absolute value of + // the smallest member of an integral type is larger than the largest value. + case "-9223372036854775808L": + case "-9223372036854775808l": + return convert(Long.MIN_VALUE, valueKind, false); + case "-2147483648": + return convert(Integer.MIN_VALUE, valueKind, false); + default: + if (((UnaryExpr) expr).getOperator() == UnaryExpr.Operator.MINUS) { + Object value = + getValueOfExpressionInAnnotation( + name, ((UnaryExpr) expr).getExpression(), valueKind); + if (value instanceof Number) { + return convert((Number) value, valueKind, true); + } + } + throw new AnnotationFileParserException( + "unexpected Unary annotation expression: " + expr); + } + } else if (expr instanceof ClassExpr) { + ClassExpr classExpr = (ClassExpr) expr; + @SuppressWarnings("signature") // Type.toString(): @FullyQualifiedName + @FullyQualifiedName String className = classExpr.getType().toString(); + if (importedTypes.containsKey(className)) { + return importedTypes.get(className).asType(); + } + TypeElement typeElement = findTypeOfName(className); + if (typeElement == null) { + throw new AnnotationFileParserException("unknown class name " + className); + } + + return typeElement.asType(); + } else if (expr instanceof NullLiteralExpr) { + throw new AnnotationFileParserException("illegal annotation value null, for " + name); + } else { + throw new AnnotationFileParserException("unexpected annotation expression: " + expr); + } + } + + /** + * Returns the TypeElement with the name {@code name}, if one exists. Otherwise, checks the class + * and package of {@code typeBeingParsed} for a class named {@code name}. + * + * @param name classname (simple, or Outer.Inner, or fully-qualified) + * @return the TypeElement for {@code name}, or null if not found + */ + @SuppressWarnings("signature:argument.type.incompatible") // string concatenation + private @Nullable TypeElement findTypeOfName(@FullyQualifiedName String name) { + String packageName = typeBeingParsed.packageName; + String packagePrefix = (packageName == null) ? "" : packageName + "."; + + // warn("findTypeOfName(%s), typeBeingParsed %s %s", name, packageName, enclosingClass); + + // As soon as typeElement is set to a non-null value, it will be returned. + TypeElement typeElement = getTypeElementOrNull(name); + if (typeElement == null && packageName != null) { + typeElement = getTypeElementOrNull(packagePrefix + name); + } + String enclosingClass = typeBeingParsed.className; + while (typeElement == null && enclosingClass != null) { + typeElement = getTypeElementOrNull(packagePrefix + enclosingClass + "." + name); + int lastDot = enclosingClass.lastIndexOf('.'); + if (lastDot == -1) { + break; + } else { + enclosingClass = enclosingClass.substring(0, lastDot); + } + } + if (typeElement == null && !"java.lang".equals(packageName)) { + typeElement = getTypeElementOrNull("java.lang." + name); + } + return typeElement; + } + + /** + * Converts {@code number} to {@code expectedKind}. + * + *


+   *   @interface Anno { long value(); }
+   *   @Anno(1)
+   * 
+ * + * To properly build @Anno, the IntegerLiteralExpr "1" must be converted from an int to a long. + */ + private Object convert(Number number, TypeKind expectedKind) { + return convert(number, expectedKind, false); + } + + /** + * Converts {@code number} to {@code expectedKind}. The value converted is multiplied by -1 if + * {@code negate} is true + * + * @param number a Number value to be converted + * @param expectedKind one of type {byte, short, int, long, char, float, double} + * @param negate whether to negate the value of the Number Object while converting + * @return the converted Object + */ + private Object convert(Number number, TypeKind expectedKind, boolean negate) { + byte scalefactor = (byte) (negate ? -1 : 1); + switch (expectedKind) { + case BYTE: + return number.byteValue() * scalefactor; + case SHORT: + return number.shortValue() * scalefactor; + case INT: + return number.intValue() * scalefactor; + case LONG: + return number.longValue() * scalefactor; + case CHAR: + // It's not possible for `number` to be negative when `expectedkind` is a CHAR, and + // casting a negative value to char is illegal. + if (negate) { + throw new BugInCF( + "convert(%s, %s, %s): can't negate a char", number, expectedKind, negate); + } + return (char) number.intValue(); + case FLOAT: + return number.floatValue() * scalefactor; + case DOUBLE: + return number.doubleValue() * scalefactor; + default: + throw new BugInCF("unexpected expectedKind: " + expectedKind); + } + } + + /** + * Adds an annotation element (argument) to {@code builder}. The element is a Java expression. + * + * @param builder the builder to side-effect + * @param name the element name + * @param expr the element value + * @throws AnnotationFileParserException if the expression cannot be parsed and added to {@code + * builder} + */ + private void builderAddElement(AnnotationBuilder builder, String name, Expression expr) + throws AnnotationFileParserException { + ExecutableElement var = builder.findElement(name); + TypeMirror declaredType = var.getReturnType(); + TypeKind valueKind; + if (declaredType.getKind() == TypeKind.ARRAY) { + valueKind = ((ArrayType) declaredType).getComponentType().getKind(); + } else { + valueKind = declaredType.getKind(); + } + if (expr instanceof ArrayInitializerExpr) { + if (declaredType.getKind() != TypeKind.ARRAY) { + throw new AnnotationFileParserException( + "unhandled annotation attribute type: " + expr + " and declaredType: " + declaredType); + } + + List arrayExpressions = ((ArrayInitializerExpr) expr).getValues(); + Object[] values = new Object[arrayExpressions.size()]; + + for (int i = 0; i < arrayExpressions.size(); ++i) { + Expression eltExpr = arrayExpressions.get(i); + values[i] = getValueOfExpressionInAnnotation(name, eltExpr, valueKind); + } + builder.setValue(name, values); + } else { + Object value = getValueOfExpressionInAnnotation(name, expr, valueKind); + if (declaredType.getKind() == TypeKind.ARRAY) { + Object[] valueArray = {value}; + builder.setValue(name, valueArray); + } else { + builderSetValue(builder, name, value); + } + } + } + + /** + * Cast to non-array values so that correct the correct AnnotationBuilder#setValue method is + * called. (Different types of values are handled differently.) + * + * @param builder the builder to side-effect + * @param name the element name + * @param value the element value + */ + private void builderSetValue(AnnotationBuilder builder, String name, Object value) { + if (value instanceof Boolean) { + builder.setValue(name, (Boolean) value); + } else if (value instanceof Character) { + builder.setValue(name, (Character) value); + } else if (value instanceof Class) { + builder.setValue(name, (Class) value); + } else if (value instanceof Double) { + builder.setValue(name, (Double) value); + } else if (value instanceof Enum) { + builder.setValue(name, (Enum) value); + } else if (value instanceof Float) { + builder.setValue(name, (Float) value); + } else if (value instanceof Integer) { + builder.setValue(name, (Integer) value); + } else if (value instanceof Long) { + builder.setValue(name, (Long) value); + } else if (value instanceof Short) { + builder.setValue(name, (Short) value); + } else if (value instanceof String) { + builder.setValue(name, (String) value); + } else if (value instanceof TypeMirror) { + builder.setValue(name, (TypeMirror) value); + } else if (value instanceof VariableElement) { + builder.setValue(name, (VariableElement) value); + } else { + throw new BugInCF("unexpected builder value: %s", value); + } + } + + /** + * Mapping of a name access expression that has already been encountered to the resolved variable + * element. + */ + private final Map findVariableElementNameCache = new HashMap<>(); + + /** + * Returns the element for the given variable. + * + * @param nexpr the variable name + * @return the element for the given variable + */ + private @Nullable VariableElement findVariableElement(NameExpr nexpr) { + if (findVariableElementNameCache.containsKey(nexpr)) { + return findVariableElementNameCache.get(nexpr); + } + + VariableElement res = null; + boolean importFound = false; + for (String imp : importedConstants) { + IPair<@FullyQualifiedName String, String> partitionedName = + AnnotationFileUtil.partitionQualifiedName(imp); + String typeName = partitionedName.first; + String fieldName = partitionedName.second; + if (fieldName.equals(nexpr.getNameAsString())) { + TypeElement enclType = + getTypeElement( + typeName, + String.format("enclosing type of static import %s not found", fieldName), + nexpr); + + if (enclType == null) { + return null; + } else { + importFound = true; + res = findFieldElement(enclType, fieldName, nexpr); + break; + } + } + } + + if (res == null) { + if (importFound) { + // TODO: Is this warning redundant? Maybe imported but invalid types or fields will + // have warnings from above. + stubWarnNotFound(nexpr, nexpr.getName() + " was imported but not found"); + } else { + stubWarnNotFound(nexpr, "static field " + nexpr.getName() + " is not imported"); + } + } + + findVariableElementNameCache.put(nexpr, res); + return res; + } + + /** + * Mapping of a field access expression that has already been encountered to the resolved variable + * element. + */ + private final Map findVariableElementFieldCache = + new HashMap<>(); + + /** + * Returns the VariableElement for the given field access. + * + * @param faexpr a field access expression + * @return the VariableElement for the given field access + */ + @SuppressWarnings("signature:argument.type.incompatible") // string manipulation + private @Nullable VariableElement findVariableElement(FieldAccessExpr faexpr) { + if (findVariableElementFieldCache.containsKey(faexpr)) { + return findVariableElementFieldCache.get(faexpr); + } + TypeElement rcvElt = elements.getTypeElement(faexpr.getScope().toString()); + if (rcvElt == null) { + // Search importedConstants for full annotation name. + for (String imp : importedConstants) { + // TODO: should this use AnnotationFileUtil.partitionQualifiedName? + String[] importDelimited = imp.split("\\."); + if (importDelimited[importDelimited.length - 1].equals(faexpr.getScope().toString())) { + StringBuilder fullAnnotation = new StringBuilder(); + for (int i = 0; i < importDelimited.length - 1; i++) { + fullAnnotation.append(importDelimited[i]); + fullAnnotation.append('.'); + } + fullAnnotation.append(faexpr.getScope().toString()); + rcvElt = elements.getTypeElement(fullAnnotation); + break; + } + } + + if (rcvElt == null) { + stubWarnNotFound(faexpr, "type " + faexpr.getScope() + " not found"); + return null; + } + } + + VariableElement res = findFieldElement(rcvElt, faexpr.getNameAsString(), faexpr); + findVariableElementFieldCache.put(faexpr, res); + return res; + } + + /////////////////////////////////////////////////////////////////////////// + /// Map utilities + /// + + /** + * Just like Map.put, but does not override any existing value in the map. + * + * @param the key type + * @param the value type + * @param m a map + * @param key a key + * @param value the value to associate with the key, if the key isn't already in the map + */ + public static void putIfAbsent(Map m, K key, V value) { + if (key == null) { + throw new BugInCF("AnnotationFileParser: key is null for value " + value); + } + if (!m.containsKey(key)) { + m.put(key, value); + } + } + + /** + * If the key is already in the {@code annotationFileAnnos.declAnnos} map, then add the annos to + * the map value. Otherwise put the key and the annos in the map. + * + * @param key a name (actually declaration element string) + * @param annos the set of declaration annotations on it, as written in the annotation file; is + * not modified + */ + private void putOrAddToDeclAnnos(String key, AnnotationMirrorSet annos) { + AnnotationMirrorSet stored = annotationFileAnnos.declAnnos.get(key); + if (stored == null) { + annotationFileAnnos.declAnnos.put(key, new AnnotationMirrorSet(annos)); + } else { + // TODO: Currently, we assume there can be at most one annotation of the same name + // in both `stored` and `annos`. Maybe we should consider the situation of multiple + // entries having the same name. + AnnotationMirrorSet annotationsToAdd = annos; + if (fileType == AnnotationFileType.JDK_STUB) { + // JDK annotations should not replace any annotation of the same type. + annotationsToAdd = + annos.stream() + .filter(am -> !AnnotationUtils.containsSameByName(stored, am)) + .collect(Collectors.toCollection(AnnotationMirrorSet::new)); + } else { + // Annotations that are not from the annotated JDK may replace existing + // annotations of the same type. + stored.removeIf(am -> AnnotationUtils.containsSameByName(annos, am)); + } + stored.addAll(annotationsToAdd); + } + } + + /** + * Just like Map.put, but modifies an existing annotated type for the given key in {@code m}. If + * {@code m} already has an annotated type for {@code key}, each annotation in {@code newType} + * will replace annotations from the same hierarchy at the same location in the existing annotated + * type. Annotations in other hierarchies will be preserved. + * + * @param m the map to put the new type into + * @param key the key for the map + * @param newType the new type for the key + */ + private void putMerge( + Map m, Element key, AnnotatedTypeMirror newType) { + if (key == null) { + throw new BugInCF("AnnotationFileParser: key is null"); + } + if (m.containsKey(key)) { + AnnotatedTypeMirror existingType = m.get(key); + // If the newType is from a JDK stub file, then keep the existing type. This + // way user-supplied stub files override JDK stub files. + // This works because the JDK is always parsed last, on demand, after all other stub + // files. + if (fileType != AnnotationFileType.JDK_STUB) { + atypeFactory.replaceAnnotations(newType, existingType); + } + // existingType is already in the map, so no need to put into m. + } else { + m.put(key, newType); + } + } + + /** + * Just like Map.putAll, but modifies existing values using {@link #putIfAbsent(Map, Object, + * Object)}. + * + * @param m the destination map + * @param m2 the source map + * @param the key type for the maps + * @param the value type for the maps + */ + public static void putAllNew(Map m, Map m2) { + for (Map.Entry e2 : m2.entrySet()) { + putIfAbsent(m, e2.getKey(), e2.getValue()); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Issue warnings + /// + + /** The warnings that have been issued so far. */ + private static final Set warnings = new HashSet<>(); + + /** + * Issues the given warning about missing elements, only if it has not been previously issued and + * the -AstubWarnIfNotFound command-line argument was passed. + * + * @param astNode where to report errors + * @param warning warning to print + */ + private void stubWarnNotFound(NodeWithRange astNode, String warning) { + stubWarnNotFound(astNode, warning, warnIfNotFound); + } + + /** + * Issues the given warning about missing elements, only if it has not been previously issued and + * the {@code warnIfNotFound} formal parameter is true. + * + * @param astNode where to report errors + * @param warning warning to print + * @param warnIfNotFound whether to print warnings about types/members that were not found + */ + private void stubWarnNotFound(NodeWithRange astNode, String warning, boolean warnIfNotFound) { + if (warnIfNotFound || debugAnnotationFileParser) { + warn(astNode, warning); + } + } + + /** + * Issues the given warning about overwriting bytecode, only if it has not been previously issued + * and the -AstubWarnIfOverwritesBytecode command-line argument was passed. + * + * @param astNode where to report errors + * @param message the warning message to print + */ + @SuppressWarnings("UnusedMethod") // not currently used + private void stubWarnOverwritesBytecode(NodeWithRange astNode, String message) { + if (warnIfStubOverwritesBytecode || debugAnnotationFileParser) { + warn(astNode, message); + } + } + + /** + * Issues a warning, only if it has not been previously issued. + * + * @param astNode where to report errors + * @param warning a format string + * @param args the arguments for {@code warning} + */ + @FormatMethod + private void warn(@Nullable NodeWithRange astNode, String warning, Object... args) { + if (!fileType.isBuiltIn()) { + warn(astNode, String.format(warning, args)); + } + } + + /** + * Issues a warning, only if it has not been previously issued. + * + * @param astNode where to report errors + * @param warning a warning message + */ + private void warn(@Nullable NodeWithRange astNode, String warning) { + if (fileType != AnnotationFileType.JDK_STUB) { + if (warnings.add(warning)) { + processingEnv + .getMessager() + .printMessage(stubWarnDiagnosticKind, fileAndLine(astNode) + warning); + } + } + } + + /** + * If {@code warning} hasn't been printed yet, and {@link #debugAnnotationFileParser} is true, + * prints the given warning as a diagnostic message. + * + * @param fmt format string + * @param args arguments to the format string + */ + @FormatMethod + private void stubDebug(String fmt, Object... args) { + if (debugAnnotationFileParser) { + String warning = String.format(fmt, args); + if (warnings.add(warning)) { + System.out.flush(); + SystemPlume.sleep(1); + processingEnv + .getMessager() + .printMessage(javax.tools.Diagnostic.Kind.NOTE, "AnnotationFileParser: " + warning); + System.out.flush(); + SystemPlume.sleep(1); + } + } + } + + /** + * If {@code warning} hasn't been printed yet, prints the given warning as a diagnostic message. + * Ignores {@code debugAnnotationFileParser}. + * + * @param processingEnv the processing environment + * @param fmt format string + * @param args arguments to the format string + */ + @FormatMethod + /*package-private*/ static void stubDebugStatic( + ProcessingEnvironment processingEnv, String fmt, Object... args) { + String warning = String.format(fmt, args); + if (warnings.add(warning)) { + System.out.flush(); + SystemPlume.sleep(1); + processingEnv + .getMessager() + .printMessage(javax.tools.Diagnostic.Kind.NOTE, "AnnotationFileParser: " + warning); + System.out.flush(); + SystemPlume.sleep(1); + } + } + + /** + * After obtaining the JavaParser AST for an ajava file and the javac tree for its corresponding + * Java file, walks both in tandem. For each program construct with annotations, stores the + * annotations from the ajava file in {@link #annotationFileAnnos} by calling the process method + * corresponding to that construct, such as {@link #processCallableDeclaration} or {@link + * #processField}. + */ + private class AjavaAnnotationCollectorVisitor extends DefaultJointVisitor { + + /** Default constructor. */ + private AjavaAnnotationCollectorVisitor() {} + + // This method overrides super.visitCompilationUnit() to prevent parsing import + // statements. Requiring imports in both ajava file and the source file to be + // exactly same is error-prone and unnecessary. + @Override + public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { + CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); + processCompilationUnit(javacTree, node); + visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); + visitLists(javacTree.getTypeDecls(), node.getTypes()); + return null; + } + + @Override + public Void visitClass(ClassTree javacTree, Node javaParserNode) { + List typeDeclTypeParameters = null; + boolean shouldProcessTypeDecl = + javaParserNode instanceof TypeDeclaration + && !(javaParserNode instanceof AnnotationDeclaration); + Optional typeDeclName = Optional.empty(); + boolean callListener = false; + + if (shouldProcessTypeDecl) { + TypeDeclaration typeDecl = (TypeDeclaration) javaParserNode; + typeDeclName = typeDecl.getFullyQualifiedName(); + callListener = typeDeclName.isPresent() && typeDecl.isTopLevelType(); + } + + if (callListener) { + @SuppressWarnings("optional:method.invocation.invalid") // from callListener + String typeDeclNameString = typeDeclName.get(); + fileElementTypes.preProcessTopLevelType(typeDeclNameString); + } + try { + if (shouldProcessTypeDecl) { + typeDeclTypeParameters = + processTypeDecl((TypeDeclaration) javaParserNode, null, javacTree); + } + super.visitClass(javacTree, javaParserNode); + } finally { + if (typeDeclTypeParameters != null) { + typeParameters.removeAll(typeDeclTypeParameters); } - } - - /** - * The main entry point. Parse a stub file and side-effects the {@code annotationFileAnnos} - * argument. - * - * @param filename name of stub file, used only for diagnostic messages - * @param inputStream of stub file to parse - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param annotationFileAnnos annotations from the annotation file; side-effected by this method - * @param fileType the annotation file type and source - * @param fileElementTypes the manager that controls the stub file parsing process - */ - public static void parseStubFile( - String filename, - InputStream inputStream, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileAnnotations annotationFileAnnos, - AnnotationFileType fileType, - AnnotationFileElementTypes fileElementTypes) { - AnnotationFileParser afp = - new AnnotationFileParser( - filename, atypeFactory, processingEnv, fileType, fileElementTypes); - try { - afp.parseStubUnit(inputStream); - afp.process(annotationFileAnnos); - } catch (ParseProblemException e) { - for (Problem p : e.getProblems()) { - afp.warn(null, p.getVerboseMessage()); - } - } catch (Throwable t) { - afp.warn(null, "Parse problem: " + t); + if (callListener) { + @SuppressWarnings("optional:method.invocation.invalid") // from callListener + String typeDeclNameString = typeDeclName.get(); + fileElementTypes.postProcessTopLevelType(typeDeclNameString); } + } + + return null; } - /** - * The main entry point when parsing an ajava file. Parses an ajava file and side-effects the - * last two arguments. - * - * @param filename name of ajava file, used only for diagnostic messages - * @param inputStream of ajava file to parse - * @param root javac tree for the file to be parsed - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param ajavaAnnos annotations from the ajava file; side-effected by this method - * @param fileElementTypes the manager that controls the stub file parsing process - */ - public static void parseAjavaFile( - String filename, - InputStream inputStream, - CompilationUnitTree root, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileAnnotations ajavaAnnos, - AnnotationFileElementTypes fileElementTypes) { - AnnotationFileParser afp = - new AnnotationFileParser( - filename, - atypeFactory, - processingEnv, - AnnotationFileType.AJAVA, - fileElementTypes); - try { - afp.parseStubUnit(inputStream); - JavaParserUtil.concatenateAddedStringLiterals(afp.stubUnit); - afp.setRoot(root); - afp.process(ajavaAnnos); - } catch (ParseProblemException e) { - for (Problem p : e.getProblems()) { - afp.warn(null, filename + ": " + p.getVerboseMessage()); - } - } catch (Throwable t) { - afp.warn(null, "Parse problem: " + t); + @Override + public Void visitVariable(VariableTree javacTree, Node javaParserNode) { + VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); + if (elt != null) { + if (elt.getKind() == ElementKind.FIELD) { + VariableDeclarator varDecl = (VariableDeclarator) javaParserNode; + processField((FieldDeclaration) varDecl.getParentNode().get(), elt); } - } - /** - * Parse a stub file that is a part of the annotated JDK and side-effects the {@code stubAnnos} - * argument. - * - * @param filename name of stub file, used only for diagnostic messages - * @param inputStream of stub file to parse - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param stubAnnos annotations from the stub file; side-effected by this method - * @param fileElementTypes the manager that controls the stub file parsing process - */ - public static void parseJdkFileAsStub( - String filename, - InputStream inputStream, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileAnnotations stubAnnos, - AnnotationFileElementTypes fileElementTypes) { - Map options = processingEnv.getOptions(); - boolean debugAnnotationFileParser = options.containsKey("stubDebug"); - if (debugAnnotationFileParser) { - stubDebugStatic( - processingEnv, - "parseJdkFileAsStub(%s, _, %s, _, _)%n", - filename, - atypeFactory.getClass().getSimpleName()); + if (elt.getKind() == ElementKind.ENUM_CONSTANT) { + processEnumConstant((EnumConstantDeclaration) javaParserNode, elt); } + } - parseStubFile( - filename, - inputStream, - atypeFactory, - processingEnv, - stubAnnos, - AnnotationFileType.JDK_STUB, - fileElementTypes); + super.visitVariable(javacTree, javaParserNode); + return null; } - /** - * Delegate to the Stub Parser to parse the annotation file to an AST, and save it in {@link - * #stubUnit}. Also sets {@link #allAnnotations}. Does not copy annotations out of {@link - * #stubUnit}; that is done by the {@code process*} methods. - * - *

Subsequently, all work uses the AST. - * - * @param inputStream the stream from which to read an annotation file - */ - private void parseStubUnit(InputStream inputStream) { - stubDebug( - "started parsing annotation file %s for %s", - filename, atypeFactory.getClass().getSimpleName()); - stubUnit = JavaParserUtil.parseStubUnit(inputStream); - - // getImportedAnnotations() also modifies importedConstants and importedTypes. This should - // be refactored to be nicer. - allAnnotations = getImportedAnnotations(); - if (allAnnotations.isEmpty() - && fileType.isStub() - && fileType != AnnotationFileType.AJAVA_AS_STUB) { - // Issue a warning if the stub file contains no import statements. The warning is - // incorrect if the stub file contains fully-qualified annotations. - stubWarnNotFound( - null, - String.format( - "No supported annotations found! Does stub file %s import them?", - filename)); - } - // Annotations in java.lang might be used without an import statement, so add them in case. - allAnnotations.putAll(annosInPackage(findPackage("java.lang", null))); + @Override + public Void visitMethod(MethodTree javacTree, Node javaParserNode) { + List variablesToClear = null; + Element elt = TreeUtils.elementFromDeclaration(javacTree); + if (javaParserNode instanceof CallableDeclaration) { + variablesToClear = + processCallableDeclaration( + (CallableDeclaration) javaParserNode, (ExecutableElement) elt); + } - if (debugAnnotationFileParser) { - stubDebug( - "finished parsing annotation file %s for %s", - filename, atypeFactory.getClass().getSimpleName()); - } - } + super.visitMethod(javacTree, javaParserNode); + if (variablesToClear != null) { + typeParameters.removeAll(variablesToClear); + } - /** - * Process {@link #stubUnit}, which is the AST produced by {@link #parseStubUnit}. Processing - * means copying annotations from Stub Parser data structures to {@code #annotationFileAnnos}. - * - * @param annotationFileAnnos annotations from the file; side-effected by this method - */ - private void process(AnnotationFileAnnotations annotationFileAnnos) { - this.annotationFileAnnos = annotationFileAnnos; - processStubUnit(this.stubUnit); - this.annotationFileAnnos = null; + return null; } + } - /** - * Process the given StubUnit: copy its annotations to {@code #annotationFileAnnos}. - * - * @param su the StubUnit to process - */ - private void processStubUnit(StubUnit su) { - for (CompilationUnit cu : su.getCompilationUnits()) { - processCompilationUnit(cu); - } - } + /** + * Return the prefix for a warning line: A file name, line number, and column number. + * + * @param astNode where to report errors + * @return file name, line number, and column number + */ + private String fileAndLine(NodeWithRange astNode) { + String filenamePrinted = + (processingEnv.getOptions().containsKey("nomsgtext") + ? new File(filename).getName() + : filename); - /** - * Process the given CompilationUnit: copy its annotations to {@code #annotationFileAnnos}. - * - * @param cu the CompilationUnit to process - */ - private void processCompilationUnit(CompilationUnit cu) { - - if (cu.getPackageDeclaration().isPresent()) { - PackageDeclaration pDecl = cu.getPackageDeclaration().get(); - packageAnnos = pDecl.getAnnotations(); - if (debugAnnotationFileParser - || (!warnIfNotFoundIgnoresClasses - && !hasNoAnnotationFileParserWarning(packageAnnos))) { - String packageName = pDecl.getName().toString(); - if (elements.getPackageElement(packageName) == null) { - stubWarnNotFound(pDecl, "package not found: " + packageName); - } - } - processPackage(pDecl); - } else { - packageAnnos = null; - typeBeingParsed = new FqName(null, null); - } + Optional begin = astNode == null ? Optional.empty() : astNode.getBegin(); + String lineAndColumn = (begin.isPresent() ? begin.get() + ":" : ""); + return filenamePrinted + ":" + lineAndColumn + " "; + } - if (fileType.isStub()) { - if (cu.getTypes() != null) { - for (TypeDeclaration typeDeclaration : cu.getTypes()) { - Optional typeDeclName = typeDeclaration.getFullyQualifiedName(); - - typeDeclName.ifPresent(fileElementTypes::preProcessTopLevelType); - try { - // Not processing an ajava file, so ignore the return value. - processTypeDecl(typeDeclaration, null, null); - } finally { - typeDeclName.ifPresent(fileElementTypes::postProcessTopLevelType); - } - } - } - } else { - root.accept(new AjavaAnnotationCollectorVisitor(), cu); - } + /** An exception indicating a problem while parsing an annotation file. */ + public static class AnnotationFileParserException extends Exception { - packageAnnos = null; - } + private static final long serialVersionUID = 20201222; /** - * Process the given package declaration: copy its annotations to {@code #annotationFileAnnos}. + * Create a new AnnotationFileParserException. * - * @param packDecl the package declaration to process + * @param message a description of the problem */ - private void processPackage(PackageDeclaration packDecl) { - assert (packDecl != null); - if (!isAnnotatedForThisChecker(packDecl.getAnnotations())) { - return; - } - String packageName = packDecl.getNameAsString(); - typeBeingParsed = new FqName(packageName, null); - Element elem = elements.getPackageElement(packageName); - // If the element lookup fails (that is, elem == null), it's because we have an annotation - // for a package that isn't on the classpath, which is fine. - if (elem != null) { - recordDeclAnnotation(elem, packDecl.getAnnotations(), packDecl); - } - // TODO: Handle atypes??? + AnnotationFileParserException(String message) { + super(message); } + } - /** - * Returns true if the given program construct need not be read: it is private and one of the - * following is true: - * - *

    - *
  • It is in the annotated JDK. Private constructs can't be referenced outside of the JDK - * and might refer to types that are not accessible. - *
  • It is not an ajava file and {@code -AmergeStubsWithSource} was not supplied. As - * described at https://eisop.github.io/cf/manual/#stub-multiple-specifications, source - * files take precedence over stub files unless {@code -AmergeStubsWithSource} is - * supplied. As described at https://eisop.github.io/cf/manual/#ajava-using, source files - * do not take precedence over ajava files (when reading an ajava file, it is as if {@code - * -AmergeStubsWithSource} were supplied). - *
- * - * @param node a declaration - * @return true if the given program construct is in the annotated JDK and is private - */ - private boolean skipNode(NodeWithAccessModifiers node) { - // Must include everything with no access modifier, because stub files are allowed to omit - // the access modifier. Also, interface methods have no access modifier, but they are still - // public. - // Must include protected JDK methods. For example, Object.clone is protected, but it - // contains annotations that apply to calls like `super.clone()` and `myArray.clone()`. - return (fileType == AnnotationFileType.BUILTIN_STUB - || (fileType.isStub() - && fileType != AnnotationFileType.AJAVA_AS_STUB - && !mergeStubsWithSource)) - && node.getModifiers().contains(Modifier.privateModifier()); - } + /////////////////////////////////////////////////////////////////////////// + /// Parse state + /// + + /** Represents a class: its package name and name (including outer class names if any). */ + private static class FqName { + /** Name of the package being parsed, or null. */ + public final @Nullable String packageName; /** - * Returns the string representation of {@code n}, one one line, truncated to {@code length} - * characters. - * - * @param n a JavaParser node - * @param length the maximum length of the string representation - * @return the truncated string representation of {@code n} + * Name of the type being parsed. Includes outer class names if any. Null if the parser has + * parsed a package declaration but has not yet gotten to a type declaration. */ - private String javaParserNodeToStringTruncated(Node n, int length) { - String oneLine = - n.toString() - .replace("\t", " ") - .replace("\n", " ") - .replace("\r", " ") - .replaceAll(" +", " "); - if (oneLine.length() <= length) { - return oneLine; - } else { - return oneLine.substring(0, length - 3) + "..."; - } - } + public final @Nullable String className; /** - * Process a type declaration: copy its annotations to {@code #annotationFileAnnos}. - * - *

This method stores the declaration's type parameters in {@link #typeParameters}. When - * processing an ajava file, where traversal is handled externaly by a {@link - * org.checkerframework.framework.ajava.JointJavacJavaParserVisitor}, these type variables must - * be removed after processing the type's members. Otherwise, this method removes them. + * Create a new FqName, which represents a class. * - * @param typeDecl the type declaration to process - * @param outerTypeName the name of the containing class, when processing a nested class; - * otherwise null - * @param classTree the tree corresponding to typeDecl if processing an ajava file, null - * otherwise - * @return a list of types variables for {@code typeDecl}. Only non-null if processing an ajava - * file, in which case the contents should be removed from {@link #typeParameters} after - * processing the type declaration's members + * @param packageName name of the package, or null + * @param className unqualified name of the type, including outer class names if any. May be + * null. */ - private @Nullable List processTypeDecl( - TypeDeclaration typeDecl, - @Nullable String outerTypeName, - @Nullable ClassTree classTree) { - assert typeBeingParsed != null; - if (skipNode(typeDecl)) { - return null; - } - String innerName; - @FullyQualifiedName String fqTypeName; - TypeElement typeElt; - if (classTree != null) { - typeElt = TreeUtils.elementFromDeclaration(classTree); - innerName = typeElt.getQualifiedName().toString(); - typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); - fqTypeName = typeBeingParsed.toString(); - } else { - String packagePrefix = outerTypeName == null ? "" : outerTypeName + "."; - innerName = packagePrefix + typeDecl.getNameAsString(); - typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); - fqTypeName = typeBeingParsed.toString(); - typeElt = elements.getTypeElement(fqTypeName); - } - - if (!isAnnotatedForThisChecker(typeDecl.getAnnotations())) { - return null; - } - if (typeElt == null) { - if (debugAnnotationFileParser - || (!warnIfNotFoundIgnoresClasses - && !hasNoAnnotationFileParserWarning(typeDecl.getAnnotations()) - && !hasNoAnnotationFileParserWarning(packageAnnos))) { - if (elements.getAllTypeElements(fqTypeName).isEmpty()) { - stubWarnNotFound(typeDecl, "type not found: " + fqTypeName); - } else { - stubWarnNotFound( - typeDecl, - "type not found uniquely: " - + fqTypeName - + " : " - + elements.getAllTypeElements(fqTypeName)); - } - } - return null; - } - - List typeDeclTypeParameters = null; - if (typeElt.getKind() == ElementKind.ENUM) { - if (!(typeDecl instanceof EnumDeclaration)) { - warn( - typeDecl, - innerName - + " is an enum, but stub file declared it as " - + javaParserNodeToStringTruncated(typeDecl, 100)); - return null; - } - typeDeclTypeParameters = processEnum((EnumDeclaration) typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } else if (typeElt.getKind() == ElementKind.ANNOTATION_TYPE) { - if (!(typeDecl instanceof AnnotationDeclaration)) { - warn( - typeDecl, - innerName - + " is an annotation, but stub file declared it as " - + javaParserNodeToStringTruncated(typeDecl, 100)); - return null; - } - typeDeclTypeParameters = processType(typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } else if (typeDecl instanceof ClassOrInterfaceDeclaration) { - // TODO: This test is never satisfied, because it is the opposite of that on the line - // above. - if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) { - warn( - typeDecl, - innerName - + " is a class or interface, but stub file declared it as " - + javaParserNodeToStringTruncated(typeDecl, 100)); - return null; - } - typeDeclTypeParameters = processType(typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } else if (typeDecl instanceof RecordDeclaration) { - typeDeclTypeParameters = processType(typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } // else it's an EmptyTypeDeclaration. TODO: An EmptyTypeDeclaration can have - // annotations, right? - - // If processing an ajava file, then traversal is handled by a visitor, rather than the rest - // of this method. - if (fileType == AnnotationFileType.AJAVA) { - return typeDeclTypeParameters; - } - - if (typeDecl instanceof RecordDeclaration) { - RecordDeclaration recordDecl = (RecordDeclaration) typeDecl; - NodeList recordMembers = recordDecl.getParameters(); - Map byName = - ArrayMap.newArrayMapOrLinkedHashMap(recordMembers.size()); - for (Parameter recordMember : recordMembers) { - RecordComponentStub stub = - processRecordField( - recordMember, - findFieldElement( - typeElt, recordMember.getNameAsString(), recordMember)); - byName.put(recordMember.getNameAsString(), stub); - } - annotationFileAnnos.records.put( - recordDecl.getFullyQualifiedName().get(), new RecordStub(byName)); - } + public FqName(@Nullable String packageName, @Nullable String className) { + this.packageName = packageName; + this.className = className; + } - IPair>, Map>>> members = - getMembers(typeDecl, typeElt, typeDecl); - for (Map.Entry> entry : members.first.entrySet()) { - Element elt = entry.getKey(); - BodyDeclaration decl = entry.getValue(); - switch (elt.getKind()) { - case FIELD: - processField((FieldDeclaration) decl, (VariableElement) elt); - break; - case ENUM_CONSTANT: - // Enum constants can occur as fields in stubs files when their - // type has an annotation on it, e.g. see DeviceTypeTest which ends up with - // the TRACKER enum constant annotated with DefaultType: - if (decl instanceof FieldDeclaration) { - processField((FieldDeclaration) decl, (VariableElement) elt); - } else if (decl instanceof EnumConstantDeclaration) { - processEnumConstant((EnumConstantDeclaration) decl, (VariableElement) elt); - } else { - throw new BugInCF( - "unexpected decl type " - + decl.getClass() - + " for ENUM_CONSTANT kind, original: " - + decl); - } - break; - case CONSTRUCTOR: - case METHOD: - processCallableDeclaration( - (CallableDeclaration) decl, (ExecutableElement) elt); - break; - case CLASS: - case INTERFACE: - // Not processing an ajava file, so ignore the return value. - processTypeDecl((ClassOrInterfaceDeclaration) decl, innerName, null); - break; - case ENUM: - // Not processing an ajava file, so ignore the return value. - processTypeDecl((EnumDeclaration) decl, innerName, null); - break; - default: - /* do nothing */ - stubWarnNotFound(decl, "AnnotationFileParser ignoring: " + elt); - break; - } - } - for (Map.Entry>> entry : members.second.entrySet()) { - ExecutableElement fakeOverridden = (ExecutableElement) entry.getKey(); - List> fakeOverrideDecls = entry.getValue(); - for (BodyDeclaration bodyDecl : fakeOverrideDecls) { - processFakeOverride(fakeOverridden, (CallableDeclaration) bodyDecl, typeElt); - } - } - - if (typeDeclTypeParameters != null) { - typeParameters.removeAll(typeDeclTypeParameters); - } - - return null; - } - - /** - * Returns true if the argument contains {@code @NoAnnotationFileParserWarning}. - * - * @param aexprs collection of annotation expressions - * @return true if {@code aexprs} contains {@code @NoAnnotationFileParserWarning} - */ - private boolean hasNoAnnotationFileParserWarning(Iterable aexprs) { - if (aexprs == null) { - return false; - } - for (AnnotationExpr anno : aexprs) { - if (anno.getNameAsString().equals("NoAnnotationFileParserWarning")) { - return true; - } - } - return false; - } - - /** - * Process the type's declaration: copy its annotations to {@code #annotationFileAnnos}. Does - * not process any of its members. Returns the type's type parameter declarations. - * - * @param decl a type declaration - * @param elt the type's element - * @return the type's type parameter declarations - */ - private List processType(TypeDeclaration decl, TypeElement elt) { - - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - AnnotatedDeclaredType type = atypeFactory.fromElement(elt); - annotate(type, decl.getAnnotations(), decl); - - List typeArguments = type.getTypeArguments(); - List typeParameters; - if (decl instanceof NodeWithTypeParameters) { - typeParameters = ((NodeWithTypeParameters) decl).getTypeParameters(); - } else { - typeParameters = Collections.emptyList(); - } - - // It can be the case that args=[] and params=null, so don't crash in that case. - // if ((typeParameters == null) != (typeArguments == null)) { - // throw new Error(String.format("parseType (%s, %s): inconsistent nullness for args and - // params%n args = %s%n params = %s%n", decl, elt, typeArguments, typeParameters)); - // } - - if (debugAnnotationFileParser) { - int numParams = (typeParameters == null ? 0 : typeParameters.size()); - int numArgs = (typeArguments == null ? 0 : typeArguments.size()); - if (numParams != numArgs) { - stubDebug( - "parseType: mismatched sizes for typeParameters=%s (size %d)" - + " and typeArguments=%s (size %d);" - + " decl=%s; elt=%s (%s); type=%s (%s); typeBeingParsed=%s", - typeParameters, - numParams, - typeArguments, - numArgs, - decl.toString().replace(LINE_SEPARATOR, " "), - elt.toString().replace(LINE_SEPARATOR, " "), - elt.getClass(), - type, - type.getClass(), - typeBeingParsed); - stubDebug("proceeding despite mismatched sizes"); - } - } - - annotateTypeParameters(decl, elt, typeArguments, typeParameters); - if (decl instanceof ClassOrInterfaceDeclaration) { - annotateSupertypes((ClassOrInterfaceDeclaration) decl, type); - } - putMerge(annotationFileAnnos.atypes, elt, type); - List typeVariables = new ArrayList<>(type.getTypeArguments().size()); - for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { - if (typeV.getKind() != TypeKind.TYPEVAR) { - warn( - decl, - "expected an AnnotatedTypeVariable but found type kind " - + typeV.getKind() - + ": " - + typeV); - } else { - typeVariables.add((AnnotatedTypeVariable) typeV); - } - } - return typeVariables; - } - - /** - * Process an enum: copy its annotations to {@code #annotationFileAnnos}. Returns the enum's - * type parameter declarations. - * - * @param decl enum declaration - * @param elt element representing enum - * @return the enum's type parameter declarations - */ - private List processEnum(EnumDeclaration decl, TypeElement elt) { - - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - AnnotatedDeclaredType type = atypeFactory.fromElement(elt); - annotate(type, decl.getAnnotations(), decl); - - putMerge(annotationFileAnnos.atypes, elt, type); - List typeVariables = new ArrayList<>(type.getTypeArguments().size()); - for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { - if (typeV.getKind() != TypeKind.TYPEVAR) { - warn( - decl, - "expected an AnnotatedTypeVariable but found type kind " - + typeV.getKind() - + ": " - + typeV); - } else { - typeVariables.add((AnnotatedTypeVariable) typeV); - } - } - return typeVariables; - } - - private void annotateSupertypes( - ClassOrInterfaceDeclaration typeDecl, AnnotatedDeclaredType type) { - if (typeDecl.getExtendedTypes() != null) { - for (ClassOrInterfaceType supertype : typeDecl.getExtendedTypes()) { - AnnotatedDeclaredType annotatedSupertype = - findAnnotatedType(supertype, type.directSupertypes(), typeDecl); - if (annotatedSupertype == null) { - warn( - typeDecl, - "stub file does not match bytecode: " - + "could not find direct superclass " - + supertype - + " from type " - + type); - } else { - annotate(annotatedSupertype, supertype, null, typeDecl); - } - } - } - if (typeDecl.getImplementedTypes() != null) { - for (ClassOrInterfaceType supertype : typeDecl.getImplementedTypes()) { - AnnotatedDeclaredType annotatedSupertype = - findAnnotatedType(supertype, type.directSupertypes(), typeDecl); - if (annotatedSupertype == null) { - warn( - typeDecl, - "stub file does not match bytecode: " - + "could not find direct superinterface " - + supertype - + " from type " - + type); - } else { - annotate(annotatedSupertype, supertype, null, typeDecl); - } - } - } - } - - /** - * Process a method or constructor declaration: copy its annotations to {@code - * #annotationFileAnnos}. - * - * @param decl a method or constructor declaration, as read from an annotation file - * @param elt the method or constructor's element - * @return type variables for the method - */ - private @Nullable List processCallableDeclaration( - CallableDeclaration decl, ExecutableElement elt) { - if (!isAnnotatedForThisChecker(decl.getAnnotations())) { - return null; - } - // Declaration annotations - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - if (decl.isMethodDeclaration()) { - // AnnotationFileParser parses all annotations in type annotation position as type - // annotations. - recordDeclAnnotation(elt, ((MethodDeclaration) decl).getType().getAnnotations(), decl); - } - markAsFromStubFile(elt); - - AnnotatedExecutableType methodType; - try { - methodType = atypeFactory.fromElement(elt); - } catch (ErrorTypeKindException e) { - stubWarnNotFound(decl, "Error type kind occurred: " + e.getLocalizedMessage()); - return Collections.emptyList(); - } - - AnnotatedExecutableType origMethodType = - warnIfStubRedundantWithBytecode ? methodType.deepCopy() : null; - - // Type Parameters - annotateTypeParameters(decl, elt, methodType.getTypeVariables(), decl.getTypeParameters()); - typeParameters.addAll(methodType.getTypeVariables()); - - // Return type, from declaration annotations on the method or constructor - if (decl.isMethodDeclaration()) { - MethodDeclaration methodDeclaration = (MethodDeclaration) decl; - if (methodDeclaration.getParameters().isEmpty()) { - String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); - RecordStub recordStub = annotationFileAnnos.records.get(qualRecordName); - if (recordStub != null) { - RecordComponentStub recordComponentStub = - recordStub.componentsByName.get(methodDeclaration.getNameAsString()); - if (recordComponentStub != null) { - recordComponentStub.hasAccessorInStubs = true; - } - } - } - - try { - annotate( - methodType.getReturnType(), - methodDeclaration.getType(), - decl.getAnnotations(), - decl); - } catch (ErrorTypeKindException e) { - // See https://github.com/typetools/checker-framework/issues/244 . - // Issue a warning, to enable fixes to the classpath. - stubWarnNotFound(decl, "Error type kind occurred: " + e); - } - } else { - assert decl.isConstructorDeclaration(); - if (AnnotationFileUtil.isCanonicalConstructor(elt, atypeFactory.types)) { - // If this is the (user-written) canonical constructor, record that the component - // annotations should not be automatically transferred: - String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); - if (annotationFileAnnos.records.containsKey(qualRecordName)) { - List parameters = elt.getParameters(); - ArrayList annotatedParameters = - new ArrayList<>(parameters.size()); - for (int i = 0; i < parameters.size(); i++) { - VariableElement parameter = parameters.get(i); - AnnotatedTypeMirror atm = - AnnotatedTypeMirror.createType( - parameter.asType(), atypeFactory, false); - annotate(atm, decl.getParameter(i).getAnnotations(), decl.getParameter(i)); - annotatedParameters.add(atm); - } - annotationFileAnnos.records.get(qualRecordName) - .componentsInCanonicalConstructor = - annotatedParameters; - } - } - annotate(methodType.getReturnType(), decl.getAnnotations(), decl); - } - - // Parameters - processParameters(decl, elt, methodType); - - // Receiver - if (decl.getReceiverParameter().isPresent()) { - ReceiverParameter receiverParameter = decl.getReceiverParameter().get(); - if (methodType.getReceiverType() == null) { - if (decl.isConstructorDeclaration()) { - warn( - receiverParameter, - "parseParameter: constructor %s of a top-level class" - + " cannot have receiver annotations %s", - methodType, - receiverParameter.getAnnotations()); - } else { - warn( - receiverParameter, - "parseParameter: static method %s cannot have receiver annotations %s", - methodType, - receiverParameter.getAnnotations()); - } - } else { - // Add declaration annotations. - annotate( - methodType.getReceiverType(), - receiverParameter.getAnnotations(), - receiverParameter); - // Add type annotations. - annotate( - methodType.getReceiverType(), - receiverParameter.getType(), - receiverParameter.getAnnotations(), - receiverParameter); - } - } - - if (warnIfStubRedundantWithBytecode - && methodType.toString().equals(origMethodType.toString()) - && fileType != AnnotationFileType.BUILTIN_STUB) { - warn( - decl, - String.format( - "stub file specification is same as bytecode for %s", - ElementUtils.getQualifiedName(elt))); - } - - // Store the type. - putMerge(annotationFileAnnos.atypes, elt, methodType); - if (fileType.isStub()) { - typeParameters.removeAll(methodType.getTypeVariables()); - } - - return methodType.getTypeVariables(); - } - - /** - * Process the parameters of a method or constructor declaration: copy their annotations to - * {@code #annotationFileAnnos}. - * - * @param method a method or constructor declaration - * @param elt the element for {@code method} - * @param methodType the annotated type of {@code method} - */ - private void processParameters( - CallableDeclaration method, - ExecutableElement elt, - AnnotatedExecutableType methodType) { - List params = method.getParameters(); - List paramElts = elt.getParameters(); - List paramTypes = methodType.getParameterTypes(); - - for (int i = 0; i < methodType.getParameterTypes().size(); ++i) { - VariableElement paramElt = paramElts.get(i); - AnnotatedTypeMirror paramType = paramTypes.get(i); - Parameter param = params.get(i); - - recordDeclAnnotation(paramElt, param.getAnnotations(), param); - recordDeclAnnotation(paramElt, param.getType().getAnnotations(), param); - - if (param.isVarArgs()) { - assert paramType.getKind() == TypeKind.ARRAY; - // The "type" of param is actually the component type of the vararg. - // For example, in "Object..." the type would be "Object". - annotate( - ((AnnotatedArrayType) paramType).getComponentType(), - param.getType(), - param.getAnnotations(), - param); - // The "VarArgsAnnotations" are those just before "...". - annotate(paramType, param.getVarArgsAnnotations(), param); - } else { - annotate(paramType, param.getType(), param.getAnnotations(), param); - putMerge(annotationFileAnnos.atypes, paramElt, paramType); - } - } - } - - /** - * Clear (remove) existing annotations on the type. - * - *

Stub files override annotations read from .class files. Using {@code replaceAnnotation} - * usually achieves this; however, for annotations on type variables, it is sometimes necessary - * to remove an existing annotation, leaving no annotation on the type variable. This method - * does so. - * - * @param atype the type to modify - * @param typeDef the type from the annotation file, used only for diagnostic messages - */ - @SuppressWarnings("unused") // for disabled warning message - private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) { - // TODO: only produce output if the removed annotation isn't the top or default - // annotation in the type hierarchy. See https://tinyurl.com/cfissue/2759 . - /* - if (!atype.getAnnotations().isEmpty()) { - stubWarnOverwritesBytecode( - String.format( - "in file %s at line %s removed existing annotations on type: %s", - filename.substring(filename.lastIndexOf('/') + 1), - typeDef.getBegin().get().line, - atype.toString(true))); - } - */ - // Clear existing annotations, which only makes a difference for - // type variables, but doesn't hurt in other cases. - atype.clearAnnotations(); - } - - /** - * Add the annotations from {@code type} to {@code atype}. Type annotations that parsed as - * declaration annotations (i.e., type annotations in {@code declAnnos}) are applied to the - * innermost component type. - * - * @param atype annotated type to which to add annotations - * @param type parsed type - * @param declAnnos annotations stored on the declaration of the variable with this type or null - * @param astNode where to report errors - */ - private void annotateAsArray( - AnnotatedArrayType atype, - ReferenceType type, - @Nullable NodeList declAnnos, - NodeWithRange astNode) { - annotateInnermostComponentType(atype, declAnnos, astNode); - Type typeDef = type; - AnnotatedTypeMirror currentAtype = atype; - while (typeDef.isArrayType()) { - if (currentAtype.getKind() != TypeKind.ARRAY) { - warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); - return; - } - - // handle generic type - clearAnnotations(currentAtype, typeDef); - - List annotations = typeDef.getAnnotations(); - if (annotations != null) { - annotate(currentAtype, annotations, astNode); - } - typeDef = ((com.github.javaparser.ast.type.ArrayType) typeDef).getComponentType(); - currentAtype = ((AnnotatedArrayType) currentAtype).getComponentType(); - } - if (currentAtype.getKind() == TypeKind.ARRAY) { - warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); - } - } - - private @Nullable ClassOrInterfaceType unwrapDeclaredType(Type type) { - if (type instanceof ClassOrInterfaceType) { - return (ClassOrInterfaceType) type; - } else if (type instanceof ReferenceType && type.getArrayLevel() == 0) { - return unwrapDeclaredType(type.getElementType()); - } else { - return null; - } - } - - /** - * Add to formal parameter {@code atype}: - * - *

    - *
  1. the annotations from {@code typeDef}, and - *
  2. any type annotations that parsed as declaration annotations (i.e., type annotations in - * {@code declAnnos}). - *
- * - * @param atype annotated type to which to add annotations - * @param typeDef parsed type - * @param declAnnos annotations stored on the declaration of the variable with this type, or - * null - * @param astNode where to report errors - */ - private void annotate( - AnnotatedTypeMirror atype, - Type typeDef, - @Nullable NodeList declAnnos, - NodeWithRange astNode) { - if (atype.getKind() == TypeKind.ARRAY) { - if (typeDef instanceof ReferenceType) { - annotateAsArray( - (AnnotatedArrayType) atype, (ReferenceType) typeDef, declAnnos, astNode); - } else { - warn(astNode, "expected ReferenceType but found: " + typeDef); - } - return; - } - - clearAnnotations(atype, typeDef); - - // Primary annotations for the type of a variable declaration are not stored in typeDef, but - // rather as declaration annotations (passed as declAnnos to this method). But, if typeDef - // is not the type of a variable, then the primary annotations are stored in typeDef. - NodeList primaryAnnotations; - if (typeDef.getAnnotations().isEmpty() && declAnnos != null) { - primaryAnnotations = declAnnos; - } else { - primaryAnnotations = typeDef.getAnnotations(); - } - if (atype.getKind() != TypeKind.WILDCARD) { - // The primary annotation on a wildcard applies to the super or extends bound and - // are added below. - annotate(atype, primaryAnnotations, astNode); - } - - switch (atype.getKind()) { - case DECLARED: - ClassOrInterfaceType declType = unwrapDeclaredType(typeDef); - if (declType == null) { - break; - } - AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType) atype; - // Process type arguments. - @SuppressWarnings( - "optional:optional.collection") // JavaParser uses Optional - Optional> oDeclTypeArgs = declType.getTypeArguments(); - List adeclTypeArgs = adeclType.getTypeArguments(); - if (oDeclTypeArgs.isPresent() - && !oDeclTypeArgs.get().isEmpty() - && !adeclTypeArgs.isEmpty()) { - NodeList declTypeArgs = oDeclTypeArgs.get(); - if (declTypeArgs.size() != adeclTypeArgs.size()) { - warn( - astNode, - String.format( - "Mismatch in type argument size between %s (%d) and %s (%d)", - declType, - declTypeArgs.size(), - adeclType, - adeclTypeArgs.size())); - break; - } - for (int i = 0; i < declTypeArgs.size(); ++i) { - annotate(adeclTypeArgs.get(i), declTypeArgs.get(i), null, astNode); - } - } - break; - case WILDCARD: - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atype; - // Ensure that the file also has a wildcard type, report an error otherwise - if (!typeDef.isWildcardType()) { - // We throw an error here, as otherwise we are just getting a generic cast error - // on the very next line. - warn( - astNode, - "wildcard type <" - + atype - + "> does not match type in stubs file" - + filename - + ": <" - + typeDef - + ">" - + " while parsing " - + typeBeingParsed); - return; - } - WildcardType wildcardDef = (WildcardType) typeDef; - if (wildcardDef.getExtendedType().isPresent()) { - annotate( - wildcardType.getExtendsBound(), - wildcardDef.getExtendedType().get(), - null, - astNode); - annotate(wildcardType.getSuperBound(), primaryAnnotations, astNode); - } else if (wildcardDef.getSuperType().isPresent()) { - annotate( - wildcardType.getSuperBound(), - wildcardDef.getSuperType().get(), - null, - astNode); - annotate(wildcardType.getExtendsBound(), primaryAnnotations, astNode); - } else if (primaryAnnotations.isEmpty()) { - // Unannotated unbounded wildcard "?": remove any existing annotations and - // add the annotations from the type variable corresponding to the wildcard. - wildcardType.getExtendsBound().clearAnnotations(); - wildcardType.getSuperBound().clearAnnotations(); - AnnotatedTypeVariable atv = - (AnnotatedTypeVariable) - atypeFactory.getAnnotatedType( - wildcardType.getTypeVariable().asElement()); - wildcardType - .getExtendsBound() - .addAnnotations(atv.getUpperBound().getAnnotations()); - wildcardType - .getSuperBound() - .addAnnotations(atv.getLowerBound().getAnnotations()); - } else { - // Annotated unbounded wildcard "@A ?": use annotations. - annotate(atype, primaryAnnotations, astNode); - } - break; - case TYPEVAR: - // Add annotations from the declaration of the TypeVariable - AnnotatedTypeVariable typeVarUse = (AnnotatedTypeVariable) atype; - Types typeUtils = processingEnv.getTypeUtils(); - for (AnnotatedTypeVariable typePar : typeParameters) { - if (typeUtils.isSameType( - typePar.getUnderlyingType(), atype.getUnderlyingType())) { - atypeFactory.replaceAnnotations( - typePar.getUpperBound(), typeVarUse.getUpperBound()); - atypeFactory.replaceAnnotations( - typePar.getLowerBound(), typeVarUse.getLowerBound()); - } - } - break; - default: - // No additional annotations to add. - } - } - - /** - * Process the field declaration in decl: copy its annotations to {@code #annotationFileAnnos}. - * - * @param decl the declaration in the annotation file - * @param elt the element representing that same declaration - */ - private void processField(FieldDeclaration decl, VariableElement elt) { - if (skipNode(decl)) { - // Don't process private fields of the JDK. They can't be referenced outside of the JDK - // and might refer to types that are not accessible. - return; - } - markAsFromStubFile(elt); - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - // AnnotationFileParser parses all annotations in type annotation position as type - // annotations - recordDeclAnnotation(elt, decl.getElementType().getAnnotations(), decl); - AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); - - VariableDeclarator fieldVarDecl = null; - String eltName = elt.getSimpleName().toString(); - for (VariableDeclarator var : decl.getVariables()) { - if (var.getName().toString().equals(eltName)) { - fieldVarDecl = var; - break; - } - } - assert fieldVarDecl != null; - annotate(fieldType, fieldVarDecl.getType(), decl.getAnnotations(), fieldVarDecl); - putMerge(annotationFileAnnos.atypes, elt, fieldType); - } - - /** - * Processes a parameter in a record header (i.e., a record component). - * - * @param decl the parameter in the record header - * @param elt the corresponding variable declaration element - * @return a representation of the record component in the stub file - */ - private RecordComponentStub processRecordField(Parameter decl, VariableElement elt) { - markAsFromStubFile(elt); - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - // AnnotationFileParser parses all annotations in type annotation position as type - // annotations. - recordDeclAnnotation(elt, decl.getType().getAnnotations(), decl); - AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); - - annotate(fieldType, decl.getType(), decl.getAnnotations(), decl); - putMerge(annotationFileAnnos.atypes, elt, fieldType); - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - for (AnnotationExpr annotation : decl.getAnnotations()) { - AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); - annos.add(annoMirror); - } - return new RecordComponentStub(fieldType, annos); - } - - /** - * Adds the annotations present on the declaration of an enum constant to the ATM of that - * constant. - * - * @param decl the enum constant, in Javaparser AST form (the source of annotations) - * @param elt the enum constant declaration, as an element (the destination for annotations) - */ - private void processEnumConstant(EnumConstantDeclaration decl, VariableElement elt) { - markAsFromStubFile(elt); - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - AnnotatedTypeMirror enumConstType = atypeFactory.fromElement(elt); - annotate(enumConstType, decl.getAnnotations(), decl); - putMerge(annotationFileAnnos.atypes, elt, enumConstType); - } - - /** - * Returns the innermost component type of {@code type}. - * - * @param type array type - * @return the innermost component type of {@code type} - */ - private AnnotatedTypeMirror innermostComponentType(AnnotatedArrayType type) { - AnnotatedTypeMirror componentType = type; - while (componentType.getKind() == TypeKind.ARRAY) { - componentType = ((AnnotatedArrayType) componentType).getComponentType(); - } - return componentType; - } - - /** - * Adds {@code annotations} to the innermost component type of {@code type}. - * - * @param type array type - * @param annotations annotations to add - * @param astNode where to report errors - */ - private void annotateInnermostComponentType( - AnnotatedArrayType type, List annotations, NodeWithRange astNode) { - annotate(innermostComponentType(type), annotations, astNode); - } - - /** - * Annotate the type with the given type annotations, removing any existing annotations from the - * same qualifier hierarchies. - * - * @param type the type to annotate - * @param annotations the new annotations for the type; if null, nothing is done - * @param astNode where to report errors - */ - private void annotate( - AnnotatedTypeMirror type, - @Nullable List annotations, - NodeWithRange astNode) { - if (annotations == null) { - return; - } - for (AnnotationExpr annotation : annotations) { - AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); - if (annoMirror != null) { - type.replaceAnnotation(annoMirror); - } else { - // TODO: Maybe always warn here. It's so easy to forget an import statement and - // have an annotation silently ignored. - stubWarnNotFound(astNode, "unknown annotation " + annotation); - } - } - } - - /** - * Adds to {@code annotationFileAnnos} all the annotations in {@code annotations} that are - * applicable to {@code elt}'s location. For example, if an annotation is a type annotation but - * {@code elt} is a field declaration, the type annotation will be ignored. - * - * @param elt the element to be annotated - * @param annotations set of annotations that may be applicable to elt - * @param astNode where to report errors - */ - private void recordDeclAnnotation( - Element elt, List annotations, NodeWithRange astNode) { - if (annotations == null || annotations.isEmpty()) { - return; - } - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - for (AnnotationExpr annotation : annotations) { - AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); - if (annoMirror != null) { - // The @Target annotation on `annotation`/`annoMirror` - Target target = - annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); - // Only add the declaration annotation if the annotation applies to the element. - if (AnnotationUtils.getElementKindsForTarget(target).contains(elt.getKind())) { - // `annoMirror` is applicable to `elt` - annos.add(annoMirror); - } - } else { - // TODO: Maybe always warn here. It's so easy to forget an import statement and - // have an annotation silently ignored. - stubWarnNotFound(astNode, String.format("unknown annotation %s", annotation)); - } - } - String eltName = ElementUtils.getQualifiedName(elt); - putOrAddToDeclAnnos(eltName, annos); - } - - /** - * Adds the declaration annotation {@code @FromStubFile} to {@link #annotationFileAnnos}, unless - * we are parsing the JDK as a stub file. - * - * @param elt an element to be annotated as {@code @FromStubFile} - */ - private void markAsFromStubFile(Element elt) { - if (fileType == AnnotationFileType.AJAVA || fileType == AnnotationFileType.JDK_STUB) { - return; - } - putOrAddToDeclAnnos( - ElementUtils.getQualifiedName(elt), - AnnotationMirrorSet.singleton(fromStubFileAnno)); - } - - private void annotateTypeParameters( - BodyDeclaration decl, // for debugging - Object elt, // for debugging; TypeElement or ExecutableElement - List typeArguments, - List typeParameters) { - if (typeParameters == null) { - return; - } - - if (typeParameters.size() != typeArguments.size()) { - String msg = - String.format( - "annotateTypeParameters: mismatched sizes:" - + " typeParameters (size %d)=%s;" - + " typeArguments (size %d)=%s;" - + " decl=%s; elt=%s (%s).", - typeParameters.size(), - typeParameters, - typeArguments.size(), - typeArguments, - decl.toString().replace(LINE_SEPARATOR, " "), - elt.toString().replace(LINE_SEPARATOR, " "), - elt.getClass()); - if (!debugAnnotationFileParser) { - msg = msg + "; for more details, run with -AstubDebug"; - } - warn(decl, msg); - return; - } - for (int i = 0; i < typeParameters.size(); ++i) { - TypeParameter param = typeParameters.get(i); - AnnotatedTypeVariable paramType = (AnnotatedTypeVariable) typeArguments.get(i); - - // Handle type bounds - if (param.getTypeBound() == null || param.getTypeBound().isEmpty()) { - // No type bound, so annotations are both lower and upper bounds. - annotate(paramType, param.getAnnotations(), param); - } else if (param.getTypeBound() != null && !param.getTypeBound().isEmpty()) { - annotate(paramType.getLowerBound(), param.getAnnotations(), param); - if (param.getTypeBound().size() == 1) { - // The additional declAnnos (third argument) is always null in this call to - // `annotate`, but the type bound (second argument) might have annotations. - annotate(paramType.getUpperBound(), param.getTypeBound().get(0), null, param); - } else { - // param.getTypeBound().size() > 1 - ArrayList typeBoundsWithAnotations = - new ArrayList<>(param.getTypeBound().size()); - for (ClassOrInterfaceType typeBound : param.getTypeBound()) { - if (!typeBound.getAnnotations().isEmpty()) { - typeBoundsWithAnotations.add(typeBound); - } - } - int numBounds = typeBoundsWithAnotations.size(); - if (numBounds == 0) { - // nothing to do - } else if (numBounds == 1) { - annotate( - paramType.getUpperBound(), - typeBoundsWithAnotations.get(0), - null, - param); - } else { - // TODO: add support for intersection types - // One problem is that `annotate()` removes any existing annotations from - // the same qualifier hierarchies, so paramType.getLowerBound() would end up - // with the annotations of only the last type bound. - - // String msg = - // String.format( - // "annotateTypeParameters: multiple type bounds: - // typeParameters=%s; " - // + "param #%d=%s; bounds=%s; decl=%s; elt=%s (%s).", - // typeParameters, - // i, - // param, - // param.getTypeBound(), - // decl.toString().replace(LINE_SEPARATOR, " "), - // elt.toString().replace(LINE_SEPARATOR, " "), - // elt.getClass()); - // warn(decl, msg); - - stubWarnNotFound( - param, - "annotations on intersection types are not yet supported: " - + param); - } - } - if (param.getTypeBound().size() == 1 - && param.getTypeBound().get(0).getAnnotations().isEmpty() - && TypesUtils.isObject(paramType.getUpperBound().getUnderlyingType())) { - // If there is an explicit "T extends Object" type parameter bound, - // treat it like an explicit use of "Object" in code. - AnnotatedTypeMirror ub = atypeFactory.getAnnotatedType(Object.class); - paramType.getUpperBound().replaceAnnotations(ub.getAnnotations()); - } - } - - putMerge( - annotationFileAnnos.atypes, - paramType.getUnderlyingType().asElement(), - paramType); - } - } - - /** - * Returns a pair of mappings. For each member declaration of the JavaParser type declaration - * {@code typeDecl}: - * - *
    - *
  • If {@code typeElt} contains a member element for it, the first mapping maps the member - * element to it. - *
  • If it is a fake override, the second mapping maps each element it overrides to it. - *
  • Otherwise, does nothing. - *
- * - * This method does not read or write the field {@link #annotationFileAnnos}. - * - * @param typeDecl a JavaParser type declaration - * @param typeElt the javac element for {@code typeDecl} - * @return two mappings: from javac elements to their JavaParser declaration, and from javac - * elements to fake overrides of them - * @param astNode where to report errors - */ - private IPair>, Map>>> - getMembers(TypeDeclaration typeDecl, TypeElement typeElt, NodeWithRange astNode) { - assert (typeElt.getSimpleName().contentEquals(typeDecl.getNameAsString()) - || typeDecl.getNameAsString().endsWith("$" + typeElt.getSimpleName())) - : String.format("%s %s", typeElt.getSimpleName(), typeDecl.getName()); - - Map> elementsToDecl = new LinkedHashMap<>(); - Map>> fakeOverrideDecls = new LinkedHashMap<>(); - - for (BodyDeclaration member : typeDecl.getMembers()) { - putNewElement( - elementsToDecl, - fakeOverrideDecls, - typeElt, - member, - typeDecl.getNameAsString(), - astNode); - } - // For an enum type declaration, also add the enum constants - if (typeDecl instanceof EnumDeclaration) { - EnumDeclaration enumDecl = (EnumDeclaration) typeDecl; - // getEntries() gives the list of enum constant declarations - for (BodyDeclaration member : enumDecl.getEntries()) { - putNewElement( - elementsToDecl, - fakeOverrideDecls, - typeElt, - member, - typeDecl.getNameAsString(), - astNode); - } - } - - return IPair.of(elementsToDecl, fakeOverrideDecls); - } - - // Used only by getMembers(). - /** - * If {@code typeElt} contains an element for {@code member}, adds to {@code elementsToDecl} a - * mapping from member's element to member. Does nothing if a mapping already exists. - * - *

Otherwise (if there is no element for {@code member}), adds to {@code fakeOverrideDecls} - * zero or more mappings. Each mapping is from an element that {@code member} would override to - * {@code member}. - * - *

This method does not read or write field {@link #annotationFileAnnos}. - * - * @param elementsToDecl the mapping that is side-effected by this method - * @param fakeOverrideDecls fake overrides, also side-effected by this method - * @param typeElt the class in which {@code member} is declared - * @param member the stub file declaration of a method - * @param typeDeclName used only for debugging - * @param astNode where to report errors - */ - private void putNewElement( - Map> elementsToDecl, - Map>> fakeOverrideDecls, - TypeElement typeElt, - BodyDeclaration member, - String typeDeclName, - NodeWithRange astNode) { - if (member instanceof MethodDeclaration) { - MethodDeclaration method = (MethodDeclaration) member; - Element elt = findElement(typeElt, method, /* noWarn= */ true); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, method); - } else { - ExecutableElement overriddenMethod = fakeOverriddenMethod(typeElt, method); - if (overriddenMethod == null) { - // Didn't find the element and it isn't a fake override. Issue a warning. - findElement(typeElt, method, /* noWarn= */ false); - } else { - List> l = - fakeOverrideDecls.computeIfAbsent( - overriddenMethod, __ -> new ArrayList<>(1)); - l.add(member); - } - } - } else if (member instanceof ConstructorDeclaration) { - Element elt = findElement(typeElt, (ConstructorDeclaration) member); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else if (member instanceof FieldDeclaration) { - FieldDeclaration fieldDecl = (FieldDeclaration) member; - for (VariableDeclarator var : fieldDecl.getVariables()) { - Element varelt = findElement(typeElt, var); - if (varelt != null) { - putIfAbsent(elementsToDecl, varelt, fieldDecl); - } - } - } else if (member instanceof EnumConstantDeclaration) { - Element elt = findElement(typeElt, (EnumConstantDeclaration) member, astNode); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else if (member instanceof ClassOrInterfaceDeclaration) { - Element elt = findElement(typeElt, (ClassOrInterfaceDeclaration) member); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else if (member instanceof EnumDeclaration) { - Element elt = findElement(typeElt, (EnumDeclaration) member); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else { - stubDebug("ignoring element of type %s in %s", member.getClass(), typeDeclName); - } - } - - /** - * Given a method declaration that does not correspond to an element, returns the method it - * directly overrides or implements. As Java does, this prefers a method in a superclass to one - * in an interface. - * - *

As with regular overrides, the parameter types must be exact matches; contravariance is - * not permitted. - * - * @param typeElt the type in which the method appears - * @param methodDecl the method declaration that does not correspond to an element - * @return the methods that the given method declaration would override, or null if none - */ - private @Nullable ExecutableElement fakeOverriddenMethod( - TypeElement typeElt, MethodDeclaration methodDecl) { - for (Element elt : typeElt.getEnclosedElements()) { - if (elt.getKind() != ElementKind.METHOD) { - continue; - } - ExecutableElement candidate = (ExecutableElement) elt; - if (!candidate.getSimpleName().contentEquals(methodDecl.getName().getIdentifier())) { - continue; - } - List candidateParams = candidate.getParameters(); - if (sameTypes(candidateParams, methodDecl.getParameters())) { - return candidate; - } - } - - TypeElement superType = ElementUtils.getSuperClass(typeElt); - if (superType != null) { - ExecutableElement result = fakeOverriddenMethod(superType, methodDecl); - if (result != null) { - return result; - } - } - - for (TypeMirror interfaceTypeMirror : typeElt.getInterfaces()) { - TypeElement interfaceElement = - (TypeElement) ((DeclaredType) interfaceTypeMirror).asElement(); - ExecutableElement result = fakeOverriddenMethod(interfaceElement, methodDecl); - if (result != null) { - return result; - } - } - - return null; - } - - /** - * Returns true if the two signatures (represented as lists of formal parameters) are the same. - * No contravariance is permitted. - * - * @param javacParams parameter list in javac form - * @param javaParserParams parameter list in JavaParser form - * @return true if the two signatures are the same - */ - private boolean sameTypes( - List javacParams, NodeList javaParserParams) { - if (javacParams.size() != javaParserParams.size()) { - return false; - } - for (int i = 0; i < javacParams.size(); i++) { - TypeMirror javacType = javacParams.get(i).asType(); - Parameter javaParserParam = javaParserParams.get(i); - Type javaParserType = javaParserParam.getType(); - if (javacType.getKind() == TypeKind.TYPEVAR) { - // TODO: Hack, need to viewpoint-adapt. - javacType = ((TypeVariable) javacType).getUpperBound(); - } - if (!sameType(javacType, javaParserType)) { - return false; - } - } - return true; - } - - /** - * Returns true if the two types are the same. - * - * @param javacType type in javac form - * @param javaParserType type in JavaParser form - * @return true if the two types are the same - */ - private boolean sameType(TypeMirror javacType, Type javaParserType) { - - switch (javacType.getKind()) { - case BOOLEAN: - return javaParserType.equals(PrimitiveType.booleanType()); - case BYTE: - return javaParserType.equals(PrimitiveType.byteType()); - case CHAR: - return javaParserType.equals(PrimitiveType.charType()); - case DOUBLE: - return javaParserType.equals(PrimitiveType.doubleType()); - case FLOAT: - return javaParserType.equals(PrimitiveType.floatType()); - case INT: - return javaParserType.equals(PrimitiveType.intType()); - case LONG: - return javaParserType.equals(PrimitiveType.longType()); - case SHORT: - return javaParserType.equals(PrimitiveType.shortType()); - - case DECLARED: - case TYPEVAR: - if (!(javaParserType instanceof ClassOrInterfaceType)) { - return false; - } - com.sun.tools.javac.code.Type javacTypeInternal = - (com.sun.tools.javac.code.Type) javacType; - ClassOrInterfaceType javaParserClassType = (ClassOrInterfaceType) javaParserType; - - // Use asString() because toString() includes annotations. - String javaParserString = javaParserClassType.asString(); - Element javacElement = javacTypeInternal.asElement(); - // Check both fully-qualified name and simple name. - return javacElement.toString().equals(javaParserString) - || javacElement.getSimpleName().contentEquals(javaParserString); - - case ARRAY: - return javaParserType.isArrayType() - && sameType( - ((ArrayType) javacType).getComponentType(), - javaParserType.asArrayType().getComponentType()); - - default: - throw new BugInCF("unhandled type %s of kind %s", javacType, javacType.getKind()); - } - } - - /** - * Process a fake override: copy its annotations to the fake overrides part of {@code - * #annotationFileAnnos}. - * - * @param element a real element - * @param decl a fake override of the element - * @param fakeLocation where the fake override was defined - */ - private void processFakeOverride( - ExecutableElement element, CallableDeclaration decl, TypeElement fakeLocation) { - // This is a fresh type, which this code may side-effect. - AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(element); - - // Here is a hacky solution that does not use the visitor. It just handles the return type. - // TODO: Walk the type and the declaration, copying annotations from the declaration to the - // element. I think PR #3977 has a visitor that does that, which I should use after it is - // merged. - - // The annotations on the method. These include type annotations on the return type. - NodeList annotations = decl.getAnnotations(); - annotate( - methodType.getReturnType(), - ((MethodDeclaration) decl).getType(), - annotations, - decl); - - List> l = - annotationFileAnnos.fakeOverrides.computeIfAbsent( - element, __ -> new ArrayList<>(1)); - l.add(IPair.of(fakeLocation.asType(), methodType)); - } - - /** - * Return the annotated type corresponding to {@code type}, or null if none exists. More - * specifically, returns the element of {@code types} whose name matches {@code type}. - * - * @param type the type to search for - * @param types the list of AnnotatedDeclaredTypes to search in - * @param astNode where to report errors - * @return the annotated type in {@code types} corresponding to {@code type}, or null if none - * exists - */ - private @Nullable AnnotatedDeclaredType findAnnotatedType( - ClassOrInterfaceType type, - List types, - NodeWithRange astNode) { - String typeString = type.getNameAsString(); - for (AnnotatedDeclaredType supertype : types) { - if (supertype - .getUnderlyingType() - .asElement() - .getSimpleName() - .contentEquals(typeString)) { - return supertype; - } - } - stubWarnNotFound(astNode, "direct supertype " + typeString + " not found"); - if (debugAnnotationFileParser) { - stubDebug("direct supertypes that were searched:"); - for (AnnotatedDeclaredType supertype : types) { - stubDebug(" %s", supertype); - } - } - return null; - } - - /** - * Looks for the nested type element in the typeElt and returns it if the element has the same - * name as provided class or interface declaration. In case nested element is not found it - * returns null. - * - * @param typeElt an element where nested type element should be looked for - * @param ciDecl class or interface declaration which name should be found among nested elements - * of the typeElt - * @return nested in typeElt element with the name of the class or interface, or null if nested - * element is not found - */ - private @Nullable Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) { - String wantedClassOrInterfaceName = ciDecl.getNameAsString(); - for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { - if (wantedClassOrInterfaceName.equals(typeElement.getSimpleName().toString())) { - return typeElement; - } - } - - stubWarnNotFound( - ciDecl, - "class/interface " + wantedClassOrInterfaceName + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - stubDebug(" type declarations of %s:", typeElt); - for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - return null; - } - - /** - * Looks for the nested enum element in the typeElt and returns it if the element has the same - * name as provided enum declaration. In case nested element is not found it returns null. - * - * @param typeElt an element where nested enum element should be looked for - * @param enumDecl enum declaration which name should be found among nested elements of the - * typeElt - * @return nested in typeElt enum element with the name of the provided enum, or null if nested - * element is not found - */ - private @Nullable Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) { - String wantedEnumName = enumDecl.getNameAsString(); - for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { - if (wantedEnumName.equals(typeElement.getSimpleName().toString())) { - return typeElement; - } - } - - stubWarnNotFound(enumDecl, "enum " + wantedEnumName + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - stubDebug(" type declarations of %s:", typeElt); - for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - return null; - } - - /** - * Looks for an enum constant element in the typeElt and returns it if the element has the same - * name as provided. In case enum constant element is not found it returns null. - * - * @param typeElt type element where enum constant element should be looked for - * @param enumConstDecl the declaration of the enum constant - * @param astNode where to report errors - * @return enum constant element in typeElt with the provided name, or null if enum constant - * element is not found - */ - private @Nullable VariableElement findElement( - TypeElement typeElt, EnumConstantDeclaration enumConstDecl, NodeWithRange astNode) { - String enumConstName = enumConstDecl.getNameAsString(); - return findFieldElement(typeElt, enumConstName, astNode); - } - - /** - * Looks for a method element in {@code typeElt} that has the same name and formal parameter - * types as {@code methodDecl}. Returns null, and possibly issues a warning, if no such method - * element is found. - * - * @param typeElt type element where method element should be looked for - * @param methodDecl method declaration with signature that should be found among methods in the - * typeElt - * @param noWarn if true, don't issue a warning if the element is not found - * @return method element in typeElt with the same signature as the provided method declaration - * or null if method element is not found - */ - private @Nullable ExecutableElement findElement( - TypeElement typeElt, MethodDeclaration methodDecl, boolean noWarn) { - if (skipNode(methodDecl)) { - return null; - } - String wantedMethodName = methodDecl.getNameAsString(); - int wantedMethodParams = - (methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size(); - String wantedMethodString = AnnotationFileUtil.toString(methodDecl); - for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (wantedMethodParams == method.getParameters().size() - && wantedMethodName.contentEquals(method.getSimpleName().toString()) - && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { - return method; - } - } - if (!noWarn) { - if (methodDecl.getAccessSpecifier() == AccessSpecifier.NONE) { - // This might be a false positive warning. The stub parser permits a stub file to - // omit the access specifier, but package-private methods aren't in the TypeElement. - stubWarnNotFound( - methodDecl, - "package-private method " - + wantedMethodString - + " not found in type " - + typeElt - + System.lineSeparator() - + "If the method is not package-private," - + " add an access specifier in the stub file" - + " and use -AstubDebug to receive a more useful error message."); - } else { - stubWarnNotFound( - methodDecl, - "method " + wantedMethodString + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - stubDebug(" methods of %s:", typeElt); - for (ExecutableElement method : - ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - } - } - return null; - } - - /** - * Looks for a constructor element in the typeElt and returns it if the element has the same - * signature as provided constructor declaration. In case constructor element is not found it - * returns null. - * - * @param typeElt type element where constructor element should be looked for - * @param constructorDecl constructor declaration with signature that should be found among - * constructors in the typeElt - * @return constructor element in typeElt with the same signature as the provided constructor - * declaration or null if constructor element is not found - */ - private @Nullable ExecutableElement findElement( - TypeElement typeElt, ConstructorDeclaration constructorDecl) { - if (skipNode(constructorDecl)) { - return null; - } - int wantedMethodParams = - (constructorDecl.getParameters() == null) - ? 0 - : constructorDecl.getParameters().size(); - String wantedMethodString = AnnotationFileUtil.toString(constructorDecl); - for (ExecutableElement method : - ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { - if (wantedMethodParams == method.getParameters().size() - && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { - return method; - } - } - - stubWarnNotFound( - constructorDecl, - "constructor " + wantedMethodString + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - for (ExecutableElement method : - ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - return null; - } - - /** - * Returns the element for the given variable. - * - * @param typeElt the type in which the variable is contained - * @param variable the variable whose element to return - * @return the element for the given variable - */ - private VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) { - String fieldName = variable.getNameAsString(); - return findFieldElement(typeElt, fieldName, variable); - } - - /** - * Looks for a field element in the typeElt and returns it if the element has the same name as - * provided. In case field element is not found it returns null. - * - * @param typeElt type element where field element should be looked for - * @param fieldName field name that should be found - * @param astNode where to report errors - * @return field element in typeElt with the provided name or null if field element is not found - */ - private @Nullable VariableElement findFieldElement( - TypeElement typeElt, String fieldName, NodeWithRange astNode) { - for (VariableElement field : ElementUtils.getAllFieldsIn(typeElt, elements)) { - // field.getSimpleName() is a CharSequence, not a String - if (fieldName.equals(field.getSimpleName().toString())) { - return field; - } - } - - stubWarnNotFound(astNode, "field " + fieldName + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", field); - } - } - return null; - } - - /** - * Given a fully-qualified type name, return a TypeElement for it, or null if none exists. Also - * cache in importedTypes. - * - * @param name a fully-qualified type name - * @return a TypeElement for the name, or null - */ - private @Nullable TypeElement getTypeElementOrNull(@FullyQualifiedName String name) { - TypeElement typeElement = elements.getTypeElement(name); - if (typeElement != null) { - importedTypes.put(name, typeElement); - } - // for debugging: warn("getTypeElementOrNull(%s) => %s", name, typeElement); - return typeElement; - } - - /** - * Get the type element for the given fully-qualified type name. If none is found, issue a - * warning and return null. - * - * @param typeName a type name - * @param msg a warning message to issue if the type element for {@code typeName} cannot be - * found - * @param astNode where to report errors - * @return the type element for the given fully-qualified type name, or null - */ - private @Nullable TypeElement getTypeElement( - @FullyQualifiedName String typeName, String msg, NodeWithRange astNode) { - TypeElement classElement = elements.getTypeElement(typeName); - if (classElement == null) { - stubWarnNotFound(astNode, msg + ": " + typeName); - } - return classElement; - } - - /** - * Returns the element for the given package. - * - * @param packageName the package's name - * @param astNode where to report errors - * @return the element for the given package - */ - private PackageElement findPackage(String packageName, NodeWithRange astNode) { - PackageElement packageElement = elements.getPackageElement(packageName); - if (packageElement == null) { - stubWarnNotFound(astNode, "imported package not found: " + packageName); - } - return packageElement; - } - - /** - * Returns true if one of the annotations is {@link AnnotatedFor} and this checker is in its - * list of checkers. If none of the annotations are {@code AnnotatedFor}, then also return true. - * - * @param annotations a list of JavaParser annotations - * @return true if one of the annotations is {@link AnnotatedFor} and its list of checkers does - * not contain this checker - */ - private boolean isAnnotatedForThisChecker(List annotations) { - if (fileType == AnnotationFileType.JDK_STUB) { - // The JDK stubs have purity annotations that should be read for all checkers. - // TODO: Parse the JDK stubs, but only save the declaration annotations. - return true; - } - for (AnnotationExpr ae : annotations) { - if (ae.getNameAsString().equals("AnnotatedFor") - || ae.getNameAsString() - .equals("org.checkerframework.framework.qual.AnnotatedFor")) { - AnnotationMirror af = getAnnotation(ae, allAnnotations); - if (atypeFactory.areSameByClass(af, AnnotatedFor.class)) { - return atypeFactory.doesAnnotatedForApplyToThisChecker(af); - } - } - } - return true; - } - - /** - * Convert {@code annotation} into an AnnotationMirror. Returns null if the annotation isn't - * supported by the checker or if some error occurred while converting it. - * - * @param annotation syntax tree for an annotation - * @param allAnnotations map from simple name to annotation definition; side-effected by this - * method - * @return the AnnotationMirror for the annotation, or null if it cannot be built - */ - private @Nullable AnnotationMirror getAnnotation( - AnnotationExpr annotation, Map allAnnotations) { - - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); - TypeElement annoTypeElt = allAnnotations.get(annoNameFq); - if (annoTypeElt == null) { - // If the annotation was not imported, then #getImportedAnnotations did not add it to - // the allAnnotations field. This code adds the annotation when it is encountered (i.e. - // here). - // Note that this does not call AnnotationFileParser#getTypeElement to avoid a spurious - // diagnostic if the annotation is actually unknown. - annoTypeElt = elements.getTypeElement(annoNameFq); - if (annoTypeElt == null) { - // Not a supported annotation -> ignore - return null; - } - putAllNew( - allAnnotations, - createNameToAnnotationMap(Collections.singletonList(annoTypeElt))); - } - @SuppressWarnings("signature") // not anonymous, so name is not empty - @CanonicalName String annoName = annoTypeElt.getQualifiedName().toString(); - - if (annotation instanceof MarkerAnnotationExpr) { - return AnnotationBuilder.fromName(elements, annoName); - } else if (annotation instanceof NormalAnnotationExpr) { - NormalAnnotationExpr nrmanno = (NormalAnnotationExpr) annotation; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); - List pairs = nrmanno.getPairs(); - if (pairs != null) { - for (MemberValuePair mvp : pairs) { - String member = mvp.getNameAsString(); - Expression exp = mvp.getValue(); - try { - builderAddElement(builder, member, exp); - } catch (AnnotationFileParserException e) { - warn( - exp, - "for annotation %s, could not add %s=%s because %s", - annotation, - member, - exp, - e.getMessage()); - return null; - } - } - } - return builder.build(); - } else if (annotation instanceof SingleMemberAnnotationExpr) { - SingleMemberAnnotationExpr sglanno = (SingleMemberAnnotationExpr) annotation; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); - Expression valExpr = sglanno.getMemberValue(); - try { - builderAddElement(builder, "value", valExpr); - } catch (AnnotationFileParserException e) { - warn( - valExpr, - "for annotation %s, could not add value=%s because %s", - annotation, - valExpr, - e.getMessage()); - return null; - } - return builder.build(); - } else { - throw new BugInCF("AnnotationFileParser: unknown annotation type: " + annotation); - } - } - - /** - * Returns the value of {@code expr}. - * - * @param name the name of an annotation element/argument, used for diagnostic messages - * @param expr the expression to determine the value of - * @param valueKind the type of the result - * @return the value of {@code expr} - * @throws AnnotationFileParserException if a problem occurred getting the value - */ - private Object getValueOfExpressionInAnnotation( - String name, Expression expr, TypeKind valueKind) throws AnnotationFileParserException { - if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) { - VariableElement elem; - if (expr instanceof NameExpr) { - elem = findVariableElement((NameExpr) expr); - } else { - elem = findVariableElement((FieldAccessExpr) expr); - } - if (elem == null) { - throw new AnnotationFileParserException( - String.format("variable %s not found", expr)); - } - Object value = elem.getConstantValue() != null ? elem.getConstantValue() : elem; - if (value instanceof Number) { - return convert((Number) value, valueKind); - } else { - return value; - } - } else if (expr instanceof StringLiteralExpr) { - return ((StringLiteralExpr) expr).asString(); - } else if (expr instanceof BooleanLiteralExpr) { - return ((BooleanLiteralExpr) expr).getValue(); - } else if (expr instanceof CharLiteralExpr) { - return convert((int) ((CharLiteralExpr) expr).asChar(), valueKind); - } else if (expr instanceof DoubleLiteralExpr) { - // No conversion needed if the expression is a double, the annotation value must be a - // double, too. - return ((DoubleLiteralExpr) expr).asDouble(); - } else if (expr instanceof IntegerLiteralExpr) { - return convert(((IntegerLiteralExpr) expr).asNumber(), valueKind); - } else if (expr instanceof LongLiteralExpr) { - return convert(((LongLiteralExpr) expr).asNumber(), valueKind); - } else if (expr instanceof UnaryExpr) { - switch (expr.toString()) { - // Special-case the minimum values. Separately parsing a "-" and a value - // doesn't correctly handle the minimum values, because the absolute value of - // the smallest member of an integral type is larger than the largest value. - case "-9223372036854775808L": - case "-9223372036854775808l": - return convert(Long.MIN_VALUE, valueKind, false); - case "-2147483648": - return convert(Integer.MIN_VALUE, valueKind, false); - default: - if (((UnaryExpr) expr).getOperator() == UnaryExpr.Operator.MINUS) { - Object value = - getValueOfExpressionInAnnotation( - name, ((UnaryExpr) expr).getExpression(), valueKind); - if (value instanceof Number) { - return convert((Number) value, valueKind, true); - } - } - throw new AnnotationFileParserException( - "unexpected Unary annotation expression: " + expr); - } - } else if (expr instanceof ClassExpr) { - ClassExpr classExpr = (ClassExpr) expr; - @SuppressWarnings("signature") // Type.toString(): @FullyQualifiedName - @FullyQualifiedName String className = classExpr.getType().toString(); - if (importedTypes.containsKey(className)) { - return importedTypes.get(className).asType(); - } - TypeElement typeElement = findTypeOfName(className); - if (typeElement == null) { - throw new AnnotationFileParserException("unknown class name " + className); - } - - return typeElement.asType(); - } else if (expr instanceof NullLiteralExpr) { - throw new AnnotationFileParserException("illegal annotation value null, for " + name); - } else { - throw new AnnotationFileParserException("unexpected annotation expression: " + expr); - } - } - - /** - * Returns the TypeElement with the name {@code name}, if one exists. Otherwise, checks the - * class and package of {@code typeBeingParsed} for a class named {@code name}. - * - * @param name classname (simple, or Outer.Inner, or fully-qualified) - * @return the TypeElement for {@code name}, or null if not found - */ - @SuppressWarnings("signature:argument.type.incompatible") // string concatenation - private @Nullable TypeElement findTypeOfName(@FullyQualifiedName String name) { - String packageName = typeBeingParsed.packageName; - String packagePrefix = (packageName == null) ? "" : packageName + "."; - - // warn("findTypeOfName(%s), typeBeingParsed %s %s", name, packageName, enclosingClass); - - // As soon as typeElement is set to a non-null value, it will be returned. - TypeElement typeElement = getTypeElementOrNull(name); - if (typeElement == null && packageName != null) { - typeElement = getTypeElementOrNull(packagePrefix + name); - } - String enclosingClass = typeBeingParsed.className; - while (typeElement == null && enclosingClass != null) { - typeElement = getTypeElementOrNull(packagePrefix + enclosingClass + "." + name); - int lastDot = enclosingClass.lastIndexOf('.'); - if (lastDot == -1) { - break; - } else { - enclosingClass = enclosingClass.substring(0, lastDot); - } - } - if (typeElement == null && !"java.lang".equals(packageName)) { - typeElement = getTypeElementOrNull("java.lang." + name); - } - return typeElement; - } - - /** - * Converts {@code number} to {@code expectedKind}. - * - *


-     *   @interface Anno { long value(); }
-     *   @Anno(1)
-     * 
- * - * To properly build @Anno, the IntegerLiteralExpr "1" must be converted from an int to a long. - */ - private Object convert(Number number, TypeKind expectedKind) { - return convert(number, expectedKind, false); - } - - /** - * Converts {@code number} to {@code expectedKind}. The value converted is multiplied by -1 if - * {@code negate} is true - * - * @param number a Number value to be converted - * @param expectedKind one of type {byte, short, int, long, char, float, double} - * @param negate whether to negate the value of the Number Object while converting - * @return the converted Object - */ - private Object convert(Number number, TypeKind expectedKind, boolean negate) { - byte scalefactor = (byte) (negate ? -1 : 1); - switch (expectedKind) { - case BYTE: - return number.byteValue() * scalefactor; - case SHORT: - return number.shortValue() * scalefactor; - case INT: - return number.intValue() * scalefactor; - case LONG: - return number.longValue() * scalefactor; - case CHAR: - // It's not possible for `number` to be negative when `expectedkind` is a CHAR, and - // casting a negative value to char is illegal. - if (negate) { - throw new BugInCF( - "convert(%s, %s, %s): can't negate a char", - number, expectedKind, negate); - } - return (char) number.intValue(); - case FLOAT: - return number.floatValue() * scalefactor; - case DOUBLE: - return number.doubleValue() * scalefactor; - default: - throw new BugInCF("unexpected expectedKind: " + expectedKind); - } - } - - /** - * Adds an annotation element (argument) to {@code builder}. The element is a Java expression. - * - * @param builder the builder to side-effect - * @param name the element name - * @param expr the element value - * @throws AnnotationFileParserException if the expression cannot be parsed and added to {@code - * builder} - */ - private void builderAddElement(AnnotationBuilder builder, String name, Expression expr) - throws AnnotationFileParserException { - ExecutableElement var = builder.findElement(name); - TypeMirror declaredType = var.getReturnType(); - TypeKind valueKind; - if (declaredType.getKind() == TypeKind.ARRAY) { - valueKind = ((ArrayType) declaredType).getComponentType().getKind(); - } else { - valueKind = declaredType.getKind(); - } - if (expr instanceof ArrayInitializerExpr) { - if (declaredType.getKind() != TypeKind.ARRAY) { - throw new AnnotationFileParserException( - "unhandled annotation attribute type: " - + expr - + " and declaredType: " - + declaredType); - } - - List arrayExpressions = ((ArrayInitializerExpr) expr).getValues(); - Object[] values = new Object[arrayExpressions.size()]; - - for (int i = 0; i < arrayExpressions.size(); ++i) { - Expression eltExpr = arrayExpressions.get(i); - values[i] = getValueOfExpressionInAnnotation(name, eltExpr, valueKind); - } - builder.setValue(name, values); - } else { - Object value = getValueOfExpressionInAnnotation(name, expr, valueKind); - if (declaredType.getKind() == TypeKind.ARRAY) { - Object[] valueArray = {value}; - builder.setValue(name, valueArray); - } else { - builderSetValue(builder, name, value); - } - } - } - - /** - * Cast to non-array values so that correct the correct AnnotationBuilder#setValue method is - * called. (Different types of values are handled differently.) - * - * @param builder the builder to side-effect - * @param name the element name - * @param value the element value - */ - private void builderSetValue(AnnotationBuilder builder, String name, Object value) { - if (value instanceof Boolean) { - builder.setValue(name, (Boolean) value); - } else if (value instanceof Character) { - builder.setValue(name, (Character) value); - } else if (value instanceof Class) { - builder.setValue(name, (Class) value); - } else if (value instanceof Double) { - builder.setValue(name, (Double) value); - } else if (value instanceof Enum) { - builder.setValue(name, (Enum) value); - } else if (value instanceof Float) { - builder.setValue(name, (Float) value); - } else if (value instanceof Integer) { - builder.setValue(name, (Integer) value); - } else if (value instanceof Long) { - builder.setValue(name, (Long) value); - } else if (value instanceof Short) { - builder.setValue(name, (Short) value); - } else if (value instanceof String) { - builder.setValue(name, (String) value); - } else if (value instanceof TypeMirror) { - builder.setValue(name, (TypeMirror) value); - } else if (value instanceof VariableElement) { - builder.setValue(name, (VariableElement) value); - } else { - throw new BugInCF("unexpected builder value: %s", value); - } - } - - /** - * Mapping of a name access expression that has already been encountered to the resolved - * variable element. - */ - private final Map findVariableElementNameCache = new HashMap<>(); - - /** - * Returns the element for the given variable. - * - * @param nexpr the variable name - * @return the element for the given variable - */ - private @Nullable VariableElement findVariableElement(NameExpr nexpr) { - if (findVariableElementNameCache.containsKey(nexpr)) { - return findVariableElementNameCache.get(nexpr); - } - - VariableElement res = null; - boolean importFound = false; - for (String imp : importedConstants) { - IPair<@FullyQualifiedName String, String> partitionedName = - AnnotationFileUtil.partitionQualifiedName(imp); - String typeName = partitionedName.first; - String fieldName = partitionedName.second; - if (fieldName.equals(nexpr.getNameAsString())) { - TypeElement enclType = - getTypeElement( - typeName, - String.format( - "enclosing type of static import %s not found", fieldName), - nexpr); - - if (enclType == null) { - return null; - } else { - importFound = true; - res = findFieldElement(enclType, fieldName, nexpr); - break; - } - } - } - - if (res == null) { - if (importFound) { - // TODO: Is this warning redundant? Maybe imported but invalid types or fields will - // have warnings from above. - stubWarnNotFound(nexpr, nexpr.getName() + " was imported but not found"); - } else { - stubWarnNotFound(nexpr, "static field " + nexpr.getName() + " is not imported"); - } - } - - findVariableElementNameCache.put(nexpr, res); - return res; - } - - /** - * Mapping of a field access expression that has already been encountered to the resolved - * variable element. - */ - private final Map findVariableElementFieldCache = - new HashMap<>(); - - /** - * Returns the VariableElement for the given field access. - * - * @param faexpr a field access expression - * @return the VariableElement for the given field access - */ - @SuppressWarnings("signature:argument.type.incompatible") // string manipulation - private @Nullable VariableElement findVariableElement(FieldAccessExpr faexpr) { - if (findVariableElementFieldCache.containsKey(faexpr)) { - return findVariableElementFieldCache.get(faexpr); - } - TypeElement rcvElt = elements.getTypeElement(faexpr.getScope().toString()); - if (rcvElt == null) { - // Search importedConstants for full annotation name. - for (String imp : importedConstants) { - // TODO: should this use AnnotationFileUtil.partitionQualifiedName? - String[] importDelimited = imp.split("\\."); - if (importDelimited[importDelimited.length - 1].equals( - faexpr.getScope().toString())) { - StringBuilder fullAnnotation = new StringBuilder(); - for (int i = 0; i < importDelimited.length - 1; i++) { - fullAnnotation.append(importDelimited[i]); - fullAnnotation.append('.'); - } - fullAnnotation.append(faexpr.getScope().toString()); - rcvElt = elements.getTypeElement(fullAnnotation); - break; - } - } - - if (rcvElt == null) { - stubWarnNotFound(faexpr, "type " + faexpr.getScope() + " not found"); - return null; - } - } - - VariableElement res = findFieldElement(rcvElt, faexpr.getNameAsString(), faexpr); - findVariableElementFieldCache.put(faexpr, res); - return res; - } - - /////////////////////////////////////////////////////////////////////////// - /// Map utilities - /// - - /** - * Just like Map.put, but does not override any existing value in the map. - * - * @param the key type - * @param the value type - * @param m a map - * @param key a key - * @param value the value to associate with the key, if the key isn't already in the map - */ - public static void putIfAbsent(Map m, K key, V value) { - if (key == null) { - throw new BugInCF("AnnotationFileParser: key is null for value " + value); - } - if (!m.containsKey(key)) { - m.put(key, value); - } - } - - /** - * If the key is already in the {@code annotationFileAnnos.declAnnos} map, then add the annos to - * the map value. Otherwise put the key and the annos in the map. - * - * @param key a name (actually declaration element string) - * @param annos the set of declaration annotations on it, as written in the annotation file; is - * not modified - */ - private void putOrAddToDeclAnnos(String key, AnnotationMirrorSet annos) { - AnnotationMirrorSet stored = annotationFileAnnos.declAnnos.get(key); - if (stored == null) { - annotationFileAnnos.declAnnos.put(key, new AnnotationMirrorSet(annos)); - } else { - // TODO: Currently, we assume there can be at most one annotation of the same name - // in both `stored` and `annos`. Maybe we should consider the situation of multiple - // entries having the same name. - AnnotationMirrorSet annotationsToAdd = annos; - if (fileType == AnnotationFileType.JDK_STUB) { - // JDK annotations should not replace any annotation of the same type. - annotationsToAdd = - annos.stream() - .filter(am -> !AnnotationUtils.containsSameByName(stored, am)) - .collect(Collectors.toCollection(AnnotationMirrorSet::new)); - } else { - // Annotations that are not from the annotated JDK may replace existing - // annotations of the same type. - stored.removeIf(am -> AnnotationUtils.containsSameByName(annos, am)); - } - stored.addAll(annotationsToAdd); - } - } - - /** - * Just like Map.put, but modifies an existing annotated type for the given key in {@code m}. If - * {@code m} already has an annotated type for {@code key}, each annotation in {@code newType} - * will replace annotations from the same hierarchy at the same location in the existing - * annotated type. Annotations in other hierarchies will be preserved. - * - * @param m the map to put the new type into - * @param key the key for the map - * @param newType the new type for the key - */ - private void putMerge( - Map m, Element key, AnnotatedTypeMirror newType) { - if (key == null) { - throw new BugInCF("AnnotationFileParser: key is null"); - } - if (m.containsKey(key)) { - AnnotatedTypeMirror existingType = m.get(key); - // If the newType is from a JDK stub file, then keep the existing type. This - // way user-supplied stub files override JDK stub files. - // This works because the JDK is always parsed last, on demand, after all other stub - // files. - if (fileType != AnnotationFileType.JDK_STUB) { - atypeFactory.replaceAnnotations(newType, existingType); - } - // existingType is already in the map, so no need to put into m. - } else { - m.put(key, newType); - } - } - - /** - * Just like Map.putAll, but modifies existing values using {@link #putIfAbsent(Map, Object, - * Object)}. - * - * @param m the destination map - * @param m2 the source map - * @param the key type for the maps - * @param the value type for the maps - */ - public static void putAllNew(Map m, Map m2) { - for (Map.Entry e2 : m2.entrySet()) { - putIfAbsent(m, e2.getKey(), e2.getValue()); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Issue warnings - /// - - /** The warnings that have been issued so far. */ - private static final Set warnings = new HashSet<>(); - - /** - * Issues the given warning about missing elements, only if it has not been previously issued - * and the -AstubWarnIfNotFound command-line argument was passed. - * - * @param astNode where to report errors - * @param warning warning to print - */ - private void stubWarnNotFound(NodeWithRange astNode, String warning) { - stubWarnNotFound(astNode, warning, warnIfNotFound); - } - - /** - * Issues the given warning about missing elements, only if it has not been previously issued - * and the {@code warnIfNotFound} formal parameter is true. - * - * @param astNode where to report errors - * @param warning warning to print - * @param warnIfNotFound whether to print warnings about types/members that were not found - */ - private void stubWarnNotFound( - NodeWithRange astNode, String warning, boolean warnIfNotFound) { - if (warnIfNotFound || debugAnnotationFileParser) { - warn(astNode, warning); - } - } - - /** - * Issues the given warning about overwriting bytecode, only if it has not been previously - * issued and the -AstubWarnIfOverwritesBytecode command-line argument was passed. - * - * @param astNode where to report errors - * @param message the warning message to print - */ - @SuppressWarnings("UnusedMethod") // not currently used - private void stubWarnOverwritesBytecode(NodeWithRange astNode, String message) { - if (warnIfStubOverwritesBytecode || debugAnnotationFileParser) { - warn(astNode, message); - } - } - - /** - * Issues a warning, only if it has not been previously issued. - * - * @param astNode where to report errors - * @param warning a format string - * @param args the arguments for {@code warning} - */ - @FormatMethod - private void warn(@Nullable NodeWithRange astNode, String warning, Object... args) { - if (!fileType.isBuiltIn()) { - warn(astNode, String.format(warning, args)); - } - } - - /** - * Issues a warning, only if it has not been previously issued. - * - * @param astNode where to report errors - * @param warning a warning message - */ - private void warn(@Nullable NodeWithRange astNode, String warning) { - if (fileType != AnnotationFileType.JDK_STUB) { - if (warnings.add(warning)) { - processingEnv - .getMessager() - .printMessage(stubWarnDiagnosticKind, fileAndLine(astNode) + warning); - } - } - } - - /** - * If {@code warning} hasn't been printed yet, and {@link #debugAnnotationFileParser} is true, - * prints the given warning as a diagnostic message. - * - * @param fmt format string - * @param args arguments to the format string - */ - @FormatMethod - private void stubDebug(String fmt, Object... args) { - if (debugAnnotationFileParser) { - String warning = String.format(fmt, args); - if (warnings.add(warning)) { - System.out.flush(); - SystemPlume.sleep(1); - processingEnv - .getMessager() - .printMessage( - javax.tools.Diagnostic.Kind.NOTE, - "AnnotationFileParser: " + warning); - System.out.flush(); - SystemPlume.sleep(1); - } - } - } - - /** - * If {@code warning} hasn't been printed yet, prints the given warning as a diagnostic message. - * Ignores {@code debugAnnotationFileParser}. - * - * @param processingEnv the processing environment - * @param fmt format string - * @param args arguments to the format string - */ - @FormatMethod - /*package-private*/ static void stubDebugStatic( - ProcessingEnvironment processingEnv, String fmt, Object... args) { - String warning = String.format(fmt, args); - if (warnings.add(warning)) { - System.out.flush(); - SystemPlume.sleep(1); - processingEnv - .getMessager() - .printMessage( - javax.tools.Diagnostic.Kind.NOTE, "AnnotationFileParser: " + warning); - System.out.flush(); - SystemPlume.sleep(1); - } - } - - /** - * After obtaining the JavaParser AST for an ajava file and the javac tree for its corresponding - * Java file, walks both in tandem. For each program construct with annotations, stores the - * annotations from the ajava file in {@link #annotationFileAnnos} by calling the process method - * corresponding to that construct, such as {@link #processCallableDeclaration} or {@link - * #processField}. - */ - private class AjavaAnnotationCollectorVisitor extends DefaultJointVisitor { - - /** Default constructor. */ - private AjavaAnnotationCollectorVisitor() {} - - // This method overrides super.visitCompilationUnit() to prevent parsing import - // statements. Requiring imports in both ajava file and the source file to be - // exactly same is error-prone and unnecessary. - @Override - public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { - CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); - processCompilationUnit(javacTree, node); - visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); - visitLists(javacTree.getTypeDecls(), node.getTypes()); - return null; - } - - @Override - public Void visitClass(ClassTree javacTree, Node javaParserNode) { - List typeDeclTypeParameters = null; - boolean shouldProcessTypeDecl = - javaParserNode instanceof TypeDeclaration - && !(javaParserNode instanceof AnnotationDeclaration); - Optional typeDeclName = Optional.empty(); - boolean callListener = false; - - if (shouldProcessTypeDecl) { - TypeDeclaration typeDecl = (TypeDeclaration) javaParserNode; - typeDeclName = typeDecl.getFullyQualifiedName(); - callListener = typeDeclName.isPresent() && typeDecl.isTopLevelType(); - } - - if (callListener) { - @SuppressWarnings("optional:method.invocation.invalid") // from callListener - String typeDeclNameString = typeDeclName.get(); - fileElementTypes.preProcessTopLevelType(typeDeclNameString); - } - try { - if (shouldProcessTypeDecl) { - typeDeclTypeParameters = - processTypeDecl((TypeDeclaration) javaParserNode, null, javacTree); - } - super.visitClass(javacTree, javaParserNode); - } finally { - if (typeDeclTypeParameters != null) { - typeParameters.removeAll(typeDeclTypeParameters); - } - if (callListener) { - @SuppressWarnings("optional:method.invocation.invalid") // from callListener - String typeDeclNameString = typeDeclName.get(); - fileElementTypes.postProcessTopLevelType(typeDeclNameString); - } - } - - return null; - } - - @Override - public Void visitVariable(VariableTree javacTree, Node javaParserNode) { - VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); - if (elt != null) { - if (elt.getKind() == ElementKind.FIELD) { - VariableDeclarator varDecl = (VariableDeclarator) javaParserNode; - processField((FieldDeclaration) varDecl.getParentNode().get(), elt); - } - - if (elt.getKind() == ElementKind.ENUM_CONSTANT) { - processEnumConstant((EnumConstantDeclaration) javaParserNode, elt); - } - } - - super.visitVariable(javacTree, javaParserNode); - return null; - } - - @Override - public Void visitMethod(MethodTree javacTree, Node javaParserNode) { - List variablesToClear = null; - Element elt = TreeUtils.elementFromDeclaration(javacTree); - if (javaParserNode instanceof CallableDeclaration) { - variablesToClear = - processCallableDeclaration( - (CallableDeclaration) javaParserNode, (ExecutableElement) elt); - } - - super.visitMethod(javacTree, javaParserNode); - if (variablesToClear != null) { - typeParameters.removeAll(variablesToClear); - } - - return null; - } - } - - /** - * Return the prefix for a warning line: A file name, line number, and column number. - * - * @param astNode where to report errors - * @return file name, line number, and column number - */ - private String fileAndLine(NodeWithRange astNode) { - String filenamePrinted = - (processingEnv.getOptions().containsKey("nomsgtext") - ? new File(filename).getName() - : filename); - - Optional begin = astNode == null ? Optional.empty() : astNode.getBegin(); - String lineAndColumn = (begin.isPresent() ? begin.get() + ":" : ""); - return filenamePrinted + ":" + lineAndColumn + " "; - } - - /** An exception indicating a problem while parsing an annotation file. */ - public static class AnnotationFileParserException extends Exception { - - private static final long serialVersionUID = 20201222; - - /** - * Create a new AnnotationFileParserException. - * - * @param message a description of the problem - */ - AnnotationFileParserException(String message) { - super(message); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Parse state - /// - - /** Represents a class: its package name and name (including outer class names if any). */ - private static class FqName { - /** Name of the package being parsed, or null. */ - public final @Nullable String packageName; - - /** - * Name of the type being parsed. Includes outer class names if any. Null if the parser has - * parsed a package declaration but has not yet gotten to a type declaration. - */ - public final @Nullable String className; - - /** - * Create a new FqName, which represents a class. - * - * @param packageName name of the package, or null - * @param className unqualified name of the type, including outer class names if any. May be - * null. - */ - public FqName(@Nullable String packageName, @Nullable String className) { - this.packageName = packageName; - this.className = className; - } - - /** Fully-qualified name of the class. */ - @Override - @SuppressWarnings("signature") // string concatenation - public @FullyQualifiedName String toString() { - if (packageName == null) { - return className; - } else { - return packageName + "." + className; - } - } + /** Fully-qualified name of the class. */ + @Override + @SuppressWarnings("signature") // string concatenation + public @FullyQualifiedName String toString() { + if (packageName == null) { + return className; + } else { + return packageName + "." + className; + } } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java index 14a66f4a009..a120d91d1e7 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java @@ -5,13 +5,13 @@ /** Interface for sources of stub data. */ public interface AnnotationFileResource { - /** - * Returns a user-friendly description of the resource (e.g. a filesystem path). - * - * @return a description of the resource - */ - String getDescription(); + /** + * Returns a user-friendly description of the resource (e.g. a filesystem path). + * + * @return a description of the resource + */ + String getDescription(); - /** Returns a stream for reading the contents of the resource. */ - InputStream getInputStream() throws IOException; + /** Returns a stream for reading the contents of the resource. */ + InputStream getInputStream() throws IOException; } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java index f7001b6fb5d..50718c08213 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java @@ -15,13 +15,6 @@ import com.github.javaparser.ast.type.VoidType; import com.github.javaparser.ast.type.WildcardType; import com.github.javaparser.ast.visitor.SimpleVoidVisitor; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.plumelib.util.IPair; - import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -32,482 +25,484 @@ import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; - import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.plumelib.util.IPair; /** Utility class for annotation files (stub files and ajava files). */ public class AnnotationFileUtil { + /** + * The types of files that can contain annotations. Also indicates the file's source, such as from + * the JDK, built in, or from the command line. + * + *

Stub files have extension ".astub". Ajava files have extension ".ajava". + */ + public enum AnnotationFileType { + /** Stub file in the annotated JDK. */ + JDK_STUB, + /** Stub file built into a checker. */ + BUILTIN_STUB, + /** Stub file provided on command line. */ + COMMAND_LINE_STUB, + /** Ajava file being parsed as if it is a stub file. */ + AJAVA_AS_STUB, + /** Ajava file provided on command line. */ + AJAVA; + /** - * The types of files that can contain annotations. Also indicates the file's source, such as - * from the JDK, built in, or from the command line. + * Returns true if this represents a stub file. * - *

Stub files have extension ".astub". Ajava files have extension ".ajava". + * @return true if this represents a stub file */ - public enum AnnotationFileType { - /** Stub file in the annotated JDK. */ - JDK_STUB, - /** Stub file built into a checker. */ - BUILTIN_STUB, - /** Stub file provided on command line. */ - COMMAND_LINE_STUB, - /** Ajava file being parsed as if it is a stub file. */ - AJAVA_AS_STUB, - /** Ajava file provided on command line. */ - AJAVA; - - /** - * Returns true if this represents a stub file. - * - * @return true if this represents a stub file - */ - public boolean isStub() { - switch (this) { - case JDK_STUB: - case BUILTIN_STUB: - case COMMAND_LINE_STUB: - case AJAVA_AS_STUB: - return true; - case AJAVA: - return false; - default: - throw new BugInCF("unhandled case " + this); - } - } - - /** - * Returns true if this annotation file is built-in (not provided on the command line). - * - * @return true if this annotation file is built-in (not provided on the command line) - */ - public boolean isBuiltIn() { - switch (this) { - case JDK_STUB: - case BUILTIN_STUB: - return true; - case COMMAND_LINE_STUB: - case AJAVA_AS_STUB: - case AJAVA: - return false; - default: - throw new BugInCF("unhandled case " + this); - } - } - - /** - * Returns true if this annotation file was provided on the command line (not built-in). - * - * @return true if this annotation file was provided on the command line (not built-in) - */ - public boolean isCommandLine() { - switch (this) { - case JDK_STUB: - case BUILTIN_STUB: - return false; - case COMMAND_LINE_STUB: - case AJAVA_AS_STUB: - case AJAVA: - return true; - default: - throw new BugInCF("unhandled case " + this); - } - } + public boolean isStub() { + switch (this) { + case JDK_STUB: + case BUILTIN_STUB: + case COMMAND_LINE_STUB: + case AJAVA_AS_STUB: + return true; + case AJAVA: + return false; + default: + throw new BugInCF("unhandled case " + this); + } } /** - * Finds the type declaration with the given class name in a StubUnit. + * Returns true if this annotation file is built-in (not provided on the command line). * - * @param className fully qualified name of the type declaration to find - * @param indexFile a StubUnit to search - * @return the declaration in {@code indexFile} with {@code className} if it exists, null - * otherwise. + * @return true if this annotation file is built-in (not provided on the command line) */ - /*package-private*/ static @Nullable TypeDeclaration findDeclaration( - String className, StubUnit indexFile) { - int indexOfDot = className.lastIndexOf('.'); - - if (indexOfDot == -1) { - // classes not within a package needs to be the first in the index file - CompilationUnit cu = indexFile.getCompilationUnits().get(0); - assert !cu.getPackageDeclaration().isPresent(); - return findDeclaration(className, cu); - } - - String packageName = className.substring(0, indexOfDot); - String simpleName = className.substring(indexOfDot + 1); - - for (CompilationUnit cu : indexFile.getCompilationUnits()) { - if (cu.getPackageDeclaration().isPresent() - && cu.getPackageDeclaration().get().getNameAsString().equals(packageName)) { - TypeDeclaration type = findDeclaration(simpleName, cu); - if (type != null) { - return type; - } - } - } - - // Couldn't find it - return null; + public boolean isBuiltIn() { + switch (this) { + case JDK_STUB: + case BUILTIN_STUB: + return true; + case COMMAND_LINE_STUB: + case AJAVA_AS_STUB: + case AJAVA: + return false; + default: + throw new BugInCF("unhandled case " + this); + } } - /*package-private*/ static TypeDeclaration findDeclaration( - TypeElement type, StubUnit indexFile) { - return findDeclaration(type.getQualifiedName().toString(), indexFile); + /** + * Returns true if this annotation file was provided on the command line (not built-in). + * + * @return true if this annotation file was provided on the command line (not built-in) + */ + public boolean isCommandLine() { + switch (this) { + case JDK_STUB: + case BUILTIN_STUB: + return false; + case COMMAND_LINE_STUB: + case AJAVA_AS_STUB: + case AJAVA: + return true; + default: + throw new BugInCF("unhandled case " + this); + } } - - /*package-private*/ static @Nullable FieldDeclaration findDeclaration( - VariableElement field, StubUnit indexFile) { - TypeDeclaration type = - findDeclaration((TypeElement) field.getEnclosingElement(), indexFile); - if (type == null) { - return null; - } - - for (BodyDeclaration member : type.getMembers()) { - if (!(member instanceof FieldDeclaration)) { - continue; - } - FieldDeclaration decl = (FieldDeclaration) member; - for (VariableDeclarator var : decl.getVariables()) { - if (toString(var).equals(field.getSimpleName().toString())) { - return decl; - } - } - } - return null; + } + + /** + * Finds the type declaration with the given class name in a StubUnit. + * + * @param className fully qualified name of the type declaration to find + * @param indexFile a StubUnit to search + * @return the declaration in {@code indexFile} with {@code className} if it exists, null + * otherwise. + */ + /*package-private*/ static @Nullable TypeDeclaration findDeclaration( + String className, StubUnit indexFile) { + int indexOfDot = className.lastIndexOf('.'); + + if (indexOfDot == -1) { + // classes not within a package needs to be the first in the index file + CompilationUnit cu = indexFile.getCompilationUnits().get(0); + assert !cu.getPackageDeclaration().isPresent(); + return findDeclaration(className, cu); } - /*package-private*/ static @Nullable BodyDeclaration findDeclaration( - ExecutableElement method, StubUnit indexFile) { - TypeDeclaration type = - findDeclaration((TypeElement) method.getEnclosingElement(), indexFile); - if (type == null) { - return null; - } + String packageName = className.substring(0, indexOfDot); + String simpleName = className.substring(indexOfDot + 1); - String methodRep = toString(method); - - for (BodyDeclaration member : type.getMembers()) { - if (member instanceof MethodDeclaration) { - if (toString((MethodDeclaration) member).equals(methodRep)) { - return member; - } - } else if (member instanceof ConstructorDeclaration) { - if (toString((ConstructorDeclaration) member).equals(methodRep)) { - return member; - } - } + for (CompilationUnit cu : indexFile.getCompilationUnits()) { + if (cu.getPackageDeclaration().isPresent() + && cu.getPackageDeclaration().get().getNameAsString().equals(packageName)) { + TypeDeclaration type = findDeclaration(simpleName, cu); + if (type != null) { + return type; } - return null; + } } - /*package-private*/ static @Nullable TypeDeclaration findDeclaration( - String simpleName, CompilationUnit cu) { - for (TypeDeclaration type : cu.getTypes()) { - if (simpleName.equals(type.getNameAsString())) { - return type; - } - } - // Couldn't find it - return null; - } + // Couldn't find it + return null; + } - /*package-private*/ static String toString(MethodDeclaration method) { - return ElementPrinter.toString(method); - } + /*package-private*/ static TypeDeclaration findDeclaration( + TypeElement type, StubUnit indexFile) { + return findDeclaration(type.getQualifiedName().toString(), indexFile); + } - /*package-private*/ static String toString(ConstructorDeclaration constructor) { - return ElementPrinter.toString(constructor); + /*package-private*/ static @Nullable FieldDeclaration findDeclaration( + VariableElement field, StubUnit indexFile) { + TypeDeclaration type = findDeclaration((TypeElement) field.getEnclosingElement(), indexFile); + if (type == null) { + return null; } - /*package-private*/ static String toString(VariableDeclarator field) { - return field.getNameAsString(); + for (BodyDeclaration member : type.getMembers()) { + if (!(member instanceof FieldDeclaration)) { + continue; + } + FieldDeclaration decl = (FieldDeclaration) member; + for (VariableDeclarator var : decl.getVariables()) { + if (toString(var).equals(field.getSimpleName().toString())) { + return decl; + } + } } - - /*package-private*/ static String toString(FieldDeclaration field) { - assert field.getVariables().size() == 1; - return toString(field.getVariables().get(0)); + return null; + } + + /*package-private*/ static @Nullable BodyDeclaration findDeclaration( + ExecutableElement method, StubUnit indexFile) { + TypeDeclaration type = + findDeclaration((TypeElement) method.getEnclosingElement(), indexFile); + if (type == null) { + return null; } - /*package-private*/ static String toString(VariableElement element) { - assert element.getKind().isField(); - return element.getSimpleName().toString(); - } + String methodRep = toString(method); - /*package-private*/ static @Nullable String toString(Element element) { - if (element instanceof ExecutableElement) { - return toString((ExecutableElement) element); - } else if (element instanceof VariableElement) { - return toString((VariableElement) element); - } else { - return null; + for (BodyDeclaration member : type.getMembers()) { + if (member instanceof MethodDeclaration) { + if (toString((MethodDeclaration) member).equals(methodRep)) { + return member; + } + } else if (member instanceof ConstructorDeclaration) { + if (toString((ConstructorDeclaration) member).equals(methodRep)) { + return member; } + } } - - /** - * Split a name (which comes from an import statement) into the part before the last period and - * the part after the last period. - * - * @param imported the name to split - * @return a pair of the type name and the field name - */ - @SuppressWarnings("signature") // string parsing - public static IPair<@FullyQualifiedName String, String> partitionQualifiedName( - String imported) { - @FullyQualifiedName String typeName = imported.substring(0, imported.lastIndexOf(".")); - String name = imported.substring(imported.lastIndexOf(".") + 1); - IPair typeParts = IPair.of(typeName, name); - return typeParts; + return null; + } + + /*package-private*/ static @Nullable TypeDeclaration findDeclaration( + String simpleName, CompilationUnit cu) { + for (TypeDeclaration type : cu.getTypes()) { + if (simpleName.equals(type.getNameAsString())) { + return type; + } + } + // Couldn't find it + return null; + } + + /*package-private*/ static String toString(MethodDeclaration method) { + return ElementPrinter.toString(method); + } + + /*package-private*/ static String toString(ConstructorDeclaration constructor) { + return ElementPrinter.toString(constructor); + } + + /*package-private*/ static String toString(VariableDeclarator field) { + return field.getNameAsString(); + } + + /*package-private*/ static String toString(FieldDeclaration field) { + assert field.getVariables().size() == 1; + return toString(field.getVariables().get(0)); + } + + /*package-private*/ static String toString(VariableElement element) { + assert element.getKind().isField(); + return element.getSimpleName().toString(); + } + + /*package-private*/ static @Nullable String toString(Element element) { + if (element instanceof ExecutableElement) { + return toString((ExecutableElement) element); + } else if (element instanceof VariableElement) { + return toString((VariableElement) element); + } else { + return null; + } + } + + /** + * Split a name (which comes from an import statement) into the part before the last period and + * the part after the last period. + * + * @param imported the name to split + * @return a pair of the type name and the field name + */ + @SuppressWarnings("signature") // string parsing + public static IPair<@FullyQualifiedName String, String> partitionQualifiedName(String imported) { + @FullyQualifiedName String typeName = imported.substring(0, imported.lastIndexOf(".")); + String name = imported.substring(imported.lastIndexOf(".") + 1); + IPair typeParts = IPair.of(typeName, name); + return typeParts; + } + + private static final class ElementPrinter extends SimpleVoidVisitor { + public static String toString(Node n) { + ElementPrinter printer = new ElementPrinter(); + n.accept(printer, null); + return printer.getOutput(); } - private static final class ElementPrinter extends SimpleVoidVisitor { - public static String toString(Node n) { - ElementPrinter printer = new ElementPrinter(); - n.accept(printer, null); - return printer.getOutput(); - } - - private final StringBuilder sb = new StringBuilder(); + private final StringBuilder sb = new StringBuilder(); - public String getOutput() { - return sb.toString(); - } + public String getOutput() { + return sb.toString(); + } - @Override - public void visit(ConstructorDeclaration n, Void arg) { - sb.append(""); - - sb.append("("); - if (n.getParameters() != null) { - for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { - Parameter p = i.next(); - p.accept(this, arg); - - if (i.hasNext()) { - sb.append(","); - } - } - } - sb.append(")"); - } + @Override + public void visit(ConstructorDeclaration n, Void arg) { + sb.append(""); - @Override - public void visit(MethodDeclaration n, Void arg) { - sb.append(n.getName()); - - sb.append("("); - if (n.getParameters() != null) { - for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { - Parameter p = i.next(); - p.accept(this, arg); - - if (i.hasNext()) { - sb.append(","); - } - } - } - sb.append(")"); - } - - @Override - public void visit(Parameter n, Void arg) { - n.getType().accept(this, arg); - if (n.isVarArgs()) { - sb.append("[]"); - } - } + sb.append("("); + if (n.getParameters() != null) { + for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { + Parameter p = i.next(); + p.accept(this, arg); - // Types - @Override - public void visit(ClassOrInterfaceType n, Void arg) { - sb.append(n.getName()); + if (i.hasNext()) { + sb.append(","); + } } + } + sb.append(")"); + } - @Override - public void visit(PrimitiveType n, Void arg) { - switch (n.getType()) { - case BOOLEAN: - sb.append("boolean"); - break; - case BYTE: - sb.append("byte"); - break; - case CHAR: - sb.append("char"); - break; - case DOUBLE: - sb.append("double"); - break; - case FLOAT: - sb.append("float"); - break; - case INT: - sb.append("int"); - break; - case LONG: - sb.append("long"); - break; - case SHORT: - sb.append("short"); - break; - default: - throw new BugInCF("AnnotationFileUtil: unknown type: " + n.getType()); - } - } + @Override + public void visit(MethodDeclaration n, Void arg) { + sb.append(n.getName()); - @Override - public void visit(com.github.javaparser.ast.type.ArrayType n, Void arg) { - n.getComponentType().accept(this, arg); - sb.append("[]"); - } - - @Override - public void visit(VoidType n, Void arg) { - sb.append("void"); - } + sb.append("("); + if (n.getParameters() != null) { + for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { + Parameter p = i.next(); + p.accept(this, arg); - @Override - public void visit(WildcardType n, Void arg) { - // We don't write type arguments - // TODO: Why? - throw new BugInCF("AnnotationFileUtil: don't print type args"); + if (i.hasNext()) { + sb.append(","); + } } + } + sb.append(")"); } - /** - * Return annotation files found at a given file system location (does not look on classpath). - * - * @param location an annotation file (stub file or ajava file), a jarfile, or a directory. Look - * for it as an absolute file and relative to the current directory. - * @param fileType file type of files to collect - * @return annotation files with the given file type found in the file system (does not look on - * classpath). Returns null if the file system location does not exist; the caller may wish - * to issue a warning in that case. - */ - public static @Nullable List allAnnotationFiles( - String location, AnnotationFileType fileType) { - File file = new File(location); - if (file.exists()) { - List resources = new ArrayList<>(); - addAnnotationFilesToList(file, resources, fileType); - return resources; - } + @Override + public void visit(Parameter n, Void arg) { + n.getType().accept(this, arg); + if (n.isVarArgs()) { + sb.append("[]"); + } + } - // The file doesn't exist. Maybe it is relative to the current working directory, so try - // that. - file = new File(System.getProperty("user.dir"), location); - if (file.exists()) { - List resources = new ArrayList<>(); - addAnnotationFilesToList(file, resources, fileType); - return resources; - } + // Types + @Override + public void visit(ClassOrInterfaceType n, Void arg) { + sb.append(n.getName()); + } - return null; + @Override + public void visit(PrimitiveType n, Void arg) { + switch (n.getType()) { + case BOOLEAN: + sb.append("boolean"); + break; + case BYTE: + sb.append("byte"); + break; + case CHAR: + sb.append("char"); + break; + case DOUBLE: + sb.append("double"); + break; + case FLOAT: + sb.append("float"); + break; + case INT: + sb.append("int"); + break; + case LONG: + sb.append("long"); + break; + case SHORT: + sb.append("short"); + break; + default: + throw new BugInCF("AnnotationFileUtil: unknown type: " + n.getType()); + } } - /** - * Returns true if the given file is an annotation file of the given type. - * - * @param f the file to check - * @param fileType the type of file to check against - * @return true if {@code f} is a file with file type matching {@code fileType}, false otherwise - */ - private static boolean isAnnotationFile(File f, AnnotationFileType fileType) { - return f.isFile() && isAnnotationFile(f.getName(), fileType); + @Override + public void visit(com.github.javaparser.ast.type.ArrayType n, Void arg) { + n.getComponentType().accept(this, arg); + sb.append("[]"); } - /** - * Returns true if the given file is an annotation file of the given kind. - * - * @param path a file - * @param fileType the type of file to check against - * @return true if {@code path} represents a file with file type matching {@code fileType}, - * false otherwise - */ - private static boolean isAnnotationFile(String path, AnnotationFileType fileType) { - return path.endsWith(fileType.isStub() ? ".astub" : ".ajava"); + @Override + public void visit(VoidType n, Void arg) { + sb.append("void"); } - private static boolean isJar(File f) { - return f.isFile() && f.getName().endsWith(".jar"); + @Override + public void visit(WildcardType n, Void arg) { + // We don't write type arguments + // TODO: Why? + throw new BugInCF("AnnotationFileUtil: don't print type args"); + } + } + + /** + * Return annotation files found at a given file system location (does not look on classpath). + * + * @param location an annotation file (stub file or ajava file), a jarfile, or a directory. Look + * for it as an absolute file and relative to the current directory. + * @param fileType file type of files to collect + * @return annotation files with the given file type found in the file system (does not look on + * classpath). Returns null if the file system location does not exist; the caller may wish to + * issue a warning in that case. + */ + public static @Nullable List allAnnotationFiles( + String location, AnnotationFileType fileType) { + File file = new File(location); + if (file.exists()) { + List resources = new ArrayList<>(); + addAnnotationFilesToList(file, resources, fileType); + return resources; } - /** - * Side-effects {@code resources} by adding annotation files of the given file type to it. - * - * @param location an annotation file (a stub file or ajava file), a jarfile, or a directory. If - * a stub file or ajava file, add it to the {@code resources} list. If a jarfile, use all - * annotation files (of type {@code fileType}) contained in it. If a directory, recurse on - * all files contained in it. - * @param resources the list to add the found files to - * @param fileType type of annotation files to add - */ - @SuppressWarnings({ - "JdkObsolete", // JarFile.entries() - "nullness:argument", // inference failed in Arrays.sort - "builder:required.method.not.called" // ownership passed to list of - // JarEntryAnnotationFileResource, where `file` appears in every element of the list - }) - private static void addAnnotationFilesToList( - File location, List resources, AnnotationFileType fileType) { - if (isAnnotationFile(location, fileType)) { - resources.add(new FileAnnotationFileResource(location)); - } else if (isJar(location)) { - JarFile file; - try { - file = new JarFile(location); - } catch (IOException e) { - System.err.println("AnnotationFileUtil: could not process JAR file: " + location); - return; - } - Enumeration entries = file.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (isAnnotationFile(entry.getName(), fileType)) { - resources.add(new JarEntryAnnotationFileResource(file, entry)); - } - } - - } else if (location.isDirectory()) { - File[] directoryContents = location.listFiles(); - Arrays.sort(directoryContents, Comparator.comparing(File::getName)); - for (File enclosed : directoryContents) { - addAnnotationFilesToList(enclosed, resources, fileType); - } - } + // The file doesn't exist. Maybe it is relative to the current working directory, so try + // that. + file = new File(System.getProperty("user.dir"), location); + if (file.exists()) { + List resources = new ArrayList<>(); + addAnnotationFilesToList(file, resources, fileType); + return resources; } - /** - * Returns true if the given {@link ExecutableElement} is the canonical constructor of a record - * (i.e., the parameter types of the constructor correspond to the parameter types of the record - * components, ignoring annotations). - * - * @param elt the constructor/method to check - * @param types the Types instance to use for comparing types - * @return true if elt is the canonical constructor of the record containing it - */ - public static boolean isCanonicalConstructor(ExecutableElement elt, Types types) { - if (elt.getKind() != ElementKind.CONSTRUCTOR) { - return false; - } - TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); - if (!ElementUtils.isRecordElement(enclosing)) { - return false; + return null; + } + + /** + * Returns true if the given file is an annotation file of the given type. + * + * @param f the file to check + * @param fileType the type of file to check against + * @return true if {@code f} is a file with file type matching {@code fileType}, false otherwise + */ + private static boolean isAnnotationFile(File f, AnnotationFileType fileType) { + return f.isFile() && isAnnotationFile(f.getName(), fileType); + } + + /** + * Returns true if the given file is an annotation file of the given kind. + * + * @param path a file + * @param fileType the type of file to check against + * @return true if {@code path} represents a file with file type matching {@code fileType}, false + * otherwise + */ + private static boolean isAnnotationFile(String path, AnnotationFileType fileType) { + return path.endsWith(fileType.isStub() ? ".astub" : ".ajava"); + } + + private static boolean isJar(File f) { + return f.isFile() && f.getName().endsWith(".jar"); + } + + /** + * Side-effects {@code resources} by adding annotation files of the given file type to it. + * + * @param location an annotation file (a stub file or ajava file), a jarfile, or a directory. If a + * stub file or ajava file, add it to the {@code resources} list. If a jarfile, use all + * annotation files (of type {@code fileType}) contained in it. If a directory, recurse on all + * files contained in it. + * @param resources the list to add the found files to + * @param fileType type of annotation files to add + */ + @SuppressWarnings({ + "JdkObsolete", // JarFile.entries() + "nullness:argument", // inference failed in Arrays.sort + "builder:required.method.not.called" // ownership passed to list of + // JarEntryAnnotationFileResource, where `file` appears in every element of the list + }) + private static void addAnnotationFilesToList( + File location, List resources, AnnotationFileType fileType) { + if (isAnnotationFile(location, fileType)) { + resources.add(new FileAnnotationFileResource(location)); + } else if (isJar(location)) { + JarFile file; + try { + file = new JarFile(location); + } catch (IOException e) { + System.err.println("AnnotationFileUtil: could not process JAR file: " + location); + return; + } + Enumeration entries = file.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (isAnnotationFile(entry.getName(), fileType)) { + resources.add(new JarEntryAnnotationFileResource(file, entry)); } - List recordComponents = ElementUtils.getRecordComponents(enclosing); - if (recordComponents.size() == elt.getParameters().size()) { - for (int i = 0; i < recordComponents.size(); i++) { - if (!types.isSameType( - recordComponents.get(i).asType(), elt.getParameters().get(i).asType())) { - return false; - } - } - return true; + } + + } else if (location.isDirectory()) { + File[] directoryContents = location.listFiles(); + Arrays.sort(directoryContents, Comparator.comparing(File::getName)); + for (File enclosed : directoryContents) { + addAnnotationFilesToList(enclosed, resources, fileType); + } + } + } + + /** + * Returns true if the given {@link ExecutableElement} is the canonical constructor of a record + * (i.e., the parameter types of the constructor correspond to the parameter types of the record + * components, ignoring annotations). + * + * @param elt the constructor/method to check + * @param types the Types instance to use for comparing types + * @return true if elt is the canonical constructor of the record containing it + */ + public static boolean isCanonicalConstructor(ExecutableElement elt, Types types) { + if (elt.getKind() != ElementKind.CONSTRUCTOR) { + return false; + } + TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); + if (!ElementUtils.isRecordElement(enclosing)) { + return false; + } + List recordComponents = ElementUtils.getRecordComponents(enclosing); + if (recordComponents.size() == elt.getParameters().size()) { + for (int i = 0; i < recordComponents.size(); i++) { + if (!types.isSameType( + recordComponents.get(i).asType(), elt.getParameters().get(i).asType())) { + return false; } - return false; + } + return true; } + return false; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java index 152742efb99..d79cf51016e 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java @@ -7,26 +7,26 @@ /** {@link File}-based implementation of {@link AnnotationFileResource}. */ public class FileAnnotationFileResource implements AnnotationFileResource { - /** The underlying file. */ - private final File file; + /** The underlying file. */ + private final File file; - /** - * Constructs a {@code AnnotationFileResource} for the specified annotation file (stub file or - * ajava file). - * - * @param file the annotation file - */ - public FileAnnotationFileResource(File file) { - this.file = file; - } + /** + * Constructs a {@code AnnotationFileResource} for the specified annotation file (stub file or + * ajava file). + * + * @param file the annotation file + */ + public FileAnnotationFileResource(File file) { + this.file = file; + } - @Override - public String getDescription() { - return file.getAbsolutePath(); - } + @Override + public String getDescription() { + return file.getAbsolutePath(); + } - @Override - public InputStream getInputStream() throws IOException { - return new FileInputStream(file); - } + @Override + public InputStream getInputStream() throws IOException { + return new FileInputStream(file); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java index 2bc8e2391ef..475f9241707 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java @@ -7,31 +7,30 @@ /** {@link JarEntry}-based implementation of {@link AnnotationFileResource}. */ public class JarEntryAnnotationFileResource implements AnnotationFileResource { - /** The underlying JarFile. */ - private final JarFile file; + /** The underlying JarFile. */ + private final JarFile file; - /** The entry in the jar file. */ - private final JarEntry entry; + /** The entry in the jar file. */ + private final JarEntry entry; - /** - * Constructs a {@code AnnotationFileResource} for the specified entry in the specified JAR - * file. - * - * @param file the JAR file - * @param entry the JAR entry - */ - public JarEntryAnnotationFileResource(JarFile file, JarEntry entry) { - this.file = file; - this.entry = entry; - } + /** + * Constructs a {@code AnnotationFileResource} for the specified entry in the specified JAR file. + * + * @param file the JAR file + * @param entry the JAR entry + */ + public JarEntryAnnotationFileResource(JarFile file, JarEntry entry) { + this.file = file; + this.entry = entry; + } - @Override - public String getDescription() { - return file.getName() + "!" + entry.getName(); - } + @Override + public String getDescription() { + return file.getName() + "!" + entry.getName(); + } - @Override - public InputStream getInputStream() throws IOException { - return file.getInputStream(entry); - } + @Override + public InputStream getInputStream() throws IOException { + return file.getInputStream(entry); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java b/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java index 2d923e1d289..73dc0675091 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java @@ -20,14 +20,12 @@ import com.github.javaparser.utils.ParserCollectionStrategy; import com.github.javaparser.utils.ProjectRoot; import com.github.javaparser.utils.SourceRoot; - -import org.checkerframework.framework.util.JavaParserUtil; - import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; +import org.checkerframework.framework.util.JavaParserUtil; /** * Process Java source files in a directory to produce, in-place, minimal stub files. @@ -44,221 +42,220 @@ * */ public class JavaStubifier { - /** - * Processes each provided command-line argument; see class documentation for details. - * - * @param args command-line arguments: directories to process - */ - public static void main(String[] args) { - if (args.length < 1) { - System.err.println("Usage: provide one or more directory names to process"); - System.exit(1); - } - for (String arg : args) { - process(arg); - } + /** + * Processes each provided command-line argument; see class documentation for details. + * + * @param args command-line arguments: directories to process + */ + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("Usage: provide one or more directory names to process"); + System.exit(1); } + for (String arg : args) { + process(arg); + } + } - /** - * Process each file in the given directory; see class documentation for details. - * - * @param dir directory to process - */ - private static void process(String dir) { - Path root = dirnameToPath(dir); - MinimizerCallback mc = new MinimizerCallback(); - CollectionStrategy strategy = new ParserCollectionStrategy(); - // Required to include directories that contain a module-info.java, which don't parse by - // default. - strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); - ProjectRoot projectRoot = strategy.collect(root); + /** + * Process each file in the given directory; see class documentation for details. + * + * @param dir directory to process + */ + private static void process(String dir) { + Path root = dirnameToPath(dir); + MinimizerCallback mc = new MinimizerCallback(); + CollectionStrategy strategy = new ParserCollectionStrategy(); + // Required to include directories that contain a module-info.java, which don't parse by + // default. + strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); + ProjectRoot projectRoot = strategy.collect(root); - projectRoot - .getSourceRoots() - .forEach( - sourceRoot -> { - try { - sourceRoot.parse("", mc); - } catch (IOException e) { - System.err.println("IOException: " + e); - } - }); - } + projectRoot + .getSourceRoots() + .forEach( + sourceRoot -> { + try { + sourceRoot.parse("", mc); + } catch (IOException e) { + System.err.println("IOException: " + e); + } + }); + } - /** - * Converts a directory name to a path. It issues a warning and terminates the program if the - * argument does not exist or is not a directory. - * - *

Unlike {@code Paths.get}, it handles "." which means the current directory in Unix. - * - * @param dir a directory name - * @return a path for the directory name - */ - public static Path dirnameToPath(String dir) { - File f = new File(dir); - if (!f.exists()) { - System.err.printf("Directory %s (%s) does not exist.%n", dir, f); - System.exit(1); - } - if (!f.isDirectory()) { - System.err.printf("Not a directory: %s (%s).%n", dir, f); - System.exit(1); - } - String absoluteDir = f.getAbsolutePath(); - if (absoluteDir.endsWith("/.")) { - absoluteDir = absoluteDir.substring(0, absoluteDir.length() - 2); - } - return Paths.get(absoluteDir); + /** + * Converts a directory name to a path. It issues a warning and terminates the program if the + * argument does not exist or is not a directory. + * + *

Unlike {@code Paths.get}, it handles "." which means the current directory in Unix. + * + * @param dir a directory name + * @return a path for the directory name + */ + public static Path dirnameToPath(String dir) { + File f = new File(dir); + if (!f.exists()) { + System.err.printf("Directory %s (%s) does not exist.%n", dir, f); + System.exit(1); } + if (!f.isDirectory()) { + System.err.printf("Not a directory: %s (%s).%n", dir, f); + System.exit(1); + } + String absoluteDir = f.getAbsolutePath(); + if (absoluteDir.endsWith("/.")) { + absoluteDir = absoluteDir.substring(0, absoluteDir.length() - 2); + } + return Paths.get(absoluteDir); + } - /** Callback to process each Java file; see class documentation for details. */ - private static class MinimizerCallback implements SourceRoot.Callback { - /** The visitor instance. */ - private final MinimizerVisitor mv; + /** Callback to process each Java file; see class documentation for details. */ + private static class MinimizerCallback implements SourceRoot.Callback { + /** The visitor instance. */ + private final MinimizerVisitor mv; - /** Create a MinimizerCallback instance. */ - public MinimizerCallback() { - this.mv = new MinimizerVisitor(); - } + /** Create a MinimizerCallback instance. */ + public MinimizerCallback() { + this.mv = new MinimizerVisitor(); + } - @Override - public Result process( - Path localPath, Path absolutePath, ParseResult result) { - Result res = Result.SAVE; - // System.out.printf("Minimizing %s%n", absolutePath); - Optional opt = result.getResult(); - if (opt.isPresent()) { - CompilationUnit cu = opt.get(); - // Only remove the "contained" comments so that the copyright comment is not - // removed. - cu.getAllContainedComments().forEach(Node::remove); - mv.visit(cu, null); - if (cu.findAll(ClassOrInterfaceDeclaration.class).isEmpty() - && cu.findAll(AnnotationDeclaration.class).isEmpty() - && cu.findAll(EnumDeclaration.class).isEmpty() - && !absolutePath.endsWith("package-info.java")) { - // All content is removed, delete this file. - new File(absolutePath.toUri()).delete(); - res = Result.DONT_SAVE; - } - } - return res; + @Override + public Result process(Path localPath, Path absolutePath, ParseResult result) { + Result res = Result.SAVE; + // System.out.printf("Minimizing %s%n", absolutePath); + Optional opt = result.getResult(); + if (opt.isPresent()) { + CompilationUnit cu = opt.get(); + // Only remove the "contained" comments so that the copyright comment is not + // removed. + cu.getAllContainedComments().forEach(Node::remove); + mv.visit(cu, null); + if (cu.findAll(ClassOrInterfaceDeclaration.class).isEmpty() + && cu.findAll(AnnotationDeclaration.class).isEmpty() + && cu.findAll(EnumDeclaration.class).isEmpty() + && !absolutePath.endsWith("package-info.java")) { + // All content is removed, delete this file. + new File(absolutePath.toUri()).delete(); + res = Result.DONT_SAVE; } + } + return res; } + } - /** Visitor to process one compilation unit; see class documentation for details. */ - private static class MinimizerVisitor extends ModifierVisitor { - /** Whether to consider members implicitly public. */ - private boolean implicitlyPublic = false; + /** Visitor to process one compilation unit; see class documentation for details. */ + private static class MinimizerVisitor extends ModifierVisitor { + /** Whether to consider members implicitly public. */ + private boolean implicitlyPublic = false; - @Override - public ClassOrInterfaceDeclaration visit(ClassOrInterfaceDeclaration cid, Void arg) { - boolean prevIP = implicitlyPublic; - if (cid.isInterface()) { - // All members of interfaces are implicitly public. - implicitlyPublic = true; - } - super.visit(cid, arg); - if (cid.isInterface()) { - implicitlyPublic = prevIP; - } - // Do not remove private or package-private classes, because there could - // be externally-visible members in externally-visible subclasses. - return cid; - } + @Override + public ClassOrInterfaceDeclaration visit(ClassOrInterfaceDeclaration cid, Void arg) { + boolean prevIP = implicitlyPublic; + if (cid.isInterface()) { + // All members of interfaces are implicitly public. + implicitlyPublic = true; + } + super.visit(cid, arg); + if (cid.isInterface()) { + implicitlyPublic = prevIP; + } + // Do not remove private or package-private classes, because there could + // be externally-visible members in externally-visible subclasses. + return cid; + } - @Override - public EnumDeclaration visit(EnumDeclaration ed, Void arg) { - super.visit(ed, arg); - // Enums can't be extended, so it is ok to remove them if they are not externally - // visible. - removeIfPrivateOrPkgPrivate(ed); - return ed; - } + @Override + public EnumDeclaration visit(EnumDeclaration ed, Void arg) { + super.visit(ed, arg); + // Enums can't be extended, so it is ok to remove them if they are not externally + // visible. + removeIfPrivateOrPkgPrivate(ed); + return ed; + } - @Override - public ConstructorDeclaration visit(ConstructorDeclaration cd, Void arg) { - super.visit(cd, arg); - // Constructors cannot be overridden, so it is ok to remove them if they are - // not externally visible. - if (!removeIfPrivateOrPkgPrivate(cd)) { - // ConstructorDeclaration has to have a body - cd.setBody(new BlockStmt()); - } - return cd; - } + @Override + public ConstructorDeclaration visit(ConstructorDeclaration cd, Void arg) { + super.visit(cd, arg); + // Constructors cannot be overridden, so it is ok to remove them if they are + // not externally visible. + if (!removeIfPrivateOrPkgPrivate(cd)) { + // ConstructorDeclaration has to have a body + cd.setBody(new BlockStmt()); + } + return cd; + } - @Override - public MethodDeclaration visit(MethodDeclaration md, Void arg) { - super.visit(md, arg); - // Non-private methods could be overridden with larger visibility. - // So it is only safe to remove private methods, which can't be overridden. - if (!removeIfPrivate(md)) { - md.removeBody(); - } - return md; - } + @Override + public MethodDeclaration visit(MethodDeclaration md, Void arg) { + super.visit(md, arg); + // Non-private methods could be overridden with larger visibility. + // So it is only safe to remove private methods, which can't be overridden. + if (!removeIfPrivate(md)) { + md.removeBody(); + } + return md; + } - @Override - public FieldDeclaration visit(FieldDeclaration fd, Void arg) { - super.visit(fd, arg); - // It is safe to remove fields that are not externally visible. - if (!removeIfPrivateOrPkgPrivate(fd)) { - fd.getVariables().forEach(v -> v.getInitializer().ifPresent(Node::remove)); - } - return fd; - } + @Override + public FieldDeclaration visit(FieldDeclaration fd, Void arg) { + super.visit(fd, arg); + // It is safe to remove fields that are not externally visible. + if (!removeIfPrivateOrPkgPrivate(fd)) { + fd.getVariables().forEach(v -> v.getInitializer().ifPresent(Node::remove)); + } + return fd; + } - @Override - public InitializerDeclaration visit(InitializerDeclaration id, Void arg) { - super.visit(id, arg); - id.remove(); - return id; - } + @Override + public InitializerDeclaration visit(InitializerDeclaration id, Void arg) { + super.visit(id, arg); + id.remove(); + return id; + } - @Override - public NormalAnnotationExpr visit(NormalAnnotationExpr nae, Void arg) { - super.visit(nae, arg); - if (nae.getNameAsString().equals("Deprecated")) { - nae.setPairs(new NodeList<>()); - } - return nae; - } + @Override + public NormalAnnotationExpr visit(NormalAnnotationExpr nae, Void arg) { + super.visit(nae, arg); + if (nae.getNameAsString().equals("Deprecated")) { + nae.setPairs(new NodeList<>()); + } + return nae; + } - /** - * Remove the whole node if it is private or package private. - * - * @param node a Node to inspect - * @return true if the node was removed - */ - private boolean removeIfPrivateOrPkgPrivate(NodeWithAccessModifiers node) { - if (implicitlyPublic) { - return false; - } - AccessSpecifier as = node.getAccessSpecifier(); - if (as == AccessSpecifier.PRIVATE || as == AccessSpecifier.NONE) { - ((Node) node).remove(); - return true; - } - return false; - } + /** + * Remove the whole node if it is private or package private. + * + * @param node a Node to inspect + * @return true if the node was removed + */ + private boolean removeIfPrivateOrPkgPrivate(NodeWithAccessModifiers node) { + if (implicitlyPublic) { + return false; + } + AccessSpecifier as = node.getAccessSpecifier(); + if (as == AccessSpecifier.PRIVATE || as == AccessSpecifier.NONE) { + ((Node) node).remove(); + return true; + } + return false; + } - /** - * Remove the whole node if it is private. - * - * @param node a Node to inspect - * @return true if the node was removed - */ - private boolean removeIfPrivate(NodeWithAccessModifiers node) { - if (implicitlyPublic) { - return false; - } - AccessSpecifier as = node.getAccessSpecifier(); - if (as == AccessSpecifier.PRIVATE) { - ((Node) node).remove(); - return true; - } - return false; - } + /** + * Remove the whole node if it is private. + * + * @param node a Node to inspect + * @return true if the node was removed + */ + private boolean removeIfPrivate(NodeWithAccessModifiers node) { + if (implicitlyPublic) { + return false; + } + AccessSpecifier as = node.getAccessSpecifier(); + if (as == AccessSpecifier.PRIVATE) { + ((Node) node).remove(); + return true; + } + return false; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java index 712d1da3159..bb243681f55 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java @@ -25,15 +25,6 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.common.reflect.ClassPath; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.util.JavaParserUtil; -import org.checkerframework.javacutil.BugInCF; -import org.plumelib.util.ArraysPlume; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; - import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.FileWriter; @@ -52,6 +43,13 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.util.JavaParserUtil; +import org.checkerframework.javacutil.BugInCF; +import org.plumelib.util.ArraysPlume; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; /** * Process Java source files to remove annotations that ought to be inferred. @@ -83,536 +81,530 @@ */ public class RemoveAnnotationsForInference { - /** - * Do not instantiate. This is a standalone program whose entry point is {@link - * #main(String[])}. - */ - private RemoveAnnotationsForInference() { - throw new Error("Do not instantiate RemoveAnnotationsForInference."); - } - - /** - * A list of annotations not to remove (i.e., to keep in the source code). Used to prevent - * project-specific annotations that must remain for the project to build from being removed by - * this program. (It would be burdensome to add all project-specific annotations to the global - * list in {@link #isTrustedAnnotation(String)}.) - */ - private static @MonotonicNonNull Set annotationsToKeep = null; - - /** - * Processes each provided command-line argument; see {@link RemoveAnnotationsForInference class - * documentation} for details. - * - * @param args command-line arguments: directories to process - */ - public static void main(String[] args) { - // TODO: using plume-lib's options here would be better, but would add a dependency - // to the whole Checker Framework, which is undesirable. Move this program elsewhere - // (e.g., to a plume-lib project)? - if (args[0].contentEquals("-keepFile")) { - if (args.length < 2) { - System.err.println( - "Usage: -keepFile requires an argument immediately after it: the path to the keep" - + " file."); - System.exit(2); - } - String keepFilePath = args[1]; - try (Stream lines = Files.lines(Paths.get(keepFilePath))) { - annotationsToKeep = lines.collect(Collectors.toSet()); - } catch (FileNotFoundException e) { - System.err.println("Error: Keep file " + keepFilePath + " not found."); - System.exit(3); - } catch (IOException e) { - System.err.println( - "Problem reading keep file " + keepFilePath + ": " + e.getMessage()); - System.exit(4); - } - - // Check for common mistake of adding "@" before the annotation name. - for (String annotationToKeep : annotationsToKeep) { - if (annotationToKeep.startsWith("@")) { - System.err.println( - "Error: Keep file includes an @ symbol before this annotation: " - + annotationToKeep - + ". Annotations should be listed in the keep file without the @ symbol."); - System.exit(5); - } - } - - args = ArraysPlume.subarray(args, 2, args.length - 2); + /** + * Do not instantiate. This is a standalone program whose entry point is {@link #main(String[])}. + */ + private RemoveAnnotationsForInference() { + throw new Error("Do not instantiate RemoveAnnotationsForInference."); + } + + /** + * A list of annotations not to remove (i.e., to keep in the source code). Used to prevent + * project-specific annotations that must remain for the project to build from being removed by + * this program. (It would be burdensome to add all project-specific annotations to the global + * list in {@link #isTrustedAnnotation(String)}.) + */ + private static @MonotonicNonNull Set annotationsToKeep = null; + + /** + * Processes each provided command-line argument; see {@link RemoveAnnotationsForInference class + * documentation} for details. + * + * @param args command-line arguments: directories to process + */ + public static void main(String[] args) { + // TODO: using plume-lib's options here would be better, but would add a dependency + // to the whole Checker Framework, which is undesirable. Move this program elsewhere + // (e.g., to a plume-lib project)? + if (args[0].contentEquals("-keepFile")) { + if (args.length < 2) { + System.err.println( + "Usage: -keepFile requires an argument immediately after it: the path to the keep" + + " file."); + System.exit(2); + } + String keepFilePath = args[1]; + try (Stream lines = Files.lines(Paths.get(keepFilePath))) { + annotationsToKeep = lines.collect(Collectors.toSet()); + } catch (FileNotFoundException e) { + System.err.println("Error: Keep file " + keepFilePath + " not found."); + System.exit(3); + } catch (IOException e) { + System.err.println("Problem reading keep file " + keepFilePath + ": " + e.getMessage()); + System.exit(4); + } + + // Check for common mistake of adding "@" before the annotation name. + for (String annotationToKeep : annotationsToKeep) { + if (annotationToKeep.startsWith("@")) { + System.err.println( + "Error: Keep file includes an @ symbol before this annotation: " + + annotationToKeep + + ". Annotations should be listed in the keep file without the @ symbol."); + System.exit(5); } - if (args.length < 1) { - System.err.println("Usage: provide one or more directory names to process"); - System.exit(1); - } - for (String arg : args) { - process(arg); - } - } + } - /** - * Maps from simple names to fully-qualified names of annotations. (Actually, it includes every - * class on the classpath.) - */ - static Multimap simpleToFullyQualified = ArrayListMultimap.create(); - - static { - try { - ClassPath cp = ClassPath.from(RemoveAnnotationsForInference.class.getClassLoader()); - for (ClassPath.ClassInfo ci : cp.getTopLevelClasses()) { - // There is no way to determine whether `ci` represents an annotation, without - // loading it. - // I could filter using a heuristic: only include classes in a package named "qual". - simpleToFullyQualified.put(ci.getSimpleName(), ci.getName()); - } - } catch (IOException e) { - throw new BugInCF(e); - } + args = ArraysPlume.subarray(args, 2, args.length - 2); } - - /** - * Process each file in the given directory; see the {@link RemoveAnnotationsForInference class - * documentation} for details. - * - * @param dir directory to process - */ - private static void process(String dir) { - - Path root = JavaStubifier.dirnameToPath(dir); - - RemoveAnnotationsCallback rac = new RemoveAnnotationsCallback(); - CollectionStrategy strategy = new ParserCollectionStrategy(); - // Required to include directories that contain a module-info.java, which don't parse by - // default. - strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); - ProjectRoot projectRoot = strategy.collect(root); - - for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { - try { - sourceRoot.parse("", rac); - } catch (IOException e) { - throw new BugInCF(e); - } - } + if (args.length < 1) { + System.err.println("Usage: provide one or more directory names to process"); + System.exit(1); } - - /** - * Callback to process each Java file; see the {@link RemoveAnnotationsForInference class - * documentation} for details. - */ - private static class RemoveAnnotationsCallback implements SourceRoot.Callback { - /** The visitor instance. */ - private final RemoveAnnotationsVisitor rav = new RemoveAnnotationsVisitor(); - - @Override - public Result process( - Path localPath, Path absolutePath, ParseResult result) { - Optional opt = result.getResult(); - if (opt.isPresent()) { - CompilationUnit cu = opt.get(); - List removals = rav.visit(cu, null); - removeAnnotations(absolutePath, removals); - } - return Result.DONT_SAVE; - } + for (String arg : args) { + process(arg); } - - // An earlier implementation used ModifierVisitor. However, JavaParser's unparser can change - // the structure of the program. For example, it changes `protected @Nullable Object x;` to - // `@Nullable protected Object x;` which yields a type.anno.before.modifier error. - - /** - * Rewrites the file in place, removing the given annotations from it. - * - * @param absolutePath the path to the file - * @param removals the annotations to remove - */ - static void removeAnnotations(Path absolutePath, List removals) { - if (removals.isEmpty()) { - return; - } - - List lines; - try { - lines = Files.readAllLines(absolutePath); - } catch (IOException e) { - System.out.printf("Problem reading %s: %s%n", absolutePath, e.getMessage()); - System.exit(1); - throw new Error("unreachable"); - } - - PositionUtils.sortByBeginPosition(removals); - Collections.reverse(removals); - - // This code (correctly) assumes that no element of `removals` is contained within another. - for (AnnotationExpr removal : removals) { - Position begin = removal.getBegin().get(); - Position end = removal.getEnd().get(); - int beginLine = begin.line - 1; - int beginColumn = begin.column - 1; - int endLine = end.line - 1; - int endColumn = end.column; // a JavaParser range is inclusive of the character at "end" - if (beginLine == endLine) { - String line = lines.get(beginLine); - String prefix = line.substring(0, beginColumn); - String suffix = line.substring(endColumn); - - // Remove whitespace to beautify formatting. - suffix = CharMatcher.whitespace().trimLeadingFrom(suffix); - if (suffix.startsWith("[")) { - prefix = CharMatcher.whitespace().trimTrailingFrom(prefix); - } - - String newLine = prefix + suffix; - replaceLine(lines, beginLine, newLine); - } else { - String newLastLine = lines.get(endLine).substring(endColumn); - replaceLine(lines, endLine, newLastLine); - for (int lineno = endLine - 1; lineno > beginLine; lineno--) { - lines.remove(lineno); - } - String newFirstLine = lines.get(beginLine).substring(0, beginColumn); - replaceLine(lines, beginLine, newFirstLine); - } - } - - try (PrintWriter pw = - new PrintWriter(new BufferedWriter(new FileWriter(absolutePath.toString())))) { - for (String line : lines) { - pw.println(line); - } - } catch (IOException e) { - throw new UncheckedIOException("problem writing " + absolutePath.toString(), e); - } + } + + /** + * Maps from simple names to fully-qualified names of annotations. (Actually, it includes every + * class on the classpath.) + */ + static Multimap simpleToFullyQualified = ArrayListMultimap.create(); + + static { + try { + ClassPath cp = ClassPath.from(RemoveAnnotationsForInference.class.getClassLoader()); + for (ClassPath.ClassInfo ci : cp.getTopLevelClasses()) { + // There is no way to determine whether `ci` represents an annotation, without + // loading it. + // I could filter using a heuristic: only include classes in a package named "qual". + simpleToFullyQualified.put(ci.getSimpleName(), ci.getName()); + } + } catch (IOException e) { + throw new BugInCF(e); } - - /** - * If {@code newLine} is blank, removes the given line. Otherwise replaces the given line. - * - * @param lines the list in which to do replacement or removal - * @param lineno the index of the line to be removed or replaced - * @param newLine the new line for index {@code lineno} - */ - static void replaceLine(List lines, int lineno, String newLine) { - if (StringsPlume.isBlank(newLine)) { - lines.remove(lineno); - } else { - lines.set(lineno, newLine); - } + } + + /** + * Process each file in the given directory; see the {@link RemoveAnnotationsForInference class + * documentation} for details. + * + * @param dir directory to process + */ + private static void process(String dir) { + + Path root = JavaStubifier.dirnameToPath(dir); + + RemoveAnnotationsCallback rac = new RemoveAnnotationsCallback(); + CollectionStrategy strategy = new ParserCollectionStrategy(); + // Required to include directories that contain a module-info.java, which don't parse by + // default. + strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); + ProjectRoot projectRoot = strategy.collect(root); + + for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { + try { + sourceRoot.parse("", rac); + } catch (IOException e) { + throw new BugInCF(e); + } + } + } + + /** + * Callback to process each Java file; see the {@link RemoveAnnotationsForInference class + * documentation} for details. + */ + private static class RemoveAnnotationsCallback implements SourceRoot.Callback { + /** The visitor instance. */ + private final RemoveAnnotationsVisitor rav = new RemoveAnnotationsVisitor(); + + @Override + public Result process(Path localPath, Path absolutePath, ParseResult result) { + Optional opt = result.getResult(); + if (opt.isPresent()) { + CompilationUnit cu = opt.get(); + List removals = rav.visit(cu, null); + removeAnnotations(absolutePath, removals); + } + return Result.DONT_SAVE; + } + } + + // An earlier implementation used ModifierVisitor. However, JavaParser's unparser can change + // the structure of the program. For example, it changes `protected @Nullable Object x;` to + // `@Nullable protected Object x;` which yields a type.anno.before.modifier error. + + /** + * Rewrites the file in place, removing the given annotations from it. + * + * @param absolutePath the path to the file + * @param removals the annotations to remove + */ + static void removeAnnotations(Path absolutePath, List removals) { + if (removals.isEmpty()) { + return; } - /** - * Visits one compilation unit, collecting the annotations that should be removed. See the - * {@link RemoveAnnotationsForInference class documentation} for more details. - * - *

The annotations will be removed from the source code by the {@link #removeAnnotations} - * method. - */ - private static class RemoveAnnotationsVisitor - extends GenericListVisitorAdapter { - - /** - * Returns annotations that should be removed from source code. - * - * @param n an annotation - * @param superResult the result of calling {@code super.visit} on n; this includes - * processing the subcomponents of n - * @return the argument to remove it, or superResult to retain it - */ - List processAnnotation(AnnotationExpr n, List superResult) { - if (n == null) { - // TODO: How is this possible? - return superResult; - } - - String name = n.getNameAsString(); - - // Retain annotations defined in the JDK. - if (isJdkAnnotation(name)) { - return superResult; - } - // Retain trusted annotations. - if (isTrustedAnnotation(name)) { - return superResult; - } - // Retain annotations that the user requested specifically should be kept. - if (shouldBeKept(name)) { - return superResult; - } - // Retain annotations for which warnings are suppressed. - if (isSuppressed(n)) { - return superResult; - } - - // The default behavior is to remove the annotation. - // Don't include superResult, which is contained within `n`. - return Collections.singletonList(n); - } - - // There are three JavaParser AST nodes that represent annotations - - @Override - public List visit(MarkerAnnotationExpr n, Void arg) { - return processAnnotation(n, super.visit(n, arg)); - } + List lines; + try { + lines = Files.readAllLines(absolutePath); + } catch (IOException e) { + System.out.printf("Problem reading %s: %s%n", absolutePath, e.getMessage()); + System.exit(1); + throw new Error("unreachable"); + } - @Override - public List visit(NormalAnnotationExpr n, Void arg) { - return processAnnotation(n, super.visit(n, arg)); + PositionUtils.sortByBeginPosition(removals); + Collections.reverse(removals); + + // This code (correctly) assumes that no element of `removals` is contained within another. + for (AnnotationExpr removal : removals) { + Position begin = removal.getBegin().get(); + Position end = removal.getEnd().get(); + int beginLine = begin.line - 1; + int beginColumn = begin.column - 1; + int endLine = end.line - 1; + int endColumn = end.column; // a JavaParser range is inclusive of the character at "end" + if (beginLine == endLine) { + String line = lines.get(beginLine); + String prefix = line.substring(0, beginColumn); + String suffix = line.substring(endColumn); + + // Remove whitespace to beautify formatting. + suffix = CharMatcher.whitespace().trimLeadingFrom(suffix); + if (suffix.startsWith("[")) { + prefix = CharMatcher.whitespace().trimTrailingFrom(prefix); } - @Override - public List visit(SingleMemberAnnotationExpr n, Void arg) { - return processAnnotation(n, super.visit(n, arg)); + String newLine = prefix + suffix; + replaceLine(lines, beginLine, newLine); + } else { + String newLastLine = lines.get(endLine).substring(endColumn); + replaceLine(lines, endLine, newLastLine); + for (int lineno = endLine - 1; lineno > beginLine; lineno--) { + lines.remove(lineno); } + String newFirstLine = lines.get(beginLine).substring(0, beginColumn); + replaceLine(lines, beginLine, newFirstLine); + } } - /** - * Returns true if the given annotation is defined in the JDK. - * - * @param name the annotation's name (simple or fully-qualified) - * @return true if the given annotation is defined in the JDK - */ - static boolean isJdkAnnotation(String name) { - return name.equals("Serial") - || name.equals("java.io.Serial") - || name.equals("Deprecated") - || name.equals("java.lang.Deprecated") - || name.equals("FunctionalInterface") - || name.equals("java.lang.FunctionalInterface") - || name.equals("Override") - || name.equals("java.lang.Override") - || name.equals("SafeVarargs") - || name.equals("java.lang.SafeVarargs") - || name.equals("Documented") - || name.equals("java.lang.annotation.Documented") - || name.equals("Inherited") - || name.equals("java.lang.annotation.Inherited") - || name.equals("Native") - || name.equals("java.lang.annotation.Native") - || name.equals("Repeatable") - || name.equals("java.lang.annotation.Repeatable") - || name.equals("Retention") - || name.equals("java.lang.annotation.Retention") - || name.equals("SuppressWarnings") - || name.equals("java.lang.SuppressWarnings") - || name.equals("Target") - || name.equals("java.lang.annotation.Target"); + try (PrintWriter pw = + new PrintWriter(new BufferedWriter(new FileWriter(absolutePath.toString())))) { + for (String line : lines) { + pw.println(line); + } + } catch (IOException e) { + throw new UncheckedIOException("problem writing " + absolutePath.toString(), e); } - - /** - * Returns true if the given annotation is trusted, not checked/verified. - * - * @param name the annotation's name (simple or fully-qualified) - * @return true if the given annotation is trusted, not verified - */ - static boolean isTrustedAnnotation(String name) { - // This list was determined by grepping for "trusted" in `qual` directories. - return name.equals("Untainted") - || name.equals("org.checkerframework.checker.tainting.qual.Untainted") - || name.equals("InternedDistinct") - || name.equals("org.checkerframework.checker.interning.qual.InternedDistinct") - || name.equals("ReturnsReceiver") - || name.equals("org.checkerframework.checker.builder.qual.ReturnsReceiver") - || name.equals("TerminatesExecution") - || name.equals("org.checkerframework.dataflow.qual.TerminatesExecution") - || name.equals("Covariant") - || name.equals("org.checkerframework.framework.qual.Covariant") - || name.equals("NonLeaked") - || name.equals("org.checkerframework.common.aliasing.qual.NonLeaked") - || name.equals("LeakedToResult") - || name.equals("org.checkerframework.common.aliasing.qual.LeakedToResult"); + } + + /** + * If {@code newLine} is blank, removes the given line. Otherwise replaces the given line. + * + * @param lines the list in which to do replacement or removal + * @param lineno the index of the line to be removed or replaced + * @param newLine the new line for index {@code lineno} + */ + static void replaceLine(List lines, int lineno, String newLine) { + if (StringsPlume.isBlank(newLine)) { + lines.remove(lineno); + } else { + lines.set(lineno, newLine); } + } + + /** + * Visits one compilation unit, collecting the annotations that should be removed. See the {@link + * RemoveAnnotationsForInference class documentation} for more details. + * + *

The annotations will be removed from the source code by the {@link #removeAnnotations} + * method. + */ + private static class RemoveAnnotationsVisitor + extends GenericListVisitorAdapter { /** - * Returns true iff the annotation is present in the user-supplied file of annotations to keep - * (via the {@code -keepFile} command-line option). + * Returns annotations that should be removed from source code. * - * @param name the annotation's name (simple or fully-qualified) - * @return true if the user requested that this annotation be kept in the source code + * @param n an annotation + * @param superResult the result of calling {@code super.visit} on n; this includes processing + * the subcomponents of n + * @return the argument to remove it, or superResult to retain it */ - private static boolean shouldBeKept(String name) { - return annotationsToKeep != null && annotationsToKeep.contains(name); + List processAnnotation(AnnotationExpr n, List superResult) { + if (n == null) { + // TODO: How is this possible? + return superResult; + } + + String name = n.getNameAsString(); + + // Retain annotations defined in the JDK. + if (isJdkAnnotation(name)) { + return superResult; + } + // Retain trusted annotations. + if (isTrustedAnnotation(name)) { + return superResult; + } + // Retain annotations that the user requested specifically should be kept. + if (shouldBeKept(name)) { + return superResult; + } + // Retain annotations for which warnings are suppressed. + if (isSuppressed(n)) { + return superResult; + } + + // The default behavior is to remove the annotation. + // Don't include superResult, which is contained within `n`. + return Collections.singletonList(n); } - // This approach searches upward to find all the active warning suppressions. - // An alternative, more efficient approach would be to track the current set of warning - // suppressions, using a stack. - // There are two problems with the alternative approach (and besides, this approach is fast - // enough as it is). - // 1. JavaParser sometimes visits members before the annotation, so there was not a chance to - // observe the annotation and place it on the suppression stack. This should be fixed for - // ModifierVisitor (but not for other visitors such as GenericListVisitorAdapter) in - // JavaParser release 3.19.0. - // 2. A user might write an annotation before @SuppressWarnings, as in: - // @Interned @SuppressWarnings("interning") - // The {@code @Interned} annotation is visited before the {@code @SuppressWarnings} - // annotation is. This could be addressed by searching just the parent's annotations. - - /** - * Returns true if warnings about the given annotation are suppressed. - * - *

Its heuristic is to look for a {@code @SuppressWarnings} annotation on a containing - * program element, whose string is one of the elements of the annotation's fully-qualified - * name. - * - * @param arg an annotation - * @return true if warnings about the given annotation are suppressed - */ - private static boolean isSuppressed(AnnotationExpr arg) { - String name = arg.getNameAsString(); - - // If it's a simple name for which we know a fully-qualified name, - // try all fully-qualified names that it could expand to. - Collection names; - if (simpleToFullyQualified.containsKey(name)) { - names = simpleToFullyQualified.get(name); - } else { - names = Collections.singletonList(name); - } + // There are three JavaParser AST nodes that represent annotations - Iterator itor = new Node.ParentsVisitor(arg); - while (itor.hasNext()) { - Node n = itor.next(); - if (n instanceof NodeWithAnnotations) { - for (AnnotationExpr ae : ((NodeWithAnnotations) n).getAnnotations()) { - if (suppresses(ae, names)) { - return true; - } - } - } - } - return false; + @Override + public List visit(MarkerAnnotationExpr n, Void arg) { + return processAnnotation(n, super.visit(n, arg)); } - /** - * Returns true if {@code suppressor} suppresses warnings regarding {@code suppressees}. - * - * @param suppressor an annotation that might be {@code @SuppressWarnings} or like it - * @param suppressees an annotation for which warnings might be suppressed. This is actually a - * list: if the annotation was written unqualified, it contains all the fully-qualified - * names that the unqualified annotation might stand for. - * @return true if {@code suppressor} suppresses warnings regarding {@code suppressees} - */ - static boolean suppresses(AnnotationExpr suppressor, Collection suppressees) { - List suppressWarningsStrings = suppressWarningsStrings(suppressor); - if (suppressWarningsStrings == null) { - return false; - } - List checkerNames = - CollectionsPlume.mapList( - RemoveAnnotationsForInference::checkerName, suppressWarningsStrings); - // "allcheckers" suppresses all warnings. - if (checkerNames.contains("allcheckers")) { - return true; - } - - // Try every element of suppressee's fully-qualified name. - for (String suppressee : suppressees) { - for (String fqPart : suppressee.split("\\.")) { - if (checkerNames.contains(fqPart)) { - return true; - } - } - } + @Override + public List visit(NormalAnnotationExpr n, Void arg) { + return processAnnotation(n, super.visit(n, arg)); + } - return false; + @Override + public List visit(SingleMemberAnnotationExpr n, Void arg) { + return processAnnotation(n, super.visit(n, arg)); + } + } + + /** + * Returns true if the given annotation is defined in the JDK. + * + * @param name the annotation's name (simple or fully-qualified) + * @return true if the given annotation is defined in the JDK + */ + static boolean isJdkAnnotation(String name) { + return name.equals("Serial") + || name.equals("java.io.Serial") + || name.equals("Deprecated") + || name.equals("java.lang.Deprecated") + || name.equals("FunctionalInterface") + || name.equals("java.lang.FunctionalInterface") + || name.equals("Override") + || name.equals("java.lang.Override") + || name.equals("SafeVarargs") + || name.equals("java.lang.SafeVarargs") + || name.equals("Documented") + || name.equals("java.lang.annotation.Documented") + || name.equals("Inherited") + || name.equals("java.lang.annotation.Inherited") + || name.equals("Native") + || name.equals("java.lang.annotation.Native") + || name.equals("Repeatable") + || name.equals("java.lang.annotation.Repeatable") + || name.equals("Retention") + || name.equals("java.lang.annotation.Retention") + || name.equals("SuppressWarnings") + || name.equals("java.lang.SuppressWarnings") + || name.equals("Target") + || name.equals("java.lang.annotation.Target"); + } + + /** + * Returns true if the given annotation is trusted, not checked/verified. + * + * @param name the annotation's name (simple or fully-qualified) + * @return true if the given annotation is trusted, not verified + */ + static boolean isTrustedAnnotation(String name) { + // This list was determined by grepping for "trusted" in `qual` directories. + return name.equals("Untainted") + || name.equals("org.checkerframework.checker.tainting.qual.Untainted") + || name.equals("InternedDistinct") + || name.equals("org.checkerframework.checker.interning.qual.InternedDistinct") + || name.equals("ReturnsReceiver") + || name.equals("org.checkerframework.checker.builder.qual.ReturnsReceiver") + || name.equals("TerminatesExecution") + || name.equals("org.checkerframework.dataflow.qual.TerminatesExecution") + || name.equals("Covariant") + || name.equals("org.checkerframework.framework.qual.Covariant") + || name.equals("NonLeaked") + || name.equals("org.checkerframework.common.aliasing.qual.NonLeaked") + || name.equals("LeakedToResult") + || name.equals("org.checkerframework.common.aliasing.qual.LeakedToResult"); + } + + /** + * Returns true iff the annotation is present in the user-supplied file of annotations to keep + * (via the {@code -keepFile} command-line option). + * + * @param name the annotation's name (simple or fully-qualified) + * @return true if the user requested that this annotation be kept in the source code + */ + private static boolean shouldBeKept(String name) { + return annotationsToKeep != null && annotationsToKeep.contains(name); + } + + // This approach searches upward to find all the active warning suppressions. + // An alternative, more efficient approach would be to track the current set of warning + // suppressions, using a stack. + // There are two problems with the alternative approach (and besides, this approach is fast + // enough as it is). + // 1. JavaParser sometimes visits members before the annotation, so there was not a chance to + // observe the annotation and place it on the suppression stack. This should be fixed for + // ModifierVisitor (but not for other visitors such as GenericListVisitorAdapter) in + // JavaParser release 3.19.0. + // 2. A user might write an annotation before @SuppressWarnings, as in: + // @Interned @SuppressWarnings("interning") + // The {@code @Interned} annotation is visited before the {@code @SuppressWarnings} + // annotation is. This could be addressed by searching just the parent's annotations. + + /** + * Returns true if warnings about the given annotation are suppressed. + * + *

Its heuristic is to look for a {@code @SuppressWarnings} annotation on a containing program + * element, whose string is one of the elements of the annotation's fully-qualified name. + * + * @param arg an annotation + * @return true if warnings about the given annotation are suppressed + */ + private static boolean isSuppressed(AnnotationExpr arg) { + String name = arg.getNameAsString(); + + // If it's a simple name for which we know a fully-qualified name, + // try all fully-qualified names that it could expand to. + Collection names; + if (simpleToFullyQualified.containsKey(name)) { + names = simpleToFullyQualified.get(name); + } else { + names = Collections.singletonList(name); } - /** - * Given a @SuppressWarnings annotation, returns its strings. Given a different annotation that - * suppresses warnings (e.g., @IgnoreInWholeProgramInference, @Inject, @Singleton), returns - * strings for what it suppresses. Otherwise, returns null. - * - * @param n an annotation - * @return the (effective) arguments to {@code @SuppressWarnings}, or null - */ - private static @Nullable List suppressWarningsStrings(AnnotationExpr n) { - String name = n.getNameAsString(); - - if (name.equals("SuppressWarnings") || name.equals("java.lang.SuppressWarnings")) { - if (n instanceof MarkerAnnotationExpr) { - return Collections.emptyList(); - } else if (n instanceof NormalAnnotationExpr) { - NodeList pairs = ((NormalAnnotationExpr) n).getPairs(); - assert pairs.size() == 1; - MemberValuePair pair = pairs.get(0); - assert pair.getName().asString().equals("value"); - return annotationElementStrings(pair.getValue()); - } else if (n instanceof SingleMemberAnnotationExpr) { - return annotationElementStrings(((SingleMemberAnnotationExpr) n).getMemberValue()); - } else { - throw new BugInCF("Unexpected AnnotationExpr of type %s: %s", n.getClass(), n); - } + Iterator itor = new Node.ParentsVisitor(arg); + while (itor.hasNext()) { + Node n = itor.next(); + if (n instanceof NodeWithAnnotations) { + for (AnnotationExpr ae : ((NodeWithAnnotations) n).getAnnotations()) { + if (suppresses(ae, names)) { + return true; + } } + } + } + return false; + } + + /** + * Returns true if {@code suppressor} suppresses warnings regarding {@code suppressees}. + * + * @param suppressor an annotation that might be {@code @SuppressWarnings} or like it + * @param suppressees an annotation for which warnings might be suppressed. This is actually a + * list: if the annotation was written unqualified, it contains all the fully-qualified names + * that the unqualified annotation might stand for. + * @return true if {@code suppressor} suppresses warnings regarding {@code suppressees} + */ + static boolean suppresses(AnnotationExpr suppressor, Collection suppressees) { + List suppressWarningsStrings = suppressWarningsStrings(suppressor); + if (suppressWarningsStrings == null) { + return false; + } + List checkerNames = + CollectionsPlume.mapList( + RemoveAnnotationsForInference::checkerName, suppressWarningsStrings); + // "allcheckers" suppresses all warnings. + if (checkerNames.contains("allcheckers")) { + return true; + } - if (name.equals("IgnoreInWholeProgramInference") - || name.equals("org.checkerframework.framework.qual.IgnoreInWholeProgramInference") - || name.equals("Inject") - || name.equals("javax.inject.Inject") - || name.equals("Singleton") - || name.equals("javax.inject.Singleton") - || name.equals("Option") - || name.equals("org.plumelib.options.Option")) { - return Collections.singletonList("allcheckers"); + // Try every element of suppressee's fully-qualified name. + for (String suppressee : suppressees) { + for (String fqPart : suppressee.split("\\.")) { + if (checkerNames.contains(fqPart)) { + return true; } + } + } - return null; + return false; + } + + /** + * Given a @SuppressWarnings annotation, returns its strings. Given a different annotation that + * suppresses warnings (e.g., @IgnoreInWholeProgramInference, @Inject, @Singleton), returns + * strings for what it suppresses. Otherwise, returns null. + * + * @param n an annotation + * @return the (effective) arguments to {@code @SuppressWarnings}, or null + */ + private static @Nullable List suppressWarningsStrings(AnnotationExpr n) { + String name = n.getNameAsString(); + + if (name.equals("SuppressWarnings") || name.equals("java.lang.SuppressWarnings")) { + if (n instanceof MarkerAnnotationExpr) { + return Collections.emptyList(); + } else if (n instanceof NormalAnnotationExpr) { + NodeList pairs = ((NormalAnnotationExpr) n).getPairs(); + assert pairs.size() == 1; + MemberValuePair pair = pairs.get(0); + assert pair.getName().asString().equals("value"); + return annotationElementStrings(pair.getValue()); + } else if (n instanceof SingleMemberAnnotationExpr) { + return annotationElementStrings(((SingleMemberAnnotationExpr) n).getMemberValue()); + } else { + throw new BugInCF("Unexpected AnnotationExpr of type %s: %s", n.getClass(), n); + } } - /** - * Given an annotation argument for an element of type String[], return a list of strings. - * Returns null if the list of suppressed strings is unknown (e.g., if the argument is a name - * expression). - * - * @param e an annotation argument - * @return the strings expressed by {@code e} - */ - private static @Nullable List annotationElementStrings(Expression e) { - if (e instanceof StringLiteralExpr) { - return Collections.singletonList(((StringLiteralExpr) e).asString()); - } else if (e instanceof ArrayInitializerExpr) { - NodeList values = ((ArrayInitializerExpr) e).getValues(); - List result = new ArrayList<>(values.size()); - for (Expression v : values) { - if (v instanceof StringLiteralExpr) { - result.add(((StringLiteralExpr) v).asString()); - } else if (v instanceof NameExpr) { - // TODO: is it better to return null here, thus causing nothing under this - // warning to be treated as "suppressed", or to return any keys that are string - // literals? - // Returning null here ensures that if any argument to the SW annotation isn't a - // string literal, then none of them are considered. - return null; - } else { - throw new BugInCF( - "Unexpected annotation element of type %s: %s", v.getClass(), v); - } - } - return result; - } else if (e instanceof NameExpr) { - // TODO: it would be better to check if the NameExpr represents a compile-time constant, - // and, if so, to use its value. But, it's not possible to determine that from just the - // result of the parser. - return null; - } else { - throw new BugInCF("Unexpected %s: %s", e.getClass(), e); - } + if (name.equals("IgnoreInWholeProgramInference") + || name.equals("org.checkerframework.framework.qual.IgnoreInWholeProgramInference") + || name.equals("Inject") + || name.equals("javax.inject.Inject") + || name.equals("Singleton") + || name.equals("javax.inject.Singleton") + || name.equals("Option") + || name.equals("org.plumelib.options.Option")) { + return Collections.singletonList("allcheckers"); } - /** - * Returns the "checker name" part of a SuppressWarnings string: the part before the colon, or - * the whole thing if it contains no colon. - * - * @param s a SuppressWarnings string: the argument to {@code @SuppressWarnings} - * @return the part of s before the colon, or the whole thing if it contains no colon - */ - private static String checkerName(String s) { - int colonPos = s.indexOf(":"); - if (colonPos == -1) { - return s; + return null; + } + + /** + * Given an annotation argument for an element of type String[], return a list of strings. Returns + * null if the list of suppressed strings is unknown (e.g., if the argument is a name expression). + * + * @param e an annotation argument + * @return the strings expressed by {@code e} + */ + private static @Nullable List annotationElementStrings(Expression e) { + if (e instanceof StringLiteralExpr) { + return Collections.singletonList(((StringLiteralExpr) e).asString()); + } else if (e instanceof ArrayInitializerExpr) { + NodeList values = ((ArrayInitializerExpr) e).getValues(); + List result = new ArrayList<>(values.size()); + for (Expression v : values) { + if (v instanceof StringLiteralExpr) { + result.add(((StringLiteralExpr) v).asString()); + } else if (v instanceof NameExpr) { + // TODO: is it better to return null here, thus causing nothing under this + // warning to be treated as "suppressed", or to return any keys that are string + // literals? + // Returning null here ensures that if any argument to the SW annotation isn't a + // string literal, then none of them are considered. + return null; } else { - return s.substring(colonPos + 1); + throw new BugInCF("Unexpected annotation element of type %s: %s", v.getClass(), v); } + } + return result; + } else if (e instanceof NameExpr) { + // TODO: it would be better to check if the NameExpr represents a compile-time constant, + // and, if so, to use its value. But, it's not possible to determine that from just the + // result of the parser. + return null; + } else { + throw new BugInCF("Unexpected %s: %s", e.getClass(), e); + } + } + + /** + * Returns the "checker name" part of a SuppressWarnings string: the part before the colon, or the + * whole thing if it contains no colon. + * + * @param s a SuppressWarnings string: the argument to {@code @SuppressWarnings} + * @return the part of s before the colon, or the whole thing if it contains no colon + */ + private static String checkerName(String s) { + int colonPos = s.indexOf(":"); + if (colonPos == -1) { + return s; + } else { + return s.substring(colonPos + 1); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java index a4be1123196..af69a930f10 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java @@ -5,21 +5,11 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Options; - -import org.checkerframework.checker.mustcall.qual.MustCallUnknown; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; - import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -32,6 +22,13 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; +import org.checkerframework.checker.mustcall.qual.MustCallUnknown; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; /** * Generates a stub file from a single class or an entire package. @@ -42,429 +39,428 @@ * @checker_framework.manual #stub Using stub classes */ public class StubGenerator { - /** The indentation for the class. */ - private static final String INDENTION = " "; - - /** The output stream. */ - private final PrintStream out; - - /** the current indentation for the line being processed. */ - private String currentIndention = ""; - - /** the package of the class being processed. */ - private String currentPackage = null; - - /** Constructs a {@code StubGenerator} that outputs to {@code System.out}. */ - public StubGenerator() { - this(System.out); - } - - /** - * Constructs a {@code StubGenerator} that outputs to the provided output stream. - * - * @param out the output stream - */ - public StubGenerator(PrintStream out) { - this.out = out; - } - - /** - * Constructs a {@code StubGenerator} that outputs to the provided output stream. - * - * @param out the output stream - */ - public StubGenerator(OutputStream out) { - this.out = new PrintStream(out); - } - - /** Generate the stub file for all the classes within the provided package. */ - public void stubFromField(Element elt) { - if (!(elt.getKind() == ElementKind.FIELD)) { - return; - } - - String pkg = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); - if (!"".equals(pkg)) { - currentPackage = pkg; - currentIndention = " "; - indent(); - } - VariableElement field = (VariableElement) elt; - printFieldDecl(field); - } - - /** Generate the stub file for all the classes within the provided package. */ - public void stubFromPackage(PackageElement packageElement) { - currentPackage = packageElement.getQualifiedName().toString(); - - indent(); - out.print("package "); - out.print(currentPackage); - out.println(";"); - - for (TypeElement element : ElementFilter.typesIn(packageElement.getEnclosedElements())) { - if (isPublicOrProtected(element)) { - out.println(); - printClass(element); - } - } - } - - /** - * Generate the stub file for all the classes within the package that contains {@code elt}. - * - * @param elt a method or constructor; generate stub files for its package - */ - public void stubFromMethod(ExecutableElement elt) { - if (!(elt.getKind() == ElementKind.CONSTRUCTOR || elt.getKind() == ElementKind.METHOD)) { - return; - } - - String newPackage = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); - if (!newPackage.isEmpty()) { - currentPackage = newPackage; - currentIndention = " "; - indent(); - } - - printMethodDecl(elt); - } - - /** Generate the stub file for provided class. The generated file includes the package name. */ - public void stubFromType(TypeElement typeElement) { - - // only output stub for classes or interfaces. not enums - if (typeElement.getKind() != ElementKind.CLASS - && typeElement.getKind() != ElementKind.INTERFACE) { - return; - } - - String newPackageName = - ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(typeElement)); - boolean newPackage = !newPackageName.equals(currentPackage); - currentPackage = newPackageName; - - if (newPackage) { - indent(); - - out.print("package "); - out.print(currentPackage); - out.println(";"); - out.println(); - } - String fullClassName = ElementUtils.getQualifiedClassName(typeElement).toString(); - - String className = - fullClassName.substring( - fullClassName.indexOf(currentPackage) - + currentPackage.length() - // +1 because currentPackage doesn't include - // the . between the package name and the classname - + 1); - - int index = className.lastIndexOf('.'); - if (index == -1) { - printClass(typeElement); - } else { - String outer = className.substring(0, index); - printClass(typeElement, outer.replace('.', '$')); - } - } - - /** helper method that outputs the index for the provided class. */ - private void printClass(TypeElement typeElement) { - printClass(typeElement, null); - } - - /** - * Helper method that prints the stub file for the provided class. - * - * @param typeElement the class to output - * @param outerClass the outer class of the class, or null if {@code typeElement} is a top-level - * class - */ - private void printClass(TypeElement typeElement, @Nullable String outerClass) { - indent(); - - List teannos = typeElement.getAnnotationMirrors(); - if (teannos != null && !teannos.isEmpty()) { - for (AnnotationMirror am : teannos) { - out.println(am); - } - } - - // This could be a `switch` statement. - if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { - out.print("@interface"); - } else if (typeElement.getKind() == ElementKind.ENUM) { - out.print("enum"); - } else if (typeElement.getKind() == ElementKind.INTERFACE) { - out.print("interface"); - } else if (ElementUtils.isRecordElement(typeElement)) { - out.print("record"); - } else if (typeElement.getKind() == ElementKind.CLASS) { - out.print("class"); - } else { - // Shouldn't this throw an exception? - return; - } - - out.print(' '); - if (outerClass != null) { - out.print(outerClass + "$"); - } - out.print(typeElement.getSimpleName()); - - // Type parameters - if (!typeElement.getTypeParameters().isEmpty()) { - out.print('<'); - out.print(formatList(typeElement.getTypeParameters())); - out.print('>'); - } - - // Extends - if (typeElement.getSuperclass().getKind() != TypeKind.NONE - && !TypesUtils.isObject(typeElement.getSuperclass())) { - out.print(" extends "); - out.print(formatType(typeElement.getSuperclass())); - } - - // implements - if (!typeElement.getInterfaces().isEmpty()) { - boolean isInterface = typeElement.getKind() == ElementKind.INTERFACE; - out.print(isInterface ? " extends " : " implements "); - List ls = - CollectionsPlume.mapList( - StubGenerator::formatType, typeElement.getInterfaces()); - out.print(formatList(ls)); - } - - out.println(" {"); - String tempIndention = currentIndention; - - currentIndention = currentIndention + INDENTION; - - // Inner classes, which the stub generator prints later. - List innerClass = new ArrayList<>(); - // side-effects innerClass - printTypeMembers(typeElement.getEnclosedElements(), innerClass); - - currentIndention = tempIndention; - indent(); - out.println("}"); - - for (TypeElement element : innerClass) { - printClass(element, typeElement.getSimpleName().toString()); - } - } - - /** - * Helper method that outputs the public or protected inner members of a class. - * - * @param members list of the class members - */ - private void printTypeMembers(List members, List innerClass) { - for (Element element : members) { - if (isPublicOrProtected(element)) { - printMember(element, innerClass); - } - } - } - - /** Helper method that outputs the declaration of the member. */ - private void printMember(Element member, List innerClass) { - if (member.getKind().isField()) { - printFieldDecl((VariableElement) member); - } else if (member instanceof ExecutableElement) { - printMethodDecl((ExecutableElement) member); - } else if (member instanceof TypeElement) { - innerClass.add((TypeElement) member); - } - } - - /** - * Helper method that outputs the field declaration for the given field. - * - *

It indicates whether the field is {@code protected}. - */ - private void printFieldDecl(VariableElement field) { - if ("class".equals(field.getSimpleName().toString())) { - error("Cannot write class literals in stub files."); - return; - } - - indent(); - - List veannos = field.getAnnotationMirrors(); - if (veannos != null && !veannos.isEmpty()) { - for (AnnotationMirror am : veannos) { - out.println(am); - } - } - - // if protected, indicate that, but not public - if (field.getModifiers().contains(Modifier.PROTECTED)) { - out.print("protected "); - } - if (field.getModifiers().contains(Modifier.STATIC)) { - out.print("static "); - } - if (field.getModifiers().contains(Modifier.FINAL)) { - out.print("final "); - } - - out.print(formatType(field.asType())); - - out.print(" "); - out.print(field.getSimpleName()); - out.println(';'); - } - - /** - * Helper method that outputs the method declaration for the given method. - * - *

IT indicates whether the field is {@code protected}. - */ - private void printMethodDecl(ExecutableElement method) { - indent(); - - List eeannos = method.getAnnotationMirrors(); - if (eeannos != null && !eeannos.isEmpty()) { - for (AnnotationMirror am : eeannos) { - out.println(am); - } - } - - // if protected, indicate that, but not public - if (method.getModifiers().contains(Modifier.PROTECTED)) { - out.print("protected "); - } - if (method.getModifiers().contains(Modifier.STATIC)) { - out.print("static "); - } - - // print Generic arguments - if (!method.getTypeParameters().isEmpty()) { - out.print('<'); - out.print(formatList(method.getTypeParameters())); - out.print("> "); - } - - // not return type for constructors - if (method.getKind() != ElementKind.CONSTRUCTOR) { - out.print(formatType(method.getReturnType())); - out.print(" "); - out.print(method.getSimpleName()); - } else { - out.print(method.getEnclosingElement().getSimpleName()); - } - - out.print('('); - - boolean isFirst = true; - for (VariableElement param : method.getParameters()) { - if (!isFirst) { - out.print(", "); - } - out.print(formatType(param.asType())); - out.print(' '); - out.print(param.getSimpleName()); - isFirst = false; - } - - out.print(')'); - - if (!method.getThrownTypes().isEmpty()) { - out.print(" throws "); - List ltt = - CollectionsPlume.mapList(StubGenerator::formatType, method.getThrownTypes()); - out.print(formatList(ltt)); - } - out.println(';'); - } - - /** Indent the current line. */ - private void indent() { - out.print(currentIndention); - } - - /** - * Return a string representation of the list in the form of {@code item1, item2, item3, ...}, - * without surrounding square brackets as the default representation has. - * - * @param lst a list to format - * @return a string representation of the list, without surrounding square brackets - */ - private String formatList(@MustCallUnknown List lst) { - return StringsPlume.join(", ", lst); - } - - /** Returns true if the element is public or protected element. */ - private boolean isPublicOrProtected(Element element) { - return element.getModifiers().contains(Modifier.PUBLIC) - || element.getModifiers().contains(Modifier.PROTECTED); - } - - /** - * Returns the simple name of the type. - * - * @param typeRep a type - * @return the simple name of the type - */ - private static String formatType(TypeMirror typeRep) { - StringTokenizer tokenizer = new StringTokenizer(typeRep.toString(), "()<>[], ", true); - StringBuilder sb = new StringBuilder(); - - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - if (token.length() == 1 || token.lastIndexOf('.') == -1) { - sb.append(token); - } else { - int index = token.lastIndexOf('.'); - sb.append(token.substring(index + 1)); - } - } - return sb.toString(); - } - - /** - * The main entry point to StubGenerator. - * - * @param args command-line arguments - */ - @SuppressWarnings("signature") // User-supplied arguments to main - public static void main(String[] args) { - if (args.length != 1) { - System.out.println("Usage:"); - System.out.println(" java StubGenerator [class or package name]"); - return; - } - - Context context = new Context(); - Options options = Options.instance(context); - if (SystemUtil.jreVersion == 8) { - options.put(Option.SOURCE, "8"); - options.put(Option.TARGET, "8"); - } - - JavaCompiler javac = JavaCompiler.instance(context); - javac.initModules(com.sun.tools.javac.util.List.nil()); - javac.enterDone(); - - ProcessingEnvironment env = JavacProcessingEnvironment.instance(context); - - StubGenerator generator = new StubGenerator(); - - if (env.getElementUtils().getPackageElement(args[0]) != null) { - generator.stubFromPackage(env.getElementUtils().getPackageElement(args[0])); - } else if (env.getElementUtils().getTypeElement(args[0]) != null) { - generator.stubFromType(env.getElementUtils().getTypeElement(args[0])); - } else { - error("Couldn't find a package or a class named " + args[0]); - } - } - - private static void error(String string) { - System.err.println("StubGenerator: " + string); + /** The indentation for the class. */ + private static final String INDENTION = " "; + + /** The output stream. */ + private final PrintStream out; + + /** the current indentation for the line being processed. */ + private String currentIndention = ""; + + /** the package of the class being processed. */ + private String currentPackage = null; + + /** Constructs a {@code StubGenerator} that outputs to {@code System.out}. */ + public StubGenerator() { + this(System.out); + } + + /** + * Constructs a {@code StubGenerator} that outputs to the provided output stream. + * + * @param out the output stream + */ + public StubGenerator(PrintStream out) { + this.out = out; + } + + /** + * Constructs a {@code StubGenerator} that outputs to the provided output stream. + * + * @param out the output stream + */ + public StubGenerator(OutputStream out) { + this.out = new PrintStream(out); + } + + /** Generate the stub file for all the classes within the provided package. */ + public void stubFromField(Element elt) { + if (!(elt.getKind() == ElementKind.FIELD)) { + return; + } + + String pkg = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); + if (!"".equals(pkg)) { + currentPackage = pkg; + currentIndention = " "; + indent(); + } + VariableElement field = (VariableElement) elt; + printFieldDecl(field); + } + + /** Generate the stub file for all the classes within the provided package. */ + public void stubFromPackage(PackageElement packageElement) { + currentPackage = packageElement.getQualifiedName().toString(); + + indent(); + out.print("package "); + out.print(currentPackage); + out.println(";"); + + for (TypeElement element : ElementFilter.typesIn(packageElement.getEnclosedElements())) { + if (isPublicOrProtected(element)) { + out.println(); + printClass(element); + } + } + } + + /** + * Generate the stub file for all the classes within the package that contains {@code elt}. + * + * @param elt a method or constructor; generate stub files for its package + */ + public void stubFromMethod(ExecutableElement elt) { + if (!(elt.getKind() == ElementKind.CONSTRUCTOR || elt.getKind() == ElementKind.METHOD)) { + return; } + + String newPackage = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); + if (!newPackage.isEmpty()) { + currentPackage = newPackage; + currentIndention = " "; + indent(); + } + + printMethodDecl(elt); + } + + /** Generate the stub file for provided class. The generated file includes the package name. */ + public void stubFromType(TypeElement typeElement) { + + // only output stub for classes or interfaces. not enums + if (typeElement.getKind() != ElementKind.CLASS + && typeElement.getKind() != ElementKind.INTERFACE) { + return; + } + + String newPackageName = + ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(typeElement)); + boolean newPackage = !newPackageName.equals(currentPackage); + currentPackage = newPackageName; + + if (newPackage) { + indent(); + + out.print("package "); + out.print(currentPackage); + out.println(";"); + out.println(); + } + String fullClassName = ElementUtils.getQualifiedClassName(typeElement).toString(); + + String className = + fullClassName.substring( + fullClassName.indexOf(currentPackage) + + currentPackage.length() + // +1 because currentPackage doesn't include + // the . between the package name and the classname + + 1); + + int index = className.lastIndexOf('.'); + if (index == -1) { + printClass(typeElement); + } else { + String outer = className.substring(0, index); + printClass(typeElement, outer.replace('.', '$')); + } + } + + /** helper method that outputs the index for the provided class. */ + private void printClass(TypeElement typeElement) { + printClass(typeElement, null); + } + + /** + * Helper method that prints the stub file for the provided class. + * + * @param typeElement the class to output + * @param outerClass the outer class of the class, or null if {@code typeElement} is a top-level + * class + */ + private void printClass(TypeElement typeElement, @Nullable String outerClass) { + indent(); + + List teannos = typeElement.getAnnotationMirrors(); + if (teannos != null && !teannos.isEmpty()) { + for (AnnotationMirror am : teannos) { + out.println(am); + } + } + + // This could be a `switch` statement. + if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { + out.print("@interface"); + } else if (typeElement.getKind() == ElementKind.ENUM) { + out.print("enum"); + } else if (typeElement.getKind() == ElementKind.INTERFACE) { + out.print("interface"); + } else if (ElementUtils.isRecordElement(typeElement)) { + out.print("record"); + } else if (typeElement.getKind() == ElementKind.CLASS) { + out.print("class"); + } else { + // Shouldn't this throw an exception? + return; + } + + out.print(' '); + if (outerClass != null) { + out.print(outerClass + "$"); + } + out.print(typeElement.getSimpleName()); + + // Type parameters + if (!typeElement.getTypeParameters().isEmpty()) { + out.print('<'); + out.print(formatList(typeElement.getTypeParameters())); + out.print('>'); + } + + // Extends + if (typeElement.getSuperclass().getKind() != TypeKind.NONE + && !TypesUtils.isObject(typeElement.getSuperclass())) { + out.print(" extends "); + out.print(formatType(typeElement.getSuperclass())); + } + + // implements + if (!typeElement.getInterfaces().isEmpty()) { + boolean isInterface = typeElement.getKind() == ElementKind.INTERFACE; + out.print(isInterface ? " extends " : " implements "); + List ls = + CollectionsPlume.mapList(StubGenerator::formatType, typeElement.getInterfaces()); + out.print(formatList(ls)); + } + + out.println(" {"); + String tempIndention = currentIndention; + + currentIndention = currentIndention + INDENTION; + + // Inner classes, which the stub generator prints later. + List innerClass = new ArrayList<>(); + // side-effects innerClass + printTypeMembers(typeElement.getEnclosedElements(), innerClass); + + currentIndention = tempIndention; + indent(); + out.println("}"); + + for (TypeElement element : innerClass) { + printClass(element, typeElement.getSimpleName().toString()); + } + } + + /** + * Helper method that outputs the public or protected inner members of a class. + * + * @param members list of the class members + */ + private void printTypeMembers(List members, List innerClass) { + for (Element element : members) { + if (isPublicOrProtected(element)) { + printMember(element, innerClass); + } + } + } + + /** Helper method that outputs the declaration of the member. */ + private void printMember(Element member, List innerClass) { + if (member.getKind().isField()) { + printFieldDecl((VariableElement) member); + } else if (member instanceof ExecutableElement) { + printMethodDecl((ExecutableElement) member); + } else if (member instanceof TypeElement) { + innerClass.add((TypeElement) member); + } + } + + /** + * Helper method that outputs the field declaration for the given field. + * + *

It indicates whether the field is {@code protected}. + */ + private void printFieldDecl(VariableElement field) { + if ("class".equals(field.getSimpleName().toString())) { + error("Cannot write class literals in stub files."); + return; + } + + indent(); + + List veannos = field.getAnnotationMirrors(); + if (veannos != null && !veannos.isEmpty()) { + for (AnnotationMirror am : veannos) { + out.println(am); + } + } + + // if protected, indicate that, but not public + if (field.getModifiers().contains(Modifier.PROTECTED)) { + out.print("protected "); + } + if (field.getModifiers().contains(Modifier.STATIC)) { + out.print("static "); + } + if (field.getModifiers().contains(Modifier.FINAL)) { + out.print("final "); + } + + out.print(formatType(field.asType())); + + out.print(" "); + out.print(field.getSimpleName()); + out.println(';'); + } + + /** + * Helper method that outputs the method declaration for the given method. + * + *

IT indicates whether the field is {@code protected}. + */ + private void printMethodDecl(ExecutableElement method) { + indent(); + + List eeannos = method.getAnnotationMirrors(); + if (eeannos != null && !eeannos.isEmpty()) { + for (AnnotationMirror am : eeannos) { + out.println(am); + } + } + + // if protected, indicate that, but not public + if (method.getModifiers().contains(Modifier.PROTECTED)) { + out.print("protected "); + } + if (method.getModifiers().contains(Modifier.STATIC)) { + out.print("static "); + } + + // print Generic arguments + if (!method.getTypeParameters().isEmpty()) { + out.print('<'); + out.print(formatList(method.getTypeParameters())); + out.print("> "); + } + + // not return type for constructors + if (method.getKind() != ElementKind.CONSTRUCTOR) { + out.print(formatType(method.getReturnType())); + out.print(" "); + out.print(method.getSimpleName()); + } else { + out.print(method.getEnclosingElement().getSimpleName()); + } + + out.print('('); + + boolean isFirst = true; + for (VariableElement param : method.getParameters()) { + if (!isFirst) { + out.print(", "); + } + out.print(formatType(param.asType())); + out.print(' '); + out.print(param.getSimpleName()); + isFirst = false; + } + + out.print(')'); + + if (!method.getThrownTypes().isEmpty()) { + out.print(" throws "); + List ltt = + CollectionsPlume.mapList(StubGenerator::formatType, method.getThrownTypes()); + out.print(formatList(ltt)); + } + out.println(';'); + } + + /** Indent the current line. */ + private void indent() { + out.print(currentIndention); + } + + /** + * Return a string representation of the list in the form of {@code item1, item2, item3, ...}, + * without surrounding square brackets as the default representation has. + * + * @param lst a list to format + * @return a string representation of the list, without surrounding square brackets + */ + private String formatList(@MustCallUnknown List lst) { + return StringsPlume.join(", ", lst); + } + + /** Returns true if the element is public or protected element. */ + private boolean isPublicOrProtected(Element element) { + return element.getModifiers().contains(Modifier.PUBLIC) + || element.getModifiers().contains(Modifier.PROTECTED); + } + + /** + * Returns the simple name of the type. + * + * @param typeRep a type + * @return the simple name of the type + */ + private static String formatType(TypeMirror typeRep) { + StringTokenizer tokenizer = new StringTokenizer(typeRep.toString(), "()<>[], ", true); + StringBuilder sb = new StringBuilder(); + + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (token.length() == 1 || token.lastIndexOf('.') == -1) { + sb.append(token); + } else { + int index = token.lastIndexOf('.'); + sb.append(token.substring(index + 1)); + } + } + return sb.toString(); + } + + /** + * The main entry point to StubGenerator. + * + * @param args command-line arguments + */ + @SuppressWarnings("signature") // User-supplied arguments to main + public static void main(String[] args) { + if (args.length != 1) { + System.out.println("Usage:"); + System.out.println(" java StubGenerator [class or package name]"); + return; + } + + Context context = new Context(); + Options options = Options.instance(context); + if (SystemUtil.jreVersion == 8) { + options.put(Option.SOURCE, "8"); + options.put(Option.TARGET, "8"); + } + + JavaCompiler javac = JavaCompiler.instance(context); + javac.initModules(com.sun.tools.javac.util.List.nil()); + javac.enterDone(); + + ProcessingEnvironment env = JavacProcessingEnvironment.instance(context); + + StubGenerator generator = new StubGenerator(); + + if (env.getElementUtils().getPackageElement(args[0]) != null) { + generator.stubFromPackage(env.getElementUtils().getPackageElement(args[0])); + } else if (env.getElementUtils().getTypeElement(args[0]) != null) { + generator.stubFromType(env.getElementUtils().getTypeElement(args[0])); + } else { + error("Couldn't find a package or a class named " + args[0]); + } + } + + private static void error(String string) { + System.err.println("StubGenerator: " + string); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java index 68255195aea..ca029981a40 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java @@ -34,7 +34,22 @@ import com.github.javaparser.ast.type.VoidType; import com.github.javaparser.ast.type.WildcardType; import com.github.javaparser.ast.visitor.GenericVisitorAdapter; - +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AClass; import org.checkerframework.afu.scenelib.annotations.el.ADeclaration; @@ -59,23 +74,6 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.reflection.Signatures; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedWriter; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * Convert a JAIF file plus a stub file into index files (JAIFs). Note that the resulting index * files will not include annotation definitions, for which stubfiles do not generally provide @@ -85,631 +83,622 @@ * #main(String[])} method converts multiple stub files, instantiating the class multiple times. */ public class ToIndexFileConverter extends GenericVisitorAdapter { - // The possessive modifiers "*+" are for efficiency only. - // private static Pattern packagePattern = - // Pattern.compile("\\bpackage *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); - /** A pattern that matches an import statement. */ - private static final Pattern importPattern = - Pattern.compile("\\bimport *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); - - /** - * Package name that is active at the current point in the input file. Changes as package - * declarations are encountered. - */ - private final @DotSeparatedIdentifiers String pkgName; - - /** Imports that appear in the stub file. */ - private final List imports; - - /** A scene read from the input JAIF file, and will be written to the output JAIF file. */ - private final AScene scene; - - /** - * Creates a new ToIndexFileConverter. - * - * @param pkgDecl the AST node for package declaration - * @param importDecls the AST nodes for import declarations - * @param scene scene for visitor methods to fill in - */ - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString - public ToIndexFileConverter( - @Nullable PackageDeclaration pkgDecl, - List importDecls, - AScene scene) { - this.scene = scene; - pkgName = pkgDecl == null ? null : pkgDecl.getNameAsString(); - if (importDecls == null) { - imports = Collections.emptyList(); - } else { - ArrayList imps = new ArrayList<>(importDecls.size()); - for (ImportDeclaration decl : importDecls) { - if (!decl.isStatic()) { - Matcher m = importPattern.matcher(decl.toString()); - if (m.find()) { - String s = m.group(1); - if (s != null) { - imps.add(s); - } - } - } + // The possessive modifiers "*+" are for efficiency only. + // private static Pattern packagePattern = + // Pattern.compile("\\bpackage *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); + /** A pattern that matches an import statement. */ + private static final Pattern importPattern = + Pattern.compile("\\bimport *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); + + /** + * Package name that is active at the current point in the input file. Changes as package + * declarations are encountered. + */ + private final @DotSeparatedIdentifiers String pkgName; + + /** Imports that appear in the stub file. */ + private final List imports; + + /** A scene read from the input JAIF file, and will be written to the output JAIF file. */ + private final AScene scene; + + /** + * Creates a new ToIndexFileConverter. + * + * @param pkgDecl the AST node for package declaration + * @param importDecls the AST nodes for import declarations + * @param scene scene for visitor methods to fill in + */ + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString + public ToIndexFileConverter( + @Nullable PackageDeclaration pkgDecl, List importDecls, AScene scene) { + this.scene = scene; + pkgName = pkgDecl == null ? null : pkgDecl.getNameAsString(); + if (importDecls == null) { + imports = Collections.emptyList(); + } else { + ArrayList imps = new ArrayList<>(importDecls.size()); + for (ImportDeclaration decl : importDecls) { + if (!decl.isStatic()) { + Matcher m = importPattern.matcher(decl.toString()); + if (m.find()) { + String s = m.group(1); + if (s != null) { + imps.add(s); } - imps.trimToSize(); - imports = Collections.unmodifiableList(imps); + } } + } + imps.trimToSize(); + imports = Collections.unmodifiableList(imps); } - - /** - * Parse stub files and write out equivalent JAIFs. Note that the results do not include - * annotation definitions, for which stubfiles do not generally provide complete information. - * - * @param args name of JAIF with annotation definition, followed by names of stub files to be - * converted (if none given, program reads from standard input) - */ - public static void main(String[] args) { - if (args.length < 1) { - System.err.println("usage: java ToIndexFileConverter myfile.jaif [stubfile...]"); - System.err.println("(myfile.jaif contains needed annotation definitions)"); - System.exit(1); - } - - AScene scene = new AScene(); - try { - // args[0] is a jaif file with needed annotation definitions - IndexFileParser.parseFile(args[0], scene); - - if (args.length == 1) { - convert(scene, System.in, System.out); - return; - } - - for (int i = 1; i < args.length; i++) { - String f0 = args[i]; - String f1 = - (f0.endsWith(".astub") ? f0.substring(0, f0.length() - 6) : f0) + ".jaif"; - try (InputStream in = new BufferedInputStream(new FileInputStream(f0)); - OutputStream out = new BufferedOutputStream(new FileOutputStream(f1)); ) { - convert(new AScene(scene), in, out); - } - } - } catch (Throwable e) { - e.printStackTrace(); - System.exit(1); - } + } + + /** + * Parse stub files and write out equivalent JAIFs. Note that the results do not include + * annotation definitions, for which stubfiles do not generally provide complete information. + * + * @param args name of JAIF with annotation definition, followed by names of stub files to be + * converted (if none given, program reads from standard input) + */ + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("usage: java ToIndexFileConverter myfile.jaif [stubfile...]"); + System.err.println("(myfile.jaif contains needed annotation definitions)"); + System.exit(1); } - /** - * Augment given scene with information from stubfile, reading stubs from input stream and - * writing JAIF to output stream. - * - * @param scene the initial scene - * @param in stubfile contents - * @param out the output stream for the JAIF file that holds the augmented scene - * @throws ParseException if the stub file cannot be parsed - * @throws DefException if two different definitions of the same annotation cannot be unified - * @throws IOException if there is trouble with file reading or writing - */ - private static void convert(AScene scene, InputStream in, OutputStream out) - throws IOException, DefException, ParseException { - StubUnit iu; - try { - iu = JavaParserUtil.parseStubUnit(in); - } catch (ParseProblemException e) { - iu = null; - throw new BugInCF( - "ToIndexFileConverter: exception from JavaParser.parseStubUnit for InputStream." - + System.lineSeparator() - + "Problem message with problems encountered: " - + e.getMessage()); - } - extractScene(iu, scene); - try (Writer w = new BufferedWriter(new OutputStreamWriter(out))) { - IndexFileWriter.write(scene, w); + AScene scene = new AScene(); + try { + // args[0] is a jaif file with needed annotation definitions + IndexFileParser.parseFile(args[0], scene); + + if (args.length == 1) { + convert(scene, System.in, System.out); + return; + } + + for (int i = 1; i < args.length; i++) { + String f0 = args[i]; + String f1 = (f0.endsWith(".astub") ? f0.substring(0, f0.length() - 6) : f0) + ".jaif"; + try (InputStream in = new BufferedInputStream(new FileInputStream(f0)); + OutputStream out = new BufferedOutputStream(new FileOutputStream(f1)); ) { + convert(new AScene(scene), in, out); } + } + } catch (Throwable e) { + e.printStackTrace(); + System.exit(1); } - - /** - * Entry point of recursive-descent IndexUnit to AScene transformer. It operates by visiting the - * stub and scene in parallel, descending into them in the same way. It augments the existing - * scene (it does not create a new scene). - * - * @param iu {@link StubUnit} representing stubfile - */ - private static void extractScene(StubUnit iu, AScene scene) { - for (CompilationUnit cu : iu.getCompilationUnits()) { - NodeList> typeDecls = cu.getTypes(); - if (typeDecls != null && cu.getPackageDeclaration().isPresent()) { - List impDecls = cu.getImports(); - PackageDeclaration pkgDecl = cu.getPackageDeclaration().get(); - for (TypeDeclaration typeDecl : typeDecls) { - ToIndexFileConverter converter = - new ToIndexFileConverter(pkgDecl, impDecls, scene); - String pkgName = converter.pkgName; - String name = typeDecl.getNameAsString(); - if (pkgName != null) { - name = pkgName + "." + name; - } - typeDecl.accept(converter, scene.classes.getVivify(name)); - } - } - } + } + + /** + * Augment given scene with information from stubfile, reading stubs from input stream and writing + * JAIF to output stream. + * + * @param scene the initial scene + * @param in stubfile contents + * @param out the output stream for the JAIF file that holds the augmented scene + * @throws ParseException if the stub file cannot be parsed + * @throws DefException if two different definitions of the same annotation cannot be unified + * @throws IOException if there is trouble with file reading or writing + */ + private static void convert(AScene scene, InputStream in, OutputStream out) + throws IOException, DefException, ParseException { + StubUnit iu; + try { + iu = JavaParserUtil.parseStubUnit(in); + } catch (ParseProblemException e) { + iu = null; + throw new BugInCF( + "ToIndexFileConverter: exception from JavaParser.parseStubUnit for InputStream." + + System.lineSeparator() + + "Problem message with problems encountered: " + + e.getMessage()); } - - /** - * Builds simplified annotation from its declaration. Only the name is included, because - * stubfiles do not generally have access to the full definitions of annotations. - */ - private static @Nullable Annotation extractAnnotation(AnnotationExpr expr) { - String exprName = expr.toString().substring(1); // leave off leading '@' - - // Eliminate jdk.Profile+Annotation, a synthetic annotation that - // the JDK adds, apparently for profiling. - if (exprName.contains("+")) { - return null; + extractScene(iu, scene); + try (Writer w = new BufferedWriter(new OutputStreamWriter(out))) { + IndexFileWriter.write(scene, w); + } + } + + /** + * Entry point of recursive-descent IndexUnit to AScene transformer. It operates by visiting the + * stub and scene in parallel, descending into them in the same way. It augments the existing + * scene (it does not create a new scene). + * + * @param iu {@link StubUnit} representing stubfile + */ + private static void extractScene(StubUnit iu, AScene scene) { + for (CompilationUnit cu : iu.getCompilationUnits()) { + NodeList> typeDecls = cu.getTypes(); + if (typeDecls != null && cu.getPackageDeclaration().isPresent()) { + List impDecls = cu.getImports(); + PackageDeclaration pkgDecl = cu.getPackageDeclaration().get(); + for (TypeDeclaration typeDecl : typeDecls) { + ToIndexFileConverter converter = new ToIndexFileConverter(pkgDecl, impDecls, scene); + String pkgName = converter.pkgName; + String name = typeDecl.getNameAsString(); + if (pkgName != null) { + name = pkgName + "." + name; + } + typeDecl.accept(converter, scene.classes.getVivify(name)); } - @SuppressWarnings("signature") // special case for annotations containing "+" - AnnotationDef def = - new AnnotationDef(exprName, "ToIndexFileConverter.extractAnnotation(" + expr + ")"); - def.setFieldTypes(Collections.emptyMap()); - return new Annotation(def, Collections.emptyMap()); + } } - - @Override - public Void visit(AnnotationDeclaration decl, AElement elem) { - return null; + } + + /** + * Builds simplified annotation from its declaration. Only the name is included, because stubfiles + * do not generally have access to the full definitions of annotations. + */ + private static @Nullable Annotation extractAnnotation(AnnotationExpr expr) { + String exprName = expr.toString().substring(1); // leave off leading '@' + + // Eliminate jdk.Profile+Annotation, a synthetic annotation that + // the JDK adds, apparently for profiling. + if (exprName.contains("+")) { + return null; } - - @Override - public Void visit(BlockStmt stmt, AElement elem) { - return null; - // super.visit(stmt, elem); + @SuppressWarnings("signature") // special case for annotations containing "+" + AnnotationDef def = + new AnnotationDef(exprName, "ToIndexFileConverter.extractAnnotation(" + expr + ")"); + def.setFieldTypes(Collections.emptyMap()); + return new Annotation(def, Collections.emptyMap()); + } + + @Override + public Void visit(AnnotationDeclaration decl, AElement elem) { + return null; + } + + @Override + public Void visit(BlockStmt stmt, AElement elem) { + return null; + // super.visit(stmt, elem); + } + + @Override + public Void visit(ClassOrInterfaceDeclaration decl, AElement elem) { + visitDecl(decl, (ADeclaration) elem); + return super.visit(decl, elem); + } + + @Override + public Void visit(ConstructorDeclaration decl, AElement elem) { + List params = decl.getParameters(); + List rcvrAnnos = decl.getAnnotations(); + BlockStmt body = decl.getBody(); + StringBuilder sb = new StringBuilder("("); + AClass clazz = (AClass) elem; + AMethod method; + + // Some of the methods in the generated parser use null to represent an empty list. + if (params != null) { + for (Parameter param : params) { + Type ptype = param.getType(); + sb.append(getJVML(ptype)); + } } - - @Override - public Void visit(ClassOrInterfaceDeclaration decl, AElement elem) { - visitDecl(decl, (ADeclaration) elem); - return super.visit(decl, elem); + sb.append(")V"); + method = clazz.methods.getVivify(sb.toString()); + visitDecl(decl, method); + if (params != null) { + for (int i = 0; i < params.size(); i++) { + Parameter param = params.get(i); + AField field = method.parameters.getVivify(i); + visitType(param.getType(), field.type); + } } - - @Override - public Void visit(ConstructorDeclaration decl, AElement elem) { - List params = decl.getParameters(); - List rcvrAnnos = decl.getAnnotations(); - BlockStmt body = decl.getBody(); - StringBuilder sb = new StringBuilder("("); - AClass clazz = (AClass) elem; - AMethod method; - - // Some of the methods in the generated parser use null to represent an empty list. - if (params != null) { - for (Parameter param : params) { - Type ptype = param.getType(); - sb.append(getJVML(ptype)); - } - } - sb.append(")V"); - method = clazz.methods.getVivify(sb.toString()); - visitDecl(decl, method); - if (params != null) { - for (int i = 0; i < params.size(); i++) { - Parameter param = params.get(i); - AField field = method.parameters.getVivify(i); - visitType(param.getType(), field.type); - } - } - if (rcvrAnnos != null) { - for (AnnotationExpr expr : rcvrAnnos) { - Annotation anno = extractAnnotation(expr); - method.receiver.tlAnnotationsHere.add(anno); - } + if (rcvrAnnos != null) { + for (AnnotationExpr expr : rcvrAnnos) { + Annotation anno = extractAnnotation(expr); + method.receiver.tlAnnotationsHere.add(anno); + } + } + return body == null ? null : body.accept(this, method); + // return super.visit(decl, elem); + } + + @Override + public Void visit(EnumConstantDeclaration decl, AElement elem) { + AField field = ((AClass) elem).fields.getVivify(decl.getNameAsString()); + visitDecl(decl, field); + return super.visit(decl, field); + } + + @Override + public Void visit(EnumDeclaration decl, AElement elem) { + visitDecl(decl, (ADeclaration) elem); + return super.visit(decl, elem); + } + + @Override + public Void visit(FieldDeclaration decl, AElement elem) { + for (VariableDeclarator v : decl.getVariables()) { + AClass clazz = (AClass) elem; + AField field = clazz.fields.getVivify(v.getNameAsString()); + visitDecl(decl, field); + visitType(decl.getCommonType(), field.type); + } + return null; + } + + @Override + public Void visit(InitializerDeclaration decl, AElement elem) { + BlockStmt block = decl.getBody(); + AClass clazz = (AClass) elem; + block.accept(this, clazz.methods.getVivify(decl.isStatic() ? "" : "")); + return null; + } + + @Override + public Void visit(MethodDeclaration decl, AElement elem) { + Type type = decl.getType(); + List params = decl.getParameters(); + List typeParams = decl.getTypeParameters(); + Optional rcvrParam = decl.getReceiverParameter(); + BlockStmt body = decl.getBody().orElse(null); + StringBuilder sb = new StringBuilder(decl.getNameAsString()).append('('); + AClass clazz = (AClass) elem; + AMethod method; + if (params != null) { + for (Parameter param : params) { + Type ptype = param.getType(); + sb.append(getJVML(ptype)); + } + } + sb.append(')').append(getJVML(type)); + method = clazz.methods.getVivify(sb.toString()); + visitDecl(decl, method); + visitType(type, method.returnType); + if (params != null) { + for (int i = 0; i < params.size(); i++) { + Parameter param = params.get(i); + AField field = method.parameters.getVivify(i); + visitType(param.getType(), field.type); + } + } + if (rcvrParam.isPresent()) { + for (AnnotationExpr expr : rcvrParam.get().getAnnotations()) { + Annotation anno = extractAnnotation(expr); + method.receiver.type.tlAnnotationsHere.add(anno); + } + } + if (typeParams != null) { + for (int i = 0; i < typeParams.size(); i++) { + TypeParameter typeParam = typeParams.get(i); + List bounds = typeParam.getTypeBound(); + if (bounds != null) { + for (int j = 0; j < bounds.size(); j++) { + ClassOrInterfaceType bound = bounds.get(j); + BoundLocation loc = new BoundLocation(i, j); + bound.accept(this, method.bounds.getVivify(loc)); + } } - return body == null ? null : body.accept(this, method); - // return super.visit(decl, elem); + } } - - @Override - public Void visit(EnumConstantDeclaration decl, AElement elem) { - AField field = ((AClass) elem).fields.getVivify(decl.getNameAsString()); - visitDecl(decl, field); - return super.visit(decl, field); + return body == null ? null : body.accept(this, method); + } + + @Override + public Void visit(ObjectCreationExpr expr, AElement elem) { + ClassOrInterfaceType type = expr.getType(); + AClass clazz = scene.classes.getVivify(type.getNameAsString()); + Expression scope = expr.getScope().orElse(null); + List typeArgs = expr.getTypeArguments().orElse(null); + List args = expr.getArguments(); + NodeList> bodyDecls = expr.getAnonymousClassBody().orElse(null); + if (scope != null) { + scope.accept(this, elem); } - - @Override - public Void visit(EnumDeclaration decl, AElement elem) { - visitDecl(decl, (ADeclaration) elem); - return super.visit(decl, elem); + if (args != null) { + for (Expression arg : args) { + arg.accept(this, elem); + } } - - @Override - public Void visit(FieldDeclaration decl, AElement elem) { - for (VariableDeclarator v : decl.getVariables()) { - AClass clazz = (AClass) elem; - AField field = clazz.fields.getVivify(v.getNameAsString()); - visitDecl(decl, field); - visitType(decl.getCommonType(), field.type); + if (typeArgs != null) { + for (Type typeArg : typeArgs) { + typeArg.accept(this, elem); + } + } + type.accept(this, clazz); + if (bodyDecls != null) { + for (BodyDeclaration decl : bodyDecls) { + decl.accept(this, clazz); + } + } + return null; + } + + @Override + public Void visit(VariableDeclarationExpr expr, AElement elem) { + List annos = expr.getAnnotations(); + AMethod method = (AMethod) elem; + List varDecls = expr.getVariables(); + for (int i = 0; i < varDecls.size(); i++) { + VariableDeclarator decl = varDecls.get(i); + LocalLocation loc = new LocalLocation(i, decl.getNameAsString()); + AField field = method.body.locals.getVivify(loc); + visitType(expr.getCommonType(), field.type); + if (annos != null) { + for (AnnotationExpr annoExpr : annos) { + Annotation anno = extractAnnotation(annoExpr); + field.tlAnnotationsHere.add(anno); } - return null; + } } - - @Override - public Void visit(InitializerDeclaration decl, AElement elem) { - BlockStmt block = decl.getBody(); - AClass clazz = (AClass) elem; - block.accept(this, clazz.methods.getVivify(decl.isStatic() ? "" : "")); - return null; + return null; + } + + /** + * Copies information from an AST declaration node to an {@link ADeclaration}. Called by visitors + * for BodyDeclaration subclasses. + */ + private Void visitDecl(BodyDeclaration decl, ADeclaration elem) { + NodeList annoExprs = decl.getAnnotations(); + if (annoExprs != null) { + for (AnnotationExpr annoExpr : annoExprs) { + Annotation anno = extractAnnotation(annoExpr); + elem.tlAnnotationsHere.add(anno); + } } - - @Override - public Void visit(MethodDeclaration decl, AElement elem) { - Type type = decl.getType(); - List params = decl.getParameters(); - List typeParams = decl.getTypeParameters(); - Optional rcvrParam = decl.getReceiverParameter(); - BlockStmt body = decl.getBody().orElse(null); - StringBuilder sb = new StringBuilder(decl.getNameAsString()).append('('); - AClass clazz = (AClass) elem; - AMethod method; - if (params != null) { - for (Parameter param : params) { - Type ptype = param.getType(); - sb.append(getJVML(ptype)); - } + return null; + } + + /** Copies information from an AST type node to an {@link ATypeElement}. */ + private Void visitType(Type type, ATypeElement elem) { + List exprs = type.getAnnotations(); + if (exprs != null) { + for (AnnotationExpr expr : exprs) { + Annotation anno = extractAnnotation(expr); + if (anno != null) { + elem.tlAnnotationsHere.add(anno); } - sb.append(')').append(getJVML(type)); - method = clazz.methods.getVivify(sb.toString()); - visitDecl(decl, method); - visitType(type, method.returnType); - if (params != null) { - for (int i = 0; i < params.size(); i++) { - Parameter param = params.get(i); - AField field = method.parameters.getVivify(i); - visitType(param.getType(), field.type); + } + } + visitInnerTypes(type, elem); + return null; + } + + /** + * Copies information from an AST type node's inner type nodes to an {@link ATypeElement}. + * + * @param type the AST Type node to inspect + * @param elem destination type element + */ + private static Void visitInnerTypes(Type type, ATypeElement elem) { + return type.accept( + new GenericVisitorAdapter>() { + @Override + public Void visit(ClassOrInterfaceType type, List loc) { + if (type.getTypeArguments().isPresent()) { + List typeArgs = type.getTypeArguments().get(); + for (int i = 0; i < typeArgs.size(); i++) { + Type inner = typeArgs.get(i); + List ext = extendedTypePath(loc, 3, i); + visitInnerType(inner, ext); + } } - } - if (rcvrParam.isPresent()) { - for (AnnotationExpr expr : rcvrParam.get().getAnnotations()) { + return null; + } + + @Override + public Void visit(ArrayType type, List loc) { + List ext = loc; + int n = type.getArrayLevel(); + Type currentType = type; + for (int i = 0; i < n; i++) { + ext = extendedTypePath(ext, 1, 0); + for (AnnotationExpr expr : currentType.getAnnotations()) { + ATypeElement typeElem = elem.innerTypes.getVivify(ext); Annotation anno = extractAnnotation(expr); - method.receiver.type.tlAnnotationsHere.add(anno); + typeElem.tlAnnotationsHere.add(anno); + } + currentType = + ((com.github.javaparser.ast.type.ArrayType) currentType).getComponentType(); } - } - if (typeParams != null) { - for (int i = 0; i < typeParams.size(); i++) { - TypeParameter typeParam = typeParams.get(i); - List bounds = typeParam.getTypeBound(); - if (bounds != null) { - for (int j = 0; j < bounds.size(); j++) { - ClassOrInterfaceType bound = bounds.get(j); - BoundLocation loc = new BoundLocation(i, j); - bound.accept(this, method.bounds.getVivify(loc)); - } - } + return null; + } + + @Override + public Void visit(WildcardType type, List loc) { + ReferenceType lower = type.getExtendedType().orElse(null); + ReferenceType upper = type.getSuperType().orElse(null); + if (lower != null) { + List ext = extendedTypePath(loc, 2, 0); + visitInnerType(lower, ext); } - } - return body == null ? null : body.accept(this, method); - } - - @Override - public Void visit(ObjectCreationExpr expr, AElement elem) { - ClassOrInterfaceType type = expr.getType(); - AClass clazz = scene.classes.getVivify(type.getNameAsString()); - Expression scope = expr.getScope().orElse(null); - List typeArgs = expr.getTypeArguments().orElse(null); - List args = expr.getArguments(); - NodeList> bodyDecls = expr.getAnonymousClassBody().orElse(null); - if (scope != null) { - scope.accept(this, elem); - } - if (args != null) { - for (Expression arg : args) { - arg.accept(this, elem); + if (upper != null) { + List ext = extendedTypePath(loc, 2, 0); + visitInnerType(upper, ext); } - } - if (typeArgs != null) { - for (Type typeArg : typeArgs) { - typeArg.accept(this, elem); + return null; + } + + /** Copies information from an AST inner type node to an {@link ATypeElement}. */ + private void visitInnerType(Type type, List loc) { + ATypeElement typeElem = elem.innerTypes.getVivify(loc); + for (AnnotationExpr expr : type.getAnnotations()) { + Annotation anno = extractAnnotation(expr); + typeElem.tlAnnotationsHere.add(anno); + type.accept(this, loc); } - } - type.accept(this, clazz); - if (bodyDecls != null) { - for (BodyDeclaration decl : bodyDecls) { - decl.accept(this, clazz); + } + + /** + * Extends type path by one element. + * + * @see TypePathEntry(int, int) + */ + private List extendedTypePath(List loc, int tag, int arg) { + List path = new ArrayList<>(loc.size() + 1); + path.addAll(loc); + path.add(TypePathEntry.create(tag, arg)); + return path; + } + }, + Collections.emptyList()); + } + + /** + * Computes a type's "binary name". + * + * @param type the type + * @return the type's binary name + */ + private String getJVML(Type type) { + return type.accept( + new GenericVisitorAdapter() { + @Override + public String visit(ClassOrInterfaceType type, Void v) { + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString + @FullyQualifiedName String typeName = type.getNameAsString(); + @SuppressWarnings("signature" // TODO: bug in ToIndexFileConverter: + // resolve requires a @BinaryName, but this passes a @FullyQualifiedName. + // They differ for inner classes. + ) + String name = resolve(typeName); + if (name == null) { + // could be defined in the same stub file + return "L" + typeName + ";"; } - } - return null; - } - - @Override - public Void visit(VariableDeclarationExpr expr, AElement elem) { - List annos = expr.getAnnotations(); - AMethod method = (AMethod) elem; - List varDecls = expr.getVariables(); - for (int i = 0; i < varDecls.size(); i++) { - VariableDeclarator decl = varDecls.get(i); - LocalLocation loc = new LocalLocation(i, decl.getNameAsString()); - AField field = method.body.locals.getVivify(loc); - visitType(expr.getCommonType(), field.type); - if (annos != null) { - for (AnnotationExpr annoExpr : annos) { - Annotation anno = extractAnnotation(annoExpr); - field.tlAnnotationsHere.add(anno); - } + return "L" + String.join("/", name.split("\\.")) + ";"; + } + + @Override + public String visit(PrimitiveType type, Void v) { + switch (type.getType()) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case CHAR: + return "C"; + case DOUBLE: + return "D"; + case FLOAT: + return "F"; + case INT: + return "I"; + case LONG: + return "J"; + case SHORT: + return "S"; + default: + throw new BugInCF("unknown primitive type " + type); } - } - return null; - } - - /** - * Copies information from an AST declaration node to an {@link ADeclaration}. Called by - * visitors for BodyDeclaration subclasses. - */ - private Void visitDecl(BodyDeclaration decl, ADeclaration elem) { - NodeList annoExprs = decl.getAnnotations(); - if (annoExprs != null) { - for (AnnotationExpr annoExpr : annoExprs) { - Annotation anno = extractAnnotation(annoExpr); - elem.tlAnnotationsHere.add(anno); + } + + @Override + public String visit(ArrayType type, Void v) { + String typeName = type.getElementType().accept(this, null); + StringBuilder sb = new StringBuilder(); + int n = type.getArrayLevel(); + for (int i = 0; i < n; i++) { + sb.append("["); } - } - return null; + sb.append(typeName); + return sb.toString(); + } + + @Override + public String visit(VoidType type, Void v) { + return "V"; + } + + @Override + public String visit(WildcardType type, Void v) { + return type.getSuperType().get().accept(this, null); + } + }, + null); + } + + /** + * Finds the fully qualified name of the class with the given name. + * + * @param className possibly unqualified name of class + * @return fully qualified name of class that {@code className} identifies in the current context, + * or null if resolution fails + */ + private @Nullable @BinaryName String resolve(@BinaryName String className) { + + if (pkgName != null) { + String qualifiedName = Signatures.addPackage(pkgName, className); + if (loadClass(qualifiedName) != null) { + return qualifiedName; + } } - /** Copies information from an AST type node to an {@link ATypeElement}. */ - private Void visitType(Type type, ATypeElement elem) { - List exprs = type.getAnnotations(); - if (exprs != null) { - for (AnnotationExpr expr : exprs) { - Annotation anno = extractAnnotation(expr); - if (anno != null) { - elem.tlAnnotationsHere.add(anno); - } - } - } - visitInnerTypes(type, elem); - return null; + { + // Every Java program implicitly does "import java.lang.*", + // so see whether this class is in that package. + String qualifiedName = Signatures.addPackage("java.lang", className); + if (loadClass(qualifiedName) != null) { + return qualifiedName; + } } - /** - * Copies information from an AST type node's inner type nodes to an {@link ATypeElement}. - * - * @param type the AST Type node to inspect - * @param elem destination type element - */ - private static Void visitInnerTypes(Type type, ATypeElement elem) { - return type.accept( - new GenericVisitorAdapter>() { - @Override - public Void visit(ClassOrInterfaceType type, List loc) { - if (type.getTypeArguments().isPresent()) { - List typeArgs = type.getTypeArguments().get(); - for (int i = 0; i < typeArgs.size(); i++) { - Type inner = typeArgs.get(i); - List ext = extendedTypePath(loc, 3, i); - visitInnerType(inner, ext); - } - } - return null; - } - - @Override - public Void visit(ArrayType type, List loc) { - List ext = loc; - int n = type.getArrayLevel(); - Type currentType = type; - for (int i = 0; i < n; i++) { - ext = extendedTypePath(ext, 1, 0); - for (AnnotationExpr expr : currentType.getAnnotations()) { - ATypeElement typeElem = elem.innerTypes.getVivify(ext); - Annotation anno = extractAnnotation(expr); - typeElem.tlAnnotationsHere.add(anno); - } - currentType = - ((com.github.javaparser.ast.type.ArrayType) currentType) - .getComponentType(); - } - return null; - } - - @Override - public Void visit(WildcardType type, List loc) { - ReferenceType lower = type.getExtendedType().orElse(null); - ReferenceType upper = type.getSuperType().orElse(null); - if (lower != null) { - List ext = extendedTypePath(loc, 2, 0); - visitInnerType(lower, ext); - } - if (upper != null) { - List ext = extendedTypePath(loc, 2, 0); - visitInnerType(upper, ext); - } - return null; - } - - /** - * Copies information from an AST inner type node to an {@link ATypeElement}. - */ - private void visitInnerType(Type type, List loc) { - ATypeElement typeElem = elem.innerTypes.getVivify(loc); - for (AnnotationExpr expr : type.getAnnotations()) { - Annotation anno = extractAnnotation(expr); - typeElem.tlAnnotationsHere.add(anno); - type.accept(this, loc); - } - } - - /** - * Extends type path by one element. - * - * @see TypePathEntry(int, int) - */ - private List extendedTypePath( - List loc, int tag, int arg) { - List path = new ArrayList<>(loc.size() + 1); - path.addAll(loc); - path.add(TypePathEntry.create(tag, arg)); - return path; - } - }, - Collections.emptyList()); + for (String declName : imports) { + String qualifiedName = mergeImport(declName, className); + if (loadClass(qualifiedName) != null) { + return qualifiedName; + } } - /** - * Computes a type's "binary name". - * - * @param type the type - * @return the type's binary name - */ - private String getJVML(Type type) { - return type.accept( - new GenericVisitorAdapter() { - @Override - public String visit(ClassOrInterfaceType type, Void v) { - @SuppressWarnings( - "signature") // https://tinyurl.com/cfissue/658 for getNameAsString - @FullyQualifiedName String typeName = type.getNameAsString(); - @SuppressWarnings("signature" // TODO: bug in ToIndexFileConverter: - // resolve requires a @BinaryName, but this passes a @FullyQualifiedName. - // They differ for inner classes. - ) - String name = resolve(typeName); - if (name == null) { - // could be defined in the same stub file - return "L" + typeName + ";"; - } - return "L" + String.join("/", name.split("\\.")) + ";"; - } - - @Override - public String visit(PrimitiveType type, Void v) { - switch (type.getType()) { - case BOOLEAN: - return "Z"; - case BYTE: - return "B"; - case CHAR: - return "C"; - case DOUBLE: - return "D"; - case FLOAT: - return "F"; - case INT: - return "I"; - case LONG: - return "J"; - case SHORT: - return "S"; - default: - throw new BugInCF("unknown primitive type " + type); - } - } - - @Override - public String visit(ArrayType type, Void v) { - String typeName = type.getElementType().accept(this, null); - StringBuilder sb = new StringBuilder(); - int n = type.getArrayLevel(); - for (int i = 0; i < n; i++) { - sb.append("["); - } - sb.append(typeName); - return sb.toString(); - } - - @Override - public String visit(VoidType type, Void v) { - return "V"; - } - - @Override - public String visit(WildcardType type, Void v) { - return type.getSuperType().get().accept(this, null); - } - }, - null); + if (loadClass(className) != null) { + return className; } - /** - * Finds the fully qualified name of the class with the given name. - * - * @param className possibly unqualified name of class - * @return fully qualified name of class that {@code className} identifies in the current - * context, or null if resolution fails - */ - private @Nullable @BinaryName String resolve(@BinaryName String className) { - - if (pkgName != null) { - String qualifiedName = Signatures.addPackage(pkgName, className); - if (loadClass(qualifiedName) != null) { - return qualifiedName; - } - } - - { - // Every Java program implicitly does "import java.lang.*", - // so see whether this class is in that package. - String qualifiedName = Signatures.addPackage("java.lang", className); - if (loadClass(qualifiedName) != null) { - return qualifiedName; - } - } - - for (String declName : imports) { - String qualifiedName = mergeImport(declName, className); - if (loadClass(qualifiedName) != null) { - return qualifiedName; - } - } - - if (loadClass(className) != null) { - return className; - } - - return null; + return null; + } + + /** + * Combines an import with a partial binary name, yielding a binary name. + * + * @param importName package name or (for an inner class) the outer class name + * @param className the class name + * @return fully qualified class name if resolution succeeds, null otherwise + */ + @SuppressWarnings("signature") // string manipulation of signature strings + private static @Nullable @BinaryName String mergeImport( + String importName, @BinaryName String className) { + if (importName.isEmpty() || importName.equals(className)) { + return className; } - - /** - * Combines an import with a partial binary name, yielding a binary name. - * - * @param importName package name or (for an inner class) the outer class name - * @param className the class name - * @return fully qualified class name if resolution succeeds, null otherwise - */ - @SuppressWarnings("signature") // string manipulation of signature strings - private static @Nullable @BinaryName String mergeImport( - String importName, @BinaryName String className) { - if (importName.isEmpty() || importName.equals(className)) { - return className; - } - String[] importSplit = importName.split("\\."); - String[] classSplit = className.split("\\."); - String importEnd = importSplit[importSplit.length - 1]; - if ("*".equals(importEnd)) { - return importName.substring(0, importName.length() - 1) + className; - } else { - // find overlap such as in - // import a.b.C.D; - // C.D myvar; - int i = importSplit.length; - int n = i - classSplit.length; - while (--i >= n) { - if (!classSplit[i - n].equals(importSplit[i])) { - return null; - } - } - return importName; + String[] importSplit = importName.split("\\."); + String[] classSplit = className.split("\\."); + String importEnd = importSplit[importSplit.length - 1]; + if ("*".equals(importEnd)) { + return importName.substring(0, importName.length() - 1) + className; + } else { + // find overlap such as in + // import a.b.C.D; + // C.D myvar; + int i = importSplit.length; + int n = i - classSplit.length; + while (--i >= n) { + if (!classSplit[i - n].equals(importSplit[i])) { + return null; } + } + return importName; } - - /** - * Finds the {@link Class} corresponding to a name. - * - * @param className a class name - * @return the {@link Class} object corresponding to {@code className}, or null if none found - */ - private static @Nullable Class loadClass(@ClassGetName String className) { - assert className != null; - try { - return Class.forName(className, false, null); - } catch (ClassNotFoundException e) { - return null; - } + } + + /** + * Finds the {@link Class} corresponding to a name. + * + * @param className a class name + * @return the {@link Class} object corresponding to {@code className}, or null if none found + */ + private static @Nullable Class loadClass(@ClassGetName String className) { + assert className != null; + try { + return Class.forName(className, false, null); + } catch (ClassNotFoundException e) { + return null; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index a2121ad4bf0..ca499d46f4a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -1,21 +1,8 @@ package org.checkerframework.framework.type; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.plumelib.util.IPair; - import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -24,6 +11,17 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.plumelib.util.IPair; /** * Abstract utility class for performing viewpoint adaptation. @@ -37,491 +35,468 @@ */ public abstract class AbstractViewpointAdapter implements ViewpointAdapter { - /** - * True iff we are adapting type variable bounds. This prevents calling combineTypeWithType on - * type variable if it is an upper bound of another type variable. We only viewpoint adapt a - * type variable that is not an upper-bound. - */ - private boolean isTypeVarExtends = false; - - /** The annotated type factory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Construct an abstract viewpoint adapter with the given type factory. - * - * @param atypeFactory the type factory to use - */ - protected AbstractViewpointAdapter(final AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; + /** + * True iff we are adapting type variable bounds. This prevents calling combineTypeWithType on + * type variable if it is an upper bound of another type variable. We only viewpoint adapt a type + * variable that is not an upper-bound. + */ + private boolean isTypeVarExtends = false; + + /** The annotated type factory. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** + * Construct an abstract viewpoint adapter with the given type factory. + * + * @param atypeFactory the type factory to use + */ + protected AbstractViewpointAdapter(final AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } + + @Override + public void viewpointAdaptMember( + AnnotatedTypeMirror receiverType, Element memberElement, AnnotatedTypeMirror memberType) { + if (!shouldAdaptMember(memberType, memberElement)) { + return; } - @Override - public void viewpointAdaptMember( - AnnotatedTypeMirror receiverType, - Element memberElement, - AnnotatedTypeMirror memberType) { - if (!shouldAdaptMember(memberType, memberElement)) { - return; - } - - AnnotatedTypeMirror decltype = atypeFactory.getAnnotatedType(memberElement); - AnnotatedTypeMirror combinedType = combineTypeWithType(receiverType, decltype); - memberType.replaceAnnotations(combinedType.getAnnotations()); - if (memberType.getKind() == TypeKind.DECLARED - && combinedType.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adtType = (AnnotatedDeclaredType) memberType; - AnnotatedDeclaredType adtCombinedType = (AnnotatedDeclaredType) combinedType; - adtType.setTypeArguments(adtCombinedType.getTypeArguments()); - } else if (memberType.getKind() == TypeKind.ARRAY - && combinedType.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aatType = (AnnotatedArrayType) memberType; - AnnotatedArrayType aatCombinedType = (AnnotatedArrayType) combinedType; - aatType.setComponentType(aatCombinedType.getComponentType()); - } + AnnotatedTypeMirror decltype = atypeFactory.getAnnotatedType(memberElement); + AnnotatedTypeMirror combinedType = combineTypeWithType(receiverType, decltype); + memberType.replaceAnnotations(combinedType.getAnnotations()); + if (memberType.getKind() == TypeKind.DECLARED && combinedType.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adtType = (AnnotatedDeclaredType) memberType; + AnnotatedDeclaredType adtCombinedType = (AnnotatedDeclaredType) combinedType; + adtType.setTypeArguments(adtCombinedType.getTypeArguments()); + } else if (memberType.getKind() == TypeKind.ARRAY && combinedType.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aatType = (AnnotatedArrayType) memberType; + AnnotatedArrayType aatCombinedType = (AnnotatedArrayType) combinedType; + aatType.setComponentType(aatCombinedType.getComponentType()); } - - /** - * Determines whether a particular member should be viewpoint adapted or not. The default - * implementation adapts all members except for local variables and method formal parameters. - * - * @param type type of the member, used to decide whether a member should be viewpoint adapted - * or not. A subclass of {@link ViewpointAdapter} may disable viewpoint adaptation for - * elements based on their types. - * @param element element of the member - * @return true if the member needs viewpoint adaptation - */ - protected boolean shouldAdaptMember(AnnotatedTypeMirror type, Element element) { - if (element.getKind() == ElementKind.LOCAL_VARIABLE - || element.getKind() == ElementKind.PARAMETER) { - return false; - } - return true; + } + + /** + * Determines whether a particular member should be viewpoint adapted or not. The default + * implementation adapts all members except for local variables and method formal parameters. + * + * @param type type of the member, used to decide whether a member should be viewpoint adapted or + * not. A subclass of {@link ViewpointAdapter} may disable viewpoint adaptation for elements + * based on their types. + * @param element element of the member + * @return true if the member needs viewpoint adaptation + */ + protected boolean shouldAdaptMember(AnnotatedTypeMirror type, Element element) { + if (element.getKind() == ElementKind.LOCAL_VARIABLE + || element.getKind() == ElementKind.PARAMETER) { + return false; } - - @Override - public void viewpointAdaptConstructor( - AnnotatedTypeMirror receiverType, - ExecutableElement constructorElt, - AnnotatedExecutableType constructorType) { - // constructorType's typevar are not substituted when calling viewpointAdaptConstructor - AnnotatedExecutableType unsubstitutedConstructorType = constructorType.deepCopy(); - - // For constructors, we adapt parameter types, return type and type parameters - List parameterTypes = unsubstitutedConstructorType.getParameterTypes(); - List typeVariables = unsubstitutedConstructorType.getTypeVariables(); - AnnotatedTypeMirror constructorReturn = unsubstitutedConstructorType.getReturnType(); - - IdentityHashMap mappings = - new IdentityHashMap<>(); - for (AnnotatedTypeMirror parameterType : parameterTypes) { - AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); - mappings.put(parameterType, p); - } - for (AnnotatedTypeMirror typeVariable : typeVariables) { - AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); - mappings.put(typeVariable, tv); - } - AnnotatedTypeMirror cr = combineTypeWithType(receiverType, constructorReturn); - mappings.put(constructorReturn, cr); - - unsubstitutedConstructorType = - (AnnotatedExecutableType) - AnnotatedTypeCopierWithReplacement.replace( - unsubstitutedConstructorType, mappings); - - constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes()); - constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables()); - constructorType.setReturnType(unsubstitutedConstructorType.getReturnType()); + return true; + } + + @Override + public void viewpointAdaptConstructor( + AnnotatedTypeMirror receiverType, + ExecutableElement constructorElt, + AnnotatedExecutableType constructorType) { + // constructorType's typevar are not substituted when calling viewpointAdaptConstructor + AnnotatedExecutableType unsubstitutedConstructorType = constructorType.deepCopy(); + + // For constructors, we adapt parameter types, return type and type parameters + List parameterTypes = unsubstitutedConstructorType.getParameterTypes(); + List typeVariables = unsubstitutedConstructorType.getTypeVariables(); + AnnotatedTypeMirror constructorReturn = unsubstitutedConstructorType.getReturnType(); + + IdentityHashMap mappings = new IdentityHashMap<>(); + for (AnnotatedTypeMirror parameterType : parameterTypes) { + AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); + mappings.put(parameterType, p); + } + for (AnnotatedTypeMirror typeVariable : typeVariables) { + AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); + mappings.put(typeVariable, tv); + } + AnnotatedTypeMirror cr = combineTypeWithType(receiverType, constructorReturn); + mappings.put(constructorReturn, cr); + + unsubstitutedConstructorType = + (AnnotatedExecutableType) + AnnotatedTypeCopierWithReplacement.replace(unsubstitutedConstructorType, mappings); + + constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes()); + constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables()); + constructorType.setReturnType(unsubstitutedConstructorType.getReturnType()); + } + + @Override + public void viewpointAdaptMethod( + AnnotatedTypeMirror receiverType, + ExecutableElement methodElt, + AnnotatedExecutableType methodType) { + if (!shouldAdaptMethod(methodElt)) { + return; } - @Override - public void viewpointAdaptMethod( - AnnotatedTypeMirror receiverType, - ExecutableElement methodElt, - AnnotatedExecutableType methodType) { - if (!shouldAdaptMethod(methodElt)) { - return; - } - - // methodType's typevar are not substituted when calling viewpointAdaptMethod - AnnotatedExecutableType unsubstitutedMethodType = methodType.deepCopy(); + // methodType's typevar are not substituted when calling viewpointAdaptMethod + AnnotatedExecutableType unsubstitutedMethodType = methodType.deepCopy(); - // For methods, we additionally adapt method receiver compared to constructors - List parameterTypes = unsubstitutedMethodType.getParameterTypes(); - List typeVariables = unsubstitutedMethodType.getTypeVariables(); - AnnotatedTypeMirror returnType = unsubstitutedMethodType.getReturnType(); - AnnotatedTypeMirror methodReceiver = unsubstitutedMethodType.getReceiverType(); + // For methods, we additionally adapt method receiver compared to constructors + List parameterTypes = unsubstitutedMethodType.getParameterTypes(); + List typeVariables = unsubstitutedMethodType.getTypeVariables(); + AnnotatedTypeMirror returnType = unsubstitutedMethodType.getReturnType(); + AnnotatedTypeMirror methodReceiver = unsubstitutedMethodType.getReceiverType(); - IdentityHashMap mappings = - new IdentityHashMap<>(); + IdentityHashMap mappings = new IdentityHashMap<>(); - for (AnnotatedTypeMirror parameterType : parameterTypes) { - AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); - mappings.put(parameterType, p); - } + for (AnnotatedTypeMirror parameterType : parameterTypes) { + AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); + mappings.put(parameterType, p); + } - for (AnnotatedTypeVariable typeVariable : typeVariables) { - AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); - mappings.put(typeVariable, tv); - } + for (AnnotatedTypeVariable typeVariable : typeVariables) { + AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); + mappings.put(typeVariable, tv); + } - if (returnType.getKind() != TypeKind.VOID) { - AnnotatedTypeMirror r = combineTypeWithType(receiverType, returnType); - mappings.put(returnType, r); - } + if (returnType.getKind() != TypeKind.VOID) { + AnnotatedTypeMirror r = combineTypeWithType(receiverType, returnType); + mappings.put(returnType, r); + } - if (methodReceiver != null) { - AnnotatedTypeMirror mr = combineTypeWithType(receiverType, methodReceiver); - mappings.put(methodReceiver, mr); - } + if (methodReceiver != null) { + AnnotatedTypeMirror mr = combineTypeWithType(receiverType, methodReceiver); + mappings.put(methodReceiver, mr); + } - unsubstitutedMethodType = - (AnnotatedExecutableType) - AnnotatedTypeCopierWithReplacement.replace( - unsubstitutedMethodType, mappings); - - // Because we can't viewpoint adapt asMemberOf result, we adapt the declared method first, - // and sets the corresponding parts to asMemberOf result - methodType.setReturnType(unsubstitutedMethodType.getReturnType()); - methodType.setReceiverType(unsubstitutedMethodType.getReceiverType()); - methodType.setParameterTypes(unsubstitutedMethodType.getParameterTypes()); - methodType.setTypeVariables(unsubstitutedMethodType.getTypeVariables()); + unsubstitutedMethodType = + (AnnotatedExecutableType) + AnnotatedTypeCopierWithReplacement.replace(unsubstitutedMethodType, mappings); + + // Because we can't viewpoint adapt asMemberOf result, we adapt the declared method first, + // and sets the corresponding parts to asMemberOf result + methodType.setReturnType(unsubstitutedMethodType.getReturnType()); + methodType.setReceiverType(unsubstitutedMethodType.getReceiverType()); + methodType.setParameterTypes(unsubstitutedMethodType.getParameterTypes()); + methodType.setTypeVariables(unsubstitutedMethodType.getTypeVariables()); + } + + /** + * Determine if an invocation of the given method needs to be adapted. + * + * @param element the executable element for a method + * @return true if an invocation of the executable element needs to be adapted + */ + protected boolean shouldAdaptMethod(ExecutableElement element) { + return !ElementUtils.isStatic(element); + } + + @Override + public void viewpointAdaptTypeParameterBounds( + AnnotatedTypeMirror receiverType, List typeParameterBounds) { + List adaptedTypeParameterBounds = + new ArrayList<>(typeParameterBounds.size()); + for (AnnotatedTypeParameterBounds typeParameterBound : typeParameterBounds) { + AnnotatedTypeMirror adaptedUpper = + combineTypeWithType(receiverType, typeParameterBound.getUpperBound()); + AnnotatedTypeMirror adaptedLower = + combineTypeWithType(receiverType, typeParameterBound.getLowerBound()); + adaptedTypeParameterBounds.add(new AnnotatedTypeParameterBounds(adaptedUpper, adaptedLower)); } - /** - * Determine if an invocation of the given method needs to be adapted. - * - * @param element the executable element for a method - * @return true if an invocation of the executable element needs to be adapted - */ - protected boolean shouldAdaptMethod(ExecutableElement element) { - return !ElementUtils.isStatic(element); + typeParameterBounds.clear(); + typeParameterBounds.addAll(adaptedTypeParameterBounds); + } + + /** + * Viewpoint adapt declared type to receiver type, and return the result atm + * + * @param receiver receiver type + * @param declared declared type + * @return {@link AnnotatedTypeMirror} after viewpoint adaptation + */ + protected AnnotatedTypeMirror combineTypeWithType( + AnnotatedTypeMirror receiver, AnnotatedTypeMirror declared) { + assert receiver != null && declared != null; + + AnnotatedTypeMirror result = declared; + + if (receiver.getKind() == TypeKind.TYPEVAR) { + receiver = ((AnnotatedTypeVariable) receiver).getUpperBound(); + } + AnnotationMirror receiverAnnotation = extractAnnotationMirror(receiver); + if (receiverAnnotation != null) { + result = combineAnnotationWithType(receiverAnnotation, declared); + result = substituteTVars(receiver, result); } - @Override - public void viewpointAdaptTypeParameterBounds( - AnnotatedTypeMirror receiverType, - List typeParameterBounds) { - List adaptedTypeParameterBounds = - new ArrayList<>(typeParameterBounds.size()); - for (AnnotatedTypeParameterBounds typeParameterBound : typeParameterBounds) { - AnnotatedTypeMirror adaptedUpper = - combineTypeWithType(receiverType, typeParameterBound.getUpperBound()); - AnnotatedTypeMirror adaptedLower = - combineTypeWithType(receiverType, typeParameterBound.getLowerBound()); - adaptedTypeParameterBounds.add( - new AnnotatedTypeParameterBounds(adaptedUpper, adaptedLower)); - } + return result; + } + + /** + * Extract the relevant qualifier from an {@link AnnotatedTypeMirror}. + * + * @param atm AnnotatedTypeMirror from which qualifier is extracted + * @return extracted qualifier + */ + protected abstract AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm); + + /** + * Combine receiver qualifiers with declared types. Qualifiers are extracted from declared types + * to further perform viewpoint adaptation only between two qualifiers. + * + * @param receiverAnnotation receiver qualifier + * @param declared declared type + * @return {@link AnnotatedTypeMirror} after viewpoint adaptation + */ + protected AnnotatedTypeMirror combineAnnotationWithType( + AnnotationMirror receiverAnnotation, AnnotatedTypeMirror declared) { + if (declared.getKind().isPrimitive()) { + AnnotatedPrimitiveType apt = (AnnotatedPrimitiveType) declared.shallowCopy(); + + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(apt)); + apt.replaceAnnotation(resultAnnotation); + return apt; + } else if (declared.getKind() == TypeKind.TYPEVAR) { + if (!isTypeVarExtends) { + isTypeVarExtends = true; + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) declared.shallowCopy(); + IdentityHashMap mappings = + new IdentityHashMap<>(); - typeParameterBounds.clear(); - typeParameterBounds.addAll(adaptedTypeParameterBounds); - } + // For type variables, we recursively adapt upper and lower bounds + AnnotatedTypeMirror resUpper = + combineAnnotationWithType(receiverAnnotation, atv.getUpperBound()); + mappings.put(atv.getUpperBound(), resUpper); - /** - * Viewpoint adapt declared type to receiver type, and return the result atm - * - * @param receiver receiver type - * @param declared declared type - * @return {@link AnnotatedTypeMirror} after viewpoint adaptation - */ - protected AnnotatedTypeMirror combineTypeWithType( - AnnotatedTypeMirror receiver, AnnotatedTypeMirror declared) { - assert receiver != null && declared != null; - - AnnotatedTypeMirror result = declared; - - if (receiver.getKind() == TypeKind.TYPEVAR) { - receiver = ((AnnotatedTypeVariable) receiver).getUpperBound(); - } - AnnotationMirror receiverAnnotation = extractAnnotationMirror(receiver); - if (receiverAnnotation != null) { - result = combineAnnotationWithType(receiverAnnotation, declared); - result = substituteTVars(receiver, result); - } + AnnotatedTypeMirror resLower = + combineAnnotationWithType(receiverAnnotation, atv.getLowerBound()); + mappings.put(atv.getLowerBound(), resLower); + AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(atv, mappings); + + isTypeVarExtends = false; return result; + } + return declared; + } else if (declared.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) declared.shallowCopy(); + + // Mapping between declared type argument to combined type argument + IdentityHashMap mappings = new IdentityHashMap<>(); + + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(adt)); + + // Recursively combine type arguments and store to map + for (AnnotatedTypeMirror typeArgument : adt.getTypeArguments()) { + // Recursively adapt the type arguments of this adt + AnnotatedTypeMirror combinedTypeArgument = + combineAnnotationWithType(receiverAnnotation, typeArgument); + mappings.put(typeArgument, combinedTypeArgument); + } + + // Construct result type + AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); + result.replaceAnnotation(resultAnnotation); + + return result; + } else if (declared.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aat = (AnnotatedArrayType) declared.shallowCopy(); + + // Replace the main qualifier + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(aat)); + aat.replaceAnnotation(resultAnnotation); + + // Combine component type recursively and sets combined component type + AnnotatedTypeMirror compo = aat.getComponentType(); + // Recursively call itself first on the component type + AnnotatedTypeMirror combinedCompoType = combineAnnotationWithType(receiverAnnotation, compo); + aat.setComponentType(combinedCompoType); + + return aat; + } else if (declared.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType awt = (AnnotatedWildcardType) declared.shallowCopy(); + IdentityHashMap mappings = new IdentityHashMap<>(); + + // There is no main qualifier for a wildcard + + // Adapt extend + AnnotatedTypeMirror extend = awt.getExtendsBound(); + if (extend != null) { + // Recursively adapt the extends bound of this awt + AnnotatedTypeMirror combinedExtend = combineAnnotationWithType(receiverAnnotation, extend); + mappings.put(extend, combinedExtend); + } + + // Adapt super + AnnotatedTypeMirror zuper = awt.getSuperBound(); + if (zuper != null) { + // Recursively adapt the lower bound of this awt + AnnotatedTypeMirror combinedZuper = combineAnnotationWithType(receiverAnnotation, zuper); + mappings.put(zuper, combinedZuper); + } + + AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); + return result; + } else if (declared.getKind() == TypeKind.NULL) { + AnnotatedNullType ant = (AnnotatedNullType) declared.shallowCopy(true); + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(ant)); + ant.replaceAnnotation(resultAnnotation); + return ant; + } else { + throw new BugInCF( + "ViewpointAdapter::combineAnnotationWithType: Unknown decl: " + + declared + + " of kind: " + + declared.getKind()); } - - /** - * Extract the relevant qualifier from an {@link AnnotatedTypeMirror}. - * - * @param atm AnnotatedTypeMirror from which qualifier is extracted - * @return extracted qualifier - */ - protected abstract AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm); - - /** - * Combine receiver qualifiers with declared types. Qualifiers are extracted from declared types - * to further perform viewpoint adaptation only between two qualifiers. - * - * @param receiverAnnotation receiver qualifier - * @param declared declared type - * @return {@link AnnotatedTypeMirror} after viewpoint adaptation - */ - protected AnnotatedTypeMirror combineAnnotationWithType( - AnnotationMirror receiverAnnotation, AnnotatedTypeMirror declared) { - if (declared.getKind().isPrimitive()) { - AnnotatedPrimitiveType apt = (AnnotatedPrimitiveType) declared.shallowCopy(); - - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation( - receiverAnnotation, extractAnnotationMirror(apt)); - apt.replaceAnnotation(resultAnnotation); - return apt; - } else if (declared.getKind() == TypeKind.TYPEVAR) { - if (!isTypeVarExtends) { - isTypeVarExtends = true; - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) declared.shallowCopy(); - IdentityHashMap mappings = - new IdentityHashMap<>(); - - // For type variables, we recursively adapt upper and lower bounds - AnnotatedTypeMirror resUpper = - combineAnnotationWithType(receiverAnnotation, atv.getUpperBound()); - mappings.put(atv.getUpperBound(), resUpper); - - AnnotatedTypeMirror resLower = - combineAnnotationWithType(receiverAnnotation, atv.getLowerBound()); - mappings.put(atv.getLowerBound(), resLower); - - AnnotatedTypeMirror result = - AnnotatedTypeCopierWithReplacement.replace(atv, mappings); - - isTypeVarExtends = false; - return result; - } - return declared; - } else if (declared.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adt = (AnnotatedDeclaredType) declared.shallowCopy(); - - // Mapping between declared type argument to combined type argument - IdentityHashMap mappings = - new IdentityHashMap<>(); - - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation( - receiverAnnotation, extractAnnotationMirror(adt)); - - // Recursively combine type arguments and store to map - for (AnnotatedTypeMirror typeArgument : adt.getTypeArguments()) { - // Recursively adapt the type arguments of this adt - AnnotatedTypeMirror combinedTypeArgument = - combineAnnotationWithType(receiverAnnotation, typeArgument); - mappings.put(typeArgument, combinedTypeArgument); - } - - // Construct result type - AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); - result.replaceAnnotation(resultAnnotation); - - return result; - } else if (declared.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aat = (AnnotatedArrayType) declared.shallowCopy(); - - // Replace the main qualifier - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation( - receiverAnnotation, extractAnnotationMirror(aat)); - aat.replaceAnnotation(resultAnnotation); - - // Combine component type recursively and sets combined component type - AnnotatedTypeMirror compo = aat.getComponentType(); - // Recursively call itself first on the component type - AnnotatedTypeMirror combinedCompoType = - combineAnnotationWithType(receiverAnnotation, compo); - aat.setComponentType(combinedCompoType); - - return aat; - } else if (declared.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType awt = (AnnotatedWildcardType) declared.shallowCopy(); - IdentityHashMap mappings = - new IdentityHashMap<>(); - - // There is no main qualifier for a wildcard - - // Adapt extend - AnnotatedTypeMirror extend = awt.getExtendsBound(); - if (extend != null) { - // Recursively adapt the extends bound of this awt - AnnotatedTypeMirror combinedExtend = - combineAnnotationWithType(receiverAnnotation, extend); - mappings.put(extend, combinedExtend); - } - - // Adapt super - AnnotatedTypeMirror zuper = awt.getSuperBound(); - if (zuper != null) { - // Recursively adapt the lower bound of this awt - AnnotatedTypeMirror combinedZuper = - combineAnnotationWithType(receiverAnnotation, zuper); - mappings.put(zuper, combinedZuper); - } - - AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); - return result; - } else if (declared.getKind() == TypeKind.NULL) { - AnnotatedNullType ant = (AnnotatedNullType) declared.shallowCopy(true); - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation( - receiverAnnotation, extractAnnotationMirror(ant)); - ant.replaceAnnotation(resultAnnotation); - return ant; - } else { - throw new BugInCF( - "ViewpointAdapter::combineAnnotationWithType: Unknown decl: " - + declared - + " of kind: " - + declared.getKind()); - } + } + + /** + * Viewpoint adapt declared qualifier to receiver qualifier. + * + * @param receiverAnnotation receiver qualifier + * @param declaredAnnotation declared qualifier + * @return result qualifier after viewpoint adaptation + */ + @SideEffectFree + protected abstract AnnotationMirror combineAnnotationWithAnnotation( + AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation); + + /** + * If rhs contains/is a type variable use whose type arguments should be inferred from the + * receiver, i.e. lhs, this method substitutes that type argument into rhs, and returns the + * reference to rhs. This method is side effect free, because rhs will be copied and that copy + * gets modified and returned. + * + * @param lhs type from which type arguments are extracted to replace formal type parameters of + * rhs. + * @param rhs AnnotatedTypeMirror that might be a formal type parameter + * @return rhs' copy with its type parameter substituted + */ + private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTypeMirror rhs) { + if (rhs.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) rhs.shallowCopy(); + + // Base case where actual type argument is extracted + if (lhs.getKind() == TypeKind.DECLARED) { + rhs = getTypeVariableSubstitution((AnnotatedDeclaredType) lhs, atv); + } + } else if (rhs.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) rhs.shallowCopy(); + IdentityHashMap mappings = new IdentityHashMap<>(); + + for (AnnotatedTypeMirror formalTypeParameter : adt.getTypeArguments()) { + AnnotatedTypeMirror actualTypeArgument = substituteTVars(lhs, formalTypeParameter); + mappings.put(formalTypeParameter, actualTypeArgument); + // The following code does the wrong thing! + } + // We must use AnnotatedTypeReplacer to replace the formal type parameters with actual + // type arguments, but not replace with its main qualifier + rhs = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); + } else if (rhs.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType awt = (AnnotatedWildcardType) rhs.shallowCopy(); + IdentityHashMap mappings = new IdentityHashMap<>(); + + AnnotatedTypeMirror extend = awt.getExtendsBound(); + if (extend != null) { + AnnotatedTypeMirror substExtend = substituteTVars(lhs, extend); + mappings.put(extend, substExtend); + } + + AnnotatedTypeMirror zuper = awt.getSuperBound(); + if (zuper != null) { + AnnotatedTypeMirror substZuper = substituteTVars(lhs, zuper); + mappings.put(zuper, substZuper); + } + + rhs = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); + } else if (rhs.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aat = (AnnotatedArrayType) rhs.shallowCopy(); + IdentityHashMap mappings = new IdentityHashMap<>(); + + AnnotatedTypeMirror compnentType = aat.getComponentType(); + // Type variable of compnentType already gets substituted + AnnotatedTypeMirror substCompnentType = substituteTVars(lhs, compnentType); + mappings.put(compnentType, substCompnentType); + + // Construct result type + rhs = AnnotatedTypeCopierWithReplacement.replace(aat, mappings); + } else if (rhs.getKind().isPrimitive() || rhs.getKind() == TypeKind.NULL) { + // nothing to do for primitive types and the null type + } else { + throw new BugInCF( + "ViewpointAdapter::substituteTVars: Cannot handle rhs: " + + rhs + + " of kind: " + + rhs.getKind()); } - /** - * Viewpoint adapt declared qualifier to receiver qualifier. - * - * @param receiverAnnotation receiver qualifier - * @param declaredAnnotation declared qualifier - * @return result qualifier after viewpoint adaptation - */ - @SideEffectFree - protected abstract AnnotationMirror combineAnnotationWithAnnotation( - AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation); - - /** - * If rhs contains/is a type variable use whose type arguments should be inferred from the - * receiver, i.e. lhs, this method substitutes that type argument into rhs, and returns the - * reference to rhs. This method is side effect free, because rhs will be copied and that copy - * gets modified and returned. - * - * @param lhs type from which type arguments are extracted to replace formal type parameters of - * rhs. - * @param rhs AnnotatedTypeMirror that might be a formal type parameter - * @return rhs' copy with its type parameter substituted - */ - private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTypeMirror rhs) { - if (rhs.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) rhs.shallowCopy(); - - // Base case where actual type argument is extracted - if (lhs.getKind() == TypeKind.DECLARED) { - rhs = getTypeVariableSubstitution((AnnotatedDeclaredType) lhs, atv); - } - } else if (rhs.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adt = (AnnotatedDeclaredType) rhs.shallowCopy(); - IdentityHashMap mappings = - new IdentityHashMap<>(); - - for (AnnotatedTypeMirror formalTypeParameter : adt.getTypeArguments()) { - AnnotatedTypeMirror actualTypeArgument = substituteTVars(lhs, formalTypeParameter); - mappings.put(formalTypeParameter, actualTypeArgument); - // The following code does the wrong thing! - } - // We must use AnnotatedTypeReplacer to replace the formal type parameters with actual - // type arguments, but not replace with its main qualifier - rhs = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); - } else if (rhs.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType awt = (AnnotatedWildcardType) rhs.shallowCopy(); - IdentityHashMap mappings = - new IdentityHashMap<>(); - - AnnotatedTypeMirror extend = awt.getExtendsBound(); - if (extend != null) { - AnnotatedTypeMirror substExtend = substituteTVars(lhs, extend); - mappings.put(extend, substExtend); - } - - AnnotatedTypeMirror zuper = awt.getSuperBound(); - if (zuper != null) { - AnnotatedTypeMirror substZuper = substituteTVars(lhs, zuper); - mappings.put(zuper, substZuper); - } - - rhs = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); - } else if (rhs.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aat = (AnnotatedArrayType) rhs.shallowCopy(); - IdentityHashMap mappings = - new IdentityHashMap<>(); - - AnnotatedTypeMirror compnentType = aat.getComponentType(); - // Type variable of compnentType already gets substituted - AnnotatedTypeMirror substCompnentType = substituteTVars(lhs, compnentType); - mappings.put(compnentType, substCompnentType); - - // Construct result type - rhs = AnnotatedTypeCopierWithReplacement.replace(aat, mappings); - } else if (rhs.getKind().isPrimitive() || rhs.getKind() == TypeKind.NULL) { - // nothing to do for primitive types and the null type - } else { - throw new BugInCF( - "ViewpointAdapter::substituteTVars: Cannot handle rhs: " - + rhs - + " of kind: " - + rhs.getKind()); - } - - return rhs; + return rhs; + } + + /** + * Return actual type argument for formal type parameter "var" from "type" + * + * @param type type from which type arguments are extracted to replace "var" + * @param var formal type parameter that needs real type arguments + * @return Real type argument + */ + private AnnotatedTypeMirror getTypeVariableSubstitution( + AnnotatedDeclaredType type, AnnotatedTypeVariable var) { + IPair res = findDeclType(type, var); + + if (res == null) { + return var; } - /** - * Return actual type argument for formal type parameter "var" from "type" - * - * @param type type from which type arguments are extracted to replace "var" - * @param var formal type parameter that needs real type arguments - * @return Real type argument - */ - private AnnotatedTypeMirror getTypeVariableSubstitution( - AnnotatedDeclaredType type, AnnotatedTypeVariable var) { - IPair res = findDeclType(type, var); - - if (res == null) { - return var; - } - - AnnotatedDeclaredType decltype = res.first; - int foundindex = res.second; - - List tas = decltype.getTypeArguments(); - // return a copy, as we want to modify the type later. - return tas.get(foundindex).shallowCopy(true); + AnnotatedDeclaredType decltype = res.first; + int foundindex = res.second; + + List tas = decltype.getTypeArguments(); + // return a copy, as we want to modify the type later. + return tas.get(foundindex).shallowCopy(true); + } + + /** + * Find the index (position) of this type variable from type + * + * @param type type from which we infer actual type arguments + * @param var formal type parameter + * @return index(position) of this type variable from type + */ + private IPair findDeclType( + AnnotatedDeclaredType type, AnnotatedTypeVariable var) { + Element varelem = var.getUnderlyingType().asElement(); + + DeclaredType dtype = type.getUnderlyingType(); + TypeElement el = (TypeElement) dtype.asElement(); + List tparams = el.getTypeParameters(); + int foundindex = 0; + + for (TypeParameterElement tparam : tparams) { + if (tparam.equals(varelem)) { + // we found the right index! + break; + } + ++foundindex; } - /** - * Find the index (position) of this type variable from type - * - * @param type type from which we infer actual type arguments - * @param var formal type parameter - * @return index(position) of this type variable from type - */ - private IPair findDeclType( - AnnotatedDeclaredType type, AnnotatedTypeVariable var) { - Element varelem = var.getUnderlyingType().asElement(); - - DeclaredType dtype = type.getUnderlyingType(); - TypeElement el = (TypeElement) dtype.asElement(); - List tparams = el.getTypeParameters(); - int foundindex = 0; - - for (TypeParameterElement tparam : tparams) { - if (tparam.equals(varelem)) { - // we found the right index! - break; - } - ++foundindex; + if (foundindex >= tparams.size()) { + // Didn't find the desired type => Head for super type of "type"! + for (AnnotatedDeclaredType sup : type.directSupertypes()) { + IPair res = findDeclType(sup, var); + if (res != null) { + return res; } - - if (foundindex >= tparams.size()) { - // Didn't find the desired type => Head for super type of "type"! - for (AnnotatedDeclaredType sup : type.directSupertypes()) { - IPair res = findDeclType(sup, var); - if (res != null) { - return res; - } - } - // We reach this point if the variable wasn't found in any recursive call on ALL direct - // supertypes. - return null; - } - - return IPair.of(type, foundindex); + } + // We reach this point if the variable wasn't found in any recursive call on ALL direct + // supertypes. + return null; } + + return IPair.of(type, foundindex); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java index d072b53099a..83bfa641173 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -13,11 +17,6 @@ import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; import org.plumelib.util.CollectionsPlume; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; - /** * AnnotatedTypeCopier is a visitor that deep copies an AnnotatedTypeMirror exactly, including any * lazily initialized fields. That is, if a field has already been initialized, it will be @@ -42,355 +41,348 @@ * @see org.checkerframework.framework.type.AnnotatedTypeCopierWithReplacement */ public class AnnotatedTypeCopier - implements AnnotatedTypeVisitor< - AnnotatedTypeMirror, IdentityHashMap> { - - /** - * This is a hack to handle the curious behavior of substitution on an AnnotatedExecutableType. - * - * @see org.checkerframework.framework.type.TypeVariableSubstitutor It is poor form to include - * such a flag on the base class for exclusive use in a subclass but it is the least bad - * option in this case. - */ - protected boolean visitingExecutableTypeParam = false; - - /** - * See {@link #AnnotatedTypeCopier(boolean)}. - * - * @see #AnnotatedTypeCopier(boolean) - */ - protected final boolean copyAnnotations; - - /** - * Creates an AnnotatedTypeCopier that may or may not copyAnnotations By default - * AnnotatedTypeCopier provides two major properties in its copies: - * - *

    - *
  1. Structure preservation -- the exact structure of the original AnnotatedTypeMirror is - * preserved in the copy including all component types. - *
  2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror - * and its components have been copied to the new type. - *
- * - * If copyAnnotations is set to false, the second property, annotation preservation, is removed. - * This is useful for cases in which the user may want to copy the structure of a type exactly - * but NOT its annotations. - */ - public AnnotatedTypeCopier(boolean copyAnnotations) { - this.copyAnnotations = copyAnnotations; + implements AnnotatedTypeVisitor< + AnnotatedTypeMirror, IdentityHashMap> { + + /** + * This is a hack to handle the curious behavior of substitution on an AnnotatedExecutableType. + * + * @see org.checkerframework.framework.type.TypeVariableSubstitutor It is poor form to include + * such a flag on the base class for exclusive use in a subclass but it is the least bad + * option in this case. + */ + protected boolean visitingExecutableTypeParam = false; + + /** + * See {@link #AnnotatedTypeCopier(boolean)}. + * + * @see #AnnotatedTypeCopier(boolean) + */ + protected final boolean copyAnnotations; + + /** + * Creates an AnnotatedTypeCopier that may or may not copyAnnotations By default + * AnnotatedTypeCopier provides two major properties in its copies: + * + *
    + *
  1. Structure preservation -- the exact structure of the original AnnotatedTypeMirror is + * preserved in the copy including all component types. + *
  2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror + * and its components have been copied to the new type. + *
+ * + * If copyAnnotations is set to false, the second property, annotation preservation, is removed. + * This is useful for cases in which the user may want to copy the structure of a type exactly but + * NOT its annotations. + */ + public AnnotatedTypeCopier(boolean copyAnnotations) { + this.copyAnnotations = copyAnnotations; + } + + /** + * Creates an AnnotatedTypeCopier that copies both the structure and annotations of the source + * AnnotatedTypeMirror. + * + * @see #AnnotatedTypeCopier(boolean) + */ + public AnnotatedTypeCopier() { + this(true); + } + + @Override + public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { + return type.accept(this, new IdentityHashMap<>()); + } + + @Override + public AnnotatedTypeMirror visit( + AnnotatedTypeMirror type, + IdentityHashMap originalToCopy) { + return type.accept(this, originalToCopy); + } + + @Override + public AnnotatedTypeMirror visitDeclared( + AnnotatedDeclaredType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); } - /** - * Creates an AnnotatedTypeCopier that copies both the structure and annotations of the source - * AnnotatedTypeMirror. - * - * @see #AnnotatedTypeCopier(boolean) - */ - public AnnotatedTypeCopier() { - this(true); - } + AnnotatedDeclaredType copy = makeOrReturnCopy(original, originalToCopy); - @Override - public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { - return type.accept(this, new IdentityHashMap<>()); + if (original.isUnderlyingTypeRaw()) { + copy.setIsUnderlyingTypeRaw(); } - @Override - public AnnotatedTypeMirror visit( - AnnotatedTypeMirror type, - IdentityHashMap originalToCopy) { - return type.accept(this, originalToCopy); + if (original.enclosingType != null) { + copy.enclosingType = (AnnotatedDeclaredType) visit(original.enclosingType, originalToCopy); } - @Override - public AnnotatedTypeMirror visitDeclared( - AnnotatedDeclaredType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } - - AnnotatedDeclaredType copy = makeOrReturnCopy(original, originalToCopy); - - if (original.isUnderlyingTypeRaw()) { - copy.setIsUnderlyingTypeRaw(); - } - - if (original.enclosingType != null) { - copy.enclosingType = - (AnnotatedDeclaredType) visit(original.enclosingType, originalToCopy); - } - - if (original.typeArgs != null) { - List copyTypeArgs = - CollectionsPlume.mapList( - (AnnotatedTypeMirror typeArg) -> visit(typeArg, originalToCopy), - original.getTypeArguments()); - copy.setTypeArguments(copyTypeArgs); - } - - return copy; + if (original.typeArgs != null) { + List copyTypeArgs = + CollectionsPlume.mapList( + (AnnotatedTypeMirror typeArg) -> visit(typeArg, originalToCopy), + original.getTypeArguments()); + copy.setTypeArguments(copyTypeArgs); } - @Override - public AnnotatedTypeMirror visitIntersection( - AnnotatedIntersectionType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } - - AnnotatedIntersectionType copy = makeOrReturnCopy(original, originalToCopy); - - if (original.bounds != null) { - List copySupertypes = - CollectionsPlume.mapList( - (AnnotatedTypeMirror bound) -> visit(bound, originalToCopy), - original.bounds); - copy.bounds = Collections.unmodifiableList(copySupertypes); - } - - return copy; + return copy; + } + + @Override + public AnnotatedTypeMirror visitIntersection( + AnnotatedIntersectionType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); } - @Override - public AnnotatedTypeMirror visitUnion( - AnnotatedUnionType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } - - AnnotatedUnionType copy = makeOrReturnCopy(original, originalToCopy); - - if (original.alternatives != null) { - List copyAlternatives = - CollectionsPlume.mapList( - (AnnotatedDeclaredType supertype) -> - (AnnotatedDeclaredType) visit(supertype, originalToCopy), - original.alternatives); - copy.alternatives = Collections.unmodifiableList(copyAlternatives); - } - - return copy; + AnnotatedIntersectionType copy = makeOrReturnCopy(original, originalToCopy); + + if (original.bounds != null) { + List copySupertypes = + CollectionsPlume.mapList( + (AnnotatedTypeMirror bound) -> visit(bound, originalToCopy), original.bounds); + copy.bounds = Collections.unmodifiableList(copySupertypes); } - @Override - public AnnotatedTypeMirror visitExecutable( - AnnotatedExecutableType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } - - AnnotatedExecutableType copy = makeOrReturnCopy(original, originalToCopy); - - copy.setElement(original.getElement()); - - if (original.getReceiverType() != null) { - copy.setReceiverType( - (AnnotatedDeclaredType) visit(original.getReceiverType(), originalToCopy)); - } - - List originalParameterTypes = original.getParameterTypes(); - if (originalParameterTypes.isEmpty()) { - copy.setParameterTypes(Collections.emptyList()); - } else { - List copyParamTypes = - new ArrayList<>(originalParameterTypes.size()); - for (AnnotatedTypeMirror param : originalParameterTypes) { - copyParamTypes.add(visit(param, originalToCopy)); - } - copy.setParameterTypes(Collections.unmodifiableList(copyParamTypes)); - } - - if (original.getVarargType() != null) { - copy.setVarargType(original.getVarargType()); - } else { - copy.computeVarargType(); - } - - List originalThrownTypes = original.getThrownTypes(); - if (originalThrownTypes.isEmpty()) { - copy.setThrownTypes(Collections.emptyList()); - } else { - List copyThrownTypes = new ArrayList<>(originalThrownTypes.size()); - for (AnnotatedTypeMirror thrown : original.getThrownTypes()) { - copyThrownTypes.add(visit(thrown, originalToCopy)); - } - copy.setThrownTypes(Collections.unmodifiableList(copyThrownTypes)); - } - - copy.setReturnType(visit(original.getReturnType(), originalToCopy)); - - List originalTypeVariables = original.getTypeVariables(); - if (originalTypeVariables.isEmpty()) { - copy.setTypeVariables(Collections.emptyList()); - } else { - List copyTypeVarTypes = - new ArrayList<>(originalTypeVariables.size()); - for (AnnotatedTypeVariable typeVariable : originalTypeVariables) { - // This field is needed to identify exactly when the declaration of an executable's - // type parameter is visited. When subtypes of this class visit the type - // parameter's component types, they will likely set visitingExecutableTypeParam to - // false. - // Therefore, we set this variable on each iteration of the loop. - // See TypeVariableSubstitutor.Visitor.visitTypeVariable for an example of this. - visitingExecutableTypeParam = true; - copyTypeVarTypes.add((AnnotatedTypeVariable) visit(typeVariable, originalToCopy)); - } - copy.setTypeVariables(Collections.unmodifiableList(copyTypeVarTypes)); - visitingExecutableTypeParam = false; - } - - return copy; + return copy; + } + + @Override + public AnnotatedTypeMirror visitUnion( + AnnotatedUnionType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); } - @Override - public AnnotatedTypeMirror visitArray( - AnnotatedArrayType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } + AnnotatedUnionType copy = makeOrReturnCopy(original, originalToCopy); - AnnotatedArrayType copy = makeOrReturnCopy(original, originalToCopy); + if (original.alternatives != null) { + List copyAlternatives = + CollectionsPlume.mapList( + (AnnotatedDeclaredType supertype) -> + (AnnotatedDeclaredType) visit(supertype, originalToCopy), + original.alternatives); + copy.alternatives = Collections.unmodifiableList(copyAlternatives); + } - copy.setComponentType(visit(original.getComponentType(), originalToCopy)); + return copy; + } - return copy; + @Override + public AnnotatedTypeMirror visitExecutable( + AnnotatedExecutableType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); } - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } + AnnotatedExecutableType copy = makeOrReturnCopy(original, originalToCopy); - AnnotatedTypeVariable copy = makeOrReturnCopy(original, originalToCopy); + copy.setElement(original.getElement()); - if (original.getUpperBoundField() != null) { - copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); - } + if (original.getReceiverType() != null) { + copy.setReceiverType( + (AnnotatedDeclaredType) visit(original.getReceiverType(), originalToCopy)); + } - if (original.getLowerBoundField() != null) { - copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); - } + List originalParameterTypes = original.getParameterTypes(); + if (originalParameterTypes.isEmpty()) { + copy.setParameterTypes(Collections.emptyList()); + } else { + List copyParamTypes = new ArrayList<>(originalParameterTypes.size()); + for (AnnotatedTypeMirror param : originalParameterTypes) { + copyParamTypes.add(visit(param, originalToCopy)); + } + copy.setParameterTypes(Collections.unmodifiableList(copyParamTypes)); + } - return copy; + if (original.getVarargType() != null) { + copy.setVarargType(original.getVarargType()); + } else { + copy.computeVarargType(); } - @Override - public AnnotatedTypeMirror visitPrimitive( - AnnotatedPrimitiveType original, - IdentityHashMap originalToCopy) { - return makeOrReturnCopy(original, originalToCopy); + List originalThrownTypes = original.getThrownTypes(); + if (originalThrownTypes.isEmpty()) { + copy.setThrownTypes(Collections.emptyList()); + } else { + List copyThrownTypes = new ArrayList<>(originalThrownTypes.size()); + for (AnnotatedTypeMirror thrown : original.getThrownTypes()) { + copyThrownTypes.add(visit(thrown, originalToCopy)); + } + copy.setThrownTypes(Collections.unmodifiableList(copyThrownTypes)); } - @Override - public AnnotatedTypeMirror visitNoType( - AnnotatedNoType original, - IdentityHashMap originalToCopy) { - return makeOrReturnCopy(original, originalToCopy); + copy.setReturnType(visit(original.getReturnType(), originalToCopy)); + + List originalTypeVariables = original.getTypeVariables(); + if (originalTypeVariables.isEmpty()) { + copy.setTypeVariables(Collections.emptyList()); + } else { + List copyTypeVarTypes = new ArrayList<>(originalTypeVariables.size()); + for (AnnotatedTypeVariable typeVariable : originalTypeVariables) { + // This field is needed to identify exactly when the declaration of an executable's + // type parameter is visited. When subtypes of this class visit the type + // parameter's component types, they will likely set visitingExecutableTypeParam to + // false. + // Therefore, we set this variable on each iteration of the loop. + // See TypeVariableSubstitutor.Visitor.visitTypeVariable for an example of this. + visitingExecutableTypeParam = true; + copyTypeVarTypes.add((AnnotatedTypeVariable) visit(typeVariable, originalToCopy)); + } + copy.setTypeVariables(Collections.unmodifiableList(copyTypeVarTypes)); + visitingExecutableTypeParam = false; } - @Override - public AnnotatedTypeMirror visitNull( - AnnotatedNullType original, - IdentityHashMap originalToCopy) { - return makeOrReturnCopy(original, originalToCopy); + return copy; + } + + @Override + public AnnotatedTypeMirror visitArray( + AnnotatedArrayType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); } - @Override - public AnnotatedTypeMirror visitWildcard( - AnnotatedWildcardType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } + AnnotatedArrayType copy = makeOrReturnCopy(original, originalToCopy); - AnnotatedWildcardType copy = makeOrReturnCopy(original, originalToCopy); + copy.setComponentType(visit(original.getComponentType(), originalToCopy)); - if (original.isUninferredTypeArgument()) { - copy.setUninferredTypeArgument(); - } + return copy; + } - if (original.getExtendsBoundField() != null) { - copy.setExtendsBound(visit(original.getExtendsBoundField(), originalToCopy).asUse()); - } + @Override + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } + + AnnotatedTypeVariable copy = makeOrReturnCopy(original, originalToCopy); - if (original.getSuperBoundField() != null) { - copy.setSuperBound(visit(original.getSuperBoundField(), originalToCopy).asUse()); - } + if (original.getUpperBoundField() != null) { + copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); + } - copy.setTypeVariable(original.getTypeVariable()); + if (original.getLowerBoundField() != null) { + copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); + } + + return copy; + } + + @Override + public AnnotatedTypeMirror visitPrimitive( + AnnotatedPrimitiveType original, + IdentityHashMap originalToCopy) { + return makeOrReturnCopy(original, originalToCopy); + } + + @Override + public AnnotatedTypeMirror visitNoType( + AnnotatedNoType original, + IdentityHashMap originalToCopy) { + return makeOrReturnCopy(original, originalToCopy); + } + + @Override + public AnnotatedTypeMirror visitNull( + AnnotatedNullType original, + IdentityHashMap originalToCopy) { + return makeOrReturnCopy(original, originalToCopy); + } + + @Override + public AnnotatedTypeMirror visitWildcard( + AnnotatedWildcardType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } + + AnnotatedWildcardType copy = makeOrReturnCopy(original, originalToCopy); + + if (original.isUninferredTypeArgument()) { + copy.setUninferredTypeArgument(); + } - return copy; + if (original.getExtendsBoundField() != null) { + copy.setExtendsBound(visit(original.getExtendsBoundField(), originalToCopy).asUse()); } - /** - * For any given object in the type being copied, we only want to generate one copy of that - * object. When that object is encountered again, using the previously generated copy will - * preserve the structure of the original AnnotatedTypeMirror. - * - *

makeOrReturnCopy first checks to see if an object has been encountered before. If so, it - * returns the previously generated duplicate of that object if not, it creates a duplicate of - * the object and stores it in the history, originalToCopy - * - * @param original a reference to a type to copy - * @param originalToCopy a mapping of previously encountered references to the copies made for - * those references - * @param the type of original copy, this is a shortcut to avoid having to insert casts all - * over the visitor - * @return a copy of original - */ - @SuppressWarnings("unchecked") - protected T makeOrReturnCopy( - T original, IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return (T) originalToCopy.get(original); - } - - T copy = makeCopy(original); - originalToCopy.put(original, copy); - - return copy; + if (original.getSuperBoundField() != null) { + copy.setSuperBound(visit(original.getSuperBoundField(), originalToCopy).asUse()); } - /** - * Returns a copy of the given type. - * - * @param the type of the AnnotatedTypeMirror to copy - * @param original an AnnotatedTypeMirror (more specifically, a {@code T}) - * @return a copy of the given AnnotatedTypeMirror - */ - @SuppressWarnings("unchecked") - protected T makeCopy(T original) { - T copy = - (T) - AnnotatedTypeMirror.createType( - original.getUnderlyingType(), - original.atypeFactory, - original.isDeclaration()); - maybeCopyPrimaryAnnotations(original, copy); - - return copy; + copy.setTypeVariable(original.getTypeVariable()); + + return copy; + } + + /** + * For any given object in the type being copied, we only want to generate one copy of that + * object. When that object is encountered again, using the previously generated copy will + * preserve the structure of the original AnnotatedTypeMirror. + * + *

makeOrReturnCopy first checks to see if an object has been encountered before. If so, it + * returns the previously generated duplicate of that object if not, it creates a duplicate of the + * object and stores it in the history, originalToCopy + * + * @param original a reference to a type to copy + * @param originalToCopy a mapping of previously encountered references to the copies made for + * those references + * @param the type of original copy, this is a shortcut to avoid having to insert casts all + * over the visitor + * @return a copy of original + */ + @SuppressWarnings("unchecked") + protected T makeOrReturnCopy( + T original, IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return (T) originalToCopy.get(original); } - /** - * This method is called in any location in which a primary annotation would be copied from - * source to dest. Note, this method obeys the copyAnnotations field. Subclasses of - * AnnotatedTypeCopier can use this method to customize annotations before copying. - * - * @param source the type whose primary annotations are being copied - * @param dest a copy of source that should receive its primary annotations - */ - protected void maybeCopyPrimaryAnnotations( - AnnotatedTypeMirror source, AnnotatedTypeMirror dest) { - if (copyAnnotations) { - dest.addAnnotations(source.getAnnotationsField()); - } + T copy = makeCopy(original); + originalToCopy.put(original, copy); + + return copy; + } + + /** + * Returns a copy of the given type. + * + * @param the type of the AnnotatedTypeMirror to copy + * @param original an AnnotatedTypeMirror (more specifically, a {@code T}) + * @return a copy of the given AnnotatedTypeMirror + */ + @SuppressWarnings("unchecked") + protected T makeCopy(T original) { + T copy = + (T) + AnnotatedTypeMirror.createType( + original.getUnderlyingType(), original.atypeFactory, original.isDeclaration()); + maybeCopyPrimaryAnnotations(original, copy); + + return copy; + } + + /** + * This method is called in any location in which a primary annotation would be copied from source + * to dest. Note, this method obeys the copyAnnotations field. Subclasses of AnnotatedTypeCopier + * can use this method to customize annotations before copying. + * + * @param source the type whose primary annotations are being copied + * @param dest a copy of source that should receive its primary annotations + */ + protected void maybeCopyPrimaryAnnotations(AnnotatedTypeMirror source, AnnotatedTypeMirror dest) { + if (copyAnnotations) { + dest.addAnnotations(source.getAnnotationsField()); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java index 42e44fb0a8c..224027e102d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java @@ -1,84 +1,81 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; - import java.util.IdentityHashMap; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; /** Duplicates annotated types and replaces components according to a replacement map. */ public class AnnotatedTypeCopierWithReplacement { - /** - * Return a copy of type after making the specified replacements. - * - * @param type the type that will be copied with replaced components - * @param replacementMap a mapping of {@literal referenceToReplace => referenceOfReplacement} - * @return a duplicate of type in which every reference that was a key in replacementMap has - * been replaced by its corresponding value - */ - public static AnnotatedTypeMirror replace( - AnnotatedTypeMirror type, - IdentityHashMap - replacementMap) { - return new Visitor(replacementMap).visit(type); - } - - /** - * AnnotatedTypeCopier maintains a mapping of {@literal typeVisited => copyOfTypeVisited} When a - * reference, typeVisited, is encountered again, it will use the recorded reference, - * copyOfTypeVisited, instead of generating a new copy of typeVisited. Visitor pre-populates - * this mapping so that references are replaced not by their copies but by those in the - * replacementMap provided in the constructor. - * - *

All types NOT in the replacement map are duplicated as per AnnotatedTypeCopier.visit - */ - protected static class Visitor extends AnnotatedTypeCopier { + /** + * Return a copy of type after making the specified replacements. + * + * @param type the type that will be copied with replaced components + * @param replacementMap a mapping of {@literal referenceToReplace => referenceOfReplacement} + * @return a duplicate of type in which every reference that was a key in replacementMap has been + * replaced by its corresponding value + */ + public static AnnotatedTypeMirror replace( + AnnotatedTypeMirror type, + IdentityHashMap + replacementMap) { + return new Visitor(replacementMap).visit(type); + } - private final IdentityHashMap - originalMappings; + /** + * AnnotatedTypeCopier maintains a mapping of {@literal typeVisited => copyOfTypeVisited} When a + * reference, typeVisited, is encountered again, it will use the recorded reference, + * copyOfTypeVisited, instead of generating a new copy of typeVisited. Visitor pre-populates this + * mapping so that references are replaced not by their copies but by those in the replacementMap + * provided in the constructor. + * + *

All types NOT in the replacement map are duplicated as per AnnotatedTypeCopier.visit + */ + protected static class Visitor extends AnnotatedTypeCopier { - public Visitor( - final IdentityHashMap - mappings) { - originalMappings = new IdentityHashMap<>(mappings); - } + private final IdentityHashMap + originalMappings; - @Override - public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { - return type.accept(this, new IdentityHashMap<>(originalMappings)); - } + public Visitor( + final IdentityHashMap + mappings) { + originalMappings = new IdentityHashMap<>(mappings); + } - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - // AnnotatedTypeCopier will visit the type parameters of a method and copy them. - // Without this flag, any mappings in originalToCopy would replace the type parameters. - // However, we do not replace the type parameters in an AnnotatedExecutableType. Also, - // AnnotatedExecutableType.typeVarTypes is of type List so if the - // mapping contained a type parameter -> (Non-type variable AnnotatedTypeMirror) then a - // runtime exception would occur. - if (visitingExecutableTypeParam) { - visitingExecutableTypeParam = false; - AnnotatedTypeVariable copy = - (AnnotatedTypeVariable) - AnnotatedTypeMirror.createType( - original.getUnderlyingType(), - original.atypeFactory, - original.isDeclaration()); - maybeCopyPrimaryAnnotations(original, copy); - originalToCopy.put(original, copy); + @Override + public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { + return type.accept(this, new IdentityHashMap<>(originalMappings)); + } - if (original.getUpperBoundField() != null) { - copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); - } + @Override + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + // AnnotatedTypeCopier will visit the type parameters of a method and copy them. + // Without this flag, any mappings in originalToCopy would replace the type parameters. + // However, we do not replace the type parameters in an AnnotatedExecutableType. Also, + // AnnotatedExecutableType.typeVarTypes is of type List so if the + // mapping contained a type parameter -> (Non-type variable AnnotatedTypeMirror) then a + // runtime exception would occur. + if (visitingExecutableTypeParam) { + visitingExecutableTypeParam = false; + AnnotatedTypeVariable copy = + (AnnotatedTypeVariable) + AnnotatedTypeMirror.createType( + original.getUnderlyingType(), original.atypeFactory, original.isDeclaration()); + maybeCopyPrimaryAnnotations(original, copy); + originalToCopy.put(original, copy); - if (original.getLowerBoundField() != null) { - copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); - } - return copy; - } + if (original.getUpperBoundField() != null) { + copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); + } - return super.visitTypeVariable(original, originalToCopy); + if (original.getLowerBoundField() != null) { + copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); } + return copy; + } + + return super.visitTypeVariable(original, originalToCopy); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 8303920acd5..8967e398d39 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -27,7 +27,49 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Options; - +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; @@ -93,51 +135,6 @@ import org.plumelib.util.IPair; import org.plumelib.util.StringsPlume; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Target; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.IntersectionType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - /** * The methods of this class take an element or AST node, and return the annotated type as an {@link * AnnotatedTypeMirror}. The methods are: @@ -171,5949 +168,5828 @@ */ public class AnnotatedTypeFactory implements AnnotationProvider { - /** Whether to print verbose debugging messages about stub files. */ - private final boolean debugStubParser; - - /** The {@link Trees} instance to use for tree node path finding. */ - protected final Trees trees; - - /** Optional! The AST of the source file being operated on. */ - // TODO: when should root be null? What are the use cases? - // None of the existing test checkers has a null root. - // Should not be modified between calls to "visit". - private @Nullable CompilationUnitTree root; - - /** The processing environment to use for accessing compiler internals. */ - protected final ProcessingEnvironment processingEnv; - - /** Utility class for working with {@link Element}s. */ - protected final Elements elements; - - /** Utility class for working with {@link TypeMirror}s. */ - public final Types types; - - /** - * A TreePath to the current tree that an external "visitor" is visiting. The visitor is either - * a subclass of {@link BaseTypeVisitor} or {@link - * org.checkerframework.framework.flow.CFAbstractTransfer}. - */ - private @Nullable TreePath visitorTreePath; - - // These variables cannot be static because they depend on the ProcessingEnvironment. - /** The AnnotatedFor.value argument/element. */ - protected final ExecutableElement annotatedForValueElement; - - /** The EnsuresQualifier.expression field/element. */ - protected final ExecutableElement ensuresQualifierExpressionElement; - - /** The EnsuresQualifier.List.value field/element. */ - protected final ExecutableElement ensuresQualifierListValueElement; - - /** The EnsuresQualifierIf.expression field/element. */ - protected final ExecutableElement ensuresQualifierIfExpressionElement; - - /** The EnsuresQualifierIf.result argument/element. */ - protected final ExecutableElement ensuresQualifierIfResultElement; - - /** The EnsuresQualifierIf.List.value field/element. */ - protected final ExecutableElement ensuresQualifierIfListValueElement; - - /** The FieldInvariant.field argument/element. */ - protected final ExecutableElement fieldInvariantFieldElement; - - /** The FieldInvariant.qualifier argument/element. */ - protected final ExecutableElement fieldInvariantQualifierElement; - - /** The HasQualifierParameter.value field/element. */ - protected final ExecutableElement hasQualifierParameterValueElement; - - /** The MethodVal.className argument/element. */ - public final ExecutableElement methodValClassNameElement; - - /** The MethodVal.methodName argument/element. */ - public final ExecutableElement methodValMethodNameElement; - - /** The MethodVal.params argument/element. */ - public final ExecutableElement methodValParamsElement; - - /** The NoQualifierParameter.value field/element. */ - protected final ExecutableElement noQualifierParameterValueElement; - - /** The RequiresQualifier.expression field/element. */ - protected final ExecutableElement requiresQualifierExpressionElement; - - /** The RequiresQualifier.List.value field/element. */ - protected final ExecutableElement requiresQualifierListValueElement; + /** Whether to print verbose debugging messages about stub files. */ + private final boolean debugStubParser; - /** The RequiresQualifier type. */ - protected final TypeMirror requiresQualifierTM; + /** The {@link Trees} instance to use for tree node path finding. */ + protected final Trees trees; - /** The RequiresQualifier.List type. */ - protected final TypeMirror requiresQualifierListTM; + /** Optional! The AST of the source file being operated on. */ + // TODO: when should root be null? What are the use cases? + // None of the existing test checkers has a null root. + // Should not be modified between calls to "visit". + private @Nullable CompilationUnitTree root; - /** The EnsuresQualifier type. */ - protected final TypeMirror ensuresQualifierTM; + /** The processing environment to use for accessing compiler internals. */ + protected final ProcessingEnvironment processingEnv; - /** The EnsuresQualifier.List type. */ - protected final TypeMirror ensuresQualifierListTM; + /** Utility class for working with {@link Element}s. */ + protected final Elements elements; - /** The EnsuresQualifierIf type. */ - protected final TypeMirror ensuresQualifierIfTM; + /** Utility class for working with {@link TypeMirror}s. */ + public final Types types; - /** The EnsuresQualifierIf.List type. */ - protected final TypeMirror ensuresQualifierIfListTM; + /** + * A TreePath to the current tree that an external "visitor" is visiting. The visitor is either a + * subclass of {@link BaseTypeVisitor} or {@link + * org.checkerframework.framework.flow.CFAbstractTransfer}. + */ + private @Nullable TreePath visitorTreePath; - /** - * ===== postInit initialized fields ==== Note: qualHierarchy and typeHierarchy are both - * initialized in the postInit. - * - * @see #postInit() This means, they cannot be final and cannot be referred to in any subclass - * constructor or method until after postInit is called - */ - - /** Represent the annotation relations. */ - // This field cannot be final because it is set in `postInit()`. - protected QualifierHierarchy qualHierarchy; + // These variables cannot be static because they depend on the ProcessingEnvironment. + /** The AnnotatedFor.value argument/element. */ + protected final ExecutableElement annotatedForValueElement; - /** Represent the type relations. */ - protected TypeHierarchy typeHierarchy; + /** The EnsuresQualifier.expression field/element. */ + protected final ExecutableElement ensuresQualifierExpressionElement; - /* NO-AFU Performs whole-program inference. If null, whole-program inference is disabled. */ - /* NO-AFU - private final @Nullable WholeProgramInference wholeProgramInference; - */ + /** The EnsuresQualifier.List.value field/element. */ + protected final ExecutableElement ensuresQualifierListValueElement; - /** Viewpoint adapter used to perform viewpoint adaptation or null */ - protected @Nullable ViewpointAdapter viewpointAdapter; + /** The EnsuresQualifierIf.expression field/element. */ + protected final ExecutableElement ensuresQualifierIfExpressionElement; - /** - * This formatter is used for converting AnnotatedTypeMirrors to Strings. This formatter will be - * used by all AnnotatedTypeMirrors created by this factory in their toString methods. - */ - protected final AnnotatedTypeFormatter typeFormatter; + /** The EnsuresQualifierIf.result argument/element. */ + protected final ExecutableElement ensuresQualifierIfResultElement; - /** - * Annotation formatter is used to format AnnotationMirrors. It is primarily used by - * SourceChecker when generating error messages. - */ - private final AnnotationFormatter annotationFormatter; + /** The EnsuresQualifierIf.List.value field/element. */ + protected final ExecutableElement ensuresQualifierIfListValueElement; - /** Holds the qualifier upper bounds for type uses. */ - protected QualifierUpperBounds qualifierUpperBounds; + /** The FieldInvariant.field argument/element. */ + protected final ExecutableElement fieldInvariantFieldElement; - /** - * Provides utility method to substitute arguments for their type variables. Field should be - * final, but can only be set in postInit, because subtypes might need other state to be - * initialized first. - */ - protected TypeVariableSubstitutor typeVarSubstitutor; + /** The FieldInvariant.qualifier argument/element. */ + protected final ExecutableElement fieldInvariantQualifierElement; - /** Provides utility method to infer type arguments. */ - protected TypeArgumentInference typeArgumentInference; + /** The HasQualifierParameter.value field/element. */ + protected final ExecutableElement hasQualifierParameterValueElement; - /** - * Caches the supported type qualifier classes. Call {@link #getSupportedTypeQualifiers()} - * instead of using this field directly, as it may not have been initialized. - */ - private @MonotonicNonNull Set> supportedQuals = null; + /** The MethodVal.className argument/element. */ + public final ExecutableElement methodValClassNameElement; - /** - * Caches the fully-qualified names of the classes in {@link #supportedQuals}. Call {@link - * #getSupportedTypeQualifierNames()} instead of using this field directly, as it may not have - * been initialized. - */ - private @MonotonicNonNull Set<@CanonicalName String> supportedQualNames = null; + /** The MethodVal.methodName argument/element. */ + public final ExecutableElement methodValMethodNameElement; - /** Parses stub files and stores annotations on public elements from stub files. */ - public final AnnotationFileElementTypes stubTypes; + /** The MethodVal.params argument/element. */ + public final ExecutableElement methodValParamsElement; - /** Parses ajava files and stores annotations on public elements from ajava files. */ - public final AnnotationFileElementTypes ajavaTypes; + /** The NoQualifierParameter.value field/element. */ + protected final ExecutableElement noQualifierParameterValueElement; - /** - * If type checking a Java file, stores annotations read from an ajava file for that class if - * one exists. Unlike {@link #ajavaTypes}, which only stores annotations on public elements, - * this stores annotations on all element locations such as in anonymous class bodies. - */ - protected @Nullable AnnotationFileElementTypes currentFileAjavaTypes; + /** The RequiresQualifier.expression field/element. */ + protected final ExecutableElement requiresQualifierExpressionElement; - /** - * A cache used to store elements whose declaration annotations have already been stored by - * calling the method {@link #getDeclAnnotations(Element)}. - */ - private final Map cacheDeclAnnos; + /** The RequiresQualifier.List.value field/element. */ + protected final ExecutableElement requiresQualifierListValueElement; - /** - * A set containing declaration annotations that should be inherited. A declaration annotation - * will be inherited if it is in this set, or if it has the - * meta-annotation @InheritedAnnotation. - */ - private final AnnotationMirrorSet inheritedAnnotations = new AnnotationMirrorSet(); + /** The RequiresQualifier type. */ + protected final TypeMirror requiresQualifierTM; - /** The checker to use for option handling and resource management. */ - protected final BaseTypeChecker checker; + /** The RequiresQualifier.List type. */ + protected final TypeMirror requiresQualifierListTM; - /** - * Scans all parts of the {@link AnnotatedTypeMirror} so that all of its fields are initialized. - */ - private final SimpleAnnotatedTypeScanner atmInitializer = - new SimpleAnnotatedTypeScanner<>((type1, q) -> null); + /** The EnsuresQualifier type. */ + protected final TypeMirror ensuresQualifierTM; - /** - * True if all methods should be assumed to be @SideEffectFree, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeSideEffectFree; + /** The EnsuresQualifier.List type. */ + protected final TypeMirror ensuresQualifierListTM; - /** - * True if all methods should be assumed to be @Deterministic, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeDeterministic; + /** The EnsuresQualifierIf type. */ + protected final TypeMirror ensuresQualifierIfTM; - /** - * True if all getter methods should be assumed to be @Pure, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumePureGetters; + /** The EnsuresQualifierIf.List type. */ + protected final TypeMirror ensuresQualifierIfListTM; - /** True if -AmergeStubsWithSource was provided on the command line. */ - private final boolean mergeStubsWithSource; + /** + * ===== postInit initialized fields ==== Note: qualHierarchy and typeHierarchy are both + * initialized in the postInit. + * + * @see #postInit() This means, they cannot be final and cannot be referred to in any subclass + * constructor or method until after postInit is called + */ - /** - * Initializes all fields of {@code type}. - * - * @param type annotated type mirror - */ - /*package-private*/ void initializeAtm(AnnotatedTypeMirror type) { - atmInitializer.visit(type); + /** Represent the annotation relations. */ + // This field cannot be final because it is set in `postInit()`. + protected QualifierHierarchy qualHierarchy; + + /** Represent the type relations. */ + protected TypeHierarchy typeHierarchy; + + /* NO-AFU Performs whole-program inference. If null, whole-program inference is disabled. */ + /* NO-AFU + private final @Nullable WholeProgramInference wholeProgramInference; + */ + + /** Viewpoint adapter used to perform viewpoint adaptation or null */ + protected @Nullable ViewpointAdapter viewpointAdapter; + + /** + * This formatter is used for converting AnnotatedTypeMirrors to Strings. This formatter will be + * used by all AnnotatedTypeMirrors created by this factory in their toString methods. + */ + protected final AnnotatedTypeFormatter typeFormatter; + + /** + * Annotation formatter is used to format AnnotationMirrors. It is primarily used by SourceChecker + * when generating error messages. + */ + private final AnnotationFormatter annotationFormatter; + + /** Holds the qualifier upper bounds for type uses. */ + protected QualifierUpperBounds qualifierUpperBounds; + + /** + * Provides utility method to substitute arguments for their type variables. Field should be + * final, but can only be set in postInit, because subtypes might need other state to be + * initialized first. + */ + protected TypeVariableSubstitutor typeVarSubstitutor; + + /** Provides utility method to infer type arguments. */ + protected TypeArgumentInference typeArgumentInference; + + /** + * Caches the supported type qualifier classes. Call {@link #getSupportedTypeQualifiers()} instead + * of using this field directly, as it may not have been initialized. + */ + private @MonotonicNonNull Set> supportedQuals = null; + + /** + * Caches the fully-qualified names of the classes in {@link #supportedQuals}. Call {@link + * #getSupportedTypeQualifierNames()} instead of using this field directly, as it may not have + * been initialized. + */ + private @MonotonicNonNull Set<@CanonicalName String> supportedQualNames = null; + + /** Parses stub files and stores annotations on public elements from stub files. */ + public final AnnotationFileElementTypes stubTypes; + + /** Parses ajava files and stores annotations on public elements from ajava files. */ + public final AnnotationFileElementTypes ajavaTypes; + + /** + * If type checking a Java file, stores annotations read from an ajava file for that class if one + * exists. Unlike {@link #ajavaTypes}, which only stores annotations on public elements, this + * stores annotations on all element locations such as in anonymous class bodies. + */ + protected @Nullable AnnotationFileElementTypes currentFileAjavaTypes; + + /** + * A cache used to store elements whose declaration annotations have already been stored by + * calling the method {@link #getDeclAnnotations(Element)}. + */ + private final Map cacheDeclAnnos; + + /** + * A set containing declaration annotations that should be inherited. A declaration annotation + * will be inherited if it is in this set, or if it has the meta-annotation @InheritedAnnotation. + */ + private final AnnotationMirrorSet inheritedAnnotations = new AnnotationMirrorSet(); + + /** The checker to use for option handling and resource management. */ + protected final BaseTypeChecker checker; + + /** + * Scans all parts of the {@link AnnotatedTypeMirror} so that all of its fields are initialized. + */ + private final SimpleAnnotatedTypeScanner atmInitializer = + new SimpleAnnotatedTypeScanner<>((type1, q) -> null); + + /** + * True if all methods should be assumed to be @SideEffectFree, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeSideEffectFree; + + /** + * True if all methods should be assumed to be @Deterministic, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeDeterministic; + + /** + * True if all getter methods should be assumed to be @Pure, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumePureGetters; + + /** True if -AmergeStubsWithSource was provided on the command line. */ + private final boolean mergeStubsWithSource; + + /** + * Initializes all fields of {@code type}. + * + * @param type annotated type mirror + */ + /*package-private*/ void initializeAtm(AnnotatedTypeMirror type) { + atmInitializer.visit(type); + } + + /** Map keys are canonical names of aliased annotations. */ + private final Map<@FullyQualifiedName String, Alias> aliases = new HashMap<>(); + + /** + * A map from the canonical name of a declaration annotation to the mapping of the canonical name + * of a declaration annotation with the same meaning (an alias) to the annotation mirror that + * should be used instead (an instance of the canonical declaration annotation). + */ + // A further generalization is to do something similar to `aliases`, where we allow copying + // elements from the alias to the canonical annotation. + private final Map<@FullyQualifiedName String, Map<@FullyQualifiedName String, AnnotationMirror>> + declAliases = new HashMap<>(); + + /** + * Information about one annotation alias. + * + *

The information is either an AnotationMirror that can be used directly, or information for a + * builder (name and fields not to copy); see checkRep. + */ + private static class Alias { + /** The canonical annotation (or null if copyElements == true). */ + final AnnotationMirror canonical; + + /** Whether elements should be copied over when translating to the canonical annotation. */ + final boolean copyElements; + + /** The canonical annotation name (or null if copyElements == false). */ + final @CanonicalName String canonicalName; + + /** Which elements should not be copied over (or null if copyElements == false). */ + final String[] ignorableElements; + + /** + * Create an Alias with the given components. + * + * @param aliasName the alias name; only used for debugging + * @param canonical the canonical annotation + * @param copyElements whether elements should be copied over when translating to the canonical + * annotation + * @param canonicalName the canonical annotation name (or null if copyElements == false) + * @param ignorableElements elements that should not be copied over + */ + Alias( + String aliasName, + AnnotationMirror canonical, + boolean copyElements, + @Nullable @CanonicalName String canonicalName, + String[] ignorableElements) { + this.canonical = canonical; + this.copyElements = copyElements; + this.canonicalName = canonicalName; + this.ignorableElements = ignorableElements; + checkRep(aliasName); + } + + /** + * Throw an exception if this object is malformed. + * + * @param aliasName the alias name; only used for diagnostic messages + */ + void checkRep(String aliasName) { + if (copyElements) { + if (!(canonical == null && canonicalName != null && ignorableElements != null)) { + throw new BugInCF( + "Bad Alias for %s: [canonical=%s] copyElements=%s canonicalName=%s" + + " ignorableElements=%s", + aliasName, canonical, copyElements, canonicalName, ignorableElements); + } + } else { + if (!(canonical != null && canonicalName == null && ignorableElements == null)) { + throw new BugInCF( + "Bad Alias for %s: canonical=%s copyElements=%s [canonicalName=%s" + + " ignorableElements=%s]", + aliasName, canonical, copyElements, canonicalName, ignorableElements); + } + } } - - /** Map keys are canonical names of aliased annotations. */ - private final Map<@FullyQualifiedName String, Alias> aliases = new HashMap<>(); - - /** - * A map from the canonical name of a declaration annotation to the mapping of the canonical - * name of a declaration annotation with the same meaning (an alias) to the annotation mirror - * that should be used instead (an instance of the canonical declaration annotation). - */ - // A further generalization is to do something similar to `aliases`, where we allow copying - // elements from the alias to the canonical annotation. - private final Map<@FullyQualifiedName String, Map<@FullyQualifiedName String, AnnotationMirror>> - declAliases = new HashMap<>(); - - /** - * Information about one annotation alias. - * - *

The information is either an AnotationMirror that can be used directly, or information for - * a builder (name and fields not to copy); see checkRep. - */ - private static class Alias { - /** The canonical annotation (or null if copyElements == true). */ - final AnnotationMirror canonical; - - /** Whether elements should be copied over when translating to the canonical annotation. */ - final boolean copyElements; - - /** The canonical annotation name (or null if copyElements == false). */ - final @CanonicalName String canonicalName; - - /** Which elements should not be copied over (or null if copyElements == false). */ - final String[] ignorableElements; - - /** - * Create an Alias with the given components. - * - * @param aliasName the alias name; only used for debugging - * @param canonical the canonical annotation - * @param copyElements whether elements should be copied over when translating to the - * canonical annotation - * @param canonicalName the canonical annotation name (or null if copyElements == false) - * @param ignorableElements elements that should not be copied over - */ - Alias( - String aliasName, - AnnotationMirror canonical, - boolean copyElements, - @Nullable @CanonicalName String canonicalName, - String[] ignorableElements) { - this.canonical = canonical; - this.copyElements = copyElements; - this.canonicalName = canonicalName; - this.ignorableElements = ignorableElements; - checkRep(aliasName); + } + + /** Unique ID counter; for debugging purposes. */ + private static int uidCounter = 0; + + /** Unique ID of the current object; for debugging purposes. */ + public final int uid; + + /** + * Object that is used to resolve reflective method calls, if reflection resolution is turned on. + */ + protected ReflectionResolver reflectionResolver; + + /** This loads type annotation classes via reflective lookup. */ + protected AnnotationClassLoader loader; + + /* NO-AFU + * Which whole-program inference output format to use, if doing whole-program inference. This + * variable would be final, but it is not set unless WPI is enabled. + */ + /* NO-AFU + public WholeProgramInference.OutputFormat wpiOutputFormat; + */ + + /** + * Should results be cached? This means that ATM.deepCopy() will be called. ATM.deepCopy() used to + * (and perhaps still does) side effect the ATM being copied. So setting this to false is not + * equivalent to setting shouldReadCache to false. + */ + public boolean shouldCache; + + /** Size of LRU cache if one isn't specified using the atfCacheSize option. */ + private static final int DEFAULT_CACHE_SIZE = 300; + + /** Mapping from a Tree to its annotated type; defaults have been applied. */ + private final Map classAndMethodTreeCache; + + /** + * Mapping from an expression tree to its annotated type; before defaults are applied, just what + * the programmer wrote. + */ + protected final Map fromExpressionTreeCache; + + /** + * Mapping from a member tree to its annotated type; before defaults are applied, just what the + * programmer wrote. + */ + protected final Map fromMemberTreeCache; + + /** + * Mapping from a type tree to its annotated type; before defaults are applied, just what the + * programmer wrote. + */ + protected final Map fromTypeTreeCache; + + /** + * Mapping from an Element to its annotated type; before defaults are applied, just what the + * programmer wrote. + */ + private final Map elementCache; + + /** Mapping from an Element to the source Tree of the declaration. */ + private final Map elementToTreeCache; + + /** Mapping from a Tree to its TreePath. Shared between all instances. */ + private final TreePathCacher treePathCache; + + /** + * Whether to ignore uninferred type arguments. This is a temporary flag to work around Issue 979. + */ + public final boolean ignoreUninferredTypeArguments; + + /** The Object.getClass method. */ + protected final ExecutableElement objectGetClass; + + /** Size of the annotationClassNames cache. */ + private static final int ANNOTATION_CACHE_SIZE = 500; + + /** Maps classes representing AnnotationMirrors to their canonical names. */ + private final Map, @CanonicalName String> annotationClassNames; + + /** An annotated type of the declaration of {@link Iterable} without any annotations. */ + private AnnotatedDeclaredType iterableDeclType; + + /** + * If the option "lspTypeInfo" is defined, this presenter will report the type information of + * every type-checked class. This information can be visualized by an editor/IDE that supports + * LSP. + */ + protected final TypeInformationPresenter typeInformationPresenter; + + /** + * Constructs a factory from the given checker. + * + *

A subclass must call postInit at the end of its constructor. postInit must be the last call + * in the constructor or else types from stub files may not be created as expected. + * + * @param checker the {@link SourceChecker} to which this factory belongs + */ + public AnnotatedTypeFactory(BaseTypeChecker checker) { + uid = ++uidCounter; + this.processingEnv = checker.getProcessingEnvironment(); + this.checker = checker; + this.assumeSideEffectFree = + checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); + this.assumeDeterministic = + checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); + this.assumePureGetters = checker.hasOption("assumePureGetters"); + + this.trees = Trees.instance(processingEnv); + this.elements = processingEnv.getElementUtils(); + this.types = processingEnv.getTypeUtils(); + + this.stubTypes = new AnnotationFileElementTypes(this); + this.ajavaTypes = new AnnotationFileElementTypes(this); + this.currentFileAjavaTypes = null; + + this.cacheDeclAnnos = new HashMap<>(); + + // get the shared instance from the checker + this.treePathCache = checker.getTreePathCacher(); + + this.shouldCache = !checker.hasOption("atfDoNotCache"); + if (shouldCache) { + int cacheSize = getCacheSize(); + this.classAndMethodTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.fromExpressionTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.fromMemberTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.fromTypeTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.elementCache = CollectionsPlume.createLruCache(cacheSize); + this.elementToTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.annotationClassNames = + Collections.synchronizedMap(CollectionsPlume.createLruCache(ANNOTATION_CACHE_SIZE)); + } else { + this.classAndMethodTreeCache = null; + this.fromExpressionTreeCache = null; + this.fromMemberTreeCache = null; + this.fromTypeTreeCache = null; + this.elementCache = null; + this.elementToTreeCache = null; + this.annotationClassNames = null; + } + + this.typeFormatter = createAnnotatedTypeFormatter(); + this.annotationFormatter = createAnnotationFormatter(); + this.typeInformationPresenter = createTypeInformationPresenter(); + + // Alias provided via -AaliasedTypeAnnos command-line option. + // This can only be used for annotations whose attributes have the same names as in the + // canonical annotation, e.g. this will not be usable to declare an alias @Regex(index = 5) + // for @Regex(value = 5). + if (checker.hasOption("aliasedTypeAnnos")) { + String aliasesOption = checker.getOption("aliasedTypeAnnos"); + String[] annos = aliasesOption.split(";"); + for (String alias : annos) { + IPair, @FullyQualifiedName String[]> aliasPair = + parseAliasesFromString(alias); + for (@FullyQualifiedName String a : aliasPair.second) { + addAliasedTypeAnnotation(a, aliasPair.first, true); } + } + } - /** - * Throw an exception if this object is malformed. - * - * @param aliasName the alias name; only used for diagnostic messages - */ - void checkRep(String aliasName) { - if (copyElements) { - if (!(canonical == null && canonicalName != null && ignorableElements != null)) { - throw new BugInCF( - "Bad Alias for %s: [canonical=%s] copyElements=%s canonicalName=%s" - + " ignorableElements=%s", - aliasName, canonical, copyElements, canonicalName, ignorableElements); - } - } else { - if (!(canonical != null && canonicalName == null && ignorableElements == null)) { - throw new BugInCF( - "Bad Alias for %s: canonical=%s copyElements=%s [canonicalName=%s" - + " ignorableElements=%s]", - aliasName, canonical, copyElements, canonicalName, ignorableElements); - } - } + // Alias provided via -AaliasedDeclAnnos command-line option. + // This can only be used for annotations without attributes, + // e.g. this will not be usable to declare an alias for @EnsuresNonNull(...). + if (checker.hasOption("aliasedDeclAnnos")) { + String aliasesOption = checker.getOption("aliasedDeclAnnos"); + String[] annos = aliasesOption.split(";"); + for (String alias : annos) { + IPair, @FullyQualifiedName String[]> aliasPair = + parseAliasesFromString(alias); + AnnotationMirror anno = AnnotationBuilder.fromClass(elements, aliasPair.first); + for (String a : aliasPair.second) { + addAliasedDeclAnnotation(a, aliasPair.first.getCanonicalName(), anno); } + } } - /** Unique ID counter; for debugging purposes. */ - private static int uidCounter = 0; - - /** Unique ID of the current object; for debugging purposes. */ - public final int uid; - - /** - * Object that is used to resolve reflective method calls, if reflection resolution is turned - * on. - */ - protected ReflectionResolver reflectionResolver; - - /** This loads type annotation classes via reflective lookup. */ - protected AnnotationClassLoader loader; - - /* NO-AFU - * Which whole-program inference output format to use, if doing whole-program inference. This - * variable would be final, but it is not set unless WPI is enabled. - */ /* NO-AFU - public WholeProgramInference.OutputFormat wpiOutputFormat; + if (checker.hasOption("infer")) { + checkInvalidOptionsInferSignatures(); + String inferArg = checker.getOption("infer"); + // No argument means "jaifs", for (temporary) backwards compatibility. + if (inferArg == null) { + inferArg = "jaifs"; + } + switch (inferArg) { + case "stubs": + wpiOutputFormat = WholeProgramInference.OutputFormat.STUB; + break; + case "jaifs": + wpiOutputFormat = WholeProgramInference.OutputFormat.JAIF; + break; + case "ajava": + wpiOutputFormat = WholeProgramInference.OutputFormat.AJAVA; + break; + default: + throw new UserError( + "Bad argument -Ainfer=" + + inferArg + + " should be one of: -Ainfer=jaifs, -Ainfer=stubs, -Ainfer=ajava"); + } + boolean showWpiFailedInferences = checker.hasOption("showWpiFailedInferences"); + boolean inferOutputOriginal = checker.hasOption("inferOutputOriginal"); + if (inferOutputOriginal && wpiOutputFormat != WholeProgramInference.OutputFormat.AJAVA) { + checker.message( + Diagnostic.Kind.WARNING, + "-AinferOutputOriginal only works with -Ainfer=ajava, so it is being ignored."); + } + if (wpiOutputFormat == WholeProgramInference.OutputFormat.AJAVA) { + wholeProgramInference = + new WholeProgramInferenceImplementation( + this, + new WholeProgramInferenceJavaParserStorage(this, inferOutputOriginal), + showWpiFailedInferences); + } else { + wholeProgramInference = + new WholeProgramInferenceImplementation( + this, new WholeProgramInferenceScenesStorage(this), showWpiFailedInferences); + } + if (!checker.hasOption("warns")) { + // Without -Awarns, the inference output may be incomplete, because javac halts + // after issuing an error. + checker.message(Diagnostic.Kind.ERROR, "Do not supply -Ainfer without -Awarns"); + } + } else { + wholeProgramInference = null; + } */ - /** - * Should results be cached? This means that ATM.deepCopy() will be called. ATM.deepCopy() used - * to (and perhaps still does) side effect the ATM being copied. So setting this to false is not - * equivalent to setting shouldReadCache to false. - */ - public boolean shouldCache; - - /** Size of LRU cache if one isn't specified using the atfCacheSize option. */ - private static final int DEFAULT_CACHE_SIZE = 300; - - /** Mapping from a Tree to its annotated type; defaults have been applied. */ - private final Map classAndMethodTreeCache; - - /** - * Mapping from an expression tree to its annotated type; before defaults are applied, just what - * the programmer wrote. - */ - protected final Map fromExpressionTreeCache; - - /** - * Mapping from a member tree to its annotated type; before defaults are applied, just what the - * programmer wrote. - */ - protected final Map fromMemberTreeCache; - - /** - * Mapping from a type tree to its annotated type; before defaults are applied, just what the - * programmer wrote. - */ - protected final Map fromTypeTreeCache; - - /** - * Mapping from an Element to its annotated type; before defaults are applied, just what the - * programmer wrote. - */ - private final Map elementCache; - - /** Mapping from an Element to the source Tree of the declaration. */ - private final Map elementToTreeCache; - - /** Mapping from a Tree to its TreePath. Shared between all instances. */ - private final TreePathCacher treePathCache; - - /** - * Whether to ignore uninferred type arguments. This is a temporary flag to work around Issue - * 979. - */ - public final boolean ignoreUninferredTypeArguments; - - /** The Object.getClass method. */ - protected final ExecutableElement objectGetClass; - - /** Size of the annotationClassNames cache. */ - private static final int ANNOTATION_CACHE_SIZE = 500; - - /** Maps classes representing AnnotationMirrors to their canonical names. */ - private final Map, @CanonicalName String> annotationClassNames; - - /** An annotated type of the declaration of {@link Iterable} without any annotations. */ - private AnnotatedDeclaredType iterableDeclType; - - /** - * If the option "lspTypeInfo" is defined, this presenter will report the type information of - * every type-checked class. This information can be visualized by an editor/IDE that supports - * LSP. - */ - protected final TypeInformationPresenter typeInformationPresenter; - - /** - * Constructs a factory from the given checker. - * - *

A subclass must call postInit at the end of its constructor. postInit must be the last - * call in the constructor or else types from stub files may not be created as expected. - * - * @param checker the {@link SourceChecker} to which this factory belongs - */ - public AnnotatedTypeFactory(BaseTypeChecker checker) { - uid = ++uidCounter; - this.processingEnv = checker.getProcessingEnvironment(); - this.checker = checker; - this.assumeSideEffectFree = - checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); - this.assumeDeterministic = - checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); - this.assumePureGetters = checker.hasOption("assumePureGetters"); - - this.trees = Trees.instance(processingEnv); - this.elements = processingEnv.getElementUtils(); - this.types = processingEnv.getTypeUtils(); - - this.stubTypes = new AnnotationFileElementTypes(this); - this.ajavaTypes = new AnnotationFileElementTypes(this); - this.currentFileAjavaTypes = null; - - this.cacheDeclAnnos = new HashMap<>(); - - // get the shared instance from the checker - this.treePathCache = checker.getTreePathCacher(); - - this.shouldCache = !checker.hasOption("atfDoNotCache"); - if (shouldCache) { - int cacheSize = getCacheSize(); - this.classAndMethodTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.fromExpressionTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.fromMemberTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.fromTypeTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.elementCache = CollectionsPlume.createLruCache(cacheSize); - this.elementToTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.annotationClassNames = - Collections.synchronizedMap( - CollectionsPlume.createLruCache(ANNOTATION_CACHE_SIZE)); - } else { - this.classAndMethodTreeCache = null; - this.fromExpressionTreeCache = null; - this.fromMemberTreeCache = null; - this.fromTypeTreeCache = null; - this.elementCache = null; - this.elementToTreeCache = null; - this.annotationClassNames = null; + ignoreUninferredTypeArguments = !checker.hasOption("conservativeUninferredTypeArguments"); + + objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv); + + this.debugStubParser = checker.hasOption("stubDebug"); + + annotatedForValueElement = TreeUtils.getMethod(AnnotatedFor.class, "value", 0, processingEnv); + ensuresQualifierExpressionElement = + TreeUtils.getMethod(EnsuresQualifier.class, "expression", 0, processingEnv); + ensuresQualifierListValueElement = + TreeUtils.getMethod(EnsuresQualifier.List.class, "value", 0, processingEnv); + ensuresQualifierIfExpressionElement = + TreeUtils.getMethod(EnsuresQualifierIf.class, "expression", 0, processingEnv); + ensuresQualifierIfResultElement = + TreeUtils.getMethod(EnsuresQualifierIf.class, "result", 0, processingEnv); + ensuresQualifierIfListValueElement = + TreeUtils.getMethod(EnsuresQualifierIf.List.class, "value", 0, processingEnv); + fieldInvariantFieldElement = + TreeUtils.getMethod(FieldInvariant.class, "field", 0, processingEnv); + fieldInvariantQualifierElement = + TreeUtils.getMethod(FieldInvariant.class, "qualifier", 0, processingEnv); + hasQualifierParameterValueElement = + TreeUtils.getMethod(HasQualifierParameter.class, "value", 0, processingEnv); + methodValClassNameElement = TreeUtils.getMethod(MethodVal.class, "className", 0, processingEnv); + methodValMethodNameElement = + TreeUtils.getMethod(MethodVal.class, "methodName", 0, processingEnv); + methodValParamsElement = TreeUtils.getMethod(MethodVal.class, "params", 0, processingEnv); + noQualifierParameterValueElement = + TreeUtils.getMethod(NoQualifierParameter.class, "value", 0, processingEnv); + requiresQualifierExpressionElement = + TreeUtils.getMethod(RequiresQualifier.class, "expression", 0, processingEnv); + requiresQualifierListValueElement = + TreeUtils.getMethod(RequiresQualifier.List.class, "value", 0, processingEnv); + + requiresQualifierTM = + ElementUtils.getTypeElement(processingEnv, RequiresQualifier.class).asType(); + requiresQualifierListTM = + ElementUtils.getTypeElement(processingEnv, RequiresQualifier.List.class).asType(); + ensuresQualifierTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.class).asType(); + ensuresQualifierListTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.List.class).asType(); + ensuresQualifierIfTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.class).asType(); + ensuresQualifierIfListTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.List.class).asType(); + + mergeStubsWithSource = checker.hasOption("mergeStubsWithSource"); + } + + /** + * Parse a string in the format {@code FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2} + * to a pair of {@code (FQN.canonical.Qualifier.class, ["FQN.alias1.Qual1", "FQN.alias2.Qual2"])}. + * + * @param alias in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2 + * @return a pair with the first argument being the canonical qualifier class and the second + * argument being the list of aliases with fully qualified names + */ + // signature is suppressed because there is no way to reason about parsed strings + @SuppressWarnings({"unchecked", "signature"}) + private IPair, @FullyQualifiedName String[]> parseAliasesFromString( + String alias) { + String[] parts = alias.split(":"); + if (parts.length != 2) { + throw new UserError( + String.format( + "Alias argument must be in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2, got %s instead.", + alias)); + } + Class canonical; + try { + canonical = (Class) Class.forName(parts[0].trim()); + } catch (ClassNotFoundException | ClassCastException ex) { + throw new UserError(String.format("The name %s is an invalid annotation name.", parts[0])); + } + String[] aliases = parts[1].trim().split("\\s*,\\s*"); + return IPair.of(canonical, aliases); + } + + /** + * Requires that supportedQuals is non-null and non-empty and each element is a type qualifier. + * That is, no element has a {@code @Target} meta-annotation that contains something besides + * TYPE_USE or TYPE_PARAMETER. (@Target({}) is allowed.) @ + * + * @throws BugInCF If supportedQuals is empty or contaions a non-type qualifier + */ + private void checkSupportedQualsAreTypeQuals() { + if (supportedQuals == null || supportedQuals.isEmpty()) { + throw new TypeSystemError("Found no supported qualifiers."); + } + for (Class annotationClass : supportedQuals) { + // Check @Target values + ElementType[] targetValues = annotationClass.getAnnotation(Target.class).value(); + List badTargetValues = new ArrayList<>(0); + for (ElementType element : targetValues) { + if (!(element == ElementType.TYPE_USE || element == ElementType.TYPE_PARAMETER)) { + // if there's an ElementType with an enumerated value of something other + // than TYPE_USE or TYPE_PARAMETER then it isn't a valid qualifier + badTargetValues.add(element); } - - this.typeFormatter = createAnnotatedTypeFormatter(); - this.annotationFormatter = createAnnotationFormatter(); - this.typeInformationPresenter = createTypeInformationPresenter(); - - // Alias provided via -AaliasedTypeAnnos command-line option. - // This can only be used for annotations whose attributes have the same names as in the - // canonical annotation, e.g. this will not be usable to declare an alias @Regex(index = 5) - // for @Regex(value = 5). - if (checker.hasOption("aliasedTypeAnnos")) { - String aliasesOption = checker.getOption("aliasedTypeAnnos"); - String[] annos = aliasesOption.split(";"); - for (String alias : annos) { - IPair, @FullyQualifiedName String[]> aliasPair = - parseAliasesFromString(alias); - for (@FullyQualifiedName String a : aliasPair.second) { - addAliasedTypeAnnotation(a, aliasPair.first, true); - } - } + } + if (!badTargetValues.isEmpty()) { + String msg = + "The @Target meta-annotation on type qualifier " + + annotationClass.toString() + + " must not contain " + + StringsPlume.conjunction("or", badTargetValues) + + "."; + throw new TypeSystemError(msg); + } + } + } + + /* NO-AFU + * This method is called only when {@code -Ainfer} is passed as an option. It checks if another + * option that should not occur simultaneously with the whole-program inference is also passed + * as argument, and aborts the process if that is the case. For example, the whole-program + * inference process was not designed to work with conservative defaults. + * + *

Subclasses may override this method to add more options. + */ + /* NO-AFU + protected void checkInvalidOptionsInferSignatures() { + // See Issue 683 + // https://github.com/typetools/checker-framework/issues/683 + if (checker.useConservativeDefault("source") + || checker.useConservativeDefault("bytecode")) { + throw new UserError( + "The option -Ainfer=... cannot be used together with conservative defaults."); + } + } + */ + + /** + * Actions that logically belong in the constructor, but need to run after the subclass + * constructor has completed. In particular, {@link AnnotationFileElementTypes#parseStubFiles()} + * may try to do type resolution with this AnnotatedTypeFactory. + */ + protected void postInit( + @UnderInitialization(AnnotatedTypeFactory.class) AnnotatedTypeFactory this) { + this.qualHierarchy = createQualifierHierarchy(); + if (qualHierarchy == null) { + throw new TypeSystemError( + "AnnotatedTypeFactory with null qualifier hierarchy not supported."); + } else if (!qualHierarchy.isValid()) { + throw new TypeSystemError( + "AnnotatedTypeFactory: invalid qualifier hierarchy: %s %s ", + qualHierarchy.getClass(), qualHierarchy); + } + this.typeHierarchy = createTypeHierarchy(); + this.typeVarSubstitutor = createTypeVariableSubstitutor(); + this.typeArgumentInference = createTypeArgumentInference(); + this.viewpointAdapter = createViewpointAdapter(); + this.qualifierUpperBounds = createQualifierUpperBounds(); + + // TODO: is this the best location for declaring this alias? + addAliasedDeclAnnotation( + org.jmlspecs.annotation.Pure.class, + org.checkerframework.dataflow.qual.Pure.class, + AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class)); + + // Accommodate the inability to write @InheritedAnnotation on these annotations. + addInheritedAnnotation( + AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class)); + addInheritedAnnotation( + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.SideEffectFree.class)); + addInheritedAnnotation( + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.Deterministic.class)); + addInheritedAnnotation( + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.TerminatesExecution.class)); + + initializeReflectionResolution(); + + if (this.getClass() == AnnotatedTypeFactory.class) { + this.parseAnnotationFiles(); + } + TypeMirror iterableTypeMirror = + ElementUtils.getTypeElement(processingEnv, Iterable.class).asType(); + this.iterableDeclType = + (AnnotatedDeclaredType) AnnotatedTypeMirror.createType(iterableTypeMirror, this, true); + } + + /** + * Returns the checker associated with this factory. + * + * @return the checker associated with this factory + */ + public BaseTypeChecker getChecker() { + return checker; + } + + /** + * Returns the names of the annotation processors that are being run. + * + * @return the names of the annotation processors that are being run + */ + @SuppressWarnings("JdkObsolete") // ClassLoader.getResources returns an Enumeration + public List getCheckerNames() { + com.sun.tools.javac.util.Context context = + ((JavacProcessingEnvironment) processingEnv).getContext(); + String processorArg = Options.instance(context).get("-processor"); + if (processorArg != null) { + return SystemUtil.COMMA_SPLITTER.splitToList(processorArg); + } + try { + String filename = "META-INF/services/javax.annotation.processing.Processor"; + List result = new ArrayList<>(); + Enumeration urls = getClass().getClassLoader().getResources(filename); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) { + result.addAll(in.lines().collect(Collectors.toList())); } + } + return result; + } catch (IOException e) { + throw new BugInCF(e); + } + } + + /** + * Creates {@link QualifierUpperBounds} for this type factory. + * + * @return a new {@link QualifierUpperBounds} for this type factory + */ + protected QualifierUpperBounds createQualifierUpperBounds() { + return new QualifierUpperBounds(this); + } + + /** + * Return {@link QualifierUpperBounds} for this type factory. + * + * @return {@link QualifierUpperBounds} for this type factory + */ + public QualifierUpperBounds getQualifierUpperBounds() { + return qualifierUpperBounds; + } + + /* NO-AFU + * Returns the WholeProgramInference instance (may be null). + * + * @return the WholeProgramInference instance, or null + */ + /* NO-AFU + public @Nullable WholeProgramInference getWholeProgramInference() { + return wholeProgramInference; + } + */ + + /** Initialize reflection resolution. */ + protected void initializeReflectionResolution() { + if (checker.shouldResolveReflection()) { + boolean debug = "debug".equals(checker.getOption("resolveReflection")); + + MethodValChecker methodValChecker = checker.getSubchecker(MethodValChecker.class); + assert methodValChecker != null + : "AnnotatedTypeFactory: reflection resolution was requested," + + " but MethodValChecker isn't a subchecker."; + MethodValAnnotatedTypeFactory methodValATF = + (MethodValAnnotatedTypeFactory) methodValChecker.getAnnotationProvider(); + + reflectionResolver = new DefaultReflectionResolver(checker, methodValATF, debug); + } + } + + /** + * Get the current CompilationUnitTree. + * + * @return the current compilation unit being used, or null + */ + protected @Nullable CompilationUnitTree getRoot() { + return root; + } + + /** + * Set the CompilationUnitTree that should be used. + * + * @param root the new compilation unit to use + */ + public void setRoot(@Nullable CompilationUnitTree root) { + /* NO-AFU + if (root != null && wholeProgramInference != null) { + for (Tree typeDecl : root.getTypeDecls()) { + if (typeDecl.getKind() == Tree.Kind.CLASS) { + ClassTree classTree = (ClassTree) typeDecl; + wholeProgramInference.preprocessClassTree(classTree); + } + } + } + */ - // Alias provided via -AaliasedDeclAnnos command-line option. - // This can only be used for annotations without attributes, - // e.g. this will not be usable to declare an alias for @EnsuresNonNull(...). - if (checker.hasOption("aliasedDeclAnnos")) { - String aliasesOption = checker.getOption("aliasedDeclAnnos"); - String[] annos = aliasesOption.split(";"); - for (String alias : annos) { - IPair, @FullyQualifiedName String[]> aliasPair = - parseAliasesFromString(alias); - AnnotationMirror anno = AnnotationBuilder.fromClass(elements, aliasPair.first); - for (String a : aliasPair.second) { - addAliasedDeclAnnotation(a, aliasPair.first.getCanonicalName(), anno); - } - } - } + this.root = root; + // Do not clear here. Only the primary checker should clear this cache. + // treePathCache.clear(); + + if (shouldCache) { + // Clear the caches with trees because once the compilation unit changes, + // the trees may be modified and lose type arguments. + elementToTreeCache.clear(); + fromExpressionTreeCache.clear(); + fromMemberTreeCache.clear(); + fromTypeTreeCache.clear(); + classAndMethodTreeCache.clear(); + + // There is no need to clear the following cache, it is limited by cache size and it + // contents won't change between compilation units. + // elementCache.clear(); + } + + if (root != null && checker.hasOption("ajava")) { + // Search for an ajava file with annotations for the current source file and the current + // checker. It will be in a directory specified by the "ajava" option in a subdirectory + // corresponding to this file's package. For example, a file in package a.b would be in + // a subdirectory a/b. The filename is ClassName-checker.qualified.name.ajava. If such a + // file exists, read its detailed annotation data, including annotations on private + // elements. + + String packagePrefix = + root.getPackageName() != null + ? TreeUtils.nameExpressionToString(root.getPackageName()) + "." + : ""; + + // The method getName() returns a path. + String rootFile = root.getSourceFile().getName(); + String className = rootFile; + // Extract the basename. + int lastSeparator = className.lastIndexOf(File.separator); + if (lastSeparator != -1) { + className = className.substring(lastSeparator + 1); + } + // Drop the ".java" extension. + if (className.endsWith(".java")) { + className = className.substring(0, className.length() - ".java".length()); + } - /* NO-AFU - if (checker.hasOption("infer")) { - checkInvalidOptionsInferSignatures(); - String inferArg = checker.getOption("infer"); - // No argument means "jaifs", for (temporary) backwards compatibility. - if (inferArg == null) { - inferArg = "jaifs"; - } - switch (inferArg) { - case "stubs": - wpiOutputFormat = WholeProgramInference.OutputFormat.STUB; - break; - case "jaifs": - wpiOutputFormat = WholeProgramInference.OutputFormat.JAIF; - break; - case "ajava": - wpiOutputFormat = WholeProgramInference.OutputFormat.AJAVA; - break; - default: - throw new UserError( - "Bad argument -Ainfer=" - + inferArg - + " should be one of: -Ainfer=jaifs, -Ainfer=stubs, -Ainfer=ajava"); - } - boolean showWpiFailedInferences = checker.hasOption("showWpiFailedInferences"); - boolean inferOutputOriginal = checker.hasOption("inferOutputOriginal"); - if (inferOutputOriginal && wpiOutputFormat != WholeProgramInference.OutputFormat.AJAVA) { - checker.message( - Diagnostic.Kind.WARNING, - "-AinferOutputOriginal only works with -Ainfer=ajava, so it is being ignored."); - } - if (wpiOutputFormat == WholeProgramInference.OutputFormat.AJAVA) { - wholeProgramInference = - new WholeProgramInferenceImplementation( - this, - new WholeProgramInferenceJavaParserStorage(this, inferOutputOriginal), - showWpiFailedInferences); - } else { - wholeProgramInference = - new WholeProgramInferenceImplementation( - this, new WholeProgramInferenceScenesStorage(this), showWpiFailedInferences); - } - if (!checker.hasOption("warns")) { - // Without -Awarns, the inference output may be incomplete, because javac halts - // after issuing an error. - checker.message(Diagnostic.Kind.ERROR, "Do not supply -Ainfer without -Awarns"); - } + String qualifiedName = packagePrefix + className; + + // If the set candidateAjavaFiles has exactly one element after the loop, a specific + // .ajava file was supplied, with no ambiguity, and can be parsed. For an explanation, + // see the comment below about possible ambiguity. + Set candidateAjavaFiles = new HashSet<>(1); + // All .ajava files for this class + checker combo end in this string. + String ajavaEnding = + qualifiedName.replaceAll("\\.", "/") + + "-" + + checker.getClass().getCanonicalName() + + ".ajava"; + for (String ajavaLocation : checker.getStringsOption("ajava", File.pathSeparator)) { + // ajavaLocation might either be (1) a directory, or (2) the name of a specific + // ajava file. This code must handle both possible cases. + // Case (1): ajavaPath is a directory + String ajavaPath = ajavaLocation + File.separator + ajavaEnding; + File ajavaFileInDir = new File(ajavaPath); + if (ajavaFileInDir.exists()) { + // There is a candidate ajava file in one of the root directories. + candidateAjavaFiles.add(ajavaPath); } else { - wholeProgramInference = null; - } - */ - - ignoreUninferredTypeArguments = !checker.hasOption("conservativeUninferredTypeArguments"); - - objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv); - - this.debugStubParser = checker.hasOption("stubDebug"); - - annotatedForValueElement = - TreeUtils.getMethod(AnnotatedFor.class, "value", 0, processingEnv); - ensuresQualifierExpressionElement = - TreeUtils.getMethod(EnsuresQualifier.class, "expression", 0, processingEnv); - ensuresQualifierListValueElement = - TreeUtils.getMethod(EnsuresQualifier.List.class, "value", 0, processingEnv); - ensuresQualifierIfExpressionElement = - TreeUtils.getMethod(EnsuresQualifierIf.class, "expression", 0, processingEnv); - ensuresQualifierIfResultElement = - TreeUtils.getMethod(EnsuresQualifierIf.class, "result", 0, processingEnv); - ensuresQualifierIfListValueElement = - TreeUtils.getMethod(EnsuresQualifierIf.List.class, "value", 0, processingEnv); - fieldInvariantFieldElement = - TreeUtils.getMethod(FieldInvariant.class, "field", 0, processingEnv); - fieldInvariantQualifierElement = - TreeUtils.getMethod(FieldInvariant.class, "qualifier", 0, processingEnv); - hasQualifierParameterValueElement = - TreeUtils.getMethod(HasQualifierParameter.class, "value", 0, processingEnv); - methodValClassNameElement = - TreeUtils.getMethod(MethodVal.class, "className", 0, processingEnv); - methodValMethodNameElement = - TreeUtils.getMethod(MethodVal.class, "methodName", 0, processingEnv); - methodValParamsElement = TreeUtils.getMethod(MethodVal.class, "params", 0, processingEnv); - noQualifierParameterValueElement = - TreeUtils.getMethod(NoQualifierParameter.class, "value", 0, processingEnv); - requiresQualifierExpressionElement = - TreeUtils.getMethod(RequiresQualifier.class, "expression", 0, processingEnv); - requiresQualifierListValueElement = - TreeUtils.getMethod(RequiresQualifier.List.class, "value", 0, processingEnv); - - requiresQualifierTM = - ElementUtils.getTypeElement(processingEnv, RequiresQualifier.class).asType(); - requiresQualifierListTM = - ElementUtils.getTypeElement(processingEnv, RequiresQualifier.List.class).asType(); - ensuresQualifierTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.class).asType(); - ensuresQualifierListTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.List.class).asType(); - ensuresQualifierIfTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.class).asType(); - ensuresQualifierIfListTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.List.class).asType(); - - mergeStubsWithSource = checker.hasOption("mergeStubsWithSource"); - } - - /** - * Parse a string in the format {@code - * FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2} to a pair of {@code - * (FQN.canonical.Qualifier.class, ["FQN.alias1.Qual1", "FQN.alias2.Qual2"])}. - * - * @param alias in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2 - * @return a pair with the first argument being the canonical qualifier class and the second - * argument being the list of aliases with fully qualified names - */ - // signature is suppressed because there is no way to reason about parsed strings - @SuppressWarnings({"unchecked", "signature"}) - private IPair, @FullyQualifiedName String[]> parseAliasesFromString( - String alias) { - String[] parts = alias.split(":"); - if (parts.length != 2) { - throw new UserError( - String.format( - "Alias argument must be in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2, got %s instead.", - alias)); + // Check case (2): ajavaPath might be a specific .ajava file. The tricky thing + // about this is that the "root" is not known, so the correct .ajava file might + // be ambiguous. Consider the following: there are two ajava files: + // ~/foo/foo/Bar-checker.ajava and ~/baz/foo/Bar-checker.ajava. Which is the + // correct one for class foo.Bar? It depends on whether there is a foo.foo.Bar + // or a baz.foo.Bar elsewhere in the project. For that reason, parsing using a + // specific file is done at the **end** of the loop, and if there is more than + // one match no file is parsed for this class and a warning is issued instead. + // The user can disambiguate by supplying a root directory, instead of specific + // files. + if (ajavaLocation.endsWith(File.separator + ajavaEnding)) { + // This is a candidate ajava file. If it is the only candidate, then it + // might be unambiguous. If not, issue a warning. + candidateAjavaFiles.add(ajavaLocation); + } } - Class canonical; + } + if (candidateAjavaFiles.size() == 1) { + currentFileAjavaTypes = new AnnotationFileElementTypes(this); + String ajavaPath = candidateAjavaFiles.toArray(new String[candidateAjavaFiles.size()])[0]; try { - canonical = (Class) Class.forName(parts[0].trim()); - } catch (ClassNotFoundException | ClassCastException ex) { - throw new UserError( - String.format("The name %s is an invalid annotation name.", parts[0])); - } - String[] aliases = parts[1].trim().split("\\s*,\\s*"); - return IPair.of(canonical, aliases); - } - - /** - * Requires that supportedQuals is non-null and non-empty and each element is a type qualifier. - * That is, no element has a {@code @Target} meta-annotation that contains something besides - * TYPE_USE or TYPE_PARAMETER. (@Target({}) is allowed.) @ - * - * @throws BugInCF If supportedQuals is empty or contaions a non-type qualifier - */ - private void checkSupportedQualsAreTypeQuals() { - if (supportedQuals == null || supportedQuals.isEmpty()) { - throw new TypeSystemError("Found no supported qualifiers."); - } - for (Class annotationClass : supportedQuals) { - // Check @Target values - ElementType[] targetValues = annotationClass.getAnnotation(Target.class).value(); - List badTargetValues = new ArrayList<>(0); - for (ElementType element : targetValues) { - if (!(element == ElementType.TYPE_USE || element == ElementType.TYPE_PARAMETER)) { - // if there's an ElementType with an enumerated value of something other - // than TYPE_USE or TYPE_PARAMETER then it isn't a valid qualifier - badTargetValues.add(element); - } - } - if (!badTargetValues.isEmpty()) { - String msg = - "The @Target meta-annotation on type qualifier " - + annotationClass.toString() - + " must not contain " - + StringsPlume.conjunction("or", badTargetValues) - + "."; - throw new TypeSystemError(msg); - } + currentFileAjavaTypes.parseAjavaFileWithTree(ajavaPath, root); + } catch (Throwable e) { + throw new Error( + "Problem while parsing " + ajavaPath + " that corresponds to " + rootFile, e); } + } else if (candidateAjavaFiles.size() > 1) { + checker.reportWarning(root, "ambiguous.ajava", String.join(", ", candidateAjavaFiles)); + } + } else { + currentFileAjavaTypes = null; + } + } + + @SideEffectFree + @Override + public String toString() { + return getClass().getSimpleName() + "#" + uid; + } + + /** + * Returns the {@link QualifierHierarchy} to be used by this checker. + * + *

The implementation builds the type qualifier hierarchy for the {@link + * #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current + * implementation returns an instance of {@code NoElementQualifierHierarchy}. + * + *

Subclasses must override this method if their qualifiers have elements; the method must + * return an implementation of {@link QualifierHierarchy}, such as {@link + * ElementQualifierHierarchy}. + * + * @return a QualifierHierarchy for this type system + */ + protected QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy( + this.getSupportedTypeQualifiers(), + elements, + (GenericAnnotatedTypeFactory) this); + } + + /** + * Returns the type qualifier hierarchy graph to be used by this processor. + * + * @see #createQualifierHierarchy() + * @return the {@link QualifierHierarchy} for this checker + */ + public final QualifierHierarchy getQualifierHierarchy() { + return qualHierarchy; + } + + /** + * Creates the type hierarchy to be used by this factory. + * + *

Subclasses may override this method to specify new type-checking rules beyond the typical + * Java subtyping rules. + * + * @return the type relations class to check type subtyping + */ + protected TypeHierarchy createTypeHierarchy() { + return new DefaultTypeHierarchy( + checker, + getQualifierHierarchy(), + checker.getBooleanOption("ignoreRawTypeArguments", true), + checker.hasOption("invariantArrays")); + } + + public final TypeHierarchy getTypeHierarchy() { + return typeHierarchy; + } + + /** + * Factory method to create a ViewpointAdapter. Subclasses should implement and instantiate a + * ViewpointAdapter subclass if viewpoint adaptation is needed for a type system. + * + * @return viewpoint adapter to perform viewpoint adaptation or null + */ + protected @Nullable ViewpointAdapter createViewpointAdapter() { + return null; + } + + /** TypeVariableSubstitutor provides a method to replace type parameters with their arguments. */ + protected TypeVariableSubstitutor createTypeVariableSubstitutor() { + return new TypeVariableSubstitutor(); + } + + public TypeVariableSubstitutor getTypeVarSubstitutor() { + return typeVarSubstitutor; + } + + /** + * TypeArgumentInference infers the method type arguments when they are not explicitly written. + */ + protected TypeArgumentInference createTypeArgumentInference() { + return new DefaultTypeArgumentInference(this); + } + + public TypeArgumentInference getTypeArgumentInference() { + return typeArgumentInference; + } + + /** + * Factory method to easily change what {@link AnnotationClassLoader} is created to load type + * annotation classes. Subclasses can override this method and return a custom + * AnnotationClassLoader subclass to customize loading logic. + */ + protected AnnotationClassLoader createAnnotationClassLoader() { + return new AnnotationClassLoader(checker); + } + + /** + * Returns a mutable set of annotation classes that are supported by a checker. + * + *

Subclasses may override this method to return a mutable set of their supported type + * qualifiers through one of the 5 approaches shown below. + * + *

Subclasses should not call this method; they should call {@link #getSupportedTypeQualifiers} + * instead. + * + *

By default, a checker supports all annotations located in a subdirectory called {@literal + * qual} that's located in the same directory as the checker. Note that only annotations defined + * with the {@code @Target({ElementType.TYPE_USE})} meta-annotation (and optionally with the + * additional value of {@code ElementType.TYPE_PARAMETER}, but no other {@code ElementType} + * values) are automatically considered as supported annotations. + * + *

To support a different set of annotations than those in the {@literal qual} subdirectory, or + * that have other {@code ElementType} values, see examples below. + * + *

In total, there are 5 ways to indicate annotations that are supported by a checker: + * + *

    + *
  1. Only support annotations located in a checker's {@literal qual} directory: + *

    This is the default behavior. Simply place those annotations within the {@literal + * qual} directory. + *

  2. Support annotations located in a checker's {@literal qual} directory and a list of other + * annotations: + *

    Place those annotations within the {@literal qual} directory, and override {@link + * #createSupportedTypeQualifiers()} by calling {@link #getBundledTypeQualifiers(Class...)} + * with a varargs parameter list of the other annotations. Code example: + *

    +   * {@code @Override protected Set> createSupportedTypeQualifiers() {
    +   *      return getBundledTypeQualifiers(Regex.class, PartialRegex.class, RegexBottom.class, UnknownRegex.class);
    +   *  } }
    +   * 
    + *
  3. Supporting only annotations that are explicitly listed: Override {@link + * #createSupportedTypeQualifiers()} and return a mutable set of the supported annotations. + * Code example: + *
    +   * {@code @Override protected Set> createSupportedTypeQualifiers() {
    +   *      return new HashSet>(
    +   *              Arrays.asList(A.class, B.class));
    +   *  } }
    +   * 
    + * The set of qualifiers returned by {@link #createSupportedTypeQualifiers()} must be a + * fresh, mutable set. The methods {@link #getBundledTypeQualifiers(Class...)} must return a + * fresh, mutable set + *
+ * + * @return the type qualifiers supported this processor, or an empty set if none + */ + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(); + } + + /** + * Loads all annotations contained in the qual directory of a checker via reflection; if a + * polymorphic type qualifier exists, and an explicit array of annotations to the set of + * annotation classes. + * + *

This method can be called in the overridden versions of {@link + * #createSupportedTypeQualifiers()} in each checker. + * + * @param explicitlyListedAnnotations a varargs array of explicitly listed annotation classes to + * be added to the returned set. For example, it is used frequently to add Bottom qualifiers. + * @return a mutable set of the loaded and listed annotation classes + */ + @SafeVarargs + protected final Set> getBundledTypeQualifiers( + Class... explicitlyListedAnnotations) { + return loadTypeAnnotationsFromQualDir(explicitlyListedAnnotations); + } + + /** + * Instantiates the AnnotationClassLoader and loads all annotations contained in the qual + * directory of a checker via reflection, and has the option to include an explicitly stated list + * of annotations (eg ones found in a different directory than qual). + * + *

The annotations that are automatically loaded must have the {@link + * java.lang.annotation.Target Target} meta-annotation with the value of {@link + * ElementType#TYPE_USE} (and optionally {@link ElementType#TYPE_PARAMETER}). If it has other + * {@link ElementType} values, it won't be loaded. Other annotation classes must be explicitly + * listed even if they are in the same directory as the checker's qual directory. + * + * @param explicitlyListedAnnotations a set of explicitly listed annotation classes to be added to + * the returned set, for example, it is used frequently to add Bottom qualifiers + * @return a set of annotation class instances + */ + @SafeVarargs + @SuppressWarnings("varargs") + private final Set> loadTypeAnnotationsFromQualDir( + Class... explicitlyListedAnnotations) { + if (loader != null) { + loader.close(); + } + loader = createAnnotationClassLoader(); + + Set> annotations = loader.getBundledAnnotationClasses(); + + // add in all explicitly Listed qualifiers + if (explicitlyListedAnnotations != null) { + annotations.addAll(Arrays.asList(explicitlyListedAnnotations)); + } + + return annotations; + } + + /** + * Creates the {@link AnnotatedTypeFormatter} used by this type factory and all {@link + * AnnotatedTypeMirror}s it creates. The {@link AnnotatedTypeFormatter} is used in {@link + * AnnotatedTypeMirror#toString()} and will affect the error messages printed for checkers that + * use this type factory. + * + * @return the {@link AnnotatedTypeFormatter} to pass to all {@link AnnotatedTypeMirror}s + */ + protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { + boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); + return new DefaultAnnotatedTypeFormatter( + printVerboseGenerics, + // -AprintVerboseGenerics implies -AprintAllQualifiers + printVerboseGenerics || checker.hasOption("printAllQualifiers")); + } + + /** + * Return the current {@link AnnotatedTypeFormatter}. + * + * @return the current {@link AnnotatedTypeFormatter} + */ + public AnnotatedTypeFormatter getAnnotatedTypeFormatter() { + return typeFormatter; + } + + /** + * Creates the {@link AnnotationFormatter} used by this type factory. + * + * @return the {@link AnnotationFormatter} used by this type factory + */ + protected AnnotationFormatter createAnnotationFormatter() { + return new DefaultAnnotationFormatter(); + } + + /** + * Return the current {@link AnnotationFormatter}. + * + * @return the current {@link AnnotationFormatter} + */ + public AnnotationFormatter getAnnotationFormatter() { + return annotationFormatter; + } + + /** + * Creates the {@link TypeInformationPresenter} used in {@link #postProcessClassTree(ClassTree)} + * to output type information about the current class. + * + * @return the {@link TypeInformationPresenter} used by this type factory, or null + */ + protected @Nullable TypeInformationPresenter createTypeInformationPresenter() { + // TODO: look into a similar mechanism as for CFG visualization. + if (checker.hasOption("lspTypeInfo")) { + return new LspTypeInformationPresenter(this); + } else { + return null; + } + } + + /** + * Returns an immutable set of the classes corresponding to the type qualifiers supported by this + * checker. + * + *

Subclasses cannot override this method; they should override {@link + * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. + * + * @see #createSupportedTypeQualifiers() + * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are + * supported + */ + public final Set> getSupportedTypeQualifiers() { + if (this.supportedQuals == null) { + supportedQuals = createSupportedTypeQualifiers(); + checkSupportedQualsAreTypeQuals(); + } + return supportedQuals; + } + + /** + * Returns an immutable set of the fully qualified names of the type qualifiers supported by this + * checker. + * + *

Subclasses cannot override this method; they should override {@link + * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. + * + * @see #createSupportedTypeQualifiers() + * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are + * supported + */ + public final Set<@CanonicalName String> getSupportedTypeQualifierNames() { + if (this.supportedQualNames == null) { + supportedQualNames = new HashSet<>(); + for (Class clazz : getSupportedTypeQualifiers()) { + supportedQualNames.add(clazz.getCanonicalName()); + } + supportedQualNames = Collections.unmodifiableSet(supportedQualNames); + } + return supportedQualNames; + } + + // ********************************************************************** + // Factories for annotated types that account for default qualifiers + // ********************************************************************** + + /** + * Returns the size for LRU caches. It is either the value supplied via the {@code -AatfCacheSize} + * option or the default cache size. + * + * @return cache size passed as argument to checker or DEFAULT_CACHE_SIZE + */ + protected int getCacheSize() { + String option = checker.getOption("atfCacheSize"); + if (option == null) { + return DEFAULT_CACHE_SIZE; + } + try { + return Integer.valueOf(option); + } catch (NumberFormatException ex) { + throw new UserError("atfCacheSize was not an integer: " + option); + } + } + + /** + * Returns an AnnotatedTypeMirror representing the annotated type of {@code elt}. + * + * @param elt the element + * @return the annotated type of {@code elt} + */ + public AnnotatedTypeMirror getAnnotatedType(Element elt) { + if (elt == null) { + throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null element"); + } + // Annotations explicitly written in the source code, + // or obtained from bytecode. + AnnotatedTypeMirror type = fromElement(elt); + addComputedTypeAnnotations(elt, type); + return type; + } + + /** + * Returns an AnnotatedTypeMirror representing the annotated type of {@code clazz}. + * + * @param clazz a class + * @return the annotated type of {@code clazz} + */ + public AnnotatedTypeMirror getAnnotatedType(Class clazz) { + return getAnnotatedType(elements.getTypeElement(clazz.getCanonicalName())); + } + + @Override + public @Nullable AnnotationMirror getAnnotationMirror( + Tree tree, Class target) { + if (isSupportedQualifier(target)) { + AnnotatedTypeMirror atm = getAnnotatedType(tree); + return atm.getAnnotation(target); + } + return null; + } + + /** + * Returns an AnnotatedTypeMirror representing the annotated type of {@code tree}. + * + * @param tree the AST node + * @return the annotated type of {@code tree} + */ + public AnnotatedTypeMirror getAnnotatedType(Tree tree) { + if (tree == null) { + throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null tree"); + } + if (shouldCache && classAndMethodTreeCache.containsKey(tree)) { + return classAndMethodTreeCache.get(tree).deepCopy(); + } + + AnnotatedTypeMirror type; + if (TreeUtils.isClassTree(tree)) { + type = fromClass((ClassTree) tree); + } else if (tree.getKind() == Tree.Kind.METHOD || tree.getKind() == Tree.Kind.VARIABLE) { + type = fromMember(tree); + } else if (TreeUtils.isExpressionTree(tree)) { + tree = TreeUtils.withoutParens((ExpressionTree) tree); + type = fromExpression((ExpressionTree) tree); + } else { + throw new BugInCF( + "AnnotatedTypeFactory.getAnnotatedType: query of annotated type for tree " + + tree.getKind()); + } + + addComputedTypeAnnotations(tree, type); + + if (shouldCache && (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD)) { + // Don't cache VARIABLE + classAndMethodTreeCache.put(tree, type.deepCopy()); + } else { + // No caching otherwise + } + + return type; + } + + /** + * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} before the classTree is type + * checked. + * + * @param classTree the class on which to perform preprocessing + */ + public void preProcessClassTree(ClassTree classTree) {} + + /** + * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} after the ClassTree has been type + * checked. + * + *

The default implementation uses this to store the defaulted AnnotatedTypeMirrors and + * inherited declaration annotations back into the corresponding Elements. Subclasses might want + * to override this method if storing defaulted types is not desirable. + */ + public void postProcessClassTree(ClassTree tree) { + TypesIntoElements.store(processingEnv, this, tree); + DeclarationsIntoElements.store(processingEnv, this, tree); + + if (typeInformationPresenter != null) { + typeInformationPresenter.process(tree, getPath(tree)); } /* NO-AFU - * This method is called only when {@code -Ainfer} is passed as an option. It checks if another - * option that should not occur simultaneously with the whole-program inference is also passed - * as argument, and aborts the process if that is the case. For example, the whole-program - * inference process was not designed to work with conservative defaults. - * - *

Subclasses may override this method to add more options. - */ - /* NO-AFU - protected void checkInvalidOptionsInferSignatures() { - // See Issue 683 - // https://github.com/typetools/checker-framework/issues/683 - if (checker.useConservativeDefault("source") - || checker.useConservativeDefault("bytecode")) { - throw new UserError( - "The option -Ainfer=... cannot be used together with conservative defaults."); - } - } + if (wholeProgramInference != null) { + // Write out the results of whole-program inference, just once for each class. As soon + // as any class is finished processing, all modified scenes are written to files, in + // case this was the last class to be processed. Post-processing of subsequent classes + // might result in re-writing some of the scenes if new information has been written to + // them. + wholeProgramInference.writeResultsToFile(wpiOutputFormat, this.checker); + } */ - - /** - * Actions that logically belong in the constructor, but need to run after the subclass - * constructor has completed. In particular, {@link AnnotationFileElementTypes#parseStubFiles()} - * may try to do type resolution with this AnnotatedTypeFactory. - */ - protected void postInit( - @UnderInitialization(AnnotatedTypeFactory.class) AnnotatedTypeFactory this) { - this.qualHierarchy = createQualifierHierarchy(); - if (qualHierarchy == null) { - throw new TypeSystemError( - "AnnotatedTypeFactory with null qualifier hierarchy not supported."); - } else if (!qualHierarchy.isValid()) { - throw new TypeSystemError( - "AnnotatedTypeFactory: invalid qualifier hierarchy: %s %s ", - qualHierarchy.getClass(), qualHierarchy); - } - this.typeHierarchy = createTypeHierarchy(); - this.typeVarSubstitutor = createTypeVariableSubstitutor(); - this.typeArgumentInference = createTypeArgumentInference(); - this.viewpointAdapter = createViewpointAdapter(); - this.qualifierUpperBounds = createQualifierUpperBounds(); - - // TODO: is this the best location for declaring this alias? - addAliasedDeclAnnotation( - org.jmlspecs.annotation.Pure.class, - org.checkerframework.dataflow.qual.Pure.class, - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.Pure.class)); - - // Accommodate the inability to write @InheritedAnnotation on these annotations. - addInheritedAnnotation( - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.Pure.class)); - addInheritedAnnotation( - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.SideEffectFree.class)); - addInheritedAnnotation( - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.Deterministic.class)); - addInheritedAnnotation( - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.TerminatesExecution.class)); - - initializeReflectionResolution(); - - if (this.getClass() == AnnotatedTypeFactory.class) { - this.parseAnnotationFiles(); - } - TypeMirror iterableTypeMirror = - ElementUtils.getTypeElement(processingEnv, Iterable.class).asType(); - this.iterableDeclType = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType(iterableTypeMirror, this, true); - } - - /** - * Returns the checker associated with this factory. - * - * @return the checker associated with this factory - */ - public BaseTypeChecker getChecker() { - return checker; - } - - /** - * Returns the names of the annotation processors that are being run. - * - * @return the names of the annotation processors that are being run - */ - @SuppressWarnings("JdkObsolete") // ClassLoader.getResources returns an Enumeration - public List getCheckerNames() { - com.sun.tools.javac.util.Context context = - ((JavacProcessingEnvironment) processingEnv).getContext(); - String processorArg = Options.instance(context).get("-processor"); - if (processorArg != null) { - return SystemUtil.COMMA_SPLITTER.splitToList(processorArg); - } - try { - String filename = "META-INF/services/javax.annotation.processing.Processor"; - List result = new ArrayList<>(); - Enumeration urls = getClass().getClassLoader().getResources(filename); - while (urls.hasMoreElements()) { - URL url = urls.nextElement(); - try (BufferedReader in = - new BufferedReader(new InputStreamReader(url.openStream()))) { - result.addAll(in.lines().collect(Collectors.toList())); - } - } - return result; - } catch (IOException e) { - throw new BugInCF(e); - } - } - - /** - * Creates {@link QualifierUpperBounds} for this type factory. - * - * @return a new {@link QualifierUpperBounds} for this type factory - */ - protected QualifierUpperBounds createQualifierUpperBounds() { - return new QualifierUpperBounds(this); - } - - /** - * Return {@link QualifierUpperBounds} for this type factory. - * - * @return {@link QualifierUpperBounds} for this type factory - */ - public QualifierUpperBounds getQualifierUpperBounds() { - return qualifierUpperBounds; - } - - /* NO-AFU - * Returns the WholeProgramInference instance (may be null). - * - * @return the WholeProgramInference instance, or null - */ - /* NO-AFU - public @Nullable WholeProgramInference getWholeProgramInference() { - return wholeProgramInference; - } - */ - - /** Initialize reflection resolution. */ - protected void initializeReflectionResolution() { - if (checker.shouldResolveReflection()) { - boolean debug = "debug".equals(checker.getOption("resolveReflection")); - - MethodValChecker methodValChecker = checker.getSubchecker(MethodValChecker.class); - assert methodValChecker != null - : "AnnotatedTypeFactory: reflection resolution was requested," - + " but MethodValChecker isn't a subchecker."; - MethodValAnnotatedTypeFactory methodValATF = - (MethodValAnnotatedTypeFactory) methodValChecker.getAnnotationProvider(); - - reflectionResolver = new DefaultReflectionResolver(checker, methodValATF, debug); - } - } - - /** - * Get the current CompilationUnitTree. - * - * @return the current compilation unit being used, or null - */ - protected @Nullable CompilationUnitTree getRoot() { - return root; - } - - /** - * Set the CompilationUnitTree that should be used. - * - * @param root the new compilation unit to use - */ - public void setRoot(@Nullable CompilationUnitTree root) { - /* NO-AFU - if (root != null && wholeProgramInference != null) { - for (Tree typeDecl : root.getTypeDecls()) { - if (typeDecl.getKind() == Tree.Kind.CLASS) { - ClassTree classTree = (ClassTree) typeDecl; - wholeProgramInference.preprocessClassTree(classTree); - } - } - } - */ - - this.root = root; - // Do not clear here. Only the primary checker should clear this cache. - // treePathCache.clear(); - - if (shouldCache) { - // Clear the caches with trees because once the compilation unit changes, - // the trees may be modified and lose type arguments. - elementToTreeCache.clear(); - fromExpressionTreeCache.clear(); - fromMemberTreeCache.clear(); - fromTypeTreeCache.clear(); - classAndMethodTreeCache.clear(); - - // There is no need to clear the following cache, it is limited by cache size and it - // contents won't change between compilation units. - // elementCache.clear(); - } - - if (root != null && checker.hasOption("ajava")) { - // Search for an ajava file with annotations for the current source file and the current - // checker. It will be in a directory specified by the "ajava" option in a subdirectory - // corresponding to this file's package. For example, a file in package a.b would be in - // a subdirectory a/b. The filename is ClassName-checker.qualified.name.ajava. If such a - // file exists, read its detailed annotation data, including annotations on private - // elements. - - String packagePrefix = - root.getPackageName() != null - ? TreeUtils.nameExpressionToString(root.getPackageName()) + "." - : ""; - - // The method getName() returns a path. - String rootFile = root.getSourceFile().getName(); - String className = rootFile; - // Extract the basename. - int lastSeparator = className.lastIndexOf(File.separator); - if (lastSeparator != -1) { - className = className.substring(lastSeparator + 1); - } - // Drop the ".java" extension. - if (className.endsWith(".java")) { - className = className.substring(0, className.length() - ".java".length()); - } - - String qualifiedName = packagePrefix + className; - - // If the set candidateAjavaFiles has exactly one element after the loop, a specific - // .ajava file was supplied, with no ambiguity, and can be parsed. For an explanation, - // see the comment below about possible ambiguity. - Set candidateAjavaFiles = new HashSet<>(1); - // All .ajava files for this class + checker combo end in this string. - String ajavaEnding = - qualifiedName.replaceAll("\\.", "/") - + "-" - + checker.getClass().getCanonicalName() - + ".ajava"; - for (String ajavaLocation : checker.getStringsOption("ajava", File.pathSeparator)) { - // ajavaLocation might either be (1) a directory, or (2) the name of a specific - // ajava file. This code must handle both possible cases. - // Case (1): ajavaPath is a directory - String ajavaPath = ajavaLocation + File.separator + ajavaEnding; - File ajavaFileInDir = new File(ajavaPath); - if (ajavaFileInDir.exists()) { - // There is a candidate ajava file in one of the root directories. - candidateAjavaFiles.add(ajavaPath); - } else { - // Check case (2): ajavaPath might be a specific .ajava file. The tricky thing - // about this is that the "root" is not known, so the correct .ajava file might - // be ambiguous. Consider the following: there are two ajava files: - // ~/foo/foo/Bar-checker.ajava and ~/baz/foo/Bar-checker.ajava. Which is the - // correct one for class foo.Bar? It depends on whether there is a foo.foo.Bar - // or a baz.foo.Bar elsewhere in the project. For that reason, parsing using a - // specific file is done at the **end** of the loop, and if there is more than - // one match no file is parsed for this class and a warning is issued instead. - // The user can disambiguate by supplying a root directory, instead of specific - // files. - if (ajavaLocation.endsWith(File.separator + ajavaEnding)) { - // This is a candidate ajava file. If it is the only candidate, then it - // might be unambiguous. If not, issue a warning. - candidateAjavaFiles.add(ajavaLocation); - } - } - } - if (candidateAjavaFiles.size() == 1) { - currentFileAjavaTypes = new AnnotationFileElementTypes(this); - String ajavaPath = - candidateAjavaFiles.toArray(new String[candidateAjavaFiles.size()])[0]; - try { - currentFileAjavaTypes.parseAjavaFileWithTree(ajavaPath, root); - } catch (Throwable e) { - throw new Error( - "Problem while parsing " - + ajavaPath - + " that corresponds to " - + rootFile, - e); - } - } else if (candidateAjavaFiles.size() > 1) { - checker.reportWarning( - root, "ambiguous.ajava", String.join(", ", candidateAjavaFiles)); - } - } else { - currentFileAjavaTypes = null; - } - } - - @SideEffectFree - @Override - public String toString() { - return getClass().getSimpleName() + "#" + uid; - } - - /** - * Returns the {@link QualifierHierarchy} to be used by this checker. - * - *

The implementation builds the type qualifier hierarchy for the {@link - * #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current - * implementation returns an instance of {@code NoElementQualifierHierarchy}. - * - *

Subclasses must override this method if their qualifiers have elements; the method must - * return an implementation of {@link QualifierHierarchy}, such as {@link - * ElementQualifierHierarchy}. - * - * @return a QualifierHierarchy for this type system - */ - protected QualifierHierarchy createQualifierHierarchy() { - return new NoElementQualifierHierarchy( - this.getSupportedTypeQualifiers(), - elements, - (GenericAnnotatedTypeFactory) this); - } - - /** - * Returns the type qualifier hierarchy graph to be used by this processor. - * - * @see #createQualifierHierarchy() - * @return the {@link QualifierHierarchy} for this checker - */ - public final QualifierHierarchy getQualifierHierarchy() { - return qualHierarchy; - } - - /** - * Creates the type hierarchy to be used by this factory. - * - *

Subclasses may override this method to specify new type-checking rules beyond the typical - * Java subtyping rules. - * - * @return the type relations class to check type subtyping - */ - protected TypeHierarchy createTypeHierarchy() { - return new DefaultTypeHierarchy( - checker, - getQualifierHierarchy(), - checker.getBooleanOption("ignoreRawTypeArguments", true), - checker.hasOption("invariantArrays")); - } - - public final TypeHierarchy getTypeHierarchy() { - return typeHierarchy; - } - - /** - * Factory method to create a ViewpointAdapter. Subclasses should implement and instantiate a - * ViewpointAdapter subclass if viewpoint adaptation is needed for a type system. - * - * @return viewpoint adapter to perform viewpoint adaptation or null - */ - protected @Nullable ViewpointAdapter createViewpointAdapter() { - return null; - } - - /** - * TypeVariableSubstitutor provides a method to replace type parameters with their arguments. - */ - protected TypeVariableSubstitutor createTypeVariableSubstitutor() { - return new TypeVariableSubstitutor(); - } - - public TypeVariableSubstitutor getTypeVarSubstitutor() { - return typeVarSubstitutor; - } - - /** - * TypeArgumentInference infers the method type arguments when they are not explicitly written. - */ - protected TypeArgumentInference createTypeArgumentInference() { - return new DefaultTypeArgumentInference(this); - } - - public TypeArgumentInference getTypeArgumentInference() { - return typeArgumentInference; - } - - /** - * Factory method to easily change what {@link AnnotationClassLoader} is created to load type - * annotation classes. Subclasses can override this method and return a custom - * AnnotationClassLoader subclass to customize loading logic. - */ - protected AnnotationClassLoader createAnnotationClassLoader() { - return new AnnotationClassLoader(checker); - } - - /** - * Returns a mutable set of annotation classes that are supported by a checker. - * - *

Subclasses may override this method to return a mutable set of their supported type - * qualifiers through one of the 5 approaches shown below. - * - *

Subclasses should not call this method; they should call {@link - * #getSupportedTypeQualifiers} instead. - * - *

By default, a checker supports all annotations located in a subdirectory called {@literal - * qual} that's located in the same directory as the checker. Note that only annotations defined - * with the {@code @Target({ElementType.TYPE_USE})} meta-annotation (and optionally with the - * additional value of {@code ElementType.TYPE_PARAMETER}, but no other {@code ElementType} - * values) are automatically considered as supported annotations. - * - *

To support a different set of annotations than those in the {@literal qual} subdirectory, - * or that have other {@code ElementType} values, see examples below. - * - *

In total, there are 5 ways to indicate annotations that are supported by a checker: - * - *

    - *
  1. Only support annotations located in a checker's {@literal qual} directory: - *

    This is the default behavior. Simply place those annotations within the {@literal - * qual} directory. - *

  2. Support annotations located in a checker's {@literal qual} directory and a list of - * other annotations: - *

    Place those annotations within the {@literal qual} directory, and override {@link - * #createSupportedTypeQualifiers()} by calling {@link - * #getBundledTypeQualifiers(Class...)} with a varargs parameter list of the other - * annotations. Code example: - *

    -     * {@code @Override protected Set> createSupportedTypeQualifiers() {
    -     *      return getBundledTypeQualifiers(Regex.class, PartialRegex.class, RegexBottom.class, UnknownRegex.class);
    -     *  } }
    -     * 
    - *
  3. Supporting only annotations that are explicitly listed: Override {@link - * #createSupportedTypeQualifiers()} and return a mutable set of the supported - * annotations. Code example: - *
    -     * {@code @Override protected Set> createSupportedTypeQualifiers() {
    -     *      return new HashSet>(
    -     *              Arrays.asList(A.class, B.class));
    -     *  } }
    -     * 
    - * The set of qualifiers returned by {@link #createSupportedTypeQualifiers()} must be a - * fresh, mutable set. The methods {@link #getBundledTypeQualifiers(Class...)} must return - * a fresh, mutable set - *
- * - * @return the type qualifiers supported this processor, or an empty set if none - */ - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(); - } - - /** - * Loads all annotations contained in the qual directory of a checker via reflection; if a - * polymorphic type qualifier exists, and an explicit array of annotations to the set of - * annotation classes. - * - *

This method can be called in the overridden versions of {@link - * #createSupportedTypeQualifiers()} in each checker. - * - * @param explicitlyListedAnnotations a varargs array of explicitly listed annotation classes to - * be added to the returned set. For example, it is used frequently to add Bottom - * qualifiers. - * @return a mutable set of the loaded and listed annotation classes - */ - @SafeVarargs - protected final Set> getBundledTypeQualifiers( - Class... explicitlyListedAnnotations) { - return loadTypeAnnotationsFromQualDir(explicitlyListedAnnotations); - } - - /** - * Instantiates the AnnotationClassLoader and loads all annotations contained in the qual - * directory of a checker via reflection, and has the option to include an explicitly stated - * list of annotations (eg ones found in a different directory than qual). - * - *

The annotations that are automatically loaded must have the {@link - * java.lang.annotation.Target Target} meta-annotation with the value of {@link - * ElementType#TYPE_USE} (and optionally {@link ElementType#TYPE_PARAMETER}). If it has other - * {@link ElementType} values, it won't be loaded. Other annotation classes must be explicitly - * listed even if they are in the same directory as the checker's qual directory. - * - * @param explicitlyListedAnnotations a set of explicitly listed annotation classes to be added - * to the returned set, for example, it is used frequently to add Bottom qualifiers - * @return a set of annotation class instances - */ - @SafeVarargs - @SuppressWarnings("varargs") - private final Set> loadTypeAnnotationsFromQualDir( - Class... explicitlyListedAnnotations) { - if (loader != null) { - loader.close(); - } - loader = createAnnotationClassLoader(); - - Set> annotations = loader.getBundledAnnotationClasses(); - - // add in all explicitly Listed qualifiers - if (explicitlyListedAnnotations != null) { - annotations.addAll(Arrays.asList(explicitlyListedAnnotations)); - } - - return annotations; - } - - /** - * Creates the {@link AnnotatedTypeFormatter} used by this type factory and all {@link - * AnnotatedTypeMirror}s it creates. The {@link AnnotatedTypeFormatter} is used in {@link - * AnnotatedTypeMirror#toString()} and will affect the error messages printed for checkers that - * use this type factory. - * - * @return the {@link AnnotatedTypeFormatter} to pass to all {@link AnnotatedTypeMirror}s - */ - protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { - boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); - return new DefaultAnnotatedTypeFormatter( - printVerboseGenerics, - // -AprintVerboseGenerics implies -AprintAllQualifiers - printVerboseGenerics || checker.hasOption("printAllQualifiers")); - } - - /** - * Return the current {@link AnnotatedTypeFormatter}. - * - * @return the current {@link AnnotatedTypeFormatter} - */ - public AnnotatedTypeFormatter getAnnotatedTypeFormatter() { - return typeFormatter; - } - - /** - * Creates the {@link AnnotationFormatter} used by this type factory. - * - * @return the {@link AnnotationFormatter} used by this type factory - */ - protected AnnotationFormatter createAnnotationFormatter() { - return new DefaultAnnotationFormatter(); - } - - /** - * Return the current {@link AnnotationFormatter}. - * - * @return the current {@link AnnotationFormatter} - */ - public AnnotationFormatter getAnnotationFormatter() { - return annotationFormatter; - } - - /** - * Creates the {@link TypeInformationPresenter} used in {@link #postProcessClassTree(ClassTree)} - * to output type information about the current class. - * - * @return the {@link TypeInformationPresenter} used by this type factory, or null - */ - protected @Nullable TypeInformationPresenter createTypeInformationPresenter() { - // TODO: look into a similar mechanism as for CFG visualization. - if (checker.hasOption("lspTypeInfo")) { - return new LspTypeInformationPresenter(this); - } else { - return null; - } - } - - /** - * Returns an immutable set of the classes corresponding to the type qualifiers supported by - * this checker. - * - *

Subclasses cannot override this method; they should override {@link - * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. - * - * @see #createSupportedTypeQualifiers() - * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers - * are supported - */ - public final Set> getSupportedTypeQualifiers() { - if (this.supportedQuals == null) { - supportedQuals = createSupportedTypeQualifiers(); - checkSupportedQualsAreTypeQuals(); - } - return supportedQuals; - } - - /** - * Returns an immutable set of the fully qualified names of the type qualifiers supported by - * this checker. - * - *

Subclasses cannot override this method; they should override {@link - * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. - * - * @see #createSupportedTypeQualifiers() - * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers - * are supported - */ - public final Set<@CanonicalName String> getSupportedTypeQualifierNames() { - if (this.supportedQualNames == null) { - supportedQualNames = new HashSet<>(); - for (Class clazz : getSupportedTypeQualifiers()) { - supportedQualNames.add(clazz.getCanonicalName()); - } - supportedQualNames = Collections.unmodifiableSet(supportedQualNames); - } - return supportedQualNames; - } - - // ********************************************************************** - // Factories for annotated types that account for default qualifiers - // ********************************************************************** - - /** - * Returns the size for LRU caches. It is either the value supplied via the {@code - * -AatfCacheSize} option or the default cache size. - * - * @return cache size passed as argument to checker or DEFAULT_CACHE_SIZE - */ - protected int getCacheSize() { - String option = checker.getOption("atfCacheSize"); - if (option == null) { - return DEFAULT_CACHE_SIZE; - } - try { - return Integer.valueOf(option); - } catch (NumberFormatException ex) { - throw new UserError("atfCacheSize was not an integer: " + option); - } - } - - /** - * Returns an AnnotatedTypeMirror representing the annotated type of {@code elt}. - * - * @param elt the element - * @return the annotated type of {@code elt} - */ - public AnnotatedTypeMirror getAnnotatedType(Element elt) { - if (elt == null) { - throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null element"); - } - // Annotations explicitly written in the source code, - // or obtained from bytecode. - AnnotatedTypeMirror type = fromElement(elt); - addComputedTypeAnnotations(elt, type); - return type; - } - - /** - * Returns an AnnotatedTypeMirror representing the annotated type of {@code clazz}. - * - * @param clazz a class - * @return the annotated type of {@code clazz} - */ - public AnnotatedTypeMirror getAnnotatedType(Class clazz) { - return getAnnotatedType(elements.getTypeElement(clazz.getCanonicalName())); - } - - @Override - public @Nullable AnnotationMirror getAnnotationMirror( - Tree tree, Class target) { - if (isSupportedQualifier(target)) { - AnnotatedTypeMirror atm = getAnnotatedType(tree); - return atm.getAnnotation(target); - } - return null; - } - - /** - * Returns an AnnotatedTypeMirror representing the annotated type of {@code tree}. - * - * @param tree the AST node - * @return the annotated type of {@code tree} - */ - public AnnotatedTypeMirror getAnnotatedType(Tree tree) { - if (tree == null) { - throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null tree"); - } - if (shouldCache && classAndMethodTreeCache.containsKey(tree)) { - return classAndMethodTreeCache.get(tree).deepCopy(); - } - - AnnotatedTypeMirror type; - if (TreeUtils.isClassTree(tree)) { - type = fromClass((ClassTree) tree); - } else if (tree.getKind() == Tree.Kind.METHOD || tree.getKind() == Tree.Kind.VARIABLE) { - type = fromMember(tree); - } else if (TreeUtils.isExpressionTree(tree)) { - tree = TreeUtils.withoutParens((ExpressionTree) tree); - type = fromExpression((ExpressionTree) tree); - } else { - throw new BugInCF( - "AnnotatedTypeFactory.getAnnotatedType: query of annotated type for tree " - + tree.getKind()); - } - - addComputedTypeAnnotations(tree, type); - - if (shouldCache && (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD)) { - // Don't cache VARIABLE - classAndMethodTreeCache.put(tree, type.deepCopy()); - } else { - // No caching otherwise - } - - return type; - } - - /** - * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} before the classTree is type - * checked. - * - * @param classTree the class on which to perform preprocessing - */ - public void preProcessClassTree(ClassTree classTree) {} - - /** - * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} after the ClassTree has been - * type checked. - * - *

The default implementation uses this to store the defaulted AnnotatedTypeMirrors and - * inherited declaration annotations back into the corresponding Elements. Subclasses might want - * to override this method if storing defaulted types is not desirable. - */ - public void postProcessClassTree(ClassTree tree) { - TypesIntoElements.store(processingEnv, this, tree); - DeclarationsIntoElements.store(processingEnv, this, tree); - - if (typeInformationPresenter != null) { - typeInformationPresenter.process(tree, getPath(tree)); - } - - /* NO-AFU - if (wholeProgramInference != null) { - // Write out the results of whole-program inference, just once for each class. As soon - // as any class is finished processing, all modified scenes are written to files, in - // case this was the last class to be processed. Post-processing of subsequent classes - // might result in re-writing some of the scenes if new information has been written to - // them. - wholeProgramInference.writeResultsToFile(wpiOutputFormat, this.checker); - } - */ - } - - /** - * Determines the annotated type from a type in tree form. - * - *

Note that we cannot decide from a Tree whether it is a type use or an expression. - * TreeUtils.isTypeTree is only an under-approximation. For example, an identifier can be either - * a type or an expression. - * - * @param tree the type tree - * @return the annotated type of the type in the AST - */ - public AnnotatedTypeMirror getAnnotatedTypeFromTypeTree(Tree tree) { - if (tree == null) { - throw new BugInCF("AnnotatedTypeFactory.getAnnotatedTypeFromTypeTree: null tree"); - } - AnnotatedTypeMirror type = fromTypeTree(tree); - addComputedTypeAnnotations(tree, type); - return type; - } - - /** - * Returns the set of qualifiers that are the upper bounds for a use of the type. - * - *

For a specific type system, the type declaration bound is retrieved in the following - * precedence: (1) the annotation on the type declaration bound (2) if an annotation with - * {@code @UpperBoundFor} mentions the type or the type kind, use that annotation (3) the top - * annotation - * - * @param type a type whose upper bounds to obtain - * @return the set of qualifiers that are the upper bounds for a use of the type - */ - public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { - return qualifierUpperBounds.getBoundQualifiers(type); - } - - /** - * Compare the given {@code annos} with the declaration bounds of {@code type} and return the - * appropriate qualifiers. For each qualifier in {@code annos}, if it is a subtype of the - * declaration bound in the same hierarchy, it will be added to the result; otherwise, the - * declaration bound will be added to the result instead. - * - * @param type java type that specifies the qualifier upper bound - * @param annos a set of qualifiers to be compared with the declaration bounds of {@code type} - * @return the modified {@code annos} after applying the rules described above - */ - public AnnotationMirrorSet getAnnotationOrTypeDeclarationBound( - TypeMirror type, Set annos) { - AnnotationMirrorSet boundAnnos = getTypeDeclarationBounds(type); - AnnotationMirrorSet results = new AnnotationMirrorSet(); - - for (AnnotationMirror anno : annos) { - AnnotationMirror boundAnno = - qualHierarchy.findAnnotationInSameHierarchy(boundAnnos, anno); - assert boundAnno != null; - - if (!qualHierarchy.isSubtypeQualifiersOnly(anno, boundAnno)) { - results.add(boundAnno); - } else { - results.add(anno); - } - } - return results; - } - - /** - * Returns the set of qualifiers that are the upper bound for a type use if no other bound is - * specified for the type. - * - *

This implementation returns the top qualifiers by default. Subclass may override to return - * different qualifiers. - * - * @return the set of qualifiers that are the upper bound for a type use if no other bound is - * specified for the type - */ - protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { - return qualHierarchy.getTopAnnotations(); - } - - /** - * Returns the type of the extends or implements clause. - * - *

The primary qualifier is either an explicit annotation on {@code clause}, or it is the - * qualifier upper bounds for uses of the type of the clause. - * - * @param clause tree that represents an extends or implements clause - * @return the type of the extends or implements clause - */ - public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { - AnnotatedTypeMirror fromTypeTree = fromTypeTree(clause); - AnnotationMirrorSet bound = getTypeDeclarationBounds(fromTypeTree.getUnderlyingType()); - fromTypeTree.addMissingAnnotations(bound); - addComputedTypeAnnotations(clause, fromTypeTree); - return fromTypeTree; - } - - // ********************************************************************** - // Factories for annotated types that do not account for default qualifiers. - // They only include qualifiers explicitly inserted by the user. - // ********************************************************************** - - /** - * Creates an AnnotatedTypeMirror for {@code elt} that includes: annotations explicitly written - * on the element and annotations from stub files. - * - *

Does not include default qualifiers. To obtain them, use {@link - * #getAnnotatedType(Element)}. - * - *

Does not include fake overrides from the stub file. - * - * @param elt the element - * @return AnnotatedTypeMirror of the element with explicitly-written and stub file annotations - */ - public AnnotatedTypeMirror fromElement(Element elt) { - if (shouldCache && elementCache.containsKey(elt)) { - return elementCache.get(elt).deepCopy(); - } - if (elt.getKind() == ElementKind.PACKAGE) { - return toAnnotatedType(elt.asType(), false); - } - AnnotatedTypeMirror type; - - // Because of a bug in Java 8, annotations on type parameters are not stored in elements, so - // get explicit annotations from the tree. (This bug has been fixed in Java 9.) Also, since - // annotations computed by the AnnotatedTypeFactory are stored in the element, the - // annotations have to be retrieved from the tree so that only explicit annotations are - // returned. - Tree decl = declarationFromElement(elt); - - if (decl == null) { - type = stubTypes.getAnnotatedTypeMirror(elt); - if (type == null) { - type = toAnnotatedType(elt.asType(), ElementUtils.isTypeDeclaration(elt)); - ElementAnnotationApplier.apply(type, elt, this); - } - } else if (decl instanceof ClassTree) { - type = fromClass((ClassTree) decl); - } else if (decl instanceof VariableTree) { - type = fromMember(decl); - } else if (decl instanceof MethodTree) { - type = fromMember(decl); - } else if (decl.getKind() == Tree.Kind.TYPE_PARAMETER) { - type = fromTypeTree(decl); - } else { - throw new BugInCF( - "AnnotatedTypeFactory.fromElement: cannot be here. decl: " - + decl.getKind() - + " elt: " - + elt); - } - - type = mergeAnnotationFileAnnosIntoType(type, elt, ajavaTypes); - if (currentFileAjavaTypes != null) { - type = mergeAnnotationFileAnnosIntoType(type, elt, currentFileAjavaTypes); - } - - if (mergeStubsWithSource) { - if (debugStubParser) { - System.out.printf("fromElement: mergeStubsIntoType(%s, %s)", type, elt); - } - type = mergeAnnotationFileAnnosIntoType(type, elt, stubTypes); - if (debugStubParser) { - System.out.printf(" => %s%n", type); - } - } - // Caching is disabled if annotation files are being parsed, because calls to this - // method before the annotation files are fully read can return incorrect results. - if (shouldCache - && !stubTypes.isParsing() - && !ajavaTypes.isParsing() - && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { - elementCache.put(elt, type.deepCopy()); - } - return type; - } - - /** - * Returns an AnnotatedDeclaredType with explicit annotations from the ClassTree {@code tree}. - * - * @param tree the class declaration - * @return AnnotatedDeclaredType with explicit annotations from {@code tree} - */ - private AnnotatedDeclaredType fromClass(ClassTree tree) { - return TypeFromTree.fromClassTree(this, tree); - } - - /** - * Creates an AnnotatedTypeMirror for a variable or method declaration tree. The - * AnnotatedTypeMirror contains annotations explicitly written on the tree, and possibly others - * as described below. - * - *

If a VariableTree is a parameter to a lambda, this method also adds annotations from the - * declared type of the functional interface and the executable type of its method. - * - *

The returned AnnotatedTypeMirror also contains explicitly written annotations from any - * ajava file and if {@code -AmergeStubsWithSource} is passed, it also merges any explicitly - * written annotations from stub files. - * - * @param tree a {@link MethodTree} or {@link VariableTree} - * @return AnnotatedTypeMirror with explicit annotations from {@code tree} - */ - private AnnotatedTypeMirror fromMember(Tree tree) { - if (!(tree instanceof MethodTree || tree instanceof VariableTree)) { - throw new BugInCF( - "AnnotatedTypeFactory.fromMember: not a method or variable declaration: " - + tree); - } - if (shouldCache && fromMemberTreeCache.containsKey(tree)) { - return fromMemberTreeCache.get(tree).deepCopy(); - } - AnnotatedTypeMirror result = TypeFromTree.fromMember(this, tree); - - result = mergeAnnotationFileAnnosIntoType(result, tree, ajavaTypes); - if (currentFileAjavaTypes != null) { - result = mergeAnnotationFileAnnosIntoType(result, tree, currentFileAjavaTypes); - } - - if (mergeStubsWithSource) { - if (debugStubParser) { - System.out.printf("fromClass: mergeStubsIntoType(%s, %s)", result, tree); - } - result = mergeAnnotationFileAnnosIntoType(result, tree, stubTypes); - if (debugStubParser) { - System.out.printf(" => %s%n", result); - } - } - - if (shouldCache) { - fromMemberTreeCache.put(tree, result.deepCopy()); - } - - return result; - } - - /** - * Merges types from annotation files for {@code tree} into {@code type} by taking the greatest - * lower bound of the annotations in both. - * - * @param type the type to apply annotation file types to - * @param tree the tree from which to read annotation file types - * @param source storage for current annotation file annotations - * @return the given type, side-effected to add the annotation file types - */ - private AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( - @Nullable AnnotatedTypeMirror type, Tree tree, AnnotationFileElementTypes source) { - Element elt = TreeUtils.elementFromTree(tree); - return mergeAnnotationFileAnnosIntoType(type, elt, source); - } - - /** - * A scanner used to combine annotations from two AnnotatedTypeMirrors. The scanner requires - * {@link #qualHierarchy}, which is set in {@link #postInit()} rather than the construtor, so - * lazily initialize this field before use. - */ - private @MonotonicNonNull AnnotatedTypeCombiner annotatedTypeCombiner = null; - - /** - * Merges types from annotation files for {@code elt} into {@code type} by taking the greatest - * lower bound of the annotations in both. - * - * @param type the type to apply annotation file types to - * @param elt the element from which to read annotation file types - * @param source storage for current annotation file annotations - * @return the type, side-effected to add the annotation file types - */ - protected AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( - @Nullable AnnotatedTypeMirror type, Element elt, AnnotationFileElementTypes source) { - AnnotatedTypeMirror typeFromFile = source.getAnnotatedTypeMirror(elt); - if (typeFromFile == null) { - return type; - } - if (type == null) { - return typeFromFile; - } - if (annotatedTypeCombiner == null) { - annotatedTypeCombiner = new AnnotatedTypeCombiner(qualHierarchy); - } - // Must merge (rather than only take the annotation file type if it is a subtype) to support - // WPI. - annotatedTypeCombiner.visit(typeFromFile, type); - return type; - } - - /** - * Creates an AnnotatedTypeMirror for an ExpressionTree. The AnnotatedTypeMirror contains - * explicit annotations written on the expression and for some expressions, annotations from - * sub-expressions that could have been explicitly written, defaulted, refined, or otherwise - * computed. (Expression whose type include annotations from sub-expressions are: - * ArrayAccessTree, ConditionalExpressionTree, IdentifierTree, MemberSelectTree, and - * MethodInvocationTree.) - * - *

For example, the AnnotatedTypeMirror returned for an array access expression is the fully - * annotated type of the array component of the array being accessed. - * - * @param tree an expression - * @return AnnotatedTypeMirror of the expressions either fully-annotated or partially annotated - * depending on the kind of expression - * @see TypeFromExpressionVisitor - */ - private AnnotatedTypeMirror fromExpression(ExpressionTree tree) { - if (shouldCache && fromExpressionTreeCache.containsKey(tree)) { - return fromExpressionTreeCache.get(tree).deepCopy(); - } - - AnnotatedTypeMirror result = TypeFromTree.fromExpression(this, tree); - - if (shouldCache - && tree.getKind() != Tree.Kind.NEW_CLASS - && tree.getKind() != Tree.Kind.NEW_ARRAY - && tree.getKind() != Tree.Kind.CONDITIONAL_EXPRESSION) { - // Don't cache the type of some expressions, because incorrect annotations would be - // cached during dataflow analysis. See Issue #602. - fromExpressionTreeCache.put(tree, result.deepCopy()); - } - return result; - } - - /** - * Creates an AnnotatedTypeMirror for the tree. The AnnotatedTypeMirror contains annotations - * explicitly written on the tree. It also adds type arguments to raw types that include - * annotations from the element declaration of the type {@link #fromElement(Element)}. - * - *

Called on the following trees: AnnotatedTypeTree, ArrayTypeTree, ParameterizedTypeTree, - * PrimitiveTypeTree, TypeParameterTree, WildcardTree, UnionType, IntersectionTypeTree, and - * IdentifierTree, MemberSelectTree. - * - * @param tree the type tree - * @return the (partially) annotated type of the type in the AST - */ - /*package-private*/ final AnnotatedTypeMirror fromTypeTree(Tree tree) { - if (shouldCache && fromTypeTreeCache.containsKey(tree)) { - return fromTypeTreeCache.get(tree).deepCopy(); - } - - AnnotatedTypeMirror result = TypeFromTree.fromTypeTree(this, tree); - - if (shouldCache) { - fromTypeTreeCache.put(tree, result.deepCopy()); - } - return result; - } - - // ********************************************************************** - // Customization methods meant to be overridden by subclasses to include - // defaulted annotations - // ********************************************************************** - - /** - * Changes annotations on a type obtained from a {@link Tree}. By default, this method does - * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting and inference - * (flow-sensitive type refinement). Its subclasses usually override it only to customize - * default annotations. - * - *

Subclasses that override this method should also override {@link - * #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)}. - * - * @param tree an AST node - * @param type the type obtained from {@code tree} - */ - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - // Pass. - } - - /** - * Changes annotations on a type obtained from an {@link Element}. By default, this method does - * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting. - * - *

Subclasses that override this method should also override {@link - * #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. - * - * @param elt an element - * @param type the type obtained from {@code elt} - */ - protected void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - // Pass. - } - - /** - * Adds default annotations to {@code type}. This method should only be used in places where the - * correct annotations cannot be computed because of uninferred type arguments. (See {@link - * AnnotatedWildcardType#isUninferredTypeArgument()}.) - * - * @param type annotated type to which default annotations are added - */ - public void addDefaultAnnotations(AnnotatedTypeMirror type) { - // Pass. - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize directSupertypes(). - * Overriding methods should merely change the annotations on the supertypes, without adding or - * removing new types. - * - *

The default provided implementation adds {@code type} annotations to {@code supertypes}. - * This allows the {@code type} and its supertypes to have the qualifiers. - * - * @param type the type whose supertypes are desired - * @param supertypes the supertypes as specified by the base AnnotatedTypeFactory - */ - protected void postDirectSuperTypes( - AnnotatedTypeMirror type, List supertypes) { - // Use the effective annotations here to get the correct annotations - // for type variables and wildcards. - AnnotationMirrorSet annotations = type.getEffectiveAnnotations(); - for (AnnotatedTypeMirror supertype : supertypes) { - if (!annotations.equals(supertype.getEffectiveAnnotations())) { - supertype.clearAnnotations(); - // TODO: is this correct for type variables and wildcards? - supertype.addAnnotations(annotations); - } - } - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize - * AnnotatedTypes.asMemberOf(). Overriding methods should merely change the annotations on the - * subtypes, without changing the types. - * - * @param type the annotated type of the element - * @param owner the annotated type of the receiver of the accessing tree - * @param element the element of the field or method - */ - public void postAsMemberOf( - AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { - if (element.getKind() == ElementKind.FIELD) { - addAnnotationFromFieldInvariant(type, owner, (VariableElement) element); - } - addComputedTypeAnnotations(element, type); - - if (viewpointAdapter != null && type.getKind() != TypeKind.EXECUTABLE) { - viewpointAdapter.viewpointAdaptMember(owner, element, type); - } - } - - /** - * Adds the qualifier specified by a field invariant for {@code field} to {@code type}. - * - * @param type annotated type to which the annotation is added - * @param accessedVia the annotated type of the receiver of the accessing tree. (Only used to - * get the type element of the underling type.) - * @param field element representing the field - */ - protected void addAnnotationFromFieldInvariant( - AnnotatedTypeMirror type, AnnotatedTypeMirror accessedVia, VariableElement field) { - TypeMirror declaringType = accessedVia.getUnderlyingType(); - // Find the first upper bound that isn't a wildcard or type variable - while (declaringType.getKind() == TypeKind.WILDCARD - || declaringType.getKind() == TypeKind.TYPEVAR) { - if (declaringType.getKind() == TypeKind.WILDCARD) { - declaringType = TypesUtils.wildUpperBound(declaringType, processingEnv); - } else if (declaringType.getKind() == TypeKind.TYPEVAR) { - declaringType = ((TypeVariable) declaringType).getUpperBound(); - } - } - TypeElement typeElement = TypesUtils.getTypeElement(declaringType); - if (ElementUtils.enclosingTypeElement(field).equals(typeElement)) { - // If the field is declared in the accessedVia class, then the field in the invariant - // cannot be this field, even if the field has the same name. - return; - } - - FieldInvariants invariants = getFieldInvariants(typeElement); - if (invariants == null) { - return; - } - List invariantAnnos = invariants.getQualifiersFor(field.getSimpleName()); - type.replaceAnnotations(invariantAnnos); - } - - /** - * Returns the field invariants for the given class, as expressed by the user in {@link - * FieldInvariant @FieldInvariant} method annotations. - * - *

Subclasses may implement their own field invariant annotations if {@link - * FieldInvariant @FieldInvariant} is not expressive enough. They must override this method to - * properly create AnnotationMirror and also override {@link - * #getFieldInvariantDeclarationAnnotations()} to return their field invariants. - * - * @param element class for which to get invariants - * @return field invariants for {@code element} - */ - public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { - if (element == null) { - return null; - } - AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, FieldInvariant.class); - if (fieldInvarAnno == null) { - return null; - } - List fields = - AnnotationUtils.getElementValueArray( - fieldInvarAnno, fieldInvariantFieldElement, String.class); - List<@CanonicalName Name> classes = - AnnotationUtils.getElementValueClassNames( - fieldInvarAnno, fieldInvariantQualifierElement); - List qualifiers = - CollectionsPlume.mapList( - name -> - // Calling AnnotationBuilder.fromName (which ignores - // elements/fields) is acceptable because @FieldInvariant - // does not handle classes with elements/fields. - AnnotationBuilder.fromName(elements, name), - classes); - if (qualifiers.size() == 1) { - while (fields.size() > qualifiers.size()) { - qualifiers.add(qualifiers.get(0)); - } - } - if (fields.size() != qualifiers.size()) { - // The user wrote a malformed @FieldInvariant annotation, so just return a malformed - // FieldInvariants object. The BaseTypeVisitor will issue an error. - return new FieldInvariants(fields, qualifiers, this); - } - - // Only keep qualifiers that are supported by this checker. (The other qualifiers cannot - // be checked by this checker, so they must be ignored.) - List annotatedFields = new ArrayList<>(); - List supportedQualifiers = new ArrayList<>(); - for (int i = 0; i < fields.size(); i++) { - if (isSupportedQualifier(qualifiers.get(i))) { - annotatedFields.add(fields.get(i)); - supportedQualifiers.add(qualifiers.get(i)); - } - } - if (annotatedFields.isEmpty()) { - return null; - } - - return new FieldInvariants(annotatedFields, supportedQualifiers, this); - } - - /** - * Returns the element of {@code annoTrees} that is a use of one of the field invariant - * annotations (as specified via {@link #getFieldInvariantDeclarationAnnotations()}. If one - * isn't found, null is returned. - * - * @param annoTrees list of trees to search; the result is one of the list elements, or null - * @return the AnnotationTree that is a use of one of the field invariant annotations, or null - * if one isn't found - */ - public @Nullable AnnotationTree getFieldInvariantAnnotationTree( - @Nullable List annoTrees) { - List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); - for (int i = 0; i < annos.size(); i++) { - for (Class clazz : getFieldInvariantDeclarationAnnotations()) { - if (areSameByClass(annos.get(i), clazz)) { - return annoTrees.get(i); - } - } - } - return null; - } - - /** The classes of field invariant annotations. */ - private final Set> fieldInvariantDeclarationAnnotations = - Collections.singleton(FieldInvariant.class); - - /** - * Returns the set of classes of field invariant annotations. - * - * @return the set of classes of field invariant annotations - */ - protected Set> getFieldInvariantDeclarationAnnotations() { - return fieldInvariantDeclarationAnnotations; - } - - /** - * Adapt the upper bounds of the type variables of a class relative to the type instantiation. - * In some type systems, the upper bounds depend on the instantiation of the class. For example, - * in the Generic Universe Type system, consider a class declaration - * - *

{@code   class C }
- * - * then the instantiation - * - *
{@code   @Rep C<@Rep Object> }
- * - * is legal. The upper bounds of class C have to be adapted by the main modifier. - * - *

An example of an adaptation follows. Suppose, I have a declaration: - * - *

{@code  class MyClass>}
- * - * And an instantiation: - * - *
{@code  new MyClass<@NonNull String>()}
- * - *

The upper bound of E adapted to the argument String, would be {@code List<@NonNull - * String>} and the lower bound would be an AnnotatedNullType. - * - *

TODO: ensure that this method is consistently used instead of directly querying the type - * variables. - * - * @param type the use of the type - * @param element the corresponding element - * @return the adapted bounds of the type parameters - */ - public List typeVariablesFromUse( - AnnotatedDeclaredType type, TypeElement element) { - AnnotatedDeclaredType generic = getAnnotatedType(element); - List targs = type.getTypeArguments(); - List tvars = generic.getTypeArguments(); - - assert targs.size() == tvars.size() - : "Mismatch in type argument size between " + type + " and " + generic; - - // System.err.printf("TVFU%n type: %s%n generic: %s%n", type, generic); - - Map typeParamToTypeArg = new HashMap<>(); - - AnnotatedDeclaredType enclosing = type; - while (enclosing != null) { - List enclosingTArgs = enclosing.getTypeArguments(); - AnnotatedDeclaredType declaredType = - getAnnotatedType((TypeElement) enclosing.getUnderlyingType().asElement()); - List enclosingTVars = declaredType.getTypeArguments(); - for (int i = 0; i < enclosingTArgs.size(); i++) { - AnnotatedTypeVariable enclosingTVar = (AnnotatedTypeVariable) enclosingTVars.get(i); - typeParamToTypeArg.put(enclosingTVar.getUnderlyingType(), enclosingTArgs.get(i)); - } - enclosing = enclosing.getEnclosingType(); - } - - List res = new ArrayList<>(tvars.size()); - - for (AnnotatedTypeMirror atm : tvars) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) atm; - AnnotatedTypeMirror upper = - typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getUpperBound()); - AnnotatedTypeMirror lower = - typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getLowerBound()); - res.add(new AnnotatedTypeParameterBounds(upper, lower)); - } - - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptTypeParameterBounds(type, res); - } - return res; - } - - /** - * Creates and returns an AnnotatedNullType qualified with {@code annotations}. - * - * @param annotations set of AnnotationMirrors to qualify the returned type with - * @return AnnotatedNullType qualified with {@code annotations} - */ - public AnnotatedNullType getAnnotatedNullType(Set annotations) { - AnnotatedTypeMirror.AnnotatedNullType nullType = - (AnnotatedNullType) - toAnnotatedType(processingEnv.getTypeUtils().getNullType(), false); - nullType.addAnnotations(annotations); - return nullType; - } - - // ********************************************************************** - // Utilities method for getting specific types from trees or elements - // ********************************************************************** - - /** - * Return the implicit receiver type of an expression tree. - * - *

The result is null for expressions that don't have a receiver, e.g. for a local variable - * or method parameter access. The result is also null for expressions that have an explicit - * receiver. - * - *

Clients should generally call {@link #getReceiverType}. - * - * @param tree the expression that might have an implicit receiver - * @return the type of the implicit receiver. Returns null if the expression has an explicit - * receiver or doesn't have a receiver. - */ - protected @Nullable AnnotatedDeclaredType getImplicitReceiverType(ExpressionTree tree) { - assert (tree.getKind() == Tree.Kind.IDENTIFIER - || tree.getKind() == Tree.Kind.MEMBER_SELECT - || tree.getKind() == Tree.Kind.METHOD_INVOCATION - || tree.getKind() == Tree.Kind.NEW_CLASS) - : "Unexpected tree kind: " + tree.getKind(); - - // Return null if the element kind has no receiver. - Element element = TreeUtils.elementFromUse(tree); - assert element != null : "Unexpected null element for tree: " + tree; - if (!ElementUtils.hasReceiver(element)) { - return null; - } - - // Return null if the receiver is explicit. - if (TreeUtils.getReceiverTree(tree) != null) { - return null; - } - - TypeElement elementOfImplicitReceiver = ElementUtils.enclosingTypeElement(element); - if (tree.getKind() == Tree.Kind.NEW_CLASS) { - if (elementOfImplicitReceiver.getEnclosingElement() != null) { - elementOfImplicitReceiver = - ElementUtils.enclosingTypeElement( - elementOfImplicitReceiver.getEnclosingElement()); - } else { - elementOfImplicitReceiver = null; - } - if (elementOfImplicitReceiver == null) { - // If the typeElt does not have an enclosing class, then the NewClassTree - // does not have an implicit receiver. - return null; - } - } - - TypeMirror typeOfImplicitReceiver = elementOfImplicitReceiver.asType(); - AnnotatedDeclaredType thisType = getSelfType(tree); - if (thisType == null) { - return null; - } - // An implicit receiver is the first enclosing type that is a subtype of the type where the - // element is declared. - while (thisType != null - && !isSubtype(thisType.getUnderlyingType(), typeOfImplicitReceiver)) { - thisType = thisType.getEnclosingType(); - } - return thisType; - } - - /** - * Returns the type of {@code this} at the location of {@code tree}. Returns {@code null} if - * {@code tree} is in a location where {@code this} has no meaning, such as the body of a static - * method. - * - *

The parameter is an arbitrary tree and does not have to mention "this", neither explicitly - * nor implicitly. This method can be overridden for type-system specific behavior. - * - * @param tree location used to decide the type of {@code this} - * @return the type of {@code this} at the location of {@code tree} - */ - public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { - if (TreeUtils.isClassTree(tree)) { - return getAnnotatedType(TreeUtils.elementFromDeclaration((ClassTree) tree)); - } - - Tree enclosingTree = getEnclosingClassOrMethod(tree); - if (enclosingTree == null) { - // tree is inside an annotation, where "this" is not allowed. So, no self type exists. - return null; - } else if (enclosingTree.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = (MethodTree) enclosingTree; - if (TreeUtils.isConstructor(enclosingMethod)) { - return (AnnotatedDeclaredType) getAnnotatedType(enclosingMethod).getReturnType(); - } else { - return getAnnotatedType(enclosingMethod).getReceiverType(); - } - } else if (TreeUtils.isClassTree(enclosingTree)) { - return (AnnotatedDeclaredType) getAnnotatedType(enclosingTree); - } - return null; - } - - /** A set containing class, method, and annotation tree kinds. */ - private static final Set classMethodAnnotationKinds = - EnumSet.copyOf(TreeUtils.classTreeKinds()); - - static { - classMethodAnnotationKinds.add(Tree.Kind.METHOD); - classMethodAnnotationKinds.add(Tree.Kind.TYPE_ANNOTATION); - classMethodAnnotationKinds.add(Tree.Kind.ANNOTATION); - } - - /** - * Returns the innermost enclosing method or class tree of {@code tree}. Since artificial trees - * are assigned to be the child node of the original tree, their enclosing trees are found the - * same way as normal trees. - * - *

If the tree is inside an annotation, then {@code null} is returned. - * - * @param tree tree to whose innermost enclosing method or class to return - * @return the innermost enclosing method or class tree of {@code tree}, or {@code null} if - * {@code tree} is inside an annotation - */ - public @Nullable Tree getEnclosingClassOrMethod(Tree tree) { - TreePath path = getPath(tree); - Tree enclosing = TreePathUtil.enclosingOfKind(path, classMethodAnnotationKinds); - if (enclosing != null) { - if (enclosing.getKind() == Tree.Kind.ANNOTATION - || enclosing.getKind() == Tree.Kind.TYPE_ANNOTATION) { - return null; - } - return enclosing; - } - - return TreePathUtil.enclosingClass(path); - } - - /** - * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} - * that is the same type as {@code typeElement}. - * - * @param typeElement type of the enclosing type to return - * @param tree location to use - * @return the enclosing type at the location of {@code tree} that is the same type as {@code - * typeElement} - */ - public AnnotatedDeclaredType getEnclosingType(TypeElement typeElement, Tree tree) { - AnnotatedDeclaredType thisType = getSelfType(tree); - while (!isSameType(thisType.getUnderlyingType(), typeElement.asType())) { - thisType = thisType.getEnclosingType(); - } - return thisType; - } - - /** - * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} - * that is a subtype of {@code typeElement}. - * - * @param typeElement super type of the enclosing type to return - * @param tree location to use - * @return the enclosing type at the location of {@code tree} that is a subtype of {@code - * typeElement} - */ - public AnnotatedDeclaredType getEnclosingSubType(TypeElement typeElement, Tree tree) { - AnnotatedDeclaredType thisType = getSelfType(tree); - while (!isSubtype(thisType.getUnderlyingType(), typeElement.asType())) { - thisType = thisType.getEnclosingType(); - } - return thisType; - } - - /** - * Returns true if the erasure of {@code type1} is a Java subtype of the erasure of {@code - * type2}. - * - * @param type1 a type - * @param type2 a type - * @return true if the erasure of {@code type1} is a Java subtype of the erasure of {@code - * type2} - */ - private boolean isSubtype(TypeMirror type1, TypeMirror type2) { - return types.isSubtype(types.erasure(type1), types.erasure(type2)); - } - - /** - * Returns true if the erasure of {@code type1} is the same Java type as the erasure of {@code - * type2}. - * - * @param type1 a type - * @param type2 a type - * @return true if the erasure of {@code type1} is the same Java type as the erasure of {@code - * type2} - */ - private boolean isSameType(TypeMirror type1, TypeMirror type2) { - return types.isSameType(types.erasure(type1), types.erasure(type2)); - } - - /** - * Returns the receiver type of the expression tree, which might be the type of an implicit - * {@code this}. Returns null if the expression has no explicit or implicit receiver. - * - * @param expression the expression for which to determine the receiver type - * @return the type of the receiver of expression - */ - public final @Nullable AnnotatedTypeMirror getReceiverType(ExpressionTree expression) { - AnnotatedTypeMirror receiverType; - ExpressionTree receiver = TreeUtils.getReceiverTree(expression); - if (receiver != null) { - receiverType = getAnnotatedType(receiver); - } else { - Element element = TreeUtils.elementFromTree(expression); - if (element != null && ElementUtils.hasReceiver(element)) { - // The tree references an element that has a receiver, but the tree does not have an - // explicit receiver. So, the tree must have an implicit receiver of "this" or - // "Outer.this". - receiverType = getImplicitReceiverType(expression); - } else { - receiverType = null; - } - } - // In Java versions below 11, consider the following code: - // class Outer { - // class Inner{} - // } - // class Top { - // void test(Outer outer) { - // outer.new Inner(){}; - // } - // } - // the receiverType of outer.new Inner(){} is Top instead of Outer, - // because Java below 11 organizes newClassTree of an anonymous class in a different - // way: there is a synthetic argument representing the enclosing expression type. - // In such case, use the synthetic argument as its receiver type. - if ((expression instanceof NewClassTree) - && TreeUtils.hasSyntheticArgument((NewClassTree) expression)) { - receiverType = getAnnotatedType(((NewClassTree) expression).getArguments().get(0)); - } - return receiverType; - } - - /** The type for an instantiated generic method or constructor. */ - public static class ParameterizedExecutableType { - /** The method's/constructor's type. */ - public final AnnotatedExecutableType executableType; - - /** The types of the generic type arguments. */ - public final List typeArgs; - - /** Create a ParameterizedExecutableType. */ - public ParameterizedExecutableType( - AnnotatedExecutableType executableType, List typeArgs) { - this.executableType = executableType; - this.typeArgs = typeArgs; - } - - @Override - public String toString() { - if (typeArgs.isEmpty()) { - return executableType.toString(); - } else { - StringJoiner typeArgsString = new StringJoiner(",", "<", ">"); - for (AnnotatedTypeMirror atm : typeArgs) { - typeArgsString.add(atm.toString()); - } - return typeArgsString + " " + executableType.toString(); - } - } - } - - /** - * Determines the type of the invoked method based on the passed method invocation tree. - * - *

The returned method type has all type variables resolved, whether based on receiver type, - * passed type parameters if any, and method invocation parameter. - * - *

Subclasses may override this method to customize inference of types or qualifiers based on - * method invocation parameters. - * - *

As an implementation detail, this method depends on {@link - * AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and - * customization based on receiver type should be in accordance to its specification. - * - *

The return type is a pair of the type of the invoked method and the (inferred) type - * arguments. Note that neither the explicitly passed nor the inferred type arguments are - * guaranteed to be subtypes of the corresponding upper bounds. See method {@link - * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of - * type argument well-formedness. - * - *

Note that "this" and "super" constructor invocations are also handled by this method - * (explicit or implicit ones, at the beginning of a constructor). Method {@link - * #constructorFromUse(NewClassTree)} is only used for a constructor invocation in a "new" - * expression. - * - * @param tree the method invocation tree - * @return the method type being invoked with tree and the (inferred) type arguments - */ - public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - AnnotatedTypeMirror receiverType = getReceiverType(tree); - if (receiverType == null - && (TreeUtils.isSuperConstructorCall(tree) - || TreeUtils.isThisConstructorCall(tree))) { - // super() and this() calls don't have a receiver, but they should be view-point adapted - // as if "this" is the receiver. - receiverType = getSelfType(tree); - } - if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) { - receiverType = applyCaptureConversion(receiverType); - } - - ParameterizedExecutableType result = methodFromUse(tree, methodElt, receiverType); - if (checker.shouldResolveReflection() - && reflectionResolver.isReflectiveMethodInvocation(tree)) { - result = reflectionResolver.resolveReflectiveCall(this, tree, result); - } - - AnnotatedExecutableType method = result.executableType; - if (method.getReturnType().getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) method.getReturnType()).isUninferredTypeArgument()) { - // Get the correct Java type from the tree and use it as the upper bound of the - // wildcard. - TypeMirror tm = TreeUtils.typeOf(tree); - AnnotatedTypeMirror t = toAnnotatedType(tm, false); - - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) method.getReturnType(); - if (ignoreUninferredTypeArguments) { - // Remove the annotations so that default annotations are used instead. - // (See call to addDefaultAnnotations below.) - t.clearAnnotations(); - } else { - t.replaceAnnotations(wildcard.getExtendsBound().getAnnotations()); - } - wildcard.setExtendsBound(t); - addDefaultAnnotations(wildcard); - } - - // Store varargType before calling setParameterTypes, otherwise we may lose the varargType - // as it is the last element of the original parameterTypes. - method.computeVarargType(); - // Adapt parameters, which makes parameters and arguments be the same size for later - // checking. - List parameters = - AnnotatedTypes.adaptParameters(this, method, tree.getArguments(), null); - method.setParameterTypes(parameters); - return result; - } - - /** - * Determines the type of the invoked method based on the passed expression tree, executable - * element, and receiver type. - * - * @param tree either a MethodInvocationTree or a MemberReferenceTree - * @param methodElt the element of the referenced method - * @param receiverType the type of the receiver - * @return the method type being invoked with tree and the (inferred) type arguments - * @see #methodFromUse(MethodInvocationTree) - */ - public ParameterizedExecutableType methodFromUse( - ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - AnnotatedExecutableType memberTypeWithoutOverrides = - getAnnotatedType(methodElt); // get unsubstituted type - AnnotatedExecutableType memberTypeWithOverrides = - applyFakeOverrides(receiverType, methodElt, memberTypeWithoutOverrides); - memberTypeWithOverrides = applyRecordTypesToAccessors(methodElt, memberTypeWithOverrides); - methodFromUsePreSubstitution(tree, memberTypeWithOverrides); - - // Perform viewpoint adaption before type argument substitution. - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptMethod(receiverType, methodElt, memberTypeWithOverrides); - } - - AnnotatedExecutableType methodType = - AnnotatedTypes.asMemberOf( - types, this, receiverType, methodElt, memberTypeWithOverrides); - List typeargs = new ArrayList<>(methodType.getTypeVariables().size()); - - Map typeParamToTypeArg = - AnnotatedTypes.findTypeArguments(processingEnv, this, tree, methodElt, methodType); - - if (!typeParamToTypeArg.isEmpty()) { - typeParamToTypeArg = - captureMethodTypeArgs( - typeParamToTypeArg, memberTypeWithOverrides.getTypeVariables()); - for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { - if (typeParamToTypeArg.get(tv.getUnderlyingType()) == null) { - throw new BugInCF( - "AnnotatedTypeFactory.methodFromUse: mismatch between" - + " declared method type variables and the inferred method type arguments." - + " Method type variables: " - + methodType.getTypeVariables() - + "; " - + "Inferred method type arguments: " - + typeParamToTypeArg); - } - typeargs.add(typeParamToTypeArg.get(tv.getUnderlyingType())); - } - methodType = - (AnnotatedExecutableType) - typeVarSubstitutor.substitute(typeParamToTypeArg, methodType); - } - - if (tree.getKind() == Tree.Kind.METHOD_INVOCATION - && TreeUtils.isMethodInvocation(tree, objectGetClass, processingEnv)) { - adaptGetClassReturnTypeToReceiver(methodType, receiverType, tree); - } - - methodType.setReturnType(applyCaptureConversion(methodType.getReturnType())); - return new ParameterizedExecutableType(methodType, typeargs); - } - - /** - * Apply capture conversion to the type arguments of a method invocation. - * - * @param typeVarToAnnotatedTypeArg mapping from type variable in the method declaration to the - * corresponding (annotated) type argument at the method invocation - * @param declTypeVar list of the (annotated) type variable declarations in the method - * @return a mapping from type variable in the method declaration to its captured type argument. - * Its keys are the same as in {@code typeVarToAnnotatedTypeArg}, and the values are their - * captures (for a non-wildcard, capture conversion is the identity). - */ - // TODO: This should happen as part of Java 8 inference and this method should be removed when - // #979 is fixed. - private Map captureMethodTypeArgs( - Map typeVarToAnnotatedTypeArg, - List declTypeVar) { - Map typeParameter = new HashMap<>(); - for (AnnotatedTypeVariable t : declTypeVar) { - typeParameter.put(t.getUnderlyingType(), t); - } - // `newTypeVarToAnnotatedTypeArg` is the result of this method. - Map newTypeVarToAnnotatedTypeArg = new HashMap<>(); - Map capturedTypeVarToAnnotatedTypeVar = - new HashMap<>(); - - // The first loop replaces each wildcard by a fresh type variable. - for (Map.Entry entry : - typeVarToAnnotatedTypeArg.entrySet()) { - TypeVariable typeVariable = entry.getKey(); - AnnotatedTypeMirror originalTypeArg = entry.getValue(); - if (originalTypeArg.containsUninferredTypeArguments()) { - // Don't capture uninferred type arguments; return the argument. - return typeVarToAnnotatedTypeArg; - } - if (originalTypeArg.getKind() == TypeKind.WILDCARD) { - TypeMirror cap = - TypesUtils.freshTypeVariable( - originalTypeArg.getUnderlyingType(), processingEnv); - AnnotatedTypeMirror capturedArg = AnnotatedTypeMirror.createType(cap, this, false); - newTypeVarToAnnotatedTypeArg.put(typeVariable, capturedArg); - capturedTypeVarToAnnotatedTypeVar.put( - (TypeVariable) cap, (AnnotatedTypeVariable) capturedArg); - } else { - newTypeVarToAnnotatedTypeArg.put(typeVariable, originalTypeArg); - } - } - - // The second loop captures: it side-effects the new type variables. - List order = TypesUtils.order(typeVarToAnnotatedTypeArg.keySet(), types); - for (TypeVariable typeVariable : order) { - AnnotatedTypeMirror originalTypeArg = typeVarToAnnotatedTypeArg.get(typeVariable); - AnnotatedTypeMirror newTypeArg = newTypeVarToAnnotatedTypeArg.get(typeVariable); - if (TypesUtils.isCapturedTypeVariable(newTypeArg.getUnderlyingType()) - && originalTypeArg.getKind() == TypeKind.WILDCARD) { - annotateCapturedTypeVar( - newTypeVarToAnnotatedTypeArg, - capturedTypeVarToAnnotatedTypeVar, - (AnnotatedWildcardType) originalTypeArg, - typeParameter.get(typeVariable), - (AnnotatedTypeVariable) newTypeArg); - } - } - return newTypeVarToAnnotatedTypeArg; - } - - /** - * Given a member and its type, returns the type with fake overrides applied to it. - * - * @param receiverType the type of the class that contains member (or a subtype of it) - * @param member a type member, such as a method or field - * @param memberType the type of {@code member} - * @return {@code memberType}, adjusted according to fake overrides - */ - private AnnotatedExecutableType applyFakeOverrides( - AnnotatedTypeMirror receiverType, Element member, AnnotatedExecutableType memberType) { - // Currently, handle only methods, not fields. TODO: Handle fields. - if (memberType.getKind() != TypeKind.EXECUTABLE) { - return memberType; - } - - AnnotationFileElementTypes afet = stubTypes; - AnnotatedExecutableType methodType = afet.getFakeOverride(member, receiverType); - if (methodType == null) { - methodType = memberType; - } - return methodType; - } - - /** - * Given a method, checks if there is: a record component with the same name AND the record - * component has an annotation AND the method has no-arguments. If so, replaces the annotations - * on the method return type with those from the record type in the same hierarchy. - * - * @param member a method or constructor - * @param memberType the type of the method/constructor; side-effected by this method - * @return {@code memberType} with annotations replaced if applicable - */ - private AnnotatedExecutableType applyRecordTypesToAccessors( - ExecutableElement member, AnnotatedExecutableType memberType) { - if (memberType.getKind() != TypeKind.EXECUTABLE) { - throw new BugInCF( - "member %s has type %s of kind %s", member, memberType, memberType.getKind()); - } - - stubTypes.injectRecordComponentType(types, member, memberType); - - return memberType; - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the - * declared method type before type variable substitution. - * - * @param tree either a method invocation or a member reference tree - * @param type declared method type before type variable substitution - */ - protected void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { - assert tree instanceof MethodInvocationTree || tree instanceof MemberReferenceTree; - } - - /** - * Java special-cases the return type of {@link java.lang.Class#getClass() getClass()}. Though - * the method has a return type of {@code Class}, the compiler special cases this return-type - * and changes the bound of the type argument to the erasure of the receiver type. For example: - * - *

    - *
  • {@code x.getClass()} has the type {@code Class< ? extends erasure_of_x >} - *
  • {@code someInteger.getClass()} has the type {@code Class< ? extends Integer >} - *
- * - * @param getClassType this must be a type representing a call to Object.getClass otherwise a - * runtime exception will be thrown. It is modified by side effect. - * @param receiverType the receiver type of the method invocation (not the declared receiver - * type) - * @param tree getClass method invocation tree - */ - protected void adaptGetClassReturnTypeToReceiver( - AnnotatedExecutableType getClassType, - AnnotatedTypeMirror receiverType, - ExpressionTree tree) { - TypeMirror type = TreeUtils.typeOf(tree); - AnnotatedTypeMirror returnType = AnnotatedTypeMirror.createType(type, this, false); - - if (returnType == null - || !(returnType.getKind() == TypeKind.DECLARED) - || ((AnnotatedDeclaredType) returnType).getTypeArguments().size() != 1) { - throw new BugInCF( - "Unexpected type passed to AnnotatedTypes.adaptGetClassReturnTypeToReceiver%n" - + "getClassType=%s%nreceiverType=%s", - getClassType, receiverType); - } - - AnnotatedWildcardType classWildcardArg = - (AnnotatedWildcardType) - ((AnnotatedDeclaredType) getClassType.getReturnType()) - .getTypeArguments() - .get(0); - getClassType.setReturnType(returnType); - - // Usually, the only locations that will add annotations to the return type are getClass in - // stub files defaults and propagation tree annotator. Since getClass is final they cannot - // come from source code. Also, since the newBound is an erased type we have no type - // arguments. So, we just copy the annotations from the bound of the declared type to the - // new bound. - AnnotationMirrorSet newAnnos = new AnnotationMirrorSet(); - AnnotationMirrorSet receiverTypeBoundAnnos = - getTypeDeclarationBounds(receiverType.getErased().getUnderlyingType()); - AnnotationMirrorSet wildcardBoundAnnos = classWildcardArg.getEffectiveAnnotations(); - for (AnnotationMirror receiverTypeBoundAnno : receiverTypeBoundAnnos) { - AnnotationMirror wildcardAnno = - qualHierarchy.findAnnotationInSameHierarchy( - wildcardBoundAnnos, receiverTypeBoundAnno); - if (typeHierarchy.isSubtypeShallowEffective(receiverTypeBoundAnno, classWildcardArg)) { - newAnnos.add(receiverTypeBoundAnno); - } else { - newAnnos.add(wildcardAnno); - } - } - AnnotatedTypeMirror newTypeArg = - ((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0); - ((AnnotatedTypeVariable) newTypeArg).getUpperBound().replaceAnnotations(newAnnos); - } - - /** - * Return the element type of {@code expression}. This is usually the type of {@code - * expression.itertor().next()}. If {@code expression} is an array, it is the component type of - * the array. - * - * @param expression an expression whose type is an array or implements {@link Iterable} - * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, - * the component type of the array. - */ - public AnnotatedTypeMirror getIterableElementType(ExpressionTree expression) { - return getIterableElementType(expression, getAnnotatedType(expression)); - } - - /** - * Return the element type of {@code iterableType}. This is usually the type of {@code - * expression.itertor().next()}. If {@code expression} is an array, it is the component type of - * the array. - * - * @param expression an expression whose type is an array or implements {@link Iterable} - * @param iterableType the type of the expression - * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, - * the component type of the array. - */ - protected AnnotatedTypeMirror getIterableElementType( - ExpressionTree expression, AnnotatedTypeMirror iterableType) { - switch (iterableType.getKind()) { - case ARRAY: - return ((AnnotatedArrayType) iterableType).getComponentType(); - case WILDCARD: - return getIterableElementType( - expression, - ((AnnotatedWildcardType) iterableType).getExtendsBound().deepCopy()); - case TYPEVAR: - return getIterableElementType( - expression, ((AnnotatedTypeVariable) iterableType).getUpperBound()); - case DECLARED: - AnnotatedDeclaredType dt = - AnnotatedTypes.asSuper(this, iterableType, this.iterableDeclType); - if (dt.getTypeArguments().isEmpty()) { - TypeElement e = ElementUtils.getTypeElement(processingEnv, Object.class); - return getAnnotatedType(e); - } else { - return dt.getTypeArguments().get(0); - } - - // TODO: Properly desugar Iterator.next(), which is needed if an annotated JDK has - // annotations on Iterator#next. - // The below doesn't work because methodFromUse() assumes that the expression tree - // matches the method element. - // TypeElement iteratorElement = - // ElementUtils.getTypeElement(processingEnv, Iterator.class); - // AnnotatedTypeMirror iteratorType = - // AnnotatedTypeMirror.createType(iteratorElement.asType(), this, false); - // Map mapping = new HashMap<>(); - // mapping.put( - // (TypeVariable) iteratorElement.getTypeParameters().get(0).asType(), - // typeArg); - // iteratorType = typeVarSubstitutor.substitute(mapping, iteratorType); - // ExecutableElement next = - // TreeUtils.getMethod("java.util.Iterator", "next", 0, processingEnv); - // ParameterizedExecutableType m = methodFromUse(expression, next, iteratorType); - // return m.executableType.getReturnType(); - default: - throw new BugInCF( - "AnnotatedTypeFactory.getIterableElementType: not iterable type: " - + iterableType); - } - } - - /** - * Determines the type of the invoked constructor based on the passed new class tree. - * - *

The returned method type has all type variables resolved, whether based on receiver type, - * passed type parameters if any, and constructor invocation parameter. - * - *

Subclasses may override this method to customize inference of types or qualifiers based on - * constructor invocation parameters. - * - *

As an implementation detail, this method depends on {@link - * AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and - * customization based on receiver type should be in accordance with its specification. - * - *

The return type is a pair of the type of the invoked constructor and the (inferred) type - * arguments. Note that neither the explicitly passed nor the inferred type arguments are - * guaranteed to be subtypes of the corresponding upper bounds. See method {@link - * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of - * type argument well-formedness. - * - *

Note that "this" and "super" constructor invocations are handled by method {@link - * #methodFromUse}. This method only handles constructor invocations in a "new" expression. - * - * @param tree the constructor invocation tree - * @return the annotated type of the invoked constructor (as an executable type) and the - * (inferred) type arguments - */ - public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { - // Get the annotations written on the new class tree. - AnnotatedDeclaredType type = - (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false); - if (!TreeUtils.isDiamondTree(tree)) { - if (tree.getClassBody() == null) { - type.setTypeArguments(getExplicitNewClassClassTypeArgs(tree)); - } - } else { - type = getAnnotatedType(TypesUtils.getTypeElement(type.underlyingType)); - // Add explicit annotations below. - type.clearAnnotations(); - } - - AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(tree); - type.addAnnotations(explicitAnnos); - - // Get the enclosing type of the constructor, if one exists. - // this.new InnerClass() - AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(tree); - type.setEnclosingType(enclosingType); - - // Add computed annotations to the type. - addComputedTypeAnnotations(tree, type); - - ExecutableElement ctor = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type - constructorFromUsePreSubstitution(tree, con); - - if (tree.getClassBody() != null) { - // Because the anonymous constructor can't have explicit annotations on its parameters, - // they are copied from the super constructor invoked in the anonymous constructor. To - // do this: - // 1. get unsubstituted type of the super constructor. - // 2. adapt it to this call site. - // 3. compute and store the vararg type. - // 4. copy the parameters to the anonymous constructor, `con`. - // 5. copy annotations on the return type to `con`. - AnnotatedExecutableType superCon = - getAnnotatedType(TreeUtils.getSuperConstructor(tree)); - constructorFromUsePreSubstitution(tree, superCon); - // no viewpoint adaptation needed for super invocation - superCon = - AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon); - con.computeVarargType(superCon); - if (superCon.getParameterTypes().size() == con.getParameterTypes().size()) { - con.setParameterTypes(superCon.getParameterTypes()); - } else { - // If the super class of the anonymous class has an enclosing type, then it is the - // first parameter of the anonymous constructor. For example, - // class Outer { class Inner {} } - // new Inner(){}; - // Then javac creates the following constructor: - // (.Outer x0) { - // x0.super(); - // } - // So the code below deals with this. - // Because the anonymous constructor doesn't have annotated receiver type, - // we copy the receiver type from the super constructor invoked in the anonymous - // constructor - List p = - new ArrayList<>(superCon.getParameterTypes().size() + 1); - p.add(con.getParameterTypes().get(0)); - con.setReceiverType(superCon.getReceiverType()); - p.addAll(superCon.getParameterTypes()); - con.setParameterTypes(Collections.unmodifiableList(p)); - } - con.getReturnType().replaceAnnotations(superCon.getReturnType().getAnnotations()); - } else { - // Store varargType before calling setParameterTypes, otherwise we may lose the - // varargType as it is the last element of the original parameterTypes. - // AnnotatedTypes.asMemberOf handles vararg type properly, so we do not need to compute - // vararg type again. - con.computeVarargType(); - con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con); - } - - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); - } - - Map typeParamToTypeArg = - AnnotatedTypes.findTypeArguments(processingEnv, this, tree, ctor, con); - List typeargs; - if (typeParamToTypeArg.isEmpty()) { - typeargs = Collections.emptyList(); - } else { - typeargs = - CollectionsPlume.mapList( - (AnnotatedTypeVariable tv) -> - typeParamToTypeArg.get(tv.getUnderlyingType()), - con.getTypeVariables()); - } - if (TreeUtils.isDiamondTree(tree)) { - // TODO: This should be done at the same time as type argument inference. - List classTypeArgs = inferDiamondType(tree); - int i = 0; - for (AnnotatedTypeMirror typeParam : type.getTypeArguments()) { - typeParamToTypeArg.put( - (TypeVariable) typeParam.getUnderlyingType(), classTypeArgs.get(i)); - i++; - } - } - con = (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, con); - - stubTypes.injectRecordComponentType(types, ctor, con); - if (enclosingType != null) { - // Reset the enclosing type because it can be substituted incorrectly. - ((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(enclosingType); - } - - if (ctor.getEnclosingElement().getKind() == ElementKind.ENUM) { - AnnotationMirrorSet enumAnnos = getEnumConstructorQualifiers(); - con.getReturnType().replaceAnnotations(enumAnnos); - } - - // Adapt parameters, which makes parameters and arguments be the same size for later - // checking. The vararg type of con has been already computed and stored when calling - // typeVarSubstitutor.substitute. - List parameters = - AnnotatedTypes.adaptParameters(this, con, tree.getArguments(), tree); - con.setParameterTypes(parameters); - - return new ParameterizedExecutableType(con, typeargs); - } - - /** - * Returns the annotations that should be applied to enum constructors. This implementation - * returns an empty set. Subclasses can override to return a different set. - * - * @return the annotations that should be applied to enum constructors - */ - protected AnnotationMirrorSet getEnumConstructorQualifiers() { - return new AnnotationMirrorSet(); - } - - /** - * Creates an AnnotatedDeclaredType for a NewClassTree. Only adds explicit annotations, unless - * newClassTree has a diamond operator. In that case, the annotations on the type arguments are - * inferred using the assignment context and contain defaults. - * - *

(Subclass beside {@link GenericAnnotatedTypeFactory} should not override this method.) - * - * @param newClassTree a NewClassTree - * @return the AnnotatedDeclaredType - * @deprecated Use {@link #getExplicitNewClassAnnos(NewClassTree)}, {@link - * #getExplicitNewClassClassTypeArgs(NewClassTree)}, or {@link #getAnnotatedType(ClassTree)} - * instead. - */ - @Deprecated // This should be removed when #979 is fixed and the remaining use is removed. - public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) { - AnnotatedDeclaredType type = - (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false); - if (!TreeUtils.isDiamondTree(newClassTree)) { - if (newClassTree.getClassBody() == null) { - type.setTypeArguments(getExplicitNewClassClassTypeArgs(newClassTree)); - } - } else { - assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree"; - TreePath p = getPath(newClassTree); - AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p); - if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype; - if (type.getTypeArguments().size() == adctx.getTypeArguments().size()) { - // Try to simply take the type arguments from LHS. - List oldArgs = type.getTypeArguments(); - List newArgs = adctx.getTypeArguments(); - for (int i = 0; i < type.getTypeArguments().size(); ++i) { - if (!types.isSubtype( - newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) { - // One of the underlying types doesn't match. Give up. - newArgs = oldArgs; - break; - } - } - type.setTypeArguments(newArgs); - } - } - } - - AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(newClassTree); - // Type may already have explicit dependent type annotations that have not yet been vpa. - type.clearAnnotations(); - type.addAnnotations(explicitAnnos); - // Use the receiver type as enclosing type, if present. - AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(newClassTree); - if (enclosingType != null) { - type.setEnclosingType(enclosingType); - } - return type; - } - - /** - * Returns the annotations explicitly written on a NewClassTree. - * - *

{@code new @HERE Class()} - * - * @param newClassTree a constructor invocation - * @return the annotations explicitly written on a NewClassTree - */ - public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { - if (newClassTree.getClassBody() != null) { - // In Java 17+, the annotations are on the identifier, so copy them. - AnnotatedTypeMirror identifierType = fromTypeTree(newClassTree.getIdentifier()); - // In Java 11 and lower, if newClassTree creates an anonymous class, then annotations in - // this location: - // new @HERE Class() {} - // are not on the identifier newClassTree, but rather on the modifier newClassTree. - List annoTrees = - newClassTree.getClassBody().getModifiers().getAnnotations(); - // Add the annotations to an AnnotatedTypeMirror removes the annotations that are not - // supported by this type system. - identifierType.addAnnotations(TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees)); - return identifierType.getAnnotations(); - } else { - return fromTypeTree(newClassTree.getIdentifier()).getAnnotations(); - } - } - - /** - * Returns the partially-annotated explicit class type arguments of the new class tree. The - * {@code AnnotatedTypeMirror} only include the annotations explicitly written on the explict - * type arguments. (If {@code newClass} use a diamond operator, this method returns the empty - * list.) For example, when called with {@code new MyClass<@HERE String>()} this method would - * return a list containing {@code @HERE String}. - * - * @param newClass a new class tree - * @return the partially annotated {@code AnnotatedTypeMirror}s for the (explicit) class type - * arguments of the new class tree - */ - protected List getExplicitNewClassClassTypeArgs(NewClassTree newClass) { - if (!TreeUtils.isDiamondTree(newClass)) { - return ((AnnotatedDeclaredType) fromTypeTree(newClass.getIdentifier())) - .getTypeArguments(); - } - return Collections.emptyList(); - } - - /** - * Infer the class type arguments for the diamond operator. - * - *

If {@code newClassTree} is assigned to the same type (not a supertype), then the type - * arguments are inferred to be the same as the assignment. Otherwise, the type arguments are - * annotated by {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. - * - * @param newClassTree a diamond new class tree - * @return the class type arguments for {@code newClassTree} - */ - private List inferDiamondType(NewClassTree newClassTree) { - assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree"; - AnnotatedDeclaredType diamondType = - (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false); - - TreePath p = getPath(newClassTree); - AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p); - if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype; - if (diamondType.getTypeArguments().size() == adctx.getTypeArguments().size()) { - // Try to simply take the type arguments from LHS. - List oldArgs = diamondType.getTypeArguments(); - List newArgs = adctx.getTypeArguments(); - boolean useLhs = true; - for (int i = 0; i < diamondType.getTypeArguments().size(); ++i) { - if (!types.isSubtype( - newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) { - // One of the underlying types doesn't match. Give up. - useLhs = false; - break; - } - } - if (useLhs) { - return newArgs; - } - } - } - addComputedTypeAnnotations(newClassTree, diamondType); - return diamondType.getTypeArguments(); - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the - * declared constructor type before type variable substitution. - * - * @param tree a NewClassTree from constructorFromUse() - * @param type declared method type before type variable substitution - */ - protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type) {} - - /** - * Returns the return type of the method {@code m}. - * - * @param m tree of a method declaration - * @return the return type of the method - */ - public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { - AnnotatedExecutableType methodType = getAnnotatedType(m); - AnnotatedTypeMirror ret = methodType.getReturnType(); - return ret; - } - - /** - * Returns the return type of the method {@code m} at the return statement {@code r}. This - * implementation just calls {@link #getMethodReturnType(MethodTree)}, but subclasses may - * override this method to change the type based on the return statement. - * - * @param m tree of a method declaration - * @param r a return statement within method {@code m} - * @return the return type of the method {@code m} at the return statement {@code r} - */ - public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { - return getMethodReturnType(m); - } - - /** - * Returns the annotated boxed type of the given primitive type. The returned type would only - * have the annotations on the given type. - * - *

Subclasses may override this method safely to override this behavior. - * - * @param type the primitive type - * @return the boxed declared type of the passed primitive type - */ - public AnnotatedDeclaredType getBoxedType(AnnotatedPrimitiveType type) { - TypeElement typeElt = types.boxedClass(type.getUnderlyingType()); - AnnotatedDeclaredType dt = fromElement(typeElt).asUse(); - dt.addAnnotations(type.getAnnotations()); - return dt; - } - - /** - * Return a primitive type: either the argument, or the result of unboxing it (which might - * affect its annotations). - * - *

Subclasses should override {@link #getUnboxedType} rather than this method. - * - * @param type a type: a primitive or boxed primitive - * @return the unboxed variant of the type - */ - public final AnnotatedPrimitiveType applyUnboxing(AnnotatedTypeMirror type) { - TypeMirror underlying = type.getUnderlyingType(); - if (TypesUtils.isPrimitive(underlying)) { - return (AnnotatedPrimitiveType) type; - } else if (TypesUtils.isBoxedPrimitive(underlying)) { - return getUnboxedType((AnnotatedDeclaredType) type); - } else { - throw new BugInCF("Bad argument to applyUnboxing: " + type); - } - } - - /** - * Returns the annotated primitive type of the given declared type if it is a boxed declared - * type. Otherwise, it throws IllegalArgumentException exception. - * - *

In the {@code AnnotatedTypeFactory} implementation, the returned type has the same primary - * annotations as the given type. Subclasses may override this behavior. - * - * @param type the declared type - * @return the unboxed primitive type - * @throws IllegalArgumentException if the type given has no unbox conversion - */ - public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) - throws IllegalArgumentException { - PrimitiveType primitiveType = types.unboxedType(type.getUnderlyingType()); - AnnotatedPrimitiveType pt = - (AnnotatedPrimitiveType) AnnotatedTypeMirror.createType(primitiveType, this, false); - pt.addAnnotations(type.getAnnotations()); - return pt; - } - - /** - * Returns AnnotatedDeclaredType with underlying type String and annotations copied from type. - * Subclasses may change the annotations. - * - * @param type type to convert to String - * @return AnnotatedTypeMirror that results from converting type to a String type - */ - // TODO: Test that this is called in all the correct locations - // See Issue #715 - // https://github.com/typetools/checker-framework/issues/715 - public AnnotatedDeclaredType getStringType(AnnotatedTypeMirror type) { - TypeMirror stringTypeMirror = TypesUtils.typeFromClass(String.class, types, elements); - AnnotatedDeclaredType stringATM = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType( - stringTypeMirror, this, type.isDeclaration()); - stringATM.addAnnotations(type.getEffectiveAnnotations()); - return stringATM; - } - - /** - * Returns a widened type if applicable, otherwise returns its first argument. - * - *

Subclasses should override {@link #getWidenedAnnotations} rather than this method. - * - * @param exprType type to possibly widen - * @param widenedType type to possibly widen to; its annotations are ignored - * @return if widening is applicable, the result of converting {@code type} to the underlying - * type of {@code widenedType}; otherwise {@code type} - */ - public final AnnotatedTypeMirror getWidenedType( - AnnotatedTypeMirror exprType, AnnotatedTypeMirror widenedType) { - TypeKind exprKind = exprType.getKind(); - TypeKind widenedKind = widenedType.getKind(); - - if (!TypeKindUtils.isNumeric(widenedKind)) { - // The target type is not a numeric primitive, so primitive widening is not applicable. - return exprType; - } - - AnnotatedPrimitiveType exprPrimitiveType; - if (TypeKindUtils.isNumeric(exprKind)) { - exprPrimitiveType = (AnnotatedPrimitiveType) exprType; - } else if (TypesUtils.isNumericBoxed(exprType.getUnderlyingType())) { - exprPrimitiveType = getUnboxedType((AnnotatedDeclaredType) exprType); - } else { - return exprType; - } - - switch (TypeKindUtils.getPrimitiveConversionKind( - exprPrimitiveType.getKind(), widenedType.getKind())) { - case WIDENING: - return getWidenedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); - case NARROWING: - return getNarrowedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); - case SAME: - return exprType; - default: - throw new BugInCF("unhandled PrimitiveConversionKind"); - } - } - - /** - * Applies widening if applicable, otherwise returns its first argument. - * - *

Subclasses should override {@link #getWidenedAnnotations} rather than this method. - * - * @param exprAnnos annotations to possibly widen - * @param exprTypeMirror type to possibly widen - * @param widenedType type to possibly widen to; its annotations are ignored - * @return if widening is applicable, the result of converting {@code type} to the underlying - * type of {@code widenedType}; otherwise {@code type} - */ - public final AnnotatedTypeMirror getWidenedType( - AnnotationMirrorSet exprAnnos, - TypeMirror exprTypeMirror, - AnnotatedTypeMirror widenedType) { - AnnotatedTypeMirror exprType = toAnnotatedType(exprTypeMirror, false); - exprType.replaceAnnotations(exprAnnos); - return getWidenedType(exprType, widenedType); - } - - /** - * Returns an AnnotatedPrimitiveType with underlying type {@code widenedTypeMirror} and with - * annotations copied or adapted from {@code type}. - * - * @param type type to widen; a primitive or boxed primitive - * @param widenedTypeMirror underlying type for the returned type mirror; a primitive or boxed - * primitive (same boxing as {@code type}) - * @return result of converting {@code type} to {@code widenedTypeMirror} - */ - private AnnotatedPrimitiveType getWidenedPrimitive( - AnnotatedPrimitiveType type, TypeMirror widenedTypeMirror) { - AnnotatedPrimitiveType result = - (AnnotatedPrimitiveType) - AnnotatedTypeMirror.createType( - widenedTypeMirror, this, type.isDeclaration()); - result.addAnnotations( - getWidenedAnnotations(type.getAnnotations(), type.getKind(), result.getKind())); - return result; - } - - /** - * Returns annotations applicable to type {@code narrowedTypeKind}, that are copied or adapted - * from {@code annos}. - * - * @param annos annotations to narrow, from a primitive or boxed primitive - * @param typeKind primitive type to narrow - * @param narrowedTypeKind target for the returned annotations; a primitive type that is - * narrower than {@code typeKind} (in the sense of JLS 5.1.3). - * @return result of converting {@code annos} from {@code typeKind} to {@code narrowedTypeKind} - */ - public AnnotationMirrorSet getNarrowedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { - return annos; - } - - /** - * Returns annotations applicable to type {@code widenedTypeKind}, that are copied or adapted - * from {@code annos}. - * - * @param annos annotations to widen, from a primitive or boxed primitive - * @param typeKind primitive type to widen - * @param widenedTypeKind target for the returned annotations; a primitive type that is wider - * than {@code typeKind} (in the sense of JLS 5.1.2) - * @return result of converting {@code annos} from {@code typeKind} to {@code widenedTypeKind} - */ - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - return annos; - } - - /** - * Returns the types of the two arguments to the BinaryTree. Please refer to {@link - * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} )} for more - * details. - * - * @param tree a binary tree - * @return the types of the two arguments - */ - public IPair binaryTreeArgTypes(BinaryTree tree) { - return binaryTreeArgTypes( - TreeUtils.typeOf(tree), - getAnnotatedType(tree.getLeftOperand()), - getAnnotatedType(tree.getRightOperand())); - } - - /** - * Returns the types of the two arguments to the CompoundAssignmentTree. Please refer to {@link - * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} ) for more - * details. - * - * @param tree a compound assignment tree - * @return the types of the two arguments - */ - public IPair compoundAssignmentTreeArgTypes( - CompoundAssignmentTree tree) { - return binaryTreeArgTypes( - TreeUtils.typeOf(tree.getVariable()), - getAnnotatedType(tree.getVariable()), - getAnnotatedType(tree.getExpression())); - } - - /** - * Returns the types of the two arguments to a binary operation. There are two special cases: - * - *

1. If both operands have numeric type, widening and unboxing will be applied accordingly. - * - *

2. If we have a non-string operand in a string concatenation (i.e., result is a string), - * we will always return a string ATM for the operand. The resulting ATM will have the original - * annotations with the declaration bounds of string type applied. Please check {@link - * #getAnnotationOrTypeDeclarationBound} for more details. - * - * @param resultType the type of the result of a binary operation - * @param left the type of the left argument of a binary operation - * @param right the type of the right argument of a binary operation - * @return the types of the two arguments - */ - protected IPair binaryTreeArgTypes( - TypeMirror resultType, AnnotatedTypeMirror left, AnnotatedTypeMirror right) { - TypeKind widenedNumericType = - TypeKindUtils.widenedNumericType( - left.getUnderlyingType(), right.getUnderlyingType()); - if (TypeKindUtils.isNumeric(widenedNumericType)) { - TypeMirror widenedNumericTypeMirror = types.getPrimitiveType(widenedNumericType); - AnnotatedPrimitiveType leftUnboxed = applyUnboxing(left); - AnnotatedPrimitiveType rightUnboxed = applyUnboxing(right); - AnnotatedPrimitiveType leftWidened = - (leftUnboxed.getKind() == widenedNumericType - ? leftUnboxed - : getWidenedPrimitive(leftUnboxed, widenedNumericTypeMirror)); - AnnotatedPrimitiveType rightWidened = - (rightUnboxed.getKind() == widenedNumericType - ? rightUnboxed - : getWidenedPrimitive(rightUnboxed, widenedNumericTypeMirror)); - return IPair.of(leftWidened, rightWidened); - } else if (TypesUtils.isString(resultType)) { - // the result of a binary operation is String iff it's string concatenation - AnnotatedTypeMirror leftStringConverted = left; - AnnotatedTypeMirror rightStringConverted = right; - - if (!TypesUtils.isString(left.getUnderlyingType())) { - leftStringConverted = toAnnotatedType(resultType, false); - AnnotationMirrorSet annos = - getAnnotationOrTypeDeclarationBound( - resultType, left.getEffectiveAnnotations()); - leftStringConverted.addAnnotations(annos); - } - if (!TypesUtils.isString(right.getUnderlyingType())) { - rightStringConverted = toAnnotatedType(resultType, false); - AnnotationMirrorSet annos = - getAnnotationOrTypeDeclarationBound( - resultType, right.getEffectiveAnnotations()); - rightStringConverted.addAnnotations(annos); - } - return IPair.of(leftStringConverted, rightStringConverted); - } - - return IPair.of(left, right); - } - - /** - * Returns AnnotatedPrimitiveType with underlying type {@code narrowedTypeMirror} and with - * annotations copied or adapted from {@code type}. - * - *

Currently this method is called only for primitives that are narrowed at assignments from - * literal ints, for example, {@code byte b = 1;}. All other narrowing conversions happen at - * typecasts. - * - * @param type type to narrow - * @param narrowedTypeMirror underlying type for the returned type mirror - * @return result of converting {@code type} to {@code narrowedTypeMirror} - */ - public AnnotatedPrimitiveType getNarrowedPrimitive( - AnnotatedPrimitiveType type, TypeMirror narrowedTypeMirror) { - AnnotatedPrimitiveType narrowed = - (AnnotatedPrimitiveType) - AnnotatedTypeMirror.createType( - narrowedTypeMirror, this, type.isDeclaration()); - narrowed.addAnnotations(type.getAnnotations()); - return narrowed; - } - - // ********************************************************************** - // random methods wrapping #getAnnotatedType(Tree) and #fromElement(Tree) - // with appropriate casts to reduce casts on the client side - // ********************************************************************** - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedDeclaredType getAnnotatedType(ClassTree tree) { - return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedDeclaredType getAnnotatedType(NewClassTree tree) { - return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedArrayType getAnnotatedType(NewArrayTree tree) { - return (AnnotatedArrayType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedExecutableType getAnnotatedType(MethodTree tree) { - return (AnnotatedExecutableType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Element)}. - * - * @see #getAnnotatedType(Element) - */ - public final AnnotatedDeclaredType getAnnotatedType(TypeElement elt) { - return (AnnotatedDeclaredType) getAnnotatedType((Element) elt); - } - - /** - * See {@link #getAnnotatedType(Element)}. - * - * @see #getAnnotatedType(Element) - */ - public final AnnotatedExecutableType getAnnotatedType(ExecutableElement elt) { - return (AnnotatedExecutableType) getAnnotatedType((Element) elt); - } - - /** - * See {@link #fromElement(Element)}. - * - * @see #fromElement(Element) - */ - public final AnnotatedDeclaredType fromElement(TypeElement elt) { - return (AnnotatedDeclaredType) fromElement((Element) elt); - } - - /** - * See {@link #fromElement(Element)}. - * - * @see #fromElement(Element) - */ - public final AnnotatedExecutableType fromElement(ExecutableElement elt) { - return (AnnotatedExecutableType) fromElement((Element) elt); - } - - // ********************************************************************** - // Helper methods for this classes - // ********************************************************************** - - /** - * Returns true if the given annotation is a part of the type system under which this type - * factory operates. Null is never a supported qualifier; the parameter is nullable to allow the - * result of canonicalAnnotation to be passed in directly. - * - * @param a any annotation - * @return true if that annotation is part of the type system under which this type factory - * operates, false otherwise - */ - @EnsuresNonNullIf(expression = "#1", result = true) - public boolean isSupportedQualifier(@Nullable AnnotationMirror a) { - if (a == null) { - return false; - } - return isSupportedQualifier(AnnotationUtils.annotationName(a)); - } - - /** - * Returns true if the given class is a part of the type system under which this type factory - * operates. - * - * @param clazz annotation class - * @return true if that class is a type qualifier in the type system under which this type - * factory operates, false otherwise - */ - public boolean isSupportedQualifier(Class clazz) { - return getSupportedTypeQualifiers().contains(clazz); - } - - /** - * Returns true if the given class name is a part of the type system under which this type - * factory operates. - * - * @param className fully-qualified annotation class name - * @return true if that class name is a type qualifier in the type system under which this type - * factory operates, false otherwise - */ - public boolean isSupportedQualifier(String className) { - return getSupportedTypeQualifierNames().contains(className); - } - - /** - * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code - * canonicalAnno} that will be used by the Checker Framework in the alias's place. - * - *

By specifying the alias/canonical relationship using this method, the elements of the - * alias are not preserved when the canonical annotation to use is constructed from the alias. - * If you want the elements to be copied over as well, use {@link - * #addAliasedTypeAnnotation(Class, Class, boolean, String...)}. - * - * @param aliasClass the class of the aliased annotation - * @param canonicalAnno the canonical annotation - */ - protected void addAliasedTypeAnnotation(Class aliasClass, AnnotationMirror canonicalAnno) { - if (getSupportedTypeQualifiers().contains(aliasClass)) { - throw new BugInCF( - "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", - aliasClass, this.getClass().getSimpleName()); - } - addAliasedTypeAnnotation(aliasClass.getCanonicalName(), canonicalAnno); - } - - /** - * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias - * for the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework - * in the alias's place. - * - *

Use this method if the alias class is not necessarily on the classpath at Checker - * Framework compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, - * AnnotationMirror)} which prevents the possibility of a typo in the class name. - * - * @param aliasName the canonical name of the aliased annotation - * @param canonicalAnno the canonical annotation - */ - // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the - // name of an external annotation is a canonical name. - protected void addAliasedTypeAnnotation( - @FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) { - aliases.put(aliasName, new Alias(aliasName, canonicalAnno, false, null, null)); - } - - /** - * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code - * canonicalClass} that will be used by the Checker Framework in the alias's place. - * - *

You may specify the copyElements flag to indicate whether you want the elements of the - * alias to be copied over when the canonical annotation is constructed as a copy of {@code - * canonicalClass}. Be careful that the framework will try to copy the elements by name - * matching, so make sure that names and types of the elements to be copied over are exactly the - * same as the ones in the canonical annotation. Otherwise, an 'Couldn't find element in - * annotation' error is raised. - * - *

To facilitate the cases where some of the elements are ignored on purpose when - * constructing the canonical annotation, this method also provides a varargs {@code - * ignorableElements} for you to explicitly specify the ignoring rules. For example, {@code - * org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code - * org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of - * {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases - * where all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class, - * AnnotationMirror)} instead. - * - * @param aliasClass the class of the aliased annotation - * @param canonicalClass the class of the canonical annotation - * @param copyElements a flag that indicates whether you want to copy the elements over when - * getting the alias from the canonical annotation - * @param ignorableElements a list of elements that can be safely dropped when the elements are - * being copied over - */ - protected void addAliasedTypeAnnotation( - Class aliasClass, - Class canonicalClass, - boolean copyElements, - String... ignorableElements) { - if (getSupportedTypeQualifiers().contains(aliasClass)) { - throw new BugInCF( - "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", - aliasClass, this.getClass().getSimpleName()); - } - addAliasedTypeAnnotation( - aliasClass.getCanonicalName(), canonicalClass, copyElements, ignorableElements); - } - - /** - * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias - * for the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework - * in the alias's place. - * - *

Use this method if the alias class is not necessarily on the classpath at Checker - * Framework compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, Class, - * boolean, String[])} which prevents the possibility of a typo in the class name. - * - * @param aliasName the canonical name of the aliased class - * @param canonicalAnno the canonical annotation - * @param copyElements a flag that indicates whether we want to copy the elements over when - * getting the alias from the canonical annotation - * @param ignorableElements a list of elements that can be safely dropped when the elements are - * being copied over - */ - // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the - // name of an external annotation is a canoncal name. - protected void addAliasedTypeAnnotation( - @FullyQualifiedName String aliasName, - Class canonicalAnno, - boolean copyElements, - String... ignorableElements) { - // The copyElements argument disambiguates overloading. - if (!copyElements) { - throw new BugInCF("Do not call with false"); - } - aliases.put( - aliasName, - new Alias( - aliasName, - null, - copyElements, - canonicalAnno.getCanonicalName(), - ignorableElements)); - } - - /** - * Returns the canonical annotation for the passed annotation. Returns null if the passed - * annotation is not an alias of a canonical one in the framework. - * - *

A canonical annotation is the internal annotation that will be used by the Checker - * Framework in the aliased annotation's place. - * - * @param a the qualifier to check for an alias - * @return the canonical annotation, or null if none exists - */ - public @Nullable AnnotationMirror canonicalAnnotation(AnnotationMirror a) { - TypeElement elem = (TypeElement) a.getAnnotationType().asElement(); - String qualName = elem.getQualifiedName().toString(); - Alias alias = aliases.get(qualName); - if (alias == null) { - return null; - } - if (alias.copyElements) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, alias.canonicalName); - builder.copyElementValuesFromAnnotation(a, alias.ignorableElements); - return builder.build(); - } else { - return alias.canonical; - } - } - - /** - * Add the annotation {@code alias} as an alias for the declaration annotation {@code - * annotation}, where the annotation mirror {@code annotationToUse} will be used instead. If - * multiple calls are made with the same {@code annotation}, then the {@code annotationToUse} - * must be the same. - * - *

The point of {@code annotationToUse} is that it may include elements/fields. - * - * @param alias the class of the alias annotation - * @param annotation the class of the canonical annotation - * @param annotationToUse the annotation mirror to use - */ - protected void addAliasedDeclAnnotation( - Class alias, - Class annotation, - AnnotationMirror annotationToUse) { - addAliasedDeclAnnotation( - alias.getCanonicalName(), annotation.getCanonicalName(), annotationToUse); - } - - /** - * Add the annotation {@code alias} as an alias for the declaration annotation {@code - * annotation}, where the annotation mirror {@code annotationToUse} will be used instead. If - * multiple calls are made with the same {@code annotation}, then the {@code annotationToUse} - * must be the same. - * - *

The point of {@code annotationToUse} is that it may include elements/fields. - * - * @param alias the fully-qualified name of the alias annotation - * @param annotation the fully-qualified name of the canonical annotation - * @param annotationToUse the annotation mirror to use - */ - protected void addAliasedDeclAnnotation( - @FullyQualifiedName String alias, - @FullyQualifiedName String annotation, - AnnotationMirror annotationToUse) { - Map<@FullyQualifiedName String, AnnotationMirror> mapping = declAliases.get(annotation); - if (mapping == null) { - mapping = new HashMap<>(1); - declAliases.put(annotation, mapping); - } - AnnotationMirror prev = mapping.put(alias, annotationToUse); - // There already was a mapping. Raise an error. - if (prev != null && !AnnotationUtils.areSame(prev, annotationToUse)) { - throw new TypeSystemError( - "Multiple aliases for %s: %s cannot map to %s and %s.", - annotation, alias, prev, annotationToUse); - } - } - - /** - * Adds the annotation {@code annotation} in the set of declaration annotations that should be - * inherited. A declaration annotation will be inherited if it is in this list, or if it has the - * meta-annotation @InheritedAnnotation. The meta-annotation @InheritedAnnotation should be used - * instead of this method, if possible. - */ - protected void addInheritedAnnotation(AnnotationMirror annotation) { - inheritedAnnotations.add(annotation); - } - - /** - * A convenience method that converts a {@link TypeMirror} to an empty {@link - * AnnotatedTypeMirror} using {@link AnnotatedTypeMirror#createType}. - * - * @param t the {@link TypeMirror} - * @param declaration true if the result should be marked as a type declaration - * @return an {@link AnnotatedTypeMirror} that has {@code t} as its underlying type - */ - protected final AnnotatedTypeMirror toAnnotatedType(TypeMirror t, boolean declaration) { - return AnnotatedTypeMirror.createType(t, this, declaration); - } - - /** - * Determines an empty annotated type of the given tree. In other words, finds the {@link - * TypeMirror} for the tree and converts that into an {@link AnnotatedTypeMirror}, but does not - * add any annotations to the result. - * - *

Most users will want to use {@link #getAnnotatedType(Tree)} instead; this method is mostly - * for internal use. - * - * @param tree the tree to analyze - * @return the type of {@code tree}, without any annotations - */ - protected final AnnotatedTypeMirror type(Tree tree) { - boolean isDeclaration = TreeUtils.isTypeDeclaration(tree); - - // Attempt to obtain the type via JCTree. - if (TreeUtils.typeOf(tree) != null) { - AnnotatedTypeMirror result = toAnnotatedType(TreeUtils.typeOf(tree), isDeclaration); - return result; - } - - // Attempt to obtain the type via TreePath (slower). - TreePath path = this.getPath(tree); - assert path != null - : "No path or type in tree: " + tree + " [" + tree.getClass().getSimpleName() + "]"; - - TypeMirror t = trees.getTypeMirror(path); - assert validType(t) : "Invalid type " + t + " for tree " + t; - - AnnotatedTypeMirror result = toAnnotatedType(t, isDeclaration); - return result; - } - - /** - * Gets the declaration tree for the element, if the source is available. - * - *

TODO: would be nice to move this to InternalUtils/TreeUtils. - * - * @param elt an element - * @return the tree declaration of the element if found - */ - public final @Nullable Tree declarationFromElement(Element elt) { - // if root is null, we cannot find any declaration - if (root == null) { - return null; - } - if (shouldCache && elementToTreeCache.containsKey(elt)) { - return elementToTreeCache.get(elt); - } - - // Check for new declarations, outside of the AST. - if (elt instanceof DetachedVarSymbol) { - return ((DetachedVarSymbol) elt).getDeclaration(); - } - - // TODO: handle type parameter declarations? - Tree fromElt; - // Prevent calling declarationFor on elements we know we don't have the tree for. - - switch (ElementUtils.getKindRecordAsClass(elt)) { - case CLASS: // Including RECORD - case ENUM: - case INTERFACE: - case ANNOTATION_TYPE: - case FIELD: - case ENUM_CONSTANT: - case METHOD: - case CONSTRUCTOR: - fromElt = trees.getTree(elt); - break; - default: - fromElt = - com.sun.tools.javac.tree.TreeInfo.declarationFor( - (com.sun.tools.javac.code.Symbol) elt, - (com.sun.tools.javac.tree.JCTree) root); - break; - } - if (shouldCache) { - elementToTreeCache.put(elt, fromElt); - } - return fromElt; - } - - /** - * Returns true if {@code tree} is within a constructor. - * - * @param tree the tree that might be within a constructor - * @return true if {@code tree} is within a constructor - */ - protected final boolean isWithinConstructor(Tree tree) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getPath(tree)); - return enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod); - } - - /** - * Sets the path to the tree that an external "visitor" is visiting. The visitor is either a - * subclass of {@link BaseTypeVisitor} or {@link - * org.checkerframework.framework.flow.CFAbstractTransfer}. - * - * @param visitorTreePath path to the current tree that an external "visitor" is visiting - */ - public void setVisitorTreePath(@Nullable TreePath visitorTreePath) { - this.visitorTreePath = visitorTreePath; - } - - /** - * Returns the path to the tree that an external "visitor" is visiting. The type factory does - * not update this value as it computes the types of any tree or element needed compute the type - * of the tree being visited. Therefore this path may not be the path to the tree whose type is - * being computed. This method should not be used directly. Use {@link #getPath(Tree)} instead. - * - *

This method is used to save the previous tree path and to give a hint to {@link - * #getPath(Tree)} on where to look for a tree rather than searching starting at the root. - * - * @return the path to the tree that an external "visitor" is visiting - */ - public @Nullable TreePath getVisitorTreePath() { - return visitorTreePath; - } - - /** - * Gets the path for the given {@link Tree} under the current root by checking from the - * visitor's current path, and using {@link Trees#getPath(CompilationUnitTree, Tree)} (which is - * much slower) only if {@code tree} is not found on the current path. - * - *

Note that the given Tree has to be within the current compilation unit, otherwise null - * will be returned. - * - *

Within a subclass of BaseTypeVisitor, use {@code getCurrentPath()} rather than this - * method. - * - * @param tree the {@link Tree} to get the path for - * @return the path for {@code tree} under the current root. Returns null if {@code tree} is not - * within the current compilation unit. - */ - public final @Nullable TreePath getPath(@FindDistinct Tree tree) { - assert root != null - : "AnnotatedTypeFactory.getPath(" - + tree.getKind() - + "): root needs to be set when used on trees; factory: " - + this.getClass().getSimpleName(); - - if (tree == null) { - return null; - } - - if (treePathCache.isCached(tree)) { - return treePathCache.getPath(root, tree); - } - - TreePath currentPath = visitorTreePath; - if (currentPath == null) { - TreePath path = TreePath.getPath(root, tree); - treePathCache.addPath(tree, path); - return path; - } - - // This method uses multiple heuristics to avoid calling - // TreePath.getPath() - - // If the current path you are visiting is for this tree we are done - if (currentPath.getLeaf() == tree) { - treePathCache.addPath(tree, currentPath); - return currentPath; - } - - // When running on Daikon, we noticed that a lot of calls happened - // within a small subtree containing the tree we are currently visiting - - // When testing on Daikon, two steps resulted in the best performance - if (currentPath.getParentPath() != null) { - currentPath = currentPath.getParentPath(); - treePathCache.addPath(currentPath.getLeaf(), currentPath); - if (currentPath.getLeaf() == tree) { - return currentPath; - } - if (currentPath.getParentPath() != null) { - currentPath = currentPath.getParentPath(); - treePathCache.addPath(currentPath.getLeaf(), currentPath); - if (currentPath.getLeaf() == tree) { - return currentPath; - } - } - } - - TreePath pathWithinSubtree = TreePath.getPath(currentPath, tree); - if (pathWithinSubtree != null) { - treePathCache.addPath(tree, pathWithinSubtree); - return pathWithinSubtree; - } - - // climb the current path till we see that - // Works when getPath called on the enclosing method, enclosing class. - TreePath current = currentPath; - while (current != null) { - treePathCache.addPath(current.getLeaf(), current); - if (current.getLeaf() == tree) { - return current; - } - current = current.getParentPath(); - } - - // OK, we give up. Use the cache to look up. - return treePathCache.getPath(root, tree); - } - - /** - * Set the tree path for the given artificial tree. - * - *

See {@code - * org.checkerframework.framework.flow.CFCFGBuilder.CFCFGTranslationPhaseOne.handleArtificialTree(Tree)}. - * - * @param tree the artificial {@link Tree} to set the path for - * @param path the {@link TreePath} for the artificial tree - */ - public final void setPathForArtificialTree(Tree tree, TreePath path) { - treePathCache.addPath(tree, path); - } - - /** - * Assert that the type is a type of valid type mirror, i.e. not an ERROR or OTHER type. - * - * @param type an annotated type - * @return true if the type is a valid annotated type, false otherwise - */ - /*package-private*/ static final boolean validAnnotatedType(AnnotatedTypeMirror type) { - if (type == null) { - return false; - } - return validType(type.getUnderlyingType()); - } - - /** - * Used for asserting that a type is valid for converting to an annotated type. - * - * @return true if {@code type} can be converted to an annotated type, false otherwise - */ - private static boolean validType(TypeMirror type) { - if (type == null) { - return false; - } - switch (type.getKind()) { - case ERROR: - case OTHER: - case PACKAGE: - return false; - default: - return true; - } - } - - /** - * Parses all annotation files in the following order: - * - *

    - *
  1. Stub files, see {@link AnnotationFileElementTypes#parseStubFiles()}; - *
  2. Ajava files, see {@link AnnotationFileElementTypes#parseAjavaFiles()}. - *
- * - *

If a type is annotated with a qualifier from the same hierarchy in more than one stub - * file, the qualifier in the last stub file is applied. - * - *

The annotations are stored by side-effecting {@link #stubTypes} and {@link #ajavaTypes}. - */ - protected void parseAnnotationFiles() { - stubTypes.parseStubFiles(); - ajavaTypes.parseAjavaFiles(); - } - - /** - * Returns all of the declaration annotations whose name equals the passed annotation class (or - * is an alias for it) including annotations: - * - *

    - *
  • on the element - *
  • written in stubfiles - *
  • inherited from overridden methods, (see {@link InheritedAnnotation}) - *
  • inherited from superclasses or super interfaces (see {@link Inherited}) - *
- * - * @see #getDeclAnnotationNoAliases - * @param elt the element to retrieve the declaration annotation from - * @param anno annotation class - * @return the annotation mirror for anno - */ - @Override - public final AnnotationMirror getDeclAnnotation(Element elt, Class anno) { - AnnotationMirror result = getDeclAnnotation(elt, anno, true); - return result; - } - - /** - * Returns the annotation mirror used to annotate this element, whose name equals the passed - * annotation class. Looks in the same places specified by {@link #getDeclAnnotation(Element, - * Class)}. Returns null if none exists. Does not check for aliases of the annotation class. - * - *

Call this method from a checker that needs to alias annotations for one purpose and not - * for another. For example, in the Lock Checker, {@code @LockingFree} and - * {@code @ReleasesNoLocks} are both aliases of {@code @SideEffectFree} since they are all - * considered side-effect-free with regard to the set of locks held before and after the method - * call. However, a {@code synchronized} block is permitted inside a {@code @ReleasesNoLocks} - * method but not inside a {@code @LockingFree} or {@code @SideEffectFree} method. - * - * @see #getDeclAnnotation - * @param elt the element to retrieve the declaration annotation from - * @param anno annotation class - * @return the annotation mirror for anno - */ - public final @Nullable AnnotationMirror getDeclAnnotationNoAliases( - Element elt, Class anno) { - return getDeclAnnotation(elt, anno, false); - } - - /** - * Returns true if the element appears in a stub file (Currently only works for methods, - * constructors, and fields). - */ - public boolean isFromStubFile(Element element) { - return this.getDeclAnnotation(element, FromStubFile.class) != null; - } - - /** - * Returns true if the element is from bytecode and the if the element did not appear in a stub - * file. Currently only works for methods, constructors, and fields. - */ - public boolean isFromByteCode(Element element) { - if (isFromStubFile(element)) { - return false; - } - return ElementUtils.isElementFromByteCode(element); - } - - /** - * Returns true if redundancy between a stub file and bytecode should be reported. - * - *

For most type systems the default behavior of returning true is correct. For subcheckers, - * redundancy in one of the type hierarchies can be ok. Such implementations should return - * false. - * - * @return whether to warn about redundancy between a stub file and bytecode - */ - public boolean shouldWarnIfStubRedundantWithBytecode() { - return true; - } - - /** - * Returns the actual annotation mirror used to annotate this element, whose name equals the - * passed annotation class (or is an alias for it). Looks in the same places specified by {@link - * #getDeclAnnotation(Element, Class)}. Returns null if none exists. May return the canonical - * annotation that annotationName is an alias for. - * - *

This is the private implementation of the same-named, public method. - * - *

An option is provided to not check for aliases of annotations. For example, an annotated - * type factory may use aliasing for a pair of annotations for convenience while needing in some - * cases to determine a strict ordering between them, such as when determining whether the - * annotations on an overrider method are more specific than the annotations of an overridden - * method. - * - * @param elt the element to retrieve the annotation from - * @param annoClass the class of the annotation to retrieve - * @param checkAliases whether to return an annotation mirror for an alias of the requested - * annotation class name - * @return the annotation mirror for the requested annotation, or null if not found - */ - private @Nullable AnnotationMirror getDeclAnnotation( - Element elt, Class annoClass, boolean checkAliases) { - return getDeclAnnotation(elt, annoClass.getCanonicalName(), checkAliases); - } - - /** - * Returns the actual annotation mirror used to annotate this element, whose name equals the - * passed canonical annotation name (or is an alias for it). Returns null if none exists. May - * return the canonical annotation that annotationName is an alias for. - * - *

An option is provided not to check for aliases of annotations. For example, an annotated - * type factory may use aliasing for a pair of annotations for convenience while needing in some - * cases to determine a strict ordering between them, such as when determining whether the - * annotations on an overrider method are more specific than the annotations of an overridden - * method. - * - * @param elt the element to retrieve the annotation from - * @param annoName the canonical annotation name to retrieve - * @param checkAliases whether to return an annotation mirror for an alias of the requested - * annotation class name - * @return the annotation mirror for the requested annotation, or null if not found - */ - private AnnotationMirror getDeclAnnotation( - Element elt, @FullyQualifiedName String annoName, boolean checkAliases) { - AnnotationMirrorSet declAnnos = getDeclAnnotations(elt); - - for (AnnotationMirror am : declAnnos) { - if (AnnotationUtils.areSameByName(am, annoName)) { - return am; - } - } - if (!checkAliases) { - return null; - } - // Look through aliases. - Map<@FullyQualifiedName String, AnnotationMirror> aliases = declAliases.get(annoName); - if (aliases == null) { - return null; - } - for (AnnotationMirror am : declAnnos) { - AnnotationMirror match = aliases.get(AnnotationUtils.annotationName(am)); - if (match != null) { - return match; - } - } - - // Not found. - return null; - } - - /** - * Returns all of the declaration annotations on this element including annotations: - * - *

    - *
  • on the element - *
  • written in stubfiles - *
  • inherited from overridden methods, (see {@link InheritedAnnotation}) - *
  • inherited from superclasses or super interfaces (see {@link Inherited}) - *
- * - *

This method returns the actual annotations not their aliases. {@link - * #getDeclAnnotation(Element, Class)} returns aliases. - * - * @param elt the element for which to determine annotations - * @return all of the declaration annotations on this element, written in stub files, or - * inherited - */ - public AnnotationMirrorSet getDeclAnnotations(Element elt) { - AnnotationMirrorSet cachedValue = cacheDeclAnnos.get(elt); - if (cachedValue != null) { - // Found in cache, return result. - return cachedValue; - } - - AnnotationMirrorSet results = new AnnotationMirrorSet(); - // Retrieving the annotations from the element. - // This includes annotations inherited from superclasses, but not superinterfaces or - // overridden methods. - List fromEle = elements.getAllAnnotationMirrors(elt); - for (AnnotationMirror annotation : fromEle) { - try { - results.add(annotation); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // If a CompletionFailure occurs, issue a warning. - checker.reportWarning( - annotation.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(elt), - annotation); - } - } - - // Add annotations from annotation files. - results.addAll(stubTypes.getDeclAnnotations(elt)); - results.addAll(ajavaTypes.getDeclAnnotations(elt)); - if (currentFileAjavaTypes != null) { - results.addAll(currentFileAjavaTypes.getDeclAnnotations(elt)); - } - - if (elt.getKind() == ElementKind.METHOD) { - // Retrieve the annotations from the overridden method's element. - inheritOverriddenDeclAnnos((ExecutableElement) elt, results); - } else if (ElementUtils.isTypeDeclaration(elt)) { - inheritOverriddenDeclAnnosFromTypeDecl(elt.asType(), results); - } - - // Add the element and its annotations to the cache. - if (!stubTypes.isParsing() - && !ajavaTypes.isParsing() - && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { - cacheDeclAnnos.put(elt, results); - } - return results; - } - - /** - * Adds into {@code results} the inherited declaration annotations found in all elements of the - * super types of {@code typeMirror}. (Both superclasses and superinterfaces.) - * - * @param typeMirror type - * @param results set of AnnotationMirrors to which this method adds declarations annotations - */ - private void inheritOverriddenDeclAnnosFromTypeDecl( - TypeMirror typeMirror, AnnotationMirrorSet results) { - List superTypes = types.directSupertypes(typeMirror); - for (TypeMirror superType : superTypes) { - TypeElement elt = TypesUtils.getTypeElement(superType); - if (elt == null) { - continue; - } - AnnotationMirrorSet superAnnos = getDeclAnnotations(elt); - for (AnnotationMirror annotation : superAnnos) { - List annotationsOnAnnotation; - try { - annotationsOnAnnotation = - annotation.getAnnotationType().asElement().getAnnotationMirrors(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. - checker.reportWarning( - annotation.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(elt), - annotation); - continue; - } - if (containsSameByClass(annotationsOnAnnotation, Inherited.class) - || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) { - addOrMerge(results, annotation); - } - } - } + } + + /** + * Determines the annotated type from a type in tree form. + * + *

Note that we cannot decide from a Tree whether it is a type use or an expression. + * TreeUtils.isTypeTree is only an under-approximation. For example, an identifier can be either a + * type or an expression. + * + * @param tree the type tree + * @return the annotated type of the type in the AST + */ + public AnnotatedTypeMirror getAnnotatedTypeFromTypeTree(Tree tree) { + if (tree == null) { + throw new BugInCF("AnnotatedTypeFactory.getAnnotatedTypeFromTypeTree: null tree"); + } + AnnotatedTypeMirror type = fromTypeTree(tree); + addComputedTypeAnnotations(tree, type); + return type; + } + + /** + * Returns the set of qualifiers that are the upper bounds for a use of the type. + * + *

For a specific type system, the type declaration bound is retrieved in the following + * precedence: (1) the annotation on the type declaration bound (2) if an annotation with + * {@code @UpperBoundFor} mentions the type or the type kind, use that annotation (3) the top + * annotation + * + * @param type a type whose upper bounds to obtain + * @return the set of qualifiers that are the upper bounds for a use of the type + */ + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { + return qualifierUpperBounds.getBoundQualifiers(type); + } + + /** + * Compare the given {@code annos} with the declaration bounds of {@code type} and return the + * appropriate qualifiers. For each qualifier in {@code annos}, if it is a subtype of the + * declaration bound in the same hierarchy, it will be added to the result; otherwise, the + * declaration bound will be added to the result instead. + * + * @param type java type that specifies the qualifier upper bound + * @param annos a set of qualifiers to be compared with the declaration bounds of {@code type} + * @return the modified {@code annos} after applying the rules described above + */ + public AnnotationMirrorSet getAnnotationOrTypeDeclarationBound( + TypeMirror type, Set annos) { + AnnotationMirrorSet boundAnnos = getTypeDeclarationBounds(type); + AnnotationMirrorSet results = new AnnotationMirrorSet(); + + for (AnnotationMirror anno : annos) { + AnnotationMirror boundAnno = qualHierarchy.findAnnotationInSameHierarchy(boundAnnos, anno); + assert boundAnno != null; + + if (!qualHierarchy.isSubtypeQualifiersOnly(anno, boundAnno)) { + results.add(boundAnno); + } else { + results.add(anno); + } } - - /** - * Adds into {@code results} the declaration annotations found in all elements that the method - * element {@code elt} overrides. - * - * @param elt method element - * @param results {@code elt} local declaration annotations. The ones found in stub files and in - * the element itself. - */ - private void inheritOverriddenDeclAnnos(ExecutableElement elt, AnnotationMirrorSet results) { - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, this, elt); - - if (overriddenMethods != null) { - for (ExecutableElement superElt : overriddenMethods.values()) { - AnnotationMirrorSet superAnnos = getDeclAnnotations(superElt); - - for (AnnotationMirror annotation : superAnnos) { - List annotationsOnAnnotation; - try { - annotationsOnAnnotation = - annotation.getAnnotationType().asElement().getAnnotationMirrors(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. - checker.reportWarning( - annotation.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(elt), - annotation); - continue; - } - if (containsSameByClass(annotationsOnAnnotation, InheritedAnnotation.class) - || AnnotationUtils.containsSameByName( - inheritedAnnotations, annotation)) { - addOrMerge(results, annotation); - } - } - } - } + return results; + } + + /** + * Returns the set of qualifiers that are the upper bound for a type use if no other bound is + * specified for the type. + * + *

This implementation returns the top qualifiers by default. Subclass may override to return + * different qualifiers. + * + * @return the set of qualifiers that are the upper bound for a type use if no other bound is + * specified for the type + */ + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + return qualHierarchy.getTopAnnotations(); + } + + /** + * Returns the type of the extends or implements clause. + * + *

The primary qualifier is either an explicit annotation on {@code clause}, or it is the + * qualifier upper bounds for uses of the type of the clause. + * + * @param clause tree that represents an extends or implements clause + * @return the type of the extends or implements clause + */ + public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { + AnnotatedTypeMirror fromTypeTree = fromTypeTree(clause); + AnnotationMirrorSet bound = getTypeDeclarationBounds(fromTypeTree.getUnderlyingType()); + fromTypeTree.addMissingAnnotations(bound); + addComputedTypeAnnotations(clause, fromTypeTree); + return fromTypeTree; + } + + // ********************************************************************** + // Factories for annotated types that do not account for default qualifiers. + // They only include qualifiers explicitly inserted by the user. + // ********************************************************************** + + /** + * Creates an AnnotatedTypeMirror for {@code elt} that includes: annotations explicitly written on + * the element and annotations from stub files. + * + *

Does not include default qualifiers. To obtain them, use {@link #getAnnotatedType(Element)}. + * + *

Does not include fake overrides from the stub file. + * + * @param elt the element + * @return AnnotatedTypeMirror of the element with explicitly-written and stub file annotations + */ + public AnnotatedTypeMirror fromElement(Element elt) { + if (shouldCache && elementCache.containsKey(elt)) { + return elementCache.get(elt).deepCopy(); + } + if (elt.getKind() == ElementKind.PACKAGE) { + return toAnnotatedType(elt.asType(), false); + } + AnnotatedTypeMirror type; + + // Because of a bug in Java 8, annotations on type parameters are not stored in elements, so + // get explicit annotations from the tree. (This bug has been fixed in Java 9.) Also, since + // annotations computed by the AnnotatedTypeFactory are stored in the element, the + // annotations have to be retrieved from the tree so that only explicit annotations are + // returned. + Tree decl = declarationFromElement(elt); + + if (decl == null) { + type = stubTypes.getAnnotatedTypeMirror(elt); + if (type == null) { + type = toAnnotatedType(elt.asType(), ElementUtils.isTypeDeclaration(elt)); + ElementAnnotationApplier.apply(type, elt, this); + } + } else if (decl instanceof ClassTree) { + type = fromClass((ClassTree) decl); + } else if (decl instanceof VariableTree) { + type = fromMember(decl); + } else if (decl instanceof MethodTree) { + type = fromMember(decl); + } else if (decl.getKind() == Tree.Kind.TYPE_PARAMETER) { + type = fromTypeTree(decl); + } else { + throw new BugInCF( + "AnnotatedTypeFactory.fromElement: cannot be here. decl: " + + decl.getKind() + + " elt: " + + elt); + } + + type = mergeAnnotationFileAnnosIntoType(type, elt, ajavaTypes); + if (currentFileAjavaTypes != null) { + type = mergeAnnotationFileAnnosIntoType(type, elt, currentFileAjavaTypes); + } + + if (mergeStubsWithSource) { + if (debugStubParser) { + System.out.printf("fromElement: mergeStubsIntoType(%s, %s)", type, elt); + } + type = mergeAnnotationFileAnnosIntoType(type, elt, stubTypes); + if (debugStubParser) { + System.out.printf(" => %s%n", type); + } } - - private void addOrMerge(AnnotationMirrorSet results, AnnotationMirror annotation) { - if (AnnotationUtils.containsSameByName(results, annotation)) { - /* - * TODO: feature request: figure out a way to merge multiple annotations - * of the same kind. For some annotations this might mean merging some - * arrays, for others it might mean converting a single annotation into a - * container annotation. We should define a protected method for subclasses - * to adapt the behavior. - * For now, do nothing and just take the first, most concrete, annotation. - AnnotationMirror prev = null; - for (AnnotationMirror an : results) { - if (AnnotationUtils.areSameByName(an, annotation)) { - prev = an; - break; - } - } - results.remove(prev); - AnnotationMirror merged = ...; - results.add(merged); - */ - } else { - results.add(annotation); - } + // Caching is disabled if annotation files are being parsed, because calls to this + // method before the annotation files are fully read can return incorrect results. + if (shouldCache + && !stubTypes.isParsing() + && !ajavaTypes.isParsing() + && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { + elementCache.put(elt, type.deepCopy()); + } + return type; + } + + /** + * Returns an AnnotatedDeclaredType with explicit annotations from the ClassTree {@code tree}. + * + * @param tree the class declaration + * @return AnnotatedDeclaredType with explicit annotations from {@code tree} + */ + private AnnotatedDeclaredType fromClass(ClassTree tree) { + return TypeFromTree.fromClassTree(this, tree); + } + + /** + * Creates an AnnotatedTypeMirror for a variable or method declaration tree. The + * AnnotatedTypeMirror contains annotations explicitly written on the tree, and possibly others as + * described below. + * + *

If a VariableTree is a parameter to a lambda, this method also adds annotations from the + * declared type of the functional interface and the executable type of its method. + * + *

The returned AnnotatedTypeMirror also contains explicitly written annotations from any ajava + * file and if {@code -AmergeStubsWithSource} is passed, it also merges any explicitly written + * annotations from stub files. + * + * @param tree a {@link MethodTree} or {@link VariableTree} + * @return AnnotatedTypeMirror with explicit annotations from {@code tree} + */ + private AnnotatedTypeMirror fromMember(Tree tree) { + if (!(tree instanceof MethodTree || tree instanceof VariableTree)) { + throw new BugInCF( + "AnnotatedTypeFactory.fromMember: not a method or variable declaration: " + tree); + } + if (shouldCache && fromMemberTreeCache.containsKey(tree)) { + return fromMemberTreeCache.get(tree).deepCopy(); + } + AnnotatedTypeMirror result = TypeFromTree.fromMember(this, tree); + + result = mergeAnnotationFileAnnosIntoType(result, tree, ajavaTypes); + if (currentFileAjavaTypes != null) { + result = mergeAnnotationFileAnnosIntoType(result, tree, currentFileAjavaTypes); + } + + if (mergeStubsWithSource) { + if (debugStubParser) { + System.out.printf("fromClass: mergeStubsIntoType(%s, %s)", result, tree); + } + result = mergeAnnotationFileAnnosIntoType(result, tree, stubTypes); + if (debugStubParser) { + System.out.printf(" => %s%n", result); + } } - /** - * Returns a list of all declaration annotations used to annotate the element, which have a - * meta-annotation (i.e., an annotation on that annotation) with class {@code - * metaAnnotationClass}. - * - * @param element the element for which to determine annotations - * @param metaAnnotationClass the class of the meta-annotation that needs to be present - * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror - * at {@code element}, and {@code metaAnno} is the annotation mirror (of type {@code - * metaAnnotationClass}) used to meta-annotate the declaration of {@code anno} - */ - public List> getDeclAnnotationWithMetaAnnotation( - Element element, Class metaAnnotationClass) { - List> result = new ArrayList<>(); - AnnotationMirrorSet annotationMirrors = getDeclAnnotations(element); - - for (AnnotationMirror candidate : annotationMirrors) { - List metaAnnotationsOnAnnotation; - try { - metaAnnotationsOnAnnotation = - candidate.getAnnotationType().asElement().getAnnotationMirrors(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Fix for Issue 309: If a CompletionFailure occurs, issue a warning. - // I didn't find a nicer alternative to check whether the Symbol can be completed. - // The completer field of a Symbol might be non-null also in successful cases. - // Issue a warning (exception only happens once) and continue. - checker.reportWarning( - candidate.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(element), - candidate); - continue; - } - // First call copier, if exception, continue normal modula laws. - for (AnnotationMirror ma : metaAnnotationsOnAnnotation) { - if (areSameByClass(ma, metaAnnotationClass)) { - // This candidate has the right kind of meta-annotation. - // It might be a real contract, or a list of contracts. - if (isListForRepeatedAnnotation(candidate)) { - @SuppressWarnings("deprecation") // concrete annotation class is not known - List wrappedCandidates = - AnnotationUtils.getElementValueArray( - candidate, "value", AnnotationMirror.class, false); - for (AnnotationMirror wrappedCandidate : wrappedCandidates) { - result.add(IPair.of(wrappedCandidate, ma)); - } - } else { - result.add(IPair.of(candidate, ma)); - } - } - } - } - return result; + if (shouldCache) { + fromMemberTreeCache.put(tree, result.deepCopy()); + } + + return result; + } + + /** + * Merges types from annotation files for {@code tree} into {@code type} by taking the greatest + * lower bound of the annotations in both. + * + * @param type the type to apply annotation file types to + * @param tree the tree from which to read annotation file types + * @param source storage for current annotation file annotations + * @return the given type, side-effected to add the annotation file types + */ + private AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( + @Nullable AnnotatedTypeMirror type, Tree tree, AnnotationFileElementTypes source) { + Element elt = TreeUtils.elementFromTree(tree); + return mergeAnnotationFileAnnosIntoType(type, elt, source); + } + + /** + * A scanner used to combine annotations from two AnnotatedTypeMirrors. The scanner requires + * {@link #qualHierarchy}, which is set in {@link #postInit()} rather than the construtor, so + * lazily initialize this field before use. + */ + private @MonotonicNonNull AnnotatedTypeCombiner annotatedTypeCombiner = null; + + /** + * Merges types from annotation files for {@code elt} into {@code type} by taking the greatest + * lower bound of the annotations in both. + * + * @param type the type to apply annotation file types to + * @param elt the element from which to read annotation file types + * @param source storage for current annotation file annotations + * @return the type, side-effected to add the annotation file types + */ + protected AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( + @Nullable AnnotatedTypeMirror type, Element elt, AnnotationFileElementTypes source) { + AnnotatedTypeMirror typeFromFile = source.getAnnotatedTypeMirror(elt); + if (typeFromFile == null) { + return type; + } + if (type == null) { + return typeFromFile; + } + if (annotatedTypeCombiner == null) { + annotatedTypeCombiner = new AnnotatedTypeCombiner(qualHierarchy); + } + // Must merge (rather than only take the annotation file type if it is a subtype) to support + // WPI. + annotatedTypeCombiner.visit(typeFromFile, type); + return type; + } + + /** + * Creates an AnnotatedTypeMirror for an ExpressionTree. The AnnotatedTypeMirror contains explicit + * annotations written on the expression and for some expressions, annotations from + * sub-expressions that could have been explicitly written, defaulted, refined, or otherwise + * computed. (Expression whose type include annotations from sub-expressions are: ArrayAccessTree, + * ConditionalExpressionTree, IdentifierTree, MemberSelectTree, and MethodInvocationTree.) + * + *

For example, the AnnotatedTypeMirror returned for an array access expression is the fully + * annotated type of the array component of the array being accessed. + * + * @param tree an expression + * @return AnnotatedTypeMirror of the expressions either fully-annotated or partially annotated + * depending on the kind of expression + * @see TypeFromExpressionVisitor + */ + private AnnotatedTypeMirror fromExpression(ExpressionTree tree) { + if (shouldCache && fromExpressionTreeCache.containsKey(tree)) { + return fromExpressionTreeCache.get(tree).deepCopy(); + } + + AnnotatedTypeMirror result = TypeFromTree.fromExpression(this, tree); + + if (shouldCache + && tree.getKind() != Tree.Kind.NEW_CLASS + && tree.getKind() != Tree.Kind.NEW_ARRAY + && tree.getKind() != Tree.Kind.CONDITIONAL_EXPRESSION) { + // Don't cache the type of some expressions, because incorrect annotations would be + // cached during dataflow analysis. See Issue #602. + fromExpressionTreeCache.put(tree, result.deepCopy()); + } + return result; + } + + /** + * Creates an AnnotatedTypeMirror for the tree. The AnnotatedTypeMirror contains annotations + * explicitly written on the tree. It also adds type arguments to raw types that include + * annotations from the element declaration of the type {@link #fromElement(Element)}. + * + *

Called on the following trees: AnnotatedTypeTree, ArrayTypeTree, ParameterizedTypeTree, + * PrimitiveTypeTree, TypeParameterTree, WildcardTree, UnionType, IntersectionTypeTree, and + * IdentifierTree, MemberSelectTree. + * + * @param tree the type tree + * @return the (partially) annotated type of the type in the AST + */ + /*package-private*/ final AnnotatedTypeMirror fromTypeTree(Tree tree) { + if (shouldCache && fromTypeTreeCache.containsKey(tree)) { + return fromTypeTreeCache.get(tree).deepCopy(); + } + + AnnotatedTypeMirror result = TypeFromTree.fromTypeTree(this, tree); + + if (shouldCache) { + fromTypeTreeCache.put(tree, result.deepCopy()); + } + return result; + } + + // ********************************************************************** + // Customization methods meant to be overridden by subclasses to include + // defaulted annotations + // ********************************************************************** + + /** + * Changes annotations on a type obtained from a {@link Tree}. By default, this method does + * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting and inference + * (flow-sensitive type refinement). Its subclasses usually override it only to customize default + * annotations. + * + *

Subclasses that override this method should also override {@link + * #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)}. + * + * @param tree an AST node + * @param type the type obtained from {@code tree} + */ + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + // Pass. + } + + /** + * Changes annotations on a type obtained from an {@link Element}. By default, this method does + * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting. + * + *

Subclasses that override this method should also override {@link + * #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. + * + * @param elt an element + * @param type the type obtained from {@code elt} + */ + protected void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + // Pass. + } + + /** + * Adds default annotations to {@code type}. This method should only be used in places where the + * correct annotations cannot be computed because of uninferred type arguments. (See {@link + * AnnotatedWildcardType#isUninferredTypeArgument()}.) + * + * @param type annotated type to which default annotations are added + */ + public void addDefaultAnnotations(AnnotatedTypeMirror type) { + // Pass. + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize directSupertypes(). + * Overriding methods should merely change the annotations on the supertypes, without adding or + * removing new types. + * + *

The default provided implementation adds {@code type} annotations to {@code supertypes}. + * This allows the {@code type} and its supertypes to have the qualifiers. + * + * @param type the type whose supertypes are desired + * @param supertypes the supertypes as specified by the base AnnotatedTypeFactory + */ + protected void postDirectSuperTypes( + AnnotatedTypeMirror type, List supertypes) { + // Use the effective annotations here to get the correct annotations + // for type variables and wildcards. + AnnotationMirrorSet annotations = type.getEffectiveAnnotations(); + for (AnnotatedTypeMirror supertype : supertypes) { + if (!annotations.equals(supertype.getEffectiveAnnotations())) { + supertype.clearAnnotations(); + // TODO: is this correct for type variables and wildcards? + supertype.addAnnotations(annotations); + } } - - /** Cache for {@link #isListForRepeatedAnnotation}. */ - private final Map isListForRepeatedAnnotationCache = new HashMap<>(); - - /** - * Returns true if the given annotation is a wrapper for multiple repeated annotations. - * - * @param a an annotation that might be a wrapper - * @return true if the argument is a wrapper for multiple repeated annotations - */ - private boolean isListForRepeatedAnnotation(AnnotationMirror a) { - DeclaredType annotationType = a.getAnnotationType(); - Boolean resultObject = isListForRepeatedAnnotationCache.get(annotationType); - if (resultObject != null) { - return resultObject; - } - boolean result = isListForRepeatedAnnotationImplementation(annotationType); - isListForRepeatedAnnotationCache.put(annotationType, result); - return result; + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize + * AnnotatedTypes.asMemberOf(). Overriding methods should merely change the annotations on the + * subtypes, without changing the types. + * + * @param type the annotated type of the element + * @param owner the annotated type of the receiver of the accessing tree + * @param element the element of the field or method + */ + public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { + if (element.getKind() == ElementKind.FIELD) { + addAnnotationFromFieldInvariant(type, owner, (VariableElement) element); + } + addComputedTypeAnnotations(element, type); + + if (viewpointAdapter != null && type.getKind() != TypeKind.EXECUTABLE) { + viewpointAdapter.viewpointAdaptMember(owner, element, type); + } + } + + /** + * Adds the qualifier specified by a field invariant for {@code field} to {@code type}. + * + * @param type annotated type to which the annotation is added + * @param accessedVia the annotated type of the receiver of the accessing tree. (Only used to get + * the type element of the underling type.) + * @param field element representing the field + */ + protected void addAnnotationFromFieldInvariant( + AnnotatedTypeMirror type, AnnotatedTypeMirror accessedVia, VariableElement field) { + TypeMirror declaringType = accessedVia.getUnderlyingType(); + // Find the first upper bound that isn't a wildcard or type variable + while (declaringType.getKind() == TypeKind.WILDCARD + || declaringType.getKind() == TypeKind.TYPEVAR) { + if (declaringType.getKind() == TypeKind.WILDCARD) { + declaringType = TypesUtils.wildUpperBound(declaringType, processingEnv); + } else if (declaringType.getKind() == TypeKind.TYPEVAR) { + declaringType = ((TypeVariable) declaringType).getUpperBound(); + } } - - /** - * Returns true if the annotation is a wrapper for multiple repeated annotations. - * - * @param annotationType the declaration of the annotation to test - * @return true if the annotation is a wrapper for multiple repeated annotations - */ - private boolean isListForRepeatedAnnotationImplementation(DeclaredType annotationType) { - TypeMirror enclosingType = annotationType.getEnclosingType(); - if (enclosingType == null) { - return false; - } - if (!annotationType.asElement().getSimpleName().contentEquals("List")) { - return false; - } - List annoElements = annotationType.asElement().getEnclosedElements(); - if (annoElements.size() != 1) { - return false; - } - // TODO: should check that the type of the single element is: "array of enclosingType". - return true; + TypeElement typeElement = TypesUtils.getTypeElement(declaringType); + if (ElementUtils.enclosingTypeElement(field).equals(typeElement)) { + // If the field is declared in the accessedVia class, then the field in the invariant + // cannot be this field, even if the field has the same name. + return; + } + + FieldInvariants invariants = getFieldInvariants(typeElement); + if (invariants == null) { + return; + } + List invariantAnnos = invariants.getQualifiersFor(field.getSimpleName()); + type.replaceAnnotations(invariantAnnos); + } + + /** + * Returns the field invariants for the given class, as expressed by the user in {@link + * FieldInvariant @FieldInvariant} method annotations. + * + *

Subclasses may implement their own field invariant annotations if {@link + * FieldInvariant @FieldInvariant} is not expressive enough. They must override this method to + * properly create AnnotationMirror and also override {@link + * #getFieldInvariantDeclarationAnnotations()} to return their field invariants. + * + * @param element class for which to get invariants + * @return field invariants for {@code element} + */ + public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { + if (element == null) { + return null; + } + AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, FieldInvariant.class); + if (fieldInvarAnno == null) { + return null; + } + List fields = + AnnotationUtils.getElementValueArray( + fieldInvarAnno, fieldInvariantFieldElement, String.class); + List<@CanonicalName Name> classes = + AnnotationUtils.getElementValueClassNames(fieldInvarAnno, fieldInvariantQualifierElement); + List qualifiers = + CollectionsPlume.mapList( + name -> + // Calling AnnotationBuilder.fromName (which ignores + // elements/fields) is acceptable because @FieldInvariant + // does not handle classes with elements/fields. + AnnotationBuilder.fromName(elements, name), + classes); + if (qualifiers.size() == 1) { + while (fields.size() > qualifiers.size()) { + qualifiers.add(qualifiers.get(0)); + } } - - /** - * Returns a list of all annotations used to annotate this element, which have a meta-annotation - * (i.e., an annotation on that annotation) with class {@code metaAnnotationClass}. - * - * @param element the element at which to look for annotations - * @param metaAnnotationClass the class of the meta-annotation that needs to be present - * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror - * at {@code element}, and {@code metaAnno} is the annotation mirror used to annotate {@code - * anno}. - */ - public List> getAnnotationWithMetaAnnotation( - Element element, Class metaAnnotationClass) { - AnnotationMirrorSet annotationMirrors = new AnnotationMirrorSet(); - // Consider real annotations. - annotationMirrors.addAll(getAnnotatedType(element).getAnnotations()); - // Consider declaration annotations - annotationMirrors.addAll(getDeclAnnotations(element)); - - List> result = new ArrayList<>(); - - // Go through all annotations found. - for (AnnotationMirror annotation : annotationMirrors) { - List annotationsOnAnnotation = - annotation.getAnnotationType().asElement().getAnnotationMirrors(); - for (AnnotationMirror a : annotationsOnAnnotation) { - if (areSameByClass(a, metaAnnotationClass)) { - result.add(IPair.of(annotation, a)); - } - } - } - return result; + if (fields.size() != qualifiers.size()) { + // The user wrote a malformed @FieldInvariant annotation, so just return a malformed + // FieldInvariants object. The BaseTypeVisitor will issue an error. + return new FieldInvariants(fields, qualifiers, this); } - /** - * Whether or not the {@code annotatedTypeMirror} has a qualifier parameter. - * - * @param annotatedTypeMirror the type to check - * @param top the top of the hierarchy to check - * @return true if the type has a qualifier parameter - */ - public boolean hasQualifierParameterInHierarchy( - AnnotatedTypeMirror annotatedTypeMirror, AnnotationMirror top) { - return AnnotationUtils.containsSame( - getQualifierParameterHierarchies(annotatedTypeMirror), top); + // Only keep qualifiers that are supported by this checker. (The other qualifiers cannot + // be checked by this checker, so they must be ignored.) + List annotatedFields = new ArrayList<>(); + List supportedQualifiers = new ArrayList<>(); + for (int i = 0; i < fields.size(); i++) { + if (isSupportedQualifier(qualifiers.get(i))) { + annotatedFields.add(fields.get(i)); + supportedQualifiers.add(qualifiers.get(i)); + } } - - /** - * Whether or not the {@code element} has a qualifier parameter. - * - * @param element element to check - * @param top the top of the hierarchy to check - * @return true if the type has a qualifier parameter - */ - public boolean hasQualifierParameterInHierarchy( - @Nullable Element element, AnnotationMirror top) { - if (element == null) { - return false; + if (annotatedFields.isEmpty()) { + return null; + } + + return new FieldInvariants(annotatedFields, supportedQualifiers, this); + } + + /** + * Returns the element of {@code annoTrees} that is a use of one of the field invariant + * annotations (as specified via {@link #getFieldInvariantDeclarationAnnotations()}. If one isn't + * found, null is returned. + * + * @param annoTrees list of trees to search; the result is one of the list elements, or null + * @return the AnnotationTree that is a use of one of the field invariant annotations, or null if + * one isn't found + */ + public @Nullable AnnotationTree getFieldInvariantAnnotationTree( + @Nullable List annoTrees) { + List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); + for (int i = 0; i < annos.size(); i++) { + for (Class clazz : getFieldInvariantDeclarationAnnotations()) { + if (areSameByClass(annos.get(i), clazz)) { + return annoTrees.get(i); } - return AnnotationUtils.containsSame(getQualifierParameterHierarchies(element), top); + } } - - /** - * Returns whether the {@code HasQualifierParameter} annotation was explicitly written on {@code - * element} for the hierarchy given by {@code top}. - * - * @param element the Element to check - * @param top the top qualifier for the hierarchy to check - * @return whether the class given by {@code element} has been explicitly annotated with {@code - * HasQualifierParameter} for the given hierarchy - */ - public boolean hasExplicitQualifierParameterInHierarchy(Element element, AnnotationMirror top) { - return AnnotationUtils.containsSame( - getSupportedAnnotationsInElementAnnotation( - element, HasQualifierParameter.class, hasQualifierParameterValueElement), - top); + return null; + } + + /** The classes of field invariant annotations. */ + private final Set> fieldInvariantDeclarationAnnotations = + Collections.singleton(FieldInvariant.class); + + /** + * Returns the set of classes of field invariant annotations. + * + * @return the set of classes of field invariant annotations + */ + protected Set> getFieldInvariantDeclarationAnnotations() { + return fieldInvariantDeclarationAnnotations; + } + + /** + * Adapt the upper bounds of the type variables of a class relative to the type instantiation. In + * some type systems, the upper bounds depend on the instantiation of the class. For example, in + * the Generic Universe Type system, consider a class declaration + * + *

{@code   class C }
+ * + * then the instantiation + * + *
{@code   @Rep C<@Rep Object> }
+ * + * is legal. The upper bounds of class C have to be adapted by the main modifier. + * + *

An example of an adaptation follows. Suppose, I have a declaration: + * + *

{@code  class MyClass>}
+ * + * And an instantiation: + * + *
{@code  new MyClass<@NonNull String>()}
+ * + *

The upper bound of E adapted to the argument String, would be {@code List<@NonNull String>} + * and the lower bound would be an AnnotatedNullType. + * + *

TODO: ensure that this method is consistently used instead of directly querying the type + * variables. + * + * @param type the use of the type + * @param element the corresponding element + * @return the adapted bounds of the type parameters + */ + public List typeVariablesFromUse( + AnnotatedDeclaredType type, TypeElement element) { + AnnotatedDeclaredType generic = getAnnotatedType(element); + List targs = type.getTypeArguments(); + List tvars = generic.getTypeArguments(); + + assert targs.size() == tvars.size() + : "Mismatch in type argument size between " + type + " and " + generic; + + // System.err.printf("TVFU%n type: %s%n generic: %s%n", type, generic); + + Map typeParamToTypeArg = new HashMap<>(); + + AnnotatedDeclaredType enclosing = type; + while (enclosing != null) { + List enclosingTArgs = enclosing.getTypeArguments(); + AnnotatedDeclaredType declaredType = + getAnnotatedType((TypeElement) enclosing.getUnderlyingType().asElement()); + List enclosingTVars = declaredType.getTypeArguments(); + for (int i = 0; i < enclosingTArgs.size(); i++) { + AnnotatedTypeVariable enclosingTVar = (AnnotatedTypeVariable) enclosingTVars.get(i); + typeParamToTypeArg.put(enclosingTVar.getUnderlyingType(), enclosingTArgs.get(i)); + } + enclosing = enclosing.getEnclosingType(); + } + + List res = new ArrayList<>(tvars.size()); + + for (AnnotatedTypeMirror atm : tvars) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) atm; + AnnotatedTypeMirror upper = + typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getUpperBound()); + AnnotatedTypeMirror lower = + typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getLowerBound()); + res.add(new AnnotatedTypeParameterBounds(upper, lower)); + } + + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptTypeParameterBounds(type, res); + } + return res; + } + + /** + * Creates and returns an AnnotatedNullType qualified with {@code annotations}. + * + * @param annotations set of AnnotationMirrors to qualify the returned type with + * @return AnnotatedNullType qualified with {@code annotations} + */ + public AnnotatedNullType getAnnotatedNullType(Set annotations) { + AnnotatedTypeMirror.AnnotatedNullType nullType = + (AnnotatedNullType) toAnnotatedType(processingEnv.getTypeUtils().getNullType(), false); + nullType.addAnnotations(annotations); + return nullType; + } + + // ********************************************************************** + // Utilities method for getting specific types from trees or elements + // ********************************************************************** + + /** + * Return the implicit receiver type of an expression tree. + * + *

The result is null for expressions that don't have a receiver, e.g. for a local variable or + * method parameter access. The result is also null for expressions that have an explicit + * receiver. + * + *

Clients should generally call {@link #getReceiverType}. + * + * @param tree the expression that might have an implicit receiver + * @return the type of the implicit receiver. Returns null if the expression has an explicit + * receiver or doesn't have a receiver. + */ + protected @Nullable AnnotatedDeclaredType getImplicitReceiverType(ExpressionTree tree) { + assert (tree.getKind() == Tree.Kind.IDENTIFIER + || tree.getKind() == Tree.Kind.MEMBER_SELECT + || tree.getKind() == Tree.Kind.METHOD_INVOCATION + || tree.getKind() == Tree.Kind.NEW_CLASS) + : "Unexpected tree kind: " + tree.getKind(); + + // Return null if the element kind has no receiver. + Element element = TreeUtils.elementFromUse(tree); + assert element != null : "Unexpected null element for tree: " + tree; + if (!ElementUtils.hasReceiver(element)) { + return null; + } + + // Return null if the receiver is explicit. + if (TreeUtils.getReceiverTree(tree) != null) { + return null; + } + + TypeElement elementOfImplicitReceiver = ElementUtils.enclosingTypeElement(element); + if (tree.getKind() == Tree.Kind.NEW_CLASS) { + if (elementOfImplicitReceiver.getEnclosingElement() != null) { + elementOfImplicitReceiver = + ElementUtils.enclosingTypeElement(elementOfImplicitReceiver.getEnclosingElement()); + } else { + elementOfImplicitReceiver = null; + } + if (elementOfImplicitReceiver == null) { + // If the typeElt does not have an enclosing class, then the NewClassTree + // does not have an implicit receiver. + return null; + } } - /** - * Returns whether the {@code NoQualifierParameter} annotation was explicitly written on {@code - * element} for the hierarchy given by {@code top}. - * - * @param element the Element to check - * @param top the top qualifier for the hierarchy to check - * @return whether the class given by {@code element} has been explicitly annotated with {@code - * NoQualifierParameter} for the given hierarchy - */ - public boolean hasExplicitNoQualifierParameterInHierarchy( - Element element, AnnotationMirror top) { - return AnnotationUtils.containsSame( - getSupportedAnnotationsInElementAnnotation( - element, NoQualifierParameter.class, noQualifierParameterValueElement), - top); + TypeMirror typeOfImplicitReceiver = elementOfImplicitReceiver.asType(); + AnnotatedDeclaredType thisType = getSelfType(tree); + if (thisType == null) { + return null; + } + // An implicit receiver is the first enclosing type that is a subtype of the type where the + // element is declared. + while (thisType != null && !isSubtype(thisType.getUnderlyingType(), typeOfImplicitReceiver)) { + thisType = thisType.getEnclosingType(); + } + return thisType; + } + + /** + * Returns the type of {@code this} at the location of {@code tree}. Returns {@code null} if + * {@code tree} is in a location where {@code this} has no meaning, such as the body of a static + * method. + * + *

The parameter is an arbitrary tree and does not have to mention "this", neither explicitly + * nor implicitly. This method can be overridden for type-system specific behavior. + * + * @param tree location used to decide the type of {@code this} + * @return the type of {@code this} at the location of {@code tree} + */ + public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { + if (TreeUtils.isClassTree(tree)) { + return getAnnotatedType(TreeUtils.elementFromDeclaration((ClassTree) tree)); + } + + Tree enclosingTree = getEnclosingClassOrMethod(tree); + if (enclosingTree == null) { + // tree is inside an annotation, where "this" is not allowed. So, no self type exists. + return null; + } else if (enclosingTree.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = (MethodTree) enclosingTree; + if (TreeUtils.isConstructor(enclosingMethod)) { + return (AnnotatedDeclaredType) getAnnotatedType(enclosingMethod).getReturnType(); + } else { + return getAnnotatedType(enclosingMethod).getReceiverType(); + } + } else if (TreeUtils.isClassTree(enclosingTree)) { + return (AnnotatedDeclaredType) getAnnotatedType(enclosingTree); + } + return null; + } + + /** A set containing class, method, and annotation tree kinds. */ + private static final Set classMethodAnnotationKinds = + EnumSet.copyOf(TreeUtils.classTreeKinds()); + + static { + classMethodAnnotationKinds.add(Tree.Kind.METHOD); + classMethodAnnotationKinds.add(Tree.Kind.TYPE_ANNOTATION); + classMethodAnnotationKinds.add(Tree.Kind.ANNOTATION); + } + + /** + * Returns the innermost enclosing method or class tree of {@code tree}. Since artificial trees + * are assigned to be the child node of the original tree, their enclosing trees are found the + * same way as normal trees. + * + *

If the tree is inside an annotation, then {@code null} is returned. + * + * @param tree tree to whose innermost enclosing method or class to return + * @return the innermost enclosing method or class tree of {@code tree}, or {@code null} if {@code + * tree} is inside an annotation + */ + public @Nullable Tree getEnclosingClassOrMethod(Tree tree) { + TreePath path = getPath(tree); + Tree enclosing = TreePathUtil.enclosingOfKind(path, classMethodAnnotationKinds); + if (enclosing != null) { + if (enclosing.getKind() == Tree.Kind.ANNOTATION + || enclosing.getKind() == Tree.Kind.TYPE_ANNOTATION) { + return null; + } + return enclosing; + } + + return TreePathUtil.enclosingClass(path); + } + + /** + * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} + * that is the same type as {@code typeElement}. + * + * @param typeElement type of the enclosing type to return + * @param tree location to use + * @return the enclosing type at the location of {@code tree} that is the same type as {@code + * typeElement} + */ + public AnnotatedDeclaredType getEnclosingType(TypeElement typeElement, Tree tree) { + AnnotatedDeclaredType thisType = getSelfType(tree); + while (!isSameType(thisType.getUnderlyingType(), typeElement.asType())) { + thisType = thisType.getEnclosingType(); + } + return thisType; + } + + /** + * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} + * that is a subtype of {@code typeElement}. + * + * @param typeElement super type of the enclosing type to return + * @param tree location to use + * @return the enclosing type at the location of {@code tree} that is a subtype of {@code + * typeElement} + */ + public AnnotatedDeclaredType getEnclosingSubType(TypeElement typeElement, Tree tree) { + AnnotatedDeclaredType thisType = getSelfType(tree); + while (!isSubtype(thisType.getUnderlyingType(), typeElement.asType())) { + thisType = thisType.getEnclosingType(); + } + return thisType; + } + + /** + * Returns true if the erasure of {@code type1} is a Java subtype of the erasure of {@code type2}. + * + * @param type1 a type + * @param type2 a type + * @return true if the erasure of {@code type1} is a Java subtype of the erasure of {@code type2} + */ + private boolean isSubtype(TypeMirror type1, TypeMirror type2) { + return types.isSubtype(types.erasure(type1), types.erasure(type2)); + } + + /** + * Returns true if the erasure of {@code type1} is the same Java type as the erasure of {@code + * type2}. + * + * @param type1 a type + * @param type2 a type + * @return true if the erasure of {@code type1} is the same Java type as the erasure of {@code + * type2} + */ + private boolean isSameType(TypeMirror type1, TypeMirror type2) { + return types.isSameType(types.erasure(type1), types.erasure(type2)); + } + + /** + * Returns the receiver type of the expression tree, which might be the type of an implicit {@code + * this}. Returns null if the expression has no explicit or implicit receiver. + * + * @param expression the expression for which to determine the receiver type + * @return the type of the receiver of expression + */ + public final @Nullable AnnotatedTypeMirror getReceiverType(ExpressionTree expression) { + AnnotatedTypeMirror receiverType; + ExpressionTree receiver = TreeUtils.getReceiverTree(expression); + if (receiver != null) { + receiverType = getAnnotatedType(receiver); + } else { + Element element = TreeUtils.elementFromTree(expression); + if (element != null && ElementUtils.hasReceiver(element)) { + // The tree references an element that has a receiver, but the tree does not have an + // explicit receiver. So, the tree must have an implicit receiver of "this" or + // "Outer.this". + receiverType = getImplicitReceiverType(expression); + } else { + receiverType = null; + } } - - /** - * Returns the set of top annotations representing all the hierarchies for which this type has a - * qualifier parameter. - * - * @param annotatedType the type to check - * @return the set of top annotations representing all the hierarchies for which this type has a - * qualifier parameter - */ - public AnnotationMirrorSet getQualifierParameterHierarchies(AnnotatedTypeMirror annotatedType) { - while (annotatedType.getKind() == TypeKind.TYPEVAR - || annotatedType.getKind() == TypeKind.WILDCARD) { - if (annotatedType.getKind() == TypeKind.TYPEVAR) { - annotatedType = ((AnnotatedTypeVariable) annotatedType).getUpperBound(); - } else if (annotatedType.getKind() == TypeKind.WILDCARD) { - annotatedType = ((AnnotatedWildcardType) annotatedType).getSuperBound(); - } - } - - if (annotatedType.getKind() != TypeKind.DECLARED) { - return AnnotationMirrorSet.emptySet(); - } - - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) annotatedType; - Element element = declaredType.getUnderlyingType().asElement(); - if (element == null) { - return AnnotationMirrorSet.emptySet(); - } - return getQualifierParameterHierarchies(element); + // In Java versions below 11, consider the following code: + // class Outer { + // class Inner{} + // } + // class Top { + // void test(Outer outer) { + // outer.new Inner(){}; + // } + // } + // the receiverType of outer.new Inner(){} is Top instead of Outer, + // because Java below 11 organizes newClassTree of an anonymous class in a different + // way: there is a synthetic argument representing the enclosing expression type. + // In such case, use the synthetic argument as its receiver type. + if ((expression instanceof NewClassTree) + && TreeUtils.hasSyntheticArgument((NewClassTree) expression)) { + receiverType = getAnnotatedType(((NewClassTree) expression).getArguments().get(0)); + } + return receiverType; + } + + /** The type for an instantiated generic method or constructor. */ + public static class ParameterizedExecutableType { + /** The method's/constructor's type. */ + public final AnnotatedExecutableType executableType; + + /** The types of the generic type arguments. */ + public final List typeArgs; + + /** Create a ParameterizedExecutableType. */ + public ParameterizedExecutableType( + AnnotatedExecutableType executableType, List typeArgs) { + this.executableType = executableType; + this.typeArgs = typeArgs; } - /** - * Returns the set of top annotations representing all the hierarchies for which this element - * has a qualifier parameter. - * - * @param element the Element to check - * @return the set of top annotations representing all the hierarchies for which this element - * has a qualifier parameter - */ - public AnnotationMirrorSet getQualifierParameterHierarchies(Element element) { - if (!ElementUtils.isTypeDeclaration(element)) { - return AnnotationMirrorSet.emptySet(); - } - - AnnotationMirrorSet found = new AnnotationMirrorSet(); - found.addAll( - getSupportedAnnotationsInElementAnnotation( - element, HasQualifierParameter.class, hasQualifierParameterValueElement)); - AnnotationMirrorSet hasQualifierParameterTops = new AnnotationMirrorSet(); - PackageElement packageElement = ElementUtils.enclosingPackage(element); - - // Traverse all packages containing this element. - while (packageElement != null) { - AnnotationMirrorSet packageDefaultTops = - getSupportedAnnotationsInElementAnnotation( - packageElement, - HasQualifierParameter.class, - hasQualifierParameterValueElement); - hasQualifierParameterTops.addAll(packageDefaultTops); - - packageElement = ElementUtils.parentPackage(packageElement, elements); - } - - AnnotationMirrorSet noQualifierParamClasses = - getSupportedAnnotationsInElementAnnotation( - element, NoQualifierParameter.class, noQualifierParameterValueElement); - for (AnnotationMirror anno : hasQualifierParameterTops) { - if (!AnnotationUtils.containsSame(noQualifierParamClasses, anno)) { - found.add(anno); - } - } - - return found; + @Override + public String toString() { + if (typeArgs.isEmpty()) { + return executableType.toString(); + } else { + StringJoiner typeArgsString = new StringJoiner(",", "<", ">"); + for (AnnotatedTypeMirror atm : typeArgs) { + typeArgsString.add(atm.toString()); + } + return typeArgsString + " " + executableType.toString(); + } } - - /** - * Returns a set of supported annotation mirrors corresponding to the annotation classes listed - * in the value element of an annotation with class {@code annoClass} on {@code element}. - * - * @param element the Element to check - * @param annoClass the class for an annotation that's written on elements, whose value element - * is a list of annotation classes. It is always HasQualifierParameter or - * NoQualifierParameter - * @param valueElement the {@code value} field/element of an annotation with class {@code - * annoClass} - * @return the set of supported annotations with classes listed in the value element of an - * annotation with class {@code annoClass} on the {@code element}. Returns an empty set if - * {@code annoClass} is not written on {@code element} or {@code element} is null. - */ - private AnnotationMirrorSet getSupportedAnnotationsInElementAnnotation( - @Nullable Element element, - Class annoClass, - ExecutableElement valueElement) { - if (element == null) { - return AnnotationMirrorSet.emptySet(); - } - // TODO: caching - AnnotationMirror annotation = getDeclAnnotation(element, annoClass); - if (annotation == null) { - return AnnotationMirrorSet.emptySet(); - } - - AnnotationMirrorSet found = new AnnotationMirrorSet(); - List<@CanonicalName Name> qualClasses = - AnnotationUtils.getElementValueClassNames(annotation, valueElement); - for (Name qual : qualClasses) { - AnnotationMirror annotationMirror = AnnotationBuilder.fromName(elements, qual); - if (isSupportedQualifier(annotationMirror)) { - found.add(annotationMirror); - } - } - return found; + } + + /** + * Determines the type of the invoked method based on the passed method invocation tree. + * + *

The returned method type has all type variables resolved, whether based on receiver type, + * passed type parameters if any, and method invocation parameter. + * + *

Subclasses may override this method to customize inference of types or qualifiers based on + * method invocation parameters. + * + *

As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types, + * AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type + * should be in accordance to its specification. + * + *

The return type is a pair of the type of the invoked method and the (inferred) type + * arguments. Note that neither the explicitly passed nor the inferred type arguments are + * guaranteed to be subtypes of the corresponding upper bounds. See method {@link + * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type + * argument well-formedness. + * + *

Note that "this" and "super" constructor invocations are also handled by this method + * (explicit or implicit ones, at the beginning of a constructor). Method {@link + * #constructorFromUse(NewClassTree)} is only used for a constructor invocation in a "new" + * expression. + * + * @param tree the method invocation tree + * @return the method type being invoked with tree and the (inferred) type arguments + */ + public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + AnnotatedTypeMirror receiverType = getReceiverType(tree); + if (receiverType == null + && (TreeUtils.isSuperConstructorCall(tree) || TreeUtils.isThisConstructorCall(tree))) { + // super() and this() calls don't have a receiver, but they should be view-point adapted + // as if "this" is the receiver. + receiverType = getSelfType(tree); + } + if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) { + receiverType = applyCaptureConversion(receiverType); + } + + ParameterizedExecutableType result = methodFromUse(tree, methodElt, receiverType); + if (checker.shouldResolveReflection() + && reflectionResolver.isReflectiveMethodInvocation(tree)) { + result = reflectionResolver.resolveReflectiveCall(this, tree, result); + } + + AnnotatedExecutableType method = result.executableType; + if (method.getReturnType().getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) method.getReturnType()).isUninferredTypeArgument()) { + // Get the correct Java type from the tree and use it as the upper bound of the + // wildcard. + TypeMirror tm = TreeUtils.typeOf(tree); + AnnotatedTypeMirror t = toAnnotatedType(tm, false); + + AnnotatedWildcardType wildcard = (AnnotatedWildcardType) method.getReturnType(); + if (ignoreUninferredTypeArguments) { + // Remove the annotations so that default annotations are used instead. + // (See call to addDefaultAnnotations below.) + t.clearAnnotations(); + } else { + t.replaceAnnotations(wildcard.getExtendsBound().getAnnotations()); + } + wildcard.setExtendsBound(t); + addDefaultAnnotations(wildcard); + } + + // Store varargType before calling setParameterTypes, otherwise we may lose the varargType + // as it is the last element of the original parameterTypes. + method.computeVarargType(); + // Adapt parameters, which makes parameters and arguments be the same size for later + // checking. + List parameters = + AnnotatedTypes.adaptParameters(this, method, tree.getArguments(), null); + method.setParameterTypes(parameters); + return result; + } + + /** + * Determines the type of the invoked method based on the passed expression tree, executable + * element, and receiver type. + * + * @param tree either a MethodInvocationTree or a MemberReferenceTree + * @param methodElt the element of the referenced method + * @param receiverType the type of the receiver + * @return the method type being invoked with tree and the (inferred) type arguments + * @see #methodFromUse(MethodInvocationTree) + */ + public ParameterizedExecutableType methodFromUse( + ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { + AnnotatedExecutableType memberTypeWithoutOverrides = + getAnnotatedType(methodElt); // get unsubstituted type + AnnotatedExecutableType memberTypeWithOverrides = + applyFakeOverrides(receiverType, methodElt, memberTypeWithoutOverrides); + memberTypeWithOverrides = applyRecordTypesToAccessors(methodElt, memberTypeWithOverrides); + methodFromUsePreSubstitution(tree, memberTypeWithOverrides); + + // Perform viewpoint adaption before type argument substitution. + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptMethod(receiverType, methodElt, memberTypeWithOverrides); + } + + AnnotatedExecutableType methodType = + AnnotatedTypes.asMemberOf(types, this, receiverType, methodElt, memberTypeWithOverrides); + List typeargs = new ArrayList<>(methodType.getTypeVariables().size()); + + Map typeParamToTypeArg = + AnnotatedTypes.findTypeArguments(processingEnv, this, tree, methodElt, methodType); + + if (!typeParamToTypeArg.isEmpty()) { + typeParamToTypeArg = + captureMethodTypeArgs(typeParamToTypeArg, memberTypeWithOverrides.getTypeVariables()); + for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { + if (typeParamToTypeArg.get(tv.getUnderlyingType()) == null) { + throw new BugInCF( + "AnnotatedTypeFactory.methodFromUse: mismatch between" + + " declared method type variables and the inferred method type arguments." + + " Method type variables: " + + methodType.getTypeVariables() + + "; " + + "Inferred method type arguments: " + + typeParamToTypeArg); + } + typeargs.add(typeParamToTypeArg.get(tv.getUnderlyingType())); + } + methodType = + (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, methodType); + } + + if (tree.getKind() == Tree.Kind.METHOD_INVOCATION + && TreeUtils.isMethodInvocation(tree, objectGetClass, processingEnv)) { + adaptGetClassReturnTypeToReceiver(methodType, receiverType, tree); + } + + methodType.setReturnType(applyCaptureConversion(methodType.getReturnType())); + return new ParameterizedExecutableType(methodType, typeargs); + } + + /** + * Apply capture conversion to the type arguments of a method invocation. + * + * @param typeVarToAnnotatedTypeArg mapping from type variable in the method declaration to the + * corresponding (annotated) type argument at the method invocation + * @param declTypeVar list of the (annotated) type variable declarations in the method + * @return a mapping from type variable in the method declaration to its captured type argument. + * Its keys are the same as in {@code typeVarToAnnotatedTypeArg}, and the values are their + * captures (for a non-wildcard, capture conversion is the identity). + */ + // TODO: This should happen as part of Java 8 inference and this method should be removed when + // #979 is fixed. + private Map captureMethodTypeArgs( + Map typeVarToAnnotatedTypeArg, + List declTypeVar) { + Map typeParameter = new HashMap<>(); + for (AnnotatedTypeVariable t : declTypeVar) { + typeParameter.put(t.getUnderlyingType(), t); + } + // `newTypeVarToAnnotatedTypeArg` is the result of this method. + Map newTypeVarToAnnotatedTypeArg = new HashMap<>(); + Map capturedTypeVarToAnnotatedTypeVar = new HashMap<>(); + + // The first loop replaces each wildcard by a fresh type variable. + for (Map.Entry entry : + typeVarToAnnotatedTypeArg.entrySet()) { + TypeVariable typeVariable = entry.getKey(); + AnnotatedTypeMirror originalTypeArg = entry.getValue(); + if (originalTypeArg.containsUninferredTypeArguments()) { + // Don't capture uninferred type arguments; return the argument. + return typeVarToAnnotatedTypeArg; + } + if (originalTypeArg.getKind() == TypeKind.WILDCARD) { + TypeMirror cap = + TypesUtils.freshTypeVariable(originalTypeArg.getUnderlyingType(), processingEnv); + AnnotatedTypeMirror capturedArg = AnnotatedTypeMirror.createType(cap, this, false); + newTypeVarToAnnotatedTypeArg.put(typeVariable, capturedArg); + capturedTypeVarToAnnotatedTypeVar.put( + (TypeVariable) cap, (AnnotatedTypeVariable) capturedArg); + } else { + newTypeVarToAnnotatedTypeArg.put(typeVariable, originalTypeArg); + } } - /** - * A scanner that replaces annotations in one type with annotations from another. Used by {@link - * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror)} and {@link - * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. - */ - private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); - - /** - * Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code - * from} will be used everywhere they exist, but annotations in {@code to} will be kept anywhere - * that {@code from} is unannotated. - * - * @param from the annotated type mirror from which to take new annotations - * @param to the annotated type mirror to which the annotations will be added - */ - public void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - annotatedTypeReplacer.visit(from, to); + // The second loop captures: it side-effects the new type variables. + List order = TypesUtils.order(typeVarToAnnotatedTypeArg.keySet(), types); + for (TypeVariable typeVariable : order) { + AnnotatedTypeMirror originalTypeArg = typeVarToAnnotatedTypeArg.get(typeVariable); + AnnotatedTypeMirror newTypeArg = newTypeVarToAnnotatedTypeArg.get(typeVariable); + if (TypesUtils.isCapturedTypeVariable(newTypeArg.getUnderlyingType()) + && originalTypeArg.getKind() == TypeKind.WILDCARD) { + annotateCapturedTypeVar( + newTypeVarToAnnotatedTypeArg, + capturedTypeVarToAnnotatedTypeVar, + (AnnotatedWildcardType) originalTypeArg, + typeParameter.get(typeVariable), + (AnnotatedTypeVariable) newTypeArg); + } } - - /** - * Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}. - * Annotations from {@code from} will be used everywhere they exist, but annotations in {@code - * to} will be kept anywhere that {@code from} is unannotated. - * - * @param from the annotated type mirror from which to take new annotations - * @param to the annotated type mirror to which the annotations will be added - * @param top the top type of the hierarchy whose annotations will be added - */ - public void replaceAnnotations( - AnnotatedTypeMirror from, AnnotatedTypeMirror to, AnnotationMirror top) { - annotatedTypeReplacer.setTop(top); - annotatedTypeReplacer.visit(from, to); - annotatedTypeReplacer.setTop(null); - } - - /** The implementation of the visitor for #containsUninferredTypeArguments. */ - private final SimpleAnnotatedTypeScanner uninferredTypeArgumentScanner = - new SimpleAnnotatedTypeScanner<>( - (type, p) -> - type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument(), - Boolean::logicalOr, - false); - - /** - * Returns whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType. - * - * @param type type to check - * @return whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient - */ - public boolean containsUninferredTypeArguments(AnnotatedTypeMirror type) { - return uninferredTypeArgumentScanner.visit(type); + return newTypeVarToAnnotatedTypeArg; + } + + /** + * Given a member and its type, returns the type with fake overrides applied to it. + * + * @param receiverType the type of the class that contains member (or a subtype of it) + * @param member a type member, such as a method or field + * @param memberType the type of {@code member} + * @return {@code memberType}, adjusted according to fake overrides + */ + private AnnotatedExecutableType applyFakeOverrides( + AnnotatedTypeMirror receiverType, Element member, AnnotatedExecutableType memberType) { + // Currently, handle only methods, not fields. TODO: Handle fields. + if (memberType.getKind() != TypeKind.EXECUTABLE) { + return memberType; + } + + AnnotationFileElementTypes afet = stubTypes; + AnnotatedExecutableType methodType = afet.getFakeOverride(member, receiverType); + if (methodType == null) { + methodType = memberType; + } + return methodType; + } + + /** + * Given a method, checks if there is: a record component with the same name AND the record + * component has an annotation AND the method has no-arguments. If so, replaces the annotations on + * the method return type with those from the record type in the same hierarchy. + * + * @param member a method or constructor + * @param memberType the type of the method/constructor; side-effected by this method + * @return {@code memberType} with annotations replaced if applicable + */ + private AnnotatedExecutableType applyRecordTypesToAccessors( + ExecutableElement member, AnnotatedExecutableType memberType) { + if (memberType.getKind() != TypeKind.EXECUTABLE) { + throw new BugInCF( + "member %s has type %s of kind %s", member, memberType, memberType.getKind()); + } + + stubTypes.injectRecordComponentType(types, member, memberType); + + return memberType; + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the + * declared method type before type variable substitution. + * + * @param tree either a method invocation or a member reference tree + * @param type declared method type before type variable substitution + */ + protected void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { + assert tree instanceof MethodInvocationTree || tree instanceof MemberReferenceTree; + } + + /** + * Java special-cases the return type of {@link java.lang.Class#getClass() getClass()}. Though the + * method has a return type of {@code Class}, the compiler special cases this return-type and + * changes the bound of the type argument to the erasure of the receiver type. For example: + * + *

    + *
  • {@code x.getClass()} has the type {@code Class< ? extends erasure_of_x >} + *
  • {@code someInteger.getClass()} has the type {@code Class< ? extends Integer >} + *
+ * + * @param getClassType this must be a type representing a call to Object.getClass otherwise a + * runtime exception will be thrown. It is modified by side effect. + * @param receiverType the receiver type of the method invocation (not the declared receiver type) + * @param tree getClass method invocation tree + */ + protected void adaptGetClassReturnTypeToReceiver( + AnnotatedExecutableType getClassType, AnnotatedTypeMirror receiverType, ExpressionTree tree) { + TypeMirror type = TreeUtils.typeOf(tree); + AnnotatedTypeMirror returnType = AnnotatedTypeMirror.createType(type, this, false); + + if (returnType == null + || !(returnType.getKind() == TypeKind.DECLARED) + || ((AnnotatedDeclaredType) returnType).getTypeArguments().size() != 1) { + throw new BugInCF( + "Unexpected type passed to AnnotatedTypes.adaptGetClassReturnTypeToReceiver%n" + + "getClassType=%s%nreceiverType=%s", + getClassType, receiverType); + } + + AnnotatedWildcardType classWildcardArg = + (AnnotatedWildcardType) + ((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0); + getClassType.setReturnType(returnType); + + // Usually, the only locations that will add annotations to the return type are getClass in + // stub files defaults and propagation tree annotator. Since getClass is final they cannot + // come from source code. Also, since the newBound is an erased type we have no type + // arguments. So, we just copy the annotations from the bound of the declared type to the + // new bound. + AnnotationMirrorSet newAnnos = new AnnotationMirrorSet(); + AnnotationMirrorSet receiverTypeBoundAnnos = + getTypeDeclarationBounds(receiverType.getErased().getUnderlyingType()); + AnnotationMirrorSet wildcardBoundAnnos = classWildcardArg.getEffectiveAnnotations(); + for (AnnotationMirror receiverTypeBoundAnno : receiverTypeBoundAnnos) { + AnnotationMirror wildcardAnno = + qualHierarchy.findAnnotationInSameHierarchy(wildcardBoundAnnos, receiverTypeBoundAnno); + if (typeHierarchy.isSubtypeShallowEffective(receiverTypeBoundAnno, classWildcardArg)) { + newAnnos.add(receiverTypeBoundAnno); + } else { + newAnnos.add(wildcardAnno); + } } - - /** - * Returns a wildcard type to be used as a type argument when the correct type could not be - * inferred. The wildcard will be marked as an uninferred wildcard so that {@link - * AnnotatedWildcardType#isUninferredTypeArgument()} returns true. - * - *

This method should only be used by type argument inference. - * org.checkerframework.framework.util.AnnotatedTypes.inferTypeArguments(ProcessingEnvironment, - * AnnotatedTypeFactory, ExpressionTree, ExecutableElement) - * - * @param typeVar the TypeVariable that could not be inferred - * @return a wildcard that is marked as an uninferred type argument - */ - public AnnotatedWildcardType getUninferredWildcardType(AnnotatedTypeVariable typeVar) { - final boolean intersectionType; - final TypeMirror boundType; - if (typeVar.getUpperBound().getKind() == TypeKind.INTERSECTION) { - boundType = typeVar.getUpperBound().directSupertypes().get(0).getUnderlyingType(); - intersectionType = true; - } else { - boundType = typeVar.getUnderlyingType().getUpperBound(); - intersectionType = false; - } - - WildcardType wc = types.getWildcardType(boundType, null); - AnnotatedWildcardType wctype = - (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wc, this, false); - wctype.setTypeVariable(typeVar.getUnderlyingType()); - if (!intersectionType) { - wctype.setExtendsBound(typeVar.getUpperBound().deepCopy()); + AnnotatedTypeMirror newTypeArg = + ((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0); + ((AnnotatedTypeVariable) newTypeArg).getUpperBound().replaceAnnotations(newAnnos); + } + + /** + * Return the element type of {@code expression}. This is usually the type of {@code + * expression.itertor().next()}. If {@code expression} is an array, it is the component type of + * the array. + * + * @param expression an expression whose type is an array or implements {@link Iterable} + * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, + * the component type of the array. + */ + public AnnotatedTypeMirror getIterableElementType(ExpressionTree expression) { + return getIterableElementType(expression, getAnnotatedType(expression)); + } + + /** + * Return the element type of {@code iterableType}. This is usually the type of {@code + * expression.itertor().next()}. If {@code expression} is an array, it is the component type of + * the array. + * + * @param expression an expression whose type is an array or implements {@link Iterable} + * @param iterableType the type of the expression + * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, + * the component type of the array. + */ + protected AnnotatedTypeMirror getIterableElementType( + ExpressionTree expression, AnnotatedTypeMirror iterableType) { + switch (iterableType.getKind()) { + case ARRAY: + return ((AnnotatedArrayType) iterableType).getComponentType(); + case WILDCARD: + return getIterableElementType( + expression, ((AnnotatedWildcardType) iterableType).getExtendsBound().deepCopy()); + case TYPEVAR: + return getIterableElementType( + expression, ((AnnotatedTypeVariable) iterableType).getUpperBound()); + case DECLARED: + AnnotatedDeclaredType dt = + AnnotatedTypes.asSuper(this, iterableType, this.iterableDeclType); + if (dt.getTypeArguments().isEmpty()) { + TypeElement e = ElementUtils.getTypeElement(processingEnv, Object.class); + return getAnnotatedType(e); } else { - wctype.getExtendsBound().addAnnotations(typeVar.getUpperBound().getAnnotations()); - } - wctype.setSuperBound(typeVar.getLowerBound().deepCopy()); - wctype.addAnnotations(typeVar.getAnnotations()); - addDefaultAnnotations(wctype); - wctype.setUninferredTypeArgument(); - return wctype; - } - - /** - * Returns the function type that this member reference targets. - * - *

The function type is the type of the single method declared in the functional interface - * adapted as if it were invoked using the functional interface as the receiver expression. - * - *

The target type of a member reference is the type to which it is assigned or casted. - * - * @param tree member reference tree - * @return the function type that this method reference targets - */ - public AnnotatedExecutableType getFunctionTypeFromTree(MemberReferenceTree tree) { - return getFnInterfaceFromTree(tree).second; - } - - /** - * Returns the function type that this lambda targets. - * - *

The function type is the type of the single method declared in the functional interface - * adapted as if it were invoked using the functional interface as the receiver expression. - * - *

The target type of a lambda is the type to which it is assigned or casted. - * - * @param tree lambda expression tree - * @return the function type that this lambda targets - */ - public AnnotatedExecutableType getFunctionTypeFromTree(LambdaExpressionTree tree) { - return getFnInterfaceFromTree(tree).second; - } - - /** - * Returns the functional interface and the function type that this lambda or member references - * targets. - * - *

The function type is the type of the single method declared in the functional interface - * adapted as if it were invoked using the functional interface as the receiver expression. - * - *

The target type of a lambda or a method reference is the type to which it is assigned or - * casted. - * - * @param tree lambda expression tree or member reference tree - * @return the functional interface and the function type that this method reference or lambda - * targets - */ - public IPair getFnInterfaceFromTree(Tree tree) { - // Functional interface - AnnotatedTypeMirror functionalInterfaceType = getFunctionalInterfaceType(tree); - if (functionalInterfaceType.getKind() == TypeKind.DECLARED) { - functionalInterfaceType = - makeGroundTargetType( - (AnnotatedDeclaredType) functionalInterfaceType, - (DeclaredType) TreeUtils.typeOf(tree)); - } - - // Functional method - ExecutableElement fnElement = TreeUtils.findFunction(tree, processingEnv); - - // Function type - AnnotatedExecutableType functionType = - AnnotatedTypes.asMemberOf(types, this, functionalInterfaceType, fnElement); - return IPair.of(functionalInterfaceType, functionType); - } - - /** - * Get the AnnotatedDeclaredType for the FunctionalInterface from assignment context of the - * method reference or lambda expression which may be a variable assignment, a method call, or a - * cast. - * - *

The assignment context is not always correct, so we must search up the AST. It will - * recursively search for lambdas nested in lambdas. - * - * @param tree the tree of the lambda or method reference - * @return the functional interface type or an uninferred type argument - */ - private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) { - TreePath parentPath = getPath(tree).getParentPath(); - Tree parentTree = parentPath.getLeaf(); - switch (parentTree.getKind()) { - case PARENTHESIZED: - return getFunctionalInterfaceType(parentTree); - - case TYPE_CAST: - TypeCastTree cast = (TypeCastTree) parentTree; - assertIsFunctionalInterface( - trees.getTypeMirror(getPath(cast.getType())), parentTree, tree); - AnnotatedTypeMirror castATM = getAnnotatedType(cast.getType()); - if (castATM.getKind() == TypeKind.INTERSECTION) { - AnnotatedIntersectionType itype = (AnnotatedIntersectionType) castATM; - for (AnnotatedTypeMirror t : itype.directSupertypes()) { - if (TypesUtils.isFunctionalInterface( - t.getUnderlyingType(), getProcessingEnv())) { - return t; - } - } - // We should never reach here: isFunctionalInterface performs the same check - // and would have raised an error already. - throw new BugInCF( - "Expected the type of a cast tree in an assignment context to contain" - + " a functional interface bound." - + " Found type: %s for tree: %s in lambda tree: %s", - castATM, cast, tree); - } - return castATM; - - case NEW_CLASS: - NewClassTree newClass = (NewClassTree) parentTree; - int indexOfLambda = newClass.getArguments().indexOf(tree); - ParameterizedExecutableType con = this.constructorFromUse(newClass); - AnnotatedTypeMirror constructorParam = - AnnotatedTypes.getAnnotatedTypeMirrorOfParameter( - con.executableType, indexOfLambda); - assertIsFunctionalInterface(constructorParam.getUnderlyingType(), parentTree, tree); - return constructorParam; - - case NEW_ARRAY: - NewArrayTree newArray = (NewArrayTree) parentTree; - AnnotatedArrayType newArrayATM = getAnnotatedType(newArray); - AnnotatedTypeMirror elementATM = newArrayATM.getComponentType(); - assertIsFunctionalInterface(elementATM.getUnderlyingType(), parentTree, tree); - return elementATM; - - case METHOD_INVOCATION: - MethodInvocationTree method = (MethodInvocationTree) parentTree; - int index = method.getArguments().indexOf(tree); - ParameterizedExecutableType exe = this.methodFromUse(method); - AnnotatedTypeMirror param = - AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(exe.executableType, index); - if (param.getKind() == TypeKind.WILDCARD) { - // param is an uninferred wildcard. - TypeMirror typeMirror = TreeUtils.typeOf(tree); - param = AnnotatedTypeMirror.createType(typeMirror, this, false); - addDefaultAnnotations(param); - } - assertIsFunctionalInterface(param.getUnderlyingType(), parentTree, tree); - return param; - - case VARIABLE: - VariableTree varTree = (VariableTree) parentTree; - assertIsFunctionalInterface(TreeUtils.typeOf(varTree), parentTree, tree); - return getAnnotatedType(varTree.getType()); - - case ASSIGNMENT: - AssignmentTree assignmentTree = (AssignmentTree) parentTree; - assertIsFunctionalInterface(TreeUtils.typeOf(assignmentTree), parentTree, tree); - return getAnnotatedType(assignmentTree.getVariable()); - - case RETURN: - Tree enclosing = - TreePathUtil.enclosingOfKind( - getPath(parentTree), - new HashSet<>( - Arrays.asList( - Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION))); - - if (enclosing.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = (MethodTree) enclosing; - return getAnnotatedType(enclosingMethod.getReturnType()); - } else { - LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) enclosing; - AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); - return methodExe.getReturnType(); - } - - case LAMBDA_EXPRESSION: - LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) parentTree; - AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); - return methodExe.getReturnType(); - - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree conditionalExpressionTree = - (ConditionalExpressionTree) parentTree; - AnnotatedTypeMirror trueType = - getAnnotatedType(conditionalExpressionTree.getTrueExpression()); - AnnotatedTypeMirror falseType = - getAnnotatedType(conditionalExpressionTree.getFalseExpression()); - - // Known cases where we must use LUB because falseType/trueType will not be equal: - // a) when one of the types is a type variable that extends a functional interface - // or extends a type variable that extends a functional interface - // b) When one of the two sides of the expression is a reference to a sub-interface. - // e.g. interface ConsumeStr { - // public void consume(String s) - // } - // interface SubConsumer extends ConsumeStr { - // default void someOtherMethod() { ... } - // } - // SubConsumer s = ...; - // ConsumeStr stringConsumer = (someCondition) ? s : System.out::println; - AnnotatedTypeMirror conditionalType = - AnnotatedTypes.leastUpperBound(this, trueType, falseType); - assertIsFunctionalInterface(conditionalType.getUnderlyingType(), parentTree, tree); - return conditionalType; - case CASE: - // Get the functional interface type of the whole switch expression. - Tree switchTree = parentPath.getParentPath().getLeaf(); - return getFunctionalInterfaceType(switchTree); - - default: - if (parentTree.getKind().toString().equals("YIELD")) { - TreePath pathToCase = TreePathUtil.pathTillOfKind(parentPath, Kind.CASE); - return getFunctionalInterfaceType(pathToCase.getParentPath().getLeaf()); - } - throw new BugInCF( - "Could not find functional interface from assignment context. " - + "Unexpected tree type: " - + parentTree.getKind() - + " For lambda tree: " - + tree); - } - } - - /** - * Throws an exception if the type is not a funtional interface. - * - * @param typeMirror a type that must be a funtional interface - * @param contextTree the tree that has the given type; used only for diagnostic messages - * @param tree a labmba tree that encloses {@code contextTree}; used only for diagnostic - * messages - */ - private void assertIsFunctionalInterface(TypeMirror typeMirror, Tree contextTree, Tree tree) { - if (typeMirror.getKind() == TypeKind.WILDCARD) { - // Ignore wildcards, because they are uninferred type arguments. - return; - } - Type type = (Type) typeMirror; - if (TypesUtils.isFunctionalInterface(type, processingEnv)) { - return; - } - - if (type.getKind() == TypeKind.INTERSECTION) { - IntersectionType itype = (IntersectionType) type; - for (TypeMirror t : itype.getBounds()) { - if (TypesUtils.isFunctionalInterface(t, processingEnv)) { - // As long as any of the bounds is a functional interface, we should be fine. - return; - } - } - } - + return dt.getTypeArguments().get(0); + } + + // TODO: Properly desugar Iterator.next(), which is needed if an annotated JDK has + // annotations on Iterator#next. + // The below doesn't work because methodFromUse() assumes that the expression tree + // matches the method element. + // TypeElement iteratorElement = + // ElementUtils.getTypeElement(processingEnv, Iterator.class); + // AnnotatedTypeMirror iteratorType = + // AnnotatedTypeMirror.createType(iteratorElement.asType(), this, false); + // Map mapping = new HashMap<>(); + // mapping.put( + // (TypeVariable) iteratorElement.getTypeParameters().get(0).asType(), + // typeArg); + // iteratorType = typeVarSubstitutor.substitute(mapping, iteratorType); + // ExecutableElement next = + // TreeUtils.getMethod("java.util.Iterator", "next", 0, processingEnv); + // ParameterizedExecutableType m = methodFromUse(expression, next, iteratorType); + // return m.executableType.getReturnType(); + default: throw new BugInCF( - "Expected the type of %s tree in assignment context to be a functional interface. " - + "Found type: %s for tree: %s in lambda tree: %s", - contextTree.getKind(), type, contextTree, tree); - } - - /** - * Create the ground target type of the functional interface. - * - *

Basically, it replaces the wildcards with their bounds doing a capture conversion like glb - * for extends bounds. - * - * @see "JLS 9.9" - * @param functionalType the functional interface type - * @param groundTargetJavaType the Java type as found by javac - * @return the grounded functional type - */ - private AnnotatedDeclaredType makeGroundTargetType( - AnnotatedDeclaredType functionalType, DeclaredType groundTargetJavaType) { - if (functionalType.getTypeArguments().isEmpty()) { - return functionalType; - } - - List bounds = - this.typeVariablesFromUse( - functionalType, - (TypeElement) functionalType.getUnderlyingType().asElement()); - - boolean sizesDiffer = - functionalType.getTypeArguments().size() - != groundTargetJavaType.getTypeArguments().size(); - - // This is the declared type of the functional type meaning that the type arguments are the - // type parameters. - DeclaredType declaredType = - (DeclaredType) functionalType.getUnderlyingType().asElement().asType(); - Map typeVarToTypeArg = - new HashMap<>(functionalType.getTypeArguments().size()); - for (int i = 0; i < functionalType.getTypeArguments().size(); i++) { - TypeVariable typeVariable = (TypeVariable) declaredType.getTypeArguments().get(i); - AnnotatedTypeMirror argType = functionalType.getTypeArguments().get(i); - - if (argType.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) argType; - - TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType(); - - if (wildcardType.isUninferredTypeArgument()) { - // Keep the uninferred type so that it is ignored by later subtyping and - // containment checks. - typeVarToTypeArg.put(typeVariable, wildcardType); - } else if (isExtendsWildcard(wildcardType)) { - TypeMirror correctArgType; - if (sizesDiffer) { - // The Java type is raw. - TypeMirror typeParamUbType = - bounds.get(i).getUpperBound().getUnderlyingType(); - correctArgType = - TypesUtils.greatestLowerBound( - typeParamUbType, - wildcardUbType, - this.checker.getProcessingEnvironment()); - } else { - correctArgType = groundTargetJavaType.getTypeArguments().get(i); - } - - final AnnotatedTypeMirror newArg; - if (types.isSameType(wildcardUbType, correctArgType)) { - newArg = wildcardType.getExtendsBound().deepCopy(); - } else if (correctArgType.getKind() == TypeKind.TYPEVAR) { - newArg = this.toAnnotatedType(correctArgType, false); - AnnotatedTypeVariable newArgAsTypeVar = (AnnotatedTypeVariable) newArg; - newArgAsTypeVar - .getUpperBound() - .replaceAnnotations( - wildcardType.getExtendsBound().getAnnotations()); - newArgAsTypeVar - .getLowerBound() - .replaceAnnotations(wildcardType.getSuperBound().getAnnotations()); - } else { - newArg = this.toAnnotatedType(correctArgType, false); - newArg.replaceAnnotations(wildcardType.getExtendsBound().getAnnotations()); - } - - typeVarToTypeArg.put(typeVariable, newArg); - } else { - typeVarToTypeArg.put(typeVariable, wildcardType.getSuperBound()); - } - } else { - typeVarToTypeArg.put(typeVariable, argType); - } - } - - // The ground functional type must be created using type variable substitution or else the - // underlying type will not match the annotated type. - AnnotatedDeclaredType groundFunctionalType = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType( - declaredType, this, functionalType.isDeclaration()); - initializeAtm(groundFunctionalType); - groundFunctionalType = - (AnnotatedDeclaredType) - getTypeVarSubstitutor().substitute(typeVarToTypeArg, groundFunctionalType); - groundFunctionalType.addAnnotations(functionalType.getAnnotations()); - - // When the groundTargetJavaType is different from the underlying type of functionalType, - // only the main annotations are copied. Add default annotations in places without - // annotations. - addDefaultAnnotations(groundFunctionalType); - return groundFunctionalType; - } - - /** - * Return true if {@code type} should be captured. - * - *

{@code type} should be captured if all of the following are true: - * - *

    - *
  • {@code type} and {@code typeMirror} are both declared types. - *
  • {@code type} does not have an uninferred type argument and its underlying type is not - * raw. - *
  • {@code type} has a wildcard as a type argument and {@code typeMirror} has a captured - * type variable as the corresponding type argument. - *
- * - * @param type annotated type that might need to be captured - * @param typeMirror the capture of the underlying type of {@code type} - * @return true if {@code type} should be captured - */ - private boolean shouldCapture(AnnotatedTypeMirror type, TypeMirror typeMirror) { - if (type.getKind() != TypeKind.DECLARED || typeMirror.getKind() != TypeKind.DECLARED) { - return false; - } - - AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; - DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; - if (capturedTypeMirror.getTypeArguments().isEmpty()) { - return false; - } - - if (uncapturedType.isUnderlyingTypeRaw() - || uncapturedType.containsUninferredTypeArguments()) { - return false; - } - - if (capturedTypeMirror.getTypeArguments().size() - != uncapturedType.getTypeArguments().size()) { - throw new BugInCF( - "Not the same number of type arguments: capturedTypeMirror: %s uncapturedType:" - + " %s", - capturedTypeMirror, uncapturedType); - } - - for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { - TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD - && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) - || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { - return true; - } - } - return false; - } - - /** - * Apply capture conversion to {@code typeToCapture}. - * - *

Capture conversion is the process of converting wildcards in a parameterized type to fresh - * type variables. See JLS - * 5.1.10 for details. - * - *

If {@code type} is not a declared type or if it does not have any wildcard type arguments, - * this method returns {@code type}. - * - * @param typeToCapture type to capture - * @return the result of applying capture conversion to {@code typeToCapture} - */ - public AnnotatedTypeMirror applyCaptureConversion(AnnotatedTypeMirror typeToCapture) { - TypeMirror capturedTypeMirror = types.capture(typeToCapture.getUnderlyingType()); - return applyCaptureConversion(typeToCapture, capturedTypeMirror); - } - - /** - * Apply capture conversion to {@code type}. - * - *

Capture conversion is the process of converting wildcards in a parameterized type to fresh - * type variables. See JLS - * 5.1.10 for details. - * - *

If {@code type} is not a declared type or if it does not have any wildcard type arguments, - * this method returns {@code type}. - * - * @param type type to capture - * @param typeMirror the result of applying capture conversion to the underlying type of {@code - * type}; it is used as the underlying type of the returned type - * @return the result of applying capture conversion to {@code type} - */ - public AnnotatedTypeMirror applyCaptureConversion( - AnnotatedTypeMirror type, TypeMirror typeMirror) { - // If the type contains uninferred type arguments, don't capture, but mark all wildcards - // that should have been captured as "uninferred" before it is returned. - if (type.containsUninferredTypeArguments() - && typeMirror.getKind() == TypeKind.DECLARED - && type.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; - DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; - for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); - if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD - && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) - || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { - ((AnnotatedWildcardType) uncapturedTypeArg).setUninferredTypeArgument(); - } - } - return type; - } - - if (!shouldCapture(type, typeMirror)) { - return type; - } - - AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; - DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; - // `capturedType` is the return value of this method. - AnnotatedDeclaredType capturedType = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType(capturedTypeMirror, this, false); - - nonWildcardTypeArgCopier.copy(uncapturedType, capturedType); - - AnnotatedDeclaredType typeDeclaration = - (AnnotatedDeclaredType) - getAnnotatedType(uncapturedType.getUnderlyingType().asElement()); - - // A mapping from type variable to its type argument in the captured type. - Map typeVarToAnnotatedTypeArg = new HashMap<>(); - // A mapping from a captured type variable to the annotated captured type variable. - Map capturedTypeVarToAnnotatedTypeVar = - new HashMap<>(); - // `newTypeArgs` will be the type arguments of the result of this method. - List newTypeArgs = new ArrayList<>(); - for (int i = 0; i < typeDeclaration.getTypeArguments().size(); i++) { - TypeVariable typeVarTypeMirror = - (TypeVariable) typeDeclaration.getTypeArguments().get(i).getUnderlyingType(); - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - AnnotatedTypeMirror capturedTypeArg = capturedType.getTypeArguments().get(i); - if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD) { - // The type argument is a captured type variable. Use the type argument from the - // newly created and yet-to-be annotated capturedType. (The annotations are added - // by #annotateCapturedTypeVar, which is called at the end of this method.) - typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, capturedTypeArg); - newTypeArgs.add(capturedTypeArg); - if (TypesUtils.isCapturedTypeVariable(capturedTypeArg.getUnderlyingType())) { - // Also, add a mapping from the captured type variable to the annotated captured - // type variable, so that if one captured type variable refers to another, the - // same AnnotatedTypeVariable object is used. - capturedTypeVarToAnnotatedTypeVar.put( - ((AnnotatedTypeVariable) capturedTypeArg).getUnderlyingType(), - (AnnotatedTypeVariable) capturedTypeArg); - } else { - // Javac used a declared type instead of a captured type variable. This seems - // to happen when the bounds of the captured type variable would have been - // identical. This seems to be a violation of the JLS, but javac does this, so - // the Checker Framework must handle that case. (See - // https://bugs.openjdk.org/browse/JDK-8054309.) - replaceAnnotations( - ((AnnotatedWildcardType) uncapturedTypeArg).getSuperBound(), - capturedTypeArg); - } - } else { - // The type argument is not a wildcard. - // typeVarTypeMirror is the type parameter for which uncapturedTypeArg is a type - // argument. - typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, uncapturedTypeArg); - if (uncapturedTypeArg.getKind() == TypeKind.TYPEVAR) { - // If the type arg is a type variable also add it to the - // typeVarToAnnotatedTypeArg map, so that references to the type variable are - // substituted. - AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) uncapturedTypeArg; - typeVarToAnnotatedTypeArg.put(typeVar.getUnderlyingType(), typeVar); - } - newTypeArgs.add(uncapturedTypeArg); - } - } - - // Set the annotations of each captured type variable. - List orderToCapture = - order(capturedTypeVarToAnnotatedTypeVar.values()); - for (AnnotatedTypeVariable capturedTypeArg : orderToCapture) { - int i = - capturedTypeMirror - .getTypeArguments() - .indexOf(capturedTypeArg.getUnderlyingType()); - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - AnnotatedTypeVariable typeVariable = - (AnnotatedTypeVariable) typeDeclaration.getTypeArguments().get(i); - annotateCapturedTypeVar( - typeVarToAnnotatedTypeArg, - capturedTypeVarToAnnotatedTypeVar, - (AnnotatedWildcardType) uncapturedTypeArg, - typeVariable, - capturedTypeArg); - newTypeArgs.set(i, capturedTypeArg); - } - - capturedType.setTypeArguments(newTypeArgs); - capturedType.addAnnotations(uncapturedType.getAnnotations()); - return capturedType; - } - - /** - * Copy the non-wildcard type args from a uncapturedType to its capturedType. Also, ensure that - * type variables in capturedType are the same object when they are refer to the same type - * variable. - * - *

To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. - */ - private final NonWildcardTypeArgCopier nonWildcardTypeArgCopier = - new NonWildcardTypeArgCopier(); - - /** - * Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. Also, - * ensure that type variables in {@code capturedType} are the same object when they refer to the - * same type variable. - * - *

To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. - */ - private class NonWildcardTypeArgCopier extends AnnotatedTypeCopier { - - /** - * Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. - * Also, ensure that type variables {@code capturedType} are the same object when they are - * refer to the same type variable. - * - * @param uncapturedType a declared type that has not under gone capture conversion - * @param capturedType the captured version of {@code uncapturedType} before it has been - * annotated - */ - private void copy( - AnnotatedDeclaredType uncapturedType, AnnotatedDeclaredType capturedType) { - // The name "originalToCopy" means a mapping from the original to the copy, not an - // original that needs to be copied. - IdentityHashMap originalToCopy = - new IdentityHashMap<>(); - originalToCopy.put(uncapturedType, capturedType); - int numTypeArgs = uncapturedType.getTypeArguments().size(); - - AnnotatedTypeMirror[] newTypeArgs = new AnnotatedTypeMirror[numTypeArgs]; - // Mapping from type var to it's AnnotatedTypeVariable. These are type variables - // that are type arguments of the uncaptured type. - Map typeVarToAnnotatedTypeVar = - new HashMap<>(numTypeArgs); - // Copy the non-wildcard type args from uncapturedType to newTypeArgs. - // If the non-wildcard type arg is a type var, add it to typeVarToAnnotatedTypeVar. - for (int i = 0; i < numTypeArgs; i++) { - AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); - if (uncapturedArg.getKind() != TypeKind.WILDCARD) { - AnnotatedTypeMirror copyOfArg = visit(uncapturedArg, originalToCopy); - newTypeArgs[i] = copyOfArg; - if (copyOfArg.getKind() == TypeKind.TYPEVAR) { - typeVarToAnnotatedTypeVar.put( - ((AnnotatedTypeVariable) copyOfArg).getUnderlyingType(), copyOfArg); - } - } - } - - // Substitute the type variables in each type argument of capturedType using - // typeVarToAnnotatedTypeVar. - // This makes type variables in capturedType the same object when they are the same type - // variable. - for (int i = 0; i < numTypeArgs; i++) { - AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); - AnnotatedTypeMirror capturedArg = capturedType.getTypeArguments().get(i); - // Note: This `if` statement can't be replaced with - // if (TypesUtils.isCapturedTypeVariable(capturedArg)) - // because if the bounds of the captured wildcard are equal, then instead of a - // captured wildcard, the type of the bound is used. - if (uncapturedArg.getKind() == TypeKind.WILDCARD) { - AnnotatedTypeMirror newCapArg = - typeVarSubstitutor.substituteWithoutCopyingTypeArguments( - typeVarToAnnotatedTypeVar, capturedArg); - newTypeArgs[i] = newCapArg; - } - } - // Set capturedType type args to newTypeArgs. - capturedType.setTypeArguments(Arrays.asList(newTypeArgs)); - - // Visit the enclosing type. - if (uncapturedType.getEnclosingType() != null) { - capturedType.setEnclosingType( - (AnnotatedDeclaredType) - visit(uncapturedType.getEnclosingType(), originalToCopy)); + "AnnotatedTypeFactory.getIterableElementType: not iterable type: " + iterableType); + } + } + + /** + * Determines the type of the invoked constructor based on the passed new class tree. + * + *

The returned method type has all type variables resolved, whether based on receiver type, + * passed type parameters if any, and constructor invocation parameter. + * + *

Subclasses may override this method to customize inference of types or qualifiers based on + * constructor invocation parameters. + * + *

As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types, + * AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type + * should be in accordance with its specification. + * + *

The return type is a pair of the type of the invoked constructor and the (inferred) type + * arguments. Note that neither the explicitly passed nor the inferred type arguments are + * guaranteed to be subtypes of the corresponding upper bounds. See method {@link + * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type + * argument well-formedness. + * + *

Note that "this" and "super" constructor invocations are handled by method {@link + * #methodFromUse}. This method only handles constructor invocations in a "new" expression. + * + * @param tree the constructor invocation tree + * @return the annotated type of the invoked constructor (as an executable type) and the + * (inferred) type arguments + */ + public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + // Get the annotations written on the new class tree. + AnnotatedDeclaredType type = + (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false); + if (!TreeUtils.isDiamondTree(tree)) { + if (tree.getClassBody() == null) { + type.setTypeArguments(getExplicitNewClassClassTypeArgs(tree)); + } + } else { + type = getAnnotatedType(TypesUtils.getTypeElement(type.underlyingType)); + // Add explicit annotations below. + type.clearAnnotations(); + } + + AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(tree); + type.addAnnotations(explicitAnnos); + + // Get the enclosing type of the constructor, if one exists. + // this.new InnerClass() + AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(tree); + type.setEnclosingType(enclosingType); + + // Add computed annotations to the type. + addComputedTypeAnnotations(tree, type); + + ExecutableElement ctor = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type + constructorFromUsePreSubstitution(tree, con); + + if (tree.getClassBody() != null) { + // Because the anonymous constructor can't have explicit annotations on its parameters, + // they are copied from the super constructor invoked in the anonymous constructor. To + // do this: + // 1. get unsubstituted type of the super constructor. + // 2. adapt it to this call site. + // 3. compute and store the vararg type. + // 4. copy the parameters to the anonymous constructor, `con`. + // 5. copy annotations on the return type to `con`. + AnnotatedExecutableType superCon = getAnnotatedType(TreeUtils.getSuperConstructor(tree)); + constructorFromUsePreSubstitution(tree, superCon); + // no viewpoint adaptation needed for super invocation + superCon = AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon); + con.computeVarargType(superCon); + if (superCon.getParameterTypes().size() == con.getParameterTypes().size()) { + con.setParameterTypes(superCon.getParameterTypes()); + } else { + // If the super class of the anonymous class has an enclosing type, then it is the + // first parameter of the anonymous constructor. For example, + // class Outer { class Inner {} } + // new Inner(){}; + // Then javac creates the following constructor: + // (.Outer x0) { + // x0.super(); + // } + // So the code below deals with this. + // Because the anonymous constructor doesn't have annotated receiver type, + // we copy the receiver type from the super constructor invoked in the anonymous + // constructor + List p = new ArrayList<>(superCon.getParameterTypes().size() + 1); + p.add(con.getParameterTypes().get(0)); + con.setReceiverType(superCon.getReceiverType()); + p.addAll(superCon.getParameterTypes()); + con.setParameterTypes(Collections.unmodifiableList(p)); + } + con.getReturnType().replaceAnnotations(superCon.getReturnType().getAnnotations()); + } else { + // Store varargType before calling setParameterTypes, otherwise we may lose the + // varargType as it is the last element of the original parameterTypes. + // AnnotatedTypes.asMemberOf handles vararg type properly, so we do not need to compute + // vararg type again. + con.computeVarargType(); + con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con); + } + + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); + } + + Map typeParamToTypeArg = + AnnotatedTypes.findTypeArguments(processingEnv, this, tree, ctor, con); + List typeargs; + if (typeParamToTypeArg.isEmpty()) { + typeargs = Collections.emptyList(); + } else { + typeargs = + CollectionsPlume.mapList( + (AnnotatedTypeVariable tv) -> typeParamToTypeArg.get(tv.getUnderlyingType()), + con.getTypeVariables()); + } + if (TreeUtils.isDiamondTree(tree)) { + // TODO: This should be done at the same time as type argument inference. + List classTypeArgs = inferDiamondType(tree); + int i = 0; + for (AnnotatedTypeMirror typeParam : type.getTypeArguments()) { + typeParamToTypeArg.put((TypeVariable) typeParam.getUnderlyingType(), classTypeArgs.get(i)); + i++; + } + } + con = (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, con); + + stubTypes.injectRecordComponentType(types, ctor, con); + if (enclosingType != null) { + // Reset the enclosing type because it can be substituted incorrectly. + ((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(enclosingType); + } + + if (ctor.getEnclosingElement().getKind() == ElementKind.ENUM) { + AnnotationMirrorSet enumAnnos = getEnumConstructorQualifiers(); + con.getReturnType().replaceAnnotations(enumAnnos); + } + + // Adapt parameters, which makes parameters and arguments be the same size for later + // checking. The vararg type of con has been already computed and stored when calling + // typeVarSubstitutor.substitute. + List parameters = + AnnotatedTypes.adaptParameters(this, con, tree.getArguments(), tree); + con.setParameterTypes(parameters); + + return new ParameterizedExecutableType(con, typeargs); + } + + /** + * Returns the annotations that should be applied to enum constructors. This implementation + * returns an empty set. Subclasses can override to return a different set. + * + * @return the annotations that should be applied to enum constructors + */ + protected AnnotationMirrorSet getEnumConstructorQualifiers() { + return new AnnotationMirrorSet(); + } + + /** + * Creates an AnnotatedDeclaredType for a NewClassTree. Only adds explicit annotations, unless + * newClassTree has a diamond operator. In that case, the annotations on the type arguments are + * inferred using the assignment context and contain defaults. + * + *

(Subclass beside {@link GenericAnnotatedTypeFactory} should not override this method.) + * + * @param newClassTree a NewClassTree + * @return the AnnotatedDeclaredType + * @deprecated Use {@link #getExplicitNewClassAnnos(NewClassTree)}, {@link + * #getExplicitNewClassClassTypeArgs(NewClassTree)}, or {@link #getAnnotatedType(ClassTree)} + * instead. + */ + @Deprecated // This should be removed when #979 is fixed and the remaining use is removed. + public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) { + AnnotatedDeclaredType type = + (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false); + if (!TreeUtils.isDiamondTree(newClassTree)) { + if (newClassTree.getClassBody() == null) { + type.setTypeArguments(getExplicitNewClassClassTypeArgs(newClassTree)); + } + } else { + assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree"; + TreePath p = getPath(newClassTree); + AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p); + if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype; + if (type.getTypeArguments().size() == adctx.getTypeArguments().size()) { + // Try to simply take the type arguments from LHS. + List oldArgs = type.getTypeArguments(); + List newArgs = adctx.getTypeArguments(); + for (int i = 0; i < type.getTypeArguments().size(); ++i) { + if (!types.isSubtype(newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) { + // One of the underlying types doesn't match. Give up. + newArgs = oldArgs; + break; } + } + type.setTypeArguments(newArgs); } + } } - /** - * Returns the list of type variables such that a type variable in the list only references type - * variables at a lower index than itself. - * - * @param collection a collection of type variables - * @return the type variables ordered so that each type variable only references earlier type - * variables - */ - public List order(Collection collection) { - List list = new ArrayList<>(collection); - List ordered = new ArrayList<>(); - while (!list.isEmpty()) { - AnnotatedTypeVariable free = doesNotContainOthers(list); - list.remove(free); - ordered.add(free); + AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(newClassTree); + // Type may already have explicit dependent type annotations that have not yet been vpa. + type.clearAnnotations(); + type.addAnnotations(explicitAnnos); + // Use the receiver type as enclosing type, if present. + AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(newClassTree); + if (enclosingType != null) { + type.setEnclosingType(enclosingType); + } + return type; + } + + /** + * Returns the annotations explicitly written on a NewClassTree. + * + *

{@code new @HERE Class()} + * + * @param newClassTree a constructor invocation + * @return the annotations explicitly written on a NewClassTree + */ + public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { + if (newClassTree.getClassBody() != null) { + // In Java 17+, the annotations are on the identifier, so copy them. + AnnotatedTypeMirror identifierType = fromTypeTree(newClassTree.getIdentifier()); + // In Java 11 and lower, if newClassTree creates an anonymous class, then annotations in + // this location: + // new @HERE Class() {} + // are not on the identifier newClassTree, but rather on the modifier newClassTree. + List annoTrees = + newClassTree.getClassBody().getModifiers().getAnnotations(); + // Add the annotations to an AnnotatedTypeMirror removes the annotations that are not + // supported by this type system. + identifierType.addAnnotations(TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees)); + return identifierType.getAnnotations(); + } else { + return fromTypeTree(newClassTree.getIdentifier()).getAnnotations(); + } + } + + /** + * Returns the partially-annotated explicit class type arguments of the new class tree. The {@code + * AnnotatedTypeMirror} only include the annotations explicitly written on the explict type + * arguments. (If {@code newClass} use a diamond operator, this method returns the empty list.) + * For example, when called with {@code new MyClass<@HERE String>()} this method would return a + * list containing {@code @HERE String}. + * + * @param newClass a new class tree + * @return the partially annotated {@code AnnotatedTypeMirror}s for the (explicit) class type + * arguments of the new class tree + */ + protected List getExplicitNewClassClassTypeArgs(NewClassTree newClass) { + if (!TreeUtils.isDiamondTree(newClass)) { + return ((AnnotatedDeclaredType) fromTypeTree(newClass.getIdentifier())).getTypeArguments(); + } + return Collections.emptyList(); + } + + /** + * Infer the class type arguments for the diamond operator. + * + *

If {@code newClassTree} is assigned to the same type (not a supertype), then the type + * arguments are inferred to be the same as the assignment. Otherwise, the type arguments are + * annotated by {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. + * + * @param newClassTree a diamond new class tree + * @return the class type arguments for {@code newClassTree} + */ + private List inferDiamondType(NewClassTree newClassTree) { + assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree"; + AnnotatedDeclaredType diamondType = + (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false); + + TreePath p = getPath(newClassTree); + AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p); + if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype; + if (diamondType.getTypeArguments().size() == adctx.getTypeArguments().size()) { + // Try to simply take the type arguments from LHS. + List oldArgs = diamondType.getTypeArguments(); + List newArgs = adctx.getTypeArguments(); + boolean useLhs = true; + for (int i = 0; i < diamondType.getTypeArguments().size(); ++i) { + if (!types.isSubtype(newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) { + // One of the underlying types doesn't match. Give up. + useLhs = false; + break; + } } - return ordered; - } - - /** - * Returns the first TypeVariable in {@code collection} that does not lexically contain any - * other type in the collection. Or if all the TypeVariables contain another, then it returns - * the first TypeVariable in {@code collection}. - * - * @param collection a collection of type variables - * @return the first TypeVariable in {@code collection} that does not contain any other type in - * the collection, except possibly itself - */ - @SuppressWarnings("interning:not.interned") // must be the same object from collection - private AnnotatedTypeVariable doesNotContainOthers( - Collection collection) { - AnnotatedTypeVariable first = null; - for (AnnotatedTypeVariable candidate : collection) { - if (first == null) { - first = candidate; - } - boolean doesNotContain = true; - for (AnnotatedTypeVariable other : collection) { - if (candidate != other - && captureScanner.visit(candidate, other.getUnderlyingType())) { - doesNotContain = false; - break; - } - } - if (doesNotContain) { - return candidate; - } + if (useLhs) { + return newArgs; } - return first; + } } - - /** - * Scanner that returns true if the underlying type of any part of an {@link - * AnnotatedTypeMirror} is the passed captured type variable. - * - *

The second argument to visit must be a captured type variable. - */ - @SuppressWarnings("interning:not.interned") // Captured type vars can be compared with ==. - private final SimpleAnnotatedTypeScanner captureScanner = - new SimpleAnnotatedTypeScanner<>( - (type, other) -> type.getUnderlyingType() == other, Boolean::logicalOr, false); - - /** - * Set the annotated bounds for fresh type variable {@code capturedTypeVar}, so that it is the - * capture of {@code wildcard}. Also, sets {@code capturedTypeVar} primary annotation if the - * annotation on the bounds is identical. - * - * @param typeVarToAnnotatedTypeArg mapping from a (type mirror) type variable to its (annotated - * type mirror) type argument - * @param capturedTypeVarToAnnotatedTypeVar mapping from a captured type variable to its {@link - * AnnotatedTypeMirror} - * @param wildcard wildcard which is converted to {@code capturedTypeVar} - * @param typeVariable type variable for which {@code wildcard} is a type argument - * @param capturedTypeVar the fresh type variable which is side-effected by this method - */ - private void annotateCapturedTypeVar( - Map typeVarToAnnotatedTypeArg, - Map capturedTypeVarToAnnotatedTypeVar, - AnnotatedWildcardType wildcard, - AnnotatedTypeVariable typeVariable, - AnnotatedTypeVariable capturedTypeVar) { - AnnotatedTypeMirror typeVarUpperBound = - typeVarSubstitutor.substituteWithoutCopyingTypeArguments( - typeVarToAnnotatedTypeArg, typeVariable.getUpperBound()); - AnnotatedTypeMirror upperBound = - AnnotatedTypes.annotatedGLB(this, typeVarUpperBound, wildcard.getExtendsBound()); - if (upperBound.getKind() == TypeKind.INTERSECTION - && capturedTypeVar.getUpperBound().getKind() != TypeKind.INTERSECTION) { - // There is a bug in javac such that the upper bound of the captured type variable is - // not the greatest lower bound. So the - // captureTypeVar.getUnderlyingType().getUpperBound() may not - // be the same type as upperbound.getUnderlyingType(). See - // framework/tests/all-systems/Issue4890Interfaces.java, - // framework/tests/all-systems/Issue4890.java and - // framework/tests/all-systems/Issue4877.java. - // (I think this is https://bugs.openjdk.org/browse/JDK-8039222.) - for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) upperBound).getBounds()) { - if (types.isSameType( - bound.underlyingType, - capturedTypeVar.getUpperBound().getUnderlyingType())) { - upperBound = bound; - } - } + addComputedTypeAnnotations(newClassTree, diamondType); + return diamondType.getTypeArguments(); + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the + * declared constructor type before type variable substitution. + * + * @param tree a NewClassTree from constructorFromUse() + * @param type declared method type before type variable substitution + */ + protected void constructorFromUsePreSubstitution( + NewClassTree tree, AnnotatedExecutableType type) {} + + /** + * Returns the return type of the method {@code m}. + * + * @param m tree of a method declaration + * @return the return type of the method + */ + public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { + AnnotatedExecutableType methodType = getAnnotatedType(m); + AnnotatedTypeMirror ret = methodType.getReturnType(); + return ret; + } + + /** + * Returns the return type of the method {@code m} at the return statement {@code r}. This + * implementation just calls {@link #getMethodReturnType(MethodTree)}, but subclasses may override + * this method to change the type based on the return statement. + * + * @param m tree of a method declaration + * @param r a return statement within method {@code m} + * @return the return type of the method {@code m} at the return statement {@code r} + */ + public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { + return getMethodReturnType(m); + } + + /** + * Returns the annotated boxed type of the given primitive type. The returned type would only have + * the annotations on the given type. + * + *

Subclasses may override this method safely to override this behavior. + * + * @param type the primitive type + * @return the boxed declared type of the passed primitive type + */ + public AnnotatedDeclaredType getBoxedType(AnnotatedPrimitiveType type) { + TypeElement typeElt = types.boxedClass(type.getUnderlyingType()); + AnnotatedDeclaredType dt = fromElement(typeElt).asUse(); + dt.addAnnotations(type.getAnnotations()); + return dt; + } + + /** + * Return a primitive type: either the argument, or the result of unboxing it (which might affect + * its annotations). + * + *

Subclasses should override {@link #getUnboxedType} rather than this method. + * + * @param type a type: a primitive or boxed primitive + * @return the unboxed variant of the type + */ + public final AnnotatedPrimitiveType applyUnboxing(AnnotatedTypeMirror type) { + TypeMirror underlying = type.getUnderlyingType(); + if (TypesUtils.isPrimitive(underlying)) { + return (AnnotatedPrimitiveType) type; + } else if (TypesUtils.isBoxedPrimitive(underlying)) { + return getUnboxedType((AnnotatedDeclaredType) type); + } else { + throw new BugInCF("Bad argument to applyUnboxing: " + type); + } + } + + /** + * Returns the annotated primitive type of the given declared type if it is a boxed declared type. + * Otherwise, it throws IllegalArgumentException exception. + * + *

In the {@code AnnotatedTypeFactory} implementation, the returned type has the same primary + * annotations as the given type. Subclasses may override this behavior. + * + * @param type the declared type + * @return the unboxed primitive type + * @throws IllegalArgumentException if the type given has no unbox conversion + */ + public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) + throws IllegalArgumentException { + PrimitiveType primitiveType = types.unboxedType(type.getUnderlyingType()); + AnnotatedPrimitiveType pt = + (AnnotatedPrimitiveType) AnnotatedTypeMirror.createType(primitiveType, this, false); + pt.addAnnotations(type.getAnnotations()); + return pt; + } + + /** + * Returns AnnotatedDeclaredType with underlying type String and annotations copied from type. + * Subclasses may change the annotations. + * + * @param type type to convert to String + * @return AnnotatedTypeMirror that results from converting type to a String type + */ + // TODO: Test that this is called in all the correct locations + // See Issue #715 + // https://github.com/typetools/checker-framework/issues/715 + public AnnotatedDeclaredType getStringType(AnnotatedTypeMirror type) { + TypeMirror stringTypeMirror = TypesUtils.typeFromClass(String.class, types, elements); + AnnotatedDeclaredType stringATM = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType(stringTypeMirror, this, type.isDeclaration()); + stringATM.addAnnotations(type.getEffectiveAnnotations()); + return stringATM; + } + + /** + * Returns a widened type if applicable, otherwise returns its first argument. + * + *

Subclasses should override {@link #getWidenedAnnotations} rather than this method. + * + * @param exprType type to possibly widen + * @param widenedType type to possibly widen to; its annotations are ignored + * @return if widening is applicable, the result of converting {@code type} to the underlying type + * of {@code widenedType}; otherwise {@code type} + */ + public final AnnotatedTypeMirror getWidenedType( + AnnotatedTypeMirror exprType, AnnotatedTypeMirror widenedType) { + TypeKind exprKind = exprType.getKind(); + TypeKind widenedKind = widenedType.getKind(); + + if (!TypeKindUtils.isNumeric(widenedKind)) { + // The target type is not a numeric primitive, so primitive widening is not applicable. + return exprType; + } + + AnnotatedPrimitiveType exprPrimitiveType; + if (TypeKindUtils.isNumeric(exprKind)) { + exprPrimitiveType = (AnnotatedPrimitiveType) exprType; + } else if (TypesUtils.isNumericBoxed(exprType.getUnderlyingType())) { + exprPrimitiveType = getUnboxedType((AnnotatedDeclaredType) exprType); + } else { + return exprType; + } + + switch (TypeKindUtils.getPrimitiveConversionKind( + exprPrimitiveType.getKind(), widenedType.getKind())) { + case WIDENING: + return getWidenedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); + case NARROWING: + return getNarrowedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); + case SAME: + return exprType; + default: + throw new BugInCF("unhandled PrimitiveConversionKind"); + } + } + + /** + * Applies widening if applicable, otherwise returns its first argument. + * + *

Subclasses should override {@link #getWidenedAnnotations} rather than this method. + * + * @param exprAnnos annotations to possibly widen + * @param exprTypeMirror type to possibly widen + * @param widenedType type to possibly widen to; its annotations are ignored + * @return if widening is applicable, the result of converting {@code type} to the underlying type + * of {@code widenedType}; otherwise {@code type} + */ + public final AnnotatedTypeMirror getWidenedType( + AnnotationMirrorSet exprAnnos, TypeMirror exprTypeMirror, AnnotatedTypeMirror widenedType) { + AnnotatedTypeMirror exprType = toAnnotatedType(exprTypeMirror, false); + exprType.replaceAnnotations(exprAnnos); + return getWidenedType(exprType, widenedType); + } + + /** + * Returns an AnnotatedPrimitiveType with underlying type {@code widenedTypeMirror} and with + * annotations copied or adapted from {@code type}. + * + * @param type type to widen; a primitive or boxed primitive + * @param widenedTypeMirror underlying type for the returned type mirror; a primitive or boxed + * primitive (same boxing as {@code type}) + * @return result of converting {@code type} to {@code widenedTypeMirror} + */ + private AnnotatedPrimitiveType getWidenedPrimitive( + AnnotatedPrimitiveType type, TypeMirror widenedTypeMirror) { + AnnotatedPrimitiveType result = + (AnnotatedPrimitiveType) + AnnotatedTypeMirror.createType(widenedTypeMirror, this, type.isDeclaration()); + result.addAnnotations( + getWidenedAnnotations(type.getAnnotations(), type.getKind(), result.getKind())); + return result; + } + + /** + * Returns annotations applicable to type {@code narrowedTypeKind}, that are copied or adapted + * from {@code annos}. + * + * @param annos annotations to narrow, from a primitive or boxed primitive + * @param typeKind primitive type to narrow + * @param narrowedTypeKind target for the returned annotations; a primitive type that is narrower + * than {@code typeKind} (in the sense of JLS 5.1.3). + * @return result of converting {@code annos} from {@code typeKind} to {@code narrowedTypeKind} + */ + public AnnotationMirrorSet getNarrowedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { + return annos; + } + + /** + * Returns annotations applicable to type {@code widenedTypeKind}, that are copied or adapted from + * {@code annos}. + * + * @param annos annotations to widen, from a primitive or boxed primitive + * @param typeKind primitive type to widen + * @param widenedTypeKind target for the returned annotations; a primitive type that is wider than + * {@code typeKind} (in the sense of JLS 5.1.2) + * @return result of converting {@code annos} from {@code typeKind} to {@code widenedTypeKind} + */ + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + return annos; + } + + /** + * Returns the types of the two arguments to the BinaryTree. Please refer to {@link + * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} )} for more details. + * + * @param tree a binary tree + * @return the types of the two arguments + */ + public IPair binaryTreeArgTypes(BinaryTree tree) { + return binaryTreeArgTypes( + TreeUtils.typeOf(tree), + getAnnotatedType(tree.getLeftOperand()), + getAnnotatedType(tree.getRightOperand())); + } + + /** + * Returns the types of the two arguments to the CompoundAssignmentTree. Please refer to {@link + * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} ) for more details. + * + * @param tree a compound assignment tree + * @return the types of the two arguments + */ + public IPair compoundAssignmentTreeArgTypes( + CompoundAssignmentTree tree) { + return binaryTreeArgTypes( + TreeUtils.typeOf(tree.getVariable()), + getAnnotatedType(tree.getVariable()), + getAnnotatedType(tree.getExpression())); + } + + /** + * Returns the types of the two arguments to a binary operation. There are two special cases: + * + *

1. If both operands have numeric type, widening and unboxing will be applied accordingly. + * + *

2. If we have a non-string operand in a string concatenation (i.e., result is a string), we + * will always return a string ATM for the operand. The resulting ATM will have the original + * annotations with the declaration bounds of string type applied. Please check {@link + * #getAnnotationOrTypeDeclarationBound} for more details. + * + * @param resultType the type of the result of a binary operation + * @param left the type of the left argument of a binary operation + * @param right the type of the right argument of a binary operation + * @return the types of the two arguments + */ + protected IPair binaryTreeArgTypes( + TypeMirror resultType, AnnotatedTypeMirror left, AnnotatedTypeMirror right) { + TypeKind widenedNumericType = + TypeKindUtils.widenedNumericType(left.getUnderlyingType(), right.getUnderlyingType()); + if (TypeKindUtils.isNumeric(widenedNumericType)) { + TypeMirror widenedNumericTypeMirror = types.getPrimitiveType(widenedNumericType); + AnnotatedPrimitiveType leftUnboxed = applyUnboxing(left); + AnnotatedPrimitiveType rightUnboxed = applyUnboxing(right); + AnnotatedPrimitiveType leftWidened = + (leftUnboxed.getKind() == widenedNumericType + ? leftUnboxed + : getWidenedPrimitive(leftUnboxed, widenedNumericTypeMirror)); + AnnotatedPrimitiveType rightWidened = + (rightUnboxed.getKind() == widenedNumericType + ? rightUnboxed + : getWidenedPrimitive(rightUnboxed, widenedNumericTypeMirror)); + return IPair.of(leftWidened, rightWidened); + } else if (TypesUtils.isString(resultType)) { + // the result of a binary operation is String iff it's string concatenation + AnnotatedTypeMirror leftStringConverted = left; + AnnotatedTypeMirror rightStringConverted = right; + + if (!TypesUtils.isString(left.getUnderlyingType())) { + leftStringConverted = toAnnotatedType(resultType, false); + AnnotationMirrorSet annos = + getAnnotationOrTypeDeclarationBound(resultType, left.getEffectiveAnnotations()); + leftStringConverted.addAnnotations(annos); + } + if (!TypesUtils.isString(right.getUnderlyingType())) { + rightStringConverted = toAnnotatedType(resultType, false); + AnnotationMirrorSet annos = + getAnnotationOrTypeDeclarationBound(resultType, right.getEffectiveAnnotations()); + rightStringConverted.addAnnotations(annos); + } + return IPair.of(leftStringConverted, rightStringConverted); + } + + return IPair.of(left, right); + } + + /** + * Returns AnnotatedPrimitiveType with underlying type {@code narrowedTypeMirror} and with + * annotations copied or adapted from {@code type}. + * + *

Currently this method is called only for primitives that are narrowed at assignments from + * literal ints, for example, {@code byte b = 1;}. All other narrowing conversions happen at + * typecasts. + * + * @param type type to narrow + * @param narrowedTypeMirror underlying type for the returned type mirror + * @return result of converting {@code type} to {@code narrowedTypeMirror} + */ + public AnnotatedPrimitiveType getNarrowedPrimitive( + AnnotatedPrimitiveType type, TypeMirror narrowedTypeMirror) { + AnnotatedPrimitiveType narrowed = + (AnnotatedPrimitiveType) + AnnotatedTypeMirror.createType(narrowedTypeMirror, this, type.isDeclaration()); + narrowed.addAnnotations(type.getAnnotations()); + return narrowed; + } + + // ********************************************************************** + // random methods wrapping #getAnnotatedType(Tree) and #fromElement(Tree) + // with appropriate casts to reduce casts on the client side + // ********************************************************************** + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedDeclaredType getAnnotatedType(ClassTree tree) { + return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedDeclaredType getAnnotatedType(NewClassTree tree) { + return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedArrayType getAnnotatedType(NewArrayTree tree) { + return (AnnotatedArrayType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedExecutableType getAnnotatedType(MethodTree tree) { + return (AnnotatedExecutableType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Element)}. + * + * @see #getAnnotatedType(Element) + */ + public final AnnotatedDeclaredType getAnnotatedType(TypeElement elt) { + return (AnnotatedDeclaredType) getAnnotatedType((Element) elt); + } + + /** + * See {@link #getAnnotatedType(Element)}. + * + * @see #getAnnotatedType(Element) + */ + public final AnnotatedExecutableType getAnnotatedType(ExecutableElement elt) { + return (AnnotatedExecutableType) getAnnotatedType((Element) elt); + } + + /** + * See {@link #fromElement(Element)}. + * + * @see #fromElement(Element) + */ + public final AnnotatedDeclaredType fromElement(TypeElement elt) { + return (AnnotatedDeclaredType) fromElement((Element) elt); + } + + /** + * See {@link #fromElement(Element)}. + * + * @see #fromElement(Element) + */ + public final AnnotatedExecutableType fromElement(ExecutableElement elt) { + return (AnnotatedExecutableType) fromElement((Element) elt); + } + + // ********************************************************************** + // Helper methods for this classes + // ********************************************************************** + + /** + * Returns true if the given annotation is a part of the type system under which this type factory + * operates. Null is never a supported qualifier; the parameter is nullable to allow the result of + * canonicalAnnotation to be passed in directly. + * + * @param a any annotation + * @return true if that annotation is part of the type system under which this type factory + * operates, false otherwise + */ + @EnsuresNonNullIf(expression = "#1", result = true) + public boolean isSupportedQualifier(@Nullable AnnotationMirror a) { + if (a == null) { + return false; + } + return isSupportedQualifier(AnnotationUtils.annotationName(a)); + } + + /** + * Returns true if the given class is a part of the type system under which this type factory + * operates. + * + * @param clazz annotation class + * @return true if that class is a type qualifier in the type system under which this type factory + * operates, false otherwise + */ + public boolean isSupportedQualifier(Class clazz) { + return getSupportedTypeQualifiers().contains(clazz); + } + + /** + * Returns true if the given class name is a part of the type system under which this type factory + * operates. + * + * @param className fully-qualified annotation class name + * @return true if that class name is a type qualifier in the type system under which this type + * factory operates, false otherwise + */ + public boolean isSupportedQualifier(String className) { + return getSupportedTypeQualifierNames().contains(className); + } + + /** + * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code + * canonicalAnno} that will be used by the Checker Framework in the alias's place. + * + *

By specifying the alias/canonical relationship using this method, the elements of the alias + * are not preserved when the canonical annotation to use is constructed from the alias. If you + * want the elements to be copied over as well, use {@link #addAliasedTypeAnnotation(Class, Class, + * boolean, String...)}. + * + * @param aliasClass the class of the aliased annotation + * @param canonicalAnno the canonical annotation + */ + protected void addAliasedTypeAnnotation(Class aliasClass, AnnotationMirror canonicalAnno) { + if (getSupportedTypeQualifiers().contains(aliasClass)) { + throw new BugInCF( + "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", + aliasClass, this.getClass().getSimpleName()); + } + addAliasedTypeAnnotation(aliasClass.getCanonicalName(), canonicalAnno); + } + + /** + * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for + * the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in + * the alias's place. + * + *

Use this method if the alias class is not necessarily on the classpath at Checker Framework + * compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, AnnotationMirror)} + * which prevents the possibility of a typo in the class name. + * + * @param aliasName the canonical name of the aliased annotation + * @param canonicalAnno the canonical annotation + */ + // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the + // name of an external annotation is a canonical name. + protected void addAliasedTypeAnnotation( + @FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) { + aliases.put(aliasName, new Alias(aliasName, canonicalAnno, false, null, null)); + } + + /** + * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code + * canonicalClass} that will be used by the Checker Framework in the alias's place. + * + *

You may specify the copyElements flag to indicate whether you want the elements of the alias + * to be copied over when the canonical annotation is constructed as a copy of {@code + * canonicalClass}. Be careful that the framework will try to copy the elements by name matching, + * so make sure that names and types of the elements to be copied over are exactly the same as the + * ones in the canonical annotation. Otherwise, an 'Couldn't find element in annotation' error is + * raised. + * + *

To facilitate the cases where some of the elements are ignored on purpose when constructing + * the canonical annotation, this method also provides a varargs {@code ignorableElements} for you + * to explicitly specify the ignoring rules. For example, {@code + * org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code + * org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of + * {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases where + * all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class, + * AnnotationMirror)} instead. + * + * @param aliasClass the class of the aliased annotation + * @param canonicalClass the class of the canonical annotation + * @param copyElements a flag that indicates whether you want to copy the elements over when + * getting the alias from the canonical annotation + * @param ignorableElements a list of elements that can be safely dropped when the elements are + * being copied over + */ + protected void addAliasedTypeAnnotation( + Class aliasClass, + Class canonicalClass, + boolean copyElements, + String... ignorableElements) { + if (getSupportedTypeQualifiers().contains(aliasClass)) { + throw new BugInCF( + "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", + aliasClass, this.getClass().getSimpleName()); + } + addAliasedTypeAnnotation( + aliasClass.getCanonicalName(), canonicalClass, copyElements, ignorableElements); + } + + /** + * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for + * the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in + * the alias's place. + * + *

Use this method if the alias class is not necessarily on the classpath at Checker Framework + * compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, Class, boolean, + * String[])} which prevents the possibility of a typo in the class name. + * + * @param aliasName the canonical name of the aliased class + * @param canonicalAnno the canonical annotation + * @param copyElements a flag that indicates whether we want to copy the elements over when + * getting the alias from the canonical annotation + * @param ignorableElements a list of elements that can be safely dropped when the elements are + * being copied over + */ + // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the + // name of an external annotation is a canoncal name. + protected void addAliasedTypeAnnotation( + @FullyQualifiedName String aliasName, + Class canonicalAnno, + boolean copyElements, + String... ignorableElements) { + // The copyElements argument disambiguates overloading. + if (!copyElements) { + throw new BugInCF("Do not call with false"); + } + aliases.put( + aliasName, + new Alias( + aliasName, null, copyElements, canonicalAnno.getCanonicalName(), ignorableElements)); + } + + /** + * Returns the canonical annotation for the passed annotation. Returns null if the passed + * annotation is not an alias of a canonical one in the framework. + * + *

A canonical annotation is the internal annotation that will be used by the Checker Framework + * in the aliased annotation's place. + * + * @param a the qualifier to check for an alias + * @return the canonical annotation, or null if none exists + */ + public @Nullable AnnotationMirror canonicalAnnotation(AnnotationMirror a) { + TypeElement elem = (TypeElement) a.getAnnotationType().asElement(); + String qualName = elem.getQualifiedName().toString(); + Alias alias = aliases.get(qualName); + if (alias == null) { + return null; + } + if (alias.copyElements) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, alias.canonicalName); + builder.copyElementValuesFromAnnotation(a, alias.ignorableElements); + return builder.build(); + } else { + return alias.canonical; + } + } + + /** + * Add the annotation {@code alias} as an alias for the declaration annotation {@code annotation}, + * where the annotation mirror {@code annotationToUse} will be used instead. If multiple calls are + * made with the same {@code annotation}, then the {@code annotationToUse} must be the same. + * + *

The point of {@code annotationToUse} is that it may include elements/fields. + * + * @param alias the class of the alias annotation + * @param annotation the class of the canonical annotation + * @param annotationToUse the annotation mirror to use + */ + protected void addAliasedDeclAnnotation( + Class alias, + Class annotation, + AnnotationMirror annotationToUse) { + addAliasedDeclAnnotation( + alias.getCanonicalName(), annotation.getCanonicalName(), annotationToUse); + } + + /** + * Add the annotation {@code alias} as an alias for the declaration annotation {@code annotation}, + * where the annotation mirror {@code annotationToUse} will be used instead. If multiple calls are + * made with the same {@code annotation}, then the {@code annotationToUse} must be the same. + * + *

The point of {@code annotationToUse} is that it may include elements/fields. + * + * @param alias the fully-qualified name of the alias annotation + * @param annotation the fully-qualified name of the canonical annotation + * @param annotationToUse the annotation mirror to use + */ + protected void addAliasedDeclAnnotation( + @FullyQualifiedName String alias, + @FullyQualifiedName String annotation, + AnnotationMirror annotationToUse) { + Map<@FullyQualifiedName String, AnnotationMirror> mapping = declAliases.get(annotation); + if (mapping == null) { + mapping = new HashMap<>(1); + declAliases.put(annotation, mapping); + } + AnnotationMirror prev = mapping.put(alias, annotationToUse); + // There already was a mapping. Raise an error. + if (prev != null && !AnnotationUtils.areSame(prev, annotationToUse)) { + throw new TypeSystemError( + "Multiple aliases for %s: %s cannot map to %s and %s.", + annotation, alias, prev, annotationToUse); + } + } + + /** + * Adds the annotation {@code annotation} in the set of declaration annotations that should be + * inherited. A declaration annotation will be inherited if it is in this list, or if it has the + * meta-annotation @InheritedAnnotation. The meta-annotation @InheritedAnnotation should be used + * instead of this method, if possible. + */ + protected void addInheritedAnnotation(AnnotationMirror annotation) { + inheritedAnnotations.add(annotation); + } + + /** + * A convenience method that converts a {@link TypeMirror} to an empty {@link AnnotatedTypeMirror} + * using {@link AnnotatedTypeMirror#createType}. + * + * @param t the {@link TypeMirror} + * @param declaration true if the result should be marked as a type declaration + * @return an {@link AnnotatedTypeMirror} that has {@code t} as its underlying type + */ + protected final AnnotatedTypeMirror toAnnotatedType(TypeMirror t, boolean declaration) { + return AnnotatedTypeMirror.createType(t, this, declaration); + } + + /** + * Determines an empty annotated type of the given tree. In other words, finds the {@link + * TypeMirror} for the tree and converts that into an {@link AnnotatedTypeMirror}, but does not + * add any annotations to the result. + * + *

Most users will want to use {@link #getAnnotatedType(Tree)} instead; this method is mostly + * for internal use. + * + * @param tree the tree to analyze + * @return the type of {@code tree}, without any annotations + */ + protected final AnnotatedTypeMirror type(Tree tree) { + boolean isDeclaration = TreeUtils.isTypeDeclaration(tree); + + // Attempt to obtain the type via JCTree. + if (TreeUtils.typeOf(tree) != null) { + AnnotatedTypeMirror result = toAnnotatedType(TreeUtils.typeOf(tree), isDeclaration); + return result; + } + + // Attempt to obtain the type via TreePath (slower). + TreePath path = this.getPath(tree); + assert path != null + : "No path or type in tree: " + tree + " [" + tree.getClass().getSimpleName() + "]"; + + TypeMirror t = trees.getTypeMirror(path); + assert validType(t) : "Invalid type " + t + " for tree " + t; + + AnnotatedTypeMirror result = toAnnotatedType(t, isDeclaration); + return result; + } + + /** + * Gets the declaration tree for the element, if the source is available. + * + *

TODO: would be nice to move this to InternalUtils/TreeUtils. + * + * @param elt an element + * @return the tree declaration of the element if found + */ + public final @Nullable Tree declarationFromElement(Element elt) { + // if root is null, we cannot find any declaration + if (root == null) { + return null; + } + if (shouldCache && elementToTreeCache.containsKey(elt)) { + return elementToTreeCache.get(elt); + } + + // Check for new declarations, outside of the AST. + if (elt instanceof DetachedVarSymbol) { + return ((DetachedVarSymbol) elt).getDeclaration(); + } + + // TODO: handle type parameter declarations? + Tree fromElt; + // Prevent calling declarationFor on elements we know we don't have the tree for. + + switch (ElementUtils.getKindRecordAsClass(elt)) { + case CLASS: // Including RECORD + case ENUM: + case INTERFACE: + case ANNOTATION_TYPE: + case FIELD: + case ENUM_CONSTANT: + case METHOD: + case CONSTRUCTOR: + fromElt = trees.getTree(elt); + break; + default: + fromElt = + com.sun.tools.javac.tree.TreeInfo.declarationFor( + (com.sun.tools.javac.code.Symbol) elt, (com.sun.tools.javac.tree.JCTree) root); + break; + } + if (shouldCache) { + elementToTreeCache.put(elt, fromElt); + } + return fromElt; + } + + /** + * Returns true if {@code tree} is within a constructor. + * + * @param tree the tree that might be within a constructor + * @return true if {@code tree} is within a constructor + */ + protected final boolean isWithinConstructor(Tree tree) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getPath(tree)); + return enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod); + } + + /** + * Sets the path to the tree that an external "visitor" is visiting. The visitor is either a + * subclass of {@link BaseTypeVisitor} or {@link + * org.checkerframework.framework.flow.CFAbstractTransfer}. + * + * @param visitorTreePath path to the current tree that an external "visitor" is visiting + */ + public void setVisitorTreePath(@Nullable TreePath visitorTreePath) { + this.visitorTreePath = visitorTreePath; + } + + /** + * Returns the path to the tree that an external "visitor" is visiting. The type factory does not + * update this value as it computes the types of any tree or element needed compute the type of + * the tree being visited. Therefore this path may not be the path to the tree whose type is being + * computed. This method should not be used directly. Use {@link #getPath(Tree)} instead. + * + *

This method is used to save the previous tree path and to give a hint to {@link + * #getPath(Tree)} on where to look for a tree rather than searching starting at the root. + * + * @return the path to the tree that an external "visitor" is visiting + */ + public @Nullable TreePath getVisitorTreePath() { + return visitorTreePath; + } + + /** + * Gets the path for the given {@link Tree} under the current root by checking from the visitor's + * current path, and using {@link Trees#getPath(CompilationUnitTree, Tree)} (which is much slower) + * only if {@code tree} is not found on the current path. + * + *

Note that the given Tree has to be within the current compilation unit, otherwise null will + * be returned. + * + *

Within a subclass of BaseTypeVisitor, use {@code getCurrentPath()} rather than this method. + * + * @param tree the {@link Tree} to get the path for + * @return the path for {@code tree} under the current root. Returns null if {@code tree} is not + * within the current compilation unit. + */ + public final @Nullable TreePath getPath(@FindDistinct Tree tree) { + assert root != null + : "AnnotatedTypeFactory.getPath(" + + tree.getKind() + + "): root needs to be set when used on trees; factory: " + + this.getClass().getSimpleName(); + + if (tree == null) { + return null; + } + + if (treePathCache.isCached(tree)) { + return treePathCache.getPath(root, tree); + } + + TreePath currentPath = visitorTreePath; + if (currentPath == null) { + TreePath path = TreePath.getPath(root, tree); + treePathCache.addPath(tree, path); + return path; + } + + // This method uses multiple heuristics to avoid calling + // TreePath.getPath() + + // If the current path you are visiting is for this tree we are done + if (currentPath.getLeaf() == tree) { + treePathCache.addPath(tree, currentPath); + return currentPath; + } + + // When running on Daikon, we noticed that a lot of calls happened + // within a small subtree containing the tree we are currently visiting + + // When testing on Daikon, two steps resulted in the best performance + if (currentPath.getParentPath() != null) { + currentPath = currentPath.getParentPath(); + treePathCache.addPath(currentPath.getLeaf(), currentPath); + if (currentPath.getLeaf() == tree) { + return currentPath; + } + if (currentPath.getParentPath() != null) { + currentPath = currentPath.getParentPath(); + treePathCache.addPath(currentPath.getLeaf(), currentPath); + if (currentPath.getLeaf() == tree) { + return currentPath; } + } + } - capturedTypeVar.setUpperBound(upperBound); - - // typeVariable's lower bound is a NullType, so there's nothing to substitute. - AnnotatedTypeMirror lowerBound = - AnnotatedTypes.leastUpperBound( - this, typeVariable.getLowerBound(), wildcard.getSuperBound()); - capturedTypeVar.setLowerBound(lowerBound); - - // Add as a primary annotation any qualifiers that are the same on the upper and lower - // bound. - AnnotationMirrorSet p = - new AnnotationMirrorSet(capturedTypeVar.getUpperBound().getAnnotations()); - p.retainAll(capturedTypeVar.getLowerBound().getAnnotations()); - capturedTypeVar.replaceAnnotations(p); + TreePath pathWithinSubtree = TreePath.getPath(currentPath, tree); + if (pathWithinSubtree != null) { + treePathCache.addPath(tree, pathWithinSubtree); + return pathWithinSubtree; + } - capturedTypeVarSubstitutor.substitute(capturedTypeVar, capturedTypeVarToAnnotatedTypeVar); + // climb the current path till we see that + // Works when getPath called on the enclosing method, enclosing class. + TreePath current = currentPath; + while (current != null) { + treePathCache.addPath(current.getLeaf(), current); + if (current.getLeaf() == tree) { + return current; + } + current = current.getParentPath(); + } + + // OK, we give up. Use the cache to look up. + return treePathCache.getPath(root, tree); + } + + /** + * Set the tree path for the given artificial tree. + * + *

See {@code + * org.checkerframework.framework.flow.CFCFGBuilder.CFCFGTranslationPhaseOne.handleArtificialTree(Tree)}. + * + * @param tree the artificial {@link Tree} to set the path for + * @param path the {@link TreePath} for the artificial tree + */ + public final void setPathForArtificialTree(Tree tree, TreePath path) { + treePathCache.addPath(tree, path); + } + + /** + * Assert that the type is a type of valid type mirror, i.e. not an ERROR or OTHER type. + * + * @param type an annotated type + * @return true if the type is a valid annotated type, false otherwise + */ + /*package-private*/ static final boolean validAnnotatedType(AnnotatedTypeMirror type) { + if (type == null) { + return false; + } + return validType(type.getUnderlyingType()); + } + + /** + * Used for asserting that a type is valid for converting to an annotated type. + * + * @return true if {@code type} can be converted to an annotated type, false otherwise + */ + private static boolean validType(TypeMirror type) { + if (type == null) { + return false; + } + switch (type.getKind()) { + case ERROR: + case OTHER: + case PACKAGE: + return false; + default: + return true; + } + } + + /** + * Parses all annotation files in the following order: + * + *

    + *
  1. Stub files, see {@link AnnotationFileElementTypes#parseStubFiles()}; + *
  2. Ajava files, see {@link AnnotationFileElementTypes#parseAjavaFiles()}. + *
+ * + *

If a type is annotated with a qualifier from the same hierarchy in more than one stub file, + * the qualifier in the last stub file is applied. + * + *

The annotations are stored by side-effecting {@link #stubTypes} and {@link #ajavaTypes}. + */ + protected void parseAnnotationFiles() { + stubTypes.parseStubFiles(); + ajavaTypes.parseAjavaFiles(); + } + + /** + * Returns all of the declaration annotations whose name equals the passed annotation class (or is + * an alias for it) including annotations: + * + *

    + *
  • on the element + *
  • written in stubfiles + *
  • inherited from overridden methods, (see {@link InheritedAnnotation}) + *
  • inherited from superclasses or super interfaces (see {@link Inherited}) + *
+ * + * @see #getDeclAnnotationNoAliases + * @param elt the element to retrieve the declaration annotation from + * @param anno annotation class + * @return the annotation mirror for anno + */ + @Override + public final AnnotationMirror getDeclAnnotation(Element elt, Class anno) { + AnnotationMirror result = getDeclAnnotation(elt, anno, true); + return result; + } + + /** + * Returns the annotation mirror used to annotate this element, whose name equals the passed + * annotation class. Looks in the same places specified by {@link #getDeclAnnotation(Element, + * Class)}. Returns null if none exists. Does not check for aliases of the annotation class. + * + *

Call this method from a checker that needs to alias annotations for one purpose and not for + * another. For example, in the Lock Checker, {@code @LockingFree} and {@code @ReleasesNoLocks} + * are both aliases of {@code @SideEffectFree} since they are all considered side-effect-free with + * regard to the set of locks held before and after the method call. However, a {@code + * synchronized} block is permitted inside a {@code @ReleasesNoLocks} method but not inside a + * {@code @LockingFree} or {@code @SideEffectFree} method. + * + * @see #getDeclAnnotation + * @param elt the element to retrieve the declaration annotation from + * @param anno annotation class + * @return the annotation mirror for anno + */ + public final @Nullable AnnotationMirror getDeclAnnotationNoAliases( + Element elt, Class anno) { + return getDeclAnnotation(elt, anno, false); + } + + /** + * Returns true if the element appears in a stub file (Currently only works for methods, + * constructors, and fields). + */ + public boolean isFromStubFile(Element element) { + return this.getDeclAnnotation(element, FromStubFile.class) != null; + } + + /** + * Returns true if the element is from bytecode and the if the element did not appear in a stub + * file. Currently only works for methods, constructors, and fields. + */ + public boolean isFromByteCode(Element element) { + if (isFromStubFile(element)) { + return false; + } + return ElementUtils.isElementFromByteCode(element); + } + + /** + * Returns true if redundancy between a stub file and bytecode should be reported. + * + *

For most type systems the default behavior of returning true is correct. For subcheckers, + * redundancy in one of the type hierarchies can be ok. Such implementations should return false. + * + * @return whether to warn about redundancy between a stub file and bytecode + */ + public boolean shouldWarnIfStubRedundantWithBytecode() { + return true; + } + + /** + * Returns the actual annotation mirror used to annotate this element, whose name equals the + * passed annotation class (or is an alias for it). Looks in the same places specified by {@link + * #getDeclAnnotation(Element, Class)}. Returns null if none exists. May return the canonical + * annotation that annotationName is an alias for. + * + *

This is the private implementation of the same-named, public method. + * + *

An option is provided to not check for aliases of annotations. For example, an annotated + * type factory may use aliasing for a pair of annotations for convenience while needing in some + * cases to determine a strict ordering between them, such as when determining whether the + * annotations on an overrider method are more specific than the annotations of an overridden + * method. + * + * @param elt the element to retrieve the annotation from + * @param annoClass the class of the annotation to retrieve + * @param checkAliases whether to return an annotation mirror for an alias of the requested + * annotation class name + * @return the annotation mirror for the requested annotation, or null if not found + */ + private @Nullable AnnotationMirror getDeclAnnotation( + Element elt, Class annoClass, boolean checkAliases) { + return getDeclAnnotation(elt, annoClass.getCanonicalName(), checkAliases); + } + + /** + * Returns the actual annotation mirror used to annotate this element, whose name equals the + * passed canonical annotation name (or is an alias for it). Returns null if none exists. May + * return the canonical annotation that annotationName is an alias for. + * + *

An option is provided not to check for aliases of annotations. For example, an annotated + * type factory may use aliasing for a pair of annotations for convenience while needing in some + * cases to determine a strict ordering between them, such as when determining whether the + * annotations on an overrider method are more specific than the annotations of an overridden + * method. + * + * @param elt the element to retrieve the annotation from + * @param annoName the canonical annotation name to retrieve + * @param checkAliases whether to return an annotation mirror for an alias of the requested + * annotation class name + * @return the annotation mirror for the requested annotation, or null if not found + */ + private AnnotationMirror getDeclAnnotation( + Element elt, @FullyQualifiedName String annoName, boolean checkAliases) { + AnnotationMirrorSet declAnnos = getDeclAnnotations(elt); + + for (AnnotationMirror am : declAnnos) { + if (AnnotationUtils.areSameByName(am, annoName)) { + return am; + } + } + if (!checkAliases) { + return null; + } + // Look through aliases. + Map<@FullyQualifiedName String, AnnotationMirror> aliases = declAliases.get(annoName); + if (aliases == null) { + return null; + } + for (AnnotationMirror am : declAnnos) { + AnnotationMirror match = aliases.get(AnnotationUtils.annotationName(am)); + if (match != null) { + return match; + } } - /** - * Substitutes references to captured type variables. - * - *

Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code - * substitute} to use. - */ - private final CapturedTypeVarSubstitutor capturedTypeVarSubstitutor = - new CapturedTypeVarSubstitutor(); + // Not found. + return null; + } + + /** + * Returns all of the declaration annotations on this element including annotations: + * + *

    + *
  • on the element + *
  • written in stubfiles + *
  • inherited from overridden methods, (see {@link InheritedAnnotation}) + *
  • inherited from superclasses or super interfaces (see {@link Inherited}) + *
+ * + *

This method returns the actual annotations not their aliases. {@link + * #getDeclAnnotation(Element, Class)} returns aliases. + * + * @param elt the element for which to determine annotations + * @return all of the declaration annotations on this element, written in stub files, or inherited + */ + public AnnotationMirrorSet getDeclAnnotations(Element elt) { + AnnotationMirrorSet cachedValue = cacheDeclAnnos.get(elt); + if (cachedValue != null) { + // Found in cache, return result. + return cachedValue; + } + + AnnotationMirrorSet results = new AnnotationMirrorSet(); + // Retrieving the annotations from the element. + // This includes annotations inherited from superclasses, but not superinterfaces or + // overridden methods. + List fromEle = elements.getAllAnnotationMirrors(elt); + for (AnnotationMirror annotation : fromEle) { + try { + results.add(annotation); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // If a CompletionFailure occurs, issue a warning. + checker.reportWarning( + annotation.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(elt), + annotation); + } + } - /** - * Substitutes references to captured types in {@code type} using {@code - * capturedTypeVarToAnnotatedTypeVar}. - * - *

Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code - * substitute} to use. - */ - private static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier { - - /** A mapping from a captured type variable to its AnnotatedTypeVariable. */ - private Map capturedTypeVarToAnnotatedTypeVar; - - /** - * Substitutes references to captured type variable in {@code type} using {@code - * capturedTypeVarToAnnotatedTypeVar}. - * - *

Unlike {@link #typeVarSubstitutor}, this method does not copy the type. - * - * @param type the type whose captured type variables are substituted with those in {@code - * capturedTypeVarToAnnotatedTypeVar} - * @param capturedTypeVarToAnnotatedTypeVar mapping from a TypeVariable (which is a captured - * type variable) to an AnnotatedTypeVariable - */ - private void substitute( - AnnotatedTypeVariable type, - Map capturedTypeVarToAnnotatedTypeVar) { - this.capturedTypeVarToAnnotatedTypeVar = capturedTypeVarToAnnotatedTypeVar; - IdentityHashMap mapping = - new IdentityHashMap<>(); - visit(type.getLowerBound(), mapping); - visit(type.getUpperBound(), mapping); - this.capturedTypeVarToAnnotatedTypeVar = null; + // Add annotations from annotation files. + results.addAll(stubTypes.getDeclAnnotations(elt)); + results.addAll(ajavaTypes.getDeclAnnotations(elt)); + if (currentFileAjavaTypes != null) { + results.addAll(currentFileAjavaTypes.getDeclAnnotations(elt)); + } + + if (elt.getKind() == ElementKind.METHOD) { + // Retrieve the annotations from the overridden method's element. + inheritOverriddenDeclAnnos((ExecutableElement) elt, results); + } else if (ElementUtils.isTypeDeclaration(elt)) { + inheritOverriddenDeclAnnosFromTypeDecl(elt.asType(), results); + } + + // Add the element and its annotations to the cache. + if (!stubTypes.isParsing() + && !ajavaTypes.isParsing() + && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { + cacheDeclAnnos.put(elt, results); + } + return results; + } + + /** + * Adds into {@code results} the inherited declaration annotations found in all elements of the + * super types of {@code typeMirror}. (Both superclasses and superinterfaces.) + * + * @param typeMirror type + * @param results set of AnnotationMirrors to which this method adds declarations annotations + */ + private void inheritOverriddenDeclAnnosFromTypeDecl( + TypeMirror typeMirror, AnnotationMirrorSet results) { + List superTypes = types.directSupertypes(typeMirror); + for (TypeMirror superType : superTypes) { + TypeElement elt = TypesUtils.getTypeElement(superType); + if (elt == null) { + continue; + } + AnnotationMirrorSet superAnnos = getDeclAnnotations(elt); + for (AnnotationMirror annotation : superAnnos) { + List annotationsOnAnnotation; + try { + annotationsOnAnnotation = + annotation.getAnnotationType().asElement().getAnnotationMirrors(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. + checker.reportWarning( + annotation.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(elt), + annotation); + continue; + } + if (containsSameByClass(annotationsOnAnnotation, Inherited.class) + || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) { + addOrMerge(results, annotation); } - - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - AnnotatedTypeMirror cap = - capturedTypeVarToAnnotatedTypeVar.get(original.getUnderlyingType()); - if (cap != null) { - return cap; - } - return super.visitTypeVariable(original, originalToCopy); + } + } + } + + /** + * Adds into {@code results} the declaration annotations found in all elements that the method + * element {@code elt} overrides. + * + * @param elt method element + * @param results {@code elt} local declaration annotations. The ones found in stub files and in + * the element itself. + */ + private void inheritOverriddenDeclAnnos(ExecutableElement elt, AnnotationMirrorSet results) { + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, this, elt); + + if (overriddenMethods != null) { + for (ExecutableElement superElt : overriddenMethods.values()) { + AnnotationMirrorSet superAnnos = getDeclAnnotations(superElt); + + for (AnnotationMirror annotation : superAnnos) { + List annotationsOnAnnotation; + try { + annotationsOnAnnotation = + annotation.getAnnotationType().asElement().getAnnotationMirrors(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. + checker.reportWarning( + annotation.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(elt), + annotation); + continue; + } + if (containsSameByClass(annotationsOnAnnotation, InheritedAnnotation.class) + || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) { + addOrMerge(results, annotation); + } } - - @Override - protected T makeOrReturnCopy( - T original, - IdentityHashMap originalToCopy) { - AnnotatedTypeMirror copy = originalToCopy.get(original); - if (copy != null) { - @SuppressWarnings( - "unchecked" // the key-value pairs in originalToCopy are always the same - // kind of AnnotatedTypeMirror. - ) - T copyCasted = (T) copy; - return copyCasted; - } - - if (original.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeMirror captureType = - capturedTypeVarToAnnotatedTypeVar.get( - ((AnnotatedTypeVariable) original).getUnderlyingType()); - if (captureType != null) { - originalToCopy.put(original, captureType); - @SuppressWarnings( - "unchecked" // the key-value pairs in originalToCopy are always the same - // kind of AnnotatedTypeMirror. - ) - T captureTypeCasted = (T) captureType; - return captureTypeCasted; - } + } + } + } + + private void addOrMerge(AnnotationMirrorSet results, AnnotationMirror annotation) { + if (AnnotationUtils.containsSameByName(results, annotation)) { + /* + * TODO: feature request: figure out a way to merge multiple annotations + * of the same kind. For some annotations this might mean merging some + * arrays, for others it might mean converting a single annotation into a + * container annotation. We should define a protected method for subclasses + * to adapt the behavior. + * For now, do nothing and just take the first, most concrete, annotation. + AnnotationMirror prev = null; + for (AnnotationMirror an : results) { + if (AnnotationUtils.areSameByName(an, annotation)) { + prev = an; + break; + } + } + results.remove(prev); + AnnotationMirror merged = ...; + results.add(merged); + */ + } else { + results.add(annotation); + } + } + + /** + * Returns a list of all declaration annotations used to annotate the element, which have a + * meta-annotation (i.e., an annotation on that annotation) with class {@code + * metaAnnotationClass}. + * + * @param element the element for which to determine annotations + * @param metaAnnotationClass the class of the meta-annotation that needs to be present + * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at + * {@code element}, and {@code metaAnno} is the annotation mirror (of type {@code + * metaAnnotationClass}) used to meta-annotate the declaration of {@code anno} + */ + public List> getDeclAnnotationWithMetaAnnotation( + Element element, Class metaAnnotationClass) { + List> result = new ArrayList<>(); + AnnotationMirrorSet annotationMirrors = getDeclAnnotations(element); + + for (AnnotationMirror candidate : annotationMirrors) { + List metaAnnotationsOnAnnotation; + try { + metaAnnotationsOnAnnotation = + candidate.getAnnotationType().asElement().getAnnotationMirrors(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Fix for Issue 309: If a CompletionFailure occurs, issue a warning. + // I didn't find a nicer alternative to check whether the Symbol can be completed. + // The completer field of a Symbol might be non-null also in successful cases. + // Issue a warning (exception only happens once) and continue. + checker.reportWarning( + candidate.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(element), + candidate); + continue; + } + // First call copier, if exception, continue normal modula laws. + for (AnnotationMirror ma : metaAnnotationsOnAnnotation) { + if (areSameByClass(ma, metaAnnotationClass)) { + // This candidate has the right kind of meta-annotation. + // It might be a real contract, or a list of contracts. + if (isListForRepeatedAnnotation(candidate)) { + @SuppressWarnings("deprecation") // concrete annotation class is not known + List wrappedCandidates = + AnnotationUtils.getElementValueArray( + candidate, "value", AnnotationMirror.class, false); + for (AnnotationMirror wrappedCandidate : wrappedCandidates) { + result.add(IPair.of(wrappedCandidate, ma)); } - originalToCopy.put(original, original); - return original; + } else { + result.add(IPair.of(candidate, ma)); + } } + } } - - /** - * Check that a wildcard is an extends wildcard. - * - * @param awt the wildcard type - * @return true if awt is an extends wildcard - */ - private boolean isExtendsWildcard(AnnotatedWildcardType awt) { - return awt.getUnderlyingType().getSuperBound() == null; + return result; + } + + /** Cache for {@link #isListForRepeatedAnnotation}. */ + private final Map isListForRepeatedAnnotationCache = new HashMap<>(); + + /** + * Returns true if the given annotation is a wrapper for multiple repeated annotations. + * + * @param a an annotation that might be a wrapper + * @return true if the argument is a wrapper for multiple repeated annotations + */ + private boolean isListForRepeatedAnnotation(AnnotationMirror a) { + DeclaredType annotationType = a.getAnnotationType(); + Boolean resultObject = isListForRepeatedAnnotationCache.get(annotationType); + if (resultObject != null) { + return resultObject; + } + boolean result = isListForRepeatedAnnotationImplementation(annotationType); + isListForRepeatedAnnotationCache.put(annotationType, result); + return result; + } + + /** + * Returns true if the annotation is a wrapper for multiple repeated annotations. + * + * @param annotationType the declaration of the annotation to test + * @return true if the annotation is a wrapper for multiple repeated annotations + */ + private boolean isListForRepeatedAnnotationImplementation(DeclaredType annotationType) { + TypeMirror enclosingType = annotationType.getEnclosingType(); + if (enclosingType == null) { + return false; + } + if (!annotationType.asElement().getSimpleName().contentEquals("List")) { + return false; + } + List annoElements = annotationType.asElement().getEnclosedElements(); + if (annoElements.size() != 1) { + return false; + } + // TODO: should check that the type of the single element is: "array of enclosingType". + return true; + } + + /** + * Returns a list of all annotations used to annotate this element, which have a meta-annotation + * (i.e., an annotation on that annotation) with class {@code metaAnnotationClass}. + * + * @param element the element at which to look for annotations + * @param metaAnnotationClass the class of the meta-annotation that needs to be present + * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at + * {@code element}, and {@code metaAnno} is the annotation mirror used to annotate {@code + * anno}. + */ + public List> getAnnotationWithMetaAnnotation( + Element element, Class metaAnnotationClass) { + AnnotationMirrorSet annotationMirrors = new AnnotationMirrorSet(); + // Consider real annotations. + annotationMirrors.addAll(getAnnotatedType(element).getAnnotations()); + // Consider declaration annotations + annotationMirrors.addAll(getDeclAnnotations(element)); + + List> result = new ArrayList<>(); + + // Go through all annotations found. + for (AnnotationMirror annotation : annotationMirrors) { + List annotationsOnAnnotation = + annotation.getAnnotationType().asElement().getAnnotationMirrors(); + for (AnnotationMirror a : annotationsOnAnnotation) { + if (areSameByClass(a, metaAnnotationClass)) { + result.add(IPair.of(annotation, a)); + } + } + } + return result; + } + + /** + * Whether or not the {@code annotatedTypeMirror} has a qualifier parameter. + * + * @param annotatedTypeMirror the type to check + * @param top the top of the hierarchy to check + * @return true if the type has a qualifier parameter + */ + public boolean hasQualifierParameterInHierarchy( + AnnotatedTypeMirror annotatedTypeMirror, AnnotationMirror top) { + return AnnotationUtils.containsSame(getQualifierParameterHierarchies(annotatedTypeMirror), top); + } + + /** + * Whether or not the {@code element} has a qualifier parameter. + * + * @param element element to check + * @param top the top of the hierarchy to check + * @return true if the type has a qualifier parameter + */ + public boolean hasQualifierParameterInHierarchy(@Nullable Element element, AnnotationMirror top) { + if (element == null) { + return false; + } + return AnnotationUtils.containsSame(getQualifierParameterHierarchies(element), top); + } + + /** + * Returns whether the {@code HasQualifierParameter} annotation was explicitly written on {@code + * element} for the hierarchy given by {@code top}. + * + * @param element the Element to check + * @param top the top qualifier for the hierarchy to check + * @return whether the class given by {@code element} has been explicitly annotated with {@code + * HasQualifierParameter} for the given hierarchy + */ + public boolean hasExplicitQualifierParameterInHierarchy(Element element, AnnotationMirror top) { + return AnnotationUtils.containsSame( + getSupportedAnnotationsInElementAnnotation( + element, HasQualifierParameter.class, hasQualifierParameterValueElement), + top); + } + + /** + * Returns whether the {@code NoQualifierParameter} annotation was explicitly written on {@code + * element} for the hierarchy given by {@code top}. + * + * @param element the Element to check + * @param top the top qualifier for the hierarchy to check + * @return whether the class given by {@code element} has been explicitly annotated with {@code + * NoQualifierParameter} for the given hierarchy + */ + public boolean hasExplicitNoQualifierParameterInHierarchy(Element element, AnnotationMirror top) { + return AnnotationUtils.containsSame( + getSupportedAnnotationsInElementAnnotation( + element, NoQualifierParameter.class, noQualifierParameterValueElement), + top); + } + + /** + * Returns the set of top annotations representing all the hierarchies for which this type has a + * qualifier parameter. + * + * @param annotatedType the type to check + * @return the set of top annotations representing all the hierarchies for which this type has a + * qualifier parameter + */ + public AnnotationMirrorSet getQualifierParameterHierarchies(AnnotatedTypeMirror annotatedType) { + while (annotatedType.getKind() == TypeKind.TYPEVAR + || annotatedType.getKind() == TypeKind.WILDCARD) { + if (annotatedType.getKind() == TypeKind.TYPEVAR) { + annotatedType = ((AnnotatedTypeVariable) annotatedType).getUpperBound(); + } else if (annotatedType.getKind() == TypeKind.WILDCARD) { + annotatedType = ((AnnotatedWildcardType) annotatedType).getSuperBound(); + } } - /** - * Returns the utility class for working with {@link Element}s. - * - * @return the utility class for working with {@link Element}s - */ - public final Elements getElementUtils() { - return this.elements; + if (annotatedType.getKind() != TypeKind.DECLARED) { + return AnnotationMirrorSet.emptySet(); } - /** Accessor for the tree utilities. */ - public Trees getTreeUtils() { - return this.trees; + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) annotatedType; + Element element = declaredType.getUnderlyingType().asElement(); + if (element == null) { + return AnnotationMirrorSet.emptySet(); } + return getQualifierParameterHierarchies(element); + } - /** - * Accessor for the processing environment. - * - * @return the processing environment - */ - public ProcessingEnvironment getProcessingEnv() { - return this.processingEnv; + /** + * Returns the set of top annotations representing all the hierarchies for which this element has + * a qualifier parameter. + * + * @param element the Element to check + * @return the set of top annotations representing all the hierarchies for which this element has + * a qualifier parameter + */ + public AnnotationMirrorSet getQualifierParameterHierarchies(Element element) { + if (!ElementUtils.isTypeDeclaration(element)) { + return AnnotationMirrorSet.emptySet(); } - /** Matches addition of a constant. */ - private static final Pattern plusConstant = Pattern.compile(" *\\+ *(-?[0-9]+)$"); + AnnotationMirrorSet found = new AnnotationMirrorSet(); + found.addAll( + getSupportedAnnotationsInElementAnnotation( + element, HasQualifierParameter.class, hasQualifierParameterValueElement)); + AnnotationMirrorSet hasQualifierParameterTops = new AnnotationMirrorSet(); + PackageElement packageElement = ElementUtils.enclosingPackage(element); - /** Matches subtraction of a constant. */ - private static final Pattern minusConstant = Pattern.compile(" *- *(-?[0-9]+)$"); + // Traverse all packages containing this element. + while (packageElement != null) { + AnnotationMirrorSet packageDefaultTops = + getSupportedAnnotationsInElementAnnotation( + packageElement, HasQualifierParameter.class, hasQualifierParameterValueElement); + hasQualifierParameterTops.addAll(packageDefaultTops); - /** Matches a string whose only parens are at the beginning and end of the string. */ - private static final Pattern surroundingParensPattern = Pattern.compile("^\\([^()]\\)"); + packageElement = ElementUtils.parentPackage(packageElement, elements); + } - /** - * Given an expression, split it into a subexpression and a constant offset. For example: - * - *

{@code
-     * "a" => <"a", "0">
-     * "a + 5" => <"a", "5">
-     * "a + -5" => <"a", "-5">
-     * "a - 5" => <"a", "-5">
-     * }
- * - * There are methods that can only take as input an expression that represents a JavaExpression. - * The purpose of this is to pre-process expressions to make those methods more likely to - * succeed. - * - * @param expression an expression to remove a constant offset from - * @return a sub-expression and a constant offset. The offset is "0" if this routine is unable - * to splite the given expression - */ - // TODO: generalize. There is no reason this couldn't handle arbitrary addition and subtraction - // expressions, given the Index Checker's support for OffsetEquation. That might even make its - // implementation simpler. - public static IPair getExpressionAndOffset(String expression) { - String expr = expression; - String offset = "0"; - - // Is this normalization necessary? - // Remove surrounding whitespace. - expr = expr.trim(); - // Remove surrounding parentheses. - if (surroundingParensPattern.matcher(expr).matches()) { - expr = expr.substring(1, expr.length() - 2).trim(); - } + AnnotationMirrorSet noQualifierParamClasses = + getSupportedAnnotationsInElementAnnotation( + element, NoQualifierParameter.class, noQualifierParameterValueElement); + for (AnnotationMirror anno : hasQualifierParameterTops) { + if (!AnnotationUtils.containsSame(noQualifierParamClasses, anno)) { + found.add(anno); + } + } - Matcher mPlus = plusConstant.matcher(expr); - Matcher mMinus = minusConstant.matcher(expr); - if (mPlus.find()) { - expr = expr.substring(0, mPlus.start()); - offset = mPlus.group(1); - } else if (mMinus.find()) { - expr = expr.substring(0, mMinus.start()); - offset = negateConstant(mMinus.group(1)); + return found; + } + + /** + * Returns a set of supported annotation mirrors corresponding to the annotation classes listed in + * the value element of an annotation with class {@code annoClass} on {@code element}. + * + * @param element the Element to check + * @param annoClass the class for an annotation that's written on elements, whose value element is + * a list of annotation classes. It is always HasQualifierParameter or NoQualifierParameter + * @param valueElement the {@code value} field/element of an annotation with class {@code + * annoClass} + * @return the set of supported annotations with classes listed in the value element of an + * annotation with class {@code annoClass} on the {@code element}. Returns an empty set if + * {@code annoClass} is not written on {@code element} or {@code element} is null. + */ + private AnnotationMirrorSet getSupportedAnnotationsInElementAnnotation( + @Nullable Element element, + Class annoClass, + ExecutableElement valueElement) { + if (element == null) { + return AnnotationMirrorSet.emptySet(); + } + // TODO: caching + AnnotationMirror annotation = getDeclAnnotation(element, annoClass); + if (annotation == null) { + return AnnotationMirrorSet.emptySet(); + } + + AnnotationMirrorSet found = new AnnotationMirrorSet(); + List<@CanonicalName Name> qualClasses = + AnnotationUtils.getElementValueClassNames(annotation, valueElement); + for (Name qual : qualClasses) { + AnnotationMirror annotationMirror = AnnotationBuilder.fromName(elements, qual); + if (isSupportedQualifier(annotationMirror)) { + found.add(annotationMirror); + } + } + return found; + } + + /** + * A scanner that replaces annotations in one type with annotations from another. Used by {@link + * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror)} and {@link + * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. + */ + private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); + + /** + * Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code from} + * will be used everywhere they exist, but annotations in {@code to} will be kept anywhere that + * {@code from} is unannotated. + * + * @param from the annotated type mirror from which to take new annotations + * @param to the annotated type mirror to which the annotations will be added + */ + public void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + annotatedTypeReplacer.visit(from, to); + } + + /** + * Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}. + * Annotations from {@code from} will be used everywhere they exist, but annotations in {@code to} + * will be kept anywhere that {@code from} is unannotated. + * + * @param from the annotated type mirror from which to take new annotations + * @param to the annotated type mirror to which the annotations will be added + * @param top the top type of the hierarchy whose annotations will be added + */ + public void replaceAnnotations( + AnnotatedTypeMirror from, AnnotatedTypeMirror to, AnnotationMirror top) { + annotatedTypeReplacer.setTop(top); + annotatedTypeReplacer.visit(from, to); + annotatedTypeReplacer.setTop(null); + } + + /** The implementation of the visitor for #containsUninferredTypeArguments. */ + private final SimpleAnnotatedTypeScanner uninferredTypeArgumentScanner = + new SimpleAnnotatedTypeScanner<>( + (type, p) -> + type.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type).isUninferredTypeArgument(), + Boolean::logicalOr, + false); + + /** + * Returns whether this type or any component type is a wildcard type for which Java 7 type + * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType. + * + * @param type type to check + * @return whether this type or any component type is a wildcard type for which Java 7 type + * inference is insufficient + */ + public boolean containsUninferredTypeArguments(AnnotatedTypeMirror type) { + return uninferredTypeArgumentScanner.visit(type); + } + + /** + * Returns a wildcard type to be used as a type argument when the correct type could not be + * inferred. The wildcard will be marked as an uninferred wildcard so that {@link + * AnnotatedWildcardType#isUninferredTypeArgument()} returns true. + * + *

This method should only be used by type argument inference. + * org.checkerframework.framework.util.AnnotatedTypes.inferTypeArguments(ProcessingEnvironment, + * AnnotatedTypeFactory, ExpressionTree, ExecutableElement) + * + * @param typeVar the TypeVariable that could not be inferred + * @return a wildcard that is marked as an uninferred type argument + */ + public AnnotatedWildcardType getUninferredWildcardType(AnnotatedTypeVariable typeVar) { + final boolean intersectionType; + final TypeMirror boundType; + if (typeVar.getUpperBound().getKind() == TypeKind.INTERSECTION) { + boundType = typeVar.getUpperBound().directSupertypes().get(0).getUnderlyingType(); + intersectionType = true; + } else { + boundType = typeVar.getUnderlyingType().getUpperBound(); + intersectionType = false; + } + + WildcardType wc = types.getWildcardType(boundType, null); + AnnotatedWildcardType wctype = + (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wc, this, false); + wctype.setTypeVariable(typeVar.getUnderlyingType()); + if (!intersectionType) { + wctype.setExtendsBound(typeVar.getUpperBound().deepCopy()); + } else { + wctype.getExtendsBound().addAnnotations(typeVar.getUpperBound().getAnnotations()); + } + wctype.setSuperBound(typeVar.getLowerBound().deepCopy()); + wctype.addAnnotations(typeVar.getAnnotations()); + addDefaultAnnotations(wctype); + wctype.setUninferredTypeArgument(); + return wctype; + } + + /** + * Returns the function type that this member reference targets. + * + *

The function type is the type of the single method declared in the functional interface + * adapted as if it were invoked using the functional interface as the receiver expression. + * + *

The target type of a member reference is the type to which it is assigned or casted. + * + * @param tree member reference tree + * @return the function type that this method reference targets + */ + public AnnotatedExecutableType getFunctionTypeFromTree(MemberReferenceTree tree) { + return getFnInterfaceFromTree(tree).second; + } + + /** + * Returns the function type that this lambda targets. + * + *

The function type is the type of the single method declared in the functional interface + * adapted as if it were invoked using the functional interface as the receiver expression. + * + *

The target type of a lambda is the type to which it is assigned or casted. + * + * @param tree lambda expression tree + * @return the function type that this lambda targets + */ + public AnnotatedExecutableType getFunctionTypeFromTree(LambdaExpressionTree tree) { + return getFnInterfaceFromTree(tree).second; + } + + /** + * Returns the functional interface and the function type that this lambda or member references + * targets. + * + *

The function type is the type of the single method declared in the functional interface + * adapted as if it were invoked using the functional interface as the receiver expression. + * + *

The target type of a lambda or a method reference is the type to which it is assigned or + * casted. + * + * @param tree lambda expression tree or member reference tree + * @return the functional interface and the function type that this method reference or lambda + * targets + */ + public IPair getFnInterfaceFromTree(Tree tree) { + // Functional interface + AnnotatedTypeMirror functionalInterfaceType = getFunctionalInterfaceType(tree); + if (functionalInterfaceType.getKind() == TypeKind.DECLARED) { + functionalInterfaceType = + makeGroundTargetType( + (AnnotatedDeclaredType) functionalInterfaceType, + (DeclaredType) TreeUtils.typeOf(tree)); + } + + // Functional method + ExecutableElement fnElement = TreeUtils.findFunction(tree, processingEnv); + + // Function type + AnnotatedExecutableType functionType = + AnnotatedTypes.asMemberOf(types, this, functionalInterfaceType, fnElement); + return IPair.of(functionalInterfaceType, functionType); + } + + /** + * Get the AnnotatedDeclaredType for the FunctionalInterface from assignment context of the method + * reference or lambda expression which may be a variable assignment, a method call, or a cast. + * + *

The assignment context is not always correct, so we must search up the AST. It will + * recursively search for lambdas nested in lambdas. + * + * @param tree the tree of the lambda or method reference + * @return the functional interface type or an uninferred type argument + */ + private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) { + TreePath parentPath = getPath(tree).getParentPath(); + Tree parentTree = parentPath.getLeaf(); + switch (parentTree.getKind()) { + case PARENTHESIZED: + return getFunctionalInterfaceType(parentTree); + + case TYPE_CAST: + TypeCastTree cast = (TypeCastTree) parentTree; + assertIsFunctionalInterface(trees.getTypeMirror(getPath(cast.getType())), parentTree, tree); + AnnotatedTypeMirror castATM = getAnnotatedType(cast.getType()); + if (castATM.getKind() == TypeKind.INTERSECTION) { + AnnotatedIntersectionType itype = (AnnotatedIntersectionType) castATM; + for (AnnotatedTypeMirror t : itype.directSupertypes()) { + if (TypesUtils.isFunctionalInterface(t.getUnderlyingType(), getProcessingEnv())) { + return t; + } + } + // We should never reach here: isFunctionalInterface performs the same check + // and would have raised an error already. + throw new BugInCF( + "Expected the type of a cast tree in an assignment context to contain" + + " a functional interface bound." + + " Found type: %s for tree: %s in lambda tree: %s", + castATM, cast, tree); + } + return castATM; + + case NEW_CLASS: + NewClassTree newClass = (NewClassTree) parentTree; + int indexOfLambda = newClass.getArguments().indexOf(tree); + ParameterizedExecutableType con = this.constructorFromUse(newClass); + AnnotatedTypeMirror constructorParam = + AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(con.executableType, indexOfLambda); + assertIsFunctionalInterface(constructorParam.getUnderlyingType(), parentTree, tree); + return constructorParam; + + case NEW_ARRAY: + NewArrayTree newArray = (NewArrayTree) parentTree; + AnnotatedArrayType newArrayATM = getAnnotatedType(newArray); + AnnotatedTypeMirror elementATM = newArrayATM.getComponentType(); + assertIsFunctionalInterface(elementATM.getUnderlyingType(), parentTree, tree); + return elementATM; + + case METHOD_INVOCATION: + MethodInvocationTree method = (MethodInvocationTree) parentTree; + int index = method.getArguments().indexOf(tree); + ParameterizedExecutableType exe = this.methodFromUse(method); + AnnotatedTypeMirror param = + AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(exe.executableType, index); + if (param.getKind() == TypeKind.WILDCARD) { + // param is an uninferred wildcard. + TypeMirror typeMirror = TreeUtils.typeOf(tree); + param = AnnotatedTypeMirror.createType(typeMirror, this, false); + addDefaultAnnotations(param); + } + assertIsFunctionalInterface(param.getUnderlyingType(), parentTree, tree); + return param; + + case VARIABLE: + VariableTree varTree = (VariableTree) parentTree; + assertIsFunctionalInterface(TreeUtils.typeOf(varTree), parentTree, tree); + return getAnnotatedType(varTree.getType()); + + case ASSIGNMENT: + AssignmentTree assignmentTree = (AssignmentTree) parentTree; + assertIsFunctionalInterface(TreeUtils.typeOf(assignmentTree), parentTree, tree); + return getAnnotatedType(assignmentTree.getVariable()); + + case RETURN: + Tree enclosing = + TreePathUtil.enclosingOfKind( + getPath(parentTree), + new HashSet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION))); + + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = (MethodTree) enclosing; + return getAnnotatedType(enclosingMethod.getReturnType()); + } else { + LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) enclosing; + AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); + return methodExe.getReturnType(); + } + + case LAMBDA_EXPRESSION: + LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) parentTree; + AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); + return methodExe.getReturnType(); + + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditionalExpressionTree = + (ConditionalExpressionTree) parentTree; + AnnotatedTypeMirror trueType = + getAnnotatedType(conditionalExpressionTree.getTrueExpression()); + AnnotatedTypeMirror falseType = + getAnnotatedType(conditionalExpressionTree.getFalseExpression()); + + // Known cases where we must use LUB because falseType/trueType will not be equal: + // a) when one of the types is a type variable that extends a functional interface + // or extends a type variable that extends a functional interface + // b) When one of the two sides of the expression is a reference to a sub-interface. + // e.g. interface ConsumeStr { + // public void consume(String s) + // } + // interface SubConsumer extends ConsumeStr { + // default void someOtherMethod() { ... } + // } + // SubConsumer s = ...; + // ConsumeStr stringConsumer = (someCondition) ? s : System.out::println; + AnnotatedTypeMirror conditionalType = + AnnotatedTypes.leastUpperBound(this, trueType, falseType); + assertIsFunctionalInterface(conditionalType.getUnderlyingType(), parentTree, tree); + return conditionalType; + case CASE: + // Get the functional interface type of the whole switch expression. + Tree switchTree = parentPath.getParentPath().getLeaf(); + return getFunctionalInterfaceType(switchTree); + + default: + if (parentTree.getKind().toString().equals("YIELD")) { + TreePath pathToCase = TreePathUtil.pathTillOfKind(parentPath, Kind.CASE); + return getFunctionalInterfaceType(pathToCase.getParentPath().getLeaf()); } - - if (offset.equals("-0")) { - offset = "0"; + throw new BugInCF( + "Could not find functional interface from assignment context. " + + "Unexpected tree type: " + + parentTree.getKind() + + " For lambda tree: " + + tree); + } + } + + /** + * Throws an exception if the type is not a funtional interface. + * + * @param typeMirror a type that must be a funtional interface + * @param contextTree the tree that has the given type; used only for diagnostic messages + * @param tree a labmba tree that encloses {@code contextTree}; used only for diagnostic messages + */ + private void assertIsFunctionalInterface(TypeMirror typeMirror, Tree contextTree, Tree tree) { + if (typeMirror.getKind() == TypeKind.WILDCARD) { + // Ignore wildcards, because they are uninferred type arguments. + return; + } + Type type = (Type) typeMirror; + if (TypesUtils.isFunctionalInterface(type, processingEnv)) { + return; + } + + if (type.getKind() == TypeKind.INTERSECTION) { + IntersectionType itype = (IntersectionType) type; + for (TypeMirror t : itype.getBounds()) { + if (TypesUtils.isFunctionalInterface(t, processingEnv)) { + // As long as any of the bounds is a functional interface, we should be fine. + return; } + } + } - expr = expr.intern(); - offset = offset.intern(); + throw new BugInCF( + "Expected the type of %s tree in assignment context to be a functional interface. " + + "Found type: %s for tree: %s in lambda tree: %s", + contextTree.getKind(), type, contextTree, tree); + } + + /** + * Create the ground target type of the functional interface. + * + *

Basically, it replaces the wildcards with their bounds doing a capture conversion like glb + * for extends bounds. + * + * @see "JLS 9.9" + * @param functionalType the functional interface type + * @param groundTargetJavaType the Java type as found by javac + * @return the grounded functional type + */ + private AnnotatedDeclaredType makeGroundTargetType( + AnnotatedDeclaredType functionalType, DeclaredType groundTargetJavaType) { + if (functionalType.getTypeArguments().isEmpty()) { + return functionalType; + } + + List bounds = + this.typeVariablesFromUse( + functionalType, (TypeElement) functionalType.getUnderlyingType().asElement()); + + boolean sizesDiffer = + functionalType.getTypeArguments().size() != groundTargetJavaType.getTypeArguments().size(); + + // This is the declared type of the functional type meaning that the type arguments are the + // type parameters. + DeclaredType declaredType = + (DeclaredType) functionalType.getUnderlyingType().asElement().asType(); + Map typeVarToTypeArg = + new HashMap<>(functionalType.getTypeArguments().size()); + for (int i = 0; i < functionalType.getTypeArguments().size(); i++) { + TypeVariable typeVariable = (TypeVariable) declaredType.getTypeArguments().get(i); + AnnotatedTypeMirror argType = functionalType.getTypeArguments().get(i); + + if (argType.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) argType; + + TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType(); + + if (wildcardType.isUninferredTypeArgument()) { + // Keep the uninferred type so that it is ignored by later subtyping and + // containment checks. + typeVarToTypeArg.put(typeVariable, wildcardType); + } else if (isExtendsWildcard(wildcardType)) { + TypeMirror correctArgType; + if (sizesDiffer) { + // The Java type is raw. + TypeMirror typeParamUbType = bounds.get(i).getUpperBound().getUnderlyingType(); + correctArgType = + TypesUtils.greatestLowerBound( + typeParamUbType, wildcardUbType, this.checker.getProcessingEnvironment()); + } else { + correctArgType = groundTargetJavaType.getTypeArguments().get(i); + } - return IPair.of(expr, offset); - } + final AnnotatedTypeMirror newArg; + if (types.isSameType(wildcardUbType, correctArgType)) { + newArg = wildcardType.getExtendsBound().deepCopy(); + } else if (correctArgType.getKind() == TypeKind.TYPEVAR) { + newArg = this.toAnnotatedType(correctArgType, false); + AnnotatedTypeVariable newArgAsTypeVar = (AnnotatedTypeVariable) newArg; + newArgAsTypeVar + .getUpperBound() + .replaceAnnotations(wildcardType.getExtendsBound().getAnnotations()); + newArgAsTypeVar + .getLowerBound() + .replaceAnnotations(wildcardType.getSuperBound().getAnnotations()); + } else { + newArg = this.toAnnotatedType(correctArgType, false); + newArg.replaceAnnotations(wildcardType.getExtendsBound().getAnnotations()); + } - /** - * Given an expression string, returns its negation. - * - * @param constantExpression a string representing an integer constant - * @return the negation of constantExpression - */ - // Also see Subsequence.negateString which is similar but more sophisticated. - public static String negateConstant(String constantExpression) { - if (constantExpression.startsWith("-")) { - return constantExpression.substring(1); + typeVarToTypeArg.put(typeVariable, newArg); } else { - if (constantExpression.startsWith("+")) { - constantExpression = constantExpression.substring(1); - } - return "-" + constantExpression; + typeVarToTypeArg.put(typeVariable, wildcardType.getSuperBound()); } + } else { + typeVarToTypeArg.put(typeVariable, argType); + } } - /** - * Returns {@code null} or an annotated type mirror that type argument inference should assume - * {@code expressionTree} is assigned to. - * - *

If {@code null} is returned, inference proceeds normally. - * - *

If a type is returned, then inference assumes that {@code expressionTree} was asigned to - * it. This biases the inference algorithm toward the annotations in the returned type. In - * particular, if the annotations on type variables in invariant positions are a super type of - * the annotations inferred, the super type annotations are chosen. - * - *

This implementation returns null, but subclasses may override this method to return a - * type. - * - * @param expressionTree an expression which has no assignment context and for which type - * arguments need to be inferred - * @return {@code null} or an annotated type mirror that inferrence should pretend {@code - * expressionTree} is assigned to - */ - public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { - return null; + // The ground functional type must be created using type variable substitution or else the + // underlying type will not match the annotated type. + AnnotatedDeclaredType groundFunctionalType = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType(declaredType, this, functionalType.isDeclaration()); + initializeAtm(groundFunctionalType); + groundFunctionalType = + (AnnotatedDeclaredType) + getTypeVarSubstitutor().substitute(typeVarToTypeArg, groundFunctionalType); + groundFunctionalType.addAnnotations(functionalType.getAnnotations()); + + // When the groundTargetJavaType is different from the underlying type of functionalType, + // only the main annotations are copied. Add default annotations in places without + // annotations. + addDefaultAnnotations(groundFunctionalType); + return groundFunctionalType; + } + + /** + * Return true if {@code type} should be captured. + * + *

{@code type} should be captured if all of the following are true: + * + *

    + *
  • {@code type} and {@code typeMirror} are both declared types. + *
  • {@code type} does not have an uninferred type argument and its underlying type is not + * raw. + *
  • {@code type} has a wildcard as a type argument and {@code typeMirror} has a captured type + * variable as the corresponding type argument. + *
+ * + * @param type annotated type that might need to be captured + * @param typeMirror the capture of the underlying type of {@code type} + * @return true if {@code type} should be captured + */ + private boolean shouldCapture(AnnotatedTypeMirror type, TypeMirror typeMirror) { + if (type.getKind() != TypeKind.DECLARED || typeMirror.getKind() != TypeKind.DECLARED) { + return false; + } + + AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; + DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; + if (capturedTypeMirror.getTypeArguments().isEmpty()) { + return false; + } + + if (uncapturedType.isUnderlyingTypeRaw() || uncapturedType.containsUninferredTypeArguments()) { + return false; + } + + if (capturedTypeMirror.getTypeArguments().size() != uncapturedType.getTypeArguments().size()) { + throw new BugInCF( + "Not the same number of type arguments: capturedTypeMirror: %s uncapturedType:" + " %s", + capturedTypeMirror, uncapturedType); + } + + for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { + TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD + && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) + || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { + return true; + } } - - /** - * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. - * - *

This method is faster than {@link AnnotationUtils#areSameByClass(AnnotationMirror, Class)} - * because it caches the name of the class rather than computing it each time. - * - * @param am the AnnotationMirror whose class to compare - * @param annoClass the class to compare - * @return true if annoclass is the class of am - */ - public boolean areSameByClass(AnnotationMirror am, Class annoClass) { - if (!shouldCache) { - return AnnotationUtils.areSameByName(am, annoClass.getCanonicalName()); + return false; + } + + /** + * Apply capture conversion to {@code typeToCapture}. + * + *

Capture conversion is the process of converting wildcards in a parameterized type to fresh + * type variables. See JLS 5.1.10 + * for details. + * + *

If {@code type} is not a declared type or if it does not have any wildcard type arguments, + * this method returns {@code type}. + * + * @param typeToCapture type to capture + * @return the result of applying capture conversion to {@code typeToCapture} + */ + public AnnotatedTypeMirror applyCaptureConversion(AnnotatedTypeMirror typeToCapture) { + TypeMirror capturedTypeMirror = types.capture(typeToCapture.getUnderlyingType()); + return applyCaptureConversion(typeToCapture, capturedTypeMirror); + } + + /** + * Apply capture conversion to {@code type}. + * + *

Capture conversion is the process of converting wildcards in a parameterized type to fresh + * type variables. See JLS 5.1.10 + * for details. + * + *

If {@code type} is not a declared type or if it does not have any wildcard type arguments, + * this method returns {@code type}. + * + * @param type type to capture + * @param typeMirror the result of applying capture conversion to the underlying type of {@code + * type}; it is used as the underlying type of the returned type + * @return the result of applying capture conversion to {@code type} + */ + public AnnotatedTypeMirror applyCaptureConversion( + AnnotatedTypeMirror type, TypeMirror typeMirror) { + // If the type contains uninferred type arguments, don't capture, but mark all wildcards + // that should have been captured as "uninferred" before it is returned. + if (type.containsUninferredTypeArguments() + && typeMirror.getKind() == TypeKind.DECLARED + && type.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; + DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; + for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); + if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD + && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) + || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { + ((AnnotatedWildcardType) uncapturedTypeArg).setUninferredTypeArgument(); } - @SuppressWarnings("nullness") // assume getCanonicalName returns non-null - String canonicalName = - annotationClassNames.computeIfAbsent(annoClass, Class::getCanonicalName); - return AnnotationUtils.areSameByName(am, canonicalName); + } + return type; + } + + if (!shouldCapture(type, typeMirror)) { + return type; + } + + AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; + DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; + // `capturedType` is the return value of this method. + AnnotatedDeclaredType capturedType = + (AnnotatedDeclaredType) AnnotatedTypeMirror.createType(capturedTypeMirror, this, false); + + nonWildcardTypeArgCopier.copy(uncapturedType, capturedType); + + AnnotatedDeclaredType typeDeclaration = + (AnnotatedDeclaredType) getAnnotatedType(uncapturedType.getUnderlyingType().asElement()); + + // A mapping from type variable to its type argument in the captured type. + Map typeVarToAnnotatedTypeArg = new HashMap<>(); + // A mapping from a captured type variable to the annotated captured type variable. + Map capturedTypeVarToAnnotatedTypeVar = new HashMap<>(); + // `newTypeArgs` will be the type arguments of the result of this method. + List newTypeArgs = new ArrayList<>(); + for (int i = 0; i < typeDeclaration.getTypeArguments().size(); i++) { + TypeVariable typeVarTypeMirror = + (TypeVariable) typeDeclaration.getTypeArguments().get(i).getUnderlyingType(); + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + AnnotatedTypeMirror capturedTypeArg = capturedType.getTypeArguments().get(i); + if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD) { + // The type argument is a captured type variable. Use the type argument from the + // newly created and yet-to-be annotated capturedType. (The annotations are added + // by #annotateCapturedTypeVar, which is called at the end of this method.) + typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, capturedTypeArg); + newTypeArgs.add(capturedTypeArg); + if (TypesUtils.isCapturedTypeVariable(capturedTypeArg.getUnderlyingType())) { + // Also, add a mapping from the captured type variable to the annotated captured + // type variable, so that if one captured type variable refers to another, the + // same AnnotatedTypeVariable object is used. + capturedTypeVarToAnnotatedTypeVar.put( + ((AnnotatedTypeVariable) capturedTypeArg).getUnderlyingType(), + (AnnotatedTypeVariable) capturedTypeArg); + } else { + // Javac used a declared type instead of a captured type variable. This seems + // to happen when the bounds of the captured type variable would have been + // identical. This seems to be a violation of the JLS, but javac does this, so + // the Checker Framework must handle that case. (See + // https://bugs.openjdk.org/browse/JDK-8054309.) + replaceAnnotations( + ((AnnotatedWildcardType) uncapturedTypeArg).getSuperBound(), capturedTypeArg); + } + } else { + // The type argument is not a wildcard. + // typeVarTypeMirror is the type parameter for which uncapturedTypeArg is a type + // argument. + typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, uncapturedTypeArg); + if (uncapturedTypeArg.getKind() == TypeKind.TYPEVAR) { + // If the type arg is a type variable also add it to the + // typeVarToAnnotatedTypeArg map, so that references to the type variable are + // substituted. + AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) uncapturedTypeArg; + typeVarToAnnotatedTypeArg.put(typeVar.getUnderlyingType(), typeVar); + } + newTypeArgs.add(uncapturedTypeArg); + } } - /** - * Checks that the collection contains the annotation. Using {@code Collection.contains} does - * not always work, because it does not use {@code areSame()} for comparison. - * - *

This method is faster than {@link AnnotationUtils#containsSameByClass(Collection, Class)} - * because is caches the name of the class rather than computing it each time. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation class to search for in c - * @return true iff c contains anno, according to areSameByClass - */ - public boolean containsSameByClass( - Collection c, Class anno) { - return getAnnotationByClass(c, anno) != null; - } + // Set the annotations of each captured type variable. + List orderToCapture = order(capturedTypeVarToAnnotatedTypeVar.values()); + for (AnnotatedTypeVariable capturedTypeArg : orderToCapture) { + int i = capturedTypeMirror.getTypeArguments().indexOf(capturedTypeArg.getUnderlyingType()); + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + AnnotatedTypeVariable typeVariable = + (AnnotatedTypeVariable) typeDeclaration.getTypeArguments().get(i); + annotateCapturedTypeVar( + typeVarToAnnotatedTypeArg, + capturedTypeVarToAnnotatedTypeVar, + (AnnotatedWildcardType) uncapturedTypeArg, + typeVariable, + capturedTypeArg); + newTypeArgs.set(i, capturedTypeArg); + } + + capturedType.setTypeArguments(newTypeArgs); + capturedType.addAnnotations(uncapturedType.getAnnotations()); + return capturedType; + } + + /** + * Copy the non-wildcard type args from a uncapturedType to its capturedType. Also, ensure that + * type variables in capturedType are the same object when they are refer to the same type + * variable. + * + *

To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. + */ + private final NonWildcardTypeArgCopier nonWildcardTypeArgCopier = new NonWildcardTypeArgCopier(); + + /** + * Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. Also, + * ensure that type variables in {@code capturedType} are the same object when they refer to the + * same type variable. + * + *

To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. + */ + private class NonWildcardTypeArgCopier extends AnnotatedTypeCopier { /** - * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. - * - *

This method is faster than {@link AnnotationUtils#getAnnotationByClass(Collection, Class)} - * because is caches the name of the class rather than computing it each time. - * - * @param c a collection of AnnotationMirrors - * @param anno the class to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according - * to areSameByClass; otherwise, {@code null} - */ - public @Nullable AnnotationMirror getAnnotationByClass( - Collection c, Class anno) { - for (AnnotationMirror an : c) { - if (areSameByClass(an, anno)) { - return an; - } + * Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. Also, + * ensure that type variables {@code capturedType} are the same object when they are refer to + * the same type variable. + * + * @param uncapturedType a declared type that has not under gone capture conversion + * @param capturedType the captured version of {@code uncapturedType} before it has been + * annotated + */ + private void copy(AnnotatedDeclaredType uncapturedType, AnnotatedDeclaredType capturedType) { + // The name "originalToCopy" means a mapping from the original to the copy, not an + // original that needs to be copied. + IdentityHashMap originalToCopy = + new IdentityHashMap<>(); + originalToCopy.put(uncapturedType, capturedType); + int numTypeArgs = uncapturedType.getTypeArguments().size(); + + AnnotatedTypeMirror[] newTypeArgs = new AnnotatedTypeMirror[numTypeArgs]; + // Mapping from type var to it's AnnotatedTypeVariable. These are type variables + // that are type arguments of the uncaptured type. + Map typeVarToAnnotatedTypeVar = new HashMap<>(numTypeArgs); + // Copy the non-wildcard type args from uncapturedType to newTypeArgs. + // If the non-wildcard type arg is a type var, add it to typeVarToAnnotatedTypeVar. + for (int i = 0; i < numTypeArgs; i++) { + AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); + if (uncapturedArg.getKind() != TypeKind.WILDCARD) { + AnnotatedTypeMirror copyOfArg = visit(uncapturedArg, originalToCopy); + newTypeArgs[i] = copyOfArg; + if (copyOfArg.getKind() == TypeKind.TYPEVAR) { + typeVarToAnnotatedTypeVar.put( + ((AnnotatedTypeVariable) copyOfArg).getUnderlyingType(), copyOfArg); + } } - return null; - } - - /* NO-AFU - * Changes the type of {@code rhsATM} when being assigned to a field, for use by whole-program - * inference. The default implementation does nothing. - * - * @param lhsTree the tree for the field whose type will be changed - * @param element the element for the field whose type will be changed - * @param fieldName the name of the field whose type will be changed - * @param rhsATM the type of the expression being assigned to the field, which is side-effected - * by this method - */ - /* NO-AFU - public void wpiAdjustForUpdateField( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {} - */ - - /* NO-AFU - * Changes the type of {@code rhsATM} when being assigned to anything other than a field, for - * use by whole-program inference. The default implementation does nothing. - * - * @param rhsATM the type of the rhs of the pseudo-assignment, which is side-effected by this - * method - */ - /* NO-AFU - public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {} - */ + } - /* NO-AFU - * Returns whether whole-program inference should infer types for receiver expressions. For some - * type systems, such as nullness, it doesn't make sense for WPI to do inference on receivers. - * - * @return true if WPI should infer types for method receiver parameters, false otherwise - */ - /* NO-AFU - public boolean wpiShouldInferTypesForReceivers() { - return true; - } - */ + // Substitute the type variables in each type argument of capturedType using + // typeVarToAnnotatedTypeVar. + // This makes type variables in capturedType the same object when they are the same type + // variable. + for (int i = 0; i < numTypeArgs; i++) { + AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); + AnnotatedTypeMirror capturedArg = capturedType.getTypeArguments().get(i); + // Note: This `if` statement can't be replaced with + // if (TypesUtils.isCapturedTypeVariable(capturedArg)) + // because if the bounds of the captured wildcard are equal, then instead of a + // captured wildcard, the type of the bound is used. + if (uncapturedArg.getKind() == TypeKind.WILDCARD) { + AnnotatedTypeMirror newCapArg = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + typeVarToAnnotatedTypeVar, capturedArg); + newTypeArgs[i] = newCapArg; + } + } + // Set capturedType type args to newTypeArgs. + capturedType.setTypeArguments(Arrays.asList(newTypeArgs)); - /* NO-AFU - * Side-effects the method or constructor annotations to make any desired changes before writing - * to an annotation file. - * - * @param methodAnnos the method or constructor annotations to modify - */ - /* NO-AFU - public void wpiPrepareMethodForWriting(AMethod methodAnnos) { - // This implementation does nothing. + // Visit the enclosing type. + if (uncapturedType.getEnclosingType() != null) { + capturedType.setEnclosingType( + (AnnotatedDeclaredType) visit(uncapturedType.getEnclosingType(), originalToCopy)); + } } - */ - - /* NO-AFU - * Side-effects the method or constructor annotations to make any desired changes before writing - * to an ajava file. - * - *

Overriding implementations should call {@code super.wpiPrepareMethodForWriting()}. - * - * @param methodAnnos the method or constructor annotations to modify - * @param inSupertypes the method or constructor annotations for all overridden methods; not - * side-effected - * @param inSubtypes the method or constructor annotations for all overriding methods; not - * side-effected - */ - /* NO-AFU - public void wpiPrepareMethodForWriting( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, - Collection inSupertypes, - Collection inSubtypes) { - Map> precondMap = - methodAnnos.getPreconditions(); - Map> postcondMap = - methodAnnos.getPostconditions(); - for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSupertype : - inSupertypes) { - makeConditionConsistentWithOtherMethod(precondMap, inSupertype, true, true); - makeConditionConsistentWithOtherMethod(postcondMap, inSupertype, false, true); + } + + /** + * Returns the list of type variables such that a type variable in the list only references type + * variables at a lower index than itself. + * + * @param collection a collection of type variables + * @return the type variables ordered so that each type variable only references earlier type + * variables + */ + public List order(Collection collection) { + List list = new ArrayList<>(collection); + List ordered = new ArrayList<>(); + while (!list.isEmpty()) { + AnnotatedTypeVariable free = doesNotContainOthers(list); + list.remove(free); + ordered.add(free); + } + return ordered; + } + + /** + * Returns the first TypeVariable in {@code collection} that does not lexically contain any other + * type in the collection. Or if all the TypeVariables contain another, then it returns the first + * TypeVariable in {@code collection}. + * + * @param collection a collection of type variables + * @return the first TypeVariable in {@code collection} that does not contain any other type in + * the collection, except possibly itself + */ + @SuppressWarnings("interning:not.interned") // must be the same object from collection + private AnnotatedTypeVariable doesNotContainOthers( + Collection collection) { + AnnotatedTypeVariable first = null; + for (AnnotatedTypeVariable candidate : collection) { + if (first == null) { + first = candidate; + } + boolean doesNotContain = true; + for (AnnotatedTypeVariable other : collection) { + if (candidate != other && captureScanner.visit(candidate, other.getUnderlyingType())) { + doesNotContain = false; + break; + } } - for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSubtype : inSubtypes) { - makeConditionConsistentWithOtherMethod(precondMap, inSubtype, true, false); - makeConditionConsistentWithOtherMethod(postcondMap, inSubtype, false, false); + if (doesNotContain) { + return candidate; } } - */ - - /* NO-AFU - * Performs side effects to make {@code conditionMap} obey behavioral subtyping constraints with - * {@code otherDeclAnnos}, that is, postconditions must be at least as strong as the postcondition - * on the superclass, and preconditions must be at most as strong as the condition on the - * superclass. - * - *

Overriding implementations should call {@code - * super.makeConditionConsistentWithOtherMethod()}. - * - * @param conditionMap pre- or post-condition annotations on a method M; may be side-effected - * @param otherDeclAnnos annotations on a method that M overrides or that overrides M; that is, on - * a method in the same "method family" as M; may be side-effected - * @param isPrecondition true if the annotations are pre-condition annotations, false if they are - * post-condition annotations - * @param otherIsSupertype true if {@code otherDeclAnnos} are on a supertype; false if they are on - * a subtype - */ - /* NO-AFU - protected void makeConditionConsistentWithOtherMethod( - Map> conditionMap, - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos otherDeclAnnos, - boolean isPrecondition, - boolean otherIsSupertype) { - for (Map.Entry> entry : - conditionMap.entrySet()) { - String expr = entry.getKey(); - IPair pair = entry.getValue(); - AnnotatedTypeMirror inferredType = pair.first; - AnnotatedTypeMirror declaredType = pair.second; - if (otherIsSupertype ? isPrecondition : !isPrecondition) { - // other is a supertype & compare preconditions, or - // other is a subtype & compare postconditions. - Map> otherConditionMap = - isPrecondition ? otherDeclAnnos.getPreconditions() : otherDeclAnnos.getPostconditions(); - // TODO: Complete support for "every expression" conditions, then remove the - // `!otherConditionMap.containsKey(expr)` test. - // If a condition map contains the key "every expression", that means that inference - // completed without inferring any conditions of that type. For example, if no - // @EnsuresCalledMethods was inferred for any expression, the map would contain the key - // "every expression", which is not a legal Java expression. - if (otherConditionMap.containsKey("every expression") - || !otherConditionMap.containsKey(expr)) { - // `otherInferredType` was inferred to be the top type. - // Put the top type on `inferredType`. - inferredType.replaceAnnotations(declaredType.getAnnotations()); - } else { - AnnotatedTypeMirror otherInferredType = - isPrecondition - ? otherDeclAnnos.getPreconditionsForExpression(expr, declaredType, this) - : otherDeclAnnos.getPostconditionsForExpression(expr, declaredType, this); - this.getWholeProgramInference().updateAtmWithLub(inferredType, otherInferredType); - } + return first; + } + + /** + * Scanner that returns true if the underlying type of any part of an {@link AnnotatedTypeMirror} + * is the passed captured type variable. + * + *

The second argument to visit must be a captured type variable. + */ + @SuppressWarnings("interning:not.interned") // Captured type vars can be compared with ==. + private final SimpleAnnotatedTypeScanner captureScanner = + new SimpleAnnotatedTypeScanner<>( + (type, other) -> type.getUnderlyingType() == other, Boolean::logicalOr, false); + + /** + * Set the annotated bounds for fresh type variable {@code capturedTypeVar}, so that it is the + * capture of {@code wildcard}. Also, sets {@code capturedTypeVar} primary annotation if the + * annotation on the bounds is identical. + * + * @param typeVarToAnnotatedTypeArg mapping from a (type mirror) type variable to its (annotated + * type mirror) type argument + * @param capturedTypeVarToAnnotatedTypeVar mapping from a captured type variable to its {@link + * AnnotatedTypeMirror} + * @param wildcard wildcard which is converted to {@code capturedTypeVar} + * @param typeVariable type variable for which {@code wildcard} is a type argument + * @param capturedTypeVar the fresh type variable which is side-effected by this method + */ + private void annotateCapturedTypeVar( + Map typeVarToAnnotatedTypeArg, + Map capturedTypeVarToAnnotatedTypeVar, + AnnotatedWildcardType wildcard, + AnnotatedTypeVariable typeVariable, + AnnotatedTypeVariable capturedTypeVar) { + AnnotatedTypeMirror typeVarUpperBound = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + typeVarToAnnotatedTypeArg, typeVariable.getUpperBound()); + AnnotatedTypeMirror upperBound = + AnnotatedTypes.annotatedGLB(this, typeVarUpperBound, wildcard.getExtendsBound()); + if (upperBound.getKind() == TypeKind.INTERSECTION + && capturedTypeVar.getUpperBound().getKind() != TypeKind.INTERSECTION) { + // There is a bug in javac such that the upper bound of the captured type variable is + // not the greatest lower bound. So the + // captureTypeVar.getUnderlyingType().getUpperBound() may not + // be the same type as upperbound.getUnderlyingType(). See + // framework/tests/all-systems/Issue4890Interfaces.java, + // framework/tests/all-systems/Issue4890.java and + // framework/tests/all-systems/Issue4877.java. + // (I think this is https://bugs.openjdk.org/browse/JDK-8039222.) + for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) upperBound).getBounds()) { + if (types.isSameType( + bound.underlyingType, capturedTypeVar.getUpperBound().getUnderlyingType())) { + upperBound = bound; } } } - */ - /** - * Does {@code annotatedForAnno}, which is an {@link - * org.checkerframework.framework.qual.AnnotatedFor} annotation, apply to this checker? - * - * @param annotatedForAnno an {@link AnnotatedFor} annotation - * @return whether {@code annotatedForAnno} applies to this checker - */ - public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForAnno) { - List annotatedForCheckers = - AnnotationUtils.getElementValueArray( - annotatedForAnno, annotatedForValueElement, String.class); - List<@FullyQualifiedName String> upstreamCheckerNames = checker.getUpstreamCheckerNames(); - for (String annoForChecker : annotatedForCheckers) { - if (upstreamCheckerNames.contains(annoForChecker) - || CheckerMain.matchesFullyQualifiedProcessor( - annoForChecker, upstreamCheckerNames, true)) { - return true; - } - } - return false; - } + capturedTypeVar.setUpperBound(upperBound); - /** - * Get the {@code expression} field/element of the given contract annotation. - * - * @param contractAnno a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link - * EnsuresQualifier} - * @return the {@code expression} field/element of the given annotation - */ - public List getContractExpressions(AnnotationMirror contractAnno) { - DeclaredType annoType = contractAnno.getAnnotationType(); - if (types.isSameType(annoType, requiresQualifierTM)) { - return AnnotationUtils.getElementValueArray( - contractAnno, requiresQualifierExpressionElement, String.class); - } else if (types.isSameType(annoType, ensuresQualifierTM)) { - return AnnotationUtils.getElementValueArray( - contractAnno, ensuresQualifierExpressionElement, String.class); - } else if (types.isSameType(annoType, ensuresQualifierIfTM)) { - return AnnotationUtils.getElementValueArray( - contractAnno, ensuresQualifierIfExpressionElement, String.class); - } else { - throw new BugInCF("Not a contract annotation: " + contractAnno); - } - } + // typeVariable's lower bound is a NullType, so there's nothing to substitute. + AnnotatedTypeMirror lowerBound = + AnnotatedTypes.leastUpperBound( + this, typeVariable.getLowerBound(), wildcard.getSuperBound()); + capturedTypeVar.setLowerBound(lowerBound); - /** - * Get the {@code value} field/element of the given contract list annotation. - * - * @param contractListAnno a {@link org.checkerframework.framework.qual.RequiresQualifier.List - * RequiresQualifier.List}, {@link org.checkerframework.framework.qual.EnsuresQualifier.List - * EnsuresQualifier.List}, or {@link - * org.checkerframework.framework.qual.EnsuresQualifierIf.List EnsuresQualifierIf.List} - * @return the {@code value} field/element of the given annotation - */ - public List getContractListValues(AnnotationMirror contractListAnno) { - DeclaredType annoType = contractListAnno.getAnnotationType(); - if (types.isSameType(annoType, requiresQualifierListTM)) { - return AnnotationUtils.getElementValueArray( - contractListAnno, requiresQualifierListValueElement, AnnotationMirror.class); - } else if (types.isSameType(annoType, ensuresQualifierListTM)) { - return AnnotationUtils.getElementValueArray( - contractListAnno, ensuresQualifierListValueElement, AnnotationMirror.class); - } else if (types.isSameType(annoType, ensuresQualifierIfListTM)) { - return AnnotationUtils.getElementValueArray( - contractListAnno, ensuresQualifierIfListValueElement, AnnotationMirror.class); - } else { - throw new BugInCF("Not a contract list annotation: " + contractListAnno); - } - } + // Add as a primary annotation any qualifiers that are the same on the upper and lower + // bound. + AnnotationMirrorSet p = + new AnnotationMirrorSet(capturedTypeVar.getUpperBound().getAnnotations()); + p.retainAll(capturedTypeVar.getLowerBound().getAnnotations()); + capturedTypeVar.replaceAnnotations(p); + + capturedTypeVarSubstitutor.substitute(capturedTypeVar, capturedTypeVarToAnnotatedTypeVar); + } + + /** + * Substitutes references to captured type variables. + * + *

Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code + * substitute} to use. + */ + private final CapturedTypeVarSubstitutor capturedTypeVarSubstitutor = + new CapturedTypeVarSubstitutor(); + + /** + * Substitutes references to captured types in {@code type} using {@code + * capturedTypeVarToAnnotatedTypeVar}. + * + *

Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code + * substitute} to use. + */ + private static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier { + + /** A mapping from a captured type variable to its AnnotatedTypeVariable. */ + private Map capturedTypeVarToAnnotatedTypeVar; /** - * Returns true if the type is immutable. Subclasses can override this method to add types that - * are mutable, but the annotated type of an object is immutable. + * Substitutes references to captured type variable in {@code type} using {@code + * capturedTypeVarToAnnotatedTypeVar}. * - * @param type type to test - * @return true if the type is immutable + *

Unlike {@link #typeVarSubstitutor}, this method does not copy the type. + * + * @param type the type whose captured type variables are substituted with those in {@code + * capturedTypeVarToAnnotatedTypeVar} + * @param capturedTypeVarToAnnotatedTypeVar mapping from a TypeVariable (which is a captured + * type variable) to an AnnotatedTypeVariable */ - public boolean isImmutable(TypeMirror type) { - return TypesUtils.isImmutableTypeInJdk(type); + private void substitute( + AnnotatedTypeVariable type, + Map capturedTypeVarToAnnotatedTypeVar) { + this.capturedTypeVarToAnnotatedTypeVar = capturedTypeVarToAnnotatedTypeVar; + IdentityHashMap mapping = new IdentityHashMap<>(); + visit(type.getLowerBound(), mapping); + visit(type.getUpperBound(), mapping); + this.capturedTypeVarToAnnotatedTypeVar = null; } @Override - public boolean isSideEffectFree(ExecutableElement methodElement) { - if (assumeSideEffectFree || (assumePureGetters && ElementUtils.isGetter(methodElement))) { - return true; - } - if (ElementUtils.isRecordAccessor(methodElement) - && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { - return true; - } - for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { - if (areSameByClass(anno, org.checkerframework.dataflow.qual.SideEffectFree.class) - || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) - || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { - return true; - } - } - return false; + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + AnnotatedTypeMirror cap = capturedTypeVarToAnnotatedTypeVar.get(original.getUnderlyingType()); + if (cap != null) { + return cap; + } + return super.visitTypeVariable(original, originalToCopy); } @Override - public boolean isDeterministic(ExecutableElement methodElement) { - if (assumeDeterministic || (assumePureGetters && ElementUtils.isGetter(methodElement))) { - return true; - } - if (ElementUtils.isRecordAccessor(methodElement) - && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { - return true; + protected T makeOrReturnCopy( + T original, IdentityHashMap originalToCopy) { + AnnotatedTypeMirror copy = originalToCopy.get(original); + if (copy != null) { + @SuppressWarnings("unchecked" // the key-value pairs in originalToCopy are always the same + // kind of AnnotatedTypeMirror. + ) + T copyCasted = (T) copy; + return copyCasted; + } + + if (original.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeMirror captureType = + capturedTypeVarToAnnotatedTypeVar.get( + ((AnnotatedTypeVariable) original).getUnderlyingType()); + if (captureType != null) { + originalToCopy.put(original, captureType); + @SuppressWarnings("unchecked" // the key-value pairs in originalToCopy are always the same + // kind of AnnotatedTypeMirror. + ) + T captureTypeCasted = (T) captureType; + return captureTypeCasted; } - for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { - if (areSameByClass(anno, org.checkerframework.dataflow.qual.Deterministic.class) - || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) - || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { - return true; - } + } + originalToCopy.put(original, original); + return original; + } + } + + /** + * Check that a wildcard is an extends wildcard. + * + * @param awt the wildcard type + * @return true if awt is an extends wildcard + */ + private boolean isExtendsWildcard(AnnotatedWildcardType awt) { + return awt.getUnderlyingType().getSuperBound() == null; + } + + /** + * Returns the utility class for working with {@link Element}s. + * + * @return the utility class for working with {@link Element}s + */ + public final Elements getElementUtils() { + return this.elements; + } + + /** Accessor for the tree utilities. */ + public Trees getTreeUtils() { + return this.trees; + } + + /** + * Accessor for the processing environment. + * + * @return the processing environment + */ + public ProcessingEnvironment getProcessingEnv() { + return this.processingEnv; + } + + /** Matches addition of a constant. */ + private static final Pattern plusConstant = Pattern.compile(" *\\+ *(-?[0-9]+)$"); + + /** Matches subtraction of a constant. */ + private static final Pattern minusConstant = Pattern.compile(" *- *(-?[0-9]+)$"); + + /** Matches a string whose only parens are at the beginning and end of the string. */ + private static final Pattern surroundingParensPattern = Pattern.compile("^\\([^()]\\)"); + + /** + * Given an expression, split it into a subexpression and a constant offset. For example: + * + *

{@code
+   * "a" => <"a", "0">
+   * "a + 5" => <"a", "5">
+   * "a + -5" => <"a", "-5">
+   * "a - 5" => <"a", "-5">
+   * }
+ * + * There are methods that can only take as input an expression that represents a JavaExpression. + * The purpose of this is to pre-process expressions to make those methods more likely to succeed. + * + * @param expression an expression to remove a constant offset from + * @return a sub-expression and a constant offset. The offset is "0" if this routine is unable to + * splite the given expression + */ + // TODO: generalize. There is no reason this couldn't handle arbitrary addition and subtraction + // expressions, given the Index Checker's support for OffsetEquation. That might even make its + // implementation simpler. + public static IPair getExpressionAndOffset(String expression) { + String expr = expression; + String offset = "0"; + + // Is this normalization necessary? + // Remove surrounding whitespace. + expr = expr.trim(); + // Remove surrounding parentheses. + if (surroundingParensPattern.matcher(expr).matches()) { + expr = expr.substring(1, expr.length() - 2).trim(); + } + + Matcher mPlus = plusConstant.matcher(expr); + Matcher mMinus = minusConstant.matcher(expr); + if (mPlus.find()) { + expr = expr.substring(0, mPlus.start()); + offset = mPlus.group(1); + } else if (mMinus.find()) { + expr = expr.substring(0, mMinus.start()); + offset = negateConstant(mMinus.group(1)); + } + + if (offset.equals("-0")) { + offset = "0"; + } + + expr = expr.intern(); + offset = offset.intern(); + + return IPair.of(expr, offset); + } + + /** + * Given an expression string, returns its negation. + * + * @param constantExpression a string representing an integer constant + * @return the negation of constantExpression + */ + // Also see Subsequence.negateString which is similar but more sophisticated. + public static String negateConstant(String constantExpression) { + if (constantExpression.startsWith("-")) { + return constantExpression.substring(1); + } else { + if (constantExpression.startsWith("+")) { + constantExpression = constantExpression.substring(1); + } + return "-" + constantExpression; + } + } + + /** + * Returns {@code null} or an annotated type mirror that type argument inference should assume + * {@code expressionTree} is assigned to. + * + *

If {@code null} is returned, inference proceeds normally. + * + *

If a type is returned, then inference assumes that {@code expressionTree} was asigned to it. + * This biases the inference algorithm toward the annotations in the returned type. In particular, + * if the annotations on type variables in invariant positions are a super type of the annotations + * inferred, the super type annotations are chosen. + * + *

This implementation returns null, but subclasses may override this method to return a type. + * + * @param expressionTree an expression which has no assignment context and for which type + * arguments need to be inferred + * @return {@code null} or an annotated type mirror that inferrence should pretend {@code + * expressionTree} is assigned to + */ + public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { + return null; + } + + /** + * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. + * + *

This method is faster than {@link AnnotationUtils#areSameByClass(AnnotationMirror, Class)} + * because it caches the name of the class rather than computing it each time. + * + * @param am the AnnotationMirror whose class to compare + * @param annoClass the class to compare + * @return true if annoclass is the class of am + */ + public boolean areSameByClass(AnnotationMirror am, Class annoClass) { + if (!shouldCache) { + return AnnotationUtils.areSameByName(am, annoClass.getCanonicalName()); + } + @SuppressWarnings("nullness") // assume getCanonicalName returns non-null + String canonicalName = annotationClassNames.computeIfAbsent(annoClass, Class::getCanonicalName); + return AnnotationUtils.areSameByName(am, canonicalName); + } + + /** + * Checks that the collection contains the annotation. Using {@code Collection.contains} does not + * always work, because it does not use {@code areSame()} for comparison. + * + *

This method is faster than {@link AnnotationUtils#containsSameByClass(Collection, Class)} + * because is caches the name of the class rather than computing it each time. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation class to search for in c + * @return true iff c contains anno, according to areSameByClass + */ + public boolean containsSameByClass( + Collection c, Class anno) { + return getAnnotationByClass(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. + * + *

This method is faster than {@link AnnotationUtils#getAnnotationByClass(Collection, Class)} + * because is caches the name of the class rather than computing it each time. + * + * @param c a collection of AnnotationMirrors + * @param anno the class to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to + * areSameByClass; otherwise, {@code null} + */ + public @Nullable AnnotationMirror getAnnotationByClass( + Collection c, Class anno) { + for (AnnotationMirror an : c) { + if (areSameByClass(an, anno)) { + return an; + } + } + return null; + } + + /* NO-AFU + * Changes the type of {@code rhsATM} when being assigned to a field, for use by whole-program + * inference. The default implementation does nothing. + * + * @param lhsTree the tree for the field whose type will be changed + * @param element the element for the field whose type will be changed + * @param fieldName the name of the field whose type will be changed + * @param rhsATM the type of the expression being assigned to the field, which is side-effected + * by this method + */ + /* NO-AFU + public void wpiAdjustForUpdateField( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {} + */ + + /* NO-AFU + * Changes the type of {@code rhsATM} when being assigned to anything other than a field, for + * use by whole-program inference. The default implementation does nothing. + * + * @param rhsATM the type of the rhs of the pseudo-assignment, which is side-effected by this + * method + */ + /* NO-AFU + public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {} + */ + + /* NO-AFU + * Returns whether whole-program inference should infer types for receiver expressions. For some + * type systems, such as nullness, it doesn't make sense for WPI to do inference on receivers. + * + * @return true if WPI should infer types for method receiver parameters, false otherwise + */ + /* NO-AFU + public boolean wpiShouldInferTypesForReceivers() { + return true; + } + */ + + /* NO-AFU + * Side-effects the method or constructor annotations to make any desired changes before writing + * to an annotation file. + * + * @param methodAnnos the method or constructor annotations to modify + */ + /* NO-AFU + public void wpiPrepareMethodForWriting(AMethod methodAnnos) { + // This implementation does nothing. + } + */ + + /* NO-AFU + * Side-effects the method or constructor annotations to make any desired changes before writing + * to an ajava file. + * + *

Overriding implementations should call {@code super.wpiPrepareMethodForWriting()}. + * + * @param methodAnnos the method or constructor annotations to modify + * @param inSupertypes the method or constructor annotations for all overridden methods; not + * side-effected + * @param inSubtypes the method or constructor annotations for all overriding methods; not + * side-effected + */ + /* NO-AFU + public void wpiPrepareMethodForWriting( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, + Collection inSupertypes, + Collection inSubtypes) { + Map> precondMap = + methodAnnos.getPreconditions(); + Map> postcondMap = + methodAnnos.getPostconditions(); + for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSupertype : + inSupertypes) { + makeConditionConsistentWithOtherMethod(precondMap, inSupertype, true, true); + makeConditionConsistentWithOtherMethod(postcondMap, inSupertype, false, true); + } + for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSubtype : inSubtypes) { + makeConditionConsistentWithOtherMethod(precondMap, inSubtype, true, false); + makeConditionConsistentWithOtherMethod(postcondMap, inSubtype, false, false); + } + } + */ + + /* NO-AFU + * Performs side effects to make {@code conditionMap} obey behavioral subtyping constraints with + * {@code otherDeclAnnos}, that is, postconditions must be at least as strong as the postcondition + * on the superclass, and preconditions must be at most as strong as the condition on the + * superclass. + * + *

Overriding implementations should call {@code + * super.makeConditionConsistentWithOtherMethod()}. + * + * @param conditionMap pre- or post-condition annotations on a method M; may be side-effected + * @param otherDeclAnnos annotations on a method that M overrides or that overrides M; that is, on + * a method in the same "method family" as M; may be side-effected + * @param isPrecondition true if the annotations are pre-condition annotations, false if they are + * post-condition annotations + * @param otherIsSupertype true if {@code otherDeclAnnos} are on a supertype; false if they are on + * a subtype + */ + /* NO-AFU + protected void makeConditionConsistentWithOtherMethod( + Map> conditionMap, + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos otherDeclAnnos, + boolean isPrecondition, + boolean otherIsSupertype) { + for (Map.Entry> entry : + conditionMap.entrySet()) { + String expr = entry.getKey(); + IPair pair = entry.getValue(); + AnnotatedTypeMirror inferredType = pair.first; + AnnotatedTypeMirror declaredType = pair.second; + if (otherIsSupertype ? isPrecondition : !isPrecondition) { + // other is a supertype & compare preconditions, or + // other is a subtype & compare postconditions. + Map> otherConditionMap = + isPrecondition ? otherDeclAnnos.getPreconditions() : otherDeclAnnos.getPostconditions(); + // TODO: Complete support for "every expression" conditions, then remove the + // `!otherConditionMap.containsKey(expr)` test. + // If a condition map contains the key "every expression", that means that inference + // completed without inferring any conditions of that type. For example, if no + // @EnsuresCalledMethods was inferred for any expression, the map would contain the key + // "every expression", which is not a legal Java expression. + if (otherConditionMap.containsKey("every expression") + || !otherConditionMap.containsKey(expr)) { + // `otherInferredType` was inferred to be the top type. + // Put the top type on `inferredType`. + inferredType.replaceAnnotations(declaredType.getAnnotations()); + } else { + AnnotatedTypeMirror otherInferredType = + isPrecondition + ? otherDeclAnnos.getPreconditionsForExpression(expr, declaredType, this) + : otherDeclAnnos.getPostconditionsForExpression(expr, declaredType, this); + this.getWholeProgramInference().updateAtmWithLub(inferredType, otherInferredType); } - return false; + } + } + } + */ + + /** + * Does {@code annotatedForAnno}, which is an {@link + * org.checkerframework.framework.qual.AnnotatedFor} annotation, apply to this checker? + * + * @param annotatedForAnno an {@link AnnotatedFor} annotation + * @return whether {@code annotatedForAnno} applies to this checker + */ + public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForAnno) { + List annotatedForCheckers = + AnnotationUtils.getElementValueArray( + annotatedForAnno, annotatedForValueElement, String.class); + List<@FullyQualifiedName String> upstreamCheckerNames = checker.getUpstreamCheckerNames(); + for (String annoForChecker : annotatedForCheckers) { + if (upstreamCheckerNames.contains(annoForChecker) + || CheckerMain.matchesFullyQualifiedProcessor( + annoForChecker, upstreamCheckerNames, true)) { + return true; + } + } + return false; + } + + /** + * Get the {@code expression} field/element of the given contract annotation. + * + * @param contractAnno a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifier} + * @return the {@code expression} field/element of the given annotation + */ + public List getContractExpressions(AnnotationMirror contractAnno) { + DeclaredType annoType = contractAnno.getAnnotationType(); + if (types.isSameType(annoType, requiresQualifierTM)) { + return AnnotationUtils.getElementValueArray( + contractAnno, requiresQualifierExpressionElement, String.class); + } else if (types.isSameType(annoType, ensuresQualifierTM)) { + return AnnotationUtils.getElementValueArray( + contractAnno, ensuresQualifierExpressionElement, String.class); + } else if (types.isSameType(annoType, ensuresQualifierIfTM)) { + return AnnotationUtils.getElementValueArray( + contractAnno, ensuresQualifierIfExpressionElement, String.class); + } else { + throw new BugInCF("Not a contract annotation: " + contractAnno); + } + } + + /** + * Get the {@code value} field/element of the given contract list annotation. + * + * @param contractListAnno a {@link org.checkerframework.framework.qual.RequiresQualifier.List + * RequiresQualifier.List}, {@link org.checkerframework.framework.qual.EnsuresQualifier.List + * EnsuresQualifier.List}, or {@link + * org.checkerframework.framework.qual.EnsuresQualifierIf.List EnsuresQualifierIf.List} + * @return the {@code value} field/element of the given annotation + */ + public List getContractListValues(AnnotationMirror contractListAnno) { + DeclaredType annoType = contractListAnno.getAnnotationType(); + if (types.isSameType(annoType, requiresQualifierListTM)) { + return AnnotationUtils.getElementValueArray( + contractListAnno, requiresQualifierListValueElement, AnnotationMirror.class); + } else if (types.isSameType(annoType, ensuresQualifierListTM)) { + return AnnotationUtils.getElementValueArray( + contractListAnno, ensuresQualifierListValueElement, AnnotationMirror.class); + } else if (types.isSameType(annoType, ensuresQualifierIfListTM)) { + return AnnotationUtils.getElementValueArray( + contractListAnno, ensuresQualifierIfListValueElement, AnnotationMirror.class); + } else { + throw new BugInCF("Not a contract list annotation: " + contractListAnno); + } + } + + /** + * Returns true if the type is immutable. Subclasses can override this method to add types that + * are mutable, but the annotated type of an object is immutable. + * + * @param type type to test + * @return true if the type is immutable + */ + public boolean isImmutable(TypeMirror type) { + return TypesUtils.isImmutableTypeInJdk(type); + } + + @Override + public boolean isSideEffectFree(ExecutableElement methodElement) { + if (assumeSideEffectFree || (assumePureGetters && ElementUtils.isGetter(methodElement))) { + return true; + } + if (ElementUtils.isRecordAccessor(methodElement) + && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { + return true; + } + for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { + if (areSameByClass(anno, org.checkerframework.dataflow.qual.SideEffectFree.class) + || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) + || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { + return true; + } + } + return false; + } + + @Override + public boolean isDeterministic(ExecutableElement methodElement) { + if (assumeDeterministic || (assumePureGetters && ElementUtils.isGetter(methodElement))) { + return true; + } + if (ElementUtils.isRecordAccessor(methodElement) + && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { + return true; + } + for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { + if (areSameByClass(anno, org.checkerframework.dataflow.qual.Deterministic.class) + || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) + || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { + return true; + } } + return false; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java index 0475b56fe86..74bc1879711 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java @@ -8,25 +8,25 @@ * @see org.checkerframework.framework.util.AnnotationFormatter */ public interface AnnotatedTypeFormatter { - /** - * Formats type into a String. Uses an implementation specific default for printing "invisible - * annotations" - * - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @param type the type to be converted - * @return a string representation of type - */ - @SideEffectFree - public String format(AnnotatedTypeMirror type); + /** + * Formats type into a String. Uses an implementation specific default for printing "invisible + * annotations" + * + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @param type the type to be converted + * @return a string representation of type + */ + @SideEffectFree + public String format(AnnotatedTypeMirror type); - /** - * Formats type into a String. - * - * @param type the type to be converted - * @param printVerbose whether or not to print verbosely - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @return a string representation of type - */ - @SideEffectFree - public String format(AnnotatedTypeMirror type, boolean printVerbose); + /** + * Formats type into a String. + * + * @param type the type to be converted + * @param printVerbose whether or not to print verbosely + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @return a string representation of type + */ + @SideEffectFree + public String format(AnnotatedTypeMirror type, boolean printVerbose); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index 99bd9f76596..a55eb9926be 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -1,31 +1,12 @@ package org.checkerframework.framework.type; import com.sun.tools.javac.code.Symbol.MethodSymbol; - -import org.checkerframework.checker.formatter.qual.FormatMethod; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TypeKindUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.DeepCopyable; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -45,6 +26,22 @@ import javax.lang.model.type.UnionType; import javax.lang.model.type.WildcardType; import javax.lang.model.util.Types; +import org.checkerframework.checker.formatter.qual.FormatMethod; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TypeKindUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.DeepCopyable; /** * Represents an annotated type in the Java programming language, including: @@ -74,2631 +71,2580 @@ */ public abstract class AnnotatedTypeMirror implements DeepCopyable { - /** An EqualityAtmComparer. */ - protected static final EqualityAtmComparer EQUALITY_COMPARER = new EqualityAtmComparer(); + /** An EqualityAtmComparer. */ + protected static final EqualityAtmComparer EQUALITY_COMPARER = new EqualityAtmComparer(); + + /** A HashcodeAtmVisitor. */ + protected static final HashcodeAtmVisitor HASHCODE_VISITOR = new HashcodeAtmVisitor(); + + /** The factory to use for lazily creating annotated types. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** The actual type wrapped by this AnnotatedTypeMirror. */ + protected final TypeMirror underlyingType; + + /** + * Saves the result of {@code underlyingType.toString().hashCode()} to use when computing the hash + * code of this. (Because AnnotatedTypeMirrors are mutable, the hash code for this cannot be + * saved.) Call {@link #getUnderlyingTypeHashCode()} rather than using the field directly. + */ + private int underlyingTypeHashCode = -1; + + /** The annotations on this type. */ + // AnnotationMirror doesn't override Object.hashCode, .equals, so we use + // the class name of Annotation instead. + // Caution: Assumes that a type can have at most one AnnotationMirror for any Annotation type. + protected final AnnotationMirrorSet primaryAnnotations = new AnnotationMirrorSet(); + + // /** The explicitly written annotations on this type. */ + // TODO: use this to cache the result once computed? For generic types? + // protected final AnnotationMirrorSet explicitannotations = + // new AnnotationMirrorSet(); + + /** + * Constructor for AnnotatedTypeMirror. + * + * @param underlyingType the underlying type + * @param atypeFactory used to create further types and to access global information (Types, + * Elements, ...) + */ + private AnnotatedTypeMirror(TypeMirror underlyingType, AnnotatedTypeFactory atypeFactory) { + this.underlyingType = underlyingType; + assert atypeFactory != null; + this.atypeFactory = atypeFactory; + } + + /// This class doesn't customize the clone() method; use deepCopy() instead. + // @Override + // public AnnotatedTypeMirror clone() { ... } + + /** + * Creates an AnnotatedTypeMirror for the provided type. The result contains no annotations. + * + * @param type the underlying type for the resulting AnnotatedTypeMirror + * @param atypeFactory the type factory that will build the result + * @param isDeclaration true if the result should represent a declaration, rather than a use, of a + * type + * @return an AnnotatedTypeMirror whose underlying type is {@code type} + */ + public static AnnotatedTypeMirror createType( + TypeMirror type, AnnotatedTypeFactory atypeFactory, boolean isDeclaration) { + if (type == null) { + throw new BugInCF("AnnotatedTypeMirror.createType: input type must not be null"); + } - /** A HashcodeAtmVisitor. */ - protected static final HashcodeAtmVisitor HASHCODE_VISITOR = new HashcodeAtmVisitor(); + AnnotatedTypeMirror result; + switch (type.getKind()) { + case ARRAY: + result = new AnnotatedArrayType((ArrayType) type, atypeFactory); + break; + case DECLARED: + result = new AnnotatedDeclaredType((DeclaredType) type, atypeFactory, isDeclaration); + break; + case ERROR: + throw new ErrorTypeKindException( + "AnnotatedTypeMirror.createType: input is not compilable. Found error type:" + + " " + + type); + + case EXECUTABLE: + result = new AnnotatedExecutableType((ExecutableType) type, atypeFactory); + break; + case VOID: + case PACKAGE: + case NONE: + result = new AnnotatedNoType((NoType) type, atypeFactory); + break; + case NULL: + result = new AnnotatedNullType((NullType) type, atypeFactory); + break; + case TYPEVAR: + result = new AnnotatedTypeVariable((TypeVariable) type, atypeFactory, isDeclaration); + break; + case WILDCARD: + result = new AnnotatedWildcardType((WildcardType) type, atypeFactory); + break; + case INTERSECTION: + result = new AnnotatedIntersectionType((IntersectionType) type, atypeFactory); + break; + case UNION: + result = new AnnotatedUnionType((UnionType) type, atypeFactory); + break; + default: + if (type.getKind().isPrimitive()) { + result = new AnnotatedPrimitiveType((PrimitiveType) type, atypeFactory); + break; + } + throw new BugInCF( + "AnnotatedTypeMirror.createType: unidentified type " + + type + + " (" + + type.getKind() + + ")"); + } + /*if (jctype.isAnnotated()) { + result.addAnnotations(jctype.getAnnotationMirrors()); + }*/ + return result; + } + + @Override + public final boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } - /** The factory to use for lazily creating annotated types. */ - protected final AnnotatedTypeFactory atypeFactory; + if (!(o instanceof AnnotatedTypeMirror)) { + return false; + } - /** The actual type wrapped by this AnnotatedTypeMirror. */ - protected final TypeMirror underlyingType; + return EQUALITY_COMPARER.visit(this, (AnnotatedTypeMirror) o, null); + } + + @Pure + @Override + public final int hashCode() { + return HASHCODE_VISITOR.visit(this); + } + + /** + * Applies a visitor to this type. + * + * @param the return type of the visitor's methods + * @param

the type of the additional parameter to the visitor's methods + * @param v the visitor operating on this type + * @param p additional parameter to the visitor + * @return a visitor-specified result + */ + public abstract R accept(AnnotatedTypeVisitor v, P p); + + /** + * Returns the {@code kind} of this type. + * + * @return the kind of this type + */ + public TypeKind getKind() { + return underlyingType.getKind(); + } + + /** + * Given a primitive type, return its kind. Given a boxed primitive type, return the corresponding + * primitive type kind. Otherwise, return null. + * + * @return a primitive type kind if this is a primitive type or boxed primitive type; otherwise + * null + */ + public @Nullable TypeKind getPrimitiveKind() { + return TypeKindUtils.primitiveOrBoxedToTypeKind(getUnderlyingType()); + } + + /** + * Returns the underlying unannotated Java type, which this wraps. + * + * @return the underlying type + */ + public TypeMirror getUnderlyingType() { + return underlyingType; + } + + /** + * Returns true if this type mirror represents a declaration, rather than a use, of a type. + * + *

For example, {@code class List { ... }} declares a new type {@code List}, while {@code + * List} is a use of the type. + * + * @return true if this represents a declaration + */ + public boolean isDeclaration() { + return false; + } + + public AnnotatedTypeMirror asUse() { + return this; + } + + /** + * Returns true if this type has a primary annotation in the same hierarchy as {@code annotation}. + * + *

This method does not account for annotations in deep types (type arguments, array + * components, etc). + * + * @param annotation the qualifier hierarchy to check for + * @return true iff this type has a primary annotation in the same hierarchy as {@code + * annotation}. + */ + // typetools: hasPrimaryAnnotationInHierarchy + public boolean hasAnnotationInHierarchy(AnnotationMirror annotation) { + return getAnnotationInHierarchy(annotation) != null; + } + + /** + * Returns the primary annotation on this type that is in the same hierarchy as {@code + * annotation}. For {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code + * null} may be returned when the upper bound may have an annotation with that class, so {@link + * #getEffectiveAnnotationInHierarchy(AnnotationMirror)} should be called instead. + * + *

This method does not account for annotations in deep types (type arguments, array + * components, etc). + * + *

May return null if the receiver is a type variable or a wildcard without a primary + * annotation, or if the receiver is not yet fully annotated. + * + * @param annotation an annotation in the qualifier hierarchy to check for + * @return the annotation mirror whose class is named {@code annoNAme} or null + */ + // typetools: getPrimaryAnnotationInHierarchy + public @Nullable AnnotationMirror getAnnotationInHierarchy(AnnotationMirror annotation) { + if (primaryAnnotations.isEmpty()) { + return null; + } + AnnotationMirror canonical = annotation; + if (!atypeFactory.isSupportedQualifier(canonical)) { + canonical = atypeFactory.canonicalAnnotation(annotation); + if (canonical == null) { + // This can happen if annotation is unrelated to this AnnotatedTypeMirror. + return null; + } + } + if (atypeFactory.isSupportedQualifier(canonical)) { + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + AnnotationMirror anno = + qualHierarchy.findAnnotationInSameHierarchy(primaryAnnotations, canonical); + if (anno != null) { + return anno; + } + } + return null; + } + + /** + * Returns the "effective" annotation from the same hierarchy as {@code annotation}, otherwise + * returns {@code null}. + * + *

An effective annotation is the annotation on the type itself, or on the upper/extends bound + * of a type variable/wildcard (recursively, until a class type is reached). + * + * @param annotation an annotation in the qualifier hierarchy to check for + * @return an annotation from the same hierarchy as {@code annotation} if present + */ + public @Nullable AnnotationMirror getEffectiveAnnotationInHierarchy(AnnotationMirror annotation) { + AnnotationMirror canonical = annotation; + if (!atypeFactory.isSupportedQualifier(canonical)) { + canonical = atypeFactory.canonicalAnnotation(annotation); + } + if (atypeFactory.isSupportedQualifier(canonical)) { + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + AnnotationMirror anno = + qualHierarchy.findAnnotationInSameHierarchy(getEffectiveAnnotations(), canonical); + if (anno != null) { + return anno; + } + } + return null; + } + + /** + * Returns the primary annotations on this type. For {@link AnnotatedTypeVariable}s and {@link + * AnnotatedWildcardType}s, the returned annotations may be empty or missing annotations in + * hierarchies, so {@link #getEffectiveAnnotations()} should be called instead. + * + *

It does not include annotations in deep types (type arguments, array components, etc). + * + *

To get the single primary annotation in a particular hierarchy, use {@link + * #getAnnotationInHierarchy}. + * + * @return an unmodifiable set of the annotations on this + */ + // typetools: getPrimaryAnnotations + // typetools: removed method getPrimaryAnnotation + public final AnnotationMirrorSet getAnnotations() { + return AnnotationMirrorSet.unmodifiableSet(primaryAnnotations); + } + + /** + * Returns the annotations on this type; mutations affect this object, because the return type is + * an alias of the {@code annotations} field. It does not include annotations in deep types (type + * arguments, array components, etc). + * + *

The returned set should not be modified, but for efficiency reasons modification is not + * prevented. Modifications might break invariants. + * + * @return the set of the annotations on this; mutations affect this object + */ + // typetools: getPrimaryAnnotationsField + protected final AnnotationMirrorSet getAnnotationsField() { + return primaryAnnotations; + } + + /** + * Returns the "effective" annotations on this type, i.e. the annotations on the type itself, or + * on the upper/extends bound of a type variable/wildcard (recursively, until a class type is + * reached). If this is fully-annotated, the returned set will contain one annotation per + * hierarchy. + * + * @return a set of the annotations on this + */ + // TODO: When the current, deprecated `getAnnotations()` (deprecation date 2023-06-15) is + // removed, + // rename all the "getEffectiveAnnotation...()" methods to just "getAnnotation...()". + // EISOP will not do this renaming, it would introduce inconsistent behavior with how + // getAnnotations in javac APIs works. + // Removed getEffectiveAnnotation + public AnnotationMirrorSet getEffectiveAnnotations() { + AnnotationMirrorSet effectiveAnnotations = getErased().getAnnotations(); + // assert atypeFactory.qualHierarchy.getWidth() == effectiveAnnotations + // .size() : "Invalid number of effective annotations (" + // + effectiveAnnotations + "). Should be " + // + atypeFactory.qualHierarchy.getWidth() + " but is " + // + effectiveAnnotations.size() + ". Type: " + this; + return effectiveAnnotations; + } + + /** + * Returns the primary annotation on this type whose class is {@code annoClass}. For {@link + * AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be returned when + * the upper bound may have an annotation with that class, so {@link + * #getEffectiveAnnotation(Class)} should be called instead. + * + * @param annoClass annotation class + * @return the annotation mirror whose class is {@code annoClass} or null + */ + // typetools: getPrimaryAnnotation + public @Nullable AnnotationMirror getAnnotation(Class annoClass) { + for (AnnotationMirror annoMirror : primaryAnnotations) { + if (atypeFactory.areSameByClass(annoMirror, annoClass)) { + return annoMirror; + } + } + return null; + } + + /** + * Returns the primary annotations on this type whose annotation class name {@code annoName}. For + * {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be returned + * when the upper bound may have an annotation with that class, so {@link + * #getEffectiveAnnotation(Class)} should be called instead. + * + * @param annoName annotation class name + * @return the annotation mirror whose class is named {@code annoName} or null + */ + // typetools: getPrimaryAnnotation + public @Nullable AnnotationMirror getAnnotation(String annoName) { + for (AnnotationMirror annoMirror : primaryAnnotations) { + if (AnnotationUtils.areSameByName(annoMirror, annoName)) { + return annoMirror; + } + } + return null; + } + + /** + * Returns the set of explicitly written annotations on this type that are supported by this + * checker. This is useful to check the validity of annotations explicitly present on a type, as + * flow inference might add annotations that were not previously present. Note that since + * AnnotatedTypeMirror instances are created for type uses, this method will return explicit + * annotations in type use locations but will not return explicit annotations that had an impact + * on defaulting, such as an explicit annotation on a class declaration. For example, given: + * + *

{@code @MyExplicitAnno class MyClass {}; MyClass myClassInstance; } + * + *

the result of calling {@code + * atypeFactory.getAnnotatedType(variableTreeForMyClassInstance).getExplicitAnnotations()} + * + *

will not contain {@code @MyExplicitAnno}. + * + * @return the set of explicitly written annotations on this type that are supported by this + * checker + */ + public AnnotationMirrorSet getExplicitAnnotations() { + // TODO JSR 308: The explicit type annotations should be always present + AnnotationMirrorSet explicitAnnotations = new AnnotationMirrorSet(); + List typeAnnotations = + this.getUnderlyingType().getAnnotationMirrors(); + + for (AnnotationMirror explicitAnno : typeAnnotations) { + if (atypeFactory.isSupportedQualifier(explicitAnno)) { + explicitAnnotations.add(explicitAnno); + } + } - /** - * Saves the result of {@code underlyingType.toString().hashCode()} to use when computing the - * hash code of this. (Because AnnotatedTypeMirrors are mutable, the hash code for this cannot - * be saved.) Call {@link #getUnderlyingTypeHashCode()} rather than using the field directly. - */ - private int underlyingTypeHashCode = -1; + return explicitAnnotations; + } + + /** + * Returns true if this type has a primary annotation that is the same as {@code a}. + * + *

This method considers the annotation's values. If the type is {@code @A("s") @B(3) Object}, + * then a call with {@code @A("t")} or {@code @A} will return false, whereas a call with + * {@code @B(3)} will return true. + * + *

In contrast to {@link #hasAnnotationRelaxed(AnnotationMirror)} this method also compares + * annotation values. + * + * @param a the annotation to check for + * @return true iff this type has a primary annotation that is the same as {@code a} + * @see #hasAnnotationRelaxed(AnnotationMirror) + */ + // typetools: hasPrimaryAnnotation + public boolean hasAnnotation(AnnotationMirror a) { + return AnnotationUtils.containsSame(primaryAnnotations, a); + } + + /** + * Returns true if this type has a primary annotation that has the same annotation type as {@code + * a}. This method does not consider an annotation's values. + * + * @param a the class of annotation to check for + * @return true iff the type contains an annotation with the same type as the annotation given by + * {@code a} + */ + public boolean hasAnnotation(Class a) { + return getAnnotation(a) != null; + } + + /** + * Returns the "effective" annotation on this type with the class {@code annoClass} or {@code + * null} if this type does not have one. + * + *

An effective annotation is the annotation on the type itself, or on the upper/extends bound + * of a type variable/wildcard (recursively, until a class type is reached). + * + * @param annoClass annotation class + * @return the effective annotation with the same class as {@code annoClass} + */ + public @Nullable AnnotationMirror getEffectiveAnnotation(Class annoClass) { + for (AnnotationMirror annoMirror : getEffectiveAnnotations()) { + if (atypeFactory.areSameByClass(annoMirror, annoClass)) { + return annoMirror; + } + } + return null; + } + + /** + * A version of {@link #hasAnnotation(Class)} that considers annotations on the upper bound of + * wildcards and type variables. + */ + public boolean hasEffectiveAnnotation(Class a) { + return getEffectiveAnnotation(a) != null; + } + + /** + * A version of {@link #hasAnnotation(AnnotationMirror)} that considers annotations on the upper + * bound of wildcards and type variables. + */ + public boolean hasEffectiveAnnotation(AnnotationMirror a) { + return AnnotationUtils.containsSame(getEffectiveAnnotations(), a); + } + + /** + * Returns true if this type contains the given annotation explicitly written at declaration. This + * method considers the annotation's values. If the type is {@code @A("s") @B(3) Object}, a call + * with {@code @A("t")} or {@code @A} will return false, whereas a call with {@code @B(3)} will + * return true. + * + *

In contrast to {@link #hasExplicitAnnotationRelaxed(AnnotationMirror)} this method also + * compares annotation values. + * + *

See the documentation for {@link #getExplicitAnnotations()} for details on which explicit + * annotations are not included. + * + * @param a the annotation to check for + * @return true iff the annotation {@code a} is explicitly written on the type + * @see #hasExplicitAnnotationRelaxed(AnnotationMirror) + * @see #getExplicitAnnotations() + */ + public boolean hasExplicitAnnotation(AnnotationMirror a) { + return AnnotationUtils.containsSame(getExplicitAnnotations(), a); + } + + /** + * Returns true if this type has a primary annotation that has the same annotation class as {@code + * a}. + * + *

This method does not consider an annotation's values. If the type is {@code @A("s") @B(3) + * Object}, then a call with {@code @A("t")}, {@code @A}, or {@code @B} will return true. + * + * @param a the annotation to check for + * @return true iff the type has a primary annotation with the same type as {@code a} + * @see #hasAnnotation(AnnotationMirror) + */ + // typetools: hasPrimaryAnnotationRelaxed + public boolean hasAnnotationRelaxed(AnnotationMirror a) { + return AnnotationUtils.containsSameByName(primaryAnnotations, a); + } + + /** + * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that considers annotations on the + * upper bound of wildcards and type variables. + */ + public boolean hasEffectiveAnnotationRelaxed(AnnotationMirror a) { + return AnnotationUtils.containsSameByName(getEffectiveAnnotations(), a); + } + + /** + * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that only considers annotations + * that are explicitly written on the type. + * + *

See the documentation for {@link #getExplicitAnnotations()} for details on which explicit + * annotations are not included. + */ + public boolean hasExplicitAnnotationRelaxed(AnnotationMirror a) { + return AnnotationUtils.containsSameByName(getExplicitAnnotations(), a); + } + + /** + * Returns true if this type contains an explicitly written annotation with the same annotation + * type as a particular annotation. This method does not consider an annotation's values. + * + *

See the documentation for {@link #getExplicitAnnotations()} for details on which explicit + * annotations are not included. + * + * @param a the class of annotation to check for + * @return true iff the type contains an explicitly written annotation with the same type as the + * annotation given by {@code a} + * @see #getExplicitAnnotations() + */ + public boolean hasExplicitAnnotation(Class a) { + return AnnotationUtils.containsSameByName(getExplicitAnnotations(), getAnnotation(a)); + } + + /** + * Adds the canonical version of {@code annotation} as a primary annotation of this type and, in + * the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and {@link + * AnnotatedIntersectionType}s, adds it to all bounds. (The canonical version is found via {@link + * AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of {@code annotation} is + * not a supported qualifier, then no annotation is added. If this type already has annotation in + * the same hierarchy as {@code annotation}, the behavior of this method is undefined. + * + * @param annotation the annotation to add + */ + public void addAnnotation(AnnotationMirror annotation) { + if (annotation == null) { + throw new BugInCF("AnnotatedTypeMirror.addAnnotation: null argument."); + } + if (atypeFactory.isSupportedQualifier(annotation)) { + this.primaryAnnotations.add(annotation); + } else { + AnnotationMirror canonical = atypeFactory.canonicalAnnotation(annotation); + if (atypeFactory.isSupportedQualifier(canonical)) { + addAnnotation(canonical); + } + } + } + + /** + * Adds an annotation to this type, removing any existing primary annotations from the same + * qualifier hierarchy first. + * + * @param a the annotation to add + */ + public void replaceAnnotation(AnnotationMirror a) { + this.removeAnnotationInHierarchy(a); + this.addAnnotation(a); + } + + /** + * Adds an annotation to this type. + * + * @param a the class of the annotation to add + * @deprecated This method creates a new {@code AnnotationMirror} every time it is called. Instead + * of calling this method, store the {@code AnnotationMirror} in a field and use {@link + * #addAnnotation(AnnotationMirror)} instead. + */ + @Deprecated // 2023-06-15 + public void addAnnotation(Class a) { + AnnotationMirror anno = AnnotationBuilder.fromClass(atypeFactory.elements, a); + addAnnotation(anno); + } + + /** + * Adds the canonical version of all {@code annotations} as primary annotations of this type and, + * in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and {@link + * AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found via + * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code + * annotation} is not a supported qualifier, then that annotation is not add added. If this type + * already has annotation in the same hierarchy as any of the {@code annotations}, the behavior of + * this method is undefined. + * + * @param annotations the annotations to add + */ + public void addAnnotations(Iterable annotations) { + for (AnnotationMirror a : annotations) { + this.addAnnotation(a); + } + } + + /** + * Adds only the annotations in {@code annotations} that the type does not already have a primary + * annotation in the same hierarchy. + * + *

The canonical version of the {@code annotations} are added as primary annotations of this + * type and, in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and + * {@link AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found + * via {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an + * annotation is not a supported qualifier, then that annotation is not add added. + * + * @param annotations the annotations to add + */ + public void addMissingAnnotations(Iterable annotations) { + for (AnnotationMirror a : annotations) { + addMissingAnnotation(a); + } + } + + /** + * Add {@code annotation} if the type does not already have a primary annotation in the same + * hierarchy. + * + *

The canonical version of the {@code annotation} is added as a primary annotation of this + * type and (in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and + * {@link AnnotatedIntersectionType}s) added to all bounds. (The canonical version is found via + * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code + * annotation} is not a supported qualifier, then that annotation is not add added. + * + * @param annotation the annotations to add + */ + public void addMissingAnnotation(AnnotationMirror annotation) { + if (!this.hasAnnotationInHierarchy(annotation)) { + this.addAnnotation(annotation); + } + } + + /** + * Adds multiple annotations to this type, removing any existing primary annotations from the same + * qualifier hierarchy first. + * + * @param replAnnos the annotations to replace + */ + public void replaceAnnotations(Iterable replAnnos) { + for (AnnotationMirror a : replAnnos) { + this.replaceAnnotation(a); + } + } + + /** + * Removes a primary annotation from the type. + * + * @param a the annotation to remove + * @return true if the annotation was removed, false if the type's annotations were unchanged + */ + // typetools removePrimaryAnnotation + public boolean removeAnnotation(AnnotationMirror a) { + AnnotationMirror anno = AnnotationUtils.getSame(primaryAnnotations, a); + if (anno != null) { + return primaryAnnotations.remove(anno); + } + return false; + } + + /** + * Removes a primary annotation of the given class from the type. + * + * @param a the class of the annotation to remove + * @return true if the annotation was removed, false if the type's annotations were unchanged + */ + // typetools: removePrimaryAnnotationByClass + public boolean removeAnnotationByClass(Class a) { + AnnotationMirror anno = atypeFactory.getAnnotationByClass(primaryAnnotations, a); + if (anno != null) { + return this.removeAnnotation(anno); + } + return false; + } + + /** + * Remove any primary annotation that is in the same qualifier hierarchy as the parameter. + * + * @param a an annotation from the same qualifier hierarchy + * @return if an annotation was removed + */ + // typetools removePrimaryAnnotationInHierarchy + public boolean removeAnnotationInHierarchy(AnnotationMirror a) { + AnnotationMirror prev = this.getAnnotationInHierarchy(a); + if (prev != null) { + return this.removeAnnotation(prev); + } + return false; + } + + /** + * Remove an annotation that is in the same qualifier hierarchy as the parameter, unless it's the + * top annotation. + * + * @param a an annotation from the same qualifier hierarchy + * @return if an annotation was removed + * @deprecated This will be removed in a future release + */ + @Deprecated // 2023-06-15 + public boolean removeNonTopAnnotationInHierarchy(AnnotationMirror a) { + AnnotationMirror prev = this.getAnnotationInHierarchy(a); + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + if (prev != null && !prev.equals(qualHierarchy.getTopAnnotation(a))) { + return this.removeAnnotation(prev); + } + return false; + } + + /** + * Removes multiple primary annotations from the type. + * + * @param annotations the annotations to remove + * @return true if at least one annotation was removed, false if the type's annotations were + * unchanged + */ + // typetools: removePrimaryAnnotations + public boolean removeAnnotations(Iterable annotations) { + boolean changed = false; + for (AnnotationMirror a : annotations) { + changed |= this.removeAnnotation(a); + } + return changed; + } + + /** Removes all primary annotations on this type. */ + // typetools: clearPrimaryAnnotations + public void clearAnnotations() { + primaryAnnotations.clear(); + } + + @SideEffectFree + @Override + public final String toString() { + return atypeFactory.getAnnotatedTypeFormatter().format(this); + } + + @SideEffectFree + public final String toString(boolean verbose) { + return atypeFactory.getAnnotatedTypeFormatter().format(this, verbose); + } + + /** + * Returns the erasure type of this type, according to JLS specifications. + * + * @see https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.6 + * @return the erasure of this AnnotatedTypeMirror, this is always a copy even if the erasure and + * the original type are equivalent + */ + public AnnotatedTypeMirror getErased() { + return deepCopy(); + } + + /** + * Returns a deep copy of this type. A deep copy implies that each component type is copied + * recursively and the returned type refers to those copies in its component locations. + * + *

Note: deepCopy provides two important properties in the returned copy: + * + *

    + *
  1. Structure preservation -- The exact structure of the original AnnotatedTypeMirror is + * preserved in the copy including all component types. + *
  2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror + * and its components have been copied to the new type. + *
+ * + * If copyAnnotations is set to false, the second property, Annotation preservation, is removed. + * This is useful for cases in which the user may want to copy the structure of a type exactly but + * NOT its annotations. + * + * @return a deep copy + */ + public abstract AnnotatedTypeMirror deepCopy(boolean copyAnnotations); + + /** + * Returns a deep copy of this type with annotations. + * + *

Each subclass implements this method with the subclass return type. The method body must + * always be a call to deepCopy(true). + * + * @return a deep copy of this type with annotations + * @see #deepCopy(boolean) + */ + @Override + public abstract AnnotatedTypeMirror deepCopy(); + + /** + * Returns a shallow copy of this type. A shallow copy implies that each component type in the + * output copy refers to the same object as the object being copied. + * + * @param copyAnnotations whether copy should have annotations, i.e. whether field {@code + * annotations} should be copied. + */ + public abstract AnnotatedTypeMirror shallowCopy(boolean copyAnnotations); + + /** + * Returns a shallow copy of this type with annotations. + * + *

Each subclass implements this method with the subclass return type. The method body must + * always be a call to shallowCopy(true). + * + * @see #shallowCopy(boolean) + * @return a shallow copy of this type with annotations + */ + public abstract AnnotatedTypeMirror shallowCopy(); + + /** + * Returns whether this type or any component type is a wildcard type for which Java 7 type + * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType. + * + * @return whether this type or any component type is a wildcard type for which Java 7 type + * inference is insufficient + */ + public boolean containsUninferredTypeArguments() { + return atypeFactory.containsUninferredTypeArguments(this); + } + + /** + * Create an {@link AnnotatedDeclaredType} with the underlying type of {@link Object}. It includes + * any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. + * + * @param atypeFactory type factory to use + * @return AnnotatedDeclaredType for Object + */ + protected static AnnotatedDeclaredType createTypeOfObject(AnnotatedTypeFactory atypeFactory) { + AnnotatedDeclaredType objectType = + atypeFactory.fromElement( + atypeFactory.elements.getTypeElement(Object.class.getCanonicalName())); + objectType.declaration = false; + return objectType; + } + + /** + * Create an {@link AnnotatedDeclaredType} with the underlying type of {@code java.lang.Record}. + * It includes any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. + * + * @param atypeFactory type factory to use + * @return AnnotatedDeclaredType for Record + */ + protected static AnnotatedDeclaredType createTypeOfRecord(AnnotatedTypeFactory atypeFactory) { + AnnotatedDeclaredType recordType = + atypeFactory.fromElement(atypeFactory.elements.getTypeElement("java.lang.Record")); + recordType.declaration = false; + return recordType; + } + + /** + * Returns the result of calling {@code underlyingType.toString().hashcode()}. This method saves + * the result in a field so that it isn't recomputed each time. + * + * @return the result of calling {@code underlyingType.toString().hashcode()} + */ + public int getUnderlyingTypeHashCode() { + if (underlyingTypeHashCode == -1) { + underlyingTypeHashCode = underlyingType.toString().hashCode(); + } + return underlyingTypeHashCode; + } - /** The annotations on this type. */ - // AnnotationMirror doesn't override Object.hashCode, .equals, so we use - // the class name of Annotation instead. - // Caution: Assumes that a type can have at most one AnnotationMirror for any Annotation type. - protected final AnnotationMirrorSet primaryAnnotations = new AnnotationMirrorSet(); + /** Represents a declared type (whether class or interface). */ + public static class AnnotatedDeclaredType extends AnnotatedTypeMirror { - // /** The explicitly written annotations on this type. */ - // TODO: use this to cache the result once computed? For generic types? - // protected final AnnotationMirrorSet explicitannotations = - // new AnnotationMirrorSet(); + /** Parametrized Type Arguments. */ + protected List typeArgs; /** - * Constructor for AnnotatedTypeMirror. + * Whether the type was initially raw, i.e. the user did not provide the type arguments. + * typeArgs will contain inferred type arguments, which might be too conservative at the moment. + * TODO: improve inference. * - * @param underlyingType the underlying type - * @param atypeFactory used to create further types and to access global information (Types, - * Elements, ...) + *

Ideally, the field would be final. However, when we determine the supertype of a raw type, + * we need to set isUnderlyingTypeRaw for the supertype. */ - private AnnotatedTypeMirror(TypeMirror underlyingType, AnnotatedTypeFactory atypeFactory) { - this.underlyingType = underlyingType; - assert atypeFactory != null; - this.atypeFactory = atypeFactory; - } + private boolean isUnderlyingTypeRaw; - /// This class doesn't customize the clone() method; use deepCopy() instead. - // @Override - // public AnnotatedTypeMirror clone() { ... } + /** The enclosing type. May be null. May be changed. */ + protected @Nullable AnnotatedDeclaredType enclosingType; + + /** True if this represents a declaration, rather than a use, of a type. */ + private boolean declaration; /** - * Creates an AnnotatedTypeMirror for the provided type. The result contains no annotations. + * Constructor for this type. The result contains no annotations. * - * @param type the underlying type for the resulting AnnotatedTypeMirror - * @param atypeFactory the type factory that will build the result - * @param isDeclaration true if the result should represent a declaration, rather than a use, of - * a type - * @return an AnnotatedTypeMirror whose underlying type is {@code type} - */ - public static AnnotatedTypeMirror createType( - TypeMirror type, AnnotatedTypeFactory atypeFactory, boolean isDeclaration) { - if (type == null) { - throw new BugInCF("AnnotatedTypeMirror.createType: input type must not be null"); - } - - AnnotatedTypeMirror result; - switch (type.getKind()) { - case ARRAY: - result = new AnnotatedArrayType((ArrayType) type, atypeFactory); - break; - case DECLARED: - result = - new AnnotatedDeclaredType((DeclaredType) type, atypeFactory, isDeclaration); - break; - case ERROR: - throw new ErrorTypeKindException( - "AnnotatedTypeMirror.createType: input is not compilable. Found error type:" - + " " - + type); - - case EXECUTABLE: - result = new AnnotatedExecutableType((ExecutableType) type, atypeFactory); - break; - case VOID: - case PACKAGE: - case NONE: - result = new AnnotatedNoType((NoType) type, atypeFactory); - break; - case NULL: - result = new AnnotatedNullType((NullType) type, atypeFactory); - break; - case TYPEVAR: - result = - new AnnotatedTypeVariable((TypeVariable) type, atypeFactory, isDeclaration); - break; - case WILDCARD: - result = new AnnotatedWildcardType((WildcardType) type, atypeFactory); - break; - case INTERSECTION: - result = new AnnotatedIntersectionType((IntersectionType) type, atypeFactory); - break; - case UNION: - result = new AnnotatedUnionType((UnionType) type, atypeFactory); - break; - default: - if (type.getKind().isPrimitive()) { - result = new AnnotatedPrimitiveType((PrimitiveType) type, atypeFactory); - break; - } - throw new BugInCF( - "AnnotatedTypeMirror.createType: unidentified type " - + type - + " (" - + type.getKind() - + ")"); - } - /*if (jctype.isAnnotated()) { - result.addAnnotations(jctype.getAnnotationMirrors()); - }*/ - return result; + * @param type underlying kind of this type + * @param atypeFactory the AnnotatedTypeFactory used to create this type + */ + private AnnotatedDeclaredType( + DeclaredType type, AnnotatedTypeFactory atypeFactory, boolean declaration) { + super(type, atypeFactory); + TypeElement typeelem = (TypeElement) type.asElement(); + DeclaredType declty = (DeclaredType) typeelem.asType(); + isUnderlyingTypeRaw = + !declty.getTypeArguments().isEmpty() && type.getTypeArguments().isEmpty(); + + TypeMirror encl = type.getEnclosingType(); + if (encl.getKind() == TypeKind.DECLARED) { + this.enclosingType = (AnnotatedDeclaredType) createType(encl, atypeFactory, declaration); + } else if (encl.getKind() == TypeKind.NONE) { + this.enclosingType = null; + } else { + throw new BugInCF( + "AnnotatedDeclaredType: unsupported enclosing type: " + + type.getEnclosingType() + + " (" + + encl.getKind() + + ")"); + } + + this.declaration = declaration; } @Override - public final boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } + public boolean isDeclaration() { + return declaration; + } - if (!(o instanceof AnnotatedTypeMirror)) { - return false; - } + @Override + public AnnotatedDeclaredType deepCopy(boolean copyAnnotations) { + return (AnnotatedDeclaredType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - return EQUALITY_COMPARER.visit(this, (AnnotatedTypeMirror) o, null); + @Override + public AnnotatedDeclaredType deepCopy() { + return deepCopy(true); } - @Pure @Override - public final int hashCode() { - return HASHCODE_VISITOR.visit(this); + public AnnotatedDeclaredType asUse() { + if (!this.isDeclaration()) { + return this; + } + AnnotatedDeclaredType result = this.shallowCopy(true); + result.declaration = false; + if (this.enclosingType != null) { + result.enclosingType = this.enclosingType.asUse(); + } + // setTypeArguments calls asUse on all the new type arguments. + result.setTypeArguments(typeArgs); + + // If "this" is a type declaration with a type variable that references itself, e.g. + // MyClass>, then the type variable is a declaration, i.e. the first + // T, but the reference to the type variable is a use, i.e. the second T. When "this" + // is converted to a use, then both type variables are uses and should be the same + // object. + // The code below does this. + Map mapping = new HashMap<>(typeArgs.size()); + for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { + AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; + mapping.put(typeVar.getUnderlyingType(), typeVar); + } + for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { + AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; + AnnotatedTypeMirror upperBound = + atypeFactory + .getTypeVarSubstitutor() + .substituteWithoutCopyingTypeArguments(mapping, typeVar.getUpperBound()); + typeVar.setUpperBound(upperBound); + } + + return result; } - /** - * Applies a visitor to this type. - * - * @param the return type of the visitor's methods - * @param

the type of the additional parameter to the visitor's methods - * @param v the visitor operating on this type - * @param p additional parameter to the visitor - * @return a visitor-specified result - */ - public abstract R accept(AnnotatedTypeVisitor v, P p); + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitDeclared(this, p); + } /** - * Returns the {@code kind} of this type. + * Sets the type arguments on this type. * - * @return the kind of this type + * @param ts a list of type arguments to be captured by this method */ - public TypeKind getKind() { - return underlyingType.getKind(); + public void setTypeArguments(List ts) { + if (ts == null || ts.isEmpty()) { + typeArgs = Collections.emptyList(); + } else if (isDeclaration()) { + for (AnnotatedTypeMirror typeArg : ts) { + if (typeArg.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF( + "Type declaration must have type variables as type arguments. Found %s", typeArg); + } + if (!typeArg.isDeclaration()) { + throw new BugInCF( + "Type declarations must have type variables that are declarations. Found %s", + typeArg); + } + } + typeArgs = Collections.unmodifiableList(ts); + } else { + List uses = CollectionsPlume.mapList(AnnotatedTypeMirror::asUse, ts); + typeArgs = Collections.unmodifiableList(uses); + } } /** - * Given a primitive type, return its kind. Given a boxed primitive type, return the - * corresponding primitive type kind. Otherwise, return null. + * Returns the type argument for this type. * - * @return a primitive type kind if this is a primitive type or boxed primitive type; otherwise - * null + * @return the type argument for this type */ - public @Nullable TypeKind getPrimitiveKind() { - return TypeKindUtils.primitiveOrBoxedToTypeKind(getUnderlyingType()); + public List getTypeArguments() { + if (typeArgs != null) { + return typeArgs; + } else if (isUnderlyingTypeRaw()) { + // Initialize the type arguments with uninferred wildcards. + BoundsInitializer.initializeTypeArgs(this); + return typeArgs; + } else if (getUnderlyingType().getTypeArguments().isEmpty()) { + typeArgs = Collections.emptyList(); + return typeArgs; + } else { + // Initialize type argument for a non-raw declared type that has type arguments/ + BoundsInitializer.initializeTypeArgs(this); + return typeArgs; + } } /** - * Returns the underlying unannotated Java type, which this wraps. + * Returns true if the underlying type is raw. The receiver of this method is not raw, however; + * its annotated type arguments have been inferred. * - * @return the underlying type + * @return true iff the type was raw */ - public TypeMirror getUnderlyingType() { - return underlyingType; + public boolean isUnderlyingTypeRaw() { + return isUnderlyingTypeRaw; } /** - * Returns true if this type mirror represents a declaration, rather than a use, of a type. - * - *

For example, {@code class List { ... }} declares a new type {@code List}, while - * {@code List} is a use of the type. - * - * @return true if this represents a declaration + * Set the isUnderlyingTypeRaw flag to true. This should only be necessary when determining the + * supertypes of a raw type. */ - public boolean isDeclaration() { - return false; + protected void setIsUnderlyingTypeRaw() { + this.isUnderlyingTypeRaw = true; } - public AnnotatedTypeMirror asUse() { - return this; + @Override + public DeclaredType getUnderlyingType() { + return (DeclaredType) underlyingType; } - /** - * Returns true if this type has a primary annotation in the same hierarchy as {@code - * annotation}. - * - *

This method does not account for annotations in deep types (type arguments, array - * components, etc). - * - * @param annotation the qualifier hierarchy to check for - * @return true iff this type has a primary annotation in the same hierarchy as {@code - * annotation}. - */ - // typetools: hasPrimaryAnnotationInHierarchy - public boolean hasAnnotationInHierarchy(AnnotationMirror annotation) { - return getAnnotationInHierarchy(annotation) != null; + @Override + public List directSupertypes() { + return Collections.unmodifiableList(SupertypeFinder.directSupertypes(this)); } - /** - * Returns the primary annotation on this type that is in the same hierarchy as {@code - * annotation}. For {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code - * null} may be returned when the upper bound may have an annotation with that class, so {@link - * #getEffectiveAnnotationInHierarchy(AnnotationMirror)} should be called instead. - * - *

This method does not account for annotations in deep types (type arguments, array - * components, etc). - * - *

May return null if the receiver is a type variable or a wildcard without a primary - * annotation, or if the receiver is not yet fully annotated. - * - * @param annotation an annotation in the qualifier hierarchy to check for - * @return the annotation mirror whose class is named {@code annoNAme} or null - */ - // typetools: getPrimaryAnnotationInHierarchy - public @Nullable AnnotationMirror getAnnotationInHierarchy(AnnotationMirror annotation) { - if (primaryAnnotations.isEmpty()) { - return null; - } - AnnotationMirror canonical = annotation; - if (!atypeFactory.isSupportedQualifier(canonical)) { - canonical = atypeFactory.canonicalAnnotation(annotation); - if (canonical == null) { - // This can happen if annotation is unrelated to this AnnotatedTypeMirror. - return null; - } - } - if (atypeFactory.isSupportedQualifier(canonical)) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - AnnotationMirror anno = - qualHierarchy.findAnnotationInSameHierarchy(primaryAnnotations, canonical); - if (anno != null) { - return anno; - } - } - return null; + @Override + public AnnotatedDeclaredType shallowCopy() { + return shallowCopy(true); } - /** - * Returns the "effective" annotation from the same hierarchy as {@code annotation}, otherwise - * returns {@code null}. - * - *

An effective annotation is the annotation on the type itself, or on the upper/extends - * bound of a type variable/wildcard (recursively, until a class type is reached). - * - * @param annotation an annotation in the qualifier hierarchy to check for - * @return an annotation from the same hierarchy as {@code annotation} if present - */ - public @Nullable AnnotationMirror getEffectiveAnnotationInHierarchy( - AnnotationMirror annotation) { - AnnotationMirror canonical = annotation; - if (!atypeFactory.isSupportedQualifier(canonical)) { - canonical = atypeFactory.canonicalAnnotation(annotation); - } - if (atypeFactory.isSupportedQualifier(canonical)) { - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - AnnotationMirror anno = - qualHierarchy.findAnnotationInSameHierarchy( - getEffectiveAnnotations(), canonical); - if (anno != null) { - return anno; - } - } - return null; + @Override + public AnnotatedDeclaredType shallowCopy(boolean copyAnnotations) { + AnnotatedDeclaredType type = + new AnnotatedDeclaredType(getUnderlyingType(), atypeFactory, declaration); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.setEnclosingType(getEnclosingType()); + type.setTypeArguments(getTypeArguments()); + return type; } /** - * Returns the primary annotations on this type. For {@link AnnotatedTypeVariable}s and {@link - * AnnotatedWildcardType}s, the returned annotations may be empty or missing annotations in - * hierarchies, so {@link #getEffectiveAnnotations()} should be called instead. - * - *

It does not include annotations in deep types (type arguments, array components, etc). - * - *

To get the single primary annotation in a particular hierarchy, use {@link - * #getAnnotationInHierarchy}. + * Return the declared type with its type arguments removed. This also replaces the underlying + * type with its erasure. * - * @return an unmodifiable set of the annotations on this + * @return a fresh copy of the declared type with no type arguments */ - // typetools: getPrimaryAnnotations - // typetools: removed method getPrimaryAnnotation - public final AnnotationMirrorSet getAnnotations() { - return AnnotationMirrorSet.unmodifiableSet(primaryAnnotations); + @Override + public AnnotatedDeclaredType getErased() { + AnnotatedDeclaredType erased = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + atypeFactory.types.erasure(underlyingType), atypeFactory, false); + erased.addAnnotations(this.getAnnotations()); + AnnotatedDeclaredType erasedEnclosing = erased.getEnclosingType(); + AnnotatedDeclaredType thisEnclosing = this.getEnclosingType(); + while (erasedEnclosing != null) { + erasedEnclosing.addAnnotations(thisEnclosing.getAnnotations()); + erasedEnclosing = erasedEnclosing.getEnclosingType(); + thisEnclosing = thisEnclosing.getEnclosingType(); + } + return erased; } /** - * Returns the annotations on this type; mutations affect this object, because the return type - * is an alias of the {@code annotations} field. It does not include annotations in deep types - * (type arguments, array components, etc). - * - *

The returned set should not be modified, but for efficiency reasons modification is not - * prevented. Modifications might break invariants. + * Sets the enclosing type. * - * @return the set of the annotations on this; mutations affect this object + * @param enclosingType the new enclosing type */ - // typetools: getPrimaryAnnotationsField - protected final AnnotationMirrorSet getAnnotationsField() { - return primaryAnnotations; + public void setEnclosingType(@Nullable AnnotatedDeclaredType enclosingType) { + this.enclosingType = enclosingType; } /** - * Returns the "effective" annotations on this type, i.e. the annotations on the type itself, or - * on the upper/extends bound of a type variable/wildcard (recursively, until a class type is - * reached). If this is fully-annotated, the returned set will contain one annotation per - * hierarchy. + * Returns the enclosing type, as in the type of {@code A} in the type {@code A.B}. May return + * null. * - * @return a set of the annotations on this - */ - // TODO: When the current, deprecated `getAnnotations()` (deprecation date 2023-06-15) is - // removed, - // rename all the "getEffectiveAnnotation...()" methods to just "getAnnotation...()". - // EISOP will not do this renaming, it would introduce inconsistent behavior with how - // getAnnotations in javac APIs works. - // Removed getEffectiveAnnotation - public AnnotationMirrorSet getEffectiveAnnotations() { - AnnotationMirrorSet effectiveAnnotations = getErased().getAnnotations(); - // assert atypeFactory.qualHierarchy.getWidth() == effectiveAnnotations - // .size() : "Invalid number of effective annotations (" - // + effectiveAnnotations + "). Should be " - // + atypeFactory.qualHierarchy.getWidth() + " but is " - // + effectiveAnnotations.size() + ". Type: " + this; - return effectiveAnnotations; - } - - /** - * Returns the primary annotation on this type whose class is {@code annoClass}. For {@link - * AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be returned when - * the upper bound may have an annotation with that class, so {@link - * #getEffectiveAnnotation(Class)} should be called instead. - * - * @param annoClass annotation class - * @return the annotation mirror whose class is {@code annoClass} or null - */ - // typetools: getPrimaryAnnotation - public @Nullable AnnotationMirror getAnnotation(Class annoClass) { - for (AnnotationMirror annoMirror : primaryAnnotations) { - if (atypeFactory.areSameByClass(annoMirror, annoClass)) { - return annoMirror; - } - } - return null; + * @return enclosingType the enclosing type, or null if this is a top-level type + */ + public @Nullable AnnotatedDeclaredType getEnclosingType() { + return enclosingType; } + } - /** - * Returns the primary annotations on this type whose annotation class name {@code annoName}. - * For {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be - * returned when the upper bound may have an annotation with that class, so {@link - * #getEffectiveAnnotation(Class)} should be called instead. - * - * @param annoName annotation class name - * @return the annotation mirror whose class is named {@code annoName} or null - */ - // typetools: getPrimaryAnnotation - public @Nullable AnnotationMirror getAnnotation(String annoName) { - for (AnnotationMirror annoMirror : primaryAnnotations) { - if (AnnotationUtils.areSameByName(annoMirror, annoName)) { - return annoMirror; - } - } - return null; - } + /** Represents a type of an executable. An executable is a method, constructor, or initializer. */ + public static class AnnotatedExecutableType extends AnnotatedTypeMirror { - /** - * Returns the set of explicitly written annotations on this type that are supported by this - * checker. This is useful to check the validity of annotations explicitly present on a type, as - * flow inference might add annotations that were not previously present. Note that since - * AnnotatedTypeMirror instances are created for type uses, this method will return explicit - * annotations in type use locations but will not return explicit annotations that had an impact - * on defaulting, such as an explicit annotation on a class declaration. For example, given: - * - *

{@code @MyExplicitAnno class MyClass {}; MyClass myClassInstance; } - * - *

the result of calling {@code - * atypeFactory.getAnnotatedType(variableTreeForMyClassInstance).getExplicitAnnotations()} - * - *

will not contain {@code @MyExplicitAnno}. - * - * @return the set of explicitly written annotations on this type that are supported by this - * checker - */ - public AnnotationMirrorSet getExplicitAnnotations() { - // TODO JSR 308: The explicit type annotations should be always present - AnnotationMirrorSet explicitAnnotations = new AnnotationMirrorSet(); - List typeAnnotations = - this.getUnderlyingType().getAnnotationMirrors(); - - for (AnnotationMirror explicitAnno : typeAnnotations) { - if (atypeFactory.isSupportedQualifier(explicitAnno)) { - explicitAnnotations.add(explicitAnno); - } - } + private @MonotonicNonNull ExecutableElement element; - return explicitAnnotations; + private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factory) { + super(type, factory); } + /** The parameter types; an unmodifiable list. */ + private @MonotonicNonNull List paramTypes = null; + + /** Whether {@link #paramTypes} has been computed. */ + private boolean paramTypesComputed = false; + /** - * Returns true if this type has a primary annotation that is the same as {@code a}. - * - *

This method considers the annotation's values. If the type is {@code @A("s") @B(3) - * Object}, then a call with {@code @A("t")} or {@code @A} will return false, whereas a call - * with {@code @B(3)} will return true. - * - *

In contrast to {@link #hasAnnotationRelaxed(AnnotationMirror)} this method also compares - * annotation values. - * - * @param a the annotation to check for - * @return true iff this type has a primary annotation that is the same as {@code a} - * @see #hasAnnotationRelaxed(AnnotationMirror) + * The receiver type of this executable type; null for static methods and constructors of + * top-level classes. */ - // typetools: hasPrimaryAnnotation - public boolean hasAnnotation(AnnotationMirror a) { - return AnnotationUtils.containsSame(primaryAnnotations, a); - } + private @Nullable AnnotatedDeclaredType receiverType; /** - * Returns true if this type has a primary annotation that has the same annotation type as - * {@code a}. This method does not consider an annotation's values. - * - * @param a the class of annotation to check for - * @return true iff the type contains an annotation with the same type as the annotation given - * by {@code a} + * The varargs type is the last element of {@link #paramTypes} if the method or constructor + * accepts a variable number of arguments and the {@link #paramTypes} has not been expanded yet. + * This type needs to be stored in the field to avoid being affected by calling {@link + * AnnotatedTypes#adaptParameters(AnnotatedTypeFactory, + * AnnotatedTypeMirror.AnnotatedExecutableType, List, com.sun.source.tree.NewClassTree)}. */ - public boolean hasAnnotation(Class a) { - return getAnnotation(a) != null; - } + private @MonotonicNonNull AnnotatedArrayType varargType = null; + + /** Whether {@link #receiverType} has been computed. */ + private boolean receiverTypeComputed = false; + + /** The return type. */ + private AnnotatedTypeMirror returnType; + + /** Whether {@link #returnType} has been computed. */ + private boolean returnTypeComputed = false; + + /** The thrown types; an unmodifiable list. */ + private List thrownTypes; + + /** Whether {@link #thrownTypes} has been computed. */ + private boolean thrownTypesComputed = false; + + /** The type variables; an unmodifiable list. */ + private List typeVarTypes; + + /** Whether {@link #typeVarTypes} has been computed. */ + private boolean typeVarTypesComputed = false; /** - * Returns the "effective" annotation on this type with the class {@code annoClass} or {@code - * null} if this type does not have one. - * - *

An effective annotation is the annotation on the type itself, or on the upper/extends - * bound of a type variable/wildcard (recursively, until a class type is reached). + * Returns true if this type represents a varargs method. * - * @param annoClass annotation class - * @return the effective annotation with the same class as {@code annoClass} - */ - public @Nullable AnnotationMirror getEffectiveAnnotation( - Class annoClass) { - for (AnnotationMirror annoMirror : getEffectiveAnnotations()) { - if (atypeFactory.areSameByClass(annoMirror, annoClass)) { - return annoMirror; - } - } - return null; + * @return true if this type represents a varargs method + */ + public boolean isVarArgs() { + return this.element.isVarArgs(); } - /** - * A version of {@link #hasAnnotation(Class)} that considers annotations on the upper bound of - * wildcards and type variables. - */ - public boolean hasEffectiveAnnotation(Class a) { - return getEffectiveAnnotation(a) != null; + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitExecutable(this, p); } - /** - * A version of {@link #hasAnnotation(AnnotationMirror)} that considers annotations on the upper - * bound of wildcards and type variables. - */ - public boolean hasEffectiveAnnotation(AnnotationMirror a) { - return AnnotationUtils.containsSame(getEffectiveAnnotations(), a); + @Override + public ExecutableType getUnderlyingType() { + return (ExecutableType) this.underlyingType; } /** - * Returns true if this type contains the given annotation explicitly written at declaration. - * This method considers the annotation's values. If the type is {@code @A("s") @B(3) Object}, a - * call with {@code @A("t")} or {@code @A} will return false, whereas a call with {@code @B(3)} - * will return true. - * - *

In contrast to {@link #hasExplicitAnnotationRelaxed(AnnotationMirror)} this method also - * compares annotation values. - * - *

See the documentation for {@link #getExplicitAnnotations()} for details on which explicit - * annotations are not included. + * It never makes sense to add annotations to an executable type. Instead, they should be added + * to the appropriate component. * - * @param a the annotation to check for - * @return true iff the annotation {@code a} is explicitly written on the type - * @see #hasExplicitAnnotationRelaxed(AnnotationMirror) - * @see #getExplicitAnnotations() + * @deprecated add to the appropriate component */ - public boolean hasExplicitAnnotation(AnnotationMirror a) { - return AnnotationUtils.containsSame(getExplicitAnnotations(), a); + @Deprecated // not for removal + @Override + public void addAnnotation(AnnotationMirror annotation) { + assert false : "AnnotatedExecutableType.addAnnotation should never be called"; } /** - * Returns true if this type has a primary annotation that has the same annotation class as - * {@code a}. + * Sets the parameter types of this executable type, excluding the receiver.If paramTypes has + * been computed and this type is a varargs method, computes and store {@link #varargType} + * before calling this method, @see {@link #varargType} * - *

This method does not consider an annotation's values. If the type is {@code @A("s") @B(3) - * Object}, then a call with {@code @A("t")}, {@code @A}, or {@code @B} will return true. - * - * @param a the annotation to check for - * @return true iff the type has a primary annotation with the same type as {@code a} - * @see #hasAnnotation(AnnotationMirror) + * @param params an unmodifiable list of parameter types to be captured by this method, + * excluding the receiver */ - // typetools: hasPrimaryAnnotationRelaxed - public boolean hasAnnotationRelaxed(AnnotationMirror a) { - return AnnotationUtils.containsSameByName(primaryAnnotations, a); + /*package-private*/ void setParameterTypes(List params) { + if (paramTypesComputed && isVarArgs() && varargType == null) { + throw new BugInCF("Set vararg type before resetting parameter types"); + } + paramTypes = params; + paramTypesComputed = true; } /** - * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that considers annotations on - * the upper bound of wildcards and type variables. + * Returns the parameter types of this executable type, excluding the receiver. + * + * @return the parameter types of this executable type, excluding the receiver */ - public boolean hasEffectiveAnnotationRelaxed(AnnotationMirror a) { - return AnnotationUtils.containsSameByName(getEffectiveAnnotations(), a); + public List getParameterTypes() { + if (!paramTypesComputed) { + assert paramTypes == null; + List underlyingParameterTypes = + ((ExecutableType) underlyingType).getParameterTypes(); + if (underlyingParameterTypes.isEmpty()) { + setParameterTypes(Collections.emptyList()); + } else { + List newParamTypes = + new ArrayList<>(underlyingParameterTypes.size()); + for (TypeMirror t : underlyingParameterTypes) { + if (t.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed + // yet (see Issue #244). + throw new ErrorTypeKindException( + "Problem with parameter type of %s.%s: %s [%s %s]", + element, element.getEnclosingElement(), t, t.getKind(), t.getClass()); + } + newParamTypes.add(createType(t, atypeFactory, false)); + } + setParameterTypes(Collections.unmodifiableList(newParamTypes)); + } + } + // No need to copy or wrap; it is an unmodifiable list. + return paramTypes; } /** - * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that only considers annotations - * that are explicitly written on the type. + * Sets the vararg type of this executable type. * - *

See the documentation for {@link #getExplicitAnnotations()} for details on which explicit - * annotations are not included. + * @param varargType the vararg type of this executable type */ - public boolean hasExplicitAnnotationRelaxed(AnnotationMirror a) { - return AnnotationUtils.containsSameByName(getExplicitAnnotations(), a); + /*package-private*/ void setVarargType(@NonNull AnnotatedArrayType varargType) { + this.varargType = varargType; } /** - * Returns true if this type contains an explicitly written annotation with the same annotation - * type as a particular annotation. This method does not consider an annotation's values. - * - *

See the documentation for {@link #getExplicitAnnotations()} for details on which explicit - * annotations are not included. + * Computes the vararg type of this executable type and stores it in {@link #varargType}. * - * @param a the class of annotation to check for - * @return true iff the type contains an explicitly written annotation with the same type as the - * annotation given by {@code a} - * @see #getExplicitAnnotations() + *

This method computes {@link #varargType} using the {@link #paramTypes} of this executable + * type. To use the {@link #paramTypes} from different executable type, use {@link + * #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. */ - public boolean hasExplicitAnnotation(Class a) { - return AnnotationUtils.containsSameByName(getExplicitAnnotations(), getAnnotation(a)); + /*package-private*/ void computeVarargType() { + computeVarargType(paramTypes); } /** - * Adds the canonical version of {@code annotation} as a primary annotation of this type and, in - * the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and {@link - * AnnotatedIntersectionType}s, adds it to all bounds. (The canonical version is found via - * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of {@code - * annotation} is not a supported qualifier, then no annotation is added. If this type already - * has annotation in the same hierarchy as {@code annotation}, the behavior of this method is - * undefined. + * Computes the vararg type using the passed executable type and stores it in this {@link + * #varargType}. * - * @param annotation the annotation to add + * @param annotatedExecutableType an AnnotatedExecutableType */ - public void addAnnotation(AnnotationMirror annotation) { - if (annotation == null) { - throw new BugInCF("AnnotatedTypeMirror.addAnnotation: null argument."); - } - if (atypeFactory.isSupportedQualifier(annotation)) { - this.primaryAnnotations.add(annotation); - } else { - AnnotationMirror canonical = atypeFactory.canonicalAnnotation(annotation); - if (atypeFactory.isSupportedQualifier(canonical)) { - addAnnotation(canonical); - } - } + /*package-private*/ void computeVarargType(AnnotatedExecutableType annotatedExecutableType) { + computeVarargType(annotatedExecutableType.getParameterTypes()); } /** - * Adds an annotation to this type, removing any existing primary annotations from the same - * qualifier hierarchy first. + * Helper function for {@link #computeVarargType()} and {@link + * #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. * - * @param a the annotation to add + * @param paramTypes the parameter types to determine the vararg type */ - public void replaceAnnotation(AnnotationMirror a) { - this.removeAnnotationInHierarchy(a); - this.addAnnotation(a); + private void computeVarargType(List paramTypes) { + if (!isVarArgs()) { + return; + } + varargType = (AnnotatedArrayType) paramTypes.get(paramTypes.size() - 1); } /** - * Adds an annotation to this type. + * Returns the vararg type of this executable type. * - * @param a the class of the annotation to add - * @deprecated This method creates a new {@code AnnotationMirror} every time it is called. - * Instead of calling this method, store the {@code AnnotationMirror} in a field and use - * {@link #addAnnotation(AnnotationMirror)} instead. + * @return the vararg type of this executable type */ - @Deprecated // 2023-06-15 - public void addAnnotation(Class a) { - AnnotationMirror anno = AnnotationBuilder.fromClass(atypeFactory.elements, a); - addAnnotation(anno); + public @Nullable AnnotatedArrayType getVarargType() { + return varargType; } /** - * Adds the canonical version of all {@code annotations} as primary annotations of this type - * and, in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and - * {@link AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found - * via {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code - * annotation} is not a supported qualifier, then that annotation is not add added. If this type - * already has annotation in the same hierarchy as any of the {@code annotations}, the behavior - * of this method is undefined. + * Sets the return type of this executable type. * - * @param annotations the annotations to add + * @param returnType the new return type */ - public void addAnnotations(Iterable annotations) { - for (AnnotationMirror a : annotations) { - this.addAnnotation(a); - } + /*package-private*/ void setReturnType(AnnotatedTypeMirror returnType) { + this.returnType = returnType; + returnTypeComputed = true; } /** - * Adds only the annotations in {@code annotations} that the type does not already have a - * primary annotation in the same hierarchy. - * - *

The canonical version of the {@code annotations} are added as primary annotations of this - * type and, in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and - * {@link AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found - * via {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an - * annotation is not a supported qualifier, then that annotation is not add added. + * The return type of a method or constructor. For constructors, the return type is not VOID, + * but the type of the enclosing class. * - * @param annotations the annotations to add + * @return the return type of this executable type */ - public void addMissingAnnotations(Iterable annotations) { - for (AnnotationMirror a : annotations) { - addMissingAnnotation(a); + public AnnotatedTypeMirror getReturnType() { + if (!returnTypeComputed) { + assert returnType == null : "returnType = " + returnType; + if (element != null && ((ExecutableType) underlyingType).getReturnType() != null) { + TypeMirror aret = ((ExecutableType) underlyingType).getReturnType(); + if (aret.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed yet + // (see Issue #244). + throw new ErrorTypeKindException( + "Problem with return type of %s.%s: %s [%s %s]", + element, element.getEnclosingElement(), aret, aret.getKind(), aret.getClass()); + } + if (((MethodSymbol) element).isConstructor()) { + // For constructors, the underlying return type is void. + // Take the type of the enclosing class instead. + aret = element.getEnclosingElement().asType(); + if (aret.getKind() == TypeKind.ERROR) { + throw new ErrorTypeKindException( + "Input is not compilable; problem with constructor %s return type: %s [%s %s]" + + " (enclosing element = %s [%s])", + element, + aret, + aret.getKind(), + aret.getClass(), + element.getEnclosingElement(), + element.getEnclosingElement().getClass()); + } + } + returnType = createType(aret, atypeFactory, false); } + returnTypeComputed = true; + } + return returnType; } /** - * Add {@code annotation} if the type does not already have a primary annotation in the same - * hierarchy. - * - *

The canonical version of the {@code annotation} is added as a primary annotation of this - * type and (in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and - * {@link AnnotatedIntersectionType}s) added to all bounds. (The canonical version is found via - * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code - * annotation} is not a supported qualifier, then that annotation is not add added. + * Sets the receiver type on this executable type. * - * @param annotation the annotations to add + * @param receiverType the receiver type */ - public void addMissingAnnotation(AnnotationMirror annotation) { - if (!this.hasAnnotationInHierarchy(annotation)) { - this.addAnnotation(annotation); - } + /*package-private*/ void setReceiverType(@Nullable AnnotatedDeclaredType receiverType) { + this.receiverType = receiverType; + receiverTypeComputed = true; } /** - * Adds multiple annotations to this type, removing any existing primary annotations from the - * same qualifier hierarchy first. + * Returns the receiver type of this executable type; null for static methods and constructors + * of top-level classes. * - * @param replAnnos the annotations to replace + * @return the receiver type of this executable type; null for static methods and constructors + * of top-level classes */ - public void replaceAnnotations(Iterable replAnnos) { - for (AnnotationMirror a : replAnnos) { - this.replaceAnnotation(a); - } + public @Nullable AnnotatedDeclaredType getReceiverType() { + if (!receiverTypeComputed) { + assert receiverType == null; + Element element = getElement(); + if (ElementUtils.hasReceiver(element)) { + // Initial value of `encl`; might be updated. + TypeElement encl = ElementUtils.enclosingTypeElement(element); + if (element.getKind() == ElementKind.CONSTRUCTOR) { + // Can only reach this branch if we're the constructor of a nested class + encl = ElementUtils.enclosingTypeElement(encl.getEnclosingElement()); + } + TypeMirror enclType = encl.asType(); + if (enclType.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed yet + // (see Issue #244). + throw new ErrorTypeKindException( + "Problem with receiver type of %s.%s: %s [%s %s]", + element, + element.getEnclosingElement(), + enclType, + enclType.getKind(), + enclType.getClass()); + } + AnnotatedTypeMirror type = createType(enclType, atypeFactory, false); + assert type instanceof AnnotatedDeclaredType; + receiverType = (AnnotatedDeclaredType) type; + } + receiverTypeComputed = true; + } + return receiverType; } /** - * Removes a primary annotation from the type. + * Sets the thrown types of this executable type. * - * @param a the annotation to remove - * @return true if the annotation was removed, false if the type's annotations were unchanged + * @param thrownTypes an unmodifiable list of thrown types to be captured by this method */ - // typetools removePrimaryAnnotation - public boolean removeAnnotation(AnnotationMirror a) { - AnnotationMirror anno = AnnotationUtils.getSame(primaryAnnotations, a); - if (anno != null) { - return primaryAnnotations.remove(anno); - } - return false; + /*package-private*/ void setThrownTypes(List thrownTypes) { + this.thrownTypes = thrownTypes; + thrownTypesComputed = true; } /** - * Removes a primary annotation of the given class from the type. + * Returns the thrown types of this executable type. * - * @param a the class of the annotation to remove - * @return true if the annotation was removed, false if the type's annotations were unchanged - */ - // typetools: removePrimaryAnnotationByClass - public boolean removeAnnotationByClass(Class a) { - AnnotationMirror anno = atypeFactory.getAnnotationByClass(primaryAnnotations, a); - if (anno != null) { - return this.removeAnnotation(anno); + * @return the thrown types of this executable type + */ + public List getThrownTypes() { + if (!thrownTypesComputed) { + assert thrownTypes == null; + List underlyingThrownTypes = + ((ExecutableType) underlyingType).getThrownTypes(); + if (underlyingThrownTypes.isEmpty()) { + setThrownTypes(Collections.emptyList()); + } else { + List newThrownTypes = new ArrayList<>(underlyingThrownTypes.size()); + for (TypeMirror t : underlyingThrownTypes) { + if (t.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed + // yet (see Issue #244). + throw new ErrorTypeKindException( + "Problem with thrown type of %s.%s: %s [%s %s]", + element, element.getEnclosingElement(), t, t.getKind(), t.getClass()); + } + newThrownTypes.add(createType(t, atypeFactory, false)); + } + setThrownTypes(Collections.unmodifiableList(newThrownTypes)); } - return false; + } + // No need to copy or wrap; it is an unmodifiable list. + return thrownTypes; } /** - * Remove any primary annotation that is in the same qualifier hierarchy as the parameter. + * Sets the type variables associated with this executable type. * - * @param a an annotation from the same qualifier hierarchy - * @return if an annotation was removed - */ - // typetools removePrimaryAnnotationInHierarchy - public boolean removeAnnotationInHierarchy(AnnotationMirror a) { - AnnotationMirror prev = this.getAnnotationInHierarchy(a); - if (prev != null) { - return this.removeAnnotation(prev); - } - return false; + * @param types an unmodifiable list of type variables of this executable type to be captured by + * this method + */ + /*package-private*/ void setTypeVariables(List types) { + typeVarTypes = types; + typeVarTypesComputed = true; } /** - * Remove an annotation that is in the same qualifier hierarchy as the parameter, unless it's - * the top annotation. + * Returns the type variables of this executable type, if any. * - * @param a an annotation from the same qualifier hierarchy - * @return if an annotation was removed - * @deprecated This will be removed in a future release - */ - @Deprecated // 2023-06-15 - public boolean removeNonTopAnnotationInHierarchy(AnnotationMirror a) { - AnnotationMirror prev = this.getAnnotationInHierarchy(a); - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - if (prev != null && !prev.equals(qualHierarchy.getTopAnnotation(a))) { - return this.removeAnnotation(prev); + * @return the type variables of this executable type, if any + */ + public List getTypeVariables() { + if (!typeVarTypesComputed) { + assert typeVarTypes == null; + List underlyingTypeVariables = + ((ExecutableType) underlyingType).getTypeVariables(); + if (underlyingTypeVariables.isEmpty()) { + setTypeVariables(Collections.emptyList()); + } else { + List newTypeVarTypes = + new ArrayList<>(underlyingTypeVariables.size()); + for (TypeMirror t : underlyingTypeVariables) { + if (t.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed + // yet (see Issue #244). + throw new ErrorTypeKindException( + "Problem with type variables of %s.%s: %s [%s %s]", + element, element.getEnclosingElement(), t, t.getKind(), t.getClass()); + } + newTypeVarTypes.add((AnnotatedTypeVariable) createType(t, atypeFactory, true)); + } + setTypeVariables(Collections.unmodifiableList(newTypeVarTypes)); } - return false; + } + // No need to copy or wrap; it is an unmodifiable list. + return typeVarTypes; } - /** - * Removes multiple primary annotations from the type. - * - * @param annotations the annotations to remove - * @return true if at least one annotation was removed, false if the type's annotations were - * unchanged - */ - // typetools: removePrimaryAnnotations - public boolean removeAnnotations(Iterable annotations) { - boolean changed = false; - for (AnnotationMirror a : annotations) { - changed |= this.removeAnnotation(a); - } - return changed; + @Override + public AnnotatedExecutableType deepCopy(boolean copyAnnotations) { + return (AnnotatedExecutableType) new AnnotatedTypeCopier(copyAnnotations).visit(this); } - /** Removes all primary annotations on this type. */ - // typetools: clearPrimaryAnnotations - public void clearAnnotations() { - primaryAnnotations.clear(); + @Override + public AnnotatedExecutableType deepCopy() { + return deepCopy(true); } - @SideEffectFree @Override - public final String toString() { - return atypeFactory.getAnnotatedTypeFormatter().format(this); + public AnnotatedExecutableType shallowCopy(boolean copyAnnotations) { + AnnotatedExecutableType type = new AnnotatedExecutableType(getUnderlyingType(), atypeFactory); + + type.setElement(getElement()); + type.setParameterTypes(getParameterTypes()); + if (getVarargType() != null) { + type.setVarargType(getVarargType()); + } else { + type.computeVarargType(); + } + type.setReceiverType(getReceiverType()); + type.setReturnType(getReturnType()); + type.setThrownTypes(getThrownTypes()); + type.setTypeVariables(getTypeVariables()); + + return type; } - @SideEffectFree - public final String toString(boolean verbose) { - return atypeFactory.getAnnotatedTypeFormatter().format(this, verbose); + @Override + public AnnotatedExecutableType shallowCopy() { + return shallowCopy(true); } /** - * Returns the erasure type of this type, according to JLS specifications. + * Returns the element of this AnnotatedExecutableType. * - * @see https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.6 - * @return the erasure of this AnnotatedTypeMirror, this is always a copy even if the erasure - * and the original type are equivalent + * @return the element of this AnnotatedExecutableType */ - public AnnotatedTypeMirror getErased() { - return deepCopy(); + public ExecutableElement getElement() { + return element; } /** - * Returns a deep copy of this type. A deep copy implies that each component type is copied - * recursively and the returned type refers to those copies in its component locations. - * - *

Note: deepCopy provides two important properties in the returned copy: - * - *

    - *
  1. Structure preservation -- The exact structure of the original AnnotatedTypeMirror is - * preserved in the copy including all component types. - *
  2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror - * and its components have been copied to the new type. - *
- * - * If copyAnnotations is set to false, the second property, Annotation preservation, is removed. - * This is useful for cases in which the user may want to copy the structure of a type exactly - * but NOT its annotations. + * Sets the element of this AnnotatedExecutableType. * - * @return a deep copy + * @param elem the new element for this AnnotatedExecutableType */ - public abstract AnnotatedTypeMirror deepCopy(boolean copyAnnotations); + public void setElement(ExecutableElement elem) { + this.element = elem; + } - /** - * Returns a deep copy of this type with annotations. - * - *

Each subclass implements this method with the subclass return type. The method body must - * always be a call to deepCopy(true). - * - * @return a deep copy of this type with annotations - * @see #deepCopy(boolean) - */ @Override - public abstract AnnotatedTypeMirror deepCopy(); + public AnnotatedExecutableType getErased() { + AnnotatedExecutableType type = + new AnnotatedExecutableType( + (ExecutableType) atypeFactory.types.erasure(getUnderlyingType()), atypeFactory); + type.setElement(getElement()); + type.setParameterTypes(erasureList(getParameterTypes())); + if (getVarargType() != null) { + type.setVarargType(getVarargType().getErased()); + } else { + type.computeVarargType(); + } + if (getReceiverType() != null) { + type.setReceiverType(getReceiverType().getErased()); + } else { + type.setReceiverType(null); + } + type.setReturnType(getReturnType().getErased()); + type.setThrownTypes(erasureList(getThrownTypes())); + + return type; + } /** - * Returns a shallow copy of this type. A shallow copy implies that each component type in the - * output copy refers to the same object as the object being copied. + * Returns the erased types corresponding to the given types. * - * @param copyAnnotations whether copy should have annotations, i.e. whether field {@code - * annotations} should be copied. + * @param lst annotated type mirrors + * @return erased annotated type mirrors in an unmodifiable list */ - public abstract AnnotatedTypeMirror shallowCopy(boolean copyAnnotations); + private List erasureList(List lst) { + if (lst.isEmpty()) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableList( + CollectionsPlume.mapList(AnnotatedTypeMirror::getErased, lst)); + } + } + } - /** - * Returns a shallow copy of this type with annotations. - * - *

Each subclass implements this method with the subclass return type. The method body must - * always be a call to shallowCopy(true). - * - * @see #shallowCopy(boolean) - * @return a shallow copy of this type with annotations - */ - public abstract AnnotatedTypeMirror shallowCopy(); + /** + * Represents Array types in java. A multidimensional array type is represented as an array type + * whose component type is also an array type. + */ + public static class AnnotatedArrayType extends AnnotatedTypeMirror { - /** - * Returns whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType. - * - * @return whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient - */ - public boolean containsUninferredTypeArguments() { - return atypeFactory.containsUninferredTypeArguments(this); + private AnnotatedArrayType(ArrayType type, AnnotatedTypeFactory factory) { + super(type, factory); } - /** - * Create an {@link AnnotatedDeclaredType} with the underlying type of {@link Object}. It - * includes any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. - * - * @param atypeFactory type factory to use - * @return AnnotatedDeclaredType for Object - */ - protected static AnnotatedDeclaredType createTypeOfObject(AnnotatedTypeFactory atypeFactory) { - AnnotatedDeclaredType objectType = - atypeFactory.fromElement( - atypeFactory.elements.getTypeElement(Object.class.getCanonicalName())); - objectType.declaration = false; - return objectType; + /** The component type of this array type. */ + private AnnotatedTypeMirror componentType; + + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitArray(this, p); + } + + @Override + public ArrayType getUnderlyingType() { + return (ArrayType) this.underlyingType; } /** - * Create an {@link AnnotatedDeclaredType} with the underlying type of {@code java.lang.Record}. - * It includes any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. + * Sets the component type of this array. * - * @param atypeFactory type factory to use - * @return AnnotatedDeclaredType for Record + * @param type the component type */ - protected static AnnotatedDeclaredType createTypeOfRecord(AnnotatedTypeFactory atypeFactory) { - AnnotatedDeclaredType recordType = - atypeFactory.fromElement(atypeFactory.elements.getTypeElement("java.lang.Record")); - recordType.declaration = false; - return recordType; + public void setComponentType(AnnotatedTypeMirror type) { + this.componentType = type; } /** - * Returns the result of calling {@code underlyingType.toString().hashcode()}. This method saves - * the result in a field so that it isn't recomputed each time. + * Returns the component type of this array. * - * @return the result of calling {@code underlyingType.toString().hashcode()} + * @return the component type of this array */ - public int getUnderlyingTypeHashCode() { - if (underlyingTypeHashCode == -1) { - underlyingTypeHashCode = underlyingType.toString().hashCode(); - } - return underlyingTypeHashCode; - } - - /** Represents a declared type (whether class or interface). */ - public static class AnnotatedDeclaredType extends AnnotatedTypeMirror { - - /** Parametrized Type Arguments. */ - protected List typeArgs; - - /** - * Whether the type was initially raw, i.e. the user did not provide the type arguments. - * typeArgs will contain inferred type arguments, which might be too conservative at the - * moment. TODO: improve inference. - * - *

Ideally, the field would be final. However, when we determine the supertype of a raw - * type, we need to set isUnderlyingTypeRaw for the supertype. - */ - private boolean isUnderlyingTypeRaw; - - /** The enclosing type. May be null. May be changed. */ - protected @Nullable AnnotatedDeclaredType enclosingType; - - /** True if this represents a declaration, rather than a use, of a type. */ - private boolean declaration; - - /** - * Constructor for this type. The result contains no annotations. - * - * @param type underlying kind of this type - * @param atypeFactory the AnnotatedTypeFactory used to create this type - */ - private AnnotatedDeclaredType( - DeclaredType type, AnnotatedTypeFactory atypeFactory, boolean declaration) { - super(type, atypeFactory); - TypeElement typeelem = (TypeElement) type.asElement(); - DeclaredType declty = (DeclaredType) typeelem.asType(); - isUnderlyingTypeRaw = - !declty.getTypeArguments().isEmpty() && type.getTypeArguments().isEmpty(); - - TypeMirror encl = type.getEnclosingType(); - if (encl.getKind() == TypeKind.DECLARED) { - this.enclosingType = - (AnnotatedDeclaredType) createType(encl, atypeFactory, declaration); - } else if (encl.getKind() == TypeKind.NONE) { - this.enclosingType = null; - } else { - throw new BugInCF( - "AnnotatedDeclaredType: unsupported enclosing type: " - + type.getEnclosingType() - + " (" - + encl.getKind() - + ")"); - } - - this.declaration = declaration; - } + public AnnotatedTypeMirror getComponentType() { + if (componentType == null) { // lazy init + setComponentType( + createType(((ArrayType) underlyingType).getComponentType(), atypeFactory, false)); + } + return componentType; + } - @Override - public boolean isDeclaration() { - return declaration; - } + @Override + public AnnotatedArrayType deepCopy(boolean copyAnnotations) { + return (AnnotatedArrayType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - @Override - public AnnotatedDeclaredType deepCopy(boolean copyAnnotations) { - return (AnnotatedDeclaredType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + @Override + public AnnotatedArrayType deepCopy() { + return deepCopy(true); + } - @Override - public AnnotatedDeclaredType deepCopy() { - return deepCopy(true); - } + @Override + public AnnotatedArrayType shallowCopy(boolean copyAnnotations) { + AnnotatedArrayType type = new AnnotatedArrayType((ArrayType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.setComponentType(getComponentType()); + return type; + } - @Override - public AnnotatedDeclaredType asUse() { - if (!this.isDeclaration()) { - return this; - } - AnnotatedDeclaredType result = this.shallowCopy(true); - result.declaration = false; - if (this.enclosingType != null) { - result.enclosingType = this.enclosingType.asUse(); - } - // setTypeArguments calls asUse on all the new type arguments. - result.setTypeArguments(typeArgs); - - // If "this" is a type declaration with a type variable that references itself, e.g. - // MyClass>, then the type variable is a declaration, i.e. the first - // T, but the reference to the type variable is a use, i.e. the second T. When "this" - // is converted to a use, then both type variables are uses and should be the same - // object. - // The code below does this. - Map mapping = new HashMap<>(typeArgs.size()); - for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { - AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; - mapping.put(typeVar.getUnderlyingType(), typeVar); - } - for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { - AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; - AnnotatedTypeMirror upperBound = - atypeFactory - .getTypeVarSubstitutor() - .substituteWithoutCopyingTypeArguments( - mapping, typeVar.getUpperBound()); - typeVar.setUpperBound(upperBound); - } + @Override + public AnnotatedArrayType shallowCopy() { + return shallowCopy(true); + } - return result; - } + @Override + public AnnotatedArrayType getErased() { + // IMPORTANT NOTE: The returned type is a fresh Object because + // the componentType is the only component of arrays and the + // call to getErased will return a fresh object. + // | T[ ] | = |T| [ ] + AnnotatedArrayType at = shallowCopy(); + AnnotatedTypeMirror ct = at.getComponentType().getErased(); + at.setComponentType(ct); + return at; + } + } + + /** + * Throw an exception if the boundType is null or a declaration. + * + * @param boundDescription the variety of bound: "Lower", "Super", or "Extends" + * @param boundType the type being tested + * @param thisType the object for which boundType is a bound + */ + private static void checkBound( + String boundDescription, AnnotatedTypeMirror boundType, AnnotatedTypeMirror thisType) { + if (boundType == null || boundType.isDeclaration()) { + throw new BugInCF( + "%s bounds should never be null or a declaration.%n new bound = %s%n type =" + " %s", + boundDescription, boundType, thisType); + } + } + + /** + * Represents a type variable. A type variable may be explicitly declared by a type parameter of a + * type, method, or constructor. A type variable may also be declared implicitly, as by the + * capture conversion of a wildcard type argument (see chapter 5 of The Java Language + * Specification, Third Edition). + */ + public static class AnnotatedTypeVariable extends AnnotatedTypeMirror { + + private AnnotatedTypeVariable( + TypeVariable type, AnnotatedTypeFactory atypeFactory, boolean declaration) { + super(type, atypeFactory); + this.declaration = declaration; + } - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitDeclared(this, p); - } + /** The lower bound of the type variable. */ + private AnnotatedTypeMirror lowerBound; - /** - * Sets the type arguments on this type. - * - * @param ts a list of type arguments to be captured by this method - */ - public void setTypeArguments(List ts) { - if (ts == null || ts.isEmpty()) { - typeArgs = Collections.emptyList(); - } else if (isDeclaration()) { - for (AnnotatedTypeMirror typeArg : ts) { - if (typeArg.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF( - "Type declaration must have type variables as type arguments. Found %s", - typeArg); - } - if (!typeArg.isDeclaration()) { - throw new BugInCF( - "Type declarations must have type variables that are declarations. Found %s", - typeArg); - } - } - typeArgs = Collections.unmodifiableList(ts); - } else { - List uses = - CollectionsPlume.mapList(AnnotatedTypeMirror::asUse, ts); - typeArgs = Collections.unmodifiableList(uses); - } - } + /** The upper bound of the type variable. */ + private AnnotatedTypeMirror upperBound; - /** - * Returns the type argument for this type. - * - * @return the type argument for this type - */ - public List getTypeArguments() { - if (typeArgs != null) { - return typeArgs; - } else if (isUnderlyingTypeRaw()) { - // Initialize the type arguments with uninferred wildcards. - BoundsInitializer.initializeTypeArgs(this); - return typeArgs; - } else if (getUnderlyingType().getTypeArguments().isEmpty()) { - typeArgs = Collections.emptyList(); - return typeArgs; - } else { - // Initialize type argument for a non-raw declared type that has type arguments/ - BoundsInitializer.initializeTypeArgs(this); - return typeArgs; - } - } + private boolean declaration; - /** - * Returns true if the underlying type is raw. The receiver of this method is not raw, - * however; its annotated type arguments have been inferred. - * - * @return true iff the type was raw - */ - public boolean isUnderlyingTypeRaw() { - return isUnderlyingTypeRaw; - } - - /** - * Set the isUnderlyingTypeRaw flag to true. This should only be necessary when determining - * the supertypes of a raw type. - */ - protected void setIsUnderlyingTypeRaw() { - this.isUnderlyingTypeRaw = true; - } - - @Override - public DeclaredType getUnderlyingType() { - return (DeclaredType) underlyingType; - } - - @Override - public List directSupertypes() { - return Collections.unmodifiableList(SupertypeFinder.directSupertypes(this)); - } - - @Override - public AnnotatedDeclaredType shallowCopy() { - return shallowCopy(true); - } - - @Override - public AnnotatedDeclaredType shallowCopy(boolean copyAnnotations) { - AnnotatedDeclaredType type = - new AnnotatedDeclaredType(getUnderlyingType(), atypeFactory, declaration); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.setEnclosingType(getEnclosingType()); - type.setTypeArguments(getTypeArguments()); - return type; - } - - /** - * Return the declared type with its type arguments removed. This also replaces the - * underlying type with its erasure. - * - * @return a fresh copy of the declared type with no type arguments - */ - @Override - public AnnotatedDeclaredType getErased() { - AnnotatedDeclaredType erased = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType( - atypeFactory.types.erasure(underlyingType), - atypeFactory, - false); - erased.addAnnotations(this.getAnnotations()); - AnnotatedDeclaredType erasedEnclosing = erased.getEnclosingType(); - AnnotatedDeclaredType thisEnclosing = this.getEnclosingType(); - while (erasedEnclosing != null) { - erasedEnclosing.addAnnotations(thisEnclosing.getAnnotations()); - erasedEnclosing = erasedEnclosing.getEnclosingType(); - thisEnclosing = thisEnclosing.getEnclosingType(); - } - return erased; - } + @Override + public boolean isDeclaration() { + return declaration; + } - /** - * Sets the enclosing type. - * - * @param enclosingType the new enclosing type - */ - public void setEnclosingType(@Nullable AnnotatedDeclaredType enclosingType) { - this.enclosingType = enclosingType; - } + @Override + public void addAnnotation(AnnotationMirror annotation) { + super.addAnnotation(annotation); + fixupBoundAnnotations(); + } - /** - * Returns the enclosing type, as in the type of {@code A} in the type {@code A.B}. May - * return null. - * - * @return enclosingType the enclosing type, or null if this is a top-level type - */ - public @Nullable AnnotatedDeclaredType getEnclosingType() { - return enclosingType; - } + @Override + public boolean removeAnnotation(AnnotationMirror a) { + boolean ret = super.removeAnnotation(a); + if (lowerBound != null) { + ret |= lowerBound.removeAnnotation(a); + } + if (upperBound != null) { + ret |= upperBound.removeAnnotation(a); + } + return ret; } /** - * Represents a type of an executable. An executable is a method, constructor, or initializer. + * Change whether this {@code AnnotatedTypeVariable} is considered a use or a declaration (use + * this method with caution). + * + * @param declaration true if this type variable should be considered a declaration */ - public static class AnnotatedExecutableType extends AnnotatedTypeMirror { - - private @MonotonicNonNull ExecutableElement element; - - private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + public void setDeclaration(boolean declaration) { + this.declaration = declaration; + } - /** The parameter types; an unmodifiable list. */ - private @MonotonicNonNull List paramTypes = null; + @Override + public AnnotatedTypeVariable asUse() { + if (!this.isDeclaration()) { + return this; + } + + AnnotatedTypeVariable result = this.shallowCopy(); + result.declaration = false; + Map mapping = new HashMap<>(1); + mapping.put(getUnderlyingType(), result); + AnnotatedTypeMirror upperBound = + atypeFactory + .getTypeVarSubstitutor() + .substituteWithoutCopyingTypeArguments(mapping, result.getUpperBound()); + result.setUpperBound(upperBound); + + return result; + } - /** Whether {@link #paramTypes} has been computed. */ - private boolean paramTypesComputed = false; + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitTypeVariable(this, p); + } - /** - * The receiver type of this executable type; null for static methods and constructors of - * top-level classes. - */ - private @Nullable AnnotatedDeclaredType receiverType; + @Override + public TypeVariable getUnderlyingType() { + return (TypeVariable) this.underlyingType; + } - /** - * The varargs type is the last element of {@link #paramTypes} if the method or constructor - * accepts a variable number of arguments and the {@link #paramTypes} has not been expanded - * yet. This type needs to be stored in the field to avoid being affected by calling {@link - * AnnotatedTypes#adaptParameters(AnnotatedTypeFactory, - * AnnotatedTypeMirror.AnnotatedExecutableType, List, com.sun.source.tree.NewClassTree)}. - */ - private @MonotonicNonNull AnnotatedArrayType varargType = null; + /** + * Set the lower bound of this variable type. + * + *

Returns the lower bound of this type variable. While a type parameter cannot include an + * explicit lower bound declaration, capture conversion can produce a type variable with a + * non-trivial lower bound. Type variables otherwise have a lower bound of NullType. + * + * @param type the lower bound type + */ + /*package-private*/ void setLowerBound(AnnotatedTypeMirror type) { + checkBound("Lower", type, this); + this.lowerBound = type; + fixupBoundAnnotations(); + } - /** Whether {@link #receiverType} has been computed. */ - private boolean receiverTypeComputed = false; + /** + * Get the lower bound field directly, bypassing any lazy initialization. This method is + * necessary to prevent infinite recursions in initialization. In general, prefer getLowerBound. + * + * @return the lower bound field + */ + public AnnotatedTypeMirror getLowerBoundField() { + return lowerBound; + } - /** The return type. */ - private AnnotatedTypeMirror returnType; + /** + * Returns the lower bound type of this type variable. + * + * @return the lower bound type of this type variable + */ + public AnnotatedTypeMirror getLowerBound() { + if (lowerBound == null) { // lazy init + BoundsInitializer.initializeBounds(this); + fixupBoundAnnotations(); + } + return lowerBound; + } - /** Whether {@link #returnType} has been computed. */ - private boolean returnTypeComputed = false; + // If the lower bound was not present in underlyingType, then its annotation was defaulted + // from the AnnotatedTypeFactory. If the lower bound annotation is a supertype of the upper + // bound annotation, then the type is ill-formed. In that case, change the defaulted lower + // bound to be consistent with the explicitly-written upper bound. + // + // As a concrete example, if the default annotation is @Nullable, then the type "X extends + // @NonNull Y" should not be converted into "X extends @NonNull Y super @Nullable + // bottomtype" but be converted into "X extends @NonNull Y super @NonNull bottomtype". + // + // In addition, ensure consistency of annotations on type variables + // and the upper bound. Assume class C. + // The type of "@Nullable X" has to be "@Nullable X extends @Nullable Object", + // because otherwise the annotations are inconsistent. + private void fixupBoundAnnotations() { + if (!this.getAnnotationsField().isEmpty()) { + AnnotationMirrorSet newAnnos = this.getAnnotationsField(); + if (upperBound != null) { + upperBound.replaceAnnotations(newAnnos); + } + + // Note: + // if the lower bound is a type variable then when we place annotations on the + // primary annotation this will actually cause the type variable to be exact and + // propagate the primary annotation to the type variable because primary annotations + // overwrite the upper and lower bounds of type variables when + // getUpperBound/getLowerBound is called. + if (lowerBound != null) { + lowerBound.replaceAnnotations(newAnnos); + } + } + } - /** The thrown types; an unmodifiable list. */ - private List thrownTypes; + /** + * Set the upper bound of this variable type. + * + * @param type the upper bound type + */ + /*package-private*/ void setUpperBound(AnnotatedTypeMirror type) { + checkBound("Upper", type, this); + this.upperBound = type; + fixupBoundAnnotations(); + } - /** Whether {@link #thrownTypes} has been computed. */ - private boolean thrownTypesComputed = false; + /** + * Get the upper bound field directly, bypassing any lazy initialization. This method is + * necessary to prevent infinite recursions in initialization. In general, prefer getUpperBound. + * + * @return the upper bound field + */ + public AnnotatedTypeMirror getUpperBoundField() { + return upperBound; + } - /** The type variables; an unmodifiable list. */ - private List typeVarTypes; + /** + * Get the upper bound of the type variable, possibly lazily initializing it. Attention: If the + * upper bound is lazily initialized, it will not contain any annotations! Callers of the method + * have to make sure that an AnnotatedTypeFactory first processed the bound. + * + * @return the upper bound type of this type variable + */ + public AnnotatedTypeMirror getUpperBound() { + if (upperBound == null) { // lazy init + BoundsInitializer.initializeBounds(this); + fixupBoundAnnotations(); + } + return upperBound; + } - /** Whether {@link #typeVarTypes} has been computed. */ - private boolean typeVarTypesComputed = false; + public AnnotatedTypeParameterBounds getBounds() { + return new AnnotatedTypeParameterBounds(getUpperBound(), getLowerBound()); + } - /** - * Returns true if this type represents a varargs method. - * - * @return true if this type represents a varargs method - */ - public boolean isVarArgs() { - return this.element.isVarArgs(); - } + public AnnotatedTypeParameterBounds getBoundFields() { + return new AnnotatedTypeParameterBounds(getUpperBoundField(), getLowerBoundField()); + } - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitExecutable(this, p); - } + @Override + public AnnotatedTypeVariable deepCopy(boolean copyAnnotations) { + return (AnnotatedTypeVariable) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - @Override - public ExecutableType getUnderlyingType() { - return (ExecutableType) this.underlyingType; - } + @Override + public AnnotatedTypeVariable deepCopy() { + return deepCopy(true); + } - /** - * It never makes sense to add annotations to an executable type. Instead, they should be - * added to the appropriate component. - * - * @deprecated add to the appropriate component - */ - @Deprecated // not for removal - @Override - public void addAnnotation(AnnotationMirror annotation) { - assert false : "AnnotatedExecutableType.addAnnotation should never be called"; - } + @Override + public AnnotatedTypeVariable shallowCopy(boolean copyAnnotations) { + // Because type variables can refer to themselves, they can't be shallow copied, so + // return a deep copy instead. + AnnotatedTypeVariable type = deepCopy(true); + if (!copyAnnotations) { + type.getAnnotationsField().clear(); + } + return type; + } - /** - * Sets the parameter types of this executable type, excluding the receiver.If paramTypes - * has been computed and this type is a varargs method, computes and store {@link - * #varargType} before calling this method, @see {@link #varargType} - * - * @param params an unmodifiable list of parameter types to be captured by this method, - * excluding the receiver - */ - /*package-private*/ void setParameterTypes(List params) { - if (paramTypesComputed && isVarArgs() && varargType == null) { - throw new BugInCF("Set vararg type before resetting parameter types"); - } - paramTypes = params; - paramTypesComputed = true; - } + @Override + public AnnotatedTypeVariable shallowCopy() { + return shallowCopy(true); + } - /** - * Returns the parameter types of this executable type, excluding the receiver. - * - * @return the parameter types of this executable type, excluding the receiver - */ - public List getParameterTypes() { - if (!paramTypesComputed) { - assert paramTypes == null; - List underlyingParameterTypes = - ((ExecutableType) underlyingType).getParameterTypes(); - if (underlyingParameterTypes.isEmpty()) { - setParameterTypes(Collections.emptyList()); - } else { - List newParamTypes = - new ArrayList<>(underlyingParameterTypes.size()); - for (TypeMirror t : underlyingParameterTypes) { - if (t.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed - // yet (see Issue #244). - throw new ErrorTypeKindException( - "Problem with parameter type of %s.%s: %s [%s %s]", - element, - element.getEnclosingElement(), - t, - t.getKind(), - t.getClass()); - } - newParamTypes.add(createType(t, atypeFactory, false)); - } - setParameterTypes(Collections.unmodifiableList(newParamTypes)); - } - } - // No need to copy or wrap; it is an unmodifiable list. - return paramTypes; - } + /** + * This method will traverse the upper bound of this type variable calling getErased until it + * finds the concrete upper bound. e.g. + * + *

{@code  , T extends S, S extends List>}
+ * + * A call to getErased will return the type List + * + * @return the erasure of the upper bound of this type + *

IMPORTANT NOTE: getErased should always return a FRESH object. This will occur for + * type variables if all other getErased methods are implemented appropriately. Therefore, + * to avoid extra copy calls, this method will not call deepCopy on getUpperBound + */ + @Override + public AnnotatedTypeMirror getErased() { + // |T extends A&B| = |A| + return this.getUpperBound().getErased(); + } + } + + /** + * A pseudo-type used where no actual type is appropriate. The kinds of NoType are: + * + *

    + *
  • VOID -- corresponds to the keyword void. + *
  • PACKAGE -- the pseudo-type of a package element. + *
  • NONE -- used in other cases where no actual type is appropriate; for example, the + * superclass of java.lang.Object. + *
+ */ + public static class AnnotatedNoType extends AnnotatedTypeMirror { + + private AnnotatedNoType(NoType type, AnnotatedTypeFactory factory) { + super(type, factory); + } - /** - * Sets the vararg type of this executable type. - * - * @param varargType the vararg type of this executable type - */ - /*package-private*/ void setVarargType(@NonNull AnnotatedArrayType varargType) { - this.varargType = varargType; - } + // No need for methods + // Might like to override annotate(), include(), execlude() + // AS NoType does not accept any annotations - /** - * Computes the vararg type of this executable type and stores it in {@link #varargType}. - * - *

This method computes {@link #varargType} using the {@link #paramTypes} of this - * executable type. To use the {@link #paramTypes} from different executable type, use - * {@link #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. - */ - /*package-private*/ void computeVarargType() { - computeVarargType(paramTypes); - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitNoType(this, p); + } - /** - * Computes the vararg type using the passed executable type and stores it in this {@link - * #varargType}. - * - * @param annotatedExecutableType an AnnotatedExecutableType - */ - /*package-private*/ void computeVarargType( - AnnotatedExecutableType annotatedExecutableType) { - computeVarargType(annotatedExecutableType.getParameterTypes()); - } + @Override + public NoType getUnderlyingType() { + return (NoType) this.underlyingType; + } - /** - * Helper function for {@link #computeVarargType()} and {@link - * #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. - * - * @param paramTypes the parameter types to determine the vararg type - */ - private void computeVarargType(List paramTypes) { - if (!isVarArgs()) { - return; - } - varargType = (AnnotatedArrayType) paramTypes.get(paramTypes.size() - 1); - } + @Override + public AnnotatedNoType deepCopy(boolean copyAnnotations) { + return (AnnotatedNoType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - /** - * Returns the vararg type of this executable type. - * - * @return the vararg type of this executable type - */ - public @Nullable AnnotatedArrayType getVarargType() { - return varargType; - } + @Override + public AnnotatedNoType deepCopy() { + return deepCopy(true); + } - /** - * Sets the return type of this executable type. - * - * @param returnType the new return type - */ - /*package-private*/ void setReturnType(AnnotatedTypeMirror returnType) { - this.returnType = returnType; - returnTypeComputed = true; - } + @Override + public AnnotatedNoType shallowCopy(boolean copyAnnotations) { + AnnotatedNoType type = new AnnotatedNoType((NoType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + return type; + } - /** - * The return type of a method or constructor. For constructors, the return type is not - * VOID, but the type of the enclosing class. - * - * @return the return type of this executable type - */ - public AnnotatedTypeMirror getReturnType() { - if (!returnTypeComputed) { - assert returnType == null : "returnType = " + returnType; - if (element != null && ((ExecutableType) underlyingType).getReturnType() != null) { - TypeMirror aret = ((ExecutableType) underlyingType).getReturnType(); - if (aret.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed yet - // (see Issue #244). - throw new ErrorTypeKindException( - "Problem with return type of %s.%s: %s [%s %s]", - element, - element.getEnclosingElement(), - aret, - aret.getKind(), - aret.getClass()); - } - if (((MethodSymbol) element).isConstructor()) { - // For constructors, the underlying return type is void. - // Take the type of the enclosing class instead. - aret = element.getEnclosingElement().asType(); - if (aret.getKind() == TypeKind.ERROR) { - throw new ErrorTypeKindException( - "Input is not compilable; problem with constructor %s return type: %s [%s %s]" - + " (enclosing element = %s [%s])", - element, - aret, - aret.getKind(), - aret.getClass(), - element.getEnclosingElement(), - element.getEnclosingElement().getClass()); - } - } - returnType = createType(aret, atypeFactory, false); - } - returnTypeComputed = true; - } - return returnType; - } + @Override + public AnnotatedNoType shallowCopy() { + return shallowCopy(true); + } + } - /** - * Sets the receiver type on this executable type. - * - * @param receiverType the receiver type - */ - /*package-private*/ void setReceiverType(@Nullable AnnotatedDeclaredType receiverType) { - this.receiverType = receiverType; - receiverTypeComputed = true; - } + /** Represents the null type. This is the type of the expression {@code null}. */ + public static class AnnotatedNullType extends AnnotatedTypeMirror { - /** - * Returns the receiver type of this executable type; null for static methods and - * constructors of top-level classes. - * - * @return the receiver type of this executable type; null for static methods and - * constructors of top-level classes - */ - public @Nullable AnnotatedDeclaredType getReceiverType() { - if (!receiverTypeComputed) { - assert receiverType == null; - Element element = getElement(); - if (ElementUtils.hasReceiver(element)) { - // Initial value of `encl`; might be updated. - TypeElement encl = ElementUtils.enclosingTypeElement(element); - if (element.getKind() == ElementKind.CONSTRUCTOR) { - // Can only reach this branch if we're the constructor of a nested class - encl = ElementUtils.enclosingTypeElement(encl.getEnclosingElement()); - } - TypeMirror enclType = encl.asType(); - if (enclType.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed yet - // (see Issue #244). - throw new ErrorTypeKindException( - "Problem with receiver type of %s.%s: %s [%s %s]", - element, - element.getEnclosingElement(), - enclType, - enclType.getKind(), - enclType.getClass()); - } - AnnotatedTypeMirror type = createType(enclType, atypeFactory, false); - assert type instanceof AnnotatedDeclaredType; - receiverType = (AnnotatedDeclaredType) type; - } - receiverTypeComputed = true; - } - return receiverType; - } + private AnnotatedNullType(NullType type, AnnotatedTypeFactory factory) { + super(type, factory); + } - /** - * Sets the thrown types of this executable type. - * - * @param thrownTypes an unmodifiable list of thrown types to be captured by this method - */ - /*package-private*/ void setThrownTypes(List thrownTypes) { - this.thrownTypes = thrownTypes; - thrownTypesComputed = true; - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitNull(this, p); + } - /** - * Returns the thrown types of this executable type. - * - * @return the thrown types of this executable type - */ - public List getThrownTypes() { - if (!thrownTypesComputed) { - assert thrownTypes == null; - List underlyingThrownTypes = - ((ExecutableType) underlyingType).getThrownTypes(); - if (underlyingThrownTypes.isEmpty()) { - setThrownTypes(Collections.emptyList()); - } else { - List newThrownTypes = - new ArrayList<>(underlyingThrownTypes.size()); - for (TypeMirror t : underlyingThrownTypes) { - if (t.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed - // yet (see Issue #244). - throw new ErrorTypeKindException( - "Problem with thrown type of %s.%s: %s [%s %s]", - element, - element.getEnclosingElement(), - t, - t.getKind(), - t.getClass()); - } - newThrownTypes.add(createType(t, atypeFactory, false)); - } - setThrownTypes(Collections.unmodifiableList(newThrownTypes)); - } - } - // No need to copy or wrap; it is an unmodifiable list. - return thrownTypes; - } + @Override + public NullType getUnderlyingType() { + return (NullType) this.underlyingType; + } - /** - * Sets the type variables associated with this executable type. - * - * @param types an unmodifiable list of type variables of this executable type to be - * captured by this method - */ - /*package-private*/ void setTypeVariables(List types) { - typeVarTypes = types; - typeVarTypesComputed = true; - } + @Override + public AnnotatedNullType deepCopy(boolean copyAnnotations) { + return (AnnotatedNullType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - /** - * Returns the type variables of this executable type, if any. - * - * @return the type variables of this executable type, if any - */ - public List getTypeVariables() { - if (!typeVarTypesComputed) { - assert typeVarTypes == null; - List underlyingTypeVariables = - ((ExecutableType) underlyingType).getTypeVariables(); - if (underlyingTypeVariables.isEmpty()) { - setTypeVariables(Collections.emptyList()); - } else { - List newTypeVarTypes = - new ArrayList<>(underlyingTypeVariables.size()); - for (TypeMirror t : underlyingTypeVariables) { - if (t.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed - // yet (see Issue #244). - throw new ErrorTypeKindException( - "Problem with type variables of %s.%s: %s [%s %s]", - element, - element.getEnclosingElement(), - t, - t.getKind(), - t.getClass()); - } - newTypeVarTypes.add( - (AnnotatedTypeVariable) createType(t, atypeFactory, true)); - } - setTypeVariables(Collections.unmodifiableList(newTypeVarTypes)); - } - } - // No need to copy or wrap; it is an unmodifiable list. - return typeVarTypes; - } + @Override + public AnnotatedNullType deepCopy() { + return deepCopy(true); + } - @Override - public AnnotatedExecutableType deepCopy(boolean copyAnnotations) { - return (AnnotatedExecutableType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + @Override + public AnnotatedNullType shallowCopy(boolean copyAnnotations) { + AnnotatedNullType type = new AnnotatedNullType((NullType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + return type; + } - @Override - public AnnotatedExecutableType deepCopy() { - return deepCopy(true); - } + @Override + public AnnotatedNullType shallowCopy() { + return shallowCopy(true); + } + } - @Override - public AnnotatedExecutableType shallowCopy(boolean copyAnnotations) { - AnnotatedExecutableType type = - new AnnotatedExecutableType(getUnderlyingType(), atypeFactory); - - type.setElement(getElement()); - type.setParameterTypes(getParameterTypes()); - if (getVarargType() != null) { - type.setVarargType(getVarargType()); - } else { - type.computeVarargType(); - } - type.setReceiverType(getReceiverType()); - type.setReturnType(getReturnType()); - type.setThrownTypes(getThrownTypes()); - type.setTypeVariables(getTypeVariables()); + /** + * Represents a primitive type. These include {@code boolean}, {@code byte}, {@code short}, {@code + * int}, {@code long}, {@code char}, {@code float}, and {@code double}. + */ + public static class AnnotatedPrimitiveType extends AnnotatedTypeMirror { - return type; - } + private AnnotatedPrimitiveType(PrimitiveType type, AnnotatedTypeFactory factory) { + super(type, factory); + } - @Override - public AnnotatedExecutableType shallowCopy() { - return shallowCopy(true); - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitPrimitive(this, p); + } - /** - * Returns the element of this AnnotatedExecutableType. - * - * @return the element of this AnnotatedExecutableType - */ - public ExecutableElement getElement() { - return element; - } + @Override + public PrimitiveType getUnderlyingType() { + return (PrimitiveType) this.underlyingType; + } - /** - * Sets the element of this AnnotatedExecutableType. - * - * @param elem the new element for this AnnotatedExecutableType - */ - public void setElement(ExecutableElement elem) { - this.element = elem; - } + @Override + public AnnotatedPrimitiveType deepCopy(boolean copyAnnotations) { + return (AnnotatedPrimitiveType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - @Override - public AnnotatedExecutableType getErased() { - AnnotatedExecutableType type = - new AnnotatedExecutableType( - (ExecutableType) atypeFactory.types.erasure(getUnderlyingType()), - atypeFactory); - type.setElement(getElement()); - type.setParameterTypes(erasureList(getParameterTypes())); - if (getVarargType() != null) { - type.setVarargType(getVarargType().getErased()); - } else { - type.computeVarargType(); - } - if (getReceiverType() != null) { - type.setReceiverType(getReceiverType().getErased()); - } else { - type.setReceiverType(null); - } - type.setReturnType(getReturnType().getErased()); - type.setThrownTypes(erasureList(getThrownTypes())); + @Override + public AnnotatedPrimitiveType deepCopy() { + return deepCopy(true); + } - return type; - } + @Override + public AnnotatedPrimitiveType shallowCopy(boolean copyAnnotations) { + AnnotatedPrimitiveType type = + new AnnotatedPrimitiveType((PrimitiveType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + return type; + } - /** - * Returns the erased types corresponding to the given types. - * - * @param lst annotated type mirrors - * @return erased annotated type mirrors in an unmodifiable list - */ - private List erasureList(List lst) { - if (lst.isEmpty()) { - return Collections.emptyList(); - } else { - return Collections.unmodifiableList( - CollectionsPlume.mapList(AnnotatedTypeMirror::getErased, lst)); - } - } + @Override + public AnnotatedPrimitiveType shallowCopy() { + return shallowCopy(true); } + } + + /** + * Represents a wildcard type argument. Examples include: + * + *

? ? extends Number ? super T + * + *

A wildcard may have its upper bound explicitly set by an extends clause, its lower bound + * explicitly set by a super clause, or neither (but not both). + */ + public static class AnnotatedWildcardType extends AnnotatedTypeMirror { + /** Lower ({@code super}) bound. */ + private AnnotatedTypeMirror superBound; + + /** Upper ({@code extends} bound. */ + private AnnotatedTypeMirror extendsBound; /** - * Represents Array types in java. A multidimensional array type is represented as an array type - * whose component type is also an array type. + * The type variable to which this wildcard is an argument. Used to initialize the upper bound + * of unbounded wildcards and wildcards in raw types. */ - public static class AnnotatedArrayType extends AnnotatedTypeMirror { - - private AnnotatedArrayType(ArrayType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + @SuppressWarnings("nullness") // is reset during initialization + private @NonNull TypeVariable typeVariable = null; - /** The component type of this array type. */ - private AnnotatedTypeMirror componentType; - - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitArray(this, p); - } - - @Override - public ArrayType getUnderlyingType() { - return (ArrayType) this.underlyingType; - } - - /** - * Sets the component type of this array. - * - * @param type the component type - */ - public void setComponentType(AnnotatedTypeMirror type) { - this.componentType = type; - } - - /** - * Returns the component type of this array. - * - * @return the component type of this array - */ - public AnnotatedTypeMirror getComponentType() { - if (componentType == null) { // lazy init - setComponentType( - createType( - ((ArrayType) underlyingType).getComponentType(), - atypeFactory, - false)); - } - return componentType; - } - - @Override - public AnnotatedArrayType deepCopy(boolean copyAnnotations) { - return (AnnotatedArrayType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + private AnnotatedWildcardType(WildcardType type, AnnotatedTypeFactory factory) { + super(type, factory); + } - @Override - public AnnotatedArrayType deepCopy() { - return deepCopy(true); - } + @Override + public void addAnnotation(AnnotationMirror annotation) { + super.addAnnotation(annotation); + fixupBoundAnnotations(); + } - @Override - public AnnotatedArrayType shallowCopy(boolean copyAnnotations) { - AnnotatedArrayType type = - new AnnotatedArrayType((ArrayType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.setComponentType(getComponentType()); - return type; - } + @Override + public boolean removeAnnotation(AnnotationMirror a) { + boolean ret = super.removeAnnotation(a); + if (superBound != null) { + ret |= superBound.removeAnnotation(a); + } + if (extendsBound != null) { + ret |= extendsBound.removeAnnotation(a); + } + return ret; + } - @Override - public AnnotatedArrayType shallowCopy() { - return shallowCopy(true); - } + /** + * Sets the super bound of this wildcard. + * + * @param type the type of the lower bound + */ + /*package-private*/ void setSuperBound(AnnotatedTypeMirror type) { + checkBound("Super", type, this); + this.superBound = type; + fixupBoundAnnotations(); + } - @Override - public AnnotatedArrayType getErased() { - // IMPORTANT NOTE: The returned type is a fresh Object because - // the componentType is the only component of arrays and the - // call to getErased will return a fresh object. - // | T[ ] | = |T| [ ] - AnnotatedArrayType at = shallowCopy(); - AnnotatedTypeMirror ct = at.getComponentType().getErased(); - at.setComponentType(ct); - return at; - } + public AnnotatedTypeMirror getSuperBoundField() { + return superBound; } /** - * Throw an exception if the boundType is null or a declaration. + * Returns the lower bound of this wildcard. If no lower bound is explicitly declared, returns + * an {@link AnnotatedNullType}. * - * @param boundDescription the variety of bound: "Lower", "Super", or "Extends" - * @param boundType the type being tested - * @param thisType the object for which boundType is a bound + * @return the lower bound of this wildcard, or an {@link AnnotatedNullType} if none is + * explicitly declared */ - private static void checkBound( - String boundDescription, AnnotatedTypeMirror boundType, AnnotatedTypeMirror thisType) { - if (boundType == null || boundType.isDeclaration()) { - throw new BugInCF( - "%s bounds should never be null or a declaration.%n new bound = %s%n type =" - + " %s", - boundDescription, boundType, thisType); - } + public AnnotatedTypeMirror getSuperBound() { + if (superBound == null) { + BoundsInitializer.initializeSuperBound(this); + fixupBoundAnnotations(); + } + return this.superBound; } /** - * Represents a type variable. A type variable may be explicitly declared by a type parameter of - * a type, method, or constructor. A type variable may also be declared implicitly, as by the - * capture conversion of a wildcard type argument (see chapter 5 of The Java Language - * Specification, Third Edition). + * Sets the upper bound of this wildcard. + * + * @param type the type of the upper bound */ - public static class AnnotatedTypeVariable extends AnnotatedTypeMirror { - - private AnnotatedTypeVariable( - TypeVariable type, AnnotatedTypeFactory atypeFactory, boolean declaration) { - super(type, atypeFactory); - this.declaration = declaration; - } - - /** The lower bound of the type variable. */ - private AnnotatedTypeMirror lowerBound; - - /** The upper bound of the type variable. */ - private AnnotatedTypeMirror upperBound; - - private boolean declaration; - - @Override - public boolean isDeclaration() { - return declaration; - } - - @Override - public void addAnnotation(AnnotationMirror annotation) { - super.addAnnotation(annotation); - fixupBoundAnnotations(); - } - - @Override - public boolean removeAnnotation(AnnotationMirror a) { - boolean ret = super.removeAnnotation(a); - if (lowerBound != null) { - ret |= lowerBound.removeAnnotation(a); - } - if (upperBound != null) { - ret |= upperBound.removeAnnotation(a); - } - return ret; - } - - /** - * Change whether this {@code AnnotatedTypeVariable} is considered a use or a declaration - * (use this method with caution). - * - * @param declaration true if this type variable should be considered a declaration - */ - public void setDeclaration(boolean declaration) { - this.declaration = declaration; - } - - @Override - public AnnotatedTypeVariable asUse() { - if (!this.isDeclaration()) { - return this; - } - - AnnotatedTypeVariable result = this.shallowCopy(); - result.declaration = false; - Map mapping = new HashMap<>(1); - mapping.put(getUnderlyingType(), result); - AnnotatedTypeMirror upperBound = - atypeFactory - .getTypeVarSubstitutor() - .substituteWithoutCopyingTypeArguments(mapping, result.getUpperBound()); - result.setUpperBound(upperBound); - - return result; - } - - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitTypeVariable(this, p); - } - - @Override - public TypeVariable getUnderlyingType() { - return (TypeVariable) this.underlyingType; - } - - /** - * Set the lower bound of this variable type. - * - *

Returns the lower bound of this type variable. While a type parameter cannot include - * an explicit lower bound declaration, capture conversion can produce a type variable with - * a non-trivial lower bound. Type variables otherwise have a lower bound of NullType. - * - * @param type the lower bound type - */ - /*package-private*/ void setLowerBound(AnnotatedTypeMirror type) { - checkBound("Lower", type, this); - this.lowerBound = type; - fixupBoundAnnotations(); - } - - /** - * Get the lower bound field directly, bypassing any lazy initialization. This method is - * necessary to prevent infinite recursions in initialization. In general, prefer - * getLowerBound. - * - * @return the lower bound field - */ - public AnnotatedTypeMirror getLowerBoundField() { - return lowerBound; - } - - /** - * Returns the lower bound type of this type variable. - * - * @return the lower bound type of this type variable - */ - public AnnotatedTypeMirror getLowerBound() { - if (lowerBound == null) { // lazy init - BoundsInitializer.initializeBounds(this); - fixupBoundAnnotations(); - } - return lowerBound; - } - - // If the lower bound was not present in underlyingType, then its annotation was defaulted - // from the AnnotatedTypeFactory. If the lower bound annotation is a supertype of the upper - // bound annotation, then the type is ill-formed. In that case, change the defaulted lower - // bound to be consistent with the explicitly-written upper bound. - // - // As a concrete example, if the default annotation is @Nullable, then the type "X extends - // @NonNull Y" should not be converted into "X extends @NonNull Y super @Nullable - // bottomtype" but be converted into "X extends @NonNull Y super @NonNull bottomtype". - // - // In addition, ensure consistency of annotations on type variables - // and the upper bound. Assume class C. - // The type of "@Nullable X" has to be "@Nullable X extends @Nullable Object", - // because otherwise the annotations are inconsistent. - private void fixupBoundAnnotations() { - if (!this.getAnnotationsField().isEmpty()) { - AnnotationMirrorSet newAnnos = this.getAnnotationsField(); - if (upperBound != null) { - upperBound.replaceAnnotations(newAnnos); - } - - // Note: - // if the lower bound is a type variable then when we place annotations on the - // primary annotation this will actually cause the type variable to be exact and - // propagate the primary annotation to the type variable because primary annotations - // overwrite the upper and lower bounds of type variables when - // getUpperBound/getLowerBound is called. - if (lowerBound != null) { - lowerBound.replaceAnnotations(newAnnos); - } - } - } - - /** - * Set the upper bound of this variable type. - * - * @param type the upper bound type - */ - /*package-private*/ void setUpperBound(AnnotatedTypeMirror type) { - checkBound("Upper", type, this); - this.upperBound = type; - fixupBoundAnnotations(); - } - - /** - * Get the upper bound field directly, bypassing any lazy initialization. This method is - * necessary to prevent infinite recursions in initialization. In general, prefer - * getUpperBound. - * - * @return the upper bound field - */ - public AnnotatedTypeMirror getUpperBoundField() { - return upperBound; - } - - /** - * Get the upper bound of the type variable, possibly lazily initializing it. Attention: If - * the upper bound is lazily initialized, it will not contain any annotations! Callers of - * the method have to make sure that an AnnotatedTypeFactory first processed the bound. - * - * @return the upper bound type of this type variable - */ - public AnnotatedTypeMirror getUpperBound() { - if (upperBound == null) { // lazy init - BoundsInitializer.initializeBounds(this); - fixupBoundAnnotations(); - } - return upperBound; - } - - public AnnotatedTypeParameterBounds getBounds() { - return new AnnotatedTypeParameterBounds(getUpperBound(), getLowerBound()); - } - - public AnnotatedTypeParameterBounds getBoundFields() { - return new AnnotatedTypeParameterBounds(getUpperBoundField(), getLowerBoundField()); - } - - @Override - public AnnotatedTypeVariable deepCopy(boolean copyAnnotations) { - return (AnnotatedTypeVariable) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } - - @Override - public AnnotatedTypeVariable deepCopy() { - return deepCopy(true); - } - - @Override - public AnnotatedTypeVariable shallowCopy(boolean copyAnnotations) { - // Because type variables can refer to themselves, they can't be shallow copied, so - // return a deep copy instead. - AnnotatedTypeVariable type = deepCopy(true); - if (!copyAnnotations) { - type.getAnnotationsField().clear(); - } - return type; - } - - @Override - public AnnotatedTypeVariable shallowCopy() { - return shallowCopy(true); - } + /*package-private*/ void setExtendsBound(AnnotatedTypeMirror type) { + checkBound("Extends", type, this); + this.extendsBound = type; + fixupBoundAnnotations(); + } - /** - * This method will traverse the upper bound of this type variable calling getErased until - * it finds the concrete upper bound. e.g. - * - *

{@code  , T extends S, S extends List>}
- * - * A call to getErased will return the type List - * - * @return the erasure of the upper bound of this type - *

IMPORTANT NOTE: getErased should always return a FRESH object. This will occur for - * type variables if all other getErased methods are implemented appropriately. - * Therefore, to avoid extra copy calls, this method will not call deepCopy on - * getUpperBound - */ - @Override - public AnnotatedTypeMirror getErased() { - // |T extends A&B| = |A| - return this.getUpperBound().getErased(); - } + public AnnotatedTypeMirror getExtendsBoundField() { + return extendsBound; } /** - * A pseudo-type used where no actual type is appropriate. The kinds of NoType are: + * Returns the upper bound of this wildcard. If no upper bound is explicitly declared, returns + * the upper bound of the type variable to which the wildcard is bound. * - *

    - *
  • VOID -- corresponds to the keyword void. - *
  • PACKAGE -- the pseudo-type of a package element. - *
  • NONE -- used in other cases where no actual type is appropriate; for example, the - * superclass of java.lang.Object. - *
+ * @return the upper bound of this wildcard. If no upper bound is explicitly declared, returns + * the upper bound of the type variable to which the wildcard is bound. */ - public static class AnnotatedNoType extends AnnotatedTypeMirror { - - private AnnotatedNoType(NoType type, AnnotatedTypeFactory factory) { - super(type, factory); - } - - // No need for methods - // Might like to override annotate(), include(), execlude() - // AS NoType does not accept any annotations - - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitNoType(this, p); - } - - @Override - public NoType getUnderlyingType() { - return (NoType) this.underlyingType; - } - - @Override - public AnnotatedNoType deepCopy(boolean copyAnnotations) { - return (AnnotatedNoType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + public AnnotatedTypeMirror getExtendsBound() { + if (extendsBound == null) { + BoundsInitializer.initializeExtendsBound(this); + fixupBoundAnnotations(); + } + return this.extendsBound; + } - @Override - public AnnotatedNoType deepCopy() { - return deepCopy(true); + private void fixupBoundAnnotations() { + if (!this.getAnnotationsField().isEmpty()) { + if (superBound != null) { + superBound.replaceAnnotations(this.getAnnotationsField()); } - - @Override - public AnnotatedNoType shallowCopy(boolean copyAnnotations) { - AnnotatedNoType type = new AnnotatedNoType((NoType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - return type; + if (extendsBound != null) { + extendsBound.replaceAnnotations(this.getAnnotationsField()); } + } + } - @Override - public AnnotatedNoType shallowCopy() { - return shallowCopy(true); - } + /** + * Sets type variable to which this wildcard is an argument. This method should only be called + * during initialization of the type. + * + * @param typeParameterElement the type variable to which this wildcard is an argument + */ + /*package-private*/ void setTypeVariable(TypeParameterElement typeParameterElement) { + this.typeVariable = (TypeVariable) typeParameterElement.asType(); } - /** Represents the null type. This is the type of the expression {@code null}. */ - public static class AnnotatedNullType extends AnnotatedTypeMirror { + /** + * Sets type variable to which this wildcard is an argument. This method should only be called + * during initialization of the type. + * + * @param typeVariable the type variable to which this wildcard is an argument + */ + /*package-private*/ void setTypeVariable(TypeVariable typeVariable) { + this.typeVariable = typeVariable; + } - private AnnotatedNullType(NullType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + /** + * Returns the type variable to which this wildcard is an argument. Used to initialize the upper + * bound of wildcards in raw types. + * + * @return the type variable to which this wildcard is an argument + */ + public TypeVariable getTypeVariable() { + return typeVariable; + } - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitNull(this, p); - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitWildcard(this, p); + } - @Override - public NullType getUnderlyingType() { - return (NullType) this.underlyingType; - } + @Override + public WildcardType getUnderlyingType() { + return (WildcardType) this.underlyingType; + } - @Override - public AnnotatedNullType deepCopy(boolean copyAnnotations) { - return (AnnotatedNullType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + @Override + public AnnotatedWildcardType deepCopy(boolean copyAnnotations) { + return (AnnotatedWildcardType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - @Override - public AnnotatedNullType deepCopy() { - return deepCopy(true); - } + @Override + public AnnotatedWildcardType deepCopy() { + return deepCopy(true); + } - @Override - public AnnotatedNullType shallowCopy(boolean copyAnnotations) { - AnnotatedNullType type = new AnnotatedNullType((NullType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - return type; - } + @Override + public AnnotatedWildcardType shallowCopy(boolean copyAnnotations) { + // Because wildcards can refer to themselves, they can't be shallow copied, so return a + // deep copy instead. + AnnotatedWildcardType type = deepCopy(true); + if (!copyAnnotations) { + type.getAnnotationsField().clear(); + } + return type; + } - @Override - public AnnotatedNullType shallowCopy() { - return shallowCopy(true); - } + @Override + public AnnotatedWildcardType shallowCopy() { + return shallowCopy(true); } /** - * Represents a primitive type. These include {@code boolean}, {@code byte}, {@code short}, - * {@code int}, {@code long}, {@code char}, {@code float}, and {@code double}. + * @see + * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable#getErased() */ - public static class AnnotatedPrimitiveType extends AnnotatedTypeMirror { + @Override + public AnnotatedTypeMirror getErased() { + // |? extends A&B| = |A| + return getExtendsBound().getErased(); + } - private AnnotatedPrimitiveType(PrimitiveType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + // Remove the uninferredTypeArgument once method type + // argument inference and raw type handling is improved. + private boolean uninferredTypeArgument = false; - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitPrimitive(this, p); - } + /** + * Set that this wildcard is from an uninferred type argument. This method should only be used + * within the framework. Once issues that depend on this hack, in particular Issue 979, are + * fixed, this must be removed. + */ + public void setUninferredTypeArgument() { + uninferredTypeArgument = true; + } - @Override - public PrimitiveType getUnderlyingType() { - return (PrimitiveType) this.underlyingType; - } + /** + * Returns whether or not this wildcard is a type argument for which inference failed to infer a + * type. + * + * @return true if this wildcard is a type argument for which inference failed + */ + public boolean isUninferredTypeArgument() { + return uninferredTypeArgument; + } + } - @Override - public AnnotatedPrimitiveType deepCopy(boolean copyAnnotations) { - return (AnnotatedPrimitiveType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + /** + * Represents an intersection type. + * + *

For example: {@code MyObject & Serializable & Comparable} + */ + public static class AnnotatedIntersectionType extends AnnotatedTypeMirror { - @Override - public AnnotatedPrimitiveType deepCopy() { - return deepCopy(true); - } - - @Override - public AnnotatedPrimitiveType shallowCopy(boolean copyAnnotations) { - AnnotatedPrimitiveType type = - new AnnotatedPrimitiveType((PrimitiveType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - return type; - } + /** + * A list of the bounds of this which are also its direct super types. + * + *

Is set by {@link #shallowCopy}. + */ + protected List bounds; - @Override - public AnnotatedPrimitiveType shallowCopy() { - return shallowCopy(true); - } + /** + * Creates an {@code AnnotatedIntersectionType} with the underlying type {@code type}. The + * result contains no annotations. + * + * @param type underlying kind of this type + * @param atypeFactory the factory used to construct this intersection type + */ + private AnnotatedIntersectionType(IntersectionType type, AnnotatedTypeFactory atypeFactory) { + super(type, atypeFactory); } /** - * Represents a wildcard type argument. Examples include: + * {@inheritDoc} * - *

? ? extends Number ? super T + *

Also, copies {@code a} to all the bounds. * - *

A wildcard may have its upper bound explicitly set by an extends clause, its lower bound - * explicitly set by a super clause, or neither (but not both). + * @param annotation the annotation to add */ - public static class AnnotatedWildcardType extends AnnotatedTypeMirror { - /** Lower ({@code super}) bound. */ - private AnnotatedTypeMirror superBound; - - /** Upper ({@code extends} bound. */ - private AnnotatedTypeMirror extendsBound; - - /** - * The type variable to which this wildcard is an argument. Used to initialize the upper - * bound of unbounded wildcards and wildcards in raw types. - */ - @SuppressWarnings("nullness") // is reset during initialization - private @NonNull TypeVariable typeVariable = null; - - private AnnotatedWildcardType(WildcardType type, AnnotatedTypeFactory factory) { - super(type, factory); - } - - @Override - public void addAnnotation(AnnotationMirror annotation) { - super.addAnnotation(annotation); - fixupBoundAnnotations(); - } - - @Override - public boolean removeAnnotation(AnnotationMirror a) { - boolean ret = super.removeAnnotation(a); - if (superBound != null) { - ret |= superBound.removeAnnotation(a); - } - if (extendsBound != null) { - ret |= extendsBound.removeAnnotation(a); - } - return ret; - } - - /** - * Sets the super bound of this wildcard. - * - * @param type the type of the lower bound - */ - /*package-private*/ void setSuperBound(AnnotatedTypeMirror type) { - checkBound("Super", type, this); - this.superBound = type; - fixupBoundAnnotations(); - } - - public AnnotatedTypeMirror getSuperBoundField() { - return superBound; - } - - /** - * Returns the lower bound of this wildcard. If no lower bound is explicitly declared, - * returns an {@link AnnotatedNullType}. - * - * @return the lower bound of this wildcard, or an {@link AnnotatedNullType} if none is - * explicitly declared - */ - public AnnotatedTypeMirror getSuperBound() { - if (superBound == null) { - BoundsInitializer.initializeSuperBound(this); - fixupBoundAnnotations(); - } - return this.superBound; - } - - /** - * Sets the upper bound of this wildcard. - * - * @param type the type of the upper bound - */ - /*package-private*/ void setExtendsBound(AnnotatedTypeMirror type) { - checkBound("Extends", type, this); - this.extendsBound = type; - fixupBoundAnnotations(); - } - - public AnnotatedTypeMirror getExtendsBoundField() { - return extendsBound; - } - - /** - * Returns the upper bound of this wildcard. If no upper bound is explicitly declared, - * returns the upper bound of the type variable to which the wildcard is bound. - * - * @return the upper bound of this wildcard. If no upper bound is explicitly declared, - * returns the upper bound of the type variable to which the wildcard is bound. - */ - public AnnotatedTypeMirror getExtendsBound() { - if (extendsBound == null) { - BoundsInitializer.initializeExtendsBound(this); - fixupBoundAnnotations(); - } - return this.extendsBound; - } - - private void fixupBoundAnnotations() { - if (!this.getAnnotationsField().isEmpty()) { - if (superBound != null) { - superBound.replaceAnnotations(this.getAnnotationsField()); - } - if (extendsBound != null) { - extendsBound.replaceAnnotations(this.getAnnotationsField()); - } - } - } - - /** - * Sets type variable to which this wildcard is an argument. This method should only be - * called during initialization of the type. - * - * @param typeParameterElement the type variable to which this wildcard is an argument - */ - /*package-private*/ void setTypeVariable(TypeParameterElement typeParameterElement) { - this.typeVariable = (TypeVariable) typeParameterElement.asType(); - } - - /** - * Sets type variable to which this wildcard is an argument. This method should only be - * called during initialization of the type. - * - * @param typeVariable the type variable to which this wildcard is an argument - */ - /*package-private*/ void setTypeVariable(TypeVariable typeVariable) { - this.typeVariable = typeVariable; - } - - /** - * Returns the type variable to which this wildcard is an argument. Used to initialize the - * upper bound of wildcards in raw types. - * - * @return the type variable to which this wildcard is an argument - */ - public TypeVariable getTypeVariable() { - return typeVariable; - } - - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitWildcard(this, p); - } + @Override + public void addAnnotation(AnnotationMirror annotation) { + super.addAnnotation(annotation); + fixupBoundAnnotations(); + } - @Override - public WildcardType getUnderlyingType() { - return (WildcardType) this.underlyingType; + @Override + public boolean removeAnnotation(AnnotationMirror a) { + boolean ret = super.removeAnnotation(a); + if (bounds != null) { + for (AnnotatedTypeMirror bound : bounds) { + ret |= bound.removeAnnotation(a); } + } + return ret; + } - @Override - public AnnotatedWildcardType deepCopy(boolean copyAnnotations) { - return (AnnotatedWildcardType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + /** + * Copies {@link #primaryAnnotations} to all the bounds, replacing any existing annotations in + * the same hierarchy. + */ + private void fixupBoundAnnotations() { + if (!this.getAnnotationsField().isEmpty()) { + AnnotationMirrorSet newAnnos = this.getAnnotationsField(); + if (bounds != null) { + for (AnnotatedTypeMirror bound : bounds) { + bound.replaceAnnotations(newAnnos); + } + } + } + } - @Override - public AnnotatedWildcardType deepCopy() { - return deepCopy(true); - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitIntersection(this, p); + } - @Override - public AnnotatedWildcardType shallowCopy(boolean copyAnnotations) { - // Because wildcards can refer to themselves, they can't be shallow copied, so return a - // deep copy instead. - AnnotatedWildcardType type = deepCopy(true); - if (!copyAnnotations) { - type.getAnnotationsField().clear(); - } - return type; - } + @Override + public IntersectionType getUnderlyingType() { + return (IntersectionType) super.getUnderlyingType(); + } - @Override - public AnnotatedWildcardType shallowCopy() { - return shallowCopy(true); - } + @Override + public AnnotatedIntersectionType deepCopy(boolean copyAnnotations) { + return (AnnotatedIntersectionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - /** - * @see - * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable#getErased() - */ - @Override - public AnnotatedTypeMirror getErased() { - // |? extends A&B| = |A| - return getExtendsBound().getErased(); - } + @Override + public AnnotatedIntersectionType deepCopy() { + return deepCopy(true); + } - // Remove the uninferredTypeArgument once method type - // argument inference and raw type handling is improved. - private boolean uninferredTypeArgument = false; - - /** - * Set that this wildcard is from an uninferred type argument. This method should only be - * used within the framework. Once issues that depend on this hack, in particular Issue 979, - * are fixed, this must be removed. - */ - public void setUninferredTypeArgument() { - uninferredTypeArgument = true; - } + @Override + public AnnotatedIntersectionType shallowCopy(boolean copyAnnotations) { + AnnotatedIntersectionType type = + new AnnotatedIntersectionType((IntersectionType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.bounds = this.bounds; + return type; + } - /** - * Returns whether or not this wildcard is a type argument for which inference failed to - * infer a type. - * - * @return true if this wildcard is a type argument for which inference failed - */ - public boolean isUninferredTypeArgument() { - return uninferredTypeArgument; - } + @Override + public AnnotatedIntersectionType shallowCopy() { + return shallowCopy(true); } /** - * Represents an intersection type. + * {@inheritDoc} * - *

For example: {@code MyObject & Serializable & Comparable} - */ - public static class AnnotatedIntersectionType extends AnnotatedTypeMirror { - - /** - * A list of the bounds of this which are also its direct super types. - * - *

Is set by {@link #shallowCopy}. - */ - protected List bounds; - - /** - * Creates an {@code AnnotatedIntersectionType} with the underlying type {@code type}. The - * result contains no annotations. - * - * @param type underlying kind of this type - * @param atypeFactory the factory used to construct this intersection type - */ - private AnnotatedIntersectionType( - IntersectionType type, AnnotatedTypeFactory atypeFactory) { - super(type, atypeFactory); - } - - /** - * {@inheritDoc} - * - *

Also, copies {@code a} to all the bounds. - * - * @param annotation the annotation to add - */ - @Override - public void addAnnotation(AnnotationMirror annotation) { - super.addAnnotation(annotation); - fixupBoundAnnotations(); - } - - @Override - public boolean removeAnnotation(AnnotationMirror a) { - boolean ret = super.removeAnnotation(a); - if (bounds != null) { - for (AnnotatedTypeMirror bound : bounds) { - ret |= bound.removeAnnotation(a); - } - } - return ret; - } - - /** - * Copies {@link #primaryAnnotations} to all the bounds, replacing any existing annotations - * in the same hierarchy. - */ - private void fixupBoundAnnotations() { - if (!this.getAnnotationsField().isEmpty()) { - AnnotationMirrorSet newAnnos = this.getAnnotationsField(); - if (bounds != null) { - for (AnnotatedTypeMirror bound : bounds) { - bound.replaceAnnotations(newAnnos); - } - } - } - } - - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitIntersection(this, p); - } - - @Override - public IntersectionType getUnderlyingType() { - return (IntersectionType) super.getUnderlyingType(); - } - - @Override - public AnnotatedIntersectionType deepCopy(boolean copyAnnotations) { - return (AnnotatedIntersectionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } - - @Override - public AnnotatedIntersectionType deepCopy() { - return deepCopy(true); - } - - @Override - public AnnotatedIntersectionType shallowCopy(boolean copyAnnotations) { - AnnotatedIntersectionType type = - new AnnotatedIntersectionType((IntersectionType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.bounds = this.bounds; - return type; - } - - @Override - public AnnotatedIntersectionType shallowCopy() { - return shallowCopy(true); - } - - /** - * {@inheritDoc} - * - *

This returns the same types as {@link #getBounds()}. - * - * @return the direct super types of this - */ - @Override - public List directSupertypes() { - return getBounds(); - } - - /** - * This returns the bounds of the intersection type. Although only declared types can appear - * in an explicitly written intersections, during capture conversion, intersections with - * other kinds of types are created. - * - *

This returns the same types as {@link #directSupertypes()}. - * - * @return the bounds of this, which are also the direct super types of this - */ - public List getBounds() { - if (bounds == null) { - List ubounds = - ((IntersectionType) underlyingType).getBounds(); - List res = - CollectionsPlume.mapList( - (TypeMirror bnd) -> createType(bnd, atypeFactory, false), ubounds); - bounds = Collections.unmodifiableList(res); - fixupBoundAnnotations(); - } - return bounds; - } + *

This returns the same types as {@link #getBounds()}. + * + * @return the direct super types of this + */ + @Override + public List directSupertypes() { + return getBounds(); + } - /** - * Sets the bounds. - * - * @param bounds a list of bounds to be captured by this method - */ - public void setBounds(List bounds) { - this.bounds = bounds; - } + /** + * This returns the bounds of the intersection type. Although only declared types can appear in + * an explicitly written intersections, during capture conversion, intersections with other + * kinds of types are created. + * + *

This returns the same types as {@link #directSupertypes()}. + * + * @return the bounds of this, which are also the direct super types of this + */ + public List getBounds() { + if (bounds == null) { + List ubounds = ((IntersectionType) underlyingType).getBounds(); + List res = + CollectionsPlume.mapList( + (TypeMirror bnd) -> createType(bnd, atypeFactory, false), ubounds); + bounds = Collections.unmodifiableList(res); + fixupBoundAnnotations(); + } + return bounds; + } - /** - * Copy the first annotation (in each hierarchy) on a bound to the primary annotation - * location of the intersection type. - * - *

For example, in the type {@code @NonNull Object & @Initialized @Nullable - * Serializable}, {@code @Nullable} and {@code @Initialized} are copied to the primary - * annotation location. - */ - public void copyIntersectionBoundAnnotations() { - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - for (AnnotatedTypeMirror bound : getBounds()) { - for (AnnotationMirror a : bound.getAnnotations()) { - if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, a) - == null) { - annos.add(a); - } - } - } - addAnnotations(annos); - } + /** + * Sets the bounds. + * + * @param bounds a list of bounds to be captured by this method + */ + public void setBounds(List bounds) { + this.bounds = bounds; } - // TODO: Ensure union types are handled everywhere. - // TODO: Should field "annotations" contain anything? - public static class AnnotatedUnionType extends AnnotatedTypeMirror { + /** + * Copy the first annotation (in each hierarchy) on a bound to the primary annotation location + * of the intersection type. + * + *

For example, in the type {@code @NonNull Object & @Initialized @Nullable Serializable}, + * {@code @Nullable} and {@code @Initialized} are copied to the primary annotation location. + */ + public void copyIntersectionBoundAnnotations() { + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + for (AnnotatedTypeMirror bound : getBounds()) { + for (AnnotationMirror a : bound.getAnnotations()) { + if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, a) + == null) { + annos.add(a); + } + } + } + addAnnotations(annos); + } + } - /** - * Creates a new AnnotatedUnionType. - * - * @param type underlying kind of this type - * @param atypeFactory type factory - */ - private AnnotatedUnionType(UnionType type, AnnotatedTypeFactory atypeFactory) { - super(type, atypeFactory); - } + // TODO: Ensure union types are handled everywhere. + // TODO: Should field "annotations" contain anything? + public static class AnnotatedUnionType extends AnnotatedTypeMirror { - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitUnion(this, p); - } + /** + * Creates a new AnnotatedUnionType. + * + * @param type underlying kind of this type + * @param atypeFactory type factory + */ + private AnnotatedUnionType(UnionType type, AnnotatedTypeFactory atypeFactory) { + super(type, atypeFactory); + } - @Override - public AnnotatedUnionType deepCopy(boolean copyAnnotations) { - return (AnnotatedUnionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitUnion(this, p); + } - @Override - public AnnotatedUnionType deepCopy() { - return deepCopy(true); - } + @Override + public AnnotatedUnionType deepCopy(boolean copyAnnotations) { + return (AnnotatedUnionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - @Override - public AnnotatedUnionType shallowCopy(boolean copyAnnotations) { - AnnotatedUnionType type = - new AnnotatedUnionType((UnionType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.alternatives = this.alternatives; - return type; - } + @Override + public AnnotatedUnionType deepCopy() { + return deepCopy(true); + } - @Override - public AnnotatedUnionType shallowCopy() { - return shallowCopy(true); - } + @Override + public AnnotatedUnionType shallowCopy(boolean copyAnnotations) { + AnnotatedUnionType type = new AnnotatedUnionType((UnionType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.alternatives = this.alternatives; + return type; + } - /** - * The types that are unioned to form this AnnotatedUnionType. - * - *

Is set by {@link #getAlternatives} and {@link #shallowCopy}. - */ - protected @MonotonicNonNull List alternatives; - - /** - * Returns the types that are unioned to form this AnnotatedUnionType. - * - * @return the types that are unioned to form this AnnotatedUnionType - */ - public List getAlternatives() { - if (alternatives == null) { - List ualts = ((UnionType) underlyingType).getAlternatives(); - List res = - CollectionsPlume.mapList( - (TypeMirror alt) -> - (AnnotatedDeclaredType) - createType(alt, atypeFactory, false), - ualts); - alternatives = Collections.unmodifiableList(res); - } - return alternatives; - } + @Override + public AnnotatedUnionType shallowCopy() { + return shallowCopy(true); } /** - * This method returns a list of AnnotatedTypeMirrors where the Java type of each ATM is an - * immediate supertype (class or interface) of the Java type of this. The interface types, if - * any, appear at the end of the list. If the directSuperType has type arguments, then the - * annotations on those type arguments are taken with proper translation from the declaration of - * the Java type of this. + * The types that are unioned to form this AnnotatedUnionType. * - *

For example, - * - *

-     * {@code class B { ... } }
-     * {@code class A extends B<@NonNull String> { ... } }
-     * {@code @Nullable A a;}
-     * 
- * - * The direct supertype of the ATM {@code @Nullable A} is {@code @Nullable B<@NonNull String>}. - * - *

An example with more complex type arguments: - * - *

-     * {@code class D { ... } }
-     * {@code class A extends D { ... } }
-     * {@code @Nullable A<@NonNull String, @NonNull Object> a;}
-     * 
- * - * The direct supertype of the ATM {@code @Nullable A<@NonNull String, @NonNull Object>} is - * {@code @Nullable B<@NonNull Object, @NonNull String>}. - * - *

An example with more than one direct supertype: - * - *

-     * {@code class B implements List { ... } }
-     * {@code class A extends B<@NonNull String> implements List { ... } }
-     * {@code @Nullable A a;}
-     * 
- * - * The direct supertypes of the ATM {@code @Nullable A} are {@code @Nullable B <@NonNull - * String>} and {@code @Nullable List<@NonNull Integer>}. + *

Is set by {@link #getAlternatives} and {@link #shallowCopy}. + */ + protected @MonotonicNonNull List alternatives; + + /** + * Returns the types that are unioned to form this AnnotatedUnionType. * - * @return the immediate supertypes of this - * @see Types#directSupertypes(TypeMirror) + * @return the types that are unioned to form this AnnotatedUnionType */ - public List directSupertypes() { - return SupertypeFinder.directSupertypes(this); + public List getAlternatives() { + if (alternatives == null) { + List ualts = ((UnionType) underlyingType).getAlternatives(); + List res = + CollectionsPlume.mapList( + (TypeMirror alt) -> (AnnotatedDeclaredType) createType(alt, atypeFactory, false), + ualts); + alternatives = Collections.unmodifiableList(res); + } + return alternatives; } + } + + /** + * This method returns a list of AnnotatedTypeMirrors where the Java type of each ATM is an + * immediate supertype (class or interface) of the Java type of this. The interface types, if any, + * appear at the end of the list. If the directSuperType has type arguments, then the annotations + * on those type arguments are taken with proper translation from the declaration of the Java type + * of this. + * + *

For example, + * + *

+   * {@code class B { ... } }
+   * {@code class A extends B<@NonNull String> { ... } }
+   * {@code @Nullable A a;}
+   * 
+ * + * The direct supertype of the ATM {@code @Nullable A} is {@code @Nullable B<@NonNull String>}. + * + *

An example with more complex type arguments: + * + *

+   * {@code class D { ... } }
+   * {@code class A extends D { ... } }
+   * {@code @Nullable A<@NonNull String, @NonNull Object> a;}
+   * 
+ * + * The direct supertype of the ATM {@code @Nullable A<@NonNull String, @NonNull Object>} is + * {@code @Nullable B<@NonNull Object, @NonNull String>}. + * + *

An example with more than one direct supertype: + * + *

+   * {@code class B implements List { ... } }
+   * {@code class A extends B<@NonNull String> implements List { ... } }
+   * {@code @Nullable A a;}
+   * 
+ * + * The direct supertypes of the ATM {@code @Nullable A} are {@code @Nullable B <@NonNull String>} + * and {@code @Nullable List<@NonNull Integer>}. + * + * @return the immediate supertypes of this + * @see Types#directSupertypes(TypeMirror) + */ + public List directSupertypes() { + return SupertypeFinder.directSupertypes(this); + } + + /** + * Returns true if this type has a primary annotation in the same hierarchy as {@code annotation}. + * + * @param annotation the qualifier hierarchy to check for + * @return true iff this type has a primary annotation in the same hierarchy as {@code + * annotation}. + * @deprecated use {@link #hasAnnotationInHierarchy(AnnotationMirror)} + */ + @Deprecated // 2023-06-15 + public boolean isAnnotatedInHierarchy(AnnotationMirror annotation) { + return hasAnnotationInHierarchy(annotation); + } + + /** An ERROR TypeKind was found. */ + @SuppressWarnings("serial") + public static class ErrorTypeKindException extends Error { /** - * Returns true if this type has a primary annotation in the same hierarchy as {@code - * annotation}. + * Creates an ErrorTypeKindException. * - * @param annotation the qualifier hierarchy to check for - * @return true iff this type has a primary annotation in the same hierarchy as {@code - * annotation}. - * @deprecated use {@link #hasAnnotationInHierarchy(AnnotationMirror)} - */ - @Deprecated // 2023-06-15 - public boolean isAnnotatedInHierarchy(AnnotationMirror annotation) { - return hasAnnotationInHierarchy(annotation); - } - - /** An ERROR TypeKind was found. */ - @SuppressWarnings("serial") - public static class ErrorTypeKindException extends Error { - - /** - * Creates an ErrorTypeKindException. - * - * @param format format string - * @param args arguments to the format string - */ - @FormatMethod - public ErrorTypeKindException(String format, Object... args) { - super(String.format(format, args)); - } + * @param format format string + * @param args arguments to the format string + */ + @FormatMethod + public ErrorTypeKindException(String format, Object... args) { + super(String.format(format, args)); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java index 6a1aabe5d75..c845f5ddea3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java @@ -1,54 +1,53 @@ package org.checkerframework.framework.type; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** Represents upper and lower bounds, each an AnnotatedTypeMirror. */ public class AnnotatedTypeParameterBounds { - private final AnnotatedTypeMirror upper; - private final AnnotatedTypeMirror lower; - - public AnnotatedTypeParameterBounds(AnnotatedTypeMirror upper, AnnotatedTypeMirror lower) { - this.upper = upper; - this.lower = lower; - } - - public AnnotatedTypeMirror getUpperBound() { - return upper; - } - - public AnnotatedTypeMirror getLowerBound() { - return lower; - } - - @Override - public String toString() { - return "[extends " + upper + " super " + lower + "]"; - } - - /** - * Return a possibly-verbose string representation of this. - * - * @param verbose if true, returned representation is verbose - * @return a possibly-verbose string representation of this - */ - public String toString(boolean verbose) { - return "[extends " + upper.toString(verbose) + " super " + lower.toString(verbose) + "]"; - } - - @Override - public int hashCode() { - return Objects.hash(upper, lower); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof AnnotatedTypeParameterBounds)) { - return false; - } - AnnotatedTypeParameterBounds other = (AnnotatedTypeParameterBounds) obj; - return (this.upper == null ? other.upper == null : this.upper.equals(other.upper)) - && (this.lower == null ? other.lower == null : this.lower.equals(other.lower)); + private final AnnotatedTypeMirror upper; + private final AnnotatedTypeMirror lower; + + public AnnotatedTypeParameterBounds(AnnotatedTypeMirror upper, AnnotatedTypeMirror lower) { + this.upper = upper; + this.lower = lower; + } + + public AnnotatedTypeMirror getUpperBound() { + return upper; + } + + public AnnotatedTypeMirror getLowerBound() { + return lower; + } + + @Override + public String toString() { + return "[extends " + upper + " super " + lower + "]"; + } + + /** + * Return a possibly-verbose string representation of this. + * + * @param verbose if true, returned representation is verbose + * @return a possibly-verbose string representation of this + */ + public String toString(boolean verbose) { + return "[extends " + upper.toString(verbose) + " super " + lower.toString(verbose) + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(upper, lower); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof AnnotatedTypeParameterBounds)) { + return false; } + AnnotatedTypeParameterBounds other = (AnnotatedTypeParameterBounds) obj; + return (this.upper == null ? other.upper == null : this.upper.equals(other.upper)) + && (this.lower == null ? other.lower == null : this.lower.equals(other.lower)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java index 0689d78727d..7940b4edadd 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java @@ -1,17 +1,15 @@ package org.checkerframework.framework.type; +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.visitor.DoubleAnnotatedTypeScanner; import org.checkerframework.javacutil.BugInCF; -import java.util.ArrayList; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; - /** * Replaces or adds all the annotations in the parameter with the annotations from the visited type. * An annotation is replaced if the parameter type already has an annotation in the same hierarchy @@ -27,107 +25,107 @@ */ public class AnnotatedTypeReplacer extends DoubleAnnotatedTypeScanner { - /** If top != null we replace only the annotations in the hierarchy of top. */ - private @Nullable AnnotationMirror top; + /** If top != null we replace only the annotations in the hierarchy of top. */ + private @Nullable AnnotationMirror top; - /** Construct an AnnotatedTypeReplacer that will replace all annotations. */ - public AnnotatedTypeReplacer() { - this.top = null; - } + /** Construct an AnnotatedTypeReplacer that will replace all annotations. */ + public AnnotatedTypeReplacer() { + this.top = null; + } - /** - * Construct an AnnotatedTypeReplacer that will only replace annotations in {@code top}'s - * hierarchy. - * - * @param top if top != null, then only annotations in the hierarchy of top are affected - */ - public AnnotatedTypeReplacer(@Nullable AnnotationMirror top) { - this.top = top; - } + /** + * Construct an AnnotatedTypeReplacer that will only replace annotations in {@code top}'s + * hierarchy. + * + * @param top if top != null, then only annotations in the hierarchy of top are affected + */ + public AnnotatedTypeReplacer(@Nullable AnnotationMirror top) { + this.top = top; + } - /** - * If {@code top != null}, then only annotations in the hierarchy of {@code top} are affected; - * otherwise, all annotations are replaced. - * - * @param top if top != null, then only annotations in the hierarchy of top are replaced; - * otherwise, all annotations are replaced. - */ - public void setTop(@Nullable AnnotationMirror top) { - this.top = top; - } + /** + * If {@code top != null}, then only annotations in the hierarchy of {@code top} are affected; + * otherwise, all annotations are replaced. + * + * @param top if top != null, then only annotations in the hierarchy of top are replaced; + * otherwise, all annotations are replaced. + */ + public void setTop(@Nullable AnnotationMirror top) { + this.top = top; + } - @SuppressWarnings("interning:not.interned") // assertion - @Override - protected Void defaultAction(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - assert from != to; - if (from != null && to != null) { - replaceAnnotations(from, to); - } - return null; + @SuppressWarnings("interning:not.interned") // assertion + @Override + protected Void defaultAction(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + assert from != to; + if (from != null && to != null) { + replaceAnnotations(from, to); } + return null; + } - /** - * Replace the annotations in to with the annotations in from, wherever from has an annotation. - * - * @param from the source of the annotations - * @param to the destination of the annotations, modified by this method - */ - protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - if (top == null) { - to.replaceAnnotations(from.getAnnotations()); - } else { - AnnotationMirror replacement = from.getAnnotationInHierarchy(top); - if (replacement != null) { - to.replaceAnnotation(from.getAnnotationInHierarchy(top)); - } - } + /** + * Replace the annotations in to with the annotations in from, wherever from has an annotation. + * + * @param from the source of the annotations + * @param to the destination of the annotations, modified by this method + */ + protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + if (top == null) { + to.replaceAnnotations(from.getAnnotations()); + } else { + AnnotationMirror replacement = from.getAnnotationInHierarchy(top); + if (replacement != null) { + to.replaceAnnotation(from.getAnnotationInHierarchy(top)); + } } + } - @Override - public Void visitTypeVariable(AnnotatedTypeVariable from, AnnotatedTypeMirror to) { - resolvePrimaries(from, to); - return super.visitTypeVariable(from, to); - } + @Override + public Void visitTypeVariable(AnnotatedTypeVariable from, AnnotatedTypeMirror to) { + resolvePrimaries(from, to); + return super.visitTypeVariable(from, to); + } - @Override - public Void visitWildcard(AnnotatedWildcardType from, AnnotatedTypeMirror to) { - resolvePrimaries(from, to); - return super.visitWildcard(from, to); - } + @Override + public Void visitWildcard(AnnotatedWildcardType from, AnnotatedTypeMirror to) { + resolvePrimaries(from, to); + return super.visitWildcard(from, to); + } - /** - * For type variables and wildcards, the absence of a primary annotations has an implied meaning - * on substitution. Therefore, in these cases we remove the primary annotation and rely on the - * fact that the bounds are also merged into the type to. - * - * @param from a type variable or wildcard - * @param to the destination annotated type mirror - */ - public void resolvePrimaries(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - if (from.getKind() == TypeKind.WILDCARD || from.getKind() == TypeKind.TYPEVAR) { - if (top != null) { - if (from.getAnnotationInHierarchy(top) == null) { - to.removeAnnotationInHierarchy(top); - } - } else { - List toRemove = new ArrayList<>(1); - for (AnnotationMirror toPrimaryAnno : to.getAnnotations()) { - if (from.getAnnotationInHierarchy(toPrimaryAnno) == null) { - // Doing the removal here directly can lead to a - // ConcurrentModificationException, - // because this loop is iterating over the annotations in `to`. - toRemove.add(toPrimaryAnno); - } - } - for (AnnotationMirror annoToRemove : toRemove) { - to.removeAnnotation(annoToRemove); - } - } - } else { - throw new BugInCF( - "ResolvePrimaries's from argument should be a type variable OR wildcard%n" - + "from=%s%nto=%s", - from.toString(true), to.toString(true)); + /** + * For type variables and wildcards, the absence of a primary annotations has an implied meaning + * on substitution. Therefore, in these cases we remove the primary annotation and rely on the + * fact that the bounds are also merged into the type to. + * + * @param from a type variable or wildcard + * @param to the destination annotated type mirror + */ + public void resolvePrimaries(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + if (from.getKind() == TypeKind.WILDCARD || from.getKind() == TypeKind.TYPEVAR) { + if (top != null) { + if (from.getAnnotationInHierarchy(top) == null) { + to.removeAnnotationInHierarchy(top); + } + } else { + List toRemove = new ArrayList<>(1); + for (AnnotationMirror toPrimaryAnno : to.getAnnotations()) { + if (from.getAnnotationInHierarchy(toPrimaryAnno) == null) { + // Doing the removal here directly can lead to a + // ConcurrentModificationException, + // because this loop is iterating over the annotations in `to`. + toRemove.add(toPrimaryAnno); + } + } + for (AnnotationMirror annoToRemove : toRemove) { + to.removeAnnotation(annoToRemove); } + } + } else { + throw new BugInCF( + "ResolvePrimaries's from argument should be a type variable OR wildcard%n" + + "from=%s%nto=%s", + from.toString(true), to.toString(true)); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java index 233b42f66df..82f7226fb0d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java @@ -1,21 +1,5 @@ package org.checkerframework.framework.type; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.mustcall.qual.InheritableMustCall; -import org.checkerframework.checker.mustcall.qual.Owning; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; -import org.checkerframework.checker.signature.qual.Identifier; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.InternalUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.UserError; -import org.plumelib.reflection.Signatures; - import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -40,13 +24,27 @@ import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.mustcall.qual.InheritableMustCall; +import org.checkerframework.checker.mustcall.qual.Owning; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; +import org.checkerframework.checker.signature.qual.Identifier; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.UserError; +import org.plumelib.reflection.Signatures; /** * This class assists the {@link AnnotatedTypeFactory} by reflectively looking up the list of @@ -70,798 +68,783 @@ * org.checkerframework.checker.units.UnitsAnnotationClassLoader} for an example. */ @SuppressWarnings( - "mustcall:inconsistent.mustcall.subtype" // No need to check that AnnotationClassLoaders are + "mustcall:inconsistent.mustcall.subtype" // No need to check that AnnotationClassLoaders are // closed. (Just one is created per type factory.) ) @InheritableMustCall({}) public class AnnotationClassLoader implements Closeable { - /** For issuing errors to the user. */ - protected final BaseTypeChecker checker; - - // For loading from a source package directory - /** The package name. */ - private final @DotSeparatedIdentifiers String packageName; - - /** The package name, with periods replaced by slashes. */ - private final String packageNameWithSlashes; - - /** The atomic package names (the package name split at dots). */ - private final List<@Identifier String> fullyQualifiedPackageNameSegments; - - /** The name of a Checker's qualifier package. */ - private static final String QUAL_PACKAGE = "qual"; - - // For loading from a Jar file - /** The suffix for a .jar file. */ - private static final String JAR_SUFFIX = ".jar"; - - /** The suffix for a .class file. */ - private static final String CLASS_SUFFIX = ".class"; - - // Constants - /** The package separator. */ - private static final char DOT = '.'; - - /** The path separator, in .jar files, binary names, etc. */ - private static final char SLASH = '/'; - - /** - * Processing Env used to create an {@link AnnotationBuilder}, which is in turn used to build - * the annotation mirror from the loaded class. - */ - protected final ProcessingEnvironment processingEnv; - - /** The resource URL of the qual directory of a checker class. */ - private final URL resourceURL; - - /** The class loader used to load annotation classes. */ - @SuppressWarnings("builder:required.method.not.called") // this class is @MustCall({}) - protected final @Owning URLClassLoader classLoader; - - /** - * The annotation classes bundled with a checker (located in its qual directory) that are deemed - * supported by the checker (non-alias annotations). Each checker can override {@link - * #isSupportedAnnotationClass(Class)} to determine whether an annotation is supported or not. - * Call {@link #getBundledAnnotationClasses()} to obtain a reference to the set of classes. - */ - private final Set> supportedBundledAnnotationClasses; - - /** - * Constructor for loading annotations defined for a checker. - * - * @param checker a {@link BaseTypeChecker} or its subclass - */ - @SuppressWarnings("signature") // TODO: reduce use of string manipulation - public AnnotationClassLoader(BaseTypeChecker checker) { - this.checker = checker; - processingEnv = checker.getProcessingEnvironment(); - - // package name must use dots, this is later prepended to annotation - // class names as we load the classes using the class loader - Package checkerPackage = checker.getClass().getPackage(); - packageName = - checkerPackage != null && !checkerPackage.getName().isEmpty() - ? checkerPackage.getName() + DOT + QUAL_PACKAGE - : QUAL_PACKAGE; - - // the package name with dots replaced by slashes will be used to scan file directories - packageNameWithSlashes = packageName.replace(DOT, SLASH); - - // Each component of the fully qualified package name will be used later to recursively - // descend from a root directory to see if the package exists in some particular root - // directory. - fullyQualifiedPackageNameSegments = new ArrayList<>(); - - // from the fully qualified package name, split it at every dot then add to the list - fullyQualifiedPackageNameSegments.addAll(SystemUtil.DOT_SPLITTER.splitToList(packageName)); - - classLoader = getClassLoader(); - - URL localResourceURL; - if (classLoader != null) { - // if the application classloader is accessible, then directly retrieve the resource URL - // of the qual package resource URLs must use slashes - localResourceURL = classLoader.getResource(packageNameWithSlashes); - - // thread based application classloader, if needed in the future: - // resourceURL = - // Thread.currentThread().getContextClassLoader().getResource(packageNameWithSlashes); - } else { - // Signal failure to find resource - localResourceURL = null; - } - - if (localResourceURL == null) { - // if the application classloader is not accessible (which means the checker class was - // loaded using the bootstrap classloader) or if the classloader didn't find the - // package, then scan the classpaths to find a jar or directory which contains the qual - // package and set the resource URL to that jar or qual directory - localResourceURL = getURLFromClasspaths(); - } - resourceURL = localResourceURL; - - supportedBundledAnnotationClasses = new LinkedHashSet<>(); - - loadBundledAnnotationClasses(); + /** For issuing errors to the user. */ + protected final BaseTypeChecker checker; + + // For loading from a source package directory + /** The package name. */ + private final @DotSeparatedIdentifiers String packageName; + + /** The package name, with periods replaced by slashes. */ + private final String packageNameWithSlashes; + + /** The atomic package names (the package name split at dots). */ + private final List<@Identifier String> fullyQualifiedPackageNameSegments; + + /** The name of a Checker's qualifier package. */ + private static final String QUAL_PACKAGE = "qual"; + + // For loading from a Jar file + /** The suffix for a .jar file. */ + private static final String JAR_SUFFIX = ".jar"; + + /** The suffix for a .class file. */ + private static final String CLASS_SUFFIX = ".class"; + + // Constants + /** The package separator. */ + private static final char DOT = '.'; + + /** The path separator, in .jar files, binary names, etc. */ + private static final char SLASH = '/'; + + /** + * Processing Env used to create an {@link AnnotationBuilder}, which is in turn used to build the + * annotation mirror from the loaded class. + */ + protected final ProcessingEnvironment processingEnv; + + /** The resource URL of the qual directory of a checker class. */ + private final URL resourceURL; + + /** The class loader used to load annotation classes. */ + @SuppressWarnings("builder:required.method.not.called") // this class is @MustCall({}) + protected final @Owning URLClassLoader classLoader; + + /** + * The annotation classes bundled with a checker (located in its qual directory) that are deemed + * supported by the checker (non-alias annotations). Each checker can override {@link + * #isSupportedAnnotationClass(Class)} to determine whether an annotation is supported or not. + * Call {@link #getBundledAnnotationClasses()} to obtain a reference to the set of classes. + */ + private final Set> supportedBundledAnnotationClasses; + + /** + * Constructor for loading annotations defined for a checker. + * + * @param checker a {@link BaseTypeChecker} or its subclass + */ + @SuppressWarnings("signature") // TODO: reduce use of string manipulation + public AnnotationClassLoader(BaseTypeChecker checker) { + this.checker = checker; + processingEnv = checker.getProcessingEnvironment(); + + // package name must use dots, this is later prepended to annotation + // class names as we load the classes using the class loader + Package checkerPackage = checker.getClass().getPackage(); + packageName = + checkerPackage != null && !checkerPackage.getName().isEmpty() + ? checkerPackage.getName() + DOT + QUAL_PACKAGE + : QUAL_PACKAGE; + + // the package name with dots replaced by slashes will be used to scan file directories + packageNameWithSlashes = packageName.replace(DOT, SLASH); + + // Each component of the fully qualified package name will be used later to recursively + // descend from a root directory to see if the package exists in some particular root + // directory. + fullyQualifiedPackageNameSegments = new ArrayList<>(); + + // from the fully qualified package name, split it at every dot then add to the list + fullyQualifiedPackageNameSegments.addAll(SystemUtil.DOT_SPLITTER.splitToList(packageName)); + + classLoader = getClassLoader(); + + URL localResourceURL; + if (classLoader != null) { + // if the application classloader is accessible, then directly retrieve the resource URL + // of the qual package resource URLs must use slashes + localResourceURL = classLoader.getResource(packageNameWithSlashes); + + // thread based application classloader, if needed in the future: + // resourceURL = + // Thread.currentThread().getContextClassLoader().getResource(packageNameWithSlashes); + } else { + // Signal failure to find resource + localResourceURL = null; } - @EnsuresCalledMethods(value = "classLoader", methods = "close") - @Override - public void close() { - try { - classLoader.close(); - } catch (IOException e) { - checker.message(Diagnostic.Kind.NOTE, "Failed to close AnnotationClassLoader"); - } + if (localResourceURL == null) { + // if the application classloader is not accessible (which means the checker class was + // loaded using the bootstrap classloader) or if the classloader didn't find the + // package, then scan the classpaths to find a jar or directory which contains the qual + // package and set the resource URL to that jar or qual directory + localResourceURL = getURLFromClasspaths(); } + resourceURL = localResourceURL; - /** - * Scans all classpaths and returns the resource URL to the jar which contains the checker's - * qual package, or the qual package directory if it exists, or null if no jar or directory - * contains the package. - * - * @return a URL to the jar that contains the qual package, or to the qual package's directory, - * or null if no jar or directory contains the qual package - */ - private @Nullable URL getURLFromClasspaths() { - // TODO: This method could probably be replaced with - // io.github.classgraph.ClassGraph#getClasspathURIs() - - // Debug use, uncomment if needed to see all of the classpaths (boot - // classpath, extension classpath, and classpath) - // printPaths(); - - URL url = null; - - // obtain all classpaths - Set paths = getClasspaths(); - - // In checkers, there will be a resource URL for the qual directory. But when called in the - // framework (eg GeneralAnnotatedTypeFactory), there won't be a resourceURL since there - // isn't a qual directory. - - // Each path from the set of classpaths will be checked to see if it contains the qual - // directory of a checker, if so, the first directory or jar that contains the package will - // be used as the source for loading classes from the qual package. - - // If either a directory or a jar contains the package, resourceURL will be updated to refer - // to that source, otherwise resourceURL remains as null. - - // If both a jar and a directory contain the qual package, then the order of the jar and the - // directory in the command line option(s) or environment variables will decide which one - // gets examined first. - for (String path : paths) { - // see if the current classpath segment is a jar or a directory - if (path.endsWith(JAR_SUFFIX)) { - // current classpath segment is a jar - url = getJarURL(path); - - // see if the jar contains the package - if (url != null && containsPackage(url)) { - return url; - } - } else { - // current classpath segment is a directory - url = getDirectoryURL(path); - - // see if the directory contains the package - if (url != null && containsPackage(url)) { - // append a slash if necessary - if (!path.endsWith(Character.toString(SLASH))) { - path += SLASH; - } - - // update URL to the qual directory - url = getDirectoryURL(path + packageNameWithSlashes); - - return url; - } - } - } + supportedBundledAnnotationClasses = new LinkedHashSet<>(); - // if no jar or directory contains the qual package, then return null - return null; - } + loadBundledAnnotationClasses(); + } - /** - * Checks to see if the jar or directory referred by the URL contains the qual package of a - * specific checker. - * - * @param url a URL referring to either a jar or a directory - * @return true if the jar or the directory contains the qual package, false otherwise - */ - private boolean containsPackage(URL url) { - // see whether the resource URL has a protocol of jar or file - if (url.getProtocol().equals("jar")) { - // try to open up the jar file - try { - JarURLConnection connection = (JarURLConnection) url.openConnection(); - try (JarFile jarFile = connection.getJarFile()) { - // check to see if the jar file contains the package - return checkJarForPackage(jarFile); - } - } catch (IOException e) { - // do nothing for missing or un-openable Jar files - } - } else if (url.getProtocol().equals("file")) { - // open up the directory - File rootDir = new File(url.getFile()); - - // check to see if the directory contains the package - return checkDirForPackage(rootDir, fullyQualifiedPackageNameSegments.iterator()); - } - - return false; + @EnsuresCalledMethods(value = "classLoader", methods = "close") + @Override + public void close() { + try { + classLoader.close(); + } catch (IOException e) { + checker.message(Diagnostic.Kind.NOTE, "Failed to close AnnotationClassLoader"); } - - /** - * Checks to see if the jar file contains the qual package of a specific checker. - * - * @param jar a jar file - * @return true if the jar file contains the qual package, false otherwise - */ - @SuppressWarnings("JdkObsolete") - private boolean checkJarForPackage(JarFile jar) { - Enumeration jarEntries = jar.entries(); - - // loop through the entries in the jar - while (jarEntries.hasMoreElements()) { - JarEntry je = jarEntries.nextElement(); - - // Each entry is the fully qualified path and file name to a particular artifact in the - // jar file (eg a class file). - // If the jar has the package, one of the entry's name will begin with the package name - // in slash notation. - String entryName = je.getName(); - if (entryName.startsWith(packageNameWithSlashes + SLASH)) { - return true; - } + } + + /** + * Scans all classpaths and returns the resource URL to the jar which contains the checker's qual + * package, or the qual package directory if it exists, or null if no jar or directory contains + * the package. + * + * @return a URL to the jar that contains the qual package, or to the qual package's directory, or + * null if no jar or directory contains the qual package + */ + private @Nullable URL getURLFromClasspaths() { + // TODO: This method could probably be replaced with + // io.github.classgraph.ClassGraph#getClasspathURIs() + + // Debug use, uncomment if needed to see all of the classpaths (boot + // classpath, extension classpath, and classpath) + // printPaths(); + + URL url = null; + + // obtain all classpaths + Set paths = getClasspaths(); + + // In checkers, there will be a resource URL for the qual directory. But when called in the + // framework (eg GeneralAnnotatedTypeFactory), there won't be a resourceURL since there + // isn't a qual directory. + + // Each path from the set of classpaths will be checked to see if it contains the qual + // directory of a checker, if so, the first directory or jar that contains the package will + // be used as the source for loading classes from the qual package. + + // If either a directory or a jar contains the package, resourceURL will be updated to refer + // to that source, otherwise resourceURL remains as null. + + // If both a jar and a directory contain the qual package, then the order of the jar and the + // directory in the command line option(s) or environment variables will decide which one + // gets examined first. + for (String path : paths) { + // see if the current classpath segment is a jar or a directory + if (path.endsWith(JAR_SUFFIX)) { + // current classpath segment is a jar + url = getJarURL(path); + + // see if the jar contains the package + if (url != null && containsPackage(url)) { + return url; } + } else { + // current classpath segment is a directory + url = getDirectoryURL(path); - return false; - } + // see if the directory contains the package + if (url != null && containsPackage(url)) { + // append a slash if necessary + if (!path.endsWith(Character.toString(SLASH))) { + path += SLASH; + } - /** - * Checks to see if the current directory contains the qual package through recursion currentDir - * starts at the root directory (a directory passed in as part of the classpaths), the iterator - * goes through each segment of the fully qualified package name (each segment is separated by a - * dot). - * - *

Each step of the recursion checks to see if there's a subdirectory in the current - * directory that has a name matching the package name segment, if so, it recursively descends - * into that subdirectory to check the next package name segment - * - *

If there's no more segments left, then we've found the qual directory of interest - * - *

If we've checked every subdirectory and none of them match the current package name - * segment, then the qual directory of interest does not exist in the given root directory (at - * the beginning of recursion) - * - * @param currentDir current directory - * @param pkgNames an iterator which provides each segment of the fully qualified qual package - * name - * @return true if the qual package exists within the root directory, false otherwise - */ - private boolean checkDirForPackage(File currentDir, Iterator pkgNames) { - // if the iterator has no more package name segments, then we've found - // the qual directory of interest - if (!pkgNames.hasNext()) { - return true; - } - // if the file doesn't exist or it isn't a directory, return false - if (currentDir == null || !currentDir.isDirectory()) { - return false; - } + // update URL to the qual directory + url = getDirectoryURL(path + packageNameWithSlashes); - // if it isn't empty, dequeue one segment of the fully qualified package name - String currentPackageDirName = pkgNames.next(); - - // scan current directory to see if there's a sub-directory that has a - // matching name as the package name segment - for (File file : currentDir.listFiles()) { - if (file.isDirectory() && file.getName().equals(currentPackageDirName)) { - // if so, recursively descend and look at the next segment of - // the package name - return checkDirForPackage(file, pkgNames); - } + return url; } - - // if no sub-directory has a matching name, then that means there isn't - // a matching qual package - return false; + } } - /** - * Given an absolute path to a directory, this method will return a URL reference to that - * directory. - * - * @param absolutePathToDirectory an absolute path to a directory - * @return a URL reference to the directory, or null if the URL is malformed - */ - private @Nullable URL getDirectoryURL(String absolutePathToDirectory) { - URL directoryURL = null; - - try { - directoryURL = new File(absolutePathToDirectory).toURI().toURL(); - } catch (MalformedURLException e) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.NOTE, - "Directory URL " + absolutePathToDirectory + " is malformed"); + // if no jar or directory contains the qual package, then return null + return null; + } + + /** + * Checks to see if the jar or directory referred by the URL contains the qual package of a + * specific checker. + * + * @param url a URL referring to either a jar or a directory + * @return true if the jar or the directory contains the qual package, false otherwise + */ + private boolean containsPackage(URL url) { + // see whether the resource URL has a protocol of jar or file + if (url.getProtocol().equals("jar")) { + // try to open up the jar file + try { + JarURLConnection connection = (JarURLConnection) url.openConnection(); + try (JarFile jarFile = connection.getJarFile()) { + // check to see if the jar file contains the package + return checkJarForPackage(jarFile); } - - return directoryURL; + } catch (IOException e) { + // do nothing for missing or un-openable Jar files + } + } else if (url.getProtocol().equals("file")) { + // open up the directory + File rootDir = new File(url.getFile()); + + // check to see if the directory contains the package + return checkDirForPackage(rootDir, fullyQualifiedPackageNameSegments.iterator()); } - /** - * Given an absolute path to a jar file, this method will return a URL reference to that jar - * file. - * - * @param absolutePathToJarFile an absolute path to a jar file - * @return a URL reference to the jar file, or null if the URL is malformed - */ - private @Nullable URL getJarURL(String absolutePathToJarFile) { - URL jarURL = null; - - try { - String normalizedPath = absolutePathToJarFile.replace("\\", "/"); - String osName = System.getProperty("os.name").toString().toLowerCase(Locale.ENGLISH); - String prefix = osName.startsWith("windows") ? "jar:file:///" : "jar:file:"; - - jarURL = new URI(prefix + normalizedPath + "!/").toURL(); - } catch (MalformedURLException | URISyntaxException e) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.NOTE, - "Jar URL " + absolutePathToJarFile + " is malformed"); - } - - return jarURL; + return false; + } + + /** + * Checks to see if the jar file contains the qual package of a specific checker. + * + * @param jar a jar file + * @return true if the jar file contains the qual package, false otherwise + */ + @SuppressWarnings("JdkObsolete") + private boolean checkJarForPackage(JarFile jar) { + Enumeration jarEntries = jar.entries(); + + // loop through the entries in the jar + while (jarEntries.hasMoreElements()) { + JarEntry je = jarEntries.nextElement(); + + // Each entry is the fully qualified path and file name to a particular artifact in the + // jar file (eg a class file). + // If the jar has the package, one of the entry's name will begin with the package name + // in slash notation. + String entryName = je.getName(); + if (entryName.startsWith(packageNameWithSlashes + SLASH)) { + return true; + } } - /** - * Obtains and returns a set of the classpaths from compiler options, system environment - * variables, and by examining the classloader to see what paths it has access to. - * - *

The classpaths will be obtained in the order of: - * - *

    - *
  1. extension paths (from java.ext.dirs) - *
  2. classpaths (set in {@code CLASSPATH}, or through {@code -classpath} and {@code -cp}) - *
  3. paths accessible and examined by the classloader - *
- * - * In each of these paths, the order of the paths as specified in the command line options or - * environment variables will be the order returned in the set - * - * @return an immutable linked hashset of the classpaths - */ - private Set getClasspaths() { - Set paths = new LinkedHashSet<>(); - - // add all extension paths - paths.addAll(SystemUtil.getPathsProperty("java.ext.dirs")); - - // add all paths in CLASSPATH, -cp, and -classpath - paths.addAll(SystemUtil.getPathsProperty("java.class.path")); - - // add all paths that are examined by the classloader - if (classLoader != null) { - URL[] urls = classLoader.getURLs(); - for (int i = 0; i < urls.length; i++) { - paths.add(urls[i].getFile().toString()); - } - } - - return Collections.unmodifiableSet(paths); + return false; + } + + /** + * Checks to see if the current directory contains the qual package through recursion currentDir + * starts at the root directory (a directory passed in as part of the classpaths), the iterator + * goes through each segment of the fully qualified package name (each segment is separated by a + * dot). + * + *

Each step of the recursion checks to see if there's a subdirectory in the current directory + * that has a name matching the package name segment, if so, it recursively descends into that + * subdirectory to check the next package name segment + * + *

If there's no more segments left, then we've found the qual directory of interest + * + *

If we've checked every subdirectory and none of them match the current package name segment, + * then the qual directory of interest does not exist in the given root directory (at the + * beginning of recursion) + * + * @param currentDir current directory + * @param pkgNames an iterator which provides each segment of the fully qualified qual package + * name + * @return true if the qual package exists within the root directory, false otherwise + */ + private boolean checkDirForPackage(File currentDir, Iterator pkgNames) { + // if the iterator has no more package name segments, then we've found + // the qual directory of interest + if (!pkgNames.hasNext()) { + return true; } - - /** - * Obtains the classloader used to load the checker class, if that isn't available then it will - * try to obtain the system classloader. - * - * @return the classloader used to load the checker class, or the system classloader, or null if - * both are unavailable - */ - private @Nullable URLClassLoader getClassLoader() { - ClassLoader result = InternalUtils.getClassLoaderForClass(checker.getClass()); - if (result instanceof URLClassLoader) { - return (@Nullable URLClassLoader) result; - } else { - // Java 9+ use an internal classloader that doesn't support getting URLs. Ignore. - return null; - } + // if the file doesn't exist or it isn't a directory, return false + if (currentDir == null || !currentDir.isDirectory()) { + return false; } - /** Debug Use: Displays all classpaths examined by the class loader. */ - @SuppressWarnings("unused") // for debugging - protected final void printPaths() { - // all paths in Xbootclasspath - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "bootclass path:"); - for (String path : SystemUtil.getPathsProperty("sun.boot.class.path")) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); - } - - // all extension paths - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "extension dirs:"); - for (String path : SystemUtil.getPathsProperty("java.ext.dirs")) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); - } - - // all paths in CLASSPATH, -cp, and -classpath - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "java.class.path property:"); - for (String path : SystemUtil.getPathsProperty("java.class.path")) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); - } - - // add all paths that are examined by the classloader - processingEnv - .getMessager() - .printMessage(Diagnostic.Kind.NOTE, "classloader examined paths:"); - if (classLoader != null) { - URL[] urls = classLoader.getURLs(); - for (int i = 0; i < urls.length; i++) { - processingEnv - .getMessager() - .printMessage(Diagnostic.Kind.NOTE, "\t" + urls[i].getFile()); - } - } else { - processingEnv - .getMessager() - .printMessage(Diagnostic.Kind.NOTE, "classloader unavailable"); - } + // if it isn't empty, dequeue one segment of the fully qualified package name + String currentPackageDirName = pkgNames.next(); + + // scan current directory to see if there's a sub-directory that has a + // matching name as the package name segment + for (File file : currentDir.listFiles()) { + if (file.isDirectory() && file.getName().equals(currentPackageDirName)) { + // if so, recursively descend and look at the next segment of + // the package name + return checkDirForPackage(file, pkgNames); + } } - /** - * Loads the set of annotation classes in the qual directory of a checker shipped with the - * Checker Framework. - */ - private void loadBundledAnnotationClasses() { - // retrieve the fully qualified class names of the annotations - Set<@BinaryName String> annotationNames; - // see whether the resource URL has a protocol of jar or file - if (resourceURL != null && resourceURL.getProtocol().contentEquals("jar")) { - // if the checker class file is contained within a jar, then the resource URL for the - // qual directory will have the protocol "jar". This means the whole checker is loaded - // as a jar file. - - JarURLConnection connection; - // create a connection to the jar file - try { - connection = (JarURLConnection) resourceURL.openConnection(); - - // disable caching / connection sharing of the low level URLConnection to the Jar - // file - connection.setDefaultUseCaches(false); - connection.setUseCaches(false); - - // connect to the Jar file - connection.connect(); - } catch (IOException e) { - throw new BugInCF( - "AnnotationClassLoader: cannot open a connection to the Jar file " - + resourceURL.getFile()); - } - - // open up that jar file and extract annotation class names - try (JarFile jarFile = connection.getJarFile()) { - // get class names inside the jar file within the particular package - annotationNames = getBundledAnnotationNamesFromJar(jarFile); - } catch (IOException e) { - throw new BugInCF( - "AnnotationClassLoader: cannot open the Jar file " + resourceURL.getFile()); - } - } else if (resourceURL != null && resourceURL.getProtocol().contentEquals("file")) { - // If the checker class file is found within the file system itself within some - // directory (usually development build directories), then process the package as a file - // directory in the file system and load the annotations contained in the qual - // directory. - - // open up the directory - File packageDir = new File(resourceURL.getFile()); - annotationNames = getAnnotationNamesFromDirectory(packageName, packageDir, packageDir); - } else { - // We do not support a resource URL with any other protocols, so create an empty set. - annotationNames = Collections.emptySet(); - } - if (annotationNames.isEmpty()) { - PackageElement pkgEle = checker.getElementUtils().getPackageElement(packageName); - if (pkgEle != null) { - for (Element e : pkgEle.getEnclosedElements()) { - if (e.getKind() == ElementKind.ANNOTATION_TYPE) { - @SuppressWarnings( - "signature:assignment.type.incompatible") // Elements needs to be - // annotated. - @BinaryName String annoBinName = - checker.getElementUtils().getBinaryName((TypeElement) e).toString(); - annotationNames.add(annoBinName); - } - } - } - } - supportedBundledAnnotationClasses.addAll(loadAnnotationClasses(annotationNames)); + // if no sub-directory has a matching name, then that means there isn't + // a matching qual package + return false; + } + + /** + * Given an absolute path to a directory, this method will return a URL reference to that + * directory. + * + * @param absolutePathToDirectory an absolute path to a directory + * @return a URL reference to the directory, or null if the URL is malformed + */ + private @Nullable URL getDirectoryURL(String absolutePathToDirectory) { + URL directoryURL = null; + + try { + directoryURL = new File(absolutePathToDirectory).toURI().toURL(); + } catch (MalformedURLException e) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.NOTE, "Directory URL " + absolutePathToDirectory + " is malformed"); } - /** - * Gets the set of annotation classes in the qual directory of a checker shipped with the - * Checker Framework. Note that the returned set from this method is mutable. This method is - * intended to be called within {@link AnnotatedTypeFactory#createSupportedTypeQualifiers() - * createSupportedTypeQualifiers()} (or its helper methods) to help define the set of supported - * qualifiers. - * - * @see AnnotatedTypeFactory#createSupportedTypeQualifiers() - * @return a mutable set of the loaded bundled annotation classes - */ - public final Set> getBundledAnnotationClasses() { - return supportedBundledAnnotationClasses; + return directoryURL; + } + + /** + * Given an absolute path to a jar file, this method will return a URL reference to that jar file. + * + * @param absolutePathToJarFile an absolute path to a jar file + * @return a URL reference to the jar file, or null if the URL is malformed + */ + private @Nullable URL getJarURL(String absolutePathToJarFile) { + URL jarURL = null; + + try { + String normalizedPath = absolutePathToJarFile.replace("\\", "/"); + String osName = System.getProperty("os.name").toString().toLowerCase(Locale.ENGLISH); + String prefix = osName.startsWith("windows") ? "jar:file:///" : "jar:file:"; + + jarURL = new URI(prefix + normalizedPath + "!/").toURL(); + } catch (MalformedURLException | URISyntaxException e) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.NOTE, "Jar URL " + absolutePathToJarFile + " is malformed"); } - /** - * Retrieves the annotation class file names from the qual directory contained inside a jar. - * - * @param jar the JarFile containing the annotation class files - * @return a set of fully qualified class names of the annotations - */ - @SuppressWarnings("JdkObsolete") - private Set<@BinaryName String> getBundledAnnotationNamesFromJar(JarFile jar) { - Set<@BinaryName String> annos = new LinkedHashSet<>(); - - // get an enumeration iterator for all the content entries in the jar file - Enumeration jarEntries = jar.entries(); - - // enumerate through the entries - while (jarEntries.hasMoreElements()) { - JarEntry je = jarEntries.nextElement(); - // filter out directories and non-class files - if (je.isDirectory() || !je.getName().endsWith(CLASS_SUFFIX)) { - continue; - } - - String className = Signatures.classfilenameToBinaryName(je.getName()); - - // filter for qual package - if (className.startsWith(packageName + DOT)) { - // add to set - annos.add(className); - } - } + return jarURL; + } + + /** + * Obtains and returns a set of the classpaths from compiler options, system environment + * variables, and by examining the classloader to see what paths it has access to. + * + *

The classpaths will be obtained in the order of: + * + *

    + *
  1. extension paths (from java.ext.dirs) + *
  2. classpaths (set in {@code CLASSPATH}, or through {@code -classpath} and {@code -cp}) + *
  3. paths accessible and examined by the classloader + *
+ * + * In each of these paths, the order of the paths as specified in the command line options or + * environment variables will be the order returned in the set + * + * @return an immutable linked hashset of the classpaths + */ + private Set getClasspaths() { + Set paths = new LinkedHashSet<>(); + + // add all extension paths + paths.addAll(SystemUtil.getPathsProperty("java.ext.dirs")); + + // add all paths in CLASSPATH, -cp, and -classpath + paths.addAll(SystemUtil.getPathsProperty("java.class.path")); + + // add all paths that are examined by the classloader + if (classLoader != null) { + URL[] urls = classLoader.getURLs(); + for (int i = 0; i < urls.length; i++) { + paths.add(urls[i].getFile().toString()); + } + } - return annos; + return Collections.unmodifiableSet(paths); + } + + /** + * Obtains the classloader used to load the checker class, if that isn't available then it will + * try to obtain the system classloader. + * + * @return the classloader used to load the checker class, or the system classloader, or null if + * both are unavailable + */ + private @Nullable URLClassLoader getClassLoader() { + ClassLoader result = InternalUtils.getClassLoaderForClass(checker.getClass()); + if (result instanceof URLClassLoader) { + return (@Nullable URLClassLoader) result; + } else { + // Java 9+ use an internal classloader that doesn't support getting URLs. Ignore. + return null; + } + } + + /** Debug Use: Displays all classpaths examined by the class loader. */ + @SuppressWarnings("unused") // for debugging + protected final void printPaths() { + // all paths in Xbootclasspath + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "bootclass path:"); + for (String path : SystemUtil.getPathsProperty("sun.boot.class.path")) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); } - /** - * This method takes as input the canonical name of an external annotation class and loads and - * returns that class via the class loader. This method returns null if the external annotation - * class was loaded successfully but was deemed not supported by a checker. Errors are issued if - * the external class is not an annotation, or if it could not be loaded successfully. - * - * @param annoName canonical name of an external annotation class, e.g. - * "myproject.qual.myannotation" - * @return the loaded annotation class, or null if it was not a supported annotation as decided - * by {@link #isSupportedAnnotationClass(Class)} - */ - public final @Nullable Class loadExternalAnnotationClass( - @BinaryName String annoName) { - return loadAnnotationClass(annoName, true); + // all extension paths + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "extension dirs:"); + for (String path : SystemUtil.getPathsProperty("java.ext.dirs")) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); } - /** - * This method takes as input a fully qualified path to a directory, and loads and returns the - * set of all supported annotation classes from that directory. - * - * @param dirName absolute path to a directory containing annotation classes - * @return a set of annotation classes - */ - public final Set> loadExternalAnnotationClassesFromDirectory( - String dirName) { - File rootDirectory = new File(dirName); - Set<@BinaryName String> annoNames = - getAnnotationNamesFromDirectory(null, rootDirectory, rootDirectory); - return loadAnnotationClasses(annoNames); + // all paths in CLASSPATH, -cp, and -classpath + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "java.class.path property:"); + for (String path : SystemUtil.getPathsProperty("java.class.path")) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); } - /** - * Retrieves all annotation names from the current directory, and recursively descends and - * retrieves annotation names from sub-directories. - * - * @param packageName the name of the package that contains the qual package, or null - * @param rootDirectory a {@link File} object representing the root directory of a set of - * annotations, which is subtracted from class names to retrieve each class's fully - * qualified class names - * @param currentDirectory a {@link File} object representing the current sub-directory of the - * root directory - * @return a set fully qualified annotation class name, for annotations in the root directory or - * its sub-directories - */ - @SuppressWarnings("signature") // TODO: reduce use of string manipulation - private Set<@BinaryName String> getAnnotationNamesFromDirectory( - @Nullable @DotSeparatedIdentifiers String packageName, - File rootDirectory, - File currentDirectory) { - Set<@BinaryName String> results = new LinkedHashSet<>(); - - // Full path to root directory - String rootPath = rootDirectory.getAbsolutePath(); - - // check every file and directory within the current directory - File[] directoryContents = currentDirectory.listFiles(); - if (directoryContents == null) { - throw new UserError("Directory does not exist: %s", currentDirectory); - } - Arrays.sort(directoryContents, Comparator.comparing(File::getName)); - for (File file : directoryContents) { - if (file.isFile()) { - // TODO: simplify all this string manipulation. - - // Full file name, including path to file - String fullFileName = file.getAbsolutePath(); - // Simple file name - String fileName = - fullFileName.substring( - fullFileName.lastIndexOf(File.separator) + 1, - fullFileName.length()); - // Path to file - String filePath = - fullFileName.substring(0, fullFileName.lastIndexOf(File.separator)); - // Package name beginning with "qual" - String qualPackage = null; - if (!filePath.equals(rootPath)) { - qualPackage = - filePath.substring(rootPath.length() + 1, filePath.length()) - .replace(SLASH, DOT); - } - // Simple annotation name, which is the same as the file name (without directory) - // but with file extension removed. - @BinaryName String annotationName = fileName; - if (fileName.lastIndexOf(DOT) != -1) { - annotationName = fileName.substring(0, fileName.lastIndexOf(DOT)); - } - - // Fully qualified annotation class name (a @BinaryName, not a @FullyQualifiedName) - @BinaryName String fullyQualifiedAnnoName = - Signatures.addPackage( - packageName, Signatures.addPackage(qualPackage, annotationName)); - - if (fileName.endsWith(CLASS_SUFFIX)) { - // add the fully qualified annotation class name to the set - results.add(fullyQualifiedAnnoName); - } - } else if (file.isDirectory()) { - // recursively add all sub directories's fully qualified annotation class name - results.addAll(getAnnotationNamesFromDirectory(packageName, rootDirectory, file)); - } + // add all paths that are examined by the classloader + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "classloader examined paths:"); + if (classLoader != null) { + URL[] urls = classLoader.getURLs(); + for (int i = 0; i < urls.length; i++) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + urls[i].getFile()); + } + } else { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "classloader unavailable"); + } + } + + /** + * Loads the set of annotation classes in the qual directory of a checker shipped with the Checker + * Framework. + */ + private void loadBundledAnnotationClasses() { + // retrieve the fully qualified class names of the annotations + Set<@BinaryName String> annotationNames; + // see whether the resource URL has a protocol of jar or file + if (resourceURL != null && resourceURL.getProtocol().contentEquals("jar")) { + // if the checker class file is contained within a jar, then the resource URL for the + // qual directory will have the protocol "jar". This means the whole checker is loaded + // as a jar file. + + JarURLConnection connection; + // create a connection to the jar file + try { + connection = (JarURLConnection) resourceURL.openConnection(); + + // disable caching / connection sharing of the low level URLConnection to the Jar + // file + connection.setDefaultUseCaches(false); + connection.setUseCaches(false); + + // connect to the Jar file + connection.connect(); + } catch (IOException e) { + throw new BugInCF( + "AnnotationClassLoader: cannot open a connection to the Jar file " + + resourceURL.getFile()); + } + + // open up that jar file and extract annotation class names + try (JarFile jarFile = connection.getJarFile()) { + // get class names inside the jar file within the particular package + annotationNames = getBundledAnnotationNamesFromJar(jarFile); + } catch (IOException e) { + throw new BugInCF( + "AnnotationClassLoader: cannot open the Jar file " + resourceURL.getFile()); + } + } else if (resourceURL != null && resourceURL.getProtocol().contentEquals("file")) { + // If the checker class file is found within the file system itself within some + // directory (usually development build directories), then process the package as a file + // directory in the file system and load the annotations contained in the qual + // directory. + + // open up the directory + File packageDir = new File(resourceURL.getFile()); + annotationNames = getAnnotationNamesFromDirectory(packageName, packageDir, packageDir); + } else { + // We do not support a resource URL with any other protocols, so create an empty set. + annotationNames = Collections.emptySet(); + } + if (annotationNames.isEmpty()) { + PackageElement pkgEle = checker.getElementUtils().getPackageElement(packageName); + if (pkgEle != null) { + for (Element e : pkgEle.getEnclosedElements()) { + if (e.getKind() == ElementKind.ANNOTATION_TYPE) { + @SuppressWarnings("signature:assignment.type.incompatible") // Elements needs to be + // annotated. + @BinaryName String annoBinName = + checker.getElementUtils().getBinaryName((TypeElement) e).toString(); + annotationNames.add(annoBinName); + } } - - return results; + } + } + supportedBundledAnnotationClasses.addAll(loadAnnotationClasses(annotationNames)); + } + + /** + * Gets the set of annotation classes in the qual directory of a checker shipped with the Checker + * Framework. Note that the returned set from this method is mutable. This method is intended to + * be called within {@link AnnotatedTypeFactory#createSupportedTypeQualifiers() + * createSupportedTypeQualifiers()} (or its helper methods) to help define the set of supported + * qualifiers. + * + * @see AnnotatedTypeFactory#createSupportedTypeQualifiers() + * @return a mutable set of the loaded bundled annotation classes + */ + public final Set> getBundledAnnotationClasses() { + return supportedBundledAnnotationClasses; + } + + /** + * Retrieves the annotation class file names from the qual directory contained inside a jar. + * + * @param jar the JarFile containing the annotation class files + * @return a set of fully qualified class names of the annotations + */ + @SuppressWarnings("JdkObsolete") + private Set<@BinaryName String> getBundledAnnotationNamesFromJar(JarFile jar) { + Set<@BinaryName String> annos = new LinkedHashSet<>(); + + // get an enumeration iterator for all the content entries in the jar file + Enumeration jarEntries = jar.entries(); + + // enumerate through the entries + while (jarEntries.hasMoreElements()) { + JarEntry je = jarEntries.nextElement(); + // filter out directories and non-class files + if (je.isDirectory() || !je.getName().endsWith(CLASS_SUFFIX)) { + continue; + } + + String className = Signatures.classfilenameToBinaryName(je.getName()); + + // filter for qual package + if (className.startsWith(packageName + DOT)) { + // add to set + annos.add(className); + } } - /** - * Loads the class indicated by the name, and checks to see if it is an annotation that is - * supported by a checker. - * - * @param className the name of the class, in binary name format - * @param issueError set to true to issue a warning when a loaded annotation is not a type - * annotation. It is useful to set this to true if a given annotation must be a well-defined - * type annotation (eg for annotation class names given as command line arguments). It - * should be set to false if the annotation is a meta-annotation or non-type annotation. - * @return the loaded annotation class if it has a {@code @Target} meta-annotation with the - * required ElementType values, and is a supported annotation by a checker. If the - * annotation is not supported by a checker, null is returned. - */ - protected final @Nullable Class loadAnnotationClass( - @BinaryName String className, boolean issueError) { - - // load the class - Class cls = null; - try { - if (classLoader != null) { - cls = Class.forName(className, true, classLoader); - } else { - cls = Class.forName(className); - } - } catch (ClassNotFoundException e) { - throw new UserError( - checker.getClass().getSimpleName() - + ": could not load class for annotation: " - + className - + ". Ensure that it is a type annotation" - + " and your classpath is correct."); + return annos; + } + + /** + * This method takes as input the canonical name of an external annotation class and loads and + * returns that class via the class loader. This method returns null if the external annotation + * class was loaded successfully but was deemed not supported by a checker. Errors are issued if + * the external class is not an annotation, or if it could not be loaded successfully. + * + * @param annoName canonical name of an external annotation class, e.g. + * "myproject.qual.myannotation" + * @return the loaded annotation class, or null if it was not a supported annotation as decided by + * {@link #isSupportedAnnotationClass(Class)} + */ + public final @Nullable Class loadExternalAnnotationClass( + @BinaryName String annoName) { + return loadAnnotationClass(annoName, true); + } + + /** + * This method takes as input a fully qualified path to a directory, and loads and returns the set + * of all supported annotation classes from that directory. + * + * @param dirName absolute path to a directory containing annotation classes + * @return a set of annotation classes + */ + public final Set> loadExternalAnnotationClassesFromDirectory( + String dirName) { + File rootDirectory = new File(dirName); + Set<@BinaryName String> annoNames = + getAnnotationNamesFromDirectory(null, rootDirectory, rootDirectory); + return loadAnnotationClasses(annoNames); + } + + /** + * Retrieves all annotation names from the current directory, and recursively descends and + * retrieves annotation names from sub-directories. + * + * @param packageName the name of the package that contains the qual package, or null + * @param rootDirectory a {@link File} object representing the root directory of a set of + * annotations, which is subtracted from class names to retrieve each class's fully qualified + * class names + * @param currentDirectory a {@link File} object representing the current sub-directory of the + * root directory + * @return a set fully qualified annotation class name, for annotations in the root directory or + * its sub-directories + */ + @SuppressWarnings("signature") // TODO: reduce use of string manipulation + private Set<@BinaryName String> getAnnotationNamesFromDirectory( + @Nullable @DotSeparatedIdentifiers String packageName, + File rootDirectory, + File currentDirectory) { + Set<@BinaryName String> results = new LinkedHashSet<>(); + + // Full path to root directory + String rootPath = rootDirectory.getAbsolutePath(); + + // check every file and directory within the current directory + File[] directoryContents = currentDirectory.listFiles(); + if (directoryContents == null) { + throw new UserError("Directory does not exist: %s", currentDirectory); + } + Arrays.sort(directoryContents, Comparator.comparing(File::getName)); + for (File file : directoryContents) { + if (file.isFile()) { + // TODO: simplify all this string manipulation. + + // Full file name, including path to file + String fullFileName = file.getAbsolutePath(); + // Simple file name + String fileName = + fullFileName.substring( + fullFileName.lastIndexOf(File.separator) + 1, fullFileName.length()); + // Path to file + String filePath = fullFileName.substring(0, fullFileName.lastIndexOf(File.separator)); + // Package name beginning with "qual" + String qualPackage = null; + if (!filePath.equals(rootPath)) { + qualPackage = + filePath.substring(rootPath.length() + 1, filePath.length()).replace(SLASH, DOT); } - - // If the freshly loaded class is not an annotation, then issue error if required and then - // return null - if (!cls.isAnnotation()) { - if (issueError) { - throw new UserError( - checker.getClass().getSimpleName() - + ": the loaded class: " - + cls.getCanonicalName() - + " is not a type annotation."); - } - return null; + // Simple annotation name, which is the same as the file name (without directory) + // but with file extension removed. + @BinaryName String annotationName = fileName; + if (fileName.lastIndexOf(DOT) != -1) { + annotationName = fileName.substring(0, fileName.lastIndexOf(DOT)); } - Class annoClass = cls.asSubclass(Annotation.class); - // Check the loaded annotation to see if it has a @Target meta-annotation with the required - // ElementType values - if (hasWellDefinedTargetMetaAnnotation(annoClass)) { - // If so, return the loaded annotation if it is supported by a checker - return isSupportedAnnotationClass(annoClass) ? annoClass : null; - } else if (issueError) { - // issueError is set to true for loading explicitly named external annotations. - // We issue an error here when one of those annotations is not well-defined, since the - // user expects these external annotations to be loaded. - throw new UserError( - checker.getClass().getSimpleName() - + ": the loaded annotation: " - + annoClass.getCanonicalName() - + " is not a type annotation." - + " Check its @Target meta-annotation."); - } else { - // issueError is set to false for loading the qual directory or any external - // directories. - // We don't issue any errors since there may be meta-annotations or non-type annotations - // in such directories. - return null; - } - } + // Fully qualified annotation class name (a @BinaryName, not a @FullyQualifiedName) + @BinaryName String fullyQualifiedAnnoName = + Signatures.addPackage(packageName, Signatures.addPackage(qualPackage, annotationName)); - /** - * Loads a set of annotations indicated by their names. - * - * @param annoNames a set of binary names for annotation classes - * @return a set of loaded annotation classes - * @see #loadAnnotationClass(String, boolean) - */ - protected final Set> loadAnnotationClasses( - @Nullable Set<@BinaryName String> annoNames) { - Set> loadedClasses = new LinkedHashSet<>(); - - if (annoNames != null && !annoNames.isEmpty()) { - // loop through each class name & load the class - for (String annoName : annoNames) { - Class annoClass = loadAnnotationClass(annoName, false); - if (annoClass != null) { - loadedClasses.add(annoClass); - } - } + if (fileName.endsWith(CLASS_SUFFIX)) { + // add the fully qualified annotation class name to the set + results.add(fullyQualifiedAnnoName); } + } else if (file.isDirectory()) { + // recursively add all sub directories's fully qualified annotation class name + results.addAll(getAnnotationNamesFromDirectory(packageName, rootDirectory, file)); + } + } - return loadedClasses; + return results; + } + + /** + * Loads the class indicated by the name, and checks to see if it is an annotation that is + * supported by a checker. + * + * @param className the name of the class, in binary name format + * @param issueError set to true to issue a warning when a loaded annotation is not a type + * annotation. It is useful to set this to true if a given annotation must be a well-defined + * type annotation (eg for annotation class names given as command line arguments). It should + * be set to false if the annotation is a meta-annotation or non-type annotation. + * @return the loaded annotation class if it has a {@code @Target} meta-annotation with the + * required ElementType values, and is a supported annotation by a checker. If the annotation + * is not supported by a checker, null is returned. + */ + protected final @Nullable Class loadAnnotationClass( + @BinaryName String className, boolean issueError) { + + // load the class + Class cls = null; + try { + if (classLoader != null) { + cls = Class.forName(className, true, classLoader); + } else { + cls = Class.forName(className); + } + } catch (ClassNotFoundException e) { + throw new UserError( + checker.getClass().getSimpleName() + + ": could not load class for annotation: " + + className + + ". Ensure that it is a type annotation" + + " and your classpath is correct."); } - /** - * Checks to see whether a particular annotation class has the {@link Target} meta-annotation, - * and has the required {@link ElementType} values. - * - *

A subclass may override this method to load annotations that are not intended to be - * annotated in source code. E.g.: {@code SubtypingChecker} overrides this method to load {@code - * Unqualified}. - * - * @param annoClass an annotation class - * @return true if the annotation is well defined, false if it isn't - */ - protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { - return annoClass.getAnnotation(Target.class) != null - && AnnotationUtils.hasTypeQualifierElementTypes( - annoClass.getAnnotation(Target.class).value(), annoClass); + // If the freshly loaded class is not an annotation, then issue error if required and then + // return null + if (!cls.isAnnotation()) { + if (issueError) { + throw new UserError( + checker.getClass().getSimpleName() + + ": the loaded class: " + + cls.getCanonicalName() + + " is not a type annotation."); + } + return null; } - /** - * Checks to see whether a particular annotation class is supported. - * - *

By default, all loaded annotations that pass the basic checks in {@link - * #loadAnnotationClass(String, boolean)} are supported. - * - *

Individual checkers can create a subclass of AnnotationClassLoader and override this - * method to indicate whether a particular annotation is supported. - * - * @param annoClass an annotation class - * @return true if the annotation is supported, false if it isn't - */ - protected boolean isSupportedAnnotationClass(Class annoClass) { - return true; + Class annoClass = cls.asSubclass(Annotation.class); + // Check the loaded annotation to see if it has a @Target meta-annotation with the required + // ElementType values + if (hasWellDefinedTargetMetaAnnotation(annoClass)) { + // If so, return the loaded annotation if it is supported by a checker + return isSupportedAnnotationClass(annoClass) ? annoClass : null; + } else if (issueError) { + // issueError is set to true for loading explicitly named external annotations. + // We issue an error here when one of those annotations is not well-defined, since the + // user expects these external annotations to be loaded. + throw new UserError( + checker.getClass().getSimpleName() + + ": the loaded annotation: " + + annoClass.getCanonicalName() + + " is not a type annotation." + + " Check its @Target meta-annotation."); + } else { + // issueError is set to false for loading the qual directory or any external + // directories. + // We don't issue any errors since there may be meta-annotations or non-type annotations + // in such directories. + return null; } + } + + /** + * Loads a set of annotations indicated by their names. + * + * @param annoNames a set of binary names for annotation classes + * @return a set of loaded annotation classes + * @see #loadAnnotationClass(String, boolean) + */ + protected final Set> loadAnnotationClasses( + @Nullable Set<@BinaryName String> annoNames) { + Set> loadedClasses = new LinkedHashSet<>(); + + if (annoNames != null && !annoNames.isEmpty()) { + // loop through each class name & load the class + for (String annoName : annoNames) { + Class annoClass = loadAnnotationClass(annoName, false); + if (annoClass != null) { + loadedClasses.add(annoClass); + } + } + } + + return loadedClasses; + } + + /** + * Checks to see whether a particular annotation class has the {@link Target} meta-annotation, and + * has the required {@link ElementType} values. + * + *

A subclass may override this method to load annotations that are not intended to be + * annotated in source code. E.g.: {@code SubtypingChecker} overrides this method to load {@code + * Unqualified}. + * + * @param annoClass an annotation class + * @return true if the annotation is well defined, false if it isn't + */ + protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { + return annoClass.getAnnotation(Target.class) != null + && AnnotationUtils.hasTypeQualifierElementTypes( + annoClass.getAnnotation(Target.class).value(), annoClass); + } + + /** + * Checks to see whether a particular annotation class is supported. + * + *

By default, all loaded annotations that pass the basic checks in {@link + * #loadAnnotationClass(String, boolean)} are supported. + * + *

Individual checkers can create a subclass of AnnotationClassLoader and override this method + * to indicate whether a particular annotation is supported. + * + * @param annoClass an annotation class + * @return true if the annotation is supported, false if it isn't + */ + protected boolean isSupportedAnnotationClass(Class annoClass) { + return true; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java index 4674d2c679e..1bf94dcd1f1 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java @@ -1,5 +1,14 @@ package org.checkerframework.framework.type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Types; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -14,842 +23,822 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; - /** * Implements asSuper {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, AnnotatedTypeMirror, * AnnotatedTypeMirror)}. */ public class AsSuperVisitor extends AbstractAtmComboVisitor { - /** Type utilities. */ - private final Types types; - - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy. */ - private final QualifierHierarchy qualHierarchy; - - /** - * Whether or not the type being visited is an uninferred type argument. If true, then the - * underlying type may not have the correct relationship with the supertype. - */ - private boolean isUninferredTypeArgument = false; - - /** - * Create a new AsSuperVisitor. - * - * @param atypeFactory the type factory - */ - public AsSuperVisitor(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.types = atypeFactory.types; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - } - - /** - * Implements asSuper. See {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, - * AnnotatedTypeMirror, AnnotatedTypeMirror)} for details. - * - * @param the type of the supertype - * @param type type from which to copy annotations - * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java - * type. - * @return a copy of {@code superType} with annotations copied from {@code type} and type - * variables substituted from {@code type}. - */ - @SuppressWarnings({ - "unchecked", - "interning:not.interned" // optimized special case - }) - public T asSuper(AnnotatedTypeMirror type, T superType) { - if (type == null || superType == null) { - throw new BugInCF( - "AsSuperVisitor.asSuper(%s, %s): arguments cannot be null", type, superType); - } - - if (type == superType) { - return (T) type.deepCopy(); - } - - // This visitor modifies superType and may return type, so pass it copies so that the - // parameters to asSuper are not changed and a copy is returned. - AnnotatedTypeMirror copyType = type.deepCopy(); - AnnotatedTypeMirror copySuperType = superType.deepCopy(); - reset(); - AnnotatedTypeMirror result = visit(copyType, copySuperType, null); - - if (result == null) { - throw new BugInCF( - "AsSuperVisitor returned null.%ntype: %s%nsuperType: %s", type, copySuperType); - } - - return (T) result; - } - - /** Resets this. */ - private void reset() { - isUninferredTypeArgument = false; - } - - @Override - public AnnotatedTypeMirror visit( - AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { - ensurePrimaryIsCorrectForUnions(type); - return super.visit(type, superType, p); - } - - /** - * The code in this class is assuming that the primary annotation of an {@link - * AnnotatedUnionType} is the least upper bound of its alternatives. This method makes this - * assumption true. - * - * @param type any kind of {@code AnnotatedTypeMirror} - */ - private void ensurePrimaryIsCorrectForUnions(AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.UNION) { - AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) type; - AnnotationMirrorSet lubs = null; - for (AnnotatedDeclaredType altern : annotatedUnionType.getAlternatives()) { - if (lubs == null) { - lubs = altern.getAnnotations(); - } else { - TypeMirror typeMirror = type.getUnderlyingType(); - AnnotationMirrorSet newLubs = new AnnotationMirrorSet(); - for (AnnotationMirror lub : lubs) { - AnnotationMirror anno = altern.getAnnotationInHierarchy(lub); - newLubs.add( - qualHierarchy.leastUpperBoundShallow( - anno, altern.getUnderlyingType(), lub, typeMirror)); - } - lubs = newLubs; - } - } - type.replaceAnnotations(lubs); - } - } - - private AnnotatedTypeMirror errorTypeNotErasedSubtypeOfSuperType( - AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { - if (TypesUtils.isString(superType.getUnderlyingType())) { - // Any type can be converted to String - return visit(atypeFactory.getStringType(type), superType, p); - } - if (isUninferredTypeArgument) { - return copyPrimaryAnnos(type, superType); - } - throw new BugInCF( - "AsSuperVisitor: type is not an erased subtype of supertype." - + "%ntype: %s%nsuperType: %s", - type, superType); - } - - private AnnotatedTypeMirror copyPrimaryAnnos(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - // There may have been annotations added by a recursive call to asSuper, so replace existing - // annotations - to.replaceAnnotations(new ArrayList<>(from.getAnnotations())); - // if to is a Typevar or Wildcard, then replaceAnnotations also sets primary annotations on - // the bounds to from.getAnnotations() - - if (to.getKind() == TypeKind.UNION) { - // Make sure that the alternatives have a primary annotations - // Alternatives cannot have type arguments, so asSuper isn't called recursively - AnnotatedUnionType unionType = (AnnotatedUnionType) to; - for (AnnotatedDeclaredType altern : unionType.getAlternatives()) { - altern.addMissingAnnotations(unionType.getAnnotations()); - } - } - return to; - } - - /** - * A helper method for asSuper(AMT, Wildcard) methods to use to annotate the wildcard's lower - * bound. - * - *

If the lower bound of superType is Null, then return copyPrimarayAnnos(type, superType) - * - *

otherwise, return asSuper(type, superType.getLowerBound() - * - *

An error is issued if type is a Primitive or Wildcard -- those case are handled in - * asSuper(Primitive, Wildcard) and asSuper(Wildcard, Wildcard) - * - *

An error is issued if the lower bound of superType is not Null and type is not a subtype - * of the lower bound. - */ - private AnnotatedTypeMirror asSuperWildcardLowerBound( - AnnotatedTypeMirror type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror lowerBound = superType.getSuperBound(); - return asSuperLowerBound(type, p, lowerBound); - } - - /** Same as #asSuperWildcardLowerBound, but for Typevars. */ - private AnnotatedTypeMirror asSuperTypevarLowerBound( - AnnotatedTypeMirror type, AnnotatedTypeVariable superType, Void p) { - AnnotatedTypeMirror lowerBound = superType.getLowerBound(); - return asSuperLowerBound(type, p, lowerBound); - } - - private AnnotatedTypeMirror asSuperLowerBound( - AnnotatedTypeMirror type, Void p, AnnotatedTypeMirror lowerBound) { - if (lowerBound.getKind() == TypeKind.NULL) { - AnnotationMirrorSet typeLowerBound = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type); - lowerBound.replaceAnnotations(typeLowerBound); - return lowerBound; - } - if (areErasedJavaTypesEquivalent(type, lowerBound)) { - return visit(type, lowerBound, p); - } - // If type and lowerBound are not the same type, then lowerBound is a subtype of type, - // but there is no way to convert type to a subtype -- there is not an asSub method. So, - // just copy the primary annotations. - return copyPrimaryAnnos(type, lowerBound); - } - - /** - * Returns true if the underlying, erased Java type of {@code subtype} is a subtype of the - * underlying, erased Java type of {@code supertype}. - * - * @param subtype a type - * @param supertype a type - * @return true if the underlying, erased Java type of {@code subtype} is a subtype of the - * underlying, erased Java type of {@code supertype} - */ - private boolean isErasedJavaSubtype( - AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype) { - TypeMirror javaSubtype = types.erasure(subtype.getUnderlyingType()); - TypeMirror javaSupertype = types.erasure(supertype.getUnderlyingType()); - return types.isSubtype(javaSubtype, javaSupertype); - } - - /** - * Returns true if the underlying, erased Java type of {@code typeA} and {@code typeB} are - * equivalent. - * - * @param typeA a type - * @param typeB a type - * @return true if the underlying, erased Java type of {@code typeA} and {@code typeB} are - * equivalent - */ - private boolean areErasedJavaTypesEquivalent( - AnnotatedTypeMirror typeA, AnnotatedTypeMirror typeB) { - TypeMirror underlyingTypeA = types.erasure(typeA.getUnderlyingType()); - TypeMirror underlyingTypeB = types.erasure(typeB.getUnderlyingType()); - return types.isSameType(underlyingTypeA, underlyingTypeB); - } - - // - @Override - public AnnotatedTypeMirror visitArray_Array( - AnnotatedArrayType type, AnnotatedArrayType superType, Void p) { - AnnotatedTypeMirror asSuperCT = - visit(type.getComponentType(), superType.getComponentType(), p); - superType.setComponentType(asSuperCT); - return copyPrimaryAnnos(type, superType); - } - - /** The fully-qualified names of java.lang.Cloneable and java.io.Serializable. */ - private static List cloneableOrSerializable = - Arrays.asList("java.lang.Cloneable", "java.io.Serializable"); - - @Override - public AnnotatedTypeMirror visitArray_Intersection( - AnnotatedArrayType type, AnnotatedIntersectionType superType, Void p) { - for (AnnotatedTypeMirror bounds : superType.getBounds()) { - if (!(TypesUtils.isObject(bounds.getUnderlyingType()) - || TypesUtils.isDeclaredOfName( - bounds.getUnderlyingType(), cloneableOrSerializable))) { - return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - copyPrimaryAnnos(type, bounds); - } - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitArray_Declared( - AnnotatedArrayType type, AnnotatedDeclaredType superType, Void p) { - - TypeElement array = TypesUtils.getTypeElement(type.getUnderlyingType()); - TypeElement possibleArray = TypesUtils.getTypeElement(superType.getUnderlyingType()); - // If the TypeElements of type and superType are equal, then superType's underlyingType is - // Array.class. Array.class is the receiver of methods such as clone() of which an array - // can be the receiver. (new int[].clone()) - boolean isArrayClass = array.equals(possibleArray); - - if (isArrayClass - || TypesUtils.isObject(superType.getUnderlyingType()) - || TypesUtils.isDeclaredOfName( - superType.getUnderlyingType(), cloneableOrSerializable)) { - return copyPrimaryAnnos(type, superType); - } - return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitArray_Typevar( - AnnotatedArrayType type, AnnotatedTypeVariable superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); - superType.setLowerBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitArray_Wildcard( - AnnotatedArrayType type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - // - @Override - public AnnotatedTypeMirror visitDeclared_Declared( - AnnotatedDeclaredType type, AnnotatedDeclaredType superType, Void p) { - if (areErasedJavaTypesEquivalent(type, superType)) { - return type; - } - - // Not same erased Java type. - // Walk up the directSupertypes. - // directSupertypes() annotates type variables correctly and handles substitution. - for (AnnotatedDeclaredType dst : type.directSupertypes()) { - if (isErasedJavaSubtype(dst, superType)) { - // If two direct supertypes of type, dst1 and dst2, are subtypes of superType then - // asSuper(dst1, superType) and asSuper(dst2, superType) return equivalent ATMs, so - // return the first one found. - return visit(dst, superType, p); - } - } - - return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Intersection( - AnnotatedDeclaredType type, AnnotatedIntersectionType superType, Void p) { - List newBounds = new ArrayList<>(); - // Each type in the intersection must be a supertype of type, so call asSuper on all types - // in the intersection. - for (AnnotatedTypeMirror superBound : superType.getBounds()) { - if (types.isSubtype(type.getUnderlyingType(), superBound.getUnderlyingType())) { - AnnotatedTypeMirror found = visit(type, superBound, p); - newBounds.add(found); - } - } - // The ATM for each type in an intersection is stored in the direct super types field. - superType.setBounds(newBounds); - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Primitive( - AnnotatedDeclaredType type, AnnotatedPrimitiveType superType, Void p) { - if (!TypesUtils.isBoxedPrimitive(type.getUnderlyingType())) { - throw new BugInCF("AsSuperVisitor Declared_Primitive: type is not a boxed primitive."); - } - AnnotatedTypeMirror unboxedType = atypeFactory.getUnboxedType(type); - return copyPrimaryAnnos(unboxedType, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Typevar( - AnnotatedDeclaredType type, AnnotatedTypeVariable superType, Void p) { - // setUpperBound() may have a side effect on parameter "type" when the upper bound of - // "superType" equals to "type" (referencing the same object: changes will be shared) - // copy before visiting to avoid - // without fix, this would fail: - // https://github.com/typetools/checker-framework/blob/ed340b2dfa1e51bbc0a7313f22638179d15bf2df/checker/tests/nullness/Issue2432b.java - AnnotatedTypeMirror typeCopy = type.deepCopy(); - AnnotatedTypeMirror upperBound = visit(typeCopy, superType.getUpperBound(), p).asUse(); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p).asUse(); - superType.setLowerBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Union( - AnnotatedDeclaredType type, AnnotatedUnionType superType, Void p) { - // Alternatives in a union type can't have type args, so just copy the primary annotation - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Wildcard( - AnnotatedDeclaredType type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p).asUse(); - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p).asUse(); - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - // - - @Override - public AnnotatedTypeMirror visitIntersection_Declared( - AnnotatedIntersectionType type, AnnotatedDeclaredType superType, Void p) { - for (AnnotatedTypeMirror bound : type.getBounds()) { - // Find the directSuperType that is a subtype of superType, then recur on that type so - // that type arguments in superType are annotated correctly. - if (bound.getKind() == TypeKind.DECLARED - && isErasedJavaSubtype((AnnotatedDeclaredType) bound, superType)) { - AnnotatedTypeMirror asSuper = visit(bound, superType, p); - - // The directSuperType might have a primary annotation that is a supertype of - // primary annotation on type. Copy the primary annotation, because it is more - // precise. - return copyPrimaryAnnos(type, asSuper); - } + /** Type utilities. */ + private final Types types; + + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** The qualifier hierarchy. */ + private final QualifierHierarchy qualHierarchy; + + /** + * Whether or not the type being visited is an uninferred type argument. If true, then the + * underlying type may not have the correct relationship with the supertype. + */ + private boolean isUninferredTypeArgument = false; + + /** + * Create a new AsSuperVisitor. + * + * @param atypeFactory the type factory + */ + public AsSuperVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.types = atypeFactory.types; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + } + + /** + * Implements asSuper. See {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, + * AnnotatedTypeMirror, AnnotatedTypeMirror)} for details. + * + * @param the type of the supertype + * @param type type from which to copy annotations + * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java + * type. + * @return a copy of {@code superType} with annotations copied from {@code type} and type + * variables substituted from {@code type}. + */ + @SuppressWarnings({ + "unchecked", + "interning:not.interned" // optimized special case + }) + public T asSuper(AnnotatedTypeMirror type, T superType) { + if (type == null || superType == null) { + throw new BugInCF( + "AsSuperVisitor.asSuper(%s, %s): arguments cannot be null", type, superType); + } + + if (type == superType) { + return (T) type.deepCopy(); + } + + // This visitor modifies superType and may return type, so pass it copies so that the + // parameters to asSuper are not changed and a copy is returned. + AnnotatedTypeMirror copyType = type.deepCopy(); + AnnotatedTypeMirror copySuperType = superType.deepCopy(); + reset(); + AnnotatedTypeMirror result = visit(copyType, copySuperType, null); + + if (result == null) { + throw new BugInCF( + "AsSuperVisitor returned null.%ntype: %s%nsuperType: %s", type, copySuperType); + } + + return (T) result; + } + + /** Resets this. */ + private void reset() { + isUninferredTypeArgument = false; + } + + @Override + public AnnotatedTypeMirror visit( + AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { + ensurePrimaryIsCorrectForUnions(type); + return super.visit(type, superType, p); + } + + /** + * The code in this class is assuming that the primary annotation of an {@link AnnotatedUnionType} + * is the least upper bound of its alternatives. This method makes this assumption true. + * + * @param type any kind of {@code AnnotatedTypeMirror} + */ + private void ensurePrimaryIsCorrectForUnions(AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.UNION) { + AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) type; + AnnotationMirrorSet lubs = null; + for (AnnotatedDeclaredType altern : annotatedUnionType.getAlternatives()) { + if (lubs == null) { + lubs = altern.getAnnotations(); + } else { + TypeMirror typeMirror = type.getUnderlyingType(); + AnnotationMirrorSet newLubs = new AnnotationMirrorSet(); + for (AnnotationMirror lub : lubs) { + AnnotationMirror anno = altern.getAnnotationInHierarchy(lub); + newLubs.add( + qualHierarchy.leastUpperBoundShallow( + anno, altern.getUnderlyingType(), lub, typeMirror)); + } + lubs = newLubs; } + } + type.replaceAnnotations(lubs); + } + } + + private AnnotatedTypeMirror errorTypeNotErasedSubtypeOfSuperType( + AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { + if (TypesUtils.isString(superType.getUnderlyingType())) { + // Any type can be converted to String + return visit(atypeFactory.getStringType(type), superType, p); + } + if (isUninferredTypeArgument) { + return copyPrimaryAnnos(type, superType); + } + throw new BugInCF( + "AsSuperVisitor: type is not an erased subtype of supertype." + "%ntype: %s%nsuperType: %s", + type, superType); + } + + private AnnotatedTypeMirror copyPrimaryAnnos(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + // There may have been annotations added by a recursive call to asSuper, so replace existing + // annotations + to.replaceAnnotations(new ArrayList<>(from.getAnnotations())); + // if to is a Typevar or Wildcard, then replaceAnnotations also sets primary annotations on + // the bounds to from.getAnnotations() + + if (to.getKind() == TypeKind.UNION) { + // Make sure that the alternatives have a primary annotations + // Alternatives cannot have type arguments, so asSuper isn't called recursively + AnnotatedUnionType unionType = (AnnotatedUnionType) to; + for (AnnotatedDeclaredType altern : unionType.getAlternatives()) { + altern.addMissingAnnotations(unionType.getAnnotations()); + } + } + return to; + } + + /** + * A helper method for asSuper(AMT, Wildcard) methods to use to annotate the wildcard's lower + * bound. + * + *

If the lower bound of superType is Null, then return copyPrimarayAnnos(type, superType) + * + *

otherwise, return asSuper(type, superType.getLowerBound() + * + *

An error is issued if type is a Primitive or Wildcard -- those case are handled in + * asSuper(Primitive, Wildcard) and asSuper(Wildcard, Wildcard) + * + *

An error is issued if the lower bound of superType is not Null and type is not a subtype of + * the lower bound. + */ + private AnnotatedTypeMirror asSuperWildcardLowerBound( + AnnotatedTypeMirror type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror lowerBound = superType.getSuperBound(); + return asSuperLowerBound(type, p, lowerBound); + } + + /** Same as #asSuperWildcardLowerBound, but for Typevars. */ + private AnnotatedTypeMirror asSuperTypevarLowerBound( + AnnotatedTypeMirror type, AnnotatedTypeVariable superType, Void p) { + AnnotatedTypeMirror lowerBound = superType.getLowerBound(); + return asSuperLowerBound(type, p, lowerBound); + } + + private AnnotatedTypeMirror asSuperLowerBound( + AnnotatedTypeMirror type, Void p, AnnotatedTypeMirror lowerBound) { + if (lowerBound.getKind() == TypeKind.NULL) { + AnnotationMirrorSet typeLowerBound = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type); + lowerBound.replaceAnnotations(typeLowerBound); + return lowerBound; + } + if (areErasedJavaTypesEquivalent(type, lowerBound)) { + return visit(type, lowerBound, p); + } + // If type and lowerBound are not the same type, then lowerBound is a subtype of type, + // but there is no way to convert type to a subtype -- there is not an asSub method. So, + // just copy the primary annotations. + return copyPrimaryAnnos(type, lowerBound); + } + + /** + * Returns true if the underlying, erased Java type of {@code subtype} is a subtype of the + * underlying, erased Java type of {@code supertype}. + * + * @param subtype a type + * @param supertype a type + * @return true if the underlying, erased Java type of {@code subtype} is a subtype of the + * underlying, erased Java type of {@code supertype} + */ + private boolean isErasedJavaSubtype( + AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype) { + TypeMirror javaSubtype = types.erasure(subtype.getUnderlyingType()); + TypeMirror javaSupertype = types.erasure(supertype.getUnderlyingType()); + return types.isSubtype(javaSubtype, javaSupertype); + } + + /** + * Returns true if the underlying, erased Java type of {@code typeA} and {@code typeB} are + * equivalent. + * + * @param typeA a type + * @param typeB a type + * @return true if the underlying, erased Java type of {@code typeA} and {@code typeB} are + * equivalent + */ + private boolean areErasedJavaTypesEquivalent( + AnnotatedTypeMirror typeA, AnnotatedTypeMirror typeB) { + TypeMirror underlyingTypeA = types.erasure(typeA.getUnderlyingType()); + TypeMirror underlyingTypeB = types.erasure(typeB.getUnderlyingType()); + return types.isSameType(underlyingTypeA, underlyingTypeB); + } + + // + @Override + public AnnotatedTypeMirror visitArray_Array( + AnnotatedArrayType type, AnnotatedArrayType superType, Void p) { + AnnotatedTypeMirror asSuperCT = visit(type.getComponentType(), superType.getComponentType(), p); + superType.setComponentType(asSuperCT); + return copyPrimaryAnnos(type, superType); + } + + /** The fully-qualified names of java.lang.Cloneable and java.io.Serializable. */ + private static List cloneableOrSerializable = + Arrays.asList("java.lang.Cloneable", "java.io.Serializable"); + + @Override + public AnnotatedTypeMirror visitArray_Intersection( + AnnotatedArrayType type, AnnotatedIntersectionType superType, Void p) { + for (AnnotatedTypeMirror bounds : superType.getBounds()) { + if (!(TypesUtils.isObject(bounds.getUnderlyingType()) + || TypesUtils.isDeclaredOfName(bounds.getUnderlyingType(), cloneableOrSerializable))) { return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Intersection( - AnnotatedIntersectionType type, AnnotatedIntersectionType superType, Void p) { - List newDirectSupertypes = new ArrayList<>(); - for (AnnotatedTypeMirror superBound : superType.getBounds()) { - AnnotatedTypeMirror found = null; - TypeMirror javaSupertype = types.erasure(superBound.getUnderlyingType()); - for (AnnotatedTypeMirror bound : type.getBounds()) { - TypeMirror javaSubtype = types.erasure(bound.getUnderlyingType()); - if (types.isSubtype(javaSubtype, javaSupertype)) { - found = visit(bound, superBound, p); - newDirectSupertypes.add(found); - break; - } - } - if (found == null) { - throw new BugInCF( - "AsSuperVisitor visitIntersection_Intersection:%ntype: %s superType: %s", - type, superType); - } - } - superType.setBounds(newDirectSupertypes); - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Primitive( - AnnotatedIntersectionType type, AnnotatedPrimitiveType superType, Void p) { - for (AnnotatedTypeMirror bound : type.getBounds()) { - // Find the directSuperType that is a subtype of superType, then recur on that type - // so that type arguments in superType are annotated correctly - if (TypesUtils.isBoxedPrimitive(bound.getUnderlyingType())) { - AnnotatedTypeMirror asSuper = visit(bound, superType, p); - - // The directSuperType might have a primary annotation that is a supertype of - // primary annotation on type. Copy the primary annotation, because it is more - // precise. - return copyPrimaryAnnos(type, asSuper); - } - } - // Cannot happen: one of the types in the intersection must be a subtype of superType. - throw new BugInCF( - "AsSuperVisitor visitIntersection_Primitive:%ntype: %s superType: %s", - type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Typevar( - AnnotatedIntersectionType type, AnnotatedTypeVariable superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); - superType.setLowerBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Union( - AnnotatedIntersectionType type, AnnotatedUnionType superType, Void p) { - TypeMirror javaSupertype = types.erasure(type.getUnderlyingType()); - for (AnnotatedTypeMirror bound : type.getBounds()) { - TypeMirror javaSubtype = types.erasure(superType.getUnderlyingType()); - if (types.isSubtype(javaSubtype, javaSupertype)) { - AnnotatedTypeMirror asSuper = visit(bound, superType, p); - return copyPrimaryAnnos(type, asSuper); - } - } - // Cannot happen: one of the types in the intersection must be a subtype of superType. - throw new BugInCF( - "AsSuperVisitor visitIntersection_Union:%ntype: %s%nsuperType: %s", - type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Wildcard( - AnnotatedIntersectionType type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - // - - @Override - public AnnotatedTypeMirror visitPrimitive_Primitive( - AnnotatedPrimitiveType type, AnnotatedPrimitiveType superType, Void p) { - return copyPrimaryAnnos(type, superType); - } - - /** - * A helper method for visiting a primitive and a non-primitive. - * - * @param type a primitive type - * @param superType some other type - * @param p ignore - * @return {@code type}, viewed as a {@code superType} - */ - private AnnotatedTypeMirror visitPrimitive_Other( - AnnotatedPrimitiveType type, AnnotatedTypeMirror superType, Void p) { - return visit(atypeFactory.getBoxedType(type), superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Declared( - AnnotatedPrimitiveType type, AnnotatedDeclaredType superType, Void p) { - if (TypesUtils.isBoxedPrimitive(superType.getUnderlyingType())) { - TypeMirror unboxedSuper = types.unboxedType(superType.getUnderlyingType()); - if (unboxedSuper.getKind() != type.getKind() - && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { - AnnotatedPrimitiveType narrowedType = - atypeFactory.getNarrowedPrimitive(type, unboxedSuper); - return visit(narrowedType, superType, p); - } - } - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Intersection( - AnnotatedPrimitiveType type, AnnotatedIntersectionType superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Typevar( - AnnotatedPrimitiveType type, AnnotatedTypeVariable superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Union( - AnnotatedPrimitiveType type, AnnotatedUnionType superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Wildcard( - AnnotatedPrimitiveType type, AnnotatedWildcardType superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - // - - // - private AnnotatedTypeMirror visitTypevar_NotTypevarNorWildcard( - AnnotatedTypeVariable type, AnnotatedTypeMirror superType, Void p) { - AnnotatedTypeMirror asSuper = visit(type.getUpperBound(), superType, p); + } + copyPrimaryAnnos(type, bounds); + } + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitArray_Declared( + AnnotatedArrayType type, AnnotatedDeclaredType superType, Void p) { + + TypeElement array = TypesUtils.getTypeElement(type.getUnderlyingType()); + TypeElement possibleArray = TypesUtils.getTypeElement(superType.getUnderlyingType()); + // If the TypeElements of type and superType are equal, then superType's underlyingType is + // Array.class. Array.class is the receiver of methods such as clone() of which an array + // can be the receiver. (new int[].clone()) + boolean isArrayClass = array.equals(possibleArray); + + if (isArrayClass + || TypesUtils.isObject(superType.getUnderlyingType()) + || TypesUtils.isDeclaredOfName(superType.getUnderlyingType(), cloneableOrSerializable)) { + return copyPrimaryAnnos(type, superType); + } + return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitArray_Typevar( + AnnotatedArrayType type, AnnotatedTypeVariable superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); + superType.setLowerBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitArray_Wildcard( + AnnotatedArrayType type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + // + @Override + public AnnotatedTypeMirror visitDeclared_Declared( + AnnotatedDeclaredType type, AnnotatedDeclaredType superType, Void p) { + if (areErasedJavaTypesEquivalent(type, superType)) { + return type; + } + + // Not same erased Java type. + // Walk up the directSupertypes. + // directSupertypes() annotates type variables correctly and handles substitution. + for (AnnotatedDeclaredType dst : type.directSupertypes()) { + if (isErasedJavaSubtype(dst, superType)) { + // If two direct supertypes of type, dst1 and dst2, are subtypes of superType then + // asSuper(dst1, superType) and asSuper(dst2, superType) return equivalent ATMs, so + // return the first one found. + return visit(dst, superType, p); + } + } + + return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Intersection( + AnnotatedDeclaredType type, AnnotatedIntersectionType superType, Void p) { + List newBounds = new ArrayList<>(); + // Each type in the intersection must be a supertype of type, so call asSuper on all types + // in the intersection. + for (AnnotatedTypeMirror superBound : superType.getBounds()) { + if (types.isSubtype(type.getUnderlyingType(), superBound.getUnderlyingType())) { + AnnotatedTypeMirror found = visit(type, superBound, p); + newBounds.add(found); + } + } + // The ATM for each type in an intersection is stored in the direct super types field. + superType.setBounds(newBounds); + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Primitive( + AnnotatedDeclaredType type, AnnotatedPrimitiveType superType, Void p) { + if (!TypesUtils.isBoxedPrimitive(type.getUnderlyingType())) { + throw new BugInCF("AsSuperVisitor Declared_Primitive: type is not a boxed primitive."); + } + AnnotatedTypeMirror unboxedType = atypeFactory.getUnboxedType(type); + return copyPrimaryAnnos(unboxedType, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Typevar( + AnnotatedDeclaredType type, AnnotatedTypeVariable superType, Void p) { + // setUpperBound() may have a side effect on parameter "type" when the upper bound of + // "superType" equals to "type" (referencing the same object: changes will be shared) + // copy before visiting to avoid + // without fix, this would fail: + // https://github.com/typetools/checker-framework/blob/ed340b2dfa1e51bbc0a7313f22638179d15bf2df/checker/tests/nullness/Issue2432b.java + AnnotatedTypeMirror typeCopy = type.deepCopy(); + AnnotatedTypeMirror upperBound = visit(typeCopy, superType.getUpperBound(), p).asUse(); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p).asUse(); + superType.setLowerBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Union( + AnnotatedDeclaredType type, AnnotatedUnionType superType, Void p) { + // Alternatives in a union type can't have type args, so just copy the primary annotation + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Wildcard( + AnnotatedDeclaredType type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p).asUse(); + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p).asUse(); + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + // + + @Override + public AnnotatedTypeMirror visitIntersection_Declared( + AnnotatedIntersectionType type, AnnotatedDeclaredType superType, Void p) { + for (AnnotatedTypeMirror bound : type.getBounds()) { + // Find the directSuperType that is a subtype of superType, then recur on that type so + // that type arguments in superType are annotated correctly. + if (bound.getKind() == TypeKind.DECLARED + && isErasedJavaSubtype((AnnotatedDeclaredType) bound, superType)) { + AnnotatedTypeMirror asSuper = visit(bound, superType, p); + + // The directSuperType might have a primary annotation that is a supertype of + // primary annotation on type. Copy the primary annotation, because it is more + // precise. return copyPrimaryAnnos(type, asSuper); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Declared( - AnnotatedTypeVariable type, AnnotatedDeclaredType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Intersection( - AnnotatedTypeVariable type, AnnotatedIntersectionType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Primitive( - AnnotatedTypeVariable type, AnnotatedPrimitiveType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Typevar( - AnnotatedTypeVariable type, AnnotatedTypeVariable superType, Void p) { - // Clear the superType annotations and copy over the primary annotations before computing - // bounds, so that the superType annotations don't override the type annotations on the - // bounds. - superType.clearAnnotations(); - copyPrimaryAnnos(type, superType); - - AnnotatedTypeMirror upperBound = visit(type.getUpperBound(), superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound; - if (type.getLowerBound().getKind() == TypeKind.NULL - && superType.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getLowerBound()); - } else if (type.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getLowerBound(), p); - } else { - lowerBound = asSuperTypevarLowerBound(type.getLowerBound(), superType, p); + } + } + return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Intersection( + AnnotatedIntersectionType type, AnnotatedIntersectionType superType, Void p) { + List newDirectSupertypes = new ArrayList<>(); + for (AnnotatedTypeMirror superBound : superType.getBounds()) { + AnnotatedTypeMirror found = null; + TypeMirror javaSupertype = types.erasure(superBound.getUnderlyingType()); + for (AnnotatedTypeMirror bound : type.getBounds()) { + TypeMirror javaSubtype = types.erasure(bound.getUnderlyingType()); + if (types.isSubtype(javaSubtype, javaSupertype)) { + found = visit(bound, superBound, p); + newDirectSupertypes.add(found); + break; } - superType.setLowerBound(lowerBound); - - return superType; - } - - @Override - public AnnotatedTypeMirror visitTypevar_Union( - AnnotatedTypeVariable type, AnnotatedUnionType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Wildcard( - AnnotatedTypeVariable type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound; - if (superType.getExtendsBound().getUnderlyingType().getKind() == TypeKind.TYPEVAR - && TypesUtils.areSame( - type.getUnderlyingType(), - (TypeVariable) superType.getExtendsBound().getUnderlyingType())) { - upperBound = visit(type, superType.getExtendsBound(), p); - } else { - upperBound = visit(type.getUpperBound(), superType.getExtendsBound(), p); - } - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound; - if (type.getLowerBound().getKind() == TypeKind.NULL - && superType.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getSuperBound()); - } else if (type.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getSuperBound(), p); - } else { - lowerBound = asSuperWildcardLowerBound(type.getLowerBound(), superType, p); - } - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - /* The primary annotation on a union type is the LUB of the primary annotations on its alternatives. #ensurePrimaryIsCorrectForUnions ensures that this is the case. - - All the alternatives in a union type must be subtype of Throwable and cannot have type arguments; - however, a union type can be a subtype of an interface with a type argument. For example: - interface Interface{} - class MyException1 extends Throwable implements Interface{} - class MyException2 extends Throwable implements Interface{} - - MyException1 <: MyException1 | MyException2 <: Interface - MyException1 | MyException2 <: Throwable & Interface - */ - // - - private AnnotatedTypeMirror visitUnion_Other( - AnnotatedUnionType type, AnnotatedTypeMirror superType, Void p) { - // asSuper on any of the alternatives is the same, so just use the first one. - AnnotatedTypeMirror asSuper = visit(type.getAlternatives().get(0), superType, p); + } + if (found == null) { + throw new BugInCF( + "AsSuperVisitor visitIntersection_Intersection:%ntype: %s superType: %s", + type, superType); + } + } + superType.setBounds(newDirectSupertypes); + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Primitive( + AnnotatedIntersectionType type, AnnotatedPrimitiveType superType, Void p) { + for (AnnotatedTypeMirror bound : type.getBounds()) { + // Find the directSuperType that is a subtype of superType, then recur on that type + // so that type arguments in superType are annotated correctly + if (TypesUtils.isBoxedPrimitive(bound.getUnderlyingType())) { + AnnotatedTypeMirror asSuper = visit(bound, superType, p); + + // The directSuperType might have a primary annotation that is a supertype of + // primary annotation on type. Copy the primary annotation, because it is more + // precise. return copyPrimaryAnnos(type, asSuper); - } - - @Override - public AnnotatedTypeMirror visitUnion_Declared( - AnnotatedUnionType type, AnnotatedDeclaredType superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitUnion_Intersection( - AnnotatedUnionType type, AnnotatedIntersectionType superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitUnion_Typevar( - AnnotatedUnionType type, AnnotatedTypeVariable superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitUnion_Union( - AnnotatedUnionType type, AnnotatedUnionType superType, Void p) { - for (AnnotatedTypeMirror superAltern : superType.getAlternatives()) { - copyPrimaryAnnos(type, superAltern); - } - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitUnion_Wildcard( - AnnotatedUnionType type, AnnotatedWildcardType superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - // - - // - - private AnnotatedTypeMirror visitWildcard_NotTypvarNorWildcard( - AnnotatedWildcardType type, AnnotatedTypeMirror superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; - if (type.isUninferredTypeArgument()) { - isUninferredTypeArgument = true; - } - AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, p); - isUninferredTypeArgument = oldIsUninferredTypeArgument; - atypeFactory.addDefaultAnnotations(superType); - + } + } + // Cannot happen: one of the types in the intersection must be a subtype of superType. + throw new BugInCF( + "AsSuperVisitor visitIntersection_Primitive:%ntype: %s superType: %s", type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Typevar( + AnnotatedIntersectionType type, AnnotatedTypeVariable superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); + superType.setLowerBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Union( + AnnotatedIntersectionType type, AnnotatedUnionType superType, Void p) { + TypeMirror javaSupertype = types.erasure(type.getUnderlyingType()); + for (AnnotatedTypeMirror bound : type.getBounds()) { + TypeMirror javaSubtype = types.erasure(superType.getUnderlyingType()); + if (types.isSubtype(javaSubtype, javaSupertype)) { + AnnotatedTypeMirror asSuper = visit(bound, superType, p); return copyPrimaryAnnos(type, asSuper); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Array( - AnnotatedWildcardType type, AnnotatedArrayType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Declared( - AnnotatedWildcardType type, AnnotatedDeclaredType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Intersection( - AnnotatedWildcardType type, AnnotatedIntersectionType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Primitive( - AnnotatedWildcardType type, AnnotatedPrimitiveType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Typevar( - AnnotatedWildcardType type, AnnotatedTypeVariable superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; - if (type.isUninferredTypeArgument()) { - isUninferredTypeArgument = true; - } - AnnotatedTypeMirror upperBound = - visit(type.getExtendsBound(), superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound; - if (type.getSuperBound().getKind() == TypeKind.NULL - && superType.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getLowerBound()); - } else if (type.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getLowerBound(), p); - } else { - lowerBound = asSuperTypevarLowerBound(type.getSuperBound(), superType, p); - } - superType.setLowerBound(lowerBound); - isUninferredTypeArgument = oldIsUninferredTypeArgument; - atypeFactory.addDefaultAnnotations(superType); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Union( - AnnotatedWildcardType type, AnnotatedUnionType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Wildcard( - AnnotatedWildcardType type, AnnotatedWildcardType superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; - if (type.isUninferredTypeArgument()) { - isUninferredTypeArgument = true; - superType.setUninferredTypeArgument(); - } - if (types.isSubtype( - type.getExtendsBound().getUnderlyingType(), - superType.getExtendsBound().getUnderlyingType())) { - AnnotatedTypeMirror upperBound = - visit(type.getExtendsBound(), superType.getExtendsBound(), p); - superType.setExtendsBound(upperBound); - } else { - // The upper bound of a wildcard can be a super type of upper bound of the type - // parameter for which it is an argument. - // See org.checkerframework.framework.type.AnnotatedTypeFactory.widenToUpperBound for an - // example. In these cases, the upper bound of type might be a super type of the - // upper bound of superType. - - // The underlying type of the annotated type mirror returned by asSuper must be the - // same as the passed type, so just copy the primary annotations. - copyPrimaryAnnos(type.getExtendsBound(), superType.getExtendsBound()); - - // Add defaults in case any locations are missing annotations. - atypeFactory.addDefaultAnnotations(superType.getExtendsBound()); - } - - AnnotatedTypeMirror lowerBound; - if (type.getSuperBound().getKind() == TypeKind.NULL - && superType.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getSuperBound()); - } else if (type.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getSuperBound(), p); - } else { - lowerBound = asSuperWildcardLowerBound(type.getSuperBound(), superType, p); - } - superType.setSuperBound(lowerBound); - isUninferredTypeArgument = oldIsUninferredTypeArgument; - atypeFactory.addDefaultAnnotations(superType); - - return copyPrimaryAnnos(type, superType); - } - - /** - * Returns true if the atypeFactory for this is the given value. - * - * @param atypeFactory a factory to compare to that of this - * @return true if the atypeFactory for this is the given value - */ - public boolean sameAnnotatedTypeFactory(@FindDistinct AnnotatedTypeFactory atypeFactory) { - return this.atypeFactory == atypeFactory; - } - // + } + } + // Cannot happen: one of the types in the intersection must be a subtype of superType. + throw new BugInCF( + "AsSuperVisitor visitIntersection_Union:%ntype: %s%nsuperType: %s", type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Wildcard( + AnnotatedIntersectionType type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + // + + @Override + public AnnotatedTypeMirror visitPrimitive_Primitive( + AnnotatedPrimitiveType type, AnnotatedPrimitiveType superType, Void p) { + return copyPrimaryAnnos(type, superType); + } + + /** + * A helper method for visiting a primitive and a non-primitive. + * + * @param type a primitive type + * @param superType some other type + * @param p ignore + * @return {@code type}, viewed as a {@code superType} + */ + private AnnotatedTypeMirror visitPrimitive_Other( + AnnotatedPrimitiveType type, AnnotatedTypeMirror superType, Void p) { + return visit(atypeFactory.getBoxedType(type), superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Declared( + AnnotatedPrimitiveType type, AnnotatedDeclaredType superType, Void p) { + if (TypesUtils.isBoxedPrimitive(superType.getUnderlyingType())) { + TypeMirror unboxedSuper = types.unboxedType(superType.getUnderlyingType()); + if (unboxedSuper.getKind() != type.getKind() + && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { + AnnotatedPrimitiveType narrowedType = atypeFactory.getNarrowedPrimitive(type, unboxedSuper); + return visit(narrowedType, superType, p); + } + } + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Intersection( + AnnotatedPrimitiveType type, AnnotatedIntersectionType superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Typevar( + AnnotatedPrimitiveType type, AnnotatedTypeVariable superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Union( + AnnotatedPrimitiveType type, AnnotatedUnionType superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Wildcard( + AnnotatedPrimitiveType type, AnnotatedWildcardType superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + // + + // + private AnnotatedTypeMirror visitTypevar_NotTypevarNorWildcard( + AnnotatedTypeVariable type, AnnotatedTypeMirror superType, Void p) { + AnnotatedTypeMirror asSuper = visit(type.getUpperBound(), superType, p); + return copyPrimaryAnnos(type, asSuper); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Declared( + AnnotatedTypeVariable type, AnnotatedDeclaredType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Intersection( + AnnotatedTypeVariable type, AnnotatedIntersectionType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Primitive( + AnnotatedTypeVariable type, AnnotatedPrimitiveType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Typevar( + AnnotatedTypeVariable type, AnnotatedTypeVariable superType, Void p) { + // Clear the superType annotations and copy over the primary annotations before computing + // bounds, so that the superType annotations don't override the type annotations on the + // bounds. + superType.clearAnnotations(); + copyPrimaryAnnos(type, superType); + + AnnotatedTypeMirror upperBound = visit(type.getUpperBound(), superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound; + if (type.getLowerBound().getKind() == TypeKind.NULL + && superType.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getLowerBound()); + } else if (type.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getLowerBound(), p); + } else { + lowerBound = asSuperTypevarLowerBound(type.getLowerBound(), superType, p); + } + superType.setLowerBound(lowerBound); + + return superType; + } + + @Override + public AnnotatedTypeMirror visitTypevar_Union( + AnnotatedTypeVariable type, AnnotatedUnionType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Wildcard( + AnnotatedTypeVariable type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound; + if (superType.getExtendsBound().getUnderlyingType().getKind() == TypeKind.TYPEVAR + && TypesUtils.areSame( + type.getUnderlyingType(), + (TypeVariable) superType.getExtendsBound().getUnderlyingType())) { + upperBound = visit(type, superType.getExtendsBound(), p); + } else { + upperBound = visit(type.getUpperBound(), superType.getExtendsBound(), p); + } + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound; + if (type.getLowerBound().getKind() == TypeKind.NULL + && superType.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getSuperBound()); + } else if (type.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getSuperBound(), p); + } else { + lowerBound = asSuperWildcardLowerBound(type.getLowerBound(), superType, p); + } + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + /* The primary annotation on a union type is the LUB of the primary annotations on its alternatives. #ensurePrimaryIsCorrectForUnions ensures that this is the case. + + All the alternatives in a union type must be subtype of Throwable and cannot have type arguments; + however, a union type can be a subtype of an interface with a type argument. For example: + interface Interface{} + class MyException1 extends Throwable implements Interface{} + class MyException2 extends Throwable implements Interface{} + + MyException1 <: MyException1 | MyException2 <: Interface + MyException1 | MyException2 <: Throwable & Interface + */ + // + + private AnnotatedTypeMirror visitUnion_Other( + AnnotatedUnionType type, AnnotatedTypeMirror superType, Void p) { + // asSuper on any of the alternatives is the same, so just use the first one. + AnnotatedTypeMirror asSuper = visit(type.getAlternatives().get(0), superType, p); + return copyPrimaryAnnos(type, asSuper); + } + + @Override + public AnnotatedTypeMirror visitUnion_Declared( + AnnotatedUnionType type, AnnotatedDeclaredType superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitUnion_Intersection( + AnnotatedUnionType type, AnnotatedIntersectionType superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitUnion_Typevar( + AnnotatedUnionType type, AnnotatedTypeVariable superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitUnion_Union( + AnnotatedUnionType type, AnnotatedUnionType superType, Void p) { + for (AnnotatedTypeMirror superAltern : superType.getAlternatives()) { + copyPrimaryAnnos(type, superAltern); + } + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitUnion_Wildcard( + AnnotatedUnionType type, AnnotatedWildcardType superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + // + + // + + private AnnotatedTypeMirror visitWildcard_NotTypvarNorWildcard( + AnnotatedWildcardType type, AnnotatedTypeMirror superType, Void p) { + boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; + if (type.isUninferredTypeArgument()) { + isUninferredTypeArgument = true; + } + AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, p); + isUninferredTypeArgument = oldIsUninferredTypeArgument; + atypeFactory.addDefaultAnnotations(superType); + + return copyPrimaryAnnos(type, asSuper); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Array( + AnnotatedWildcardType type, AnnotatedArrayType superType, Void p) { + return visitWildcard_NotTypvarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Declared( + AnnotatedWildcardType type, AnnotatedDeclaredType superType, Void p) { + return visitWildcard_NotTypvarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Intersection( + AnnotatedWildcardType type, AnnotatedIntersectionType superType, Void p) { + return visitWildcard_NotTypvarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Primitive( + AnnotatedWildcardType type, AnnotatedPrimitiveType superType, Void p) { + return visitWildcard_NotTypvarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Typevar( + AnnotatedWildcardType type, AnnotatedTypeVariable superType, Void p) { + boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; + if (type.isUninferredTypeArgument()) { + isUninferredTypeArgument = true; + } + AnnotatedTypeMirror upperBound = visit(type.getExtendsBound(), superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound; + if (type.getSuperBound().getKind() == TypeKind.NULL + && superType.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getLowerBound()); + } else if (type.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getLowerBound(), p); + } else { + lowerBound = asSuperTypevarLowerBound(type.getSuperBound(), superType, p); + } + superType.setLowerBound(lowerBound); + isUninferredTypeArgument = oldIsUninferredTypeArgument; + atypeFactory.addDefaultAnnotations(superType); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Union( + AnnotatedWildcardType type, AnnotatedUnionType superType, Void p) { + return visitWildcard_NotTypvarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Wildcard( + AnnotatedWildcardType type, AnnotatedWildcardType superType, Void p) { + boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; + if (type.isUninferredTypeArgument()) { + isUninferredTypeArgument = true; + superType.setUninferredTypeArgument(); + } + if (types.isSubtype( + type.getExtendsBound().getUnderlyingType(), + superType.getExtendsBound().getUnderlyingType())) { + AnnotatedTypeMirror upperBound = + visit(type.getExtendsBound(), superType.getExtendsBound(), p); + superType.setExtendsBound(upperBound); + } else { + // The upper bound of a wildcard can be a super type of upper bound of the type + // parameter for which it is an argument. + // See org.checkerframework.framework.type.AnnotatedTypeFactory.widenToUpperBound for an + // example. In these cases, the upper bound of type might be a super type of the + // upper bound of superType. + + // The underlying type of the annotated type mirror returned by asSuper must be the + // same as the passed type, so just copy the primary annotations. + copyPrimaryAnnos(type.getExtendsBound(), superType.getExtendsBound()); + + // Add defaults in case any locations are missing annotations. + atypeFactory.addDefaultAnnotations(superType.getExtendsBound()); + } + + AnnotatedTypeMirror lowerBound; + if (type.getSuperBound().getKind() == TypeKind.NULL + && superType.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getSuperBound()); + } else if (type.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getSuperBound(), p); + } else { + lowerBound = asSuperWildcardLowerBound(type.getSuperBound(), superType, p); + } + superType.setSuperBound(lowerBound); + isUninferredTypeArgument = oldIsUninferredTypeArgument; + atypeFactory.addDefaultAnnotations(superType); + + return copyPrimaryAnnos(type, superType); + } + + /** + * Returns true if the atypeFactory for this is the given value. + * + * @param atypeFactory a factory to compare to that of this + * @return true if the atypeFactory for this is the given value + */ + public boolean sameAnnotatedTypeFactory(@FindDistinct AnnotatedTypeFactory atypeFactory) { + return this.atypeFactory == atypeFactory; + } + // } diff --git a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java index 7ec310019a1..7248e34b65c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java @@ -3,7 +3,19 @@ import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -24,21 +36,6 @@ import org.plumelib.util.IPair; import org.plumelib.util.StringsPlume; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.IntersectionType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; - /** * BoundsInitializer creates AnnotatedTypeMirrors (without annotations) for the bounds of type * variables and wildcards. Its static helper methods are called from AnnotatedTypeMirror. When an @@ -46,1374 +43,1344 @@ * circular references, will be created. */ public class BoundsInitializer { - // ============================================================================================ - // Static helper methods called from AnnotatedTypeMirror to initialize bounds of wildcards or - // type variables - // ============================================================================================ + // ============================================================================================ + // Static helper methods called from AnnotatedTypeMirror to initialize bounds of wildcards or + // type variables + // ============================================================================================ + + /** + * Initializes the type arguments of {@code declaredType}. The upper bound of unbound wildcards is + * set to the upper bound of the type parameter for which it is an argument. If {@code + * declaredType} is raw, then the type arguments are uninferred wildcards. + * + * @param declaredType type whose arguments are initialized + */ + public static void initializeTypeArgs(AnnotatedDeclaredType declaredType) { + DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType; + if (underlyingType.getTypeArguments().isEmpty() && !declaredType.isUnderlyingTypeRaw()) { + // No type arguments to initialize. + return; + } + TypeElement typeElement = + (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType); + int numTypeParameters = typeElement.getTypeParameters().size(); + List typeArgs = new ArrayList<>(numTypeParameters); + + // Create AnnotatedTypeMirror for each type argument and store them in the typeArgsMap. + // Take un-annotated type variables as the key for this map. + Map typeArgMap = new HashMap<>(numTypeParameters); + for (int i = 0; i < numTypeParameters; i++) { + TypeMirror javaTypeArg; + if (declaredType.isUnderlyingTypeRaw()) { + TypeVariable typeVariable = (TypeVariable) typeElement.getTypeParameters().get(i).asType(); + javaTypeArg = getUpperBoundAsWildcard(typeVariable, declaredType.atypeFactory); + } else { + javaTypeArg = declaredType.getUnderlyingType().getTypeArguments().get(i); + } + + AnnotatedTypeMirror typeArg = + AnnotatedTypeMirror.createType( + javaTypeArg, declaredType.atypeFactory, declaredType.isDeclaration()); + if (typeArg.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg; + wildcardType.setTypeVariable(typeElement.getTypeParameters().get(i)); + if (declaredType.isUnderlyingTypeRaw()) { + wildcardType.setUninferredTypeArgument(); + } + } + typeArgs.add(typeArg); + + // Add mapping from type parameter to the annotated type argument. + TypeVariable key = + (TypeVariable) + TypeAnnotationUtils.unannotatedType(typeElement.getTypeParameters().get(i).asType()); + typeArgMap.put(key, typeArg); + + if (javaTypeArg.getKind() == TypeKind.TYPEVAR) { + // Add mapping from Java type argument to the annotated type argument. + key = (TypeVariable) TypeAnnotationUtils.unannotatedType(javaTypeArg); + typeArgMap.put(key, typeArg); + } + } + + // Initialize type argument bounds using the typeArgsMap. + for (AnnotatedTypeMirror typeArg : typeArgs) { + switch (typeArg.getKind()) { + case WILDCARD: + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg; + initializeExtendsBound(wildcardType, typeArgMap); + initializeSuperBound(wildcardType, typeArgMap); + break; + case TYPEVAR: + initializeBounds((AnnotatedTypeVariable) typeArg, typeArgMap); + break; + default: + // do nothing + } + } + declaredType.typeArgs = Collections.unmodifiableList(typeArgs); + } + + /** + * Returns a wildcard whose upper bound is the same as {@code typeVariable}. If the upper bound is + * an intersection, then this method returns an unbound wildcard. + */ + private static WildcardType getUpperBoundAsWildcard( + TypeVariable typeVariable, AnnotatedTypeFactory factory) { + TypeMirror upperBound = typeVariable.getUpperBound(); + switch (upperBound.getKind()) { + case ARRAY: + case DECLARED: + case TYPEVAR: + return factory.types.getWildcardType(upperBound, null); + case INTERSECTION: + // Can't create a wildcard with an intersection as the upper bound, so use + // an unbound wildcard instead. The extends bound of the + // AnnotatedWildcardType will be initialized properly by this class. + return factory.types.getWildcardType(null, null); + default: + throw new BugInCF( + "Unexpected upper bound kind: %s type: %s", upperBound.getKind(), upperBound); + } + } + + /** + * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a + * typeVar is recursive the appropriate cycles will be introduced in the type + * + * @param typeVar the type variable whose lower bound is being initialized + */ + public static void initializeBounds(AnnotatedTypeVariable typeVar) { + initializeBounds(typeVar, null); + } + + /** + * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a + * typeVar is recursive the appropriate cycles will be introduced in the type + * + * @param typeVar the type variable whose lower bound is being initialized + * @param map a mapping of type parameters to type arguments. May be null. + */ + private static void initializeBounds( + AnnotatedTypeVariable typeVar, @Nullable Map map) { + AnnotationMirrorSet annos = saveAnnotations(typeVar); + + InitializerVisitor visitor = new InitializerVisitor(new TypeVariableStructure(typeVar), map); + visitor.initializeLowerBound(typeVar); + visitor.resolveTypeVarReferences(typeVar); + + InitializerVisitor visitor2 = new InitializerVisitor(new TypeVariableStructure(typeVar), map); + visitor2.initializeUpperBound(typeVar); + visitor2.resolveTypeVarReferences(typeVar); + + restoreAnnotations(typeVar, annos); + } + + /** + * Returns a type's primary annotations, and clears those annotations. + * + *

If we are initializing a type variable with a primary annotation than we should first + * initialize it as if it were a declaration (i.e. as if it had no primary annotations) and then + * apply the primary annotations. We do this so that when we make copies of the original type to + * represent recursive references the recursive references don't have the primary annotation. + * + *

Example: The declaration {@code >}.
+ * If we do not do this, the NonNull on the use @NonNull E would be copied to the primary + * annotation on E in the bound {@code List}.
+ * i.e. the use would be {@code <@NonNull E extends @NonNull List<@NonNull E>>}
+ * rather than {@code <@NonNull E extends @NonNull List>} + * + * @param type a type whose annotations to read, clear, and return + * @return the original primary annotations on {@code type}, or null if none + */ + private static @Nullable AnnotationMirrorSet saveAnnotations(AnnotatedTypeMirror type) { + if (!type.getAnnotationsField().isEmpty()) { + AnnotationMirrorSet annos = new AnnotationMirrorSet(type.getAnnotations()); + type.clearAnnotations(); + return annos; + } + + return null; + } + + private static void restoreAnnotations(AnnotatedTypeMirror type, AnnotationMirrorSet annos) { + if (annos != null) { + type.addAnnotations(annos); + } + } + + /** + * Create the entire super bound, with no missing information, for wildcard. If a wildcard is + * recursive the appropriate cycles will be introduced in the type + * + * @param wildcard the wildcard whose lower bound is being initialized + */ + public static void initializeSuperBound(AnnotatedWildcardType wildcard) { + initializeSuperBound(wildcard, null); + } + + /** + * Create the entire super bound, with no missing information, for wildcard. If a wildcard is + * recursive the appropriate cycles will be introduced in the type + * + * @param wildcard the wildcard whose lower bound is being initialized + * @param map a mapping of type parameters to type arguments. May be null. + */ + private static void initializeSuperBound( + AnnotatedWildcardType wildcard, @Nullable Map map) { + AnnotationMirrorSet annos = saveAnnotations(wildcard); + + InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map); + visitor.initializeSuperBound(wildcard); + visitor.resolveTypeVarReferences(wildcard); + + restoreAnnotations(wildcard, annos); + } + + /** + * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is + * recursive the appropriate cycles will be introduced in the type + * + * @param wildcard the wildcard whose extends bound is being initialized + */ + public static void initializeExtendsBound(AnnotatedWildcardType wildcard) { + initializeExtendsBound(wildcard, null); + } + + /** + * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is + * recursive the appropriate cycles will be introduced in the type + * + * @param wildcard the wildcard whose extends bound is being initialized + * @param map a mapping of type parameters to type arguments. May be null. + */ + private static void initializeExtendsBound( + AnnotatedWildcardType wildcard, @Nullable Map map) { + AnnotationMirrorSet annos = saveAnnotations(wildcard); + InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map); + visitor.initializeExtendsBound(wildcard); + visitor.resolveTypeVarReferences(wildcard); + restoreAnnotations(wildcard, annos); + } + + // ============================================================================================ + // Classes and methods used to make the above static helper methods work + // ============================================================================================ + + /** + * Creates the AnnotatedTypeMirrors (without annotations) for the bounds of all type variables and + * wildcards in a given type. If the type is recursive, {@code T extends Comparable}, then all + * references to the same type variable are references to the same AnnotatedTypeMirror. + */ + private static class InitializerVisitor implements AnnotatedTypeVisitor { /** - * Initializes the type arguments of {@code declaredType}. The upper bound of unbound wildcards - * is set to the upper bound of the type parameter for which it is an argument. If {@code - * declaredType} is raw, then the type arguments are uninferred wildcards. - * - * @param declaredType type whose arguments are initialized + * The {@link RecursiveTypeStructure} corresponding to the first wildcard or type variable bound + * initialization that kicked this visitation off. */ - public static void initializeTypeArgs(AnnotatedDeclaredType declaredType) { - DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType; - if (underlyingType.getTypeArguments().isEmpty() && !declaredType.isUnderlyingTypeRaw()) { - // No type arguments to initialize. - return; - } + private final RecursiveTypeStructure topLevelStructure; - TypeElement typeElement = - (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType); - int numTypeParameters = typeElement.getTypeParameters().size(); - List typeArgs = new ArrayList<>(numTypeParameters); + /** + * The {@link RecursiveTypeStructure} corresponding to the wildcard or type variable that is + * currently being visited. + */ + private RecursiveTypeStructure currentStructure; - // Create AnnotatedTypeMirror for each type argument and store them in the typeArgsMap. - // Take un-annotated type variables as the key for this map. - Map typeArgMap = new HashMap<>(numTypeParameters); - for (int i = 0; i < numTypeParameters; i++) { - TypeMirror javaTypeArg; - if (declaredType.isUnderlyingTypeRaw()) { - TypeVariable typeVariable = - (TypeVariable) typeElement.getTypeParameters().get(i).asType(); - javaTypeArg = getUpperBoundAsWildcard(typeVariable, declaredType.atypeFactory); - } else { - javaTypeArg = declaredType.getUnderlyingType().getTypeArguments().get(i); - } - - AnnotatedTypeMirror typeArg = - AnnotatedTypeMirror.createType( - javaTypeArg, declaredType.atypeFactory, declaredType.isDeclaration()); - if (typeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg; - wildcardType.setTypeVariable(typeElement.getTypeParameters().get(i)); - if (declaredType.isUnderlyingTypeRaw()) { - wildcardType.setUninferredTypeArgument(); - } - } - typeArgs.add(typeArg); - - // Add mapping from type parameter to the annotated type argument. - TypeVariable key = - (TypeVariable) - TypeAnnotationUtils.unannotatedType( - typeElement.getTypeParameters().get(i).asType()); - typeArgMap.put(key, typeArg); - - if (javaTypeArg.getKind() == TypeKind.TYPEVAR) { - // Add mapping from Java type argument to the annotated type argument. - key = (TypeVariable) TypeAnnotationUtils.unannotatedType(javaTypeArg); - typeArgMap.put(key, typeArg); - } - } + /** A mapping from TypeVariable to its {@link TypeVariableStructure}. */ + private final Map typeVarToStructure = new HashMap<>(); - // Initialize type argument bounds using the typeArgsMap. - for (AnnotatedTypeMirror typeArg : typeArgs) { - switch (typeArg.getKind()) { - case WILDCARD: - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg; - initializeExtendsBound(wildcardType, typeArgMap); - initializeSuperBound(wildcardType, typeArgMap); - break; - case TYPEVAR: - initializeBounds((AnnotatedTypeVariable) typeArg, typeArgMap); - break; - default: - // do nothing - } - } - declaredType.typeArgs = Collections.unmodifiableList(typeArgs); - } + /** + * A mapping from WildcardType to its AnnotatedWildcardType. The first time this visitor + * encounters a wildcard it creates an annotated type and adds it to this map. The next time the + * wilcard is encounter, the annotated type in this map is returned. + */ + private final Map wildcards = new HashMap<>(); /** - * Returns a wildcard whose upper bound is the same as {@code typeVariable}. If the upper bound - * is an intersection, then this method returns an unbound wildcard. + * A mapping from IntersectionType to its AnnotatedIntersectionType. The first time this visitor + * encounters an intersection it creates an annotated type and adds it to this map. The next + * time the intersection is encounter, the annotated type in this map is returned. */ - private static WildcardType getUpperBoundAsWildcard( - TypeVariable typeVariable, AnnotatedTypeFactory factory) { - TypeMirror upperBound = typeVariable.getUpperBound(); - switch (upperBound.getKind()) { - case ARRAY: - case DECLARED: - case TYPEVAR: - return factory.types.getWildcardType(upperBound, null); - case INTERSECTION: - // Can't create a wildcard with an intersection as the upper bound, so use - // an unbound wildcard instead. The extends bound of the - // AnnotatedWildcardType will be initialized properly by this class. - return factory.types.getWildcardType(null, null); - default: - throw new BugInCF( - "Unexpected upper bound kind: %s type: %s", - upperBound.getKind(), upperBound); - } - } + private final Map intersections = new HashMap<>(); /** - * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a - * typeVar is recursive the appropriate cycles will be introduced in the type - * - * @param typeVar the type variable whose lower bound is being initialized + * Mapping from TypeVariable to AnnotatedTypeMirror. The annotated type mirror should be used + * for any use of the type variable rather than creating and initializing a new annotated type. + * This is used for type arguments that have already been initialized outside of this visitor. */ - public static void initializeBounds(AnnotatedTypeVariable typeVar) { - initializeBounds(typeVar, null); - } + private final Map typevars; /** - * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a - * typeVar is recursive the appropriate cycles will be introduced in the type + * Creates an InitializerVisitor. * - * @param typeVar the type variable whose lower bound is being initialized - * @param map a mapping of type parameters to type arguments. May be null. + * @param recursiveTypeStructure structure for the type being initialized + * @param typevars a mapping from type variable to annotated types that have already been + * initialized */ - private static void initializeBounds( - AnnotatedTypeVariable typeVar, @Nullable Map map) { - AnnotationMirrorSet annos = saveAnnotations(typeVar); + public InitializerVisitor( + RecursiveTypeStructure recursiveTypeStructure, + Map typevars) { + this.topLevelStructure = recursiveTypeStructure; + this.currentStructure = recursiveTypeStructure; + if (typevars != null) { + this.typevars = typevars; + } else { + this.typevars = Collections.emptyMap(); + } + if (recursiveTypeStructure instanceof TypeVariableStructure) { + TypeVariableStructure typeVarStruct = (TypeVariableStructure) recursiveTypeStructure; + typeVarToStructure.put(typeVarStruct.typeVar, typeVarStruct); + } + } - InitializerVisitor visitor = - new InitializerVisitor(new TypeVariableStructure(typeVar), map); - visitor.initializeLowerBound(typeVar); - visitor.resolveTypeVarReferences(typeVar); + // ---------------------------------------------------------------------------------------- + // Visit methods that keep track of the path traversed through type variable bounds, and the + // wildcards/intersections that have been encountered. + // ---------------------------------------------------------------------------------------- - InitializerVisitor visitor2 = - new InitializerVisitor(new TypeVariableStructure(typeVar), map); - visitor2.initializeUpperBound(typeVar); - visitor2.resolveTypeVarReferences(typeVar); + @Override + public Void visit(AnnotatedTypeMirror type) { + type.accept(this, null); + return null; + } - restoreAnnotations(typeVar, annos); + @Override + public Void visit(AnnotatedTypeMirror type, Void aVoid) { + visit(type); + return null; } - /** - * Returns a type's primary annotations, and clears those annotations. - * - *

If we are initializing a type variable with a primary annotation than we should first - * initialize it as if it were a declaration (i.e. as if it had no primary annotations) and then - * apply the primary annotations. We do this so that when we make copies of the original type to - * represent recursive references the recursive references don't have the primary annotation. - * - *

Example: The declaration {@code >}.
- * If we do not do this, the NonNull on the use @NonNull E would be copied to the primary - * annotation on E in the bound {@code List}.
- * i.e. the use would be {@code <@NonNull E extends @NonNull List<@NonNull E>>}
- * rather than {@code <@NonNull E extends @NonNull List>} - * - * @param type a type whose annotations to read, clear, and return - * @return the original primary annotations on {@code type}, or null if none - */ - private static @Nullable AnnotationMirrorSet saveAnnotations(AnnotatedTypeMirror type) { - if (!type.getAnnotationsField().isEmpty()) { - AnnotationMirrorSet annos = new AnnotationMirrorSet(type.getAnnotations()); - type.clearAnnotations(); - return annos; - } + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + initializeTypeArgs(type); + if (type.enclosingType != null) { + TypePathNode node = currentStructure.addPathNode(new EnclosingTypeNode()); + visit(type.enclosingType); + currentStructure.removePathNode(node); + } + return null; + } + @Override + public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { + if (intersections.containsKey(type.getUnderlyingType())) { return null; + } + + intersections.put(type.getUnderlyingType(), type); + + List bounds = type.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + AnnotatedTypeMirror supertype = bounds.get(i); + TypePathNode node = currentStructure.addPathNode(new IntersectionBoundNode(i)); + visit(supertype); + currentStructure.removePathNode(node); + } + return null; } - private static void restoreAnnotations(AnnotatedTypeMirror type, AnnotationMirrorSet annos) { - if (annos != null) { - type.addAnnotations(annos); - } + @Override + public Void visitUnion(AnnotatedUnionType type, Void aVoid) { + List alts = type.getAlternatives(); + for (int i = 0; i < alts.size(); i++) { + AnnotatedDeclaredType alt = alts.get(i); + TypePathNode node = currentStructure.addPathNode(new AlternativeTypeNode(i)); + visit(alt); + currentStructure.removePathNode(node); + } + return null; } - /** - * Create the entire super bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type - * - * @param wildcard the wildcard whose lower bound is being initialized - */ - public static void initializeSuperBound(AnnotatedWildcardType wildcard) { - initializeSuperBound(wildcard, null); + @Override + public Void visitArray(AnnotatedArrayType type, Void aVoid) { + if (!TypesUtils.isPrimitive(type.getComponentType().getUnderlyingType())) { + // Only recur on component type if it's not a primitive. + // Array component types are the only place a primitive is allowed in bounds + TypePathNode componentNode = currentStructure.addPathNode(new ArrayComponentNode()); + type.setComponentType(getOrVisit(type.getComponentType())); + currentStructure.removePathNode(componentNode); + } + return null; } - /** - * Create the entire super bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type - * - * @param wildcard the wildcard whose lower bound is being initialized - * @param map a mapping of type parameters to type arguments. May be null. - */ - private static void initializeSuperBound( - AnnotatedWildcardType wildcard, @Nullable Map map) { - AnnotationMirrorSet annos = saveAnnotations(wildcard); + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { + this.currentStructure.addTypeVar(type.getUnderlyingType()); + if (typeVarToStructure.containsKey(type.getUnderlyingType())) { + return null; + } + TypeVariableStructure typeVarStruct = new TypeVariableStructure(type); + typeVarToStructure.put(type.getUnderlyingType(), typeVarStruct); + RecursiveTypeStructure parentStructure = this.currentStructure; + + // If type is a captured type variable, then its type variables should be created new, + // rather than using one from the rest of the type. So, clear the typevars map of all + // but the mapping with key type. + Map hold = new HashMap<>(); + if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { + for (Map.Entry entry : + new ArrayList<>(typevars.entrySet())) { + if (!type.atypeFactory.types.isSameType( + entry.getKey(), entry.getValue().underlyingType)) { + hold.put(entry.getKey(), entry.getValue()); + typevars.remove(entry.getKey(), entry.getValue()); + } + } + } + this.currentStructure = typeVarStruct; + initializeUpperBound(type); + initializeLowerBound(type); + this.currentStructure = parentStructure; + typevars.putAll(hold); + + return null; + } - InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map); - visitor.initializeSuperBound(wildcard); - visitor.resolveTypeVarReferences(wildcard); + @Override + public Void visitNull(AnnotatedNullType type, Void aVoid) { + return null; + } - restoreAnnotations(wildcard, annos); + @Override + public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { + if (wildcard.getSuperBoundField() == null) { + initializeSuperBound(wildcard); + } else { + throw new BugInCF( + "Wildcard super field should not be initialized:%n" + + "wildcard=%s%n" + + "currentStructure=%s%n", + wildcard, currentStructure); + } + + if (wildcard.getExtendsBoundField() == null) { + initializeExtendsBound(wildcard); + } else { + throw new BugInCF( + "Wildcard extends field should not be initialized:%n" + + "wildcard=%s%n" + + "currentStructure=%s%n", + wildcard, currentStructure); + } + + return null; } - /** - * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type - * - * @param wildcard the wildcard whose extends bound is being initialized - */ - public static void initializeExtendsBound(AnnotatedWildcardType wildcard) { - initializeExtendsBound(wildcard, null); + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { + throw new BugInCF("Unexpected AnnotatedPrimitiveType " + type); + } + + @Override + public Void visitNoType(AnnotatedNoType type, Void aVoid) { + throw new BugInCF("Unexpected AnnotatedNoType " + type); + } + + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { + throw new BugInCF("Unexpected AnnotatedExecutableType " + type); } /** - * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type + * If the underlying type of {@code type} has been visited before, return the previous + * AnnotatedTypeMirror. Otherwise, visit {@code type} and return it. * - * @param wildcard the wildcard whose extends bound is being initialized - * @param map a mapping of type parameters to type arguments. May be null. + * @param type type to visit + * @return {@code type} or an AnnotatedTypeMirror with the same underlying type that was + * previously visited. */ - private static void initializeExtendsBound( - AnnotatedWildcardType wildcard, @Nullable Map map) { - AnnotationMirrorSet annos = saveAnnotations(wildcard); - InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map); - visitor.initializeExtendsBound(wildcard); - visitor.resolveTypeVarReferences(wildcard); - restoreAnnotations(wildcard, annos); + public AnnotatedTypeMirror getOrVisit(AnnotatedTypeMirror type) { + switch (type.getKind()) { + case WILDCARD: + AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; + if (wildcards.containsKey(wildcard.getUnderlyingType())) { + return wildcards.get(wildcard.getUnderlyingType()); + } + break; + case INTERSECTION: + if (intersections.containsKey(type.getUnderlyingType())) { + return intersections.get(type.getUnderlyingType()); + } + break; + case TYPEVAR: + TypeVariable key = + (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()); + if (typevars.containsKey(key)) { + return typevars.get(key); + } + break; + default: + // do nothing + } + visit(type); + return type; } - // ============================================================================================ - // Classes and methods used to make the above static helper methods work - // ============================================================================================ + // ---------------------------------------------------------------------------------------- + // /** - * Creates the AnnotatedTypeMirrors (without annotations) for the bounds of all type variables - * and wildcards in a given type. If the type is recursive, {@code T extends Comparable}, - * then all references to the same type variable are references to the same AnnotatedTypeMirror. + * Initialize {@code typeVar}'s upper bound. + * + * @param typeVar type variable whose upper bound is initialized */ - private static class InitializerVisitor implements AnnotatedTypeVisitor { - /** - * The {@link RecursiveTypeStructure} corresponding to the first wildcard or type variable - * bound initialization that kicked this visitation off. - */ - private final RecursiveTypeStructure topLevelStructure; - - /** - * The {@link RecursiveTypeStructure} corresponding to the wildcard or type variable that is - * currently being visited. - */ - private RecursiveTypeStructure currentStructure; - - /** A mapping from TypeVariable to its {@link TypeVariableStructure}. */ - private final Map typeVarToStructure = new HashMap<>(); - - /** - * A mapping from WildcardType to its AnnotatedWildcardType. The first time this visitor - * encounters a wildcard it creates an annotated type and adds it to this map. The next time - * the wilcard is encounter, the annotated type in this map is returned. - */ - private final Map wildcards = new HashMap<>(); - - /** - * A mapping from IntersectionType to its AnnotatedIntersectionType. The first time this - * visitor encounters an intersection it creates an annotated type and adds it to this map. - * The next time the intersection is encounter, the annotated type in this map is returned. - */ - private final Map intersections = - new HashMap<>(); - - /** - * Mapping from TypeVariable to AnnotatedTypeMirror. The annotated type mirror should be - * used for any use of the type variable rather than creating and initializing a new - * annotated type. This is used for type arguments that have already been initialized - * outside of this visitor. - */ - private final Map typevars; - - /** - * Creates an InitializerVisitor. - * - * @param recursiveTypeStructure structure for the type being initialized - * @param typevars a mapping from type variable to annotated types that have already been - * initialized - */ - public InitializerVisitor( - RecursiveTypeStructure recursiveTypeStructure, - Map typevars) { - this.topLevelStructure = recursiveTypeStructure; - this.currentStructure = recursiveTypeStructure; - if (typevars != null) { - this.typevars = typevars; - } else { - this.typevars = Collections.emptyMap(); - } - if (recursiveTypeStructure instanceof TypeVariableStructure) { - TypeVariableStructure typeVarStruct = - (TypeVariableStructure) recursiveTypeStructure; - typeVarToStructure.put(typeVarStruct.typeVar, typeVarStruct); - } - } - - // ---------------------------------------------------------------------------------------- - // Visit methods that keep track of the path traversed through type variable bounds, and the - // wildcards/intersections that have been encountered. - // ---------------------------------------------------------------------------------------- + public void initializeUpperBound(AnnotatedTypeVariable typeVar) { + AnnotatedTypeMirror upperBound = createAndSetUpperBound(typeVar); - @Override - public Void visit(AnnotatedTypeMirror type) { - type.accept(this, null); - return null; - } - - @Override - public Void visit(AnnotatedTypeMirror type, Void aVoid) { - visit(type); - return null; - } + TypePathNode pathNode = new UpperBoundNode(); + currentStructure.addPathNode(pathNode); + visit(upperBound); + currentStructure.removePathNode(pathNode); + } - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - initializeTypeArgs(type); - if (type.enclosingType != null) { - TypePathNode node = currentStructure.addPathNode(new EnclosingTypeNode()); - visit(type.enclosingType); - currentStructure.removePathNode(node); - } - return null; - } + /** + * Initialize {@code typeVar}'s lower bound. + * + * @param typeVar type variable whose lower bound is initialized + */ + public void initializeLowerBound(AnnotatedTypeVariable typeVar) { + AnnotatedTypeMirror lowerBound = createAndSetLowerBound(typeVar); - @Override - public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { - if (intersections.containsKey(type.getUnderlyingType())) { - return null; - } - - intersections.put(type.getUnderlyingType(), type); - - List bounds = type.getBounds(); - for (int i = 0; i < bounds.size(); i++) { - AnnotatedTypeMirror supertype = bounds.get(i); - TypePathNode node = currentStructure.addPathNode(new IntersectionBoundNode(i)); - visit(supertype); - currentStructure.removePathNode(node); - } - return null; - } + TypePathNode pathNode = new LowerBoundNode(); + currentStructure.addPathNode(pathNode); + visit(lowerBound); + currentStructure.removePathNode(pathNode); + } - @Override - public Void visitUnion(AnnotatedUnionType type, Void aVoid) { - List alts = type.getAlternatives(); - for (int i = 0; i < alts.size(); i++) { - AnnotatedDeclaredType alt = alts.get(i); - TypePathNode node = currentStructure.addPathNode(new AlternativeTypeNode(i)); - visit(alt); - currentStructure.removePathNode(node); - } - return null; - } + /** + * Initialize {@code wildcard}'s super bound. + * + * @param wildcard wildcard whose super bound is initialized + */ + public void initializeSuperBound(AnnotatedWildcardType wildcard) { + AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; - @Override - public Void visitArray(AnnotatedArrayType type, Void aVoid) { - if (!TypesUtils.isPrimitive(type.getComponentType().getUnderlyingType())) { - // Only recur on component type if it's not a primitive. - // Array component types are the only place a primitive is allowed in bounds - TypePathNode componentNode = currentStructure.addPathNode(new ArrayComponentNode()); - type.setComponentType(getOrVisit(type.getComponentType())); - currentStructure.removePathNode(componentNode); - } - return null; - } + WildcardType underlyingType = wildcard.getUnderlyingType(); + TypeMirror underlyingSuperBound = underlyingType.getSuperBound(); + if (underlyingSuperBound == null) { + underlyingSuperBound = + TypesUtils.wildLowerBound(underlyingType, wildcard.atypeFactory.processingEnv); + } - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { - this.currentStructure.addTypeVar(type.getUnderlyingType()); - if (typeVarToStructure.containsKey(type.getUnderlyingType())) { - return null; - } - TypeVariableStructure typeVarStruct = new TypeVariableStructure(type); - typeVarToStructure.put(type.getUnderlyingType(), typeVarStruct); - RecursiveTypeStructure parentStructure = this.currentStructure; - - // If type is a captured type variable, then its type variables should be created new, - // rather than using one from the rest of the type. So, clear the typevars map of all - // but the mapping with key type. - Map hold = new HashMap<>(); - if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { - for (Map.Entry entry : - new ArrayList<>(typevars.entrySet())) { - if (!type.atypeFactory.types.isSameType( - entry.getKey(), entry.getValue().underlyingType)) { - hold.put(entry.getKey(), entry.getValue()); - typevars.remove(entry.getKey(), entry.getValue()); - } - } - } - this.currentStructure = typeVarStruct; - initializeUpperBound(type); - initializeLowerBound(type); - this.currentStructure = parentStructure; - typevars.putAll(hold); - - return null; - } + AnnotatedTypeMirror superBound = + AnnotatedTypeMirror.createType(underlyingSuperBound, typeFactory, false); + wildcard.setSuperBound(superBound); - @Override - public Void visitNull(AnnotatedNullType type, Void aVoid) { - return null; - } + this.wildcards.put(wildcard.getUnderlyingType(), wildcard); - @Override - public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { - if (wildcard.getSuperBoundField() == null) { - initializeSuperBound(wildcard); - } else { - throw new BugInCF( - "Wildcard super field should not be initialized:%n" - + "wildcard=%s%n" - + "currentStructure=%s%n", - wildcard, currentStructure); - } - - if (wildcard.getExtendsBoundField() == null) { - initializeExtendsBound(wildcard); - } else { - throw new BugInCF( - "Wildcard extends field should not be initialized:%n" - + "wildcard=%s%n" - + "currentStructure=%s%n", - wildcard, currentStructure); - } - - return null; - } + TypePathNode superNode = currentStructure.addPathNode(new SuperBoundNode()); + visit(superBound); + currentStructure.removePathNode(superNode); + } - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - throw new BugInCF("Unexpected AnnotatedPrimitiveType " + type); - } + /** + * Initialize {@code wildcard}'s extends bound. + * + * @param wildcard wildcard whose extends bound is initialized + */ + public void initializeExtendsBound(AnnotatedWildcardType wildcard) { + AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; + + WildcardType javaWildcardType = wildcard.getUnderlyingType(); + TypeMirror javaExtendsBound; + if (javaWildcardType.getExtendsBound() != null) { + javaExtendsBound = javaWildcardType.getExtendsBound(); + } else { + javaExtendsBound = TypesUtils.getObjectTypeMirror(typeFactory.processingEnv); + } + + if (wildcard.isUninferredTypeArgument()) { + rawTypeWildcards.put(wildcard.getTypeVariable(), wildcard.getUnderlyingType()); + } + + AnnotatedTypeMirror extendsBound = + AnnotatedTypeMirror.createType(javaExtendsBound, typeFactory, false); + wildcard.setExtendsBound(extendsBound); + + this.wildcards.put(wildcard.getUnderlyingType(), wildcard); + + TypePathNode extendsNode = currentStructure.addPathNode(new ExtendsBoundNode()); + visit(extendsBound); + currentStructure.removePathNode(extendsNode); + } - @Override - public Void visitNoType(AnnotatedNoType type, Void aVoid) { - throw new BugInCF("Unexpected AnnotatedNoType " + type); - } + /** + * Initialize {@code declaredType}'s type arguments. + * + * @param declaredType declared type whose type arguments are initialized + */ + private void initializeTypeArgs(AnnotatedDeclaredType declaredType) { + DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType; + if (underlyingType.getTypeArguments().isEmpty() && !declaredType.isUnderlyingTypeRaw()) { + return; + } + TypeElement typeElement = + (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType); + List typeArgs; + if (declaredType.typeArgs == null) { + int numTypeParameters = typeElement.getTypeParameters().size(); + typeArgs = new ArrayList<>(numTypeParameters); + for (int i = 0; i < numTypeParameters; i++) { + TypeMirror javaTypeArg = getJavaType(declaredType, typeElement.getTypeParameters(), i); + AnnotatedTypeMirror atmArg = + AnnotatedTypeMirror.createType(javaTypeArg, declaredType.atypeFactory, false); + typeArgs.add(atmArg); + if (atmArg.getKind() == TypeKind.WILDCARD && declaredType.isUnderlyingTypeRaw()) { + ((AnnotatedWildcardType) atmArg).setUninferredTypeArgument(); + } + } + } else { + typeArgs = declaredType.typeArgs; + } + + List typeArgReplacements = new ArrayList<>(typeArgs.size()); + for (int i = 0; i < typeArgs.size(); i++) { + AnnotatedTypeMirror typeArg = typeArgs.get(i); + TypePathNode node = currentStructure.addPathNode(new TypeArgNode(i)); + if (typeArg.getKind() == TypeKind.WILDCARD) { + ((AnnotatedWildcardType) typeArg).setTypeVariable(typeElement.getTypeParameters().get(i)); + } + typeArgReplacements.add(getOrVisit(typeArg)); + currentStructure.removePathNode(node); + } + + declaredType.setTypeArguments(typeArgReplacements); + } - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - throw new BugInCF("Unexpected AnnotatedExecutableType " + type); - } + /** + * Store the wildcards created as type arguments to raw types. + * + *

{@code class Foo {}} The upper bound of the wildcard in {@code Foo} is + * {@code Foo}. The type argument of {@code Foo} is initialized to {@code ? extends Foo}. The + * type argument of {@code Foo} in {@code ? extends Foo} needs to be initialized to the same + * type argument as the first {@code Foo} so that + * BoundsInitializer.InitializerVisitor#getOrVisit will return the cached AnnotatedWildcardType. + */ + private final Map rawTypeWildcards = new HashMap<>(); - /** - * If the underlying type of {@code type} has been visited before, return the previous - * AnnotatedTypeMirror. Otherwise, visit {@code type} and return it. - * - * @param type type to visit - * @return {@code type} or an AnnotatedTypeMirror with the same underlying type that was - * previously visited. - */ - public AnnotatedTypeMirror getOrVisit(AnnotatedTypeMirror type) { - switch (type.getKind()) { - case WILDCARD: - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; - if (wildcards.containsKey(wildcard.getUnderlyingType())) { - return wildcards.get(wildcard.getUnderlyingType()); - } - break; - case INTERSECTION: - if (intersections.containsKey(type.getUnderlyingType())) { - return intersections.get(type.getUnderlyingType()); - } - break; - case TYPEVAR: - TypeVariable key = - (TypeVariable) - TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()); - if (typevars.containsKey(key)) { - return typevars.get(key); - } - break; - default: - // do nothing - } - visit(type); - return type; - } + /** + * Returns the underlying Java type of the {@code i}-th type argument of {@code type}. If {@code + * type} is raw, then a new wildcard is created or returned from {@code rawTypeWildcards}. + * + * @param type declared type + * @param parameters elements of the type parameters + * @param i index of the type parameter + * @return the underlying Java type of the {@code i}-th type argument of {@code type} + */ + private TypeMirror getJavaType( + AnnotatedDeclaredType type, List parameters, int i) { + if (type.isUnderlyingTypeRaw()) { + TypeVariable typeVariable = (TypeVariable) parameters.get(i).asType(); + if (rawTypeWildcards.containsKey(typeVariable)) { + return rawTypeWildcards.get(typeVariable); + } + WildcardType wildcard = getUpperBoundAsWildcard(typeVariable, type.atypeFactory); + rawTypeWildcards.put(typeVariable, wildcard); + return wildcard; + } else { + return type.getUnderlyingType().getTypeArguments().get(i); + } + } - // ---------------------------------------------------------------------------------------- - // - - /** - * Initialize {@code typeVar}'s upper bound. - * - * @param typeVar type variable whose upper bound is initialized - */ - public void initializeUpperBound(AnnotatedTypeVariable typeVar) { - AnnotatedTypeMirror upperBound = createAndSetUpperBound(typeVar); - - TypePathNode pathNode = new UpperBoundNode(); - currentStructure.addPathNode(pathNode); - visit(upperBound); - currentStructure.removePathNode(pathNode); - } + /** + * Replace all type variables in type with the AnnotatedTypeMirrors created when initializing + * it. + * + * @param type all type variables are replaced + */ + public void resolveTypeVarReferences(AnnotatedTypeMirror type) { + List annotatedTypeVars = new ArrayList<>(); + if (type.getKind() == TypeKind.TYPEVAR) { + annotatedTypeVars.add((AnnotatedTypeVariable) type); + } + + // Gather a list of all AnnotatedTypeVariables and all the replacements to perform. + for (TypeVariableStructure typeVarStruct : typeVarToStructure.values()) { + typeVarStruct.findAllReplacements(typeVarToStructure); + annotatedTypeVars.addAll(typeVarStruct.getAnnotatedTypeVars()); + } + + // Do the replacements. + for (AnnotatedTypeVariable atv : annotatedTypeVars) { + TypeVariableStructure list = typeVarToStructure.get(atv.getUnderlyingType()); + list.replaceTypeVariablesInType(atv); + } + + if (type.getKind() == TypeKind.WILDCARD) { + // Do the "top level" replacements. + AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; + topLevelStructure.findAllReplacements(typeVarToStructure); + for (AnnotatedTypeVariable typeVar : topLevelStructure.getAnnotatedTypeVars()) { + TypeVariableStructure list = typeVarToStructure.get(typeVar.getUnderlyingType()); + list.replaceTypeVariablesInType(typeVar); + } + topLevelStructure.replaceTypeVariablesInType(wildcard); + } + } + } + + /** + * Creates the upper bound type for {@code typeVar} and sets it. + * + * @param typeVar type variable + * @return the newly created upper bound + */ + private static AnnotatedTypeMirror createAndSetUpperBound(AnnotatedTypeVariable typeVar) { + AnnotatedTypeMirror upperBound = + AnnotatedTypeMirror.createType( + typeVar.getUnderlyingType().getUpperBound(), typeVar.atypeFactory, false); + typeVar.setUpperBound(upperBound); + return upperBound; + } + + /** + * Creates the lower bound type for {@code typeVar} and sets it. If the type variable does not + * have a lower bound, then a null type is created. + * + * @param typeVar type variable + * @return the newly created lower bound + */ + private static AnnotatedTypeMirror createAndSetLowerBound(AnnotatedTypeVariable typeVar) { + TypeMirror lb = typeVar.getUnderlyingType().getLowerBound(); + if (lb == null) { + // Use bottom type to ensure there is a lower bound. + Context context = + ((JavacProcessingEnvironment) typeVar.atypeFactory.processingEnv).getContext(); + Symtab syms = Symtab.instance(context); + lb = syms.botType; + } + AnnotatedTypeMirror lowerBound = + AnnotatedTypeMirror.createType(lb, typeVar.atypeFactory, false); + typeVar.setLowerBound(lowerBound); + return lowerBound; + } - /** - * Initialize {@code typeVar}'s lower bound. - * - * @param typeVar type variable whose lower bound is initialized - */ - public void initializeLowerBound(AnnotatedTypeVariable typeVar) { - AnnotatedTypeMirror lowerBound = createAndSetLowerBound(typeVar); - - TypePathNode pathNode = new LowerBoundNode(); - currentStructure.addPathNode(pathNode); - visit(lowerBound); - currentStructure.removePathNode(pathNode); - } + /** + * Contains all the type variables and the type path to reach them found when scanning a + * particular type variable or wildcard. Then uses this information to replace the type variables + * with AnnotatedTypeVariables. + */ + private static class RecursiveTypeStructure { - /** - * Initialize {@code wildcard}'s super bound. - * - * @param wildcard wildcard whose super bound is initialized - */ - public void initializeSuperBound(AnnotatedWildcardType wildcard) { - AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; - - WildcardType underlyingType = wildcard.getUnderlyingType(); - TypeMirror underlyingSuperBound = underlyingType.getSuperBound(); - if (underlyingSuperBound == null) { - underlyingSuperBound = - TypesUtils.wildLowerBound( - underlyingType, wildcard.atypeFactory.processingEnv); - } - - AnnotatedTypeMirror superBound = - AnnotatedTypeMirror.createType(underlyingSuperBound, typeFactory, false); - wildcard.setSuperBound(superBound); - - this.wildcards.put(wildcard.getUnderlyingType(), wildcard); - - TypePathNode superNode = currentStructure.addPathNode(new SuperBoundNode()); - visit(superBound); - currentStructure.removePathNode(superNode); - } + /** List of TypePath and TypeVariables that were found will traversing this type. */ + private final List> typeVarsInType = new ArrayList<>(); - /** - * Initialize {@code wildcard}'s extends bound. - * - * @param wildcard wildcard whose extends bound is initialized - */ - public void initializeExtendsBound(AnnotatedWildcardType wildcard) { - AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; - - WildcardType javaWildcardType = wildcard.getUnderlyingType(); - TypeMirror javaExtendsBound; - if (javaWildcardType.getExtendsBound() != null) { - javaExtendsBound = javaWildcardType.getExtendsBound(); - } else { - javaExtendsBound = TypesUtils.getObjectTypeMirror(typeFactory.processingEnv); - } - - if (wildcard.isUninferredTypeArgument()) { - rawTypeWildcards.put(wildcard.getTypeVariable(), wildcard.getUnderlyingType()); - } - - AnnotatedTypeMirror extendsBound = - AnnotatedTypeMirror.createType(javaExtendsBound, typeFactory, false); - wildcard.setExtendsBound(extendsBound); - - this.wildcards.put(wildcard.getUnderlyingType(), wildcard); - - TypePathNode extendsNode = currentStructure.addPathNode(new ExtendsBoundNode()); - visit(extendsBound); - currentStructure.removePathNode(extendsNode); - } + /** Current path used to mark the locations of TypeVariables. */ + private final TypePath currentPath = new TypePath(); - /** - * Initialize {@code declaredType}'s type arguments. - * - * @param declaredType declared type whose type arguments are initialized - */ - private void initializeTypeArgs(AnnotatedDeclaredType declaredType) { - DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType; - if (underlyingType.getTypeArguments().isEmpty() - && !declaredType.isUnderlyingTypeRaw()) { - return; - } - TypeElement typeElement = - (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType); - List typeArgs; - if (declaredType.typeArgs == null) { - int numTypeParameters = typeElement.getTypeParameters().size(); - typeArgs = new ArrayList<>(numTypeParameters); - for (int i = 0; i < numTypeParameters; i++) { - TypeMirror javaTypeArg = - getJavaType(declaredType, typeElement.getTypeParameters(), i); - AnnotatedTypeMirror atmArg = - AnnotatedTypeMirror.createType( - javaTypeArg, declaredType.atypeFactory, false); - typeArgs.add(atmArg); - if (atmArg.getKind() == TypeKind.WILDCARD - && declaredType.isUnderlyingTypeRaw()) { - ((AnnotatedWildcardType) atmArg).setUninferredTypeArgument(); - } - } - } else { - typeArgs = declaredType.typeArgs; - } - - List typeArgReplacements = new ArrayList<>(typeArgs.size()); - for (int i = 0; i < typeArgs.size(); i++) { - AnnotatedTypeMirror typeArg = typeArgs.get(i); - TypePathNode node = currentStructure.addPathNode(new TypeArgNode(i)); - if (typeArg.getKind() == TypeKind.WILDCARD) { - ((AnnotatedWildcardType) typeArg) - .setTypeVariable(typeElement.getTypeParameters().get(i)); - } - typeArgReplacements.add(getOrVisit(typeArg)); - currentStructure.removePathNode(node); - } - - declaredType.setTypeArguments(typeArgReplacements); - } + /** + * Add a type variable found at the current path while visiting the type variable or wildcard + * associated with this structure. + * + * @param typeVariable a type variable + */ + public void addTypeVar(TypeVariable typeVariable) { + typeVarsInType.add(IPair.of(this.currentPath.copy(), typeVariable)); + } - /** - * Store the wildcards created as type arguments to raw types. - * - *

{@code class Foo {}} The upper bound of the wildcard in {@code Foo} - * is {@code Foo}. The type argument of {@code Foo} is initialized to {@code ? extends Foo}. - * The type argument of {@code Foo} in {@code ? extends Foo} needs to be initialized to the - * same type argument as the first {@code Foo} so that - * BoundsInitializer.InitializerVisitor#getOrVisit will return the cached - * AnnotatedWildcardType. - */ - private final Map rawTypeWildcards = new HashMap<>(); - - /** - * Returns the underlying Java type of the {@code i}-th type argument of {@code type}. If - * {@code type} is raw, then a new wildcard is created or returned from {@code - * rawTypeWildcards}. - * - * @param type declared type - * @param parameters elements of the type parameters - * @param i index of the type parameter - * @return the underlying Java type of the {@code i}-th type argument of {@code type} - */ - private TypeMirror getJavaType( - AnnotatedDeclaredType type, - List parameters, - int i) { - if (type.isUnderlyingTypeRaw()) { - TypeVariable typeVariable = (TypeVariable) parameters.get(i).asType(); - if (rawTypeWildcards.containsKey(typeVariable)) { - return rawTypeWildcards.get(typeVariable); - } - WildcardType wildcard = getUpperBoundAsWildcard(typeVariable, type.atypeFactory); - rawTypeWildcards.put(typeVariable, wildcard); - return wildcard; - } else { - return type.getUnderlyingType().getTypeArguments().get(i); - } - } + /** + * Add a node in the path. + * + * @param node node to add + * @return {@code node} + */ + public TypePathNode addPathNode(TypePathNode node) { + currentPath.add(node); + return node; + } - /** - * Replace all type variables in type with the AnnotatedTypeMirrors created when - * initializing it. - * - * @param type all type variables are replaced - */ - public void resolveTypeVarReferences(AnnotatedTypeMirror type) { - List annotatedTypeVars = new ArrayList<>(); - if (type.getKind() == TypeKind.TYPEVAR) { - annotatedTypeVars.add((AnnotatedTypeVariable) type); - } - - // Gather a list of all AnnotatedTypeVariables and all the replacements to perform. - for (TypeVariableStructure typeVarStruct : typeVarToStructure.values()) { - typeVarStruct.findAllReplacements(typeVarToStructure); - annotatedTypeVars.addAll(typeVarStruct.getAnnotatedTypeVars()); - } - - // Do the replacements. - for (AnnotatedTypeVariable atv : annotatedTypeVars) { - TypeVariableStructure list = typeVarToStructure.get(atv.getUnderlyingType()); - list.replaceTypeVariablesInType(atv); - } - - if (type.getKind() == TypeKind.WILDCARD) { - // Do the "top level" replacements. - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; - topLevelStructure.findAllReplacements(typeVarToStructure); - for (AnnotatedTypeVariable typeVar : topLevelStructure.getAnnotatedTypeVars()) { - TypeVariableStructure list = - typeVarToStructure.get(typeVar.getUnderlyingType()); - list.replaceTypeVariablesInType(typeVar); - } - topLevelStructure.replaceTypeVariablesInType(wildcard); - } - } + /** + * Remove the last node in the path if it is {@code node}; otherwise, throw an exception. + * + * @param node last node in the path + */ + public void removePathNode(@FindDistinct TypePathNode node) { + if (currentPath.getLeaf() != node) { + throw new BugInCF( + "Cannot remove node: %s. It is not the last node. currentPath= %s", node, currentPath); + } + currentPath.removeLeaf(); } /** - * Creates the upper bound type for {@code typeVar} and sets it. + * For all type variables contained within the type variable or wildcard that this structure + * represents, this a list of the replacement {@link AnnotatedTypeVariable} for the location + * specified by the {@link TypePath}. + */ + private List> replacementList; + + /** + * Find the AnnotatedTypeVariables that should replace the type variables found in this type. * - * @param typeVar type variable - * @return the newly created upper bound + * @param typeVarToStructure a mapping from TypeVariable to TypeVariableStructure */ - private static AnnotatedTypeMirror createAndSetUpperBound(AnnotatedTypeVariable typeVar) { - AnnotatedTypeMirror upperBound = - AnnotatedTypeMirror.createType( - typeVar.getUnderlyingType().getUpperBound(), typeVar.atypeFactory, false); - typeVar.setUpperBound(upperBound); - return upperBound; + public void findAllReplacements(Map typeVarToStructure) { + this.annotatedTypeVariables = new ArrayList<>(typeVarsInType.size()); + this.replacementList = new ArrayList<>(typeVarsInType.size()); + for (IPair pair : typeVarsInType) { + TypeVariableStructure targetStructure = typeVarToStructure.get(pair.second); + AnnotatedTypeVariable template = targetStructure.annotatedTypeVar.deepCopy().asUse(); + annotatedTypeVariables.add(template); + replacementList.add(IPair.of(pair.first, template)); + } } + /** List of {@link AnnotatedTypeVariable}s found in this type. */ + private List annotatedTypeVariables; + /** - * Creates the lower bound type for {@code typeVar} and sets it. If the type variable does not - * have a lower bound, then a null type is created. + * A list of all AnnotatedTypeVariables found in this type. {@link #findAllReplacements(Map)} + * must be called first. * - * @param typeVar type variable - * @return the newly created lower bound + * @return a list of all AnnotatedTypeVariables found in this type */ - private static AnnotatedTypeMirror createAndSetLowerBound(AnnotatedTypeVariable typeVar) { - TypeMirror lb = typeVar.getUnderlyingType().getLowerBound(); - if (lb == null) { - // Use bottom type to ensure there is a lower bound. - Context context = - ((JavacProcessingEnvironment) typeVar.atypeFactory.processingEnv).getContext(); - Symtab syms = Symtab.instance(context); - lb = syms.botType; - } - AnnotatedTypeMirror lowerBound = - AnnotatedTypeMirror.createType(lb, typeVar.atypeFactory, false); - typeVar.setLowerBound(lowerBound); - return lowerBound; + public List getAnnotatedTypeVars() { + if (annotatedTypeVariables == null) { + throw new BugInCF("Call createReplacementList before calling this method."); + } + return annotatedTypeVariables; } /** - * Contains all the type variables and the type path to reach them found when scanning a - * particular type variable or wildcard. Then uses this information to replace the type - * variables with AnnotatedTypeVariables. + * Replaces all type variables in {@code type} with their replacements. ({@link + * #findAllReplacements(Map)} must be called first so that the replacements can be found.) + * + * @param type annotated type whose type variables are replaced */ - private static class RecursiveTypeStructure { - - /** List of TypePath and TypeVariables that were found will traversing this type. */ - private final List> typeVarsInType = new ArrayList<>(); - - /** Current path used to mark the locations of TypeVariables. */ - private final TypePath currentPath = new TypePath(); - - /** - * Add a type variable found at the current path while visiting the type variable or - * wildcard associated with this structure. - * - * @param typeVariable a type variable - */ - public void addTypeVar(TypeVariable typeVariable) { - typeVarsInType.add(IPair.of(this.currentPath.copy(), typeVariable)); - } + public void replaceTypeVariablesInType(AnnotatedTypeMirror type) { + if (replacementList == null) { + throw new BugInCF("Call createReplacementList before calling this method."); + } + for (IPair entry : replacementList) { + TypePath path = entry.first; + AnnotatedTypeVariable replacement = entry.second; + path.replaceTypeVariable(type, replacement); + } + } + } - /** - * Add a node in the path. - * - * @param node node to add - * @return {@code node} - */ - public TypePathNode addPathNode(TypePathNode node) { - currentPath.add(node); - return node; - } + /** A {@link RecursiveTypeStructure} for a type variable. */ + private static class TypeVariableStructure extends RecursiveTypeStructure { + /** The type variable whose structure is being described. */ + public final TypeVariable typeVar; - /** - * Remove the last node in the path if it is {@code node}; otherwise, throw an exception. - * - * @param node last node in the path - */ - public void removePathNode(@FindDistinct TypePathNode node) { - if (currentPath.getLeaf() != node) { - throw new BugInCF( - "Cannot remove node: %s. It is not the last node. currentPath= %s", - node, currentPath); - } - currentPath.removeLeaf(); - } + /** + * The first annotated type variable that was encountered and traversed in order to describe + * typeVar. It is expanded during visitation and it is later used as a template for other uses + * of typeVar + */ + public final AnnotatedTypeVariable annotatedTypeVar; - /** - * For all type variables contained within the type variable or wildcard that this structure - * represents, this a list of the replacement {@link AnnotatedTypeVariable} for the location - * specified by the {@link TypePath}. - */ - private List> replacementList; - - /** - * Find the AnnotatedTypeVariables that should replace the type variables found in this - * type. - * - * @param typeVarToStructure a mapping from TypeVariable to TypeVariableStructure - */ - public void findAllReplacements( - Map typeVarToStructure) { - this.annotatedTypeVariables = new ArrayList<>(typeVarsInType.size()); - this.replacementList = new ArrayList<>(typeVarsInType.size()); - for (IPair pair : typeVarsInType) { - TypeVariableStructure targetStructure = typeVarToStructure.get(pair.second); - AnnotatedTypeVariable template = - targetStructure.annotatedTypeVar.deepCopy().asUse(); - annotatedTypeVariables.add(template); - replacementList.add(IPair.of(pair.first, template)); - } - } + /** + * Creates an {@link TypeVariableStructure} + * + * @param annotatedTypeVar annotated type for the type variable whose structure is being + * described + */ + public TypeVariableStructure(AnnotatedTypeVariable annotatedTypeVar) { + this.typeVar = annotatedTypeVar.getUnderlyingType(); + this.annotatedTypeVar = annotatedTypeVar; + } + } + + /** + * A list of {@link TypePathNode}s. Each node represents a "location" of a composite type. For + * example, an {@link UpperBoundNode} represents the upper bound type of a type variable + */ + @SuppressWarnings("serial") + private static class TypePath extends ArrayList { + + @Override + public String toString() { + return StringsPlume.join(",", this); + } - /** List of {@link AnnotatedTypeVariable}s found in this type. */ - private List annotatedTypeVariables; - - /** - * A list of all AnnotatedTypeVariables found in this type. {@link - * #findAllReplacements(Map)} must be called first. - * - * @return a list of all AnnotatedTypeVariables found in this type - */ - public List getAnnotatedTypeVars() { - if (annotatedTypeVariables == null) { - throw new BugInCF("Call createReplacementList before calling this method."); - } - return annotatedTypeVariables; - } + /** + * Create a copy of this path. + * + * @return a copy of this path + */ + public TypePath copy() { + TypePath copy = new TypePath(); + for (TypePathNode node : this) { + copy.add(node.copy()); + } + return copy; + } - /** - * Replaces all type variables in {@code type} with their replacements. ({@link - * #findAllReplacements(Map)} must be called first so that the replacements can be found.) - * - * @param type annotated type whose type variables are replaced - */ - public void replaceTypeVariablesInType(AnnotatedTypeMirror type) { - if (replacementList == null) { - throw new BugInCF("Call createReplacementList before calling this method."); - } - for (IPair entry : replacementList) { - TypePath path = entry.first; - AnnotatedTypeVariable replacement = entry.second; - path.replaceTypeVariable(type, replacement); - } - } + /** + * Return the leaf node of this path. + * + * @return the leaf node or null if the path is empty + */ + public @Nullable TypePathNode getLeaf() { + if (this.isEmpty()) { + return null; + } + return this.get(size() - 1); } - /** A {@link RecursiveTypeStructure} for a type variable. */ - private static class TypeVariableStructure extends RecursiveTypeStructure { - /** The type variable whose structure is being described. */ - public final TypeVariable typeVar; - - /** - * The first annotated type variable that was encountered and traversed in order to describe - * typeVar. It is expanded during visitation and it is later used as a template for other - * uses of typeVar - */ - public final AnnotatedTypeVariable annotatedTypeVar; - - /** - * Creates an {@link TypeVariableStructure} - * - * @param annotatedTypeVar annotated type for the type variable whose structure is being - * described - */ - public TypeVariableStructure(AnnotatedTypeVariable annotatedTypeVar) { - this.typeVar = annotatedTypeVar.getUnderlyingType(); - this.annotatedTypeVar = annotatedTypeVar; - } + /** Remove the leaf node if one exists. */ + public void removeLeaf() { + if (this.isEmpty()) { + return; + } + this.remove(size() - 1); } /** - * A list of {@link TypePathNode}s. Each node represents a "location" of a composite type. For - * example, an {@link UpperBoundNode} represents the upper bound type of a type variable + * In {@code type}, replace the type at the location specified by this path with {@code + * replacement}. + * + * @param type annotated type that is side-effected + * @param replacement annotated type to add to {@code type} */ - @SuppressWarnings("serial") - private static class TypePath extends ArrayList { + public void replaceTypeVariable(AnnotatedTypeMirror type, AnnotatedTypeVariable replacement) { + AnnotatedTypeMirror current = type; + for (int i = 0; i < size() - 1; i++) { + current = get(i).getType(current); + } + this.getLeaf().replaceType(current, replacement); + } + } - @Override - public String toString() { - return StringsPlume.join(",", this); - } + /** + * A {@link TypePathNode} represents a "location" of a composite type. For example, an {@link + * UpperBoundNode} represents the upper bound type of a type variable. + */ + private abstract static class TypePathNode { - /** - * Create a copy of this path. - * - * @return a copy of this path - */ - public TypePath copy() { - TypePath copy = new TypePath(); - for (TypePathNode node : this) { - copy.add(node.copy()); - } - return copy; - } + /** The {@link TypeKind} of the parent of this node. */ + public final TypeKind parentTypeKind; - /** - * Return the leaf node of this path. - * - * @return the leaf node or null if the path is empty - */ - public @Nullable TypePathNode getLeaf() { - if (this.isEmpty()) { - return null; - } - return this.get(size() - 1); - } + /** + * Creates a {@link TypePathNode}. + * + * @param parentTypeKind kind of parent of this node + */ + TypePathNode(TypeKind parentTypeKind) { + this.parentTypeKind = parentTypeKind; + } - /** Remove the leaf node if one exists. */ - public void removeLeaf() { - if (this.isEmpty()) { - return; - } - this.remove(size() - 1); - } + /** + * A copy constructor. + * + * @param template node to copy + */ + TypePathNode(TypePathNode template) { + this.parentTypeKind = template.parentTypeKind; + } - /** - * In {@code type}, replace the type at the location specified by this path with {@code - * replacement}. - * - * @param type annotated type that is side-effected - * @param replacement annotated type to add to {@code type} - */ - public void replaceTypeVariable( - AnnotatedTypeMirror type, AnnotatedTypeVariable replacement) { - AnnotatedTypeMirror current = type; - for (int i = 0; i < size() - 1; i++) { - current = get(i).getType(current); - } - this.getLeaf().replaceType(current, replacement); - } + @Override + public String toString() { + return this.getClass().getSimpleName(); } /** - * A {@link TypePathNode} represents a "location" of a composite type. For example, an {@link - * UpperBoundNode} represents the upper bound type of a type variable. + * Returns the annotated type at the location represented by this node in {@code type}. + * + * @param type parent type + * @return the annotated type at the location represented by this node in {@code type} + * @throws BugInCF if {@code type} does not have a type at this location */ - private abstract static class TypePathNode { - - /** The {@link TypeKind} of the parent of this node. */ - public final TypeKind parentTypeKind; - - /** - * Creates a {@link TypePathNode}. - * - * @param parentTypeKind kind of parent of this node - */ - TypePathNode(TypeKind parentTypeKind) { - this.parentTypeKind = parentTypeKind; - } + public final AnnotatedTypeMirror getType(AnnotatedTypeMirror type) { + abortIfNotKind(parentTypeKind, null, type); + return getTypeInternal(type); + } - /** - * A copy constructor. - * - * @param template node to copy - */ - TypePathNode(TypePathNode template) { - this.parentTypeKind = template.parentTypeKind; - } + /** + * Internal implementation of {@link #getType(AnnotatedTypeMirror)}. + * + * @param parent type that is sideffected by this method + * @return the annotated type at the location represented by this node in {@code type} + */ + protected abstract AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent); - @Override - public String toString() { - return this.getClass().getSimpleName(); - } + /** + * Replaces the type at the location represented by this node in {@code parent} with {@code + * replacement}. + * + * @param parent type that is sideffected by this method + * @param replacement the replacement + * @throws BugInCF if {@code type} does not have a type at this location + */ + public final void replaceType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + abortIfNotKind(parentTypeKind, replacement, parent); + replaceTypeInternal(parent, replacement); + } - /** - * Returns the annotated type at the location represented by this node in {@code type}. - * - * @param type parent type - * @return the annotated type at the location represented by this node in {@code type} - * @throws BugInCF if {@code type} does not have a type at this location - */ - public final AnnotatedTypeMirror getType(AnnotatedTypeMirror type) { - abortIfNotKind(parentTypeKind, null, type); - return getTypeInternal(type); - } + /** + * Internal implementation of #replaceType. + * + * @param parent type that is sideffected by this method + * @param replacement the replacement + */ + protected abstract void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement); - /** - * Internal implementation of {@link #getType(AnnotatedTypeMirror)}. - * - * @param parent type that is sideffected by this method - * @return the annotated type at the location represented by this node in {@code type} - */ - protected abstract AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent); - - /** - * Replaces the type at the location represented by this node in {@code parent} with {@code - * replacement}. - * - * @param parent type that is sideffected by this method - * @param replacement the replacement - * @throws BugInCF if {@code type} does not have a type at this location - */ - public final void replaceType( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - abortIfNotKind(parentTypeKind, replacement, parent); - replaceTypeInternal(parent, replacement); - } + /** + * Returns a copy of the node. + * + * @return a copy of this node + */ + public abstract TypePathNode copy(); - /** - * Internal implementation of #replaceType. - * - * @param parent type that is sideffected by this method - * @param replacement the replacement - */ - protected abstract void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement); - - /** - * Returns a copy of the node. - * - * @return a copy of this node - */ - public abstract TypePathNode copy(); - - /** - * Throws a {@link BugInCF} if {@code parent} is {@code typeKind}. - * - * @param typeKind the desired TypeKind - * @param replacement for debugging - * @param parent possible parent type of this node - * @throws BugInCF if {@code parent.getKind()} is not {@code typeKind} - */ - private void abortIfNotKind( - TypeKind typeKind, AnnotatedTypeVariable replacement, AnnotatedTypeMirror parent) { - if (parent.getKind() == typeKind) { - return; - } - - throw new BugInCF( - "Unexpected parent kind:%nparent= %s%nreplacements= %s%n expected= %s", - parent, replacement, typeKind); - } + /** + * Throws a {@link BugInCF} if {@code parent} is {@code typeKind}. + * + * @param typeKind the desired TypeKind + * @param replacement for debugging + * @param parent possible parent type of this node + * @throws BugInCF if {@code parent.getKind()} is not {@code typeKind} + */ + private void abortIfNotKind( + TypeKind typeKind, AnnotatedTypeVariable replacement, AnnotatedTypeMirror parent) { + if (parent.getKind() == typeKind) { + return; + } + + throw new BugInCF( + "Unexpected parent kind:%nparent= %s%nreplacements= %s%n expected= %s", + parent, replacement, typeKind); } + } - /** Represents an enclosing type of a declared type. */ - private static class EnclosingTypeNode extends TypePathNode { + /** Represents an enclosing type of a declared type. */ + private static class EnclosingTypeNode extends TypePathNode { - /** Create an enclosing node. */ - EnclosingTypeNode() { - super(TypeKind.DECLARED); - } + /** Create an enclosing node. */ + EnclosingTypeNode() { + super(TypeKind.DECLARED); + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - // An enclosing type cannot be a type variable, so do nothing. - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + // An enclosing type cannot be a type variable, so do nothing. + } - @Override - public AnnotatedDeclaredType getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedDeclaredType) parent).getEnclosingType(); - } + @Override + public AnnotatedDeclaredType getTypeInternal(AnnotatedTypeMirror parent) { + return ((AnnotatedDeclaredType) parent).getEnclosingType(); + } - @Override - public TypePathNode copy() { - return new EnclosingTypeNode(); - } + @Override + public TypePathNode copy() { + return new EnclosingTypeNode(); } + } - /** Represents an extends bound of a wildcard. */ - private static class ExtendsBoundNode extends TypePathNode { - /** Creates an ExtendsBoundNode. */ - ExtendsBoundNode() { - super(TypeKind.WILDCARD); - } + /** Represents an extends bound of a wildcard. */ + private static class ExtendsBoundNode extends TypePathNode { + /** Creates an ExtendsBoundNode. */ + ExtendsBoundNode() { + super(TypeKind.WILDCARD); + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedWildcardType) parent).setExtendsBound(replacement); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + ((AnnotatedWildcardType) parent).setExtendsBound(replacement); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedWildcardType) parent).getExtendsBound(); - } + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + return ((AnnotatedWildcardType) parent).getExtendsBound(); + } - @Override - public TypePathNode copy() { - return new ExtendsBoundNode(); - } + @Override + public TypePathNode copy() { + return new ExtendsBoundNode(); } + } - /** Represents a super bound of a wildcard. */ - private static class SuperBoundNode extends TypePathNode { - /** Creates a SuperBoundNode. */ - SuperBoundNode() { - super(TypeKind.WILDCARD); - } + /** Represents a super bound of a wildcard. */ + private static class SuperBoundNode extends TypePathNode { + /** Creates a SuperBoundNode. */ + SuperBoundNode() { + super(TypeKind.WILDCARD); + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedWildcardType) parent).setSuperBound(replacement); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + ((AnnotatedWildcardType) parent).setSuperBound(replacement); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedWildcardType) parent).getSuperBound(); - } + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + return ((AnnotatedWildcardType) parent).getSuperBound(); + } - @Override - public TypePathNode copy() { - return new SuperBoundNode(); - } + @Override + public TypePathNode copy() { + return new SuperBoundNode(); } + } - /** Represents an upper bound of a type variable. */ - private static class UpperBoundNode extends TypePathNode { + /** Represents an upper bound of a type variable. */ + private static class UpperBoundNode extends TypePathNode { - /** Creates an UpperBoundNode. */ - UpperBoundNode() { - super(TypeKind.TYPEVAR); - } + /** Creates an UpperBoundNode. */ + UpperBoundNode() { + super(TypeKind.TYPEVAR); + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedTypeVariable) parent).setUpperBound(replacement); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + ((AnnotatedTypeVariable) parent).setUpperBound(replacement); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; - if (parentAtv.getUpperBoundField() != null) { - return parentAtv.getUpperBoundField(); - } - return createAndSetUpperBound((AnnotatedTypeVariable) parent); - } + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; + if (parentAtv.getUpperBoundField() != null) { + return parentAtv.getUpperBoundField(); + } + return createAndSetUpperBound((AnnotatedTypeVariable) parent); + } - @Override - public TypePathNode copy() { - return new UpperBoundNode(); - } + @Override + public TypePathNode copy() { + return new UpperBoundNode(); } + } - /** Represents a lower bound of a type variable. */ - private static class LowerBoundNode extends TypePathNode { + /** Represents a lower bound of a type variable. */ + private static class LowerBoundNode extends TypePathNode { - /** Creates a LowerBoundNode. */ - LowerBoundNode() { - super(TypeKind.TYPEVAR); - } + /** Creates a LowerBoundNode. */ + LowerBoundNode() { + super(TypeKind.TYPEVAR); + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedTypeVariable) parent).setLowerBound(replacement); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + ((AnnotatedTypeVariable) parent).setLowerBound(replacement); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; - if (parentAtv.getLowerBoundField() != null) { - return parentAtv.getLowerBoundField(); - } - // else // TODO: I think this should never happen at this point, throw exception - return createAndSetLowerBound((AnnotatedTypeVariable) parent); - } + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; + if (parentAtv.getLowerBoundField() != null) { + return parentAtv.getLowerBoundField(); + } + // else // TODO: I think this should never happen at this point, throw exception + return createAndSetLowerBound((AnnotatedTypeVariable) parent); + } - @Override - public TypePathNode copy() { - return new LowerBoundNode(); - } + @Override + public TypePathNode copy() { + return new LowerBoundNode(); } + } - /** Represents a component type of an array type. */ - private static class ArrayComponentNode extends TypePathNode { + /** Represents a component type of an array type. */ + private static class ArrayComponentNode extends TypePathNode { - /** Create ArrayComponentNode. */ - ArrayComponentNode() { - super(TypeKind.ARRAY); - } + /** Create ArrayComponentNode. */ + ArrayComponentNode() { + super(TypeKind.ARRAY); + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedArrayType) parent).setComponentType(replacement); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + ((AnnotatedArrayType) parent).setComponentType(replacement); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedArrayType) parent).getComponentType(); - } + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + return ((AnnotatedArrayType) parent).getComponentType(); + } - @Override - public TypePathNode copy() { - return new ArrayComponentNode(); - } + @Override + public TypePathNode copy() { + return new ArrayComponentNode(); } + } - /** A bound type of an intersection type. */ - private static class IntersectionBoundNode extends TypePathNode { + /** A bound type of an intersection type. */ + private static class IntersectionBoundNode extends TypePathNode { - /** The index of the particular bound type of an intersection type this node represents. */ - public final int boundIndex; + /** The index of the particular bound type of an intersection type this node represents. */ + public final int boundIndex; - /** - * Creates an IntersectionBoundNode. - * - * @param boundIndex the index of the particular bound type of an intersection type this - * node represents - */ - IntersectionBoundNode(int boundIndex) { - super(TypeKind.INTERSECTION); - this.boundIndex = boundIndex; - } + /** + * Creates an IntersectionBoundNode. + * + * @param boundIndex the index of the particular bound type of an intersection type this node + * represents + */ + IntersectionBoundNode(int boundIndex) { + super(TypeKind.INTERSECTION); + this.boundIndex = boundIndex; + } - /** - * Copy constructor. - * - * @param template node to copy - */ - IntersectionBoundNode(IntersectionBoundNode template) { - super(template); - boundIndex = template.boundIndex; - } + /** + * Copy constructor. + * + * @param template node to copy + */ + IntersectionBoundNode(IntersectionBoundNode template) { + super(template); + boundIndex = template.boundIndex; + } - @Override - public String toString() { - return super.toString() + "( superIndex=" + boundIndex + " )"; - } + @Override + public String toString() { + return super.toString() + "( superIndex=" + boundIndex + " )"; + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) parent; - List bounds = new ArrayList<>(intersection.bounds); - bounds.set(boundIndex, replacement); - intersection.setBounds(bounds); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) parent; + List bounds = new ArrayList<>(intersection.bounds); + bounds.set(boundIndex, replacement); + intersection.setBounds(bounds); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedIntersectionType isect = (AnnotatedIntersectionType) parent; - if (isect.getBounds().size() <= boundIndex) { - throw new BugInCF("Invalid superIndex %d: parent=%s", boundIndex, parent); - } + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + AnnotatedIntersectionType isect = (AnnotatedIntersectionType) parent; + if (isect.getBounds().size() <= boundIndex) { + throw new BugInCF("Invalid superIndex %d: parent=%s", boundIndex, parent); + } - return isect.getBounds().get(boundIndex); - } + return isect.getBounds().get(boundIndex); + } - @Override - public TypePathNode copy() { - return new IntersectionBoundNode(this); - } + @Override + public TypePathNode copy() { + return new IntersectionBoundNode(this); } + } - /** Represents an alternative type of a union node. */ - private static class AlternativeTypeNode extends TypePathNode { + /** Represents an alternative type of a union node. */ + private static class AlternativeTypeNode extends TypePathNode { - /** - * The index of the particular alternative type of the union node that this node represents. - */ - public final int altIndex; + /** The index of the particular alternative type of the union node that this node represents. */ + public final int altIndex; - /** - * Creates a AlternativeTypeNode. - * - * @param altIndex the index of the particular alternative type of the union node that this - * node represents - */ - AlternativeTypeNode(int altIndex) { - super(TypeKind.UNION); - this.altIndex = altIndex; - } + /** + * Creates a AlternativeTypeNode. + * + * @param altIndex the index of the particular alternative type of the union node that this node + * represents + */ + AlternativeTypeNode(int altIndex) { + super(TypeKind.UNION); + this.altIndex = altIndex; + } - /** - * Copy constructor. - * - * @param template node to copy - */ - AlternativeTypeNode(AlternativeTypeNode template) { - super(template); - altIndex = template.altIndex; - } + /** + * Copy constructor. + * + * @param template node to copy + */ + AlternativeTypeNode(AlternativeTypeNode template) { + super(template); + altIndex = template.altIndex; + } - @Override - public String toString() { - return super.toString() + "( altIndex=" + altIndex + " )"; - } + @Override + public String toString() { + return super.toString() + "( altIndex=" + altIndex + " )"; + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - throw new BugInCF( - "Union types cannot be intersection bounds.%nparent=%s%nreplacement=%s", - parent, replacement); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + throw new BugInCF( + "Union types cannot be intersection bounds.%nparent=%s%nreplacement=%s", + parent, replacement); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedUnionType isect = (AnnotatedUnionType) parent; - if (parent.directSupertypes().size() <= altIndex) { - throw new BugInCF("Invalid altIndex( %s ):%nparent=%s", altIndex, parent); - } + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + AnnotatedUnionType isect = (AnnotatedUnionType) parent; + if (parent.directSupertypes().size() <= altIndex) { + throw new BugInCF("Invalid altIndex( %s ):%nparent=%s", altIndex, parent); + } - return isect.directSupertypes().get(altIndex); - } + return isect.directSupertypes().get(altIndex); + } - @Override - public TypePathNode copy() { - return new AlternativeTypeNode(this); - } + @Override + public TypePathNode copy() { + return new AlternativeTypeNode(this); } + } - /** Represents a type argument of a declared type. */ - private static class TypeArgNode extends TypePathNode { + /** Represents a type argument of a declared type. */ + private static class TypeArgNode extends TypePathNode { - /** The index of the type argument that this node represents. */ - public final int argIndex; + /** The index of the type argument that this node represents. */ + public final int argIndex; - /** - * Creates a TypeArgumentNode. - * - * @param argIndex index of the type argument that this node represents - */ - TypeArgNode(int argIndex) { - super(TypeKind.DECLARED); - this.argIndex = argIndex; - } + /** + * Creates a TypeArgumentNode. + * + * @param argIndex index of the type argument that this node represents + */ + TypeArgNode(int argIndex) { + super(TypeKind.DECLARED); + this.argIndex = argIndex; + } - /** - * Copy constructor. - * - * @param template node to copy - */ - TypeArgNode(TypeArgNode template) { - super(template); - this.argIndex = template.argIndex; - } + /** + * Copy constructor. + * + * @param template node to copy + */ + TypeArgNode(TypeArgNode template) { + super(template); + this.argIndex = template.argIndex; + } - @Override - public String toString() { - return super.toString() + "( argIndex=" + argIndex + " )"; - } + @Override + public String toString() { + return super.toString() + "( argIndex=" + argIndex + " )"; + } - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; - List typeArgs = new ArrayList<>(parentAdt.getTypeArguments()); - if (argIndex >= typeArgs.size()) { - throw new BugInCF( - StringsPlume.joinLines( - "Invalid type arg index.", - "parent=" + parent, - "replacement=" + replacement, - "argIndex=" + argIndex)); - } - typeArgs.add(argIndex, replacement); - typeArgs.remove(argIndex + 1); - parentAdt.setTypeArguments(typeArgs); - } + @Override + protected void replaceTypeInternal( + AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { + AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; + List typeArgs = new ArrayList<>(parentAdt.getTypeArguments()); + if (argIndex >= typeArgs.size()) { + throw new BugInCF( + StringsPlume.joinLines( + "Invalid type arg index.", + "parent=" + parent, + "replacement=" + replacement, + "argIndex=" + argIndex)); + } + typeArgs.add(argIndex, replacement); + typeArgs.remove(argIndex + 1); + parentAdt.setTypeArguments(typeArgs); + } - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; + @Override + protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { + AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; - List typeArgs = parentAdt.getTypeArguments(); - if (argIndex >= typeArgs.size()) { - throw new BugInCF( - StringsPlume.joinLines( - "Invalid type arg index.", - "parent=" + parent, - "argIndex=" + argIndex)); - } + List typeArgs = parentAdt.getTypeArguments(); + if (argIndex >= typeArgs.size()) { + throw new BugInCF( + StringsPlume.joinLines( + "Invalid type arg index.", "parent=" + parent, "argIndex=" + argIndex)); + } - return typeArgs.get(argIndex); - } + return typeArgs.get(argIndex); + } - @Override - public TypePathNode copy() { - return new TypeArgNode(this); - } + @Override + public TypePathNode copy() { + return new TypeArgNode(this); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java b/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java index 6e8e9057adf..76efc2b6e7e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java @@ -6,16 +6,14 @@ import com.sun.tools.javac.code.Attribute.Compound; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.util.List; - +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeAnnotationUtils; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; - /** * A helper class that puts the declaration annotations from a method declaration back into the * corresponding Elements, so that they get stored in the bytecode by the compiler. @@ -26,50 +24,50 @@ */ public final class DeclarationsIntoElements { - /** Do not instantiate. */ - private DeclarationsIntoElements() { - throw new AssertionError("Class DeclarationsIntoElements cannot be instantiated."); - } + /** Do not instantiate. */ + private DeclarationsIntoElements() { + throw new AssertionError("Class DeclarationsIntoElements cannot be instantiated."); + } - /** - * The entry point. - * - * @param atypeFactory the type factory - * @param tree the ClassTree to process - */ - public static void store( - ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, ClassTree tree) { - for (Tree mem : tree.getMembers()) { - if (mem.getKind() == Tree.Kind.METHOD) { - storeMethod(env, atypeFactory, (MethodTree) mem); - } - } + /** + * The entry point. + * + * @param atypeFactory the type factory + * @param tree the ClassTree to process + */ + public static void store( + ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, ClassTree tree) { + for (Tree mem : tree.getMembers()) { + if (mem.getKind() == Tree.Kind.METHOD) { + storeMethod(env, atypeFactory, (MethodTree) mem); + } } + } - /** - * Add inherited declaration annotations from overridden methods into the corresponding Elements - * so they are written into bytecode. - * - * @param env the processing environment - * @param atypeFactory the type factory - * @param meth the MethodTree to add the annotations - */ - private static void storeMethod( - ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, MethodTree meth) { - ExecutableElement element = TreeUtils.elementFromDeclaration(meth); - MethodSymbol sym = (MethodSymbol) element; - java.util.List elementAnnos = element.getAnnotationMirrors(); + /** + * Add inherited declaration annotations from overridden methods into the corresponding Elements + * so they are written into bytecode. + * + * @param env the processing environment + * @param atypeFactory the type factory + * @param meth the MethodTree to add the annotations + */ + private static void storeMethod( + ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, MethodTree meth) { + ExecutableElement element = TreeUtils.elementFromDeclaration(meth); + MethodSymbol sym = (MethodSymbol) element; + java.util.List elementAnnos = element.getAnnotationMirrors(); - AnnotationMirrorSet declAnnotations = atypeFactory.getDeclAnnotations(sym); - List tcs = List.nil(); + AnnotationMirrorSet declAnnotations = atypeFactory.getDeclAnnotations(sym); + List tcs = List.nil(); - for (AnnotationMirror anno : declAnnotations) { - // Only add the annotation if it isn't in the Element already. - if (!AnnotationUtils.containsSame(elementAnnos, anno)) { - tcs = tcs.append(TypeAnnotationUtils.createCompoundFromAnnotationMirror(anno, env)); - } - } - - sym.appendAttributes(tcs); + for (AnnotationMirror anno : declAnnotations) { + // Only add the annotation if it isn't in the Element already. + if (!AnnotationUtils.containsSame(elementAnnos, anno)) { + tcs = tcs.append(TypeAnnotationUtils.createCompoundFromAnnotationMirror(anno, env)); + } } + + sym.appendAttributes(tcs); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java index af7aac68aee..400f4c4fd39 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java @@ -1,7 +1,15 @@ package org.checkerframework.framework.type; import com.sun.tools.javac.code.Type; - +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVariable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -20,17 +28,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.WeakIdentityHashMap; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; - -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; - /** * An AnnotatedTypeFormatter used by default by all AnnotatedTypeFactory (and therefore all * annotated types). @@ -40,464 +37,458 @@ */ public class DefaultAnnotatedTypeFormatter implements AnnotatedTypeFormatter { - /** The formatting visitor. */ - protected final FormattingVisitor formattingVisitor; + /** The formatting visitor. */ + protected final FormattingVisitor formattingVisitor; + + /** + * Constructs a DefaultAnnotatedTypeFormatter that does not print invisible annotations by + * default. + */ + public DefaultAnnotatedTypeFormatter() { + this(new DefaultAnnotationFormatter(), true, false); + } + + /** + * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more + * information + * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print + * invisible annotations + */ + public DefaultAnnotatedTypeFormatter( + boolean printVerboseGenerics, boolean defaultPrintInvisibleAnnos) { + this(new DefaultAnnotationFormatter(), printVerboseGenerics, defaultPrintInvisibleAnnos); + } + + /** + * @param formatter an object that converts annotation mirrors to strings + * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more + * information + * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print + * invisible annotations + */ + public DefaultAnnotatedTypeFormatter( + AnnotationFormatter formatter, + boolean printVerboseGenerics, + boolean defaultPrintInvisibleAnnos) { + this(new FormattingVisitor(formatter, printVerboseGenerics, defaultPrintInvisibleAnnos)); + } + + /** + * Used by subclasses and other constructors to specify the underlying implementation of this + * DefaultAnnotatedTypeFormatter. + */ + protected DefaultAnnotatedTypeFormatter(FormattingVisitor visitor) { + this.formattingVisitor = visitor; + } + + /** + * Maps from type variables to deterministic IDs. This is useful for comparing output across two + * runs of the Checker Framework. + * + *

This map is necessary for deterministic and informative output. javac might print two + * distinct capture-converted variables as "capture#222" if the second is created after the first + * is garbage-collected, or if they just happen to have the same hash code based on memory layout. + * Such javac output is misleading because it looks like the two printed representations refer to + * the same variable. + * + *

This map contains type variables that have been formatted. Therefore, the numbers may differ + * between Checker Framework runs if the different runs print different values (say, one of them + * prints more type variables than the other). + */ + protected static final Map captureConversionIds = + new WeakIdentityHashMap<>(); + + /** The last deterministic capture conversion ID that was used. */ + protected static int prevCaptureConversionId = 0; + + /** + * Returns a deterministic capture conversion ID for the given javac captured type. + * + * @param capturedType a type variable, which must be a capture-converted type variable + * @return a deterministic capture conversion ID + */ + protected static int getCaptureConversionId(TypeVariable capturedType) { + return captureConversionIds.computeIfAbsent(capturedType, key -> ++prevCaptureConversionId); + } + + @Override + public String format(AnnotatedTypeMirror type) { + formattingVisitor.resetPrintVerboseSettings(); + return formattingVisitor.visit(type); + } + + @Override + public String format(AnnotatedTypeMirror type, boolean printVerbose) { + formattingVisitor.setVerboseSettings(printVerbose); + return formattingVisitor.visit(type); + } + + /** A scanning visitor that prints the entire AnnotatedTypeMirror passed to visit. */ + protected static class FormattingVisitor + implements AnnotatedTypeVisitor> { + + /** The object responsible for converting annotations to strings. */ + protected final AnnotationFormatter annoFormatter; /** - * Constructs a DefaultAnnotatedTypeFormatter that does not print invisible annotations by - * default. + * Represents whether or not invisible annotations should be printed if the client of this class + * does not use the printInvisibleAnnos parameter. */ - public DefaultAnnotatedTypeFormatter() { - this(new DefaultAnnotationFormatter(), true, false); - } + protected final boolean defaultInvisiblesSetting; /** - * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more - * information - * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print - * invisible annotations + * For a given call to format, this setting specifies whether or not to printInvisibles. If a + * user did not specify a printInvisible parameter in the call to format then this value will + * equal DefaultAnnotatedTypeFormatter.defaultInvisibleSettings for this object */ - public DefaultAnnotatedTypeFormatter( - boolean printVerboseGenerics, boolean defaultPrintInvisibleAnnos) { - this(new DefaultAnnotationFormatter(), printVerboseGenerics, defaultPrintInvisibleAnnos); - } + protected boolean currentPrintInvisibleSetting; - /** - * @param formatter an object that converts annotation mirrors to strings - * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more - * information - * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print - * invisible annotations - */ - public DefaultAnnotatedTypeFormatter( - AnnotationFormatter formatter, - boolean printVerboseGenerics, - boolean defaultPrintInvisibleAnnos) { - this(new FormattingVisitor(formatter, printVerboseGenerics, defaultPrintInvisibleAnnos)); - } + /** Default value of currentPrintVerboseGenerics. */ + protected final boolean defaultPrintVerboseGenerics; /** - * Used by subclasses and other constructors to specify the underlying implementation of this - * DefaultAnnotatedTypeFormatter. + * Prints type variables in a less ambiguous manner using [] to delimit them. Always prints both + * bounds even if they lower bound is an AnnotatedNull type. */ - protected DefaultAnnotatedTypeFormatter(FormattingVisitor visitor) { - this.formattingVisitor = visitor; - } + protected boolean currentPrintVerboseGenerics; + + /** Whether the visitor is currently printing a raw type. */ + protected boolean currentlyPrintingRaw; /** - * Maps from type variables to deterministic IDs. This is useful for comparing output across two - * runs of the Checker Framework. - * - *

This map is necessary for deterministic and informative output. javac might print two - * distinct capture-converted variables as "capture#222" if the second is created after the - * first is garbage-collected, or if they just happen to have the same hash code based on memory - * layout. Such javac output is misleading because it looks like the two printed representations - * refer to the same variable. + * Creates the visitor. * - *

This map contains type variables that have been formatted. Therefore, the numbers may - * differ between Checker Framework runs if the different runs print different values (say, one - * of them prints more type variables than the other). + * @param annoFormatter formatter used for {@code AnnotationMirror}s + * @param printVerboseGenerics whether to verbosely print type variables and wildcards + * @param defaultInvisiblesSetting whether to print invisible qualifiers */ - protected static final Map captureConversionIds = - new WeakIdentityHashMap<>(); + public FormattingVisitor( + AnnotationFormatter annoFormatter, + boolean printVerboseGenerics, + boolean defaultInvisiblesSetting) { + this.annoFormatter = annoFormatter; + this.defaultPrintVerboseGenerics = printVerboseGenerics; + this.currentPrintVerboseGenerics = printVerboseGenerics; + this.defaultInvisiblesSetting = defaultInvisiblesSetting; + this.currentPrintInvisibleSetting = false; + this.currentlyPrintingRaw = false; + } - /** The last deterministic capture conversion ID that was used. */ - protected static int prevCaptureConversionId = 0; + /** Set the current verbose settings to use while printing. */ + protected void setVerboseSettings(boolean printVerbose) { + this.currentPrintInvisibleSetting = printVerbose; + this.currentPrintVerboseGenerics = printVerbose; + } + + /** Set verbose settings to the default. */ + protected void resetPrintVerboseSettings() { + this.currentPrintInvisibleSetting = defaultInvisiblesSetting; + this.currentPrintVerboseGenerics = defaultPrintVerboseGenerics; + } /** - * Returns a deterministic capture conversion ID for the given javac captured type. - * - * @param capturedType a type variable, which must be a capture-converted type variable - * @return a deterministic capture conversion ID + * Print, to sb, {@code keyWord} followed by {@code field}. NULL types are substituted with + * their annotations followed by " Void" */ - protected static int getCaptureConversionId(TypeVariable capturedType) { - return captureConversionIds.computeIfAbsent(capturedType, key -> ++prevCaptureConversionId); + @SideEffectFree + protected void printBound( + String keyWord, + AnnotatedTypeMirror field, + Set visiting, + StringBuilder sb) { + if (!currentPrintVerboseGenerics && (field == null || field.getKind() == TypeKind.NULL)) { + return; + } + + sb.append(" "); + sb.append(keyWord); + sb.append(" "); + + if (field == null) { + sb.append(""); + } else if (field.getKind() != TypeKind.NULL) { + sb.append(visit(field, visiting)); + } else { + sb.append( + annoFormatter.formatAnnotationString( + field.getAnnotations(), currentPrintInvisibleSetting)); + sb.append("Void"); + } } + @SideEffectFree @Override - public String format(AnnotatedTypeMirror type) { - formattingVisitor.resetPrintVerboseSettings(); - return formattingVisitor.visit(type); + public String visit(AnnotatedTypeMirror type) { + return type.accept(this, Collections.newSetFromMap(new IdentityHashMap<>())); } @Override - public String format(AnnotatedTypeMirror type, boolean printVerbose) { - formattingVisitor.setVerboseSettings(printVerbose); - return formattingVisitor.visit(type); + public String visit(AnnotatedTypeMirror type, Set annotatedTypeVariables) { + return type.accept(this, annotatedTypeVariables); } - /** A scanning visitor that prints the entire AnnotatedTypeMirror passed to visit. */ - protected static class FormattingVisitor - implements AnnotatedTypeVisitor> { - - /** The object responsible for converting annotations to strings. */ - protected final AnnotationFormatter annoFormatter; - - /** - * Represents whether or not invisible annotations should be printed if the client of this - * class does not use the printInvisibleAnnos parameter. - */ - protected final boolean defaultInvisiblesSetting; - - /** - * For a given call to format, this setting specifies whether or not to printInvisibles. If - * a user did not specify a printInvisible parameter in the call to format then this value - * will equal DefaultAnnotatedTypeFormatter.defaultInvisibleSettings for this object - */ - protected boolean currentPrintInvisibleSetting; - - /** Default value of currentPrintVerboseGenerics. */ - protected final boolean defaultPrintVerboseGenerics; - - /** - * Prints type variables in a less ambiguous manner using [] to delimit them. Always prints - * both bounds even if they lower bound is an AnnotatedNull type. - */ - protected boolean currentPrintVerboseGenerics; - - /** Whether the visitor is currently printing a raw type. */ - protected boolean currentlyPrintingRaw; - - /** - * Creates the visitor. - * - * @param annoFormatter formatter used for {@code AnnotationMirror}s - * @param printVerboseGenerics whether to verbosely print type variables and wildcards - * @param defaultInvisiblesSetting whether to print invisible qualifiers - */ - public FormattingVisitor( - AnnotationFormatter annoFormatter, - boolean printVerboseGenerics, - boolean defaultInvisiblesSetting) { - this.annoFormatter = annoFormatter; - this.defaultPrintVerboseGenerics = printVerboseGenerics; - this.currentPrintVerboseGenerics = printVerboseGenerics; - this.defaultInvisiblesSetting = defaultInvisiblesSetting; - this.currentPrintInvisibleSetting = false; - this.currentlyPrintingRaw = false; - } - - /** Set the current verbose settings to use while printing. */ - protected void setVerboseSettings(boolean printVerbose) { - this.currentPrintInvisibleSetting = printVerbose; - this.currentPrintVerboseGenerics = printVerbose; + @Override + public String visitDeclared(AnnotatedDeclaredType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (type.isDeclaration() && currentPrintInvisibleSetting) { + sb.append("/*DECL*/ "); + } + + if (type.getEnclosingType() != null) { + sb.append(this.visit(type.getEnclosingType(), visiting)); + sb.append('.'); + } + Element typeElt = type.getUnderlyingType().asElement(); + String smpl = typeElt.getSimpleName().toString(); + if (smpl.isEmpty()) { + // For anonymous classes smpl is empty - toString + // of the element is more useful. + smpl = typeElt.toString(); + } + sb.append( + annoFormatter.formatAnnotationString( + type.getAnnotations(), currentPrintInvisibleSetting)); + sb.append(smpl); + + boolean oldPrintingRaw = currentlyPrintingRaw; + if (type.isUnderlyingTypeRaw()) { + currentlyPrintingRaw = true; + } + if (type.typeArgs != null) { + // getTypeArguments sets the field if it does not already exist. + List typeArgs = type.typeArgs; + if (!typeArgs.isEmpty()) { + StringJoiner sj = new StringJoiner(", ", "<", ">"); + if (!currentPrintVerboseGenerics && currentlyPrintingRaw) { + sj.add("/*RAW*/"); + } else { + for (AnnotatedTypeMirror typeArg : typeArgs) { + sj.add(visit(typeArg, visiting)); + } + } + sb.append(sj); } + } + currentlyPrintingRaw = oldPrintingRaw; + return sb.toString(); + } - /** Set verbose settings to the default. */ - protected void resetPrintVerboseSettings() { - this.currentPrintInvisibleSetting = defaultInvisiblesSetting; - this.currentPrintVerboseGenerics = defaultPrintVerboseGenerics; + @Override + public String visitIntersection( + AnnotatedIntersectionType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + + boolean isFirst = true; + for (AnnotatedTypeMirror bound : type.getBounds()) { + if (!isFirst) { + sb.append(" & "); } + sb.append(visit(bound, visiting)); + isFirst = false; + } + return sb.toString(); + } - /** - * Print, to sb, {@code keyWord} followed by {@code field}. NULL types are substituted with - * their annotations followed by " Void" - */ - @SideEffectFree - protected void printBound( - String keyWord, - AnnotatedTypeMirror field, - Set visiting, - StringBuilder sb) { - if (!currentPrintVerboseGenerics - && (field == null || field.getKind() == TypeKind.NULL)) { - return; - } + @Override + public String visitUnion(AnnotatedUnionType type, Set visiting) { + StringBuilder sb = new StringBuilder(); - sb.append(" "); - sb.append(keyWord); - sb.append(" "); - - if (field == null) { - sb.append(""); - } else if (field.getKind() != TypeKind.NULL) { - sb.append(visit(field, visiting)); - } else { - sb.append( - annoFormatter.formatAnnotationString( - field.getAnnotations(), currentPrintInvisibleSetting)); - sb.append("Void"); - } + boolean isFirst = true; + for (AnnotatedDeclaredType adt : type.getAlternatives()) { + if (!isFirst) { + sb.append(" | "); } + sb.append(visit(adt, visiting)); + isFirst = false; + } + return sb.toString(); + } - @SideEffectFree - @Override - public String visit(AnnotatedTypeMirror type) { - return type.accept(this, Collections.newSetFromMap(new IdentityHashMap<>())); + @Override + public String visitExecutable(AnnotatedExecutableType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (!type.getTypeVariables().isEmpty()) { + StringJoiner sj = new StringJoiner(", ", "<", "> "); + for (AnnotatedTypeVariable atv : type.getTypeVariables()) { + sj.add(visit(atv, visiting)); } - - @Override - public String visit( - AnnotatedTypeMirror type, Set annotatedTypeVariables) { - return type.accept(this, annotatedTypeVariables); + sb.append(sj.toString()); + } + if (type.getReturnType() != null) { + sb.append(visit(type.getReturnType(), visiting)); + } else { + sb.append(""); + } + sb.append(' '); + if (type.getElement() != null) { + sb.append(type.getElement().getSimpleName()); + } else { + sb.append("METHOD"); + } + sb.append('('); + AnnotatedDeclaredType rcv; + try { + rcv = type.getReceiverType(); + } catch (NullPointerException e) { + sb.append("[[NPE in getReceiverType()]], "); + rcv = null; + } + if (rcv != null) { + sb.append(visit(rcv, visiting)); + sb.append(" this"); + } + if (!type.getParameterTypes().isEmpty()) { + int p = 0; + for (AnnotatedTypeMirror atm : type.getParameterTypes()) { + if (rcv != null || p > 0) { + sb.append(", "); + } + sb.append(visit(atm, visiting)); + // Output some parameter names to make it look more like a method. + // TODO: go to the element and look up real parameter names, maybe. + sb.append(" p"); + sb.append(p++); } - - @Override - public String visitDeclared(AnnotatedDeclaredType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (type.isDeclaration() && currentPrintInvisibleSetting) { - sb.append("/*DECL*/ "); - } - - if (type.getEnclosingType() != null) { - sb.append(this.visit(type.getEnclosingType(), visiting)); - sb.append('.'); - } - Element typeElt = type.getUnderlyingType().asElement(); - String smpl = typeElt.getSimpleName().toString(); - if (smpl.isEmpty()) { - // For anonymous classes smpl is empty - toString - // of the element is more useful. - smpl = typeElt.toString(); - } - sb.append( - annoFormatter.formatAnnotationString( - type.getAnnotations(), currentPrintInvisibleSetting)); - sb.append(smpl); - - boolean oldPrintingRaw = currentlyPrintingRaw; - if (type.isUnderlyingTypeRaw()) { - currentlyPrintingRaw = true; - } - if (type.typeArgs != null) { - // getTypeArguments sets the field if it does not already exist. - List typeArgs = type.typeArgs; - if (!typeArgs.isEmpty()) { - StringJoiner sj = new StringJoiner(", ", "<", ">"); - if (!currentPrintVerboseGenerics && currentlyPrintingRaw) { - sj.add("/*RAW*/"); - } else { - for (AnnotatedTypeMirror typeArg : typeArgs) { - sj.add(visit(typeArg, visiting)); - } - } - sb.append(sj); - } - } - currentlyPrintingRaw = oldPrintingRaw; - return sb.toString(); + } + sb.append(')'); + if (!type.getThrownTypes().isEmpty()) { + sb.append(" throws "); + for (AnnotatedTypeMirror atm : type.getThrownTypes()) { + sb.append(visit(atm, visiting)); } + } + return sb.toString(); + } - @Override - public String visitIntersection( - AnnotatedIntersectionType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - - boolean isFirst = true; - for (AnnotatedTypeMirror bound : type.getBounds()) { - if (!isFirst) { - sb.append(" & "); - } - sb.append(visit(bound, visiting)); - isFirst = false; - } - return sb.toString(); + @Override + public String visitArray(AnnotatedArrayType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + + AnnotatedArrayType array = type; + AnnotatedTypeMirror component; + while (true) { + component = array.getComponentType(); + if (!array.getAnnotations().isEmpty()) { + sb.append(' '); + sb.append( + annoFormatter.formatAnnotationString( + array.getAnnotations(), currentPrintInvisibleSetting)); } - - @Override - public String visitUnion(AnnotatedUnionType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - - boolean isFirst = true; - for (AnnotatedDeclaredType adt : type.getAlternatives()) { - if (!isFirst) { - sb.append(" | "); - } - sb.append(visit(adt, visiting)); - isFirst = false; - } - return sb.toString(); + sb.append("[]"); + if (!(component instanceof AnnotatedArrayType)) { + sb.insert(0, visit(component, visiting)); + break; } + array = (AnnotatedArrayType) component; + } + return sb.toString(); + } - @Override - public String visitExecutable( - AnnotatedExecutableType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (!type.getTypeVariables().isEmpty()) { - StringJoiner sj = new StringJoiner(", ", "<", "> "); - for (AnnotatedTypeVariable atv : type.getTypeVariables()) { - sj.add(visit(atv, visiting)); - } - sb.append(sj.toString()); - } - if (type.getReturnType() != null) { - sb.append(visit(type.getReturnType(), visiting)); - } else { - sb.append(""); - } - sb.append(' '); - if (type.getElement() != null) { - sb.append(type.getElement().getSimpleName()); - } else { - sb.append("METHOD"); - } - sb.append('('); - AnnotatedDeclaredType rcv; - try { - rcv = type.getReceiverType(); - } catch (NullPointerException e) { - sb.append("[[NPE in getReceiverType()]], "); - rcv = null; - } - if (rcv != null) { - sb.append(visit(rcv, visiting)); - sb.append(" this"); - } - if (!type.getParameterTypes().isEmpty()) { - int p = 0; - for (AnnotatedTypeMirror atm : type.getParameterTypes()) { - if (rcv != null || p > 0) { - sb.append(", "); - } - sb.append(visit(atm, visiting)); - // Output some parameter names to make it look more like a method. - // TODO: go to the element and look up real parameter names, maybe. - sb.append(" p"); - sb.append(p++); - } - } - sb.append(')'); - if (!type.getThrownTypes().isEmpty()) { - sb.append(" throws "); - for (AnnotatedTypeMirror atm : type.getThrownTypes()) { - sb.append(visit(atm, visiting)); - } - } - return sb.toString(); + @Override + public String visitTypeVariable(AnnotatedTypeVariable type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (TypesUtils.isCapturedTypeVariable(type.underlyingType)) { + // underlyingType.toString() has this form: "capture#826 of ? extends + // java.lang.Object". + // assert underlyingType.toString().startsWith("capture#"); + // We output only the "capture#826" part. + + // TODO: If deterministic output is not needed, we could avoid the use of + // getCaptureConversionId() by using this code instead: + // sb.append(underlyingType, 0, underlyingType.indexOf(" of ")); + // The choice would be controlled by a command-line argument. + + // We output a deterministic number; we prefix it by "0" + // so we know whether a number is deterministic or from javac. + sb.append("capture#0").append(getCaptureConversionId((TypeVariable) type.underlyingType)); + } else { + sb.append(type.underlyingType); + } + + if (!visiting.contains(type)) { + if (type.isDeclaration() && currentPrintInvisibleSetting) { + sb.append("/*DECL*/ "); } - @Override - public String visitArray(AnnotatedArrayType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - - AnnotatedArrayType array = type; - AnnotatedTypeMirror component; - while (true) { - component = array.getComponentType(); - if (!array.getAnnotations().isEmpty()) { - sb.append(' '); - sb.append( - annoFormatter.formatAnnotationString( - array.getAnnotations(), currentPrintInvisibleSetting)); - } - sb.append("[]"); - if (!(component instanceof AnnotatedArrayType)) { - sb.insert(0, visit(component, visiting)); - break; - } - array = (AnnotatedArrayType) component; - } - return sb.toString(); + try { + visiting.add(type); + if (currentPrintVerboseGenerics) { + sb.append("["); + } + printBound("extends", type.getUpperBoundField(), visiting, sb); + printBound("super", type.getLowerBoundField(), visiting, sb); + if (currentPrintVerboseGenerics) { + sb.append("]"); + } + + } finally { + visiting.remove(type); } + } + return sb.toString(); + } - @Override - public String visitTypeVariable( - AnnotatedTypeVariable type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (TypesUtils.isCapturedTypeVariable(type.underlyingType)) { - // underlyingType.toString() has this form: "capture#826 of ? extends - // java.lang.Object". - // assert underlyingType.toString().startsWith("capture#"); - // We output only the "capture#826" part. - - // TODO: If deterministic output is not needed, we could avoid the use of - // getCaptureConversionId() by using this code instead: - // sb.append(underlyingType, 0, underlyingType.indexOf(" of ")); - // The choice would be controlled by a command-line argument. - - // We output a deterministic number; we prefix it by "0" - // so we know whether a number is deterministic or from javac. - sb.append("capture#0") - .append(getCaptureConversionId((TypeVariable) type.underlyingType)); - } else { - sb.append(type.underlyingType); - } - - if (!visiting.contains(type)) { - if (type.isDeclaration() && currentPrintInvisibleSetting) { - sb.append("/*DECL*/ "); - } - - try { - visiting.add(type); - if (currentPrintVerboseGenerics) { - sb.append("["); - } - printBound("extends", type.getUpperBoundField(), visiting, sb); - printBound("super", type.getLowerBoundField(), visiting, sb); - if (currentPrintVerboseGenerics) { - sb.append("]"); - } - - } finally { - visiting.remove(type); - } - } - return sb.toString(); - } + @SideEffectFree + @Override + public String visitPrimitive(AnnotatedPrimitiveType type, Set visiting) { + return formatFlatType(type); + } - @SideEffectFree - @Override - public String visitPrimitive( - AnnotatedPrimitiveType type, Set visiting) { - return formatFlatType(type); - } + @SideEffectFree + @Override + public String visitNoType(AnnotatedNoType type, Set visiting) { + return formatFlatType(type); + } - @SideEffectFree - @Override - public String visitNoType(AnnotatedNoType type, Set visiting) { - return formatFlatType(type); - } + @SideEffectFree + @Override + public String visitNull(AnnotatedNullType type, Set visiting) { + return annoFormatter.formatAnnotationString( + type.getAnnotations(), currentPrintInvisibleSetting) + + "NullType"; + } - @SideEffectFree - @Override - public String visitNull(AnnotatedNullType type, Set visiting) { - return annoFormatter.formatAnnotationString( - type.getAnnotations(), currentPrintInvisibleSetting) - + "NullType"; + @Override + public String visitWildcard(AnnotatedWildcardType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (type.isUninferredTypeArgument()) { + if (currentlyPrintingRaw) { + sb.append("/*RAW TYPE ARGUMENT:*/ "); + } else { + sb.append("/*INFERENCE FAILED for:*/ "); } - - @Override - public String visitWildcard(AnnotatedWildcardType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (type.isUninferredTypeArgument()) { - if (currentlyPrintingRaw) { - sb.append("/*RAW TYPE ARGUMENT:*/ "); - } else { - sb.append("/*INFERENCE FAILED for:*/ "); - } - } - - sb.append( - annoFormatter.formatAnnotationString( - type.getAnnotationsField(), currentPrintInvisibleSetting)); - - sb.append("?"); - if (!visiting.contains(type)) { - try { - visiting.add(type); - - if (currentPrintVerboseGenerics) { - sb.append("["); - } - printBound("extends", type.getExtendsBoundField(), visiting, sb); - printBound("super", type.getSuperBoundField(), visiting, sb); - if (currentPrintVerboseGenerics) { - sb.append("]"); - } - - } finally { - visiting.remove(type); - } - } - return sb.toString(); + } + + sb.append( + annoFormatter.formatAnnotationString( + type.getAnnotationsField(), currentPrintInvisibleSetting)); + + sb.append("?"); + if (!visiting.contains(type)) { + try { + visiting.add(type); + + if (currentPrintVerboseGenerics) { + sb.append("["); + } + printBound("extends", type.getExtendsBoundField(), visiting, sb); + printBound("super", type.getSuperBoundField(), visiting, sb); + if (currentPrintVerboseGenerics) { + sb.append("]"); + } + + } finally { + visiting.remove(type); } + } + return sb.toString(); + } - @SideEffectFree - protected String formatFlatType(AnnotatedTypeMirror flatType) { - return annoFormatter.formatAnnotationString( - flatType.getAnnotations(), currentPrintInvisibleSetting) - + TypeAnnotationUtils.unannotatedType((Type) flatType.getUnderlyingType()); - } + @SideEffectFree + protected String formatFlatType(AnnotatedTypeMirror flatType) { + return annoFormatter.formatAnnotationString( + flatType.getAnnotations(), currentPrintInvisibleSetting) + + TypeAnnotationUtils.unannotatedType((Type) flatType.getUnderlyingType()); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java index e9551a21606..a334d964a2d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java @@ -1,157 +1,150 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypesUtils; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypesUtils; /** Utility class for applying the annotations inferred by dataflow to a given type. */ public class DefaultInferredTypesApplier { - // At the moment, only Inference uses the omitSubtypingCheck option. - // In actuality the subtyping check should be unnecessary since inferred - // types should be subtypes of their declaration. - private final boolean omitSubtypingCheck; + // At the moment, only Inference uses the omitSubtypingCheck option. + // In actuality the subtyping check should be unnecessary since inferred + // types should be subtypes of their declaration. + private final boolean omitSubtypingCheck; - private final QualifierHierarchy hierarchy; - private final AnnotatedTypeFactory atypeFactory; + private final QualifierHierarchy hierarchy; + private final AnnotatedTypeFactory atypeFactory; - public DefaultInferredTypesApplier( - QualifierHierarchy hierarchy, AnnotatedTypeFactory atypeFactory) { - this(false, hierarchy, atypeFactory); - } + public DefaultInferredTypesApplier( + QualifierHierarchy hierarchy, AnnotatedTypeFactory atypeFactory) { + this(false, hierarchy, atypeFactory); + } - public DefaultInferredTypesApplier( - boolean omitSubtypingCheck, - QualifierHierarchy hierarchy, - AnnotatedTypeFactory atypeFactory) { - this.omitSubtypingCheck = omitSubtypingCheck; - this.hierarchy = hierarchy; - this.atypeFactory = atypeFactory; - } + public DefaultInferredTypesApplier( + boolean omitSubtypingCheck, QualifierHierarchy hierarchy, AnnotatedTypeFactory atypeFactory) { + this.omitSubtypingCheck = omitSubtypingCheck; + this.hierarchy = hierarchy; + this.atypeFactory = atypeFactory; + } - /** - * For each top in qualifier hierarchy, traverse inferred and copy the required annotations over - * to type. - * - * @param type the type to which annotations are being applied - * @param inferredSet the type inferred by data flow - * @param inferredTypeMirror underlying inferred type - */ - public void applyInferredType( - AnnotatedTypeMirror type, - AnnotationMirrorSet inferredSet, - TypeMirror inferredTypeMirror) { - if (inferredSet == null) { - return; - } - if (inferredTypeMirror.getKind() == TypeKind.WILDCARD) { - // Dataflow might infer a wildcard that extends a type variable for types that are - // actually type variables. Use the type variable instead. - while (inferredTypeMirror.getKind() == TypeKind.WILDCARD - && (((WildcardType) inferredTypeMirror).getExtendsBound() != null)) { - inferredTypeMirror = ((WildcardType) inferredTypeMirror).getExtendsBound(); - } - } - for (AnnotationMirror top : hierarchy.getTopAnnotations()) { - AnnotationMirror inferred = hierarchy.findAnnotationInHierarchy(inferredSet, top); + /** + * For each top in qualifier hierarchy, traverse inferred and copy the required annotations over + * to type. + * + * @param type the type to which annotations are being applied + * @param inferredSet the type inferred by data flow + * @param inferredTypeMirror underlying inferred type + */ + public void applyInferredType( + AnnotatedTypeMirror type, AnnotationMirrorSet inferredSet, TypeMirror inferredTypeMirror) { + if (inferredSet == null) { + return; + } + if (inferredTypeMirror.getKind() == TypeKind.WILDCARD) { + // Dataflow might infer a wildcard that extends a type variable for types that are + // actually type variables. Use the type variable instead. + while (inferredTypeMirror.getKind() == TypeKind.WILDCARD + && (((WildcardType) inferredTypeMirror).getExtendsBound() != null)) { + inferredTypeMirror = ((WildcardType) inferredTypeMirror).getExtendsBound(); + } + } + for (AnnotationMirror top : hierarchy.getTopAnnotations()) { + AnnotationMirror inferred = hierarchy.findAnnotationInHierarchy(inferredSet, top); - apply(type, inferred, inferredTypeMirror, top); - } + apply(type, inferred, inferredTypeMirror, top); } + } - private void apply( - AnnotatedTypeMirror type, - AnnotationMirror inferred, - TypeMirror inferredTypeMirror, - AnnotationMirror top) { - AnnotationMirror primary = type.getAnnotationInHierarchy(top); - if (inferred == null) { - if (primary == null) { - // Type doesn't have a primary either, nothing to remove - } else if (type.getKind() == TypeKind.TYPEVAR) { - removePrimaryAnnotationTypeVar( - (AnnotatedTypeVariable) type, inferredTypeMirror, top, primary); - } else { - removePrimaryTypeVarApplyUpperBound(type, inferredTypeMirror, top, primary); - } - } else { - if (primary == null) { - AnnotationMirrorSet lowerbounds = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, type); - primary = hierarchy.findAnnotationInHierarchy(lowerbounds, top); - } - if ((omitSubtypingCheck - || hierarchy.isSubtypeShallow( - inferred, inferredTypeMirror, primary, type.getUnderlyingType()))) { - type.replaceAnnotation(inferred); - } - } + private void apply( + AnnotatedTypeMirror type, + AnnotationMirror inferred, + TypeMirror inferredTypeMirror, + AnnotationMirror top) { + AnnotationMirror primary = type.getAnnotationInHierarchy(top); + if (inferred == null) { + if (primary == null) { + // Type doesn't have a primary either, nothing to remove + } else if (type.getKind() == TypeKind.TYPEVAR) { + removePrimaryAnnotationTypeVar( + (AnnotatedTypeVariable) type, inferredTypeMirror, top, primary); + } else { + removePrimaryTypeVarApplyUpperBound(type, inferredTypeMirror, top, primary); + } + } else { + if (primary == null) { + AnnotationMirrorSet lowerbounds = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, type); + primary = hierarchy.findAnnotationInHierarchy(lowerbounds, top); + } + if ((omitSubtypingCheck + || hierarchy.isSubtypeShallow( + inferred, inferredTypeMirror, primary, type.getUnderlyingType()))) { + type.replaceAnnotation(inferred); + } } + } - private void removePrimaryTypeVarApplyUpperBound( - AnnotatedTypeMirror type, - TypeMirror inferredTypeMirror, - AnnotationMirror top, - AnnotationMirror notInferred) { - if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF( - "Inferred value should not be missing annotations: " + inferredTypeMirror); - } - if (TypesUtils.isCapturedTypeVariable(inferredTypeMirror)) { - return; - } + private void removePrimaryTypeVarApplyUpperBound( + AnnotatedTypeMirror type, + TypeMirror inferredTypeMirror, + AnnotationMirror top, + AnnotationMirror notInferred) { + if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Inferred value should not be missing annotations: " + inferredTypeMirror); + } + if (TypesUtils.isCapturedTypeVariable(inferredTypeMirror)) { + return; + } - TypeVariable typeVar = (TypeVariable) inferredTypeMirror; - AnnotatedTypeVariable typeVariableDecl = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); - AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); + TypeVariable typeVar = (TypeVariable) inferredTypeMirror; + AnnotatedTypeVariable typeVariableDecl = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); + AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); - if (omitSubtypingCheck - || hierarchy.isSubtypeShallow( - upperBound, typeVar, notInferred, type.getUnderlyingType())) { - type.replaceAnnotation(upperBound); - } + if (omitSubtypingCheck + || hierarchy.isSubtypeShallow(upperBound, typeVar, notInferred, type.getUnderlyingType())) { + type.replaceAnnotation(upperBound); } + } - private void removePrimaryAnnotationTypeVar( - AnnotatedTypeVariable annotatedTypeVariable, - TypeMirror inferredTypeMirror, - AnnotationMirror top, - AnnotationMirror previousAnnotation) { - if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF("Missing annos"); - } - TypeVariable typeVar = (TypeVariable) inferredTypeMirror; - AnnotatedTypeVariable typeVariableDecl = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); - AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); - if (omitSubtypingCheck - || hierarchy.isSubtypeShallow( - upperBound, - typeVariableDecl.getUnderlyingType(), - previousAnnotation, - annotatedTypeVariable.getUnderlyingType())) { - // TODO: clean up this method and whole class. - AnnotationMirror ub = typeVariableDecl.getUpperBound().getAnnotationInHierarchy(top); - AnnotationMirror lb = typeVariableDecl.getLowerBound().getAnnotationInHierarchy(top); - AnnotatedTypeMirror atvUB = annotatedTypeVariable.getUpperBound(); - AnnotatedTypeMirror atvLB = annotatedTypeVariable.getLowerBound(); - AnnotationMirror atvUBAnno = atvUB.getAnnotationInHierarchy(top); - AnnotationMirror atvLBAnno = atvLB.getAnnotationInHierarchy(top); + private void removePrimaryAnnotationTypeVar( + AnnotatedTypeVariable annotatedTypeVariable, + TypeMirror inferredTypeMirror, + AnnotationMirror top, + AnnotationMirror previousAnnotation) { + if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Missing annos"); + } + TypeVariable typeVar = (TypeVariable) inferredTypeMirror; + AnnotatedTypeVariable typeVariableDecl = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); + AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); + if (omitSubtypingCheck + || hierarchy.isSubtypeShallow( + upperBound, + typeVariableDecl.getUnderlyingType(), + previousAnnotation, + annotatedTypeVariable.getUnderlyingType())) { + // TODO: clean up this method and whole class. + AnnotationMirror ub = typeVariableDecl.getUpperBound().getAnnotationInHierarchy(top); + AnnotationMirror lb = typeVariableDecl.getLowerBound().getAnnotationInHierarchy(top); + AnnotatedTypeMirror atvUB = annotatedTypeVariable.getUpperBound(); + AnnotatedTypeMirror atvLB = annotatedTypeVariable.getLowerBound(); + AnnotationMirror atvUBAnno = atvUB.getAnnotationInHierarchy(top); + AnnotationMirror atvLBAnno = atvLB.getAnnotationInHierarchy(top); - annotatedTypeVariable.removeAnnotationInHierarchy(top); - atvUB.addAnnotation(atvUBAnno); - atvLB.addAnnotation(atvLBAnno); - apply(atvUB, ub, typeVar.getUpperBound(), top); - apply(atvLB, lb, typeVar.getLowerBound(), top); - } + annotatedTypeVariable.removeAnnotationInHierarchy(top); + atvUB.addAnnotation(atvUBAnno); + atvLB.addAnnotation(atvLBAnno); + apply(atvUB, ub, typeVar.getUpperBound(), top); + apply(atvLB, lb, typeVar.getLowerBound(), top); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 3a0ff8b650e..cfcf886bf26 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -1,5 +1,14 @@ package org.checkerframework.framework.type; +import java.util.Collection; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.qual.Covariant; @@ -20,17 +29,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.Collection; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; - /** * Default implementation of TypeHierarchy that implements the JLS specification with minor * deviations as outlined by the Checker Framework manual. Changes to the JLS include forbidding @@ -48,1291 +46,1263 @@ *

The visit methods return true if the first argument is a subtype of the second argument. */ public class DefaultTypeHierarchy extends AbstractAtmComboVisitor - implements TypeHierarchy { - /** - * The type-checker that is associated with this. - * - *

Used for processingEnvironment when needed. - */ - protected final BaseTypeChecker checker; - - /** The qualifier hierarchy that is associated with this. */ - protected final QualifierHierarchy qualHierarchy; - - /** The equality comparer. */ - protected final StructuralEqualityComparer equalityComparer; - - /** Whether to ignore raw types. */ - protected final boolean ignoreRawTypes; - - /** Whether to make array subtyping invariant with respect to array component types. */ - protected final boolean invariantArrayComponents; - - /** The top annotation of the hierarchy currently being checked. */ - protected AnnotationMirror currentTop; - - /** Stores the result of isSubtype, if that result is true. */ - protected final SubtypeVisitHistory isSubtypeVisitHistory; - - /** - * Stores the result of {@link #areEqualInHierarchy(AnnotatedTypeMirror, AnnotatedTypeMirror)} - * for type arguments. Prevents infinite recursion on types that refer to themselves. (Stores - * both true and false results.) - */ - protected final StructuralEqualityVisitHistory areEqualVisitHistory; - - /** The Covariant.value field/element. */ - protected final ExecutableElement covariantValueElement; - - /** - * Creates a DefaultTypeHierarchy. - * - * @param checker the type-checker that is associated with this - * @param qualHierarchy the qualifier hierarchy that is associated with this - * @param ignoreRawTypes whether to ignore raw types - * @param invariantArrayComponents whether to make array subtyping invariant with respect to - * array component types - */ - public DefaultTypeHierarchy( - BaseTypeChecker checker, - QualifierHierarchy qualHierarchy, - boolean ignoreRawTypes, - boolean invariantArrayComponents) { - this.checker = checker; - this.qualHierarchy = qualHierarchy; - this.isSubtypeVisitHistory = new SubtypeVisitHistory(); - this.areEqualVisitHistory = new StructuralEqualityVisitHistory(); - this.equalityComparer = createEqualityComparer(); - - this.ignoreRawTypes = ignoreRawTypes; - this.invariantArrayComponents = invariantArrayComponents; - - covariantValueElement = - TreeUtils.getMethod( - Covariant.class, "value", 0, checker.getProcessingEnvironment()); - } - - /** - * Create the equality comparer. - * - * @return the equality comparer - */ - protected StructuralEqualityComparer createEqualityComparer() { - return new StructuralEqualityComparer(areEqualVisitHistory); - } - - /** - * Returns true if subtype {@literal <:} supertype. - * - *

This implementation iterates over all top annotations and invokes {@link - * #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. Most type systems - * should not override this method, but instead override {@link #isSubtype(AnnotatedTypeMirror, - * AnnotatedTypeMirror, AnnotationMirror)} or some of the {@code visitXXX} methods. - * - * @param subtype expected subtype - * @param supertype expected supertype - * @return true if subtype is a subtype of supertype or equal to it - */ - @Override - public boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (!isSubtype(subtype, supertype, top)) { - return false; - } - } - - return true; - } - - /** A set of annotations and a {@link TypeMirror}. */ - @AnnotatedFor("nullness") - private static class ShallowType { - - /** A set of annotations. */ - AnnotationMirrorSet annos; - - /** A TypeMirror. */ - TypeMirror typeMirror; - - /** - * Creates a {@code ShallowType}. - * - * @param annos a set of annotations - * @param typeMirror a type mirror - */ - private ShallowType(AnnotationMirrorSet annos, TypeMirror typeMirror) { - this.annos = annos; - this.typeMirror = typeMirror; - } - - /** - * Creates a {@code ShallowType} from {@code type}: the annotations are the effective - * annotations on {@code type} and the type mirror is the underlying type of {@code type}. - * - * @param type an annotated type to convert to a {@code ShallowType} - * @return a shallow type created from {@code type} - */ - @SuppressWarnings("nullness") // AnnotatedTypeMirror isn't annotated for nullness. - public static ShallowType create(AnnotatedTypeMirror type) { - AnnotatedTypeMirror erasedType = type.getErased(); - TypeMirror typeMirror = - erasedType.getKind() == type.getKind() - ? type.getUnderlyingType() - : erasedType.getUnderlyingType(); - // The effective annotations are the primary annotations on the erased type. - return new ShallowType(erasedType.getAnnotations(), typeMirror); - } - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - ShallowType subShallowType = ShallowType.create(subtype); - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - subShallowType.annos, - subShallowType.typeMirror, - superShallowType.annos, - superShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, - AnnotatedTypeMirror supertype, - AnnotationMirror hierarchy) { - ShallowType subShallowType = ShallowType.create(subtype); - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, hierarchy), - subShallowType.typeMirror, - qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, hierarchy), - superShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, Collection superQualifiers) { - ShallowType subShallowType = ShallowType.create(subtype); - return qualHierarchy.isSubtypeShallow( - subShallowType.annos, superQualifiers, subShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - Collection subQualifiers, AnnotatedTypeMirror supertype) { - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - subQualifiers, superShallowType.annos, superShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, AnnotationMirror superQualifier) { - ShallowType subShallowType = ShallowType.create(subtype); - return qualHierarchy.isSubtypeShallow( - qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, superQualifier), - superQualifier, - subShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotationMirror subQualifier, AnnotatedTypeMirror supertype) { - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - subQualifier, - qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, subQualifier), - superShallowType.typeMirror); - } - - /** - * Returns true if {@code subtype <: supertype}, but only for the hierarchy of which {@code top} - * is the top. - * - * @param subtype expected subtype - * @param supertype expected supertype - * @param top the top of the hierarchy for which we want to make a comparison - * @return true if {@code subtype} is a subtype of or equal to {@code supertype}, in the - * qualifier hierarchy whose top is {@code top} - */ - protected boolean isSubtype( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror top) { - assert top != null; - currentTop = top; - return AtmCombo.accept(subtype, supertype, null, this); - } - - /** - * Returns error message for the case when two types shouldn't be compared. - * - * @return error message for the case when two types shouldn't be compared - */ - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Void p) { - return super.defaultErrorMessage(subtype, supertype, p) - + System.lineSeparator() - + " visitHistory = " - + isSubtypeVisitHistory; - } - - /** - * Compare the primary annotations of {@code subtype} and {@code supertype}. Neither type can be - * missing annotations. - * - * @param subtype a type that might be a subtype (with respect to primary annotations) - * @param supertype a type that might be a supertype (with respect to primary annotations) - * @return true if the primary annotation on subtype {@literal <:} primary annotation on - * supertype for the current top. - */ - protected boolean isPrimarySubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - TypeMirror subTM = subtype.getUnderlyingType(); - TypeMirror superTM = supertype.getUnderlyingType(); - - AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); - AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); - if (checker.getTypeFactory().hasQualifierParameterInHierarchy(supertype, currentTop) - && checker.getTypeFactory().hasQualifierParameterInHierarchy(subtype, currentTop)) { - // If the types have a class qualifier parameter, the qualifiers must be equivalent. - return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM) - && qualHierarchy.isSubtypeShallow(supertypeAnno, superTM, subtypeAnno, subTM); - } - - return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM); - } - - /** - * Like {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}, but uses a cache to - * prevent infinite recursion on recursive types. - * - * @param subtype a type that may be a subtype - * @param supertype a type that may be a supertype - * @return true if subtype {@literal <:} supertype - */ - protected boolean isSubtypeCaching(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { - // visitHistory only contains pairs in a subtype relationship. - return true; - } - - boolean result = isSubtype(subtype, supertype, currentTop); - // The call to put has no effect if result is false. - isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); - return result; - } - - /** - * Are all the types in {@code subtypes} a subtype of {@code supertype}? - * - *

The underlying type mirrors of {@code subtypes} must be subtypes of the underlying type - * mirror of {@code supertype}. - */ - protected boolean areAllSubtypes( - Iterable subtypes, AnnotatedTypeMirror supertype) { - for (AnnotatedTypeMirror subtype : subtypes) { - if (!isSubtype(subtype, supertype, currentTop)) { - return false; - } - } - - return true; - } - - protected boolean areEqualInHierarchy(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - return equalityComparer.areEqualInHierarchy(type1, type2, currentTop); - } - - /** - * Returns true if {@code outside} contains {@code inside}, that is, if the set of types denoted - * by {@code outside} is a superset of, or equal to, the set of types denoted by {@code inside}. - * - *

Containment is described in JLS section - * 4.5.1 "Type Arguments of Parameterized Types". - * - *

As described in JLS section - * 4.10.2 Subtyping among Class and Interface Types, a declared type S is considered a - * supertype of another declared type T only if all of S's type arguments "contain" the - * corresponding type arguments of the subtype T. - * - * @param inside a possibly-contained type; its underlying type is contained by {@code - * outside}'s underlying type - * @param outside a possibly-containing type; its underlying type contains {@code inside}'s - * underlying type - * @param canBeCovariant whether or not type arguments are allowed to be covariant - * @return true if inside is contained by outside, or if canBeCovariant == true and {@code - * inside <: outside} - */ - protected boolean isContainedBy( - AnnotatedTypeMirror inside, AnnotatedTypeMirror outside, boolean canBeCovariant) { - Boolean previousResult = areEqualVisitHistory.get(inside, outside, currentTop); - if (previousResult != null) { - return previousResult; - } - - if (shouldIgnoreUninferredTypeArgs(inside) || shouldIgnoreUninferredTypeArgs(outside)) { - areEqualVisitHistory.put(inside, outside, currentTop, true); - return true; - } - - if (outside.getKind() == TypeKind.WILDCARD) { - // This is all cases except bullet 6, "T <= T". - AnnotatedWildcardType outsideWildcard = (AnnotatedWildcardType) outside; - - // Add a placeholder in case of recursion, to prevent infinite regress. - areEqualVisitHistory.put(inside, outside, currentTop, true); - boolean result = - isContainedWithinBounds( - inside, - outsideWildcard.getSuperBound(), - outsideWildcard.getExtendsBound(), - canBeCovariant); - areEqualVisitHistory.put(inside, outside, currentTop, result); - return result; - } - if (TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType()) - && !TypesUtils.isCapturedTypeVariable(inside.getUnderlyingType())) { - // TODO: This branch should be removed after #979 is fixed. - // This workaround is only needed when outside is a captured type variable, - // but inside is not. - AnnotatedTypeVariable outsideTypeVar = (AnnotatedTypeVariable) outside; - - // Add a placeholder in case of recursion, to prevent infinite regress. - areEqualVisitHistory.put(inside, outside, currentTop, true); - boolean result = - isContainedWithinBounds( - inside, - outsideTypeVar.getLowerBound(), - outsideTypeVar.getUpperBound(), - canBeCovariant); - - areEqualVisitHistory.put(inside, outside, currentTop, result); - return result; - } - // The remainder of the method is bullet 6, "T <= T". - if (canBeCovariant) { - return isSubtype(inside, outside, currentTop); - } - return areEqualInHierarchy(inside, outside); - } - - /** - * Let {@code outside} be {@code ? super outsideLower extends outsideUpper}. Returns true if - * {@code outside} contains {@code inside}, that is, if the set of types denoted by {@code - * outside} is a superset of, or equal to, the set of types denoted by {@code inside}. - * - *

This method is a helper method for {@link #isContainedBy(AnnotatedTypeMirror, - * AnnotatedTypeMirror, boolean)}. - * - * @param inside a possibly-contained type - * @param outsideLower the lower bound of the possibly-containing type - * @param outsideUpper the upper bound of the possibly-containing type - * @param canBeCovariant whether or not type arguments are allowed to be covariant - * @return true if inside is contained by outside, or if canBeCovariant == true and {@code - * inside <: outside} - */ - protected boolean isContainedWithinBounds( - AnnotatedTypeMirror inside, - AnnotatedTypeMirror outsideLower, - AnnotatedTypeMirror outsideUpper, - boolean canBeCovariant) { - try { - if (canBeCovariant) { - if (outsideLower.getKind() == TypeKind.NULL) { - return isSubtype(inside, outsideUpper); - } else { - return isSubtype(outsideLower, inside); - } - } - // If inside is a wildcard, then isSubtype(outsideLower, inside) calls - // isSubtype(outsideLower, inside.getLowerBound()) and isSubtype(inside, outsideUpper) - // calls isSubtype(inside.getUpperBound(), outsideUpper). This is slightly different - // from the algorithm in the JLS. Only one of the Java type bounds can be specified, - // but there can be annotations on both the upper and lower bound of a wildcard. - return isSubtype(outsideLower, inside) && isSubtype(inside, outsideUpper); - } catch (Throwable ex) { - // Work around: - // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8265255 - if (ex.getMessage().contains("AsSuperVisitor")) { - return false; - } - throw ex; - } - } - - /** - * Returns true if {@code type} is an uninferred type argument and if the checker should not - * issue warnings about uninferred type arguments. - * - * @param type type to check - * @return true if {@code type} is an uninferred type argument and if the checker should not - * issue warnings about uninferred type arguments - */ - private boolean shouldIgnoreUninferredTypeArgs(AnnotatedTypeMirror type) { - return type.atypeFactory.ignoreUninferredTypeArguments - && type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument(); - } - - // ------------------------------------------------------------------------ - // The rest of this file is the visitor methods. It is a lot of methods, one for each - // combination of types. - - // ------------------------------------------------------------------------ - // Arrays as subtypes - - @Override - public Boolean visitArray_Array( - AnnotatedArrayType subtype, AnnotatedArrayType supertype, Void p) { - return isPrimarySubtype(subtype, supertype) - && (invariantArrayComponents - ? areEqualInHierarchy( - subtype.getComponentType(), supertype.getComponentType()) - : isSubtype( - subtype.getComponentType(), - supertype.getComponentType(), - currentTop)); - } - - @Override - public Boolean visitArray_Declared( - AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitArray_Null( - AnnotatedArrayType subtype, AnnotatedNullType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitArray_Intersection( - AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, Void p) { - return isSubtype( - AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype), - supertype, - currentTop); - } - - @Override - public Boolean visitArray_Wildcard( - AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - @Override - public Boolean visitArray_Typevar( - AnnotatedArrayType subtype, AnnotatedTypeVariable superType, Void p) { - return visitType_Typevar(subtype, superType); + implements TypeHierarchy { + /** + * The type-checker that is associated with this. + * + *

Used for processingEnvironment when needed. + */ + protected final BaseTypeChecker checker; + + /** The qualifier hierarchy that is associated with this. */ + protected final QualifierHierarchy qualHierarchy; + + /** The equality comparer. */ + protected final StructuralEqualityComparer equalityComparer; + + /** Whether to ignore raw types. */ + protected final boolean ignoreRawTypes; + + /** Whether to make array subtyping invariant with respect to array component types. */ + protected final boolean invariantArrayComponents; + + /** The top annotation of the hierarchy currently being checked. */ + protected AnnotationMirror currentTop; + + /** Stores the result of isSubtype, if that result is true. */ + protected final SubtypeVisitHistory isSubtypeVisitHistory; + + /** + * Stores the result of {@link #areEqualInHierarchy(AnnotatedTypeMirror, AnnotatedTypeMirror)} for + * type arguments. Prevents infinite recursion on types that refer to themselves. (Stores both + * true and false results.) + */ + protected final StructuralEqualityVisitHistory areEqualVisitHistory; + + /** The Covariant.value field/element. */ + protected final ExecutableElement covariantValueElement; + + /** + * Creates a DefaultTypeHierarchy. + * + * @param checker the type-checker that is associated with this + * @param qualHierarchy the qualifier hierarchy that is associated with this + * @param ignoreRawTypes whether to ignore raw types + * @param invariantArrayComponents whether to make array subtyping invariant with respect to array + * component types + */ + public DefaultTypeHierarchy( + BaseTypeChecker checker, + QualifierHierarchy qualHierarchy, + boolean ignoreRawTypes, + boolean invariantArrayComponents) { + this.checker = checker; + this.qualHierarchy = qualHierarchy; + this.isSubtypeVisitHistory = new SubtypeVisitHistory(); + this.areEqualVisitHistory = new StructuralEqualityVisitHistory(); + this.equalityComparer = createEqualityComparer(); + + this.ignoreRawTypes = ignoreRawTypes; + this.invariantArrayComponents = invariantArrayComponents; + + covariantValueElement = + TreeUtils.getMethod(Covariant.class, "value", 0, checker.getProcessingEnvironment()); + } + + /** + * Create the equality comparer. + * + * @return the equality comparer + */ + protected StructuralEqualityComparer createEqualityComparer() { + return new StructuralEqualityComparer(areEqualVisitHistory); + } + + /** + * Returns true if subtype {@literal <:} supertype. + * + *

This implementation iterates over all top annotations and invokes {@link + * #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. Most type systems + * should not override this method, but instead override {@link #isSubtype(AnnotatedTypeMirror, + * AnnotatedTypeMirror, AnnotationMirror)} or some of the {@code visitXXX} methods. + * + * @param subtype expected subtype + * @param supertype expected supertype + * @return true if subtype is a subtype of supertype or equal to it + */ + @Override + public boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (!isSubtype(subtype, supertype, top)) { + return false; + } } - // ------------------------------------------------------------------------ - // Declared as subtype - @Override - public Boolean visitDeclared_Array( - AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } + return true; + } - @Override - public Boolean visitDeclared_Declared( - AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, Void p) { - if (!isPrimarySubtype(subtype, supertype)) { - return false; - } - AnnotatedTypeFactory factory = subtype.atypeFactory; - if (factory.ignoreUninferredTypeArguments - && (factory.containsUninferredTypeArguments(subtype) - || factory.containsUninferredTypeArguments(supertype))) { - // Calling castedAsSuper may cause the uninferredTypeArguments to be lost. So, just - // return true here. - return true; - } + /** A set of annotations and a {@link TypeMirror}. */ + @AnnotatedFor("nullness") + private static class ShallowType { - if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { - return true; - } + /** A set of annotations. */ + AnnotationMirrorSet annos; - boolean result = - visitTypeArgs( - subtype, - supertype, - subtype.isUnderlyingTypeRaw(), - supertype.isUnderlyingTypeRaw()); - isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); - - return result; - } + /** A TypeMirror. */ + TypeMirror typeMirror; /** - * Returns true if the type arguments in {@code supertype} contain the type arguments in {@code - * subtype} and false otherwise. See {@link #isContainedBy} for an explanation of containment. + * Creates a {@code ShallowType}. * - * @param subtype a possible subtype - * @param supertype a possible supertype - * @param subtypeRaw whether {@code subtype} is a raw type - * @param supertypeRaw whether {@code supertype} is a raw type - * @return true if the type arguments in {@code supertype} contain the type arguments in {@code - * subtype} and false otherwise + * @param annos a set of annotations + * @param typeMirror a type mirror */ - protected boolean visitTypeArgs( - AnnotatedDeclaredType subtype, - AnnotatedDeclaredType supertype, - boolean subtypeRaw, - boolean supertypeRaw) { - AnnotatedTypeFactory typeFactory = subtype.atypeFactory; - - // JLS 11: 4.10.2. Subtyping among Class and Interface Types - // 4th paragraph, bullet 1. - AnnotatedDeclaredType subtypeAsSuper = - AnnotatedTypes.castedAsSuper(typeFactory, subtype, supertype); - - if (ignoreRawTypes && (subtypeRaw || supertypeRaw)) { - return true; - } - - List subtypeTypeArgs = subtypeAsSuper.getTypeArguments(); - List supertypeTypeArgs = supertype.getTypeArguments(); - - if (subtypeTypeArgs.size() != supertypeTypeArgs.size()) { - throw new BugInCF( - "Type arguments are not the same size: %s %s", subtypeAsSuper, supertype); - } - // This method, `visitTypeArgs`, is called even if `subtype` doesn't have type arguments. - if (subtypeTypeArgs.isEmpty()) { - return true; - } - - TypeElement supertypeElem = (TypeElement) supertype.getUnderlyingType().asElement(); - AnnotationMirror covariantAnno = - typeFactory.getDeclAnnotation(supertypeElem, Covariant.class); - - List covariantArgIndexes = - (covariantAnno == null) - ? null - : AnnotationUtils.getElementValueArray( - covariantAnno, covariantValueElement, Integer.class); - - // JLS 11: 4.10.2. Subtyping among Class and Interface Types - // 4th paragraph, bullet 2 - try { - if (isContainedMany( - subtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes)) { - return true; - } - } catch (Exception e) { - // Some types need to be captured first, so ignore crashes. - for (int i = 0; i < supertypeTypeArgs.size(); i++) { - areEqualVisitHistory.remove( - subtypeAsSuper.getTypeArguments().get(i), - supertypeTypeArgs.get(i), - currentTop); - } - } - // 5th paragraph: - // Instead of calling isSubtype with the captured type, just check for containment. - AnnotatedDeclaredType capturedSubtype = - (AnnotatedDeclaredType) typeFactory.applyCaptureConversion(subtype); - AnnotatedDeclaredType capturedSubtypeAsSuper = - AnnotatedTypes.castedAsSuper(typeFactory, capturedSubtype, supertype); - return isContainedMany( - capturedSubtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes); + private ShallowType(AnnotationMirrorSet annos, TypeMirror typeMirror) { + this.annos = annos; + this.typeMirror = typeMirror; } /** - * Calls {@link #isContainedBy(AnnotatedTypeMirror, AnnotatedTypeMirror, boolean)} on the two - * lists of type arguments. Returns true if every type argument in {@code supertypeTypeArgs} - * contains the type argument at the same index in {@code subtypeTypeArgs}. + * Creates a {@code ShallowType} from {@code type}: the annotations are the effective + * annotations on {@code type} and the type mirror is the underlying type of {@code type}. * - * @param subtypeTypeArgs subtype arguments - * @param supertypeTypeArgs supertype arguments - * @param covariantArgIndexes indexes into the type arguments list which correspond to the type - * arguments that are marked @{@link Covariant}. - * @return whether {@code supertypeTypeArgs} contain {@code subtypeTypeArgs} + * @param type an annotated type to convert to a {@code ShallowType} + * @return a shallow type created from {@code type} */ - protected boolean isContainedMany( - List subtypeTypeArgs, - List supertypeTypeArgs, - List covariantArgIndexes) { - for (int i = 0; i < supertypeTypeArgs.size(); i++) { - AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i); - AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i); - boolean covariant = covariantArgIndexes != null && covariantArgIndexes.contains(i); - if (!isContainedBy(subTypeArg, superTypeArg, covariant)) { - return false; - } - } - return true; - } - - @Override - public Boolean visitDeclared_Intersection( - AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, Void p) { - return visitType_Intersection(subtype, supertype); - } - - @Override - public Boolean visitDeclared_Null( - AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitDeclared_Primitive( - AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, Void p) { - AnnotatedTypeMirror unboxedType; - try { - unboxedType = subtype.atypeFactory.getUnboxedType(subtype); - } catch (IllegalArgumentException ex) { - throw new BugInCF( - "DefaultTypeHierarchy: subtype isn't a boxed type: subtype: %s superType: %s", - subtype, supertype); - } - return isPrimarySubtype(unboxedType, supertype); - } - - @Override - public Boolean visitDeclared_Typevar( - AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitType_Typevar(subtype, supertype); - } - - @Override - public Boolean visitDeclared_Union( - AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Void p) { - Types types = checker.getTypeUtils(); - for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { - if (TypesUtils.isErasedSubtype( - subtype.getUnderlyingType(), supertypeAltern.getUnderlyingType(), types) - && isSubtype(subtype, supertypeAltern, currentTop)) { - return true; - } - } + @SuppressWarnings("nullness") // AnnotatedTypeMirror isn't annotated for nullness. + public static ShallowType create(AnnotatedTypeMirror type) { + AnnotatedTypeMirror erasedType = type.getErased(); + TypeMirror typeMirror = + erasedType.getKind() == type.getKind() + ? type.getUnderlyingType() + : erasedType.getUnderlyingType(); + // The effective annotations are the primary annotations on the erased type. + return new ShallowType(erasedType.getAnnotations(), typeMirror); + } + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + ShallowType subShallowType = ShallowType.create(subtype); + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + subShallowType.annos, + subShallowType.typeMirror, + superShallowType.annos, + superShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror hierarchy) { + ShallowType subShallowType = ShallowType.create(subtype); + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, hierarchy), + subShallowType.typeMirror, + qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, hierarchy), + superShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, Collection superQualifiers) { + ShallowType subShallowType = ShallowType.create(subtype); + return qualHierarchy.isSubtypeShallow( + subShallowType.annos, superQualifiers, subShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + Collection subQualifiers, AnnotatedTypeMirror supertype) { + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + subQualifiers, superShallowType.annos, superShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, AnnotationMirror superQualifier) { + ShallowType subShallowType = ShallowType.create(subtype); + return qualHierarchy.isSubtypeShallow( + qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, superQualifier), + superQualifier, + subShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotationMirror subQualifier, AnnotatedTypeMirror supertype) { + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + subQualifier, + qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, subQualifier), + superShallowType.typeMirror); + } + + /** + * Returns true if {@code subtype <: supertype}, but only for the hierarchy of which {@code top} + * is the top. + * + * @param subtype expected subtype + * @param supertype expected supertype + * @param top the top of the hierarchy for which we want to make a comparison + * @return true if {@code subtype} is a subtype of or equal to {@code supertype}, in the qualifier + * hierarchy whose top is {@code top} + */ + protected boolean isSubtype( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror top) { + assert top != null; + currentTop = top; + return AtmCombo.accept(subtype, supertype, null, this); + } + + /** + * Returns error message for the case when two types shouldn't be compared. + * + * @return error message for the case when two types shouldn't be compared + */ + @Override + public String defaultErrorMessage( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Void p) { + return super.defaultErrorMessage(subtype, supertype, p) + + System.lineSeparator() + + " visitHistory = " + + isSubtypeVisitHistory; + } + + /** + * Compare the primary annotations of {@code subtype} and {@code supertype}. Neither type can be + * missing annotations. + * + * @param subtype a type that might be a subtype (with respect to primary annotations) + * @param supertype a type that might be a supertype (with respect to primary annotations) + * @return true if the primary annotation on subtype {@literal <:} primary annotation on supertype + * for the current top. + */ + protected boolean isPrimarySubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + TypeMirror subTM = subtype.getUnderlyingType(); + TypeMirror superTM = supertype.getUnderlyingType(); + + AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); + AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); + if (checker.getTypeFactory().hasQualifierParameterInHierarchy(supertype, currentTop) + && checker.getTypeFactory().hasQualifierParameterInHierarchy(subtype, currentTop)) { + // If the types have a class qualifier parameter, the qualifiers must be equivalent. + return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM) + && qualHierarchy.isSubtypeShallow(supertypeAnno, superTM, subtypeAnno, subTM); + } + + return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM); + } + + /** + * Like {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}, but uses a cache to prevent + * infinite recursion on recursive types. + * + * @param subtype a type that may be a subtype + * @param supertype a type that may be a supertype + * @return true if subtype {@literal <:} supertype + */ + protected boolean isSubtypeCaching(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { + // visitHistory only contains pairs in a subtype relationship. + return true; + } + + boolean result = isSubtype(subtype, supertype, currentTop); + // The call to put has no effect if result is false. + isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); + return result; + } + + /** + * Are all the types in {@code subtypes} a subtype of {@code supertype}? + * + *

The underlying type mirrors of {@code subtypes} must be subtypes of the underlying type + * mirror of {@code supertype}. + */ + protected boolean areAllSubtypes( + Iterable subtypes, AnnotatedTypeMirror supertype) { + for (AnnotatedTypeMirror subtype : subtypes) { + if (!isSubtype(subtype, supertype, currentTop)) { return false; - } - - @Override - public Boolean visitDeclared_Wildcard( - AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // Intersection as subtype - @Override - public Boolean visitIntersection_Declared( - AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, Void p) { - return visitIntersection_Type(subtype, supertype); - } - - @Override - public Boolean visitIntersection_Primitive( - AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, Void p) { - for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { - if (TypesUtils.isBoxedPrimitive(subtypeBound.getUnderlyingType()) - && isSubtype(subtypeBound, supertype, currentTop)) { - return true; - } + } + } + + return true; + } + + protected boolean areEqualInHierarchy(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + return equalityComparer.areEqualInHierarchy(type1, type2, currentTop); + } + + /** + * Returns true if {@code outside} contains {@code inside}, that is, if the set of types denoted + * by {@code outside} is a superset of, or equal to, the set of types denoted by {@code inside}. + * + *

Containment is described in JLS section + * 4.5.1 "Type Arguments of Parameterized Types". + * + *

As described in JLS section + * 4.10.2 Subtyping among Class and Interface Types, a declared type S is considered a + * supertype of another declared type T only if all of S's type arguments "contain" the + * corresponding type arguments of the subtype T. + * + * @param inside a possibly-contained type; its underlying type is contained by {@code outside}'s + * underlying type + * @param outside a possibly-containing type; its underlying type contains {@code inside}'s + * underlying type + * @param canBeCovariant whether or not type arguments are allowed to be covariant + * @return true if inside is contained by outside, or if canBeCovariant == true and {@code inside + * <: outside} + */ + protected boolean isContainedBy( + AnnotatedTypeMirror inside, AnnotatedTypeMirror outside, boolean canBeCovariant) { + Boolean previousResult = areEqualVisitHistory.get(inside, outside, currentTop); + if (previousResult != null) { + return previousResult; + } + + if (shouldIgnoreUninferredTypeArgs(inside) || shouldIgnoreUninferredTypeArgs(outside)) { + areEqualVisitHistory.put(inside, outside, currentTop, true); + return true; + } + + if (outside.getKind() == TypeKind.WILDCARD) { + // This is all cases except bullet 6, "T <= T". + AnnotatedWildcardType outsideWildcard = (AnnotatedWildcardType) outside; + + // Add a placeholder in case of recursion, to prevent infinite regress. + areEqualVisitHistory.put(inside, outside, currentTop, true); + boolean result = + isContainedWithinBounds( + inside, + outsideWildcard.getSuperBound(), + outsideWildcard.getExtendsBound(), + canBeCovariant); + areEqualVisitHistory.put(inside, outside, currentTop, result); + return result; + } + if (TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType()) + && !TypesUtils.isCapturedTypeVariable(inside.getUnderlyingType())) { + // TODO: This branch should be removed after #979 is fixed. + // This workaround is only needed when outside is a captured type variable, + // but inside is not. + AnnotatedTypeVariable outsideTypeVar = (AnnotatedTypeVariable) outside; + + // Add a placeholder in case of recursion, to prevent infinite regress. + areEqualVisitHistory.put(inside, outside, currentTop, true); + boolean result = + isContainedWithinBounds( + inside, + outsideTypeVar.getLowerBound(), + outsideTypeVar.getUpperBound(), + canBeCovariant); + + areEqualVisitHistory.put(inside, outside, currentTop, result); + return result; + } + // The remainder of the method is bullet 6, "T <= T". + if (canBeCovariant) { + return isSubtype(inside, outside, currentTop); + } + return areEqualInHierarchy(inside, outside); + } + + /** + * Let {@code outside} be {@code ? super outsideLower extends outsideUpper}. Returns true if + * {@code outside} contains {@code inside}, that is, if the set of types denoted by {@code + * outside} is a superset of, or equal to, the set of types denoted by {@code inside}. + * + *

This method is a helper method for {@link #isContainedBy(AnnotatedTypeMirror, + * AnnotatedTypeMirror, boolean)}. + * + * @param inside a possibly-contained type + * @param outsideLower the lower bound of the possibly-containing type + * @param outsideUpper the upper bound of the possibly-containing type + * @param canBeCovariant whether or not type arguments are allowed to be covariant + * @return true if inside is contained by outside, or if canBeCovariant == true and {@code inside + * <: outside} + */ + protected boolean isContainedWithinBounds( + AnnotatedTypeMirror inside, + AnnotatedTypeMirror outsideLower, + AnnotatedTypeMirror outsideUpper, + boolean canBeCovariant) { + try { + if (canBeCovariant) { + if (outsideLower.getKind() == TypeKind.NULL) { + return isSubtype(inside, outsideUpper); + } else { + return isSubtype(outsideLower, inside); } + } + // If inside is a wildcard, then isSubtype(outsideLower, inside) calls + // isSubtype(outsideLower, inside.getLowerBound()) and isSubtype(inside, outsideUpper) + // calls isSubtype(inside.getUpperBound(), outsideUpper). This is slightly different + // from the algorithm in the JLS. Only one of the Java type bounds can be specified, + // but there can be annotations on both the upper and lower bound of a wildcard. + return isSubtype(outsideLower, inside) && isSubtype(inside, outsideUpper); + } catch (Throwable ex) { + // Work around: + // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8265255 + if (ex.getMessage().contains("AsSuperVisitor")) { return false; - } - - @Override - public Boolean visitIntersection_Intersection( - AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, Void p) { - Types types = checker.getTypeUtils(); - for (AnnotatedTypeMirror subBound : subtype.getBounds()) { - for (AnnotatedTypeMirror superBound : supertype.getBounds()) { - if (TypesUtils.isErasedSubtype( - subBound.getUnderlyingType(), superBound.getUnderlyingType(), types) - && !isSubtype(subBound, superBound, currentTop)) { - return false; - } - } - } + } + throw ex; + } + } + + /** + * Returns true if {@code type} is an uninferred type argument and if the checker should not issue + * warnings about uninferred type arguments. + * + * @param type type to check + * @return true if {@code type} is an uninferred type argument and if the checker should not issue + * warnings about uninferred type arguments + */ + private boolean shouldIgnoreUninferredTypeArgs(AnnotatedTypeMirror type) { + return type.atypeFactory.ignoreUninferredTypeArguments + && type.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type).isUninferredTypeArgument(); + } + + // ------------------------------------------------------------------------ + // The rest of this file is the visitor methods. It is a lot of methods, one for each + // combination of types. + + // ------------------------------------------------------------------------ + // Arrays as subtypes + + @Override + public Boolean visitArray_Array( + AnnotatedArrayType subtype, AnnotatedArrayType supertype, Void p) { + return isPrimarySubtype(subtype, supertype) + && (invariantArrayComponents + ? areEqualInHierarchy(subtype.getComponentType(), supertype.getComponentType()) + : isSubtype(subtype.getComponentType(), supertype.getComponentType(), currentTop)); + } + + @Override + public Boolean visitArray_Declared( + AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitArray_Null(AnnotatedArrayType subtype, AnnotatedNullType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitArray_Intersection( + AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, Void p) { + return isSubtype( + AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype), + supertype, + currentTop); + } + + @Override + public Boolean visitArray_Wildcard( + AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + @Override + public Boolean visitArray_Typevar( + AnnotatedArrayType subtype, AnnotatedTypeVariable superType, Void p) { + return visitType_Typevar(subtype, superType); + } + + // ------------------------------------------------------------------------ + // Declared as subtype + @Override + public Boolean visitDeclared_Array( + AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitDeclared_Declared( + AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, Void p) { + if (!isPrimarySubtype(subtype, supertype)) { + return false; + } + AnnotatedTypeFactory factory = subtype.atypeFactory; + if (factory.ignoreUninferredTypeArguments + && (factory.containsUninferredTypeArguments(subtype) + || factory.containsUninferredTypeArguments(supertype))) { + // Calling castedAsSuper may cause the uninferredTypeArguments to be lost. So, just + // return true here. + return true; + } + + if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { + return true; + } + + boolean result = + visitTypeArgs( + subtype, supertype, subtype.isUnderlyingTypeRaw(), supertype.isUnderlyingTypeRaw()); + isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); + + return result; + } + + /** + * Returns true if the type arguments in {@code supertype} contain the type arguments in {@code + * subtype} and false otherwise. See {@link #isContainedBy} for an explanation of containment. + * + * @param subtype a possible subtype + * @param supertype a possible supertype + * @param subtypeRaw whether {@code subtype} is a raw type + * @param supertypeRaw whether {@code supertype} is a raw type + * @return true if the type arguments in {@code supertype} contain the type arguments in {@code + * subtype} and false otherwise + */ + protected boolean visitTypeArgs( + AnnotatedDeclaredType subtype, + AnnotatedDeclaredType supertype, + boolean subtypeRaw, + boolean supertypeRaw) { + AnnotatedTypeFactory typeFactory = subtype.atypeFactory; + + // JLS 11: 4.10.2. Subtyping among Class and Interface Types + // 4th paragraph, bullet 1. + AnnotatedDeclaredType subtypeAsSuper = + AnnotatedTypes.castedAsSuper(typeFactory, subtype, supertype); + + if (ignoreRawTypes && (subtypeRaw || supertypeRaw)) { + return true; + } + + List subtypeTypeArgs = subtypeAsSuper.getTypeArguments(); + List supertypeTypeArgs = supertype.getTypeArguments(); + + if (subtypeTypeArgs.size() != supertypeTypeArgs.size()) { + throw new BugInCF("Type arguments are not the same size: %s %s", subtypeAsSuper, supertype); + } + // This method, `visitTypeArgs`, is called even if `subtype` doesn't have type arguments. + if (subtypeTypeArgs.isEmpty()) { + return true; + } + + TypeElement supertypeElem = (TypeElement) supertype.getUnderlyingType().asElement(); + AnnotationMirror covariantAnno = typeFactory.getDeclAnnotation(supertypeElem, Covariant.class); + + List covariantArgIndexes = + (covariantAnno == null) + ? null + : AnnotationUtils.getElementValueArray( + covariantAnno, covariantValueElement, Integer.class); + + // JLS 11: 4.10.2. Subtyping among Class and Interface Types + // 4th paragraph, bullet 2 + try { + if (isContainedMany( + subtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes)) { return true; - } - - @Override - public Boolean visitIntersection_Null( - AnnotatedIntersectionType subtype, AnnotatedNullType supertype, Void p) { - // this can occur through capture conversion/comparing bounds - for (AnnotatedTypeMirror bound : subtype.getBounds()) { - if (isPrimarySubtype(bound, supertype)) { - return true; - } - } - return false; - } - - @Override - public Boolean visitIntersection_Typevar( - AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitIntersection_Type(subtype, supertype); - } - - @Override - public Boolean visitIntersection_Wildcard( - AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, Void p) { - return visitIntersection_Type(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // Null as subtype - @Override - public Boolean visitNull_Array( - AnnotatedNullType subtype, AnnotatedArrayType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Declared( - AnnotatedNullType subtype, AnnotatedDeclaredType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Typevar( - AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitType_Typevar(subtype, supertype); - } - - @Override - public Boolean visitNull_Wildcard( - AnnotatedNullType subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - @Override - public Boolean visitNull_Null(AnnotatedNullType subtype, AnnotatedNullType supertype, Void p) { - // this can occur when comparing typevar lower bounds since they are usually null types - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Union( - AnnotatedNullType subtype, AnnotatedUnionType supertype, Void p) { - for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { - if (isSubtype(subtype, supertypeAltern, currentTop)) { - return true; - } - } + } + } catch (Exception e) { + // Some types need to be captured first, so ignore crashes. + for (int i = 0; i < supertypeTypeArgs.size(); i++) { + areEqualVisitHistory.remove( + subtypeAsSuper.getTypeArguments().get(i), supertypeTypeArgs.get(i), currentTop); + } + } + // 5th paragraph: + // Instead of calling isSubtype with the captured type, just check for containment. + AnnotatedDeclaredType capturedSubtype = + (AnnotatedDeclaredType) typeFactory.applyCaptureConversion(subtype); + AnnotatedDeclaredType capturedSubtypeAsSuper = + AnnotatedTypes.castedAsSuper(typeFactory, capturedSubtype, supertype); + return isContainedMany( + capturedSubtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes); + } + + /** + * Calls {@link #isContainedBy(AnnotatedTypeMirror, AnnotatedTypeMirror, boolean)} on the two + * lists of type arguments. Returns true if every type argument in {@code supertypeTypeArgs} + * contains the type argument at the same index in {@code subtypeTypeArgs}. + * + * @param subtypeTypeArgs subtype arguments + * @param supertypeTypeArgs supertype arguments + * @param covariantArgIndexes indexes into the type arguments list which correspond to the type + * arguments that are marked @{@link Covariant}. + * @return whether {@code supertypeTypeArgs} contain {@code subtypeTypeArgs} + */ + protected boolean isContainedMany( + List subtypeTypeArgs, + List supertypeTypeArgs, + List covariantArgIndexes) { + for (int i = 0; i < supertypeTypeArgs.size(); i++) { + AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i); + AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i); + boolean covariant = covariantArgIndexes != null && covariantArgIndexes.contains(i); + if (!isContainedBy(subTypeArg, superTypeArg, covariant)) { return false; - } - - @Override - public Boolean visitNull_Intersection( - AnnotatedNullType subtype, AnnotatedIntersectionType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Primitive( - AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // Primitive as subtype - @Override - public Boolean visitPrimitive_Declared( - AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, Void p) { - AnnotatedTypeFactory atypeFactory = subtype.atypeFactory; - Types types = atypeFactory.types; - AnnotatedPrimitiveType narrowedType = subtype; - if (TypesUtils.isBoxedPrimitive(supertype.getUnderlyingType())) { - TypeMirror unboxedSuper = types.unboxedType(supertype.getUnderlyingType()); - if (unboxedSuper.getKind() != subtype.getKind() - && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { - narrowedType = atypeFactory.getNarrowedPrimitive(subtype, unboxedSuper); - } + } + } + return true; + } + + @Override + public Boolean visitDeclared_Intersection( + AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, Void p) { + return visitType_Intersection(subtype, supertype); + } + + @Override + public Boolean visitDeclared_Null( + AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitDeclared_Primitive( + AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, Void p) { + AnnotatedTypeMirror unboxedType; + try { + unboxedType = subtype.atypeFactory.getUnboxedType(subtype); + } catch (IllegalArgumentException ex) { + throw new BugInCF( + "DefaultTypeHierarchy: subtype isn't a boxed type: subtype: %s superType: %s", + subtype, supertype); + } + return isPrimarySubtype(unboxedType, supertype); + } + + @Override + public Boolean visitDeclared_Typevar( + AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitType_Typevar(subtype, supertype); + } + + @Override + public Boolean visitDeclared_Union( + AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Void p) { + Types types = checker.getTypeUtils(); + for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { + if (TypesUtils.isErasedSubtype( + subtype.getUnderlyingType(), supertypeAltern.getUnderlyingType(), types) + && isSubtype(subtype, supertypeAltern, currentTop)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitDeclared_Wildcard( + AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // Intersection as subtype + @Override + public Boolean visitIntersection_Declared( + AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, Void p) { + return visitIntersection_Type(subtype, supertype); + } + + @Override + public Boolean visitIntersection_Primitive( + AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, Void p) { + for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { + if (TypesUtils.isBoxedPrimitive(subtypeBound.getUnderlyingType()) + && isSubtype(subtypeBound, supertype, currentTop)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitIntersection_Intersection( + AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, Void p) { + Types types = checker.getTypeUtils(); + for (AnnotatedTypeMirror subBound : subtype.getBounds()) { + for (AnnotatedTypeMirror superBound : supertype.getBounds()) { + if (TypesUtils.isErasedSubtype( + subBound.getUnderlyingType(), superBound.getUnderlyingType(), types) + && !isSubtype(subBound, superBound, currentTop)) { + return false; } - AnnotatedTypeMirror boxedSubtype = atypeFactory.getBoxedType(narrowedType); - return isPrimarySubtype(boxedSubtype, supertype); + } } + return true; + } - @Override - public Boolean visitPrimitive_Primitive( - AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, Void p) { + @Override + public Boolean visitIntersection_Null( + AnnotatedIntersectionType subtype, AnnotatedNullType supertype, Void p) { + // this can occur through capture conversion/comparing bounds + for (AnnotatedTypeMirror bound : subtype.getBounds()) { + if (isPrimarySubtype(bound, supertype)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitIntersection_Typevar( + AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitIntersection_Type(subtype, supertype); + } + + @Override + public Boolean visitIntersection_Wildcard( + AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, Void p) { + return visitIntersection_Type(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // Null as subtype + @Override + public Boolean visitNull_Array(AnnotatedNullType subtype, AnnotatedArrayType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Declared( + AnnotatedNullType subtype, AnnotatedDeclaredType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Typevar( + AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitType_Typevar(subtype, supertype); + } + + @Override + public Boolean visitNull_Wildcard( + AnnotatedNullType subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + @Override + public Boolean visitNull_Null(AnnotatedNullType subtype, AnnotatedNullType supertype, Void p) { + // this can occur when comparing typevar lower bounds since they are usually null types + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Union(AnnotatedNullType subtype, AnnotatedUnionType supertype, Void p) { + for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { + if (isSubtype(subtype, supertypeAltern, currentTop)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitNull_Intersection( + AnnotatedNullType subtype, AnnotatedIntersectionType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Primitive( + AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // Primitive as subtype + @Override + public Boolean visitPrimitive_Declared( + AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, Void p) { + AnnotatedTypeFactory atypeFactory = subtype.atypeFactory; + Types types = atypeFactory.types; + AnnotatedPrimitiveType narrowedType = subtype; + if (TypesUtils.isBoxedPrimitive(supertype.getUnderlyingType())) { + TypeMirror unboxedSuper = types.unboxedType(supertype.getUnderlyingType()); + if (unboxedSuper.getKind() != subtype.getKind() + && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { + narrowedType = atypeFactory.getNarrowedPrimitive(subtype, unboxedSuper); + } + } + AnnotatedTypeMirror boxedSubtype = atypeFactory.getBoxedType(narrowedType); + return isPrimarySubtype(boxedSubtype, supertype); + } + + @Override + public Boolean visitPrimitive_Primitive( + AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitPrimitive_Intersection( + AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, Void p) { + return visitType_Intersection(subtype, supertype); + } + + @Override + public Boolean visitPrimitive_Typevar( + AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, Void p) { + return AtmCombo.accept(subtype, supertype.getUpperBound(), null, this); + } + + @Override + public Boolean visitPrimitive_Wildcard( + AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, Void p) { + if (supertype.atypeFactory.ignoreUninferredTypeArguments + && supertype.isUninferredTypeArgument()) { + return true; + } + // this can occur when passing a primitive to a method on a raw type (see test + // checker/tests/nullness/RawAndPrimitive.java). This can also occur because we don't box + // primitives when we should and don't capture convert. + return isPrimarySubtype(subtype, supertype.getSuperBound()); + } + + // ------------------------------------------------------------------------ + // Union as subtype + @Override + public Boolean visitUnion_Declared( + AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, Void p) { + return visitUnion_Type(subtype, supertype); + } + + @Override + public Boolean visitUnion_Intersection( + AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, Void p) { + // For example: + // void method(T param) {} + // ... + // catch (Exception1 | Exception2 union) { // Assuming Exception1 and Exception2 implement + // Cloneable + // method(union); + // This case happens when checking that the inferred type argument is a subtype of the + // declared type argument of method. + // See org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments + return visitUnion_Type(subtype, supertype); + } + + @Override + public Boolean visitUnion_Union( + AnnotatedUnionType subtype, AnnotatedUnionType supertype, Void p) { + // For example: + // void method(T param) {} + // ... + // catch (Exception1 | Exception2 union) { + // method(union); + // This case happens when checking the arguments to method after type variable substitution + return visitUnion_Type(subtype, supertype); + } + + @Override + public Boolean visitUnion_Wildcard( + AnnotatedUnionType subtype, AnnotatedWildcardType supertype, Void p) { + // For example: + // } catch (RuntimeException | IOException e) { + // ArrayList lWildcard = new ArrayList<>(); + // lWildcard.add(e); + + return visitType_Wildcard(subtype, supertype); + } + + @Override + public Boolean visitUnion_Typevar( + AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, Void p) { + // For example: + // } catch (RuntimeException | IOException e) { + // ArrayList lWildcard = new ArrayList<>(); + // lWildcard.add(e); + + return visitType_Typevar(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // typevar as subtype + @Override + public Boolean visitTypevar_Declared( + AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, Void p) { + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Intersection( + AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, Void p) { + // this can happen when checking type param bounds + return visitType_Intersection(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Primitive( + AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, Void p) { + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Array( + AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, Void p) { + // This happens when the type variable is a captured wildcard. + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Typevar( + AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, Void p) { + TypeMirror subTM = subtype.getUnderlyingType(); + TypeMirror superTM = supertype.getUnderlyingType(); + if (AnnotatedTypes.haveSameDeclaration(checker.getTypeUtils(), subtype, supertype)) { + // The underlying types of subtype and supertype are uses of the same type parameter, + // but they may have different primary annotations. + AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); + boolean subtypeHasAnno = subtypeAnno != null; + AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); + boolean supertypeHasAnno = supertypeAnno != null; + + if (subtypeHasAnno && supertypeHasAnno) { + // If both have primary annotations then just check the primary annotations + // as the bounds are the same. return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitPrimitive_Intersection( - AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, Void p) { - return visitType_Intersection(subtype, supertype); - } - - @Override - public Boolean visitPrimitive_Typevar( - AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, Void p) { - return AtmCombo.accept(subtype, supertype.getUpperBound(), null, this); - } - - @Override - public Boolean visitPrimitive_Wildcard( - AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, Void p) { - if (supertype.atypeFactory.ignoreUninferredTypeArguments - && supertype.isUninferredTypeArgument()) { - return true; + } else if (!subtypeHasAnno && !supertypeHasAnno) { + // Two unannotated uses of the same type parameter need to compare + // both upper and lower bounds. + + // Upper bound of the subtype needs to be below the upper bound of the supertype. + if (!qualHierarchy.isSubtypeShallow( + subtype.getEffectiveAnnotationInHierarchy(currentTop), + subTM, + supertype.getEffectiveAnnotationInHierarchy(currentTop), + superTM)) { + return false; } - // this can occur when passing a primitive to a method on a raw type (see test - // checker/tests/nullness/RawAndPrimitive.java). This can also occur because we don't box - // primitives when we should and don't capture convert. - return isPrimarySubtype(subtype, supertype.getSuperBound()); - } - - // ------------------------------------------------------------------------ - // Union as subtype - @Override - public Boolean visitUnion_Declared( - AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, Void p) { - return visitUnion_Type(subtype, supertype); - } - - @Override - public Boolean visitUnion_Intersection( - AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, Void p) { - // For example: - // void method(T param) {} - // ... - // catch (Exception1 | Exception2 union) { // Assuming Exception1 and Exception2 implement - // Cloneable - // method(union); - // This case happens when checking that the inferred type argument is a subtype of the - // declared type argument of method. - // See org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments - return visitUnion_Type(subtype, supertype); - } - - @Override - public Boolean visitUnion_Union( - AnnotatedUnionType subtype, AnnotatedUnionType supertype, Void p) { - // For example: - // void method(T param) {} - // ... - // catch (Exception1 | Exception2 union) { - // method(union); - // This case happens when checking the arguments to method after type variable substitution - return visitUnion_Type(subtype, supertype); - } - - @Override - public Boolean visitUnion_Wildcard( - AnnotatedUnionType subtype, AnnotatedWildcardType supertype, Void p) { - // For example: - // } catch (RuntimeException | IOException e) { - // ArrayList lWildcard = new ArrayList<>(); - // lWildcard.add(e); - - return visitType_Wildcard(subtype, supertype); - } - - @Override - public Boolean visitUnion_Typevar( - AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, Void p) { - // For example: - // } catch (RuntimeException | IOException e) { - // ArrayList lWildcard = new ArrayList<>(); - // lWildcard.add(e); - - return visitType_Typevar(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // typevar as subtype - @Override - public Boolean visitTypevar_Declared( - AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, Void p) { - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Intersection( - AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, Void p) { - // this can happen when checking type param bounds - return visitType_Intersection(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Primitive( - AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, Void p) { - return visitTypevar_Type(subtype, supertype); - } - @Override - public Boolean visitTypevar_Array( - AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, Void p) { - // This happens when the type variable is a captured wildcard. - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Typevar( - AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, Void p) { - TypeMirror subTM = subtype.getUnderlyingType(); - TypeMirror superTM = supertype.getUnderlyingType(); - if (AnnotatedTypes.haveSameDeclaration(checker.getTypeUtils(), subtype, supertype)) { - // The underlying types of subtype and supertype are uses of the same type parameter, - // but they may have different primary annotations. - AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); - boolean subtypeHasAnno = subtypeAnno != null; - AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); - boolean supertypeHasAnno = supertypeAnno != null; - - if (subtypeHasAnno && supertypeHasAnno) { - // If both have primary annotations then just check the primary annotations - // as the bounds are the same. - return isPrimarySubtype(subtype, supertype); - } else if (!subtypeHasAnno && !supertypeHasAnno) { - // Two unannotated uses of the same type parameter need to compare - // both upper and lower bounds. - - // Upper bound of the subtype needs to be below the upper bound of the supertype. - if (!qualHierarchy.isSubtypeShallow( - subtype.getEffectiveAnnotationInHierarchy(currentTop), - subTM, - supertype.getEffectiveAnnotationInHierarchy(currentTop), - superTM)) { - return false; - } - - // Lower bound of the subtype needs to be below the lower bound of the supertype. - // TODO: Think through this and add better test coverage. - AnnotationMirrorSet subLBs = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, subtype); - AnnotationMirror subLB = - qualHierarchy.findAnnotationInHierarchy(subLBs, currentTop); - AnnotationMirrorSet superLBs = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); - AnnotationMirror superLB = - qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); - return qualHierarchy.isSubtypeShallow(subLB, subTM, superLB, superTM); - } else if (subtypeHasAnno && !supertypeHasAnno) { - // This is the case "@A T <: T" where T is a type variable. - // TODO: should this also test the upper bounds? - AnnotationMirrorSet superLBs = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); - AnnotationMirror superLB = - qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); - return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, superLB, superTM); - } else if (!subtypeHasAnno && supertypeHasAnno) { - // This is the case "T <: @A T" where T is a type variable. - // TODO: should this also test the lower bounds? - return qualHierarchy.isSubtypeShallow( - subtype.getEffectiveAnnotationInHierarchy(currentTop), - subTM, - supertypeAnno, - superTM); - } else { - throw new BugInCF("Unreachable"); - } - } - - if (AnnotatedTypes.areCorrespondingTypeVariables( - checker.getProcessingEnvironment().getElementUtils(), subtype, supertype)) { - if (areEqualInHierarchy(subtype, supertype)) { - return true; - } - } - - if (TypesUtils.isCapturedTypeVariable(subTM) - && TypesUtils.isCapturedTypeVariable(superTM)) { - // This should be removed when 979 is fixed. - // This case happens when the captured type variables should be the same type, but - // aren't because type argument inference isn't implemented correctly. - if (isContainedWithinBounds( - subtype, supertype.getLowerBound(), supertype.getUpperBound(), false)) { - return true; - } - } - - if (supertype.getLowerBound().getKind() != TypeKind.NULL) { - return visit(subtype, supertype.getLowerBound(), p); - } - // check that the upper bound of the subtype is below the lower bound of the supertype - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Null( - AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Void p) { - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Wildcard( - AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // wildcard as subtype - - @Override - public Boolean visitWildcard_Array( - AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Void p) { - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Declared( - AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, Void p) { - if (subtype.isUninferredTypeArgument()) { - if (subtype.atypeFactory.ignoreUninferredTypeArguments) { - return true; - } else if (supertype.getTypeArguments().isEmpty()) { - // visitWildcard_Type doesn't check uninferred type arguments, because the - // underlying Java types may not be in the correct relationship. But, if the - // declared type does not have type arguments, then checking primary annotations is - // sufficient. - // For example, if the wildcard is ? extends @Nullable Object and the supertype is - // @Nullable String, then it is safe to return true. However if the supertype is - // @NullableList<@NonNull String> then it's not possible to decide if it is a - // subtype of the wildcard. - return isSubtypeShallowEffective(subtype, supertype, currentTop); - } - } - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Intersection( - AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, Void p) { - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Primitive( - AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, Void p) { - if (subtype.isUninferredTypeArgument()) { - return isSubtypeShallowEffective(subtype, supertype, currentTop); - } - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Typevar( - AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Wildcard( - AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, Void p) { - return visitWildcard_Type(subtype, supertype); + // Lower bound of the subtype needs to be below the lower bound of the supertype. + // TODO: Think through this and add better test coverage. + AnnotationMirrorSet subLBs = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, subtype); + AnnotationMirror subLB = qualHierarchy.findAnnotationInHierarchy(subLBs, currentTop); + AnnotationMirrorSet superLBs = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); + AnnotationMirror superLB = qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); + return qualHierarchy.isSubtypeShallow(subLB, subTM, superLB, superTM); + } else if (subtypeHasAnno && !supertypeHasAnno) { + // This is the case "@A T <: T" where T is a type variable. + // TODO: should this also test the upper bounds? + AnnotationMirrorSet superLBs = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); + AnnotationMirror superLB = qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); + return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, superLB, superTM); + } else if (!subtypeHasAnno && supertypeHasAnno) { + // This is the case "T <: @A T" where T is a type variable. + // TODO: should this also test the lower bounds? + return qualHierarchy.isSubtypeShallow( + subtype.getEffectiveAnnotationInHierarchy(currentTop), subTM, supertypeAnno, superTM); + } else { + throw new BugInCF("Unreachable"); + } } - // ------------------------------------------------------------------------ - // These "visit" methods are utility methods that aren't part of the visit interface - // but that handle cases that more than one visit method shares in common. - - /** - * An intersection is a supertype if all of its bounds are a supertype of subtype. - * - * @param subtype the possible subtype - * @param supertype the possible supertype - * @return true {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitType_Intersection( - AnnotatedTypeMirror subtype, AnnotatedIntersectionType supertype) { - if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { - return true; - } - boolean result = true; - for (AnnotatedTypeMirror bound : supertype.getBounds()) { - // Only call isSubtype if the Java type is actually a subtype; otherwise, - // only check primary qualifiers. - if (TypesUtils.isErasedSubtype( - subtype.getUnderlyingType(), - bound.getUnderlyingType(), - subtype.atypeFactory.types) - && !isSubtype(subtype, bound, currentTop)) { - result = false; - break; - } - } - isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); - return result; + if (AnnotatedTypes.areCorrespondingTypeVariables( + checker.getProcessingEnvironment().getElementUtils(), subtype, supertype)) { + if (areEqualInHierarchy(subtype, supertype)) { + return true; + } } - /** - * An intersection is a subtype if one of its bounds is a subtype of {@code supertype}. - * - * @param subtype an intersection type - * @param supertype an annotated type - * @return whether {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitIntersection_Type( - AnnotatedIntersectionType subtype, AnnotatedTypeMirror supertype) { + if (TypesUtils.isCapturedTypeVariable(subTM) && TypesUtils.isCapturedTypeVariable(superTM)) { + // This should be removed when 979 is fixed. + // This case happens when the captured type variables should be the same type, but + // aren't because type argument inference isn't implemented correctly. + if (isContainedWithinBounds( + subtype, supertype.getLowerBound(), supertype.getUpperBound(), false)) { + return true; + } + } + + if (supertype.getLowerBound().getKind() != TypeKind.NULL) { + return visit(subtype, supertype.getLowerBound(), p); + } + // check that the upper bound of the subtype is below the lower bound of the supertype + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Null( + AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Void p) { + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Wildcard( + AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // wildcard as subtype + + @Override + public Boolean visitWildcard_Array( + AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Declared( + AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, Void p) { + if (subtype.isUninferredTypeArgument()) { + if (subtype.atypeFactory.ignoreUninferredTypeArguments) { + return true; + } else if (supertype.getTypeArguments().isEmpty()) { + // visitWildcard_Type doesn't check uninferred type arguments, because the + // underlying Java types may not be in the correct relationship. But, if the + // declared type does not have type arguments, then checking primary annotations is + // sufficient. + // For example, if the wildcard is ? extends @Nullable Object and the supertype is + // @Nullable String, then it is safe to return true. However if the supertype is + // @NullableList<@NonNull String> then it's not possible to decide if it is a + // subtype of the wildcard. + return isSubtypeShallowEffective(subtype, supertype, currentTop); + } + } + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Intersection( + AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Primitive( + AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, Void p) { + if (subtype.isUninferredTypeArgument()) { + return isSubtypeShallowEffective(subtype, supertype, currentTop); + } + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Typevar( + AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Wildcard( + AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // These "visit" methods are utility methods that aren't part of the visit interface + // but that handle cases that more than one visit method shares in common. + + /** + * An intersection is a supertype if all of its bounds are a supertype of subtype. + * + * @param subtype the possible subtype + * @param supertype the possible supertype + * @return true {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitType_Intersection( + AnnotatedTypeMirror subtype, AnnotatedIntersectionType supertype) { + if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { + return true; + } + boolean result = true; + for (AnnotatedTypeMirror bound : supertype.getBounds()) { + // Only call isSubtype if the Java type is actually a subtype; otherwise, + // only check primary qualifiers. + if (TypesUtils.isErasedSubtype( + subtype.getUnderlyingType(), bound.getUnderlyingType(), subtype.atypeFactory.types) + && !isSubtype(subtype, bound, currentTop)) { + result = false; + break; + } + } + isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); + return result; + } + + /** + * An intersection is a subtype if one of its bounds is a subtype of {@code supertype}. + * + * @param subtype an intersection type + * @param supertype an annotated type + * @return whether {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitIntersection_Type( + AnnotatedIntersectionType subtype, AnnotatedTypeMirror supertype) { + Types types = checker.getTypeUtils(); + // The primary annotations of the bounds should already be the same as the annotations on + // the intersection type. + for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { + if (TypesUtils.isErasedSubtype( + subtypeBound.getUnderlyingType(), supertype.getUnderlyingType(), types) + && isSubtype(subtypeBound, supertype, currentTop)) { + return true; + } + } + return false; + } + + /** + * A type variable is a supertype if its lower bound is above subtype. + * + * @param subtype a type that might be a subtype + * @param supertype a type that might be a supertype + * @return true if {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitType_Typevar( + AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype) { + return isSubtypeCaching(subtype, supertype.getLowerBound()); + } + + /** + * A type variable is a subtype if its upper bound is below the supertype. + * + * @param subtype a type that might be a subtype + * @param supertype a type that might be a supertype + * @return true if {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitTypevar_Type( + AnnotatedTypeVariable subtype, AnnotatedTypeMirror supertype) { + AnnotatedTypeMirror subtypeUpperBound = subtype.getUpperBound(); + if (TypesUtils.isBoxedPrimitive(subtypeUpperBound.getUnderlyingType()) + && supertype instanceof AnnotatedPrimitiveType) { + subtypeUpperBound = + subtype.atypeFactory.getUnboxedType((AnnotatedDeclaredType) subtypeUpperBound); + } + if (supertype.getKind() == TypeKind.DECLARED + && TypesUtils.getTypeElement(supertype.getUnderlyingType()).getKind() + == ElementKind.INTERFACE) { + // The supertype is an interface. + subtypeUpperBound = getNonWildcardOrTypeVarUpperBound(subtypeUpperBound); + if (subtypeUpperBound.getKind() == TypeKind.INTERSECTION) { + // Only compare the primary annotations. Types types = checker.getTypeUtils(); - // The primary annotations of the bounds should already be the same as the annotations on - // the intersection type. - for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { - if (TypesUtils.isErasedSubtype( - subtypeBound.getUnderlyingType(), supertype.getUnderlyingType(), types) - && isSubtype(subtypeBound, supertype, currentTop)) { - return true; - } + for (AnnotatedTypeMirror bound : + ((AnnotatedIntersectionType) subtypeUpperBound).getBounds()) { + // Make sure the upper bound is no wildcard or type variable. + bound = getNonWildcardOrTypeVarUpperBound(bound); + if (TypesUtils.isErasedSubtype( + bound.getUnderlyingType(), supertype.getUnderlyingType(), types) + && isPrimarySubtype(bound, supertype)) { + return true; + } } return false; + } + } + try { + return isSubtypeCaching(subtypeUpperBound, supertype); + } catch (BugInCF e) { + if (TypesUtils.isCapturedTypeVariable(subtype.underlyingType)) { + // The upper bound of captured type variable may be computed incorrectly by javac. + // javac computes the upper bound as a declared type, when it should be an + // intersection type. + // (This is a bug in the GLB algorithm; see + // https://bugs.openjdk.org/browse/JDK-8039222) + // In this case, the upperbound is not a subtype of `supertype` and the Checker + // Framework crashes. So catch that crash and just return false. + // TODO: catch the problem more locally. + return false; + } + throw e; + } + } + + /** + * If {@code type} is a type variable or wildcard recur on its upper bound until an upper bound is + * found that is neither a type variable nor a wildcard. + * + * @param type the type + * @return if {@code type} is a type variable or wildcard, recur on its upper bound until an upper + * bound is found that is neither a type variable nor a wildcard. Otherwise, return {@code + * type} itself. + */ + private AnnotatedTypeMirror getNonWildcardOrTypeVarUpperBound(AnnotatedTypeMirror type) { + while (type.getKind() == TypeKind.TYPEVAR || type.getKind() == TypeKind.WILDCARD) { + if (type.getKind() == TypeKind.TYPEVAR) { + type = ((AnnotatedTypeVariable) type).getUpperBound(); + } + if (type.getKind() == TypeKind.WILDCARD) { + type = ((AnnotatedWildcardType) type).getExtendsBound(); + } + } + return type; + } + + /** + * A union type is a subtype if ALL of its alternatives are subtypes of supertype. + * + * @param subtype the potential subtype to check + * @param supertype the supertype to check + * @return whether all the alternatives of subtype are subtypes of supertype + */ + protected boolean visitUnion_Type(AnnotatedUnionType subtype, AnnotatedTypeMirror supertype) { + return areAllSubtypes(subtype.getAlternatives(), supertype); + } + + /** + * Check a wildcard type's relation against a subtype. + * + * @param subtype the potential subtype to check + * @param supertype the wildcard supertype to check + * @return whether the subtype is a subtype of the supertype's super bound + */ + protected boolean visitType_Wildcard( + AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype) { + if (supertype.isUninferredTypeArgument()) { // TODO: REMOVE WHEN WE FIX TYPE ARG INFERENCE + // Can't call isSubtype because underlying Java types won't be subtypes. + return supertype.atypeFactory.ignoreUninferredTypeArguments; + } + return isSubtype(subtype, supertype.getSuperBound(), currentTop); + } + + /** + * Check a wildcard type's relation against a supertype. + * + * @param subtype the potential wildcard subtype to check + * @param supertype the supertype to check + * @return whether the subtype's extends bound is a subtype of the supertype + */ + protected boolean visitWildcard_Type( + AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype) { + if (subtype.isUninferredTypeArgument()) { + return subtype.atypeFactory.ignoreUninferredTypeArguments; + } + + if (supertype.getKind() == TypeKind.WILDCARD) { + // This can happen at a method invocation where a type variable in the method + // declaration is substituted with a wildcard. + // For example: + // void method(Gen t) {} + // Gen x; + // method(x); + // visitWildcard_Type is called when checking the method call `method(x)`, + // and also when checking lambdas. + + boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null; + boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null; + + if (subtypeHasAnno && supertypeHasAnno) { + // If both have primary annotations then just check the primary annotations + // as the bounds are the same. + return isPrimarySubtype(subtype, supertype); + } else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype)) { + // Two unannotated uses of wildcard types are the same type + return true; + } } - /** - * A type variable is a supertype if its lower bound is above subtype. - * - * @param subtype a type that might be a subtype - * @param supertype a type that might be a supertype - * @return true if {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitType_Typevar( - AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype) { - return isSubtypeCaching(subtype, supertype.getLowerBound()); - } - - /** - * A type variable is a subtype if its upper bound is below the supertype. - * - * @param subtype a type that might be a subtype - * @param supertype a type that might be a supertype - * @return true if {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitTypevar_Type( - AnnotatedTypeVariable subtype, AnnotatedTypeMirror supertype) { - AnnotatedTypeMirror subtypeUpperBound = subtype.getUpperBound(); - if (TypesUtils.isBoxedPrimitive(subtypeUpperBound.getUnderlyingType()) - && supertype instanceof AnnotatedPrimitiveType) { - subtypeUpperBound = - subtype.atypeFactory.getUnboxedType((AnnotatedDeclaredType) subtypeUpperBound); - } - if (supertype.getKind() == TypeKind.DECLARED - && TypesUtils.getTypeElement(supertype.getUnderlyingType()).getKind() - == ElementKind.INTERFACE) { - // The supertype is an interface. - subtypeUpperBound = getNonWildcardOrTypeVarUpperBound(subtypeUpperBound); - if (subtypeUpperBound.getKind() == TypeKind.INTERSECTION) { - // Only compare the primary annotations. - Types types = checker.getTypeUtils(); - for (AnnotatedTypeMirror bound : - ((AnnotatedIntersectionType) subtypeUpperBound).getBounds()) { - // Make sure the upper bound is no wildcard or type variable. - bound = getNonWildcardOrTypeVarUpperBound(bound); - if (TypesUtils.isErasedSubtype( - bound.getUnderlyingType(), supertype.getUnderlyingType(), types) - && isPrimarySubtype(bound, supertype)) { - return true; - } - } - return false; - } - } - try { - return isSubtypeCaching(subtypeUpperBound, supertype); - } catch (BugInCF e) { - if (TypesUtils.isCapturedTypeVariable(subtype.underlyingType)) { - // The upper bound of captured type variable may be computed incorrectly by javac. - // javac computes the upper bound as a declared type, when it should be an - // intersection type. - // (This is a bug in the GLB algorithm; see - // https://bugs.openjdk.org/browse/JDK-8039222) - // In this case, the upperbound is not a subtype of `supertype` and the Checker - // Framework crashes. So catch that crash and just return false. - // TODO: catch the problem more locally. - return false; - } - throw e; - } - } - - /** - * If {@code type} is a type variable or wildcard recur on its upper bound until an upper bound - * is found that is neither a type variable nor a wildcard. - * - * @param type the type - * @return if {@code type} is a type variable or wildcard, recur on its upper bound until an - * upper bound is found that is neither a type variable nor a wildcard. Otherwise, return - * {@code type} itself. - */ - private AnnotatedTypeMirror getNonWildcardOrTypeVarUpperBound(AnnotatedTypeMirror type) { - while (type.getKind() == TypeKind.TYPEVAR || type.getKind() == TypeKind.WILDCARD) { - if (type.getKind() == TypeKind.TYPEVAR) { - type = ((AnnotatedTypeVariable) type).getUpperBound(); - } - if (type.getKind() == TypeKind.WILDCARD) { - type = ((AnnotatedWildcardType) type).getExtendsBound(); - } - } - return type; - } - - /** - * A union type is a subtype if ALL of its alternatives are subtypes of supertype. - * - * @param subtype the potential subtype to check - * @param supertype the supertype to check - * @return whether all the alternatives of subtype are subtypes of supertype - */ - protected boolean visitUnion_Type(AnnotatedUnionType subtype, AnnotatedTypeMirror supertype) { - return areAllSubtypes(subtype.getAlternatives(), supertype); - } - - /** - * Check a wildcard type's relation against a subtype. - * - * @param subtype the potential subtype to check - * @param supertype the wildcard supertype to check - * @return whether the subtype is a subtype of the supertype's super bound - */ - protected boolean visitType_Wildcard( - AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype) { - if (supertype.isUninferredTypeArgument()) { // TODO: REMOVE WHEN WE FIX TYPE ARG INFERENCE - // Can't call isSubtype because underlying Java types won't be subtypes. - return supertype.atypeFactory.ignoreUninferredTypeArguments; - } - return isSubtype(subtype, supertype.getSuperBound(), currentTop); - } - - /** - * Check a wildcard type's relation against a supertype. - * - * @param subtype the potential wildcard subtype to check - * @param supertype the supertype to check - * @return whether the subtype's extends bound is a subtype of the supertype - */ - protected boolean visitWildcard_Type( - AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype) { - if (subtype.isUninferredTypeArgument()) { - return subtype.atypeFactory.ignoreUninferredTypeArguments; - } - - if (supertype.getKind() == TypeKind.WILDCARD) { - // This can happen at a method invocation where a type variable in the method - // declaration is substituted with a wildcard. - // For example: - // void method(Gen t) {} - // Gen x; - // method(x); - // visitWildcard_Type is called when checking the method call `method(x)`, - // and also when checking lambdas. - - boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null; - boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null; - - if (subtypeHasAnno && supertypeHasAnno) { - // If both have primary annotations then just check the primary annotations - // as the bounds are the same. - return isPrimarySubtype(subtype, supertype); - } else if (!subtypeHasAnno - && !supertypeHasAnno - && areEqualInHierarchy(subtype, supertype)) { - // Two unannotated uses of wildcard types are the same type - return true; - } - } - - return isSubtype(subtype.getExtendsBound(), supertype, currentTop); - } + return isSubtype(subtype.getExtendsBound(), supertype, currentTop); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java index e0f090fdaf6..cf3e34d23a6 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java @@ -4,7 +4,12 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Symbol; - +import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; @@ -22,14 +27,6 @@ import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.IPair; -import java.util.List; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; - /** * Utility methods for adding the annotations that are stored in an Element to the type that * represents that element (or a use of that Element). @@ -47,183 +44,180 @@ */ public final class ElementAnnotationApplier { - /** Do not instantiate. */ - private ElementAnnotationApplier() { - throw new AssertionError("Class ElementAnnotationApplier cannot be instantiated."); + /** Do not instantiate. */ + private ElementAnnotationApplier() { + throw new AssertionError("Class ElementAnnotationApplier cannot be instantiated."); + } + + /** + * Add all of the relevant annotations stored in Element to type. This includes both top-level + * primary annotations and nested annotations. For the most part the TypeAnnotationPosition of the + * element annotations are used to locate the annotation in the right AnnotatedTypeMirror location + * though the individual applier classes may have special rules (such as those for upper and lower + * bounds and intersections). + * + *

Note: Element annotations come from two sources. + * + *

    + *
  1. Annotations found on elements may represent those in source code or bytecode; these are + * added to the element by the compiler. + *
  2. The annotations may also represent those that were inferred or defaulted by the Checker + * Framework after a previous call to this method. The Checker Framework will store any + * annotations on declarations back into the elements that represent them (see {@link + * org.checkerframework.framework.type.TypesIntoElements}). Subsequent, calls to apply will + * encounter these annotations on the provided element. + *
+ * + * Note: This is not the ONLY place that annotations are explicitly added to types. See {@link + * org.checkerframework.framework.type.TypeFromTree}. + * + * @param type the type to which we wish to apply the element's annotations + * @param element an element that possibly contains annotations + * @param typeFactory the typeFactory used to create the given type + */ + public static void apply( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) { + try { + applyInternal(type, element, typeFactory); + } catch (UnexpectedAnnotationLocationException e) { + reportInvalidLocation(element, typeFactory); } - - /** - * Add all of the relevant annotations stored in Element to type. This includes both top-level - * primary annotations and nested annotations. For the most part the TypeAnnotationPosition of - * the element annotations are used to locate the annotation in the right AnnotatedTypeMirror - * location though the individual applier classes may have special rules (such as those for - * upper and lower bounds and intersections). - * - *

Note: Element annotations come from two sources. - * - *

    - *
  1. Annotations found on elements may represent those in source code or bytecode; these are - * added to the element by the compiler. - *
  2. The annotations may also represent those that were inferred or defaulted by the Checker - * Framework after a previous call to this method. The Checker Framework will store any - * annotations on declarations back into the elements that represent them (see {@link - * org.checkerframework.framework.type.TypesIntoElements}). Subsequent, calls to apply - * will encounter these annotations on the provided element. - *
- * - * Note: This is not the ONLY place that annotations are explicitly added to types. See {@link - * org.checkerframework.framework.type.TypeFromTree}. - * - * @param type the type to which we wish to apply the element's annotations - * @param element an element that possibly contains annotations - * @param typeFactory the typeFactory used to create the given type - */ - public static void apply( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) { - try { - applyInternal(type, element, typeFactory); - } catch (UnexpectedAnnotationLocationException e) { - reportInvalidLocation(element, typeFactory); - } - // Also copy annotations from type parameters to their uses. - new TypeVarAnnotator().visit(type, typeFactory); + // Also copy annotations from type parameters to their uses. + new TypeVarAnnotator().visit(type, typeFactory); + } + + /** Issues an "invalid.annotation.location.bytecode warning. */ + private static void reportInvalidLocation(Element element, AnnotatedTypeFactory typeFactory) { + Element report = element; + if (element.getEnclosingElement().getKind() == ElementKind.METHOD) { + report = element.getEnclosingElement(); } - - /** Issues an "invalid.annotation.location.bytecode warning. */ - private static void reportInvalidLocation(Element element, AnnotatedTypeFactory typeFactory) { - Element report = element; - if (element.getEnclosingElement().getKind() == ElementKind.METHOD) { - report = element.getEnclosingElement(); - } - // There's a bug in Java 8 compiler that creates bad bytecode such that an - // annotation on a lambda parameter is applied to a method parameter. (This bug has - // been fixed in Java 9.) If this happens, then the location could refer to a - // location, such as a type argument, that doesn't exist. Since Java 8 bytecode - // might be on the classpath, catch this exception and ignore the type. - // TODO: Issue an error if this annotation is from Java 9+ bytecode. - if (!typeFactory.checker.hasOption("ignoreInvalidAnnotationLocations")) { - typeFactory.checker.reportWarning( - element, - "invalid.annotation.location.bytecode", - ElementUtils.getQualifiedName(report)); - } + // There's a bug in Java 8 compiler that creates bad bytecode such that an + // annotation on a lambda parameter is applied to a method parameter. (This bug has + // been fixed in Java 9.) If this happens, then the location could refer to a + // location, such as a type argument, that doesn't exist. Since Java 8 bytecode + // might be on the classpath, catch this exception and ignore the type. + // TODO: Issue an error if this annotation is from Java 9+ bytecode. + if (!typeFactory.checker.hasOption("ignoreInvalidAnnotationLocations")) { + typeFactory.checker.reportWarning( + element, "invalid.annotation.location.bytecode", ElementUtils.getQualifiedName(report)); } - - /** Same as apply except that annotations aren't copied from type parameter declarations. */ - private static void applyInternal( - final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) - throws UnexpectedAnnotationLocationException { - if (element == null) { - throw new BugInCF("ElementAnnotationUtil.apply: element cannot be null"); - } else if (TypeVarUseApplier.accepts(type, element)) { - TypeVarUseApplier.apply(type, element, typeFactory); - } else if (VariableApplier.accepts(type, element)) { - if (!ElementUtils.isLocalVariable(element)) { - // For local variables we have the source code, - // so there is no need to look at the Element. - // This is needed to avoid a bug in the JDK: - // https://github.com/eisop/checker-framework/issues/14 - VariableApplier.apply(type, element); - } - } else if (MethodApplier.accepts(type, element)) { - MethodApplier.apply(type, element, typeFactory); - } else if (TypeDeclarationApplier.accepts(type, element)) { - TypeDeclarationApplier.apply(type, element, typeFactory); - } else if (ClassTypeParamApplier.accepts(type, element)) { - ClassTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); - } else if (MethodTypeParamApplier.accepts(type, element)) { - MethodTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); - } else if (ParamApplier.accepts(type, element)) { - ParamApplier.apply(type, (VariableElement) element, typeFactory); - } else if (isCaptureConvertedTypeVar(element)) { - // Types resulting from capture conversion cannot have explicit annotations - } else if (ElementUtils.isBindingVariable(element)) { - // TODO: verify that there are no type use annotations that would need decoding - } else { - throw new BugInCF( - "ElementAnnotationUtil.apply: illegal argument: " - + element - + " [" - + element.getKind() - + "]" - + " with type " - + type); - } - } - - /** - * Annotate the list of supertypes using the annotations on the TypeElement representing a class - * or interface. - * - * @param supertypes types representing supertype declarations of TypeElement - * @param subtypeElement an element representing the declaration of the class which is a subtype - * of supertypes - */ - public static void annotateSupers( - List supertypes, TypeElement subtypeElement) { - try { - SuperTypeApplier.annotateSupers(supertypes, subtypeElement); - } catch (UnexpectedAnnotationLocationException e) { - reportInvalidLocation(subtypeElement, supertypes.get(0).atypeFactory); - } + } + + /** Same as apply except that annotations aren't copied from type parameter declarations. */ + private static void applyInternal( + final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) + throws UnexpectedAnnotationLocationException { + if (element == null) { + throw new BugInCF("ElementAnnotationUtil.apply: element cannot be null"); + } else if (TypeVarUseApplier.accepts(type, element)) { + TypeVarUseApplier.apply(type, element, typeFactory); + } else if (VariableApplier.accepts(type, element)) { + if (!ElementUtils.isLocalVariable(element)) { + // For local variables we have the source code, + // so there is no need to look at the Element. + // This is needed to avoid a bug in the JDK: + // https://github.com/eisop/checker-framework/issues/14 + VariableApplier.apply(type, element); + } + } else if (MethodApplier.accepts(type, element)) { + MethodApplier.apply(type, element, typeFactory); + } else if (TypeDeclarationApplier.accepts(type, element)) { + TypeDeclarationApplier.apply(type, element, typeFactory); + } else if (ClassTypeParamApplier.accepts(type, element)) { + ClassTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); + } else if (MethodTypeParamApplier.accepts(type, element)) { + MethodTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); + } else if (ParamApplier.accepts(type, element)) { + ParamApplier.apply(type, (VariableElement) element, typeFactory); + } else if (isCaptureConvertedTypeVar(element)) { + // Types resulting from capture conversion cannot have explicit annotations + } else if (ElementUtils.isBindingVariable(element)) { + // TODO: verify that there are no type use annotations that would need decoding + } else { + throw new BugInCF( + "ElementAnnotationUtil.apply: illegal argument: " + + element + + " [" + + element.getKind() + + "]" + + " with type " + + type); } - - /** - * Helper method to get the lambda tree for ParamApplier. Ideally, this method would be located - * in ElementAnnotationUtil but since AnnotatedTypeFactory.declarationFromElement is protected, - * it has been placed here. - * - * @param varEle the element that may represent a lambda's parameter - * @return a LambdaExpressionTree if the varEle represents a parameter in a lambda expression, - * otherwise null - */ - public static @Nullable IPair getParamAndLambdaTree( - VariableElement varEle, AnnotatedTypeFactory typeFactory) { - VariableTree paramDecl = (VariableTree) typeFactory.declarationFromElement(varEle); - - if (paramDecl != null) { - Tree parentTree = typeFactory.getPath(paramDecl).getParentPath().getLeaf(); - if (parentTree != null && parentTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - return IPair.of(paramDecl, (LambdaExpressionTree) parentTree); - } - } - - return null; + } + + /** + * Annotate the list of supertypes using the annotations on the TypeElement representing a class + * or interface. + * + * @param supertypes types representing supertype declarations of TypeElement + * @param subtypeElement an element representing the declaration of the class which is a subtype + * of supertypes + */ + public static void annotateSupers( + List supertypes, TypeElement subtypeElement) { + try { + SuperTypeApplier.annotateSupers(supertypes, subtypeElement); + } catch (UnexpectedAnnotationLocationException e) { + reportInvalidLocation(subtypeElement, supertypes.get(0).atypeFactory); } - - /** - * Was the type passed in generated by capture conversion. - * - * @param element the element which type represents - * @return true if type was generated via capture conversion false otherwise - */ - private static boolean isCaptureConvertedTypeVar(Element element) { - Element enclosure = element.getEnclosingElement(); - return (((Symbol) enclosure).kind == com.sun.tools.javac.code.Kinds.Kind.NIL); + } + + /** + * Helper method to get the lambda tree for ParamApplier. Ideally, this method would be located in + * ElementAnnotationUtil but since AnnotatedTypeFactory.declarationFromElement is protected, it + * has been placed here. + * + * @param varEle the element that may represent a lambda's parameter + * @return a LambdaExpressionTree if the varEle represents a parameter in a lambda expression, + * otherwise null + */ + public static @Nullable IPair getParamAndLambdaTree( + VariableElement varEle, AnnotatedTypeFactory typeFactory) { + VariableTree paramDecl = (VariableTree) typeFactory.declarationFromElement(varEle); + + if (paramDecl != null) { + Tree parentTree = typeFactory.getPath(paramDecl).getParentPath().getLeaf(); + if (parentTree != null && parentTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + return IPair.of(paramDecl, (LambdaExpressionTree) parentTree); + } } - /** - * Annotates uses of type variables with annotation written explicitly on the type parameter - * declaration and/or its upper bound. - */ - private static class TypeVarAnnotator extends AnnotatedTypeScanner { - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeFactory factory) { - TypeParameterElement tpelt = - (TypeParameterElement) type.getUnderlyingType().asElement(); - - if (type.getAnnotations().isEmpty() - && type.getUpperBound().getAnnotations().isEmpty() - && tpelt.getEnclosingElement().getKind() != ElementKind.TYPE_PARAMETER) { - try { - ElementAnnotationApplier.applyInternal(type, tpelt, factory); - } catch (UnexpectedAnnotationLocationException e) { - // The above is the second call to applyInternal on this type and element, so - // any errors were already reported by the first call. (See the only use of this - // class.) - } - } - return super.visitTypeVariable(type, factory); + return null; + } + + /** + * Was the type passed in generated by capture conversion. + * + * @param element the element which type represents + * @return true if type was generated via capture conversion false otherwise + */ + private static boolean isCaptureConvertedTypeVar(Element element) { + Element enclosure = element.getEnclosingElement(); + return (((Symbol) enclosure).kind == com.sun.tools.javac.code.Kinds.Kind.NIL); + } + + /** + * Annotates uses of type variables with annotation written explicitly on the type parameter + * declaration and/or its upper bound. + */ + private static class TypeVarAnnotator extends AnnotatedTypeScanner { + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeFactory factory) { + TypeParameterElement tpelt = (TypeParameterElement) type.getUnderlyingType().asElement(); + + if (type.getAnnotations().isEmpty() + && type.getUpperBound().getAnnotations().isEmpty() + && tpelt.getEnclosingElement().getKind() != ElementKind.TYPE_PARAMETER) { + try { + ElementAnnotationApplier.applyInternal(type, tpelt, factory); + } catch (UnexpectedAnnotationLocationException e) { + // The above is the second call to applyInternal on this type and element, so + // any errors were already reported by the first call. (See the only use of this + // class.) } + } + return super.visitTypeVariable(type, factory); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java index 15cbc22dc99..03b733167f5 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.type; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -15,15 +22,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** * A {@link QualifierHierarchy} where qualifiers may be represented by annotations with elements. * @@ -38,230 +36,229 @@ @AnnotatedFor("nullness") public abstract class ElementQualifierHierarchy extends QualifierHierarchy { - /** {@link org.checkerframework.javacutil.ElementUtils}. */ - private final Elements elements; + /** {@link org.checkerframework.javacutil.ElementUtils}. */ + private final Elements elements; - /** {@link QualifierKindHierarchy}. */ - protected final QualifierKindHierarchy qualifierKindHierarchy; + /** {@link QualifierKindHierarchy}. */ + protected final QualifierKindHierarchy qualifierKindHierarchy; - // The following fields duplicate information in qualifierKindHierarchy, but using - // AnnotationMirrors instead of QualifierKinds. + // The following fields duplicate information in qualifierKindHierarchy, but using + // AnnotationMirrors instead of QualifierKinds. - /** A mapping from top QualifierKinds to their corresponding AnnotationMirror. */ - protected final Map topsMap; + /** A mapping from top QualifierKinds to their corresponding AnnotationMirror. */ + protected final Map topsMap; - /** The set of top annotation mirrors. */ - protected final AnnotationMirrorSet tops; + /** The set of top annotation mirrors. */ + protected final AnnotationMirrorSet tops; - /** A mapping from bottom QualifierKinds to their corresponding AnnotationMirror. */ - protected final Map bottomsMap; + /** A mapping from bottom QualifierKinds to their corresponding AnnotationMirror. */ + protected final Map bottomsMap; - /** The set of bottom annotation mirrors. */ - protected final AnnotationMirrorSet bottoms; + /** The set of bottom annotation mirrors. */ + protected final AnnotationMirrorSet bottoms; - /** - * A mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations do not - * have elements. - */ - protected final Map kindToElementlessQualifier; + /** + * A mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations do not + * have elements. + */ + protected final Map kindToElementlessQualifier; - /** - * Creates a ElementQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - * @param atypeFactory the associated type factory - */ - protected ElementQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** + * Creates a ElementQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + * @param atypeFactory the associated type factory + */ + protected ElementQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); - this.elements = elements; - this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); + this.elements = elements; + this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); - this.topsMap = Collections.unmodifiableMap(createTopsMap()); - this.tops = AnnotationMirrorSet.unmodifiableSet(topsMap.values()); + this.topsMap = Collections.unmodifiableMap(createTopsMap()); + this.tops = AnnotationMirrorSet.unmodifiableSet(topsMap.values()); - this.bottomsMap = Collections.unmodifiableMap(createBottomsMap()); - this.bottoms = AnnotationMirrorSet.unmodifiableSet(bottomsMap.values()); + this.bottomsMap = Collections.unmodifiableMap(createBottomsMap()); + this.bottoms = AnnotationMirrorSet.unmodifiableSet(bottomsMap.values()); - this.kindToElementlessQualifier = createElementlessQualifierMap(); - } + this.kindToElementlessQualifier = createElementlessQualifierMap(); + } - @Override - public boolean isValid() { - for (AnnotationMirror top : tops) { - // This throws an error if poly is a qualifier that has an element. - getPolymorphicAnnotation(top); - } - return true; + @Override + public boolean isValid() { + for (AnnotationMirror top : tops) { + // This throws an error if poly is a qualifier that has an element. + getPolymorphicAnnotation(top); } - - /** - * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of - * QualifierKindHierarchy.) - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @return the newly created qualifier kind hierarchy - */ - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization ElementQualifierHierarchy this, - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses); + return true; + } + + /** + * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of + * QualifierKindHierarchy.) + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @return the newly created qualifier kind hierarchy + */ + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization ElementQualifierHierarchy this, + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses); + } + + /** + * Creates a mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations + * do not have elements. + * + * @return the mapping + */ + @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) + protected Map createElementlessQualifierMap( + @UnderInitialization ElementQualifierHierarchy this) { + Map quals = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { + if (!kind.hasElements()) { + quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + } } - - /** - * Creates a mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations - * do not have elements. - * - * @return the mapping - */ - @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) - protected Map createElementlessQualifierMap( - @UnderInitialization ElementQualifierHierarchy this) { - Map quals = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { - if (!kind.hasElements()) { - quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); - } - } - return Collections.unmodifiableMap(quals); + return Collections.unmodifiableMap(quals); + } + + /** + * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is top and + * the AnnotationMirror is top in their respective hierarchies. + * + *

This implementation works if the top annotation has no elements, or if it has elements, + * provides a default, and that default is the top. Otherwise, subclasses must override this. + * + * @return a mapping from top QualifierKind to top AnnotationMirror + */ + @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) + protected Map createTopsMap( + @UnderInitialization ElementQualifierHierarchy this) { + Map topsMap = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.getTops()) { + topsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); } - - /** - * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is top and - * the AnnotationMirror is top in their respective hierarchies. - * - *

This implementation works if the top annotation has no elements, or if it has elements, - * provides a default, and that default is the top. Otherwise, subclasses must override this. - * - * @return a mapping from top QualifierKind to top AnnotationMirror - */ - @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) - protected Map createTopsMap( - @UnderInitialization ElementQualifierHierarchy this) { - Map topsMap = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.getTops()) { - topsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); - } - return topsMap; - } - - /** - * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is bottom - * and the AnnotationMirror is bottom in their respective hierarchies. - * - *

This implementation works if the bottom annotation has no elements, or if it has elements, - * provides a default, and that default is the bottom. Otherwise, subclasses must override this. - * - * @return a mapping from bottom QualifierKind to bottom AnnotationMirror - */ - @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) - protected Map createBottomsMap( - @UnderInitialization ElementQualifierHierarchy this) { - Map bottomsMap = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.getBottoms()) { - bottomsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); - } - return bottomsMap; - } - - /** - * Returns the qualifier kind for the given annotation. - * - * @param anno an annotation mirror that is in this hierarchy - * @return the qualifier kind for the given annotation - */ - protected QualifierKind getQualifierKind(AnnotationMirror anno) { - String name = AnnotationUtils.annotationName(anno); - QualifierKind result = getQualifierKind(name); - if (result == null) { - throw new BugInCF("No qualifier kind for " + anno); - } - return result; - } - - /** - * Returns the qualifier kind for the annotation with the canonical name {@code name}. - * - * @param name fully qualified annotation name - * @return the qualifier kind for the annotation named {@code name} - */ - protected QualifierKind getQualifierKind(@CanonicalName String name) { - QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); - if (kind == null) { - throw new BugInCF("QualifierKind %s not in hierarchy", name); - } - return kind; - } - - @Override - public AnnotationMirrorSet getTopAnnotations() { - return tops; + return topsMap; + } + + /** + * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is bottom and + * the AnnotationMirror is bottom in their respective hierarchies. + * + *

This implementation works if the bottom annotation has no elements, or if it has elements, + * provides a default, and that default is the bottom. Otherwise, subclasses must override this. + * + * @return a mapping from bottom QualifierKind to bottom AnnotationMirror + */ + @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) + protected Map createBottomsMap( + @UnderInitialization ElementQualifierHierarchy this) { + Map bottomsMap = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.getBottoms()) { + bottomsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); } - - @Override - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - @SuppressWarnings( - "nullness:assignment.type.incompatible") // All tops are a key for topsMap. - @NonNull AnnotationMirror result = topsMap.get(kind.getTop()); - return result; + return bottomsMap; + } + + /** + * Returns the qualifier kind for the given annotation. + * + * @param anno an annotation mirror that is in this hierarchy + * @return the qualifier kind for the given annotation + */ + protected QualifierKind getQualifierKind(AnnotationMirror anno) { + String name = AnnotationUtils.annotationName(anno); + QualifierKind result = getQualifierKind(name); + if (result == null) { + throw new BugInCF("No qualifier kind for " + anno); } - - @Override - public AnnotationMirrorSet getBottomAnnotations() { - return bottoms; + return result; + } + + /** + * Returns the qualifier kind for the annotation with the canonical name {@code name}. + * + * @param name fully qualified annotation name + * @return the qualifier kind for the annotation named {@code name} + */ + protected QualifierKind getQualifierKind(@CanonicalName String name) { + QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); + if (kind == null) { + throw new BugInCF("QualifierKind %s not in hierarchy", name); } - - @Override - public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { - QualifierKind polyKind = getQualifierKind(start).getPolymorphic(); - if (polyKind == null) { - return null; - } - AnnotationMirror poly = kindToElementlessQualifier.get(polyKind); - if (poly == null) { - throw new TypeSystemError( - "Poly %s has an element. Override" - + " ElementQualifierHierarchy#getPolymorphicAnnotation.", - polyKind); - } - return poly; + return kind; + } + + @Override + public AnnotationMirrorSet getTopAnnotations() { + return tops; + } + + @Override + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + @SuppressWarnings("nullness:assignment.type.incompatible") // All tops are a key for topsMap. + @NonNull AnnotationMirror result = topsMap.get(kind.getTop()); + return result; + } + + @Override + public AnnotationMirrorSet getBottomAnnotations() { + return bottoms; + } + + @Override + public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { + QualifierKind polyKind = getQualifierKind(start).getPolymorphic(); + if (polyKind == null) { + return null; } - - @Override - public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { - return getQualifierKind(qualifier).isPoly(); + AnnotationMirror poly = kindToElementlessQualifier.get(polyKind); + if (poly == null) { + throw new TypeSystemError( + "Poly %s has an element. Override" + + " ElementQualifierHierarchy#getPolymorphicAnnotation.", + polyKind); } - - @Override - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - @SuppressWarnings( - "nullness:assignment.type.incompatible") // All bottoms are keys for bottomsMap. - @NonNull AnnotationMirror result = bottomsMap.get(kind.getBottom()); - return result; - } - - @Override - public @Nullable AnnotationMirror findAnnotationInSameHierarchy( - Collection annos, AnnotationMirror annotationMirror) { - QualifierKind kind = getQualifierKind(annotationMirror); - for (AnnotationMirror candidate : annos) { - QualifierKind candidateKind = getQualifierKind(candidate); - if (candidateKind.isInSameHierarchyAs(kind)) { - return candidate; - } - } - return null; - } - - @Override - public @Nullable AnnotationMirror findAnnotationInHierarchy( - Collection annos, AnnotationMirror top) { - return findAnnotationInSameHierarchy(annos, top); + return poly; + } + + @Override + public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { + return getQualifierKind(qualifier).isPoly(); + } + + @Override + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + @SuppressWarnings( + "nullness:assignment.type.incompatible") // All bottoms are keys for bottomsMap. + @NonNull AnnotationMirror result = bottomsMap.get(kind.getBottom()); + return result; + } + + @Override + public @Nullable AnnotationMirror findAnnotationInSameHierarchy( + Collection annos, AnnotationMirror annotationMirror) { + QualifierKind kind = getQualifierKind(annotationMirror); + for (AnnotationMirror candidate : annos) { + QualifierKind candidateKind = getQualifierKind(candidate); + if (candidateKind.isInSameHierarchyAs(kind)) { + return candidate; + } } + return null; + } + + @Override + public @Nullable AnnotationMirror findAnnotationInHierarchy( + Collection annos, AnnotationMirror top) { + return findAnnotationInSameHierarchy(annos, top); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java index b07808acc4c..5f504114efd 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java @@ -20,60 +20,59 @@ */ public class EqualityAtmComparer extends EquivalentAtmComboScanner { - /** - * Return true if {@code type1} and {@code type2} have equivalent sets of annotations. - * - * @param type1 a type - * @param type2 a type - * @return true if {@code type1} and {@code type2} have equivalent sets of annotations - */ - protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - return AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations()); - } - - /** - * Return true if the twe types are the same. - * - * @param type1 the first type to compare - * @param type2 the second type to compare - * @return true if the twe types are the same - */ - @EqualsMethod // to make Interning Checker permit the == comparison - protected boolean compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (type1 == type2) { - return true; - } - if (type1 == null || type2 == null) { - return false; - } + /** + * Return true if {@code type1} and {@code type2} have equivalent sets of annotations. + * + * @param type1 a type + * @param type2 a type + * @return true if {@code type1} and {@code type2} have equivalent sets of annotations + */ + protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + return AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations()); + } - @SuppressWarnings("TypeEquals") // TODO - boolean sameUnderlyingType = type1.getUnderlyingType().equals(type2.getUnderlyingType()); - return sameUnderlyingType && arePrimaryAnnosEqual(type1, type2); + /** + * Return true if the twe types are the same. + * + * @param type1 the first type to compare + * @param type2 the second type to compare + * @return true if the twe types are the same + */ + @EqualsMethod // to make Interning Checker permit the == comparison + protected boolean compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (type1 == type2) { + return true; } - - @SuppressWarnings("interning:not.interned") - @Override - protected Boolean scanWithNull( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { - // one of them should be null, therefore they are only equal if the other is null - return type1 == type2; + if (type1 == null || type2 == null) { + return false; } - @Override - protected Boolean scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void v) { - return compare(type1, type2) && reduce(true, super.scan(type1, type2, v)); - } + @SuppressWarnings("TypeEquals") // TODO + boolean sameUnderlyingType = type1.getUnderlyingType().equals(type2.getUnderlyingType()); + return sameUnderlyingType && arePrimaryAnnosEqual(type1, type2); + } + + @SuppressWarnings("interning:not.interned") + @Override + protected Boolean scanWithNull(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { + // one of them should be null, therefore they are only equal if the other is null + return type1 == type2; + } + + @Override + protected Boolean scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void v) { + return compare(type1, type2) && reduce(true, super.scan(type1, type2, v)); + } - /** Used to combine the results from component types or a type and its component types. */ - @Override - protected Boolean reduce(Boolean r1, Boolean r2) { - if (r1 == null) { - return r2; - } else if (r2 == null) { - return r1; - } else { - return r1 && r2; - } + /** Used to combine the results from component types or a type and its component types. */ + @Override + protected Boolean reduce(Boolean r1, Boolean r2) { + if (r1 == null) { + return r2; + } else if (r2 == null) { + return r1; + } else { + return r1 && r2; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java index aef2d003a88..5dd9c9f7044 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -15,7 +15,32 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.lang.annotation.Annotation; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.StringJoiner; +import java.util.regex.Pattern; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -102,34 +127,6 @@ import org.plumelib.util.IPair; import org.plumelib.util.SystemPlume; -import java.lang.annotation.Annotation; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.StringJoiner; -import java.util.regex.Pattern; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - /** * A factory that extends {@link AnnotatedTypeFactory} to optionally use flow-sensitive qualifier * inference. @@ -141,2533 +138,2541 @@ * AnnotatedTypeFactory}; it is not clear why they are defined in this class. */ public abstract class GenericAnnotatedTypeFactory< - Value extends CFAbstractValue, - Store extends CFAbstractStore, - TransferFunction extends CFAbstractTransfer, - FlowAnalysis extends CFAbstractAnalysis> - extends AnnotatedTypeFactory { - - /** - * Whether to output verbose, low-level debugging messages. Also see {@code TreeAnnotator.debug} - * and {@link AnnotatedTypeFactory#debugStubParser}. - */ - private static final boolean debug = false; - - /** To cache the supported monotonic type qualifiers. */ - private @MonotonicNonNull Set> supportedMonotonicQuals; - - /** to annotate types based on the given tree */ - protected TypeAnnotator typeAnnotator; - - /** for use in addAnnotationsFromDefaultForType */ - private DefaultQualifierForUseTypeAnnotator defaultQualifierForUseTypeAnnotator; - - /** for use in addAnnotationsFromDefaultForType */ - private DefaultForTypeAnnotator defaultForTypeAnnotator; - - /** to annotate types based on the given un-annotated types */ - protected TreeAnnotator treeAnnotator; - - /** to handle any polymorphic types */ - protected QualifierPolymorphism poly; - - /** to handle defaults specified by the user */ - protected QualifierDefaults defaults; - - /** To handle dependent type annotations and contract expressions. */ - protected DependentTypesHelper dependentTypesHelper; - - /** To handle method pre- and postconditions. */ - protected final ContractsFromMethod contractsUtils; - - /** - * The Java types on which users may write this type system's type annotations. null means no - * restrictions. Arrays are handled by separate field {@code #arraysAreRelevant}. - * - *

If the relevant type is generic, this contains its erasure. - * - *

Although a {@code Class} object exists for every element, this does not contain those - * {@code Class} objects because the elements will be compared to TypeMirrors for which Class - * objects may not exist (they might not be on the classpath). - */ - public final @Nullable Set relevantJavaTypes; - - /** - * Whether users may write type annotations on arrays. Ignored unless {@link #relevantJavaTypes} - * is non-null. - */ - protected final boolean arraysAreRelevant; - - // Flow related fields - - /** Should flow be used by default? */ - protected static boolean flowByDefault = true; - - /** - * Should use flow-sensitive type refinement analysis? This value can be changed when an - * AnnotatedTypeMirror without annotations from data flow is required. - * - * @see #getAnnotatedTypeLhs(Tree) - */ - private boolean useFlow; - - /** Is this type factory configured to use flow-sensitive type refinement? */ - private final boolean everUseFlow; - - /** - * Should the local variable default annotation be applied to type variables? - * - *

It is initialized to true if data flow is used by the checker. It is set to false when - * getting the assignment context for type argument inference. - * - * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault - */ - private boolean shouldDefaultTypeVarLocals; - - /** - * The inferred types applier utility to use. Initialized in postInit() and should not be - * re-assigned after initialization. - */ - private DefaultInferredTypesApplier inferredTypesApplier; - - /** - * Elements representing variables for which the type of the initializer is being determined in - * order to apply qualifier parameter defaults. - * - *

Local variables with a qualifier parameter get their declared type from the type of their - * initializer. Sometimes the initializer's type depends on the type of the variable, such as - * during type variable inference or when a variable is used in its own initializer as in - * "Object o = (o = null)". This creates a circular dependency resulting in infinite recursion. - * To prevent this, variables in this set should not be typed based on their initializer, but by - * using normal defaults. - * - *

This set should only be modified in - * GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults which clears - * variables after computing their initializer types. - * - * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults - */ - private final Set variablesUnderInitialization = new HashSet<>(); - - /** - * Caches types of initializers for local variables with a qualifier parameter, so that they - * aren't computed each time the type of a variable is looked up. - * - * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults - */ - private final Map initializerCache; - - /** - * Should the analysis assume that side effects to a value can change the type of aliased - * references? - * - *

For many type systems, once a local variable's type is refined, side effects to the - * variable's value do not change the variable's type annotations. For some type systems, a side - * effect to the value could change them; set this field to true. - */ - // Not final so that subclasses can set it. - public boolean sideEffectsUnrefineAliases = false; - - /** - * True if this checker either has one or more subcheckers, or if this checker is a subchecker. - * False otherwise. All uses of the methods {@link #addSharedCFGForTree(Tree, ControlFlowGraph)} - * and {@link #getSharedCFGForTree(Tree)} should be guarded by a check that this is true. - */ - public final boolean hasOrIsSubchecker; - - /** An empty store. */ - // Set in postInit only - protected Store emptyStore; - - // Set in postInit only - protected FlowAnalysis analysis; - - // Set in postInit only - protected TransferFunction transfer; - - // Maintain for every class the store that is used when we analyze initialization code - protected Store initializationStore; - - // Maintain for every class the store that is used when we analyze static initialization code - protected Store initializationStaticStore; - - /** - * Caches for {@link AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, - * IdentityHashMap, Map)}. This cache is enabled if {@link #shouldCache} is true. The cache size - * is derived from {@link #getCacheSize()}. - * - * @see AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, - * IdentityHashMap, Map) - */ - protected final Map< - TransferInput, - IdentityHashMap>> - flowResultAnalysisCaches; - - /** - * Subcheckers share the same ControlFlowGraph for each analyzed code statement. This maps from - * code statements to the shared control flow graphs. This map is null in all subcheckers (i.e. - * any checker for which getParentChecker() returns non-null). This map is also unused (and - * therefore null) for a checker with no subcheckers with which it can share CFGs. - * - *

The initial capacity of the map is set by {@link #getCacheSize()}. - */ - protected @MonotonicNonNull Map subcheckerSharedCFG; - - /** - * If true, {@link #setRoot(CompilationUnitTree)} should clear the {@link #subcheckerSharedCFG} - * map, freeing memory. - * - *

For each compilation unit, all the subcheckers run first and finally the ultimate parent - * checker runs. The ultimate parent checker's {@link #setRoot(CompilationUnitTree)} (the last - * to run) sets this field to true. - * - *

In first subchecker to run for the next compilation unit, {@link - * #setRoot(CompilationUnitTree)} observes the true value, clears the {@link - * #subcheckerSharedCFG} map, and sets this field back to false. That first subchecker will - * create a CFG and re-populate the map, and subsequent subcheckers will use the map. - */ - protected boolean shouldClearSubcheckerSharedCFGs = true; - - /** - * Creates a type factory. Its compilation unit is not yet set. - * - * @param checker the checker to which this type factory belongs - * @param useFlow whether flow analysis should be performed - */ - protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { - super(checker); - - this.everUseFlow = useFlow; - this.shouldDefaultTypeVarLocals = useFlow; - this.useFlow = useFlow; - - this.flowResult = null; - this.regularExitStores = new IdentityHashMap<>(); - this.exceptionalExitStores = new IdentityHashMap<>(); - this.returnStatementStores = new IdentityHashMap<>(); - - this.initializationStore = null; - this.initializationStaticStore = null; - - this.cfgVisualizer = createCFGVisualizer(); - this.handleCFGViz = checker.hasOption("flowdotdir") || checker.hasOption("cfgviz"); - - if (shouldCache) { - int cacheSize = getCacheSize(); - flowResultAnalysisCaches = CollectionsPlume.createLruCache(cacheSize); - initializerCache = CollectionsPlume.createLruCache(cacheSize); + Value extends CFAbstractValue, + Store extends CFAbstractStore, + TransferFunction extends CFAbstractTransfer, + FlowAnalysis extends CFAbstractAnalysis> + extends AnnotatedTypeFactory { + + /** + * Whether to output verbose, low-level debugging messages. Also see {@code TreeAnnotator.debug} + * and {@link AnnotatedTypeFactory#debugStubParser}. + */ + private static final boolean debug = false; + + /** To cache the supported monotonic type qualifiers. */ + private @MonotonicNonNull Set> supportedMonotonicQuals; + + /** to annotate types based on the given tree */ + protected TypeAnnotator typeAnnotator; + + /** for use in addAnnotationsFromDefaultForType */ + private DefaultQualifierForUseTypeAnnotator defaultQualifierForUseTypeAnnotator; + + /** for use in addAnnotationsFromDefaultForType */ + private DefaultForTypeAnnotator defaultForTypeAnnotator; + + /** to annotate types based on the given un-annotated types */ + protected TreeAnnotator treeAnnotator; + + /** to handle any polymorphic types */ + protected QualifierPolymorphism poly; + + /** to handle defaults specified by the user */ + protected QualifierDefaults defaults; + + /** To handle dependent type annotations and contract expressions. */ + protected DependentTypesHelper dependentTypesHelper; + + /** To handle method pre- and postconditions. */ + protected final ContractsFromMethod contractsUtils; + + /** + * The Java types on which users may write this type system's type annotations. null means no + * restrictions. Arrays are handled by separate field {@code #arraysAreRelevant}. + * + *

If the relevant type is generic, this contains its erasure. + * + *

Although a {@code Class} object exists for every element, this does not contain those + * {@code Class} objects because the elements will be compared to TypeMirrors for which Class + * objects may not exist (they might not be on the classpath). + */ + public final @Nullable Set relevantJavaTypes; + + /** + * Whether users may write type annotations on arrays. Ignored unless {@link #relevantJavaTypes} + * is non-null. + */ + protected final boolean arraysAreRelevant; + + // Flow related fields + + /** Should flow be used by default? */ + protected static boolean flowByDefault = true; + + /** + * Should use flow-sensitive type refinement analysis? This value can be changed when an + * AnnotatedTypeMirror without annotations from data flow is required. + * + * @see #getAnnotatedTypeLhs(Tree) + */ + private boolean useFlow; + + /** Is this type factory configured to use flow-sensitive type refinement? */ + private final boolean everUseFlow; + + /** + * Should the local variable default annotation be applied to type variables? + * + *

It is initialized to true if data flow is used by the checker. It is set to false when + * getting the assignment context for type argument inference. + * + * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault + */ + private boolean shouldDefaultTypeVarLocals; + + /** + * The inferred types applier utility to use. Initialized in postInit() and should not be + * re-assigned after initialization. + */ + private DefaultInferredTypesApplier inferredTypesApplier; + + /** + * Elements representing variables for which the type of the initializer is being determined in + * order to apply qualifier parameter defaults. + * + *

Local variables with a qualifier parameter get their declared type from the type of their + * initializer. Sometimes the initializer's type depends on the type of the variable, such as + * during type variable inference or when a variable is used in its own initializer as in "Object + * o = (o = null)". This creates a circular dependency resulting in infinite recursion. To prevent + * this, variables in this set should not be typed based on their initializer, but by using normal + * defaults. + * + *

This set should only be modified in + * GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults which clears variables + * after computing their initializer types. + * + * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults + */ + private final Set variablesUnderInitialization = new HashSet<>(); + + /** + * Caches types of initializers for local variables with a qualifier parameter, so that they + * aren't computed each time the type of a variable is looked up. + * + * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults + */ + private final Map initializerCache; + + /** + * Should the analysis assume that side effects to a value can change the type of aliased + * references? + * + *

For many type systems, once a local variable's type is refined, side effects to the + * variable's value do not change the variable's type annotations. For some type systems, a side + * effect to the value could change them; set this field to true. + */ + // Not final so that subclasses can set it. + public boolean sideEffectsUnrefineAliases = false; + + /** + * True if this checker either has one or more subcheckers, or if this checker is a subchecker. + * False otherwise. All uses of the methods {@link #addSharedCFGForTree(Tree, ControlFlowGraph)} + * and {@link #getSharedCFGForTree(Tree)} should be guarded by a check that this is true. + */ + public final boolean hasOrIsSubchecker; + + /** An empty store. */ + // Set in postInit only + protected Store emptyStore; + + // Set in postInit only + protected FlowAnalysis analysis; + + // Set in postInit only + protected TransferFunction transfer; + + // Maintain for every class the store that is used when we analyze initialization code + protected Store initializationStore; + + // Maintain for every class the store that is used when we analyze static initialization code + protected Store initializationStaticStore; + + /** + * Caches for {@link AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, + * IdentityHashMap, Map)}. This cache is enabled if {@link #shouldCache} is true. The cache size + * is derived from {@link #getCacheSize()}. + * + * @see AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, + * IdentityHashMap, Map) + */ + protected final Map< + TransferInput, IdentityHashMap>> + flowResultAnalysisCaches; + + /** + * Subcheckers share the same ControlFlowGraph for each analyzed code statement. This maps from + * code statements to the shared control flow graphs. This map is null in all subcheckers (i.e. + * any checker for which getParentChecker() returns non-null). This map is also unused (and + * therefore null) for a checker with no subcheckers with which it can share CFGs. + * + *

The initial capacity of the map is set by {@link #getCacheSize()}. + */ + protected @MonotonicNonNull Map subcheckerSharedCFG; + + /** + * If true, {@link #setRoot(CompilationUnitTree)} should clear the {@link #subcheckerSharedCFG} + * map, freeing memory. + * + *

For each compilation unit, all the subcheckers run first and finally the ultimate parent + * checker runs. The ultimate parent checker's {@link #setRoot(CompilationUnitTree)} (the last to + * run) sets this field to true. + * + *

In first subchecker to run for the next compilation unit, {@link + * #setRoot(CompilationUnitTree)} observes the true value, clears the {@link #subcheckerSharedCFG} + * map, and sets this field back to false. That first subchecker will create a CFG and re-populate + * the map, and subsequent subcheckers will use the map. + */ + protected boolean shouldClearSubcheckerSharedCFGs = true; + + /** + * Creates a type factory. Its compilation unit is not yet set. + * + * @param checker the checker to which this type factory belongs + * @param useFlow whether flow analysis should be performed + */ + protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { + super(checker); + + this.everUseFlow = useFlow; + this.shouldDefaultTypeVarLocals = useFlow; + this.useFlow = useFlow; + + this.flowResult = null; + this.regularExitStores = new IdentityHashMap<>(); + this.exceptionalExitStores = new IdentityHashMap<>(); + this.returnStatementStores = new IdentityHashMap<>(); + + this.initializationStore = null; + this.initializationStaticStore = null; + + this.cfgVisualizer = createCFGVisualizer(); + this.handleCFGViz = checker.hasOption("flowdotdir") || checker.hasOption("cfgviz"); + + if (shouldCache) { + int cacheSize = getCacheSize(); + flowResultAnalysisCaches = CollectionsPlume.createLruCache(cacheSize); + initializerCache = CollectionsPlume.createLruCache(cacheSize); + } else { + flowResultAnalysisCaches = null; + initializerCache = null; + } + + RelevantJavaTypes relevantJavaTypesAnno = + checker.getClass().getAnnotation(RelevantJavaTypes.class); + if (relevantJavaTypesAnno == null) { + this.relevantJavaTypes = null; + this.arraysAreRelevant = true; + } else { + Types types = getChecker().getTypeUtils(); + Elements elements = getElementUtils(); + Class[] classes = relevantJavaTypesAnno.value(); + Set relevantJavaTypesTemp = + new HashSet<>(CollectionsPlume.mapCapacity(classes.length)); + boolean arraysAreRelevantTemp = false; + for (Class clazz : classes) { + if (clazz == Object[].class) { + arraysAreRelevantTemp = true; + } else if (clazz.isArray()) { + throw new TypeSystemError( + "Don't use arrays other than Object[] in @RelevantJavaTypes on " + + this.getClass().getSimpleName()); } else { - flowResultAnalysisCaches = null; - initializerCache = null; - } - - RelevantJavaTypes relevantJavaTypesAnno = - checker.getClass().getAnnotation(RelevantJavaTypes.class); - if (relevantJavaTypesAnno == null) { - this.relevantJavaTypes = null; - this.arraysAreRelevant = true; - } else { - Types types = getChecker().getTypeUtils(); - Elements elements = getElementUtils(); - Class[] classes = relevantJavaTypesAnno.value(); - Set relevantJavaTypesTemp = - new HashSet<>(CollectionsPlume.mapCapacity(classes.length)); - boolean arraysAreRelevantTemp = false; - for (Class clazz : classes) { - if (clazz == Object[].class) { - arraysAreRelevantTemp = true; - } else if (clazz.isArray()) { - throw new TypeSystemError( - "Don't use arrays other than Object[] in @RelevantJavaTypes on " - + this.getClass().getSimpleName()); - } else { - TypeMirror relevantType = TypesUtils.typeFromClass(clazz, types, elements); - TypeMirror erased = types.erasure(relevantType); - relevantJavaTypesTemp.add(erased); - } - } - this.relevantJavaTypes = Collections.unmodifiableSet(relevantJavaTypesTemp); - this.arraysAreRelevant = arraysAreRelevantTemp; - } - - contractsUtils = createContractsFromMethod(); - - hasOrIsSubchecker = - !this.getChecker().getSubcheckers().isEmpty() - || this.getChecker().getParentChecker() != null; - - // Every subclass must call postInit, but it must be called after - // all other initialization is finished. - } - - /** - * Determines whether flow-sensitive type refinement should be used or not. - * - * @return whether flow-sensitive type refinement should be used or not - * @see #useFlow - */ - protected boolean getUseFlow() { - return useFlow; - } - - @Override - protected void postInit( - @UnderInitialization(GenericAnnotatedTypeFactory.class) GenericAnnotatedTypeFactory - this) { - super.postInit(); - - this.dependentTypesHelper = createDependentTypesHelper(); - this.defaults = createAndInitQualifierDefaults(); - this.treeAnnotator = createTreeAnnotator(); - this.typeAnnotator = createTypeAnnotator(); - this.defaultQualifierForUseTypeAnnotator = createDefaultForUseTypeAnnotator(); - this.defaultForTypeAnnotator = createDefaultForTypeAnnotator(); - - this.poly = createQualifierPolymorphism(); - - this.analysis = createFlowAnalysis(); - this.transfer = analysis.getTransferFunction(); - this.emptyStore = analysis.createEmptyStore(transfer.usesSequentialSemantics()); - - this.parseAnnotationFiles(); - - this.inferredTypesApplier = new DefaultInferredTypesApplier(getQualifierHierarchy(), this); - } - - /** - * Performs flow-sensitive type refinement on {@code classTree} if this type factory is - * configured to do so. - * - * @param classTree tree on which to perform flow-sensitive type refinement - */ - @Override - public void preProcessClassTree(ClassTree classTree) { - if (this.everUseFlow) { - checkAndPerformFlowAnalysis(classTree); - } - } - - /** - * Creates a type factory. Its compilation unit is not yet set. This constructor might get - * reflectively called by BaseTypeVisitor.createTypeFactory and uses flowByDefault to determine - * whether flow refinement should be enabled. Subclasses should instead use the two-parameter - * constructor and explicitly set whether to use flow refinement. - * - * @param checker the checker to which this type factory belongs - */ - protected GenericAnnotatedTypeFactory(BaseTypeChecker checker) { - this(checker, flowByDefault); - } - - @Override - public void setRoot(@Nullable CompilationUnitTree root) { - if (this.defaultQualifierForUseTypeAnnotator == null) { - throw new TypeSystemError( - "Does the constructor for %s call postInit()?", - this.getClass().getSimpleName()); - } - - super.setRoot(root); - this.scannedClasses.clear(); - // this.reachableNodes.clear(); - this.flowResult = null; - this.regularExitStores.clear(); - this.exceptionalExitStores.clear(); - this.returnStatementStores.clear(); - this.initializationStore = null; - this.initializationStaticStore = null; - - if (shouldCache) { - this.flowResultAnalysisCaches.clear(); - this.initializerCache.clear(); - this.defaultQualifierForUseTypeAnnotator.clearCache(); - - if (this.checker.getParentChecker() == null) { - // This is an ultimate parent checker, so after it runs the shared CFG it is using - // will no longer be needed, and can be cleared. - this.shouldClearSubcheckerSharedCFGs = true; - if (this.checker.getSubcheckers().isEmpty()) { - // If this checker has no subcheckers, then any maps that are currently - // being maintained should be cleared right away. - clearSharedCFG(this); - } - } else { - GenericAnnotatedTypeFactory ultimateParentATF = - this.checker.getUltimateParentChecker().getTypeFactory(); - clearSharedCFG(ultimateParentATF); - } + TypeMirror relevantType = TypesUtils.typeFromClass(clazz, types, elements); + TypeMirror erased = types.erasure(relevantType); + relevantJavaTypesTemp.add(erased); } + } + this.relevantJavaTypes = Collections.unmodifiableSet(relevantJavaTypesTemp); + this.arraysAreRelevant = arraysAreRelevantTemp; + } + + contractsUtils = createContractsFromMethod(); + + hasOrIsSubchecker = + !this.getChecker().getSubcheckers().isEmpty() + || this.getChecker().getParentChecker() != null; + + // Every subclass must call postInit, but it must be called after + // all other initialization is finished. + } + + /** + * Determines whether flow-sensitive type refinement should be used or not. + * + * @return whether flow-sensitive type refinement should be used or not + * @see #useFlow + */ + protected boolean getUseFlow() { + return useFlow; + } + + @Override + protected void postInit( + @UnderInitialization(GenericAnnotatedTypeFactory.class) GenericAnnotatedTypeFactory this) { + super.postInit(); + + this.dependentTypesHelper = createDependentTypesHelper(); + this.defaults = createAndInitQualifierDefaults(); + this.treeAnnotator = createTreeAnnotator(); + this.typeAnnotator = createTypeAnnotator(); + this.defaultQualifierForUseTypeAnnotator = createDefaultForUseTypeAnnotator(); + this.defaultForTypeAnnotator = createDefaultForTypeAnnotator(); + + this.poly = createQualifierPolymorphism(); + + this.analysis = createFlowAnalysis(); + this.transfer = analysis.getTransferFunction(); + this.emptyStore = analysis.createEmptyStore(transfer.usesSequentialSemantics()); + + this.parseAnnotationFiles(); + + this.inferredTypesApplier = new DefaultInferredTypesApplier(getQualifierHierarchy(), this); + } + + /** + * Performs flow-sensitive type refinement on {@code classTree} if this type factory is configured + * to do so. + * + * @param classTree tree on which to perform flow-sensitive type refinement + */ + @Override + public void preProcessClassTree(ClassTree classTree) { + if (this.everUseFlow) { + checkAndPerformFlowAnalysis(classTree); + } + } + + /** + * Creates a type factory. Its compilation unit is not yet set. This constructor might get + * reflectively called by BaseTypeVisitor.createTypeFactory and uses flowByDefault to determine + * whether flow refinement should be enabled. Subclasses should instead use the two-parameter + * constructor and explicitly set whether to use flow refinement. + * + * @param checker the checker to which this type factory belongs + */ + protected GenericAnnotatedTypeFactory(BaseTypeChecker checker) { + this(checker, flowByDefault); + } + + @Override + public void setRoot(@Nullable CompilationUnitTree root) { + if (this.defaultQualifierForUseTypeAnnotator == null) { + throw new TypeSystemError( + "Does the constructor for %s call postInit()?", this.getClass().getSimpleName()); + } + + super.setRoot(root); + this.scannedClasses.clear(); + // this.reachableNodes.clear(); + this.flowResult = null; + this.regularExitStores.clear(); + this.exceptionalExitStores.clear(); + this.returnStatementStores.clear(); + this.initializationStore = null; + this.initializationStaticStore = null; + + if (shouldCache) { + this.flowResultAnalysisCaches.clear(); + this.initializerCache.clear(); + this.defaultQualifierForUseTypeAnnotator.clearCache(); + + if (this.checker.getParentChecker() == null) { + // This is an ultimate parent checker, so after it runs the shared CFG it is using + // will no longer be needed, and can be cleared. + this.shouldClearSubcheckerSharedCFGs = true; + if (this.checker.getSubcheckers().isEmpty()) { + // If this checker has no subcheckers, then any maps that are currently + // being maintained should be cleared right away. + clearSharedCFG(this); + } + } else { + GenericAnnotatedTypeFactory ultimateParentATF = + this.checker.getUltimateParentChecker().getTypeFactory(); + clearSharedCFG(ultimateParentATF); + } } - - /** - * Clears the caches associated with the shared CFG for the given type factory, if it is safe to - * do so. - * - * @param factory a type factory - */ - private void clearSharedCFG(GenericAnnotatedTypeFactory factory) { - if (factory.shouldClearSubcheckerSharedCFGs) { - // This is the first subchecker running in a group that share CFGs, so it must clear its - // ultimate parent's shared CFG before adding a new shared CFG. - factory.shouldClearSubcheckerSharedCFGs = false; - if (factory.subcheckerSharedCFG != null) { - factory.subcheckerSharedCFG.clear(); - } - } + } + + /** + * Clears the caches associated with the shared CFG for the given type factory, if it is safe to + * do so. + * + * @param factory a type factory + */ + private void clearSharedCFG(GenericAnnotatedTypeFactory factory) { + if (factory.shouldClearSubcheckerSharedCFGs) { + // This is the first subchecker running in a group that share CFGs, so it must clear its + // ultimate parent's shared CFG before adding a new shared CFG. + factory.shouldClearSubcheckerSharedCFGs = false; + if (factory.subcheckerSharedCFG != null) { + factory.subcheckerSharedCFG.clear(); + } } - - // ********************************************************************** - // Factory Methods for the appropriate annotator classes - // ********************************************************************** - - /** - * Returns an immutable set of the monotonic type qualifiers supported by this checker. - * - * @return the monotonic type qualifiers supported this processor, or an empty set if none - * @see MonotonicQualifier - */ - public final Set> getSupportedMonotonicTypeQualifiers() { - if (supportedMonotonicQuals == null) { - supportedMonotonicQuals = new HashSet<>(); - for (Class anno : getSupportedTypeQualifiers()) { - MonotonicQualifier mono = anno.getAnnotation(MonotonicQualifier.class); - if (mono != null) { - supportedMonotonicQuals.add(anno); - } - } - } - return supportedMonotonicQuals; - } - - /** - * Returns a {@link TreeAnnotator} that adds annotations to a type based on the contents of a - * tree. - * - *

The default tree annotator is a {@link ListTreeAnnotator} of the following: - * - *

    - *
  1. {@link PropagationTreeAnnotator}: Propagates annotations from subtrees - *
  2. {@link LiteralTreeAnnotator}: Adds annotations based on {@link QualifierForLiterals} - * meta-annotations - *
  3. {@link DependentTypesTreeAnnotator}: Adapts dependent annotations based on context - *
- * - *

Subclasses may override this method to specify additional tree annotators, for example: - * - *

-     * new ListTreeAnnotator(super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this));
-     * 
- * - * @return a tree annotator - */ - protected TreeAnnotator createTreeAnnotator() { - List treeAnnotators = new ArrayList<>(2); - treeAnnotators.add(new PropagationTreeAnnotator(this)); - treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); - if (dependentTypesHelper.hasDependentAnnotations()) { - treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); - } - return new ListTreeAnnotator(treeAnnotators); - } - - /** - * Returns a {@link DefaultForTypeAnnotator} that adds annotations to a type based on the - * content of the type itself. - * - *

Subclass may override this method. The default type annotator is a {@link - * ListTypeAnnotator} of the following: - * - *

    - *
  1. {@link IrrelevantTypeAnnotator}: Adds top to types not listed in the {@code @}{@link - * RelevantJavaTypes} annotation on the checker. - *
  2. {@link PropagationTypeAnnotator}: Propagates annotation onto wildcards. - *
- * - * @return a type annotator - */ - protected TypeAnnotator createTypeAnnotator() { - List typeAnnotators = new ArrayList<>(1); - if (relevantJavaTypes != null) { - typeAnnotators.add(new IrrelevantTypeAnnotator(this)); - } - typeAnnotators.add(new PropagationTypeAnnotator(this)); - return new ListTypeAnnotator(typeAnnotators); - } - - /** - * Returns the annotations that should appear on the given irrelevant Java type. If the type is - * relevant, this method's behavior is undefined. - * - * @param tm an irrelevant Java type - * @return the annotations that should appear on the given irrelevant Java type - */ - public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { - return getQualifierHierarchy().getTopAnnotations(); - } - - /** - * Creates an {@link DefaultQualifierForUseTypeAnnotator}. - * - * @return a new {@link DefaultQualifierForUseTypeAnnotator} - */ - protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { - return new DefaultQualifierForUseTypeAnnotator(this); - } - - /** - * Creates an {@link DefaultForTypeAnnotator}. - * - * @return a new {@link DefaultForTypeAnnotator} - */ - protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { - return new DefaultForTypeAnnotator(this); - } - - /** - * Returns the {@link DefaultForTypeAnnotator}. - * - * @return the {@link DefaultForTypeAnnotator} - */ - public DefaultForTypeAnnotator getDefaultForTypeAnnotator() { - return defaultForTypeAnnotator; - } - - /** - * Returns the appropriate flow analysis class that is used for the - * org.checkerframework.dataflow analysis. - * - *

This implementation uses the checker naming convention to create the appropriate analysis. - * If no transfer function is found, it returns an instance of {@link CFAnalysis}. - * - *

Subclasses have to override this method to create the appropriate analysis if they do not - * follow the checker naming convention. - * - * @return the appropriate flow analysis class that is used for the - * org.checkerframework.dataflow analysis - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - protected FlowAnalysis createFlowAnalysis() { - // Try to reflectively load the visitor. - Class checkerClass = checker.getClass(); - - while (checkerClass != BaseTypeChecker.class) { - FlowAnalysis result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Analysis"), - new Class[] {BaseTypeChecker.class, this.getClass()}, - new Object[] {checker, this}); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If an analysis couldn't be loaded reflectively, return the default. - return (FlowAnalysis) new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this); - } - - /** - * Returns the appropriate transfer function that is used for the given - * org.checkerframework.dataflow analysis. - * - *

This implementation uses the checker naming convention to create the appropriate transfer - * function. If no transfer function is found, it returns an instance of {@link CFTransfer}. - * - *

Subclasses have to override this method to create the appropriate transfer function if - * they do not follow the checker naming convention. - * - * @param analysis a dataflow analysis - * @return a new transfer function - */ - // A more precise type for the parameter would be FlowAnalysis, which - // is the type parameter bounded by the current parameter type CFAbstractAnalysis. - // However, we ran into issues in callers of the method if we used that type. - public TransferFunction createFlowTransferFunction( - CFAbstractAnalysis analysis) { - // Try to reflectively load the visitor. - Class checkerClass = checker.getClass(); - - while (checkerClass != BaseTypeChecker.class) { - TransferFunction result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Transfer"), - new Class[] {analysis.getClass()}, - new Object[] {analysis}); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If a transfer function couldn't be loaded reflectively, return the default. - @SuppressWarnings("unchecked") - TransferFunction ret = - (TransferFunction) - new CFTransfer((CFAbstractAnalysis) analysis); - return ret; - } - - /** - * Creates a {@link DependentTypesHelper} and returns it. Use {@link #getDependentTypesHelper} - * to access the value. - * - * @return a new {@link DependentTypesHelper} - */ - protected DependentTypesHelper createDependentTypesHelper() { - return new DependentTypesHelper(this); - } - - /** - * Returns the DependentTypesHelper. - * - * @return the DependentTypesHelper - */ - public DependentTypesHelper getDependentTypesHelper() { - return dependentTypesHelper; - } - - /** - * Creates an {@link DefaultContractsFromMethod} and returns it. If contract annotations are not - * used for a type system, override this method and return a {@link NoContractsFromMethod}. - * - * @return a new {@link ContractsFromMethod} - */ - protected ContractsFromMethod createContractsFromMethod() { - return new DefaultContractsFromMethod(this); - } - - /** - * Returns the helper for method pre- and postconditions. - * - * @return the helper for method pre- and postconditions - */ - public ContractsFromMethod getContractsFromMethod() { - return contractsUtils; - } - - @Override - protected List getExplicitNewClassClassTypeArgs( - NewClassTree newClassTree) { - List superResult = - super.getExplicitNewClassClassTypeArgs(newClassTree); - for (AnnotatedTypeMirror superR : superResult) { - dependentTypesHelper.atExpression(superR, newClassTree); - } - return superResult; - } - - @Override - public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { - AnnotationMirrorSet superResult = super.getExplicitNewClassAnnos(newClassTree); - AnnotatedTypeMirror dummy = getAnnotatedNullType(superResult); - dependentTypesHelper.atExpression(dummy, newClassTree); - return dummy.getAnnotations(); - } - - /** - * Create {@link QualifierDefaults} which handles checker specified defaults, and initialize the - * created {@link QualifierDefaults}. Subclasses should override {@link - * GenericAnnotatedTypeFactory#addCheckedCodeDefaults(QualifierDefaults defs)} to add more - * defaults or use different defaults. - * - * @return the QualifierDefaults object - */ - // TODO: When changing this method, also look into - // {@link - // org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesHelper#shouldIgnore}. - // Both methods should have some functionality merged into a single location. - // See Issue 683 - // https://github.com/typetools/checker-framework/issues/683 - protected final QualifierDefaults createAndInitQualifierDefaults() { - QualifierDefaults defs = createQualifierDefaults(); - addCheckedCodeDefaults(defs); - addCheckedStandardDefaults(defs); - addUncheckedStandardDefaults(defs); - checkForDefaultQualifierInHierarchy(defs); - - return defs; - } - - /** - * Create {@link QualifierDefaults} which handles checker specified defaults. Sub-classes - * override this method to provide a different {@code QualifierDefault} implementation. - */ - protected QualifierDefaults createQualifierDefaults() { - return new QualifierDefaults(elements, this); - } - - /** - * Creates and returns a string containing the number of qualifiers and the canonical class - * names of each qualifier that has been added to this checker's supported qualifier set. The - * names are alphabetically sorted. - * - * @return a string containing the number of qualifiers and canonical names of each qualifier - */ - protected final String getSortedQualifierNames() { - Set> stq = getSupportedTypeQualifiers(); - if (stq.isEmpty()) { - return "No qualifiers examined"; - } - if (stq.size() == 1) { - return "1 qualifier examined: " + stq.iterator().next().getCanonicalName(); - } - - // Create a list of the supported qualifiers and sort the list alphabetically - List> sortedSupportedQuals = new ArrayList<>(stq); - sortedSupportedQuals.sort(Comparator.comparing(Class::getCanonicalName)); - - // display the number of qualifiers as well as the names of each qualifier. - StringJoiner sj = - new StringJoiner(", ", sortedSupportedQuals.size() + " qualifiers examined: ", ""); - for (Class qual : sortedSupportedQuals) { - sj.add(qual.getCanonicalName()); - } - return sj.toString(); - } - - /** - * Adds default qualifiers for type-checked code by reading {@link DefaultFor} and {@link - * DefaultQualifierInHierarchy} meta-annotations. Subclasses may override this method to add - * defaults that cannot be specified with a {@link DefaultFor} or {@link - * DefaultQualifierInHierarchy} meta-annotations. - * - * @param defs the QualifierDefault object to which defaults are added - */ - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy - for (Class qual : getSupportedTypeQualifiers()) { - DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); - if (defaultFor != null) { - TypeUseLocation[] locations = defaultFor.value(); - defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); - } - - if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { - defs.addCheckedCodeDefault( - AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); - } + } + + // ********************************************************************** + // Factory Methods for the appropriate annotator classes + // ********************************************************************** + + /** + * Returns an immutable set of the monotonic type qualifiers supported by this checker. + * + * @return the monotonic type qualifiers supported this processor, or an empty set if none + * @see MonotonicQualifier + */ + public final Set> getSupportedMonotonicTypeQualifiers() { + if (supportedMonotonicQuals == null) { + supportedMonotonicQuals = new HashSet<>(); + for (Class anno : getSupportedTypeQualifiers()) { + MonotonicQualifier mono = anno.getAnnotation(MonotonicQualifier.class); + if (mono != null) { + supportedMonotonicQuals.add(anno); } + } } + return supportedMonotonicQuals; + } + + /** + * Returns a {@link TreeAnnotator} that adds annotations to a type based on the contents of a + * tree. + * + *

The default tree annotator is a {@link ListTreeAnnotator} of the following: + * + *

    + *
  1. {@link PropagationTreeAnnotator}: Propagates annotations from subtrees + *
  2. {@link LiteralTreeAnnotator}: Adds annotations based on {@link QualifierForLiterals} + * meta-annotations + *
  3. {@link DependentTypesTreeAnnotator}: Adapts dependent annotations based on context + *
+ * + *

Subclasses may override this method to specify additional tree annotators, for example: + * + *

+   * new ListTreeAnnotator(super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this));
+   * 
+ * + * @return a tree annotator + */ + protected TreeAnnotator createTreeAnnotator() { + List treeAnnotators = new ArrayList<>(2); + treeAnnotators.add(new PropagationTreeAnnotator(this)); + treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); + if (dependentTypesHelper.hasDependentAnnotations()) { + treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); + } + return new ListTreeAnnotator(treeAnnotators); + } + + /** + * Returns a {@link DefaultForTypeAnnotator} that adds annotations to a type based on the content + * of the type itself. + * + *

Subclass may override this method. The default type annotator is a {@link ListTypeAnnotator} + * of the following: + * + *

    + *
  1. {@link IrrelevantTypeAnnotator}: Adds top to types not listed in the {@code @}{@link + * RelevantJavaTypes} annotation on the checker. + *
  2. {@link PropagationTypeAnnotator}: Propagates annotation onto wildcards. + *
+ * + * @return a type annotator + */ + protected TypeAnnotator createTypeAnnotator() { + List typeAnnotators = new ArrayList<>(1); + if (relevantJavaTypes != null) { + typeAnnotators.add(new IrrelevantTypeAnnotator(this)); + } + typeAnnotators.add(new PropagationTypeAnnotator(this)); + return new ListTypeAnnotator(typeAnnotators); + } + + /** + * Returns the annotations that should appear on the given irrelevant Java type. If the type is + * relevant, this method's behavior is undefined. + * + * @param tm an irrelevant Java type + * @return the annotations that should appear on the given irrelevant Java type + */ + public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { + return getQualifierHierarchy().getTopAnnotations(); + } + + /** + * Creates an {@link DefaultQualifierForUseTypeAnnotator}. + * + * @return a new {@link DefaultQualifierForUseTypeAnnotator} + */ + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new DefaultQualifierForUseTypeAnnotator(this); + } + + /** + * Creates an {@link DefaultForTypeAnnotator}. + * + * @return a new {@link DefaultForTypeAnnotator} + */ + protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { + return new DefaultForTypeAnnotator(this); + } + + /** + * Returns the {@link DefaultForTypeAnnotator}. + * + * @return the {@link DefaultForTypeAnnotator} + */ + public DefaultForTypeAnnotator getDefaultForTypeAnnotator() { + return defaultForTypeAnnotator; + } + + /** + * Returns the appropriate flow analysis class that is used for the org.checkerframework.dataflow + * analysis. + * + *

This implementation uses the checker naming convention to create the appropriate analysis. + * If no transfer function is found, it returns an instance of {@link CFAnalysis}. + * + *

Subclasses have to override this method to create the appropriate analysis if they do not + * follow the checker naming convention. + * + * @return the appropriate flow analysis class that is used for the org.checkerframework.dataflow + * analysis + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + protected FlowAnalysis createFlowAnalysis() { + // Try to reflectively load the visitor. + Class checkerClass = checker.getClass(); + + while (checkerClass != BaseTypeChecker.class) { + FlowAnalysis result = + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, "Analysis"), + new Class[] {BaseTypeChecker.class, this.getClass()}, + new Object[] {checker, this}); + if (result != null) { + return result; + } + checkerClass = checkerClass.getSuperclass(); + } + + // If an analysis couldn't be loaded reflectively, return the default. + return (FlowAnalysis) new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this); + } + + /** + * Returns the appropriate transfer function that is used for the given + * org.checkerframework.dataflow analysis. + * + *

This implementation uses the checker naming convention to create the appropriate transfer + * function. If no transfer function is found, it returns an instance of {@link CFTransfer}. + * + *

Subclasses have to override this method to create the appropriate transfer function if they + * do not follow the checker naming convention. + * + * @param analysis a dataflow analysis + * @return a new transfer function + */ + // A more precise type for the parameter would be FlowAnalysis, which + // is the type parameter bounded by the current parameter type CFAbstractAnalysis. + // However, we ran into issues in callers of the method if we used that type. + public TransferFunction createFlowTransferFunction( + CFAbstractAnalysis analysis) { + // Try to reflectively load the visitor. + Class checkerClass = checker.getClass(); + + while (checkerClass != BaseTypeChecker.class) { + TransferFunction result = + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, "Transfer"), + new Class[] {analysis.getClass()}, + new Object[] {analysis}); + if (result != null) { + return result; + } + checkerClass = checkerClass.getSuperclass(); + } + + // If a transfer function couldn't be loaded reflectively, return the default. + @SuppressWarnings("unchecked") + TransferFunction ret = + (TransferFunction) + new CFTransfer((CFAbstractAnalysis) analysis); + return ret; + } + + /** + * Creates a {@link DependentTypesHelper} and returns it. Use {@link #getDependentTypesHelper} to + * access the value. + * + * @return a new {@link DependentTypesHelper} + */ + protected DependentTypesHelper createDependentTypesHelper() { + return new DependentTypesHelper(this); + } + + /** + * Returns the DependentTypesHelper. + * + * @return the DependentTypesHelper + */ + public DependentTypesHelper getDependentTypesHelper() { + return dependentTypesHelper; + } + + /** + * Creates an {@link DefaultContractsFromMethod} and returns it. If contract annotations are not + * used for a type system, override this method and return a {@link NoContractsFromMethod}. + * + * @return a new {@link ContractsFromMethod} + */ + protected ContractsFromMethod createContractsFromMethod() { + return new DefaultContractsFromMethod(this); + } + + /** + * Returns the helper for method pre- and postconditions. + * + * @return the helper for method pre- and postconditions + */ + public ContractsFromMethod getContractsFromMethod() { + return contractsUtils; + } + + @Override + protected List getExplicitNewClassClassTypeArgs(NewClassTree newClassTree) { + List superResult = super.getExplicitNewClassClassTypeArgs(newClassTree); + for (AnnotatedTypeMirror superR : superResult) { + dependentTypesHelper.atExpression(superR, newClassTree); + } + return superResult; + } + + @Override + public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { + AnnotationMirrorSet superResult = super.getExplicitNewClassAnnos(newClassTree); + AnnotatedTypeMirror dummy = getAnnotatedNullType(superResult); + dependentTypesHelper.atExpression(dummy, newClassTree); + return dummy.getAnnotations(); + } + + /** + * Create {@link QualifierDefaults} which handles checker specified defaults, and initialize the + * created {@link QualifierDefaults}. Subclasses should override {@link + * GenericAnnotatedTypeFactory#addCheckedCodeDefaults(QualifierDefaults defs)} to add more + * defaults or use different defaults. + * + * @return the QualifierDefaults object + */ + // TODO: When changing this method, also look into + // {@link + // org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesHelper#shouldIgnore}. + // Both methods should have some functionality merged into a single location. + // See Issue 683 + // https://github.com/typetools/checker-framework/issues/683 + protected final QualifierDefaults createAndInitQualifierDefaults() { + QualifierDefaults defs = createQualifierDefaults(); + addCheckedCodeDefaults(defs); + addCheckedStandardDefaults(defs); + addUncheckedStandardDefaults(defs); + checkForDefaultQualifierInHierarchy(defs); + + return defs; + } + + /** + * Create {@link QualifierDefaults} which handles checker specified defaults. Sub-classes override + * this method to provide a different {@code QualifierDefault} implementation. + */ + protected QualifierDefaults createQualifierDefaults() { + return new QualifierDefaults(elements, this); + } + + /** + * Creates and returns a string containing the number of qualifiers and the canonical class names + * of each qualifier that has been added to this checker's supported qualifier set. The names are + * alphabetically sorted. + * + * @return a string containing the number of qualifiers and canonical names of each qualifier + */ + protected final String getSortedQualifierNames() { + Set> stq = getSupportedTypeQualifiers(); + if (stq.isEmpty()) { + return "No qualifiers examined"; + } + if (stq.size() == 1) { + return "1 qualifier examined: " + stq.iterator().next().getCanonicalName(); + } + + // Create a list of the supported qualifiers and sort the list alphabetically + List> sortedSupportedQuals = new ArrayList<>(stq); + sortedSupportedQuals.sort(Comparator.comparing(Class::getCanonicalName)); + + // display the number of qualifiers as well as the names of each qualifier. + StringJoiner sj = + new StringJoiner(", ", sortedSupportedQuals.size() + " qualifiers examined: ", ""); + for (Class qual : sortedSupportedQuals) { + sj.add(qual.getCanonicalName()); + } + return sj.toString(); + } + + /** + * Adds default qualifiers for type-checked code by reading {@link DefaultFor} and {@link + * DefaultQualifierInHierarchy} meta-annotations. Subclasses may override this method to add + * defaults that cannot be specified with a {@link DefaultFor} or {@link + * DefaultQualifierInHierarchy} meta-annotations. + * + * @param defs the QualifierDefault object to which defaults are added + */ + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy + for (Class qual : getSupportedTypeQualifiers()) { + DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); + if (defaultFor != null) { + TypeUseLocation[] locations = defaultFor.value(); + defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); + } - /** - * Adds the standard CLIMB defaults that do not conflict with previously added defaults. - * - * @param defs {@link QualifierDefaults} object to which defaults are added - */ - protected void addCheckedStandardDefaults(QualifierDefaults defs) { - if (this.everUseFlow) { - defs.addClimbStandardDefaults(); - } + if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { + defs.addCheckedCodeDefault( + AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); + } } - - /** - * Adds standard unchecked defaults that do not conflict with previously added defaults. - * - * @param defs {@link QualifierDefaults} object to which defaults are added - */ - protected void addUncheckedStandardDefaults(QualifierDefaults defs) { - defs.addUncheckedStandardDefaults(); - } - - /** - * Check that a default qualifier (in at least one hierarchy) has been set and issue an error if - * not. - * - * @param defs {@link QualifierDefaults} object to which defaults are added - */ - protected void checkForDefaultQualifierInHierarchy(QualifierDefaults defs) { - if (!defs.hasDefaultsForCheckedCode()) { - throw new BugInCF( - "GenericAnnotatedTypeFactory.createQualifierDefaults:" - + " @DefaultQualifierInHierarchy or @DefaultFor(TypeUseLocation.OTHERWISE)" - + " not found. Every checker must specify a default qualifier. " - + getSortedQualifierNames()); - } - - // If a default unchecked code qualifier isn't specified, the defaults - // for checked code will be used. - } - - /** - * Creates the {@link QualifierPolymorphism} instance which supports the QualifierPolymorphism - * mechanism. - * - * @return the QualifierPolymorphism instance to use - */ - protected QualifierPolymorphism createQualifierPolymorphism() { - return new DefaultQualifierPolymorphism(processingEnv, this); - } - - /** - * Gives the current {@link QualifierPolymorphism} instance which supports the - * QualifierPolymorphism mechanism. - * - * @return the QualifierPolymorphism instance to use - */ - public QualifierPolymorphism getQualifierPolymorphism() { - return this.poly; - } - - // ********************************************************************** - // Factory Methods for the appropriate annotator classes - // ********************************************************************** - - @Override - protected void postDirectSuperTypes( - AnnotatedTypeMirror type, List supertypes) { - super.postDirectSuperTypes(type, supertypes); - if (type.getKind() == TypeKind.DECLARED) { - for (AnnotatedTypeMirror supertype : supertypes) { - Element elt = ((DeclaredType) supertype.getUnderlyingType()).asElement(); - addComputedTypeAnnotations(elt, supertype); - } - } + } + + /** + * Adds the standard CLIMB defaults that do not conflict with previously added defaults. + * + * @param defs {@link QualifierDefaults} object to which defaults are added + */ + protected void addCheckedStandardDefaults(QualifierDefaults defs) { + if (this.everUseFlow) { + defs.addClimbStandardDefaults(); + } + } + + /** + * Adds standard unchecked defaults that do not conflict with previously added defaults. + * + * @param defs {@link QualifierDefaults} object to which defaults are added + */ + protected void addUncheckedStandardDefaults(QualifierDefaults defs) { + defs.addUncheckedStandardDefaults(); + } + + /** + * Check that a default qualifier (in at least one hierarchy) has been set and issue an error if + * not. + * + * @param defs {@link QualifierDefaults} object to which defaults are added + */ + protected void checkForDefaultQualifierInHierarchy(QualifierDefaults defs) { + if (!defs.hasDefaultsForCheckedCode()) { + throw new BugInCF( + "GenericAnnotatedTypeFactory.createQualifierDefaults:" + + " @DefaultQualifierInHierarchy or @DefaultFor(TypeUseLocation.OTHERWISE)" + + " not found. Every checker must specify a default qualifier. " + + getSortedQualifierNames()); + } + + // If a default unchecked code qualifier isn't specified, the defaults + // for checked code will be used. + } + + /** + * Creates the {@link QualifierPolymorphism} instance which supports the QualifierPolymorphism + * mechanism. + * + * @return the QualifierPolymorphism instance to use + */ + protected QualifierPolymorphism createQualifierPolymorphism() { + return new DefaultQualifierPolymorphism(processingEnv, this); + } + + /** + * Gives the current {@link QualifierPolymorphism} instance which supports the + * QualifierPolymorphism mechanism. + * + * @return the QualifierPolymorphism instance to use + */ + public QualifierPolymorphism getQualifierPolymorphism() { + return this.poly; + } + + // ********************************************************************** + // Factory Methods for the appropriate annotator classes + // ********************************************************************** + + @Override + protected void postDirectSuperTypes( + AnnotatedTypeMirror type, List supertypes) { + super.postDirectSuperTypes(type, supertypes); + if (type.getKind() == TypeKind.DECLARED) { + for (AnnotatedTypeMirror supertype : supertypes) { + Element elt = ((DeclaredType) supertype.getUnderlyingType()).asElement(); + addComputedTypeAnnotations(elt, supertype); + } } - - /** - * Gets the type of the resulting constructor call of a MemberReferenceTree. - * - * @param memberReferenceTree MemberReferenceTree where the member is a constructor - * @param constructorType AnnotatedExecutableType of the declaration of the constructor - * @return AnnotatedTypeMirror of the resulting type of the constructor - */ - public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference( - MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) { - assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW; - - // The return type for constructors should only have explicit annotations from the - // constructor. Recreate some of the logic from TypeFromTree.visitNewClass here. - - // The return type of the constructor will be the type of the expression of the member - // reference tree. - AnnotatedDeclaredType constructorReturnType = - (AnnotatedDeclaredType) fromTypeTree(memberReferenceTree.getQualifierExpression()); - - // Keep only explicit annotations and those from @Poly - AnnotatedTypes.copyOnlyExplicitConstructorAnnotations( - this, constructorReturnType, constructorType); - - // Now add back defaulting. - addComputedTypeAnnotations( - memberReferenceTree.getQualifierExpression(), constructorReturnType); - return constructorReturnType; - } - - /** - * Returns the primary annotation on expression if it were evaluated at path. - * - * @param expression a Java expression - * @param tree current tree - * @param path location at which expression is evaluated - * @param clazz class of the annotation - * @return the annotation on expression or null if one does not exist - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - */ - public @Nullable AnnotationMirror getAnnotationFromJavaExpressionString( - String expression, Tree tree, TreePath path, Class clazz) - throws JavaExpressionParseException { - JavaExpression expressionObj = parseJavaExpressionString(expression, path); - return getAnnotationFromJavaExpression(expressionObj, tree, clazz); - } - - /** - * Returns the primary annotation on an expression, at a particular location. - * - * @param expr the expression for which the annotation is returned - * @param tree current tree - * @param clazz the Class of the annotation - * @return the annotation on expression or null if one does not exist - */ - public @Nullable AnnotationMirror getAnnotationFromJavaExpression( - JavaExpression expr, Tree tree, Class clazz) { - return getAnnotationByClass(getAnnotationsFromJavaExpression(expr, tree), clazz); - } - - /** - * Returns the primary annotations on an expression, at a particular location. - * - * @param expr the expression for which the annotation is returned - * @param tree current tree - * @return the annotation on expression or null if one does not exist - */ - public @Nullable AnnotationMirrorSet getAnnotationsFromJavaExpression( - JavaExpression expr, Tree tree) { - // Look in the store - if (CFAbstractStore.canInsertJavaExpression(expr)) { - Store store = getStoreBefore(tree); - // `store` can be null if the tree is in a field initializer. - if (store != null) { - Value value = store.getValue(expr); - if (value != null) { - // Is it possible that this lacks some annotations that appear in the type - // factory? - return value.getAnnotations(); - } - } - } - - // Look in the type factory, if not found in the store. - if (expr instanceof LocalVariable) { - Element ele = ((LocalVariable) expr).getElement(); - // Because of - // https://github.com/eisop/checker-framework/issues/14 - // and the workaround in - // org.checkerframework.framework.type.ElementAnnotationApplier.applyInternal - // The annotationMirror may not contain all explicitly written annotations. - return getAnnotatedType(ele).getAnnotations(); - } else if (expr instanceof FieldAccess) { - Element ele = ((FieldAccess) expr).getField(); - return getAnnotatedType(ele).getAnnotations(); - } else { - return AnnotationMirrorSet.emptySet(); + } + + /** + * Gets the type of the resulting constructor call of a MemberReferenceTree. + * + * @param memberReferenceTree MemberReferenceTree where the member is a constructor + * @param constructorType AnnotatedExecutableType of the declaration of the constructor + * @return AnnotatedTypeMirror of the resulting type of the constructor + */ + public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference( + MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) { + assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW; + + // The return type for constructors should only have explicit annotations from the + // constructor. Recreate some of the logic from TypeFromTree.visitNewClass here. + + // The return type of the constructor will be the type of the expression of the member + // reference tree. + AnnotatedDeclaredType constructorReturnType = + (AnnotatedDeclaredType) fromTypeTree(memberReferenceTree.getQualifierExpression()); + + // Keep only explicit annotations and those from @Poly + AnnotatedTypes.copyOnlyExplicitConstructorAnnotations( + this, constructorReturnType, constructorType); + + // Now add back defaulting. + addComputedTypeAnnotations(memberReferenceTree.getQualifierExpression(), constructorReturnType); + return constructorReturnType; + } + + /** + * Returns the primary annotation on expression if it were evaluated at path. + * + * @param expression a Java expression + * @param tree current tree + * @param path location at which expression is evaluated + * @param clazz class of the annotation + * @return the annotation on expression or null if one does not exist + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + */ + public @Nullable AnnotationMirror getAnnotationFromJavaExpressionString( + String expression, Tree tree, TreePath path, Class clazz) + throws JavaExpressionParseException { + JavaExpression expressionObj = parseJavaExpressionString(expression, path); + return getAnnotationFromJavaExpression(expressionObj, tree, clazz); + } + + /** + * Returns the primary annotation on an expression, at a particular location. + * + * @param expr the expression for which the annotation is returned + * @param tree current tree + * @param clazz the Class of the annotation + * @return the annotation on expression or null if one does not exist + */ + public @Nullable AnnotationMirror getAnnotationFromJavaExpression( + JavaExpression expr, Tree tree, Class clazz) { + return getAnnotationByClass(getAnnotationsFromJavaExpression(expr, tree), clazz); + } + + /** + * Returns the primary annotations on an expression, at a particular location. + * + * @param expr the expression for which the annotation is returned + * @param tree current tree + * @return the annotation on expression or null if one does not exist + */ + public @Nullable AnnotationMirrorSet getAnnotationsFromJavaExpression( + JavaExpression expr, Tree tree) { + // Look in the store + if (CFAbstractStore.canInsertJavaExpression(expr)) { + Store store = getStoreBefore(tree); + // `store` can be null if the tree is in a field initializer. + if (store != null) { + Value value = store.getValue(expr); + if (value != null) { + // Is it possible that this lacks some annotations that appear in the type + // factory? + return value.getAnnotations(); } + } } - /** - * Produces the JavaExpression as if {@code expression} were written at {@code currentPath}. - * - * @param expression a Java expression - * @param currentPath the current path - * @return the JavaExpression associated with expression on currentPath - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - */ - public JavaExpression parseJavaExpressionString(String expression, TreePath currentPath) - throws JavaExpressionParseException { - return StringToJavaExpression.atPath(expression, currentPath, checker); - } - - /** - * Produces the JavaExpression and offset associated with an expression. For instance, "n+1" has - * no associated JavaExpression, but this method produces a pair of a JavaExpression (for "n") - * and an offset ("1"). - * - * @param expression a Java expression, possibly with a constant offset - * @param currentPath location at which expression is evaluated - * @return the JavaExpression and offset for the given expression - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - */ - public IPair getExpressionAndOffsetFromJavaExpressionString( - String expression, TreePath currentPath) throws JavaExpressionParseException { - IPair p = getExpressionAndOffset(expression); - JavaExpression r = parseJavaExpressionString(p.first, currentPath); - return IPair.of(r, p.second); - } - - /** - * Returns the annotation mirror from dataflow for {@code expression}. - * - *

This will output a different annotation than {@link - * #getAnnotationFromJavaExpressionString(String, Tree, TreePath, Class)}, because if the - * specified annotation isn't found in the store, the type from the factory is used. - * - * @param expression a Java expression - * @param tree the tree at the location to parse the expression - * @param currentPath location at which expression is evaluated - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - * @return an AnnotationMirror representing the type in the store at the given location from - * this type factory's type system, or null if one is not available - */ - public @Nullable AnnotationMirror getAnnotationMirrorFromJavaExpressionString( - String expression, Tree tree, TreePath currentPath) - throws JavaExpressionParseException { - JavaExpression je = parseJavaExpressionString(expression, currentPath); - if (je == null || !CFAbstractStore.canInsertJavaExpression(je)) { - return null; + // Look in the type factory, if not found in the store. + if (expr instanceof LocalVariable) { + Element ele = ((LocalVariable) expr).getElement(); + // Because of + // https://github.com/eisop/checker-framework/issues/14 + // and the workaround in + // org.checkerframework.framework.type.ElementAnnotationApplier.applyInternal + // The annotationMirror may not contain all explicitly written annotations. + return getAnnotatedType(ele).getAnnotations(); + } else if (expr instanceof FieldAccess) { + Element ele = ((FieldAccess) expr).getField(); + return getAnnotatedType(ele).getAnnotations(); + } else { + return AnnotationMirrorSet.emptySet(); + } + } + + /** + * Produces the JavaExpression as if {@code expression} were written at {@code currentPath}. + * + * @param expression a Java expression + * @param currentPath the current path + * @return the JavaExpression associated with expression on currentPath + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + */ + public JavaExpression parseJavaExpressionString(String expression, TreePath currentPath) + throws JavaExpressionParseException { + return StringToJavaExpression.atPath(expression, currentPath, checker); + } + + /** + * Produces the JavaExpression and offset associated with an expression. For instance, "n+1" has + * no associated JavaExpression, but this method produces a pair of a JavaExpression (for "n") and + * an offset ("1"). + * + * @param expression a Java expression, possibly with a constant offset + * @param currentPath location at which expression is evaluated + * @return the JavaExpression and offset for the given expression + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + */ + public IPair getExpressionAndOffsetFromJavaExpressionString( + String expression, TreePath currentPath) throws JavaExpressionParseException { + IPair p = getExpressionAndOffset(expression); + JavaExpression r = parseJavaExpressionString(p.first, currentPath); + return IPair.of(r, p.second); + } + + /** + * Returns the annotation mirror from dataflow for {@code expression}. + * + *

This will output a different annotation than {@link + * #getAnnotationFromJavaExpressionString(String, Tree, TreePath, Class)}, because if the + * specified annotation isn't found in the store, the type from the factory is used. + * + * @param expression a Java expression + * @param tree the tree at the location to parse the expression + * @param currentPath location at which expression is evaluated + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + * @return an AnnotationMirror representing the type in the store at the given location from this + * type factory's type system, or null if one is not available + */ + public @Nullable AnnotationMirror getAnnotationMirrorFromJavaExpressionString( + String expression, Tree tree, TreePath currentPath) throws JavaExpressionParseException { + JavaExpression je = parseJavaExpressionString(expression, currentPath); + if (je == null || !CFAbstractStore.canInsertJavaExpression(je)) { + return null; + } + Store store = getStoreBefore(tree); + Value value = store.getValue(je); + return value != null ? value.getAnnotations().iterator().next() : null; + } + + /* + * Returns true if the {@code exprTree} is unreachable. This is a conservative estimate and may + * return {@code false} even though the {@code exprTree} is unreachable. + * + * @param exprTree an expression tree + * @return true if the {@code exprTree} is unreachable + * + public boolean isUnreachable(ExpressionTree exprTree) { + if (!everUseFlow) { + return false; + } + Set nodes = getNodesForTree(exprTree); + if (nodes == null) { + // Dataflow has no any information about the tree, so conservatively consider the tree + // reachable. + return false; + } + for (Node n : nodes) { + if (n.getTree() != null && reachableNodes.contains(n.getTree())) { + return false; + } + } + // None of the corresponding nodes is reachable, so this tree is dead. + return true; + } + */ + + /** + * Track the state of org.checkerframework.dataflow analysis scanning for each class tree in the + * compilation unit. + */ + protected enum ScanState { + /** Dataflow analysis in progress. */ + IN_PROGRESS, + /** Dataflow analysis finished. */ + FINISHED + } + + /** Map from ClassTree to their dataflow analysis state. */ + protected final Map scannedClasses = new HashMap<>(); + + /* + * A set of trees whose corresponding nodes are reachable. This is not an exhaustive set of + * reachable trees. Use {@link #isUnreachable(ExpressionTree)} instead of this set directly. + * + *

This cannot be a set of Nodes, because two LocalVariableNodes are equal if they have the + * same name but represent different uses of the variable. So instead of storing Nodes, it + * stores the result of {@code Node#getTree}. + */ + // private final Set reachableNodes = new HashSet<>(); + + /** + * The result of the flow analysis. Invariant: + * + *

+   *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
+   * 
+ * + * Note that flowResult contains analysis results for Trees from multiple classes which are + * produced by multiple calls to performFlowAnalysis. + */ + protected @MonotonicNonNull AnalysisResult flowResult; + + /** + * A mapping from methods (or other code blocks) to their regular exit store (used to check + * postconditions). + */ + protected final IdentityHashMap regularExitStores; + + /** A mapping from methods (or other code blocks) to their exceptional exit store. */ + protected final IdentityHashMap exceptionalExitStores; + + /** A mapping from methods to a list with all return statements and the corresponding store. */ + protected final IdentityHashMap>>> + returnStatementStores; + + /** + * Returns the regular exit store for a method or another code block (such as static + * initializers). Returns {@code null} if there is no such store. This can happen because the + * method cannot exit through the regular exit block, or it is abstract or in an interface. + * + * @param tree a MethodTree or other code block, such as a static initializer + * @return the regular exit store, or {@code null} + */ + public @Nullable Store getRegularExitStore(Tree tree) { + if (regularExitStores == null) { + if (tree.getKind() == Tree.Kind.METHOD) { + if (((MethodTree) tree).getBody() == null) { + // No body: the method is abstract or in an interface + return null; } - Store store = getStoreBefore(tree); - Value value = store.getValue(je); - return value != null ? value.getAnnotations().iterator().next() : null; + } + throw new BugInCF("regularExitStores==null for [" + tree.getClass() + "]" + tree); + } + return regularExitStores.get(tree); + } + + /** + * Returns the exceptional exit store for a method or another code block (such as static + * initializers). + * + * @param tree a MethodTree or other code block, such as a static initializer + * @return the exceptional exit store, or {@code null}, if there is no such store + */ + public @Nullable Store getExceptionalExitStore(Tree tree) { + return exceptionalExitStores.get(tree); + } + + /** + * Returns a list of all return statements of {@code method} paired with their corresponding + * {@link TransferResult}. If {@code method} has no return statement, then the empty list is + * returned. + * + * @param methodTree method whose return statements should be returned + * @return a list of all return statements of {@code method} paired with their corresponding + * {@link TransferResult} or an empty list if {@code method} has no return statements + */ + public List>> getReturnStatementStores( + MethodTree methodTree) { + assert returnStatementStores.containsKey(methodTree); + return returnStatementStores.get(methodTree); + } + + /** + * Returns the store immediately before a given {@link Tree}. + * + * @return the store immediately before a given {@link Tree} + */ + public Store getStoreBefore(Tree tree) { + if (!analysis.isRunning()) { + return flowResult.getStoreBefore(tree); + } + Set nodes = analysis.getNodesForTree(tree); + if (nodes != null) { + return getStoreBefore(nodes); + } else { + return flowResult.getStoreBefore(tree); + } + } + + /** + * Returns the store immediately before a given Set of {@link Node}s. + * + * @return the store immediately before a given Set of {@link Node}s + */ + public Store getStoreBefore(Set nodes) { + Store merge = null; + for (Node aNode : nodes) { + Store s = getStoreBefore(aNode); + if (merge == null) { + merge = s; + } else if (s != null) { + merge = merge.leastUpperBound(s); + } } - - /* - * Returns true if the {@code exprTree} is unreachable. This is a conservative estimate and may - * return {@code false} even though the {@code exprTree} is unreachable. - * - * @param exprTree an expression tree - * @return true if the {@code exprTree} is unreachable - * - public boolean isUnreachable(ExpressionTree exprTree) { - if (!everUseFlow) { - return false; - } - Set nodes = getNodesForTree(exprTree); - if (nodes == null) { - // Dataflow has no any information about the tree, so conservatively consider the tree - // reachable. - return false; - } - for (Node n : nodes) { - if (n.getTree() != null && reachableNodes.contains(n.getTree())) { - return false; - } - } - // None of the corresponding nodes is reachable, so this tree is dead. - return true; + return merge; + } + + /** + * Returns the store immediately before a given node. + * + * @param node a node whose pre-store to return + * @return the store immediately before {@code node} + */ + public @Nullable Store getStoreBefore(Node node) { + if (!analysis.isRunning()) { + return flowResult.getStoreBefore(node); + } + TransferInput prevStore = analysis.getInput(node.getBlock()); + if (prevStore == null) { + return null; + } + Store store = + AnalysisResult.runAnalysisFor( + node, + Analysis.BeforeOrAfter.BEFORE, + prevStore, + analysis.getNodeValues(), + flowResultAnalysisCaches); + return store; + } + + /** + * Returns the store immediately after a given tree. + * + *

May return null; for example, after a {@code return} statement. + * + * @param tree the tree whose post-store to return + * @return the store immediately after a given tree + */ + public @Nullable Store getStoreAfter(Tree tree) { + if (!analysis.isRunning()) { + return flowResult.getStoreAfter(tree); + } + Set nodes = analysis.getNodesForTree(tree); + return getStoreAfter(nodes); + } + + /** + * Returns the store immediately after a given set of nodes. + * + * @param nodes the nodes whose post-stores to LUB + * @return the LUB of the stores store immediately after {@code nodes} + */ + public Store getStoreAfter(Set nodes) { + Store merge = null; + for (Node node : nodes) { + Store s = getStoreAfter(node); + if (merge == null) { + merge = s; + } else if (s != null) { + merge = merge.leastUpperBound(s); + } } - */ - - /** - * Track the state of org.checkerframework.dataflow analysis scanning for each class tree in the - * compilation unit. - */ - protected enum ScanState { - /** Dataflow analysis in progress. */ - IN_PROGRESS, - /** Dataflow analysis finished. */ - FINISHED + return merge; + } + + /** + * Returns the store immediately after a given {@link Node}. + * + * @param node node after which the store is returned + * @return the store immediately after a given {@link Node} + */ + public Store getStoreAfter(Node node) { + if (!analysis.isRunning()) { + return flowResult.getStoreAfter(node); + } + Store res = + AnalysisResult.runAnalysisFor( + node, + Analysis.BeforeOrAfter.AFTER, + analysis.getInput(node.getBlock()), + analysis.getNodeValues(), + flowResultAnalysisCaches); + return res; + } + + /** + * See {@link org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)}. + * + * @param tree a tree + * @return the {@link Node}s for a given {@link Tree} + * @see org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree) + */ + public @Nullable Set getNodesForTree(Tree tree) { + return flowResult.getNodesForTree(tree); + } + + /** + * Return the first {@link Node} for a given {@link Tree} that has class {@code kind}. + * + *

You probably don't want to use this function: iterate over the result of {@link + * #getNodesForTree(Tree)} yourself or ask for a conservative approximation of the store using + * {@link #getStoreBefore(Tree)} or {@link #getStoreAfter(Tree)}. This method is for code that + * uses a {@link Node} in a rather unusual way. Callers should probably be rewritten to not use a + * {@link Node} at all. + * + * @param the class of the node to return + * @param tree a tree in which to search for a node of class {@code kind} + * @param kind the class of the node to return + * @return the first {@link Node} for a given {@link Tree} that has class {@code kind} + * @see #getNodesForTree(Tree) + * @see #getStoreBefore(Tree) + * @see #getStoreAfter(Tree) + */ + public @Nullable T getFirstNodeOfKindForTree(Tree tree, Class kind) { + Set nodes = getNodesForTree(tree); + for (Node node : nodes) { + if (node.getClass() == kind) { + return kind.cast(node); + } } + return null; + } + + /** + * Returns the value of effectively final local variables. + * + * @return the value of effectively final local variables + */ + public Map getFinalLocalValues() { + return flowResult.getFinalLocalValues(); + } + + /** + * Returns true if the receiver of a method or constructor might not be fully initialized. + * + * @param methodDeclTree the declaration of the method or constructor + * @return true if the receiver of a method or constructor might not be fully initialized + */ + @Pure + public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { + return TreeUtils.isConstructor(methodDeclTree); + } + + /** + * Perform a org.checkerframework.dataflow analysis over a single class tree and its nested + * classes. + * + * @param classTree the class to analyze + */ + protected void performFlowAnalysis(ClassTree classTree) { + if (flowResult == null) { + this.regularExitStores.clear(); + this.exceptionalExitStores.clear(); + this.returnStatementStores.clear(); + this.flowResult = new AnalysisResult<>(flowResultAnalysisCaches); + } + + // no need to scan annotations + if (classTree.getKind() == Tree.Kind.ANNOTATION_TYPE) { + // Mark finished so that default annotations will be applied. + scannedClasses.put(classTree, ScanState.FINISHED); + return; + } + + // class trees and their initial stores + Queue> classQueue = new ArrayDeque<>(); + List> fieldValues = new ArrayList<>(); + + // No captured store for top-level classes. + classQueue.add(IPair.of(classTree, null)); + + while (!classQueue.isEmpty()) { + IPair qel = classQueue.remove(); + ClassTree ct = qel.first; + Store capturedStore = qel.second; + scannedClasses.put(ct, ScanState.IN_PROGRESS); + + TreePath preTreePath = getVisitorTreePath(); + + // Don't call AnnotatedTypeFactory#getPath, because it uses visitorTreePath. + setVisitorTreePath(TreePath.getPath(this.getRoot(), ct)); + + // start with the captured store as initialization store + initializationStaticStore = capturedStore; + initializationStore = capturedStore; + + // The store is null if the lambda is unreachable. + Queue> lambdaQueue = new ArrayDeque<>(); + + // Queue up classes (for top-level `while` loop) and methods (for within this `try` + // construct); analyze top-level blocks and variable initializers as they are + // encountered. + try { + List methods = new ArrayList<>(); + List members = ct.getMembers(); + if (!Ordering.from(sortVariablesFirst).isOrdered(members)) { + members = new ArrayList<>(members); + // Process variables before methods, so all field initializers are observed + // before the constructor is analyzed and reports uninitialized variables. + members.sort(sortVariablesFirst); + } + for (Tree m : members) { + switch (TreeUtils.getKindRecordAsClass(m)) { + case CLASS: // Including RECORD + case ANNOTATION_TYPE: + case INTERFACE: + case ENUM: + // Visit inner and nested class trees. + // TODO: Use no store for them? What can be captured? + classQueue.add(IPair.of((ClassTree) m, capturedStore)); + break; + case METHOD: + MethodTree mt = (MethodTree) m; - /** Map from ClassTree to their dataflow analysis state. */ - protected final Map scannedClasses = new HashMap<>(); + // Skip abstract and native methods because they have no body. + Set flags = mt.getModifiers().getFlags(); + if (flags.contains(Modifier.ABSTRACT) || flags.contains(Modifier.NATIVE)) { + break; + } + // Abstract methods in an interface have a null body but do not have an + // ABSTRACT flag. + if (mt.getBody() == null) { + break; + } - /* - * A set of trees whose corresponding nodes are reachable. This is not an exhaustive set of - * reachable trees. Use {@link #isUnreachable(ExpressionTree)} instead of this set directly. - * - *

This cannot be a set of Nodes, because two LocalVariableNodes are equal if they have the - * same name but represent different uses of the variable. So instead of storing Nodes, it - * stores the result of {@code Node#getTree}. - */ - // private final Set reachableNodes = new HashSet<>(); - - /** - * The result of the flow analysis. Invariant: - * - *

-     *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
-     * 
- * - * Note that flowResult contains analysis results for Trees from multiple classes which are - * produced by multiple calls to performFlowAnalysis. - */ - protected @MonotonicNonNull AnalysisResult flowResult; - - /** - * A mapping from methods (or other code blocks) to their regular exit store (used to check - * postconditions). - */ - protected final IdentityHashMap regularExitStores; - - /** A mapping from methods (or other code blocks) to their exceptional exit store. */ - protected final IdentityHashMap exceptionalExitStores; - - /** A mapping from methods to a list with all return statements and the corresponding store. */ - protected final IdentityHashMap< - MethodTree, List>>> - returnStatementStores; - - /** - * Returns the regular exit store for a method or another code block (such as static - * initializers). Returns {@code null} if there is no such store. This can happen because the - * method cannot exit through the regular exit block, or it is abstract or in an interface. - * - * @param tree a MethodTree or other code block, such as a static initializer - * @return the regular exit store, or {@code null} - */ - public @Nullable Store getRegularExitStore(Tree tree) { - if (regularExitStores == null) { - if (tree.getKind() == Tree.Kind.METHOD) { - if (((MethodTree) tree).getBody() == null) { - // No body: the method is abstract or in an interface - return null; + // Wait with scanning the method until all other members + // have been processed. + CFGMethod met = new CFGMethod(mt, ct); + methods.add(met); + break; + case VARIABLE: + VariableTree vt = (VariableTree) m; + ExpressionTree initializer = vt.getInitializer(); + AnnotatedTypeMirror declaredType = getAnnotatedTypeLhs(vt); + Value declaredValue = analysis.createAbstractValue(declaredType); + FieldAccess fieldExpr = (FieldAccess) JavaExpression.fromVariableTree(vt); + // analyze initializer if present + if (initializer != null) { + boolean isStatic = vt.getModifiers().getFlags().contains(Modifier.STATIC); + analyze( + classQueue, + lambdaQueue, + new CFGStatement(vt, ct), + fieldValues, + classTree, + true, + true, + isStatic, + capturedStore); + Value initializerValue = flowResult.getValue(initializer); + if (initializerValue != null) { + fieldValues.add( + new FieldInitialValue<>(fieldExpr, declaredValue, initializerValue)); + break; } - } - throw new BugInCF("regularExitStores==null for [" + tree.getClass() + "]" + tree); - } - return regularExitStores.get(tree); - } - - /** - * Returns the exceptional exit store for a method or another code block (such as static - * initializers). - * - * @param tree a MethodTree or other code block, such as a static initializer - * @return the exceptional exit store, or {@code null}, if there is no such store - */ - public @Nullable Store getExceptionalExitStore(Tree tree) { - return exceptionalExitStores.get(tree); - } - - /** - * Returns a list of all return statements of {@code method} paired with their corresponding - * {@link TransferResult}. If {@code method} has no return statement, then the empty list is - * returned. - * - * @param methodTree method whose return statements should be returned - * @return a list of all return statements of {@code method} paired with their corresponding - * {@link TransferResult} or an empty list if {@code method} has no return statements - */ - public List>> getReturnStatementStores( - MethodTree methodTree) { - assert returnStatementStores.containsKey(methodTree); - return returnStatementStores.get(methodTree); - } - - /** - * Returns the store immediately before a given {@link Tree}. - * - * @return the store immediately before a given {@link Tree} - */ - public Store getStoreBefore(Tree tree) { - if (!analysis.isRunning()) { - return flowResult.getStoreBefore(tree); - } - Set nodes = analysis.getNodesForTree(tree); - if (nodes != null) { - return getStoreBefore(nodes); + } + fieldValues.add(new FieldInitialValue<>(fieldExpr, declaredValue, null)); + break; + case BLOCK: + BlockTree b = (BlockTree) m; + analyze( + classQueue, + lambdaQueue, + new CFGStatement(b, ct), + fieldValues, + ct, + true, + true, + b.isStatic(), + capturedStore); + break; + default: + assert false : "Unexpected member: " + m.getKind(); + break; + } + } + + // Now analyze all methods. + // TODO: at this point, we don't have any information about + // fields of superclasses. + for (CFGMethod met : methods) { + analyze( + classQueue, + lambdaQueue, + met, + fieldValues, + classTree, + TreeUtils.isConstructor(met.getMethod()), + false, + false, + capturedStore); + } + + while (!lambdaQueue.isEmpty()) { + IPair lambdaPair = lambdaQueue.poll(); + MethodTree mt = + (MethodTree) + TreePathUtil.enclosingOfKind(getPath(lambdaPair.first), Tree.Kind.METHOD); + analyze( + classQueue, + lambdaQueue, + new CFGLambda(lambdaPair.first, classTree, mt), + fieldValues, + classTree, + false, + false, + false, + lambdaPair.second); + } + + // By convention we store the static initialization store as the regular exit + // store of the class node, so that it can later be used to check + // that all fields are initialized properly. + // See InitializationVisitor.visitClass(). + if (initializationStaticStore == null) { + regularExitStores.put(ct, emptyStore); } else { - return flowResult.getStoreBefore(tree); - } - } - - /** - * Returns the store immediately before a given Set of {@link Node}s. - * - * @return the store immediately before a given Set of {@link Node}s - */ - public Store getStoreBefore(Set nodes) { - Store merge = null; - for (Node aNode : nodes) { - Store s = getStoreBefore(aNode); - if (merge == null) { - merge = s; - } else if (s != null) { - merge = merge.leastUpperBound(s); - } - } - return merge; - } - - /** - * Returns the store immediately before a given node. - * - * @param node a node whose pre-store to return - * @return the store immediately before {@code node} - */ - public @Nullable Store getStoreBefore(Node node) { - if (!analysis.isRunning()) { - return flowResult.getStoreBefore(node); + regularExitStores.put(ct, initializationStaticStore); } - TransferInput prevStore = analysis.getInput(node.getBlock()); - if (prevStore == null) { - return null; - } - Store store = - AnalysisResult.runAnalysisFor( - node, - Analysis.BeforeOrAfter.BEFORE, - prevStore, - analysis.getNodeValues(), - flowResultAnalysisCaches); - return store; - } - - /** - * Returns the store immediately after a given tree. - * - *

May return null; for example, after a {@code return} statement. - * - * @param tree the tree whose post-store to return - * @return the store immediately after a given tree - */ - public @Nullable Store getStoreAfter(Tree tree) { - if (!analysis.isRunning()) { - return flowResult.getStoreAfter(tree); - } - Set nodes = analysis.getNodesForTree(tree); - return getStoreAfter(nodes); - } - - /** - * Returns the store immediately after a given set of nodes. - * - * @param nodes the nodes whose post-stores to LUB - * @return the LUB of the stores store immediately after {@code nodes} - */ - public Store getStoreAfter(Set nodes) { - Store merge = null; - for (Node node : nodes) { - Store s = getStoreAfter(node); - if (merge == null) { - merge = s; - } else if (s != null) { - merge = merge.leastUpperBound(s); - } - } - return merge; - } - - /** - * Returns the store immediately after a given {@link Node}. - * - * @param node node after which the store is returned - * @return the store immediately after a given {@link Node} - */ - public Store getStoreAfter(Node node) { - if (!analysis.isRunning()) { - return flowResult.getStoreAfter(node); - } - Store res = - AnalysisResult.runAnalysisFor( - node, - Analysis.BeforeOrAfter.AFTER, - analysis.getInput(node.getBlock()), - analysis.getNodeValues(), - flowResultAnalysisCaches); - return res; - } - - /** - * See {@link org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)}. - * - * @param tree a tree - * @return the {@link Node}s for a given {@link Tree} - * @see org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree) - */ - public @Nullable Set getNodesForTree(Tree tree) { - return flowResult.getNodesForTree(tree); - } - - /** - * Return the first {@link Node} for a given {@link Tree} that has class {@code kind}. - * - *

You probably don't want to use this function: iterate over the result of {@link - * #getNodesForTree(Tree)} yourself or ask for a conservative approximation of the store using - * {@link #getStoreBefore(Tree)} or {@link #getStoreAfter(Tree)}. This method is for code that - * uses a {@link Node} in a rather unusual way. Callers should probably be rewritten to not use - * a {@link Node} at all. - * - * @param the class of the node to return - * @param tree a tree in which to search for a node of class {@code kind} - * @param kind the class of the node to return - * @return the first {@link Node} for a given {@link Tree} that has class {@code kind} - * @see #getNodesForTree(Tree) - * @see #getStoreBefore(Tree) - * @see #getStoreAfter(Tree) - */ - public @Nullable T getFirstNodeOfKindForTree(Tree tree, Class kind) { - Set nodes = getNodesForTree(tree); - for (Node node : nodes) { - if (node.getClass() == kind) { - return kind.cast(node); - } - } - return null; - } - - /** - * Returns the value of effectively final local variables. - * - * @return the value of effectively final local variables - */ - public Map getFinalLocalValues() { - return flowResult.getFinalLocalValues(); - } - - /** - * Returns true if the receiver of a method or constructor might not be fully initialized. - * - * @param methodDeclTree the declaration of the method or constructor - * @return true if the receiver of a method or constructor might not be fully initialized - */ - @Pure - public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { - return TreeUtils.isConstructor(methodDeclTree); - } - - /** - * Perform a org.checkerframework.dataflow analysis over a single class tree and its nested - * classes. - * - * @param classTree the class to analyze - */ - protected void performFlowAnalysis(ClassTree classTree) { - if (flowResult == null) { - this.regularExitStores.clear(); - this.exceptionalExitStores.clear(); - this.returnStatementStores.clear(); - this.flowResult = new AnalysisResult<>(flowResultAnalysisCaches); - } - - // no need to scan annotations - if (classTree.getKind() == Tree.Kind.ANNOTATION_TYPE) { - // Mark finished so that default annotations will be applied. - scannedClasses.put(classTree, ScanState.FINISHED); - return; - } - - // class trees and their initial stores - Queue> classQueue = new ArrayDeque<>(); - List> fieldValues = new ArrayList<>(); - - // No captured store for top-level classes. - classQueue.add(IPair.of(classTree, null)); - - while (!classQueue.isEmpty()) { - IPair qel = classQueue.remove(); - ClassTree ct = qel.first; - Store capturedStore = qel.second; - scannedClasses.put(ct, ScanState.IN_PROGRESS); - - TreePath preTreePath = getVisitorTreePath(); - - // Don't call AnnotatedTypeFactory#getPath, because it uses visitorTreePath. - setVisitorTreePath(TreePath.getPath(this.getRoot(), ct)); - - // start with the captured store as initialization store - initializationStaticStore = capturedStore; - initializationStore = capturedStore; - - // The store is null if the lambda is unreachable. - Queue> lambdaQueue = new ArrayDeque<>(); - - // Queue up classes (for top-level `while` loop) and methods (for within this `try` - // construct); analyze top-level blocks and variable initializers as they are - // encountered. - try { - List methods = new ArrayList<>(); - List members = ct.getMembers(); - if (!Ordering.from(sortVariablesFirst).isOrdered(members)) { - members = new ArrayList<>(members); - // Process variables before methods, so all field initializers are observed - // before the constructor is analyzed and reports uninitialized variables. - members.sort(sortVariablesFirst); - } - for (Tree m : members) { - switch (TreeUtils.getKindRecordAsClass(m)) { - case CLASS: // Including RECORD - case ANNOTATION_TYPE: - case INTERFACE: - case ENUM: - // Visit inner and nested class trees. - // TODO: Use no store for them? What can be captured? - classQueue.add(IPair.of((ClassTree) m, capturedStore)); - break; - case METHOD: - MethodTree mt = (MethodTree) m; - - // Skip abstract and native methods because they have no body. - Set flags = mt.getModifiers().getFlags(); - if (flags.contains(Modifier.ABSTRACT) - || flags.contains(Modifier.NATIVE)) { - break; - } - // Abstract methods in an interface have a null body but do not have an - // ABSTRACT flag. - if (mt.getBody() == null) { - break; - } - - // Wait with scanning the method until all other members - // have been processed. - CFGMethod met = new CFGMethod(mt, ct); - methods.add(met); - break; - case VARIABLE: - VariableTree vt = (VariableTree) m; - ExpressionTree initializer = vt.getInitializer(); - AnnotatedTypeMirror declaredType = getAnnotatedTypeLhs(vt); - Value declaredValue = analysis.createAbstractValue(declaredType); - FieldAccess fieldExpr = - (FieldAccess) JavaExpression.fromVariableTree(vt); - // analyze initializer if present - if (initializer != null) { - boolean isStatic = - vt.getModifiers().getFlags().contains(Modifier.STATIC); - analyze( - classQueue, - lambdaQueue, - new CFGStatement(vt, ct), - fieldValues, - classTree, - true, - true, - isStatic, - capturedStore); - Value initializerValue = flowResult.getValue(initializer); - if (initializerValue != null) { - fieldValues.add( - new FieldInitialValue<>( - fieldExpr, declaredValue, initializerValue)); - break; - } - } - fieldValues.add( - new FieldInitialValue<>(fieldExpr, declaredValue, null)); - break; - case BLOCK: - BlockTree b = (BlockTree) m; - analyze( - classQueue, - lambdaQueue, - new CFGStatement(b, ct), - fieldValues, - ct, - true, - true, - b.isStatic(), - capturedStore); - break; - default: - assert false : "Unexpected member: " + m.getKind(); - break; - } - } - - // Now analyze all methods. - // TODO: at this point, we don't have any information about - // fields of superclasses. - for (CFGMethod met : methods) { - analyze( - classQueue, - lambdaQueue, - met, - fieldValues, - classTree, - TreeUtils.isConstructor(met.getMethod()), - false, - false, - capturedStore); - } - - while (!lambdaQueue.isEmpty()) { - IPair lambdaPair = lambdaQueue.poll(); - MethodTree mt = - (MethodTree) - TreePathUtil.enclosingOfKind( - getPath(lambdaPair.first), Tree.Kind.METHOD); - analyze( - classQueue, - lambdaQueue, - new CFGLambda(lambdaPair.first, classTree, mt), - fieldValues, - classTree, - false, - false, - false, - lambdaPair.second); - } - - // By convention we store the static initialization store as the regular exit - // store of the class node, so that it can later be used to check - // that all fields are initialized properly. - // See InitializationVisitor.visitClass(). - if (initializationStaticStore == null) { - regularExitStores.put(ct, emptyStore); - } else { - regularExitStores.put(ct, initializationStaticStore); - } - } finally { - setVisitorTreePath(preTreePath); - } + } finally { + setVisitorTreePath(preTreePath); + } - scannedClasses.put(ct, ScanState.FINISHED); - } + scannedClasses.put(ct, ScanState.FINISHED); } + } - /** Sorts a list of trees with the variables first. */ - private final Comparator sortVariablesFirst = - (t1, t2) -> { - boolean variable1 = t1.getKind() == Tree.Kind.VARIABLE; - boolean variable2 = t2.getKind() == Tree.Kind.VARIABLE; - if (variable1 && !variable2) { - return -1; - } else if (!variable1 && variable2) { - return 1; - } else { - return 0; - } - }; - - /** - * Analyze the AST {@code ast} and store the result. Additional operations that should be - * performed after analysis should be implemented in {@link #postAnalyze(ControlFlowGraph)}. - * - * @param classQueue the queue for encountered class trees and their initial stores - * @param lambdaQueue the queue for encountered lambda expression trees and their initial stores - * @param ast the AST to analyze - * @param fieldValues the abstract values for all fields of the same class - * @param currentClass the class we are currently looking at - * @param isInitializationCode are we analyzing a (static/non-static) initializer block of a - * class - * @param updateInitializationStore should the initialization store be updated - * @param isStatic are we analyzing a static construct - * @param capturedStore the input Store to use for captured variables, e.g. in a lambda - * @see #postAnalyze(org.checkerframework.dataflow.cfg.ControlFlowGraph) - */ - protected void analyze( - Queue> classQueue, - Queue> lambdaQueue, - UnderlyingAST ast, - List> fieldValues, - ClassTree currentClass, - boolean isInitializationCode, - boolean updateInitializationStore, - boolean isStatic, - @Nullable Store capturedStore) { - ControlFlowGraph cfg = - CFCFGBuilder.build(this.getRoot(), ast, checker, this, processingEnv); - /* - cfg.getAllNodes(this::isIgnoredExceptionType) - .forEach( - node -> { - if (node.getTree() != null) { - reachableNodes.add(node.getTree()); - } - }); - */ - if (isInitializationCode) { - Store initStore = !isStatic ? initializationStore : initializationStaticStore; - if (initStore != null) { - // we have already seen initialization code and analyzed it, and - // the analysis ended with the store initStore. - // use it to start the next analysis. - transfer.setFixedInitialStore(initStore); - } else { - transfer.setFixedInitialStore(capturedStore); - } - } else { - transfer.setFixedInitialStore(capturedStore); - } - analysis.performAnalysis(cfg, fieldValues); - AnalysisResult result = analysis.getResult(); - - // store result - flowResult.combine(result); - if (ast.getKind() == UnderlyingAST.Kind.METHOD) { - // store exit store (for checking postconditions) - CFGMethod mast = (CFGMethod) ast; - MethodTree method = mast.getMethod(); - Store regularExitStore = analysis.getRegularExitStore(); - if (regularExitStore != null) { - regularExitStores.put(method, regularExitStore); - } - Store exceptionalExitStore = analysis.getExceptionalExitStore(); - if (exceptionalExitStore != null) { - exceptionalExitStores.put(method, exceptionalExitStore); - } - returnStatementStores.put(method, analysis.getReturnStatementStores()); - } else if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { - CFGStatement block = (CFGStatement) ast; - Store regularExitStore = analysis.getRegularExitStore(); - if (regularExitStore != null) { - regularExitStores.put(block.getCode(), regularExitStore); - } - Store exceptionalExitStore = analysis.getExceptionalExitStore(); - if (exceptionalExitStore != null) { - exceptionalExitStores.put(block.getCode(), exceptionalExitStore); - } - } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { - // TODO: Postconditions? - - CFGLambda block = (CFGLambda) ast; - Store regularExitStore = analysis.getRegularExitStore(); - if (regularExitStore != null) { - regularExitStores.put(block.getCode(), regularExitStore); - } - Store exceptionalExitStore = analysis.getExceptionalExitStore(); - if (exceptionalExitStore != null) { - exceptionalExitStores.put(block.getCode(), exceptionalExitStore); - } + /** Sorts a list of trees with the variables first. */ + private final Comparator sortVariablesFirst = + (t1, t2) -> { + boolean variable1 = t1.getKind() == Tree.Kind.VARIABLE; + boolean variable2 = t2.getKind() == Tree.Kind.VARIABLE; + if (variable1 && !variable2) { + return -1; + } else if (!variable1 && variable2) { + return 1; } else { - assert false : "Unexpected AST kind: " + ast.getKind(); - } - - if (isInitializationCode && updateInitializationStore) { - Store newInitStore = analysis.getRegularExitStore(); - if (!isStatic) { - initializationStore = newInitStore; - } else { - initializationStaticStore = newInitStore; - } - } - - // add classes declared in CFG - for (ClassTree cls : cfg.getDeclaredClasses()) { - classQueue.add(IPair.of(cls, getStoreBefore(cls))); - } - // add lambdas declared in CFG - for (LambdaExpressionTree lambda : cfg.getDeclaredLambdas()) { - lambdaQueue.add(IPair.of(lambda, getStoreBefore(lambda))); - } - - postAnalyze(cfg); - } + return 0; + } + }; + + /** + * Analyze the AST {@code ast} and store the result. Additional operations that should be + * performed after analysis should be implemented in {@link #postAnalyze(ControlFlowGraph)}. + * + * @param classQueue the queue for encountered class trees and their initial stores + * @param lambdaQueue the queue for encountered lambda expression trees and their initial stores + * @param ast the AST to analyze + * @param fieldValues the abstract values for all fields of the same class + * @param currentClass the class we are currently looking at + * @param isInitializationCode are we analyzing a (static/non-static) initializer block of a class + * @param updateInitializationStore should the initialization store be updated + * @param isStatic are we analyzing a static construct + * @param capturedStore the input Store to use for captured variables, e.g. in a lambda + * @see #postAnalyze(org.checkerframework.dataflow.cfg.ControlFlowGraph) + */ + protected void analyze( + Queue> classQueue, + Queue> lambdaQueue, + UnderlyingAST ast, + List> fieldValues, + ClassTree currentClass, + boolean isInitializationCode, + boolean updateInitializationStore, + boolean isStatic, + @Nullable Store capturedStore) { + ControlFlowGraph cfg = CFCFGBuilder.build(this.getRoot(), ast, checker, this, processingEnv); + /* + cfg.getAllNodes(this::isIgnoredExceptionType) + .forEach( + node -> { + if (node.getTree() != null) { + reachableNodes.add(node.getTree()); + } + }); + */ + if (isInitializationCode) { + Store initStore = !isStatic ? initializationStore : initializationStaticStore; + if (initStore != null) { + // we have already seen initialization code and analyzed it, and + // the analysis ended with the store initStore. + // use it to start the next analysis. + transfer.setFixedInitialStore(initStore); + } else { + transfer.setFixedInitialStore(capturedStore); + } + } else { + transfer.setFixedInitialStore(capturedStore); + } + analysis.performAnalysis(cfg, fieldValues); + AnalysisResult result = analysis.getResult(); + + // store result + flowResult.combine(result); + if (ast.getKind() == UnderlyingAST.Kind.METHOD) { + // store exit store (for checking postconditions) + CFGMethod mast = (CFGMethod) ast; + MethodTree method = mast.getMethod(); + Store regularExitStore = analysis.getRegularExitStore(); + if (regularExitStore != null) { + regularExitStores.put(method, regularExitStore); + } + Store exceptionalExitStore = analysis.getExceptionalExitStore(); + if (exceptionalExitStore != null) { + exceptionalExitStores.put(method, exceptionalExitStore); + } + returnStatementStores.put(method, analysis.getReturnStatementStores()); + } else if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { + CFGStatement block = (CFGStatement) ast; + Store regularExitStore = analysis.getRegularExitStore(); + if (regularExitStore != null) { + regularExitStores.put(block.getCode(), regularExitStore); + } + Store exceptionalExitStore = analysis.getExceptionalExitStore(); + if (exceptionalExitStore != null) { + exceptionalExitStores.put(block.getCode(), exceptionalExitStore); + } + } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { + // TODO: Postconditions? - /** - * Returns true if {@code typeMirror} is an exception type that should be ignored. - * - * @param typeMirror an exception type - * @return true if {@code typeMirror} is an exception type that should be ignored - */ - public boolean isIgnoredExceptionType(TypeMirror typeMirror) { - return false; + CFGLambda block = (CFGLambda) ast; + Store regularExitStore = analysis.getRegularExitStore(); + if (regularExitStore != null) { + regularExitStores.put(block.getCode(), regularExitStore); + } + Store exceptionalExitStore = analysis.getExceptionalExitStore(); + if (exceptionalExitStore != null) { + exceptionalExitStores.put(block.getCode(), exceptionalExitStore); + } + } else { + assert false : "Unexpected AST kind: " + ast.getKind(); } - /** - * Perform any additional operations on a CFG. Called once per CFG, after the CFG has been - * analyzed by {@link #analyze(Queue, Queue, UnderlyingAST, List, ClassTree, boolean, boolean, - * boolean, CFAbstractStore)}. This method can be used to initialize additional state or to - * perform any analyses that are easier to perform on the CFG instead of the AST. - * - * @param cfg the CFG - * @see #analyze(java.util.Queue, java.util.Queue, - * org.checkerframework.dataflow.cfg.UnderlyingAST, java.util.List, - * com.sun.source.tree.ClassTree, boolean, boolean, boolean, - * org.checkerframework.framework.flow.CFAbstractStore) - */ - protected void postAnalyze(ControlFlowGraph cfg) { - handleCFGViz(cfg); - } - - /** Whether handling CFG visualization is necessary. */ - private final boolean handleCFGViz; - - /** - * Handle the visualization of the CFG, if necessary. - * - * @param cfg the CFG - */ - protected void handleCFGViz(ControlFlowGraph cfg) { - if (handleCFGViz) { - getCFGVisualizer().visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); - } + if (isInitializationCode && updateInitializationStore) { + Store newInitStore = analysis.getRegularExitStore(); + if (!isStatic) { + initializationStore = newInitStore; + } else { + initializationStaticStore = newInitStore; + } } - /** - * Returns the type of the left-hand side of an assignment without applying local variable - * defaults to type variables. - * - *

The type variables that are types of local variables are defaulted to top so that they can - * be refined by dataflow. When these types are used as context during type argument inference, - * this default is too conservative. So this method is used instead of {@link - * GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree)}. - * - *

{@link TypeArgInferenceUtil#assignedToVariable(AnnotatedTypeFactory, VariableTree)} - * explains why a different type is used. - * - * @param lhsTree left-hand side of an assignment - * @return AnnotatedTypeMirror of {@code lhsTree} - */ - public AnnotatedTypeMirror getAnnotatedTypeLhsNoTypeVarDefault(Tree lhsTree) { - boolean old = this.shouldDefaultTypeVarLocals; - shouldDefaultTypeVarLocals = false; - AnnotatedTypeMirror type = getAnnotatedTypeLhs(lhsTree); - this.shouldDefaultTypeVarLocals = old; - return type; - } - - /** - * Whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This can be used by - * subclasses whenever the type of expression differs depending on whether it occurs on the - * left- or right-hand side of an assignment (e.g., in the presence of type refinements or for - * uninitialized fields in the Initialization Checker.) - * - * @see #isComputingAnnotatedTypeMirrorOfLhs() - */ - private boolean computingAnnotatedTypeMirrorOfLhs = false; - - /** - * Returns whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This controls which - * hierarchies' qualifiers are changed based on the receiver type and the declared annotations - * for a field. - * - * @return whether {@link #getAnnotatedTypeLhs(Tree)} is running right now - * @see #getAnnotatedTypeLhs(Tree) - */ - public boolean isComputingAnnotatedTypeMirrorOfLhs() { - return computingAnnotatedTypeMirrorOfLhs; - } - - /** - * Returns the type of a JavaExpression {@code expr} if it were evaluated before a tree {@code - * tree}. - * - *

This is used by {@link BaseTypeVisitor#visitMethodInvocation(MethodInvocationTree, Void)} - * to check the preconditions of method calls. - * - * @param expr the expression to type - * @param tree a tree - * @return the type of {@code expr} if it were evaluated before tree {@code tree} - */ - public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { - Store store = getStoreBefore(tree); - Value value = null; - if (CFAbstractStore.canInsertJavaExpression(expr)) { - value = store.getValue(expr); - } - Set annos = null; - if (value != null) { - annos = value.getAnnotations(); + // add classes declared in CFG + for (ClassTree cls : cfg.getDeclaredClasses()) { + classQueue.add(IPair.of(cls, getStoreBefore(cls))); + } + // add lambdas declared in CFG + for (LambdaExpressionTree lambda : cfg.getDeclaredLambdas()) { + lambdaQueue.add(IPair.of(lambda, getStoreBefore(lambda))); + } + + postAnalyze(cfg); + } + + /** + * Returns true if {@code typeMirror} is an exception type that should be ignored. + * + * @param typeMirror an exception type + * @return true if {@code typeMirror} is an exception type that should be ignored + */ + public boolean isIgnoredExceptionType(TypeMirror typeMirror) { + return false; + } + + /** + * Perform any additional operations on a CFG. Called once per CFG, after the CFG has been + * analyzed by {@link #analyze(Queue, Queue, UnderlyingAST, List, ClassTree, boolean, boolean, + * boolean, CFAbstractStore)}. This method can be used to initialize additional state or to + * perform any analyses that are easier to perform on the CFG instead of the AST. + * + * @param cfg the CFG + * @see #analyze(java.util.Queue, java.util.Queue, + * org.checkerframework.dataflow.cfg.UnderlyingAST, java.util.List, + * com.sun.source.tree.ClassTree, boolean, boolean, boolean, + * org.checkerframework.framework.flow.CFAbstractStore) + */ + protected void postAnalyze(ControlFlowGraph cfg) { + handleCFGViz(cfg); + } + + /** Whether handling CFG visualization is necessary. */ + private final boolean handleCFGViz; + + /** + * Handle the visualization of the CFG, if necessary. + * + * @param cfg the CFG + */ + protected void handleCFGViz(ControlFlowGraph cfg) { + if (handleCFGViz) { + getCFGVisualizer().visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); + } + } + + /** + * Returns the type of the left-hand side of an assignment without applying local variable + * defaults to type variables. + * + *

The type variables that are types of local variables are defaulted to top so that they can + * be refined by dataflow. When these types are used as context during type argument inference, + * this default is too conservative. So this method is used instead of {@link + * GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree)}. + * + *

{@link TypeArgInferenceUtil#assignedToVariable(AnnotatedTypeFactory, VariableTree)} explains + * why a different type is used. + * + * @param lhsTree left-hand side of an assignment + * @return AnnotatedTypeMirror of {@code lhsTree} + */ + public AnnotatedTypeMirror getAnnotatedTypeLhsNoTypeVarDefault(Tree lhsTree) { + boolean old = this.shouldDefaultTypeVarLocals; + shouldDefaultTypeVarLocals = false; + AnnotatedTypeMirror type = getAnnotatedTypeLhs(lhsTree); + this.shouldDefaultTypeVarLocals = old; + return type; + } + + /** + * Whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This can be used by subclasses + * whenever the type of expression differs depending on whether it occurs on the left- or + * right-hand side of an assignment (e.g., in the presence of type refinements or for + * uninitialized fields in the Initialization Checker.) + * + * @see #isComputingAnnotatedTypeMirrorOfLhs() + */ + private boolean computingAnnotatedTypeMirrorOfLhs = false; + + /** + * Returns whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This controls which + * hierarchies' qualifiers are changed based on the receiver type and the declared annotations for + * a field. + * + * @return whether {@link #getAnnotatedTypeLhs(Tree)} is running right now + * @see #getAnnotatedTypeLhs(Tree) + */ + public boolean isComputingAnnotatedTypeMirrorOfLhs() { + return computingAnnotatedTypeMirrorOfLhs; + } + + /** + * Returns the type of a JavaExpression {@code expr} if it were evaluated before a tree {@code + * tree}. + * + *

This is used by {@link BaseTypeVisitor#visitMethodInvocation(MethodInvocationTree, Void)} to + * check the preconditions of method calls. + * + * @param expr the expression to type + * @param tree a tree + * @return the type of {@code expr} if it were evaluated before tree {@code tree} + */ + public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { + Store store = getStoreBefore(tree); + Value value = null; + if (CFAbstractStore.canInsertJavaExpression(expr)) { + value = store.getValue(expr); + } + Set annos = null; + if (value != null) { + annos = value.getAnnotations(); + } else { + // If there is no information in the store (possible if e.g., no refinement + // of the field has occurred), use top instead of automatically + // issuing a warning. This is not perfectly precise: for example, + // if jeExpr is a field it would be more precise to use the field's + // declared type rather than top. However, doing so would be unsound + // in at least three circumstances where the type of the field depends + // on the type of the receiver: (1) all fields in Nullness Checker, + // because of possibility that the receiver is under initialization, + // (2) polymorphic fields, and (3) fields whose type is a type variable. + // Using top here instead means that the method is always sound; + // a subclass can then override it with a more precise implementation. + annos = getQualifierHierarchy().getTopAnnotations(); + } + + AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(expr.getType(), this, false); + res.addAnnotations(annos); + return res; + } + + /** + * Returns the type of a left-hand side of an assignment. + * + *

The default implementation returns the type without considering dataflow type refinement. + * Subclass can override this method and add additional logic for computing the type of a LHS. + * + * @param lhsTree left-hand side of an assignment + * @return AnnotatedTypeMirror of {@code lhsTree} + */ + public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { + boolean oldUseFlow = useFlow; + boolean oldShouldCache = shouldCache; + boolean oldComputingAnnotatedTypeMirrorOfLhs = computingAnnotatedTypeMirrorOfLhs; + useFlow = false; + // Don't cache the result because getAnnotatedType(lhsTree) could + // be called from elsewhere and would expect flow-sensitive type refinements. + shouldCache = false; + computingAnnotatedTypeMirrorOfLhs = true; + + AnnotatedTypeMirror res; + switch (lhsTree.getKind()) { + case VARIABLE: + boolean isVarTree = TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) lhsTree); + if (isVarTree) { + // If this variable is declared using `var`, re-enable caching to avoid + // re-computing the initializer expression type. + shouldCache = oldShouldCache; + } + res = getAnnotatedType(lhsTree); + // Value of shouldCache no longer used below, so no need to reset. + break; + case IDENTIFIER: + Element elt = TreeUtils.elementFromTree(lhsTree); + if (elt != null) { + Tree decl = declarationFromElement(elt); + if (decl != null + && decl.getKind() == Tree.Kind.VARIABLE + && TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) decl)) { + // If this identifier accesses a variable that was declared using `var`, + // re-enable caching to avoid re-computing the initializer expression type. + shouldCache = oldShouldCache; + } + } + res = getAnnotatedType(lhsTree); + // Value of shouldCache no longer used below, so no need to reset. + break; + case MEMBER_SELECT: + case ARRAY_ACCESS: + res = getAnnotatedType(lhsTree); + break; + case PARENTHESIZED: + res = getAnnotatedTypeLhs(TreeUtils.withoutParens((ExpressionTree) lhsTree)); + break; + default: + if (TreeUtils.isTypeTree(lhsTree)) { + // lhsTree is a type tree at the pseudo assignment of a returned expression to + // declared return type. + res = getAnnotatedType(lhsTree); } else { - // If there is no information in the store (possible if e.g., no refinement - // of the field has occurred), use top instead of automatically - // issuing a warning. This is not perfectly precise: for example, - // if jeExpr is a field it would be more precise to use the field's - // declared type rather than top. However, doing so would be unsound - // in at least three circumstances where the type of the field depends - // on the type of the receiver: (1) all fields in Nullness Checker, - // because of possibility that the receiver is under initialization, - // (2) polymorphic fields, and (3) fields whose type is a type variable. - // Using top here instead means that the method is always sound; - // a subclass can then override it with a more precise implementation. - annos = getQualifierHierarchy().getTopAnnotations(); - } - - AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(expr.getType(), this, false); - res.addAnnotations(annos); - return res; - } - - /** - * Returns the type of a left-hand side of an assignment. - * - *

The default implementation returns the type without considering dataflow type refinement. - * Subclass can override this method and add additional logic for computing the type of a LHS. - * - * @param lhsTree left-hand side of an assignment - * @return AnnotatedTypeMirror of {@code lhsTree} - */ - public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { - boolean oldUseFlow = useFlow; - boolean oldShouldCache = shouldCache; - boolean oldComputingAnnotatedTypeMirrorOfLhs = computingAnnotatedTypeMirrorOfLhs; - useFlow = false; - // Don't cache the result because getAnnotatedType(lhsTree) could - // be called from elsewhere and would expect flow-sensitive type refinements. - shouldCache = false; - computingAnnotatedTypeMirrorOfLhs = true; - - AnnotatedTypeMirror res; - switch (lhsTree.getKind()) { - case VARIABLE: - boolean isVarTree = - TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) lhsTree); - if (isVarTree) { - // If this variable is declared using `var`, re-enable caching to avoid - // re-computing the initializer expression type. - shouldCache = oldShouldCache; - } - res = getAnnotatedType(lhsTree); - // Value of shouldCache no longer used below, so no need to reset. - break; - case IDENTIFIER: - Element elt = TreeUtils.elementFromTree(lhsTree); - if (elt != null) { - Tree decl = declarationFromElement(elt); - if (decl != null - && decl.getKind() == Tree.Kind.VARIABLE - && TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) decl)) { - // If this identifier accesses a variable that was declared using `var`, - // re-enable caching to avoid re-computing the initializer expression type. - shouldCache = oldShouldCache; - } - } - res = getAnnotatedType(lhsTree); - // Value of shouldCache no longer used below, so no need to reset. - break; - case MEMBER_SELECT: - case ARRAY_ACCESS: - res = getAnnotatedType(lhsTree); - break; - case PARENTHESIZED: - res = getAnnotatedTypeLhs(TreeUtils.withoutParens((ExpressionTree) lhsTree)); - break; - default: - if (TreeUtils.isTypeTree(lhsTree)) { - // lhsTree is a type tree at the pseudo assignment of a returned expression to - // declared return type. - res = getAnnotatedType(lhsTree); - } else { - throw new BugInCF( - "GenericAnnotatedTypeFactory: Unexpected tree passed to" - + " getAnnotatedTypeLhs. lhsTree: " - + lhsTree - + " Tree.Kind: " - + lhsTree.getKind()); - } - } - useFlow = oldUseFlow; - shouldCache = oldShouldCache; - computingAnnotatedTypeMirrorOfLhs = oldComputingAnnotatedTypeMirrorOfLhs; - return res; - } - - /** - * As long as everUseFlow is true, always enable flow refinement for the receiver. This method - * is an implementation detail and visible inside the package only. See the comment in this - * testcase for more details framework/tests/viewpointtest/TestGetAnnotatedLhs.java. - * - * @see #getAnnotatedType(Tree) - * @see #getAnnotatedTypeLhs(Tree) - * @param tree an expression tree - * @return the refined type of the expression tree - */ - /*package-private*/ AnnotatedTypeMirror getAnnotatedTypeWithReceiverRefinement(Tree tree) { - boolean oldUseFlow = useFlow; - useFlow = everUseFlow; - AnnotatedTypeMirror result = getAnnotatedType(tree); - useFlow = oldUseFlow; - return result; - } - - /** - * Returns the type of a varargs array of a method invocation or a constructor invocation. - * Returns null only if private field {@code useFlow} is false. - * - * @param tree a method invocation or a constructor invocation - * @return AnnotatedTypeMirror of varargs array for a method or constructor invocation {@code - * tree}; returns null if private field {@code useFlow} is false - */ - public @Nullable AnnotatedTypeMirror getAnnotatedTypeVarargsArray(Tree tree) { - if (!useFlow) { - return null; - } - - // Get the synthetic NewArray tree that dataflow creates as the last argument of a call to a - // vararg method. Do this by getting the MethodInvocationNode to which "tree" maps. The last - // argument node of the MethodInvocationNode stores the synthetic NewArray tree. - List args; - switch (tree.getKind()) { - case METHOD_INVOCATION: - args = getFirstNodeOfKindForTree(tree, MethodInvocationNode.class).getArguments(); - break; - case NEW_CLASS: - args = getFirstNodeOfKindForTree(tree, ObjectCreationNode.class).getArguments(); - break; - default: - throw new BugInCF("Unexpected kind of tree: " + tree); - } - - assert !args.isEmpty() : "Arguments are empty"; - Node varargsArray = args.get(args.size() - 1); - AnnotatedTypeMirror varargtype = getAnnotatedType(varargsArray.getTree()); - return varargtype; - } - - /** - * Returns the type of {@code v + 1} or {@code v - 1} where {@code v} is the expression in the - * postfixed increment or decrement expression. - * - * @param tree a postfixed increment or decrement tree - * @return AnnotatedTypeMirror of a right-hand side of an assignment for unary operation - */ - public AnnotatedTypeMirror getAnnotatedTypeRhsUnaryAssign(UnaryTree tree) { - if (!useFlow) { - return getAnnotatedType(tree); - } - BinaryTree binaryTree = flowResult.getPostfixBinaryTree(tree); - return getAnnotatedType(binaryTree); - } - - @Override - public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { - ParameterizedExecutableType mType = super.constructorFromUse(tree); - AnnotatedExecutableType method = mType.executableType; - dependentTypesHelper.atConstructorInvocation(method, tree); - return mType; - } - - @Override - protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type) { - poly.resolve(tree, type); - } - - @Override - public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { - AnnotatedTypeMirror returnType = super.getMethodReturnType(m); - dependentTypesHelper.atMethodBody(returnType, m); - return returnType; - } - - @Override - public void addDefaultAnnotations(AnnotatedTypeMirror type) { - addAnnotationsFromDefaultForType(null, type); - typeAnnotator.visit(type, null); - defaults.annotate((Element) null, type); - } - - /** - * Removes all primary annotations on a copy of the type and calculates the default annotations - * that apply to the copied type, without type refinements. - * - * @param tree tree where the type is used - * @param type type to determine the defaulted version for - * @return the annotated type mirror with default annotations - */ - public AnnotatedTypeMirror getDefaultAnnotations(Tree tree, AnnotatedTypeMirror type) { - AnnotatedTypeMirror copy = type.deepCopy(); - copy.removeAnnotations(type.getAnnotations()); - addComputedTypeAnnotationsWithoutFlow(tree, copy); - return copy; - } - - /** - * Like {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding - * implementations typically simply pass the boolean to calls to super. - * - * @param tree an AST node - * @param type the type obtained from tree - * @param iUseFlow whether to use information from dataflow analysis - * @deprecated use {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} or {@link - * #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror)} if you want to add - * computed type annotations without using flow information - */ - @Deprecated // 2024-07-07 - @SuppressWarnings("unused") - protected void addComputedTypeAnnotations( - Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { - addComputedTypeAnnotationsWithoutFlow(tree, type); - } - - /** - * A helper method to add computed type annotations to a type without using flow information. - * - *

This method is final; override {@link #addComputedTypeAnnotations(Tree, - * AnnotatedTypeMirror)} instead. - * - * @param tree an AST node - * @param type the type obtained from tree - * @see #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror) - */ - protected final void addComputedTypeAnnotationsWithoutFlow( - Tree tree, AnnotatedTypeMirror type) { - boolean oldUseflow = useFlow; - useFlow = false; - addComputedTypeAnnotations(tree, type); - useFlow = oldUseflow; - } - - /** - * {@inheritDoc} - * - *

This method adds defaults and flow-sensitive type refinements. - * - * @see #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror) - */ - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - if (this.getRoot() == null && ajavaTypes.isParsing()) { - return; - } - assert this.getRoot() != null - : "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: " - + " root needs to be set when used on trees; factory: " - + this.getClass(); - - String thisClass = null; - String treeString = null; - if (debug) { - thisClass = this.getClass().getSimpleName(); - if (thisClass.endsWith("AnnotatedTypeFactory")) { - thisClass = - thisClass.substring( - 0, thisClass.length() - "AnnotatedTypeFactory".length()); - } - treeString = TreeUtils.toStringTruncated(tree, 60); - } - log( - "%s GATF.addComputedTypeAnnotations#1(%s, %s, %s)%n", - thisClass, treeString, type, this.useFlow); - if (!TreeUtils.isExpressionTree(tree)) { - // Don't apply defaults to expressions. Their types may be computed from subexpressions - // in treeAnnotator. - addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); - log("%s GATF.addComputedTypeAnnotations#2(%s, %s)%n", thisClass, treeString, type); - } - applyQualifierParameterDefaults(tree, type); - log("%s GATF.addComputedTypeAnnotations#3(%s, %s)%n", thisClass, treeString, type); - treeAnnotator.visit(tree, type); - log( - "%s GATF.addComputedTypeAnnotations#4(%s, %s)%n treeAnnotator=%s%n", - thisClass, treeString, type, treeAnnotator); - if (TreeUtils.isExpressionTree(tree)) { - // If a tree annotator did not add a type, add the DefaultForUse default. - addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); - log("%s GATF.addComputedTypeAnnotations#5(%s, %s)%n", thisClass, treeString, type); - } - typeAnnotator.visit(type, null); - log( - "%s GATF.addComputedTypeAnnotations#6(%s, %s)%n typeAnnotator=%s%n", - thisClass, treeString, type, typeAnnotator); - defaults.annotate(tree, type); - log("%s GATF.addComputedTypeAnnotations#7(%s, %s)%n", thisClass, treeString, type); - - if (this.useFlow) { - Value inferred = getInferredValueFor(tree); - if (inferred != null) { - applyInferredAnnotations(type, inferred); - log( - "%s GATF.addComputedTypeAnnotations#8(%s, %s), inferred=%s%n", - thisClass, treeString, type, inferred); - } - } + throw new BugInCF( + "GenericAnnotatedTypeFactory: Unexpected tree passed to" + + " getAnnotatedTypeLhs. lhsTree: " + + lhsTree + + " Tree.Kind: " + + lhsTree.getKind()); + } + } + useFlow = oldUseFlow; + shouldCache = oldShouldCache; + computingAnnotatedTypeMirrorOfLhs = oldComputingAnnotatedTypeMirrorOfLhs; + return res; + } + + /** + * As long as everUseFlow is true, always enable flow refinement for the receiver. This method is + * an implementation detail and visible inside the package only. See the comment in this testcase + * for more details framework/tests/viewpointtest/TestGetAnnotatedLhs.java. + * + * @see #getAnnotatedType(Tree) + * @see #getAnnotatedTypeLhs(Tree) + * @param tree an expression tree + * @return the refined type of the expression tree + */ + /*package-private*/ AnnotatedTypeMirror getAnnotatedTypeWithReceiverRefinement(Tree tree) { + boolean oldUseFlow = useFlow; + useFlow = everUseFlow; + AnnotatedTypeMirror result = getAnnotatedType(tree); + useFlow = oldUseFlow; + return result; + } + + /** + * Returns the type of a varargs array of a method invocation or a constructor invocation. Returns + * null only if private field {@code useFlow} is false. + * + * @param tree a method invocation or a constructor invocation + * @return AnnotatedTypeMirror of varargs array for a method or constructor invocation {@code + * tree}; returns null if private field {@code useFlow} is false + */ + public @Nullable AnnotatedTypeMirror getAnnotatedTypeVarargsArray(Tree tree) { + if (!useFlow) { + return null; + } + + // Get the synthetic NewArray tree that dataflow creates as the last argument of a call to a + // vararg method. Do this by getting the MethodInvocationNode to which "tree" maps. The last + // argument node of the MethodInvocationNode stores the synthetic NewArray tree. + List args; + switch (tree.getKind()) { + case METHOD_INVOCATION: + args = getFirstNodeOfKindForTree(tree, MethodInvocationNode.class).getArguments(); + break; + case NEW_CLASS: + args = getFirstNodeOfKindForTree(tree, ObjectCreationNode.class).getArguments(); + break; + default: + throw new BugInCF("Unexpected kind of tree: " + tree); + } + + assert !args.isEmpty() : "Arguments are empty"; + Node varargsArray = args.get(args.size() - 1); + AnnotatedTypeMirror varargtype = getAnnotatedType(varargsArray.getTree()); + return varargtype; + } + + /** + * Returns the type of {@code v + 1} or {@code v - 1} where {@code v} is the expression in the + * postfixed increment or decrement expression. + * + * @param tree a postfixed increment or decrement tree + * @return AnnotatedTypeMirror of a right-hand side of an assignment for unary operation + */ + public AnnotatedTypeMirror getAnnotatedTypeRhsUnaryAssign(UnaryTree tree) { + if (!useFlow) { + return getAnnotatedType(tree); + } + BinaryTree binaryTree = flowResult.getPostfixBinaryTree(tree); + return getAnnotatedType(binaryTree); + } + + @Override + public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + ParameterizedExecutableType mType = super.constructorFromUse(tree); + AnnotatedExecutableType method = mType.executableType; + dependentTypesHelper.atConstructorInvocation(method, tree); + return mType; + } + + @Override + protected void constructorFromUsePreSubstitution( + NewClassTree tree, AnnotatedExecutableType type) { + poly.resolve(tree, type); + } + + @Override + public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { + AnnotatedTypeMirror returnType = super.getMethodReturnType(m); + dependentTypesHelper.atMethodBody(returnType, m); + return returnType; + } + + @Override + public void addDefaultAnnotations(AnnotatedTypeMirror type) { + addAnnotationsFromDefaultForType(null, type); + typeAnnotator.visit(type, null); + defaults.annotate((Element) null, type); + } + + /** + * Removes all primary annotations on a copy of the type and calculates the default annotations + * that apply to the copied type, without type refinements. + * + * @param tree tree where the type is used + * @param type type to determine the defaulted version for + * @return the annotated type mirror with default annotations + */ + public AnnotatedTypeMirror getDefaultAnnotations(Tree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror copy = type.deepCopy(); + copy.removeAnnotations(type.getAnnotations()); + addComputedTypeAnnotationsWithoutFlow(tree, copy); + return copy; + } + + /** + * Like {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding implementations + * typically simply pass the boolean to calls to super. + * + * @param tree an AST node + * @param type the type obtained from tree + * @param iUseFlow whether to use information from dataflow analysis + * @deprecated use {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} or {@link + * #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror)} if you want to add + * computed type annotations without using flow information + */ + @Deprecated // 2024-07-07 + @SuppressWarnings("unused") + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { + addComputedTypeAnnotationsWithoutFlow(tree, type); + } + + /** + * A helper method to add computed type annotations to a type without using flow information. + * + *

This method is final; override {@link #addComputedTypeAnnotations(Tree, + * AnnotatedTypeMirror)} instead. + * + * @param tree an AST node + * @param type the type obtained from tree + * @see #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror) + */ + protected final void addComputedTypeAnnotationsWithoutFlow(Tree tree, AnnotatedTypeMirror type) { + boolean oldUseflow = useFlow; + useFlow = false; + addComputedTypeAnnotations(tree, type); + useFlow = oldUseflow; + } + + /** + * {@inheritDoc} + * + *

This method adds defaults and flow-sensitive type refinements. + * + * @see #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror) + */ + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + if (this.getRoot() == null && ajavaTypes.isParsing()) { + return; + } + assert this.getRoot() != null + : "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: " + + " root needs to be set when used on trees; factory: " + + this.getClass(); + + String thisClass = null; + String treeString = null; + if (debug) { + thisClass = this.getClass().getSimpleName(); + if (thisClass.endsWith("AnnotatedTypeFactory")) { + thisClass = thisClass.substring(0, thisClass.length() - "AnnotatedTypeFactory".length()); + } + treeString = TreeUtils.toStringTruncated(tree, 60); + } + log( + "%s GATF.addComputedTypeAnnotations#1(%s, %s, %s)%n", + thisClass, treeString, type, this.useFlow); + if (!TreeUtils.isExpressionTree(tree)) { + // Don't apply defaults to expressions. Their types may be computed from subexpressions + // in treeAnnotator. + addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); + log("%s GATF.addComputedTypeAnnotations#2(%s, %s)%n", thisClass, treeString, type); + } + applyQualifierParameterDefaults(tree, type); + log("%s GATF.addComputedTypeAnnotations#3(%s, %s)%n", thisClass, treeString, type); + treeAnnotator.visit(tree, type); + log( + "%s GATF.addComputedTypeAnnotations#4(%s, %s)%n treeAnnotator=%s%n", + thisClass, treeString, type, treeAnnotator); + if (TreeUtils.isExpressionTree(tree)) { + // If a tree annotator did not add a type, add the DefaultForUse default. + addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); + log("%s GATF.addComputedTypeAnnotations#5(%s, %s)%n", thisClass, treeString, type); + } + typeAnnotator.visit(type, null); + log( + "%s GATF.addComputedTypeAnnotations#6(%s, %s)%n typeAnnotator=%s%n", + thisClass, treeString, type, typeAnnotator); + defaults.annotate(tree, type); + log("%s GATF.addComputedTypeAnnotations#7(%s, %s)%n", thisClass, treeString, type); + + if (this.useFlow) { + Value inferred = getInferredValueFor(tree); + if (inferred != null) { + applyInferredAnnotations(type, inferred); log( - "%s GATF.addComputedTypeAnnotations#9(%s, %s, %s) done%n", - thisClass, treeString, type, this.useFlow); - } - - /** - * Flow analysis will be performed if all of the following are true. - * - *

    - *
  • {@code tree} is a {@link ClassTree} - *
  • Flow analysis has not already been performed on {@code tree} - *
- * - * @param tree the tree to check and possibly perform flow analysis on - */ - protected void checkAndPerformFlowAnalysis(Tree tree) { - // For performance reasons, we require that getAnnotatedType is called - // on the ClassTree before it's called on any code contained in the class, - // so that we can perform flow analysis on the class. Previously we - // used TreePath.getPath to find enclosing classes, but that call - // alone consumed more than 10% of execution time. See - // BaseTypeVisitor.visitClass for the call to getAnnotatedType that - // triggers analysis. - if (tree instanceof ClassTree) { - ClassTree classTree = (ClassTree) tree; - if (!scannedClasses.containsKey(classTree)) { - performFlowAnalysis(classTree); - } - } + "%s GATF.addComputedTypeAnnotations#8(%s, %s), inferred=%s%n", + thisClass, treeString, type, inferred); + } } - - /** - * Returns the inferred value (by the org.checkerframework.dataflow analysis) for a given tree. - * - * @param tree the tree - * @return the value for the tree, if one has been computed by dataflow. If no value has been - * computed, null is returned (this does not mean that no value will ever be computed for - * the given tree). - */ - public @Nullable Value getInferredValueFor(Tree tree) { - if (tree == null) { - throw new BugInCF( - "GenericAnnotatedTypeFactory.getInferredValueFor called with null tree"); - } - if (!analysis.isRunning() && flowResult == null) { - // When parsing stub or ajava files, the analysis is not running (it has not yet - // started), and flowResult is null (no analysis has occurred). Instead of attempting to - // find a non-existent inferred type, return null. - return null; - } - Value as = null; - if (analysis.isRunning()) { - as = analysis.getValue(tree); - } - if (as == null) { - as = flowResult.getValue(tree); - } - return as; - } - - /** - * Applies the annotations inferred by the org.checkerframework.dataflow analysis to the type - * {@code type}. - * - * @param type the type to modify - * @param inferred the inferred annotations to apply - */ - protected void applyInferredAnnotations(AnnotatedTypeMirror type, Value inferred) { - inferredTypesApplier.applyInferredType( - type, inferred.getAnnotations(), inferred.getUnderlyingType()); - } - - /** - * Applies defaults for types in a class with an qualifier parameter. - * - *

Within a class with {@code @HasQualifierParameter}, types with that class default to the - * polymorphic qualifier rather than the typical default. Local variables with a type that has a - * qualifier parameter are initialized to the type of their initializer, rather than the default - * for local variables. - * - * @param tree a Tree whose type is {@code type} - * @param type where the defaults are applied - */ - protected void applyQualifierParameterDefaults(Tree tree, AnnotatedTypeMirror type) { - applyQualifierParameterDefaults(TreeUtils.elementFromTree(tree), type); - } - - /** - * Applies defaults for types in a class with an qualifier parameter. - * - *

Within a class with {@code @HasQualifierParameter}, types with that class default to the - * polymorphic qualifier rather than the typical default. Local variables with a type that has a - * qualifier parameter are initialized to the type of their initializer, rather than the default - * for local variables. - * - * @param elt an Element whose type is {@code type} - * @param type where the defaults are applied - */ - protected void applyQualifierParameterDefaults( - @Nullable Element elt, AnnotatedTypeMirror type) { - if (elt == null) { - return; - } - switch (elt.getKind()) { - case CONSTRUCTOR: - case METHOD: - case FIELD: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - case LOCAL_VARIABLE: - case PARAMETER: - break; - default: - return; - } - - applyLocalVariableQualifierParameterDefaults(elt, type); - - TypeElement enclosingClass = ElementUtils.enclosingTypeElement(elt); - AnnotationMirrorSet tops; - if (enclosingClass != null) { - tops = getQualifierParameterHierarchies(enclosingClass); - } else { - return; - } - if (tops.isEmpty()) { - return; - } - AnnotationMirrorSet polyWithQualParam = new AnnotationMirrorSet(); - for (AnnotationMirror top : tops) { - AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); - if (poly != null) { - polyWithQualParam.add(poly); - } - } - new TypeAnnotator(this) { - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - if (type.getUnderlyingType().asElement().equals(enclosingClass)) { - type.addMissingAnnotations(polyWithQualParam); - } - return super.visitDeclared(type, aVoid); - } - }.visit(type); - } - - /** - * Defaults local variables with types that have a qualifier parameter to the type of their - * initializer, if an initializer is present. Does nothing for local variables with no - * initializer. - * - * @param elt an Element whose type is {@code type} - * @param type where the defaults are applied - */ - private void applyLocalVariableQualifierParameterDefaults( - Element elt, AnnotatedTypeMirror type) { - if (!ElementUtils.isLocalVariable(elt) - || getQualifierParameterHierarchies(type).isEmpty() - || variablesUnderInitialization.contains(elt)) { - return; - } - - Tree declTree = declarationFromElement(elt); - if (declTree == null || declTree.getKind() != Tree.Kind.VARIABLE) { - return; - } - - ExpressionTree initializer = ((VariableTree) declTree).getInitializer(); - if (initializer == null) { - return; - } - - VariableElement variableElt = (VariableElement) elt; - variablesUnderInitialization.add(variableElt); - AnnotatedTypeMirror initializerType; - if (shouldCache && initializerCache.containsKey(initializer)) { - initializerType = initializerCache.get(initializer); - } else { - // When this method is called by getAnnotatedTypeLhs, flow is turned off. - // Turn it back on so the type of the initializer is the refined type. - boolean oldUseFlow = useFlow; - useFlow = everUseFlow; - try { - initializerType = getAnnotatedType(initializer); - } finally { - useFlow = oldUseFlow; - } - } - - AnnotationMirrorSet qualParamTypes = new AnnotationMirrorSet(); - for (AnnotationMirror initializerAnnotation : initializerType.getAnnotations()) { - if (hasQualifierParameterInHierarchy( - type, qualHierarchy.getTopAnnotation(initializerAnnotation))) { - qualParamTypes.add(initializerAnnotation); - } - } - - type.addMissingAnnotations(qualParamTypes); - variablesUnderInitialization.remove(variableElt); - if (shouldCache) { - initializerCache.put(initializer, initializerType); - } + log( + "%s GATF.addComputedTypeAnnotations#9(%s, %s, %s) done%n", + thisClass, treeString, type, this.useFlow); + } + + /** + * Flow analysis will be performed if all of the following are true. + * + *

    + *
  • {@code tree} is a {@link ClassTree} + *
  • Flow analysis has not already been performed on {@code tree} + *
+ * + * @param tree the tree to check and possibly perform flow analysis on + */ + protected void checkAndPerformFlowAnalysis(Tree tree) { + // For performance reasons, we require that getAnnotatedType is called + // on the ClassTree before it's called on any code contained in the class, + // so that we can perform flow analysis on the class. Previously we + // used TreePath.getPath to find enclosing classes, but that call + // alone consumed more than 10% of execution time. See + // BaseTypeVisitor.visitClass for the call to getAnnotatedType that + // triggers analysis. + if (tree instanceof ClassTree) { + ClassTree classTree = (ClassTree) tree; + if (!scannedClasses.containsKey(classTree)) { + performFlowAnalysis(classTree); + } } - - /** - * To add annotations to the type of method or constructor parameters, add a {@link - * TypeAnnotator} using {@link #createTypeAnnotator()} and see the comment in {@link - * TypeAnnotator#visitExecutable(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType, - * Void)}. - * - * @param elt an element - * @param type the type obtained from {@code elt} - */ - @Override - public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - addAnnotationsFromDefaultForType(elt, type); - applyQualifierParameterDefaults(elt, type); - typeAnnotator.visit(type, null); - defaults.annotate(elt, type); - dependentTypesHelper.atLocalVariable(type, elt); - } - - @Override - public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { - ParameterizedExecutableType mType = super.methodFromUse(tree); - AnnotatedExecutableType method = mType.executableType; - dependentTypesHelper.atMethodInvocation(method, tree); - return mType; - } - - @Override - public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { - super.methodFromUsePreSubstitution(tree, type); - if (tree instanceof MethodInvocationTree) { - poly.resolve((MethodInvocationTree) tree, type); - } + } + + /** + * Returns the inferred value (by the org.checkerframework.dataflow analysis) for a given tree. + * + * @param tree the tree + * @return the value for the tree, if one has been computed by dataflow. If no value has been + * computed, null is returned (this does not mean that no value will ever be computed for the + * given tree). + */ + public @Nullable Value getInferredValueFor(Tree tree) { + if (tree == null) { + throw new BugInCF("GenericAnnotatedTypeFactory.getInferredValueFor called with null tree"); + } + if (!analysis.isRunning() && flowResult == null) { + // When parsing stub or ajava files, the analysis is not running (it has not yet + // started), and flowResult is null (no analysis has occurred). Instead of attempting to + // find a non-existent inferred type, return null. + return null; + } + Value as = null; + if (analysis.isRunning()) { + as = analysis.getValue(tree); + } + if (as == null) { + as = flowResult.getValue(tree); + } + return as; + } + + /** + * Applies the annotations inferred by the org.checkerframework.dataflow analysis to the type + * {@code type}. + * + * @param type the type to modify + * @param inferred the inferred annotations to apply + */ + protected void applyInferredAnnotations(AnnotatedTypeMirror type, Value inferred) { + inferredTypesApplier.applyInferredType( + type, inferred.getAnnotations(), inferred.getUnderlyingType()); + } + + /** + * Applies defaults for types in a class with an qualifier parameter. + * + *

Within a class with {@code @HasQualifierParameter}, types with that class default to the + * polymorphic qualifier rather than the typical default. Local variables with a type that has a + * qualifier parameter are initialized to the type of their initializer, rather than the default + * for local variables. + * + * @param tree a Tree whose type is {@code type} + * @param type where the defaults are applied + */ + protected void applyQualifierParameterDefaults(Tree tree, AnnotatedTypeMirror type) { + applyQualifierParameterDefaults(TreeUtils.elementFromTree(tree), type); + } + + /** + * Applies defaults for types in a class with an qualifier parameter. + * + *

Within a class with {@code @HasQualifierParameter}, types with that class default to the + * polymorphic qualifier rather than the typical default. Local variables with a type that has a + * qualifier parameter are initialized to the type of their initializer, rather than the default + * for local variables. + * + * @param elt an Element whose type is {@code type} + * @param type where the defaults are applied + */ + protected void applyQualifierParameterDefaults(@Nullable Element elt, AnnotatedTypeMirror type) { + if (elt == null) { + return; + } + switch (elt.getKind()) { + case CONSTRUCTOR: + case METHOD: + case FIELD: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + case LOCAL_VARIABLE: + case PARAMETER: + break; + default: + return; + } + + applyLocalVariableQualifierParameterDefaults(elt, type); + + TypeElement enclosingClass = ElementUtils.enclosingTypeElement(elt); + AnnotationMirrorSet tops; + if (enclosingClass != null) { + tops = getQualifierParameterHierarchies(enclosingClass); + } else { + return; + } + if (tops.isEmpty()) { + return; + } + AnnotationMirrorSet polyWithQualParam = new AnnotationMirrorSet(); + for (AnnotationMirror top : tops) { + AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); + if (poly != null) { + polyWithQualParam.add(poly); + } } - - @Override - public List typeVariablesFromUse( - AnnotatedDeclaredType type, TypeElement element) { - List f = super.typeVariablesFromUse(type, element); - dependentTypesHelper.atParameterizedTypeUse(f, element); - return f; - } - - /** - * Returns the empty store. - * - * @return the empty store - */ - public Store getEmptyStore() { - return emptyStore; - } - - /** - * Returns the type factory used by a subchecker. Throws an exception if no matching subchecker - * was found or if the type factory is null. The caller must know the exact checker class to - * request. - * - *

Because the visitor path is copied, call this method each time a subfactory is needed - * rather than store the returned subfactory in a field. - * - * @param subCheckerClass the exact class of the subchecker - * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} - * @return the AnnotatedTypeFactory of the subchecker; never null - * @see #getTypeFactoryOfSubcheckerOrNull - */ - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public final > T getTypeFactoryOfSubchecker( - Class subCheckerClass) { - T result = getTypeFactoryOfSubcheckerOrNull(subCheckerClass); - if (result == null) { - throw new TypeSystemError( - "In " - + this.getClass().getSimpleName() - + ", no type factory found for " - + subCheckerClass.getSimpleName() - + "."); + new TypeAnnotator(this) { + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + if (type.getUnderlyingType().asElement().equals(enclosingClass)) { + type.addMissingAnnotations(polyWithQualParam); } - return result; + return super.visitDeclared(type, aVoid); + } + }.visit(type); + } + + /** + * Defaults local variables with types that have a qualifier parameter to the type of their + * initializer, if an initializer is present. Does nothing for local variables with no + * initializer. + * + * @param elt an Element whose type is {@code type} + * @param type where the defaults are applied + */ + private void applyLocalVariableQualifierParameterDefaults(Element elt, AnnotatedTypeMirror type) { + if (!ElementUtils.isLocalVariable(elt) + || getQualifierParameterHierarchies(type).isEmpty() + || variablesUnderInitialization.contains(elt)) { + return; + } + + Tree declTree = declarationFromElement(elt); + if (declTree == null || declTree.getKind() != Tree.Kind.VARIABLE) { + return; + } + + ExpressionTree initializer = ((VariableTree) declTree).getInitializer(); + if (initializer == null) { + return; + } + + VariableElement variableElt = (VariableElement) elt; + variablesUnderInitialization.add(variableElt); + AnnotatedTypeMirror initializerType; + if (shouldCache && initializerCache.containsKey(initializer)) { + initializerType = initializerCache.get(initializer); + } else { + // When this method is called by getAnnotatedTypeLhs, flow is turned off. + // Turn it back on so the type of the initializer is the refined type. + boolean oldUseFlow = useFlow; + useFlow = everUseFlow; + try { + initializerType = getAnnotatedType(initializer); + } finally { + useFlow = oldUseFlow; + } } - /** - * Returns the type factory used by a subchecker. Returns null if no matching subchecker was - * found or if the type factory is null. The caller must know the exact checker class to - * request. - * - *

Because the visitor path is copied, call this method each time a subfactory is needed - * rather than store the returned subfactory in a field. - * - * @param subCheckerClass the exact class of the subchecker - * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} - * @return the AnnotatedTypeFactory of the subchecker or null if no subchecker exists - * @see #getTypeFactoryOfSubchecker - */ - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public > - @Nullable T getTypeFactoryOfSubcheckerOrNull( - Class subCheckerClass) { - BaseTypeChecker subchecker = checker.getSubchecker(subCheckerClass); - if (subchecker == null) { - return null; - } - - @SuppressWarnings( - "unchecked" // This might not be safe, but the caller of the method should use the - // correct type. - ) - T subFactory = (T) subchecker.getTypeFactory(); - if (subFactory != null) { - subFactory.setVisitorTreePath(getVisitorTreePath()); - } - return subFactory; - } - - /** - * Should the local variable default annotation be applied to type variables? - * - *

It is initialized to true if data flow is used by the checker. It is set to false when - * getting the assignment context for type argument inference. - * - * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault - * @return shouldDefaultTypeVarLocals - */ - public boolean getShouldDefaultTypeVarLocals() { - return shouldDefaultTypeVarLocals; - } - - /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ - protected final CFGVisualizer cfgVisualizer; - - /** - * Create a new CFGVisualizer. - * - * @return a new CFGVisualizer, or null if none will be used on this run - */ - protected @Nullable CFGVisualizer createCFGVisualizer() { - if (checker.hasOption("flowdotdir")) { - String flowdotdir = checker.getOption("flowdotdir"); - if (flowdotdir.isEmpty()) { - throw new UserError("Empty string provided for -Aflowdotdir command-line argument"); - } - boolean verbose = checker.hasOption("verbosecfg"); - - Map args = new HashMap<>(2); - args.put("outdir", flowdotdir); - args.put("verbose", verbose); - args.put("checkerName", getCheckerName()); - - CFGVisualizer res = new DOTCFGVisualizer<>(); - res.init(args); - return res; - } else if (checker.hasOption("cfgviz")) { - List opts = checker.getStringsOption("cfgviz", ','); - if (opts.isEmpty()) { - throw new UserError( - "-Acfgviz specified without arguments, should be" - + " -Acfgviz=VizClassName[,opts,...]"); - } - String vizClassName = opts.get(0); - if (!Signatures.isBinaryName(vizClassName)) { - throw new UserError( - "Bad -Acfgviz class name \"%s\", should be a binary name.", vizClassName); - } - - Map args = processCFGVisualizerOption(opts); - if (!args.containsKey("verbose")) { - boolean verbose = checker.hasOption("verbosecfg"); - args.put("verbose", verbose); - } - args.put("checkerName", getCheckerName()); - - CFGVisualizer res = - BaseTypeChecker.invokeConstructorFor(vizClassName, null, null); - res.init(args); - return res; - } - // Nobody expected to use cfgVisualizer if neither option given. - return null; - } - - /** - * A simple utility method to determine a short checker name to be used by CFG visualizations. - */ - private String getCheckerName() { - String checkerName = checker.getClass().getSimpleName(); - if (checkerName.endsWith("Checker")) { - checkerName = checkerName.substring(0, checkerName.length() - "Checker".length()); - } else if (checkerName.endsWith("Subchecker")) { - checkerName = checkerName.substring(0, checkerName.length() - "Subchecker".length()); - } - return checkerName; - } - - /** - * Parse keys or key-value pairs into a map from key to value (to true if no value is provided). - * - * @param opts the CFG visualization options - * @return a map that represents the options - */ - private Map processCFGVisualizerOption(List opts) { - Map res = new HashMap<>(CollectionsPlume.mapCapacity(opts.size() - 1)); - // Index 0 is the visualizer class name and can be ignored. - for (int i = 1; i < opts.size(); ++i) { - String opt = opts.get(i); - String[] split = opt.split("="); - switch (split.length) { - case 1: - res.put(split[0], true); - break; - case 2: - res.put(split[0], split[1]); - break; - default: - throw new UserError("Too many '=' in cfgviz option: " + opt); - } - } - return res; + AnnotationMirrorSet qualParamTypes = new AnnotationMirrorSet(); + for (AnnotationMirror initializerAnnotation : initializerType.getAnnotations()) { + if (hasQualifierParameterInHierarchy( + type, qualHierarchy.getTopAnnotation(initializerAnnotation))) { + qualParamTypes.add(initializerAnnotation); + } } - /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ - public CFGVisualizer getCFGVisualizer() { - return cfgVisualizer; - } + type.addMissingAnnotations(qualParamTypes); + variablesUnderInitialization.remove(variableElt); + if (shouldCache) { + initializerCache.put(initializer, initializerType); + } + } + + /** + * To add annotations to the type of method or constructor parameters, add a {@link TypeAnnotator} + * using {@link #createTypeAnnotator()} and see the comment in {@link + * TypeAnnotator#visitExecutable(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType, + * Void)}. + * + * @param elt an element + * @param type the type obtained from {@code elt} + */ + @Override + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + addAnnotationsFromDefaultForType(elt, type); + applyQualifierParameterDefaults(elt, type); + typeAnnotator.visit(type, null); + defaults.annotate(elt, type); + dependentTypesHelper.atLocalVariable(type, elt); + } + + @Override + public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { + ParameterizedExecutableType mType = super.methodFromUse(tree); + AnnotatedExecutableType method = mType.executableType; + dependentTypesHelper.atMethodInvocation(method, tree); + return mType; + } + + @Override + public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { + super.methodFromUsePreSubstitution(tree, type); + if (tree instanceof MethodInvocationTree) { + poly.resolve((MethodInvocationTree) tree, type); + } + } + + @Override + public List typeVariablesFromUse( + AnnotatedDeclaredType type, TypeElement element) { + List f = super.typeVariablesFromUse(type, element); + dependentTypesHelper.atParameterizedTypeUse(f, element); + return f; + } + + /** + * Returns the empty store. + * + * @return the empty store + */ + public Store getEmptyStore() { + return emptyStore; + } + + /** + * Returns the type factory used by a subchecker. Throws an exception if no matching subchecker + * was found or if the type factory is null. The caller must know the exact checker class to + * request. + * + *

Because the visitor path is copied, call this method each time a subfactory is needed rather + * than store the returned subfactory in a field. + * + * @param subCheckerClass the exact class of the subchecker + * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} + * @return the AnnotatedTypeFactory of the subchecker; never null + * @see #getTypeFactoryOfSubcheckerOrNull + */ + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public final > T getTypeFactoryOfSubchecker( + Class subCheckerClass) { + T result = getTypeFactoryOfSubcheckerOrNull(subCheckerClass); + if (result == null) { + throw new TypeSystemError( + "In " + + this.getClass().getSimpleName() + + ", no type factory found for " + + subCheckerClass.getSimpleName() + + "."); + } + return result; + } + + /** + * Returns the type factory used by a subchecker. Returns null if no matching subchecker was found + * or if the type factory is null. The caller must know the exact checker class to request. + * + *

Because the visitor path is copied, call this method each time a subfactory is needed rather + * than store the returned subfactory in a field. + * + * @param subCheckerClass the exact class of the subchecker + * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} + * @return the AnnotatedTypeFactory of the subchecker or null if no subchecker exists + * @see #getTypeFactoryOfSubchecker + */ + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public > + @Nullable T getTypeFactoryOfSubcheckerOrNull( + Class subCheckerClass) { + BaseTypeChecker subchecker = checker.getSubchecker(subCheckerClass); + if (subchecker == null) { + return null; + } + + @SuppressWarnings( + "unchecked" // This might not be safe, but the caller of the method should use the + // correct type. + ) + T subFactory = (T) subchecker.getTypeFactory(); + if (subFactory != null) { + subFactory.setVisitorTreePath(getVisitorTreePath()); + } + return subFactory; + } + + /** + * Should the local variable default annotation be applied to type variables? + * + *

It is initialized to true if data flow is used by the checker. It is set to false when + * getting the assignment context for type argument inference. + * + * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault + * @return shouldDefaultTypeVarLocals + */ + public boolean getShouldDefaultTypeVarLocals() { + return shouldDefaultTypeVarLocals; + } + + /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ + protected final CFGVisualizer cfgVisualizer; + + /** + * Create a new CFGVisualizer. + * + * @return a new CFGVisualizer, or null if none will be used on this run + */ + protected @Nullable CFGVisualizer createCFGVisualizer() { + if (checker.hasOption("flowdotdir")) { + String flowdotdir = checker.getOption("flowdotdir"); + if (flowdotdir.isEmpty()) { + throw new UserError("Empty string provided for -Aflowdotdir command-line argument"); + } + boolean verbose = checker.hasOption("verbosecfg"); + + Map args = new HashMap<>(2); + args.put("outdir", flowdotdir); + args.put("verbose", verbose); + args.put("checkerName", getCheckerName()); + + CFGVisualizer res = new DOTCFGVisualizer<>(); + res.init(args); + return res; + } else if (checker.hasOption("cfgviz")) { + List opts = checker.getStringsOption("cfgviz", ','); + if (opts.isEmpty()) { + throw new UserError( + "-Acfgviz specified without arguments, should be" + + " -Acfgviz=VizClassName[,opts,...]"); + } + String vizClassName = opts.get(0); + if (!Signatures.isBinaryName(vizClassName)) { + throw new UserError( + "Bad -Acfgviz class name \"%s\", should be a binary name.", vizClassName); + } - @Override - public void postAsMemberOf( - AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { - super.postAsMemberOf(type, owner, element); - if (element.getKind() == ElementKind.FIELD) { - poly.resolve(((VariableElement) element), owner, type); - } + Map args = processCFGVisualizerOption(opts); + if (!args.containsKey("verbose")) { + boolean verbose = checker.hasOption("verbosecfg"); + args.put("verbose", verbose); + } + args.put("checkerName", getCheckerName()); + + CFGVisualizer res = + BaseTypeChecker.invokeConstructorFor(vizClassName, null, null); + res.init(args); + return res; + } + // Nobody expected to use cfgVisualizer if neither option given. + return null; + } + + /** A simple utility method to determine a short checker name to be used by CFG visualizations. */ + private String getCheckerName() { + String checkerName = checker.getClass().getSimpleName(); + if (checkerName.endsWith("Checker")) { + checkerName = checkerName.substring(0, checkerName.length() - "Checker".length()); + } else if (checkerName.endsWith("Subchecker")) { + checkerName = checkerName.substring(0, checkerName.length() - "Subchecker".length()); + } + return checkerName; + } + + /** + * Parse keys or key-value pairs into a map from key to value (to true if no value is provided). + * + * @param opts the CFG visualization options + * @return a map that represents the options + */ + private Map processCFGVisualizerOption(List opts) { + Map res = new HashMap<>(CollectionsPlume.mapCapacity(opts.size() - 1)); + // Index 0 is the visualizer class name and can be ignored. + for (int i = 1; i < opts.size(); ++i) { + String opt = opts.get(i); + String[] split = opt.split("="); + switch (split.length) { + case 1: + res.put(split[0], true); + break; + case 2: + res.put(split[0], split[1]); + break; + default: + throw new UserError("Too many '=' in cfgviz option: " + opt); + } } - - /** - * Adds default qualifiers based on the underlying type of {@code type} to {@code type}. If - * {@code element} is a local variable, or if the type already has an annotation from the - * relevant type hierarchy, then the defaults are not added. - * - *

(This uses both the {@link DefaultQualifierForUseTypeAnnotator} and {@link - * DefaultForTypeAnnotator}.) - * - * @param element possibly null element whose type is {@code type} - * @param type the type to which defaults are added - */ - protected void addAnnotationsFromDefaultForType( - @Nullable Element element, AnnotatedTypeMirror type) { - if (element != null && ElementUtils.isLocalVariable(element)) { - // It's a local variable. - if (type.getKind() == TypeKind.DECLARED) { - // If this is a type for a local variable, don't apply the default to the primary - // location. - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; - if (declaredType.getEnclosingType() != null) { - defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType()); - defaultForTypeAnnotator.visit(declaredType.getEnclosingType()); - } - for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) { - defaultQualifierForUseTypeAnnotator.visit(typeArg); - defaultForTypeAnnotator.visit(typeArg); - } - } else if (type.getKind().isPrimitive()) { - // Don't apply the default for local variables with primitive types. (The primary - // location is the only location, so this is a special case of the above.) - } else { - defaultQualifierForUseTypeAnnotator.visit(type); - defaultForTypeAnnotator.visit(type); - } - } else { - // It's not a local variable. - defaultQualifierForUseTypeAnnotator.visit(type); - defaultForTypeAnnotator.visit(type); + return res; + } + + /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ + public CFGVisualizer getCFGVisualizer() { + return cfgVisualizer; + } + + @Override + public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { + super.postAsMemberOf(type, owner, element); + if (element.getKind() == ElementKind.FIELD) { + poly.resolve(((VariableElement) element), owner, type); + } + } + + /** + * Adds default qualifiers based on the underlying type of {@code type} to {@code type}. If {@code + * element} is a local variable, or if the type already has an annotation from the relevant type + * hierarchy, then the defaults are not added. + * + *

(This uses both the {@link DefaultQualifierForUseTypeAnnotator} and {@link + * DefaultForTypeAnnotator}.) + * + * @param element possibly null element whose type is {@code type} + * @param type the type to which defaults are added + */ + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + if (element != null && ElementUtils.isLocalVariable(element)) { + // It's a local variable. + if (type.getKind() == TypeKind.DECLARED) { + // If this is a type for a local variable, don't apply the default to the primary + // location. + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; + if (declaredType.getEnclosingType() != null) { + defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType()); + defaultForTypeAnnotator.visit(declaredType.getEnclosingType()); + } + for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) { + defaultQualifierForUseTypeAnnotator.visit(typeArg); + defaultForTypeAnnotator.visit(typeArg); + } + } else if (type.getKind().isPrimitive()) { + // Don't apply the default for local variables with primitive types. (The primary + // location is the only location, so this is a special case of the above.) + } else { + defaultQualifierForUseTypeAnnotator.visit(type); + defaultForTypeAnnotator.visit(type); + } + } else { + // It's not a local variable. + defaultQualifierForUseTypeAnnotator.visit(type); + defaultForTypeAnnotator.visit(type); + } + } + + /** + * Output a message, if logging is on. + * + * @param format a format string + * @param args arguments to the format string + */ + @FormatMethod + private static void log(String format, Object... args) { + if (debug) { + SystemPlume.sleep(1); // logging can interleave with typechecker output + System.out.printf(format, args); + } + } + + /** For each type, whether it is relevant. A cache to avoid repeated re-computation. */ + private final Map isRelevantCache = CollectionsPlume.createLruCache(300); + + /** + * Returns true if users can write type annotations from this type system directly on the given + * Java type. + * + *

For a compound type, returns true only if a programmer may write a type qualifier on the top + * level of the compound type. That is, this method may return false, when it is possible to write + * type qualifiers on elements of the type. + * + *

Subclasses should override {@code #isRelevantImpl} instead of this method. + * + * @param tm a type + * @return true if users can write type annotations from this type system directly on the given + * Java type + */ + public final boolean isRelevant(TypeMirror tm) { + if (relevantJavaTypes == null) { + return true; + } + if (tm.getKind() != TypeKind.PACKAGE && tm.getKind() != TypeKind.MODULE) { + tm = types.erasure(tm); + } + Boolean cachedResult = isRelevantCache.get(tm); + if (cachedResult != null) { + return cachedResult; + } + boolean result = isRelevantImpl(tm); + isRelevantCache.put(tm, result); + return result; + } + + /** + * Returns true if users can write type annotations from this type system directly on the given + * Java type. + * + *

For a compound type, returns true only if it a programmer may write a type qualifier on the + * top level of the compound type. That is, this method may return false, when it is possible to + * write type qualifiers on elements of the type. + * + *

Subclasses should override {@code #isRelevantImpl} instead of this method. + * + * @param tm a type + * @return true if users can write type annotations from this type system directly on the given + * Java type + */ + public final boolean isRelevant(AnnotatedTypeMirror tm) { + return isRelevant(tm.getUnderlyingType()); + } + + /** + * Returns true if users can write type annotations from this type system on the given Java type. + * Does not use a cache. + * + *

Clients should never call this. Call {@link #isRelevant} instead. This is a helper method + * for {@link #isRelevant}. + * + * @param tm a type + * @return true if users can write type annotations from this type system on the given Java type + */ + protected boolean isRelevantImpl(TypeMirror tm) { + if (relevantJavaTypes == null) { + return true; + } + if (relevantJavaTypes.contains(tm)) { + return true; + } + + switch (tm.getKind()) { + + // Primitives have no subtyping relationships, but the lookup might have failed + // because tm has metadata such as annotations. + case BOOLEAN: + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + for (TypeMirror relevantJavaType : relevantJavaTypes) { + if (types.isSameType(tm, relevantJavaType)) { + return true; + } } - } + return false; - /** - * Output a message, if logging is on. - * - * @param format a format string - * @param args arguments to the format string - */ - @FormatMethod - private static void log(String format, Object... args) { - if (debug) { - SystemPlume.sleep(1); // logging can interleave with typechecker output - System.out.printf(format, args); - } - } + // Void is never relevant + case VOID: + return false; - /** For each type, whether it is relevant. A cache to avoid repeated re-computation. */ - private final Map isRelevantCache = CollectionsPlume.createLruCache(300); - - /** - * Returns true if users can write type annotations from this type system directly on the given - * Java type. - * - *

For a compound type, returns true only if a programmer may write a type qualifier on the - * top level of the compound type. That is, this method may return false, when it is possible to - * write type qualifiers on elements of the type. - * - *

Subclasses should override {@code #isRelevantImpl} instead of this method. - * - * @param tm a type - * @return true if users can write type annotations from this type system directly on the given - * Java type - */ - public final boolean isRelevant(TypeMirror tm) { - if (relevantJavaTypes == null) { - return true; - } - if (tm.getKind() != TypeKind.PACKAGE && tm.getKind() != TypeKind.MODULE) { - tm = types.erasure(tm); - } - Boolean cachedResult = isRelevantCache.get(tm); - if (cachedResult != null) { - return cachedResult; - } - boolean result = isRelevantImpl(tm); - isRelevantCache.put(tm, result); - return result; - } + case ARRAY: + return arraysAreRelevant; - /** - * Returns true if users can write type annotations from this type system directly on the given - * Java type. - * - *

For a compound type, returns true only if it a programmer may write a type qualifier on - * the top level of the compound type. That is, this method may return false, when it is - * possible to write type qualifiers on elements of the type. - * - *

Subclasses should override {@code #isRelevantImpl} instead of this method. - * - * @param tm a type - * @return true if users can write type annotations from this type system directly on the given - * Java type - */ - public final boolean isRelevant(AnnotatedTypeMirror tm) { - return isRelevant(tm.getUnderlyingType()); - } - - /** - * Returns true if users can write type annotations from this type system on the given Java - * type. Does not use a cache. - * - *

Clients should never call this. Call {@link #isRelevant} instead. This is a helper method - * for {@link #isRelevant}. - * - * @param tm a type - * @return true if users can write type annotations from this type system on the given Java type - */ - protected boolean isRelevantImpl(TypeMirror tm) { - if (relevantJavaTypes == null) { - return true; - } - if (relevantJavaTypes.contains(tm)) { + case DECLARED: + for (TypeMirror relevantJavaType : relevantJavaTypes) { + if (types.isSubtype(relevantJavaType, tm) || types.isSubtype(tm, relevantJavaType)) { return true; + } } + return false; - switch (tm.getKind()) { + case TYPEVAR: + return isRelevant(((TypeVariable) tm).getUpperBound()); - // Primitives have no subtyping relationships, but the lookup might have failed - // because tm has metadata such as annotations. + case NULL: + for (TypeMirror relevantJavaType : relevantJavaTypes) { + switch (relevantJavaType.getKind()) { case BOOLEAN: case BYTE: case CHAR: @@ -2676,599 +2681,553 @@ protected boolean isRelevantImpl(TypeMirror tm) { case INT: case LONG: case SHORT: - for (TypeMirror relevantJavaType : relevantJavaTypes) { - if (types.isSameType(tm, relevantJavaType)) { - return true; - } - } - return false; + continue; - // Void is never relevant + case ERROR: + case NONE: case VOID: - return false; - - case ARRAY: - return arraysAreRelevant; - - case DECLARED: - for (TypeMirror relevantJavaType : relevantJavaTypes) { - if (types.isSubtype(relevantJavaType, tm) - || types.isSubtype(tm, relevantJavaType)) { - return true; - } - } - return false; + continue; - case TYPEVAR: - return isRelevant(((TypeVariable) tm).getUpperBound()); - - case NULL: - for (TypeMirror relevantJavaType : relevantJavaTypes) { - switch (relevantJavaType.getKind()) { - case BOOLEAN: - case BYTE: - case CHAR: - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - continue; - - case ERROR: - case NONE: - case VOID: - continue; - - case MODULE: - case PACKAGE: - continue; - - case NULL: - default: - return true; - } - } - return false; - - case EXECUTABLE: case MODULE: case PACKAGE: - return false; + continue; + case NULL: default: - throw new BugInCF("isRelevantHelper(%s): Unexpected TypeKind %s", tm, tm.getKind()); - } - } - - /** The cached message about relevant types. */ - private @MonotonicNonNull String irrelevantExtraMessage = null; - - /** - * Returns a string that can be passed to the "anno.on.irrelevant" error, giving information - * about which types are relevant. - * - * @return a string that can be passed to the "anno.on.irrelevant" error, possibly the empty - * string - */ - public String irrelevantExtraMessage() { - if (irrelevantExtraMessage == null) { - if (relevantJavaTypes == null) { - irrelevantExtraMessage = ""; - } else { - irrelevantExtraMessage = "; only applicable to " + relevantJavaTypes; - if (arraysAreRelevant) { - irrelevantExtraMessage += " and arrays"; - } - } + return true; + } } - return irrelevantExtraMessage; - } - - /** - * Return the type of the default value of the given type. The default value is 0, false, or - * null. - * - * @param typeMirror a type - * @return the annotated type of {@code type}'s default value - */ - // TODO: Cache results to avoid recomputation. - public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { - Tree defaultValueTree = TreeUtils.getDefaultValueTree(typeMirror, processingEnv); - TypeMirror defaultValueTM = TreeUtils.typeOf(defaultValueTree); - AnnotatedTypeMirror defaultValueATM = - AnnotatedTypeMirror.createType(defaultValueTM, this, false); - addComputedTypeAnnotationsWithoutFlow(defaultValueTree, defaultValueATM); - return defaultValueATM; - } - - /* NO-AFU - * Return the contract annotations (that is, pre- and post-conditions) for the given AMethod. Does - * not modify the AMethod. - * - *

This overload must only be called when using WholeProgramInferenceScenes. - * - * @param m the AFU representation of a method - * @return the contract annotations for the method - */ - /* NO-AFU - public List getContractAnnotations(AMethod m) { - List preconds = getPreconditionAnnotations(m); - List postconds = getPostconditionAnnotations(m, preconds); - - List result = preconds; - result.addAll(postconds); - return result; - } - */ - - /* NO-AFU - * Return the precondition annotations for the given AMethod. Does not modify the AMethod. - * - *

This overload must only be called when using WholeProgramInferenceScenes. - * - * @param m the AFU representation of a method - * @return the precondition annotations for the method - */ - /* NO-AFU - public List getPreconditionAnnotations(AMethod m) { - int size = m.getPreconditions().size(); - List result = new ArrayList<>(size); - if (size == 0) { - return result; - } + return false; - WholeProgramInferenceImplementation wholeProgramInference = - (WholeProgramInferenceImplementation) getWholeProgramInference(); - WholeProgramInferenceScenesStorage storage = - (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); + case EXECUTABLE: + case MODULE: + case PACKAGE: + return false; - for (Map.Entry entry : m.getPreconditions().entrySet()) { - TypeMirror typeMirror = entry.getValue().getTypeMirror(); - if (typeMirror == null) { - throw new BugInCF( - "null TypeMirror in AField inferred by WPI precondition inference. AField: " - + entry.getValue().toString()); + default: + throw new BugInCF("isRelevantHelper(%s): Unexpected TypeKind %s", tm, tm.getKind()); + } + } + + /** The cached message about relevant types. */ + private @MonotonicNonNull String irrelevantExtraMessage = null; + + /** + * Returns a string that can be passed to the "anno.on.irrelevant" error, giving information about + * which types are relevant. + * + * @return a string that can be passed to the "anno.on.irrelevant" error, possibly the empty + * string + */ + public String irrelevantExtraMessage() { + if (irrelevantExtraMessage == null) { + if (relevantJavaTypes == null) { + irrelevantExtraMessage = ""; + } else { + irrelevantExtraMessage = "; only applicable to " + relevantJavaTypes; + if (arraysAreRelevant) { + irrelevantExtraMessage += " and arrays"; } - Collections.sort(result, Ordering.usingToString()); - return result; - } - */ - - /* NO-AFU - * Return the postcondition annotations for the given AMethod. Does not modify the AMethod. - * - *

This overload must only be called when using WholeProgramInferenceScenes. - * - * @param m the AFU representation of a method - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions - * @return the postcondition annotations for the method - */ - /* NO-AFU - public List getPostconditionAnnotations( - AMethod m, List preconds) { - int size = m.getPostconditions().size(); - List result = new ArrayList<>(size); - if (size == 0) { - return result; } - - WholeProgramInferenceImplementation wholeProgramInference = - (WholeProgramInferenceImplementation) getWholeProgramInference(); - WholeProgramInferenceScenesStorage storage = - (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); - - for (Map.Entry entry : m.getPostconditions().entrySet()) { - TypeMirror typeMirror = entry.getValue().getTypeMirror(); - if (typeMirror == null) { - throw new BugInCF( - "null TypeMirror in AField inferred by WPI postcondition inference. AField: " - + entry.getValue().toString()); - } - Collections.sort(result, Ordering.usingToString()); - return result; } - */ - - /* NO-AFU - * Return the contract annotations (that is, pre- and post-conditions) for the given - * CallableDeclarationAnnos. Does not modify the CallableDeclarationAnnos. - * - *

This overload must only be called when using WholeProgramInferenceJavaParserStorage. - * - * @param methodAnnos annotation data for a method - * @return contract annotations for the method - */ - /* NO-AFU - public List getContractAnnotations( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - List preconds = getPreconditionAnnotations(methodAnnos); - List postconds = getPostconditionAnnotations(methodAnnos, preconds); - - List result = preconds; - result.addAll(postconds); + return irrelevantExtraMessage; + } + + /** + * Return the type of the default value of the given type. The default value is 0, false, or null. + * + * @param typeMirror a type + * @return the annotated type of {@code type}'s default value + */ + // TODO: Cache results to avoid recomputation. + public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { + Tree defaultValueTree = TreeUtils.getDefaultValueTree(typeMirror, processingEnv); + TypeMirror defaultValueTM = TreeUtils.typeOf(defaultValueTree); + AnnotatedTypeMirror defaultValueATM = + AnnotatedTypeMirror.createType(defaultValueTM, this, false); + addComputedTypeAnnotationsWithoutFlow(defaultValueTree, defaultValueATM); + return defaultValueATM; + } + + /* NO-AFU + * Return the contract annotations (that is, pre- and post-conditions) for the given AMethod. Does + * not modify the AMethod. + * + *

This overload must only be called when using WholeProgramInferenceScenes. + * + * @param m the AFU representation of a method + * @return the contract annotations for the method + */ + /* NO-AFU + public List getContractAnnotations(AMethod m) { + List preconds = getPreconditionAnnotations(m); + List postconds = getPostconditionAnnotations(m, preconds); + + List result = preconds; + result.addAll(postconds); + return result; + } + */ + + /* NO-AFU + * Return the precondition annotations for the given AMethod. Does not modify the AMethod. + * + *

This overload must only be called when using WholeProgramInferenceScenes. + * + * @param m the AFU representation of a method + * @return the precondition annotations for the method + */ + /* NO-AFU + public List getPreconditionAnnotations(AMethod m) { + int size = m.getPreconditions().size(); + List result = new ArrayList<>(size); + if (size == 0) { return result; } - */ - /* NO-AFU - * Return the precondition annotations for the given CallableDeclarationAnnos. Does not modify - * the CallableDeclarationAnnos. - * - *

This overload must only be called when using WholeProgramInferenceJavaParserStorage. - * - * @param methodAnnos annotation data for a method - * @return precondition annotations for the method - */ - /* NO-AFU - public List getPreconditionAnnotations( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - List result = new ArrayList<>(); - for (Map.Entry> entry : - methodAnnos.getPreconditions().entrySet()) { - result.addAll( - getPreconditionAnnotations( - entry.getKey(), entry.getValue().first, entry.getValue().second)); + WholeProgramInferenceImplementation wholeProgramInference = + (WholeProgramInferenceImplementation) getWholeProgramInference(); + WholeProgramInferenceScenesStorage storage = + (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); + + for (Map.Entry entry : m.getPreconditions().entrySet()) { + TypeMirror typeMirror = entry.getValue().getTypeMirror(); + if (typeMirror == null) { + throw new BugInCF( + "null TypeMirror in AField inferred by WPI precondition inference. AField: " + + entry.getValue().toString()); } Collections.sort(result, Ordering.usingToString()); return result; + } + */ + + /* NO-AFU + * Return the postcondition annotations for the given AMethod. Does not modify the AMethod. + * + *

This overload must only be called when using WholeProgramInferenceScenes. + * + * @param m the AFU representation of a method + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions + * @return the postcondition annotations for the method + */ + /* NO-AFU + public List getPostconditionAnnotations( + AMethod m, List preconds) { + int size = m.getPostconditions().size(); + List result = new ArrayList<>(size); + if (size == 0) { + return result; } - */ - /* NO-AFU - * Return the postcondition annotations for the given CallableDeclarationAnnos. Does not modify - * the CallableDeclarationAnnos. - * - *

This overload must only be called when using WholeProgramInferenceJavaParserStorage. - * - * @param methodAnnos annotation data for a method - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions - * @return postcondition annotations for the method - */ - /* NO-AFU - public List getPostconditionAnnotations( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, - List preconds) { - List result = new ArrayList<>(); - for (Map.Entry> entry : - methodAnnos.getPostconditions().entrySet()) { - result.addAll( - getPostconditionAnnotations( - entry.getKey(), entry.getValue().first, entry.getValue().second, preconds)); + WholeProgramInferenceImplementation wholeProgramInference = + (WholeProgramInferenceImplementation) getWholeProgramInference(); + WholeProgramInferenceScenesStorage storage = + (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); + + for (Map.Entry entry : m.getPostconditions().entrySet()) { + TypeMirror typeMirror = entry.getValue().getTypeMirror(); + if (typeMirror == null) { + throw new BugInCF( + "null TypeMirror in AField inferred by WPI postcondition inference. AField: " + + entry.getValue().toString()); } Collections.sort(result, Ordering.usingToString()); return result; - } - */ - - /* NO-AFU - * Returns a list of inferred {@code @RequiresQualifier} annotations for the given expression. - * By default this list does not include any qualifier that has elements/arguments, which - * {@code @RequiresQualifier} does not support. Subclasses may remove this restriction by - * overriding {@link #createRequiresOrEnsuresQualifier}. - * - *

Each annotation in the list is of the form - * {@code @RequiresQualifier(expression="expression", qualifier=MyQual.class)}. {@code - * expression} must be a valid Java Expression string, in the same format used by {@link - * RequiresQualifier}. - * - * @param expression an expression - * @param inferredType the type of the expression, on method entry - * @param declaredType the declared type of the expression - * @return precondition annotations for the element (possibly an empty list) - */ - /* NO-AFU - public final List getPreconditionAnnotations( - String expression, AnnotatedTypeMirror inferredType, AnnotatedTypeMirror declaredType) { - return getPreOrPostconditionAnnotations( - expression, inferredType, declaredType, BeforeOrAfter.BEFORE, null); - } - */ - - /* NO-AFU - * Returns a list of inferred {@code @EnsuresQualifier} annotations for the given expression. By - * default this list does not include any qualifier that has elements/arguments, which - * {@code @EnsuresQualifier} does not support; and, preconditions are not used to suppress - * redundant postconditions. Subclasses may remove these restrictions by overriding {@link - * #createRequiresOrEnsuresQualifier}. - * - *

Each annotation in the list is of the form - * {@code @EnsuresQualifier(expression="expression", qualifier=MyQual.class)}. {@code - * expression} must be a valid Java Expression string, in the same format used by {@link - * EnsuresQualifier}. - * - * @param expression an expression - * @param inferredType the type of the expression, on method exit - * @param declaredType the declared type of the expression - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions - * @return postcondition annotations for the element (possibly an empty list) - */ - /* NO-AFU - public final List getPostconditionAnnotations( - String expression, - AnnotatedTypeMirror inferredType, - AnnotatedTypeMirror declaredType, - List preconds) { - return getPreOrPostconditionAnnotations( - expression, inferredType, declaredType, BeforeOrAfter.AFTER, preconds); - } - */ - - /* NO-AFU - * Creates pre- and postcondition annotations. Helper method for {@link - * #getPreconditionAnnotations} and {@link #getPostconditionAnnotations}. - * - *

Returns a {@code @RequiresQualifier} or {@code @EnsuresQualifier} annotation for the given - * expression. Returns an empty list if none can be created, because the qualifier has - * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not - * support. - * - *

This implementation makes no assumptions about preconditions suppressing postconditions, - * but subclasses may do so. - * - * @param expression an expression whose type annotations to return - * @param inferredType the type of the expression, on method entry or exit (depending on the - * value of {@code preOrPost}) - * @param declaredType the declared type of the expression, which is used to determine if the - * inferred type supplies no additional information beyond the declared type - * @param preOrPost whether to return preconditions or postconditions - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions; non-null exactly when {@code preOrPost} is {@code AFTER} - * @return precondition or postcondition annotations for the element (possibly an empty list) - */ - /* NO-AFU - protected List getPreOrPostconditionAnnotations( - String expression, - AnnotatedTypeMirror inferredType, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - assert (preOrPost == BeforeOrAfter.BEFORE) == (preconds == null); - - if (getWholeProgramInference() == null) { - return Collections.emptyList(); + } + */ + + /* NO-AFU + * Return the contract annotations (that is, pre- and post-conditions) for the given + * CallableDeclarationAnnos. Does not modify the CallableDeclarationAnnos. + * + *

This overload must only be called when using WholeProgramInferenceJavaParserStorage. + * + * @param methodAnnos annotation data for a method + * @return contract annotations for the method + */ + /* NO-AFU + public List getContractAnnotations( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + List preconds = getPreconditionAnnotations(methodAnnos); + List postconds = getPostconditionAnnotations(methodAnnos, preconds); + + List result = preconds; + result.addAll(postconds); + return result; + } + */ + + /* NO-AFU + * Return the precondition annotations for the given CallableDeclarationAnnos. Does not modify + * the CallableDeclarationAnnos. + * + *

This overload must only be called when using WholeProgramInferenceJavaParserStorage. + * + * @param methodAnnos annotation data for a method + * @return precondition annotations for the method + */ + /* NO-AFU + public List getPreconditionAnnotations( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + List result = new ArrayList<>(); + for (Map.Entry> entry : + methodAnnos.getPreconditions().entrySet()) { + result.addAll( + getPreconditionAnnotations( + entry.getKey(), entry.getValue().first, entry.getValue().second)); + } + Collections.sort(result, Ordering.usingToString()); + return result; + } + */ + + /* NO-AFU + * Return the postcondition annotations for the given CallableDeclarationAnnos. Does not modify + * the CallableDeclarationAnnos. + * + *

This overload must only be called when using WholeProgramInferenceJavaParserStorage. + * + * @param methodAnnos annotation data for a method + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions + * @return postcondition annotations for the method + */ + /* NO-AFU + public List getPostconditionAnnotations( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, + List preconds) { + List result = new ArrayList<>(); + for (Map.Entry> entry : + methodAnnos.getPostconditions().entrySet()) { + result.addAll( + getPostconditionAnnotations( + entry.getKey(), entry.getValue().first, entry.getValue().second, preconds)); + } + Collections.sort(result, Ordering.usingToString()); + return result; + } + */ + + /* NO-AFU + * Returns a list of inferred {@code @RequiresQualifier} annotations for the given expression. + * By default this list does not include any qualifier that has elements/arguments, which + * {@code @RequiresQualifier} does not support. Subclasses may remove this restriction by + * overriding {@link #createRequiresOrEnsuresQualifier}. + * + *

Each annotation in the list is of the form + * {@code @RequiresQualifier(expression="expression", qualifier=MyQual.class)}. {@code + * expression} must be a valid Java Expression string, in the same format used by {@link + * RequiresQualifier}. + * + * @param expression an expression + * @param inferredType the type of the expression, on method entry + * @param declaredType the declared type of the expression + * @return precondition annotations for the element (possibly an empty list) + */ + /* NO-AFU + public final List getPreconditionAnnotations( + String expression, AnnotatedTypeMirror inferredType, AnnotatedTypeMirror declaredType) { + return getPreOrPostconditionAnnotations( + expression, inferredType, declaredType, BeforeOrAfter.BEFORE, null); + } + */ + + /* NO-AFU + * Returns a list of inferred {@code @EnsuresQualifier} annotations for the given expression. By + * default this list does not include any qualifier that has elements/arguments, which + * {@code @EnsuresQualifier} does not support; and, preconditions are not used to suppress + * redundant postconditions. Subclasses may remove these restrictions by overriding {@link + * #createRequiresOrEnsuresQualifier}. + * + *

Each annotation in the list is of the form + * {@code @EnsuresQualifier(expression="expression", qualifier=MyQual.class)}. {@code + * expression} must be a valid Java Expression string, in the same format used by {@link + * EnsuresQualifier}. + * + * @param expression an expression + * @param inferredType the type of the expression, on method exit + * @param declaredType the declared type of the expression + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions + * @return postcondition annotations for the element (possibly an empty list) + */ + /* NO-AFU + public final List getPostconditionAnnotations( + String expression, + AnnotatedTypeMirror inferredType, + AnnotatedTypeMirror declaredType, + List preconds) { + return getPreOrPostconditionAnnotations( + expression, inferredType, declaredType, BeforeOrAfter.AFTER, preconds); + } + */ + + /* NO-AFU + * Creates pre- and postcondition annotations. Helper method for {@link + * #getPreconditionAnnotations} and {@link #getPostconditionAnnotations}. + * + *

Returns a {@code @RequiresQualifier} or {@code @EnsuresQualifier} annotation for the given + * expression. Returns an empty list if none can be created, because the qualifier has + * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not + * support. + * + *

This implementation makes no assumptions about preconditions suppressing postconditions, + * but subclasses may do so. + * + * @param expression an expression whose type annotations to return + * @param inferredType the type of the expression, on method entry or exit (depending on the + * value of {@code preOrPost}) + * @param declaredType the declared type of the expression, which is used to determine if the + * inferred type supplies no additional information beyond the declared type + * @param preOrPost whether to return preconditions or postconditions + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions; non-null exactly when {@code preOrPost} is {@code AFTER} + * @return precondition or postcondition annotations for the element (possibly an empty list) + */ + /* NO-AFU + protected List getPreOrPostconditionAnnotations( + String expression, + AnnotatedTypeMirror inferredType, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + assert (preOrPost == BeforeOrAfter.BEFORE) == (preconds == null); + + if (getWholeProgramInference() == null) { + return Collections.emptyList(); + } + + // TODO: should this only check the top-level annotations? + if (declaredType.equals(inferredType)) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (AnnotationMirror inferredAm : inferredType.getAnnotations()) { + AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); + if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { + continue; } // TODO: should this only check the top-level annotations? if (declaredType.equals(inferredType)) { - return Collections.emptyList(); + return Collections.emptyList(); } List result = new ArrayList<>(); for (AnnotationMirror inferredAm : inferredType.getAnnotations()) { - AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); - if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { - continue; - } - - // TODO: should this only check the top-level annotations? - if (declaredType.equals(inferredType)) { - return Collections.emptyList(); - } - - List result = new ArrayList<>(); - for (AnnotationMirror inferredAm : inferredType.getAnnotations()) { - AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); - if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { - continue; - } - AnnotationMirror anno = - createRequiresOrEnsuresQualifier( - expression, inferredAm, declaredType, preOrPost, preconds); - if (anno != null) { - result.add(anno); - } - } - return result; - } - */ - - /** - * Matches parameter expressions as they appear in {@link EnsuresQualifier} and {@link - * RequiresQualifier} annotations, e.g. "#1", "#2", etc. - */ - protected static final Pattern formalParameterPattern = Pattern.compile("^#[0-9]+$"); - - /** - * Creates a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for - * the given expression. - * - *

This is of the form {@code @RequiresQualifier(expression="expression", - * qualifier=MyQual.class)} or {@code @EnsuresQualifier(expression="expression", - * qualifier=MyQual.class)}, where "expression" is exactly the string {@code expression} and - * MyQual is the annotation represented by {@code qualifier}. - * - *

Returns null if the expression is invalid when combined with the kind of annotation: for - * example, precondition annotations on "this" and parameters ("#1", etc.) are not supported, - * because receiver/parameter annotations should be inferred instead. - * - *

This implementation returns null if no annotation can be created, because the qualifier - * has elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not - * support. Subclasses may override this method to return qualifiers that do have arguments - * instead of returning null. - * - * @param expression the expression to which the qualifier applies - * @param qualifier the qualifier that must be present - * @param declaredType the declared type of the expression, which is used to avoid inferring - * redundant pre- or postcondition annotations - * @param preOrPost whether to return a precondition or postcondition annotation - * @param preconds the list of precondition annotations; used to suppress redundant - * postconditions; non-null exactly when {@code preOrPost} is {@code BeforeOrAfter.BEFORE} - * @return a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for - * the given expression, or null - */ - protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( - String expression, - AnnotationMirror qualifier, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - // Do not generate RequiresQualifier annotations for "this" or parameter expressions. - if (preOrPost == BeforeOrAfter.BEFORE - && ("this".equals(expression) - || formalParameterPattern.matcher(expression).matches())) { - return null; - } - if (!qualifier.getElementValues().isEmpty()) { - // @RequiresQualifier and @EnsuresQualifier do not yet support annotations with - // elements/arguments. - return null; - } - - AnnotationBuilder builder = - new AnnotationBuilder( - processingEnv, - preOrPost == BeforeOrAfter.BEFORE - ? RequiresQualifier.class - : EnsuresQualifier.class); - builder.setValue("expression", new String[] {expression}); - builder.setValue("qualifier", AnnotationUtils.annotationMirrorToClass(qualifier)); - return builder.build(); - } - - /** - * Add a new entry to the shared CFG. If this is a subchecker, this method delegates to the - * superchecker's GenericAnnotatedTypeFactory, if it exists. Duplicate keys must map to the same - * CFG. - * - *

Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is - * nonsensical to have a shared CFG when a checker is running alone. - * - * @param tree the source code corresponding to cfg - * @param cfg the control flow graph to use for tree - * @return whether a shared CFG was found to actually add to (duplicate keys also return true) - */ - public boolean addSharedCFGForTree(Tree tree, ControlFlowGraph cfg) { - if (!shouldCache) { - return false; - } - BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); - @SuppressWarnings("interning") // Checking reference equality. - boolean parentIsThisChecker = parentChecker == this.checker; - if (parentIsThisChecker) { - // This is the ultimate parent. - if (this.subcheckerSharedCFG == null) { - this.subcheckerSharedCFG = new HashMap<>(getCacheSize()); - } - if (!this.subcheckerSharedCFG.containsKey(tree)) { - this.subcheckerSharedCFG.put(tree, cfg); - } else { - assert this.subcheckerSharedCFG.get(tree).equals(cfg); - } - return true; - } - - // This is a subchecker. - if (parentChecker != null) { - GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); - return parentAtf.addSharedCFGForTree(tree, cfg); - } else { - return false; - } - } - - /** - * Get the shared control flow graph used for {@code tree} by this checker's topmost - * superchecker. Returns null if no information is available about the given tree, or if this - * checker has a parent checker that does not have a GenericAnnotatedTypeFactory. - * - *

Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is - * nonsensical to have a shared CFG when a checker is running alone. - * - * @param tree the tree whose CFG should be looked up - * @return the CFG stored by this checker's uppermost superchecker for tree, or null if it is - * not available - */ - public @Nullable ControlFlowGraph getSharedCFGForTree(Tree tree) { - if (!shouldCache) { - return null; - } - BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); - @SuppressWarnings("interning") // Checking reference equality. - boolean parentIsThisChecker = parentChecker == this.checker; - if (parentIsThisChecker) { - // This is the ultimate parent; - return this.subcheckerSharedCFG == null - ? null - : this.subcheckerSharedCFG.getOrDefault(tree, null); - } - - // This is a subchecker. - if (parentChecker != null) { - GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); - return parentAtf.getSharedCFGForTree(tree); - } else { - return null; - } - } - - /** - * If kind = CONDITIONALPOSTCONDITION, return the result element, e.g. {@link - * EnsuresQualifierIf#result}. Otherwise, return null. - * - * @param kind the kind of {@code contractAnnotation} - * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link - * EnsuresQualifierIf} - * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have - * a {@code result} element - */ - public @Nullable Boolean getEnsuresQualifierIfResult( - Contract.Kind kind, AnnotationMirror contractAnnotation) { - if (kind == Contract.Kind.CONDITIONALPOSTCONDITION) { - if (contractAnnotation instanceof EnsuresQualifierIf) { - // It's the framework annotation @EnsuresQualifierIf - return AnnotationUtils.getElementValueBoolean( - contractAnnotation, - ensuresQualifierIfResultElement, /*default is irrelevant*/ - false); - } else { - // It's a checker-specific annotation such as @EnsuresMinLenIf - @SuppressWarnings("deprecation") // concrete annotation class is not known - Boolean result = - AnnotationUtils.getElementValue( - contractAnnotation, "result", Boolean.class, false); - return result; - } - } else { - return null; - } - } - - /** - * If {@code contractAnnotation} is a framework annotation, return its {@code expression} - * element. Otherwise, {@code contractAnnotation} is defined in a checker. If kind = - * CONDITIONALPOSTCONDITION, return its {@code expression} element, else return its {@code - * value} element. - * - * @param kind the kind of {@code contractAnnotation} - * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link - * EnsuresQualifierIf} - * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have - * a {@code result} element - */ - public @Nullable List getContractExpressions( - Contract.Kind kind, AnnotationMirror contractAnnotation) { - // First, handle framework annotations. - if (contractAnnotation instanceof RequiresQualifier) { - return AnnotationUtils.getElementValueArray( - contractAnnotation, requiresQualifierExpressionElement, String.class); - } else if (contractAnnotation instanceof EnsuresQualifier) { - return AnnotationUtils.getElementValueArray( - contractAnnotation, ensuresQualifierExpressionElement, String.class); - } else if (contractAnnotation instanceof EnsuresQualifierIf) { - return AnnotationUtils.getElementValueArray( - contractAnnotation, ensuresQualifierIfExpressionElement, String.class); - } - // `contractAnnotation` is defined in a checker. - String elementName = - kind == Contract.Kind.CONDITIONALPOSTCONDITION ? "expression" : "value"; + AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); + if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { + continue; + } + AnnotationMirror anno = + createRequiresOrEnsuresQualifier( + expression, inferredAm, declaredType, preOrPost, preconds); + if (anno != null) { + result.add(anno); + } + } + return result; + } + */ + + /** + * Matches parameter expressions as they appear in {@link EnsuresQualifier} and {@link + * RequiresQualifier} annotations, e.g. "#1", "#2", etc. + */ + protected static final Pattern formalParameterPattern = Pattern.compile("^#[0-9]+$"); + + /** + * Creates a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for + * the given expression. + * + *

This is of the form {@code @RequiresQualifier(expression="expression", + * qualifier=MyQual.class)} or {@code @EnsuresQualifier(expression="expression", + * qualifier=MyQual.class)}, where "expression" is exactly the string {@code expression} and + * MyQual is the annotation represented by {@code qualifier}. + * + *

Returns null if the expression is invalid when combined with the kind of annotation: for + * example, precondition annotations on "this" and parameters ("#1", etc.) are not supported, + * because receiver/parameter annotations should be inferred instead. + * + *

This implementation returns null if no annotation can be created, because the qualifier has + * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not + * support. Subclasses may override this method to return qualifiers that do have arguments + * instead of returning null. + * + * @param expression the expression to which the qualifier applies + * @param qualifier the qualifier that must be present + * @param declaredType the declared type of the expression, which is used to avoid inferring + * redundant pre- or postcondition annotations + * @param preOrPost whether to return a precondition or postcondition annotation + * @param preconds the list of precondition annotations; used to suppress redundant + * postconditions; non-null exactly when {@code preOrPost} is {@code BeforeOrAfter.BEFORE} + * @return a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for + * the given expression, or null + */ + protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( + String expression, + AnnotationMirror qualifier, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + // Do not generate RequiresQualifier annotations for "this" or parameter expressions. + if (preOrPost == BeforeOrAfter.BEFORE + && ("this".equals(expression) || formalParameterPattern.matcher(expression).matches())) { + return null; + } + if (!qualifier.getElementValues().isEmpty()) { + // @RequiresQualifier and @EnsuresQualifier do not yet support annotations with + // elements/arguments. + return null; + } + + AnnotationBuilder builder = + new AnnotationBuilder( + processingEnv, + preOrPost == BeforeOrAfter.BEFORE ? RequiresQualifier.class : EnsuresQualifier.class); + builder.setValue("expression", new String[] {expression}); + builder.setValue("qualifier", AnnotationUtils.annotationMirrorToClass(qualifier)); + return builder.build(); + } + + /** + * Add a new entry to the shared CFG. If this is a subchecker, this method delegates to the + * superchecker's GenericAnnotatedTypeFactory, if it exists. Duplicate keys must map to the same + * CFG. + * + *

Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is + * nonsensical to have a shared CFG when a checker is running alone. + * + * @param tree the source code corresponding to cfg + * @param cfg the control flow graph to use for tree + * @return whether a shared CFG was found to actually add to (duplicate keys also return true) + */ + public boolean addSharedCFGForTree(Tree tree, ControlFlowGraph cfg) { + if (!shouldCache) { + return false; + } + BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); + @SuppressWarnings("interning") // Checking reference equality. + boolean parentIsThisChecker = parentChecker == this.checker; + if (parentIsThisChecker) { + // This is the ultimate parent. + if (this.subcheckerSharedCFG == null) { + this.subcheckerSharedCFG = new HashMap<>(getCacheSize()); + } + if (!this.subcheckerSharedCFG.containsKey(tree)) { + this.subcheckerSharedCFG.put(tree, cfg); + } else { + assert this.subcheckerSharedCFG.get(tree).equals(cfg); + } + return true; + } + + // This is a subchecker. + if (parentChecker != null) { + GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); + return parentAtf.addSharedCFGForTree(tree, cfg); + } else { + return false; + } + } + + /** + * Get the shared control flow graph used for {@code tree} by this checker's topmost superchecker. + * Returns null if no information is available about the given tree, or if this checker has a + * parent checker that does not have a GenericAnnotatedTypeFactory. + * + *

Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is + * nonsensical to have a shared CFG when a checker is running alone. + * + * @param tree the tree whose CFG should be looked up + * @return the CFG stored by this checker's uppermost superchecker for tree, or null if it is not + * available + */ + public @Nullable ControlFlowGraph getSharedCFGForTree(Tree tree) { + if (!shouldCache) { + return null; + } + BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); + @SuppressWarnings("interning") // Checking reference equality. + boolean parentIsThisChecker = parentChecker == this.checker; + if (parentIsThisChecker) { + // This is the ultimate parent; + return this.subcheckerSharedCFG == null + ? null + : this.subcheckerSharedCFG.getOrDefault(tree, null); + } + + // This is a subchecker. + if (parentChecker != null) { + GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); + return parentAtf.getSharedCFGForTree(tree); + } else { + return null; + } + } + + /** + * If kind = CONDITIONALPOSTCONDITION, return the result element, e.g. {@link + * EnsuresQualifierIf#result}. Otherwise, return null. + * + * @param kind the kind of {@code contractAnnotation} + * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifierIf} + * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a + * {@code result} element + */ + public @Nullable Boolean getEnsuresQualifierIfResult( + Contract.Kind kind, AnnotationMirror contractAnnotation) { + if (kind == Contract.Kind.CONDITIONALPOSTCONDITION) { + if (contractAnnotation instanceof EnsuresQualifierIf) { + // It's the framework annotation @EnsuresQualifierIf + return AnnotationUtils.getElementValueBoolean( + contractAnnotation, ensuresQualifierIfResultElement, /*default is irrelevant*/ false); + } else { + // It's a checker-specific annotation such as @EnsuresMinLenIf @SuppressWarnings("deprecation") // concrete annotation class is not known - List result = - AnnotationUtils.getElementValueArray( - contractAnnotation, elementName, String.class, true); + Boolean result = + AnnotationUtils.getElementValue(contractAnnotation, "result", Boolean.class, false); return result; - } + } + } else { + return null; + } + } + + /** + * If {@code contractAnnotation} is a framework annotation, return its {@code expression} element. + * Otherwise, {@code contractAnnotation} is defined in a checker. If kind = + * CONDITIONALPOSTCONDITION, return its {@code expression} element, else return its {@code value} + * element. + * + * @param kind the kind of {@code contractAnnotation} + * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifierIf} + * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a + * {@code result} element + */ + public @Nullable List getContractExpressions( + Contract.Kind kind, AnnotationMirror contractAnnotation) { + // First, handle framework annotations. + if (contractAnnotation instanceof RequiresQualifier) { + return AnnotationUtils.getElementValueArray( + contractAnnotation, requiresQualifierExpressionElement, String.class); + } else if (contractAnnotation instanceof EnsuresQualifier) { + return AnnotationUtils.getElementValueArray( + contractAnnotation, ensuresQualifierExpressionElement, String.class); + } else if (contractAnnotation instanceof EnsuresQualifierIf) { + return AnnotationUtils.getElementValueArray( + contractAnnotation, ensuresQualifierIfExpressionElement, String.class); + } + // `contractAnnotation` is defined in a checker. + String elementName = kind == Contract.Kind.CONDITIONALPOSTCONDITION ? "expression" : "value"; + @SuppressWarnings("deprecation") // concrete annotation class is not known + List result = + AnnotationUtils.getElementValueArray(contractAnnotation, elementName, String.class, true); + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java index 90f873429e3..1796ef1b894 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java @@ -1,8 +1,7 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner; - import java.util.Objects; +import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner; /** * Computes the hashcode of an AnnotatedTypeMirror using the underlying type and primary annotations @@ -15,24 +14,24 @@ */ public class HashcodeAtmVisitor extends SimpleAnnotatedTypeScanner { - /** Creates a {@link HashcodeAtmVisitor}. */ - public HashcodeAtmVisitor() { - super(Integer::sum, 0); - } + /** Creates a {@link HashcodeAtmVisitor}. */ + public HashcodeAtmVisitor() { + super(Integer::sum, 0); + } - /** - * Generates hashcode for type using the underlying type and the primary annotation. This method - * does not descend into component types (this occurs in the scan method) - * - * @param type the type - */ - @Override - protected Integer defaultAction(AnnotatedTypeMirror type, Void v) { - // To differentiate between partially initialized type's (which may have null components) - // and fully initialized types, null values are allowed - if (type == null) { - return 0; - } - return Objects.hash(type.getUnderlyingTypeHashCode(), type.getAnnotations().toString()); + /** + * Generates hashcode for type using the underlying type and the primary annotation. This method + * does not descend into component types (this occurs in the scan method) + * + * @param type the type + */ + @Override + protected Integer defaultAction(AnnotatedTypeMirror type, Void v) { + // To differentiate between partially initialized type's (which may have null components) + // and fully initialized types, null values are allowed + if (type == null) { + return 0; } + return Objects.hash(type.getUnderlyingTypeHashCode(), type.getAnnotations().toString()); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java index 5e1d89d6bfd..b7926bf874a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java @@ -1,16 +1,14 @@ package org.checkerframework.framework.type; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.util.QualifierKind; -import org.checkerframework.framework.util.QualifierKindHierarchy; - import java.lang.annotation.Annotation; import java.util.Collection; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.util.QualifierKind; +import org.checkerframework.framework.util.QualifierKindHierarchy; /** * A {@link org.checkerframework.framework.type.QualifierHierarchy} where qualifiers may be @@ -40,121 +38,121 @@ @AnnotatedFor("nullness") public abstract class MostlyNoElementQualifierHierarchy extends ElementQualifierHierarchy { - /** - * Creates a MostlyNoElementQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - * @param atypeFactory the associated type factory - */ - protected MostlyNoElementQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, elements, atypeFactory); - } + /** + * Creates a MostlyNoElementQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + * @param atypeFactory the associated type factory + */ + protected MostlyNoElementQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, elements, atypeFactory); + } - @Override - public final boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - QualifierKind subKind = getQualifierKind(subAnno); - QualifierKind superKind = getQualifierKind(superAnno); - if (subKind.isSubtypeOf(superKind)) { - if (superKind.hasElements() && subKind.hasElements()) { - return isSubtypeWithElements(subAnno, subKind, superAnno, superKind); - } else { - return true; - } - } - return false; + @Override + public final boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + QualifierKind subKind = getQualifierKind(subAnno); + QualifierKind superKind = getQualifierKind(superAnno); + if (subKind.isSubtypeOf(superKind)) { + if (superKind.hasElements() && subKind.hasElements()) { + return isSubtypeWithElements(subAnno, subKind, superAnno, superKind); + } else { + return true; + } } + return false; + } - /** - * Returns true if {@code subAnno} is a subtype of {@code superAnno}. Both {@code subAnno} and - * {@code superAnno} are annotations with elements. {@code subKind} is a sub qualifier kind of - * {@code superKind}. - * - * @param subAnno possible subtype annotation; has elements - * @param subKind the QualifierKind of {@code subAnno} - * @param superAnno possible super annotation; has elements - * @param superKind the QualifierKind of {@code superAnno} - * @return true if {@code subAnno} is a subtype of {@code superAnno} - */ - protected abstract boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind); + /** + * Returns true if {@code subAnno} is a subtype of {@code superAnno}. Both {@code subAnno} and + * {@code superAnno} are annotations with elements. {@code subKind} is a sub qualifier kind of + * {@code superKind}. + * + * @param subAnno possible subtype annotation; has elements + * @param subKind the QualifierKind of {@code subAnno} + * @param superAnno possible super annotation; has elements + * @param superKind the QualifierKind of {@code superAnno} + * @return true if {@code subAnno} is a subtype of {@code superAnno} + */ + protected abstract boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind); - @Override - public final @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); - QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); - if (lub == null) { - // Qualifiers are not in the same hierarchy. - return null; - } - if (lub.hasElements()) { - return leastUpperBoundWithElements(a1, qual1, a2, qual2, lub); - } - return kindToElementlessQualifier.get(lub); + @Override + public final @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); + QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); + if (lub == null) { + // Qualifiers are not in the same hierarchy. + return null; + } + if (lub.hasElements()) { + return leastUpperBoundWithElements(a1, qual1, a2, qual2, lub); } + return kindToElementlessQualifier.get(lub); + } - /** - * Returns the least upper bound of {@code a1} and {@code a2} in cases where the lub of {@code - * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the lub - * of {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link - * #leastUpperBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns - * the correct {@code AnnotationMirror} without calling this method. - * - * @param a1 first annotation - * @param qualifierKind1 QualifierKind for {@code a1} - * @param a2 second annotation - * @param qualifierKind2 QualifierKind for {@code a2} - * @param lubKind the kind of the lub of {@code qualifierKind1} and {@code qualifierKind2} - * @return the least upper bound of {@code a1} and {@code a2} - */ - protected abstract AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind); + /** + * Returns the least upper bound of {@code a1} and {@code a2} in cases where the lub of {@code + * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the lub of + * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link + * #leastUpperBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns + * the correct {@code AnnotationMirror} without calling this method. + * + * @param a1 first annotation + * @param qualifierKind1 QualifierKind for {@code a1} + * @param a2 second annotation + * @param qualifierKind2 QualifierKind for {@code a2} + * @param lubKind the kind of the lub of {@code qualifierKind1} and {@code qualifierKind2} + * @return the least upper bound of {@code a1} and {@code a2} + */ + protected abstract AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind); - @Override - public final @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); - QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); - if (glb == null) { - // Qualifiers are not in the same hierarchy. - return null; - } - if (glb.hasElements()) { - return greatestLowerBoundWithElements(a1, qual1, a2, qual2, glb); - } - return kindToElementlessQualifier.get(glb); + @Override + public final @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); + QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); + if (glb == null) { + // Qualifiers are not in the same hierarchy. + return null; + } + if (glb.hasElements()) { + return greatestLowerBoundWithElements(a1, qual1, a2, qual2, glb); } + return kindToElementlessQualifier.get(glb); + } - /** - * Returns the greatest lower bound of {@code a1} and {@code a2} in cases where the glb of - * {@code qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If - * the glb of {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then - * {@link #greatestLowerBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, - * TypeMirror)} returns the correct {@code AnnotationMirror} without calling this method. - * - * @param a1 first annotation - * @param qualifierKind1 QualifierKind for {@code a1} - * @param a2 second annotation - * @param qualifierKind2 QualifierKind for {@code a2} - * @return the greatest lower bound between {@code a1} and {@code a2} - */ - protected abstract AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind); + /** + * Returns the greatest lower bound of {@code a1} and {@code a2} in cases where the glb of {@code + * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the glb of + * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link + * #greatestLowerBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns + * the correct {@code AnnotationMirror} without calling this method. + * + * @param a1 first annotation + * @param qualifierKind1 QualifierKind for {@code a1} + * @param a2 second annotation + * @param qualifierKind2 QualifierKind for {@code a2} + * @return the greatest lower bound between {@code a1} and {@code a2} + */ + protected abstract AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java index 1002c6ecbdb..3d8e177200c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java @@ -1,5 +1,13 @@ package org.checkerframework.framework.type; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -14,16 +22,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** * A {@link QualifierHierarchy} where no qualifier has arguments; that is, no qualifier is * represented by an annotation with elements. The meta-annotation {@link @@ -36,226 +34,223 @@ @AnnotatedFor("nullness") public class NoElementQualifierHierarchy extends QualifierHierarchy { - /** {@link QualifierKindHierarchy}. */ - protected final QualifierKindHierarchy qualifierKindHierarchy; + /** {@link QualifierKindHierarchy}. */ + protected final QualifierKindHierarchy qualifierKindHierarchy; - /** Set of top annotation mirrors. */ - protected final AnnotationMirrorSet tops; + /** Set of top annotation mirrors. */ + protected final AnnotationMirrorSet tops; - /** Set of bottom annotation mirrors. */ - protected final AnnotationMirrorSet bottoms; + /** Set of bottom annotation mirrors. */ + protected final AnnotationMirrorSet bottoms; - /** Mapping from {@link QualifierKind} to its corresponding {@link AnnotationMirror}. */ - protected final Map kindToAnnotationMirror; + /** Mapping from {@link QualifierKind} to its corresponding {@link AnnotationMirror}. */ + protected final Map kindToAnnotationMirror; - /** Set of all annotations in all the hierarchies. */ - protected final Set qualifiers; + /** Set of all annotations in all the hierarchies. */ + protected final Set qualifiers; - /** - * Creates a NoElementQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - * @param atypeFactory the associated type factory - */ - public NoElementQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** + * Creates a NoElementQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + * @param atypeFactory the associated type factory + */ + public NoElementQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); - this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); + this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); - this.kindToAnnotationMirror = createAnnotationMirrors(elements); - this.qualifiers = AnnotationMirrorSet.unmodifiableSet(kindToAnnotationMirror.values()); + this.kindToAnnotationMirror = createAnnotationMirrors(elements); + this.qualifiers = AnnotationMirrorSet.unmodifiableSet(kindToAnnotationMirror.values()); - this.tops = createTops(); - this.bottoms = createBottoms(); - } + this.tops = createTops(); + this.bottoms = createBottoms(); + } - /** - * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of - * QualifierKindHierarchy.) - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @return the newly created qualifier kind hierarchy - */ - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization NoElementQualifierHierarchy this, - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses); - } + /** + * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of + * QualifierKindHierarchy.) + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @return the newly created qualifier kind hierarchy + */ + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization NoElementQualifierHierarchy this, + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses); + } - /** - * Creates and returns a mapping from qualifier kind to an annotation mirror created from the - * qualifier kind's annotation class. - * - * @param elements element utils - * @return a mapping from qualifier kind to its annotation mirror - */ - @RequiresNonNull("this.qualifierKindHierarchy") - protected Map createAnnotationMirrors( - @UnderInitialization NoElementQualifierHierarchy this, Elements elements) { - Map quals = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { - if (kind.hasElements()) { - throw new TypeSystemError(kind + " has elements"); - } - quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); - } - return Collections.unmodifiableMap(quals); + /** + * Creates and returns a mapping from qualifier kind to an annotation mirror created from the + * qualifier kind's annotation class. + * + * @param elements element utils + * @return a mapping from qualifier kind to its annotation mirror + */ + @RequiresNonNull("this.qualifierKindHierarchy") + protected Map createAnnotationMirrors( + @UnderInitialization NoElementQualifierHierarchy this, Elements elements) { + Map quals = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { + if (kind.hasElements()) { + throw new TypeSystemError(kind + " has elements"); + } + quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); } + return Collections.unmodifiableMap(quals); + } - /** - * Creates and returns the unmodifiable set of top {@link AnnotationMirror}s. - * - * @return the unmodifiable set of top {@link AnnotationMirror}s - */ - @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) - protected AnnotationMirrorSet createTops( - @UnderInitialization NoElementQualifierHierarchy this) { - AnnotationMirrorSet tops = new AnnotationMirrorSet(); - for (QualifierKind top : qualifierKindHierarchy.getTops()) { - @SuppressWarnings( - "nullness:assignment.type.incompatible" // All QualifierKinds are keys in - // kindToAnnotationMirror - ) - @NonNull AnnotationMirror topAnno = kindToAnnotationMirror.get(top); - tops.add(topAnno); - } - return AnnotationMirrorSet.unmodifiableSet(tops); + /** + * Creates and returns the unmodifiable set of top {@link AnnotationMirror}s. + * + * @return the unmodifiable set of top {@link AnnotationMirror}s + */ + @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) + protected AnnotationMirrorSet createTops(@UnderInitialization NoElementQualifierHierarchy this) { + AnnotationMirrorSet tops = new AnnotationMirrorSet(); + for (QualifierKind top : qualifierKindHierarchy.getTops()) { + @SuppressWarnings("nullness:assignment.type.incompatible" // All QualifierKinds are keys in + // kindToAnnotationMirror + ) + @NonNull AnnotationMirror topAnno = kindToAnnotationMirror.get(top); + tops.add(topAnno); } + return AnnotationMirrorSet.unmodifiableSet(tops); + } - /** - * Creates and returns the unmodifiable set of bottom {@link AnnotationMirror}s. - * - * @return the unmodifiable set of bottom {@link AnnotationMirror}s - */ - @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) - protected AnnotationMirrorSet createBottoms( - @UnderInitialization NoElementQualifierHierarchy this) { - AnnotationMirrorSet bottoms = new AnnotationMirrorSet(); - for (QualifierKind bottom : qualifierKindHierarchy.getBottoms()) { - @SuppressWarnings( - "nullness:assignment.type.incompatible" // All QualifierKinds are keys in - // kindToAnnotationMirror - ) - @NonNull AnnotationMirror bottomAnno = kindToAnnotationMirror.get(bottom); - bottoms.add(bottomAnno); - } - return AnnotationMirrorSet.unmodifiableSet(bottoms); + /** + * Creates and returns the unmodifiable set of bottom {@link AnnotationMirror}s. + * + * @return the unmodifiable set of bottom {@link AnnotationMirror}s + */ + @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) + protected AnnotationMirrorSet createBottoms( + @UnderInitialization NoElementQualifierHierarchy this) { + AnnotationMirrorSet bottoms = new AnnotationMirrorSet(); + for (QualifierKind bottom : qualifierKindHierarchy.getBottoms()) { + @SuppressWarnings("nullness:assignment.type.incompatible" // All QualifierKinds are keys in + // kindToAnnotationMirror + ) + @NonNull AnnotationMirror bottomAnno = kindToAnnotationMirror.get(bottom); + bottoms.add(bottomAnno); } + return AnnotationMirrorSet.unmodifiableSet(bottoms); + } - /** - * Returns the {@link QualifierKind} for the given annotation. - * - * @param anno an annotation that is a qualifier in this - * @return the {@code QualifierKind} for the given annotation - */ - protected QualifierKind getQualifierKind(AnnotationMirror anno) { - String name = AnnotationUtils.annotationName(anno); - QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); - if (kind == null) { - throw new BugInCF("Annotation not in hierarchy: %s", anno); - } - return kind; + /** + * Returns the {@link QualifierKind} for the given annotation. + * + * @param anno an annotation that is a qualifier in this + * @return the {@code QualifierKind} for the given annotation + */ + protected QualifierKind getQualifierKind(AnnotationMirror anno) { + String name = AnnotationUtils.annotationName(anno); + QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); + if (kind == null) { + throw new BugInCF("Annotation not in hierarchy: %s", anno); } + return kind; + } - @Override - public @Nullable AnnotationMirror findAnnotationInSameHierarchy( - Collection annos, AnnotationMirror annotationMirror) { - if (annos.isEmpty()) { - return null; - } - QualifierKind kind = getQualifierKind(annotationMirror); - for (AnnotationMirror candidate : annos) { - QualifierKind candidateKind = getQualifierKind(candidate); - if (candidateKind.isInSameHierarchyAs(kind)) { - return candidate; - } - } - return null; + @Override + public @Nullable AnnotationMirror findAnnotationInSameHierarchy( + Collection annos, AnnotationMirror annotationMirror) { + if (annos.isEmpty()) { + return null; } - - @Override - public @Nullable AnnotationMirror findAnnotationInHierarchy( - Collection annos, AnnotationMirror top) { - return findAnnotationInSameHierarchy(annos, top); + QualifierKind kind = getQualifierKind(annotationMirror); + for (AnnotationMirror candidate : annos) { + QualifierKind candidateKind = getQualifierKind(candidate); + if (candidateKind.isInSameHierarchyAs(kind)) { + return candidate; + } } + return null; + } - @Override - public AnnotationMirrorSet getTopAnnotations() { - return tops; - } + @Override + public @Nullable AnnotationMirror findAnnotationInHierarchy( + Collection annos, AnnotationMirror top) { + return findAnnotationInSameHierarchy(annos, top); + } - @Override - @SuppressWarnings( - "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding - // kindToAnnotationMirror - ) - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - return kindToAnnotationMirror.get(kind.getTop()); - } + @Override + public AnnotationMirrorSet getTopAnnotations() { + return tops; + } - @Override - public AnnotationMirrorSet getBottomAnnotations() { - return bottoms; - } + @Override + @SuppressWarnings( + "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding + // kindToAnnotationMirror + ) + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + return kindToAnnotationMirror.get(kind.getTop()); + } - @Override - @SuppressWarnings( - "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding - // kindToAnnotationMirror - ) - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - return kindToAnnotationMirror.get(kind.getBottom()); - } + @Override + public AnnotationMirrorSet getBottomAnnotations() { + return bottoms; + } - @Override - public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { - QualifierKind poly = getQualifierKind(start).getPolymorphic(); - if (poly == null) { - return null; - } - return kindToAnnotationMirror.get(poly); - } + @Override + @SuppressWarnings( + "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding + // kindToAnnotationMirror + ) + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + return kindToAnnotationMirror.get(kind.getBottom()); + } - @Override - public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { - return getQualifierKind(qualifier).isPoly(); + @Override + public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { + QualifierKind poly = getQualifierKind(start).getPolymorphic(); + if (poly == null) { + return null; } + return kindToAnnotationMirror.get(poly); + } - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - QualifierKind subKind = getQualifierKind(subAnno); - QualifierKind superKind = getQualifierKind(superAnno); - return subKind.isSubtypeOf(superKind); - } + @Override + public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { + return getQualifierKind(qualifier).isPoly(); + } + + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + QualifierKind subKind = getQualifierKind(subAnno); + QualifierKind superKind = getQualifierKind(superAnno); + return subKind.isSubtypeOf(superKind); + } - @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); + @Override + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); - QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); - if (lub == null) { - return null; - } - return kindToAnnotationMirror.get(lub); + QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); + if (lub == null) { + return null; } + return kindToAnnotationMirror.get(lub); + } - @Override - public @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); - QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); - if (glb == null) { - return null; - } - return kindToAnnotationMirror.get(glb); + @Override + public @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); + QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); + if (glb == null) { + return null; } + return kindToAnnotationMirror.get(glb); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java index b53fb03b587..e78f60aed97 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java @@ -1,5 +1,11 @@ package org.checkerframework.framework.type; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.mustcall.qual.MustCallUnknown; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -8,14 +14,6 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * Represents multiple type qualifier hierarchies. {@link #getWidth} gives the number of hierarchies * that this object represents. Each hierarchy has its own top and bottom, and subtyping @@ -32,658 +30,638 @@ @AnnotatedFor("nullness") public abstract class QualifierHierarchy { - /** The associated type factory. This is used only for checking whether types are relevant. */ - protected GenericAnnotatedTypeFactory atypeFactory; - - /** - * Creates a new QualifierHierarchy. - * - * @param atypeFactory the associated type factory - */ - public QualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; + /** The associated type factory. This is used only for checking whether types are relevant. */ + protected GenericAnnotatedTypeFactory atypeFactory; + + /** + * Creates a new QualifierHierarchy. + * + * @param atypeFactory the associated type factory + */ + public QualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } + + /** + * Determine whether this QualifierHierarchy is valid. + * + * @return true if this QualifierHierarchy is valid + */ + public boolean isValid() { + return true; + } + + // ********************************************************************** + // Getter methods about this hierarchy + // ********************************************************************** + + /** + * Returns the width of this hierarchy, i.e. the expected number of annotations on any valid type. + * + * @return the width of this QualifierHierarchy + */ + public int getWidth() { + return getTopAnnotations().size(); + } + + /** + * Returns the top (ultimate super) type qualifiers in the type system. The size of this set is + * equal to {@link #getWidth}. + * + * @return the top (ultimate super) type qualifiers in the type system + */ + public abstract AnnotationMirrorSet getTopAnnotations(); + + /** + * Return the top qualifier for the given qualifier, that is, the qualifier that is a supertype of + * {@code qualifier} but no further supertypes exist. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the top qualifier of {@code qualifier}'s hierarchy + */ + public abstract AnnotationMirror getTopAnnotation(AnnotationMirror qualifier); + + /** + * Returns the bottom type qualifiers in the hierarchy. The size of this set is equal to {@link + * #getWidth}. + * + * @return the bottom type qualifiers in the hierarchy + */ + public abstract AnnotationMirrorSet getBottomAnnotations(); + + /** + * Return the bottom for the given qualifier, that is, the qualifier that is a subtype of {@code + * qualifier} but no further subtypes exist. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the bottom qualifier of {@code qualifier}'s hierarchy + */ + public abstract AnnotationMirror getBottomAnnotation(AnnotationMirror qualifier); + + /** + * Returns the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy + */ + public abstract @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror qualifier); + + /** + * Returns {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code + * false}. + * + * @param qualifier qualifier + * @return {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code + * false}. + */ + public abstract boolean isPolymorphicQualifier(AnnotationMirror qualifier); + + /** + * Returns the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no parametric qualifier in that hierarchy. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy + */ + public @Nullable AnnotationMirror getParametricQualifier(AnnotationMirror qualifier) { + return null; + } + + /** + * Returns {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code + * false}. + * + * @param qualifier qualifier + * @return {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code + * false}. + */ + public boolean isParametricQualifier(AnnotationMirror qualifier) { + return false; + } + + // ********************************************************************** + // Qualifier Hierarchy Queries + // ********************************************************************** + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy, ignoring Java basetypes. + * + *

Clients should generally call {@link #isSubtypeShallow}. However, subtypes should generally + * override this method (if needed). + * + *

This method behaves the same as {@link #isSubtypeQualifiersOnly(AnnotationMirror, + * AnnotationMirror)}, which calls this method. This method is for clients inside the framework, + * and it has {@code protected} access to prevent use by clients outside the framework. This makes + * it easy to find places where code outside the framework is ignoring Java basetypes -- at calls + * to {@link #isSubtypeQualifiersOnly}. + * + * @param subQualifier possible subqualifier + * @param superQualifier possible superqualifier + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} + */ + protected abstract boolean isSubtypeQualifiers( + AnnotationMirror subQualifier, AnnotationMirror superQualifier); + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy, ignoring Java basetypes. + * + *

This method is for clients outside the framework, and should not be used by framework code. + * + * @param subQualifier possible subqualifier + * @param superQualifier possible superqualifier + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} + */ + public final boolean isSubtypeQualifiersOnly( + AnnotationMirror subQualifier, AnnotationMirror superQualifier) { + return isSubtypeQualifiers(subQualifier, superQualifier); + } + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy. The types {@code subType} and {@code superType} are + * not necessarily in a Java subtyping relationship with one another and are only used by this + * method for special cases when qualifier subtyping depends on the Java basetype. + * + *

Clients should usually call {@code isSubtypeShallow()} (this method). Rarely, to ignore the + * Java basetype, a client can call {@link #isSubtypeQualifiersOnly}. + * + *

Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier + * subtyping depends on Java basetypes. + * + * @param subQualifier possible subqualifier + * @param subType the Java basetype associated with {@code subQualifier} + * @param superQualifier possible superqualifier + * @param superType the Java basetype associated with {@code superQualifier} + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} + */ + @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. + public boolean isSubtypeShallow( + AnnotationMirror subQualifier, + TypeMirror subType, + AnnotationMirror superQualifier, + TypeMirror superType) { + if (!atypeFactory.isRelevant(subType) || !atypeFactory.isRelevant(superType)) { + // At least one of the types is not relevant. + return true; } - - /** - * Determine whether this QualifierHierarchy is valid. - * - * @return true if this QualifierHierarchy is valid - */ - public boolean isValid() { - return true; - } - - // ********************************************************************** - // Getter methods about this hierarchy - // ********************************************************************** - - /** - * Returns the width of this hierarchy, i.e. the expected number of annotations on any valid - * type. - * - * @return the width of this QualifierHierarchy - */ - public int getWidth() { - return getTopAnnotations().size(); - } - - /** - * Returns the top (ultimate super) type qualifiers in the type system. The size of this set is - * equal to {@link #getWidth}. - * - * @return the top (ultimate super) type qualifiers in the type system - */ - public abstract AnnotationMirrorSet getTopAnnotations(); - - /** - * Return the top qualifier for the given qualifier, that is, the qualifier that is a supertype - * of {@code qualifier} but no further supertypes exist. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the top qualifier of {@code qualifier}'s hierarchy - */ - public abstract AnnotationMirror getTopAnnotation(AnnotationMirror qualifier); - - /** - * Returns the bottom type qualifiers in the hierarchy. The size of this set is equal to {@link - * #getWidth}. - * - * @return the bottom type qualifiers in the hierarchy - */ - public abstract AnnotationMirrorSet getBottomAnnotations(); - - /** - * Return the bottom for the given qualifier, that is, the qualifier that is a subtype of {@code - * qualifier} but no further subtypes exist. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the bottom qualifier of {@code qualifier}'s hierarchy - */ - public abstract AnnotationMirror getBottomAnnotation(AnnotationMirror qualifier); - - /** - * Returns the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no polymorphic qualifier in that hierarchy. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no polymorphic qualifier in that hierarchy - */ - public abstract @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror qualifier); - - /** - * Returns {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code - * false}. - * - * @param qualifier qualifier - * @return {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code - * false}. - */ - public abstract boolean isPolymorphicQualifier(AnnotationMirror qualifier); - - /** - * Returns the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no parametric qualifier in that hierarchy. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no polymorphic qualifier in that hierarchy - */ - public @Nullable AnnotationMirror getParametricQualifier(AnnotationMirror qualifier) { - return null; - } - - /** - * Returns {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code - * false}. - * - * @param qualifier qualifier - * @return {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code - * false}. - */ - public boolean isParametricQualifier(AnnotationMirror qualifier) { + return isSubtypeQualifiers(subQualifier, superQualifier); + } + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy. The type {@code typeMirror} is only used by this + * method for special cases when qualifier subtyping depends on the Java basetype. + * + *

Clients should usually call {@link #isSubtypeShallow(AnnotationMirror, AnnotationMirror, + * TypeMirror)} (this method) or {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, + * AnnotationMirror, TypeMirror)}. Rarely, to ignore the Java basetype, a client can call {@link + * #isSubtypeQualifiersOnly}. + * + *

Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier + * subtyping depends on Java basetypes. + * + * @param subQualifier possible subqualifier + * @param superQualifier possible superqualifier + * @param typeMirror the Java basetype associated with both {@code subQualifier} and {@code + * superQualifier} + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} + */ + public final boolean isSubtypeShallow( + AnnotationMirror subQualifier, AnnotationMirror superQualifier, TypeMirror typeMirror) { + return isSubtypeShallow(subQualifier, typeMirror, superQualifier, typeMirror); + } + + /** + * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers}. The types {@code subType} and + * {@code superType} are not necessarily in a Java subtyping relationship with one another and are + * only used by this method for special cases when qualifier subtyping depends on the Java + * basetype. + * + *

Subtypes more often override {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, + * AnnotationMirror, TypeMirror)} than this method. + * + * @param subQualifiers set of qualifiers; exactly one per hierarchy + * @param subType the type associated with {@code subQualifiers} + * @param superQualifiers set of qualifiers; exactly one per hierarchy + * @param superType the type associated with {@code superQualifiers} + * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers} + */ + public final boolean isSubtypeShallow( + Collection subQualifiers, + TypeMirror subType, + Collection superQualifiers, + TypeMirror superType) { + assertSameSize(subQualifiers, superQualifiers); + for (AnnotationMirror subQual : subQualifiers) { + AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual); + if (superQual == null) { + throw new BugInCF( + "QualifierHierarchy: missing annotation in hierarchy %s. found: %s", + subQual, StringsPlume.join(",", superQualifiers)); + } + if (!isSubtypeShallow(subQual, subType, superQual, superType)) { return false; + } } - - // ********************************************************************** - // Qualifier Hierarchy Queries - // ********************************************************************** - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy, ignoring Java basetypes. - * - *

Clients should generally call {@link #isSubtypeShallow}. However, subtypes should - * generally override this method (if needed). - * - *

This method behaves the same as {@link #isSubtypeQualifiersOnly(AnnotationMirror, - * AnnotationMirror)}, which calls this method. This method is for clients inside the framework, - * and it has {@code protected} access to prevent use by clients outside the framework. This - * makes it easy to find places where code outside the framework is ignoring Java basetypes -- - * at calls to {@link #isSubtypeQualifiersOnly}. - * - * @param subQualifier possible subqualifier - * @param superQualifier possible superqualifier - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code - * superQualifier} - */ - protected abstract boolean isSubtypeQualifiers( - AnnotationMirror subQualifier, AnnotationMirror superQualifier); - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy, ignoring Java basetypes. - * - *

This method is for clients outside the framework, and should not be used by framework - * code. - * - * @param subQualifier possible subqualifier - * @param superQualifier possible superqualifier - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code - * superQualifier} - */ - public final boolean isSubtypeQualifiersOnly( - AnnotationMirror subQualifier, AnnotationMirror superQualifier) { - return isSubtypeQualifiers(subQualifier, superQualifier); + return true; + } + + /** + * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier of or equal to the + * qualifier in the same hierarchy in {@code superQualifiers}. The type {@code typeMirror} is only + * used by this method for special cases when qualifier subtyping depends on the Java basetype. + * + *

Subtypes more often override {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, + * AnnotationMirror, TypeMirror)} than this method. + * + * @param subQualifiers a set of qualifiers; exactly one per hierarchy + * @param superQualifiers a set of qualifiers; exactly one per hierarchy + * @param typeMirror the type associated with both sets of qualifiers + * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers} + */ + public final boolean isSubtypeShallow( + Collection subQualifiers, + Collection superQualifiers, + TypeMirror typeMirror) { + return isSubtypeShallow(subQualifiers, typeMirror, superQualifiers, typeMirror); + } + + /** + * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code + * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. + * Ignores Java basetypes. + * + *

Examples: + * + *

    + *
  • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable + *
+ * + * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} + * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code qualifier1} + * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from + * different hierarchies + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + protected abstract @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror qualifier1, AnnotationMirror qualifier2); + + /** + * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code + * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. + * Ignores Java basetypes. + * + *

Examples: + * + *

    + *
  • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable + *
+ * + * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} + * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code qualifier1} + * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from + * different hierarchies + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + public final @Nullable AnnotationMirror leastUpperBoundQualifiersOnly( + AnnotationMirror qualifier1, AnnotationMirror qualifier2) { + return leastUpperBoundQualifiers(qualifier1, qualifier2); + } + + /** + * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code + * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. + * + *

Examples: + * + *

    + *
  • leastUpperBound('Nullable', 'NonNull') ⇒ Nullable + *
+ * + * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} + * @param tm1 the type on which qualifier1 appears + * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code qualifier1} + * @param tm2 the type on which qualifier2 appears + * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from + * different hierarchies + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. + public @Nullable AnnotationMirror leastUpperBoundShallow( + AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { + boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); + boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); + if (tm1IsRelevant == tm2IsRelevant) { + return leastUpperBoundQualifiers(qualifier1, qualifier2); + } else if (tm1IsRelevant) { + return qualifier1; + } else { // if (tm2IsRelevant) { + return qualifier2; } - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy. The types {@code subType} and {@code superType} - * are not necessarily in a Java subtyping relationship with one another and are only used by - * this method for special cases when qualifier subtyping depends on the Java basetype. - * - *

Clients should usually call {@code isSubtypeShallow()} (this method). Rarely, to ignore - * the Java basetype, a client can call {@link #isSubtypeQualifiersOnly}. - * - *

Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier - * subtyping depends on Java basetypes. - * - * @param subQualifier possible subqualifier - * @param subType the Java basetype associated with {@code subQualifier} - * @param superQualifier possible superqualifier - * @param superType the Java basetype associated with {@code superQualifier} - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code - * superQualifier} - */ - @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. - public boolean isSubtypeShallow( - AnnotationMirror subQualifier, - TypeMirror subType, - AnnotationMirror superQualifier, - TypeMirror superType) { - if (!atypeFactory.isRelevant(subType) || !atypeFactory.isRelevant(superType)) { - // At least one of the types is not relevant. - return true; - } - return isSubtypeQualifiers(subQualifier, superQualifier); - } - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy. The type {@code typeMirror} is only used by this - * method for special cases when qualifier subtyping depends on the Java basetype. - * - *

Clients should usually call {@link #isSubtypeShallow(AnnotationMirror, AnnotationMirror, - * TypeMirror)} (this method) or {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, - * AnnotationMirror, TypeMirror)}. Rarely, to ignore the Java basetype, a client can call {@link - * #isSubtypeQualifiersOnly}. - * - *

Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier - * subtyping depends on Java basetypes. - * - * @param subQualifier possible subqualifier - * @param superQualifier possible superqualifier - * @param typeMirror the Java basetype associated with both {@code subQualifier} and {@code - * superQualifier} - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code - * superQualifier} - */ - public final boolean isSubtypeShallow( - AnnotationMirror subQualifier, AnnotationMirror superQualifier, TypeMirror typeMirror) { - return isSubtypeShallow(subQualifier, typeMirror, superQualifier, typeMirror); + } + + /** + * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 set of qualifiers; exactly one per hierarchy + * @param tm1 the type on which qualifiers1 appear + * @param qualifiers2 set of qualifiers; exactly one per hierarchy + * @param tm2 the type on which qualifiers2 appear + * @return the least upper bound of the two sets of qualifiers + */ + public final Set leastUpperBoundsShallow( + Collection qualifiers1, + TypeMirror tm1, + Collection qualifiers2, + TypeMirror tm2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); } - /** - * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers}. The types {@code subType} and - * {@code superType} are not necessarily in a Java subtyping relationship with one another and - * are only used by this method for special cases when qualifier subtyping depends on the Java - * basetype. - * - *

Subtypes more often override {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, - * AnnotationMirror, TypeMirror)} than this method. - * - * @param subQualifiers set of qualifiers; exactly one per hierarchy - * @param subType the type associated with {@code subQualifiers} - * @param superQualifiers set of qualifiers; exactly one per hierarchy - * @param superType the type associated with {@code superQualifiers} - * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers} - */ - public final boolean isSubtypeShallow( - Collection subQualifiers, - TypeMirror subType, - Collection superQualifiers, - TypeMirror superType) { - assertSameSize(subQualifiers, superQualifiers); - for (AnnotationMirror subQual : subQualifiers) { - AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual); - if (superQual == null) { - throw new BugInCF( - "QualifierHierarchy: missing annotation in hierarchy %s. found: %s", - subQual, StringsPlume.join(",", superQualifiers)); - } - if (!isSubtypeShallow(subQual, subType, superQual, superType)) { - return false; - } + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror lub = leastUpperBoundShallow(a1, tm1, a2, tm2); + if (lub != null) { + result.add(lub); } - return true; + } } - /** - * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier of or equal to the - * qualifier in the same hierarchy in {@code superQualifiers}. The type {@code typeMirror} is - * only used by this method for special cases when qualifier subtyping depends on the Java - * basetype. - * - *

Subtypes more often override {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, - * AnnotationMirror, TypeMirror)} than this method. - * - * @param subQualifiers a set of qualifiers; exactly one per hierarchy - * @param superQualifiers a set of qualifiers; exactly one per hierarchy - * @param typeMirror the type associated with both sets of qualifiers - * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers} - */ - public final boolean isSubtypeShallow( - Collection subQualifiers, - Collection superQualifiers, - TypeMirror typeMirror) { - return isSubtypeShallow(subQualifiers, typeMirror, superQualifiers, typeMirror); + assertSameSize(result, qualifiers1); + return result; + } + + /** + * Returns the number of iterations dataflow should perform before {@link + * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never be + * called. + * + * @return the number of iterations dataflow should perform before {@link + * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never + * be called. + */ + public int numberOfIterationsBeforeWidening() { + return -1; + } + + /** + * If the qualifier hierarchy has an infinite ascending chain, then the dataflow analysis might + * never reach a fixed point. To prevent this, implement this method such that it returns an upper + * bound for the two qualifiers that is a strict super type of the least upper bound. If this + * method is implemented, also override {@link #numberOfIterationsBeforeWidening()} to return a + * positive number. + * + *

{@code newQualifier} is newest qualifier dataflow computed for some expression and {@code + * previousQualifier} is the qualifier dataflow computed on the last iteration. + * + *

If the qualifier hierarchy has no infinite ascending chain, returns the least upper bound of + * the two annotations. + * + * @param newQualifier new qualifier dataflow computed for some expression; must be in the same + * hierarchy as {@code previousQualifier} + * @param previousQualifier the previous qualifier dataflow computed on the last iteration; must + * be in the same hierarchy as {@code previousQualifier} + * @return an upper bound that is higher than the least upper bound of newQualifier and + * previousQualifier (or the lub if the qualifier hierarchy does not require this) + */ + public AnnotationMirror widenedUpperBound( + AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { + AnnotationMirror widenedUpperBound = leastUpperBoundQualifiers(newQualifier, previousQualifier); + if (widenedUpperBound == null) { + throw new BugInCF( + "widenedUpperBound(%s, %s): unrelated qualifiers", newQualifier, previousQualifier); } - - /** - * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code - * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier - * hierarchy. Ignores Java basetypes. - * - *

Examples: - * - *

    - *
  • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable - *
- * - * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} - * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code - * qualifier1} - * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from - * different hierarchies - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - protected abstract @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror qualifier1, AnnotationMirror qualifier2); - - /** - * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code - * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier - * hierarchy. Ignores Java basetypes. - * - *

Examples: - * - *

    - *
  • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable - *
- * - * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} - * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code - * qualifier1} - * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from - * different hierarchies - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - public final @Nullable AnnotationMirror leastUpperBoundQualifiersOnly( - AnnotationMirror qualifier1, AnnotationMirror qualifier2) { - return leastUpperBoundQualifiers(qualifier1, qualifier2); + return widenedUpperBound; + } + + /** + * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null if + * the qualifiers are not from the same qualifier hierarchy. + * + * @param qualifier1 first qualifier + * @param qualifier2 second qualifier + * @return greatest lower bound of the two annotations, or null if the two annotations are not + * from the same hierarchy + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + protected abstract @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror qualifier1, AnnotationMirror qualifier2); + + /** + * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null if + * the qualifiers are not from the same qualifier hierarchy. + * + * @param qualifier1 first qualifier + * @param qualifier2 second qualifier + * @return greatest lower bound of the two annotations, or null if the two annotations are not + * from the same hierarchy + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + public final @Nullable AnnotationMirror greatestLowerBoundQualifiersOnly( + AnnotationMirror qualifier1, AnnotationMirror qualifier2) { + return greatestLowerBoundQualifiers(qualifier1, qualifier2); + } + + /** + * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null if + * the qualifiers are not from the same qualifier hierarchy. + * + * @param qualifier1 first qualifier + * @param tm1 the type that is annotated by qualifier1 + * @param qualifier2 second qualifier + * @param tm2 the type that is annotated by qualifier2 + * @return greatest lower bound of the two annotations, or null if the two annotations are not + * from the same hierarchy + */ + @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. + public @Nullable AnnotationMirror greatestLowerBoundShallow( + AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { + boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); + boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); + if (tm1IsRelevant == tm2IsRelevant) { + return greatestLowerBoundQualifiers(qualifier1, qualifier2); + } else if (tm1IsRelevant) { + return qualifier1; + } else { // if (tm2IsRelevant) { + return qualifier2; } - - /** - * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code - * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier - * hierarchy. - * - *

Examples: - * - *

    - *
  • leastUpperBound('Nullable', 'NonNull') ⇒ Nullable - *
- * - * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} - * @param tm1 the type on which qualifier1 appears - * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code - * qualifier1} - * @param tm2 the type on which qualifier2 appears - * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from - * different hierarchies - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. - public @Nullable AnnotationMirror leastUpperBoundShallow( - AnnotationMirror qualifier1, - TypeMirror tm1, - AnnotationMirror qualifier2, - TypeMirror tm2) { - boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); - boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); - if (tm1IsRelevant == tm2IsRelevant) { - return leastUpperBoundQualifiers(qualifier1, qualifier2); - } else if (tm1IsRelevant) { - return qualifier1; - } else { // if (tm2IsRelevant) { - return qualifier2; - } + } + + /** + * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 set of qualifiers; exactly one per hierarchy + * @param tm1 the type that is annotated by qualifier1 + * @param qualifiers2 set of qualifiers; exactly one per hierarchy + * @param tm2 the type that is annotated by qualifier2 + * @return the greatest lower bound of the two sets of qualifiers + */ + public final Set greatestLowerBoundsShallow( + Collection qualifiers1, + TypeMirror tm1, + Collection qualifiers2, + TypeMirror tm2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty" + " sets"); } - /** - * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the - * qualifier for the same hierarchy in each set. - * - * @param qualifiers1 set of qualifiers; exactly one per hierarchy - * @param tm1 the type on which qualifiers1 appear - * @param qualifiers2 set of qualifiers; exactly one per hierarchy - * @param tm2 the type on which qualifiers2 appear - * @return the least upper bound of the two sets of qualifiers - */ - public final Set leastUpperBoundsShallow( - Collection qualifiers1, - TypeMirror tm1, - Collection qualifiers2, - TypeMirror tm2) { - assertSameSize(qualifiers1, qualifiers2); - if (qualifiers1.isEmpty()) { - throw new BugInCF( - "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror glb = greatestLowerBoundShallow(a1, tm1, a2, tm2); + if (glb != null) { + result.add(glb); } - - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror a1 : qualifiers1) { - for (AnnotationMirror a2 : qualifiers2) { - AnnotationMirror lub = leastUpperBoundShallow(a1, tm1, a2, tm2); - if (lub != null) { - result.add(lub); - } - } - } - - assertSameSize(result, qualifiers1); - return result; - } - - /** - * Returns the number of iterations dataflow should perform before {@link - * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never be - * called. - * - * @return the number of iterations dataflow should perform before {@link - * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should - * never be called. - */ - public int numberOfIterationsBeforeWidening() { - return -1; - } - - /** - * If the qualifier hierarchy has an infinite ascending chain, then the dataflow analysis might - * never reach a fixed point. To prevent this, implement this method such that it returns an - * upper bound for the two qualifiers that is a strict super type of the least upper bound. If - * this method is implemented, also override {@link #numberOfIterationsBeforeWidening()} to - * return a positive number. - * - *

{@code newQualifier} is newest qualifier dataflow computed for some expression and {@code - * previousQualifier} is the qualifier dataflow computed on the last iteration. - * - *

If the qualifier hierarchy has no infinite ascending chain, returns the least upper bound - * of the two annotations. - * - * @param newQualifier new qualifier dataflow computed for some expression; must be in the same - * hierarchy as {@code previousQualifier} - * @param previousQualifier the previous qualifier dataflow computed on the last iteration; must - * be in the same hierarchy as {@code previousQualifier} - * @return an upper bound that is higher than the least upper bound of newQualifier and - * previousQualifier (or the lub if the qualifier hierarchy does not require this) - */ - public AnnotationMirror widenedUpperBound( - AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { - AnnotationMirror widenedUpperBound = - leastUpperBoundQualifiers(newQualifier, previousQualifier); - if (widenedUpperBound == null) { - throw new BugInCF( - "widenedUpperBound(%s, %s): unrelated qualifiers", - newQualifier, previousQualifier); - } - return widenedUpperBound; - } - - /** - * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null - * if the qualifiers are not from the same qualifier hierarchy. - * - * @param qualifier1 first qualifier - * @param qualifier2 second qualifier - * @return greatest lower bound of the two annotations, or null if the two annotations are not - * from the same hierarchy - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - protected abstract @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror qualifier1, AnnotationMirror qualifier2); - - /** - * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null - * if the qualifiers are not from the same qualifier hierarchy. - * - * @param qualifier1 first qualifier - * @param qualifier2 second qualifier - * @return greatest lower bound of the two annotations, or null if the two annotations are not - * from the same hierarchy - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - public final @Nullable AnnotationMirror greatestLowerBoundQualifiersOnly( - AnnotationMirror qualifier1, AnnotationMirror qualifier2) { - return greatestLowerBoundQualifiers(qualifier1, qualifier2); - } - - /** - * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null - * if the qualifiers are not from the same qualifier hierarchy. - * - * @param qualifier1 first qualifier - * @param tm1 the type that is annotated by qualifier1 - * @param qualifier2 second qualifier - * @param tm2 the type that is annotated by qualifier2 - * @return greatest lower bound of the two annotations, or null if the two annotations are not - * from the same hierarchy - */ - @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. - public @Nullable AnnotationMirror greatestLowerBoundShallow( - AnnotationMirror qualifier1, - TypeMirror tm1, - AnnotationMirror qualifier2, - TypeMirror tm2) { - boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); - boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); - if (tm1IsRelevant == tm2IsRelevant) { - return greatestLowerBoundQualifiers(qualifier1, qualifier2); - } else if (tm1IsRelevant) { - return qualifier1; - } else { // if (tm2IsRelevant) { - return qualifier2; - } - } - - /** - * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the - * qualifier for the same hierarchy in each set. - * - * @param qualifiers1 set of qualifiers; exactly one per hierarchy - * @param tm1 the type that is annotated by qualifier1 - * @param qualifiers2 set of qualifiers; exactly one per hierarchy - * @param tm2 the type that is annotated by qualifier2 - * @return the greatest lower bound of the two sets of qualifiers - */ - public final Set greatestLowerBoundsShallow( - Collection qualifiers1, - TypeMirror tm1, - Collection qualifiers2, - TypeMirror tm2) { - assertSameSize(qualifiers1, qualifiers2); - if (qualifiers1.isEmpty()) { - throw new BugInCF( - "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty" - + " sets"); - } - - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror a1 : qualifiers1) { - for (AnnotationMirror a2 : qualifiers2) { - AnnotationMirror glb = greatestLowerBoundShallow(a1, tm1, a2, tm2); - if (glb != null) { - result.add(glb); - } - } - } - - assertSameSize(qualifiers1, qualifiers2, result); - return result; - } - - /** - * Returns true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set - * with fewer qualifiers than the width of the QualifierHierarchy. - * - * @param type the type to test - * @return true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set - * with fewer qualifiers than the width of the QualifierHierarchy - */ - public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { - return type.getKind() == TypeKind.TYPEVAR - || type.getKind() == TypeKind.WILDCARD - || - // TODO: or should the union/intersection be the LUB of the alternatives? - type.getKind() == TypeKind.UNION - || type.getKind() == TypeKind.INTERSECTION; - } - - /** - * Returns the annotation in {@code qualifiers} that is in the same hierarchy as {@code - * qualifier}. - * - *

The default implementation calls {@link #getTopAnnotation(AnnotationMirror)} and then - * calls {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)}. So, if {@code - * qualifier} is a top qualifier, then call {@link #findAnnotationInHierarchy(Collection, - * AnnotationMirror)} directly is faster. - * - * @param qualifiers set of annotations to search - * @param qualifier annotation that is in the same hierarchy as the returned annotation - * @return annotation in the same hierarchy as qualifier, or null if one is not found - */ - public @Nullable AnnotationMirror findAnnotationInSameHierarchy( - Collection qualifiers, AnnotationMirror qualifier) { - AnnotationMirror top = this.getTopAnnotation(qualifier); - return findAnnotationInHierarchy(qualifiers, top); + } } - /** - * Returns the annotation in {@code qualifiers} that is in the hierarchy for which {@code top} - * is top. - * - * @param qualifiers set of annotations to search - * @param top the top annotation in the hierarchy to which the returned annotation belongs - * @return annotation in the same hierarchy as annotationMirror, or null if one is not found - */ - public @Nullable AnnotationMirror findAnnotationInHierarchy( - Collection qualifiers, AnnotationMirror top) { - for (AnnotationMirror anno : qualifiers) { - if (isSubtypeQualifiers(anno, top)) { - return anno; - } - } - return null; + assertSameSize(qualifiers1, qualifiers2, result); + return result; + } + + /** + * Returns true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set with + * fewer qualifiers than the width of the QualifierHierarchy. + * + * @param type the type to test + * @return true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set with + * fewer qualifiers than the width of the QualifierHierarchy + */ + public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { + return type.getKind() == TypeKind.TYPEVAR + || type.getKind() == TypeKind.WILDCARD + || + // TODO: or should the union/intersection be the LUB of the alternatives? + type.getKind() == TypeKind.UNION + || type.getKind() == TypeKind.INTERSECTION; + } + + /** + * Returns the annotation in {@code qualifiers} that is in the same hierarchy as {@code + * qualifier}. + * + *

The default implementation calls {@link #getTopAnnotation(AnnotationMirror)} and then calls + * {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)}. So, if {@code qualifier} is a + * top qualifier, then call {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)} + * directly is faster. + * + * @param qualifiers set of annotations to search + * @param qualifier annotation that is in the same hierarchy as the returned annotation + * @return annotation in the same hierarchy as qualifier, or null if one is not found + */ + public @Nullable AnnotationMirror findAnnotationInSameHierarchy( + Collection qualifiers, AnnotationMirror qualifier) { + AnnotationMirror top = this.getTopAnnotation(qualifier); + return findAnnotationInHierarchy(qualifiers, top); + } + + /** + * Returns the annotation in {@code qualifiers} that is in the hierarchy for which {@code top} is + * top. + * + * @param qualifiers set of annotations to search + * @param top the top annotation in the hierarchy to which the returned annotation belongs + * @return annotation in the same hierarchy as annotationMirror, or null if one is not found + */ + public @Nullable AnnotationMirror findAnnotationInHierarchy( + Collection qualifiers, AnnotationMirror top) { + for (AnnotationMirror anno : qualifiers) { + if (isSubtypeQualifiers(anno, top)) { + return anno; + } } - - /** - * Update a mapping from {@code key} to a set of AnnotationMirrors. If {@code key} is not - * already in the map, then put it in the map with a value of a new set containing {@code - * qualifier}. If the map contains {@code key}, then add {@code qualifier} to the set to which - * {@code key} maps. If that set contains a qualifier in the same hierarchy as {@code - * qualifier}, then don't add it and return false. - * - * @param map the mapping to modify - * @param key the key to update or add - * @param qualifier the value to update or add - * @param type of the map's keys - * @return true if the update was done; false if there was a qualifier hierarchy collision - */ - public boolean updateMappingToMutableSet( - Map map, T key, AnnotationMirror qualifier) { - // https://github.com/typetools/checker-framework/issues/2000 - @SuppressWarnings("nullness:argument.type.incompatible") - boolean mapContainsKey = map.containsKey(key); - if (mapContainsKey) { - @SuppressWarnings("nullness:assignment.type.incompatible") // key is a key for map. - @NonNull AnnotationMirrorSet prevs = map.get(key); - AnnotationMirror old = findAnnotationInSameHierarchy(prevs, qualifier); - if (old != null) { - return false; - } - prevs.add(qualifier); - map.put(key, prevs); - } else { - AnnotationMirrorSet set = new AnnotationMirrorSet(); - set.add(qualifier); - map.put(key, set); - } - return true; + return null; + } + + /** + * Update a mapping from {@code key} to a set of AnnotationMirrors. If {@code key} is not already + * in the map, then put it in the map with a value of a new set containing {@code qualifier}. If + * the map contains {@code key}, then add {@code qualifier} to the set to which {@code key} maps. + * If that set contains a qualifier in the same hierarchy as {@code qualifier}, then don't add it + * and return false. + * + * @param map the mapping to modify + * @param key the key to update or add + * @param qualifier the value to update or add + * @param type of the map's keys + * @return true if the update was done; false if there was a qualifier hierarchy collision + */ + public boolean updateMappingToMutableSet( + Map map, T key, AnnotationMirror qualifier) { + // https://github.com/typetools/checker-framework/issues/2000 + @SuppressWarnings("nullness:argument.type.incompatible") + boolean mapContainsKey = map.containsKey(key); + if (mapContainsKey) { + @SuppressWarnings("nullness:assignment.type.incompatible") // key is a key for map. + @NonNull AnnotationMirrorSet prevs = map.get(key); + AnnotationMirror old = findAnnotationInSameHierarchy(prevs, qualifier); + if (old != null) { + return false; + } + prevs.add(qualifier); + map.put(key, prevs); + } else { + AnnotationMirrorSet set = new AnnotationMirrorSet(); + set.add(qualifier); + map.put(key, set); } - - /** - * Throws an exception if the given collections do not have the same size. - * - * @param c1 the first collection - * @param c2 the second collection - */ - public static void assertSameSize(Collection c1, Collection c2) { - if (c1.size() != c2.size()) { - throw new BugInCF( - "inconsistent sizes (%d, %d):%n [%s]%n [%s]", - c1.size(), c2.size(), StringsPlume.join(",", c1), StringsPlume.join(",", c2)); - } + return true; + } + + /** + * Throws an exception if the given collections do not have the same size. + * + * @param c1 the first collection + * @param c2 the second collection + */ + public static void assertSameSize(Collection c1, Collection c2) { + if (c1.size() != c2.size()) { + throw new BugInCF( + "inconsistent sizes (%d, %d):%n [%s]%n [%s]", + c1.size(), c2.size(), StringsPlume.join(",", c1), StringsPlume.join(",", c2)); } - - /** - * Throws an exception if the result and the inputs do not all have the same size. - * - * @param c1 the first collection - * @param c2 the second collection - * @param result the result collection - */ - public static void assertSameSize( - @MustCallUnknown Collection c1, - @MustCallUnknown Collection c2, - @MustCallUnknown Collection result) { - if (c1.size() != result.size() || c2.size() != result.size()) { - throw new BugInCF( - "inconsistent sizes (%d, %d, %d):%n %s%n %s%n %s", - c1.size(), - c2.size(), - result.size(), - StringsPlume.join(",", c1), - StringsPlume.join(",", c2), - StringsPlume.join(",", result)); - } + } + + /** + * Throws an exception if the result and the inputs do not all have the same size. + * + * @param c1 the first collection + * @param c2 the second collection + * @param result the result collection + */ + public static void assertSameSize( + @MustCallUnknown Collection c1, + @MustCallUnknown Collection c2, + @MustCallUnknown Collection result) { + if (c1.size() != result.size() || c2.size() != result.size()) { + throw new BugInCF( + "inconsistent sizes (%d, %d, %d):%n %s%n %s%n %s", + c1.size(), + c2.size(), + result.size(), + StringsPlume.join(",", c1), + StringsPlume.join(",", c2), + StringsPlume.join(",", result)); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java index 1cc33a457da..56d3409cc4e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java @@ -1,166 +1,160 @@ package org.checkerframework.framework.type; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.framework.qual.UpperBoundFor; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypesUtils; - import java.lang.annotation.Annotation; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import java.util.Set; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.framework.qual.UpperBoundFor; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypesUtils; /** Class that computes and stores the qualifier upper bounds for type uses. */ public class QualifierUpperBounds { - /** Map from {@link TypeKind} to annotations. */ - private final Map typeKinds; - - /** Map from canonical class name strings to annotations. */ - private final Map<@CanonicalName String, AnnotationMirrorSet> types; - - /** {@link QualifierHierarchy} */ - private final QualifierHierarchy qualHierarchy; - - private final AnnotatedTypeFactory atypeFactory; - - /** - * Creates a {@link QualifierUpperBounds} from the given checker, using that checker to - * determine the annotations that are in the type hierarchy. - */ - public QualifierUpperBounds(AnnotatedTypeFactory typeFactory) { - this.atypeFactory = typeFactory; - this.typeKinds = new EnumMap<>(TypeKind.class); - this.types = new HashMap<>(); - - this.qualHierarchy = typeFactory.getQualifierHierarchy(); - - // Get type qualifiers from the checker. - Set> quals = typeFactory.getSupportedTypeQualifiers(); - - // For each qualifier, read the @UpperBoundFor annotation and put its type classes and kinds - // into maps. - for (Class qual : quals) { - UpperBoundFor upperBoundFor = qual.getAnnotation(UpperBoundFor.class); - if (upperBoundFor == null) { - continue; - } - - AnnotationMirror theQual = - AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); - for (org.checkerframework.framework.qual.TypeKind typeKind : - upperBoundFor.typeKinds()) { - TypeKind mappedTk = mapTypeKinds(typeKind); - addTypeKind(mappedTk, theQual); - } - - for (Class typeName : upperBoundFor.types()) { - addType(typeName, theQual); - } - } - } + /** Map from {@link TypeKind} to annotations. */ + private final Map typeKinds; - /** - * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link TypeKind}. - * - * @param typeKind the Checker Framework TypeKind - * @return the javax TypeKind - */ - private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { - return TypeKind.valueOf(typeKind.name()); - } + /** Map from canonical class name strings to annotations. */ + private final Map<@CanonicalName String, AnnotationMirrorSet> types; - /** Add default qualifier, {@code theQual}, for the given TypeKind. */ - public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); - if (!res) { - throw new BugInCF( - "QualifierUpperBounds: invalid update of typeKinds $s at %s with %s.", - typeKinds, typeKind, theQual); - } - } + /** {@link QualifierHierarchy} */ + private final QualifierHierarchy qualHierarchy; + + private final AnnotatedTypeFactory atypeFactory; + + /** + * Creates a {@link QualifierUpperBounds} from the given checker, using that checker to determine + * the annotations that are in the type hierarchy. + */ + public QualifierUpperBounds(AnnotatedTypeFactory typeFactory) { + this.atypeFactory = typeFactory; + this.typeKinds = new EnumMap<>(TypeKind.class); + this.types = new HashMap<>(); + + this.qualHierarchy = typeFactory.getQualifierHierarchy(); + + // Get type qualifiers from the checker. + Set> quals = typeFactory.getSupportedTypeQualifiers(); - /** Add default qualifier, {@code theQual}, for the given class. */ - public void addType(Class type, AnnotationMirror theQual) { - String typeNameString = type.getCanonicalName(); - boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); - if (!res) { - throw new BugInCF( - "QualifierUpperBounds: invalid update of types $s at %s with %s.", - types, type, theQual); - } + // For each qualifier, read the @UpperBoundFor annotation and put its type classes and kinds + // into maps. + for (Class qual : quals) { + UpperBoundFor upperBoundFor = qual.getAnnotation(UpperBoundFor.class); + if (upperBoundFor == null) { + continue; + } + + AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); + for (org.checkerframework.framework.qual.TypeKind typeKind : upperBoundFor.typeKinds()) { + TypeKind mappedTk = mapTypeKinds(typeKind); + addTypeKind(mappedTk, theQual); + } + + for (Class typeName : upperBoundFor.types()) { + addType(typeName, theQual); + } + } + } + + /** + * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link TypeKind}. + * + * @param typeKind the Checker Framework TypeKind + * @return the javax TypeKind + */ + private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { + return TypeKind.valueOf(typeKind.name()); + } + + /** Add default qualifier, {@code theQual}, for the given TypeKind. */ + public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); + if (!res) { + throw new BugInCF( + "QualifierUpperBounds: invalid update of typeKinds $s at %s with %s.", + typeKinds, typeKind, theQual); + } + } + + /** Add default qualifier, {@code theQual}, for the given class. */ + public void addType(Class type, AnnotationMirror theQual) { + String typeNameString = type.getCanonicalName(); + boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); + if (!res) { + throw new BugInCF( + "QualifierUpperBounds: invalid update of types $s at %s with %s.", types, type, theQual); + } + } + + /** + * Returns the set of qualifiers that are the upper bounds for a use of the type. + * + * @param type the TypeMirror + * @return the set of qualifiers that are the upper bounds for a use of the type + */ + public AnnotationMirrorSet getBoundQualifiers(TypeMirror type) { + AnnotationMirrorSet bounds = new AnnotationMirrorSet(); + String qname; + if (type.getKind() == TypeKind.DECLARED) { + DeclaredType declaredType = (DeclaredType) type; + bounds.addAll(getAnnotationFromElement(declaredType.asElement())); + qname = TypesUtils.getQualifiedName(declaredType); + } else if (type.getKind().isPrimitive()) { + qname = type.toString(); + } else { + qname = null; } - /** - * Returns the set of qualifiers that are the upper bounds for a use of the type. - * - * @param type the TypeMirror - * @return the set of qualifiers that are the upper bounds for a use of the type - */ - public AnnotationMirrorSet getBoundQualifiers(TypeMirror type) { - AnnotationMirrorSet bounds = new AnnotationMirrorSet(); - String qname; - if (type.getKind() == TypeKind.DECLARED) { - DeclaredType declaredType = (DeclaredType) type; - bounds.addAll(getAnnotationFromElement(declaredType.asElement())); - qname = TypesUtils.getQualifiedName(declaredType); - } else if (type.getKind().isPrimitive()) { - qname = type.toString(); - } else { - qname = null; - } - - if (qname != null && types.containsKey(qname)) { - AnnotationMirrorSet fnd = types.get(qname); - addMissingAnnotations(bounds, fnd); - } - - // If the type's kind is in the appropriate map, annotate the type. - - if (typeKinds.containsKey(type.getKind())) { - AnnotationMirrorSet fnd = typeKinds.get(type.getKind()); - addMissingAnnotations(bounds, fnd); - } - - addMissingAnnotations(bounds, atypeFactory.getDefaultTypeDeclarationBounds()); - return bounds; + if (qname != null && types.containsKey(qname)) { + AnnotationMirrorSet fnd = types.get(qname); + addMissingAnnotations(bounds, fnd); } - /** - * Returns the explicit annotations on the element. Subclass can override this behavior to add - * annotations. - * - * @param element element whose annotations to return - * @return the explicit annotations on the element - */ - protected AnnotationMirrorSet getAnnotationFromElement(Element element) { - return atypeFactory.fromElement(element).getAnnotations(); + // If the type's kind is in the appropriate map, annotate the type. + + if (typeKinds.containsKey(type.getKind())) { + AnnotationMirrorSet fnd = typeKinds.get(type.getKind()); + addMissingAnnotations(bounds, fnd); } - /** - * Adds each annotation in {@code missing} to {@code annos}, for which no annotation from the - * same qualifier hierarchy is present. - * - * @param annos an annotation set to side-effect - * @param missing annotations to add to {@code annos}, if {@code annos} does not have an - * annotation from the same qualifier hierarchy - */ - private void addMissingAnnotations( - AnnotationMirrorSet annos, Set missing) { - for (AnnotationMirror miss : missing) { - if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, miss) - == null) { - annos.add(miss); - } - } + addMissingAnnotations(bounds, atypeFactory.getDefaultTypeDeclarationBounds()); + return bounds; + } + + /** + * Returns the explicit annotations on the element. Subclass can override this behavior to add + * annotations. + * + * @param element element whose annotations to return + * @return the explicit annotations on the element + */ + protected AnnotationMirrorSet getAnnotationFromElement(Element element) { + return atypeFactory.fromElement(element).getAnnotations(); + } + + /** + * Adds each annotation in {@code missing} to {@code annos}, for which no annotation from the same + * qualifier hierarchy is present. + * + * @param annos an annotation set to side-effect + * @param missing annotations to add to {@code annos}, if {@code annos} does not have an + * annotation from the same qualifier hierarchy + */ + private void addMissingAnnotations( + AnnotationMirrorSet annos, Set missing) { + for (AnnotationMirror miss : missing) { + if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, miss) == null) { + annos.add(miss); + } } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java index aaf67b53535..6b394cee812 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java @@ -1,5 +1,11 @@ package org.checkerframework.framework.type; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -13,14 +19,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.StringsPlume; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * A visitor used to compare two type mirrors for "structural" equality. Structural equality implies * that, for two objects, all fields are also structurally equal and for primitives their values are @@ -29,377 +27,373 @@ *

See also DefaultTypeHierarchy, and SubtypeVisitHistory */ public class StructuralEqualityComparer extends AbstractAtmComboVisitor { - /** History saving the result of previous comparisons. */ - protected final StructuralEqualityVisitHistory visitHistory; - - // See org.checkerframework.framework.type.DefaultTypeHierarchy.currentTop - private AnnotationMirror currentTop = null; - - /** - * Create a StructuralEqualityComparer. - * - * @param typeargVisitHistory history saving the result of previous comparisons - */ - public StructuralEqualityComparer(StructuralEqualityVisitHistory typeargVisitHistory) { - this.visitHistory = typeargVisitHistory; + /** History saving the result of previous comparisons. */ + protected final StructuralEqualityVisitHistory visitHistory; + + // See org.checkerframework.framework.type.DefaultTypeHierarchy.currentTop + private AnnotationMirror currentTop = null; + + /** + * Create a StructuralEqualityComparer. + * + * @param typeargVisitHistory history saving the result of previous comparisons + */ + public StructuralEqualityComparer(StructuralEqualityVisitHistory typeargVisitHistory) { + this.visitHistory = typeargVisitHistory; + } + + @Override + public Boolean defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { + if (type1.getKind() == TypeKind.NULL || type2.getKind() == TypeKind.NULL) { + // If one of the types is the NULL type, compare main qualifiers only. + return arePrimaryAnnosEqual(type1, type2); } - @Override - public Boolean defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { - if (type1.getKind() == TypeKind.NULL || type2.getKind() == TypeKind.NULL) { - // If one of the types is the NULL type, compare main qualifiers only. - return arePrimaryAnnosEqual(type1, type2); - } - - if (type1.containsUninferredTypeArguments() || type2.containsUninferredTypeArguments()) { - return type1.atypeFactory.ignoreUninferredTypeArguments; - } - - return super.defaultAction(type1, type2, p); + if (type1.containsUninferredTypeArguments() || type2.containsUninferredTypeArguments()) { + return type1.atypeFactory.ignoreUninferredTypeArguments; } - /** - * Called for every combination that isn't specifically handled. - * - * @return error message explaining the two types' classes are not the same - */ - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { - return super.defaultErrorMessage(type1, type2, p) - + System.lineSeparator() - + " visitHistory = " - + visitHistory; + return super.defaultAction(type1, type2, p); + } + + /** + * Called for every combination that isn't specifically handled. + * + * @return error message explaining the two types' classes are not the same + */ + @Override + public String defaultErrorMessage(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { + return super.defaultErrorMessage(type1, type2, p) + + System.lineSeparator() + + " visitHistory = " + + visitHistory; + } + + /** + * Returns true if type1 and type2 are structurally equivalent. With one exception, + * type1.getClass().equals(type2.getClass()) must be true. However, because the Checker Framework + * sometimes "infers" Typevars to be Wildcards, we allow the combination Wildcard,Typevar. In this + * case, the two types are "equal" if their bounds are. + * + * @param type1 the first AnnotatedTypeMirror to compare + * @param type2 the second AnnotatedTypeMirror to compare + * @return true if type1 and type2 are equal + */ + @EqualsMethod + private boolean areEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (type1 == type2) { + return true; } - - /** - * Returns true if type1 and type2 are structurally equivalent. With one exception, - * type1.getClass().equals(type2.getClass()) must be true. However, because the Checker - * Framework sometimes "infers" Typevars to be Wildcards, we allow the combination - * Wildcard,Typevar. In this case, the two types are "equal" if their bounds are. - * - * @param type1 the first AnnotatedTypeMirror to compare - * @param type2 the second AnnotatedTypeMirror to compare - * @return true if type1 and type2 are equal - */ - @EqualsMethod - private boolean areEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (type1 == type2) { - return true; - } - assert currentTop != null; - if (type1 == null || type2 == null) { - return false; - } - return AtmCombo.accept(type1, type2, null, this); + assert currentTop != null; + if (type1 == null || type2 == null) { + return false; } - - public boolean areEqualInHierarchy( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror top) { - assert top != null; - boolean areEqual; - AnnotationMirror prevTop = currentTop; - currentTop = top; - try { - areEqual = areEqual(type1, type2); - } finally { - currentTop = prevTop; - } - - return areEqual; + return AtmCombo.accept(type1, type2, null, this); + } + + public boolean areEqualInHierarchy( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror top) { + assert top != null; + boolean areEqual; + AnnotationMirror prevTop = currentTop; + currentTop = top; + try { + areEqual = areEqual(type1, type2); + } finally { + currentTop = prevTop; } - /** - * Return true if type1 and type2 have the same set of annotations. - * - * @param type1 a type - * @param type2 a type - * @return true if type1 and type2 have the same set of annotations - */ - protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (currentTop != null) { - AnnotationMirror anno1 = type1.getAnnotationInHierarchy(currentTop); - AnnotationMirror anno2 = type2.getAnnotationInHierarchy(currentTop); - TypeMirror typeMirror1 = type1.underlyingType; - TypeMirror typeMirror2 = type2.underlyingType; - QualifierHierarchy qh = type1.atypeFactory.getQualifierHierarchy(); - return qh.isSubtypeShallow(anno1, typeMirror1, anno2, typeMirror2) - && qh.isSubtypeShallow(anno2, typeMirror2, anno1, typeMirror1); - } else { - throw new BugInCF("currentTop null"); - } + return areEqual; + } + + /** + * Return true if type1 and type2 have the same set of annotations. + * + * @param type1 a type + * @param type2 a type + * @return true if type1 and type2 have the same set of annotations + */ + protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (currentTop != null) { + AnnotationMirror anno1 = type1.getAnnotationInHierarchy(currentTop); + AnnotationMirror anno2 = type2.getAnnotationInHierarchy(currentTop); + TypeMirror typeMirror1 = type1.underlyingType; + TypeMirror typeMirror2 = type2.underlyingType; + QualifierHierarchy qh = type1.atypeFactory.getQualifierHierarchy(); + return qh.isSubtypeShallow(anno1, typeMirror1, anno2, typeMirror2) + && qh.isSubtypeShallow(anno2, typeMirror2, anno1, typeMirror1); + } else { + throw new BugInCF("currentTop null"); } - - /** - * Compare each type in types1 and types2 pairwise and return true if they are all equal. This - * method throws an exceptions if types1.size() != types2.size() - * - * @return true if for each pair (t1 = types1.get(i); t2 = types2.get(i)), areEqual(t1,t2) - */ - protected boolean areAllEqual( - Collection types1, - Collection types2) { - if (types1.size() != types2.size()) { - throw new BugInCF( - "Mismatching collection sizes:%n types 1: %s (%d)%n types 2: %s (%d)", - StringsPlume.join("; ", types1), - types1.size(), - StringsPlume.join("; ", types2), - types2.size()); - } - - Iterator types1Iter = types1.iterator(); - Iterator types2Iter = types2.iterator(); - while (types1Iter.hasNext()) { - AnnotatedTypeMirror type1 = types1Iter.next(); - AnnotatedTypeMirror type2 = types2Iter.next(); - if (!checkOrAreEqual(type1, type2)) { - return false; - } - } - - return true; + } + + /** + * Compare each type in types1 and types2 pairwise and return true if they are all equal. This + * method throws an exceptions if types1.size() != types2.size() + * + * @return true if for each pair (t1 = types1.get(i); t2 = types2.get(i)), areEqual(t1,t2) + */ + protected boolean areAllEqual( + Collection types1, + Collection types2) { + if (types1.size() != types2.size()) { + throw new BugInCF( + "Mismatching collection sizes:%n types 1: %s (%d)%n types 2: %s (%d)", + StringsPlume.join("; ", types1), + types1.size(), + StringsPlume.join("; ", types2), + types2.size()); } - /** - * First check visitHistory to see if type1 and type2 have been compared once already. If so - * return true; otherwise compare them and put them in visitHistory. - * - * @param type1 the first type - * @param type2 the second type - * @return whether the two types are equal - */ - protected boolean checkOrAreEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; - } - - Boolean result = areEqual(type1, type2); - visitHistory.put(type1, type2, currentTop, result); - return result; + Iterator types1Iter = types1.iterator(); + Iterator types2Iter = types2.iterator(); + while (types1Iter.hasNext()) { + AnnotatedTypeMirror type1 = types1Iter.next(); + AnnotatedTypeMirror type2 = types2Iter.next(); + if (!checkOrAreEqual(type1, type2)) { + return false; + } } - /** - * Two arrays are equal if: - * - *

    - *
  1. Their sets of primary annotations are equal, and - *
  2. Their component types are equal - *
- */ - @Override - public Boolean visitArray_Array(AnnotatedArrayType type1, AnnotatedArrayType type2, Void p) { - if (!arePrimaryAnnosEqual(type1, type2)) { - return false; - } - - return areEqual(type1.getComponentType(), type2.getComponentType()); + return true; + } + + /** + * First check visitHistory to see if type1 and type2 have been compared once already. If so + * return true; otherwise compare them and put them in visitHistory. + * + * @param type1 the first type + * @param type2 the second type + * @return whether the two types are equal + */ + protected boolean checkOrAreEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; } - /** - * Two declared types are equal if: - * - *
    - *
  1. The types are of the same class/interfaces - *
  2. Their sets of primary annotations are equal - *
  3. Their sets of type arguments are equal or one type is raw - *
- */ - @Override - public Boolean visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void p) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; - } + Boolean result = areEqual(type1, type2); + visitHistory.put(type1, type2, currentTop, result); + return result; + } + + /** + * Two arrays are equal if: + * + *
    + *
  1. Their sets of primary annotations are equal, and + *
  2. Their component types are equal + *
+ */ + @Override + public Boolean visitArray_Array(AnnotatedArrayType type1, AnnotatedArrayType type2, Void p) { + if (!arePrimaryAnnosEqual(type1, type2)) { + return false; + } - // TODO: same class/interface is not enforced. Why? + return areEqual(type1.getComponentType(), type2.getComponentType()); + } + + /** + * Two declared types are equal if: + * + *
    + *
  1. The types are of the same class/interfaces + *
  2. Their sets of primary annotations are equal + *
  3. Their sets of type arguments are equal or one type is raw + *
+ */ + @Override + public Boolean visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void p) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; + } - if (!arePrimaryAnnosEqual(type1, type2)) { - return false; - } - // Prevent infinite recursion e.g. in Issue1587b - visitHistory.put(type1, type2, currentTop, true); - - List type1Args = type1.getTypeArguments(); - List type2Args = type2.getTypeArguments(); - - // Capture the types because the wildcards are only not equal if they are provably distinct. - // Provably distinct is computed using the captured and erased upper bounds of wildcards. - // See JLS 4.5.1. Type Arguments of Parameterized Types. - AnnotatedTypeFactory atypeFactory = type1.atypeFactory; - AnnotatedDeclaredType capturedType1 = - (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type1); - AnnotatedDeclaredType capturedType2 = - (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type2); - visitHistory.put(capturedType1, capturedType2, currentTop, true); - - List capturedType1Args = capturedType1.getTypeArguments(); - List capturedType2Args = capturedType2.getTypeArguments(); - boolean result = true; - for (int i = 0; i < type1.getTypeArguments().size(); i++) { - AnnotatedTypeMirror type1Arg = type1Args.get(i); - AnnotatedTypeMirror type2Arg = type2Args.get(i); - Boolean pastResultTA = visitHistory.get(type1Arg, type2Arg, currentTop); - if (pastResultTA != null) { - result = pastResultTA; - } else { - if (type1Arg.getKind() != TypeKind.WILDCARD - || type2Arg.getKind() != TypeKind.WILDCARD) { - result = areEqual(type1Arg, type2Arg); - } else { - AnnotatedWildcardType wildcardType1 = (AnnotatedWildcardType) type1Arg; - AnnotatedWildcardType wildcardType2 = (AnnotatedWildcardType) type2Arg; - if (type1.atypeFactory.ignoreUninferredTypeArguments - && (wildcardType1.isUninferredTypeArgument() - || wildcardType2.isUninferredTypeArgument())) { - result = true; - } else { - AnnotatedTypeMirror capturedType1Arg = capturedType1Args.get(i); - AnnotatedTypeMirror capturedType2Arg = capturedType2Args.get(i); - result = - areEqual( - capturedType1Arg.getErased(), capturedType2Arg.getErased()); - } - } - } - if (!result) { - break; - } - } + // TODO: same class/interface is not enforced. Why? - visitHistory.put(capturedType1, capturedType2, currentTop, result); - visitHistory.put(type1, type2, currentTop, result); - return result; + if (!arePrimaryAnnosEqual(type1, type2)) { + return false; } - - /** - * Two intersection types are equal if: - * - *
    - *
  • Their sets of primary annotations are equal - *
  • Their sets of bounds (the types being intersected) are equal - *
- */ - @Override - public Boolean visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void p) { - if (!arePrimaryAnnosEqual(type1, type2)) { - return false; + // Prevent infinite recursion e.g. in Issue1587b + visitHistory.put(type1, type2, currentTop, true); + + List type1Args = type1.getTypeArguments(); + List type2Args = type2.getTypeArguments(); + + // Capture the types because the wildcards are only not equal if they are provably distinct. + // Provably distinct is computed using the captured and erased upper bounds of wildcards. + // See JLS 4.5.1. Type Arguments of Parameterized Types. + AnnotatedTypeFactory atypeFactory = type1.atypeFactory; + AnnotatedDeclaredType capturedType1 = + (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type1); + AnnotatedDeclaredType capturedType2 = + (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type2); + visitHistory.put(capturedType1, capturedType2, currentTop, true); + + List capturedType1Args = capturedType1.getTypeArguments(); + List capturedType2Args = capturedType2.getTypeArguments(); + boolean result = true; + for (int i = 0; i < type1.getTypeArguments().size(); i++) { + AnnotatedTypeMirror type1Arg = type1Args.get(i); + AnnotatedTypeMirror type2Arg = type2Args.get(i); + Boolean pastResultTA = visitHistory.get(type1Arg, type2Arg, currentTop); + if (pastResultTA != null) { + result = pastResultTA; + } else { + if (type1Arg.getKind() != TypeKind.WILDCARD || type2Arg.getKind() != TypeKind.WILDCARD) { + result = areEqual(type1Arg, type2Arg); + } else { + AnnotatedWildcardType wildcardType1 = (AnnotatedWildcardType) type1Arg; + AnnotatedWildcardType wildcardType2 = (AnnotatedWildcardType) type2Arg; + if (type1.atypeFactory.ignoreUninferredTypeArguments + && (wildcardType1.isUninferredTypeArgument() + || wildcardType2.isUninferredTypeArgument())) { + result = true; + } else { + AnnotatedTypeMirror capturedType1Arg = capturedType1Args.get(i); + AnnotatedTypeMirror capturedType2Arg = capturedType2Args.get(i); + result = areEqual(capturedType1Arg.getErased(), capturedType2Arg.getErased()); + } } - - boolean result = areAllEqual(type1.getBounds(), type2.getBounds()); - visitHistory.put(type1, type2, currentTop, result); - return result; + } + if (!result) { + break; + } } - /** - * Two primitive types are equal if: - * - *
    - *
  • Their sets of primary annotations are equal - *
- */ - @Override - public Boolean visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void p) { - return arePrimaryAnnosEqual(type1, type2); + visitHistory.put(capturedType1, capturedType2, currentTop, result); + visitHistory.put(type1, type2, currentTop, result); + return result; + } + + /** + * Two intersection types are equal if: + * + *
    + *
  • Their sets of primary annotations are equal + *
  • Their sets of bounds (the types being intersected) are equal + *
+ */ + @Override + public Boolean visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void p) { + if (!arePrimaryAnnosEqual(type1, type2)) { + return false; } - /** - * Two type variables are equal if: - * - *
    - *
  • Their bounds are equal - *
- * - * Note: Primary annotations will be taken into account when the bounds are retrieved - */ - @Override - public Boolean visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void p) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; - } - - Boolean result = - areEqual(type1.getUpperBound(), type2.getUpperBound()) - && areEqual(type1.getLowerBound(), type2.getLowerBound()); - visitHistory.put(type1, type2, currentTop, result); - return result; + boolean result = areAllEqual(type1.getBounds(), type2.getBounds()); + visitHistory.put(type1, type2, currentTop, result); + return result; + } + + /** + * Two primitive types are equal if: + * + *
    + *
  • Their sets of primary annotations are equal + *
+ */ + @Override + public Boolean visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void p) { + return arePrimaryAnnosEqual(type1, type2); + } + + /** + * Two type variables are equal if: + * + *
    + *
  • Their bounds are equal + *
+ * + * Note: Primary annotations will be taken into account when the bounds are retrieved + */ + @Override + public Boolean visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void p) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; } - /** - * Two wildcards are equal if: - * - *
    - *
  • Their bounds are equal - *
- * - * Note: Primary annotations will be taken into account when the bounds are retrieved - */ - @Override - public Boolean visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void p) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; - } - - if (type1.atypeFactory.ignoreUninferredTypeArguments - && (type1.isUninferredTypeArgument() || type2.isUninferredTypeArgument())) { - return true; - } - - Boolean result = - areEqual(type1.getExtendsBound(), type2.getExtendsBound()) - && areEqual(type1.getSuperBound(), type2.getSuperBound()); - visitHistory.put(type1, type2, currentTop, result); - return result; + Boolean result = + areEqual(type1.getUpperBound(), type2.getUpperBound()) + && areEqual(type1.getLowerBound(), type2.getLowerBound()); + visitHistory.put(type1, type2, currentTop, result); + return result; + } + + /** + * Two wildcards are equal if: + * + *
    + *
  • Their bounds are equal + *
+ * + * Note: Primary annotations will be taken into account when the bounds are retrieved + */ + @Override + public Boolean visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void p) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; } - @Override - public Boolean visitWildcard_Typevar( - AnnotatedWildcardType type1, AnnotatedTypeVariable type2, Void p) { - // Once #979 is completed, this should be removed. - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; - } - - if (type1.atypeFactory.ignoreUninferredTypeArguments && type1.isUninferredTypeArgument()) { - return true; - } - - Boolean result = - areEqual(type1.getExtendsBound(), type2.getUpperBound()) - && areEqual(type1.getSuperBound(), type2.getLowerBound()); + if (type1.atypeFactory.ignoreUninferredTypeArguments + && (type1.isUninferredTypeArgument() || type2.isUninferredTypeArgument())) { + return true; + } - visitHistory.put(type1, type2, currentTop, result); - return result; + Boolean result = + areEqual(type1.getExtendsBound(), type2.getExtendsBound()) + && areEqual(type1.getSuperBound(), type2.getSuperBound()); + visitHistory.put(type1, type2, currentTop, result); + return result; + } + + @Override + public Boolean visitWildcard_Typevar( + AnnotatedWildcardType type1, AnnotatedTypeVariable type2, Void p) { + // Once #979 is completed, this should be removed. + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; } - // Since we don't do a boxing conversion between primitive and declared types, in some cases - // we must compare primitives with their boxed counterparts. - @Override - public Boolean visitDeclared_Primitive( - AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, Void p) { - if (!TypesUtils.isBoxOf(type1.getUnderlyingType(), type2.getUnderlyingType())) { - throw new BugInCF(defaultErrorMessage(type1, type2, p)); - } + if (type1.atypeFactory.ignoreUninferredTypeArguments && type1.isUninferredTypeArgument()) { + return true; + } - return arePrimaryAnnosEqual(type1, type2); + Boolean result = + areEqual(type1.getExtendsBound(), type2.getUpperBound()) + && areEqual(type1.getSuperBound(), type2.getLowerBound()); + + visitHistory.put(type1, type2, currentTop, result); + return result; + } + + // Since we don't do a boxing conversion between primitive and declared types, in some cases + // we must compare primitives with their boxed counterparts. + @Override + public Boolean visitDeclared_Primitive( + AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, Void p) { + if (!TypesUtils.isBoxOf(type1.getUnderlyingType(), type2.getUnderlyingType())) { + throw new BugInCF(defaultErrorMessage(type1, type2, p)); } - @Override - public Boolean visitPrimitive_Declared( - AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, Void p) { - if (!TypesUtils.isBoxOf(type2.getUnderlyingType(), type1.getUnderlyingType())) { - throw new BugInCF(defaultErrorMessage(type1, type2, p)); - } + return arePrimaryAnnosEqual(type1, type2); + } - return arePrimaryAnnosEqual(type1, type2); + @Override + public Boolean visitPrimitive_Declared( + AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, Void p) { + if (!TypesUtils.isBoxOf(type2.getUnderlyingType(), type1.getUnderlyingType())) { + throw new BugInCF(defaultErrorMessage(type1, type2, p)); } + + return arePrimaryAnnosEqual(type1, type2); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java index 8fbfff30c7e..71cd9db1f57 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java @@ -1,8 +1,7 @@ package org.checkerframework.framework.type; -import org.checkerframework.checker.nullness.qual.Nullable; - import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Stores the result of {@link StructuralEqualityComparer} for type arguments. @@ -11,83 +10,81 @@ */ public class StructuralEqualityVisitHistory { - /** - * Types in this history are structurally equal. (Use {@link SubtypeVisitHistory} because it - * implements a {@code Map, - * AnnotationMirrorSet>}) - */ - private final SubtypeVisitHistory trueHistory; + /** + * Types in this history are structurally equal. (Use {@link SubtypeVisitHistory} because it + * implements a {@code Map, AnnotationMirrorSet>}) + */ + private final SubtypeVisitHistory trueHistory; - /** - * Types in this history are not structurally equal. (Use {@link SubtypeVisitHistory} because it - * implements a {@code Map, - * AnnotationMirrorSet>}) - */ - private final SubtypeVisitHistory falseHistory; + /** + * Types in this history are not structurally equal. (Use {@link SubtypeVisitHistory} because it + * implements a {@code Map, AnnotationMirrorSet>}) + */ + private final SubtypeVisitHistory falseHistory; - /** Creates an empty StructuralEqualityVisitHistory. */ - public StructuralEqualityVisitHistory() { - this.trueHistory = new SubtypeVisitHistory(); - this.falseHistory = new SubtypeVisitHistory(); - } + /** Creates an empty StructuralEqualityVisitHistory. */ + public StructuralEqualityVisitHistory() { + this.trueHistory = new SubtypeVisitHistory(); + this.falseHistory = new SubtypeVisitHistory(); + } - /** - * Put result of comparing {@code type1} and {@code type2} for structural equality for the given - * hierarchy. - * - * @param type1 the first type - * @param type2 the second type - * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy - * are considered - * @param result whether {@code type1} is structurally equal to {@code type2} - */ - public void put( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - AnnotationMirror hierarchy, - boolean result) { - if (result) { - trueHistory.put(type1, type2, hierarchy, true); - falseHistory.remove(type1, type2, hierarchy); - } else { - falseHistory.put(type1, type2, hierarchy, true); - trueHistory.remove(type1, type2, hierarchy); - } + /** + * Put result of comparing {@code type1} and {@code type2} for structural equality for the given + * hierarchy. + * + * @param type1 the first type + * @param type2 the second type + * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy + * are considered + * @param result whether {@code type1} is structurally equal to {@code type2} + */ + public void put( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + AnnotationMirror hierarchy, + boolean result) { + if (result) { + trueHistory.put(type1, type2, hierarchy, true); + falseHistory.remove(type1, type2, hierarchy); + } else { + falseHistory.put(type1, type2, hierarchy, true); + trueHistory.remove(type1, type2, hierarchy); } + } - /** - * Return whether or not the two types are structurally equal for the given hierarchy or {@code - * null} if the types have not been visited for the given hierarchy. - * - * @param type1 the first type - * @param type2 the second type - * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy - * are considered - * @return whether or not the two types are structurally equal for the given hierarchy or {@code - * null} if the types have not been visited for the given hierarchy - */ - public @Nullable Boolean get( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { - if (falseHistory.contains(type1, type2, hierarchy)) { - return false; - } else if (trueHistory.contains(type1, type2, hierarchy)) { - return true; - } - return null; + /** + * Return whether or not the two types are structurally equal for the given hierarchy or {@code + * null} if the types have not been visited for the given hierarchy. + * + * @param type1 the first type + * @param type2 the second type + * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy + * are considered + * @return whether or not the two types are structurally equal for the given hierarchy or {@code + * null} if the types have not been visited for the given hierarchy + */ + public @Nullable Boolean get( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { + if (falseHistory.contains(type1, type2, hierarchy)) { + return false; + } else if (trueHistory.contains(type1, type2, hierarchy)) { + return true; } + return null; + } - /** - * Remove the result of comparing {@code type1} and {@code type2} for structural equality for - * the given hierarchy. - * - * @param type1 the first type - * @param type2 the second type - * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy - * are considered - */ - public void remove( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { - falseHistory.remove(type1, type2, hierarchy); - trueHistory.remove(type1, type2, hierarchy); - } + /** + * Remove the result of comparing {@code type1} and {@code type2} for structural equality for the + * given hierarchy. + * + * @param type1 the first type + * @param type2 the second type + * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy + * are considered + */ + public void remove( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { + falseHistory.remove(type1, type2, hierarchy); + trueHistory.remove(type1, type2, hierarchy); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java index c072d946797..7e4e84445ec 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java @@ -1,19 +1,17 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.util.QualifierKind; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; - import java.lang.annotation.Annotation; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.util.QualifierKind; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; /** * A {@link org.checkerframework.framework.type.QualifierHierarchy} where, when a qualifier has @@ -26,109 +24,105 @@ @AnnotatedFor("nullness") public class SubtypeIsSubsetQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /** The processing environment; used for creating annotations. */ - private final ProcessingEnvironment processingEnv; + /** The processing environment; used for creating annotations. */ + private final ProcessingEnvironment processingEnv; - /** - * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param processingEnv processing environment - * @param atypeFactory the associated type factory - */ - public SubtypeIsSubsetQualifierHierarchy( - Collection> qualifierClasses, - ProcessingEnvironment processingEnv, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); - this.processingEnv = processingEnv; - } + /** + * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param processingEnv processing environment + * @param atypeFactory the associated type factory + */ + public SubtypeIsSubsetQualifierHierarchy( + Collection> qualifierClasses, + ProcessingEnvironment processingEnv, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); + this.processingEnv = processingEnv; + } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == superKind) { - List superValues = valuesStringList(superAnno); - List subValues = valuesStringList(subAnno); - return superValues.containsAll(subValues); - } - return subKind.isSubtypeOf(superKind); + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == superKind) { + List superValues = valuesStringList(superAnno); + List subValues = valuesStringList(subAnno); + return superValues.containsAll(subValues); } + return subKind.isSubtypeOf(superKind); + } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.addAll(a2Values); - return createAnnotationMirrorWithValue(lubKind, set); - } else if (lubKind == qualifierKind1) { - return a1; - } else if (lubKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF( - "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); - } + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.addAll(a2Values); + return createAnnotationMirrorWithValue(lubKind, set); + } else if (lubKind == qualifierKind1) { + return a1; + } else if (lubKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); } + } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.retainAll(a2Values); - return createAnnotationMirrorWithValue(glbKind, set); - } else if (glbKind == qualifierKind1) { - return a1; - } else if (glbKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF( - "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); - } + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.retainAll(a2Values); + return createAnnotationMirrorWithValue(glbKind, set); + } else if (glbKind == qualifierKind1) { + return a1; + } else if (glbKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); } + } - /** - * Returns a mutable list containing the {@code values} element of the given annotation. The - * {@code values} element must be an array of strings. - * - * @param anno an annotation - * @return a mutable list containing the {@code values} element; may be the empty list - */ - private List valuesStringList(AnnotationMirror anno) { - @SuppressWarnings("deprecation") // concrete annotation class is not known - List result = - AnnotationUtils.getElementValueArray(anno, "value", String.class, true); - return result; - } + /** + * Returns a mutable list containing the {@code values} element of the given annotation. The + * {@code values} element must be an array of strings. + * + * @param anno an annotation + * @return a mutable list containing the {@code values} element; may be the empty list + */ + private List valuesStringList(AnnotationMirror anno) { + @SuppressWarnings("deprecation") // concrete annotation class is not known + List result = AnnotationUtils.getElementValueArray(anno, "value", String.class, true); + return result; + } - /** - * Returns an AnnotationMirror corresponding to the given kind and values. - * - * @param kind the qualifier kind - * @param values the annotation's {@code values} element/argument - * @return an annotation of the given kind and values - */ - private AnnotationMirror createAnnotationMirrorWithValue( - QualifierKind kind, Set values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); - builder.setValue("value", values.toArray()); - return builder.build(); - } + /** + * Returns an AnnotationMirror corresponding to the given kind and values. + * + * @param kind the qualifier kind + * @param values the annotation's {@code values} element/argument + * @return an annotation of the given kind and values + */ + private AnnotationMirror createAnnotationMirrorWithValue(QualifierKind kind, Set values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); + builder.setValue("value", values.toArray()); + return builder.build(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java index 6a8c6fddaea..8f9a434e1aa 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java @@ -1,19 +1,17 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.util.QualifierKind; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; - import java.lang.annotation.Annotation; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.util.QualifierKind; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; /** * A {@link org.checkerframework.framework.type.QualifierHierarchy} where, when a qualifier has @@ -26,109 +24,105 @@ @AnnotatedFor("nullness") public class SubtypeIsSupersetQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /** The processing environment; used for creating annotations. */ - private final ProcessingEnvironment processingEnv; + /** The processing environment; used for creating annotations. */ + private final ProcessingEnvironment processingEnv; - /** - * Creates a SubtypeIsSupersetQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param processingEnv processing environment - * @param atypeFactory the associated type factory - */ - public SubtypeIsSupersetQualifierHierarchy( - Collection> qualifierClasses, - ProcessingEnvironment processingEnv, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); - this.processingEnv = processingEnv; - } + /** + * Creates a SubtypeIsSupersetQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param processingEnv processing environment + * @param atypeFactory the associated type factory + */ + public SubtypeIsSupersetQualifierHierarchy( + Collection> qualifierClasses, + ProcessingEnvironment processingEnv, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); + this.processingEnv = processingEnv; + } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == superKind) { - List superValues = valuesStringList(superAnno); - List subValues = valuesStringList(subAnno); - return subValues.containsAll(superValues); - } - return subKind.isSubtypeOf(superKind); + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == superKind) { + List superValues = valuesStringList(superAnno); + List subValues = valuesStringList(subAnno); + return subValues.containsAll(superValues); } + return subKind.isSubtypeOf(superKind); + } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.retainAll(a2Values); - return createAnnotationMirrorWithValue(lubKind, set); - } else if (lubKind == qualifierKind1) { - return a1; - } else if (lubKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF( - "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); - } + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.retainAll(a2Values); + return createAnnotationMirrorWithValue(lubKind, set); + } else if (lubKind == qualifierKind1) { + return a1; + } else if (lubKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); } + } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.addAll(a2Values); - return createAnnotationMirrorWithValue(glbKind, set); - } else if (glbKind == qualifierKind1) { - return a1; - } else if (glbKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF( - "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); - } + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.addAll(a2Values); + return createAnnotationMirrorWithValue(glbKind, set); + } else if (glbKind == qualifierKind1) { + return a1; + } else if (glbKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); } + } - /** - * Returns a mutable list containing the {@code values} element of the given annotation. The - * {@code values} element must be an array of strings. - * - * @param anno an annotation - * @return a mutable list containing the {@code values} element; may be the empty list - */ - private List valuesStringList(AnnotationMirror anno) { - @SuppressWarnings("deprecation") // concrete annotation class is not known - List result = - AnnotationUtils.getElementValueArray(anno, "value", String.class, true); - return result; - } + /** + * Returns a mutable list containing the {@code values} element of the given annotation. The + * {@code values} element must be an array of strings. + * + * @param anno an annotation + * @return a mutable list containing the {@code values} element; may be the empty list + */ + private List valuesStringList(AnnotationMirror anno) { + @SuppressWarnings("deprecation") // concrete annotation class is not known + List result = AnnotationUtils.getElementValueArray(anno, "value", String.class, true); + return result; + } - /** - * Returns an AnnotationMirror corresponding to the given kind and values. - * - * @param kind the qualifier kind - * @param values the annotation's {@code values} element/argument - * @return an annotation of the given kind and values - */ - private AnnotationMirror createAnnotationMirrorWithValue( - QualifierKind kind, Set values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); - builder.setValue("value", values.toArray()); - return builder.build(); - } + /** + * Returns an AnnotationMirror corresponding to the given kind and values. + * + * @param kind the qualifier kind + * @param values the annotation's {@code values} element/argument + * @return an annotation of the given kind and values + */ + private AnnotationMirror createAnnotationMirrorWithValue(QualifierKind kind, Set values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); + builder.setValue("value", values.toArray()); + return builder.build(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java index 48de587c95b..f946e1f03df 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java @@ -1,12 +1,10 @@ package org.checkerframework.framework.type; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.plumelib.util.IPair; - import java.util.HashMap; import java.util.Map; - import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.plumelib.util.IPair; /** * THIS CLASS IS DESIGNED FOR USE WITH DefaultTypeHierarchy, DefaultRawnessComparer, and @@ -26,77 +24,77 @@ // TODO: do we need to clear the history sometimes? public class SubtypeVisitHistory { - /** - * The keys are pairs of types; the value is the set of qualifier hierarchy roots for which the - * key is in a subtype relationship. - */ - private final Map, AnnotationMirrorSet> visited; - - /** Creates a new SubtypeVisitHistory. */ - public SubtypeVisitHistory() { - this.visited = new HashMap<>(); - } + /** + * The keys are pairs of types; the value is the set of qualifier hierarchy roots for which the + * key is in a subtype relationship. + */ + private final Map, AnnotationMirrorSet> visited; - /** - * Put a visit for {@code type1}, {@code type2}, and {@code top} in the history. Has no effect - * if isSubtype is false. - * - * @param type1 the first type - * @param type2 the second type - * @param currentTop the top of the relevant type hierarchy; only annotations from that - * hierarchy are considered - * @param isSubtype whether {@code type1} is a subtype of {@code type2}; if false, this method - * does nothing - */ - public void put( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - AnnotationMirror currentTop, - boolean isSubtype) { - if (!isSubtype) { - // Only store information about subtype relations that hold. - return; - } - IPair key = IPair.of(type1, type2); - AnnotationMirrorSet hit = visited.get(key); + /** Creates a new SubtypeVisitHistory. */ + public SubtypeVisitHistory() { + this.visited = new HashMap<>(); + } - if (hit != null) { - hit.add(currentTop); - } else { - hit = new AnnotationMirrorSet(); - hit.add(currentTop); - this.visited.put(key, hit); - } + /** + * Put a visit for {@code type1}, {@code type2}, and {@code top} in the history. Has no effect if + * isSubtype is false. + * + * @param type1 the first type + * @param type2 the second type + * @param currentTop the top of the relevant type hierarchy; only annotations from that hierarchy + * are considered + * @param isSubtype whether {@code type1} is a subtype of {@code type2}; if false, this method + * does nothing + */ + public void put( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + AnnotationMirror currentTop, + boolean isSubtype) { + if (!isSubtype) { + // Only store information about subtype relations that hold. + return; } + IPair key = IPair.of(type1, type2); + AnnotationMirrorSet hit = visited.get(key); - /** Remove {@code type1} and {@code type2}. */ - public void remove( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { - IPair key = IPair.of(type1, type2); - AnnotationMirrorSet hit = visited.get(key); - if (hit != null) { - hit.remove(currentTop); - if (hit.isEmpty()) { - visited.remove(key); - } - } + if (hit != null) { + hit.add(currentTop); + } else { + hit = new AnnotationMirrorSet(); + hit.add(currentTop); + this.visited.put(key, hit); } + } - /** - * Returns true if type1 and type2 (or an equivalent pair) have been passed to the put method - * previously. - * - * @return true if an equivalent pair has already been added to the history - */ - public boolean contains( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { - IPair key = IPair.of(type1, type2); - AnnotationMirrorSet hit = visited.get(key); - return hit != null && hit.contains(currentTop); + /** Remove {@code type1} and {@code type2}. */ + public void remove( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { + IPair key = IPair.of(type1, type2); + AnnotationMirrorSet hit = visited.get(key); + if (hit != null) { + hit.remove(currentTop); + if (hit.isEmpty()) { + visited.remove(key); + } } + } - @Override - public String toString() { - return "VisitHistory( " + visited + " )"; - } + /** + * Returns true if type1 and type2 (or an equivalent pair) have been passed to the put method + * previously. + * + * @return true if an equivalent pair has already been added to the history + */ + public boolean contains( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { + IPair key = IPair.of(type1, type2); + AnnotationMirrorSet hit = visited.get(key); + return hit != null && hit.contains(currentTop); + } + + @Override + public String toString() { + return "VisitHistory( " + visited + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java index 0fc1867cf7a..41315a24b02 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java @@ -2,20 +2,6 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.Tree; - -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; - import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -23,7 +9,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; @@ -33,6 +18,18 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Types; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; /** * Finds the direct supertypes of an input AnnotatedTypeMirror. See directSupertypes(AnnotatedDeclaredType type) { + SupertypeFindingVisitor supertypeFindingVisitor = + new SupertypeFindingVisitor(type.atypeFactory); + List supertypes = + supertypeFindingVisitor.visitDeclared(type.asUse(), null); + type.atypeFactory.postDirectSuperTypes(type, supertypes); + return supertypes; + } + + // Version of method above for all types + /** + * See {@link Types#directSupertypes(TypeMirror)}. + * + * @param type the type whose supertypes to return + * @return the immediate supertypes of {@code type} + * @see Types#directSupertypes(TypeMirror) + */ + public static final List directSupertypes( + AnnotatedTypeMirror type) { + SupertypeFindingVisitor supertypeFindingVisitor = + new SupertypeFindingVisitor(type.atypeFactory); + List supertypes = supertypeFindingVisitor.visit(type, null); + type.atypeFactory.postDirectSuperTypes(type, supertypes); + return supertypes; + } + + /** Computes the direct supertypes of annotated types. */ + private static class SupertypeFindingVisitor + extends SimpleAnnotatedTypeVisitor, Void> { + + /** Types util class. */ + private final Types types; + + /** Annotated type factory. */ + private final AnnotatedTypeFactory atypeFactory; - // Version of method below for declared types /** - * See {@link Types#directSupertypes(TypeMirror)}. + * Creates a {@code SupertypeFindingVisitor}. * - * @param type the type whose supertypes to return - * @return the immediate supertypes of {@code type} - * @see Types#directSupertypes(TypeMirror) + * @param atypeFactory annotated type factory */ - public static List directSupertypes(AnnotatedDeclaredType type) { - SupertypeFindingVisitor supertypeFindingVisitor = - new SupertypeFindingVisitor(type.atypeFactory); - List supertypes = - supertypeFindingVisitor.visitDeclared(type.asUse(), null); - type.atypeFactory.postDirectSuperTypes(type, supertypes); - return supertypes; + SupertypeFindingVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.types = atypeFactory.types; + } + + @Override + public List defaultAction(AnnotatedTypeMirror t, Void p) { + return Collections.emptyList(); } - // Version of method above for all types /** - * See {@link Types#directSupertypes(TypeMirror)}. + * Primitive Rules: * - * @param type the type whose supertypes to return - * @return the immediate supertypes of {@code type} - * @see Types#directSupertypes(TypeMirror) + *
{@code
+     * double >1 float
+     * float >1 long
+     * long >1 int
+     * int >1 char
+     * int >1 short
+     * short >1 byte
+     * }
+ * + * For easiness: + * + *
{@code
+     * boxed(primitiveType) >: primitiveType
+     * }
*/ - public static final List directSupertypes( - AnnotatedTypeMirror type) { - SupertypeFindingVisitor supertypeFindingVisitor = - new SupertypeFindingVisitor(type.atypeFactory); - List supertypes = supertypeFindingVisitor.visit(type, null); - type.atypeFactory.postDirectSuperTypes(type, supertypes); - return supertypes; + @Override + public List visitPrimitive(AnnotatedPrimitiveType type, Void p) { + List superTypes = new ArrayList<>(1); + AnnotationMirrorSet annotations = type.getAnnotations(); + + // Find Boxed type + TypeElement boxed = types.boxedClass(type.getUnderlyingType()); + AnnotatedDeclaredType boxedType = atypeFactory.getAnnotatedType(boxed); + boxedType.replaceAnnotations(annotations); + superTypes.add(boxedType); + + TypeKind superPrimitiveType = null; + + if (type.getKind() == TypeKind.BOOLEAN) { + // Nothing + } else if (type.getKind() == TypeKind.BYTE) { + superPrimitiveType = TypeKind.SHORT; + } else if (type.getKind() == TypeKind.CHAR) { + superPrimitiveType = TypeKind.INT; + } else if (type.getKind() == TypeKind.DOUBLE) { + // Nothing + } else if (type.getKind() == TypeKind.FLOAT) { + superPrimitiveType = TypeKind.DOUBLE; + } else if (type.getKind() == TypeKind.INT) { + superPrimitiveType = TypeKind.LONG; + } else if (type.getKind() == TypeKind.LONG) { + superPrimitiveType = TypeKind.FLOAT; + } else if (type.getKind() == TypeKind.SHORT) { + superPrimitiveType = TypeKind.INT; + } else { + assert false : "Forgot the primitive " + type; + } + + if (superPrimitiveType != null) { + AnnotatedPrimitiveType superPrimitive = + (AnnotatedPrimitiveType) + atypeFactory.toAnnotatedType(types.getPrimitiveType(superPrimitiveType), false); + superPrimitive.addAnnotations(annotations); + superTypes.add(superPrimitive); + } + + return superTypes; } - /** Computes the direct supertypes of annotated types. */ - private static class SupertypeFindingVisitor - extends SimpleAnnotatedTypeVisitor, Void> { - - /** Types util class. */ - private final Types types; + @Override + public List visitDeclared(AnnotatedDeclaredType type, Void p) { + // AnnotationMirrorSet annotations = type.getAnnotations(); - /** Annotated type factory. */ - private final AnnotatedTypeFactory atypeFactory; + TypeElement typeElement = (TypeElement) type.getUnderlyingType().asElement(); - /** - * Creates a {@code SupertypeFindingVisitor}. - * - * @param atypeFactory annotated type factory - */ - SupertypeFindingVisitor(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.types = atypeFactory.types; - } - - @Override - public List defaultAction(AnnotatedTypeMirror t, Void p) { - return Collections.emptyList(); + if (type.getTypeArguments().size() != typeElement.getTypeParameters().size()) { + if (!type.isUnderlyingTypeRaw()) { + throw new BugInCF( + "AnnotatedDeclaredType's element has a different number of type" + + " parameters than type.%ntype=%s%nelement=%s", + type, typeElement); } + } + List supertypes = new ArrayList<>(); + ClassTree classTree = atypeFactory.trees.getTree(typeElement); + // Testing against enum and annotation. Ideally we can simply use element! + if (classTree != null) { + supertypes.addAll(supertypesFromTree(type, classTree)); + } else { + supertypes.addAll(supertypesFromElement(type, typeElement)); + // Element elem = type.getElement() == null ? typeElement : type.getElement(); + } + + if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { + TypeElement jlaElement = + atypeFactory.elements.getTypeElement(Annotation.class.getCanonicalName()); + AnnotatedDeclaredType jlaAnnotation = atypeFactory.fromElement(jlaElement); + jlaAnnotation.addAnnotations(type.getAnnotations()); + supertypes.add(jlaAnnotation); + } + + Map typeVarToTypeArg = getTypeVarToTypeArg(type); + + List superTypesNew = new ArrayList<>(); + for (AnnotatedDeclaredType dt : supertypes) { + type.atypeFactory.initializeAtm(dt); + superTypesNew.add( + (AnnotatedDeclaredType) + atypeFactory.getTypeVarSubstitutor().substitute(typeVarToTypeArg, dt)); + } + + return superTypesNew; + } - /** - * Primitive Rules: - * - *
{@code
-         * double >1 float
-         * float >1 long
-         * long >1 int
-         * int >1 char
-         * int >1 short
-         * short >1 byte
-         * }
- * - * For easiness: - * - *
{@code
-         * boxed(primitiveType) >: primitiveType
-         * }
- */ - @Override - public List visitPrimitive(AnnotatedPrimitiveType type, Void p) { - List superTypes = new ArrayList<>(1); - AnnotationMirrorSet annotations = type.getAnnotations(); - - // Find Boxed type - TypeElement boxed = types.boxedClass(type.getUnderlyingType()); - AnnotatedDeclaredType boxedType = atypeFactory.getAnnotatedType(boxed); - boxedType.replaceAnnotations(annotations); - superTypes.add(boxedType); - - TypeKind superPrimitiveType = null; - - if (type.getKind() == TypeKind.BOOLEAN) { - // Nothing - } else if (type.getKind() == TypeKind.BYTE) { - superPrimitiveType = TypeKind.SHORT; - } else if (type.getKind() == TypeKind.CHAR) { - superPrimitiveType = TypeKind.INT; - } else if (type.getKind() == TypeKind.DOUBLE) { - // Nothing - } else if (type.getKind() == TypeKind.FLOAT) { - superPrimitiveType = TypeKind.DOUBLE; - } else if (type.getKind() == TypeKind.INT) { - superPrimitiveType = TypeKind.LONG; - } else if (type.getKind() == TypeKind.LONG) { - superPrimitiveType = TypeKind.FLOAT; - } else if (type.getKind() == TypeKind.SHORT) { - superPrimitiveType = TypeKind.INT; - } else { - assert false : "Forgot the primitive " + type; - } + /** + * Creates a mapping from a type parameter to its corresponding annotated type argument for all + * type parameters of {@code type}, its enclosing types, and all super types of all {@code + * type}'s enclosing types. + * + *

It does not get the type parameters of the supertypes of {@code type} because the result + * of this method is used to substitute the type arguments of the supertypes of {@code type}. + * + * @param type a type + * @return a mapping from each type parameter to its corresponding annotated type argument + */ + private Map getTypeVarToTypeArg(AnnotatedDeclaredType type) { + Map mapping = new HashMap<>(); + // addTypeVarsFromEnclosingTypes can't be called with `type` because it calls + // `directSupertypes(types)`, which then calls this method. Add the type variables from + // `type` and then call addTypeVarsFromEnclosingTypes on the enclosing type. + addTypeVariablesToMapping(type, mapping); + addTypeVarsFromEnclosingTypes(type.getEnclosingType(), mapping); + return mapping; + } - if (superPrimitiveType != null) { - AnnotatedPrimitiveType superPrimitive = - (AnnotatedPrimitiveType) - atypeFactory.toAnnotatedType( - types.getPrimitiveType(superPrimitiveType), false); - superPrimitive.addAnnotations(annotations); - superTypes.add(superPrimitive); - } + /** + * Adds a mapping from a type parameter to its corresponding annotated type argument for all + * type parameters of {@code type}. + * + * @param type a type + * @param mapping type variable to type argument map; side-effected by this method + */ + private void addTypeVariablesToMapping( + AnnotatedDeclaredType type, Map mapping) { + TypeElement enclosingTypeElement = (TypeElement) type.getUnderlyingType().asElement(); + List typeParams = enclosingTypeElement.getTypeParameters(); + List typeArgs = type.getTypeArguments(); + for (int i = 0; i < type.getTypeArguments().size(); ++i) { + AnnotatedTypeMirror typArg = typeArgs.get(i); + TypeParameterElement ele = typeParams.get(i); + mapping.put((TypeVariable) ele.asType(), typArg); + } + } - return superTypes; + /** + * Adds a mapping from a type parameter to its corresponding annotated type argument for all + * type parameters of {@code enclosing} and its enclosing types. This method recurs on all the + * super types of {@code enclosing}. + * + * @param mapping type variable to type argument map; side-effected by this method + * @param enclosing a type + */ + private void addTypeVarsFromEnclosingTypes( + AnnotatedDeclaredType enclosing, Map mapping) { + while (enclosing != null) { + addTypeVariablesToMapping(enclosing, mapping); + for (AnnotatedDeclaredType enclSuper : directSupertypes(enclosing)) { + addTypeVarsFromEnclosingTypes(enclSuper, mapping); } + enclosing = enclosing.getEnclosingType(); + } + } - @Override - public List visitDeclared(AnnotatedDeclaredType type, Void p) { - // AnnotationMirrorSet annotations = type.getAnnotations(); - - TypeElement typeElement = (TypeElement) type.getUnderlyingType().asElement(); - - if (type.getTypeArguments().size() != typeElement.getTypeParameters().size()) { - if (!type.isUnderlyingTypeRaw()) { - throw new BugInCF( - "AnnotatedDeclaredType's element has a different number of type" - + " parameters than type.%ntype=%s%nelement=%s", - type, typeElement); - } - } - List supertypes = new ArrayList<>(); - ClassTree classTree = atypeFactory.trees.getTree(typeElement); - // Testing against enum and annotation. Ideally we can simply use element! - if (classTree != null) { - supertypes.addAll(supertypesFromTree(type, classTree)); - } else { - supertypes.addAll(supertypesFromElement(type, typeElement)); - // Element elem = type.getElement() == null ? typeElement : type.getElement(); - } - - if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { - TypeElement jlaElement = - atypeFactory.elements.getTypeElement(Annotation.class.getCanonicalName()); - AnnotatedDeclaredType jlaAnnotation = atypeFactory.fromElement(jlaElement); - jlaAnnotation.addAnnotations(type.getAnnotations()); - supertypes.add(jlaAnnotation); - } - - Map typeVarToTypeArg = getTypeVarToTypeArg(type); - - List superTypesNew = new ArrayList<>(); - for (AnnotatedDeclaredType dt : supertypes) { - type.atypeFactory.initializeAtm(dt); - superTypesNew.add( - (AnnotatedDeclaredType) - atypeFactory - .getTypeVarSubstitutor() - .substitute(typeVarToTypeArg, dt)); - } - - return superTypesNew; + private List supertypesFromElement( + AnnotatedDeclaredType type, TypeElement typeElement) { + List supertypes = new ArrayList<>(); + // Find the super types: Start with enums and superclass + if (typeElement.getKind() == ElementKind.ENUM) { + supertypes.add(createEnumSuperType(type, typeElement)); + } else if (typeElement.getSuperclass().getKind() != TypeKind.NONE + && typeElement.getSuperclass().getKind() != TypeKind.ERROR) { + DeclaredType superClass = (DeclaredType) typeElement.getSuperclass(); + AnnotatedDeclaredType dt = + (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(superClass, false); + supertypes.add(dt); + + } else if (!ElementUtils.isObject(typeElement)) { + supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); + } + + for (TypeMirror st : typeElement.getInterfaces()) { + if (st.getKind() == TypeKind.ERROR) { + // This can happen while parsing the JDK. + continue; } - - /** - * Creates a mapping from a type parameter to its corresponding annotated type argument for - * all type parameters of {@code type}, its enclosing types, and all super types of all - * {@code type}'s enclosing types. - * - *

It does not get the type parameters of the supertypes of {@code type} because the - * result of this method is used to substitute the type arguments of the supertypes of - * {@code type}. - * - * @param type a type - * @return a mapping from each type parameter to its corresponding annotated type argument - */ - private Map getTypeVarToTypeArg( - AnnotatedDeclaredType type) { - Map mapping = new HashMap<>(); - // addTypeVarsFromEnclosingTypes can't be called with `type` because it calls - // `directSupertypes(types)`, which then calls this method. Add the type variables from - // `type` and then call addTypeVarsFromEnclosingTypes on the enclosing type. - addTypeVariablesToMapping(type, mapping); - addTypeVarsFromEnclosingTypes(type.getEnclosingType(), mapping); - return mapping; + if (type.isUnderlyingTypeRaw()) { + st = types.erasure(st); } - - /** - * Adds a mapping from a type parameter to its corresponding annotated type argument for all - * type parameters of {@code type}. - * - * @param type a type - * @param mapping type variable to type argument map; side-effected by this method - */ - private void addTypeVariablesToMapping( - AnnotatedDeclaredType type, Map mapping) { - TypeElement enclosingTypeElement = (TypeElement) type.getUnderlyingType().asElement(); - List typeParams = - enclosingTypeElement.getTypeParameters(); - List typeArgs = type.getTypeArguments(); - for (int i = 0; i < type.getTypeArguments().size(); ++i) { - AnnotatedTypeMirror typArg = typeArgs.get(i); - TypeParameterElement ele = typeParams.get(i); - mapping.put((TypeVariable) ele.asType(), typArg); + AnnotatedDeclaredType ast = (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(st, false); + supertypes.add(ast); + if (type.isUnderlyingTypeRaw()) { + if (st.getKind() == TypeKind.DECLARED) { + List typeArgs = ((DeclaredType) st).getTypeArguments(); + List annotatedTypeArgs = ast.getTypeArguments(); + for (int i = 0; i < typeArgs.size(); i++) { + atypeFactory.addComputedTypeAnnotations( + types.asElement(typeArgs.get(i)), annotatedTypeArgs.get(i)); } + } } + } + ElementAnnotationApplier.annotateSupers(supertypes, typeElement); - /** - * Adds a mapping from a type parameter to its corresponding annotated type argument for all - * type parameters of {@code enclosing} and its enclosing types. This method recurs on all - * the super types of {@code enclosing}. - * - * @param mapping type variable to type argument map; side-effected by this method - * @param enclosing a type - */ - private void addTypeVarsFromEnclosingTypes( - AnnotatedDeclaredType enclosing, Map mapping) { - while (enclosing != null) { - addTypeVariablesToMapping(enclosing, mapping); - for (AnnotatedDeclaredType enclSuper : directSupertypes(enclosing)) { - addTypeVarsFromEnclosingTypes(enclSuper, mapping); - } - enclosing = enclosing.getEnclosingType(); - } + if (type.isUnderlyingTypeRaw()) { + for (AnnotatedDeclaredType adt : supertypes) { + adt.setIsUnderlyingTypeRaw(); } + } + return supertypes; + } - private List supertypesFromElement( - AnnotatedDeclaredType type, TypeElement typeElement) { - List supertypes = new ArrayList<>(); - // Find the super types: Start with enums and superclass - if (typeElement.getKind() == ElementKind.ENUM) { - supertypes.add(createEnumSuperType(type, typeElement)); - } else if (typeElement.getSuperclass().getKind() != TypeKind.NONE - && typeElement.getSuperclass().getKind() != TypeKind.ERROR) { - DeclaredType superClass = (DeclaredType) typeElement.getSuperclass(); - AnnotatedDeclaredType dt = - (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(superClass, false); - supertypes.add(dt); - - } else if (!ElementUtils.isObject(typeElement)) { - supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); - } - - for (TypeMirror st : typeElement.getInterfaces()) { - if (st.getKind() == TypeKind.ERROR) { - // This can happen while parsing the JDK. - continue; - } - if (type.isUnderlyingTypeRaw()) { - st = types.erasure(st); - } - AnnotatedDeclaredType ast = - (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(st, false); - supertypes.add(ast); - if (type.isUnderlyingTypeRaw()) { - if (st.getKind() == TypeKind.DECLARED) { - List typeArgs = - ((DeclaredType) st).getTypeArguments(); - List annotatedTypeArgs = ast.getTypeArguments(); - for (int i = 0; i < typeArgs.size(); i++) { - atypeFactory.addComputedTypeAnnotations( - types.asElement(typeArgs.get(i)), annotatedTypeArgs.get(i)); - } - } - } - } - ElementAnnotationApplier.annotateSupers(supertypes, typeElement); - - if (type.isUnderlyingTypeRaw()) { - for (AnnotatedDeclaredType adt : supertypes) { - adt.setIsUnderlyingTypeRaw(); - } - } - return supertypes; + private List supertypesFromTree( + AnnotatedDeclaredType type, ClassTree classTree) { + List supertypes = new ArrayList<>(); + if (classTree.getExtendsClause() != null) { + AnnotatedDeclaredType adt = + (AnnotatedDeclaredType) + atypeFactory.getAnnotatedTypeFromTypeTree(classTree.getExtendsClause()); + supertypes.add(adt); + } else if (!ElementUtils.isObject(TreeUtils.elementFromDeclaration(classTree))) { + if (classTree.getKind().name().contentEquals("RECORD")) { + supertypes.add(AnnotatedTypeMirror.createTypeOfRecord(atypeFactory)); + } else { + supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); } - - private List supertypesFromTree( - AnnotatedDeclaredType type, ClassTree classTree) { - List supertypes = new ArrayList<>(); - if (classTree.getExtendsClause() != null) { - AnnotatedDeclaredType adt = - (AnnotatedDeclaredType) - atypeFactory.getAnnotatedTypeFromTypeTree( - classTree.getExtendsClause()); - supertypes.add(adt); - } else if (!ElementUtils.isObject(TreeUtils.elementFromDeclaration(classTree))) { - if (classTree.getKind().name().contentEquals("RECORD")) { - supertypes.add(AnnotatedTypeMirror.createTypeOfRecord(atypeFactory)); - } else { - supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); - } - } - - for (Tree implemented : classTree.getImplementsClause()) { - AnnotatedDeclaredType adt = - (AnnotatedDeclaredType) - atypeFactory.getAnnotatedTypeFromTypeTree(implemented); - if (adt.getTypeArguments().size() - != adt.getUnderlyingType().getTypeArguments().size() - && classTree.getSimpleName().contentEquals("")) { - // classTree is an anonymous class with a diamond. - List args = - CollectionsPlume.mapList( - (TypeParameterElement element) -> { - AnnotatedTypeMirror arg = - AnnotatedTypeMirror.createType( - element.asType(), atypeFactory, false); - // TODO: After #979 is fixed, calculate the correct type - // using inference. - return atypeFactory.getUninferredWildcardType( - (AnnotatedTypeVariable) arg); - }, - TypesUtils.getTypeElement(adt.getUnderlyingType()) - .getTypeParameters()); - adt.setTypeArguments(args); - } - supertypes.add(adt); - } - - TypeElement elem = TreeUtils.elementFromDeclaration(classTree); - if (elem.getKind() == ElementKind.ENUM) { - supertypes.add(createEnumSuperType(type, elem)); - } - if (type.isUnderlyingTypeRaw()) { - for (AnnotatedDeclaredType adt : supertypes) { - adt.setIsUnderlyingTypeRaw(); - } - } - return supertypes; + } + + for (Tree implemented : classTree.getImplementsClause()) { + AnnotatedDeclaredType adt = + (AnnotatedDeclaredType) atypeFactory.getAnnotatedTypeFromTypeTree(implemented); + if (adt.getTypeArguments().size() != adt.getUnderlyingType().getTypeArguments().size() + && classTree.getSimpleName().contentEquals("")) { + // classTree is an anonymous class with a diamond. + List args = + CollectionsPlume.mapList( + (TypeParameterElement element) -> { + AnnotatedTypeMirror arg = + AnnotatedTypeMirror.createType(element.asType(), atypeFactory, false); + // TODO: After #979 is fixed, calculate the correct type + // using inference. + return atypeFactory.getUninferredWildcardType((AnnotatedTypeVariable) arg); + }, + TypesUtils.getTypeElement(adt.getUnderlyingType()).getTypeParameters()); + adt.setTypeArguments(args); } - - /** - * All enums implicitly extend {@code Enum}, where {@code MyEnum} is the type of the - * enum. This method creates the AnnotatedTypeMirror for {@code Enum} where the - * annotation on {@code MyEnum} is copied from the annotation on the upper bound of the type - * argument to Enum. For example, {@code class Enum>}. - * - * @param type annotated type of an enum - * @param elem element corresponding to {@code type} - * @return enum super type - */ - private AnnotatedDeclaredType createEnumSuperType( - AnnotatedDeclaredType type, TypeElement elem) { - DeclaredType dt = (DeclaredType) elem.getSuperclass(); - AnnotatedDeclaredType adt = - (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(dt, false); - for (AnnotatedTypeMirror t : adt.getTypeArguments()) { - // If the type argument of super is the same as the input type - if (atypeFactory.types.isSameType( - t.getUnderlyingType(), type.getUnderlyingType())) { - t.addAnnotations(type.primaryAnnotations); - } - } - adt.addAnnotations(type.getAnnotations()); - return adt; + supertypes.add(adt); + } + + TypeElement elem = TreeUtils.elementFromDeclaration(classTree); + if (elem.getKind() == ElementKind.ENUM) { + supertypes.add(createEnumSuperType(type, elem)); + } + if (type.isUnderlyingTypeRaw()) { + for (AnnotatedDeclaredType adt : supertypes) { + adt.setIsUnderlyingTypeRaw(); } + } + return supertypes; + } - /** - * - * - *

{@code
-         * For type = A[ ] ==>
-         *  Object >: A[ ]
-         *  Clonable >: A[ ]
-         *  java.io.Serializable >: A[ ]
-         *
-         * if A is reference type, then also
-         *  B[ ] >: A[ ] for any B[ ] >: A[ ]
-         * }
- */ - @Override - public List visitArray(AnnotatedArrayType type, Void p) { - List superTypes = new ArrayList<>(); - AnnotationMirrorSet annotations = type.getAnnotations(); - AnnotatedTypeMirror objectType = atypeFactory.getAnnotatedType(Object.class); - objectType.addAnnotations(annotations); - superTypes.add(objectType); - - AnnotatedTypeMirror cloneableType = atypeFactory.getAnnotatedType(Cloneable.class); - cloneableType.addAnnotations(annotations); - superTypes.add(cloneableType); - - AnnotatedTypeMirror serializableType = - atypeFactory.getAnnotatedType(Serializable.class); - serializableType.addAnnotations(annotations); - superTypes.add(serializableType); - - for (AnnotatedTypeMirror sup : type.getComponentType().directSupertypes()) { - ArrayType arrType = atypeFactory.types.getArrayType(sup.getUnderlyingType()); - AnnotatedArrayType aarrType = - (AnnotatedArrayType) atypeFactory.toAnnotatedType(arrType, false); - aarrType.setComponentType(sup); - aarrType.addAnnotations(annotations); - superTypes.add(aarrType); - } - - return superTypes; + /** + * All enums implicitly extend {@code Enum}, where {@code MyEnum} is the type of the + * enum. This method creates the AnnotatedTypeMirror for {@code Enum} where the + * annotation on {@code MyEnum} is copied from the annotation on the upper bound of the type + * argument to Enum. For example, {@code class Enum>}. + * + * @param type annotated type of an enum + * @param elem element corresponding to {@code type} + * @return enum super type + */ + private AnnotatedDeclaredType createEnumSuperType( + AnnotatedDeclaredType type, TypeElement elem) { + DeclaredType dt = (DeclaredType) elem.getSuperclass(); + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(dt, false); + for (AnnotatedTypeMirror t : adt.getTypeArguments()) { + // If the type argument of super is the same as the input type + if (atypeFactory.types.isSameType(t.getUnderlyingType(), type.getUnderlyingType())) { + t.addAnnotations(type.primaryAnnotations); } + } + adt.addAnnotations(type.getAnnotations()); + return adt; + } - @Override - public List visitTypeVariable(AnnotatedTypeVariable type, Void p) { - return Collections.singletonList(type.getUpperBound().deepCopy()); - } + /** + * + * + *
{@code
+     * For type = A[ ] ==>
+     *  Object >: A[ ]
+     *  Clonable >: A[ ]
+     *  java.io.Serializable >: A[ ]
+     *
+     * if A is reference type, then also
+     *  B[ ] >: A[ ] for any B[ ] >: A[ ]
+     * }
+ */ + @Override + public List visitArray(AnnotatedArrayType type, Void p) { + List superTypes = new ArrayList<>(); + AnnotationMirrorSet annotations = type.getAnnotations(); + AnnotatedTypeMirror objectType = atypeFactory.getAnnotatedType(Object.class); + objectType.addAnnotations(annotations); + superTypes.add(objectType); + + AnnotatedTypeMirror cloneableType = atypeFactory.getAnnotatedType(Cloneable.class); + cloneableType.addAnnotations(annotations); + superTypes.add(cloneableType); + + AnnotatedTypeMirror serializableType = atypeFactory.getAnnotatedType(Serializable.class); + serializableType.addAnnotations(annotations); + superTypes.add(serializableType); + + for (AnnotatedTypeMirror sup : type.getComponentType().directSupertypes()) { + ArrayType arrType = atypeFactory.types.getArrayType(sup.getUnderlyingType()); + AnnotatedArrayType aarrType = + (AnnotatedArrayType) atypeFactory.toAnnotatedType(arrType, false); + aarrType.setComponentType(sup); + aarrType.addAnnotations(annotations); + superTypes.add(aarrType); + } + + return superTypes; + } - @Override - public List visitWildcard(AnnotatedWildcardType type, Void p) { - return Collections.singletonList(type.getExtendsBound().deepCopy()); - } + @Override + public List visitTypeVariable(AnnotatedTypeVariable type, Void p) { + return Collections.singletonList(type.getUpperBound().deepCopy()); + } + + @Override + public List visitWildcard(AnnotatedWildcardType type, Void p) { + return Collections.singletonList(type.getExtendsBound().deepCopy()); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java b/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java index 1f7a11a7df9..179b0174c99 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java @@ -1,11 +1,10 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; - import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; /** * SyntheticArrays exists solely to fix AnnotatedTypeMirrors that need to be adapted from Array type @@ -14,37 +13,36 @@ */ public final class SyntheticArrays { - /** Do not instantiate. */ - private SyntheticArrays() { - throw new AssertionError("Class SyntheticArrays cannot be instantiated."); - } + /** Do not instantiate. */ + private SyntheticArrays() { + throw new AssertionError("Class SyntheticArrays cannot be instantiated."); + } - /** - * Returns true if this combination of type/elem represents an array.clone. - * - * @param type a type with a method/field of elem - * @param elem an element which is a member of type - * @return true if this combination of type/elem represents an array.clone - */ - public static boolean isArrayClone(AnnotatedTypeMirror type, Element elem) { - return type.getKind() == TypeKind.ARRAY - && elem.getKind() == ElementKind.METHOD - && elem.getSimpleName().contentEquals("clone"); - } + /** + * Returns true if this combination of type/elem represents an array.clone. + * + * @param type a type with a method/field of elem + * @param elem an element which is a member of type + * @return true if this combination of type/elem represents an array.clone + */ + public static boolean isArrayClone(AnnotatedTypeMirror type, Element elem) { + return type.getKind() == TypeKind.ARRAY + && elem.getKind() == ElementKind.METHOD + && elem.getSimpleName().contentEquals("clone"); + } - /** - * Returns the annotated type of methodElem with its return type replaced by newReturnType. - * - * @param methodElem identifies a method that should have an AnnotatedArrayType as its return - * type - * @param newReturnType identifies a type that should replace methodElem's return type - * @return the annotated type of methodElem with its return type replaced by newReturnType - */ - public static AnnotatedExecutableType replaceReturnType( - Element methodElem, AnnotatedArrayType newReturnType) { - AnnotatedExecutableType method = - (AnnotatedExecutableType) newReturnType.atypeFactory.getAnnotatedType(methodElem); - method.setReturnType(newReturnType); - return method; - } + /** + * Returns the annotated type of methodElem with its return type replaced by newReturnType. + * + * @param methodElem identifies a method that should have an AnnotatedArrayType as its return type + * @param newReturnType identifies a type that should replace methodElem's return type + * @return the annotated type of methodElem with its return type replaced by newReturnType + */ + public static AnnotatedExecutableType replaceReturnType( + Element methodElem, AnnotatedArrayType newReturnType) { + AnnotatedExecutableType method = + (AnnotatedExecutableType) newReturnType.atypeFactory.getAnnotatedType(methodElem); + method.setReturnType(newReturnType); + return method; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java index 42330dc568e..afe6b1fb263 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java @@ -1,10 +1,8 @@ package org.checkerframework.framework.type; import com.sun.source.tree.ClassTree; - -import org.checkerframework.javacutil.TreeUtils; - import javax.lang.model.element.TypeElement; +import org.checkerframework.javacutil.TreeUtils; /** * Converts ClassTrees into AnnotatedDeclaredType. @@ -13,13 +11,13 @@ */ class TypeFromClassVisitor extends TypeFromTreeVisitor { - @Override - public AnnotatedTypeMirror visitClass(ClassTree tree, AnnotatedTypeFactory f) { - TypeElement elt = TreeUtils.elementFromDeclaration(tree); - AnnotatedTypeMirror result = f.toAnnotatedType(elt.asType(), true); + @Override + public AnnotatedTypeMirror visitClass(ClassTree tree, AnnotatedTypeFactory f) { + TypeElement elt = TreeUtils.elementFromDeclaration(tree); + AnnotatedTypeMirror result = f.toAnnotatedType(elt.asType(), true); - ElementAnnotationApplier.apply(result, elt, f); + ElementAnnotationApplier.apply(result, elt, f); - return result; - } + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java index d61276dbe9d..1a586353090 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java @@ -25,7 +25,12 @@ import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.WildcardTree; - +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -41,14 +46,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * Converts ExpressionTrees into AnnotatedTypeMirrors. * @@ -80,367 +77,365 @@ /*package-private*/ class TypeFromExpressionVisitor extends TypeFromTreeVisitor { - /** Creates a new TypeFromTreeVisitor. */ - /*package-private*/ TypeFromExpressionVisitor() { - // nothing to do - } - - @Override - public AnnotatedTypeMirror visitAnnotation(AnnotationTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitBinary(BinaryTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitCompoundAssignment( - CompoundAssignmentTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitInstanceOf(InstanceOfTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitLiteral(LiteralTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitUnary(UnaryTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { - return f.fromTypeTree(tree); + /** Creates a new TypeFromTreeVisitor. */ + /*package-private*/ TypeFromExpressionVisitor() { + // nothing to do + } + + @Override + public AnnotatedTypeMirror visitAnnotation(AnnotationTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitBinary(BinaryTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitCompoundAssignment( + CompoundAssignmentTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitInstanceOf(InstanceOfTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitLiteral(LiteralTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitUnary(UnaryTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { + return f.fromTypeTree(tree); + } + + @Override + public AnnotatedTypeMirror visitTypeCast(TypeCastTree tree, AnnotatedTypeFactory f) { + // Use the annotated type of the type in the cast. + return f.fromTypeTree(tree.getType()); + } + + @Override + public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { + // for e.g. "int.class" + return f.fromTypeTree(tree); + } + + @Override + public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { + // for e.g. "int[].class" + return f.fromTypeTree(tree); + } + + @Override + public AnnotatedTypeMirror visitParameterizedType( + ParameterizedTypeTree tree, AnnotatedTypeFactory f) { + return f.fromTypeTree(tree); + } + + @Override + public AnnotatedTypeMirror visitIntersectionType( + IntersectionTypeTree tree, AnnotatedTypeFactory f) { + return f.fromTypeTree(tree); + } + + @Override + public AnnotatedTypeMirror visitMemberReference( + MemberReferenceTree tree, AnnotatedTypeFactory f) { + return f.toAnnotatedType(TreeUtils.typeOf(tree), false); + } + + @Override + public AnnotatedTypeMirror visitLambdaExpression( + LambdaExpressionTree tree, AnnotatedTypeFactory f) { + return f.toAnnotatedType(TreeUtils.typeOf(tree), false); + } + + @Override + public AnnotatedTypeMirror visitAssignment(AssignmentTree tree, AnnotatedTypeFactory f) { + // Recurse on the type of the variable. + return visit(tree.getVariable(), f); + } + + @Override + public AnnotatedTypeMirror visitConditionalExpression( + ConditionalExpressionTree tree, AnnotatedTypeFactory f) { + // The Java type of a conditional expression is generally the LUB of the boxed types + // of the true and false expressions, but with a few exceptions. See JLS 15.25. + // So, use the type of the ConditionalExpressionTree instead of + // InternalUtils#leastUpperBound + TypeMirror alub = TreeUtils.typeOf(tree); + + AnnotatedTypeMirror trueType = f.getAnnotatedType(tree.getTrueExpression()); + AnnotatedTypeMirror falseType = f.getAnnotatedType(tree.getFalseExpression()); + + return AnnotatedTypes.leastUpperBound(f, trueType, falseType, alub); + } + + // TODO: remove method and instead use JCP to add version-specific methods + // Switch expressions first appeared in 12, standard in 14, so don't use 17. + @Override + public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { + if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { + return visitSwitchExpressionTree17(tree, f); } - - @Override - public AnnotatedTypeMirror visitTypeCast(TypeCastTree tree, AnnotatedTypeFactory f) { - // Use the annotated type of the type in the cast. - return f.fromTypeTree(tree.getType()); + return super.defaultAction(tree, f); + } + + /** + * Compute the type of the switch expression tree. + * + * @param switchExpressionTree a SwitchExpressionTree; typed as Tree so method signature is + * backward-compatible + * @param f an AnnotatedTypeFactory + * @return the type of the switch expression + */ + public AnnotatedTypeMirror visitSwitchExpressionTree17( + Tree switchExpressionTree, AnnotatedTypeFactory f) { + TypeMirror switchTypeMirror = TreeUtils.typeOf(switchExpressionTree); + SwitchExpressionScanner luber = + new FunctionalSwitchExpressionScanner<>( + // Function applied to each result expression of the switch expression. + (valueTree, unused) -> f.getAnnotatedType(valueTree), + // Function used to combine the types of each result expression. + (type1, type2) -> { + if (type1 == null) { + return type2; + } else if (type2 == null) { + return type1; + } else { + return AnnotatedTypes.leastUpperBound(f, type1, type2, switchTypeMirror); + } + }); + return luber.scanSwitchExpression(switchExpressionTree, null); + } + + @Override + public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { + if (tree.getName().contentEquals("this") || tree.getName().contentEquals("super")) { + AnnotatedDeclaredType res = f.getSelfType(tree); + return res; } - @Override - public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { - // for e.g. "int.class" - return f.fromTypeTree(tree); + Element elt = TreeUtils.elementFromUse(tree); + AnnotatedTypeMirror selfType = f.getImplicitReceiverType(tree); + if (selfType != null) { + AnnotatedTypeMirror type = AnnotatedTypes.asMemberOf(f.types, f, selfType, elt).asUse(); + return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); } - @Override - public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { - // for e.g. "int[].class" - return f.fromTypeTree(tree); - } + AnnotatedTypeMirror type = f.getAnnotatedType(elt); - @Override - public AnnotatedTypeMirror visitParameterizedType( - ParameterizedTypeTree tree, AnnotatedTypeFactory f) { - return f.fromTypeTree(tree); - } + return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); + } - @Override - public AnnotatedTypeMirror visitIntersectionType( - IntersectionTypeTree tree, AnnotatedTypeFactory f) { - return f.fromTypeTree(tree); - } + @Override + public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { + Element elt = TreeUtils.elementFromUse(tree); - @Override - public AnnotatedTypeMirror visitMemberReference( - MemberReferenceTree tree, AnnotatedTypeFactory f) { - return f.toAnnotatedType(TreeUtils.typeOf(tree), false); + if (TreeUtils.isClassLiteral(tree)) { + // the type of a class literal is the type of the "class" element. + return f.getAnnotatedType(elt); } - - @Override - public AnnotatedTypeMirror visitLambdaExpression( - LambdaExpressionTree tree, AnnotatedTypeFactory f) { - return f.toAnnotatedType(TreeUtils.typeOf(tree), false); + switch (ElementUtils.getKindRecordAsClass(elt)) { + case METHOD: + case CONSTRUCTOR: // x0.super() in anoymous classes + case PACKAGE: // "java.lang" in new java.lang.Short("2") + case CLASS: // o instanceof MyClass.InnerClass + case ENUM: + case INTERFACE: // o instanceof MyClass.InnerInterface + case ANNOTATION_TYPE: + return f.fromElement(elt); + default: + // Fall-through. } - @Override - public AnnotatedTypeMirror visitAssignment(AssignmentTree tree, AnnotatedTypeFactory f) { - // Recurse on the type of the variable. - return visit(tree.getVariable(), f); + if (tree.getIdentifier().contentEquals("this")) { + // Tree is "MyClass.this", where "MyClass" may be the innermost enclosing type or any + // outer type. + return f.getEnclosingType(TypesUtils.getTypeElement(TreeUtils.typeOf(tree)), tree); + } else if (tree.getIdentifier().contentEquals("super")) { + // Tree is "MyClass.super", where "MyClass" may be the innermost enclosing type or any + // outer type. + TypeMirror superTypeMirror = TreeUtils.typeOf(tree); + TypeElement superTypeElement = TypesUtils.getTypeElement(superTypeMirror); + AnnotatedDeclaredType thisType = f.getEnclosingSubType(superTypeElement, tree); + return AnnotatedTypes.asSuper( + f, thisType, AnnotatedTypeMirror.createType(superTypeMirror, f, false)); + } else { + // tree must be a field access, so get the type of the expression, and then call + // asMemberOf. + AnnotatedTypeMirror t; + if (f instanceof GenericAnnotatedTypeFactory) { + // If calling GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree lhsTree) to + // get the type of this MemberSelectTree, flow refinement is disabled. However, + // we want the receiver to have the refined type because type + // systems can use receiver-dependent qualifiers for viewpoint adaptation. + // Thus, we re-enable the flow refinement for a while just for the receiver + // expression. + // See framework/tests/viewpointtest/TestGetAnnotatedLhs.java for a concrete + // example. + t = + ((GenericAnnotatedTypeFactory) f) + .getAnnotatedTypeWithReceiverRefinement(tree.getExpression()); + } else { + t = f.getAnnotatedType(tree.getExpression()); + } + t = f.applyCaptureConversion(t); + return AnnotatedTypes.asMemberOf(f.types, f, t, elt).asUse(); } - - @Override - public AnnotatedTypeMirror visitConditionalExpression( - ConditionalExpressionTree tree, AnnotatedTypeFactory f) { - // The Java type of a conditional expression is generally the LUB of the boxed types - // of the true and false expressions, but with a few exceptions. See JLS 15.25. - // So, use the type of the ConditionalExpressionTree instead of - // InternalUtils#leastUpperBound - TypeMirror alub = TreeUtils.typeOf(tree); - - AnnotatedTypeMirror trueType = f.getAnnotatedType(tree.getTrueExpression()); - AnnotatedTypeMirror falseType = f.getAnnotatedType(tree.getFalseExpression()); - - return AnnotatedTypes.leastUpperBound(f, trueType, falseType, alub); + } + + @Override + public AnnotatedTypeMirror visitArrayAccess(ArrayAccessTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.getAnnotatedType(tree.getExpression()); + if (type.getKind() == TypeKind.ARRAY) { + return ((AnnotatedArrayType) type).getComponentType(); + } else if (type.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type).isUninferredTypeArgument()) { + // Clean-up after Issue #979. + AnnotatedTypeMirror wcbound = ((AnnotatedWildcardType) type).getExtendsBound(); + if (wcbound instanceof AnnotatedArrayType) { + return ((AnnotatedArrayType) wcbound).getComponentType(); + } } + throw new BugInCF("Unexpected type: " + type); + } - // TODO: remove method and instead use JCP to add version-specific methods - // Switch expressions first appeared in 12, standard in 14, so don't use 17. - @Override - public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { - if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { - return visitSwitchExpressionTree17(tree, f); - } - return super.defaultAction(tree, f); - } + @Override + public AnnotatedTypeMirror visitNewArray(NewArrayTree tree, AnnotatedTypeFactory f) { + // Don't use fromTypeTree here, because tree.getType() is not an array type! + AnnotatedArrayType result = (AnnotatedArrayType) f.type(tree); - /** - * Compute the type of the switch expression tree. - * - * @param switchExpressionTree a SwitchExpressionTree; typed as Tree so method signature is - * backward-compatible - * @param f an AnnotatedTypeFactory - * @return the type of the switch expression - */ - public AnnotatedTypeMirror visitSwitchExpressionTree17( - Tree switchExpressionTree, AnnotatedTypeFactory f) { - TypeMirror switchTypeMirror = TreeUtils.typeOf(switchExpressionTree); - SwitchExpressionScanner luber = - new FunctionalSwitchExpressionScanner<>( - // Function applied to each result expression of the switch expression. - (valueTree, unused) -> f.getAnnotatedType(valueTree), - // Function used to combine the types of each result expression. - (type1, type2) -> { - if (type1 == null) { - return type2; - } else if (type2 == null) { - return type1; - } else { - return AnnotatedTypes.leastUpperBound( - f, type1, type2, switchTypeMirror); - } - }); - return luber.scanSwitchExpression(switchExpressionTree, null); + if (tree.getType() == null) { // e.g., byte[] b = {(byte)1, (byte)2}; + return result; } - @Override - public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { - if (tree.getName().contentEquals("this") || tree.getName().contentEquals("super")) { - AnnotatedDeclaredType res = f.getSelfType(tree); - return res; - } - - Element elt = TreeUtils.elementFromUse(tree); - AnnotatedTypeMirror selfType = f.getImplicitReceiverType(tree); - if (selfType != null) { - AnnotatedTypeMirror type = AnnotatedTypes.asMemberOf(f.types, f, selfType, elt).asUse(); - return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); - } + annotateArrayAsArray(result, tree, f); - AnnotatedTypeMirror type = f.getAnnotatedType(elt); + return result; + } - return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); + private AnnotatedTypeMirror descendBy(AnnotatedTypeMirror type, int depth) { + AnnotatedTypeMirror result = type; + while (depth > 0) { + result = ((AnnotatedArrayType) result).getComponentType(); + depth--; } - - @Override - public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { - Element elt = TreeUtils.elementFromUse(tree); - - if (TreeUtils.isClassLiteral(tree)) { - // the type of a class literal is the type of the "class" element. - return f.getAnnotatedType(elt); - } - switch (ElementUtils.getKindRecordAsClass(elt)) { - case METHOD: - case CONSTRUCTOR: // x0.super() in anoymous classes - case PACKAGE: // "java.lang" in new java.lang.Short("2") - case CLASS: // o instanceof MyClass.InnerClass - case ENUM: - case INTERFACE: // o instanceof MyClass.InnerInterface - case ANNOTATION_TYPE: - return f.fromElement(elt); - default: - // Fall-through. - } - - if (tree.getIdentifier().contentEquals("this")) { - // Tree is "MyClass.this", where "MyClass" may be the innermost enclosing type or any - // outer type. - return f.getEnclosingType(TypesUtils.getTypeElement(TreeUtils.typeOf(tree)), tree); - } else if (tree.getIdentifier().contentEquals("super")) { - // Tree is "MyClass.super", where "MyClass" may be the innermost enclosing type or any - // outer type. - TypeMirror superTypeMirror = TreeUtils.typeOf(tree); - TypeElement superTypeElement = TypesUtils.getTypeElement(superTypeMirror); - AnnotatedDeclaredType thisType = f.getEnclosingSubType(superTypeElement, tree); - return AnnotatedTypes.asSuper( - f, thisType, AnnotatedTypeMirror.createType(superTypeMirror, f, false)); - } else { - // tree must be a field access, so get the type of the expression, and then call - // asMemberOf. - AnnotatedTypeMirror t; - if (f instanceof GenericAnnotatedTypeFactory) { - // If calling GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree lhsTree) to - // get the type of this MemberSelectTree, flow refinement is disabled. However, - // we want the receiver to have the refined type because type - // systems can use receiver-dependent qualifiers for viewpoint adaptation. - // Thus, we re-enable the flow refinement for a while just for the receiver - // expression. - // See framework/tests/viewpointtest/TestGetAnnotatedLhs.java for a concrete - // example. - t = - ((GenericAnnotatedTypeFactory) f) - .getAnnotatedTypeWithReceiverRefinement(tree.getExpression()); - } else { - t = f.getAnnotatedType(tree.getExpression()); - } - t = f.applyCaptureConversion(t); - return AnnotatedTypes.asMemberOf(f.types, f, t, elt).asUse(); - } + return result; + } + + /** + * Add annotations to an array type. + * + * @param result an array type; is side-effected by this method + * @param tree an array construction expression from which to obtain annotations + * @param f the type factory + */ + private void annotateArrayAsArray( + AnnotatedArrayType result, NewArrayTree tree, AnnotatedTypeFactory f) { + // Copy annotations from the type. + AnnotatedTypeMirror treeElem = f.fromTypeTree(tree.getType()); + boolean hasInit = tree.getInitializers() != null; + AnnotatedTypeMirror typeElem = descendBy(result, hasInit ? 1 : tree.getDimensions().size()); + while (true) { + typeElem.addAnnotations(treeElem.getAnnotations()); + if (!(treeElem instanceof AnnotatedArrayType)) { + break; + } + assert typeElem instanceof AnnotatedArrayType; + treeElem = ((AnnotatedArrayType) treeElem).getComponentType(); + typeElem = ((AnnotatedArrayType) typeElem).getComponentType(); } - - @Override - public AnnotatedTypeMirror visitArrayAccess(ArrayAccessTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.getAnnotatedType(tree.getExpression()); - if (type.getKind() == TypeKind.ARRAY) { - return ((AnnotatedArrayType) type).getComponentType(); - } else if (type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument()) { - // Clean-up after Issue #979. - AnnotatedTypeMirror wcbound = ((AnnotatedWildcardType) type).getExtendsBound(); - if (wcbound instanceof AnnotatedArrayType) { - return ((AnnotatedArrayType) wcbound).getComponentType(); - } - } - throw new BugInCF("Unexpected type: " + type); + // Add all dimension annotations. + int idx = 0; + AnnotatedTypeMirror level = result; + while (level.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType array = (AnnotatedArrayType) level; + List annos = TreeUtils.annotationsFromArrayCreation(tree, idx++); + array.addAnnotations(annos); + level = array.getComponentType(); } - @Override - public AnnotatedTypeMirror visitNewArray(NewArrayTree tree, AnnotatedTypeFactory f) { - // Don't use fromTypeTree here, because tree.getType() is not an array type! - AnnotatedArrayType result = (AnnotatedArrayType) f.type(tree); - - if (tree.getType() == null) { // e.g., byte[] b = {(byte)1, (byte)2}; - return result; - } - - annotateArrayAsArray(result, tree, f); - - return result; + // Add top-level annotations. + result.addAnnotations(TreeUtils.annotationsFromArrayCreation(tree, -1)); + } + + /** + * Creates an AnnotatedDeclaredType for the NewClassTree and adds, for each hierarchy, one of: + * + *
    + *
  • an explicit annotation on the new class expression ({@code new @HERE MyClass()}), or + *
  • an explicit annotation on the declaration of the class ({@code @HERE class MyClass {}}), + * or + *
  • an explicit or default annotation on the declaration of the constructor ({@code @HERE + * public MyClass() {}}). + *
+ * + * @param tree a NewClassTree + * @param f the type factory + * @return AnnotatedDeclaredType of {@code tree} + */ + @Override + public AnnotatedTypeMirror visitNewClass(NewClassTree tree, AnnotatedTypeFactory f) { + // Add annotations that are on the constructor declaration. + AnnotatedDeclaredType returnType = + (AnnotatedDeclaredType) f.constructorFromUse(tree).executableType.getReturnType(); + // Clear the annotations on the return type, so that the explicit annotations can be added + // first, then the annotations from the return type are added as needed. + AnnotationMirrorSet fromReturn = new AnnotationMirrorSet(returnType.getAnnotations()); + returnType.clearAnnotations(); + returnType.addAnnotations(f.getExplicitNewClassAnnos(tree)); + returnType.addMissingAnnotations(fromReturn); + return returnType; + } + + @Override + public AnnotatedTypeMirror visitMethodInvocation( + MethodInvocationTree tree, AnnotatedTypeFactory f) { + AnnotatedExecutableType ex = f.methodFromUse(tree).executableType; + AnnotatedTypeMirror returnT = ex.getReturnType().asUse(); + if (TypesUtils.isCapturedTypeVariable(returnT.getUnderlyingType()) + && !TypesUtils.isCapturedTypeVariable(TreeUtils.typeOf(tree))) { + // Sometimes javac types an expression as the upper bound of a captured type variable + // instead of the captured type variable itself. This seems to be a bug in javac. Detect + // this case and match the annotated type to the Java type. + returnT = ((AnnotatedTypeVariable) returnT).getUpperBound(); } - - private AnnotatedTypeMirror descendBy(AnnotatedTypeMirror type, int depth) { - AnnotatedTypeMirror result = type; - while (depth > 0) { - result = ((AnnotatedArrayType) result).getComponentType(); - depth--; - } - return result; - } - - /** - * Add annotations to an array type. - * - * @param result an array type; is side-effected by this method - * @param tree an array construction expression from which to obtain annotations - * @param f the type factory - */ - private void annotateArrayAsArray( - AnnotatedArrayType result, NewArrayTree tree, AnnotatedTypeFactory f) { - // Copy annotations from the type. - AnnotatedTypeMirror treeElem = f.fromTypeTree(tree.getType()); - boolean hasInit = tree.getInitializers() != null; - AnnotatedTypeMirror typeElem = descendBy(result, hasInit ? 1 : tree.getDimensions().size()); - while (true) { - typeElem.addAnnotations(treeElem.getAnnotations()); - if (!(treeElem instanceof AnnotatedArrayType)) { - break; - } - assert typeElem instanceof AnnotatedArrayType; - treeElem = ((AnnotatedArrayType) treeElem).getComponentType(); - typeElem = ((AnnotatedArrayType) typeElem).getComponentType(); - } - // Add all dimension annotations. - int idx = 0; - AnnotatedTypeMirror level = result; - while (level.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType array = (AnnotatedArrayType) level; - List annos = - TreeUtils.annotationsFromArrayCreation(tree, idx++); - array.addAnnotations(annos); - level = array.getComponentType(); - } - - // Add top-level annotations. - result.addAnnotations(TreeUtils.annotationsFromArrayCreation(tree, -1)); - } - - /** - * Creates an AnnotatedDeclaredType for the NewClassTree and adds, for each hierarchy, one of: - * - *
    - *
  • an explicit annotation on the new class expression ({@code new @HERE MyClass()}), or - *
  • an explicit annotation on the declaration of the class ({@code @HERE class MyClass - * {}}), or - *
  • an explicit or default annotation on the declaration of the constructor ({@code @HERE - * public MyClass() {}}). - *
- * - * @param tree a NewClassTree - * @param f the type factory - * @return AnnotatedDeclaredType of {@code tree} - */ - @Override - public AnnotatedTypeMirror visitNewClass(NewClassTree tree, AnnotatedTypeFactory f) { - // Add annotations that are on the constructor declaration. - AnnotatedDeclaredType returnType = - (AnnotatedDeclaredType) f.constructorFromUse(tree).executableType.getReturnType(); - // Clear the annotations on the return type, so that the explicit annotations can be added - // first, then the annotations from the return type are added as needed. - AnnotationMirrorSet fromReturn = new AnnotationMirrorSet(returnType.getAnnotations()); - returnType.clearAnnotations(); - returnType.addAnnotations(f.getExplicitNewClassAnnos(tree)); - returnType.addMissingAnnotations(fromReturn); - return returnType; - } - - @Override - public AnnotatedTypeMirror visitMethodInvocation( - MethodInvocationTree tree, AnnotatedTypeFactory f) { - AnnotatedExecutableType ex = f.methodFromUse(tree).executableType; - AnnotatedTypeMirror returnT = ex.getReturnType().asUse(); - if (TypesUtils.isCapturedTypeVariable(returnT.getUnderlyingType()) - && !TypesUtils.isCapturedTypeVariable(TreeUtils.typeOf(tree))) { - // Sometimes javac types an expression as the upper bound of a captured type variable - // instead of the captured type variable itself. This seems to be a bug in javac. Detect - // this case and match the annotated type to the Java type. - returnT = ((AnnotatedTypeVariable) returnT).getUpperBound(); - } - return f.applyCaptureConversion(returnT); - } - - @Override - public AnnotatedTypeMirror visitParenthesized(ParenthesizedTree tree, AnnotatedTypeFactory f) { - // Recurse on the expression inside the parens. - return visit(tree.getExpression(), f); - } - - @Override - public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror bound = visit(tree.getBound(), f); - - AnnotatedTypeMirror result = f.type(tree); - assert result instanceof AnnotatedWildcardType; - - // Instead of directly overwriting the bound, replace each annotation - // to ensure that the structure of the wildcard will match that created by - // BoundsInitializer/createType. - if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { - f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getSuperBound()); - - } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { - f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getExtendsBound()); - } - return result; + return f.applyCaptureConversion(returnT); + } + + @Override + public AnnotatedTypeMirror visitParenthesized(ParenthesizedTree tree, AnnotatedTypeFactory f) { + // Recurse on the expression inside the parens. + return visit(tree.getExpression(), f); + } + + @Override + public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror bound = visit(tree.getBound(), f); + + AnnotatedTypeMirror result = f.type(tree); + assert result instanceof AnnotatedWildcardType; + + // Instead of directly overwriting the bound, replace each annotation + // to ensure that the structure of the wildcard will match that created by + // BoundsInitializer/createType. + if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { + f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getSuperBound()); + + } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { + f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getExtendsBound()); } + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java index 9d31441bac3..84394cfa31f 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java @@ -5,24 +5,21 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; - import java.util.Collections; import java.util.List; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; /** * Converts a field or methods tree into an AnnotatedTypeMirror. Using addAnnotations for innerType @@ -33,160 +30,158 @@ */ class TypeFromMemberVisitor extends TypeFromTreeVisitor { - @Override - public AnnotatedTypeMirror visitVariable(VariableTree variableTree, AnnotatedTypeFactory f) { - Element elt = TreeUtils.elementFromDeclaration(variableTree); + @Override + public AnnotatedTypeMirror visitVariable(VariableTree variableTree, AnnotatedTypeFactory f) { + Element elt = TreeUtils.elementFromDeclaration(variableTree); + + // Create the ATM and add non-primary annotations + AnnotatedTypeMirror result; + // Propagate initializer annotated type to variable if declared using var. + // Skip propagation of annotations when initializer can be null. + // E.g. + // for (var i : list) {} + if (variableTree.getInitializer() != null + && TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { + // BaseTypeVisitor#visitVariable does not need to check the type of `var` declarations. + result = f.getAnnotatedType(variableTree.getInitializer()); + // Let normal defaulting happen for the primary annotation. + result.clearAnnotations(); + } else if (variableTree.getType() == null + || TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { + // VariableTree#getType returns null for binding variables from a + // DeconstructionPatternTree. + result = f.type(variableTree); + } else { + // (variableTree.getType() does not include the annotation before the type, so those + // are added to the type below). + result = TypeFromTree.fromTypeTree(f, variableTree.getType()); + } - // Create the ATM and add non-primary annotations - AnnotatedTypeMirror result; - // Propagate initializer annotated type to variable if declared using var. - // Skip propagation of annotations when initializer can be null. - // E.g. - // for (var i : list) {} - if (variableTree.getInitializer() != null - && TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { - // BaseTypeVisitor#visitVariable does not need to check the type of `var` declarations. - result = f.getAnnotatedType(variableTree.getInitializer()); - // Let normal defaulting happen for the primary annotation. - result.clearAnnotations(); - } else if (variableTree.getType() == null - || TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { - // VariableTree#getType returns null for binding variables from a - // DeconstructionPatternTree. - result = f.type(variableTree); - } else { - // (variableTree.getType() does not include the annotation before the type, so those - // are added to the type below). - result = TypeFromTree.fromTypeTree(f, variableTree.getType()); - } + // Handle any annotations in variableTree.getModifiers(). + List modifierAnnos; + List annoTrees = variableTree.getModifiers().getAnnotations(); + if (annoTrees != null && !annoTrees.isEmpty()) { + modifierAnnos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); + } else { + modifierAnnos = Collections.emptyList(); + } - // Handle any annotations in variableTree.getModifiers(). - List modifierAnnos; - List annoTrees = variableTree.getModifiers().getAnnotations(); - if (annoTrees != null && !annoTrees.isEmpty()) { - modifierAnnos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); - } else { - modifierAnnos = Collections.emptyList(); - } + if (result.getKind() != TypeKind.TYPEVAR || modifierAnnos.isEmpty()) { + // See comment in visitMethod + ElementAnnotationApplier.apply(result, elt, f); + } - if (result.getKind() != TypeKind.TYPEVAR || modifierAnnos.isEmpty()) { - // See comment in visitMethod - ElementAnnotationApplier.apply(result, elt, f); + if (result.getKind() == TypeKind.DECLARED + && + // Annotations on enum constants are not in the TypeMirror and always apply to the + // innermost type, so handle them in the else block. + elt.getKind() != ElementKind.ENUM_CONSTANT) { + // Decode the annotations from the type mirror because the annotations are already in + // the correct place for enclosing types. The annotations in + // variableTree.getModifiers() might apply to the enclosing type or the type itself. + // For example, @Tainted Outer.Inner y and @Tainted Inner x. + // @Tainted is stored in variableTree.getModifiers() of the variable tree corresponding + // to both x and y, but @Tainted applies to different types. + AnnotatedDeclaredType annotatedDeclaredType = (AnnotatedDeclaredType) result; + // The underlying type of result does not have all annotations, but the TypeMirror of + // variableTree.getType() does. + // VariableTree#getType returns null for binding variables from a + // DeconstructionPatternTree. + if (variableTree.getType() != null + && !TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { + DeclaredType declaredType = (DeclaredType) TreeUtils.typeOf(variableTree.getType()); + AnnotatedTypes.applyAnnotationsFromDeclaredType(annotatedDeclaredType, declaredType); + } + + // Handle declaration annotations + for (AnnotationMirror anno : modifierAnnos) { + if (!AnnotationUtils.isTypeUseAnnotation(anno)) { + // This does not treat Checker Framework compatqual annotations differently, + // because it's not clear whether the annotation should apply to the outermost + // enclosing type or the innermost. + result.addAnnotation(anno); } - - if (result.getKind() == TypeKind.DECLARED - && - // Annotations on enum constants are not in the TypeMirror and always apply to the - // innermost type, so handle them in the else block. - elt.getKind() != ElementKind.ENUM_CONSTANT) { - // Decode the annotations from the type mirror because the annotations are already in - // the correct place for enclosing types. The annotations in - // variableTree.getModifiers() might apply to the enclosing type or the type itself. - // For example, @Tainted Outer.Inner y and @Tainted Inner x. - // @Tainted is stored in variableTree.getModifiers() of the variable tree corresponding - // to both x and y, but @Tainted applies to different types. - AnnotatedDeclaredType annotatedDeclaredType = (AnnotatedDeclaredType) result; - // The underlying type of result does not have all annotations, but the TypeMirror of - // variableTree.getType() does. - // VariableTree#getType returns null for binding variables from a - // DeconstructionPatternTree. - if (variableTree.getType() != null - && !TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { - DeclaredType declaredType = (DeclaredType) TreeUtils.typeOf(variableTree.getType()); - AnnotatedTypes.applyAnnotationsFromDeclaredType( - annotatedDeclaredType, declaredType); - } - - // Handle declaration annotations - for (AnnotationMirror anno : modifierAnnos) { - if (!AnnotationUtils.isTypeUseAnnotation(anno)) { - // This does not treat Checker Framework compatqual annotations differently, - // because it's not clear whether the annotation should apply to the outermost - // enclosing type or the innermost. - result.addAnnotation(anno); - } - // If anno is not a declaration annotation, it should have been applied in the call - // to applyAnnotationsFromDeclaredType above. - } + // If anno is not a declaration annotation, it should have been applied in the call + // to applyAnnotationsFromDeclaredType above. + } + } else { + // Add the primary annotation from the variableTree.getModifiers(); + AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(result); + for (AnnotationMirror anno : modifierAnnos) { + // The code here is similar to + // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. + if (AnnotationUtils.isTypeUseAnnotation(anno) + // Always treat Checker Framework annotations as type annotations. + || AnnotationUtils.annotationName(anno).startsWith("org.checkerframework")) { + // Type annotations apply to the innermost type. + innerType.addAnnotation(anno); } else { - // Add the primary annotation from the variableTree.getModifiers(); - AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(result); - for (AnnotationMirror anno : modifierAnnos) { - // The code here is similar to - // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. - if (AnnotationUtils.isTypeUseAnnotation(anno) - // Always treat Checker Framework annotations as type annotations. - || AnnotationUtils.annotationName(anno) - .startsWith("org.checkerframework")) { - // Type annotations apply to the innermost type. - innerType.addAnnotation(anno); - } else { - // Declaration annotations apply to the outer type. - result.addAnnotation(anno); - } - } - } - - AnnotatedTypeMirror lambdaParamType = inferLambdaParamAnnotations(f, result, elt); - if (lambdaParamType != null) { - return lambdaParamType; + // Declaration annotations apply to the outer type. + result.addAnnotation(anno); } - return result; + } } - @Override - public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) { - ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); - - AnnotatedExecutableType result = - (AnnotatedExecutableType) f.toAnnotatedType(elt.asType(), false); - result.setElement(elt); - // Make sure the return type field gets initialized... otherwise - // some code throws NPE. This should be cleaned up. - result.getReturnType(); - - // TODO: Needed to visit parameter types, etc. - // It would be nicer if this didn't decode the information from the Element and - // instead also used the Tree. If this is implemented, then care needs to be taken to put - // any alias declaration annotations in the correct place for return types that are arrays. - // This would be similar to - // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. - ElementAnnotationApplier.apply(result, elt, f); - return result; + AnnotatedTypeMirror lambdaParamType = inferLambdaParamAnnotations(f, result, elt); + if (lambdaParamType != null) { + return lambdaParamType; } - - /** - * Returns the type of the lambda parameter, or null if paramElement is not a lambda parameter. - * - * @return the type of the lambda parameter, or null if paramElement is not a lambda parameter - */ - private static @Nullable AnnotatedTypeMirror inferLambdaParamAnnotations( - AnnotatedTypeFactory f, AnnotatedTypeMirror lambdaParam, Element paramElement) { - if (paramElement.getKind() != ElementKind.PARAMETER - || f.declarationFromElement(paramElement) == null - || f.getPath(f.declarationFromElement(paramElement)) == null - || f.getPath(f.declarationFromElement(paramElement)).getParentPath() == null) { - - return null; - } - Tree declaredInTree = - f.getPath(f.declarationFromElement(paramElement)).getParentPath().getLeaf(); - if (declaredInTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION - && TreeUtils.isImplicitlyTypedLambda(declaredInTree)) { - LambdaExpressionTree lambdaDecl = (LambdaExpressionTree) declaredInTree; - int index = lambdaDecl.getParameters().indexOf(f.declarationFromElement(paramElement)); - AnnotatedExecutableType functionType = f.getFunctionTypeFromTree(lambdaDecl); - AnnotatedTypeMirror funcTypeParam = functionType.getParameterTypes().get(index); - // The Java types should be exactly the same, but because invocation type - // inference (#979) isn't implement, check first. Use the erased types because the - // type arguments are not substituted when the annotated type arguments are. - if (TypesUtils.isErasedSubtype( - funcTypeParam.underlyingType, lambdaParam.underlyingType, f.types)) { - return AnnotatedTypes.asSuper(f, funcTypeParam, lambdaParam); - } - lambdaParam.addMissingAnnotations(funcTypeParam.getAnnotations()); - return lambdaParam; - } - return null; + return result; + } + + @Override + public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) { + ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); + + AnnotatedExecutableType result = + (AnnotatedExecutableType) f.toAnnotatedType(elt.asType(), false); + result.setElement(elt); + // Make sure the return type field gets initialized... otherwise + // some code throws NPE. This should be cleaned up. + result.getReturnType(); + + // TODO: Needed to visit parameter types, etc. + // It would be nicer if this didn't decode the information from the Element and + // instead also used the Tree. If this is implemented, then care needs to be taken to put + // any alias declaration annotations in the correct place for return types that are arrays. + // This would be similar to + // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. + ElementAnnotationApplier.apply(result, elt, f); + return result; + } + + /** + * Returns the type of the lambda parameter, or null if paramElement is not a lambda parameter. + * + * @return the type of the lambda parameter, or null if paramElement is not a lambda parameter + */ + private static @Nullable AnnotatedTypeMirror inferLambdaParamAnnotations( + AnnotatedTypeFactory f, AnnotatedTypeMirror lambdaParam, Element paramElement) { + if (paramElement.getKind() != ElementKind.PARAMETER + || f.declarationFromElement(paramElement) == null + || f.getPath(f.declarationFromElement(paramElement)) == null + || f.getPath(f.declarationFromElement(paramElement)).getParentPath() == null) { + + return null; + } + Tree declaredInTree = + f.getPath(f.declarationFromElement(paramElement)).getParentPath().getLeaf(); + if (declaredInTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION + && TreeUtils.isImplicitlyTypedLambda(declaredInTree)) { + LambdaExpressionTree lambdaDecl = (LambdaExpressionTree) declaredInTree; + int index = lambdaDecl.getParameters().indexOf(f.declarationFromElement(paramElement)); + AnnotatedExecutableType functionType = f.getFunctionTypeFromTree(lambdaDecl); + AnnotatedTypeMirror funcTypeParam = functionType.getParameterTypes().get(index); + // The Java types should be exactly the same, but because invocation type + // inference (#979) isn't implement, check first. Use the erased types because the + // type arguments are not substituted when the annotated type arguments are. + if (TypesUtils.isErasedSubtype( + funcTypeParam.underlyingType, lambdaParam.underlyingType, f.types)) { + return AnnotatedTypes.asSuper(f, funcTypeParam, lambdaParam); + } + lambdaParam.addMissingAnnotations(funcTypeParam.getAnnotations()); + return lambdaParam; } + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java index e32615daefa..9a5e58a6c5c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java @@ -3,13 +3,11 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; - +import javax.lang.model.type.TypeKind; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.javacutil.BugInCF; -import javax.lang.model.type.TypeKind; - /** * A utility class to convert trees into corresponding AnnotatedTypeMirrors. This class should be * used ONLY from AnnotatedTypeFactory. @@ -21,118 +19,115 @@ */ class TypeFromTree { - private static final TypeFromTypeTreeVisitor typeTreeVisitor = new TypeFromTypeTreeVisitor(); - private static final TypeFromMemberVisitor memberVisitor = new TypeFromMemberVisitor(); - private static final TypeFromClassVisitor classVisitor = new TypeFromClassVisitor(); - private static final TypeFromExpressionVisitor expressionVisitor = - new TypeFromExpressionVisitor(); - - /** - * Returns an AnnotatedTypeMirror representing the input expression tree. - * - * @param tree must be an ExpressionTree - * @return an AnnotatedTypeMirror representing the input expression tree - */ - public static AnnotatedTypeMirror fromExpression( - AnnotatedTypeFactory typeFactory, ExpressionTree tree) { - abortIfTreeIsNull(typeFactory, tree); - - final AnnotatedTypeMirror type; - try { - type = expressionVisitor.visit(tree, typeFactory); - } catch (Throwable t) { - throw new BugInCF( - t, - "Error in AnnotatedTypeMirror.fromExpression(%s, %s): %s", - typeFactory.getClass().getSimpleName(), - tree, - t.getMessage()); - } - ifExecutableCheckElement(typeFactory, tree, type); - - return type; - } - - /** - * Returns an AnnotatedTypeMirror representing the input tree. - * - * @param tree must represent a class member - * @return an AnnotatedTypeMirror representing the input tree - */ - public static AnnotatedTypeMirror fromMember(AnnotatedTypeFactory typeFactory, Tree tree) { - abortIfTreeIsNull(typeFactory, tree); - - AnnotatedTypeMirror type = memberVisitor.visit(tree, typeFactory); - ifExecutableCheckElement(typeFactory, tree, type); - return type; - } - - /** - * Returns an AnnotatedTypeMirror representing the input type tree. - * - * @param tree must be a type tree - * @return an AnnotatedTypeMirror representing the input type tree - */ - public static AnnotatedTypeMirror fromTypeTree(AnnotatedTypeFactory typeFactory, Tree tree) { - abortIfTreeIsNull(typeFactory, tree); - - AnnotatedTypeMirror type = typeTreeVisitor.visit(tree, typeFactory); - abortIfTypeIsExecutable(typeFactory, tree, type); - return type; + private static final TypeFromTypeTreeVisitor typeTreeVisitor = new TypeFromTypeTreeVisitor(); + private static final TypeFromMemberVisitor memberVisitor = new TypeFromMemberVisitor(); + private static final TypeFromClassVisitor classVisitor = new TypeFromClassVisitor(); + private static final TypeFromExpressionVisitor expressionVisitor = + new TypeFromExpressionVisitor(); + + /** + * Returns an AnnotatedTypeMirror representing the input expression tree. + * + * @param tree must be an ExpressionTree + * @return an AnnotatedTypeMirror representing the input expression tree + */ + public static AnnotatedTypeMirror fromExpression( + AnnotatedTypeFactory typeFactory, ExpressionTree tree) { + abortIfTreeIsNull(typeFactory, tree); + + final AnnotatedTypeMirror type; + try { + type = expressionVisitor.visit(tree, typeFactory); + } catch (Throwable t) { + throw new BugInCF( + t, + "Error in AnnotatedTypeMirror.fromExpression(%s, %s): %s", + typeFactory.getClass().getSimpleName(), + tree, + t.getMessage()); } - - /** - * Returns an AnnotatedDeclaredType representing the input ClassTree. - * - * @return an AnnotatedDeclaredType representing the input ClassTree - */ - public static AnnotatedDeclaredType fromClassTree( - AnnotatedTypeFactory typeFactory, ClassTree tree) { - abortIfTreeIsNull(typeFactory, tree); - - AnnotatedDeclaredType type = (AnnotatedDeclaredType) classVisitor.visit(tree, typeFactory); - abortIfTypeIsExecutable(typeFactory, tree, type); - return type; - } - - protected static void abortIfTreeIsNull(AnnotatedTypeFactory typeFactory, Tree tree) { - if (tree == null) { - throw new BugInCF("Encountered null tree" + summarize(typeFactory, tree)); - } - } - - protected static void ifExecutableCheckElement( - AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.EXECUTABLE) { - if (((AnnotatedExecutableType) type).getElement() == null) { - throw new BugInCF( - "Executable has no element:%n%s", summarize(typeFactory, tree, type)); - } - } - } - - protected static void abortIfTypeIsExecutable( - AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.EXECUTABLE) { - throw new BugInCF( - "Unexpected Executable typekind:%n%s", summarize(typeFactory, tree, type)); - } + ifExecutableCheckElement(typeFactory, tree, type); + + return type; + } + + /** + * Returns an AnnotatedTypeMirror representing the input tree. + * + * @param tree must represent a class member + * @return an AnnotatedTypeMirror representing the input tree + */ + public static AnnotatedTypeMirror fromMember(AnnotatedTypeFactory typeFactory, Tree tree) { + abortIfTreeIsNull(typeFactory, tree); + + AnnotatedTypeMirror type = memberVisitor.visit(tree, typeFactory); + ifExecutableCheckElement(typeFactory, tree, type); + return type; + } + + /** + * Returns an AnnotatedTypeMirror representing the input type tree. + * + * @param tree must be a type tree + * @return an AnnotatedTypeMirror representing the input type tree + */ + public static AnnotatedTypeMirror fromTypeTree(AnnotatedTypeFactory typeFactory, Tree tree) { + abortIfTreeIsNull(typeFactory, tree); + + AnnotatedTypeMirror type = typeTreeVisitor.visit(tree, typeFactory); + abortIfTypeIsExecutable(typeFactory, tree, type); + return type; + } + + /** + * Returns an AnnotatedDeclaredType representing the input ClassTree. + * + * @return an AnnotatedDeclaredType representing the input ClassTree + */ + public static AnnotatedDeclaredType fromClassTree( + AnnotatedTypeFactory typeFactory, ClassTree tree) { + abortIfTreeIsNull(typeFactory, tree); + + AnnotatedDeclaredType type = (AnnotatedDeclaredType) classVisitor.visit(tree, typeFactory); + abortIfTypeIsExecutable(typeFactory, tree, type); + return type; + } + + protected static void abortIfTreeIsNull(AnnotatedTypeFactory typeFactory, Tree tree) { + if (tree == null) { + throw new BugInCF("Encountered null tree" + summarize(typeFactory, tree)); } - - /** - * Return a string with the two arguments, for diagnostics. - * - * @param typeFactory a type factory - * @param tree a tree - * @return a string with the two arguments - */ - protected static String summarize(AnnotatedTypeFactory typeFactory, Tree tree) { - return String.format( - "tree=%s%ntypeFactory=%s", tree, typeFactory.getClass().getSimpleName()); + } + + protected static void ifExecutableCheckElement( + AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.EXECUTABLE) { + if (((AnnotatedExecutableType) type).getElement() == null) { + throw new BugInCF("Executable has no element:%n%s", summarize(typeFactory, tree, type)); + } } + } - protected static String summarize( - AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { - return "type=" + type + System.lineSeparator() + summarize(typeFactory, tree); + protected static void abortIfTypeIsExecutable( + AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.EXECUTABLE) { + throw new BugInCF("Unexpected Executable typekind:%n%s", summarize(typeFactory, tree, type)); } + } + + /** + * Return a string with the two arguments, for diagnostics. + * + * @param typeFactory a type factory + * @param tree a tree + * @return a string with the two arguments + */ + protected static String summarize(AnnotatedTypeFactory typeFactory, Tree tree) { + return String.format("tree=%s%ntypeFactory=%s", tree, typeFactory.getClass().getSimpleName()); + } + + protected static String summarize( + AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { + return "type=" + type + System.lineSeparator() + summarize(typeFactory, tree); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java index 63bf425ff9c..f11e5b9d7a9 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java @@ -2,7 +2,6 @@ import com.sun.source.tree.Tree; import com.sun.source.util.SimpleTreeVisitor; - import org.checkerframework.javacutil.BugInCF; /** @@ -18,18 +17,18 @@ * @see org.checkerframework.framework.type.TypeFromTree */ abstract class TypeFromTreeVisitor - extends SimpleTreeVisitor { + extends SimpleTreeVisitor { - TypeFromTreeVisitor() {} + TypeFromTreeVisitor() {} - @Override - public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { - if (tree == null) { - throw new BugInCF("TypeFromTree.defaultAction: null tree"); - } - throw new BugInCF( - this.getClass().getCanonicalName() - + ": conversion undefined for tree type " - + tree.getKind()); + @Override + public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { + if (tree == null) { + throw new BugInCF("TypeFromTree.defaultAction: null tree"); } + throw new BugInCF( + this.getClass().getCanonicalName() + + ": conversion undefined for tree type " + + tree.getKind()); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java index 44fa2096a3b..611e74509f1 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java @@ -19,24 +19,11 @@ import com.sun.tools.javac.code.Type.TypeVar; import com.sun.tools.javac.code.Type.WildcardType; import com.sun.tools.javac.tree.JCTree.JCWildcard; - -import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; - import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -44,6 +31,16 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; /** * Converts type trees into AnnotatedTypeMirrors. @@ -52,323 +49,321 @@ */ class TypeFromTypeTreeVisitor extends TypeFromTreeVisitor { - private final Map visitedBounds = new HashMap<>(); - - @Override - public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = visit(tree.getUnderlyingType(), f); - if (type == null) { // e.g., for receiver type - type = f.toAnnotatedType(f.types.getNoType(TypeKind.NONE), false); - } - assert AnnotatedTypeFactory.validAnnotatedType(type); - List annos = TreeUtils.annotationsFromTree(tree); - - if (type.getKind() == TypeKind.WILDCARD) { - // Work-around for https://github.com/eisop/checker-framework/issues/17 - // For an annotated wildcard tree tree, the type attached to the - // tree is a WildcardType with a correct bound (set to the type - // variable which the wildcard instantiates). The underlying type is - // also a WildcardType but with a bound of null. Here we update the - // bound of the underlying WildcardType to be consistent. - WildcardType wildcardAttachedToNode = (WildcardType) TreeUtils.typeOf(tree); - WildcardType underlyingWildcard = (WildcardType) type.getUnderlyingType(); - underlyingWildcard.withTypeVar(wildcardAttachedToNode.bound); - // End of work-around - - AnnotatedWildcardType wctype = ((AnnotatedWildcardType) type); - ExpressionTree underlyingTree = tree.getUnderlyingType(); - - if (underlyingTree.getKind() == Tree.Kind.UNBOUNDED_WILDCARD) { - // primary annotations on unbounded wildcard types apply to both bounds - wctype.getExtendsBound().addAnnotations(annos); - wctype.getSuperBound().addAnnotations(annos); - } else if (underlyingTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { - wctype.getSuperBound().addAnnotations(annos); - } else if (underlyingTree.getKind() == Tree.Kind.SUPER_WILDCARD) { - wctype.getExtendsBound().addAnnotations(annos); - } else { - throw new BugInCF( - "Unexpected kind for type. tree=" - + tree - + " type=" - + type - + " kind=" - + underlyingTree.getKind()); - } - } else { - type.addAnnotations(annos); - } + private final Map visitedBounds = new HashMap<>(); - return type; + @Override + public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = visit(tree.getUnderlyingType(), f); + if (type == null) { // e.g., for receiver type + type = f.toAnnotatedType(f.types.getNoType(TypeKind.NONE), false); } - - @Override - public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror component = visit(tree.getType(), f); - - AnnotatedTypeMirror result = f.type(tree); - assert result instanceof AnnotatedArrayType; - ((AnnotatedArrayType) result).setComponentType(component); - return result; + assert AnnotatedTypeFactory.validAnnotatedType(type); + List annos = TreeUtils.annotationsFromTree(tree); + + if (type.getKind() == TypeKind.WILDCARD) { + // Work-around for https://github.com/eisop/checker-framework/issues/17 + // For an annotated wildcard tree tree, the type attached to the + // tree is a WildcardType with a correct bound (set to the type + // variable which the wildcard instantiates). The underlying type is + // also a WildcardType but with a bound of null. Here we update the + // bound of the underlying WildcardType to be consistent. + WildcardType wildcardAttachedToNode = (WildcardType) TreeUtils.typeOf(tree); + WildcardType underlyingWildcard = (WildcardType) type.getUnderlyingType(); + underlyingWildcard.withTypeVar(wildcardAttachedToNode.bound); + // End of work-around + + AnnotatedWildcardType wctype = ((AnnotatedWildcardType) type); + ExpressionTree underlyingTree = tree.getUnderlyingType(); + + if (underlyingTree.getKind() == Tree.Kind.UNBOUNDED_WILDCARD) { + // primary annotations on unbounded wildcard types apply to both bounds + wctype.getExtendsBound().addAnnotations(annos); + wctype.getSuperBound().addAnnotations(annos); + } else if (underlyingTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { + wctype.getSuperBound().addAnnotations(annos); + } else if (underlyingTree.getKind() == Tree.Kind.SUPER_WILDCARD) { + wctype.getExtendsBound().addAnnotations(annos); + } else { + throw new BugInCF( + "Unexpected kind for type. tree=" + + tree + + " type=" + + type + + " kind=" + + underlyingTree.getKind()); + } + } else { + type.addAnnotations(annos); } - @Override - public AnnotatedTypeMirror visitParameterizedType( - ParameterizedTypeTree tree, AnnotatedTypeFactory f) { - ClassSymbol baseType = (ClassSymbol) TreeUtils.elementFromTree(tree.getType()); - updateWildcardBounds(tree.getTypeArguments(), baseType.getTypeParameters()); - - List args = - CollectionsPlume.mapList((Tree t) -> visit(t, f), tree.getTypeArguments()); - - AnnotatedTypeMirror result = f.type(tree); // use creator? - AnnotatedTypeMirror atype = visit(tree.getType(), f); - result.addAnnotations(atype.getAnnotations()); - // new ArrayList<>() type is AnnotatedExecutableType for some reason - - // Don't initialize the type arguments if they are empty. The type arguments might be a - // diamond which should be inferred. - if (result instanceof AnnotatedDeclaredType && !args.isEmpty()) { - assert result instanceof AnnotatedDeclaredType : tree + " --> " + result; - ((AnnotatedDeclaredType) result).setTypeArguments(args); - } - return result; + return type; + } + + @Override + public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror component = visit(tree.getType(), f); + + AnnotatedTypeMirror result = f.type(tree); + assert result instanceof AnnotatedArrayType; + ((AnnotatedArrayType) result).setComponentType(component); + return result; + } + + @Override + public AnnotatedTypeMirror visitParameterizedType( + ParameterizedTypeTree tree, AnnotatedTypeFactory f) { + ClassSymbol baseType = (ClassSymbol) TreeUtils.elementFromTree(tree.getType()); + updateWildcardBounds(tree.getTypeArguments(), baseType.getTypeParameters()); + + List args = + CollectionsPlume.mapList((Tree t) -> visit(t, f), tree.getTypeArguments()); + + AnnotatedTypeMirror result = f.type(tree); // use creator? + AnnotatedTypeMirror atype = visit(tree.getType(), f); + result.addAnnotations(atype.getAnnotations()); + // new ArrayList<>() type is AnnotatedExecutableType for some reason + + // Don't initialize the type arguments if they are empty. The type arguments might be a + // diamond which should be inferred. + if (result instanceof AnnotatedDeclaredType && !args.isEmpty()) { + assert result instanceof AnnotatedDeclaredType : tree + " --> " + result; + ((AnnotatedDeclaredType) result).setTypeArguments(args); } - - /** - * Work around a bug in javac 9 where sometimes the bound field is set to the transitive - * supertype's type parameter instead of the type parameter which the wildcard directly - * instantiates. See https://github.com/eisop/checker-framework/issues/18 - * - *

Sets each wildcard type argument's bound from typeArgs to the corresponding type parameter - * from typeParams. - * - *

If typeArgs.size() == 0 the method does nothing and returns. Otherwise, typeArgs.size() - * has to be equal to typeParams.size(). - * - *

For each wildcard type argument and corresponding type parameter, sets the - * WildcardType.bound field to the corresponding type parameter, if and only if the owners of - * the existing bound and the type parameter are different. - * - *

In scenarios where the bound's owner is the same, we don't want to replace a - * capture-converted bound in the wildcard type with a non-capture-converted bound given by the - * type parameter declaration. - * - * @param typeArgs the type of the arguments at (e.g., at the call side) - * @param typeParams the type of the formal parameters (e.g., at the method declaration) - */ - @SuppressWarnings("interning:not.interned") // workaround for javac bug - private void updateWildcardBounds( - List typeArgs, List typeParams) { - if (typeArgs.isEmpty()) { - // Nothing to do for empty type arguments. - return; - } - assert typeArgs.size() == typeParams.size(); - - Iterator typeArgsItr = typeArgs.iterator(); - Iterator typeParamsItr = typeParams.iterator(); - while (typeArgsItr.hasNext()) { - Tree typeArg = typeArgsItr.next(); - TypeVariableSymbol typeParam = typeParamsItr.next(); - if (typeArg instanceof WildcardTree) { - TypeVar typeVar = (TypeVar) typeParam.asType(); - WildcardType wcType = (WildcardType) ((JCWildcard) typeArg).type; - if (wcType.bound != null - && wcType.bound.tsym != null - && typeVar.tsym != null - && wcType.bound.tsym.owner != typeVar.tsym.owner) { - wcType.withTypeVar(typeVar); - } - } + return result; + } + + /** + * Work around a bug in javac 9 where sometimes the bound field is set to the transitive + * supertype's type parameter instead of the type parameter which the wildcard directly + * instantiates. See https://github.com/eisop/checker-framework/issues/18 + * + *

Sets each wildcard type argument's bound from typeArgs to the corresponding type parameter + * from typeParams. + * + *

If typeArgs.size() == 0 the method does nothing and returns. Otherwise, typeArgs.size() has + * to be equal to typeParams.size(). + * + *

For each wildcard type argument and corresponding type parameter, sets the + * WildcardType.bound field to the corresponding type parameter, if and only if the owners of the + * existing bound and the type parameter are different. + * + *

In scenarios where the bound's owner is the same, we don't want to replace a + * capture-converted bound in the wildcard type with a non-capture-converted bound given by the + * type parameter declaration. + * + * @param typeArgs the type of the arguments at (e.g., at the call side) + * @param typeParams the type of the formal parameters (e.g., at the method declaration) + */ + @SuppressWarnings("interning:not.interned") // workaround for javac bug + private void updateWildcardBounds( + List typeArgs, List typeParams) { + if (typeArgs.isEmpty()) { + // Nothing to do for empty type arguments. + return; + } + assert typeArgs.size() == typeParams.size(); + + Iterator typeArgsItr = typeArgs.iterator(); + Iterator typeParamsItr = typeParams.iterator(); + while (typeArgsItr.hasNext()) { + Tree typeArg = typeArgsItr.next(); + TypeVariableSymbol typeParam = typeParamsItr.next(); + if (typeArg instanceof WildcardTree) { + TypeVar typeVar = (TypeVar) typeParam.asType(); + WildcardType wcType = (WildcardType) ((JCWildcard) typeArg).type; + if (wcType.bound != null + && wcType.bound.tsym != null + && typeVar.tsym != null + && wcType.bound.tsym.owner != typeVar.tsym.owner) { + wcType.withTypeVar(typeVar); } + } } - - @Override - public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { - return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeVariable visitTypeParameter( + TypeParameterTree tree, @FindDistinct AnnotatedTypeFactory f) { + List bounds = new ArrayList<>(tree.getBounds().size()); + for (Tree t : tree.getBounds()) { + AnnotatedTypeMirror bound; + if (visitedBounds.containsKey(t) && f == visitedBounds.get(t).atypeFactory) { + bound = visitedBounds.get(t); + } else { + visitedBounds.put(t, f.type(t)); + bound = visit(t, f); + visitedBounds.remove(t); + } + bounds.add(bound); } - @Override - public AnnotatedTypeVariable visitTypeParameter( - TypeParameterTree tree, @FindDistinct AnnotatedTypeFactory f) { - List bounds = new ArrayList<>(tree.getBounds().size()); - for (Tree t : tree.getBounds()) { - AnnotatedTypeMirror bound; - if (visitedBounds.containsKey(t) && f == visitedBounds.get(t).atypeFactory) { - bound = visitedBounds.get(t); - } else { - visitedBounds.put(t, f.type(t)); - bound = visit(t, f); - visitedBounds.remove(t); - } - bounds.add(bound); - } - - AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(tree); - List annotations = TreeUtils.annotationsFromTree(tree); - result.getLowerBound().addAnnotations(annotations); - - switch (bounds.size()) { - case 0: - break; - case 1: - result.setUpperBound(bounds.get(0)); - break; - default: - AnnotatedIntersectionType intersection = - (AnnotatedIntersectionType) result.getUpperBound(); - intersection.setBounds(bounds); - intersection.copyIntersectionBoundAnnotations(); - } - - return result; + AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(tree); + List annotations = TreeUtils.annotationsFromTree(tree); + result.getLowerBound().addAnnotations(annotations); + + switch (bounds.size()) { + case 0: + break; + case 1: + result.setUpperBound(bounds.get(0)); + break; + default: + AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) result.getUpperBound(); + intersection.setBounds(bounds); + intersection.copyIntersectionBoundAnnotations(); } - @Override - public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror bound = visit(tree.getBound(), f); - AnnotatedTypeMirror result = f.type(tree); - assert result instanceof AnnotatedWildcardType; + return result; + } - // for wildcards unlike type variables there are bounds that differ in type from - // result. These occur for RAW types. In this case, use the newly created bound - // rather than merging into result - if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { - ((AnnotatedWildcardType) result).setSuperBound(bound); + @Override + public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror bound = visit(tree.getBound(), f); + AnnotatedTypeMirror result = f.type(tree); + assert result instanceof AnnotatedWildcardType; - } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { - ((AnnotatedWildcardType) result).setExtendsBound(bound); - } - return result; - } + // for wildcards unlike type variables there are bounds that differ in type from + // result. These occur for RAW types. In this case, use the newly created bound + // rather than merging into result + if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { + ((AnnotatedWildcardType) result).setSuperBound(bound); - /** - * If a tree is can be found for the declaration of the type variable {@code type}, then a - * {@link AnnotatedTypeVariable} is returned with explicit annotations from the type variables - * declared bounds. If a tree cannot be found, then {@code type}, converted to a use, is - * returned. - * - * @param type type variable used to find declaration tree - * @param f annotated type factory - * @return the AnnotatedTypeVariable from the declaration of {@code type} or {@code type} if no - * tree is found. - */ - private AnnotatedTypeVariable getTypeVariableFromDeclaration( - AnnotatedTypeVariable type, AnnotatedTypeFactory f) { - TypeVariable typeVar = type.getUnderlyingType(); - TypeParameterElement tpe = (TypeParameterElement) typeVar.asElement(); - Element elt = tpe.getGenericElement(); - if (elt instanceof TypeElement) { - TypeElement typeElt = (TypeElement) elt; - int idx = typeElt.getTypeParameters().indexOf(tpe); - if (idx == -1) { - idx = findIndex(typeElt.getTypeParameters(), tpe); - } - ClassTree cls = (ClassTree) f.declarationFromElement(typeElt); - if (cls == null || cls.getTypeParameters().isEmpty()) { - // The type parameters in the source tree were already erased. The element already - // contains all necessary information and we can return that. - return type.asUse(); - } - - // `forTypeVariable` is called for Identifier, MemberSelect and UnionType trees, - // none of which are declarations. But `cls.getTypeParameters()` returns a list - // of type parameter declarations (`TypeParameterTree`), so this call - // will return a declaration ATV. So change it to a use. - return visitTypeParameter(cls.getTypeParameters().get(idx), f).asUse(); - } else if (elt instanceof ExecutableElement) { - ExecutableElement exElt = (ExecutableElement) elt; - int idx = exElt.getTypeParameters().indexOf(tpe); - if (idx == -1) { - idx = findIndex(exElt.getTypeParameters(), tpe); - } - MethodTree meth = (MethodTree) f.declarationFromElement(exElt); - if (meth == null) { - // meth can be null when no source code was found for it. - return type.asUse(); - } - // This works the same as the case above. Even though `meth` itself is not a - // type declaration tree, the elements of `meth.getTypeParameters()` still are. - AnnotatedTypeVariable result = - visitTypeParameter(meth.getTypeParameters().get(idx), f).shallowCopy(); - result.setDeclaration(false); - return result; - } else if (TypesUtils.isCapturedTypeVariable(typeVar)) { - // Captured type variables can have a generic element (owner) that is - // not an element at all, namely Symtab.noSymbol. - return type.asUse(); - } else { - throw new BugInCF("TypeFromTree.forTypeVariable: not a supported element: " + elt); - } + } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { + ((AnnotatedWildcardType) result).setExtendsBound(bound); } - - /** - * Finds the index of {@code type} in {@code typeParameters} using {@link - * TypesUtils#areSame(TypeVariable, TypeVariable)} instead of {@link Object#equals(Object)}. - * - * @param typeParameters a list of type parameters - * @param type a type parameter - * @return the index of {@code type} in {@code typeParameters} using {@link - * TypesUtils#areSame(TypeVariable, TypeVariable)} or -1 if it does not exist - */ - private int findIndex( - List typeParameters, TypeParameterElement type) { - TypeVariable typeVariable = (TypeVariable) type.asType(); - - for (int i = 0; i < typeParameters.size(); i++) { - TypeVariable typeVariable1 = (TypeVariable) typeParameters.get(i).asType(); - if (TypesUtils.areSame(typeVariable1, typeVariable)) { - return i; - } - } - return -1; + return result; + } + + /** + * If a tree is can be found for the declaration of the type variable {@code type}, then a {@link + * AnnotatedTypeVariable} is returned with explicit annotations from the type variables declared + * bounds. If a tree cannot be found, then {@code type}, converted to a use, is returned. + * + * @param type type variable used to find declaration tree + * @param f annotated type factory + * @return the AnnotatedTypeVariable from the declaration of {@code type} or {@code type} if no + * tree is found. + */ + private AnnotatedTypeVariable getTypeVariableFromDeclaration( + AnnotatedTypeVariable type, AnnotatedTypeFactory f) { + TypeVariable typeVar = type.getUnderlyingType(); + TypeParameterElement tpe = (TypeParameterElement) typeVar.asElement(); + Element elt = tpe.getGenericElement(); + if (elt instanceof TypeElement) { + TypeElement typeElt = (TypeElement) elt; + int idx = typeElt.getTypeParameters().indexOf(tpe); + if (idx == -1) { + idx = findIndex(typeElt.getTypeParameters(), tpe); + } + ClassTree cls = (ClassTree) f.declarationFromElement(typeElt); + if (cls == null || cls.getTypeParameters().isEmpty()) { + // The type parameters in the source tree were already erased. The element already + // contains all necessary information and we can return that. + return type.asUse(); + } + + // `forTypeVariable` is called for Identifier, MemberSelect and UnionType trees, + // none of which are declarations. But `cls.getTypeParameters()` returns a list + // of type parameter declarations (`TypeParameterTree`), so this call + // will return a declaration ATV. So change it to a use. + return visitTypeParameter(cls.getTypeParameters().get(idx), f).asUse(); + } else if (elt instanceof ExecutableElement) { + ExecutableElement exElt = (ExecutableElement) elt; + int idx = exElt.getTypeParameters().indexOf(tpe); + if (idx == -1) { + idx = findIndex(exElt.getTypeParameters(), tpe); + } + MethodTree meth = (MethodTree) f.declarationFromElement(exElt); + if (meth == null) { + // meth can be null when no source code was found for it. + return type.asUse(); + } + // This works the same as the case above. Even though `meth` itself is not a + // type declaration tree, the elements of `meth.getTypeParameters()` still are. + AnnotatedTypeVariable result = + visitTypeParameter(meth.getTypeParameters().get(idx), f).shallowCopy(); + result.setDeclaration(false); + return result; + } else if (TypesUtils.isCapturedTypeVariable(typeVar)) { + // Captured type variables can have a generic element (owner) that is + // not an element at all, namely Symtab.noSymbol. + return type.asUse(); + } else { + throw new BugInCF("TypeFromTree.forTypeVariable: not a supported element: " + elt); } + } + + /** + * Finds the index of {@code type} in {@code typeParameters} using {@link + * TypesUtils#areSame(TypeVariable, TypeVariable)} instead of {@link Object#equals(Object)}. + * + * @param typeParameters a list of type parameters + * @param type a type parameter + * @return the index of {@code type} in {@code typeParameters} using {@link + * TypesUtils#areSame(TypeVariable, TypeVariable)} or -1 if it does not exist + */ + private int findIndex( + List typeParameters, TypeParameterElement type) { + TypeVariable typeVariable = (TypeVariable) type.asType(); + + for (int i = 0; i < typeParameters.size(); i++) { + TypeVariable typeVariable1 = (TypeVariable) typeParameters.get(i).asType(); + if (TypesUtils.areSame(typeVariable1, typeVariable)) { + return i; + } + } + return -1; + } - @Override - public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.type(tree); - - if (type.getKind() == TypeKind.TYPEVAR) { - return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); - } + @Override + public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.type(tree); - return type; + if (type.getKind() == TypeKind.TYPEVAR) { + return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); } - @Override - public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.type(tree); + return type; + } - if (type.getKind() == TypeKind.TYPEVAR) { - return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); - } + @Override + public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.type(tree); - return type; + if (type.getKind() == TypeKind.TYPEVAR) { + return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); } - @Override - public AnnotatedTypeMirror visitUnionType(UnionTypeTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.type(tree); + return type; + } - if (type.getKind() == TypeKind.TYPEVAR) { - return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); - } + @Override + public AnnotatedTypeMirror visitUnionType(UnionTypeTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.type(tree); - return type; + if (type.getKind() == TypeKind.TYPEVAR) { + return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); } - @Override - public AnnotatedTypeMirror visitIntersectionType( - IntersectionTypeTree tree, AnnotatedTypeFactory f) { - // This method is only called for IntersectionTypes in casts. There is no - // IntersectionTypeTree - // for a type variable bound that is an intersection. See #visitTypeParameter. - AnnotatedIntersectionType type = (AnnotatedIntersectionType) f.type(tree); - List bounds = - CollectionsPlume.mapList((Tree boundTree) -> visit(boundTree, f), tree.getBounds()); - type.setBounds(bounds); - type.copyIntersectionBoundAnnotations(); - return type; - } + return type; + } + + @Override + public AnnotatedTypeMirror visitIntersectionType( + IntersectionTypeTree tree, AnnotatedTypeFactory f) { + // This method is only called for IntersectionTypes in casts. There is no + // IntersectionTypeTree + // for a type variable bound that is an intersection. See #visitTypeParameter. + AnnotatedIntersectionType type = (AnnotatedIntersectionType) f.type(tree); + List bounds = + CollectionsPlume.mapList((Tree boundTree) -> visit(boundTree, f), tree.getBounds()); + type.setBounds(bounds); + type.copyIntersectionBoundAnnotations(); + return type; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java index 152981fd5d8..9c07741e0d4 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java @@ -1,166 +1,164 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.util.AnnotatedTypes; - import java.util.Collection; - import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.util.AnnotatedTypes; /** Compares AnnotatedTypeMirrors for subtype relationships. See also {@link QualifierHierarchy}. */ public interface TypeHierarchy { - // This can be used if: - // * the type is fully annotated, - // * the basetypes are Java subtypes, and - // * you want to check the full type - // Otherwise, call QualifierHierarchy. + // This can be used if: + // * the type is fully annotated, + // * the basetypes are Java subtypes, and + // * you want to check the full type + // Otherwise, call QualifierHierarchy. - // `TypeHierarchy` is an interface because the only implementation, DefaultTypeHierarchy, has - // public visitor methods that clients should never call. + // `TypeHierarchy` is an interface because the only implementation, DefaultTypeHierarchy, has + // public visitor methods that clients should never call. - /** - * Returns true if {@code subtype} is a subtype of or convertible to {@code supertype} for all - * hierarchies present. If the underlying Java type of {@code subtype} is not a subtype of or - * convertible to the underlying Java type of {@code supertype}, then the behavior of this - * method is undefined. - * - *

Ideally, types that require conversions would be converted before isSubtype is called, but - * instead, isSubtype performs some of these conversions. - * - *

JLS 5.1 specifies 13 categories of conversions. - * - *

3 categories are converted in isSubtype: - * - *

    - *
  • Boxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getBoxedType} - *
  • Unboxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getUnboxedType} - *
  • String conversions: Any type to String. isSubtype calls {@link AnnotatedTypes#asSuper} - * which calls {@link AnnotatedTypeFactory#getStringType(AnnotatedTypeMirror)} - *
- * - * 2 happen elsewhere: - * - *
    - *
  • Unchecked conversions: Generic type to raw type. Raw types are instantiated with bounds - * in AnnotatedTypeFactory#fromTypeTree before is subtype is called - *
  • Capture conversions: Wildcards are captured in {@link - * AnnotatedTypeFactory#applyCaptureConversion(AnnotatedTypeMirror)} - *
- * - * 7 are not explicitly converted and are treated as though the types are actually subtypes. - * - *
    - *
  • Identity conversions: type to same type - *
  • Widening primitive conversions: primitive to primitive (no loss of information, byte to - * short for example) - *
  • Narrowing primitive conversions: primitive to primitive (possibly loss of information, - * short to byte for example) - *
  • Widening and Narrowing Primitive Conversion: byte to char - *
  • Widening reference conversions: Upcast - *
  • Narrowing reference conversions: Downcast - *
  • Value set conversions: floating-point value from one value set to another without - * changing its type. - *
- * - * @param subtype possible subtype - * @param supertype possible supertype - * @return true if {@code subtype} is a subtype of {@code supertype} for all hierarchies present - */ - boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); + /** + * Returns true if {@code subtype} is a subtype of or convertible to {@code supertype} for all + * hierarchies present. If the underlying Java type of {@code subtype} is not a subtype of or + * convertible to the underlying Java type of {@code supertype}, then the behavior of this method + * is undefined. + * + *

Ideally, types that require conversions would be converted before isSubtype is called, but + * instead, isSubtype performs some of these conversions. + * + *

JLS 5.1 specifies 13 categories of conversions. + * + *

3 categories are converted in isSubtype: + * + *

    + *
  • Boxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getBoxedType} + *
  • Unboxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getUnboxedType} + *
  • String conversions: Any type to String. isSubtype calls {@link AnnotatedTypes#asSuper} + * which calls {@link AnnotatedTypeFactory#getStringType(AnnotatedTypeMirror)} + *
+ * + * 2 happen elsewhere: + * + *
    + *
  • Unchecked conversions: Generic type to raw type. Raw types are instantiated with bounds + * in AnnotatedTypeFactory#fromTypeTree before is subtype is called + *
  • Capture conversions: Wildcards are captured in {@link + * AnnotatedTypeFactory#applyCaptureConversion(AnnotatedTypeMirror)} + *
+ * + * 7 are not explicitly converted and are treated as though the types are actually subtypes. + * + *
    + *
  • Identity conversions: type to same type + *
  • Widening primitive conversions: primitive to primitive (no loss of information, byte to + * short for example) + *
  • Narrowing primitive conversions: primitive to primitive (possibly loss of information, + * short to byte for example) + *
  • Widening and Narrowing Primitive Conversion: byte to char + *
  • Widening reference conversions: Upcast + *
  • Narrowing reference conversions: Downcast + *
  • Value set conversions: floating-point value from one value set to another without + * changing its type. + *
+ * + * @param subtype possible subtype + * @param supertype possible supertype + * @return true if {@code subtype} is a subtype of {@code supertype} for all hierarchies present + */ + boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - /** - * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers - * of the effective annotations of {@code supertype}, according to the type qualifier hierarchy. - * - *

The underlying types of {@code subtype} and {@code supertype} are not necessarily in a - * Java subtyping relationship with one another and are only used by this method for special - * cases when qualifier subtyping depends on the Java basetype. - * - * @param subtype possible subtype - * @param supertype possible supertype - * @return true iff the effective annotations of {@code subtype} are equal to or are - * sub-qualifiers of the effective annotations of {@code supertype} - */ - boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); + /** + * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers + * of the effective annotations of {@code supertype}, according to the type qualifier hierarchy. + * + *

The underlying types of {@code subtype} and {@code supertype} are not necessarily in a Java + * subtyping relationship with one another and are only used by this method for special cases when + * qualifier subtyping depends on the Java basetype. + * + * @param subtype possible subtype + * @param supertype possible supertype + * @return true iff the effective annotations of {@code subtype} are equal to or are + * sub-qualifiers of the effective annotations of {@code supertype} + */ + boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - /** - * Tests whether the effective annotation in the same hierarchy as {@code hierarchy} of {@code - * subtype} are equal to or are sub-qualifiers of the effective annotation of {@code supertype} - * in the same hierarchy as {@code hierarchy}, according to the type qualifier hierarchy. Other - * annotations in {@code subtype} and {@code supertype} are ignored. - * - *

The underlying types of {@code subtype} and {@code supertype} are not necessarily in a - * Java subtyping relationship with one another and are only used by this method for special - * cases when qualifier subtyping depends on the Java basetype. - * - * @param subtype possible subtype - * @param supertype possible supertype - * @param hierarchy an annotation whose hierarchy is used to compare {@code subtype} and {@code - * supertype} - * @return true iff the effective annotation in the same hierarchy as {@code hierarchy} of - * {@code subtype} are equal to or are sub-qualifiers of the effective annotation of {@code - * supertype} in the same hierarchy as {@code hierarchy} - */ - boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror hierarchy); + /** + * Tests whether the effective annotation in the same hierarchy as {@code hierarchy} of {@code + * subtype} are equal to or are sub-qualifiers of the effective annotation of {@code supertype} in + * the same hierarchy as {@code hierarchy}, according to the type qualifier hierarchy. Other + * annotations in {@code subtype} and {@code supertype} are ignored. + * + *

The underlying types of {@code subtype} and {@code supertype} are not necessarily in a Java + * subtyping relationship with one another and are only used by this method for special cases when + * qualifier subtyping depends on the Java basetype. + * + * @param subtype possible subtype + * @param supertype possible supertype + * @param hierarchy an annotation whose hierarchy is used to compare {@code subtype} and {@code + * supertype} + * @return true iff the effective annotation in the same hierarchy as {@code hierarchy} of {@code + * subtype} are equal to or are sub-qualifiers of the effective annotation of {@code + * supertype} in the same hierarchy as {@code hierarchy} + */ + boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror hierarchy); - /** - * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers - * of {@code superQualifiers}, according to the type qualifier hierarchy. Other annotations in - * {@code subtype} are ignored. - * - *

The underlying type of {@code subtype} is only used by this method for special cases when - * qualifier subtyping depends on the Java basetype. - * - * @param subtype possible subtype - * @param superQualifiers possible superQualifiers - * @return true iff the effective annotations of {@code subtype} are equal to or are - * sub-qualifiers of {@code superQualifiers} - */ - boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, Collection superQualifiers); + /** + * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers + * of {@code superQualifiers}, according to the type qualifier hierarchy. Other annotations in + * {@code subtype} are ignored. + * + *

The underlying type of {@code subtype} is only used by this method for special cases when + * qualifier subtyping depends on the Java basetype. + * + * @param subtype possible subtype + * @param superQualifiers possible superQualifiers + * @return true iff the effective annotations of {@code subtype} are equal to or are + * sub-qualifiers of {@code superQualifiers} + */ + boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, Collection superQualifiers); - /** - * Tests whether {@code subQualifiers} are equal to or are sub-qualifiers of the effective - * annotations of {@code supertype}, according to the type qualifier hierarchy. Other - * annotations in {@code supertype} are ignored. - * - *

The underlying type of {@code supertype} is used by this method for special cases when - * qualifier subtyping depends on the Java basetype. - * - * @param subQualifiers possible subQualifiers - * @param supertype possible supertype - * @return true iff {@code subQualifiers} are equal to or are sub-qualifiers of the effective - * annotations of {@code supertype} - */ - boolean isSubtypeShallowEffective( - Collection subQualifiers, AnnotatedTypeMirror supertype); + /** + * Tests whether {@code subQualifiers} are equal to or are sub-qualifiers of the effective + * annotations of {@code supertype}, according to the type qualifier hierarchy. Other annotations + * in {@code supertype} are ignored. + * + *

The underlying type of {@code supertype} is used by this method for special cases when + * qualifier subtyping depends on the Java basetype. + * + * @param subQualifiers possible subQualifiers + * @param supertype possible supertype + * @return true iff {@code subQualifiers} are equal to or are sub-qualifiers of the effective + * annotations of {@code supertype} + */ + boolean isSubtypeShallowEffective( + Collection subQualifiers, AnnotatedTypeMirror supertype); - /** - * Tests whether the effective annotation of {@code subtype} in the same hierarchy as {@code - * superQualifier} is equal to or sub-qualifier of {@code superQualifier}, according to the type - * qualifier hierarchy. The underlying types of {@code subtype} is only used by this method for - * special cases when qualifier subtyping depends on the Java basetype. Other annotations in - * {@code subtype} are ignored. - * - * @param subtype possible subtype - * @param superQualifier possible super qualifier - * @return true iffhe effective annotation of {@code subtype} in the same hierarchy as {@code - * superQualifier} is equal to or sub-qualifier of {@code superQualifier} - */ - boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotationMirror superQualifier); + /** + * Tests whether the effective annotation of {@code subtype} in the same hierarchy as {@code + * superQualifier} is equal to or sub-qualifier of {@code superQualifier}, according to the type + * qualifier hierarchy. The underlying types of {@code subtype} is only used by this method for + * special cases when qualifier subtyping depends on the Java basetype. Other annotations in + * {@code subtype} are ignored. + * + * @param subtype possible subtype + * @param superQualifier possible super qualifier + * @return true iffhe effective annotation of {@code subtype} in the same hierarchy as {@code + * superQualifier} is equal to or sub-qualifier of {@code superQualifier} + */ + boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotationMirror superQualifier); - /** - * Tests whether {@code subQualifier} is equal to or sub-qualifier of the effective annotation - * of {@code supertype} in the same hierarchy as {@code subQualifier} according to the type - * qualifier hierarchy. The underlying types of {@code supertype} is only used by this method - * for special cases when qualifier subtyping depends on the Java basetype. Other annotations in - * {@code supertype} are ignored. - * - * @param subQualifier possible subQualifier - * @param supertype possible supertype - * @return true {@code subQualifier} is equal to or sub-qualifier of the effective annotation of - * {@code supertype} in the same hierarchy as {@code subQualifier} - */ - boolean isSubtypeShallowEffective(AnnotationMirror subQualifier, AnnotatedTypeMirror supertype); + /** + * Tests whether {@code subQualifier} is equal to or sub-qualifier of the effective annotation of + * {@code supertype} in the same hierarchy as {@code subQualifier} according to the type qualifier + * hierarchy. The underlying types of {@code supertype} is only used by this method for special + * cases when qualifier subtyping depends on the Java basetype. Other annotations in {@code + * supertype} are ignored. + * + * @param subQualifier possible subQualifier + * @param supertype possible supertype + * @return true {@code subQualifier} is equal to or sub-qualifier of the effective annotation of + * {@code supertype} in the same hierarchy as {@code subQualifier} + */ + boolean isSubtypeShallowEffective(AnnotationMirror subQualifier, AnnotatedTypeMirror supertype); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java index 6ca013fb464..a1fb12986ef 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java @@ -1,178 +1,169 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.javacutil.TypesUtils; - import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; - import javax.lang.model.element.Element; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.javacutil.TypesUtils; /** TypeVariableSubstitutor replaces type variables from a declaration with arguments to its use. */ public class TypeVariableSubstitutor { + /** + * Given a mapping from type variable to its type argument, replace each instance of a type + * variable with a copy of type argument. + * + * @see #substituteTypeVariable(AnnotatedTypeMirror, + * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) + * @param typeVarToTypeArgument a mapping from type variable to its type argument + * @param type the type to substitute + * @return a copy of type with its type variables substituted + */ + public AnnotatedTypeMirror substitute( + Map typeVarToTypeArgument, AnnotatedTypeMirror type) { + return new Visitor(typeVarToTypeArgument, true).visit(type); + } + + /** + * Given a mapping from type variable to its type argument, replace each instance of a type + * variable with the given type argument. + * + * @see #substituteTypeVariable(AnnotatedTypeMirror, + * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) + * @param typeVarToTypeArgument a mapping from type variable to its type argument + * @param type the type to substitute + * @return a copy of type with its type variables substituted + */ + public AnnotatedTypeMirror substituteWithoutCopyingTypeArguments( + Map typeVarToTypeArgument, AnnotatedTypeMirror type) { + return new Visitor(typeVarToTypeArgument, false).visit(type); + } + + /** + * Given the types of a type parameter declaration, the argument to that type parameter + * declaration, and a given use of that declaration, return a substitute for the use with the + * correct annotations. + * + *

To determine what primary annotations are correct for the substitute the following rules are + * used: If the type variable use has a primary annotation then apply that primary annotation to + * the substitute. Otherwise, use the annotations of the argument. + * + * @param argument the argument to declaration (this will be a value in typeParamToArg) + * @param use the use that is being replaced + * @return a deep copy of argument with the appropriate annotations applied + */ + protected AnnotatedTypeMirror substituteTypeVariable( + AnnotatedTypeMirror argument, AnnotatedTypeVariable use) { + AnnotatedTypeMirror substitute = argument.deepCopy(true); + if (!use.getAnnotationsField().isEmpty()) { + substitute.replaceAnnotations(use.getAnnotations()); + } + return substitute; + } + + /** + * Visitor that makes the substitution. This is an inner class so that its methods cannot be + * called by clients of {@link TypeVariableSubstitutor}. + */ + protected class Visitor extends AnnotatedTypeCopier { + /** - * Given a mapping from type variable to its type argument, replace each instance of a type - * variable with a copy of type argument. - * - * @see #substituteTypeVariable(AnnotatedTypeMirror, - * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) - * @param typeVarToTypeArgument a mapping from type variable to its type argument - * @param type the type to substitute - * @return a copy of type with its type variables substituted + * A mapping from {@link TypeParameterElement} to the {@link AnnotatedTypeMirror} that should + * replace its uses. */ - public AnnotatedTypeMirror substitute( - Map typeVarToTypeArgument, - AnnotatedTypeMirror type) { - return new Visitor(typeVarToTypeArgument, true).visit(type); - } + private final Map elementToArgMap; /** - * Given a mapping from type variable to its type argument, replace each instance of a type - * variable with the given type argument. - * - * @see #substituteTypeVariable(AnnotatedTypeMirror, - * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) - * @param typeVarToTypeArgument a mapping from type variable to its type argument - * @param type the type to substitute - * @return a copy of type with its type variables substituted + * A list of type variables that should be replaced by the type mirror at the same index in + * {@code typeMirrors} */ - public AnnotatedTypeMirror substituteWithoutCopyingTypeArguments( - Map typeVarToTypeArgument, - AnnotatedTypeMirror type) { - return new Visitor(typeVarToTypeArgument, false).visit(type); - } + private final List typeVars; /** - * Given the types of a type parameter declaration, the argument to that type parameter - * declaration, and a given use of that declaration, return a substitute for the use with the - * correct annotations. - * - *

To determine what primary annotations are correct for the substitute the following rules - * are used: If the type variable use has a primary annotation then apply that primary - * annotation to the substitute. Otherwise, use the annotations of the argument. - * - * @param argument the argument to declaration (this will be a value in typeParamToArg) - * @param use the use that is being replaced - * @return a deep copy of argument with the appropriate annotations applied + * A list of TypeMirrors that should replace the type variable at the same index in {@code + * typeVars} */ - protected AnnotatedTypeMirror substituteTypeVariable( - AnnotatedTypeMirror argument, AnnotatedTypeVariable use) { - AnnotatedTypeMirror substitute = argument.deepCopy(true); - if (!use.getAnnotationsField().isEmpty()) { - substitute.replaceAnnotations(use.getAnnotations()); - } - return substitute; - } + private final List typeMirrors; + + /** Whether or not a copy of type argument should be substituted. */ + private final boolean copyArgument; /** - * Visitor that makes the substitution. This is an inner class so that its methods cannot be - * called by clients of {@link TypeVariableSubstitutor}. + * Creates the Visitor. + * + * @param typeParamToArg mapping from TypeVariable to the AnnotatedTypeMirror that will replace + * it + * @param copyArgument whether or not a copy of type argument should be substituted */ - protected class Visitor extends AnnotatedTypeCopier { - - /** - * A mapping from {@link TypeParameterElement} to the {@link AnnotatedTypeMirror} that - * should replace its uses. - */ - private final Map elementToArgMap; - - /** - * A list of type variables that should be replaced by the type mirror at the same index in - * {@code typeMirrors} - */ - private final List typeVars; - - /** - * A list of TypeMirrors that should replace the type variable at the same index in {@code - * typeVars} - */ - private final List typeMirrors; - - /** Whether or not a copy of type argument should be substituted. */ - private final boolean copyArgument; - - /** - * Creates the Visitor. - * - * @param typeParamToArg mapping from TypeVariable to the AnnotatedTypeMirror that will - * replace it - * @param copyArgument whether or not a copy of type argument should be substituted - */ - public Visitor( - Map typeParamToArg, boolean copyArgument) { - int size = typeParamToArg.size(); - elementToArgMap = new HashMap<>(size); - typeVars = new ArrayList<>(size); - typeMirrors = new ArrayList<>(size); - - for (Map.Entry paramToArg : - typeParamToArg.entrySet()) { - elementToArgMap.put( - (TypeParameterElement) paramToArg.getKey().asElement(), - paramToArg.getValue()); - typeVars.add(paramToArg.getKey()); - typeMirrors.add(paramToArg.getValue().getUnderlyingType()); - } - this.copyArgument = copyArgument; - } + public Visitor(Map typeParamToArg, boolean copyArgument) { + int size = typeParamToArg.size(); + elementToArgMap = new HashMap<>(size); + typeVars = new ArrayList<>(size); + typeMirrors = new ArrayList<>(size); + + for (Map.Entry paramToArg : typeParamToArg.entrySet()) { + elementToArgMap.put( + (TypeParameterElement) paramToArg.getKey().asElement(), paramToArg.getValue()); + typeVars.add(paramToArg.getKey()); + typeMirrors.add(paramToArg.getValue().getUnderlyingType()); + } + this.copyArgument = copyArgument; + } - @Override - protected T makeCopy(T original) { - if (original.getKind() == TypeKind.TYPEVAR) { - return super.makeCopy(original); - } - TypeMirror s = - TypesUtils.substitute( - original.getUnderlyingType(), - typeVars, - typeMirrors, - original.atypeFactory.processingEnv); - - @SuppressWarnings("unchecked") - T copy = - (T) - AnnotatedTypeMirror.createType( - s, original.atypeFactory, original.isDeclaration()); - maybeCopyPrimaryAnnotations(original, copy); - - return copy; - } + @Override + protected T makeCopy(T original) { + if (original.getKind() == TypeKind.TYPEVAR) { + return super.makeCopy(original); + } + TypeMirror s = + TypesUtils.substitute( + original.getUnderlyingType(), + typeVars, + typeMirrors, + original.atypeFactory.processingEnv); + + @SuppressWarnings("unchecked") + T copy = + (T) AnnotatedTypeMirror.createType(s, original.atypeFactory, original.isDeclaration()); + maybeCopyPrimaryAnnotations(original, copy); + + return copy; + } - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - if (visitingExecutableTypeParam) { - // AnnotatedExecutableType differs from AnnotatedDeclaredType in that its list of - // type parameters cannot be adapted in place since the - // AnnotatedExecutable.typeVarTypes field is of type AnnotatedTypeVariable and not - // AnnotatedTypeMirror. When substituting, all component types that contain a use - // of the executable's type parameters will be substituted. The executable's type - // parameters will have their bounds substituted but the top-level - // AnnotatedTypeVariable's will remain - visitingExecutableTypeParam = false; - return super.visitTypeVariable(original, originalToCopy); - } else { - Element typeVarElem = original.getUnderlyingType().asElement(); - if (elementToArgMap.containsKey(typeVarElem)) { - AnnotatedTypeMirror argument = elementToArgMap.get(typeVarElem); - if (copyArgument) { - return substituteTypeVariable(argument, original); - } else { - return argument; - } - } - } - - return super.visitTypeVariable(original, originalToCopy); + @Override + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + if (visitingExecutableTypeParam) { + // AnnotatedExecutableType differs from AnnotatedDeclaredType in that its list of + // type parameters cannot be adapted in place since the + // AnnotatedExecutable.typeVarTypes field is of type AnnotatedTypeVariable and not + // AnnotatedTypeMirror. When substituting, all component types that contain a use + // of the executable's type parameters will be substituted. The executable's type + // parameters will have their bounds substituted but the top-level + // AnnotatedTypeVariable's will remain + visitingExecutableTypeParam = false; + return super.visitTypeVariable(original, originalToCopy); + } else { + Element typeVarElem = original.getUnderlyingType().asElement(); + if (elementToArgMap.containsKey(typeVarElem)) { + AnnotatedTypeMirror argument = elementToArgMap.get(typeVarElem); + if (copyArgument) { + return substituteTypeVariable(argument, original); + } else { + return argument; + } } + } + + return super.visitTypeVariable(original, originalToCopy); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java b/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java index ad96366af5e..fd15c5cf9f5 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java @@ -17,7 +17,10 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; - +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -31,11 +34,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeAnnotationUtils; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Types; - /** * A helper class that puts the annotations from an AnnotatedTypeMirrors back into the corresponding * Elements, so that they get stored in the bytecode by the compiler. @@ -46,481 +44,459 @@ */ public final class TypesIntoElements { - /** Do not instantiate. */ - private TypesIntoElements() { - throw new AssertionError("Class TypesIntoElements cannot be instantiated."); + /** Do not instantiate. */ + private TypesIntoElements() { + throw new AssertionError("Class TypesIntoElements cannot be instantiated."); + } + + /** + * The entry point. + * + * @param processingEnv the environment + * @param atypeFactory the type factory + * @param tree the ClassTree to process + */ + public static void store( + ProcessingEnvironment processingEnv, AnnotatedTypeFactory atypeFactory, ClassTree tree) { + Symbol.ClassSymbol csym = (Symbol.ClassSymbol) TreeUtils.elementFromDeclaration(tree); + Types types = processingEnv.getTypeUtils(); + + storeTypeParameters(processingEnv, types, atypeFactory, tree.getTypeParameters(), csym); + + /* TODO: storing extends/implements types results in + * a strange error e.g. from the Nullness Checker. + * I think somewhere we take the annotations on extends/implements as + * the receiver annotation on a constructor, breaking logic there. + * I assume that the problem is the default that we use for these locations. + * Once we've decided the defaulting, enable this. + * See example of code that fails when this is enabled in + * checker/jtreg/nullness/annotationsOnExtends. Also, see + * https://github.com/typetools/checker-framework/pull/876 for + * a better implementation (though it also causes the error). + storeClassExtends(processingEnv, types, atypeFactory, tree.getExtendsClause(), csym, -1); + { + int implidx = 0; + for (Tree imp : tree.getImplementsClause()) { + storeClassExtends(processingEnv, types, atypeFactory, imp, csym, implidx); + ++implidx; + } + } + */ + + for (Tree mem : tree.getMembers()) { + if (mem.getKind() == Tree.Kind.METHOD) { + storeMethod(processingEnv, types, atypeFactory, (MethodTree) mem); + } else if (mem.getKind() == Tree.Kind.VARIABLE) { + storeVariable(processingEnv, types, atypeFactory, (VariableTree) mem); + } else { + // System.out.println("Unhandled member tree: " + mem); + } + } + } + + private static void storeMethod( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + MethodTree meth) { + AnnotatedExecutableType mtype = atypeFactory.getAnnotatedType(meth); + MethodSymbol sym = (MethodSymbol) TreeUtils.elementFromDeclaration(meth); + TypeAnnotationPosition tapos; + List tcs = List.nil(); + + storeTypeParameters(processingEnv, types, atypeFactory, meth.getTypeParameters(), sym); + + { + // return type + JCTree ret = ((JCTree.JCMethodDecl) meth).getReturnType(); + if (ret != null) { + tapos = TypeAnnotationUtils.methodReturnTAPosition(ret.pos); + tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReturnType(), tapos)); + } + } + { + // receiver + JCTree receiverTree = ((JCTree.JCMethodDecl) meth).getReceiverParameter(); + if (receiverTree != null) { + tapos = TypeAnnotationUtils.methodReceiverTAPosition(receiverTree.pos); + tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReceiverType(), tapos)); + } + } + { + // parameters + int pidx = 0; + java.util.List ptypes = mtype.getParameterTypes(); + for (JCTree param : ((JCTree.JCMethodDecl) meth).getParameters()) { + tapos = TypeAnnotationUtils.methodParameterTAPosition(pidx, param.pos); + tcs = tcs.appendList(generateTypeCompounds(processingEnv, ptypes.get(pidx), tapos)); + ++pidx; + } + } + { + // throws clauses + int tidx = 0; + java.util.List ttypes = mtype.getThrownTypes(); + for (JCTree thr : ((JCTree.JCMethodDecl) meth).getThrows()) { + tapos = TypeAnnotationUtils.methodThrowsTAPosition(tidx, thr.pos); + tcs = tcs.appendList(generateTypeCompounds(processingEnv, ttypes.get(tidx), tapos)); + ++tidx; + } } - /** - * The entry point. - * - * @param processingEnv the environment - * @param atypeFactory the type factory - * @param tree the ClassTree to process - */ - public static void store( - ProcessingEnvironment processingEnv, - AnnotatedTypeFactory atypeFactory, - ClassTree tree) { - Symbol.ClassSymbol csym = (Symbol.ClassSymbol) TreeUtils.elementFromDeclaration(tree); - Types types = processingEnv.getTypeUtils(); - - storeTypeParameters(processingEnv, types, atypeFactory, tree.getTypeParameters(), csym); - - /* TODO: storing extends/implements types results in - * a strange error e.g. from the Nullness Checker. - * I think somewhere we take the annotations on extends/implements as - * the receiver annotation on a constructor, breaking logic there. - * I assume that the problem is the default that we use for these locations. - * Once we've decided the defaulting, enable this. - * See example of code that fails when this is enabled in - * checker/jtreg/nullness/annotationsOnExtends. Also, see - * https://github.com/typetools/checker-framework/pull/876 for - * a better implementation (though it also causes the error). - storeClassExtends(processingEnv, types, atypeFactory, tree.getExtendsClause(), csym, -1); - { - int implidx = 0; - for (Tree imp : tree.getImplementsClause()) { - storeClassExtends(processingEnv, types, atypeFactory, imp, csym, implidx); - ++implidx; - } - } - */ - - for (Tree mem : tree.getMembers()) { - if (mem.getKind() == Tree.Kind.METHOD) { - storeMethod(processingEnv, types, atypeFactory, (MethodTree) mem); - } else if (mem.getKind() == Tree.Kind.VARIABLE) { - storeVariable(processingEnv, types, atypeFactory, (VariableTree) mem); - } else { - // System.out.println("Unhandled member tree: " + mem); - } - } + addUniqueTypeCompounds(types, sym, tcs); + } + + private static void storeVariable( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + VariableTree var) { + VarSymbol sym = (VarSymbol) TreeUtils.elementFromDeclaration(var); + AnnotatedTypeMirror type; + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + // TODO: this is rather ugly: we do not want refinement from the + // initializer of the field. We need a general way to get + // the "defaulted" type of a variable. + type = ((GenericAnnotatedTypeFactory) atypeFactory).getAnnotatedTypeLhs(var); + } else { + type = atypeFactory.getAnnotatedType(var); } - private static void storeMethod( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - MethodTree meth) { - AnnotatedExecutableType mtype = atypeFactory.getAnnotatedType(meth); - MethodSymbol sym = (MethodSymbol) TreeUtils.elementFromDeclaration(meth); - TypeAnnotationPosition tapos; - List tcs = List.nil(); - - storeTypeParameters(processingEnv, types, atypeFactory, meth.getTypeParameters(), sym); - - { - // return type - JCTree ret = ((JCTree.JCMethodDecl) meth).getReturnType(); - if (ret != null) { - tapos = TypeAnnotationUtils.methodReturnTAPosition(ret.pos); - tcs = - tcs.appendList( - generateTypeCompounds(processingEnv, mtype.getReturnType(), tapos)); - } - } - { - // receiver - JCTree receiverTree = ((JCTree.JCMethodDecl) meth).getReceiverParameter(); - if (receiverTree != null) { - tapos = TypeAnnotationUtils.methodReceiverTAPosition(receiverTree.pos); - tcs = - tcs.appendList( - generateTypeCompounds( - processingEnv, mtype.getReceiverType(), tapos)); - } - } - { - // parameters - int pidx = 0; - java.util.List ptypes = mtype.getParameterTypes(); - for (JCTree param : ((JCTree.JCMethodDecl) meth).getParameters()) { - tapos = TypeAnnotationUtils.methodParameterTAPosition(pidx, param.pos); - tcs = tcs.appendList(generateTypeCompounds(processingEnv, ptypes.get(pidx), tapos)); - ++pidx; - } + TypeAnnotationPosition tapos = TypeAnnotationUtils.fieldTAPosition(((JCTree) var).pos); + + List tcs; + tcs = generateTypeCompounds(processingEnv, type, tapos); + addUniqueTypeCompounds(types, sym, tcs); + } + + @SuppressWarnings("unused") // TODO: see usage in comments above + private static void storeClassExtends( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + Tree ext, + Symbol.ClassSymbol csym, + int implidx) { + + AnnotatedTypeMirror type; + int pos; + if (ext == null) { + // The implicit superclass is always java.lang.Object. + // TODO: is this a good way to get the type? + type = atypeFactory.fromElement(csym.getSuperclass().asElement()); + pos = -1; + } else { + type = atypeFactory.getAnnotatedTypeFromTypeTree(ext); + pos = ((JCTree) ext).pos; + } + + TypeAnnotationPosition tapos = TypeAnnotationUtils.classExtendsTAPosition(implidx, pos); + + List tcs; + tcs = generateTypeCompounds(processingEnv, type, tapos); + addUniqueTypeCompounds(types, csym, tcs); + } + + private static void storeTypeParameters( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + java.util.List tps, + Symbol sym) { + boolean isClassOrInterface = sym.getKind().isClass() || sym.getKind().isInterface(); + List tcs = List.nil(); + + int tpidx = 0; + for (TypeParameterTree tp : tps) { + AnnotatedTypeVariable typeVar = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tp); + // System.out.println("The Type for type parameter " + tp + " is " + type); + + TypeAnnotationPosition tapos; + // Note: we use the type parameter pos also for the bounds; + // the bounds may not be explicit and we couldn't look up separate pos. + if (isClassOrInterface) { + tapos = TypeAnnotationUtils.typeParameterTAPosition(tpidx, ((JCTree) tp).pos); + } else { + tapos = TypeAnnotationUtils.methodTypeParameterTAPosition(tpidx, ((JCTree) tp).pos); + } + + { // This block is essentially direct annotations, perhaps we should refactor that + // method out + List res = List.nil(); + for (AnnotationMirror am : typeVar.getLowerBound().getAnnotations()) { + Attribute.TypeCompound tc = + TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv); + res = res.prepend(tc); } - { - // throws clauses - int tidx = 0; - java.util.List ttypes = mtype.getThrownTypes(); - for (JCTree thr : ((JCTree.JCMethodDecl) meth).getThrows()) { - tapos = TypeAnnotationUtils.methodThrowsTAPosition(tidx, thr.pos); - tcs = tcs.appendList(generateTypeCompounds(processingEnv, ttypes.get(tidx), tapos)); - ++tidx; - } + tcs = tcs.appendList(res); + } + + AnnotatedTypeMirror tpbound = typeVar.getUpperBound(); + java.util.List bounds; + if (tpbound.getKind() == TypeKind.INTERSECTION) { + bounds = ((AnnotatedIntersectionType) tpbound).getBounds(); + } else { + bounds = List.of(tpbound); + } + + int bndidx = 0; + for (AnnotatedTypeMirror bound : bounds) { + if (bndidx == 0 && ((Type) bound.getUnderlyingType()).isInterface()) { + // If the first bound is an interface, there is an implicit java.lang.Object + ++bndidx; } - addUniqueTypeCompounds(types, sym, tcs); - } - - private static void storeVariable( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - VariableTree var) { - VarSymbol sym = (VarSymbol) TreeUtils.elementFromDeclaration(var); - AnnotatedTypeMirror type; - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - // TODO: this is rather ugly: we do not want refinement from the - // initializer of the field. We need a general way to get - // the "defaulted" type of a variable. - type = - ((GenericAnnotatedTypeFactory) atypeFactory) - .getAnnotatedTypeLhs(var); + if (isClassOrInterface) { + tapos = + TypeAnnotationUtils.typeParameterBoundTAPosition(tpidx, bndidx, ((JCTree) tp).pos); } else { - type = atypeFactory.getAnnotatedType(var); + tapos = + TypeAnnotationUtils.methodTypeParameterBoundTAPosition( + tpidx, bndidx, ((JCTree) tp).pos); } - TypeAnnotationPosition tapos = TypeAnnotationUtils.fieldTAPosition(((JCTree) var).pos); - - List tcs; - tcs = generateTypeCompounds(processingEnv, type, tapos); - addUniqueTypeCompounds(types, sym, tcs); + tcs = tcs.appendList(generateTypeCompounds(processingEnv, bound, tapos)); + ++bndidx; + } + ++tpidx; } - @SuppressWarnings("unused") // TODO: see usage in comments above - private static void storeClassExtends( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - Tree ext, - Symbol.ClassSymbol csym, - int implidx) { - - AnnotatedTypeMirror type; - int pos; - if (ext == null) { - // The implicit superclass is always java.lang.Object. - // TODO: is this a good way to get the type? - type = atypeFactory.fromElement(csym.getSuperclass().asElement()); - pos = -1; - } else { - type = atypeFactory.getAnnotatedTypeFromTypeTree(ext); - pos = ((JCTree) ext).pos; - } + // System.out.println("Adding " + tcs + " to " + sym); + addUniqueTypeCompounds(types, sym, tcs); + } - TypeAnnotationPosition tapos = TypeAnnotationUtils.classExtendsTAPosition(implidx, pos); + private static void addUniqueTypeCompounds(Types types, Symbol sym, List tcs) { + List raw = sym.getRawTypeAttributes(); + List res = List.nil(); - List tcs; - tcs = generateTypeCompounds(processingEnv, type, tapos); - addUniqueTypeCompounds(types, csym, tcs); + for (Attribute.TypeCompound tc : tcs) { + if (!TypeAnnotationUtils.isTypeCompoundContained(raw, tc, types)) { + res = res.append(tc); + } } + // That method only uses reference equality. isTypeCompoundContained does a deep comparison. + sym.appendUniqueTypeAttributes(res); + } + + // Do not return null. Return List.nil() if there are no TypeCompounds to return. + private static List generateTypeCompounds( + ProcessingEnvironment processingEnv, AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { + return new TCConvert(processingEnv).scan(type, tapos); + } + + /** + * Convert an AnnotatedTypeMirror and a TypeAnnotationPosition into the corresponding + * TypeCompounds. + */ + private static class TCConvert + extends AnnotatedTypeScanner, TypeAnnotationPosition> { + + /** The processing environment. */ + private final ProcessingEnvironment processingEnv; - private static void storeTypeParameters( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - java.util.List tps, - Symbol sym) { - boolean isClassOrInterface = sym.getKind().isClass() || sym.getKind().isInterface(); - List tcs = List.nil(); - - int tpidx = 0; - for (TypeParameterTree tp : tps) { - AnnotatedTypeVariable typeVar = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tp); - // System.out.println("The Type for type parameter " + tp + " is " + type); - - TypeAnnotationPosition tapos; - // Note: we use the type parameter pos also for the bounds; - // the bounds may not be explicit and we couldn't look up separate pos. - if (isClassOrInterface) { - tapos = TypeAnnotationUtils.typeParameterTAPosition(tpidx, ((JCTree) tp).pos); - } else { - tapos = TypeAnnotationUtils.methodTypeParameterTAPosition(tpidx, ((JCTree) tp).pos); - } - - { // This block is essentially direct annotations, perhaps we should refactor that - // method out - List res = List.nil(); - for (AnnotationMirror am : typeVar.getLowerBound().getAnnotations()) { - Attribute.TypeCompound tc = - TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( - am, tapos, processingEnv); - res = res.prepend(tc); - } - tcs = tcs.appendList(res); - } - - AnnotatedTypeMirror tpbound = typeVar.getUpperBound(); - java.util.List bounds; - if (tpbound.getKind() == TypeKind.INTERSECTION) { - bounds = ((AnnotatedIntersectionType) tpbound).getBounds(); - } else { - bounds = List.of(tpbound); - } - - int bndidx = 0; - for (AnnotatedTypeMirror bound : bounds) { - if (bndidx == 0 && ((Type) bound.getUnderlyingType()).isInterface()) { - // If the first bound is an interface, there is an implicit java.lang.Object - ++bndidx; - } - - if (isClassOrInterface) { - tapos = - TypeAnnotationUtils.typeParameterBoundTAPosition( - tpidx, bndidx, ((JCTree) tp).pos); - } else { - tapos = - TypeAnnotationUtils.methodTypeParameterBoundTAPosition( - tpidx, bndidx, ((JCTree) tp).pos); - } - - tcs = tcs.appendList(generateTypeCompounds(processingEnv, bound, tapos)); - ++bndidx; - } - ++tpidx; - } - - // System.out.println("Adding " + tcs + " to " + sym); - addUniqueTypeCompounds(types, sym, tcs); + /** + * Creates a {@link TCConvert}. + * + * @param processingEnv the processing environment + */ + TCConvert(ProcessingEnvironment processingEnv) { + super(List.nil()); + this.processingEnv = processingEnv; } - private static void addUniqueTypeCompounds(Types types, Symbol sym, List tcs) { - List raw = sym.getRawTypeAttributes(); - List res = List.nil(); - - for (Attribute.TypeCompound tc : tcs) { - if (!TypeAnnotationUtils.isTypeCompoundContained(raw, tc, types)) { - res = res.append(tc); - } - } - // That method only uses reference equality. isTypeCompoundContained does a deep comparison. - sym.appendUniqueTypeAttributes(res); + @Override + public List scan(AnnotatedTypeMirror type, TypeAnnotationPosition pos) { + if (pos == null) { + throw new BugInCF("TypesIntoElements: invalid usage, null pos with type: " + type); + } + List res = super.scan(type, pos); + return res; } - // Do not return null. Return List.nil() if there are no TypeCompounds to return. - private static List generateTypeCompounds( - ProcessingEnvironment processingEnv, - AnnotatedTypeMirror type, - TypeAnnotationPosition tapos) { - return new TCConvert(processingEnv).scan(type, tapos); + @Override + public List reduce(List r1, List r2) { + if (r1 == null) { + return r2; + } + if (r2 == null) { + return r1; + } + return r1.appendList(r2); } - /** - * Convert an AnnotatedTypeMirror and a TypeAnnotationPosition into the corresponding - * TypeCompounds. - */ - private static class TCConvert - extends AnnotatedTypeScanner, TypeAnnotationPosition> { - - /** The processing environment. */ - private final ProcessingEnvironment processingEnv; - - /** - * Creates a {@link TCConvert}. - * - * @param processingEnv the processing environment - */ - TCConvert(ProcessingEnvironment processingEnv) { - super(List.nil()); - this.processingEnv = processingEnv; - } - - @Override - public List scan(AnnotatedTypeMirror type, TypeAnnotationPosition pos) { - if (pos == null) { - throw new BugInCF("TypesIntoElements: invalid usage, null pos with type: " + type); - } - List res = super.scan(type, pos); - return res; - } - - @Override - public List reduce(List r1, List r2) { - if (r1 == null) { - return r2; - } - if (r2 == null) { - return r1; - } - return r1.appendList(r2); - } + private List directAnnotations( + AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { + List res = List.nil(); + + for (AnnotationMirror am : type.getAnnotations()) { + // TODO: I BELIEVE THIS ISN'T TRUE BECAUSE PARAMETERS MAY HAVE ANNOTATIONS THAT CAME + // FROM THE ELEMENT OF THE CLASS WHICH PREVIOUSLY WAS WRITTEN OUT BY + // TYPESINTOELEMENT. + // if (am instanceof Attribute.TypeCompound) { + // // If it is a TypeCompound it was already present in source + // (right?), + // // so there is nothing to do. + // // System.out.println(" found TypeComound: " + am + " pos: " + // + ((Attribute.TypeCompound)am).position); + // } else { + // TODO: DOES THIS LEAD TO DOUBLING UP ON THE SAME ANNOTATION IN THE ELEMENT? + Attribute.TypeCompound tc = + TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv); + res = res.prepend(tc); + // } + } + return res; + } - private List directAnnotations( - AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { - List res = List.nil(); - - for (AnnotationMirror am : type.getAnnotations()) { - // TODO: I BELIEVE THIS ISN'T TRUE BECAUSE PARAMETERS MAY HAVE ANNOTATIONS THAT CAME - // FROM THE ELEMENT OF THE CLASS WHICH PREVIOUSLY WAS WRITTEN OUT BY - // TYPESINTOELEMENT. - // if (am instanceof Attribute.TypeCompound) { - // // If it is a TypeCompound it was already present in source - // (right?), - // // so there is nothing to do. - // // System.out.println(" found TypeComound: " + am + " pos: " - // + ((Attribute.TypeCompound)am).position); - // } else { - // TODO: DOES THIS LEAD TO DOUBLING UP ON THE SAME ANNOTATION IN THE ELEMENT? - Attribute.TypeCompound tc = - TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( - am, tapos, processingEnv); - res = res.prepend(tc); - // } - } - return res; + @Override + public List visitDeclared( + AnnotatedDeclaredType type, TypeAnnotationPosition tapos) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + // Hack for termination + visitedNodes.put(type, List.nil()); + List res; + + TypeAnnotationPosition oldpos = TypeAnnotationUtils.copyTAPosition(tapos); + locateNestedTypes(type, tapos); + + res = directAnnotations(type, tapos); + + // We sometimes fix-up raw types with wildcards. Do not write these into the bytecode + // as there are no corresponding type arguments and therefore no location to actually + // add them to. + if (!type.isUnderlyingTypeRaw()) { + int arg = 0; + for (AnnotatedTypeMirror ta : type.getTypeArguments()) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = + tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); + res = scanAndReduce(ta, newpos, res); + ++arg; } + } + + AnnotatedTypeMirror encl = type.getEnclosingType(); + if (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) { + // use original tapos + res = scanAndReduce(encl, oldpos, res); + } + visitedNodes.put(type, res); + return res; + } - @Override - public List visitDeclared( - AnnotatedDeclaredType type, TypeAnnotationPosition tapos) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - // Hack for termination - visitedNodes.put(type, List.nil()); - List res; - - TypeAnnotationPosition oldpos = TypeAnnotationUtils.copyTAPosition(tapos); - locateNestedTypes(type, tapos); - - res = directAnnotations(type, tapos); - - // We sometimes fix-up raw types with wildcards. Do not write these into the bytecode - // as there are no corresponding type arguments and therefore no location to actually - // add them to. - if (!type.isUnderlyingTypeRaw()) { - int arg = 0; - for (AnnotatedTypeMirror ta : type.getTypeArguments()) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = - tapos.location.append( - new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); - res = scanAndReduce(ta, newpos, res); - ++arg; - } - } - - AnnotatedTypeMirror encl = type.getEnclosingType(); - if (encl != null - && encl.getKind() != TypeKind.NONE - && encl.getKind() != TypeKind.ERROR) { - // use original tapos - res = scanAndReduce(encl, oldpos, res); - } - visitedNodes.put(type, res); - return res; - } + /* Modeled after + * {@link com.sun.tools.javac.code.TypeAnnotations.TypeAnnotationPositions#locateNestedTypes(Type, TypeAnnotationPosition)} + */ + private void locateNestedTypes(AnnotatedDeclaredType type, TypeAnnotationPosition p) { + // The number of "steps" to get from the full type to the + // left-most outer type. + ListBuffer depth = new ListBuffer<>(); + + Type encl = (Type) type.getUnderlyingType().getEnclosingType(); + while (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) { + depth = depth.append(TypePathEntry.INNER_TYPE); + encl = encl.getEnclosingType(); + } + + if (depth.nonEmpty()) { + p.location = p.location.appendList(depth.toList()); + } + } - /* Modeled after - * {@link com.sun.tools.javac.code.TypeAnnotations.TypeAnnotationPositions#locateNestedTypes(Type, TypeAnnotationPosition)} - */ - private void locateNestedTypes(AnnotatedDeclaredType type, TypeAnnotationPosition p) { - // The number of "steps" to get from the full type to the - // left-most outer type. - ListBuffer depth = new ListBuffer<>(); - - Type encl = (Type) type.getUnderlyingType().getEnclosingType(); - while (encl != null - && encl.getKind() != TypeKind.NONE - && encl.getKind() != TypeKind.ERROR) { - depth = depth.append(TypePathEntry.INNER_TYPE); - encl = encl.getEnclosingType(); - } - - if (depth.nonEmpty()) { - p.location = p.location.appendList(depth.toList()); - } - } + @Override + public List visitIntersection( + AnnotatedIntersectionType type, TypeAnnotationPosition tapos) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, List.nil()); + List res; + res = directAnnotations(type, tapos); + + int arg = 0; + for (AnnotatedTypeMirror bound : type.getBounds()) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = + tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); + res = scanAndReduce(bound, newpos, res); + ++arg; + } + visitedNodes.put(type, res); + return res; + } - @Override - public List visitIntersection( - AnnotatedIntersectionType type, TypeAnnotationPosition tapos) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, List.nil()); - List res; - res = directAnnotations(type, tapos); - - int arg = 0; - for (AnnotatedTypeMirror bound : type.getBounds()) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = - tapos.location.append( - new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); - res = scanAndReduce(bound, newpos, res); - ++arg; - } - visitedNodes.put(type, res); - return res; - } + @Override + public List visitUnion(AnnotatedUnionType type, TypeAnnotationPosition tapos) { + // We should never need to write a union type, so raise an error. + throw new BugInCF( + "TypesIntoElement: encountered union type: " + type + " at position: " + tapos); + } - @Override - public List visitUnion( - AnnotatedUnionType type, TypeAnnotationPosition tapos) { - // We should never need to write a union type, so raise an error. - throw new BugInCF( - "TypesIntoElement: encountered union type: " + type + " at position: " + tapos); - } + @Override + public List visitArray(AnnotatedArrayType type, TypeAnnotationPosition tapos) { + List res; + res = directAnnotations(type, tapos); - @Override - public List visitArray( - AnnotatedArrayType type, TypeAnnotationPosition tapos) { - List res; - res = directAnnotations(type, tapos); + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = tapos.location.append(TypePathEntry.ARRAY); - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = tapos.location.append(TypePathEntry.ARRAY); + return reduce(super.visitArray(type, newpos), res); + } - return reduce(super.visitArray(type, newpos), res); - } + @Override + public List visitPrimitive( + AnnotatedPrimitiveType type, TypeAnnotationPosition tapos) { + List res; + res = directAnnotations(type, tapos); + return res; + } - @Override - public List visitPrimitive( - AnnotatedPrimitiveType type, TypeAnnotationPosition tapos) { - List res; - res = directAnnotations(type, tapos); - return res; - } + @Override + public List visitTypeVariable( + AnnotatedTypeVariable type, TypeAnnotationPosition tapos) { + List res; + res = directAnnotations(type, tapos); + // Do not call super. The bound will be visited separately. + return res; + } - @Override - public List visitTypeVariable( - AnnotatedTypeVariable type, TypeAnnotationPosition tapos) { - List res; - res = directAnnotations(type, tapos); - // Do not call super. The bound will be visited separately. - return res; + @Override + public List visitWildcard( + AnnotatedWildcardType type, TypeAnnotationPosition tapos) { + if (this.visitedNodes.containsKey(type)) { + return List.nil(); + } + // Hack for termination, otherwise we'll visit one type too far (the same recursive + // wildcard twice and generate extra type annos) + visitedNodes.put(type, List.nil()); + List res; + + // Note: By default, an Unbound wildcard will return true for both isExtendsBound and + // isSuperBound + if (((Type.WildcardType) type.getUnderlyingType()).isExtendsBound()) { + res = directAnnotations(type.getSuperBound(), tapos); + + AnnotatedTypeMirror ext = type.getExtendsBound(); + if (ext != null) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = tapos.location.append(TypePathEntry.WILDCARD); + res = scanAndReduce(ext, newpos, res); } - @Override - public List visitWildcard( - AnnotatedWildcardType type, TypeAnnotationPosition tapos) { - if (this.visitedNodes.containsKey(type)) { - return List.nil(); - } - // Hack for termination, otherwise we'll visit one type too far (the same recursive - // wildcard twice and generate extra type annos) - visitedNodes.put(type, List.nil()); - List res; - - // Note: By default, an Unbound wildcard will return true for both isExtendsBound and - // isSuperBound - if (((Type.WildcardType) type.getUnderlyingType()).isExtendsBound()) { - res = directAnnotations(type.getSuperBound(), tapos); - - AnnotatedTypeMirror ext = type.getExtendsBound(); - if (ext != null) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = tapos.location.append(TypePathEntry.WILDCARD); - res = scanAndReduce(ext, newpos, res); - } - - } else { - res = directAnnotations(type.getExtendsBound(), tapos); - AnnotatedTypeMirror sup = type.getSuperBoundField(); - if (sup != null) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = tapos.location.append(TypePathEntry.WILDCARD); - res = scanAndReduce(sup, newpos, res); - } - } - visitedNodes.put(type, res); - return res; + } else { + res = directAnnotations(type.getExtendsBound(), tapos); + AnnotatedTypeMirror sup = type.getSuperBoundField(); + if (sup != null) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = tapos.location.append(TypePathEntry.WILDCARD); + res = scanAndReduce(sup, newpos, res); } + } + visitedNodes.put(type, res); + return res; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java index 97f1279dff9..ceddec41c2c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java @@ -1,11 +1,9 @@ package org.checkerframework.framework.type; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; - import java.util.List; - import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; /** * A viewpoint adapter. @@ -15,60 +13,57 @@ */ public interface ViewpointAdapter { - /** - * Viewpoint adapts a member/field access. - * - *

Developer notes: When this method is invoked on a member/field with a type given by a type - * parameter, the type arguments are correctly substituted, and memberType is already in a good - * shape. Only annotations on the memberType should be replaced by the viewpoint adapted ones. - * - * @param receiverType receiver type through which the member/field is accessed. - * @param memberElement element of the accessed member/field. - * @param memberType accessed type of the member/field. After the method returns, it will be - * mutated to the viewpoint adapted result. - */ - void viewpointAdaptMember( - AnnotatedTypeMirror receiverType, - Element memberElement, - AnnotatedTypeMirror memberType); + /** + * Viewpoint adapts a member/field access. + * + *

Developer notes: When this method is invoked on a member/field with a type given by a type + * parameter, the type arguments are correctly substituted, and memberType is already in a good + * shape. Only annotations on the memberType should be replaced by the viewpoint adapted ones. + * + * @param receiverType receiver type through which the member/field is accessed. + * @param memberElement element of the accessed member/field. + * @param memberType accessed type of the member/field. After the method returns, it will be + * mutated to the viewpoint adapted result. + */ + void viewpointAdaptMember( + AnnotatedTypeMirror receiverType, Element memberElement, AnnotatedTypeMirror memberType); - /** - * Viewpoint adapts a constructor invocation. Takes an unsubstituted method invocation type and - * performs the viewpoint adaption in place, modifying the parameter. - * - * @param receiverType receiver type through which a constructor is invoked. - * @param constructorElt element of the invoked constructor. - * @param constructorType invoked type of the constructor with type variables not substituted. - * After the method returns, it will be mutated to the viewpoint adapted constructor type. - */ - void viewpointAdaptConstructor( - AnnotatedTypeMirror receiverType, - ExecutableElement constructorElt, - AnnotatedExecutableType constructorType); + /** + * Viewpoint adapts a constructor invocation. Takes an unsubstituted method invocation type and + * performs the viewpoint adaption in place, modifying the parameter. + * + * @param receiverType receiver type through which a constructor is invoked. + * @param constructorElt element of the invoked constructor. + * @param constructorType invoked type of the constructor with type variables not substituted. + * After the method returns, it will be mutated to the viewpoint adapted constructor type. + */ + void viewpointAdaptConstructor( + AnnotatedTypeMirror receiverType, + ExecutableElement constructorElt, + AnnotatedExecutableType constructorType); - /** - * Viewpoint adapts a method invocation. Takes an unsubstituted method invocation type and - * performs the viewpoint adaption in place, modifying the parameter. - * - * @param receiverType receiver type through which a method is invoked. - * @param methodElt element of the invoked method. Only used to determine whether this type - * should be viewpoint adapted - * @param methodType invoked type of the method with type variables not substituted. After the - * method returns, it will be mutated to the viewpoint adapted method type. - */ - void viewpointAdaptMethod( - AnnotatedTypeMirror receiverType, - ExecutableElement methodElt, - AnnotatedExecutableType methodType); + /** + * Viewpoint adapts a method invocation. Takes an unsubstituted method invocation type and + * performs the viewpoint adaption in place, modifying the parameter. + * + * @param receiverType receiver type through which a method is invoked. + * @param methodElt element of the invoked method. Only used to determine whether this type should + * be viewpoint adapted + * @param methodType invoked type of the method with type variables not substituted. After the + * method returns, it will be mutated to the viewpoint adapted method type. + */ + void viewpointAdaptMethod( + AnnotatedTypeMirror receiverType, + ExecutableElement methodElt, + AnnotatedExecutableType methodType); - /** - * Viewpoint adapts a type parameter bound when being instantiated. - * - * @param receiverType receiver type through which the type parameter is instantiated. - * @param typeParameterBounds a list of type parameter bounds. After the method returns, it will - * be mutated to the viewpoint adapted type parameter bounds. - */ - void viewpointAdaptTypeParameterBounds( - AnnotatedTypeMirror receiverType, - List typeParameterBounds); + /** + * Viewpoint adapts a type parameter bound when being instantiated. + * + * @param receiverType receiver type through which the type parameter is instantiated. + * @param typeParameterBounds a list of type parameter bounds. After the method returns, it will + * be mutated to the viewpoint adapted type parameter bounds. + */ + void viewpointAdaptTypeParameterBounds( + AnnotatedTypeMirror receiverType, List typeParameterBounds); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java index fa14a5174c9..541dd923e45 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java @@ -2,7 +2,17 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -25,19 +35,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; - /** * Implements framework support for qualifier polymorphism. * @@ -59,555 +56,545 @@ */ public abstract class AbstractQualifierPolymorphism implements QualifierPolymorphism { - /** Annotated type factory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy to use. */ - protected final QualifierHierarchy qualHierarchy; - - /** - * The polymorphic qualifiers: mapping from a polymorphic qualifier of {@code qualHierarchy} to - * the top qualifier of that hierarchy. - */ - protected final AnnotationMirrorMap polyQuals = new AnnotationMirrorMap<>(); - - /** - * The qualifiers at the top of {@code qualHierarchy}. These are the values in {@code - * polyQuals}. - */ - protected final AnnotationMirrorSet topQuals; + /** Annotated type factory. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** The qualifier hierarchy to use. */ + protected final QualifierHierarchy qualHierarchy; + + /** + * The polymorphic qualifiers: mapping from a polymorphic qualifier of {@code qualHierarchy} to + * the top qualifier of that hierarchy. + */ + protected final AnnotationMirrorMap polyQuals = new AnnotationMirrorMap<>(); + + /** + * The qualifiers at the top of {@code qualHierarchy}. These are the values in {@code polyQuals}. + */ + protected final AnnotationMirrorSet topQuals; + + /** Determines the instantiations for each polymorphic qualifier. */ + private final PolyCollector collector = new PolyCollector(); + + /** Resolves each polymorphic qualifier by replacing it with its instantiation. */ + private final SimpleAnnotatedTypeScanner> replacer; + + /** + * Completes a type by removing any unresolved polymorphic qualifiers, replacing them with the + * bottom qualifiers. + */ + private final SimpleAnnotatedTypeScanner completer; + + /** Mapping from poly qualifier to its instantiation for types with a qualifier parameter. */ + protected final AnnotationMirrorMap polyInstantiationForQualifierParameter = + new AnnotationMirrorMap<>(); + + /** The visit method returns true if the passed type has any polymorphic qualifiers. */ + protected final SimpleAnnotatedTypeScanner polyScanner; + + /** + * Creates an {@link AbstractQualifierPolymorphism} instance that uses the given checker for + * querying type qualifiers and the given factory for getting annotated types. Subclasses need to + * add polymorphic qualifiers to {@code this.polyQuals}. + * + * @param env the processing environment + * @param factory the factory for the current checker + */ + protected AbstractQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { + this.atypeFactory = factory; + this.qualHierarchy = factory.getQualifierHierarchy(); + this.topQuals = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); + + this.completer = + new SimpleAnnotatedTypeScanner<>( + (type, p) -> { + for (Map.Entry entry : polyQuals.entrySet()) { + AnnotationMirror poly = entry.getKey(); + AnnotationMirror top = entry.getValue(); + if (type.hasAnnotation(poly)) { + type.removeAnnotation(poly); + if (type.getKind() != TypeKind.TYPEVAR && type.getKind() != TypeKind.WILDCARD) { + // Do not add qualifiers to type variables and + // wildcards + type.addAnnotation(this.qualHierarchy.getBottomAnnotation(top)); + } + } + } + return null; + }); + + this.replacer = + new SimpleAnnotatedTypeScanner<>( + (type, map) -> { + replace(type, map); + return null; + }); + + this.polyScanner = + new SimpleAnnotatedTypeScanner<>( + (type, notused) -> { + for (AnnotationMirror a : type.getAnnotations()) { + if (qualHierarchy.isPolymorphicQualifier(a)) { + return true; + } + } + return false; + }, + Boolean::logicalOr, + false); + } + + /** + * Reset to allow reuse of the same instance. Subclasses should override this method. The + * overriding implementation should clear its additional state and then call the super + * implementation. + */ + protected void reset() { + collector.reset(); + replacer.reset(); + completer.reset(); + polyInstantiationForQualifierParameter.clear(); + } + + /** + * Returns true if {@code type} has any polymorphic qualifiers + * + * @param type a type that might have polymorphic qualifiers + * @return true if {@code type} has any polymorphic qualifiers + */ + protected boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type) { + return polyScanner.visit(type); + } + + /** + * Resolves polymorphism annotations for the given type. + * + * @param tree the tree associated with the type + * @param type the type to annotate + */ + @Override + public void resolve(MethodInvocationTree tree, AnnotatedExecutableType type) { + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { + return; + } - /** Determines the instantiations for each polymorphic qualifier. */ - private final PolyCollector collector = new PolyCollector(); + // javac produces enum super calls with zero arguments even though the + // method element requires two. + // See also BaseTypeVisitor.visitMethodInvocation and + // CFGBuilder.CFGTranslationPhaseOne.visitMethodInvocation. + if (TreeUtils.isEnumSuperCall(tree)) { + return; + } + List parameters = + AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments(), null); + List arguments = + CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); + + AnnotationMirrorMap instantiationMapping = + collector.visit(arguments, parameters); + + // For super() and this() method calls, getReceiverType(tree) does not return the correct + // type. So, just skip those. This is consistent with skipping receivers of constructors + // below. + if (type.getReceiverType() != null + && !TreeUtils.isSuperConstructorCall(tree) + && !TreeUtils.isThisConstructorCall(tree)) { + instantiationMapping = + collector.reduce( + instantiationMapping, + collector.visit(atypeFactory.getReceiverType(tree), type.getReceiverType())); + } - /** Resolves each polymorphic qualifier by replacing it with its instantiation. */ - private final SimpleAnnotatedTypeScanner> replacer; + if (instantiationMapping != null && !instantiationMapping.isEmpty()) { + replacer.visit(type, instantiationMapping); + } else { + completer.visit(type); + } + reset(); + } - /** - * Completes a type by removing any unresolved polymorphic qualifiers, replacing them with the - * bottom qualifiers. - */ - private final SimpleAnnotatedTypeScanner completer; + @Override + public void resolve(NewClassTree tree, AnnotatedExecutableType type) { + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { + return; + } + List parameters = type.getParameterTypes(); + List arguments = + CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); + + AnnotationMirrorMap instantiationMapping = + collector.visit(arguments, parameters); + // TODO: poly on receiver for constructors? + // instantiationMapping = collector.reduce(instantiationMapping, + // collector.visit(factory.getReceiverType(tree), type.getReceiverType())); + + AnnotatedTypeMirror newClassType = type.getReturnType().deepCopy(); + newClassType.clearAnnotations(); + newClassType.replaceAnnotations(atypeFactory.getExplicitNewClassAnnos(tree)); + + instantiationMapping = + collector.reduce( + instantiationMapping, mapQualifierToPoly(newClassType, type.getReturnType())); + + if (instantiationMapping != null && !instantiationMapping.isEmpty()) { + replacer.visit(type, instantiationMapping); + } else { + completer.visit(type); + } + reset(); + } - /** Mapping from poly qualifier to its instantiation for types with a qualifier parameter. */ - protected final AnnotationMirrorMap polyInstantiationForQualifierParameter = - new AnnotationMirrorMap<>(); + @Override + public void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type) { + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { + return; + } + AnnotationMirrorMap matchingMapping = new AnnotationMirrorMap<>(); + polyQuals.forEach( + (polyAnnotation, topAnno) -> { + AnnotationMirror annoOnOwner = owner.getAnnotationInHierarchy(topAnno); + if (annoOnOwner != null) { + matchingMapping.put(polyAnnotation, annoOnOwner); + } + }); + if (!matchingMapping.isEmpty()) { + replacer.visit(type, matchingMapping); + } else { + completer.visit(type); + } + reset(); + } + + @Override + public void resolve( + AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) { + if (hasPolymorphicQualifiers(functionalInterface.getReturnType())) { + // functional interface has a polymorphic qualifier, so they should not be resolved + // on memberReference. + return; + } + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(memberReference)) { + return; + } + AnnotationMirrorMap instantiationMapping; + + List parameters = memberReference.getParameterTypes(); + List args = functionalInterface.getParameterTypes(); + if (args.size() == parameters.size() + 1) { + // If the member reference is a reference to an instance method of an arbitrary + // object, then first parameter of the functional interface corresponds to the + // receiver of the member reference. + List newParameters = new ArrayList<>(parameters.size() + 1); + newParameters.add(memberReference.getReceiverType()); + newParameters.addAll(parameters); + parameters = newParameters; + instantiationMapping = new AnnotationMirrorMap<>(); + } else { + if (memberReference.getReceiverType() != null + && functionalInterface.getReceiverType() != null) { + instantiationMapping = + mapQualifierToPoly( + functionalInterface.getReceiverType(), memberReference.getReceiverType()); + } else { + instantiationMapping = new AnnotationMirrorMap<>(); + } + } + // Deal with varargs + if (memberReference.isVarArgs() && !functionalInterface.isVarArgs()) { + parameters = AnnotatedTypes.expandVarArgsParametersFromTypes(memberReference, args); + } - /** The visit method returns true if the passed type has any polymorphic qualifiers. */ - protected final SimpleAnnotatedTypeScanner polyScanner; + instantiationMapping = + collector.reduce(instantiationMapping, collector.visit(args, parameters)); - /** - * Creates an {@link AbstractQualifierPolymorphism} instance that uses the given checker for - * querying type qualifiers and the given factory for getting annotated types. Subclasses need - * to add polymorphic qualifiers to {@code this.polyQuals}. - * - * @param env the processing environment - * @param factory the factory for the current checker - */ - protected AbstractQualifierPolymorphism( - ProcessingEnvironment env, AnnotatedTypeFactory factory) { - this.atypeFactory = factory; - this.qualHierarchy = factory.getQualifierHierarchy(); - this.topQuals = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - - this.completer = - new SimpleAnnotatedTypeScanner<>( - (type, p) -> { - for (Map.Entry entry : - polyQuals.entrySet()) { - AnnotationMirror poly = entry.getKey(); - AnnotationMirror top = entry.getValue(); - if (type.hasAnnotation(poly)) { - type.removeAnnotation(poly); - if (type.getKind() != TypeKind.TYPEVAR - && type.getKind() != TypeKind.WILDCARD) { - // Do not add qualifiers to type variables and - // wildcards - type.addAnnotation( - this.qualHierarchy.getBottomAnnotation(top)); - } - } - } - return null; - }); - - this.replacer = - new SimpleAnnotatedTypeScanner<>( - (type, map) -> { - replace(type, map); - return null; - }); - - this.polyScanner = - new SimpleAnnotatedTypeScanner<>( - (type, notused) -> { - for (AnnotationMirror a : type.getAnnotations()) { - if (qualHierarchy.isPolymorphicQualifier(a)) { - return true; - } - } - return false; - }, - Boolean::logicalOr, - false); + if (instantiationMapping != null && !instantiationMapping.isEmpty()) { + replacer.visit(memberReference, instantiationMapping); + } else { + // TODO: Do we need this (return type?) + completer.visit(memberReference); } - - /** - * Reset to allow reuse of the same instance. Subclasses should override this method. The - * overriding implementation should clear its additional state and then call the super - * implementation. - */ - protected void reset() { - collector.reset(); - replacer.reset(); - completer.reset(); - polyInstantiationForQualifierParameter.clear(); + reset(); + } + + /** + * If the primary annotation of {@code polyType} is a polymorphic qualifier, then it is mapped to + * the primary annotation of {@code type} and the map is returned. Otherwise, an empty map is + * returned. + * + * @param type type with qualifier to us in the map + * @param polyType type that may have polymorphic qualifiers + * @return a mapping from the polymorphic qualifiers in {@code polyType} to the qualifiers in + * {@code type} + */ + private AnnotationMirrorMap mapQualifierToPoly( + AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { + AnnotationMirrorMap result = new AnnotationMirrorMap<>(); + + for (Map.Entry kv : polyQuals.entrySet()) { + AnnotationMirror top = kv.getValue(); + AnnotationMirror poly = kv.getKey(); + if (polyType.hasAnnotation(poly)) { + AnnotationMirror typeQual = type.getAnnotationInHierarchy(top); + if (typeQual != null) { + if (atypeFactory.hasQualifierParameterInHierarchy(type, top)) { + polyInstantiationForQualifierParameter.put(poly, typeQual); + } + result.put(poly, typeQual); + } + } } + return result; + } + + /** + * Returns annotation that is the combination of the two annotations. The annotations are + * instantiations for {@code polyQual}. + * + *

The combination is typically their least upper bound. (It could be the GLB in the case that + * all arguments to a polymorphic method must have the same annotation.) + * + * @param polyQual polymorphic qualifier for which {@code a1} and {@code a2} are instantiations + * @param a1 an annotation that is an instantiation of {@code polyQual} + * @param a2 an annotation that is an instantiation of {@code polyQual} + * @return an annotation that is the combination of the two annotations + */ + protected abstract AnnotationMirror combine( + AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2); + + /** + * Replaces the top-level polymorphic annotations in {@code type} with the instantiations in + * {@code replacements}. + * + *

This method is called on all parts of a type. + * + * @param type the AnnotatedTypeMirror whose poly annotations are replaced; it is side-effected by + * this method + * @param replacements a mapping from polymorphic annotation to instantiation + */ + protected abstract void replace( + AnnotatedTypeMirror type, AnnotationMirrorMap replacements); + + /** + * A helper class that resolves the polymorphic qualifiers with the most restrictive qualifier. It + * returns a mapping from the polymorphic qualifier to the substitution for that qualifier. + */ + private class PolyCollector + extends EquivalentAtmComboScanner, Void> { /** - * Returns true if {@code type} has any polymorphic qualifiers + * Set of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been visited. + * Used to prevent infinite recursion on recursive types. * - * @param type a type that might have polymorphic qualifiers - * @return true if {@code type} has any polymorphic qualifiers + *

Uses reference equality rather than equals because the visitor may visit two types that + * are structurally equal, but not actually the same. For example, the wildcards in {@code + * IPair} may be equal, but they both should be visited. */ - protected boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type) { - return polyScanner.visit(type); - } + private final Set visitedTypes = + Collections.newSetFromMap(new IdentityHashMap()); /** - * Resolves polymorphism annotations for the given type. - * - * @param tree the tree associated with the type - * @param type the type to annotate + * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is + * added to the list of visited AnnotatedTypeMirrors. */ - @Override - public void resolve(MethodInvocationTree tree, AnnotatedExecutableType type) { - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { - return; - } - - // javac produces enum super calls with zero arguments even though the - // method element requires two. - // See also BaseTypeVisitor.visitMethodInvocation and - // CFGBuilder.CFGTranslationPhaseOne.visitMethodInvocation. - if (TreeUtils.isEnumSuperCall(tree)) { - return; - } - List parameters = - AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments(), null); - List arguments = - CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); - - AnnotationMirrorMap instantiationMapping = - collector.visit(arguments, parameters); - - // For super() and this() method calls, getReceiverType(tree) does not return the correct - // type. So, just skip those. This is consistent with skipping receivers of constructors - // below. - if (type.getReceiverType() != null - && !TreeUtils.isSuperConstructorCall(tree) - && !TreeUtils.isThisConstructorCall(tree)) { - instantiationMapping = - collector.reduce( - instantiationMapping, - collector.visit( - atypeFactory.getReceiverType(tree), type.getReceiverType())); - } - - if (instantiationMapping != null && !instantiationMapping.isEmpty()) { - replacer.visit(type, instantiationMapping); - } else { - completer.visit(type); - } - reset(); + private boolean visited(AnnotatedTypeMirror atm) { + return !visitedTypes.add(atm); } @Override - public void resolve(NewClassTree tree, AnnotatedExecutableType type) { - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { - return; - } - List parameters = type.getParameterTypes(); - List arguments = - CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); - - AnnotationMirrorMap instantiationMapping = - collector.visit(arguments, parameters); - // TODO: poly on receiver for constructors? - // instantiationMapping = collector.reduce(instantiationMapping, - // collector.visit(factory.getReceiverType(tree), type.getReceiverType())); - - AnnotatedTypeMirror newClassType = type.getReturnType().deepCopy(); - newClassType.clearAnnotations(); - newClassType.replaceAnnotations(atypeFactory.getExplicitNewClassAnnos(tree)); - - instantiationMapping = - collector.reduce( - instantiationMapping, - mapQualifierToPoly(newClassType, type.getReturnType())); - - if (instantiationMapping != null && !instantiationMapping.isEmpty()) { - replacer.visit(type, instantiationMapping); - } else { - completer.visit(type); - } - reset(); + protected AnnotationMirrorMap scanWithNull( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { + return new AnnotationMirrorMap<>(); } @Override - public void resolve( - VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type) { - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { - return; - } - AnnotationMirrorMap matchingMapping = new AnnotationMirrorMap<>(); - polyQuals.forEach( - (polyAnnotation, topAnno) -> { - AnnotationMirror annoOnOwner = owner.getAnnotationInHierarchy(topAnno); - if (annoOnOwner != null) { - matchingMapping.put(polyAnnotation, annoOnOwner); - } - }); - if (!matchingMapping.isEmpty()) { - replacer.visit(type, matchingMapping); - } else { - completer.visit(type); - } - reset(); - } - - @Override - public void resolve( - AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) { - if (hasPolymorphicQualifiers(functionalInterface.getReturnType())) { - // functional interface has a polymorphic qualifier, so they should not be resolved - // on memberReference. - return; - } - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(memberReference)) { - return; - } - AnnotationMirrorMap instantiationMapping; - - List parameters = memberReference.getParameterTypes(); - List args = functionalInterface.getParameterTypes(); - if (args.size() == parameters.size() + 1) { - // If the member reference is a reference to an instance method of an arbitrary - // object, then first parameter of the functional interface corresponds to the - // receiver of the member reference. - List newParameters = new ArrayList<>(parameters.size() + 1); - newParameters.add(memberReference.getReceiverType()); - newParameters.addAll(parameters); - parameters = newParameters; - instantiationMapping = new AnnotationMirrorMap<>(); - } else { - if (memberReference.getReceiverType() != null - && functionalInterface.getReceiverType() != null) { - instantiationMapping = - mapQualifierToPoly( - functionalInterface.getReceiverType(), - memberReference.getReceiverType()); - } else { - instantiationMapping = new AnnotationMirrorMap<>(); - } - } - // Deal with varargs - if (memberReference.isVarArgs() && !functionalInterface.isVarArgs()) { - parameters = AnnotatedTypes.expandVarArgsParametersFromTypes(memberReference, args); - } - - instantiationMapping = - collector.reduce(instantiationMapping, collector.visit(args, parameters)); - - if (instantiationMapping != null && !instantiationMapping.isEmpty()) { - replacer.visit(memberReference, instantiationMapping); + public AnnotationMirrorMap reduce( + AnnotationMirrorMap r1, AnnotationMirrorMap r2) { + + if (r1 == null || r1.isEmpty()) { + return r2; + } + if (r2 == null || r2.isEmpty()) { + return r1; + } + + AnnotationMirrorMap res = new AnnotationMirrorMap<>(); + // Ensure that all qualifiers from r1 and r2 are visited. + AnnotationMirrorSet r2remain = new AnnotationMirrorSet(); + r2remain.addAll(r2.keySet()); + for (Map.Entry entry : r1.entrySet()) { + AnnotationMirror polyQual = entry.getKey(); + AnnotationMirror a1Annos = entry.getValue(); + AnnotationMirror a2Annos = r2.get(polyQual); + if (a2Annos == null) { + res.put(polyQual, a1Annos); } else { - // TODO: Do we need this (return type?) - completer.visit(memberReference); + res.put(polyQual, combine(polyQual, a1Annos, a2Annos)); } - reset(); + r2remain.remove(polyQual); + } + for (AnnotationMirror key2 : r2remain) { + res.put(key2, r2.get(key2)); + } + return res; } /** - * If the primary annotation of {@code polyType} is a polymorphic qualifier, then it is mapped - * to the primary annotation of {@code type} and the map is returned. Otherwise, an empty map is - * returned. + * Calls {@link #visit(AnnotatedTypeMirror, AnnotatedTypeMirror)} for each type in {@code + * types}. * - * @param type type with qualifier to us in the map - * @param polyType type that may have polymorphic qualifiers - * @return a mapping from the polymorphic qualifiers in {@code polyType} to the qualifiers in - * {@code type} + * @param types the AnnotateTypeMirrors used to find instantiations + * @param polyTypes the AnnotatedTypeMirrors that may have polymorphic qualifiers + * @return a mapping of polymorphic qualifiers to their instantiations */ - private AnnotationMirrorMap mapQualifierToPoly( - AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { - AnnotationMirrorMap result = new AnnotationMirrorMap<>(); - - for (Map.Entry kv : polyQuals.entrySet()) { - AnnotationMirror top = kv.getValue(); - AnnotationMirror poly = kv.getKey(); - if (polyType.hasAnnotation(poly)) { - AnnotationMirror typeQual = type.getAnnotationInHierarchy(top); - if (typeQual != null) { - if (atypeFactory.hasQualifierParameterInHierarchy(type, top)) { - polyInstantiationForQualifierParameter.put(poly, typeQual); - } - result.put(poly, typeQual); - } - } - } - return result; + private AnnotationMirrorMap visit( + Iterable types, + Iterable polyTypes) { + AnnotationMirrorMap result = new AnnotationMirrorMap<>(); + + Iterator itert = types.iterator(); + Iterator itera = polyTypes.iterator(); + + while (itert.hasNext() && itera.hasNext()) { + AnnotatedTypeMirror type = itert.next(); + AnnotatedTypeMirror actualType = itera.next(); + result = reduce(result, visit(type, actualType)); + } + if (itert.hasNext()) { + throw new BugInCF( + "PolyCollector.visit: types is longer than polyTypes:%n" + + " types = %s%n polyTypes = %s%n", + types, polyTypes); + } + if (itera.hasNext()) { + throw new BugInCF( + "PolyCollector.visit: types is shorter than polyTypes:%n" + + " types = %s%n polyTypes = %s%n", + types, polyTypes); + } + return result; } /** - * Returns annotation that is the combination of the two annotations. The annotations are - * instantiations for {@code polyQual}. - * - *

The combination is typically their least upper bound. (It could be the GLB in the case - * that all arguments to a polymorphic method must have the same annotation.) + * Creates a mapping of polymorphic qualifiers to their instantiations by visiting each + * composite type in {@code type}. * - * @param polyQual polymorphic qualifier for which {@code a1} and {@code a2} are instantiations - * @param a1 an annotation that is an instantiation of {@code polyQual} - * @param a2 an annotation that is an instantiation of {@code polyQual} - * @return an annotation that is the combination of the two annotations + * @param type the AnnotateTypeMirror used to find instantiations + * @param polyType the AnnotatedTypeMirror that may have polymorphic qualifiers + * @return a mapping of polymorphic qualifiers to their instantiations */ - protected abstract AnnotationMirror combine( - AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2); - - /** - * Replaces the top-level polymorphic annotations in {@code type} with the instantiations in - * {@code replacements}. - * - *

This method is called on all parts of a type. - * - * @param type the AnnotatedTypeMirror whose poly annotations are replaced; it is side-effected - * by this method - * @param replacements a mapping from polymorphic annotation to instantiation - */ - protected abstract void replace( - AnnotatedTypeMirror type, AnnotationMirrorMap replacements); - - /** - * A helper class that resolves the polymorphic qualifiers with the most restrictive qualifier. - * It returns a mapping from the polymorphic qualifier to the substitution for that qualifier. - */ - private class PolyCollector - extends EquivalentAtmComboScanner, Void> { - - /** - * Set of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been - * visited. Used to prevent infinite recursion on recursive types. - * - *

Uses reference equality rather than equals because the visitor may visit two types - * that are structurally equal, but not actually the same. For example, the wildcards in - * {@code IPair} may be equal, but they both should be visited. - */ - private final Set visitedTypes = - Collections.newSetFromMap(new IdentityHashMap()); - - /** - * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it - * is added to the list of visited AnnotatedTypeMirrors. - */ - private boolean visited(AnnotatedTypeMirror atm) { - return !visitedTypes.add(atm); + private AnnotationMirrorMap visit( + AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { + if (type.getKind() == TypeKind.NULL) { + return mapQualifierToPoly(type, polyType); + } + + if (type.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) type; + if (wildcardType.getExtendsBound().getKind() == TypeKind.WILDCARD) { + wildcardType = (AnnotatedWildcardType) wildcardType.getExtendsBound(); } - - @Override - protected AnnotationMirrorMap scanWithNull( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { - return new AnnotationMirrorMap<>(); - } - - @Override - public AnnotationMirrorMap reduce( - AnnotationMirrorMap r1, - AnnotationMirrorMap r2) { - - if (r1 == null || r1.isEmpty()) { - return r2; - } - if (r2 == null || r2.isEmpty()) { - return r1; - } - - AnnotationMirrorMap res = new AnnotationMirrorMap<>(); - // Ensure that all qualifiers from r1 and r2 are visited. - AnnotationMirrorSet r2remain = new AnnotationMirrorSet(); - r2remain.addAll(r2.keySet()); - for (Map.Entry entry : r1.entrySet()) { - AnnotationMirror polyQual = entry.getKey(); - AnnotationMirror a1Annos = entry.getValue(); - AnnotationMirror a2Annos = r2.get(polyQual); - if (a2Annos == null) { - res.put(polyQual, a1Annos); - } else { - res.put(polyQual, combine(polyQual, a1Annos, a2Annos)); - } - r2remain.remove(polyQual); - } - for (AnnotationMirror key2 : r2remain) { - res.put(key2, r2.get(key2)); - } - return res; + if (wildcardType.isUninferredTypeArgument()) { + return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); } - /** - * Calls {@link #visit(AnnotatedTypeMirror, AnnotatedTypeMirror)} for each type in {@code - * types}. - * - * @param types the AnnotateTypeMirrors used to find instantiations - * @param polyTypes the AnnotatedTypeMirrors that may have polymorphic qualifiers - * @return a mapping of polymorphic qualifiers to their instantiations - */ - private AnnotationMirrorMap visit( - Iterable types, - Iterable polyTypes) { - AnnotationMirrorMap result = new AnnotationMirrorMap<>(); - - Iterator itert = types.iterator(); - Iterator itera = polyTypes.iterator(); - - while (itert.hasNext() && itera.hasNext()) { - AnnotatedTypeMirror type = itert.next(); - AnnotatedTypeMirror actualType = itera.next(); - result = reduce(result, visit(type, actualType)); - } - if (itert.hasNext()) { - throw new BugInCF( - "PolyCollector.visit: types is longer than polyTypes:%n" - + " types = %s%n polyTypes = %s%n", - types, polyTypes); - } - if (itera.hasNext()) { - throw new BugInCF( - "PolyCollector.visit: types is shorter than polyTypes:%n" - + " types = %s%n polyTypes = %s%n", - types, polyTypes); - } - return result; + switch (polyType.getKind()) { + case WILDCARD: + AnnotatedTypeMirror asSuper = + AnnotatedTypes.asSuper(atypeFactory, wildcardType, polyType); + return visit(asSuper, polyType, null); + case TYPEVAR: + return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); + default: + return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); } + } - /** - * Creates a mapping of polymorphic qualifiers to their instantiations by visiting each - * composite type in {@code type}. - * - * @param type the AnnotateTypeMirror used to find instantiations - * @param polyType the AnnotatedTypeMirror that may have polymorphic qualifiers - * @return a mapping of polymorphic qualifiers to their instantiations - */ - private AnnotationMirrorMap visit( - AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { - if (type.getKind() == TypeKind.NULL) { - return mapQualifierToPoly(type, polyType); - } - - if (type.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) type; - if (wildcardType.getExtendsBound().getKind() == TypeKind.WILDCARD) { - wildcardType = (AnnotatedWildcardType) wildcardType.getExtendsBound(); - } - if (wildcardType.isUninferredTypeArgument()) { - return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); - } - - switch (polyType.getKind()) { - case WILDCARD: - AnnotatedTypeMirror asSuper = - AnnotatedTypes.asSuper(atypeFactory, wildcardType, polyType); - return visit(asSuper, polyType, null); - case TYPEVAR: - return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); - default: - return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); - } - } + AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(atypeFactory, type, polyType); - AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(atypeFactory, type, polyType); - - return visit(asSuper, polyType, null); - } + return visit(asSuper, polyType, null); + } - @Override - public AnnotationMirrorMap visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, Void aVoid) { - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitArray_Array(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, Void aVoid) { + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitArray_Array(type1, type2, aVoid)); + } - @Override - public AnnotationMirrorMap visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void aVoid) { - // Don't call super because asSuper has to be called on each type argument. - if (visited(type2)) { - return new AnnotationMirrorMap<>(); - } - - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - - Iterator type2Args = type2.getTypeArguments().iterator(); - for (AnnotatedTypeMirror type1Arg : type1.getTypeArguments()) { - AnnotatedTypeMirror type2Arg = type2Args.next(); - if (TypesUtils.isErasedSubtype( - type1Arg.getUnderlyingType(), - type2Arg.getUnderlyingType(), - atypeFactory.getChecker().getTypeUtils())) { - result = reduce(result, visit(type1Arg, type2Arg)); - } // else an unchecked warning was issued by Java, ignore this part of the type. - } - - return result; - } + @Override + public AnnotationMirrorMap visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void aVoid) { + // Don't call super because asSuper has to be called on each type argument. + if (visited(type2)) { + return new AnnotationMirrorMap<>(); + } + + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + + Iterator type2Args = type2.getTypeArguments().iterator(); + for (AnnotatedTypeMirror type1Arg : type1.getTypeArguments()) { + AnnotatedTypeMirror type2Arg = type2Args.next(); + if (TypesUtils.isErasedSubtype( + type1Arg.getUnderlyingType(), + type2Arg.getUnderlyingType(), + atypeFactory.getChecker().getTypeUtils())) { + result = reduce(result, visit(type1Arg, type2Arg)); + } // else an unchecked warning was issued by Java, ignore this part of the type. + } + + return result; + } - @Override - public AnnotationMirrorMap visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void aVoid) { - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitIntersection_Intersection(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void aVoid) { + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitIntersection_Intersection(type1, type2, aVoid)); + } - @Override - public AnnotationMirrorMap visitNull_Null( - AnnotatedNullType type1, AnnotatedNullType type2, Void aVoid) { - return mapQualifierToPoly(type1, type2); - } + @Override + public AnnotationMirrorMap visitNull_Null( + AnnotatedNullType type1, AnnotatedNullType type2, Void aVoid) { + return mapQualifierToPoly(type1, type2); + } - @Override - public AnnotationMirrorMap visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void aVoid) { - return mapQualifierToPoly(type1, type2); - } + @Override + public AnnotationMirrorMap visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void aVoid) { + return mapQualifierToPoly(type1, type2); + } - @Override - public AnnotationMirrorMap visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void aVoid) { - if (visited(type2)) { - return new AnnotationMirrorMap<>(); - } - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitTypevar_Typevar(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void aVoid) { + if (visited(type2)) { + return new AnnotationMirrorMap<>(); + } + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitTypevar_Typevar(type1, type2, aVoid)); + } - @Override - public AnnotationMirrorMap visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, Void aVoid) { - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitUnion_Union(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, Void aVoid) { + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitUnion_Union(type1, type2, aVoid)); + } - @Override - public AnnotationMirrorMap visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void aVoid) { - if (visited(type2)) { - return new AnnotationMirrorMap<>(); - } - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitWildcard_Wildcard(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void aVoid) { + if (visited(type2)) { + return new AnnotationMirrorMap<>(); + } + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitWildcard_Wildcard(type1, type2, aVoid)); + } - /** Resets the state. */ - public void reset() { - this.visitedTypes.clear(); - this.visited.clear(); - } + /** Resets the state. */ + public void reset() { + this.visitedTypes.clear(); + this.visited.clear(); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java index cee62f76190..f571fc5eec4 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java @@ -1,13 +1,11 @@ package org.checkerframework.framework.type.poly; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.javacutil.AnnotationMirrorMap; - import java.util.Map; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.javacutil.AnnotationMirrorMap; /** * Default implementation of {@link AbstractQualifierPolymorphism}. The polymorphic qualifiers for a @@ -16,55 +14,55 @@ */ public class DefaultQualifierPolymorphism extends AbstractQualifierPolymorphism { - /** - * Creates a {@link DefaultQualifierPolymorphism} instance that uses {@code factory} for - * querying type qualifiers and for getting annotated types. - * - * @param env the processing environment - * @param factory the factory for the current checker - */ - public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { - super(env, factory); + /** + * Creates a {@link DefaultQualifierPolymorphism} instance that uses {@code factory} for querying + * type qualifiers and for getting annotated types. + * + * @param env the processing environment + * @param factory the factory for the current checker + */ + public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { + super(env, factory); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); - if (poly != null) { - polyQuals.put(poly, top); - } - } + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); + if (poly != null) { + polyQuals.put(poly, top); + } } + } - @Override - protected void replace( - AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { - for (Map.Entry pqentry : replacements.entrySet()) { - AnnotationMirror poly = pqentry.getKey(); - if (type.hasAnnotation(poly)) { - type.removeAnnotation(poly); - AnnotationMirror qual; - if (polyInstantiationForQualifierParameter.containsKey(poly)) { - qual = polyInstantiationForQualifierParameter.get(poly); - } else { - qual = pqentry.getValue(); - } - type.replaceAnnotation(qual); - } + @Override + protected void replace( + AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { + for (Map.Entry pqentry : replacements.entrySet()) { + AnnotationMirror poly = pqentry.getKey(); + if (type.hasAnnotation(poly)) { + type.removeAnnotation(poly); + AnnotationMirror qual; + if (polyInstantiationForQualifierParameter.containsKey(poly)) { + qual = polyInstantiationForQualifierParameter.get(poly); + } else { + qual = pqentry.getValue(); } + type.replaceAnnotation(qual); + } } + } - /** - * This implementation combines the two annotations using the least upper bound. - * - *

{@inheritDoc} - */ - @Override - protected AnnotationMirror combine( - AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == null) { - return a2; - } else if (a2 == null) { - return a1; - } - return qualHierarchy.leastUpperBoundQualifiersOnly(a1, a2); + /** + * This implementation combines the two annotations using the least upper bound. + * + *

{@inheritDoc} + */ + @Override + protected AnnotationMirror combine( + AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == null) { + return a2; + } else if (a2 == null) { + return a1; } + return qualHierarchy.leastUpperBoundQualifiersOnly(a1, a2); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java index a6aa73a69f8..a0944c66701 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java @@ -2,13 +2,11 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; - +import javax.lang.model.element.VariableElement; import org.checkerframework.framework.qual.PolymorphicQualifier; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import javax.lang.model.element.VariableElement; - /** * Interface to implement qualifier polymorphism. * @@ -18,37 +16,37 @@ */ public interface QualifierPolymorphism { - /** - * Resolves polymorphism annotations for the given type. - * - * @param tree the tree associated with the type - * @param type the type to annotate; is side-effected by this method - */ - void resolve(MethodInvocationTree tree, AnnotatedExecutableType type); - - /** - * Resolves polymorphism annotations for the given type. - * - * @param tree the tree associated with the type - * @param type the type to annotate; is side-effected by this method - */ - void resolve(NewClassTree tree, AnnotatedExecutableType type); - - /** - * Resolves polymorphism annotations for the given type. - * - * @param functionalInterface the function type of {@code memberReference} - * @param memberReference the type of a member reference; is side-effected by this method - */ - void resolve( - AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference); - - /** - * Resolves polymorphism annotations for the given field type. - * - * @param field field element to whose poly annotation must be resolved - * @param owner the type of the object whose field is being typed - * @param type type of the field which still has poly annotations - */ - void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type); + /** + * Resolves polymorphism annotations for the given type. + * + * @param tree the tree associated with the type + * @param type the type to annotate; is side-effected by this method + */ + void resolve(MethodInvocationTree tree, AnnotatedExecutableType type); + + /** + * Resolves polymorphism annotations for the given type. + * + * @param tree the tree associated with the type + * @param type the type to annotate; is side-effected by this method + */ + void resolve(NewClassTree tree, AnnotatedExecutableType type); + + /** + * Resolves polymorphism annotations for the given type. + * + * @param functionalInterface the function type of {@code memberReference} + * @param memberReference the type of a member reference; is side-effected by this method + */ + void resolve( + AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference); + + /** + * Resolves polymorphism annotations for the given field type. + * + * @param field field element to whose poly annotation must be resolved + * @param owner the type of the object whose field is being typed + * @param type type of the field which still has poly annotations + */ + void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java index a622fe80978..75d9dfd457f 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java @@ -1,54 +1,52 @@ package org.checkerframework.framework.type.treeannotator; import com.sun.source.tree.Tree; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; - import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import org.checkerframework.framework.type.AnnotatedTypeMirror; /** A ListTreeAnnotator implementation that additionally outputs debugging information. */ public class DebugListTreeAnnotator extends ListTreeAnnotator { - /** The tree kinds to debug. */ - private final Set kinds; - - /** - * Constructs a DebugListTreeAnnotator that does not output any debug information. - * - * @param annotators the annotators for ListTreeAnnotator - */ - public DebugListTreeAnnotator(TreeAnnotator... annotators) { - super(annotators); - kinds = Collections.emptySet(); + /** The tree kinds to debug. */ + private final Set kinds; + + /** + * Constructs a DebugListTreeAnnotator that does not output any debug information. + * + * @param annotators the annotators for ListTreeAnnotator + */ + public DebugListTreeAnnotator(TreeAnnotator... annotators) { + super(annotators); + kinds = Collections.emptySet(); + } + + /** + * Constructs a DebugListTreeAnnotator that outputs debug for the given tree kinds. + * + * @param kinds the tree kinds to output debug info for + * @param annotators the annotators for ListTreeAnnotator + */ + public DebugListTreeAnnotator(Tree.Kind[] kinds, TreeAnnotator... annotators) { + super(annotators); + this.kinds = new HashSet<>(Arrays.asList(kinds)); + } + + @Override + public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { + if (kinds.contains(tree.getKind())) { + System.out.println("DebugListTreeAnnotator input tree: " + tree); + System.out.println(" Initial type: " + type); + for (TreeAnnotator annotator : annotators) { + System.out.println(" Running annotator: " + annotator.getClass()); + annotator.visit(tree, type); + System.out.println(" Current type: " + type); + } + } else { + super.defaultAction(tree, type); } - /** - * Constructs a DebugListTreeAnnotator that outputs debug for the given tree kinds. - * - * @param kinds the tree kinds to output debug info for - * @param annotators the annotators for ListTreeAnnotator - */ - public DebugListTreeAnnotator(Tree.Kind[] kinds, TreeAnnotator... annotators) { - super(annotators); - this.kinds = new HashSet<>(Arrays.asList(kinds)); - } - - @Override - public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { - if (kinds.contains(tree.getKind())) { - System.out.println("DebugListTreeAnnotator input tree: " + tree); - System.out.println(" Initial type: " + type); - for (TreeAnnotator annotator : annotators) { - System.out.println(" Running annotator: " + annotator.getClass()); - annotator.visit(tree, type); - System.out.println(" Current type: " + type); - } - } else { - super.defaultAction(tree, type); - } - - return null; - } + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java index b8ff8473e69..9fea8110876 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java @@ -1,13 +1,11 @@ package org.checkerframework.framework.type.treeannotator; import com.sun.source.tree.Tree; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.framework.type.AnnotatedTypeMirror; /** * ListTreeAnnotator is a TreeVisitor that executes a list of {@link TreeAnnotator} for each tree @@ -21,44 +19,44 @@ */ public class ListTreeAnnotator extends TreeAnnotator { - protected final List annotators; - - /** - * @param annotators the annotators that will be executed for each tree scanned by this - * TreeAnnotator. They are executed in the order passed in. - */ - public ListTreeAnnotator(TreeAnnotator... annotators) { - this(Arrays.asList(annotators)); + protected final List annotators; + + /** + * @param annotators the annotators that will be executed for each tree scanned by this + * TreeAnnotator. They are executed in the order passed in. + */ + public ListTreeAnnotator(TreeAnnotator... annotators) { + this(Arrays.asList(annotators)); + } + + /** + * @param annotators the annotators that will be executed for each tree scanned by this + * TreeAnnotator. They are executed in the order passed in. + */ + public ListTreeAnnotator(List annotators) { + super(null); + List annotatorList = new ArrayList<>(annotators.size()); + for (TreeAnnotator annotator : annotators) { + if (annotator instanceof ListTreeAnnotator) { + annotatorList.addAll(((ListTreeAnnotator) annotator).annotators); + } else { + annotatorList.add(annotator); + } } + this.annotators = Collections.unmodifiableList(annotatorList); + } - /** - * @param annotators the annotators that will be executed for each tree scanned by this - * TreeAnnotator. They are executed in the order passed in. - */ - public ListTreeAnnotator(List annotators) { - super(null); - List annotatorList = new ArrayList<>(annotators.size()); - for (TreeAnnotator annotator : annotators) { - if (annotator instanceof ListTreeAnnotator) { - annotatorList.addAll(((ListTreeAnnotator) annotator).annotators); - } else { - annotatorList.add(annotator); - } - } - this.annotators = Collections.unmodifiableList(annotatorList); + @Override + public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { + for (TreeAnnotator annotator : annotators) { + annotator.visit(tree, type); } - @Override - public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { - for (TreeAnnotator annotator : annotators) { - annotator.visit(tree, type); - } - - return null; - } + return null; + } - @Override - public String toString() { - return "ListTreeAnnotator(" + annotators + ")"; - } + @Override + public String toString() { + return "ListTreeAnnotator(" + annotators + ")"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java index edc7b58a646..9d738da21bf 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java @@ -2,19 +2,6 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; - -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.plumelib.util.StringsPlume; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.EnumMap; @@ -24,9 +11,19 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.plumelib.util.StringsPlume; /** * Adds annotations to a type based on the contents of a tree. This class applies annotations @@ -40,247 +37,244 @@ */ public class LiteralTreeAnnotator extends TreeAnnotator { - /* The following three fields are mappings from a particular AST kind, - * AST Class, or String literal pattern to the set of AnnotationMirrors - * that should be defaulted. - * There can be at most one qualifier per qualifier hierarchy. - * For type systems with single top qualifiers, the sets will always contain - * at most one element. - */ - /** Maps AST kind to the set of AnnotationMirrors that should be defaulted. */ - private final Map treeKinds; + /* The following three fields are mappings from a particular AST kind, + * AST Class, or String literal pattern to the set of AnnotationMirrors + * that should be defaulted. + * There can be at most one qualifier per qualifier hierarchy. + * For type systems with single top qualifiers, the sets will always contain + * at most one element. + */ + /** Maps AST kind to the set of AnnotationMirrors that should be defaulted. */ + private final Map treeKinds; - /** Maps AST class to the set of AnnotationMirrors that should be defaulted. */ - private final Map, AnnotationMirrorSet> treeClasses; + /** Maps AST class to the set of AnnotationMirrors that should be defaulted. */ + private final Map, AnnotationMirrorSet> treeClasses; - /** Maps String literal pattern to the set of AnnotationMirrors that should be defaulted. */ - private final IdentityHashMap stringPatterns; + /** Maps String literal pattern to the set of AnnotationMirrors that should be defaulted. */ + private final IdentityHashMap stringPatterns; - /** The qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; + /** The qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; - /** - * Map of {@link LiteralKind}s to {@link Tree.Kind}s. This is here and not in LiteralKinds - * because LiteralKind is in the checker-qual.jar which cannot depend on classes, such as - * Tree.Kind, that are in the tools.jar - */ - private static final Map literalKindToTreeKind = - new EnumMap<>(LiteralKind.class); + /** + * Map of {@link LiteralKind}s to {@link Tree.Kind}s. This is here and not in LiteralKinds because + * LiteralKind is in the checker-qual.jar which cannot depend on classes, such as Tree.Kind, that + * are in the tools.jar + */ + private static final Map literalKindToTreeKind = + new EnumMap<>(LiteralKind.class); - static { - literalKindToTreeKind.put(LiteralKind.BOOLEAN, Tree.Kind.BOOLEAN_LITERAL); - literalKindToTreeKind.put(LiteralKind.CHAR, Tree.Kind.CHAR_LITERAL); - literalKindToTreeKind.put(LiteralKind.DOUBLE, Tree.Kind.DOUBLE_LITERAL); - literalKindToTreeKind.put(LiteralKind.FLOAT, Tree.Kind.FLOAT_LITERAL); - literalKindToTreeKind.put(LiteralKind.INT, Tree.Kind.INT_LITERAL); - literalKindToTreeKind.put(LiteralKind.LONG, Tree.Kind.LONG_LITERAL); - literalKindToTreeKind.put(LiteralKind.NULL, Tree.Kind.NULL_LITERAL); - literalKindToTreeKind.put(LiteralKind.STRING, Tree.Kind.STRING_LITERAL); - } + static { + literalKindToTreeKind.put(LiteralKind.BOOLEAN, Tree.Kind.BOOLEAN_LITERAL); + literalKindToTreeKind.put(LiteralKind.CHAR, Tree.Kind.CHAR_LITERAL); + literalKindToTreeKind.put(LiteralKind.DOUBLE, Tree.Kind.DOUBLE_LITERAL); + literalKindToTreeKind.put(LiteralKind.FLOAT, Tree.Kind.FLOAT_LITERAL); + literalKindToTreeKind.put(LiteralKind.INT, Tree.Kind.INT_LITERAL); + literalKindToTreeKind.put(LiteralKind.LONG, Tree.Kind.LONG_LITERAL); + literalKindToTreeKind.put(LiteralKind.NULL, Tree.Kind.NULL_LITERAL); + literalKindToTreeKind.put(LiteralKind.STRING, Tree.Kind.STRING_LITERAL); + } - /** - * Creates a {@link LiteralTreeAnnotator} for the given {@code atypeFactory}. - * - * @param atypeFactory the type factory to make an annotator for - */ - public LiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - this.treeKinds = new EnumMap<>(Tree.Kind.class); - this.treeClasses = new HashMap<>(); - this.stringPatterns = new IdentityHashMap<>(); + /** + * Creates a {@link LiteralTreeAnnotator} for the given {@code atypeFactory}. + * + * @param atypeFactory the type factory to make an annotator for + */ + public LiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + this.treeKinds = new EnumMap<>(Tree.Kind.class); + this.treeClasses = new HashMap<>(); + this.stringPatterns = new IdentityHashMap<>(); - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - // Get type qualifiers from the checker. - Set> quals = atypeFactory.getSupportedTypeQualifiers(); + // Get type qualifiers from the checker. + Set> quals = atypeFactory.getSupportedTypeQualifiers(); - // For each qualifier, read the @QualifierForLiterals annotation and put its contents into - // maps. - for (Class qual : quals) { - QualifierForLiterals forLiterals = qual.getAnnotation(QualifierForLiterals.class); - if (forLiterals == null) { - continue; - } + // For each qualifier, read the @QualifierForLiterals annotation and put its contents into + // maps. + for (Class qual : quals) { + QualifierForLiterals forLiterals = qual.getAnnotation(QualifierForLiterals.class); + if (forLiterals == null) { + continue; + } - AnnotationMirror theQual = - AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), qual); - for (LiteralKind literalKind : forLiterals.value()) { - addLiteralKind(literalKind, theQual); - } + AnnotationMirror theQual = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), qual); + for (LiteralKind literalKind : forLiterals.value()) { + addLiteralKind(literalKind, theQual); + } - for (String pattern : forLiterals.stringPatterns()) { - addStringPattern(pattern, theQual); - } + for (String pattern : forLiterals.stringPatterns()) { + addStringPattern(pattern, theQual); + } - if (forLiterals.value().length == 0 && forLiterals.stringPatterns().length == 0) { - addLiteralKind(LiteralKind.ALL, theQual); - } - } + if (forLiterals.value().length == 0 && forLiterals.stringPatterns().length == 0) { + addLiteralKind(LiteralKind.ALL, theQual); + } } + } - /** - * Adds standard qualifiers for literals. Currently sets the null literal to bottom if no other - * default is set for null literals. Also, see {@link - * DefaultForTypeAnnotator#addStandardDefaults()}. - * - * @return this - */ - public LiteralTreeAnnotator addStandardLiteralQualifiers() { - // Set null to bottom if no other qualifier is given. - if (!treeKinds.containsKey(Tree.Kind.NULL_LITERAL)) { - for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { - addLiteralKind(LiteralKind.NULL, bottom); - } - return this; - } - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet defaultForNull = treeKinds.get(Tree.Kind.NULL_LITERAL); - if (tops.size() == defaultForNull.size()) { - return this; - } - for (AnnotationMirror top : tops) { - if (qualHierarchy.findAnnotationInHierarchy(defaultForNull, top) == null) { - defaultForNull.add(qualHierarchy.getBottomAnnotation(top)); - } - } - return this; + /** + * Adds standard qualifiers for literals. Currently sets the null literal to bottom if no other + * default is set for null literals. Also, see {@link + * DefaultForTypeAnnotator#addStandardDefaults()}. + * + * @return this + */ + public LiteralTreeAnnotator addStandardLiteralQualifiers() { + // Set null to bottom if no other qualifier is given. + if (!treeKinds.containsKey(Tree.Kind.NULL_LITERAL)) { + for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { + addLiteralKind(LiteralKind.NULL, bottom); + } + return this; + } + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet defaultForNull = treeKinds.get(Tree.Kind.NULL_LITERAL); + if (tops.size() == defaultForNull.size()) { + return this; } + for (AnnotationMirror top : tops) { + if (qualHierarchy.findAnnotationInHierarchy(defaultForNull, top) == null) { + defaultForNull.add(qualHierarchy.getBottomAnnotation(top)); + } + } + return this; + } - /** - * Added a rule for a particular {@link LiteralKind} - * - * @param literalKind {@code LiteralKind} that should be defaulted to {@code theQual} - * @param theQual the {@code AnnotationMirror} that should be applied to the {@code literalKind} - */ - public void addLiteralKind(LiteralKind literalKind, AnnotationMirror theQual) { - if (literalKind == LiteralKind.ALL) { - for (LiteralKind iterLiteralKind : LiteralKind.allLiteralKinds()) { - addLiteralKind(iterLiteralKind, theQual); - } - } else if (literalKind == LiteralKind.PRIMITIVE) { - for (LiteralKind iterLiteralKind : LiteralKind.primitiveLiteralKinds()) { - addLiteralKind(iterLiteralKind, theQual); - } - } else { - Tree.Kind treeKind = literalKindToTreeKind.get(literalKind); - if (treeKind != null) { - addTreeKind(treeKind, theQual); - } else { - throw new BugInCF("LiteralKind " + literalKind + " is not mapped to a Tree.Kind."); - } - } + /** + * Added a rule for a particular {@link LiteralKind} + * + * @param literalKind {@code LiteralKind} that should be defaulted to {@code theQual} + * @param theQual the {@code AnnotationMirror} that should be applied to the {@code literalKind} + */ + public void addLiteralKind(LiteralKind literalKind, AnnotationMirror theQual) { + if (literalKind == LiteralKind.ALL) { + for (LiteralKind iterLiteralKind : LiteralKind.allLiteralKinds()) { + addLiteralKind(iterLiteralKind, theQual); + } + } else if (literalKind == LiteralKind.PRIMITIVE) { + for (LiteralKind iterLiteralKind : LiteralKind.primitiveLiteralKinds()) { + addLiteralKind(iterLiteralKind, theQual); + } + } else { + Tree.Kind treeKind = literalKindToTreeKind.get(literalKind); + if (treeKind != null) { + addTreeKind(treeKind, theQual); + } else { + throw new BugInCF("LiteralKind " + literalKind + " is not mapped to a Tree.Kind."); + } } + } - /** - * Added a rule for a particular {@link com.sun.source.tree.Tree.Kind} - * - * @param treeKind {@code Tree.Kind} that should be implicited to {@code theQual} - * @param theQual the {@code AnnotationMirror} that should be applied to the {@code treeKind} - */ - private void addTreeKind(Tree.Kind treeKind, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(treeKinds, treeKind, theQual); - if (!res) { - throw new BugInCF( - "LiteralTreeAnnotator: tried to add mapping %s=%s to %s", - treeKind, theQual, treeKinds); - } + /** + * Added a rule for a particular {@link com.sun.source.tree.Tree.Kind} + * + * @param treeKind {@code Tree.Kind} that should be implicited to {@code theQual} + * @param theQual the {@code AnnotationMirror} that should be applied to the {@code treeKind} + */ + private void addTreeKind(Tree.Kind treeKind, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(treeKinds, treeKind, theQual); + if (!res) { + throw new BugInCF( + "LiteralTreeAnnotator: tried to add mapping %s=%s to %s", treeKind, theQual, treeKinds); } + } - /** - * Added a rule for all String literals that match the given pattern. - * - * @param pattern pattern to match Strings against - * @param theQual {@code AnnotationMirror} to apply to Strings that match the pattern - */ - public void addStringPattern(String pattern, AnnotationMirror theQual) { - boolean res = - qualHierarchy.updateMappingToMutableSet( - stringPatterns, Pattern.compile(pattern), theQual); - if (!res) { - throw new BugInCF( - "LiteralTreeAnnotator: invalid update of stringPatterns " - + stringPatterns - + " at " - + pattern - + " with " - + theQual); - } + /** + * Added a rule for all String literals that match the given pattern. + * + * @param pattern pattern to match Strings against + * @param theQual {@code AnnotationMirror} to apply to Strings that match the pattern + */ + public void addStringPattern(String pattern, AnnotationMirror theQual) { + boolean res = + qualHierarchy.updateMappingToMutableSet(stringPatterns, Pattern.compile(pattern), theQual); + if (!res) { + throw new BugInCF( + "LiteralTreeAnnotator: invalid update of stringPatterns " + + stringPatterns + + " at " + + pattern + + " with " + + theQual); } + } - @Override - public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { - if (tree == null || type == null) { - return null; - } + @Override + public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { + if (tree == null || type == null) { + return null; + } - // If this tree's kind is in treeKinds, annotate the type. + // If this tree's kind is in treeKinds, annotate the type. - // If this tree's class or any of its interfaces are in treeClasses, annotate the type, and - // if it was an interface add a mapping for it to treeClasses. - if (treeKinds.containsKey(tree.getKind())) { - AnnotationMirrorSet fnd = treeKinds.get(tree.getKind()); - type.addMissingAnnotations(fnd); - } else if (!treeClasses.isEmpty()) { - Class t = tree.getClass(); - if (treeClasses.containsKey(t)) { - AnnotationMirrorSet fnd = treeClasses.get(t); - type.addMissingAnnotations(fnd); - } - for (Class c : t.getInterfaces()) { - if (treeClasses.containsKey(c)) { - AnnotationMirrorSet fnd = treeClasses.get(c); - type.addMissingAnnotations(fnd); - treeClasses.put(t, treeClasses.get(c)); - } - } + // If this tree's class or any of its interfaces are in treeClasses, annotate the type, and + // if it was an interface add a mapping for it to treeClasses. + if (treeKinds.containsKey(tree.getKind())) { + AnnotationMirrorSet fnd = treeKinds.get(tree.getKind()); + type.addMissingAnnotations(fnd); + } else if (!treeClasses.isEmpty()) { + Class t = tree.getClass(); + if (treeClasses.containsKey(t)) { + AnnotationMirrorSet fnd = treeClasses.get(t); + type.addMissingAnnotations(fnd); + } + for (Class c : t.getInterfaces()) { + if (treeClasses.containsKey(c)) { + AnnotationMirrorSet fnd = treeClasses.get(c); + type.addMissingAnnotations(fnd); + treeClasses.put(t, treeClasses.get(c)); } - return null; + } } + return null; + } - /** Go through the string patterns and add the greatest lower bound of all matching patterns. */ - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!stringPatterns.isEmpty() && tree.getKind() == Tree.Kind.STRING_LITERAL) { - List> matches = new ArrayList<>(); - List> nonMatches = new ArrayList<>(); + /** Go through the string patterns and add the greatest lower bound of all matching patterns. */ + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!stringPatterns.isEmpty() && tree.getKind() == Tree.Kind.STRING_LITERAL) { + List> matches = new ArrayList<>(); + List> nonMatches = new ArrayList<>(); - String string = (String) tree.getValue(); - for (Map.Entry entry : stringPatterns.entrySet()) { - Pattern pattern = entry.getKey(); - AnnotationMirrorSet sam = entry.getValue(); - if (pattern.matcher(string).matches()) { - matches.add(sam); - } else { - nonMatches.add(sam); - } - } - if (!matches.isEmpty()) { - TypeMirror tm = type.getUnderlyingType(); - Set res = matches.get(0); - for (Set sam : matches) { - res = qualHierarchy.greatestLowerBoundsShallow(res, tm, sam, tm); - } - // Verify that res is not a subtype of any type in nonMatches - for (Set sam : nonMatches) { - if (qualHierarchy.isSubtypeShallow(res, sam, tm)) { - String matchesOnePerLine = ""; - for (Set match : matches) { - matchesOnePerLine += System.lineSeparator() + " " + match; - } - throw new BugInCF( - StringsPlume.joinLines( - "Bug in @QualifierForLiterals(stringpatterns=...) in type" - + " hierarchy definition:", - " the glb of `matches` for \"" + string + "\" is " + res, - " which is a subtype of " + sam, - " whose pattern does not match \"" + string + "\".", - " matches = " + matchesOnePerLine, - " nonMatches = " + nonMatches)); - } - } - type.addAnnotations(res); + String string = (String) tree.getValue(); + for (Map.Entry entry : stringPatterns.entrySet()) { + Pattern pattern = entry.getKey(); + AnnotationMirrorSet sam = entry.getValue(); + if (pattern.matcher(string).matches()) { + matches.add(sam); + } else { + nonMatches.add(sam); + } + } + if (!matches.isEmpty()) { + TypeMirror tm = type.getUnderlyingType(); + Set res = matches.get(0); + for (Set sam : matches) { + res = qualHierarchy.greatestLowerBoundsShallow(res, tm, sam, tm); + } + // Verify that res is not a subtype of any type in nonMatches + for (Set sam : nonMatches) { + if (qualHierarchy.isSubtypeShallow(res, sam, tm)) { + String matchesOnePerLine = ""; + for (Set match : matches) { + matchesOnePerLine += System.lineSeparator() + " " + match; } + throw new BugInCF( + StringsPlume.joinLines( + "Bug in @QualifierForLiterals(stringpatterns=...) in type" + + " hierarchy definition:", + " the glb of `matches` for \"" + string + "\" is " + res, + " which is a subtype of " + sam, + " whose pattern does not match \"" + string + "\".", + " matches = " + matchesOnePerLine, + " nonMatches = " + nonMatches)); + } } - return super.visitLiteral(tree, type); + type.addAnnotations(res); + } } + return super.visitLiteral(tree, type); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java index 238b0e662ba..3e125f95f34 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java @@ -12,7 +12,11 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -26,13 +30,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * {@link PropagationTreeAnnotator} adds qualifiers to types where the resulting type is a function * of an input type, e.g. the result of a binary operation is a LUB of the type of expressions in @@ -47,348 +44,340 @@ */ public class PropagationTreeAnnotator extends TreeAnnotator { - private final QualifierHierarchy qualHierarchy; - - /** Creates a {@link PropagationTreeAnnotator} for the given {@code atypeFactory}. */ - public PropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + private final QualifierHierarchy qualHierarchy; + + /** Creates a {@link PropagationTreeAnnotator} for the given {@code atypeFactory}. */ + public PropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + } + + /** + * Whether to use the assignment context when computing the type of a new array expression. This + * is a hack to prevent infinite recursion if computing the type of the assignment context + * includes computing the type of the right-hand side of the assignment. This happens when the + * assignment is the pseudo-assignment of a method argument to a formal parameter. + */ + private boolean useAssignmentContext = true; + + /** + * A mapping from {@code MethodInvocationTree} to the type of its declaration adapted to the call + * site. This is a cache used when getting the type of a new array expression that is an argument + * to a method. Getting the call-site-adapted type of a method also gets the type of all the + * arguments at the call site. (This happens both for resolving polymorphic methods and for method + * type argument inference.) {@link #useAssignmentContext} is used to prevent infinite recursion + * and this cache is used to improve performance. + */ + private final Map methodInvocationToType = + CollectionsPlume.createLruCache(300); + + @Override + public Void visitNewArray(NewArrayTree arrayTree, AnnotatedTypeMirror arrayType) { + assert arrayType.getKind() == TypeKind.ARRAY + : "PropagationTreeAnnotator.visitNewArray: should be an array type"; + + AnnotatedTypeMirror componentType = ((AnnotatedArrayType) arrayType).getComponentType(); + TypeMirror componentTM = componentType.getUnderlyingType(); + + // prev is the lub of the initializers if they exist, otherwise the current component type. + Set prev = null; + if (arrayTree.getInitializers() != null && !arrayTree.getInitializers().isEmpty()) { + // We have initializers, either with or without an array type. + + // TODO (issue #599): This only works at the top level. It should work at all levels of + // the array. + for (ExpressionTree init : arrayTree.getInitializers()) { + AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); + // initType might be a typeVariable, so use effectiveAnnotations. + AnnotationMirrorSet annos = initType.getEffectiveAnnotations(); + + prev = + (prev == null) + ? annos + : qualHierarchy.leastUpperBoundsShallow( + prev, componentTM, annos, initType.getUnderlyingType()); + } + } else { + prev = componentType.getAnnotations(); } - /** - * Whether to use the assignment context when computing the type of a new array expression. This - * is a hack to prevent infinite recursion if computing the type of the assignment context - * includes computing the type of the right-hand side of the assignment. This happens when the - * assignment is the pseudo-assignment of a method argument to a formal parameter. - */ - private boolean useAssignmentContext = true; - - /** - * A mapping from {@code MethodInvocationTree} to the type of its declaration adapted to the - * call site. This is a cache used when getting the type of a new array expression that is an - * argument to a method. Getting the call-site-adapted type of a method also gets the type of - * all the arguments at the call site. (This happens both for resolving polymorphic methods and - * for method type argument inference.) {@link #useAssignmentContext} is used to prevent - * infinite recursion and this cache is used to improve performance. - */ - private final Map methodInvocationToType = - CollectionsPlume.createLruCache(300); - - @Override - public Void visitNewArray(NewArrayTree arrayTree, AnnotatedTypeMirror arrayType) { - assert arrayType.getKind() == TypeKind.ARRAY - : "PropagationTreeAnnotator.visitNewArray: should be an array type"; - - AnnotatedTypeMirror componentType = ((AnnotatedArrayType) arrayType).getComponentType(); - TypeMirror componentTM = componentType.getUnderlyingType(); - - // prev is the lub of the initializers if they exist, otherwise the current component type. - Set prev = null; - if (arrayTree.getInitializers() != null && !arrayTree.getInitializers().isEmpty()) { - // We have initializers, either with or without an array type. - - // TODO (issue #599): This only works at the top level. It should work at all levels of - // the array. - for (ExpressionTree init : arrayTree.getInitializers()) { - AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); - // initType might be a typeVariable, so use effectiveAnnotations. - AnnotationMirrorSet annos = initType.getEffectiveAnnotations(); - - prev = - (prev == null) - ? annos - : qualHierarchy.leastUpperBoundsShallow( - prev, componentTM, annos, initType.getUnderlyingType()); - } - } else { - prev = componentType.getAnnotations(); + assert prev != null + : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers"; + + TreePath path = atypeFactory.getPath(arrayTree); + AnnotatedTypeMirror contextType = null; + if (path != null && path.getParentPath() != null) { + Tree parentTree = path.getParentPath().getLeaf(); + if (parentTree.getKind() == Tree.Kind.ASSIGNMENT) { + Tree var = ((AssignmentTree) parentTree).getVariable(); + contextType = atypeFactory.getAnnotatedType(var); + } else if (parentTree.getKind() == Tree.Kind.VARIABLE) { + if (!TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) parentTree)) { + contextType = atypeFactory.getAnnotatedType(parentTree); } - - assert prev != null - : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers"; - - TreePath path = atypeFactory.getPath(arrayTree); - AnnotatedTypeMirror contextType = null; - if (path != null && path.getParentPath() != null) { - Tree parentTree = path.getParentPath().getLeaf(); - if (parentTree.getKind() == Tree.Kind.ASSIGNMENT) { - Tree var = ((AssignmentTree) parentTree).getVariable(); - contextType = atypeFactory.getAnnotatedType(var); - } else if (parentTree.getKind() == Tree.Kind.VARIABLE) { - if (!TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) parentTree)) { - contextType = atypeFactory.getAnnotatedType(parentTree); - } - } else if (parentTree instanceof CompoundAssignmentTree) { - Tree var = ((CompoundAssignmentTree) parentTree).getVariable(); - contextType = atypeFactory.getAnnotatedType(var); - } else if (parentTree.getKind() == Tree.Kind.RETURN) { - Tree methodTree = TreePathUtil.enclosingMethodOrLambda(path.getParentPath()); - if (methodTree.getKind() == Tree.Kind.METHOD) { - AnnotatedExecutableType methodType = - atypeFactory.getAnnotatedType((MethodTree) methodTree); - contextType = methodType.getReturnType(); - } - } else if (parentTree.getKind() == Tree.Kind.METHOD_INVOCATION - && useAssignmentContext) { - MethodInvocationTree methodInvocationTree = (MethodInvocationTree) parentTree; - useAssignmentContext = false; - AnnotatedExecutableType m; - try { - if (atypeFactory.shouldCache - && methodInvocationToType.containsKey(methodInvocationTree)) { - m = methodInvocationToType.get(methodInvocationTree); - } else { - m = atypeFactory.methodFromUse(methodInvocationTree).executableType; - if (atypeFactory.shouldCache) { - methodInvocationToType.put(methodInvocationTree, m); - } - } - } finally { - useAssignmentContext = true; - } - - int parametersCount = m.getParameterTypes().size(); - boolean isVarargs = m.getElement().isVarArgs(); - - // If the method accepts varargs, we handle the vararg parameter after this - // for-loop. - for (int i = 0; i < parametersCount; i++) { - @SuppressWarnings("interning") // Tree must be exactly the same. - boolean foundArgument = methodInvocationTree.getArguments().get(i) == arrayTree; - if (foundArgument) { - contextType = m.getParameterTypes().get(i); - break; - } - } - - if (isVarargs && contextType == null) { - // The previous for-loop did not find any arguments matching the - // new array tree, thus the tree has to be an argument for varargs. - // - // The tree could be an artificial tree when the code doesn't provide - // any explicit arguments for the varargs (i.e., an empty array). - // So we don't try getting the argument from methodInvocationTree - // to avoid out-of-bound exception. - contextType = m.getVarargType(); - } - } + } else if (parentTree instanceof CompoundAssignmentTree) { + Tree var = ((CompoundAssignmentTree) parentTree).getVariable(); + contextType = atypeFactory.getAnnotatedType(var); + } else if (parentTree.getKind() == Tree.Kind.RETURN) { + Tree methodTree = TreePathUtil.enclosingMethodOrLambda(path.getParentPath()); + if (methodTree.getKind() == Tree.Kind.METHOD) { + AnnotatedExecutableType methodType = + atypeFactory.getAnnotatedType((MethodTree) methodTree); + contextType = methodType.getReturnType(); } - Set post; - - if (contextType instanceof AnnotatedArrayType) { - AnnotatedTypeMirror contextComponentType = - ((AnnotatedArrayType) contextType).getComponentType(); - // Only compare the qualifiers that existed in the array type. - // Defaulting wasn't performed yet, so prev might have fewer qualifiers than - // contextComponentType, which would cause a failure. - // TODO: better solution? - TypeMirror contextCTM = contextComponentType.getUnderlyingType(); - boolean prevIsSubtype = true; - for (AnnotationMirror am : prev) { - if (contextComponentType.hasAnnotationInHierarchy(am) - && !this.qualHierarchy.isSubtypeShallow( - am, - contextCTM, - contextComponentType.getAnnotationInHierarchy(am), - contextCTM)) { - prevIsSubtype = false; - } + } else if (parentTree.getKind() == Tree.Kind.METHOD_INVOCATION && useAssignmentContext) { + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) parentTree; + useAssignmentContext = false; + AnnotatedExecutableType m; + try { + if (atypeFactory.shouldCache + && methodInvocationToType.containsKey(methodInvocationTree)) { + m = methodInvocationToType.get(methodInvocationTree); + } else { + m = atypeFactory.methodFromUse(methodInvocationTree).executableType; + if (atypeFactory.shouldCache) { + methodInvocationToType.put(methodInvocationTree, m); } - // TODO: checking conformance of component kinds is a basic sanity check - // It fails for array initializer expressions. Those should be handled nicer. - if (contextComponentType.getKind() == componentType.getKind() - && (prev.isEmpty() - || (!contextComponentType.getAnnotations().isEmpty() - && prevIsSubtype))) { - post = contextComponentType.getAnnotations(); - } else { - // The type of the array initializers is incompatible with the context type! - // Somebody else will complain. - post = prev; - } - } else { - // No context is available - simply use what we have. - post = prev; + } + } finally { + useAssignmentContext = true; } - // TODO (issue #599): This only works at the top level. It should work at all levels of - // the array. - addAnnoOrBound(componentType, post); - - return null; - } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - return null; + int parametersCount = m.getParameterTypes().size(); + boolean isVarargs = m.getElement().isVarArgs(); + + // If the method accepts varargs, we handle the vararg parameter after this + // for-loop. + for (int i = 0; i < parametersCount; i++) { + @SuppressWarnings("interning") // Tree must be exactly the same. + boolean foundArgument = methodInvocationTree.getArguments().get(i) == arrayTree; + if (foundArgument) { + contextType = m.getParameterTypes().get(i); + break; + } } - IPair argTypes = - atypeFactory.compoundAssignmentTreeArgTypes(tree); - AnnotatedTypeMirror rhs = argTypes.first; - AnnotatedTypeMirror lhs = argTypes.second; - Set lubs = - qualHierarchy.leastUpperBoundsShallow( - rhs.getEffectiveAnnotations(), - rhs.getUnderlyingType(), - lhs.getEffectiveAnnotations(), - lhs.getUnderlyingType()); - type.addMissingAnnotations(lubs); - - return null; - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - // Also, calling getAnnotatedType on the left and right operands is potentially - // expensive. - return null; + if (isVarargs && contextType == null) { + // The previous for-loop did not find any arguments matching the + // new array tree, thus the tree has to be an argument for varargs. + // + // The tree could be an artificial tree when the code doesn't provide + // any explicit arguments for the varargs (i.e., an empty array). + // So we don't try getting the argument from methodInvocationTree + // to avoid out-of-bound exception. + contextType = m.getVarargType(); } - - if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { - return null; - } - - IPair argTypes = - atypeFactory.binaryTreeArgTypes(tree); - AnnotatedTypeMirror type1 = argTypes.first; - AnnotatedTypeMirror type2 = argTypes.second; - Set lubs = - qualHierarchy.leastUpperBoundsShallow( - type1.getEffectiveAnnotations(), - type1.getUnderlyingType(), - type2.getEffectiveAnnotations(), - type2.getUnderlyingType()); - - if (TreeUtils.isBinaryComparison(tree)) { - // When we have binary comparison, the result type (boolean) can be different - // from the operands' types. So we need to apply the bounds of boolean to the - // lubs. - lubs = atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), lubs); + } + } + Set post; + + if (contextType instanceof AnnotatedArrayType) { + AnnotatedTypeMirror contextComponentType = + ((AnnotatedArrayType) contextType).getComponentType(); + // Only compare the qualifiers that existed in the array type. + // Defaulting wasn't performed yet, so prev might have fewer qualifiers than + // contextComponentType, which would cause a failure. + // TODO: better solution? + TypeMirror contextCTM = contextComponentType.getUnderlyingType(); + boolean prevIsSubtype = true; + for (AnnotationMirror am : prev) { + if (contextComponentType.hasAnnotationInHierarchy(am) + && !this.qualHierarchy.isSubtypeShallow( + am, contextCTM, contextComponentType.getAnnotationInHierarchy(am), contextCTM)) { + prevIsSubtype = false; } + } + // TODO: checking conformance of component kinds is a basic sanity check + // It fails for array initializer expressions. Those should be handled nicer. + if (contextComponentType.getKind() == componentType.getKind() + && (prev.isEmpty() + || (!contextComponentType.getAnnotations().isEmpty() && prevIsSubtype))) { + post = contextComponentType.getAnnotations(); + } else { + // The type of the array initializers is incompatible with the context type! + // Somebody else will complain. + post = prev; + } + } else { + // No context is available - simply use what we have. + post = prev; + } + // TODO (issue #599): This only works at the top level. It should work at all levels of + // the array. + addAnnoOrBound(componentType, post); + + return null; + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + return null; + } - log( - "%s PTA.visitBinary(%s, %s)%n argTypes=%s%n lubs=%s%n", - atypeFactory.getClass().getSimpleName(), tree, type, argTypes, lubs); - - type.addMissingAnnotations(lubs); - log("PTA.visitBinary(%s, ...): final type = %s%n", tree, type); - - return null; + IPair argTypes = + atypeFactory.compoundAssignmentTreeArgTypes(tree); + AnnotatedTypeMirror rhs = argTypes.first; + AnnotatedTypeMirror lhs = argTypes.second; + Set lubs = + qualHierarchy.leastUpperBoundsShallow( + rhs.getEffectiveAnnotations(), + rhs.getUnderlyingType(), + lhs.getEffectiveAnnotations(), + lhs.getUnderlyingType()); + type.addMissingAnnotations(lubs); + + return null; + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + // Also, calling getAnnotatedType on the left and right operands is potentially + // expensive. + return null; } - @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - return null; - } + if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { + return null; + } - AnnotatedTypeMirror exp = atypeFactory.getAnnotatedType(tree.getExpression()); - type.addMissingAnnotations(exp.getAnnotations()); - return null; + IPair argTypes = + atypeFactory.binaryTreeArgTypes(tree); + AnnotatedTypeMirror type1 = argTypes.first; + AnnotatedTypeMirror type2 = argTypes.second; + Set lubs = + qualHierarchy.leastUpperBoundsShallow( + type1.getEffectiveAnnotations(), + type1.getUnderlyingType(), + type2.getEffectiveAnnotations(), + type2.getUnderlyingType()); + + if (TreeUtils.isBinaryComparison(tree)) { + // When we have binary comparison, the result type (boolean) can be different + // from the operands' types. So we need to apply the bounds of boolean to the + // lubs. + lubs = atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), lubs); } - /* - * TODO: would this make sense in general? - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, AnnotatedTypeMirror type) { - if (!type.isAnnotated()) { - AnnotatedTypeMirror a = typeFactory.getAnnotatedType(tree.getTrueExpression()); - AnnotatedTypeMirror b = typeFactory.getAnnotatedType(tree.getFalseExpression()); - AnnotationMirrorSet lubs = qualHierarchy.leastUpperBounds(a.getEffectiveAnnotations(), b.getEffectiveAnnotations()); - type.replaceAnnotations(lubs); - } - return super.visitConditionalExpression(tree, type); - }*/ + log( + "%s PTA.visitBinary(%s, %s)%n argTypes=%s%n lubs=%s%n", + atypeFactory.getClass().getSimpleName(), tree, type, argTypes, lubs); - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { - return null; - } + type.addMissingAnnotations(lubs); + log("PTA.visitBinary(%s, ...): final type = %s%n", tree, type); - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type is already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - log("PTA.visitTypeCast(%s, %s): hasPrimaryAnnotationInAllHierarchies%n", tree, type); - return null; - } + return null; + } - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); - if (type.getKind() == TypeKind.TYPEVAR) { - if (exprType.getKind() == TypeKind.TYPEVAR) { - // If both types are type variables, take the direct annotations. - type.addMissingAnnotations(exprType.getAnnotations()); - } - // else do nothing. - } else { - // Use effective annotations from the expression, to get upper bound of type variables. - AnnotationMirrorSet expressionAnnos = exprType.getEffectiveAnnotations(); - log( - "PTA.visitTypeCast(%s, %s): getEffectiveAnnotations(%s) = %s%n", - tree, type, exprType, expressionAnnos); - - TypeKind castKind = type.getPrimitiveKind(); - if (castKind != null) { - TypeKind exprKind = exprType.getPrimitiveKind(); - if (exprKind != null) { - switch (TypeKindUtils.getPrimitiveConversionKind(exprKind, castKind)) { - case WIDENING: - expressionAnnos = - atypeFactory.getWidenedAnnotations( - expressionAnnos, exprKind, castKind); - break; - case NARROWING: - atypeFactory.getNarrowedAnnotations( - expressionAnnos, exprKind, castKind); - break; - case SAME: - // Nothing to do - break; - } - } - } + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + return null; + } - // If the qualifier on the expression type is a supertype of the qualifier upper bound - // of the cast type, then apply the bound as the default qualifier rather than the - // expression qualifier. - addAnnoOrBound(type, expressionAnnos); - } + AnnotatedTypeMirror exp = atypeFactory.getAnnotatedType(tree.getExpression()); + type.addMissingAnnotations(exp.getAnnotations()); + return null; + } + + /* + * TODO: would this make sense in general? + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, AnnotatedTypeMirror type) { + if (!type.isAnnotated()) { + AnnotatedTypeMirror a = typeFactory.getAnnotatedType(tree.getTrueExpression()); + AnnotatedTypeMirror b = typeFactory.getAnnotatedType(tree.getFalseExpression()); + AnnotationMirrorSet lubs = qualHierarchy.leastUpperBounds(a.getEffectiveAnnotations(), b.getEffectiveAnnotations()); + type.replaceAnnotations(lubs); + } + return super.visitConditionalExpression(tree, type); + }*/ + + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { + return null; + } - return null; + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type is already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + log("PTA.visitTypeCast(%s, %s): hasPrimaryAnnotationInAllHierarchies%n", tree, type); + return null; } - private boolean hasPrimaryAnnotationInAllHierarchies(AnnotatedTypeMirror type) { - boolean annotated = true; - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (type.getEffectiveAnnotationInHierarchy(top) == null) { - annotated = false; - } + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); + if (type.getKind() == TypeKind.TYPEVAR) { + if (exprType.getKind() == TypeKind.TYPEVAR) { + // If both types are type variables, take the direct annotations. + type.addMissingAnnotations(exprType.getAnnotations()); + } + // else do nothing. + } else { + // Use effective annotations from the expression, to get upper bound of type variables. + AnnotationMirrorSet expressionAnnos = exprType.getEffectiveAnnotations(); + log( + "PTA.visitTypeCast(%s, %s): getEffectiveAnnotations(%s) = %s%n", + tree, type, exprType, expressionAnnos); + + TypeKind castKind = type.getPrimitiveKind(); + if (castKind != null) { + TypeKind exprKind = exprType.getPrimitiveKind(); + if (exprKind != null) { + switch (TypeKindUtils.getPrimitiveConversionKind(exprKind, castKind)) { + case WIDENING: + expressionAnnos = + atypeFactory.getWidenedAnnotations(expressionAnnos, exprKind, castKind); + break; + case NARROWING: + atypeFactory.getNarrowedAnnotations(expressionAnnos, exprKind, castKind); + break; + case SAME: + // Nothing to do + break; + } } - return annotated; + } + + // If the qualifier on the expression type is a supertype of the qualifier upper bound + // of the cast type, then apply the bound as the default qualifier rather than the + // expression qualifier. + addAnnoOrBound(type, expressionAnnos); } - /** - * Adds the qualifiers in {@code annos} to {@code type} that are below the qualifier upper bound - * of type and for which type does not already have annotation in the same hierarchy. If a - * qualifier in {@code annos} is above the bound, then the bound is added to {@code type} - * instead. - * - * @param type annotations are added to this type - * @param annos annotations to add to type - */ - private void addAnnoOrBound(AnnotatedTypeMirror type, Set annos) { - log("addAnnoOrBound(%s, %s)%n", type, annos); - AnnotationMirrorSet annosToAdd = - atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), annos); - type.addMissingAnnotations(annosToAdd); - log("addAnnoOrBound#2(%s, %s)%n", type, annos); + return null; + } + + private boolean hasPrimaryAnnotationInAllHierarchies(AnnotatedTypeMirror type) { + boolean annotated = true; + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (type.getEffectiveAnnotationInHierarchy(top) == null) { + annotated = false; + } } + return annotated; + } + + /** + * Adds the qualifiers in {@code annos} to {@code type} that are below the qualifier upper bound + * of type and for which type does not already have annotation in the same hierarchy. If a + * qualifier in {@code annos} is above the bound, then the bound is added to {@code type} instead. + * + * @param type annotations are added to this type + * @param annos annotations to add to type + */ + private void addAnnoOrBound(AnnotatedTypeMirror type, Set annos) { + log("addAnnoOrBound(%s, %s)%n", type, annos); + AnnotationMirrorSet annosToAdd = + atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), annos); + type.addMissingAnnotations(annosToAdd); + log("addAnnoOrBound#2(%s, %s)%n", type, annos); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java index c97d61c5f77..dcb88343745 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java @@ -4,7 +4,6 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.util.SimpleTreeVisitor; - import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -23,64 +22,64 @@ */ public abstract class TreeAnnotator extends SimpleTreeVisitor { - /** - * Whether to output verbose, low-level debugging messages. Also see {@code - * GenericAnnotatedTypeFactory.debug}. - */ - private static final boolean debug = false; + /** + * Whether to output verbose, low-level debugging messages. Also see {@code + * GenericAnnotatedTypeFactory.debug}. + */ + private static final boolean debug = false; - /** The type factory. */ - protected final AnnotatedTypeFactory atypeFactory; + /** The type factory. */ + protected final AnnotatedTypeFactory atypeFactory; - /** - * Create a new TreeAnnotator. - * - * @param atypeFactory the type factory - */ - protected TreeAnnotator(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** + * Create a new TreeAnnotator. + * + * @param atypeFactory the type factory + */ + protected TreeAnnotator(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - /** - * Output a message, if logging is on. - * - * @param format a format string - * @param args arguments to the format string - */ - @FormatMethod - protected void log(String format, Object... args) { - if (debug) { - SystemPlume.sleep(1); // logging can interleave with typechecker output - System.out.printf(format, args); - } + /** + * Output a message, if logging is on. + * + * @param format a format string + * @param args arguments to the format string + */ + @FormatMethod + protected void log(String format, Object... args) { + if (debug) { + SystemPlume.sleep(1); // logging can interleave with typechecker output + System.out.printf(format, args); } + } - /** - * This method is not called when checking a method invocation against its declaration. So, - * instead of overriding this method, override TypeAnnotator.visitExecutable. - * TypeAnnotator.visitExecutable is called both when checking method declarations and method - * invocations. - * - * @see org.checkerframework.framework.type.typeannotator.TypeAnnotator - */ - @Override - public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { - return super.visitMethod(tree, p); - } + /** + * This method is not called when checking a method invocation against its declaration. So, + * instead of overriding this method, override TypeAnnotator.visitExecutable. + * TypeAnnotator.visitExecutable is called both when checking method declarations and method + * invocations. + * + * @see org.checkerframework.framework.type.typeannotator.TypeAnnotator + */ + @Override + public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { + return super.visitMethod(tree, p); + } - /** - * When overriding this method, getAnnotatedType on the left and right operands should only be - * called when absolutely necessary. Otherwise, the checker will be very slow on heavily nested - * binary trees. (For example, a + b + c + d + e + f + g + h.) - * - *

If a checker's performance is still too slow, the types of binary trees could be computed - * in a subclass of {@link org.checkerframework.framework.flow.CFTransfer}. When computing the - * types in a transfer, look up the value in the store rather than the AnnotatedTypeFactory. - * Then this method should annotate binary trees with top so that the type applied in the - * transfer is always a subtype of the type the AnnotatedTypeFactory computes. - */ - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror mirror) { - return super.visitBinary(tree, mirror); - } + /** + * When overriding this method, getAnnotatedType on the left and right operands should only be + * called when absolutely necessary. Otherwise, the checker will be very slow on heavily nested + * binary trees. (For example, a + b + c + d + e + f + g + h.) + * + *

If a checker's performance is still too slow, the types of binary trees could be computed in + * a subclass of {@link org.checkerframework.framework.flow.CFTransfer}. When computing the types + * in a transfer, look up the value in the store rather than the AnnotatedTypeFactory. Then this + * method should annotate binary trees with top so that the type applied in the transfer is always + * a subtype of the type the AnnotatedTypeFactory computes. + */ + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror mirror) { + return super.visitBinary(tree, mirror); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java index ba3e69a0fef..60865e96593 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java @@ -1,20 +1,5 @@ package org.checkerframework.framework.type.typeannotator; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.TypeSystemError; -import org.checkerframework.javacutil.TypesUtils; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.EnumMap; @@ -25,13 +10,26 @@ import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypeAnnotationUtils; +import org.checkerframework.javacutil.TypeSystemError; +import org.checkerframework.javacutil.TypesUtils; /** * Adds annotations to a type based on the use of a type. This class applies annotations specified @@ -47,329 +45,320 @@ */ public class DefaultForTypeAnnotator extends TypeAnnotator { - /** Map from {@link TypeKind} to annotations. */ - private final Map typeKinds; + /** Map from {@link TypeKind} to annotations. */ + private final Map typeKinds; - /** Map from {@link AnnotatedTypeMirror} classes to annotations. */ - private final Map, AnnotationMirrorSet> atmClasses; + /** Map from {@link AnnotatedTypeMirror} classes to annotations. */ + private final Map, AnnotationMirrorSet> atmClasses; - /** Map from fully qualified class name strings to annotations. */ - private final Map types; + /** Map from fully qualified class name strings to annotations. */ + private final Map types; - /** - * A list where each element associates an annotation with name regexes and name exception - * regexes. - */ - private final ListOfNameRegexes listOfNameRegexes; + /** + * A list where each element associates an annotation with name regexes and name exception + * regexes. + */ + private final ListOfNameRegexes listOfNameRegexes; - /** {@link QualifierHierarchy} */ - private final QualifierHierarchy qualHierarchy; + /** {@link QualifierHierarchy} */ + private final QualifierHierarchy qualHierarchy; - /** - * Creates a {@link DefaultForTypeAnnotator} from the given checker, using that checker to - * determine the annotations that are in the type hierarchy. - */ - public DefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - this.typeKinds = new EnumMap<>(TypeKind.class); - this.atmClasses = new HashMap<>(); - this.types = new HashMap<>(); - this.listOfNameRegexes = new ListOfNameRegexes(); - - this.qualHierarchy = typeFactory.getQualifierHierarchy(); - - // Get type qualifiers from the checker. - Set> quals = typeFactory.getSupportedTypeQualifiers(); - - // For each qualifier, read the @DefaultFor annotation and put its types, kinds, and names - // into maps. - for (Class qual : quals) { - DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); - if (defaultFor == null) { - continue; - } - - AnnotationMirror theQual = - AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); - - for (org.checkerframework.framework.qual.TypeKind typeKind : defaultFor.typeKinds()) { - TypeKind mappedTk = mapTypeKinds(typeKind); - addTypeKind(mappedTk, theQual); - } - - for (Class typeName : defaultFor.types()) { - addTypes(typeName, theQual); - } - - listOfNameRegexes.add(theQual, defaultFor); - } + /** + * Creates a {@link DefaultForTypeAnnotator} from the given checker, using that checker to + * determine the annotations that are in the type hierarchy. + */ + public DefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + this.typeKinds = new EnumMap<>(TypeKind.class); + this.atmClasses = new HashMap<>(); + this.types = new HashMap<>(); + this.listOfNameRegexes = new ListOfNameRegexes(); + + this.qualHierarchy = typeFactory.getQualifierHierarchy(); + + // Get type qualifiers from the checker. + Set> quals = typeFactory.getSupportedTypeQualifiers(); + + // For each qualifier, read the @DefaultFor annotation and put its types, kinds, and names + // into maps. + for (Class qual : quals) { + DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); + if (defaultFor == null) { + continue; + } + + AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); + + for (org.checkerframework.framework.qual.TypeKind typeKind : defaultFor.typeKinds()) { + TypeKind mappedTk = mapTypeKinds(typeKind); + addTypeKind(mappedTk, theQual); + } + + for (Class typeName : defaultFor.types()) { + addTypes(typeName, theQual); + } + + listOfNameRegexes.add(theQual, defaultFor); + } + } + + /** + * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link + * javax.lang.model.type.TypeKind}. + * + * @param typeKind the Checker Framework TypeKind + * @return the javax TypeKind + */ + private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { + return TypeKind.valueOf(typeKind.name()); + } + + /** Add default qualifier, {@code theQual}, for the given TypeKind. */ + public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); + if (!res) { + throw new BugInCF( + "TypeAnnotator: invalid update of typeKinds " + + typeKinds + + " at " + + typeKind + + " with " + + theQual); + } + } + + /** Add default qualifier, {@code theQual}, for the given {@link AnnotatedTypeMirror} class. */ + public void addAtmClass( + Class typeClass, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(atmClasses, typeClass, theQual); + if (!res) { + throw new BugInCF( + "TypeAnnotator: invalid update of atmClasses " + + atmClasses + + " at " + + typeClass + + " with " + + theQual); + } + } + + /** Add default qualifier, {@code theQual}, for the given type. */ + public void addTypes(Class clazz, AnnotationMirror theQual) { + String typeNameString = clazz.getCanonicalName(); + boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); + if (!res) { + throw new BugInCF( + "TypeAnnotator: invalid update of types " + types + " at " + clazz + " with " + theQual); + } + } + + @Override + protected Void scan(AnnotatedTypeMirror type, Void p) { + // If the type's fully-qualified name is in the appropriate map, annotate the type. Do this + // before looking at kind or class, as this information is more specific. + + String qname; + // We have to use the type name without annotations for the lookup. + TypeMirror unannotatedType = TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()); + if (type.getKind() == TypeKind.DECLARED) { + qname = TypesUtils.getQualifiedName((DeclaredType) unannotatedType); + } else if (type.getKind().isPrimitive()) { + qname = unannotatedType.toString(); + } else { + qname = null; } - /** - * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link - * javax.lang.model.type.TypeKind}. - * - * @param typeKind the Checker Framework TypeKind - * @return the javax TypeKind - */ - private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { - return TypeKind.valueOf(typeKind.name()); + // Perform the lookup. + if (qname != null) { + AnnotationMirrorSet fromQname = types.get(qname); + if (fromQname != null) { + type.addMissingAnnotations(fromQname); + } } - /** Add default qualifier, {@code theQual}, for the given TypeKind. */ - public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); - if (!res) { - throw new BugInCF( - "TypeAnnotator: invalid update of typeKinds " - + typeKinds - + " at " - + typeKind - + " with " - + theQual); - } + // If the type's kind or class is in the appropriate map, annotate the type. + AnnotationMirrorSet fromKind = typeKinds.get(type.getKind()); + if (fromKind != null) { + type.addMissingAnnotations(fromKind); + } else if (!atmClasses.isEmpty()) { + Class t = type.getClass(); + AnnotationMirrorSet fromClass = atmClasses.get(t); + if (fromClass != null) { + type.addMissingAnnotations(fromClass); + } } - /** Add default qualifier, {@code theQual}, for the given {@link AnnotatedTypeMirror} class. */ - public void addAtmClass( - Class typeClass, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(atmClasses, typeClass, theQual); - if (!res) { - throw new BugInCF( - "TypeAnnotator: invalid update of atmClasses " - + atmClasses - + " at " - + typeClass - + " with " - + theQual); + return super.scan(type, p); + } + + /** + * Adds standard rules. Currently, sets Void to bottom if no other qualifier is set for Void. + * Also, see {@link LiteralTreeAnnotator#addStandardLiteralQualifiers()}. + * + * @return this + */ + public DefaultForTypeAnnotator addStandardDefaults() { + if (!types.containsKey(Void.class.getCanonicalName())) { + for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { + addTypes(Void.class, bottom); + } + } else { + AnnotationMirrorSet annos = types.get(Void.class.getCanonicalName()); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (qualHierarchy.findAnnotationInHierarchy(annos, top) == null) { + addTypes(Void.class, qualHierarchy.getBottomAnnotation(top)); } + } } - /** Add default qualifier, {@code theQual}, for the given type. */ - public void addTypes(Class clazz, AnnotationMirror theQual) { - String typeNameString = clazz.getCanonicalName(); - boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); - if (!res) { - throw new BugInCF( - "TypeAnnotator: invalid update of types " - + types - + " at " - + clazz - + " with " - + theQual); - } + return this; + } + + /** + * Apply defaults based on a variable name to a type. + * + * @param type a type to apply defaults to + * @param name the name of the variable that has type {@code type}, or the name of the method + * whose return type is {@code type} + */ + public void defaultTypeFromName(AnnotatedTypeMirror type, String name) { + // TODO: Check whether the annotation is applicable to this Java type? + AnnotationMirror defaultAnno = listOfNameRegexes.getDefaultAnno(name); + if (defaultAnno != null) { + if (atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(type.getAnnotations(), defaultAnno) + == null) { + type.addAnnotation(defaultAnno); + } } + } - @Override - protected Void scan(AnnotatedTypeMirror type, Void p) { - // If the type's fully-qualified name is in the appropriate map, annotate the type. Do this - // before looking at kind or class, as this information is more specific. - - String qname; - // We have to use the type name without annotations for the lookup. - TypeMirror unannotatedType = TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()); - if (type.getKind() == TypeKind.DECLARED) { - qname = TypesUtils.getQualifiedName((DeclaredType) unannotatedType); - } else if (type.getKind().isPrimitive()) { - qname = unannotatedType.toString(); - } else { - qname = null; - } + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { + ExecutableElement element = type.getElement(); - // Perform the lookup. - if (qname != null) { - AnnotationMirrorSet fromQname = types.get(qname); - if (fromQname != null) { - type.addMissingAnnotations(fromQname); - } - } + Iterator paramTypes = type.getParameterTypes().iterator(); + for (VariableElement paramElt : element.getParameters()) { + String paramName = paramElt.getSimpleName().toString(); + AnnotatedTypeMirror paramType = paramTypes.next(); + defaultTypeFromName(paramType, paramName); + } - // If the type's kind or class is in the appropriate map, annotate the type. - AnnotationMirrorSet fromKind = typeKinds.get(type.getKind()); - if (fromKind != null) { - type.addMissingAnnotations(fromKind); - } else if (!atmClasses.isEmpty()) { - Class t = type.getClass(); - AnnotationMirrorSet fromClass = atmClasses.get(t); - if (fromClass != null) { - type.addMissingAnnotations(fromClass); - } - } + String methodName = element.getSimpleName().toString(); + AnnotatedTypeMirror returnType = type.getReturnType(); + defaultTypeFromName(returnType, methodName); - return super.scan(type, p); - } + return super.visitExecutable(type, aVoid); + } + + /** + * A list where each element associates an annotation with name regexes and name exception + * regexes. + */ + private static class ListOfNameRegexes extends ArrayList { + + static final long serialVersionUID = 20200218L; /** - * Adds standard rules. Currently, sets Void to bottom if no other qualifier is set for Void. - * Also, see {@link LiteralTreeAnnotator#addStandardLiteralQualifiers()}. + * Update this list from the {@code names} and {@code namesExceptions} fields of a @DefaultFor + * annotation. * - * @return this + * @param theQual the qualifier that a @DefaultFor annotation is written on + * @param defaultFor the @DefaultFor annotation written on {@code theQual} */ - public DefaultForTypeAnnotator addStandardDefaults() { - if (!types.containsKey(Void.class.getCanonicalName())) { - for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { - addTypes(Void.class, bottom); - } - } else { - AnnotationMirrorSet annos = types.get(Void.class.getCanonicalName()); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (qualHierarchy.findAnnotationInHierarchy(annos, top) == null) { - addTypes(Void.class, qualHierarchy.getBottomAnnotation(top)); - } - } + void add(AnnotationMirror theQual, DefaultFor defaultFor) { + if (defaultFor.names().length != 0) { + NameRegexes thisName = new NameRegexes(theQual); + for (String nameRegex : defaultFor.names()) { + try { + thisName.names.add(Pattern.compile(nameRegex)); + } catch (PatternSyntaxException e) { + throw new TypeSystemError( + "In annotation %s, names() value \"%s\" is not a regular" + " expression", + theQual, nameRegex); + } } - - return this; + for (String namesExceptionsRegex : defaultFor.namesExceptions()) { + try { + thisName.namesExceptions.add(Pattern.compile(namesExceptionsRegex)); + } catch (PatternSyntaxException e) { + throw new TypeSystemError( + "In annotation %s, namesExceptions() value \"%s\" is not a regular" + " expression", + theQual, namesExceptionsRegex); + } + } + add(thisName); + } else if (defaultFor.namesExceptions().length != 0) { + throw new TypeSystemError( + "On annotation %s, %s has empty names() but nonempty namesExceptions()", + theQual, defaultFor); + } } /** - * Apply defaults based on a variable name to a type. + * Returns the annotation that should be the default for a variable of the given name, or for + * the return type of a method of the given name. * - * @param type a type to apply defaults to - * @param name the name of the variable that has type {@code type}, or the name of the method - * whose return type is {@code type} + * @param name a variable name + * @return the annotation that should be the default for a variable named {@code name}, or null + * if none */ - public void defaultTypeFromName(AnnotatedTypeMirror type, String name) { - // TODO: Check whether the annotation is applicable to this Java type? - AnnotationMirror defaultAnno = listOfNameRegexes.getDefaultAnno(name); - if (defaultAnno != null) { - if (atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(type.getAnnotations(), defaultAnno) - == null) { - type.addAnnotation(defaultAnno); - } + @Nullable AnnotationMirror getDefaultAnno(String name) { + if (this.isEmpty()) { + return null; + } + AnnotationMirror result = null; + for (NameRegexes nameRegexes : this) { + if (nameRegexes.matches(name)) { + if (result == null) { + result = nameRegexes.anno; + } else { + // This could combine the annotations instead, but I think doing so + // silently would confuse users. + throw new TypeSystemError( + "Multiple annotations are applicable to the name \"%s\"", name); + } } + } + return result; } + } - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - ExecutableElement element = type.getElement(); - - Iterator paramTypes = type.getParameterTypes().iterator(); - for (VariableElement paramElt : element.getParameters()) { - String paramName = paramElt.getSimpleName().toString(); - AnnotatedTypeMirror paramType = paramTypes.next(); - defaultTypeFromName(paramType, paramName); - } + /** + * Associates an annotation with the variable names that cause the annotation to be chosen as a + * default. + */ + private static class NameRegexes { + /** The annotation. */ + final AnnotationMirror anno; - String methodName = element.getSimpleName().toString(); - AnnotatedTypeMirror returnType = type.getReturnType(); - defaultTypeFromName(returnType, methodName); + /** The name regexes. */ + final List names = new ArrayList<>(0); - return super.visitExecutable(type, aVoid); - } + /** The name exception regexes. */ + final List namesExceptions = new ArrayList<>(0); /** - * A list where each element associates an annotation with name regexes and name exception - * regexes. + * Constructs a NameRegexes from a @DefaultFor annotation. + * + * @param theQual the qualifier that {@code defaultFor} is written on */ - private static class ListOfNameRegexes extends ArrayList { - - static final long serialVersionUID = 20200218L; - - /** - * Update this list from the {@code names} and {@code namesExceptions} fields of - * a @DefaultFor annotation. - * - * @param theQual the qualifier that a @DefaultFor annotation is written on - * @param defaultFor the @DefaultFor annotation written on {@code theQual} - */ - void add(AnnotationMirror theQual, DefaultFor defaultFor) { - if (defaultFor.names().length != 0) { - NameRegexes thisName = new NameRegexes(theQual); - for (String nameRegex : defaultFor.names()) { - try { - thisName.names.add(Pattern.compile(nameRegex)); - } catch (PatternSyntaxException e) { - throw new TypeSystemError( - "In annotation %s, names() value \"%s\" is not a regular" - + " expression", - theQual, nameRegex); - } - } - for (String namesExceptionsRegex : defaultFor.namesExceptions()) { - try { - thisName.namesExceptions.add(Pattern.compile(namesExceptionsRegex)); - } catch (PatternSyntaxException e) { - throw new TypeSystemError( - "In annotation %s, namesExceptions() value \"%s\" is not a regular" - + " expression", - theQual, namesExceptionsRegex); - } - } - add(thisName); - } else if (defaultFor.namesExceptions().length != 0) { - throw new TypeSystemError( - "On annotation %s, %s has empty names() but nonempty namesExceptions()", - theQual, defaultFor); - } - } - - /** - * Returns the annotation that should be the default for a variable of the given name, or - * for the return type of a method of the given name. - * - * @param name a variable name - * @return the annotation that should be the default for a variable named {@code name}, or - * null if none - */ - @Nullable AnnotationMirror getDefaultAnno(String name) { - if (this.isEmpty()) { - return null; - } - AnnotationMirror result = null; - for (NameRegexes nameRegexes : this) { - if (nameRegexes.matches(name)) { - if (result == null) { - result = nameRegexes.anno; - } else { - // This could combine the annotations instead, but I think doing so - // silently would confuse users. - throw new TypeSystemError( - "Multiple annotations are applicable to the name \"%s\"", name); - } - } - } - return result; - } + NameRegexes(AnnotationMirror theQual) { + this.anno = theQual; } /** - * Associates an annotation with the variable names that cause the annotation to be chosen as a - * default. + * Returns true if the regular expressions match the given name -- that is, if {@link #anno} + * should be used as the default type for a variable named {@code name}, or for the return type + * of a method named {@code name}. + * + * @param name a variable or method name + * @return true if {@link #anno} should be used as the default for a variable named {@code name} */ - private static class NameRegexes { - /** The annotation. */ - final AnnotationMirror anno; - - /** The name regexes. */ - final List names = new ArrayList<>(0); - - /** The name exception regexes. */ - final List namesExceptions = new ArrayList<>(0); - - /** - * Constructs a NameRegexes from a @DefaultFor annotation. - * - * @param theQual the qualifier that {@code defaultFor} is written on - */ - NameRegexes(AnnotationMirror theQual) { - this.anno = theQual; - } - - /** - * Returns true if the regular expressions match the given name -- that is, if {@link #anno} - * should be used as the default type for a variable named {@code name}, or for the return - * type of a method named {@code name}. - * - * @param name a variable or method name - * @return true if {@link #anno} should be used as the default for a variable named {@code - * name} - */ - public boolean matches(String name) { - return names.stream().anyMatch(p -> p.matcher(name).matches()) - && namesExceptions.stream().noneMatch(p -> p.matcher(name).matches()); - } + public boolean matches(String name) { + return names.stream().anyMatch(p -> p.matcher(name).matches()) + && namesExceptions.stream().noneMatch(p -> p.matcher(name).matches()); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java index 29cae3aa003..3a6e6bc6a80 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.type.typeannotator; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.framework.qual.DefaultQualifierForUse; import org.checkerframework.framework.qual.NoDefaultQualifierForUse; @@ -12,176 +19,161 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; -import java.util.List; -import java.util.Map; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; - /** * Implements support for {@link DefaultQualifierForUse} and {@link NoDefaultQualifierForUse}. Adds * default annotations on types that have no annotation. */ public class DefaultQualifierForUseTypeAnnotator extends TypeAnnotator { - /** The DefaultQualifierForUse.value field/element. */ - private final ExecutableElement defaultQualifierForUseValueElement; - - /** The NoDefaultQualifierForUse.value field/element. */ - private final ExecutableElement noDefaultQualifierForUseValueElement; - - /** - * Creates an DefaultQualifierForUseTypeAnnotator for {@code typeFactory}. - * - * @param typeFactory the type factory - */ - public DefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - ProcessingEnvironment processingEnv = typeFactory.getProcessingEnv(); - defaultQualifierForUseValueElement = - TreeUtils.getMethod(DefaultQualifierForUse.class, "value", 0, processingEnv); - noDefaultQualifierForUseValueElement = - TreeUtils.getMethod(NoDefaultQualifierForUse.class, "value", 0, processingEnv); + /** The DefaultQualifierForUse.value field/element. */ + private final ExecutableElement defaultQualifierForUseValueElement; + + /** The NoDefaultQualifierForUse.value field/element. */ + private final ExecutableElement noDefaultQualifierForUseValueElement; + + /** + * Creates an DefaultQualifierForUseTypeAnnotator for {@code typeFactory}. + * + * @param typeFactory the type factory + */ + public DefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + ProcessingEnvironment processingEnv = typeFactory.getProcessingEnv(); + defaultQualifierForUseValueElement = + TreeUtils.getMethod(DefaultQualifierForUse.class, "value", 0, processingEnv); + noDefaultQualifierForUseValueElement = + TreeUtils.getMethod(NoDefaultQualifierForUse.class, "value", 0, processingEnv); + } + + // There is no `visitPrimitive()` because `@DefaultQualifierForUse` is an annotation the goes on + // a type declaration. Defaults for primitives are add via the meta-annotation @DefaultFor, + // which is handled elsewhere. + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + Element element = type.getUnderlyingType().asElement(); + AnnotationMirrorSet annosToApply = getDefaultAnnosForUses(element); + type.addMissingAnnotations(annosToApply); + return super.visitDeclared(type, aVoid); + } + + /** + * Cache of elements to the set of annotations that should be applied to unannotated uses of the + * element. + */ + protected final Map elementToDefaults = + CollectionsPlume.createLruCache(100); + + /** Clears all caches. */ + public void clearCache() { + elementToDefaults.clear(); + } + + /** + * Returns the set of qualifiers that should be applied to unannotated uses of the given element + * + * @param element the element for which to determine default qualifiers + * @return the set of qualifiers that should be applied to unannotated uses of {@code element} + */ + protected AnnotationMirrorSet getDefaultAnnosForUses(Element element) { + if (atypeFactory.shouldCache && elementToDefaults.containsKey(element)) { + return elementToDefaults.get(element); } - - // There is no `visitPrimitive()` because `@DefaultQualifierForUse` is an annotation the goes on - // a type declaration. Defaults for primitives are add via the meta-annotation @DefaultFor, - // which is handled elsewhere. - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - Element element = type.getUnderlyingType().asElement(); - AnnotationMirrorSet annosToApply = getDefaultAnnosForUses(element); - type.addMissingAnnotations(annosToApply); - return super.visitDeclared(type, aVoid); - } - - /** - * Cache of elements to the set of annotations that should be applied to unannotated uses of the - * element. - */ - protected final Map elementToDefaults = - CollectionsPlume.createLruCache(100); - - /** Clears all caches. */ - public void clearCache() { - elementToDefaults.clear(); - } - - /** - * Returns the set of qualifiers that should be applied to unannotated uses of the given element - * - * @param element the element for which to determine default qualifiers - * @return the set of qualifiers that should be applied to unannotated uses of {@code element} - */ - protected AnnotationMirrorSet getDefaultAnnosForUses(Element element) { - if (atypeFactory.shouldCache && elementToDefaults.containsKey(element)) { - return elementToDefaults.get(element); - } - AnnotationMirrorSet explictAnnos = getExplicitAnnos(element); - AnnotationMirrorSet defaultAnnos = getDefaultQualifierForUses(element); - AnnotationMirrorSet noDefaultAnnos = getHierarchiesNoDefault(element); - AnnotationMirrorSet annosToApply = new AnnotationMirrorSet(); - - for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) { - if (AnnotationUtils.containsSame(noDefaultAnnos, top)) { - continue; - } - AnnotationMirror defaultAnno = - atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(defaultAnnos, top); - if (defaultAnno != null) { - annosToApply.add(defaultAnno); - } else { - AnnotationMirror explict = - atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(explictAnnos, top); - if (explict != null) { - annosToApply.add(explict); - } - } + AnnotationMirrorSet explictAnnos = getExplicitAnnos(element); + AnnotationMirrorSet defaultAnnos = getDefaultQualifierForUses(element); + AnnotationMirrorSet noDefaultAnnos = getHierarchiesNoDefault(element); + AnnotationMirrorSet annosToApply = new AnnotationMirrorSet(); + + for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) { + if (AnnotationUtils.containsSame(noDefaultAnnos, top)) { + continue; + } + AnnotationMirror defaultAnno = + atypeFactory.getQualifierHierarchy().findAnnotationInHierarchy(defaultAnnos, top); + if (defaultAnno != null) { + annosToApply.add(defaultAnno); + } else { + AnnotationMirror explict = + atypeFactory.getQualifierHierarchy().findAnnotationInHierarchy(explictAnnos, top); + if (explict != null) { + annosToApply.add(explict); } - // If parsing stub files, then the annosToApply is incomplete, so don't cache them. - if (atypeFactory.shouldCache - && !atypeFactory.stubTypes.isParsing() - && !atypeFactory.ajavaTypes.isParsing()) { - elementToDefaults.put(element, annosToApply); - } - return annosToApply; + } } - - /** - * Return the annotations explicitly written on the element. - * - * @param element an element - * @return the annotations explicitly written on the element - */ - protected AnnotationMirrorSet getExplicitAnnos(Element element) { - AnnotatedTypeMirror explicitAnnoOnDecl = atypeFactory.fromElement(element); - return explicitAnnoOnDecl.getAnnotations(); + // If parsing stub files, then the annosToApply is incomplete, so don't cache them. + if (atypeFactory.shouldCache + && !atypeFactory.stubTypes.isParsing() + && !atypeFactory.ajavaTypes.isParsing()) { + elementToDefaults.put(element, annosToApply); } - - /** - * Return the default qualifiers for uses of {@code element} as specified by a {@link - * DefaultQualifierForUse} annotation. - * - *

Subclasses may override to use an annotation other than {@link DefaultQualifierForUse}. - * - * @param element an element - * @return the default qualifiers for uses of {@code element} - */ - protected AnnotationMirrorSet getDefaultQualifierForUses(Element element) { - AnnotationMirror defaultQualifier = - atypeFactory.getDeclAnnotation(element, DefaultQualifierForUse.class); - if (defaultQualifier == null) { - return AnnotationMirrorSet.emptySet(); - } - return supportedAnnosFromAnnotationMirror( - AnnotationUtils.getElementValueClassNames( - defaultQualifier, defaultQualifierForUseValueElement)); + return annosToApply; + } + + /** + * Return the annotations explicitly written on the element. + * + * @param element an element + * @return the annotations explicitly written on the element + */ + protected AnnotationMirrorSet getExplicitAnnos(Element element) { + AnnotatedTypeMirror explicitAnnoOnDecl = atypeFactory.fromElement(element); + return explicitAnnoOnDecl.getAnnotations(); + } + + /** + * Return the default qualifiers for uses of {@code element} as specified by a {@link + * DefaultQualifierForUse} annotation. + * + *

Subclasses may override to use an annotation other than {@link DefaultQualifierForUse}. + * + * @param element an element + * @return the default qualifiers for uses of {@code element} + */ + protected AnnotationMirrorSet getDefaultQualifierForUses(Element element) { + AnnotationMirror defaultQualifier = + atypeFactory.getDeclAnnotation(element, DefaultQualifierForUse.class); + if (defaultQualifier == null) { + return AnnotationMirrorSet.emptySet(); } - - /** - * Returns top annotations in hierarchies for which no default for use qualifier should be - * added. - * - * @param element an element - * @return top annotations in hierarchies for which no default for use qualifier should be added - */ - protected AnnotationMirrorSet getHierarchiesNoDefault(Element element) { - AnnotationMirror noDefaultQualifier = - atypeFactory.getDeclAnnotation(element, NoDefaultQualifierForUse.class); - if (noDefaultQualifier == null) { - return AnnotationMirrorSet.emptySet(); - } - return supportedAnnosFromAnnotationMirror( - AnnotationUtils.getElementValueClassNames( - noDefaultQualifier, noDefaultQualifierForUseValueElement)); + return supportedAnnosFromAnnotationMirror( + AnnotationUtils.getElementValueClassNames( + defaultQualifier, defaultQualifierForUseValueElement)); + } + + /** + * Returns top annotations in hierarchies for which no default for use qualifier should be added. + * + * @param element an element + * @return top annotations in hierarchies for which no default for use qualifier should be added + */ + protected AnnotationMirrorSet getHierarchiesNoDefault(Element element) { + AnnotationMirror noDefaultQualifier = + atypeFactory.getDeclAnnotation(element, NoDefaultQualifierForUse.class); + if (noDefaultQualifier == null) { + return AnnotationMirrorSet.emptySet(); } - - /** - * Returns the set of qualifiers supported by this type system from the value element of {@code - * annotationMirror}. - * - * @param annoClassNames a list of annotation class names - * @return the set of qualifiers supported by this type system from the value element of {@code - * annotationMirror} - */ - protected final AnnotationMirrorSet supportedAnnosFromAnnotationMirror( - List<@CanonicalName Name> annoClassNames) { - AnnotationMirrorSet supportAnnos = new AnnotationMirrorSet(); - for (Name annoName : annoClassNames) { - AnnotationMirror anno = - AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); - if (atypeFactory.isSupportedQualifier(anno)) { - supportAnnos.add(anno); - } - } - return supportAnnos; + return supportedAnnosFromAnnotationMirror( + AnnotationUtils.getElementValueClassNames( + noDefaultQualifier, noDefaultQualifierForUseValueElement)); + } + + /** + * Returns the set of qualifiers supported by this type system from the value element of {@code + * annotationMirror}. + * + * @param annoClassNames a list of annotation class names + * @return the set of qualifiers supported by this type system from the value element of {@code + * annotationMirror} + */ + protected final AnnotationMirrorSet supportedAnnosFromAnnotationMirror( + List<@CanonicalName Name> annoClassNames) { + AnnotationMirrorSet supportAnnos = new AnnotationMirrorSet(); + for (Name annoName : annoClassNames) { + AnnotationMirror anno = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); + if (atypeFactory.isSupportedQualifier(anno)) { + supportAnnos.add(anno); + } } + return supportAnnos; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java index 877f9797af4..311c341beaa 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java @@ -1,85 +1,83 @@ package org.checkerframework.framework.type.typeannotator; +import javax.lang.model.type.TypeMirror; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.javacutil.BugInCF; -import javax.lang.model.type.TypeMirror; - /** * Adds annotations to types that are not relevant specified by the {@link RelevantJavaTypes} on a * checker. */ public class IrrelevantTypeAnnotator extends TypeAnnotator { - /** - * Annotate every type except for those whose underlying Java type is one of (or a subtype or - * supertype of) a class in relevantClasses. (Only adds annotationMirror if no annotation in the - * hierarchy are already on the type.) If relevantClasses includes Object[].class, then all - * arrays are considered relevant. - * - * @param atypeFactory a GenericAnnotatedTypeFactory - */ - @SuppressWarnings("rawtypes") - public IrrelevantTypeAnnotator(GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - GenericAnnotatedTypeFactory gatf = (GenericAnnotatedTypeFactory) atypeFactory; + /** + * Annotate every type except for those whose underlying Java type is one of (or a subtype or + * supertype of) a class in relevantClasses. (Only adds annotationMirror if no annotation in the + * hierarchy are already on the type.) If relevantClasses includes Object[].class, then all arrays + * are considered relevant. + * + * @param atypeFactory a GenericAnnotatedTypeFactory + */ + @SuppressWarnings("rawtypes") + public IrrelevantTypeAnnotator(GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - TypeMirror tm = type.getUnderlyingType(); - if (shouldAddPrimaryAnnotation(tm) && !gatf.isRelevant(tm)) { - type.addMissingAnnotations( - gatf.annotationsForIrrelevantJavaType(type.getUnderlyingType())); - } + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + GenericAnnotatedTypeFactory gatf = (GenericAnnotatedTypeFactory) atypeFactory; - return super.scan(type, aVoid); + TypeMirror tm = type.getUnderlyingType(); + if (shouldAddPrimaryAnnotation(tm) && !gatf.isRelevant(tm)) { + type.addMissingAnnotations(gatf.annotationsForIrrelevantJavaType(type.getUnderlyingType())); } - /** - * Returns true if IrrelevantTypeAnnotator should add a primary annotation. - * - * @param tm a type mirror - * @return true if IrrelevantTypeAnnotator should add a primary annotation - */ - boolean shouldAddPrimaryAnnotation(TypeMirror tm) { - switch (tm.getKind()) { - case BOOLEAN: - case BYTE: - case CHAR: - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - return true; + return super.scan(type, aVoid); + } + + /** + * Returns true if IrrelevantTypeAnnotator should add a primary annotation. + * + * @param tm a type mirror + * @return true if IrrelevantTypeAnnotator should add a primary annotation + */ + boolean shouldAddPrimaryAnnotation(TypeMirror tm) { + switch (tm.getKind()) { + case BOOLEAN: + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + return true; - case DECLARED: - return true; + case DECLARED: + return true; - case ARRAY: - return true; - case TYPEVAR: - case WILDCARD: - return false; + case ARRAY: + return true; + case TYPEVAR: + case WILDCARD: + return false; - case ERROR: - case EXECUTABLE: - case INTERSECTION: - case MODULE: - case NONE: - case NULL: - case OTHER: - case PACKAGE: - case UNION: - case VOID: - return false; + case ERROR: + case EXECUTABLE: + case INTERSECTION: + case MODULE: + case NONE: + case NULL: + case OTHER: + case PACKAGE: + case UNION: + case VOID: + return false; - default: - throw new BugInCF("Unknown type kind %s for %s", tm.getKind(), tm); - } + default: + throw new BugInCF("Unknown type kind %s for %s", tm.getKind(), tm); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java index 0de2e13c462..50e693bea4a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java @@ -1,11 +1,10 @@ package org.checkerframework.framework.type.typeannotator; -import org.checkerframework.framework.type.AnnotatedTypeMirror; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.framework.type.AnnotatedTypeMirror; /** * ListTypeAnnotator is a TypeAnnotator that executes a list of {@link TypeAnnotator} for each type @@ -19,50 +18,50 @@ */ public final class ListTypeAnnotator extends TypeAnnotator { - /** - * The annotators that will be executed for each type scanned by this TypeAnnotator. They are - * executed in order. - */ - private final List annotators; + /** + * The annotators that will be executed for each type scanned by this TypeAnnotator. They are + * executed in order. + */ + private final List annotators; - /** - * Create a new ListTypeAnnotator. - * - * @param annotators the annotators that will be executed for each type scanned by this - * TypeAnnotator. They are executed in the order passed in. - */ - public ListTypeAnnotator(TypeAnnotator... annotators) { - this(Arrays.asList(annotators)); - } + /** + * Create a new ListTypeAnnotator. + * + * @param annotators the annotators that will be executed for each type scanned by this + * TypeAnnotator. They are executed in the order passed in. + */ + public ListTypeAnnotator(TypeAnnotator... annotators) { + this(Arrays.asList(annotators)); + } - /** - * @param annotators the annotators that will be executed for each type scanned by this - * TypeAnnotator. They are executed in the order passed in. - */ - public ListTypeAnnotator(List annotators) { - super(null); - List annotatorList = new ArrayList<>(annotators.size()); - for (TypeAnnotator annotator : annotators) { - if (annotator instanceof ListTypeAnnotator) { - annotatorList.addAll(((ListTypeAnnotator) annotator).annotators); - } else { - annotatorList.add(annotator); - } - } - this.annotators = Collections.unmodifiableList(annotatorList); + /** + * @param annotators the annotators that will be executed for each type scanned by this + * TypeAnnotator. They are executed in the order passed in. + */ + public ListTypeAnnotator(List annotators) { + super(null); + List annotatorList = new ArrayList<>(annotators.size()); + for (TypeAnnotator annotator : annotators) { + if (annotator instanceof ListTypeAnnotator) { + annotatorList.addAll(((ListTypeAnnotator) annotator).annotators); + } else { + annotatorList.add(annotator); + } } + this.annotators = Collections.unmodifiableList(annotatorList); + } - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - for (TypeAnnotator annotator : annotators) { - annotator.visit(type, aVoid); - } - - return null; + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + for (TypeAnnotator annotator : annotators) { + annotator.visit(type, aVoid); } - @Override - public String toString() { - return "ListTypeAnnotator" + annotators; - } + return null; + } + + @Override + public String toString() { + return "ListTypeAnnotator" + annotators; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java index 7923883b48e..3cf36a84d4a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.type.typeannotator; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -12,15 +19,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.StringsPlume; -import java.util.ArrayDeque; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; - /** * {@link PropagationTypeAnnotator} adds qualifiers to types where the qualifier to add should be * transferred from one or more other types. @@ -35,195 +33,192 @@ */ public class PropagationTypeAnnotator extends TypeAnnotator { - /** - * The PropagationTypeAnnotator is called recursively via - * TypeAnnotatorUtil.eraseBoundsThenAnnotate. This flag prevents infinite recursion. - */ - private boolean pause = false; - - /** The parents. */ - private final ArrayDeque parents = new ArrayDeque<>(); - - /** - * Creates a new PropagationTypeAnnotator. - * - * @param typeFactory the type factory - */ - public PropagationTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); + /** + * The PropagationTypeAnnotator is called recursively via + * TypeAnnotatorUtil.eraseBoundsThenAnnotate. This flag prevents infinite recursion. + */ + private boolean pause = false; + + /** The parents. */ + private final ArrayDeque parents = new ArrayDeque<>(); + + /** + * Creates a new PropagationTypeAnnotator. + * + * @param typeFactory the type factory + */ + public PropagationTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + @Override + public void reset() { + if (!pause) { + // when the PropagationTypeAnnotator is called recursively we don't + // want the visit method to reset the list of visited types + super.reset(); } - - @Override - public void reset() { - if (!pause) { - // when the PropagationTypeAnnotator is called recursively we don't - // want the visit method to reset the list of visited types - super.reset(); - } + } + + /* + * When pause == true, the PropagationTypeAnnotator caused a recursive call + * and there is no need to execute the PropagationTypeAnnotator + */ + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + if (pause) { + return null; } - /* - * When pause == true, the PropagationTypeAnnotator caused a recursive call - * and there is no need to execute the PropagationTypeAnnotator - */ - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - if (pause) { - return null; - } - - return super.scan(type, aVoid); + return super.scan(type, aVoid); + } + + /** + * Sometimes the underlying type parameters of AnnotatedWildcardTypes are not available on the + * wildcards themselves. Instead, record enclosing class to find the type parameter to use as a + * backup in visitWildcards. + * + * @param declaredType type to record + */ + @Override + public Void visitDeclared(AnnotatedDeclaredType declaredType, Void aVoid) { + if (pause) { + return null; } - - /** - * Sometimes the underlying type parameters of AnnotatedWildcardTypes are not available on the - * wildcards themselves. Instead, record enclosing class to find the type parameter to use as a - * backup in visitWildcards. - * - * @param declaredType type to record - */ - @Override - public Void visitDeclared(AnnotatedDeclaredType declaredType, Void aVoid) { - if (pause) { - return null; - } - if (declaredType.isUnderlyingTypeRaw()) { - // Copy annotations from the declaration to the wildcards. - AnnotatedDeclaredType declaration = - (AnnotatedDeclaredType) - atypeFactory.fromElement(declaredType.getUnderlyingType().asElement()); - List typeArgs = declaredType.getTypeArguments(); - for (int i = 0; i < typeArgs.size(); i++) { - if (typeArgs.get(i).getKind() != TypeKind.WILDCARD - || !((AnnotatedWildcardType) typeArgs.get(i)).isUninferredTypeArgument()) { - // Sometimes the framework infers a more precise type argument, so just use it. - continue; - } - AnnotatedTypeVariable typeParam = - (AnnotatedTypeVariable) declaration.getTypeArguments().get(i); - AnnotatedWildcardType wct = (AnnotatedWildcardType) typeArgs.get(i); - wct.getExtendsBound() - .replaceAnnotations(typeParam.getUpperBound().getAnnotations()); - wct.getSuperBound().replaceAnnotations(typeParam.getLowerBound().getAnnotations()); - wct.replaceAnnotations(typeParam.getAnnotations()); - } + if (declaredType.isUnderlyingTypeRaw()) { + // Copy annotations from the declaration to the wildcards. + AnnotatedDeclaredType declaration = + (AnnotatedDeclaredType) + atypeFactory.fromElement(declaredType.getUnderlyingType().asElement()); + List typeArgs = declaredType.getTypeArguments(); + for (int i = 0; i < typeArgs.size(); i++) { + if (typeArgs.get(i).getKind() != TypeKind.WILDCARD + || !((AnnotatedWildcardType) typeArgs.get(i)).isUninferredTypeArgument()) { + // Sometimes the framework infers a more precise type argument, so just use it. + continue; } - - parents.addFirst(declaredType); - super.visitDeclared(declaredType, aVoid); - parents.removeFirst(); - return null; + AnnotatedTypeVariable typeParam = + (AnnotatedTypeVariable) declaration.getTypeArguments().get(i); + AnnotatedWildcardType wct = (AnnotatedWildcardType) typeArgs.get(i); + wct.getExtendsBound().replaceAnnotations(typeParam.getUpperBound().getAnnotations()); + wct.getSuperBound().replaceAnnotations(typeParam.getLowerBound().getAnnotations()); + wct.replaceAnnotations(typeParam.getAnnotations()); + } } - /** - * Rather than defaulting the missing bounds of a wildcard, find the bound annotations on the - * type parameter it replaced. Place those annotations on the wildcard. - * - * @param wildcard type to annotate - */ - @Override - public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { - if (visitedNodes.containsKey(wildcard) || pause) { - return null; - } - visitedNodes.put(wildcard, null); - - Element typeParamElement = TypesUtils.wildcardToTypeParam(wildcard.getUnderlyingType()); - if (typeParamElement == null && !parents.isEmpty()) { - typeParamElement = getTypeParameterElement(wildcard, parents.peekFirst()); - } - - if (typeParamElement != null) { - pause = true; - AnnotatedTypeVariable typeParam = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeParamElement); - pause = false; - - AnnotationMirrorSet tops = atypeFactory.getQualifierHierarchy().getTopAnnotations(); - - if (AnnotatedTypes.hasNoExplicitBound(wildcard)) { - propagateExtendsBound(wildcard, typeParam, tops); - propagateSuperBound(wildcard, typeParam, tops); - } else if (AnnotatedTypes.hasExplicitExtendsBound(wildcard)) { - propagateSuperBound(wildcard, typeParam, tops); - } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { - propagateExtendsBound(wildcard, typeParam, tops); - } else { - // If this is thrown, then it means that there's a bug in one of the - // AnnotatedTypes.hasNoExplicit*Bound methods. Probably something changed in the - // javac implementation. - throw new BugInCF( - "Wildcard is neither unbound nor does it have an explicit bound."); - } - } - scan(wildcard.getExtendsBound(), null); - scan(wildcard.getSuperBound(), null); - return null; + parents.addFirst(declaredType); + super.visitDeclared(declaredType, aVoid); + parents.removeFirst(); + return null; + } + + /** + * Rather than defaulting the missing bounds of a wildcard, find the bound annotations on the type + * parameter it replaced. Place those annotations on the wildcard. + * + * @param wildcard type to annotate + */ + @Override + public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { + if (visitedNodes.containsKey(wildcard) || pause) { + return null; } + visitedNodes.put(wildcard, null); - private void propagateSuperBound( - AnnotatedWildcardType wildcard, - AnnotatedTypeVariable typeParam, - Set tops) { - applyAnnosFromBound(wildcard.getSuperBound(), typeParam.getLowerBound(), tops); + Element typeParamElement = TypesUtils.wildcardToTypeParam(wildcard.getUnderlyingType()); + if (typeParamElement == null && !parents.isEmpty()) { + typeParamElement = getTypeParameterElement(wildcard, parents.peekFirst()); } - private void propagateExtendsBound( - AnnotatedWildcardType wildcard, - AnnotatedTypeVariable typeParam, - Set tops) { - applyAnnosFromBound(wildcard.getExtendsBound(), typeParam.getUpperBound(), tops); + if (typeParamElement != null) { + pause = true; + AnnotatedTypeVariable typeParam = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeParamElement); + pause = false; + + AnnotationMirrorSet tops = atypeFactory.getQualifierHierarchy().getTopAnnotations(); + + if (AnnotatedTypes.hasNoExplicitBound(wildcard)) { + propagateExtendsBound(wildcard, typeParam, tops); + propagateSuperBound(wildcard, typeParam, tops); + } else if (AnnotatedTypes.hasExplicitExtendsBound(wildcard)) { + propagateSuperBound(wildcard, typeParam, tops); + } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { + propagateExtendsBound(wildcard, typeParam, tops); + } else { + // If this is thrown, then it means that there's a bug in one of the + // AnnotatedTypes.hasNoExplicit*Bound methods. Probably something changed in the + // javac implementation. + throw new BugInCF("Wildcard is neither unbound nor does it have an explicit bound."); + } } - - /** - * Take the primary annotations from typeParamBound and place them as primary annotations on - * wildcard bound. - */ - private void applyAnnosFromBound( - AnnotatedTypeMirror wildcardBound, - AnnotatedTypeMirror typeParamBound, - Set tops) { - // Type variables do not need primary annotations. - // The type variable will have annotations placed on its - // bounds via its declaration or defaulting rules - if (wildcardBound.getKind() == TypeKind.TYPEVAR - || typeParamBound.getKind() == TypeKind.TYPEVAR) { - return; - } - - for (AnnotationMirror top : tops) { - if (wildcardBound.getAnnotationInHierarchy(top) == null) { - AnnotationMirror typeParamAnno = typeParamBound.getAnnotationInHierarchy(top); - if (typeParamAnno == null) { - throw new BugInCF( - StringsPlume.joinLines( - "Missing annotation on type parameter", - "top=" + top, - "wildcardBound=" + wildcardBound, - "typeParamBound=" + typeParamBound)); - } // else - wildcardBound.addAnnotation(typeParamAnno); - } - } + scan(wildcard.getExtendsBound(), null); + scan(wildcard.getSuperBound(), null); + return null; + } + + private void propagateSuperBound( + AnnotatedWildcardType wildcard, + AnnotatedTypeVariable typeParam, + Set tops) { + applyAnnosFromBound(wildcard.getSuperBound(), typeParam.getLowerBound(), tops); + } + + private void propagateExtendsBound( + AnnotatedWildcardType wildcard, + AnnotatedTypeVariable typeParam, + Set tops) { + applyAnnosFromBound(wildcard.getExtendsBound(), typeParam.getUpperBound(), tops); + } + + /** + * Take the primary annotations from typeParamBound and place them as primary annotations on + * wildcard bound. + */ + private void applyAnnosFromBound( + AnnotatedTypeMirror wildcardBound, + AnnotatedTypeMirror typeParamBound, + Set tops) { + // Type variables do not need primary annotations. + // The type variable will have annotations placed on its + // bounds via its declaration or defaulting rules + if (wildcardBound.getKind() == TypeKind.TYPEVAR + || typeParamBound.getKind() == TypeKind.TYPEVAR) { + return; } - /** - * Search {@code declaredType}'s type arguments for {@code typeArg}. Using the index of {@code - * typeArg}, find the corresponding type parameter element and return it. - * - * @param typeArg a typeArg of {@code declaredType} - * @param declaredType the type in which {@code typeArg} is a type argument - * @return the type parameter in {@code declaredType} that corresponds to {@code typeArg} - */ - private Element getTypeParameterElement( - @FindDistinct AnnotatedTypeMirror typeArg, AnnotatedDeclaredType declaredType) { - for (int i = 0; i < declaredType.getTypeArguments().size(); i++) { - if (declaredType.getTypeArguments().get(i) == typeArg) { - TypeElement typeElement = - TypesUtils.getTypeElement(declaredType.getUnderlyingType()); - return typeElement.getTypeParameters().get(i); - } - } - throw new BugInCF("Wildcard %s is not a type argument of %s", typeArg, declaredType); + for (AnnotationMirror top : tops) { + if (wildcardBound.getAnnotationInHierarchy(top) == null) { + AnnotationMirror typeParamAnno = typeParamBound.getAnnotationInHierarchy(top); + if (typeParamAnno == null) { + throw new BugInCF( + StringsPlume.joinLines( + "Missing annotation on type parameter", + "top=" + top, + "wildcardBound=" + wildcardBound, + "typeParamBound=" + typeParamBound)); + } // else + wildcardBound.addAnnotation(typeParamAnno); + } + } + } + + /** + * Search {@code declaredType}'s type arguments for {@code typeArg}. Using the index of {@code + * typeArg}, find the corresponding type parameter element and return it. + * + * @param typeArg a typeArg of {@code declaredType} + * @param declaredType the type in which {@code typeArg} is a type argument + * @return the type parameter in {@code declaredType} that corresponds to {@code typeArg} + */ + private Element getTypeParameterElement( + @FindDistinct AnnotatedTypeMirror typeArg, AnnotatedDeclaredType declaredType) { + for (int i = 0; i < declaredType.getTypeArguments().size(); i++) { + if (declaredType.getTypeArguments().get(i) == typeArg) { + TypeElement typeElement = TypesUtils.getTypeElement(declaredType.getUnderlyingType()); + return typeElement.getTypeParameters().get(i); + } } + throw new BugInCF("Wildcard %s is not a type argument of %s", typeArg, declaredType); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java index d9cd5f1db99..37d29ef448a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.type.typeannotator; +import javax.lang.model.element.Element; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; -import javax.lang.model.element.Element; - /** * {@link TypeAnnotator} is an abstract AnnotatedTypeScanner to be used with {@link * ListTypeAnnotator}. @@ -17,29 +16,29 @@ */ public abstract class TypeAnnotator extends AnnotatedTypeScanner { - /** The type factory. */ - protected final AnnotatedTypeFactory atypeFactory; + /** The type factory. */ + protected final AnnotatedTypeFactory atypeFactory; - /** - * Creates a new TypeAnnotator. - * - * @param atypeFactory the type factory - */ - protected TypeAnnotator(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** + * Creates a new TypeAnnotator. + * + * @param atypeFactory the type factory + */ + protected TypeAnnotator(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - /** - * {@inheritDoc} - * - *

If this method adds annotations to the type of method parameters, then {@link - * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Element, - * AnnotatedTypeMirror)} should be overridden and the same annotations added to the type of - * elements with kind {@link javax.lang.model.element.ElementKind#PARAMETER}. Likewise for - * return types. - */ - @Override - public Void visitExecutable(AnnotatedExecutableType method, Void aVoid) { - return super.visitExecutable(method, aVoid); - } + /** + * {@inheritDoc} + * + *

If this method adds annotations to the type of method parameters, then {@link + * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Element, + * AnnotatedTypeMirror)} should be overridden and the same annotations added to the type of + * elements with kind {@link javax.lang.model.element.ElementKind#PARAMETER}. Likewise for return + * types. + */ + @Override + public Void visitExecutable(AnnotatedExecutableType method, Void aVoid) { + return super.visitExecutable(method, aVoid); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java index 843c9570408..cd278c12e2e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java @@ -20,608 +20,603 @@ *

This class does no traversal. */ public abstract class AbstractAtmComboVisitor - implements AtmComboVisitor { - - /** - * Dispatches to a more specific {@code visit*} method. - * - * @param type1 the first type to visit - * @param type2 the second type to visit - * @param param a value passed to every visit method - * @return the result of calling the more specific {@code visit*} method - */ - public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - return AtmCombo.accept(type1, type2, param, this); - } - - @Override - public RETURN_TYPE visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Declared( - AnnotatedArrayType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Executable( - AnnotatedArrayType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Intersection( - AnnotatedArrayType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_None( - AnnotatedArrayType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Null( - AnnotatedArrayType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Primitive( - AnnotatedArrayType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Typevar( - AnnotatedArrayType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Union( - AnnotatedArrayType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Wildcard( - AnnotatedArrayType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Array( - AnnotatedDeclaredType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Executable( - AnnotatedDeclaredType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Intersection( - AnnotatedDeclaredType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_None( - AnnotatedDeclaredType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Null( - AnnotatedDeclaredType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Primitive( - AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Typevar( - AnnotatedDeclaredType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Union( - AnnotatedDeclaredType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Wildcard( - AnnotatedDeclaredType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Array( - AnnotatedExecutableType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Declared( - AnnotatedExecutableType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Executable( - AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Intersection( - AnnotatedExecutableType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_None( - AnnotatedExecutableType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Null( - AnnotatedExecutableType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Primitive( - AnnotatedExecutableType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Typevar( - AnnotatedExecutableType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Union( - AnnotatedExecutableType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Wildcard( - AnnotatedExecutableType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Array( - AnnotatedIntersectionType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Declared( - AnnotatedIntersectionType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Executable( - AnnotatedIntersectionType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_None( - AnnotatedIntersectionType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Null( - AnnotatedIntersectionType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Primitive( - AnnotatedIntersectionType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Typevar( - AnnotatedIntersectionType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Union( - AnnotatedIntersectionType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Wildcard( - AnnotatedIntersectionType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Array( - AnnotatedNoType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Declared( - AnnotatedNoType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Executable( - AnnotatedNoType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Intersection( - AnnotatedNoType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_None(AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Null(AnnotatedNoType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Primitive( - AnnotatedNoType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Union( - AnnotatedNoType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Wildcard( - AnnotatedNoType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Array( - AnnotatedNullType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Declared( - AnnotatedNullType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Executable( - AnnotatedNullType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Intersection( - AnnotatedNullType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_None(AnnotatedNullType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Null( - AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Primitive( - AnnotatedNullType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Typevar( - AnnotatedNullType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Union( - AnnotatedNullType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Wildcard( - AnnotatedNullType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Array( - AnnotatedPrimitiveType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Declared( - AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Executable( - AnnotatedPrimitiveType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Intersection( - AnnotatedPrimitiveType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_None( - AnnotatedPrimitiveType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Null( - AnnotatedPrimitiveType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Typevar( - AnnotatedPrimitiveType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Union( - AnnotatedPrimitiveType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Wildcard( - AnnotatedPrimitiveType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Array( - AnnotatedUnionType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Declared( - AnnotatedUnionType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Executable( - AnnotatedUnionType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Intersection( - AnnotatedUnionType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_None( - AnnotatedUnionType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Null( - AnnotatedUnionType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Primitive( - AnnotatedUnionType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Typevar( - AnnotatedUnionType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Wildcard( - AnnotatedUnionType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Array( - AnnotatedTypeVariable type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Declared( - AnnotatedTypeVariable type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Executable( - AnnotatedTypeVariable type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Intersection( - AnnotatedTypeVariable type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_None( - AnnotatedTypeVariable type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Null( - AnnotatedTypeVariable type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Primitive( - AnnotatedTypeVariable type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Union( - AnnotatedTypeVariable type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Wildcard( - AnnotatedTypeVariable type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Array( - AnnotatedWildcardType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Declared( - AnnotatedWildcardType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Executable( - AnnotatedWildcardType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Intersection( - AnnotatedWildcardType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_None( - AnnotatedWildcardType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Null( - AnnotatedWildcardType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Primitive( - AnnotatedWildcardType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Typevar( - AnnotatedWildcardType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Union( - AnnotatedWildcardType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } + implements AtmComboVisitor { + + /** + * Dispatches to a more specific {@code visit*} method. + * + * @param type1 the first type to visit + * @param type2 the second type to visit + * @param param a value passed to every visit method + * @return the result of calling the more specific {@code visit*} method + */ + public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + return AtmCombo.accept(type1, type2, param, this); + } + + @Override + public RETURN_TYPE visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Declared( + AnnotatedArrayType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Executable( + AnnotatedArrayType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Intersection( + AnnotatedArrayType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_None(AnnotatedArrayType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Null( + AnnotatedArrayType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Primitive( + AnnotatedArrayType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Typevar( + AnnotatedArrayType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Union( + AnnotatedArrayType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Wildcard( + AnnotatedArrayType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Array( + AnnotatedDeclaredType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Executable( + AnnotatedDeclaredType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Intersection( + AnnotatedDeclaredType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_None( + AnnotatedDeclaredType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Null( + AnnotatedDeclaredType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Primitive( + AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Typevar( + AnnotatedDeclaredType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Union( + AnnotatedDeclaredType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Wildcard( + AnnotatedDeclaredType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Array( + AnnotatedExecutableType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Declared( + AnnotatedExecutableType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Executable( + AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Intersection( + AnnotatedExecutableType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_None( + AnnotatedExecutableType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Null( + AnnotatedExecutableType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Primitive( + AnnotatedExecutableType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Typevar( + AnnotatedExecutableType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Union( + AnnotatedExecutableType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Wildcard( + AnnotatedExecutableType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Array( + AnnotatedIntersectionType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Declared( + AnnotatedIntersectionType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Executable( + AnnotatedIntersectionType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_None( + AnnotatedIntersectionType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Null( + AnnotatedIntersectionType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Primitive( + AnnotatedIntersectionType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Typevar( + AnnotatedIntersectionType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Union( + AnnotatedIntersectionType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Wildcard( + AnnotatedIntersectionType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Array(AnnotatedNoType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Declared( + AnnotatedNoType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Executable( + AnnotatedNoType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Intersection( + AnnotatedNoType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_None(AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Null(AnnotatedNoType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Primitive( + AnnotatedNoType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Union(AnnotatedNoType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Wildcard( + AnnotatedNoType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Array( + AnnotatedNullType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Declared( + AnnotatedNullType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Executable( + AnnotatedNullType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Intersection( + AnnotatedNullType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_None(AnnotatedNullType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Null(AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Primitive( + AnnotatedNullType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Typevar( + AnnotatedNullType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Union( + AnnotatedNullType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Wildcard( + AnnotatedNullType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Array( + AnnotatedPrimitiveType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Declared( + AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Executable( + AnnotatedPrimitiveType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Intersection( + AnnotatedPrimitiveType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_None( + AnnotatedPrimitiveType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Null( + AnnotatedPrimitiveType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Typevar( + AnnotatedPrimitiveType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Union( + AnnotatedPrimitiveType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Wildcard( + AnnotatedPrimitiveType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Array( + AnnotatedUnionType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Declared( + AnnotatedUnionType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Executable( + AnnotatedUnionType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Intersection( + AnnotatedUnionType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_None(AnnotatedUnionType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Null( + AnnotatedUnionType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Primitive( + AnnotatedUnionType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Typevar( + AnnotatedUnionType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Wildcard( + AnnotatedUnionType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Array( + AnnotatedTypeVariable type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Declared( + AnnotatedTypeVariable type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Executable( + AnnotatedTypeVariable type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Intersection( + AnnotatedTypeVariable type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_None( + AnnotatedTypeVariable type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Null( + AnnotatedTypeVariable type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Primitive( + AnnotatedTypeVariable type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Union( + AnnotatedTypeVariable type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Wildcard( + AnnotatedTypeVariable type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Array( + AnnotatedWildcardType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Declared( + AnnotatedWildcardType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Executable( + AnnotatedWildcardType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Intersection( + AnnotatedWildcardType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_None( + AnnotatedWildcardType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Null( + AnnotatedWildcardType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Primitive( + AnnotatedWildcardType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Typevar( + AnnotatedWildcardType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Union( + AnnotatedWildcardType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java index 5cbaeb9674f..699f7fa87f6 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java @@ -1,77 +1,76 @@ package org.checkerframework.framework.type.visitor; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.BugInCF; -import javax.lang.model.element.AnnotationMirror; - /** Changes each parameter type to be the GLB of the parameter type and visited type. */ public class AnnotatedTypeCombiner extends DoubleAnnotatedTypeScanner { - /** - * Combines all annotations from {@code from} and {@code to} into {@code to} using the GLB. - * - * @param from the annotated type mirror from which to take annotations - * @param to the annotated type mirror into which annotations should be combined - * @param hierarchy the top type of the hierarchy whose annotations should be combined - */ - @SuppressWarnings("interning:not.interned") // assertion - public static void combine( - AnnotatedTypeMirror from, AnnotatedTypeMirror to, QualifierHierarchy hierarchy) { - if (from == to) { - throw new BugInCF("from == to: %s", from); - } - new AnnotatedTypeCombiner(hierarchy).visit(from, to); + /** + * Combines all annotations from {@code from} and {@code to} into {@code to} using the GLB. + * + * @param from the annotated type mirror from which to take annotations + * @param to the annotated type mirror into which annotations should be combined + * @param hierarchy the top type of the hierarchy whose annotations should be combined + */ + @SuppressWarnings("interning:not.interned") // assertion + public static void combine( + AnnotatedTypeMirror from, AnnotatedTypeMirror to, QualifierHierarchy hierarchy) { + if (from == to) { + throw new BugInCF("from == to: %s", from); } + new AnnotatedTypeCombiner(hierarchy).visit(from, to); + } - /** The hierarchy used to compute the GLB. */ - private final QualifierHierarchy hierarchy; + /** The hierarchy used to compute the GLB. */ + private final QualifierHierarchy hierarchy; - /** - * Create an AnnotatedTypeCombiner. - * - * @param hierarchy the hierarchy used to the compute the GLB - */ - public AnnotatedTypeCombiner(QualifierHierarchy hierarchy) { - this.hierarchy = hierarchy; - } + /** + * Create an AnnotatedTypeCombiner. + * + * @param hierarchy the hierarchy used to the compute the GLB + */ + public AnnotatedTypeCombiner(QualifierHierarchy hierarchy) { + this.hierarchy = hierarchy; + } - @Override - @SuppressWarnings("interning:not.interned") // assertion - protected Void defaultAction(AnnotatedTypeMirror one, AnnotatedTypeMirror two) { - assert one != two; - if (one != null && two != null) { - combineAnnotations(one, two); - } - return null; + @Override + @SuppressWarnings("interning:not.interned") // assertion + protected Void defaultAction(AnnotatedTypeMirror one, AnnotatedTypeMirror two) { + assert one != two; + if (one != null && two != null) { + combineAnnotations(one, two); } + return null; + } - /** - * Computes the greatest lower bound of each set of annotations shared by from and to, and - * replaces the annotations in to with the result. - * - * @param from the first set of annotations - * @param to the second set of annotations. This is modified by side-effect to hold the result. - */ - protected void combineAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + /** + * Computes the greatest lower bound of each set of annotations shared by from and to, and + * replaces the annotations in to with the result. + * + * @param from the first set of annotations + * @param to the second set of annotations. This is modified by side-effect to hold the result. + */ + protected void combineAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - AnnotationMirrorSet combinedAnnotations = new AnnotationMirrorSet(); + AnnotationMirrorSet combinedAnnotations = new AnnotationMirrorSet(); - for (AnnotationMirror top : hierarchy.getTopAnnotations()) { - AnnotationMirror aFrom = from.getAnnotationInHierarchy(top); - AnnotationMirror aTo = to.getAnnotationInHierarchy(top); - if (aFrom != null && aTo != null) { - combinedAnnotations.add( - hierarchy.greatestLowerBoundShallow( - aFrom, from.getUnderlyingType(), aTo, to.getUnderlyingType())); - } else if (aFrom != null) { - combinedAnnotations.add(aFrom); - } else if (aTo != null) { - combinedAnnotations.add(aTo); - } - } - to.replaceAnnotations(combinedAnnotations); + for (AnnotationMirror top : hierarchy.getTopAnnotations()) { + AnnotationMirror aFrom = from.getAnnotationInHierarchy(top); + AnnotationMirror aTo = to.getAnnotationInHierarchy(top); + if (aFrom != null && aTo != null) { + combinedAnnotations.add( + hierarchy.greatestLowerBoundShallow( + aFrom, from.getUnderlyingType(), aTo, to.getUnderlyingType())); + } else if (aFrom != null) { + combinedAnnotations.add(aFrom); + } else if (aTo != null) { + combinedAnnotations.add(aTo); + } } + to.replaceAnnotations(combinedAnnotations); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java index 8d7300c7b20..c29fdb75bbb 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java @@ -1,5 +1,6 @@ package org.checkerframework.framework.type.visitor; +import java.util.IdentityHashMap; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -13,8 +14,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import java.util.IdentityHashMap; - /** * An {@code AnnotatedTypeScanner} visits an {@link AnnotatedTypeMirror} and all of its child {@link * AnnotatedTypeMirror}s and performs some function depending on the kind of type. (By contrast, a @@ -88,277 +87,277 @@ */ public abstract class AnnotatedTypeScanner implements AnnotatedTypeVisitor { + /** + * Reduces two results into a single result. + * + * @param the result type + */ + @FunctionalInterface + public interface Reduce { + /** - * Reduces two results into a single result. + * Returns the combination of two results. * - * @param the result type + * @param r1 the first result + * @param r2 the second result + * @return the combination of the two results */ - @FunctionalInterface - public interface Reduce { + R reduce(R r1, R r2); + } - /** - * Returns the combination of two results. - * - * @param r1 the first result - * @param r2 the second result - * @return the combination of the two results - */ - R reduce(R r1, R r2); - } - - /** The reduce function to use. */ - protected final Reduce reduceFunction; + /** The reduce function to use. */ + protected final Reduce reduceFunction; - /** The result to return if no other result is provided. It should be immutable. */ - protected final R defaultResult; + /** The result to return if no other result is provided. It should be immutable. */ + protected final R defaultResult; - /** - * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} - * is null, then the reduce function returns the first result if it is nonnull; otherwise the - * second result is returned. - * - * @param reduceFunction function used to combine two results - * @param defaultResult the result to return if a visit type method is not overridden; it should - * be immutable - */ - protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction, R defaultResult) { - if (reduceFunction == null) { - this.reduceFunction = (r1, r2) -> r1 == null ? r2 : r1; - } else { - this.reduceFunction = reduceFunction; - } - this.defaultResult = defaultResult; + /** + * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is + * null, then the reduce function returns the first result if it is nonnull; otherwise the second + * result is returned. + * + * @param reduceFunction function used to combine two results + * @param defaultResult the result to return if a visit type method is not overridden; it should + * be immutable + */ + protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction, R defaultResult) { + if (reduceFunction == null) { + this.reduceFunction = (r1, r2) -> r1 == null ? r2 : r1; + } else { + this.reduceFunction = reduceFunction; } + this.defaultResult = defaultResult; + } - /** - * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} - * is null, then the reduce function returns the first result if it is nonnull; otherwise the - * second result is returned. The default result is {@code null} - * - * @param reduceFunction function used to combine two results - */ - protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction) { - this(reduceFunction, null); - } + /** + * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is + * null, then the reduce function returns the first result if it is nonnull; otherwise the second + * result is returned. The default result is {@code null} + * + * @param reduceFunction function used to combine two results + */ + protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction) { + this(reduceFunction, null); + } - /** - * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it - * is nonnull; otherwise the second result is returned. - * - * @param defaultResult the result to return if a visit type method is not overridden; it should - * be immutable - */ - protected AnnotatedTypeScanner(R defaultResult) { - this(null, defaultResult); - } + /** + * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is + * nonnull; otherwise the second result is returned. + * + * @param defaultResult the result to return if a visit type method is not overridden; it should + * be immutable + */ + protected AnnotatedTypeScanner(R defaultResult) { + this(null, defaultResult); + } - /** - * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it - * is nonnull; otherwise the second result is returned. The default result is {@code null}. - */ - protected AnnotatedTypeScanner() { - this(null, null); - } + /** + * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is + * nonnull; otherwise the second result is returned. The default result is {@code null}. + */ + protected AnnotatedTypeScanner() { + this(null, null); + } - // To prevent infinite loops - protected final IdentityHashMap visitedNodes = new IdentityHashMap<>(); + // To prevent infinite loops + protected final IdentityHashMap visitedNodes = new IdentityHashMap<>(); - /** - * Reset the scanner to allow reuse of the same instance. Subclasses should override this method - * to clear their additional state; they must call the super implementation. - */ - public void reset() { - visitedNodes.clear(); - } + /** + * Reset the scanner to allow reuse of the same instance. Subclasses should override this method + * to clear their additional state; they must call the super implementation. + */ + public void reset() { + visitedNodes.clear(); + } - /** - * Calls {@link #reset()} and then scans {@code type} using null as the parameter. - * - * @param type type to scan - * @return result of scanning {@code type} - */ - @Override - public final R visit(AnnotatedTypeMirror type) { - return visit(type, null); - } + /** + * Calls {@link #reset()} and then scans {@code type} using null as the parameter. + * + * @param type type to scan + * @return result of scanning {@code type} + */ + @Override + public final R visit(AnnotatedTypeMirror type) { + return visit(type, null); + } - /** - * Calls {@link #reset()} and then scans {@code type} using {@code p} as the parameter. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return result of scanning {@code type} - */ - @Override - public final R visit(AnnotatedTypeMirror type, P p) { - reset(); - return scan(type, p); - } + /** + * Calls {@link #reset()} and then scans {@code type} using {@code p} as the parameter. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return result of scanning {@code type} + */ + @Override + public final R visit(AnnotatedTypeMirror type, P p) { + reset(); + return scan(type, p); + } - /** - * Scan {@code type} by calling {@code type.accept(this, p)}; this method may be overridden by - * subclasses. - * - * @param type type to scan - * @param p the parameter to use - * @return the result of visiting {@code type} - */ - protected R scan(AnnotatedTypeMirror type, P p) { - return type.accept(this, p); - } + /** + * Scan {@code type} by calling {@code type.accept(this, p)}; this method may be overridden by + * subclasses. + * + * @param type type to scan + * @param p the parameter to use + * @return the result of visiting {@code type} + */ + protected R scan(AnnotatedTypeMirror type, P p) { + return type.accept(this, p); + } - /** - * Scan all the types and returns the reduced result. - * - * @param types types to scan - * @param p the parameter to use - * @return the reduced result of scanning all the types - */ - protected R scan(@Nullable Iterable types, P p) { - if (types == null) { - return defaultResult; - } - R r = defaultResult; - boolean first = true; - for (AnnotatedTypeMirror type : types) { - r = (first ? scan(type, p) : scanAndReduce(type, p, r)); - first = false; - } - return r; + /** + * Scan all the types and returns the reduced result. + * + * @param types types to scan + * @param p the parameter to use + * @return the reduced result of scanning all the types + */ + protected R scan(@Nullable Iterable types, P p) { + if (types == null) { + return defaultResult; } - - protected R scanAndReduce(Iterable types, P p, R r) { - return reduce(scan(types, p), r); + R r = defaultResult; + boolean first = true; + for (AnnotatedTypeMirror type : types) { + r = (first ? scan(type, p) : scanAndReduce(type, p, r)); + first = false; } + return r; + } - /** - * Scans {@code type} with the parameter {@code p} and reduces the result with {@code r}. - * - * @param type type to scan - * @param p parameter to use for when scanning {@code type} - * @param r result to combine with the result of scanning {@code type} - * @return the combination of {@code r} with the result of scanning {@code type} - */ - protected R scanAndReduce(AnnotatedTypeMirror type, P p, R r) { - return reduce(scan(type, p), r); - } + protected R scanAndReduce(Iterable types, P p, R r) { + return reduce(scan(types, p), r); + } - /** - * Combines {@code r1} and {@code r2} and returns the result. The default implementation returns - * {@code r1} if it is not null; otherwise, it returns {@code r2}. - * - * @param r1 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method - * never returns null - * @param r2 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method - * never returns null - * @return the combination of {@code r1} and {@code r2} - */ - protected R reduce(R r1, R r2) { - return reduceFunction.reduce(r1, r2); - } + /** + * Scans {@code type} with the parameter {@code p} and reduces the result with {@code r}. + * + * @param type type to scan + * @param p parameter to use for when scanning {@code type} + * @param r result to combine with the result of scanning {@code type} + * @return the combination of {@code r} with the result of scanning {@code type} + */ + protected R scanAndReduce(AnnotatedTypeMirror type, P p, R r) { + return reduce(scan(type, p), r); + } - @Override - public R visitDeclared(AnnotatedDeclaredType type, P p) { - // Only declared types with type arguments might be recursive, so only store those. - boolean shouldStoreType = !type.getTypeArguments().isEmpty(); - if (shouldStoreType && visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - if (shouldStoreType) { - visitedNodes.put(type, defaultResult); - } - R r = defaultResult; - if (type.getEnclosingType() != null) { - r = scan(type.getEnclosingType(), p); - if (shouldStoreType) { - visitedNodes.put(type, r); - } - } - r = scanAndReduce(type.getTypeArguments(), p, r); - if (shouldStoreType) { - visitedNodes.put(type, r); - } - return r; - } + /** + * Combines {@code r1} and {@code r2} and returns the result. The default implementation returns + * {@code r1} if it is not null; otherwise, it returns {@code r2}. + * + * @param r1 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never + * returns null + * @param r2 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never + * returns null + * @return the combination of {@code r1} and {@code r2} + */ + protected R reduce(R r1, R r2) { + return reduceFunction.reduce(r1, r2); + } - @Override - public R visitIntersection(AnnotatedIntersectionType type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, defaultResult); - R r = scan(type.getBounds(), p); - visitedNodes.put(type, r); - return r; + @Override + public R visitDeclared(AnnotatedDeclaredType type, P p) { + // Only declared types with type arguments might be recursive, so only store those. + boolean shouldStoreType = !type.getTypeArguments().isEmpty(); + if (shouldStoreType && visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - - @Override - public R visitUnion(AnnotatedUnionType type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, defaultResult); - R r = scan(type.getAlternatives(), p); + if (shouldStoreType) { + visitedNodes.put(type, defaultResult); + } + R r = defaultResult; + if (type.getEnclosingType() != null) { + r = scan(type.getEnclosingType(), p); + if (shouldStoreType) { visitedNodes.put(type, r); - return r; + } } - - @Override - public R visitArray(AnnotatedArrayType type, P p) { - R r = scan(type.getComponentType(), p); - return r; + r = scanAndReduce(type.getTypeArguments(), p, r); + if (shouldStoreType) { + visitedNodes.put(type, r); } + return r; + } - @Override - public R visitExecutable(AnnotatedExecutableType type, P p) { - R r = scan(type.getReturnType(), p); - if (type.getReceiverType() != null) { - r = scanAndReduce(type.getReceiverType(), p, r); - } - r = scanAndReduce(type.getParameterTypes(), p, r); - r = scanAndReduce(type.getThrownTypes(), p, r); - r = scanAndReduce(type.getTypeVariables(), p, r); - return r; + @Override + public R visitIntersection(AnnotatedIntersectionType type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } + visitedNodes.put(type, defaultResult); + R r = scan(type.getBounds(), p); + visitedNodes.put(type, r); + return r; + } - @Override - public R visitTypeVariable(AnnotatedTypeVariable type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, defaultResult); - R r = scan(type.getLowerBound(), p); - visitedNodes.put(type, r); - r = scanAndReduce(type.getUpperBound(), p, r); - visitedNodes.put(type, r); - return r; + @Override + public R visitUnion(AnnotatedUnionType type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } + visitedNodes.put(type, defaultResult); + R r = scan(type.getAlternatives(), p); + visitedNodes.put(type, r); + return r; + } - @Override - public R visitNoType(AnnotatedNoType type, P p) { - return defaultResult; - } + @Override + public R visitArray(AnnotatedArrayType type, P p) { + R r = scan(type.getComponentType(), p); + return r; + } - @Override - public R visitNull(AnnotatedNullType type, P p) { - return defaultResult; + @Override + public R visitExecutable(AnnotatedExecutableType type, P p) { + R r = scan(type.getReturnType(), p); + if (type.getReceiverType() != null) { + r = scanAndReduce(type.getReceiverType(), p, r); } + r = scanAndReduce(type.getParameterTypes(), p, r); + r = scanAndReduce(type.getThrownTypes(), p, r); + r = scanAndReduce(type.getTypeVariables(), p, r); + return r; + } - @Override - public R visitPrimitive(AnnotatedPrimitiveType type, P p) { - return defaultResult; + @Override + public R visitTypeVariable(AnnotatedTypeVariable type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } + visitedNodes.put(type, defaultResult); + R r = scan(type.getLowerBound(), p); + visitedNodes.put(type, r); + r = scanAndReduce(type.getUpperBound(), p, r); + visitedNodes.put(type, r); + return r; + } - @Override - public R visitWildcard(AnnotatedWildcardType type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, defaultResult); - R r = scan(type.getExtendsBound(), p); - visitedNodes.put(type, r); - r = scanAndReduce(type.getSuperBound(), p, r); - visitedNodes.put(type, r); - return r; + @Override + public R visitNoType(AnnotatedNoType type, P p) { + return defaultResult; + } + + @Override + public R visitNull(AnnotatedNullType type, P p) { + return defaultResult; + } + + @Override + public R visitPrimitive(AnnotatedPrimitiveType type, P p) { + return defaultResult; + } + + @Override + public R visitWildcard(AnnotatedWildcardType type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } + visitedNodes.put(type, defaultResult); + R r = scan(type.getExtendsBound(), p); + visitedNodes.put(type, r); + r = scanAndReduce(type.getSuperBound(), p, r); + visitedNodes.put(type, r); + return r; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java index 5de4912cf58..76564e765b9 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java @@ -29,119 +29,119 @@ */ public interface AnnotatedTypeVisitor { - /** - * A convenience method equivalent to {@code v.visit(t, null)}. - * - * @param type the type to visit - * @return a visitor-specified result - */ - public R visit(AnnotatedTypeMirror type); + /** + * A convenience method equivalent to {@code v.visit(t, null)}. + * + * @param type the type to visit + * @return a visitor-specified result + */ + public R visit(AnnotatedTypeMirror type); - /** - * Visits a type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visit(AnnotatedTypeMirror type, P p); + /** + * Visits a type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visit(AnnotatedTypeMirror type, P p); - /** - * Visits a declared type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - // public R visitType(AnnotatedTypeMirror type, P p); + /** + * Visits a declared type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + // public R visitType(AnnotatedTypeMirror type, P p); - /** - * Visits a declared type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitDeclared(AnnotatedDeclaredType type, P p); + /** + * Visits a declared type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitDeclared(AnnotatedDeclaredType type, P p); - /** - * Visits an intersection type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitIntersection(AnnotatedIntersectionType type, P p); + /** + * Visits an intersection type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitIntersection(AnnotatedIntersectionType type, P p); - /** - * Visits an union type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitUnion(AnnotatedUnionType type, P p); + /** + * Visits an union type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitUnion(AnnotatedUnionType type, P p); - /** - * Visits an executable type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitExecutable(AnnotatedExecutableType type, P p); + /** + * Visits an executable type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitExecutable(AnnotatedExecutableType type, P p); - /** - * Visits an array type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitArray(AnnotatedArrayType type, P p); + /** + * Visits an array type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitArray(AnnotatedArrayType type, P p); - /** - * Visits a type variable. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitTypeVariable(AnnotatedTypeVariable type, P p); + /** + * Visits a type variable. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitTypeVariable(AnnotatedTypeVariable type, P p); - /** - * Visits a primitive type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitPrimitive(AnnotatedPrimitiveType type, P p); + /** + * Visits a primitive type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitPrimitive(AnnotatedPrimitiveType type, P p); - /** - * Visits NoType type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitNoType(AnnotatedNoType type, P p); + /** + * Visits NoType type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitNoType(AnnotatedNoType type, P p); - /** - * Visits a {@code null} type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitNull(AnnotatedNullType type, P p); + /** + * Visits a {@code null} type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitNull(AnnotatedNullType type, P p); - /** - * Visits a wildcard type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitWildcard(AnnotatedWildcardType type, P p); + /** + * Visits a wildcard type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitWildcard(AnnotatedWildcardType type, P p); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java index 42574ddbdfc..40579704994 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java @@ -24,342 +24,342 @@ */ public interface AtmComboVisitor { - /** - * Formats type1, type2 and param into an error message used by all methods of - * AbstractAtmComboVisitor that are not overridden. Normally, this method should indicate that - * the given method (and therefore the given pair of type mirror classes) is not supported by - * this class. - * - * @param type1 the first AnnotatedTypeMirror parameter to the visit method called - * @param type2 the second AnnotatedTypeMirror parameter to the visit method called - * @param param subtype specific parameter passed to every visit method - * @return an error message - */ - default String defaultErrorMessage( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - // Message is on one line, without line breaks, because in a stack trace only the first line - // of the message may be shown. - return String.format( - "%s: unexpected combination: type: [%s %s] %s supertype: [%s %s] %s", - this.getClass().getSimpleName(), - type1.getKind(), - type1.getClass(), - type1, - type2.getKind(), - type2.getClass(), - type2); - } - - /** - * Called by the default implementation of every AbstractAtmComboVisitor visit method. This - * methodnS issues a runtime exception by default. In general, it should handle the case where a - * visit method has been called with a pair of type mirrors that should never be passed to this - * particular visitor. - * - * @param type1 the first AnnotatedTypeMirror parameter to the visit method called - * @param type2 the second AnnotatedTypeMirror parameter to the visit method called - * @param param subtype specific parameter passed to every visit method - * @return a value of type RETURN_TYPE, if no exception is thrown - */ - default RETURN_TYPE defaultAction( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - throw new BugInCF(defaultErrorMessage(type1, type2, param)); - } + /** + * Formats type1, type2 and param into an error message used by all methods of + * AbstractAtmComboVisitor that are not overridden. Normally, this method should indicate that the + * given method (and therefore the given pair of type mirror classes) is not supported by this + * class. + * + * @param type1 the first AnnotatedTypeMirror parameter to the visit method called + * @param type2 the second AnnotatedTypeMirror parameter to the visit method called + * @param param subtype specific parameter passed to every visit method + * @return an error message + */ + default String defaultErrorMessage( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + // Message is on one line, without line breaks, because in a stack trace only the first line + // of the message may be shown. + return String.format( + "%s: unexpected combination: type: [%s %s] %s supertype: [%s %s] %s", + this.getClass().getSimpleName(), + type1.getKind(), + type1.getClass(), + type1, + type2.getKind(), + type2.getClass(), + type2); + } + + /** + * Called by the default implementation of every AbstractAtmComboVisitor visit method. This + * methodnS issues a runtime exception by default. In general, it should handle the case where a + * visit method has been called with a pair of type mirrors that should never be passed to this + * particular visitor. + * + * @param type1 the first AnnotatedTypeMirror parameter to the visit method called + * @param type2 the second AnnotatedTypeMirror parameter to the visit method called + * @param param subtype specific parameter passed to every visit method + * @return a value of type RETURN_TYPE, if no exception is thrown + */ + default RETURN_TYPE defaultAction( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + throw new BugInCF(defaultErrorMessage(type1, type2, param)); + } - public RETURN_TYPE visitArray_Array( - AnnotatedArrayType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitArray_Array( + AnnotatedArrayType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitArray_Declared( - AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitArray_Declared( + AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitArray_Executable( - AnnotatedArrayType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitArray_Executable( + AnnotatedArrayType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitArray_Intersection( - AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitArray_Intersection( + AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitArray_None( - AnnotatedArrayType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitArray_None( + AnnotatedArrayType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitArray_Null( - AnnotatedArrayType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitArray_Null( + AnnotatedArrayType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitArray_Primitive( - AnnotatedArrayType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitArray_Primitive( + AnnotatedArrayType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitArray_Typevar( - AnnotatedArrayType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitArray_Typevar( + AnnotatedArrayType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitArray_Union( - AnnotatedArrayType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitArray_Union( + AnnotatedArrayType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitArray_Wildcard( - AnnotatedArrayType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitArray_Wildcard( + AnnotatedArrayType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Array( - AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Array( + AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Declared( - AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Declared( + AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Executable( - AnnotatedDeclaredType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Executable( + AnnotatedDeclaredType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Intersection( - AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Intersection( + AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitDeclared_None( - AnnotatedDeclaredType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitDeclared_None( + AnnotatedDeclaredType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Null( - AnnotatedDeclaredType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Null( + AnnotatedDeclaredType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Primitive( - AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Primitive( + AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Typevar( - AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitDeclared_Typevar( + AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitDeclared_Union( - AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Union( + AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Wildcard( - AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Wildcard( + AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Array( - AnnotatedExecutableType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Array( + AnnotatedExecutableType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Declared( - AnnotatedExecutableType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Declared( + AnnotatedExecutableType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Executable( - AnnotatedExecutableType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Executable( + AnnotatedExecutableType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Intersection( - AnnotatedExecutableType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Intersection( + AnnotatedExecutableType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitExecutable_None( - AnnotatedExecutableType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitExecutable_None( + AnnotatedExecutableType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Null( - AnnotatedExecutableType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Null( + AnnotatedExecutableType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Primitive( - AnnotatedExecutableType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Primitive( + AnnotatedExecutableType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Typevar( - AnnotatedExecutableType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitExecutable_Typevar( + AnnotatedExecutableType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitExecutable_Union( - AnnotatedExecutableType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Union( + AnnotatedExecutableType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Wildcard( - AnnotatedExecutableType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Wildcard( + AnnotatedExecutableType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Array( - AnnotatedIntersectionType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Array( + AnnotatedIntersectionType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Declared( - AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Declared( + AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Executable( - AnnotatedIntersectionType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Executable( + AnnotatedIntersectionType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Intersection( - AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Intersection( + AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitIntersection_None( - AnnotatedIntersectionType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitIntersection_None( + AnnotatedIntersectionType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Null( - AnnotatedIntersectionType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Null( + AnnotatedIntersectionType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Primitive( - AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Primitive( + AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Typevar( - AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitIntersection_Typevar( + AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitIntersection_Union( - AnnotatedIntersectionType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Union( + AnnotatedIntersectionType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Wildcard( - AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Wildcard( + AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitNone_Array( - AnnotatedNoType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitNone_Array( + AnnotatedNoType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitNone_Declared( - AnnotatedNoType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitNone_Declared( + AnnotatedNoType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitNone_Executable( - AnnotatedNoType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitNone_Executable( + AnnotatedNoType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitNone_Intersection( - AnnotatedNoType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitNone_Intersection( + AnnotatedNoType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitNone_None( - AnnotatedNoType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitNone_None( + AnnotatedNoType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitNone_Null( - AnnotatedNoType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitNone_Null( + AnnotatedNoType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitNone_Primitive( - AnnotatedNoType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitNone_Primitive( + AnnotatedNoType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitNone_Union( - AnnotatedNoType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitNone_Union( + AnnotatedNoType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitNone_Wildcard( - AnnotatedNoType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitNone_Wildcard( + AnnotatedNoType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitNull_Array( - AnnotatedNullType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitNull_Array( + AnnotatedNullType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitNull_Declared( - AnnotatedNullType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitNull_Declared( + AnnotatedNullType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitNull_Executable( - AnnotatedNullType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitNull_Executable( + AnnotatedNullType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitNull_Intersection( - AnnotatedNullType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitNull_Intersection( + AnnotatedNullType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitNull_None( - AnnotatedNullType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitNull_None( + AnnotatedNullType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitNull_Null( - AnnotatedNullType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitNull_Null( + AnnotatedNullType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitNull_Primitive( - AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitNull_Primitive( + AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitNull_Typevar( - AnnotatedNullType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitNull_Typevar( + AnnotatedNullType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitNull_Union( - AnnotatedNullType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitNull_Union( + AnnotatedNullType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitNull_Wildcard( - AnnotatedNullType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitNull_Wildcard( + AnnotatedNullType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Array( - AnnotatedPrimitiveType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Array( + AnnotatedPrimitiveType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Declared( - AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Declared( + AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Executable( - AnnotatedPrimitiveType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Executable( + AnnotatedPrimitiveType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Intersection( - AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Intersection( + AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_None( - AnnotatedPrimitiveType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_None( + AnnotatedPrimitiveType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Null( - AnnotatedPrimitiveType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Null( + AnnotatedPrimitiveType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Primitive( - AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Primitive( + AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Typevar( - AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Typevar( + AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Union( - AnnotatedPrimitiveType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Union( + AnnotatedPrimitiveType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Wildcard( - AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Wildcard( + AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitUnion_Array( - AnnotatedUnionType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitUnion_Array( + AnnotatedUnionType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitUnion_Declared( - AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitUnion_Declared( + AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitUnion_Executable( - AnnotatedUnionType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitUnion_Executable( + AnnotatedUnionType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitUnion_Intersection( - AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitUnion_Intersection( + AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitUnion_None( - AnnotatedUnionType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitUnion_None( + AnnotatedUnionType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitUnion_Null( - AnnotatedUnionType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitUnion_Null( + AnnotatedUnionType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitUnion_Primitive( - AnnotatedUnionType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitUnion_Primitive( + AnnotatedUnionType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitUnion_Typevar( - AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitUnion_Typevar( + AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitUnion_Union( - AnnotatedUnionType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitUnion_Union( + AnnotatedUnionType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitUnion_Wildcard( - AnnotatedUnionType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitUnion_Wildcard( + AnnotatedUnionType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Array( - AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Array( + AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Declared( - AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Declared( + AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Executable( - AnnotatedTypeVariable subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Executable( + AnnotatedTypeVariable subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Intersection( - AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Intersection( + AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitTypevar_None( - AnnotatedTypeVariable subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitTypevar_None( + AnnotatedTypeVariable subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Null( - AnnotatedTypeVariable subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Null( + AnnotatedTypeVariable subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Primitive( - AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Primitive( + AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Typevar( - AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitTypevar_Typevar( + AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitTypevar_Union( - AnnotatedTypeVariable subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Union( + AnnotatedTypeVariable subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Wildcard( - AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Wildcard( + AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Array( - AnnotatedWildcardType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Array( + AnnotatedWildcardType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Declared( - AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Declared( + AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Executable( - AnnotatedWildcardType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Executable( + AnnotatedWildcardType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Intersection( - AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Intersection( + AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitWildcard_None( - AnnotatedWildcardType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitWildcard_None( + AnnotatedWildcardType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Null( - AnnotatedWildcardType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Null( + AnnotatedWildcardType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Primitive( - AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Primitive( + AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Typevar( - AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitWildcard_Typevar( + AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitWildcard_Union( - AnnotatedWildcardType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Union( + AnnotatedWildcardType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Wildcard( - AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Wildcard( + AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, PARAM param); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java index 2bc0564f692..bfc3773c874 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java @@ -1,5 +1,6 @@ package org.checkerframework.framework.type.visitor; +import java.util.Iterator; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -10,8 +11,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.javacutil.BugInCF; -import java.util.Iterator; - /** * An {@link AnnotatedTypeScanner} that scans two {@link AnnotatedTypeMirror}s simultaneously and * performs {@link #defaultAction(AnnotatedTypeMirror, AnnotatedTypeMirror)} on the pair. Both @@ -27,190 +26,183 @@ * @param the result of scanning the two {@code AnnotatedTypeMirror}s */ public abstract class DoubleAnnotatedTypeScanner - extends AnnotatedTypeScanner { - - /** - * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it - * is nonnull; otherwise the second result is returned. The default result is {@code null}. - */ - protected DoubleAnnotatedTypeScanner() { - super(); - } - - /** - * Creates a scanner with the given {@code reduce} function and {@code defaultResult}. - * - * @param reduce function used to combine the results of scan - * @param defaultResult result to use by default - */ - protected DoubleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { - super(reduce, defaultResult); - } - - /** - * Called by default for any visit method that is not overridden. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - protected abstract R defaultAction(AnnotatedTypeMirror type, AnnotatedTypeMirror p); - - /** - * Scans {@code types1} and {@code types2}. If they are empty, then {@link #defaultResult} is - * returned. - * - * @param types1 types - * @param types2 types - * @return the result of scanning and reducing all the types in {@code types1} and {@code - * types2} or {@link #defaultResult} if they are empty - */ - protected R scan( - Iterable types1, - Iterable types2) { - if (types1 == null || types2 == null) { - return defaultResult; - } - R r = defaultResult; - boolean first = true; - Iterator iter1 = types1.iterator(); - Iterator iter2 = types2.iterator(); - while (iter1.hasNext() && iter2.hasNext()) { - r = - (first - ? scan(iter1.next(), iter2.next()) - : scanAndReduce(iter1.next(), iter2.next(), r)); - first = false; - } - return r; - } - - /** - * Run {@link #scan} on types and p, then run {@link #reduce} on the result (plus r) to return a - * single element. - */ - protected R scanAndReduce( - Iterable types, - Iterable p, - R r) { - return reduce(scan(types, p), r); + extends AnnotatedTypeScanner { + + /** + * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is + * nonnull; otherwise the second result is returned. The default result is {@code null}. + */ + protected DoubleAnnotatedTypeScanner() { + super(); + } + + /** + * Creates a scanner with the given {@code reduce} function and {@code defaultResult}. + * + * @param reduce function used to combine the results of scan + * @param defaultResult result to use by default + */ + protected DoubleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { + super(reduce, defaultResult); + } + + /** + * Called by default for any visit method that is not overridden. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + protected abstract R defaultAction(AnnotatedTypeMirror type, AnnotatedTypeMirror p); + + /** + * Scans {@code types1} and {@code types2}. If they are empty, then {@link #defaultResult} is + * returned. + * + * @param types1 types + * @param types2 types + * @return the result of scanning and reducing all the types in {@code types1} and {@code types2} + * or {@link #defaultResult} if they are empty + */ + protected R scan( + Iterable types1, + Iterable types2) { + if (types1 == null || types2 == null) { + return defaultResult; } - - @Override - protected final R scanAndReduce( - Iterable types, AnnotatedTypeMirror p, R r) { - throw new BugInCF( - "DoubleAnnotatedTypeScanner.scanAndReduce: " - + p - + " is not Iterable"); + R r = defaultResult; + boolean first = true; + Iterator iter1 = types1.iterator(); + Iterator iter2 = types2.iterator(); + while (iter1.hasNext() && iter2.hasNext()) { + r = (first ? scan(iter1.next(), iter2.next()) : scanAndReduce(iter1.next(), iter2.next(), r)); + first = false; } - - @Override - protected R scan(AnnotatedTypeMirror type, AnnotatedTypeMirror p) { - return reduce(super.scan(type, p), defaultAction(type, p)); + return r; + } + + /** + * Run {@link #scan} on types and p, then run {@link #reduce} on the result (plus r) to return a + * single element. + */ + protected R scanAndReduce( + Iterable types, + Iterable p, + R r) { + return reduce(scan(types, p), r); + } + + @Override + protected final R scanAndReduce( + Iterable types, AnnotatedTypeMirror p, R r) { + throw new BugInCF( + "DoubleAnnotatedTypeScanner.scanAndReduce: " + + p + + " is not Iterable"); + } + + @Override + protected R scan(AnnotatedTypeMirror type, AnnotatedTypeMirror p) { + return reduce(super.scan(type, p), defaultAction(type, p)); + } + + @Override + public final R visitDeclared(AnnotatedDeclaredType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedDeclaredType : p; + R r = scan(type.getTypeArguments(), ((AnnotatedDeclaredType) p).getTypeArguments()); + if (type.getEnclosingType() != null) { + r = scanAndReduce(type.getEnclosingType(), ((AnnotatedDeclaredType) p).getEnclosingType(), r); } - - @Override - public final R visitDeclared(AnnotatedDeclaredType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedDeclaredType : p; - R r = scan(type.getTypeArguments(), ((AnnotatedDeclaredType) p).getTypeArguments()); - if (type.getEnclosingType() != null) { - r = - scanAndReduce( - type.getEnclosingType(), - ((AnnotatedDeclaredType) p).getEnclosingType(), - r); - } - return r; + return r; + } + + @Override + public final R visitArray(AnnotatedArrayType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedArrayType : p; + R r = scan(type.getComponentType(), ((AnnotatedArrayType) p).getComponentType()); + return r; + } + + @Override + public final R visitExecutable(AnnotatedExecutableType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedExecutableType : p; + AnnotatedExecutableType ex = (AnnotatedExecutableType) p; + R r = scan(type.getReturnType(), ex.getReturnType()); + if (type.getReceiverType() != null) { + r = scanAndReduce(type.getReceiverType(), ex.getReceiverType(), r); } - - @Override - public final R visitArray(AnnotatedArrayType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedArrayType : p; - R r = scan(type.getComponentType(), ((AnnotatedArrayType) p).getComponentType()); - return r; + r = scanAndReduce(type.getParameterTypes(), ex.getParameterTypes(), r); + r = scanAndReduce(type.getThrownTypes(), ex.getThrownTypes(), r); + r = scanAndReduce(type.getTypeVariables(), ex.getTypeVariables(), r); + return r; + } + + @Override + public R visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeMirror p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - - @Override - public final R visitExecutable(AnnotatedExecutableType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedExecutableType : p; - AnnotatedExecutableType ex = (AnnotatedExecutableType) p; - R r = scan(type.getReturnType(), ex.getReturnType()); - if (type.getReceiverType() != null) { - r = scanAndReduce(type.getReceiverType(), ex.getReceiverType(), r); - } - r = scanAndReduce(type.getParameterTypes(), ex.getParameterTypes(), r); - r = scanAndReduce(type.getThrownTypes(), ex.getThrownTypes(), r); - r = scanAndReduce(type.getTypeVariables(), ex.getTypeVariables(), r); - return r; + visitedNodes.put(type, null); + + R r; + if (p instanceof AnnotatedTypeVariable) { + AnnotatedTypeVariable tv = (AnnotatedTypeVariable) p; + r = scan(type.getLowerBound(), tv.getLowerBound()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getUpperBound(), tv.getUpperBound(), r); + visitedNodes.put(type, r); + } else { + r = scan(type.getLowerBound(), p.getErased()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getUpperBound(), p.getErased(), r); + visitedNodes.put(type, r); } + return r; + } - @Override - public R visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeMirror p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, null); - - R r; - if (p instanceof AnnotatedTypeVariable) { - AnnotatedTypeVariable tv = (AnnotatedTypeVariable) p; - r = scan(type.getLowerBound(), tv.getLowerBound()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getUpperBound(), tv.getUpperBound(), r); - visitedNodes.put(type, r); - } else { - r = scan(type.getLowerBound(), p.getErased()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getUpperBound(), p.getErased(), r); - visitedNodes.put(type, r); - } - return r; + @Override + public R visitWildcard(AnnotatedWildcardType type, AnnotatedTypeMirror p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - - @Override - public R visitWildcard(AnnotatedWildcardType type, AnnotatedTypeMirror p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, null); - - R r; - if (p instanceof AnnotatedWildcardType) { - AnnotatedWildcardType w = (AnnotatedWildcardType) p; - r = scan(type.getExtendsBound(), w.getExtendsBound()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getSuperBound(), w.getSuperBound(), r); - visitedNodes.put(type, r); - } else { - r = scan(type.getExtendsBound(), p.getErased()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getSuperBound(), p.getErased(), r); - visitedNodes.put(type, r); - } - return r; + visitedNodes.put(type, null); + + R r; + if (p instanceof AnnotatedWildcardType) { + AnnotatedWildcardType w = (AnnotatedWildcardType) p; + r = scan(type.getExtendsBound(), w.getExtendsBound()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getSuperBound(), w.getSuperBound(), r); + visitedNodes.put(type, r); + } else { + r = scan(type.getExtendsBound(), p.getErased()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getSuperBound(), p.getErased(), r); + visitedNodes.put(type, r); } + return r; + } - @Override - public R visitIntersection(AnnotatedIntersectionType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedIntersectionType : p; + @Override + public R visitIntersection(AnnotatedIntersectionType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedIntersectionType : p; - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, null); - R r = scan(type.getBounds(), ((AnnotatedIntersectionType) p).getBounds()); - return r; + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - - @Override - public R visitUnion(AnnotatedUnionType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedUnionType : p; - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, null); - R r = scan(type.getAlternatives(), ((AnnotatedUnionType) p).getAlternatives()); - return r; + visitedNodes.put(type, null); + R r = scan(type.getBounds(), ((AnnotatedIntersectionType) p).getBounds()); + return r; + } + + @Override + public R visitUnion(AnnotatedUnionType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedUnionType : p; + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } + visitedNodes.put(type, null); + R r = scan(type.getAlternatives(), ((AnnotatedUnionType) p).getAlternatives()); + return r; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java index 647d5e3d83e..26d93af991a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java @@ -1,5 +1,7 @@ package org.checkerframework.framework.type.visitor; +import java.util.IdentityHashMap; +import java.util.Iterator; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -14,235 +16,227 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.util.AtmCombo; -import java.util.IdentityHashMap; -import java.util.Iterator; - /** * EquivalentAtmComboScanner is an AtmComboVisitor that accepts combinations that are identical in * TypeMirror structure but might differ in contained AnnotationMirrors. This method will scan the * individual components of the visited type pairs together. */ public abstract class EquivalentAtmComboScanner - extends AbstractAtmComboVisitor { + extends AbstractAtmComboVisitor { - /** - * A history of type pairs that have already been visited and the return type of their visit. - */ - protected final Visited visited = new Visited(); + /** A history of type pairs that have already been visited and the return type of their visit. */ + protected final Visited visited = new Visited(); + + /** Entry point for this scanner. */ + @Override + public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + visited.clear(); + return scan(type1, type2, param); + } + + /** + * In an AnnotatedTypeScanner a null type is encounter than null is returned. A user may want to + * customize the behavior of this scanner depending on whether or not one or both types is null. + * + * @param type1 a nullable AnnotatedTypeMirror + * @param type2 a nullable AnnotatedTypeMirror + * @param param the visitor param + * @return a subclass specific return type/value + */ + protected abstract RETURN_TYPE scanWithNull( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param); + + protected RETURN_TYPE scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + if (type1 == null || type2 == null) { + return scanWithNull(type1, type2, param); + } + + return AtmCombo.accept(type1, type2, param, this); + } + + protected RETURN_TYPE scan( + Iterable types1, + Iterable types2, + PARAM param) { + RETURN_TYPE r = null; + boolean first = true; + + Iterator tIter1 = types1.iterator(); + Iterator tIter2 = types2.iterator(); + + while (tIter1.hasNext() && tIter2.hasNext()) { + AnnotatedTypeMirror type1 = tIter1.next(); + AnnotatedTypeMirror type2 = tIter2.next(); + + r = first ? scan(type1, type2, param) : scanAndReduce(type1, type2, param, r); + } + + return r; + } + + protected RETURN_TYPE scanAndReduce( + Iterable types1, + Iterable types2, + PARAM param, + RETURN_TYPE r) { + return reduce(scan(types1, types2, param), r); + } + + protected RETURN_TYPE scanAndReduce( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param, RETURN_TYPE r) { + return reduce(scan(type1, type2, param), r); + } + + protected RETURN_TYPE reduce(RETURN_TYPE r1, RETURN_TYPE r2) { + if (r1 == null) { + return r2; + } + return r1; + } + + @Override + public RETURN_TYPE visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } + visited.add(type1, type2, null); + + return scan(type1.getComponentType(), type2.getComponentType(), param); + } - /** Entry point for this scanner. */ - @Override - public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - visited.clear(); - return scan(type1, type2, param); + @Override + public RETURN_TYPE visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); } + visited.add(type1, type2, null); - /** - * In an AnnotatedTypeScanner a null type is encounter than null is returned. A user may want to - * customize the behavior of this scanner depending on whether or not one or both types is null. - * - * @param type1 a nullable AnnotatedTypeMirror - * @param type2 a nullable AnnotatedTypeMirror - * @param param the visitor param - * @return a subclass specific return type/value - */ - protected abstract RETURN_TYPE scanWithNull( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param); + return scan(type1.getTypeArguments(), type2.getTypeArguments(), param); + } - protected RETURN_TYPE scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - if (type1 == null || type2 == null) { - return scanWithNull(type1, type2, param); - } - - return AtmCombo.accept(type1, type2, param, this); + @Override + public RETURN_TYPE visitExecutable_Executable( + AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); } + visited.add(type1, type2, null); - protected RETURN_TYPE scan( - Iterable types1, - Iterable types2, - PARAM param) { - RETURN_TYPE r = null; - boolean first = true; - - Iterator tIter1 = types1.iterator(); - Iterator tIter2 = types2.iterator(); + RETURN_TYPE r = scan(type1.getReturnType(), type2.getReturnType(), param); + r = scanAndReduce(type1.getReceiverType(), type2.getReceiverType(), param, r); + r = scanAndReduce(type1.getParameterTypes(), type2.getParameterTypes(), param, r); + r = scanAndReduce(type1.getThrownTypes(), type2.getThrownTypes(), param, r); + r = scanAndReduce(type1.getTypeVariables(), type2.getTypeVariables(), param, r); + return r; + } - while (tIter1.hasNext() && tIter2.hasNext()) { - AnnotatedTypeMirror type1 = tIter1.next(); - AnnotatedTypeMirror type2 = tIter2.next(); - - r = first ? scan(type1, type2, param) : scanAndReduce(type1, type2, param, r); - } - - return r; + @Override + public RETURN_TYPE visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); } + visited.add(type1, type2, null); - protected RETURN_TYPE scanAndReduce( - Iterable types1, - Iterable types2, - PARAM param, - RETURN_TYPE r) { - return reduce(scan(types1, types2, param), r); - } + return scan(type1.getBounds(), type2.getBounds(), param); + } - protected RETURN_TYPE scanAndReduce( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param, RETURN_TYPE r) { - return reduce(scan(type1, type2, param), r); - } + @Override + public @Nullable RETURN_TYPE visitNone_None( + AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { + return null; + } - protected RETURN_TYPE reduce(RETURN_TYPE r1, RETURN_TYPE r2) { - if (r1 == null) { - return r2; - } - return r1; - } + @Override + public @Nullable RETURN_TYPE visitNull_Null( + AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { + return null; + } - @Override - public RETURN_TYPE visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } - visited.add(type1, type2, null); + @Override + public @Nullable RETURN_TYPE visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { + return null; + } - return scan(type1.getComponentType(), type2.getComponentType(), param); + @Override + public RETURN_TYPE visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); } - @Override - public RETURN_TYPE visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } - visited.add(type1, type2, null); + visited.add(type1, type2, null); - return scan(type1.getTypeArguments(), type2.getTypeArguments(), param); - } + return scan(type1.getAlternatives(), type2.getAlternatives(), param); + } - @Override - public RETURN_TYPE visitExecutable_Executable( - AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } - visited.add(type1, type2, null); - - RETURN_TYPE r = scan(type1.getReturnType(), type2.getReturnType(), param); - r = scanAndReduce(type1.getReceiverType(), type2.getReceiverType(), param, r); - r = scanAndReduce(type1.getParameterTypes(), type2.getParameterTypes(), param, r); - r = scanAndReduce(type1.getThrownTypes(), type2.getThrownTypes(), param, r); - r = scanAndReduce(type1.getTypeVariables(), type2.getTypeVariables(), param, r); - return r; + @Override + public RETURN_TYPE visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); } - @Override - public RETURN_TYPE visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } - visited.add(type1, type2, null); + visited.add(type1, type2, null); - return scan(type1.getBounds(), type2.getBounds(), param); - } + RETURN_TYPE r = scan(type1.getUpperBound(), type2.getUpperBound(), param); + r = scanAndReduce(type1.getLowerBound(), type2.getLowerBound(), param, r); + return r; + } - @Override - public @Nullable RETURN_TYPE visitNone_None( - AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { - return null; + @Override + public RETURN_TYPE visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); } - @Override - public @Nullable RETURN_TYPE visitNull_Null( - AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { - return null; - } + visited.add(type1, type2, null); - @Override - public @Nullable RETURN_TYPE visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { - return null; - } + RETURN_TYPE r = scan(type1.getExtendsBound(), type2.getExtendsBound(), param); + r = scanAndReduce(type1.getSuperBound(), type2.getSuperBound(), param, r); + return r; + } - @Override - public RETURN_TYPE visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } + /** A history of type pairs that have already been visited and the return type of their visit. */ + protected class Visited { - visited.add(type1, type2, null); + private final IdentityHashMap< + AnnotatedTypeMirror, IdentityHashMap> + visits = new IdentityHashMap<>(); - return scan(type1.getAlternatives(), type2.getAlternatives(), param); + public void clear() { + visits.clear(); } - @Override - public RETURN_TYPE visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } - - visited.add(type1, type2, null); - - RETURN_TYPE r = scan(type1.getUpperBound(), type2.getUpperBound(), param); - r = scanAndReduce(type1.getLowerBound(), type2.getLowerBound(), param, r); - return r; + public boolean contains(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + IdentityHashMap recordFor1 = visits.get(type1); + return recordFor1 != null && recordFor1.containsKey(type2); } - @Override - public RETURN_TYPE visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } - - visited.add(type1, type2, null); + public @Nullable RETURN_TYPE getResult(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + IdentityHashMap recordFor1 = visits.get(type1); + if (recordFor1 == null) { + return null; + } - RETURN_TYPE r = scan(type1.getExtendsBound(), type2.getExtendsBound(), param); - r = scanAndReduce(type1.getSuperBound(), type2.getSuperBound(), param, r); - return r; + return recordFor1.get(type2); } /** - * A history of type pairs that have already been visited and the return type of their visit. + * Add a new pair to the history. + * + * @param type1 the first type + * @param type2 the second type + * @param ret the result */ - protected class Visited { - - private final IdentityHashMap< - AnnotatedTypeMirror, IdentityHashMap> - visits = new IdentityHashMap<>(); - - public void clear() { - visits.clear(); - } - - public boolean contains(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - IdentityHashMap recordFor1 = visits.get(type1); - return recordFor1 != null && recordFor1.containsKey(type2); - } - - public @Nullable RETURN_TYPE getResult( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - IdentityHashMap recordFor1 = visits.get(type1); - if (recordFor1 == null) { - return null; - } - - return recordFor1.get(type2); - } - - /** - * Add a new pair to the history. - * - * @param type1 the first type - * @param type2 the second type - * @param ret the result - */ - public void add(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, RETURN_TYPE ret) { - IdentityHashMap recordFor1 = - visits.computeIfAbsent(type1, __ -> new IdentityHashMap<>()); - recordFor1.put(type2, ret); - } + public void add(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, RETURN_TYPE ret) { + IdentityHashMap recordFor1 = + visits.computeIfAbsent(type1, __ -> new IdentityHashMap<>()); + recordFor1.put(type2, ret); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java index a06280e6440..a57be68b6b9 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java @@ -27,189 +27,188 @@ */ public class SimpleAnnotatedTypeScanner extends AnnotatedTypeScanner { - /** - * Represents an action to perform on every type. - * - * @param the type of the result of the action - * @param

the type of the parameter of action - */ - @FunctionalInterface - public interface DefaultAction { - - /** - * The action to perform on every type. - * - * @param type a type on which to perform some action - * @param p argument to pass to the action - * @return result of the action - */ - R defaultAction(AnnotatedTypeMirror type, P p); - } - - /** The action to perform on every type. */ - protected final DefaultAction defaultAction; - - /** - * Creates a scanner that performs {@code defaultAction} on every type. - * - *

Use this constructor if the type of result of the default action is {@link Void}. - * - * @param defaultAction action to perform on every type - */ - public SimpleAnnotatedTypeScanner(DefaultAction defaultAction) { - this(defaultAction, null, null); - } - - /** - * Creates a scanner that performs {@code defaultAction} on every type and use {@code reduce} to - * combine the results. - * - *

Use this constructor if the default action returns a result. - * - * @param defaultAction action to perform on every type - * @param reduce function used to combine results - * @param defaultResult result to use by default - */ - public SimpleAnnotatedTypeScanner( - DefaultAction defaultAction, Reduce reduce, R defaultResult) { - super(reduce, defaultResult); - this.defaultAction = defaultAction; - } - - /** - * Creates a scanner without specifying the default action. Subclasses may only use this - * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. - */ - protected SimpleAnnotatedTypeScanner() { - this(null, null, null); - } - - /** - * Creates a scanner without specifying the default action. Subclasses may only use this - * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. - * - * @param reduce function used to combine results - * @param defaultResult result to use by default - */ - protected SimpleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { - this(null, reduce, defaultResult); - } - - /** - * Called by default for any visit method that is not overridden. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - protected R defaultAction(AnnotatedTypeMirror type, P p) { - if (defaultAction == null) { - // The no argument constructor sets default action to null. - throw new BugInCF( - "%s did not provide a default action. Please override #defaultAction", - this.getClass()); - } - return defaultAction.defaultAction(type, p); - } - - /** - * Visits a declared type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitDeclared(AnnotatedDeclaredType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitDeclared(type, p), r); - } - - /** - * Visits an executable type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitExecutable(AnnotatedExecutableType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitExecutable(type, p), r); - } - - /** - * Visits an array type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitArray(AnnotatedArrayType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitArray(type, p), r); - } - - /** - * Visits a type variable. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitTypeVariable(AnnotatedTypeVariable type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitTypeVariable(type, p), r); - } - - /** - * Visits a primitive type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitPrimitive(AnnotatedPrimitiveType type, P p) { - return defaultAction(type, p); - } - - /** - * Visits NoType type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitNoType(AnnotatedNoType type, P p) { - return defaultAction(type, p); - } - - /** - * Visits a {@code null} type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitNull(AnnotatedNullType type, P p) { - return defaultAction(type, p); - } + /** + * Represents an action to perform on every type. + * + * @param the type of the result of the action + * @param

the type of the parameter of action + */ + @FunctionalInterface + public interface DefaultAction { /** - * Visits a wildcard type. + * The action to perform on every type. * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result + * @param type a type on which to perform some action + * @param p argument to pass to the action + * @return result of the action */ - @Override - public final R visitWildcard(AnnotatedWildcardType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitWildcard(type, p), r); + R defaultAction(AnnotatedTypeMirror type, P p); + } + + /** The action to perform on every type. */ + protected final DefaultAction defaultAction; + + /** + * Creates a scanner that performs {@code defaultAction} on every type. + * + *

Use this constructor if the type of result of the default action is {@link Void}. + * + * @param defaultAction action to perform on every type + */ + public SimpleAnnotatedTypeScanner(DefaultAction defaultAction) { + this(defaultAction, null, null); + } + + /** + * Creates a scanner that performs {@code defaultAction} on every type and use {@code reduce} to + * combine the results. + * + *

Use this constructor if the default action returns a result. + * + * @param defaultAction action to perform on every type + * @param reduce function used to combine results + * @param defaultResult result to use by default + */ + public SimpleAnnotatedTypeScanner( + DefaultAction defaultAction, Reduce reduce, R defaultResult) { + super(reduce, defaultResult); + this.defaultAction = defaultAction; + } + + /** + * Creates a scanner without specifying the default action. Subclasses may only use this + * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. + */ + protected SimpleAnnotatedTypeScanner() { + this(null, null, null); + } + + /** + * Creates a scanner without specifying the default action. Subclasses may only use this + * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. + * + * @param reduce function used to combine results + * @param defaultResult result to use by default + */ + protected SimpleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { + this(null, reduce, defaultResult); + } + + /** + * Called by default for any visit method that is not overridden. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + protected R defaultAction(AnnotatedTypeMirror type, P p) { + if (defaultAction == null) { + // The no argument constructor sets default action to null. + throw new BugInCF( + "%s did not provide a default action. Please override #defaultAction", this.getClass()); } + return defaultAction.defaultAction(type, p); + } + + /** + * Visits a declared type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitDeclared(AnnotatedDeclaredType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitDeclared(type, p), r); + } + + /** + * Visits an executable type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitExecutable(AnnotatedExecutableType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitExecutable(type, p), r); + } + + /** + * Visits an array type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitArray(AnnotatedArrayType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitArray(type, p), r); + } + + /** + * Visits a type variable. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitTypeVariable(AnnotatedTypeVariable type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitTypeVariable(type, p), r); + } + + /** + * Visits a primitive type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitPrimitive(AnnotatedPrimitiveType type, P p) { + return defaultAction(type, p); + } + + /** + * Visits NoType type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitNoType(AnnotatedNoType type, P p) { + return defaultAction(type, p); + } + + /** + * Visits a {@code null} type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitNull(AnnotatedNullType type, P p) { + return defaultAction(type, p); + } + + /** + * Visits a wildcard type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitWildcard(AnnotatedWildcardType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitWildcard(type, p), r); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java index ef2f396f6a2..b5ec2184999 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java @@ -22,95 +22,95 @@ */ public abstract class SimpleAnnotatedTypeVisitor implements AnnotatedTypeVisitor { - /** The default value to return as a default action. */ - protected final R DEFAULT_VALUE; - - /** - * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with default value being {@code - * null}. - */ - protected SimpleAnnotatedTypeVisitor() { - this(null); - } - - /** - * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with the default value being the - * passed defaultValue. - * - * @param defaultValue the default value this class should return - */ - protected SimpleAnnotatedTypeVisitor(R defaultValue) { - this.DEFAULT_VALUE = defaultValue; - } - - /** - * Performs the default action for visiting trees, if subclasses do not override the visitFOO - * node. - * - *

This implementation merely returns the default value (as specified by the protected field - * {@code DEFAULT_VALUE}). - */ - protected R defaultAction(AnnotatedTypeMirror type, P p) { - return DEFAULT_VALUE; - } - - @Override - public R visit(AnnotatedTypeMirror type) { - return visit(type, null); - } - - @Override - public R visit(AnnotatedTypeMirror type, P p) { - return (type == null) ? null : type.accept(this, p); - } - - @Override - public R visitDeclared(AnnotatedDeclaredType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitIntersection(AnnotatedIntersectionType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitUnion(AnnotatedUnionType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitArray(AnnotatedArrayType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitExecutable(AnnotatedExecutableType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitTypeVariable(AnnotatedTypeVariable type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitWildcard(AnnotatedWildcardType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitPrimitive(AnnotatedPrimitiveType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitNull(AnnotatedNullType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitNoType(AnnotatedNoType type, P p) { - return defaultAction(type, p); - } + /** The default value to return as a default action. */ + protected final R DEFAULT_VALUE; + + /** + * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with default value being {@code + * null}. + */ + protected SimpleAnnotatedTypeVisitor() { + this(null); + } + + /** + * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with the default value being the + * passed defaultValue. + * + * @param defaultValue the default value this class should return + */ + protected SimpleAnnotatedTypeVisitor(R defaultValue) { + this.DEFAULT_VALUE = defaultValue; + } + + /** + * Performs the default action for visiting trees, if subclasses do not override the visitFOO + * node. + * + *

This implementation merely returns the default value (as specified by the protected field + * {@code DEFAULT_VALUE}). + */ + protected R defaultAction(AnnotatedTypeMirror type, P p) { + return DEFAULT_VALUE; + } + + @Override + public R visit(AnnotatedTypeMirror type) { + return visit(type, null); + } + + @Override + public R visit(AnnotatedTypeMirror type, P p) { + return (type == null) ? null : type.accept(this, p); + } + + @Override + public R visitDeclared(AnnotatedDeclaredType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitIntersection(AnnotatedIntersectionType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitUnion(AnnotatedUnionType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitArray(AnnotatedArrayType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitExecutable(AnnotatedExecutableType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitTypeVariable(AnnotatedTypeVariable type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitWildcard(AnnotatedWildcardType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitPrimitive(AnnotatedPrimitiveType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitNull(AnnotatedNullType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitNoType(AnnotatedNoType type, P p) { + return defaultAction(type, p); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index 62c4c482ed6..b2930b9df5b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -8,32 +8,6 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.AsSuperVisitor; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.SyntheticArrays; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.IPair; -import org.plumelib.util.StringsPlume; - import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -46,7 +20,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -60,1632 +33,1610 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.AsSuperVisitor; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.SyntheticArrays; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.IPair; +import org.plumelib.util.StringsPlume; /** * Utility methods for operating on {@code AnnotatedTypeMirror}. This class mimics the class {@link * Types}. */ public class AnnotatedTypes { - /** Class cannot be instantiated. */ - private AnnotatedTypes() { - throw new AssertionError("Class AnnotatedTypes cannot be instantiated."); + /** Class cannot be instantiated. */ + private AnnotatedTypes() { + throw new AssertionError("Class AnnotatedTypes cannot be instantiated."); + } + + /** Implements {@code asSuper}. */ + private static @MonotonicNonNull AsSuperVisitor asSuperVisitor; + + /** + * Copies annotations from {@code type} to a copy of {@code superType} where the type variables of + * {@code superType} have been substituted. How the annotations are copied depends on the kinds of + * AnnotatedTypeMirrors given. Generally, if {@code type} and {@code superType} are both declared + * types, asSuper is called recursively on the direct super types, see {@link + * AnnotatedTypeMirror#directSupertypes()}, of {@code type} until {@code type}'s erased Java type + * is the same as {@code superType}'s erased super type. Then {@code type is returned}. For + * compound types, asSuper is called recursively on components. + * + *

Preconditions:
+ * {@code superType} may have annotations, but they are ignored.
+ * {@code type} may not be an instanceof AnnotatedNullType, because if {@code superType} is a + * compound type, the annotations on the component types are undefined.
+ * The underlying {@code type} (ie the Java type) of {@code type} should be a subtype (or the same + * type) of the underlying type of {@code superType}. Except for these cases: + * + *

    + *
  • If {@code type} is a primitive, then the boxed type of {@code type} must be subtype of + * {@code superType}. + *
  • If {@code superType} is a primitive, then {@code type} must be convertible to {@code + * superType}. + *
  • If {@code superType} is a type variable or wildcard without a lower bound, then {@code + * type} must be a subtype of the upper bound of {@code superType}. (This relaxed rule is + * used during type argument inference where the type variable or wildcard is the type + * argument that was inferred.) + *
  • If {@code superType} is a wildcard with a lower bound, then {@code type} must be a + * subtype of the lower bound of {@code superType}. + *
+ * + *

Postconditions: {@code type} and {@code superType} are not modified. + * + * @param atypeFactory {@link AnnotatedTypeFactory} + * @param type type from which to copy annotations + * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java + * type. + * @return {@code superType} with annotations copied from {@code type} and type variables + * substituted from {@code type}. + */ + public static T asSuper( + AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type, T superType) { + if (asSuperVisitor == null || !asSuperVisitor.sameAnnotatedTypeFactory(atypeFactory)) { + asSuperVisitor = new AsSuperVisitor(atypeFactory); } - - /** Implements {@code asSuper}. */ - private static @MonotonicNonNull AsSuperVisitor asSuperVisitor; - - /** - * Copies annotations from {@code type} to a copy of {@code superType} where the type variables - * of {@code superType} have been substituted. How the annotations are copied depends on the - * kinds of AnnotatedTypeMirrors given. Generally, if {@code type} and {@code superType} are - * both declared types, asSuper is called recursively on the direct super types, see {@link - * AnnotatedTypeMirror#directSupertypes()}, of {@code type} until {@code type}'s erased Java - * type is the same as {@code superType}'s erased super type. Then {@code type is returned}. For - * compound types, asSuper is called recursively on components. - * - *

Preconditions:
- * {@code superType} may have annotations, but they are ignored.
- * {@code type} may not be an instanceof AnnotatedNullType, because if {@code superType} is a - * compound type, the annotations on the component types are undefined.
- * The underlying {@code type} (ie the Java type) of {@code type} should be a subtype (or the - * same type) of the underlying type of {@code superType}. Except for these cases: - * - *

    - *
  • If {@code type} is a primitive, then the boxed type of {@code type} must be subtype of - * {@code superType}. - *
  • If {@code superType} is a primitive, then {@code type} must be convertible to {@code - * superType}. - *
  • If {@code superType} is a type variable or wildcard without a lower bound, then {@code - * type} must be a subtype of the upper bound of {@code superType}. (This relaxed rule is - * used during type argument inference where the type variable or wildcard is the type - * argument that was inferred.) - *
  • If {@code superType} is a wildcard with a lower bound, then {@code type} must be a - * subtype of the lower bound of {@code superType}. - *
- * - *

Postconditions: {@code type} and {@code superType} are not modified. - * - * @param atypeFactory {@link AnnotatedTypeFactory} - * @param type type from which to copy annotations - * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java - * type. - * @return {@code superType} with annotations copied from {@code type} and type variables - * substituted from {@code type}. - */ - public static T asSuper( - AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type, T superType) { - if (asSuperVisitor == null || !asSuperVisitor.sameAnnotatedTypeFactory(atypeFactory)) { - asSuperVisitor = new AsSuperVisitor(atypeFactory); - } - return asSuperVisitor.asSuper(type, superType); + return asSuperVisitor.asSuper(type, superType); + } + + /** + * Calls asSuper and casts the result to the same type as the input supertype. + * + * @param the type of supertype and return type + * @param atypeFactory the type factory + * @param subtype subtype to be transformed to supertype + * @param supertype supertype that subtype is transformed to + * @return subtype as an instance of supertype + */ + public static T castedAsSuper( + AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror subtype, T supertype) { + Types types = atypeFactory.getProcessingEnv().getTypeUtils(); + + if (subtype.getKind() == TypeKind.NULL) { + // Make a copy of the supertype so that if supertype is a composite type, the + // returned type will be fully annotated. (For example, if sub is @C null and super is + // @A List<@B String>, then the returned type is @C List<@B String>.) + @SuppressWarnings("unchecked") + T copy = (T) supertype.deepCopy(); + copy.replaceAnnotations(subtype.getAnnotations()); + return copy; } - /** - * Calls asSuper and casts the result to the same type as the input supertype. - * - * @param the type of supertype and return type - * @param atypeFactory the type factory - * @param subtype subtype to be transformed to supertype - * @param supertype supertype that subtype is transformed to - * @return subtype as an instance of supertype - */ - public static T castedAsSuper( - AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror subtype, T supertype) { - Types types = atypeFactory.getProcessingEnv().getTypeUtils(); - - if (subtype.getKind() == TypeKind.NULL) { - // Make a copy of the supertype so that if supertype is a composite type, the - // returned type will be fully annotated. (For example, if sub is @C null and super is - // @A List<@B String>, then the returned type is @C List<@B String>.) - @SuppressWarnings("unchecked") - T copy = (T) supertype.deepCopy(); - copy.replaceAnnotations(subtype.getAnnotations()); - return copy; - } - - Elements elements = atypeFactory.getProcessingEnv().getElementUtils(); - if (supertype != null - && AnnotatedTypes.isEnum(supertype) - && AnnotatedTypes.isDeclarationOfJavaLangEnum(types, elements, supertype)) { - // Don't return the asSuper result because it causes an infinite loop. - @SuppressWarnings("unchecked") - T result = (T) supertype.deepCopy(); - return result; - } - - T asSuperType = AnnotatedTypes.asSuper(atypeFactory, subtype, supertype); - - fixUpRawTypes(subtype, asSuperType, supertype, types); - - return asSuperType; + Elements elements = atypeFactory.getProcessingEnv().getElementUtils(); + if (supertype != null + && AnnotatedTypes.isEnum(supertype) + && AnnotatedTypes.isDeclarationOfJavaLangEnum(types, elements, supertype)) { + // Don't return the asSuper result because it causes an infinite loop. + @SuppressWarnings("unchecked") + T result = (T) supertype.deepCopy(); + return result; } - /** - * Some times we create type arguments for types that were raw. When we do an asSuper we lose - * these arguments. If in the converted type (i.e. the subtype as super) is missing type - * arguments AND those type arguments should come from the original subtype's type arguments - * then we copy the original type arguments to the converted type. e.g. We have a type W, that - * "wasRaw" {@code ArrayList} When W is converted to type A, List, using - * asSuper it no longer has its type argument. But since the type argument to List should be the - * same as that to ArrayList we copy over the type argument of W to A. A becomes {@code List} - * - * @param originalSubtype the subtype before being converted by asSuper - * @param asSuperType he subtype after being converted by asSuper - * @param supertype the supertype for which asSuperType should have the same underlying type - * @param types the types utility - */ - private static void fixUpRawTypes( - AnnotatedTypeMirror originalSubtype, - AnnotatedTypeMirror asSuperType, - AnnotatedTypeMirror supertype, - Types types) { - if (asSuperType == null - || asSuperType.getKind() != TypeKind.DECLARED - || originalSubtype.getKind() != TypeKind.DECLARED) { - return; - } - - AnnotatedDeclaredType declaredAsSuper = (AnnotatedDeclaredType) asSuperType; - AnnotatedDeclaredType declaredSubtype = (AnnotatedDeclaredType) originalSubtype; - - if (!declaredAsSuper.isUnderlyingTypeRaw() - || !declaredAsSuper.getTypeArguments().isEmpty() - || declaredSubtype.getTypeArguments().isEmpty()) { - return; - } - - Set> typeArgMap = - TypeArgumentMapper.mapTypeArgumentIndices( - (TypeElement) declaredSubtype.getUnderlyingType().asElement(), - (TypeElement) declaredAsSuper.getUnderlyingType().asElement(), - types); - - if (typeArgMap.size() != declaredSubtype.getTypeArguments().size()) { - return; - } - - List> orderedByDestination = new ArrayList<>(typeArgMap); - orderedByDestination.sort(Comparator.comparingInt(o -> o.second)); - - if (typeArgMap.size() == ((AnnotatedDeclaredType) supertype).getTypeArguments().size()) { - List subTypeArgs = declaredSubtype.getTypeArguments(); - List newTypeArgs = - CollectionsPlume.mapList( - mapping -> subTypeArgs.get(mapping.first).deepCopy(), - orderedByDestination); - declaredAsSuper.setTypeArguments(newTypeArgs); - } else { - declaredAsSuper.setTypeArguments(Collections.emptyList()); - } + T asSuperType = AnnotatedTypes.asSuper(atypeFactory, subtype, supertype); + + fixUpRawTypes(subtype, asSuperType, supertype, types); + + return asSuperType; + } + + /** + * Some times we create type arguments for types that were raw. When we do an asSuper we lose + * these arguments. If in the converted type (i.e. the subtype as super) is missing type arguments + * AND those type arguments should come from the original subtype's type arguments then we copy + * the original type arguments to the converted type. e.g. We have a type W, that "wasRaw" {@code + * ArrayList} When W is converted to type A, List, using asSuper it no longer + * has its type argument. But since the type argument to List should be the same as that to + * ArrayList we copy over the type argument of W to A. A becomes {@code List} + * + * @param originalSubtype the subtype before being converted by asSuper + * @param asSuperType he subtype after being converted by asSuper + * @param supertype the supertype for which asSuperType should have the same underlying type + * @param types the types utility + */ + private static void fixUpRawTypes( + AnnotatedTypeMirror originalSubtype, + AnnotatedTypeMirror asSuperType, + AnnotatedTypeMirror supertype, + Types types) { + if (asSuperType == null + || asSuperType.getKind() != TypeKind.DECLARED + || originalSubtype.getKind() != TypeKind.DECLARED) { + return; } - /** - * Returns the result of calling {@link #asSuper(AnnotatedTypeFactory, AnnotatedTypeMirror, - * AnnotatedTypeMirror)} on {@code type} and {@code superType} or an enclosing type of {@code - * type}. - * - *

If the underlying type of {@code type} is a subtype of the underlying type of {@code - * superType}, then this method returns the result of calling {@code asSuper(atypeFactory, type, - * superType)}. - * - *

If the underlying type of an enclosing of {@code type} is a subtype of the underlying type - * of {@code superType}, then this method returns the result of calling {@code - * asSuper(atypeFactory, type.getEnclosingType(), superType)}. - * - *

Otherwise, throws {@link BugInCF}. - * - * @param types types utils - * @param atypeFactory the type factory - * @param type a type - * @param superType a supertype of {@code type} or a supertype of an enclosing type of {@code - * type} - * @return {@code type} or an enclosing type of {@code type} as {@code superType} - */ - private static AnnotatedTypeMirror asOuterSuper( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror type, - AnnotatedTypeMirror superType) { - if (type.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType dt = (AnnotatedDeclaredType) type; - AnnotatedDeclaredType enclosingType = dt; - TypeMirror superTypeMirror = types.erasure(superType.getUnderlyingType()); - while (enclosingType != null) { - TypeMirror enclosingTypeMirror = types.erasure(enclosingType.getUnderlyingType()); - if (types.isSubtype(enclosingTypeMirror, superTypeMirror)) { - dt = enclosingType; - break; - } - enclosingType = enclosingType.getEnclosingType(); - } - if (enclosingType == null) { - // TODO: work around a failure in guava that happens without this hack. - // throw new BugInCF("Enclosing type not found %s %s", dt, superType); - return superType; - } - return asSuper(atypeFactory, dt, superType); - } - return asSuper(atypeFactory, type, superType); - } + AnnotatedDeclaredType declaredAsSuper = (AnnotatedDeclaredType) asSuperType; + AnnotatedDeclaredType declaredSubtype = (AnnotatedDeclaredType) originalSubtype; - /** - * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, - * Element)} with more precise return type. - * - * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @return the type of elem as member of t - */ - public static AnnotatedExecutableType asMemberOf( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror t, - ExecutableElement elem) { - return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem); + if (!declaredAsSuper.isUnderlyingTypeRaw() + || !declaredAsSuper.getTypeArguments().isEmpty() + || declaredSubtype.getTypeArguments().isEmpty()) { + return; } - /** - * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, - * Element, AnnotatedTypeMirror)} with more precise return type. - * - * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element, - * AnnotatedTypeMirror) - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @param type unsubstituted type of member - * @return the type of member as member of {@code t}, with initial type memberType; can be an - * alias to memberType - */ - public static AnnotatedExecutableType asMemberOf( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror t, - ExecutableElement elem, - AnnotatedExecutableType type) { - return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem, type); - } + Set> typeArgMap = + TypeArgumentMapper.mapTypeArgumentIndices( + (TypeElement) declaredSubtype.getUnderlyingType().asElement(), + (TypeElement) declaredAsSuper.getUnderlyingType().asElement(), + types); - /** - * Returns the type of an element when that element is viewed as a member of, or otherwise - * directly contained by, a given type. - * - *

For example, when viewed as a member of the parameterized type {@code Set<@NonNull - * String>}, the {@code Set.add} method is an {@code ExecutableType} whose parameter is of type - * {@code @NonNull String}. - * - *

Before returning the result, this method adjusts it by calling {@link - * AnnotatedTypeFactory#postAsMemberOf(AnnotatedTypeMirror, AnnotatedTypeMirror, Element)}. - * - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @return the type of elem as member of t - */ - public static AnnotatedTypeMirror asMemberOf( - Types types, AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror t, Element elem) { - AnnotatedTypeMirror memberType = atypeFactory.getAnnotatedType(elem); - return asMemberOf(types, atypeFactory, t, elem, memberType); + if (typeArgMap.size() != declaredSubtype.getTypeArguments().size()) { + return; } - /** - * Returns the type of an element when that element is viewed as a member of, or otherwise - * directly contained by, a given type. An initial type for the member is provided, to allow for - * earlier changes to the declared type of elem. For example, polymorphic qualifiers must be - * substituted before type variables are substituted. - * - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @param elemType unsubstituted type of elem - * @return the type of elem as member of t - * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) - */ - public static AnnotatedTypeMirror asMemberOf( - Types types, - AnnotatedTypeFactory atypeFactory, - @Nullable AnnotatedTypeMirror t, - Element elem, - AnnotatedTypeMirror elemType) { - // asMemberOf is only for fields, variables, and methods! - // Otherwise, simply use fromElement. - switch (elem.getKind()) { - case PACKAGE: - case INSTANCE_INIT: - case OTHER: - case STATIC_INIT: - case TYPE_PARAMETER: - return elemType; - default: - if (t == null || ElementUtils.isStatic(elem)) { - return elemType; - } - AnnotatedTypeMirror res = asMemberOfImpl(types, atypeFactory, t, elem, elemType); - atypeFactory.postAsMemberOf(res, t, elem); - return res; - } + List> orderedByDestination = new ArrayList<>(typeArgMap); + orderedByDestination.sort(Comparator.comparingInt(o -> o.second)); + + if (typeArgMap.size() == ((AnnotatedDeclaredType) supertype).getTypeArguments().size()) { + List subTypeArgs = declaredSubtype.getTypeArguments(); + List newTypeArgs = + CollectionsPlume.mapList( + mapping -> subTypeArgs.get(mapping.first).deepCopy(), orderedByDestination); + declaredAsSuper.setTypeArguments(newTypeArgs); + } else { + declaredAsSuper.setTypeArguments(Collections.emptyList()); } - - /** - * Helper for {@link AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, - * Element)}. - * - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param receiverType the receiver type - * @param member the element that should be viewed as member of receiverType - * @param memberType unsubstituted type of member - * @return the type of member as a member of receiverType; can be an alias to memberType - */ - private static AnnotatedTypeMirror asMemberOfImpl( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror receiverType, - Element member, - AnnotatedTypeMirror memberType) { - switch (receiverType.getKind()) { - case ARRAY: - // Method references like String[]::clone should have a return type of String[] - // rather than Object. - if (SyntheticArrays.isArrayClone(receiverType, member)) { - return SyntheticArrays.replaceReturnType( - member, (AnnotatedArrayType) receiverType); - } - return memberType; - case TYPEVAR: - return asMemberOf( - types, - atypeFactory, - atypeFactory.applyCaptureConversion( - ((AnnotatedTypeVariable) receiverType).getUpperBound()), - member, - memberType); - case WILDCARD: - if (((AnnotatedWildcardType) receiverType).isUninferredTypeArgument()) { - return substituteUninferredTypeArgs(atypeFactory, member, memberType); - } - return asMemberOf( - types, - atypeFactory, - ((AnnotatedWildcardType) receiverType).getExtendsBound().deepCopy(), - member, - memberType); - case INTERSECTION: - AnnotatedTypeMirror result = memberType; - TypeMirror enclosingElementType = member.getEnclosingElement().asType(); - for (AnnotatedTypeMirror bound : - ((AnnotatedIntersectionType) receiverType).getBounds()) { - if (TypesUtils.isErasedSubtype( - bound.getUnderlyingType(), enclosingElementType, types)) { - result = - substituteTypeVariables( - types, - atypeFactory, - atypeFactory.applyCaptureConversion(bound), - member, - result); - } - } - return result; - case UNION: - return substituteTypeVariables( - types, atypeFactory, receiverType, member, memberType); - case DECLARED: - AnnotatedDeclaredType receiverTypeDT = (AnnotatedDeclaredType) receiverType; - if (isRawCall(receiverTypeDT, member, types)) { - return memberType.getErased(); - } - return substituteTypeVariables( - types, atypeFactory, receiverType, member, memberType); - default: - throw new BugInCF("asMemberOf called on unexpected type.%nt: %s", receiverType); + } + + /** + * Returns the result of calling {@link #asSuper(AnnotatedTypeFactory, AnnotatedTypeMirror, + * AnnotatedTypeMirror)} on {@code type} and {@code superType} or an enclosing type of {@code + * type}. + * + *

If the underlying type of {@code type} is a subtype of the underlying type of {@code + * superType}, then this method returns the result of calling {@code asSuper(atypeFactory, type, + * superType)}. + * + *

If the underlying type of an enclosing of {@code type} is a subtype of the underlying type + * of {@code superType}, then this method returns the result of calling {@code + * asSuper(atypeFactory, type.getEnclosingType(), superType)}. + * + *

Otherwise, throws {@link BugInCF}. + * + * @param types types utils + * @param atypeFactory the type factory + * @param type a type + * @param superType a supertype of {@code type} or a supertype of an enclosing type of {@code + * type} + * @return {@code type} or an enclosing type of {@code type} as {@code superType} + */ + private static AnnotatedTypeMirror asOuterSuper( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror type, + AnnotatedTypeMirror superType) { + if (type.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType dt = (AnnotatedDeclaredType) type; + AnnotatedDeclaredType enclosingType = dt; + TypeMirror superTypeMirror = types.erasure(superType.getUnderlyingType()); + while (enclosingType != null) { + TypeMirror enclosingTypeMirror = types.erasure(enclosingType.getUnderlyingType()); + if (types.isSubtype(enclosingTypeMirror, superTypeMirror)) { + dt = enclosingType; + break; } + enclosingType = enclosingType.getEnclosingType(); + } + if (enclosingType == null) { + // TODO: work around a failure in guava that happens without this hack. + // throw new BugInCF("Enclosing type not found %s %s", dt, superType); + return superType; + } + return asSuper(atypeFactory, dt, superType); } - - /** - * Is the call to {@code method} with {@code receiver} raw? - * - * @param receiver type of the receiver of the call - * @param method the element of a method or constructor - * @param types type utilities - * @return whether the call to {@code method} with {@code receiver} raw - */ - private static boolean isRawCall(AnnotatedDeclaredType receiver, Element method, Types types) { - // Section 4.8, "Raw Types". - // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.8) - // - // The type of a constructor (§8.8), instance method (8.4, 9.4), or non-static field - // (8.3) of a raw type C that is not inherited from its superclasses or superinterfaces - // is the raw type that corresponds to the erasure of its type in the generic declaration - // corresponding to C. - if (method.getEnclosingElement().equals(receiver.getUnderlyingType().asElement())) { - return receiver.isUnderlyingTypeRaw(); + return asSuper(atypeFactory, type, superType); + } + + /** + * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, + * Element)} with more precise return type. + * + * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @return the type of elem as member of t + */ + public static AnnotatedExecutableType asMemberOf( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror t, + ExecutableElement elem) { + return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem); + } + + /** + * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element, + * AnnotatedTypeMirror)} with more precise return type. + * + * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element, + * AnnotatedTypeMirror) + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @param type unsubstituted type of member + * @return the type of member as member of {@code t}, with initial type memberType; can be an + * alias to memberType + */ + public static AnnotatedExecutableType asMemberOf( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror t, + ExecutableElement elem, + AnnotatedExecutableType type) { + return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem, type); + } + + /** + * Returns the type of an element when that element is viewed as a member of, or otherwise + * directly contained by, a given type. + * + *

For example, when viewed as a member of the parameterized type {@code Set<@NonNull String>}, + * the {@code Set.add} method is an {@code ExecutableType} whose parameter is of type + * {@code @NonNull String}. + * + *

Before returning the result, this method adjusts it by calling {@link + * AnnotatedTypeFactory#postAsMemberOf(AnnotatedTypeMirror, AnnotatedTypeMirror, Element)}. + * + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @return the type of elem as member of t + */ + public static AnnotatedTypeMirror asMemberOf( + Types types, AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror t, Element elem) { + AnnotatedTypeMirror memberType = atypeFactory.getAnnotatedType(elem); + return asMemberOf(types, atypeFactory, t, elem, memberType); + } + + /** + * Returns the type of an element when that element is viewed as a member of, or otherwise + * directly contained by, a given type. An initial type for the member is provided, to allow for + * earlier changes to the declared type of elem. For example, polymorphic qualifiers must be + * substituted before type variables are substituted. + * + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @param elemType unsubstituted type of elem + * @return the type of elem as member of t + * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) + */ + public static AnnotatedTypeMirror asMemberOf( + Types types, + AnnotatedTypeFactory atypeFactory, + @Nullable AnnotatedTypeMirror t, + Element elem, + AnnotatedTypeMirror elemType) { + // asMemberOf is only for fields, variables, and methods! + // Otherwise, simply use fromElement. + switch (elem.getKind()) { + case PACKAGE: + case INSTANCE_INIT: + case OTHER: + case STATIC_INIT: + case TYPE_PARAMETER: + return elemType; + default: + if (t == null || ElementUtils.isStatic(elem)) { + return elemType; } - - // The below is checking for a super() call where the super type is a raw type. - // See framework/tests/all-systems/RawSuper.java for an example. - if ("".contentEquals(method.getSimpleName())) { - ExecutableElement constructor = (ExecutableElement) method; - TypeMirror constructorClass = types.erasure(constructor.getEnclosingElement().asType()); - TypeMirror directSuper = types.directSupertypes(receiver.getUnderlyingType()).get(0); - while (!types.isSameType(types.erasure(directSuper), constructorClass) - && !TypesUtils.isObject(directSuper)) { - directSuper = types.directSupertypes(directSuper).get(0); - } - if (directSuper.getKind() == TypeKind.DECLARED) { - DeclaredType declaredType = (DeclaredType) directSuper; - TypeElement typeelem = (TypeElement) declaredType.asElement(); - DeclaredType declty = (DeclaredType) typeelem.asType(); - return !declty.getTypeArguments().isEmpty() - && declaredType.getTypeArguments().isEmpty(); - } - } - - return false; + AnnotatedTypeMirror res = asMemberOfImpl(types, atypeFactory, t, elem, elemType); + atypeFactory.postAsMemberOf(res, t, elem); + return res; } - - /** - * Substitute type variables. - * - * @param types type utilities - * @param atypeFactory the type factory - * @param receiverType the type of the class that contains member (or a subtype of it) - * @param member a type member, such as a method or field - * @param memberType the type of {@code member} - * @return {@code memberType}, substituted - */ - private static AnnotatedTypeMirror substituteTypeVariables( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror receiverType, - Element member, - AnnotatedTypeMirror memberType) { - - // Basic Algorithm: - // 1. Find the enclosingClassOfMember of the element - // 2. Find the base type of enclosingClassOfMember (e.g. type of enclosingClassOfMember as - // supertype of passed type) - // 3. Substitute for type variables if any exist - TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); - DeclaredType enclosingType = (DeclaredType) enclosingClassOfMember.asType(); - Map mappings = new HashMap<>(); - - // Look for all enclosing types that have type variables - // and collect type to be substituted for those type variables - while (enclosingType != null) { - TypeElement enclosingTypeElement = (TypeElement) enclosingType.asElement(); - addTypeVarMappings(types, atypeFactory, receiverType, enclosingTypeElement, mappings); - if (enclosingType.getEnclosingType() != null - && enclosingType.getEnclosingType().getKind() == TypeKind.DECLARED) { - enclosingType = (DeclaredType) enclosingType.getEnclosingType(); - } else { - enclosingType = null; - } - } - - if (!mappings.isEmpty()) { - memberType = atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); + } + + /** + * Helper for {@link AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, + * Element)}. + * + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param receiverType the receiver type + * @param member the element that should be viewed as member of receiverType + * @param memberType unsubstituted type of member + * @return the type of member as a member of receiverType; can be an alias to memberType + */ + private static AnnotatedTypeMirror asMemberOfImpl( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror receiverType, + Element member, + AnnotatedTypeMirror memberType) { + switch (receiverType.getKind()) { + case ARRAY: + // Method references like String[]::clone should have a return type of String[] + // rather than Object. + if (SyntheticArrays.isArrayClone(receiverType, member)) { + return SyntheticArrays.replaceReturnType(member, (AnnotatedArrayType) receiverType); } - return memberType; - } - - private static void addTypeVarMappings( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror t, - TypeElement enclosingClassOfElem, - Map mappings) { - if (enclosingClassOfElem.getTypeParameters().isEmpty()) { - return; - } - AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfElem); - AnnotatedDeclaredType base = - (AnnotatedDeclaredType) asOuterSuper(types, atypeFactory, t, enclosingType); - base = (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(base); - - List ownerParams = - new ArrayList<>(enclosingType.getTypeArguments().size()); - for (AnnotatedTypeMirror typeParam : enclosingType.getTypeArguments()) { - if (typeParam.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF( - StringsPlume.joinLines( - "Type arguments of a declaration should be type variables.", - " enclosingClassOfElem=" + enclosingClassOfElem, - " enclosingType=" + enclosingType, - " typeMirror=" + t)); - } - ownerParams.add((AnnotatedTypeVariable) typeParam); + case TYPEVAR: + return asMemberOf( + types, + atypeFactory, + atypeFactory.applyCaptureConversion( + ((AnnotatedTypeVariable) receiverType).getUpperBound()), + member, + memberType); + case WILDCARD: + if (((AnnotatedWildcardType) receiverType).isUninferredTypeArgument()) { + return substituteUninferredTypeArgs(atypeFactory, member, memberType); } - - List baseParams = base.getTypeArguments(); - if (ownerParams.size() != baseParams.size() && !base.isUnderlyingTypeRaw()) { - throw new BugInCF( - StringsPlume.joinLines( - "Unexpected number of parameters.", - "enclosingType=" + enclosingType, - "baseType=" + base)); + return asMemberOf( + types, + atypeFactory, + ((AnnotatedWildcardType) receiverType).getExtendsBound().deepCopy(), + member, + memberType); + case INTERSECTION: + AnnotatedTypeMirror result = memberType; + TypeMirror enclosingElementType = member.getEnclosingElement().asType(); + for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) receiverType).getBounds()) { + if (TypesUtils.isErasedSubtype(bound.getUnderlyingType(), enclosingElementType, types)) { + result = + substituteTypeVariables( + types, + atypeFactory, + atypeFactory.applyCaptureConversion(bound), + member, + result); + } } - if (!ownerParams.isEmpty() && baseParams.isEmpty() && base.isUnderlyingTypeRaw()) { - // If base type was raw and the type arguments are missing, set them to the erased - // type of the type variable (which is the erased type of the upper bound). - baseParams = CollectionsPlume.mapList(AnnotatedTypeVariable::getErased, ownerParams); + return result; + case UNION: + return substituteTypeVariables(types, atypeFactory, receiverType, member, memberType); + case DECLARED: + AnnotatedDeclaredType receiverTypeDT = (AnnotatedDeclaredType) receiverType; + if (isRawCall(receiverTypeDT, member, types)) { + return memberType.getErased(); } + return substituteTypeVariables(types, atypeFactory, receiverType, member, memberType); + default: + throw new BugInCF("asMemberOf called on unexpected type.%nt: %s", receiverType); + } + } + + /** + * Is the call to {@code method} with {@code receiver} raw? + * + * @param receiver type of the receiver of the call + * @param method the element of a method or constructor + * @param types type utilities + * @return whether the call to {@code method} with {@code receiver} raw + */ + private static boolean isRawCall(AnnotatedDeclaredType receiver, Element method, Types types) { + // Section 4.8, "Raw Types". + // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.8) + // + // The type of a constructor (§8.8), instance method (8.4, 9.4), or non-static field + // (8.3) of a raw type C that is not inherited from its superclasses or superinterfaces + // is the raw type that corresponds to the erasure of its type in the generic declaration + // corresponding to C. + if (method.getEnclosingElement().equals(receiver.getUnderlyingType().asElement())) { + return receiver.isUnderlyingTypeRaw(); + } - for (int i = 0; i < ownerParams.size(); ++i) { - mappings.put(ownerParams.get(i).getUnderlyingType(), baseParams.get(i).asUse()); - } + // The below is checking for a super() call where the super type is a raw type. + // See framework/tests/all-systems/RawSuper.java for an example. + if ("".contentEquals(method.getSimpleName())) { + ExecutableElement constructor = (ExecutableElement) method; + TypeMirror constructorClass = types.erasure(constructor.getEnclosingElement().asType()); + TypeMirror directSuper = types.directSupertypes(receiver.getUnderlyingType()).get(0); + while (!types.isSameType(types.erasure(directSuper), constructorClass) + && !TypesUtils.isObject(directSuper)) { + directSuper = types.directSupertypes(directSuper).get(0); + } + if (directSuper.getKind() == TypeKind.DECLARED) { + DeclaredType declaredType = (DeclaredType) directSuper; + TypeElement typeelem = (TypeElement) declaredType.asElement(); + DeclaredType declty = (DeclaredType) typeelem.asType(); + return !declty.getTypeArguments().isEmpty() && declaredType.getTypeArguments().isEmpty(); + } } - /** - * Substitutes uninferred type arguments for type variables in {@code memberType}. - * - * @param atypeFactory the type factory - * @param member the element with type {@code memberType}; used to obtain the enclosing type - * @param memberType the type to side-effect - * @return memberType, with type arguments substituted for type variables - */ - private static AnnotatedTypeMirror substituteUninferredTypeArgs( - AnnotatedTypeFactory atypeFactory, Element member, AnnotatedTypeMirror memberType) { - TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); - Map mappings = new HashMap<>(); - - while (enclosingClassOfMember != null) { - if (!enclosingClassOfMember.getTypeParameters().isEmpty()) { - AnnotatedDeclaredType enclosingType = - atypeFactory.getAnnotatedType(enclosingClassOfMember); - for (AnnotatedTypeMirror type : enclosingType.getTypeArguments()) { - AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type; - mappings.put( - typeParameter.getUnderlyingType(), - atypeFactory.getUninferredWildcardType(typeParameter)); - } - } - enclosingClassOfMember = - ElementUtils.enclosingTypeElement(enclosingClassOfMember.getEnclosingElement()); - } + return false; + } + + /** + * Substitute type variables. + * + * @param types type utilities + * @param atypeFactory the type factory + * @param receiverType the type of the class that contains member (or a subtype of it) + * @param member a type member, such as a method or field + * @param memberType the type of {@code member} + * @return {@code memberType}, substituted + */ + private static AnnotatedTypeMirror substituteTypeVariables( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror receiverType, + Element member, + AnnotatedTypeMirror memberType) { + + // Basic Algorithm: + // 1. Find the enclosingClassOfMember of the element + // 2. Find the base type of enclosingClassOfMember (e.g. type of enclosingClassOfMember as + // supertype of passed type) + // 3. Substitute for type variables if any exist + TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); + DeclaredType enclosingType = (DeclaredType) enclosingClassOfMember.asType(); + Map mappings = new HashMap<>(); + + // Look for all enclosing types that have type variables + // and collect type to be substituted for those type variables + while (enclosingType != null) { + TypeElement enclosingTypeElement = (TypeElement) enclosingType.asElement(); + addTypeVarMappings(types, atypeFactory, receiverType, enclosingTypeElement, mappings); + if (enclosingType.getEnclosingType() != null + && enclosingType.getEnclosingType().getKind() == TypeKind.DECLARED) { + enclosingType = (DeclaredType) enclosingType.getEnclosingType(); + } else { + enclosingType = null; + } + } - if (!mappings.isEmpty()) { - return atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); - } + if (!mappings.isEmpty()) { + memberType = atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); + } - return memberType; + return memberType; + } + + private static void addTypeVarMappings( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror t, + TypeElement enclosingClassOfElem, + Map mappings) { + if (enclosingClassOfElem.getTypeParameters().isEmpty()) { + return; + } + AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfElem); + AnnotatedDeclaredType base = + (AnnotatedDeclaredType) asOuterSuper(types, atypeFactory, t, enclosingType); + base = (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(base); + + List ownerParams = + new ArrayList<>(enclosingType.getTypeArguments().size()); + for (AnnotatedTypeMirror typeParam : enclosingType.getTypeArguments()) { + if (typeParam.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF( + StringsPlume.joinLines( + "Type arguments of a declaration should be type variables.", + " enclosingClassOfElem=" + enclosingClassOfElem, + " enclosingType=" + enclosingType, + " typeMirror=" + t)); + } + ownerParams.add((AnnotatedTypeVariable) typeParam); } - /** - * Returns all the supertypes (direct or indirect) of the given declared type. - * - * @param type a declared type - * @return all the supertypes of the given type - */ - public static Set getSuperTypes(AnnotatedDeclaredType type) { - - Set supertypes = new LinkedHashSet<>(); - if (type == null) { - return supertypes; - } + List baseParams = base.getTypeArguments(); + if (ownerParams.size() != baseParams.size() && !base.isUnderlyingTypeRaw()) { + throw new BugInCF( + StringsPlume.joinLines( + "Unexpected number of parameters.", + "enclosingType=" + enclosingType, + "baseType=" + base)); + } + if (!ownerParams.isEmpty() && baseParams.isEmpty() && base.isUnderlyingTypeRaw()) { + // If base type was raw and the type arguments are missing, set them to the erased + // type of the type variable (which is the erased type of the upper bound). + baseParams = CollectionsPlume.mapList(AnnotatedTypeVariable::getErased, ownerParams); + } - // Set up a stack containing the type mirror of subtype, which - // is our starting point. - Deque stack = new ArrayDeque<>(); - stack.push(type); - - while (!stack.isEmpty()) { - AnnotatedDeclaredType current = stack.pop(); - - // For each direct supertype of the current type, if it - // hasn't already been visited, push it onto the stack and - // add it to our supertypes set. - for (AnnotatedDeclaredType supertype : current.directSupertypes()) { - if (!supertypes.contains(supertype)) { - stack.push(supertype); - supertypes.add(supertype); - } - } + for (int i = 0; i < ownerParams.size(); ++i) { + mappings.put(ownerParams.get(i).getUnderlyingType(), baseParams.get(i).asUse()); + } + } + + /** + * Substitutes uninferred type arguments for type variables in {@code memberType}. + * + * @param atypeFactory the type factory + * @param member the element with type {@code memberType}; used to obtain the enclosing type + * @param memberType the type to side-effect + * @return memberType, with type arguments substituted for type variables + */ + private static AnnotatedTypeMirror substituteUninferredTypeArgs( + AnnotatedTypeFactory atypeFactory, Element member, AnnotatedTypeMirror memberType) { + TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); + Map mappings = new HashMap<>(); + + while (enclosingClassOfMember != null) { + if (!enclosingClassOfMember.getTypeParameters().isEmpty()) { + AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfMember); + for (AnnotatedTypeMirror type : enclosingType.getTypeArguments()) { + AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type; + mappings.put( + typeParameter.getUnderlyingType(), + atypeFactory.getUninferredWildcardType(typeParameter)); } - - return Collections.unmodifiableSet(supertypes); + } + enclosingClassOfMember = + ElementUtils.enclosingTypeElement(enclosingClassOfMember.getEnclosingElement()); } - /** - * Given a method, return the methods that it overrides. - * - * @param method the overriding method - * @return a map from types to methods that {@code method} overrides - */ - public static Map overriddenMethods( - Elements elements, AnnotatedTypeFactory atypeFactory, ExecutableElement method) { - TypeElement elem = (TypeElement) method.getEnclosingElement(); - AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(elem); - Collection supertypes = getSuperTypes(type); - return overriddenMethods(elements, method, supertypes); + if (!mappings.isEmpty()) { + return atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); } - /** - * Given a method and all supertypes (recursively) of the method's containing class, returns the - * methods that the method overrides. - * - * @param method the overriding method - * @param supertypes the set of supertypes to check for methods that are overridden by {@code - * method} - * @return a map from types to methods that {@code method} overrides - */ - public static Map overriddenMethods( - Elements elements, - ExecutableElement method, - Collection supertypes) { - - Map overrides = new LinkedHashMap<>(); - - for (AnnotatedDeclaredType supertype : supertypes) { - TypeElement superElement = (TypeElement) supertype.getUnderlyingType().asElement(); - assert superElement != null; - // For all method in the supertype, add it to the set if - // it overrides the given method. - for (ExecutableElement supermethod : - ElementFilter.methodsIn(superElement.getEnclosedElements())) { - if (elements.overrides(method, supermethod, superElement)) { - overrides.put(supertype, supermethod); - break; - } - } - } + return memberType; + } - return Collections.unmodifiableMap(overrides); - } + /** + * Returns all the supertypes (direct or indirect) of the given declared type. + * + * @param type a declared type + * @return all the supertypes of the given type + */ + public static Set getSuperTypes(AnnotatedDeclaredType type) { - /** - * Given a method or constructor invocation, return a mapping of the type variables to their - * type arguments, if any exist. - * - *

It uses the method or constructor invocation type arguments if they were specified and - * otherwise it infers them based on the passed arguments or the return type context, according - * to JLS 15.12.2. - * - * @param atypeFactory the annotated type factory - * @param expr the method or constructor invocation tree; the passed argument has to be a - * subtype of MethodInvocationTree or NewClassTree - * @param elt the element corresponding to the tree - * @param preType the (partially annotated) type corresponding to the tree - the result of - * AnnotatedTypes.asMemberOf with the receiver and elt - * @return the mapping of the type variables to type arguments for this method or constructor - * invocation - */ - public static Map findTypeArguments( - ProcessingEnvironment processingEnv, - AnnotatedTypeFactory atypeFactory, - ExpressionTree expr, - ExecutableElement elt, - AnnotatedExecutableType preType) { - - // Is the method a generic method? - if (elt.getTypeParameters().isEmpty()) { - return new HashMap<>(); - } + Set supertypes = new LinkedHashSet<>(); + if (type == null) { + return supertypes; + } - List targs; - if (expr instanceof MethodInvocationTree) { - targs = ((MethodInvocationTree) expr).getTypeArguments(); - } else if (expr instanceof NewClassTree) { - targs = ((NewClassTree) expr).getTypeArguments(); - } else if (expr instanceof MemberReferenceTree) { - targs = ((MemberReferenceTree) expr).getTypeArguments(); - if (targs == null) { - // TODO: Add type argument inference as part of fix for #979 - return new HashMap<>(); - } - } else { - // This case should never happen. - throw new BugInCF("AnnotatedTypes.findTypeArguments: unexpected tree: " + expr); + // Set up a stack containing the type mirror of subtype, which + // is our starting point. + Deque stack = new ArrayDeque<>(); + stack.push(type); + + while (!stack.isEmpty()) { + AnnotatedDeclaredType current = stack.pop(); + + // For each direct supertype of the current type, if it + // hasn't already been visited, push it onto the stack and + // add it to our supertypes set. + for (AnnotatedDeclaredType supertype : current.directSupertypes()) { + if (!supertypes.contains(supertype)) { + stack.push(supertype); + supertypes.add(supertype); } + } + } - // Has the user supplied type arguments? - if (!targs.isEmpty()) { - List tvars = preType.getTypeVariables(); - if (tvars.isEmpty()) { - // This happens when the method is invoked with a raw receiver. - return Collections.emptyMap(); - } - - Map typeArguments = new HashMap<>(); - for (int i = 0; i < elt.getTypeParameters().size(); ++i) { - AnnotatedTypeVariable typeVar = tvars.get(i); - AnnotatedTypeMirror typeArg = - atypeFactory.getAnnotatedTypeFromTypeTree(targs.get(i)); - // TODO: the call to getTypeParameterDeclaration shouldn't be necessary - typeVar - // already should be a declaration. - typeArguments.put(typeVar.getUnderlyingType(), typeArg); - } - return typeArguments; - } else { - return atypeFactory - .getTypeArgumentInference() - .inferTypeArgs(atypeFactory, expr, elt, preType); + return Collections.unmodifiableSet(supertypes); + } + + /** + * Given a method, return the methods that it overrides. + * + * @param method the overriding method + * @return a map from types to methods that {@code method} overrides + */ + public static Map overriddenMethods( + Elements elements, AnnotatedTypeFactory atypeFactory, ExecutableElement method) { + TypeElement elem = (TypeElement) method.getEnclosingElement(); + AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(elem); + Collection supertypes = getSuperTypes(type); + return overriddenMethods(elements, method, supertypes); + } + + /** + * Given a method and all supertypes (recursively) of the method's containing class, returns the + * methods that the method overrides. + * + * @param method the overriding method + * @param supertypes the set of supertypes to check for methods that are overridden by {@code + * method} + * @return a map from types to methods that {@code method} overrides + */ + public static Map overriddenMethods( + Elements elements, ExecutableElement method, Collection supertypes) { + + Map overrides = new LinkedHashMap<>(); + + for (AnnotatedDeclaredType supertype : supertypes) { + TypeElement superElement = (TypeElement) supertype.getUnderlyingType().asElement(); + assert superElement != null; + // For all method in the supertype, add it to the set if + // it overrides the given method. + for (ExecutableElement supermethod : + ElementFilter.methodsIn(superElement.getEnclosedElements())) { + if (elements.overrides(method, supermethod, superElement)) { + overrides.put(supertype, supermethod); + break; } + } } - /** - * Returns the lub of two annotated types. - * - * @param atypeFactory the type factory - * @param type1 a type - * @param type2 another type - * @return the lub of {@code type1} and {@code type2} - */ - public static AnnotatedTypeMirror leastUpperBound( - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2) { - TypeMirror lub = - TypesUtils.leastUpperBound( - type1.getUnderlyingType(), - type2.getUnderlyingType(), - atypeFactory.getProcessingEnv()); - return leastUpperBound(atypeFactory, type1, type2, lub); + return Collections.unmodifiableMap(overrides); + } + + /** + * Given a method or constructor invocation, return a mapping of the type variables to their type + * arguments, if any exist. + * + *

It uses the method or constructor invocation type arguments if they were specified and + * otherwise it infers them based on the passed arguments or the return type context, according to + * JLS 15.12.2. + * + * @param atypeFactory the annotated type factory + * @param expr the method or constructor invocation tree; the passed argument has to be a subtype + * of MethodInvocationTree or NewClassTree + * @param elt the element corresponding to the tree + * @param preType the (partially annotated) type corresponding to the tree - the result of + * AnnotatedTypes.asMemberOf with the receiver and elt + * @return the mapping of the type variables to type arguments for this method or constructor + * invocation + */ + public static Map findTypeArguments( + ProcessingEnvironment processingEnv, + AnnotatedTypeFactory atypeFactory, + ExpressionTree expr, + ExecutableElement elt, + AnnotatedExecutableType preType) { + + // Is the method a generic method? + if (elt.getTypeParameters().isEmpty()) { + return new HashMap<>(); } - /** - * Returns the lub, whose underlying type is {@code lubTypeMirror} of two annotated types. - * - * @param atypeFactory a type factory - * @param type1 annotated type whose underlying type must be a subtype or convertible to - * lubTypeMirror - * @param type2 annotated type whose underlying type must be a subtype or convertible to - * lubTypeMirror - * @param lubTypeMirror underlying type of the returned lub - * @return the lub of type1 and type2 with underlying type lubTypeMirror - */ - public static AnnotatedTypeMirror leastUpperBound( - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - TypeMirror lubTypeMirror) { - return new AtmLubVisitor(atypeFactory).lub(type1, type2, lubTypeMirror); + List targs; + if (expr instanceof MethodInvocationTree) { + targs = ((MethodInvocationTree) expr).getTypeArguments(); + } else if (expr instanceof NewClassTree) { + targs = ((NewClassTree) expr).getTypeArguments(); + } else if (expr instanceof MemberReferenceTree) { + targs = ((MemberReferenceTree) expr).getTypeArguments(); + if (targs == null) { + // TODO: Add type argument inference as part of fix for #979 + return new HashMap<>(); + } + } else { + // This case should never happen. + throw new BugInCF("AnnotatedTypes.findTypeArguments: unexpected tree: " + expr); } - /** - * Returns the "annotated greatest lower bound" of {@code type1} and {@code type2}. - * - *

Suppose that there is an expression e with annotated type T. The underlying type of T must - * be the same as javac's type for e. (This is a requirement of the Checker Framework.) As a - * corollary, when computing a glb of atype1 and atype2, it is required that - * underlyingType(cfGLB(atype1, atype2) == glb(javacGLB(underlyingType(atype1), - * underlyingType(atype2)). Because of this requirement, the return value of this method (the - * "annotated GLB") may not be a subtype of one of the types. - * - *

The "annotated greatest lower bound" is defined as follows: - * - *

    - *
  1. If the underlying type of {@code type1} and {@code type2} are the same, then return a - * copy of {@code type1} whose primary annotations are the greatest lower bound of the - * primary annotations on {@code type1} and {@code type2}. - *
  2. If the underlying type of {@code type1} is a subtype of the underlying type of {@code - * type2}, then return a copy of {@code type1} whose primary annotations are the greatest - * lower bound of the primary annotations on {@code type1} and {@code type2}. - *
  3. If the underlying type of {@code type1} is a supertype of the underlying type of {@code - * type2}, then return a copy of {@code type2} whose primary annotations are the greatest - * lower bound of the primary annotations on {@code type1} and {@code type2}. - *
  4. If the underlying type of {@code type1} and {@code type2} are not in a subtyping - * relationship, then return an annotated intersection type whose bounds are {@code type1} - * and {@code type2}. - *
- * - * @param atypeFactory the AnnotatedTypeFactory - * @param type1 annotated type - * @param type2 annotated type - * @return the annotated glb of type1 and type2 - */ - public static AnnotatedTypeMirror annotatedGLB( - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2) { - TypeMirror tm1 = type1.getUnderlyingType(); - TypeMirror tm2 = type2.getUnderlyingType(); - TypeMirror glbJava = - TypesUtils.greatestLowerBound(tm1, tm2, atypeFactory.getProcessingEnv()); - Types types = atypeFactory.types; - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - if (types.isSubtype(tm1, tm2)) { - return glbSubtype(qualHierarchy, type1, type2); - } else if (types.isSubtype(tm2, tm1)) { - return glbSubtype(qualHierarchy, type2, type1); - } - - if (types.isSameType(tm1, glbJava)) { - return glbSubtype(qualHierarchy, type1, type2); - } else if (types.isSameType(tm2, glbJava)) { - return glbSubtype(qualHierarchy, type2, type1); - } - - if (glbJava.getKind() != TypeKind.INTERSECTION) { - // If one type isn't a subtype of the other, then GLB must be an intersection. - throw new BugInCF( - "AnnotatedTypes#annotatedGLB: expected intersection, got [%s] %s. " - + "type1: %s, type2: %s", - glbJava.getKind(), glbJava, type1, type2); - } - AnnotationMirrorSet set1 = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type1); - AnnotationMirrorSet set2 = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type2); - Set glbAnnos = - qualHierarchy.greatestLowerBoundsShallow(set1, tm1, set2, tm2); - - AnnotatedIntersectionType glb = - (AnnotatedIntersectionType) - AnnotatedTypeMirror.createType(glbJava, atypeFactory, false); - - List newBounds = new ArrayList<>(2); - for (AnnotatedTypeMirror bound : glb.getBounds()) { - if (types.isSameType(bound.getUnderlyingType(), tm1)) { - newBounds.add(type1.deepCopy()); - } else if (types.isSameType(bound.getUnderlyingType(), tm2)) { - newBounds.add(type2.deepCopy()); - } else if (type1.getKind() == TypeKind.INTERSECTION) { - AnnotatedIntersectionType intertype1 = (AnnotatedIntersectionType) type1; - for (AnnotatedTypeMirror otherBound : intertype1.getBounds()) { - if (types.isSameType( - bound.getUnderlyingType(), otherBound.getUnderlyingType())) { - newBounds.add(otherBound.deepCopy()); - } - } - } else if (type2.getKind() == TypeKind.INTERSECTION) { - AnnotatedIntersectionType intertype2 = (AnnotatedIntersectionType) type2; - for (AnnotatedTypeMirror otherBound : intertype2.getBounds()) { - if (types.isSameType( - bound.getUnderlyingType(), otherBound.getUnderlyingType())) { - newBounds.add(otherBound.deepCopy()); - } - } - } else { - throw new BugInCF( - "Neither %s nor %s is one of the intersection bounds in %s. Bound: %s", - type1, type2, bound, glb); - } - } - - glb.setBounds(newBounds); - glb.addAnnotations(glbAnnos); - return glb; + // Has the user supplied type arguments? + if (!targs.isEmpty()) { + List tvars = preType.getTypeVariables(); + if (tvars.isEmpty()) { + // This happens when the method is invoked with a raw receiver. + return Collections.emptyMap(); + } + + Map typeArguments = new HashMap<>(); + for (int i = 0; i < elt.getTypeParameters().size(); ++i) { + AnnotatedTypeVariable typeVar = tvars.get(i); + AnnotatedTypeMirror typeArg = atypeFactory.getAnnotatedTypeFromTypeTree(targs.get(i)); + // TODO: the call to getTypeParameterDeclaration shouldn't be necessary - typeVar + // already should be a declaration. + typeArguments.put(typeVar.getUnderlyingType(), typeArg); + } + return typeArguments; + } else { + return atypeFactory + .getTypeArgumentInference() + .inferTypeArgs(atypeFactory, expr, elt, preType); + } + } + + /** + * Returns the lub of two annotated types. + * + * @param atypeFactory the type factory + * @param type1 a type + * @param type2 another type + * @return the lub of {@code type1} and {@code type2} + */ + public static AnnotatedTypeMirror leastUpperBound( + AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + TypeMirror lub = + TypesUtils.leastUpperBound( + type1.getUnderlyingType(), type2.getUnderlyingType(), atypeFactory.getProcessingEnv()); + return leastUpperBound(atypeFactory, type1, type2, lub); + } + + /** + * Returns the lub, whose underlying type is {@code lubTypeMirror} of two annotated types. + * + * @param atypeFactory a type factory + * @param type1 annotated type whose underlying type must be a subtype or convertible to + * lubTypeMirror + * @param type2 annotated type whose underlying type must be a subtype or convertible to + * lubTypeMirror + * @param lubTypeMirror underlying type of the returned lub + * @return the lub of type1 and type2 with underlying type lubTypeMirror + */ + public static AnnotatedTypeMirror leastUpperBound( + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + TypeMirror lubTypeMirror) { + return new AtmLubVisitor(atypeFactory).lub(type1, type2, lubTypeMirror); + } + + /** + * Returns the "annotated greatest lower bound" of {@code type1} and {@code type2}. + * + *

Suppose that there is an expression e with annotated type T. The underlying type of T must + * be the same as javac's type for e. (This is a requirement of the Checker Framework.) As a + * corollary, when computing a glb of atype1 and atype2, it is required that + * underlyingType(cfGLB(atype1, atype2) == glb(javacGLB(underlyingType(atype1), + * underlyingType(atype2)). Because of this requirement, the return value of this method (the + * "annotated GLB") may not be a subtype of one of the types. + * + *

The "annotated greatest lower bound" is defined as follows: + * + *

    + *
  1. If the underlying type of {@code type1} and {@code type2} are the same, then return a + * copy of {@code type1} whose primary annotations are the greatest lower bound of the + * primary annotations on {@code type1} and {@code type2}. + *
  2. If the underlying type of {@code type1} is a subtype of the underlying type of {@code + * type2}, then return a copy of {@code type1} whose primary annotations are the greatest + * lower bound of the primary annotations on {@code type1} and {@code type2}. + *
  3. If the underlying type of {@code type1} is a supertype of the underlying type of {@code + * type2}, then return a copy of {@code type2} whose primary annotations are the greatest + * lower bound of the primary annotations on {@code type1} and {@code type2}. + *
  4. If the underlying type of {@code type1} and {@code type2} are not in a subtyping + * relationship, then return an annotated intersection type whose bounds are {@code type1} + * and {@code type2}. + *
+ * + * @param atypeFactory the AnnotatedTypeFactory + * @param type1 annotated type + * @param type2 annotated type + * @return the annotated glb of type1 and type2 + */ + public static AnnotatedTypeMirror annotatedGLB( + AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + TypeMirror tm1 = type1.getUnderlyingType(); + TypeMirror tm2 = type2.getUnderlyingType(); + TypeMirror glbJava = TypesUtils.greatestLowerBound(tm1, tm2, atypeFactory.getProcessingEnv()); + Types types = atypeFactory.types; + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + if (types.isSubtype(tm1, tm2)) { + return glbSubtype(qualHierarchy, type1, type2); + } else if (types.isSubtype(tm2, tm1)) { + return glbSubtype(qualHierarchy, type2, type1); } - /** - * Returns the annotated greatest lower bound of {@code subtype} and {@code supertype}, where - * the underlying Java types are in a subtyping relationship. - * - *

This handles cases 1, 2, and 3 mentioned in the Javadoc of {@link - * #annotatedGLB(AnnotatedTypeFactory, AnnotatedTypeMirror, AnnotatedTypeMirror)}. - * - * @param qualHierarchy the qualifier hierarchy - * @param subtype annotated type whose underlying type is a subtype of {@code supertype} - * @param supertype annotated type whose underlying type is a supertype of {@code subtype} - * @return the annotated greatest lower bound of {@code subtype} and {@code supertype} - */ - private static AnnotatedTypeMirror glbSubtype( - QualifierHierarchy qualHierarchy, - AnnotatedTypeMirror subtype, - AnnotatedTypeMirror supertype) { - AnnotatedTypeMirror glb = subtype.deepCopy(); - glb.clearAnnotations(); - - TypeMirror subTM = subtype.getUnderlyingType(); - TypeMirror superTM = supertype.getUnderlyingType(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror subAnno = subtype.getAnnotationInHierarchy(top); - AnnotationMirror superAnno = supertype.getAnnotationInHierarchy(top); - if (subAnno != null && superAnno != null) { - glb.addAnnotation( - qualHierarchy.greatestLowerBoundShallow( - subAnno, subTM, superAnno, superTM)); - } else if (subAnno == null && superAnno == null) { - if (subtype.getKind() != TypeKind.TYPEVAR - || supertype.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF( - "Missing primary annotations: subtype: %s, supertype: %s", - subtype, supertype); - } - } else if (subAnno == null) { - if (subtype.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF("Missing primary annotations: subtype: %s", subtype); - } - AnnotationMirror ubAnno = subtype.getEffectiveAnnotationInHierarchy(top); - if (!qualHierarchy.isSubtypeQualifiersOnly(ubAnno, superAnno)) { - // Instead of superAnno <: ubAnno check for ubAnno This expands the parameters if the call uses varargs or contracts the parameters if the - * call is to an anonymous class that extends a class with an enclosing type. If the call is - * neither of these, then the parameters are returned unchanged. - * - * @param atypeFactory the type factory to use for fetching annotated types - * @param method the method or constructor's type - * @param args the arguments to the method or constructor invocation - * @param tree the NewClassTree if method is a constructor - * @return a list of the types that the invocation arguments need to be subtype of; has the same - * length as {@code args} - */ - public static List adaptParameters( - AnnotatedTypeFactory atypeFactory, - AnnotatedExecutableType method, - List args, - @Nullable NewClassTree tree) { - List parameters = method.getParameterTypes(); - - if (parameters.isEmpty()) { - return parameters; + if (glbJava.getKind() != TypeKind.INTERSECTION) { + // If one type isn't a subtype of the other, then GLB must be an intersection. + throw new BugInCF( + "AnnotatedTypes#annotatedGLB: expected intersection, got [%s] %s. " + + "type1: %s, type2: %s", + glbJava.getKind(), glbJava, type1, type2); + } + AnnotationMirrorSet set1 = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type1); + AnnotationMirrorSet set2 = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type2); + Set glbAnnos = + qualHierarchy.greatestLowerBoundsShallow(set1, tm1, set2, tm2); + + AnnotatedIntersectionType glb = + (AnnotatedIntersectionType) AnnotatedTypeMirror.createType(glbJava, atypeFactory, false); + + List newBounds = new ArrayList<>(2); + for (AnnotatedTypeMirror bound : glb.getBounds()) { + if (types.isSameType(bound.getUnderlyingType(), tm1)) { + newBounds.add(type1.deepCopy()); + } else if (types.isSameType(bound.getUnderlyingType(), tm2)) { + newBounds.add(type2.deepCopy()); + } else if (type1.getKind() == TypeKind.INTERSECTION) { + AnnotatedIntersectionType intertype1 = (AnnotatedIntersectionType) type1; + for (AnnotatedTypeMirror otherBound : intertype1.getBounds()) { + if (types.isSameType(bound.getUnderlyingType(), otherBound.getUnderlyingType())) { + newBounds.add(otherBound.deepCopy()); + } } - - // Handle anonymous constructors that extend a class with an enclosing type. - // There is a mismatch between the number of parameters and arguments when - // the following conditions are met: - // 1. Java version >= 11 - // 2. the method is an anonymous constructor - // 3. the constructor is invoked with an explicit enclosing expression - // In the case, we should remove the first parameter. - if (SystemUtil.jreVersion >= 11 - && tree != null - && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( - method.getElement(), tree)) { - if (parameters.size() != args.size() || args.isEmpty()) { - List p = new ArrayList<>(parameters.size()); - p.addAll(parameters.subList(1, parameters.size())); - parameters = p; - } + } else if (type2.getKind() == TypeKind.INTERSECTION) { + AnnotatedIntersectionType intertype2 = (AnnotatedIntersectionType) type2; + for (AnnotatedTypeMirror otherBound : intertype2.getBounds()) { + if (types.isSameType(bound.getUnderlyingType(), otherBound.getUnderlyingType())) { + newBounds.add(otherBound.deepCopy()); + } } + } else { + throw new BugInCF( + "Neither %s nor %s is one of the intersection bounds in %s. Bound: %s", + type1, type2, bound, glb); + } + } - // Handle vararg methods. - if (!method.getElement().isVarArgs()) { - return parameters; + glb.setBounds(newBounds); + glb.addAnnotations(glbAnnos); + return glb; + } + + /** + * Returns the annotated greatest lower bound of {@code subtype} and {@code supertype}, where the + * underlying Java types are in a subtyping relationship. + * + *

This handles cases 1, 2, and 3 mentioned in the Javadoc of {@link + * #annotatedGLB(AnnotatedTypeFactory, AnnotatedTypeMirror, AnnotatedTypeMirror)}. + * + * @param qualHierarchy the qualifier hierarchy + * @param subtype annotated type whose underlying type is a subtype of {@code supertype} + * @param supertype annotated type whose underlying type is a supertype of {@code subtype} + * @return the annotated greatest lower bound of {@code subtype} and {@code supertype} + */ + private static AnnotatedTypeMirror glbSubtype( + QualifierHierarchy qualHierarchy, + AnnotatedTypeMirror subtype, + AnnotatedTypeMirror supertype) { + AnnotatedTypeMirror glb = subtype.deepCopy(); + glb.clearAnnotations(); + + TypeMirror subTM = subtype.getUnderlyingType(); + TypeMirror superTM = supertype.getUnderlyingType(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror subAnno = subtype.getAnnotationInHierarchy(top); + AnnotationMirror superAnno = supertype.getAnnotationInHierarchy(top); + if (subAnno != null && superAnno != null) { + glb.addAnnotation( + qualHierarchy.greatestLowerBoundShallow(subAnno, subTM, superAnno, superTM)); + } else if (subAnno == null && superAnno == null) { + if (subtype.getKind() != TypeKind.TYPEVAR || supertype.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF( + "Missing primary annotations: subtype: %s, supertype: %s", subtype, supertype); } - - AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1); - - if (parameters.size() == args.size()) { - // Check if one sent an element or an array - AnnotatedTypeMirror lastArg = atypeFactory.getAnnotatedType(args.get(args.size() - 1)); - if (lastArg.getKind() == TypeKind.NULL - || (lastArg.getKind() == TypeKind.ARRAY - && getArrayDepth(varargs) - == getArrayDepth((AnnotatedArrayType) lastArg))) { - return parameters; - } + } else if (subAnno == null) { + if (subtype.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Missing primary annotations: subtype: %s", subtype); } - - parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); - for (int i = args.size() - parameters.size(); i > 0; --i) { - parameters.add(varargs.getComponentType().deepCopy()); + AnnotationMirror ubAnno = subtype.getEffectiveAnnotationInHierarchy(top); + if (!qualHierarchy.isSubtypeQualifiersOnly(ubAnno, superAnno)) { + // Instead of superAnno <: ubAnno check for ubAnno This expands the parameters if the call uses varargs or contracts the parameters if the call + * is to an anonymous class that extends a class with an enclosing type. If the call is neither of + * these, then the parameters are returned unchanged. + * + * @param atypeFactory the type factory to use for fetching annotated types + * @param method the method or constructor's type + * @param args the arguments to the method or constructor invocation + * @param tree the NewClassTree if method is a constructor + * @return a list of the types that the invocation arguments need to be subtype of; has the same + * length as {@code args} + */ + public static List adaptParameters( + AnnotatedTypeFactory atypeFactory, + AnnotatedExecutableType method, + List args, + @Nullable NewClassTree tree) { + List parameters = method.getParameterTypes(); + + if (parameters.isEmpty()) { + return parameters; } - /** - * Returns the method parameters for the invoked method, with the same number of formal - * parameters as the arguments in the given list. - * - * @param method the method's type - * @param args the types of the arguments at the call site - * @return the method parameters, with varargs replaced by instances of its component type - */ - public static List expandVarArgsParametersFromTypes( - AnnotatedExecutableType method, List args) { - List parameters = method.getParameterTypes(); - if (!method.getElement().isVarArgs()) { - return parameters; - } + // Handle anonymous constructors that extend a class with an enclosing type. + // There is a mismatch between the number of parameters and arguments when + // the following conditions are met: + // 1. Java version >= 11 + // 2. the method is an anonymous constructor + // 3. the constructor is invoked with an explicit enclosing expression + // In the case, we should remove the first parameter. + if (SystemUtil.jreVersion >= 11 + && tree != null + && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( + method.getElement(), tree)) { + if (parameters.size() != args.size() || args.isEmpty()) { + List p = new ArrayList<>(parameters.size()); + p.addAll(parameters.subList(1, parameters.size())); + parameters = p; + } + } - AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1); - - if (parameters.size() == args.size()) { - // Check if one sent an element or an array - AnnotatedTypeMirror lastArg = args.get(args.size() - 1); - if (lastArg.getKind() == TypeKind.ARRAY - && (getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg) - // If the array depths don't match, but the component type of the vararg - // is a type variable, then that type variable might later be - // substituted for an array. - || varargs.getComponentType().getKind() == TypeKind.TYPEVAR)) { - return parameters; - } - } + // Handle vararg methods. + if (!method.getElement().isVarArgs()) { + return parameters; + } - parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); - for (int i = args.size() - parameters.size(); i > 0; --i) { - parameters.add(varargs.getComponentType()); - } + AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1); + if (parameters.size() == args.size()) { + // Check if one sent an element or an array + AnnotatedTypeMirror lastArg = atypeFactory.getAnnotatedType(args.get(args.size() - 1)); + if (lastArg.getKind() == TypeKind.NULL + || (lastArg.getKind() == TypeKind.ARRAY + && getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg))) { return parameters; + } } - /** - * Given an AnnotatedExecutableType of a method or constructor declaration, get the parameter - * type expected at the indexth position (unwrapping varargs if necessary). - * - * @param methodType the type of a method or constructor containing the parameter to return - * @param index position of the parameter type to return - * @return the type of the parameter in the index position. If that parameter is a varArgs, - * return the component type of the varargs and NOT the array type. - */ - public static AnnotatedTypeMirror getAnnotatedTypeMirrorOfParameter( - AnnotatedExecutableType methodType, int index) { - List parameterTypes = methodType.getParameterTypes(); - boolean hasVarArg = methodType.getElement().isVarArgs(); - - int lastIndex = parameterTypes.size() - 1; - AnnotatedTypeMirror lastType = parameterTypes.get(lastIndex); - boolean parameterBeforeVarargs = index < lastIndex; - if (!parameterBeforeVarargs && lastType instanceof AnnotatedArrayType) { - AnnotatedArrayType arrayType = (AnnotatedArrayType) lastType; - if (hasVarArg) { - return arrayType.getComponentType(); - } - } - return parameterTypes.get(index); + parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); + for (int i = args.size() - parameters.size(); i > 0; --i) { + parameters.add(varargs.getComponentType().deepCopy()); } - /** - * Returns the depth of the array type of the provided array. - * - * @param array the type of the array - * @return the depth of the provided array - */ - public static int getArrayDepth(AnnotatedArrayType array) { - int counter = 0; - AnnotatedTypeMirror type = array; - while (type.getKind() == TypeKind.ARRAY) { - counter++; - type = ((AnnotatedArrayType) type).getComponentType(); - } - return counter; + return parameters; + } + + /** + * Returns the method parameters for the invoked method, with the same number of formal parameters + * as the arguments in the given list. + * + * @param method the method's type + * @param args the types of the arguments at the call site + * @return the method parameters, with varargs replaced by instances of its component type + */ + public static List expandVarArgsParametersFromTypes( + AnnotatedExecutableType method, List args) { + List parameters = method.getParameterTypes(); + if (!method.getElement().isVarArgs()) { + return parameters; } - // The innermost *array* type. - public static AnnotatedTypeMirror innerMostType(AnnotatedTypeMirror t) { - AnnotatedTypeMirror inner = t; - while (inner.getKind() == TypeKind.ARRAY) { - inner = ((AnnotatedArrayType) inner).getComponentType(); - } - return inner; + AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1); + + if (parameters.size() == args.size()) { + // Check if one sent an element or an array + AnnotatedTypeMirror lastArg = args.get(args.size() - 1); + if (lastArg.getKind() == TypeKind.ARRAY + && (getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg) + // If the array depths don't match, but the component type of the vararg + // is a type variable, then that type variable might later be + // substituted for an array. + || varargs.getComponentType().getKind() == TypeKind.TYPEVAR)) { + return parameters; + } } - /** - * Checks whether type contains the given modifier, also recursively in type arguments and - * arrays. This method might be easier to implement directly as instance method in - * AnnotatedTypeMirror; it corresponds to a "deep" version of {@link - * AnnotatedTypeMirror#hasAnnotation(AnnotationMirror)}. - * - * @param type the type to search - * @param modifier the modifier to search for - * @return whether the type contains the modifier - */ - public static boolean containsModifier(AnnotatedTypeMirror type, AnnotationMirror modifier) { - return containsModifierImpl(type, modifier, new ArrayList<>()); + parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); + for (int i = args.size() - parameters.size(); i > 0; --i) { + parameters.add(varargs.getComponentType()); } - /* - * For type variables we might hit the same type again. We keep a list of visited types. - */ - private static boolean containsModifierImpl( - AnnotatedTypeMirror type, - AnnotationMirror modifier, - List visited) { - boolean found = type.hasAnnotation(modifier); - boolean vis = visited.contains(type); - visited.add(type); - - if (!found && !vis) { - if (type.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; - for (AnnotatedTypeMirror typeMirror : declaredType.getTypeArguments()) { - found |= containsModifierImpl(typeMirror, modifier, visited); - if (found) { - break; - } - } - } else if (type.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType arrayType = (AnnotatedArrayType) type; - found = containsModifierImpl(arrayType.getComponentType(), modifier, visited); - } else if (type.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) type; - if (atv.getUpperBound() != null) { - found = containsModifierImpl(atv.getUpperBound(), modifier, visited); - } - if (!found && atv.getLowerBound() != null) { - found = containsModifierImpl(atv.getLowerBound(), modifier, visited); - } - } else if (type.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType awc = (AnnotatedWildcardType) type; - if (awc.getExtendsBound() != null) { - found = containsModifierImpl(awc.getExtendsBound(), modifier, visited); - } - if (!found && awc.getSuperBound() != null) { - found = containsModifierImpl(awc.getSuperBound(), modifier, visited); - } - } - } - - return found; + return parameters; + } + + /** + * Given an AnnotatedExecutableType of a method or constructor declaration, get the parameter type + * expected at the indexth position (unwrapping varargs if necessary). + * + * @param methodType the type of a method or constructor containing the parameter to return + * @param index position of the parameter type to return + * @return the type of the parameter in the index position. If that parameter is a varArgs, return + * the component type of the varargs and NOT the array type. + */ + public static AnnotatedTypeMirror getAnnotatedTypeMirrorOfParameter( + AnnotatedExecutableType methodType, int index) { + List parameterTypes = methodType.getParameterTypes(); + boolean hasVarArg = methodType.getElement().isVarArgs(); + + int lastIndex = parameterTypes.size() - 1; + AnnotatedTypeMirror lastType = parameterTypes.get(lastIndex); + boolean parameterBeforeVarargs = index < lastIndex; + if (!parameterBeforeVarargs && lastType instanceof AnnotatedArrayType) { + AnnotatedArrayType arrayType = (AnnotatedArrayType) lastType; + if (hasVarArg) { + return arrayType.getComponentType(); + } } - - /** java.lang.annotation.Annotation.class canonical name. */ - private static final @CanonicalName String annotationClassName = - java.lang.annotation.Annotation.class.getCanonicalName(); - - /** - * Returns true if the underlying type of this atm is a java.lang.annotation.Annotation. - * - * @return true if the underlying type of this atm is a java.lang.annotation.Annotation - */ - public static boolean isJavaLangAnnotation(AnnotatedTypeMirror atm) { - return TypesUtils.isDeclaredOfName(atm.getUnderlyingType(), annotationClassName); + return parameterTypes.get(index); + } + + /** + * Returns the depth of the array type of the provided array. + * + * @param array the type of the array + * @return the depth of the provided array + */ + public static int getArrayDepth(AnnotatedArrayType array) { + int counter = 0; + AnnotatedTypeMirror type = array; + while (type.getKind() == TypeKind.ARRAY) { + counter++; + type = ((AnnotatedArrayType) type).getComponentType(); } - - /** - * Returns true if atm is an Annotation interface, i.e., an implementation of - * java.lang.annotation.Annotation. Given {@code @interface MyAnno}, a call to {@code - * implementsAnnotation} returns true when called on an AnnotatedDeclaredType representing a use - * of MyAnno. - * - * @return true if atm is an Annotation interface - */ - public static boolean implementsAnnotation(AnnotatedTypeMirror atm) { - if (atm.getKind() != TypeKind.DECLARED) { - return false; + return counter; + } + + // The innermost *array* type. + public static AnnotatedTypeMirror innerMostType(AnnotatedTypeMirror t) { + AnnotatedTypeMirror inner = t; + while (inner.getKind() == TypeKind.ARRAY) { + inner = ((AnnotatedArrayType) inner).getComponentType(); + } + return inner; + } + + /** + * Checks whether type contains the given modifier, also recursively in type arguments and arrays. + * This method might be easier to implement directly as instance method in AnnotatedTypeMirror; it + * corresponds to a "deep" version of {@link AnnotatedTypeMirror#hasAnnotation(AnnotationMirror)}. + * + * @param type the type to search + * @param modifier the modifier to search for + * @return whether the type contains the modifier + */ + public static boolean containsModifier(AnnotatedTypeMirror type, AnnotationMirror modifier) { + return containsModifierImpl(type, modifier, new ArrayList<>()); + } + + /* + * For type variables we might hit the same type again. We keep a list of visited types. + */ + private static boolean containsModifierImpl( + AnnotatedTypeMirror type, AnnotationMirror modifier, List visited) { + boolean found = type.hasAnnotation(modifier); + boolean vis = visited.contains(type); + visited.add(type); + + if (!found && !vis) { + if (type.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; + for (AnnotatedTypeMirror typeMirror : declaredType.getTypeArguments()) { + found |= containsModifierImpl(typeMirror, modifier, visited); + if (found) { + break; + } } - AnnotatedTypeMirror.AnnotatedDeclaredType declaredType = - (AnnotatedTypeMirror.AnnotatedDeclaredType) atm; - - Symbol.ClassSymbol classSymbol = - (Symbol.ClassSymbol) declaredType.getUnderlyingType().asElement(); - for (Type iface : classSymbol.getInterfaces()) { - if (TypesUtils.isDeclaredOfName(iface, annotationClassName)) { - return true; - } + } else if (type.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType arrayType = (AnnotatedArrayType) type; + found = containsModifierImpl(arrayType.getComponentType(), modifier, visited); + } else if (type.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) type; + if (atv.getUpperBound() != null) { + found = containsModifierImpl(atv.getUpperBound(), modifier, visited); } - - return false; - } - - public static boolean isEnum(AnnotatedTypeMirror typeMirror) { - if (typeMirror.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adt = (AnnotatedDeclaredType) typeMirror; - return TypesUtils.isDeclaredOfName( - adt.getUnderlyingType(), java.lang.Enum.class.getName()); + if (!found && atv.getLowerBound() != null) { + found = containsModifierImpl(atv.getLowerBound(), modifier, visited); } - - return false; - } - - public static boolean isDeclarationOfJavaLangEnum( - Types types, Elements elements, AnnotatedTypeMirror typeMirror) { - if (isEnum(typeMirror)) { - return elements.getTypeElement(Enum.class.getCanonicalName()) - .equals(((AnnotatedDeclaredType) typeMirror).getUnderlyingType().asElement()); + } else if (type.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType awc = (AnnotatedWildcardType) type; + if (awc.getExtendsBound() != null) { + found = containsModifierImpl(awc.getExtendsBound(), modifier, visited); } - - return false; - } - - /** - * Returns true if the typeVar1 and typeVar2 are two uses of the same type variable. - * - * @param types type utils - * @param typeVar1 a type variable - * @param typeVar2 a type variable - * @return true if the typeVar1 and typeVar2 are two uses of the same type variable - */ - @SuppressWarnings( - "interning:not.interned" // This is an equals method but @EqualsMethod can't be used - // because this method has 3 arguments. - ) - public static boolean haveSameDeclaration( - Types types, AnnotatedTypeVariable typeVar1, AnnotatedTypeVariable typeVar2) { - - if (typeVar1.getUnderlyingType() == typeVar2.getUnderlyingType()) { - return true; + if (!found && awc.getSuperBound() != null) { + found = containsModifierImpl(awc.getSuperBound(), modifier, visited); } - return types.isSameType(typeVar1.getUnderlyingType(), typeVar2.getUnderlyingType()); + } } - /** - * When overriding a method, you must include the same number of type parameters as the base - * method. By index, these parameters are considered equivalent to the type parameters of the - * overridden method. - * - *

Necessary conditions: - * - *

    - *
  • Both type variables are defined in methods. - *
  • One of the two methods overrides the other. - *
  • Within their method declaration, both types have the same type parameter index. - *
- * - * @return true if type1 and type2 are corresponding type variables (that is, either one - * "overrides" the other) - */ - public static boolean areCorrespondingTypeVariables( - Elements elements, AnnotatedTypeVariable type1, AnnotatedTypeVariable type2) { - TypeParameterElement type1ParamElem = - (TypeParameterElement) type1.getUnderlyingType().asElement(); - TypeParameterElement type2ParamElem = - (TypeParameterElement) type2.getUnderlyingType().asElement(); - - if (type1ParamElem.getGenericElement() instanceof ExecutableElement - && type2ParamElem.getGenericElement() instanceof ExecutableElement) { - ExecutableElement type1Executable = - (ExecutableElement) type1ParamElem.getGenericElement(); - ExecutableElement type2Executable = - (ExecutableElement) type2ParamElem.getGenericElement(); - - TypeElement type1Class = (TypeElement) type1Executable.getEnclosingElement(); - TypeElement type2Class = (TypeElement) type2Executable.getEnclosingElement(); - - boolean methodIsOverridden = - elements.overrides(type1Executable, type2Executable, type1Class) - || elements.overrides(type2Executable, type1Executable, type2Class); - if (methodIsOverridden) { - boolean haveSameIndex = - type1Executable.getTypeParameters().indexOf(type1ParamElem) - == type2Executable.getTypeParameters().indexOf(type2ParamElem); - return haveSameIndex; - } - } - - return false; + return found; + } + + /** java.lang.annotation.Annotation.class canonical name. */ + private static final @CanonicalName String annotationClassName = + java.lang.annotation.Annotation.class.getCanonicalName(); + + /** + * Returns true if the underlying type of this atm is a java.lang.annotation.Annotation. + * + * @return true if the underlying type of this atm is a java.lang.annotation.Annotation + */ + public static boolean isJavaLangAnnotation(AnnotatedTypeMirror atm) { + return TypesUtils.isDeclaredOfName(atm.getUnderlyingType(), annotationClassName); + } + + /** + * Returns true if atm is an Annotation interface, i.e., an implementation of + * java.lang.annotation.Annotation. Given {@code @interface MyAnno}, a call to {@code + * implementsAnnotation} returns true when called on an AnnotatedDeclaredType representing a use + * of MyAnno. + * + * @return true if atm is an Annotation interface + */ + public static boolean implementsAnnotation(AnnotatedTypeMirror atm) { + if (atm.getKind() != TypeKind.DECLARED) { + return false; } - - /** - * When comparing types against the bounds of a type variable, we may encounter other type - * variables, wildcards, and intersections in those bounds. This method traverses the bounds - * until it finds a concrete type from which it can pull an annotation. - * - * @param top the top of the hierarchy for which you are searching - * @return the AnnotationMirror that represents the type of toSearch in the hierarchy of top - */ - public static AnnotationMirror findEffectiveAnnotationInHierarchy( - QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch, AnnotationMirror top) { - return findEffectiveAnnotationInHierarchy(qualHierarchy, toSearch, top, false); + AnnotatedTypeMirror.AnnotatedDeclaredType declaredType = + (AnnotatedTypeMirror.AnnotatedDeclaredType) atm; + + Symbol.ClassSymbol classSymbol = + (Symbol.ClassSymbol) declaredType.getUnderlyingType().asElement(); + for (Type iface : classSymbol.getInterfaces()) { + if (TypesUtils.isDeclaredOfName(iface, annotationClassName)) { + return true; + } } - /** - * When comparing types against the bounds of a type variable, we may encounter other type - * variables, wildcards, and intersections in those bounds. This method traverses the bounds - * until it finds a concrete type from which it can pull an annotation. - * - * @param top the top of the hierarchy for which you are searching - * @param canBeEmpty whether or not the effective type can have NO annotation in the hierarchy - * specified by top. If this param is false, an exception will be thrown if no annotation is - * found. Otherwise the result is null. - * @return the AnnotationMirror that represents the type of {@code toSearch} in the hierarchy of - * {@code top} - */ - public static @Nullable AnnotationMirror findEffectiveAnnotationInHierarchy( - QualifierHierarchy qualHierarchy, - AnnotatedTypeMirror toSearch, - AnnotationMirror top, - boolean canBeEmpty) { - AnnotatedTypeMirror source = toSearch; - while (source.getAnnotationInHierarchy(top) == null) { - - switch (source.getKind()) { - case TYPEVAR: - source = ((AnnotatedTypeVariable) source).getUpperBound(); - break; - - case WILDCARD: - source = ((AnnotatedWildcardType) source).getExtendsBound(); - break; - - case INTERSECTION: - // if there are multiple conflicting annotations, choose the lowest - AnnotationMirror glb = - glbOfBoundsInHierarchy( - (AnnotatedIntersectionType) source, top, qualHierarchy); - - if (glb == null) { - throw new BugInCF( - "AnnotatedIntersectionType has no annotation in hierarchy " - + "on any of its supertypes." - + System.lineSeparator() - + "intersectionType=" - + source); - } - return glb; - - default: - if (canBeEmpty) { - return null; - } - - throw new BugInCF( - StringsPlume.joinLines( - "Unexpected AnnotatedTypeMirror with no primary annotation.", - "toSearch=" + toSearch, - "top=" + top, - "source=" + source)); - } - } + return false; + } - return source.getAnnotationInHierarchy(top); + public static boolean isEnum(AnnotatedTypeMirror typeMirror) { + if (typeMirror.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) typeMirror; + return TypesUtils.isDeclaredOfName(adt.getUnderlyingType(), java.lang.Enum.class.getName()); } - /** - * This method returns the effective annotation on the lower bound of a type, or on the type - * itself if the type has no lower bound (it is not a type variable, wildcard, or intersection). - * - * @param qualHierarchy the qualifier hierarchy - * @param toSearch the type whose lower bound to examine - * @return the set of effective annotation mirrors in all hierarchies - */ - public static AnnotationMirrorSet findEffectiveLowerBoundAnnotations( - QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { - AnnotatedTypeMirror source = toSearch; - TypeKind kind = source.getKind(); - while (kind == TypeKind.TYPEVAR - || kind == TypeKind.WILDCARD - || kind == TypeKind.INTERSECTION) { - - switch (source.getKind()) { - case TYPEVAR: - source = ((AnnotatedTypeVariable) source).getLowerBound(); - break; - - case WILDCARD: - source = ((AnnotatedWildcardType) source).getSuperBound(); - break; - - case INTERSECTION: - // if there are multiple conflicting annotations, choose the lowest - AnnotationMirrorSet glb = - glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); - return glb; - - default: - throw new BugInCF( - "Unexpected AnnotatedTypeMirror with no primary annotation;" - + " toSearch=" - + toSearch - + " source=" - + source); - } - - kind = source.getKind(); - } + return false; + } - return source.getAnnotations(); + public static boolean isDeclarationOfJavaLangEnum( + Types types, Elements elements, AnnotatedTypeMirror typeMirror) { + if (isEnum(typeMirror)) { + return elements + .getTypeElement(Enum.class.getCanonicalName()) + .equals(((AnnotatedDeclaredType) typeMirror).getUnderlyingType().asElement()); } - /** - * When comparing types against the bounds of a type variable, we may encounter other type - * variables, wildcards, and intersections in those bounds. This method traverses the bounds - * until it finds a concrete type from which it can pull an annotation. This occurs for every - * hierarchy in QualifierHierarchy. - * - * @param qualHierarchy the qualifier hierarchy - * @param toSearch the type whose effective annotations to determine - * @return the set of effective annotation mirrors in all hierarchies - */ - public static AnnotationMirrorSet findEffectiveAnnotations( - QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { - AnnotatedTypeMirror source = toSearch; - TypeKind kind = source.getKind(); - while (kind == TypeKind.TYPEVAR - || kind == TypeKind.WILDCARD - || kind == TypeKind.INTERSECTION) { - - switch (source.getKind()) { - case TYPEVAR: - source = ((AnnotatedTypeVariable) source).getUpperBound(); - break; - - case WILDCARD: - source = ((AnnotatedWildcardType) source).getExtendsBound(); - break; - - case INTERSECTION: - // if there are multiple conflicting annotations, choose the lowest - AnnotationMirrorSet glb = - glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); - return glb; - - default: - throw new BugInCF( - "Unexpected AnnotatedTypeMirror with no primary annotation;" - + " toSearch=" - + toSearch - + " source=" - + source); - } - - kind = source.getKind(); - } - - return source.getAnnotations(); + return false; + } + + /** + * Returns true if the typeVar1 and typeVar2 are two uses of the same type variable. + * + * @param types type utils + * @param typeVar1 a type variable + * @param typeVar2 a type variable + * @return true if the typeVar1 and typeVar2 are two uses of the same type variable + */ + @SuppressWarnings( + "interning:not.interned" // This is an equals method but @EqualsMethod can't be used + // because this method has 3 arguments. + ) + public static boolean haveSameDeclaration( + Types types, AnnotatedTypeVariable typeVar1, AnnotatedTypeVariable typeVar2) { + + if (typeVar1.getUnderlyingType() == typeVar2.getUnderlyingType()) { + return true; } - - private static AnnotationMirror glbOfBoundsInHierarchy( - AnnotatedIntersectionType isect, - AnnotationMirror top, - QualifierHierarchy qualHierarchy) { - AnnotationMirror anno = isect.getAnnotationInHierarchy(top); - for (AnnotatedTypeMirror bound : isect.getBounds()) { - AnnotationMirror boundAnno = bound.getAnnotationInHierarchy(top); - if (boundAnno != null - && (anno == null - || qualHierarchy.isSubtypeShallow( - boundAnno, - bound.getUnderlyingType(), - anno, - isect.getUnderlyingType()))) { - anno = boundAnno; - } - } - - return anno; + return types.isSameType(typeVar1.getUnderlyingType(), typeVar2.getUnderlyingType()); + } + + /** + * When overriding a method, you must include the same number of type parameters as the base + * method. By index, these parameters are considered equivalent to the type parameters of the + * overridden method. + * + *

Necessary conditions: + * + *

    + *
  • Both type variables are defined in methods. + *
  • One of the two methods overrides the other. + *
  • Within their method declaration, both types have the same type parameter index. + *
+ * + * @return true if type1 and type2 are corresponding type variables (that is, either one + * "overrides" the other) + */ + public static boolean areCorrespondingTypeVariables( + Elements elements, AnnotatedTypeVariable type1, AnnotatedTypeVariable type2) { + TypeParameterElement type1ParamElem = + (TypeParameterElement) type1.getUnderlyingType().asElement(); + TypeParameterElement type2ParamElem = + (TypeParameterElement) type2.getUnderlyingType().asElement(); + + if (type1ParamElem.getGenericElement() instanceof ExecutableElement + && type2ParamElem.getGenericElement() instanceof ExecutableElement) { + ExecutableElement type1Executable = (ExecutableElement) type1ParamElem.getGenericElement(); + ExecutableElement type2Executable = (ExecutableElement) type2ParamElem.getGenericElement(); + + TypeElement type1Class = (TypeElement) type1Executable.getEnclosingElement(); + TypeElement type2Class = (TypeElement) type2Executable.getEnclosingElement(); + + boolean methodIsOverridden = + elements.overrides(type1Executable, type2Executable, type1Class) + || elements.overrides(type2Executable, type1Executable, type2Class); + if (methodIsOverridden) { + boolean haveSameIndex = + type1Executable.getTypeParameters().indexOf(type1ParamElem) + == type2Executable.getTypeParameters().indexOf(type2ParamElem); + return haveSameIndex; + } } - /** - * Gets the lowest primary annotation of all bounds in the intersection. - * - * @param isect the intersection for which we are glbing bounds - * @param qualHierarchy the qualifier used to get the hierarchies in which to glb - * @return a set of annotations representing the glb of the intersection's bounds - */ - public static AnnotationMirrorSet glbOfBounds( - AnnotatedIntersectionType isect, QualifierHierarchy qualHierarchy) { - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror glbAnno = glbOfBoundsInHierarchy(isect, top, qualHierarchy); - if (glbAnno != null) { - result.add(glbAnno); - } - } - - return result; + return false; + } + + /** + * When comparing types against the bounds of a type variable, we may encounter other type + * variables, wildcards, and intersections in those bounds. This method traverses the bounds until + * it finds a concrete type from which it can pull an annotation. + * + * @param top the top of the hierarchy for which you are searching + * @return the AnnotationMirror that represents the type of toSearch in the hierarchy of top + */ + public static AnnotationMirror findEffectiveAnnotationInHierarchy( + QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch, AnnotationMirror top) { + return findEffectiveAnnotationInHierarchy(qualHierarchy, toSearch, top, false); + } + + /** + * When comparing types against the bounds of a type variable, we may encounter other type + * variables, wildcards, and intersections in those bounds. This method traverses the bounds until + * it finds a concrete type from which it can pull an annotation. + * + * @param top the top of the hierarchy for which you are searching + * @param canBeEmpty whether or not the effective type can have NO annotation in the hierarchy + * specified by top. If this param is false, an exception will be thrown if no annotation is + * found. Otherwise the result is null. + * @return the AnnotationMirror that represents the type of {@code toSearch} in the hierarchy of + * {@code top} + */ + public static @Nullable AnnotationMirror findEffectiveAnnotationInHierarchy( + QualifierHierarchy qualHierarchy, + AnnotatedTypeMirror toSearch, + AnnotationMirror top, + boolean canBeEmpty) { + AnnotatedTypeMirror source = toSearch; + while (source.getAnnotationInHierarchy(top) == null) { + + switch (source.getKind()) { + case TYPEVAR: + source = ((AnnotatedTypeVariable) source).getUpperBound(); + break; + + case WILDCARD: + source = ((AnnotatedWildcardType) source).getExtendsBound(); + break; + + case INTERSECTION: + // if there are multiple conflicting annotations, choose the lowest + AnnotationMirror glb = + glbOfBoundsInHierarchy((AnnotatedIntersectionType) source, top, qualHierarchy); + + if (glb == null) { + throw new BugInCF( + "AnnotatedIntersectionType has no annotation in hierarchy " + + "on any of its supertypes." + + System.lineSeparator() + + "intersectionType=" + + source); + } + return glb; + + default: + if (canBeEmpty) { + return null; + } + + throw new BugInCF( + StringsPlume.joinLines( + "Unexpected AnnotatedTypeMirror with no primary annotation.", + "toSearch=" + toSearch, + "top=" + top, + "source=" + source)); + } } - // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. - // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". - - /** - * This method identifies wildcard types that are unbound. - * - * @param wildcardType the type to check - * @return true if the given card is an unbounded wildcard - */ - public static boolean hasNoExplicitBound(AnnotatedTypeMirror wildcardType) { - return TypesUtils.hasNoExplicitBound(wildcardType.getUnderlyingType()); + return source.getAnnotationInHierarchy(top); + } + + /** + * This method returns the effective annotation on the lower bound of a type, or on the type + * itself if the type has no lower bound (it is not a type variable, wildcard, or intersection). + * + * @param qualHierarchy the qualifier hierarchy + * @param toSearch the type whose lower bound to examine + * @return the set of effective annotation mirrors in all hierarchies + */ + public static AnnotationMirrorSet findEffectiveLowerBoundAnnotations( + QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { + AnnotatedTypeMirror source = toSearch; + TypeKind kind = source.getKind(); + while (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD || kind == TypeKind.INTERSECTION) { + + switch (source.getKind()) { + case TYPEVAR: + source = ((AnnotatedTypeVariable) source).getLowerBound(); + break; + + case WILDCARD: + source = ((AnnotatedWildcardType) source).getSuperBound(); + break; + + case INTERSECTION: + // if there are multiple conflicting annotations, choose the lowest + AnnotationMirrorSet glb = glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); + return glb; + + default: + throw new BugInCF( + "Unexpected AnnotatedTypeMirror with no primary annotation;" + + " toSearch=" + + toSearch + + " source=" + + source); + } + + kind = source.getKind(); } - /** - * Returns true if wildcard type is explicitly super bounded. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly super bounded - * @deprecated Use {@link #hasExplicitSuperBound(AnnotatedTypeMirror)} - */ - @Deprecated // 2023-03-01 - public static boolean isExplicitlySuperBounded(AnnotatedWildcardType wildcardType) { - return hasExplicitSuperBound(wildcardType); + return source.getAnnotations(); + } + + /** + * When comparing types against the bounds of a type variable, we may encounter other type + * variables, wildcards, and intersections in those bounds. This method traverses the bounds until + * it finds a concrete type from which it can pull an annotation. This occurs for every hierarchy + * in QualifierHierarchy. + * + * @param qualHierarchy the qualifier hierarchy + * @param toSearch the type whose effective annotations to determine + * @return the set of effective annotation mirrors in all hierarchies + */ + public static AnnotationMirrorSet findEffectiveAnnotations( + QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { + AnnotatedTypeMirror source = toSearch; + TypeKind kind = source.getKind(); + while (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD || kind == TypeKind.INTERSECTION) { + + switch (source.getKind()) { + case TYPEVAR: + source = ((AnnotatedTypeVariable) source).getUpperBound(); + break; + + case WILDCARD: + source = ((AnnotatedWildcardType) source).getExtendsBound(); + break; + + case INTERSECTION: + // if there are multiple conflicting annotations, choose the lowest + AnnotationMirrorSet glb = glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); + return glb; + + default: + throw new BugInCF( + "Unexpected AnnotatedTypeMirror with no primary annotation;" + + " toSearch=" + + toSearch + + " source=" + + source); + } + + kind = source.getKind(); } - /** - * Returns true if wildcard type has an explicit super bound. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly super bounded - */ - public static boolean hasExplicitSuperBound(AnnotatedTypeMirror wildcardType) { - return TypesUtils.hasExplicitSuperBound(wildcardType.getUnderlyingType()); + return source.getAnnotations(); + } + + private static AnnotationMirror glbOfBoundsInHierarchy( + AnnotatedIntersectionType isect, AnnotationMirror top, QualifierHierarchy qualHierarchy) { + AnnotationMirror anno = isect.getAnnotationInHierarchy(top); + for (AnnotatedTypeMirror bound : isect.getBounds()) { + AnnotationMirror boundAnno = bound.getAnnotationInHierarchy(top); + if (boundAnno != null + && (anno == null + || qualHierarchy.isSubtypeShallow( + boundAnno, bound.getUnderlyingType(), anno, isect.getUnderlyingType()))) { + anno = boundAnno; + } } - /** - * Returns true if wildcard type is explicitly extends bounded. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly extends bounded - * @deprecated Use {@link #hasExplicitExtendsBound(AnnotatedTypeMirror)}. - */ - @Deprecated // 2023-03-01 - public static boolean isExplicitlyExtendsBounded(AnnotatedWildcardType wildcardType) { - return hasExplicitExtendsBound(wildcardType); + return anno; + } + + /** + * Gets the lowest primary annotation of all bounds in the intersection. + * + * @param isect the intersection for which we are glbing bounds + * @param qualHierarchy the qualifier used to get the hierarchies in which to glb + * @return a set of annotations representing the glb of the intersection's bounds + */ + public static AnnotationMirrorSet glbOfBounds( + AnnotatedIntersectionType isect, QualifierHierarchy qualHierarchy) { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror glbAnno = glbOfBoundsInHierarchy(isect, top, qualHierarchy); + if (glbAnno != null) { + result.add(glbAnno); + } } - /** - * Returns true if wildcard type has an explicit extends bound. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly extends bounded - */ - public static boolean hasExplicitExtendsBound(AnnotatedTypeMirror wildcardType) { - return TypesUtils.hasExplicitExtendsBound(wildcardType.getUnderlyingType()); + return result; + } + + // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. + // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". + + /** + * This method identifies wildcard types that are unbound. + * + * @param wildcardType the type to check + * @return true if the given card is an unbounded wildcard + */ + public static boolean hasNoExplicitBound(AnnotatedTypeMirror wildcardType) { + return TypesUtils.hasNoExplicitBound(wildcardType.getUnderlyingType()); + } + + /** + * Returns true if wildcard type is explicitly super bounded. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly super bounded + * @deprecated Use {@link #hasExplicitSuperBound(AnnotatedTypeMirror)} + */ + @Deprecated // 2023-03-01 + public static boolean isExplicitlySuperBounded(AnnotatedWildcardType wildcardType) { + return hasExplicitSuperBound(wildcardType); + } + + /** + * Returns true if wildcard type has an explicit super bound. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly super bounded + */ + public static boolean hasExplicitSuperBound(AnnotatedTypeMirror wildcardType) { + return TypesUtils.hasExplicitSuperBound(wildcardType.getUnderlyingType()); + } + + /** + * Returns true if wildcard type is explicitly extends bounded. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly extends bounded + * @deprecated Use {@link #hasExplicitExtendsBound(AnnotatedTypeMirror)}. + */ + @Deprecated // 2023-03-01 + public static boolean isExplicitlyExtendsBounded(AnnotatedWildcardType wildcardType) { + return hasExplicitExtendsBound(wildcardType); + } + + /** + * Returns true if wildcard type has an explicit extends bound. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly extends bounded + */ + public static boolean hasExplicitExtendsBound(AnnotatedTypeMirror wildcardType) { + return TypesUtils.hasExplicitExtendsBound(wildcardType.getUnderlyingType()); + } + + /** + * Returns true if this type is super bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is super bounded or unbounded + */ + public static boolean isUnboundedOrSuperBounded(AnnotatedWildcardType wildcardType) { + return TypesUtils.isUnboundedOrSuperBounded(wildcardType.getUnderlyingType()); + } + + /** + * Returns true if this type is extends bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is extends bounded or unbounded + */ + public static boolean isUnboundedOrExtendsBounded(AnnotatedWildcardType wildcardType) { + return TypesUtils.isUnboundedOrExtendsBounded(wildcardType.getUnderlyingType()); + } + + /** + * Copies explicit annotations and annotations resulting from resolution of polymorphic qualifiers + * from {@code constructor} to {@code returnType}. If {@code returnType} has an annotation in the + * same hierarchy of an annotation to be copied, that annotation is not copied. + * + * @param atypeFactory type factory + * @param returnType return type to copy annotations to + * @param constructorType the ATM for the constructor + */ + public static void copyOnlyExplicitConstructorAnnotations( + AnnotatedTypeFactory atypeFactory, + AnnotatedDeclaredType returnType, + AnnotatedExecutableType constructorType) { + + // TODO: There will be a nicer way to access this in 308 soon. + List decall = + ((Symbol) constructorType.getElement()).getRawTypeAttributes(); + AnnotationMirrorSet decret = new AnnotationMirrorSet(); + for (Attribute.TypeCompound da : decall) { + if (da.position.type == com.sun.tools.javac.code.TargetType.METHOD_RETURN) { + decret.add(da); + } } - /** - * Returns true if this type is super bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is super bounded or unbounded - */ - public static boolean isUnboundedOrSuperBounded(AnnotatedWildcardType wildcardType) { - return TypesUtils.isUnboundedOrSuperBounded(wildcardType.getUnderlyingType()); - } + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - /** - * Returns true if this type is extends bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is extends bounded or unbounded - */ - public static boolean isUnboundedOrExtendsBounded(AnnotatedWildcardType wildcardType) { - return TypesUtils.isUnboundedOrExtendsBounded(wildcardType.getUnderlyingType()); + // Collect all polymorphic qualifiers; we should substitute them. + AnnotationMirrorSet polys = new AnnotationMirrorSet(); + for (AnnotationMirror anno : returnType.getAnnotations()) { + if (qualHierarchy.isPolymorphicQualifier(anno)) { + polys.add(anno); + } } - /** - * Copies explicit annotations and annotations resulting from resolution of polymorphic - * qualifiers from {@code constructor} to {@code returnType}. If {@code returnType} has an - * annotation in the same hierarchy of an annotation to be copied, that annotation is not - * copied. - * - * @param atypeFactory type factory - * @param returnType return type to copy annotations to - * @param constructorType the ATM for the constructor - */ - public static void copyOnlyExplicitConstructorAnnotations( - AnnotatedTypeFactory atypeFactory, - AnnotatedDeclaredType returnType, - AnnotatedExecutableType constructorType) { - - // TODO: There will be a nicer way to access this in 308 soon. - List decall = - ((Symbol) constructorType.getElement()).getRawTypeAttributes(); - AnnotationMirrorSet decret = new AnnotationMirrorSet(); - for (Attribute.TypeCompound da : decall) { - if (da.position.type == com.sun.tools.javac.code.TargetType.METHOD_RETURN) { - decret.add(da); - } - } - - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - - // Collect all polymorphic qualifiers; we should substitute them. - AnnotationMirrorSet polys = new AnnotationMirrorSet(); - for (AnnotationMirror anno : returnType.getAnnotations()) { - if (qualHierarchy.isPolymorphicQualifier(anno)) { - polys.add(anno); - } + for (AnnotationMirror cta : constructorType.getReturnType().getAnnotations()) { + AnnotationMirror ctatop = qualHierarchy.getTopAnnotation(cta); + if (returnType.hasAnnotationInHierarchy(cta)) { + continue; + } + if (atypeFactory.isSupportedQualifier(cta) && !returnType.hasAnnotationInHierarchy(cta)) { + for (AnnotationMirror fromDecl : decret) { + if (atypeFactory.isSupportedQualifier(fromDecl) + && AnnotationUtils.areSame(ctatop, qualHierarchy.getTopAnnotation(fromDecl))) { + returnType.addAnnotation(cta); + break; + } } - - for (AnnotationMirror cta : constructorType.getReturnType().getAnnotations()) { - AnnotationMirror ctatop = qualHierarchy.getTopAnnotation(cta); - if (returnType.hasAnnotationInHierarchy(cta)) { - continue; - } - if (atypeFactory.isSupportedQualifier(cta) - && !returnType.hasAnnotationInHierarchy(cta)) { - for (AnnotationMirror fromDecl : decret) { - if (atypeFactory.isSupportedQualifier(fromDecl) - && AnnotationUtils.areSame( - ctatop, qualHierarchy.getTopAnnotation(fromDecl))) { - returnType.addAnnotation(cta); - break; - } - } - } - - // Go through the polymorphic qualifiers and see whether - // there is anything left to replace. - for (AnnotationMirror pa : polys) { - if (AnnotationUtils.areSame(ctatop, qualHierarchy.getTopAnnotation(pa))) { - returnType.replaceAnnotation(cta); - break; - } - } + } + + // Go through the polymorphic qualifiers and see whether + // there is anything left to replace. + for (AnnotationMirror pa : polys) { + if (AnnotationUtils.areSame(ctatop, qualHierarchy.getTopAnnotation(pa))) { + returnType.replaceAnnotation(cta); + break; } + } } - - /** - * Add all the annotations in {@code declaredType} to {@code annotatedDeclaredType}. - * - *

(The {@code TypeMirror} returned by {@code annotatedDeclaredType#getUnderlyingType} may - * not have all the annotations on the type, so allow the user to specify a different one.) - * - * @param annotatedDeclaredType annotated type to which annotations are added - * @param declaredType a type that may have annotations - */ - public static void applyAnnotationsFromDeclaredType( - AnnotatedDeclaredType annotatedDeclaredType, DeclaredType declaredType) { - TypeMirror underlyingTypeMirror = declaredType; - while (annotatedDeclaredType != null) { - List annosOnTypeMirror = - underlyingTypeMirror.getAnnotationMirrors(); - annotatedDeclaredType.addAnnotations(annosOnTypeMirror); - annotatedDeclaredType = annotatedDeclaredType.getEnclosingType(); - underlyingTypeMirror = ((DeclaredType) underlyingTypeMirror).getEnclosingType(); - } + } + + /** + * Add all the annotations in {@code declaredType} to {@code annotatedDeclaredType}. + * + *

(The {@code TypeMirror} returned by {@code annotatedDeclaredType#getUnderlyingType} may not + * have all the annotations on the type, so allow the user to specify a different one.) + * + * @param annotatedDeclaredType annotated type to which annotations are added + * @param declaredType a type that may have annotations + */ + public static void applyAnnotationsFromDeclaredType( + AnnotatedDeclaredType annotatedDeclaredType, DeclaredType declaredType) { + TypeMirror underlyingTypeMirror = declaredType; + while (annotatedDeclaredType != null) { + List annosOnTypeMirror = + underlyingTypeMirror.getAnnotationMirrors(); + annotatedDeclaredType.addAnnotations(annosOnTypeMirror); + annotatedDeclaredType = annotatedDeclaredType.getEnclosingType(); + underlyingTypeMirror = ((DeclaredType) underlyingTypeMirror).getEnclosingType(); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java index c526d392c5b..b07670f389f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java @@ -1,32 +1,30 @@ package org.checkerframework.framework.util; -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.Collection; - import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.dataflow.qual.SideEffectFree; /** Converts AnnotationMirrors to Strings. Used when converting AnnotatedTypeMirrors to Strings. */ public interface AnnotationFormatter { - /** - * Converts a collection of annotation mirrors into a String. - * - * @param annos a collection of annotations to print - * @param printInvisible whether or not to print "invisible" annotation mirrors - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @return a string representation of annos - */ - @SideEffectFree - public String formatAnnotationString( - Collection annos, boolean printInvisible); + /** + * Converts a collection of annotation mirrors into a String. + * + * @param annos a collection of annotations to print + * @param printInvisible whether or not to print "invisible" annotation mirrors + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @return a string representation of annos + */ + @SideEffectFree + public String formatAnnotationString( + Collection annos, boolean printInvisible); - /** - * Converts an individual annotation mirror into a String. - * - * @param anno the annotation mirror to convert - * @return a String representation of anno - */ - @SideEffectFree - public String formatAnnotationMirror(AnnotationMirror anno); + /** + * Converts an individual annotation mirror into a String. + * + * @param anno the annotation mirror to convert + * @return a String representation of anno + */ + @SideEffectFree + public String formatAnnotationMirror(AnnotationMirror anno); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java index 854fd9f0ba6..93d0d49b300 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java @@ -22,41 +22,41 @@ * AtmKind.ordinal(). See AtmCombo.comboMap */ enum AtmKind { - ARRAY(AnnotatedArrayType.class), - DECLARED(AnnotatedDeclaredType.class), - EXECUTABLE(AnnotatedExecutableType.class), - INTERSECTION(AnnotatedIntersectionType.class), - NONE(AnnotatedNoType.class), - NULL(AnnotatedNullType.class), - PRIMITIVE(AnnotatedPrimitiveType.class), - TYPEVAR(AnnotatedTypeVariable.class), - UNION(AnnotatedUnionType.class), - WILDCARD(AnnotatedWildcardType.class); - - // The AnnotatedTypeMirror subclass that represents types of this kind - public final Class atmClass; - - AtmKind(Class atmClass) { - this.atmClass = atmClass; + ARRAY(AnnotatedArrayType.class), + DECLARED(AnnotatedDeclaredType.class), + EXECUTABLE(AnnotatedExecutableType.class), + INTERSECTION(AnnotatedIntersectionType.class), + NONE(AnnotatedNoType.class), + NULL(AnnotatedNullType.class), + PRIMITIVE(AnnotatedPrimitiveType.class), + TYPEVAR(AnnotatedTypeVariable.class), + UNION(AnnotatedUnionType.class), + WILDCARD(AnnotatedWildcardType.class); + + // The AnnotatedTypeMirror subclass that represents types of this kind + public final Class atmClass; + + AtmKind(Class atmClass) { + this.atmClass = atmClass; + } + + /** + * Returns the AtmKind corresponding to the class of atm. + * + * @return the AtmKind corresponding to the class of atm + */ + public static AtmKind valueOf(AnnotatedTypeMirror atm) { + Class argClass = atm.getClass(); + + for (AtmKind atmKind : AtmKind.values()) { + Class kindClass = atmKind.atmClass; + if (argClass == kindClass) { + return atmKind; + } } - /** - * Returns the AtmKind corresponding to the class of atm. - * - * @return the AtmKind corresponding to the class of atm - */ - public static AtmKind valueOf(AnnotatedTypeMirror atm) { - Class argClass = atm.getClass(); - - for (AtmKind atmKind : AtmKind.values()) { - Class kindClass = atmKind.atmClass; - if (argClass == kindClass) { - return atmKind; - } - } - - throw new BugInCF("Unhandled AnnotatedTypeMirror ( " + atm.getClass() + " )"); - } + throw new BugInCF("Unhandled AnnotatedTypeMirror ( " + atm.getClass() + " )"); + } } /** @@ -81,651 +81,589 @@ public static AtmKind valueOf(AnnotatedTypeMirror atm) { */ @SuppressWarnings("EnumOrdinal") // Use enum ordinals as array indices. public enum AtmCombo { - ARRAY_ARRAY(AtmKind.ARRAY, AtmKind.ARRAY), - ARRAY_DECLARED(AtmKind.ARRAY, AtmKind.DECLARED), - ARRAY_EXECUTABLE(AtmKind.ARRAY, AtmKind.EXECUTABLE), - ARRAY_INTERSECTION(AtmKind.ARRAY, AtmKind.INTERSECTION), - ARRAY_NONE(AtmKind.ARRAY, AtmKind.NONE), - ARRAY_NULL(AtmKind.ARRAY, AtmKind.NULL), - ARRAY_PRIMITIVE(AtmKind.ARRAY, AtmKind.PRIMITIVE), - ARRAY_UNION(AtmKind.ARRAY, AtmKind.UNION), - ARRAY_TYPEVAR(AtmKind.ARRAY, AtmKind.TYPEVAR), - ARRAY_WILDCARD(AtmKind.ARRAY, AtmKind.WILDCARD), - - DECLARED_ARRAY(AtmKind.DECLARED, AtmKind.ARRAY), - DECLARED_DECLARED(AtmKind.DECLARED, AtmKind.DECLARED), - DECLARED_EXECUTABLE(AtmKind.DECLARED, AtmKind.EXECUTABLE), - DECLARED_INTERSECTION(AtmKind.DECLARED, AtmKind.INTERSECTION), - DECLARED_NONE(AtmKind.DECLARED, AtmKind.NONE), - DECLARED_NULL(AtmKind.DECLARED, AtmKind.NULL), - DECLARED_PRIMITIVE(AtmKind.DECLARED, AtmKind.PRIMITIVE), - DECLARED_TYPEVAR(AtmKind.DECLARED, AtmKind.TYPEVAR), - DECLARED_UNION(AtmKind.DECLARED, AtmKind.UNION), - DECLARED_WILDCARD(AtmKind.DECLARED, AtmKind.WILDCARD), - - EXECUTABLE_ARRAY(AtmKind.EXECUTABLE, AtmKind.ARRAY), - EXECUTABLE_DECLARED(AtmKind.EXECUTABLE, AtmKind.DECLARED), - EXECUTABLE_EXECUTABLE(AtmKind.EXECUTABLE, AtmKind.EXECUTABLE), - EXECUTABLE_INTERSECTION(AtmKind.EXECUTABLE, AtmKind.INTERSECTION), - EXECUTABLE_NONE(AtmKind.EXECUTABLE, AtmKind.NONE), - EXECUTABLE_NULL(AtmKind.EXECUTABLE, AtmKind.NULL), - EXECUTABLE_PRIMITIVE(AtmKind.EXECUTABLE, AtmKind.PRIMITIVE), - EXECUTABLE_TYPEVAR(AtmKind.EXECUTABLE, AtmKind.TYPEVAR), - EXECUTABLE_UNION(AtmKind.EXECUTABLE, AtmKind.UNION), - EXECUTABLE_WILDCARD(AtmKind.EXECUTABLE, AtmKind.WILDCARD), - - INTERSECTION_ARRAY(AtmKind.INTERSECTION, AtmKind.ARRAY), - INTERSECTION_DECLARED(AtmKind.INTERSECTION, AtmKind.DECLARED), - INTERSECTION_EXECUTABLE(AtmKind.INTERSECTION, AtmKind.EXECUTABLE), - INTERSECTION_INTERSECTION(AtmKind.INTERSECTION, AtmKind.INTERSECTION), - INTERSECTION_NONE(AtmKind.INTERSECTION, AtmKind.NONE), - INTERSECTION_NULL(AtmKind.INTERSECTION, AtmKind.NULL), - INTERSECTION_PRIMITIVE(AtmKind.INTERSECTION, AtmKind.PRIMITIVE), - INTERSECTION_TYPEVAR(AtmKind.INTERSECTION, AtmKind.TYPEVAR), - INTERSECTION_UNION(AtmKind.INTERSECTION, AtmKind.UNION), - INTERSECTION_WILDCARD(AtmKind.INTERSECTION, AtmKind.WILDCARD), - - NONE_ARRAY(AtmKind.NONE, AtmKind.ARRAY), - NONE_DECLARED(AtmKind.NONE, AtmKind.DECLARED), - NONE_EXECUTABLE(AtmKind.NONE, AtmKind.EXECUTABLE), - NONE_INTERSECTION(AtmKind.NONE, AtmKind.INTERSECTION), - NONE_NONE(AtmKind.NONE, AtmKind.NONE), - NONE_NULL(AtmKind.NONE, AtmKind.NULL), - NONE_PRIMITIVE(AtmKind.NONE, AtmKind.PRIMITIVE), - NONE_TYPEVAR(AtmKind.NONE, AtmKind.TYPEVAR), - NONE_UNION(AtmKind.NONE, AtmKind.UNION), - NONE_WILDCARD(AtmKind.NONE, AtmKind.WILDCARD), - - NULL_ARRAY(AtmKind.NULL, AtmKind.ARRAY), - NULL_DECLARED(AtmKind.NULL, AtmKind.DECLARED), - NULL_EXECUTABLE(AtmKind.NULL, AtmKind.EXECUTABLE), - NULL_INTERSECTION(AtmKind.NULL, AtmKind.INTERSECTION), - NULL_NONE(AtmKind.NULL, AtmKind.NONE), - NULL_NULL(AtmKind.NULL, AtmKind.NULL), - NULL_PRIMITIVE(AtmKind.NULL, AtmKind.PRIMITIVE), - NULL_TYPEVAR(AtmKind.NULL, AtmKind.TYPEVAR), - NULL_UNION(AtmKind.NULL, AtmKind.UNION), - NULL_WILDCARD(AtmKind.NULL, AtmKind.WILDCARD), - - PRIMITIVE_ARRAY(AtmKind.PRIMITIVE, AtmKind.ARRAY), - PRIMITIVE_DECLARED(AtmKind.PRIMITIVE, AtmKind.DECLARED), - PRIMITIVE_EXECUTABLE(AtmKind.PRIMITIVE, AtmKind.EXECUTABLE), - PRIMITIVE_INTERSECTION(AtmKind.PRIMITIVE, AtmKind.INTERSECTION), - PRIMITIVE_NONE(AtmKind.PRIMITIVE, AtmKind.NONE), - PRIMITIVE_NULL(AtmKind.PRIMITIVE, AtmKind.NULL), - PRIMITIVE_PRIMITIVE(AtmKind.PRIMITIVE, AtmKind.PRIMITIVE), - PRIMITIVE_TYPEVAR(AtmKind.PRIMITIVE, AtmKind.TYPEVAR), - PRIMITIVE_UNION(AtmKind.PRIMITIVE, AtmKind.UNION), - PRIMITIVE_WILDCARD(AtmKind.PRIMITIVE, AtmKind.WILDCARD), - - TYPEVAR_ARRAY(AtmKind.TYPEVAR, AtmKind.ARRAY), - TYPEVAR_DECLARED(AtmKind.TYPEVAR, AtmKind.DECLARED), - TYPEVAR_EXECUTABLE(AtmKind.TYPEVAR, AtmKind.EXECUTABLE), - TYPEVAR_INTERSECTION(AtmKind.TYPEVAR, AtmKind.INTERSECTION), - TYPEVAR_NONE(AtmKind.TYPEVAR, AtmKind.NONE), - TYPEVAR_NULL(AtmKind.TYPEVAR, AtmKind.NULL), - TYPEVAR_PRIMITIVE(AtmKind.TYPEVAR, AtmKind.PRIMITIVE), - TYPEVAR_TYPEVAR(AtmKind.TYPEVAR, AtmKind.TYPEVAR), - TYPEVAR_UNION(AtmKind.TYPEVAR, AtmKind.UNION), - TYPEVAR_WILDCARD(AtmKind.TYPEVAR, AtmKind.WILDCARD), - - UNION_ARRAY(AtmKind.UNION, AtmKind.ARRAY), - UNION_DECLARED(AtmKind.UNION, AtmKind.DECLARED), - UNION_EXECUTABLE(AtmKind.UNION, AtmKind.EXECUTABLE), - UNION_INTERSECTION(AtmKind.UNION, AtmKind.INTERSECTION), - UNION_NONE(AtmKind.UNION, AtmKind.NONE), - UNION_NULL(AtmKind.UNION, AtmKind.NULL), - UNION_PRIMITIVE(AtmKind.UNION, AtmKind.PRIMITIVE), - UNION_TYPEVAR(AtmKind.UNION, AtmKind.TYPEVAR), - UNION_UNION(AtmKind.UNION, AtmKind.UNION), - UNION_WILDCARD(AtmKind.UNION, AtmKind.WILDCARD), - - WILDCARD_ARRAY(AtmKind.WILDCARD, AtmKind.ARRAY), - WILDCARD_DECLARED(AtmKind.WILDCARD, AtmKind.DECLARED), - WILDCARD_EXECUTABLE(AtmKind.WILDCARD, AtmKind.EXECUTABLE), - WILDCARD_INTERSECTION(AtmKind.WILDCARD, AtmKind.INTERSECTION), - WILDCARD_NONE(AtmKind.WILDCARD, AtmKind.NONE), - WILDCARD_NULL(AtmKind.WILDCARD, AtmKind.NULL), - WILDCARD_PRIMITIVE(AtmKind.WILDCARD, AtmKind.PRIMITIVE), - WILDCARD_TYPEVAR(AtmKind.WILDCARD, AtmKind.TYPEVAR), - WILDCARD_UNION(AtmKind.WILDCARD, AtmKind.UNION), - WILDCARD_WILDCARD(AtmKind.WILDCARD, AtmKind.WILDCARD); - - /** First AtmKind. */ - public final AtmKind type1Kind; - - /** Second AtmKind. */ - public final AtmKind type2Kind; - - /** - * Creates an AtmCombo. - * - * @param type1Kind first kind - * @param type2Kind second kind - */ - AtmCombo(AtmKind type1Kind, AtmKind type2Kind) { - this.type1Kind = type1Kind; - this.type2Kind = type2Kind; + ARRAY_ARRAY(AtmKind.ARRAY, AtmKind.ARRAY), + ARRAY_DECLARED(AtmKind.ARRAY, AtmKind.DECLARED), + ARRAY_EXECUTABLE(AtmKind.ARRAY, AtmKind.EXECUTABLE), + ARRAY_INTERSECTION(AtmKind.ARRAY, AtmKind.INTERSECTION), + ARRAY_NONE(AtmKind.ARRAY, AtmKind.NONE), + ARRAY_NULL(AtmKind.ARRAY, AtmKind.NULL), + ARRAY_PRIMITIVE(AtmKind.ARRAY, AtmKind.PRIMITIVE), + ARRAY_UNION(AtmKind.ARRAY, AtmKind.UNION), + ARRAY_TYPEVAR(AtmKind.ARRAY, AtmKind.TYPEVAR), + ARRAY_WILDCARD(AtmKind.ARRAY, AtmKind.WILDCARD), + + DECLARED_ARRAY(AtmKind.DECLARED, AtmKind.ARRAY), + DECLARED_DECLARED(AtmKind.DECLARED, AtmKind.DECLARED), + DECLARED_EXECUTABLE(AtmKind.DECLARED, AtmKind.EXECUTABLE), + DECLARED_INTERSECTION(AtmKind.DECLARED, AtmKind.INTERSECTION), + DECLARED_NONE(AtmKind.DECLARED, AtmKind.NONE), + DECLARED_NULL(AtmKind.DECLARED, AtmKind.NULL), + DECLARED_PRIMITIVE(AtmKind.DECLARED, AtmKind.PRIMITIVE), + DECLARED_TYPEVAR(AtmKind.DECLARED, AtmKind.TYPEVAR), + DECLARED_UNION(AtmKind.DECLARED, AtmKind.UNION), + DECLARED_WILDCARD(AtmKind.DECLARED, AtmKind.WILDCARD), + + EXECUTABLE_ARRAY(AtmKind.EXECUTABLE, AtmKind.ARRAY), + EXECUTABLE_DECLARED(AtmKind.EXECUTABLE, AtmKind.DECLARED), + EXECUTABLE_EXECUTABLE(AtmKind.EXECUTABLE, AtmKind.EXECUTABLE), + EXECUTABLE_INTERSECTION(AtmKind.EXECUTABLE, AtmKind.INTERSECTION), + EXECUTABLE_NONE(AtmKind.EXECUTABLE, AtmKind.NONE), + EXECUTABLE_NULL(AtmKind.EXECUTABLE, AtmKind.NULL), + EXECUTABLE_PRIMITIVE(AtmKind.EXECUTABLE, AtmKind.PRIMITIVE), + EXECUTABLE_TYPEVAR(AtmKind.EXECUTABLE, AtmKind.TYPEVAR), + EXECUTABLE_UNION(AtmKind.EXECUTABLE, AtmKind.UNION), + EXECUTABLE_WILDCARD(AtmKind.EXECUTABLE, AtmKind.WILDCARD), + + INTERSECTION_ARRAY(AtmKind.INTERSECTION, AtmKind.ARRAY), + INTERSECTION_DECLARED(AtmKind.INTERSECTION, AtmKind.DECLARED), + INTERSECTION_EXECUTABLE(AtmKind.INTERSECTION, AtmKind.EXECUTABLE), + INTERSECTION_INTERSECTION(AtmKind.INTERSECTION, AtmKind.INTERSECTION), + INTERSECTION_NONE(AtmKind.INTERSECTION, AtmKind.NONE), + INTERSECTION_NULL(AtmKind.INTERSECTION, AtmKind.NULL), + INTERSECTION_PRIMITIVE(AtmKind.INTERSECTION, AtmKind.PRIMITIVE), + INTERSECTION_TYPEVAR(AtmKind.INTERSECTION, AtmKind.TYPEVAR), + INTERSECTION_UNION(AtmKind.INTERSECTION, AtmKind.UNION), + INTERSECTION_WILDCARD(AtmKind.INTERSECTION, AtmKind.WILDCARD), + + NONE_ARRAY(AtmKind.NONE, AtmKind.ARRAY), + NONE_DECLARED(AtmKind.NONE, AtmKind.DECLARED), + NONE_EXECUTABLE(AtmKind.NONE, AtmKind.EXECUTABLE), + NONE_INTERSECTION(AtmKind.NONE, AtmKind.INTERSECTION), + NONE_NONE(AtmKind.NONE, AtmKind.NONE), + NONE_NULL(AtmKind.NONE, AtmKind.NULL), + NONE_PRIMITIVE(AtmKind.NONE, AtmKind.PRIMITIVE), + NONE_TYPEVAR(AtmKind.NONE, AtmKind.TYPEVAR), + NONE_UNION(AtmKind.NONE, AtmKind.UNION), + NONE_WILDCARD(AtmKind.NONE, AtmKind.WILDCARD), + + NULL_ARRAY(AtmKind.NULL, AtmKind.ARRAY), + NULL_DECLARED(AtmKind.NULL, AtmKind.DECLARED), + NULL_EXECUTABLE(AtmKind.NULL, AtmKind.EXECUTABLE), + NULL_INTERSECTION(AtmKind.NULL, AtmKind.INTERSECTION), + NULL_NONE(AtmKind.NULL, AtmKind.NONE), + NULL_NULL(AtmKind.NULL, AtmKind.NULL), + NULL_PRIMITIVE(AtmKind.NULL, AtmKind.PRIMITIVE), + NULL_TYPEVAR(AtmKind.NULL, AtmKind.TYPEVAR), + NULL_UNION(AtmKind.NULL, AtmKind.UNION), + NULL_WILDCARD(AtmKind.NULL, AtmKind.WILDCARD), + + PRIMITIVE_ARRAY(AtmKind.PRIMITIVE, AtmKind.ARRAY), + PRIMITIVE_DECLARED(AtmKind.PRIMITIVE, AtmKind.DECLARED), + PRIMITIVE_EXECUTABLE(AtmKind.PRIMITIVE, AtmKind.EXECUTABLE), + PRIMITIVE_INTERSECTION(AtmKind.PRIMITIVE, AtmKind.INTERSECTION), + PRIMITIVE_NONE(AtmKind.PRIMITIVE, AtmKind.NONE), + PRIMITIVE_NULL(AtmKind.PRIMITIVE, AtmKind.NULL), + PRIMITIVE_PRIMITIVE(AtmKind.PRIMITIVE, AtmKind.PRIMITIVE), + PRIMITIVE_TYPEVAR(AtmKind.PRIMITIVE, AtmKind.TYPEVAR), + PRIMITIVE_UNION(AtmKind.PRIMITIVE, AtmKind.UNION), + PRIMITIVE_WILDCARD(AtmKind.PRIMITIVE, AtmKind.WILDCARD), + + TYPEVAR_ARRAY(AtmKind.TYPEVAR, AtmKind.ARRAY), + TYPEVAR_DECLARED(AtmKind.TYPEVAR, AtmKind.DECLARED), + TYPEVAR_EXECUTABLE(AtmKind.TYPEVAR, AtmKind.EXECUTABLE), + TYPEVAR_INTERSECTION(AtmKind.TYPEVAR, AtmKind.INTERSECTION), + TYPEVAR_NONE(AtmKind.TYPEVAR, AtmKind.NONE), + TYPEVAR_NULL(AtmKind.TYPEVAR, AtmKind.NULL), + TYPEVAR_PRIMITIVE(AtmKind.TYPEVAR, AtmKind.PRIMITIVE), + TYPEVAR_TYPEVAR(AtmKind.TYPEVAR, AtmKind.TYPEVAR), + TYPEVAR_UNION(AtmKind.TYPEVAR, AtmKind.UNION), + TYPEVAR_WILDCARD(AtmKind.TYPEVAR, AtmKind.WILDCARD), + + UNION_ARRAY(AtmKind.UNION, AtmKind.ARRAY), + UNION_DECLARED(AtmKind.UNION, AtmKind.DECLARED), + UNION_EXECUTABLE(AtmKind.UNION, AtmKind.EXECUTABLE), + UNION_INTERSECTION(AtmKind.UNION, AtmKind.INTERSECTION), + UNION_NONE(AtmKind.UNION, AtmKind.NONE), + UNION_NULL(AtmKind.UNION, AtmKind.NULL), + UNION_PRIMITIVE(AtmKind.UNION, AtmKind.PRIMITIVE), + UNION_TYPEVAR(AtmKind.UNION, AtmKind.TYPEVAR), + UNION_UNION(AtmKind.UNION, AtmKind.UNION), + UNION_WILDCARD(AtmKind.UNION, AtmKind.WILDCARD), + + WILDCARD_ARRAY(AtmKind.WILDCARD, AtmKind.ARRAY), + WILDCARD_DECLARED(AtmKind.WILDCARD, AtmKind.DECLARED), + WILDCARD_EXECUTABLE(AtmKind.WILDCARD, AtmKind.EXECUTABLE), + WILDCARD_INTERSECTION(AtmKind.WILDCARD, AtmKind.INTERSECTION), + WILDCARD_NONE(AtmKind.WILDCARD, AtmKind.NONE), + WILDCARD_NULL(AtmKind.WILDCARD, AtmKind.NULL), + WILDCARD_PRIMITIVE(AtmKind.WILDCARD, AtmKind.PRIMITIVE), + WILDCARD_TYPEVAR(AtmKind.WILDCARD, AtmKind.TYPEVAR), + WILDCARD_UNION(AtmKind.WILDCARD, AtmKind.UNION), + WILDCARD_WILDCARD(AtmKind.WILDCARD, AtmKind.WILDCARD); + + /** First AtmKind. */ + public final AtmKind type1Kind; + + /** Second AtmKind. */ + public final AtmKind type2Kind; + + /** + * Creates an AtmCombo. + * + * @param type1Kind first kind + * @param type2Kind second kind + */ + AtmCombo(AtmKind type1Kind, AtmKind type2Kind) { + this.type1Kind = type1Kind; + this.type2Kind = type2Kind; + } + + /** + * Used to locate AtmCombo pairs using AtmKinds as indices into a two-dimensional array. This + * ensures that all pairs are included. + */ + private static final AtmCombo[][] comboMap = + new AtmCombo[AtmKind.values().length][AtmKind.values().length]; + + static { + for (AtmCombo atmCombo : AtmCombo.values()) { + comboMap[atmCombo.type1Kind.ordinal()][atmCombo.type2Kind.ordinal()] = atmCombo; } + } + + /** + * Returns the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. {@literal + * (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE}. + * + * @return the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. {@literal + * (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE} + */ + public static AtmCombo valueOf(AtmKind type1, AtmKind type2) { + return comboMap[type1.ordinal()][type2.ordinal()]; + } + + /** + * Returns the AtmCombo corresponding to the pair of the classes for the given + * AnnotatedTypeMirrors. e.g. {@literal (AnnotatedPrimitiveType, AnnotatedDeclaredType) => + * AtmCombo.PRIMITIVE_DECLARED} + * + * @return the AtmCombo corresponding to the pair of the classes for the given + * AnnotatedTypeMirrors + */ + public static AtmCombo valueOf(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + return valueOf(AtmKind.valueOf(type1), AtmKind.valueOf(type2)); + } + + /** + * Call the visit method that corresponds to the AtmCombo that represents the classes of type1 and + * type2. That is, get the combo for type1 and type 2, use it to identify the correct visitor + * method, and call that method with type1, type2, and initialParam as arguments to the visit + * method. + * + * @param type1 first argument to the called visit method + * @param type2 second argument to the called visit method + * @param initialParam the parameter passed to the called visit method + * @param visitor the visitor that is visiting the given types + * @param the return type of the visitor's visit methods + * @param the parameter type of the visitor's visit methods + * @return the return value of the visit method called + */ + public static RETURN_TYPE accept( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + PARAM initialParam, + AtmComboVisitor visitor) { + AtmCombo combo = valueOf(type1, type2); + switch (combo) { + case ARRAY_ARRAY: + return visitor.visitArray_Array( + (AnnotatedArrayType) type1, (AnnotatedArrayType) type2, initialParam); + + case ARRAY_DECLARED: + return visitor.visitArray_Declared( + (AnnotatedArrayType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case ARRAY_EXECUTABLE: + return visitor.visitArray_Executable( + (AnnotatedArrayType) type1, (AnnotatedExecutableType) type2, initialParam); + + case ARRAY_INTERSECTION: + return visitor.visitArray_Intersection( + (AnnotatedArrayType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case ARRAY_NONE: + return visitor.visitArray_None( + (AnnotatedArrayType) type1, (AnnotatedNoType) type2, initialParam); + + case ARRAY_NULL: + return visitor.visitArray_Null( + (AnnotatedArrayType) type1, (AnnotatedNullType) type2, initialParam); + + case ARRAY_PRIMITIVE: + return visitor.visitArray_Primitive( + (AnnotatedArrayType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case ARRAY_TYPEVAR: + return visitor.visitArray_Typevar( + (AnnotatedArrayType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case ARRAY_UNION: + return visitor.visitArray_Union( + (AnnotatedArrayType) type1, (AnnotatedUnionType) type2, initialParam); + + case ARRAY_WILDCARD: + return visitor.visitArray_Wildcard( + (AnnotatedArrayType) type1, (AnnotatedWildcardType) type2, initialParam); + + case DECLARED_ARRAY: + return visitor.visitDeclared_Array( + (AnnotatedDeclaredType) type1, (AnnotatedArrayType) type2, initialParam); + + case DECLARED_DECLARED: + return visitor.visitDeclared_Declared( + (AnnotatedDeclaredType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case DECLARED_EXECUTABLE: + return visitor.visitDeclared_Executable( + (AnnotatedDeclaredType) type1, (AnnotatedExecutableType) type2, initialParam); + + case DECLARED_INTERSECTION: + return visitor.visitDeclared_Intersection( + (AnnotatedDeclaredType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case DECLARED_NONE: + return visitor.visitDeclared_None( + (AnnotatedDeclaredType) type1, (AnnotatedNoType) type2, initialParam); + + case DECLARED_NULL: + return visitor.visitDeclared_Null( + (AnnotatedDeclaredType) type1, (AnnotatedNullType) type2, initialParam); + + case DECLARED_PRIMITIVE: + return visitor.visitDeclared_Primitive( + (AnnotatedDeclaredType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case DECLARED_TYPEVAR: + return visitor.visitDeclared_Typevar( + (AnnotatedDeclaredType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case DECLARED_UNION: + return visitor.visitDeclared_Union( + (AnnotatedDeclaredType) type1, (AnnotatedUnionType) type2, initialParam); + + case DECLARED_WILDCARD: + return visitor.visitDeclared_Wildcard( + (AnnotatedDeclaredType) type1, (AnnotatedWildcardType) type2, initialParam); + + case EXECUTABLE_ARRAY: + return visitor.visitExecutable_Array( + (AnnotatedExecutableType) type1, (AnnotatedArrayType) type2, initialParam); + + case EXECUTABLE_DECLARED: + return visitor.visitExecutable_Declared( + (AnnotatedExecutableType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case EXECUTABLE_EXECUTABLE: + return visitor.visitExecutable_Executable( + (AnnotatedExecutableType) type1, (AnnotatedExecutableType) type2, initialParam); + + case EXECUTABLE_INTERSECTION: + return visitor.visitExecutable_Intersection( + (AnnotatedExecutableType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case EXECUTABLE_NONE: + return visitor.visitExecutable_None( + (AnnotatedExecutableType) type1, (AnnotatedNoType) type2, initialParam); - /** - * Used to locate AtmCombo pairs using AtmKinds as indices into a two-dimensional array. This - * ensures that all pairs are included. - */ - private static final AtmCombo[][] comboMap = - new AtmCombo[AtmKind.values().length][AtmKind.values().length]; - - static { - for (AtmCombo atmCombo : AtmCombo.values()) { - comboMap[atmCombo.type1Kind.ordinal()][atmCombo.type2Kind.ordinal()] = atmCombo; - } - } + case EXECUTABLE_NULL: + return visitor.visitExecutable_Null( + (AnnotatedExecutableType) type1, (AnnotatedNullType) type2, initialParam); - /** - * Returns the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. - * {@literal (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE}. - * - * @return the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. - * {@literal (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE} - */ - public static AtmCombo valueOf(AtmKind type1, AtmKind type2) { - return comboMap[type1.ordinal()][type2.ordinal()]; - } + case EXECUTABLE_PRIMITIVE: + return visitor.visitExecutable_Primitive( + (AnnotatedExecutableType) type1, (AnnotatedPrimitiveType) type2, initialParam); - /** - * Returns the AtmCombo corresponding to the pair of the classes for the given - * AnnotatedTypeMirrors. e.g. {@literal (AnnotatedPrimitiveType, AnnotatedDeclaredType) => - * AtmCombo.PRIMITIVE_DECLARED} - * - * @return the AtmCombo corresponding to the pair of the classes for the given - * AnnotatedTypeMirrors - */ - public static AtmCombo valueOf(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - return valueOf(AtmKind.valueOf(type1), AtmKind.valueOf(type2)); - } + case EXECUTABLE_TYPEVAR: + return visitor.visitExecutable_Typevar( + (AnnotatedExecutableType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case EXECUTABLE_UNION: + return visitor.visitExecutable_Union( + (AnnotatedExecutableType) type1, (AnnotatedUnionType) type2, initialParam); + + case EXECUTABLE_WILDCARD: + return visitor.visitExecutable_Wildcard( + (AnnotatedExecutableType) type1, (AnnotatedWildcardType) type2, initialParam); + + case INTERSECTION_ARRAY: + return visitor.visitIntersection_Array( + (AnnotatedIntersectionType) type1, (AnnotatedArrayType) type2, initialParam); + + case INTERSECTION_DECLARED: + return visitor.visitIntersection_Declared( + (AnnotatedIntersectionType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case INTERSECTION_EXECUTABLE: + return visitor.visitIntersection_Executable( + (AnnotatedIntersectionType) type1, (AnnotatedExecutableType) type2, initialParam); + + case INTERSECTION_INTERSECTION: + return visitor.visitIntersection_Intersection( + (AnnotatedIntersectionType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case INTERSECTION_NONE: + return visitor.visitIntersection_None( + (AnnotatedIntersectionType) type1, (AnnotatedNoType) type2, initialParam); + + case INTERSECTION_NULL: + return visitor.visitIntersection_Null( + (AnnotatedIntersectionType) type1, (AnnotatedNullType) type2, initialParam); + + case INTERSECTION_PRIMITIVE: + return visitor.visitIntersection_Primitive( + (AnnotatedIntersectionType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case INTERSECTION_TYPEVAR: + return visitor.visitIntersection_Typevar( + (AnnotatedIntersectionType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case INTERSECTION_UNION: + return visitor.visitIntersection_Union( + (AnnotatedIntersectionType) type1, (AnnotatedUnionType) type2, initialParam); + + case INTERSECTION_WILDCARD: + return visitor.visitIntersection_Wildcard( + (AnnotatedIntersectionType) type1, (AnnotatedWildcardType) type2, initialParam); + + case NONE_ARRAY: + return visitor.visitNone_Array( + (AnnotatedNoType) type1, (AnnotatedArrayType) type2, initialParam); + + case NONE_DECLARED: + return visitor.visitNone_Declared( + (AnnotatedNoType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case NONE_EXECUTABLE: + return visitor.visitNone_Executable( + (AnnotatedNoType) type1, (AnnotatedExecutableType) type2, initialParam); + + case NONE_INTERSECTION: + return visitor.visitNone_Intersection( + (AnnotatedNoType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case NONE_NONE: + return visitor.visitNone_None( + (AnnotatedNoType) type1, (AnnotatedNoType) type2, initialParam); + + case NONE_NULL: + return visitor.visitNone_Null( + (AnnotatedNoType) type1, (AnnotatedNullType) type2, initialParam); + + case NONE_PRIMITIVE: + return visitor.visitNone_Primitive( + (AnnotatedNoType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case NONE_UNION: + return visitor.visitNone_Union( + (AnnotatedNoType) type1, (AnnotatedUnionType) type2, initialParam); + + case NONE_WILDCARD: + return visitor.visitNone_Wildcard( + (AnnotatedNoType) type1, (AnnotatedWildcardType) type2, initialParam); + + case NULL_ARRAY: + return visitor.visitNull_Array( + (AnnotatedNullType) type1, (AnnotatedArrayType) type2, initialParam); + + case NULL_DECLARED: + return visitor.visitNull_Declared( + (AnnotatedNullType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case NULL_EXECUTABLE: + return visitor.visitNull_Executable( + (AnnotatedNullType) type1, (AnnotatedExecutableType) type2, initialParam); + + case NULL_INTERSECTION: + return visitor.visitNull_Intersection( + (AnnotatedNullType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case NULL_NONE: + return visitor.visitNull_None( + (AnnotatedNullType) type1, (AnnotatedNoType) type2, initialParam); + + case NULL_NULL: + return visitor.visitNull_Null( + (AnnotatedNullType) type1, (AnnotatedNullType) type2, initialParam); + + case NULL_PRIMITIVE: + return visitor.visitNull_Primitive( + (AnnotatedNullType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case NULL_TYPEVAR: + return visitor.visitNull_Typevar( + (AnnotatedNullType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case NULL_UNION: + return visitor.visitNull_Union( + (AnnotatedNullType) type1, (AnnotatedUnionType) type2, initialParam); + + case NULL_WILDCARD: + return visitor.visitNull_Wildcard( + (AnnotatedNullType) type1, (AnnotatedWildcardType) type2, initialParam); + + case PRIMITIVE_ARRAY: + return visitor.visitPrimitive_Array( + (AnnotatedPrimitiveType) type1, (AnnotatedArrayType) type2, initialParam); + + case PRIMITIVE_DECLARED: + return visitor.visitPrimitive_Declared( + (AnnotatedPrimitiveType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case PRIMITIVE_EXECUTABLE: + return visitor.visitPrimitive_Executable( + (AnnotatedPrimitiveType) type1, (AnnotatedExecutableType) type2, initialParam); + + case PRIMITIVE_INTERSECTION: + return visitor.visitPrimitive_Intersection( + (AnnotatedPrimitiveType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case PRIMITIVE_NONE: + return visitor.visitPrimitive_None( + (AnnotatedPrimitiveType) type1, (AnnotatedNoType) type2, initialParam); + + case PRIMITIVE_NULL: + return visitor.visitPrimitive_Null( + (AnnotatedPrimitiveType) type1, (AnnotatedNullType) type2, initialParam); + + case PRIMITIVE_PRIMITIVE: + return visitor.visitPrimitive_Primitive( + (AnnotatedPrimitiveType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case PRIMITIVE_TYPEVAR: + return visitor.visitPrimitive_Typevar( + (AnnotatedPrimitiveType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case PRIMITIVE_UNION: + return visitor.visitPrimitive_Union( + (AnnotatedPrimitiveType) type1, (AnnotatedUnionType) type2, initialParam); + + case PRIMITIVE_WILDCARD: + return visitor.visitPrimitive_Wildcard( + (AnnotatedPrimitiveType) type1, (AnnotatedWildcardType) type2, initialParam); + + case UNION_ARRAY: + return visitor.visitUnion_Array( + (AnnotatedUnionType) type1, (AnnotatedArrayType) type2, initialParam); + + case UNION_DECLARED: + return visitor.visitUnion_Declared( + (AnnotatedUnionType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case UNION_EXECUTABLE: + return visitor.visitUnion_Executable( + (AnnotatedUnionType) type1, (AnnotatedExecutableType) type2, initialParam); + + case UNION_INTERSECTION: + return visitor.visitUnion_Intersection( + (AnnotatedUnionType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case UNION_NONE: + return visitor.visitUnion_None( + (AnnotatedUnionType) type1, (AnnotatedNoType) type2, initialParam); + + case UNION_NULL: + return visitor.visitUnion_Null( + (AnnotatedUnionType) type1, (AnnotatedNullType) type2, initialParam); + + case UNION_PRIMITIVE: + return visitor.visitUnion_Primitive( + (AnnotatedUnionType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case UNION_TYPEVAR: + return visitor.visitUnion_Typevar( + (AnnotatedUnionType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case UNION_UNION: + return visitor.visitUnion_Union( + (AnnotatedUnionType) type1, (AnnotatedUnionType) type2, initialParam); + + case UNION_WILDCARD: + return visitor.visitUnion_Wildcard( + (AnnotatedUnionType) type1, (AnnotatedWildcardType) type2, initialParam); + + case TYPEVAR_ARRAY: + return visitor.visitTypevar_Array( + (AnnotatedTypeVariable) type1, (AnnotatedArrayType) type2, initialParam); + + case TYPEVAR_DECLARED: + return visitor.visitTypevar_Declared( + (AnnotatedTypeVariable) type1, (AnnotatedDeclaredType) type2, initialParam); + + case TYPEVAR_EXECUTABLE: + return visitor.visitTypevar_Executable( + (AnnotatedTypeVariable) type1, (AnnotatedExecutableType) type2, initialParam); + + case TYPEVAR_INTERSECTION: + return visitor.visitTypevar_Intersection( + (AnnotatedTypeVariable) type1, (AnnotatedIntersectionType) type2, initialParam); + + case TYPEVAR_NONE: + return visitor.visitTypevar_None( + (AnnotatedTypeVariable) type1, (AnnotatedNoType) type2, initialParam); + + case TYPEVAR_NULL: + return visitor.visitTypevar_Null( + (AnnotatedTypeVariable) type1, (AnnotatedNullType) type2, initialParam); + + case TYPEVAR_PRIMITIVE: + return visitor.visitTypevar_Primitive( + (AnnotatedTypeVariable) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case TYPEVAR_TYPEVAR: + return visitor.visitTypevar_Typevar( + (AnnotatedTypeVariable) type1, (AnnotatedTypeVariable) type2, initialParam); + + case TYPEVAR_UNION: + return visitor.visitTypevar_Union( + (AnnotatedTypeVariable) type1, (AnnotatedUnionType) type2, initialParam); + + case TYPEVAR_WILDCARD: + return visitor.visitTypevar_Wildcard( + (AnnotatedTypeVariable) type1, (AnnotatedWildcardType) type2, initialParam); + + case WILDCARD_ARRAY: + return visitor.visitWildcard_Array( + (AnnotatedWildcardType) type1, (AnnotatedArrayType) type2, initialParam); + + case WILDCARD_DECLARED: + return visitor.visitWildcard_Declared( + (AnnotatedWildcardType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case WILDCARD_EXECUTABLE: + return visitor.visitWildcard_Executable( + (AnnotatedWildcardType) type1, (AnnotatedExecutableType) type2, initialParam); + + case WILDCARD_INTERSECTION: + return visitor.visitWildcard_Intersection( + (AnnotatedWildcardType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case WILDCARD_NONE: + return visitor.visitWildcard_None( + (AnnotatedWildcardType) type1, (AnnotatedNoType) type2, initialParam); + + case WILDCARD_NULL: + return visitor.visitWildcard_Null( + (AnnotatedWildcardType) type1, (AnnotatedNullType) type2, initialParam); + + case WILDCARD_PRIMITIVE: + return visitor.visitWildcard_Primitive( + (AnnotatedWildcardType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case WILDCARD_TYPEVAR: + return visitor.visitWildcard_Typevar( + (AnnotatedWildcardType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case WILDCARD_UNION: + return visitor.visitWildcard_Union( + (AnnotatedWildcardType) type1, (AnnotatedUnionType) type2, initialParam); + + case WILDCARD_WILDCARD: + return visitor.visitWildcard_Wildcard( + (AnnotatedWildcardType) type1, (AnnotatedWildcardType) type2, initialParam); - /** - * Call the visit method that corresponds to the AtmCombo that represents the classes of type1 - * and type2. That is, get the combo for type1 and type 2, use it to identify the correct - * visitor method, and call that method with type1, type2, and initialParam as arguments to the - * visit method. - * - * @param type1 first argument to the called visit method - * @param type2 second argument to the called visit method - * @param initialParam the parameter passed to the called visit method - * @param visitor the visitor that is visiting the given types - * @param the return type of the visitor's visit methods - * @param the parameter type of the visitor's visit methods - * @return the return value of the visit method called - */ - public static RETURN_TYPE accept( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - PARAM initialParam, - AtmComboVisitor visitor) { - AtmCombo combo = valueOf(type1, type2); - switch (combo) { - case ARRAY_ARRAY: - return visitor.visitArray_Array( - (AnnotatedArrayType) type1, (AnnotatedArrayType) type2, initialParam); - - case ARRAY_DECLARED: - return visitor.visitArray_Declared( - (AnnotatedArrayType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case ARRAY_EXECUTABLE: - return visitor.visitArray_Executable( - (AnnotatedArrayType) type1, (AnnotatedExecutableType) type2, initialParam); - - case ARRAY_INTERSECTION: - return visitor.visitArray_Intersection( - (AnnotatedArrayType) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case ARRAY_NONE: - return visitor.visitArray_None( - (AnnotatedArrayType) type1, (AnnotatedNoType) type2, initialParam); - - case ARRAY_NULL: - return visitor.visitArray_Null( - (AnnotatedArrayType) type1, (AnnotatedNullType) type2, initialParam); - - case ARRAY_PRIMITIVE: - return visitor.visitArray_Primitive( - (AnnotatedArrayType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case ARRAY_TYPEVAR: - return visitor.visitArray_Typevar( - (AnnotatedArrayType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case ARRAY_UNION: - return visitor.visitArray_Union( - (AnnotatedArrayType) type1, (AnnotatedUnionType) type2, initialParam); - - case ARRAY_WILDCARD: - return visitor.visitArray_Wildcard( - (AnnotatedArrayType) type1, (AnnotatedWildcardType) type2, initialParam); - - case DECLARED_ARRAY: - return visitor.visitDeclared_Array( - (AnnotatedDeclaredType) type1, (AnnotatedArrayType) type2, initialParam); - - case DECLARED_DECLARED: - return visitor.visitDeclared_Declared( - (AnnotatedDeclaredType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case DECLARED_EXECUTABLE: - return visitor.visitDeclared_Executable( - (AnnotatedDeclaredType) type1, - (AnnotatedExecutableType) type2, - initialParam); - - case DECLARED_INTERSECTION: - return visitor.visitDeclared_Intersection( - (AnnotatedDeclaredType) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case DECLARED_NONE: - return visitor.visitDeclared_None( - (AnnotatedDeclaredType) type1, (AnnotatedNoType) type2, initialParam); - - case DECLARED_NULL: - return visitor.visitDeclared_Null( - (AnnotatedDeclaredType) type1, (AnnotatedNullType) type2, initialParam); - - case DECLARED_PRIMITIVE: - return visitor.visitDeclared_Primitive( - (AnnotatedDeclaredType) type1, - (AnnotatedPrimitiveType) type2, - initialParam); - - case DECLARED_TYPEVAR: - return visitor.visitDeclared_Typevar( - (AnnotatedDeclaredType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case DECLARED_UNION: - return visitor.visitDeclared_Union( - (AnnotatedDeclaredType) type1, (AnnotatedUnionType) type2, initialParam); - - case DECLARED_WILDCARD: - return visitor.visitDeclared_Wildcard( - (AnnotatedDeclaredType) type1, (AnnotatedWildcardType) type2, initialParam); - - case EXECUTABLE_ARRAY: - return visitor.visitExecutable_Array( - (AnnotatedExecutableType) type1, (AnnotatedArrayType) type2, initialParam); - - case EXECUTABLE_DECLARED: - return visitor.visitExecutable_Declared( - (AnnotatedExecutableType) type1, - (AnnotatedDeclaredType) type2, - initialParam); - - case EXECUTABLE_EXECUTABLE: - return visitor.visitExecutable_Executable( - (AnnotatedExecutableType) type1, - (AnnotatedExecutableType) type2, - initialParam); - - case EXECUTABLE_INTERSECTION: - return visitor.visitExecutable_Intersection( - (AnnotatedExecutableType) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case EXECUTABLE_NONE: - return visitor.visitExecutable_None( - (AnnotatedExecutableType) type1, (AnnotatedNoType) type2, initialParam); - - case EXECUTABLE_NULL: - return visitor.visitExecutable_Null( - (AnnotatedExecutableType) type1, (AnnotatedNullType) type2, initialParam); - - case EXECUTABLE_PRIMITIVE: - return visitor.visitExecutable_Primitive( - (AnnotatedExecutableType) type1, - (AnnotatedPrimitiveType) type2, - initialParam); - - case EXECUTABLE_TYPEVAR: - return visitor.visitExecutable_Typevar( - (AnnotatedExecutableType) type1, - (AnnotatedTypeVariable) type2, - initialParam); - - case EXECUTABLE_UNION: - return visitor.visitExecutable_Union( - (AnnotatedExecutableType) type1, (AnnotatedUnionType) type2, initialParam); - - case EXECUTABLE_WILDCARD: - return visitor.visitExecutable_Wildcard( - (AnnotatedExecutableType) type1, - (AnnotatedWildcardType) type2, - initialParam); - - case INTERSECTION_ARRAY: - return visitor.visitIntersection_Array( - (AnnotatedIntersectionType) type1, - (AnnotatedArrayType) type2, - initialParam); - - case INTERSECTION_DECLARED: - return visitor.visitIntersection_Declared( - (AnnotatedIntersectionType) type1, - (AnnotatedDeclaredType) type2, - initialParam); - - case INTERSECTION_EXECUTABLE: - return visitor.visitIntersection_Executable( - (AnnotatedIntersectionType) type1, - (AnnotatedExecutableType) type2, - initialParam); - - case INTERSECTION_INTERSECTION: - return visitor.visitIntersection_Intersection( - (AnnotatedIntersectionType) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case INTERSECTION_NONE: - return visitor.visitIntersection_None( - (AnnotatedIntersectionType) type1, (AnnotatedNoType) type2, initialParam); - - case INTERSECTION_NULL: - return visitor.visitIntersection_Null( - (AnnotatedIntersectionType) type1, (AnnotatedNullType) type2, initialParam); - - case INTERSECTION_PRIMITIVE: - return visitor.visitIntersection_Primitive( - (AnnotatedIntersectionType) type1, - (AnnotatedPrimitiveType) type2, - initialParam); - - case INTERSECTION_TYPEVAR: - return visitor.visitIntersection_Typevar( - (AnnotatedIntersectionType) type1, - (AnnotatedTypeVariable) type2, - initialParam); - - case INTERSECTION_UNION: - return visitor.visitIntersection_Union( - (AnnotatedIntersectionType) type1, - (AnnotatedUnionType) type2, - initialParam); - - case INTERSECTION_WILDCARD: - return visitor.visitIntersection_Wildcard( - (AnnotatedIntersectionType) type1, - (AnnotatedWildcardType) type2, - initialParam); - - case NONE_ARRAY: - return visitor.visitNone_Array( - (AnnotatedNoType) type1, (AnnotatedArrayType) type2, initialParam); - - case NONE_DECLARED: - return visitor.visitNone_Declared( - (AnnotatedNoType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case NONE_EXECUTABLE: - return visitor.visitNone_Executable( - (AnnotatedNoType) type1, (AnnotatedExecutableType) type2, initialParam); - - case NONE_INTERSECTION: - return visitor.visitNone_Intersection( - (AnnotatedNoType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case NONE_NONE: - return visitor.visitNone_None( - (AnnotatedNoType) type1, (AnnotatedNoType) type2, initialParam); - - case NONE_NULL: - return visitor.visitNone_Null( - (AnnotatedNoType) type1, (AnnotatedNullType) type2, initialParam); - - case NONE_PRIMITIVE: - return visitor.visitNone_Primitive( - (AnnotatedNoType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case NONE_UNION: - return visitor.visitNone_Union( - (AnnotatedNoType) type1, (AnnotatedUnionType) type2, initialParam); - - case NONE_WILDCARD: - return visitor.visitNone_Wildcard( - (AnnotatedNoType) type1, (AnnotatedWildcardType) type2, initialParam); - - case NULL_ARRAY: - return visitor.visitNull_Array( - (AnnotatedNullType) type1, (AnnotatedArrayType) type2, initialParam); - - case NULL_DECLARED: - return visitor.visitNull_Declared( - (AnnotatedNullType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case NULL_EXECUTABLE: - return visitor.visitNull_Executable( - (AnnotatedNullType) type1, (AnnotatedExecutableType) type2, initialParam); - - case NULL_INTERSECTION: - return visitor.visitNull_Intersection( - (AnnotatedNullType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case NULL_NONE: - return visitor.visitNull_None( - (AnnotatedNullType) type1, (AnnotatedNoType) type2, initialParam); - - case NULL_NULL: - return visitor.visitNull_Null( - (AnnotatedNullType) type1, (AnnotatedNullType) type2, initialParam); - - case NULL_PRIMITIVE: - return visitor.visitNull_Primitive( - (AnnotatedNullType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case NULL_TYPEVAR: - return visitor.visitNull_Typevar( - (AnnotatedNullType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case NULL_UNION: - return visitor.visitNull_Union( - (AnnotatedNullType) type1, (AnnotatedUnionType) type2, initialParam); - - case NULL_WILDCARD: - return visitor.visitNull_Wildcard( - (AnnotatedNullType) type1, (AnnotatedWildcardType) type2, initialParam); - - case PRIMITIVE_ARRAY: - return visitor.visitPrimitive_Array( - (AnnotatedPrimitiveType) type1, (AnnotatedArrayType) type2, initialParam); - - case PRIMITIVE_DECLARED: - return visitor.visitPrimitive_Declared( - (AnnotatedPrimitiveType) type1, - (AnnotatedDeclaredType) type2, - initialParam); - - case PRIMITIVE_EXECUTABLE: - return visitor.visitPrimitive_Executable( - (AnnotatedPrimitiveType) type1, - (AnnotatedExecutableType) type2, - initialParam); - - case PRIMITIVE_INTERSECTION: - return visitor.visitPrimitive_Intersection( - (AnnotatedPrimitiveType) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case PRIMITIVE_NONE: - return visitor.visitPrimitive_None( - (AnnotatedPrimitiveType) type1, (AnnotatedNoType) type2, initialParam); - - case PRIMITIVE_NULL: - return visitor.visitPrimitive_Null( - (AnnotatedPrimitiveType) type1, (AnnotatedNullType) type2, initialParam); - - case PRIMITIVE_PRIMITIVE: - return visitor.visitPrimitive_Primitive( - (AnnotatedPrimitiveType) type1, - (AnnotatedPrimitiveType) type2, - initialParam); - - case PRIMITIVE_TYPEVAR: - return visitor.visitPrimitive_Typevar( - (AnnotatedPrimitiveType) type1, - (AnnotatedTypeVariable) type2, - initialParam); - - case PRIMITIVE_UNION: - return visitor.visitPrimitive_Union( - (AnnotatedPrimitiveType) type1, (AnnotatedUnionType) type2, initialParam); - - case PRIMITIVE_WILDCARD: - return visitor.visitPrimitive_Wildcard( - (AnnotatedPrimitiveType) type1, - (AnnotatedWildcardType) type2, - initialParam); - - case UNION_ARRAY: - return visitor.visitUnion_Array( - (AnnotatedUnionType) type1, (AnnotatedArrayType) type2, initialParam); - - case UNION_DECLARED: - return visitor.visitUnion_Declared( - (AnnotatedUnionType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case UNION_EXECUTABLE: - return visitor.visitUnion_Executable( - (AnnotatedUnionType) type1, (AnnotatedExecutableType) type2, initialParam); - - case UNION_INTERSECTION: - return visitor.visitUnion_Intersection( - (AnnotatedUnionType) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case UNION_NONE: - return visitor.visitUnion_None( - (AnnotatedUnionType) type1, (AnnotatedNoType) type2, initialParam); - - case UNION_NULL: - return visitor.visitUnion_Null( - (AnnotatedUnionType) type1, (AnnotatedNullType) type2, initialParam); - - case UNION_PRIMITIVE: - return visitor.visitUnion_Primitive( - (AnnotatedUnionType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case UNION_TYPEVAR: - return visitor.visitUnion_Typevar( - (AnnotatedUnionType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case UNION_UNION: - return visitor.visitUnion_Union( - (AnnotatedUnionType) type1, (AnnotatedUnionType) type2, initialParam); - - case UNION_WILDCARD: - return visitor.visitUnion_Wildcard( - (AnnotatedUnionType) type1, (AnnotatedWildcardType) type2, initialParam); - - case TYPEVAR_ARRAY: - return visitor.visitTypevar_Array( - (AnnotatedTypeVariable) type1, (AnnotatedArrayType) type2, initialParam); - - case TYPEVAR_DECLARED: - return visitor.visitTypevar_Declared( - (AnnotatedTypeVariable) type1, (AnnotatedDeclaredType) type2, initialParam); - - case TYPEVAR_EXECUTABLE: - return visitor.visitTypevar_Executable( - (AnnotatedTypeVariable) type1, - (AnnotatedExecutableType) type2, - initialParam); - - case TYPEVAR_INTERSECTION: - return visitor.visitTypevar_Intersection( - (AnnotatedTypeVariable) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case TYPEVAR_NONE: - return visitor.visitTypevar_None( - (AnnotatedTypeVariable) type1, (AnnotatedNoType) type2, initialParam); - - case TYPEVAR_NULL: - return visitor.visitTypevar_Null( - (AnnotatedTypeVariable) type1, (AnnotatedNullType) type2, initialParam); - - case TYPEVAR_PRIMITIVE: - return visitor.visitTypevar_Primitive( - (AnnotatedTypeVariable) type1, - (AnnotatedPrimitiveType) type2, - initialParam); - - case TYPEVAR_TYPEVAR: - return visitor.visitTypevar_Typevar( - (AnnotatedTypeVariable) type1, (AnnotatedTypeVariable) type2, initialParam); - - case TYPEVAR_UNION: - return visitor.visitTypevar_Union( - (AnnotatedTypeVariable) type1, (AnnotatedUnionType) type2, initialParam); - - case TYPEVAR_WILDCARD: - return visitor.visitTypevar_Wildcard( - (AnnotatedTypeVariable) type1, (AnnotatedWildcardType) type2, initialParam); - - case WILDCARD_ARRAY: - return visitor.visitWildcard_Array( - (AnnotatedWildcardType) type1, (AnnotatedArrayType) type2, initialParam); - - case WILDCARD_DECLARED: - return visitor.visitWildcard_Declared( - (AnnotatedWildcardType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case WILDCARD_EXECUTABLE: - return visitor.visitWildcard_Executable( - (AnnotatedWildcardType) type1, - (AnnotatedExecutableType) type2, - initialParam); - - case WILDCARD_INTERSECTION: - return visitor.visitWildcard_Intersection( - (AnnotatedWildcardType) type1, - (AnnotatedIntersectionType) type2, - initialParam); - - case WILDCARD_NONE: - return visitor.visitWildcard_None( - (AnnotatedWildcardType) type1, (AnnotatedNoType) type2, initialParam); - - case WILDCARD_NULL: - return visitor.visitWildcard_Null( - (AnnotatedWildcardType) type1, (AnnotatedNullType) type2, initialParam); - - case WILDCARD_PRIMITIVE: - return visitor.visitWildcard_Primitive( - (AnnotatedWildcardType) type1, - (AnnotatedPrimitiveType) type2, - initialParam); - - case WILDCARD_TYPEVAR: - return visitor.visitWildcard_Typevar( - (AnnotatedWildcardType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case WILDCARD_UNION: - return visitor.visitWildcard_Union( - (AnnotatedWildcardType) type1, (AnnotatedUnionType) type2, initialParam); - - case WILDCARD_WILDCARD: - return visitor.visitWildcard_Wildcard( - (AnnotatedWildcardType) type1, (AnnotatedWildcardType) type2, initialParam); - - default: - // Reaching this point indicates that there is an AtmCombo missing - throw new BugInCF("Unhandled AtmCombo ( " + combo + " ) "); - } + default: + // Reaching this point indicates that there is an AtmCombo missing + throw new BugInCF("Unhandled AtmCombo ( " + combo + " ) "); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java index f74b7ddcf9f..e7aa6e7ada4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.util; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -17,15 +24,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; - /** * Helper class to compute the least upper bound of two AnnotatedTypeMirrors. * @@ -34,418 +32,411 @@ */ class AtmLubVisitor extends AbstractAtmComboVisitor { - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy. */ - private final QualifierHierarchy qualHierarchy; - - /** - * List of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been - * visited. Call {@link #visited(AnnotatedTypeMirror)} to check if the type have been visited, - * so that reference equality is used rather than {@link #equals(Object)}. - */ - private final List visited = new ArrayList<>(); - - AtmLubVisitor(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** The qualifier hierarchy. */ + private final QualifierHierarchy qualHierarchy; + + /** + * List of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been visited. + * Call {@link #visited(AnnotatedTypeMirror)} to check if the type have been visited, so that + * reference equality is used rather than {@link #equals(Object)}. + */ + private final List visited = new ArrayList<>(); + + AtmLubVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + } + + /** + * Returns an ATM that is the least upper bound of type1 and type2 and whose Java type is + * lubJavaType. lubJavaType must be a super type or convertible to the Java types of type1 and + * type2. + */ + AnnotatedTypeMirror lub( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, TypeMirror lubJavaType) { + AnnotatedTypeMirror lub = AnnotatedTypeMirror.createType(lubJavaType, atypeFactory, false); + + if (type1.getKind() == TypeKind.NULL) { + return lubWithNull((AnnotatedNullType) type1, type2, lub); } - - /** - * Returns an ATM that is the least upper bound of type1 and type2 and whose Java type is - * lubJavaType. lubJavaType must be a super type or convertible to the Java types of type1 and - * type2. - */ - AnnotatedTypeMirror lub( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, TypeMirror lubJavaType) { - AnnotatedTypeMirror lub = AnnotatedTypeMirror.createType(lubJavaType, atypeFactory, false); - - if (type1.getKind() == TypeKind.NULL) { - return lubWithNull((AnnotatedNullType) type1, type2, lub); - } - if (type2.getKind() == TypeKind.NULL) { - return lubWithNull((AnnotatedNullType) type2, type1, lub); - } - - AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); - AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); - - visit(type1AsLub, type2AsLub, lub); - visited.clear(); - return lub; + if (type2.getKind() == TypeKind.NULL) { + return lubWithNull((AnnotatedNullType) type2, type1, lub); } - /** - * Lub a type with the nulltype. - * - * @param nullType an annotated null type - * @param otherType the other type to lub - * @param lub a type mirror that will be copied, side-effected, and returned - * @return the lub - */ - private AnnotatedTypeMirror lubWithNull( - AnnotatedNullType nullType, AnnotatedTypeMirror otherType, AnnotatedTypeMirror lub) { - TypeMirror nullTM = nullType.getUnderlyingType(); - AnnotatedTypeMirror otherAsLub; - if (otherType.getKind() == TypeKind.NULL) { - otherAsLub = otherType.deepCopy(); - } else { - otherAsLub = AnnotatedTypes.asSuper(atypeFactory, otherType, lub); - } - TypeMirror otherTM = otherAsLub.getUnderlyingType(); - - lub = otherAsLub.deepCopy(); - - if (otherAsLub.getKind() != TypeKind.TYPEVAR && otherAsLub.getKind() != TypeKind.WILDCARD) { - for (AnnotationMirror nullAnno : nullType.getAnnotations()) { - AnnotationMirror otherAnno = otherAsLub.getAnnotationInHierarchy(nullAnno); - AnnotationMirror lubAnno = - qualHierarchy.leastUpperBoundShallow(nullAnno, nullTM, otherAnno, otherTM); - lub.replaceAnnotation(lubAnno); - } - return lub; - } - - // LUB(@N null, T), where T's upper bound is @U and T's lower bound is @L - // if @L <: @U <: @N then LUB(@N null, T) = @N T - // if @L <: @N <:@U && @N != @L then LUB(@N null, T) = @U T - // if @N <: @L <: @U then LUB(@N null, T) = T - AnnotationMirrorSet lowerBounds = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, otherAsLub); - for (AnnotationMirror lowerBound : lowerBounds) { - AnnotationMirror nullAnno = nullType.getAnnotationInHierarchy(lowerBound); - AnnotationMirror upperBound = otherAsLub.getEffectiveAnnotationInHierarchy(lowerBound); - if (qualHierarchy.isSubtypeShallow(upperBound, otherTM, nullAnno, nullTM)) { - // @L <: @U <: @N - lub.replaceAnnotation(nullAnno); - } else if (qualHierarchy.isSubtypeShallow(lowerBound, otherTM, nullAnno, nullTM) - && !qualHierarchy.isSubtypeShallow(nullAnno, nullTM, lowerBound, otherTM)) { - // @L <: @N <:@U && @N != @L - lub.replaceAnnotation(upperBound); - } // else @N <: @L <: @U - } - return lub; + AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); + AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); + + visit(type1AsLub, type2AsLub, lub); + visited.clear(); + return lub; + } + + /** + * Lub a type with the nulltype. + * + * @param nullType an annotated null type + * @param otherType the other type to lub + * @param lub a type mirror that will be copied, side-effected, and returned + * @return the lub + */ + private AnnotatedTypeMirror lubWithNull( + AnnotatedNullType nullType, AnnotatedTypeMirror otherType, AnnotatedTypeMirror lub) { + TypeMirror nullTM = nullType.getUnderlyingType(); + AnnotatedTypeMirror otherAsLub; + if (otherType.getKind() == TypeKind.NULL) { + otherAsLub = otherType.deepCopy(); + } else { + otherAsLub = AnnotatedTypes.asSuper(atypeFactory, otherType, lub); } - - /** - * Replaces the primary annotations of lub with the lub of the primary annotations of type1 and - * type2. - */ - private void lubPrimaryAnnotations( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - Set lubSet; - if (type1.getAnnotations().isEmpty()) { - lubSet = type2.getAnnotations(); - } else if (type2.getAnnotations().isEmpty()) { - lubSet = type1.getAnnotations(); - } else { - lubSet = - qualHierarchy.leastUpperBoundsShallow( - type1.getAnnotations(), - type1.getUnderlyingType(), - type2.getAnnotations(), - type2.getUnderlyingType()); - } - lub.replaceAnnotations(lubSet); + TypeMirror otherTM = otherAsLub.getUnderlyingType(); + + lub = otherAsLub.deepCopy(); + + if (otherAsLub.getKind() != TypeKind.TYPEVAR && otherAsLub.getKind() != TypeKind.WILDCARD) { + for (AnnotationMirror nullAnno : nullType.getAnnotations()) { + AnnotationMirror otherAnno = otherAsLub.getAnnotationInHierarchy(nullAnno); + AnnotationMirror lubAnno = + qualHierarchy.leastUpperBoundShallow(nullAnno, nullTM, otherAnno, otherTM); + lub.replaceAnnotation(lubAnno); + } + return lub; } - /** - * Casts lub to the type of type, or issues an error if type and lub are not the same kind. - * - * @param the type to cast to - * @param type a values of the type to cast to - * @param lub the value to cast to {@code T} - * @return {@code lub}, casted to {@code T} - */ - private T castLub(T type, AnnotatedTypeMirror lub) { - if (type.getKind() != lub.getKind()) { - throw new BugInCF( - "AtmLubVisitor: unexpected type. Found: %s Required %s", - lub.getKind(), type.getKind()); - } - @SuppressWarnings("unchecked") - T castedLub = (T) lub; - return castedLub; + // LUB(@N null, T), where T's upper bound is @U and T's lower bound is @L + // if @L <: @U <: @N then LUB(@N null, T) = @N T + // if @L <: @N <:@U && @N != @L then LUB(@N null, T) = @U T + // if @N <: @L <: @U then LUB(@N null, T) = T + AnnotationMirrorSet lowerBounds = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, otherAsLub); + for (AnnotationMirror lowerBound : lowerBounds) { + AnnotationMirror nullAnno = nullType.getAnnotationInHierarchy(lowerBound); + AnnotationMirror upperBound = otherAsLub.getEffectiveAnnotationInHierarchy(lowerBound); + if (qualHierarchy.isSubtypeShallow(upperBound, otherTM, nullAnno, nullTM)) { + // @L <: @U <: @N + lub.replaceAnnotation(nullAnno); + } else if (qualHierarchy.isSubtypeShallow(lowerBound, otherTM, nullAnno, nullTM) + && !qualHierarchy.isSubtypeShallow(nullAnno, nullTM, lowerBound, otherTM)) { + // @L <: @N <:@U && @N != @L + lub.replaceAnnotation(upperBound); + } // else @N <: @L <: @U } - - @Override - public Void visitNull_Null( - AnnotatedNullType type1, AnnotatedNullType type2, AnnotatedTypeMirror lub) { - // Called to issue warning - castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); - return null; + return lub; + } + + /** + * Replaces the primary annotations of lub with the lub of the primary annotations of type1 and + * type2. + */ + private void lubPrimaryAnnotations( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + Set lubSet; + if (type1.getAnnotations().isEmpty()) { + lubSet = type2.getAnnotations(); + } else if (type2.getAnnotations().isEmpty()) { + lubSet = type1.getAnnotations(); + } else { + lubSet = + qualHierarchy.leastUpperBoundsShallow( + type1.getAnnotations(), + type1.getUnderlyingType(), + type2.getAnnotations(), + type2.getUnderlyingType()); } - - @Override - public Void visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, AnnotatedTypeMirror lub) { - AnnotatedArrayType lubArray = castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lubArray); - - visit(type1.getComponentType(), type2.getComponentType(), lubArray.getComponentType()); - return null; + lub.replaceAnnotations(lubSet); + } + + /** + * Casts lub to the type of type, or issues an error if type and lub are not the same kind. + * + * @param the type to cast to + * @param type a values of the type to cast to + * @param lub the value to cast to {@code T} + * @return {@code lub}, casted to {@code T} + */ + private T castLub(T type, AnnotatedTypeMirror lub) { + if (type.getKind() != lub.getKind()) { + throw new BugInCF( + "AtmLubVisitor: unexpected type. Found: %s Required %s", lub.getKind(), type.getKind()); } - - @Override - public Void visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, AnnotatedTypeMirror lub) { - AnnotatedDeclaredType castedLub = castLub(type1, lub); - - lubPrimaryAnnotations(type1, type2, lub); - - if (lub.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType enclosingLub = ((AnnotatedDeclaredType) lub).getEnclosingType(); - AnnotatedDeclaredType enclosing1 = type1.getEnclosingType(); - AnnotatedDeclaredType enclosing2 = type2.getEnclosingType(); - if (enclosingLub != null && enclosing1 != null && enclosing2 != null) { - visitDeclared_Declared(enclosing1, enclosing2, enclosingLub); - } - } - - for (int i = 0; i < type1.getTypeArguments().size(); i++) { - AnnotatedTypeMirror type1TypeArg = type1.getTypeArguments().get(i); - AnnotatedTypeMirror type2TypeArg = type2.getTypeArguments().get(i); - AnnotatedTypeMirror lubTypeArg = castedLub.getTypeArguments().get(i); - lubTypeArgument(type1TypeArg, type2TypeArg, lubTypeArg); - } - return null; + @SuppressWarnings("unchecked") + T castedLub = (T) lub; + return castedLub; + } + + @Override + public Void visitNull_Null( + AnnotatedNullType type1, AnnotatedNullType type2, AnnotatedTypeMirror lub) { + // Called to issue warning + castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); + return null; + } + + @Override + public Void visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, AnnotatedTypeMirror lub) { + AnnotatedArrayType lubArray = castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lubArray); + + visit(type1.getComponentType(), type2.getComponentType(), lubArray.getComponentType()); + return null; + } + + @Override + public Void visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, AnnotatedTypeMirror lub) { + AnnotatedDeclaredType castedLub = castLub(type1, lub); + + lubPrimaryAnnotations(type1, type2, lub); + + if (lub.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType enclosingLub = ((AnnotatedDeclaredType) lub).getEnclosingType(); + AnnotatedDeclaredType enclosing1 = type1.getEnclosingType(); + AnnotatedDeclaredType enclosing2 = type2.getEnclosingType(); + if (enclosingLub != null && enclosing1 != null && enclosing2 != null) { + visitDeclared_Declared(enclosing1, enclosing2, enclosingLub); + } } - /** - * Annotate the least upper bound of two type arguments. - * - * @param type1 the first type argument - * @param type2 the second type argument - * @param lub the least upper bound - */ - private void lubTypeArgument( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - if ((type1.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type1).isUninferredTypeArgument()) - || (type2.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type2).isUninferredTypeArgument())) { - // The asSuper calls below don't seem to retain if a type variable was uninferred. - // There is a similar check in the wildcards branch below, not sure when that is - // actually hit. - // TODO: see whether anything else should be done. See typetools issue 6438 and eisop - // issue 703. - if (lub.getKind() == TypeKind.WILDCARD) { - ((AnnotatedWildcardType) lub).setUninferredTypeArgument(); - } - return; - } - - // In lub(), asSuper is called on type1 and type2, but asSuper does not recur into type - // arguments, so call asSuper on the type arguments so that they have the same underlying - // type. - AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); - AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); - - // If the type argument is a wildcard or captured type argument, then the lub computation is - // slightly different. The primary annotation on the lower bound is the glb of lower bounds - // of the type types. This is because the lub of Gen<@A ? extends @A Object> and Gen<@B ? - // extends @A Object> is Gen<@B ? extends @A Object>. If visit(type1AsLub, type2AsLub, lub) - // was called instead of the below code, then the lub would be Gen<@A ? extends @A Object>. - // (Note the lub of Gen<@A ? super @A Object> and Gen<@A ? super @B Object> does not exist, - // but Gen<@A ? super @B Object> is returned.) - if (lub.getKind() == TypeKind.WILDCARD) { - if (visited(lub)) { - return; - } - AnnotatedWildcardType type1Wildcard = (AnnotatedWildcardType) type1AsLub; - AnnotatedWildcardType type2Wildcard = (AnnotatedWildcardType) type2AsLub; - AnnotatedWildcardType lubWildcard = (AnnotatedWildcardType) lub; - if (type1Wildcard.isUninferredTypeArgument() - || type2Wildcard.isUninferredTypeArgument()) { - lubWildcard.setUninferredTypeArgument(); - } - lubWildcard( - type1Wildcard.getSuperBound(), - type1Wildcard.getExtendsBound(), - type2Wildcard.getSuperBound(), - type2Wildcard.getExtendsBound(), - lubWildcard.getSuperBound(), - lubWildcard.getExtendsBound()); - } else if (lub.getKind() == TypeKind.TYPEVAR - && TypesUtils.isCapturedTypeVariable((TypeVariable) lub.getUnderlyingType())) { - if (visited(lub)) { - return; - } - AnnotatedTypeVariable type1typevar = (AnnotatedTypeVariable) type1AsLub; - AnnotatedTypeVariable type2typevar = (AnnotatedTypeVariable) type2AsLub; - AnnotatedTypeVariable lubTypevar = (AnnotatedTypeVariable) lub; - lubWildcard( - type1typevar.getLowerBound(), - type1typevar.getUpperBound(), - type2typevar.getLowerBound(), - type2typevar.getUpperBound(), - lubTypevar.getLowerBound(), - lubTypevar.getUpperBound()); - } else { - // Don't add to visit history because that will happen in visitTypevar_Typevar or - // visitWildcard_Wildcard if needed. - visit(type1AsLub, type2AsLub, lub); - } + for (int i = 0; i < type1.getTypeArguments().size(); i++) { + AnnotatedTypeMirror type1TypeArg = type1.getTypeArguments().get(i); + AnnotatedTypeMirror type2TypeArg = type2.getTypeArguments().get(i); + AnnotatedTypeMirror lubTypeArg = castedLub.getTypeArguments().get(i); + lubTypeArgument(type1TypeArg, type2TypeArg, lubTypeArg); } - - private void lubWildcard( - AnnotatedTypeMirror type1LowerBound, - AnnotatedTypeMirror type1UpperBound, - AnnotatedTypeMirror type2LowerBound, - AnnotatedTypeMirror type2UpperBound, - AnnotatedTypeMirror lubLowerBound, - AnnotatedTypeMirror lubUpperBound) { - visit(type1UpperBound, type2UpperBound, lubUpperBound); - visit(type1LowerBound, type2LowerBound, lubLowerBound); - - TypeMirror tm1 = type1LowerBound.getUnderlyingType(); - TypeMirror tm2 = type2LowerBound.getUnderlyingType(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror anno1 = type1LowerBound.getAnnotationInHierarchy(top); - AnnotationMirror anno2 = type2LowerBound.getAnnotationInHierarchy(top); - - if (anno1 != null && anno2 != null) { - AnnotationMirror glb = - qualHierarchy.greatestLowerBoundShallow(anno1, tm1, anno2, tm2); - lubLowerBound.replaceAnnotation(glb); - } - } + return null; + } + + /** + * Annotate the least upper bound of two type arguments. + * + * @param type1 the first type argument + * @param type2 the second type argument + * @param lub the least upper bound + */ + private void lubTypeArgument( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + if ((type1.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type1).isUninferredTypeArgument()) + || (type2.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type2).isUninferredTypeArgument())) { + // The asSuper calls below don't seem to retain if a type variable was uninferred. + // There is a similar check in the wildcards branch below, not sure when that is + // actually hit. + // TODO: see whether anything else should be done. See typetools issue 6438 and eisop + // issue 703. + if (lub.getKind() == TypeKind.WILDCARD) { + ((AnnotatedWildcardType) lub).setUninferredTypeArgument(); + } + return; } - @Override - public Void visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, AnnotatedTypeMirror lub) { - // Called to issue warning - castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); - return null; + // In lub(), asSuper is called on type1 and type2, but asSuper does not recur into type + // arguments, so call asSuper on the type arguments so that they have the same underlying + // type. + AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); + AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); + + // If the type argument is a wildcard or captured type argument, then the lub computation is + // slightly different. The primary annotation on the lower bound is the glb of lower bounds + // of the type types. This is because the lub of Gen<@A ? extends @A Object> and Gen<@B ? + // extends @A Object> is Gen<@B ? extends @A Object>. If visit(type1AsLub, type2AsLub, lub) + // was called instead of the below code, then the lub would be Gen<@A ? extends @A Object>. + // (Note the lub of Gen<@A ? super @A Object> and Gen<@A ? super @B Object> does not exist, + // but Gen<@A ? super @B Object> is returned.) + if (lub.getKind() == TypeKind.WILDCARD) { + if (visited(lub)) { + return; + } + AnnotatedWildcardType type1Wildcard = (AnnotatedWildcardType) type1AsLub; + AnnotatedWildcardType type2Wildcard = (AnnotatedWildcardType) type2AsLub; + AnnotatedWildcardType lubWildcard = (AnnotatedWildcardType) lub; + if (type1Wildcard.isUninferredTypeArgument() || type2Wildcard.isUninferredTypeArgument()) { + lubWildcard.setUninferredTypeArgument(); + } + lubWildcard( + type1Wildcard.getSuperBound(), + type1Wildcard.getExtendsBound(), + type2Wildcard.getSuperBound(), + type2Wildcard.getExtendsBound(), + lubWildcard.getSuperBound(), + lubWildcard.getExtendsBound()); + } else if (lub.getKind() == TypeKind.TYPEVAR + && TypesUtils.isCapturedTypeVariable((TypeVariable) lub.getUnderlyingType())) { + if (visited(lub)) { + return; + } + AnnotatedTypeVariable type1typevar = (AnnotatedTypeVariable) type1AsLub; + AnnotatedTypeVariable type2typevar = (AnnotatedTypeVariable) type2AsLub; + AnnotatedTypeVariable lubTypevar = (AnnotatedTypeVariable) lub; + lubWildcard( + type1typevar.getLowerBound(), + type1typevar.getUpperBound(), + type2typevar.getLowerBound(), + type2typevar.getUpperBound(), + lubTypevar.getLowerBound(), + lubTypevar.getUpperBound()); + } else { + // Don't add to visit history because that will happen in visitTypevar_Typevar or + // visitWildcard_Wildcard if needed. + visit(type1AsLub, type2AsLub, lub); + } + } + + private void lubWildcard( + AnnotatedTypeMirror type1LowerBound, + AnnotatedTypeMirror type1UpperBound, + AnnotatedTypeMirror type2LowerBound, + AnnotatedTypeMirror type2UpperBound, + AnnotatedTypeMirror lubLowerBound, + AnnotatedTypeMirror lubUpperBound) { + visit(type1UpperBound, type2UpperBound, lubUpperBound); + visit(type1LowerBound, type2LowerBound, lubLowerBound); + + TypeMirror tm1 = type1LowerBound.getUnderlyingType(); + TypeMirror tm2 = type2LowerBound.getUnderlyingType(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror anno1 = type1LowerBound.getAnnotationInHierarchy(top); + AnnotationMirror anno2 = type2LowerBound.getAnnotationInHierarchy(top); + + if (anno1 != null && anno2 != null) { + AnnotationMirror glb = qualHierarchy.greatestLowerBoundShallow(anno1, tm1, anno2, tm2); + lubLowerBound.replaceAnnotation(glb); + } + } + } + + @Override + public Void visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, AnnotatedTypeMirror lub) { + // Called to issue warning + castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); + return null; + } + + @Override + public Void visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, AnnotatedTypeMirror lub1) { + if (visited(lub1)) { + return null; } - @Override - public Void visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, AnnotatedTypeMirror lub1) { - if (visited(lub1)) { - return null; - } - - AnnotatedTypeVariable lub = castLub(type1, lub1); - visit(type1.getUpperBound(), type2.getUpperBound(), lub.getUpperBound()); - visit(type1.getLowerBound(), type2.getLowerBound(), lub.getLowerBound()); + AnnotatedTypeVariable lub = castLub(type1, lub1); + visit(type1.getUpperBound(), type2.getUpperBound(), lub.getUpperBound()); + visit(type1.getLowerBound(), type2.getLowerBound(), lub.getLowerBound()); - lubPrimaryOnBoundedType(type1, type2, lub); + lubPrimaryOnBoundedType(type1, type2, lub); - return null; - } + return null; + } - @Override - public Void visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, AnnotatedTypeMirror lub1) { - if (visited(lub1)) { - return null; - } - AnnotatedWildcardType lub = castLub(type1, lub1); - visit(type1.getExtendsBound(), type2.getExtendsBound(), lub.getExtendsBound()); - visit(type1.getSuperBound(), type2.getSuperBound(), lub.getSuperBound()); - lubPrimaryOnBoundedType(type1, type2, lub); - - return null; + @Override + public Void visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, AnnotatedTypeMirror lub1) { + if (visited(lub1)) { + return null; } - - private void lubPrimaryOnBoundedType( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - // For each hierarchy, if type1 is not a subtype of type2 and type2 is not a - // subtype of type1, then the primary annotation on lub must be the effective upper - // bound of type1 or type2, whichever is higher. - AnnotationMirrorSet type1LowerBoundAnnos = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type1); - AnnotationMirrorSet type2LowerBoundAnnos = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type2); - - TypeMirror typeMirror1 = type1.getUnderlyingType(); - TypeMirror typeMirror2 = type2.getUnderlyingType(); - - for (AnnotationMirror lower1 : type1LowerBoundAnnos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(lower1); - - // Can't just call isSubtype because it will return false if bounds have - // different annotations on component types - AnnotationMirror lower2 = - qualHierarchy.findAnnotationInHierarchy(type2LowerBoundAnnos, top); - AnnotationMirror upper1 = type1.getEffectiveAnnotationInHierarchy(lower1); - AnnotationMirror upper2 = type2.getEffectiveAnnotationInHierarchy(lower1); - - if (qualHierarchy.isSubtypeShallow(upper2, typeMirror2, upper1, typeMirror1) - && qualHierarchy.isSubtypeShallow(upper1, typeMirror1, upper2, typeMirror2) - && qualHierarchy.isSubtypeShallow(lower1, typeMirror1, lower2, typeMirror2) - && qualHierarchy.isSubtypeShallow(lower2, typeMirror2, lower1, typeMirror1)) { - continue; - } - - if (!qualHierarchy.isSubtypeShallow(upper2, typeMirror2, lower1, typeMirror1) - && !qualHierarchy.isSubtypeShallow(upper1, typeMirror1, lower2, typeMirror2)) { - lub.replaceAnnotation( - qualHierarchy.leastUpperBoundShallow( - upper1, typeMirror1, upper2, typeMirror2)); - } - } + AnnotatedWildcardType lub = castLub(type1, lub1); + visit(type1.getExtendsBound(), type2.getExtendsBound(), lub.getExtendsBound()); + visit(type1.getSuperBound(), type2.getSuperBound(), lub.getSuperBound()); + lubPrimaryOnBoundedType(type1, type2, lub); + + return null; + } + + private void lubPrimaryOnBoundedType( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + // For each hierarchy, if type1 is not a subtype of type2 and type2 is not a + // subtype of type1, then the primary annotation on lub must be the effective upper + // bound of type1 or type2, whichever is higher. + AnnotationMirrorSet type1LowerBoundAnnos = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type1); + AnnotationMirrorSet type2LowerBoundAnnos = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type2); + + TypeMirror typeMirror1 = type1.getUnderlyingType(); + TypeMirror typeMirror2 = type2.getUnderlyingType(); + + for (AnnotationMirror lower1 : type1LowerBoundAnnos) { + AnnotationMirror top = qualHierarchy.getTopAnnotation(lower1); + + // Can't just call isSubtype because it will return false if bounds have + // different annotations on component types + AnnotationMirror lower2 = qualHierarchy.findAnnotationInHierarchy(type2LowerBoundAnnos, top); + AnnotationMirror upper1 = type1.getEffectiveAnnotationInHierarchy(lower1); + AnnotationMirror upper2 = type2.getEffectiveAnnotationInHierarchy(lower1); + + if (qualHierarchy.isSubtypeShallow(upper2, typeMirror2, upper1, typeMirror1) + && qualHierarchy.isSubtypeShallow(upper1, typeMirror1, upper2, typeMirror2) + && qualHierarchy.isSubtypeShallow(lower1, typeMirror1, lower2, typeMirror2) + && qualHierarchy.isSubtypeShallow(lower2, typeMirror2, lower1, typeMirror1)) { + continue; + } + + if (!qualHierarchy.isSubtypeShallow(upper2, typeMirror2, lower1, typeMirror1) + && !qualHierarchy.isSubtypeShallow(upper1, typeMirror1, lower2, typeMirror2)) { + lub.replaceAnnotation( + qualHierarchy.leastUpperBoundShallow(upper1, typeMirror1, upper2, typeMirror2)); + } } + } - @Override - public Void visitIntersection_Intersection( - AnnotatedIntersectionType type1, - AnnotatedIntersectionType type2, - AnnotatedTypeMirror lub) { - AnnotatedIntersectionType castedLub = castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); + @Override + public Void visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, AnnotatedTypeMirror lub) { + AnnotatedIntersectionType castedLub = castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); - for (int i = 0; i < castedLub.getBounds().size(); i++) { - AnnotatedTypeMirror lubST = castedLub.getBounds().get(i); - visit(type1.getBounds().get(i), type2.getBounds().get(i), lubST); - } - - return null; + for (int i = 0; i < castedLub.getBounds().size(); i++) { + AnnotatedTypeMirror lubST = castedLub.getBounds().get(i); + visit(type1.getBounds().get(i), type2.getBounds().get(i), lubST); } - @Override - public Void visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, AnnotatedTypeMirror lub) { - AnnotatedUnionType castedLub = castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); - - for (int i = 0; i < castedLub.getAlternatives().size(); i++) { - AnnotatedDeclaredType lubAltern = castedLub.getAlternatives().get(i); - visit(type1.getAlternatives().get(i), type2.getAlternatives().get(i), lubAltern); - } - return null; - } + return null; + } - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - return super.defaultErrorMessage(type1, type2, lub) - + String.format("%n lub: %s %s", lub.getKind(), lub); - } + @Override + public Void visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, AnnotatedTypeMirror lub) { + AnnotatedUnionType castedLub = castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); - /** - * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is - * added to the list of visited AnnotatedTypeMirrors. This prevents infinite recursion on - * recursive types. - * - * @param atm the type that might have been visited - * @return true if the given type has been visited - */ - private boolean visited(@FindDistinct AnnotatedTypeMirror atm) { - for (AnnotatedTypeMirror atmVisit : visited) { - // Use reference equality rather than equals because the visitor may visit two types - // that are structurally equal, but not actually the same. For example, the - // wildcards in IPair may be equal, but they both should be visited. - if (atmVisit == atm) { - return true; - } - } - visited.add(atm); - return false; + for (int i = 0; i < castedLub.getAlternatives().size(); i++) { + AnnotatedDeclaredType lubAltern = castedLub.getAlternatives().get(i); + visit(type1.getAlternatives().get(i), type2.getAlternatives().get(i), lubAltern); + } + return null; + } + + @Override + public String defaultErrorMessage( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + return super.defaultErrorMessage(type1, type2, lub) + + String.format("%n lub: %s %s", lub.getKind(), lub); + } + + /** + * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is + * added to the list of visited AnnotatedTypeMirrors. This prevents infinite recursion on + * recursive types. + * + * @param atm the type that might have been visited + * @return true if the given type has been visited + */ + private boolean visited(@FindDistinct AnnotatedTypeMirror atm) { + for (AnnotatedTypeMirror atmVisit : visited) { + // Use reference equality rather than equals because the visitor may visit two types + // that are structurally equal, but not actually the same. For example, the + // wildcards in IPair may be equal, but they both should be visited. + if (atmVisit == atm) { + return true; + } } + visited.add(atm); + return false; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java index 13baeaf25d7..af4646cbd9c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java +++ b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java @@ -1,14 +1,5 @@ package org.checkerframework.framework.util; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.PolyNull; -import org.checkerframework.checker.regex.qual.Regex; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.UserError; -import org.plumelib.util.CollectionsPlume; - import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -29,6 +20,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; +import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.UserError; +import org.plumelib.util.CollectionsPlume; /** * This class behaves similarly to javac. CheckerMain does the following: @@ -56,935 +55,915 @@ */ public class CheckerMain { - /** - * Any exception thrown by the Checker Framework escapes to the command line. - * - * @param args command-line arguments - */ - public static void main(String[] args) { - File pathToThisJar = new File(findPathTo(CheckerMain.class, false)); - ArrayList alargs = new ArrayList<>(Arrays.asList(args)); - CheckerMain program = new CheckerMain(pathToThisJar, alargs); - int exitStatus = program.invokeCompiler(); - System.exit(exitStatus); - } - - /** The path to the javacJar to use. */ - protected final File javacJar; - - /** The path to the jar containing CheckerMain.class (i.e. checker.jar). */ - protected final File checkerJar; - - /** The path to checker-qual.jar. */ - protected final File checkerQualJar; - - /** The path to checker-util.jar. */ - protected final File checkerUtilJar; - - /** Compilation bootclasspath. */ - private final List compilationBootclasspath; - - private final List runtimeClasspath; - - private final List jvmOpts; - - /** - * Each element is either a classpath element (a directory or jar file) or is a classpath - * (containing elements separated by File.pathSeparator). To produce the final classpath, - * concatenate them all (separated by File.pathSeparator). - */ - private final List cpOpts; - - /** Processor path options. */ - private final List ppOpts; - - /** Arguments to the Checker Framework. */ - private final List toolOpts; - - /** Command-line argument files (specified with @ on the command line). */ - private final List argListFiles; - - /** - * Option name for specifying an alternative checker-qual.jar location. The accompanying value - * MUST be the path to the jar file (NOT the path to its encompassing directory) - */ - public static final String CHECKER_QUAL_PATH_OPT = "-checkerQualJar"; - - /** - * Option name for specifying an alternative checker-util.jar location. The accompanying value - * MUST be the path to the jar file (NOT the path to its encompassing directory) - */ - public static final String CHECKER_UTIL_PATH_OPT = "-checkerUtilJar"; - - /** - * Option name for specifying an alternative javac.jar location. The accompanying value MUST be - * the path to the jar file (NOT the path to its encompassing directory) - */ - public static final String JAVAC_PATH_OPT = "-javacJar"; - - /** - * Option name for specifying an alternative jdk.jar location. The accompanying value MUST be - * the path to the jar file (NOT the path to its encompassing directory) - */ - public static final String JDK_PATH_OPT = "-jdkJar"; - - /** - * Construct all the relevant file locations and Java version given the path to this jar and a - * set of directories in which to search for jars. - */ - public CheckerMain(File checkerJar, List args) { - - this.checkerJar = checkerJar; - File searchPath = checkerJar.getParentFile(); - - replaceShorthandProcessor(args); - argListFiles = collectArgFiles(args); - - this.checkerQualJar = - extractFileArg( - CHECKER_QUAL_PATH_OPT, new File(searchPath, "checker-qual.jar"), args); - - this.checkerUtilJar = - extractFileArg( - CHECKER_UTIL_PATH_OPT, new File(searchPath, "checker-util.jar"), args); - - this.javacJar = extractFileArg(JAVAC_PATH_OPT, new File(searchPath, "javac.jar"), args); - - this.compilationBootclasspath = createCompilationBootclasspath(args); - this.runtimeClasspath = createRuntimeClasspath(args); - this.jvmOpts = extractJvmOpts(args); - - this.cpOpts = createCpOpts(args); - this.ppOpts = createPpOpts(args); - this.toolOpts = args; - - assertValidState(); - } - - /** Assert that required jars exist. */ - protected void assertValidState() { - if (SystemUtil.jreVersion == 8) { - assertFilesExist(javacJar, checkerJar, checkerQualJar, checkerUtilJar); - } else { - assertFilesExist(checkerJar, checkerQualJar, checkerUtilJar); - } - } - - public void addToClasspath(List cpOpts) { - this.cpOpts.addAll(cpOpts); - } - - public void addToProcessorpath(List ppOpts) { - this.ppOpts.addAll(ppOpts); - } - - public void addToRuntimeClasspath(List runtimeClasspathOpts) { - this.runtimeClasspath.addAll(runtimeClasspathOpts); - } - - protected List createRuntimeClasspath(List argsList) { - return new ArrayList<>(Arrays.asList(javacJar.getAbsolutePath())); - } - - /** - * Returns the compilation bootclasspath from {@code argsList}. - * - * @param argsList args to add - * @return the compilation bootclasspath from {@code argsList} - */ - protected List createCompilationBootclasspath(List argsList) { - return extractBootClassPath(argsList); - } - - protected List createCpOpts(List argsList) { - List extractedOpts = extractCpOpts(argsList); - extractedOpts.add(0, this.checkerQualJar.getAbsolutePath()); - extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); - - return extractedOpts; - } - - /** - * Returns processor path options. - * - *

This method assumes that createCpOpts has already been run. - * - * @param argsList arguments - * @return processor path options - */ - protected List createPpOpts(List argsList) { - List extractedOpts = new ArrayList<>(extractPpOpts(argsList)); - if (extractedOpts.isEmpty()) { - // If processorpath is not provided, then javac uses the classpath. - // CheckerMain always supplies a processorpath, so if the user - // didn't specify a processorpath, then use the classpath. - extractedOpts.addAll(this.cpOpts); - } - extractedOpts.add(0, this.checkerJar.getAbsolutePath()); - extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); - - return extractedOpts; - } - - /** - * Return the arguments that start with @ and therefore are files that contain javac arguments. - * - * @param args a list of command-line arguments; is not modified - * @return a List of files representing all arguments that started with @ - */ - protected List collectArgFiles(List args) { - List argListFiles = new ArrayList<>(); - for (String arg : args) { - if (arg.startsWith("@")) { - argListFiles.add(new File(arg.substring(1))); - } - } - - return argListFiles; - } - - /** - * Remove the argument given by argumentName and the subsequent value from the list args if - * present. Return the subsequent value. - * - * @param argumentName a command-line option name whose argument to extract - * @param alternative default value to return if argumentName does not appear in args - * @param args the current list of arguments - * @return the string that follows argumentName if argumentName is in args, or alternative if - * argumentName is not present in args - */ - protected static @PolyNull String extractArg( - String argumentName, @PolyNull String alternative, List args) { - int i = args.indexOf(argumentName); - if (i == -1) { - return alternative; - } else if (i == args.size() - 1) { - throw new BugInCF( - "Command line contains " + argumentName + " but no value following it"); - } else { - args.remove(i); - return args.remove(i); - } - } - - /** - * Remove the argument given by argumentName and the subsequent value from the list args if - * present. Return the subsequent value wrapped as a File. - * - * @param argumentName argument to extract - * @param alternative file to return if argumentName is not found in args - * @param args the current list of arguments - * @return the string that follows argumentName wrapped as a File if argumentName is in args or - * alternative if argumentName is not present in args - */ - protected static File extractFileArg(String argumentName, File alternative, List args) { - String filePath = extractArg(argumentName, null, args); - if (filePath == null) { - return alternative; - } else { - return new File(filePath); - } - } - - /** - * Find all args that match the given pattern and extract their index 1 group. Add all the index - * 1 groups to the returned list. Remove all matching args from the input args list. - * - * @param pattern a pattern with at least one matching group - * @param allowEmpties whether or not to add empty group(1) matches to the returned list - * @param args the arguments to extract from - * @return a list of arguments from the first group that matched the pattern for each input args - * or the empty list if there were none - */ - protected static List extractOptWithPattern( - @Regex(1) Pattern pattern, boolean allowEmpties, List args) { - List matchedArgs = new ArrayList<>(); - - int i = 0; - while (i < args.size()) { - Matcher matcher = pattern.matcher(args.get(i)); - if (matcher.matches()) { - String group1 = matcher.group(1); - if (group1 == null) { - throw new BugInCF("Regex didn't capture group 1: " + pattern); - } - String arg = group1.trim(); - - if (!arg.isEmpty() || allowEmpties) { - matchedArgs.add(arg); - } - - args.remove(i); - } else { - i++; - } - } - - return matchedArgs; - } - - /** - * A pattern to match bootclasspath prepend entries, used to construct one {@code - * -Xbootclasspath/p:} command-line argument. - */ - protected static final Pattern BOOT_CLASS_PATH_REGEX = - Pattern.compile("^(?:-J)?-Xbootclasspath/p:(.*)$"); - - // TODO: Why does this treat -J and -J-X the same? They have different semantics, don't they? - /** - * Remove all {@code -Xbootclasspath/p:} or {@code -J-Xbootclasspath/p:} arguments from args and - * add them to the returned list. - * - * @param args the arguments to extract from - * @return all non-empty arguments matching BOOT_CLASS_PATH_REGEX or an empty list if there were - * none - */ - protected static List extractBootClassPath(List args) { - return extractOptWithPattern(BOOT_CLASS_PATH_REGEX, false, args); - } - - /** Matches all {@code -J} arguments. */ - protected static final Pattern JVM_OPTS_REGEX = Pattern.compile("^(?:-J)(.*)$"); - - /** - * Remove all {@code -J} arguments from {@code args} and add them to the returned list (without - * the {@code -J} prefix). - * - * @param args the arguments to extract from - * @return all {@code -J} arguments (without the {@code -J} prefix) or an empty list if there - * were none - */ - protected static List extractJvmOpts(List args) { - return extractOptWithPattern(JVM_OPTS_REGEX, false, args); - } - - /** - * Return the last {@code -cp} or {@code -classpath} option. If no {@code -cp} or {@code - * -classpath} arguments were present, then return the CLASSPATH environment variable (if set) - * followed by the current directory. - * - *

Also removes all {@code -cp} and {@code -classpath} options from args. - * - * @param args a list of arguments to extract from; is side-effected by this - * @return collection of classpaths to concatenate to use when calling javac.jar - */ - protected static List extractCpOpts(List args) { - List actualArgs = new ArrayList<>(); - - String lastCpArg = null; - - for (int i = 0; i < args.size(); i++) { - if ((args.get(i).equals("-cp") || args.get(i).equals("-classpath")) - && (i + 1 < args.size())) { - args.remove(i); - // Every classpath entry overrides the one before it. - lastCpArg = args.remove(i); - // re-process whatever is currently at element i - i--; - } - } - - // The logic below is exactly what the javac script does. If no command-line classpath is - // specified, use the "CLASSPATH" environment variable followed by the current directory. - if (lastCpArg == null) { - String systemClassPath = System.getenv("CLASSPATH"); - if (systemClassPath != null && !systemClassPath.trim().isEmpty()) { - actualArgs.add(systemClassPath.trim()); - } - - actualArgs.add("."); - } else { - actualArgs.add(lastCpArg); - } - - return actualArgs; - } - - /** - * Remove the {@code -processorpath} options and their arguments from args. Return the last - * argument. - * - * @param args a list of arguments to extract from - * @return the arguments that should be put on the processorpath when calling javac.jar - */ - protected static List extractPpOpts(List args) { - String path = null; - - for (int i = 0; i < args.size(); i++) { - if (args.get(i).equals("-processorpath") && (i + 1 < args.size())) { - args.remove(i); - path = args.remove(i); - // re-process whatever is currently at element i - i--; - } - } - - if (path != null) { - return Collections.singletonList(path); - } else { - return Collections.emptyList(); - } - } - - protected void addMainToArgs(List args) { - args.add("com.sun.tools.javac.Main"); - } - - /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ - public List getExecArguments() { - List args = new ArrayList<>(jvmOpts.size() + cpOpts.size() + toolOpts.size() + 7); - - // TODO: do we need java.exe on Windows? - String java = "java"; - args.add(java); - - if (SystemUtil.jreVersion == 8) { - args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, runtimeClasspath)); - } else { - // See comments in build.gradle - args.addAll( - // Keep this list in sync with the lists in checker-framework/build.gradle in - // compilerArgsForRunningCFs, the sections with labels - // "javac-jdk11-non-modularized", "maven", and "sbt" in the manual, and in the - // checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject - Arrays.asList( - // These are required in Java 17+ because the --illegal-access option is - // set to deny by default. None of these packages are accessed via - // reflection, so the module only needs to be exported, but not opened. - "--add-exports", - "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - // Required because the Checker Framework reflectively accesses private - // members in com.sun.tools.javac.comp. - "--add-opens", - "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED")); - } - - args.add("-classpath"); - args.add(String.join(File.pathSeparator, runtimeClasspath)); - args.add("-ea"); - // com.sun.tools needs to be enabled separately - args.add("-ea:com.sun.tools..."); - - args.addAll(jvmOpts); - - addMainToArgs(args); + /** + * Any exception thrown by the Checker Framework escapes to the command line. + * + * @param args command-line arguments + */ + public static void main(String[] args) { + File pathToThisJar = new File(findPathTo(CheckerMain.class, false)); + ArrayList alargs = new ArrayList<>(Arrays.asList(args)); + CheckerMain program = new CheckerMain(pathToThisJar, alargs); + int exitStatus = program.invokeCompiler(); + System.exit(exitStatus); + } - if (!argsListHasClassPath(argListFiles)) { - args.add("-classpath"); - args.add(quote(concatenatePaths(cpOpts))); - } - if (!argsListHasProcessorPath(argListFiles)) { - args.add("-processorpath"); - args.add(quote(concatenatePaths(ppOpts))); - } + /** The path to the javacJar to use. */ + protected final File javacJar; - if (SystemUtil.jreVersion == 8) { - // No classes on the compilation bootclasspath will be loaded - // during compilation, but the classes are read by the compiler - // without loading them. The compiler assumes that any class on - // this bootclasspath will be on the bootclasspath of the JVM used - // to later run the classfiles that Javac produces. - args.add( - "-Xbootclasspath/p:" - + String.join(File.pathSeparator, compilationBootclasspath)); - } + /** The path to the jar containing CheckerMain.class (i.e. checker.jar). */ + protected final File checkerJar; + + /** The path to checker-qual.jar. */ + protected final File checkerQualJar; - args.addAll(toolOpts); - return args; - } + /** The path to checker-util.jar. */ + protected final File checkerUtilJar; - /** Given a list of paths, concatenate them to form a single path. Also expand wildcards. */ - private String concatenatePaths(List paths) { - List elements = new ArrayList<>(); - for (String path : paths) { - for (String element : SystemUtil.PATH_SEPARATOR_SPLITTER.split(path)) { - elements.addAll(expandWildcards(element)); - } - } - return String.join(File.pathSeparator, elements); - } - - /** The string "/*" (on Unix). */ - private static final String FILESEP_STAR = File.separator + "*"; - - /** - * Given a path element that might be a wildcard, return a list of the elements it expands to. - * If the element isn't a wildcard, return a singleton list containing the argument. Since the - * original argument list is placed after 'com.sun.tools.javac.Main' in the new command line, - * the JVM doesn't do wildcard expansion of jar files in any classpaths in the original argument - * list. - * - * @param pathElement an element of a classpath - * @return all elements of a classpath with wildcards expanded - */ - private List expandWildcards(String pathElement) { - if (pathElement.equals("*")) { - return jarFiles("."); - } else if (pathElement.endsWith(FILESEP_STAR)) { - return jarFiles(pathElement.substring(0, pathElement.length() - 1)); - } else if (pathElement.isEmpty()) { - return Collections.emptyList(); - } else { - return Collections.singletonList(pathElement); - } - } + /** Compilation bootclasspath. */ + private final List compilationBootclasspath; - /** - * Returns all the .jar and .JAR files in the given directory. - * - * @param directory a directory - * @return all the .jar and .JAR files in the given directory - */ - private List jarFiles(String directory) { - File dir = new File(directory); - String[] jarFiles = dir.list((d, name) -> name.endsWith(".jar") || name.endsWith(".JAR")); - if (jarFiles == null) { - return Collections.emptyList(); - } - // concat directory with jar file path to give full path - for (int i = 0; i < jarFiles.length; i++) { - jarFiles[i] = directory + jarFiles[i]; - } - return Arrays.asList(jarFiles); - } - - /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ - public int invokeCompiler() { - List args = getExecArguments(); + private final List runtimeClasspath; + private final List jvmOpts; + + /** + * Each element is either a classpath element (a directory or jar file) or is a classpath + * (containing elements separated by File.pathSeparator). To produce the final classpath, + * concatenate them all (separated by File.pathSeparator). + */ + private final List cpOpts; + + /** Processor path options. */ + private final List ppOpts; + + /** Arguments to the Checker Framework. */ + private final List toolOpts; + + /** Command-line argument files (specified with @ on the command line). */ + private final List argListFiles; + + /** + * Option name for specifying an alternative checker-qual.jar location. The accompanying value + * MUST be the path to the jar file (NOT the path to its encompassing directory) + */ + public static final String CHECKER_QUAL_PATH_OPT = "-checkerQualJar"; + + /** + * Option name for specifying an alternative checker-util.jar location. The accompanying value + * MUST be the path to the jar file (NOT the path to its encompassing directory) + */ + public static final String CHECKER_UTIL_PATH_OPT = "-checkerUtilJar"; + + /** + * Option name for specifying an alternative javac.jar location. The accompanying value MUST be + * the path to the jar file (NOT the path to its encompassing directory) + */ + public static final String JAVAC_PATH_OPT = "-javacJar"; + + /** + * Option name for specifying an alternative jdk.jar location. The accompanying value MUST be the + * path to the jar file (NOT the path to its encompassing directory) + */ + public static final String JDK_PATH_OPT = "-jdkJar"; + + /** + * Construct all the relevant file locations and Java version given the path to this jar and a set + * of directories in which to search for jars. + */ + public CheckerMain(File checkerJar, List args) { + + this.checkerJar = checkerJar; + File searchPath = checkerJar.getParentFile(); + + replaceShorthandProcessor(args); + argListFiles = collectArgFiles(args); + + this.checkerQualJar = + extractFileArg(CHECKER_QUAL_PATH_OPT, new File(searchPath, "checker-qual.jar"), args); + + this.checkerUtilJar = + extractFileArg(CHECKER_UTIL_PATH_OPT, new File(searchPath, "checker-util.jar"), args); + + this.javacJar = extractFileArg(JAVAC_PATH_OPT, new File(searchPath, "javac.jar"), args); + + this.compilationBootclasspath = createCompilationBootclasspath(args); + this.runtimeClasspath = createRuntimeClasspath(args); + this.jvmOpts = extractJvmOpts(args); + + this.cpOpts = createCpOpts(args); + this.ppOpts = createPpOpts(args); + this.toolOpts = args; + + assertValidState(); + } + + /** Assert that required jars exist. */ + protected void assertValidState() { + if (SystemUtil.jreVersion == 8) { + assertFilesExist(javacJar, checkerJar, checkerQualJar, checkerUtilJar); + } else { + assertFilesExist(checkerJar, checkerQualJar, checkerUtilJar); + } + } + + public void addToClasspath(List cpOpts) { + this.cpOpts.addAll(cpOpts); + } + + public void addToProcessorpath(List ppOpts) { + this.ppOpts.addAll(ppOpts); + } + + public void addToRuntimeClasspath(List runtimeClasspathOpts) { + this.runtimeClasspath.addAll(runtimeClasspathOpts); + } + + protected List createRuntimeClasspath(List argsList) { + return new ArrayList<>(Arrays.asList(javacJar.getAbsolutePath())); + } + + /** + * Returns the compilation bootclasspath from {@code argsList}. + * + * @param argsList args to add + * @return the compilation bootclasspath from {@code argsList} + */ + protected List createCompilationBootclasspath(List argsList) { + return extractBootClassPath(argsList); + } + + protected List createCpOpts(List argsList) { + List extractedOpts = extractCpOpts(argsList); + extractedOpts.add(0, this.checkerQualJar.getAbsolutePath()); + extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); + + return extractedOpts; + } + + /** + * Returns processor path options. + * + *

This method assumes that createCpOpts has already been run. + * + * @param argsList arguments + * @return processor path options + */ + protected List createPpOpts(List argsList) { + List extractedOpts = new ArrayList<>(extractPpOpts(argsList)); + if (extractedOpts.isEmpty()) { + // If processorpath is not provided, then javac uses the classpath. + // CheckerMain always supplies a processorpath, so if the user + // didn't specify a processorpath, then use the classpath. + extractedOpts.addAll(this.cpOpts); + } + extractedOpts.add(0, this.checkerJar.getAbsolutePath()); + extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); + + return extractedOpts; + } + + /** + * Return the arguments that start with @ and therefore are files that contain javac arguments. + * + * @param args a list of command-line arguments; is not modified + * @return a List of files representing all arguments that started with @ + */ + protected List collectArgFiles(List args) { + List argListFiles = new ArrayList<>(); + for (String arg : args) { + if (arg.startsWith("@")) { + argListFiles.add(new File(arg.substring(1))); + } + } + + return argListFiles; + } + + /** + * Remove the argument given by argumentName and the subsequent value from the list args if + * present. Return the subsequent value. + * + * @param argumentName a command-line option name whose argument to extract + * @param alternative default value to return if argumentName does not appear in args + * @param args the current list of arguments + * @return the string that follows argumentName if argumentName is in args, or alternative if + * argumentName is not present in args + */ + protected static @PolyNull String extractArg( + String argumentName, @PolyNull String alternative, List args) { + int i = args.indexOf(argumentName); + if (i == -1) { + return alternative; + } else if (i == args.size() - 1) { + throw new BugInCF("Command line contains " + argumentName + " but no value following it"); + } else { + args.remove(i); + return args.remove(i); + } + } + + /** + * Remove the argument given by argumentName and the subsequent value from the list args if + * present. Return the subsequent value wrapped as a File. + * + * @param argumentName argument to extract + * @param alternative file to return if argumentName is not found in args + * @param args the current list of arguments + * @return the string that follows argumentName wrapped as a File if argumentName is in args or + * alternative if argumentName is not present in args + */ + protected static File extractFileArg(String argumentName, File alternative, List args) { + String filePath = extractArg(argumentName, null, args); + if (filePath == null) { + return alternative; + } else { + return new File(filePath); + } + } + + /** + * Find all args that match the given pattern and extract their index 1 group. Add all the index 1 + * groups to the returned list. Remove all matching args from the input args list. + * + * @param pattern a pattern with at least one matching group + * @param allowEmpties whether or not to add empty group(1) matches to the returned list + * @param args the arguments to extract from + * @return a list of arguments from the first group that matched the pattern for each input args + * or the empty list if there were none + */ + protected static List extractOptWithPattern( + @Regex(1) Pattern pattern, boolean allowEmpties, List args) { + List matchedArgs = new ArrayList<>(); + + int i = 0; + while (i < args.size()) { + Matcher matcher = pattern.matcher(args.get(i)); + if (matcher.matches()) { + String group1 = matcher.group(1); + if (group1 == null) { + throw new BugInCF("Regex didn't capture group 1: " + pattern); + } + String arg = group1.trim(); + + if (!arg.isEmpty() || allowEmpties) { + matchedArgs.add(arg); + } + + args.remove(i); + } else { + i++; + } + } + + return matchedArgs; + } + + /** + * A pattern to match bootclasspath prepend entries, used to construct one {@code + * -Xbootclasspath/p:} command-line argument. + */ + protected static final Pattern BOOT_CLASS_PATH_REGEX = + Pattern.compile("^(?:-J)?-Xbootclasspath/p:(.*)$"); + + // TODO: Why does this treat -J and -J-X the same? They have different semantics, don't they? + /** + * Remove all {@code -Xbootclasspath/p:} or {@code -J-Xbootclasspath/p:} arguments from args and + * add them to the returned list. + * + * @param args the arguments to extract from + * @return all non-empty arguments matching BOOT_CLASS_PATH_REGEX or an empty list if there were + * none + */ + protected static List extractBootClassPath(List args) { + return extractOptWithPattern(BOOT_CLASS_PATH_REGEX, false, args); + } + + /** Matches all {@code -J} arguments. */ + protected static final Pattern JVM_OPTS_REGEX = Pattern.compile("^(?:-J)(.*)$"); + + /** + * Remove all {@code -J} arguments from {@code args} and add them to the returned list (without + * the {@code -J} prefix). + * + * @param args the arguments to extract from + * @return all {@code -J} arguments (without the {@code -J} prefix) or an empty list if there were + * none + */ + protected static List extractJvmOpts(List args) { + return extractOptWithPattern(JVM_OPTS_REGEX, false, args); + } + + /** + * Return the last {@code -cp} or {@code -classpath} option. If no {@code -cp} or {@code + * -classpath} arguments were present, then return the CLASSPATH environment variable (if set) + * followed by the current directory. + * + *

Also removes all {@code -cp} and {@code -classpath} options from args. + * + * @param args a list of arguments to extract from; is side-effected by this + * @return collection of classpaths to concatenate to use when calling javac.jar + */ + protected static List extractCpOpts(List args) { + List actualArgs = new ArrayList<>(); + + String lastCpArg = null; + + for (int i = 0; i < args.size(); i++) { + if ((args.get(i).equals("-cp") || args.get(i).equals("-classpath")) + && (i + 1 < args.size())) { + args.remove(i); + // Every classpath entry overrides the one before it. + lastCpArg = args.remove(i); + // re-process whatever is currently at element i + i--; + } + } + + // The logic below is exactly what the javac script does. If no command-line classpath is + // specified, use the "CLASSPATH" environment variable followed by the current directory. + if (lastCpArg == null) { + String systemClassPath = System.getenv("CLASSPATH"); + if (systemClassPath != null && !systemClassPath.trim().isEmpty()) { + actualArgs.add(systemClassPath.trim()); + } + + actualArgs.add("."); + } else { + actualArgs.add(lastCpArg); + } + + return actualArgs; + } + + /** + * Remove the {@code -processorpath} options and their arguments from args. Return the last + * argument. + * + * @param args a list of arguments to extract from + * @return the arguments that should be put on the processorpath when calling javac.jar + */ + protected static List extractPpOpts(List args) { + String path = null; + + for (int i = 0; i < args.size(); i++) { + if (args.get(i).equals("-processorpath") && (i + 1 < args.size())) { + args.remove(i); + path = args.remove(i); + // re-process whatever is currently at element i + i--; + } + } + + if (path != null) { + return Collections.singletonList(path); + } else { + return Collections.emptyList(); + } + } + + protected void addMainToArgs(List args) { + args.add("com.sun.tools.javac.Main"); + } + + /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ + public List getExecArguments() { + List args = new ArrayList<>(jvmOpts.size() + cpOpts.size() + toolOpts.size() + 7); + + // TODO: do we need java.exe on Windows? + String java = "java"; + args.add(java); + + if (SystemUtil.jreVersion == 8) { + args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, runtimeClasspath)); + } else { + // See comments in build.gradle + args.addAll( + // Keep this list in sync with the lists in checker-framework/build.gradle in + // compilerArgsForRunningCFs, the sections with labels + // "javac-jdk11-non-modularized", "maven", and "sbt" in the manual, and in the + // checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject + Arrays.asList( + // These are required in Java 17+ because the --illegal-access option is + // set to deny by default. None of these packages are accessed via + // reflection, so the module only needs to be exported, but not opened. + "--add-exports", + "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + // Required because the Checker Framework reflectively accesses private + // members in com.sun.tools.javac.comp. + "--add-opens", + "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED")); + } + + args.add("-classpath"); + args.add(String.join(File.pathSeparator, runtimeClasspath)); + args.add("-ea"); + // com.sun.tools needs to be enabled separately + args.add("-ea:com.sun.tools..."); + + args.addAll(jvmOpts); + + addMainToArgs(args); + + if (!argsListHasClassPath(argListFiles)) { + args.add("-classpath"); + args.add(quote(concatenatePaths(cpOpts))); + } + if (!argsListHasProcessorPath(argListFiles)) { + args.add("-processorpath"); + args.add(quote(concatenatePaths(ppOpts))); + } + + if (SystemUtil.jreVersion == 8) { + // No classes on the compilation bootclasspath will be loaded + // during compilation, but the classes are read by the compiler + // without loading them. The compiler assumes that any class on + // this bootclasspath will be on the bootclasspath of the JVM used + // to later run the classfiles that Javac produces. + args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, compilationBootclasspath)); + } + + args.addAll(toolOpts); + return args; + } + + /** Given a list of paths, concatenate them to form a single path. Also expand wildcards. */ + private String concatenatePaths(List paths) { + List elements = new ArrayList<>(); + for (String path : paths) { + for (String element : SystemUtil.PATH_SEPARATOR_SPLITTER.split(path)) { + elements.addAll(expandWildcards(element)); + } + } + return String.join(File.pathSeparator, elements); + } + + /** The string "/*" (on Unix). */ + private static final String FILESEP_STAR = File.separator + "*"; + + /** + * Given a path element that might be a wildcard, return a list of the elements it expands to. If + * the element isn't a wildcard, return a singleton list containing the argument. Since the + * original argument list is placed after 'com.sun.tools.javac.Main' in the new command line, the + * JVM doesn't do wildcard expansion of jar files in any classpaths in the original argument list. + * + * @param pathElement an element of a classpath + * @return all elements of a classpath with wildcards expanded + */ + private List expandWildcards(String pathElement) { + if (pathElement.equals("*")) { + return jarFiles("."); + } else if (pathElement.endsWith(FILESEP_STAR)) { + return jarFiles(pathElement.substring(0, pathElement.length() - 1)); + } else if (pathElement.isEmpty()) { + return Collections.emptyList(); + } else { + return Collections.singletonList(pathElement); + } + } + + /** + * Returns all the .jar and .JAR files in the given directory. + * + * @param directory a directory + * @return all the .jar and .JAR files in the given directory + */ + private List jarFiles(String directory) { + File dir = new File(directory); + String[] jarFiles = dir.list((d, name) -> name.endsWith(".jar") || name.endsWith(".JAR")); + if (jarFiles == null) { + return Collections.emptyList(); + } + // concat directory with jar file path to give full path + for (int i = 0; i < jarFiles.length; i++) { + jarFiles[i] = directory + jarFiles[i]; + } + return Arrays.asList(jarFiles); + } + + /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ + public int invokeCompiler() { + List args = getExecArguments(); + + for (int i = 0; i < args.size(); i++) { + String arg = args.get(i); + + if (arg.startsWith("-AoutputArgsToFile=")) { + String fileName = arg.substring(19); + args.remove(i); + outputArgumentsToFile(fileName, args); + break; + } + } + + // Actually invoke the compiler + return ExecUtil.execute(args.toArray(new String[args.size()]), System.out, System.err); + } + + private static void outputArgumentsToFile(String outputFilename, List args) { + if (outputFilename != null) { + String errorMessage = null; + + try { + @SuppressWarnings("builder:required.method.not.called") // called when needed + PrintWriter writer = + (outputFilename.equals("-") + ? new PrintWriter(System.out) + : new PrintWriter(outputFilename, "UTF-8")); for (int i = 0; i < args.size(); i++) { - String arg = args.get(i); - - if (arg.startsWith("-AoutputArgsToFile=")) { - String fileName = arg.substring(19); - args.remove(i); - outputArgumentsToFile(fileName, args); - break; - } - } - - // Actually invoke the compiler - return ExecUtil.execute(args.toArray(new String[args.size()]), System.out, System.err); - } - - private static void outputArgumentsToFile(String outputFilename, List args) { - if (outputFilename != null) { - String errorMessage = null; - - try { - @SuppressWarnings("builder:required.method.not.called") // called when needed - PrintWriter writer = - (outputFilename.equals("-") - ? new PrintWriter(System.out) - : new PrintWriter(outputFilename, "UTF-8")); - for (int i = 0; i < args.size(); i++) { - String arg = args.get(i); - - // We would like to include the filename of the argfile instead of its contents. - // The problem is that the file will sometimes disappear by the time the user - // can look at or run the resulting script. Maven deletes the argfile very - // shortly after it has been handed off to javac, for example. Ideally we would - // print the argfile filename as a comment but the resulting file couldn't then - // be run as a script on Unix or Windows. - if (arg.startsWith("@")) { - // Read argfile and include its parameters in the output file. - String inputFilename = arg.substring(1); - - try (BufferedReader br = - new BufferedReader(new FileReader(inputFilename))) { - String line; - while ((line = br.readLine()) != null) { - writer.print(line); - writer.print(" "); - } - } - } else { - writer.print(arg); - writer.print(" "); - } - writer.flush(); - } - if (!outputFilename.equals("-")) { - writer.close(); - } - } catch (IOException e) { - errorMessage = e.toString(); - } - - if (errorMessage != null) { - System.err.println( - "Failed to output command-line arguments to file " - + outputFilename - + " due to exception: " - + errorMessage); - } - } - } - - /** - * Returns true if some @arglist file sets the classpath. - * - * @param argListFiles command-line argument files (specified with @ on the command line) - */ - private static boolean argsListHasClassPath(List argListFiles) { - for (String arg : expandArgFiles(argListFiles)) { - if (arg.contains("-classpath") || arg.contains("-cp")) { - return true; + String arg = args.get(i); + + // We would like to include the filename of the argfile instead of its contents. + // The problem is that the file will sometimes disappear by the time the user + // can look at or run the resulting script. Maven deletes the argfile very + // shortly after it has been handed off to javac, for example. Ideally we would + // print the argfile filename as a comment but the resulting file couldn't then + // be run as a script on Unix or Windows. + if (arg.startsWith("@")) { + // Read argfile and include its parameters in the output file. + String inputFilename = arg.substring(1); + + try (BufferedReader br = new BufferedReader(new FileReader(inputFilename))) { + String line; + while ((line = br.readLine()) != null) { + writer.print(line); + writer.print(" "); + } } - } - - return false; - } - - /** - * Returns true if some @arglist file sets the processorpath. - * - * @param argListFiles command-line argument files (specified with @ on the command line) - */ - private static boolean argsListHasProcessorPath(List argListFiles) { - for (String arg : expandArgFiles(argListFiles)) { - if (arg.contains("-processorpath")) { - return true; - } - } - - return false; - } - - /** - * Return all the lines in all the files. - * - * @param files a list of files - * @return a list of all the lines in all the files - */ - protected static List expandArgFiles(List files) { - List content = new ArrayList<>(); - for (File file : files) { - try { - content.addAll(Files.readAllLines(file.toPath())); - } catch (IOException exc) { - throw new RuntimeException("Could not open file: " + file.getAbsolutePath(), exc); - } - } - return content; - } - - /** - * Find the jar file or directory containing the .class file from which cls was loaded. - * - * @param cls the class whose .class file we wish to locate; if null, CheckerMain.class - * @param errIfFromDirectory if false, throw an exception if the file was loaded from a - * directory - */ - public static String findPathTo(@Nullable Class cls, boolean errIfFromDirectory) - throws IllegalStateException { - if (cls == null) { - cls = CheckerMain.class; - } - String name = cls.getName(); - String classFileName; - /* name is something like pakkage.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ - { - int idx = name.lastIndexOf('.'); - classFileName = (idx == -1 ? name : name.substring(idx + 1)) + ".class"; - } - - URL classFileUrl = cls.getResource(classFileName); - if (classFileUrl == null) { - throw new BugInCF("Cannot find resource " + classFileName); - } - if (classFileUrl.getProtocol().equals("file")) { - if (errIfFromDirectory) { - return classFileUrl.toString(); - } else { - throw new IllegalStateException( - "This class has been loaded from a directory and not from a jar file."); - } - } - String uri = classFileUrl.toString(); - if (!uri.startsWith("jar:file:")) { - int idx = uri.indexOf(':'); - String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx); - throw new IllegalStateException( - "This class has been loaded remotely via the " - + protocol - + " protocol. Only loading from a jar on the local file system is" - + " supported."); - } - - int idx = uri.indexOf('!'); - // Sanity check - if (idx == -1) { - throw new IllegalStateException( - "You appear to have loaded this class from a local jar file, but URI has no" - + " \"!\": " - + uri); - } - - try { - String fileName = - URLDecoder.decode( - uri.substring("jar:file:".length(), idx), - Charset.defaultCharset().name()); - return new File(fileName).getAbsolutePath(); - } catch (UnsupportedEncodingException e) { - throw new BugInCF("Default charset doesn't exist. Your VM is borked."); - } - } - - /** - * Assert that all files in the list exist and if they don't, throw a RuntimeException with a - * list of the files that do not exist. - * - * @param expectedFiles files that must exist - */ - private static void assertFilesExist(File... expectedFiles) { - List missingFiles = new ArrayList<>(); - for (File file : expectedFiles) { - if (file == null) { - throw new RuntimeException("Null passed to assertFilesExist"); - } - if (!file.exists()) { - missingFiles.add(file); - } - } - - if (!missingFiles.isEmpty()) { - if (missingFiles.size() == 1) { - File missingFile = missingFiles.get(0); - if (missingFile.getName().equals("javac.jar")) { - throw new UserError( - "Could not find " - + missingFile.getAbsolutePath() - + ". This may be because you built the Checker Framework under" - + " Java 11 but are running it under Java 8."); - } - } - List missingAbsoluteFilenames = - CollectionsPlume.mapList(File::getAbsolutePath, missingFiles); - throw new RuntimeException( - "The following files could not be located: " - + String.join(", ", missingAbsoluteFilenames)); - } - } - - private static String quote(String str) { - if (str.contains(" ")) { - if (str.contains("\"")) { - throw new BugInCF( - "Don't know how to quote a string containing a double-quote character " - + str); - } - return "\"" + str + "\""; - } - return str; - } - - /////////////////////////////////////////////////////////////////////////// - /// Shorthand checker names - /// - - /** Processor shorthand is enabled for processors in this directory in checker.jar. */ - protected static final String CHECKER_BASE_DIR_NAME = "org/checkerframework/checker/"; - - /** Processor shorthand is enabled for processors in this directory in checker.jar. */ - protected static final String COMMON_BASE_DIR_NAME = "org/checkerframework/common/"; - - /** - * Returns true if processorString, once transformed into fully-qualified form, is present in - * fullyQualifiedCheckerNames. Used by SourceChecker to determine whether a class is annotated - * for any processor that is being run. - * - * @param processorString the name of a single processor, not a comma-separated list of - * processors - * @param fullyQualifiedCheckerNames a list of fully-qualified checker names - * @return true if the fully-qualified version of {@code processorString} is in {@code - * fullyQualifiedCheckerNames} - */ - public static boolean matchesCheckerOrSubcheckerFromList( - String processorString, List<@FullyQualifiedName String> fullyQualifiedCheckerNames) { - if (processorString.contains(",")) { - return false; // Do not process strings containing multiple processors. - } - - return fullyQualifiedCheckerNames.contains( - unshorthandProcessorNames(processorString, fullyQualifiedCheckerNames, true)); - } - - /** - * For every "-processor" argument in args, replace its immediate successor argument using - * unabbreviateProcessorNames. - */ - protected void replaceShorthandProcessor(List args) { - for (int i = 0; i < args.size(); i++) { - int nextIndex = i + 1; - if (args.size() > nextIndex) { - if (args.get(i).equals("-processor")) { - String replacement = - unshorthandProcessorNames( - args.get(nextIndex), getAllCheckerClassNames(), false); - args.remove(nextIndex); - args.add(nextIndex, replacement); - } - } - } - } - - /** - * Returns the list of fully qualified names of the checkers found in checker.jar. This covers - * only checkers with the name ending in "Checker". Checkers with a name ending in "Subchecker" - * are not included in the returned list. Note however that it is possible for a checker with - * the name ending in "Checker" to be used as a subchecker. - * - * @return fully qualified names of the checkers found in checker.jar - */ - private List<@FullyQualifiedName String> getAllCheckerClassNames() { - ArrayList<@FullyQualifiedName String> checkerClassNames = new ArrayList<>(); - try (FileInputStream fis = new FileInputStream(checkerJar); - JarInputStream checkerJarIs = new JarInputStream(fis)) { - ZipEntry entry; - while ((entry = checkerJarIs.getNextEntry()) != null) { - String name = entry.getName(); - // Checkers ending in "Subchecker" are not included in this list used by - // CheckerMain. - if ((name.startsWith(CHECKER_BASE_DIR_NAME) - || name.startsWith(COMMON_BASE_DIR_NAME)) - && name.endsWith("Checker.class")) { - // Forward slash is used instead of File.separator because checker.jar uses / as - // the separator. - @SuppressWarnings("signature") // string manipulation - @FullyQualifiedName String fqName = - String.join( - ".", - name.substring(0, name.length() - ".class".length()) - .split("/")); - checkerClassNames.add(fqName); - } - } - } catch (IOException e) { - // Issue a warning instead of aborting execution. - System.err.printf( - "Could not read %s. Shorthand processor names will not work.%n", checkerJar); - } - - return checkerClassNames; - } - - /** - * Takes a string of comma-separated processor names, and expands any shorthands to - * fully-qualified names from the fullyQualifiedCheckerNames list. For example: - * - *

-     * NullnessChecker → org.checkerframework.checker.nullness.NullnessChecker
-     * nullness → org.checkerframework.checker.nullness.NullnessChecker
-     * NullnessChecker,RegexChecker → org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.regex.RegexChecker
-     * 
- * - * Note, a processor entry only gets replaced if it contains NO "." (i.e., it is not qualified - * by a package name) and can be found under the package org.checkerframework.checker in - * checker.jar. - * - * @param processorsString a comma-separated string identifying processors; often just one - * processor - * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match - * processorsString against - * @param allowSubcheckers whether to match against fully qualified checker names ending with - * "Subchecker" - * @return processorsString where all shorthand references to Checker Framework built-in - * checkers are replaced with fully-qualified references - */ - protected static String unshorthandProcessorNames( - String processorsString, - List<@FullyQualifiedName String> fullyQualifiedCheckerNames, - boolean allowSubcheckers) { - StringJoiner result = new StringJoiner(","); - for (String processor : SystemUtil.COMMA_SPLITTER.split(processorsString)) { - if (!processor.contains(".")) { // Not already fully qualified - processor = - unshorthandProcessorName( - processor, fullyQualifiedCheckerNames, allowSubcheckers); - } - result.add(processor); - } - return result.toString(); - } - - /** - * Given a processor name, tries to expand it to a checker in the fullyQualifiedCheckerNames - * list. Returns that expansion, or the argument itself if the expansion fails. - * - * @param processorName a processor name, possibly in shorthand - * @param fullyQualifiedCheckerNames all checker names - * @param allowSubcheckers whether to match subcheckers as well as checkers - * @return the fully-qualified version of {@code processorName} in {@code - * fullyQualifiedCheckerNames}, or else {@code processorName} itself - */ - private static String unshorthandProcessorName( - String processorName, - List<@FullyQualifiedName String> fullyQualifiedCheckerNames, - boolean allowSubcheckers) { - for (String name : fullyQualifiedCheckerNames) { - boolean tryMatch = false; - String[] checkerPath = - name.substring(0, name.length() - "Checker".length()).split("\\."); - String checkerNameShort = checkerPath[checkerPath.length - 1]; - String checkerName = checkerNameShort + "Checker"; - - if (name.endsWith("Checker")) { - checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); - checkerNameShort = checkerPath[checkerPath.length - 1]; - checkerName = checkerNameShort + "Checker"; - tryMatch = true; - } else if (allowSubcheckers && name.endsWith("Subchecker")) { - checkerPath = name.substring(0, name.length() - "Subchecker".length()).split("\\."); - checkerNameShort = checkerPath[checkerPath.length - 1]; - checkerName = checkerNameShort + "Subchecker"; - tryMatch = true; - } - - if (tryMatch) { - if (processorName.equalsIgnoreCase(checkerName) - || processorName.equalsIgnoreCase(checkerNameShort)) { - return name; - } - } - } - - return processorName; // If not matched, return the input string. - } - - /** - * Given a shorthand processor name, returns true if it can be expanded to a checker in the - * fullyQualifiedCheckerNames list. - * - * @param processorName a string identifying one processor - * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match - * processorName against - * @param allowSubcheckers whether to match against fully qualified checker names ending with - * "Subchecker" - * @return true if the shorthand processor name can be expanded to a checker in {@code - * fullyQualifiedCheckerNames} - */ - public static boolean matchesFullyQualifiedProcessor( - String processorName, - List<@FullyQualifiedName String> fullyQualifiedCheckerNames, - boolean allowSubcheckers) { - return !processorName.equals( - unshorthandProcessorName( - processorName, fullyQualifiedCheckerNames, allowSubcheckers)); - } + } else { + writer.print(arg); + writer.print(" "); + } + writer.flush(); + } + if (!outputFilename.equals("-")) { + writer.close(); + } + } catch (IOException e) { + errorMessage = e.toString(); + } + + if (errorMessage != null) { + System.err.println( + "Failed to output command-line arguments to file " + + outputFilename + + " due to exception: " + + errorMessage); + } + } + } + + /** + * Returns true if some @arglist file sets the classpath. + * + * @param argListFiles command-line argument files (specified with @ on the command line) + */ + private static boolean argsListHasClassPath(List argListFiles) { + for (String arg : expandArgFiles(argListFiles)) { + if (arg.contains("-classpath") || arg.contains("-cp")) { + return true; + } + } + + return false; + } + + /** + * Returns true if some @arglist file sets the processorpath. + * + * @param argListFiles command-line argument files (specified with @ on the command line) + */ + private static boolean argsListHasProcessorPath(List argListFiles) { + for (String arg : expandArgFiles(argListFiles)) { + if (arg.contains("-processorpath")) { + return true; + } + } + + return false; + } + + /** + * Return all the lines in all the files. + * + * @param files a list of files + * @return a list of all the lines in all the files + */ + protected static List expandArgFiles(List files) { + List content = new ArrayList<>(); + for (File file : files) { + try { + content.addAll(Files.readAllLines(file.toPath())); + } catch (IOException exc) { + throw new RuntimeException("Could not open file: " + file.getAbsolutePath(), exc); + } + } + return content; + } + + /** + * Find the jar file or directory containing the .class file from which cls was loaded. + * + * @param cls the class whose .class file we wish to locate; if null, CheckerMain.class + * @param errIfFromDirectory if false, throw an exception if the file was loaded from a directory + */ + public static String findPathTo(@Nullable Class cls, boolean errIfFromDirectory) + throws IllegalStateException { + if (cls == null) { + cls = CheckerMain.class; + } + String name = cls.getName(); + String classFileName; + /* name is something like pakkage.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ + { + int idx = name.lastIndexOf('.'); + classFileName = (idx == -1 ? name : name.substring(idx + 1)) + ".class"; + } + + URL classFileUrl = cls.getResource(classFileName); + if (classFileUrl == null) { + throw new BugInCF("Cannot find resource " + classFileName); + } + if (classFileUrl.getProtocol().equals("file")) { + if (errIfFromDirectory) { + return classFileUrl.toString(); + } else { + throw new IllegalStateException( + "This class has been loaded from a directory and not from a jar file."); + } + } + String uri = classFileUrl.toString(); + if (!uri.startsWith("jar:file:")) { + int idx = uri.indexOf(':'); + String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx); + throw new IllegalStateException( + "This class has been loaded remotely via the " + + protocol + + " protocol. Only loading from a jar on the local file system is" + + " supported."); + } + + int idx = uri.indexOf('!'); + // Sanity check + if (idx == -1) { + throw new IllegalStateException( + "You appear to have loaded this class from a local jar file, but URI has no" + + " \"!\": " + + uri); + } + + try { + String fileName = + URLDecoder.decode( + uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name()); + return new File(fileName).getAbsolutePath(); + } catch (UnsupportedEncodingException e) { + throw new BugInCF("Default charset doesn't exist. Your VM is borked."); + } + } + + /** + * Assert that all files in the list exist and if they don't, throw a RuntimeException with a list + * of the files that do not exist. + * + * @param expectedFiles files that must exist + */ + private static void assertFilesExist(File... expectedFiles) { + List missingFiles = new ArrayList<>(); + for (File file : expectedFiles) { + if (file == null) { + throw new RuntimeException("Null passed to assertFilesExist"); + } + if (!file.exists()) { + missingFiles.add(file); + } + } + + if (!missingFiles.isEmpty()) { + if (missingFiles.size() == 1) { + File missingFile = missingFiles.get(0); + if (missingFile.getName().equals("javac.jar")) { + throw new UserError( + "Could not find " + + missingFile.getAbsolutePath() + + ". This may be because you built the Checker Framework under" + + " Java 11 but are running it under Java 8."); + } + } + List missingAbsoluteFilenames = + CollectionsPlume.mapList(File::getAbsolutePath, missingFiles); + throw new RuntimeException( + "The following files could not be located: " + + String.join(", ", missingAbsoluteFilenames)); + } + } + + private static String quote(String str) { + if (str.contains(" ")) { + if (str.contains("\"")) { + throw new BugInCF( + "Don't know how to quote a string containing a double-quote character " + str); + } + return "\"" + str + "\""; + } + return str; + } + + /////////////////////////////////////////////////////////////////////////// + /// Shorthand checker names + /// + + /** Processor shorthand is enabled for processors in this directory in checker.jar. */ + protected static final String CHECKER_BASE_DIR_NAME = "org/checkerframework/checker/"; + + /** Processor shorthand is enabled for processors in this directory in checker.jar. */ + protected static final String COMMON_BASE_DIR_NAME = "org/checkerframework/common/"; + + /** + * Returns true if processorString, once transformed into fully-qualified form, is present in + * fullyQualifiedCheckerNames. Used by SourceChecker to determine whether a class is annotated for + * any processor that is being run. + * + * @param processorString the name of a single processor, not a comma-separated list of processors + * @param fullyQualifiedCheckerNames a list of fully-qualified checker names + * @return true if the fully-qualified version of {@code processorString} is in {@code + * fullyQualifiedCheckerNames} + */ + public static boolean matchesCheckerOrSubcheckerFromList( + String processorString, List<@FullyQualifiedName String> fullyQualifiedCheckerNames) { + if (processorString.contains(",")) { + return false; // Do not process strings containing multiple processors. + } + + return fullyQualifiedCheckerNames.contains( + unshorthandProcessorNames(processorString, fullyQualifiedCheckerNames, true)); + } + + /** + * For every "-processor" argument in args, replace its immediate successor argument using + * unabbreviateProcessorNames. + */ + protected void replaceShorthandProcessor(List args) { + for (int i = 0; i < args.size(); i++) { + int nextIndex = i + 1; + if (args.size() > nextIndex) { + if (args.get(i).equals("-processor")) { + String replacement = + unshorthandProcessorNames(args.get(nextIndex), getAllCheckerClassNames(), false); + args.remove(nextIndex); + args.add(nextIndex, replacement); + } + } + } + } + + /** + * Returns the list of fully qualified names of the checkers found in checker.jar. This covers + * only checkers with the name ending in "Checker". Checkers with a name ending in "Subchecker" + * are not included in the returned list. Note however that it is possible for a checker with the + * name ending in "Checker" to be used as a subchecker. + * + * @return fully qualified names of the checkers found in checker.jar + */ + private List<@FullyQualifiedName String> getAllCheckerClassNames() { + ArrayList<@FullyQualifiedName String> checkerClassNames = new ArrayList<>(); + try (FileInputStream fis = new FileInputStream(checkerJar); + JarInputStream checkerJarIs = new JarInputStream(fis)) { + ZipEntry entry; + while ((entry = checkerJarIs.getNextEntry()) != null) { + String name = entry.getName(); + // Checkers ending in "Subchecker" are not included in this list used by + // CheckerMain. + if ((name.startsWith(CHECKER_BASE_DIR_NAME) || name.startsWith(COMMON_BASE_DIR_NAME)) + && name.endsWith("Checker.class")) { + // Forward slash is used instead of File.separator because checker.jar uses / as + // the separator. + @SuppressWarnings("signature") // string manipulation + @FullyQualifiedName String fqName = + String.join(".", name.substring(0, name.length() - ".class".length()).split("/")); + checkerClassNames.add(fqName); + } + } + } catch (IOException e) { + // Issue a warning instead of aborting execution. + System.err.printf( + "Could not read %s. Shorthand processor names will not work.%n", checkerJar); + } + + return checkerClassNames; + } + + /** + * Takes a string of comma-separated processor names, and expands any shorthands to + * fully-qualified names from the fullyQualifiedCheckerNames list. For example: + * + *
+   * NullnessChecker → org.checkerframework.checker.nullness.NullnessChecker
+   * nullness → org.checkerframework.checker.nullness.NullnessChecker
+   * NullnessChecker,RegexChecker → org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.regex.RegexChecker
+   * 
+ * + * Note, a processor entry only gets replaced if it contains NO "." (i.e., it is not qualified by + * a package name) and can be found under the package org.checkerframework.checker in checker.jar. + * + * @param processorsString a comma-separated string identifying processors; often just one + * processor + * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match + * processorsString against + * @param allowSubcheckers whether to match against fully qualified checker names ending with + * "Subchecker" + * @return processorsString where all shorthand references to Checker Framework built-in checkers + * are replaced with fully-qualified references + */ + protected static String unshorthandProcessorNames( + String processorsString, + List<@FullyQualifiedName String> fullyQualifiedCheckerNames, + boolean allowSubcheckers) { + StringJoiner result = new StringJoiner(","); + for (String processor : SystemUtil.COMMA_SPLITTER.split(processorsString)) { + if (!processor.contains(".")) { // Not already fully qualified + processor = + unshorthandProcessorName(processor, fullyQualifiedCheckerNames, allowSubcheckers); + } + result.add(processor); + } + return result.toString(); + } + + /** + * Given a processor name, tries to expand it to a checker in the fullyQualifiedCheckerNames list. + * Returns that expansion, or the argument itself if the expansion fails. + * + * @param processorName a processor name, possibly in shorthand + * @param fullyQualifiedCheckerNames all checker names + * @param allowSubcheckers whether to match subcheckers as well as checkers + * @return the fully-qualified version of {@code processorName} in {@code + * fullyQualifiedCheckerNames}, or else {@code processorName} itself + */ + private static String unshorthandProcessorName( + String processorName, + List<@FullyQualifiedName String> fullyQualifiedCheckerNames, + boolean allowSubcheckers) { + for (String name : fullyQualifiedCheckerNames) { + boolean tryMatch = false; + String[] checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); + String checkerNameShort = checkerPath[checkerPath.length - 1]; + String checkerName = checkerNameShort + "Checker"; + + if (name.endsWith("Checker")) { + checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); + checkerNameShort = checkerPath[checkerPath.length - 1]; + checkerName = checkerNameShort + "Checker"; + tryMatch = true; + } else if (allowSubcheckers && name.endsWith("Subchecker")) { + checkerPath = name.substring(0, name.length() - "Subchecker".length()).split("\\."); + checkerNameShort = checkerPath[checkerPath.length - 1]; + checkerName = checkerNameShort + "Subchecker"; + tryMatch = true; + } + + if (tryMatch) { + if (processorName.equalsIgnoreCase(checkerName) + || processorName.equalsIgnoreCase(checkerNameShort)) { + return name; + } + } + } + + return processorName; // If not matched, return the input string. + } + + /** + * Given a shorthand processor name, returns true if it can be expanded to a checker in the + * fullyQualifiedCheckerNames list. + * + * @param processorName a string identifying one processor + * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match + * processorName against + * @param allowSubcheckers whether to match against fully qualified checker names ending with + * "Subchecker" + * @return true if the shorthand processor name can be expanded to a checker in {@code + * fullyQualifiedCheckerNames} + */ + public static boolean matchesFullyQualifiedProcessor( + String processorName, + List<@FullyQualifiedName String> fullyQualifiedCheckerNames, + boolean allowSubcheckers) { + return !processorName.equals( + unshorthandProcessorName(processorName, fullyQualifiedCheckerNames, allowSubcheckers)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/Contract.java b/framework/src/main/java/org/checkerframework/framework/util/Contract.java index 245b3a56c3e..58aea0f6a52 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Contract.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Contract.java @@ -1,7 +1,9 @@ package org.checkerframework.framework.util; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Objects; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; @@ -14,11 +16,6 @@ import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper; import org.checkerframework.javacutil.BugInCF; -import java.lang.annotation.Annotation; -import java.util.Objects; - -import javax.lang.model.element.AnnotationMirror; - /** * A contract represents an annotation on an expression. It is a precondition, postcondition, or * conditional postcondition. @@ -29,290 +26,286 @@ */ public abstract class Contract { + /** + * The expression for which the condition must hold, such as {@code "foo"} in + * {@code @RequiresNonNull("foo")}. + * + *

An annotation like {@code @RequiresNonNull({"a", "b", "c"})} would be represented by + * multiple Contracts. + */ + public final String expressionString; + + /** The annotation on the type of expression, according to this contract. */ + public final AnnotationMirror annotation; + + /** The annotation that expressed this contract; used for diagnostic messages. */ + public final AnnotationMirror contractAnnotation; + + // This is redundant with the contract's class and is not used in this file, but the field + // is used by clients, for its fields. + /** The kind of contract: precondition, postcondition, or conditional postcondition. */ + public final Kind kind; + + /** Enumerates the kinds of contracts. */ + public enum Kind { + /** A precondition. */ + PRECONDITION( + "precondition", + PreconditionAnnotation.class, + RequiresQualifier.class, + RequiresQualifier.List.class), + /** A postcondition. */ + POSTCONDITION( + "postcondition", + PostconditionAnnotation.class, + EnsuresQualifier.class, + EnsuresQualifier.List.class), + /** A conditional postcondition. */ + CONDITIONALPOSTCONDITION( + "conditional postcondition", + ConditionalPostconditionAnnotation.class, + EnsuresQualifierIf.class, + EnsuresQualifierIf.List.class); + + /** Used for constructing error messages. */ + public final String errorKey; + + /** The meta-annotation identifying annotations of this kind. */ + public final Class metaAnnotation; + + /** The built-in framework qualifier for this contract. */ + public final Class frameworkContractClass; + + /** The built-in framework qualifier for repeated occurrences of this contract. */ + public final Class frameworkContractListClass; + /** - * The expression for which the condition must hold, such as {@code "foo"} in - * {@code @RequiresNonNull("foo")}. + * Create a new Kind. * - *

An annotation like {@code @RequiresNonNull({"a", "b", "c"})} would be represented by - * multiple Contracts. + * @param errorKey used for constructing error messages + * @param metaAnnotation the meta-annotation identifying annotations of this kind + * @param frameworkContractClass the built-in framework qualifier for this contract + * @param frameworkContractListClass the built-in framework qualifier for repeated occurrences + * of this contract */ - public final String expressionString; - - /** The annotation on the type of expression, according to this contract. */ - public final AnnotationMirror annotation; + Kind( + String errorKey, + Class metaAnnotation, + Class frameworkContractClass, + Class frameworkContractListClass) { + this.errorKey = errorKey; + this.metaAnnotation = metaAnnotation; + this.frameworkContractClass = frameworkContractClass; + this.frameworkContractListClass = frameworkContractListClass; + } + } - /** The annotation that expressed this contract; used for diagnostic messages. */ - public final AnnotationMirror contractAnnotation; + /** + * Creates a new Contract. This should be called only by the constructors for {@link + * Precondition}, {@link Postcondition}, and {@link ConditionalPostcondition}. + * + * @param kind precondition, postcondition, or conditional postcondition + * @param expressionString the Java expression that should have a type qualifier + * @param annotation the type qualifier that {@code expressionString} should have + * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; used + * for diagnostic messages + */ + private Contract( + Kind kind, + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation) { + this.expressionString = expressionString; + this.annotation = annotation; + this.contractAnnotation = contractAnnotation; + this.kind = kind; + } - // This is redundant with the contract's class and is not used in this file, but the field - // is used by clients, for its fields. - /** The kind of contract: precondition, postcondition, or conditional postcondition. */ - public final Kind kind; + /** + * Creates a new Contract. + * + * @param kind precondition, postcondition, or conditional postcondition + * @param expressionString the Java expression that should have a type qualifier + * @param annotation the type qualifier that {@code expressionString} should have + * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; used + * for diagnostic messages + * @param ensuresQualifierIf the ensuresQualifierIf field, for a conditional postcondition + * @return a new contract + */ + public static Contract create( + Kind kind, + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation, + Boolean ensuresQualifierIf) { + if ((ensuresQualifierIf != null) != (kind == Kind.CONDITIONALPOSTCONDITION)) { + throw new BugInCF( + "Mismatch: Contract.create(%s, %s, %s, %s, %s)", + kind, expressionString, annotation, contractAnnotation, ensuresQualifierIf); + } + switch (kind) { + case PRECONDITION: + return new Precondition(expressionString, annotation, contractAnnotation); + case POSTCONDITION: + return new Postcondition(expressionString, annotation, contractAnnotation); + case CONDITIONALPOSTCONDITION: + return new ConditionalPostcondition( + expressionString, annotation, contractAnnotation, ensuresQualifierIf); + default: + throw new BugInCF("Unrecognized kind: " + kind); + } + } - /** Enumerates the kinds of contracts. */ - public enum Kind { - /** A precondition. */ - PRECONDITION( - "precondition", - PreconditionAnnotation.class, - RequiresQualifier.class, - RequiresQualifier.List.class), - /** A postcondition. */ - POSTCONDITION( - "postcondition", - PostconditionAnnotation.class, - EnsuresQualifier.class, - EnsuresQualifier.List.class), - /** A conditional postcondition. */ - CONDITIONALPOSTCONDITION( - "conditional postcondition", - ConditionalPostconditionAnnotation.class, - EnsuresQualifierIf.class, - EnsuresQualifierIf.List.class); + // Note that equality requires exact match of the run-time class and that it ignores the + // `contractAnnotation` field. + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + if (getClass() != o.getClass()) { + return false; + } - /** Used for constructing error messages. */ - public final String errorKey; + Contract otherContract = (Contract) o; - /** The meta-annotation identifying annotations of this kind. */ - public final Class metaAnnotation; + return kind == otherContract.kind + && Objects.equals(expressionString, otherContract.expressionString) + && Objects.equals(annotation, otherContract.annotation); + } - /** The built-in framework qualifier for this contract. */ - public final Class frameworkContractClass; + @Override + public int hashCode() { + return Objects.hash(kind, expressionString, annotation); + } - /** The built-in framework qualifier for repeated occurrences of this contract. */ - public final Class frameworkContractListClass; + @Override + public String toString() { + return String.format( + "%s{expressionString=%s, annotation=%s, contractAnnotation=%s}", + getClass().getSimpleName(), expressionString, annotation, contractAnnotation); + } - /** - * Create a new Kind. - * - * @param errorKey used for constructing error messages - * @param metaAnnotation the meta-annotation identifying annotations of this kind - * @param frameworkContractClass the built-in framework qualifier for this contract - * @param frameworkContractListClass the built-in framework qualifier for repeated - * occurrences of this contract - */ - Kind( - String errorKey, - Class metaAnnotation, - Class frameworkContractClass, - Class frameworkContractListClass) { - this.errorKey = errorKey; - this.metaAnnotation = metaAnnotation; - this.frameworkContractClass = frameworkContractClass; - this.frameworkContractListClass = frameworkContractListClass; - } + /** + * Viewpoint-adapt {@link #annotation} using {@code stringToJavaExpr}. + * + *

For example, if the contract is {@code @EnsuresKeyFor(value = "this.field", map = "map")}, + * {@code annotation} is {@code @KeyFor("map")}. This method applies {@code stringToJava} to "map" + * and returns a new {@code KeyFor} annotation with the result. + * + * @param factory used to get {@link DependentTypesHelper} + * @param stringToJavaExpr function used to convert strings to {@link JavaExpression}s + * @param errorTree if non-null, where to report any errors that occur when parsing the dependent + * type annotation; if null, report no errors + * @return the viewpoint-adapted annotation, or {@link #annotation} if it is not a dependent type + * annotation + */ + public AnnotationMirror viewpointAdaptDependentTypeAnnotation( + GenericAnnotatedTypeFactory factory, + StringToJavaExpression stringToJavaExpr, + @Nullable Tree errorTree) { + DependentTypesHelper dependentTypesHelper = factory.getDependentTypesHelper(); + AnnotationMirror standardized = + dependentTypesHelper.convertAnnotationMirror(stringToJavaExpr, annotation); + if (standardized == null) { + return annotation; + } + if (errorTree != null) { + dependentTypesHelper.checkAnnotationForErrorExpressions(standardized, errorTree); } + return standardized; + } + /** A precondition contract. */ + public static class Precondition extends Contract { /** - * Creates a new Contract. This should be called only by the constructors for {@link - * Precondition}, {@link Postcondition}, and {@link ConditionalPostcondition}. + * Create a precondition contract. * - * @param kind precondition, postcondition, or conditional postcondition * @param expressionString the Java expression that should have a type qualifier * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; - * used for diagnostic messages + * @param contractAnnotation the precondition annotation that the programmer wrote; used for + * diagnostic messages */ - private Contract( - Kind kind, - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation) { - this.expressionString = expressionString; - this.annotation = annotation; - this.contractAnnotation = contractAnnotation; - this.kind = kind; + public Precondition( + String expressionString, AnnotationMirror annotation, AnnotationMirror contractAnnotation) { + super(Kind.PRECONDITION, expressionString, annotation, contractAnnotation); } + } + /** A postcondition contract. */ + public static class Postcondition extends Contract { /** - * Creates a new Contract. + * Create a postcondition contract. * - * @param kind precondition, postcondition, or conditional postcondition * @param expressionString the Java expression that should have a type qualifier * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; - * used for diagnostic messages - * @param ensuresQualifierIf the ensuresQualifierIf field, for a conditional postcondition - * @return a new contract + * @param contractAnnotation the postcondition annotation that the programmer wrote; used for + * diagnostic messages */ - public static Contract create( - Kind kind, - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation, - Boolean ensuresQualifierIf) { - if ((ensuresQualifierIf != null) != (kind == Kind.CONDITIONALPOSTCONDITION)) { - throw new BugInCF( - "Mismatch: Contract.create(%s, %s, %s, %s, %s)", - kind, expressionString, annotation, contractAnnotation, ensuresQualifierIf); - } - switch (kind) { - case PRECONDITION: - return new Precondition(expressionString, annotation, contractAnnotation); - case POSTCONDITION: - return new Postcondition(expressionString, annotation, contractAnnotation); - case CONDITIONALPOSTCONDITION: - return new ConditionalPostcondition( - expressionString, annotation, contractAnnotation, ensuresQualifierIf); - default: - throw new BugInCF("Unrecognized kind: " + kind); - } - } - - // Note that equality requires exact match of the run-time class and that it ignores the - // `contractAnnotation` field. - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null) { - return false; - } - if (getClass() != o.getClass()) { - return false; - } - - Contract otherContract = (Contract) o; - - return kind == otherContract.kind - && Objects.equals(expressionString, otherContract.expressionString) - && Objects.equals(annotation, otherContract.annotation); + public Postcondition( + String expressionString, AnnotationMirror annotation, AnnotationMirror contractAnnotation) { + super(Kind.POSTCONDITION, expressionString, annotation, contractAnnotation); } + } - @Override - public int hashCode() { - return Objects.hash(kind, expressionString, annotation); - } - - @Override - public String toString() { - return String.format( - "%s{expressionString=%s, annotation=%s, contractAnnotation=%s}", - getClass().getSimpleName(), expressionString, annotation, contractAnnotation); - } + /** + * Represents a conditional postcondition that must be verified by {@code BaseTypeVisitor} or one + * of its subclasses. Automatically extracted from annotations with meta-annotation + * {@code @ConditionalPostconditionAnnotation}, such as {@code EnsuresNonNullIf}. + */ + public static class ConditionalPostcondition extends Contract { /** - * Viewpoint-adapt {@link #annotation} using {@code stringToJavaExpr}. + * The return value for the annotated method that ensures that the conditional postcondition + * holds. For example, given * - *

For example, if the contract is {@code @EnsuresKeyFor(value = "this.field", map = "map")}, - * {@code annotation} is {@code @KeyFor("map")}. This method applies {@code stringToJava} to - * "map" and returns a new {@code KeyFor} annotation with the result. + *

+     * {@code @EnsuresNonNullIf(expression="foo", result=false) boolean method()}
+     * 
* - * @param factory used to get {@link DependentTypesHelper} - * @param stringToJavaExpr function used to convert strings to {@link JavaExpression}s - * @param errorTree if non-null, where to report any errors that occur when parsing the - * dependent type annotation; if null, report no errors - * @return the viewpoint-adapted annotation, or {@link #annotation} if it is not a dependent - * type annotation + * {@code foo} is guaranteed to be {@code @NonNull} after a call to {@code method()} that + * returns {@code false}. */ - public AnnotationMirror viewpointAdaptDependentTypeAnnotation( - GenericAnnotatedTypeFactory factory, - StringToJavaExpression stringToJavaExpr, - @Nullable Tree errorTree) { - DependentTypesHelper dependentTypesHelper = factory.getDependentTypesHelper(); - AnnotationMirror standardized = - dependentTypesHelper.convertAnnotationMirror(stringToJavaExpr, annotation); - if (standardized == null) { - return annotation; - } - if (errorTree != null) { - dependentTypesHelper.checkAnnotationForErrorExpressions(standardized, errorTree); - } - return standardized; - } - - /** A precondition contract. */ - public static class Precondition extends Contract { - /** - * Create a precondition contract. - * - * @param expressionString the Java expression that should have a type qualifier - * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the precondition annotation that the programmer wrote; used for - * diagnostic messages - */ - public Precondition( - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation) { - super(Kind.PRECONDITION, expressionString, annotation, contractAnnotation); - } - } - - /** A postcondition contract. */ - public static class Postcondition extends Contract { - /** - * Create a postcondition contract. - * - * @param expressionString the Java expression that should have a type qualifier - * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the postcondition annotation that the programmer wrote; used - * for diagnostic messages - */ - public Postcondition( - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation) { - super(Kind.POSTCONDITION, expressionString, annotation, contractAnnotation); - } - } + public final boolean resultValue; /** - * Represents a conditional postcondition that must be verified by {@code BaseTypeVisitor} or - * one of its subclasses. Automatically extracted from annotations with meta-annotation - * {@code @ConditionalPostconditionAnnotation}, such as {@code EnsuresNonNullIf}. + * Create a new conditional postcondition. + * + * @param expressionString the Java expression that should have a type qualifier + * @param annotation the type qualifier that {@code expressionString} should have + * @param contractAnnotation the postcondition annotation that the programmer wrote; used for + * diagnostic messages + * @param resultValue whether the condition is the method returning true or false */ - public static class ConditionalPostcondition extends Contract { - - /** - * The return value for the annotated method that ensures that the conditional postcondition - * holds. For example, given - * - *
-         * {@code @EnsuresNonNullIf(expression="foo", result=false) boolean method()}
-         * 
- * - * {@code foo} is guaranteed to be {@code @NonNull} after a call to {@code method()} that - * returns {@code false}. - */ - public final boolean resultValue; - - /** - * Create a new conditional postcondition. - * - * @param expressionString the Java expression that should have a type qualifier - * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the postcondition annotation that the programmer wrote; used - * for diagnostic messages - * @param resultValue whether the condition is the method returning true or false - */ - public ConditionalPostcondition( - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation, - boolean resultValue) { - super(Kind.CONDITIONALPOSTCONDITION, expressionString, annotation, contractAnnotation); - this.resultValue = resultValue; - } + public ConditionalPostcondition( + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation, + boolean resultValue) { + super(Kind.CONDITIONALPOSTCONDITION, expressionString, annotation, contractAnnotation); + this.resultValue = resultValue; + } - @Override - public boolean equals(@Nullable Object o) { - return super.equals(o) && resultValue == ((ConditionalPostcondition) o).resultValue; - } + @Override + public boolean equals(@Nullable Object o) { + return super.equals(o) && resultValue == ((ConditionalPostcondition) o).resultValue; + } - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), resultValue); - } + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), resultValue); + } - @Override - public String toString() { - String superToString = super.toString(); - return superToString.substring(0, superToString.length() - 1) - + ", annoResult=" - + resultValue - + "}"; - } + @Override + public String toString() { + String superToString = super.toString(); + return superToString.substring(0, superToString.length() - 1) + + ", annoResult=" + + resultValue + + "}"; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java index 144882d44de..5ce98c58e65 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java @@ -1,5 +1,7 @@ package org.checkerframework.framework.util; +import java.util.Set; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; import org.checkerframework.framework.qual.EnsuresQualifier; import org.checkerframework.framework.qual.EnsuresQualifierIf; @@ -7,10 +9,6 @@ import org.checkerframework.framework.qual.PreconditionAnnotation; import org.checkerframework.framework.qual.RequiresQualifier; -import java.util.Set; - -import javax.lang.model.element.ExecutableElement; - /** * Interface to retrieve pre- and postconditions from a method. * @@ -23,36 +21,36 @@ */ public interface ContractsFromMethod { - /** - * Returns all the contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method or constructor whose contracts to retrieve - * @return the contracts on {@code executableElement} - */ - Set getContracts(ExecutableElement executableElement); - - /** - * Returns the precondition contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the precondition contracts on {@code executableElement} - */ - Set getPreconditions(ExecutableElement executableElement); - - /** - * Returns the postcondition contracts on {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the postcondition contracts on {@code executableElement} - */ - Set getPostconditions(ExecutableElement executableElement); - - /** - * Returns the conditional postcondition contracts on method {@code methodElement}. - * - * @param methodElement the method whose contracts to return - * @return the conditional postcondition contracts on {@code methodElement} - */ - Set getConditionalPostconditions( - ExecutableElement methodElement); + /** + * Returns all the contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method or constructor whose contracts to retrieve + * @return the contracts on {@code executableElement} + */ + Set getContracts(ExecutableElement executableElement); + + /** + * Returns the precondition contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the precondition contracts on {@code executableElement} + */ + Set getPreconditions(ExecutableElement executableElement); + + /** + * Returns the postcondition contracts on {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the postcondition contracts on {@code executableElement} + */ + Set getPostconditions(ExecutableElement executableElement); + + /** + * Returns the conditional postcondition contracts on method {@code methodElement}. + * + * @param methodElement the method whose contracts to return + * @return the conditional postcondition contracts on {@code methodElement} + */ + Set getConditionalPostconditions( + ExecutableElement methodElement); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java index b6b8b155416..9a534ad6410 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java @@ -1,68 +1,66 @@ package org.checkerframework.framework.util; +import java.util.Collection; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; -import java.util.Collection; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; - /** A utility for converting AnnotationMirrors to Strings. It omits full package names. */ public class DefaultAnnotationFormatter implements AnnotationFormatter { - /** - * Returns true if, by default, anno should not be printed. - * - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @param anno the annotation mirror to test - * @return true if anno's declaration was qualified by InvisibleQualifier - */ - public static boolean isInvisibleQualified(AnnotationMirror anno) { - TypeElement annoElement = (TypeElement) anno.getAnnotationType().asElement(); - return annoElement.getAnnotation(InvisibleQualifier.class) != null; - } + /** + * Returns true if, by default, anno should not be printed. + * + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @param anno the annotation mirror to test + * @return true if anno's declaration was qualified by InvisibleQualifier + */ + public static boolean isInvisibleQualified(AnnotationMirror anno) { + TypeElement annoElement = (TypeElement) anno.getAnnotationType().asElement(); + return annoElement.getAnnotation(InvisibleQualifier.class) != null; + } - /** - * Creates a String of each annotation in annos separated by a single space character and - * terminated by a space character, obeying the printInvisible parameter. - * - * @param annos a collection of annotations to print - * @param printInvisible whether or not to print "invisible" annotation mirrors - * @return the list of annotations converted to a String - */ - @Override - @SideEffectFree - public String formatAnnotationString( - Collection annos, boolean printInvisible) { - StringBuilder sb = new StringBuilder(); - for (AnnotationMirror obj : annos) { - if (obj == null) { - throw new BugInCF( - "AnnotatedTypeMirror.formatAnnotationString: found null AnnotationMirror"); - } - if (isInvisibleQualified(obj) && !printInvisible) { - continue; - } - AnnotationUtils.toStringSimple(obj, sb); - sb.append(" "); - } - return sb.toString(); + /** + * Creates a String of each annotation in annos separated by a single space character and + * terminated by a space character, obeying the printInvisible parameter. + * + * @param annos a collection of annotations to print + * @param printInvisible whether or not to print "invisible" annotation mirrors + * @return the list of annotations converted to a String + */ + @Override + @SideEffectFree + public String formatAnnotationString( + Collection annos, boolean printInvisible) { + StringBuilder sb = new StringBuilder(); + for (AnnotationMirror obj : annos) { + if (obj == null) { + throw new BugInCF( + "AnnotatedTypeMirror.formatAnnotationString: found null AnnotationMirror"); + } + if (isInvisibleQualified(obj) && !printInvisible) { + continue; + } + AnnotationUtils.toStringSimple(obj, sb); + sb.append(" "); } + return sb.toString(); + } - /** - * Returns the string representation of a single AnnotationMirror, without showing full package - * names. - * - * @param anno the annotation mirror to convert - * @return the string representation of a single AnnotationMirror, without showing full package - * names - */ - @Override - @SideEffectFree - public String formatAnnotationMirror(AnnotationMirror anno) { - return AnnotationUtils.toStringSimple(anno); - } + /** + * Returns the string representation of a single AnnotationMirror, without showing full package + * names. + * + * @param anno the annotation mirror to convert + * @return the string representation of a single AnnotationMirror, without showing full package + * names + */ + @Override + @SideEffectFree + public String formatAnnotationMirror(AnnotationMirror anno) { + return AnnotationUtils.toStringSimple(anno); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java index 3ff77fcf748..ba9946aea2a 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java @@ -1,5 +1,16 @@ package org.checkerframework.framework.util; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.util.ElementFilter; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; import org.checkerframework.framework.qual.EnsuresQualifier; @@ -14,19 +25,6 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.util.ElementFilter; - /** * A utility class to retrieve pre- and postconditions from a method. * @@ -41,293 +39,279 @@ // more helpful error message. public class DefaultContractsFromMethod implements ContractsFromMethod { - /** The QualifierArgument.value field/element. */ - protected final ExecutableElement qualifierArgumentValueElement; + /** The QualifierArgument.value field/element. */ + protected final ExecutableElement qualifierArgumentValueElement; - /** The factory that this ContractsFromMethod is associated with. */ - protected final GenericAnnotatedTypeFactory atypeFactory; + /** The factory that this ContractsFromMethod is associated with. */ + protected final GenericAnnotatedTypeFactory atypeFactory; - /** - * Creates a ContractsFromMethod for the given factory. - * - * @param atypeFactory the type factory associated with the newly-created ContractsFromMethod - */ - public DefaultContractsFromMethod(GenericAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - qualifierArgumentValueElement = - TreeUtils.getMethod( - QualifierArgument.class, "value", 0, atypeFactory.getProcessingEnv()); - } + /** + * Creates a ContractsFromMethod for the given factory. + * + * @param atypeFactory the type factory associated with the newly-created ContractsFromMethod + */ + public DefaultContractsFromMethod(GenericAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + qualifierArgumentValueElement = + TreeUtils.getMethod(QualifierArgument.class, "value", 0, atypeFactory.getProcessingEnv()); + } - /** - * Returns all the contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method or constructor whose contracts to retrieve - * @return the contracts on {@code executableElement} - */ - @Override - public Set getContracts(ExecutableElement executableElement) { - Set contracts = new LinkedHashSet<>(); - contracts.addAll(getPreconditions(executableElement)); - contracts.addAll(getPostconditions(executableElement)); - contracts.addAll(getConditionalPostconditions(executableElement)); - return contracts; - } + /** + * Returns all the contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method or constructor whose contracts to retrieve + * @return the contracts on {@code executableElement} + */ + @Override + public Set getContracts(ExecutableElement executableElement) { + Set contracts = new LinkedHashSet<>(); + contracts.addAll(getPreconditions(executableElement)); + contracts.addAll(getPostconditions(executableElement)); + contracts.addAll(getConditionalPostconditions(executableElement)); + return contracts; + } - /** - * Returns the precondition contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the precondition contracts on {@code executableElement} - */ - @Override - public Set getPreconditions(ExecutableElement executableElement) { - return getContracts( - executableElement, Contract.Kind.PRECONDITION, Contract.Precondition.class); - } + /** + * Returns the precondition contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the precondition contracts on {@code executableElement} + */ + @Override + public Set getPreconditions(ExecutableElement executableElement) { + return getContracts(executableElement, Contract.Kind.PRECONDITION, Contract.Precondition.class); + } - /** - * Returns the postcondition contracts on {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the postcondition contracts on {@code executableElement} - */ - @Override - public Set getPostconditions(ExecutableElement executableElement) { - return getContracts( - executableElement, Contract.Kind.POSTCONDITION, Contract.Postcondition.class); - } + /** + * Returns the postcondition contracts on {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the postcondition contracts on {@code executableElement} + */ + @Override + public Set getPostconditions(ExecutableElement executableElement) { + return getContracts( + executableElement, Contract.Kind.POSTCONDITION, Contract.Postcondition.class); + } - /** - * Returns the conditional postcondition contracts on method {@code methodElement}. - * - * @param methodElement the method whose contracts to return - * @return the conditional postcondition contracts on {@code methodElement} - */ - @Override - public Set getConditionalPostconditions( - ExecutableElement methodElement) { - return getContracts( - methodElement, - Contract.Kind.CONDITIONALPOSTCONDITION, - Contract.ConditionalPostcondition.class); - } + /** + * Returns the conditional postcondition contracts on method {@code methodElement}. + * + * @param methodElement the method whose contracts to return + * @return the conditional postcondition contracts on {@code methodElement} + */ + @Override + public Set getConditionalPostconditions( + ExecutableElement methodElement) { + return getContracts( + methodElement, + Contract.Kind.CONDITIONALPOSTCONDITION, + Contract.ConditionalPostcondition.class); + } - /// Helper methods + /// Helper methods - /** - * Returns the contracts (of a particular kind) on method or constructor {@code - * executableElement}. - * - * @param the type of {@link Contract} to return - * @param executableElement the method whose contracts to return - * @param kind the kind of contracts to retrieve - * @param clazz the class to determine the return type - * @return the contracts on {@code executableElement} - */ - private Set getContracts( - ExecutableElement executableElement, Contract.Kind kind, Class clazz) { - Set result = new LinkedHashSet<>(); - // Check for a single framework-defined contract annotation. - AnnotationMirror frameworkContractAnno = - atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractClass); - result.addAll(getContract(kind, frameworkContractAnno, clazz)); + /** + * Returns the contracts (of a particular kind) on method or constructor {@code + * executableElement}. + * + * @param the type of {@link Contract} to return + * @param executableElement the method whose contracts to return + * @param kind the kind of contracts to retrieve + * @param clazz the class to determine the return type + * @return the contracts on {@code executableElement} + */ + private Set getContracts( + ExecutableElement executableElement, Contract.Kind kind, Class clazz) { + Set result = new LinkedHashSet<>(); + // Check for a single framework-defined contract annotation. + AnnotationMirror frameworkContractAnno = + atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractClass); + result.addAll(getContract(kind, frameworkContractAnno, clazz)); - // Check for a framework-defined wrapper around contract annotations. - // The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. - AnnotationMirror frameworkContractListAnno = - atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractListClass); - if (frameworkContractListAnno != null) { - List frameworkContractAnnoList = - atypeFactory.getContractListValues(frameworkContractListAnno); - for (AnnotationMirror a : frameworkContractAnnoList) { - result.addAll(getContract(kind, a, clazz)); - } - } + // Check for a framework-defined wrapper around contract annotations. + // The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. + AnnotationMirror frameworkContractListAnno = + atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractListClass); + if (frameworkContractListAnno != null) { + List frameworkContractAnnoList = + atypeFactory.getContractListValues(frameworkContractListAnno); + for (AnnotationMirror a : frameworkContractAnnoList) { + result.addAll(getContract(kind, a, clazz)); + } + } - // Check for type-system specific annotations. - List> declAnnotations = - atypeFactory.getDeclAnnotationWithMetaAnnotation( - executableElement, kind.metaAnnotation); - for (IPair r : declAnnotations) { - AnnotationMirror anno = r.first; - // contractAnno is the meta-annotation on anno. - AnnotationMirror contractAnno = r.second; - AnnotationMirror enforcedQualifier = - getQualifierEnforcedByContractAnnotation(contractAnno, anno); - if (enforcedQualifier == null) { - continue; - } - List expressions = atypeFactory.getContractExpressions(kind, anno); - Collections.sort(expressions); - Boolean ensuresQualifierIfResult = atypeFactory.getEnsuresQualifierIfResult(kind, anno); + // Check for type-system specific annotations. + List> declAnnotations = + atypeFactory.getDeclAnnotationWithMetaAnnotation(executableElement, kind.metaAnnotation); + for (IPair r : declAnnotations) { + AnnotationMirror anno = r.first; + // contractAnno is the meta-annotation on anno. + AnnotationMirror contractAnno = r.second; + AnnotationMirror enforcedQualifier = + getQualifierEnforcedByContractAnnotation(contractAnno, anno); + if (enforcedQualifier == null) { + continue; + } + List expressions = atypeFactory.getContractExpressions(kind, anno); + Collections.sort(expressions); + Boolean ensuresQualifierIfResult = atypeFactory.getEnsuresQualifierIfResult(kind, anno); - for (String expr : expressions) { - T contract = - clazz.cast( - Contract.create( - kind, - expr, - enforcedQualifier, - anno, - ensuresQualifierIfResult)); - result.add(contract); - } - } - return result; + for (String expr : expressions) { + T contract = + clazz.cast( + Contract.create(kind, expr, enforcedQualifier, anno, ensuresQualifierIfResult)); + result.add(contract); + } } + return result; + } - /** - * Returns the contracts expressed by the given framework contract annotation. - * - * @param the type of {@link Contract} to return - * @param kind the kind of {@code contractAnnotation} - * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, {@link - * EnsuresQualifierIf}, or null - * @param clazz the class to determine the return type - * @return the contracts expressed by the given annotation, or the empty set if the argument is - * null - */ - private Set getContract( - Contract.Kind kind, @Nullable AnnotationMirror contractAnnotation, Class clazz) { - if (contractAnnotation == null) { - return Collections.emptySet(); - } + /** + * Returns the contracts expressed by the given framework contract annotation. + * + * @param the type of {@link Contract} to return + * @param kind the kind of {@code contractAnnotation} + * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, {@link + * EnsuresQualifierIf}, or null + * @param clazz the class to determine the return type + * @return the contracts expressed by the given annotation, or the empty set if the argument is + * null + */ + private Set getContract( + Contract.Kind kind, @Nullable AnnotationMirror contractAnnotation, Class clazz) { + if (contractAnnotation == null) { + return Collections.emptySet(); + } - AnnotationMirror enforcedQualifier = - getQualifierEnforcedByContractAnnotation(contractAnnotation); - if (enforcedQualifier == null) { - return Collections.emptySet(); - } + AnnotationMirror enforcedQualifier = + getQualifierEnforcedByContractAnnotation(contractAnnotation); + if (enforcedQualifier == null) { + return Collections.emptySet(); + } - List expressions = atypeFactory.getContractExpressions(contractAnnotation); - Collections.sort(expressions); + List expressions = atypeFactory.getContractExpressions(contractAnnotation); + Collections.sort(expressions); - Boolean ensuresQualifierIfResult = - atypeFactory.getEnsuresQualifierIfResult(kind, contractAnnotation); + Boolean ensuresQualifierIfResult = + atypeFactory.getEnsuresQualifierIfResult(kind, contractAnnotation); - Set result = new LinkedHashSet<>(); - for (String expr : expressions) { - T contract = - clazz.cast( - Contract.create( - kind, - expr, - enforcedQualifier, - contractAnnotation, - ensuresQualifierIfResult)); - result.add(contract); - } - return result; + Set result = new LinkedHashSet<>(); + for (String expr : expressions) { + T contract = + clazz.cast( + Contract.create( + kind, expr, enforcedQualifier, contractAnnotation, ensuresQualifierIfResult)); + result.add(contract); } + return result; + } - /** - * Returns the annotation mirror as specified by the {@code qualifier} element in {@code - * contractAnno}. May return null. - * - * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} - * @return the type annotation specified in {@code contractAnno.qualifier} - */ - private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( - AnnotationMirror contractAnno) { - return getQualifierEnforcedByContractAnnotation(contractAnno, null, null); - } + /** + * Returns the annotation mirror as specified by the {@code qualifier} element in {@code + * contractAnno}. May return null. + * + * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} + * @return the type annotation specified in {@code contractAnno.qualifier} + */ + private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( + AnnotationMirror contractAnno) { + return getQualifierEnforcedByContractAnnotation(contractAnno, null, null); + } - /** - * Returns the annotation mirror as specified by the {@code qualifier} element in {@code - * contractAnno}, with elements/arguments taken from {@code argumentAnno}. May return null. - * - * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} - * @param argumentAnno supplies the elements/fields in the return value - * @return the type annotation specified in {@code contractAnno.qualifier} - */ - private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( - AnnotationMirror contractAnno, AnnotationMirror argumentAnno) { + /** + * Returns the annotation mirror as specified by the {@code qualifier} element in {@code + * contractAnno}, with elements/arguments taken from {@code argumentAnno}. May return null. + * + * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} + * @param argumentAnno supplies the elements/fields in the return value + * @return the type annotation specified in {@code contractAnno.qualifier} + */ + private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( + AnnotationMirror contractAnno, AnnotationMirror argumentAnno) { - Map argumentRenaming = - makeArgumentRenaming(argumentAnno.getAnnotationType().asElement()); - return getQualifierEnforcedByContractAnnotation( - contractAnno, argumentAnno, argumentRenaming); - } + Map argumentRenaming = + makeArgumentRenaming(argumentAnno.getAnnotationType().asElement()); + return getQualifierEnforcedByContractAnnotation(contractAnno, argumentAnno, argumentRenaming); + } - /** - * Returns the annotation mirror as specified by the "qualifier" element in {@code - * contractAnno}. If {@code argumentAnno} is specified, then elements/arguments are copied from - * {@code argumentAnno} to the returned annotation, renamed according to {@code - * argumentRenaming}. If {@code argumentAnno} is not specified, the result has no - * elements/arguments; this may make it invalid. - * - *

This is a helper method. Use one of its overloads if possible. - * - * @param contractAnno a contract annotation, such as {@code @RequiresQualifier}, which has a - * {@code qualifier} element/field - * @param argumentAnno annotation containing the element {@code values}, or {@code null} - * @param argumentRenaming renaming of argument names, which maps from names in {@code - * argumentAnno} to names used in the returned annotation, or {@code null} - * @return a qualifier whose type is that of {@code contract.qualifier}, or an alias for it, or - * null if it is not a supported qualifier of the type system - */ - private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( - AnnotationMirror contractAnno, - @Nullable AnnotationMirror argumentAnno, - @Nullable Map argumentRenaming) { + /** + * Returns the annotation mirror as specified by the "qualifier" element in {@code contractAnno}. + * If {@code argumentAnno} is specified, then elements/arguments are copied from {@code + * argumentAnno} to the returned annotation, renamed according to {@code argumentRenaming}. If + * {@code argumentAnno} is not specified, the result has no elements/arguments; this may make it + * invalid. + * + *

This is a helper method. Use one of its overloads if possible. + * + * @param contractAnno a contract annotation, such as {@code @RequiresQualifier}, which has a + * {@code qualifier} element/field + * @param argumentAnno annotation containing the element {@code values}, or {@code null} + * @param argumentRenaming renaming of argument names, which maps from names in {@code + * argumentAnno} to names used in the returned annotation, or {@code null} + * @return a qualifier whose type is that of {@code contract.qualifier}, or an alias for it, or + * null if it is not a supported qualifier of the type system + */ + private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( + AnnotationMirror contractAnno, + @Nullable AnnotationMirror argumentAnno, + @Nullable Map argumentRenaming) { - @SuppressWarnings("deprecation") // permitted for use in the framework - Name c = AnnotationUtils.getElementValueClassName(contractAnno, "qualifier", false); + @SuppressWarnings("deprecation") // permitted for use in the framework + Name c = AnnotationUtils.getElementValueClassName(contractAnno, "qualifier", false); - AnnotationMirror anno; - if (argumentAnno == null || argumentRenaming.isEmpty()) { - // If there are no arguments, use factory method that allows caching - anno = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), c); - } else { - AnnotationBuilder builder = new AnnotationBuilder(atypeFactory.getProcessingEnv(), c); - builder.copyRenameElementValuesFromAnnotation(argumentAnno, argumentRenaming); - anno = builder.build(); - } + AnnotationMirror anno; + if (argumentAnno == null || argumentRenaming.isEmpty()) { + // If there are no arguments, use factory method that allows caching + anno = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), c); + } else { + AnnotationBuilder builder = new AnnotationBuilder(atypeFactory.getProcessingEnv(), c); + builder.copyRenameElementValuesFromAnnotation(argumentAnno, argumentRenaming); + anno = builder.build(); + } - if (atypeFactory.isSupportedQualifier(anno)) { - return anno; - } else { - AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); - if (atypeFactory.isSupportedQualifier(aliasedAnno)) { - return aliasedAnno; - } else { - return null; - } - } + if (atypeFactory.isSupportedQualifier(anno)) { + return anno; + } else { + AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); + if (atypeFactory.isSupportedQualifier(aliasedAnno)) { + return aliasedAnno; + } else { + return null; + } } + } - /** - * Makes a map from element names of a contract annotation to qualifier argument names, as - * defined by {@link QualifierArgument}. - * - *

Each element of {@code contractAnnoElement} that is annotated by {@link QualifierArgument} - * is mapped to the name specified by the value of {@link QualifierArgument}. If the value is - * not specified or is an empty string, then the element is mapped to an argument of the same - * name. - * - * @param contractAnnoElement the declaration of the contract annotation containing the elements - * @return map from the names of elements of {@code sourceArgumentNames} to the corresponding - * qualifier argument names - * @see QualifierArgument - */ - private Map makeArgumentRenaming(Element contractAnnoElement) { - HashMap argumentRenaming = new HashMap<>(); - for (ExecutableElement meth : - ElementFilter.methodsIn(contractAnnoElement.getEnclosedElements())) { - AnnotationMirror argumentAnnotation = - atypeFactory.getDeclAnnotation(meth, QualifierArgument.class); - if (argumentAnnotation != null) { - String sourceName = meth.getSimpleName().toString(); - String targetName = - AnnotationUtils.getElementValue( - argumentAnnotation, qualifierArgumentValueElement, String.class); - if (targetName == null || targetName.isEmpty()) { - targetName = sourceName; - } - argumentRenaming.put(sourceName, targetName); - } + /** + * Makes a map from element names of a contract annotation to qualifier argument names, as defined + * by {@link QualifierArgument}. + * + *

Each element of {@code contractAnnoElement} that is annotated by {@link QualifierArgument} + * is mapped to the name specified by the value of {@link QualifierArgument}. If the value is not + * specified or is an empty string, then the element is mapped to an argument of the same name. + * + * @param contractAnnoElement the declaration of the contract annotation containing the elements + * @return map from the names of elements of {@code sourceArgumentNames} to the corresponding + * qualifier argument names + * @see QualifierArgument + */ + private Map makeArgumentRenaming(Element contractAnnoElement) { + HashMap argumentRenaming = new HashMap<>(); + for (ExecutableElement meth : + ElementFilter.methodsIn(contractAnnoElement.getEnclosedElements())) { + AnnotationMirror argumentAnnotation = + atypeFactory.getDeclAnnotation(meth, QualifierArgument.class); + if (argumentAnnotation != null) { + String sourceName = meth.getSimpleName().toString(); + String targetName = + AnnotationUtils.getElementValue( + argumentAnnotation, qualifierArgumentValueElement, String.class); + if (targetName == null || targetName.isEmpty()) { + targetName = sourceName; } - return argumentRenaming; + argumentRenaming.put(sourceName, targetName); + } } + return argumentRenaming; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java index ccb58e04cd7..88223829a8d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java @@ -1,5 +1,18 @@ package org.checkerframework.framework.util; +import java.lang.annotation.Annotation; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.interning.qual.Interned; @@ -16,20 +29,6 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.StringsPlume; -import java.lang.annotation.Annotation; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - /** * This is the default implementation of {@link QualifierKindHierarchy}. * @@ -53,795 +52,785 @@ @AnnotatedFor("nullness") public class DefaultQualifierKindHierarchy implements QualifierKindHierarchy { - /** - * A mapping from canonical name of a qualifier class to the QualifierKind object representing - * that class. - */ - protected final Map<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind; - - /** - * A list of all {@link QualifierKind}s for this DefaultQualifierKindHierarchy, sorted in - * ascending order. - */ - protected final List qualifierKinds; - - /** All the qualifier kinds that are the top qualifier in their hierarchy. */ - private final Set tops; - - /** All the qualifier kinds that are the bottom qualifier in their hierarchy. */ - private final Set bottoms; - - /** - * Holds the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 - * and kind2. - */ - private final Map> lubs; - - /** - * Holds the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 - * and kind2. - */ - private final Map> glbs; - - @Override - public Set getTops() { - return tops; + /** + * A mapping from canonical name of a qualifier class to the QualifierKind object representing + * that class. + */ + protected final Map<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind; + + /** + * A list of all {@link QualifierKind}s for this DefaultQualifierKindHierarchy, sorted in + * ascending order. + */ + protected final List qualifierKinds; + + /** All the qualifier kinds that are the top qualifier in their hierarchy. */ + private final Set tops; + + /** All the qualifier kinds that are the bottom qualifier in their hierarchy. */ + private final Set bottoms; + + /** + * Holds the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 + * and kind2. + */ + private final Map> lubs; + + /** + * Holds the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 + * and kind2. + */ + private final Map> glbs; + + @Override + public Set getTops() { + return tops; + } + + @Override + public Set getBottoms() { + return bottoms; + } + + @Override + public @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2) { + @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in lubs. + QualifierKind result = lubs.get(q1).get(q2); + return result; + } + + @Override + public @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2) { + @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in glbs. + QualifierKind result = glbs.get(q1).get(q2); + return result; + } + + @Override + public List allQualifierKinds() { + return qualifierKinds; + } + + @Override + public QualifierKind getQualifierKind( + @UnknownInitialization(DefaultQualifierKindHierarchy.class) DefaultQualifierKindHierarchy this, + @CanonicalName String name) { + QualifierKind result = nameToQualifierKind.get(name); + if (result == null) { + throw new BugInCF("getQualifierKind(%s) => null", name); } - - @Override - public Set getBottoms() { - return bottoms; + return result; + } + + /** + * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its + * qualifier kinds. + * + * @param qualifierClasses all the classes of qualifiers supported by this hierarchy + */ + public DefaultQualifierKindHierarchy(Collection> qualifierClasses) { + this(qualifierClasses, null, null); + } + + /** + * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its + * qualifier kinds. + * + *

For some type systems, qualifiers may be added at run time, so the {@link SubtypeOf} + * meta-annotation on the bottom qualifier class cannot specify all other qualifiers. For those + * type systems, use this constructor. Otherwise, use {@link + * #DefaultQualifierKindHierarchy(Collection)}. + * + * @param qualifierClasses all the classes of qualifiers supported by this hierarchy + * @param bottom the bottom qualifier of this hierarchy + */ + public DefaultQualifierKindHierarchy( + Collection> qualifierClasses, + Class bottom) { + this(qualifierClasses, bottom, null); + } + + /** + * Private constructor that sets the bottom qualifier if {@code bottom} is nonnull. + * + * @param qualifierClasses all the classes of qualifiers supported by this hierarchy + * @param bottom the bottom qualifier of this hierarchy or null if bottom can be inferred from the + * meta-annotations + * @param voidParam void parameter to differentiate from {@link + * #DefaultQualifierKindHierarchy(Collection, Class)} + */ + private DefaultQualifierKindHierarchy( + Collection> qualifierClasses, + @Nullable Class bottom, + @SuppressWarnings("UnusedVariable") Void voidParam) { + this.nameToQualifierKind = createQualifierKinds(qualifierClasses); + this.qualifierKinds = new ArrayList<>(nameToQualifierKind.values()); + Collections.sort(qualifierKinds); + + Map> directSuperMap = createDirectSuperMap(); + if (bottom != null) { + setBottom(bottom, directSuperMap); } - - @Override - public @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2) { - @SuppressWarnings( - "nullness:dereference.of.nullable") // All QualifierKinds are keys in lubs. - QualifierKind result = lubs.get(q1).get(q2); - return result; + this.tops = createTopsSet(directSuperMap); + this.bottoms = createBottomsSet(directSuperMap); + initializePolymorphicQualifiers(); + initializeQualifierKindFields(directSuperMap); + this.lubs = createLubsMap(); + this.glbs = createGlbsMap(); + + verifyHierarchy(directSuperMap); + } + + /** + * Verifies that the {@link DefaultQualifierKindHierarchy} is a valid hierarchy. + * + * @param directSuperMap mapping from qualifier to its direct supertypes; used to verify that a + * polymorphic annotation does not have a {@link SubtypeOf} meta-annotation + * @throws TypeSystemError if the hierarchy isn't valid + */ + @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) + protected void verifyHierarchy( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + boolean isPoly = qualifierKind.isPoly(); + boolean hasSubtypeOfAnno = directSuperMap.containsKey(qualifierKind); + if (isPoly && hasSubtypeOfAnno) { + // Polymorphic qualifiers with upper and lower bounds are currently not supported. + throw new TypeSystemError( + "AnnotatedTypeFactory: " + + qualifierKind + + " is polymorphic and specifies super qualifiers.%nRemove the" + + " @PolymorphicQualifier or @SubtypeOf annotation from it."); + } else if (!isPoly && !hasSubtypeOfAnno) { + throw new TypeSystemError( + "AnnotatedTypeFactory: %s does not specify its super qualifiers.%nAdd an" + + " @SubtypeOf or @PolymorphicQualifier annotation to it,%nor if it is" + + " an alias, exclude it from `createSupportedTypeQualifiers()`.", + qualifierKind); + } else if (isPoly) { + if (qualifierKind.top == null) { + throw new TypeSystemError( + "PolymorphicQualifier, %s, has to specify a type hierarchy in its" + + " @PolymorphicQualifier meta-annotation, if more than one exists;" + + " top types: [%s].", + qualifierKind, StringsPlume.join(", ", tops)); + } else if (!tops.contains(qualifierKind.top)) { + throw new TypeSystemError( + "Polymorphic qualifier %s has invalid top %s. Top qualifiers: %s", + qualifierKind, qualifierKind.top, StringsPlume.join(", ", tops)); + } + } } - @Override - public @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2) { - @SuppressWarnings( - "nullness:dereference.of.nullable") // All QualifierKinds are keys in glbs. - QualifierKind result = glbs.get(q1).get(q2); - return result; + if (bottoms.size() != tops.size()) { + throw new TypeSystemError( + "Number of tops not equal to number of bottoms: Tops: [%s] Bottoms: [%s]", + StringsPlume.join(", ", tops), StringsPlume.join(", ", bottoms)); } - - @Override - public List allQualifierKinds() { - return qualifierKinds; + } + + /** + * Creates all QualifierKind objects for the given qualifier classes and adds them to + * qualifierClassMap. This method does not initialize all fields in the {@link QualifierKind}; + * that is done by {@link #initializeQualifierKindFields(Map)}. + * + * @param qualifierClasses classes of annotations that are type qualifiers + * @return a mapping from the canonical name of an annotation class to {@link QualifierKind} + */ + protected Map<@Interned @CanonicalName String, DefaultQualifierKind> createQualifierKinds( + @UnderInitialization DefaultQualifierKindHierarchy this, + Collection> qualifierClasses) { + TreeMap<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind = + new TreeMap<>(); + for (Class clazz : qualifierClasses) { + @SuppressWarnings("interning") // uniqueness is tested immediately below + @Interned DefaultQualifierKind qualifierKind = new DefaultQualifierKind(clazz); + if (nameToQualifierKind.containsKey(qualifierKind.getName())) { + throw new TypeSystemError("Duplicate QualifierKind " + qualifierKind.getName()); + } + nameToQualifierKind.put(qualifierKind.getName(), qualifierKind); } - - @Override - public QualifierKind getQualifierKind( - @UnknownInitialization(DefaultQualifierKindHierarchy.class) DefaultQualifierKindHierarchy this, - @CanonicalName String name) { - QualifierKind result = nameToQualifierKind.get(name); - if (result == null) { - throw new BugInCF("getQualifierKind(%s) => null", name); - } - return result; + return Collections.unmodifiableMap(nameToQualifierKind); + } + + /** + * Creates a mapping from a {@link QualifierKind} to a set of its direct super qualifier kinds. + * The direct super qualifier kinds do not contain the qualifier itself. This mapping is used to + * create the bottom set, to create the top set, and by {@link + * #initializeQualifierKindFields(Map)}. + * + *

This implementation uses the {@link SubtypeOf} meta-annotation. Subclasses may override this + * method to create the direct super map some other way. + * + *

Note that this method is called from the constructor when {@link #nameToQualifierKind} and + * {@link #qualifierKinds} are the only fields that have non-null values. This method is not + * static, so it can be overridden by subclasses. + * + * @return a mapping from each {@link QualifierKind} to a set of its direct super qualifiers + */ + @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) + protected Map> createDirectSuperMap( + @UnderInitialization DefaultQualifierKindHierarchy this) { + Map> directSuperMap = new TreeMap<>(); + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + SubtypeOf subtypeOfMetaAnno = + qualifierKind.getAnnotationClass().getAnnotation(SubtypeOf.class); + if (subtypeOfMetaAnno == null) { + // qualifierKind has no @SubtypeOf: it must be top or polymorphic + continue; + } + Set directSupers = new TreeSet<>(); + for (Class superClazz : subtypeOfMetaAnno.value()) { + String superName = QualifierKindHierarchy.annotationClassName(superClazz); + DefaultQualifierKind superQualifier = nameToQualifierKind.get(superName); + if (superQualifier == null) { + throw new TypeSystemError( + "In %s, @SubtypeOf(%s) argument isn't in the hierarchy." + + " Have you mis-defined createSupportedTypeQualifiers()? Qualifiers: [%s]", + qualifierKind, superName, StringsPlume.join(", ", qualifierKinds)); + } + directSupers.add(superQualifier); + } + directSuperMap.put(qualifierKind, directSupers); } - - /** - * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its - * qualifier kinds. - * - * @param qualifierClasses all the classes of qualifiers supported by this hierarchy - */ - public DefaultQualifierKindHierarchy(Collection> qualifierClasses) { - this(qualifierClasses, null, null); + return directSuperMap; + } + + /** + * This method sets bottom to the given class and modifies {@code directSuperMap} to add all + * leaves to its super qualifier kinds. Leaves are qualifier kinds that are not super qualifier + * kinds of another qualifier kind and are not polymorphic. + * + * @param bottom the class of the bottom qualifier in the hierarchy + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifiers; side-effected by this method + */ + @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) + private void setBottom( + @UnderInitialization DefaultQualifierKindHierarchy this, + Class bottom, + Map> directSuperMap) { + DefaultQualifierKind bottomKind = + nameToQualifierKind.get(QualifierKindHierarchy.annotationClassName(bottom)); + if (bottomKind == null) { + throw new TypeSystemError( + "QualifierKindHierarchy#setBottom: %s is not in the hierarchy", + bottom.getCanonicalName()); } - /** - * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its - * qualifier kinds. - * - *

For some type systems, qualifiers may be added at run time, so the {@link SubtypeOf} - * meta-annotation on the bottom qualifier class cannot specify all other qualifiers. For those - * type systems, use this constructor. Otherwise, use {@link - * #DefaultQualifierKindHierarchy(Collection)}. - * - * @param qualifierClasses all the classes of qualifiers supported by this hierarchy - * @param bottom the bottom qualifier of this hierarchy - */ - public DefaultQualifierKindHierarchy( - Collection> qualifierClasses, - Class bottom) { - this(qualifierClasses, bottom, null); + Set leaves = new TreeSet<>(qualifierKinds); + leaves.remove(bottomKind); + directSuperMap.forEach((sub, supers) -> leaves.removeAll(supers)); + Set bottomDirectSuperQuals = directSuperMap.get(bottomKind); + if (bottomDirectSuperQuals == null) { + directSuperMap.put(bottomKind, leaves); + } else { + bottomDirectSuperQuals.addAll(leaves); } - - /** - * Private constructor that sets the bottom qualifier if {@code bottom} is nonnull. - * - * @param qualifierClasses all the classes of qualifiers supported by this hierarchy - * @param bottom the bottom qualifier of this hierarchy or null if bottom can be inferred from - * the meta-annotations - * @param voidParam void parameter to differentiate from {@link - * #DefaultQualifierKindHierarchy(Collection, Class)} - */ - private DefaultQualifierKindHierarchy( - Collection> qualifierClasses, - @Nullable Class bottom, - @SuppressWarnings("UnusedVariable") Void voidParam) { - this.nameToQualifierKind = createQualifierKinds(qualifierClasses); - this.qualifierKinds = new ArrayList<>(nameToQualifierKind.values()); - Collections.sort(qualifierKinds); - - Map> directSuperMap = - createDirectSuperMap(); - if (bottom != null) { - setBottom(bottom, directSuperMap); - } - this.tops = createTopsSet(directSuperMap); - this.bottoms = createBottomsSet(directSuperMap); - initializePolymorphicQualifiers(); - initializeQualifierKindFields(directSuperMap); - this.lubs = createLubsMap(); - this.glbs = createGlbsMap(); - - verifyHierarchy(directSuperMap); + } + + /** + * Creates the set of top {@link QualifierKind}s by searching {@code directSuperMap} for qualifier + * kinds without any direct super qualifier kinds. + * + *

Subclasses should override {@link #createDirectSuperMap} to change the tops and not this + * method, because other methods expect the directSuperMap to be complete. + * + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + * @return the set of top {@link QualifierKind}s + */ + private Set createTopsSet( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + Set tops = new TreeSet<>(); + directSuperMap.forEach( + (qualifierKind, superQuals) -> { + if (superQuals.isEmpty()) { + tops.add(qualifierKind); + } + }); + return tops; + } + + /** + * Creates the set of bottom {@link QualifierKind}s by searching {@code directSuperMap} for + * qualifiers that are not a direct super qualifier kind of another qualifier kind. + * + *

Subclasses should override {@link #createDirectSuperMap} or {@link #setBottom} to change the + * bottoms and not this method, because other methods expect the directSuperMap to be complete. + * + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + * @return the set of bottom {@link QualifierKind}s + */ + private Set createBottomsSet( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + Set bottoms = new HashSet<>(directSuperMap.keySet()); + for (Set superKinds : directSuperMap.values()) { + bottoms.removeAll(superKinds); } - - /** - * Verifies that the {@link DefaultQualifierKindHierarchy} is a valid hierarchy. - * - * @param directSuperMap mapping from qualifier to its direct supertypes; used to verify that a - * polymorphic annotation does not have a {@link SubtypeOf} meta-annotation - * @throws TypeSystemError if the hierarchy isn't valid - */ - @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) - protected void verifyHierarchy( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - boolean isPoly = qualifierKind.isPoly(); - boolean hasSubtypeOfAnno = directSuperMap.containsKey(qualifierKind); - if (isPoly && hasSubtypeOfAnno) { - // Polymorphic qualifiers with upper and lower bounds are currently not supported. - throw new TypeSystemError( - "AnnotatedTypeFactory: " - + qualifierKind - + " is polymorphic and specifies super qualifiers.%nRemove the" - + " @PolymorphicQualifier or @SubtypeOf annotation from it."); - } else if (!isPoly && !hasSubtypeOfAnno) { - throw new TypeSystemError( - "AnnotatedTypeFactory: %s does not specify its super qualifiers.%nAdd an" - + " @SubtypeOf or @PolymorphicQualifier annotation to it,%nor if it is" - + " an alias, exclude it from `createSupportedTypeQualifiers()`.", - qualifierKind); - } else if (isPoly) { - if (qualifierKind.top == null) { - throw new TypeSystemError( - "PolymorphicQualifier, %s, has to specify a type hierarchy in its" - + " @PolymorphicQualifier meta-annotation, if more than one exists;" - + " top types: [%s].", - qualifierKind, StringsPlume.join(", ", tops)); - } else if (!tops.contains(qualifierKind.top)) { - throw new TypeSystemError( - "Polymorphic qualifier %s has invalid top %s. Top qualifiers: %s", - qualifierKind, qualifierKind.top, StringsPlume.join(", ", tops)); - } - } - } - - if (bottoms.size() != tops.size()) { + return bottoms; + } + + /** + * Iterates over all the qualifier kinds and adds all polymorphic qualifier kinds to + * polymorphicQualifiers. Also sets {@link DefaultQualifierKind#poly} and {@link + * DefaultQualifierKind#top} for the polymorphic qualifiers, and sets {@link + * DefaultQualifierKind#poly} for the top qualifiers. + * + *

Requires that tops has been initialized. + */ + @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds", "this.tops"}) + protected void initializePolymorphicQualifiers( + @UnderInitialization DefaultQualifierKindHierarchy this) { + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + Class clazz = qualifierKind.getAnnotationClass(); + PolymorphicQualifier polyMetaAnno = clazz.getAnnotation(PolymorphicQualifier.class); + if (polyMetaAnno == null) { + continue; + } + qualifierKind.poly = qualifierKind; + String topName = QualifierKindHierarchy.annotationClassName(polyMetaAnno.value()); + if (nameToQualifierKind.containsKey(topName)) { + qualifierKind.top = nameToQualifierKind.get(topName); + } else if (topName.equals(Annotation.class.getCanonicalName())) { + // Annotation.class is the default value of PolymorphicQualifier. If it is used, + // then there must be exactly one top. + if (tops.size() == 1) { + qualifierKind.top = tops.iterator().next(); + } else { + throw new TypeSystemError( + "Polymorphic qualifier %s did not specify a top annotation class. Tops:" + " [%s]", + qualifierKind, StringsPlume.join(", ", tops)); + } + } else { + throw new TypeSystemError( + "Polymorphic qualifier %s's top, %s, is not a qualifier.", qualifierKind, topName); + } + qualifierKind.strictSuperTypes = Collections.singleton(qualifierKind.top); + qualifierKind.top.poly = qualifierKind; + } + } + + /** + * For each qualifier kind in {@code directSuperMap}, initializes {@link + * DefaultQualifierKind#strictSuperTypes}, {@link DefaultQualifierKind#top}, {@link + * DefaultQualifierKind#bottom}, and {@link DefaultQualifierKind#poly}. + * + *

Requires tops, bottoms, and polymorphicQualifiers to be initialized. + * + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + */ + @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) + protected void initializeQualifierKindFields( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + for (DefaultQualifierKind qualifierKind : directSuperMap.keySet()) { + if (!qualifierKind.isPoly()) { + qualifierKind.strictSuperTypes = findAllTheSupers(qualifierKind, directSuperMap); + } + } + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + for (DefaultQualifierKind top : tops) { + if (qualifierKind.isSubtypeOf(top)) { + if (qualifierKind.top == null) { + qualifierKind.top = top; + } else if (qualifierKind.top != top) { throw new TypeSystemError( - "Number of tops not equal to number of bottoms: Tops: [%s] Bottoms: [%s]", - StringsPlume.join(", ", tops), StringsPlume.join(", ", bottoms)); - } + "Multiple tops found for qualifier %s. Tops: %s and %s.", + qualifierKind, top, qualifierKind.top); + } + } + } + if (qualifierKind.top == null) { + throw new TypeSystemError( + "Qualifier %s isn't a subtype of any top. tops = %s", qualifierKind, tops); + } + qualifierKind.poly = qualifierKind.top.poly; } - - /** - * Creates all QualifierKind objects for the given qualifier classes and adds them to - * qualifierClassMap. This method does not initialize all fields in the {@link QualifierKind}; - * that is done by {@link #initializeQualifierKindFields(Map)}. - * - * @param qualifierClasses classes of annotations that are type qualifiers - * @return a mapping from the canonical name of an annotation class to {@link QualifierKind} - */ - protected Map<@Interned @CanonicalName String, DefaultQualifierKind> createQualifierKinds( - @UnderInitialization DefaultQualifierKindHierarchy this, - Collection> qualifierClasses) { - TreeMap<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind = - new TreeMap<>(); - for (Class clazz : qualifierClasses) { - @SuppressWarnings("interning") // uniqueness is tested immediately below - @Interned DefaultQualifierKind qualifierKind = new DefaultQualifierKind(clazz); - if (nameToQualifierKind.containsKey(qualifierKind.getName())) { - throw new TypeSystemError("Duplicate QualifierKind " + qualifierKind.getName()); - } - nameToQualifierKind.put(qualifierKind.getName(), qualifierKind); - } - return Collections.unmodifiableMap(nameToQualifierKind); + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + for (DefaultQualifierKind bot : bottoms) { + if (bot.top != qualifierKind.top) { + continue; + } + if (qualifierKind.bottom == null) { + qualifierKind.bottom = bot; + } else if (qualifierKind.top != bot) { + throw new TypeSystemError( + "Multiple bottoms found for qualifier %s. Bottoms: %s and %s.", + qualifierKind, bot, qualifierKind.bottom); + } + if (qualifierKind.isPoly()) { + assert bot.strictSuperTypes != null + : "@AssumeAssertion(nullness): strictSuperTypes should be nonnull."; + bot.strictSuperTypes.add(qualifierKind); + } + } + if (qualifierKind.bottom == null) { + throw new TypeSystemError( + "Cannot find a bottom qualifier for %s. bottoms = %s", qualifierKind, bottoms); + } } - - /** - * Creates a mapping from a {@link QualifierKind} to a set of its direct super qualifier kinds. - * The direct super qualifier kinds do not contain the qualifier itself. This mapping is used to - * create the bottom set, to create the top set, and by {@link - * #initializeQualifierKindFields(Map)}. - * - *

This implementation uses the {@link SubtypeOf} meta-annotation. Subclasses may override - * this method to create the direct super map some other way. - * - *

Note that this method is called from the constructor when {@link #nameToQualifierKind} and - * {@link #qualifierKinds} are the only fields that have non-null values. This method is not - * static, so it can be overridden by subclasses. - * - * @return a mapping from each {@link QualifierKind} to a set of its direct super qualifiers - */ - @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) - protected Map> createDirectSuperMap( - @UnderInitialization DefaultQualifierKindHierarchy this) { - Map> directSuperMap = new TreeMap<>(); - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - SubtypeOf subtypeOfMetaAnno = - qualifierKind.getAnnotationClass().getAnnotation(SubtypeOf.class); - if (subtypeOfMetaAnno == null) { - // qualifierKind has no @SubtypeOf: it must be top or polymorphic - continue; - } - Set directSupers = new TreeSet<>(); - for (Class superClazz : subtypeOfMetaAnno.value()) { - String superName = QualifierKindHierarchy.annotationClassName(superClazz); - DefaultQualifierKind superQualifier = nameToQualifierKind.get(superName); - if (superQualifier == null) { - throw new TypeSystemError( - "In %s, @SubtypeOf(%s) argument isn't in the hierarchy." - + " Have you mis-defined createSupportedTypeQualifiers()? Qualifiers: [%s]", - qualifierKind, superName, StringsPlume.join(", ", qualifierKinds)); - } - directSupers.add(superQualifier); - } - directSuperMap.put(qualifierKind, directSupers); - } - return directSuperMap; + } + + /** + * Returns the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}. + * + * @param qualifierKind the qualifier kind whose super types should be returned + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + * @return the set of all qualifier kinds that are a strict supertype of {@code qualifierKind} + */ + private Set findAllTheSupers( + @UnderInitialization DefaultQualifierKindHierarchy this, + @KeyFor("#2") QualifierKind qualifierKind, + Map> directSuperMap) { + + Set allSupers = new TreeSet<>(directSuperMap.get(qualifierKind)); + + // Visit every super qualifier kind and add its super qualifier kinds to allSupers. + Queue toVisit = new ArrayDeque<>(directSuperMap.get(qualifierKind)); + Set visited = new HashSet<>(); + while (!toVisit.isEmpty()) { + DefaultQualifierKind superQualKind = toVisit.remove(); + if (superQualKind == qualifierKind) { + throw new TypeSystemError("Cycle in hierarchy: %s", qualifierKind); + } + + if (!visited.add(superQualKind) || superQualKind.isPoly()) { + continue; + } + + Set superSuperQuals = directSuperMap.get(superQualKind); + if (superSuperQuals == null) { + throw new TypeSystemError( + superQualKind + + " is not a key in the directSuperMap." + + " Does it have a @SubtypeOf annotation?"); + } + toVisit.addAll(superSuperQuals); + allSupers.addAll(superSuperQuals); + } + return allSupers; + } + + /** + * Creates the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 + * and kind2. + * + * @return a mapping of lubs + */ + @RequiresNonNull("this.qualifierKinds") + protected Map> createLubsMap( + @UnderInitialization DefaultQualifierKindHierarchy this) { + Map> lubs = new HashMap<>(); + for (QualifierKind qual1 : qualifierKinds) { + for (QualifierKind qual2 : qualifierKinds) { + if (qual1.getTop() != qual2.getTop()) { + continue; + } + QualifierKind lub = findLub(qual1, qual2); + addToMapOfMap(lubs, qual1, qual2, lub, "lub"); + addToMapOfMap(lubs, qual2, qual1, lub, "lub"); + } + } + return lubs; + } + + /** + * Returns the least upper bound of {@code qual1} and {@code qual2}. + * + * @param qual1 a qualifier kind + * @param qual2 a qualifier kind + * @return the least upper bound of {@code qual1} and {@code qual2} + */ + private QualifierKind findLub( + @UnderInitialization DefaultQualifierKindHierarchy this, + QualifierKind qual1, + QualifierKind qual2) { + if (qual1 == qual2) { + return qual1; + } else if (qual1.isSubtypeOf(qual2)) { + return qual2; + } else if (qual2.isSubtypeOf(qual1)) { + return qual1; + } + Set allSuperTypes = new TreeSet<>(qual1.getStrictSuperTypes()); + Set qual2StrictSuperTypes = qual2.getStrictSuperTypes(); + allSuperTypes.retainAll(qual2StrictSuperTypes); + Set lubs = findLowestQualifiers(allSuperTypes); + if (lubs.size() != 1) { + throw new TypeSystemError( + "lub(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", lubs)); + } + QualifierKind lub = lubs.iterator().next(); + if (lub.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { + throw new TypeSystemError("lub(%s, %s) can't be poly: %s", qual1, qual2, lub); + } + return lub; + } + + /** + * Returns the lowest qualifiers in the passed set. + * + * @param qualifierKinds a set of qualifiers + * @return the lowest qualifiers in the passed set + */ + protected static Set findLowestQualifiers(Set qualifierKinds) { + Set lowestQualifiers = new TreeSet<>(qualifierKinds); + for (QualifierKind a1 : qualifierKinds) { + lowestQualifiers.removeIf(a2 -> a1 != a2 && a1.isSubtypeOf(a2)); } + return lowestQualifiers; + } + + /** + * Creates the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 + * and kind2. + * + * @return a mapping of glb + */ + @RequiresNonNull("this.qualifierKinds") + protected Map> createGlbsMap( + @UnderInitialization DefaultQualifierKindHierarchy this) { + Map> glbs = new TreeMap<>(); + for (QualifierKind qual1 : qualifierKinds) { + for (QualifierKind qual2 : qualifierKinds) { + if (qual1.getTop() != qual2.getTop()) { + continue; + } + QualifierKind glb = findGlb(qual1, qual2); + addToMapOfMap(glbs, qual1, qual2, glb, "glb"); + addToMapOfMap(glbs, qual2, qual1, glb, "glb"); + } + } + return glbs; + } + + /** + * Returns the greatest lower bound of {@code qual1} and {@code qual2}. + * + * @param qual1 a qualifier kind + * @param qual2 a qualifier kind + * @return the greatest lower bound of {@code qual1} and {@code qual2} + */ + @RequiresNonNull("this.qualifierKinds") + private QualifierKind findGlb( + @UnderInitialization DefaultQualifierKindHierarchy this, + QualifierKind qual1, + QualifierKind qual2) { + if (qual1 == qual2) { + return qual1; + } else if (qual1.isSubtypeOf(qual2)) { + return qual1; + } else if (qual2.isSubtypeOf(qual1)) { + return qual2; + } + Set allSubTypes = new TreeSet<>(); + for (QualifierKind qualifierKind : qualifierKinds) { + if (qualifierKind.isSubtypeOf(qual1) && qualifierKind.isSubtypeOf(qual2)) { + allSubTypes.add(qualifierKind); + } + } + Set glbs = findHighestQualifiers(allSubTypes); + if (glbs.size() != 1) { + throw new TypeSystemError( + "glb(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", glbs)); + } + QualifierKind glb = glbs.iterator().next(); + if (glb.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { + throw new TypeSystemError("glb(%s, %s) can't be poly: %s", qual1, qual2, glb); + } + return glb; + } + + /** + * Returns the highest qualifiers in the passed set. + * + * @param qualifierKinds a set of qualifiers + * @return the highest qualifiers in the passed set + */ + protected static Set findHighestQualifiers(Set qualifierKinds) { + Set highestQualifiers = new TreeSet<>(qualifierKinds); + for (QualifierKind a1 : qualifierKinds) { + highestQualifiers.removeIf(a2 -> a1 != a2 && a2.isSubtypeOf(a1)); + } + return highestQualifiers; + } + + /** + * Add Key: qual1, Value: (Key: qual2, Value: value) to {@code map}. If already in map, throw an + * exception if value is different. + * + * @param map mapping to side-effect + * @param qual1 the first qualifier kind + * @param qual2 the second qualifier kind + * @param value the value to add + * @param operationName "lub" or "glb"; used only for error messages + */ + private static void addToMapOfMap( + Map> map, + QualifierKind qual1, + QualifierKind qual2, + QualifierKind value, + String operationName) { + Map qual1Map = map.computeIfAbsent(qual1, k -> new HashMap<>()); + QualifierKind existingValue = qual1Map.get(qual2); + if (existingValue == null) { + qual1Map.put(qual2, value); + } else { + if (existingValue != value) { + throw new TypeSystemError( + "Multiple %ss for qualifiers %s and %s. Found map %s and %s", + operationName, qual1, qual2, value, existingValue); + } + } + } - /** - * This method sets bottom to the given class and modifies {@code directSuperMap} to add all - * leaves to its super qualifier kinds. Leaves are qualifier kinds that are not super qualifier - * kinds of another qualifier kind and are not polymorphic. - * - * @param bottom the class of the bottom qualifier in the hierarchy - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifiers; side-effected by this method - */ - @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) - private void setBottom( - @UnderInitialization DefaultQualifierKindHierarchy this, - Class bottom, - Map> directSuperMap) { - DefaultQualifierKind bottomKind = - nameToQualifierKind.get(QualifierKindHierarchy.annotationClassName(bottom)); - if (bottomKind == null) { - throw new TypeSystemError( - "QualifierKindHierarchy#setBottom: %s is not in the hierarchy", - bottom.getCanonicalName()); - } + /** + * The default implementation of {@link QualifierKind}. + * + *

The fields in this class that refer to {@link QualifierKind}s are set when creating the + * {@link DefaultQualifierKindHierarchy}. So the getter methods for these fields should not be + * called until after the {@code DefaultQualifierKindHierarchy} is created. + */ + @AnnotatedFor("nullness") + public @Interned static class DefaultQualifierKind implements QualifierKind { - Set leaves = new TreeSet<>(qualifierKinds); - leaves.remove(bottomKind); - directSuperMap.forEach((sub, supers) -> leaves.removeAll(supers)); - Set bottomDirectSuperQuals = directSuperMap.get(bottomKind); - if (bottomDirectSuperQuals == null) { - directSuperMap.put(bottomKind, leaves); - } else { - bottomDirectSuperQuals.addAll(leaves); - } - } + /** The canonical name of the annotation class of this. */ + private final @Interned @CanonicalName String name; - /** - * Creates the set of top {@link QualifierKind}s by searching {@code directSuperMap} for - * qualifier kinds without any direct super qualifier kinds. - * - *

Subclasses should override {@link #createDirectSuperMap} to change the tops and not this - * method, because other methods expect the directSuperMap to be complete. - * - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - * @return the set of top {@link QualifierKind}s - */ - private Set createTopsSet( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - Set tops = new TreeSet<>(); - directSuperMap.forEach( - (qualifierKind, superQuals) -> { - if (superQuals.isEmpty()) { - tops.add(qualifierKind); - } - }); - return tops; - } + /** The annotation class for this. */ + private final Class clazz; - /** - * Creates the set of bottom {@link QualifierKind}s by searching {@code directSuperMap} for - * qualifiers that are not a direct super qualifier kind of another qualifier kind. - * - *

Subclasses should override {@link #createDirectSuperMap} or {@link #setBottom} to change - * the bottoms and not this method, because other methods expect the directSuperMap to be - * complete. - * - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - * @return the set of bottom {@link QualifierKind}s - */ - private Set createBottomsSet( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - Set bottoms = new HashSet<>(directSuperMap.keySet()); - for (Set superKinds : directSuperMap.values()) { - bottoms.removeAll(superKinds); - } - return bottoms; - } + /** True if the annotation class of this has annotation elements/arguments. */ + private final boolean hasElements; - /** - * Iterates over all the qualifier kinds and adds all polymorphic qualifier kinds to - * polymorphicQualifiers. Also sets {@link DefaultQualifierKind#poly} and {@link - * DefaultQualifierKind#top} for the polymorphic qualifiers, and sets {@link - * DefaultQualifierKind#poly} for the top qualifiers. - * - *

Requires that tops has been initialized. - */ - @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds", "this.tops"}) - protected void initializePolymorphicQualifiers( - @UnderInitialization DefaultQualifierKindHierarchy this) { - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - Class clazz = qualifierKind.getAnnotationClass(); - PolymorphicQualifier polyMetaAnno = clazz.getAnnotation(PolymorphicQualifier.class); - if (polyMetaAnno == null) { - continue; - } - qualifierKind.poly = qualifierKind; - String topName = QualifierKindHierarchy.annotationClassName(polyMetaAnno.value()); - if (nameToQualifierKind.containsKey(topName)) { - qualifierKind.top = nameToQualifierKind.get(topName); - } else if (topName.equals(Annotation.class.getCanonicalName())) { - // Annotation.class is the default value of PolymorphicQualifier. If it is used, - // then there must be exactly one top. - if (tops.size() == 1) { - qualifierKind.top = tops.iterator().next(); - } else { - throw new TypeSystemError( - "Polymorphic qualifier %s did not specify a top annotation class. Tops:" - + " [%s]", - qualifierKind, StringsPlume.join(", ", tops)); - } - } else { - throw new TypeSystemError( - "Polymorphic qualifier %s's top, %s, is not a qualifier.", - qualifierKind, topName); - } - qualifierKind.strictSuperTypes = Collections.singleton(qualifierKind.top); - qualifierKind.top.poly = qualifierKind; - } - } + /** The top of the hierarchy to which this belongs. */ + // Set while creating the QualifierKindHierarchy. + protected @MonotonicNonNull DefaultQualifierKind top; - /** - * For each qualifier kind in {@code directSuperMap}, initializes {@link - * DefaultQualifierKind#strictSuperTypes}, {@link DefaultQualifierKind#top}, {@link - * DefaultQualifierKind#bottom}, and {@link DefaultQualifierKind#poly}. - * - *

Requires tops, bottoms, and polymorphicQualifiers to be initialized. - * - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - */ - @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) - protected void initializeQualifierKindFields( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - for (DefaultQualifierKind qualifierKind : directSuperMap.keySet()) { - if (!qualifierKind.isPoly()) { - qualifierKind.strictSuperTypes = findAllTheSupers(qualifierKind, directSuperMap); - } - } - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - for (DefaultQualifierKind top : tops) { - if (qualifierKind.isSubtypeOf(top)) { - if (qualifierKind.top == null) { - qualifierKind.top = top; - } else if (qualifierKind.top != top) { - throw new TypeSystemError( - "Multiple tops found for qualifier %s. Tops: %s and %s.", - qualifierKind, top, qualifierKind.top); - } - } - } - if (qualifierKind.top == null) { - throw new TypeSystemError( - "Qualifier %s isn't a subtype of any top. tops = %s", qualifierKind, tops); - } - qualifierKind.poly = qualifierKind.top.poly; - } - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - for (DefaultQualifierKind bot : bottoms) { - if (bot.top != qualifierKind.top) { - continue; - } - if (qualifierKind.bottom == null) { - qualifierKind.bottom = bot; - } else if (qualifierKind.top != bot) { - throw new TypeSystemError( - "Multiple bottoms found for qualifier %s. Bottoms: %s and %s.", - qualifierKind, bot, qualifierKind.bottom); - } - if (qualifierKind.isPoly()) { - assert bot.strictSuperTypes != null - : "@AssumeAssertion(nullness): strictSuperTypes should be nonnull."; - bot.strictSuperTypes.add(qualifierKind); - } - } - if (qualifierKind.bottom == null) { - throw new TypeSystemError( - "Cannot find a bottom qualifier for %s. bottoms = %s", - qualifierKind, bottoms); - } - } - } + /** The bottom of the hierarchy to which this belongs. */ + // Set while creating the QualifierKindHierarchy. + protected @MonotonicNonNull DefaultQualifierKind bottom; - /** - * Returns the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}. - * - * @param qualifierKind the qualifier kind whose super types should be returned - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - * @return the set of all qualifier kinds that are a strict supertype of {@code qualifierKind} - */ - private Set findAllTheSupers( - @UnderInitialization DefaultQualifierKindHierarchy this, - @KeyFor("#2") QualifierKind qualifierKind, - Map> directSuperMap) { - - Set allSupers = new TreeSet<>(directSuperMap.get(qualifierKind)); - - // Visit every super qualifier kind and add its super qualifier kinds to allSupers. - Queue toVisit = new ArrayDeque<>(directSuperMap.get(qualifierKind)); - Set visited = new HashSet<>(); - while (!toVisit.isEmpty()) { - DefaultQualifierKind superQualKind = toVisit.remove(); - if (superQualKind == qualifierKind) { - throw new TypeSystemError("Cycle in hierarchy: %s", qualifierKind); - } - - if (!visited.add(superQualKind) || superQualKind.isPoly()) { - continue; - } - - Set superSuperQuals = directSuperMap.get(superQualKind); - if (superSuperQuals == null) { - throw new TypeSystemError( - superQualKind - + " is not a key in the directSuperMap." - + " Does it have a @SubtypeOf annotation?"); - } - toVisit.addAll(superSuperQuals); - allSupers.addAll(superSuperQuals); - } - return allSupers; - } + /** The polymorphic qualifier of the hierarchy to which this belongs. */ + // Set while creating the QualifierKindHierarchy. + protected @Nullable DefaultQualifierKind poly; /** - * Creates the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of - * kind1 and kind2. - * - * @return a mapping of lubs + * All the qualifier kinds that are a strict super qualifier kind of this. Does not include this + * qualifier kind itself. */ - @RequiresNonNull("this.qualifierKinds") - protected Map> createLubsMap( - @UnderInitialization DefaultQualifierKindHierarchy this) { - Map> lubs = new HashMap<>(); - for (QualifierKind qual1 : qualifierKinds) { - for (QualifierKind qual2 : qualifierKinds) { - if (qual1.getTop() != qual2.getTop()) { - continue; - } - QualifierKind lub = findLub(qual1, qual2); - addToMapOfMap(lubs, qual1, qual2, lub, "lub"); - addToMapOfMap(lubs, qual2, qual1, lub, "lub"); - } - } - return lubs; - } + // Set while creating the QualifierKindHierarchy. + protected @MonotonicNonNull Set strictSuperTypes; /** - * Returns the least upper bound of {@code qual1} and {@code qual2}. + * Creates a {@link DefaultQualifierKind} for the given annotation class. * - * @param qual1 a qualifier kind - * @param qual2 a qualifier kind - * @return the least upper bound of {@code qual1} and {@code qual2} + * @param clazz annotation class for a qualifier */ - private QualifierKind findLub( - @UnderInitialization DefaultQualifierKindHierarchy this, - QualifierKind qual1, - QualifierKind qual2) { - if (qual1 == qual2) { - return qual1; - } else if (qual1.isSubtypeOf(qual2)) { - return qual2; - } else if (qual2.isSubtypeOf(qual1)) { - return qual1; - } - Set allSuperTypes = new TreeSet<>(qual1.getStrictSuperTypes()); - Set qual2StrictSuperTypes = qual2.getStrictSuperTypes(); - allSuperTypes.retainAll(qual2StrictSuperTypes); - Set lubs = findLowestQualifiers(allSuperTypes); - if (lubs.size() != 1) { - throw new TypeSystemError( - "lub(%s, %s) should have size 1: [%s]", - qual1, qual2, StringsPlume.join(", ", lubs)); - } - QualifierKind lub = lubs.iterator().next(); - if (lub.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { - throw new TypeSystemError("lub(%s, %s) can't be poly: %s", qual1, qual2, lub); - } - return lub; + DefaultQualifierKind(Class clazz) { + this.clazz = clazz; + this.hasElements = clazz.getDeclaredMethods().length != 0; + this.name = QualifierKindHierarchy.annotationClassName(clazz).intern(); + this.poly = null; } - /** - * Returns the lowest qualifiers in the passed set. - * - * @param qualifierKinds a set of qualifiers - * @return the lowest qualifiers in the passed set - */ - protected static Set findLowestQualifiers(Set qualifierKinds) { - Set lowestQualifiers = new TreeSet<>(qualifierKinds); - for (QualifierKind a1 : qualifierKinds) { - lowestQualifiers.removeIf(a2 -> a1 != a2 && a1.isSubtypeOf(a2)); - } - return lowestQualifiers; + @Override + public @Interned @CanonicalName String getName() { + return name; } - /** - * Creates the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of - * kind1 and kind2. - * - * @return a mapping of glb - */ - @RequiresNonNull("this.qualifierKinds") - protected Map> createGlbsMap( - @UnderInitialization DefaultQualifierKindHierarchy this) { - Map> glbs = new TreeMap<>(); - for (QualifierKind qual1 : qualifierKinds) { - for (QualifierKind qual2 : qualifierKinds) { - if (qual1.getTop() != qual2.getTop()) { - continue; - } - QualifierKind glb = findGlb(qual1, qual2); - addToMapOfMap(glbs, qual1, qual2, glb, "glb"); - addToMapOfMap(glbs, qual2, qual1, glb, "glb"); - } - } - return glbs; + @Override + public Class getAnnotationClass() { + return clazz; } - /** - * Returns the greatest lower bound of {@code qual1} and {@code qual2}. - * - * @param qual1 a qualifier kind - * @param qual2 a qualifier kind - * @return the greatest lower bound of {@code qual1} and {@code qual2} - */ - @RequiresNonNull("this.qualifierKinds") - private QualifierKind findGlb( - @UnderInitialization DefaultQualifierKindHierarchy this, - QualifierKind qual1, - QualifierKind qual2) { - if (qual1 == qual2) { - return qual1; - } else if (qual1.isSubtypeOf(qual2)) { - return qual1; - } else if (qual2.isSubtypeOf(qual1)) { - return qual2; - } - Set allSubTypes = new TreeSet<>(); - for (QualifierKind qualifierKind : qualifierKinds) { - if (qualifierKind.isSubtypeOf(qual1) && qualifierKind.isSubtypeOf(qual2)) { - allSubTypes.add(qualifierKind); - } - } - Set glbs = findHighestQualifiers(allSubTypes); - if (glbs.size() != 1) { - throw new TypeSystemError( - "glb(%s, %s) should have size 1: [%s]", - qual1, qual2, StringsPlume.join(", ", glbs)); - } - QualifierKind glb = glbs.iterator().next(); - if (glb.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { - throw new TypeSystemError("glb(%s, %s) can't be poly: %s", qual1, qual2, glb); - } - return glb; + @Override + public QualifierKind getTop() { + if (top == null) { + throw new BugInCF( + "DefaultQualifierKindHierarchy#getTop: Top is null for QualifierKind %s." + + " Don't call this method during initialization of" + + " DefaultQualifierKindHierarchy.", + name); + } + return top; } - /** - * Returns the highest qualifiers in the passed set. - * - * @param qualifierKinds a set of qualifiers - * @return the highest qualifiers in the passed set - */ - protected static Set findHighestQualifiers(Set qualifierKinds) { - Set highestQualifiers = new TreeSet<>(qualifierKinds); - for (QualifierKind a1 : qualifierKinds) { - highestQualifiers.removeIf(a2 -> a1 != a2 && a2.isSubtypeOf(a1)); - } - return highestQualifiers; + @Override + public boolean isTop() { + return this.top == this; } - /** - * Add Key: qual1, Value: (Key: qual2, Value: value) to {@code map}. If already in map, throw an - * exception if value is different. - * - * @param map mapping to side-effect - * @param qual1 the first qualifier kind - * @param qual2 the second qualifier kind - * @param value the value to add - * @param operationName "lub" or "glb"; used only for error messages - */ - private static void addToMapOfMap( - Map> map, - QualifierKind qual1, - QualifierKind qual2, - QualifierKind value, - String operationName) { - Map qual1Map = - map.computeIfAbsent(qual1, k -> new HashMap<>()); - QualifierKind existingValue = qual1Map.get(qual2); - if (existingValue == null) { - qual1Map.put(qual2, value); - } else { - if (existingValue != value) { - throw new TypeSystemError( - "Multiple %ss for qualifiers %s and %s. Found map %s and %s", - operationName, qual1, qual2, value, existingValue); - } - } + @Override + public QualifierKind getBottom() { + if (bottom == null) { + throw new BugInCF( + "DefaultQualifierKind#getBottom:Bottom is null for QualifierKind %s. Don't" + + " call this method during initialization of" + + " DefaultQualifierKindHierarchy.", + name); + } + return bottom; } - /** - * The default implementation of {@link QualifierKind}. - * - *

The fields in this class that refer to {@link QualifierKind}s are set when creating the - * {@link DefaultQualifierKindHierarchy}. So the getter methods for these fields should not be - * called until after the {@code DefaultQualifierKindHierarchy} is created. - */ - @AnnotatedFor("nullness") - public @Interned static class DefaultQualifierKind implements QualifierKind { - - /** The canonical name of the annotation class of this. */ - private final @Interned @CanonicalName String name; - - /** The annotation class for this. */ - private final Class clazz; - - /** True if the annotation class of this has annotation elements/arguments. */ - private final boolean hasElements; - - /** The top of the hierarchy to which this belongs. */ - // Set while creating the QualifierKindHierarchy. - protected @MonotonicNonNull DefaultQualifierKind top; - - /** The bottom of the hierarchy to which this belongs. */ - // Set while creating the QualifierKindHierarchy. - protected @MonotonicNonNull DefaultQualifierKind bottom; - - /** The polymorphic qualifier of the hierarchy to which this belongs. */ - // Set while creating the QualifierKindHierarchy. - protected @Nullable DefaultQualifierKind poly; - - /** - * All the qualifier kinds that are a strict super qualifier kind of this. Does not include - * this qualifier kind itself. - */ - // Set while creating the QualifierKindHierarchy. - protected @MonotonicNonNull Set strictSuperTypes; - - /** - * Creates a {@link DefaultQualifierKind} for the given annotation class. - * - * @param clazz annotation class for a qualifier - */ - DefaultQualifierKind(Class clazz) { - this.clazz = clazz; - this.hasElements = clazz.getDeclaredMethods().length != 0; - this.name = QualifierKindHierarchy.annotationClassName(clazz).intern(); - this.poly = null; - } - - @Override - public @Interned @CanonicalName String getName() { - return name; - } - - @Override - public Class getAnnotationClass() { - return clazz; - } - - @Override - public QualifierKind getTop() { - if (top == null) { - throw new BugInCF( - "DefaultQualifierKindHierarchy#getTop: Top is null for QualifierKind %s." - + " Don't call this method during initialization of" - + " DefaultQualifierKindHierarchy.", - name); - } - return top; - } - - @Override - public boolean isTop() { - return this.top == this; - } - - @Override - public QualifierKind getBottom() { - if (bottom == null) { - throw new BugInCF( - "DefaultQualifierKind#getBottom:Bottom is null for QualifierKind %s. Don't" - + " call this method during initialization of" - + " DefaultQualifierKindHierarchy.", - name); - } - return bottom; - } - - @Override - public boolean isBottom() { - return this.bottom == this; - } + @Override + public boolean isBottom() { + return this.bottom == this; + } - @Override - public @Nullable QualifierKind getPolymorphic() { - return poly; - } + @Override + public @Nullable QualifierKind getPolymorphic() { + return poly; + } - @Pure - @Override - public boolean isPoly() { - return this.poly == this; - } + @Pure + @Override + public boolean isPoly() { + return this.poly == this; + } - @Override - public boolean hasElements() { - return hasElements; - } + @Override + public boolean hasElements() { + return hasElements; + } - @Override - public Set getStrictSuperTypes() { - if (strictSuperTypes == null) { - throw new BugInCF( - "DefaultQualifierKind#getStrictSuperTypes: strictSuperTypes was null. Don't" - + " call this method during initialization of" - + " DefaultQualifierKindHierarchy."); - } - return strictSuperTypes; - } + @Override + public Set getStrictSuperTypes() { + if (strictSuperTypes == null) { + throw new BugInCF( + "DefaultQualifierKind#getStrictSuperTypes: strictSuperTypes was null. Don't" + + " call this method during initialization of" + + " DefaultQualifierKindHierarchy."); + } + return strictSuperTypes; + } - @Override - public boolean isInSameHierarchyAs(QualifierKind other) { - return this.top == other.getTop(); - } + @Override + public boolean isInSameHierarchyAs(QualifierKind other) { + return this.top == other.getTop(); + } - @Override - public boolean isSubtypeOf(QualifierKind superQualKind) { - if (strictSuperTypes == null) { - throw new BugInCF( - "DefaultQualifierKind#isSubtypeOf: strictSuperTypes was null. Don't call" - + " this method during initialization of" - + " DefaultQualifierKindHierarchy."); - } - return this == superQualKind || strictSuperTypes.contains(superQualKind); - } + @Override + public boolean isSubtypeOf(QualifierKind superQualKind) { + if (strictSuperTypes == null) { + throw new BugInCF( + "DefaultQualifierKind#isSubtypeOf: strictSuperTypes was null. Don't call" + + " this method during initialization of" + + " DefaultQualifierKindHierarchy."); + } + return this == superQualKind || strictSuperTypes.contains(superQualKind); + } - @Override - public String toString() { - return clazz.getSimpleName(); - } + @Override + public String toString() { + return clazz.getSimpleName(); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java index 57bdc267bac..efe142b3160 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java @@ -9,77 +9,77 @@ /** Utilities for executing external processes. */ public class ExecUtil { - public static int execute(String[] cmd, OutputStream std, OutputStream err) { + public static int execute(String[] cmd, OutputStream std, OutputStream err) { - Redirection outRedirect = new Redirection(std, BLOCK_SIZE); - Redirection errRedirect = new Redirection(err, BLOCK_SIZE); + Redirection outRedirect = new Redirection(std, BLOCK_SIZE); + Redirection errRedirect = new Redirection(err, BLOCK_SIZE); - try { - Process proc = Runtime.getRuntime().exec(cmd); - outRedirect.redirect(proc.getInputStream()); - errRedirect.redirect(proc.getErrorStream()); + try { + Process proc = Runtime.getRuntime().exec(cmd); + outRedirect.redirect(proc.getInputStream()); + errRedirect.redirect(proc.getErrorStream()); - IOException stdExc = outRedirect.join(); - IOException errExc = errRedirect.join(); - int exitStatus = proc.waitFor(); + IOException stdExc = outRedirect.join(); + IOException errExc = errRedirect.join(); + int exitStatus = proc.waitFor(); - if (stdExc != null) { - throw stdExc; - } + if (stdExc != null) { + throw stdExc; + } - if (errExc != null) { - throw errExc; - } + if (errExc != null) { + throw errExc; + } - return exitStatus; + return exitStatus; - } catch (InterruptedException e) { - throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); - } catch (IOException e) { - throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); - } + } catch (InterruptedException e) { + throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); + } catch (IOException e) { + throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); } + } - public static final int BLOCK_SIZE = 1024; - - public static class Redirection { - private final char[] buffer; - private final OutputStreamWriter out; - - private Thread thread; - private IOException exception; - - public Redirection(OutputStream out, int bufferSize) { - this.buffer = new char[bufferSize]; - this.out = new OutputStreamWriter(out); - } - - public void redirect(InputStream inStream) { - - exception = null; - - this.thread = - new Thread( - () -> { - try (InputStreamReader in = new InputStreamReader(inStream)) { - int read = 0; - while (read > -1) { - read = in.read(buffer); - if (read > 0) { - out.write(buffer, 0, read); - } - } - out.flush(); - } catch (IOException exc) { - exception = exc; - } - }); - thread.start(); - } - - public IOException join() throws InterruptedException { - thread.join(); - return exception; - } + public static final int BLOCK_SIZE = 1024; + + public static class Redirection { + private final char[] buffer; + private final OutputStreamWriter out; + + private Thread thread; + private IOException exception; + + public Redirection(OutputStream out, int bufferSize) { + this.buffer = new char[bufferSize]; + this.out = new OutputStreamWriter(out); + } + + public void redirect(InputStream inStream) { + + exception = null; + + this.thread = + new Thread( + () -> { + try (InputStreamReader in = new InputStreamReader(inStream)) { + int read = 0; + while (read > -1) { + read = in.read(buffer); + if (read > 0) { + out.write(buffer, 0, read); + } + } + out.flush(); + } catch (IOException exc) { + exception = exc; + } + }); + thread.start(); + } + + public IOException join() throws InterruptedException { + thread.join(); + return exception; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java b/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java index 779326267f9..655ea3a81ca 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java +++ b/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java @@ -1,17 +1,15 @@ package org.checkerframework.framework.util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.source.DiagMessage; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.javacutil.BugInCF; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; - /** * Represents field invariants, which the user states by writing {@code @FieldInvariant}. Think of * this as a set of (field name, qualifier) pairs. @@ -21,134 +19,130 @@ */ public class FieldInvariants { - /** - * A list of simple field names. A field may appear more than once in this list. This list has - * the same length as {@link #qualifiers}. - */ - private final List fields; + /** + * A list of simple field names. A field may appear more than once in this list. This list has the + * same length as {@link #qualifiers}. + */ + private final List fields; + + /** + * A list of qualifiers that apply to the field at the same index in {@link #fields}. In a + * well-formed FieldInvariants, has the same length as {@link #fields}. + */ + private final List qualifiers; - /** - * A list of qualifiers that apply to the field at the same index in {@link #fields}. In a - * well-formed FieldInvariants, has the same length as {@link #fields}. - */ - private final List qualifiers; + /** The type factory associated with this. */ + private final AnnotatedTypeFactory atypeFactory; - /** The type factory associated with this. */ - private final AnnotatedTypeFactory atypeFactory; + /** + * Creates a new FieldInvariants object. The result is well-formed if the length of qualifiers is + * either 1 or equal to length of {@code fields}. + * + * @param fields list of fields + * @param qualifiers list of qualifiers, or a single qualifier that applies to all fields + * @param atypeFactory the type factory + */ + public FieldInvariants( + List fields, List qualifiers, AnnotatedTypeFactory atypeFactory) { + this(null, fields, qualifiers, atypeFactory); + } - /** - * Creates a new FieldInvariants object. The result is well-formed if the length of qualifiers - * is either 1 or equal to length of {@code fields}. - * - * @param fields list of fields - * @param qualifiers list of qualifiers, or a single qualifier that applies to all fields - * @param atypeFactory the type factory - */ - public FieldInvariants( - List fields, - List qualifiers, - AnnotatedTypeFactory atypeFactory) { - this(null, fields, qualifiers, atypeFactory); + /** + * Creates a new object with all the invariants in {@code other}, plus those specified by {@code + * fields} and {@code qualifiers}. The result is well-formed if the length of qualifiers is either + * 1 or equal to length of {@code fields}. + * + * @param other other invariant object, may be null + * @param fields list of fields + * @param qualifiers list of qualifiers + * @param atypeFactory the type factory + */ + public FieldInvariants( + @Nullable FieldInvariants other, + List fields, + List qualifiers, + AnnotatedTypeFactory atypeFactory) { + if (qualifiers.size() == 1) { + while (fields.size() > qualifiers.size()) { + qualifiers.add(qualifiers.get(0)); + } + } + if (other != null) { + fields.addAll(other.fields); + qualifiers.addAll(other.qualifiers); } - /** - * Creates a new object with all the invariants in {@code other}, plus those specified by {@code - * fields} and {@code qualifiers}. The result is well-formed if the length of qualifiers is - * either 1 or equal to length of {@code fields}. - * - * @param other other invariant object, may be null - * @param fields list of fields - * @param qualifiers list of qualifiers - * @param atypeFactory the type factory - */ - public FieldInvariants( - @Nullable FieldInvariants other, - List fields, - List qualifiers, - AnnotatedTypeFactory atypeFactory) { - if (qualifiers.size() == 1) { - while (fields.size() > qualifiers.size()) { - qualifiers.add(qualifiers.get(0)); - } - } - if (other != null) { - fields.addAll(other.fields); - qualifiers.addAll(other.qualifiers); - } + this.fields = Collections.unmodifiableList(fields); + this.qualifiers = qualifiers; + this.atypeFactory = atypeFactory; + } - this.fields = Collections.unmodifiableList(fields); - this.qualifiers = qualifiers; - this.atypeFactory = atypeFactory; - } + /** The simple names of the fields that have a qualifier. May contain duplicates. */ + public List getFields() { + return fields; + } - /** The simple names of the fields that have a qualifier. May contain duplicates. */ - public List getFields() { - return fields; + /** + * Returns a list of qualifiers for {@code field}. If {@code field} has no qualifiers, returns an + * empty list. + * + * @param field simple field name + * @return a list of qualifiers for {@code field}, possibly empty + */ + public List getQualifiersFor(CharSequence field) { + if (!isWellFormed()) { + throw new BugInCF("malformed FieldInvariants"); } - - /** - * Returns a list of qualifiers for {@code field}. If {@code field} has no qualifiers, returns - * an empty list. - * - * @param field simple field name - * @return a list of qualifiers for {@code field}, possibly empty - */ - public List getQualifiersFor(CharSequence field) { - if (!isWellFormed()) { - throw new BugInCF("malformed FieldInvariants"); - } - String fieldString = field.toString(); - int index = fields.indexOf(fieldString); - if (index == -1) { - return Collections.emptyList(); - } - List list = new ArrayList<>(); - for (int i = 0; i < fields.size(); i++) { - if (fields.get(i).equals(fieldString)) { - list.add(qualifiers.get(i)); - } - } - return list; + String fieldString = field.toString(); + int index = fields.indexOf(fieldString); + if (index == -1) { + return Collections.emptyList(); } - - /** - * Returns true if there is a qualifier for each field in {@code fields}. - * - * @return true if there is a qualifier for each field in {@code fields} - */ - public boolean isWellFormed() { - return qualifiers.size() == fields.size(); + List list = new ArrayList<>(); + for (int i = 0; i < fields.size(); i++) { + if (fields.get(i).equals(fieldString)) { + list.add(qualifiers.get(i)); + } } + return list; + } - /** - * Returns null if this is stronger than the given FieldInvariants, otherwise returns the error - * message. This is stronger if each of its qualifiers is a subtype of (or equal to) the - * respective qualfier in the given FieldInvariants. - * - * @param superInvar the value to check for being a weaker invariant - * @return null if this is stronger, otherwise returns an error message - */ - public @Nullable DiagMessage isStrongerThan(FieldInvariants superInvar) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - if (!this.fields.containsAll(superInvar.fields)) { - List missingFields = new ArrayList<>(superInvar.fields); - missingFields.removeAll(fields); - return DiagMessage.error( - "field.invariant.not.found.superclass", String.join(", ", missingFields)); - } + /** + * Returns true if there is a qualifier for each field in {@code fields}. + * + * @return true if there is a qualifier for each field in {@code fields} + */ + public boolean isWellFormed() { + return qualifiers.size() == fields.size(); + } + + /** + * Returns null if this is stronger than the given FieldInvariants, otherwise returns the error + * message. This is stronger if each of its qualifiers is a subtype of (or equal to) the + * respective qualfier in the given FieldInvariants. + * + * @param superInvar the value to check for being a weaker invariant + * @return null if this is stronger, otherwise returns an error message + */ + public @Nullable DiagMessage isStrongerThan(FieldInvariants superInvar) { + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + if (!this.fields.containsAll(superInvar.fields)) { + List missingFields = new ArrayList<>(superInvar.fields); + missingFields.removeAll(fields); + return DiagMessage.error( + "field.invariant.not.found.superclass", String.join(", ", missingFields)); + } - for (String field : superInvar.fields) { - List superQualifiers = superInvar.getQualifiersFor(field); - List subQualifiers = this.getQualifiersFor(field); - for (AnnotationMirror superA : superQualifiers) { - AnnotationMirror sub = - qualHierarchy.findAnnotationInSameHierarchy(subQualifiers, superA); - if (sub == null || !qualHierarchy.isSubtypeQualifiersOnly(sub, superA)) { - return DiagMessage.error( - "field.invariant.not.subtype.superclass", field, sub, superA); - } - } + for (String field : superInvar.fields) { + List superQualifiers = superInvar.getQualifiersFor(field); + List subQualifiers = this.getQualifiersFor(field); + for (AnnotationMirror superA : superQualifiers) { + AnnotationMirror sub = qualHierarchy.findAnnotationInSameHierarchy(subQualifiers, superA); + if (sub == null || !qualHierarchy.isSubtypeQualifiersOnly(sub, superA)) { + return DiagMessage.error("field.invariant.not.subtype.superclass", field, sub, superA); } - return null; + } } + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java index 001d5c68689..d3253672b31 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java @@ -9,12 +9,10 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.util.SimpleTreeVisitor; import com.sun.source.util.TreePath; - -import org.checkerframework.javacutil.TreePathUtil; -import org.checkerframework.javacutil.TreeUtils; - import java.util.ArrayDeque; import java.util.Arrays; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; /** * Utilities for determining tree-based heuristics. @@ -23,227 +21,223 @@ */ public class Heuristics { - /** - * Returns true if a tree has a particular set of direct parents, ignoring blocks and - * parentheses. - * - *

For example, to test whether an expression (specified by {@code path}) is immediately - * contained by an if statement which is immediately contained in a method, one would invoke: - * - *

-     * matchParents(path, Kind.IF, Tree.Kind.METHOD)
-     * 
- * - * @param path the path to match - * @param kinds the tree kinds to match against, in ascending order starting from the desired - * kind of the parent - * @return true if the tree path matches the desired kinds, skipping blocks and parentheses, for - * as many kinds as specified - */ - public static boolean matchParents(TreePath path, Tree.Kind... kinds) { - - TreePath parentPath = path.getParentPath(); - boolean result = true; - - ArrayDeque queue = new ArrayDeque<>(Arrays.asList(kinds)); - - Tree tree; - while ((tree = parentPath.getLeaf()) != null) { + /** + * Returns true if a tree has a particular set of direct parents, ignoring blocks and parentheses. + * + *

For example, to test whether an expression (specified by {@code path}) is immediately + * contained by an if statement which is immediately contained in a method, one would invoke: + * + *

+   * matchParents(path, Kind.IF, Tree.Kind.METHOD)
+   * 
+ * + * @param path the path to match + * @param kinds the tree kinds to match against, in ascending order starting from the desired kind + * of the parent + * @return true if the tree path matches the desired kinds, skipping blocks and parentheses, for + * as many kinds as specified + */ + public static boolean matchParents(TreePath path, Tree.Kind... kinds) { + + TreePath parentPath = path.getParentPath(); + boolean result = true; + + ArrayDeque queue = new ArrayDeque<>(Arrays.asList(kinds)); + + Tree tree; + while ((tree = parentPath.getLeaf()) != null) { + + if (queue.isEmpty()) { + break; + } + + if (tree.getKind() == Tree.Kind.BLOCK || tree.getKind() == Tree.Kind.PARENTHESIZED) { + parentPath = parentPath.getParentPath(); + continue; + } + + result &= queue.removeFirst() == parentPath.getLeaf().getKind(); + parentPath = parentPath.getParentPath(); + } - if (queue.isEmpty()) { - break; - } + return result; + } - if (tree.getKind() == Tree.Kind.BLOCK || tree.getKind() == Tree.Kind.PARENTHESIZED) { - parentPath = parentPath.getParentPath(); - continue; - } + /** A base class for tree-matching algorithms. Skips parentheses by default. */ + public static class Matcher extends SimpleTreeVisitor { - result &= queue.removeFirst() == parentPath.getLeaf().getKind(); - parentPath = parentPath.getParentPath(); - } - - return result; + @Override + protected Boolean defaultAction(Tree tree, Void p) { + return false; } - /** A base class for tree-matching algorithms. Skips parentheses by default. */ - public static class Matcher extends SimpleTreeVisitor { - - @Override - protected Boolean defaultAction(Tree tree, Void p) { - return false; - } - - @Override - public Boolean visitParenthesized(ParenthesizedTree tree, Void p) { - return visit(tree.getExpression(), p); - } + @Override + public Boolean visitParenthesized(ParenthesizedTree tree, Void p) { + return visit(tree.getExpression(), p); + } - /** - * Returns true if the given path matches this Matcher. - * - * @param path the path to test - * @return true if the given path matches this Matcher - */ - public boolean match(TreePath path) { - return visit(path.getLeaf(), null); - } + /** + * Returns true if the given path matches this Matcher. + * + * @param path the path to test + * @return true if the given path matches this Matcher + */ + public boolean match(TreePath path) { + return visit(path.getLeaf(), null); } + } - public static class PreceededBy extends Matcher { - private final Matcher matcher; + public static class PreceededBy extends Matcher { + private final Matcher matcher; - public PreceededBy(Matcher matcher) { - this.matcher = matcher; - } + public PreceededBy(Matcher matcher) { + this.matcher = matcher; + } - @SuppressWarnings("interning:not.interned") - @Override - public boolean match(TreePath path) { - StatementTree stmt = TreePathUtil.enclosingOfClass(path, StatementTree.class); - if (stmt == null) { - return false; - } - TreePath p = path; - while (p.getLeaf() != stmt) { - p = p.getParentPath(); - } - assert p.getLeaf() == stmt; - - while (p != null && p.getLeaf() instanceof StatementTree) { - if (p.getParentPath().getLeaf() instanceof BlockTree) { - BlockTree block = (BlockTree) p.getParentPath().getLeaf(); - for (StatementTree st : block.getStatements()) { - if (st == p.getLeaf()) { - break; - } - - if (matcher.match(new TreePath(p, st))) { - return true; - } - } - } - p = p.getParentPath(); + @SuppressWarnings("interning:not.interned") + @Override + public boolean match(TreePath path) { + StatementTree stmt = TreePathUtil.enclosingOfClass(path, StatementTree.class); + if (stmt == null) { + return false; + } + TreePath p = path; + while (p.getLeaf() != stmt) { + p = p.getParentPath(); + } + assert p.getLeaf() == stmt; + + while (p != null && p.getLeaf() instanceof StatementTree) { + if (p.getParentPath().getLeaf() instanceof BlockTree) { + BlockTree block = (BlockTree) p.getParentPath().getLeaf(); + for (StatementTree st : block.getStatements()) { + if (st == p.getLeaf()) { + break; } - return false; + if (matcher.match(new TreePath(p, st))) { + return true; + } + } } + p = p.getParentPath(); + } + + return false; } + } + + /** + * {@code match()} returns true if called on a path, any element of which matches the given + * matcher (supplied at object initialization). That matcher is usually one that matches only the + * leaf of a path, ignoring all other parts of it. + */ + public static class Within extends Matcher { + /** The matcher that {@code Within.match} will try, on every parent of the path it is given. */ + private final Matcher matcher; /** - * {@code match()} returns true if called on a path, any element of which matches the given - * matcher (supplied at object initialization). That matcher is usually one that matches only - * the leaf of a path, ignoring all other parts of it. + * Create a new Within matcher. + * + * @param matcher the matcher that {@code Within.match} will try, on every parent of the path it + * is given */ - public static class Within extends Matcher { - /** - * The matcher that {@code Within.match} will try, on every parent of the path it is given. - */ - private final Matcher matcher; - - /** - * Create a new Within matcher. - * - * @param matcher the matcher that {@code Within.match} will try, on every parent of the - * path it is given - */ - public Within(Matcher matcher) { - this.matcher = matcher; - } - - @Override - public boolean match(TreePath path) { - TreePath p = path; - while (p != null) { - if (matcher.match(p)) { - return true; - } - p = p.getParentPath(); - } - - return false; - } + public Within(Matcher matcher) { + this.matcher = matcher; } - /** - * {@code match()} returns true if called on a path whose leaf is within the "then" clause of an - * if whose condition matches the matcher (supplied at object initialization). Also returns true - * if the leaf is within the "else" of a negated condition that matches the supplied matcher. - */ - public static class WithinTrueBranch extends Matcher { - /** conditionMatcher for the condition */ - private final Matcher matcher; - - /** - * @param conditionMatcher for the condition - */ - public WithinTrueBranch(Matcher conditionMatcher) { - this.matcher = conditionMatcher; + @Override + public boolean match(TreePath path) { + TreePath p = path; + while (p != null) { + if (matcher.match(p)) { + return true; } + p = p.getParentPath(); + } - @SuppressWarnings("interning:not.interned") - @Override - public boolean match(TreePath path) { - TreePath prev = path, p = path.getParentPath(); - while (p != null) { - if (p.getLeaf().getKind() == Tree.Kind.IF) { - IfTree ifTree = (IfTree) p.getLeaf(); - ExpressionTree cond = TreeUtils.withoutParens(ifTree.getCondition()); - if (ifTree.getThenStatement() == prev.getLeaf() - && matcher.match(new TreePath(p, cond))) { - return true; - } - if (cond.getKind() == Tree.Kind.LOGICAL_COMPLEMENT - && matcher.match(new TreePath(p, ((UnaryTree) cond).getExpression()))) { - return true; - } - } - prev = p; - p = p.getParentPath(); - } - - return false; - } + return false; } + } + + /** + * {@code match()} returns true if called on a path whose leaf is within the "then" clause of an + * if whose condition matches the matcher (supplied at object initialization). Also returns true + * if the leaf is within the "else" of a negated condition that matches the supplied matcher. + */ + public static class WithinTrueBranch extends Matcher { + /** conditionMatcher for the condition */ + private final Matcher matcher; /** - * {@code match()} returns true if called on a path whose leaf has the given kind (supplied at - * object initialization). + * @param conditionMatcher for the condition */ - public static class OfKind extends Matcher { - private final Tree.Kind kind; - private final Matcher matcher; + public WithinTrueBranch(Matcher conditionMatcher) { + this.matcher = conditionMatcher; + } - public OfKind(Tree.Kind kind, Matcher matcher) { - this.kind = kind; - this.matcher = matcher; + @SuppressWarnings("interning:not.interned") + @Override + public boolean match(TreePath path) { + TreePath prev = path, p = path.getParentPath(); + while (p != null) { + if (p.getLeaf().getKind() == Tree.Kind.IF) { + IfTree ifTree = (IfTree) p.getLeaf(); + ExpressionTree cond = TreeUtils.withoutParens(ifTree.getCondition()); + if (ifTree.getThenStatement() == prev.getLeaf() && matcher.match(new TreePath(p, cond))) { + return true; + } + if (cond.getKind() == Tree.Kind.LOGICAL_COMPLEMENT + && matcher.match(new TreePath(p, ((UnaryTree) cond).getExpression()))) { + return true; + } } + prev = p; + p = p.getParentPath(); + } - @Override - public boolean match(TreePath path) { - if (path.getLeaf().getKind() == kind) { - return matcher.match(path); - } - return false; - } + return false; + } + } + + /** + * {@code match()} returns true if called on a path whose leaf has the given kind (supplied at + * object initialization). + */ + public static class OfKind extends Matcher { + private final Tree.Kind kind; + private final Matcher matcher; + + public OfKind(Tree.Kind kind, Matcher matcher) { + this.kind = kind; + this.matcher = matcher; } - /** {@code match()} returns true if any of the given matchers returns true. */ - public static class OrMatcher extends Matcher { - private final Matcher[] matchers; + @Override + public boolean match(TreePath path) { + if (path.getLeaf().getKind() == kind) { + return matcher.match(path); + } + return false; + } + } - public OrMatcher(Matcher... matchers) { - this.matchers = matchers; - } + /** {@code match()} returns true if any of the given matchers returns true. */ + public static class OrMatcher extends Matcher { + private final Matcher[] matchers; - @Override - public boolean match(TreePath path) { - for (Matcher matcher : matchers) { - if (matcher.match(path)) { - return true; - } - } - return false; + public OrMatcher(Matcher... matchers) { + this.matchers = matchers; + } + + @Override + public boolean match(TreePath path) { + for (Matcher matcher : matchers) { + if (matcher.match(path)) { + return true; } + } + return false; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java index 379d0b0abec..35e4c0fbb41 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java @@ -32,7 +32,22 @@ import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.ClassType; - +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -61,24 +76,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - /** * Helper methods to parse a string that represents a restricted Java expression. * @@ -89,1179 +86,1125 @@ */ public class JavaExpressionParseUtil { - /** Regular expression for a formal parameter use. */ - protected static final String PARAMETER_REGEX = "#([1-9][0-9]*)"; + /** Regular expression for a formal parameter use. */ + protected static final String PARAMETER_REGEX = "#([1-9][0-9]*)"; + + /** + * Anchored pattern for a formal parameter use; matches a string that is exactly a formal + * parameter use. + */ + protected static final @Regex(1) Pattern ANCHORED_PARAMETER_PATTERN = + Pattern.compile("^" + PARAMETER_REGEX + "$"); + + /** + * Unanchored pattern for a formal parameter use; can be used to find all formal parameter uses. + */ + protected static final @Regex(1) Pattern UNANCHORED_PARAMETER_PATTERN = + Pattern.compile(PARAMETER_REGEX); + + /** + * Parsable replacement for formal parameter references. It is parsable because it is a Java + * identifier. + */ + private static final String PARAMETER_PREFIX = "_param_"; + + /** The length of {@link #PARAMETER_PREFIX}. */ + private static final int PARAMETER_PREFIX_LENGTH = PARAMETER_PREFIX.length(); + + /** A pattern that matches the start of a formal parameter in "#2" syntax. */ + private static final Pattern FORMAL_PARAMETER = Pattern.compile("#(\\d)"); + + /** The replacement for a formal parameter in "#2" syntax. */ + private static final String PARAMETER_REPLACEMENT = PARAMETER_PREFIX + "$1"; + + /** + * Parses a string to a {@link JavaExpression}. + * + *

For most uses, clients should call one of the static methods in {@link + * StringToJavaExpression} rather than calling this method directly. + * + * @param expression the string expression to parse + * @param enclosingType type of the class that encloses the JavaExpression + * @param thisReference the JavaExpression to which to parse "this", or null if "this" should not + * appear in the expression + * @param parameters list of JavaExpressions to which to parse formal parameter references such as + * "#2", or null if formal parameter references should not appear in the expression + * @param localVarPath if non-null, the expression is parsed as if it were written at this + * location; affects only parsing of local variables + * @param pathToCompilationUnit required to use the underlying Javac API + * @param env the processing environment + * @return {@code expression} as a {@code JavaExpression} + * @throws JavaExpressionParseException if the string cannot be parsed + */ + public static JavaExpression parse( + String expression, + TypeMirror enclosingType, + @Nullable ThisReference thisReference, + @Nullable List parameters, + @Nullable TreePath localVarPath, + TreePath pathToCompilationUnit, + ProcessingEnvironment env) + throws JavaExpressionParseException { + + // Use the current source version to parse with because a JavaExpression could refer to a + // variable named "var", which is a keyword in Java 10 and later. + LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); + String expressionWithParameterNames = + StringsPlume.replaceAll(expression, FORMAL_PARAMETER, PARAMETER_REPLACEMENT); + Expression expr; + try { + expr = JavaParserUtil.parseExpression(expressionWithParameterNames, currentSourceVersion); + } catch (ParseProblemException e) { + String extra = "."; + if (!e.getProblems().isEmpty()) { + String message = e.getProblems().get(0).getMessage(); + int newLine = message.indexOf(System.lineSeparator()); + if (newLine != -1) { + message = message.substring(0, newLine); + } + extra = ". Error message: " + message; + } + throw constructJavaExpressionParseError(expression, "the expression did not parse" + extra); + } + + JavaExpression result = + ExpressionToJavaExpressionVisitor.convert( + expr, + enclosingType, + thisReference, + parameters, + localVarPath, + pathToCompilationUnit, + env); + + if (result instanceof ClassName && !expression.endsWith(".class")) { + throw constructJavaExpressionParseError( + expression, + String.format( + "a class name cannot terminate a Java expression string, where" + " result=%s [%s]", + result, result.getClass())); + } + return result; + } + + /** + * A visitor class that converts a JavaParser {@link Expression} to a {@link JavaExpression}. This + * class does not viewpoint-adapt the expression. + */ + private static class ExpressionToJavaExpressionVisitor + extends GenericVisitorWithDefaults { /** - * Anchored pattern for a formal parameter use; matches a string that is exactly a formal - * parameter use. + * The underlying javac API used to convert from Strings to Elements requires a tree path even + * when the information could be deduced from elements alone. So use the path to the current + * CompilationUnit. */ - protected static final @Regex(1) Pattern ANCHORED_PARAMETER_PATTERN = - Pattern.compile("^" + PARAMETER_REGEX + "$"); + private final TreePath pathToCompilationUnit; + + /** If non-null, the expression is parsed as if it were written at this location. */ + private final @Nullable TreePath localVarPath; + + /** The processing environment. */ + private final ProcessingEnvironment env; + + /** The resolver. Computed from the environment, but lazily initialized. */ + private @MonotonicNonNull Resolver resolver = null; + + /** The type utilities. */ + private final Types types; + + /** The java.lang.String type. */ + private final TypeMirror stringTypeMirror; + + /** The enclosing type. Used to look up unqualified method, field, and class names. */ + private final TypeMirror enclosingType; /** - * Unanchored pattern for a formal parameter use; can be used to find all formal parameter uses. + * The expression to use for "this". If {@code null}, a parse error will be thrown if "this" + * appears in the expression. */ - protected static final @Regex(1) Pattern UNANCHORED_PARAMETER_PATTERN = - Pattern.compile(PARAMETER_REGEX); + private final @Nullable ThisReference thisReference; /** - * Parsable replacement for formal parameter references. It is parsable because it is a Java - * identifier. + * For each formal parameter, the expression to which to parse it. For example, the second + * (index 1) element of the list is what "#2" parses to. If this field is {@code null}, a parse + * error will be thrown if "#2" appears in the expression. */ - private static final String PARAMETER_PREFIX = "_param_"; - - /** The length of {@link #PARAMETER_PREFIX}. */ - private static final int PARAMETER_PREFIX_LENGTH = PARAMETER_PREFIX.length(); - - /** A pattern that matches the start of a formal parameter in "#2" syntax. */ - private static final Pattern FORMAL_PARAMETER = Pattern.compile("#(\\d)"); - - /** The replacement for a formal parameter in "#2" syntax. */ - private static final String PARAMETER_REPLACEMENT = PARAMETER_PREFIX + "$1"; + private final @Nullable List parameters; /** - * Parses a string to a {@link JavaExpression}. + * Create a new ExpressionToJavaExpressionVisitor. * - *

For most uses, clients should call one of the static methods in {@link - * StringToJavaExpression} rather than calling this method directly. - * - * @param expression the string expression to parse * @param enclosingType type of the class that encloses the JavaExpression - * @param thisReference the JavaExpression to which to parse "this", or null if "this" should - * not appear in the expression - * @param parameters list of JavaExpressions to which to parse formal parameter references such - * as "#2", or null if formal parameter references should not appear in the expression + * @param thisReference a JavaExpression to which to parse "this", or null if "this" should not + * appear in the expression + * @param parameters list of JavaExpressions to which to parse a formal parameter reference such + * as "#2", or null if parameters should not appear in the expression * @param localVarPath if non-null, the expression is parsed as if it were written at this - * location; affects only parsing of local variables + * location * @param pathToCompilationUnit required to use the underlying Javac API * @param env the processing environment - * @return {@code expression} as a {@code JavaExpression} - * @throws JavaExpressionParseException if the string cannot be parsed */ - public static JavaExpression parse( - String expression, - TypeMirror enclosingType, - @Nullable ThisReference thisReference, - @Nullable List parameters, - @Nullable TreePath localVarPath, - TreePath pathToCompilationUnit, - ProcessingEnvironment env) - throws JavaExpressionParseException { - - // Use the current source version to parse with because a JavaExpression could refer to a - // variable named "var", which is a keyword in Java 10 and later. - LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); - String expressionWithParameterNames = - StringsPlume.replaceAll(expression, FORMAL_PARAMETER, PARAMETER_REPLACEMENT); - Expression expr; - try { - expr = - JavaParserUtil.parseExpression( - expressionWithParameterNames, currentSourceVersion); - } catch (ParseProblemException e) { - String extra = "."; - if (!e.getProblems().isEmpty()) { - String message = e.getProblems().get(0).getMessage(); - int newLine = message.indexOf(System.lineSeparator()); - if (newLine != -1) { - message = message.substring(0, newLine); - } - extra = ". Error message: " + message; - } - throw constructJavaExpressionParseError( - expression, "the expression did not parse" + extra); - } + private ExpressionToJavaExpressionVisitor( + TypeMirror enclosingType, + @Nullable ThisReference thisReference, + @Nullable List parameters, + @Nullable TreePath localVarPath, + TreePath pathToCompilationUnit, + ProcessingEnvironment env) { + this.pathToCompilationUnit = pathToCompilationUnit; + this.localVarPath = localVarPath; + this.env = env; + this.types = env.getTypeUtils(); + this.stringTypeMirror = env.getElementUtils().getTypeElement("java.lang.String").asType(); + this.enclosingType = enclosingType; + this.thisReference = thisReference; + this.parameters = parameters; + } - JavaExpression result = - ExpressionToJavaExpressionVisitor.convert( - expr, - enclosingType, - thisReference, - parameters, - localVarPath, - pathToCompilationUnit, - env); - - if (result instanceof ClassName && !expression.endsWith(".class")) { - throw constructJavaExpressionParseError( - expression, - String.format( - "a class name cannot terminate a Java expression string, where" - + " result=%s [%s]", - result, result.getClass())); - } - return result; + /** + * Converts a JavaParser {@link Expression} to a {@link JavaExpression}. + * + * @param expr the JavaParser {@link Expression} to convert + * @param enclosingType type of the class that encloses the JavaExpression + * @param thisReference a JavaExpression to which to parse "this", or null if "this" should not + * appear in the expression + * @param parameters list of JavaExpressions to which to parse parameters, or null if parameters + * should not appear in the expression + * @param localVarPath if non-null, the expression is parsed as if it were written at this + * location + * @param pathToCompilationUnit required to use the underlying Javac API + * @param env the processing environment + * @return {@code expr} as a {@code JavaExpression} + * @throws JavaExpressionParseException if {@code expr} cannot be converted to a {@code + * JavaExpression} + */ + public static JavaExpression convert( + Expression expr, + TypeMirror enclosingType, + @Nullable ThisReference thisReference, + @Nullable List parameters, + @Nullable TreePath localVarPath, + TreePath pathToCompilationUnit, + ProcessingEnvironment env) + throws JavaExpressionParseException { + try { + return expr.accept( + new ExpressionToJavaExpressionVisitor( + enclosingType, thisReference, parameters, localVarPath, pathToCompilationUnit, env), + null); + } catch (ParseRuntimeException e) { + // Convert unchecked to checked exception. Visitor methods can't throw checked + // exceptions. They override the methods in the superclass, and a checked exception + // would change the method signature. + throw e.getCheckedException(); + } } /** - * A visitor class that converts a JavaParser {@link Expression} to a {@link JavaExpression}. - * This class does not viewpoint-adapt the expression. + * Initializes the {@code resolver} field if necessary. Does nothing on invocations after the + * first. */ - private static class ExpressionToJavaExpressionVisitor - extends GenericVisitorWithDefaults { - - /** - * The underlying javac API used to convert from Strings to Elements requires a tree path - * even when the information could be deduced from elements alone. So use the path to the - * current CompilationUnit. - */ - private final TreePath pathToCompilationUnit; - - /** If non-null, the expression is parsed as if it were written at this location. */ - private final @Nullable TreePath localVarPath; - - /** The processing environment. */ - private final ProcessingEnvironment env; - - /** The resolver. Computed from the environment, but lazily initialized. */ - private @MonotonicNonNull Resolver resolver = null; - - /** The type utilities. */ - private final Types types; - - /** The java.lang.String type. */ - private final TypeMirror stringTypeMirror; - - /** The enclosing type. Used to look up unqualified method, field, and class names. */ - private final TypeMirror enclosingType; - - /** - * The expression to use for "this". If {@code null}, a parse error will be thrown if "this" - * appears in the expression. - */ - private final @Nullable ThisReference thisReference; - - /** - * For each formal parameter, the expression to which to parse it. For example, the second - * (index 1) element of the list is what "#2" parses to. If this field is {@code null}, a - * parse error will be thrown if "#2" appears in the expression. - */ - private final @Nullable List parameters; - - /** - * Create a new ExpressionToJavaExpressionVisitor. - * - * @param enclosingType type of the class that encloses the JavaExpression - * @param thisReference a JavaExpression to which to parse "this", or null if "this" should - * not appear in the expression - * @param parameters list of JavaExpressions to which to parse a formal parameter reference - * such as "#2", or null if parameters should not appear in the expression - * @param localVarPath if non-null, the expression is parsed as if it were written at this - * location - * @param pathToCompilationUnit required to use the underlying Javac API - * @param env the processing environment - */ - private ExpressionToJavaExpressionVisitor( - TypeMirror enclosingType, - @Nullable ThisReference thisReference, - @Nullable List parameters, - @Nullable TreePath localVarPath, - TreePath pathToCompilationUnit, - ProcessingEnvironment env) { - this.pathToCompilationUnit = pathToCompilationUnit; - this.localVarPath = localVarPath; - this.env = env; - this.types = env.getTypeUtils(); - this.stringTypeMirror = - env.getElementUtils().getTypeElement("java.lang.String").asType(); - this.enclosingType = enclosingType; - this.thisReference = thisReference; - this.parameters = parameters; - } + @EnsuresNonNull("resolver") + private void setResolverField() { + if (resolver == null) { + resolver = new Resolver(env); + } + } - /** - * Converts a JavaParser {@link Expression} to a {@link JavaExpression}. - * - * @param expr the JavaParser {@link Expression} to convert - * @param enclosingType type of the class that encloses the JavaExpression - * @param thisReference a JavaExpression to which to parse "this", or null if "this" should - * not appear in the expression - * @param parameters list of JavaExpressions to which to parse parameters, or null if - * parameters should not appear in the expression - * @param localVarPath if non-null, the expression is parsed as if it were written at this - * location - * @param pathToCompilationUnit required to use the underlying Javac API - * @param env the processing environment - * @return {@code expr} as a {@code JavaExpression} - * @throws JavaExpressionParseException if {@code expr} cannot be converted to a {@code - * JavaExpression} - */ - public static JavaExpression convert( - Expression expr, - TypeMirror enclosingType, - @Nullable ThisReference thisReference, - @Nullable List parameters, - @Nullable TreePath localVarPath, - TreePath pathToCompilationUnit, - ProcessingEnvironment env) - throws JavaExpressionParseException { - try { - return expr.accept( - new ExpressionToJavaExpressionVisitor( - enclosingType, - thisReference, - parameters, - localVarPath, - pathToCompilationUnit, - env), - null); - } catch (ParseRuntimeException e) { - // Convert unchecked to checked exception. Visitor methods can't throw checked - // exceptions. They override the methods in the superclass, and a checked exception - // would change the method signature. - throw e.getCheckedException(); - } - } + /** If the expression is not supported, throw a {@link ParseRuntimeException} by default. */ + @Override + public JavaExpression defaultAction(com.github.javaparser.ast.Node n, Void aVoid) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + n.toString(), n.getClass() + " is not a supported expression")); + } - /** - * Initializes the {@code resolver} field if necessary. Does nothing on invocations after - * the first. - */ - @EnsuresNonNull("resolver") - private void setResolverField() { - if (resolver == null) { - resolver = new Resolver(env); - } - } + @Override + public JavaExpression visit(NullLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getNullType(), (Object) null); + } - /** If the expression is not supported, throw a {@link ParseRuntimeException} by default. */ - @Override - public JavaExpression defaultAction(com.github.javaparser.ast.Node n, Void aVoid) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - n.toString(), n.getClass() + " is not a supported expression")); - } + @Override + public JavaExpression visit(IntegerLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.INT), expr.asNumber()); + } - @Override - public JavaExpression visit(NullLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getNullType(), (Object) null); - } + @Override + public JavaExpression visit(LongLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.LONG), expr.asNumber()); + } - @Override - public JavaExpression visit(IntegerLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.INT), expr.asNumber()); - } + @Override + public JavaExpression visit(CharLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.CHAR), expr.asChar()); + } - @Override - public JavaExpression visit(LongLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.LONG), expr.asNumber()); - } + @Override + public JavaExpression visit(DoubleLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.DOUBLE), expr.asDouble()); + } - @Override - public JavaExpression visit(CharLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.CHAR), expr.asChar()); - } + @Override + public JavaExpression visit(StringLiteralExpr expr, Void aVoid) { + return new ValueLiteral(stringTypeMirror, expr.asString()); + } - @Override - public JavaExpression visit(DoubleLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.DOUBLE), expr.asDouble()); - } + @Override + public JavaExpression visit(BooleanLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.BOOLEAN), expr.getValue()); + } - @Override - public JavaExpression visit(StringLiteralExpr expr, Void aVoid) { - return new ValueLiteral(stringTypeMirror, expr.asString()); - } + @Override + public JavaExpression visit(ThisExpr n, Void aVoid) { + if (thisReference == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError("this", "\"this\" isn't allowed here")); + } + return thisReference; + } - @Override - public JavaExpression visit(BooleanLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.BOOLEAN), expr.getValue()); - } + @Override + public JavaExpression visit(SuperExpr n, Void aVoid) { + // super literal + TypeMirror superclass = TypesUtils.getSuperclass(enclosingType, types); + if (superclass == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError("super", enclosingType + " has no superclass")); + } + return new ThisReference(superclass); + } - @Override - public JavaExpression visit(ThisExpr n, Void aVoid) { - if (thisReference == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError("this", "\"this\" isn't allowed here")); - } - return thisReference; - } + // expr is an expression in parentheses. + @Override + public JavaExpression visit(EnclosedExpr expr, Void aVoid) { + return expr.getInner().accept(this, null); + } - @Override - public JavaExpression visit(SuperExpr n, Void aVoid) { - // super literal - TypeMirror superclass = TypesUtils.getSuperclass(enclosingType, types); - if (superclass == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - "super", enclosingType + " has no superclass")); - } - return new ThisReference(superclass); - } + @Override + public JavaExpression visit(ArrayAccessExpr expr, Void aVoid) { + JavaExpression array = expr.getName().accept(this, null); + TypeMirror arrayType = array.getType(); + if (arrayType.getKind() != TypeKind.ARRAY) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + String.format( + "expected an array, found %s of type %s [%s]", + array, arrayType, arrayType.getKind()))); + } + TypeMirror componentType = ((ArrayType) arrayType).getComponentType(); + + JavaExpression index = expr.getIndex().accept(this, null); + + return new ArrayAccess(componentType, array, index); + } - // expr is an expression in parentheses. - @Override - public JavaExpression visit(EnclosedExpr expr, Void aVoid) { - return expr.getInner().accept(this, null); + // expr is an identifier with no dots in its name. + @Override + public JavaExpression visit(NameExpr expr, Void aVoid) { + String s = expr.getNameAsString(); + setResolverField(); + + // Formal parameter, using "#2" syntax. + JavaExpression parameter = getParameterJavaExpression(s); + if (parameter != null) { + // A parameter is a local variable, but it can be referenced outside of local scope + // (at the method scope) using the special #NN syntax. + return parameter; + } + + // Local variable or parameter. + if (localVarPath != null) { + // Attempt to match a local variable within the scope of the + // given path before attempting to match a field. + VariableElement varElem = resolver.findLocalVariableOrParameter(s, localVarPath); + if (varElem != null) { + return new LocalVariable(varElem); } - - @Override - public JavaExpression visit(ArrayAccessExpr expr, Void aVoid) { - JavaExpression array = expr.getName().accept(this, null); - TypeMirror arrayType = array.getType(); - if (arrayType.getKind() != TypeKind.ARRAY) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - String.format( - "expected an array, found %s of type %s [%s]", - array, arrayType, arrayType.getKind()))); - } - TypeMirror componentType = ((ArrayType) arrayType).getComponentType(); - - JavaExpression index = expr.getIndex().accept(this, null); - - return new ArrayAccess(componentType, array, index); + } + + // Field access + JavaExpression fieldAccessReceiver; + if (thisReference != null) { + fieldAccessReceiver = thisReference; + } else { + fieldAccessReceiver = new ClassName(enclosingType); + } + FieldAccess fieldAccess = getIdentifierAsFieldAccess(fieldAccessReceiver, s); + if (fieldAccess != null) { + return fieldAccess; + } + + if (localVarPath != null) { + Element classElem = resolver.findClass(s, localVarPath); + TypeMirror classType = ElementUtils.getType(classElem); + if (classType != null) { + return new ClassName(classType); } - - // expr is an identifier with no dots in its name. - @Override - public JavaExpression visit(NameExpr expr, Void aVoid) { - String s = expr.getNameAsString(); - setResolverField(); - - // Formal parameter, using "#2" syntax. - JavaExpression parameter = getParameterJavaExpression(s); - if (parameter != null) { - // A parameter is a local variable, but it can be referenced outside of local scope - // (at the method scope) using the special #NN syntax. - return parameter; - } - - // Local variable or parameter. - if (localVarPath != null) { - // Attempt to match a local variable within the scope of the - // given path before attempting to match a field. - VariableElement varElem = resolver.findLocalVariableOrParameter(s, localVarPath); - if (varElem != null) { - return new LocalVariable(varElem); - } - } - - // Field access - JavaExpression fieldAccessReceiver; - if (thisReference != null) { - fieldAccessReceiver = thisReference; - } else { - fieldAccessReceiver = new ClassName(enclosingType); - } - FieldAccess fieldAccess = getIdentifierAsFieldAccess(fieldAccessReceiver, s); - if (fieldAccess != null) { - return fieldAccess; - } - - if (localVarPath != null) { - Element classElem = resolver.findClass(s, localVarPath); - TypeMirror classType = ElementUtils.getType(classElem); - if (classType != null) { - return new ClassName(classType); - } - } - - ClassName classType = getIdentifierAsUnqualifiedClassName(s); - if (classType != null) { - return classType; - } - - // Err if a formal parameter name is used, instead of the "#2" syntax. - if (parameters != null) { - for (int i = 0; i < parameters.size(); i++) { - Element varElt = parameters.get(i).getElement(); - if (varElt.getSimpleName().contentEquals(s)) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - s, - String.format( - DependentTypesError.FORMAL_PARAM_NAME_STRING, - i + 1, - s))); - } - } - } - + } + + ClassName classType = getIdentifierAsUnqualifiedClassName(s); + if (classType != null) { + return classType; + } + + // Err if a formal parameter name is used, instead of the "#2" syntax. + if (parameters != null) { + for (int i = 0; i < parameters.size(); i++) { + Element varElt = parameters.get(i).getElement(); + if (varElt.getSimpleName().contentEquals(s)) { throw new ParseRuntimeException( - constructJavaExpressionParseError(s, "identifier not found")); + constructJavaExpressionParseError( + s, String.format(DependentTypesError.FORMAL_PARAM_NAME_STRING, i + 1, s))); + } } + } - /** - * If {@code s} a parameter expressed using the {@code #NN} syntax, then returns a - * JavaExpression for the given parameter; that is, returns an element of {@code - * parameters}. Otherwise, returns {@code null}. - * - * @param s a String that starts with PARAMETER_PREFIX - * @return the JavaExpression for the given parameter or {@code null} if {@code s} is not a - * parameter - */ - private @Nullable JavaExpression getParameterJavaExpression(String s) { - if (!s.startsWith(PARAMETER_PREFIX)) { - return null; - } - if (parameters == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError(s, "no parameters found")); - } - int idx = Integer.parseInt(s.substring(PARAMETER_PREFIX_LENGTH)); - - if (idx == 0) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - "#0", - "Use \"this\" for the receiver or \"#1\" for the first formal" - + " parameter")); - } - if (idx > parameters.size()) { - throw new ParseRuntimeException( - new JavaExpressionParseException( - "flowexpr.parse.index.too.big", Integer.toString(idx))); - } - return parameters.get(idx - 1); - } + throw new ParseRuntimeException(constructJavaExpressionParseError(s, "identifier not found")); + } - /** - * If {@code identifier} is the simple class name of any inner class of {@code type}, return - * the {@link ClassName} for the inner class. If not, return null. - * - * @param type type to search for {@code identifier} - * @param identifier possible simple class name - * @return the {@code ClassName} for {@code identifier}, or null if it is not a simple class - * name - */ - protected @Nullable ClassName getIdentifierAsInnerClassName( - TypeMirror type, String identifier) { - if (type.getKind() != TypeKind.DECLARED) { - return null; - } - - Element outerClass = ((DeclaredType) type).asElement(); - for (Element memberElement : outerClass.getEnclosedElements()) { - if (!(memberElement.getKind().isClass() || memberElement.getKind().isInterface())) { - continue; - } - if (memberElement.getSimpleName().contentEquals(identifier)) { - return new ClassName(ElementUtils.getType(memberElement)); - } - } - return null; - } + /** + * If {@code s} a parameter expressed using the {@code #NN} syntax, then returns a + * JavaExpression for the given parameter; that is, returns an element of {@code parameters}. + * Otherwise, returns {@code null}. + * + * @param s a String that starts with PARAMETER_PREFIX + * @return the JavaExpression for the given parameter or {@code null} if {@code s} is not a + * parameter + */ + private @Nullable JavaExpression getParameterJavaExpression(String s) { + if (!s.startsWith(PARAMETER_PREFIX)) { + return null; + } + if (parameters == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError(s, "no parameters found")); + } + int idx = Integer.parseInt(s.substring(PARAMETER_PREFIX_LENGTH)); + + if (idx == 0) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + "#0", + "Use \"this\" for the receiver or \"#1\" for the first formal" + " parameter")); + } + if (idx > parameters.size()) { + throw new ParseRuntimeException( + new JavaExpressionParseException( + "flowexpr.parse.index.too.big", Integer.toString(idx))); + } + return parameters.get(idx - 1); + } - /** - * If {@code identifier} is a class name with that can be referenced using only its simple - * name within {@code enclosingType}, return the {@link ClassName} for the class. If not, - * return null. - * - *

{@code identifier} may be - * - *

    - *
  1. the simple name of {@code type}. - *
  2. the simple name of a class declared in {@code type} or in an enclosing type of - * {@code type}. - *
  3. the simple name of a class in the java.lang package. - *
  4. the simple name of a class in the unnamed package. - *
- * - * @param identifier possible class name - * @return the {@code ClassName} for {@code identifier}, or null if it is not a class name - */ - protected @Nullable ClassName getIdentifierAsUnqualifiedClassName(String identifier) { - // Is identifier an inner class of enclosingType or of any enclosing class of - // enclosingType? - TypeMirror searchType = enclosingType; - while (searchType.getKind() == TypeKind.DECLARED) { - DeclaredType searchDeclaredType = (DeclaredType) searchType; - if (searchDeclaredType.asElement().getSimpleName().contentEquals(identifier)) { - return new ClassName(searchType); - } - ClassName className = getIdentifierAsInnerClassName(searchType, identifier); - if (className != null) { - return className; - } - searchType = getTypeOfEnclosingClass(searchDeclaredType); - } - - setResolverField(); - - if (enclosingType.getKind() == TypeKind.DECLARED) { - // Is identifier in the same package as this? - PackageSymbol packageSymbol = - (PackageSymbol) - ElementUtils.enclosingPackage( - ((DeclaredType) enclosingType).asElement()); - ClassSymbol classSymbol = - resolver.findClassInPackage( - identifier, packageSymbol, pathToCompilationUnit); - if (classSymbol != null) { - return new ClassName(classSymbol.asType()); - } - } - // Is identifier a simple name for a class in java.lang? - Symbol.PackageSymbol packageSymbol = - resolver.findPackage("java.lang", pathToCompilationUnit); - if (packageSymbol == null) { - throw new BugInCF("Can't find java.lang package."); - } - ClassSymbol classSymbol = - resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); - if (classSymbol != null) { - return new ClassName(classSymbol.asType()); - } - - // Is identifier a class in the unnamed package? - Element classElem = resolver.findClass(identifier, pathToCompilationUnit); - if (classElem != null) { - PackageElement pkg = ElementUtils.enclosingPackage(classElem); - if (pkg != null && pkg.isUnnamed()) { - TypeMirror classType = ElementUtils.getType(classElem); - if (classType != null) { - return new ClassName(classType); - } - } - } - - return null; + /** + * If {@code identifier} is the simple class name of any inner class of {@code type}, return the + * {@link ClassName} for the inner class. If not, return null. + * + * @param type type to search for {@code identifier} + * @param identifier possible simple class name + * @return the {@code ClassName} for {@code identifier}, or null if it is not a simple class + * name + */ + protected @Nullable ClassName getIdentifierAsInnerClassName( + TypeMirror type, String identifier) { + if (type.getKind() != TypeKind.DECLARED) { + return null; + } + + Element outerClass = ((DeclaredType) type).asElement(); + for (Element memberElement : outerClass.getEnclosedElements()) { + if (!(memberElement.getKind().isClass() || memberElement.getKind().isInterface())) { + continue; } - - /** - * Return the {@link FieldAccess} expression for the field with name {@code identifier} - * accessed via {@code receiverExpr}. If no such field exists, then {@code null} is - * returned. - * - * @param receiverExpr the receiver of the field access; the expression used to access the - * field - * @param identifier possibly a field name - * @return a field access, or null if {@code identifier} is not a field that can be accessed - * via {@code receiverExpr} - */ - protected @Nullable FieldAccess getIdentifierAsFieldAccess( - JavaExpression receiverExpr, String identifier) { - setResolverField(); - // Find the field element. - TypeMirror enclosingTypeOfField = receiverExpr.getType(); - VariableElement fieldElem; - if (identifier.equals("length") && enclosingTypeOfField.getKind() == TypeKind.ARRAY) { - fieldElem = - resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); - if (fieldElem == null) { - throw new BugInCF("length field not found for type %s", enclosingTypeOfField); - } - } else { - fieldElem = null; - // Search for field in each enclosing class. - while (enclosingTypeOfField.getKind() == TypeKind.DECLARED) { - fieldElem = - resolver.findField( - identifier, enclosingTypeOfField, pathToCompilationUnit); - if (fieldElem != null) { - break; - } - enclosingTypeOfField = - getTypeOfEnclosingClass((DeclaredType) enclosingTypeOfField); - } - if (fieldElem == null) { - // field not found. - return null; - } - } - - // `fieldElem` is now set. Construct a FieldAccess expression. - - if (ElementUtils.isStatic(fieldElem)) { - Element classElem = fieldElem.getEnclosingElement(); - JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); - return new FieldAccess(staticClassReceiver, fieldElem); - } - - // fieldElem is an instance field. - - if (receiverExpr instanceof ClassName) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - fieldElem.getSimpleName().toString(), - "a non-static field cannot have a class name as a receiver.")); - } - - // There are two possibilities, captured by local variable fieldDeclaredInReceiverType: - // * true: it's an instance field declared in the type (or supertype) of receiverExpr. - // * false: it's an instance field declared in an enclosing type of receiverExpr. - - @SuppressWarnings("interning:not.interned") // Checking for exact object - boolean fieldDeclaredInReceiverType = enclosingTypeOfField == receiverExpr.getType(); - if (fieldDeclaredInReceiverType) { - TypeMirror fieldType = ElementUtils.getType(fieldElem); - return new FieldAccess(receiverExpr, fieldType, fieldElem); - } else { - if (!(receiverExpr instanceof ThisReference)) { - String msg = - String.format( - "%s is declared in an outer type of the type of the receiver" - + " expression, %s.", - identifier, receiverExpr); - throw new ParseRuntimeException( - constructJavaExpressionParseError(identifier, msg)); - } - TypeElement receiverTypeElement = TypesUtils.getTypeElement(receiverExpr.getType()); - if (receiverTypeElement == null || ElementUtils.isStatic(receiverTypeElement)) { - String msg = - String.format( - "%s is a non-static field declared in an outer type this.", - identifier); - throw new ParseRuntimeException( - constructJavaExpressionParseError(identifier, msg)); - } - JavaExpression locationOfField = new ThisReference(enclosingTypeOfField); - return new FieldAccess(locationOfField, fieldElem); - } + if (memberElement.getSimpleName().contentEquals(identifier)) { + return new ClassName(ElementUtils.getType(memberElement)); } + } + return null; + } - @Override - public JavaExpression visit(MethodCallExpr expr, Void aVoid) { - setResolverField(); - - JavaExpression receiverExpr; - if (expr.getScope().isPresent()) { - receiverExpr = expr.getScope().get().accept(this, null); - expr = expr.removeScope(); - } else if (thisReference != null) { - receiverExpr = thisReference; - } else { - receiverExpr = new ClassName(enclosingType); - } - - String methodName = expr.getNameAsString(); - - // parse argument list - List arguments = - CollectionsPlume.mapList( - argument -> argument.accept(this, null), expr.getArguments()); - - ExecutableElement methodElement; - try { - methodElement = - getMethodElement( - methodName, - receiverExpr.getType(), - pathToCompilationUnit, - arguments, - resolver); - } catch (JavaExpressionParseException e) { - throw new ParseRuntimeException(e); - } - - // Box any arguments that require it. - for (int i = 0; i < arguments.size(); i++) { - VariableElement parameter = methodElement.getParameters().get(i); - TypeMirror parameterType = parameter.asType(); - JavaExpression argument = arguments.get(i); - TypeMirror argumentType = argument.getType(); - // is boxing necessary? - if (TypesUtils.isBoxedPrimitive(parameterType) - && TypesUtils.isPrimitive(argumentType)) { - // boxing is necessary - MethodSymbol valueOfMethod = TreeBuilder.getValueOfMethod(env, parameterType); - JavaExpression boxedParam = - new MethodCall( - parameterType, - valueOfMethod, - new ClassName(parameterType), - Collections.singletonList(argument)); - arguments.set(i, boxedParam); - } - } - - // Build the MethodCall expression object. - if (ElementUtils.isStatic(methodElement)) { - Element classElem = methodElement.getEnclosingElement(); - JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); - return new MethodCall( - ElementUtils.getType(methodElement), - methodElement, - staticClassReceiver, - arguments); - } else { - if (receiverExpr instanceof ClassName) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - "a non-static method call cannot have a class name as a" - + " receiver")); - } - TypeMirror methodType = - TypesUtils.substituteMethodReturnType( - methodElement, receiverExpr.getType(), env); - return new MethodCall(methodType, methodElement, receiverExpr, arguments); - } + /** + * If {@code identifier} is a class name with that can be referenced using only its simple name + * within {@code enclosingType}, return the {@link ClassName} for the class. If not, return + * null. + * + *

{@code identifier} may be + * + *

    + *
  1. the simple name of {@code type}. + *
  2. the simple name of a class declared in {@code type} or in an enclosing type of {@code + * type}. + *
  3. the simple name of a class in the java.lang package. + *
  4. the simple name of a class in the unnamed package. + *
+ * + * @param identifier possible class name + * @return the {@code ClassName} for {@code identifier}, or null if it is not a class name + */ + protected @Nullable ClassName getIdentifierAsUnqualifiedClassName(String identifier) { + // Is identifier an inner class of enclosingType or of any enclosing class of + // enclosingType? + TypeMirror searchType = enclosingType; + while (searchType.getKind() == TypeKind.DECLARED) { + DeclaredType searchDeclaredType = (DeclaredType) searchType; + if (searchDeclaredType.asElement().getSimpleName().contentEquals(identifier)) { + return new ClassName(searchType); } - - /** - * Returns the ExecutableElement for a method, or throws an exception. - * - *

(This method takes into account autoboxing.) - * - * @param methodName the method name - * @param receiverType the receiver type - * @param pathToCompilationUnit the path to the compilation unit - * @param arguments the arguments - * @param resolver the resolver - * @return the ExecutableElement for a method, or throws an exception - * @throws JavaExpressionParseException if the string cannot be parsed as a method name - */ - private ExecutableElement getMethodElement( - String methodName, - TypeMirror receiverType, - TreePath pathToCompilationUnit, - List arguments, - Resolver resolver) - throws JavaExpressionParseException { - - List argumentTypes = - CollectionsPlume.mapList(JavaExpression::getType, arguments); - - if (receiverType.getKind() == TypeKind.ARRAY) { - ExecutableElement element = - resolver.findMethod( - methodName, receiverType, pathToCompilationUnit, argumentTypes); - if (element == null) { - throw constructJavaExpressionParseError(methodName, "no such method"); - } - return element; - } - - // Search for method in each enclosing class. - while (receiverType.getKind() == TypeKind.DECLARED) { - ExecutableElement element = - resolver.findMethod( - methodName, receiverType, pathToCompilationUnit, argumentTypes); - if (element != null) { - return element; - } - receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType); - } - - // Method not found. - throw constructJavaExpressionParseError(methodName, "no such method"); + ClassName className = getIdentifierAsInnerClassName(searchType, identifier); + if (className != null) { + return className; } + searchType = getTypeOfEnclosingClass(searchDeclaredType); + } + + setResolverField(); + + if (enclosingType.getKind() == TypeKind.DECLARED) { + // Is identifier in the same package as this? + PackageSymbol packageSymbol = + (PackageSymbol) + ElementUtils.enclosingPackage(((DeclaredType) enclosingType).asElement()); + ClassSymbol classSymbol = + resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); + if (classSymbol != null) { + return new ClassName(classSymbol.asType()); + } + } + // Is identifier a simple name for a class in java.lang? + Symbol.PackageSymbol packageSymbol = resolver.findPackage("java.lang", pathToCompilationUnit); + if (packageSymbol == null) { + throw new BugInCF("Can't find java.lang package."); + } + ClassSymbol classSymbol = + resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); + if (classSymbol != null) { + return new ClassName(classSymbol.asType()); + } + + // Is identifier a class in the unnamed package? + Element classElem = resolver.findClass(identifier, pathToCompilationUnit); + if (classElem != null) { + PackageElement pkg = ElementUtils.enclosingPackage(classElem); + if (pkg != null && pkg.isUnnamed()) { + TypeMirror classType = ElementUtils.getType(classElem); + if (classType != null) { + return new ClassName(classType); + } + } + } - // `expr` should be a field access, a fully qualified class name, or a class name qualified - // with another class name (e.g. {@code OuterClass.InnerClass}). - // If the expression refers to a class that is not available to the resolver (the class - // wasn't passed to javac on the command line), then the argument can be - // "outerpackage.innerpackage", which will lead to a confusing error message. - @Override - public JavaExpression visit(FieldAccessExpr expr, Void aVoid) { - setResolverField(); - - Expression scope = expr.getScope(); - String name = expr.getNameAsString(); - - // Check for fully qualified class name. - Symbol.PackageSymbol packageSymbol = - resolver.findPackage(scope.toString(), pathToCompilationUnit); - if (packageSymbol != null) { - ClassSymbol classSymbol = - resolver.findClassInPackage(name, packageSymbol, pathToCompilationUnit); - if (classSymbol != null) { - return new ClassName(classSymbol.asType()); - } - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - "could not find class " - + expr.getNameAsString() - + " in package " - + scope.toString())); - } - - JavaExpression receiver = scope.accept(this, null); - - // Check for field access expression. - FieldAccess fieldAccess = getIdentifierAsFieldAccess(receiver, name); - if (fieldAccess != null) { - return fieldAccess; - } - - // Check for inner class. - ClassName classType = getIdentifierAsInnerClassName(receiver.getType(), name); - if (classType != null) { - return classType; - } + return null; + } - throw new ParseRuntimeException( - constructJavaExpressionParseError( - name, - String.format("field or class %s not found in %s", name, receiver))); + /** + * Return the {@link FieldAccess} expression for the field with name {@code identifier} accessed + * via {@code receiverExpr}. If no such field exists, then {@code null} is returned. + * + * @param receiverExpr the receiver of the field access; the expression used to access the field + * @param identifier possibly a field name + * @return a field access, or null if {@code identifier} is not a field that can be accessed via + * {@code receiverExpr} + */ + protected @Nullable FieldAccess getIdentifierAsFieldAccess( + JavaExpression receiverExpr, String identifier) { + setResolverField(); + // Find the field element. + TypeMirror enclosingTypeOfField = receiverExpr.getType(); + VariableElement fieldElem; + if (identifier.equals("length") && enclosingTypeOfField.getKind() == TypeKind.ARRAY) { + fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); + if (fieldElem == null) { + throw new BugInCF("length field not found for type %s", enclosingTypeOfField); } - - // expr is a Class literal - @Override - public JavaExpression visit(ClassExpr expr, Void aVoid) { - TypeMirror result = convertTypeToTypeMirror(expr.getType()); - if (result == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), "it is an unparsable class literal")); - } - return new ClassName(result); + } else { + fieldElem = null; + // Search for field in each enclosing class. + while (enclosingTypeOfField.getKind() == TypeKind.DECLARED) { + fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); + if (fieldElem != null) { + break; + } + enclosingTypeOfField = getTypeOfEnclosingClass((DeclaredType) enclosingTypeOfField); } - - @Override - public JavaExpression visit(ArrayCreationExpr expr, Void aVoid) { - List<@Nullable JavaExpression> dimensions = - CollectionsPlume.mapList( - (ArrayCreationLevel dimension) -> - dimension - .getDimension() - .map(dim -> dim.accept(this, aVoid)) - .orElse(null), - expr.getLevels()); - - List initializers; - if (expr.getInitializer().isPresent()) { - initializers = - CollectionsPlume.mapList( - (Expression initializer) -> initializer.accept(this, null), - expr.getInitializer().get().getValues()); - } else { - initializers = Collections.emptyList(); - } - TypeMirror arrayType = convertTypeToTypeMirror(expr.getElementType()); - if (arrayType == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.getElementType().asString(), "type not parsable")); - } - for (int i = 0; i < dimensions.size(); i++) { - arrayType = TypesUtils.createArrayType(arrayType, env.getTypeUtils()); - } - return new ArrayCreation(arrayType, dimensions, initializers); + if (fieldElem == null) { + // field not found. + return null; } - - @Override - public JavaExpression visit(UnaryExpr expr, Void aVoid) { - Tree.Kind treeKind = javaParserUnaryOperatorToTreeKind(expr.getOperator()); - JavaExpression operand = expr.getExpression().accept(this, null); - // This eliminates + and performs constant-folding for -; it could also do so for other - // operations. - switch (treeKind) { - case UNARY_PLUS: - return operand; - case UNARY_MINUS: - if (operand instanceof ValueLiteral) { - return ((ValueLiteral) operand).negate(); - } - break; - default: - // Not optimization for this operand - break; - } - return new UnaryOperation(operand.getType(), treeKind, operand); + } + + // `fieldElem` is now set. Construct a FieldAccess expression. + + if (ElementUtils.isStatic(fieldElem)) { + Element classElem = fieldElem.getEnclosingElement(); + JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); + return new FieldAccess(staticClassReceiver, fieldElem); + } + + // fieldElem is an instance field. + + if (receiverExpr instanceof ClassName) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + fieldElem.getSimpleName().toString(), + "a non-static field cannot have a class name as a receiver.")); + } + + // There are two possibilities, captured by local variable fieldDeclaredInReceiverType: + // * true: it's an instance field declared in the type (or supertype) of receiverExpr. + // * false: it's an instance field declared in an enclosing type of receiverExpr. + + @SuppressWarnings("interning:not.interned") // Checking for exact object + boolean fieldDeclaredInReceiverType = enclosingTypeOfField == receiverExpr.getType(); + if (fieldDeclaredInReceiverType) { + TypeMirror fieldType = ElementUtils.getType(fieldElem); + return new FieldAccess(receiverExpr, fieldType, fieldElem); + } else { + if (!(receiverExpr instanceof ThisReference)) { + String msg = + String.format( + "%s is declared in an outer type of the type of the receiver" + + " expression, %s.", + identifier, receiverExpr); + throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg)); } - - /** - * Convert a JavaParser unary operator to a TreeKind. - * - * @param op a JavaParser unary operator - * @return a TreeKind for the unary operator - */ - private Tree.Kind javaParserUnaryOperatorToTreeKind(UnaryExpr.Operator op) { - switch (op) { - case BITWISE_COMPLEMENT: - return Tree.Kind.BITWISE_COMPLEMENT; - case LOGICAL_COMPLEMENT: - return Tree.Kind.LOGICAL_COMPLEMENT; - case MINUS: - return Tree.Kind.UNARY_MINUS; - case PLUS: - return Tree.Kind.UNARY_PLUS; - case POSTFIX_DECREMENT: - return Tree.Kind.POSTFIX_DECREMENT; - case POSTFIX_INCREMENT: - return Tree.Kind.POSTFIX_INCREMENT; - case PREFIX_DECREMENT: - return Tree.Kind.PREFIX_DECREMENT; - case PREFIX_INCREMENT: - return Tree.Kind.PREFIX_INCREMENT; - default: - throw new BugInCF("unhandled " + op); - } + TypeElement receiverTypeElement = TypesUtils.getTypeElement(receiverExpr.getType()); + if (receiverTypeElement == null || ElementUtils.isStatic(receiverTypeElement)) { + String msg = + String.format("%s is a non-static field declared in an outer type this.", identifier); + throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg)); } + JavaExpression locationOfField = new ThisReference(enclosingTypeOfField); + return new FieldAccess(locationOfField, fieldElem); + } + } - @Override - public JavaExpression visit(BinaryExpr expr, Void aVoid) { - JavaExpression leftJe = expr.getLeft().accept(this, null); - JavaExpression rightJe = expr.getRight().accept(this, null); - TypeMirror leftType = leftJe.getType(); - TypeMirror rightType = rightJe.getType(); - TypeMirror type; - // isSubtype() first does the cheaper test isSameType(), so no need to do it here. - if (types.isSubtype(leftType, rightType)) { - type = rightType; - } else if (types.isSubtype(rightType, leftType)) { - type = leftType; - } else if (expr.getOperator() == BinaryExpr.Operator.PLUS - && (TypesUtils.isString(leftType) || TypesUtils.isString(rightType))) { - type = stringTypeMirror; - } else { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - String.format( - "inconsistent types %s %s for %s", - leftType, rightType, expr))); - } - return new BinaryOperation( - type, javaParserBinaryOperatorToTreeKind(expr.getOperator()), leftJe, rightJe); + @Override + public JavaExpression visit(MethodCallExpr expr, Void aVoid) { + setResolverField(); + + JavaExpression receiverExpr; + if (expr.getScope().isPresent()) { + receiverExpr = expr.getScope().get().accept(this, null); + expr = expr.removeScope(); + } else if (thisReference != null) { + receiverExpr = thisReference; + } else { + receiverExpr = new ClassName(enclosingType); + } + + String methodName = expr.getNameAsString(); + + // parse argument list + List arguments = + CollectionsPlume.mapList(argument -> argument.accept(this, null), expr.getArguments()); + + ExecutableElement methodElement; + try { + methodElement = + getMethodElement( + methodName, receiverExpr.getType(), pathToCompilationUnit, arguments, resolver); + } catch (JavaExpressionParseException e) { + throw new ParseRuntimeException(e); + } + + // Box any arguments that require it. + for (int i = 0; i < arguments.size(); i++) { + VariableElement parameter = methodElement.getParameters().get(i); + TypeMirror parameterType = parameter.asType(); + JavaExpression argument = arguments.get(i); + TypeMirror argumentType = argument.getType(); + // is boxing necessary? + if (TypesUtils.isBoxedPrimitive(parameterType) && TypesUtils.isPrimitive(argumentType)) { + // boxing is necessary + MethodSymbol valueOfMethod = TreeBuilder.getValueOfMethod(env, parameterType); + JavaExpression boxedParam = + new MethodCall( + parameterType, + valueOfMethod, + new ClassName(parameterType), + Collections.singletonList(argument)); + arguments.set(i, boxedParam); + } + } + + // Build the MethodCall expression object. + if (ElementUtils.isStatic(methodElement)) { + Element classElem = methodElement.getEnclosingElement(); + JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); + return new MethodCall( + ElementUtils.getType(methodElement), methodElement, staticClassReceiver, arguments); + } else { + if (receiverExpr instanceof ClassName) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + "a non-static method call cannot have a class name as a" + " receiver")); } + TypeMirror methodType = + TypesUtils.substituteMethodReturnType(methodElement, receiverExpr.getType(), env); + return new MethodCall(methodType, methodElement, receiverExpr, arguments); + } + } - /** - * Convert a JavaParser binary operator to a TreeKind. - * - * @param op a JavaParser binary operator - * @return a TreeKind for the binary operator - */ - private Tree.Kind javaParserBinaryOperatorToTreeKind(BinaryExpr.Operator op) { - switch (op) { - case AND: - return Tree.Kind.CONDITIONAL_AND; - case BINARY_AND: - return Tree.Kind.AND; - case BINARY_OR: - return Tree.Kind.OR; - case DIVIDE: - return Tree.Kind.DIVIDE; - case EQUALS: - return Tree.Kind.EQUAL_TO; - case GREATER: - return Tree.Kind.GREATER_THAN; - case GREATER_EQUALS: - return Tree.Kind.GREATER_THAN_EQUAL; - case LEFT_SHIFT: - return Tree.Kind.LEFT_SHIFT; - case LESS: - return Tree.Kind.LESS_THAN; - case LESS_EQUALS: - return Tree.Kind.LESS_THAN_EQUAL; - case MINUS: - return Tree.Kind.MINUS; - case MULTIPLY: - return Tree.Kind.MULTIPLY; - case NOT_EQUALS: - return Tree.Kind.NOT_EQUAL_TO; - case OR: - return Tree.Kind.CONDITIONAL_OR; - case PLUS: - return Tree.Kind.PLUS; - case REMAINDER: - return Tree.Kind.REMAINDER; - case SIGNED_RIGHT_SHIFT: - return Tree.Kind.RIGHT_SHIFT; - case UNSIGNED_RIGHT_SHIFT: - return Tree.Kind.UNSIGNED_RIGHT_SHIFT; - case XOR: - return Tree.Kind.XOR; - default: - throw new BugInCF("unhandled " + op); - } + /** + * Returns the ExecutableElement for a method, or throws an exception. + * + *

(This method takes into account autoboxing.) + * + * @param methodName the method name + * @param receiverType the receiver type + * @param pathToCompilationUnit the path to the compilation unit + * @param arguments the arguments + * @param resolver the resolver + * @return the ExecutableElement for a method, or throws an exception + * @throws JavaExpressionParseException if the string cannot be parsed as a method name + */ + private ExecutableElement getMethodElement( + String methodName, + TypeMirror receiverType, + TreePath pathToCompilationUnit, + List arguments, + Resolver resolver) + throws JavaExpressionParseException { + + List argumentTypes = CollectionsPlume.mapList(JavaExpression::getType, arguments); + + if (receiverType.getKind() == TypeKind.ARRAY) { + ExecutableElement element = + resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes); + if (element == null) { + throw constructJavaExpressionParseError(methodName, "no such method"); } + return element; + } + + // Search for method in each enclosing class. + while (receiverType.getKind() == TypeKind.DECLARED) { + ExecutableElement element = + resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes); + if (element != null) { + return element; + } + receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType); + } + + // Method not found. + throw constructJavaExpressionParseError(methodName, "no such method"); + } - /** - * Converts the JavaParser type to a TypeMirror. Returns null if {@code type} is not - * handled; this method does not handle type variables, union types, or intersection types. - * - * @param type a JavaParser type - * @return a TypeMirror corresponding to {@code type}, or null if {@code type} isn't handled - */ - private @Nullable TypeMirror convertTypeToTypeMirror(Type type) { - if (type.isClassOrInterfaceType()) { - LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); - try { - return JavaParserUtil.parseExpression(type.asString(), currentSourceVersion) - .accept(this, null) - .getType(); - } catch (ParseProblemException e) { - return null; - } - } else if (type.isPrimitiveType()) { - switch (type.asPrimitiveType().getType()) { - case BOOLEAN: - return types.getPrimitiveType(TypeKind.BOOLEAN); - case BYTE: - return types.getPrimitiveType(TypeKind.BYTE); - case SHORT: - return types.getPrimitiveType(TypeKind.SHORT); - case INT: - return types.getPrimitiveType(TypeKind.INT); - case CHAR: - return types.getPrimitiveType(TypeKind.CHAR); - case FLOAT: - return types.getPrimitiveType(TypeKind.FLOAT); - case LONG: - return types.getPrimitiveType(TypeKind.LONG); - case DOUBLE: - return types.getPrimitiveType(TypeKind.DOUBLE); - } - } else if (type.isVoidType()) { - return types.getNoType(TypeKind.VOID); - } else if (type.isArrayType()) { - TypeMirror componentType = - convertTypeToTypeMirror(type.asArrayType().getComponentType()); - if (componentType == null) { - return null; - } - return types.getArrayType(componentType); - } - return null; + // `expr` should be a field access, a fully qualified class name, or a class name qualified + // with another class name (e.g. {@code OuterClass.InnerClass}). + // If the expression refers to a class that is not available to the resolver (the class + // wasn't passed to javac on the command line), then the argument can be + // "outerpackage.innerpackage", which will lead to a confusing error message. + @Override + public JavaExpression visit(FieldAccessExpr expr, Void aVoid) { + setResolverField(); + + Expression scope = expr.getScope(); + String name = expr.getNameAsString(); + + // Check for fully qualified class name. + Symbol.PackageSymbol packageSymbol = + resolver.findPackage(scope.toString(), pathToCompilationUnit); + if (packageSymbol != null) { + ClassSymbol classSymbol = + resolver.findClassInPackage(name, packageSymbol, pathToCompilationUnit); + if (classSymbol != null) { + return new ClassName(classSymbol.asType()); } + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + "could not find class " + + expr.getNameAsString() + + " in package " + + scope.toString())); + } + + JavaExpression receiver = scope.accept(this, null); + + // Check for field access expression. + FieldAccess fieldAccess = getIdentifierAsFieldAccess(receiver, name); + if (fieldAccess != null) { + return fieldAccess; + } + + // Check for inner class. + ClassName classType = getIdentifierAsInnerClassName(receiver.getType(), name); + if (classType != null) { + return classType; + } + + throw new ParseRuntimeException( + constructJavaExpressionParseError( + name, String.format("field or class %s not found in %s", name, receiver))); + } + + // expr is a Class literal + @Override + public JavaExpression visit(ClassExpr expr, Void aVoid) { + TypeMirror result = convertTypeToTypeMirror(expr.getType()); + if (result == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), "it is an unparsable class literal")); + } + return new ClassName(result); + } + + @Override + public JavaExpression visit(ArrayCreationExpr expr, Void aVoid) { + List<@Nullable JavaExpression> dimensions = + CollectionsPlume.mapList( + (ArrayCreationLevel dimension) -> + dimension.getDimension().map(dim -> dim.accept(this, aVoid)).orElse(null), + expr.getLevels()); + + List initializers; + if (expr.getInitializer().isPresent()) { + initializers = + CollectionsPlume.mapList( + (Expression initializer) -> initializer.accept(this, null), + expr.getInitializer().get().getValues()); + } else { + initializers = Collections.emptyList(); + } + TypeMirror arrayType = convertTypeToTypeMirror(expr.getElementType()); + if (arrayType == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.getElementType().asString(), "type not parsable")); + } + for (int i = 0; i < dimensions.size(); i++) { + arrayType = TypesUtils.createArrayType(arrayType, env.getTypeUtils()); + } + return new ArrayCreation(arrayType, dimensions, initializers); + } + + @Override + public JavaExpression visit(UnaryExpr expr, Void aVoid) { + Tree.Kind treeKind = javaParserUnaryOperatorToTreeKind(expr.getOperator()); + JavaExpression operand = expr.getExpression().accept(this, null); + // This eliminates + and performs constant-folding for -; it could also do so for other + // operations. + switch (treeKind) { + case UNARY_PLUS: + return operand; + case UNARY_MINUS: + if (operand instanceof ValueLiteral) { + return ((ValueLiteral) operand).negate(); + } + break; + default: + // Not optimization for this operand + break; + } + return new UnaryOperation(operand.getType(), treeKind, operand); } /** - * If {@code s} is exactly a formal parameter, return its 1-based index. Returns -1 otherwise. + * Convert a JavaParser unary operator to a TreeKind. * - * @param s a Java expression - * @return the 1-based index of the formal parameter that {@code s} represents, or -1 + * @param op a JavaParser unary operator + * @return a TreeKind for the unary operator */ - public static int parameterIndex(String s) { - Matcher matcher = ANCHORED_PARAMETER_PATTERN.matcher(s); - if (matcher.find()) { - @SuppressWarnings( - "nullness:assignment") // group 1 is non-null due to the structure of the regex - @NonNull String group1 = matcher.group(1); - return Integer.parseInt(group1); - } - return -1; + private Tree.Kind javaParserUnaryOperatorToTreeKind(UnaryExpr.Operator op) { + switch (op) { + case BITWISE_COMPLEMENT: + return Tree.Kind.BITWISE_COMPLEMENT; + case LOGICAL_COMPLEMENT: + return Tree.Kind.LOGICAL_COMPLEMENT; + case MINUS: + return Tree.Kind.UNARY_MINUS; + case PLUS: + return Tree.Kind.UNARY_PLUS; + case POSTFIX_DECREMENT: + return Tree.Kind.POSTFIX_DECREMENT; + case POSTFIX_INCREMENT: + return Tree.Kind.POSTFIX_INCREMENT; + case PREFIX_DECREMENT: + return Tree.Kind.PREFIX_DECREMENT; + case PREFIX_INCREMENT: + return Tree.Kind.PREFIX_INCREMENT; + default: + throw new BugInCF("unhandled " + op); + } } - /////////////////////////////////////////////////////////////////////////// - /// Contexts - /// + @Override + public JavaExpression visit(BinaryExpr expr, Void aVoid) { + JavaExpression leftJe = expr.getLeft().accept(this, null); + JavaExpression rightJe = expr.getRight().accept(this, null); + TypeMirror leftType = leftJe.getType(); + TypeMirror rightType = rightJe.getType(); + TypeMirror type; + // isSubtype() first does the cheaper test isSameType(), so no need to do it here. + if (types.isSubtype(leftType, rightType)) { + type = rightType; + } else if (types.isSubtype(rightType, leftType)) { + type = leftType; + } else if (expr.getOperator() == BinaryExpr.Operator.PLUS + && (TypesUtils.isString(leftType) || TypesUtils.isString(rightType))) { + type = stringTypeMirror; + } else { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + String.format("inconsistent types %s %s for %s", leftType, rightType, expr))); + } + return new BinaryOperation( + type, javaParserBinaryOperatorToTreeKind(expr.getOperator()), leftJe, rightJe); + } /** - * Returns the type of the innermost enclosing class. Returns Type.noType if the type is a - * top-level class. + * Convert a JavaParser binary operator to a TreeKind. * - *

If the innermost enclosing class is static, this method returns the type of that class. By - * contrast, {@link DeclaredType#getEnclosingType()} returns the type of the innermost enclosing - * class that is not static. - * - * @param type a DeclaredType - * @return the type of the innermost enclosing class or Type.noType + * @param op a JavaParser binary operator + * @return a TreeKind for the binary operator */ - private static TypeMirror getTypeOfEnclosingClass(DeclaredType type) { - if (type instanceof ClassType) { - // enclClass() needs to be called on tsym.owner, because tsym.enclClass() == tsym. - Symbol sym = ((ClassType) type).tsym.owner; - if (sym == null) { - return com.sun.tools.javac.code.Type.noType; - } - - ClassSymbol cs = sym.enclClass(); - if (cs == null) { - return com.sun.tools.javac.code.Type.noType; - } - - return cs.asType(); - } else { - return type.getEnclosingType(); - } + private Tree.Kind javaParserBinaryOperatorToTreeKind(BinaryExpr.Operator op) { + switch (op) { + case AND: + return Tree.Kind.CONDITIONAL_AND; + case BINARY_AND: + return Tree.Kind.AND; + case BINARY_OR: + return Tree.Kind.OR; + case DIVIDE: + return Tree.Kind.DIVIDE; + case EQUALS: + return Tree.Kind.EQUAL_TO; + case GREATER: + return Tree.Kind.GREATER_THAN; + case GREATER_EQUALS: + return Tree.Kind.GREATER_THAN_EQUAL; + case LEFT_SHIFT: + return Tree.Kind.LEFT_SHIFT; + case LESS: + return Tree.Kind.LESS_THAN; + case LESS_EQUALS: + return Tree.Kind.LESS_THAN_EQUAL; + case MINUS: + return Tree.Kind.MINUS; + case MULTIPLY: + return Tree.Kind.MULTIPLY; + case NOT_EQUALS: + return Tree.Kind.NOT_EQUAL_TO; + case OR: + return Tree.Kind.CONDITIONAL_OR; + case PLUS: + return Tree.Kind.PLUS; + case REMAINDER: + return Tree.Kind.REMAINDER; + case SIGNED_RIGHT_SHIFT: + return Tree.Kind.RIGHT_SHIFT; + case UNSIGNED_RIGHT_SHIFT: + return Tree.Kind.UNSIGNED_RIGHT_SHIFT; + case XOR: + return Tree.Kind.XOR; + default: + throw new BugInCF("unhandled " + op); + } } - /////////////////////////////////////////////////////////////////////////// - /// Exceptions - /// - /** - * An exception that indicates a parse error. Call {@link #getDiagMessage} to obtain a {@link - * DiagMessage} that can be used for error reporting. + * Converts the JavaParser type to a TypeMirror. Returns null if {@code type} is not handled; + * this method does not handle type variables, union types, or intersection types. + * + * @param type a JavaParser type + * @return a TypeMirror corresponding to {@code type}, or null if {@code type} isn't handled */ - public static class JavaExpressionParseException extends Exception { - /** The serial version identifier. */ - private static final long serialVersionUID = 2L; - - /** The error message key. */ - private final @CompilerMessageKey String errorKey; - - /** The arguments to the error message key. */ - @SuppressWarnings( - "serial") // I do not intend to serialize JavaExpressionParseException objects - public final Object[] args; - - /** - * Create a new JavaExpressionParseException. - * - * @param errorKey the error message key - * @param args the arguments to the error message key - */ - public JavaExpressionParseException(@CompilerMessageKey String errorKey, Object... args) { - this(null, errorKey, args); + private @Nullable TypeMirror convertTypeToTypeMirror(Type type) { + if (type.isClassOrInterfaceType()) { + LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); + try { + return JavaParserUtil.parseExpression(type.asString(), currentSourceVersion) + .accept(this, null) + .getType(); + } catch (ParseProblemException e) { + return null; } - - /** - * Create a new JavaExpressionParseException. - * - * @param cause cause - * @param errorKey the error message key - * @param args the arguments to the error message key - */ - public JavaExpressionParseException( - @Nullable Throwable cause, @CompilerMessageKey String errorKey, Object... args) { - super(cause); - this.errorKey = errorKey; - this.args = args; + } else if (type.isPrimitiveType()) { + switch (type.asPrimitiveType().getType()) { + case BOOLEAN: + return types.getPrimitiveType(TypeKind.BOOLEAN); + case BYTE: + return types.getPrimitiveType(TypeKind.BYTE); + case SHORT: + return types.getPrimitiveType(TypeKind.SHORT); + case INT: + return types.getPrimitiveType(TypeKind.INT); + case CHAR: + return types.getPrimitiveType(TypeKind.CHAR); + case FLOAT: + return types.getPrimitiveType(TypeKind.FLOAT); + case LONG: + return types.getPrimitiveType(TypeKind.LONG); + case DOUBLE: + return types.getPrimitiveType(TypeKind.DOUBLE); } - - @Override - public String getMessage() { - return errorKey + " " + Arrays.toString(args); + } else if (type.isVoidType()) { + return types.getNoType(TypeKind.VOID); + } else if (type.isArrayType()) { + TypeMirror componentType = convertTypeToTypeMirror(type.asArrayType().getComponentType()); + if (componentType == null) { + return null; } + return types.getArrayType(componentType); + } + return null; + } + } + + /** + * If {@code s} is exactly a formal parameter, return its 1-based index. Returns -1 otherwise. + * + * @param s a Java expression + * @return the 1-based index of the formal parameter that {@code s} represents, or -1 + */ + public static int parameterIndex(String s) { + Matcher matcher = ANCHORED_PARAMETER_PATTERN.matcher(s); + if (matcher.find()) { + @SuppressWarnings( + "nullness:assignment") // group 1 is non-null due to the structure of the regex + @NonNull String group1 = matcher.group(1); + return Integer.parseInt(group1); + } + return -1; + } + + /////////////////////////////////////////////////////////////////////////// + /// Contexts + /// + + /** + * Returns the type of the innermost enclosing class. Returns Type.noType if the type is a + * top-level class. + * + *

If the innermost enclosing class is static, this method returns the type of that class. By + * contrast, {@link DeclaredType#getEnclosingType()} returns the type of the innermost enclosing + * class that is not static. + * + * @param type a DeclaredType + * @return the type of the innermost enclosing class or Type.noType + */ + private static TypeMirror getTypeOfEnclosingClass(DeclaredType type) { + if (type instanceof ClassType) { + // enclClass() needs to be called on tsym.owner, because tsym.enclClass() == tsym. + Symbol sym = ((ClassType) type).tsym.owner; + if (sym == null) { + return com.sun.tools.javac.code.Type.noType; + } + + ClassSymbol cs = sym.enclClass(); + if (cs == null) { + return com.sun.tools.javac.code.Type.noType; + } + + return cs.asType(); + } else { + return type.getEnclosingType(); + } + } - /** - * Return a DiagMessage that can be used for error reporting. - * - * @return a DiagMessage that can be used for error reporting - */ - public DiagMessage getDiagMessage() { - return new DiagMessage(Diagnostic.Kind.ERROR, errorKey, args); - } + /////////////////////////////////////////////////////////////////////////// + /// Exceptions + /// - public boolean isFlowParseError() { - return errorKey.endsWith("flowexpr.parse.error"); - } + /** + * An exception that indicates a parse error. Call {@link #getDiagMessage} to obtain a {@link + * DiagMessage} that can be used for error reporting. + */ + public static class JavaExpressionParseException extends Exception { + /** The serial version identifier. */ + private static final long serialVersionUID = 2L; + + /** The error message key. */ + private final @CompilerMessageKey String errorKey; + + /** The arguments to the error message key. */ + @SuppressWarnings("serial") // I do not intend to serialize JavaExpressionParseException objects + public final Object[] args; + + /** + * Create a new JavaExpressionParseException. + * + * @param errorKey the error message key + * @param args the arguments to the error message key + */ + public JavaExpressionParseException(@CompilerMessageKey String errorKey, Object... args) { + this(null, errorKey, args); } /** - * Returns a {@link JavaExpressionParseException} with error key "flowexpr.parse.error" for the - * expression {@code expr} with explanation {@code explanation}. + * Create a new JavaExpressionParseException. * - * @param expr the string that could not be parsed - * @param explanation an explanation of the parse failure - * @return a {@link JavaExpressionParseException} for the expression {@code expr} with - * explanation {@code explanation}. + * @param cause cause + * @param errorKey the error message key + * @param args the arguments to the error message key */ - private static JavaExpressionParseException constructJavaExpressionParseError( - String expr, String explanation) { - if (expr == null) { - throw new BugInCF("Must have an expression."); - } - if (explanation == null) { - throw new BugInCF("Must have an explanation."); - } - return new JavaExpressionParseException( - (Throwable) null, - "flowexpr.parse.error", - "Invalid '" + expr + "' because " + explanation); + public JavaExpressionParseException( + @Nullable Throwable cause, @CompilerMessageKey String errorKey, Object... args) { + super(cause); + this.errorKey = errorKey; + this.args = args; + } + + @Override + public String getMessage() { + return errorKey + " " + Arrays.toString(args); } /** - * The unchecked exception equivalent of checked exception {@link JavaExpressionParseException}. + * Return a DiagMessage that can be used for error reporting. + * + * @return a DiagMessage that can be used for error reporting */ - private static class ParseRuntimeException extends RuntimeException { - private static final long serialVersionUID = 2L; - private final JavaExpressionParseException exception; + public DiagMessage getDiagMessage() { + return new DiagMessage(Diagnostic.Kind.ERROR, errorKey, args); + } - private ParseRuntimeException(JavaExpressionParseException exception) { - this.exception = exception; - } + public boolean isFlowParseError() { + return errorKey.endsWith("flowexpr.parse.error"); + } + } + + /** + * Returns a {@link JavaExpressionParseException} with error key "flowexpr.parse.error" for the + * expression {@code expr} with explanation {@code explanation}. + * + * @param expr the string that could not be parsed + * @param explanation an explanation of the parse failure + * @return a {@link JavaExpressionParseException} for the expression {@code expr} with explanation + * {@code explanation}. + */ + private static JavaExpressionParseException constructJavaExpressionParseError( + String expr, String explanation) { + if (expr == null) { + throw new BugInCF("Must have an expression."); + } + if (explanation == null) { + throw new BugInCF("Must have an explanation."); + } + return new JavaExpressionParseException( + (Throwable) null, "flowexpr.parse.error", "Invalid '" + expr + "' because " + explanation); + } + + /** + * The unchecked exception equivalent of checked exception {@link JavaExpressionParseException}. + */ + private static class ParseRuntimeException extends RuntimeException { + private static final long serialVersionUID = 2L; + private final JavaExpressionParseException exception; + + private ParseRuntimeException(JavaExpressionParseException exception) { + this.exception = exception; + } - private JavaExpressionParseException getCheckedException() { - return exception; - } + private JavaExpressionParseException getCheckedException() { + return exception; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java b/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java index 870018a637e..162bf377f75 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java @@ -19,16 +19,13 @@ import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; - -import org.checkerframework.javacutil.BugInCF; - import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; import java.util.Optional; - import javax.annotation.processing.ProcessingEnvironment; +import org.checkerframework.javacutil.BugInCF; /** * Utility methods for working with JavaParser. It is a replacement for StaticJavaParser that does @@ -36,391 +33,389 @@ */ public class JavaParserUtil { - /** - * The Language Level to use when parsing if a specific level isn't applied. This should be the - * highest version of Java that the Checker Framework can process. - */ - // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as of version - // 3.25.1 (2023-02-28). See - // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . - public static final LanguageLevel DEFAULT_LANGUAGE_LEVEL = LanguageLevel.JAVA_17; + /** + * The Language Level to use when parsing if a specific level isn't applied. This should be the + * highest version of Java that the Checker Framework can process. + */ + // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as of version + // 3.25.1 (2023-02-28). See + // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . + public static final LanguageLevel DEFAULT_LANGUAGE_LEVEL = LanguageLevel.JAVA_17; - /// - /// Replacements for StaticJavaParser - /// + /// + /// Replacements for StaticJavaParser + /// - /** - * Parses the Java code contained in the {@code InputStream} and returns a {@code - * CompilationUnit} that represents it. - * - *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because - * it creates a new instance of JavaParser each time it is invoked. Re-using {@code - * StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param inputStream the Java source code - * @return CompilationUnit representing the Java source code - * @throws ParseProblemException if the source code has parser errors - */ - public static CompilationUnit parseCompilationUnit(InputStream inputStream) { - ParserConfiguration parserConfiguration = new ParserConfiguration(); - parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - JavaParser javaParser = new JavaParser(parserConfiguration); - ParseResult parseResult = javaParser.parse(inputStream); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); - } + /** + * Parses the Java code contained in the {@code InputStream} and returns a {@code CompilationUnit} + * that represents it. + * + *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it + * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} + * causes memory problems because it retains too much memory. + * + * @param inputStream the Java source code + * @return CompilationUnit representing the Java source code + * @throws ParseProblemException if the source code has parser errors + */ + public static CompilationUnit parseCompilationUnit(InputStream inputStream) { + ParserConfiguration parserConfiguration = new ParserConfiguration(); + parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + JavaParser javaParser = new JavaParser(parserConfiguration); + ParseResult parseResult = javaParser.parse(inputStream); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); } + } - /** - * Parses the Java code contained in the {@code File} and returns a {@code CompilationUnit} that - * represents it. - * - *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because - * it creates a new instance of JavaParser each time it is invoked. Re-using {@code - * StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param file the Java source code - * @return CompilationUnit representing the Java source code - * @throws ParseProblemException if the source code has parser errors - * @throws FileNotFoundException if the file was not found - */ - public static CompilationUnit parseCompilationUnit(File file) throws FileNotFoundException { - ParserConfiguration configuration = new ParserConfiguration(); - configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - JavaParser javaParser = new JavaParser(configuration); - ParseResult parseResult = javaParser.parse(file); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); - } + /** + * Parses the Java code contained in the {@code File} and returns a {@code CompilationUnit} that + * represents it. + * + *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it + * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} + * causes memory problems because it retains too much memory. + * + * @param file the Java source code + * @return CompilationUnit representing the Java source code + * @throws ParseProblemException if the source code has parser errors + * @throws FileNotFoundException if the file was not found + */ + public static CompilationUnit parseCompilationUnit(File file) throws FileNotFoundException { + ParserConfiguration configuration = new ParserConfiguration(); + configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + JavaParser javaParser = new JavaParser(configuration); + ParseResult parseResult = javaParser.parse(file); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); } + } - /** - * Parses the Java code contained in the {@code String} and returns a {@code CompilationUnit} - * that represents it. - * - *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because - * it creates a new instance of JavaParser each time it is invoked. Re-using {@code - * StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param javaSource the Java source code - * @return CompilationUnit representing the Java source code - * @throws ParseProblemException if the source code has parser errors - */ - public static CompilationUnit parseCompilationUnit(String javaSource) { - ParserConfiguration parserConfiguration = new ParserConfiguration(); - parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - JavaParser javaParser = new JavaParser(parserConfiguration); - ParseResult parseResult = javaParser.parse(javaSource); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); - } + /** + * Parses the Java code contained in the {@code String} and returns a {@code CompilationUnit} that + * represents it. + * + *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it + * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} + * causes memory problems because it retains too much memory. + * + * @param javaSource the Java source code + * @return CompilationUnit representing the Java source code + * @throws ParseProblemException if the source code has parser errors + */ + public static CompilationUnit parseCompilationUnit(String javaSource) { + ParserConfiguration parserConfiguration = new ParserConfiguration(); + parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + JavaParser javaParser = new JavaParser(parserConfiguration); + ParseResult parseResult = javaParser.parse(javaSource); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); } + } - /** - * Parses the stub file contained in the {@code InputStream} and returns a {@code StubUnit} that - * represents it. - * - *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because - * it creates a new instance of JavaParser each time it is invoked. Re-using {@code - * StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param inputStream the stub file - * @return StubUnit representing the stub file - * @throws ParseProblemException if the source code has parser errors - */ - public static StubUnit parseStubUnit(InputStream inputStream) { - // The ParserConfiguration accumulates data each time parse is called, so create a new one - // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a - // JavaParser has to be created each time. - ParserConfiguration configuration = new ParserConfiguration(); - configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - // Store the tokens so that errors have line and column numbers. - // configuration.setStoreTokens(false); - configuration.setLexicalPreservationEnabled(false); - configuration.setAttributeComments(false); - configuration.setDetectOriginalLineSeparator(false); - JavaParser javaParser = new JavaParser(configuration); - ParseResult parseResult = javaParser.parseStubUnit(inputStream); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); - } + /** + * Parses the stub file contained in the {@code InputStream} and returns a {@code StubUnit} that + * represents it. + * + *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it + * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} + * causes memory problems because it retains too much memory. + * + * @param inputStream the stub file + * @return StubUnit representing the stub file + * @throws ParseProblemException if the source code has parser errors + */ + public static StubUnit parseStubUnit(InputStream inputStream) { + // The ParserConfiguration accumulates data each time parse is called, so create a new one + // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a + // JavaParser has to be created each time. + ParserConfiguration configuration = new ParserConfiguration(); + configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + // Store the tokens so that errors have line and column numbers. + // configuration.setStoreTokens(false); + configuration.setLexicalPreservationEnabled(false); + configuration.setAttributeComments(false); + configuration.setDetectOriginalLineSeparator(false); + JavaParser javaParser = new JavaParser(configuration); + ParseResult parseResult = javaParser.parseStubUnit(inputStream); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); } + } - /** - * Parses the {@code expression} and returns an {@code Expression} that represents it. - * - *

This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory - * leaks because it creates a new instance of JavaParser each time it is invoked. Re-using - * {@code StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param expression the expression string - * @return the parsed expression - * @throws ParseProblemException if the expression has parser errors - */ - public static Expression parseExpression(String expression) { - return parseExpression(expression, DEFAULT_LANGUAGE_LEVEL); - } + /** + * Parses the {@code expression} and returns an {@code Expression} that represents it. + * + *

This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory leaks + * because it creates a new instance of JavaParser each time it is invoked. Re-using {@code + * StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param expression the expression string + * @return the parsed expression + * @throws ParseProblemException if the expression has parser errors + */ + public static Expression parseExpression(String expression) { + return parseExpression(expression, DEFAULT_LANGUAGE_LEVEL); + } - /** - * Parses the {@code expression} and returns an {@code Expression} that represents it. - * - *

This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory - * leaks because it creates a new instance of JavaParser each time it is invoked. Re-using - * {@code StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param expression the expression string - * @param languageLevel the language level to use when parsing the Java source - * @return the parsed expression - * @throws ParseProblemException if the expression has parser errors - */ - public static Expression parseExpression(String expression, LanguageLevel languageLevel) { - // The ParserConfiguration accumulates data each time parse is called, so create a new one - // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a - // JavaParser has to be created each time. - ParserConfiguration configuration = new ParserConfiguration(); - configuration.setLanguageLevel(languageLevel); - configuration.setStoreTokens(false); - configuration.setLexicalPreservationEnabled(false); - configuration.setAttributeComments(false); - configuration.setDetectOriginalLineSeparator(false); - JavaParser javaParser = new JavaParser(configuration); - ParseResult parseResult = javaParser.parseExpression(expression); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); - } + /** + * Parses the {@code expression} and returns an {@code Expression} that represents it. + * + *

This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory leaks + * because it creates a new instance of JavaParser each time it is invoked. Re-using {@code + * StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param expression the expression string + * @param languageLevel the language level to use when parsing the Java source + * @return the parsed expression + * @throws ParseProblemException if the expression has parser errors + */ + public static Expression parseExpression(String expression, LanguageLevel languageLevel) { + // The ParserConfiguration accumulates data each time parse is called, so create a new one + // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a + // JavaParser has to be created each time. + ParserConfiguration configuration = new ParserConfiguration(); + configuration.setLanguageLevel(languageLevel); + configuration.setStoreTokens(false); + configuration.setLexicalPreservationEnabled(false); + configuration.setAttributeComments(false); + configuration.setDetectOriginalLineSeparator(false); + JavaParser javaParser = new JavaParser(configuration); + ParseResult parseResult = javaParser.parseExpression(expression); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); } + } - /// - /// Other methods - /// + /// + /// Other methods + /// - /** - * Given the compilation unit node for a source file, returns the top level type definition with - * the given name. - * - * @param root compilation unit to search - * @param name name of a top level type declaration in {@code root} - * @return a top level type declaration in {@code root} named {@code name} - */ - public static TypeDeclaration getTypeDeclarationByName(CompilationUnit root, String name) { - Optional classDecl = root.getClassByName(name); - if (classDecl.isPresent()) { - return classDecl.get(); - } - - Optional interfaceDecl = root.getInterfaceByName(name); - if (interfaceDecl.isPresent()) { - return interfaceDecl.get(); - } - - Optional enumDecl = root.getEnumByName(name); - if (enumDecl.isPresent()) { - return enumDecl.get(); - } + /** + * Given the compilation unit node for a source file, returns the top level type definition with + * the given name. + * + * @param root compilation unit to search + * @param name name of a top level type declaration in {@code root} + * @return a top level type declaration in {@code root} named {@code name} + */ + public static TypeDeclaration getTypeDeclarationByName(CompilationUnit root, String name) { + Optional classDecl = root.getClassByName(name); + if (classDecl.isPresent()) { + return classDecl.get(); + } - Optional annoDecl = root.getAnnotationDeclarationByName(name); - if (annoDecl.isPresent()) { - return annoDecl.get(); - } + Optional interfaceDecl = root.getInterfaceByName(name); + if (interfaceDecl.isPresent()) { + return interfaceDecl.get(); + } - Optional recordDecl = getRecordByName(root, name); - if (recordDecl.isPresent()) { - return recordDecl.get(); - } + Optional enumDecl = root.getEnumByName(name); + if (enumDecl.isPresent()) { + return enumDecl.get(); + } - Optional storage = root.getStorage(); - if (storage.isPresent()) { - throw new BugInCF("Type " + name + " not found in " + storage.get().getPath()); - } else { - throw new BugInCF("Type " + name + " not found in " + root); - } + Optional annoDecl = root.getAnnotationDeclarationByName(name); + if (annoDecl.isPresent()) { + return annoDecl.get(); } - /** - * JavaParser's {@link CompilationUnit} class has methods like this for every other kind of - * class-like structure (e.g., classes, enums, annotation declarations, etc.), but not for - * records. This implementation is based on the implementation of {@link - * CompilationUnit#getClassByName(String)}, and has the same interface as the other, similar - * JavaParser methods (except that it is static and takes the CompilationUnit as a parameter, - * rather than being an instance method on the CompilationUnit). - * - * @param cu the CompilationUnit to search - * @param recordName the name of the record - * @return the record declaration in the compilation unit with the given name, or an empty - * Optional if no such record declaration exists - */ - private static Optional getRecordByName( - CompilationUnit cu, String recordName) { - return cu.getTypes().stream() - .filter( - (type) -> { - return type.getNameAsString().equals(recordName) - && type instanceof RecordDeclaration; - }) - .findFirst() - .map( - (t) -> { - return (RecordDeclaration) t; - }); + Optional recordDecl = getRecordByName(root, name); + if (recordDecl.isPresent()) { + return recordDecl.get(); } - /** - * Returns the fully qualified name of a type appearing in a given compilation unit. - * - * @param type a type declaration - * @param compilationUnit the compilation unit containing {@code type} - * @return the fully qualified name of {@code type} if {@code compilationUnit} contains a - * package declaration, or just the name of {@code type} otherwise - */ - public static String getFullyQualifiedName( - TypeDeclaration type, CompilationUnit compilationUnit) { - if (compilationUnit.getPackageDeclaration().isPresent()) { - return compilationUnit.getPackageDeclaration().get().getNameAsString() - + "." - + type.getNameAsString(); - } else { - return type.getNameAsString(); - } + Optional storage = root.getStorage(); + if (storage.isPresent()) { + throw new BugInCF("Type " + name + " not found in " + storage.get().getPath()); + } else { + throw new BugInCF("Type " + name + " not found in " + root); } + } + + /** + * JavaParser's {@link CompilationUnit} class has methods like this for every other kind of + * class-like structure (e.g., classes, enums, annotation declarations, etc.), but not for + * records. This implementation is based on the implementation of {@link + * CompilationUnit#getClassByName(String)}, and has the same interface as the other, similar + * JavaParser methods (except that it is static and takes the CompilationUnit as a parameter, + * rather than being an instance method on the CompilationUnit). + * + * @param cu the CompilationUnit to search + * @param recordName the name of the record + * @return the record declaration in the compilation unit with the given name, or an empty + * Optional if no such record declaration exists + */ + private static Optional getRecordByName( + CompilationUnit cu, String recordName) { + return cu.getTypes().stream() + .filter( + (type) -> { + return type.getNameAsString().equals(recordName) && type instanceof RecordDeclaration; + }) + .findFirst() + .map( + (t) -> { + return (RecordDeclaration) t; + }); + } - /** - * Side-effects {@code node} by removing all annotations from anywhere inside its subtree. - * - * @param node a JavaParser Node - */ - public static void clearAnnotations(Node node) { - node.accept(new ClearAnnotationsVisitor(), null); + /** + * Returns the fully qualified name of a type appearing in a given compilation unit. + * + * @param type a type declaration + * @param compilationUnit the compilation unit containing {@code type} + * @return the fully qualified name of {@code type} if {@code compilationUnit} contains a package + * declaration, or just the name of {@code type} otherwise + */ + public static String getFullyQualifiedName( + TypeDeclaration type, CompilationUnit compilationUnit) { + if (compilationUnit.getPackageDeclaration().isPresent()) { + return compilationUnit.getPackageDeclaration().get().getNameAsString() + + "." + + type.getNameAsString(); + } else { + return type.getNameAsString(); } + } - /** A visitor that clears all annotations from a JavaParser AST. */ - private static class ClearAnnotationsVisitor extends VoidVisitorWithDefaultAction { - @Override - public void defaultAction(Node node) { - for (Node child : new ArrayList<>(node.getChildNodes())) { - if (child instanceof AnnotationExpr) { - node.remove(child); - } - } - } + /** + * Side-effects {@code node} by removing all annotations from anywhere inside its subtree. + * + * @param node a JavaParser Node + */ + public static void clearAnnotations(Node node) { + node.accept(new ClearAnnotationsVisitor(), null); + } - @Override - public void visit(ArrayInitializerExpr node, Void p) { - // Do not remove annotations that are array elements. + /** A visitor that clears all annotations from a JavaParser AST. */ + private static class ClearAnnotationsVisitor extends VoidVisitorWithDefaultAction { + @Override + public void defaultAction(Node node) { + for (Node child : new ArrayList<>(node.getChildNodes())) { + if (child instanceof AnnotationExpr) { + node.remove(child); } + } } - /** - * Side-effects node by combining any added String literals in node's subtree into their - * concatenation. For example, the expression {@code "a" + "b"} becomes {@code "ab"}. This - * occurs even if, when reading from left to right, the two string literals are not added - * directly. For example, the expression {@code 1 + "a" + "b"} parses as {@code (1 + "a") + - * "b"}}, but it is transformed into {@code 1 + "ab"}. - * - *

This is the same transformation performed by javac automatically. Javac seems to ignore - * string literals surrounded in parentheses, so this method does as well. - * - * @param node a JavaParser Node - */ - public static void concatenateAddedStringLiterals(Node node) { - node.accept(new StringLiteralConcatenateVisitor(), null); + @Override + public void visit(ArrayInitializerExpr node, Void p) { + // Do not remove annotations that are array elements. } + } + + /** + * Side-effects node by combining any added String literals in node's subtree into their + * concatenation. For example, the expression {@code "a" + "b"} becomes {@code "ab"}. This occurs + * even if, when reading from left to right, the two string literals are not added directly. For + * example, the expression {@code 1 + "a" + "b"} parses as {@code (1 + "a") + "b"}}, but it is + * transformed into {@code 1 + "ab"}. + * + *

This is the same transformation performed by javac automatically. Javac seems to ignore + * string literals surrounded in parentheses, so this method does as well. + * + * @param node a JavaParser Node + */ + public static void concatenateAddedStringLiterals(Node node) { + node.accept(new StringLiteralConcatenateVisitor(), null); + } - /** Visitor that combines added String literals, see {@link #concatenateAddedStringLiterals}. */ - public static class StringLiteralConcatenateVisitor extends VoidVisitorAdapter { - @Override - public void visit(BinaryExpr node, Void p) { - super.visit(node, p); - if (node.getOperator() == BinaryExpr.Operator.PLUS - && node.getRight().isStringLiteralExpr()) { - String right = node.getRight().asStringLiteralExpr().getValue(); - if (node.getLeft().isStringLiteralExpr()) { - String left = node.getLeft().asStringLiteralExpr().getValue(); - node.replace(new StringLiteralExpr(left + right)); - } else if (node.getLeft().isBinaryExpr()) { - BinaryExpr leftExpr = node.getLeft().asBinaryExpr(); - if (leftExpr.getOperator() == BinaryExpr.Operator.PLUS - && leftExpr.getRight().isStringLiteralExpr()) { - String left = leftExpr.getRight().asStringLiteralExpr().getValue(); - node.replace( - new BinaryExpr( - leftExpr.getLeft(), - new StringLiteralExpr(left + right), - BinaryExpr.Operator.PLUS)); - } - } - } + /** Visitor that combines added String literals, see {@link #concatenateAddedStringLiterals}. */ + public static class StringLiteralConcatenateVisitor extends VoidVisitorAdapter { + @Override + public void visit(BinaryExpr node, Void p) { + super.visit(node, p); + if (node.getOperator() == BinaryExpr.Operator.PLUS && node.getRight().isStringLiteralExpr()) { + String right = node.getRight().asStringLiteralExpr().getValue(); + if (node.getLeft().isStringLiteralExpr()) { + String left = node.getLeft().asStringLiteralExpr().getValue(); + node.replace(new StringLiteralExpr(left + right)); + } else if (node.getLeft().isBinaryExpr()) { + BinaryExpr leftExpr = node.getLeft().asBinaryExpr(); + if (leftExpr.getOperator() == BinaryExpr.Operator.PLUS + && leftExpr.getRight().isStringLiteralExpr()) { + String left = leftExpr.getRight().asStringLiteralExpr().getValue(); + node.replace( + new BinaryExpr( + leftExpr.getLeft(), + new StringLiteralExpr(left + right), + BinaryExpr.Operator.PLUS)); + } } + } } + } - /** - * Initialized by {@link #getCurrentSourceVersion(ProcessingEnvironment)}. Use that method to - * access. - */ - private static LanguageLevel currentSourceVersion = null; + /** + * Initialized by {@link #getCurrentSourceVersion(ProcessingEnvironment)}. Use that method to + * access. + */ + private static LanguageLevel currentSourceVersion = null; - /** - * Returns the {@link com.github.javaparser.ParserConfiguration.LanguageLevel} corresponding to - * the current source version. - * - * @param env processing environment used to get source version - * @return the current source version - */ - public static ParserConfiguration.LanguageLevel getCurrentSourceVersion( - ProcessingEnvironment env) { - if (currentSourceVersion == null) { - // Use String comparison so we can compile on older JDKs which - // don't have all the latest SourceVersion constants: - switch (env.getSourceVersion().name()) { - case "RELEASE_8": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_8; - break; - case "RELEASE_9": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_9; - break; - case "RELEASE_10": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_10; - break; - case "RELEASE_11": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_11; - break; - case "RELEASE_12": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_12; - break; - case "RELEASE_13": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_13; - break; - case "RELEASE_14": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_14; - break; - case "RELEASE_15": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_15; - break; - case "RELEASE_16": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_16; - break; - case "RELEASE_17": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_17; - break; - // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as - // of version 3.25.1 (2023-02-28). See - // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . - // case "RELEASE_18": - // currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_18; - // break; - default: - currentSourceVersion = DEFAULT_LANGUAGE_LEVEL; - } - } - return currentSourceVersion; + /** + * Returns the {@link com.github.javaparser.ParserConfiguration.LanguageLevel} corresponding to + * the current source version. + * + * @param env processing environment used to get source version + * @return the current source version + */ + public static ParserConfiguration.LanguageLevel getCurrentSourceVersion( + ProcessingEnvironment env) { + if (currentSourceVersion == null) { + // Use String comparison so we can compile on older JDKs which + // don't have all the latest SourceVersion constants: + switch (env.getSourceVersion().name()) { + case "RELEASE_8": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_8; + break; + case "RELEASE_9": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_9; + break; + case "RELEASE_10": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_10; + break; + case "RELEASE_11": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_11; + break; + case "RELEASE_12": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_12; + break; + case "RELEASE_13": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_13; + break; + case "RELEASE_14": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_14; + break; + case "RELEASE_15": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_15; + break; + case "RELEASE_16": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_16; + break; + case "RELEASE_17": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_17; + break; + // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as + // of version 3.25.1 (2023-02-28). See + // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . + // case "RELEASE_18": + // currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_18; + // break; + default: + currentSourceVersion = DEFAULT_LANGUAGE_LEVEL; + } } + return currentSourceVersion; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java index 21c66caf9ef..270d948b8a3 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java @@ -2,57 +2,56 @@ import java.util.Collections; import java.util.Set; - import javax.lang.model.element.ExecutableElement; /** Dummy implementation of {@link ContractsFromMethod} that only returns empty sets. */ public class NoContractsFromMethod implements ContractsFromMethod { - /** Creates a NoContractsFromMethod object. */ - public NoContractsFromMethod() {} - - /** - * Returns an empty set. - * - * @param executableElement the method or constructor whose contracts to retrieve - * @return an empty set - */ - @Override - public Set getContracts(ExecutableElement executableElement) { - return Collections.emptySet(); - } - - /** - * Returns an empty set - * - * @param executableElement the method whose contracts to return - * @return an empty set - */ - @Override - public Set getPreconditions(ExecutableElement executableElement) { - return Collections.emptySet(); - } - - /** - * Returns an empty set - * - * @param executableElement the method whose contracts to return - * @return an empty set - */ - @Override - public Set getPostconditions(ExecutableElement executableElement) { - return Collections.emptySet(); - } - - /** - * Returns an empty set. - * - * @param methodElement the method whose contracts to return - * @return an empty set - */ - @Override - public Set getConditionalPostconditions( - ExecutableElement methodElement) { - return Collections.emptySet(); - } + /** Creates a NoContractsFromMethod object. */ + public NoContractsFromMethod() {} + + /** + * Returns an empty set. + * + * @param executableElement the method or constructor whose contracts to retrieve + * @return an empty set + */ + @Override + public Set getContracts(ExecutableElement executableElement) { + return Collections.emptySet(); + } + + /** + * Returns an empty set + * + * @param executableElement the method whose contracts to return + * @return an empty set + */ + @Override + public Set getPreconditions(ExecutableElement executableElement) { + return Collections.emptySet(); + } + + /** + * Returns an empty set + * + * @param executableElement the method whose contracts to return + * @return an empty set + */ + @Override + public Set getPostconditions(ExecutableElement executableElement) { + return Collections.emptySet(); + } + + /** + * Returns an empty set. + * + * @param methodElement the method whose contracts to return + * @return an empty set + */ + @Override + public Set getConditionalPostconditions( + ExecutableElement methodElement) { + return Collections.emptySet(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java b/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java index d941f910cb1..6d24142e38e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java +++ b/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java @@ -1,127 +1,126 @@ package org.checkerframework.framework.util; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.source.SupportedOptions; - import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.source.SupportedOptions; /** Provides methods for querying the Checker's options. */ public interface OptionConfiguration { - /** - * Return all active options for this checker. - * - * @return all active options for this checker - */ - Map getOptions(); + /** + * Return all active options for this checker. + * + * @return all active options for this checker + */ + Map getOptions(); - /** - * Check whether the given option is provided. - * - *

Note that {@link #getOption} can still return null even if {@code hasOption} returns true: - * this happens e.g. for {@code -Amyopt} - * - * @param name the name of the option to check - * @return true if the option name was provided, false otherwise - */ - boolean hasOption(String name); + /** + * Check whether the given option is provided. + * + *

Note that {@link #getOption} can still return null even if {@code hasOption} returns true: + * this happens e.g. for {@code -Amyopt} + * + * @param name the name of the option to check + * @return true if the option name was provided, false otherwise + */ + boolean hasOption(String name); - /** - * Determines the value of the option with the given name. - * - *

Note that {@code getOption} can still return null even if {@link #hasOption} returns true: - * this happens e.g. for {@code -Amyopt} - * - * @param name the name of the option to check - * @return the value of the option with the given name - * @see #getOption(String,String) - */ - @Nullable String getOption(String name); + /** + * Determines the value of the option with the given name. + * + *

Note that {@code getOption} can still return null even if {@link #hasOption} returns true: + * this happens e.g. for {@code -Amyopt} + * + * @param name the name of the option to check + * @return the value of the option with the given name + * @see #getOption(String,String) + */ + @Nullable String getOption(String name); - /** - * Determines the boolean value of the option with the given name. Returns {@code defaultValue} - * if the option is not set. - * - * @param name the name of the option to check - * @param defaultValue the default value to return if the option is not set - * @return the value of the option with the given name, or {@code defaultValue} - * @see #getOption(String) - */ - String getOption(String name, String defaultValue); + /** + * Determines the boolean value of the option with the given name. Returns {@code defaultValue} if + * the option is not set. + * + * @param name the name of the option to check + * @param defaultValue the default value to return if the option is not set + * @return the value of the option with the given name, or {@code defaultValue} + * @see #getOption(String) + */ + String getOption(String name, String defaultValue); - /** - * Determines the boolean value of the option with the given name. Returns false if the option - * is not set. - * - * @param name the name of the option to check - * @return the boolean value of the option - */ - boolean getBooleanOption(String name); + /** + * Determines the boolean value of the option with the given name. Returns false if the option is + * not set. + * + * @param name the name of the option to check + * @return the boolean value of the option + */ + boolean getBooleanOption(String name); - /** - * Determines the boolean value of the option with the given name. Returns the given default - * value if the option is not set. - * - * @param name the name of the option to check - * @param defaultValue the default value to use if the option is not set - * @return the boolean value of the option - */ - boolean getBooleanOption(String name, boolean defaultValue); + /** + * Determines the boolean value of the option with the given name. Returns the given default value + * if the option is not set. + * + * @param name the name of the option to check + * @param defaultValue the default value to use if the option is not set + * @return the boolean value of the option + */ + boolean getBooleanOption(String name, boolean defaultValue); - /** - * Determines the string list value of the option with the given name. The option's value is - * split on the given separator. Returns an empty list if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @return the list of options - */ - default List getStringsOption(String name, char separator) { - return getStringsOption(name, separator, Collections.emptyList()); - } + /** + * Determines the string list value of the option with the given name. The option's value is split + * on the given separator. Returns an empty list if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @return the list of options + */ + default List getStringsOption(String name, char separator) { + return getStringsOption(name, separator, Collections.emptyList()); + } - /** - * Determines the string list value of the option with the given name. The option's value is - * split on the given separator. Returns the given default value if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @param defaultValue the default value to use if the option is not set - * @return the list of options - */ - public List getStringsOption(String name, char separator, List defaultValue); + /** + * Determines the string list value of the option with the given name. The option's value is split + * on the given separator. Returns the given default value if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @param defaultValue the default value to use if the option is not set + * @return the list of options + */ + public List getStringsOption(String name, char separator, List defaultValue); - /** - * Determines the string list value of the option with the given name. The option's value is - * split on the given separator. Returns an empty list if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @return the list of options - */ - default List getStringsOption(String name, String separator) { - return getStringsOption(name, separator, Collections.emptyList()); - } + /** + * Determines the string list value of the option with the given name. The option's value is split + * on the given separator. Returns an empty list if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @return the list of options + */ + default List getStringsOption(String name, String separator) { + return getStringsOption(name, separator, Collections.emptyList()); + } - /** - * Determines the string list value of the option with the given name. The option's value is - * split on the given separator. Returns the given default value if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @param defaultValue the default value to use if the option is not set - * @return the list of options - */ - public List getStringsOption(String name, String separator, List defaultValue); + /** + * Determines the string list value of the option with the given name. The option's value is split + * on the given separator. Returns the given default value if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @param defaultValue the default value to use if the option is not set + * @return the list of options + */ + public List getStringsOption(String name, String separator, List defaultValue); - /** - * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation - * provided version {@link javax.annotation.processing.SupportedOptions}. - * - * @return the supported options - */ - Set getSupportedOptions(); + /** + * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation + * provided version {@link javax.annotation.processing.SupportedOptions}. + * + * @return the supported options + */ + Set getSupportedOptions(); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java index 8716f307c99..ff62ab479cc 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.util; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.qual.PurityUnqualified; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.PurityUnqualified; /** AnnotatedTypeFactory for the {@link PurityChecker}. */ public class PurityAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public PurityAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public PurityAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>(Arrays.asList(PurityUnqualified.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>(Arrays.asList(PurityUnqualified.class)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java b/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java index 0a23d711767..6d50face48b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java @@ -9,7 +9,7 @@ * flow-sensitive analysis */ public class PurityChecker extends BaseTypeChecker { - // There is no implementation here. - // It uses functionality from BaseTypeChecker, which itself calls - // dataflow's purity implementation. + // There is no implementation here. + // It uses functionality from BaseTypeChecker, which itself calls + // dataflow's purity implementation. } diff --git a/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java b/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java index e85a899a8a2..e06e9f35fa4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java +++ b/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.util; +import java.lang.annotation.Annotation; +import java.util.Set; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.framework.qual.AnnotatedFor; -import java.lang.annotation.Annotation; -import java.util.Set; - /** * Represents a kind of qualifier, which is an annotation class. Does not represent annotation * elements. If two qualifiers use the same annotation class, then they have the same qualifier @@ -27,99 +26,99 @@ @AnnotatedFor("nullness") public @Interned interface QualifierKind extends Comparable { - /** - * Returns the canonical name of the annotation class of this. - * - * @return the canonical name of the annotation class of this - */ - @Interned @CanonicalName String getName(); - - /** - * Returns the annotation class for this. - * - * @return the annotation class for this - */ - Class getAnnotationClass(); - - /** - * Returns the top qualifier kind of the hierarchy to which this qualifier kind belongs. - * - * @return the top qualifier kind of the hierarchy to which this qualifier kind belongs - */ - QualifierKind getTop(); - - /** - * Returns true if this is the top qualifier of its hierarchy. - * - * @return true if this is the top qualifier of its hierarchy - */ - boolean isTop(); - - /** - * Returns the bottom qualifier kind of the hierarchy to which this qualifier kind belongs. - * - * @return the bottom qualifier kind of the hierarchy to which this qualifier kind belongs - */ - QualifierKind getBottom(); - - /** - * Returns true if this is the bottom qualifier of its hierarchy. - * - * @return true if this is the bottom qualifier of its hierarchy - */ - boolean isBottom(); - - /** - * Returns the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, - * or null if one does not exist. - * - * @return the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, - * or null if one does not exist - */ - @Nullable QualifierKind getPolymorphic(); - - /** - * Returns true if this is polymorphic. - * - * @return true if this is polymorphic - */ - @Pure - boolean isPoly(); - - /** - * Returns true if the annotation class this qualifier kind represents has annotation - * elements/arguments. - * - * @return true if the annotation class this qualifier kind represents has elements/arguments - */ - boolean hasElements(); - - /** - * All the qualifier kinds that are a strict super qualifier of this qualifier. Does not include - * this qualifier kind itself. - * - * @return all the qualifier kinds that are a strict super qualifier of this qualifier - */ - Set getStrictSuperTypes(); - - /** - * Returns true if this and {@code other} are in the same hierarchy. - * - * @param other a qualifier kind - * @return true if this and {@code other} are in the same hierarchy - */ - boolean isInSameHierarchyAs(QualifierKind other); - - /** - * Returns true if this qualifier kind is a subtype of or equal to {@code superQualKind}. - * - * @param superQualKind other qualifier kind - * @return true if this qualifier kind is a subtype of or equal to {@code superQualKind} - */ - boolean isSubtypeOf(QualifierKind superQualKind); - - @Override - default int compareTo(QualifierKind o) { - return this.getName().compareTo(o.getName()); - } + /** + * Returns the canonical name of the annotation class of this. + * + * @return the canonical name of the annotation class of this + */ + @Interned @CanonicalName String getName(); + + /** + * Returns the annotation class for this. + * + * @return the annotation class for this + */ + Class getAnnotationClass(); + + /** + * Returns the top qualifier kind of the hierarchy to which this qualifier kind belongs. + * + * @return the top qualifier kind of the hierarchy to which this qualifier kind belongs + */ + QualifierKind getTop(); + + /** + * Returns true if this is the top qualifier of its hierarchy. + * + * @return true if this is the top qualifier of its hierarchy + */ + boolean isTop(); + + /** + * Returns the bottom qualifier kind of the hierarchy to which this qualifier kind belongs. + * + * @return the bottom qualifier kind of the hierarchy to which this qualifier kind belongs + */ + QualifierKind getBottom(); + + /** + * Returns true if this is the bottom qualifier of its hierarchy. + * + * @return true if this is the bottom qualifier of its hierarchy + */ + boolean isBottom(); + + /** + * Returns the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, + * or null if one does not exist. + * + * @return the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, + * or null if one does not exist + */ + @Nullable QualifierKind getPolymorphic(); + + /** + * Returns true if this is polymorphic. + * + * @return true if this is polymorphic + */ + @Pure + boolean isPoly(); + + /** + * Returns true if the annotation class this qualifier kind represents has annotation + * elements/arguments. + * + * @return true if the annotation class this qualifier kind represents has elements/arguments + */ + boolean hasElements(); + + /** + * All the qualifier kinds that are a strict super qualifier of this qualifier. Does not include + * this qualifier kind itself. + * + * @return all the qualifier kinds that are a strict super qualifier of this qualifier + */ + Set getStrictSuperTypes(); + + /** + * Returns true if this and {@code other} are in the same hierarchy. + * + * @param other a qualifier kind + * @return true if this and {@code other} are in the same hierarchy + */ + boolean isInSameHierarchyAs(QualifierKind other); + + /** + * Returns true if this qualifier kind is a subtype of or equal to {@code superQualKind}. + * + * @param superQualKind other qualifier kind + * @return true if this qualifier kind is a subtype of or equal to {@code superQualKind} + */ + boolean isSubtypeOf(QualifierKind superQualKind); + + @Override + default int compareTo(QualifierKind o) { + return this.getName().compareTo(o.getName()); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java index fab9d20f63e..1ab64cddd14 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.util; +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.framework.qual.AnnotatedFor; @@ -8,12 +12,6 @@ import org.checkerframework.framework.type.NoElementQualifierHierarchy; import org.checkerframework.javacutil.TypeSystemError; -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; - /** * This interface holds information about the subtyping relationships between kinds of qualifiers. A * "kind" of qualifier is its annotation class and is represented by the {@link QualifierKind} @@ -38,72 +36,70 @@ @AnnotatedFor("nullness") public interface QualifierKindHierarchy { - /** - * Returns the qualifier kinds that are the top qualifier in their hierarchies. - * - * @return the qualifier kinds that are the top qualifier in their hierarchies - */ - Set getTops(); + /** + * Returns the qualifier kinds that are the top qualifier in their hierarchies. + * + * @return the qualifier kinds that are the top qualifier in their hierarchies + */ + Set getTops(); - /** - * Returns the qualifier kinds that are the bottom qualifier in their hierarchies. - * - * @return the qualifier kinds that are the bottom qualifier in their hierarchies - */ - Set getBottoms(); + /** + * Returns the qualifier kinds that are the bottom qualifier in their hierarchies. + * + * @return the qualifier kinds that are the bottom qualifier in their hierarchies + */ + Set getBottoms(); - /** - * Returns the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier - * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always - * does). - * - * @param q1 a qualifier kind - * @param q2 a qualifier kind - * @return the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier - * kinds are not in the same hierarchy - */ - @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2); + /** + * Returns the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier + * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does). + * + * @param q1 a qualifier kind + * @param q2 a qualifier kind + * @return the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier + * kinds are not in the same hierarchy + */ + @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2); - /** - * Returns the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the - * qualifier kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind - * always does). - * - * @param q1 a qualifier kind - * @param q2 a qualifier kind - * @return the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the - * qualifier kinds are not in the same hierarchy - */ - @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2); + /** + * Returns the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier + * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does). + * + * @param q1 a qualifier kind + * @param q2 a qualifier kind + * @return the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier + * kinds are not in the same hierarchy + */ + @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2); - /** - * Returns a list of all {@link QualifierKind}s sorted in ascending order. - * - * @return a list of all {@link QualifierKind}s sorted in ascending order - */ - List allQualifierKinds(); + /** + * Returns a list of all {@link QualifierKind}s sorted in ascending order. + * + * @return a list of all {@link QualifierKind}s sorted in ascending order + */ + List allQualifierKinds(); - /** - * Returns the {@link QualifierKind} for the given annotation class name. Throws an exception if - * one does not exist. - * - * @param name canonical name of an annotation class - * @return the {@link QualifierKind} for the given annotation class name - */ - QualifierKind getQualifierKind(@CanonicalName String name); + /** + * Returns the {@link QualifierKind} for the given annotation class name. Throws an exception if + * one does not exist. + * + * @param name canonical name of an annotation class + * @return the {@link QualifierKind} for the given annotation class name + */ + QualifierKind getQualifierKind(@CanonicalName String name); - /** - * Returns the canonical name of {@code clazz}. Throws a {@link TypeSystemError} if {@code - * clazz} is anonymous or otherwise does not have a name. - * - * @param clazz annotation class - * @return the canonical name of {@code clazz} - */ - static @CanonicalName String annotationClassName(Class clazz) { - String name = clazz.getCanonicalName(); - if (name == null) { - throw new TypeSystemError("Qualifier classes must not be anonymous."); - } - return name; + /** + * Returns the canonical name of {@code clazz}. Throws a {@link TypeSystemError} if {@code clazz} + * is anonymous or otherwise does not have a name. + * + * @param clazz annotation class + * @return the canonical name of {@code clazz} + */ + static @CanonicalName String annotationClassName(Class clazz) { + String name = clazz.getCanonicalName(); + if (name == null) { + throw new TypeSystemError("Qualifier classes must not be anonymous."); } + return name; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java b/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java index 42db91ab672..6278db973b9 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java +++ b/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java @@ -7,7 +7,12 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.expression.FormalParameter; @@ -21,14 +26,6 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; -import java.util.ArrayList; -import java.util.List; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - /** * This interface is both a functional interface, see {@link #toJavaExpression(String)}, and also a * collection of static methods that convert a string to a JavaExpression at common locations. @@ -43,315 +40,311 @@ @FunctionalInterface public interface StringToJavaExpression { - /** - * Convert a string to a {@link JavaExpression}. Returns {@code null} if no conversion exists. - * - *

Conversion includes parsing {@code stringExpr} to a {@code JavaExpression} and optionally - * transforming the result of parsing into another {@code JavaExpression}. An example of - * transformation is viewpoint adaptation. - * - * @param stringExpr a Java expression - * @return a {@code JavaExpression} or {@code null} if no conversion from {@code stringExpr} - * exists - * @throws JavaExpressionParseException if {@code stringExpr} cannot be parsed to a {@code - * JavaExpression} - */ - @Nullable JavaExpression toJavaExpression(String stringExpr) - throws JavaExpressionParseException; + /** + * Convert a string to a {@link JavaExpression}. Returns {@code null} if no conversion exists. + * + *

Conversion includes parsing {@code stringExpr} to a {@code JavaExpression} and optionally + * transforming the result of parsing into another {@code JavaExpression}. An example of + * transformation is viewpoint adaptation. + * + * @param stringExpr a Java expression + * @return a {@code JavaExpression} or {@code null} if no conversion from {@code stringExpr} + * exists + * @throws JavaExpressionParseException if {@code stringExpr} cannot be parsed to a {@code + * JavaExpression} + */ + @Nullable JavaExpression toJavaExpression(String stringExpr) throws JavaExpressionParseException; - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code typeElement}. - * - * @param expression a Java expression to parse - * @param typeElement type element at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atTypeDecl( - String expression, TypeElement typeElement, SourceChecker checker) - throws JavaExpressionParseException { - ThisReference thisReference = new ThisReference(typeElement.asType()); - List parameters = null; - return JavaExpressionParseUtil.parse( - expression, - typeElement.asType(), - thisReference, - parameters, - null, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - } + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code typeElement}. + * + * @param expression a Java expression to parse + * @param typeElement type element at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atTypeDecl( + String expression, TypeElement typeElement, SourceChecker checker) + throws JavaExpressionParseException { + ThisReference thisReference = new ThisReference(typeElement.asType()); + List parameters = null; + return JavaExpressionParseUtil.parse( + expression, + typeElement.asType(), + thisReference, + parameters, + null, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + } - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code fieldElement}. - * - * @param expression a Java expression to parse - * @param fieldElement variable element at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atFieldDecl( - String expression, VariableElement fieldElement, SourceChecker checker) - throws JavaExpressionParseException { - TypeMirror enclosingType = ElementUtils.enclosingTypeElement(fieldElement).asType(); - ThisReference thisReference; - if (ElementUtils.isStatic(fieldElement)) { - // Can't use "this" on a static fieldElement - thisReference = null; - } else { - thisReference = new ThisReference(enclosingType); - } - List parameters = null; - return JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - null, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code fieldElement}. + * + * @param expression a Java expression to parse + * @param fieldElement variable element at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atFieldDecl( + String expression, VariableElement fieldElement, SourceChecker checker) + throws JavaExpressionParseException { + TypeMirror enclosingType = ElementUtils.enclosingTypeElement(fieldElement).asType(); + ThisReference thisReference; + if (ElementUtils.isStatic(fieldElement)) { + // Can't use "this" on a static fieldElement + thisReference = null; + } else { + thisReference = new ThisReference(enclosingType); } + List parameters = null; + return JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + null, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + } - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code method}. The - * returned {@code JavaExpression} uses {@link FormalParameter}s to represent parameters. Use - * {@link #atMethodBody(String, MethodTree, SourceChecker)} if parameters should be {@link - * LocalVariable}s instead. - * - * @param expression a Java expression to parse - * @param method method element at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodDecl( - String expression, ExecutableElement method, SourceChecker checker) - throws JavaExpressionParseException { - TypeMirror enclosingType = ElementUtils.enclosingTypeElement(method).asType(); - ThisReference thisReference; - if (ElementUtils.isStatic(method)) { - // Can't use "this" on a static method - thisReference = null; - } else { - thisReference = new ThisReference(enclosingType); - } - List parameters = JavaExpression.getFormalParameters(method); - return JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - null, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code method}. The + * returned {@code JavaExpression} uses {@link FormalParameter}s to represent parameters. Use + * {@link #atMethodBody(String, MethodTree, SourceChecker)} if parameters should be {@link + * LocalVariable}s instead. + * + * @param expression a Java expression to parse + * @param method method element at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodDecl( + String expression, ExecutableElement method, SourceChecker checker) + throws JavaExpressionParseException { + TypeMirror enclosingType = ElementUtils.enclosingTypeElement(method).asType(); + ThisReference thisReference; + if (ElementUtils.isStatic(method)) { + // Can't use "this" on a static method + thisReference = null; + } else { + thisReference = new ThisReference(enclosingType); } + List parameters = JavaExpression.getFormalParameters(method); + return JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + null, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + } - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code methodTree}. The - * returned {@code JavaExpression} uses {@link LocalVariable}s to represent parameters. Use - * {@link #atMethodDecl(String, ExecutableElement, SourceChecker)} if parameters should be - * {@link FormalParameter}s instead. - * - * @param expression a Java expression to parse - * @param methodTree method declaration tree at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodBody( - String expression, MethodTree methodTree, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromDeclaration(methodTree); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atMethodBody(methodTree); - } + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code methodTree}. The + * returned {@code JavaExpression} uses {@link LocalVariable}s to represent parameters. Use {@link + * #atMethodDecl(String, ExecutableElement, SourceChecker)} if parameters should be {@link + * FormalParameter}s instead. + * + * @param expression a Java expression to parse + * @param methodTree method declaration tree at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodBody( + String expression, MethodTree methodTree, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromDeclaration(methodTree); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atMethodBody(methodTree); + } - /** - * Parses a string as if it were written at the declaration of the invoked method and then - * viewpoint-adapts the result to the call site. - * - * @param expression a Java expression to parse - * @param methodInvocationTree method invocation tree - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodInvocation( - String expression, MethodInvocationTree methodInvocationTree, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationTree); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atMethodInvocation(methodInvocationTree); - } + /** + * Parses a string as if it were written at the declaration of the invoked method and then + * viewpoint-adapts the result to the call site. + * + * @param expression a Java expression to parse + * @param methodInvocationTree method invocation tree + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodInvocation( + String expression, MethodInvocationTree methodInvocationTree, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationTree); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atMethodInvocation(methodInvocationTree); + } - /** - * Parses a string as if it were written at the declaration of the invoked method and then - * viewpoint-adapts the result to the call site. - * - * @param expression a Java expression to parse - * @param methodInvocationNode method invocation node - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodInvocation( - String expression, MethodInvocationNode methodInvocationNode, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationNode.getTree()); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atMethodInvocation(methodInvocationNode); - } + /** + * Parses a string as if it were written at the declaration of the invoked method and then + * viewpoint-adapts the result to the call site. + * + * @param expression a Java expression to parse + * @param methodInvocationNode method invocation node + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodInvocation( + String expression, MethodInvocationNode methodInvocationNode, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationNode.getTree()); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atMethodInvocation(methodInvocationNode); + } - /** - * Parses a string as if it were written at the declaration of the invoked constructor and then - * viewpoint-adapts the result to the call site. - * - * @param expression a Java expression to parse - * @param newClassTree constructor invocation - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atConstructorInvocation( - String expression, NewClassTree newClassTree, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromUse(newClassTree); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atConstructorInvocation(newClassTree); - } + /** + * Parses a string as if it were written at the declaration of the invoked constructor and then + * viewpoint-adapts the result to the call site. + * + * @param expression a Java expression to parse + * @param newClassTree constructor invocation + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atConstructorInvocation( + String expression, NewClassTree newClassTree, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromUse(newClassTree); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atConstructorInvocation(newClassTree); + } - /** - * uf found Parses a string as if it were written at the declaration of the field and then - * viewpoint-adapts the result to the use. - * - * @param expression a Java expression to parse - * @param fieldAccess the field access tree - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atFieldAccess( - String expression, MemberSelectTree fieldAccess, SourceChecker checker) - throws JavaExpressionParseException { + /** + * uf found Parses a string as if it were written at the declaration of the field and then + * viewpoint-adapts the result to the use. + * + * @param expression a Java expression to parse + * @param fieldAccess the field access tree + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atFieldAccess( + String expression, MemberSelectTree fieldAccess, SourceChecker checker) + throws JavaExpressionParseException { - VariableElement fieldEle = TreeUtils.variableElementFromUse(fieldAccess); - JavaExpression receiver = JavaExpression.fromTree(fieldAccess.getExpression()); - JavaExpression javaExpr = StringToJavaExpression.atFieldDecl(expression, fieldEle, checker); - return javaExpr.atFieldAccess(receiver); - } + VariableElement fieldEle = TreeUtils.variableElementFromUse(fieldAccess); + JavaExpression receiver = JavaExpression.fromTree(fieldAccess.getExpression()); + JavaExpression javaExpr = StringToJavaExpression.atFieldDecl(expression, fieldEle, checker); + return javaExpr.atFieldAccess(receiver); + } - /** - * Parses a string as if it were written at one of the parameters of {@code lambdaTree}. - * Parameters of the lambda are expressed as {@link LocalVariable}s. - * - * @param expression a Java expression to parse - * @param lambdaTree the lambda tree - * @param parentPath path to the parent of {@code lambdaTree}; required because the expression - * can reference final local variables of the enclosing method - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atLambdaParameter( - String expression, - LambdaExpressionTree lambdaTree, - TreePath parentPath, - SourceChecker checker) - throws JavaExpressionParseException { + /** + * Parses a string as if it were written at one of the parameters of {@code lambdaTree}. + * Parameters of the lambda are expressed as {@link LocalVariable}s. + * + * @param expression a Java expression to parse + * @param lambdaTree the lambda tree + * @param parentPath path to the parent of {@code lambdaTree}; required because the expression can + * reference final local variables of the enclosing method + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atLambdaParameter( + String expression, + LambdaExpressionTree lambdaTree, + TreePath parentPath, + SourceChecker checker) + throws JavaExpressionParseException { - TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(parentPath)); - JavaExpression receiver = JavaExpression.getPseudoReceiver(parentPath, enclosingType); - // If receiver isn't a ThisReference, then the lambda is in a static context and "this" - // cannot be referenced in the expression. - ThisReference thisReference = - receiver instanceof ThisReference ? (ThisReference) receiver : null; - List paramsAsLocals = new ArrayList<>(lambdaTree.getParameters().size()); - List parameters = new ArrayList<>(lambdaTree.getParameters().size()); - int oneBasedIndex = 1; - for (VariableTree arg : lambdaTree.getParameters()) { - LocalVariable param = (LocalVariable) JavaExpression.fromVariableTree(arg); - paramsAsLocals.add(param); - parameters.add(new FormalParameter(oneBasedIndex, param.getElement())); - oneBasedIndex++; - } - - JavaExpression javaExpr = - JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - parentPath, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); + TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(parentPath)); + JavaExpression receiver = JavaExpression.getPseudoReceiver(parentPath, enclosingType); + // If receiver isn't a ThisReference, then the lambda is in a static context and "this" + // cannot be referenced in the expression. + ThisReference thisReference = + receiver instanceof ThisReference ? (ThisReference) receiver : null; + List paramsAsLocals = new ArrayList<>(lambdaTree.getParameters().size()); + List parameters = new ArrayList<>(lambdaTree.getParameters().size()); + int oneBasedIndex = 1; + for (VariableTree arg : lambdaTree.getParameters()) { + LocalVariable param = (LocalVariable) JavaExpression.fromVariableTree(arg); + paramsAsLocals.add(param); + parameters.add(new FormalParameter(oneBasedIndex, param.getElement())); + oneBasedIndex++; } - /** - * Parses a string as if it were written at {@code localVarPath}. - * - * @param expression a Java expression to parse - * @param localVarPath location at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atPath(String expression, TreePath localVarPath, SourceChecker checker) - throws JavaExpressionParseException { + JavaExpression javaExpr = + JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + parentPath, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); + } - TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(localVarPath)); - ThisReference thisReference = - TreePathUtil.isTreeInStaticScope(localVarPath) - ? null - : new ThisReference(enclosingType); + /** + * Parses a string as if it were written at {@code localVarPath}. + * + * @param expression a Java expression to parse + * @param localVarPath location at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atPath(String expression, TreePath localVarPath, SourceChecker checker) + throws JavaExpressionParseException { - MethodTree methodTree = TreePathUtil.enclosingMethod(localVarPath); - if (methodTree == null) { - return JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - null, - localVarPath, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - } + TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(localVarPath)); + ThisReference thisReference = + TreePathUtil.isTreeInStaticScope(localVarPath) ? null : new ThisReference(enclosingType); - ExecutableElement methodEle = TreeUtils.elementFromDeclaration(methodTree); - List parameters = JavaExpression.getFormalParameters(methodEle); - JavaExpression javaExpr = - JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - localVarPath, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - List paramsAsLocals = - JavaExpression.getParametersAsLocalVariables(methodEle); - return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); + MethodTree methodTree = TreePathUtil.enclosingMethod(localVarPath); + if (methodTree == null) { + return JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + null, + localVarPath, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); } + + ExecutableElement methodEle = TreeUtils.elementFromDeclaration(methodTree); + List parameters = JavaExpression.getFormalParameters(methodEle); + JavaExpression javaExpr = + JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + localVarPath, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + List paramsAsLocals = JavaExpression.getParametersAsLocalVariables(methodEle); + return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java index dc2c554b11c..55ba73a5e63 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java +++ b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java @@ -4,12 +4,10 @@ import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import com.sun.source.util.TreeScanner; - -import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.checker.nullness.qual.Nullable; /** * TreePathCacher is a TreeScanner that creates and caches a TreePath for a target Tree. @@ -20,109 +18,109 @@ */ public class TreePathCacher extends TreeScanner { - private final Map foundPaths = new HashMap<>(32); - - /** - * The TreePath of the previous tree scanned. It is always set back to null after a scan has - * completed. - */ - private @Nullable TreePath path; + private final Map foundPaths = new HashMap<>(32); + + /** + * The TreePath of the previous tree scanned. It is always set back to null after a scan has + * completed. + */ + private @Nullable TreePath path; + + /** + * Returns true if the tree is cached. + * + * @param target the tree to search for + * @return true if the tree is cached + */ + public boolean isCached(Tree target) { + return foundPaths.containsKey(target); + } + + /** + * Adds the given key and value to the cache. + * + * @param target the tree to add + * @param path the path to cache + */ + public void addPath(Tree target, TreePath path) { + foundPaths.put(target, path); + } + + /** + * Return the TreePath for a Tree. + * + * @param root the compilation unit to search in + * @param target the target tree to look for + * @return the TreePath corresponding to target, or null if target is not found in the compilation + * root + */ + public @Nullable TreePath getPath(CompilationUnitTree root, @FindDistinct Tree target) { + // This method uses try/catch and the private {@code Result} exception for control flow to + // stop the superclass from scanning other subtrees when target is found. + + if (foundPaths.containsKey(target)) { + return foundPaths.get(target); + } - /** - * Returns true if the tree is cached. - * - * @param target the tree to search for - * @return true if the tree is cached - */ - public boolean isCached(Tree target) { - return foundPaths.containsKey(target); + TreePath path = new TreePath(root); + if (path.getLeaf() == target) { + return path; } - /** - * Adds the given key and value to the cache. - * - * @param target the tree to add - * @param path the path to cache - */ - public void addPath(Tree target, TreePath path) { - foundPaths.put(target, path); + try { + this.scan(path, target); + } catch (Result result) { + return result.path; } + // If a path wasn't found, cache null so the whole compilation unit isn't searched again. + foundPaths.put(target, null); + return null; + } + + /** The result of {@link #getPath}. This exception is used for control flow. */ + private static class Result extends Error { + /** Unique identifier for serialization. */ + private static final long serialVersionUID = 4948452207518392627L; + + /** The result of {@link #getPath}. */ + @SuppressWarnings("serial") // I do not intend to serialize Result objects + private final TreePath path; /** - * Return the TreePath for a Tree. + * Create a {@link #getPath} result. * - * @param root the compilation unit to search in - * @param target the target tree to look for - * @return the TreePath corresponding to target, or null if target is not found in the - * compilation root + * @param path the result of {@link #getPath} */ - public @Nullable TreePath getPath(CompilationUnitTree root, @FindDistinct Tree target) { - // This method uses try/catch and the private {@code Result} exception for control flow to - // stop the superclass from scanning other subtrees when target is found. - - if (foundPaths.containsKey(target)) { - return foundPaths.get(target); - } - - TreePath path = new TreePath(root); - if (path.getLeaf() == target) { - return path; - } - - try { - this.scan(path, target); - } catch (Result result) { - return result.path; - } - // If a path wasn't found, cache null so the whole compilation unit isn't searched again. - foundPaths.put(target, null); - return null; + Result(TreePath path) { + this.path = path; } - - /** The result of {@link #getPath}. This exception is used for control flow. */ - private static class Result extends Error { - /** Unique identifier for serialization. */ - private static final long serialVersionUID = 4948452207518392627L; - - /** The result of {@link #getPath}. */ - @SuppressWarnings("serial") // I do not intend to serialize Result objects - private final TreePath path; - - /** - * Create a {@link #getPath} result. - * - * @param path the result of {@link #getPath} - */ - Result(TreePath path) { - this.path = path; - } + } + + public void clear() { + foundPaths.clear(); + } + + /** Scan a single node. The current path is updated for the duration of the scan. */ + @SuppressWarnings("interning:not.interned") // assertion + @Override + public TreePath scan(Tree tree, Tree target) { + TreePath prev = path; + if (tree != null) { + TreePath foundPath = foundPaths.get(tree); + if (foundPath == null) { + foundPath = new TreePath(path, tree); + foundPaths.put(tree, foundPath); + } + this.path = foundPath; } - public void clear() { - foundPaths.clear(); + if (tree == target) { + throw new Result(path); } - - /** Scan a single node. The current path is updated for the duration of the scan. */ - @SuppressWarnings("interning:not.interned") // assertion - @Override - public TreePath scan(Tree tree, Tree target) { - TreePath prev = path; - if (tree != null) { - TreePath foundPath = foundPaths.get(tree); - if (foundPath == null) { - foundPath = new TreePath(path, tree); - foundPaths.put(tree, foundPath); - } - this.path = foundPath; - } - - if (tree == target) { - throw new Result(path); - } - try { - return super.scan(tree, target); - } finally { - this.path = prev; - } + try { + return super.scan(tree, target); + } finally { + this.path = prev; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java b/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java index c530be918fd..2f3cda91657 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java +++ b/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java @@ -1,8 +1,5 @@ package org.checkerframework.framework.util; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.IPair; - import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -12,7 +9,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; @@ -20,6 +16,8 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.IPair; /** * Records any mapping between the type parameters of a subtype to the corresponding type parameters @@ -63,289 +61,280 @@ */ public class TypeArgumentMapper { - /** - * Returns a mapping from subtype's type parameter indices to the indices of corresponding type - * parameters in supertype. - */ - public static Set> mapTypeArgumentIndices( - TypeElement subtype, TypeElement supertype, Types types) { - Set> result = new HashSet<>(); - if (subtype.equals(supertype)) { - for (int i = 0; i < subtype.getTypeParameters().size(); i++) { - result.add(IPair.of(Integer.valueOf(i), Integer.valueOf(i))); - } - - } else { - Map> subToSuperElements = - mapTypeArguments(subtype, supertype, types); - Map supertypeIndexes = getElementToIndex(supertype); - - List subtypeParams = subtype.getTypeParameters(); - for (int subtypeIndex = 0; subtypeIndex < subtypeParams.size(); subtypeIndex++) { - TypeParameterElement subtypeParam = subtypeParams.get(subtypeIndex); - - Set correspondingSuperArgs = - subToSuperElements.get(subtypeParam); - if (correspondingSuperArgs != null) { - for (TypeParameterElement supertypeParam : - subToSuperElements.get(subtypeParam)) { - result.add(IPair.of(subtypeIndex, supertypeIndexes.get(supertypeParam))); - } - } - } + /** + * Returns a mapping from subtype's type parameter indices to the indices of corresponding type + * parameters in supertype. + */ + public static Set> mapTypeArgumentIndices( + TypeElement subtype, TypeElement supertype, Types types) { + Set> result = new HashSet<>(); + if (subtype.equals(supertype)) { + for (int i = 0; i < subtype.getTypeParameters().size(); i++) { + result.add(IPair.of(Integer.valueOf(i), Integer.valueOf(i))); + } + + } else { + Map> subToSuperElements = + mapTypeArguments(subtype, supertype, types); + Map supertypeIndexes = getElementToIndex(supertype); + + List subtypeParams = subtype.getTypeParameters(); + for (int subtypeIndex = 0; subtypeIndex < subtypeParams.size(); subtypeIndex++) { + TypeParameterElement subtypeParam = subtypeParams.get(subtypeIndex); + + Set correspondingSuperArgs = subToSuperElements.get(subtypeParam); + if (correspondingSuperArgs != null) { + for (TypeParameterElement supertypeParam : subToSuperElements.get(subtypeParam)) { + result.add(IPair.of(subtypeIndex, supertypeIndexes.get(supertypeParam))); + } } - - return result; + } } - /** - * Returns a Map(type parameter symbol → index in type parameter list). - * - * @param typeElement a type whose type parameters to summarize - * @return a Map(type parameter symbol → index in type parameter list) - */ - private static Map getElementToIndex(TypeElement typeElement) { - Map result = new LinkedHashMap<>(); - - List params = typeElement.getTypeParameters(); - for (int i = 0; i < params.size(); i++) { - result.put(params.get(i), Integer.valueOf(i)); - } - - return result; + return result; + } + + /** + * Returns a Map(type parameter symbol → index in type parameter list). + * + * @param typeElement a type whose type parameters to summarize + * @return a Map(type parameter symbol → index in type parameter list) + */ + private static Map getElementToIndex(TypeElement typeElement) { + Map result = new LinkedHashMap<>(); + + List params = typeElement.getTypeParameters(); + for (int i = 0; i < params.size(); i++) { + result.put(params.get(i), Integer.valueOf(i)); } - /** - * Returns a mapping from the type parameters of subtype to a list of the type parameters in - * supertype that must be the same type as subtype. - * - *

e.g., - * - *

{@code
-     * class A
-     * class B extends A {}
-     * }
- * - * results in a {@code Map(B1 => [A1,A2], B2 => [], B3 => [A3], B4 => [])} - * - * @return a mapping from the type parameters of subtype to the supertype type parameter's that - * to which they are a type argument - */ - public static Map> mapTypeArguments( - TypeElement subtype, TypeElement supertype, Types types) { - - List pathToSupertype = depthFirstSearchForSupertype(subtype, supertype, types); - - if (pathToSupertype == null || pathToSupertype.isEmpty()) { - return new LinkedHashMap<>(); - } - - Map> intermediate = new LinkedHashMap<>(); - Set currentTypeParams = new HashSet<>(); - - // takes a type records of the form: - // TypeRecord(element = MyMap, type = null) - // TypeRecord(element = AbstractMap, type = AbstractMap) - // TypeRecord(element = Map, type = AbstractMap) - // And makes a map: - // Map(Y1 -> [A1], Y2 -> [A2], A1 -> [M1], A2 -> M2] - Iterator path = pathToSupertype.iterator(); - TypeRecord current = path.next(); - while (path.hasNext()) { - TypeRecord next = path.next(); - - List nextTypeParameter = - next.element.getTypeParameters(); - List nextTypeArgs = - next.type != null ? next.type.getTypeArguments() : Collections.emptyList(); - currentTypeParams.clear(); - currentTypeParams.addAll(current.element.getTypeParameters()); - - for (int i = 0; i < nextTypeArgs.size(); i++) { - TypeParameterElement correspondingParameter = nextTypeParameter.get(i); - TypeMirror typeArg = nextTypeArgs.get(i); - Element typeArgEle = types.asElement(typeArg); - - if (currentTypeParams.contains(typeArgEle)) { - addToSetMap( - intermediate, - (TypeParameterElement) typeArgEle, - correspondingParameter); - } - } - } - - List supertypeParams = supertype.getTypeParameters(); - Map> result = - new LinkedHashMap<>(subtype.getTypeParameters().size()); - - // You can think of the map above as a set of links from SubtypeParameter -> Supertype - // Parameter - for (TypeParameterElement subtypeParam : subtype.getTypeParameters()) { - Set subtypePath = - flattenPath(intermediate.get(subtypeParam), intermediate); - subtypePath.retainAll(supertypeParams); - result.put(subtypeParam, subtypePath); - } - - return result; + return result; + } + + /** + * Returns a mapping from the type parameters of subtype to a list of the type parameters in + * supertype that must be the same type as subtype. + * + *

e.g., + * + *

{@code
+   * class A
+   * class B extends A {}
+   * }
+ * + * results in a {@code Map(B1 => [A1,A2], B2 => [], B3 => [A3], B4 => [])} + * + * @return a mapping from the type parameters of subtype to the supertype type parameter's that to + * which they are a type argument + */ + public static Map> mapTypeArguments( + TypeElement subtype, TypeElement supertype, Types types) { + + List pathToSupertype = depthFirstSearchForSupertype(subtype, supertype, types); + + if (pathToSupertype == null || pathToSupertype.isEmpty()) { + return new LinkedHashMap<>(); } - private static Set flattenPath( - Set elements, - Map> map) { - Set result = new HashSet<>(); - if (elements == null) { - return result; - } - for (TypeParameterElement oldElement : elements) { - Set substitutions = map.get(oldElement); - if (substitutions != null) { - result.addAll(flattenPath(elements, map)); - } else { - result.add(oldElement); - } + Map> intermediate = new LinkedHashMap<>(); + Set currentTypeParams = new HashSet<>(); + + // takes a type records of the form: + // TypeRecord(element = MyMap, type = null) + // TypeRecord(element = AbstractMap, type = AbstractMap) + // TypeRecord(element = Map, type = AbstractMap) + // And makes a map: + // Map(Y1 -> [A1], Y2 -> [A2], A1 -> [M1], A2 -> M2] + Iterator path = pathToSupertype.iterator(); + TypeRecord current = path.next(); + while (path.hasNext()) { + TypeRecord next = path.next(); + + List nextTypeParameter = next.element.getTypeParameters(); + List nextTypeArgs = + next.type != null ? next.type.getTypeArguments() : Collections.emptyList(); + currentTypeParams.clear(); + currentTypeParams.addAll(current.element.getTypeParameters()); + + for (int i = 0; i < nextTypeArgs.size(); i++) { + TypeParameterElement correspondingParameter = nextTypeParameter.get(i); + TypeMirror typeArg = nextTypeArgs.get(i); + Element typeArgEle = types.asElement(typeArg); + + if (currentTypeParams.contains(typeArgEle)) { + addToSetMap(intermediate, (TypeParameterElement) typeArgEle, correspondingParameter); } - return result; + } } - private static void addToSetMap( - Map> setMap, - TypeParameterElement element, - TypeParameterElement typeParam) { - Set set = setMap.computeIfAbsent(element, __ -> new HashSet<>()); - set.add(typeParam); + List supertypeParams = supertype.getTypeParameters(); + Map> result = + new LinkedHashMap<>(subtype.getTypeParameters().size()); + + // You can think of the map above as a set of links from SubtypeParameter -> Supertype + // Parameter + for (TypeParameterElement subtypeParam : subtype.getTypeParameters()) { + Set subtypePath = + flattenPath(intermediate.get(subtypeParam), intermediate); + subtypePath.retainAll(supertypeParams); + result.put(subtypeParam, subtypePath); } - /** - * Create a list of TypeRecord's that form a "path" to target from subtype. e.g. Suppose I have - * the types - * - *
{@code
-     * interface Map
-     * class AbstractMap implements Map, Iterable>
-     * class MyMap extends AbstractMap implements List>
-     * }
- * - * The path from MyMap to Map would be: - * - *
{@code
-     * TypeRecord(element = MyMap, type = null)
-     * TypeRecord(element = AbstractMap, type = AbstractMap)
-     * TypeRecord(element = Map, type = AbstractMap)
-     * }
- * - * Note: You can have an implementation of the same interface inherited multiple times as long - * as the parameterization of that interface remains the same e.g. - * - *
{@code
-     * interface List
-     * class AbstractList implements List
-     * class ArrayList extends AbstractList implements List
-     * }
- * - * Notice how ArrayList implements list both by inheriting from AbstractList and from explicitly - * listing it in the implements clause. We prioritize finding a path through the list of - * interfaces first since this will be the shorter path. - * - * @param subtype the start of the resulting sequence - * @param target the end of the resulting sequence - * @param types utility methods for operating on types - * @return a list of type records that represents the sequence of directSupertypes between - * subtype and target - */ - private static List depthFirstSearchForSupertype( - TypeElement subtype, TypeElement target, Types types) { - ArrayDeque pathFromRoot = new ArrayDeque<>(); - TypeRecord pathStart = new TypeRecord(subtype, null); - pathFromRoot.push(pathStart); - List result = recursiveDepthFirstSearch(pathFromRoot, target, types); - return result; + return result; + } + + private static Set flattenPath( + Set elements, + Map> map) { + Set result = new HashSet<>(); + if (elements == null) { + return result; + } + for (TypeParameterElement oldElement : elements) { + Set substitutions = map.get(oldElement); + if (substitutions != null) { + result.addAll(flattenPath(elements, map)); + } else { + result.add(oldElement); + } + } + return result; + } + + private static void addToSetMap( + Map> setMap, + TypeParameterElement element, + TypeParameterElement typeParam) { + Set set = setMap.computeIfAbsent(element, __ -> new HashSet<>()); + set.add(typeParam); + } + + /** + * Create a list of TypeRecord's that form a "path" to target from subtype. e.g. Suppose I have + * the types + * + *
{@code
+   * interface Map
+   * class AbstractMap implements Map, Iterable>
+   * class MyMap extends AbstractMap implements List>
+   * }
+ * + * The path from MyMap to Map would be: + * + *
{@code
+   * TypeRecord(element = MyMap, type = null)
+   * TypeRecord(element = AbstractMap, type = AbstractMap)
+   * TypeRecord(element = Map, type = AbstractMap)
+   * }
+ * + * Note: You can have an implementation of the same interface inherited multiple times as long as + * the parameterization of that interface remains the same e.g. + * + *
{@code
+   * interface List
+   * class AbstractList implements List
+   * class ArrayList extends AbstractList implements List
+   * }
+ * + * Notice how ArrayList implements list both by inheriting from AbstractList and from explicitly + * listing it in the implements clause. We prioritize finding a path through the list of + * interfaces first since this will be the shorter path. + * + * @param subtype the start of the resulting sequence + * @param target the end of the resulting sequence + * @param types utility methods for operating on types + * @return a list of type records that represents the sequence of directSupertypes between subtype + * and target + */ + private static List depthFirstSearchForSupertype( + TypeElement subtype, TypeElement target, Types types) { + ArrayDeque pathFromRoot = new ArrayDeque<>(); + TypeRecord pathStart = new TypeRecord(subtype, null); + pathFromRoot.push(pathStart); + List result = recursiveDepthFirstSearch(pathFromRoot, target, types); + return result; + } + + /** + * Computes one level for depthFirstSearchForSupertype then recurses. + * + * @param pathFromRoot the path so far + * @param target the end of the resulting path + * @param types utility methods for operating on types + * @return a list of type records that extends pathFromRoot (a sequence of directSupertypes) to + * target + */ + private static @Nullable List recursiveDepthFirstSearch( + ArrayDeque pathFromRoot, TypeElement target, Types types) { + if (pathFromRoot.isEmpty()) { + return null; } - /** - * Computes one level for depthFirstSearchForSupertype then recurses. - * - * @param pathFromRoot the path so far - * @param target the end of the resulting path - * @param types utility methods for operating on types - * @return a list of type records that extends pathFromRoot (a sequence of directSupertypes) to - * target - */ - private static @Nullable List recursiveDepthFirstSearch( - ArrayDeque pathFromRoot, TypeElement target, Types types) { - if (pathFromRoot.isEmpty()) { - return null; - } + TypeRecord currentRecord = pathFromRoot.peekLast(); + TypeElement currentElement = currentRecord.element; - TypeRecord currentRecord = pathFromRoot.peekLast(); - TypeElement currentElement = currentRecord.element; + if (currentElement.equals(target)) { + return new ArrayList<>(pathFromRoot); + } - if (currentElement.equals(target)) { - return new ArrayList<>(pathFromRoot); - } + Iterator interfaces = currentElement.getInterfaces().iterator(); + TypeMirror superclassType = currentElement.getSuperclass(); - Iterator interfaces = currentElement.getInterfaces().iterator(); - TypeMirror superclassType = currentElement.getSuperclass(); - - List path = null; - - while (path == null && interfaces.hasNext()) { - TypeMirror intface = interfaces.next(); - if (intface.getKind() != TypeKind.NONE) { - DeclaredType interfaceDeclared = (DeclaredType) intface; - pathFromRoot.addLast( - new TypeRecord( - (TypeElement) types.asElement(interfaceDeclared), - interfaceDeclared)); - path = recursiveDepthFirstSearch(pathFromRoot, target, types); - pathFromRoot.removeLast(); - } - } + List path = null; - if (path == null && superclassType.getKind() != TypeKind.NONE) { - DeclaredType superclass = (DeclaredType) superclassType; + while (path == null && interfaces.hasNext()) { + TypeMirror intface = interfaces.next(); + if (intface.getKind() != TypeKind.NONE) { + DeclaredType interfaceDeclared = (DeclaredType) intface; + pathFromRoot.addLast( + new TypeRecord((TypeElement) types.asElement(interfaceDeclared), interfaceDeclared)); + path = recursiveDepthFirstSearch(pathFromRoot, target, types); + pathFromRoot.removeLast(); + } + } - pathFromRoot.addLast( - new TypeRecord((TypeElement) types.asElement(superclass), superclass)); - path = recursiveDepthFirstSearch(pathFromRoot, target, types); - pathFromRoot.removeLast(); - } + if (path == null && superclassType.getKind() != TypeKind.NONE) { + DeclaredType superclass = (DeclaredType) superclassType; - return path; + pathFromRoot.addLast(new TypeRecord((TypeElement) types.asElement(superclass), superclass)); + path = recursiveDepthFirstSearch(pathFromRoot, target, types); + pathFromRoot.removeLast(); } - /** - * Maps a class or interface's declaration element to the type it would be if viewed from a - * subtype class or interface. - * - *

e.g. suppose we have the elements for the declarations: - * - *

{@code
-     * class A
-     * class B extends A
-     * }
- * - * The type record of B if it is viewed as class A would bed: - * - *
{@code
-     * TypeRecord( element = A, type = A )
-     * }
- * - * That is, B can be viewed as an object of type A with an type argument of type parameter Tb - */ - private static class TypeRecord { - public final TypeElement element; - public final DeclaredType type; - - TypeRecord(TypeElement element, DeclaredType type) { - this.element = element; - this.type = type; - } + return path; + } + + /** + * Maps a class or interface's declaration element to the type it would be if viewed from a + * subtype class or interface. + * + *

e.g. suppose we have the elements for the declarations: + * + *

{@code
+   * class A
+   * class B extends A
+   * }
+ * + * The type record of B if it is viewed as class A would bed: + * + *
{@code
+   * TypeRecord( element = A, type = A )
+   * }
+ * + * That is, B can be viewed as an object of type A with an type argument of type parameter Tb + */ + private static class TypeRecord { + public final TypeElement element; + public final DeclaredType type; + + TypeRecord(TypeElement element, DeclaredType type) { + this.element = element; + this.type = type; + } - @Override - public String toString() { - return String.format("[%s => %s]", element, type); - } + @Override + public String toString() { + return String.format("[%s => %s]", element, type); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java b/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java index a3404ffc0e9..683b2706c30 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java +++ b/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java @@ -111,604 +111,604 @@ * performing an action on each node of an AST. */ public abstract class VoidVisitorWithDefaultAction extends VoidVisitorAdapter { - /** - * Action performed on each visited node. - * - * @param node node to perform action on - */ - public abstract void defaultAction(Node node); - - @Override - public void visit(AnnotationDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(AnnotationMemberDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayAccessExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayCreationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayCreationLevel n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayInitializerExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(AssertStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(AssignExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BinaryExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BlockComment n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BlockStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BooleanLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BreakStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CastExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CatchClause n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CharLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ClassExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ClassOrInterfaceDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ClassOrInterfaceType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CompilationUnit n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(StubUnit n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ConditionalExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ConstructorDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ContinueStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(DoStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(DoubleLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EmptyStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EnclosedExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EnumConstantDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EnumDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ExplicitConstructorInvocationStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ExpressionStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(FieldAccessExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(FieldDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ForStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ForEachStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(IfStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ImportDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(InitializerDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(InstanceOfExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(IntegerLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(IntersectionType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(JavadocComment n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LabeledStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LambdaExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LineComment n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LocalClassDeclarationStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LongLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MarkerAnnotationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MemberValuePair n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MethodCallExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MethodDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MethodReferenceExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(NameExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(Name n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(NormalAnnotationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(NullLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ObjectCreationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(PackageDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(Parameter n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(PrimitiveType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ReturnStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SimpleName n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SingleMemberAnnotationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(StringLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SuperExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SwitchEntry n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SwitchStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SynchronizedStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ThisExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ThrowStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TryStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TypeExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TypeParameter n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnaryExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnionType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnknownType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VariableDeclarationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VariableDeclarator n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VoidType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(WhileStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(WildcardType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleRequiresDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleExportsDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleProvidesDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleUsesDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleOpensDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnparsableStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ReceiverParameter n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VarType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(Modifier n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SwitchExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TextBlockLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(YieldStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(RecordDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LocalRecordDeclarationStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CompactConstructorDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } + /** + * Action performed on each visited node. + * + * @param node node to perform action on + */ + public abstract void defaultAction(Node node); + + @Override + public void visit(AnnotationDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(AnnotationMemberDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayAccessExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayCreationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayCreationLevel n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayInitializerExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(AssertStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(AssignExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BinaryExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BlockComment n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BlockStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BooleanLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BreakStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CastExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CatchClause n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CharLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ClassExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ClassOrInterfaceDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ClassOrInterfaceType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CompilationUnit n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(StubUnit n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ConditionalExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ConstructorDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ContinueStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(DoStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(DoubleLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EmptyStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EnclosedExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EnumConstantDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EnumDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ExplicitConstructorInvocationStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ExpressionStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(FieldAccessExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(FieldDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ForStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ForEachStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(IfStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ImportDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(InitializerDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(InstanceOfExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(IntegerLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(IntersectionType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(JavadocComment n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LabeledStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LambdaExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LineComment n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LocalClassDeclarationStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LongLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MarkerAnnotationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MemberValuePair n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MethodCallExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MethodDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MethodReferenceExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(NameExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(Name n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(NormalAnnotationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(NullLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ObjectCreationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(PackageDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(Parameter n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(PrimitiveType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ReturnStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SimpleName n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SingleMemberAnnotationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(StringLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SuperExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SwitchEntry n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SwitchStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SynchronizedStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ThisExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ThrowStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TryStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TypeExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TypeParameter n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnaryExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnionType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnknownType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VariableDeclarationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VariableDeclarator n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VoidType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(WhileStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(WildcardType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleRequiresDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleExportsDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleProvidesDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleUsesDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleOpensDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnparsableStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ReceiverParameter n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VarType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(Modifier n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SwitchExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TextBlockLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(YieldStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(RecordDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LocalRecordDeclarationStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CompactConstructorDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java index dd42d8ad91f..bc02c881efb 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java @@ -1,13 +1,11 @@ package org.checkerframework.framework.util.defaults; +import java.util.Objects; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.javacutil.AnnotationUtils; -import java.util.Objects; - -import javax.lang.model.element.AnnotationMirror; - /** * Represents a mapping from an Annotation to a TypeUseLocation it should be applied to during * defaulting. The Comparable ordering of this class first tests location then tests annotation @@ -16,73 +14,73 @@ *

It also has a handy toString method that is useful for debugging. */ public class Default implements Comparable { - // please remember to add any fields to the hashcode calculation - /** The default annotation mirror. */ - public final AnnotationMirror anno; + // please remember to add any fields to the hashcode calculation + /** The default annotation mirror. */ + public final AnnotationMirror anno; - /** The type use location. */ - public final TypeUseLocation location; + /** The type use location. */ + public final TypeUseLocation location; - /** Whether the default should be inherited by subpackages. */ - public final boolean applyToSubpackages; + /** Whether the default should be inherited by subpackages. */ + public final boolean applyToSubpackages; - /** - * Construct a Default object. - * - * @param anno the default annotation mirror - * @param location the type use location - * @param applyToSubpackages whether the default should be inherited by subpackages - */ - public Default(AnnotationMirror anno, TypeUseLocation location, boolean applyToSubpackages) { - this.anno = anno; - this.location = location; - this.applyToSubpackages = applyToSubpackages; - } + /** + * Construct a Default object. + * + * @param anno the default annotation mirror + * @param location the type use location + * @param applyToSubpackages whether the default should be inherited by subpackages + */ + public Default(AnnotationMirror anno, TypeUseLocation location, boolean applyToSubpackages) { + this.anno = anno; + this.location = location; + this.applyToSubpackages = applyToSubpackages; + } - @Override - public int compareTo(Default other) { - int locationOrder = location.compareTo(other.location); - if (locationOrder == 0) { - int annoOrder = AnnotationUtils.compareAnnotationMirrors(anno, other.anno); - if (annoOrder == 0) { - if (applyToSubpackages == other.applyToSubpackages) { - return 0; - } else { - return applyToSubpackages ? 1 : -1; - } - } else { - return annoOrder; - } + @Override + public int compareTo(Default other) { + int locationOrder = location.compareTo(other.location); + if (locationOrder == 0) { + int annoOrder = AnnotationUtils.compareAnnotationMirrors(anno, other.anno); + if (annoOrder == 0) { + if (applyToSubpackages == other.applyToSubpackages) { + return 0; } else { - return locationOrder; + return applyToSubpackages ? 1 : -1; } + } else { + return annoOrder; + } + } else { + return locationOrder; } + } - @Override - public boolean equals(@Nullable Object thatObj) { - if (thatObj == this) { - return true; - } - - if (thatObj == null || thatObj.getClass() != Default.class) { - return false; - } - - return compareTo((Default) thatObj) == 0; + @Override + public boolean equals(@Nullable Object thatObj) { + if (thatObj == this) { + return true; } - @Override - public int hashCode() { - return Objects.hash(anno, location, applyToSubpackages); + if (thatObj == null || thatObj.getClass() != Default.class) { + return false; } - @Override - public String toString() { - return "( " - + location.name() - + " => " - + anno - + (applyToSubpackages ? " applies to subpackages" : "") - + " )"; - } + return compareTo((Default) thatObj) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(anno, location, applyToSubpackages); + } + + @Override + public String toString() { + return "( " + + location.name() + + " => " + + anno + + (applyToSubpackages ? " applies to subpackages" : "") + + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java index 8ba438bd1a8..465bc253b25 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java @@ -1,8 +1,7 @@ package org.checkerframework.framework.util.defaults; -import org.plumelib.util.StringsPlume; - import java.util.TreeSet; +import org.plumelib.util.StringsPlume; /** * An ordered set of Defaults (see {@link org.checkerframework.framework.util.defaults.Default}). @@ -11,15 +10,15 @@ @SuppressWarnings("serial") class DefaultSet extends TreeSet { - /** Creates a DefaultSet. */ - public DefaultSet() { - super(Default::compareTo); - } + /** Creates a DefaultSet. */ + public DefaultSet() { + super(Default::compareTo); + } - @Override - public String toString() { - return "DefaultSet( " + StringsPlume.join(", ", this) + " )"; - } + @Override + public String toString() { + return "DefaultSet( " + StringsPlume.join(", ", this) + " )"; + } - public static final DefaultSet EMPTY = new DefaultSet(); + public static final DefaultSet EMPTY = new DefaultSet(); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 4bab9d9688e..935658ae169 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -10,7 +10,22 @@ import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - +import java.util.Arrays; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.AnnotatedFor; @@ -38,24 +53,6 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; -import java.util.Arrays; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; - /** * Determines the default qualifiers on a type. Default qualifiers are specified via the {@link * org.checkerframework.framework.qual.DefaultQualifier} annotation. @@ -78,1358 +75,1327 @@ */ public class QualifierDefaults { - // TODO add visitor state to get the default annotations from the top down? - // TODO apply from package elements also - // TODO try to remove some dependencies (e.g. on factory) - - /** Element utilities to use. */ - private final Elements elements; - - /** The value() element/field of a @DefaultQualifier annotation. */ - protected final ExecutableElement defaultQualifierValueElement; - - /** The locations() element/field of a @DefaultQualifier annotation. */ - protected final ExecutableElement defaultQualifierLocationsElement; - - /** The applyToSubpackages() element/field of a @DefaultQualifier annotation. */ - protected final ExecutableElement defaultQualifierApplyToSubpackagesElement; - - /** The value() element/field of a @DefaultQualifier.List annotation. */ - protected final ExecutableElement defaultQualifierListValueElement; - - /** AnnotatedTypeFactory to use. */ - private final AnnotatedTypeFactory atypeFactory; - - /** Defaults for checked code. */ - private final DefaultSet checkedCodeDefaults = new DefaultSet(); - - /** Defaults for unchecked code. */ - private final DefaultSet uncheckedCodeDefaults = new DefaultSet(); - - /** Size for caches. */ - private static final int CACHE_SIZE = 300; - - /** Mapping from an Element to the bound type. */ - protected final Map elementToBoundType = - CollectionsPlume.createLruCache(CACHE_SIZE); - - /** - * Defaults that apply for a certain Element. On the one hand this is used for caching (an - * earlier name for the field was "qualifierCache"). It can also be used by type systems to set - * defaults for certain Elements. - */ - private final IdentityHashMap elementDefaults = new IdentityHashMap<>(); - - /** A mapping of Element → Whether or not that element is AnnotatedFor this type system. */ - private final IdentityHashMap elementAnnotatedFors = new IdentityHashMap<>(); - - /** CLIMB locations whose standard default is top for a given type system. */ - public static final List STANDARD_CLIMB_DEFAULTS_TOP = - Collections.unmodifiableList( - Arrays.asList( - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.RESOURCE_VARIABLE, - TypeUseLocation.EXCEPTION_PARAMETER, - TypeUseLocation.IMPLICIT_UPPER_BOUND)); - - /** CLIMB locations whose standard default is bottom for a given type system. */ - public static final List STANDARD_CLIMB_DEFAULTS_BOTTOM = - Collections.unmodifiableList(Arrays.asList(TypeUseLocation.IMPLICIT_LOWER_BOUND)); - - /** List of TypeUseLocations that are valid for unchecked code defaults. */ - private static final List validUncheckedCodeDefaultLocations = - Collections.unmodifiableList( - Arrays.asList( - TypeUseLocation.FIELD, - TypeUseLocation.PARAMETER, - TypeUseLocation.RETURN, - TypeUseLocation.RECEIVER, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.OTHERWISE, - TypeUseLocation.ALL)); - - /** Standard unchecked default locations that should be top. */ - // Fields are defaulted to top so that warnings are issued at field reads, which we believe are - // more common than field writes. Future work is to specify different defaults for field reads - // and field writes. (When a field is written to, its type should be bottom.) - public static final List STANDARD_UNCHECKED_DEFAULTS_TOP = - Collections.unmodifiableList( - Arrays.asList( - TypeUseLocation.RETURN, - TypeUseLocation.FIELD, - TypeUseLocation.UPPER_BOUND)); - - /** Standard unchecked default locations that should be bottom. */ - public static final List STANDARD_UNCHECKED_DEFAULTS_BOTTOM = - Collections.unmodifiableList( - Arrays.asList(TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND)); - - /** True if conservative defaults should be used in unannotated source code. */ - private final boolean useConservativeDefaultsSource; - - /** True if conservative defaults should be used for bytecode. */ - private final boolean useConservativeDefaultsBytecode; - - /** - * Returns an array of locations that are valid for the unchecked value defaults. These are - * simply by syntax, since an entire file is typechecked, it is not possible for local variables - * to be unchecked. - */ - public static List validLocationsForUncheckedCodeDefaults() { - return validUncheckedCodeDefaultLocations; - } - - /** - * @param elements interface to Element data in the current processing environment - * @param atypeFactory an annotation factory, used to get annotations by name - */ - public QualifierDefaults(Elements elements, AnnotatedTypeFactory atypeFactory) { - this.elements = elements; - this.atypeFactory = atypeFactory; - this.useConservativeDefaultsBytecode = - atypeFactory.getChecker().useConservativeDefault("bytecode"); - this.useConservativeDefaultsSource = - atypeFactory.getChecker().useConservativeDefault("source"); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - this.defaultQualifierValueElement = - TreeUtils.getMethod(DefaultQualifier.class, "value", 0, processingEnv); - this.defaultQualifierLocationsElement = - TreeUtils.getMethod(DefaultQualifier.class, "locations", 0, processingEnv); - this.defaultQualifierApplyToSubpackagesElement = - TreeUtils.getMethod(DefaultQualifier.class, "applyToSubpackages", 0, processingEnv); - this.defaultQualifierListValueElement = - TreeUtils.getMethod(DefaultQualifier.List.class, "value", 0, processingEnv); - } - - @Override - public String toString() { - // displays the checked and unchecked code defaults - return StringsPlume.joinLines( - "Checked code defaults: ", - StringsPlume.joinLines(checkedCodeDefaults), - "Unchecked code defaults: ", - StringsPlume.joinLines(uncheckedCodeDefaults), - "useConservativeDefaultsSource: " + useConservativeDefaultsSource, - "useConservativeDefaultsBytecode: " + useConservativeDefaultsBytecode); + // TODO add visitor state to get the default annotations from the top down? + // TODO apply from package elements also + // TODO try to remove some dependencies (e.g. on factory) + + /** Element utilities to use. */ + private final Elements elements; + + /** The value() element/field of a @DefaultQualifier annotation. */ + protected final ExecutableElement defaultQualifierValueElement; + + /** The locations() element/field of a @DefaultQualifier annotation. */ + protected final ExecutableElement defaultQualifierLocationsElement; + + /** The applyToSubpackages() element/field of a @DefaultQualifier annotation. */ + protected final ExecutableElement defaultQualifierApplyToSubpackagesElement; + + /** The value() element/field of a @DefaultQualifier.List annotation. */ + protected final ExecutableElement defaultQualifierListValueElement; + + /** AnnotatedTypeFactory to use. */ + private final AnnotatedTypeFactory atypeFactory; + + /** Defaults for checked code. */ + private final DefaultSet checkedCodeDefaults = new DefaultSet(); + + /** Defaults for unchecked code. */ + private final DefaultSet uncheckedCodeDefaults = new DefaultSet(); + + /** Size for caches. */ + private static final int CACHE_SIZE = 300; + + /** Mapping from an Element to the bound type. */ + protected final Map elementToBoundType = + CollectionsPlume.createLruCache(CACHE_SIZE); + + /** + * Defaults that apply for a certain Element. On the one hand this is used for caching (an earlier + * name for the field was "qualifierCache"). It can also be used by type systems to set defaults + * for certain Elements. + */ + private final IdentityHashMap elementDefaults = new IdentityHashMap<>(); + + /** A mapping of Element → Whether or not that element is AnnotatedFor this type system. */ + private final IdentityHashMap elementAnnotatedFors = new IdentityHashMap<>(); + + /** CLIMB locations whose standard default is top for a given type system. */ + public static final List STANDARD_CLIMB_DEFAULTS_TOP = + Collections.unmodifiableList( + Arrays.asList( + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.RESOURCE_VARIABLE, + TypeUseLocation.EXCEPTION_PARAMETER, + TypeUseLocation.IMPLICIT_UPPER_BOUND)); + + /** CLIMB locations whose standard default is bottom for a given type system. */ + public static final List STANDARD_CLIMB_DEFAULTS_BOTTOM = + Collections.unmodifiableList(Arrays.asList(TypeUseLocation.IMPLICIT_LOWER_BOUND)); + + /** List of TypeUseLocations that are valid for unchecked code defaults. */ + private static final List validUncheckedCodeDefaultLocations = + Collections.unmodifiableList( + Arrays.asList( + TypeUseLocation.FIELD, + TypeUseLocation.PARAMETER, + TypeUseLocation.RETURN, + TypeUseLocation.RECEIVER, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.OTHERWISE, + TypeUseLocation.ALL)); + + /** Standard unchecked default locations that should be top. */ + // Fields are defaulted to top so that warnings are issued at field reads, which we believe are + // more common than field writes. Future work is to specify different defaults for field reads + // and field writes. (When a field is written to, its type should be bottom.) + public static final List STANDARD_UNCHECKED_DEFAULTS_TOP = + Collections.unmodifiableList( + Arrays.asList( + TypeUseLocation.RETURN, TypeUseLocation.FIELD, TypeUseLocation.UPPER_BOUND)); + + /** Standard unchecked default locations that should be bottom. */ + public static final List STANDARD_UNCHECKED_DEFAULTS_BOTTOM = + Collections.unmodifiableList( + Arrays.asList(TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND)); + + /** True if conservative defaults should be used in unannotated source code. */ + private final boolean useConservativeDefaultsSource; + + /** True if conservative defaults should be used for bytecode. */ + private final boolean useConservativeDefaultsBytecode; + + /** + * Returns an array of locations that are valid for the unchecked value defaults. These are simply + * by syntax, since an entire file is typechecked, it is not possible for local variables to be + * unchecked. + */ + public static List validLocationsForUncheckedCodeDefaults() { + return validUncheckedCodeDefaultLocations; + } + + /** + * @param elements interface to Element data in the current processing environment + * @param atypeFactory an annotation factory, used to get annotations by name + */ + public QualifierDefaults(Elements elements, AnnotatedTypeFactory atypeFactory) { + this.elements = elements; + this.atypeFactory = atypeFactory; + this.useConservativeDefaultsBytecode = + atypeFactory.getChecker().useConservativeDefault("bytecode"); + this.useConservativeDefaultsSource = atypeFactory.getChecker().useConservativeDefault("source"); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + this.defaultQualifierValueElement = + TreeUtils.getMethod(DefaultQualifier.class, "value", 0, processingEnv); + this.defaultQualifierLocationsElement = + TreeUtils.getMethod(DefaultQualifier.class, "locations", 0, processingEnv); + this.defaultQualifierApplyToSubpackagesElement = + TreeUtils.getMethod(DefaultQualifier.class, "applyToSubpackages", 0, processingEnv); + this.defaultQualifierListValueElement = + TreeUtils.getMethod(DefaultQualifier.List.class, "value", 0, processingEnv); + } + + @Override + public String toString() { + // displays the checked and unchecked code defaults + return StringsPlume.joinLines( + "Checked code defaults: ", + StringsPlume.joinLines(checkedCodeDefaults), + "Unchecked code defaults: ", + StringsPlume.joinLines(uncheckedCodeDefaults), + "useConservativeDefaultsSource: " + useConservativeDefaultsSource, + "useConservativeDefaultsBytecode: " + useConservativeDefaultsBytecode); + } + + /** + * Check that a default with TypeUseLocation OTHERWISE or ALL is specified. + * + * @return whether we found a Default with location OTHERWISE or ALL + */ + public boolean hasDefaultsForCheckedCode() { + for (Default def : checkedCodeDefaults) { + if (def.location == TypeUseLocation.OTHERWISE || def.location == TypeUseLocation.ALL) { + return true; + } } - - /** - * Check that a default with TypeUseLocation OTHERWISE or ALL is specified. - * - * @return whether we found a Default with location OTHERWISE or ALL - */ - public boolean hasDefaultsForCheckedCode() { - for (Default def : checkedCodeDefaults) { - if (def.location == TypeUseLocation.OTHERWISE || def.location == TypeUseLocation.ALL) { - return true; - } + return false; + } + + /** Add standard unchecked defaults that do not conflict with previously added defaults. */ + public void addUncheckedStandardDefaults() { + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); + + for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_TOP) { + // Only add standard defaults in locations where a default has not be specified. + for (AnnotationMirror top : tops) { + if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, top, loc)) { + addUncheckedCodeDefault(top, loc); } - return false; + } } - /** Add standard unchecked defaults that do not conflict with previously added defaults. */ - public void addUncheckedStandardDefaults() { - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); - - for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_TOP) { - // Only add standard defaults in locations where a default has not be specified. - for (AnnotationMirror top : tops) { - if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, top, loc)) { - addUncheckedCodeDefault(top, loc); - } - } - } - - for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_BOTTOM) { - for (AnnotationMirror bottom : bottoms) { - // Only add standard defaults in locations where a default has not be specified. - if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, bottom, loc)) { - addUncheckedCodeDefault(bottom, loc); - } - } + for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_BOTTOM) { + for (AnnotationMirror bottom : bottoms) { + // Only add standard defaults in locations where a default has not be specified. + if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, bottom, loc)) { + addUncheckedCodeDefault(bottom, loc); } + } } - - /** Add standard CLIMB defaults that do not conflict with previously added defaults. */ - public void addClimbStandardDefaults() { - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); - - for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_TOP) { - for (AnnotationMirror top : tops) { - if (!conflictsWithExistingDefaults(checkedCodeDefaults, top, loc)) { - // Only add standard defaults in locations where a default has not been - // specified. - addCheckedCodeDefault(top, loc); - } - } + } + + /** Add standard CLIMB defaults that do not conflict with previously added defaults. */ + public void addClimbStandardDefaults() { + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); + + for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_TOP) { + for (AnnotationMirror top : tops) { + if (!conflictsWithExistingDefaults(checkedCodeDefaults, top, loc)) { + // Only add standard defaults in locations where a default has not been + // specified. + addCheckedCodeDefault(top, loc); } + } + } - for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_BOTTOM) { - for (AnnotationMirror bottom : bottoms) { - if (!conflictsWithExistingDefaults(checkedCodeDefaults, bottom, loc)) { - // Only add standard defaults in locations where a default has not been - // specified. - addCheckedCodeDefault(bottom, loc); - } - } + for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_BOTTOM) { + for (AnnotationMirror bottom : bottoms) { + if (!conflictsWithExistingDefaults(checkedCodeDefaults, bottom, loc)) { + // Only add standard defaults in locations where a default has not been + // specified. + addCheckedCodeDefault(bottom, loc); } + } } - - /** - * Adds a default annotation. A programmer may override this by writing the @DefaultQualifier - * annotation on an element. - * - * @param absoluteDefaultAnno the default annotation mirror - * @param location the type use location - * @param applyToSubpackages whether the default should be inherited by subpackages - */ - public void addCheckedCodeDefault( - AnnotationMirror absoluteDefaultAnno, - TypeUseLocation location, - boolean applyToSubpackages) { - checkDuplicates(checkedCodeDefaults, absoluteDefaultAnno, location); - checkedCodeDefaults.add(new Default(absoluteDefaultAnno, location, applyToSubpackages)); + } + + /** + * Adds a default annotation. A programmer may override this by writing the @DefaultQualifier + * annotation on an element. + * + * @param absoluteDefaultAnno the default annotation mirror + * @param location the type use location + * @param applyToSubpackages whether the default should be inherited by subpackages + */ + public void addCheckedCodeDefault( + AnnotationMirror absoluteDefaultAnno, TypeUseLocation location, boolean applyToSubpackages) { + checkDuplicates(checkedCodeDefaults, absoluteDefaultAnno, location); + checkedCodeDefaults.add(new Default(absoluteDefaultAnno, location, applyToSubpackages)); + } + + /** + * Adds a default annotation that also applies to subpackages, if applicable. A programmer may + * override this by writing the @DefaultQualifier annotation on an element. + * + * @param absoluteDefaultAnno the default annotation mirror + * @param location the type use location + */ + public void addCheckedCodeDefault( + AnnotationMirror absoluteDefaultAnno, TypeUseLocation location) { + addCheckedCodeDefault(absoluteDefaultAnno, location, true); + } + + /** + * Add a default annotation for unchecked elements. + * + * @param uncheckedDefaultAnno the default annotation mirror + * @param location the type use location + * @param applyToSubpackages whether the default should be inherited by subpackages + */ + public void addUncheckedCodeDefault( + AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location, boolean applyToSubpackages) { + checkDuplicates(uncheckedCodeDefaults, uncheckedDefaultAnno, location); + checkIsValidUncheckedCodeLocation(uncheckedDefaultAnno, location); + + uncheckedCodeDefaults.add(new Default(uncheckedDefaultAnno, location, applyToSubpackages)); + } + + /** + * Add a default annotation for unchecked elements that also applies to subpackages, if + * applicable. + * + * @param uncheckedDefaultAnno the default annotation mirror + * @param location the type use location + */ + public void addUncheckedCodeDefault( + AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { + addUncheckedCodeDefault(uncheckedDefaultAnno, location, true); + } + + /** Sets the default annotation for unchecked elements, with specific locations. */ + public void addUncheckedCodeDefaults( + AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { + for (TypeUseLocation location : locations) { + addUncheckedCodeDefault(absoluteDefaultAnno, location); } + } - /** - * Adds a default annotation that also applies to subpackages, if applicable. A programmer may - * override this by writing the @DefaultQualifier annotation on an element. - * - * @param absoluteDefaultAnno the default annotation mirror - * @param location the type use location - */ - public void addCheckedCodeDefault( - AnnotationMirror absoluteDefaultAnno, TypeUseLocation location) { - addCheckedCodeDefault(absoluteDefaultAnno, location, true); + public void addCheckedCodeDefaults( + AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { + for (TypeUseLocation location : locations) { + addCheckedCodeDefault(absoluteDefaultAnno, location); } - - /** - * Add a default annotation for unchecked elements. - * - * @param uncheckedDefaultAnno the default annotation mirror - * @param location the type use location - * @param applyToSubpackages whether the default should be inherited by subpackages - */ - public void addUncheckedCodeDefault( - AnnotationMirror uncheckedDefaultAnno, - TypeUseLocation location, - boolean applyToSubpackages) { - checkDuplicates(uncheckedCodeDefaults, uncheckedDefaultAnno, location); - checkIsValidUncheckedCodeLocation(uncheckedDefaultAnno, location); - - uncheckedCodeDefaults.add(new Default(uncheckedDefaultAnno, location, applyToSubpackages)); + } + + /** + * Sets the default annotations for a certain Element. + * + * @param elem the scope to set the default within + * @param elementDefaultAnno the default to set + * @param location the location to apply the default to + */ + /* + * TODO(cpovirk): This method looks dangerous for a type system to call early: If it "adds" a + * default for an Element before defaultsAt runs for that Element, that looks like it would + * prevent any @DefaultQualifier or similar annotation from having any effect (because + * defaultsAt would short-circuit after discovering that an entry already exists for the + * Element). Maybe this method should run defaultsAt before inserting its own entry? Or maybe + * it's too early to run defaultsAt? Or maybe we'd see new problems in existing code because + * we'd start running checkDuplicates to look for overlap between the @DefaultQualifier defaults + * and addElementDefault defaults? + */ + public void addElementDefault( + Element elem, AnnotationMirror elementDefaultAnno, TypeUseLocation location) { + DefaultSet prevset = elementDefaults.get(elem); + if (prevset != null) { + checkDuplicates(prevset, elementDefaultAnno, location); + } else { + prevset = new DefaultSet(); } - - /** - * Add a default annotation for unchecked elements that also applies to subpackages, if - * applicable. - * - * @param uncheckedDefaultAnno the default annotation mirror - * @param location the type use location - */ - public void addUncheckedCodeDefault( - AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { - addUncheckedCodeDefault(uncheckedDefaultAnno, location, true); + // TODO: expose applyToSubpackages + prevset.add(new Default(elementDefaultAnno, location, true)); + elementDefaults.put(elem, prevset); + } + + private void checkIsValidUncheckedCodeLocation( + AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { + boolean isValidUntypeLocation = false; + for (TypeUseLocation validLoc : validLocationsForUncheckedCodeDefaults()) { + if (location == validLoc) { + isValidUntypeLocation = true; + break; + } } - /** Sets the default annotation for unchecked elements, with specific locations. */ - public void addUncheckedCodeDefaults( - AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { - for (TypeUseLocation location : locations) { - addUncheckedCodeDefault(absoluteDefaultAnno, location); - } + if (!isValidUntypeLocation) { + throw new BugInCF( + "Invalid unchecked code default location: " + location + " -> " + uncheckedDefaultAnno); } - - public void addCheckedCodeDefaults( - AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { - for (TypeUseLocation location : locations) { - addCheckedCodeDefault(absoluteDefaultAnno, location); - } + } + + private void checkDuplicates( + DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { + if (conflictsWithExistingDefaults(previousDefaults, newAnno, newLoc)) { + throw new BugInCF( + "Only one qualifier from a hierarchy can be the default. Existing: " + + previousDefaults + + " and new: " + // TODO: expose applyToSubpackages + + new Default(newAnno, newLoc, true)); } - - /** - * Sets the default annotations for a certain Element. - * - * @param elem the scope to set the default within - * @param elementDefaultAnno the default to set - * @param location the location to apply the default to - */ - /* - * TODO(cpovirk): This method looks dangerous for a type system to call early: If it "adds" a - * default for an Element before defaultsAt runs for that Element, that looks like it would - * prevent any @DefaultQualifier or similar annotation from having any effect (because - * defaultsAt would short-circuit after discovering that an entry already exists for the - * Element). Maybe this method should run defaultsAt before inserting its own entry? Or maybe - * it's too early to run defaultsAt? Or maybe we'd see new problems in existing code because - * we'd start running checkDuplicates to look for overlap between the @DefaultQualifier defaults - * and addElementDefault defaults? - */ - public void addElementDefault( - Element elem, AnnotationMirror elementDefaultAnno, TypeUseLocation location) { - DefaultSet prevset = elementDefaults.get(elem); - if (prevset != null) { - checkDuplicates(prevset, elementDefaultAnno, location); - } else { - prevset = new DefaultSet(); + } + + /** + * Returns true if there are conflicts with existing defaults. + * + * @param previousDefaults the previous defaults + * @param newAnno the new annotation + * @param newLoc the location of the type use + * @return true if there are conflicts with existing defaults + */ + private boolean conflictsWithExistingDefaults( + DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + + for (Default previous : previousDefaults) { + if (!AnnotationUtils.areSame(newAnno, previous.anno) && previous.location == newLoc) { + AnnotationMirror previousTop = qualHierarchy.getTopAnnotation(previous.anno); + if (qualHierarchy.isSubtypeQualifiersOnly(newAnno, previousTop)) { + return true; } - // TODO: expose applyToSubpackages - prevset.add(new Default(elementDefaultAnno, location, true)); - elementDefaults.put(elem, prevset); + } } - - private void checkIsValidUncheckedCodeLocation( - AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { - boolean isValidUntypeLocation = false; - for (TypeUseLocation validLoc : validLocationsForUncheckedCodeDefaults()) { - if (location == validLoc) { - isValidUntypeLocation = true; - break; - } - } - - if (!isValidUntypeLocation) { - throw new BugInCF( - "Invalid unchecked code default location: " - + location - + " -> " - + uncheckedDefaultAnno); - } + return false; + } + + /** + * Applies default annotations to a type obtained from an {@link + * javax.lang.model.element.Element}. + * + * @param elt the element from which the type was obtained + * @param type the type to annotate + */ + public void annotate(Element elt, AnnotatedTypeMirror type) { + if (elt != null) { + switch (elt.getKind()) { + case FIELD: + case LOCAL_VARIABLE: + case PARAMETER: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + case ENUM_CONSTANT: + String varName = elt.getSimpleName().toString(); + ((GenericAnnotatedTypeFactory) atypeFactory) + .getDefaultForTypeAnnotator() + .defaultTypeFromName(type, varName); + break; + + case METHOD: + String methodName = elt.getSimpleName().toString(); + AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) type).getReturnType(); + ((GenericAnnotatedTypeFactory) atypeFactory) + .getDefaultForTypeAnnotator() + .defaultTypeFromName(returnType, methodName); + break; + + default: + break; + } } - private void checkDuplicates( - DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { - if (conflictsWithExistingDefaults(previousDefaults, newAnno, newLoc)) { - throw new BugInCF( - "Only one qualifier from a hierarchy can be the default. Existing: " - + previousDefaults - + " and new: " - // TODO: expose applyToSubpackages - + new Default(newAnno, newLoc, true)); - } + applyDefaultsElement(elt, type, false); + } + + /** + * Applies default annotations to a type given a {@link com.sun.source.tree.Tree}. + * + * @param tree the tree from which the type was obtained + * @param type the type to annotate + */ + public void annotate(Tree tree, AnnotatedTypeMirror type) { + applyDefaults(tree, type); + } + + /** + * Determines the nearest enclosing element for a tree by climbing the tree toward the root and + * obtaining the element for the first declaration (variable, method, or class) that encloses the + * tree. Initializers of local variables are handled in a special way: within an initializer we + * look for the DefaultQualifier(s) annotation and keep track of the previously visited tree. + * TODO: explain the behavior better. + * + * @param tree the tree + * @return the nearest enclosing element for a tree + */ + private @Nullable Element nearestEnclosingExceptLocal(Tree tree) { + TreePath path = atypeFactory.getPath(tree); + if (path == null) { + return TreeUtils.elementFromTree(tree); } - /** - * Returns true if there are conflicts with existing defaults. - * - * @param previousDefaults the previous defaults - * @param newAnno the new annotation - * @param newLoc the location of the type use - * @return true if there are conflicts with existing defaults - */ - private boolean conflictsWithExistingDefaults( - DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - - for (Default previous : previousDefaults) { - if (!AnnotationUtils.areSame(newAnno, previous.anno) && previous.location == newLoc) { - AnnotationMirror previousTop = qualHierarchy.getTopAnnotation(previous.anno); - if (qualHierarchy.isSubtypeQualifiersOnly(newAnno, previousTop)) { - return true; - } + Tree prev = null; + + for (Tree t : path) { + switch (TreeUtils.getKindRecordAsClass(t)) { + case ANNOTATED_TYPE: + case ANNOTATION: + // If the tree is in an annotation, then there is no relevant scope. + return null; + case VARIABLE: + VariableTree vtree = (VariableTree) t; + ExpressionTree vtreeInit = vtree.getInitializer(); + @SuppressWarnings("interning:not.interned") // check cached value + boolean sameAsPrev = (vtreeInit != null && prev == vtreeInit); + if (sameAsPrev) { + Element elt = TreeUtils.elementFromDeclaration((VariableTree) t); + AnnotationMirror d = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); + AnnotationMirror ds = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); + + if (d == null && ds == null) { + break; } - } - return false; + } + if (prev != null && prev.getKind() == Tree.Kind.MODIFIERS) { + // Annotations are modifiers. We do not want to apply the local variable + // default to annotations. Without this, test fenum/TestSwitch failed, + // because the default for an argument became incompatible with the declared + // type. + break; + } + return TreeUtils.elementFromDeclaration((VariableTree) t); + case METHOD: + return TreeUtils.elementFromDeclaration((MethodTree) t); + case CLASS: // Including RECORD + case ENUM: + case INTERFACE: + case ANNOTATION_TYPE: + return TreeUtils.elementFromDeclaration((ClassTree) t); + default: // Do nothing. + } + prev = t; } - /** - * Applies default annotations to a type obtained from an {@link - * javax.lang.model.element.Element}. - * - * @param elt the element from which the type was obtained - * @param type the type to annotate - */ - public void annotate(Element elt, AnnotatedTypeMirror type) { - if (elt != null) { - switch (elt.getKind()) { - case FIELD: - case LOCAL_VARIABLE: - case PARAMETER: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - case ENUM_CONSTANT: - String varName = elt.getSimpleName().toString(); - ((GenericAnnotatedTypeFactory) atypeFactory) - .getDefaultForTypeAnnotator() - .defaultTypeFromName(type, varName); - break; - - case METHOD: - String methodName = elt.getSimpleName().toString(); - AnnotatedTypeMirror returnType = - ((AnnotatedExecutableType) type).getReturnType(); - ((GenericAnnotatedTypeFactory) atypeFactory) - .getDefaultForTypeAnnotator() - .defaultTypeFromName(returnType, methodName); - break; - - default: - break; - } + return null; + } + + /** + * Applies default annotations to a type. A {@link com.sun.source.tree.Tree} determines the + * appropriate scope for defaults. + * + *

For instance, if the tree is associated with a declaration (e.g., it's the use of a field, + * or a method invocation), defaults in the scope of the declaration are used; if the tree + * is not associated with a declaration (e.g., a typecast), defaults in the scope of the tree are + * used. + * + * @param tree the tree associated with the type + * @param type the type to which defaults will be applied + * @see #applyDefaultsElement(javax.lang.model.element.Element, + * org.checkerframework.framework.type.AnnotatedTypeMirror,boolean) + */ + private void applyDefaults(Tree tree, AnnotatedTypeMirror type) { + // The location to take defaults from. + Element elt; + switch (tree.getKind()) { + case MEMBER_SELECT: + elt = TreeUtils.elementFromUse((MemberSelectTree) tree); + break; + + case IDENTIFIER: + elt = TreeUtils.elementFromUse((IdentifierTree) tree); + if (ElementUtils.isTypeDeclaration(elt)) { + // If the identifier is a type, then use the scope of the tree. + elt = nearestEnclosingExceptLocal(tree); } + break; - applyDefaultsElement(elt, type, false); - } + case METHOD_INVOCATION: + elt = TreeUtils.elementFromUse((MethodInvocationTree) tree); + break; - /** - * Applies default annotations to a type given a {@link com.sun.source.tree.Tree}. - * - * @param tree the tree from which the type was obtained - * @param type the type to annotate - */ - public void annotate(Tree tree, AnnotatedTypeMirror type) { - applyDefaults(tree, type); - } + // TODO cases for array access, etc. -- every expression tree + // (The above probably means that we should use defaults in the + // scope of the declaration of the array. Is that right? -MDE) - /** - * Determines the nearest enclosing element for a tree by climbing the tree toward the root and - * obtaining the element for the first declaration (variable, method, or class) that encloses - * the tree. Initializers of local variables are handled in a special way: within an initializer - * we look for the DefaultQualifier(s) annotation and keep track of the previously visited tree. - * TODO: explain the behavior better. - * - * @param tree the tree - * @return the nearest enclosing element for a tree - */ - private @Nullable Element nearestEnclosingExceptLocal(Tree tree) { - TreePath path = atypeFactory.getPath(tree); - if (path == null) { - return TreeUtils.elementFromTree(tree); - } + default: + // If no associated symbol was found, use the tree's (lexical) scope. + elt = nearestEnclosingExceptLocal(tree); + // elt = nearestEnclosing(tree); + } + // System.out.println("applyDefaults on tree " + tree + + // " gives elt: " + elt + "(" + elt.getKind() + ")"); + + applyDefaultsElement(elt, type, true); + } + + /** The default {@code value} element for a @DefaultQualifier annotation. */ + private static final TypeUseLocation[] defaultQualifierValueDefault = + new TypeUseLocation[] {org.checkerframework.framework.qual.TypeUseLocation.ALL}; + + /** + * Create a DefaultSet from a @DefaultQualifier annotation. + * + * @param dq a @DefaultQualifier annotation + * @return a DefaultSet corresponding to the @DefaultQualifier annotation + */ + private @Nullable DefaultSet fromDefaultQualifier(AnnotationMirror dq) { + @SuppressWarnings("unchecked") + Name cls = AnnotationUtils.getElementValueClassName(dq, defaultQualifierValueElement); + AnnotationMirror anno = AnnotationBuilder.fromName(elements, cls); + + if (anno == null) { + return null; + } - Tree prev = null; - - for (Tree t : path) { - switch (TreeUtils.getKindRecordAsClass(t)) { - case ANNOTATED_TYPE: - case ANNOTATION: - // If the tree is in an annotation, then there is no relevant scope. - return null; - case VARIABLE: - VariableTree vtree = (VariableTree) t; - ExpressionTree vtreeInit = vtree.getInitializer(); - @SuppressWarnings("interning:not.interned") // check cached value - boolean sameAsPrev = (vtreeInit != null && prev == vtreeInit); - if (sameAsPrev) { - Element elt = TreeUtils.elementFromDeclaration((VariableTree) t); - AnnotationMirror d = - atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); - AnnotationMirror ds = - atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); - - if (d == null && ds == null) { - break; - } - } - if (prev != null && prev.getKind() == Tree.Kind.MODIFIERS) { - // Annotations are modifiers. We do not want to apply the local variable - // default to annotations. Without this, test fenum/TestSwitch failed, - // because the default for an argument became incompatible with the declared - // type. - break; - } - return TreeUtils.elementFromDeclaration((VariableTree) t); - case METHOD: - return TreeUtils.elementFromDeclaration((MethodTree) t); - case CLASS: // Including RECORD - case ENUM: - case INTERFACE: - case ANNOTATION_TYPE: - return TreeUtils.elementFromDeclaration((ClassTree) t); - default: // Do nothing. - } - prev = t; - } + if (!atypeFactory.isSupportedQualifier(anno)) { + anno = atypeFactory.canonicalAnnotation(anno); + } - return null; + if (atypeFactory.isSupportedQualifier(anno)) { + TypeUseLocation[] locations = + AnnotationUtils.getElementValueEnumArray( + dq, + defaultQualifierLocationsElement, + TypeUseLocation.class, + defaultQualifierValueDefault); + boolean applyToSubpackages = + AnnotationUtils.getElementValue( + dq, defaultQualifierApplyToSubpackagesElement, Boolean.class, true); + + DefaultSet ret = new DefaultSet(); + for (TypeUseLocation loc : locations) { + ret.add(new Default(anno, loc, applyToSubpackages)); + } + return ret; + } else { + return null; } + } - /** - * Applies default annotations to a type. A {@link com.sun.source.tree.Tree} determines the - * appropriate scope for defaults. - * - *

For instance, if the tree is associated with a declaration (e.g., it's the use of a field, - * or a method invocation), defaults in the scope of the declaration are used; if the - * tree is not associated with a declaration (e.g., a typecast), defaults in the scope of the - * tree are used. - * - * @param tree the tree associated with the type - * @param type the type to which defaults will be applied - * @see #applyDefaultsElement(javax.lang.model.element.Element, - * org.checkerframework.framework.type.AnnotatedTypeMirror,boolean) - */ - private void applyDefaults(Tree tree, AnnotatedTypeMirror type) { - // The location to take defaults from. - Element elt; - switch (tree.getKind()) { - case MEMBER_SELECT: - elt = TreeUtils.elementFromUse((MemberSelectTree) tree); - break; - - case IDENTIFIER: - elt = TreeUtils.elementFromUse((IdentifierTree) tree); - if (ElementUtils.isTypeDeclaration(elt)) { - // If the identifier is a type, then use the scope of the tree. - elt = nearestEnclosingExceptLocal(tree); - } - break; - - case METHOD_INVOCATION: - elt = TreeUtils.elementFromUse((MethodInvocationTree) tree); - break; - - // TODO cases for array access, etc. -- every expression tree - // (The above probably means that we should use defaults in the - // scope of the declaration of the array. Is that right? -MDE) - - default: - // If no associated symbol was found, use the tree's (lexical) scope. - elt = nearestEnclosingExceptLocal(tree); - // elt = nearestEnclosing(tree); - } - // System.out.println("applyDefaults on tree " + tree + - // " gives elt: " + elt + "(" + elt.getKind() + ")"); + private boolean isElementAnnotatedForThisChecker(Element elt) { + boolean elementAnnotatedForThisChecker = false; - applyDefaultsElement(elt, type, true); + if (elt == null) { + throw new BugInCF("Call of QualifierDefaults.isElementAnnotatedForThisChecker with null"); } - /** The default {@code value} element for a @DefaultQualifier annotation. */ - private static final TypeUseLocation[] defaultQualifierValueDefault = - new TypeUseLocation[] {org.checkerframework.framework.qual.TypeUseLocation.ALL}; + if (elementAnnotatedFors.containsKey(elt)) { + return elementAnnotatedFors.get(elt); + } - /** - * Create a DefaultSet from a @DefaultQualifier annotation. - * - * @param dq a @DefaultQualifier annotation - * @return a DefaultSet corresponding to the @DefaultQualifier annotation - */ - private @Nullable DefaultSet fromDefaultQualifier(AnnotationMirror dq) { - @SuppressWarnings("unchecked") - Name cls = AnnotationUtils.getElementValueClassName(dq, defaultQualifierValueElement); - AnnotationMirror anno = AnnotationBuilder.fromName(elements, cls); + AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class); - if (anno == null) { - return null; - } + if (annotatedFor != null) { + elementAnnotatedForThisChecker = + atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor); + } - if (!atypeFactory.isSupportedQualifier(anno)) { - anno = atypeFactory.canonicalAnnotation(anno); - } + if (!elementAnnotatedForThisChecker) { + Element parent; + if (elt.getKind() == ElementKind.PACKAGE) { + // TODO: should AnnotatedFor apply to subpackages?? + // elt.getEnclosingElement() on a package is null; therefore, + // use the dedicated method. + parent = ElementUtils.parentPackage((PackageElement) elt, elements); + } else { + parent = elt.getEnclosingElement(); + } + + if (parent != null && isElementAnnotatedForThisChecker(parent)) { + elementAnnotatedForThisChecker = true; + } + } - if (atypeFactory.isSupportedQualifier(anno)) { - TypeUseLocation[] locations = - AnnotationUtils.getElementValueEnumArray( - dq, - defaultQualifierLocationsElement, - TypeUseLocation.class, - defaultQualifierValueDefault); - boolean applyToSubpackages = - AnnotationUtils.getElementValue( - dq, defaultQualifierApplyToSubpackagesElement, Boolean.class, true); - - DefaultSet ret = new DefaultSet(); - for (TypeUseLocation loc : locations) { - ret.add(new Default(anno, loc, applyToSubpackages)); - } - return ret; - } else { - return null; - } + elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker); + + return elementAnnotatedForThisChecker; + } + + /** + * Returns the defaults that apply to the given Element, considering defaults from enclosing + * Elements. + * + * @param elt the element + * @return the defaults + */ + private DefaultSet defaultsAt(Element elt) { + if (elt == null) { + return DefaultSet.EMPTY; } - private boolean isElementAnnotatedForThisChecker(Element elt) { - boolean elementAnnotatedForThisChecker = false; + if (elementDefaults.containsKey(elt)) { + return elementDefaults.get(elt); + } - if (elt == null) { - throw new BugInCF( - "Call of QualifierDefaults.isElementAnnotatedForThisChecker with null"); + DefaultSet qualifiers = defaultsAtDirect(elt); + DefaultSet parentDefaults; + if (elt.getKind() == ElementKind.PACKAGE) { + Element parent = ElementUtils.parentPackage((PackageElement) elt, elements); + DefaultSet origParentDefaults = defaultsAt(parent); + parentDefaults = new DefaultSet(); + for (Default d : origParentDefaults) { + if (d.applyToSubpackages) { + parentDefaults.add(d); } + } + } else { + Element parent = elt.getEnclosingElement(); + parentDefaults = defaultsAt(parent); + } - if (elementAnnotatedFors.containsKey(elt)) { - return elementAnnotatedFors.get(elt); - } + if (qualifiers == null || qualifiers.isEmpty()) { + qualifiers = parentDefaults; + } else { + // TODO(cpovirk): What should happen with conflicts? + qualifiers.addAll(parentDefaults); + } - AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class); + /* TODO: it would seem more efficient to also cache null/empty as the result. + * However, doing so causes KeyFor tests to fail. + if (qualifiers == null) { + qualifiers = DefaultSet.EMPTY; + } + + elementDefaults.put(elt, qualifiers); + return qualifiers; + */ + if (qualifiers != null && !qualifiers.isEmpty()) { + elementDefaults.put(elt, qualifiers); + return qualifiers; + } else { + return DefaultSet.EMPTY; + } + } + + /** + * Returns the defaults that apply directly to the given Element, without considering enclosing + * Elements. + * + * @param elt the element + * @return the defaults + */ + private DefaultSet defaultsAtDirect(Element elt) { + DefaultSet qualifiers = null; + + // Handle DefaultQualifier + AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); + + if (dqAnno != null) { + Set p = fromDefaultQualifier(dqAnno); + + if (p != null) { + qualifiers = new DefaultSet(); + qualifiers.addAll(p); + } + } - if (annotatedFor != null) { - elementAnnotatedForThisChecker = - atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor); + // Handle DefaultQualifier.List + AnnotationMirror dqListAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); + if (dqListAnno != null) { + if (qualifiers == null) { + qualifiers = new DefaultSet(); + } + List values = + AnnotationUtils.getElementValueArray( + dqListAnno, defaultQualifierListValueElement, AnnotationMirror.class); + for (AnnotationMirror dqlAnno : values) { + Set p = fromDefaultQualifier(dqlAnno); + if (p != null) { + // TODO(cpovirk): What should happen with conflicts? + qualifiers.addAll(p); } + } + } + return qualifiers; + } + + /** + * Given an element, returns whether the conservative default should be applied for it. Handles + * elements from bytecode or source code. + * + * @param annotationScope the element that the conservative default might apply to + * @return whether the conservative default applies to the given element + */ + public boolean applyConservativeDefaults(Element annotationScope) { + if (annotationScope == null) { + return false; + } - if (!elementAnnotatedForThisChecker) { - Element parent; - if (elt.getKind() == ElementKind.PACKAGE) { - // TODO: should AnnotatedFor apply to subpackages?? - // elt.getEnclosingElement() on a package is null; therefore, - // use the dedicated method. - parent = ElementUtils.parentPackage((PackageElement) elt, elements); - } else { - parent = elt.getEnclosingElement(); - } + if (uncheckedCodeDefaults.isEmpty()) { + return false; + } - if (parent != null && isElementAnnotatedForThisChecker(parent)) { - elementAnnotatedForThisChecker = true; - } - } + // TODO: I would expect this: + // atypeFactory.isFromByteCode(annotationScope)) { + // to work instead of the + // isElementFromByteCode/declarationFromElement/isFromStubFile calls, + // but it doesn't work correctly and tests fail. + + boolean isFromStubFile = atypeFactory.isFromStubFile(annotationScope); + boolean isBytecode = + ElementUtils.isElementFromByteCode(annotationScope) + && atypeFactory.declarationFromElement(annotationScope) == null + && !isFromStubFile; + if (isBytecode) { + return useConservativeDefaultsBytecode && !isElementAnnotatedForThisChecker(annotationScope); + } else if (isFromStubFile) { + // TODO: Types in stub files not annotated for a particular checker should be + // treated as unchecked bytecode. For now, all types in stub files are treated as + // checked code. Eventually, @AnnotatedFor("checker") will be programmatically added + // to methods in stub files supplied via the @StubFiles annotation. Stub files will + // be treated like unchecked code except for methods in the scope of an @AnnotatedFor. + return false; + } else if (useConservativeDefaultsSource) { + return !isElementAnnotatedForThisChecker(annotationScope); + } + return false; + } + + /** + * Applies default annotations to a type. Conservative defaults are applied first as appropriate, + * followed by source code defaults. + * + *

For a discussion on the rules for application of source code and conservative defaults, + * please see the linked manual sections. + * + * @param annotationScope the element representing the nearest enclosing default annotation scope + * for the type + * @param type the type to which defaults will be applied + * @param fromTree whether the element came from a tree + * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults and + * inference) + * @checker_framework.manual #annotating-libraries Annotating libraries + */ + private void applyDefaultsElement( + Element annotationScope, AnnotatedTypeMirror type, boolean fromTree) { + DefaultApplierElement applier = + createDefaultApplierElement(atypeFactory, annotationScope, type, fromTree); + + DefaultSet defaults = defaultsAt(annotationScope); + + // If there is a default for type variable uses, do not also apply checked/unchecked code + // defaults to type variables. Otherwise, the default in scope could decide not to annotate + // the type variable use, whereas the checked/unchecked code default could add an + // annotation. + // TODO: the checked/unchecked defaults should be added to `defaults` and then only one + // iteration through the defaults should be necessary. + boolean typeVarUseDef = false; + + for (Default def : defaults) { + applier.applyDefault(def); + typeVarUseDef |= (def.location == TypeUseLocation.TYPE_VARIABLE_USE); + } - elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker); + if (applyConservativeDefaults(annotationScope)) { + for (Default def : uncheckedCodeDefaults) { + if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { + applier.applyDefault(def); + } + } + } - return elementAnnotatedForThisChecker; + for (Default def : checkedCodeDefaults) { + if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { + applier.applyDefault(def); + } } + } + + /** + * Create the default applier element. + * + * @param atypeFactory the annotated type factory + * @param annotationScope the scope of the default + * @param type the type to which to apply the default + * @param fromTree whether the element came from a tree + * @return the default applier element + */ + protected DefaultApplierElement createDefaultApplierElement( + AnnotatedTypeFactory atypeFactory, + Element annotationScope, + AnnotatedTypeMirror type, + boolean fromTree) { + return new DefaultApplierElement(atypeFactory, annotationScope, type, fromTree); + } + + /** A default applier element. */ + protected class DefaultApplierElement { + + /** The annotated type factory. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** The qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; + + /** The scope of the default. */ + protected final Element scope; + + /** The type to which to apply the default. */ + protected final AnnotatedTypeMirror type; + + /** Whether the element came from a tree. */ + protected final boolean fromTree; /** - * Returns the defaults that apply to the given Element, considering defaults from enclosing - * Elements. + * True if type variable uses as top-level type of local variables should be defaulted. * - * @param elt the element - * @return the defaults + * @see GenericAnnotatedTypeFactory#getShouldDefaultTypeVarLocals() */ - private DefaultSet defaultsAt(Element elt) { - if (elt == null) { - return DefaultSet.EMPTY; - } - - if (elementDefaults.containsKey(elt)) { - return elementDefaults.get(elt); - } - - DefaultSet qualifiers = defaultsAtDirect(elt); - DefaultSet parentDefaults; - if (elt.getKind() == ElementKind.PACKAGE) { - Element parent = ElementUtils.parentPackage((PackageElement) elt, elements); - DefaultSet origParentDefaults = defaultsAt(parent); - parentDefaults = new DefaultSet(); - for (Default d : origParentDefaults) { - if (d.applyToSubpackages) { - parentDefaults.add(d); - } - } - } else { - Element parent = elt.getEnclosingElement(); - parentDefaults = defaultsAt(parent); - } + private final boolean shouldDefaultTypeVarLocals; - if (qualifiers == null || qualifiers.isEmpty()) { - qualifiers = parentDefaults; - } else { - // TODO(cpovirk): What should happen with conflicts? - qualifiers.addAll(parentDefaults); - } + /** Location to which to apply the default. (Should only be set by the applyDefault method.) */ + protected TypeUseLocation location; - /* TODO: it would seem more efficient to also cache null/empty as the result. - * However, doing so causes KeyFor tests to fail. - if (qualifiers == null) { - qualifiers = DefaultSet.EMPTY; - } - - elementDefaults.put(elt, qualifiers); - return qualifiers; - */ - if (qualifiers != null && !qualifiers.isEmpty()) { - elementDefaults.put(elt, qualifiers); - return qualifiers; - } else { - return DefaultSet.EMPTY; - } - } + /** The default element applier implementation. */ + protected final DefaultApplierElementImpl impl; /** - * Returns the defaults that apply directly to the given Element, without considering enclosing - * Elements. + * Create an instance. * - * @param elt the element - * @return the defaults + * @param atypeFactory the type factory + * @param scope the scope for the defaults + * @param type the type to default + * @param fromTree whether the element came from a tree */ - private DefaultSet defaultsAtDirect(Element elt) { - DefaultSet qualifiers = null; - - // Handle DefaultQualifier - AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); - - if (dqAnno != null) { - Set p = fromDefaultQualifier(dqAnno); - - if (p != null) { - qualifiers = new DefaultSet(); - qualifiers.addAll(p); - } - } - - // Handle DefaultQualifier.List - AnnotationMirror dqListAnno = - atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); - if (dqListAnno != null) { - if (qualifiers == null) { - qualifiers = new DefaultSet(); - } - List values = - AnnotationUtils.getElementValueArray( - dqListAnno, defaultQualifierListValueElement, AnnotationMirror.class); - for (AnnotationMirror dqlAnno : values) { - Set p = fromDefaultQualifier(dqlAnno); - if (p != null) { - // TODO(cpovirk): What should happen with conflicts? - qualifiers.addAll(p); - } - } - } - return qualifiers; + public DefaultApplierElement( + AnnotatedTypeFactory atypeFactory, + Element scope, + AnnotatedTypeMirror type, + boolean fromTree) { + this.atypeFactory = atypeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + this.scope = scope; + this.type = type; + this.fromTree = fromTree; + this.shouldDefaultTypeVarLocals = + (atypeFactory instanceof GenericAnnotatedTypeFactory) + && ((GenericAnnotatedTypeFactory) atypeFactory) + .getShouldDefaultTypeVarLocals(); + this.impl = new DefaultApplierElementImpl(this); } /** - * Given an element, returns whether the conservative default should be applied for it. Handles - * elements from bytecode or source code. + * Apply default to the type. * - * @param annotationScope the element that the conservative default might apply to - * @return whether the conservative default applies to the given element + * @param def default to apply */ - public boolean applyConservativeDefaults(Element annotationScope) { - if (annotationScope == null) { - return false; - } - - if (uncheckedCodeDefaults.isEmpty()) { - return false; - } - - // TODO: I would expect this: - // atypeFactory.isFromByteCode(annotationScope)) { - // to work instead of the - // isElementFromByteCode/declarationFromElement/isFromStubFile calls, - // but it doesn't work correctly and tests fail. - - boolean isFromStubFile = atypeFactory.isFromStubFile(annotationScope); - boolean isBytecode = - ElementUtils.isElementFromByteCode(annotationScope) - && atypeFactory.declarationFromElement(annotationScope) == null - && !isFromStubFile; - if (isBytecode) { - return useConservativeDefaultsBytecode - && !isElementAnnotatedForThisChecker(annotationScope); - } else if (isFromStubFile) { - // TODO: Types in stub files not annotated for a particular checker should be - // treated as unchecked bytecode. For now, all types in stub files are treated as - // checked code. Eventually, @AnnotatedFor("checker") will be programmatically added - // to methods in stub files supplied via the @StubFiles annotation. Stub files will - // be treated like unchecked code except for methods in the scope of an @AnnotatedFor. - return false; - } else if (useConservativeDefaultsSource) { - return !isElementAnnotatedForThisChecker(annotationScope); - } - return false; + public void applyDefault(Default def) { + this.location = def.location; + impl.visit(type, def.anno); } /** - * Applies default annotations to a type. Conservative defaults are applied first as - * appropriate, followed by source code defaults. - * - *

For a discussion on the rules for application of source code and conservative defaults, - * please see the linked manual sections. + * Returns true if the given qualifier should be applied to the given type. Currently we do not + * apply defaults to void types, packages, wildcards, and type variables. * - * @param annotationScope the element representing the nearest enclosing default annotation - * scope for the type - * @param type the type to which defaults will be applied - * @param fromTree whether the element came from a tree - * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults - * and inference) - * @checker_framework.manual #annotating-libraries Annotating libraries + * @param type type to which qual would be applied + * @return true if this application should proceed */ - private void applyDefaultsElement( - Element annotationScope, AnnotatedTypeMirror type, boolean fromTree) { - DefaultApplierElement applier = - createDefaultApplierElement(atypeFactory, annotationScope, type, fromTree); - - DefaultSet defaults = defaultsAt(annotationScope); - - // If there is a default for type variable uses, do not also apply checked/unchecked code - // defaults to type variables. Otherwise, the default in scope could decide not to annotate - // the type variable use, whereas the checked/unchecked code default could add an - // annotation. - // TODO: the checked/unchecked defaults should be added to `defaults` and then only one - // iteration through the defaults should be necessary. - boolean typeVarUseDef = false; - - for (Default def : defaults) { - applier.applyDefault(def); - typeVarUseDef |= (def.location == TypeUseLocation.TYPE_VARIABLE_USE); - } - - if (applyConservativeDefaults(annotationScope)) { - for (Default def : uncheckedCodeDefaults) { - if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { - applier.applyDefault(def); - } - } - } - - for (Default def : checkedCodeDefaults) { - if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { - applier.applyDefault(def); - } - } + protected boolean shouldBeAnnotated(AnnotatedTypeMirror type) { + return type != null + // TODO: executables themselves should not be annotated + // For some reason h1h2checker-tests fails with this. + // || type.getKind() == TypeKind.EXECUTABLE + && type.getKind() != TypeKind.NONE + && type.getKind() != TypeKind.WILDCARD + && type.getKind() != TypeKind.TYPEVAR + && !(type instanceof AnnotatedNoType); } /** - * Create the default applier element. + * Add the qualifier to the type if it does not already have an annotation in the same hierarchy + * as qual. * - * @param atypeFactory the annotated type factory - * @param annotationScope the scope of the default - * @param type the type to which to apply the default - * @param fromTree whether the element came from a tree - * @return the default applier element + * @param type type to add qual + * @param qual annotation to add */ - protected DefaultApplierElement createDefaultApplierElement( - AnnotatedTypeFactory atypeFactory, - Element annotationScope, - AnnotatedTypeMirror type, - boolean fromTree) { - return new DefaultApplierElement(atypeFactory, annotationScope, type, fromTree); + protected void addAnnotation(AnnotatedTypeMirror type, AnnotationMirror qual) { + // Add the default annotation, but only if no other annotation is present. + if (type.getKind() != TypeKind.EXECUTABLE) { + type.addMissingAnnotation(qual); + } } + } - /** A default applier element. */ - protected class DefaultApplierElement { - - /** The annotated type factory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; - - /** The scope of the default. */ - protected final Element scope; - - /** The type to which to apply the default. */ - protected final AnnotatedTypeMirror type; - - /** Whether the element came from a tree. */ - protected final boolean fromTree; - - /** - * True if type variable uses as top-level type of local variables should be defaulted. - * - * @see GenericAnnotatedTypeFactory#getShouldDefaultTypeVarLocals() - */ - private final boolean shouldDefaultTypeVarLocals; - - /** - * Location to which to apply the default. (Should only be set by the applyDefault method.) - */ - protected TypeUseLocation location; - - /** The default element applier implementation. */ - protected final DefaultApplierElementImpl impl; - - /** - * Create an instance. - * - * @param atypeFactory the type factory - * @param scope the scope for the defaults - * @param type the type to default - * @param fromTree whether the element came from a tree - */ - public DefaultApplierElement( - AnnotatedTypeFactory atypeFactory, - Element scope, - AnnotatedTypeMirror type, - boolean fromTree) { - this.atypeFactory = atypeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - this.scope = scope; - this.type = type; - this.fromTree = fromTree; - this.shouldDefaultTypeVarLocals = - (atypeFactory instanceof GenericAnnotatedTypeFactory) - && ((GenericAnnotatedTypeFactory) atypeFactory) - .getShouldDefaultTypeVarLocals(); - this.impl = new DefaultApplierElementImpl(this); - } - - /** - * Apply default to the type. - * - * @param def default to apply - */ - public void applyDefault(Default def) { - this.location = def.location; - impl.visit(type, def.anno); - } - - /** - * Returns true if the given qualifier should be applied to the given type. Currently we do - * not apply defaults to void types, packages, wildcards, and type variables. - * - * @param type type to which qual would be applied - * @return true if this application should proceed - */ - protected boolean shouldBeAnnotated(AnnotatedTypeMirror type) { - return type != null - // TODO: executables themselves should not be annotated - // For some reason h1h2checker-tests fails with this. - // || type.getKind() == TypeKind.EXECUTABLE - && type.getKind() != TypeKind.NONE - && type.getKind() != TypeKind.WILDCARD - && type.getKind() != TypeKind.TYPEVAR - && !(type instanceof AnnotatedNoType); - } + // Only reason this cannot be `static` is call to `getBoundType`. + protected class DefaultApplierElementImpl extends AnnotatedTypeScanner { + private final DefaultApplierElement outer; - /** - * Add the qualifier to the type if it does not already have an annotation in the same - * hierarchy as qual. - * - * @param type type to add qual - * @param qual annotation to add - */ - protected void addAnnotation(AnnotatedTypeMirror type, AnnotationMirror qual) { - // Add the default annotation, but only if no other annotation is present. - if (type.getKind() != TypeKind.EXECUTABLE) { - type.addMissingAnnotation(qual); - } - } + protected DefaultApplierElementImpl(DefaultApplierElement outer) { + this.outer = outer; } - // Only reason this cannot be `static` is call to `getBoundType`. - protected class DefaultApplierElementImpl extends AnnotatedTypeScanner { - private final DefaultApplierElement outer; - - protected DefaultApplierElementImpl(DefaultApplierElement outer) { - this.outer = outer; - } - - @Override - public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { - if (!outer.shouldBeAnnotated(t)) { - // Type variables and wildcards are separately handled in the corresponding visitors - // below. - return super.scan(t, qual); - } - - // Some defaults only apply to the top level type. - boolean isTopLevelType = t == outer.type; - switch (outer.location) { - case FIELD: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.FIELD - && isTopLevelType) { - outer.addAnnotation(t, qual); - } - break; - case LOCAL_VARIABLE: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.LOCAL_VARIABLE - && isTopLevelType) { - // TODO: how do we determine that we are in a cast or instanceof type? - outer.addAnnotation(t, qual); - } - break; - case RESOURCE_VARIABLE: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.RESOURCE_VARIABLE - && isTopLevelType) { - outer.addAnnotation(t, qual); - } - break; - case EXCEPTION_PARAMETER: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.EXCEPTION_PARAMETER - && isTopLevelType) { - outer.addAnnotation(t, qual); - if (t.getKind() == TypeKind.UNION) { - AnnotatedUnionType aut = (AnnotatedUnionType) t; - // Also apply the default to the alternative types - for (AnnotatedDeclaredType anno : aut.getAlternatives()) { - outer.addAnnotation(anno, qual); - } - } - } - break; - case PARAMETER: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.PARAMETER - && isTopLevelType) { - outer.addAnnotation(t, qual); - } else if (outer.scope != null - && (outer.scope.getKind() == ElementKind.METHOD - || outer.scope.getKind() == ElementKind.CONSTRUCTOR) - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - for (AnnotatedTypeMirror atm : - ((AnnotatedExecutableType) t).getParameterTypes()) { - if (outer.shouldBeAnnotated(atm)) { - outer.addAnnotation(atm, qual); - } - } - } - break; - case RECEIVER: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.PARAMETER - && isTopLevelType - && outer.scope.getSimpleName().contentEquals("this")) { - // TODO: comparison against "this" is ugly, won't work - // for all possible names for receiver parameter. - // Comparison to Names._this might be a bit faster. - outer.addAnnotation(t, qual); - } else if (outer.scope != null - && (outer.scope.getKind() == ElementKind.METHOD) - // TODO: Constructors can also have receivers. - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - AnnotatedDeclaredType receiver = - ((AnnotatedExecutableType) t).getReceiverType(); - if (outer.shouldBeAnnotated(receiver)) { - outer.addAnnotation(receiver, qual); - } - } - break; - case RETURN: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.METHOD - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - AnnotatedTypeMirror returnType = - ((AnnotatedExecutableType) t).getReturnType(); - if (outer.shouldBeAnnotated(returnType)) { - outer.addAnnotation(returnType, qual); - } - } - break; - case CONSTRUCTOR_RESULT: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.CONSTRUCTOR - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - // This is the return type of a constructor declaration (not a - // constructor invocation). - AnnotatedTypeMirror returnType = - ((AnnotatedExecutableType) t).getReturnType(); - if (outer.shouldBeAnnotated(returnType)) { - outer.addAnnotation(returnType, qual); - } - } - break; - case IMPLICIT_LOWER_BOUND: - if (isLowerBound - && (boundType == BoundType.TYPEVAR_UNBOUNDED - || boundType == BoundType.TYPEVAR_UPPER - || boundType == BoundType.WILDCARD_UNBOUNDED - || boundType == BoundType.WILDCARD_UPPER)) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_LOWER_BOUND: - if (isLowerBound && boundType == BoundType.WILDCARD_LOWER) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case LOWER_BOUND: - if (isLowerBound) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_UPPER_BOUND: - if (isUpperBound - && (boundType == BoundType.TYPEVAR_UNBOUNDED - || boundType == BoundType.WILDCARD_UNBOUNDED - || boundType == BoundType.WILDCARD_LOWER)) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_TYPE_PARAMETER_UPPER_BOUND: - if (isUpperBound && boundType == BoundType.TYPEVAR_UNBOUNDED) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER: - if (isUpperBound && boundType == BoundType.WILDCARD_UNBOUNDED) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_WILDCARD_UPPER_BOUND_SUPER: - if (isUpperBound && boundType == BoundType.WILDCARD_LOWER) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_WILDCARD_UPPER_BOUND: - if (isUpperBound - && (boundType == BoundType.WILDCARD_UNBOUNDED - || boundType == BoundType.WILDCARD_LOWER)) { - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_UPPER_BOUND: - if (isUpperBound - && (boundType == BoundType.TYPEVAR_UPPER - || boundType == BoundType.WILDCARD_UPPER)) { - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_TYPE_PARAMETER_UPPER_BOUND: - if (isUpperBound && boundType == BoundType.TYPEVAR_UPPER) { - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_WILDCARD_UPPER_BOUND: - if (isUpperBound && boundType == BoundType.WILDCARD_UPPER) { - outer.addAnnotation(t, qual); - } - break; - case UPPER_BOUND: - if (isUpperBound) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case OTHERWISE: - case ALL: - // TODO: forbid ALL if anything else was given. - outer.addAnnotation(t, qual); - break; - case TYPE_VARIABLE_USE: - // This location is handled in visitTypeVariable below. Do nothing here. - break; - default: - throw new BugInCF( - "QualifierDefaults.DefaultApplierElement: unhandled location: " - + outer.location); - } - - return super.scan(t, qual); - } - - @Override - public void reset() { - super.reset(); - isLowerBound = false; - isUpperBound = false; - boundType = BoundType.TYPEVAR_UNBOUNDED; - } - - /** Are we currently defaulting the lower bound of a type variable or wildcard? */ - private boolean isLowerBound = false; - - /** Are we currently defaulting the upper bound of a type variable or wildcard? */ - private boolean isUpperBound = false; - - /** The bound type of the current wildcard or type variable being defaulted. */ - private BoundType boundType = BoundType.TYPEVAR_UNBOUNDED; - - @Override - public Void visitTypeVariable( - @FindDistinct AnnotatedTypeVariable type, AnnotationMirror qual) { - if (visitedNodes.containsKey(type)) { - return null; - } - if (outer.qualHierarchy.isParametricQualifier(qual)) { - // Parametric qualifiers are only applicable to type variables and have no effect on - // their type. Therefore, do nothing. - return null; + @Override + public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { + if (!outer.shouldBeAnnotated(t)) { + // Type variables and wildcards are separately handled in the corresponding visitors + // below. + return super.scan(t, qual); + } + + // Some defaults only apply to the top level type. + boolean isTopLevelType = t == outer.type; + switch (outer.location) { + case FIELD: + if (outer.scope != null && outer.scope.getKind() == ElementKind.FIELD && isTopLevelType) { + outer.addAnnotation(t, qual); + } + break; + case LOCAL_VARIABLE: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.LOCAL_VARIABLE + && isTopLevelType) { + // TODO: how do we determine that we are in a cast or instanceof type? + outer.addAnnotation(t, qual); + } + break; + case RESOURCE_VARIABLE: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.RESOURCE_VARIABLE + && isTopLevelType) { + outer.addAnnotation(t, qual); + } + break; + case EXCEPTION_PARAMETER: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.EXCEPTION_PARAMETER + && isTopLevelType) { + outer.addAnnotation(t, qual); + if (t.getKind() == TypeKind.UNION) { + AnnotatedUnionType aut = (AnnotatedUnionType) t; + // Also apply the default to the alternative types + for (AnnotatedDeclaredType anno : aut.getAlternatives()) { + outer.addAnnotation(anno, qual); + } } - if (type.isDeclaration()) { - // For a type variable declaration, apply the defaults to the bounds. Do not apply - // `TYPE_VARIALBE_USE` defaults. - visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); - return null; + } + break; + case PARAMETER: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.PARAMETER + && isTopLevelType) { + outer.addAnnotation(t, qual); + } else if (outer.scope != null + && (outer.scope.getKind() == ElementKind.METHOD + || outer.scope.getKind() == ElementKind.CONSTRUCTOR) + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + for (AnnotatedTypeMirror atm : ((AnnotatedExecutableType) t).getParameterTypes()) { + if (outer.shouldBeAnnotated(atm)) { + outer.addAnnotation(atm, qual); + } } - - boolean isTopLevelType = type == outer.type; - boolean isLocalVariable = - outer.scope != null && ElementUtils.isLocalVariable(outer.scope); - - if (isTopLevelType && isLocalVariable) { - if (outer.shouldDefaultTypeVarLocals - && outer.fromTree - && outer.location == TypeUseLocation.LOCAL_VARIABLE) { - outer.addAnnotation(type, qual); - } else { - // TODO: Should `TYPE_VARIABLE_USE` default apply to top-level local variables, - // if they should not be defaulted according to `shouldDefaultTypeVarLocals`? - visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); - } - } else { - if (outer.location == TypeUseLocation.TYPE_VARIABLE_USE) { - outer.addAnnotation(type, qual); - } else { - visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); - } + } + break; + case RECEIVER: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.PARAMETER + && isTopLevelType + && outer.scope.getSimpleName().contentEquals("this")) { + // TODO: comparison against "this" is ugly, won't work + // for all possible names for receiver parameter. + // Comparison to Names._this might be a bit faster. + outer.addAnnotation(t, qual); + } else if (outer.scope != null + && (outer.scope.getKind() == ElementKind.METHOD) + // TODO: Constructors can also have receivers. + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + AnnotatedDeclaredType receiver = ((AnnotatedExecutableType) t).getReceiverType(); + if (outer.shouldBeAnnotated(receiver)) { + outer.addAnnotation(receiver, qual); } - return null; - } - - @Override - public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror qual) { - if (visitedNodes.containsKey(type)) { - return null; + } + break; + case RETURN: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.METHOD + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType(); + if (outer.shouldBeAnnotated(returnType)) { + outer.addAnnotation(returnType, qual); } - visitBounds(type, type.getExtendsBound(), type.getSuperBound(), qual); - return null; - } - - /** - * Visit the bounds of a type variable or a wildcard and potentially apply qual to those - * bounds. This method will also update the boundType, isLowerBound, and isUpperbound - * fields. - */ - protected void visitBounds( - AnnotatedTypeMirror boundedType, - AnnotatedTypeMirror upperBound, - AnnotatedTypeMirror lowerBound, - AnnotationMirror qual) { - boolean prevIsUpperBound = isUpperBound; - boolean prevIsLowerBound = isLowerBound; - BoundType prevBoundType = boundType; - - boundType = getBoundType(boundedType); - - try { - isLowerBound = true; - isUpperBound = false; - scanAndReduce(lowerBound, qual, null); - - visitedNodes.put(boundedType, null); - - isLowerBound = false; - isUpperBound = true; - scanAndReduce(upperBound, qual, null); - - visitedNodes.put(boundedType, null); - } finally { - isUpperBound = prevIsUpperBound; - isLowerBound = prevIsLowerBound; - boundType = prevBoundType; + } + break; + case CONSTRUCTOR_RESULT: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.CONSTRUCTOR + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + // This is the return type of a constructor declaration (not a + // constructor invocation). + AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType(); + if (outer.shouldBeAnnotated(returnType)) { + outer.addAnnotation(returnType, qual); } - } + } + break; + case IMPLICIT_LOWER_BOUND: + if (isLowerBound + && (boundType == BoundType.TYPEVAR_UNBOUNDED + || boundType == BoundType.TYPEVAR_UPPER + || boundType == BoundType.WILDCARD_UNBOUNDED + || boundType == BoundType.WILDCARD_UPPER)) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_LOWER_BOUND: + if (isLowerBound && boundType == BoundType.WILDCARD_LOWER) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case LOWER_BOUND: + if (isLowerBound) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_UPPER_BOUND: + if (isUpperBound + && (boundType == BoundType.TYPEVAR_UNBOUNDED + || boundType == BoundType.WILDCARD_UNBOUNDED + || boundType == BoundType.WILDCARD_LOWER)) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_TYPE_PARAMETER_UPPER_BOUND: + if (isUpperBound && boundType == BoundType.TYPEVAR_UNBOUNDED) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER: + if (isUpperBound && boundType == BoundType.WILDCARD_UNBOUNDED) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_WILDCARD_UPPER_BOUND_SUPER: + if (isUpperBound && boundType == BoundType.WILDCARD_LOWER) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_WILDCARD_UPPER_BOUND: + if (isUpperBound + && (boundType == BoundType.WILDCARD_UNBOUNDED + || boundType == BoundType.WILDCARD_LOWER)) { + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_UPPER_BOUND: + if (isUpperBound + && (boundType == BoundType.TYPEVAR_UPPER || boundType == BoundType.WILDCARD_UPPER)) { + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_TYPE_PARAMETER_UPPER_BOUND: + if (isUpperBound && boundType == BoundType.TYPEVAR_UPPER) { + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_WILDCARD_UPPER_BOUND: + if (isUpperBound && boundType == BoundType.WILDCARD_UPPER) { + outer.addAnnotation(t, qual); + } + break; + case UPPER_BOUND: + if (isUpperBound) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case OTHERWISE: + case ALL: + // TODO: forbid ALL if anything else was given. + outer.addAnnotation(t, qual); + break; + case TYPE_VARIABLE_USE: + // This location is handled in visitTypeVariable below. Do nothing here. + break; + default: + throw new BugInCF( + "QualifierDefaults.DefaultApplierElement: unhandled location: " + outer.location); + } + + return super.scan(t, qual); } - /** - * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an - * explicit lower bound (LOWER), or no explicit bounds (UNBOUNDED). - */ - protected enum BoundType { + @Override + public void reset() { + super.reset(); + isLowerBound = false; + isUpperBound = false; + boundType = BoundType.TYPEVAR_UNBOUNDED; + } - /** Indicates an upper-bounded type variable. */ - TYPEVAR_UPPER, + /** Are we currently defaulting the lower bound of a type variable or wildcard? */ + private boolean isLowerBound = false; - /** - * Neither bound is specified, BOTH are implicit. (If a type variable is declared in - * bytecode and the type of the upper bound is Object, then the checker assumes that the - * bound was not explicitly written in source code.) - */ - TYPEVAR_UNBOUNDED, + /** Are we currently defaulting the upper bound of a type variable or wildcard? */ + private boolean isUpperBound = false; - /** Indicates an upper-bounded wildcard. */ - WILDCARD_UPPER, + /** The bound type of the current wildcard or type variable being defaulted. */ + private BoundType boundType = BoundType.TYPEVAR_UNBOUNDED; - /** Indicates a lower-bounded wildcard. */ - WILDCARD_LOWER, + @Override + public Void visitTypeVariable(@FindDistinct AnnotatedTypeVariable type, AnnotationMirror qual) { + if (visitedNodes.containsKey(type)) { + return null; + } + if (outer.qualHierarchy.isParametricQualifier(qual)) { + // Parametric qualifiers are only applicable to type variables and have no effect on + // their type. Therefore, do nothing. + return null; + } + if (type.isDeclaration()) { + // For a type variable declaration, apply the defaults to the bounds. Do not apply + // `TYPE_VARIALBE_USE` defaults. + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + return null; + } - /** Neither bound is specified, BOTH are implicit. */ - WILDCARD_UNBOUNDED; - } + boolean isTopLevelType = type == outer.type; + boolean isLocalVariable = outer.scope != null && ElementUtils.isLocalVariable(outer.scope); - /** - * Returns the boundType for type. - * - * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or - * AnnotatedTypeVariable. - * @return the boundType for type - */ - private BoundType getBoundType(AnnotatedTypeMirror type) { - if (type instanceof AnnotatedTypeVariable) { - return getTypeVarBoundType((AnnotatedTypeVariable) type); + if (isTopLevelType && isLocalVariable) { + if (outer.shouldDefaultTypeVarLocals + && outer.fromTree + && outer.location == TypeUseLocation.LOCAL_VARIABLE) { + outer.addAnnotation(type, qual); + } else { + // TODO: Should `TYPE_VARIABLE_USE` default apply to top-level local variables, + // if they should not be defaulted according to `shouldDefaultTypeVarLocals`? + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); } - - if (type instanceof AnnotatedWildcardType) { - return getWildcardBoundType((AnnotatedWildcardType) type); + } else { + if (outer.location == TypeUseLocation.TYPE_VARIABLE_USE) { + outer.addAnnotation(type, qual); + } else { + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); } + } + return null; + } - throw new BugInCF("Unexpected type kind: type=" + type); + @Override + public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror qual) { + if (visitedNodes.containsKey(type)) { + return null; + } + visitBounds(type, type.getExtendsBound(), type.getSuperBound(), qual); + return null; } /** - * Returns the bound type of the input typeVar. - * - * @param typeVar the type variable - * @return the bound type of the input typeVar + * Visit the bounds of a type variable or a wildcard and potentially apply qual to those bounds. + * This method will also update the boundType, isLowerBound, and isUpperbound fields. */ - private BoundType getTypeVarBoundType(AnnotatedTypeVariable typeVar) { - return getTypeVarBoundType((TypeParameterElement) typeVar.getUnderlyingType().asElement()); + protected void visitBounds( + AnnotatedTypeMirror boundedType, + AnnotatedTypeMirror upperBound, + AnnotatedTypeMirror lowerBound, + AnnotationMirror qual) { + boolean prevIsUpperBound = isUpperBound; + boolean prevIsLowerBound = isLowerBound; + BoundType prevBoundType = boundType; + + boundType = getBoundType(boundedType); + + try { + isLowerBound = true; + isUpperBound = false; + scanAndReduce(lowerBound, qual, null); + + visitedNodes.put(boundedType, null); + + isLowerBound = false; + isUpperBound = true; + scanAndReduce(upperBound, qual, null); + + visitedNodes.put(boundedType, null); + } finally { + isUpperBound = prevIsUpperBound; + isLowerBound = prevIsLowerBound; + boundType = prevBoundType; + } } + } + + /** + * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an + * explicit lower bound (LOWER), or no explicit bounds (UNBOUNDED). + */ + protected enum BoundType { + + /** Indicates an upper-bounded type variable. */ + TYPEVAR_UPPER, /** - * Returns the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of - * typeParamElem. - * - * @param typeParamElem the type parameter element - * @return the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of - * typeParamElem + * Neither bound is specified, BOTH are implicit. (If a type variable is declared in bytecode + * and the type of the upper bound is Object, then the checker assumes that the bound was not + * explicitly written in source code.) */ - // Results are cached in {@link elementToBoundType}. - private BoundType getTypeVarBoundType(TypeParameterElement typeParamElem) { - BoundType prev = elementToBoundType.get(typeParamElem); - if (prev != null) { - return prev; - } + TYPEVAR_UNBOUNDED, + + /** Indicates an upper-bounded wildcard. */ + WILDCARD_UPPER, + + /** Indicates a lower-bounded wildcard. */ + WILDCARD_LOWER, + + /** Neither bound is specified, BOTH are implicit. */ + WILDCARD_UNBOUNDED; + } + + /** + * Returns the boundType for type. + * + * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or + * AnnotatedTypeVariable. + * @return the boundType for type + */ + private BoundType getBoundType(AnnotatedTypeMirror type) { + if (type instanceof AnnotatedTypeVariable) { + return getTypeVarBoundType((AnnotatedTypeVariable) type); + } - TreePath declaredTypeVarEle = atypeFactory.getTreeUtils().getPath(typeParamElem); - Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf(); - - final BoundType boundType; - if (typeParamDecl == null) { - // This is not only for elements from binaries, but also - // when the compilation unit is no-longer available. - if (typeParamElem.getBounds().size() == 1 - && TypesUtils.isObject(typeParamElem.getBounds().get(0))) { - // If the bound was Object, then it may or may not have been explicitly written. - // Assume that it was not. - boundType = BoundType.TYPEVAR_UNBOUNDED; - } else { - // The bound is not Object, so it must have been explicitly written and thus the - // type variable has an upper bound. - boundType = BoundType.TYPEVAR_UPPER; - } - } else { - if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) { - TypeParameterTree tptree = (TypeParameterTree) typeParamDecl; - - List bnds = tptree.getBounds(); - if (bnds != null && !bnds.isEmpty()) { - boundType = BoundType.TYPEVAR_UPPER; - } else { - boundType = BoundType.TYPEVAR_UNBOUNDED; - } - } else { - throw new BugInCF( - StringsPlume.joinLines( - "Unexpected tree type for typeVar Element:", - "typeParamElem=" + typeParamElem, - typeParamDecl)); - } - } + if (type instanceof AnnotatedWildcardType) { + return getWildcardBoundType((AnnotatedWildcardType) type); + } - elementToBoundType.put(typeParamElem, boundType); - return boundType; + throw new BugInCF("Unexpected type kind: type=" + type); + } + + /** + * Returns the bound type of the input typeVar. + * + * @param typeVar the type variable + * @return the bound type of the input typeVar + */ + private BoundType getTypeVarBoundType(AnnotatedTypeVariable typeVar) { + return getTypeVarBoundType((TypeParameterElement) typeVar.getUnderlyingType().asElement()); + } + + /** + * Returns the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of typeParamElem. + * + * @param typeParamElem the type parameter element + * @return the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of typeParamElem + */ + // Results are cached in {@link elementToBoundType}. + private BoundType getTypeVarBoundType(TypeParameterElement typeParamElem) { + BoundType prev = elementToBoundType.get(typeParamElem); + if (prev != null) { + return prev; } - /** - * Returns the BoundType of wildcardType. - * - * @param wildcardType the annotated wildcard type - * @return the BoundType of annotatedWildcard - */ - private BoundType getWildcardBoundType(AnnotatedWildcardType wildcardType) { - if (AnnotatedTypes.hasNoExplicitBound(wildcardType)) { - return BoundType.WILDCARD_UNBOUNDED; - } else if (AnnotatedTypes.hasExplicitSuperBound(wildcardType)) { - return BoundType.WILDCARD_LOWER; + TreePath declaredTypeVarEle = atypeFactory.getTreeUtils().getPath(typeParamElem); + Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf(); + + final BoundType boundType; + if (typeParamDecl == null) { + // This is not only for elements from binaries, but also + // when the compilation unit is no-longer available. + if (typeParamElem.getBounds().size() == 1 + && TypesUtils.isObject(typeParamElem.getBounds().get(0))) { + // If the bound was Object, then it may or may not have been explicitly written. + // Assume that it was not. + boundType = BoundType.TYPEVAR_UNBOUNDED; + } else { + // The bound is not Object, so it must have been explicitly written and thus the + // type variable has an upper bound. + boundType = BoundType.TYPEVAR_UPPER; + } + } else { + if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) { + TypeParameterTree tptree = (TypeParameterTree) typeParamDecl; + + List bnds = tptree.getBounds(); + if (bnds != null && !bnds.isEmpty()) { + boundType = BoundType.TYPEVAR_UPPER; } else { - return BoundType.WILDCARD_UPPER; + boundType = BoundType.TYPEVAR_UNBOUNDED; } + } else { + throw new BugInCF( + StringsPlume.joinLines( + "Unexpected tree type for typeVar Element:", + "typeParamElem=" + typeParamElem, + typeParamDecl)); + } + } + + elementToBoundType.put(typeParamElem, boundType); + return boundType; + } + + /** + * Returns the BoundType of wildcardType. + * + * @param wildcardType the annotated wildcard type + * @return the BoundType of annotatedWildcard + */ + private BoundType getWildcardBoundType(AnnotatedWildcardType wildcardType) { + if (AnnotatedTypes.hasNoExplicitBound(wildcardType)) { + return BoundType.WILDCARD_UNBOUNDED; + } else if (AnnotatedTypes.hasExplicitSuperBound(wildcardType)) { + return BoundType.WILDCARD_LOWER; + } else { + return BoundType.WILDCARD_UPPER; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java index 3a48107b3df..528f4189b2e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java @@ -1,15 +1,14 @@ package org.checkerframework.framework.util.dependenttypes; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; import org.checkerframework.javacutil.BugInCF; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * Helper class for creating dependent type annotation error strings. * @@ -19,117 +18,116 @@ */ public class DependentTypesError { - /// Static fields - - /** How elements of this class are formatted. */ - @SuppressWarnings("InlineFormatString") // https://github.com/google/error-prone/issues/1650 - private static final String FORMAT_STRING = "[error for expression: %s; error: %s]"; - - /** Regular expression for unparsing string representations of this class (gross). */ - private static final Pattern ERROR_PATTERN = - Pattern.compile("\\[error for expression: (.*); error: ([\\s\\S]*)\\]"); - - /** - * Returns whether or not the given expression string is an error. That is, whether it is a - * string that was generated by this class. - * - * @param expression expression string to test - * @return whether or not the given expressions string is an error - */ - public static boolean isExpressionError(String expression) { - return expression.startsWith("[error"); - } - - /** How to format warnings about use of formal parameter name. */ - public static final @Format({ConversionCategory.INT, ConversionCategory.GENERAL}) String - FORMAL_PARAM_NAME_STRING = "Use \"#%d\" rather than \"%s\""; - - /** Matches warnings about use of formal parameter name. */ - private static final Pattern FORMAL_PARAM_NAME_PATTERN = - Pattern.compile( - "^'([a-zA-Z_$][a-zA-Z0-9_$]*)' because (Use \"#\\d+\" rather than \"\\1\")$"); - - /// Instance fields - - /** The expression that is unparsable or otherwise problematic. */ - public final String expression; - - /** An error message about that expression. */ - public final String error; - - /// Constructors and methods - - /** - * Create a DependentTypesError for the given expression and error message. - * - * @param expression the incorrect Java expression - * @param error an error message about the expression - */ - public DependentTypesError(String expression, String error) { - this.expression = expression; - this.error = error; - } - - /** - * Create a DependentTypesError for the given expression and exception. - * - * @param expression the incorrect Java expression - * @param e wraps an error message about the expression - */ - public DependentTypesError(String expression, JavaExpressionParseException e) { - this.expression = expression; - this.error = e.getDiagMessage().getArgs()[0].toString(); - } - - /** - * Create a DependentTypesError by parsing a printed one. - * - * @param formattedError the toString() representation of a DependentTypesError - */ - public static DependentTypesError unparse(String formattedError) { - Matcher matcher = ERROR_PATTERN.matcher(formattedError); - if (matcher.matches()) { - assert matcher.groupCount() == 2; - return new DependentTypesError(matcher.group(1), matcher.group(2)); - } else { - throw new BugInCF("Cannot unparse: " + formattedError); - } - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - DependentTypesError that = (DependentTypesError) o; - - return expression.equals(that.expression) && error.equals(that.error); + /// Static fields + + /** How elements of this class are formatted. */ + @SuppressWarnings("InlineFormatString") // https://github.com/google/error-prone/issues/1650 + private static final String FORMAT_STRING = "[error for expression: %s; error: %s]"; + + /** Regular expression for unparsing string representations of this class (gross). */ + private static final Pattern ERROR_PATTERN = + Pattern.compile("\\[error for expression: (.*); error: ([\\s\\S]*)\\]"); + + /** + * Returns whether or not the given expression string is an error. That is, whether it is a string + * that was generated by this class. + * + * @param expression expression string to test + * @return whether or not the given expressions string is an error + */ + public static boolean isExpressionError(String expression) { + return expression.startsWith("[error"); + } + + /** How to format warnings about use of formal parameter name. */ + public static final @Format({ConversionCategory.INT, ConversionCategory.GENERAL}) String + FORMAL_PARAM_NAME_STRING = "Use \"#%d\" rather than \"%s\""; + + /** Matches warnings about use of formal parameter name. */ + private static final Pattern FORMAL_PARAM_NAME_PATTERN = + Pattern.compile("^'([a-zA-Z_$][a-zA-Z0-9_$]*)' because (Use \"#\\d+\" rather than \"\\1\")$"); + + /// Instance fields + + /** The expression that is unparsable or otherwise problematic. */ + public final String expression; + + /** An error message about that expression. */ + public final String error; + + /// Constructors and methods + + /** + * Create a DependentTypesError for the given expression and error message. + * + * @param expression the incorrect Java expression + * @param error an error message about the expression + */ + public DependentTypesError(String expression, String error) { + this.expression = expression; + this.error = error; + } + + /** + * Create a DependentTypesError for the given expression and exception. + * + * @param expression the incorrect Java expression + * @param e wraps an error message about the expression + */ + public DependentTypesError(String expression, JavaExpressionParseException e) { + this.expression = expression; + this.error = e.getDiagMessage().getArgs()[0].toString(); + } + + /** + * Create a DependentTypesError by parsing a printed one. + * + * @param formattedError the toString() representation of a DependentTypesError + */ + public static DependentTypesError unparse(String formattedError) { + Matcher matcher = ERROR_PATTERN.matcher(formattedError); + if (matcher.matches()) { + assert matcher.groupCount() == 2; + return new DependentTypesError(matcher.group(1), matcher.group(2)); + } else { + throw new BugInCF("Cannot unparse: " + formattedError); } + } - @Override - public int hashCode() { - return Objects.hash(expression, error); + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; } - - @Override - public String toString() { - return String.format(FORMAT_STRING, expression, error); + if (o == null || getClass() != o.getClass()) { + return false; } - /** - * Like toString, but uses better formatting sometimes. Use this only for the final output, - * because of the design that hides error messages in toString(). - */ - @SuppressWarnings("nullness:return") // regex groups always match text - public String format() { - Matcher m = FORMAL_PARAM_NAME_PATTERN.matcher(error); - if (m.matches()) { - return m.group(2); - } - return toString(); + DependentTypesError that = (DependentTypesError) o; + + return expression.equals(that.expression) && error.equals(that.error); + } + + @Override + public int hashCode() { + return Objects.hash(expression, error); + } + + @Override + public String toString() { + return String.format(FORMAT_STRING, expression, error); + } + + /** + * Like toString, but uses better formatting sometimes. Use this only for the final output, + * because of the design that hides error messages in toString(). + */ + @SuppressWarnings("nullness:return") // regex groups always match text + public String format() { + Matcher m = FORMAL_PARAM_NAME_PATTERN.matcher(error); + if (m.matches()) { + return m.group(2); } + return toString(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java index 4e33ef40d03..cc782a00847 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java @@ -13,7 +13,25 @@ import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.JCTree; - +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.expression.FormalParameter; @@ -45,27 +63,6 @@ import org.checkerframework.javacutil.trees.DetachedVarSymbol; import org.plumelib.util.CollectionsPlume; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - /** * A class that helps checkers use qualifiers that are represented by annotations with Java * expression strings. This class performs the following main functions: @@ -110,1254 +107,1219 @@ */ public class DependentTypesHelper { - /** AnnotatedTypeFactory */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Maps from an annotation name, the fully-qualified name of its class, to its elements that are - * Java expressions. - */ - private final Map> annoToElements; - - /** This scans an annotated type and returns a list of {@link DependentTypesError}. */ - private final ExpressionErrorCollector expressionErrorCollector = - new ExpressionErrorCollector(); - - /** - * A scanner that applies a function to each {@link AnnotationMirror} and replaces it in the - * given {@code AnnotatedTypeMirror}. (This side-effects the {@code AnnotatedTypeMirror}.) - */ - private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); - - /** - * Copies annotations that might have been viewpoint adapted from the visited type (the first - * formal parameter of {@code ViewpointAdaptedCopier#visit}) to the second formal parameter. - */ - private final ViewpointAdaptedCopier viewpointAdaptedCopier = new ViewpointAdaptedCopier(); - - /** The type mirror for java.lang.Object. */ - protected final TypeMirror objectTM; - - /** - * Creates a {@code DependentTypesHelper}. - * - * @param atypeFactory annotated type factory - */ - public DependentTypesHelper(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - - this.annoToElements = new HashMap<>(); - for (Class expressionAnno : - atypeFactory.getSupportedTypeQualifiers()) { - List elementList = - getExpressionElements(expressionAnno, atypeFactory.getProcessingEnv()); - if (!elementList.isEmpty()) { - annoToElements.put(expressionAnno.getCanonicalName(), elementList); - } - } - - this.objectTM = - TypesUtils.typeFromClass( - Object.class, atypeFactory.types, atypeFactory.getElementUtils()); + /** AnnotatedTypeFactory */ + protected final AnnotatedTypeFactory atypeFactory; + + /** + * Maps from an annotation name, the fully-qualified name of its class, to its elements that are + * Java expressions. + */ + private final Map> annoToElements; + + /** This scans an annotated type and returns a list of {@link DependentTypesError}. */ + private final ExpressionErrorCollector expressionErrorCollector = new ExpressionErrorCollector(); + + /** + * A scanner that applies a function to each {@link AnnotationMirror} and replaces it in the given + * {@code AnnotatedTypeMirror}. (This side-effects the {@code AnnotatedTypeMirror}.) + */ + private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); + + /** + * Copies annotations that might have been viewpoint adapted from the visited type (the first + * formal parameter of {@code ViewpointAdaptedCopier#visit}) to the second formal parameter. + */ + private final ViewpointAdaptedCopier viewpointAdaptedCopier = new ViewpointAdaptedCopier(); + + /** The type mirror for java.lang.Object. */ + protected final TypeMirror objectTM; + + /** + * Creates a {@code DependentTypesHelper}. + * + * @param atypeFactory annotated type factory + */ + public DependentTypesHelper(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + + this.annoToElements = new HashMap<>(); + for (Class expressionAnno : atypeFactory.getSupportedTypeQualifiers()) { + List elementList = + getExpressionElements(expressionAnno, atypeFactory.getProcessingEnv()); + if (!elementList.isEmpty()) { + annoToElements.put(expressionAnno.getCanonicalName(), elementList); + } } - /** - * Returns true if any qualifier in the type system is a dependent type annotation. - * - * @return true if any qualifier in the type system is a dependent type annotation - */ - public boolean hasDependentAnnotations() { - return !annoToElements.isEmpty(); + this.objectTM = + TypesUtils.typeFromClass(Object.class, atypeFactory.types, atypeFactory.getElementUtils()); + } + + /** + * Returns true if any qualifier in the type system is a dependent type annotation. + * + * @return true if any qualifier in the type system is a dependent type annotation + */ + public boolean hasDependentAnnotations() { + return !annoToElements.isEmpty(); + } + + /** + * Returns a list of the elements in the annotation class that should be interpreted as Java + * expressions, namely those annotated with {@code @}{@link JavaExpression}. + * + * @param clazz annotation class + * @param env processing environment for getting the ExecutableElement + * @return a list of the elements in the annotation class that should be interpreted as Java + * expressions + */ + private static List getExpressionElements( + Class clazz, ProcessingEnvironment env) { + Method[] methods = clazz.getMethods(); + if (methods == null) { + return Collections.emptyList(); } - - /** - * Returns a list of the elements in the annotation class that should be interpreted as Java - * expressions, namely those annotated with {@code @}{@link JavaExpression}. - * - * @param clazz annotation class - * @param env processing environment for getting the ExecutableElement - * @return a list of the elements in the annotation class that should be interpreted as Java - * expressions - */ - private static List getExpressionElements( - Class clazz, ProcessingEnvironment env) { - Method[] methods = clazz.getMethods(); - if (methods == null) { - return Collections.emptyList(); - } - List elements = new ArrayList<>(); - for (Method method : methods) { - org.checkerframework.framework.qual.JavaExpression javaExpressionAnno = - method.getAnnotation(org.checkerframework.framework.qual.JavaExpression.class); - if (javaExpressionAnno != null) { - elements.add( - TreeUtils.getMethod( - clazz, method.getName(), method.getParameterCount(), env)); - } - } - return elements; + List elements = new ArrayList<>(); + for (Method method : methods) { + org.checkerframework.framework.qual.JavaExpression javaExpressionAnno = + method.getAnnotation(org.checkerframework.framework.qual.JavaExpression.class); + if (javaExpressionAnno != null) { + elements.add(TreeUtils.getMethod(clazz, method.getName(), method.getParameterCount(), env)); + } } - - /** - * Returns the elements of the annotation that are Java expressions. - * - * @param am an annotation - * @return the elements of the annotation that are Java expressions - */ - private List getListOfExpressionElements(AnnotationMirror am) { - return annoToElements.getOrDefault( - AnnotationUtils.annotationName(am), Collections.emptyList()); + return elements; + } + + /** + * Returns the elements of the annotation that are Java expressions. + * + * @param am an annotation + * @return the elements of the annotation that are Java expressions + */ + private List getListOfExpressionElements(AnnotationMirror am) { + return annoToElements.getOrDefault(AnnotationUtils.annotationName(am), Collections.emptyList()); + } + + /** + * Creates a TreeAnnotator that viewpoint-adapts dependent type annotations. + * + * @return a new TreeAnnotator that viewpoint-adapts dependent type annotations + */ + public TreeAnnotator createDependentTypesTreeAnnotator() { + assert hasDependentAnnotations(); + return new DependentTypesTreeAnnotator(atypeFactory, this); + } + + /// + /// Methods that convert annotations + /// + + /** If true, log information about where lambdas are created. */ + // This variable is only set here; edit the source code to modify it. + private static final boolean debugStringToJavaExpression = false; + + /** + * Viewpoint-adapts the dependent type annotations on the bounds of the type parameters of the + * declaration of {@code typeUse} to {@code typeUse}. + * + * @param bounds annotated types of the bounds of the type parameters; its elements are + * side-effected by this method (but the list itself is not side-effected) + * @param typeUse a use of a type with type parameter bounds {@code bounds} + */ + public void atParameterizedTypeUse( + List bounds, TypeElement typeUse) { + if (!hasDependentAnnotations()) { + return; } - /** - * Creates a TreeAnnotator that viewpoint-adapts dependent type annotations. - * - * @return a new TreeAnnotator that viewpoint-adapts dependent type annotations - */ - public TreeAnnotator createDependentTypesTreeAnnotator() { - assert hasDependentAnnotations(); - return new DependentTypesTreeAnnotator(atypeFactory, this); + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atTypeDecl(stringExpr, typeUse, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atParameterizedTypeUse(%s, %s) created %s%n", bounds, typeUse, stringToJavaExpr); } - - /// - /// Methods that convert annotations - /// - - /** If true, log information about where lambdas are created. */ - // This variable is only set here; edit the source code to modify it. - private static final boolean debugStringToJavaExpression = false; - - /** - * Viewpoint-adapts the dependent type annotations on the bounds of the type parameters of the - * declaration of {@code typeUse} to {@code typeUse}. - * - * @param bounds annotated types of the bounds of the type parameters; its elements are - * side-effected by this method (but the list itself is not side-effected) - * @param typeUse a use of a type with type parameter bounds {@code bounds} - */ - public void atParameterizedTypeUse( - List bounds, TypeElement typeUse) { - if (!hasDependentAnnotations()) { - return; - } - - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atTypeDecl( - stringExpr, typeUse, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atParameterizedTypeUse(%s, %s) created %s%n", - bounds, typeUse, stringToJavaExpr); - } - for (AnnotatedTypeParameterBounds bound : bounds) { - convertAnnotatedTypeMirror(stringToJavaExpr, bound.getUpperBound()); - convertAnnotatedTypeMirror(stringToJavaExpr, bound.getLowerBound()); - } + for (AnnotatedTypeParameterBounds bound : bounds) { + convertAnnotatedTypeMirror(stringToJavaExpr, bound.getUpperBound()); + convertAnnotatedTypeMirror(stringToJavaExpr, bound.getLowerBound()); } - - /** - * Viewpoint-adapts the dependent type annotations in the methodType to the - * methodInvocationTree. - * - *

{@code methodType} has been viewpoint-adapted to the call site, except for any dependent - * type annotations. This method viewpoint-adapts the dependent type annotations. - * - * @param methodType type of the method invocation; is side-effected by this method - * @param methodInvocationTree use of the method - */ - public void atMethodInvocation( - AnnotatedExecutableType methodType, MethodInvocationTree methodInvocationTree) { - if (!hasDependentAnnotations()) { - return; - } - atInvocation(methodType, methodInvocationTree); + } + + /** + * Viewpoint-adapts the dependent type annotations in the methodType to the methodInvocationTree. + * + *

{@code methodType} has been viewpoint-adapted to the call site, except for any dependent + * type annotations. This method viewpoint-adapts the dependent type annotations. + * + * @param methodType type of the method invocation; is side-effected by this method + * @param methodInvocationTree use of the method + */ + public void atMethodInvocation( + AnnotatedExecutableType methodType, MethodInvocationTree methodInvocationTree) { + if (!hasDependentAnnotations()) { + return; } - - /** - * Viewpoint-adapts the dependent type annotations in the constructorType to the newClassTree. - * - *

{@code constructorType} has been viewpoint-adapted to the call site, except for any - * dependent type annotations. This method viewpoint-adapts the dependent type annotations. - * - * @param constructorType type of the constructor invocation; is side-effected by this method - * @param newClassTree invocation of the constructor - */ - public void atConstructorInvocation( - AnnotatedExecutableType constructorType, NewClassTree newClassTree) { - if (!hasDependentAnnotations()) { - return; - } - atInvocation(constructorType, newClassTree); + atInvocation(methodType, methodInvocationTree); + } + + /** + * Viewpoint-adapts the dependent type annotations in the constructorType to the newClassTree. + * + *

{@code constructorType} has been viewpoint-adapted to the call site, except for any + * dependent type annotations. This method viewpoint-adapts the dependent type annotations. + * + * @param constructorType type of the constructor invocation; is side-effected by this method + * @param newClassTree invocation of the constructor + */ + public void atConstructorInvocation( + AnnotatedExecutableType constructorType, NewClassTree newClassTree) { + if (!hasDependentAnnotations()) { + return; + } + atInvocation(constructorType, newClassTree); + } + + /** + * Viewpoint-adapts dependent type annotations in a method or constructor type. + * + *

{@code methodType} has been viewpoint-adapted to the call site, except for any dependent + * type annotations. (For example, type variables have been substituted and polymorphic qualifiers + * have been resolved.) This method viewpoint-adapts the dependent type annotations. + * + * @param methodType type of the method or constructor invocation; is side-effected by this method + * @param tree invocation of the method or constructor + */ + private void atInvocation(AnnotatedExecutableType methodType, ExpressionTree tree) { + assert hasDependentAnnotations(); + Element methodElt = TreeUtils.elementFromUse(tree); + // Because methodType is the type post type variable substitution, it has annotations from + // both the method declaration and the type arguments at the use of the method. Annotations + // from type arguments must not be viewpoint-adapted to the call site. For example: + // Map map = ...; + // List<@KeyFor("this.map") String> list = ...; + // list.get(0) + // + // methodType is @KeyFor("this.map") String get(int) + // "this.map" must not be viewpoint-adapted to the invocation because it is not from + // the method declaration, but added during type variable substitution. + // + // So this implementation gets the declared type of the method, declaredMethodType, + // viewpoint-adapts all dependent type annotations in declaredMethodType to the call site, + // and then copies the viewpoint-adapted annotations from methodType except for types that + // are replaced by type variable substitution. (Those annotations are viewpoint-adapted + // before type variable substitution.) + + // The annotations on `declaredMethodType` will be copied to `methodType`. + AnnotatedExecutableType declaredMethodType = + (AnnotatedExecutableType) atypeFactory.getAnnotatedType(methodElt); + if (!hasDependentType(declaredMethodType)) { + return; } - /** - * Viewpoint-adapts dependent type annotations in a method or constructor type. - * - *

{@code methodType} has been viewpoint-adapted to the call site, except for any dependent - * type annotations. (For example, type variables have been substituted and polymorphic - * qualifiers have been resolved.) This method viewpoint-adapts the dependent type annotations. - * - * @param methodType type of the method or constructor invocation; is side-effected by this - * method - * @param tree invocation of the method or constructor - */ - private void atInvocation(AnnotatedExecutableType methodType, ExpressionTree tree) { - assert hasDependentAnnotations(); - Element methodElt = TreeUtils.elementFromUse(tree); - // Because methodType is the type post type variable substitution, it has annotations from - // both the method declaration and the type arguments at the use of the method. Annotations - // from type arguments must not be viewpoint-adapted to the call site. For example: - // Map map = ...; - // List<@KeyFor("this.map") String> list = ...; - // list.get(0) - // - // methodType is @KeyFor("this.map") String get(int) - // "this.map" must not be viewpoint-adapted to the invocation because it is not from - // the method declaration, but added during type variable substitution. - // - // So this implementation gets the declared type of the method, declaredMethodType, - // viewpoint-adapts all dependent type annotations in declaredMethodType to the call site, - // and then copies the viewpoint-adapted annotations from methodType except for types that - // are replaced by type variable substitution. (Those annotations are viewpoint-adapted - // before type variable substitution.) - - // The annotations on `declaredMethodType` will be copied to `methodType`. - AnnotatedExecutableType declaredMethodType = - (AnnotatedExecutableType) atypeFactory.getAnnotatedType(methodElt); - if (!hasDependentType(declaredMethodType)) { - return; - } + StringToJavaExpression stringToJavaExpr; + if (tree instanceof MethodInvocationTree) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodInvocation( + stringExpr, (MethodInvocationTree) tree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atInvocation(%s, %s) 1 created %s%n", + methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); + } + } else if (tree instanceof NewClassTree) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atConstructorInvocation( + stringExpr, (NewClassTree) tree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atInvocation(%s, %s) 2 created %s%n", + methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); + } + } else { + throw new BugInCF("Unexpected tree: %s kind: %s", tree, tree.getKind()); + } + convertAnnotatedTypeMirror(stringToJavaExpr, declaredMethodType); + this.viewpointAdaptedCopier.visit(declaredMethodType, methodType); + } + + /** + * Viewpoint-adapts the Java expressions in annotations written on a field declaration to the use + * at {@code fieldAccess}. + * + * @param type its type; is side-effected by this method + * @param fieldAccess a field access + */ + public void atFieldAccess(AnnotatedTypeMirror type, MemberSelectTree fieldAccess) { + if (!hasDependentType(type)) { + return; + } - StringToJavaExpression stringToJavaExpr; - if (tree instanceof MethodInvocationTree) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodInvocation( - stringExpr, - (MethodInvocationTree) tree, - atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atInvocation(%s, %s) 1 created %s%n", - methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); - } - } else if (tree instanceof NewClassTree) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atConstructorInvocation( - stringExpr, (NewClassTree) tree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atInvocation(%s, %s) 2 created %s%n", - methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); - } - } else { - throw new BugInCF("Unexpected tree: %s kind: %s", tree, tree.getKind()); - } - convertAnnotatedTypeMirror(stringToJavaExpr, declaredMethodType); - this.viewpointAdaptedCopier.visit(declaredMethodType, methodType); + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atFieldAccess( + stringExpr, fieldAccess, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atFieldAccess(%s, %s) created %s%n", + type, TreeUtils.toStringTruncated(fieldAccess, 65), stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, type); + } + + /** + * Viewpoint-adapts the Java expressions in annotations written on the signature of the method + * declaration (for example, a return type) to the body of the method. This means the parameter + * syntax, e.g. "#2", is converted to the names of the parameter. + * + * @param atm a type at the method signature; is side-effected by this method + * @param methodDeclTree a method declaration + */ + public void atMethodBody(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { + if (!hasDependentType(atm)) { + return; } - /** - * Viewpoint-adapts the Java expressions in annotations written on a field declaration to the - * use at {@code fieldAccess}. - * - * @param type its type; is side-effected by this method - * @param fieldAccess a field access - */ - public void atFieldAccess(AnnotatedTypeMirror type, MemberSelectTree fieldAccess) { - if (!hasDependentType(type)) { - return; - } + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody( + stringExpr, methodDeclTree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atMethodBody(%s, %s) 1 created %s%n", + atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, atm); + } + + /** + * Standardizes the Java expressions in annotations to a type declaration. + * + * @param type the type of the type declaration; is side-effected by this method + * @param typeElt the element of the type declaration + */ + public void atTypeDecl(AnnotatedTypeMirror type, TypeElement typeElt) { + if (!hasDependentType(type)) { + return; + } - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atFieldAccess( - stringExpr, fieldAccess, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atFieldAccess(%s, %s) created %s%n", - type, TreeUtils.toStringTruncated(fieldAccess, 65), stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, type); + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atTypeDecl(stringExpr, typeElt, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf("atTypeDecl(%s, %s) created %s%n", type, typeElt, stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, type); + } + + /** A set containing {@link Tree.Kind#METHOD} and {@link Tree.Kind#LAMBDA_EXPRESSION}. */ + private static final Set METHOD_OR_LAMBDA = + EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION); + + /** + * Standardize the Java expressions in annotations in a variable declaration. Converts the + * parameter syntax, e.g "#1", to the parameter name. + * + * @param type the type of the variable declaration; is side-effected by this method + * @param declarationTree the variable declaration + * @param variableElt the element of the variable declaration + */ + public void atVariableDeclaration( + AnnotatedTypeMirror type, Tree declarationTree, VariableElement variableElt) { + if (!hasDependentType(type)) { + return; } - /** - * Viewpoint-adapts the Java expressions in annotations written on the signature of the method - * declaration (for example, a return type) to the body of the method. This means the parameter - * syntax, e.g. "#2", is converted to the names of the parameter. - * - * @param atm a type at the method signature; is side-effected by this method - * @param methodDeclTree a method declaration - */ - public void atMethodBody(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { - if (!hasDependentType(atm)) { - return; + TreePath pathToVariableDecl = atypeFactory.getPath(declarationTree); + assert pathToVariableDecl != null; + + if (variableElt instanceof DetachedVarSymbol) { + // Skip standardizing the Java expression in annotations of artificial variables, + // because artificial variables are created and initialized with standardized Java + // expressions. + // For example, for the following code fragment from + // Nullness test case checker/tests/nullness-asserts/NonNullMapValue.java + // + // ... + // static HashMap call_hashmap = new HashMap<>(); + // + // public foo() { + // for (Integer i : call_hashmap.keySet()) { + // @NonNull Date d = call_hashmap.get(i); + // } + // } + // ... + // + // + // the CFGBuilder creates the initialized artificial iterator as follows: + // iter#num0 = NonNullMapValue.call_hashmap.keySet().iterator() + // Therefore, for the Map Key Checker, the type of the variable "i" is + // @KeyFor({"NonNullMapValue.call_hashmap"}), the string in which is already + // standardized. + return; + } + ElementKind variableKind = variableElt.getKind(); + if (ElementUtils.isBindingVariable(variableElt)) { + // Treat binding variables the same as local variables. + variableKind = ElementKind.LOCAL_VARIABLE; + } + switch (variableKind) { + case PARAMETER: + TreePath pathTillEnclTree = + TreePathUtil.pathTillOfKind(pathToVariableDecl, METHOD_OR_LAMBDA); + if (pathTillEnclTree == null) { + throw new BugInCF("no enclosing method or lambda found for " + variableElt); } - - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody( - stringExpr, methodDeclTree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { + Tree enclTree = pathTillEnclTree.getLeaf(); + + if (enclTree.getKind() == Tree.Kind.METHOD) { + MethodTree methodDeclTree = (MethodTree) enclTree; + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody( + stringExpr, methodDeclTree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atVariableDeclaration(%s, %s, %s) 1 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, type); + } else { + // Lambdas can use local variables defined in the enclosing method, so allow + // identifiers to be locals in scope at the location of the lambda. + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atLambdaParameter( + stringExpr, + (LambdaExpressionTree) enclTree, + pathToVariableDecl.getParentPath(), + atypeFactory.getChecker()); + if (debugStringToJavaExpression) { System.out.printf( - "atMethodBody(%s, %s) 1 created %s%n", - atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); + "atVariableDeclaration(%s, %s, %s) 2 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, type); } - convertAnnotatedTypeMirror(stringToJavaExpr, atm); - } - - /** - * Standardizes the Java expressions in annotations to a type declaration. - * - * @param type the type of the type declaration; is side-effected by this method - * @param typeElt the element of the type declaration - */ - public void atTypeDecl(AnnotatedTypeMirror type, TypeElement typeElt) { - if (!hasDependentType(type)) { - return; + break; + + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + StringToJavaExpression stringToJavaExprVar = + stringExpr -> + StringToJavaExpression.atPath( + stringExpr, pathToVariableDecl, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atVariableDeclaration(%s, %s, %s) 3 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExprVar); } - - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atTypeDecl( - stringExpr, typeElt, atypeFactory.getChecker()); + convertAnnotatedTypeMirror(stringToJavaExprVar, type); + break; + + case FIELD: + case ENUM_CONSTANT: + StringToJavaExpression stringToJavaExprField = + stringExpr -> + StringToJavaExpression.atFieldDecl( + stringExpr, variableElt, atypeFactory.getChecker()); if (debugStringToJavaExpression) { - System.out.printf("atTypeDecl(%s, %s) created %s%n", type, typeElt, stringToJavaExpr); + System.out.printf( + "atVariableDeclaration(%s, %s, %s) 4 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExprField); } - convertAnnotatedTypeMirror(stringToJavaExpr, type); + convertAnnotatedTypeMirror(stringToJavaExprField, type); + break; + + default: + throw new BugInCF( + "unexpected element kind " + variableElt.getKind() + " for " + variableElt); + } + } + + /** + * Standardize the Java expressions in annotations in written in the {@code expressionTree}. Also, + * converts the parameter syntax, e.g. "#1", to the parameter name. + * + *

{@code expressionTree} must be an expressions which can contain explicitly written + * annotations, namely a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or {@link + * com.sun.source.tree.TypeCastTree}. For example, this method standardizes the {@code KeyFor} + * annotation in {@code (@KeyFor("map") String) key }. + * + * @param annotatedType its type; is side-effected by this method + * @param expressionTree a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or + * {@link com.sun.source.tree.TypeCastTree} + */ + public void atExpression(AnnotatedTypeMirror annotatedType, ExpressionTree expressionTree) { + if (!hasDependentType(annotatedType)) { + return; } - /** A set containing {@link Tree.Kind#METHOD} and {@link Tree.Kind#LAMBDA_EXPRESSION}. */ - private static final Set METHOD_OR_LAMBDA = - EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION); + TreePath path = atypeFactory.getPath(expressionTree); + if (path == null) { + return; + } + StringToJavaExpression stringToJavaExpr = + stringExpr -> StringToJavaExpression.atPath(stringExpr, path, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atExpression(%s, %s) created %s%n", + annotatedType, TreeUtils.toStringTruncated(expressionTree, 65), stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, annotatedType); + } + + /** + * Standardize the Java expressions in annotations in a type. Converts the parameter syntax, e.g. + * "#2", to the parameter name. + * + * @param type the type to standardize; is side-effected by this method + * @param elt the element whose type is {@code type} + */ + public void atLocalVariable(AnnotatedTypeMirror type, Element elt) { + if (!hasDependentType(type)) { + return; + } - /** - * Standardize the Java expressions in annotations in a variable declaration. Converts the - * parameter syntax, e.g "#1", to the parameter name. - * - * @param type the type of the variable declaration; is side-effected by this method - * @param declarationTree the variable declaration - * @param variableElt the element of the variable declaration - */ - public void atVariableDeclaration( - AnnotatedTypeMirror type, Tree declarationTree, VariableElement variableElt) { - if (!hasDependentType(type)) { + switch (elt.getKind()) { + case PARAMETER: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + Tree declarationTree = atypeFactory.declarationFromElement(elt); + if (declarationTree == null) { + if (elt.getKind() == ElementKind.PARAMETER) { + // The tree might be null when + // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the + // assignment context for a pseudo assignment of an argument to a method + // parameter. return; + } + throw new BugInCF(this.getClass() + ": tree not found"); + } else if (TreeUtils.typeOf(declarationTree) == null) { + // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() + // gets the assignment context for a pseudo assignment of an argument to a + // method parameter. + return; } - TreePath pathToVariableDecl = atypeFactory.getPath(declarationTree); - assert pathToVariableDecl != null; - - if (variableElt instanceof DetachedVarSymbol) { - // Skip standardizing the Java expression in annotations of artificial variables, - // because artificial variables are created and initialized with standardized Java - // expressions. - // For example, for the following code fragment from - // Nullness test case checker/tests/nullness-asserts/NonNullMapValue.java - // - // ... - // static HashMap call_hashmap = new HashMap<>(); - // - // public foo() { - // for (Integer i : call_hashmap.keySet()) { - // @NonNull Date d = call_hashmap.get(i); - // } - // } - // ... - // - // - // the CFGBuilder creates the initialized artificial iterator as follows: - // iter#num0 = NonNullMapValue.call_hashmap.keySet().iterator() - // Therefore, for the Map Key Checker, the type of the variable "i" is - // @KeyFor({"NonNullMapValue.call_hashmap"}), the string in which is already - // standardized. - return; - } - ElementKind variableKind = variableElt.getKind(); - if (ElementUtils.isBindingVariable(variableElt)) { - // Treat binding variables the same as local variables. - variableKind = ElementKind.LOCAL_VARIABLE; - } - switch (variableKind) { - case PARAMETER: - TreePath pathTillEnclTree = - TreePathUtil.pathTillOfKind(pathToVariableDecl, METHOD_OR_LAMBDA); - if (pathTillEnclTree == null) { - throw new BugInCF("no enclosing method or lambda found for " + variableElt); - } - Tree enclTree = pathTillEnclTree.getLeaf(); - - if (enclTree.getKind() == Tree.Kind.METHOD) { - MethodTree methodDeclTree = (MethodTree) enclTree; - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody( - stringExpr, methodDeclTree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 1 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, type); - } else { - // Lambdas can use local variables defined in the enclosing method, so allow - // identifiers to be locals in scope at the location of the lambda. - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atLambdaParameter( - stringExpr, - (LambdaExpressionTree) enclTree, - pathToVariableDecl.getParentPath(), - atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 2 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, type); - } - break; - - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - StringToJavaExpression stringToJavaExprVar = - stringExpr -> - StringToJavaExpression.atPath( - stringExpr, pathToVariableDecl, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 3 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExprVar); - } - convertAnnotatedTypeMirror(stringToJavaExprVar, type); - break; - - case FIELD: - case ENUM_CONSTANT: - StringToJavaExpression stringToJavaExprField = - stringExpr -> - StringToJavaExpression.atFieldDecl( - stringExpr, variableElt, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 4 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExprField); - } - convertAnnotatedTypeMirror(stringToJavaExprField, type); - break; + atVariableDeclaration(type, declarationTree, (VariableElement) elt); + return; - default: - throw new BugInCF( - "unexpected element kind " + variableElt.getKind() + " for " + variableElt); - } + default: + // It's not a local variable (it might be METHOD, CONSTRUCTOR, CLASS, or INTERFACE, + // for example), so there is nothing to do. + break; + } + } + + /** Thrown when a non-parameter local variable is found. */ + @SuppressWarnings("serial") + private static class FoundLocalException extends RuntimeException {} + + /** + * Viewpoint-adapt all dependent type annotations to the method declaration, {@code + * methodDeclTree}. This method changes occurrences of formal parameter names to the "#2" syntax, + * and it removes expressions that contain other local variables. + * + *

If a Java expression in {@code atm} references local variables (other than formal + * parameters), the expression is removed from the annotation. This could result in dependent type + * annotations with empty lists of expressions. If this is a problem, a subclass can override + * {@link #buildAnnotation(AnnotationMirror, Map)} to do something besides creating an annotation + * with a empty list. + * + * @param atm type to viewpoint-adapt; is side-effected by this method + * @param methodDeclTree the method declaration to which the annotations are viewpoint-adapted + */ + public void delocalize(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { + if (!hasDependentType(atm)) { + return; } - /** - * Standardize the Java expressions in annotations in written in the {@code expressionTree}. - * Also, converts the parameter syntax, e.g. "#1", to the parameter name. - * - *

{@code expressionTree} must be an expressions which can contain explicitly written - * annotations, namely a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or - * {@link com.sun.source.tree.TypeCastTree}. For example, this method standardizes the {@code - * KeyFor} annotation in {@code (@KeyFor("map") String) key }. - * - * @param annotatedType its type; is side-effected by this method - * @param expressionTree a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or - * {@link com.sun.source.tree.TypeCastTree} - */ - public void atExpression(AnnotatedTypeMirror annotatedType, ExpressionTree expressionTree) { - if (!hasDependentType(annotatedType)) { - return; - } - - TreePath path = atypeFactory.getPath(expressionTree); - if (path == null) { - return; - } - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atPath(stringExpr, path, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atExpression(%s, %s) created %s%n", - annotatedType, - TreeUtils.toStringTruncated(expressionTree, 65), - stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, annotatedType); + TreePath pathToMethodDecl = atypeFactory.getPath(methodDeclTree); + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodDeclTree); + List parameters = JavaExpression.getFormalParameters(methodElement); + List paramsAsLocals = + JavaExpression.getParametersAsLocalVariables(methodElement); + + StringToJavaExpression stringToJavaExpr = + expression -> { + JavaExpression javaExpr; + try { + javaExpr = + StringToJavaExpression.atPath( + expression, pathToMethodDecl, atypeFactory.getChecker()); + } catch (JavaExpressionParseException ex) { + return null; + } + JavaExpressionConverter jec = + new JavaExpressionConverter() { + @Override + protected JavaExpression visitLocalVariable( + LocalVariable localVarExpr, Void unused) { + int index = paramsAsLocals.indexOf(localVarExpr); + if (index == -1) { + throw new FoundLocalException(); + } + return parameters.get(index); + } + }; + try { + return jec.convert(javaExpr); + } catch (FoundLocalException ex) { + return null; + } + }; + if (debugStringToJavaExpression) { + System.out.printf( + "delocalize(%s, %s) created %s%n", + atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, atm); + } + + /** + * Delocalizes dependent type annotations in {@code atm} so that they can be placed on the + * declaration of the given method or constructor being invoked. Used by whole program inference + * to infer dependent types for method/constructor parameters based on the actual arguments used + * at call sites. + * + * @param atm the annotated type mirror to delocalize + * @param invocationTree the method or constructor invocation + * @param arguments the actual arguments to the method or constructor + * @param receiver the actual receiver, if there was one; null if not + * @param methodElt the declaration of the method or constructor being invoked + */ + public void delocalizeAtCallsite( + AnnotatedTypeMirror atm, + Tree invocationTree, + List arguments, + @Nullable Node receiver, + ExecutableElement methodElt) { + + // TODO: this method should also take the receiver parameter, if there was one at the + // callsite, as an argument. Before it does, WPI needs to infer receiver types from + // callsites. + + if (!hasDependentType(atm)) { + return; } - /** - * Standardize the Java expressions in annotations in a type. Converts the parameter syntax, - * e.g. "#2", to the parameter name. - * - * @param type the type to standardize; is side-effected by this method - * @param elt the element whose type is {@code type} - */ - public void atLocalVariable(AnnotatedTypeMirror type, Element elt) { - if (!hasDependentType(type)) { - return; - } + // For use in stringToJavaExpr below, to avoid re-computation. Especially + // important for the TreePath, which is expensive to compute. + List argsAsExprs = CollectionsPlume.mapList(LocalVariable::fromNode, arguments); + JavaExpression receiverAsExpr = receiver == null ? null : LocalVariable.fromNode(receiver); + TreePath path = atypeFactory.getPath(invocationTree); + + StringToJavaExpression stringToJavaExpr = + stringExpr -> { + JavaExpression expr = + StringToJavaExpression.atPath(stringExpr, path, atypeFactory.getChecker()); + JavaExpressionConverter jec = + new JavaExpressionConverter() { + @Override + public JavaExpression convert(JavaExpression javaExpr) { + // if javaExpr is an argument to the method, + // then return formal parameter expression. + int index = argsAsExprs.indexOf(javaExpr); + if (index != -1) { + return FormalParameter.getFormalParameters(methodElt).get(index); + } + if (javaExpr.equals(receiverAsExpr)) { + return new ThisReference(ElementUtils.enclosingTypeElement(methodElt).asType()); + } + return super.convert(javaExpr); + } - switch (elt.getKind()) { - case PARAMETER: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - Tree declarationTree = atypeFactory.declarationFromElement(elt); - if (declarationTree == null) { - if (elt.getKind() == ElementKind.PARAMETER) { - // The tree might be null when - // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the - // assignment context for a pseudo assignment of an argument to a method - // parameter. - return; - } - throw new BugInCF(this.getClass() + ": tree not found"); - } else if (TreeUtils.typeOf(declarationTree) == null) { - // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() - // gets the assignment context for a pseudo assignment of an argument to a - // method parameter. - return; + // Local variables and this references at the call site that do not + // correspond to any parameter need to be removed from the dependent + // type annotation, which returning null from these methods + // accomplishes. + @Override + public JavaExpression visitLocalVariable(LocalVariable local, Void unused) { + throw new FoundLocalException(); } - atVariableDeclaration(type, declarationTree, (VariableElement) elt); - return; + @Override + public JavaExpression visitThisReference(ThisReference thisRef, Void unused) { + throw new FoundLocalException(); + } + }; - default: - // It's not a local variable (it might be METHOD, CONSTRUCTOR, CLASS, or INTERFACE, - // for example), so there is nothing to do. - break; - } + try { + return jec.convert(expr); + } catch (FoundLocalException ex) { + return null; + } + }; + + convertAnnotatedTypeMirror(stringToJavaExpr, atm); + } + + /** + * Calls {@link #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} on each + * annotation mirror on type with {@code stringToJavaExpr}. And replaces the annotation with the + * one created by {@code convertAnnotationMirror}, if it's not null. If it is null, the original + * annotation is used. See {@link #convertAnnotationMirror(StringToJavaExpression, + * AnnotationMirror)} for more details. + * + * @param stringToJavaExpr function to convert a string to a {@link JavaExpression} + * @param type the type that is side-effected by this method + */ + protected void convertAnnotatedTypeMirror( + StringToJavaExpression stringToJavaExpr, AnnotatedTypeMirror type) { + this.annotatedTypeReplacer.visit(type, anno -> convertAnnotationMirror(stringToJavaExpr, anno)); + } + + /** + * Given an annotation {@code anno}, this method builds a new annotation with the Java expressions + * transformed according to {@code stringToJavaExpr}. If {@code anno} is not a dependent type + * annotation, {@code null} is returned. + * + *

If {@code stringToJavaExpr} returns {@code null}, then that expression is removed from the + * returned annotation. + * + *

Instead of overriding this method, subclasses can override the following methods to change + * the behavior of this class: + * + *

    + *
  • {@link #shouldPassThroughExpression(String)}: to control which expressions are skipped. + * If this method returns true, then the expression string is not parsed and is included in + * the new annotation unchanged. + *
  • {@link #transform(JavaExpression)}: make changes to the JavaExpression produced by {@code + * stringToJavaExpr}. + *
  • {@link #buildAnnotation(AnnotationMirror, Map)}: to change the annotation returned by + * this method. + *
+ * + * @param stringToJavaExpr function that converts strings to {@code JavaExpression}s + * @param anno annotation mirror + * @return an annotation created by applying {@code stringToJavaExpr} to all expression strings in + * {@code anno}, or null if there would be no effect + */ + public @Nullable AnnotationMirror convertAnnotationMirror( + StringToJavaExpression stringToJavaExpr, AnnotationMirror anno) { + if (!isExpressionAnno(anno)) { + return null; } - /** Thrown when a non-parameter local variable is found. */ - @SuppressWarnings("serial") - private static class FoundLocalException extends RuntimeException {} - - /** - * Viewpoint-adapt all dependent type annotations to the method declaration, {@code - * methodDeclTree}. This method changes occurrences of formal parameter names to the "#2" - * syntax, and it removes expressions that contain other local variables. - * - *

If a Java expression in {@code atm} references local variables (other than formal - * parameters), the expression is removed from the annotation. This could result in dependent - * type annotations with empty lists of expressions. If this is a problem, a subclass can - * override {@link #buildAnnotation(AnnotationMirror, Map)} to do something besides creating an - * annotation with a empty list. - * - * @param atm type to viewpoint-adapt; is side-effected by this method - * @param methodDeclTree the method declaration to which the annotations are viewpoint-adapted - */ - public void delocalize(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { - if (!hasDependentType(atm)) { - return; + Map> newElements = new HashMap<>(); + for (ExecutableElement element : getListOfExpressionElements(anno)) { + List expressionStrings = + AnnotationUtils.getElementValueArray( + anno, element, String.class, Collections.emptyList()); + List javaExprs = new ArrayList<>(expressionStrings.size()); + newElements.put(element, javaExprs); + for (String expression : expressionStrings) { + JavaExpression result; + if (shouldPassThroughExpression(expression)) { + result = new PassThroughExpression(objectTM, expression); + } else { + try { + result = stringToJavaExpr.toJavaExpression(expression); + } catch (JavaExpressionParseException e) { + result = createError(expression, e); + } } - TreePath pathToMethodDecl = atypeFactory.getPath(methodDeclTree); - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodDeclTree); - List parameters = JavaExpression.getFormalParameters(methodElement); - List paramsAsLocals = - JavaExpression.getParametersAsLocalVariables(methodElement); - - StringToJavaExpression stringToJavaExpr = - expression -> { - JavaExpression javaExpr; - try { - javaExpr = - StringToJavaExpression.atPath( - expression, pathToMethodDecl, atypeFactory.getChecker()); - } catch (JavaExpressionParseException ex) { - return null; - } - JavaExpressionConverter jec = - new JavaExpressionConverter() { - @Override - protected JavaExpression visitLocalVariable( - LocalVariable localVarExpr, Void unused) { - int index = paramsAsLocals.indexOf(localVarExpr); - if (index == -1) { - throw new FoundLocalException(); - } - return parameters.get(index); - } - }; - try { - return jec.convert(javaExpr); - } catch (FoundLocalException ex) { - return null; - } - }; - if (debugStringToJavaExpression) { - System.out.printf( - "delocalize(%s, %s) created %s%n", - atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); + if (result != null) { + result = transform(result); + javaExprs.add(result); } - convertAnnotatedTypeMirror(stringToJavaExpr, atm); + } } - - /** - * Delocalizes dependent type annotations in {@code atm} so that they can be placed on the - * declaration of the given method or constructor being invoked. Used by whole program inference - * to infer dependent types for method/constructor parameters based on the actual arguments used - * at call sites. - * - * @param atm the annotated type mirror to delocalize - * @param invocationTree the method or constructor invocation - * @param arguments the actual arguments to the method or constructor - * @param receiver the actual receiver, if there was one; null if not - * @param methodElt the declaration of the method or constructor being invoked - */ - public void delocalizeAtCallsite( - AnnotatedTypeMirror atm, - Tree invocationTree, - List arguments, - @Nullable Node receiver, - ExecutableElement methodElt) { - - // TODO: this method should also take the receiver parameter, if there was one at the - // callsite, as an argument. Before it does, WPI needs to infer receiver types from - // callsites. - - if (!hasDependentType(atm)) { - return; - } - - // For use in stringToJavaExpr below, to avoid re-computation. Especially - // important for the TreePath, which is expensive to compute. - List argsAsExprs = - CollectionsPlume.mapList(LocalVariable::fromNode, arguments); - JavaExpression receiverAsExpr = receiver == null ? null : LocalVariable.fromNode(receiver); - TreePath path = atypeFactory.getPath(invocationTree); - - StringToJavaExpression stringToJavaExpr = - stringExpr -> { - JavaExpression expr = - StringToJavaExpression.atPath( - stringExpr, path, atypeFactory.getChecker()); - JavaExpressionConverter jec = - new JavaExpressionConverter() { - @Override - public JavaExpression convert(JavaExpression javaExpr) { - // if javaExpr is an argument to the method, - // then return formal parameter expression. - int index = argsAsExprs.indexOf(javaExpr); - if (index != -1) { - return FormalParameter.getFormalParameters(methodElt) - .get(index); - } - if (javaExpr.equals(receiverAsExpr)) { - return new ThisReference( - ElementUtils.enclosingTypeElement(methodElt) - .asType()); - } - return super.convert(javaExpr); - } - - // Local variables and this references at the call site that do not - // correspond to any parameter need to be removed from the dependent - // type annotation, which returning null from these methods - // accomplishes. - @Override - public JavaExpression visitLocalVariable( - LocalVariable local, Void unused) { - throw new FoundLocalException(); - } - - @Override - public JavaExpression visitThisReference( - ThisReference thisRef, Void unused) { - throw new FoundLocalException(); - } - }; - - try { - return jec.convert(expr); - } catch (FoundLocalException ex) { - return null; - } - }; - - convertAnnotatedTypeMirror(stringToJavaExpr, atm); + return buildAnnotation(anno, newElements); + } + + /** + * This method is for subclasses to override to change JavaExpressions in some way before they are + * inserted into new annotations. This method is called after parsing and viewpoint-adaptation + * have occurred. {@code javaExpr} may be a {@link DependentTypesHelper.PassThroughExpression}. + * + *

If {@code null} is returned then the expression is not added to the new annotation. + * + *

The default implementation returns the argument, but subclasses may override it. + * + * @param javaExpr a JavaExpression + * @return a transformed JavaExpression or {@code null} if no transformation exists + */ + protected @Nullable JavaExpression transform(JavaExpression javaExpr) { + return javaExpr; + } + + /** + * Whether or not {@code expression} should be passed to the new annotation unchanged. If this + * method returns true, the {@code expression} is not parsed. + * + *

The default implementation returns true if the {@code expression} is an expression error + * according to {@link DependentTypesError#isExpressionError(String)}. Subclasses may override + * this method to add additional logic. + * + * @param expression an expression string in a dependent types annotation + * @return whether or not {@code expression} should be passed through unchanged to the new + * annotation + */ + protected boolean shouldPassThroughExpression(String expression) { + return DependentTypesError.isExpressionError(expression); + } + + /** + * Create a new annotation of the same type as {@code originalAnno} using the provided {@code + * elementMap}. + * + * @param originalAnno the annotation passed to {@link + * #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} (this method is a + * helper method for {@link #convertAnnotationMirror(StringToJavaExpression, + * AnnotationMirror)}) + * @param elementMap a mapping from element of {@code originalAnno} to {@code JavaExpression}s + * @return an annotation created from {@code elementMap} + */ + protected AnnotationMirror buildAnnotation( + AnnotationMirror originalAnno, Map> elementMap) { + AnnotationBuilder builder = + new AnnotationBuilder( + atypeFactory.getProcessingEnv(), AnnotationUtils.annotationName(originalAnno)); + builder.copyElementValuesFromAnnotation(originalAnno, elementMap.keySet()); + for (Map.Entry> entry : elementMap.entrySet()) { + List strings = CollectionsPlume.mapList(JavaExpression::toString, entry.getValue()); + builder.setValue(entry.getKey(), strings); } + return builder.build(); + } - /** - * Calls {@link #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} on each - * annotation mirror on type with {@code stringToJavaExpr}. And replaces the annotation with the - * one created by {@code convertAnnotationMirror}, if it's not null. If it is null, the original - * annotation is used. See {@link #convertAnnotationMirror(StringToJavaExpression, - * AnnotationMirror)} for more details. - * - * @param stringToJavaExpr function to convert a string to a {@link JavaExpression} - * @param type the type that is side-effected by this method - */ - protected void convertAnnotatedTypeMirror( - StringToJavaExpression stringToJavaExpr, AnnotatedTypeMirror type) { - this.annotatedTypeReplacer.visit( - type, anno -> convertAnnotationMirror(stringToJavaExpr, anno)); - } - - /** - * Given an annotation {@code anno}, this method builds a new annotation with the Java - * expressions transformed according to {@code stringToJavaExpr}. If {@code anno} is not a - * dependent type annotation, {@code null} is returned. - * - *

If {@code stringToJavaExpr} returns {@code null}, then that expression is removed from the - * returned annotation. - * - *

Instead of overriding this method, subclasses can override the following methods to change - * the behavior of this class: - * - *

    - *
  • {@link #shouldPassThroughExpression(String)}: to control which expressions are skipped. - * If this method returns true, then the expression string is not parsed and is included - * in the new annotation unchanged. - *
  • {@link #transform(JavaExpression)}: make changes to the JavaExpression produced by - * {@code stringToJavaExpr}. - *
  • {@link #buildAnnotation(AnnotationMirror, Map)}: to change the annotation returned by - * this method. - *
- * - * @param stringToJavaExpr function that converts strings to {@code JavaExpression}s - * @param anno annotation mirror - * @return an annotation created by applying {@code stringToJavaExpr} to all expression strings - * in {@code anno}, or null if there would be no effect - */ - public @Nullable AnnotationMirror convertAnnotationMirror( - StringToJavaExpression stringToJavaExpr, AnnotationMirror anno) { - if (!isExpressionAnno(anno)) { - return null; - } - - Map> newElements = new HashMap<>(); - for (ExecutableElement element : getListOfExpressionElements(anno)) { - List expressionStrings = - AnnotationUtils.getElementValueArray( - anno, element, String.class, Collections.emptyList()); - List javaExprs = new ArrayList<>(expressionStrings.size()); - newElements.put(element, javaExprs); - for (String expression : expressionStrings) { - JavaExpression result; - if (shouldPassThroughExpression(expression)) { - result = new PassThroughExpression(objectTM, expression); - } else { - try { - result = stringToJavaExpr.toJavaExpression(expression); - } catch (JavaExpressionParseException e) { - result = createError(expression, e); - } - } - - if (result != null) { - result = transform(result); - javaExprs.add(result); - } - } - } - return buildAnnotation(anno, newElements); - } + /** + * A {@link JavaExpression} that does not represent a {@link JavaExpression}, but rather allows an + * expression string to be converted to a JavaExpression and then to a string without parsing. + */ + static class PassThroughExpression extends Unknown { + /** Some string. */ + public final String string; /** - * This method is for subclasses to override to change JavaExpressions in some way before they - * are inserted into new annotations. This method is called after parsing and - * viewpoint-adaptation have occurred. {@code javaExpr} may be a {@link - * DependentTypesHelper.PassThroughExpression}. - * - *

If {@code null} is returned then the expression is not added to the new annotation. - * - *

The default implementation returns the argument, but subclasses may override it. + * Creates a PassThroughExpression. * - * @param javaExpr a JavaExpression - * @return a transformed JavaExpression or {@code null} if no transformation exists + * @param type some type + * @param string the string to convert to a JavaExpression */ - protected @Nullable JavaExpression transform(JavaExpression javaExpr) { - return javaExpr; + public PassThroughExpression(TypeMirror type, String string) { + super(type); + this.string = string; } - /** - * Whether or not {@code expression} should be passed to the new annotation unchanged. If this - * method returns true, the {@code expression} is not parsed. - * - *

The default implementation returns true if the {@code expression} is an expression error - * according to {@link DependentTypesError#isExpressionError(String)}. Subclasses may override - * this method to add additional logic. - * - * @param expression an expression string in a dependent types annotation - * @return whether or not {@code expression} should be passed through unchanged to the new - * annotation - */ - protected boolean shouldPassThroughExpression(String expression) { - return DependentTypesError.isExpressionError(expression); + @Override + public String toString() { + return string; } - - /** - * Create a new annotation of the same type as {@code originalAnno} using the provided {@code - * elementMap}. - * - * @param originalAnno the annotation passed to {@link - * #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} (this method is a - * helper method for {@link #convertAnnotationMirror(StringToJavaExpression, - * AnnotationMirror)}) - * @param elementMap a mapping from element of {@code originalAnno} to {@code JavaExpression}s - * @return an annotation created from {@code elementMap} - */ - protected AnnotationMirror buildAnnotation( - AnnotationMirror originalAnno, - Map> elementMap) { - AnnotationBuilder builder = - new AnnotationBuilder( - atypeFactory.getProcessingEnv(), - AnnotationUtils.annotationName(originalAnno)); - builder.copyElementValuesFromAnnotation(originalAnno, elementMap.keySet()); - for (Map.Entry> entry : elementMap.entrySet()) { - List strings = - CollectionsPlume.mapList(JavaExpression::toString, entry.getValue()); - builder.setValue(entry.getKey(), strings); - } - return builder.build(); + } + + /** + * Creates a {@link JavaExpression} representing the exception thrown when parsing {@code + * expression}. + * + * @param expression an expression that caused {@code e} when parsed + * @param e the exception thrown when parsing {@code expression} + * @return a Java expression + */ + protected PassThroughExpression createError(String expression, JavaExpressionParseException e) { + return new PassThroughExpression(objectTM, new DependentTypesError(expression, e).toString()); + } + + /** + * Creates a {@link JavaExpression} representing the error caused when parsing {@code expression} + * + * @param expression an expression that caused {@code error} when parsed + * @param error the error message caused by {@code expression} + * @return a Java expression + */ + protected PassThroughExpression createError(String expression, String error) { + return new PassThroughExpression( + objectTM, new DependentTypesError(expression, error).toString()); + } + + /** + * Applies the passed function to each annotation in the given {@link AnnotatedTypeMirror}. If the + * function returns a non-null annotation, then the original annotation is replaced with the + * result. If the function returns null, the original annotation is retained. + */ + private static class AnnotatedTypeReplacer + extends AnnotatedTypeScanner> { + + @Override + public Void visitTypeVariable( + AnnotatedTypeMirror.AnnotatedTypeVariable type, + Function func) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, null); + + // If the type variable has a primary annotation, then it is viewpoint-adapted before + // this method is called. The viewpoint-adapted primary annotation was already copied + // to the upper and lower bounds. These annotations cannot be viewpoint-adapted again, + // so remove them, viewpoint-adapt any other annotations in the bound, and then add them + // back. + AnnotationMirrorSet primarys = type.getAnnotations(); + type.getLowerBound().removeAnnotations(primarys); + Void r = scan(type.getLowerBound(), func); + type.getLowerBound().addAnnotations(primarys); + visitedNodes.put(type, r); + + type.getUpperBound().removeAnnotations(primarys); + r = scanAndReduce(type.getUpperBound(), func, r); + type.getUpperBound().addAnnotations(primarys); + visitedNodes.put(type, r); + return r; } - /** - * A {@link JavaExpression} that does not represent a {@link JavaExpression}, but rather allows - * an expression string to be converted to a JavaExpression and then to a string without - * parsing. - */ - static class PassThroughExpression extends Unknown { - /** Some string. */ - public final String string; - - /** - * Creates a PassThroughExpression. - * - * @param type some type - * @param string the string to convert to a JavaExpression - */ - public PassThroughExpression(TypeMirror type, String string) { - super(type); - this.string = string; - } - - @Override - public String toString() { - return string; + @Override + protected Void scan( + AnnotatedTypeMirror type, Function func) { + for (AnnotationMirror anno : new AnnotationMirrorSet(type.getAnnotations())) { + AnnotationMirror newAnno = func.apply(anno); + if (newAnno != null) { + // This code must remove and then add, rather than call `replace`, because a + // type may have multiple annotations with the same class, but different + // elements. (This is a bug; see + // https://github.com/typetools/checker-framework/issues/4451 .) + // AnnotatedTypeMirror#replace only removes one annotation that is in the same + // hierarchy as the passed argument. + type.removeAnnotation(anno); + type.addAnnotation(newAnno); } + } + return super.scan(type, func); } - - /** - * Creates a {@link JavaExpression} representing the exception thrown when parsing {@code - * expression}. - * - * @param expression an expression that caused {@code e} when parsed - * @param e the exception thrown when parsing {@code expression} - * @return a Java expression - */ - protected PassThroughExpression createError(String expression, JavaExpressionParseException e) { - return new PassThroughExpression( - objectTM, new DependentTypesError(expression, e).toString()); + } + + /// + /// Methods that check and report errors + /// + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the given type + * that is an expression error string. + * + * @param atm annotated type to check for expression errors + * @param errorTree the tree at which to report any found errors + */ + public void checkTypeForErrorExpressions(AnnotatedTypeMirror atm, Tree errorTree) { + if (!hasDependentAnnotations()) { + return; } - /** - * Creates a {@link JavaExpression} representing the error caused when parsing {@code - * expression} - * - * @param expression an expression that caused {@code error} when parsed - * @param error the error message caused by {@code expression} - * @return a Java expression - */ - protected PassThroughExpression createError(String expression, String error) { - return new PassThroughExpression( - objectTM, new DependentTypesError(expression, error).toString()); + List errors = expressionErrorCollector.visit(atm); + if (errors.isEmpty()) { + return; } - /** - * Applies the passed function to each annotation in the given {@link AnnotatedTypeMirror}. If - * the function returns a non-null annotation, then the original annotation is replaced with the - * result. If the function returns null, the original annotation is retained. - */ - private static class AnnotatedTypeReplacer - extends AnnotatedTypeScanner> { - - @Override - public Void visitTypeVariable( - AnnotatedTypeMirror.AnnotatedTypeVariable type, - Function func) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + // Report the error at the type rather than at the variable. + if (errorTree.getKind() == Tree.Kind.VARIABLE) { + Tree typeTree = ((VariableTree) errorTree).getType(); + // Don't report the error at the type if the type is not present in source code. + if (((JCTree) typeTree).getPreferredPosition() != -1) { + ModifiersTree modifiers = ((VariableTree) errorTree).getModifiers(); + errorTree = typeTree; + for (AnnotationTree annoTree : modifiers.getAnnotations()) { + String annoString = annoTree.toString(); + for (String annoName : annoToElements.keySet()) { + // TODO: Simple string containment seems too simplistic. At least check for + // a word boundary. + if (annoString.contains(annoName)) { + errorTree = annoTree; + break; } - visitedNodes.put(type, null); - - // If the type variable has a primary annotation, then it is viewpoint-adapted before - // this method is called. The viewpoint-adapted primary annotation was already copied - // to the upper and lower bounds. These annotations cannot be viewpoint-adapted again, - // so remove them, viewpoint-adapt any other annotations in the bound, and then add them - // back. - AnnotationMirrorSet primarys = type.getAnnotations(); - type.getLowerBound().removeAnnotations(primarys); - Void r = scan(type.getLowerBound(), func); - type.getLowerBound().addAnnotations(primarys); - visitedNodes.put(type, r); - - type.getUpperBound().removeAnnotations(primarys); - r = scanAndReduce(type.getUpperBound(), func, r); - type.getUpperBound().addAnnotations(primarys); - visitedNodes.put(type, r); - return r; - } - - @Override - protected Void scan( - AnnotatedTypeMirror type, Function func) { - for (AnnotationMirror anno : new AnnotationMirrorSet(type.getAnnotations())) { - AnnotationMirror newAnno = func.apply(anno); - if (newAnno != null) { - // This code must remove and then add, rather than call `replace`, because a - // type may have multiple annotations with the same class, but different - // elements. (This is a bug; see - // https://github.com/typetools/checker-framework/issues/4451 .) - // AnnotatedTypeMirror#replace only removes one annotation that is in the same - // hierarchy as the passed argument. - type.removeAnnotation(anno); - type.addAnnotation(newAnno); - } - } - return super.scan(type, func); + } } + } } - - /// - /// Methods that check and report errors - /// - - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the given - * type that is an expression error string. - * - * @param atm annotated type to check for expression errors - * @param errorTree the tree at which to report any found errors - */ - public void checkTypeForErrorExpressions(AnnotatedTypeMirror atm, Tree errorTree) { - if (!hasDependentAnnotations()) { - return; - } - - List errors = expressionErrorCollector.visit(atm); - if (errors.isEmpty()) { - return; - } - - // Report the error at the type rather than at the variable. - if (errorTree.getKind() == Tree.Kind.VARIABLE) { - Tree typeTree = ((VariableTree) errorTree).getType(); - // Don't report the error at the type if the type is not present in source code. - if (((JCTree) typeTree).getPreferredPosition() != -1) { - ModifiersTree modifiers = ((VariableTree) errorTree).getModifiers(); - errorTree = typeTree; - for (AnnotationTree annoTree : modifiers.getAnnotations()) { - String annoString = annoTree.toString(); - for (String annoName : annoToElements.keySet()) { - // TODO: Simple string containment seems too simplistic. At least check for - // a word boundary. - if (annoString.contains(annoName)) { - errorTree = annoTree; - break; - } - } - } - } - } - reportErrors(errorTree, errors); + reportErrors(errorTree, errors); + } + + /** + * Report the given errors as "expression.unparsable.type.invalid". + * + * @param errorTree where to report the errors + * @param errors the errors to report + */ + protected void reportErrors(Tree errorTree, List errors) { + SourceChecker checker = atypeFactory.getChecker(); + for (DependentTypesError dte : errors) { + checker.reportError(errorTree, "expression.unparsable.type.invalid", dte.format()); } - - /** - * Report the given errors as "expression.unparsable.type.invalid". - * - * @param errorTree where to report the errors - * @param errors the errors to report - */ - protected void reportErrors(Tree errorTree, List errors) { - SourceChecker checker = atypeFactory.getChecker(); - for (DependentTypesError dte : errors) { - checker.reportError(errorTree, "expression.unparsable.type.invalid", dte.format()); + } + + /** + * Returns a list of {@link DependentTypesError}s for all the Java expression elements of the + * annotation that are an error string as specified by DependentTypesError#isExpressionError. + * + * @param am an annotation + * @return a list of {@link DependentTypesError}s for the error strings in the given annotation + */ + private List errorElements(AnnotationMirror am) { + assert hasDependentAnnotations(); + + List errors = new ArrayList<>(); + + for (ExecutableElement element : getListOfExpressionElements(am)) { + // It's always an array, not a single value, because @JavaExpression may only be written + // on an annotation element of type String[]. + List value = + AnnotationUtils.getElementValueArray(am, element, String.class, Collections.emptyList()); + for (String v : value) { + if (DependentTypesError.isExpressionError(v)) { + errors.add(DependentTypesError.unparse(v)); } + } } - - /** - * Returns a list of {@link DependentTypesError}s for all the Java expression elements of the - * annotation that are an error string as specified by DependentTypesError#isExpressionError. - * - * @param am an annotation - * @return a list of {@link DependentTypesError}s for the error strings in the given annotation - */ - private List errorElements(AnnotationMirror am) { - assert hasDependentAnnotations(); - - List errors = new ArrayList<>(); - - for (ExecutableElement element : getListOfExpressionElements(am)) { - // It's always an array, not a single value, because @JavaExpression may only be written - // on an annotation element of type String[]. - List value = - AnnotationUtils.getElementValueArray( - am, element, String.class, Collections.emptyList()); - for (String v : value) { - if (DependentTypesError.isExpressionError(v)) { - errors.add(DependentTypesError.unparse(v)); - } - } - } - return errors; + return errors; + } + + /** + * Reports a flowexpr.parse.error error for each Java expression in the given annotation that is + * an expression error string. + * + * @param annotation annotation to check + * @param errorTree location at which to issue errors + */ + public void checkAnnotationForErrorExpressions(AnnotationMirror annotation, Tree errorTree) { + if (!hasDependentAnnotations()) { + return; } - /** - * Reports a flowexpr.parse.error error for each Java expression in the given annotation that is - * an expression error string. - * - * @param annotation annotation to check - * @param errorTree location at which to issue errors - */ - public void checkAnnotationForErrorExpressions(AnnotationMirror annotation, Tree errorTree) { - if (!hasDependentAnnotations()) { - return; - } - - List errors = errorElements(annotation); - if (errors.isEmpty()) { - return; - } - SourceChecker checker = atypeFactory.getChecker(); - for (DependentTypesError error : errors) { - checker.reportError(errorTree, "flowexpr.parse.error", error); - } + List errors = errorElements(annotation); + if (errors.isEmpty()) { + return; + } + SourceChecker checker = atypeFactory.getChecker(); + for (DependentTypesError error : errors) { + checker.reportError(errorTree, "flowexpr.parse.error", error); + } + } + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the given class + * declaration AnnotatedTypeMirror that is an expression error string. Note that this reports + * errors in the class declaration itself, not the body or extends/implements clauses. + * + * @param classTree class to check + * @param type annotated type of the class + */ + public void checkClassForErrorExpressions(ClassTree classTree, AnnotatedDeclaredType type) { + if (!hasDependentAnnotations()) { + return; } - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the given - * class declaration AnnotatedTypeMirror that is an expression error string. Note that this - * reports errors in the class declaration itself, not the body or extends/implements clauses. - * - * @param classTree class to check - * @param type annotated type of the class - */ - public void checkClassForErrorExpressions(ClassTree classTree, AnnotatedDeclaredType type) { - if (!hasDependentAnnotations()) { - return; - } - - // TODO: check that invalid annotations in type variable bounds are properly - // formatted. They are part of the type, but the output isn't nicely formatted. - checkTypeForErrorExpressions(type, classTree); + // TODO: check that invalid annotations in type variable bounds are properly + // formatted. They are part of the type, but the output isn't nicely formatted. + checkTypeForErrorExpressions(type, classTree); + } + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the method + * declaration AnnotatedTypeMirror that is an expression error string. + * + * @param methodDeclTree method to check + * @param type annotated type of the method + */ + public void checkMethodForErrorExpressions( + MethodTree methodDeclTree, AnnotatedExecutableType type) { + if (!hasDependentAnnotations()) { + return; } - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the method - * declaration AnnotatedTypeMirror that is an expression error string. - * - * @param methodDeclTree method to check - * @param type annotated type of the method - */ - public void checkMethodForErrorExpressions( - MethodTree methodDeclTree, AnnotatedExecutableType type) { - if (!hasDependentAnnotations()) { - return; - } + // Parameters and receivers are checked by visitVariable + // So only type parameters and return type need to be checked here. - // Parameters and receivers are checked by visitVariable - // So only type parameters and return type need to be checked here. - - checkTypeVariablesForErrorExpressions(methodDeclTree, type); - // Check return type - if (type.getReturnType().getKind() != TypeKind.VOID) { - AnnotatedTypeMirror returnType = atypeFactory.getMethodReturnType(methodDeclTree); - Tree treeForError = - TreeUtils.isConstructor(methodDeclTree) - ? methodDeclTree - : methodDeclTree.getReturnType(); - checkTypeForErrorExpressions(returnType, treeForError); - } + checkTypeVariablesForErrorExpressions(methodDeclTree, type); + // Check return type + if (type.getReturnType().getKind() != TypeKind.VOID) { + AnnotatedTypeMirror returnType = atypeFactory.getMethodReturnType(methodDeclTree); + Tree treeForError = + TreeUtils.isConstructor(methodDeclTree) ? methodDeclTree : methodDeclTree.getReturnType(); + checkTypeForErrorExpressions(returnType, treeForError); } - - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the given - * type variables that is an expression error string. - * - * @param tree a method declaration - * @param methodType annotated type of the method - */ - private void checkTypeVariablesForErrorExpressions( - MethodTree tree, AnnotatedExecutableType methodType) { - for (int i = 0; i < methodType.getTypeVariables().size(); i++) { - AnnotatedTypeMirror atm = methodType.getTypeVariables().get(i); - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody( - stringExpr, tree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "checkTypeVariablesForErrorExpressions(%s, %s) created %s%n", - tree, methodType, stringToJavaExpr); + } + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the given type + * variables that is an expression error string. + * + * @param tree a method declaration + * @param methodType annotated type of the method + */ + private void checkTypeVariablesForErrorExpressions( + MethodTree tree, AnnotatedExecutableType methodType) { + for (int i = 0; i < methodType.getTypeVariables().size(); i++) { + AnnotatedTypeMirror atm = methodType.getTypeVariables().get(i); + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody(stringExpr, tree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "checkTypeVariablesForErrorExpressions(%s, %s) created %s%n", + tree, methodType, stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, atm); + checkTypeForErrorExpressions(atm, tree.getTypeParameters().get(i)); + } + } + + /** + * Returns true if {@code am} is an expression annotation, that is, an annotation whose element is + * a Java expression. + * + * @param am an annotation + * @return true if {@code am} is an expression annotation + */ + private boolean isExpressionAnno(AnnotationMirror am) { + if (!hasDependentAnnotations()) { + return false; + } + return annoToElements.containsKey(AnnotationUtils.annotationName(am)); + } + + /** + * Checks all dependent type annotations in the given annotated type to see if the expression + * string is an error string as specified by DependentTypesError#isExpressionError. If the + * annotated type has any errors, then a non-empty list of {@link DependentTypesError} is + * returned. + */ + private class ExpressionErrorCollector + extends SimpleAnnotatedTypeScanner, Void> { + + /** Create ExpressionErrorCollector. */ + private ExpressionErrorCollector() { + super( + (AnnotatedTypeMirror type, Void aVoid) -> { + List errors = new ArrayList<>(); + for (AnnotationMirror am : type.getAnnotations()) { + if (isExpressionAnno(am)) { + errors.addAll(errorElements(am)); + } } - convertAnnotatedTypeMirror(stringToJavaExpr, atm); - checkTypeForErrorExpressions(atm, tree.getTypeParameters().get(i)); - } + return errors; + }, + DependentTypesHelper::concatenate, + Collections.emptyList()); } - - /** - * Returns true if {@code am} is an expression annotation, that is, an annotation whose element - * is a Java expression. - * - * @param am an annotation - * @return true if {@code am} is an expression annotation - */ - private boolean isExpressionAnno(AnnotationMirror am) { - if (!hasDependentAnnotations()) { - return false; - } - return annoToElements.containsKey(AnnotationUtils.annotationName(am)); + } + + /** + * Appends list2 to list1 in a new list. If either list is empty, returns the other. Thus, the + * result may be aliased to one of the arguments and the client should only read, not write into, + * the result. + * + * @param list1 a list + * @param list2 a list + * @return the lists, concatenated + */ + private static List concatenate( + List list1, List list2) { + if (list1.isEmpty()) { + return list2; + } else if (list2.isEmpty()) { + return list1; } - - /** - * Checks all dependent type annotations in the given annotated type to see if the expression - * string is an error string as specified by DependentTypesError#isExpressionError. If the - * annotated type has any errors, then a non-empty list of {@link DependentTypesError} is - * returned. - */ - private class ExpressionErrorCollector - extends SimpleAnnotatedTypeScanner, Void> { - - /** Create ExpressionErrorCollector. */ - private ExpressionErrorCollector() { - super( - (AnnotatedTypeMirror type, Void aVoid) -> { - List errors = new ArrayList<>(); - for (AnnotationMirror am : type.getAnnotations()) { - if (isExpressionAnno(am)) { - errors.addAll(errorElements(am)); - } - } - return errors; - }, - DependentTypesHelper::concatenate, - Collections.emptyList()); + List newList = new ArrayList<>(list1.size() + list2.size()); + newList.addAll(list1); + newList.addAll(list2); + return newList; + } + + /** + * The underlying type of the second parameter is the result of applying type variable + * substitution to the visited type (the first parameter). This class copies annotations from the + * visited type to the second formal parameter except for annotations on types that have been + * substituted. + */ + private class ViewpointAdaptedCopier extends DoubleAnnotatedTypeScanner { + @Override + protected Void scan(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + if (from == null || to == null) { + return null; + } + AnnotationMirrorSet replacements = new AnnotationMirrorSet(); + for (String vpa : annoToElements.keySet()) { + AnnotationMirror anno = from.getAnnotation(vpa); + if (anno != null) { + // Only replace annotations that might have been changed. + replacements.add(anno); } + } + to.replaceAnnotations(replacements); + if (from.getKind() != to.getKind() + || (from.getKind() == TypeKind.TYPEVAR + && TypesUtils.isCapturedTypeVariable(to.getUnderlyingType()))) { + // If the underlying types don't match, then from has been substituted for a + // from variable, so don't recur. The primary annotation was copied because + // the from variable might have had a primary annotation at a use. + // For example: + // void method(@KeyFor("a") T t) {...} + // void use(@KeyFor("b") String s) { + // method(s); // the from of the parameter should be @KeyFor("a") String + // } + return null; + } + return super.scan(from, to); } - /** - * Appends list2 to list1 in a new list. If either list is empty, returns the other. Thus, the - * result may be aliased to one of the arguments and the client should only read, not write - * into, the result. - * - * @param list1 a list - * @param list2 a list - * @return the lists, concatenated - */ - private static List concatenate( - List list1, List list2) { - if (list1.isEmpty()) { - return list2; - } else if (list2.isEmpty()) { - return list1; - } - List newList = new ArrayList<>(list1.size() + list2.size()); - newList.addAll(list1); - newList.addAll(list2); - return newList; + @Override + protected Void defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (type1 == null || type2 == null) { + return null; + } + if (type1.getKind() != type2.getKind()) { + throw new BugInCF("Should be the same. type: %s p: %s ", type1, type2); + } + return null; } - - /** - * The underlying type of the second parameter is the result of applying type variable - * substitution to the visited type (the first parameter). This class copies annotations from - * the visited type to the second formal parameter except for annotations on types that have - * been substituted. - */ - private class ViewpointAdaptedCopier extends DoubleAnnotatedTypeScanner { - @Override - protected Void scan(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - if (from == null || to == null) { - return null; - } - AnnotationMirrorSet replacements = new AnnotationMirrorSet(); - for (String vpa : annoToElements.keySet()) { - AnnotationMirror anno = from.getAnnotation(vpa); - if (anno != null) { - // Only replace annotations that might have been changed. - replacements.add(anno); - } - } - to.replaceAnnotations(replacements); - if (from.getKind() != to.getKind() - || (from.getKind() == TypeKind.TYPEVAR - && TypesUtils.isCapturedTypeVariable(to.getUnderlyingType()))) { - // If the underlying types don't match, then from has been substituted for a - // from variable, so don't recur. The primary annotation was copied because - // the from variable might have had a primary annotation at a use. - // For example: - // void method(@KeyFor("a") T t) {...} - // void use(@KeyFor("b") String s) { - // method(s); // the from of the parameter should be @KeyFor("a") String - // } - return null; - } - return super.scan(from, to); - } - - @Override - protected Void defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (type1 == null || type2 == null) { - return null; - } - if (type1.getKind() != type2.getKind()) { - throw new BugInCF("Should be the same. type: %s p: %s ", type1, type2); - } - return null; - } + } + + /** + * Returns true if {@code atm} has any dependent type annotations. If an annotated type does not + * have a dependent type annotation, then no standardization or viewpoint adaption is performed. + * (This check avoids calling time-intensive methods unless required.) + * + * @param atm a type + * @return true if {@code atm} has any dependent type annotations + */ + private boolean hasDependentType(AnnotatedTypeMirror atm) { + if (atm == null) { + return false; } - - /** - * Returns true if {@code atm} has any dependent type annotations. If an annotated type does not - * have a dependent type annotation, then no standardization or viewpoint adaption is performed. - * (This check avoids calling time-intensive methods unless required.) - * - * @param atm a type - * @return true if {@code atm} has any dependent type annotations - */ - private boolean hasDependentType(AnnotatedTypeMirror atm) { - if (atm == null) { - return false; - } - // This is a test about the type system. - if (!hasDependentAnnotations()) { - return false; - } - // This is a test about this specific type. - return hasDependentTypeScanner.visit(atm); + // This is a test about the type system. + if (!hasDependentAnnotations()) { + return false; } - - /** Returns true if the passed AnnotatedTypeMirror has any dependent type annotations. */ - private final AnnotatedTypeScanner hasDependentTypeScanner = - new SimpleAnnotatedTypeScanner<>( - (type, __) -> { - for (AnnotationMirror annotationMirror : type.getAnnotations()) { - if (isExpressionAnno(annotationMirror)) { - return true; - } - } - return false; - }, - Boolean::logicalOr, - false); + // This is a test about this specific type. + return hasDependentTypeScanner.visit(atm); + } + + /** Returns true if the passed AnnotatedTypeMirror has any dependent type annotations. */ + private final AnnotatedTypeScanner hasDependentTypeScanner = + new SimpleAnnotatedTypeScanner<>( + (type, __) -> { + for (AnnotationMirror annotationMirror : type.getAnnotations()) { + if (isExpressionAnno(annotationMirror)) { + return true; + } + } + return false; + }, + Boolean::logicalOr, + false); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java index cc001dde6a1..95537cf2a42 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java @@ -6,75 +6,71 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.VariableTree; - -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import org.checkerframework.javacutil.TreeUtils; - import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import org.checkerframework.javacutil.TreeUtils; /** * Standardizes Java expressions in annotations and also viewpoint-adapts field accesses. Other * viewpoint adaption is handled in {@link DependentTypesHelper}. */ public class DependentTypesTreeAnnotator extends TreeAnnotator { - private final DependentTypesHelper helper; + private final DependentTypesHelper helper; - public DependentTypesTreeAnnotator( - AnnotatedTypeFactory atypeFactory, DependentTypesHelper helper) { - super(atypeFactory); - this.helper = helper; - } + public DependentTypesTreeAnnotator( + AnnotatedTypeFactory atypeFactory, DependentTypesHelper helper) { + super(atypeFactory); + this.helper = helper; + } - @Override - public Void visitClass(ClassTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - TypeElement ele = TreeUtils.elementFromDeclaration(tree); - helper.atTypeDecl(annotatedTypeMirror, ele); - return super.visitClass(tree, annotatedTypeMirror); - } + @Override + public Void visitClass(ClassTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + TypeElement ele = TreeUtils.elementFromDeclaration(tree); + helper.atTypeDecl(annotatedTypeMirror, ele); + return super.visitClass(tree, annotatedTypeMirror); + } - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror annotatedType) { - helper.atExpression(annotatedType, tree); - return super.visitNewArray(tree, annotatedType); - } + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror annotatedType) { + helper.atExpression(annotatedType, tree); + return super.visitNewArray(tree, annotatedType); + } - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror annotatedType) { - log("DTTA.visitTypeCast(%s, %s)%n", tree, annotatedType); - helper.atExpression(annotatedType, tree); - log( - "DTTA.visitTypeCast(%s, ...) annotatedType=%s; about to call super%n", - tree, annotatedType); - return super.visitTypeCast(tree, annotatedType); - } + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror annotatedType) { + log("DTTA.visitTypeCast(%s, %s)%n", tree, annotatedType); + helper.atExpression(annotatedType, tree); + log("DTTA.visitTypeCast(%s, ...) annotatedType=%s; about to call super%n", tree, annotatedType); + return super.visitTypeCast(tree, annotatedType); + } - @Override - public Void visitVariable(VariableTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - VariableElement ele = TreeUtils.elementFromDeclaration(tree); - helper.atVariableDeclaration(annotatedTypeMirror, tree, ele); - return super.visitVariable(tree, annotatedTypeMirror); - } + @Override + public Void visitVariable(VariableTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + VariableElement ele = TreeUtils.elementFromDeclaration(tree); + helper.atVariableDeclaration(annotatedTypeMirror, tree, ele); + return super.visitVariable(tree, annotatedTypeMirror); + } - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - Element ele = TreeUtils.elementFromUse(tree); - if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { - helper.atVariableDeclaration(annotatedTypeMirror, tree, (VariableElement) ele); - } - return super.visitIdentifier(tree, annotatedTypeMirror); + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + Element ele = TreeUtils.elementFromUse(tree); + if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { + helper.atVariableDeclaration(annotatedTypeMirror, tree, (VariableElement) ele); } + return super.visitIdentifier(tree, annotatedTypeMirror); + } - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - Element ele = TreeUtils.elementFromUse(tree); - if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { - helper.atFieldAccess(type, tree); - } - return super.visitMemberSelect(tree, type); + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + Element ele = TreeUtils.elementFromUse(tree); + if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { + helper.atFieldAccess(type, tree); } + return super.visitMemberSelect(tree, type); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java index 5538421a44d..8e10cbbe15b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java @@ -3,125 +3,123 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; - +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; - /** * Applies the annotations present for a class type parameter onto an AnnotatedTypeVariable. See * {@link TypeParamElementAnnotationApplier} for details. */ public class ClassTypeParamApplier extends TypeParamElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new ClassTypeParamApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * Returns true if element represents a type parameter for a class. - * - * @param type ignored - * @param element the element that might be a type parameter - * @return true if element represents a type parameter for a class - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return element.getKind() == ElementKind.TYPE_PARAMETER - && element.getEnclosingElement() instanceof Symbol.ClassSymbol; - } - - /** The class that holds the type parameter element. */ - private final Symbol.ClassSymbol enclosingClass; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ ClassTypeParamApplier( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element, atypeFactory); - - if (!(element.getEnclosingElement() instanceof Symbol.ClassSymbol)) { - throw new BugInCF( - "TypeParameter not enclosed by class? Type( " - + type - + " ) " - + "Element ( " - + element - + " ) "); - } - - enclosingClass = (Symbol.ClassSymbol) element.getEnclosingElement(); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new ClassTypeParamApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * Returns true if element represents a type parameter for a class. + * + * @param type ignored + * @param element the element that might be a type parameter + * @return true if element represents a type parameter for a class + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return element.getKind() == ElementKind.TYPE_PARAMETER + && element.getEnclosingElement() instanceof Symbol.ClassSymbol; + } + + /** The class that holds the type parameter element. */ + private final Symbol.ClassSymbol enclosingClass; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ ClassTypeParamApplier( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element, atypeFactory); + + if (!(element.getEnclosingElement() instanceof Symbol.ClassSymbol)) { + throw new BugInCF( + "TypeParameter not enclosed by class? Type( " + + type + + " ) " + + "Element ( " + + element + + " ) "); } - /** - * Returns TargetType.CLASS_TYPE_PARAMETER. - * - * @return TargetType.CLASS_TYPE_PARAMETER - */ - @Override - protected TargetType lowerBoundTarget() { - return TargetType.CLASS_TYPE_PARAMETER; - } - - /** - * Returns TargetType.CLASS_TYPE_PARAMETER_BOUND. - * - * @return TargetType.CLASS_TYPE_PARAMETER_BOUND - */ - @Override - protected TargetType upperBoundTarget() { - return TargetType.CLASS_TYPE_PARAMETER_BOUND; - } - - /** - * Returns the index of element in the type parameter list of its enclosing class. - * - * @return the index of element in the type parameter list of its enclosing class - */ - @Override - public int getElementIndex() { - return enclosingClass.getTypeParameters().indexOf(element); - } - - /** The valid targets. */ - private static final TargetType[] validTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the raw type attributes of the enclosing class. - * - * @return the raw type attributes of the enclosing class - */ - @Override - protected Iterable getRawTypeAttributes() { - return enclosingClass.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } + enclosingClass = (Symbol.ClassSymbol) element.getEnclosingElement(); + } + + /** + * Returns TargetType.CLASS_TYPE_PARAMETER. + * + * @return TargetType.CLASS_TYPE_PARAMETER + */ + @Override + protected TargetType lowerBoundTarget() { + return TargetType.CLASS_TYPE_PARAMETER; + } + + /** + * Returns TargetType.CLASS_TYPE_PARAMETER_BOUND. + * + * @return TargetType.CLASS_TYPE_PARAMETER_BOUND + */ + @Override + protected TargetType upperBoundTarget() { + return TargetType.CLASS_TYPE_PARAMETER_BOUND; + } + + /** + * Returns the index of element in the type parameter list of its enclosing class. + * + * @return the index of element in the type parameter list of its enclosing class + */ + @Override + public int getElementIndex() { + return enclosingClass.getTypeParameters().indexOf(element); + } + + /** The valid targets. */ + private static final TargetType[] validTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the raw type attributes of the enclosing class. + * + * @return the raw type attributes of the enclosing class + */ + @Override + protected Iterable getRawTypeAttributes() { + return enclosingClass.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java index b1bc405f9e3..c06c2e0e435 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java @@ -7,7 +7,17 @@ import com.sun.tools.javac.code.TypeAnnotationPosition; import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind; - +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -25,19 +35,6 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; - /** * Utility methods for adding the annotations that are stored in an Element to the type that * represents that element (or a use of that Element). This class also contains package private @@ -45,591 +42,578 @@ */ public class ElementAnnotationUtil { - /** - * For each type/element pair, add all of the annotations stored in Element to type. See apply - * for more details. - * - * @param types the types to which we wish to apply element annotations - * @param elements the elements that may contain annotations to apply. elements.size must == - * types.size - * @param typeFactory the type factory used to create the AnnotatedTypeMirrors contained by - * types - */ - public static void applyAllElementAnnotations( - List types, - List elements, - AnnotatedTypeFactory typeFactory) { - if (types.size() != elements.size()) { - throw new BugInCF( - "Number of types and elements don't match. " - + "types ( " - + StringsPlume.join(", ", types) - + " ) " - + "element ( " - + StringsPlume.join(", ", elements) - + " ) "); - } - - for (int i = 0; i < types.size(); i++) { - ElementAnnotationApplier.apply(types.get(i), elements.get(i), typeFactory); - } + /** + * For each type/element pair, add all of the annotations stored in Element to type. See apply for + * more details. + * + * @param types the types to which we wish to apply element annotations + * @param elements the elements that may contain annotations to apply. elements.size must == + * types.size + * @param typeFactory the type factory used to create the AnnotatedTypeMirrors contained by types + */ + public static void applyAllElementAnnotations( + List types, + List elements, + AnnotatedTypeFactory typeFactory) { + if (types.size() != elements.size()) { + throw new BugInCF( + "Number of types and elements don't match. " + + "types ( " + + StringsPlume.join(", ", types) + + " ) " + + "element ( " + + StringsPlume.join(", ", elements) + + " ) "); } - /** - * When a declaration annotation is an alias for a type annotation, then the Checker Framework - * may move the annotation before replacing it by the canonical version. - * - *

If the annotation is one of the Checker Framework compatibility annotations, for example - * org.checkerframework.checker.nullness.compatqual.NonNullDecl, then it is interpreted as a - * type annotation in the same location. - * - * @param type the type to annotate - * @param annotations the annotations to add - */ - @SuppressWarnings("interning:not.interned") // AST node comparison - static void addDeclarationAnnotationsFromElement( - AnnotatedTypeMirror type, List annotations) { - // The code here should be similar to - // org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable - // However, in this version, note that there is no check whether the annotation is a type - // use annotation, using `AnnotationUtils.isTypeUseAnnotation`. - // The declaration annotation could also be a type use annotation, but it appears as a - // declaration annotation on the element, so treat it as a declaration annotation, unless it - // is a Checker Framework annotation. - AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type); - if (innerType != type) { - for (AnnotationMirror anno : annotations) { - if (AnnotationUtils.annotationName(anno).startsWith("org.checkerframework")) { - // Always treat Checker Framework annotations as type annotations, even if they - // are declaration annotations. - innerType.addAnnotation(anno); - } else { - // Declaration annotations apply to the outer type. - type.addAnnotation(anno); - } - } + for (int i = 0; i < types.size(); i++) { + ElementAnnotationApplier.apply(types.get(i), elements.get(i), typeFactory); + } + } + + /** + * When a declaration annotation is an alias for a type annotation, then the Checker Framework may + * move the annotation before replacing it by the canonical version. + * + *

If the annotation is one of the Checker Framework compatibility annotations, for example + * org.checkerframework.checker.nullness.compatqual.NonNullDecl, then it is interpreted as a type + * annotation in the same location. + * + * @param type the type to annotate + * @param annotations the annotations to add + */ + @SuppressWarnings("interning:not.interned") // AST node comparison + static void addDeclarationAnnotationsFromElement( + AnnotatedTypeMirror type, List annotations) { + // The code here should be similar to + // org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable + // However, in this version, note that there is no check whether the annotation is a type + // use annotation, using `AnnotationUtils.isTypeUseAnnotation`. + // The declaration annotation could also be a type use annotation, but it appears as a + // declaration annotation on the element, so treat it as a declaration annotation, unless it + // is a Checker Framework annotation. + AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type); + if (innerType != type) { + for (AnnotationMirror anno : annotations) { + if (AnnotationUtils.annotationName(anno).startsWith("org.checkerframework")) { + // Always treat Checker Framework annotations as type annotations, even if they + // are declaration annotations. + innerType.addAnnotation(anno); } else { - type.addAnnotations(annotations); + // Declaration annotations apply to the outer type. + type.addAnnotation(anno); } + } + } else { + type.addAnnotations(annotations); } - - /** - * Does expectedValues contain enumValue. This is just a linear search. - * - * @param enumValue value to search for, a needle - * @param expectedValues values to search through, a haystack - * @return true if enumValue is in expectedValues, false otherwise - */ - static boolean contains(Object enumValue, Object[] expectedValues) { - for (Object expected : expectedValues) { - if (enumValue.equals(expected)) { - return true; - } - } - - return false; + } + + /** + * Does expectedValues contain enumValue. This is just a linear search. + * + * @param enumValue value to search for, a needle + * @param expectedValues values to search through, a haystack + * @return true if enumValue is in expectedValues, false otherwise + */ + static boolean contains(Object enumValue, Object[] expectedValues) { + for (Object expected : expectedValues) { + if (enumValue.equals(expected)) { + return true; + } } - /** - * Use a map to partition annotations with the given TargetTypes into Lists, where each target - * type is a key in the output map. Any annotation that does not have one of these target types - * will be added to unmatched - * - * @param annos the collection of annotations to partition - * @param unmatched a list to add annotations with unmatched target types to - * @param targetTypes a list of target types to partition annos with - * @return a map from targetType → List of Annotations that have that targetType - */ - static Map> partitionByTargetType( - Collection annos, - List unmatched, - TargetType... targetTypes) { - Map> targetTypeToAnnos = new HashMap<>(); - for (TargetType targetType : targetTypes) { - targetTypeToAnnos.put(targetType, new ArrayList<>()); - } - - for (TypeCompound anno : annos) { - List annoSet = targetTypeToAnnos.get(anno.getPosition().type); - if (annoSet != null) { - annoSet.add(anno); - } else if (unmatched != null) { - unmatched.add(anno); - } - } - - return targetTypeToAnnos; + return false; + } + + /** + * Use a map to partition annotations with the given TargetTypes into Lists, where each target + * type is a key in the output map. Any annotation that does not have one of these target types + * will be added to unmatched + * + * @param annos the collection of annotations to partition + * @param unmatched a list to add annotations with unmatched target types to + * @param targetTypes a list of target types to partition annos with + * @return a map from targetType → List of Annotations that have that targetType + */ + static Map> partitionByTargetType( + Collection annos, List unmatched, TargetType... targetTypes) { + Map> targetTypeToAnnos = new HashMap<>(); + for (TargetType targetType : targetTypes) { + targetTypeToAnnos.put(targetType, new ArrayList<>()); } - /** - * A class used solely to annotate wildcards from Element annotations. Instances of - * WildcardBoundAnnos are used to aggregate ALL annotations for a given Wildcard and then apply - * them all at once in order to resolve the annotations in front of unbound wildcards. - * - *

Wildcard annotations are applied as follows: - * - *

    - *
  • a) If an Annotation is in front of a extends or super bounded wildcard, it applies to - * the bound that is NOT explicitly present. e.g. - *
    {@code
    -     * <@A ? extends Object> -- @A is placed on the super bound (Void)
    -     * <@B ? super CharSequence> -- @B is placed on the extends bound (probably Object)
    -     * }
    - *
  • b) If an Annotation is on a bound, it applies to that bound. E.g. - *
    {@code
    -     *  -- @A is placed on the extends bound (Object)
    -     *  -- @B is placed on the super bound (CharSequence)
    -     * }
    - *
  • c) If an Annotation is on an unbounded wildcard there are two subcases. - *
      - *
    • c.1 The user wrote the annotation explicitly -- these annotations apply to both - * bounds e.g. the user wrote - *
      {@code
      -     * <@C ?> -- the annotation is placed on the extends/super bounds
      -     * }
      - *
    • c.2 Previous calls to getAnnotatedType have annotated this wildcard with BOTH - * bounds e.g. the user wrote {@code } but the checker framework added {@code <@C - * ? extends @D Object>} to the corresponding element. - *
      -     *             {@code  -- @C is placed on the lower bound and @D is placed on the upper bound
      -     *          This case is treated just like annotations in cases a/b.
      -     * }
      - *
    - *
- */ - private static final class WildcardBoundAnnos { - /** The wildcard type. */ - public final AnnotatedWildcardType wildcard; - - /** The upper bound annotations. */ - public final AnnotationMirrorSet upperBoundAnnos; - - /** The lower bound annotations. */ - public final AnnotationMirrorSet lowerBoundAnnos; - - // indicates that this is an annotation in front of an unbounded wildcard - // e.g. < @A ? > - // For each annotation in this set, if there is no annotation in upperBoundAnnos - // that is in the same hierarchy then the annotation will be applied to both bounds - // otherwise the annotation applies to the lower bound only - public final AnnotationMirrorSet possiblyBoth; - - /** Whether or not wildcard has an explicit super bound. */ - private final boolean isSuperBounded; - - /** Whether or not wildcard has NO explicit bound whatsoever. */ - private final boolean isUnbounded; - - /** - * Creates a new WildcardBoundAnnos from the given wildcard type, with no upper- or - * lower-bound annotations. - * - * @param wildcard the wildcard type - */ - WildcardBoundAnnos(AnnotatedWildcardType wildcard) { - this.wildcard = wildcard; - this.upperBoundAnnos = new AnnotationMirrorSet(); - this.lowerBoundAnnos = new AnnotationMirrorSet(); - this.possiblyBoth = new AnnotationMirrorSet(); - - this.isSuperBounded = AnnotatedTypes.hasExplicitSuperBound(wildcard); - this.isUnbounded = AnnotatedTypes.hasNoExplicitBound(wildcard); - } - - void addAnnotation(TypeCompound anno) { - // if the typepath entry ends in Wildcard then the annotation should go on a bound - // otherwise, the annotation is in front of the wildcard - // e.g. @HERE ? extends Object - boolean isInFrontOfWildcard = - anno.getPosition().location.last() != TypePathEntry.WILDCARD; - if (isInFrontOfWildcard && isUnbounded) { - possiblyBoth.add(anno); - } else { - // A TypePathEntry of WILDCARD indicates that it is placed on the bound - // use the type of the wildcard bound to determine which set to put it in - - if (isInFrontOfWildcard) { - if (isSuperBounded) { - upperBoundAnnos.add(anno); - } else { - lowerBoundAnnos.add(anno); - } - } else { // it's on the bound - if (isSuperBounded) { - lowerBoundAnnos.add(anno); - } else { - upperBoundAnnos.add(anno); - } - } - } - } - - /** - * Apply the annotations to wildcard according to the rules outlined in the comment at the - * beginning of this class. - */ - void apply() { - AnnotatedTypeMirror extendsBound = wildcard.getExtendsBound(); - AnnotatedTypeMirror superBound = wildcard.getSuperBound(); - - for (AnnotationMirror extAnno : upperBoundAnnos) { - extendsBound.addAnnotation(extAnno); - } - for (AnnotationMirror supAnno : lowerBoundAnnos) { - superBound.addAnnotation(supAnno); - } - - for (AnnotationMirror anno : possiblyBoth) { - superBound.addAnnotation(anno); - - // This will be false if we've defaulted the bounds and are reading them again. - // In that case, we will have already created an annotation for the extends bound - // that should be honored and NOT overwritten. - extendsBound.addMissingAnnotation(anno); - } - } + for (TypeCompound anno : annos) { + List annoSet = targetTypeToAnnos.get(anno.getPosition().type); + if (annoSet != null) { + annoSet.add(anno); + } else if (unmatched != null) { + unmatched.add(anno); + } } + return targetTypeToAnnos; + } + + /** + * A class used solely to annotate wildcards from Element annotations. Instances of + * WildcardBoundAnnos are used to aggregate ALL annotations for a given Wildcard and then apply + * them all at once in order to resolve the annotations in front of unbound wildcards. + * + *

Wildcard annotations are applied as follows: + * + *

    + *
  • a) If an Annotation is in front of a extends or super bounded wildcard, it applies to the + * bound that is NOT explicitly present. e.g. + *
    {@code
    +   * <@A ? extends Object> -- @A is placed on the super bound (Void)
    +   * <@B ? super CharSequence> -- @B is placed on the extends bound (probably Object)
    +   * }
    + *
  • b) If an Annotation is on a bound, it applies to that bound. E.g. + *
    {@code
    +   *  -- @A is placed on the extends bound (Object)
    +   *  -- @B is placed on the super bound (CharSequence)
    +   * }
    + *
  • c) If an Annotation is on an unbounded wildcard there are two subcases. + *
      + *
    • c.1 The user wrote the annotation explicitly -- these annotations apply to both + * bounds e.g. the user wrote + *
      {@code
      +   * <@C ?> -- the annotation is placed on the extends/super bounds
      +   * }
      + *
    • c.2 Previous calls to getAnnotatedType have annotated this wildcard with BOTH + * bounds e.g. the user wrote {@code } but the checker framework added {@code <@C ? + * extends @D Object>} to the corresponding element. + *
      +   *             {@code  -- @C is placed on the lower bound and @D is placed on the upper bound
      +   *          This case is treated just like annotations in cases a/b.
      +   * }
      + *
    + *
+ */ + private static final class WildcardBoundAnnos { + /** The wildcard type. */ + public final AnnotatedWildcardType wildcard; + + /** The upper bound annotations. */ + public final AnnotationMirrorSet upperBoundAnnos; + + /** The lower bound annotations. */ + public final AnnotationMirrorSet lowerBoundAnnos; + + // indicates that this is an annotation in front of an unbounded wildcard + // e.g. < @A ? > + // For each annotation in this set, if there is no annotation in upperBoundAnnos + // that is in the same hierarchy then the annotation will be applied to both bounds + // otherwise the annotation applies to the lower bound only + public final AnnotationMirrorSet possiblyBoth; + + /** Whether or not wildcard has an explicit super bound. */ + private final boolean isSuperBounded; + + /** Whether or not wildcard has NO explicit bound whatsoever. */ + private final boolean isUnbounded; + /** - * TypeCompounds are implementations of AnnotationMirror that are stored on Elements. Each type - * compound has a TypeAnnotationPosition which identifies, relative to the "root" of a type, - * where an annotation should be placed. This method adds all of the given TypeCompounds to the - * correct location on type by interpreting the TypeAnnotationPosition. - * - *

Note: We handle all of the Element annotations on a type at once because we need to - * identify whether or not the element annotation in front of an unbound wildcard (e.g. {@code - * <@HERE ?>}) should apply to only the super bound or both the super bound and the extends - * bound. + * Creates a new WildcardBoundAnnos from the given wildcard type, with no upper- or lower-bound + * annotations. * - * @see org.checkerframework.framework.util.element.ElementAnnotationUtil.WildcardBoundAnnos - * @param type the type in which annos should be placed - * @param annos all of the element annotations, TypeCompounds, for type + * @param wildcard the wildcard type */ - static void annotateViaTypeAnnoPosition( - AnnotatedTypeMirror type, Collection annos) - throws UnexpectedAnnotationLocationException { - IdentityHashMap wildcardToAnnos = - new IdentityHashMap<>(); - for (TypeCompound anno : annos) { - AnnotatedTypeMirror target = - getTypeAtLocation(type, anno.position.location, anno, false); - if (target.getKind() == TypeKind.WILDCARD) { - addWildcardToBoundMap((AnnotatedWildcardType) target, anno, wildcardToAnnos); - } else { - target.addAnnotation(anno); - } - } + WildcardBoundAnnos(AnnotatedWildcardType wildcard) { + this.wildcard = wildcard; + this.upperBoundAnnos = new AnnotationMirrorSet(); + this.lowerBoundAnnos = new AnnotationMirrorSet(); + this.possiblyBoth = new AnnotationMirrorSet(); + + this.isSuperBounded = AnnotatedTypes.hasExplicitSuperBound(wildcard); + this.isUnbounded = AnnotatedTypes.hasNoExplicitBound(wildcard); + } - for (WildcardBoundAnnos wildcardAnnos : wildcardToAnnos.values()) { - wildcardAnnos.apply(); + void addAnnotation(TypeCompound anno) { + // if the typepath entry ends in Wildcard then the annotation should go on a bound + // otherwise, the annotation is in front of the wildcard + // e.g. @HERE ? extends Object + boolean isInFrontOfWildcard = anno.getPosition().location.last() != TypePathEntry.WILDCARD; + if (isInFrontOfWildcard && isUnbounded) { + possiblyBoth.add(anno); + } else { + // A TypePathEntry of WILDCARD indicates that it is placed on the bound + // use the type of the wildcard bound to determine which set to put it in + + if (isInFrontOfWildcard) { + if (isSuperBounded) { + upperBoundAnnos.add(anno); + } else { + lowerBoundAnnos.add(anno); + } + } else { // it's on the bound + if (isSuperBounded) { + lowerBoundAnnos.add(anno); + } else { + upperBoundAnnos.add(anno); + } } + } } /** - * Creates an entry in wildcardToAnnos for wildcard if one does not already exists. Adds anno to - * the WildcardBoundAnnos object for wildcard. + * Apply the annotations to wildcard according to the rules outlined in the comment at the + * beginning of this class. */ - private static void addWildcardToBoundMap( - AnnotatedWildcardType wildcard, - TypeCompound anno, - Map wildcardToAnnos) { - WildcardBoundAnnos boundAnnos = - wildcardToAnnos.computeIfAbsent(wildcard, WildcardBoundAnnos::new); - boundAnnos.addAnnotation(anno); + void apply() { + AnnotatedTypeMirror extendsBound = wildcard.getExtendsBound(); + AnnotatedTypeMirror superBound = wildcard.getSuperBound(); + + for (AnnotationMirror extAnno : upperBoundAnnos) { + extendsBound.addAnnotation(extAnno); + } + for (AnnotationMirror supAnno : lowerBoundAnnos) { + superBound.addAnnotation(supAnno); + } + + for (AnnotationMirror anno : possiblyBoth) { + superBound.addAnnotation(anno); + + // This will be false if we've defaulted the bounds and are reading them again. + // In that case, we will have already created an annotation for the extends bound + // that should be honored and NOT overwritten. + extendsBound.addMissingAnnotation(anno); + } } - - /** - * Returns true if the typeCompound is a primary annotation for the type it targets (or lower - * bound if this is a type variable or wildcard ). If you think of a type as a tree-like - * structure then a nested type any type that is not the root. E.g. {@code @T List< @N - * String>}, @T is on a top-level NON-nested type where as the annotation @N is on a nested - * type. - * - * @param typeCompound the type compound to inspect - * @return true if typeCompound is placed on a nested type, false otherwise - */ - static boolean isOnComponentType(Attribute.TypeCompound typeCompound) { - return !typeCompound.position.location.isEmpty(); + } + + /** + * TypeCompounds are implementations of AnnotationMirror that are stored on Elements. Each type + * compound has a TypeAnnotationPosition which identifies, relative to the "root" of a type, where + * an annotation should be placed. This method adds all of the given TypeCompounds to the correct + * location on type by interpreting the TypeAnnotationPosition. + * + *

Note: We handle all of the Element annotations on a type at once because we need to identify + * whether or not the element annotation in front of an unbound wildcard (e.g. {@code <@HERE ?>}) + * should apply to only the super bound or both the super bound and the extends bound. + * + * @see org.checkerframework.framework.util.element.ElementAnnotationUtil.WildcardBoundAnnos + * @param type the type in which annos should be placed + * @param annos all of the element annotations, TypeCompounds, for type + */ + static void annotateViaTypeAnnoPosition(AnnotatedTypeMirror type, Collection annos) + throws UnexpectedAnnotationLocationException { + IdentityHashMap wildcardToAnnos = + new IdentityHashMap<>(); + for (TypeCompound anno : annos) { + AnnotatedTypeMirror target = getTypeAtLocation(type, anno.position.location, anno, false); + if (target.getKind() == TypeKind.WILDCARD) { + addWildcardToBoundMap((AnnotatedWildcardType) target, anno, wildcardToAnnos); + } else { + target.addAnnotation(anno); + } } - /** - * See the Type Annotation Specification on bounds - * (https://checkerframework.org/jsr308/specification/java-annotation-design.html). - * - *

TypeAnnotationPositions have bound indices when they represent an upper bound on a - * TypeVariable. The index 0 ALWAYS refers to the superclass type. If that supertype is implied - * to be Object (because we didn't specify an extends) then the actual types will be offset by 1 - * (because index 0 is ALWAYS a class. - * - *

Therefore, These indices will be offset by -1 if the first type in the bound is an - * interface which implies the specified type itself is an interface. - * - *

Reminder: There will only be multiple bound types if the upperBound is an intersection. - * - * @param upperBoundTypes the list of upperBounds for the type with bound positions you wish to - * offset - * @return the bound offset for all TypeAnnotationPositions of TypeCompounds targeting these - * bounds - */ - static int getBoundIndexOffset(List upperBoundTypes) { - final int boundIndexOffset; - if (((Type) upperBoundTypes.get(0).getUnderlyingType()).isInterface()) { - boundIndexOffset = -1; - } else { - boundIndexOffset = 0; - } - - return boundIndexOffset; + for (WildcardBoundAnnos wildcardAnnos : wildcardToAnnos.values()) { + wildcardAnnos.apply(); } - - /** - * Overload of getTypeAtLocation with default values null/false for the annotation and array - * component flag, to make usage easier. Default visibility to allow usage within package. - */ - static AnnotatedTypeMirror getTypeAtLocation( - AnnotatedTypeMirror type, List location) - throws UnexpectedAnnotationLocationException { - return getTypeAtLocation(type, location, null, false); + } + + /** + * Creates an entry in wildcardToAnnos for wildcard if one does not already exists. Adds anno to + * the WildcardBoundAnnos object for wildcard. + */ + private static void addWildcardToBoundMap( + AnnotatedWildcardType wildcard, + TypeCompound anno, + Map wildcardToAnnos) { + WildcardBoundAnnos boundAnnos = + wildcardToAnnos.computeIfAbsent(wildcard, WildcardBoundAnnos::new); + boundAnnos.addAnnotation(anno); + } + + /** + * Returns true if the typeCompound is a primary annotation for the type it targets (or lower + * bound if this is a type variable or wildcard ). If you think of a type as a tree-like structure + * then a nested type any type that is not the root. E.g. {@code @T List< @N String>}, @T is on a + * top-level NON-nested type where as the annotation @N is on a nested type. + * + * @param typeCompound the type compound to inspect + * @return true if typeCompound is placed on a nested type, false otherwise + */ + static boolean isOnComponentType(Attribute.TypeCompound typeCompound) { + return !typeCompound.position.location.isEmpty(); + } + + /** + * See the Type Annotation Specification on bounds + * (https://checkerframework.org/jsr308/specification/java-annotation-design.html). + * + *

TypeAnnotationPositions have bound indices when they represent an upper bound on a + * TypeVariable. The index 0 ALWAYS refers to the superclass type. If that supertype is implied to + * be Object (because we didn't specify an extends) then the actual types will be offset by 1 + * (because index 0 is ALWAYS a class. + * + *

Therefore, These indices will be offset by -1 if the first type in the bound is an interface + * which implies the specified type itself is an interface. + * + *

Reminder: There will only be multiple bound types if the upperBound is an intersection. + * + * @param upperBoundTypes the list of upperBounds for the type with bound positions you wish to + * offset + * @return the bound offset for all TypeAnnotationPositions of TypeCompounds targeting these + * bounds + */ + static int getBoundIndexOffset(List upperBoundTypes) { + final int boundIndexOffset; + if (((Type) upperBoundTypes.get(0).getUnderlyingType()).isInterface()) { + boundIndexOffset = -1; + } else { + boundIndexOffset = 0; } - /** - * Given a TypePath into a type, return the component type that is located at the end of the - * TypePath. - * - * @param type a type containing the type specified by location - * @param location a type path into type - * @param anno an annotation to be applied to the inner types of a declared type if the declared - * type is itself a component type of an array - * @param isComponentTypeOfArray indicates whether the type under analysis is a component type - * of some array type - * @return the type specified by location - */ - private static AnnotatedTypeMirror getTypeAtLocation( - AnnotatedTypeMirror type, - List location, - @Nullable TypeCompound anno, - boolean isComponentTypeOfArray) - throws UnexpectedAnnotationLocationException { - if (location.isEmpty() && type.getKind() != TypeKind.DECLARED) { - // An annotation with an empty type path on a declared type applies to the outermost - // enclosing type. This logic is handled together with non-empty type paths in - // getLocationTypeADT. - // For other kinds of types, no work is required for an empty type path. - return type; - } - switch (type.getKind()) { - case NULL: - return getLocationTypeANT((AnnotatedNullType) type, location); - case DECLARED: - return getLocationTypeADT( - (AnnotatedDeclaredType) type, location, anno, isComponentTypeOfArray); - case WILDCARD: - return getLocationTypeAWT((AnnotatedWildcardType) type, location); - case ARRAY: - return getLocationTypeAAT((AnnotatedArrayType) type, location, anno); - case UNION: - return getLocationTypeAUT((AnnotatedUnionType) type, location); - case INTERSECTION: - return getLocationTypeAIT((AnnotatedIntersectionType) type, location); - default: - // Raise an error for all other types below. - } - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getTypeAtLocation: " - + "unexpected annotation with location found for type: %s (kind: %s) location: ", - type, type.getKind(), location); + return boundIndexOffset; + } + + /** + * Overload of getTypeAtLocation with default values null/false for the annotation and array + * component flag, to make usage easier. Default visibility to allow usage within package. + */ + static AnnotatedTypeMirror getTypeAtLocation( + AnnotatedTypeMirror type, List location) + throws UnexpectedAnnotationLocationException { + return getTypeAtLocation(type, location, null, false); + } + + /** + * Given a TypePath into a type, return the component type that is located at the end of the + * TypePath. + * + * @param type a type containing the type specified by location + * @param location a type path into type + * @param anno an annotation to be applied to the inner types of a declared type if the declared + * type is itself a component type of an array + * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of + * some array type + * @return the type specified by location + */ + private static AnnotatedTypeMirror getTypeAtLocation( + AnnotatedTypeMirror type, + List location, + @Nullable TypeCompound anno, + boolean isComponentTypeOfArray) + throws UnexpectedAnnotationLocationException { + if (location.isEmpty() && type.getKind() != TypeKind.DECLARED) { + // An annotation with an empty type path on a declared type applies to the outermost + // enclosing type. This logic is handled together with non-empty type paths in + // getLocationTypeADT. + // For other kinds of types, no work is required for an empty type path. + return type; + } + switch (type.getKind()) { + case NULL: + return getLocationTypeANT((AnnotatedNullType) type, location); + case DECLARED: + return getLocationTypeADT( + (AnnotatedDeclaredType) type, location, anno, isComponentTypeOfArray); + case WILDCARD: + return getLocationTypeAWT((AnnotatedWildcardType) type, location); + case ARRAY: + return getLocationTypeAAT((AnnotatedArrayType) type, location, anno); + case UNION: + return getLocationTypeAUT((AnnotatedUnionType) type, location); + case INTERSECTION: + return getLocationTypeAIT((AnnotatedIntersectionType) type, location); + default: + // Raise an error for all other types below. + } + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getTypeAtLocation: " + + "unexpected annotation with location found for type: %s (kind: %s) location: ", + type, type.getKind(), location); + } + + /** + * Given a TypePath into a declared type, return the component type that is located at the end of + * the TypePath. + * + * @param type a type containing the type specified by location + * @param location a type path into type + * @param anno an annotation to be applied to the inner types of the declared type if the declared + * type is itself a component type of an array + * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of + * some array type + * @return the type specified by location + */ + @SuppressWarnings("JdkObsolete") // error is issued on every operation, must suppress here + private static AnnotatedTypeMirror getLocationTypeADT( + AnnotatedDeclaredType type, + List location, + TypeCompound anno, + boolean isComponentTypeOfArray) + throws UnexpectedAnnotationLocationException { + // List order by outermost type to innermost type. + ArrayDeque outerToInner = new ArrayDeque<>(); + AnnotatedDeclaredType enclosing = type; + while (enclosing != null) { + outerToInner.addFirst(enclosing); + enclosing = enclosing.getEnclosingType(); } - /** - * Given a TypePath into a declared type, return the component type that is located at the end - * of the TypePath. - * - * @param type a type containing the type specified by location - * @param location a type path into type - * @param anno an annotation to be applied to the inner types of the declared type if the - * declared type is itself a component type of an array - * @param isComponentTypeOfArray indicates whether the type under analysis is a component type - * of some array type - * @return the type specified by location - */ - @SuppressWarnings("JdkObsolete") // error is issued on every operation, must suppress here - private static AnnotatedTypeMirror getLocationTypeADT( - AnnotatedDeclaredType type, - List location, - TypeCompound anno, - boolean isComponentTypeOfArray) - throws UnexpectedAnnotationLocationException { - // List order by outermost type to innermost type. - ArrayDeque outerToInner = new ArrayDeque<>(); - AnnotatedDeclaredType enclosing = type; - while (enclosing != null) { - outerToInner.addFirst(enclosing); - enclosing = enclosing.getEnclosingType(); - } - - // If the AnnotatedDeclaredType is a component of an array type, then apply anno to all - // possible inner types. - // NOTE: This workaround can be removed once - // https://bugs.openjdk.org/browse/JDK-8208470 is fixed - // The number of enclosing types is outerToInner.size() - 1; there only is - // work to do if outerToInner contains more than one element. - if (anno != null - && isComponentTypeOfArray - && location.isEmpty() - && outerToInner.size() > 1) { - ArrayDeque innerTypes = new ArrayDeque<>(outerToInner); - innerTypes.removeFirst(); - while (!innerTypes.isEmpty()) { - innerTypes.removeFirst().addAnnotation(anno); - } - } - - // Create a linked list of the location, so removing the first element is easier. - // Also, the tail() operation wouldn't work with a Deque. - @SuppressWarnings("JdkObsolete") - LinkedList tailOfLocations = new LinkedList<>(location); - boolean error = false; - while (!tailOfLocations.isEmpty()) { - TypePathEntry currentLocation = tailOfLocations.removeFirst(); - switch (currentLocation.tag) { - case INNER_TYPE: - outerToInner.removeFirst(); - break; - case TYPE_ARGUMENT: - AnnotatedDeclaredType innerType = outerToInner.getFirst(); - if (currentLocation.arg < innerType.getTypeArguments().size()) { - AnnotatedTypeMirror typeArg = - innerType.getTypeArguments().get(currentLocation.arg); - return getTypeAtLocation(typeArg, tailOfLocations); - } else { - error = true; - break; - } - default: - error = true; - } - if (error) { - break; - } - } + // If the AnnotatedDeclaredType is a component of an array type, then apply anno to all + // possible inner types. + // NOTE: This workaround can be removed once + // https://bugs.openjdk.org/browse/JDK-8208470 is fixed + // The number of enclosing types is outerToInner.size() - 1; there only is + // work to do if outerToInner contains more than one element. + if (anno != null && isComponentTypeOfArray && location.isEmpty() && outerToInner.size() > 1) { + ArrayDeque innerTypes = new ArrayDeque<>(outerToInner); + innerTypes.removeFirst(); + while (!innerTypes.isEmpty()) { + innerTypes.removeFirst().addAnnotation(anno); + } + } - if (outerToInner.isEmpty() || error) { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocationTypeADT: invalid location %s for type: %s", - location, type); - } + // Create a linked list of the location, so removing the first element is easier. + // Also, the tail() operation wouldn't work with a Deque. + @SuppressWarnings("JdkObsolete") + LinkedList tailOfLocations = new LinkedList<>(location); + boolean error = false; + while (!tailOfLocations.isEmpty()) { + TypePathEntry currentLocation = tailOfLocations.removeFirst(); + switch (currentLocation.tag) { + case INNER_TYPE: + outerToInner.removeFirst(); + break; + case TYPE_ARGUMENT: + AnnotatedDeclaredType innerType = outerToInner.getFirst(); + if (currentLocation.arg < innerType.getTypeArguments().size()) { + AnnotatedTypeMirror typeArg = innerType.getTypeArguments().get(currentLocation.arg); + return getTypeAtLocation(typeArg, tailOfLocations); + } else { + error = true; + break; + } + default: + error = true; + } + if (error) { + break; + } + } - return outerToInner.getFirst(); + if (outerToInner.isEmpty() || error) { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocationTypeADT: invalid location %s for type: %s", + location, type); } - private static AnnotatedTypeMirror getLocationTypeANT( - AnnotatedNullType type, List location) - throws UnexpectedAnnotationLocationException { - if (location.size() == 1 && location.get(0).tag == TypePathEntryKind.TYPE_ARGUMENT) { - return type; - } + return outerToInner.getFirst(); + } - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocationTypeANT: " + "invalid location %s for type: %s ", - location, type); + private static AnnotatedTypeMirror getLocationTypeANT( + AnnotatedNullType type, List location) + throws UnexpectedAnnotationLocationException { + if (location.size() == 1 && location.get(0).tag == TypePathEntryKind.TYPE_ARGUMENT) { + return type; } - private static AnnotatedTypeMirror getLocationTypeAWT( - final AnnotatedWildcardType type, List location) - throws UnexpectedAnnotationLocationException { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocationTypeANT: " + "invalid location %s for type: %s ", + location, type); + } - // the last step into the Wildcard type is handled in WildcardToBoundAnnos.addAnnotation - if (location.size() == 1) { - return type; - } + private static AnnotatedTypeMirror getLocationTypeAWT( + final AnnotatedWildcardType type, List location) + throws UnexpectedAnnotationLocationException { - if (!location.isEmpty() - && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.WILDCARD) { - if (AnnotatedTypes.hasExplicitExtendsBound(type)) { - return getTypeAtLocation(type.getExtendsBound(), tail(location)); - } else if (AnnotatedTypes.hasExplicitSuperBound(type)) { - return getTypeAtLocation(type.getSuperBound(), tail(location)); - } else { - return getTypeAtLocation(type.getExtendsBound(), tail(location)); - } - } else { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocationTypeAWT: " - + "invalid location %s for type: %s ", - location, type); - } + // the last step into the Wildcard type is handled in WildcardToBoundAnnos.addAnnotation + if (location.size() == 1) { + return type; } - /** - * When we have an (e.g. @Odd int @NonNull []) the type-annotation position of the array - * annotation (@NonNull) is really the outermost type in the TypeAnnotationPosition and it will - * NOT have TypePathEntryKind.ARRAY at the end of its position. The position of the component - * type (@Odd) is considered deeper in the type and therefore has the TypePathEntryKind.ARRAY in - * its position. - */ - private static AnnotatedTypeMirror getLocationTypeAAT( - AnnotatedArrayType type, - List location, - TypeCompound anno) - throws UnexpectedAnnotationLocationException { - if (location.size() >= 1 - && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) { - AnnotatedTypeMirror comptype = type.getComponentType(); - return getTypeAtLocation(comptype, tail(location), anno, true); - } else { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.annotateAAT: " + "invalid location %s for type: %s ", - location, type); - } + if (!location.isEmpty() + && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.WILDCARD) { + if (AnnotatedTypes.hasExplicitExtendsBound(type)) { + return getTypeAtLocation(type.getExtendsBound(), tail(location)); + } else if (AnnotatedTypes.hasExplicitSuperBound(type)) { + return getTypeAtLocation(type.getSuperBound(), tail(location)); + } else { + return getTypeAtLocation(type.getExtendsBound(), tail(location)); + } + } else { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocationTypeAWT: " + "invalid location %s for type: %s ", + location, type); } - - /* - * TODO: this case should never occur! - * A union type can only occur in special locations, e.g. for exception - * parameters. The EXCEPTION_PARAMETER TartetType should be used to - * decide which of the alternatives in the union to annotate. - * Only the TypePathEntry is not enough. - * As a hack, always annotate the first alternative. - */ - private static AnnotatedTypeMirror getLocationTypeAUT( - AnnotatedUnionType type, List location) - throws UnexpectedAnnotationLocationException { - AnnotatedTypeMirror comptype = type.getAlternatives().get(0); - return getTypeAtLocation(comptype, location); + } + + /** + * When we have an (e.g. @Odd int @NonNull []) the type-annotation position of the array + * annotation (@NonNull) is really the outermost type in the TypeAnnotationPosition and it will + * NOT have TypePathEntryKind.ARRAY at the end of its position. The position of the component type + * (@Odd) is considered deeper in the type and therefore has the TypePathEntryKind.ARRAY in its + * position. + */ + private static AnnotatedTypeMirror getLocationTypeAAT( + AnnotatedArrayType type, + List location, + TypeCompound anno) + throws UnexpectedAnnotationLocationException { + if (location.size() >= 1 + && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) { + AnnotatedTypeMirror comptype = type.getComponentType(); + return getTypeAtLocation(comptype, tail(location), anno, true); + } else { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.annotateAAT: " + "invalid location %s for type: %s ", + location, type); } - - /** Intersection types use the TYPE_ARGUMENT index to separate the individual types. */ - private static AnnotatedTypeMirror getLocationTypeAIT( - AnnotatedIntersectionType type, List location) - throws UnexpectedAnnotationLocationException { - if (location.size() >= 1 - && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT) { - AnnotatedTypeMirror bound = type.getBounds().get(location.get(0).arg); - return getTypeAtLocation(bound, tail(location)); - } else { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocatonTypeAIT: invalid location %s for type: %s ", - location, type); - } + } + + /* + * TODO: this case should never occur! + * A union type can only occur in special locations, e.g. for exception + * parameters. The EXCEPTION_PARAMETER TartetType should be used to + * decide which of the alternatives in the union to annotate. + * Only the TypePathEntry is not enough. + * As a hack, always annotate the first alternative. + */ + private static AnnotatedTypeMirror getLocationTypeAUT( + AnnotatedUnionType type, List location) + throws UnexpectedAnnotationLocationException { + AnnotatedTypeMirror comptype = type.getAlternatives().get(0); + return getTypeAtLocation(comptype, location); + } + + /** Intersection types use the TYPE_ARGUMENT index to separate the individual types. */ + private static AnnotatedTypeMirror getLocationTypeAIT( + AnnotatedIntersectionType type, List location) + throws UnexpectedAnnotationLocationException { + if (location.size() >= 1 + && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT) { + AnnotatedTypeMirror bound = type.getBounds().get(location.get(0).arg); + return getTypeAtLocation(bound, tail(location)); + } else { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocatonTypeAIT: invalid location %s for type: %s ", + location, type); } + } - private static List tail(List list) { - return list.subList(1, list.size()); - } + private static List tail(List list) { + return list.subList(1, list.size()); + } - /** Exception indicating an invalid location for an annotation was found. */ - @SuppressWarnings("serial") - public static class UnexpectedAnnotationLocationException extends Exception { - - /** - * Creates an UnexpectedAnnotationLocationException. - * - * @param format format string - * @param args arguments to the format string - */ - @FormatMethod - private UnexpectedAnnotationLocationException(String format, Object... args) { - super(String.format(format, args)); - } + /** Exception indicating an invalid location for an annotation was found. */ + @SuppressWarnings("serial") + public static class UnexpectedAnnotationLocationException extends Exception { + + /** + * Creates an UnexpectedAnnotationLocationException. + * + * @param format format string + * @param args arguments to the format string + */ + @FormatMethod + private UnexpectedAnnotationLocationException(String format, Object... args) { + super(String.format(format, args)); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java index 1bbb6d73c22..f98e6770900 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java @@ -2,13 +2,10 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.TargetType; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; - import java.util.List; import java.util.Map; - import javax.lang.model.element.Element; +import org.checkerframework.framework.type.AnnotatedTypeMirror; /** * Some Elements are members of a list (formal method parameters and type parameters). This class @@ -18,50 +15,48 @@ */ abstract class IndexedElementAnnotationApplier extends TargetedElementAnnotationApplier { - protected IndexedElementAnnotationApplier(AnnotatedTypeMirror type, Element element) { - super(type, element); + protected IndexedElementAnnotationApplier(AnnotatedTypeMirror type, Element element) { + super(type, element); + } + + /** The index of element in the list of elements that contains it. */ + public abstract int getElementIndex(); + + /** + * A TypeAnnotationPosition has a number of different indexes (type_index, bound_index, + * param_index) Return the index we are interested in. If offsetting needs to be done it should be + * done in getElementIndex not here. (see ElementAnnotationUtils.getBoundIndexOffset ) + * + * @param anno an annotation we might wish to apply + * @return the index value this applier compares against the getElementIndex + */ + public abstract int getTypeCompoundIndex(Attribute.TypeCompound anno); + + @Override + protected Map> sift( + Iterable typeCompounds) { + Map> targetClassToAnnos = super.sift(typeCompounds); + + List targeted = targetClassToAnnos.get(TargetClass.TARGETED); + List valid = targetClassToAnnos.get(TargetClass.VALID); + + int paramIndex = getElementIndex(); + + // filter out annotations in targeted that don't have the correct parameter index. (i.e the + // one's that are on the same method but don't pertain to the parameter element being + // processed, see class comments ). Place these annotations into the valid list. + int i = 0; + while (i < targeted.size()) { + Attribute.TypeCompound target = targeted.get(i); + // Annotations on parameters to record constructors are marked as fields so + // getTypeCompoundIndex does not return paramIndex. + if (target.position.type != TargetType.FIELD && getTypeCompoundIndex(target) != paramIndex) { + valid.add(targeted.remove(i)); + } else { + ++i; + } } - /** The index of element in the list of elements that contains it. */ - public abstract int getElementIndex(); - - /** - * A TypeAnnotationPosition has a number of different indexes (type_index, bound_index, - * param_index) Return the index we are interested in. If offsetting needs to be done it should - * be done in getElementIndex not here. (see ElementAnnotationUtils.getBoundIndexOffset ) - * - * @param anno an annotation we might wish to apply - * @return the index value this applier compares against the getElementIndex - */ - public abstract int getTypeCompoundIndex(Attribute.TypeCompound anno); - - @Override - protected Map> sift( - Iterable typeCompounds) { - Map> targetClassToAnnos = - super.sift(typeCompounds); - - List targeted = targetClassToAnnos.get(TargetClass.TARGETED); - List valid = targetClassToAnnos.get(TargetClass.VALID); - - int paramIndex = getElementIndex(); - - // filter out annotations in targeted that don't have the correct parameter index. (i.e the - // one's that are on the same method but don't pertain to the parameter element being - // processed, see class comments ). Place these annotations into the valid list. - int i = 0; - while (i < targeted.size()) { - Attribute.TypeCompound target = targeted.get(i); - // Annotations on parameters to record constructors are marked as fields so - // getTypeCompoundIndex does not return paramIndex. - if (target.position.type != TargetType.FIELD - && getTypeCompoundIndex(target) != paramIndex) { - valid.add(targeted.remove(i)); - } else { - ++i; - } - } - - return targetClassToAnnos; - } + return targetClassToAnnos; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java index 14bd8b248e5..82f7cff81cf 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java @@ -5,7 +5,11 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; import com.sun.tools.javac.code.TypeAnnotationPosition; - +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.Element; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -15,249 +19,236 @@ import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.StringsPlume; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.Element; - /** * Adds annotations from element to the return type, formal parameter types, type parameters, and * throws clauses of the AnnotatedExecutableType type. */ public class MethodApplier extends TargetedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new MethodApplier(type, element, atypeFactory).extractAndApply(); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new MethodApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * Returns true if typeMirror represents an {@link AnnotatedExecutableType} and element represents + * a {@link Symbol.MethodSymbol}. + * + * @param typeMirror the type to test + * @param element the corresponding element + * @return true if the MethodApplier accepts the type and element + */ + public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { + return element instanceof Symbol.MethodSymbol && typeMirror instanceof AnnotatedExecutableType; + } + + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** Method being annotated, this symbol contains all relevant annotations. */ + private final Symbol.MethodSymbol methodSymbol; + + /** Method being annotated. */ + private final AnnotatedExecutableType methodType; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ MethodApplier( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + this.atypeFactory = atypeFactory; + this.methodSymbol = (Symbol.MethodSymbol) element; + this.methodType = (AnnotatedExecutableType) type; + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] {TargetType.METHOD_RECEIVER, TargetType.METHOD_RETURN, TargetType.THROWS}; + + /** + * Returns receiver, returns, and throws. See extract and apply as we also annotate type params. + * + * @return receiver, returns, and throws + */ + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + TargetType.METHOD_TYPE_PARAMETER, + TargetType.METHOD_TYPE_PARAMETER_BOUND, + TargetType.METHOD_FORMAL_PARAMETER, + // TODO: from generic anonymous classes; remove when + // we can depend on only seeing classfiles that were + // generated by a javac that contains a fix for: + // https://bugs.openjdk.org/browse/JDK-8198945 + TargetType.CLASS_EXTENDS, + // TODO: Test case from Issue 3277 produces invalid position. + // Ignore until this javac bug is fixed: + // https://bugs.openjdk.org/browse/JDK-8233945 + TargetType.UNKNOWN, + // Annotations on parameters to record constructors are marked as fields. + TargetType.FIELD + }; + + /** + * Returns all possible annotation positions for a method except those in annotatedTargets. + * + * @return all possible annotation positions for a method except those in annotatedTargets + */ + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the annotations on the method symbol (element). + * + * @return the annotations on the method symbol (element) + */ + @Override + protected Iterable getRawTypeAttributes() { + return methodSymbol.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return MethodApplier.accepts(type, element); + } + + /** + * Sets the method's element, annotates its return type, parameters, type parameters, and throws + * annotations. + */ + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + methodType.setElement(methodSymbol); // Preserves previous behavior + + // Add declaration annotations to the return type if + if (methodType.getReturnType() instanceof AnnotatedTypeVariable) { + applyTypeVarUseOnReturnType(); } - - /** - * Returns true if typeMirror represents an {@link AnnotatedExecutableType} and element - * represents a {@link Symbol.MethodSymbol}. - * - * @param typeMirror the type to test - * @param element the corresponding element - * @return true if the MethodApplier accepts the type and element - */ - public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { - return element instanceof Symbol.MethodSymbol - && typeMirror instanceof AnnotatedExecutableType; + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + methodType.getReturnType(), methodSymbol.getAnnotationMirrors()); + + List params = methodType.getParameterTypes(); + for (int i = 0; i < params.size(); ++i) { + // Add declaration annotations to the parameter type + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + params.get(i), methodSymbol.getParameters().get(i).getAnnotationMirrors()); } - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** Method being annotated, this symbol contains all relevant annotations. */ - private final Symbol.MethodSymbol methodSymbol; - - /** Method being annotated. */ - private final AnnotatedExecutableType methodType; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ MethodApplier( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - this.atypeFactory = atypeFactory; - this.methodSymbol = (Symbol.MethodSymbol) element; - this.methodType = (AnnotatedExecutableType) type; + // ensures that we check that there are only valid target types on this class, there are no + // "invalid" locations + super.extractAndApply(); + + ElementAnnotationUtil.applyAllElementAnnotations( + methodType.getParameterTypes(), methodSymbol.getParameters(), atypeFactory); + ElementAnnotationUtil.applyAllElementAnnotations( + methodType.getTypeVariables(), methodSymbol.getTypeParameters(), atypeFactory); + } + + // NOTE that these are the only locations not handled elsewhere, otherwise we call apply + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + List unmatched = new ArrayList<>(); + Map> targetTypeToAnno = + ElementAnnotationUtil.partitionByTargetType( + targeted, + unmatched, + TargetType.METHOD_RECEIVER, + TargetType.METHOD_RETURN, + TargetType.THROWS); + + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + methodType.getReceiverType(), targetTypeToAnno.get(TargetType.METHOD_RECEIVER)); + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + methodType.getReturnType(), targetTypeToAnno.get(TargetType.METHOD_RETURN)); + applyThrowsAnnotations(targetTypeToAnno.get(TargetType.THROWS)); + + if (!unmatched.isEmpty()) { + throw new BugInCF( + "Unexpected annotations ( " + + StringsPlume.join(",", unmatched) + + " ) for" + + "type ( " + + type + + " ) and element ( " + + element + + " ) "); } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] { - TargetType.METHOD_RECEIVER, TargetType.METHOD_RETURN, TargetType.THROWS - }; - - /** - * Returns receiver, returns, and throws. See extract and apply as we also annotate type params. - * - * @return receiver, returns, and throws - */ - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; + } + + /** For each thrown type, collect all the annotations for that type and apply them. */ + private void applyThrowsAnnotations(List annos) + throws UnexpectedAnnotationLocationException { + List thrown = methodType.getThrownTypes(); + if (thrown.isEmpty()) { + return; } - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - TargetType.METHOD_TYPE_PARAMETER, - TargetType.METHOD_TYPE_PARAMETER_BOUND, - TargetType.METHOD_FORMAL_PARAMETER, - // TODO: from generic anonymous classes; remove when - // we can depend on only seeing classfiles that were - // generated by a javac that contains a fix for: - // https://bugs.openjdk.org/browse/JDK-8198945 - TargetType.CLASS_EXTENDS, - // TODO: Test case from Issue 3277 produces invalid position. - // Ignore until this javac bug is fixed: - // https://bugs.openjdk.org/browse/JDK-8233945 - TargetType.UNKNOWN, - // Annotations on parameters to record constructors are marked as fields. - TargetType.FIELD - }; - - /** - * Returns all possible annotation positions for a method except those in annotatedTargets. - * - * @return all possible annotation positions for a method except those in annotatedTargets - */ - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the annotations on the method symbol (element). - * - * @return the annotations on the method symbol (element) - */ - @Override - protected Iterable getRawTypeAttributes() { - return methodSymbol.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return MethodApplier.accepts(type, element); - } - - /** - * Sets the method's element, annotates its return type, parameters, type parameters, and throws - * annotations. - */ - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - methodType.setElement(methodSymbol); // Preserves previous behavior - - // Add declaration annotations to the return type if - if (methodType.getReturnType() instanceof AnnotatedTypeVariable) { - applyTypeVarUseOnReturnType(); - } - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - methodType.getReturnType(), methodSymbol.getAnnotationMirrors()); - - List params = methodType.getParameterTypes(); - for (int i = 0; i < params.size(); ++i) { - // Add declaration annotations to the parameter type - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - params.get(i), methodSymbol.getParameters().get(i).getAnnotationMirrors()); - } - - // ensures that we check that there are only valid target types on this class, there are no - // "invalid" locations - super.extractAndApply(); - - ElementAnnotationUtil.applyAllElementAnnotations( - methodType.getParameterTypes(), methodSymbol.getParameters(), atypeFactory); - ElementAnnotationUtil.applyAllElementAnnotations( - methodType.getTypeVariables(), methodSymbol.getTypeParameters(), atypeFactory); + Map> typeToAnnos = new LinkedHashMap<>(); + for (AnnotatedTypeMirror thrownType : thrown) { + typeToAnnos.put(thrownType, new ArrayList<>()); } - // NOTE that these are the only locations not handled elsewhere, otherwise we call apply - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - List unmatched = new ArrayList<>(); - Map> targetTypeToAnno = - ElementAnnotationUtil.partitionByTargetType( - targeted, - unmatched, - TargetType.METHOD_RECEIVER, - TargetType.METHOD_RETURN, - TargetType.THROWS); - - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - methodType.getReceiverType(), targetTypeToAnno.get(TargetType.METHOD_RECEIVER)); - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - methodType.getReturnType(), targetTypeToAnno.get(TargetType.METHOD_RETURN)); - applyThrowsAnnotations(targetTypeToAnno.get(TargetType.THROWS)); - - if (!unmatched.isEmpty()) { - throw new BugInCF( - "Unexpected annotations ( " - + StringsPlume.join(",", unmatched) - + " ) for" - + "type ( " - + type - + " ) and element ( " - + element - + " ) "); - } - } - - /** For each thrown type, collect all the annotations for that type and apply them. */ - private void applyThrowsAnnotations(List annos) - throws UnexpectedAnnotationLocationException { - List thrown = methodType.getThrownTypes(); - if (thrown.isEmpty()) { - return; - } - - Map> typeToAnnos = new LinkedHashMap<>(); - for (AnnotatedTypeMirror thrownType : thrown) { - typeToAnnos.put(thrownType, new ArrayList<>()); - } - - for (TypeCompound anno : annos) { - TypeAnnotationPosition annoPos = anno.position; - if (annoPos.type_index >= 0 && annoPos.type_index < thrown.size()) { - AnnotatedTypeMirror thrownType = thrown.get(annoPos.type_index); - typeToAnnos.get(thrownType).add(anno); - } else { - throw new BugInCF( - "MethodApplier.applyThrowsAnnotation: " - + "invalid throws index " - + annoPos.type_index - + " for annotation: " - + anno - + " for element: " - + ElementUtils.getQualifiedName(element)); - } - } - - for (Map.Entry> typeToAnno : - typeToAnnos.entrySet()) { - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - typeToAnno.getKey(), typeToAnno.getValue()); - } + for (TypeCompound anno : annos) { + TypeAnnotationPosition annoPos = anno.position; + if (annoPos.type_index >= 0 && annoPos.type_index < thrown.size()) { + AnnotatedTypeMirror thrownType = thrown.get(annoPos.type_index); + typeToAnnos.get(thrownType).add(anno); + } else { + throw new BugInCF( + "MethodApplier.applyThrowsAnnotation: " + + "invalid throws index " + + annoPos.type_index + + " for annotation: " + + anno + + " for element: " + + ElementUtils.getQualifiedName(element)); + } } - /** - * If the return type is a use of a type variable first apply the bound annotations from the - * type variables declaration. - */ - private void applyTypeVarUseOnReturnType() throws UnexpectedAnnotationLocationException { - new TypeVarUseApplier(methodType.getReturnType(), methodSymbol, atypeFactory) - .extractAndApply(); + for (Map.Entry> typeToAnno : typeToAnnos.entrySet()) { + ElementAnnotationUtil.annotateViaTypeAnnoPosition(typeToAnno.getKey(), typeToAnno.getValue()); } + } + + /** + * If the return type is a use of a type variable first apply the bound annotations from the type + * variables declaration. + */ + private void applyTypeVarUseOnReturnType() throws UnexpectedAnnotationLocationException { + new TypeVarUseApplier(methodType.getReturnType(), methodSymbol, atypeFactory).extractAndApply(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java index 79cf2f9c9db..16e5034e8d6 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java @@ -3,149 +3,147 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; - +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; - /** Applies the annotations present for a method type parameter onto an AnnotatedTypeVariable. */ public class MethodTypeParamApplier extends TypeParamElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new MethodTypeParamApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * Returns true if element represents a type parameter for a method. - * - * @param type ignored - * @param element the element that might be a type parameter for a method - * @return true if element represents a type parameter for a method - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return element.getKind() == ElementKind.TYPE_PARAMETER - && element.getEnclosingElement() instanceof Symbol.MethodSymbol; - } - - /** The enclosing method. */ - private final Symbol.MethodSymbol enclosingMethod; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory /*package-private - */ - /*package-private*/ MethodTypeParamApplier( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element, atypeFactory); - - if (!(element.getEnclosingElement() instanceof Symbol.MethodSymbol)) { - throw new BugInCF( - "TypeParameter not enclosed by method? Type( " - + type - + " ) " - + "Element ( " - + element - + " ) "); - } - - enclosingMethod = (Symbol.MethodSymbol) element.getEnclosingElement(); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new MethodTypeParamApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * Returns true if element represents a type parameter for a method. + * + * @param type ignored + * @param element the element that might be a type parameter for a method + * @return true if element represents a type parameter for a method + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return element.getKind() == ElementKind.TYPE_PARAMETER + && element.getEnclosingElement() instanceof Symbol.MethodSymbol; + } + + /** The enclosing method. */ + private final Symbol.MethodSymbol enclosingMethod; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory /*package-private + */ + /*package-private*/ MethodTypeParamApplier( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element, atypeFactory); + + if (!(element.getEnclosingElement() instanceof Symbol.MethodSymbol)) { + throw new BugInCF( + "TypeParameter not enclosed by method? Type( " + + type + + " ) " + + "Element ( " + + element + + " ) "); } - /** - * Returns TargetType.METHOD_TYPE_PARAMETER. - * - * @return TargetType.METHOD_TYPE_PARAMETER - */ - @Override - protected TargetType lowerBoundTarget() { - return TargetType.METHOD_TYPE_PARAMETER; - } - - /** - * Returns TargetType.METHOD_TYPE_PARAMETER_BOUND. - * - * @return TargetType.METHOD_TYPE_PARAMETER_BOUND - */ - @Override - protected TargetType upperBoundTarget() { - return TargetType.METHOD_TYPE_PARAMETER_BOUND; - } - - /** - * Returns the index of element in the type parameter list of its enclosing method. - * - * @return the index of element in the type parameter list of its enclosing method - */ - @Override - public int getElementIndex() { - return enclosingMethod.getTypeParameters().indexOf(element); - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.METHOD_RETURN, - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.METHOD_RECEIVER, - TargetType.THROWS, - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - // TODO: from generic anonymous classes; remove when - // we can depend on only seeing classfiles that were - // generated by a javac that contains a fix for: - // https://bugs.openjdk.org/browse/JDK-8198945 - TargetType.CLASS_EXTENDS, - // TODO: Test case from Issue 3277 produces invalid position. - // Ignore until this javac bug is fixed: - // https://bugs.openjdk.org/browse/JDK-8233945 - TargetType.UNKNOWN - }; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the TypeCompounds (annotations) of the declaring element. - * - * @return the TypeCompounds (annotations) of the declaring element - */ - @Override - protected Iterable getRawTypeAttributes() { - return enclosingMethod.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } + enclosingMethod = (Symbol.MethodSymbol) element.getEnclosingElement(); + } + + /** + * Returns TargetType.METHOD_TYPE_PARAMETER. + * + * @return TargetType.METHOD_TYPE_PARAMETER + */ + @Override + protected TargetType lowerBoundTarget() { + return TargetType.METHOD_TYPE_PARAMETER; + } + + /** + * Returns TargetType.METHOD_TYPE_PARAMETER_BOUND. + * + * @return TargetType.METHOD_TYPE_PARAMETER_BOUND + */ + @Override + protected TargetType upperBoundTarget() { + return TargetType.METHOD_TYPE_PARAMETER_BOUND; + } + + /** + * Returns the index of element in the type parameter list of its enclosing method. + * + * @return the index of element in the type parameter list of its enclosing method + */ + @Override + public int getElementIndex() { + return enclosingMethod.getTypeParameters().indexOf(element); + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.METHOD_RETURN, + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.METHOD_RECEIVER, + TargetType.THROWS, + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + // TODO: from generic anonymous classes; remove when + // we can depend on only seeing classfiles that were + // generated by a javac that contains a fix for: + // https://bugs.openjdk.org/browse/JDK-8198945 + TargetType.CLASS_EXTENDS, + // TODO: Test case from Issue 3277 produces invalid position. + // Ignore until this javac bug is fixed: + // https://bugs.openjdk.org/browse/JDK-8233945 + TargetType.UNKNOWN + }; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the TypeCompounds (annotations) of the declaring element. + * + * @return the TypeCompounds (annotations) of the declaring element + */ + @Override + protected Iterable getRawTypeAttributes() { + return enclosingMethod.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java index e5825069fa7..bdcc218ef8f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java @@ -7,7 +7,12 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; - +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.VariableElement; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -16,290 +21,281 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.IPair; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.VariableElement; - /** Adds annotations to one formal parameter of a method or lambda within a method. */ public class ParamApplier extends IndexedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type whose annotations to change - * @param element where to get annotations from - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new ParamApplier(type, element, atypeFactory).extractAndApply(); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type whose annotations to change + * @param element where to get annotations from + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new ParamApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * Returns true if element represents a parameter. + * + * @param type ignored + * @param element the element to test + * @return if the element represents a parameter + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return element.getKind() == ElementKind.PARAMETER; + } + + /** The enclosing method. */ + private final Symbol.MethodSymbol enclosingMethod; + + /** Whether this is a parameter to a lambda expression. */ + private final boolean isLambdaParam; + + /** The index of the lambda parameter, or null if isLambdaParam is false. */ + private final @Nullable Integer lambdaParamIndex; + + /** The corresponding lambda expression tree, or null if isLambdaParam is false. */ + private final @Nullable LambdaExpressionTree lambdaTree; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ ParamApplier( + AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + enclosingMethod = getParentMethod(element); + + if (enclosingMethod.getKind() != ElementKind.INSTANCE_INIT + && enclosingMethod.getKind() != ElementKind.STATIC_INIT + && enclosingMethod.getParameters().contains(element)) { + lambdaTree = null; + isLambdaParam = false; + lambdaParamIndex = null; + } else { + IPair paramToEnclosingLambda = + ElementAnnotationApplier.getParamAndLambdaTree(element, atypeFactory); + + if (paramToEnclosingLambda != null) { + VariableTree paramDecl = paramToEnclosingLambda.first; + lambdaTree = paramToEnclosingLambda.second; + isLambdaParam = true; + lambdaParamIndex = lambdaTree.getParameters().indexOf(paramDecl); + } else { + lambdaTree = null; + isLambdaParam = false; + lambdaParamIndex = null; + } } - - /** - * Returns true if element represents a parameter. - * - * @param type ignored - * @param element the element to test - * @return if the element represents a parameter - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return element.getKind() == ElementKind.PARAMETER; + } + + /** + * Returns the index of element its parent method's parameter list. Integer.MIN_VALUE if the + * element is the receiver parameter. + * + * @return the index of element its parent method's parameter list. Integer.MIN_VALUE if the + * element is the receiver parameter + */ + @Override + public int getElementIndex() { + if (isLambdaParam) { + return lambdaParamIndex; } - /** The enclosing method. */ - private final Symbol.MethodSymbol enclosingMethod; - - /** Whether this is a parameter to a lambda expression. */ - private final boolean isLambdaParam; - - /** The index of the lambda parameter, or null if isLambdaParam is false. */ - private final @Nullable Integer lambdaParamIndex; - - /** The corresponding lambda expression tree, or null if isLambdaParam is false. */ - private final @Nullable LambdaExpressionTree lambdaTree; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ ParamApplier( - AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - enclosingMethod = getParentMethod(element); - - if (enclosingMethod.getKind() != ElementKind.INSTANCE_INIT - && enclosingMethod.getKind() != ElementKind.STATIC_INIT - && enclosingMethod.getParameters().contains(element)) { - lambdaTree = null; - isLambdaParam = false; - lambdaParamIndex = null; - } else { - IPair paramToEnclosingLambda = - ElementAnnotationApplier.getParamAndLambdaTree(element, atypeFactory); - - if (paramToEnclosingLambda != null) { - VariableTree paramDecl = paramToEnclosingLambda.first; - lambdaTree = paramToEnclosingLambda.second; - isLambdaParam = true; - lambdaParamIndex = lambdaTree.getParameters().indexOf(paramDecl); - } else { - lambdaTree = null; - isLambdaParam = false; - lambdaParamIndex = null; - } - } + if (isReceiver(element)) { + return Integer.MIN_VALUE; } - /** - * Returns the index of element its parent method's parameter list. Integer.MIN_VALUE if the - * element is the receiver parameter. - * - * @return the index of element its parent method's parameter list. Integer.MIN_VALUE if the - * element is the receiver parameter - */ - @Override - public int getElementIndex() { - if (isLambdaParam) { - return lambdaParamIndex; - } - - if (isReceiver(element)) { - return Integer.MIN_VALUE; - } - - int paramIndex = enclosingMethod.getParameters().indexOf(element); - if (paramIndex == -1) { - throw new BugInCF( - "Could not find parameter Element in parameter list. " - + "Parameter( " - + element - + " ) Parent ( " - + enclosingMethod - + " ) "); - } - - return paramIndex; - } - - /** - * Returns the parameter index of anno's TypeAnnotationPosition. - * - * @return the parameter index of anno's TypeAnnotationPosition - */ - @Override - public int getTypeCompoundIndex(Attribute.TypeCompound anno) { - return anno.getPosition().parameter_index; - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] { - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.METHOD_RECEIVER, - // Annotations on parameters to record constructors are marked as fields. - TargetType.FIELD - }; - - /** - * Returns {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER}. - * - * @return {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER} - */ - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.METHOD_RETURN, - TargetType.THROWS, - TargetType.METHOD_TYPE_PARAMETER, - TargetType.METHOD_TYPE_PARAMETER_BOUND, - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - // TODO: from generic anonymous classes; remove when - // we can depend on only seeing classfiles that were - // generated by a javac that contains a fix for: - // https://bugs.openjdk.org/browse/JDK-8198945 - TargetType.CLASS_EXTENDS - }; - - /** - * Returns any annotation TargetType that can be found on a method. - * - * @return any annotation TargetType that can be found on a method - */ - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the TypeCompounds (annotations) of the enclosing method for this parameter. - * - * @return the TypeCompounds (annotations) of the enclosing method for this parameter - */ - @Override - protected Iterable getRawTypeAttributes() { - return enclosingMethod.getRawTypeAttributes(); + int paramIndex = enclosingMethod.getParameters().indexOf(element); + if (paramIndex == -1) { + throw new BugInCF( + "Could not find parameter Element in parameter list. " + + "Parameter( " + + element + + " ) Parent ( " + + enclosingMethod + + " ) "); } - @Override - protected Map> sift( - Iterable typeCompounds) { - // this will sift out the annotations that do not have the right position index - Map> targetClassToAnnos = - super.sift(typeCompounds); - - List targeted = targetClassToAnnos.get(TargetClass.TARGETED); - List valid = targetClassToAnnos.get(TargetClass.VALID); - - // if this is a lambdaParam, filter out from targeted those annos that apply to method - // formal parameters if this is a method formal param, filter out from targeted those annos - // that apply to lambdas - int i = 0; - while (i < targeted.size()) { - Tree onLambda = targeted.get(i).position.onLambda; - if (onLambda == null) { - if (!isLambdaParam) { - ++i; - } else { - valid.add(targeted.remove(i)); - } - } else { - if (onLambda.equals(this.lambdaTree)) { - ++i; - } else { - valid.add(targeted.remove(i)); - } - } + return paramIndex; + } + + /** + * Returns the parameter index of anno's TypeAnnotationPosition. + * + * @return the parameter index of anno's TypeAnnotationPosition + */ + @Override + public int getTypeCompoundIndex(Attribute.TypeCompound anno) { + return anno.getPosition().parameter_index; + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] { + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.METHOD_RECEIVER, + // Annotations on parameters to record constructors are marked as fields. + TargetType.FIELD + }; + + /** + * Returns {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER}. + * + * @return {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER} + */ + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.METHOD_RETURN, + TargetType.THROWS, + TargetType.METHOD_TYPE_PARAMETER, + TargetType.METHOD_TYPE_PARAMETER_BOUND, + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + // TODO: from generic anonymous classes; remove when + // we can depend on only seeing classfiles that were + // generated by a javac that contains a fix for: + // https://bugs.openjdk.org/browse/JDK-8198945 + TargetType.CLASS_EXTENDS + }; + + /** + * Returns any annotation TargetType that can be found on a method. + * + * @return any annotation TargetType that can be found on a method + */ + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the TypeCompounds (annotations) of the enclosing method for this parameter. + * + * @return the TypeCompounds (annotations) of the enclosing method for this parameter + */ + @Override + protected Iterable getRawTypeAttributes() { + return enclosingMethod.getRawTypeAttributes(); + } + + @Override + protected Map> sift( + Iterable typeCompounds) { + // this will sift out the annotations that do not have the right position index + Map> targetClassToAnnos = super.sift(typeCompounds); + + List targeted = targetClassToAnnos.get(TargetClass.TARGETED); + List valid = targetClassToAnnos.get(TargetClass.VALID); + + // if this is a lambdaParam, filter out from targeted those annos that apply to method + // formal parameters if this is a method formal param, filter out from targeted those annos + // that apply to lambdas + int i = 0; + while (i < targeted.size()) { + Tree onLambda = targeted.get(i).position.onLambda; + if (onLambda == null) { + if (!isLambdaParam) { + ++i; + } else { + valid.add(targeted.remove(i)); } - - return targetClassToAnnos; - } - - /** - * @param targeted type compounds with formal method parameter target types with parameter_index - * == getIndex - */ - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - List formalParams = new ArrayList<>(); - Map> targetToAnnos = - ElementAnnotationUtil.partitionByTargetType( - targeted, formalParams, TargetType.METHOD_RECEIVER); - - if (isReceiver(element)) { - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - type, targetToAnnos.get(TargetType.METHOD_RECEIVER)); + } else { + if (onLambda.equals(this.lambdaTree)) { + ++i; } else { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, formalParams); + valid.add(targeted.remove(i)); } + } } - /** - * Returns true if element represents the receiver parameter of a method. - * - * @param element an element - * @return true if element represents the receiver parameter of a method - */ - private boolean isReceiver(Element element) { - return element.getKind() == ElementKind.PARAMETER - && element.getSimpleName().contentEquals("this"); + return targetClassToAnnos; + } + + /** + * @param targeted type compounds with formal method parameter target types with parameter_index + * == getIndex + */ + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + List formalParams = new ArrayList<>(); + Map> targetToAnnos = + ElementAnnotationUtil.partitionByTargetType( + targeted, formalParams, TargetType.METHOD_RECEIVER); + + if (isReceiver(element)) { + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + type, targetToAnnos.get(TargetType.METHOD_RECEIVER)); + } else { + ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, formalParams); } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } - - /** - * Return the enclosing MethodSymbol of the given element, throwing an exception if the symbol's - * enclosing element is not a MethodSymbol. - * - * @param methodChildElem some element that is a child of a method typeDeclaration (e.g. a - * parameter or return type) - * @return the MethodSymbol of the method containing methodChildElem - */ - public static Symbol.MethodSymbol getParentMethod(Element methodChildElem) { - if (!(methodChildElem.getEnclosingElement() instanceof Symbol.MethodSymbol)) { - throw new BugInCF( - "Element is not a direct child of a MethodSymbol. Element ( " - + methodChildElem - + " parent ( " - + methodChildElem.getEnclosingElement() - + " ) "); - } - return (Symbol.MethodSymbol) methodChildElem.getEnclosingElement(); - } - - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - type, element.getAnnotationMirrors()); - super.extractAndApply(); + } + + /** + * Returns true if element represents the receiver parameter of a method. + * + * @param element an element + * @return true if element represents the receiver parameter of a method + */ + private boolean isReceiver(Element element) { + return element.getKind() == ElementKind.PARAMETER + && element.getSimpleName().contentEquals("this"); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } + + /** + * Return the enclosing MethodSymbol of the given element, throwing an exception if the symbol's + * enclosing element is not a MethodSymbol. + * + * @param methodChildElem some element that is a child of a method typeDeclaration (e.g. a + * parameter or return type) + * @return the MethodSymbol of the method containing methodChildElem + */ + public static Symbol.MethodSymbol getParentMethod(Element methodChildElem) { + if (!(methodChildElem.getEnclosingElement() instanceof Symbol.MethodSymbol)) { + throw new BugInCF( + "Element is not a direct child of a MethodSymbol. Element ( " + + methodChildElem + + " parent ( " + + methodChildElem.getEnclosingElement() + + " ) "); } + return (Symbol.MethodSymbol) methodChildElem.getEnclosingElement(); + } + + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + type, element.getAnnotationMirrors()); + super.extractAndApply(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java index d890056db76..0e459ca5f3e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java @@ -4,13 +4,10 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; - import java.util.List; - import javax.lang.model.element.TypeElement; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; /** * When discovering supertypes of an AnnotatedTypeMirror we want to annotate each supertype with the @@ -19,136 +16,132 @@ */ public class SuperTypeApplier extends IndexedElementAnnotationApplier { - /** - * Annotates each supertype with annotations from subtypeElement's extends/implements clauses. - * - * @param supertypes supertypes to annotate - * @param subtypeElement element that may have annotations to apply to supertypes - */ - public static void annotateSupers( - List supertypes, TypeElement subtypeElement) - throws UnexpectedAnnotationLocationException { - for (int i = 0; i < supertypes.size(); i++) { - AnnotatedTypeMirror supertype = supertypes.get(i); - // Offset i by -1 since typeIndex should start from -1. - // -1 represents the (implicit) extends clause class. - // 0 and greater represent the implements clause interfaces. - // For details see the JSR 308 specification: - // http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html#class-file%3Aext%3Ari%3Aextends - int typeIndex = i - 1; - new SuperTypeApplier(supertype, subtypeElement, typeIndex).extractAndApply(); - } - } - - /** The subclass symbol. */ - private final Symbol.ClassSymbol subclassSymbol; - - /** - * The type_index of the supertype being annotated. - * - *

Note: Due to the semantics of TypeAnnotationPosition, type_index/index numbering works as - * follows: - * - *

If subtypeElement represents a class and not an interface: - * - *

then the first member of supertypes represents the object and the relevant type_index = - * -1; interface indices are offset by 1. - * - *

else all members of supertypes represent interfaces and their indices == their index in - * the supertypes list - */ - private final int index; - - /** - * Constructor. - * - *

Note: This is not meant to be used in apply explicitly unlike all other AnnotationAppliers - * it is intended to be used for annotate super types via the static annotateSuper method, hence - * the private constructor. - * - * @param supertype the supertype - * @param subclassElement the corresponding subclass element - * @param index the type index of the supertype being annotated - */ - private SuperTypeApplier( - AnnotatedTypeMirror supertype, TypeElement subclassElement, int index) { - super(supertype, subclassElement); - this.subclassSymbol = (Symbol.ClassSymbol) subclassElement; - this.index = index; - } - - /** - * Returns the type_index that should represent supertype. - * - * @return the type_index that should represent supertype - */ - @Override - public int getElementIndex() { - return index; - } - - /** - * Returns the type_index of anno's TypeAnnotationPosition. - * - * @return the type_index of anno's TypeAnnotationPosition - */ - @Override - public int getTypeCompoundIndex(Attribute.TypeCompound anno) { - int typeIndex = anno.getPosition().type_index; - // TODO: this is a workaround of a bug in langtools - // https://bugs.openjdk.org/browse/JDK-8164519 - // This bug is fixed in Java 9. - return typeIndex == 0xffff ? -1 : typeIndex; - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] {TargetType.CLASS_EXTENDS}; - - /** - * Returns TargetType.CLASS_EXTENDS. - * - * @return TargetType.CLASS_EXTENDS - */ - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND - }; - - /** - * Returns TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND. - * - * @return TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND - */ - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the TypeCompounds (annotations) of the subclass. - * - * @return the TypeCompounds (annotations) of the subclass - */ - @Override - protected Iterable getRawTypeAttributes() { - return subclassSymbol.getRawTypeAttributes(); - } - - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); - } - - @Override - protected boolean isAccepted() { - return true; + /** + * Annotates each supertype with annotations from subtypeElement's extends/implements clauses. + * + * @param supertypes supertypes to annotate + * @param subtypeElement element that may have annotations to apply to supertypes + */ + public static void annotateSupers( + List supertypes, TypeElement subtypeElement) + throws UnexpectedAnnotationLocationException { + for (int i = 0; i < supertypes.size(); i++) { + AnnotatedTypeMirror supertype = supertypes.get(i); + // Offset i by -1 since typeIndex should start from -1. + // -1 represents the (implicit) extends clause class. + // 0 and greater represent the implements clause interfaces. + // For details see the JSR 308 specification: + // http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html#class-file%3Aext%3Ari%3Aextends + int typeIndex = i - 1; + new SuperTypeApplier(supertype, subtypeElement, typeIndex).extractAndApply(); } + } + + /** The subclass symbol. */ + private final Symbol.ClassSymbol subclassSymbol; + + /** + * The type_index of the supertype being annotated. + * + *

Note: Due to the semantics of TypeAnnotationPosition, type_index/index numbering works as + * follows: + * + *

If subtypeElement represents a class and not an interface: + * + *

then the first member of supertypes represents the object and the relevant type_index = -1; + * interface indices are offset by 1. + * + *

else all members of supertypes represent interfaces and their indices == their index in the + * supertypes list + */ + private final int index; + + /** + * Constructor. + * + *

Note: This is not meant to be used in apply explicitly unlike all other AnnotationAppliers + * it is intended to be used for annotate super types via the static annotateSuper method, hence + * the private constructor. + * + * @param supertype the supertype + * @param subclassElement the corresponding subclass element + * @param index the type index of the supertype being annotated + */ + private SuperTypeApplier(AnnotatedTypeMirror supertype, TypeElement subclassElement, int index) { + super(supertype, subclassElement); + this.subclassSymbol = (Symbol.ClassSymbol) subclassElement; + this.index = index; + } + + /** + * Returns the type_index that should represent supertype. + * + * @return the type_index that should represent supertype + */ + @Override + public int getElementIndex() { + return index; + } + + /** + * Returns the type_index of anno's TypeAnnotationPosition. + * + * @return the type_index of anno's TypeAnnotationPosition + */ + @Override + public int getTypeCompoundIndex(Attribute.TypeCompound anno) { + int typeIndex = anno.getPosition().type_index; + // TODO: this is a workaround of a bug in langtools + // https://bugs.openjdk.org/browse/JDK-8164519 + // This bug is fixed in Java 9. + return typeIndex == 0xffff ? -1 : typeIndex; + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; + + /** + * Returns TargetType.CLASS_EXTENDS. + * + * @return TargetType.CLASS_EXTENDS + */ + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] {TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND}; + + /** + * Returns TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND. + * + * @return TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND + */ + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the TypeCompounds (annotations) of the subclass. + * + * @return the TypeCompounds (annotations) of the subclass + */ + @Override + protected Iterable getRawTypeAttributes() { + return subclassSymbol.getRawTypeAttributes(); + } + + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); + } + + @Override + protected boolean isAccepted() { + return true; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java index 815be4283bc..5df10dfd16d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java @@ -3,20 +3,17 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.TargetType; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; -import org.checkerframework.javacutil.BugInCF; -import org.plumelib.util.StringsPlume; - import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.StringJoiner; - import javax.lang.model.element.Element; import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; +import org.checkerframework.javacutil.BugInCF; +import org.plumelib.util.StringsPlume; /** * TargetedElementAnnotationApplier filters annotations for an element into 3 groups. TARGETED @@ -31,188 +28,180 @@ * methods though they have default empty implementations for brevity. */ abstract class TargetedElementAnnotationApplier { - /** - * Three annotation types that may be encountered when calling getRawTypeAttributes. see sift(). - */ - enum TargetClass { - TARGETED, - VALID, - INVALID + /** + * Three annotation types that may be encountered when calling getRawTypeAttributes. see sift(). + */ + enum TargetClass { + TARGETED, + VALID, + INVALID + } + + /** The type to which we wish to apply annotations. */ + protected final AnnotatedTypeMirror type; + + /** An Element that type represents. */ + protected final Element element; + + /** + * Returns the TargetTypes that identify annotations we wish to apply with this object. Any + * annotations that have these target types will be passed to handleTargeted. + * + * @return the TargetTypes that identify annotations we wish to apply with this object. Any + * annotations that have these target types will be passed to handleTargeted + */ + protected abstract TargetType[] annotatedTargets(); + + /** + * Returns the TargetTypes that identify annotations that are valid but we wish to ignore. Any + * annotations that have these target types will be passed to handleValid, providing they aren't + * also in annotatedTargets. + * + * @return the TargetTypes that identify annotations that are valid but we wish to ignore + */ + protected abstract TargetType[] validTargets(); + + /** + * Annotations on elements are represented as Attribute.TypeCompounds ( a subtype of + * AnnotationMirror) that are usually accessed through a getRawTypeAttributes method on the + * element. + * + *

In Java 8 and later these annotations are generally contained by elements to which they + * apply. However, in earlier versions of Java many of these annotations are handled by either the + * enclosing method, e.g. parameters and method type parameters, or enclosing class, e.g. class + * type parameters. Therefore, many annotations are addressed by first getting all annotations on + * a method or class and the picking out only the ones we wish to target (see extractAndApply). + * + * @return the annotations that we MAY wish to apply to the given type + */ + protected abstract Iterable getRawTypeAttributes(); + + /** + * Tests element/type fields to ensure that this TargetedElementAnnotationApplier is valid for + * this element/type pair. + * + * @return true if the type/element members are handled by this class false otherwise + */ + protected abstract boolean isAccepted(); + + /** + * Constructor. + * + * @param type the type to annotate + * @param element an element identifying type + */ + /*package-private*/ TargetedElementAnnotationApplier(AnnotatedTypeMirror type, Element element) { + this.type = type; + this.element = element; + } + + /** + * This method should apply all annotations that are handled by this object. + * + * @param targeted the list of annotations that were returned by getRawTypeAttributes and had a + * TargetType contained by annotatedTargets + */ + protected abstract void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException; + + /** + * The default implementation of this method does nothing. + * + * @param valid the list of annotations that were returned by getRawTypeAttributes and had a + * TargetType contained by valid and NOT annotatedTargets + */ + protected void handleValid(List valid) {} + + /** + * This implementation reports all invalid annotations as errors. + * + * @param invalid the list of annotations that were returned by getRawTypeAttributes and were not + * handled by handleTargeted or handleValid + */ + protected void handleInvalid(List invalid) { + List remaining = new ArrayList<>(invalid.size()); + for (Attribute.TypeCompound tc : invalid) { + if (tc.getAnnotationType().getKind() != TypeKind.ERROR) { + // Filter out annotations that have an error type. javac will + // already have raised an error for them. + remaining.add(tc); + } } - - /** The type to which we wish to apply annotations. */ - protected final AnnotatedTypeMirror type; - - /** An Element that type represents. */ - protected final Element element; - - /** - * Returns the TargetTypes that identify annotations we wish to apply with this object. Any - * annotations that have these target types will be passed to handleTargeted. - * - * @return the TargetTypes that identify annotations we wish to apply with this object. Any - * annotations that have these target types will be passed to handleTargeted - */ - protected abstract TargetType[] annotatedTargets(); - - /** - * Returns the TargetTypes that identify annotations that are valid but we wish to ignore. Any - * annotations that have these target types will be passed to handleValid, providing they aren't - * also in annotatedTargets. - * - * @return the TargetTypes that identify annotations that are valid but we wish to ignore - */ - protected abstract TargetType[] validTargets(); - - /** - * Annotations on elements are represented as Attribute.TypeCompounds ( a subtype of - * AnnotationMirror) that are usually accessed through a getRawTypeAttributes method on the - * element. - * - *

In Java 8 and later these annotations are generally contained by elements to which they - * apply. However, in earlier versions of Java many of these annotations are handled by either - * the enclosing method, e.g. parameters and method type parameters, or enclosing class, e.g. - * class type parameters. Therefore, many annotations are addressed by first getting all - * annotations on a method or class and the picking out only the ones we wish to target (see - * extractAndApply). - * - * @return the annotations that we MAY wish to apply to the given type - */ - protected abstract Iterable getRawTypeAttributes(); - - /** - * Tests element/type fields to ensure that this TargetedElementAnnotationApplier is valid for - * this element/type pair. - * - * @return true if the type/element members are handled by this class false otherwise - */ - protected abstract boolean isAccepted(); - - /** - * Constructor. - * - * @param type the type to annotate - * @param element an element identifying type - */ - /*package-private*/ TargetedElementAnnotationApplier( - AnnotatedTypeMirror type, Element element) { - this.type = type; - this.element = element; + if (!remaining.isEmpty()) { + StringJoiner msg = new StringJoiner(System.lineSeparator()); + msg.add("handleInvalid(this=" + this.getClass().getName() + "):"); + msg.add("Invalid variable and element passed to extractAndApply; type: " + type); + String elementInfoPrefix = + " element: " + element + " (kind: " + element.getKind() + "), invalid annotations: "; + StringJoiner remainingInfo = new StringJoiner(", ", elementInfoPrefix, ""); + for (Attribute.TypeCompound r : remaining) { + remainingInfo.add(r.toString() + " (" + r.position + ")"); + } + msg.add(remainingInfo.toString()); + msg.add("Targeted annotations: " + StringsPlume.join(", ", annotatedTargets())); + msg.add("Valid annotations: " + StringsPlume.join(", ", validTargets())); + + throw new BugInCF(msg.toString()); } - - /** - * This method should apply all annotations that are handled by this object. - * - * @param targeted the list of annotations that were returned by getRawTypeAttributes and had a - * TargetType contained by annotatedTargets - */ - protected abstract void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException; - - /** - * The default implementation of this method does nothing. - * - * @param valid the list of annotations that were returned by getRawTypeAttributes and had a - * TargetType contained by valid and NOT annotatedTargets - */ - protected void handleValid(List valid) {} - - /** - * This implementation reports all invalid annotations as errors. - * - * @param invalid the list of annotations that were returned by getRawTypeAttributes and were - * not handled by handleTargeted or handleValid - */ - protected void handleInvalid(List invalid) { - List remaining = new ArrayList<>(invalid.size()); - for (Attribute.TypeCompound tc : invalid) { - if (tc.getAnnotationType().getKind() != TypeKind.ERROR) { - // Filter out annotations that have an error type. javac will - // already have raised an error for them. - remaining.add(tc); - } - } - if (!remaining.isEmpty()) { - StringJoiner msg = new StringJoiner(System.lineSeparator()); - msg.add("handleInvalid(this=" + this.getClass().getName() + "):"); - msg.add("Invalid variable and element passed to extractAndApply; type: " + type); - String elementInfoPrefix = - " element: " - + element - + " (kind: " - + element.getKind() - + "), invalid annotations: "; - StringJoiner remainingInfo = new StringJoiner(", ", elementInfoPrefix, ""); - for (Attribute.TypeCompound r : remaining) { - remainingInfo.add(r.toString() + " (" + r.position + ")"); - } - msg.add(remainingInfo.toString()); - msg.add("Targeted annotations: " + StringsPlume.join(", ", annotatedTargets())); - msg.add("Valid annotations: " + StringsPlume.join(", ", validTargets())); - - throw new BugInCF(msg.toString()); - } + } + + /** + * Separate the input annotations into a Map of TargetClass (TARGETED, VALID, INVALID) to the + * annotations that fall into each of those categories. + * + * @param typeCompounds annotations to sift through, should be those returned by + * getRawTypeAttributes + * @return a {@literal Map Annotations>.} + */ + protected Map> sift( + Iterable typeCompounds) { + Map> targetClassToCompound = + new EnumMap<>(TargetClass.class); + for (TargetClass targetClass : TargetClass.values()) { + targetClassToCompound.put(targetClass, new ArrayList<>()); } - /** - * Separate the input annotations into a Map of TargetClass (TARGETED, VALID, INVALID) to the - * annotations that fall into each of those categories. - * - * @param typeCompounds annotations to sift through, should be those returned by - * getRawTypeAttributes - * @return a {@literal Map Annotations>.} - */ - protected Map> sift( - Iterable typeCompounds) { - Map> targetClassToCompound = - new EnumMap<>(TargetClass.class); - for (TargetClass targetClass : TargetClass.values()) { - targetClassToCompound.put(targetClass, new ArrayList<>()); - } - - for (Attribute.TypeCompound typeCompound : typeCompounds) { - TargetType typeCompoundTarget = typeCompound.position.type; - List destList; - - if (ElementAnnotationUtil.contains(typeCompoundTarget, annotatedTargets())) { - destList = targetClassToCompound.get(TargetClass.TARGETED); - } else if (ElementAnnotationUtil.contains(typeCompoundTarget, validTargets())) { - destList = targetClassToCompound.get(TargetClass.VALID); - } else { - destList = targetClassToCompound.get(TargetClass.INVALID); - } - - destList.add(typeCompound); - } - - return targetClassToCompound; + for (Attribute.TypeCompound typeCompound : typeCompounds) { + TargetType typeCompoundTarget = typeCompound.position.type; + List destList; + + if (ElementAnnotationUtil.contains(typeCompoundTarget, annotatedTargets())) { + destList = targetClassToCompound.get(TargetClass.TARGETED); + } else if (ElementAnnotationUtil.contains(typeCompoundTarget, validTargets())) { + destList = targetClassToCompound.get(TargetClass.VALID); + } else { + destList = targetClassToCompound.get(TargetClass.INVALID); + } + + destList.add(typeCompound); } - /** - * Reads the list of annotations that apply to this element (see getRawTypeAttributes). Sifts - * them into three groups (TARGETED, INVALID, VALID) and then calls the appropriate handle - * method on them. The handleTargeted method should apply all annotations that are handled by - * this object. - * - *

This method will throw a runtime exception if isAccepted returns false. - */ - public void extractAndApply() throws UnexpectedAnnotationLocationException { - if (!isAccepted()) { - throw new BugInCF( - "LocalVariableExtractor.extractAndApply: " - + "Invalid variable and element passed to " - + this.getClass().getName() - + "::extractAndApply (" - + type - + ", " - + element); - } - - Map> targetClassToAnno = - sift(getRawTypeAttributes()); - - handleInvalid(targetClassToAnno.get(TargetClass.INVALID)); - handleValid(targetClassToAnno.get(TargetClass.VALID)); - handleTargeted(targetClassToAnno.get(TargetClass.TARGETED)); + return targetClassToCompound; + } + + /** + * Reads the list of annotations that apply to this element (see getRawTypeAttributes). Sifts them + * into three groups (TARGETED, INVALID, VALID) and then calls the appropriate handle method on + * them. The handleTargeted method should apply all annotations that are handled by this object. + * + *

This method will throw a runtime exception if isAccepted returns false. + */ + public void extractAndApply() throws UnexpectedAnnotationLocationException { + if (!isAccepted()) { + throw new BugInCF( + "LocalVariableExtractor.extractAndApply: " + + "Invalid variable and element passed to " + + this.getClass().getName() + + "::extractAndApply (" + + type + + ", " + + element); } + + Map> targetClassToAnno = sift(getRawTypeAttributes()); + + handleInvalid(targetClassToAnno.get(TargetClass.INVALID)); + handleValid(targetClassToAnno.get(TargetClass.VALID)); + handleTargeted(targetClassToAnno.get(TargetClass.TARGETED)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java index 0899b3776a2..8e238518e92 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java @@ -4,151 +4,146 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; - +import java.util.List; +import javax.lang.model.element.Element; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.TypesUtils; -import java.util.List; - -import javax.lang.model.element.Element; - /** Apply annotations to a declared type based on its declaration. */ public class TypeDeclarationApplier extends TargetedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new TypeDeclarationApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * If a type_index == -1 it means that the index refers to the immediate supertype class of the - * declaration. There is only ever one of these since Java has no multiple inheritance - */ - public static final int SUPERCLASS_INDEX = -1; - - /** - * Returns true if type is an annotated declared type and element is a ClassSymbol. - * - * @param type a type - * @param element an element - * @return true if type is an annotated declared type and element is a ClassSymbol - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return type instanceof AnnotatedDeclaredType && element instanceof Symbol.ClassSymbol; - } - - /** The type factory to use. */ - private final AnnotatedTypeFactory atypeFactory; - - /** The type symbol. */ - private final Symbol.ClassSymbol typeSymbol; - - /** The declared type. */ - private final AnnotatedDeclaredType declaredType; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ TypeDeclarationApplier( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - this.atypeFactory = atypeFactory; - this.typeSymbol = (Symbol.ClassSymbol) element; - this.declaredType = (AnnotatedDeclaredType) type; - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] {TargetType.CLASS_EXTENDS}; - - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - TargetType.CLASS_TYPE_PARAMETER, - TargetType.CLASS_TYPE_PARAMETER_BOUND - }; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** All TypeCompounds (annotations) on the ClassSymbol. */ - @Override - protected Iterable getRawTypeAttributes() { - return typeSymbol.getRawTypeAttributes(); - } - - /** - * While more than just annotations on extends or implements clause are annotated by this class, - * only these annotations are passed to handleTargeted (as they are the only in the - * annotatedTargets list). See extractAndApply for type parameters - * - * @param extendsAndImplementsAnnos annotations with a TargetType of CLASS_EXTENDS - */ - @Override - protected void handleTargeted(List extendsAndImplementsAnnos) - throws UnexpectedAnnotationLocationException { - if (TypesUtils.isAnonymous(typeSymbol.type)) { - // If this is an anonymous class, then the annotations after "new" but before the class - // name are stored as super class annotations. Treat them as annotations on the class. - for (Attribute.TypeCompound anno : extendsAndImplementsAnnos) { - if (anno.position.type_index >= SUPERCLASS_INDEX - && anno.position.location.isEmpty()) { - type.addAnnotation(anno); - } - } + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new TypeDeclarationApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * If a type_index == -1 it means that the index refers to the immediate supertype class of the + * declaration. There is only ever one of these since Java has no multiple inheritance + */ + public static final int SUPERCLASS_INDEX = -1; + + /** + * Returns true if type is an annotated declared type and element is a ClassSymbol. + * + * @param type a type + * @param element an element + * @return true if type is an annotated declared type and element is a ClassSymbol + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return type instanceof AnnotatedDeclaredType && element instanceof Symbol.ClassSymbol; + } + + /** The type factory to use. */ + private final AnnotatedTypeFactory atypeFactory; + + /** The type symbol. */ + private final Symbol.ClassSymbol typeSymbol; + + /** The declared type. */ + private final AnnotatedDeclaredType declaredType; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ TypeDeclarationApplier( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + this.atypeFactory = atypeFactory; + this.typeSymbol = (Symbol.ClassSymbol) element; + this.declaredType = (AnnotatedDeclaredType) type; + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; + + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + TargetType.CLASS_TYPE_PARAMETER, + TargetType.CLASS_TYPE_PARAMETER_BOUND + }; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** All TypeCompounds (annotations) on the ClassSymbol. */ + @Override + protected Iterable getRawTypeAttributes() { + return typeSymbol.getRawTypeAttributes(); + } + + /** + * While more than just annotations on extends or implements clause are annotated by this class, + * only these annotations are passed to handleTargeted (as they are the only in the + * annotatedTargets list). See extractAndApply for type parameters + * + * @param extendsAndImplementsAnnos annotations with a TargetType of CLASS_EXTENDS + */ + @Override + protected void handleTargeted(List extendsAndImplementsAnnos) + throws UnexpectedAnnotationLocationException { + if (TypesUtils.isAnonymous(typeSymbol.type)) { + // If this is an anonymous class, then the annotations after "new" but before the class + // name are stored as super class annotations. Treat them as annotations on the class. + for (Attribute.TypeCompound anno : extendsAndImplementsAnnos) { + if (anno.position.type_index >= SUPERCLASS_INDEX && anno.position.location.isEmpty()) { + type.addAnnotation(anno); } + } } - - /** Adds extends/implements and class annotations to type. Annotates type parameters. */ - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - // ensures that we check that there only valid target types on this class, there are no - // "targeted" locations - super.extractAndApply(); - - // Annotate raw types // TODO: ASK WERNER WHAT THIS MIGHT MEAN? WHAT ACTUALLY GOES HERE? - type.addAnnotations(typeSymbol.getAnnotationMirrors()); - - ElementAnnotationUtil.applyAllElementAnnotations( - declaredType.getTypeArguments(), typeSymbol.getTypeParameters(), atypeFactory); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } + } + + /** Adds extends/implements and class annotations to type. Annotates type parameters. */ + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + // ensures that we check that there only valid target types on this class, there are no + // "targeted" locations + super.extractAndApply(); + + // Annotate raw types // TODO: ASK WERNER WHAT THIS MIGHT MEAN? WHAT ACTUALLY GOES HERE? + type.addAnnotations(typeSymbol.getAnnotationMirrors()); + + ElementAnnotationUtil.applyAllElementAnnotations( + declaredType.getTypeArguments(), typeSymbol.getTypeParameters(), atypeFactory); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java index 0dd2247c1b8..19693cc7643 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java @@ -2,23 +2,20 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.TargetType; - -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; -import org.checkerframework.javacutil.BugInCF; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; +import org.checkerframework.javacutil.BugInCF; /** * Applies Element annotations to a single AnnotatedTypeVariable representing a type parameter. @@ -27,212 +24,208 @@ */ abstract class TypeParamElementAnnotationApplier extends IndexedElementAnnotationApplier { - /** - * Returns true if element is a TYPE_PARAMETER. - * - * @param typeMirror ignored - * @param element the element that might be a TYPE_PARAMETER - * @return true if element is a TYPE_PARAMETER - */ - public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { - return element.getKind() == ElementKind.TYPE_PARAMETER; + /** + * Returns true if element is a TYPE_PARAMETER. + * + * @param typeMirror ignored + * @param element the element that might be a TYPE_PARAMETER + * @return true if element is a TYPE_PARAMETER + */ + public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { + return element.getKind() == ElementKind.TYPE_PARAMETER; + } + + protected final AnnotatedTypeVariable typeParam; + protected final AnnotatedTypeFactory atypeFactory; + + /** + * Returns target type that represents the location of the lower bound of element. + * + * @return target type that represents the location of the lower bound of element + */ + protected abstract TargetType lowerBoundTarget(); + + /** + * Returns target type that represents the location of the upper bound of element. + * + * @return target type that represents the location of the upper bound of element + */ + protected abstract TargetType upperBoundTarget(); + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ TypeParamElementAnnotationApplier( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + this.typeParam = type; + this.atypeFactory = atypeFactory; + } + + /** + * Returns the lower bound and upper bound targets. + * + * @return the lower bound and upper bound targets + */ + @Override + protected TargetType[] annotatedTargets() { + return new TargetType[] {lowerBoundTarget(), upperBoundTarget()}; + } + + /** + * Returns the parameter_index of anno's TypeAnnotationPosition which will actually point to the + * type parameter's index in its enclosing type parameter list. + * + * @return the parameter_index of anno's TypeAnnotationPosition which will actually point to the + * type parameter's index in its enclosing type parameter list + */ + @Override + public int getTypeCompoundIndex(TypeCompound anno) { + return anno.getPosition().parameter_index; + } + + /** + * @param targeted the list of annotations that were on the lower/upper bounds of the type + * parameter + *

Note: When handling type parameters we NEVER add primary annotations to the type + * parameter. Primary annotations are reserved for the use of a type parameter (e.g. @Nullable + * T t; ) + *

If an annotation is present on the type parameter itself, it represents the lower-bound + * annotation of that type parameter. Any annotation on the extends bound of a type parameter + * is placed on that bound. + */ + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + int paramIndex = getElementIndex(); + List upperBoundAnnos = new ArrayList<>(); + List lowerBoundAnnos = new ArrayList<>(); + + for (TypeCompound anno : targeted) { + AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); + AnnotationMirror canonicalAnno = (aliasedAnno != null) ? aliasedAnno : anno; + + if (anno.position.parameter_index != paramIndex + || !atypeFactory.isSupportedQualifier(canonicalAnno)) { + continue; + } + + if (ElementAnnotationUtil.isOnComponentType(anno)) { + applyComponentAnnotation(anno); + } else if (anno.position.type == upperBoundTarget()) { + upperBoundAnnos.add(anno); + } else { + lowerBoundAnnos.add(anno); + } } - protected final AnnotatedTypeVariable typeParam; - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Returns target type that represents the location of the lower bound of element. - * - * @return target type that represents the location of the lower bound of element - */ - protected abstract TargetType lowerBoundTarget(); - - /** - * Returns target type that represents the location of the upper bound of element. - * - * @return target type that represents the location of the upper bound of element - */ - protected abstract TargetType upperBoundTarget(); - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ TypeParamElementAnnotationApplier( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - this.typeParam = type; - this.atypeFactory = atypeFactory; - } - - /** - * Returns the lower bound and upper bound targets. - * - * @return the lower bound and upper bound targets - */ - @Override - protected TargetType[] annotatedTargets() { - return new TargetType[] {lowerBoundTarget(), upperBoundTarget()}; - } - - /** - * Returns the parameter_index of anno's TypeAnnotationPosition which will actually point to the - * type parameter's index in its enclosing type parameter list. - * - * @return the parameter_index of anno's TypeAnnotationPosition which will actually point to the - * type parameter's index in its enclosing type parameter list - */ - @Override - public int getTypeCompoundIndex(TypeCompound anno) { - return anno.getPosition().parameter_index; - } - - /** - * @param targeted the list of annotations that were on the lower/upper bounds of the type - * parameter - *

Note: When handling type parameters we NEVER add primary annotations to the type - * parameter. Primary annotations are reserved for the use of a type parameter - * (e.g. @Nullable T t; ) - *

If an annotation is present on the type parameter itself, it represents the - * lower-bound annotation of that type parameter. Any annotation on the extends bound of a - * type parameter is placed on that bound. - */ - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - int paramIndex = getElementIndex(); - List upperBoundAnnos = new ArrayList<>(); - List lowerBoundAnnos = new ArrayList<>(); - - for (TypeCompound anno : targeted) { - AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); - AnnotationMirror canonicalAnno = (aliasedAnno != null) ? aliasedAnno : anno; - - if (anno.position.parameter_index != paramIndex - || !atypeFactory.isSupportedQualifier(canonicalAnno)) { - continue; - } - - if (ElementAnnotationUtil.isOnComponentType(anno)) { - applyComponentAnnotation(anno); - } else if (anno.position.type == upperBoundTarget()) { - upperBoundAnnos.add(anno); - } else { - lowerBoundAnnos.add(anno); - } + applyLowerBounds(lowerBoundAnnos); + applyUpperBounds(upperBoundAnnos); + } + + /** + * Applies a list of annotations to the upperBound of the type parameter. If the type of the upper + * bound is an intersection we must first find the correct location for each annotation. + */ + private void applyUpperBounds(List upperBounds) { + if (!upperBounds.isEmpty()) { + AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); + + if (upperBoundType.getKind() == TypeKind.INTERSECTION) { + List bounds = ((AnnotatedIntersectionType) upperBoundType).getBounds(); + int boundIndexOffset = ElementAnnotationUtil.getBoundIndexOffset(bounds); + + for (TypeCompound anno : upperBounds) { + int boundIndex = anno.position.bound_index + boundIndexOffset; + + if (boundIndex < 0 || boundIndex > bounds.size()) { + throw new BugInCF( + "Invalid bound index on element annotation ( " + + anno + + " ) " + + "for type ( " + + typeParam + + " ) with " + + "upper bound ( " + + typeParam.getUpperBound() + + " ) " + + "and boundIndex( " + + boundIndex + + " ) "); + } + + bounds.get(boundIndex).replaceAnnotation(anno); // TODO: WHY NOT ADD? } + ((AnnotatedIntersectionType) upperBoundType).copyIntersectionBoundAnnotations(); - applyLowerBounds(lowerBoundAnnos); - applyUpperBounds(upperBoundAnnos); + } else { + upperBoundType.addAnnotations(upperBounds); + } } - - /** - * Applies a list of annotations to the upperBound of the type parameter. If the type of the - * upper bound is an intersection we must first find the correct location for each annotation. - */ - private void applyUpperBounds(List upperBounds) { - if (!upperBounds.isEmpty()) { - AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); - - if (upperBoundType.getKind() == TypeKind.INTERSECTION) { - List bounds = - ((AnnotatedIntersectionType) upperBoundType).getBounds(); - int boundIndexOffset = ElementAnnotationUtil.getBoundIndexOffset(bounds); - - for (TypeCompound anno : upperBounds) { - int boundIndex = anno.position.bound_index + boundIndexOffset; - - if (boundIndex < 0 || boundIndex > bounds.size()) { - throw new BugInCF( - "Invalid bound index on element annotation ( " - + anno - + " ) " - + "for type ( " - + typeParam - + " ) with " - + "upper bound ( " - + typeParam.getUpperBound() - + " ) " - + "and boundIndex( " - + boundIndex - + " ) "); - } - - bounds.get(boundIndex).replaceAnnotation(anno); // TODO: WHY NOT ADD? - } - ((AnnotatedIntersectionType) upperBoundType).copyIntersectionBoundAnnotations(); - - } else { - upperBoundType.addAnnotations(upperBounds); - } - } + } + + /** + * In the event of multiple annotations on an AnnotatedNullType lower bound we want to preserve + * the multiple annotations so that a type.invalid error is issued later. + * + * @param annos the annotations to add to the lower bound + */ + private void applyLowerBounds(List annos) { + if (!annos.isEmpty()) { + AnnotatedTypeMirror lowerBound = typeParam.getLowerBound(); + + for (AnnotationMirror anno : annos) { + lowerBound.addAnnotation(anno); + } } - - /** - * In the event of multiple annotations on an AnnotatedNullType lower bound we want to preserve - * the multiple annotations so that a type.invalid error is issued later. - * - * @param annos the annotations to add to the lower bound - */ - private void applyLowerBounds(List annos) { - if (!annos.isEmpty()) { - AnnotatedTypeMirror lowerBound = typeParam.getLowerBound(); - - for (AnnotationMirror anno : annos) { - lowerBound.addAnnotation(anno); - } + } + + private void addAnnotationToMap( + AnnotatedTypeMirror type, + TypeCompound anno, + Map> typeToAnnos) { + List annoList = typeToAnnos.computeIfAbsent(type, __ -> new ArrayList<>()); + annoList.add(anno); + } + + private void applyComponentAnnotation(TypeCompound anno) + throws UnexpectedAnnotationLocationException { + AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); + + Map> typeToAnnotations = new HashMap<>(); + + if (anno.position.type == upperBoundTarget()) { + if (upperBoundType.getKind() == TypeKind.INTERSECTION) { + List bounds = ((AnnotatedIntersectionType) upperBoundType).getBounds(); + int boundIndex = + anno.position.bound_index + ElementAnnotationUtil.getBoundIndexOffset(bounds); + + if (boundIndex < 0 || boundIndex > bounds.size()) { + throw new BugInCF( + "Invalid bound index on element annotation ( " + + anno + + " ) " + + "for type ( " + + typeParam + + " ) with upper bound ( " + + typeParam.getUpperBound() + + " )"); } + addAnnotationToMap(bounds.get(boundIndex), anno, typeToAnnotations); + } else { + addAnnotationToMap(upperBoundType, anno, typeToAnnotations); + } + } else { + addAnnotationToMap(typeParam.getLowerBound(), anno, typeToAnnotations); } - private void addAnnotationToMap( - AnnotatedTypeMirror type, - TypeCompound anno, - Map> typeToAnnos) { - List annoList = typeToAnnos.computeIfAbsent(type, __ -> new ArrayList<>()); - annoList.add(anno); - } - - private void applyComponentAnnotation(TypeCompound anno) - throws UnexpectedAnnotationLocationException { - AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); - - Map> typeToAnnotations = new HashMap<>(); - - if (anno.position.type == upperBoundTarget()) { - if (upperBoundType.getKind() == TypeKind.INTERSECTION) { - List bounds = - ((AnnotatedIntersectionType) upperBoundType).getBounds(); - int boundIndex = - anno.position.bound_index - + ElementAnnotationUtil.getBoundIndexOffset(bounds); - - if (boundIndex < 0 || boundIndex > bounds.size()) { - throw new BugInCF( - "Invalid bound index on element annotation ( " - + anno - + " ) " - + "for type ( " - + typeParam - + " ) with upper bound ( " - + typeParam.getUpperBound() - + " )"); - } - addAnnotationToMap(bounds.get(boundIndex), anno, typeToAnnotations); - } else { - addAnnotationToMap(upperBoundType, anno, typeToAnnotations); - } - } else { - addAnnotationToMap(typeParam.getLowerBound(), anno, typeToAnnotations); - } - - for (Map.Entry> typeToAnno : - typeToAnnotations.entrySet()) { - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - typeToAnno.getKey(), typeToAnno.getValue()); - } + for (Map.Entry> typeToAnno : + typeToAnnotations.entrySet()) { + ElementAnnotationUtil.annotateViaTypeAnnoPosition(typeToAnno.getKey(), typeToAnno.getValue()); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java index 50ba7969fbe..ade9e6b67a3 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java @@ -5,7 +5,14 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.TargetType; import com.sun.tools.javac.code.TypeAnnotationPosition; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.TypeKind; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -16,332 +23,319 @@ import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.TypeKind; - /** Apply annotations to the use of a type parameter declaration. */ public class TypeVarUseApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new TypeVarUseApplier(type, element, atypeFactory).extractAndApply(); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new TypeVarUseApplier(type, element, atypeFactory).extractAndApply(); + } + + /** The ElementKinds that are accepted by this. */ + private static final ElementKind[] acceptedKinds = { + ElementKind.PARAMETER, + ElementKind.FIELD, + ElementKind.LOCAL_VARIABLE, + ElementKind.RESOURCE_VARIABLE, + ElementKind.METHOD + }; + + /** + * Returns true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable + * component, and the element is not a TYPE_PARAMETER. + * + * @param type the type to test + * @param element the corresponding element + * @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable + * component, and the element is not a TYPE_PARAMETER + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return (type instanceof AnnotatedTypeVariable || isGenericArrayType(type)) + && ElementAnnotationUtil.contains(element.getKind(), acceptedKinds); + } + + /** + * Returns true if type is an array type whose innermost component type is a type variable. + * + * @param type the type to test + * @return true if type is an array type whose innermost component type is a type variable + */ + private static boolean isGenericArrayType(AnnotatedTypeMirror type) { + return type instanceof AnnotatedArrayType + && AnnotatedTypes.innerMostType(type) instanceof AnnotatedTypeVariable; + } + + /** The generic array type, if any. */ + // In order to avoid sprinkling code for type parameter uses all over the various locations + // uses can show up, we also handle generic array types. T [] myTArr; + private final @Nullable AnnotatedArrayType arrayType; + + /** The type variable. */ + private final AnnotatedTypeVariable typeVariable; + + /** The element for the declaration. */ + private final TypeParameterElement declarationElem; + + /** The element for the use. */ + private final Element useElem; + + /** The annotated type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * Create a new TypeVarUseApplier. + * + * @param type the type of the variable use + * @param element the element for the variable use + * @param atypeFactory the type factory + */ + /*package-private*/ TypeVarUseApplier( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { + if (!accepts(type, element)) { + throw new BugInCF( + "TypeParamUseApplier does not accept type/element combination (" + + " type ( " + + type + + " ) element ( " + + element + + " ) "); } - /** The ElementKinds that are accepted by this. */ - private static final ElementKind[] acceptedKinds = { - ElementKind.PARAMETER, - ElementKind.FIELD, - ElementKind.LOCAL_VARIABLE, - ElementKind.RESOURCE_VARIABLE, - ElementKind.METHOD - }; - - /** - * Returns true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type - * variable component, and the element is not a TYPE_PARAMETER. - * - * @param type the type to test - * @param element the corresponding element - * @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type - * variable component, and the element is not a TYPE_PARAMETER - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return (type instanceof AnnotatedTypeVariable || isGenericArrayType(type)) - && ElementAnnotationUtil.contains(element.getKind(), acceptedKinds); + if (isGenericArrayType(type)) { + this.arrayType = (AnnotatedArrayType) type; + this.typeVariable = (AnnotatedTypeVariable) AnnotatedTypes.innerMostType(type); + this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); + this.useElem = element; + this.atypeFactory = atypeFactory; + } else { + this.arrayType = null; + this.typeVariable = (AnnotatedTypeVariable) type; + this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); + this.useElem = element; + this.atypeFactory = atypeFactory; } - - /** - * Returns true if type is an array type whose innermost component type is a type variable. - * - * @param type the type to test - * @return true if type is an array type whose innermost component type is a type variable - */ - private static boolean isGenericArrayType(AnnotatedTypeMirror type) { - return type instanceof AnnotatedArrayType - && AnnotatedTypes.innerMostType(type) instanceof AnnotatedTypeVariable; + } + + /** + * Applies the bound annotations from the declaration of the type parameter and then applies the + * explicit annotations written on the type variable. + * + * @throws UnexpectedAnnotationLocationException if invalid location for an annotation was found + */ + public void extractAndApply() throws UnexpectedAnnotationLocationException { + if (arrayType != null) { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + arrayType, useElem.getAnnotationMirrors()); + } else { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + typeVariable, useElem.getAnnotationMirrors()); } - /** The generic array type, if any. */ - // In order to avoid sprinkling code for type parameter uses all over the various locations - // uses can show up, we also handle generic array types. T [] myTArr; - private final @Nullable AnnotatedArrayType arrayType; - - /** The type variable. */ - private final AnnotatedTypeVariable typeVariable; - - /** The element for the declaration. */ - private final TypeParameterElement declarationElem; - - /** The element for the use. */ - private final Element useElem; - - /** The annotated type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * Create a new TypeVarUseApplier. - * - * @param type the type of the variable use - * @param element the element for the variable use - * @param atypeFactory the type factory - */ - /*package-private*/ TypeVarUseApplier( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { - if (!accepts(type, element)) { - throw new BugInCF( - "TypeParamUseApplier does not accept type/element combination (" - + " type ( " - + type - + " ) element ( " - + element - + " ) "); - } - - if (isGenericArrayType(type)) { - this.arrayType = (AnnotatedArrayType) type; - this.typeVariable = (AnnotatedTypeVariable) AnnotatedTypes.innerMostType(type); - this.declarationElem = - (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); - this.useElem = element; - this.atypeFactory = atypeFactory; - } else { - this.arrayType = null; - this.typeVariable = (AnnotatedTypeVariable) type; - this.declarationElem = - (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); - this.useElem = element; - this.atypeFactory = atypeFactory; - } - } + // apply annotations from the type parameter declaration + ElementAnnotationApplier.apply(typeVariable, declarationElem, atypeFactory); - /** - * Applies the bound annotations from the declaration of the type parameter and then applies the - * explicit annotations written on the type variable. - * - * @throws UnexpectedAnnotationLocationException if invalid location for an annotation was found - */ - public void extractAndApply() throws UnexpectedAnnotationLocationException { - if (arrayType != null) { - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - arrayType, useElem.getAnnotationMirrors()); - } else { - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - typeVariable, useElem.getAnnotationMirrors()); - } + List annotations = getAnnotations(useElem, declarationElem); - // apply annotations from the type parameter declaration - ElementAnnotationApplier.apply(typeVariable, declarationElem, atypeFactory); - - List annotations = getAnnotations(useElem, declarationElem); - - List typeVarAnnotations; - if (arrayType != null) { - // if the outer-most type is an array type then we want to ensure the outer annotations - // are not applied as the type variables primary annotation - typeVarAnnotations = removeComponentAnnotations(arrayType, annotations); - ElementAnnotationUtil.annotateViaTypeAnnoPosition(arrayType, annotations); - } else { - typeVarAnnotations = annotations; - } - - for (Attribute.TypeCompound annotation : typeVarAnnotations) { - typeVariable.replaceAnnotation(annotation); - } + List typeVarAnnotations; + if (arrayType != null) { + // if the outer-most type is an array type then we want to ensure the outer annotations + // are not applied as the type variables primary annotation + typeVarAnnotations = removeComponentAnnotations(arrayType, annotations); + ElementAnnotationUtil.annotateViaTypeAnnoPosition(arrayType, annotations); + } else { + typeVarAnnotations = annotations; } - /** - * Return the annotations that apply to the base component of the array and remove these - * annotations from the parameter. - * - * @param arrayType the array type - * @param annotations the annotations to inspect and modify - * @return the annotations that apply to the base component of the array - */ - private static List removeComponentAnnotations( - AnnotatedArrayType arrayType, List annotations) { - List componentAnnotations = new ArrayList<>(); - - for (int i = 0; i < annotations.size(); ) { - Attribute.TypeCompound anno = annotations.get(i); - if (isBaseComponent(arrayType, anno)) { - componentAnnotations.add(anno); - annotations.remove(anno); - } else { - i++; - } - } - - return componentAnnotations; + for (Attribute.TypeCompound annotation : typeVarAnnotations) { + typeVariable.replaceAnnotation(annotation); } - - /** - * Return true if anno applies to the base component of arrayType. - * - * @param arrayType the array type - * @param anno the annotation to inspect - * @return true if anno applies to the base component of arrayType - */ - private static boolean isBaseComponent( - AnnotatedArrayType arrayType, Attribute.TypeCompound anno) { - try { - return ElementAnnotationUtil.getTypeAtLocation(arrayType, anno.getPosition().location) - .getKind() - == TypeKind.TYPEVAR; - } catch (UnexpectedAnnotationLocationException ex) { - return false; - } + } + + /** + * Return the annotations that apply to the base component of the array and remove these + * annotations from the parameter. + * + * @param arrayType the array type + * @param annotations the annotations to inspect and modify + * @return the annotations that apply to the base component of the array + */ + private static List removeComponentAnnotations( + AnnotatedArrayType arrayType, List annotations) { + List componentAnnotations = new ArrayList<>(); + + for (int i = 0; i < annotations.size(); ) { + Attribute.TypeCompound anno = annotations.get(i); + if (isBaseComponent(arrayType, anno)) { + componentAnnotations.add(anno); + annotations.remove(anno); + } else { + i++; + } } - /** - * Depending on what element type the annotations are stored on, the relevant annotations might - * be stored with different annotation positions. getAnnotations finds the correct annotations - * by annotation position and element kind and returns them - */ - private static List getAnnotations( - Element useElem, Element declarationElem) { - final List annotations; - switch (useElem.getKind()) { - case METHOD: - annotations = getReturnAnnos(useElem); - break; - - case PARAMETER: - annotations = getParameterAnnos(useElem); - break; - - case FIELD: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - annotations = getVariableAnnos(useElem); - break; - - default: - throw new BugInCF( - "TypeVarUseApplier::extractAndApply : " - + "Unhandled element kind " - + useElem.getKind() - + "useElem ( " - + useElem - + " ) " - + "declarationElem ( " - + declarationElem - + " ) "); - } - - return annotations; + return componentAnnotations; + } + + /** + * Return true if anno applies to the base component of arrayType. + * + * @param arrayType the array type + * @param anno the annotation to inspect + * @return true if anno applies to the base component of arrayType + */ + private static boolean isBaseComponent( + AnnotatedArrayType arrayType, Attribute.TypeCompound anno) { + try { + return ElementAnnotationUtil.getTypeAtLocation(arrayType, anno.getPosition().location) + .getKind() + == TypeKind.TYPEVAR; + } catch (UnexpectedAnnotationLocationException ex) { + return false; + } + } + + /** + * Depending on what element type the annotations are stored on, the relevant annotations might be + * stored with different annotation positions. getAnnotations finds the correct annotations by + * annotation position and element kind and returns them + */ + private static List getAnnotations( + Element useElem, Element declarationElem) { + final List annotations; + switch (useElem.getKind()) { + case METHOD: + annotations = getReturnAnnos(useElem); + break; + + case PARAMETER: + annotations = getParameterAnnos(useElem); + break; + + case FIELD: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + annotations = getVariableAnnos(useElem); + break; + + default: + throw new BugInCF( + "TypeVarUseApplier::extractAndApply : " + + "Unhandled element kind " + + useElem.getKind() + + "useElem ( " + + useElem + + " ) " + + "declarationElem ( " + + declarationElem + + " ) "); } - /** - * Returns annotations on an element that apply to variable declarations. - * - * @param variableElem the element whose annotations to check - * @return annotations on an element that apply to variable declarations - */ - private static List getVariableAnnos(Element variableElem) { - VarSymbol varSymbol = (VarSymbol) variableElem; - List annotations = new ArrayList<>(); - - for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) { - TypeAnnotationPosition pos = anno.position; - switch (pos.type) { - case FIELD: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - annotations.add(anno); - break; - - default: - } - } - - return annotations; + return annotations; + } + + /** + * Returns annotations on an element that apply to variable declarations. + * + * @param variableElem the element whose annotations to check + * @return annotations on an element that apply to variable declarations + */ + private static List getVariableAnnos(Element variableElem) { + VarSymbol varSymbol = (VarSymbol) variableElem; + List annotations = new ArrayList<>(); + + for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) { + TypeAnnotationPosition pos = anno.position; + switch (pos.type) { + case FIELD: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + annotations.add(anno); + break; + + default: + } } - /** - * Currently, the metadata for storing annotations (i.e. the Attribute.TypeCompounds) is null - * for binary-only parameters and type parameters. However, it is present on the method. So in - * order to ensure that we correctly retrieve the annotations we need to index from the method - * and retrieve the annotations from its metadata. - * - * @return a list of annotations that were found on METHOD_FORMAL_PARAMETERS that match the - * parameter index of the input element in the parent methods formal parameter list - */ - private static List getParameterAnnos(Element paramElem) { - Element enclosingElement = paramElem.getEnclosingElement(); - if (!(enclosingElement instanceof ExecutableElement)) { - throw new BugInCF( - "Bad element passed to TypeFromElement.getTypeParameterAnnotationAttributes: " - + "element: " - + paramElem - + " not found in enclosing executable: " - + enclosingElement); - } + return annotations; + } + + /** + * Currently, the metadata for storing annotations (i.e. the Attribute.TypeCompounds) is null for + * binary-only parameters and type parameters. However, it is present on the method. So in order + * to ensure that we correctly retrieve the annotations we need to index from the method and + * retrieve the annotations from its metadata. + * + * @return a list of annotations that were found on METHOD_FORMAL_PARAMETERS that match the + * parameter index of the input element in the parent methods formal parameter list + */ + private static List getParameterAnnos(Element paramElem) { + Element enclosingElement = paramElem.getEnclosingElement(); + if (!(enclosingElement instanceof ExecutableElement)) { + throw new BugInCF( + "Bad element passed to TypeFromElement.getTypeParameterAnnotationAttributes: " + + "element: " + + paramElem + + " not found in enclosing executable: " + + enclosingElement); + } - MethodSymbol enclosingMethod = (MethodSymbol) enclosingElement; + MethodSymbol enclosingMethod = (MethodSymbol) enclosingElement; - if (enclosingMethod.getKind() != ElementKind.CONSTRUCTOR - && enclosingMethod.getKind() != ElementKind.METHOD) { - // Initializer blocks don't have parameters, so there is nothing to do. - return Collections.emptyList(); - } + if (enclosingMethod.getKind() != ElementKind.CONSTRUCTOR + && enclosingMethod.getKind() != ElementKind.METHOD) { + // Initializer blocks don't have parameters, so there is nothing to do. + return Collections.emptyList(); + } - // TODO: for the parameter in a lambda expression, the enclosingMethod isn't - // the lambda expression. Does this read the correct annotations? + // TODO: for the parameter in a lambda expression, the enclosingMethod isn't + // the lambda expression. Does this read the correct annotations? - int paramIndex = enclosingMethod.getParameters().indexOf(paramElem); - List annotations = enclosingMethod.getRawTypeAttributes(); + int paramIndex = enclosingMethod.getParameters().indexOf(paramElem); + List annotations = enclosingMethod.getRawTypeAttributes(); - List result = new ArrayList<>(); - for (Attribute.TypeCompound typeAnno : annotations) { - if (typeAnno.position.type == TargetType.METHOD_FORMAL_PARAMETER) { - if (typeAnno.position.parameter_index == paramIndex) { - result.add(typeAnno); - } - } + List result = new ArrayList<>(); + for (Attribute.TypeCompound typeAnno : annotations) { + if (typeAnno.position.type == TargetType.METHOD_FORMAL_PARAMETER) { + if (typeAnno.position.parameter_index == paramIndex) { + result.add(typeAnno); } - - return result; + } } - /** - * Returns the annotations on the return type of the input ExecutableElement. - * - * @param methodElem the method whose return type annotations to return - * @return the annotations on the return type of the input ExecutableElement - */ - private static List getReturnAnnos(Element methodElem) { - if (!(methodElem instanceof ExecutableElement)) { - throw new BugInCF( - "Bad element passed to TypeVarUseApplier.getReturnAnnos:" + methodElem); - } - - MethodSymbol enclosingMethod = (MethodSymbol) methodElem; + return result; + } + + /** + * Returns the annotations on the return type of the input ExecutableElement. + * + * @param methodElem the method whose return type annotations to return + * @return the annotations on the return type of the input ExecutableElement + */ + private static List getReturnAnnos(Element methodElem) { + if (!(methodElem instanceof ExecutableElement)) { + throw new BugInCF("Bad element passed to TypeVarUseApplier.getReturnAnnos:" + methodElem); + } - List annotations = enclosingMethod.getRawTypeAttributes(); - List result = new ArrayList<>(); - for (Attribute.TypeCompound typeAnno : annotations) { - if (typeAnno.position.type == TargetType.METHOD_RETURN) { - result.add(typeAnno); - } - } + MethodSymbol enclosingMethod = (MethodSymbol) methodElem; - return result; + List annotations = enclosingMethod.getRawTypeAttributes(); + List result = new ArrayList<>(); + for (Attribute.TypeCompound typeAnno : annotations) { + if (typeAnno.position.type == TargetType.METHOD_RETURN) { + result.add(typeAnno); + } } + + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java index 27012830236..9b17202be37 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java @@ -4,133 +4,129 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; -import org.checkerframework.javacutil.BugInCF; - import java.util.List; - import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; +import org.checkerframework.javacutil.BugInCF; /** * Applies annotations to variable declaration (providing they are not the use of a TYPE_PARAMETER). */ public class VariableApplier extends TargetedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply(AnnotatedTypeMirror type, Element element) - throws UnexpectedAnnotationLocationException { - new VariableApplier(type, element).extractAndApply(); - } - - /** The accepted element kinds. */ - private static final ElementKind[] acceptedKinds = { - ElementKind.LOCAL_VARIABLE, ElementKind.RESOURCE_VARIABLE, ElementKind.EXCEPTION_PARAMETER - }; - - /** - * Returns true if this is a variable declaration including fields an enum constants. - * - * @param typeMirror ignored - * @return true if this is a variable declaration including fields an enum constants - */ - public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { - return ElementAnnotationUtil.contains(element.getKind(), acceptedKinds) - || element.getKind().isField(); - } - - /** The variable symbol. */ - private final Symbol.VarSymbol varSymbol; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - */ - /*package-private*/ VariableApplier(AnnotatedTypeMirror type, Element element) { - super(type, element); - varSymbol = (Symbol.VarSymbol) element; - - if (type.getKind() == TypeKind.UNION - && element.getKind() != ElementKind.EXCEPTION_PARAMETER) { - throw new BugInCF( - "Union types only allowed for exception parameters. " - + "Type: " - + type - + " for element: " - + element); - } - // TODO: need a way to split the union types into the right alternative - // to use for the annotation. The exception_index is probably what we - // need to look at, but it might not be set at this point. - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] { - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.FIELD - }; - - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.CLASS_EXTENDS - }; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - @Override - protected Iterable getRawTypeAttributes() { - return varSymbol.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } - - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); - } - - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - // Add declaration annotations to the local variable type - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - type, varSymbol.getAnnotationMirrors()); - super.extractAndApply(); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply(AnnotatedTypeMirror type, Element element) + throws UnexpectedAnnotationLocationException { + new VariableApplier(type, element).extractAndApply(); + } + + /** The accepted element kinds. */ + private static final ElementKind[] acceptedKinds = { + ElementKind.LOCAL_VARIABLE, ElementKind.RESOURCE_VARIABLE, ElementKind.EXCEPTION_PARAMETER + }; + + /** + * Returns true if this is a variable declaration including fields an enum constants. + * + * @param typeMirror ignored + * @return true if this is a variable declaration including fields an enum constants + */ + public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { + return ElementAnnotationUtil.contains(element.getKind(), acceptedKinds) + || element.getKind().isField(); + } + + /** The variable symbol. */ + private final Symbol.VarSymbol varSymbol; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + */ + /*package-private*/ VariableApplier(AnnotatedTypeMirror type, Element element) { + super(type, element); + varSymbol = (Symbol.VarSymbol) element; + + if (type.getKind() == TypeKind.UNION && element.getKind() != ElementKind.EXCEPTION_PARAMETER) { + throw new BugInCF( + "Union types only allowed for exception parameters. " + + "Type: " + + type + + " for element: " + + element); } + // TODO: need a way to split the union types into the right alternative + // to use for the annotation. The exception_index is probably what we + // need to look at, but it might not be set at this point. + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] { + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.FIELD + }; + + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.CLASS_EXTENDS + }; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + @Override + protected Iterable getRawTypeAttributes() { + return varSymbol.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } + + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); + } + + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + // Add declaration annotations to the local variable type + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + type, varSymbol.getAnnotationMirrors()); + super.extractAndApply(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java index 8521fd18461..b5106d0e4cb 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java @@ -4,7 +4,25 @@ import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Symbol.MethodSymbol; - +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -41,27 +59,6 @@ import org.plumelib.util.IPair; import org.plumelib.util.StringsPlume; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - /** * An implementation of TypeArgumentInference that mostly follows the process outlined in JLS7 See * the JLS 7: */ public class DefaultTypeArgumentInference implements TypeArgumentInference { - private final EqualitiesSolver equalitiesSolver = new EqualitiesSolver(); - private final SupertypesSolver supertypesSolver = new SupertypesSolver(); - private final SubtypesSolver subtypesSolver = new SubtypesSolver(); - private final ConstraintMapBuilder constraintMapBuilder = new ConstraintMapBuilder(); - - private final boolean showInferenceSteps; - - public DefaultTypeArgumentInference(AnnotatedTypeFactory typeFactory) { - this.showInferenceSteps = typeFactory.getChecker().hasOption("showInferenceSteps"); + private final EqualitiesSolver equalitiesSolver = new EqualitiesSolver(); + private final SupertypesSolver supertypesSolver = new SupertypesSolver(); + private final SubtypesSolver subtypesSolver = new SubtypesSolver(); + private final ConstraintMapBuilder constraintMapBuilder = new ConstraintMapBuilder(); + + private final boolean showInferenceSteps; + + public DefaultTypeArgumentInference(AnnotatedTypeFactory typeFactory) { + this.showInferenceSteps = typeFactory.getChecker().hasOption("showInferenceSteps"); + } + + @Override + public Map inferTypeArgs( + AnnotatedTypeFactory typeFactory, + ExpressionTree expressionTree, + ExecutableElement methodElem, + AnnotatedExecutableType methodType) { + + List argTypes = + TypeArgInferenceUtil.getArgumentTypes(expressionTree, typeFactory); + TreePath pathToExpression = typeFactory.getPath(expressionTree); + assert pathToExpression != null; + AnnotatedTypeMirror assignedTo = TypeArgInferenceUtil.assignedTo(typeFactory, pathToExpression); + + SourceChecker checker = typeFactory.getChecker(); + + if (showInferenceSteps) { + checker.message( + Diagnostic.Kind.NOTE, + "DTAI: expression: %s%n argTypes: %s%n assignedTo: %s", + expressionTree.toString().replace(System.lineSeparator(), " "), + argTypes, + assignedTo); } - @Override - public Map inferTypeArgs( - AnnotatedTypeFactory typeFactory, - ExpressionTree expressionTree, - ExecutableElement methodElem, - AnnotatedExecutableType methodType) { - - List argTypes = - TypeArgInferenceUtil.getArgumentTypes(expressionTree, typeFactory); - TreePath pathToExpression = typeFactory.getPath(expressionTree); - assert pathToExpression != null; - AnnotatedTypeMirror assignedTo = - TypeArgInferenceUtil.assignedTo(typeFactory, pathToExpression); - - SourceChecker checker = typeFactory.getChecker(); - - if (showInferenceSteps) { - checker.message( - Diagnostic.Kind.NOTE, - "DTAI: expression: %s%n argTypes: %s%n assignedTo: %s", - expressionTree.toString().replace(System.lineSeparator(), " "), - argTypes, - assignedTo); - } - - Set targets = TypeArgInferenceUtil.methodTypeToTargets(methodType); - - if (TreePathUtil.enclosingNonParen(pathToExpression).first.getKind() - == Tree.Kind.LAMBDA_EXPRESSION - || (assignedTo == null - && TreePathUtil.getAssignmentContext(pathToExpression) != null)) { - // If the type of the assignment context isn't found, but the expression is assigned, - // then don't attempt to infer type arguments, because the Java type inferred will be - // incorrect. The assignment type is null when it includes uninferred type arguments. - // For example: - // T outMethod() - // void inMethod(U u); - // inMethod(outMethod()) - // would require solving the constraints for both type argument inferences - // simultaneously - // Also, if the parent of the expression is a lambda, then the type arguments cannot be - // inferred. - Map inferredArgs = new LinkedHashMap<>(); - handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); - return inferredArgs; - } - if (assignedTo == null) { - assignedTo = typeFactory.getDummyAssignedTo(expressionTree); - } - Map inferredArgs; - try { - inferredArgs = - infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, true); - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " after infer: %s", inferredArgs); - } - handleNullTypeArguments( - typeFactory, - methodElem, - methodType, - argTypes, - assignedTo, - targets, - inferredArgs); - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " after handleNull: %s", inferredArgs); - } - } catch (Exception ex) { - // Catch any errors thrown by inference. - inferredArgs = new LinkedHashMap<>(); - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " exception: %s", ex.getLocalizedMessage()); - } - } + Set targets = TypeArgInferenceUtil.methodTypeToTargets(methodType); + + if (TreePathUtil.enclosingNonParen(pathToExpression).first.getKind() + == Tree.Kind.LAMBDA_EXPRESSION + || (assignedTo == null && TreePathUtil.getAssignmentContext(pathToExpression) != null)) { + // If the type of the assignment context isn't found, but the expression is assigned, + // then don't attempt to infer type arguments, because the Java type inferred will be + // incorrect. The assignment type is null when it includes uninferred type arguments. + // For example: + // T outMethod() + // void inMethod(U u); + // inMethod(outMethod()) + // would require solving the constraints for both type argument inferences + // simultaneously + // Also, if the parent of the expression is a lambda, then the type arguments cannot be + // inferred. + Map inferredArgs = new LinkedHashMap<>(); + handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); + return inferredArgs; + } + if (assignedTo == null) { + assignedTo = typeFactory.getDummyAssignedTo(expressionTree); + } + Map inferredArgs; + try { + inferredArgs = + infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, true); + if (showInferenceSteps) { + checker.message(Diagnostic.Kind.NOTE, " after infer: %s", inferredArgs); + } + handleNullTypeArguments( + typeFactory, methodElem, methodType, argTypes, assignedTo, targets, inferredArgs); + if (showInferenceSteps) { + checker.message(Diagnostic.Kind.NOTE, " after handleNull: %s", inferredArgs); + } + } catch (Exception ex) { + // Catch any errors thrown by inference. + inferredArgs = new LinkedHashMap<>(); + if (showInferenceSteps) { + checker.message(Diagnostic.Kind.NOTE, " exception: %s", ex.getLocalizedMessage()); + } + } - handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); + handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " results: %s", inferredArgs); - } - try { - return TypeArgInferenceUtil.correctResults( - inferredArgs, - expressionTree, - (ExecutableType) methodElem.asType(), - typeFactory); - } catch (Throwable ex) { - // Ignore any exceptions - return inferredArgs; - } + if (showInferenceSteps) { + checker.message(Diagnostic.Kind.NOTE, " results: %s", inferredArgs); } - - /** - * If one of the inferredArgs are NullType, then re-run inference ignoring null method - * arguments. Then lub the result of the second inference with the NullType and put the new - * result back into inferredArgs. - * - * @param typeFactory type factory - * @param methodElem element of the method - * @param methodType annotated type of the method - * @param argTypes annotated types of arguments to the method - * @param assignedTo annotated type to which the result of the method invocation is assigned - * @param targets set of type variables to infer - * @param inferredArgs map of type variables to the annotated types of their type arguments - */ - private void handleNullTypeArguments( - AnnotatedTypeFactory typeFactory, - ExecutableElement methodElem, - AnnotatedExecutableType methodType, - List argTypes, - AnnotatedTypeMirror assignedTo, - Set targets, - Map inferredArgs) { - if (!hasNullType(inferredArgs)) { - return; + try { + return TypeArgInferenceUtil.correctResults( + inferredArgs, expressionTree, (ExecutableType) methodElem.asType(), typeFactory); + } catch (Throwable ex) { + // Ignore any exceptions + return inferredArgs; + } + } + + /** + * If one of the inferredArgs are NullType, then re-run inference ignoring null method arguments. + * Then lub the result of the second inference with the NullType and put the new result back into + * inferredArgs. + * + * @param typeFactory type factory + * @param methodElem element of the method + * @param methodType annotated type of the method + * @param argTypes annotated types of arguments to the method + * @param assignedTo annotated type to which the result of the method invocation is assigned + * @param targets set of type variables to infer + * @param inferredArgs map of type variables to the annotated types of their type arguments + */ + private void handleNullTypeArguments( + AnnotatedTypeFactory typeFactory, + ExecutableElement methodElem, + AnnotatedExecutableType methodType, + List argTypes, + AnnotatedTypeMirror assignedTo, + Set targets, + Map inferredArgs) { + if (!hasNullType(inferredArgs)) { + return; + } + Map inferredArgsWithoutNull = + infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, false); + for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) { + TypeVariable typeVar = atv.getUnderlyingType(); + AnnotatedTypeMirror result = inferredArgs.get(typeVar); + if (result == null) { + AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar); + if (withoutNullResult != null) { + inferredArgs.put(typeVar, withoutNullResult); } - Map inferredArgsWithoutNull = - infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, false); - for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) { - TypeVariable typeVar = atv.getUnderlyingType(); - AnnotatedTypeMirror result = inferredArgs.get(typeVar); - if (result == null) { - AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar); - if (withoutNullResult != null) { - inferredArgs.put(typeVar, withoutNullResult); - } - } else if (result.getKind() == TypeKind.NULL) { - AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar); - if (withoutNullResult == null) { - // withoutNullResult is null when the only constraint on a type argument is - // where a method argument is null. - withoutNullResult = atv.getUpperBound().deepCopy(); - } - AnnotatedTypeMirror lub = - AnnotatedTypes.leastUpperBound(typeFactory, withoutNullResult, result); - inferredArgs.put(typeVar, lub); - } + } else if (result.getKind() == TypeKind.NULL) { + AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar); + if (withoutNullResult == null) { + // withoutNullResult is null when the only constraint on a type argument is + // where a method argument is null. + withoutNullResult = atv.getUpperBound().deepCopy(); } + AnnotatedTypeMirror lub = + AnnotatedTypes.leastUpperBound(typeFactory, withoutNullResult, result); + inferredArgs.put(typeVar, lub); + } } + } - private boolean hasNullType(Map inferredArgs) { - for (AnnotatedTypeMirror atm : inferredArgs.values()) { - if (atm.getKind() == TypeKind.NULL) { - return true; - } - } - return false; + private boolean hasNullType(Map inferredArgs) { + for (AnnotatedTypeMirror atm : inferredArgs.values()) { + if (atm.getKind() == TypeKind.NULL) { + return true; + } + } + return false; + } + + /** + * This algorithm works as follows: + * + *

+ */ + private Map infer( + AnnotatedTypeFactory typeFactory, + List argumentTypes, + AnnotatedTypeMirror assignedTo, + ExecutableElement methodElem, + AnnotatedExecutableType methodType, + Set targets, + boolean useNullArguments) { + + // 1. Step 1 - Build up argument constraints + // The AFConstraints for arguments are used also in the + Set afArgumentConstraints = + createArgumentAFConstraints( + typeFactory, argumentTypes, methodType, targets, useNullArguments); + + // 2. Step 2 - Solve the constraints. + IPair argInference = + inferFromArguments(typeFactory, afArgumentConstraints, targets); + + InferenceResult fromArgEqualities = argInference.first; // result 2.a + InferenceResult fromArgSubandSupers = argInference.second; // result 2.b + + clampToLowerBound(fromArgSubandSupers, methodType.getTypeVariables(), typeFactory); + + // if this method invocation's has a return type and it is assigned/pseudo-assigned to + // a variable, assignedTo is the type of that variable + if (assignedTo == null) { + fromArgEqualities.mergeSubordinate(fromArgSubandSupers); + + return fromArgEqualities.toAtmMap(); + } // else + + AnnotatedTypeMirror declaredReturnType = methodType.getReturnType(); + AnnotatedTypeMirror boxedReturnType; + if (declaredReturnType == null) { + boxedReturnType = null; + } else if (declaredReturnType.getKind().isPrimitive()) { + boxedReturnType = typeFactory.getBoxedType((AnnotatedPrimitiveType) declaredReturnType); + } else { + boxedReturnType = declaredReturnType; } - /** - * This algorithm works as follows: - * - *
    - * - *
  • 1. Build Argument Constraints -- create a set of constraints using the arguments to the - * type parameter declarations, the formal parameters, and the arguments to the method - * call - *
  • 2. Solve Argument Constraints -- Create two solutions from the arguments. - *
      - *
    1. Equality Arg Solution: Solution inferred from arguments used in an invariant - * position (i.e. from equality constraints) - *
    2. Supertypes Arg Solution: Solution inferred from constraints in which the - * parameter is a supertype of argument types. These are kept separate and merged - * later. - *
    - * Note: If there is NO assignment context we just combine the results from 2.a and 2.b, - * giving preference to those in 2.a, and return the result. - *
  • 3. Build and Solve Initial Assignment Constraints -- Create a set of constraints from - * the assignment context WITHOUT substituting either solution from step 2. - *
  • 4. Combine the solutions from steps 2.b and 3. This handles cases like the following: - *
    {@code
    -     *  List method(T t1) {}
    -     * List<@Nullable String> nl = method("");
    -     * }
    - * If we use just the arguments to infer T we will infer @NonNull String (since the lub of - * all arguments would be @NonNull String). However, this would cause the assignment to - * fail. Instead, since {@literal @NonNull String <: @Nullable String}, we can safely - * infer T to be @Nullable String and both the argument types and the assignment types are - * compatible. In step 4, we combine the results of Step 2.b (which came from lubbing - * argument and argument component types) with the solution from equality constraints via - * the assignment context. - *

    Note, we always give preference to the results inferred from method arguments if - * there is a conflict between the steps 2 and 4. For example: - *

    {@code
    -     *  List method(T t1) {}
    -     * List<@NonNull String> nl = method(null);
    -     * }
    - * In the above example, the null argument requires that T must be @Nullable String. But - * the assignment context requires that the T must be @NonNull String. But, in this case - * if we use @NonNull String the argument "null" is invalid. In this case, we - * use @Nullable String and report an assignment.type.incompatible because we ALWAYS favor - * the arguments over the assignment context. - *
  • 5. Combine the result from 2.a and step 4, if there is a conflict use the result from - * step 2.a - *

    Suppose we have the following: - *

    {@code
    -     *  void method(List<@NonNull T> t, @Initialized Tt) { ... }
    -     * List<@FBCBottom String> lBottom = ...;
    -     * method( lbBottom, "nonNullString" );
    -     * }
    - * From the first argument we can infer that T must be exactly @FBCBottom String but we - * cannot infer anything for the Nullness hierarchy. For the second argument we can infer - * that T is at most @NonNull String but we can infer nothing in the initialization - * hierarchy. In this step we combine these two results, always favoring the equality - * constraints if there is a conflict. For the above example we would infer the following: - *
    {@code
    -     * T => @FBCBottom @NonNull String
    -     * }
    - * Another case covered in this step is: - *
    {@code
    -     *  List method(List t1) {}
    -     * List<@NonNull String> nonNullList = new ArrayList<>();
    -     * List<@Nullable String> nl = method(nonNullList);
    -     * }
    - * The above assignment should fail because T is forced to be both @NonNull and @Nullable. - * In cases like these, we use @NonNull String because we always favor constraints from - * the arguments over the assignment context. - *
  • 6. Infer from Assignment Context Finally, the JLS states that we should substitute the - * types we have inferred up until this point back into the original argument constraints. - * We should then combine the constraints we get from the assignment context and solve - * using the greatest lower bounds of all of the constraints of the form: {@literal F :> - * U} (these are referred to as "subtypes" in the ConstraintMap.TargetConstraints). - *
  • 7. Merge the result from steps 5 and 6 giving preference to 5 (the argument - * constraints). Return the result. - *
- */ - private Map infer( - AnnotatedTypeFactory typeFactory, - List argumentTypes, - AnnotatedTypeMirror assignedTo, - ExecutableElement methodElem, - AnnotatedExecutableType methodType, - Set targets, - boolean useNullArguments) { - - // 1. Step 1 - Build up argument constraints - // The AFConstraints for arguments are used also in the - Set afArgumentConstraints = - createArgumentAFConstraints( - typeFactory, argumentTypes, methodType, targets, useNullArguments); - - // 2. Step 2 - Solve the constraints. - IPair argInference = - inferFromArguments(typeFactory, afArgumentConstraints, targets); - - InferenceResult fromArgEqualities = argInference.first; // result 2.a - InferenceResult fromArgSubandSupers = argInference.second; // result 2.b - - clampToLowerBound(fromArgSubandSupers, methodType.getTypeVariables(), typeFactory); - - // if this method invocation's has a return type and it is assigned/pseudo-assigned to - // a variable, assignedTo is the type of that variable - if (assignedTo == null) { - fromArgEqualities.mergeSubordinate(fromArgSubandSupers); - - return fromArgEqualities.toAtmMap(); - } // else - - AnnotatedTypeMirror declaredReturnType = methodType.getReturnType(); - AnnotatedTypeMirror boxedReturnType; - if (declaredReturnType == null) { - boxedReturnType = null; - } else if (declaredReturnType.getKind().isPrimitive()) { - boxedReturnType = typeFactory.getBoxedType((AnnotatedPrimitiveType) declaredReturnType); - } else { - boxedReturnType = declaredReturnType; - } - - InferenceResult fromArguments = fromArgEqualities; - if (!((MethodSymbol) methodElem).isConstructor()) { - // Step 3 - Infer a solution from the equality constraints in the assignment context - InferenceResult fromAssignmentEqualities = - inferFromAssignmentEqualities( - assignedTo, boxedReturnType, targets, typeFactory); - - // Step 4 - Combine the results from 2.b and step 3 - InferenceResult combinedSupertypesAndAssignment = - combineSupertypeAndAssignmentResults( - targets, typeFactory, fromAssignmentEqualities, fromArgSubandSupers); - - // Step 5 - Combine the result from 2.a and step 4, if there is a conflict use the - // result from step 2.a - fromArgEqualities.mergeSubordinate(combinedSupertypesAndAssignment); - - // if we don't have a result for all type arguments - // Step 6 - Infer the type arguments from the greatest-lower-bounds of all "subtype" - // constraints - if (!fromArguments.isComplete(targets)) { - InferenceResult fromAssignment = - inferFromAssignment( - assignedTo, - boxedReturnType, - methodType, - afArgumentConstraints, - fromArguments, - targets, - typeFactory); - - // Step 7 - Merge the argument and the assignment constraints - fromArguments.mergeSubordinate(fromAssignment); - } - - } else { - - fromArguments.mergeSubordinate(fromArgSubandSupers); - } - - return fromArguments.toAtmMap(); + InferenceResult fromArguments = fromArgEqualities; + if (!((MethodSymbol) methodElem).isConstructor()) { + // Step 3 - Infer a solution from the equality constraints in the assignment context + InferenceResult fromAssignmentEqualities = + inferFromAssignmentEqualities(assignedTo, boxedReturnType, targets, typeFactory); + + // Step 4 - Combine the results from 2.b and step 3 + InferenceResult combinedSupertypesAndAssignment = + combineSupertypeAndAssignmentResults( + targets, typeFactory, fromAssignmentEqualities, fromArgSubandSupers); + + // Step 5 - Combine the result from 2.a and step 4, if there is a conflict use the + // result from step 2.a + fromArgEqualities.mergeSubordinate(combinedSupertypesAndAssignment); + + // if we don't have a result for all type arguments + // Step 6 - Infer the type arguments from the greatest-lower-bounds of all "subtype" + // constraints + if (!fromArguments.isComplete(targets)) { + InferenceResult fromAssignment = + inferFromAssignment( + assignedTo, + boxedReturnType, + methodType, + afArgumentConstraints, + fromArguments, + targets, + typeFactory); + + // Step 7 - Merge the argument and the assignment constraints + fromArguments.mergeSubordinate(fromAssignment); + } + + } else { + + fromArguments.mergeSubordinate(fromArgSubandSupers); } - /** - * If we have inferred a type argument from the supertype constraints and this type argument is - * BELOW the lower bound, make it AT the lower bound. - * - *

e.g. - * - *

{@code
-     * <@Initialized T extends @Initialized Object> void id(T t) { return t; }
-     * id(null);
-     *
-     * // The invocation of id will result in a type argument with primary annotations of @FBCBottom @Nullable
-     * // but this is below the lower bound of T in the initialization hierarchy so instead replace
-     * // @FBCBottom with @Initialized
-     *
-     * // This should happen ONLY with supertype constraints because raising the primary annotation would still
-     * // be valid for these constraints (since we just LUB the arguments involved) but would violate any
-     * // equality constraints
-     * }
- * - * TODO: NOTE WE ONLY DO THIS FOR InferredType results for now but we should probably include - * targets as well - * - * @param fromArgSupertypes types inferred from LUBbing types from the arguments to the formal - * parameters - * @param targetDeclarations the declared types of the type parameters whose arguments are being - * inferred - */ - private void clampToLowerBound( - InferenceResult fromArgSupertypes, - List targetDeclarations, - AnnotatedTypeFactory typeFactory) { - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - - for (AnnotatedTypeVariable targetDecl : targetDeclarations) { - InferredValue inferred = fromArgSupertypes.get(targetDecl.getUnderlyingType()); - if (inferred instanceof InferredType) { - AnnotatedTypeMirror lowerBoundAsArgument = targetDecl.getLowerBound(); - for (AnnotationMirror top : tops) { - AnnotationMirror lowerBoundAnno = - lowerBoundAsArgument.getEffectiveAnnotationInHierarchy(top); - AnnotatedTypeMirror inferredType = ((InferredType) inferred).type; - AnnotationMirror argAnno = inferredType.getEffectiveAnnotationInHierarchy(top); - if (qualHierarchy.isSubtypeQualifiersOnly(argAnno, lowerBoundAnno)) { - inferredType.replaceAnnotation(lowerBoundAnno); - } - } - } + return fromArguments.toAtmMap(); + } + + /** + * If we have inferred a type argument from the supertype constraints and this type argument is + * BELOW the lower bound, make it AT the lower bound. + * + *

e.g. + * + *

{@code
+   * <@Initialized T extends @Initialized Object> void id(T t) { return t; }
+   * id(null);
+   *
+   * // The invocation of id will result in a type argument with primary annotations of @FBCBottom @Nullable
+   * // but this is below the lower bound of T in the initialization hierarchy so instead replace
+   * // @FBCBottom with @Initialized
+   *
+   * // This should happen ONLY with supertype constraints because raising the primary annotation would still
+   * // be valid for these constraints (since we just LUB the arguments involved) but would violate any
+   * // equality constraints
+   * }
+ * + * TODO: NOTE WE ONLY DO THIS FOR InferredType results for now but we should probably include + * targets as well + * + * @param fromArgSupertypes types inferred from LUBbing types from the arguments to the formal + * parameters + * @param targetDeclarations the declared types of the type parameters whose arguments are being + * inferred + */ + private void clampToLowerBound( + InferenceResult fromArgSupertypes, + List targetDeclarations, + AnnotatedTypeFactory typeFactory) { + QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); + AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); + + for (AnnotatedTypeVariable targetDecl : targetDeclarations) { + InferredValue inferred = fromArgSupertypes.get(targetDecl.getUnderlyingType()); + if (inferred instanceof InferredType) { + AnnotatedTypeMirror lowerBoundAsArgument = targetDecl.getLowerBound(); + for (AnnotationMirror top : tops) { + AnnotationMirror lowerBoundAnno = + lowerBoundAsArgument.getEffectiveAnnotationInHierarchy(top); + AnnotatedTypeMirror inferredType = ((InferredType) inferred).type; + AnnotationMirror argAnno = inferredType.getEffectiveAnnotationInHierarchy(top); + if (qualHierarchy.isSubtypeQualifiersOnly(argAnno, lowerBoundAnno)) { + inferredType.replaceAnnotation(lowerBoundAnno); + } } + } + } + } + + /** + * Step 1: Create a constraint {@code Ai << Fi} for each Argument(Ai) to formal parameter(Fi). + * Remove any constraint that does not involve a type parameter to be inferred. Reduce the + * remaining constraints so that Fi = Tj where Tj is a type parameter with an argument to be + * inferred. Return the resulting constraint set. + * + * @param typeFactory the type factory + * @param argTypes list of annotated types corresponding to the arguments to the method + * @param methodType annotated type of the method + * @param targets type variables to be inferred + * @param useNullArguments whether or not null method arguments should be considered + * @return a set of argument constraints + */ + protected Set createArgumentAFConstraints( + AnnotatedTypeFactory typeFactory, + List argTypes, + AnnotatedExecutableType methodType, + Set targets, + boolean useNullArguments) { + List paramTypes = + AnnotatedTypes.expandVarArgsParametersFromTypes(methodType, argTypes); + + if (argTypes.size() != paramTypes.size()) { + throw new BugInCF( + StringsPlume.joinLines( + "Mismatch between formal parameter count and argument count.", + "paramTypes=" + StringsPlume.join(",", paramTypes), + "argTypes=" + StringsPlume.join(",", argTypes))); } - /** - * Step 1: Create a constraint {@code Ai << Fi} for each Argument(Ai) to formal parameter(Fi). - * Remove any constraint that does not involve a type parameter to be inferred. Reduce the - * remaining constraints so that Fi = Tj where Tj is a type parameter with an argument to be - * inferred. Return the resulting constraint set. - * - * @param typeFactory the type factory - * @param argTypes list of annotated types corresponding to the arguments to the method - * @param methodType annotated type of the method - * @param targets type variables to be inferred - * @param useNullArguments whether or not null method arguments should be considered - * @return a set of argument constraints - */ - protected Set createArgumentAFConstraints( - AnnotatedTypeFactory typeFactory, - List argTypes, - AnnotatedExecutableType methodType, - Set targets, - boolean useNullArguments) { - List paramTypes = - AnnotatedTypes.expandVarArgsParametersFromTypes(methodType, argTypes); - - if (argTypes.size() != paramTypes.size()) { - throw new BugInCF( - StringsPlume.joinLines( - "Mismatch between formal parameter count and argument count.", - "paramTypes=" + StringsPlume.join(",", paramTypes), - "argTypes=" + StringsPlume.join(",", argTypes))); - } + int numberOfParams = paramTypes.size(); + ArrayDeque afConstraints = new ArrayDeque<>(numberOfParams); + for (int i = 0; i < numberOfParams; i++) { + if (!useNullArguments && argTypes.get(i).getKind() == TypeKind.NULL) { + continue; + } + afConstraints.add(new A2F(argTypes.get(i), paramTypes.get(i))); + } - int numberOfParams = paramTypes.size(); - ArrayDeque afConstraints = new ArrayDeque<>(numberOfParams); - for (int i = 0; i < numberOfParams; i++) { - if (!useNullArguments && argTypes.get(i).getKind() == TypeKind.NULL) { - continue; - } - afConstraints.add(new A2F(argTypes.get(i), paramTypes.get(i))); + Set reducedConstraints = new LinkedHashSet<>(); + + reduceAfConstraints(typeFactory, reducedConstraints, afConstraints, targets); + return reducedConstraints; + } + + /** + * Step 2. Infer type arguments from the equality (TisU) and the supertype (TSuperU) constraints + * of the methods arguments. + */ + private IPair inferFromArguments( + AnnotatedTypeFactory typeFactory, + Set afArgumentConstraints, + Set targets) { + Set tuArgConstraints = afToTuConstraints(afArgumentConstraints, targets); + addConstraintsBetweenTargets(tuArgConstraints, targets, false, typeFactory); + + ConstraintMap argConstraints = + constraintMapBuilder.build(targets, tuArgConstraints, typeFactory); + + InferenceResult inferredFromArgEqualities = + equalitiesSolver.solveEqualities(targets, argConstraints, typeFactory); + + Set remainingTargets = + inferredFromArgEqualities.getRemainingTargets(targets, true); + InferenceResult fromSupertypes = + supertypesSolver.solveFromSupertypes(remainingTargets, argConstraints, typeFactory); + + InferenceResult fromSubtypes = + subtypesSolver.solveFromSubtypes(remainingTargets, argConstraints, typeFactory); + fromSupertypes.mergeSubordinate(fromSubtypes); + + return IPair.of(inferredFromArgEqualities, fromSupertypes); + } + + /** Step 3. Infer type arguments from the equality constraints of the assignment context. */ + private InferenceResult inferFromAssignmentEqualities( + AnnotatedTypeMirror assignedTo, + AnnotatedTypeMirror boxedReturnType, + Set targets, + AnnotatedTypeFactory typeFactory) { + Set afInitialAssignmentConstraints = + createInitialAssignmentConstraints(assignedTo, boxedReturnType, typeFactory, targets); + + Set tuInitialAssignmentConstraints = + afToTuConstraints(afInitialAssignmentConstraints, targets); + ConstraintMap initialAssignmentConstraints = + constraintMapBuilder.build(targets, tuInitialAssignmentConstraints, typeFactory); + return equalitiesSolver.solveEqualities(targets, initialAssignmentConstraints, typeFactory); + } + + /** + * Create a set of constraints between return type and any type to which it is assigned. Reduce + * these set of constraints and remove any that is not an equality (FIsA) constraint. + */ + protected Set createInitialAssignmentConstraints( + AnnotatedTypeMirror assignedTo, + AnnotatedTypeMirror boxedReturnType, + AnnotatedTypeFactory typeFactory, + Set targets) { + Set result = new LinkedHashSet<>(); + + if (assignedTo != null) { + Set reducedConstraints = new LinkedHashSet<>(); + + Queue constraints = new ArrayDeque<>(); + constraints.add(new F2A(boxedReturnType, assignedTo)); + + reduceAfConstraints(typeFactory, reducedConstraints, constraints, targets); + + for (AFConstraint reducedConstraint : reducedConstraints) { + if (reducedConstraint instanceof FIsA) { + result.add((FIsA) reducedConstraint); } - - Set reducedConstraints = new LinkedHashSet<>(); - - reduceAfConstraints(typeFactory, reducedConstraints, afConstraints, targets); - return reducedConstraints; + } } - /** - * Step 2. Infer type arguments from the equality (TisU) and the supertype (TSuperU) constraints - * of the methods arguments. - */ - private IPair inferFromArguments( - AnnotatedTypeFactory typeFactory, - Set afArgumentConstraints, - Set targets) { - Set tuArgConstraints = afToTuConstraints(afArgumentConstraints, targets); - addConstraintsBetweenTargets(tuArgConstraints, targets, false, typeFactory); - - ConstraintMap argConstraints = - constraintMapBuilder.build(targets, tuArgConstraints, typeFactory); - - InferenceResult inferredFromArgEqualities = - equalitiesSolver.solveEqualities(targets, argConstraints, typeFactory); - - Set remainingTargets = - inferredFromArgEqualities.getRemainingTargets(targets, true); - InferenceResult fromSupertypes = - supertypesSolver.solveFromSupertypes(remainingTargets, argConstraints, typeFactory); - - InferenceResult fromSubtypes = - subtypesSolver.solveFromSubtypes(remainingTargets, argConstraints, typeFactory); - fromSupertypes.mergeSubordinate(fromSubtypes); - - return IPair.of(inferredFromArgEqualities, fromSupertypes); + return result; + } + + /** + * The first half of Step 6. + * + *

This method creates constraints: + * + *

    + *
  • between the bounds of types that are already inferred and their inferred arguments + *
  • between the assignment context and the return type of the method (with the previously + * inferred arguments substituted into these constraints) + *
+ */ + public ConstraintMap createAssignmentConstraints( + AnnotatedTypeMirror assignedTo, + AnnotatedTypeMirror boxedReturnType, + AnnotatedExecutableType methodType, + Set afArgumentConstraints, + Map inferredArgs, + Set targets, + AnnotatedTypeFactory typeFactory) { + + ArrayDeque assignmentAfs = + new ArrayDeque<>(2 * methodType.getTypeVariables().size() + afArgumentConstraints.size()); + for (AnnotatedTypeVariable typeParam : methodType.getTypeVariables()) { + TypeVariable target = typeParam.getUnderlyingType(); + AnnotatedTypeMirror inferredType = inferredArgs.get(target); + // for all inferred types Ti: Ti >> Bi where Bi is upper bound and Ti << Li where Li is + // the lower bound for all uninferred types Tu: Tu >> Bi and Lu >> Tu + if (inferredType != null) { + assignmentAfs.add(new A2F(inferredType, typeParam.getUpperBound())); + assignmentAfs.add(new F2A(typeParam.getLowerBound(), inferredType)); + } else { + assignmentAfs.add(new F2A(typeParam, typeParam.getUpperBound())); + assignmentAfs.add(new A2F(typeParam.getLowerBound(), typeParam)); + } } - /** Step 3. Infer type arguments from the equality constraints of the assignment context. */ - private InferenceResult inferFromAssignmentEqualities( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - Set targets, - AnnotatedTypeFactory typeFactory) { - Set afInitialAssignmentConstraints = - createInitialAssignmentConstraints( - assignedTo, boxedReturnType, typeFactory, targets); - - Set tuInitialAssignmentConstraints = - afToTuConstraints(afInitialAssignmentConstraints, targets); - ConstraintMap initialAssignmentConstraints = - constraintMapBuilder.build(targets, tuInitialAssignmentConstraints, typeFactory); - return equalitiesSolver.solveEqualities(targets, initialAssignmentConstraints, typeFactory); + for (AFConstraint argConstraint : afArgumentConstraints) { + if (argConstraint instanceof F2A) { + assignmentAfs.add(argConstraint); + } } - /** - * Create a set of constraints between return type and any type to which it is assigned. Reduce - * these set of constraints and remove any that is not an equality (FIsA) constraint. - */ - protected Set createInitialAssignmentConstraints( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - AnnotatedTypeFactory typeFactory, - Set targets) { - Set result = new LinkedHashSet<>(); - - if (assignedTo != null) { - Set reducedConstraints = new LinkedHashSet<>(); - - Queue constraints = new ArrayDeque<>(); - constraints.add(new F2A(boxedReturnType, assignedTo)); - - reduceAfConstraints(typeFactory, reducedConstraints, constraints, targets); - - for (AFConstraint reducedConstraint : reducedConstraints) { - if (reducedConstraint instanceof FIsA) { - result.add((FIsA) reducedConstraint); - } - } - } - - return result; + ArrayDeque substitutedAssignmentConstraints = + new ArrayDeque<>(assignmentAfs.size() + 1); + for (AFConstraint afConstraint : assignmentAfs) { + substitutedAssignmentConstraints.add(afConstraint.substitute(inferredArgs)); } - /** - * The first half of Step 6. - * - *

This method creates constraints: - * - *

    - *
  • between the bounds of types that are already inferred and their inferred arguments - *
  • between the assignment context and the return type of the method (with the previously - * inferred arguments substituted into these constraints) - *
- */ - public ConstraintMap createAssignmentConstraints( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - AnnotatedExecutableType methodType, - Set afArgumentConstraints, - Map inferredArgs, - Set targets, - AnnotatedTypeFactory typeFactory) { - - ArrayDeque assignmentAfs = - new ArrayDeque<>( - 2 * methodType.getTypeVariables().size() + afArgumentConstraints.size()); - for (AnnotatedTypeVariable typeParam : methodType.getTypeVariables()) { - TypeVariable target = typeParam.getUnderlyingType(); - AnnotatedTypeMirror inferredType = inferredArgs.get(target); - // for all inferred types Ti: Ti >> Bi where Bi is upper bound and Ti << Li where Li is - // the lower bound for all uninferred types Tu: Tu >> Bi and Lu >> Tu - if (inferredType != null) { - assignmentAfs.add(new A2F(inferredType, typeParam.getUpperBound())); - assignmentAfs.add(new F2A(typeParam.getLowerBound(), inferredType)); - } else { - assignmentAfs.add(new F2A(typeParam, typeParam.getUpperBound())); - assignmentAfs.add(new A2F(typeParam.getLowerBound(), typeParam)); - } - } + AnnotatedTypeMirror substitutedReturnType = + TypeArgInferenceUtil.substitute(inferredArgs, boxedReturnType); + substitutedAssignmentConstraints.add(new F2A(substitutedReturnType, assignedTo)); + + Set reducedConstraints = new LinkedHashSet<>(); + reduceAfConstraints(typeFactory, reducedConstraints, substitutedAssignmentConstraints, targets); + Set tuAssignmentConstraints = afToTuConstraints(reducedConstraints, targets); + addConstraintsBetweenTargets(tuAssignmentConstraints, targets, true, typeFactory); + return constraintMapBuilder.build(targets, tuAssignmentConstraints, typeFactory); + } + + /** The Second half of step 6. Use the assignment context to infer a result. */ + private InferenceResult inferFromAssignment( + AnnotatedTypeMirror assignedTo, + AnnotatedTypeMirror boxedReturnType, + AnnotatedExecutableType methodType, + Set afArgumentConstraints, + InferenceResult inferredArgs, + Set targets, + AnnotatedTypeFactory typeFactory) { + ConstraintMap assignmentConstraints = + createAssignmentConstraints( + assignedTo, + boxedReturnType, + methodType, + afArgumentConstraints, + inferredArgs.toAtmMap(), + targets, + typeFactory); + + InferenceResult equalitiesResult = + equalitiesSolver.solveEqualities(targets, assignmentConstraints, typeFactory); + + Set remainingTargets = equalitiesResult.getRemainingTargets(targets, true); + InferenceResult subtypesResult = + subtypesSolver.solveFromSubtypes(remainingTargets, assignmentConstraints, typeFactory); + + equalitiesResult.mergeSubordinate(subtypesResult); + return equalitiesResult; + } + + /** + * Step 4. Combine the results from using the Supertype constraints the Equality constraints from + * the assignment context. + */ + private InferenceResult combineSupertypeAndAssignmentResults( + Set targets, + AnnotatedTypeFactory typeFactory, + InferenceResult equalityResult, + InferenceResult supertypeResult) { + TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy(); + + InferenceResult result = new InferenceResult(); + for (TypeVariable target : targets) { + InferredValue equalityInferred = equalityResult.get(target); + InferredValue supertypeInferred = supertypeResult.get(target); + + InferredValue outputValue; + if (equalityInferred instanceof InferredType) { + + if (supertypeInferred instanceof InferredType) { + AnnotatedTypeMirror superATM = ((InferredType) supertypeInferred).type; + AnnotatedTypeMirror equalityATM = ((InferredType) equalityInferred).type; + if (TypesUtils.isErasedSubtype( + equalityATM.getUnderlyingType(), + superATM.getUnderlyingType(), + typeFactory.getChecker().getTypeUtils())) { + // If the underlying type of equalityATM is a subtype of the underlying + // type of superATM, then the call to isSubtype below will issue an error. + // So call asSuper so that the isSubtype call below works correctly. + equalityATM = AnnotatedTypes.asSuper(typeFactory, equalityATM, superATM); + } + if (typeHierarchy.isSubtype(superATM, equalityATM)) { + outputValue = equalityInferred; + } else { + outputValue = supertypeInferred; + } - for (AFConstraint argConstraint : afArgumentConstraints) { - if (argConstraint instanceof F2A) { - assignmentAfs.add(argConstraint); - } + } else { + outputValue = equalityInferred; } - - ArrayDeque substitutedAssignmentConstraints = - new ArrayDeque<>(assignmentAfs.size() + 1); - for (AFConstraint afConstraint : assignmentAfs) { - substitutedAssignmentConstraints.add(afConstraint.substitute(inferredArgs)); + } else { + if (supertypeInferred != null) { + outputValue = supertypeInferred; + } else { + outputValue = null; } + } - AnnotatedTypeMirror substitutedReturnType = - TypeArgInferenceUtil.substitute(inferredArgs, boxedReturnType); - substitutedAssignmentConstraints.add(new F2A(substitutedReturnType, assignedTo)); - - Set reducedConstraints = new LinkedHashSet<>(); - reduceAfConstraints( - typeFactory, reducedConstraints, substitutedAssignmentConstraints, targets); - Set tuAssignmentConstraints = afToTuConstraints(reducedConstraints, targets); - addConstraintsBetweenTargets(tuAssignmentConstraints, targets, true, typeFactory); - return constraintMapBuilder.build(targets, tuAssignmentConstraints, typeFactory); - } - - /** The Second half of step 6. Use the assignment context to infer a result. */ - private InferenceResult inferFromAssignment( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - AnnotatedExecutableType methodType, - Set afArgumentConstraints, - InferenceResult inferredArgs, - Set targets, - AnnotatedTypeFactory typeFactory) { - ConstraintMap assignmentConstraints = - createAssignmentConstraints( - assignedTo, - boxedReturnType, - methodType, - afArgumentConstraints, - inferredArgs.toAtmMap(), - targets, - typeFactory); - - InferenceResult equalitiesResult = - equalitiesSolver.solveEqualities(targets, assignmentConstraints, typeFactory); - - Set remainingTargets = equalitiesResult.getRemainingTargets(targets, true); - InferenceResult subtypesResult = - subtypesSolver.solveFromSubtypes( - remainingTargets, assignmentConstraints, typeFactory); - - equalitiesResult.mergeSubordinate(subtypesResult); - return equalitiesResult; + if (outputValue != null) { + result.put(target, outputValue); + } } - /** - * Step 4. Combine the results from using the Supertype constraints the Equality constraints - * from the assignment context. - */ - private InferenceResult combineSupertypeAndAssignmentResults( - Set targets, - AnnotatedTypeFactory typeFactory, - InferenceResult equalityResult, - InferenceResult supertypeResult) { - TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy(); - - InferenceResult result = new InferenceResult(); - for (TypeVariable target : targets) { - InferredValue equalityInferred = equalityResult.get(target); - InferredValue supertypeInferred = supertypeResult.get(target); - - InferredValue outputValue; - if (equalityInferred instanceof InferredType) { - - if (supertypeInferred instanceof InferredType) { - AnnotatedTypeMirror superATM = ((InferredType) supertypeInferred).type; - AnnotatedTypeMirror equalityATM = ((InferredType) equalityInferred).type; - if (TypesUtils.isErasedSubtype( - equalityATM.getUnderlyingType(), - superATM.getUnderlyingType(), - typeFactory.getChecker().getTypeUtils())) { - // If the underlying type of equalityATM is a subtype of the underlying - // type of superATM, then the call to isSubtype below will issue an error. - // So call asSuper so that the isSubtype call below works correctly. - equalityATM = AnnotatedTypes.asSuper(typeFactory, equalityATM, superATM); - } - if (typeHierarchy.isSubtype(superATM, equalityATM)) { - outputValue = equalityInferred; - } else { - outputValue = supertypeInferred; - } - - } else { - outputValue = equalityInferred; - } - } else { - if (supertypeInferred != null) { - outputValue = supertypeInferred; - } else { - outputValue = null; - } - } - - if (outputValue != null) { - result.put(target, outputValue); - } + return result; + } + + /** + * For any types we have not inferred, use a wildcard with the bounds from the original type + * parameter. + */ + private void handleUninferredTypeVariables( + AnnotatedTypeFactory typeFactory, + AnnotatedExecutableType methodType, + Set targets, + Map inferredArgs) { + + for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) { + TypeVariable typeVar = atv.getUnderlyingType(); + if (targets.contains((TypeVariable) TypeAnnotationUtils.unannotatedType(typeVar))) { + AnnotatedTypeMirror inferredType = inferredArgs.get(typeVar); + if (inferredType == null) { + AnnotatedTypeMirror dummy = typeFactory.getUninferredWildcardType(atv); + inferredArgs.put(atv.getUnderlyingType(), dummy); + } else { + typeFactory.addDefaultAnnotations(inferredType); } - - return result; + } } + } + + /** + * Given a set of AFConstraints, remove all constraints that are not relevant to inference and + * return a set of AFConstraints in which the F is a use of one of the type parameters to infer. + */ + protected void reduceAfConstraints( + AnnotatedTypeFactory typeFactory, + Set outgoing, + Queue toProcess, + Set targets) { + + Set visited = new HashSet<>(); + + List reducers = + Arrays.asList( + new A2FReducer(typeFactory), new F2AReducer(typeFactory), new FIsAReducer(typeFactory)); + + Set newConstraints = new HashSet<>(10); + while (!toProcess.isEmpty()) { + newConstraints.clear(); + AFConstraint constraint = toProcess.remove(); + + if (!visited.contains(constraint)) { + if (constraint.isIrreducible(targets)) { + outgoing.add(constraint); + } else { - /** - * For any types we have not inferred, use a wildcard with the bounds from the original type - * parameter. - */ - private void handleUninferredTypeVariables( - AnnotatedTypeFactory typeFactory, - AnnotatedExecutableType methodType, - Set targets, - Map inferredArgs) { - - for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) { - TypeVariable typeVar = atv.getUnderlyingType(); - if (targets.contains((TypeVariable) TypeAnnotationUtils.unannotatedType(typeVar))) { - AnnotatedTypeMirror inferredType = inferredArgs.get(typeVar); - if (inferredType == null) { - AnnotatedTypeMirror dummy = typeFactory.getUninferredWildcardType(atv); - inferredArgs.put(atv.getUnderlyingType(), dummy); - } else { - typeFactory.addDefaultAnnotations(inferredType); - } - } - } - } + Iterator reducerIterator = reducers.iterator(); + boolean handled = false; + while (!handled && reducerIterator.hasNext()) { + handled = reducerIterator.next().reduce(constraint, newConstraints); + } - /** - * Given a set of AFConstraints, remove all constraints that are not relevant to inference and - * return a set of AFConstraints in which the F is a use of one of the type parameters to infer. - */ - protected void reduceAfConstraints( - AnnotatedTypeFactory typeFactory, - Set outgoing, - Queue toProcess, - Set targets) { - - Set visited = new HashSet<>(); - - List reducers = - Arrays.asList( - new A2FReducer(typeFactory), - new F2AReducer(typeFactory), - new FIsAReducer(typeFactory)); - - Set newConstraints = new HashSet<>(10); - while (!toProcess.isEmpty()) { - newConstraints.clear(); - AFConstraint constraint = toProcess.remove(); - - if (!visited.contains(constraint)) { - if (constraint.isIrreducible(targets)) { - outgoing.add(constraint); - } else { - - Iterator reducerIterator = reducers.iterator(); - boolean handled = false; - while (!handled && reducerIterator.hasNext()) { - handled = reducerIterator.next().reduce(constraint, newConstraints); - } - - if (!handled) { - throw new BugInCF("Unhandled constraint type: " + constraint); - } - - toProcess.addAll(newConstraints); - } - visited.add(constraint); - } - } - } + if (!handled) { + throw new BugInCF("Unhandled constraint type: " + constraint); + } - /** Convert AFConstraints to TUConstraints. */ - protected Set afToTuConstraints( - Set afConstraints, Set targets) { - Set outgoing = new LinkedHashSet<>(); - for (AFConstraint afConstraint : afConstraints) { - if (!afConstraint.isIrreducible(targets)) { - throw new BugInCF( - StringsPlume.joinLines( - "All afConstraints should be irreducible before conversion.", - "afConstraints=[ " + StringsPlume.join(", ", afConstraints) + " ]", - "targets=[ " + StringsPlume.join(", ", targets) + "]")); - } - - outgoing.add(afConstraint.toTUConstraint()); + toProcess.addAll(newConstraints); } - - return outgoing; + visited.add(constraint); + } } - - /** - * Declarations of the form: {@code } implies a TUConstraint of {@code B <: A}. - * Add these to the constraint list. - */ - public void addConstraintsBetweenTargets( - Set constraints, - Set targets, - boolean asSubtype, - AnnotatedTypeFactory typeFactory) { - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - List targetList = new ArrayList<>(targets); - - Map paramDeclarations = new HashMap<>(); - - for (int i = 0; i < targetList.size(); i++) { - TypeVariable earlierTarget = targetList.get(i); - - for (int j = i + 1; j < targetList.size(); j++) { - TypeVariable laterTarget = targetList.get(j); - if (types.isSameType(earlierTarget.getUpperBound(), laterTarget)) { - AnnotatedTypeVariable headDecl = - addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); - AnnotatedTypeVariable nextDecl = - addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations); - - if (asSubtype) { - constraints.add(new TSubU(headDecl, nextDecl)); - - } else { - constraints.add(new TSuperU(nextDecl, headDecl)); - } - } else if (types.isSameType(laterTarget.getUpperBound(), earlierTarget)) { - AnnotatedTypeVariable headDecl = - addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); - AnnotatedTypeVariable nextDecl = - addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations); - - if (asSubtype) { - constraints.add(new TSubU(nextDecl, headDecl)); - - } else { - constraints.add(new TSuperU(headDecl, nextDecl)); - } - } - } - } + } + + /** Convert AFConstraints to TUConstraints. */ + protected Set afToTuConstraints( + Set afConstraints, Set targets) { + Set outgoing = new LinkedHashSet<>(); + for (AFConstraint afConstraint : afConstraints) { + if (!afConstraint.isIrreducible(targets)) { + throw new BugInCF( + StringsPlume.joinLines( + "All afConstraints should be irreducible before conversion.", + "afConstraints=[ " + StringsPlume.join(", ", afConstraints) + " ]", + "targets=[ " + StringsPlume.join(", ", targets) + "]")); + } + + outgoing.add(afConstraint.toTUConstraint()); } - public AnnotatedTypeVariable addOrGetDeclarations( - TypeVariable target, - AnnotatedTypeFactory typeFactory, - Map declarations) { - AnnotatedTypeVariable atv = - declarations.computeIfAbsent( - target, - __ -> - (AnnotatedTypeVariable) - typeFactory.getAnnotatedType(target.asElement())); - return atv; + return outgoing; + } + + /** + * Declarations of the form: {@code } implies a TUConstraint of {@code B <: A}. + * Add these to the constraint list. + */ + public void addConstraintsBetweenTargets( + Set constraints, + Set targets, + boolean asSubtype, + AnnotatedTypeFactory typeFactory) { + Types types = typeFactory.getProcessingEnv().getTypeUtils(); + List targetList = new ArrayList<>(targets); + + Map paramDeclarations = new HashMap<>(); + + for (int i = 0; i < targetList.size(); i++) { + TypeVariable earlierTarget = targetList.get(i); + + for (int j = i + 1; j < targetList.size(); j++) { + TypeVariable laterTarget = targetList.get(j); + if (types.isSameType(earlierTarget.getUpperBound(), laterTarget)) { + AnnotatedTypeVariable headDecl = + addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); + AnnotatedTypeVariable nextDecl = + addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations); + + if (asSubtype) { + constraints.add(new TSubU(headDecl, nextDecl)); + + } else { + constraints.add(new TSuperU(nextDecl, headDecl)); + } + } else if (types.isSameType(laterTarget.getUpperBound(), earlierTarget)) { + AnnotatedTypeVariable headDecl = + addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); + AnnotatedTypeVariable nextDecl = + addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations); + + if (asSubtype) { + constraints.add(new TSubU(nextDecl, headDecl)); + + } else { + constraints.add(new TSuperU(headDecl, nextDecl)); + } + } + } } + } + + public AnnotatedTypeVariable addOrGetDeclarations( + TypeVariable target, + AnnotatedTypeFactory typeFactory, + Map declarations) { + AnnotatedTypeVariable atv = + declarations.computeIfAbsent( + target, __ -> (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement())); + return atv; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java index 1290992660e..a4ea90f7ea6 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java @@ -1,205 +1,202 @@ package org.checkerframework.framework.util.typeinference; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.TypeHierarchy; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.TypesUtils; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.TypeHierarchy; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TypesUtils; /** A class used to determine the greatest lower bounds for a set of AnnotatedTypeMirrors. */ public class GlbUtil { - /** - * Returns the greatest lower bound of the given {@code TypeMirror}s. If any of the type mirrors - * are incomparable, Returns an AnnotatedNullType that contains the greatest lower bounds of the - * primary annotations of typeMirrors. - * - *

Note: This method can be improved for wildcards and type variables. - * - * @param typeMirrors the types to glb - * @param atypeFactory the type factory - * @return the greatest lower bound of typeMirrors - */ - public static @Nullable AnnotatedTypeMirror glbAll( - Map typeMirrors, - AnnotatedTypeFactory atypeFactory) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - if (typeMirrors.isEmpty()) { - return null; - } - - // determine the greatest lower bounds for the primary annotations - AnnotationMirrorMap glbPrimaries = new AnnotationMirrorMap<>(); - for (Map.Entry tmEntry : typeMirrors.entrySet()) { - AnnotationMirrorSet typeAnnoHierarchies = tmEntry.getValue(); - AnnotatedTypeMirror type = tmEntry.getKey(); - - for (AnnotationMirror top : typeAnnoHierarchies) { - // TODO: When all of the typeMirrors are either wildcards or type variables than the - // greatest lower bound should involve handling the bounds individually rather than - // using the effective annotation. We are doing this for expediency. - AnnotationMirror typeAnno = type.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror currentAnno = glbPrimaries.get(top); - if (typeAnno != null && currentAnno != null) { - glbPrimaries.put( - top, - qualHierarchy.greatestLowerBoundQualifiersOnly(currentAnno, typeAnno)); - } else if (typeAnno != null) { - glbPrimaries.put(top, typeAnno); - } - } - } - - List glbTypes = new ArrayList<>(); - TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy(); - - // create a copy of all of the types and apply the glb primary annotation - AnnotationMirrorSet values = new AnnotationMirrorSet(glbPrimaries.values()); - for (AnnotatedTypeMirror atm : typeMirrors.keySet()) { - if (atm.getKind() != TypeKind.TYPEVAR - || !typeHierarchy.isSubtypeShallowEffective(atm, values)) { - AnnotatedTypeMirror copy = atm.deepCopy(); - copy.replaceAnnotations(values); - glbTypes.add(copy); - - } else { - // if the annotations came from the upper bound of this typevar - // we do NOT want to place them as primary annotations (and destroy the - // type vars lower bound) - glbTypes.add(atm); - } - } + /** + * Returns the greatest lower bound of the given {@code TypeMirror}s. If any of the type mirrors + * are incomparable, Returns an AnnotatedNullType that contains the greatest lower bounds of the + * primary annotations of typeMirrors. + * + *

Note: This method can be improved for wildcards and type variables. + * + * @param typeMirrors the types to glb + * @param atypeFactory the type factory + * @return the greatest lower bound of typeMirrors + */ + public static @Nullable AnnotatedTypeMirror glbAll( + Map typeMirrors, + AnnotatedTypeFactory atypeFactory) { + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + if (typeMirrors.isEmpty()) { + return null; + } - // sort placing supertypes first - sortForGlb(glbTypes, atypeFactory); - - // find the lowest type in the list that is not an AnnotatedNullType - AnnotatedTypeMirror glbType = glbTypes.get(0); - int index = 1; - while (index < glbTypes.size()) { - // avoid using null if possible, since constraints form the lower bound will often have - // NULL types - if (glbType.getKind() != TypeKind.NULL) { - glbType = glbTypes.get(index); - } - index += 1; + // determine the greatest lower bounds for the primary annotations + AnnotationMirrorMap glbPrimaries = new AnnotationMirrorMap<>(); + for (Map.Entry tmEntry : typeMirrors.entrySet()) { + AnnotationMirrorSet typeAnnoHierarchies = tmEntry.getValue(); + AnnotatedTypeMirror type = tmEntry.getKey(); + + for (AnnotationMirror top : typeAnnoHierarchies) { + // TODO: When all of the typeMirrors are either wildcards or type variables than the + // greatest lower bound should involve handling the bounds individually rather than + // using the effective annotation. We are doing this for expediency. + AnnotationMirror typeAnno = type.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror currentAnno = glbPrimaries.get(top); + if (typeAnno != null && currentAnno != null) { + glbPrimaries.put( + top, qualHierarchy.greatestLowerBoundQualifiersOnly(currentAnno, typeAnno)); + } else if (typeAnno != null) { + glbPrimaries.put(top, typeAnno); } + } + } - // if the lowest type is a subtype of all glbTypes then it is the GLB, otherwise there are - // two types in glbTypes that are incomparable and we need to use bottom (AnnotatedNullType) - boolean incomparable = false; - for (AnnotatedTypeMirror type : glbTypes) { - if (!incomparable - && type.getKind() != TypeKind.NULL - && (!TypesUtils.isErasedSubtype( - glbType.getUnderlyingType(), - type.getUnderlyingType(), - atypeFactory.getChecker().getTypeUtils()) - || !typeHierarchy.isSubtype(glbType, type))) { - incomparable = true; - } - } + List glbTypes = new ArrayList<>(); + TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy(); + + // create a copy of all of the types and apply the glb primary annotation + AnnotationMirrorSet values = new AnnotationMirrorSet(glbPrimaries.values()); + for (AnnotatedTypeMirror atm : typeMirrors.keySet()) { + if (atm.getKind() != TypeKind.TYPEVAR + || !typeHierarchy.isSubtypeShallowEffective(atm, values)) { + AnnotatedTypeMirror copy = atm.deepCopy(); + copy.replaceAnnotations(values); + glbTypes.add(copy); + + } else { + // if the annotations came from the upper bound of this typevar + // we do NOT want to place them as primary annotations (and destroy the + // type vars lower bound) + glbTypes.add(atm); + } + } - // we had two incomparable types in glbTypes - if (incomparable) { - return createBottom(atypeFactory, glbType.getEffectiveAnnotations()); - } + // sort placing supertypes first + sortForGlb(glbTypes, atypeFactory); + + // find the lowest type in the list that is not an AnnotatedNullType + AnnotatedTypeMirror glbType = glbTypes.get(0); + int index = 1; + while (index < glbTypes.size()) { + // avoid using null if possible, since constraints form the lower bound will often have + // NULL types + if (glbType.getKind() != TypeKind.NULL) { + glbType = glbTypes.get(index); + } + index += 1; + } - return glbType; + // if the lowest type is a subtype of all glbTypes then it is the GLB, otherwise there are + // two types in glbTypes that are incomparable and we need to use bottom (AnnotatedNullType) + boolean incomparable = false; + for (AnnotatedTypeMirror type : glbTypes) { + if (!incomparable + && type.getKind() != TypeKind.NULL + && (!TypesUtils.isErasedSubtype( + glbType.getUnderlyingType(), + type.getUnderlyingType(), + atypeFactory.getChecker().getTypeUtils()) + || !typeHierarchy.isSubtype(glbType, type))) { + incomparable = true; + } } - /** Returns an AnnotatedNullType with the given annotations as primaries. */ - private static AnnotatedNullType createBottom( - AnnotatedTypeFactory atypeFactory, Set annos) { - return atypeFactory.getAnnotatedNullType(annos); + // we had two incomparable types in glbTypes + if (incomparable) { + return createBottom(atypeFactory, glbType.getEffectiveAnnotations()); } + return glbType; + } + + /** Returns an AnnotatedNullType with the given annotations as primaries. */ + private static AnnotatedNullType createBottom( + AnnotatedTypeFactory atypeFactory, Set annos) { + return atypeFactory.getAnnotatedNullType(annos); + } + + /** + * Sort the list of type mirrors, placing supertypes first and subtypes last. + * + *

E.g. the list: {@code ArrayList, List, AbstractList} becomes: {@code + * List, AbstractList, ArrayList} + * + * @param typeMirrors the list to sort in place + * @param atypeFactory the type factory + */ + public static void sortForGlb( + List typeMirrors, AnnotatedTypeFactory atypeFactory) { + Collections.sort(typeMirrors, new GlbSortComparator(atypeFactory)); + } + + /** A comparator for {@link #sortForGlb}. */ + private static final class GlbSortComparator implements Comparator { + + /** The qualifier hierarchy. */ + private final QualifierHierarchy qualHierarchy; + + /** The type utiliites. */ + private final Types types; + /** - * Sort the list of type mirrors, placing supertypes first and subtypes last. + * Creates a new GlbSortComparator. * - *

E.g. the list: {@code ArrayList, List, AbstractList} becomes: - * {@code List, AbstractList, ArrayList} - * - * @param typeMirrors the list to sort in place * @param atypeFactory the type factory */ - public static void sortForGlb( - List typeMirrors, AnnotatedTypeFactory atypeFactory) { - Collections.sort(typeMirrors, new GlbSortComparator(atypeFactory)); + public GlbSortComparator(AnnotatedTypeFactory atypeFactory) { + qualHierarchy = atypeFactory.getQualifierHierarchy(); + types = atypeFactory.getProcessingEnv().getTypeUtils(); } - /** A comparator for {@link #sortForGlb}. */ - private static final class GlbSortComparator implements Comparator { - - /** The qualifier hierarchy. */ - private final QualifierHierarchy qualHierarchy; - - /** The type utiliites. */ - private final Types types; - - /** - * Creates a new GlbSortComparator. - * - * @param atypeFactory the type factory - */ - public GlbSortComparator(AnnotatedTypeFactory atypeFactory) { - qualHierarchy = atypeFactory.getQualifierHierarchy(); - types = atypeFactory.getProcessingEnv().getTypeUtils(); - } - - @Override - public int compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - TypeMirror underlyingType1 = type1.getUnderlyingType(); - TypeMirror underlyingType2 = type2.getUnderlyingType(); - - if (types.isSameType(underlyingType1, underlyingType2)) { - return compareAnnotations(type1, type2); - } else if (types.isSubtype(underlyingType1, underlyingType2)) { - return 1; - } else { - // if they're incomparable or type2 is a subtype of type1 - return -1; - } - } + @Override + public int compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + TypeMirror underlyingType1 = type1.getUnderlyingType(); + TypeMirror underlyingType2 = type2.getUnderlyingType(); + + if (types.isSameType(underlyingType1, underlyingType2)) { + return compareAnnotations(type1, type2); + } else if (types.isSubtype(underlyingType1, underlyingType2)) { + return 1; + } else { + // if they're incomparable or type2 is a subtype of type1 + return -1; + } + } - /** - * Returns -1, 0, or 1 depending on whether anno1 is a supertype, same as, or a subtype of - * annos2. - * - * @param type1 a type whose annotations to compare - * @param type2 a type whose annotations to compare - * @return the comparison of type1 and type2 - */ - private int compareAnnotations(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - AnnotationMirrorSet annos1 = type1.getAnnotations(); - AnnotationMirrorSet annos2 = type2.getAnnotations(); - if (AnnotationUtils.areSame(annos1, annos2)) { - return 0; - } - TypeMirror tm1 = type1.getUnderlyingType(); - TypeMirror tm2 = type2.getUnderlyingType(); - if (qualHierarchy.isSubtypeShallow(annos1, tm1, annos2, tm2)) { - return 1; - } else { - return -1; - } - } + /** + * Returns -1, 0, or 1 depending on whether anno1 is a supertype, same as, or a subtype of + * annos2. + * + * @param type1 a type whose annotations to compare + * @param type2 a type whose annotations to compare + * @return the comparison of type1 and type2 + */ + private int compareAnnotations(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + AnnotationMirrorSet annos1 = type1.getAnnotations(); + AnnotationMirrorSet annos2 = type2.getAnnotations(); + if (AnnotationUtils.areSame(annos1, annos2)) { + return 0; + } + TypeMirror tm1 = type1.getUnderlyingType(); + TypeMirror tm2 = type2.getUnderlyingType(); + if (qualHierarchy.isSubtypeShallow(annos1, tm1, annos2, tm2)) { + return 1; + } else { + return -1; + } } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java index c3dc797d365..ba2c560b171 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java @@ -14,30 +14,6 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - -import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.TypeVariableSubstitutor; -import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreePathUtil; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -48,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; @@ -66,685 +41,682 @@ import javax.lang.model.type.TypeVisitor; import javax.lang.model.type.UnionType; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.TypeVariableSubstitutor; +import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypeAnnotationUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; /** Miscellaneous utilities to help in type argument inference. */ public class TypeArgInferenceUtil { - /** - * Returns a list of boxed annotated types corresponding to the arguments in {@code - * methodInvocation}. - * - * @param methodInvocation {@link MethodInvocationTree} or {@link NewClassTree} - * @param typeFactory type factory - * @return a list of boxed annotated types corresponding to the arguments in {@code - * methodInvocation}. - */ - public static List getArgumentTypes( - ExpressionTree methodInvocation, AnnotatedTypeFactory typeFactory) { - final List argTrees; - - if (methodInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { - argTrees = ((MethodInvocationTree) methodInvocation).getArguments(); - - } else if (methodInvocation.getKind() == Tree.Kind.NEW_CLASS) { - argTrees = ((NewClassTree) methodInvocation).getArguments(); - - } else { - throw new BugInCF( - "TypeArgumentInference.relationsFromMethodArguments:%n" - + "couldn't determine arguments from tree: %s", - methodInvocation); - } - - List argTypes = - CollectionsPlume.mapList( - (Tree arg) -> { - AnnotatedTypeMirror argType = typeFactory.getAnnotatedType(arg); - if (TypesUtils.isPrimitive(argType.getUnderlyingType())) { - return typeFactory.getBoxedType((AnnotatedPrimitiveType) argType); - } else { - return argType; - } - }, - argTrees); - return argTypes; - } - - /** - * Given a set of type variables for which we are inferring a type, returns true if type is a - * use of a type variable in the list of targetTypeVars. - */ - public static boolean isATarget(AnnotatedTypeMirror type, Set targetTypeVars) { - return type.getKind() == TypeKind.TYPEVAR - && targetTypeVars.contains( - (TypeVariable) - TypeAnnotationUtils.unannotatedType(type.getUnderlyingType())); - } - - /** - * Given an AnnotatedExecutableType return a set of type variables that represents the generic - * type parameters of that method. - */ - public static Set methodTypeToTargets(AnnotatedExecutableType methodType) { - List annotatedTypeVars = methodType.getTypeVariables(); - Set targets = new LinkedHashSet<>(annotatedTypeVars.size()); - - for (AnnotatedTypeVariable atv : annotatedTypeVars) { - targets.add( - (TypeVariable) TypeAnnotationUtils.unannotatedType(atv.getUnderlyingType())); - } - - return targets; - } - - /** - * Returns the annotated type that the leaf of path is assigned to, if it is within an - * assignment context. Returns the annotated type that the method invocation at the leaf is - * assigned to. If the result is a primitive, return the boxed version. - * - * @param atypeFactory the type factory, for looking up types - * @param path the path whole leaf to look up a type for - * @return the type of path's leaf - */ - @SuppressWarnings("interning:not.interned") // AST node comparisons - public static @Nullable AnnotatedTypeMirror assignedTo( - AnnotatedTypeFactory atypeFactory, TreePath path) { - Tree assignmentContext = TreePathUtil.getAssignmentContext(path); - AnnotatedTypeMirror res; - if (assignmentContext == null) { - res = null; - } else if (assignmentContext instanceof AssignmentTree) { - ExpressionTree variable = ((AssignmentTree) assignmentContext).getVariable(); - res = atypeFactory.getAnnotatedType(variable); - } else if (assignmentContext instanceof CompoundAssignmentTree) { - ExpressionTree variable = ((CompoundAssignmentTree) assignmentContext).getVariable(); - res = atypeFactory.getAnnotatedType(variable); - } else if (assignmentContext instanceof MethodInvocationTree) { - MethodInvocationTree methodInvocation = (MethodInvocationTree) assignmentContext; - // TODO move to getAssignmentContext - if (methodInvocation.getMethodSelect() instanceof MemberSelectTree - && ((MemberSelectTree) methodInvocation.getMethodSelect()).getExpression() - == path.getLeaf()) { - return null; - } - ExecutableElement methodElt = TreeUtils.elementFromUse(methodInvocation); - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(methodInvocation); - if (TreeUtils.isSuperConstructorCall(methodInvocation)) { - receiver = atypeFactory.getSelfType(methodInvocation); - } - res = - assignedToExecutable( - atypeFactory, - path, - methodElt, - receiver, - methodInvocation.getArguments()); - } else if (assignmentContext instanceof NewArrayTree) { - // TODO: I left the previous implementation below, it definitely caused infinite loops - // TODO: if you called it from places like the TreeAnnotator. - res = null; - - // TODO: This may cause infinite loop - // AnnotatedTypeMirror type = - // atypeFactory.getAnnotatedType((NewArrayTree)assignmentContext); - // type = AnnotatedTypes.innerMostType(type); - // return type; - - } else if (assignmentContext instanceof NewClassTree) { - // This need to be basically like MethodTree - NewClassTree newClassTree = (NewClassTree) assignmentContext; - if (newClassTree.getEnclosingExpression() instanceof NewClassTree - && (newClassTree.getEnclosingExpression() == path.getLeaf())) { - return null; - } - ExecutableElement constructorElt = TreeUtils.elementFromUse(newClassTree); - // TODO: This call should be removed once #979 is implemented. - // Change this to atypeFactory.getAnnotatedType(newClassTree) causes infinite recursion - // in the InitializationAnnotatedTypeFactory.CommitmentTreeAnnotator.visitNewClass. - @SuppressWarnings("deprecation") - AnnotatedTypeMirror receiver = atypeFactory.fromNewClass(newClassTree); - res = - assignedToExecutable( - atypeFactory, - path, - constructorElt, - receiver, - newClassTree.getArguments()); - } else if (assignmentContext instanceof ReturnTree) { - HashSet kinds = - new HashSet<>(Arrays.asList(Tree.Kind.LAMBDA_EXPRESSION, Tree.Kind.METHOD)); - Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds); - - if (enclosing.getKind() == Tree.Kind.METHOD) { - res = atypeFactory.getAnnotatedType((MethodTree) enclosing).getReturnType(); - } else { - AnnotatedExecutableType fninf = - atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); - res = fninf.getReturnType(); - } - - } else if (assignmentContext instanceof VariableTree) { - res = assignedToVariable(atypeFactory, (VariableTree) assignmentContext); - } else { - throw new BugInCF("AnnotatedTypes.assignedTo: shouldn't be here"); - } - - if (res != null && TypesUtils.isPrimitive(res.getUnderlyingType())) { - return atypeFactory.getBoxedType((AnnotatedPrimitiveType) res); - } else { - return res; - } + /** + * Returns a list of boxed annotated types corresponding to the arguments in {@code + * methodInvocation}. + * + * @param methodInvocation {@link MethodInvocationTree} or {@link NewClassTree} + * @param typeFactory type factory + * @return a list of boxed annotated types corresponding to the arguments in {@code + * methodInvocation}. + */ + public static List getArgumentTypes( + ExpressionTree methodInvocation, AnnotatedTypeFactory typeFactory) { + final List argTrees; + + if (methodInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { + argTrees = ((MethodInvocationTree) methodInvocation).getArguments(); + + } else if (methodInvocation.getKind() == Tree.Kind.NEW_CLASS) { + argTrees = ((NewClassTree) methodInvocation).getArguments(); + + } else { + throw new BugInCF( + "TypeArgumentInference.relationsFromMethodArguments:%n" + + "couldn't determine arguments from tree: %s", + methodInvocation); } - private static @Nullable AnnotatedTypeMirror assignedToExecutable( - AnnotatedTypeFactory atypeFactory, - TreePath path, - ExecutableElement methodElt, - AnnotatedTypeMirror receiver, - List arguments) { - AnnotatedExecutableType method = - AnnotatedTypes.asMemberOf( - atypeFactory.getChecker().getTypeUtils(), - atypeFactory, - receiver, - methodElt); - int treeIndex = -1; - for (int i = 0; i < arguments.size(); ++i) { - ExpressionTree argumentTree = arguments.get(i); - if (isArgument(path, argumentTree)) { - treeIndex = i; - break; - } - } - final AnnotatedTypeMirror paramType; - if (treeIndex == -1) { - // The tree wasn't found as an argument, so it has to be the receiver. - // This can happen for inner class constructors that take an outer class argument. - paramType = method.getReceiverType(); - } else if (treeIndex + 1 >= method.getParameterTypes().size() && methodElt.isVarArgs()) { - AnnotatedTypeMirror varArgsType = - method.getParameterTypes().get(method.getParameterTypes().size() - 1); - paramType = ((AnnotatedArrayType) varArgsType).getComponentType(); - } else { - paramType = method.getParameterTypes().get(treeIndex); - } - - // Examples like this: - // T outMethod() - // void inMethod(U u); - // inMethod(outMethod()) - // would require solving the constraints for both type argument inferences simultaneously - if (paramType == null || containsUninferredTypeParameter(paramType, method)) { - return null; - } - - return paramType; - } - - /** - * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional - * expression, isArgument is called recursively on the true and false expressions. - * - * @param path the path whose leaf to test - * @param argumentTree the expression that might be path's leaf - * @return true if {@code argumentTree} is the leaf of {@code path} - */ - private static boolean isArgument(TreePath path, @FindDistinct ExpressionTree argumentTree) { - argumentTree = TreeUtils.withoutParens(argumentTree); - if (argumentTree == path.getLeaf()) { - return true; - } else if (argumentTree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { - ConditionalExpressionTree conditionalExpressionTree = - (ConditionalExpressionTree) argumentTree; - return isArgument(path, conditionalExpressionTree.getTrueExpression()) - || isArgument(path, conditionalExpressionTree.getFalseExpression()); - } - return false; - } - - /** - * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree). - * Rationale: - * - *

For example: - * - *

{@code
-     *  S bar () {...}
-     *
-     *  T foo(T p) {
-     *     T local = bar();
-     *     return local;
-     *   }
-     * }
- * - * During type argument inference of {@code bar}, the assignment context is {@code local}. If - * the local variable default is used, then the type of assignment context type is - * {@code @Nullable T} and the type argument inferred for {@code bar()} is {@code @Nullable T}. - * And an incompatible types in return error is issued. - * - *

If instead, the local variable default is not applied, then the assignment context type is - * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) - * and the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of - * {@code local} is refined to {@code T} and the return is legal. - * - *

If the assignment context type was a declared type, for example: - * - *

{@code
-     *  S bar () {...}
-     * Object foo() {
-     *     Object local = bar();
-     *     return local;
-     * }
-     * }
- * - * The local variable default must be used or else the assignment context type is missing an - * annotation. So, an incompatible types in return error is issued in the above code. We could - * improve type argument inference in this case and by using the lower bound of {@code S} - * instead of the local variable default. - * - * @param atypeFactory the type factory - * @param assignmentContext VariableTree - * @return AnnotatedTypeMirror of Assignment context - */ - public static AnnotatedTypeMirror assignedToVariable( - AnnotatedTypeFactory atypeFactory, VariableTree assignmentContext) { - if (TreeUtils.isVariableTreeDeclaredUsingVar(assignmentContext)) { - return null; - } - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory gatf = - ((GenericAnnotatedTypeFactory) atypeFactory); - return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext); - } else { - return atypeFactory.getAnnotatedType(assignmentContext); - } + List argTypes = + CollectionsPlume.mapList( + (Tree arg) -> { + AnnotatedTypeMirror argType = typeFactory.getAnnotatedType(arg); + if (TypesUtils.isPrimitive(argType.getUnderlyingType())) { + return typeFactory.getBoxedType((AnnotatedPrimitiveType) argType); + } else { + return argType; + } + }, + argTrees); + return argTypes; + } + + /** + * Given a set of type variables for which we are inferring a type, returns true if type is a use + * of a type variable in the list of targetTypeVars. + */ + public static boolean isATarget(AnnotatedTypeMirror type, Set targetTypeVars) { + return type.getKind() == TypeKind.TYPEVAR + && targetTypeVars.contains( + (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType())); + } + + /** + * Given an AnnotatedExecutableType return a set of type variables that represents the generic + * type parameters of that method. + */ + public static Set methodTypeToTargets(AnnotatedExecutableType methodType) { + List annotatedTypeVars = methodType.getTypeVariables(); + Set targets = new LinkedHashSet<>(annotatedTypeVars.size()); + + for (AnnotatedTypeVariable atv : annotatedTypeVars) { + targets.add((TypeVariable) TypeAnnotationUtils.unannotatedType(atv.getUnderlyingType())); } - /** - * Returns true if the type contains a use of a type variable from methodType. - * - * @return true if the type contains a use of a type variable from methodType - */ - private static boolean containsUninferredTypeParameter( - AnnotatedTypeMirror type, AnnotatedExecutableType methodType) { - List annotatedTypeVars = methodType.getTypeVariables(); - List typeVars = - CollectionsPlume.mapList( - (AnnotatedTypeVariable annotatedTypeVar) -> - (TypeVariable) - TypeAnnotationUtils.unannotatedType( - annotatedTypeVar.getUnderlyingType()), - annotatedTypeVars); - - return containsTypeParameter(type, typeVars); - } - - /** - * Returns true if {@code type} contains a use of a type variable in {@code typeVariables}. - * - * @param type type to search - * @param typeVariables collection of type variables - * @return true if {@code type} contains a use of a type variable in {@code typeVariables} - */ - public static boolean containsTypeParameter( - AnnotatedTypeMirror type, Collection typeVariables) { - // note NULL values creep in because the underlying visitor uses them in various places - Boolean result = type.accept(new TypeVariableFinder(), typeVariables); - return result != null && result; - } - - /** - * Take a set of annotations and separate them into a mapping of {@code hierarchy top => - * annotations in hierarchy}. - */ - public static AnnotationMirrorMap createHierarchyMap( - AnnotationMirrorSet annos, QualifierHierarchy qualHierarchy) { - AnnotationMirrorMap result = new AnnotationMirrorMap<>(); - - for (AnnotationMirror anno : annos) { - result.put(qualHierarchy.getTopAnnotation(anno), anno); - } - - return result; + return targets; + } + + /** + * Returns the annotated type that the leaf of path is assigned to, if it is within an assignment + * context. Returns the annotated type that the method invocation at the leaf is assigned to. If + * the result is a primitive, return the boxed version. + * + * @param atypeFactory the type factory, for looking up types + * @param path the path whole leaf to look up a type for + * @return the type of path's leaf + */ + @SuppressWarnings("interning:not.interned") // AST node comparisons + public static @Nullable AnnotatedTypeMirror assignedTo( + AnnotatedTypeFactory atypeFactory, TreePath path) { + Tree assignmentContext = TreePathUtil.getAssignmentContext(path); + AnnotatedTypeMirror res; + if (assignmentContext == null) { + res = null; + } else if (assignmentContext instanceof AssignmentTree) { + ExpressionTree variable = ((AssignmentTree) assignmentContext).getVariable(); + res = atypeFactory.getAnnotatedType(variable); + } else if (assignmentContext instanceof CompoundAssignmentTree) { + ExpressionTree variable = ((CompoundAssignmentTree) assignmentContext).getVariable(); + res = atypeFactory.getAnnotatedType(variable); + } else if (assignmentContext instanceof MethodInvocationTree) { + MethodInvocationTree methodInvocation = (MethodInvocationTree) assignmentContext; + // TODO move to getAssignmentContext + if (methodInvocation.getMethodSelect() instanceof MemberSelectTree + && ((MemberSelectTree) methodInvocation.getMethodSelect()).getExpression() + == path.getLeaf()) { + return null; + } + ExecutableElement methodElt = TreeUtils.elementFromUse(methodInvocation); + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(methodInvocation); + if (TreeUtils.isSuperConstructorCall(methodInvocation)) { + receiver = atypeFactory.getSelfType(methodInvocation); + } + res = + assignedToExecutable( + atypeFactory, path, methodElt, receiver, methodInvocation.getArguments()); + } else if (assignmentContext instanceof NewArrayTree) { + // TODO: I left the previous implementation below, it definitely caused infinite loops + // TODO: if you called it from places like the TreeAnnotator. + res = null; + + // TODO: This may cause infinite loop + // AnnotatedTypeMirror type = + // atypeFactory.getAnnotatedType((NewArrayTree)assignmentContext); + // type = AnnotatedTypes.innerMostType(type); + // return type; + + } else if (assignmentContext instanceof NewClassTree) { + // This need to be basically like MethodTree + NewClassTree newClassTree = (NewClassTree) assignmentContext; + if (newClassTree.getEnclosingExpression() instanceof NewClassTree + && (newClassTree.getEnclosingExpression() == path.getLeaf())) { + return null; + } + ExecutableElement constructorElt = TreeUtils.elementFromUse(newClassTree); + // TODO: This call should be removed once #979 is implemented. + // Change this to atypeFactory.getAnnotatedType(newClassTree) causes infinite recursion + // in the InitializationAnnotatedTypeFactory.CommitmentTreeAnnotator.visitNewClass. + @SuppressWarnings("deprecation") + AnnotatedTypeMirror receiver = atypeFactory.fromNewClass(newClassTree); + res = + assignedToExecutable( + atypeFactory, path, constructorElt, receiver, newClassTree.getArguments()); + } else if (assignmentContext instanceof ReturnTree) { + HashSet kinds = + new HashSet<>(Arrays.asList(Tree.Kind.LAMBDA_EXPRESSION, Tree.Kind.METHOD)); + Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds); + + if (enclosing.getKind() == Tree.Kind.METHOD) { + res = atypeFactory.getAnnotatedType((MethodTree) enclosing).getReturnType(); + } else { + AnnotatedExecutableType fninf = + atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); + res = fninf.getReturnType(); + } + + } else if (assignmentContext instanceof VariableTree) { + res = assignedToVariable(atypeFactory, (VariableTree) assignmentContext); + } else { + throw new BugInCF("AnnotatedTypes.assignedTo: shouldn't be here"); } - /** - * Throws an exception if the type is an uninferred type argument. - * - *

The error will be caught in DefaultTypeArgumentInference#infer and inference will be - * aborted, but type-checking will continue. - */ - public static void checkForUninferredTypes(AnnotatedTypeMirror type) { - if (type.getKind() != TypeKind.WILDCARD) { - return; - } - if (((AnnotatedWildcardType) type).isUninferredTypeArgument()) { - throw new BugInCF("Can't make a constraint that includes an uninferred type argument."); - } + if (res != null && TypesUtils.isPrimitive(res.getUnderlyingType())) { + return atypeFactory.getBoxedType((AnnotatedPrimitiveType) res); + } else { + return res; + } + } + + private static @Nullable AnnotatedTypeMirror assignedToExecutable( + AnnotatedTypeFactory atypeFactory, + TreePath path, + ExecutableElement methodElt, + AnnotatedTypeMirror receiver, + List arguments) { + AnnotatedExecutableType method = + AnnotatedTypes.asMemberOf( + atypeFactory.getChecker().getTypeUtils(), atypeFactory, receiver, methodElt); + int treeIndex = -1; + for (int i = 0; i < arguments.size(); ++i) { + ExpressionTree argumentTree = arguments.get(i); + if (isArgument(path, argumentTree)) { + treeIndex = i; + break; + } + } + final AnnotatedTypeMirror paramType; + if (treeIndex == -1) { + // The tree wasn't found as an argument, so it has to be the receiver. + // This can happen for inner class constructors that take an outer class argument. + paramType = method.getReceiverType(); + } else if (treeIndex + 1 >= method.getParameterTypes().size() && methodElt.isVarArgs()) { + AnnotatedTypeMirror varArgsType = + method.getParameterTypes().get(method.getParameterTypes().size() - 1); + paramType = ((AnnotatedArrayType) varArgsType).getComponentType(); + } else { + paramType = method.getParameterTypes().get(treeIndex); } - /** - * Used to detect if the visited type contains one of the type variables in the typeVars - * parameter. - */ - private static class TypeVariableFinder - extends AnnotatedTypeScanner> { + // Examples like this: + // T outMethod() + // void inMethod(U u); + // inMethod(outMethod()) + // would require solving the constraints for both type argument inferences simultaneously + if (paramType == null || containsUninferredTypeParameter(paramType, method)) { + return null; + } - /** Create TypeVariableFinder. */ - protected TypeVariableFinder() { - super(Boolean::logicalOr, false); - } + return paramType; + } + + /** + * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional + * expression, isArgument is called recursively on the true and false expressions. + * + * @param path the path whose leaf to test + * @param argumentTree the expression that might be path's leaf + * @return true if {@code argumentTree} is the leaf of {@code path} + */ + private static boolean isArgument(TreePath path, @FindDistinct ExpressionTree argumentTree) { + argumentTree = TreeUtils.withoutParens(argumentTree); + if (argumentTree == path.getLeaf()) { + return true; + } else if (argumentTree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { + ConditionalExpressionTree conditionalExpressionTree = + (ConditionalExpressionTree) argumentTree; + return isArgument(path, conditionalExpressionTree.getTrueExpression()) + || isArgument(path, conditionalExpressionTree.getFalseExpression()); + } + return false; + } + + /** + * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree). + * Rationale: + * + *

For example: + * + *

{@code
+   *  S bar () {...}
+   *
+   *  T foo(T p) {
+   *     T local = bar();
+   *     return local;
+   *   }
+   * }
+ * + * During type argument inference of {@code bar}, the assignment context is {@code local}. If the + * local variable default is used, then the type of assignment context type is {@code @Nullable T} + * and the type argument inferred for {@code bar()} is {@code @Nullable T}. And an incompatible + * types in return error is issued. + * + *

If instead, the local variable default is not applied, then the assignment context type is + * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) and + * the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of {@code + * local} is refined to {@code T} and the return is legal. + * + *

If the assignment context type was a declared type, for example: + * + *

{@code
+   *  S bar () {...}
+   * Object foo() {
+   *     Object local = bar();
+   *     return local;
+   * }
+   * }
+ * + * The local variable default must be used or else the assignment context type is missing an + * annotation. So, an incompatible types in return error is issued in the above code. We could + * improve type argument inference in this case and by using the lower bound of {@code S} instead + * of the local variable default. + * + * @param atypeFactory the type factory + * @param assignmentContext VariableTree + * @return AnnotatedTypeMirror of Assignment context + */ + public static AnnotatedTypeMirror assignedToVariable( + AnnotatedTypeFactory atypeFactory, VariableTree assignmentContext) { + if (TreeUtils.isVariableTreeDeclaredUsingVar(assignmentContext)) { + return null; + } + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + GenericAnnotatedTypeFactory gatf = + ((GenericAnnotatedTypeFactory) atypeFactory); + return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext); + } else { + return atypeFactory.getAnnotatedType(assignmentContext); + } + } + + /** + * Returns true if the type contains a use of a type variable from methodType. + * + * @return true if the type contains a use of a type variable from methodType + */ + private static boolean containsUninferredTypeParameter( + AnnotatedTypeMirror type, AnnotatedExecutableType methodType) { + List annotatedTypeVars = methodType.getTypeVariables(); + List typeVars = + CollectionsPlume.mapList( + (AnnotatedTypeVariable annotatedTypeVar) -> + (TypeVariable) + TypeAnnotationUtils.unannotatedType(annotatedTypeVar.getUnderlyingType()), + annotatedTypeVars); + + return containsTypeParameter(type, typeVars); + } + + /** + * Returns true if {@code type} contains a use of a type variable in {@code typeVariables}. + * + * @param type type to search + * @param typeVariables collection of type variables + * @return true if {@code type} contains a use of a type variable in {@code typeVariables} + */ + public static boolean containsTypeParameter( + AnnotatedTypeMirror type, Collection typeVariables) { + // note NULL values creep in because the underlying visitor uses them in various places + Boolean result = type.accept(new TypeVariableFinder(), typeVariables); + return result != null && result; + } + + /** + * Take a set of annotations and separate them into a mapping of {@code hierarchy top => + * annotations in hierarchy}. + */ + public static AnnotationMirrorMap createHierarchyMap( + AnnotationMirrorSet annos, QualifierHierarchy qualHierarchy) { + AnnotationMirrorMap result = new AnnotationMirrorMap<>(); + + for (AnnotationMirror anno : annos) { + result.put(qualHierarchy.getTopAnnotation(anno), anno); + } - @Override - public Boolean visitTypeVariable( - AnnotatedTypeVariable type, Collection typeVars) { - if (typeVars.contains( - (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()))) { - return true; - } else { - return super.visitTypeVariable(type, typeVars); - } - } + return result; + } + + /** + * Throws an exception if the type is an uninferred type argument. + * + *

The error will be caught in DefaultTypeArgumentInference#infer and inference will be + * aborted, but type-checking will continue. + */ + public static void checkForUninferredTypes(AnnotatedTypeMirror type) { + if (type.getKind() != TypeKind.WILDCARD) { + return; + } + if (((AnnotatedWildcardType) type).isUninferredTypeArgument()) { + throw new BugInCF("Can't make a constraint that includes an uninferred type argument."); + } + } + + /** + * Used to detect if the visited type contains one of the type variables in the typeVars + * parameter. + */ + private static class TypeVariableFinder + extends AnnotatedTypeScanner> { + + /** Create TypeVariableFinder. */ + protected TypeVariableFinder() { + super(Boolean::logicalOr, false); } - /* - * Various TypeArgumentInference steps require substituting types for type arguments that have already been - * inferred into constraints that are used infer other type arguments. Substituter is used in - * the utility methods to do this. - */ - private static final TypeVariableSubstitutor substitutor = new TypeVariableSubstitutor(); - - // Substituter requires an input map that the substitute methods build. We just reuse the same - // map rather than recreate it each time. - private static final Map substituteMap = new HashMap<>(5); - - /** - * Replace all uses of typeVariable with substitution in a copy of toModify using the normal - * substitution rules. Return the copy - * - * @see TypeVariableSubstitutor - */ - public static AnnotatedTypeMirror substitute( - TypeVariable typeVariable, - AnnotatedTypeMirror substitution, - AnnotatedTypeMirror toModify) { - substituteMap.clear(); - substituteMap.put(typeVariable, substitution.deepCopy()); - - AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); - substitutor.substitute(substituteMap, toModifyCopy); - return toModifyCopy; - } - - /** - * Create a copy of toModify. In the copy, for each pair {@code typeVariable => annotated type} - * replace uses of typeVariable with the corresponding annotated type using normal substitution - * rules (@see TypeVariableSubstitutor). Return the copy. - */ - public static AnnotatedTypeMirror substitute( - Map substitutions, AnnotatedTypeMirror toModify) { - final AnnotatedTypeMirror substitution; - if (toModify.getKind() == TypeKind.TYPEVAR) { - substitution = - substitutions.get( - (TypeVariable) - TypeAnnotationUtils.unannotatedType( - toModify.getUnderlyingType())); - } else { - substitution = null; - } - if (substitution != null) { - return substitution.deepCopy(); - } + @Override + public Boolean visitTypeVariable( + AnnotatedTypeVariable type, Collection typeVars) { + if (typeVars.contains( + (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()))) { + return true; + } else { + return super.visitTypeVariable(type, typeVars); + } + } + } + + /* + * Various TypeArgumentInference steps require substituting types for type arguments that have already been + * inferred into constraints that are used infer other type arguments. Substituter is used in + * the utility methods to do this. + */ + private static final TypeVariableSubstitutor substitutor = new TypeVariableSubstitutor(); + + // Substituter requires an input map that the substitute methods build. We just reuse the same + // map rather than recreate it each time. + private static final Map substituteMap = new HashMap<>(5); + + /** + * Replace all uses of typeVariable with substitution in a copy of toModify using the normal + * substitution rules. Return the copy + * + * @see TypeVariableSubstitutor + */ + public static AnnotatedTypeMirror substitute( + TypeVariable typeVariable, AnnotatedTypeMirror substitution, AnnotatedTypeMirror toModify) { + substituteMap.clear(); + substituteMap.put(typeVariable, substitution.deepCopy()); + + AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); + substitutor.substitute(substituteMap, toModifyCopy); + return toModifyCopy; + } + + /** + * Create a copy of toModify. In the copy, for each pair {@code typeVariable => annotated type} + * replace uses of typeVariable with the corresponding annotated type using normal substitution + * rules (@see TypeVariableSubstitutor). Return the copy. + */ + public static AnnotatedTypeMirror substitute( + Map substitutions, AnnotatedTypeMirror toModify) { + final AnnotatedTypeMirror substitution; + if (toModify.getKind() == TypeKind.TYPEVAR) { + substitution = + substitutions.get( + (TypeVariable) TypeAnnotationUtils.unannotatedType(toModify.getUnderlyingType())); + } else { + substitution = null; + } + if (substitution != null) { + return substitution.deepCopy(); + } - AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); - substitutor.substitute(substitutions, toModifyCopy); - return toModifyCopy; + AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); + substitutor.substitute(substitutions, toModifyCopy); + return toModifyCopy; + } + + /** + * Successively calls least upper bound on the elements of types. Unlike leastUpperBound, this + * method will box primitives if necessary + */ + public static AnnotatedTypeMirror leastUpperBound( + AnnotatedTypeFactory typeFactory, Iterable types) { + Iterator typesIter = types.iterator(); + if (!typesIter.hasNext()) { + throw new BugInCF("Calling LUB on empty list"); } + AnnotatedTypeMirror lubType = typesIter.next(); + AnnotatedTypeMirror nextType = null; + while (typesIter.hasNext()) { + nextType = typesIter.next(); - /** - * Successively calls least upper bound on the elements of types. Unlike leastUpperBound, this - * method will box primitives if necessary - */ - public static AnnotatedTypeMirror leastUpperBound( - AnnotatedTypeFactory typeFactory, Iterable types) { - Iterator typesIter = types.iterator(); - if (!typesIter.hasNext()) { - throw new BugInCF("Calling LUB on empty list"); + if (lubType.getKind().isPrimitive()) { + if (!nextType.getKind().isPrimitive()) { + lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType); } - AnnotatedTypeMirror lubType = typesIter.next(); - AnnotatedTypeMirror nextType = null; - while (typesIter.hasNext()) { - nextType = typesIter.next(); - - if (lubType.getKind().isPrimitive()) { - if (!nextType.getKind().isPrimitive()) { - lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType); - } - } else if (nextType.getKind().isPrimitive()) { - if (!lubType.getKind().isPrimitive()) { - nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType); - } - } - lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType); + } else if (nextType.getKind().isPrimitive()) { + if (!lubType.getKind().isPrimitive()) { + nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType); } + } + lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType); + } - return lubType; - } - - /** - * If the type arguments computed by DefaultTypeArgumentInference don't match the return type - * mirror of {@code invocation}, then replace those type arguments with an uninferred wildcard. - */ - protected static Map correctResults( - Map result, - ExpressionTree invocation, - ExecutableType methodType, - AnnotatedTypeFactory factory) { - ProcessingEnvironment env = factory.getProcessingEnv(); - Types types = env.getTypeUtils(); - Map fromReturn = - getMappingFromReturnType(invocation, methodType, env); - for (Map.Entry entry : - // result is side-effected by this loop, so iterate over a copy - new ArrayList<>(result.entrySet())) { - TypeVariable typeVariable = entry.getKey(); - if (!fromReturn.containsKey(typeVariable)) { - continue; - } - TypeMirror correctType = fromReturn.get(typeVariable); - TypeMirror inferredType = entry.getValue().getUnderlyingType(); - if (types.isSameType(types.erasure(correctType), types.erasure(inferredType))) { - if (areSameCapture(correctType, inferredType)) { - continue; - } - } - if (!types.isSameType(correctType, inferredType)) { - AnnotatedWildcardType wt = - factory.getUninferredWildcardType( - (AnnotatedTypeVariable) - AnnotatedTypeMirror.createType( - typeVariable, factory, false)); - wt.replaceAnnotations(entry.getValue().getAnnotations()); - result.put(typeVariable, wt); - } - } - return result; - } - - /** - * Returns true if actual and inferred are captures of the same wildcard or declared type. - * - * @param actual the actual type - * @param inferred the inferred type - * @return true if actual and inferred are captures of the same wildcard or declared type - */ - private static boolean areSameCapture(TypeMirror actual, TypeMirror inferred) { - if (TypesUtils.isCapturedTypeVariable(actual) - && TypesUtils.isCapturedTypeVariable(inferred)) { - return true; - } else if (TypesUtils.isCapturedTypeVariable(actual) - && inferred.getKind() == TypeKind.WILDCARD) { - return true; - } else if (actual.getKind() == TypeKind.DECLARED - && inferred.getKind() == TypeKind.DECLARED) { - DeclaredType actualDT = (DeclaredType) actual; - DeclaredType inferredDT = (DeclaredType) inferred; - if (actualDT.getTypeArguments().size() == inferredDT.getTypeArguments().size()) { - for (int i = 0; i < actualDT.getTypeArguments().size(); i++) { - if (!areSameCapture( - actualDT.getTypeArguments().get(i), - inferredDT.getTypeArguments().get(i))) { - return false; - } - } - return true; - } - } - return false; - } - - /** - * Returns a mapping of type variable to type argument computed using the type of {@code - * methodInvocationTree} and the return type of {@code methodType}. - */ - private static Map getMappingFromReturnType( - ExpressionTree methodInvocationTree, - ExecutableType methodType, - ProcessingEnvironment env) { - TypeMirror methodCallType = TreeUtils.typeOf(methodInvocationTree); - Types types = env.getTypeUtils(); - GetMapping mapping = new GetMapping(methodType.getTypeVariables(), types); - mapping.visit(methodType.getReturnType(), methodCallType); - return mapping.subs; - } - - /** - * Helper class for {@link #getMappingFromReturnType(ExpressionTree, ExecutableType, - * ProcessingEnvironment)} - */ - private static class GetMapping implements TypeVisitor { - - final Map subs = new HashMap<>(); - final List typeVariables; - final Types types; - - private GetMapping(List typeVariables, Types types) { - this.typeVariables = typeVariables; - this.types = types; - } + return lubType; + } + + /** + * If the type arguments computed by DefaultTypeArgumentInference don't match the return type + * mirror of {@code invocation}, then replace those type arguments with an uninferred wildcard. + */ + protected static Map correctResults( + Map result, + ExpressionTree invocation, + ExecutableType methodType, + AnnotatedTypeFactory factory) { + ProcessingEnvironment env = factory.getProcessingEnv(); + Types types = env.getTypeUtils(); + Map fromReturn = + getMappingFromReturnType(invocation, methodType, env); + for (Map.Entry entry : + // result is side-effected by this loop, so iterate over a copy + new ArrayList<>(result.entrySet())) { + TypeVariable typeVariable = entry.getKey(); + if (!fromReturn.containsKey(typeVariable)) { + continue; + } + TypeMirror correctType = fromReturn.get(typeVariable); + TypeMirror inferredType = entry.getValue().getUnderlyingType(); + if (types.isSameType(types.erasure(correctType), types.erasure(inferredType))) { + if (areSameCapture(correctType, inferredType)) { + continue; + } + } + if (!types.isSameType(correctType, inferredType)) { + AnnotatedWildcardType wt = + factory.getUninferredWildcardType( + (AnnotatedTypeVariable) + AnnotatedTypeMirror.createType(typeVariable, factory, false)); + wt.replaceAnnotations(entry.getValue().getAnnotations()); + result.put(typeVariable, wt); + } + } + return result; + } + + /** + * Returns true if actual and inferred are captures of the same wildcard or declared type. + * + * @param actual the actual type + * @param inferred the inferred type + * @return true if actual and inferred are captures of the same wildcard or declared type + */ + private static boolean areSameCapture(TypeMirror actual, TypeMirror inferred) { + if (TypesUtils.isCapturedTypeVariable(actual) && TypesUtils.isCapturedTypeVariable(inferred)) { + return true; + } else if (TypesUtils.isCapturedTypeVariable(actual) + && inferred.getKind() == TypeKind.WILDCARD) { + return true; + } else if (actual.getKind() == TypeKind.DECLARED && inferred.getKind() == TypeKind.DECLARED) { + DeclaredType actualDT = (DeclaredType) actual; + DeclaredType inferredDT = (DeclaredType) inferred; + if (actualDT.getTypeArguments().size() == inferredDT.getTypeArguments().size()) { + for (int i = 0; i < actualDT.getTypeArguments().size(); i++) { + if (!areSameCapture( + actualDT.getTypeArguments().get(i), inferredDT.getTypeArguments().get(i))) { + return false; + } + } + return true; + } + } + return false; + } + + /** + * Returns a mapping of type variable to type argument computed using the type of {@code + * methodInvocationTree} and the return type of {@code methodType}. + */ + private static Map getMappingFromReturnType( + ExpressionTree methodInvocationTree, ExecutableType methodType, ProcessingEnvironment env) { + TypeMirror methodCallType = TreeUtils.typeOf(methodInvocationTree); + Types types = env.getTypeUtils(); + GetMapping mapping = new GetMapping(methodType.getTypeVariables(), types); + mapping.visit(methodType.getReturnType(), methodCallType); + return mapping.subs; + } + + /** + * Helper class for {@link #getMappingFromReturnType(ExpressionTree, ExecutableType, + * ProcessingEnvironment)} + */ + private static class GetMapping implements TypeVisitor { + + final Map subs = new HashMap<>(); + final List typeVariables; + final Types types; + + private GetMapping(List typeVariables, Types types) { + this.typeVariables = typeVariables; + this.types = types; + } - @Override - public Void visit(TypeMirror t, TypeMirror mirror) { - if (t == null || mirror == null) { - return null; - } - return t.accept(this, mirror); - } + @Override + public Void visit(TypeMirror t, TypeMirror mirror) { + if (t == null || mirror == null) { + return null; + } + return t.accept(this, mirror); + } - @Override - public Void visit(TypeMirror t) { - return null; - } + @Override + public Void visit(TypeMirror t) { + return null; + } - @Override - public Void visitPrimitive(PrimitiveType t, TypeMirror mirror) { - return null; - } + @Override + public Void visitPrimitive(PrimitiveType t, TypeMirror mirror) { + return null; + } - @Override - public Void visitNull(NullType t, TypeMirror mirror) { - return null; - } + @Override + public Void visitNull(NullType t, TypeMirror mirror) { + return null; + } - @Override - public Void visitArray(ArrayType t, TypeMirror mirror) { - assert mirror.getKind() == TypeKind.ARRAY : mirror; - return visit(t.getComponentType(), ((ArrayType) mirror).getComponentType()); - } + @Override + public Void visitArray(ArrayType t, TypeMirror mirror) { + assert mirror.getKind() == TypeKind.ARRAY : mirror; + return visit(t.getComponentType(), ((ArrayType) mirror).getComponentType()); + } - @Override - public Void visitDeclared(DeclaredType t, TypeMirror mirror) { - assert mirror.getKind() == TypeKind.DECLARED : mirror; - DeclaredType param = (DeclaredType) mirror; - if (types.isSubtype(mirror, param)) { - // param = (DeclaredType) types.asSuper((Type) mirror, ((Type) - // param).asElement()); - } - if (t.getTypeArguments().size() == param.getTypeArguments().size()) { - for (int i = 0; i < t.getTypeArguments().size(); i++) { - visit(t.getTypeArguments().get(i), param.getTypeArguments().get(i)); - } - } - return null; - } + @Override + public Void visitDeclared(DeclaredType t, TypeMirror mirror) { + assert mirror.getKind() == TypeKind.DECLARED : mirror; + DeclaredType param = (DeclaredType) mirror; + if (types.isSubtype(mirror, param)) { + // param = (DeclaredType) types.asSuper((Type) mirror, ((Type) + // param).asElement()); + } + if (t.getTypeArguments().size() == param.getTypeArguments().size()) { + for (int i = 0; i < t.getTypeArguments().size(); i++) { + visit(t.getTypeArguments().get(i), param.getTypeArguments().get(i)); + } + } + return null; + } - @Override - public Void visitError(ErrorType t, TypeMirror mirror) { - return null; - } + @Override + public Void visitError(ErrorType t, TypeMirror mirror) { + return null; + } - @Override - public Void visitTypeVariable(TypeVariable t, TypeMirror mirror) { - if (typeVariables.contains(t)) { - subs.put(t, mirror); - } else if (mirror.getKind() == TypeKind.TYPEVAR) { - TypeVariable param = (TypeVariable) mirror; - visit(t.getUpperBound(), param.getUpperBound()); - visit(t.getLowerBound(), param.getLowerBound()); - } - // else it's not a method type variable - return null; - } + @Override + public Void visitTypeVariable(TypeVariable t, TypeMirror mirror) { + if (typeVariables.contains(t)) { + subs.put(t, mirror); + } else if (mirror.getKind() == TypeKind.TYPEVAR) { + TypeVariable param = (TypeVariable) mirror; + visit(t.getUpperBound(), param.getUpperBound()); + visit(t.getLowerBound(), param.getLowerBound()); + } + // else it's not a method type variable + return null; + } - @Override - public Void visitWildcard(javax.lang.model.type.WildcardType t, TypeMirror mirror) { - if (mirror.getKind() == TypeKind.WILDCARD) { - javax.lang.model.type.WildcardType param = - (javax.lang.model.type.WildcardType) mirror; - visit(t.getExtendsBound(), param.getExtendsBound()); - visit(t.getSuperBound(), param.getSuperBound()); - } else if (mirror.getKind() == TypeKind.TYPEVAR) { - TypeVariable param = (TypeVariable) mirror; - visit(t.getExtendsBound(), param.getUpperBound()); - visit(t.getSuperBound(), param.getLowerBound()); - } else { - assert false : mirror; - } - return null; - } + @Override + public Void visitWildcard(javax.lang.model.type.WildcardType t, TypeMirror mirror) { + if (mirror.getKind() == TypeKind.WILDCARD) { + javax.lang.model.type.WildcardType param = (javax.lang.model.type.WildcardType) mirror; + visit(t.getExtendsBound(), param.getExtendsBound()); + visit(t.getSuperBound(), param.getSuperBound()); + } else if (mirror.getKind() == TypeKind.TYPEVAR) { + TypeVariable param = (TypeVariable) mirror; + visit(t.getExtendsBound(), param.getUpperBound()); + visit(t.getSuperBound(), param.getLowerBound()); + } else { + assert false : mirror; + } + return null; + } - @Override - public Void visitExecutable(ExecutableType t, TypeMirror mirror) { - return null; - } + @Override + public Void visitExecutable(ExecutableType t, TypeMirror mirror) { + return null; + } - @Override - public Void visitNoType(NoType t, TypeMirror mirror) { - return null; - } + @Override + public Void visitNoType(NoType t, TypeMirror mirror) { + return null; + } - @Override - public Void visitUnknown(TypeMirror t, TypeMirror mirror) { - return null; - } + @Override + public Void visitUnknown(TypeMirror t, TypeMirror mirror) { + return null; + } - @Override - public Void visitUnion(UnionType t, TypeMirror mirror) { - return null; - } + @Override + public Void visitUnion(UnionType t, TypeMirror mirror) { + return null; + } - @Override - public Void visitIntersection(IntersectionType t, TypeMirror mirror) { - assert mirror.getKind() == TypeKind.INTERSECTION : mirror; - IntersectionType param = (IntersectionType) mirror; - assert t.getBounds().size() == param.getBounds().size(); + @Override + public Void visitIntersection(IntersectionType t, TypeMirror mirror) { + assert mirror.getKind() == TypeKind.INTERSECTION : mirror; + IntersectionType param = (IntersectionType) mirror; + assert t.getBounds().size() == param.getBounds().size(); - for (int i = 0; i < t.getBounds().size(); i++) { - visit(t.getBounds().get(i), param.getBounds().get(i)); - } + for (int i = 0; i < t.getBounds().size(); i++) { + visit(t.getBounds().get(i), param.getBounds().get(i)); + } - return null; - } + return null; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java index 802623be2fa..7bd41884284 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java @@ -1,15 +1,12 @@ package org.checkerframework.framework.util.typeinference; import com.sun.source.tree.ExpressionTree; - -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; - import java.util.Map; - import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; /** * Instances of TypeArgumentInference are used to infer the types of method type arguments when no @@ -40,22 +37,22 @@ */ public interface TypeArgumentInference { - /** - * Infer the type arguments for the method or constructor invocation given by invocation. - * - * @param typeFactory the type factory used to create methodType - * @param invocation a tree representing the method or constructor invocation for which we are - * inferring type arguments - * @param methodElem the element for the declaration of the method being invoked - * @param methodType the declaration type of method elem - * @return a mapping between the Java type parameter and the annotated type that was inferred - * for it. Note: We use the Java TypeVariable type because this uniquely identifies a - * declaration where as two uses of an AnnotatedTypeVariable may be uses of the same - * declaration but are not .equals to each other. - */ - public Map inferTypeArgs( - AnnotatedTypeFactory typeFactory, - ExpressionTree invocation, - ExecutableElement methodElem, - final AnnotatedExecutableType methodType); + /** + * Infer the type arguments for the method or constructor invocation given by invocation. + * + * @param typeFactory the type factory used to create methodType + * @param invocation a tree representing the method or constructor invocation for which we are + * inferring type arguments + * @param methodElem the element for the declaration of the method being invoked + * @param methodType the declaration type of method elem + * @return a mapping between the Java type parameter and the annotated type that was inferred for + * it. Note: We use the Java TypeVariable type because this uniquely identifies a declaration + * where as two uses of an AnnotatedTypeVariable may be uses of the same declaration but are + * not .equals to each other. + */ + public Map inferTypeArgs( + AnnotatedTypeFactory typeFactory, + ExpressionTree invocation, + ExecutableElement methodElem, + final AnnotatedExecutableType methodType); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java index 821ba669c73..d6c96de0c09 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java @@ -10,24 +10,23 @@ */ public class A2F extends AFConstraint { - /** Create a constraint with an argument less than a formal. */ - public A2F(AnnotatedTypeMirror argument, AnnotatedTypeMirror formalParameter) { - super(argument, formalParameter); - } + /** Create a constraint with an argument less than a formal. */ + public A2F(AnnotatedTypeMirror argument, AnnotatedTypeMirror formalParameter) { + super(argument, formalParameter); + } - @Override - public TUConstraint toTUConstraint() { - return new TSuperU((AnnotatedTypeVariable) formalParameter, argument, true); - } + @Override + public TUConstraint toTUConstraint() { + return new TSuperU((AnnotatedTypeVariable) formalParameter, argument, true); + } - @Override - protected A2F construct( - AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { - return new A2F(newArgument, newFormalParameter); - } + @Override + protected A2F construct(AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { + return new A2F(newArgument, newFormalParameter); + } - @Override - public String toString() { - return "A2F( " + argument + " << " + formalParameter + " )"; - } + @Override + public String toString() { + return "A2F( " + argument + " << " + formalParameter + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java index 541289e1efc..25ef25ee601 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.util.typeinference.constraint; +import java.util.Set; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; -import java.util.Set; - /** * A2FReducer takes an A2F constraint that is not irreducible (@see AFConstraint.isIrreducible) and * reduces it by one step. The resulting constraint may still be reducible. @@ -14,60 +13,59 @@ */ public class A2FReducer implements AFReducer { - protected final A2FReducingVisitor visitor; + protected final A2FReducingVisitor visitor; - public A2FReducer(AnnotatedTypeFactory typeFactory) { - this.visitor = new A2FReducingVisitor(typeFactory); - } + public A2FReducer(AnnotatedTypeFactory typeFactory) { + this.visitor = new A2FReducingVisitor(typeFactory); + } - @Override - public boolean reduce(AFConstraint constraint, Set newConstraints) { - if (constraint instanceof A2F) { - A2F a2f = (A2F) constraint; - visitor.visit(a2f.argument, a2f.formalParameter, newConstraints); - return true; + @Override + public boolean reduce(AFConstraint constraint, Set newConstraints) { + if (constraint instanceof A2F) { + A2F a2f = (A2F) constraint; + visitor.visit(a2f.argument, a2f.formalParameter, newConstraints); + return true; - } else { - return false; - } + } else { + return false; } + } - /** - * Given an A2F constraint of the form: A2F( typeFromMethodArgument, typeFromFormalParameter ) - * - *

A2FReducingVisitor visits the constraint as follows: visit ( typeFromMethodArgument, - * typeFromFormalParameter, newConstraints ) - * - *

The visit method will determine if the given constraint should either: - * - *

    - *
  • be discarded -- in this case, the visitor just returns - *
  • reduced to a simpler constraint or set of constraints -- in this case, the new - * constraint or set of constraints is added to newConstraints - *
- */ - private static class A2FReducingVisitor extends AFReducingVisitor { + /** + * Given an A2F constraint of the form: A2F( typeFromMethodArgument, typeFromFormalParameter ) + * + *

A2FReducingVisitor visits the constraint as follows: visit ( typeFromMethodArgument, + * typeFromFormalParameter, newConstraints ) + * + *

The visit method will determine if the given constraint should either: + * + *

    + *
  • be discarded -- in this case, the visitor just returns + *
  • reduced to a simpler constraint or set of constraints -- in this case, the new constraint + * or set of constraints is added to newConstraints + *
+ */ + private static class A2FReducingVisitor extends AFReducingVisitor { - public A2FReducingVisitor(AnnotatedTypeFactory typeFactory) { - super(A2F.class, typeFactory); - } + public A2FReducingVisitor(AnnotatedTypeFactory typeFactory) { + super(A2F.class, typeFactory); + } - @Override - public AFConstraint makeConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new A2F(subtype, supertype); - } + @Override + public AFConstraint makeConstraint(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + return new A2F(subtype, supertype); + } - @Override - public AFConstraint makeInverseConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new F2A(subtype, supertype); - } + @Override + public AFConstraint makeInverseConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + return new F2A(subtype, supertype); + } - @Override - public AFConstraint makeEqualityConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new FIsA(supertype, subtype); - } + @Override + public AFConstraint makeEqualityConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + return new FIsA(supertype, subtype); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java index dd803b8e5d8..5165289d9de 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java @@ -1,14 +1,12 @@ package org.checkerframework.framework.util.typeinference.constraint; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; - import java.util.Map; import java.util.Objects; import java.util.Set; - import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; /** * AFConstraint represent the initial constraints used to infer type arguments for method @@ -34,92 +32,89 @@ * A is represented by class FIsA */ public abstract class AFConstraint { - /** The argument type. */ - public final AnnotatedTypeMirror argument; + /** The argument type. */ + public final AnnotatedTypeMirror argument; - /** The formal parameter type. */ - public final AnnotatedTypeMirror formalParameter; + /** The formal parameter type. */ + public final AnnotatedTypeMirror formalParameter; - /** Create a constraint for type arguments for a method invocation or new class invocation. */ - protected AFConstraint(AnnotatedTypeMirror argument, AnnotatedTypeMirror formalParameter) { - this.argument = argument; - this.formalParameter = formalParameter; - TypeArgInferenceUtil.checkForUninferredTypes(argument); - } + /** Create a constraint for type arguments for a method invocation or new class invocation. */ + protected AFConstraint(AnnotatedTypeMirror argument, AnnotatedTypeMirror formalParameter) { + this.argument = argument; + this.formalParameter = formalParameter; + TypeArgInferenceUtil.checkForUninferredTypes(argument); + } - /** - * Returns true if this constraint can't be broken up into other constraints or further - * simplified. In general, if either argument or formal parameter is a use of the type - * parameters we are inferring over then the constraint cannot be reduced further. - * - * @param targets the type parameters whose arguments we are trying to solve for - * @return true if this constraint can't be broken up into other constraints or further - * simplified - */ - public boolean isIrreducible(Set targets) { - return TypeArgInferenceUtil.isATarget(argument, targets) - || TypeArgInferenceUtil.isATarget(formalParameter, targets); - } + /** + * Returns true if this constraint can't be broken up into other constraints or further + * simplified. In general, if either argument or formal parameter is a use of the type parameters + * we are inferring over then the constraint cannot be reduced further. + * + * @param targets the type parameters whose arguments we are trying to solve for + * @return true if this constraint can't be broken up into other constraints or further simplified + */ + public boolean isIrreducible(Set targets) { + return TypeArgInferenceUtil.isATarget(argument, targets) + || TypeArgInferenceUtil.isATarget(formalParameter, targets); + } - @Override - public boolean equals(@Nullable Object thatObject) { - if (this == thatObject) { - return true; - } // else + @Override + public boolean equals(@Nullable Object thatObject) { + if (this == thatObject) { + return true; + } // else - if (thatObject == null || this.getClass() != thatObject.getClass()) { - return false; - } + if (thatObject == null || this.getClass() != thatObject.getClass()) { + return false; + } - AFConstraint that = (AFConstraint) thatObject; + AFConstraint that = (AFConstraint) thatObject; - return this.argument.equals(that.argument) - && this.formalParameter.equals(that.formalParameter); - } + return this.argument.equals(that.argument) && this.formalParameter.equals(that.formalParameter); + } - @Override - public int hashCode() { - return Objects.hash(this.getClass(), formalParameter, argument); - } + @Override + public int hashCode() { + return Objects.hash(this.getClass(), formalParameter, argument); + } - /** - * Once AFConstraints are irreducible it can be converted to a TU constraint, constraints - * between individual type parameters for which we are inferring an argument (T) and Java types - * (U). - * - * @return a TUConstraint that represents this AFConstraint - */ - public abstract TUConstraint toTUConstraint(); + /** + * Once AFConstraints are irreducible it can be converted to a TU constraint, constraints between + * individual type parameters for which we are inferring an argument (T) and Java types (U). + * + * @return a TUConstraint that represents this AFConstraint + */ + public abstract TUConstraint toTUConstraint(); - /** - * Given a partial solution to our type argument inference, replace any uses of type parameters - * that have been solved with their arguments. - * - *

That is: Let S be a partial solution to our inference (i.e. we have inferred type - * arguments for some types) Let S be a map {@code (T0 => A0, T1 => A1, ..., TN => AN)} where Ti - * is a type parameter and Ai is its solved argument. For all uses of Ti in this constraint, - * replace them with Ai. - * - *

For the mapping {@code (T0 => A0)}, the following constraint: {@code ArrayList << - * List} - * - *

Becomes: {@code ArrayList << List} - * - *

A constraint: {@code T0 << T1} - * - *

Becomes: {@code A0 << T1} - * - * @param substitutions a mapping of target type parameter to the type argument to - * @return a new constraint that contains no use of the keys in substitutions - */ - public AFConstraint substitute(Map substitutions) { - AnnotatedTypeMirror newArgument = TypeArgInferenceUtil.substitute(substitutions, argument); - AnnotatedTypeMirror newFormalParameter = - TypeArgInferenceUtil.substitute(substitutions, formalParameter); - return construct(newArgument, newFormalParameter); - } + /** + * Given a partial solution to our type argument inference, replace any uses of type parameters + * that have been solved with their arguments. + * + *

That is: Let S be a partial solution to our inference (i.e. we have inferred type arguments + * for some types) Let S be a map {@code (T0 => A0, T1 => A1, ..., TN => AN)} where Ti is a type + * parameter and Ai is its solved argument. For all uses of Ti in this constraint, replace them + * with Ai. + * + *

For the mapping {@code (T0 => A0)}, the following constraint: {@code ArrayList << + * List} + * + *

Becomes: {@code ArrayList << List} + * + *

A constraint: {@code T0 << T1} + * + *

Becomes: {@code A0 << T1} + * + * @param substitutions a mapping of target type parameter to the type argument to + * @return a new constraint that contains no use of the keys in substitutions + */ + public AFConstraint substitute(Map substitutions) { + AnnotatedTypeMirror newArgument = TypeArgInferenceUtil.substitute(substitutions, argument); + AnnotatedTypeMirror newFormalParameter = + TypeArgInferenceUtil.substitute(substitutions, formalParameter); + return construct(newArgument, newFormalParameter); + } - /** Used to create a new constraint of the same subclass of AFConstraint. */ - protected abstract AFConstraint construct( - AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter); + /** Used to create a new constraint of the same subclass of AFConstraint. */ + protected abstract AFConstraint construct( + AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java index a29f8471aed..5871c884027 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java @@ -12,16 +12,16 @@ */ public interface AFReducer { - /** - * Determines if the input constraint should be handled by this reducer. If so: Reduces the - * constraint into one or more new constraints. Any new constraint that can still be reduced is - * placed in newConstraints. New irreducible constraints are placed in finish. Return true - * Return false (indicating that some other reducer needs to handle this constraint) If false is - * returned, the reducer should NOT place any constraints in newConstraints or finished - * - * @param constraint the constraint to reduce - * @param newConstraints the new constraints that may still need to be reduced - * @return true if the input constraint was handled by this reducer, false otherwise - */ - public boolean reduce(AFConstraint constraint, Set newConstraints); + /** + * Determines if the input constraint should be handled by this reducer. If so: Reduces the + * constraint into one or more new constraints. Any new constraint that can still be reduced is + * placed in newConstraints. New irreducible constraints are placed in finish. Return true Return + * false (indicating that some other reducer needs to handle this constraint) If false is + * returned, the reducer should NOT place any constraints in newConstraints or finished + * + * @param constraint the constraint to reduce + * @param newConstraints the new constraints that may still need to be reduced + * @return true if the input constraint was handled by this reducer, false otherwise + */ + public boolean reduce(AFConstraint constraint, Set newConstraints); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java index 6ec4820f2fc..895449f02d2 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java @@ -1,5 +1,8 @@ package org.checkerframework.framework.util.typeinference.constraint; +import java.util.List; +import java.util.Set; +import javax.lang.model.type.TypeKind; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -17,11 +20,6 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.StringsPlume; -import java.util.List; -import java.util.Set; - -import javax.lang.model.type.TypeKind; - /** * Takes a single step in reducing a AFConstraint. * @@ -43,560 +41,515 @@ * description of the case being covered. */ /*package-private*/ abstract class AFReducingVisitor - extends AbstractAtmComboVisitor> { - - protected final Class reducerType; - protected final AnnotatedTypeFactory atypeFactory; - - protected AFReducingVisitor( - Class reducerType, AnnotatedTypeFactory atypeFactory) { - this.reducerType = reducerType; - this.atypeFactory = atypeFactory; - } - - public abstract AFConstraint makeConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - - public abstract AFConstraint makeInverseConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - - public abstract AFConstraint makeEqualityConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - - public void addConstraint( - AnnotatedTypeMirror subtype, - AnnotatedTypeMirror supertype, - Set constraints) { - constraints.add(makeConstraint(subtype, supertype)); - } - - public void addInverseConstraint( - AnnotatedTypeMirror subtype, - AnnotatedTypeMirror supertype, - Set constraints) { - constraints.add(makeInverseConstraint(subtype, supertype)); - } - - public void addEqualityConstraint( - AnnotatedTypeMirror subtype, - AnnotatedTypeMirror supertype, - Set constraints) { - constraints.add(makeEqualityConstraint(subtype, supertype)); - } - - /** - * Called when we encounter an AF constraint on a type combination that we did not think is - * possible. This either implies that the type combination is possible, we accidentally created - * an invalid A2F or F2A Constraint, or we called the visit method on two AnnotatedTypeMirrors - * that do not appear together in a constraint. - */ - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror subtype, - AnnotatedTypeMirror supertype, - Set constraints) { - return super.defaultErrorMessage(subtype, supertype, constraints) - + System.lineSeparator() - + " constraints = " - + StringsPlume.join(", ", constraints); - } - - // ------------------------------------------------------------------------ - // Arrays as arguments - // From the JLS: - // If F = U[], where the type U involves Tj, then if A is an array type V[], or a type - // variable with an upper bound that is an array type V[], where V is a reference type, this - // algorithm is applied recursively to the constraint V << U or U << V (depending on the - // constraint type). - - @Override - public Void visitArray_Array( - AnnotatedArrayType subtype, - AnnotatedArrayType supertype, - Set constraints) { - addConstraint(subtype.getComponentType(), supertype.getComponentType(), constraints); - return null; - } - - @Override - public Void visitArray_Declared( - AnnotatedArrayType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - return null; - } - - @Override - public Void visitArray_Null( - AnnotatedArrayType subtype, - AnnotatedNullType supertype, - Set constraints) { - return null; - } - - @Override - public Void visitArray_Wildcard( - AnnotatedArrayType subtype, - AnnotatedWildcardType supertype, - Set constraints) { - visitWildcardAsSuperType(subtype, supertype, constraints); - return null; - } - - // Despite the above the comment at the beginning of the "array as arguments" section, a type - // variable cannot actually have an array type as its upper bound (e.g. is - // not allowed). - // So the only cases in which we visitArray_Typevar would be cases in which either: - // 1) Typevar is a type parameter for which we are inferring an argument, in which case the - // combination is already irreducible and we would not pass it to this class. - // 2) Typevar is an outer scope type variable, in which case it could NOT reference any of the - // type parameters for which we are inferring arguments and therefore will not lead to any - // meaningful AFConstraints. - // public void visitArray_Typevar - - // ------------------------------------------------------------------------ - // Declared as argument - - /** - * I believe there should be only 1 way to have a constraint of this form: {@code visit - * (Array, T [])} At this point, I don't think that's a valid argument for a formal - * parameter. If this occurs it is because of idiosyncrasies with the Checker Framework . We're - * going to skip this case for now. - */ - @Override - public Void visitDeclared_Array( - AnnotatedDeclaredType subtype, - AnnotatedArrayType supertype, - Set constraints) { - return null; - } - - // From the JLS Spec: - // If F has the form G<..., Yk-1,U, Yk+1, ...>, where U involves Tj - @Override - public Void visitDeclared_Declared( - AnnotatedDeclaredType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - if (subtype.isUnderlyingTypeRaw() || supertype.isUnderlyingTypeRaw()) { - // The error will be caught in {@link DefaultTypeArgumentInference#infer} and - // inference will be aborted, but type-checking will continue. - throw new BugInCF("Can't infer type arguments when raw types are involved."); - } - - if (!TypesUtils.isErasedSubtype( - subtype.getUnderlyingType(), - supertype.getUnderlyingType(), - atypeFactory.getChecker().getTypeUtils())) { - return null; - } - AnnotatedDeclaredType subAsSuper = - AnnotatedTypes.castedAsSuper(atypeFactory, subtype, supertype); - - List subTypeArgs = subAsSuper.getTypeArguments(); - List superTypeArgs = supertype.getTypeArguments(); - for (int i = 0; i < subTypeArgs.size(); i++) { - AnnotatedTypeMirror subTypeArg = subTypeArgs.get(i); - AnnotatedTypeMirror superTypeArg = superTypeArgs.get(i); - - // If F has the form G<..., Yk-1, ? extends U, Yk+1, ...>, where U involves Tj - // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj - // Since we always have both bounds in the checker framework we always compare both - if (superTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType superWc = (AnnotatedWildcardType) superTypeArg; - - if (subTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType subWc = (AnnotatedWildcardType) subTypeArg; - TypeArgInferenceUtil.checkForUninferredTypes(subWc); - addConstraint(subWc.getExtendsBound(), superWc.getExtendsBound(), constraints); - addInverseConstraint( - superWc.getSuperBound(), subWc.getSuperBound(), constraints); - } else { - addConstraint(subTypeArg, superWc.getExtendsBound(), constraints); - addInverseConstraint(superWc.getSuperBound(), subTypeArg, constraints); - } - - } else { - // If F has the form G<..., Yk-1, U, Yk+1, ...>, where U is a type expression that - // involves Tj. - addEqualityConstraint(subTypeArg, superTypeArg, constraints); - } - } - - return null; - } - - @Override - public Void visitDeclared_Intersection( - AnnotatedDeclaredType subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - - // Note: AnnotatedIntersectionTypes cannot have a type variable as one of the direct - // parameters but a type variable may be the type subtype to an intersection bound > - for (AnnotatedTypeMirror intersectionBound : supertype.getBounds()) { - if (intersectionBound instanceof AnnotatedDeclaredType - && !((AnnotatedDeclaredType) intersectionBound).getTypeArguments().isEmpty()) { - addConstraint(subtype, supertype, constraints); - } + extends AbstractAtmComboVisitor> { + + protected final Class reducerType; + protected final AnnotatedTypeFactory atypeFactory; + + protected AFReducingVisitor( + Class reducerType, AnnotatedTypeFactory atypeFactory) { + this.reducerType = reducerType; + this.atypeFactory = atypeFactory; + } + + public abstract AFConstraint makeConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); + + public abstract AFConstraint makeInverseConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); + + public abstract AFConstraint makeEqualityConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); + + public void addConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { + constraints.add(makeConstraint(subtype, supertype)); + } + + public void addInverseConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { + constraints.add(makeInverseConstraint(subtype, supertype)); + } + + public void addEqualityConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { + constraints.add(makeEqualityConstraint(subtype, supertype)); + } + + /** + * Called when we encounter an AF constraint on a type combination that we did not think is + * possible. This either implies that the type combination is possible, we accidentally created an + * invalid A2F or F2A Constraint, or we called the visit method on two AnnotatedTypeMirrors that + * do not appear together in a constraint. + */ + @Override + public String defaultErrorMessage( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { + return super.defaultErrorMessage(subtype, supertype, constraints) + + System.lineSeparator() + + " constraints = " + + StringsPlume.join(", ", constraints); + } + + // ------------------------------------------------------------------------ + // Arrays as arguments + // From the JLS: + // If F = U[], where the type U involves Tj, then if A is an array type V[], or a type + // variable with an upper bound that is an array type V[], where V is a reference type, this + // algorithm is applied recursively to the constraint V << U or U << V (depending on the + // constraint type). + + @Override + public Void visitArray_Array( + AnnotatedArrayType subtype, AnnotatedArrayType supertype, Set constraints) { + addConstraint(subtype.getComponentType(), supertype.getComponentType(), constraints); + return null; + } + + @Override + public Void visitArray_Declared( + AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Set constraints) { + return null; + } + + @Override + public Void visitArray_Null( + AnnotatedArrayType subtype, AnnotatedNullType supertype, Set constraints) { + return null; + } + + @Override + public Void visitArray_Wildcard( + AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Set constraints) { + visitWildcardAsSuperType(subtype, supertype, constraints); + return null; + } + + // Despite the above the comment at the beginning of the "array as arguments" section, a type + // variable cannot actually have an array type as its upper bound (e.g. is + // not allowed). + // So the only cases in which we visitArray_Typevar would be cases in which either: + // 1) Typevar is a type parameter for which we are inferring an argument, in which case the + // combination is already irreducible and we would not pass it to this class. + // 2) Typevar is an outer scope type variable, in which case it could NOT reference any of the + // type parameters for which we are inferring arguments and therefore will not lead to any + // meaningful AFConstraints. + // public void visitArray_Typevar + + // ------------------------------------------------------------------------ + // Declared as argument + + /** + * I believe there should be only 1 way to have a constraint of this form: {@code visit (Array, + * T [])} At this point, I don't think that's a valid argument for a formal parameter. If this + * occurs it is because of idiosyncrasies with the Checker Framework . We're going to skip this + * case for now. + */ + @Override + public Void visitDeclared_Array( + AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Set constraints) { + return null; + } + + // From the JLS Spec: + // If F has the form G<..., Yk-1,U, Yk+1, ...>, where U involves Tj + @Override + public Void visitDeclared_Declared( + AnnotatedDeclaredType subtype, + AnnotatedDeclaredType supertype, + Set constraints) { + if (subtype.isUnderlyingTypeRaw() || supertype.isUnderlyingTypeRaw()) { + // The error will be caught in {@link DefaultTypeArgumentInference#infer} and + // inference will be aborted, but type-checking will continue. + throw new BugInCF("Can't infer type arguments when raw types are involved."); + } + + if (!TypesUtils.isErasedSubtype( + subtype.getUnderlyingType(), + supertype.getUnderlyingType(), + atypeFactory.getChecker().getTypeUtils())) { + return null; + } + AnnotatedDeclaredType subAsSuper = + AnnotatedTypes.castedAsSuper(atypeFactory, subtype, supertype); + + List subTypeArgs = subAsSuper.getTypeArguments(); + List superTypeArgs = supertype.getTypeArguments(); + for (int i = 0; i < subTypeArgs.size(); i++) { + AnnotatedTypeMirror subTypeArg = subTypeArgs.get(i); + AnnotatedTypeMirror superTypeArg = superTypeArgs.get(i); + + // If F has the form G<..., Yk-1, ? extends U, Yk+1, ...>, where U involves Tj + // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj + // Since we always have both bounds in the checker framework we always compare both + if (superTypeArg.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType superWc = (AnnotatedWildcardType) superTypeArg; + + if (subTypeArg.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType subWc = (AnnotatedWildcardType) subTypeArg; + TypeArgInferenceUtil.checkForUninferredTypes(subWc); + addConstraint(subWc.getExtendsBound(), superWc.getExtendsBound(), constraints); + addInverseConstraint(superWc.getSuperBound(), subWc.getSuperBound(), constraints); + } else { + addConstraint(subTypeArg, superWc.getExtendsBound(), constraints); + addInverseConstraint(superWc.getSuperBound(), subTypeArg, constraints); } - return null; + } else { + // If F has the form G<..., Yk-1, U, Yk+1, ...>, where U is a type expression that + // involves Tj. + addEqualityConstraint(subTypeArg, superTypeArg, constraints); + } } - // Remember that NULL types can come from lower bounds - @Override - public Void visitDeclared_Null( - AnnotatedDeclaredType subtype, - AnnotatedNullType supertype, - Set constraints) { - return null; - } + return null; + } - // a primitive supertype provides us no information on the type of any type parameters for that - // method - @Override - public Void visitDeclared_Primitive( - AnnotatedDeclaredType subtype, - AnnotatedPrimitiveType supertype, - Set constraints) { - return null; - } + @Override + public Void visitDeclared_Intersection( + AnnotatedDeclaredType subtype, + AnnotatedIntersectionType supertype, + Set constraints) { - @Override - public Void visitDeclared_Typevar( - AnnotatedDeclaredType subtype, - AnnotatedTypeVariable supertype, - Set constraints) { - // Note: We expect the A2F constraints where F == a targeted type supertype to already be - // removed. Therefore, supertype should NOT be a target. + // Note: AnnotatedIntersectionTypes cannot have a type variable as one of the direct + // parameters but a type variable may be the type subtype to an intersection bound > + for (AnnotatedTypeMirror intersectionBound : supertype.getBounds()) { + if (intersectionBound instanceof AnnotatedDeclaredType + && !((AnnotatedDeclaredType) intersectionBound).getTypeArguments().isEmpty()) { addConstraint(subtype, supertype, constraints); - return null; - } - - @Override - public Void visitDeclared_Union( - AnnotatedDeclaredType subtype, - AnnotatedUnionType supertype, - Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } - - @Override - public Void visitDeclared_Wildcard( - AnnotatedDeclaredType subtype, - AnnotatedWildcardType supertype, - Set constraints) { - visitWildcardAsSuperType(subtype, supertype, constraints); - return null; - } - - // ------------------------------------------------------------------------ - // Intersection as subtype - @Override - public Void visitIntersection_Declared( - AnnotatedIntersectionType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - - // at least one of the intersection bound types must be convertible to the param type - AnnotatedDeclaredType subtypeAsParam = - AnnotatedTypes.castedAsSuper(atypeFactory, subtype, supertype); - if (subtypeAsParam != null && !subtypeAsParam.equals(supertype)) { - addConstraint(subtypeAsParam, supertype, constraints); - } - - return null; - } - - @Override - public Void visitIntersection_Intersection( - AnnotatedIntersectionType argument, - AnnotatedIntersectionType parameter, - Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } - - // provides no information as the AnnotatedNullType cannot refer to a type parameter - @Override - public Void visitIntersection_Null( - AnnotatedIntersectionType argument, - AnnotatedNullType parameter, - Set constraints) { - return null; - } - - // ------------------------------------------------------------------------ - // Null as argument - - /** - * NULL types only have primary annotations. A type parameter could only appear as a component - * of the parameter type and therefore has no relationship to these primary annotations - */ - @Override - public Void visitNull_Array( - AnnotatedNullType argument, - AnnotatedArrayType parameter, - Set constraints) { - return null; - } - - /** - * NULL types only have primary annotations. A type parameter could only appear as a component - * of the parameter type and therefore has no relationship to these primary annotations - */ - @Override - public Void visitNull_Declared( - AnnotatedNullType argument, - AnnotatedDeclaredType parameter, - Set constraints) { - return null; - } - - /** - * TODO: PERHAPS FOR ALL OF THESE WHERE WE COMPARE AGAINST THE LOWER BOUND, WE SHOULD INSTEAD - * COMPARE TODO: against the UPPER_BOUND with the LOWER_BOUND's PRIMARY ANNOTATIONS For captured - * types, the lower bound might be interesting so we compare against the lower bound but for - * most types the constraint added in this method is probably discarded in the next round of - * reduction (especially since we don't implement capture at the moment). - */ - @Override - public Void visitNull_Typevar( - AnnotatedNullType subtype, - AnnotatedTypeVariable supertype, - Set constraints) { - // Note: We would expect that parameter is not one of the targets or else it would already - // be removed. Therefore we compare NULL against its bound. - addConstraint(subtype, supertype.getLowerBound(), constraints); - return null; - } - - @Override - public Void visitNull_Wildcard( - AnnotatedNullType subtype, - AnnotatedWildcardType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(supertype); - // we don't use visitSupertype because Null types won't have interesting components - constraints.add(new A2F(subtype, supertype.getSuperBound())); - return null; - } - - @Override - public Void visitNull_Null( - AnnotatedNullType argument, - AnnotatedNullType parameter, - Set constraints) { - return null; - } - - @Override - public Void visitNull_Union( - AnnotatedNullType argument, - AnnotatedUnionType parameter, - Set constraints) { - return null; // TODO: UNIONS ARE NOT YET SUPPORTED - } - - // Despite the fact that intersections are not yet supported, this is the right implementation. - // NULL types only have primary annotations. Since type parameters cannot be a member of the - // intersection's bounds (though they can be component types), we do not need to do anything - // further. - @Override - public Void visitNull_Intersection( - AnnotatedNullType argument, - AnnotatedIntersectionType parameter, - Set constraints) { - return null; - } - - // Primitive parameter types tell us nothing about the type parameters - @Override - public Void visitNull_Primitive( - AnnotatedNullType argument, - AnnotatedPrimitiveType parameter, - Set constraints) { - return null; - } - - // ------------------------------------------------------------------------ - // Primitive as argument - - @Override - public Void visitPrimitive_Declared( - AnnotatedPrimitiveType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - // we may be able to eliminate this case, since I believe the corresponding constraint will - // just be discarded as the parameter must be a boxed primitive - addConstraint(atypeFactory.getBoxedType(subtype), supertype, constraints); - return null; - } - - // Primitive parameter types tell us nothing about the type parameters - @Override - public Void visitPrimitive_Primitive( - AnnotatedPrimitiveType subtype, - AnnotatedPrimitiveType supertype, - Set constraints) { - return null; - } - - @Override - public Void visitPrimitive_Intersection( - AnnotatedPrimitiveType subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - addConstraint(atypeFactory.getBoxedType(subtype), supertype, constraints); - return null; - } - - // ------------------------------------------------------------------------ - // Union as argument - @Override - public Void visitUnion_Declared( - AnnotatedUnionType argument, - AnnotatedDeclaredType parameter, - Set constraints) { - return null; // TODO: UNIONS ARE NOT CURRENTLY SUPPORTED - } - - // ------------------------------------------------------------------------ - // typevar as argument - // If we've reached this point, the typevar is NOT one of the types we are inferring. - - @Override - public Void visitTypevar_Declared( - AnnotatedTypeVariable subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - addConstraint(subtype.getUpperBound(), supertype, constraints); - return null; - } - - @Override - public Void visitTypevar_Typevar( - AnnotatedTypeVariable subtype, - AnnotatedTypeVariable supertype, - Set constraints) { - // if we've reached this point and the two are corresponding type variables, then they are - // NOT ones that may have a type variable we are inferring types for and therefore we can - // discard this constraint - if (!AnnotatedTypes.areCorrespondingTypeVariables( - atypeFactory.getElementUtils(), subtype, supertype)) { - addConstraint(subtype.getUpperBound(), supertype.getLowerBound(), constraints); - } - - return null; - } - - @Override - public Void visitTypevar_Null( - AnnotatedTypeVariable subtype, - AnnotatedNullType supertype, - Set constraints) { - addConstraint(subtype.getUpperBound(), supertype, constraints); - return null; - } - - @Override - public Void visitTypevar_Wildcard( - AnnotatedTypeVariable subtype, - AnnotatedWildcardType supertype, - Set constraints) { - visitWildcardAsSuperType(subtype, supertype, constraints); - return null; - } - - @Override - public Void visitTypevar_Intersection( - AnnotatedTypeVariable subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - addConstraint(subtype.getUpperBound(), supertype, constraints); - return null; - } - - // ------------------------------------------------------------------------ - // wildcard as subtype - @Override - public Void visitWildcard_Array( - AnnotatedWildcardType subtype, - AnnotatedArrayType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Declared( - AnnotatedWildcardType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Intersection( - AnnotatedWildcardType subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Primitive( - AnnotatedWildcardType subtype, - AnnotatedPrimitiveType supertype, - Set constraints) { - return null; - } - - @Override - public Void visitWildcard_Typevar( - AnnotatedWildcardType subtype, - AnnotatedTypeVariable supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Wildcard( - AnnotatedWildcardType subtype, - AnnotatedWildcardType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - // since wildcards are handled in visitDeclared_Declared this could only occur if two - // wildcards were passed to type subtype inference at the top level. This can only occur - // because we do not implement capture conversion. - visitWildcardAsSuperType(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - // should the same logic apply to typevars? - public void visitWildcardAsSuperType( - AnnotatedTypeMirror subtype, - AnnotatedWildcardType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(supertype); - // this case occur only when supertype should actually be capture converted (which we don't - // do) because all other wildcard cases would be handled via Declared_Declared - addConstraint(subtype, supertype.getSuperBound(), constraints); - - // if type1 is below the superbound then it is necessarily below the extends bound - // BUT the extends bound may have interesting component types (like the array component) - // to which we also want to apply constraints - // e.g. visitArray_Wildcard(@I String[], ? extends @A String[]) - // if @I is an annotation we are trying to infer then we still want to infer that @I <: @A - // in fact - addInverseConstraint(subtype, supertype.getExtendsBound(), constraints); - } + } + } + + return null; + } + + // Remember that NULL types can come from lower bounds + @Override + public Void visitDeclared_Null( + AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Set constraints) { + return null; + } + + // a primitive supertype provides us no information on the type of any type parameters for that + // method + @Override + public Void visitDeclared_Primitive( + AnnotatedDeclaredType subtype, + AnnotatedPrimitiveType supertype, + Set constraints) { + return null; + } + + @Override + public Void visitDeclared_Typevar( + AnnotatedDeclaredType subtype, + AnnotatedTypeVariable supertype, + Set constraints) { + // Note: We expect the A2F constraints where F == a targeted type supertype to already be + // removed. Therefore, supertype should NOT be a target. + addConstraint(subtype, supertype, constraints); + return null; + } + + @Override + public Void visitDeclared_Union( + AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Set constraints) { + return null; // TODO: NOT SUPPORTED AT THE MOMENT + } + + @Override + public Void visitDeclared_Wildcard( + AnnotatedDeclaredType subtype, + AnnotatedWildcardType supertype, + Set constraints) { + visitWildcardAsSuperType(subtype, supertype, constraints); + return null; + } + + // ------------------------------------------------------------------------ + // Intersection as subtype + @Override + public Void visitIntersection_Declared( + AnnotatedIntersectionType subtype, + AnnotatedDeclaredType supertype, + Set constraints) { + + // at least one of the intersection bound types must be convertible to the param type + AnnotatedDeclaredType subtypeAsParam = + AnnotatedTypes.castedAsSuper(atypeFactory, subtype, supertype); + if (subtypeAsParam != null && !subtypeAsParam.equals(supertype)) { + addConstraint(subtypeAsParam, supertype, constraints); + } + + return null; + } + + @Override + public Void visitIntersection_Intersection( + AnnotatedIntersectionType argument, + AnnotatedIntersectionType parameter, + Set constraints) { + return null; // TODO: NOT SUPPORTED AT THE MOMENT + } + + // provides no information as the AnnotatedNullType cannot refer to a type parameter + @Override + public Void visitIntersection_Null( + AnnotatedIntersectionType argument, + AnnotatedNullType parameter, + Set constraints) { + return null; + } + + // ------------------------------------------------------------------------ + // Null as argument + + /** + * NULL types only have primary annotations. A type parameter could only appear as a component of + * the parameter type and therefore has no relationship to these primary annotations + */ + @Override + public Void visitNull_Array( + AnnotatedNullType argument, AnnotatedArrayType parameter, Set constraints) { + return null; + } + + /** + * NULL types only have primary annotations. A type parameter could only appear as a component of + * the parameter type and therefore has no relationship to these primary annotations + */ + @Override + public Void visitNull_Declared( + AnnotatedNullType argument, AnnotatedDeclaredType parameter, Set constraints) { + return null; + } + + /** + * TODO: PERHAPS FOR ALL OF THESE WHERE WE COMPARE AGAINST THE LOWER BOUND, WE SHOULD INSTEAD + * COMPARE TODO: against the UPPER_BOUND with the LOWER_BOUND's PRIMARY ANNOTATIONS For captured + * types, the lower bound might be interesting so we compare against the lower bound but for most + * types the constraint added in this method is probably discarded in the next round of reduction + * (especially since we don't implement capture at the moment). + */ + @Override + public Void visitNull_Typevar( + AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Set constraints) { + // Note: We would expect that parameter is not one of the targets or else it would already + // be removed. Therefore we compare NULL against its bound. + addConstraint(subtype, supertype.getLowerBound(), constraints); + return null; + } + + @Override + public Void visitNull_Wildcard( + AnnotatedNullType subtype, AnnotatedWildcardType supertype, Set constraints) { + TypeArgInferenceUtil.checkForUninferredTypes(supertype); + // we don't use visitSupertype because Null types won't have interesting components + constraints.add(new A2F(subtype, supertype.getSuperBound())); + return null; + } + + @Override + public Void visitNull_Null( + AnnotatedNullType argument, AnnotatedNullType parameter, Set constraints) { + return null; + } + + @Override + public Void visitNull_Union( + AnnotatedNullType argument, AnnotatedUnionType parameter, Set constraints) { + return null; // TODO: UNIONS ARE NOT YET SUPPORTED + } + + // Despite the fact that intersections are not yet supported, this is the right implementation. + // NULL types only have primary annotations. Since type parameters cannot be a member of the + // intersection's bounds (though they can be component types), we do not need to do anything + // further. + @Override + public Void visitNull_Intersection( + AnnotatedNullType argument, + AnnotatedIntersectionType parameter, + Set constraints) { + return null; + } + + // Primitive parameter types tell us nothing about the type parameters + @Override + public Void visitNull_Primitive( + AnnotatedNullType argument, AnnotatedPrimitiveType parameter, Set constraints) { + return null; + } + + // ------------------------------------------------------------------------ + // Primitive as argument + + @Override + public Void visitPrimitive_Declared( + AnnotatedPrimitiveType subtype, + AnnotatedDeclaredType supertype, + Set constraints) { + // we may be able to eliminate this case, since I believe the corresponding constraint will + // just be discarded as the parameter must be a boxed primitive + addConstraint(atypeFactory.getBoxedType(subtype), supertype, constraints); + return null; + } + + // Primitive parameter types tell us nothing about the type parameters + @Override + public Void visitPrimitive_Primitive( + AnnotatedPrimitiveType subtype, + AnnotatedPrimitiveType supertype, + Set constraints) { + return null; + } + + @Override + public Void visitPrimitive_Intersection( + AnnotatedPrimitiveType subtype, + AnnotatedIntersectionType supertype, + Set constraints) { + addConstraint(atypeFactory.getBoxedType(subtype), supertype, constraints); + return null; + } + + // ------------------------------------------------------------------------ + // Union as argument + @Override + public Void visitUnion_Declared( + AnnotatedUnionType argument, AnnotatedDeclaredType parameter, Set constraints) { + return null; // TODO: UNIONS ARE NOT CURRENTLY SUPPORTED + } + + // ------------------------------------------------------------------------ + // typevar as argument + // If we've reached this point, the typevar is NOT one of the types we are inferring. + + @Override + public Void visitTypevar_Declared( + AnnotatedTypeVariable subtype, + AnnotatedDeclaredType supertype, + Set constraints) { + addConstraint(subtype.getUpperBound(), supertype, constraints); + return null; + } + + @Override + public Void visitTypevar_Typevar( + AnnotatedTypeVariable subtype, + AnnotatedTypeVariable supertype, + Set constraints) { + // if we've reached this point and the two are corresponding type variables, then they are + // NOT ones that may have a type variable we are inferring types for and therefore we can + // discard this constraint + if (!AnnotatedTypes.areCorrespondingTypeVariables( + atypeFactory.getElementUtils(), subtype, supertype)) { + addConstraint(subtype.getUpperBound(), supertype.getLowerBound(), constraints); + } + + return null; + } + + @Override + public Void visitTypevar_Null( + AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Set constraints) { + addConstraint(subtype.getUpperBound(), supertype, constraints); + return null; + } + + @Override + public Void visitTypevar_Wildcard( + AnnotatedTypeVariable subtype, + AnnotatedWildcardType supertype, + Set constraints) { + visitWildcardAsSuperType(subtype, supertype, constraints); + return null; + } + + @Override + public Void visitTypevar_Intersection( + AnnotatedTypeVariable subtype, + AnnotatedIntersectionType supertype, + Set constraints) { + addConstraint(subtype.getUpperBound(), supertype, constraints); + return null; + } + + // ------------------------------------------------------------------------ + // wildcard as subtype + @Override + public Void visitWildcard_Array( + AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Set constraints) { + TypeArgInferenceUtil.checkForUninferredTypes(subtype); + addConstraint(subtype.getExtendsBound(), supertype, constraints); + return null; + } + + @Override + public Void visitWildcard_Declared( + AnnotatedWildcardType subtype, + AnnotatedDeclaredType supertype, + Set constraints) { + TypeArgInferenceUtil.checkForUninferredTypes(subtype); + addConstraint(subtype.getExtendsBound(), supertype, constraints); + return null; + } + + @Override + public Void visitWildcard_Intersection( + AnnotatedWildcardType subtype, + AnnotatedIntersectionType supertype, + Set constraints) { + TypeArgInferenceUtil.checkForUninferredTypes(subtype); + addConstraint(subtype.getExtendsBound(), supertype, constraints); + return null; + } + + @Override + public Void visitWildcard_Primitive( + AnnotatedWildcardType subtype, + AnnotatedPrimitiveType supertype, + Set constraints) { + return null; + } + + @Override + public Void visitWildcard_Typevar( + AnnotatedWildcardType subtype, + AnnotatedTypeVariable supertype, + Set constraints) { + TypeArgInferenceUtil.checkForUninferredTypes(subtype); + addConstraint(subtype.getExtendsBound(), supertype, constraints); + return null; + } + + @Override + public Void visitWildcard_Wildcard( + AnnotatedWildcardType subtype, + AnnotatedWildcardType supertype, + Set constraints) { + TypeArgInferenceUtil.checkForUninferredTypes(subtype); + // since wildcards are handled in visitDeclared_Declared this could only occur if two + // wildcards were passed to type subtype inference at the top level. This can only occur + // because we do not implement capture conversion. + visitWildcardAsSuperType(subtype.getExtendsBound(), supertype, constraints); + return null; + } + + // should the same logic apply to typevars? + public void visitWildcardAsSuperType( + AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype, Set constraints) { + TypeArgInferenceUtil.checkForUninferredTypes(supertype); + // this case occur only when supertype should actually be capture converted (which we don't + // do) because all other wildcard cases would be handled via Declared_Declared + addConstraint(subtype, supertype.getSuperBound(), constraints); + + // if type1 is below the superbound then it is necessarily below the extends bound + // BUT the extends bound may have interesting component types (like the array component) + // to which we also want to apply constraints + // e.g. visitArray_Wildcard(@I String[], ? extends @A String[]) + // if @I is an annotation we are trying to infer then we still want to infer that @I <: @A + // in fact + addInverseConstraint(subtype, supertype.getExtendsBound(), constraints); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java index a5f1a6989fe..74ce873229c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java @@ -10,24 +10,23 @@ */ public class F2A extends AFConstraint { - /** Create a constraint with an argument greater than a formal. */ - public F2A(AnnotatedTypeMirror formalParameter, AnnotatedTypeMirror argument) { - super(argument, formalParameter); - } + /** Create a constraint with an argument greater than a formal. */ + public F2A(AnnotatedTypeMirror formalParameter, AnnotatedTypeMirror argument) { + super(argument, formalParameter); + } - @Override - public TUConstraint toTUConstraint() { - return new TSubU((AnnotatedTypeVariable) formalParameter, argument, true); - } + @Override + public TUConstraint toTUConstraint() { + return new TSubU((AnnotatedTypeVariable) formalParameter, argument, true); + } - @Override - protected F2A construct( - AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { - return new F2A(newFormalParameter, newArgument); - } + @Override + protected F2A construct(AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { + return new F2A(newFormalParameter, newArgument); + } - @Override - public String toString() { - return "F2A( " + formalParameter + " << " + argument + " )"; - } + @Override + public String toString() { + return "F2A( " + formalParameter + " << " + argument + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java index e213c52cce6..61882df9610 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.util.typeinference.constraint; +import java.util.Set; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; -import java.util.Set; - /** * F2AReducer takes an F2A constraint that is not irreducible (@see AFConstraint.isIrreducible) and * reduces it by one step. The resulting constraint may still be reducible. @@ -14,69 +13,68 @@ */ public class F2AReducer implements AFReducer { - protected final F2AReducingVisitor visitor; + protected final F2AReducingVisitor visitor; - public F2AReducer(AnnotatedTypeFactory typeFactory) { - this.visitor = new F2AReducingVisitor(typeFactory); - } + public F2AReducer(AnnotatedTypeFactory typeFactory) { + this.visitor = new F2AReducingVisitor(typeFactory); + } - @Override - public boolean reduce(AFConstraint constraint, Set newConstraints) { - if (constraint instanceof F2A) { - F2A f2A = (F2A) constraint; - visitor.visit(f2A.formalParameter, f2A.argument, newConstraints); - return true; + @Override + public boolean reduce(AFConstraint constraint, Set newConstraints) { + if (constraint instanceof F2A) { + F2A f2A = (F2A) constraint; + visitor.visit(f2A.formalParameter, f2A.argument, newConstraints); + return true; - } else { - return false; - } + } else { + return false; } + } - /** - * Given an F2A constraint of the form: F2A( typeFromFormalParameter, typeFromMethodArgument ) - * - *

F2AReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter, - * typeFromMethodArgument, newConstraints ) - * - *

The visit method will determine if the given constraint should either: - * - *

    - *
  • be discarded -- in this case, the visitor just returns - *
  • reduced to a simpler constraint or set of constraints -- in this case, the new - * constraint or set of constraints is added to newConstraints - *
- * - * Sprinkled throughout this class are comments of the form: - * - *
{@code
-     * // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
-     * }
- * - * These are excerpts from the JLS, if you search for them you will find the corresponding JLS - * description of the case being covered. - */ - private static class F2AReducingVisitor extends AFReducingVisitor { + /** + * Given an F2A constraint of the form: F2A( typeFromFormalParameter, typeFromMethodArgument ) + * + *

F2AReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter, + * typeFromMethodArgument, newConstraints ) + * + *

The visit method will determine if the given constraint should either: + * + *

    + *
  • be discarded -- in this case, the visitor just returns + *
  • reduced to a simpler constraint or set of constraints -- in this case, the new constraint + * or set of constraints is added to newConstraints + *
+ * + * Sprinkled throughout this class are comments of the form: + * + *
{@code
+   * // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
+   * }
+ * + * These are excerpts from the JLS, if you search for them you will find the corresponding JLS + * description of the case being covered. + */ + private static class F2AReducingVisitor extends AFReducingVisitor { - public F2AReducingVisitor(AnnotatedTypeFactory typeFactory) { - super(F2A.class, typeFactory); - } + public F2AReducingVisitor(AnnotatedTypeFactory typeFactory) { + super(F2A.class, typeFactory); + } - @Override - public AFConstraint makeConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new F2A(subtype, supertype); - } + @Override + public AFConstraint makeConstraint(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + return new F2A(subtype, supertype); + } - @Override - public AFConstraint makeInverseConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new A2F(subtype, supertype); - } + @Override + public AFConstraint makeInverseConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + return new A2F(subtype, supertype); + } - @Override - public AFConstraint makeEqualityConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new FIsA(subtype, supertype); - } + @Override + public AFConstraint makeEqualityConstraint( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + return new FIsA(subtype, supertype); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java index 835f4cc83fe..dbfec18ffe0 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java @@ -10,24 +10,24 @@ */ public class FIsA extends AFConstraint { - /** Create a constraint with an argument equal to a formal. */ - public FIsA(AnnotatedTypeMirror parameter, AnnotatedTypeMirror argument) { - super(argument, parameter); - } + /** Create a constraint with an argument equal to a formal. */ + public FIsA(AnnotatedTypeMirror parameter, AnnotatedTypeMirror argument) { + super(argument, parameter); + } - @Override - public TUConstraint toTUConstraint() { - return new TIsU((AnnotatedTypeVariable) formalParameter, argument, true); - } + @Override + public TUConstraint toTUConstraint() { + return new TIsU((AnnotatedTypeVariable) formalParameter, argument, true); + } - @Override - protected FIsA construct( - AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { - return new FIsA(newFormalParameter, newArgument); - } + @Override + protected FIsA construct( + AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { + return new FIsA(newFormalParameter, newArgument); + } - @Override - public String toString() { - return "FisA( " + formalParameter + " = " + argument + " )"; - } + @Override + public String toString() { + return "FisA( " + formalParameter + " = " + argument + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java index b012a9f921c..571330ca1fd 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java @@ -1,5 +1,8 @@ package org.checkerframework.framework.util.typeinference.constraint; +import java.util.List; +import java.util.Set; +import javax.lang.model.type.TypeKind; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -14,11 +17,6 @@ import org.checkerframework.framework.util.AnnotatedTypes; import org.plumelib.util.StringsPlume; -import java.util.List; -import java.util.Set; - -import javax.lang.model.type.TypeKind; - /** * FIsAReducer takes an FIsA constraint that is not irreducible (@see AFConstraint.isIrreducible) * and reduces it by one step. The resulting constraint may still be reducible. @@ -28,238 +26,231 @@ */ public class FIsAReducer implements AFReducer { - protected final FIsAReducingVisitor visitor; - private final AnnotatedTypeFactory atypeFactory; + protected final FIsAReducingVisitor visitor; + private final AnnotatedTypeFactory atypeFactory; - public FIsAReducer(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.visitor = new FIsAReducingVisitor(); - } + public FIsAReducer(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.visitor = new FIsAReducingVisitor(); + } - @Override - public boolean reduce(AFConstraint constraint, Set newConstraints) { - if (constraint instanceof FIsA) { - FIsA fIsA = (FIsA) constraint; - visitor.visit(fIsA.formalParameter, fIsA.argument, newConstraints); - return true; + @Override + public boolean reduce(AFConstraint constraint, Set newConstraints) { + if (constraint instanceof FIsA) { + FIsA fIsA = (FIsA) constraint; + visitor.visit(fIsA.formalParameter, fIsA.argument, newConstraints); + return true; - } else { - return false; - } + } else { + return false; + } + } + + /** + * Given an FIsA constraint of the form: FIsA( typeFromFormalParameter, typeFromMethodArgument ) + * + *

FIsAReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter, + * typeFromMethodArgument, newConstraints ) + * + *

The visit method will determine if the given constraint should either: + * + *

    + *
  • be discarded -- in this case, the visitor just returns + *
  • reduced to a simpler constraint or set of constraints -- in this case, the new constraint + * or set of constraints is added to newConstraints + *
+ * + * From the JLS, in general there are 2 rules that govern F = A constraints: If F = Tj, then the + * constraint Tj = A is implied. If F = U[], where the type U involves Tj, then if A is an array + * type V[], or a type variable with an upper bound that is an array type V[], where V is a + * reference type, this algorithm is applied recursively to the constraint {@code V >> U}. + * Otherwise, no constraint is implied on Tj. + * + *

Since both F and A may have component types this visitor delves into their components and + * applies these rules to the components. However, only one step is taken at a time (i.e. this is + * not a scanner) + */ + private class FIsAReducingVisitor extends AbstractAtmComboVisitor> { + @Override + public String defaultErrorMessage( + AnnotatedTypeMirror argument, + AnnotatedTypeMirror parameter, + Set afConstraints) { + return super.defaultErrorMessage(argument, parameter, afConstraints) + + System.lineSeparator() + + " constraints = [" + + StringsPlume.join(", ", afConstraints) + + "]"; } - /** - * Given an FIsA constraint of the form: FIsA( typeFromFormalParameter, typeFromMethodArgument ) - * - *

FIsAReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter, - * typeFromMethodArgument, newConstraints ) - * - *

The visit method will determine if the given constraint should either: - * - *

    - *
  • be discarded -- in this case, the visitor just returns - *
  • reduced to a simpler constraint or set of constraints -- in this case, the new - * constraint or set of constraints is added to newConstraints - *
- * - * From the JLS, in general there are 2 rules that govern F = A constraints: If F = Tj, then the - * constraint Tj = A is implied. If F = U[], where the type U involves Tj, then if A is an array - * type V[], or a type variable with an upper bound that is an array type V[], where V is a - * reference type, this algorithm is applied recursively to the constraint {@code V >> U}. - * Otherwise, no constraint is implied on Tj. - * - *

Since both F and A may have component types this visitor delves into their components and - * applies these rules to the components. However, only one step is taken at a time (i.e. this - * is not a scanner) - */ - private class FIsAReducingVisitor extends AbstractAtmComboVisitor> { - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror argument, - AnnotatedTypeMirror parameter, - Set afConstraints) { - return super.defaultErrorMessage(argument, parameter, afConstraints) - + System.lineSeparator() - + " constraints = [" - + StringsPlume.join(", ", afConstraints) - + "]"; - } - - // ------------------------------------------------------------------------ - // Arrays as arguments - - @Override - public Void visitArray_Array( - AnnotatedArrayType parameter, - AnnotatedArrayType argument, - Set constraints) { - constraints.add(new FIsA(parameter.getComponentType(), argument.getComponentType())); - return null; - } - - @Override - public Void visitArray_Declared( - AnnotatedArrayType parameter, - AnnotatedDeclaredType argument, - Set constraints) { - return null; - } - - @Override - public Void visitArray_Null( - AnnotatedArrayType parameter, - AnnotatedNullType argument, - Set constraints) { - return null; - } - - @Override - public Void visitArray_Wildcard( - AnnotatedArrayType parameter, - AnnotatedWildcardType argument, - Set constraints) { - constraints.add(new FIsA(parameter, argument.getExtendsBound())); - return null; - } - - // ------------------------------------------------------------------------ - // Declared as argument - @Override - public Void visitDeclared_Array( - AnnotatedDeclaredType parameter, - AnnotatedArrayType argument, - Set constraints) { - // should this be Array - T[] the new A2F(String, T) - return null; - } + // ------------------------------------------------------------------------ + // Arrays as arguments - @Override - public Void visitDeclared_Declared( - AnnotatedDeclaredType parameter, - AnnotatedDeclaredType argument, - Set constraints) { - if (argument.isUnderlyingTypeRaw() || parameter.isUnderlyingTypeRaw()) { - return null; - } + @Override + public Void visitArray_Array( + AnnotatedArrayType parameter, AnnotatedArrayType argument, Set constraints) { + constraints.add(new FIsA(parameter.getComponentType(), argument.getComponentType())); + return null; + } - AnnotatedDeclaredType argumentAsParam = - AnnotatedTypes.castedAsSuper(atypeFactory, argument, parameter); - if (argumentAsParam == null) { - return null; - } + @Override + public Void visitArray_Declared( + AnnotatedArrayType parameter, + AnnotatedDeclaredType argument, + Set constraints) { + return null; + } - List argTypeArgs = argumentAsParam.getTypeArguments(); - List paramTypeArgs = parameter.getTypeArguments(); - for (int i = 0; i < argTypeArgs.size(); i++) { - AnnotatedTypeMirror argTypeArg = argTypeArgs.get(i); - AnnotatedTypeMirror paramTypeArg = paramTypeArgs.get(i); + @Override + public Void visitArray_Null( + AnnotatedArrayType parameter, AnnotatedNullType argument, Set constraints) { + return null; + } - if (paramTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType paramWc = (AnnotatedWildcardType) paramTypeArg; + @Override + public Void visitArray_Wildcard( + AnnotatedArrayType parameter, + AnnotatedWildcardType argument, + Set constraints) { + constraints.add(new FIsA(parameter, argument.getExtendsBound())); + return null; + } - if (argTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType argWc = (AnnotatedWildcardType) argTypeArg; - constraints.add( - new FIsA(paramWc.getExtendsBound(), argWc.getExtendsBound())); - constraints.add(new FIsA(paramWc.getSuperBound(), argWc.getSuperBound())); - } + // ------------------------------------------------------------------------ + // Declared as argument + @Override + public Void visitDeclared_Array( + AnnotatedDeclaredType parameter, + AnnotatedArrayType argument, + Set constraints) { + // should this be Array - T[] the new A2F(String, T) + return null; + } - } else { - constraints.add(new FIsA(paramTypeArgs.get(i), argTypeArgs.get(i))); - } - } + @Override + public Void visitDeclared_Declared( + AnnotatedDeclaredType parameter, + AnnotatedDeclaredType argument, + Set constraints) { + if (argument.isUnderlyingTypeRaw() || parameter.isUnderlyingTypeRaw()) { + return null; + } + + AnnotatedDeclaredType argumentAsParam = + AnnotatedTypes.castedAsSuper(atypeFactory, argument, parameter); + if (argumentAsParam == null) { + return null; + } + + List argTypeArgs = argumentAsParam.getTypeArguments(); + List paramTypeArgs = parameter.getTypeArguments(); + for (int i = 0; i < argTypeArgs.size(); i++) { + AnnotatedTypeMirror argTypeArg = argTypeArgs.get(i); + AnnotatedTypeMirror paramTypeArg = paramTypeArgs.get(i); + + if (paramTypeArg.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType paramWc = (AnnotatedWildcardType) paramTypeArg; + + if (argTypeArg.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType argWc = (AnnotatedWildcardType) argTypeArg; + constraints.add(new FIsA(paramWc.getExtendsBound(), argWc.getExtendsBound())); + constraints.add(new FIsA(paramWc.getSuperBound(), argWc.getSuperBound())); + } - return null; + } else { + constraints.add(new FIsA(paramTypeArgs.get(i), argTypeArgs.get(i))); } + } - @Override - public Void visitDeclared_Null( - AnnotatedDeclaredType parameter, - AnnotatedNullType argument, - Set constraints) { - return null; - } + return null; + } - @Override - public Void visitDeclared_Primitive( - AnnotatedDeclaredType parameter, - AnnotatedPrimitiveType argument, - Set constraints) { - return null; - } + @Override + public Void visitDeclared_Null( + AnnotatedDeclaredType parameter, + AnnotatedNullType argument, + Set constraints) { + return null; + } - @Override - public Void visitDeclared_Union( - AnnotatedDeclaredType parameter, - AnnotatedUnionType argument, - Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } + @Override + public Void visitDeclared_Primitive( + AnnotatedDeclaredType parameter, + AnnotatedPrimitiveType argument, + Set constraints) { + return null; + } - @Override - public Void visitIntersection_Intersection( - AnnotatedIntersectionType parameter, - AnnotatedIntersectionType argument, - Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } + @Override + public Void visitDeclared_Union( + AnnotatedDeclaredType parameter, + AnnotatedUnionType argument, + Set constraints) { + return null; // TODO: NOT SUPPORTED AT THE MOMENT + } - @Override - public Void visitIntersection_Null( - AnnotatedIntersectionType parameter, - AnnotatedNullType argument, - Set constraints) { - return null; - } + @Override + public Void visitIntersection_Intersection( + AnnotatedIntersectionType parameter, + AnnotatedIntersectionType argument, + Set constraints) { + return null; // TODO: NOT SUPPORTED AT THE MOMENT + } - @Override - public Void visitNull_Null( - AnnotatedNullType parameter, - AnnotatedNullType argument, - Set afConstraints) { - // we sometimes get these when we have captured type variables passed as arguments - // regardless they don't give any information - return null; - } + @Override + public Void visitIntersection_Null( + AnnotatedIntersectionType parameter, + AnnotatedNullType argument, + Set constraints) { + return null; + } - // ------------------------------------------------------------------------ - // Primitive as argument - @Override - public Void visitPrimitive_Declared( - AnnotatedPrimitiveType parameter, - AnnotatedDeclaredType argument, - Set constraints) { - // we may be able to eliminate this case, since I believe the corresponding constraint - // will just be discarded as the parameter must be a boxed primitive - constraints.add(new FIsA(atypeFactory.getBoxedType(parameter), argument)); - return null; - } + @Override + public Void visitNull_Null( + AnnotatedNullType parameter, AnnotatedNullType argument, Set afConstraints) { + // we sometimes get these when we have captured type variables passed as arguments + // regardless they don't give any information + return null; + } - @Override - public Void visitPrimitive_Primitive( - AnnotatedPrimitiveType parameter, - AnnotatedPrimitiveType argument, - Set constraints) { - return null; - } + // ------------------------------------------------------------------------ + // Primitive as argument + @Override + public Void visitPrimitive_Declared( + AnnotatedPrimitiveType parameter, + AnnotatedDeclaredType argument, + Set constraints) { + // we may be able to eliminate this case, since I believe the corresponding constraint + // will just be discarded as the parameter must be a boxed primitive + constraints.add(new FIsA(atypeFactory.getBoxedType(parameter), argument)); + return null; + } - @Override - public Void visitTypevar_Typevar( - AnnotatedTypeVariable parameter, - AnnotatedTypeVariable argument, - Set constraints) { - // if we've reached this point and the two are corresponding type variables, then they - // are NOT ones that may have a type variable we are inferring types for and therefore - // we can discard this constraint - return null; - } + @Override + public Void visitPrimitive_Primitive( + AnnotatedPrimitiveType parameter, + AnnotatedPrimitiveType argument, + Set constraints) { + return null; + } - @Override - public Void visitTypevar_Null( - AnnotatedTypeVariable argument, - AnnotatedNullType parameter, - Set constraints) { - return null; - } + @Override + public Void visitTypevar_Typevar( + AnnotatedTypeVariable parameter, + AnnotatedTypeVariable argument, + Set constraints) { + // if we've reached this point and the two are corresponding type variables, then they + // are NOT ones that may have a type variable we are inferring types for and therefore + // we can discard this constraint + return null; + } + + @Override + public Void visitTypevar_Null( + AnnotatedTypeVariable argument, + AnnotatedNullType parameter, + Set constraints) { + return null; } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java index 746c4ae0b56..9343baf8374 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java @@ -9,18 +9,17 @@ * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint */ public class TIsU extends TUConstraint { - public TIsU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { - this(typeVariable, relatedType, false); - } + public TIsU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { + this(typeVariable, relatedType, false); + } - /** Create a constraint with a variable equal to a type. */ - public TIsU( - AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - super(typeVariable, relatedType, uIsArg); - } + /** Create a constraint with a variable equal to a type. */ + public TIsU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { + super(typeVariable, relatedType, uIsArg); + } - @Override - public String toString() { - return "TIsU( " + typeVariable + ", " + relatedType + " )"; - } + @Override + public String toString() { + return "TIsU( " + typeVariable + ", " + relatedType + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java index 9c241c5c0dd..9e5bad537ac 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java @@ -9,18 +9,18 @@ * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint */ public class TSubU extends TUConstraint { - public TSubU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { - this(typeVariable, relatedType, false); - } + public TSubU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { + this(typeVariable, relatedType, false); + } - /** Create a constraint with a variable less than a type. */ - public TSubU( - AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - super(typeVariable, relatedType, uIsArg); - } + /** Create a constraint with a variable less than a type. */ + public TSubU( + AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { + super(typeVariable, relatedType, uIsArg); + } - @Override - public String toString() { - return "TSubU( " + typeVariable + " <: " + relatedType + " )"; - } + @Override + public String toString() { + return "TSubU( " + typeVariable + " <: " + relatedType + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java index e12f3005b70..44703ca099c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java @@ -9,18 +9,18 @@ * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint */ public class TSuperU extends TUConstraint { - public TSuperU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { - this(typeVariable, relatedType, false); - } + public TSuperU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { + this(typeVariable, relatedType, false); + } - /** Create a constraint with a variable greater than a type. */ - public TSuperU( - AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - super(typeVariable, relatedType, uIsArg); - } + /** Create a constraint with a variable greater than a type. */ + public TSuperU( + AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { + super(typeVariable, relatedType, uIsArg); + } - @Override - public String toString() { - return "TSuperU( " + typeVariable + " :> " + relatedType + " )"; - } + @Override + public String toString() { + return "TSuperU( " + typeVariable + " :> " + relatedType + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java index 6ce3b0fc574..70633a075fa 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.util.typeinference.constraint; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; -import java.util.Objects; - /** * Subclasses of TUConstraint represent constraints between a type parameter, whose type arguments * are being inferred, and the types used to do that inference. These constraints are used by the @@ -37,48 +36,47 @@ * would be @Nullable String and both arguments would be valid. */ public abstract class TUConstraint { - /** - * An AnnotatedTypeVariable representing a target type parameter for which we are inferring a - * type argument. This is the T in the TUConstraints. - */ - public final AnnotatedTypeVariable typeVariable; + /** + * An AnnotatedTypeVariable representing a target type parameter for which we are inferring a type + * argument. This is the T in the TUConstraints. + */ + public final AnnotatedTypeVariable typeVariable; - /** - * A type used to infer an argument for the typeVariable T. This would be the U in the - * TUConstraints. - */ - public final AnnotatedTypeMirror relatedType; + /** + * A type used to infer an argument for the typeVariable T. This would be the U in the + * TUConstraints. + */ + public final AnnotatedTypeMirror relatedType; - /** Whether or not U is a type from an argument to the method. */ - public final boolean uIsArg; + /** Whether or not U is a type from an argument to the method. */ + public final boolean uIsArg; - protected TUConstraint( - AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - this.typeVariable = typeVariable; - this.relatedType = relatedType; - this.uIsArg = uIsArg; + protected TUConstraint( + AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { + this.typeVariable = typeVariable; + this.relatedType = relatedType; + this.uIsArg = uIsArg; - TypeArgInferenceUtil.checkForUninferredTypes(relatedType); - } + TypeArgInferenceUtil.checkForUninferredTypes(relatedType); + } - @Override - public boolean equals(@Nullable Object thatObject) { - if (this == thatObject) { - return true; - } // else + @Override + public boolean equals(@Nullable Object thatObject) { + if (this == thatObject) { + return true; + } // else - if (thatObject == null || this.getClass() != thatObject.getClass()) { - return false; - } + if (thatObject == null || this.getClass() != thatObject.getClass()) { + return false; + } - TUConstraint that = (TUConstraint) thatObject; + TUConstraint that = (TUConstraint) thatObject; - return this.typeVariable.equals(that.typeVariable) - && this.relatedType.equals(that.relatedType); - } + return this.typeVariable.equals(that.typeVariable) && this.relatedType.equals(that.relatedType); + } - @Override - public int hashCode() { - return Objects.hash(this.getClass(), typeVariable, relatedType); - } + @Override + public int hashCode() { + return Objects.hash(this.getClass(), typeVariable, relatedType); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java index a742b047af7..71ec6a08d2b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java @@ -1,5 +1,10 @@ package org.checkerframework.framework.util.typeinference.solver; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Equalities; @@ -7,13 +12,6 @@ import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Supertypes; import org.checkerframework.javacutil.AnnotationMirrorSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeVariable; - /** * ConstraintMap holds simplified versions of the TUConstraints for ALL type variable for which we * are inferring an argument. The ConstraintMap is edited on the fly as the various solvers work @@ -32,158 +30,157 @@ */ public class ConstraintMap { - private final Map targetToRecords = new LinkedHashMap<>(); - - public ConstraintMap(Set targets) { - for (TypeVariable target : targets) { - targetToRecords.put(target, new TargetConstraints(target)); - } - } + private final Map targetToRecords = new LinkedHashMap<>(); - public ConstraintMap(ConstraintMap toCopy) { - this.targetToRecords.putAll(toCopy.targetToRecords); + public ConstraintMap(Set targets) { + for (TypeVariable target : targets) { + targetToRecords.put(target, new TargetConstraints(target)); } - - /** Gets the equality, subtypes, and supertypes constraints for a particular target. */ - public TargetConstraints getConstraints(TypeVariable target) { - return targetToRecords.get(target); + } + + public ConstraintMap(ConstraintMap toCopy) { + this.targetToRecords.putAll(toCopy.targetToRecords); + } + + /** Gets the equality, subtypes, and supertypes constraints for a particular target. */ + public TargetConstraints getConstraints(TypeVariable target) { + return targetToRecords.get(target); + } + + /** + * Returns the set of all targets passed to the constructor of this constraint map (a target will + * appear in this list whether or not it has any constraints added). + * + * @return the set of all targets passed to the constructor of this constraint map (a target will + * appear in this list whether or not it has any constraints added) + */ + public Set getTargets() { + return targetToRecords.keySet(); + } + + /** + * Add a constraint indicating that the equivalent is equal to target in the given qualifier + * hierarchies. + */ + public void addTargetEquality( + TypeVariable target, TypeVariable equivalent, AnnotationMirrorSet hierarchies) { + Equalities equalities = targetToRecords.get(target).equalities; + AnnotationMirrorSet equivalentTops = + equalities.targets.computeIfAbsent(equivalent, __ -> new AnnotationMirrorSet()); + equivalentTops.addAll(hierarchies); + } + + /** + * Add a constraint indicating that target has primary annotations equal to the given annotations. + */ + public void addPrimaryEqualities( + TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { + Equalities equalities = targetToRecords.get(target).equalities; + + for (AnnotationMirror anno : annos) { + AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); + if (!equalities.primaries.containsKey(top)) { + equalities.primaries.put(top, anno); + } } - - /** - * Returns the set of all targets passed to the constructor of this constraint map (a target - * will appear in this list whether or not it has any constraints added). - * - * @return the set of all targets passed to the constructor of this constraint map (a target - * will appear in this list whether or not it has any constraints added) - */ - public Set getTargets() { - return targetToRecords.keySet(); + } + + /** + * Add a constraint indicating that target is a supertype of subtype in the given qualifier + * hierarchies. + * + * @param hierarchies a set of TOP annotations + */ + public void addTargetSupertype( + TypeVariable target, TypeVariable subtype, AnnotationMirrorSet hierarchies) { + Supertypes supertypes = targetToRecords.get(target).supertypes; + AnnotationMirrorSet supertypeTops = + supertypes.targets.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet()); + supertypeTops.addAll(hierarchies); + } + + /** + * Add a constraint indicating that target is a supertype of subtype in the given qualifier + * hierarchies. + * + * @param hierarchies a set of TOP annotations + */ + public void addTypeSupertype( + TypeVariable target, AnnotatedTypeMirror subtype, AnnotationMirrorSet hierarchies) { + Supertypes supertypes = targetToRecords.get(target).supertypes; + AnnotationMirrorSet supertypeTops = + supertypes.types.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet()); + supertypeTops.addAll(hierarchies); + } + + /** + * Add a constraint indicating that target's primary annotations are subtypes of the given + * annotations. + */ + public void addPrimarySupertype( + TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { + Supertypes supertypes = targetToRecords.get(target).supertypes; + for (AnnotationMirror anno : annos) { + AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); + AnnotationMirrorSet entries = + supertypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet()); + entries.add(anno); } - - /** - * Add a constraint indicating that the equivalent is equal to target in the given qualifier - * hierarchies. - */ - public void addTargetEquality( - TypeVariable target, TypeVariable equivalent, AnnotationMirrorSet hierarchies) { - Equalities equalities = targetToRecords.get(target).equalities; - AnnotationMirrorSet equivalentTops = - equalities.targets.computeIfAbsent(equivalent, __ -> new AnnotationMirrorSet()); - equivalentTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target has primary annotations equal to the given - * annotations. - */ - public void addPrimaryEqualities( - TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { - Equalities equalities = targetToRecords.get(target).equalities; - - for (AnnotationMirror anno : annos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - if (!equalities.primaries.containsKey(top)) { - equalities.primaries.put(top, anno); - } - } - } - - /** - * Add a constraint indicating that target is a supertype of subtype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTargetSupertype( - TypeVariable target, TypeVariable subtype, AnnotationMirrorSet hierarchies) { - Supertypes supertypes = targetToRecords.get(target).supertypes; - AnnotationMirrorSet supertypeTops = - supertypes.targets.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet()); - supertypeTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target is a supertype of subtype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTypeSupertype( - TypeVariable target, AnnotatedTypeMirror subtype, AnnotationMirrorSet hierarchies) { - Supertypes supertypes = targetToRecords.get(target).supertypes; - AnnotationMirrorSet supertypeTops = - supertypes.types.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet()); - supertypeTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target's primary annotations are subtypes of the given - * annotations. - */ - public void addPrimarySupertype( - TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { - Supertypes supertypes = targetToRecords.get(target).supertypes; - for (AnnotationMirror anno : annos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - AnnotationMirrorSet entries = - supertypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet()); - entries.add(anno); - } - } - - /** - * Add a constraint indicating that target is a subtype of supertype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTargetSubtype( - TypeVariable target, TypeVariable supertype, AnnotationMirrorSet hierarchies) { - Subtypes subtypes = targetToRecords.get(target).subtypes; - AnnotationMirrorSet subtypesTops = - subtypes.targets.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet()); - subtypesTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target is a subtype of supertype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTypeSubtype( - TypeVariable target, AnnotatedTypeMirror supertype, AnnotationMirrorSet hierarchies) { - Subtypes subtypes = targetToRecords.get(target).subtypes; - AnnotationMirrorSet subtypesTops = - subtypes.types.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet()); - subtypesTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target's primary annotations are subtypes of the given - * annotations. - */ - public void addPrimarySubtypes( - TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { - Subtypes subtypes = targetToRecords.get(target).subtypes; - for (AnnotationMirror anno : annos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - AnnotationMirrorSet entries = - subtypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet()); - entries.add(anno); - } - } - - /** - * Add a constraint indicating that target is equal to type in the given hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTypeEqualities( - TypeVariable target, AnnotatedTypeMirror type, AnnotationMirrorSet hierarchies) { - Equalities equalities = targetToRecords.get(target).equalities; - AnnotationMirrorSet equalityTops = - equalities.types.computeIfAbsent(type, __ -> new AnnotationMirrorSet()); - equalityTops.addAll(hierarchies); + } + + /** + * Add a constraint indicating that target is a subtype of supertype in the given qualifier + * hierarchies. + * + * @param hierarchies a set of TOP annotations + */ + public void addTargetSubtype( + TypeVariable target, TypeVariable supertype, AnnotationMirrorSet hierarchies) { + Subtypes subtypes = targetToRecords.get(target).subtypes; + AnnotationMirrorSet subtypesTops = + subtypes.targets.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet()); + subtypesTops.addAll(hierarchies); + } + + /** + * Add a constraint indicating that target is a subtype of supertype in the given qualifier + * hierarchies. + * + * @param hierarchies a set of TOP annotations + */ + public void addTypeSubtype( + TypeVariable target, AnnotatedTypeMirror supertype, AnnotationMirrorSet hierarchies) { + Subtypes subtypes = targetToRecords.get(target).subtypes; + AnnotationMirrorSet subtypesTops = + subtypes.types.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet()); + subtypesTops.addAll(hierarchies); + } + + /** + * Add a constraint indicating that target's primary annotations are subtypes of the given + * annotations. + */ + public void addPrimarySubtypes( + TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { + Subtypes subtypes = targetToRecords.get(target).subtypes; + for (AnnotationMirror anno : annos) { + AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); + AnnotationMirrorSet entries = + subtypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet()); + entries.add(anno); } + } + + /** + * Add a constraint indicating that target is equal to type in the given hierarchies. + * + * @param hierarchies a set of TOP annotations + */ + public void addTypeEqualities( + TypeVariable target, AnnotatedTypeMirror type, AnnotationMirrorSet hierarchies) { + Equalities equalities = targetToRecords.get(target).equalities; + AnnotationMirrorSet equalityTops = + equalities.types.computeIfAbsent(type, __ -> new AnnotationMirrorSet()); + equalityTops.addAll(hierarchies); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java index 76a0dcd3873..97c88971a39 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.util.typeinference.solver; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVariable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; @@ -10,211 +14,194 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.TypeAnnotationUtils; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; - /** Converts a set of TUConstraints into a ConstraintMap. */ public class ConstraintMapBuilder { - /** - * Let Ti be a the ith target being inferred Let ATV(i) be the annotated type variable that - * represents as use of Ti which may or may not have primary annotations. Let ATM be an - * annotated type mirror that may or may not be target Tx, or have a component target Tx Let Ai - * be the type argument we are trying to infer for Ti - * - *

We have a set of constraints of the form: {@code ATV(i) ATM} - * - *

Where {@code } is either a subtype ({@code <:}), supertype ({@code :>}), or equality - * relationship ({@code =}). - * - *

Regardless of what {@code } is, a constraint will only imply constraints on Ai in a - * given hierarchy if ATV(i) does NOT have a primary annotation in that hierarchy. That is: - * - *

E.g. Let ATV(i) be @NonNull Ti, the constraints @NonNull Ti = @NonNull @Initialized String - * does not imply any primary annotation in the Nullness hierarchy for type argument Ai because - * the Annotated type mirror has a primary annotation in the NUllness hierarchy. - * - *

However, it does imply that Ai has a primary annotation of @Initialized since ATV(i) has - * no primary annotation in the initialization hierarchy. - * - *

Note, constraints come in 2 forms: - * - *

    - *
  • between a target and a concrete AnnotatedTypeMirror. E.g., As seen above {@code - * (@NonNull Ti = @NonNull @Initialized String)} - *
  • between two targets E.g., {@code (@NonNull Ti = Tj)} - *
- */ - public ConstraintMap build( - Set targets, - Set constraints, - AnnotatedTypeFactory typeFactory) { - - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - ConstraintMap result = new ConstraintMap(targets); - - AnnotationMirrorSet tAnnos = new AnnotationMirrorSet(); - AnnotationMirrorSet uAnnos = new AnnotationMirrorSet(); - AnnotationMirrorSet hierarchiesInRelation = new AnnotationMirrorSet(); - - for (TUConstraint constraint : constraints) { - tAnnos.clear(); - uAnnos.clear(); - hierarchiesInRelation.clear(); - - AnnotatedTypeVariable typeT = constraint.typeVariable; - AnnotatedTypeMirror typeU = constraint.relatedType; - - // If typeU is from an argument to the method, then treat typeU as an ordinary type even - // if it is a target type variable. This is for the case where the inferred type is the - // declared type parameter. For example, - // public T get(T t) { - // return this.get(t); - // } - // The inferred type of T should be T. - if (!constraint.uIsArg - && typeU.getKind() == TypeKind.TYPEVAR - && targets.contains( - (TypeVariable) - TypeAnnotationUtils.unannotatedType( - typeU.getUnderlyingType()))) { - if (typeT.getAnnotations().isEmpty() && typeU.getAnnotations().isEmpty()) { - hierarchiesInRelation.addAll(tops); - - } else { - - for (AnnotationMirror top : tops) { - AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top); - AnnotationMirror uAnno = typeU.getAnnotationInHierarchy(top); - - if (tAnno == null) { - if (uAnno == null) { - hierarchiesInRelation.add(top); - - } else { - tAnnos.add(uAnno); - } - } else { - if (uAnno == null) { - uAnnos.add(tAnno); - - } else { - // This tells us nothing, they both should be equal but either way - // we gain no information if both type vars have annotations - } - } - } - - // If we have a case where Ti = @NonNull Tj we know that for the @Initialization - // hierarchy Ti = TJ and we know that for the @Nullable hierarchy Ti = @NonNull - // . - // This step saves @NonNull annotation. - // This case also covers the case where i = j. - if (!tAnnos.isEmpty()) { - addToPrimaryRelationship( - (TypeVariable) - TypeAnnotationUtils.unannotatedType( - typeT.getUnderlyingType()), - constraint, - result, - tAnnos, - qualHierarchy); - } - - if (!uAnnos.isEmpty()) { - addToPrimaryRelationship( - (TypeVariable) - TypeAnnotationUtils.unannotatedType( - typeU.getUnderlyingType()), - constraint, - result, - uAnnos, - qualHierarchy); - } - } - - // This is the case where we have a relationship between two different targets (Ti - // Tj and i != j) - if (!typeFactory.types.isSameType( - TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), - TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()))) { - addToTargetRelationship( - (TypeVariable) - TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), - (TypeVariable) - TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()), - result, - constraint, - hierarchiesInRelation); - } + /** + * Let Ti be a the ith target being inferred Let ATV(i) be the annotated type variable that + * represents as use of Ti which may or may not have primary annotations. Let ATM be an annotated + * type mirror that may or may not be target Tx, or have a component target Tx Let Ai be the type + * argument we are trying to infer for Ti + * + *

We have a set of constraints of the form: {@code ATV(i) ATM} + * + *

Where {@code } is either a subtype ({@code <:}), supertype ({@code :>}), or equality + * relationship ({@code =}). + * + *

Regardless of what {@code } is, a constraint will only imply constraints on Ai in a given + * hierarchy if ATV(i) does NOT have a primary annotation in that hierarchy. That is: + * + *

E.g. Let ATV(i) be @NonNull Ti, the constraints @NonNull Ti = @NonNull @Initialized String + * does not imply any primary annotation in the Nullness hierarchy for type argument Ai because + * the Annotated type mirror has a primary annotation in the NUllness hierarchy. + * + *

However, it does imply that Ai has a primary annotation of @Initialized since ATV(i) has no + * primary annotation in the initialization hierarchy. + * + *

Note, constraints come in 2 forms: + * + *

    + *
  • between a target and a concrete AnnotatedTypeMirror. E.g., As seen above {@code (@NonNull + * Ti = @NonNull @Initialized String)} + *
  • between two targets E.g., {@code (@NonNull Ti = Tj)} + *
+ */ + public ConstraintMap build( + Set targets, Set constraints, AnnotatedTypeFactory typeFactory) { + + QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); + AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); + ConstraintMap result = new ConstraintMap(targets); + + AnnotationMirrorSet tAnnos = new AnnotationMirrorSet(); + AnnotationMirrorSet uAnnos = new AnnotationMirrorSet(); + AnnotationMirrorSet hierarchiesInRelation = new AnnotationMirrorSet(); + + for (TUConstraint constraint : constraints) { + tAnnos.clear(); + uAnnos.clear(); + hierarchiesInRelation.clear(); + + AnnotatedTypeVariable typeT = constraint.typeVariable; + AnnotatedTypeMirror typeU = constraint.relatedType; + + // If typeU is from an argument to the method, then treat typeU as an ordinary type even + // if it is a target type variable. This is for the case where the inferred type is the + // declared type parameter. For example, + // public T get(T t) { + // return this.get(t); + // } + // The inferred type of T should be T. + if (!constraint.uIsArg + && typeU.getKind() == TypeKind.TYPEVAR + && targets.contains( + (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()))) { + if (typeT.getAnnotations().isEmpty() && typeU.getAnnotations().isEmpty()) { + hierarchiesInRelation.addAll(tops); + + } else { + + for (AnnotationMirror top : tops) { + AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top); + AnnotationMirror uAnno = typeU.getAnnotationInHierarchy(top); + + if (tAnno == null) { + if (uAnno == null) { + hierarchiesInRelation.add(top); + + } else { + tAnnos.add(uAnno); + } } else { - for (AnnotationMirror top : tops) { - AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top); - - if (tAnno == null) { - hierarchiesInRelation.add(top); - } - } - - addToTypeRelationship( - (TypeVariable) - TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), - typeU, - result, - constraint, - hierarchiesInRelation); + if (uAnno == null) { + uAnnos.add(tAnno); + + } else { + // This tells us nothing, they both should be equal but either way + // we gain no information if both type vars have annotations + } } + } + + // If we have a case where Ti = @NonNull Tj we know that for the @Initialization + // hierarchy Ti = TJ and we know that for the @Nullable hierarchy Ti = @NonNull + // . + // This step saves @NonNull annotation. + // This case also covers the case where i = j. + if (!tAnnos.isEmpty()) { + addToPrimaryRelationship( + (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), + constraint, + result, + tAnnos, + qualHierarchy); + } + + if (!uAnnos.isEmpty()) { + addToPrimaryRelationship( + (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()), + constraint, + result, + uAnnos, + qualHierarchy); + } } - return result; - } - - private void addToTargetRelationship( - TypeVariable typeT, - TypeVariable typeU, - ConstraintMap result, - TUConstraint constraint, - AnnotationMirrorSet hierarchiesInRelation) { - if (constraint instanceof TIsU) { - result.addTargetEquality(typeT, typeU, hierarchiesInRelation); - } else if (constraint instanceof TSuperU) { - result.addTargetSupertype(typeT, typeU, hierarchiesInRelation); - } else { - result.addTargetSubtype(typeT, typeU, hierarchiesInRelation); + // This is the case where we have a relationship between two different targets (Ti + // Tj and i != j) + if (!typeFactory.types.isSameType( + TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), + TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()))) { + addToTargetRelationship( + (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), + (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()), + result, + constraint, + hierarchiesInRelation); } - } + } else { + for (AnnotationMirror top : tops) { + AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top); - public void addToPrimaryRelationship( - TypeVariable typeVariable, - TUConstraint constraint, - ConstraintMap result, - AnnotationMirrorSet annotationMirrors, - QualifierHierarchy qualHierarchy) { - if (constraint instanceof TIsU) { - result.addPrimaryEqualities(typeVariable, qualHierarchy, annotationMirrors); - } else if (constraint instanceof TSuperU) { - result.addPrimarySupertype(typeVariable, qualHierarchy, annotationMirrors); - } else { - result.addPrimarySubtypes(typeVariable, qualHierarchy, annotationMirrors); + if (tAnno == null) { + hierarchiesInRelation.add(top); + } } + + addToTypeRelationship( + (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), + typeU, + result, + constraint, + hierarchiesInRelation); + } } - public void addToTypeRelationship( - TypeVariable target, - AnnotatedTypeMirror type, - ConstraintMap result, - TUConstraint constraint, - AnnotationMirrorSet hierarchies) { - if (constraint instanceof TIsU) { - result.addTypeEqualities(target, type, hierarchies); - } else if (constraint instanceof TSuperU) { - result.addTypeSupertype(target, type, hierarchies); - } else { - result.addTypeSubtype(target, type, hierarchies); - } + return result; + } + + private void addToTargetRelationship( + TypeVariable typeT, + TypeVariable typeU, + ConstraintMap result, + TUConstraint constraint, + AnnotationMirrorSet hierarchiesInRelation) { + if (constraint instanceof TIsU) { + result.addTargetEquality(typeT, typeU, hierarchiesInRelation); + } else if (constraint instanceof TSuperU) { + result.addTargetSupertype(typeT, typeU, hierarchiesInRelation); + } else { + result.addTargetSubtype(typeT, typeU, hierarchiesInRelation); + } + } + + public void addToPrimaryRelationship( + TypeVariable typeVariable, + TUConstraint constraint, + ConstraintMap result, + AnnotationMirrorSet annotationMirrors, + QualifierHierarchy qualHierarchy) { + if (constraint instanceof TIsU) { + result.addPrimaryEqualities(typeVariable, qualHierarchy, annotationMirrors); + } else if (constraint instanceof TSuperU) { + result.addPrimarySupertype(typeVariable, qualHierarchy, annotationMirrors); + } else { + result.addPrimarySubtypes(typeVariable, qualHierarchy, annotationMirrors); + } + } + + public void addToTypeRelationship( + TypeVariable target, + AnnotatedTypeMirror type, + ConstraintMap result, + TUConstraint constraint, + AnnotationMirrorSet hierarchies) { + if (constraint instanceof TIsU) { + result.addTypeEqualities(target, type, hierarchies); + } else if (constraint instanceof TSuperU) { + result.addTypeSupertype(target, type, hierarchies); + } else { + result.addTypeSubtype(target, type, hierarchies); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java index 175b0ef37c6..4d160d4361c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.util.typeinference.solver; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVariable; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -13,484 +20,455 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.BugInCF; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; - /** * EqualitiesSolver infers type arguments for targets using the equality constraints in * ConstraintMap. When a type is inferred, it rewrites the remaining equality/supertype constraints */ public class EqualitiesSolver { - private boolean dirty = false; - - /** - * For each target, if there is one or more equality constraints involving concrete types that - * lets us infer a primary annotation in all qualifier hierarchies then infer a concrete type - * argument. else if there is one or more equality constraints involving other targets that lets - * us infer a primary annotation in all qualifier hierarchies then infer that type argument is - * the other type argument - * - *

if we have inferred either a concrete type or another target as type argument rewrite all - * of the constraints for the current target to instead use the inferred type/target - * - *

We do this iteratively until NO new inferred type argument is found - * - * @param targets the list of type parameters for which we are inferring type arguments - * @param constraintMap the set of constraints over the set of targets - * @return a Map from target to (inferred type or target) - */ - public InferenceResult solveEqualities( - Set targets, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - InferenceResult solution = new InferenceResult(); - - do { - dirty = false; - for (TypeVariable target : targets) { - if (solution.containsKey(target)) { - continue; - } - - Equalities equalities = constraintMap.getConstraints(target).equalities; - - InferredValue inferred = - mergeConstraints(target, equalities, solution, constraintMap, typeFactory); - if (inferred != null) { - if (inferred instanceof InferredType) { - rewriteWithInferredType( - target, ((InferredType) inferred).type, constraintMap); - } else { - rewriteWithInferredTarget( - target, - ((InferredTarget) inferred).target, - constraintMap, - typeFactory); - } - - solution.put(target, inferred); - } - } - } while (dirty); - - solution.resolveChainedTargets(); - - return solution; - } - - /** - * Let Ti be a target type parameter. When we reach this method we have inferred an argument, - * Ai, for Ti. - * - *

However, there still may be constraints of the form {@literal Ti = Tj}, {@literal Ti <: - * Tj}, {@literal Tj <: Ti} in the constraint map. In this case we need to replace Ti with the - * type. That is, they become {@literal Ai = Tj}, {@literal Ai <: Tj}, and {@literal Tj <: Ai} - * - *

To do this, we find the TargetConstraints for Tj and add these constraints to the - * appropriate map in TargetConstraints. We can then clear the constraints for the current - * target since we have inferred a type. - * - * @param target the target for which we have inferred a concrete type argument - * @param type the type inferred - * @param constraints the constraints that are side-effected by this method - */ - private void rewriteWithInferredType( - @FindDistinct TypeVariable target, - AnnotatedTypeMirror type, - ConstraintMap constraints) { - - TargetConstraints targetRecord = constraints.getConstraints(target); - Map equivalentTargets = targetRecord.equalities.targets; - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred type - for (Map.Entry eqEntry : equivalentTargets.entrySet()) { - constraints.addTypeEqualities(eqEntry.getKey(), type, eqEntry.getValue()); + private boolean dirty = false; + + /** + * For each target, if there is one or more equality constraints involving concrete types that + * lets us infer a primary annotation in all qualifier hierarchies then infer a concrete type + * argument. else if there is one or more equality constraints involving other targets that lets + * us infer a primary annotation in all qualifier hierarchies then infer that type argument is the + * other type argument + * + *

if we have inferred either a concrete type or another target as type argument rewrite all of + * the constraints for the current target to instead use the inferred type/target + * + *

We do this iteratively until NO new inferred type argument is found + * + * @param targets the list of type parameters for which we are inferring type arguments + * @param constraintMap the set of constraints over the set of targets + * @return a Map from target to (inferred type or target) + */ + public InferenceResult solveEqualities( + Set targets, ConstraintMap constraintMap, AnnotatedTypeFactory typeFactory) { + InferenceResult solution = new InferenceResult(); + + do { + dirty = false; + for (TypeVariable target : targets) { + if (solution.containsKey(target)) { + continue; } - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred type - AnnotationMirrorSet hierarchies = record.equalities.targets.get(target); - if (hierarchies != null) { - record.equalities.targets.remove(target); - constraints.addTypeEqualities(otherTarget, type, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.equalities.types); - record.equalities.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = - TypeArgInferenceUtil.substitute(target, type, otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.equalities.types.put(copy, otherHierarchies); - } - } - } + Equalities equalities = constraintMap.getConstraints(target).equalities; - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred type - AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target); - if (hierarchies != null) { - record.supertypes.targets.remove(target); - constraints.addTypeEqualities(otherTarget, type, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.supertypes.types); - record.supertypes.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = - TypeArgInferenceUtil.substitute(target, type, otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.supertypes.types.put(copy, otherHierarchies); - } - } + InferredValue inferred = + mergeConstraints(target, equalities, solution, constraintMap, typeFactory); + if (inferred != null) { + if (inferred instanceof InferredType) { + rewriteWithInferredType(target, ((InferredType) inferred).type, constraintMap); + } else { + rewriteWithInferredTarget( + target, ((InferredTarget) inferred).target, constraintMap, typeFactory); + } + + solution.put(target, inferred); } - - targetRecord.equalities.clear(); - targetRecord.supertypes.clear(); + } + } while (dirty); + + solution.resolveChainedTargets(); + + return solution; + } + + /** + * Let Ti be a target type parameter. When we reach this method we have inferred an argument, Ai, + * for Ti. + * + *

However, there still may be constraints of the form {@literal Ti = Tj}, {@literal Ti <: Tj}, + * {@literal Tj <: Ti} in the constraint map. In this case we need to replace Ti with the type. + * That is, they become {@literal Ai = Tj}, {@literal Ai <: Tj}, and {@literal Tj <: Ai} + * + *

To do this, we find the TargetConstraints for Tj and add these constraints to the + * appropriate map in TargetConstraints. We can then clear the constraints for the current target + * since we have inferred a type. + * + * @param target the target for which we have inferred a concrete type argument + * @param type the type inferred + * @param constraints the constraints that are side-effected by this method + */ + private void rewriteWithInferredType( + @FindDistinct TypeVariable target, AnnotatedTypeMirror type, ConstraintMap constraints) { + + TargetConstraints targetRecord = constraints.getConstraints(target); + Map equivalentTargets = targetRecord.equalities.targets; + // each target that was equivalent to this one needs to be equivalent in the same + // hierarchies as the inferred type + for (Map.Entry eqEntry : equivalentTargets.entrySet()) { + constraints.addTypeEqualities(eqEntry.getKey(), type, eqEntry.getValue()); } - /** - * Let Ti be a target type parameter. When we reach this method we have inferred that Ti has the - * exact same argument as another target Tj - * - *

Therefore, we want to stop solving for Ti and instead wait till we solve for Tj and use - * that result. - * - *

Let ATM be any annotated type mirror and Tk be a target type parameter where k != i and k - * != j Even though we've inferred Ti = Tj, there still may be constraints of the form Ti = ATM - * or {@literal Ti <: Tk} These constraints are still useful for inferring a argument for Ti/Tj. - * So, we replace Ti in these constraints with Tj and place those constraints in the - * TargetConstraints object for Tj. - * - *

We then clear the constraints for Ti. - * - * @param target the target for which we know another target is exactly equal to this target - * @param inferredTarget the other target inferred to be equal - * @param constraints the constraints that are side-effected by this method - * @param typeFactory type factory - */ - private void rewriteWithInferredTarget( - @FindDistinct TypeVariable target, - @FindDistinct TypeVariable inferredTarget, - ConstraintMap constraints, - AnnotatedTypeFactory typeFactory) { - TargetConstraints targetRecord = constraints.getConstraints(target); - Map equivalentTypes = - targetRecord.equalities.types; - Map supertypes = targetRecord.supertypes.types; - - // each type that was equivalent to this one needs to be equivalent in the same hierarchies - // to the inferred target - for (Map.Entry eqEntry : - equivalentTypes.entrySet()) { - constraints.addTypeEqualities(inferredTarget, eqEntry.getKey(), eqEntry.getValue()); + for (TypeVariable otherTarget : constraints.getTargets()) { + if (otherTarget != target) { + TargetConstraints record = constraints.getConstraints(otherTarget); + + // each target that was equivalent to this one needs to be equivalent in the same + // hierarchies as the inferred type + AnnotationMirrorSet hierarchies = record.equalities.targets.get(target); + if (hierarchies != null) { + record.equalities.targets.remove(target); + constraints.addTypeEqualities(otherTarget, type, hierarchies); } - for (Map.Entry superEntry : - supertypes.entrySet()) { - constraints.addTypeSupertype( - inferredTarget, superEntry.getKey(), superEntry.getValue()); + // otherTypes may have AnnotatedTypeVariables of type target, run substitution on + // these with type + Map toIterate = + new LinkedHashMap<>(record.equalities.types); + record.equalities.types.clear(); + for (AnnotatedTypeMirror otherType : toIterate.keySet()) { + AnnotatedTypeMirror copy = TypeArgInferenceUtil.substitute(target, type, otherType); + AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); + record.equalities.types.put(copy, otherHierarchies); } + } + } + + for (TypeVariable otherTarget : constraints.getTargets()) { + if (otherTarget != target) { + TargetConstraints record = constraints.getConstraints(otherTarget); - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target && otherTarget != inferredTarget) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred target - AnnotationMirrorSet hierarchies = record.equalities.targets.get(target); - if (hierarchies != null) { - record.equalities.targets.remove(target); - constraints.addTargetEquality(otherTarget, inferredTarget, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.equalities.types); - record.equalities.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = - TypeArgInferenceUtil.substitute( - target, createAnnotatedTypeVar(target, typeFactory), otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.equalities.types.put(copy, otherHierarchies); - } - } + // each target that was equivalent to this one needs to be equivalent in the same + // hierarchies as the inferred type + AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target); + if (hierarchies != null) { + record.supertypes.targets.remove(target); + constraints.addTypeEqualities(otherTarget, type, hierarchies); } - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target && otherTarget != inferredTarget) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target); - if (hierarchies != null) { - record.supertypes.targets.remove(target); - constraints.addTargetSupertype(otherTarget, inferredTarget, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.supertypes.types); - record.supertypes.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = - TypeArgInferenceUtil.substitute( - target, createAnnotatedTypeVar(target, typeFactory), otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.supertypes.types.put(copy, otherHierarchies); - } - } + // otherTypes may have AnnotatedTypeVariables of type target, run substitution on + // these with type + Map toIterate = + new LinkedHashMap<>(record.supertypes.types); + record.supertypes.types.clear(); + for (AnnotatedTypeMirror otherType : toIterate.keySet()) { + AnnotatedTypeMirror copy = TypeArgInferenceUtil.substitute(target, type, otherType); + AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); + record.supertypes.types.put(copy, otherHierarchies); } + } + } - targetRecord.equalities.clear(); - targetRecord.supertypes.clear(); + targetRecord.equalities.clear(); + targetRecord.supertypes.clear(); + } + + /** + * Let Ti be a target type parameter. When we reach this method we have inferred that Ti has the + * exact same argument as another target Tj + * + *

Therefore, we want to stop solving for Ti and instead wait till we solve for Tj and use that + * result. + * + *

Let ATM be any annotated type mirror and Tk be a target type parameter where k != i and k != + * j Even though we've inferred Ti = Tj, there still may be constraints of the form Ti = ATM or + * {@literal Ti <: Tk} These constraints are still useful for inferring a argument for Ti/Tj. So, + * we replace Ti in these constraints with Tj and place those constraints in the TargetConstraints + * object for Tj. + * + *

We then clear the constraints for Ti. + * + * @param target the target for which we know another target is exactly equal to this target + * @param inferredTarget the other target inferred to be equal + * @param constraints the constraints that are side-effected by this method + * @param typeFactory type factory + */ + private void rewriteWithInferredTarget( + @FindDistinct TypeVariable target, + @FindDistinct TypeVariable inferredTarget, + ConstraintMap constraints, + AnnotatedTypeFactory typeFactory) { + TargetConstraints targetRecord = constraints.getConstraints(target); + Map equivalentTypes = targetRecord.equalities.types; + Map supertypes = targetRecord.supertypes.types; + + // each type that was equivalent to this one needs to be equivalent in the same hierarchies + // to the inferred target + for (Map.Entry eqEntry : equivalentTypes.entrySet()) { + constraints.addTypeEqualities(inferredTarget, eqEntry.getKey(), eqEntry.getValue()); } - /** Creates a declaration AnnotatedTypeVariable for TypeVariable. */ - private AnnotatedTypeVariable createAnnotatedTypeVar( - TypeVariable typeVariable, AnnotatedTypeFactory typeFactory) { - return (AnnotatedTypeVariable) typeFactory.getAnnotatedType(typeVariable.asElement()); + for (Map.Entry superEntry : supertypes.entrySet()) { + constraints.addTypeSupertype(inferredTarget, superEntry.getKey(), superEntry.getValue()); } - /** - * Returns a concrete type argument or null if there was not enough information to infer one. - * - * @param typesToHierarchies a mapping of (types → hierarchies) that indicate that the - * argument being inferred is equal to the types in each of the hierarchies - * @param primaries a map (hierarchy → annotation in hierarchy) where the annotation in - * hierarchy is equal to the primary annotation on the argument being inferred - * @param tops the set of top annotations in the qualifier hierarchy - * @return a concrete type argument or null if there was not enough information to infer one - */ - private @Nullable InferredType mergeTypesAndPrimaries( - Map typesToHierarchies, - AnnotationMirrorMap primaries, - AnnotationMirrorSet tops, - AnnotatedTypeFactory typeFactory) { - AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet(tops); - - Iterator> entryIterator = - typesToHierarchies.entrySet().iterator(); - if (!entryIterator.hasNext()) { - throw new BugInCF("Merging a list of empty types."); - } + for (TypeVariable otherTarget : constraints.getTargets()) { + if (otherTarget != target && otherTarget != inferredTarget) { + TargetConstraints record = constraints.getConstraints(otherTarget); - Map.Entry head = entryIterator.next(); - - AnnotatedTypeMirror mergedType = head.getKey(); - missingAnnos.removeAll(head.getValue()); - - // 1. if there are multiple equality constraints in a ConstraintMap then the types better - // have the same underlying type or Javac will complain and we won't be here. When building - // ConstraintMaps constraints involving AnnotatedTypeMirrors that are exactly equal are - // combined so there must be some difference between two types being merged here. - // - // 2. Otherwise, we might have the same underlying type but conflicting annotations, then we - // take the first set of annotations and show an error for the argument/return type that - // caused the second differing constraint. - // - // 3. Finally, we expect the following types to be involved in equality constraints: - // AnnotatedDeclaredTypes, AnnotatedTypeVariables, and AnnotatedArrayTypes - while (entryIterator.hasNext() && !missingAnnos.isEmpty()) { - Map.Entry current = entryIterator.next(); - AnnotatedTypeMirror currentType = current.getKey(); - AnnotationMirrorSet currentHierarchies = current.getValue(); - - AnnotationMirrorSet found = new AnnotationMirrorSet(); - for (AnnotationMirror top : missingAnnos) { - if (currentHierarchies.contains(top)) { - AnnotationMirror newAnno = currentType.getAnnotationInHierarchy(top); - if (newAnno != null) { - mergedType.replaceAnnotation(newAnno); - found.add(top); - - } else if (mergedType.getKind() == TypeKind.TYPEVAR - && typeFactory.types.isSameType( - currentType.getUnderlyingType(), - mergedType.getUnderlyingType())) { - // the options here are we are merging with the same typevar, in which case - // we can just remove the annotation from the missing list - found.add(top); - - } else { - // otherwise the other type is missing an annotation - throw new BugInCF( - "Missing annotation.%nmergedType=%s%ncurrentType=%s", - mergedType, currentType); - } - } - } - - missingAnnos.removeAll(found); + // each target that was equivalent to this one needs to be equivalent in the same + // hierarchies as the inferred target + AnnotationMirrorSet hierarchies = record.equalities.targets.get(target); + if (hierarchies != null) { + record.equalities.targets.remove(target); + constraints.addTargetEquality(otherTarget, inferredTarget, hierarchies); } - // add all the annotations from the primaries - for (AnnotationMirror top : missingAnnos) { - AnnotationMirror anno = primaries.get(top); - if (anno != null) { - mergedType.replaceAnnotation(anno); - } + // otherTypes may have AnnotatedTypeVariables of type target, run substitution on + // these with type + Map toIterate = + new LinkedHashMap<>(record.equalities.types); + record.equalities.types.clear(); + for (AnnotatedTypeMirror otherType : toIterate.keySet()) { + AnnotatedTypeMirror copy = + TypeArgInferenceUtil.substitute( + target, createAnnotatedTypeVar(target, typeFactory), otherType); + AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); + record.equalities.types.put(copy, otherHierarchies); } + } + } - typesToHierarchies.clear(); + for (TypeVariable otherTarget : constraints.getTargets()) { + if (otherTarget != target && otherTarget != inferredTarget) { + TargetConstraints record = constraints.getConstraints(otherTarget); - if (missingAnnos.isEmpty()) { - return new InferredType(mergedType); + AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target); + if (hierarchies != null) { + record.supertypes.targets.remove(target); + constraints.addTargetSupertype(otherTarget, inferredTarget, hierarchies); } - // TODO: we probably can do more with this information than just putting it back into the - // TODO: ConstraintMap (which is what's happening here) - AnnotationMirrorSet hierarchies = new AnnotationMirrorSet(tops); - hierarchies.removeAll(missingAnnos); - typesToHierarchies.put(mergedType, hierarchies); + // otherTypes may have AnnotatedTypeVariables of type target, run substitution on + // these with type + Map toIterate = + new LinkedHashMap<>(record.supertypes.types); + record.supertypes.types.clear(); + for (AnnotatedTypeMirror otherType : toIterate.keySet()) { + AnnotatedTypeMirror copy = + TypeArgInferenceUtil.substitute( + target, createAnnotatedTypeVar(target, typeFactory), otherType); + AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); + record.supertypes.types.put(copy, otherHierarchies); + } + } + } - return null; + targetRecord.equalities.clear(); + targetRecord.supertypes.clear(); + } + + /** Creates a declaration AnnotatedTypeVariable for TypeVariable. */ + private AnnotatedTypeVariable createAnnotatedTypeVar( + TypeVariable typeVariable, AnnotatedTypeFactory typeFactory) { + return (AnnotatedTypeVariable) typeFactory.getAnnotatedType(typeVariable.asElement()); + } + + /** + * Returns a concrete type argument or null if there was not enough information to infer one. + * + * @param typesToHierarchies a mapping of (types → hierarchies) that indicate that the + * argument being inferred is equal to the types in each of the hierarchies + * @param primaries a map (hierarchy → annotation in hierarchy) where the annotation in + * hierarchy is equal to the primary annotation on the argument being inferred + * @param tops the set of top annotations in the qualifier hierarchy + * @return a concrete type argument or null if there was not enough information to infer one + */ + private @Nullable InferredType mergeTypesAndPrimaries( + Map typesToHierarchies, + AnnotationMirrorMap primaries, + AnnotationMirrorSet tops, + AnnotatedTypeFactory typeFactory) { + AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet(tops); + + Iterator> entryIterator = + typesToHierarchies.entrySet().iterator(); + if (!entryIterator.hasNext()) { + throw new BugInCF("Merging a list of empty types."); } - public InferredValue mergeConstraints( - TypeVariable target, - Equalities equalities, - InferenceResult solution, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - AnnotationMirrorSet tops = - new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); - InferredValue inferred = null; - if (!equalities.types.isEmpty()) { - inferred = - mergeTypesAndPrimaries( - equalities.types, equalities.primaries, tops, typeFactory); - } + Map.Entry head = entryIterator.next(); - if (inferred != null) { - return inferred; - } // else - - // We did not have enough information to infer an annotation in all hierarchies for one - // concrete type. - // However, we have a "partial solution", one in which we know the type in some but not all - // qualifier hierarchies. - // Update our set of constraints with this information - dirty |= updateTargetsWithPartiallyInferredType(equalities, constraintMap, typeFactory); - inferred = findEqualTarget(equalities, tops); - - if (inferred == null && equalities.types.size() == 1) { - // Still could not find an inferred type in all hierarchies, so just use what type is - // known. - AnnotatedTypeMirror type = equalities.types.keySet().iterator().next(); - inferred = new InferredType(type); - } - return inferred; - } + AnnotatedTypeMirror mergedType = head.getKey(); + missingAnnos.removeAll(head.getValue()); - // If we determined that this target T1 is equal to a type ATM in hierarchies @A,@B,@C - // for each of those hierarchies, if a target is equal to T1 in that hierarchy it is also equal - // to ATM. - // e.g. - // if : T1 == @A @B @C ATM in only the A,B hierarchies - // and T1 == T2 only in @A hierarchy + // 1. if there are multiple equality constraints in a ConstraintMap then the types better + // have the same underlying type or Javac will complain and we won't be here. When building + // ConstraintMaps constraints involving AnnotatedTypeMirrors that are exactly equal are + // combined so there must be some difference between two types being merged here. // - // then T2 == @A @B @C only in the @A hierarchy + // 2. Otherwise, we might have the same underlying type but conflicting annotations, then we + // take the first set of annotations and show an error for the argument/return type that + // caused the second differing constraint. // - public boolean updateTargetsWithPartiallyInferredType( - Equalities equalities, ConstraintMap constraintMap, AnnotatedTypeFactory typeFactory) { - - boolean updated = false; - - if (!equalities.types.isEmpty()) { - if (equalities.types.size() != 1) { - throw new BugInCF("Equalities should have at most 1 constraint."); - } - - Map.Entry remainingTypeEquality; - remainingTypeEquality = equalities.types.entrySet().iterator().next(); - - AnnotatedTypeMirror remainingType = remainingTypeEquality.getKey(); - AnnotationMirrorSet remainingHierarchies = remainingTypeEquality.getValue(); - - // update targets - for (Map.Entry targetToHierarchies : - equalities.targets.entrySet()) { - TypeVariable equalTarget = targetToHierarchies.getKey(); - AnnotationMirrorSet hierarchies = targetToHierarchies.getValue(); - - AnnotationMirrorSet equalTypeHierarchies = - new AnnotationMirrorSet(remainingHierarchies); - equalTypeHierarchies.retainAll(hierarchies); - - Map otherTargetsEqualTypes = - constraintMap.getConstraints(equalTarget).equalities.types; - - AnnotationMirrorSet equalHierarchies = otherTargetsEqualTypes.get(remainingType); - if (equalHierarchies == null) { - equalHierarchies = new AnnotationMirrorSet(equalTypeHierarchies); - otherTargetsEqualTypes.put(remainingType, equalHierarchies); - updated = true; - - } else { - int size = equalHierarchies.size(); - equalHierarchies.addAll(equalTypeHierarchies); - updated = size == equalHierarchies.size(); - } - } + // 3. Finally, we expect the following types to be involved in equality constraints: + // AnnotatedDeclaredTypes, AnnotatedTypeVariables, and AnnotatedArrayTypes + while (entryIterator.hasNext() && !missingAnnos.isEmpty()) { + Map.Entry current = entryIterator.next(); + AnnotatedTypeMirror currentType = current.getKey(); + AnnotationMirrorSet currentHierarchies = current.getValue(); + + AnnotationMirrorSet found = new AnnotationMirrorSet(); + for (AnnotationMirror top : missingAnnos) { + if (currentHierarchies.contains(top)) { + AnnotationMirror newAnno = currentType.getAnnotationInHierarchy(top); + if (newAnno != null) { + mergedType.replaceAnnotation(newAnno); + found.add(top); + + } else if (mergedType.getKind() == TypeKind.TYPEVAR + && typeFactory.types.isSameType( + currentType.getUnderlyingType(), mergedType.getUnderlyingType())) { + // the options here are we are merging with the same typevar, in which case + // we can just remove the annotation from the missing list + found.add(top); + + } else { + // otherwise the other type is missing an annotation + throw new BugInCF( + "Missing annotation.%nmergedType=%s%ncurrentType=%s", mergedType, currentType); + } } + } + + missingAnnos.removeAll(found); + } + + // add all the annotations from the primaries + for (AnnotationMirror top : missingAnnos) { + AnnotationMirror anno = primaries.get(top); + if (anno != null) { + mergedType.replaceAnnotation(anno); + } + } + + typesToHierarchies.clear(); - return updated; + if (missingAnnos.isEmpty()) { + return new InferredType(mergedType); } - /** - * Attempt to find a target which is equal to this target. - * - * @return a target equal to this target in all hierarchies, or null - */ - public @Nullable InferredTarget findEqualTarget( - Equalities equalities, AnnotationMirrorSet tops) { - for (Map.Entry targetToHierarchies : - equalities.targets.entrySet()) { - TypeVariable equalTarget = targetToHierarchies.getKey(); - AnnotationMirrorSet hierarchies = targetToHierarchies.getValue(); - - // Now see if target is equal to equalTarget in all hierarchies - boolean targetIsEqualInAllHierarchies = hierarchies.size() == tops.size(); - if (targetIsEqualInAllHierarchies) { - return new InferredTarget(equalTarget, new AnnotationMirrorSet()); - - } else { - // annos in primaries that are not covered by the target's list of equal hierarchies - AnnotationMirrorSet requiredPrimaries = - new AnnotationMirrorSet(equalities.primaries.keySet()); - requiredPrimaries.removeAll(hierarchies); - - boolean typeWithPrimariesIsEqual = - (requiredPrimaries.size() + hierarchies.size()) == tops.size(); - if (typeWithPrimariesIsEqual) { - return new InferredTarget(equalTarget, requiredPrimaries); - } - } + // TODO: we probably can do more with this information than just putting it back into the + // TODO: ConstraintMap (which is what's happening here) + AnnotationMirrorSet hierarchies = new AnnotationMirrorSet(tops); + hierarchies.removeAll(missingAnnos); + typesToHierarchies.put(mergedType, hierarchies); + + return null; + } + + public InferredValue mergeConstraints( + TypeVariable target, + Equalities equalities, + InferenceResult solution, + ConstraintMap constraintMap, + AnnotatedTypeFactory typeFactory) { + AnnotationMirrorSet tops = + new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); + InferredValue inferred = null; + if (!equalities.types.isEmpty()) { + inferred = mergeTypesAndPrimaries(equalities.types, equalities.primaries, tops, typeFactory); + } + + if (inferred != null) { + return inferred; + } // else + + // We did not have enough information to infer an annotation in all hierarchies for one + // concrete type. + // However, we have a "partial solution", one in which we know the type in some but not all + // qualifier hierarchies. + // Update our set of constraints with this information + dirty |= updateTargetsWithPartiallyInferredType(equalities, constraintMap, typeFactory); + inferred = findEqualTarget(equalities, tops); + + if (inferred == null && equalities.types.size() == 1) { + // Still could not find an inferred type in all hierarchies, so just use what type is + // known. + AnnotatedTypeMirror type = equalities.types.keySet().iterator().next(); + inferred = new InferredType(type); + } + return inferred; + } + + // If we determined that this target T1 is equal to a type ATM in hierarchies @A,@B,@C + // for each of those hierarchies, if a target is equal to T1 in that hierarchy it is also equal + // to ATM. + // e.g. + // if : T1 == @A @B @C ATM in only the A,B hierarchies + // and T1 == T2 only in @A hierarchy + // + // then T2 == @A @B @C only in the @A hierarchy + // + public boolean updateTargetsWithPartiallyInferredType( + Equalities equalities, ConstraintMap constraintMap, AnnotatedTypeFactory typeFactory) { + + boolean updated = false; + + if (!equalities.types.isEmpty()) { + if (equalities.types.size() != 1) { + throw new BugInCF("Equalities should have at most 1 constraint."); + } + + Map.Entry remainingTypeEquality; + remainingTypeEquality = equalities.types.entrySet().iterator().next(); + + AnnotatedTypeMirror remainingType = remainingTypeEquality.getKey(); + AnnotationMirrorSet remainingHierarchies = remainingTypeEquality.getValue(); + + // update targets + for (Map.Entry targetToHierarchies : + equalities.targets.entrySet()) { + TypeVariable equalTarget = targetToHierarchies.getKey(); + AnnotationMirrorSet hierarchies = targetToHierarchies.getValue(); + + AnnotationMirrorSet equalTypeHierarchies = new AnnotationMirrorSet(remainingHierarchies); + equalTypeHierarchies.retainAll(hierarchies); + + Map otherTargetsEqualTypes = + constraintMap.getConstraints(equalTarget).equalities.types; + + AnnotationMirrorSet equalHierarchies = otherTargetsEqualTypes.get(remainingType); + if (equalHierarchies == null) { + equalHierarchies = new AnnotationMirrorSet(equalTypeHierarchies); + otherTargetsEqualTypes.put(remainingType, equalHierarchies); + updated = true; + + } else { + int size = equalHierarchies.size(); + equalHierarchies.addAll(equalTypeHierarchies); + updated = size == equalHierarchies.size(); } + } + } - return null; + return updated; + } + + /** + * Attempt to find a target which is equal to this target. + * + * @return a target equal to this target in all hierarchies, or null + */ + public @Nullable InferredTarget findEqualTarget(Equalities equalities, AnnotationMirrorSet tops) { + for (Map.Entry targetToHierarchies : + equalities.targets.entrySet()) { + TypeVariable equalTarget = targetToHierarchies.getKey(); + AnnotationMirrorSet hierarchies = targetToHierarchies.getValue(); + + // Now see if target is equal to equalTarget in all hierarchies + boolean targetIsEqualInAllHierarchies = hierarchies.size() == tops.size(); + if (targetIsEqualInAllHierarchies) { + return new InferredTarget(equalTarget, new AnnotationMirrorSet()); + + } else { + // annos in primaries that are not covered by the target's list of equal hierarchies + AnnotationMirrorSet requiredPrimaries = + new AnnotationMirrorSet(equalities.primaries.keySet()); + requiredPrimaries.removeAll(hierarchies); + + boolean typeWithPrimariesIsEqual = + (requiredPrimaries.size() + hierarchies.size()) == tops.size(); + if (typeWithPrimariesIsEqual) { + return new InferredTarget(equalTarget, requiredPrimaries); + } + } } + + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java index 80ed81e1081..49950439d73 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java @@ -1,174 +1,170 @@ package org.checkerframework.framework.util.typeinference.solver; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredTarget; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; - import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; - import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredTarget; +import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; /** * Represents the result from inferring type arguments. InferenceResult is a map from: target type * variable to (inferred type or target). */ public class InferenceResult extends LinkedHashMap { - private static final long serialVersionUID = 6911459752070485818L; - - /** - * Returns the set of targets that still don't have an inferred argument. - * - * @return the set of targets that still don't have an inferred argument - */ - public Set getRemainingTargets( - Set allTargets, boolean inferredTypesOnly) { - Set remainingTargets = new LinkedHashSet<>(allTargets); + private static final long serialVersionUID = 6911459752070485818L; + + /** + * Returns the set of targets that still don't have an inferred argument. + * + * @return the set of targets that still don't have an inferred argument + */ + public Set getRemainingTargets( + Set allTargets, boolean inferredTypesOnly) { + Set remainingTargets = new LinkedHashSet<>(allTargets); + + if (inferredTypesOnly) { + + for (TypeVariable target : keySet()) { + if (this.get(target) instanceof InferredType) { + remainingTargets.remove(target); + } + } - if (inferredTypesOnly) { + } else { + remainingTargets.removeAll(this.keySet()); + } - for (TypeVariable target : keySet()) { - if (this.get(target) instanceof InferredType) { - remainingTargets.remove(target); - } - } + return remainingTargets; + } + + /** + * Returns true if we have inferred a concrete type for all targets. + * + * @param targets type variables to check + * @return true if we have inferred a concrete type for all targets + */ + public boolean isComplete(Set targets) { + for (TypeVariable target : targets) { + InferredValue inferred = this.get(target); + + if (inferred == null || inferred instanceof InferredTarget) { + return false; + } else if (inferred instanceof InferredType + && ((InferredType) inferred).type.getKind() == TypeKind.NULL) { + // NullType is not a valid type argument, so continue looking for the correct type. + return false; + } + } + return this.keySet().containsAll(targets); + } + + /** + * If we had a set of inferred results, (e.g. T1 = T2, T2 = T3, T3 = String) propagate any results + * we have (the above constraints become T1 = String, T2 = String, T3 = String) + */ + public void resolveChainedTargets() { + Map inferredTypes = new LinkedHashMap<>(this.size()); + + // TODO: we can probably make this a bit more efficient + boolean grew = true; + while (grew) { + grew = false; + for (Map.Entry inferred : this.entrySet()) { + TypeVariable target = inferred.getKey(); + InferredValue value = inferred.getValue(); + + if (value instanceof InferredType) { + inferredTypes.put(target, value); } else { - remainingTargets.removeAll(this.keySet()); + InferredTarget currentTarget = (InferredTarget) value; + InferredType equivalentType = + (InferredType) inferredTypes.get(((InferredTarget) value).target); + + if (equivalentType != null) { + grew = true; + AnnotatedTypeMirror type = equivalentType.type.deepCopy(); + type.replaceAnnotations(currentTarget.additionalAnnotations); + + InferredType newConstraint = new InferredType(type); + inferredTypes.put(currentTarget.target, newConstraint); + } } - - return remainingTargets; + } } - /** - * Returns true if we have inferred a concrete type for all targets. - * - * @param targets type variables to check - * @return true if we have inferred a concrete type for all targets - */ - public boolean isComplete(Set targets) { - for (TypeVariable target : targets) { - InferredValue inferred = this.get(target); - - if (inferred == null || inferred instanceof InferredTarget) { - return false; - } else if (inferred instanceof InferredType - && ((InferredType) inferred).type.getKind() == TypeKind.NULL) { - // NullType is not a valid type argument, so continue looking for the correct type. - return false; - } - } - return this.keySet().containsAll(targets); + this.putAll(inferredTypes); + } + + public Map toAtmMap() { + Map result = new LinkedHashMap<>(this.size()); + for (Map.Entry entry : this.entrySet()) { + InferredValue inferredValue = entry.getValue(); + if (inferredValue instanceof InferredType) { + result.put(entry.getKey(), ((InferredType) inferredValue).type); + } } - /** - * If we had a set of inferred results, (e.g. T1 = T2, T2 = T3, T3 = String) propagate any - * results we have (the above constraints become T1 = String, T2 = String, T3 = String) - */ - public void resolveChainedTargets() { - Map inferredTypes = new LinkedHashMap<>(this.size()); - - // TODO: we can probably make this a bit more efficient - boolean grew = true; - while (grew) { - grew = false; - for (Map.Entry inferred : this.entrySet()) { - TypeVariable target = inferred.getKey(); - InferredValue value = inferred.getValue(); - - if (value instanceof InferredType) { - inferredTypes.put(target, value); - - } else { - InferredTarget currentTarget = (InferredTarget) value; - InferredType equivalentType = - (InferredType) inferredTypes.get(((InferredTarget) value).target); - - if (equivalentType != null) { - grew = true; - AnnotatedTypeMirror type = equivalentType.type.deepCopy(); - type.replaceAnnotations(currentTarget.additionalAnnotations); - - InferredType newConstraint = new InferredType(type); - inferredTypes.put(currentTarget.target, newConstraint); - } - } - } - } + return result; + } + + /** + * Merges values in subordinate into this result, keeping the results form any type arguments that + * were already contained by this InferenceResult. + * + * @param subordinate a result which we wish to merge into this result + */ + public void mergeSubordinate(InferenceResult subordinate) { + Set previousKeySet = new LinkedHashSet<>(this.keySet()); + Set remainingSubKeys = new LinkedHashSet<>(subordinate.keySet()); + remainingSubKeys.removeAll(keySet()); + + for (TypeVariable target : previousKeySet) { + mergeTarget(target, subordinate); + } - this.putAll(inferredTypes); + for (TypeVariable target : remainingSubKeys) { + this.put(target, subordinate.get(target)); } - public Map toAtmMap() { - Map result = new LinkedHashMap<>(this.size()); - for (Map.Entry entry : this.entrySet()) { - InferredValue inferredValue = entry.getValue(); - if (inferredValue instanceof InferredType) { - result.put(entry.getKey(), ((InferredType) inferredValue).type); - } - } + resolveChainedTargets(); + } - return result; - } + /** Performs a merge for a specific target, we keep only results that lead to a concrete type. */ + protected @Nullable InferredType mergeTarget(TypeVariable target, InferenceResult subordinate) { + InferredValue inferred = this.get(target); + if (inferred instanceof InferredTarget) { + InferredType newType = mergeTarget(((InferredTarget) inferred).target, subordinate); - /** - * Merges values in subordinate into this result, keeping the results form any type arguments - * that were already contained by this InferenceResult. - * - * @param subordinate a result which we wish to merge into this result - */ - public void mergeSubordinate(InferenceResult subordinate) { - Set previousKeySet = new LinkedHashSet<>(this.keySet()); - Set remainingSubKeys = new LinkedHashSet<>(subordinate.keySet()); - remainingSubKeys.removeAll(keySet()); - - for (TypeVariable target : previousKeySet) { - mergeTarget(target, subordinate); + if (newType == null) { + InferredValue subValue = subordinate.get(target); + if (subValue instanceof InferredType) { + this.put(target, subValue); + return null; } - - for (TypeVariable target : remainingSubKeys) { - this.put(target, subordinate.get(target)); + } else { + if (newType.type.getKind() == TypeKind.NULL) { + // If the newType is null, then use the subordinate type, but with the + // primary annotations on null. + InferredValue subValue = subordinate.get(target); + if (subValue instanceof InferredType) { + AnnotatedTypeMirror copy = ((InferredType) subValue).type.deepCopy(); + copy.replaceAnnotations(newType.type.getAnnotations()); + newType = new InferredType(copy); + } } + this.put(target, newType); + return newType; + } - resolveChainedTargets(); - } + return null; + } // else - /** - * Performs a merge for a specific target, we keep only results that lead to a concrete type. - */ - protected @Nullable InferredType mergeTarget(TypeVariable target, InferenceResult subordinate) { - InferredValue inferred = this.get(target); - if (inferred instanceof InferredTarget) { - InferredType newType = mergeTarget(((InferredTarget) inferred).target, subordinate); - - if (newType == null) { - InferredValue subValue = subordinate.get(target); - if (subValue instanceof InferredType) { - this.put(target, subValue); - return null; - } - } else { - if (newType.type.getKind() == TypeKind.NULL) { - // If the newType is null, then use the subordinate type, but with the - // primary annotations on null. - InferredValue subValue = subordinate.get(target); - if (subValue instanceof InferredType) { - AnnotatedTypeMirror copy = ((InferredType) subValue).type.deepCopy(); - copy.replaceAnnotations(newType.type.getAnnotations()); - newType = new InferredType(copy); - } - } - this.put(target, newType); - return newType; - } - - return null; - } // else - - return (InferredType) inferred; - } + return (InferredType) inferred; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java index 445b94d1f3d..f8e53d551ec 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java @@ -1,12 +1,10 @@ package org.checkerframework.framework.util.typeinference.solver; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.javacutil.AnnotationMirrorSet; - import java.util.Collection; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.javacutil.AnnotationMirrorSet; /** * When one of the constraint solvers infers that a the target has a given type/target in ALL @@ -22,34 +20,34 @@ * */ public class InferredValue { - /** - * Indicates that a corresponding target was inferred to be the field "type" in all hierarchies. - */ - public static class InferredType extends InferredValue { - public final AnnotatedTypeMirror type; - - public InferredType(AnnotatedTypeMirror type) { - this.type = type; - } + /** + * Indicates that a corresponding target was inferred to be the field "type" in all hierarchies. + */ + public static class InferredType extends InferredValue { + public final AnnotatedTypeMirror type; + + public InferredType(AnnotatedTypeMirror type) { + this.type = type; } + } + + /** + * Indicates that a corresponding target was inferred to be the field "target" in the hierarchies + * not overridden by additionalAnnotations. + */ + public static class InferredTarget extends InferredValue { + public final TypeVariable target; /** - * Indicates that a corresponding target was inferred to be the field "target" in the - * hierarchies not overridden by additionalAnnotations. + * Indicates that the inferred type should have these primary annotations and the remainder + * should come from the annotations inferred for target. */ - public static class InferredTarget extends InferredValue { - public final TypeVariable target; - - /** - * Indicates that the inferred type should have these primary annotations and the remainder - * should come from the annotations inferred for target. - */ - public final AnnotationMirrorSet additionalAnnotations; + public final AnnotationMirrorSet additionalAnnotations; - public InferredTarget( - TypeVariable target, Collection additionalAnnotations) { - this.target = target; - this.additionalAnnotations = new AnnotationMirrorSet(additionalAnnotations); - } + public InferredTarget( + TypeVariable target, Collection additionalAnnotations) { + this.target = target; + this.additionalAnnotations = new AnnotationMirrorSet(additionalAnnotations); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java index f59f3e98349..ad772acfd0f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java @@ -1,24 +1,22 @@ package org.checkerframework.framework.util.typeinference.solver; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.util.typeinference.GlbUtil; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; -import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Subtypes; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; - import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Types; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.util.typeinference.GlbUtil; +import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; +import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Subtypes; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; /** * Infers type arguments by using the Greatest Lower Bound computation on the subtype relationships @@ -26,156 +24,153 @@ */ public class SubtypesSolver { - /** - * Infers type arguments using subtype constraints. - * - * @param remainingTargets targets for which we still need to infer a value - * @param constraints the set of constraints for all targets - * @return a mapping from target to inferred type. Note this class always infers concrete types - * and will not infer that the target is equivalent to another target. - */ - public InferenceResult solveFromSubtypes( - Set remainingTargets, - ConstraintMap constraints, - AnnotatedTypeFactory typeFactory) { - return glbSubtypes(remainingTargets, constraints, typeFactory); - } - - public InferenceResult glbSubtypes( - Set remainingTargets, - ConstraintMap constraints, - AnnotatedTypeFactory typeFactory) { - InferenceResult inferenceResult = new InferenceResult(); - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - - List targetsSubtypesLast = new ArrayList<>(remainingTargets); - - // If we have two type variables order them A then B - // this is required because we will use the fact that B must be below A - // when determining the glb of B - Collections.sort( - targetsSubtypesLast, - (o1, o2) -> { - if (types.isSubtype(o1, o2)) { - return 1; - } else if (types.isSubtype(o2, o1)) { - return -1; - } - return 0; - }); - - for (TypeVariable target : targetsSubtypesLast) { - Subtypes subtypes = constraints.getConstraints(target).subtypes; - - if (subtypes.types.isEmpty()) { - continue; - } - - propagatePreviousGlbs(subtypes, inferenceResult, subtypes.types); - - // if the subtypes size is only 1 then we need not do any GLBing on the underlying types - // but we may have primary annotations that need to be GLBed - AnnotationMirrorMap primaries = subtypes.primaries; - if (subtypes.types.size() == 1) { - Map.Entry entry = - subtypes.types.entrySet().iterator().next(); - AnnotatedTypeMirror supertype = entry.getKey().deepCopy(); - - for (AnnotationMirror top : entry.getValue()) { - AnnotationMirrorSet superAnnos = primaries.get(top); - // if it is null we're just going to use the anno already on supertype - if (superAnnos != null) { - AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(top); - superAnnos.add(supertypeAnno); - } - } - - if (!primaries.isEmpty()) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror glb = - greatestLowerBound(subtypes.primaries.get(top), qualHierarchy); - supertype.replaceAnnotation(glb); - } - } - - inferenceResult.put(target, new InferredType(supertype)); - - } else { - - // GLB all of the types than combine this with the GLB of primary annotation - // constraints - AnnotatedTypeMirror glbType = GlbUtil.glbAll(subtypes.types, typeFactory); - if (glbType != null) { - if (!primaries.isEmpty()) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror glb = - greatestLowerBound(subtypes.primaries.get(top), qualHierarchy); - AnnotationMirror currentAnno = glbType.getAnnotationInHierarchy(top); - - if (currentAnno == null) { - glbType.addAnnotation(glb); - } else if (glb != null) { - glbType.replaceAnnotation( - qualHierarchy.greatestLowerBoundQualifiersOnly( - glb, currentAnno)); - } - } - } - - inferenceResult.put(target, new InferredType(glbType)); - } - } + /** + * Infers type arguments using subtype constraints. + * + * @param remainingTargets targets for which we still need to infer a value + * @param constraints the set of constraints for all targets + * @return a mapping from target to inferred type. Note this class always infers concrete types + * and will not infer that the target is equivalent to another target. + */ + public InferenceResult solveFromSubtypes( + Set remainingTargets, + ConstraintMap constraints, + AnnotatedTypeFactory typeFactory) { + return glbSubtypes(remainingTargets, constraints, typeFactory); + } + + public InferenceResult glbSubtypes( + Set remainingTargets, + ConstraintMap constraints, + AnnotatedTypeFactory typeFactory) { + InferenceResult inferenceResult = new InferenceResult(); + QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); + + Types types = typeFactory.getProcessingEnv().getTypeUtils(); + + List targetsSubtypesLast = new ArrayList<>(remainingTargets); + + // If we have two type variables order them A then B + // this is required because we will use the fact that B must be below A + // when determining the glb of B + Collections.sort( + targetsSubtypesLast, + (o1, o2) -> { + if (types.isSubtype(o1, o2)) { + return 1; + } else if (types.isSubtype(o2, o1)) { + return -1; + } + return 0; + }); + + for (TypeVariable target : targetsSubtypesLast) { + Subtypes subtypes = constraints.getConstraints(target).subtypes; + + if (subtypes.types.isEmpty()) { + continue; + } + + propagatePreviousGlbs(subtypes, inferenceResult, subtypes.types); + + // if the subtypes size is only 1 then we need not do any GLBing on the underlying types + // but we may have primary annotations that need to be GLBed + AnnotationMirrorMap primaries = subtypes.primaries; + if (subtypes.types.size() == 1) { + Map.Entry entry = + subtypes.types.entrySet().iterator().next(); + AnnotatedTypeMirror supertype = entry.getKey().deepCopy(); + + for (AnnotationMirror top : entry.getValue()) { + AnnotationMirrorSet superAnnos = primaries.get(top); + // if it is null we're just going to use the anno already on supertype + if (superAnnos != null) { + AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(top); + superAnnos.add(supertypeAnno); + } } - return inferenceResult; - } + if (!primaries.isEmpty()) { + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror glb = greatestLowerBound(subtypes.primaries.get(top), qualHierarchy); + supertype.replaceAnnotation(glb); + } + } - /** - * /** If the target corresponding to targetRecord must be a subtype of another target for which - * we have already determined a GLB, add that target's GLB to the list of subtypes to be GLBed - * for this target. - */ - protected static void propagatePreviousGlbs( - Subtypes targetSubtypes, - InferenceResult solution, - Map subtypesOfTarget) { - - for (Map.Entry subtypeTarget : - targetSubtypes.targets.entrySet()) { - InferredValue subtargetInferredGlb = solution.get(subtypeTarget.getKey()); - - if (subtargetInferredGlb != null) { - AnnotatedTypeMirror subtargetGlbType = ((InferredType) subtargetInferredGlb).type; - AnnotationMirrorSet subtargetAnnos = subtypesOfTarget.get(subtargetGlbType); - if (subtargetAnnos != null) { - // there is already an equivalent type in the list of subtypes, just add - // any hierarchies that are not in its list but are in the supertarget's list - subtargetAnnos.addAll(subtypeTarget.getValue()); - } else { - subtypesOfTarget.put(subtargetGlbType, subtypeTarget.getValue()); - } + inferenceResult.put(target, new InferredType(supertype)); + + } else { + + // GLB all of the types than combine this with the GLB of primary annotation + // constraints + AnnotatedTypeMirror glbType = GlbUtil.glbAll(subtypes.types, typeFactory); + if (glbType != null) { + if (!primaries.isEmpty()) { + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror glb = greatestLowerBound(subtypes.primaries.get(top), qualHierarchy); + AnnotationMirror currentAnno = glbType.getAnnotationInHierarchy(top); + + if (currentAnno == null) { + glbType.addAnnotation(glb); + } else if (glb != null) { + glbType.replaceAnnotation( + qualHierarchy.greatestLowerBoundQualifiersOnly(glb, currentAnno)); + } } + } + + inferenceResult.put(target, new InferredType(glbType)); } + } } - /** - * Returns the GLB of annos. - * - * @param annos a set of annotations in the same annotation hierarchy - * @param qualHierarchy the qualifier of the annotation hierarchy - * @return the GLB of annos - */ - private static AnnotationMirror greatestLowerBound( - Iterable annos, QualifierHierarchy qualHierarchy) { - Iterator annoIter = annos.iterator(); - AnnotationMirror glb = annoIter.next(); - - while (annoIter.hasNext()) { - glb = qualHierarchy.greatestLowerBoundQualifiersOnly(glb, annoIter.next()); + return inferenceResult; + } + + /** + * /** If the target corresponding to targetRecord must be a subtype of another target for which + * we have already determined a GLB, add that target's GLB to the list of subtypes to be GLBed for + * this target. + */ + protected static void propagatePreviousGlbs( + Subtypes targetSubtypes, + InferenceResult solution, + Map subtypesOfTarget) { + + for (Map.Entry subtypeTarget : + targetSubtypes.targets.entrySet()) { + InferredValue subtargetInferredGlb = solution.get(subtypeTarget.getKey()); + + if (subtargetInferredGlb != null) { + AnnotatedTypeMirror subtargetGlbType = ((InferredType) subtargetInferredGlb).type; + AnnotationMirrorSet subtargetAnnos = subtypesOfTarget.get(subtargetGlbType); + if (subtargetAnnos != null) { + // there is already an equivalent type in the list of subtypes, just add + // any hierarchies that are not in its list but are in the supertarget's list + subtargetAnnos.addAll(subtypeTarget.getValue()); + } else { + subtypesOfTarget.put(subtargetGlbType, subtypeTarget.getValue()); } - - return glb; + } + } + } + + /** + * Returns the GLB of annos. + * + * @param annos a set of annotations in the same annotation hierarchy + * @param qualHierarchy the qualifier of the annotation hierarchy + * @return the GLB of annos + */ + private static AnnotationMirror greatestLowerBound( + Iterable annos, QualifierHierarchy qualHierarchy) { + Iterator annoIter = annos.iterator(); + AnnotationMirror glb = annoIter.next(); + + while (annoIter.hasNext()) { + glb = qualHierarchy.greatestLowerBoundQualifiersOnly(glb, annoIter.next()); } + + return glb; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java index aafcf175a21..b8db39958e9 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java @@ -1,5 +1,16 @@ package org.checkerframework.framework.util.typeinference.solver; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -15,409 +26,391 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; - /** * Infers type arguments by using the Least Upper Bound computation on the supertype relationships * in a constraint map. */ public class SupertypesSolver { - /** - * Infers type arguments using supertype constraints. - * - * @param remainingTargets targets for which we still need to infer a value - * @param constraintMap the set of constraints for all targets - * @return a mapping from target to inferred type. Note this class always infers concrete types - * and will not infer that the target is equivalent to another target. - */ - public InferenceResult solveFromSupertypes( - Set remainingTargets, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - // infer a type for all targets that have supertype constraints - Lubs lubs = targetToTypeLubs(remainingTargets, constraintMap, typeFactory); - - // add the lub types to the outgoing solution - InferenceResult solution = new InferenceResult(); - for (TypeVariable target : remainingTargets) { - AnnotatedTypeMirror lub = lubs.getType(target); - AnnotationMirrorMap lubAnnos = lubs.getPrimaries(target); - - // we may have a partial solution present in the equality constraints, override - // any annotations found in the lub with annotations from the equality constraints - final InferredValue inferred; - if (lub != null) { - inferred = mergeLubTypeWithEqualities(target, lub, constraintMap, typeFactory); - } else if (lubAnnos != null) { - inferred = - mergeLubAnnosWithEqualities(target, lubAnnos, constraintMap, typeFactory); - } else { - inferred = null; - } - - if (inferred != null) { - solution.put(target, inferred); - } - } - - return solution; + /** + * Infers type arguments using supertype constraints. + * + * @param remainingTargets targets for which we still need to infer a value + * @param constraintMap the set of constraints for all targets + * @return a mapping from target to inferred type. Note this class always infers concrete types + * and will not infer that the target is equivalent to another target. + */ + public InferenceResult solveFromSupertypes( + Set remainingTargets, + ConstraintMap constraintMap, + AnnotatedTypeFactory typeFactory) { + // infer a type for all targets that have supertype constraints + Lubs lubs = targetToTypeLubs(remainingTargets, constraintMap, typeFactory); + + // add the lub types to the outgoing solution + InferenceResult solution = new InferenceResult(); + for (TypeVariable target : remainingTargets) { + AnnotatedTypeMirror lub = lubs.getType(target); + AnnotationMirrorMap lubAnnos = lubs.getPrimaries(target); + + // we may have a partial solution present in the equality constraints, override + // any annotations found in the lub with annotations from the equality constraints + final InferredValue inferred; + if (lub != null) { + inferred = mergeLubTypeWithEqualities(target, lub, constraintMap, typeFactory); + } else if (lubAnnos != null) { + inferred = mergeLubAnnosWithEqualities(target, lubAnnos, constraintMap, typeFactory); + } else { + inferred = null; + } + + if (inferred != null) { + solution.put(target, inferred); + } } - /** - * We previously found a type that is equal to target but not in all hierarchies. Use the - * primary annotations from the lub type to fill in the missing annotations in this type. Use - * that type as the inferred argument. - * - *

If we failed to infer any annotation for a given hierarchy, either previously from - * equalities or from the lub, return null. - */ - protected @Nullable InferredType mergeLubTypeWithEqualities( - TypeVariable target, - AnnotatedTypeMirror lub, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - Equalities equalities = constraintMap.getConstraints(target).equalities; - AnnotationMirrorSet tops = - new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); - - if (!equalities.types.isEmpty()) { - // there should be only one equality type if any at this point - Map.Entry eqEntry = - equalities.types.entrySet().iterator().next(); - AnnotatedTypeMirror equalityType = eqEntry.getKey(); - AnnotationMirrorSet equalityAnnos = eqEntry.getValue(); - - boolean failed = false; - for (AnnotationMirror top : tops) { - if (!equalityAnnos.contains(top)) { - AnnotationMirror lubAnno = lub.getAnnotationInHierarchy(top); - if (lubAnno == null) { - // If the LUB and the Equality were the SAME typevar, and the lub was - // unannotated then "NO ANNOTATION" is the correct choice. - if (lub.getKind() == TypeKind.TYPEVAR - && typeFactory.types.isSameType( - equalityType.getUnderlyingType(), - lub.getUnderlyingType())) { - equalityAnnos.add(top); - } else { - failed = true; - } - - } else { - equalityType.replaceAnnotation(lubAnno); - equalityAnnos.add(top); - } - } + return solution; + } + + /** + * We previously found a type that is equal to target but not in all hierarchies. Use the primary + * annotations from the lub type to fill in the missing annotations in this type. Use that type as + * the inferred argument. + * + *

If we failed to infer any annotation for a given hierarchy, either previously from + * equalities or from the lub, return null. + */ + protected @Nullable InferredType mergeLubTypeWithEqualities( + TypeVariable target, + AnnotatedTypeMirror lub, + ConstraintMap constraintMap, + AnnotatedTypeFactory typeFactory) { + Equalities equalities = constraintMap.getConstraints(target).equalities; + AnnotationMirrorSet tops = + new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); + + if (!equalities.types.isEmpty()) { + // there should be only one equality type if any at this point + Map.Entry eqEntry = + equalities.types.entrySet().iterator().next(); + AnnotatedTypeMirror equalityType = eqEntry.getKey(); + AnnotationMirrorSet equalityAnnos = eqEntry.getValue(); + + boolean failed = false; + for (AnnotationMirror top : tops) { + if (!equalityAnnos.contains(top)) { + AnnotationMirror lubAnno = lub.getAnnotationInHierarchy(top); + if (lubAnno == null) { + // If the LUB and the Equality were the SAME typevar, and the lub was + // unannotated then "NO ANNOTATION" is the correct choice. + if (lub.getKind() == TypeKind.TYPEVAR + && typeFactory.types.isSameType( + equalityType.getUnderlyingType(), lub.getUnderlyingType())) { + equalityAnnos.add(top); + } else { + failed = true; } - if (!failed) { - return new InferredType(equalityType); - } + } else { + equalityType.replaceAnnotation(lubAnno); + equalityAnnos.add(top); + } } + } - return new InferredType(lub); + if (!failed) { + return new InferredType(equalityType); + } } - /** - * We previously found a type that is equal to target but not in all hierarchies. Use the - * primary annotations from the lub annos to fill in the missing annotations in this type. Use - * that type as the inferred argument. - * - *

If we failed to infer any annotation for a given hierarchy, either previously from - * equalities or from the lub, return null. - */ - protected @Nullable InferredType mergeLubAnnosWithEqualities( - TypeVariable target, - AnnotationMirrorMap lubAnnos, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - Equalities equalities = constraintMap.getConstraints(target).equalities; - AnnotationMirrorSet tops = - new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); - - if (!equalities.types.isEmpty()) { - // there should be only equality type if any at this point - Map.Entry eqEntry = - equalities.types.entrySet().iterator().next(); - AnnotatedTypeMirror equalityType = eqEntry.getKey(); - AnnotationMirrorSet equalityAnnos = eqEntry.getValue(); - - boolean failed = false; - for (AnnotationMirror top : tops) { - if (!equalityAnnos.contains(top)) { - AnnotationMirror lubAnno = lubAnnos.get(top); - if (lubAnno == null) { - failed = true; - - } else { - equalityType.replaceAnnotation(lubAnno); - equalityAnnos.add(top); - } - } - } - - if (!failed) { - return new InferredType(equalityType); - } + return new InferredType(lub); + } + + /** + * We previously found a type that is equal to target but not in all hierarchies. Use the primary + * annotations from the lub annos to fill in the missing annotations in this type. Use that type + * as the inferred argument. + * + *

If we failed to infer any annotation for a given hierarchy, either previously from + * equalities or from the lub, return null. + */ + protected @Nullable InferredType mergeLubAnnosWithEqualities( + TypeVariable target, + AnnotationMirrorMap lubAnnos, + ConstraintMap constraintMap, + AnnotatedTypeFactory typeFactory) { + Equalities equalities = constraintMap.getConstraints(target).equalities; + AnnotationMirrorSet tops = + new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); + + if (!equalities.types.isEmpty()) { + // there should be only equality type if any at this point + Map.Entry eqEntry = + equalities.types.entrySet().iterator().next(); + AnnotatedTypeMirror equalityType = eqEntry.getKey(); + AnnotationMirrorSet equalityAnnos = eqEntry.getValue(); + + boolean failed = false; + for (AnnotationMirror top : tops) { + if (!equalityAnnos.contains(top)) { + AnnotationMirror lubAnno = lubAnnos.get(top); + if (lubAnno == null) { + failed = true; + + } else { + equalityType.replaceAnnotation(lubAnno); + equalityAnnos.add(top); + } } + } - return null; + if (!failed) { + return new InferredType(equalityType); + } } - /** Holds the least upper bounds for every target type parameter. */ - static class Lubs { - public final Map types = new LinkedHashMap<>(); - public final Map> primaries = - new LinkedHashMap<>(); + return null; + } - public void addPrimaries( - TypeVariable target, AnnotationMirrorMap primaries) { - this.primaries.put(target, new AnnotationMirrorMap<>(primaries)); - } - - public void addType(TypeVariable target, AnnotatedTypeMirror type) { - types.put(target, type); - } + /** Holds the least upper bounds for every target type parameter. */ + static class Lubs { + public final Map types = new LinkedHashMap<>(); + public final Map> primaries = + new LinkedHashMap<>(); - public AnnotationMirrorMap getPrimaries(TypeVariable target) { - return primaries.get(target); - } - - public AnnotatedTypeMirror getType(TypeVariable target) { - return types.get(target); - } + public void addPrimaries(TypeVariable target, AnnotationMirrorMap primaries) { + this.primaries.put(target, new AnnotationMirrorMap<>(primaries)); } - /** - * For each target, lub all of the types/annotations in its supertypes constraints and return - * the lubs. - * - * @param remainingTargets targets that do not already have an inferred type argument - * @param constraintMap the set of constraints for all targets - * @return the lub determined for each target that has at least 1 supertype constraint - */ - private Lubs targetToTypeLubs( - Set remainingTargets, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - - Lubs solution = new Lubs(); - - AnnotationMirrorMap lubOfPrimaries = new AnnotationMirrorMap<>(); - - List targetsSupertypesLast = new ArrayList<>(remainingTargets); - - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - // If we have two type variables order them B then A - // this is required because we will use the fact that A must be above B - // when determining the LUB of A - Collections.sort( - targetsSupertypesLast, - (o1, o2) -> { - if (types.isSubtype(o1, o2)) { - return -1; - } else if (types.isSubtype(o2, o1)) { - return 1; - } - return 0; - }); - - for (TypeVariable target : targetsSupertypesLast) { - TargetConstraints targetRecord = constraintMap.getConstraints(target); - AnnotationMirrorMap subtypeAnnos = - targetRecord.supertypes.primaries; - Map subtypesOfTarget = - targetRecord.supertypes.types; - - // If this target is a supertype of other targets and those targets have already been - // lubbed add that LUB to the list of lubs for this target (as it must be above this - // target). - propagatePreviousLubs(targetRecord, solution, subtypesOfTarget); - - // lub all the primary annotations and put them in lubOfPrimaries - lubPrimaries(lubOfPrimaries, subtypeAnnos, tops, qualHierarchy); - solution.addPrimaries(target, lubOfPrimaries); - - if (!subtypesOfTarget.isEmpty()) { - AnnotatedTypeMirror lub = leastUpperBound(target, typeFactory, subtypesOfTarget); - AnnotationMirrorSet effectiveLubAnnos = - new AnnotationMirrorSet(lub.getEffectiveAnnotations()); - - for (AnnotationMirror lubAnno : effectiveLubAnnos) { - AnnotationMirror hierarchy = qualHierarchy.getTopAnnotation(lubAnno); - AnnotationMirror primaryLub = lubOfPrimaries.get(hierarchy); - - if (primaryLub != null) { - if (qualHierarchy.isSubtypeQualifiersOnly(lubAnno, primaryLub) - && !AnnotationUtils.areSame(lubAnno, primaryLub)) { - lub.replaceAnnotation(primaryLub); - } - } - } - - solution.addType(target, lub); - } - } - return solution; + public void addType(TypeVariable target, AnnotatedTypeMirror type) { + types.put(target, type); } - /** - * If the target corresponding to targetRecord must be a supertype of another target for which - * we have already determined a lub, add that target's lub to this list. - */ - protected static void propagatePreviousLubs( - TargetConstraints targetRecord, - Lubs solution, - Map subtypesOfTarget) { - - for (Map.Entry supertypeTarget : - targetRecord.supertypes.targets.entrySet()) { - AnnotatedTypeMirror supertargetLub = solution.getType(supertypeTarget.getKey()); - if (supertargetLub != null) { - AnnotationMirrorSet supertargetTypeAnnos = subtypesOfTarget.get(supertargetLub); - if (supertargetTypeAnnos != null) { - // there is already an equivalent type in the list of subtypes, just add - // any hierarchies that are not in its list but are in the supertarget's list - supertargetTypeAnnos.addAll(supertypeTarget.getValue()); - } else { - subtypesOfTarget.put(supertargetLub, supertypeTarget.getValue()); - } - } - } + public AnnotationMirrorMap getPrimaries(TypeVariable target) { + return primaries.get(target); } - /** - * For each qualifier hierarchy in tops, take the lub of the annos in subtypeAnnos that - * correspond to that hierarchy place the lub in lubOfPrimaries. - */ - protected static void lubPrimaries( - AnnotationMirrorMap lubOfPrimaries, - AnnotationMirrorMap subtypeAnnos, - AnnotationMirrorSet tops, - QualifierHierarchy qualHierarchy) { - - lubOfPrimaries.clear(); - for (AnnotationMirror top : tops) { - AnnotationMirrorSet annosInHierarchy = subtypeAnnos.get(top); - if (annosInHierarchy != null && !annosInHierarchy.isEmpty()) { - lubOfPrimaries.put(top, leastUpperBound(annosInHierarchy, qualHierarchy)); - } else { - // If there are no annotations for this hierarchy, add bottom. This happens - // when the only constraint for a type variable is a use that is annotated in - // this hierarchy. Calls to the method below have this property. - // void method(@NonNull T t) {} - lubOfPrimaries.put(top, qualHierarchy.getBottomAnnotation(top)); - } - } + public AnnotatedTypeMirror getType(TypeVariable target) { + return types.get(target); } - - /** - * For each type in typeToHierarchies, if that type does not have a corresponding annotation for - * a given hierarchy replace it with the corresponding value in lowerBoundAnnos. - */ - public static AnnotatedTypeMirror groundMissingHierarchies( - Map.Entry typeToHierarchies, - AnnotationMirrorMap lowerBoundAnnos) { - AnnotationMirrorSet presentHierarchies = typeToHierarchies.getValue(); - AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet(); - for (AnnotationMirror top : lowerBoundAnnos.keySet()) { - if (!presentHierarchies.contains(top)) { - missingAnnos.add(lowerBoundAnnos.get(top)); + } + + /** + * For each target, lub all of the types/annotations in its supertypes constraints and return the + * lubs. + * + * @param remainingTargets targets that do not already have an inferred type argument + * @param constraintMap the set of constraints for all targets + * @return the lub determined for each target that has at least 1 supertype constraint + */ + private Lubs targetToTypeLubs( + Set remainingTargets, + ConstraintMap constraintMap, + AnnotatedTypeFactory typeFactory) { + QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); + AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); + + Lubs solution = new Lubs(); + + AnnotationMirrorMap lubOfPrimaries = new AnnotationMirrorMap<>(); + + List targetsSupertypesLast = new ArrayList<>(remainingTargets); + + Types types = typeFactory.getProcessingEnv().getTypeUtils(); + // If we have two type variables order them B then A + // this is required because we will use the fact that A must be above B + // when determining the LUB of A + Collections.sort( + targetsSupertypesLast, + (o1, o2) -> { + if (types.isSubtype(o1, o2)) { + return -1; + } else if (types.isSubtype(o2, o1)) { + return 1; + } + return 0; + }); + + for (TypeVariable target : targetsSupertypesLast) { + TargetConstraints targetRecord = constraintMap.getConstraints(target); + AnnotationMirrorMap subtypeAnnos = targetRecord.supertypes.primaries; + Map subtypesOfTarget = + targetRecord.supertypes.types; + + // If this target is a supertype of other targets and those targets have already been + // lubbed add that LUB to the list of lubs for this target (as it must be above this + // target). + propagatePreviousLubs(targetRecord, solution, subtypesOfTarget); + + // lub all the primary annotations and put them in lubOfPrimaries + lubPrimaries(lubOfPrimaries, subtypeAnnos, tops, qualHierarchy); + solution.addPrimaries(target, lubOfPrimaries); + + if (!subtypesOfTarget.isEmpty()) { + AnnotatedTypeMirror lub = leastUpperBound(target, typeFactory, subtypesOfTarget); + AnnotationMirrorSet effectiveLubAnnos = + new AnnotationMirrorSet(lub.getEffectiveAnnotations()); + + for (AnnotationMirror lubAnno : effectiveLubAnnos) { + AnnotationMirror hierarchy = qualHierarchy.getTopAnnotation(lubAnno); + AnnotationMirror primaryLub = lubOfPrimaries.get(hierarchy); + + if (primaryLub != null) { + if (qualHierarchy.isSubtypeQualifiersOnly(lubAnno, primaryLub) + && !AnnotationUtils.areSame(lubAnno, primaryLub)) { + lub.replaceAnnotation(primaryLub); } + } } - if (!missingAnnos.isEmpty()) { - AnnotatedTypeMirror copy = typeToHierarchies.getKey().deepCopy(); - copy.replaceAnnotations(missingAnnos); - - return copy; + solution.addType(target, lub); + } + } + return solution; + } + + /** + * If the target corresponding to targetRecord must be a supertype of another target for which we + * have already determined a lub, add that target's lub to this list. + */ + protected static void propagatePreviousLubs( + TargetConstraints targetRecord, + Lubs solution, + Map subtypesOfTarget) { + + for (Map.Entry supertypeTarget : + targetRecord.supertypes.targets.entrySet()) { + AnnotatedTypeMirror supertargetLub = solution.getType(supertypeTarget.getKey()); + if (supertargetLub != null) { + AnnotationMirrorSet supertargetTypeAnnos = subtypesOfTarget.get(supertargetLub); + if (supertargetTypeAnnos != null) { + // there is already an equivalent type in the list of subtypes, just add + // any hierarchies that are not in its list but are in the supertarget's list + supertargetTypeAnnos.addAll(supertypeTarget.getValue()); + } else { + subtypesOfTarget.put(supertargetLub, supertypeTarget.getValue()); } - - return typeToHierarchies.getKey(); + } + } + } + + /** + * For each qualifier hierarchy in tops, take the lub of the annos in subtypeAnnos that correspond + * to that hierarchy place the lub in lubOfPrimaries. + */ + protected static void lubPrimaries( + AnnotationMirrorMap lubOfPrimaries, + AnnotationMirrorMap subtypeAnnos, + AnnotationMirrorSet tops, + QualifierHierarchy qualHierarchy) { + + lubOfPrimaries.clear(); + for (AnnotationMirror top : tops) { + AnnotationMirrorSet annosInHierarchy = subtypeAnnos.get(top); + if (annosInHierarchy != null && !annosInHierarchy.isEmpty()) { + lubOfPrimaries.put(top, leastUpperBound(annosInHierarchy, qualHierarchy)); + } else { + // If there are no annotations for this hierarchy, add bottom. This happens + // when the only constraint for a type variable is a use that is annotated in + // this hierarchy. Calls to the method below have this property. + // void method(@NonNull T t) {} + lubOfPrimaries.put(top, qualHierarchy.getBottomAnnotation(top)); + } + } + } + + /** + * For each type in typeToHierarchies, if that type does not have a corresponding annotation for a + * given hierarchy replace it with the corresponding value in lowerBoundAnnos. + */ + public static AnnotatedTypeMirror groundMissingHierarchies( + Map.Entry typeToHierarchies, + AnnotationMirrorMap lowerBoundAnnos) { + AnnotationMirrorSet presentHierarchies = typeToHierarchies.getValue(); + AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet(); + for (AnnotationMirror top : lowerBoundAnnos.keySet()) { + if (!presentHierarchies.contains(top)) { + missingAnnos.add(lowerBoundAnnos.get(top)); + } } - /** - * Successively calls least upper bound on the elements of types. Unlike - * AnnotatedTypes.leastUpperBound, this method will box primitives if necessary - */ - public static AnnotatedTypeMirror leastUpperBound( - TypeVariable target, - AnnotatedTypeFactory typeFactory, - Map types) { - - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotatedTypeVariable targetsDeclaredType = - (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement()); - AnnotationMirrorMap lowerBoundAnnos = - TypeArgInferenceUtil.createHierarchyMap( - new AnnotationMirrorSet( - targetsDeclaredType.getLowerBound().getEffectiveAnnotations()), - qualHierarchy); - - Iterator> typesIter = - types.entrySet().iterator(); - if (!typesIter.hasNext()) { - throw new BugInCF("Calling LUB on empty list."); - } + if (!missingAnnos.isEmpty()) { + AnnotatedTypeMirror copy = typeToHierarchies.getKey().deepCopy(); + copy.replaceAnnotations(missingAnnos); - // If a constraint implies that a type parameter Ti is a supertype of an annotated type - // mirror Ai but only in a subset of all qualifier hierarchies then for all other qualifier - // hierarchies replace the primary annotation on Ai with the lowest possible annotation - // (ensuring that it won't be the LUB unless there are no other constraints, or all other - // constraints imply the bottom annotation is the LUB). Note: Even if we choose bottom as - // the lub here, the assignment context may raise this annotation. - Map.Entry head = typesIter.next(); - - AnnotatedTypeMirror lubType = groundMissingHierarchies(head, lowerBoundAnnos); - AnnotatedTypeMirror nextType = null; - while (typesIter.hasNext()) { - nextType = groundMissingHierarchies(typesIter.next(), lowerBoundAnnos); - - if (lubType.getKind().isPrimitive()) { - if (!nextType.getKind().isPrimitive()) { - lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType); - } - } else if (nextType.getKind().isPrimitive()) { - if (!lubType.getKind().isPrimitive()) { - nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType); - } - } - lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType); - } + return copy; + } - return lubType; + return typeToHierarchies.getKey(); + } + + /** + * Successively calls least upper bound on the elements of types. Unlike + * AnnotatedTypes.leastUpperBound, this method will box primitives if necessary + */ + public static AnnotatedTypeMirror leastUpperBound( + TypeVariable target, + AnnotatedTypeFactory typeFactory, + Map types) { + + QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); + AnnotatedTypeVariable targetsDeclaredType = + (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement()); + AnnotationMirrorMap lowerBoundAnnos = + TypeArgInferenceUtil.createHierarchyMap( + new AnnotationMirrorSet(targetsDeclaredType.getLowerBound().getEffectiveAnnotations()), + qualHierarchy); + + Iterator> typesIter = + types.entrySet().iterator(); + if (!typesIter.hasNext()) { + throw new BugInCF("Calling LUB on empty list."); } - /** - * Returns the lub of all the annotations in annos. - * - * @param annos a set of annotations in the same annotation hierarchy - * @param qualHierarchy the qualifier hierarchy that contains each annotation - * @return the lub of all the annotations in annos - */ - private static AnnotationMirror leastUpperBound( - Iterable annos, QualifierHierarchy qualHierarchy) { - Iterator annoIter = annos.iterator(); - AnnotationMirror lub = annoIter.next(); - - while (annoIter.hasNext()) { - lub = qualHierarchy.leastUpperBoundQualifiersOnly(lub, annoIter.next()); + // If a constraint implies that a type parameter Ti is a supertype of an annotated type + // mirror Ai but only in a subset of all qualifier hierarchies then for all other qualifier + // hierarchies replace the primary annotation on Ai with the lowest possible annotation + // (ensuring that it won't be the LUB unless there are no other constraints, or all other + // constraints imply the bottom annotation is the LUB). Note: Even if we choose bottom as + // the lub here, the assignment context may raise this annotation. + Map.Entry head = typesIter.next(); + + AnnotatedTypeMirror lubType = groundMissingHierarchies(head, lowerBoundAnnos); + AnnotatedTypeMirror nextType = null; + while (typesIter.hasNext()) { + nextType = groundMissingHierarchies(typesIter.next(), lowerBoundAnnos); + + if (lubType.getKind().isPrimitive()) { + if (!nextType.getKind().isPrimitive()) { + lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType); } + } else if (nextType.getKind().isPrimitive()) { + if (!lubType.getKind().isPrimitive()) { + nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType); + } + } + lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType); + } - return lub; + return lubType; + } + + /** + * Returns the lub of all the annotations in annos. + * + * @param annos a set of annotations in the same annotation hierarchy + * @param qualHierarchy the qualifier hierarchy that contains each annotation + * @return the lub of all the annotations in annos + */ + private static AnnotationMirror leastUpperBound( + Iterable annos, QualifierHierarchy qualHierarchy) { + Iterator annoIter = annos.iterator(); + AnnotationMirror lub = annoIter.next(); + + while (annoIter.hasNext()) { + lub = qualHierarchy.leastUpperBoundQualifiersOnly(lub, annoIter.next()); } + + return lub; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java index d3fde7b6741..e405dcb8177 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java @@ -1,14 +1,12 @@ package org.checkerframework.framework.util.typeinference.solver; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; - import java.util.LinkedHashMap; import java.util.Map; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; /** * TargetConstraints represents the set of all TUConstraints for which target was the type @@ -18,110 +16,108 @@ * @see org.checkerframework.framework.util.typeinference.solver.ConstraintMap */ public class TargetConstraints { - /** - * The type parameter for which we are inferring a type argument. All constraints in this object - * are related to this target. - */ - public final TypeVariable target; + /** + * The type parameter for which we are inferring a type argument. All constraints in this object + * are related to this target. + */ + public final TypeVariable target; + + public final Equalities equalities; + + /** + * The target is the supertype in this case, that these are supertype constraints in which target + * is the supertype. These are NOT supertypes of the target. + */ + public final Supertypes supertypes; + + /** + * The target is the supertype in this case, that these are subtype constraints in which target is + * the subtype. These are NOT subtypes of the target. + */ + public final Subtypes subtypes; + + public TargetConstraints(TypeVariable target) { + this.target = target; + this.equalities = new Equalities(); + this.supertypes = new Supertypes(); + this.subtypes = new Subtypes(); + } + + protected static class Equalities { + // Map( hierarchy top -> exact annotation in hierarchy) + public final AnnotationMirrorMap primaries = new AnnotationMirrorMap<>(); + + // Map( type -> hierarchy top for which the primary annotation of type is equal to the + // primary annotation of the target) + // note all components and underlying types are EXACTLY equal to the key to this map + public final Map types = new LinkedHashMap<>(); + + // Map( type -> hierarchy top for which the primary annotation of target is equal to the + // primary annotation of the target) + // note all components and underlying types are EXACTLY equal to the key to this map + public final Map targets = new LinkedHashMap<>(); + + public void clear() { + primaries.clear(); + types.clear(); + targets.clear(); + } + } + + // remember these are constraint in which target is the supertype + protected static class Supertypes { + // Map( hierarchy top -> annotations that are subtypes to target in hierarchy) + public final AnnotationMirrorMap primaries = new AnnotationMirrorMap<>(); + + // Map( type -> hierarchy tops for which the primary annotations of type are subtypes of the + // primary annotations of the target) + // note all components and underlying types must uphold the supertype relationship in all + // hierarchies + public final Map types = new LinkedHashMap<>(); + + // Map( otherTarget -> hierarchy tops for which the primary annotations of otherTarget are + // subtypes of the primary annotations of the target) + // note all components and underlying types must uphold the subtype relationship in all + // hierarchies + public final Map targets = new LinkedHashMap<>(); + + public void clear() { + primaries.clear(); + types.clear(); + targets.clear(); + } + } + + /** Remember these are constraints in which the target is the subtype. */ + protected static class Subtypes { + /** Create a new Subtypes. */ + public Subtypes() {} - public final Equalities equalities; + /** Map from hierarchy top to annotations that are supertypes to target in hierarchy. */ + public final AnnotationMirrorMap primaries = new AnnotationMirrorMap<>(); /** - * The target is the supertype in this case, that these are supertype constraints in which - * target is the supertype. These are NOT supertypes of the target. + * Map from type to hierarchy tops for which the primary annotations of type are supertypes of + * the primary annotations of the target. + * + *

Note all components and underlying types must uphold the supertype relationship in all + * hierarchies. */ - public final Supertypes supertypes; + public final Map types = new LinkedHashMap<>(); /** - * The target is the supertype in this case, that these are subtype constraints in which target - * is the subtype. These are NOT subtypes of the target. + * Map from otherTarget to hierarchy tops for which the primary annotations of otherTarget are + * supertypes of the primary annotations of the target. + * + *

Note all components and underlying types must uphold the subtype relationship in all + * hierarchies. */ - public final Subtypes subtypes; - - public TargetConstraints(TypeVariable target) { - this.target = target; - this.equalities = new Equalities(); - this.supertypes = new Supertypes(); - this.subtypes = new Subtypes(); - } - - protected static class Equalities { - // Map( hierarchy top -> exact annotation in hierarchy) - public final AnnotationMirrorMap primaries = new AnnotationMirrorMap<>(); - - // Map( type -> hierarchy top for which the primary annotation of type is equal to the - // primary annotation of the target) - // note all components and underlying types are EXACTLY equal to the key to this map - public final Map types = new LinkedHashMap<>(); - - // Map( type -> hierarchy top for which the primary annotation of target is equal to the - // primary annotation of the target) - // note all components and underlying types are EXACTLY equal to the key to this map - public final Map targets = new LinkedHashMap<>(); - - public void clear() { - primaries.clear(); - types.clear(); - targets.clear(); - } - } - - // remember these are constraint in which target is the supertype - protected static class Supertypes { - // Map( hierarchy top -> annotations that are subtypes to target in hierarchy) - public final AnnotationMirrorMap primaries = - new AnnotationMirrorMap<>(); - - // Map( type -> hierarchy tops for which the primary annotations of type are subtypes of the - // primary annotations of the target) - // note all components and underlying types must uphold the supertype relationship in all - // hierarchies - public final Map types = new LinkedHashMap<>(); - - // Map( otherTarget -> hierarchy tops for which the primary annotations of otherTarget are - // subtypes of the primary annotations of the target) - // note all components and underlying types must uphold the subtype relationship in all - // hierarchies - public final Map targets = new LinkedHashMap<>(); - - public void clear() { - primaries.clear(); - types.clear(); - targets.clear(); - } - } + public final Map targets = new LinkedHashMap<>(); - /** Remember these are constraints in which the target is the subtype. */ - protected static class Subtypes { - /** Create a new Subtypes. */ - public Subtypes() {} - - /** Map from hierarchy top to annotations that are supertypes to target in hierarchy. */ - public final AnnotationMirrorMap primaries = - new AnnotationMirrorMap<>(); - - /** - * Map from type to hierarchy tops for which the primary annotations of type are supertypes - * of the primary annotations of the target. - * - *

Note all components and underlying types must uphold the supertype relationship in all - * hierarchies. - */ - public final Map types = new LinkedHashMap<>(); - - /** - * Map from otherTarget to hierarchy tops for which the primary annotations of otherTarget - * are supertypes of the primary annotations of the target. - * - *

Note all components and underlying types must uphold the subtype relationship in all - * hierarchies. - */ - public final Map targets = new LinkedHashMap<>(); - - public void clear() { - primaries.clear(); - types.clear(); - targets.clear(); - } + public void clear() { + primaries.clear(); + types.clear(); + targets.clear(); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java index 213b7324928..154a5ada6df 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java @@ -19,7 +19,7 @@ import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; - +import java.util.Arrays; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; @@ -37,318 +37,296 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.ArraySet; -import java.util.Arrays; - /** Presents formatted type information for various AST trees in a class. */ public abstract class AbstractTypeInformationPresenter implements TypeInformationPresenter { - /** The {@link AnnotatedTypeFactory} for the current analysis. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * The {@link GenericAnnotatedTypeFactory} for the current analysis. null if the factory is not - * an instance of {@link GenericAnnotatedTypeFactory}; otherwise, {@code factory} and {@code - * genFactory} refer to the same object. - */ - protected final @Nullable GenericAnnotatedTypeFactory< - ? extends CFAbstractValue, - ? extends CFAbstractStore, ?>, - ? extends CFAbstractTransfer, - ? extends CFAbstractAnalysis> - genFactory; - - /** This formats the ATMs that the presenter is going to present. */ - protected final AnnotatedTypeFormatter typeFormatter; + /** The {@link AnnotatedTypeFactory} for the current analysis. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** + * The {@link GenericAnnotatedTypeFactory} for the current analysis. null if the factory is not an + * instance of {@link GenericAnnotatedTypeFactory}; otherwise, {@code factory} and {@code + * genFactory} refer to the same object. + */ + protected final @Nullable GenericAnnotatedTypeFactory< + ? extends CFAbstractValue, + ? extends CFAbstractStore, ?>, + ? extends CFAbstractTransfer, + ? extends CFAbstractAnalysis> + genFactory; + + /** This formats the ATMs that the presenter is going to present. */ + protected final AnnotatedTypeFormatter typeFormatter; + + /** + * Constructs a presenter for the given factory. + * + * @param atypeFactory the {@link AnnotatedTypeFactory} for the current analysis + */ + public AbstractTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + this.genFactory = (GenericAnnotatedTypeFactory) atypeFactory; + } else { + this.genFactory = null; + } + this.typeFormatter = createTypeFormatter(); + } + + /** + * The entry point for presenting type information of trees in the given class. + * + * @param tree a {@link ClassTree} that has been annotated by the factory + * @param treePath a {@link TreePath} to {@code tree} + */ + @Override + public void process(ClassTree tree, TreePath treePath) { + TypeInformationReporter visitor = createTypeInformationReporter(tree); + visitor.scan(treePath, null); + } + + /** + * Creates the {@link TypeInformationReporter} to use. + * + * @param tree a {@link ClassTree} that has been annotated by the factory + * @return the {@link TypeInformationReporter} to use + */ + protected abstract TypeInformationReporter createTypeInformationReporter(ClassTree tree); + + /** + * Creates the {@link AnnotatedTypeFormatter} to use for output. + * + * @return the {@link AnnotatedTypeFormatter} to use for output + */ + protected AnnotatedTypeFormatter createTypeFormatter() { + return new DefaultAnnotatedTypeFormatter(true, true); + } + + /** + * A visitor which traverses a class tree and reports type information of various sub-trees. + * + *

Note: Since nested class trees will be type-checked separately, this visitor does not dive + * into any nested class trees. + */ + protected abstract class TypeInformationReporter extends TreePathScanner { + + /** The class tree in which it traverses and reports type information. */ + protected final ClassTree classTree; + + /** Root of the current class tree. This is a helper for computing positions of a sub-tree. */ + protected final CompilationUnitTree currentRoot; + + /** The checker that's currently running. */ + protected final BaseTypeChecker checker; /** - * Constructs a presenter for the given factory. + * Constructs a new reporter for the given class tree. * - * @param atypeFactory the {@link AnnotatedTypeFactory} for the current analysis + * @param classTree the {@link ClassTree} */ - public AbstractTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - this.genFactory = (GenericAnnotatedTypeFactory) atypeFactory; - } else { - this.genFactory = null; - } - this.typeFormatter = createTypeFormatter(); + public TypeInformationReporter(ClassTree classTree) { + this.classTree = classTree; + this.checker = atypeFactory.getChecker(); + this.currentRoot = this.checker.getPathToCompilationUnit().getCompilationUnit(); } /** - * The entry point for presenting type information of trees in the given class. + * Report the {@code type} of {@code tree} in a particular {@code occurrenceKind}. * - * @param tree a {@link ClassTree} that has been annotated by the factory - * @param treePath a {@link TreePath} to {@code tree} + * @param tree the tree + * @param type the type + * @param occurrenceKind the occurrence kind */ + protected abstract void reportTreeType( + Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind); + @Override - public void process(ClassTree tree, TreePath treePath) { - TypeInformationReporter visitor = createTypeInformationReporter(tree); - visitor.scan(treePath, null); + public Void visitClass(ClassTree tree, Void unused) { + @SuppressWarnings("interning:not.interned") + boolean isNestedClass = tree != classTree; + if (isNestedClass) { + // Since nested class trees will be type-checked separately, this visitor does + // not dive into any nested class trees. + return null; + } + return super.visitClass(tree, unused); } - /** - * Creates the {@link TypeInformationReporter} to use. - * - * @param tree a {@link ClassTree} that has been annotated by the factory - * @return the {@link TypeInformationReporter} to use - */ - protected abstract TypeInformationReporter createTypeInformationReporter(ClassTree tree); + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void unused) { + reportTreeType( + tree, atypeFactory.getAnnotatedTypeFromTypeTree(tree), TypeOccurrenceKind.DECLARED_TYPE); + return super.visitTypeParameter(tree, unused); + } - /** - * Creates the {@link AnnotatedTypeFormatter} to use for output. - * - * @return the {@link AnnotatedTypeFormatter} to use for output - */ - protected AnnotatedTypeFormatter createTypeFormatter() { - return new DefaultAnnotatedTypeFormatter(true, true); + @Override + public Void visitVariable(VariableTree tree, Void unused) { + // TODO: "int x = 1" is a VariableTree, but there is no AssignmentTree and it + // TODO: is difficult to locate the "=" symbol. + AnnotatedTypeMirror varType = + genFactory != null + ? genFactory.getAnnotatedTypeLhs(tree) + : atypeFactory.getAnnotatedType(tree); + reportTreeType(tree, varType, TypeOccurrenceKind.DECLARED_TYPE); + return super.visitVariable(tree, unused); } - /** - * A visitor which traverses a class tree and reports type information of various sub-trees. - * - *

Note: Since nested class trees will be type-checked separately, this visitor does not dive - * into any nested class trees. - */ - protected abstract class TypeInformationReporter extends TreePathScanner { - - /** The class tree in which it traverses and reports type information. */ - protected final ClassTree classTree; - - /** - * Root of the current class tree. This is a helper for computing positions of a sub-tree. - */ - protected final CompilationUnitTree currentRoot; - - /** The checker that's currently running. */ - protected final BaseTypeChecker checker; - - /** - * Constructs a new reporter for the given class tree. - * - * @param classTree the {@link ClassTree} - */ - public TypeInformationReporter(ClassTree classTree) { - this.classTree = classTree; - this.checker = atypeFactory.getChecker(); - this.currentRoot = this.checker.getPathToCompilationUnit().getCompilationUnit(); - } - - /** - * Report the {@code type} of {@code tree} in a particular {@code occurrenceKind}. - * - * @param tree the tree - * @param type the type - * @param occurrenceKind the occurrence kind - */ - protected abstract void reportTreeType( - Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind); - - @Override - public Void visitClass(ClassTree tree, Void unused) { - @SuppressWarnings("interning:not.interned") - boolean isNestedClass = tree != classTree; - if (isNestedClass) { - // Since nested class trees will be type-checked separately, this visitor does - // not dive into any nested class trees. - return null; - } - return super.visitClass(tree, unused); - } - - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void unused) { - reportTreeType( - tree, - atypeFactory.getAnnotatedTypeFromTypeTree(tree), - TypeOccurrenceKind.DECLARED_TYPE); - return super.visitTypeParameter(tree, unused); - } - - @Override - public Void visitVariable(VariableTree tree, Void unused) { - // TODO: "int x = 1" is a VariableTree, but there is no AssignmentTree and it - // TODO: is difficult to locate the "=" symbol. - AnnotatedTypeMirror varType = - genFactory != null - ? genFactory.getAnnotatedTypeLhs(tree) - : atypeFactory.getAnnotatedType(tree); - reportTreeType(tree, varType, TypeOccurrenceKind.DECLARED_TYPE); - return super.visitVariable(tree, unused); - } - - @Override - public Void visitMethod(MethodTree tree, Void unused) { - reportTreeType( - tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); - return super.visitMethod(tree, unused); - } + @Override + public Void visitMethod(MethodTree tree, Void unused) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); + return super.visitMethod(tree, unused); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void unused) { - reportTreeType( - tree, - atypeFactory.methodFromUse(tree).executableType, - TypeOccurrenceKind.USE_TYPE); - return super.visitMethodInvocation(tree, unused); - } - - @Override - public Void visitAssignment(AssignmentTree tree, Void unused) { - AnnotatedTypeMirror varType = - genFactory != null - ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) - : atypeFactory.getAnnotatedType(tree.getVariable()); - reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); - reportTreeType( - tree, - atypeFactory.getAnnotatedType(tree.getExpression()), - TypeOccurrenceKind.ASSIGN_RHS_TYPE); - return super.visitAssignment(tree, unused); - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void unused) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - AnnotatedTypeMirror varType = - genFactory != null - ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) - : atypeFactory.getAnnotatedType(tree.getVariable()); - reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); - reportTreeType( - tree, - atypeFactory.getAnnotatedType(tree.getExpression()), - TypeOccurrenceKind.ASSIGN_RHS_TYPE); - return super.visitCompoundAssignment(tree, unused); - } - - @Override - public Void visitUnary(UnaryTree tree, Void unused) { - Tree.Kind treeKind = tree.getKind(); - switch (treeKind) { - case UNARY_PLUS: - case UNARY_MINUS: - case BITWISE_COMPLEMENT: - case LOGICAL_COMPLEMENT: - case PREFIX_INCREMENT: - case PREFIX_DECREMENT: - reportTreeType( - tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - break; - case POSTFIX_INCREMENT: - case POSTFIX_DECREMENT: - reportTreeType( - tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - if (genFactory != null) { - reportTreeType( - tree, - genFactory.getAnnotatedTypeRhsUnaryAssign(tree), - TypeOccurrenceKind.ASSIGN_RHS_TYPE); - } - break; - default: - throw new BugInCF( - "Unsupported unary tree type " - + treeKind - + " for " - + TypeInformationPresenter.class.getCanonicalName()); - } - return super.visitUnary(tree, unused); - } - - @Override - public Void visitBinary(BinaryTree tree, Void unused) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - return super.visitBinary(tree, unused); - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void unused) { - if (TreeUtils.isFieldAccess(tree)) { - reportTreeType( - tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - } else if (TreeUtils.isMethodAccess(tree)) { - reportTreeType( - tree, - atypeFactory.getAnnotatedType(tree), - TypeOccurrenceKind.DECLARED_TYPE); - } - - return super.visitMemberSelect(tree, unused); - } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void unused) { - // the declared type of the functional interface - reportTreeType( - tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); - // the use type of the functional interface + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void unused) { + reportTreeType( + tree, atypeFactory.methodFromUse(tree).executableType, TypeOccurrenceKind.USE_TYPE); + return super.visitMethodInvocation(tree, unused); + } + + @Override + public Void visitAssignment(AssignmentTree tree, Void unused) { + AnnotatedTypeMirror varType = + genFactory != null + ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) + : atypeFactory.getAnnotatedType(tree.getVariable()); + reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); + reportTreeType( + tree, + atypeFactory.getAnnotatedType(tree.getExpression()), + TypeOccurrenceKind.ASSIGN_RHS_TYPE); + return super.visitAssignment(tree, unused); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void unused) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + AnnotatedTypeMirror varType = + genFactory != null + ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) + : atypeFactory.getAnnotatedType(tree.getVariable()); + reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); + reportTreeType( + tree, + atypeFactory.getAnnotatedType(tree.getExpression()), + TypeOccurrenceKind.ASSIGN_RHS_TYPE); + return super.visitCompoundAssignment(tree, unused); + } + + @Override + public Void visitUnary(UnaryTree tree, Void unused) { + Tree.Kind treeKind = tree.getKind(); + switch (treeKind) { + case UNARY_PLUS: + case UNARY_MINUS: + case BITWISE_COMPLEMENT: + case LOGICAL_COMPLEMENT: + case PREFIX_INCREMENT: + case PREFIX_DECREMENT: + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + break; + case POSTFIX_INCREMENT: + case POSTFIX_DECREMENT: + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + if (genFactory != null) { reportTreeType( - tree, - atypeFactory.getFnInterfaceFromTree(tree).first, - TypeOccurrenceKind.USE_TYPE); - return super.visitMemberReference(tree, unused); - } - - @Override - public Void visitIdentifier(IdentifierTree tree, Void unused) { - switch (TreeUtils.elementFromUse(tree).getKind()) { - case ENUM_CONSTANT: - case FIELD: - case PARAMETER: - case LOCAL_VARIABLE: - case EXCEPTION_PARAMETER: - case RESOURCE_VARIABLE: - case CONSTRUCTOR: - reportTreeType( - tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - break; - case METHOD: - reportTreeType( - tree, - atypeFactory.getAnnotatedType(tree), - TypeOccurrenceKind.DECLARED_TYPE); - break; - default: - break; - } - return super.visitIdentifier(tree, unused); - } - - @Override - public Void visitLiteral(LiteralTree tree, Void unused) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - return super.visitLiteral(tree, unused); - } - - /** The tree kinds for methods and lambda expressions. */ - private final ArraySet methodAndLambdaExpression = - new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); - - @Override - public Void visitReturn(ReturnTree tree, Void unused) { - // No output for void methods. - if (tree.getExpression() == null) { - return super.visitReturn(tree, unused); - } - - Tree enclosing = - TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); - - AnnotatedTypeMirror ret = null; - if (enclosing.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); - } else { - AnnotatedExecutableType result = - atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); - ret = result.getReturnType(); - } - - if (ret != null) { - reportTreeType(tree, ret, TypeOccurrenceKind.DECLARED_TYPE); - } - return super.visitReturn(tree, unused); - } + tree, + genFactory.getAnnotatedTypeRhsUnaryAssign(tree), + TypeOccurrenceKind.ASSIGN_RHS_TYPE); + } + break; + default: + throw new BugInCF( + "Unsupported unary tree type " + + treeKind + + " for " + + TypeInformationPresenter.class.getCanonicalName()); + } + return super.visitUnary(tree, unused); + } + + @Override + public Void visitBinary(BinaryTree tree, Void unused) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + return super.visitBinary(tree, unused); + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void unused) { + if (TreeUtils.isFieldAccess(tree)) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + } else if (TreeUtils.isMethodAccess(tree)) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); + } + + return super.visitMemberSelect(tree, unused); + } + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void unused) { + // the declared type of the functional interface + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); + // the use type of the functional interface + reportTreeType( + tree, atypeFactory.getFnInterfaceFromTree(tree).first, TypeOccurrenceKind.USE_TYPE); + return super.visitMemberReference(tree, unused); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, Void unused) { + switch (TreeUtils.elementFromUse(tree).getKind()) { + case ENUM_CONSTANT: + case FIELD: + case PARAMETER: + case LOCAL_VARIABLE: + case EXCEPTION_PARAMETER: + case RESOURCE_VARIABLE: + case CONSTRUCTOR: + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + break; + case METHOD: + reportTreeType( + tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); + break; + default: + break; + } + return super.visitIdentifier(tree, unused); + } + + @Override + public Void visitLiteral(LiteralTree tree, Void unused) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + return super.visitLiteral(tree, unused); + } + + /** The tree kinds for methods and lambda expressions. */ + private final ArraySet methodAndLambdaExpression = + new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); + + @Override + public Void visitReturn(ReturnTree tree, Void unused) { + // No output for void methods. + if (tree.getExpression() == null) { + return super.visitReturn(tree, unused); + } + + Tree enclosing = TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); + + AnnotatedTypeMirror ret = null; + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); + } else { + AnnotatedExecutableType result = + atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); + ret = result.getReturnType(); + } + + if (ret != null) { + reportTreeType(tree, ret, TypeOccurrenceKind.DECLARED_TYPE); + } + return super.visitReturn(tree, unused); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java index d7d2c425317..a27faebdf1f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java @@ -12,13 +12,11 @@ import com.sun.source.tree.VariableTree; import com.sun.source.util.SourcePositions; import com.sun.tools.javac.tree.JCTree; - +import javax.tools.Diagnostic; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; -import javax.tools.Diagnostic; - /** * Presents formatted type information for various AST trees in a class. * @@ -27,197 +25,195 @@ */ public class LspTypeInformationPresenter extends AbstractTypeInformationPresenter { + /** + * Constructs a presenter for the given factory. + * + * @param atypeFactory the AnnotatedTypeFactory for the current analysis + */ + public LspTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + protected TypeInformationReporter createTypeInformationReporter(ClassTree tree) { + return new LspTypeInformationReporter(tree); + } + + /** Type information reporter that uses a format suitable for the LSP server. */ + protected class LspTypeInformationReporter extends TypeInformationReporter { + /** Computes positions of a sub-tree. */ + protected final SourcePositions sourcePositions; + /** - * Constructs a presenter for the given factory. + * Constructor. * - * @param atypeFactory the AnnotatedTypeFactory for the current analysis + * @param classTree the {@link ClassTree} */ - public LspTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + LspTypeInformationReporter(ClassTree classTree) { + super(classTree); + this.sourcePositions = atypeFactory.getTreeUtils().getSourcePositions(); } + /** + * Reports a diagnostic message indicating the range corresponding to the given tree has the + * given type. Specifically, the message has key "lsp.type.information", and it contains the + * name of the checker, the given occurrenceKind, the given type, and the computed message range + * for the tree. If the tree is an artificial tree, this does not report anything. + * + * @param tree the tree that is used to find the corresponding range to report + * @param type the type that we are going to display + * @param occurrenceKind the kind of the given type + */ @Override - protected TypeInformationReporter createTypeInformationReporter(ClassTree tree) { - return new LspTypeInformationReporter(tree); + protected void reportTreeType( + Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind) { + TypeOccurrenceRange messageRange = computeTypeOccurrenceRange(tree); + if (messageRange == null) { + // Don't report if the tree can't be found in the source file. + // Please check the implementation of computeTypeOccurrenceRange for + // more details. + return; + } + + checker.reportError( + tree, + "lsp.type.information", + checker.getClass().getSimpleName(), + occurrenceKind, + typeFormatter.format(type), + messageRange); } - /** Type information reporter that uses a format suitable for the LSP server. */ - protected class LspTypeInformationReporter extends TypeInformationReporter { - /** Computes positions of a sub-tree. */ - protected final SourcePositions sourcePositions; - - /** - * Constructor. - * - * @param classTree the {@link ClassTree} - */ - LspTypeInformationReporter(ClassTree classTree) { - super(classTree); - this.sourcePositions = atypeFactory.getTreeUtils().getSourcePositions(); - } - - /** - * Reports a diagnostic message indicating the range corresponding to the given tree has the - * given type. Specifically, the message has key "lsp.type.information", and it contains the - * name of the checker, the given occurrenceKind, the given type, and the computed message - * range for the tree. If the tree is an artificial tree, this does not report anything. - * - * @param tree the tree that is used to find the corresponding range to report - * @param type the type that we are going to display - * @param occurrenceKind the kind of the given type - */ - @Override - protected void reportTreeType( - Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind) { - TypeOccurrenceRange messageRange = computeTypeOccurrenceRange(tree); - if (messageRange == null) { - // Don't report if the tree can't be found in the source file. - // Please check the implementation of computeTypeOccurrenceRange for - // more details. - return; - } - - checker.reportError( - tree, - "lsp.type.information", - checker.getClass().getSimpleName(), - occurrenceKind, - typeFormatter.format(type), - messageRange); - } - - /** - * Computes the 0-based inclusive message range for the given tree. - * - *

Note that the range sometimes don't cover the entire source code of the tree. For - * example, in "int a = 0", we have a variable tree "int a", but we only want to report the - * range of the identifier "a". This customizes the positions where we want the type - * information to show. - * - * @param tree the tree for which we want to compute the message range - * @return a message range corresponds to the tree - */ - protected @Nullable TypeOccurrenceRange computeTypeOccurrenceRange(Tree tree) { - long startPos = sourcePositions.getStartPosition(currentRoot, tree); - long endPos = sourcePositions.getEndPosition(currentRoot, tree); - if (startPos == Diagnostic.NOPOS || endPos == Diagnostic.NOPOS) { - // The tree doesn't exist in the source file. - // For example, a class tree may contain a child that represents - // a default constructor which is not explicitly written out in - // the source file. - // For this kind of trees, there's no way to compute their range - // in the source file. - return null; - } - - LineMap lineMap = currentRoot.getLineMap(); - startPos = ((JCTree) tree).getPreferredPosition(); - long startLine = lineMap.getLineNumber(startPos); - long startCol = lineMap.getColumnNumber(startPos); - long endLine = startLine; - long endCol; - - // We are decreasing endCol by 1 because we want it to be inclusive - switch (tree.getKind()) { - case UNARY_PLUS: - case UNARY_MINUS: - case BITWISE_COMPLEMENT: - case LOGICAL_COMPLEMENT: - case MULTIPLY: - case DIVIDE: - case REMAINDER: - case PLUS: - case MINUS: - case AND: - case XOR: - case OR: - case ASSIGNMENT: - case LESS_THAN: - case GREATER_THAN: - // 1-character operators - endCol = startCol; - break; - case PREFIX_INCREMENT: - case PREFIX_DECREMENT: - case POSTFIX_INCREMENT: - case POSTFIX_DECREMENT: - case LEFT_SHIFT: - case RIGHT_SHIFT: - case CONDITIONAL_AND: - case CONDITIONAL_OR: - case MULTIPLY_ASSIGNMENT: - case DIVIDE_ASSIGNMENT: - case REMAINDER_ASSIGNMENT: - case PLUS_ASSIGNMENT: - case MINUS_ASSIGNMENT: - case AND_ASSIGNMENT: - case XOR_ASSIGNMENT: - case OR_ASSIGNMENT: - case LESS_THAN_EQUAL: - case GREATER_THAN_EQUAL: - case EQUAL_TO: - case NOT_EQUAL_TO: - // 2-character operators - endCol = startCol + 1; - break; - case UNSIGNED_RIGHT_SHIFT: - case LEFT_SHIFT_ASSIGNMENT: - case RIGHT_SHIFT_ASSIGNMENT: - // 3-character operators - endCol = startCol + 2; - break; - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - // 4-character operators - endCol = startCol + 3; - break; - case IDENTIFIER: - endCol = startCol + ((IdentifierTree) tree).getName().length() - 1; - break; - case VARIABLE: - endCol = startCol + ((VariableTree) tree).getName().length() - 1; - break; - case MEMBER_SELECT: - // The preferred start column of MemberSelectTree locates the "." - // character before the member identifier. So we increase startCol - // by 1 to point to the start of the member identifier. - startCol += 1; - endCol = startCol + ((MemberSelectTree) tree).getIdentifier().length() - 1; - break; - case MEMBER_REFERENCE: - MemberReferenceTree memberReferenceTree = (MemberReferenceTree) tree; - - final int identifierLength; - if (memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW) { - identifierLength = 3; - } else { - identifierLength = memberReferenceTree.getName().length(); - } - - // The preferred position of a MemberReferenceTree is the head of - // its expression, which is not ideal. Here we compute the range of - // its identifier using the end position and the length of the identifier. - endLine = lineMap.getLineNumber(endPos); - endCol = lineMap.getColumnNumber(endPos) - 1; - startLine = endLine; - startCol = endCol - identifierLength + 1; - break; - case TYPE_PARAMETER: - endCol = startCol + ((TypeParameterTree) tree).getName().length() - 1; - break; - case METHOD: - endCol = startCol + ((MethodTree) tree).getName().length() - 1; - break; - case METHOD_INVOCATION: - return computeTypeOccurrenceRange( - ((MethodInvocationTree) tree).getMethodSelect()); - default: - endLine = lineMap.getLineNumber(endPos); - endCol = lineMap.getColumnNumber(endPos) - 1; - break; - } - - // convert 1-based positions to 0-based positions - return TypeOccurrenceRange.of(startLine - 1, startCol - 1, endLine - 1, endCol - 1); - } + /** + * Computes the 0-based inclusive message range for the given tree. + * + *

Note that the range sometimes don't cover the entire source code of the tree. For example, + * in "int a = 0", we have a variable tree "int a", but we only want to report the range of the + * identifier "a". This customizes the positions where we want the type information to show. + * + * @param tree the tree for which we want to compute the message range + * @return a message range corresponds to the tree + */ + protected @Nullable TypeOccurrenceRange computeTypeOccurrenceRange(Tree tree) { + long startPos = sourcePositions.getStartPosition(currentRoot, tree); + long endPos = sourcePositions.getEndPosition(currentRoot, tree); + if (startPos == Diagnostic.NOPOS || endPos == Diagnostic.NOPOS) { + // The tree doesn't exist in the source file. + // For example, a class tree may contain a child that represents + // a default constructor which is not explicitly written out in + // the source file. + // For this kind of trees, there's no way to compute their range + // in the source file. + return null; + } + + LineMap lineMap = currentRoot.getLineMap(); + startPos = ((JCTree) tree).getPreferredPosition(); + long startLine = lineMap.getLineNumber(startPos); + long startCol = lineMap.getColumnNumber(startPos); + long endLine = startLine; + long endCol; + + // We are decreasing endCol by 1 because we want it to be inclusive + switch (tree.getKind()) { + case UNARY_PLUS: + case UNARY_MINUS: + case BITWISE_COMPLEMENT: + case LOGICAL_COMPLEMENT: + case MULTIPLY: + case DIVIDE: + case REMAINDER: + case PLUS: + case MINUS: + case AND: + case XOR: + case OR: + case ASSIGNMENT: + case LESS_THAN: + case GREATER_THAN: + // 1-character operators + endCol = startCol; + break; + case PREFIX_INCREMENT: + case PREFIX_DECREMENT: + case POSTFIX_INCREMENT: + case POSTFIX_DECREMENT: + case LEFT_SHIFT: + case RIGHT_SHIFT: + case CONDITIONAL_AND: + case CONDITIONAL_OR: + case MULTIPLY_ASSIGNMENT: + case DIVIDE_ASSIGNMENT: + case REMAINDER_ASSIGNMENT: + case PLUS_ASSIGNMENT: + case MINUS_ASSIGNMENT: + case AND_ASSIGNMENT: + case XOR_ASSIGNMENT: + case OR_ASSIGNMENT: + case LESS_THAN_EQUAL: + case GREATER_THAN_EQUAL: + case EQUAL_TO: + case NOT_EQUAL_TO: + // 2-character operators + endCol = startCol + 1; + break; + case UNSIGNED_RIGHT_SHIFT: + case LEFT_SHIFT_ASSIGNMENT: + case RIGHT_SHIFT_ASSIGNMENT: + // 3-character operators + endCol = startCol + 2; + break; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + // 4-character operators + endCol = startCol + 3; + break; + case IDENTIFIER: + endCol = startCol + ((IdentifierTree) tree).getName().length() - 1; + break; + case VARIABLE: + endCol = startCol + ((VariableTree) tree).getName().length() - 1; + break; + case MEMBER_SELECT: + // The preferred start column of MemberSelectTree locates the "." + // character before the member identifier. So we increase startCol + // by 1 to point to the start of the member identifier. + startCol += 1; + endCol = startCol + ((MemberSelectTree) tree).getIdentifier().length() - 1; + break; + case MEMBER_REFERENCE: + MemberReferenceTree memberReferenceTree = (MemberReferenceTree) tree; + + final int identifierLength; + if (memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW) { + identifierLength = 3; + } else { + identifierLength = memberReferenceTree.getName().length(); + } + + // The preferred position of a MemberReferenceTree is the head of + // its expression, which is not ideal. Here we compute the range of + // its identifier using the end position and the length of the identifier. + endLine = lineMap.getLineNumber(endPos); + endCol = lineMap.getColumnNumber(endPos) - 1; + startLine = endLine; + startCol = endCol - identifierLength + 1; + break; + case TYPE_PARAMETER: + endCol = startCol + ((TypeParameterTree) tree).getName().length() - 1; + break; + case METHOD: + endCol = startCol + ((MethodTree) tree).getName().length() - 1; + break; + case METHOD_INVOCATION: + return computeTypeOccurrenceRange(((MethodInvocationTree) tree).getMethodSelect()); + default: + endLine = lineMap.getLineNumber(endPos); + endCol = lineMap.getColumnNumber(endPos) - 1; + break; + } + + // convert 1-based positions to 0-based positions + return TypeOccurrenceRange.of(startLine - 1, startCol - 1, endLine - 1, endCol - 1); } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java index 13a80ac85f0..68bb4de61a2 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java @@ -11,11 +11,11 @@ * . */ public interface TypeInformationPresenter { - /** - * The entry point for presenting type information of trees in the given class. - * - * @param tree a ClassTree that has been type-checked by the factory - * @param treePath a {@link TreePath} to {@code tree} - */ - void process(ClassTree tree, TreePath treePath); + /** + * The entry point for presenting type information of trees in the given class. + * + * @param tree a ClassTree that has been type-checked by the factory + * @param treePath a {@link TreePath} to {@code tree} + */ + void process(ClassTree tree, TreePath treePath); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java index 6fcf0d960d2..60f96e5aa95 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java @@ -2,21 +2,21 @@ /** Types can occurr in different kinds of positions. */ public enum TypeOccurrenceKind { - /** The type of the tree at its use site. */ - USE_TYPE, - /** - * The declared type of the tree. For a method, it should be the method's signature. For a - * field, it should be the type of the field in its declaration. - */ - DECLARED_TYPE, - /** The declared type of the LHS of an assignment or compound assignment tree. */ - ASSIGN_LHS_DECLARED_TYPE, - /** - * The type of the RHS of an assignment or compound assignment tree. - * - *

For a postfix operation, it can be considered as a special assignment tree, in which the - * LHS is returned and the RHS is the new value of the variable. In this situation, this message - * kind means the type of the new value of the variable. - */ - ASSIGN_RHS_TYPE, + /** The type of the tree at its use site. */ + USE_TYPE, + /** + * The declared type of the tree. For a method, it should be the method's signature. For a field, + * it should be the type of the field in its declaration. + */ + DECLARED_TYPE, + /** The declared type of the LHS of an assignment or compound assignment tree. */ + ASSIGN_LHS_DECLARED_TYPE, + /** + * The type of the RHS of an assignment or compound assignment tree. + * + *

For a postfix operation, it can be considered as a special assignment tree, in which the LHS + * is returned and the RHS is the new value of the variable. In this situation, this message kind + * means the type of the new value of the variable. + */ + ASSIGN_RHS_TYPE, } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java index 297fd44b54c..d6c15a23c25 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java @@ -5,48 +5,48 @@ * a piece of type information refers. All indices are 0-based since LSP uses 0-based positions. */ public class TypeOccurrenceRange { - /** 0-based line number of the start position. */ - private final long startLine; + /** 0-based line number of the start position. */ + private final long startLine; - /** 0-based column number of the start position. */ - private final long startCol; + /** 0-based column number of the start position. */ + private final long startCol; - /** 0-based line number of the end position. */ - private final long endLine; + /** 0-based line number of the end position. */ + private final long endLine; - /** 0-based column number of the end position. */ - private final long endCol; + /** 0-based column number of the end position. */ + private final long endCol; - /** - * Constructs a new {@link TypeOccurrenceRange} with the given position information. - * - * @param startLine 0-based line number of the start position - * @param startCol 0-based column number of the start position - * @param endLine 0-based line number of the end position - * @param endCol 0-based column number of the end position - */ - private TypeOccurrenceRange(long startLine, long startCol, long endLine, long endCol) { - this.startLine = startLine; - this.startCol = startCol; - this.endLine = endLine; - this.endCol = endCol; - } + /** + * Constructs a new {@link TypeOccurrenceRange} with the given position information. + * + * @param startLine 0-based line number of the start position + * @param startCol 0-based column number of the start position + * @param endLine 0-based line number of the end position + * @param endCol 0-based column number of the end position + */ + private TypeOccurrenceRange(long startLine, long startCol, long endLine, long endCol) { + this.startLine = startLine; + this.startCol = startCol; + this.endLine = endLine; + this.endCol = endCol; + } - /** - * Constructs a new {@link TypeOccurrenceRange} with the given position information. - * - * @param startLine 0-based line number of the start position - * @param startCol 0-based column number of the start position - * @param endLine 0-based line number of the end position - * @param endCol 0-based column number of the end position - * @return a new {@link TypeOccurrenceRange} with the given position information - */ - public static TypeOccurrenceRange of(long startLine, long startCol, long endLine, long endCol) { - return new TypeOccurrenceRange(startLine, startCol, endLine, endCol); - } + /** + * Constructs a new {@link TypeOccurrenceRange} with the given position information. + * + * @param startLine 0-based line number of the start position + * @param startCol 0-based column number of the start position + * @param endLine 0-based line number of the end position + * @param endCol 0-based column number of the end position + * @return a new {@link TypeOccurrenceRange} with the given position information + */ + public static TypeOccurrenceRange of(long startLine, long startCol, long endLine, long endCol) { + return new TypeOccurrenceRange(startLine, startCol, endLine, endCol); + } - @Override - public String toString() { - return String.format("(%d, %d, %d, %d)", startLine, startCol, endLine, endCol); - } + @Override + public String toString() { + return String.format("(%d, %d, %d, %d)", startLine, startCol, endLine, endCol); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java index 09481342248..f96ccd38706 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java @@ -1,32 +1,31 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.testaccumulation.TestAccumulationNoReturnsReceiverChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * A test that the accumulation abstract checker is working correctly, using a simple accumulation * checker without a returns-receiver analysis. */ public class AccumulationNoReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AccumulationNoReturnsReceiverTest(List testFiles) { - super( - testFiles, - TestAccumulationNoReturnsReceiverChecker.class, - "accumulation-norr", - "-encoding", - "UTF-8"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AccumulationNoReturnsReceiverTest(List testFiles) { + super( + testFiles, + TestAccumulationNoReturnsReceiverChecker.class, + "accumulation-norr", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"accumulation-norr", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"accumulation-norr", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java index 273290e5074..870d7b48096 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java @@ -1,34 +1,33 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.testaccumulation.TestAccumulationChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * A test that the accumulation abstract checker is working correctly, using a simple accumulation * checker. */ public class AccumulationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AccumulationTest(List testFiles) { - super( - testFiles, - TestAccumulationChecker.class, - "accumulation", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-encoding", - "UTF-8"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AccumulationTest(List testFiles) { + super( + testFiles, + TestAccumulationChecker.class, + "accumulation", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"accumulation", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"accumulation", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java index 7a6ad6f1bfc..ce05ae7b286 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java @@ -1,23 +1,22 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.aggregate.AggregateOfCompoundChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class AggregateTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AggregateTest(List testFiles) { - super(testFiles, AggregateOfCompoundChecker.class, "aggregate", "-AresolveReflection"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AggregateTest(List testFiles) { + super(testFiles, AggregateOfCompoundChecker.class, "aggregate", "-AresolveReflection"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"aggregate"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"aggregate"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java index 2207ccb24bd..9975c10744f 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java @@ -1,22 +1,21 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class AliasingTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AliasingTest(List testFiles) { - super(testFiles, org.checkerframework.common.aliasing.AliasingChecker.class, "aliasing"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AliasingTest(List testFiles) { + super(testFiles, org.checkerframework.common.aliasing.AliasingChecker.class, "aliasing"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"aliasing", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"aliasing", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java index 016af8b7719..332d6c5ebcc 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java @@ -1,33 +1,32 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** Perform tests of the {@code @AnnotatedFor} annotation with the Subtyping Checker. */ public class AnnotatedForTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AnnotatedForTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.subtyping.SubtypingChecker.class, - "subtyping", - "-Aquals=org.checkerframework.framework.testchecker.util.SubQual,org.checkerframework.framework.testchecker.util.SuperQual", - "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AnnotatedForTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.subtyping.SubtypingChecker.class, + "subtyping", + "-Aquals=org.checkerframework.framework.testchecker.util.SubQual,org.checkerframework.framework.testchecker.util.SuperQual", + "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); + } - /** - * Define the test directories for this test. - * - * @return the test directories - */ - @Parameters - public static String[] getTestDirs() { - return new String[] {"conservative-defaults/annotatedfor"}; - } + /** + * Define the test directories for this test. + * + * @return the test directories + */ + @Parameters + public static String[] getTestDirs() { + return new String[] {"conservative-defaults/annotatedfor"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java index f6c919ecca3..7e583b813d3 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java @@ -6,7 +6,9 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Options; - +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; import org.checkerframework.framework.testchecker.util.AnnoWithStringArg; import org.checkerframework.framework.testchecker.util.Encrypted; import org.checkerframework.javacutil.AnnotationBuilder; @@ -15,310 +17,305 @@ import org.junit.Ignore; import org.junit.Test; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; - public class AnnotationBuilderTest { - private final ProcessingEnvironment env; - - public AnnotationBuilderTest() { - Context context = new Context(); - // Set source and target to 8 - Options options = Options.instance(context); - options.put(Option.SOURCE, "8"); - options.put(Option.TARGET, "8"); - - env = JavacProcessingEnvironment.instance(context); - JavaCompiler javac = JavaCompiler.instance(context); - // Even though source/target are set to 8, the modules in the JavaCompiler - // need to be initialized by setting the list of modules to nil. - javac.initModules(List.nil()); - javac.enterDone(); - } - - @Test - public void createAnnoWithoutValues() { - AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); - // AnnotationMirror anno = - builder.build(); - } - - @Test - public void createAnnoWithoutValues1() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - AnnotationMirror anno = builder.build(); - Assert.assertEquals(0, anno.getElementValues().size()); - } - - @Test - public void createAnnoWithValues0() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("value", "m"); - AnnotationMirror anno = builder.build(); - Assert.assertEquals(1, anno.getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void buildingTwice() { - AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); - builder.build(); - builder.build(); - } - - @Test(expected = BugInCF.class) - public void addingValuesAfterBuilding() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("value", "m"); - // AnnotationMirror anno = - builder.build(); - builder.setValue("value", "n"); - } - - @Test(expected = BugInCF.class) - public void notFoundElements() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("n", "m"); - } - - @Test(expected = BugInCF.class) - public void illegalValue() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("value", 1); - } - - public static @interface A { - int[] numbers(); - } - - public static @interface B { - String[] strings(); - } - - @Test - public void listArrayPrimitive() { - AnnotationBuilder builder = new AnnotationBuilder(env, A.class); - builder.setValue("numbers", new Integer[] {34, 32, 43}); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test - public void listArrayObject() { - AnnotationBuilder builder = new AnnotationBuilder(env, B.class); - builder.setValue("strings", new String[] {"m", "n"}); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void listArrayObjectWrongType() { - AnnotationBuilder builder = new AnnotationBuilder(env, B.class); - builder.setValue("strings", new Object[] {"m", "n", 1}); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void listArrayObjectWrongType1() { - AnnotationBuilder builder = new AnnotationBuilder(env, B.class); - builder.setValue("strings", 1); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - public static @interface Prim { - int a(); - } - - @Test - public void primitiveValue() { - AnnotationBuilder builder = new AnnotationBuilder(env, Prim.class); - builder.setValue("a", 3); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void primitiveValueWithException() { - AnnotationBuilder builder = new AnnotationBuilder(env, A.class); - builder.setValue("a", 3.0); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - // Multiple values - public static @interface Mult { - int a(); - - String b(); - } - - @Test - public void multiple1() { - AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); - builder.setValue("a", 2); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void multiple2() { - AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); - builder.setValue("a", "m"); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test - public void multiple3() { - AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); - builder.setValue("a", 1); - builder.setValue("b", "mark"); - Assert.assertEquals(2, builder.build().getElementValues().size()); - } - - public static @interface ClassElt { - Class value(); - } - - @Test - public void testClassPositive() { - AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); - builder.setValue("value", String.class); - builder.setValue("value", int.class); - builder.setValue("value", int[].class); - builder.setValue("value", void.class); - Object storedValue = - builder.build().getElementValues().values().iterator().next().getValue(); - Assert.assertTrue( - "storedValue is " + storedValue.getClass(), storedValue instanceof TypeMirror); - } - - @Test(expected = BugInCF.class) - public void testClassNegative() { - AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); - builder.setValue("value", 2); - } - - public static @interface RestrictedClassElt { - Class value(); - } - - @Test - public void testRestClassPositive() { - AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); - builder.setValue("value", Integer.class); - } - - // Failing test for now. AnnotationBuilder is a bit permissive - // It doesn't not check type argument subtyping - @Test(expected = BugInCF.class) - @Ignore // bug for now - public void testRetClassNegative() { - AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); - builder.setValue("value", String.class); - } - - enum MyEnum { - OK, - NOT; - } - - enum OtherEnum { - TEST; - } - - public static @interface EnumElt { - MyEnum value(); - } - - @Test - public void testEnumPositive() { - AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); - builder.setValue("value", MyEnum.OK); - builder.setValue("value", MyEnum.NOT); - } - - @Test(expected = BugInCF.class) - public void testEnumNegative() { - AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); - builder.setValue("value", 2); - } - - @Test(expected = BugInCF.class) - public void testEnumNegative2() { - AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); - builder.setValue("value", OtherEnum.TEST); - } - - public static @interface Anno { - String value(); - - int[] can(); - } - - @Test - public void testToString1() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno", - builder.build().toString()); - } - - @Test - public void testToString2() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("value", "string"); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(\"string\")", - builder.build().toString()); - } - - @Test - public void testToString3() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("can", new Object[] {1}); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(can={1})", - builder.build().toString()); - } - - @Test - public void testToString4() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("value", "m"); - builder.setValue("can", new Object[] {1}); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" - + "(value=\"m\", can={1})", - builder.build().toString()); - } - - @Test - public void testToString5() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("can", new Object[] {1}); - builder.setValue("value", "m"); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" - + "(can={1}, value=\"m\")", - builder.build().toString()); - } - - public static @interface MyAnno {} - - public static @interface ContainingAnno { - MyAnno value(); - } - - @Test - public void testAnnoAsArgPositive() { - AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), MyAnno.class); - AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); - builder.setValue("value", anno); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.ContainingAnno(@org.checkerframework.framework.test.junit.AnnotationBuilderTest.MyAnno)", - builder.build().toString()); - } - - @Test(expected = BugInCF.class) - public void testAnnoAsArgNegative() { - AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), Anno.class); - AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); - builder.setValue("value", anno); - } + private final ProcessingEnvironment env; + + public AnnotationBuilderTest() { + Context context = new Context(); + // Set source and target to 8 + Options options = Options.instance(context); + options.put(Option.SOURCE, "8"); + options.put(Option.TARGET, "8"); + + env = JavacProcessingEnvironment.instance(context); + JavaCompiler javac = JavaCompiler.instance(context); + // Even though source/target are set to 8, the modules in the JavaCompiler + // need to be initialized by setting the list of modules to nil. + javac.initModules(List.nil()); + javac.enterDone(); + } + + @Test + public void createAnnoWithoutValues() { + AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); + // AnnotationMirror anno = + builder.build(); + } + + @Test + public void createAnnoWithoutValues1() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + AnnotationMirror anno = builder.build(); + Assert.assertEquals(0, anno.getElementValues().size()); + } + + @Test + public void createAnnoWithValues0() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("value", "m"); + AnnotationMirror anno = builder.build(); + Assert.assertEquals(1, anno.getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void buildingTwice() { + AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); + builder.build(); + builder.build(); + } + + @Test(expected = BugInCF.class) + public void addingValuesAfterBuilding() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("value", "m"); + // AnnotationMirror anno = + builder.build(); + builder.setValue("value", "n"); + } + + @Test(expected = BugInCF.class) + public void notFoundElements() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("n", "m"); + } + + @Test(expected = BugInCF.class) + public void illegalValue() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("value", 1); + } + + public static @interface A { + int[] numbers(); + } + + public static @interface B { + String[] strings(); + } + + @Test + public void listArrayPrimitive() { + AnnotationBuilder builder = new AnnotationBuilder(env, A.class); + builder.setValue("numbers", new Integer[] {34, 32, 43}); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test + public void listArrayObject() { + AnnotationBuilder builder = new AnnotationBuilder(env, B.class); + builder.setValue("strings", new String[] {"m", "n"}); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void listArrayObjectWrongType() { + AnnotationBuilder builder = new AnnotationBuilder(env, B.class); + builder.setValue("strings", new Object[] {"m", "n", 1}); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void listArrayObjectWrongType1() { + AnnotationBuilder builder = new AnnotationBuilder(env, B.class); + builder.setValue("strings", 1); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + public static @interface Prim { + int a(); + } + + @Test + public void primitiveValue() { + AnnotationBuilder builder = new AnnotationBuilder(env, Prim.class); + builder.setValue("a", 3); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void primitiveValueWithException() { + AnnotationBuilder builder = new AnnotationBuilder(env, A.class); + builder.setValue("a", 3.0); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + // Multiple values + public static @interface Mult { + int a(); + + String b(); + } + + @Test + public void multiple1() { + AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); + builder.setValue("a", 2); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void multiple2() { + AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); + builder.setValue("a", "m"); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test + public void multiple3() { + AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); + builder.setValue("a", 1); + builder.setValue("b", "mark"); + Assert.assertEquals(2, builder.build().getElementValues().size()); + } + + public static @interface ClassElt { + Class value(); + } + + @Test + public void testClassPositive() { + AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); + builder.setValue("value", String.class); + builder.setValue("value", int.class); + builder.setValue("value", int[].class); + builder.setValue("value", void.class); + Object storedValue = builder.build().getElementValues().values().iterator().next().getValue(); + Assert.assertTrue( + "storedValue is " + storedValue.getClass(), storedValue instanceof TypeMirror); + } + + @Test(expected = BugInCF.class) + public void testClassNegative() { + AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); + builder.setValue("value", 2); + } + + public static @interface RestrictedClassElt { + Class value(); + } + + @Test + public void testRestClassPositive() { + AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); + builder.setValue("value", Integer.class); + } + + // Failing test for now. AnnotationBuilder is a bit permissive + // It doesn't not check type argument subtyping + @Test(expected = BugInCF.class) + @Ignore // bug for now + public void testRetClassNegative() { + AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); + builder.setValue("value", String.class); + } + + enum MyEnum { + OK, + NOT; + } + + enum OtherEnum { + TEST; + } + + public static @interface EnumElt { + MyEnum value(); + } + + @Test + public void testEnumPositive() { + AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); + builder.setValue("value", MyEnum.OK); + builder.setValue("value", MyEnum.NOT); + } + + @Test(expected = BugInCF.class) + public void testEnumNegative() { + AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); + builder.setValue("value", 2); + } + + @Test(expected = BugInCF.class) + public void testEnumNegative2() { + AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); + builder.setValue("value", OtherEnum.TEST); + } + + public static @interface Anno { + String value(); + + int[] can(); + } + + @Test + public void testToString1() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno", + builder.build().toString()); + } + + @Test + public void testToString2() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("value", "string"); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(\"string\")", + builder.build().toString()); + } + + @Test + public void testToString3() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("can", new Object[] {1}); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(can={1})", + builder.build().toString()); + } + + @Test + public void testToString4() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("value", "m"); + builder.setValue("can", new Object[] {1}); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" + + "(value=\"m\", can={1})", + builder.build().toString()); + } + + @Test + public void testToString5() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("can", new Object[] {1}); + builder.setValue("value", "m"); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" + + "(can={1}, value=\"m\")", + builder.build().toString()); + } + + public static @interface MyAnno {} + + public static @interface ContainingAnno { + MyAnno value(); + } + + @Test + public void testAnnoAsArgPositive() { + AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), MyAnno.class); + AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); + builder.setValue("value", anno); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.ContainingAnno(@org.checkerframework.framework.test.junit.AnnotationBuilderTest.MyAnno)", + builder.build().toString()); + } + + @Test(expected = BugInCF.class) + public void testAnnoAsArgNegative() { + AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), Anno.class); + AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); + builder.setValue("value", anno); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java index 1417468174b..90953ebca3f 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java @@ -1,23 +1,22 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** Tests the ClassVal Checker. */ public class ClassValTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ClassValTest(List testFiles) { - super(testFiles, org.checkerframework.common.reflection.ClassValChecker.class, "classval"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ClassValTest(List testFiles) { + super(testFiles, org.checkerframework.common.reflection.ClassValChecker.class, "classval"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"classval"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"classval"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java index 7f669d9a293..b287eefe2e9 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.compound.CompoundChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the compound checker design pattern. */ public class CompoundCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public CompoundCheckerTest(List testFiles) { - super(testFiles, CompoundChecker.class, "compound-checker"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public CompoundCheckerTest(List testFiles) { + super(testFiles, CompoundChecker.class, "compound-checker"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"compound-checker"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"compound-checker"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java index aa0018635d9..541335aea85 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.defaulting.DefaultingLowerBoundChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Created by jburke on 9/29/14. */ public class DefaultingLowerBoundTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public DefaultingLowerBoundTest(List testFiles) { - super(testFiles, DefaultingLowerBoundChecker.class, "defaulting"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public DefaultingLowerBoundTest(List testFiles) { + super(testFiles, DefaultingLowerBoundChecker.class, "defaulting"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"defaulting/lowerbound"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"defaulting/lowerbound"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java index a5fb56c473a..c0a00feaa5c 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.defaulting.DefaultingUpperBoundChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Created by jburke on 9/29/14. */ public class DefaultingUpperBoundTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public DefaultingUpperBoundTest(List testFiles) { - super(testFiles, DefaultingUpperBoundChecker.class, "defaulting"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public DefaultingUpperBoundTest(List testFiles) { + super(testFiles, DefaultingUpperBoundChecker.class, "defaulting"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"defaulting/upperbound"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"defaulting/upperbound"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java index f269c5bf3b2..df6cf4e0408 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java @@ -1,23 +1,22 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.flowexpression.FlowExpressionChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class FlowExpressionCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public FlowExpressionCheckerTest(List testFiles) { - super(testFiles, FlowExpressionChecker.class, "flowexpression"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public FlowExpressionCheckerTest(List testFiles) { + super(testFiles, FlowExpressionChecker.class, "flowexpression"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"flowexpression", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"flowexpression", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java index adcb32215b0..92f32fad51b 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.FlowTestChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** */ public class FlowTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public FlowTest(List testFiles) { - super(testFiles, FlowTestChecker.class, "flow", "-AcheckPurityAnnotations"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public FlowTest(List testFiles) { + super(testFiles, FlowTestChecker.class, "flow", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"flow", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"flow", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java index aff43610c89..53b37a0f222 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java @@ -1,23 +1,22 @@ package org.checkerframework.framework.test.junit; +import java.io.File; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.checkerframework.framework.testchecker.util.EvenOddChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; - /** JUnit tests for the Checker Framework, using the {@link EvenOddChecker}. */ // See framework/tests/framework/README for why this is a CFPerFileTest. // See FrameworkTest for tests that do not contain Java errors. public class FrameworkJavacErrorsTest extends CheckerFrameworkPerFileTest { - public FrameworkJavacErrorsTest(File testFile) { - super(testFile, EvenOddChecker.class, "framework"); - } + public FrameworkJavacErrorsTest(File testFile) { + super(testFile, EvenOddChecker.class, "framework"); + } - @Parameters - public static String[] getTestDirs() { - // Do not include all-systems! - return new String[] {"framework-javac-errors"}; - } + @Parameters + public static String[] getTestDirs() { + // Do not include all-systems! + return new String[] {"framework-javac-errors"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java index 066b2391612..37b57e80601 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java @@ -1,22 +1,21 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.EvenOddChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** JUnit tests for the Checker Framework, using the {@link EvenOddChecker}. */ // See FrameworkJavacErrorsTest for tests that can contain Java errors. public class FrameworkTest extends CheckerFrameworkPerDirectoryTest { - public FrameworkTest(List testFiles) { - super(testFiles, EvenOddChecker.class, "framework"); - } + public FrameworkTest(List testFiles) { + super(testFiles, EvenOddChecker.class, "framework"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"framework", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"framework", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java index 0d10232d123..aa912eac64f 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java @@ -1,28 +1,27 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.h1h2checker.H1H2Checker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class H1H2CheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public H1H2CheckerTest(List testFiles) { - super( - testFiles, - H1H2Checker.class, - "h1h2checker", - "-Astubs=tests/h1h2checker/h1h2checker.astub", - "-AcheckEnclosingExpr"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public H1H2CheckerTest(List testFiles) { + super( + testFiles, + H1H2Checker.class, + "h1h2checker", + "-Astubs=tests/h1h2checker/h1h2checker.astub", + "-AcheckEnclosingExpr"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"h1h2checker"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"h1h2checker"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java index 048e87ed96b..e1ee14a77a1 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java @@ -1,25 +1,24 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.initializedfields.InitializedFieldsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class InitializedFieldsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a InitializedFieldsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InitializedFieldsTest(List testFiles) { - super(testFiles, InitializedFieldsChecker.class, "initialized-fields"); - } + /** + * Create a InitializedFieldsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InitializedFieldsTest(List testFiles) { + super(testFiles, InitializedFieldsChecker.class, "initialized-fields"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"initialized-fields", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"initialized-fields", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java index 1bf1bc032ea..5a3d4431c20 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java @@ -1,33 +1,32 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class InitializedFieldsValueTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a InitializedFieldsValueTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InitializedFieldsValueTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "org.checkerframework.common.initializedfields.InitializedFieldsChecker", - "org.checkerframework.common.value.ValueChecker"), - "initialized-fields-value", - Collections.emptyList() // classpathextra - ); - } + /** + * Create a InitializedFieldsValueTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InitializedFieldsValueTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "org.checkerframework.common.initializedfields.InitializedFieldsChecker", + "org.checkerframework.common.value.ValueChecker"), + "initialized-fields-value", + Collections.emptyList() // classpathextra + ); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"initialized-fields-value", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"initialized-fields-value", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java index b559f8b03a4..0c813472bd8 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.lubglb.LubGlbChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** */ public class LubGlbTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public LubGlbTest(List testFiles) { - super(testFiles, LubGlbChecker.class, "lubglb"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public LubGlbTest(List testFiles) { + super(testFiles, LubGlbChecker.class, "lubglb"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"lubglb"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"lubglb"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java index 76278b69798..30e871cc79c 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java @@ -1,26 +1,22 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** Tests the MethodVal Checker. */ public class MethodValTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public MethodValTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.reflection.MethodValChecker.class, - "methodval"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public MethodValTest(List testFiles) { + super(testFiles, org.checkerframework.common.reflection.MethodValChecker.class, "methodval"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"methodval"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"methodval"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java index cfd2b025a3f..63737a0f26f 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.nontopdefault.NTDChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests the NonTopDefault Checker. */ public class NonTopDefaultTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public NonTopDefaultTest(List testFiles) { - super(testFiles, NTDChecker.class, "nontopdefault"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public NonTopDefaultTest(List testFiles) { + super(testFiles, NTDChecker.class, "nontopdefault"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nontopdefault"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nontopdefault"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java index bd3161418cd..fcef4d729ec 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java @@ -1,29 +1,28 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.FlowTestChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests for the {@code -AsuggestPureMethods} command-line argument. */ public class PuritySuggestionsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public PuritySuggestionsTest(List testFiles) { - super( - testFiles, - FlowTestChecker.class, - "flow", - "-AsuggestPureMethods", - "-AcheckPurityAnnotations"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public PuritySuggestionsTest(List testFiles) { + super( + testFiles, + FlowTestChecker.class, + "flow", + "-AsuggestPureMethods", + "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"purity-suggestions"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"purity-suggestions"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java index 25143bb7c05..ef9b2f9c07e 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java @@ -1,709 +1,668 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.common.value.util.Range; -import org.junit.Assert; -import org.junit.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; - import javax.lang.model.type.TypeKind; +import org.checkerframework.common.value.util.Range; +import org.junit.Assert; +import org.junit.Test; /** This class tests the Range class, independent of the Value Checker. */ public class RangeTest { - // These sets of values may be excessively long; perhaps trim them down later. - - long[] rangeBounds = { - Long.MIN_VALUE, - Long.MIN_VALUE + 1, - Integer.MIN_VALUE - 1000L, - Integer.MIN_VALUE - 10L, - Integer.MIN_VALUE, - Integer.MIN_VALUE + 1L, - Short.MIN_VALUE - 1000L, - Short.MIN_VALUE - 10L, - Short.MIN_VALUE, - Short.MIN_VALUE + 1L, - Character.MIN_VALUE - 1000L, - Character.MIN_VALUE - 10L, - Character.MIN_VALUE, // 0L - Character.MIN_VALUE + 1L, - Byte.MIN_VALUE - 1000L, - Byte.MIN_VALUE - 10L, - Byte.MIN_VALUE, - Byte.MIN_VALUE + 1L, - Byte.MAX_VALUE - 1L, - Byte.MAX_VALUE, - Byte.MAX_VALUE + 10L, - Byte.MAX_VALUE + 1000L, - Short.MAX_VALUE - 1L, - Short.MAX_VALUE, - Short.MAX_VALUE + 10L, - Short.MAX_VALUE + 1000L, - Character.MAX_VALUE - 1L, - Character.MAX_VALUE, - Character.MAX_VALUE + 10L, - Character.MAX_VALUE + 1000L, - Integer.MAX_VALUE - 1, - Integer.MAX_VALUE, - Integer.MAX_VALUE + 10L, - Integer.MAX_VALUE + 1000L, - Long.MAX_VALUE - 1, - Long.MAX_VALUE - }; - long[] values = { - Long.MIN_VALUE, - Long.MIN_VALUE + 1L, - Integer.MIN_VALUE, - Integer.MIN_VALUE + 1L, - Short.MIN_VALUE, - Short.MIN_VALUE + 1L, - Byte.MIN_VALUE, - Byte.MIN_VALUE + 1L, - -8L, - -4L, - -2L, - -1L, - Character.MIN_VALUE, // 0L - Character.MIN_VALUE + 1L, // 1L - 2L, - 4L, - 8L, - Byte.MAX_VALUE - 1L, - Byte.MAX_VALUE, - Short.MAX_VALUE - 1L, - Short.MAX_VALUE, - Character.MAX_VALUE - 1L, - Character.MAX_VALUE, - Integer.MAX_VALUE - 1L, - Integer.MAX_VALUE, - Long.MAX_VALUE - 1L, - Long.MAX_VALUE - }; - - /** Contains a Range for every combination of values in rangeBounds. */ - Range[] ranges; - - static final long INT_WIDTH = (long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE + 1; - - static final long SHORT_WIDTH = Short.MAX_VALUE - Short.MIN_VALUE + 1; - - static final long BYTE_WIDTH = Byte.MAX_VALUE - Byte.MIN_VALUE + 1; - - static final long CHAR_WIDTH = Character.MAX_VALUE - Character.MIN_VALUE + 1; - - public RangeTest() { - // Initialize the ranges list to every combination of values in rangeBounds. - List rangesList = new ArrayList<>(); - for (long lowerbound : rangeBounds) { - for (long upperbound : rangeBounds) { - if (lowerbound <= upperbound) { - rangesList.add(Range.create(lowerbound, upperbound)); - } - } - } - ranges = rangesList.toArray(new Range[rangesList.size()]); - } - - /** The element is a member of the range. */ - static class RangeAndElement { - Range range; - long element; - - RangeAndElement(Range range, long element) { - if (!range.contains(element)) { - throw new IllegalArgumentException(); - } - this.range = range; - this.element = element; - } - } - - static class RangeAndTwoElements { - Range range; - long a; - long b; - } - - ValuesInRangeIterator valuesInRange(Range r) { - return new ValuesInRangeIterator(r); - } - - RangeAndElementIterator rangeAndElements() { - return new RangeAndElementIterator(); - } - - @SuppressWarnings("IterableAndIterator") // TODO - class RangeAndElementIterator implements Iterator, Iterable { - // This is the index of the range that is currently being examined. - // It is in [0..ranges.length]. - int ri; - Range range; - ValuesInRangeIterator vi; - RangeAndElement nextValue; - boolean nextValueValid = false; - - public RangeAndElementIterator() { - ri = 0; - range = ranges[ri]; - vi = new ValuesInRangeIterator(range); - } - - @Override - public RangeAndElement next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - nextValueValid = false; - return nextValue; - } - - @Override - public boolean hasNext() { - if (nextValueValid) { - return true; - } - while (!vi.hasNext()) { - ri++; - if (ri == ranges.length) { - return false; - } - range = ranges[ri]; - vi = new ValuesInRangeIterator(range); - } - nextValue = new RangeAndElement(range, vi.next()); - nextValueValid = true; - return true; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator iterator() { - return this; - } - } - - @SuppressWarnings("IterableAndIterator") // TODO - class ValuesInRangeIterator implements Iterator, Iterable { - - Range range; - // This is the first index that has NOT been examined. It is in [0..values.length]. - int i = 0; - long nextValue; - boolean nextValueValid = false; - - @SuppressWarnings("StaticAssignmentInConstructor") - public ValuesInRangeIterator(Range range) { - this.range = range; - Range.ignoreOverflow = false; - } - - @Override - public Long next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - nextValueValid = false; - return nextValue; - } - - @Override - public boolean hasNext() { - if (nextValueValid) { - return true; - } - while (i < values.length) { - nextValue = values[i]; - i++; - if (range.contains(nextValue)) { - nextValueValid = true; - break; - } - } - return nextValueValid; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator iterator() { - return this; - } - } - - @Test - public void testIntRange() { - for (Range range : ranges) { - Range result = range.intRange(); - for (long value : values) { - if (value < range.from + INT_WIDTH - && value > range.to - INT_WIDTH - && (Math.abs(range.from) - 1) / Integer.MIN_VALUE - == (Math.abs(range.to) - 1) / Integer.MIN_VALUE) { - // filter out test data that would cause Range.intRange to return INT_EVERYTHING - int intValue = (int) value; - assert (range.contains(value) && result.contains(intValue)) - || (!range.contains(value) && !result.contains(intValue)) - : String.format( - "Range.intRange failure: %s => %s; witness = %s", - range, result, intValue); - } - } - } - } - - @Test - public void testShortRange() { - for (Range range : ranges) { - Range result = range.shortRange(); - for (long value : values) { - if (value < range.from + SHORT_WIDTH - && value > range.to - SHORT_WIDTH - && (Math.abs(range.from) - 1) / Short.MIN_VALUE - == (Math.abs(range.to) - 1) / Short.MIN_VALUE) { - // filter out test data that would cause Range.shortRange to return - // SHORT_EVERYTHING - short shortValue = (short) value; - assert (range.contains(value) && result.contains(shortValue)) - || (!range.contains(value) && !result.contains(shortValue)) - : String.format( - "Range.shortRange failure: %s => %s; witness = %s", - range, result, shortValue); - } - } - } - } - - @Test - public void testCharRange() { - Range.ignoreOverflow = false; - for (Range range : ranges) { - Range result = range.charRange(); - for (long value : values) { - if (value < range.from + CHAR_WIDTH - && value > range.to - CHAR_WIDTH - && (Math.abs(range.from + Short.MIN_VALUE) - 1) / Short.MIN_VALUE - == (Math.abs(range.to + Short.MIN_VALUE) - 1) / Short.MIN_VALUE) { - // filter out test data that would cause Range.CharRange to return - // CHAR_EVERYTHING - // char range interval is a right shift of the short range interval - char charValue = (char) value; - assert (range.contains(value) && result.contains(charValue)) - || (!range.contains(value) && !result.contains(charValue)) - : String.format( - "Range.byteRange failure: %s => %s; witness = %s", - range, result, charValue); - } - } - } - } - - @Test - public void testByteRange() { - for (Range range : ranges) { - Range result = range.byteRange(); - for (long value : values) { - if (value < range.from + BYTE_WIDTH - && value > range.to - BYTE_WIDTH - && (Math.abs(range.from) - 1) / Byte.MIN_VALUE - == (Math.abs(range.to) - 1) / Byte.MIN_VALUE) { - // filter out test data that would cause Range.ByteRange to return - // BYTE_EVERYTHING - byte byteValue = (byte) value; - assert (range.contains(value) && result.contains(byteValue)) - || (!range.contains(value) && !result.contains(byteValue)) - : String.format( - "Range.byteRange failure: %s => %s; witness = %s", - range, result, byteValue); - } - } - } - - Range r1 = Range.create(5, 1000); - Range r2 = Range.create(1024 + 17, 1024 + 22); - Range r3 = Range.create(5, Byte.MAX_VALUE + 2); - - Range.ignoreOverflow = true; - - assert r1.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); - assert r2.byteRange().equals(Range.create(Byte.MAX_VALUE, Byte.MAX_VALUE)); - assert r3.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); - - Range.ignoreOverflow = false; - - assert r1.byteRange().equals(Range.BYTE_EVERYTHING); - assert r2.byteRange().equals(Range.create(17, 22)); - assert r3.byteRange().equals(Range.BYTE_EVERYTHING); - } - - @Test - public void testUnion() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - Range result = range1.union(range2); - for (long value : values) { - if (range1.contains(value) || range2.contains(value)) { - assert result.contains(value) - : String.format( - "Range.union failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - } - - @Test - public void testIntersect() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - Range result = range1.intersect(range2); - for (long value : values) { - assert ((range1.contains(value) && range2.contains(value)) - == result.contains(value)) - : String.format( - "Range.intersect failure: %s %s => %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testPlus() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.plus(re2.range); - assert result.contains(re1.element + re2.element) - : String.format( - "Range.plus failure: %s %s => %s; witnesses %s + %s => %s", - re1.range, - re2.range, - result, - re1.element, - re2.element, - re1.element + re2.element); - } - } - } - - @Test - public void testMinus() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.minus(re2.range); - assert result.contains(re1.element - re2.element) - : String.format( - "Range.minus failure: %s %s => %s; witnesses %s - %s => %s", - re1.range, - re2.range, - result, - re1.element, - re2.element, - re1.element - re2.element); - } - } - } - - @Test - public void testTimes() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.times(re2.range); - assert result.contains(re1.element * re2.element) - : String.format( - "Range.times failure: %s %s => %s; witnesses %s * %s => %s", - re1.range, - re2.range, - result, - re1.element, - re2.element, - re1.element * re2.element); - } - } - } - - @Test - public void testDivide() { - assert Range.create(1, 2).divide(Range.create(0, 0)) == Range.NOTHING; - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - if (re2.element == 0) { - continue; - } - Range result = re1.range.divide(re2.range); - Long witness = re1.element / re2.element; - assert result.contains(witness) - : String.format( - "Range.divide failure: %s %s => %s; witnesses %s / %s => %s", - re1.range, re2.range, result, re1.element, re2.element, witness); - } - } - } - - @Test - public void testRemainder() { - assert Range.create(1, 2).remainder(Range.create(0, 0)) == Range.NOTHING; - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - if (re2.element == 0) { - continue; - } - Range result = re1.range.remainder(re2.range); - Long witness = re1.element % re2.element; - assert result.contains(witness) - : String.format( - "Range.remainder failure: %s %s => %s; witnesses %s %% %s => %s", - re1.range, re2.range, result, re1.element, re2.element, witness); - } - } - } - - @Test - public void testShiftLeft() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.shiftLeft(re2.range); - assert result.contains(re1.element << re2.element) - : String.format( - "Range.shiftLeft failure: %s %s => %s; witnesses %s << %s => %s", - re1.range, - re2.range, - result, - re1.element, - re2.element, - re1.element << re2.element); - } - } - } - - @Test - public void testSignedShiftRight() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.signedShiftRight(re2.range); - assert result.contains(re1.element >> re2.element) - : String.format( - "Range.signedShiftRight failure: %s %s => %s; witnesses %s >> %s =>" - + " %s", - re1.range, - re2.range, - result, - re1.element, - re2.element, - re1.element >> re2.element); - } - } - } - - @Test - public void testUnsignedShiftRight() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.unsignedShiftRight(re2.range); - if (re1.range.from >= 0) { - assert result.contains(re1.element >>> re2.element) - : String.format( - "Range.unsignedShiftRight failure: %s %s => %s; witnesses %s >>" - + " %s => %s", - re1.range, - re2.range, - result, - re1.element, - re2.element, - re1.element >> re2.element); - } else { - assert result == Range.EVERYTHING; - } - } - } - } - - @Test - public void testUnaryPlus() { - for (RangeAndElement re : rangeAndElements()) { - Range result = re.range.unaryPlus(); - assert result.contains(+re.element) - : String.format( - "Range.unaryPlus failure: %s => %s; witness %s => %s", - re.range, result, re.element, +re.element); - } - } - - @Test - public void testUnaryMinus() { - for (RangeAndElement re : rangeAndElements()) { - Range result = re.range.unaryMinus(); - assert result.contains(-re.element) - : String.format( - "Range.unaryMinus failure: %s => %s; witness %s => %s", - re.range, result, re.element, -re.element); - } - } - - @Test - public void testBitwiseComplement() { - for (RangeAndElement re : rangeAndElements()) { - Range result = re.range.bitwiseComplement(); - assert result.contains(~re.element) - : String.format( - "Range.bitwiseComplement failure: %s => %s; witness %s => %s", - re.range, result, re.element, ~re.element); - } - } - - @Test - public void testBitwiseAnd() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result1 = re1.range.bitwiseAnd(re2.range); - Range result2 = re2.range.bitwiseAnd(re1.range); - if (re1.range.isConstant() || re2.range.isConstant()) { - Long witness = re1.element & re2.element; - assert result1.from == result2.from; - assert result1.to == result2.to; - assert result1.contains(witness) - : String.format( - "Range.bitwiseAnd failure: %s %s => %s; witnesses %s & %s =>" - + " %s", - re1.range, - re2.range, - result1, - re1.element, - re2.element, - witness); - } else { - assert result1 == Range.EVERYTHING; - assert result2 == Range.EVERYTHING; - } - } - } - } - - @Test - public void testLessThan() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineLessThan(range2); - assert (value >= range2.to - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineLessThan failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testLessThanEq() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineLessThanEq(range2); - assert (value > range2.to - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineLessThanEq failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testGreaterThan() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineGreaterThan(range2); - assert (value <= range2.from - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineGreaterThan failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testGreaterThanEq() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineGreaterThanEq(range2); - assert (value < range2.from - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineGreaterThanEq failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testEqualTo() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineEqualTo(range2); - assert (value < range2.from || value > range2.to) - ? !result.contains(value) - : (range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineEqualTo failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testFactoryLongLong() { - Assert.assertEquals((long) 1, Range.create(1, 2).from); - Assert.assertEquals((long) 2, Range.create(1, 2).to); - } - - @Test - public void testFactoryList() { - Assert.assertEquals((long) 1, Range.create(Arrays.asList(1, 2, 3)).from); - Assert.assertEquals((long) 3, Range.create(Arrays.asList(1, 2, 3)).to); - Assert.assertEquals((long) 1, Range.create(Arrays.asList(3, 2, 1)).from); - Assert.assertEquals((long) 3, Range.create(Arrays.asList(3, 2, 1)).to); - Assert.assertEquals(Range.NOTHING, Range.create(Collections.emptyList())); - Assert.assertTrue(Range.NOTHING == Range.create(Collections.emptyList())); - } - - @Test - public void testFactoryTypeKind() { - Assert.assertEquals(Range.BYTE_EVERYTHING, Range.create(TypeKind.BYTE)); - Assert.assertEquals(Range.INT_EVERYTHING, Range.create(TypeKind.INT)); - Assert.assertEquals(Range.SHORT_EVERYTHING, Range.create(TypeKind.SHORT)); - Assert.assertEquals(Range.CHAR_EVERYTHING, Range.create(TypeKind.CHAR)); - Assert.assertEquals(Range.LONG_EVERYTHING, Range.create(TypeKind.LONG)); - } - - @Test(expected = IllegalArgumentException.class) - public void testFactoryTypeKindFailure() { - Range.create(TypeKind.FLOAT); - } + // These sets of values may be excessively long; perhaps trim them down later. + + long[] rangeBounds = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1, + Integer.MIN_VALUE - 1000L, + Integer.MIN_VALUE - 10L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L, + Short.MIN_VALUE - 1000L, + Short.MIN_VALUE - 10L, + Short.MIN_VALUE, + Short.MIN_VALUE + 1L, + Character.MIN_VALUE - 1000L, + Character.MIN_VALUE - 10L, + Character.MIN_VALUE, // 0L + Character.MIN_VALUE + 1L, + Byte.MIN_VALUE - 1000L, + Byte.MIN_VALUE - 10L, + Byte.MIN_VALUE, + Byte.MIN_VALUE + 1L, + Byte.MAX_VALUE - 1L, + Byte.MAX_VALUE, + Byte.MAX_VALUE + 10L, + Byte.MAX_VALUE + 1000L, + Short.MAX_VALUE - 1L, + Short.MAX_VALUE, + Short.MAX_VALUE + 10L, + Short.MAX_VALUE + 1000L, + Character.MAX_VALUE - 1L, + Character.MAX_VALUE, + Character.MAX_VALUE + 10L, + Character.MAX_VALUE + 1000L, + Integer.MAX_VALUE - 1, + Integer.MAX_VALUE, + Integer.MAX_VALUE + 10L, + Integer.MAX_VALUE + 1000L, + Long.MAX_VALUE - 1, + Long.MAX_VALUE + }; + long[] values = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L, + Short.MIN_VALUE, + Short.MIN_VALUE + 1L, + Byte.MIN_VALUE, + Byte.MIN_VALUE + 1L, + -8L, + -4L, + -2L, + -1L, + Character.MIN_VALUE, // 0L + Character.MIN_VALUE + 1L, // 1L + 2L, + 4L, + 8L, + Byte.MAX_VALUE - 1L, + Byte.MAX_VALUE, + Short.MAX_VALUE - 1L, + Short.MAX_VALUE, + Character.MAX_VALUE - 1L, + Character.MAX_VALUE, + Integer.MAX_VALUE - 1L, + Integer.MAX_VALUE, + Long.MAX_VALUE - 1L, + Long.MAX_VALUE + }; + + /** Contains a Range for every combination of values in rangeBounds. */ + Range[] ranges; + + static final long INT_WIDTH = (long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE + 1; + + static final long SHORT_WIDTH = Short.MAX_VALUE - Short.MIN_VALUE + 1; + + static final long BYTE_WIDTH = Byte.MAX_VALUE - Byte.MIN_VALUE + 1; + + static final long CHAR_WIDTH = Character.MAX_VALUE - Character.MIN_VALUE + 1; + + public RangeTest() { + // Initialize the ranges list to every combination of values in rangeBounds. + List rangesList = new ArrayList<>(); + for (long lowerbound : rangeBounds) { + for (long upperbound : rangeBounds) { + if (lowerbound <= upperbound) { + rangesList.add(Range.create(lowerbound, upperbound)); + } + } + } + ranges = rangesList.toArray(new Range[rangesList.size()]); + } + + /** The element is a member of the range. */ + static class RangeAndElement { + Range range; + long element; + + RangeAndElement(Range range, long element) { + if (!range.contains(element)) { + throw new IllegalArgumentException(); + } + this.range = range; + this.element = element; + } + } + + static class RangeAndTwoElements { + Range range; + long a; + long b; + } + + ValuesInRangeIterator valuesInRange(Range r) { + return new ValuesInRangeIterator(r); + } + + RangeAndElementIterator rangeAndElements() { + return new RangeAndElementIterator(); + } + + @SuppressWarnings("IterableAndIterator") // TODO + class RangeAndElementIterator implements Iterator, Iterable { + // This is the index of the range that is currently being examined. + // It is in [0..ranges.length]. + int ri; + Range range; + ValuesInRangeIterator vi; + RangeAndElement nextValue; + boolean nextValueValid = false; + + public RangeAndElementIterator() { + ri = 0; + range = ranges[ri]; + vi = new ValuesInRangeIterator(range); + } + + @Override + public RangeAndElement next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + nextValueValid = false; + return nextValue; + } + + @Override + public boolean hasNext() { + if (nextValueValid) { + return true; + } + while (!vi.hasNext()) { + ri++; + if (ri == ranges.length) { + return false; + } + range = ranges[ri]; + vi = new ValuesInRangeIterator(range); + } + nextValue = new RangeAndElement(range, vi.next()); + nextValueValid = true; + return true; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return this; + } + } + + @SuppressWarnings("IterableAndIterator") // TODO + class ValuesInRangeIterator implements Iterator, Iterable { + + Range range; + // This is the first index that has NOT been examined. It is in [0..values.length]. + int i = 0; + long nextValue; + boolean nextValueValid = false; + + @SuppressWarnings("StaticAssignmentInConstructor") + public ValuesInRangeIterator(Range range) { + this.range = range; + Range.ignoreOverflow = false; + } + + @Override + public Long next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + nextValueValid = false; + return nextValue; + } + + @Override + public boolean hasNext() { + if (nextValueValid) { + return true; + } + while (i < values.length) { + nextValue = values[i]; + i++; + if (range.contains(nextValue)) { + nextValueValid = true; + break; + } + } + return nextValueValid; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return this; + } + } + + @Test + public void testIntRange() { + for (Range range : ranges) { + Range result = range.intRange(); + for (long value : values) { + if (value < range.from + INT_WIDTH + && value > range.to - INT_WIDTH + && (Math.abs(range.from) - 1) / Integer.MIN_VALUE + == (Math.abs(range.to) - 1) / Integer.MIN_VALUE) { + // filter out test data that would cause Range.intRange to return INT_EVERYTHING + int intValue = (int) value; + assert (range.contains(value) && result.contains(intValue)) + || (!range.contains(value) && !result.contains(intValue)) + : String.format( + "Range.intRange failure: %s => %s; witness = %s", range, result, intValue); + } + } + } + } + + @Test + public void testShortRange() { + for (Range range : ranges) { + Range result = range.shortRange(); + for (long value : values) { + if (value < range.from + SHORT_WIDTH + && value > range.to - SHORT_WIDTH + && (Math.abs(range.from) - 1) / Short.MIN_VALUE + == (Math.abs(range.to) - 1) / Short.MIN_VALUE) { + // filter out test data that would cause Range.shortRange to return + // SHORT_EVERYTHING + short shortValue = (short) value; + assert (range.contains(value) && result.contains(shortValue)) + || (!range.contains(value) && !result.contains(shortValue)) + : String.format( + "Range.shortRange failure: %s => %s; witness = %s", range, result, shortValue); + } + } + } + } + + @Test + public void testCharRange() { + Range.ignoreOverflow = false; + for (Range range : ranges) { + Range result = range.charRange(); + for (long value : values) { + if (value < range.from + CHAR_WIDTH + && value > range.to - CHAR_WIDTH + && (Math.abs(range.from + Short.MIN_VALUE) - 1) / Short.MIN_VALUE + == (Math.abs(range.to + Short.MIN_VALUE) - 1) / Short.MIN_VALUE) { + // filter out test data that would cause Range.CharRange to return + // CHAR_EVERYTHING + // char range interval is a right shift of the short range interval + char charValue = (char) value; + assert (range.contains(value) && result.contains(charValue)) + || (!range.contains(value) && !result.contains(charValue)) + : String.format( + "Range.byteRange failure: %s => %s; witness = %s", range, result, charValue); + } + } + } + } + + @Test + public void testByteRange() { + for (Range range : ranges) { + Range result = range.byteRange(); + for (long value : values) { + if (value < range.from + BYTE_WIDTH + && value > range.to - BYTE_WIDTH + && (Math.abs(range.from) - 1) / Byte.MIN_VALUE + == (Math.abs(range.to) - 1) / Byte.MIN_VALUE) { + // filter out test data that would cause Range.ByteRange to return + // BYTE_EVERYTHING + byte byteValue = (byte) value; + assert (range.contains(value) && result.contains(byteValue)) + || (!range.contains(value) && !result.contains(byteValue)) + : String.format( + "Range.byteRange failure: %s => %s; witness = %s", range, result, byteValue); + } + } + } + + Range r1 = Range.create(5, 1000); + Range r2 = Range.create(1024 + 17, 1024 + 22); + Range r3 = Range.create(5, Byte.MAX_VALUE + 2); + + Range.ignoreOverflow = true; + + assert r1.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); + assert r2.byteRange().equals(Range.create(Byte.MAX_VALUE, Byte.MAX_VALUE)); + assert r3.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); + + Range.ignoreOverflow = false; + + assert r1.byteRange().equals(Range.BYTE_EVERYTHING); + assert r2.byteRange().equals(Range.create(17, 22)); + assert r3.byteRange().equals(Range.BYTE_EVERYTHING); + } + + @Test + public void testUnion() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + Range result = range1.union(range2); + for (long value : values) { + if (range1.contains(value) || range2.contains(value)) { + assert result.contains(value) + : String.format( + "Range.union failure: %s %s %s; witness = %s", range1, range2, result, value); + } + } + } + } + } + + @Test + public void testIntersect() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + Range result = range1.intersect(range2); + for (long value : values) { + assert ((range1.contains(value) && range2.contains(value)) == result.contains(value)) + : String.format( + "Range.intersect failure: %s %s => %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testPlus() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.plus(re2.range); + assert result.contains(re1.element + re2.element) + : String.format( + "Range.plus failure: %s %s => %s; witnesses %s + %s => %s", + re1.range, re2.range, result, re1.element, re2.element, re1.element + re2.element); + } + } + } + + @Test + public void testMinus() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.minus(re2.range); + assert result.contains(re1.element - re2.element) + : String.format( + "Range.minus failure: %s %s => %s; witnesses %s - %s => %s", + re1.range, re2.range, result, re1.element, re2.element, re1.element - re2.element); + } + } + } + + @Test + public void testTimes() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.times(re2.range); + assert result.contains(re1.element * re2.element) + : String.format( + "Range.times failure: %s %s => %s; witnesses %s * %s => %s", + re1.range, re2.range, result, re1.element, re2.element, re1.element * re2.element); + } + } + } + + @Test + public void testDivide() { + assert Range.create(1, 2).divide(Range.create(0, 0)) == Range.NOTHING; + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + if (re2.element == 0) { + continue; + } + Range result = re1.range.divide(re2.range); + Long witness = re1.element / re2.element; + assert result.contains(witness) + : String.format( + "Range.divide failure: %s %s => %s; witnesses %s / %s => %s", + re1.range, re2.range, result, re1.element, re2.element, witness); + } + } + } + + @Test + public void testRemainder() { + assert Range.create(1, 2).remainder(Range.create(0, 0)) == Range.NOTHING; + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + if (re2.element == 0) { + continue; + } + Range result = re1.range.remainder(re2.range); + Long witness = re1.element % re2.element; + assert result.contains(witness) + : String.format( + "Range.remainder failure: %s %s => %s; witnesses %s %% %s => %s", + re1.range, re2.range, result, re1.element, re2.element, witness); + } + } + } + + @Test + public void testShiftLeft() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.shiftLeft(re2.range); + assert result.contains(re1.element << re2.element) + : String.format( + "Range.shiftLeft failure: %s %s => %s; witnesses %s << %s => %s", + re1.range, re2.range, result, re1.element, re2.element, re1.element << re2.element); + } + } + } + + @Test + public void testSignedShiftRight() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.signedShiftRight(re2.range); + assert result.contains(re1.element >> re2.element) + : String.format( + "Range.signedShiftRight failure: %s %s => %s; witnesses %s >> %s =>" + " %s", + re1.range, re2.range, result, re1.element, re2.element, re1.element >> re2.element); + } + } + } + + @Test + public void testUnsignedShiftRight() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.unsignedShiftRight(re2.range); + if (re1.range.from >= 0) { + assert result.contains(re1.element >>> re2.element) + : String.format( + "Range.unsignedShiftRight failure: %s %s => %s; witnesses %s >>" + " %s => %s", + re1.range, + re2.range, + result, + re1.element, + re2.element, + re1.element >> re2.element); + } else { + assert result == Range.EVERYTHING; + } + } + } + } + + @Test + public void testUnaryPlus() { + for (RangeAndElement re : rangeAndElements()) { + Range result = re.range.unaryPlus(); + assert result.contains(+re.element) + : String.format( + "Range.unaryPlus failure: %s => %s; witness %s => %s", + re.range, result, re.element, +re.element); + } + } + + @Test + public void testUnaryMinus() { + for (RangeAndElement re : rangeAndElements()) { + Range result = re.range.unaryMinus(); + assert result.contains(-re.element) + : String.format( + "Range.unaryMinus failure: %s => %s; witness %s => %s", + re.range, result, re.element, -re.element); + } + } + + @Test + public void testBitwiseComplement() { + for (RangeAndElement re : rangeAndElements()) { + Range result = re.range.bitwiseComplement(); + assert result.contains(~re.element) + : String.format( + "Range.bitwiseComplement failure: %s => %s; witness %s => %s", + re.range, result, re.element, ~re.element); + } + } + + @Test + public void testBitwiseAnd() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result1 = re1.range.bitwiseAnd(re2.range); + Range result2 = re2.range.bitwiseAnd(re1.range); + if (re1.range.isConstant() || re2.range.isConstant()) { + Long witness = re1.element & re2.element; + assert result1.from == result2.from; + assert result1.to == result2.to; + assert result1.contains(witness) + : String.format( + "Range.bitwiseAnd failure: %s %s => %s; witnesses %s & %s =>" + " %s", + re1.range, re2.range, result1, re1.element, re2.element, witness); + } else { + assert result1 == Range.EVERYTHING; + assert result2 == Range.EVERYTHING; + } + } + } + } + + @Test + public void testLessThan() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineLessThan(range2); + assert (value >= range2.to + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineLessThan failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testLessThanEq() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineLessThanEq(range2); + assert (value > range2.to + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineLessThanEq failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testGreaterThan() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineGreaterThan(range2); + assert (value <= range2.from + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineGreaterThan failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testGreaterThanEq() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineGreaterThanEq(range2); + assert (value < range2.from + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineGreaterThanEq failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testEqualTo() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineEqualTo(range2); + assert (value < range2.from || value > range2.to) + ? !result.contains(value) + : (range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineEqualTo failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testFactoryLongLong() { + Assert.assertEquals((long) 1, Range.create(1, 2).from); + Assert.assertEquals((long) 2, Range.create(1, 2).to); + } + + @Test + public void testFactoryList() { + Assert.assertEquals((long) 1, Range.create(Arrays.asList(1, 2, 3)).from); + Assert.assertEquals((long) 3, Range.create(Arrays.asList(1, 2, 3)).to); + Assert.assertEquals((long) 1, Range.create(Arrays.asList(3, 2, 1)).from); + Assert.assertEquals((long) 3, Range.create(Arrays.asList(3, 2, 1)).to); + Assert.assertEquals(Range.NOTHING, Range.create(Collections.emptyList())); + Assert.assertTrue(Range.NOTHING == Range.create(Collections.emptyList())); + } + + @Test + public void testFactoryTypeKind() { + Assert.assertEquals(Range.BYTE_EVERYTHING, Range.create(TypeKind.BYTE)); + Assert.assertEquals(Range.INT_EVERYTHING, Range.create(TypeKind.INT)); + Assert.assertEquals(Range.SHORT_EVERYTHING, Range.create(TypeKind.SHORT)); + Assert.assertEquals(Range.CHAR_EVERYTHING, Range.create(TypeKind.CHAR)); + Assert.assertEquals(Range.LONG_EVERYTHING, Range.create(TypeKind.LONG)); + } + + @Test(expected = IllegalArgumentException.class) + public void testFactoryTypeKindFailure() { + Range.create(TypeKind.FLOAT); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java index ec2ac305b21..7ab27b0b972 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java @@ -1,43 +1,42 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.checkerframework.framework.testchecker.reflection.ReflectionTestChecker; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.ArrayList; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.checkerframework.framework.testchecker.reflection.ReflectionTestChecker; +import org.junit.runners.Parameterized.Parameters; /** Tests the reflection resolution using a simple type system. */ public class ReflectionTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReflectionTest(List testFiles) { - super( - testFiles, - ReflectionTestChecker.class, - "reflection", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReflectionTest(List testFiles) { + super( + testFiles, + ReflectionTestChecker.class, + "reflection", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"reflection"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"reflection"}; + } - @Override - public List customizeOptions(List previousOptions) { - List optionsWithStub = new ArrayList<>(checkerOptions); - optionsWithStub.add("-Astubs=" + getFullPath(testFiles.get(0), "reflection.astub")); - optionsWithStub.add("-AresolveReflection"); - return optionsWithStub; - } + @Override + public List customizeOptions(List previousOptions) { + List optionsWithStub = new ArrayList<>(checkerOptions); + optionsWithStub.add("-Astubs=" + getFullPath(testFiles.get(0), "reflection.astub")); + optionsWithStub.add("-AresolveReflection"); + return optionsWithStub; + } - protected String getFullPath(File javaFile, String filename) { - String dirname = javaFile.getParentFile().getAbsolutePath(); - return dirname + File.separator + filename; - } + protected String getFullPath(File javaFile, String filename) { + String dirname = javaFile.getParentFile().getAbsolutePath(); + return dirname + File.separator + filename; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java index 86b6cb24063..d8d95d617d2 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java @@ -1,26 +1,25 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class ReportModifiersTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReportModifiersTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.util.report.ReportChecker.class, - "report", - "-AreportModifiers=native"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReportModifiersTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.util.report.ReportChecker.class, + "report", + "-AreportModifiers=native"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"reportmodifiers"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"reportmodifiers"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java index a8d88352186..deffc3bad2a 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java @@ -1,26 +1,25 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class ReportTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReportTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.util.report.ReportChecker.class, - "report", - "-Astubs=tests/report/reporttest.astub"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReportTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.util.report.ReportChecker.class, + "report", + "-Astubs=tests/report/reporttest.astub"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"report"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"report"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java index c4ac4a353db..10fdf5f69bb 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java @@ -1,26 +1,25 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; public class ReportTreeKindsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReportTreeKindsTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.util.report.ReportChecker.class, - "report", - "-AreportTreeKinds=WHILE_LOOP,CONDITIONAL_AND"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReportTreeKindsTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.util.report.ReportChecker.class, + "report", + "-AreportTreeKinds=WHILE_LOOP,CONDITIONAL_AND"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"reporttreekinds"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"reporttreekinds"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java index 67d60380a1a..8588b4901f4 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java @@ -1,35 +1,33 @@ package org.checkerframework.framework.test.junit; import com.google.common.collect.ImmutableList; - -import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.Collections; import java.util.List; +import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** tests the returns receiver checker's AutoValue integration. */ public class ReturnsReceiverAutoValueTest extends CheckerFrameworkPerDirectoryTest { - public ReturnsReceiverAutoValueTest(List testFiles) { - super( - testFiles, - ImmutableList.of( - "com.google.auto.value.extension.memoized.processor.MemoizedValidator", - "com.google.auto.value.processor.AutoAnnotationProcessor", - "com.google.auto.value.processor.AutoOneOfProcessor", - "com.google.auto.value.processor.AutoValueBuilderProcessor", - "com.google.auto.value.processor.AutoValueProcessor", - ReturnsReceiverChecker.class.getName()), - "basic", - Collections.emptyList(), - "-nowarn"); - } + public ReturnsReceiverAutoValueTest(List testFiles) { + super( + testFiles, + ImmutableList.of( + "com.google.auto.value.extension.memoized.processor.MemoizedValidator", + "com.google.auto.value.processor.AutoAnnotationProcessor", + "com.google.auto.value.processor.AutoOneOfProcessor", + "com.google.auto.value.processor.AutoValueBuilderProcessor", + "com.google.auto.value.processor.AutoValueProcessor", + ReturnsReceiverChecker.class.getName()), + "basic", + Collections.emptyList(), + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"returnsreceiverautovalue"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"returnsreceiverautovalue"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java index d90e07f4360..da97b4b45f9 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java @@ -1,38 +1,37 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests the returns receiver checker's lombok integration, the test files in * tests/returnsreceiverlombok package will be delomboked into tests/returnsreceiverdelomboked * package before running the test and the returns receiver checker will run on the generated codes. */ public class ReturnsReceiverLombokTest extends CheckerFrameworkPerDirectoryTest { - public ReturnsReceiverLombokTest(List testFiles) { - super( - testFiles, - ReturnsReceiverChecker.class, - "returnsreceiverdelomboked", - "-nowarn", - "-AsuppressWarnings=type.anno.before.modifier"); - } + public ReturnsReceiverLombokTest(List testFiles) { + super( + testFiles, + ReturnsReceiverChecker.class, + "returnsreceiverdelomboked", + "-nowarn", + "-AsuppressWarnings=type.anno.before.modifier"); + } - @Override - public void run() { - // Only run if delomboked codes have been created. - if (!new File("tests/returnsreceiverdelomboked/").exists()) { - throw new RuntimeException("delombok task must be run before this test."); - } - super.run(); + @Override + public void run() { + // Only run if delomboked codes have been created. + if (!new File("tests/returnsreceiverdelomboked/").exists()) { + throw new RuntimeException("delombok task must be run before this test."); } + super.run(); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"returnsreceiverdelomboked"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"returnsreceiverdelomboked"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java index a4526de8246..97900425455 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Test runner for tests of the Returns Receiver Checker. * @@ -14,19 +13,19 @@ * case, create a Java file in that directory. */ public class ReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest { - public ReturnsReceiverTest(List testFiles) { - super( - testFiles, - ReturnsReceiverChecker.class, - "returnsreceiver", - "-Astubs=stubs/", - "-nowarn", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + public ReturnsReceiverTest(List testFiles) { + super( + testFiles, + ReturnsReceiverChecker.class, + "returnsreceiver", + "-Astubs=stubs/", + "-nowarn", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"returnsreceiver", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"returnsreceiver", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java index 447d29c5e7d..59d5f66f659 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java @@ -1,28 +1,27 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.Encrypted; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Test suite for the Subtyping Checker, using a simple {@link Encrypted} annotation. */ public class SubtypingEncryptedTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public SubtypingEncryptedTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.subtyping.SubtypingChecker.class, - "subtyping", - "-Aquals=org.checkerframework.framework.testchecker.util.Encrypted,org.checkerframework.framework.testchecker.util.PolyEncrypted,org.checkerframework.common.subtyping.qual.Unqualified"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public SubtypingEncryptedTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.subtyping.SubtypingChecker.class, + "subtyping", + "-Aquals=org.checkerframework.framework.testchecker.util.Encrypted,org.checkerframework.framework.testchecker.util.PolyEncrypted,org.checkerframework.common.subtyping.qual.Unqualified"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"subtyping", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"subtyping", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java index 67c5e36311e..b05db500b65 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java @@ -1,27 +1,26 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; /** Test suite for the Subtyping Checker, using {@code @QualifierForLiterals}. */ public class SubtypingStringPatternsFullTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public SubtypingStringPatternsFullTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.subtyping.SubtypingChecker.class, - "stringpatterns/stringpatterns-full", - "-Aquals=org.checkerframework.framework.testchecker.util.PatternUnknown,org.checkerframework.framework.testchecker.util.PatternAB,org.checkerframework.framework.testchecker.util.PatternBC,org.checkerframework.framework.testchecker.util.PatternAC,org.checkerframework.framework.testchecker.util.PatternA,org.checkerframework.framework.testchecker.util.PatternB,org.checkerframework.framework.testchecker.util.PatternC,org.checkerframework.framework.testchecker.util.PatternBottomFull"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public SubtypingStringPatternsFullTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.subtyping.SubtypingChecker.class, + "stringpatterns/stringpatterns-full", + "-Aquals=org.checkerframework.framework.testchecker.util.PatternUnknown,org.checkerframework.framework.testchecker.util.PatternAB,org.checkerframework.framework.testchecker.util.PatternBC,org.checkerframework.framework.testchecker.util.PatternAC,org.checkerframework.framework.testchecker.util.PatternA,org.checkerframework.framework.testchecker.util.PatternB,org.checkerframework.framework.testchecker.util.PatternC,org.checkerframework.framework.testchecker.util.PatternBottomFull"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"stringpatterns/stringpatterns-full", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"stringpatterns/stringpatterns-full", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java index d068dc54546..8a7c60a6e0a 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java @@ -1,23 +1,22 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.supportedquals.SupportedQualsChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class SupportedQualsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public SupportedQualsTest(List testFiles) { - super(testFiles, SupportedQualsChecker.class, "simple"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public SupportedQualsTest(List testFiles) { + super(testFiles, SupportedQualsChecker.class, "simple"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"simple"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"simple"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java index d0a03d5be2a..6d30c363b00 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java @@ -8,90 +8,87 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; - +import javax.annotation.processing.ProcessingEnvironment; import org.checkerframework.javacutil.trees.TreeParser; import org.junit.Assert; import org.junit.Test; -import javax.annotation.processing.ProcessingEnvironment; - public class TreeParserTest { - private final ProcessingEnvironment env; - private final TreeParser parser; - - public TreeParserTest() { - env = JavacProcessingEnvironment.instance(new Context()); - parser = new TreeParser(env); - } - - @Test - public void parsesIdentifiers() { - String value = "id"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof IdentifierTree); - } - - @Test - public void parsesNumbers() { - String value = "23"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof LiteralTree); - } - - @Test - public void parsesMethodInvocations() { - String value = "test()"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof MethodInvocationTree); - MethodInvocationTree invocation = (MethodInvocationTree) parsed; - Assert.assertTrue(invocation.getMethodSelect() instanceof IdentifierTree); - Assert.assertEquals( - "test", ((IdentifierTree) invocation.getMethodSelect()).getName().toString()); - } - - @Test - public void parsesMethodInvocationsWithSelect() { - String value = "Class.test()"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof MethodInvocationTree); - MethodInvocationTree invocation = (MethodInvocationTree) parsed; - Assert.assertTrue(invocation.getMethodSelect() instanceof MemberSelectTree); - MemberSelectTree select = (MemberSelectTree) invocation.getMethodSelect(); - Assert.assertEquals("test", select.getIdentifier().toString()); - Assert.assertEquals("Class", select.getExpression().toString()); - } - - @Test - public void parsesIndex() { - String value = "array[2]"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof ArrayAccessTree); - ArrayAccessTree access = (ArrayAccessTree) parsed; - - Assert.assertEquals(2, ((LiteralTree) access.getIndex()).getValue()); - Assert.assertEquals( - "array", ((IdentifierTree) access.getExpression()).getName().toString()); - } - - @Test - public void randomParses() { - ExpressionTree parsed = parser.parseTree("Class.method()[4].field[3]"); - - Assert.assertTrue(parsed instanceof ArrayAccessTree); - MemberSelectTree array = (MemberSelectTree) ((ArrayAccessTree) parsed).getExpression(); - Assert.assertEquals("field", array.getIdentifier().toString()); - Assert.assertTrue(array.getExpression() instanceof ArrayAccessTree); - } - - @Test - public void parsesMethodArguments() { - parser.parseTree("method()"); - parser.parseTree("method(1)"); - parser.parseTree("method(1,2)"); - } + private final ProcessingEnvironment env; + private final TreeParser parser; + + public TreeParserTest() { + env = JavacProcessingEnvironment.instance(new Context()); + parser = new TreeParser(env); + } + + @Test + public void parsesIdentifiers() { + String value = "id"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof IdentifierTree); + } + + @Test + public void parsesNumbers() { + String value = "23"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof LiteralTree); + } + + @Test + public void parsesMethodInvocations() { + String value = "test()"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof MethodInvocationTree); + MethodInvocationTree invocation = (MethodInvocationTree) parsed; + Assert.assertTrue(invocation.getMethodSelect() instanceof IdentifierTree); + Assert.assertEquals( + "test", ((IdentifierTree) invocation.getMethodSelect()).getName().toString()); + } + + @Test + public void parsesMethodInvocationsWithSelect() { + String value = "Class.test()"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof MethodInvocationTree); + MethodInvocationTree invocation = (MethodInvocationTree) parsed; + Assert.assertTrue(invocation.getMethodSelect() instanceof MemberSelectTree); + MemberSelectTree select = (MemberSelectTree) invocation.getMethodSelect(); + Assert.assertEquals("test", select.getIdentifier().toString()); + Assert.assertEquals("Class", select.getExpression().toString()); + } + + @Test + public void parsesIndex() { + String value = "array[2]"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof ArrayAccessTree); + ArrayAccessTree access = (ArrayAccessTree) parsed; + + Assert.assertEquals(2, ((LiteralTree) access.getIndex()).getValue()); + Assert.assertEquals("array", ((IdentifierTree) access.getExpression()).getName().toString()); + } + + @Test + public void randomParses() { + ExpressionTree parsed = parser.parseTree("Class.method()[4].field[3]"); + + Assert.assertTrue(parsed instanceof ArrayAccessTree); + MemberSelectTree array = (MemberSelectTree) ((ArrayAccessTree) parsed).getExpression(); + Assert.assertEquals("field", array.getIdentifier().toString()); + Assert.assertTrue(array.getExpression() instanceof ArrayAccessTree); + } + + @Test + public void parsesMethodArguments() { + parser.parseTree("method()"); + parser.parseTree("method(1)"); + parser.parseTree("method(1,2)"); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java index bf9d7835d0f..525f41c3a07 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java @@ -1,20 +1,19 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.typedeclbounds.TypeDeclBoundsChecker; import org.junit.runners.Parameterized; -import java.io.File; -import java.util.List; - public class TypeDeclBoundsTest extends CheckerFrameworkPerDirectoryTest { - public TypeDeclBoundsTest(List testFiles) { - super(testFiles, TypeDeclBoundsChecker.class, "typedeclbounds"); - } + public TypeDeclBoundsTest(List testFiles) { + super(testFiles, TypeDeclBoundsChecker.class, "typedeclbounds"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"typedeclbounds"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"typedeclbounds"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java index 262aea10f9a..1cb24876638 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java @@ -1,28 +1,27 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.typedecldefault.TypeDeclDefaultChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Create the TypeDeclDefault test. */ public class TypeDeclDefaultTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public TypeDeclDefaultTest(List testFiles) { - super( - testFiles, - TypeDeclDefaultChecker.class, - "typedecldefault", - "-Astubs=tests/typedecldefault/jdk.astub"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public TypeDeclDefaultTest(List testFiles) { + super( + testFiles, + TypeDeclDefaultChecker.class, + "typedecldefault", + "-Astubs=tests/typedecldefault/jdk.astub"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"typedecldefault"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"typedecldefault"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java index afd06a04138..72990bd1496 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java @@ -1,31 +1,30 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests the constant value propagation type system without overflow. */ public class ValueIgnoreRangeOverflowTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueIgnoreRangeOverflowTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-A" + ValueChecker.REPORT_EVAL_WARNS, - "-A" + ValueChecker.IGNORE_RANGE_OVERFLOW); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueIgnoreRangeOverflowTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-A" + ValueChecker.REPORT_EVAL_WARNS, + "-A" + ValueChecker.IGNORE_RANGE_OVERFLOW); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"value", "all-systems", "value-ignore-range-overflow"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"value", "all-systems", "value-ignore-range-overflow"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java index f6d75d9e364..9d3e1c8298d 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java @@ -1,30 +1,29 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - public class ValueNonNullStringsConcatenationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueNonNullStringsConcatenationTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value-non-null-strings-concatenation", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-A" + ValueChecker.REPORT_EVAL_WARNS, - "-A" + ValueChecker.NON_NULL_STRINGS_CONCATENATION); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueNonNullStringsConcatenationTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value-non-null-strings-concatenation", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-A" + ValueChecker.REPORT_EVAL_WARNS, + "-A" + ValueChecker.NON_NULL_STRINGS_CONCATENATION); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"all-systems", "value-non-null-strings-concatenation"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"all-systems", "value-non-null-strings-concatenation"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java index 01579f71a5e..e9c1a5b18b2 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestUtilities; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** * Tests the constant value propagation type system. * @@ -17,23 +16,22 @@ */ public class ValueTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - TestUtilities.adapt( - "-Astubs=tests/value/minints-stub.astub:tests/value/lowercase.astub"), - "-A" + ValueChecker.REPORT_EVAL_WARNS); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + TestUtilities.adapt("-Astubs=tests/value/minints-stub.astub:tests/value/lowercase.astub"), + "-A" + ValueChecker.REPORT_EVAL_WARNS); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"value", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"value", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java index d33bdf51dd8..e8bd5912e0c 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java @@ -1,33 +1,32 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Tests conservative defaults for the constant value propagation type system. */ public class ValueUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - ValueChecker.class, - "value", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-AuseConservativeDefaultsForUncheckedCode=btyecode", - "-A" + ValueChecker.REPORT_EVAL_WARNS); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + ValueChecker.class, + "value", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-AuseConservativeDefaultsForUncheckedCode=btyecode", + "-A" + ValueChecker.REPORT_EVAL_WARNS); + } - @Parameters - public static String[] getTestDirs() { - // The defaults for unchecked code should be the same as checked code, so use the same - // tests. - return new String[] {"value", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + // The defaults for unchecked code should be the same as checked code, so use the same + // tests. + return new String[] {"value", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java index a3e6fb05b77..ca2d9645fd0 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.test.junit; +import java.io.File; +import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker; import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.util.List; - /** Create the VariableNameDefault test. */ public class VariableNameDefaultTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public VariableNameDefaultTest(List testFiles) { - super(testFiles, VariableNameDefaultChecker.class, "variablenamedefault"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public VariableNameDefaultTest(List testFiles) { + super(testFiles, VariableNameDefaultChecker.class, "variablenamedefault"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"variablenamedefault"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"variablenamedefault"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java index 81479836a19..241f1db806e 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java @@ -1,22 +1,21 @@ package org.checkerframework.framework.test.junit; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized; - import java.io.File; import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized; public class ViewpointTestCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ViewpointTestCheckerTest(List testFiles) { - super(testFiles, viewpointtest.ViewpointTestChecker.class, "viewpointtest"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ViewpointTestCheckerTest(List testFiles) { + super(testFiles, viewpointtest.ViewpointTestChecker.class, "viewpointtest"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"viewpointtest"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"viewpointtest"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java index a78adc169e6..853afd89d41 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java @@ -1,18 +1,17 @@ package org.checkerframework.framework.testchecker.aggregate; +import java.util.Arrays; +import java.util.Collection; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.source.AggregateChecker; import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.testchecker.compound.CompoundChecker; -import java.util.Arrays; -import java.util.Collection; - /** An aggregate checker where one of the checkers is a compound checker. */ public class AggregateOfCompoundChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - return Arrays.asList(ValueChecker.class, CompoundChecker.class); - } + @Override + protected Collection> getSupportedCheckers() { + return Arrays.asList(ValueChecker.class, CompoundChecker.class); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java index 2e159cc59a5..90bea58adb0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java @@ -1,18 +1,17 @@ package org.checkerframework.framework.testchecker.aggregate; +import java.util.Arrays; +import java.util.Collection; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.source.AggregateChecker; import org.checkerframework.framework.source.SourceChecker; -import java.util.Arrays; -import java.util.Collection; - /** Basic aggregate checker. */ public class TestAggregateChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - return Arrays.asList(ValueChecker.class, AliasingChecker.class); - } + @Override + protected Collection> getSupportedCheckers() { + return Arrays.asList(ValueChecker.class, AliasingChecker.class); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java index 6846df5df4c..e34bc0aeb92 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java @@ -1,34 +1,33 @@ package org.checkerframework.framework.testchecker.compound; +import java.util.LinkedHashSet; +import java.util.Set; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; -import java.util.LinkedHashSet; -import java.util.Set; - public class AnotherCompoundChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - // Make sure that options can be accessed by sub-checkers to determine - // which subcheckers to run. - @SuppressWarnings("unused") - String option = super.getOption("nomsgtext"); - LinkedHashSet> subcheckers = new LinkedHashSet<>(); - subcheckers.addAll(super.getImmediateSubcheckerClasses()); - subcheckers.add(AliasingChecker.class); - subcheckers.add(ValueChecker.class); - return subcheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Make sure that options can be accessed by sub-checkers to determine + // which subcheckers to run. + @SuppressWarnings("unused") + String option = super.getOption("nomsgtext"); + LinkedHashSet> subcheckers = new LinkedHashSet<>(); + subcheckers.addAll(super.getImmediateSubcheckerClasses()); + subcheckers.add(AliasingChecker.class); + subcheckers.add(ValueChecker.class); + return subcheckers; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new BaseTypeVisitor(this) { - @Override - protected AnotherCompoundCheckerAnnotatedTypeFactory createTypeFactory() { - return new AnotherCompoundCheckerAnnotatedTypeFactory(checker); - } - }; - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new BaseTypeVisitor(this) { + @Override + protected AnotherCompoundCheckerAnnotatedTypeFactory createTypeFactory() { + return new AnotherCompoundCheckerAnnotatedTypeFactory(checker); + } + }; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java index c79aa94af07..59b37023d5d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java @@ -1,7 +1,10 @@ package org.checkerframework.framework.testchecker.compound; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -13,48 +16,42 @@ import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - public class AnotherCompoundCheckerAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** - * Creates a new AnotherCompoundCheckerAnnotatedTypeFactory. - * - * @param checker the checker - */ - public AnotherCompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + /** + * Creates a new AnotherCompoundCheckerAnnotatedTypeFactory. + * + * @param checker the checker + */ + public AnotherCompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList(ACCTop.class, ACCBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>(Arrays.asList(ACCTop.class, ACCBottom.class)); + } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), - new TreeAnnotator(this) { - @Override - protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { - // Just access the subchecker type factories to make - // sure they were created properly - GenericAnnotatedTypeFactory aliasingATF = - getTypeFactoryOfSubchecker(AliasingChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); - GenericAnnotatedTypeFactory valueATF = - getTypeFactoryOfSubchecker(ValueChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror value = valueATF.getAnnotatedType(tree); - return super.defaultAction(tree, p); - } - }); - } + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), + new TreeAnnotator(this) { + @Override + protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { + // Just access the subchecker type factories to make + // sure they were created properly + GenericAnnotatedTypeFactory aliasingATF = + getTypeFactoryOfSubchecker(AliasingChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); + GenericAnnotatedTypeFactory valueATF = + getTypeFactoryOfSubchecker(ValueChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror value = valueATF.getAnnotatedType(tree); + return super.defaultAction(tree, p); + } + }); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java index 4c7d6ab9e68..d617ea0a102 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java @@ -1,34 +1,33 @@ package org.checkerframework.framework.testchecker.compound; +import java.util.LinkedHashSet; +import java.util.Set; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; -import java.util.LinkedHashSet; -import java.util.Set; - /** * Used to test the compound checker design pattern. AliasingChecker and AnotherCompoundChecker are * subcheckers of this checker AnotherCompoundChecker relies on the Aliasing Checker, too. This is * so that the order of subcheckers is tested. */ public class CompoundChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - LinkedHashSet> subcheckers = new LinkedHashSet<>(); - subcheckers.addAll(super.getImmediateSubcheckerClasses()); - subcheckers.add(AliasingChecker.class); - subcheckers.add(AnotherCompoundChecker.class); - return subcheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + LinkedHashSet> subcheckers = new LinkedHashSet<>(); + subcheckers.addAll(super.getImmediateSubcheckerClasses()); + subcheckers.add(AliasingChecker.class); + subcheckers.add(AnotherCompoundChecker.class); + return subcheckers; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new BaseTypeVisitor(this) { - @Override - protected CompoundCheckerAnnotatedTypeFactory createTypeFactory() { - return new CompoundCheckerAnnotatedTypeFactory(checker); - } - }; - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new BaseTypeVisitor(this) { + @Override + protected CompoundCheckerAnnotatedTypeFactory createTypeFactory() { + return new CompoundCheckerAnnotatedTypeFactory(checker); + } + }; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java index be4e34717f5..ece07ad4c76 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java @@ -1,7 +1,10 @@ package org.checkerframework.framework.testchecker.compound; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -13,46 +16,40 @@ import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - public class CompoundCheckerAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public CompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public CompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>(Arrays.asList(CCTop.class, CCBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>(Arrays.asList(CCTop.class, CCBottom.class)); + } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), - new TreeAnnotator(this) { - @Override - protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { - // Just access the subchecker type factories to make - // sure they were created properly - GenericAnnotatedTypeFactory accATF = - getTypeFactoryOfSubchecker(AnotherCompoundChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror another = accATF.getAnnotatedType(tree); - GenericAnnotatedTypeFactory aliasingATF = - getTypeFactoryOfSubchecker(AliasingChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); - GenericAnnotatedTypeFactory valueATF = - getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); - assert valueATF == null - : "Should not be able to access the ValueChecker annotations."; - return super.defaultAction(tree, p); - } - }); - } + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), + new TreeAnnotator(this) { + @Override + protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { + // Just access the subchecker type factories to make + // sure they were created properly + GenericAnnotatedTypeFactory accATF = + getTypeFactoryOfSubchecker(AnotherCompoundChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror another = accATF.getAnnotatedType(tree); + GenericAnnotatedTypeFactory aliasingATF = + getTypeFactoryOfSubchecker(AliasingChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); + GenericAnnotatedTypeFactory valueATF = + getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); + assert valueATF == null : "Should not be able to access the ValueChecker annotations."; + return super.defaultAction(tree, p); + } + }); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java index f26f4af606f..c2a8d1550a5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.compound.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - @SubtypeOf({ACCTop.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java index 2232818be1b..19500df5419 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.compound.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java index 7567920d933..54467f5ebea 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.compound.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - @SubtypeOf({CCTop.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java index 8a87d7f4afa..a998fa40ac3 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.compound.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java index 41006bd2a5a..d6f6ea8ac2f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.testchecker.defaulting; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbBottom; @@ -7,21 +11,16 @@ import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbImplicit; import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbTop; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - public class DefaultingLowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public DefaultingLowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public DefaultingLowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList(LbTop.class, LbExplicit.class, LbImplicit.class, LbBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList(LbTop.class, LbExplicit.class, LbImplicit.class, LbBottom.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java index 909997e88c5..94bec6e43eb 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java @@ -1,27 +1,26 @@ package org.checkerframework.framework.testchecker.defaulting; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; public class DefaultingUpperBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public DefaultingUpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public DefaultingUpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - UpperBoundQual.UbTop.class, - UpperBoundQual.UbExplicit.class, - UpperBoundQual.UbImplicit.class, - UpperBoundQual.UbBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + UpperBoundQual.UbTop.class, + UpperBoundQual.UbExplicit.class, + UpperBoundQual.UbImplicit.class, + UpperBoundQual.UbBottom.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java index 66430d261c7..9bf5bb1ee27 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java @@ -1,42 +1,41 @@ package org.checkerframework.framework.testchecker.defaulting; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; public class LowerBoundQual { - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @SubtypeOf({}) - @DefaultQualifierInHierarchy - @Documented - @Retention(RetentionPolicy.RUNTIME) - public static @interface LbTop {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @SubtypeOf({}) + @DefaultQualifierInHierarchy + @Documented + @Retention(RetentionPolicy.RUNTIME) + public static @interface LbTop {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(LbTop.class) - @DefaultFor(TypeUseLocation.IMPLICIT_LOWER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface LbImplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(LbTop.class) + @DefaultFor(TypeUseLocation.IMPLICIT_LOWER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface LbImplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(LbTop.class) - @DefaultFor(TypeUseLocation.EXPLICIT_LOWER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface LbExplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(LbTop.class) + @DefaultFor(TypeUseLocation.EXPLICIT_LOWER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface LbExplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf({LbImplicit.class, LbExplicit.class}) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface LbBottom {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf({LbImplicit.class, LbExplicit.class}) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface LbBottom {} } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java index 130247ef0bc..705d42ec0d8 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java @@ -1,44 +1,43 @@ package org.checkerframework.framework.testchecker.defaulting; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** Created by jburke on 9/29/14. */ public class UpperBoundQual { - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @SubtypeOf({}) - @DefaultQualifierInHierarchy - @Documented - @Retention(RetentionPolicy.RUNTIME) - public static @interface UbTop {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @SubtypeOf({}) + @DefaultQualifierInHierarchy + @Documented + @Retention(RetentionPolicy.RUNTIME) + public static @interface UbTop {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(UbTop.class) - @DefaultFor(TypeUseLocation.IMPLICIT_UPPER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface UbImplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(UbTop.class) + @DefaultFor(TypeUseLocation.IMPLICIT_UPPER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface UbImplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(UbTop.class) - @DefaultFor(TypeUseLocation.EXPLICIT_UPPER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface UbExplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(UbTop.class) + @DefaultFor(TypeUseLocation.EXPLICIT_UPPER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface UbExplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf({UbImplicit.class, UbExplicit.class}) - @DefaultFor(TypeUseLocation.LOWER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface UbBottom {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf({UbImplicit.class, UbExplicit.class}) + @DefaultFor(TypeUseLocation.LOWER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface UbBottom {} } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java index cb113e53b5e..b653b269e30 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java @@ -1,5 +1,11 @@ package org.checkerframework.framework.testchecker.flowexpression; +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.flowexpression.qual.FEBottom; @@ -12,116 +18,104 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Set; +public class FlowExpressionAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + private AnnotationMirror TOP, BOTTOM; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; + /** The FlowExp.value field/element. */ + ExecutableElement flowExpValueElement = + TreeUtils.getMethod(FlowExp.class, "value", 0, processingEnv); -public class FlowExpressionAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private AnnotationMirror TOP, BOTTOM; + /** + * Creates a new FlowExpressionAnnotatedTypeFactory. + * + * @param checker the checker + */ + public FlowExpressionAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + TOP = AnnotationBuilder.fromClass(elements, FETop.class); + BOTTOM = AnnotationBuilder.fromClass(elements, FEBottom.class); + postInit(); + } + + @Override + protected DependentTypesHelper createDependentTypesHelper() { + return new DependentTypesHelper(this); + } + + @Override + protected FlowExpressionQualifierHierarchy createQualifierHierarchy() { + return new FlowExpressionQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } - /** The FlowExp.value field/element. */ - ExecutableElement flowExpValueElement = - TreeUtils.getMethod(FlowExp.class, "value", 0, processingEnv); + private class FlowExpressionQualifierHierarchy extends MostlyNoElementQualifierHierarchy { /** - * Creates a new FlowExpressionAnnotatedTypeFactory. + * Create a {@code FlowExpressionQualifierHierarchy}. * - * @param checker the checker + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils */ - public FlowExpressionAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - TOP = AnnotationBuilder.fromClass(elements, FETop.class); - BOTTOM = AnnotationBuilder.fromClass(elements, FEBottom.class); - postInit(); + public FlowExpressionQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, FlowExpressionAnnotatedTypeFactory.this); } @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new DependentTypesHelper(this); + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + List subtypeExpressions = + AnnotationUtils.getElementValueArray(subAnno, flowExpValueElement, String.class); + List supertypeExpressions = + AnnotationUtils.getElementValueArray(superAnno, flowExpValueElement, String.class); + return subtypeExpressions.containsAll(supertypeExpressions) + && supertypeExpressions.containsAll(subtypeExpressions); } @Override - protected FlowExpressionQualifierHierarchy createQualifierHierarchy() { - return new FlowExpressionQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.getName() == FEBottom.class.getCanonicalName()) { + return a2; + } else if (qualifierKind2.getName() == FEBottom.class.getCanonicalName()) { + return a1; + } + List a1Expressions = + AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); + List a2Expressions = + AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); + if (a1Expressions.containsAll(a2Expressions) && a2Expressions.containsAll(a1Expressions)) { + return a1; + } + return TOP; } - private class FlowExpressionQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** - * Create a {@code FlowExpressionQualifierHierarchy}. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - public FlowExpressionQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, FlowExpressionAnnotatedTypeFactory.this); - } - - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - List subtypeExpressions = - AnnotationUtils.getElementValueArray( - subAnno, flowExpValueElement, String.class); - List supertypeExpressions = - AnnotationUtils.getElementValueArray( - superAnno, flowExpValueElement, String.class); - return subtypeExpressions.containsAll(supertypeExpressions) - && supertypeExpressions.containsAll(subtypeExpressions); - } - - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.getName() == FEBottom.class.getCanonicalName()) { - return a2; - } else if (qualifierKind2.getName() == FEBottom.class.getCanonicalName()) { - return a1; - } - List a1Expressions = - AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); - List a2Expressions = - AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); - if (a1Expressions.containsAll(a2Expressions) - && a2Expressions.containsAll(a1Expressions)) { - return a1; - } - return TOP; - } - - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1.getName() == FETop.class.getCanonicalName()) { - return a2; - } else if (qualifierKind2.getName() == FETop.class.getCanonicalName()) { - return a1; - } - List a1Expressions = - AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); - List a2Expressions = - AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); - if (a1Expressions.containsAll(a2Expressions) - && a2Expressions.containsAll(a1Expressions)) { - return a1; - } - return BOTTOM; - } + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1.getName() == FETop.class.getCanonicalName()) { + return a2; + } else if (qualifierKind2.getName() == FETop.class.getCanonicalName()) { + return a1; + } + List a1Expressions = + AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); + List a2Expressions = + AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); + if (a1Expressions.containsAll(a2Expressions) && a2Expressions.containsAll(a1Expressions)) { + return a1; + } + return BOTTOM; } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java index bf0692f5068..ddad660ef93 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.flowexpression.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf({FlowExp.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java index 5c70279a7e4..f272e014b5a 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.flowexpression.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java index 8302a00f7e4..cd2a452dc89 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.testchecker.flowexpression.qual; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf({FETop.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface FlowExp { - @JavaExpression - String[] value() default {}; + @JavaExpression + String[] value() default {}; } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java index 8f33b5378dd..f0eac24510e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java @@ -2,7 +2,9 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - +import java.lang.annotation.Annotation; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Bot; @@ -20,49 +22,44 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationBuilder; -import java.lang.annotation.Annotation; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; - public class H1H2AnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - AnnotationMirror H1S2; + AnnotationMirror H1S2; - /** - * Creates a new H1H2AnnotatedTypeFactory. - * - * @param checker the checker - */ - public H1H2AnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - H1S2 = AnnotationBuilder.fromClass(elements, H1S2.class); - } + /** + * Creates a new H1H2AnnotatedTypeFactory. + * + * @param checker the checker + */ + public H1H2AnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + H1S2 = AnnotationBuilder.fromClass(elements, H1S2.class); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - H1Top.class, - H1S1.class, - H1S2.class, - H1Bot.class, - H2Top.class, - H2S1.class, - H2S2.class, - H2Bot.class, - H1Poly.class, - H2Poly.class, - H2OnlyOnLB.class, - H1Invalid.class); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + H1Top.class, + H1S1.class, + H1S2.class, + H1Bot.class, + H2Top.class, + H2S1.class, + H2S2.class, + H2Bot.class, + H1Poly.class, + H2Poly.class, + H2OnlyOnLB.class, + H1Invalid.class); + } - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(tree, type); - if (tree.getKind() == Tree.Kind.VARIABLE - && ((VariableTree) tree).getName().toString().contains("addH1S2")) { - type.replaceAnnotation(H1S2); - } + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); + if (tree.getKind() == Tree.Kind.VARIABLE + && ((VariableTree) tree).getName().toString().contains("addH1S2")) { + type.replaceAnnotation(H1S2); } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java index 492f6dc1ae7..ab88878674e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java @@ -1,7 +1,7 @@ package org.checkerframework.framework.testchecker.h1h2checker; import com.sun.source.tree.Tree; - +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -11,41 +11,37 @@ import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.javacutil.AnnotationBuilder; -import javax.lang.model.element.AnnotationMirror; - public class H1H2Visitor extends BaseTypeVisitor { - public H1H2Visitor(BaseTypeChecker checker) { - super(checker); - } + public H1H2Visitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected BaseTypeValidator createTypeValidator() { - return new H1H2TypeValidator(checker, this, atypeFactory); - } + @Override + protected BaseTypeValidator createTypeValidator() { + return new H1H2TypeValidator(checker, this, atypeFactory); + } - private final class H1H2TypeValidator extends BaseTypeValidator { + private final class H1H2TypeValidator extends BaseTypeValidator { - public H1H2TypeValidator( - BaseTypeChecker checker, - BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } + public H1H2TypeValidator( + BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree p) { - AnnotationMirror h1Invalid = AnnotationBuilder.fromClass(elements, H1Invalid.class); - if (AnnotatedTypes.containsModifier(type, h1Invalid)) { - checker.reportError( - p, - // An error specific to this type system, with no corresponding text - // in a messages.properties file; this checker is just for testing. - "h1h2checker.h1invalid.forbidden", - type.getAnnotations(), - type.toString()); - } - return super.visitDeclared(type, p); - } + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree p) { + AnnotationMirror h1Invalid = AnnotationBuilder.fromClass(elements, H1Invalid.class); + if (AnnotatedTypes.containsModifier(type, h1Invalid)) { + checker.reportError( + p, + // An error specific to this type system, with no corresponding text + // in a messages.properties file; this checker is just for testing. + "h1h2checker.h1invalid.forbidden", + type.getAnnotations(), + type.toString()); + } + return super.visitDeclared(type, p); } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java index fd18a09c40c..dcbc4ede956 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java index ce0f0b60dc7..a2e4a34e0a2 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java index 83d5863695d..9f4201b9542 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java index 8d618242ddc..26798d88272 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java index 635de6c9ddb..8608b206d98 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java index ebffe288ae2..89de2b45e08 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java index 296bf34280b..ed939bffc2e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java index 2f6daffbf55..3b5da4a08eb 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java index 54ac91a8e05..26e3a187e95 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java index 29276abd5ed..e7794e83ae4 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java @@ -1,24 +1,23 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({ - TypeUseLocation.FIELD, - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.PARAMETER, - TypeUseLocation.RETURN, - TypeUseLocation.CONSTRUCTOR_RESULT + TypeUseLocation.FIELD, + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.PARAMETER, + TypeUseLocation.RETURN, + TypeUseLocation.CONSTRUCTOR_RESULT }) @SubtypeOf({H2Top.class}) public @interface H2S1 {} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java index c5dbfe61308..0bc2a6a7a1d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java index fba193b0c5d..54edce7bdf3 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java index 1ea34c06116..4939d4b6bf5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java @@ -3,11 +3,11 @@ package org.checkerframework.framework.testchecker.lib; public class Issue3105Fields { - public static final String FIELD1 = "foo"; + public static final String FIELD1 = "foo"; - public static final String FIELD2; + public static final String FIELD2; - static { - FIELD2 = "bar"; - } + static { + FIELD2 = "bar"; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java index 1e899bafa14..96899a15063 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java @@ -1,42 +1,42 @@ package org.checkerframework.framework.testchecker.lib; public class UncheckedByteCode { - public CT classTypeVariableField; - public static Object nonFinalPublicField; + public CT classTypeVariableField; + public static Object nonFinalPublicField; - public CT getCT() { - return classTypeVariableField; - } + public CT getCT() { + return classTypeVariableField; + } - public T identity(T t) { - return t; - } + public T identity(T t) { + return t; + } - public int getInt(int i) { - return i; - } + public int getInt(int i) { + return i; + } - public Integer getInteger(Integer i) { - return i; - } + public Integer getInteger(Integer i) { + return i; + } - public String getString(CharSequence charSequence) { - return ""; - } + public String getString(CharSequence charSequence) { + return ""; + } - public I getI(I i) { - return i; - } + public I getI(I i) { + return i; + } - public Object getObject(Object o) { - return o; - } + public Object getObject(Object o) { + return o; + } - public static void unboundedWildcardParam(UncheckedByteCode param) {} + public static void unboundedWildcardParam(UncheckedByteCode param) {} - public static void upperboundedWildcardParam(UncheckedByteCode param) {} + public static void upperboundedWildcardParam(UncheckedByteCode param) {} - public static void lowerboundedWildcardParam(UncheckedByteCode param) {} + public static void lowerboundedWildcardParam(UncheckedByteCode param) {} - public static void methodWithTypeVarBoundedByNumber(F param) {} + public static void methodWithTypeVarBoundedByNumber(F param) {} } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java index 02b94e604fe..32e905ff5a9 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java @@ -4,30 +4,30 @@ /** Used by framework/tests/value/VarArgRe.java */ public class VarArgMethods { - @StaticallyExecutable - public static int test0(Object... objects) { - if (objects == null) { - return -1; - } else { - return objects.length; - } + @StaticallyExecutable + public static int test0(Object... objects) { + if (objects == null) { + return -1; + } else { + return objects.length; } + } - @StaticallyExecutable - public static int test1(String s, Object... objects) { - if (objects == null) { - return -1; - } else { - return objects.length; - } + @StaticallyExecutable + public static int test1(String s, Object... objects) { + if (objects == null) { + return -1; + } else { + return objects.length; } + } - @StaticallyExecutable - public static int test2(String s, String s2, Object... objects) { - if (objects == null) { - return -1; - } else { - return objects.length; - } + @StaticallyExecutable + public static int test2(String s, String s2, Object... objects) { + if (objects == null) { + return -1; + } else { + return objects.length; } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java index f400cecbc0d..74e6f82c6ab 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.testchecker.lubglb; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.lubglb.quals.LubglbA; @@ -10,28 +14,23 @@ import org.checkerframework.framework.testchecker.lubglb.quals.LubglbF; import org.checkerframework.framework.testchecker.lubglb.quals.PolyLubglb; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - public class LubGlbAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public LubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public LubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - LubglbA.class, - LubglbB.class, - LubglbC.class, - LubglbD.class, - LubglbE.class, - LubglbF.class, - PolyLubglb.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + LubglbA.class, + LubglbB.class, + LubglbC.class, + LubglbD.class, + LubglbE.class, + LubglbF.class, + PolyLubglb.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java index 0531afc6dcf..474e94cc3cd 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java @@ -1,5 +1,7 @@ package org.checkerframework.framework.testchecker.lubglb; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.testchecker.lubglb.quals.LubglbA; @@ -13,9 +15,6 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - // Type hierarchy: // A <-- @DefaultQualifierInHierarchy // / \ @@ -27,74 +26,68 @@ public class LubGlbChecker extends BaseTypeChecker { - private AnnotationMirror A, B, C, D, E, F, POLY; + private AnnotationMirror A, B, C, D, E, F, POLY; - @Override - public void initChecker() { - super.initChecker(); + @Override + public void initChecker() { + super.initChecker(); - Elements elements = processingEnv.getElementUtils(); + Elements elements = processingEnv.getElementUtils(); - A = AnnotationBuilder.fromClass(elements, LubglbA.class); - B = AnnotationBuilder.fromClass(elements, LubglbB.class); - C = AnnotationBuilder.fromClass(elements, LubglbC.class); - D = AnnotationBuilder.fromClass(elements, LubglbD.class); - E = AnnotationBuilder.fromClass(elements, LubglbE.class); - F = AnnotationBuilder.fromClass(elements, LubglbF.class); - POLY = AnnotationBuilder.fromClass(elements, PolyLubglb.class); + A = AnnotationBuilder.fromClass(elements, LubglbA.class); + B = AnnotationBuilder.fromClass(elements, LubglbB.class); + C = AnnotationBuilder.fromClass(elements, LubglbC.class); + D = AnnotationBuilder.fromClass(elements, LubglbD.class); + E = AnnotationBuilder.fromClass(elements, LubglbE.class); + F = AnnotationBuilder.fromClass(elements, LubglbF.class); + POLY = AnnotationBuilder.fromClass(elements, PolyLubglb.class); - lubAssert(D, E, C); - lubAssert(E, D, C); + lubAssert(D, E, C); + lubAssert(E, D, C); - glbAssert(B, C, D); - glbAssert(C, B, D); + glbAssert(B, C, D); + glbAssert(C, B, D); - glbAssert(POLY, B, F); - glbAssert(POLY, F, F); - glbAssert(POLY, A, POLY); + glbAssert(POLY, B, F); + glbAssert(POLY, F, F); + glbAssert(POLY, A, POLY); - lubAssert(POLY, B, A); - lubAssert(POLY, F, POLY); - lubAssert(POLY, A, A); - } + lubAssert(POLY, B, A); + lubAssert(POLY, F, POLY); + lubAssert(POLY, A, A); + } - /** - * Throws an exception if glb(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void glbAssert( - AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format( - "GLB of %s and %s should be %s, but is %s", - arg1, arg2, expected, result)); - } + /** + * Throws an exception if glb(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void glbAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format("GLB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); } + } - /** - * Throws an exception if lub(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void lubAssert( - AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format( - "LUB of %s and %s should be %s, but is %s", - arg1, arg2, expected, result)); - } + /** + * Throws an exception if lub(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void lubAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format("LUB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java index cfeaec85d3c..fdb862dd9e2 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.lubglb.quals; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java index 0ce07415b66..7b6d1cd1b58 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.lubglb.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java index 4033410a678..a307181199c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.lubglb.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java index 7b98b12906f..25b6760b394 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.lubglb.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java index 8ed2e6fe892..3a103946808 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.lubglb.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java index fe21be7cb1d..2c2abaa87c6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.testchecker.lubglb.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java index 73785e46140..9891e951496 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.lubglb.quals; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java index 08983de7471..4de4fc1844c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java @@ -1,22 +1,21 @@ package org.checkerframework.framework.testchecker.nontopdefault; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; - import java.lang.annotation.Annotation; import java.util.Set; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; public class NTDAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public NTDAnnotatedTypeFactory(BaseTypeChecker checker) { - // use flow inference - super(checker, true); - this.postInit(); - } + public NTDAnnotatedTypeFactory(BaseTypeChecker checker) { + // use flow inference + super(checker, true); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - // there's no polymorphic qualifiers in NTD - return getBundledTypeQualifiers(); - } + @Override + protected Set> createSupportedTypeQualifiers() { + // there's no polymorphic qualifiers in NTD + return getBundledTypeQualifiers(); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java index 9cc308e26fc..8b083e8c01c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java @@ -1,7 +1,6 @@ package org.checkerframework.framework.testchecker.nontopdefault; import com.sun.source.tree.Tree; - import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.testchecker.nontopdefault.qual.NTDBottom; @@ -9,23 +8,23 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; public class NTDVisitor extends BaseTypeVisitor { - public NTDVisitor(BaseTypeChecker checker) { - super(checker); - } + public NTDVisitor(BaseTypeChecker checker) { + super(checker); + } - // Because classes and interfaces are by default NTDMiddle, an override is defined here which - // allows references to be declared using any NDT type except NTDBottom. - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // eg for the statement "@NTDSide Double x;" the declarationType is @NTDMiddle - // Double, and the useType is @NTDSide Double - if (declarationType.getEffectiveAnnotation(NTDMiddle.class) != null - && useType.getEffectiveAnnotation(NTDBottom.class) == null) { - return true; - } else { - // otherwise check the usage using super - return super.isValidUse(declarationType, useType, tree); - } + // Because classes and interfaces are by default NTDMiddle, an override is defined here which + // allows references to be declared using any NDT type except NTDBottom. + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // eg for the statement "@NTDSide Double x;" the declarationType is @NTDMiddle + // Double, and the useType is @NTDSide Double + if (declarationType.getEffectiveAnnotation(NTDMiddle.class) != null + && useType.getEffectiveAnnotation(NTDBottom.class) == null) { + return true; + } else { + // otherwise check the usage using super + return super.isValidUse(declarationType, useType, tree); } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java index 7f69c5fb6fe..56a7697b057 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java index 61b7383e6bc..cd48dc75840 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** Middle is the default type in hierarchy. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java index fe4382b9426..269c9020dc3 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java index 26ae8d65f92..b8ba586691c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java @@ -1,22 +1,21 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) @DefaultFor({ - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.IMPLICIT_UPPER_BOUND, - TypeUseLocation.RECEIVER + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.IMPLICIT_UPPER_BOUND, + TypeUseLocation.RECEIVER }) public @interface NTDTop {} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java index 4709fab47f0..5a728249db0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java @@ -1,5 +1,6 @@ package org.checkerframework.framework.testchecker.reflection; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.LiteralKind; @@ -10,25 +11,23 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationBuilder; -import javax.lang.model.element.AnnotationMirror; - /** * AnnotatedTypeFactory with reflection resolution enabled. The used qualifier hierarchy is * straightforward and only intended for test purposes. */ public final class ReflectionTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public ReflectionTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + public ReflectionTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - public TreeAnnotator createTreeAnnotator() { - LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); - AnnotationMirror bottom = AnnotationBuilder.fromClass(elements, TestReflectBottom.class); - literalTreeAnnotator.addLiteralKind(LiteralKind.INT, bottom); - literalTreeAnnotator.addStandardLiteralQualifiers(); + @Override + public TreeAnnotator createTreeAnnotator() { + LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); + AnnotationMirror bottom = AnnotationBuilder.fromClass(elements, TestReflectBottom.class); + literalTreeAnnotator.addLiteralKind(LiteralKind.INT, bottom); + literalTreeAnnotator.addStandardLiteralQualifiers(); - return new ListTreeAnnotator(new PropagationTreeAnnotator(this), literalTreeAnnotator); - } + return new ListTreeAnnotator(new PropagationTreeAnnotator(this), literalTreeAnnotator); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java index 41235aae8f0..84bc8d8d431 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java @@ -6,8 +6,8 @@ /** Checker for a simple type system to test reflection resolution. */ public class ReflectionTestChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ReflectionTestVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ReflectionTestVisitor(this); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java index 8d535964571..75427bcbb73 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java @@ -5,14 +5,14 @@ /** Visitor for a simple type system to test reflection resolution. */ public final class ReflectionTestVisitor - extends BaseTypeVisitor { + extends BaseTypeVisitor { - public ReflectionTestVisitor(BaseTypeChecker checker) { - super(checker); - } + public ReflectionTestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected ReflectionTestAnnotatedTypeFactory createTypeFactory() { - return new ReflectionTestAnnotatedTypeFactory(checker); - } + @Override + protected ReflectionTestAnnotatedTypeFactory createTypeFactory() { + return new ReflectionTestAnnotatedTypeFactory(checker); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java index 9074b7ab177..b49de665391 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.reflection.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Toy type system for testing reflection resolution. Uses diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java index eb90ec4ad8c..e95a1559575 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.reflection.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - /** * Toy type system for testing reflection resolution. Uses * org.checkerframework.common.subtyping.qual.Bottom as bottom. diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java index 727a693eb44..1bafddb9d55 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.reflection.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing reflection resolution. Uses diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java index f47f20616d3..831a4b814ad 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.reflection.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing reflection resolution. Uses diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java index 37689e8e18e..744c64ac5b1 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.reflection.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing reflection resolution. Uses diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java index b0f4fcd5295..f2245000b20 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java @@ -1,42 +1,41 @@ package org.checkerframework.framework.testchecker.supportedquals; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.testchecker.supportedquals.qual.BottomQualifier; import org.checkerframework.framework.testchecker.supportedquals.qual.Qualifier; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - /** * Tests that annotations that have @Target(TYPE_USE, OTHER) (where OTHER is not TYPE_PARAMETER) may * be in the qual package so long as {@link BaseAnnotatedTypeFactory#createSupportedTypeQualifiers} * is overridden. */ public class SupportedQualsChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new BaseTypeVisitor(this) { - @Override - protected SupportedQualsAnnotatedTypeFactory createTypeFactory() { - return new SupportedQualsAnnotatedTypeFactory(checker); - } - }; - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new BaseTypeVisitor(this) { + @Override + protected SupportedQualsAnnotatedTypeFactory createTypeFactory() { + return new SupportedQualsAnnotatedTypeFactory(checker); + } + }; + } - static class SupportedQualsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public SupportedQualsAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + static class SupportedQualsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + public SupportedQualsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList(Qualifier.class, BottomQualifier.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList(Qualifier.class, BottomQualifier.class)); } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java index 5fdd37a143f..4cfbe9280b5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.supportedquals.qual; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - @SubtypeOf({Qualifier.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java index 1caecc0926e..4bedee90bcf 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.supportedquals.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target(ElementType.TYPE_USE) @SubtypeOf({}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java index 8e0a324e382..8c9e9199b46 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java @@ -1,7 +1,8 @@ package org.checkerframework.framework.testchecker.testaccumulation; import com.sun.source.tree.MethodInvocationTree; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; import org.checkerframework.common.accumulation.AccumulationAnalysis; import org.checkerframework.common.accumulation.AccumulationAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -13,74 +14,71 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.TreeUtils; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; - /** * The annotated type factory for a test accumulation checker, which implements a basic called * methods checker. */ public class TestAccumulationAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory { - /** - * Create a new accumulation checker's annotated type factory. - * - * @param checker the checker - */ - public TestAccumulationAnnotatedTypeFactory(BaseTypeChecker checker) { - super( - checker, - TestAccumulation.class, - TestAccumulationBottom.class, - TestAccumulationPredicate.class); - this.postInit(); - } + /** + * Create a new accumulation checker's annotated type factory. + * + * @param checker the checker + */ + public TestAccumulationAnnotatedTypeFactory(BaseTypeChecker checker) { + super( + checker, + TestAccumulation.class, + TestAccumulationBottom.class, + TestAccumulationPredicate.class); + this.postInit(); + } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new TestAccumulationTreeAnnotator(this)); - } + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new TestAccumulationTreeAnnotator(this)); + } + /** + * Necessary for the type rule for called methods described below. A new accumulation analysis + * might have other type rules here, or none at all. + */ + private class TestAccumulationTreeAnnotator extends AccumulationTreeAnnotator { /** - * Necessary for the type rule for called methods described below. A new accumulation analysis - * might have other type rules here, or none at all. + * Creates an instance of this tree annotator for the given type factory. + * + * @param factory the type factory */ - private class TestAccumulationTreeAnnotator extends AccumulationTreeAnnotator { - /** - * Creates an instance of this tree annotator for the given type factory. - * - * @param factory the type factory - */ - public TestAccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { - super(factory); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - // CalledMethods requires special treatment of the return values of methods that return - // their receiver: the default return type must include the method being invoked. - // - // The basic accumulation analysis cannot handle this case - it can use the RR checker - // to transfer an annotation from the receiver to the return type, but because - // accumulation (has to) happen in dataflow, the correct annotation may not yet be - // available. The basic accumulation analysis therefore only supports "pass-through" - // returns receiver methods; it does not support automatically accumulating at the same - // time. - if (returnsThis(tree)) { - TypeMirror tm = type.getUnderlyingType(); - String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); - AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); - type.replaceAnnotation( - qualHierarchy.greatestLowerBoundShallow( - oldAnno, tm, createAccumulatorAnnotation(methodName), tm)); - } - return super.visitMethodInvocation(tree, type); - } + public TestAccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { + super(factory); } - // Overridden because there is no TestAccumulationAnalysis. @Override - protected AccumulationAnalysis createFlowAnalysis() { - return new AccumulationAnalysis(this.getChecker(), this); + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + // CalledMethods requires special treatment of the return values of methods that return + // their receiver: the default return type must include the method being invoked. + // + // The basic accumulation analysis cannot handle this case - it can use the RR checker + // to transfer an annotation from the receiver to the return type, but because + // accumulation (has to) happen in dataflow, the correct annotation may not yet be + // available. The basic accumulation analysis therefore only supports "pass-through" + // returns receiver methods; it does not support automatically accumulating at the same + // time. + if (returnsThis(tree)) { + TypeMirror tm = type.getUnderlyingType(); + String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); + AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); + type.replaceAnnotation( + qualHierarchy.greatestLowerBoundShallow( + oldAnno, tm, createAccumulatorAnnotation(methodName), tm)); + } + return super.visitMethodInvocation(tree, type); } + } + + // Overridden because there is no TestAccumulationAnalysis. + @Override + protected AccumulationAnalysis createFlowAnalysis() { + return new AccumulationAnalysis(this.getChecker(), this); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java index cf5f81168c6..2d7b4b053d5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java @@ -7,13 +7,13 @@ * receiver support, to enable the checker's auto-discovery of its AnnotatedTypeFactory to succeed. */ public class TestAccumulationNoReturnsReceiverAnnotatedTypeFactory - extends TestAccumulationAnnotatedTypeFactory { - /** - * Create a new accumulation checker's annotated type factory. - * - * @param checker the checker - */ - public TestAccumulationNoReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - } + extends TestAccumulationAnnotatedTypeFactory { + /** + * Create a new accumulation checker's annotated type factory. + * + * @param checker the checker + */ + public TestAccumulationNoReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java index 0e9e6b48b80..7133fdc8f90 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java @@ -1,8 +1,7 @@ package org.checkerframework.framework.testchecker.testaccumulation; -import org.checkerframework.common.accumulation.AccumulationChecker; - import java.util.EnumSet; +import org.checkerframework.common.accumulation.AccumulationChecker; /** * A test accumulation checker that implements a basic version of called-methods accumulation, @@ -10,13 +9,13 @@ */ public class TestAccumulationNoReturnsReceiverChecker extends AccumulationChecker { - /** - * Get the alias analyses that this checker should employ. - * - * @return the alias analyses - */ - @Override - protected EnumSet createAliasAnalyses() { - return EnumSet.noneOf(AliasAnalysis.class); - } + /** + * Get the alias analyses that this checker should employ. + * + * @return the alias analyses + */ + @Override + protected EnumSet createAliasAnalyses() { + return EnumSet.noneOf(AliasAnalysis.class); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java index cf8fe75caf5..7a3cfb38143 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java @@ -7,12 +7,12 @@ * checker without support for the Returns Receiver Checker. */ public class TestAccumulationNoReturnsReceiverTransfer extends TestAccumulationTransfer { - /** - * default constructor - * - * @param analysis the analysis - */ - public TestAccumulationNoReturnsReceiverTransfer(AccumulationAnalysis analysis) { - super(analysis); - } + /** + * default constructor + * + * @param analysis the analysis + */ + public TestAccumulationNoReturnsReceiverTransfer(AccumulationAnalysis analysis) { + super(analysis); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java index e0d064da34c..a7ad023d24a 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java @@ -12,25 +12,25 @@ /** A basic transfer function that accumulates the names of methods called. */ public class TestAccumulationTransfer extends AccumulationTransfer { - /** - * default constructor - * - * @param analysis the analysis - */ - public TestAccumulationTransfer(AccumulationAnalysis analysis) { - super(analysis); - } + /** + * default constructor + * + * @param analysis the analysis + */ + public TestAccumulationTransfer(AccumulationAnalysis analysis) { + super(analysis); + } - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput input) { - TransferResult result = - super.visitMethodInvocation(node, input); - Node receiver = node.getTarget().getReceiver(); - if (receiver != null) { - String methodName = node.getTarget().getMethod().getSimpleName().toString(); - accumulate(receiver, result, methodName); - } - return result; + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput input) { + TransferResult result = + super.visitMethodInvocation(node, input); + Node receiver = node.getTarget().getReceiver(); + if (receiver != null) { + String methodName = node.getTarget().getMethod().getSimpleName().toString(); + accumulate(receiver, result, methodName); } + return result; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java index e3dbdd314a9..4d9378a3b6c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** Polymorphic qualifier for the test accumulation type system. */ @PolymorphicQualifier(TestAccumulation.class) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java index 26da670ab2d..2e876a0fa35 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** A test accumulation analysis qualifier. It accumulates generic strings. */ @Retention(RetentionPolicy.RUNTIME) @@ -14,10 +13,10 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy public @interface TestAccumulation { - /** - * Accumulated strings. - * - * @return the strings - */ - public String[] value() default {}; + /** + * Accumulated strings. + * + * @return the strings + */ + public String[] value() default {}; } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java index 6df1ac88fd6..824e22a54ca 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; /** A test bottom type for an accumulation type system. */ @SubtypeOf({TestAccumulation.class, TestAccumulationPredicate.class}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java index 2dfcdac1596..2056217faa4 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java @@ -1,22 +1,21 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** A test accumulation predicate annotation. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({TestAccumulation.class}) public @interface TestAccumulationPredicate { - /** - * A boolean expression indicating which values have been accumulated. - * - * @return a boolean expression indicating which values have been accumulated - * @checker_framework.manual #accumulation-qualifiers - */ - String value(); + /** + * A boolean expression indicating which values have been accumulated. + * + * @return a boolean expression indicating which values have been accumulated + * @checker_framework.manual #accumulation-qualifiers + */ + String value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java index 9644b64ee0f..d859f5fdacd 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.testchecker.typedeclbounds; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.typedeclbounds.quals.Bottom; @@ -7,20 +11,15 @@ import org.checkerframework.framework.testchecker.typedeclbounds.quals.S2; import org.checkerframework.framework.testchecker.typedeclbounds.quals.Top; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - public class TypeDeclBoundsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public TypeDeclBoundsAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + public TypeDeclBoundsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>(Arrays.asList(Top.class, Bottom.class, S1.class, S2.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>(Arrays.asList(Top.class, Bottom.class, S1.class, S2.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java index e9dbd293231..22b647976c4 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java @@ -4,12 +4,12 @@ import org.checkerframework.common.basetype.BaseTypeVisitor; public class TypeDeclBoundsVisitor extends BaseTypeVisitor { - public TypeDeclBoundsVisitor(BaseTypeChecker checker) { - super(checker); - } + public TypeDeclBoundsVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected TypeDeclBoundsAnnotatedTypeFactory createTypeFactory() { - return new TypeDeclBoundsAnnotatedTypeFactory(checker); - } + @Override + protected TypeDeclBoundsAnnotatedTypeFactory createTypeFactory() { + return new TypeDeclBoundsAnnotatedTypeFactory(checker); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java index 3368e84da73..1c38d5e6357 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** * Toy type system for testing impact of implicit java type conversion. diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java index 68508063e86..5b94b9717b0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.UpperBoundFor; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.UpperBoundFor; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java index 6d82e552151..161fa85f701 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java @@ -1,15 +1,14 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeKind; -import org.checkerframework.framework.qual.UpperBoundFor; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeKind; +import org.checkerframework.framework.qual.UpperBoundFor; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java index fa909669f56..eb27eadbec6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing impact of implicit java type conversion. diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java index 3a1e591d7a6..f181548ee12 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.testchecker.typedecldefault; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.typedecldefault.quals.PolyTypeDeclDefault; @@ -7,24 +11,19 @@ import org.checkerframework.framework.testchecker.typedecldefault.quals.TypeDeclDefaultMiddle; import org.checkerframework.framework.testchecker.typedecldefault.quals.TypeDeclDefaultTop; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - public class TypeDeclDefaultAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public TypeDeclDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public TypeDeclDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - TypeDeclDefaultTop.class, - TypeDeclDefaultMiddle.class, - TypeDeclDefaultBottom.class, - PolyTypeDeclDefault.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + TypeDeclDefaultTop.class, + TypeDeclDefaultMiddle.class, + TypeDeclDefaultBottom.class, + PolyTypeDeclDefault.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java index 178f737b9fb..71784ca50e0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** A polymorphic qualifier for the TypeDeclDefault type system. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java index 6427beba55b..a9ebbf764f7 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java @@ -1,15 +1,14 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; /** TypeDeclDefault bottom qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java index 574849132f0..7a9d17ccce5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** TypeDeclDefault middle qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java index a2372bef51f..40cde9d0392 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java @@ -1,14 +1,13 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** This is the top qualifier of the TypeDeclDefault type system. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java index a87f7541e97..c491909478e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.common.subtyping.qual.Unqualified; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.common.subtyping.qual.Unqualified; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface AnnoWithStringArg { - String value(); + String value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java index cd620b9aaa8..e452855cdc5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.common.subtyping.qual.Unqualified; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.common.subtyping.qual.Unqualified; +import org.checkerframework.framework.qual.SubtypeOf; /** Denotes an exception that is particularly important. */ @SubtypeOf(Unqualified.class) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java index 89e455aabc9..600cb2fdf2d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.util; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - /** Denotes an object with a representation that has been encrypted. */ @SubtypeOf(Unqualified.class) @DefaultFor({TypeUseLocation.LOWER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java index b9efaf21700..0b0cd24cfea 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; /** * A postcondition annotation to indicate that a method ensures certain expressions to be {@link @@ -19,5 +18,5 @@ @PostconditionAnnotation(qualifier = Odd.class) @InheritedAnnotation public @interface EnsuresOdd { - String[] value(); + String[] value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java index 81421991c7e..96d67c75e5d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * A conditional postcondition annotation to indicate that a method ensures certain expressions to @@ -19,7 +18,7 @@ @ConditionalPostconditionAnnotation(qualifier = Odd.class) @InheritedAnnotation public @interface EnsuresOddIf { - boolean result(); + boolean result(); - String[] expression(); + String[] expression(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java index 14f416636ac..f803f9841b5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.common.subtyping.qual.Unqualified; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.common.subtyping.qual.Unqualified; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java index ecf1e1eb4e8..4efbd5595ce 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java @@ -1,7 +1,13 @@ package org.checkerframework.framework.testchecker.util; import com.sun.source.tree.Tree; - +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -16,15 +22,6 @@ import org.checkerframework.framework.util.defaults.QualifierDefaults; import org.checkerframework.javacutil.AnnotationBuilder; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - /** * A simple checker used for testing the Checker Framework. It treats the {@code @Odd} and * {@code @Even} annotations as a subtype-style qualifiers with no special semantics. @@ -32,68 +29,62 @@ *

This checker should only be used for testing the framework. */ public final class EvenOddChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new TestVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new TestVisitor(this); + } } class TestVisitor extends BaseTypeVisitor { - public TestVisitor(BaseTypeChecker checker) { - super(checker); - } + public TestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected TestAnnotatedTypeFactory createTypeFactory() { - return new TestAnnotatedTypeFactory(checker); - } + @Override + protected TestAnnotatedTypeFactory createTypeFactory() { + return new TestAnnotatedTypeFactory(checker); + } - @Override - public boolean isValidUse( - AnnotatedDeclaredType type, AnnotatedDeclaredType useType, Tree tree) { - // TODO: super would result in error, because of default on classes. - return true; - } + @Override + public boolean isValidUse(AnnotatedDeclaredType type, AnnotatedDeclaredType useType, Tree tree) { + // TODO: super would result in error, because of default on classes. + return true; + } } class TestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - protected AnnotationMirror BOTTOM; + protected AnnotationMirror BOTTOM; - public TestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - Elements elements = processingEnv.getElementUtils(); - BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); + public TestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + Elements elements = processingEnv.getElementUtils(); + BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); - this.postInit(); - } + this.postInit(); + } - @Override - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); - AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); - defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); - } + @Override + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); + AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); + defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - Odd.class, - MonotonicOdd.class, - Even.class, - Unqualified.class, - Bottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList(Odd.class, MonotonicOdd.class, Even.class, Unqualified.class, Bottom.class)); + } - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this) { - @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); - } - }; - } + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this) { + @Override + protected QualifierKindHierarchy createQualifierKindHierarchy( + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); + } + }; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java index 8084faae6e0..33bdf4aec0d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java @@ -4,16 +4,6 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; import com.sun.tools.javac.tree.JCTree; - -import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.common.basetype.BaseTypeVisitor; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; - import java.io.File; import java.io.FileReader; import java.io.IOException; @@ -25,11 +15,18 @@ import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.tools.JavaFileObject; +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; /** * A specialized checker for testing purposes. It compares an expression's annotated type to an @@ -76,212 +73,210 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedOptions({"checker"}) public class FactoryTestChecker extends BaseTypeChecker { - SourceChecker checker; + SourceChecker checker; - @Override - public void initChecker() { - super.initChecker(); + @Override + public void initChecker() { + super.initChecker(); - // Find factory constructor - String checkerClassName = getOption("checker"); - try { - if (checkerClassName != null) { - Class checkerClass = Class.forName(checkerClassName); - Constructor constructor = checkerClass.getConstructor(); - Object o = constructor.newInstance(); - if (o instanceof SourceChecker) { - checker = (SourceChecker) o; - } - } - } catch (Exception e) { - throw new BugInCF("Couldn't load " + checkerClassName + " class."); + // Find factory constructor + String checkerClassName = getOption("checker"); + try { + if (checkerClassName != null) { + Class checkerClass = Class.forName(checkerClassName); + Constructor constructor = checkerClass.getConstructor(); + Object o = constructor.newInstance(); + if (o instanceof SourceChecker) { + checker = (SourceChecker) o; } + } + } catch (Exception e) { + throw new BugInCF("Couldn't load " + checkerClassName + " class."); } + } - /* - @Override - public AnnotatedTypeFactory createTypeFactory() { - return checker.createTypeFactory(); - }*/ + /* + @Override + public AnnotatedTypeFactory createTypeFactory() { + return checker.createTypeFactory(); + }*/ - @Override - public Properties getMessagesProperties() { - // We don't have any properties - Properties prop = new Properties(); - prop.setProperty( - "type.unexpected", - "unexpected type for the given tree%n" - + "Tree : %s%n" - + "Found : %s%n" - + "Expected : %s%n"); - return prop; - } + @Override + public Properties getMessagesProperties() { + // We don't have any properties + Properties prop = new Properties(); + prop.setProperty( + "type.unexpected", + "unexpected type for the given tree%n" + + "Tree : %s%n" + + "Found : %s%n" + + "Expected : %s%n"); + return prop; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ToStringVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ToStringVisitor(this); + } - /** Builds the expected type for the trees from the source file of the tree compilation unit. */ - // This method is extremely ugly - private Map buildExpected(CompilationUnitTree tree) { - Map expected = new HashMap<>(); - try { - JavaFileObject o = tree.getSourceFile(); - File sourceFile = new File(o.toUri()); - LineNumberReader reader = new LineNumberReader(new FileReader(sourceFile)); - String line = reader.readLine(); - Pattern prevsubtreePattern = Pattern.compile("\\s*///(.*)-:-(.*)"); - Pattern prevfulltreePattern = Pattern.compile("\\s*///(.*)"); - Pattern subtreePattern = Pattern.compile("(.*)///(.*)-:-(.*)"); - Pattern fulltreePattern = Pattern.compile("(.*)///(.*)"); - while (line != null) { - Matcher prevsubtreeMatcher = prevsubtreePattern.matcher(line); - Matcher prevfulltreeMatcher = prevfulltreePattern.matcher(line); - Matcher subtreeMatcher = subtreePattern.matcher(line); - Matcher fulltreeMatcher = fulltreePattern.matcher(line); - if (prevsubtreeMatcher.matches()) { - String treeString = prevsubtreeMatcher.group(1).trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber() + 1); - expected.put(treeSpec, canonizeTypeString(prevsubtreeMatcher.group(2))); - } else if (prevfulltreeMatcher.matches()) { - String treeString = reader.readLine().trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); - expected.put(treeSpec, canonizeTypeString(prevfulltreeMatcher.group(1))); - } else if (subtreeMatcher.matches()) { - String treeString = subtreeMatcher.group(2).trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); - expected.put(treeSpec, canonizeTypeString(subtreeMatcher.group(3))); - } else if (fulltreeMatcher.matches()) { - String treeString = fulltreeMatcher.group(1).trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); - expected.put(treeSpec, canonizeTypeString(fulltreeMatcher.group(2))); - } - line = reader.readLine(); - } - reader.close(); - } catch (IOException e) { - throw new BugInCF("Unexpected IOException!", e); + /** Builds the expected type for the trees from the source file of the tree compilation unit. */ + // This method is extremely ugly + private Map buildExpected(CompilationUnitTree tree) { + Map expected = new HashMap<>(); + try { + JavaFileObject o = tree.getSourceFile(); + File sourceFile = new File(o.toUri()); + LineNumberReader reader = new LineNumberReader(new FileReader(sourceFile)); + String line = reader.readLine(); + Pattern prevsubtreePattern = Pattern.compile("\\s*///(.*)-:-(.*)"); + Pattern prevfulltreePattern = Pattern.compile("\\s*///(.*)"); + Pattern subtreePattern = Pattern.compile("(.*)///(.*)-:-(.*)"); + Pattern fulltreePattern = Pattern.compile("(.*)///(.*)"); + while (line != null) { + Matcher prevsubtreeMatcher = prevsubtreePattern.matcher(line); + Matcher prevfulltreeMatcher = prevfulltreePattern.matcher(line); + Matcher subtreeMatcher = subtreePattern.matcher(line); + Matcher fulltreeMatcher = fulltreePattern.matcher(line); + if (prevsubtreeMatcher.matches()) { + String treeString = prevsubtreeMatcher.group(1).trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber() + 1); + expected.put(treeSpec, canonizeTypeString(prevsubtreeMatcher.group(2))); + } else if (prevfulltreeMatcher.matches()) { + String treeString = reader.readLine().trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); + expected.put(treeSpec, canonizeTypeString(prevfulltreeMatcher.group(1))); + } else if (subtreeMatcher.matches()) { + String treeString = subtreeMatcher.group(2).trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); + expected.put(treeSpec, canonizeTypeString(subtreeMatcher.group(3))); + } else if (fulltreeMatcher.matches()) { + String treeString = fulltreeMatcher.group(1).trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); + expected.put(treeSpec, canonizeTypeString(fulltreeMatcher.group(2))); } - return expected; + line = reader.readLine(); + } + reader.close(); + } catch (IOException e) { + throw new BugInCF("Unexpected IOException!", e); } + return expected; + } - /** A method to canonize the tree representation. */ - private static String canonizeTreeString(String str) { - String canon = str.trim(); - Pattern pattern = Pattern.compile("(@\\S+)\\(\\)"); - Matcher matcher = pattern.matcher(canon); - while (matcher.find()) { - canon = matcher.replaceFirst(matcher.group(1)); - matcher.reset(canon); - } - return canon.trim(); + /** A method to canonize the tree representation. */ + private static String canonizeTreeString(String str) { + String canon = str.trim(); + Pattern pattern = Pattern.compile("(@\\S+)\\(\\)"); + Matcher matcher = pattern.matcher(canon); + while (matcher.find()) { + canon = matcher.replaceFirst(matcher.group(1)); + matcher.reset(canon); } + return canon.trim(); + } - /** - * A method to canonize type string representation. It removes any unnecessary white spaces and - * finds the type simple name instead of the fully qualified name. - * - * @param str the type string representation - * @return a canonical representation of the type - */ - private static String canonizeTypeString(String str) { - String canon = str.trim(); - canon = canon.replaceAll("\\s+", " "); - // Remove spaces between [ ] - canon = canon.replaceAll("\\[\\s+", "["); - canon = canon.replaceAll("\\s+\\]", "]"); + /** + * A method to canonize type string representation. It removes any unnecessary white spaces and + * finds the type simple name instead of the fully qualified name. + * + * @param str the type string representation + * @return a canonical representation of the type + */ + private static String canonizeTypeString(String str) { + String canon = str.trim(); + canon = canon.replaceAll("\\s+", " "); + // Remove spaces between [ ] + canon = canon.replaceAll("\\[\\s+", "["); + canon = canon.replaceAll("\\s+\\]", "]"); - // Remove spaces between < > - canon = canon.replaceAll("<\\s+", "<"); - canon = canon.replaceAll("\\s+>", ">"); + // Remove spaces between < > + canon = canon.replaceAll("<\\s+", "<"); + canon = canon.replaceAll("\\s+>", ">"); - // Take simply names! - canon = canon.replaceAll("[^\\<]*\\.(?=\\w)", ""); - return canon; - } + // Take simply names! + canon = canon.replaceAll("[^\\<]*\\.(?=\\w)", ""); + return canon; + } - /** - * A data structure that encapsulate a string and the line number that string appears in the - * buffer - */ - private static class TreeSpec { - public final String treeString; - public final long lineNumber; + /** + * A data structure that encapsulate a string and the line number that string appears in the + * buffer + */ + private static class TreeSpec { + public final String treeString; + public final long lineNumber; - public TreeSpec(String treeString, long lineNumber) { - this.treeString = canonizeTreeString(treeString); - this.lineNumber = lineNumber; - } + public TreeSpec(String treeString, long lineNumber) { + this.treeString = canonizeTreeString(treeString); + this.lineNumber = lineNumber; + } - @Override - public int hashCode() { - return Objects.hash(treeString, lineNumber); - } + @Override + public int hashCode() { + return Objects.hash(treeString, lineNumber); + } - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof TreeSpec) { - TreeSpec other = (TreeSpec) o; - return treeString.equals(other.treeString) && lineNumber == other.lineNumber; - } - return false; - } + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof TreeSpec) { + TreeSpec other = (TreeSpec) o; + return treeString.equals(other.treeString) && lineNumber == other.lineNumber; + } + return false; + } - @Override - public String toString() { - return lineNumber + ":" + treeString; - } + @Override + public String toString() { + return lineNumber + ":" + treeString; } + } - /** - * A specialized visitor that compares the actual and expected types for the specified trees and - * report an error if they differ - */ - private class ToStringVisitor extends BaseTypeVisitor> { - Map expected; + /** + * A specialized visitor that compares the actual and expected types for the specified trees and + * report an error if they differ + */ + private class ToStringVisitor extends BaseTypeVisitor> { + Map expected; - public ToStringVisitor(BaseTypeChecker checker) { - super(checker); - this.expected = buildExpected(root); - } + public ToStringVisitor(BaseTypeChecker checker) { + super(checker); + this.expected = buildExpected(root); + } - @Override - public Void scan(Tree tree, Void p) { - if (TreeUtils.isExpressionTree(tree)) { - ExpressionTree expTree = (ExpressionTree) tree; - TreeSpec treeSpec = - new TreeSpec( - expTree.toString().trim(), - root.getLineMap().getLineNumber(((JCTree) expTree).pos)); - if (expected.containsKey(treeSpec)) { - String actualType = - canonizeTypeString(atypeFactory.getAnnotatedType(expTree).toString()); - String expectedType = expected.get(treeSpec); - if (!actualType.equals(expectedType)) { + @Override + public Void scan(Tree tree, Void p) { + if (TreeUtils.isExpressionTree(tree)) { + ExpressionTree expTree = (ExpressionTree) tree; + TreeSpec treeSpec = + new TreeSpec( + expTree.toString().trim(), root.getLineMap().getLineNumber(((JCTree) expTree).pos)); + if (expected.containsKey(treeSpec)) { + String actualType = canonizeTypeString(atypeFactory.getAnnotatedType(expTree).toString()); + String expectedType = expected.get(treeSpec); + if (!actualType.equals(expectedType)) { - // The key is added above using a setProperty call, which is not supported - // by the CompilerMessagesChecker. - @SuppressWarnings("compilermessages") - @CompilerMessageKey String key = "type.unexpected"; - FactoryTestChecker.this.reportError( - tree, key, tree.toString(), actualType, expectedType); - } - } - } - return super.scan(tree, p); + // The key is added above using a setProperty call, which is not supported + // by the CompilerMessagesChecker. + @SuppressWarnings("compilermessages") + @CompilerMessageKey String key = "type.unexpected"; + FactoryTestChecker.this.reportError( + tree, key, tree.toString(), actualType, expectedType); + } } + } + return super.scan(tree, p); } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java index 9930adf0690..4c16d26e7aa 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.testchecker.util; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.qual.Bottom; @@ -15,124 +22,109 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +public class FlowTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + protected final AnnotationMirror VALUE, BOTTOM, TOP; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; + public FlowTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + VALUE = AnnotationBuilder.fromClass(elements, ValueTypeAnno.class); + BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); + TOP = AnnotationBuilder.fromClass(elements, Unqualified.class); -public class FlowTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - protected final AnnotationMirror VALUE, BOTTOM, TOP; + this.postInit(); + } - public FlowTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - VALUE = AnnotationBuilder.fromClass(elements, ValueTypeAnno.class); - BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); - TOP = AnnotationBuilder.fromClass(elements, Unqualified.class); + @Override + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); + defs.addCheckedCodeDefault(TOP, TypeUseLocation.OTHERWISE); + } - this.postInit(); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + ValueTypeAnno.class, Odd.class, MonotonicOdd.class, Unqualified.class, Bottom.class)); + } - @Override - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); - defs.addCheckedCodeDefault(TOP, TypeUseLocation.OTHERWISE); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new FlowQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** FlowQualifierHierarchy: {@code @ValueTypeAnno(a) <: @ValueValueTypeAnno(b) iff a == b} */ + class FlowQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + final QualifierKind VALUE_KIND; + + /** + * Creates a FlowQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + public FlowQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, FlowTestAnnotatedTypeFactory.this); + this.VALUE_KIND = getQualifierKind(VALUE); } @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - ValueTypeAnno.class, - Odd.class, - MonotonicOdd.class, - Unqualified.class, - Bottom.class)); + protected QualifierKindHierarchy createQualifierKindHierarchy( + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); } @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new FlowQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + return AnnotationUtils.areSame(superAnno, subAnno); } - /** FlowQualifierHierarchy: {@code @ValueTypeAnno(a) <: @ValueValueTypeAnno(b) iff a == b} */ - class FlowQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - final QualifierKind VALUE_KIND; - - /** - * Creates a FlowQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - public FlowQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, FlowTestAnnotatedTypeFactory.this); - this.VALUE_KIND = getQualifierKind(VALUE); - } - - @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); - } - - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - return AnnotationUtils.areSame(superAnno, subAnno); - } - - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2) { - // Both are Value - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return TOP; - } - } else if (qualifierKind1 == VALUE_KIND) { - return a1; - } else if (qualifierKind2 == VALUE_KIND) { - return a2; - } - throw new BugInCF( - "Unexpected annotations: leastUpperBoundWithElements(%s, %s)", a1, a2); + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2) { + // Both are Value + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return TOP; } + } else if (qualifierKind1 == VALUE_KIND) { + return a1; + } else if (qualifierKind2 == VALUE_KIND) { + return a2; + } + throw new BugInCF("Unexpected annotations: leastUpperBoundWithElements(%s, %s)", a1, a2); + } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2) { - // Both are Value - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return BOTTOM; - } - } else if (qualifierKind1 == VALUE_KIND) { - return a1; - } else if (qualifierKind2 == VALUE_KIND) { - return a2; - } - throw new BugInCF( - "Unexpected annotations: greatestLowerBoundWithElements(%s, %s)", a1, a2); + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2) { + // Both are Value + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return BOTTOM; } + } else if (qualifierKind1 == VALUE_KIND) { + return a1; + } else if (qualifierKind2 == VALUE_KIND) { + return a2; + } + throw new BugInCF("Unexpected annotations: greatestLowerBoundWithElements(%s, %s)", a1, a2); } + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java index 9abf5735d05..36a8492989a 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.common.subtyping.qual.Unqualified; -import org.checkerframework.framework.qual.MonotonicQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Target; +import org.checkerframework.common.subtyping.qual.Unqualified; +import org.checkerframework.framework.qual.MonotonicQualifier; +import org.checkerframework.framework.qual.SubtypeOf; @Inherited @SubtypeOf(Unqualified.class) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java index 4f1b97c10e6..fe5407621b4 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(MonotonicOdd.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java index 5fc6f75c472..07d863bf16f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf({PatternAB.class, PatternAC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java index ebb8ff02fee..dc2768014bc 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(PatternUnknown.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java index 44d5786391b..bdcef53aac1 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(PatternUnknown.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java index 9ac2275227d..6b2b13d7cb4 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf({PatternAB.class, PatternBC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java index 8d62e419fb1..b68509fa218 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(PatternUnknown.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java index fd016483cea..cd69141f598 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.util; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - @SubtypeOf({PatternA.class, PatternB.class, PatternC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java index 9b54506948b..ef12d93f792 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf({PatternBC.class, PatternAC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java index 9468b2ee663..43c21ca264e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java index dce7b1ee0d1..05ec1d312c2 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; @PolymorphicQualifier @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java index 20945ecd28f..81dafd5df51 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.PreconditionAnnotation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PreconditionAnnotation; /** * A precondition annotation to indicate that a method requires certain expressions to be {@link @@ -17,5 +16,5 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @PreconditionAnnotation(qualifier = Odd.class) public @interface RequiresOdd { - String[] value(); + String[] value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java index bfaf21a661e..db0eab4ddd5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java @@ -1,9 +1,8 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** A subtype of SuperQual. */ @SubtypeOf(SuperQual.class) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java index 8bb3ddd8b26..15d29f6356e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java @@ -1,10 +1,9 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** A supertype of SubQual. */ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java index 5e14b48fcf2..3d306397a52 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.util; -import org.checkerframework.common.subtyping.qual.Unqualified; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.common.subtyping.qual.Unqualified; +import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface ValueTypeAnno { - int value() default 0; + int value() default 0; } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java index 8398ca71912..80ab054ecbe 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java @@ -1,5 +1,9 @@ package org.checkerframework.framework.testchecker.variablenamedefault; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.variablenamedefault.quals.PolyVariableNameDefault; @@ -7,24 +11,19 @@ import org.checkerframework.framework.testchecker.variablenamedefault.quals.VariableNameDefaultMiddle; import org.checkerframework.framework.testchecker.variablenamedefault.quals.VariableNameDefaultTop; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - public class VariableNameDefaultAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public VariableNameDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public VariableNameDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - VariableNameDefaultTop.class, - VariableNameDefaultMiddle.class, - VariableNameDefaultBottom.class, - PolyVariableNameDefault.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + VariableNameDefaultTop.class, + VariableNameDefaultMiddle.class, + VariableNameDefaultBottom.class, + PolyVariableNameDefault.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java index c7cb0147bb2..7b148ede0e6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java @@ -1,12 +1,11 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** A polymorphic qualifier for the VariableNameDefault type system. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java index ed75447ea2c..436c04b92f5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; /** VariableNameDefault bottom qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java index 79bde50fd5c..ddba0352fdf 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; /** VariableNameDefault middle qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java index ddd3285d772..b58a1c34f25 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java @@ -1,13 +1,12 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** This is the top qualifier of the VariableNameDefault type system. */ @Documented diff --git a/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java b/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java index a17426e754a..ffdfa8f0cfe 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java @@ -1,16 +1,13 @@ package viewpointtest; +import java.lang.annotation.Annotation; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.AbstractViewpointAdapter; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.javacutil.AnnotationBuilder; - -import java.lang.annotation.Annotation; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; - import viewpointtest.quals.A; import viewpointtest.quals.B; import viewpointtest.quals.Bottom; @@ -22,42 +19,41 @@ /** The annotated type factory for the Viewpoint Test Checker. */ public class ViewpointTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@link Top} annotation. */ - public final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, Top.class); - - /** The {@link Lost} annotation. */ - public final AnnotationMirror LOST = AnnotationBuilder.fromClass(elements, Lost.class); - - /** - * Create a new ViewpointTestAnnotatedTypeFactory. - * - * @param checker the checker to which this annotated type factory belongs - */ - public ViewpointTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - A.class, - B.class, - Bottom.class, - PolyVP.class, - ReceiverDependentQual.class, - Lost.class, - Top.class); - } - - @Override - protected AbstractViewpointAdapter createViewpointAdapter() { - return new ViewpointTestViewpointAdapter(this); - } - - @Override - public QualifierHierarchy createQualifierHierarchy() { - return new ViewpointTestQualifierHierarchy( - this.getSupportedTypeQualifiers(), elements, this); - } + /** The {@link Top} annotation. */ + public final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, Top.class); + + /** The {@link Lost} annotation. */ + public final AnnotationMirror LOST = AnnotationBuilder.fromClass(elements, Lost.class); + + /** + * Create a new ViewpointTestAnnotatedTypeFactory. + * + * @param checker the checker to which this annotated type factory belongs + */ + public ViewpointTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + A.class, + B.class, + Bottom.class, + PolyVP.class, + ReceiverDependentQual.class, + Lost.class, + Top.class); + } + + @Override + protected AbstractViewpointAdapter createViewpointAdapter() { + return new ViewpointTestViewpointAdapter(this); + } + + @Override + public QualifierHierarchy createQualifierHierarchy() { + return new ViewpointTestQualifierHierarchy(this.getSupportedTypeQualifiers(), elements, this); + } } diff --git a/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java b/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java index b5dfaed769a..6b1aa74740b 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java @@ -1,41 +1,38 @@ package viewpointtest; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.type.NoElementQualifierHierarchy; -import org.checkerframework.framework.type.QualifierHierarchy; - import java.lang.annotation.Annotation; import java.util.Collection; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.util.Elements; - +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.type.NoElementQualifierHierarchy; +import org.checkerframework.framework.type.QualifierHierarchy; import viewpointtest.quals.Bottom; import viewpointtest.quals.Lost; /** The {@link QualifierHierarchy} for the Viewpoint Test Checker. */ public class ViewpointTestQualifierHierarchy extends NoElementQualifierHierarchy { - /** - * Creates a ViewpointTestQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - * @param atypeFactory the associated type factory - */ - public ViewpointTestQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, elements, atypeFactory); - } + /** + * Creates a ViewpointTestQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + * @param atypeFactory the associated type factory + */ + public ViewpointTestQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, elements, atypeFactory); + } - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - // Lost is not reflexive and the only subtype is Bottom. - if (atypeFactory.areSameByClass(superAnno, Lost.class) - && !atypeFactory.areSameByClass(subAnno, Bottom.class)) { - return false; - } - return super.isSubtypeQualifiers(subAnno, superAnno); + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + // Lost is not reflexive and the only subtype is Bottom. + if (atypeFactory.areSameByClass(superAnno, Lost.class) + && !atypeFactory.areSameByClass(subAnno, Bottom.class)) { + return false; } + return super.isSubtypeQualifiers(subAnno, superAnno); + } } diff --git a/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java b/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java index aae61833e10..3e0a7ec24ea 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java @@ -1,13 +1,11 @@ package viewpointtest; +import javax.lang.model.element.AnnotationMirror; import org.checkerframework.framework.type.AbstractViewpointAdapter; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; - -import javax.lang.model.element.AnnotationMirror; - import viewpointtest.quals.Lost; import viewpointtest.quals.ReceiverDependentQual; import viewpointtest.quals.Top; @@ -15,39 +13,38 @@ /** The viewpoint adapter for the Viewpoint Test Checker. */ public class ViewpointTestViewpointAdapter extends AbstractViewpointAdapter { - /** The {@link Top}, {@link ReceiverDependentQual} and {@link Lost} annotation. */ - private final AnnotationMirror TOP, RECEIVERDEPENDENTQUAL, LOST; - - /** - * The class constructor. - * - * @param atypeFactory the type factory to use - */ - public ViewpointTestViewpointAdapter(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - TOP = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).TOP; - RECEIVERDEPENDENTQUAL = - AnnotationBuilder.fromClass( - atypeFactory.getElementUtils(), ReceiverDependentQual.class); - LOST = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).LOST; - } - - @Override - protected AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm) { - return atm.getAnnotationInHierarchy(TOP); - } - - @Override - protected AnnotationMirror combineAnnotationWithAnnotation( - AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation) { - - if (AnnotationUtils.areSame(declaredAnnotation, RECEIVERDEPENDENTQUAL)) { - if (AnnotationUtils.areSame(receiverAnnotation, TOP)) { - return LOST; - } else { - return receiverAnnotation; - } - } - return declaredAnnotation; + /** The {@link Top}, {@link ReceiverDependentQual} and {@link Lost} annotation. */ + private final AnnotationMirror TOP, RECEIVERDEPENDENTQUAL, LOST; + + /** + * The class constructor. + * + * @param atypeFactory the type factory to use + */ + public ViewpointTestViewpointAdapter(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + TOP = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).TOP; + RECEIVERDEPENDENTQUAL = + AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), ReceiverDependentQual.class); + LOST = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).LOST; + } + + @Override + protected AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm) { + return atm.getAnnotationInHierarchy(TOP); + } + + @Override + protected AnnotationMirror combineAnnotationWithAnnotation( + AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation) { + + if (AnnotationUtils.areSame(declaredAnnotation, RECEIVERDEPENDENTQUAL)) { + if (AnnotationUtils.areSame(receiverAnnotation, TOP)) { + return LOST; + } else { + return receiverAnnotation; + } } + return declaredAnnotation; + } } diff --git a/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java b/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java index 928860b199d..7435f376383 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java @@ -1,28 +1,27 @@ package viewpointtest; import com.sun.source.tree.NewClassTree; - import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror; /** The visitor for the Viewpoint Test Checker. */ public class ViewpointTestVisitor extends BaseTypeVisitor { - /** - * Create a new ViewpointTestVisitor. - * - * @param checker the checker to which this visitor belongs - */ - public ViewpointTestVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Create a new ViewpointTestVisitor. + * + * @param checker the checker to which this visitor belongs + */ + public ViewpointTestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - AnnotatedTypeMirror Type = atypeFactory.getAnnotatedType(tree); - if (Type.hasAnnotation(atypeFactory.TOP) || Type.hasAnnotation(atypeFactory.LOST)) { - checker.reportError(tree, "new.class.type.invalid", Type.getAnnotations()); - } - return super.visitNewClass(tree, p); + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + AnnotatedTypeMirror Type = atypeFactory.getAnnotatedType(tree); + if (Type.hasAnnotation(atypeFactory.TOP) || Type.hasAnnotation(atypeFactory.LOST)) { + checker.reportError(tree, "new.class.type.invalid", Type.getAnnotations()); } + return super.visitNewClass(tree, p); + } } diff --git a/framework/src/test/java/viewpointtest/quals/A.java b/framework/src/test/java/viewpointtest/quals/A.java index 8bee242159e..45fe3e9b6ff 100644 --- a/framework/src/test/java/viewpointtest/quals/A.java +++ b/framework/src/test/java/viewpointtest/quals/A.java @@ -1,12 +1,11 @@ package viewpointtest.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** The A qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/B.java b/framework/src/test/java/viewpointtest/quals/B.java index 957096b3c9f..5199ecee3c7 100644 --- a/framework/src/test/java/viewpointtest/quals/B.java +++ b/framework/src/test/java/viewpointtest/quals/B.java @@ -1,12 +1,11 @@ package viewpointtest.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** The B qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/Bottom.java b/framework/src/test/java/viewpointtest/quals/Bottom.java index 1cdcd149e9d..2970528116f 100644 --- a/framework/src/test/java/viewpointtest/quals/Bottom.java +++ b/framework/src/test/java/viewpointtest/quals/Bottom.java @@ -1,14 +1,13 @@ package viewpointtest.quals; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; /** The Bottom qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/Lost.java b/framework/src/test/java/viewpointtest/quals/Lost.java index 38e215c5293..f20c1363fdb 100644 --- a/framework/src/test/java/viewpointtest/quals/Lost.java +++ b/framework/src/test/java/viewpointtest/quals/Lost.java @@ -1,12 +1,11 @@ package viewpointtest.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** * The Lost qualifier indicates that a relationship cannot be expressed. It is the result of diff --git a/framework/src/test/java/viewpointtest/quals/PolyVP.java b/framework/src/test/java/viewpointtest/quals/PolyVP.java index c89a201cc80..3236c0b1694 100644 --- a/framework/src/test/java/viewpointtest/quals/PolyVP.java +++ b/framework/src/test/java/viewpointtest/quals/PolyVP.java @@ -1,12 +1,11 @@ package viewpointtest.quals; -import org.checkerframework.framework.qual.PolymorphicQualifier; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; /** The PolyVP qualifier is a polymorphic qualifier in this hierarchy. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java b/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java index cb8adfc0635..7b0df660fac 100644 --- a/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java +++ b/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java @@ -1,12 +1,11 @@ package viewpointtest.quals; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; /** The ReceiverDependentQual qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/Top.java b/framework/src/test/java/viewpointtest/quals/Top.java index e98647e51f1..22df4b2fdc5 100644 --- a/framework/src/test/java/viewpointtest/quals/Top.java +++ b/framework/src/test/java/viewpointtest/quals/Top.java @@ -1,13 +1,12 @@ package viewpointtest.quals; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; /** The Top qualifier. */ @Documented diff --git a/framework/src/testannotations/java/android/support/annotation/IntRange.java b/framework/src/testannotations/java/android/support/annotation/IntRange.java index ab7bb212f6a..03f547e925b 100644 --- a/framework/src/testannotations/java/android/support/annotation/IntRange.java +++ b/framework/src/testannotations/java/android/support/annotation/IntRange.java @@ -7,7 +7,7 @@ @Documented @Retention(RetentionPolicy.CLASS) public @interface IntRange { - long from() default Long.MIN_VALUE; + long from() default Long.MIN_VALUE; - long to() default Long.MAX_VALUE; + long to() default Long.MAX_VALUE; } diff --git a/framework/tests/Issue6060.java b/framework/tests/Issue6060.java index cdaf6e276ae..d9ee30afd15 100644 --- a/framework/tests/Issue6060.java +++ b/framework/tests/Issue6060.java @@ -3,11 +3,11 @@ public interface Issue6060 extends Iterable { - default Spliterator spliterator() { - return Iterable.super.spliterator(); - } + default Spliterator spliterator() { + return Iterable.super.spliterator(); + } - default void forEach(Consumer action) { - Iterable.super.forEach(action); - } + default void forEach(Consumer action) { + Iterable.super.forEach(action); + } } diff --git a/framework/tests/accumulation-norr/SimpleFluent.java b/framework/tests/accumulation-norr/SimpleFluent.java index aed02fa7c16..ac5a4c12551 100644 --- a/framework/tests/accumulation-norr/SimpleFluent.java +++ b/framework/tests/accumulation-norr/SimpleFluent.java @@ -7,118 +7,119 @@ /* Simple inference of a fluent builder. */ public class SimpleFluent { - SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } - - @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } - - @This SimpleFluent a() { - return this; - } - - @This SimpleFluent b() { - return this; - } - - // intentionally does not have an @This annotation - SimpleFluent c() { - return this; - } - - static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().b().build(); - } - - static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { - s.a() - // :: error: (method.invocation.invalid) - .build(); - } - - static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { - s.a().b() - .c() - // :: error: (method.invocation.invalid) - .build(); - } - - static void mixFluentAndNonFluent(SimpleFluent s1) { - s1.a().b(); - // :: error: (method.invocation.invalid) - s1.build(); - } - - static void mixFluentAndNonFluentWrong(SimpleFluent s) { - s.a(); // .b() + SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } + + @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } + + @This SimpleFluent a() { + return this; + } + + @This SimpleFluent b() { + return this; + } + + // intentionally does not have an @This annotation + SimpleFluent c() { + return this; + } + + static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().b().build(); + } + + static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { + s.a() // :: error: (method.invocation.invalid) - s.build(); - } - - static void fluentLoop(SimpleFluent t) { - SimpleFluent s = t.a(); - int i = 10; - while (i > 0) { - // :: error: (method.invocation.invalid) - s.b().build(); - i--; - s = new SimpleFluent(); - } - } - - static void m1(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.c().a().b().build(); - } + .build(); + } - static void m2(SimpleFluent s) { - s.c().a().b(); + static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { + s.a() + .b() + .c() // :: error: (method.invocation.invalid) - s.c().build(); - } - - static void m3(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.c().a().b().build(); - // :: error: (method.invocation.invalid) - s.c().a().build(); - } - - static void m4(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.c().a().b().build2().build(); - } - - static void m5(SimpleFluent s) { - s.a().c(); - // :: error: (method.invocation.invalid) - s.b().build(); - } - - static void m6(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().c().b().build(); - } - - static void m7(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().b().build().c().b().build(); - } - - static void m8(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().build().c().a().b().build(); - // :: error: (method.invocation.invalid) - s.build(); - } - - static void m9() { - // :: error: (method.invocation.invalid) - new SimpleFluent().a().b().build(); - // :: error: (method.invocation.invalid) - new SimpleFluent().a().build(); - } + .build(); + } + + static void mixFluentAndNonFluent(SimpleFluent s1) { + s1.a().b(); + // :: error: (method.invocation.invalid) + s1.build(); + } + + static void mixFluentAndNonFluentWrong(SimpleFluent s) { + s.a(); // .b() + // :: error: (method.invocation.invalid) + s.build(); + } + + static void fluentLoop(SimpleFluent t) { + SimpleFluent s = t.a(); + int i = 10; + while (i > 0) { + // :: error: (method.invocation.invalid) + s.b().build(); + i--; + s = new SimpleFluent(); + } + } + + static void m1(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.c().a().b().build(); + } + + static void m2(SimpleFluent s) { + s.c().a().b(); + // :: error: (method.invocation.invalid) + s.c().build(); + } + + static void m3(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.c().a().b().build(); + // :: error: (method.invocation.invalid) + s.c().a().build(); + } + + static void m4(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.c().a().b().build2().build(); + } + + static void m5(SimpleFluent s) { + s.a().c(); + // :: error: (method.invocation.invalid) + s.b().build(); + } + + static void m6(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().c().b().build(); + } + + static void m7(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().b().build().c().b().build(); + } + + static void m8(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().build().c().a().b().build(); + // :: error: (method.invocation.invalid) + s.build(); + } + + static void m9() { + // :: error: (method.invocation.invalid) + new SimpleFluent().a().b().build(); + // :: error: (method.invocation.invalid) + new SimpleFluent().a().build(); + } } diff --git a/framework/tests/accumulation/Generics.java b/framework/tests/accumulation/Generics.java index 0fe6454874c..4fb90b5e5a3 100644 --- a/framework/tests/accumulation/Generics.java +++ b/framework/tests/accumulation/Generics.java @@ -1,44 +1,43 @@ -import org.checkerframework.common.returnsreceiver.qual.*; -import org.checkerframework.framework.testchecker.testaccumulation.qual.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.common.returnsreceiver.qual.*; +import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class Generics { - static interface Symbol { + static interface Symbol { - boolean isStatic(); + boolean isStatic(); - void finalize(@TestAccumulation("foo") Symbol this); - } + void finalize(@TestAccumulation("foo") Symbol this); + } - static List<@TestAccumulation("foo") Symbol> makeList(@TestAccumulation("foo") Symbol s) { - ArrayList<@TestAccumulation("foo") Symbol> l = new ArrayList<>(); - l.add(s); - return l; - } + static List<@TestAccumulation("foo") Symbol> makeList(@TestAccumulation("foo") Symbol s) { + ArrayList<@TestAccumulation("foo") Symbol> l = new ArrayList<>(); + l.add(s); + return l; + } - static void useList() { - Symbol s = null; - for (Symbol t : makeList(s)) { - t.finalize(); - } + static void useList() { + Symbol s = null; + for (Symbol t : makeList(s)) { + t.finalize(); } - - // reduced from real-world code - private <@TestAccumulation() T extends Symbol> T getMember(Class type, boolean b) { - if (b) { - T sym = getMember(type, !b); - if (sym != null && sym.isStatic()) { - return sym; - } - } else { - T sym = getMember(type, b); - if (sym != null) { - return sym; - } - } - return null; + } + + // reduced from real-world code + private <@TestAccumulation() T extends Symbol> T getMember(Class type, boolean b) { + if (b) { + T sym = getMember(type, !b); + if (sym != null && sym.isStatic()) { + return sym; + } + } else { + T sym = getMember(type, b); + if (sym != null) { + return sym; + } } + return null; + } } diff --git a/framework/tests/accumulation/Not.java b/framework/tests/accumulation/Not.java index 26e04a8b849..373f074cc1d 100644 --- a/framework/tests/accumulation/Not.java +++ b/framework/tests/accumulation/Not.java @@ -2,74 +2,74 @@ public class Not { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - void notA(@TestAccumulationPredicate("!a") Foo this) {} + void notA(@TestAccumulationPredicate("!a") Foo this) {} - void notB(@TestAccumulationPredicate("!b") Foo this) {} - } + void notB(@TestAccumulationPredicate("!b") Foo this) {} + } - void test1(Foo f) { - f.notA(); - f.notB(); - } + void test1(Foo f) { + f.notA(); + f.notB(); + } - void test2(Foo f) { - f.c(); - f.notA(); - f.notB(); - } + void test2(Foo f) { + f.c(); + f.notA(); + f.notB(); + } - void test3(Foo f) { - f.a(); - // :: error: method.invocation.invalid - f.notA(); - f.notB(); - } + void test3(Foo f) { + f.a(); + // :: error: method.invocation.invalid + f.notA(); + f.notB(); + } - void test4(Foo f) { - f.b(); - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test4(Foo f) { + f.b(); + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - // DEMONSTRATION OF "UNSOUNDNESS" - f.notA(); - } + void test6(Foo f) { + callA(f); + // DEMONSTRATION OF "UNSOUNDNESS" + f.notA(); + } - void test7(@TestAccumulation("a") Foo f) { - // :: error: method.invocation.invalid - f.notA(); - } + void test7(@TestAccumulation("a") Foo f) { + // :: error: method.invocation.invalid + f.notA(); + } - void test8(Foo f, boolean test) { - if (test) { - f.a(); - } else { - f.b(); - } - // DEMONSTRATION OF "UNSOUNDNESS" - f.notA(); + void test8(Foo f, boolean test) { + if (test) { + f.a(); + } else { + f.b(); } + // DEMONSTRATION OF "UNSOUNDNESS" + f.notA(); + } } diff --git a/framework/tests/accumulation/ObjectConstructionIssue20.java b/framework/tests/accumulation/ObjectConstructionIssue20.java index b7ff7e0fcf6..ee7d5e8e057 100644 --- a/framework/tests/accumulation/ObjectConstructionIssue20.java +++ b/framework/tests/accumulation/ObjectConstructionIssue20.java @@ -4,33 +4,33 @@ public class ObjectConstructionIssue20 { - private boolean enableProtoAnnotations; - - @SuppressWarnings({"unchecked"}) - private T getProtoExtension( - E element, GeneratedExtension extension) { - // Use this method as the chokepoint for all field annotations processing, so we can - // toggle on/off annotations processing in one place. - if (!enableProtoAnnotations) { - return null; - } - return (T) element.getOptionFields().get(extension.getDescriptor()); + private boolean enableProtoAnnotations; + + @SuppressWarnings({"unchecked"}) + private T getProtoExtension( + E element, GeneratedExtension extension) { + // Use this method as the chokepoint for all field annotations processing, so we can + // toggle on/off annotations processing in one place. + if (!enableProtoAnnotations) { + return null; } + return (T) element.getOptionFields().get(extension.getDescriptor()); + } - // stubs of relevant classes - private class Message {} + // stubs of relevant classes + private class Message {} - private class ProtoElement { - public Map getOptionFields() { - return null; - } + private class ProtoElement { + public Map getOptionFields() { + return null; } + } - private class FieldDescriptor {} + private class FieldDescriptor {} - private class GeneratedExtension { - public FieldDescriptor getDescriptor() { - return null; - } + private class GeneratedExtension { + public FieldDescriptor getDescriptor() { + return null; } + } } diff --git a/framework/tests/accumulation/Parens.java b/framework/tests/accumulation/Parens.java index 2e896cf9d2f..668e65a6272 100644 --- a/framework/tests/accumulation/Parens.java +++ b/framework/tests/accumulation/Parens.java @@ -1,5 +1,5 @@ public class Parens { - public synchronized void incrementPushed(long[] pushed, int operationType) { - ++(pushed[operationType]); - } + public synchronized void incrementPushed(long[] pushed, int operationType) { + ++(pushed[operationType]); + } } diff --git a/framework/tests/accumulation/Predicates.java b/framework/tests/accumulation/Predicates.java index 1aabe26775e..3b290a2f608 100644 --- a/framework/tests/accumulation/Predicates.java +++ b/framework/tests/accumulation/Predicates.java @@ -2,606 +2,606 @@ public class Predicates { - void testOr1() { - MyClass m1 = new MyClass(); + void testOr1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.c(); - } + // :: error: method.invocation.invalid + m1.c(); + } - void testOr2() { - MyClass m1 = new MyClass(); + void testOr2() { + MyClass m1 = new MyClass(); - m1.a(); - m1.c(); - } + m1.a(); + m1.c(); + } - void testOr3() { - MyClass m1 = new MyClass(); + void testOr3() { + MyClass m1 = new MyClass(); - m1.b(); - m1.c(); - } + m1.b(); + m1.c(); + } - void testAnd1() { - MyClass m1 = new MyClass(); + void testAnd1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.d(); - } + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd2() { - MyClass m1 = new MyClass(); + void testAnd2() { + MyClass m1 = new MyClass(); - m1.a(); - // :: error: method.invocation.invalid - m1.d(); - } + m1.a(); + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd3() { - MyClass m1 = new MyClass(); + void testAnd3() { + MyClass m1 = new MyClass(); - m1.b(); - // :: error: method.invocation.invalid - m1.d(); - } + m1.b(); + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd4() { - MyClass m1 = new MyClass(); + void testAnd4() { + MyClass m1 = new MyClass(); - m1.a(); - m1.c(); - // :: error: method.invocation.invalid - m1.d(); - } + m1.a(); + m1.c(); + // :: error: method.invocation.invalid + m1.d(); + } - void testAnd5() { - MyClass m1 = new MyClass(); + void testAnd5() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.d(); - } + m1.a(); + m1.b(); + m1.d(); + } - void testAnd6() { - MyClass m1 = new MyClass(); + void testAnd6() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.c(); - m1.d(); - } + m1.a(); + m1.b(); + m1.c(); + m1.d(); + } - void testAndOr1() { - MyClass m1 = new MyClass(); + void testAndOr1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.e(); - } + // :: error: method.invocation.invalid + m1.e(); + } - void testAndOr2() { - MyClass m1 = new MyClass(); + void testAndOr2() { + MyClass m1 = new MyClass(); - m1.a(); - m1.e(); - } + m1.a(); + m1.e(); + } - void testAndOr3() { - MyClass m1 = new MyClass(); + void testAndOr3() { + MyClass m1 = new MyClass(); - m1.b(); - // :: error: method.invocation.invalid - m1.e(); - } + m1.b(); + // :: error: method.invocation.invalid + m1.e(); + } - void testAndOr4() { - MyClass m1 = new MyClass(); + void testAndOr4() { + MyClass m1 = new MyClass(); - m1.b(); - m1.c(); - m1.e(); - } + m1.b(); + m1.c(); + m1.e(); + } - void testAndOr5() { - MyClass m1 = new MyClass(); + void testAndOr5() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.c(); - m1.d(); - m1.e(); - } + m1.a(); + m1.b(); + m1.c(); + m1.d(); + m1.e(); + } - void testPrecedence1() { - MyClass m1 = new MyClass(); + void testPrecedence1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.f(); - } + // :: error: method.invocation.invalid + m1.f(); + } - void testPrecedence2() { - MyClass m1 = new MyClass(); + void testPrecedence2() { + MyClass m1 = new MyClass(); - m1.a(); - // :: error: method.invocation.invalid - m1.f(); - } + m1.a(); + // :: error: method.invocation.invalid + m1.f(); + } - void testPrecedence3() { - MyClass m1 = new MyClass(); + void testPrecedence3() { + MyClass m1 = new MyClass(); - m1.b(); - // :: error: method.invocation.invalid - m1.f(); - } + m1.b(); + // :: error: method.invocation.invalid + m1.f(); + } - void testPrecedence4() { - MyClass m1 = new MyClass(); + void testPrecedence4() { + MyClass m1 = new MyClass(); - m1.a(); - m1.b(); - m1.f(); - } + m1.a(); + m1.b(); + m1.f(); + } - void testPrecedence5() { - MyClass m1 = new MyClass(); + void testPrecedence5() { + MyClass m1 = new MyClass(); - m1.a(); - m1.c(); - m1.f(); - } + m1.a(); + m1.c(); + m1.f(); + } - void testPrecedence6() { - MyClass m1 = new MyClass(); + void testPrecedence6() { + MyClass m1 = new MyClass(); - m1.b(); - m1.c(); - m1.f(); - } + m1.b(); + m1.c(); + m1.f(); + } + + private static class MyClass { - private static class MyClass { + @TestAccumulation("a") MyClass cmA; - @TestAccumulation("a") MyClass cmA; + @TestAccumulationPredicate("a") MyClass cmpA; - @TestAccumulationPredicate("a") MyClass cmpA; + @TestAccumulation({"a", "b"}) MyClass aB; + + @TestAccumulationPredicate("a || b") MyClass aOrB; + + @TestAccumulationPredicate("a && b") MyClass aAndB; + + @TestAccumulationPredicate("a || b && c") MyClass bAndCOrA; + + @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParens; + + @TestAccumulationPredicate("a && b || c") MyClass aAndBOrC; + + @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParens; + + @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndC; + + @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndA; + + @TestAccumulationPredicate("b && c") MyClass bAndC; + + @TestAccumulationPredicate("(b && c)") MyClass bAndCParens; + + void a() {} + + void b() {} + + void c(@TestAccumulationPredicate("a || b") MyClass this) {} + + void d(@TestAccumulationPredicate("a && b") MyClass this) {} + + void e(@TestAccumulationPredicate("a || (b && c)") MyClass this) {} + + void f(@TestAccumulationPredicate("a && b || c") MyClass this) {} + + static void testAssignability1(@TestAccumulationPredicate("a || b") MyClass cAble) { + cAble.c(); + // :: error: method.invocation.invalid + cAble.d(); + // :: error: method.invocation.invalid + cAble.e(); + // :: error: method.invocation.invalid + cAble.f(); + } + + static void testAssignability2(@TestAccumulationPredicate("a && b") MyClass dAble) { + // These calls would work if subtyping between predicates was by implication. They issue + // errors, because it is not. + // :: error: method.invocation.invalid + dAble.c(); + dAble.d(); + // :: error: method.invocation.invalid + dAble.e(); + // :: error: method.invocation.invalid + dAble.f(); + } - @TestAccumulation({"a", "b"}) MyClass aB; - - @TestAccumulationPredicate("a || b") MyClass aOrB; - - @TestAccumulationPredicate("a && b") MyClass aAndB; - - @TestAccumulationPredicate("a || b && c") MyClass bAndCOrA; - - @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParens; - - @TestAccumulationPredicate("a && b || c") MyClass aAndBOrC; - - @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParens; - - @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndC; - - @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndA; - - @TestAccumulationPredicate("b && c") MyClass bAndC; - - @TestAccumulationPredicate("(b && c)") MyClass bAndCParens; - - void a() {} - - void b() {} - - void c(@TestAccumulationPredicate("a || b") MyClass this) {} - - void d(@TestAccumulationPredicate("a && b") MyClass this) {} - - void e(@TestAccumulationPredicate("a || (b && c)") MyClass this) {} - - void f(@TestAccumulationPredicate("a && b || c") MyClass this) {} - - static void testAssignability1(@TestAccumulationPredicate("a || b") MyClass cAble) { - cAble.c(); - // :: error: method.invocation.invalid - cAble.d(); - // :: error: method.invocation.invalid - cAble.e(); - // :: error: method.invocation.invalid - cAble.f(); - } - - static void testAssignability2(@TestAccumulationPredicate("a && b") MyClass dAble) { - // These calls would work if subtyping between predicates was by implication. They issue - // errors, because it is not. - // :: error: method.invocation.invalid - dAble.c(); - dAble.d(); - // :: error: method.invocation.invalid - dAble.e(); - // :: error: method.invocation.invalid - dAble.f(); - } - - void testAllAssignability() { - - @TestAccumulation("a") MyClass cmALocal; - @TestAccumulationPredicate("a") MyClass cmpALocal; - @TestAccumulationPredicate("a || b") MyClass aOrBLocal; - @TestAccumulation({"a", "b"}) MyClass aBLocal; - @TestAccumulationPredicate("a && b") MyClass aAndBLocal; - @TestAccumulationPredicate("a || b && c") MyClass bAndCOrALocal; - @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; - @TestAccumulationPredicate("a && b || c") MyClass aAndBOrCLocal; - @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; - @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndCLocal; - @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndALocal; - @TestAccumulationPredicate("b && c") MyClass bAndCLocal; - @TestAccumulationPredicate("(b && c)") MyClass bAndCParensLocal; - - cmALocal = cmA; - cmALocal = cmpA; - // :: error: assignment.type.incompatible - cmALocal = aOrB; - cmALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = aAndB; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmALocal = bAndC; - // :: error: assignment.type.incompatible - cmALocal = bAndCParens; - - cmpALocal = cmA; - cmpALocal = cmpA; - // :: error: assignment.type.incompatible - cmpALocal = aOrB; - cmpALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = aAndB; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmpALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmpALocal = bAndC; - // :: error: assignment.type.incompatible - cmpALocal = bAndCParens; - - aOrBLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = cmpA; - aOrBLocal = aOrB; - aOrBLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrC; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aBLocal = cmA; - // :: error: (assignment.type.incompatible) - aBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aBLocal = aOrB; - aBLocal = aB; - aBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrB; - aAndBLocal = aB; - aAndBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCParens; - - bAndCOrALocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aOrB; - bAndCOrALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aAndB; - bAndCOrALocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCParens; - - bAndCOrAParensLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aOrB; - bAndCOrAParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCOrA; - bAndCOrAParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = aOrB; - aAndBOrCLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrAParens; - aAndBOrCLocal = aAndBOrC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = aOrB; - aAndBOrCParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrAParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndBOrC; - aAndBOrCParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrCParens; - aOrBAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmpA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrB; - bOrCAndALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bOrCAndALocal = aAndB; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrBAndC; - bOrCAndALocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCLocal = bOrCAndA; - bAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCParensLocal = bAndC; - bAndCParensLocal = bAndCParens; - } + void testAllAssignability() { + + @TestAccumulation("a") MyClass cmALocal; + @TestAccumulationPredicate("a") MyClass cmpALocal; + @TestAccumulationPredicate("a || b") MyClass aOrBLocal; + @TestAccumulation({"a", "b"}) MyClass aBLocal; + @TestAccumulationPredicate("a && b") MyClass aAndBLocal; + @TestAccumulationPredicate("a || b && c") MyClass bAndCOrALocal; + @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; + @TestAccumulationPredicate("a && b || c") MyClass aAndBOrCLocal; + @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; + @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndCLocal; + @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndALocal; + @TestAccumulationPredicate("b && c") MyClass bAndCLocal; + @TestAccumulationPredicate("(b && c)") MyClass bAndCParensLocal; + + cmALocal = cmA; + cmALocal = cmpA; + // :: error: assignment.type.incompatible + cmALocal = aOrB; + cmALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = aAndB; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmALocal = bAndC; + // :: error: assignment.type.incompatible + cmALocal = bAndCParens; + + cmpALocal = cmA; + cmpALocal = cmpA; + // :: error: assignment.type.incompatible + cmpALocal = aOrB; + cmpALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = aAndB; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmpALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmpALocal = bAndC; + // :: error: assignment.type.incompatible + cmpALocal = bAndCParens; + + aOrBLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = cmpA; + aOrBLocal = aOrB; + aOrBLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrC; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aBLocal = cmA; + // :: error: (assignment.type.incompatible) + aBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aBLocal = aOrB; + aBLocal = aB; + aBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrB; + aAndBLocal = aB; + aAndBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCParens; + + bAndCOrALocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aOrB; + bAndCOrALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aAndB; + bAndCOrALocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCParens; + + bAndCOrAParensLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aOrB; + bAndCOrAParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCOrA; + bAndCOrAParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = aOrB; + aAndBOrCLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrAParens; + aAndBOrCLocal = aAndBOrC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = aOrB; + aAndBOrCParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrAParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndBOrC; + aAndBOrCParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrCParens; + aOrBAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmpA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrB; + bOrCAndALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bOrCAndALocal = aAndB; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrBAndC; + bOrCAndALocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCLocal = bOrCAndA; + bAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCParensLocal = bAndC; + bAndCParensLocal = bAndCParens; } + } } diff --git a/framework/tests/accumulation/SimpleFluent.java b/framework/tests/accumulation/SimpleFluent.java index b031c53283b..bad36328ea2 100644 --- a/framework/tests/accumulation/SimpleFluent.java +++ b/framework/tests/accumulation/SimpleFluent.java @@ -5,111 +5,112 @@ /* Simple inference of a fluent builder. */ public class SimpleFluent { - SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } - - @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } - - @This SimpleFluent a() { - return this; - } - - @This SimpleFluent b() { - return this; - } - - // intentionally does not have an @This annotation - SimpleFluent c() { - return this; - } - - static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { - s.a().b().build(); - } - - static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { - s.a() - // :: error: method.invocation.invalid - .build(); - } - - static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { - s.a().b() - .c() - // :: error: method.invocation.invalid - .build(); - } - - static void mixFluentAndNonFluent(SimpleFluent s1) { - s1.a().b(); - s1.build(); - } - - static void mixFluentAndNonFluentWrong(SimpleFluent s) { - s.a(); // .b() - // :: error: method.invocation.invalid - s.build(); - } + SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } - static void fluentLoop(SimpleFluent t) { - SimpleFluent s = t.a(); - int i = 10; - while (i > 0) { - // :: error: method.invocation.invalid - s.b().build(); - i--; - s = new SimpleFluent(); - } - } + @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } - static void m1(SimpleFluent s) { - s.c().a().b().build(); - } - - static void m2(SimpleFluent s) { - s.c().a().b(); - // :: error: method.invocation.invalid - s.c().build(); - } + @This SimpleFluent a() { + return this; + } - static void m3(SimpleFluent s) { - s.c().a().b().build(); - // :: error: method.invocation.invalid - s.c().a().build(); - } + @This SimpleFluent b() { + return this; + } - static void m4(SimpleFluent s) { - s.c().a().b().build2().build(); - } + // intentionally does not have an @This annotation + SimpleFluent c() { + return this; + } - static void m5(SimpleFluent s) { - s.a().c(); - s.b().build(); - } + static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { + s.a().b().build(); + } - static void m6(SimpleFluent s) { + static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { + s.a() // :: error: method.invocation.invalid - s.a().c().b().build(); - } + .build(); + } - static void m7(SimpleFluent s) { - // :: error: method.invocation.invalid - s.a().b().build().c().b().build(); - } - - static void m8(SimpleFluent s) { - // :: error: method.invocation.invalid - s.a().build().c().a().b().build(); + static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { + s.a() + .b() + .c() // :: error: method.invocation.invalid - s.build(); - } - - static void m9() { - new SimpleFluent().a().b().build(); - // :: error: method.invocation.invalid - new SimpleFluent().a().build(); - } + .build(); + } + + static void mixFluentAndNonFluent(SimpleFluent s1) { + s1.a().b(); + s1.build(); + } + + static void mixFluentAndNonFluentWrong(SimpleFluent s) { + s.a(); // .b() + // :: error: method.invocation.invalid + s.build(); + } + + static void fluentLoop(SimpleFluent t) { + SimpleFluent s = t.a(); + int i = 10; + while (i > 0) { + // :: error: method.invocation.invalid + s.b().build(); + i--; + s = new SimpleFluent(); + } + } + + static void m1(SimpleFluent s) { + s.c().a().b().build(); + } + + static void m2(SimpleFluent s) { + s.c().a().b(); + // :: error: method.invocation.invalid + s.c().build(); + } + + static void m3(SimpleFluent s) { + s.c().a().b().build(); + // :: error: method.invocation.invalid + s.c().a().build(); + } + + static void m4(SimpleFluent s) { + s.c().a().b().build2().build(); + } + + static void m5(SimpleFluent s) { + s.a().c(); + s.b().build(); + } + + static void m6(SimpleFluent s) { + // :: error: method.invocation.invalid + s.a().c().b().build(); + } + + static void m7(SimpleFluent s) { + // :: error: method.invocation.invalid + s.a().b().build().c().b().build(); + } + + static void m8(SimpleFluent s) { + // :: error: method.invocation.invalid + s.a().build().c().a().b().build(); + // :: error: method.invocation.invalid + s.build(); + } + + static void m9() { + new SimpleFluent().a().b().build(); + // :: error: method.invocation.invalid + new SimpleFluent().a().build(); + } } diff --git a/framework/tests/accumulation/SimpleInference.java b/framework/tests/accumulation/SimpleInference.java index 698c238e44c..9fabe3e3b6d 100644 --- a/framework/tests/accumulation/SimpleInference.java +++ b/framework/tests/accumulation/SimpleInference.java @@ -1,37 +1,37 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SimpleInference { - void build(@TestAccumulation({"a"}) SimpleInference this) {} - - void doublebuild(@TestAccumulation({"a", "b"}) SimpleInference this) {} - - void a() {} - - void b() {} - - static void doStuffCorrect() { - SimpleInference s = new SimpleInference(); - s.a(); - s.build(); - } - - static void doStuffCorrect2() { - SimpleInference s = new SimpleInference(); - s.a(); - s.b(); - s.doublebuild(); - } - - static void doStuffWrong() { - SimpleInference s = new SimpleInference(); - // :: error: method.invocation.invalid - s.build(); - } - - static void doStuffWrong2() { - SimpleInference s = new SimpleInference(); - s.a(); - // :: error: method.invocation.invalid - s.doublebuild(); - } + void build(@TestAccumulation({"a"}) SimpleInference this) {} + + void doublebuild(@TestAccumulation({"a", "b"}) SimpleInference this) {} + + void a() {} + + void b() {} + + static void doStuffCorrect() { + SimpleInference s = new SimpleInference(); + s.a(); + s.build(); + } + + static void doStuffCorrect2() { + SimpleInference s = new SimpleInference(); + s.a(); + s.b(); + s.doublebuild(); + } + + static void doStuffWrong() { + SimpleInference s = new SimpleInference(); + // :: error: method.invocation.invalid + s.build(); + } + + static void doStuffWrong2() { + SimpleInference s = new SimpleInference(); + s.a(); + // :: error: method.invocation.invalid + s.doublebuild(); + } } diff --git a/framework/tests/accumulation/SimpleInferenceMerge.java b/framework/tests/accumulation/SimpleInferenceMerge.java index de9f93e4c80..a74765d3ac1 100644 --- a/framework/tests/accumulation/SimpleInferenceMerge.java +++ b/framework/tests/accumulation/SimpleInferenceMerge.java @@ -1,37 +1,37 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SimpleInferenceMerge { - void build(@TestAccumulation({"a", "b"}) SimpleInferenceMerge this) {} + void build(@TestAccumulation({"a", "b"}) SimpleInferenceMerge this) {} - void a() {} + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - static void doStuffCorrectMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.a(); - s.c(); - } - s.build(); + static void doStuffCorrectMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.a(); + s.c(); } + s.build(); + } - static void doStuffWrongMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.c(); - } - // :: error: method.invocation.invalid - s.build(); + static void doStuffWrongMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.c(); } + // :: error: method.invocation.invalid + s.build(); + } } diff --git a/framework/tests/accumulation/SimplePolymorphic.java b/framework/tests/accumulation/SimplePolymorphic.java index fb48f988480..7d681d90f3c 100644 --- a/framework/tests/accumulation/SimplePolymorphic.java +++ b/framework/tests/accumulation/SimplePolymorphic.java @@ -3,17 +3,17 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SimplePolymorphic { - @PolyTestAccumulation Object id(@PolyTestAccumulation Object obj) { - return obj; - } + @PolyTestAccumulation Object id(@PolyTestAccumulation Object obj) { + return obj; + } - @TestAccumulation("foo") Object usePoly(@TestAccumulation("foo") Object obj) { - return id(obj); - } + @TestAccumulation("foo") Object usePoly(@TestAccumulation("foo") Object obj) { + return id(obj); + } - // Check that polymorphic supertype with accumulator type doesn't cause a crash. - void noCrashOnPolySuper(@TestAccumulation("foo") Object obj) { - // :: error: assignment.type.incompatible - @PolyTestAccumulation Object obj2 = obj; - } + // Check that polymorphic supertype with accumulator type doesn't cause a crash. + void noCrashOnPolySuper(@TestAccumulation("foo") Object obj) { + // :: error: assignment.type.incompatible + @PolyTestAccumulation Object obj2 = obj; + } } diff --git a/framework/tests/accumulation/SmallPredicate.java b/framework/tests/accumulation/SmallPredicate.java index d0e03a248f7..39f1577a63f 100644 --- a/framework/tests/accumulation/SmallPredicate.java +++ b/framework/tests/accumulation/SmallPredicate.java @@ -3,16 +3,16 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SmallPredicate { - void a() {} + void a() {} - void b() {} + void b() {} - void d(@TestAccumulationPredicate("a && b") SmallPredicate this) {} + void d(@TestAccumulationPredicate("a && b") SmallPredicate this) {} - static void test(SmallPredicate smallPredicate) { - smallPredicate.a(); - smallPredicate.b(); - @TestAccumulation({"a", "b"}) SmallPredicate p2 = smallPredicate; - smallPredicate.d(); - } + static void test(SmallPredicate smallPredicate) { + smallPredicate.a(); + smallPredicate.b(); + @TestAccumulation({"a", "b"}) SmallPredicate p2 = smallPredicate; + smallPredicate.d(); + } } diff --git a/framework/tests/accumulation/Subtyping.java b/framework/tests/accumulation/Subtyping.java index a723e742040..99ca343a1c7 100644 --- a/framework/tests/accumulation/Subtyping.java +++ b/framework/tests/accumulation/Subtyping.java @@ -4,70 +4,70 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class Subtyping { - void top(@TestAccumulation() Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") - // :: error: assignment.type.incompatible - Object o3 = o1; - @TestAccumulation("bar") - // :: error: assignment.type.incompatible - Object o4 = o1; - @TestAccumulation({"foo", "bar"}) - // :: error: assignment.type.incompatible - Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void top(@TestAccumulation() Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") + // :: error: assignment.type.incompatible + Object o3 = o1; + @TestAccumulation("bar") + // :: error: assignment.type.incompatible + Object o4 = o1; + @TestAccumulation({"foo", "bar"}) + // :: error: assignment.type.incompatible + Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void foo(@TestAccumulation("foo") Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") - // :: error: assignment.type.incompatible - Object o4 = o1; - @TestAccumulation({"foo", "bar"}) - // :: error: assignment.type.incompatible - Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void foo(@TestAccumulation("foo") Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") + // :: error: assignment.type.incompatible + Object o4 = o1; + @TestAccumulation({"foo", "bar"}) + // :: error: assignment.type.incompatible + Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void bar(@TestAccumulation("bar") Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") - // :: error: assignment.type.incompatible - Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) - // :: error: assignment.type.incompatible - Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void bar(@TestAccumulation("bar") Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") + // :: error: assignment.type.incompatible + Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) + // :: error: assignment.type.incompatible + Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void foobar(@TestAccumulation({"foo", "bar"}) Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void foobar(@TestAccumulation({"foo", "bar"}) Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void barfoo(@TestAccumulation({"bar", "foo"}) Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void barfoo(@TestAccumulation({"bar", "foo"}) Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void bot(@TestAccumulationBottom Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) Object o5 = o1; - @TestAccumulationBottom Object o6 = o1; - } + void bot(@TestAccumulationBottom Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) Object o5 = o1; + @TestAccumulationBottom Object o6 = o1; + } } diff --git a/framework/tests/accumulation/UnparsablePredicate.java b/framework/tests/accumulation/UnparsablePredicate.java index 33f4a428650..0d7227d081a 100644 --- a/framework/tests/accumulation/UnparsablePredicate.java +++ b/framework/tests/accumulation/UnparsablePredicate.java @@ -2,49 +2,49 @@ public class UnparsablePredicate { - // :: error: predicate.invalid - void unclosedOpen(@TestAccumulationPredicate("(foo && bar") Object x) {} + // :: error: predicate.invalid + void unclosedOpen(@TestAccumulationPredicate("(foo && bar") Object x) {} - // :: error: predicate.invalid - void unopenedClose(@TestAccumulationPredicate("foo || bar)") Object x) {} + // :: error: predicate.invalid + void unopenedClose(@TestAccumulationPredicate("foo || bar)") Object x) {} - // :: error: predicate.invalid - void badKeywords1(@TestAccumulationPredicate("foo OR bar") Object x) {} + // :: error: predicate.invalid + void badKeywords1(@TestAccumulationPredicate("foo OR bar") Object x) {} - // :: error: predicate.invalid - void badKeywords2(@TestAccumulationPredicate("foo AND bar") Object x) {} + // :: error: predicate.invalid + void badKeywords2(@TestAccumulationPredicate("foo AND bar") Object x) {} - // These tests check that valid Java identifiers don't cause problems - // when evaluating predicates. Examples of identifiers from - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 + // These tests check that valid Java identifiers don't cause problems + // when evaluating predicates. Examples of identifiers from + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 - void jls0Example(@TestAccumulationPredicate("String") Object x) {} + void jls0Example(@TestAccumulationPredicate("String") Object x) {} - void callJls0Example(@TestAccumulation("String") Object y) { - jls0Example(y); - } + void callJls0Example(@TestAccumulation("String") Object y) { + jls0Example(y); + } - void jls1Example(@TestAccumulationPredicate("i3") Object x) {} + void jls1Example(@TestAccumulationPredicate("i3") Object x) {} - void callJls1Example(@TestAccumulation("i3") Object y) { - jls1Example(y); - } + void callJls1Example(@TestAccumulation("i3") Object y) { + jls1Example(y); + } - void jls2Example(@TestAccumulationPredicate("αρετη") Object x) {} + void jls2Example(@TestAccumulationPredicate("αρετη") Object x) {} - void callJls2Example(@TestAccumulation("αρετη") Object y) { - jls2Example(y); - } + void callJls2Example(@TestAccumulation("αρετη") Object y) { + jls2Example(y); + } - void jls3Example(@TestAccumulationPredicate("MAX_VALUE") Object x) {} + void jls3Example(@TestAccumulationPredicate("MAX_VALUE") Object x) {} - void callJls3Example(@TestAccumulation("MAX_VALUE") Object y) { - jls3Example(y); - } + void callJls3Example(@TestAccumulation("MAX_VALUE") Object y) { + jls3Example(y); + } - void jls4Example(@TestAccumulationPredicate("isLetterOrDigit") Object x) {} + void jls4Example(@TestAccumulationPredicate("isLetterOrDigit") Object x) {} - void callJls4Example(@TestAccumulation("isLetterOrDigit") Object y) { - jls4Example(y); - } + void callJls4Example(@TestAccumulation("isLetterOrDigit") Object y) { + jls4Example(y); + } } diff --git a/framework/tests/accumulation/UseSimpleFluent.java b/framework/tests/accumulation/UseSimpleFluent.java index ddaab38a260..20d12968369 100644 --- a/framework/tests/accumulation/UseSimpleFluent.java +++ b/framework/tests/accumulation/UseSimpleFluent.java @@ -1,9 +1,9 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class UseSimpleFluent { - static void req(@TestAccumulation({"a", "b"}) SimpleFluent s) {} + static void req(@TestAccumulation({"a", "b"}) SimpleFluent s) {} - static void test() { - req(new SimpleFluent().a().b()); - } + static void test() { + req(new SimpleFluent().a().b()); + } } diff --git a/framework/tests/accumulation/Xor.java b/framework/tests/accumulation/Xor.java index 24c9223c3c3..804d3e389c3 100644 --- a/framework/tests/accumulation/Xor.java +++ b/framework/tests/accumulation/Xor.java @@ -2,64 +2,64 @@ public class Xor { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - // use a standard gate encoding for xor - void aXorB(@TestAccumulationPredicate("(a || b) && !(a && b)") Foo this) {} - } + // use a standard gate encoding for xor + void aXorB(@TestAccumulationPredicate("(a || b) && !(a && b)") Foo this) {} + } - void test1(Foo f) { - // :: error: method.invocation.invalid - f.aXorB(); - } + void test1(Foo f) { + // :: error: method.invocation.invalid + f.aXorB(); + } - void test2(Foo f) { - f.c(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test2(Foo f) { + f.c(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void test3(Foo f) { - f.a(); - f.aXorB(); - } + void test3(Foo f) { + f.a(); + f.aXorB(); + } - void test4(Foo f) { - f.b(); - f.aXorB(); - } + void test4(Foo f) { + f.b(); + f.aXorB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - f.b(); - // DEMONSTRATION OF "UNSOUNDNESS" - f.aXorB(); - } + void test6(Foo f) { + callA(f); + f.b(); + // DEMONSTRATION OF "UNSOUNDNESS" + f.aXorB(); + } - void test7(@TestAccumulation("a") Foo f) { - f.aXorB(); - } + void test7(@TestAccumulation("a") Foo f) { + f.aXorB(); + } - void test8(Foo f) { - callA(f); - // THIS IS AN UNAVOIDABLE FALSE POSITIVE - // :: error: method.invocation.invalid - f.aXorB(); - } + void test8(Foo f) { + callA(f); + // THIS IS AN UNAVOIDABLE FALSE POSITIVE + // :: error: method.invocation.invalid + f.aXorB(); + } } diff --git a/framework/tests/aggregate/AggregateBasicTest.java b/framework/tests/aggregate/AggregateBasicTest.java index a00d5b1442b..0d68191182a 100644 --- a/framework/tests/aggregate/AggregateBasicTest.java +++ b/framework/tests/aggregate/AggregateBasicTest.java @@ -1,12 +1,12 @@ public class AggregateBasicTest { - // Random code just to make sure that the aggregate design pattern - // does not throw any exceptions. - Object field = new Object(); - String[] array = {"hello", "world"}; + // Random code just to make sure that the aggregate design pattern + // does not throw any exceptions. + Object field = new Object(); + String[] array = {"hello", "world"}; - void foo(int arg) { - field = array[0]; - field.toString(); - int a = 1 + arg; - } + void foo(int arg) { + field = array[0]; + field.toString(); + int a = 1 + arg; + } } diff --git a/framework/tests/aggregate/JavaErrorTest.java b/framework/tests/aggregate/JavaErrorTest.java index 478fe489666..eb8776fa3e5 100644 --- a/framework/tests/aggregate/JavaErrorTest.java +++ b/framework/tests/aggregate/JavaErrorTest.java @@ -1,12 +1,12 @@ public class JavaErrorTest { - // Checking that if one checker finds a Java error - // and therefor does not set a root, then - // checkers relying on that checker should also not run. - // otherwise, it might access a subchecker whose "root" has not been set. - // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) - void foo() { - Object a; - // :: error: variable a is already defined in method foo() - Object a; - } + // Checking that if one checker finds a Java error + // and therefor does not set a root, then + // checkers relying on that checker should also not run. + // otherwise, it might access a subchecker whose "root" has not been set. + // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) + void foo() { + Object a; + // :: error: variable a is already defined in method foo() + Object a; + } } diff --git a/framework/tests/aggregate/MultiError.java b/framework/tests/aggregate/MultiError.java index c7e577ec9f9..71e9dce705d 100644 --- a/framework/tests/aggregate/MultiError.java +++ b/framework/tests/aggregate/MultiError.java @@ -3,18 +3,18 @@ import org.checkerframework.common.value.qual.StringVal; public class MultiError { - // Testing that errors from multiple checkers are issued - // on the same compilation unit - // :: error: (unique.location.forbidden) - @Unique String[] array; + // Testing that errors from multiple checkers are issued + // on the same compilation unit + // :: error: (unique.location.forbidden) + @Unique String[] array; - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "goodbye"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "goodbye"; - @MethodVal( - className = "c", - methodName = "m", - params = {0, 0}) - // :: error: (invalid.methodval) - Object o; + @MethodVal( + className = "c", + methodName = "m", + params = {0, 0}) + // :: error: (invalid.methodval) + Object o; } diff --git a/framework/tests/aliasing/AliasingConstructorTest.java b/framework/tests/aliasing/AliasingConstructorTest.java index a8a0e7b29b3..28d1d580324 100644 --- a/framework/tests/aliasing/AliasingConstructorTest.java +++ b/framework/tests/aliasing/AliasingConstructorTest.java @@ -2,22 +2,22 @@ public class AliasingConstructorTest { - public AliasingConstructorTest(@NonLeaked Object o) {} + public AliasingConstructorTest(@NonLeaked Object o) {} - // int and String parameters on the constructors below are used only - // to make a distinction among constructors. - public AliasingConstructorTest(@LeakedToResult Object o, int i) {} + // int and String parameters on the constructors below are used only + // to make a distinction among constructors. + public AliasingConstructorTest(@LeakedToResult Object o, int i) {} - public AliasingConstructorTest(Object o, String s) {} + public AliasingConstructorTest(Object o, String s) {} - public void annosInAliasingConstructorTest() { - @Unique Object o = new Object(); - new AliasingConstructorTest(o); - Object o2 = new Object(); - new AliasingConstructorTest(o2, 1); - AliasingConstructorTest ct = new AliasingConstructorTest(o2, 1); - @Unique Object o3 = new Object(); - // ::error: (unique.leaked) - new AliasingConstructorTest(o3, "someString"); - } + public void annosInAliasingConstructorTest() { + @Unique Object o = new Object(); + new AliasingConstructorTest(o); + Object o2 = new Object(); + new AliasingConstructorTest(o2, 1); + AliasingConstructorTest ct = new AliasingConstructorTest(o2, 1); + @Unique Object o3 = new Object(); + // ::error: (unique.leaked) + new AliasingConstructorTest(o3, "someString"); + } } diff --git a/framework/tests/aliasing/ArrayInitializerTest.java b/framework/tests/aliasing/ArrayInitializerTest.java index deddd1de048..e63fb40ebd0 100644 --- a/framework/tests/aliasing/ArrayInitializerTest.java +++ b/framework/tests/aliasing/ArrayInitializerTest.java @@ -2,17 +2,17 @@ public class ArrayInitializerTest { - void foo() { - @Unique Object o = new Object(); - // :: error: (unique.leaked) - Object[] ar = new Object[] {o}; + void foo() { + @Unique Object o = new Object(); + // :: error: (unique.leaked) + Object[] ar = new Object[] {o}; - @Unique Object o2 = new Object(); - // :: error: (unique.leaked) - Object @Unique [] ar2 = new Object[] {o2}; + @Unique Object o2 = new Object(); + // :: error: (unique.leaked) + Object @Unique [] ar2 = new Object[] {o2}; - Object[] arr = new Object[] {new Object()}; + Object[] arr = new Object[] {new Object()}; - Object @Unique [] arrr = new Object[2]; - } + Object @Unique [] arrr = new Object[2]; + } } diff --git a/framework/tests/aliasing/CatchTest.java b/framework/tests/aliasing/CatchTest.java index 3868f683ab0..5543ece86eb 100644 --- a/framework/tests/aliasing/CatchTest.java +++ b/framework/tests/aliasing/CatchTest.java @@ -2,16 +2,16 @@ public class CatchTest { - void foo() { - @Unique Exception exVar = new Exception(); - try { - // :: error: (unique.leaked) - throw exVar; + void foo() { + @Unique Exception exVar = new Exception(); + try { + // :: error: (unique.leaked) + throw exVar; - // :: error: (exception.parameter.invalid) - } catch (@Unique Exception e) { - // exVar and e points to the same object, therefore catch clauses - // are not allowed to have a @Unique parameter. - } + // :: error: (exception.parameter.invalid) + } catch (@Unique Exception e) { + // exVar and e points to the same object, therefore catch clauses + // are not allowed to have a @Unique parameter. } + } } diff --git a/framework/tests/aliasing/EnumTest.java b/framework/tests/aliasing/EnumTest.java index 9571cf085c5..d4d0e1c59b2 100644 --- a/framework/tests/aliasing/EnumTest.java +++ b/framework/tests/aliasing/EnumTest.java @@ -1,4 +1,4 @@ public enum EnumTest { - val1, - val2 + val1, + val2 } diff --git a/framework/tests/aliasing/ExplicitAnnotationTest.java b/framework/tests/aliasing/ExplicitAnnotationTest.java index a3ae0bf779a..b45989654f5 100644 --- a/framework/tests/aliasing/ExplicitAnnotationTest.java +++ b/framework/tests/aliasing/ExplicitAnnotationTest.java @@ -1,15 +1,15 @@ import org.checkerframework.common.aliasing.qual.Unique; @Unique class UniqueData { - @SuppressWarnings("unique.leaked") - UniqueData() {} // All objects of UniqueData are now @Unique + @SuppressWarnings("unique.leaked") + UniqueData() {} // All objects of UniqueData are now @Unique } public class ExplicitAnnotationTest { - void check(UniqueData p) { // p is @Unique UniqueData Object - // :: error: (unique.leaked) - UniqueData y = p; // @Unique p is leaked - // :: error: (unique.leaked) - Object z = p; // @Unique p is leaked - } + void check(UniqueData p) { // p is @Unique UniqueData Object + // :: error: (unique.leaked) + UniqueData y = p; // @Unique p is leaked + // :: error: (unique.leaked) + Object z = p; // @Unique p is leaked + } } diff --git a/framework/tests/aliasing/ForbiddenUniqueTest.java b/framework/tests/aliasing/ForbiddenUniqueTest.java index 61098335473..f0294ecc962 100644 --- a/framework/tests/aliasing/ForbiddenUniqueTest.java +++ b/framework/tests/aliasing/ForbiddenUniqueTest.java @@ -1,21 +1,20 @@ -import org.checkerframework.common.aliasing.qual.Unique; - import java.util.List; +import org.checkerframework.common.aliasing.qual.Unique; public class ForbiddenUniqueTest { - // :: error: (unique.location.forbidden) - @Unique int field; + // :: error: (unique.location.forbidden) + @Unique int field; - void notAllowed() { - // :: error: (unique.location.forbidden) - @Unique Object[] arr; - // :: error: (type.argument.type.incompatible) :: error: (unique.location.forbidden) - List<@Unique Object> list; - } + void notAllowed() { + // :: error: (unique.location.forbidden) + @Unique Object[] arr; + // :: error: (type.argument.type.incompatible) :: error: (unique.location.forbidden) + List<@Unique Object> list; + } - void allowed() { - Object @Unique [] ar; - @Unique List l; - } + void allowed() { + Object @Unique [] ar; + @Unique List l; + } } diff --git a/framework/tests/aliasing/ReceiverParameterTest.java b/framework/tests/aliasing/ReceiverParameterTest.java index 4e19d392922..940266d5b9d 100644 --- a/framework/tests/aliasing/ReceiverParameterTest.java +++ b/framework/tests/aliasing/ReceiverParameterTest.java @@ -2,59 +2,59 @@ public class ReceiverParameterTest { - public @Unique ReceiverParameterTest() { - nonLeaked(); - // :: error: (unique.leaked) - mayLeak(); - } - - public @Unique ReceiverParameterTest(int i) { - leakedToResult(); - // :: error: (unique.leaked) - ReceiverParameterTest b = leakedToResult(); - } - - public @Unique ReceiverParameterTest(String s) {} - - void receiverTest() { - ReceiverParameterTest rec = new ReceiverParameterTest("s"); // @Unique - isUnique(rec); - rec.leakedToResult(); - isUnique(rec); - ReceiverParameterTest other = rec.leakedToResult(); - // :: error: (argument.type.incompatible) - isUnique(rec); - // :: error: (argument.type.incompatible) - isUnique(other); - } - - void stubFileReceiverTest() { - // StringBuffer append(String s) @LeakedToResult; - StringBuffer sb = new StringBuffer(); - isUnique(sb); - sb.append("something"); - isUnique(sb); - StringBuffer sb2 = sb.append("something"); - // :: error: (argument.type.incompatible) - isUnique(sb); - // :: error: (argument.type.incompatible) - isUnique(sb2); - } - - ReceiverParameterTest leakedToResult(@LeakedToResult ReceiverParameterTest this) { - return this; - } - - void nonLeaked(@NonLeaked ReceiverParameterTest this) {} - - void mayLeak() {} - - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique ReceiverParameterTest s) {} - - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique String s) {} - - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique StringBuffer s) {} + public @Unique ReceiverParameterTest() { + nonLeaked(); + // :: error: (unique.leaked) + mayLeak(); + } + + public @Unique ReceiverParameterTest(int i) { + leakedToResult(); + // :: error: (unique.leaked) + ReceiverParameterTest b = leakedToResult(); + } + + public @Unique ReceiverParameterTest(String s) {} + + void receiverTest() { + ReceiverParameterTest rec = new ReceiverParameterTest("s"); // @Unique + isUnique(rec); + rec.leakedToResult(); + isUnique(rec); + ReceiverParameterTest other = rec.leakedToResult(); + // :: error: (argument.type.incompatible) + isUnique(rec); + // :: error: (argument.type.incompatible) + isUnique(other); + } + + void stubFileReceiverTest() { + // StringBuffer append(String s) @LeakedToResult; + StringBuffer sb = new StringBuffer(); + isUnique(sb); + sb.append("something"); + isUnique(sb); + StringBuffer sb2 = sb.append("something"); + // :: error: (argument.type.incompatible) + isUnique(sb); + // :: error: (argument.type.incompatible) + isUnique(sb2); + } + + ReceiverParameterTest leakedToResult(@LeakedToResult ReceiverParameterTest this) { + return this; + } + + void nonLeaked(@NonLeaked ReceiverParameterTest this) {} + + void mayLeak() {} + + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique ReceiverParameterTest s) {} + + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique String s) {} + + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique StringBuffer s) {} } diff --git a/framework/tests/aliasing/SuperTest.java b/framework/tests/aliasing/SuperTest.java index 4d6c3f93537..16086f41b0a 100644 --- a/framework/tests/aliasing/SuperTest.java +++ b/framework/tests/aliasing/SuperTest.java @@ -1,22 +1,22 @@ import org.checkerframework.common.aliasing.qual.Unique; class A { - static A lastA; + static A lastA; - public A() { - lastA = this; - } + public A() { + lastA = this; + } - public @Unique A(String s) {} + public @Unique A(String s) {} } class B extends A { - public @Unique B() { - // :: error: (unique.leaked) - super(); // "this" is aliased to A.lastA. - } + public @Unique B() { + // :: error: (unique.leaked) + super(); // "this" is aliased to A.lastA. + } - public @Unique B(String s) { - super(s); // no aliases created - } + public @Unique B(String s) { + super(s); // no aliases created + } } diff --git a/framework/tests/aliasing/ThrowTest.java b/framework/tests/aliasing/ThrowTest.java index 18dd55edc9a..8dad69e615f 100644 --- a/framework/tests/aliasing/ThrowTest.java +++ b/framework/tests/aliasing/ThrowTest.java @@ -2,13 +2,13 @@ public class ThrowTest { - void foo() throws Exception { - @Unique Exception e = new Exception(); - // :: error: (unique.leaked) - throw e; - } + void foo() throws Exception { + @Unique Exception e = new Exception(); + // :: error: (unique.leaked) + throw e; + } - void bar() throws Exception { - throw new Exception(); - } + void bar() throws Exception { + throw new Exception(); + } } diff --git a/framework/tests/aliasing/TypeRefinement.java b/framework/tests/aliasing/TypeRefinement.java index 40b8712c1d4..03135df0289 100644 --- a/framework/tests/aliasing/TypeRefinement.java +++ b/framework/tests/aliasing/TypeRefinement.java @@ -2,70 +2,70 @@ public class TypeRefinement { - /** - * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may - * lose its type refinement, before the LHS is type-refined. - * - *

The RHS always loses its type refinement (it is widened to @MaybeAliased, and its declared - * type must have been @MaybeAliased) except in the following cases: - * - *

    - *
  1. The RHS is a fresh expression. - *
  2. The LHS is a @NonLeaked formal parameter and the RHS is an argument in a method call or - * constructor invocation. - *
  3. The LHS is a @LeakedToResult formal parameter, the RHS is an argument in a method call - * or constructor invocation, and the method's return value is discarded. - *
      - */ + /** + * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may + * lose its type refinement, before the LHS is type-refined. + * + *

      The RHS always loses its type refinement (it is widened to @MaybeAliased, and its declared + * type must have been @MaybeAliased) except in the following cases: + * + *

        + *
      1. The RHS is a fresh expression. + *
      2. The LHS is a @NonLeaked formal parameter and the RHS is an argument in a method call or + * constructor invocation. + *
      3. The LHS is a @LeakedToResult formal parameter, the RHS is an argument in a method call or + * constructor invocation, and the method's return value is discarded. + *
          + */ - // Test cases for the Aliasing type refinement cases below. - // One method for each exception case. The usual case is tested in every method too. - // As annotated in stubfile.astub, String() has type @Unique @NonLeaked. + // Test cases for the Aliasing type refinement cases below. + // One method for each exception case. The usual case is tested in every method too. + // As annotated in stubfile.astub, String() has type @Unique @NonLeaked. - void rule1() { - String unique = new String(); - // unique is refined to @Unique here, according to the definition. - isUnique(unique); + void rule1() { + String unique = new String(); + // unique is refined to @Unique here, according to the definition. + isUnique(unique); - String notUnique = unique; // unique loses its refinement. + String notUnique = unique; // unique loses its refinement. - // :: error: (argument.type.incompatible) - isUnique(unique); - // :: error: (argument.type.incompatible) - isUnique(notUnique); - } + // :: error: (argument.type.incompatible) + isUnique(unique); + // :: error: (argument.type.incompatible) + isUnique(notUnique); + } - void rule2() { - String unique = new String(); + void rule2() { + String unique = new String(); - isUnique(unique); - nonLeaked(unique); - isUnique(unique); + isUnique(unique); + nonLeaked(unique); + isUnique(unique); - leaked(unique); - // :: error: (argument.type.incompatible) - isUnique(unique); - } + leaked(unique); + // :: error: (argument.type.incompatible) + isUnique(unique); + } - void rule3() { - String unique = new String(); - isUnique(unique); - leakedToResult(unique); - isUnique(unique); + void rule3() { + String unique = new String(); + isUnique(unique); + leakedToResult(unique); + isUnique(unique); - String notUnique = leakedToResult(unique); - // :: error: (argument.type.incompatible) - isUnique(unique); - } + String notUnique = leakedToResult(unique); + // :: error: (argument.type.incompatible) + isUnique(unique); + } - void nonLeaked(@NonLeaked String s) {} + void nonLeaked(@NonLeaked String s) {} - void leaked(String s) {} + void leaked(String s) {} - String leakedToResult(@LeakedToResult String s) { - return s; - } + String leakedToResult(@LeakedToResult String s) { + return s; + } - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique String s) {} + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique String s) {} } diff --git a/framework/tests/aliasing/UniqueAnnoTest.java b/framework/tests/aliasing/UniqueAnnoTest.java index f324adae341..816ae0ebfa2 100644 --- a/framework/tests/aliasing/UniqueAnnoTest.java +++ b/framework/tests/aliasing/UniqueAnnoTest.java @@ -2,39 +2,39 @@ public class UniqueAnnoTest { - // @Unique constructor - public @Unique UniqueAnnoTest() {} - - // @Unique constructor leaking the "this" reference. - // Each unique.leaked error is a leak. - public @Unique UniqueAnnoTest(int i) { - notLeaked(this); - leakedToResult(this); - // :: error: (unique.leaked) - UniqueAnnoTest b = leakedToResult(this); - - UniqueAnnoTest other = new UniqueAnnoTest(); - // :: error: (unique.leaked) - other = this; - // :: error: (unique.leaked) - leaked(this); - // :: error: (unique.leaked) - leaked(other); // The receiver parameter is "this", so there is a leak. - } - - // Not @Unique constructor. No warnings. - public UniqueAnnoTest(int i1, int i2) { - UniqueAnnoTest other = new UniqueAnnoTest(); - other = this; - notLeaked(this); - } - - void leaked(UniqueAnnoTest a) {} - - void notLeaked(@NonLeaked UniqueAnnoTest this, @NonLeaked UniqueAnnoTest a) {} - - UniqueAnnoTest leakedToResult( - @LeakedToResult UniqueAnnoTest this, @LeakedToResult UniqueAnnoTest a) { - return a; - } + // @Unique constructor + public @Unique UniqueAnnoTest() {} + + // @Unique constructor leaking the "this" reference. + // Each unique.leaked error is a leak. + public @Unique UniqueAnnoTest(int i) { + notLeaked(this); + leakedToResult(this); + // :: error: (unique.leaked) + UniqueAnnoTest b = leakedToResult(this); + + UniqueAnnoTest other = new UniqueAnnoTest(); + // :: error: (unique.leaked) + other = this; + // :: error: (unique.leaked) + leaked(this); + // :: error: (unique.leaked) + leaked(other); // The receiver parameter is "this", so there is a leak. + } + + // Not @Unique constructor. No warnings. + public UniqueAnnoTest(int i1, int i2) { + UniqueAnnoTest other = new UniqueAnnoTest(); + other = this; + notLeaked(this); + } + + void leaked(UniqueAnnoTest a) {} + + void notLeaked(@NonLeaked UniqueAnnoTest this, @NonLeaked UniqueAnnoTest a) {} + + UniqueAnnoTest leakedToResult( + @LeakedToResult UniqueAnnoTest this, @LeakedToResult UniqueAnnoTest a) { + return a; + } } diff --git a/framework/tests/aliasing/UniqueConstructorTest.java b/framework/tests/aliasing/UniqueConstructorTest.java index fc2a4b6d8fa..5cc3a63fe88 100644 --- a/framework/tests/aliasing/UniqueConstructorTest.java +++ b/framework/tests/aliasing/UniqueConstructorTest.java @@ -2,22 +2,22 @@ public class UniqueConstructorTest { - @Unique UniqueConstructorTest() { - // Does not raise unique.leaked error since the parent constructor (Object) is unique - } + @Unique UniqueConstructorTest() { + // Does not raise unique.leaked error since the parent constructor (Object) is unique + } - class ParentClass extends UniqueConstructorTest { + class ParentClass extends UniqueConstructorTest { - ParentClass() { - // Does not raise unique.leaked error since the parent constructor is unique - } + ParentClass() { + // Does not raise unique.leaked error since the parent constructor is unique } + } - class ChildUniqueClass extends ParentClass { + class ChildUniqueClass extends ParentClass { - // ::error: (unique.leaked) - @Unique ChildUniqueClass() { - // Raises unique.leaked error since the parent constructor is not unique - } + // ::error: (unique.leaked) + @Unique ChildUniqueClass() { + // Raises unique.leaked error since the parent constructor is not unique } + } } diff --git a/framework/tests/all-systems/Annotations.java b/framework/tests/all-systems/Annotations.java index 67369607876..da2404b48f4 100644 --- a/framework/tests/all-systems/Annotations.java +++ b/framework/tests/all-systems/Annotations.java @@ -3,15 +3,15 @@ @interface Anno {} public class Annotations { - void takeAnnotation(Annotation a) {} + void takeAnnotation(Annotation a) {} - // test that a Tree works (source for Anno is in same compilation unit) - void takeTree(Anno a1) { - takeAnnotation(a1); - } + // test that a Tree works (source for Anno is in same compilation unit) + void takeTree(Anno a1) { + takeAnnotation(a1); + } - // test that an Element works (annotation only available from class file) - void takeElem(SuppressWarnings a2) { - takeAnnotation(a2); - } + // test that an Element works (annotation only available from class file) + void takeElem(SuppressWarnings a2) { + takeAnnotation(a2); + } } diff --git a/framework/tests/all-systems/AnonymousAndInnerClass.java b/framework/tests/all-systems/AnonymousAndInnerClass.java index 5e124d0f85a..1aca2ccc8ed 100644 --- a/framework/tests/all-systems/AnonymousAndInnerClass.java +++ b/framework/tests/all-systems/AnonymousAndInnerClass.java @@ -1,48 +1,48 @@ public class AnonymousAndInnerClass { - class MyInnerClass { - public MyInnerClass() {} - - public MyInnerClass(String s) {} - - public MyInnerClass(int... i) {} - - public MyInnerClass(AnonymousAndInnerClass c) {} - - public MyInnerClass(AnonymousAndInnerClass... o) {} - } - - static class MyClass { - public MyClass() {} - - public MyClass(String s) {} - - public MyClass(int... i) {} - } - - void test(AnonymousAndInnerClass outer, String tainted) { - new MyClass() {}; - new MyClass(tainted) {}; - new MyClass(1, 2, 3) {}; - new MyClass(1) {}; - new MyInnerClass() {}; - new MyInnerClass(tainted) {}; - new MyInnerClass(1) {}; - new MyInnerClass(1, 2, 3) {}; - new MyInnerClass(this) {}; - new MyInnerClass(this, this, this) {}; - this.new MyInnerClass() {}; - this.new MyInnerClass(tainted) {}; - this.new MyInnerClass(1) {}; - this.new MyInnerClass(1, 2, 3) {}; - this.new MyInnerClass(this) {}; - this.new MyInnerClass(outer, outer, outer) {}; - this.new MyInnerClass(this, this, this) {}; - outer.new MyInnerClass() {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(1) {}; - outer.new MyInnerClass(1, 2, 3) {}; - outer.new MyInnerClass(outer) {}; - outer.new MyInnerClass(outer, outer, outer) {}; - } + class MyInnerClass { + public MyInnerClass() {} + + public MyInnerClass(String s) {} + + public MyInnerClass(int... i) {} + + public MyInnerClass(AnonymousAndInnerClass c) {} + + public MyInnerClass(AnonymousAndInnerClass... o) {} + } + + static class MyClass { + public MyClass() {} + + public MyClass(String s) {} + + public MyClass(int... i) {} + } + + void test(AnonymousAndInnerClass outer, String tainted) { + new MyClass() {}; + new MyClass(tainted) {}; + new MyClass(1, 2, 3) {}; + new MyClass(1) {}; + new MyInnerClass() {}; + new MyInnerClass(tainted) {}; + new MyInnerClass(1) {}; + new MyInnerClass(1, 2, 3) {}; + new MyInnerClass(this) {}; + new MyInnerClass(this, this, this) {}; + this.new MyInnerClass() {}; + this.new MyInnerClass(tainted) {}; + this.new MyInnerClass(1) {}; + this.new MyInnerClass(1, 2, 3) {}; + this.new MyInnerClass(this) {}; + this.new MyInnerClass(outer, outer, outer) {}; + this.new MyInnerClass(this, this, this) {}; + outer.new MyInnerClass() {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(1) {}; + outer.new MyInnerClass(1, 2, 3) {}; + outer.new MyInnerClass(outer) {}; + outer.new MyInnerClass(outer, outer, outer) {}; + } } diff --git a/framework/tests/all-systems/AnonymousClasses.java b/framework/tests/all-systems/AnonymousClasses.java index 2832fa144fc..a37f2da6197 100644 --- a/framework/tests/all-systems/AnonymousClasses.java +++ b/framework/tests/all-systems/AnonymousClasses.java @@ -4,28 +4,28 @@ // Checkers may issue type checking errors for this class, but they should not crash @SuppressWarnings("all") public class AnonymousClasses { - // test anonymous classes - private void testAnonymous() { - Foo x = new Foo() {}; - new Object() { - public boolean equals(Object o) { - return true; - } - }.equals(null); + // test anonymous classes + private void testAnonymous() { + Foo x = new Foo() {}; + new Object() { + public boolean equals(Object o) { + return true; + } + }.equals(null); - Date d = new Date() {}; - } + Date d = new Date() {}; + } - private > void testGenericAnonymous() { - Gen g = new Gen() {}; - GenInter gi = new GenInter() {}; - } + private > void testGenericAnonymous() { + Gen g = new Gen() {}; + GenInter gi = new GenInter() {}; + } - class Gen { - public Gen() {} - } + class Gen { + public Gen() {} + } - interface GenInter {} + interface GenInter {} - interface Foo {} + interface Foo {} } diff --git a/framework/tests/all-systems/AnonymousFieldAccess.java b/framework/tests/all-systems/AnonymousFieldAccess.java index 04527b15229..852c094eee7 100644 --- a/framework/tests/all-systems/AnonymousFieldAccess.java +++ b/framework/tests/all-systems/AnonymousFieldAccess.java @@ -1,12 +1,12 @@ @SuppressWarnings("all") // Check for crashes. public class AnonymousFieldAccess { - static class SomeClass { - Object fieldInSomeClass; - } + static class SomeClass { + Object fieldInSomeClass; + } - void createTreeAnnotator() { - new SomeClass() { - Object f = fieldInSomeClass; - }; - } + void createTreeAnnotator() { + new SomeClass() { + Object f = fieldInSomeClass; + }; + } } diff --git a/framework/tests/all-systems/ArrayComparator.java b/framework/tests/all-systems/ArrayComparator.java index cd82a55e00e..07c7dd81372 100644 --- a/framework/tests/all-systems/ArrayComparator.java +++ b/framework/tests/all-systems/ArrayComparator.java @@ -5,9 +5,9 @@ @SuppressWarnings("allcheckers") // Only check for crashes class ArrayComparator { - private final SortedMap map = new TreeMap<>(); + private final SortedMap map = new TreeMap<>(); - public Comparator comparator() { - return map.comparator(); - } + public Comparator comparator() { + return map.comparator(); + } } diff --git a/framework/tests/all-systems/Arrays.java b/framework/tests/all-systems/Arrays.java index 2a8003cc2f7..89ba538ca51 100644 --- a/framework/tests/all-systems/Arrays.java +++ b/framework/tests/all-systems/Arrays.java @@ -1,27 +1,27 @@ public class Arrays { - public static final String[] RELATIONSHIP_LABELS = { - "SJJ", "SJU", "SUJ", "SUU", "DJJ", "DJU", "DUJ", "DUU", "JM", "UM", "MJ", "MU" - }; + public static final String[] RELATIONSHIP_LABELS = { + "SJJ", "SJU", "SUJ", "SUU", "DJJ", "DJU", "DUJ", "DUU", "JM", "UM", "MJ", "MU" + }; - public static final int[] ia = {1, -2, 3}; + public static final int[] ia = {1, -2, 3}; - // Note that "-1.0" is _NOT_ a double literal! It's a unary minus, - // that needs to be handled correctly. - public static final double[] elts_plus_minus_one_float = {-1.0, 1.0, +1.0, 1.0 / 2.0}; + // Note that "-1.0" is _NOT_ a double literal! It's a unary minus, + // that needs to be handled correctly. + public static final double[] elts_plus_minus_one_float = {-1.0, 1.0, +1.0, 1.0 / 2.0}; - String[] vis = new String[] {"a", "b"}; + String[] vis = new String[] {"a", "b"}; - @SuppressWarnings("nullness") // Don't want to depend on @Nullable - void m() { - class VarInfo {} - VarInfo v1 = null; - VarInfo v2 = null; - VarInfo[] vis = null; + @SuppressWarnings("nullness") // Don't want to depend on @Nullable + void m() { + class VarInfo {} + VarInfo v1 = null; + VarInfo v2 = null; + VarInfo[] vis = null; - if (v2 == null) { - vis = new VarInfo[] {v1}; - } else { - vis = new VarInfo[] {v1, v2}; - } + if (v2 == null) { + vis = new VarInfo[] {v1}; + } else { + vis = new VarInfo[] {v1, v2}; } + } } diff --git a/framework/tests/all-systems/AsSuperCrashes.java b/framework/tests/all-systems/AsSuperCrashes.java index 09343e440ad..eab7218e4f0 100644 --- a/framework/tests/all-systems/AsSuperCrashes.java +++ b/framework/tests/all-systems/AsSuperCrashes.java @@ -1,92 +1,91 @@ package assuper; -import org.checkerframework.dataflow.qual.Pure; - import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.Date; import java.util.List; +import org.checkerframework.dataflow.qual.Pure; // This class has code that used to cause AsSuper to crash @SuppressWarnings("all") public class AsSuperCrashes { - // TODO: Value Checker crashes on this - /* void primitiveNarrowing() { - Byte b = 100; - Character c = 100; - Short s = 100; - - byte bb = 100; - char cc = 100; - short ss = 100; - } - */ - - // test anonymous classes - private void testAnonymous() { - new Object() { - public boolean equals(Object o) { - return true; - } - }.equals(null); - - Date d = new Date() {}; + // TODO: Value Checker crashes on this + /* void primitiveNarrowing() { + Byte b = 100; + Character c = 100; + Short s = 100; + + byte bb = 100; + char cc = 100; + short ss = 100; + } + */ + + // test anonymous classes + private void testAnonymous() { + new Object() { + public boolean equals(Object o) { + return true; + } + }.equals(null); + + Date d = new Date() {}; + } + + private void apply(Field field) { + Class type = field.getType(); + type.getSuperclass().getName().equals("java.lang.Enum"); + } + + void arrayAsMethodReceiver(Object[] array) { + array.clone(); + } + + T lowerBoundedWildcard(java.util.List> l) { + lowerBoundedWildcard(new java.util.ArrayList()); + throw new Error(); + } + + // Test super() and this() + class Inner { + public Inner() { + super(); } - private void apply(Field field) { - Class type = field.getType(); - type.getSuperclass().getName().equals("java.lang.Enum"); + public Inner(int i) { + this(); } + } - void arrayAsMethodReceiver(Object[] array) { - array.clone(); - } + public static > void foo2(T a, T b) { + a.compareTo(b); + } - T lowerBoundedWildcard(java.util.List> l) { - lowerBoundedWildcard(new java.util.ArrayList()); - throw new Error(); - } + public static > void foo(T a, T b) { + a.compareTo(b); + } - // Test super() and this() - class Inner { - public Inner() { - super(); - } + interface Interface { + void compareTo(F t); + } - public Inner(int i) { - this(); - } - } + public void m1(Class c) { + Class x = c.asSubclass(I2.class); + new WeakReference>(c.asSubclass(I2.class)); + } - public static > void foo2(T a, T b) { - a.compareTo(b); - } + interface I2 {} - public static > void foo(T a, T b) { - a.compareTo(b); - } + @Pure + void bar() { + bar(); + } - interface Interface { - void compareTo(F t); - } + public static void copy(List dest, List src) { + dest.set(0, src.get(0)); + } - public void m1(Class c) { - Class x = c.asSubclass(I2.class); - new WeakReference>(c.asSubclass(I2.class)); - } - - interface I2 {} - - @Pure - void bar() { - bar(); - } - - public static void copy(List dest, List src) { - dest.set(0, src.get(0)); - } - - public static void copy2(List dest, List src) { - dest.set(0, src.get(0)); - } + public static void copy2(List dest, List src) { + dest.set(0, src.get(0)); + } } diff --git a/framework/tests/all-systems/AssertWithSideEffect.java b/framework/tests/all-systems/AssertWithSideEffect.java index a25775c593b..dfd0176263d 100644 --- a/framework/tests/all-systems/AssertWithSideEffect.java +++ b/framework/tests/all-systems/AssertWithSideEffect.java @@ -2,8 +2,8 @@ * in conditional mode, such as in the condition of an assert statement. */ public class AssertWithSideEffect { - void CheckAssert() { - boolean assert_enabled = false; - assert (assert_enabled = true); - } + void CheckAssert() { + boolean assert_enabled = false; + assert (assert_enabled = true); + } } diff --git a/framework/tests/all-systems/AssignmentContext.java b/framework/tests/all-systems/AssignmentContext.java index 62ce3b024be..5010640706a 100644 --- a/framework/tests/all-systems/AssignmentContext.java +++ b/framework/tests/all-systems/AssignmentContext.java @@ -3,27 +3,27 @@ @SuppressWarnings("nullness") // Don't want to depend on @Nullable public class AssignmentContext { - void foo(String[] a) {} + void foo(String[] a) {} - void t1(boolean b) { - String[] s = b ? new String[] {""} : null; - } + void t1(boolean b) { + String[] s = b ? new String[] {""} : null; + } - void t2(boolean b) { - foo(b ? new String[] {""} : null); - } + void t2(boolean b) { + foo(b ? new String[] {""} : null); + } - String[] t3(boolean b) { - return b ? new String[] {""} : null; - } + String[] t3(boolean b) { + return b ? new String[] {""} : null; + } - void t4(boolean b) { - String[] s = null; - s = b ? new String[] {""} : null; - } + void t4(boolean b) { + String[] s = null; + s = b ? new String[] {""} : null; + } - void assignToCast(String @MinLen(4) [] @MinLen(5) [] currentSample) { - // This statement used to cause a null pointer exception. - ((String @MinLen(5) []) currentSample[3])[4] = currentSample[3][4]; - } + void assignToCast(String @MinLen(4) [] @MinLen(5) [] currentSample) { + // This statement used to cause a null pointer exception. + ((String @MinLen(5) []) currentSample[3])[4] = currentSample[3][4]; + } } diff --git a/framework/tests/all-systems/BigBinaryTrees.java b/framework/tests/all-systems/BigBinaryTrees.java index 3e3ce130552..6c63c533abf 100644 --- a/framework/tests/all-systems/BigBinaryTrees.java +++ b/framework/tests/all-systems/BigBinaryTrees.java @@ -4,268 +4,267 @@ // Checkers may correctly issue errors, so suppress them. @SuppressWarnings("all") public class BigBinaryTrees { - String string1; - String string2; - String string3; + String string1; + String string2; + String string3; - public void testStrings() { - String s = - getClass().getName() - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3; - } + public void testStrings() { + String s = + getClass().getName() + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3; + } - void test() { - int i0 = 163; - int i1 = 153; - int i2 = 75; - int i3 = -72; - int i4 = 61; - int i5 = 7; - int i6 = 83; - int i7 = -36; - int i8 = -90; - int i9 = -93; - int i10 = 187; - int i11 = -76; - int i12 = -16; - int i13 = -99; - int i14 = 113; - int i15 = 72; - int i16 = 58; - int i17 = -97; - int i18 = 115; - int i19 = -85; - int i20 = 156; - int i21 = -10; - int i22 = -85; - int i23 = 81; - int i24 = 63; - int i25 = -49; - int i26 = 158; - int i27 = 158; - int i28 = 25; - int i29 = 136; - int i30 = -90; - int i31 = 115; - int i32 = 179; - int i33 = 11; - int i34 = -100; - int i35 = 70; - int i36 = -46; - int i37 = -56; - int i38 = 108; - int i39 = -41; - int i40 = 124; - int i41 = -88; - int i42 = 54; - int i43 = 117; - int i44 = -92; - int i45 = 7; - int i46 = -94; - int i47 = 162; - int i48 = -34; - int i49 = 104; - int i50 = 111; - int i51 = -16; - int i52 = 197; - int i53 = -8; - int i54 = 101; - int i55 = 96; - int i56 = 132; - int i57 = -36; - int i58 = 148; - int i59 = 43; - int i60 = -59; - int i61 = 150; - int i62 = 48; - int i63 = 130; - int i64 = 74; - int i65 = -1; - int i66 = 79; - int i67 = 109; - int i68 = -70; - int i69 = 111; - int i70 = 78; - int i71 = 155; - int i72 = 176; - int i73 = 80; - int i74 = 181; - int i75 = 41; - int i76 = -85; - int i77 = 189; - int i78 = 97; - int i79 = 139; - int i80 = 9; - int i81 = 42; - int i82 = -50; - int i83 = 82; - int i84 = -70; - int i85 = 162; - int i86 = -20; - int i87 = 52; - int i88 = -94; - int i89 = 133; - int i90 = 136; - int i91 = 129; - int i92 = -55; - int i93 = 153; - int i94 = 6; - int i95 = -18; - int i96 = 132; - int i97 = 45; - int i98 = 120; - int i99 = 60; - int result = - i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 + i12 + i13 + i14 + i15 - + i16 + i17 + i18 + i19 + i20 + i21 + i22 + i23 + i24 + i25 + i26 + i27 - + i28 + i29 + i30 + i31 + i32 + i33 + i34 + i35 + i36 + i37 + i38 + i39 - + i40 + i41 + i42 + i43 + i44 + i45 + i46 + i47 + i48 + i49 + i50 + i51 - + i52 + i53 + i54 + i55 + i56 + i57 + i58 + i59 + i60 + i61 + i62 + i63 - + i64 + i65 + i66 + i67 + i68 + i69 + i70 + i71 + i72 + i73 + i74 + i75 - + i76 + i77 + i78 + i79 + i80 + i81 + i82 + i83 + i84 + i85 + i86 + i87 - + i88 + i89 + i90 + i91 + i92 + i93 + i94 + i95 + i96 + i97 + i98 + i99; - } + void test() { + int i0 = 163; + int i1 = 153; + int i2 = 75; + int i3 = -72; + int i4 = 61; + int i5 = 7; + int i6 = 83; + int i7 = -36; + int i8 = -90; + int i9 = -93; + int i10 = 187; + int i11 = -76; + int i12 = -16; + int i13 = -99; + int i14 = 113; + int i15 = 72; + int i16 = 58; + int i17 = -97; + int i18 = 115; + int i19 = -85; + int i20 = 156; + int i21 = -10; + int i22 = -85; + int i23 = 81; + int i24 = 63; + int i25 = -49; + int i26 = 158; + int i27 = 158; + int i28 = 25; + int i29 = 136; + int i30 = -90; + int i31 = 115; + int i32 = 179; + int i33 = 11; + int i34 = -100; + int i35 = 70; + int i36 = -46; + int i37 = -56; + int i38 = 108; + int i39 = -41; + int i40 = 124; + int i41 = -88; + int i42 = 54; + int i43 = 117; + int i44 = -92; + int i45 = 7; + int i46 = -94; + int i47 = 162; + int i48 = -34; + int i49 = 104; + int i50 = 111; + int i51 = -16; + int i52 = 197; + int i53 = -8; + int i54 = 101; + int i55 = 96; + int i56 = 132; + int i57 = -36; + int i58 = 148; + int i59 = 43; + int i60 = -59; + int i61 = 150; + int i62 = 48; + int i63 = 130; + int i64 = 74; + int i65 = -1; + int i66 = 79; + int i67 = 109; + int i68 = -70; + int i69 = 111; + int i70 = 78; + int i71 = 155; + int i72 = 176; + int i73 = 80; + int i74 = 181; + int i75 = 41; + int i76 = -85; + int i77 = 189; + int i78 = 97; + int i79 = 139; + int i80 = 9; + int i81 = 42; + int i82 = -50; + int i83 = 82; + int i84 = -70; + int i85 = 162; + int i86 = -20; + int i87 = 52; + int i88 = -94; + int i89 = 133; + int i90 = 136; + int i91 = 129; + int i92 = -55; + int i93 = 153; + int i94 = 6; + int i95 = -18; + int i96 = 132; + int i97 = 45; + int i98 = 120; + int i99 = 60; + int result = + i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 + i12 + i13 + i14 + i15 + i16 + + i17 + i18 + i19 + i20 + i21 + i22 + i23 + i24 + i25 + i26 + i27 + i28 + i29 + i30 + + i31 + i32 + i33 + i34 + i35 + i36 + i37 + i38 + i39 + i40 + i41 + i42 + i43 + i44 + + i45 + i46 + i47 + i48 + i49 + i50 + i51 + i52 + i53 + i54 + i55 + i56 + i57 + i58 + + i59 + i60 + i61 + i62 + i63 + i64 + i65 + i66 + i67 + i68 + i69 + i70 + i71 + i72 + + i73 + i74 + i75 + i76 + i77 + i78 + i79 + i80 + i81 + i82 + i83 + i84 + i85 + i86 + + i87 + i88 + i89 + i90 + i91 + i92 + i93 + i94 + i95 + i96 + i97 + i98 + i99; + } } diff --git a/framework/tests/all-systems/BigString.java b/framework/tests/all-systems/BigString.java index 937096b1594..35cdd4fa6e7 100644 --- a/framework/tests/all-systems/BigString.java +++ b/framework/tests/all-systems/BigString.java @@ -1,254 +1,254 @@ public class BigString { - public static final String big = - "\u00e7\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" - + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" - + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" - + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" - + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" - + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" - + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" - + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" - + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" - + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" - + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" - + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" - + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" - + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" - + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" - + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" - + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" - + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" - + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" - + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" - + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" - + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" - + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" - + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" - + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" - + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" - + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" - + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" - + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" - + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" - + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" - + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" - + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" - + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" - + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" - + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" - + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" - + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" - + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" - + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" - + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" - + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" - + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" - + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" - + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" - + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" - + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" - + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" - + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" - + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6"; - public static final String bigger = - "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" - + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" - + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" - + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" - + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" - + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" - + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" - + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" - + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" - + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" - + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" - + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" - + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" - + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" - + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" - + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" - + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" - + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" - + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" - + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" - + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" - + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" - + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" - + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" - + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" - + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" - + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" - + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" - + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" - + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" - + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" - + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" - + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" - + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" - + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" - + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" - + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" - + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" - + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" - + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" - + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" - + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" - + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" - + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" - + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" - + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" - + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" - + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" - + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" - + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" - + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" - + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" - + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" - + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" - + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" - + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" - + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" - + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" - + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" - + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" - + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" - + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" - + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" - + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" - + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" - + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" - + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" - + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" - + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" - + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" - + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" - + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" - + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" - + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" - + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" - + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" - + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" - + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" - + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" - + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" - + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" - + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" - + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" - + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" - + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" - + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" - + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" - + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" - + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" - + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" - + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" - + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" - + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" - + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" - + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" - + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" - + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" - + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" - + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" - + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" - + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" - + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" - + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" - + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" - + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" - + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" - + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" - + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" - + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" - + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" - + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" - + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" - + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" - + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" - + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" - + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" - + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" - + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" - + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" - + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" - + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" - + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" - + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" - + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" - + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" - + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" - + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" - + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" - + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" - + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" - + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" - + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" - + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" - + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" - + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" - + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" - + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" - + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" - + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" - + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" - + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" - + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" - + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" - + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" - + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" - + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" - + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" - + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" - + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" - + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" - + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" - + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" - + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" - + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" - + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" - + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" - + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" - + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" - + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" - + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" - + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" - + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" - + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" - + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" - + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" - + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" - + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" - + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" - + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" - + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" - + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" - + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" - + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" - + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" - + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" - + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" - + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" - + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" - + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" - + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" - + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" - + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" - + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" - + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" - + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" - + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" - + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" - + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" - + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" - + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" - + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" - + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" - + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" - + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" - + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" - + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" - + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" - + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" - + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" - + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; + public static final String big = + "\u00e7\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" + + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" + + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" + + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" + + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" + + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" + + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" + + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" + + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" + + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" + + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" + + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" + + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" + + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" + + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" + + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" + + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" + + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" + + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" + + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" + + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" + + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" + + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" + + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" + + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" + + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" + + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" + + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" + + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" + + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" + + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" + + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" + + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" + + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" + + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" + + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" + + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" + + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" + + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" + + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" + + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" + + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" + + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" + + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" + + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" + + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" + + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" + + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" + + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" + + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6"; + public static final String bigger = + "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" + + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" + + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" + + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" + + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" + + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" + + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" + + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" + + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" + + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" + + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" + + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" + + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" + + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" + + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" + + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" + + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" + + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" + + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" + + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" + + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" + + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" + + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" + + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" + + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" + + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" + + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" + + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" + + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" + + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" + + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" + + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" + + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" + + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" + + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" + + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" + + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" + + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" + + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" + + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" + + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" + + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" + + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" + + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" + + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" + + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" + + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" + + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" + + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" + + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" + + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" + + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" + + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" + + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" + + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" + + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" + + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" + + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" + + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" + + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" + + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" + + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" + + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" + + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" + + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" + + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" + + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" + + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" + + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" + + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" + + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" + + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" + + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" + + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" + + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" + + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" + + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" + + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" + + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" + + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" + + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" + + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" + + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" + + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" + + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" + + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" + + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" + + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" + + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" + + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" + + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" + + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" + + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" + + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" + + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" + + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" + + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" + + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" + + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" + + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" + + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" + + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" + + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" + + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" + + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" + + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" + + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" + + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" + + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" + + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" + + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" + + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" + + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" + + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" + + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" + + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" + + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" + + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" + + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" + + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" + + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" + + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" + + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" + + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" + + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" + + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" + + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" + + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" + + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" + + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" + + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" + + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" + + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" + + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" + + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" + + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" + + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" + + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" + + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" + + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" + + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" + + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" + + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" + + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" + + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" + + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" + + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" + + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" + + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" + + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" + + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" + + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" + + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" + + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" + + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" + + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" + + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" + + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" + + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" + + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" + + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" + + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" + + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" + + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" + + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" + + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" + + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" + + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" + + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" + + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" + + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" + + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" + + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" + + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" + + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" + + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" + + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" + + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" + + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" + + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" + + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" + + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" + + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" + + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" + + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" + + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" + + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" + + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" + + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" + + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" + + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" + + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" + + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" + + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" + + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" + + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" + + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" + + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" + + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" + + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; } diff --git a/framework/tests/all-systems/CaptureMethodTypeArgs.java b/framework/tests/all-systems/CaptureMethodTypeArgs.java index 3157a10b46c..e4693d0e09d 100644 --- a/framework/tests/all-systems/CaptureMethodTypeArgs.java +++ b/framework/tests/all-systems/CaptureMethodTypeArgs.java @@ -2,15 +2,15 @@ @SuppressWarnings("all") // Just check for crashes. public class CaptureMethodTypeArgs { - static class MyClass {} + static class MyClass {} - private MyClass myClass; + private MyClass myClass; - void test(Class cls) { - Object o = method(cls.getComponentType()).myClass; - } + void test(Class cls) { + Object o = method(cls.getComponentType()).myClass; + } - static CaptureMethodTypeArgs method(Class cls) { - throw new RuntimeException(); - } + static CaptureMethodTypeArgs method(Class cls) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/Catch.java b/framework/tests/all-systems/Catch.java index 55fd9624825..6fe33c22022 100644 --- a/framework/tests/all-systems/Catch.java +++ b/framework/tests/all-systems/Catch.java @@ -1,17 +1,17 @@ public class Catch { - void defaultUnionType() throws Throwable { - try { - throw new Throwable(); - } catch (IndexOutOfBoundsException | NullPointerException ex) { + void defaultUnionType() throws Throwable { + try { + throw new Throwable(); + } catch (IndexOutOfBoundsException | NullPointerException ex) { - } } + } - void defaultDeclaredType() throws Throwable { - try { - throw new Throwable(); - } catch (RuntimeException ex) { + void defaultDeclaredType() throws Throwable { + try { + throw new Throwable(); + } catch (RuntimeException ex) { - } } + } } diff --git a/framework/tests/all-systems/ComplexPatternEscape.java b/framework/tests/all-systems/ComplexPatternEscape.java index 48ff75b2eb7..5aa37b0a6a5 100644 --- a/framework/tests/all-systems/ComplexPatternEscape.java +++ b/framework/tests/all-systems/ComplexPatternEscape.java @@ -4,10 +4,10 @@ public class ComplexPatternEscape { - private static final String DOT_DELIMITED_IDS = ""; + private static final String DOT_DELIMITED_IDS = ""; - // From - // https://github.com/randoop/randoop/blob/ffed1540721212adc55da179f1ae3b3df582d0d5/agent/replacecall/src/main/java/randoop/instrument/ReplacementFileReader.java#L58 - private static final String SIGNATURE_STRING = - DOT_DELIMITED_IDS + "(?:\\.)?" + "\\([^)]*\\)"; + // From + // https://github.com/randoop/randoop/blob/ffed1540721212adc55da179f1ae3b3df582d0d5/agent/replacecall/src/main/java/randoop/instrument/ReplacementFileReader.java#L58 + private static final String SIGNATURE_STRING = + DOT_DELIMITED_IDS + "(?:\\.)?" + "\\([^)]*\\)"; } diff --git a/framework/tests/all-systems/CompoundAssignments.java b/framework/tests/all-systems/CompoundAssignments.java index 09efb7bab88..c769e3d2074 100644 --- a/framework/tests/all-systems/CompoundAssignments.java +++ b/framework/tests/all-systems/CompoundAssignments.java @@ -1,22 +1,22 @@ public class CompoundAssignments { - static final int SIZE = 4; + static final int SIZE = 4; - // There used to be a bug creating the LeftShiftAssignmentNode where the target (e.g. pow) was - // replaced by the shift amount (e.g. 1). - static void left_shift_assign() { - for (int i = 0, pow = 1; i <= SIZE; i++) { - pow <<= 1; - } + // There used to be a bug creating the LeftShiftAssignmentNode where the target (e.g. pow) was + // replaced by the shift amount (e.g. 1). + static void left_shift_assign() { + for (int i = 0, pow = 1; i <= SIZE; i++) { + pow <<= 1; } + } - // There used to be a bug computing the JavaExpression for a widening - // conversion, such as widening sum to a double below. - static int sum_with_widening() { - double[] freq = new double[SIZE]; - int sum = 0; - for (int i = 0; i < SIZE; i++) { - sum += freq[i]; - } - return sum; + // There used to be a bug computing the JavaExpression for a widening + // conversion, such as widening sum to a double below. + static int sum_with_widening() { + double[] freq = new double[SIZE]; + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += freq[i]; } + return sum; + } } diff --git a/framework/tests/all-systems/ConditionalExpressions.java b/framework/tests/all-systems/ConditionalExpressions.java index 1ea0aa35688..e2b91395f22 100644 --- a/framework/tests/all-systems/ConditionalExpressions.java +++ b/framework/tests/all-systems/ConditionalExpressions.java @@ -6,52 +6,51 @@ import java.util.List; public class ConditionalExpressions { - public static boolean flag = true; + public static boolean flag = true; - class TypeVarTypeVar { - void foo1(T tExtendsNumber, S sExtendsT) { - T o = flag ? tExtendsNumber : sExtendsT; - } + class TypeVarTypeVar { + void foo1(T tExtendsNumber, S sExtendsT) { + T o = flag ? tExtendsNumber : sExtendsT; + } - void foo2(T tExtendsNumber, S sExtendsT) { - T o = flag ? tExtendsNumber : sExtendsT; - } + void foo2(T tExtendsNumber, S sExtendsT) { + T o = flag ? tExtendsNumber : sExtendsT; + } - void foo3(T tExtendsNumber, S sExtendsInteger) { - Number o3 = flag ? tExtendsNumber : sExtendsInteger; - } + void foo3(T tExtendsNumber, S sExtendsInteger) { + Number o3 = flag ? tExtendsNumber : sExtendsInteger; + } - void foo4( - T tExtendsNumber, S sExtendsCharSequence) { - Object o2 = flag ? tExtendsNumber : sExtendsCharSequence; - } + void foo4(T tExtendsNumber, S sExtendsCharSequence) { + Object o2 = flag ? tExtendsNumber : sExtendsCharSequence; + } - void foo5(T tExtendsLong, S sExtendsInt) { - Number o = flag ? tExtendsLong : sExtendsInt; - } + void foo5(T tExtendsLong, S sExtendsInt) { + Number o = flag ? tExtendsLong : sExtendsInt; } + } - class ArrayTypes { - void foo1(String string, String[] strings) { - Serializable o2 = (flag ? string : strings); - } + class ArrayTypes { + void foo1(String string, String[] strings) { + Serializable o2 = (flag ? string : strings); + } - void foo2(Integer[] integers, String[] strings) { - Object[] o = (flag ? integers : strings); - } + void foo2(Integer[] integers, String[] strings) { + Object[] o = (flag ? integers : strings); + } - void foo3(T ts, Number[] numbers) { - Cloneable o = flag ? ts : numbers; - } + void foo3(T ts, Number[] numbers) { + Cloneable o = flag ? ts : numbers; + } - void foo4(T ts, Number[] numbers) { - Object o = (flag ? ts : numbers); - } + void foo4(T ts, Number[] numbers) { + Object o = (flag ? ts : numbers); } + } - class Generics { - void foo1(List listS, List listI) { - Number s = (flag ? listI : listS).get(0); - } + class Generics { + void foo1(List listS, List listI) { + Number s = (flag ? listI : listS).get(0); } + } } diff --git a/framework/tests/all-systems/Crash.java b/framework/tests/all-systems/Crash.java index 85f66b050fe..89ab119340b 100644 --- a/framework/tests/all-systems/Crash.java +++ b/framework/tests/all-systems/Crash.java @@ -3,29 +3,29 @@ import wildcards.Crash.GenericAnnotatedTypeFactoryCrash; public class Crash> { - public abstract static class GenericAnnotatedTypeFactoryCrash< - Value extends CFAbstractValue, - Store extends CFAbstractStore, - TransferFunction extends CFAbstractTransfer, - FlowAnalysis extends CFAbstractAnalysis> { - abstract Store getRegularExitStore(); - } + public abstract static class GenericAnnotatedTypeFactoryCrash< + Value extends CFAbstractValue, + Store extends CFAbstractStore, + TransferFunction extends CFAbstractTransfer, + FlowAnalysis extends CFAbstractAnalysis> { + abstract Store getRegularExitStore(); + } - static class CFAbstractValue> {} + static class CFAbstractValue> {} - static class CFAbstractStore, S extends CFAbstractStore> {} + static class CFAbstractStore, S extends CFAbstractStore> {} - abstract static class CFAbstractTransfer< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> {} + abstract static class CFAbstractTransfer< + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> {} - public abstract static class CFAbstractAnalysis< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> {} + public abstract static class CFAbstractAnalysis< + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> {} - void use(Factory atypeFactory) { - CFAbstractStore exitStore = atypeFactory.getRegularExitStore(); - } + void use(Factory atypeFactory) { + CFAbstractStore exitStore = atypeFactory.getRegularExitStore(); + } } diff --git a/framework/tests/all-systems/CrazyEnum.java b/framework/tests/all-systems/CrazyEnum.java index fd9a9064b0d..492a2fd3edd 100644 --- a/framework/tests/all-systems/CrazyEnum.java +++ b/framework/tests/all-systems/CrazyEnum.java @@ -1,20 +1,20 @@ @SuppressWarnings("all") // Check for crashes. public class CrazyEnum { - private enum MyEnum { - ENUM_CONST1 { - private final String s = method(); + private enum MyEnum { + ENUM_CONST1 { + private final String s = method(); - private String method() { - return "hello"; - } - }, + private String method() { + return "hello"; + } + }, - ENUM_CONST2 { - private final String s = this.method(); + ENUM_CONST2 { + private final String s = this.method(); - private String method() { - return "hello"; - } - } + private String method() { + return "hello"; + } } + } } diff --git a/framework/tests/all-systems/DeepEquals.java b/framework/tests/all-systems/DeepEquals.java index 927a386cd1c..d6167bf2b60 100644 --- a/framework/tests/all-systems/DeepEquals.java +++ b/framework/tests/all-systems/DeepEquals.java @@ -1,12 +1,12 @@ public class DeepEquals { - public static int deepEquals(Object o1) { - if (o1 instanceof boolean[]) { - return 1; - } - if (o1 instanceof byte[]) { - return 2; - } - - return 3; + public static int deepEquals(Object o1) { + if (o1 instanceof boolean[]) { + return 1; + } + if (o1 instanceof byte[]) { + return 2; } + + return 3; + } } diff --git a/framework/tests/all-systems/EisopIssue612.java b/framework/tests/all-systems/EisopIssue612.java index 8693bda0aeb..88abd9931d3 100644 --- a/framework/tests/all-systems/EisopIssue612.java +++ b/framework/tests/all-systems/EisopIssue612.java @@ -1,7 +1,7 @@ interface Issue612A {} @interface Issue612B { - Class value(); + Class value(); } class Issue612C implements Issue612A {} @@ -9,12 +9,12 @@ class Issue612C implements Issue612A {} class Issue612E {} @interface Issue612Z { - abstract class Issue612D implements Issue612A { + abstract class Issue612D implements Issue612A { - public Issue612D() { - g(new Issue612E()); - } - - static void g(Object... xs) {} + public Issue612D() { + g(new Issue612E()); } + + static void g(Object... xs) {} + } } diff --git a/framework/tests/all-systems/EisopIssue612Min.java b/framework/tests/all-systems/EisopIssue612Min.java index 797a04874d1..c30d9e1eacd 100644 --- a/framework/tests/all-systems/EisopIssue612Min.java +++ b/framework/tests/all-systems/EisopIssue612Min.java @@ -1,9 +1,9 @@ @interface Issue612Min { - class D { - D() { - g(new Object()); - } - - static void g(Object... xs) {} + class D { + D() { + g(new Object()); } + + static void g(Object... xs) {} + } } diff --git a/framework/tests/all-systems/EnumSwitch.java b/framework/tests/all-systems/EnumSwitch.java index 4330494cf5b..ff20f02091c 100644 --- a/framework/tests/all-systems/EnumSwitch.java +++ b/framework/tests/all-systems/EnumSwitch.java @@ -3,21 +3,21 @@ // A similar test is in // checker/tests/nullness/Issue6260.java enum EnumSwitch { - FOO; + FOO; - EnumSwitch getIt() { - return FOO; - } + EnumSwitch getIt() { + return FOO; + } - String go() { - EnumSwitch e = getIt(); - switch (e) { - case FOO: - return "foo"; - } - // This location is reachable in general: the enum could evolve and add a new constant. - // This cannot be the case here, because this code is in the enum declaration. - // javac does not special case this and I do not see a reason to do so here. - throw new AssertionError(e); + String go() { + EnumSwitch e = getIt(); + switch (e) { + case FOO: + return "foo"; } + // This location is reachable in general: the enum could evolve and add a new constant. + // This cannot be the case here, because this code is in the enum declaration. + // javac does not special case this and I do not see a reason to do so here. + throw new AssertionError(e); + } } diff --git a/framework/tests/all-systems/Enums.java b/framework/tests/all-systems/Enums.java index e0e0ab6799d..7f8bd397607 100644 --- a/framework/tests/all-systems/Enums.java +++ b/framework/tests/all-systems/Enums.java @@ -3,46 +3,46 @@ class MyEnumSet> {} public class Enums { - public enum VarFlags { - IS_PARAM, - NO_DUPS - }; + public enum VarFlags { + IS_PARAM, + NO_DUPS + }; - public MyEnumSet var_flags = new MyEnumSet<>(); + public MyEnumSet var_flags = new MyEnumSet<>(); - VarFlags f1 = VarFlags.IS_PARAM; + VarFlags f1 = VarFlags.IS_PARAM; - void foo1(MyEnumSet p) {} + void foo1(MyEnumSet p) {} - void foo2(MyEnumSet p) {} + void foo2(MyEnumSet p) {} - > void mtv(Class p) {} + > void mtv(Class p) {} - T checkNotNull(T ref) { - return ref; - } + T checkNotNull(T ref) { + return ref; + } - T checkNotNull2(T ref, S ref2) { - return ref; - } + T checkNotNull2(T ref, S ref2) { + return ref; + } - class Test> { - void m(Class p) { - checkNotNull(p); - } + class Test> { + void m(Class p) { + checkNotNull(p); + } - public SSS firstNonNull(SSS first, SSS second) { - @SuppressWarnings("nullness:nulltest.redundant") - SSS res = first != null ? first : checkNotNull(second); - return res; - } + public SSS firstNonNull(SSS first, SSS second) { + @SuppressWarnings("nullness:nulltest.redundant") + SSS res = first != null ? first : checkNotNull(second); + return res; } + } - class Unbound {} + class Unbound {} - class Test2, S extends Unbound> { - void m(Class p, Class q) { - checkNotNull2(p, q); - } + class Test2, S extends Unbound> { + void m(Class p, Class q) { + checkNotNull2(p, q); } + } } diff --git a/framework/tests/all-systems/EqualityTests.java b/framework/tests/all-systems/EqualityTests.java index f184d8c8f14..3b33ca5dd9a 100644 --- a/framework/tests/all-systems/EqualityTests.java +++ b/framework/tests/all-systems/EqualityTests.java @@ -1,24 +1,24 @@ public class EqualityTests { - // the Interning checker correctly issues an error below, but we would like to keep this test in - // all-systems. - @SuppressWarnings("interning") - public boolean compareLongs(Long v1, Long v2) { - // This expression used to cause an assertion - // failure in GLB computation. - return !(((v1 == 0) || (v2 == 0)) && (v1 != v2)); - } + // the Interning checker correctly issues an error below, but we would like to keep this test in + // all-systems. + @SuppressWarnings("interning") + public boolean compareLongs(Long v1, Long v2) { + // This expression used to cause an assertion + // failure in GLB computation. + return !(((v1 == 0) || (v2 == 0)) && (v1 != v2)); + } - public int charEquals(boolean cond) { - char result = 'F'; - if (cond) { - result = 'T'; - } + public int charEquals(boolean cond) { + char result = 'F'; + if (cond) { + result = 'T'; + } - if (result == 'T') { - return 1; - } else { - assert result == '?'; - } - return 10; + if (result == 'T') { + return 1; + } else { + assert result == '?'; } + return 10; + } } diff --git a/framework/tests/all-systems/FieldAccessTest.java b/framework/tests/all-systems/FieldAccessTest.java index 34a0d11a005..ec3cf454bfd 100644 --- a/framework/tests/all-systems/FieldAccessTest.java +++ b/framework/tests/all-systems/FieldAccessTest.java @@ -2,39 +2,39 @@ // See the comments at GenericNull for some tips about what might be wrong. public class FieldAccessTest { - class MyClass { + class MyClass { - Object field = new Object(); - } + Object field = new Object(); + } - class MyException extends RuntimeException { - Object field = new Object(); - } + class MyException extends RuntimeException { + Object field = new Object(); + } - class MyExceptionA extends MyException {} + class MyExceptionA extends MyException {} - class MyExceptionB extends MyException {} + class MyExceptionB extends MyException {} - @SuppressWarnings("nullness") - class MyGen { - T myClass = null; - } + @SuppressWarnings("nullness") + class MyGen { + T myClass = null; + } - void test(Object o, MyGen raw) { - // Raw type field access: - raw.myClass.field = new Object(); - - // Intersection type field access - Object a = ((MyClass & Cloneable) o).field; - try { - } catch (MyExceptionA | MyExceptionB ex) { - // Union type field access - ex.field = new Object(); - } - } + void test(Object o, MyGen raw) { + // Raw type field access: + raw.myClass.field = new Object(); - void classLiterals() { - Class c = byte.class; - Class d = void.class; + // Intersection type field access + Object a = ((MyClass & Cloneable) o).field; + try { + } catch (MyExceptionA | MyExceptionB ex) { + // Union type field access + ex.field = new Object(); } + } + + void classLiterals() { + Class c = byte.class; + Class d = void.class; + } } diff --git a/framework/tests/all-systems/FieldWithInit.java b/framework/tests/all-systems/FieldWithInit.java index f4d629dec8d..2355a066875 100644 --- a/framework/tests/all-systems/FieldWithInit.java +++ b/framework/tests/all-systems/FieldWithInit.java @@ -2,9 +2,9 @@ public class FieldWithInit { - Object f = foo(); + Object f = foo(); - Object foo(@UnknownInitialization FieldWithInit this) { - return new Object(); - } + Object foo(@UnknownInitialization FieldWithInit this) { + return new Object(); + } } diff --git a/framework/tests/all-systems/ForEach.java b/framework/tests/all-systems/ForEach.java index b551fdf9150..96d4e6e1063 100644 --- a/framework/tests/all-systems/ForEach.java +++ b/framework/tests/all-systems/ForEach.java @@ -4,43 +4,43 @@ import java.util.Set; public class ForEach { - void m1() { - Set s = new HashSet(); - for (CharSequence cs : s) { - cs.toString(); - } + void m1() { + Set s = new HashSet(); + for (CharSequence cs : s) { + cs.toString(); } + } - void m2() { - Set s = new HashSet<>(); - for (CharSequence cs : s) { - cs.toString(); - } + void m2() { + Set s = new HashSet<>(); + for (CharSequence cs : s) { + cs.toString(); } + } - void m3(T p) { - Set s = new HashSet<>(); - for (T cs : s) { - cs.toString(); - } + void m3(T p) { + Set s = new HashSet<>(); + for (T cs : s) { + cs.toString(); } + } - void m4(T p) { - Set s = new HashSet<>(); - for (Object cs : s) { - cs.toString(); - } + void m4(T p) { + Set s = new HashSet<>(); + for (Object cs : s) { + cs.toString(); } + } - public static List removeDuplicates(List l) { - // There are shorter solutions that do not maintain order. - HashSet hs = new HashSet<>(l.size()); - List result = new ArrayList<>(); - for (T t : l) { - if (hs.add(t)) { - result.add(t); - } - } - return result; + public static List removeDuplicates(List l) { + // There are shorter solutions that do not maintain order. + HashSet hs = new HashSet<>(l.size()); + List result = new ArrayList<>(); + for (T t : l) { + if (hs.add(t)) { + result.add(t); + } } + return result; + } } diff --git a/framework/tests/all-systems/GenericCrazyBounds.java b/framework/tests/all-systems/GenericCrazyBounds.java index d5ef260665d..e95a702c9cf 100644 --- a/framework/tests/all-systems/GenericCrazyBounds.java +++ b/framework/tests/all-systems/GenericCrazyBounds.java @@ -4,41 +4,41 @@ */ interface MyList { - ZZ getZZ(); + ZZ getZZ(); - void setZZ(ZZ ZZ); + void setZZ(ZZ ZZ); } interface MyMap { - K getK(); + K getK(); - V getV(); + V getV(); - void setK(K k); + void setK(K k); - void setV(V v); + void setV(V v); } class MyRec> {} class RecMyList extends MyRec implements MyList { - @SuppressWarnings("return.type.incompatible") - public RecMyList getZZ() { - return null; - } - - public void setZZ(RecMyList rl) { - return; - } + @SuppressWarnings("return.type.incompatible") + public RecMyList getZZ() { + return null; + } + + public void setZZ(RecMyList rl) { + return; + } } class Context2 { - & MyList> void main() {} + & MyList> void main() {} - void context() { - this.main(); - } + void context() { + this.main(); + } } interface Rec> {} @@ -50,38 +50,38 @@ class RecImpl implements Rec {} class SubRec extends RecImpl {} class CrazyGen2, EE extends MyMap> { - TT t2; - EE e2; - - public CrazyGen2(TT t2, EE e2) { - this.t2 = t2; - this.e2 = e2; - } - - public void context() { - t2.setZZ(e2); - e2.setK(t2.getZZ().getK()); - } + TT t2; + EE e2; + + public CrazyGen2(TT t2, EE e2) { + this.t2 = t2; + this.e2 = e2; + } + + public void context() { + t2.setZZ(e2); + e2.setK(t2.getZZ().getK()); + } } class CrazyGen3, EEE extends MyMap> { - TTT t3; - EEE e3; - - public CrazyGen3(TTT t3, EEE e3) { - this.t3 = t3; - this.e3 = e3; - } - - public void context() { - e3.setK(t3); - e3.setK(t3.getZZ()); - } + TTT t3; + EEE e3; + + public CrazyGen3(TTT t3, EEE e3) { + this.t3 = t3; + this.e3 = e3; + } + + public void context() { + e3.setK(t3); + e3.setK(t3.getZZ()); + } } class MyClass { - public > String methodToPrint(TV1 tv1, int intParam) { - return ""; - } + public > String methodToPrint(TV1 tv1, int intParam) { + return ""; + } } diff --git a/framework/tests/all-systems/GenericExtendsTypeVars.java b/framework/tests/all-systems/GenericExtendsTypeVars.java index 1c7ed51218d..c4655318788 100644 --- a/framework/tests/all-systems/GenericExtendsTypeVars.java +++ b/framework/tests/all-systems/GenericExtendsTypeVars.java @@ -9,29 +9,29 @@ interface MMyMap {} class Tester> {} class WithWildcard> { - void context() { - ZZ zz = null; - QQ qq = null; - YY yy = null; - } + void context() { + ZZ zz = null; + QQ qq = null; + YY yy = null; + } } class Test> { - KK kk; - FF ff; + KK kk; + FF ff; - Test(KK kk, FF ff) { - this.kk = kk; - this.ff = ff; - } + Test(KK kk, FF ff) { + this.kk = kk; + this.ff = ff; + } } class RecursiveTypevarClass> { - T t; + T t; - RecursiveTypevarClass(T t) { - this.t = t; - } + RecursiveTypevarClass(T t) { + this.t = t; + } } class RecursiveImplements implements MMyList {} diff --git a/framework/tests/all-systems/GenericNull.java b/framework/tests/all-systems/GenericNull.java index bf8000069b2..f5bbf04355d 100644 --- a/framework/tests/all-systems/GenericNull.java +++ b/framework/tests/all-systems/GenericNull.java @@ -17,16 +17,16 @@ // public class GenericNull { - /** - * In most type systems, null's type is bottom and therefore the generic return type T is a - * supertype of null's type. - * - *

          However, in the nullness and lock type systems, null's type is not bottom, so they exclude - * this test. For the Lock Checker, null's type is bottom for the @GuardedByUnknown hierarchy - * but not for the @LockPossiblyHeld hierarchy. - */ - @SuppressWarnings({"nullness", "lock"}) - T f() { - return null; - } + /** + * In most type systems, null's type is bottom and therefore the generic return type T is a + * supertype of null's type. + * + *

          However, in the nullness and lock type systems, null's type is not bottom, so they exclude + * this test. For the Lock Checker, null's type is bottom for the @GuardedByUnknown hierarchy but + * not for the @LockPossiblyHeld hierarchy. + */ + @SuppressWarnings({"nullness", "lock"}) + T f() { + return null; + } } diff --git a/framework/tests/all-systems/GenericTest11full.java b/framework/tests/all-systems/GenericTest11full.java index ab91a54ab04..7e6744a8462 100644 --- a/framework/tests/all-systems/GenericTest11full.java +++ b/framework/tests/all-systems/GenericTest11full.java @@ -1,24 +1,23 @@ public class GenericTest11full { - public void m(BeanManager beanManager) { - Bean bean = beanManager.getBeans(GenericTest11full.class).iterator().next(); - CreationalContext context = beanManager.createCreationalContext(bean); - GenericTest11full b = - (GenericTest11full) - beanManager.getReference(bean, GenericTest11full.class, context); - } + public void m(BeanManager beanManager) { + Bean bean = beanManager.getBeans(GenericTest11full.class).iterator().next(); + CreationalContext context = beanManager.createCreationalContext(bean); + GenericTest11full b = + (GenericTest11full) beanManager.getReference(bean, GenericTest11full.class, context); + } - static interface BeanManager { - java.util.Set> getBeans( - java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); + static interface BeanManager { + java.util.Set> getBeans( + java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); - CreationalContext createCreationalContext(Contextual arg0); + CreationalContext createCreationalContext(Contextual arg0); - Object getReference(Bean arg0, java.lang.reflect.Type arg1, CreationalContext arg2); - } + Object getReference(Bean arg0, java.lang.reflect.Type arg1, CreationalContext arg2); + } - static interface Contextual {} + static interface Contextual {} - static interface Bean extends Contextual {} + static interface Bean extends Contextual {} - static interface CreationalContext {} + static interface CreationalContext {} } diff --git a/framework/tests/all-systems/GenericTest12.java b/framework/tests/all-systems/GenericTest12.java index 3c4445aaa26..24b4b63cde4 100644 --- a/framework/tests/all-systems/GenericTest12.java +++ b/framework/tests/all-systems/GenericTest12.java @@ -1,10 +1,10 @@ // Test case from Issue 142 abstract class GenericTest12 { - interface Task {} + interface Task {} - abstract Task create(Runnable runnable, M result); + abstract Task create(Runnable runnable, M result); - void submit(Runnable runnable) { - Task task = create(runnable, null); - } + void submit(Runnable runnable) { + Task task = create(runnable, null); + } } diff --git a/framework/tests/all-systems/GenericTest12b.java b/framework/tests/all-systems/GenericTest12b.java index f2e3dc8c626..92425a9df85 100644 --- a/framework/tests/all-systems/GenericTest12b.java +++ b/framework/tests/all-systems/GenericTest12b.java @@ -1,23 +1,23 @@ @SuppressWarnings({ - "initialization", - "nullness" + "initialization", + "nullness" }) // Don't want to depend on @Nullable or @UnderInitialization public class GenericTest12b { - class Cell {} + class Cell {} - class Node { - public Node(Cell userObject) {} + class Node { + public Node(Cell userObject) {} - void nodecall(Cell userObject) {} - } - - class RootNode extends Node { - public RootNode() { - super(new Cell()); - call(new Cell()); - nodecall(new Cell()); - } + void nodecall(Cell userObject) {} + } - void call(Cell userObject) {} + class RootNode extends Node { + public RootNode() { + super(new Cell()); + call(new Cell()); + nodecall(new Cell()); } + + void call(Cell userObject) {} + } } diff --git a/framework/tests/all-systems/GenericTest13.java b/framework/tests/all-systems/GenericTest13.java index 90d43368be2..75955975090 100644 --- a/framework/tests/all-systems/GenericTest13.java +++ b/framework/tests/all-systems/GenericTest13.java @@ -2,15 +2,15 @@ // https://github.com/typetools/checker-framework/issues/142 public class GenericTest13 { - interface Entry { - V getValue(); - } + interface Entry { + V getValue(); + } - interface Iterator { - E next(); - } + interface Iterator { + E next(); + } - S call(Iterator> entryIterator) { - return entryIterator.next().getValue(); - } + S call(Iterator> entryIterator) { + return entryIterator.next().getValue(); + } } diff --git a/framework/tests/all-systems/GenericsBounds.java b/framework/tests/all-systems/GenericsBounds.java index 8e0bfb018a2..34b6877ae78 100644 --- a/framework/tests/all-systems/GenericsBounds.java +++ b/framework/tests/all-systems/GenericsBounds.java @@ -14,7 +14,7 @@ class Upper, Y extends X> {} class Lower extends Upper {} class GenericsBounds { - Upper f = new Upper<>(); + Upper f = new Upper<>(); } class Upper1> {} @@ -27,7 +27,7 @@ class Lower2 extends Upper2, LinkedList> {} class GenericGetClass { - Class getClass(Class orig, Class cast) { - return orig.asSubclass(cast); - } + Class getClass(Class orig, Class cast) { + return orig.asSubclass(cast); + } } diff --git a/framework/tests/all-systems/GenericsBounds2.java b/framework/tests/all-systems/GenericsBounds2.java index 6e8c185e94e..f6990f408a5 100644 --- a/framework/tests/all-systems/GenericsBounds2.java +++ b/framework/tests/all-systems/GenericsBounds2.java @@ -1,7 +1,7 @@ // Test for Issue 258 // https://github.com/typetools/checker-framework/issues/258 public class GenericsBounds2 { - void method(C arg) { - arg.toString(); - } + void method(C arg) { + arg.toString(); + } } diff --git a/framework/tests/all-systems/GenericsCasts.java b/framework/tests/all-systems/GenericsCasts.java index abd33b1e056..4f43b5aeb54 100644 --- a/framework/tests/all-systems/GenericsCasts.java +++ b/framework/tests/all-systems/GenericsCasts.java @@ -3,51 +3,51 @@ import java.util.List; public class GenericsCasts { - // Cast from a raw type to a generic type + // Cast from a raw type to a generic type + // :: warning: [unchecked] unchecked cast + List[] o = (List[]) new List[] {new ArrayList()}; + + class Data {} + + // Use our own dummy method as to avoid a complaint from the Signature Checker + Data forName(String p) { + throw new Error(""); + } + + void m() { + // Cast from a wildcard to a normal type argument. + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) // :: warning: [unchecked] unchecked cast - List[] o = (List[]) new List[] {new ArrayList()}; + Data c = (Data) forName("HaHa!"); + } - class Data {} + // Casts from something with one type argument to two type arguments + // are currently problematic. + // TODO: try to find a problem with skipping this check. + class Test { + class Entry extends LinkedList {} - // Use our own dummy method as to avoid a complaint from the Signature Checker - Data forName(String p) { + class Queue { + List poll() { throw new Error(""); + } } - void m() { - // Cast from a wildcard to a normal type argument. - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - Data c = (Data) forName("HaHa!"); + void trouble() { + Queue queue = new Queue<>(); + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + Entry e = (Entry) queue.poll(); } + } - // Casts from something with one type argument to two type arguments - // are currently problematic. - // TODO: try to find a problem with skipping this check. - class Test { - class Entry extends LinkedList {} - - class Queue { - List poll() { - throw new Error(""); - } - } - - void trouble() { - Queue queue = new Queue<>(); - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - Entry e = (Entry) queue.poll(); - } - } + public static int indexOf(T[] a) { + return indexOfEq(a); + } - public static int indexOf(T[] a) { - return indexOfEq(a); - } - - public static int indexOfEq(Object[] a) { - return 0; - } + public static int indexOfEq(Object[] a) { + return 0; + } } diff --git a/framework/tests/all-systems/GenericsEnclosing.java b/framework/tests/all-systems/GenericsEnclosing.java index ea62fb5c05a..62e8ab1bf5f 100644 --- a/framework/tests/all-systems/GenericsEnclosing.java +++ b/framework/tests/all-systems/GenericsEnclosing.java @@ -7,19 +7,19 @@ *

          Also see regex/GenericsEnclosing for a test case for the Regex Checker. */ public class GenericsEnclosing extends TreeMap { - class Inner { - void foo() { - put("string", "string".toString()); - put("string", "string"); - GenericsEnclosing.this.put("string", "string".toString()); - GenericsEnclosing.this.put("string", "string"); - } + class Inner { + void foo() { + put("string", "string".toString()); + put("string", "string"); + GenericsEnclosing.this.put("string", "string".toString()); + GenericsEnclosing.this.put("string", "string"); } + } } class OtherUse { - void m(GenericsEnclosing p) { - p.put("string", "string".toString()); - p.put("string", "string"); - } + void m(GenericsEnclosing p) { + p.put("string", "string".toString()); + p.put("string", "string"); + } } diff --git a/framework/tests/all-systems/GetClassTest.java b/framework/tests/all-systems/GetClassTest.java index ba7e2fe7ea1..c36d56265f2 100644 --- a/framework/tests/all-systems/GetClassTest.java +++ b/framework/tests/all-systems/GetClassTest.java @@ -3,28 +3,28 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class GetClassTest { - // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver + // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver - void context() { - Integer i = 4; - i.getClass(); - Class a = i.getClass(); - // Type arguments don't match - @SuppressWarnings("fenum:assignment.type.incompatible") - Class b = i.getClass(); - @SuppressWarnings({ - "fenum:assignment.type.incompatible", // Type arguments don't match - "signedness:assignment.type.incompatible" // Type arguments don't match - }) - Class c = i.getClass(); + void context() { + Integer i = 4; + i.getClass(); + Class a = i.getClass(); + // Type arguments don't match + @SuppressWarnings("fenum:assignment.type.incompatible") + Class b = i.getClass(); + @SuppressWarnings({ + "fenum:assignment.type.incompatible", // Type arguments don't match + "signedness:assignment.type.incompatible" // Type arguments don't match + }) + Class c = i.getClass(); - Class d = i.getClass(); - // not legal Java; that is, does not type-check under Java rules - // Class e = i.getClass(); - } + Class d = i.getClass(); + // not legal Java; that is, does not type-check under Java rules + // Class e = i.getClass(); + } - void m(Date d) { - @SuppressWarnings("fenum:assignment.type.incompatible") - Class c = d.getClass(); - } + void m(Date d) { + @SuppressWarnings("fenum:assignment.type.incompatible") + Class c = d.getClass(); + } } diff --git a/framework/tests/all-systems/InferAndIntersection.java b/framework/tests/all-systems/InferAndIntersection.java index 5ae5b686c83..d4105bbc802 100644 --- a/framework/tests/all-systems/InferAndIntersection.java +++ b/framework/tests/all-systems/InferAndIntersection.java @@ -1,8 +1,8 @@ public class InferAndIntersection { - void toInfer(Iterable t) {} + void toInfer(Iterable t) {} - > void context(U u) { - toInfer(u); - } + > void context(U u) { + toInfer(u); + } } diff --git a/framework/tests/all-systems/InferAndWildcards.java b/framework/tests/all-systems/InferAndWildcards.java index aeb5dfa2787..fcb3f4b5066 100644 --- a/framework/tests/all-systems/InferAndWildcards.java +++ b/framework/tests/all-systems/InferAndWildcards.java @@ -1,10 +1,10 @@ @SuppressWarnings("interning") public class InferAndWildcards { - Class b(Class clazz) { - return clazz; - } + Class b(Class clazz) { + return clazz; + } - void a(Class clazz) { - Class v = b(clazz); - } + void a(Class clazz) { + Class v = b(clazz); + } } diff --git a/framework/tests/all-systems/InferNullType.java b/framework/tests/all-systems/InferNullType.java index 407c1b0c4c2..313d818f207 100644 --- a/framework/tests/all-systems/InferNullType.java +++ b/framework/tests/all-systems/InferNullType.java @@ -2,36 +2,36 @@ // errors public class InferNullType { - T toInfer(T input) { - return input; - } + T toInfer(T input) { + return input; + } - T toInfer2(T input) { - return input; - } + T toInfer2(T input) { + return input; + } - T toInfer3(T input, S p2) { - return input; - } + T toInfer3(T input, S p2) { + return input; + } - T toInfer4(T input, S p2) { - return input; - } + T toInfer4(T input, S p2) { + return input; + } - void x() { - @SuppressWarnings("nullness:type.argument.type.incompatible") - Object m = toInfer(null); - Object m2 = toInfer2(null); + void x() { + @SuppressWarnings("nullness:type.argument.type.incompatible") + Object m = toInfer(null); + Object m2 = toInfer2(null); - Object m3 = toInfer3(null, null); - Object m4 = toInfer3(1, null); - Object m5 = toInfer3(null, 1); + Object m3 = toInfer3(null, null); + Object m4 = toInfer3(1, null); + Object m5 = toInfer3(null, 1); - @SuppressWarnings("nullness:type.argument.type.incompatible") - Object m6 = toInfer4(null, null); - @SuppressWarnings("nullness:type.argument.type.incompatible") - Object m7 = toInfer4(1, null); - @SuppressWarnings("nullness:type.argument.type.incompatible") - Object m8 = toInfer4(null, 1); - } + @SuppressWarnings("nullness:type.argument.type.incompatible") + Object m6 = toInfer4(null, null); + @SuppressWarnings("nullness:type.argument.type.incompatible") + Object m7 = toInfer4(1, null); + @SuppressWarnings("nullness:type.argument.type.incompatible") + Object m8 = toInfer4(null, 1); + } } diff --git a/framework/tests/all-systems/InferTypeArgs.java b/framework/tests/all-systems/InferTypeArgs.java index 7e58392fb51..a1fc726ec05 100644 --- a/framework/tests/all-systems/InferTypeArgs.java +++ b/framework/tests/all-systems/InferTypeArgs.java @@ -67,20 +67,20 @@ class CFAbstractValue> {} class CFAbstractAnalysis> {} class GenericAnnotatedTypeFactoryInferTypeArgs< - Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { + Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { - @SuppressWarnings("immutability:type.argument.type.incompatible") - protected FlowAnalysis createFlowAnalysis() { - FlowAnalysis result = invokeConstructorFor(); - return result; - } + @SuppressWarnings("immutability:type.argument.type.incompatible") + protected FlowAnalysis createFlowAnalysis() { + FlowAnalysis result = invokeConstructorFor(); + return result; + } - @SuppressWarnings({ - "nullness:return.type.incompatible", - "lock:return.type.incompatible", - "immutabilitysub:type.argument.type.incompatible" - }) - public static T invokeConstructorFor() { - return null; - } + @SuppressWarnings({ + "nullness:return.type.incompatible", + "lock:return.type.incompatible", + "immutabilitysub:type.argument.type.incompatible" + }) + public static T invokeConstructorFor() { + return null; + } } diff --git a/framework/tests/all-systems/InferTypeArgs2.java b/framework/tests/all-systems/InferTypeArgs2.java index 241635f02e6..f7e6fc03bd9 100644 --- a/framework/tests/all-systems/InferTypeArgs2.java +++ b/framework/tests/all-systems/InferTypeArgs2.java @@ -4,21 +4,21 @@ public class InferTypeArgs2> {} // InferTypeArgs1.java. In this case we end up comparing CFValue with V extends InferTypeArgs2 // which kicks off the DefaultRawnessComparer. Before I fixed it, it then blew the stack class CFValue extends InferTypeArgs2 { - public CFValue(InferTypeArgsAnalysis analysis) {} + public CFValue(InferTypeArgsAnalysis analysis) {} } class CFAbstractStore, S extends CFAbstractStore> {} class CFAbstractTransfer< - V extends InferTypeArgs2, - S extends CFAbstractStore, - T extends CFAbstractTransfer> {} + V extends InferTypeArgs2, + S extends CFAbstractStore, + T extends CFAbstractTransfer> {} class InferTypeArgsAnalysis< - V extends InferTypeArgs2, - S extends CFAbstractStore, - T extends CFAbstractTransfer> { - public CFValue defaultCreateAbstractValue(InferTypeArgsAnalysis analysis) { - return new CFValue(analysis); - } + V extends InferTypeArgs2, + S extends CFAbstractStore, + T extends CFAbstractTransfer> { + public CFValue defaultCreateAbstractValue(InferTypeArgsAnalysis analysis) { + return new CFValue(analysis); + } } diff --git a/framework/tests/all-systems/InferTypeArgs3.java b/framework/tests/all-systems/InferTypeArgs3.java index 6a4e46e6177..3e5a51f26ee 100644 --- a/framework/tests/all-systems/InferTypeArgs3.java +++ b/framework/tests/all-systems/InferTypeArgs3.java @@ -2,18 +2,18 @@ import java.util.HashSet; public class InferTypeArgs3 { - @SuppressWarnings({"deprecation", "removal", "cast.unsafe.constructor.invocation"}) - void test() { - java.util.Arrays.asList(new Integer(1), ""); - } + @SuppressWarnings({"deprecation", "removal", "cast.unsafe.constructor.invocation"}) + void test() { + java.util.Arrays.asList(new Integer(1), ""); + } - void test2() { - Integer i = Integer.valueOf(1); - java.util.Arrays.asList(i, ""); - } + void test2() { + Integer i = Integer.valueOf(1); + java.util.Arrays.asList(i, ""); + } - void foo() { - new HashSet<>(Arrays.asList(new Object())); - new HashSet(Arrays.asList(new Object())) {}; - } + void foo() { + new HashSet<>(Arrays.asList(new Object())); + new HashSet(Arrays.asList(new Object())) {}; + } } diff --git a/framework/tests/all-systems/InferTypeArgsConditionalExpression.java b/framework/tests/all-systems/InferTypeArgsConditionalExpression.java index 1d83873aac6..216a6806a8c 100644 --- a/framework/tests/all-systems/InferTypeArgsConditionalExpression.java +++ b/framework/tests/all-systems/InferTypeArgsConditionalExpression.java @@ -5,11 +5,11 @@ public class InferTypeArgsConditionalExpression { - public void foo(Generic real, Generic other, boolean flag) { - bar(flag ? real : other); - } + public void foo(Generic real, Generic other, boolean flag) { + bar(flag ? real : other); + } - void bar(Generic param) {} + void bar(Generic param) {} - interface Generic {} + interface Generic {} } diff --git a/framework/tests/all-systems/InitializationVisitor.java b/framework/tests/all-systems/InitializationVisitor.java index 4fe9db658b0..c574139e712 100644 --- a/framework/tests/all-systems/InitializationVisitor.java +++ b/framework/tests/all-systems/InitializationVisitor.java @@ -1,11 +1,11 @@ // Minimized test case from InitializationVisitor. class IATF< - Value extends CFAV, - Store extends IS, - Transfer extends IT, - Flow extends CFAA> - extends GATF {} + Value extends CFAV, + Store extends IS, + Transfer extends IT, + Flow extends CFAA> + extends GATF {} class CFAV> {} @@ -20,15 +20,15 @@ class CFAT, S extends CFAS, T extends CFAT> {} class CFAS, S extends CFAS> {} class GATF< - Value extends CFAV, - Store extends CFAS, - TransferFunction extends CFAT, - FlowAnalysis extends CFAA> {} + Value extends CFAV, + Store extends CFAS, + TransferFunction extends CFAT, + FlowAnalysis extends CFAA> {} class BTV> {} class IV< - Factory extends IATF, - Value extends CFAV, - Store extends IS> - extends BTV {} + Factory extends IATF, + Value extends CFAV, + Store extends IS> + extends BTV {} diff --git a/framework/tests/all-systems/InstanceOf.java b/framework/tests/all-systems/InstanceOf.java index 2f01b9935e4..c0e79eb8c01 100644 --- a/framework/tests/all-systems/InstanceOf.java +++ b/framework/tests/all-systems/InstanceOf.java @@ -8,23 +8,23 @@ class GETFIELD extends FieldInstruction {} class PUTFIELD extends FieldInstruction {} public class InstanceOf { - public void emptyGLB(FieldInstruction f) { - if (f instanceof GETFIELD || f instanceof PUTFIELD) { - if (f instanceof PUTFIELD) { - // During org.checkerframework.dataflow analysis, we can believe that f is both a - // GETFIELD and a PUTFIELD, which yields an empty GLB. Once - // org.checkerframework.dataflow converges, it will know that f is a PUTFIELD. - return; - } - return; - } + public void emptyGLB(FieldInstruction f) { + if (f instanceof GETFIELD || f instanceof PUTFIELD) { + if (f instanceof PUTFIELD) { + // During org.checkerframework.dataflow analysis, we can believe that f is both a + // GETFIELD and a PUTFIELD, which yields an empty GLB. Once + // org.checkerframework.dataflow converges, it will know that f is a PUTFIELD. + return; + } + return; } + } - public boolean assignInstanceOf(Object obj) { - // We fixed a bug where the type in an instanceof expression - // like Class was stored as the abstract value result of - // the expression. - boolean is_class = obj instanceof Class; - return is_class; - } + public boolean assignInstanceOf(Object obj) { + // We fixed a bug where the type in an instanceof expression + // like Class was stored as the abstract value result of + // the expression. + boolean is_class = obj instanceof Class; + return is_class; + } } diff --git a/framework/tests/all-systems/IntersectionTypes.java b/framework/tests/all-systems/IntersectionTypes.java index 610d33bd6df..3f2b4d30f41 100644 --- a/framework/tests/all-systems/IntersectionTypes.java +++ b/framework/tests/all-systems/IntersectionTypes.java @@ -8,10 +8,10 @@ interface Bar {} class Baz implements Foo, Bar {} public class IntersectionTypes { - void foo() { - Baz baz = new Baz(); - call(baz); - } + void foo() { + Baz baz = new Baz(); + call(baz); + } - void call(T p) {} + void call(T p) {} } diff --git a/framework/tests/all-systems/IsSubarrayEq.java b/framework/tests/all-systems/IsSubarrayEq.java index 2d0d0700d50..c2cc270f654 100644 --- a/framework/tests/all-systems/IsSubarrayEq.java +++ b/framework/tests/all-systems/IsSubarrayEq.java @@ -1,15 +1,14 @@ -import org.checkerframework.common.value.qual.MinLen; - import java.util.List; +import org.checkerframework.common.value.qual.MinLen; @SuppressWarnings("ainfertest") // only check WPI for crashes public class IsSubarrayEq { - // the Interning checker correctly issues an error below, but we would like to keep this test in - // all-systems. - // Fenum Checker should not issue a warning. See issue 789 - // https://github.com/typetools/checker-framework/issues/789 - @SuppressWarnings({"interning", "fenum:return.type.incompatible"}) - public static boolean isSubarrayEq(Object @MinLen(1) [] a, List sub) { - return (sub.get(0) != a[0]); - } + // the Interning checker correctly issues an error below, but we would like to keep this test in + // all-systems. + // Fenum Checker should not issue a warning. See issue 789 + // https://github.com/typetools/checker-framework/issues/789 + @SuppressWarnings({"interning", "fenum:return.type.incompatible"}) + public static boolean isSubarrayEq(Object @MinLen(1) [] a, List sub) { + return (sub.get(0) != a[0]); + } } diff --git a/framework/tests/all-systems/Issue1003.java b/framework/tests/all-systems/Issue1003.java index d5562462a5b..d4642faa37e 100644 --- a/framework/tests/all-systems/Issue1003.java +++ b/framework/tests/all-systems/Issue1003.java @@ -4,18 +4,18 @@ // The fact that M extends Issue1003 is unimportant, // I'm just doing this to wrap it up into a single class example: public class Issue1003 { - public int field = 0; + public int field = 0; - public M getFoo() { - throw new RuntimeException(); - } + public M getFoo() { + throw new RuntimeException(); + } - public static void methodMemberAccess() { - // Use of raw generics: - Issue1003 m = new Issue1003(); - // This version causes error but not exception: - // Issue1003 m = new Issue1003<>(); - // Exception caused by this line: - int x = m.getFoo().field; - } + public static void methodMemberAccess() { + // Use of raw generics: + Issue1003 m = new Issue1003(); + // This version causes error but not exception: + // Issue1003 m = new Issue1003<>(); + // Exception caused by this line: + int x = m.getFoo().field; + } } diff --git a/framework/tests/all-systems/Issue1006.java b/framework/tests/all-systems/Issue1006.java index 5010b16c6e8..dfa7a9351da 100644 --- a/framework/tests/all-systems/Issue1006.java +++ b/framework/tests/all-systems/Issue1006.java @@ -8,13 +8,13 @@ @SuppressWarnings("all") // Ignore type-checking errors. public class Issue1006 { - void foo(Stream m, Map im) { - Map l = m.collect(Collectors.toMap(Function.identity(), im::get)); - } + void foo(Stream m, Map im) { + Map l = m.collect(Collectors.toMap(Function.identity(), im::get)); + } - // alternative version with same crash - Map bar(String src) { - return Stream.of(src) - .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); - } + // alternative version with same crash + Map bar(String src) { + return Stream.of(src) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + } } diff --git a/framework/tests/all-systems/Issue1039.java b/framework/tests/all-systems/Issue1039.java index 48207b606c7..ee7c1c8e085 100644 --- a/framework/tests/all-systems/Issue1039.java +++ b/framework/tests/all-systems/Issue1039.java @@ -2,7 +2,7 @@ // https://github.com/typetools/checker-framework/issues/1039 public class Issue1039> { - Issue1039 foo() { - return new Issue1039<>(); - } + Issue1039 foo() { + return new Issue1039<>(); + } } diff --git a/framework/tests/all-systems/Issue1043.java b/framework/tests/all-systems/Issue1043.java index a9f5deb67c6..218ac117c08 100644 --- a/framework/tests/all-systems/Issue1043.java +++ b/framework/tests/all-systems/Issue1043.java @@ -2,14 +2,14 @@ // https://github.com/typetools/checker-framework/issues/1043 public class Issue1043 { - boolean foo(Class p) { - return true; - } + boolean foo(Class p) { + return true; + } - void bar(Object p) {} + void bar(Object p) {} - @SuppressWarnings("keyfor:type.argument") - void baz() { - bar(foo(this.getClass()) ? "a" : "b"); - } + @SuppressWarnings("keyfor:type.argument") + void baz() { + bar(foo(this.getClass()) ? "a" : "b"); + } } diff --git a/framework/tests/all-systems/Issue1049.java b/framework/tests/all-systems/Issue1049.java index d2e128d0dce..06bc82f7648 100644 --- a/framework/tests/all-systems/Issue1049.java +++ b/framework/tests/all-systems/Issue1049.java @@ -1,12 +1,12 @@ // Test case for Issue #1049 // https://github.com/typetools/checker-framework/issues/1049 public class Issue1049 { - interface Gen> { - T get(); - } + interface Gen> { + T get(); + } - @SuppressWarnings("nulltest.redundant") - void bar(Gen g) { - Gen l = g.get() != null ? g.get() : g; - } + @SuppressWarnings("nulltest.redundant") + void bar(Gen g) { + Gen l = g.get() != null ? g.get() : g; + } } diff --git a/framework/tests/all-systems/Issue1102.java b/framework/tests/all-systems/Issue1102.java index 9ecdc1a501e..f1e5dc52f0d 100644 --- a/framework/tests/all-systems/Issue1102.java +++ b/framework/tests/all-systems/Issue1102.java @@ -7,17 +7,16 @@ interface Issue1102Itf {} class Issue1102Base {} class Issue1102Decl extends Issue1102Base { - static Issue1102Decl newInstance( - T s) { - return new Issue1102Decl(); - } + static Issue1102Decl newInstance(T s) { + return new Issue1102Decl(); + } } @SuppressWarnings("all") // Only interested in possible crash class Issue1102Use { - U f; + U f; - void bar() { - Issue1102Decl d = Issue1102Decl.newInstance(f); - } + void bar() { + Issue1102Decl d = Issue1102Decl.newInstance(f); + } } diff --git a/framework/tests/all-systems/Issue1111.java b/framework/tests/all-systems/Issue1111.java index ff7a9159bdc..122b9ecb9d6 100644 --- a/framework/tests/all-systems/Issue1111.java +++ b/framework/tests/all-systems/Issue1111.java @@ -6,11 +6,11 @@ @SuppressWarnings("all") // just check for crash public class Issue1111 { - void foo(Box box, List list) { - bar(box, list); - } + void foo(Box box, List list) { + bar(box, list); + } - void bar(Box box, Iterable list) {} + void bar(Box box, Iterable list) {} - class Box {} + class Box {} } diff --git a/framework/tests/all-systems/Issue116Graph.java b/framework/tests/all-systems/Issue116Graph.java index 57cd09d1eb2..b8a836bfb6d 100644 --- a/framework/tests/all-systems/Issue116Graph.java +++ b/framework/tests/all-systems/Issue116Graph.java @@ -9,4 +9,4 @@ class Issue116Edge, GrEdgeType extends Issue116Edge> {} + GrNodeType extends Issue116Node, GrEdgeType extends Issue116Edge> {} diff --git a/framework/tests/all-systems/Issue1274.java b/framework/tests/all-systems/Issue1274.java index ca3efcb14d4..d7c5f90bd3f 100644 --- a/framework/tests/all-systems/Issue1274.java +++ b/framework/tests/all-systems/Issue1274.java @@ -3,21 +3,21 @@ @SuppressWarnings("all") // Just check for crashes public class Issue1274 { - static class Mine { - static Mine of(S p1, S p2) { - return null; - } + static class Mine { + static Mine of(S p1, S p2) { + return null; } + } - class Two {} + class Two {} - class C extends Two {} + class C extends Two {} - class D extends Two {} + class D extends Two {} - class Crash { - { - Mine.of(new C(), new D()); - } + class Crash { + { + Mine.of(new C(), new D()); } + } } diff --git a/framework/tests/all-systems/Issue1431.java b/framework/tests/all-systems/Issue1431.java index d228e8d8736..400acac7c3d 100644 --- a/framework/tests/all-systems/Issue1431.java +++ b/framework/tests/all-systems/Issue1431.java @@ -1,13 +1,13 @@ // Test case for Issue 1431 // https://github.com/typetools/checker-framework/issues/1431 public class Issue1431 { - static class Outer { - class Inner {} - } + static class Outer { + class Inner {} + } - Outer.Inner ic; + Outer.Inner ic; - Issue1431(Outer.Inner ic) { - this.ic = ic; - } + Issue1431(Outer.Inner ic) { + this.ic = ic; + } } diff --git a/framework/tests/all-systems/Issue1442.java b/framework/tests/all-systems/Issue1442.java index 61519502c5c..0ea99fcfeed 100644 --- a/framework/tests/all-systems/Issue1442.java +++ b/framework/tests/all-systems/Issue1442.java @@ -2,33 +2,33 @@ // https://github.com/typetools/checker-framework/issues/1442 public class Issue1442 { - protected void configure(SubConfig.SubConfigInner x) { - SubMyClass subMyClass = x.getT().getSubConfigInner().outerClassTypeVar(); - } + protected void configure(SubConfig.SubConfigInner x) { + SubMyClass subMyClass = x.getT().getSubConfigInner().outerClassTypeVar(); + } - static class MyClass> {} + static class MyClass> {} - static class SubMyClass extends MyClass {} + static class SubMyClass extends MyClass {} - static class Config> { - class ConfigInner { - public T getT() { - throw new RuntimeException(); - } - } + static class Config> { + class ConfigInner { + public T getT() { + throw new RuntimeException(); + } } + } - static class SubConfig> extends Config { - public class SubConfigInner extends ConfigInner { - public C outerClassTypeVar() { - throw new RuntimeException(); - } - } + static class SubConfig> extends Config { + public class SubConfigInner extends ConfigInner { + public C outerClassTypeVar() { + throw new RuntimeException(); + } + } - class Thing { - public SubConfigInner getSubConfigInner() { - throw new RuntimeException(); - } - } + class Thing { + public SubConfigInner getSubConfigInner() { + throw new RuntimeException(); + } } + } } diff --git a/framework/tests/all-systems/Issue1506.java b/framework/tests/all-systems/Issue1506.java index 94898a09213..6c360a36bab 100644 --- a/framework/tests/all-systems/Issue1506.java +++ b/framework/tests/all-systems/Issue1506.java @@ -6,12 +6,12 @@ @SuppressWarnings("all") // just check for crashes. public class Issue1506 { - static void m() { - ArrayList l = new ArrayList<>(); - try { - throw new IOException(); - } catch (RuntimeException | IOException e) { - l.add(e); - } + static void m() { + ArrayList l = new ArrayList<>(); + try { + throw new IOException(); + } catch (RuntimeException | IOException e) { + l.add(e); } + } } diff --git a/framework/tests/all-systems/Issue1520.java b/framework/tests/all-systems/Issue1520.java index 43f22806467..387df8274d5 100644 --- a/framework/tests/all-systems/Issue1520.java +++ b/framework/tests/all-systems/Issue1520.java @@ -4,32 +4,32 @@ import java.io.IOException; public class Issue1520 { - void start() { - new Runnable() { - public void run() { - try { - _run(); - } finally { - signal(); // Evaluating this node type as member of implicit `this` will throw - // NPE - } - } - }; - } + void start() { + new Runnable() { + public void run() { + try { + _run(); + } finally { + signal(); // Evaluating this node type as member of implicit `this` will throw + // NPE + } + } + }; + } - void signal() {} + void signal() {} - void _run() {} + void _run() {} - static class Inner {} + static class Inner {} - void test2() throws IOException { - try { - throwIO(); - } finally { - Inner inner = new Inner(); - } + void test2() throws IOException { + try { + throwIO(); + } finally { + Inner inner = new Inner(); } + } - void throwIO() throws IOException {} + void throwIO() throws IOException {} } diff --git a/framework/tests/all-systems/Issue1526.java b/framework/tests/all-systems/Issue1526.java index cc2c670e3c7..100c860fdfa 100644 --- a/framework/tests/all-systems/Issue1526.java +++ b/framework/tests/all-systems/Issue1526.java @@ -1,17 +1,17 @@ // Tet case for Issue 1526. // https://github.com/typetools/checker-framework/issues/1526 public class Issue1526 { - public T get(T t) { - return this.get(t); - } + public T get(T t) { + return this.get(t); + } - public T method(T[] t) { - return this.method(t); - } + public T method(T[] t) { + return this.method(t); + } - public T method2(Gen t) { - return this.method2(t); - } + public T method2(Gen t) { + return this.method2(t); + } - static class Gen {} + static class Gen {} } diff --git a/framework/tests/all-systems/Issue1543.java b/framework/tests/all-systems/Issue1543.java index c2a2f0d195c..6fe00114304 100644 --- a/framework/tests/all-systems/Issue1543.java +++ b/framework/tests/all-systems/Issue1543.java @@ -2,14 +2,14 @@ // https://github.com/typetools/checker-framework/issues/1543 @SuppressWarnings("all") // check for crashes only public class Issue1543 { - static class BClass {} + static class BClass {} - interface AInterface {} + interface AInterface {} - static class GClass & AInterface> {} + static class GClass & AInterface> {} - static class Test { - GClass gClassRaw; - GClass gClassWC; - } + static class Test { + GClass gClassRaw; + GClass gClassWC; + } } diff --git a/framework/tests/all-systems/Issue1546.java b/framework/tests/all-systems/Issue1546.java index 4bbd03976df..bf7c08d6306 100644 --- a/framework/tests/all-systems/Issue1546.java +++ b/framework/tests/all-systems/Issue1546.java @@ -3,15 +3,15 @@ @SuppressWarnings("all") // check for crashes public class Issue1546 { - void m(T t) {} + void m(T t) {} - { - try { - new Runnable() { - public void run() {} - }; - } finally { - m("Hi"); - } + { + try { + new Runnable() { + public void run() {} + }; + } finally { + m("Hi"); } + } } diff --git a/framework/tests/all-systems/Issue1586.java b/framework/tests/all-systems/Issue1586.java index acb2430735a..24235fa49ae 100644 --- a/framework/tests/all-systems/Issue1586.java +++ b/framework/tests/all-systems/Issue1586.java @@ -4,19 +4,19 @@ import java.util.concurrent.ExecutorService; public class Issue1586 { - void f(ExecutorService es) { - es.execute( + void f(ExecutorService es) { + es.execute( + () -> { + try { + System.err.println(); + } catch (Throwable throwable) { + System.err.println(); + } finally { + es.execute( () -> { - try { - System.err.println(); - } catch (Throwable throwable) { - System.err.println(); - } finally { - es.execute( - () -> { - System.err.println(); - }); - } + System.err.println(); }); - } + } + }); + } } diff --git a/framework/tests/all-systems/Issue1587.java b/framework/tests/all-systems/Issue1587.java index 7f0cdf2cb48..f74977619b2 100644 --- a/framework/tests/all-systems/Issue1587.java +++ b/framework/tests/all-systems/Issue1587.java @@ -1,28 +1,28 @@ abstract class Issue1587 { - static class MyObject {} + static class MyObject {} - interface Six, R> { - T d(); + interface Six, R> { + T d(); - Iterable q(); - } + Iterable q(); + } - abstract Six e(Object entity); + abstract Six e(Object entity); - public void method(MyObject one) { - g(e(one).d().q()); - } + public void method(MyObject one) { + g(e(one).d().q()); + } - abstract Iterable g(Iterable r); + abstract Iterable g(Iterable r); - interface Class2> {} + interface Class2> {} - static class Class1> { + static class Class1> { - static void test() {} + static void test() {} - void use() { - Class1.test(); - } + void use() { + Class1.test(); } + } } diff --git a/framework/tests/all-systems/Issue1587b.java b/framework/tests/all-systems/Issue1587b.java index 8f50472f132..ff3b1aa5245 100644 --- a/framework/tests/all-systems/Issue1587b.java +++ b/framework/tests/all-systems/Issue1587b.java @@ -1,33 +1,33 @@ abstract class Issue1587b { - static class One implements Two {} + static class One implements Two {} - interface Two< - E extends Two, K extends Five, C extends Enum, I extends Enum> {} + interface Two< + E extends Two, K extends Five, C extends Enum, I extends Enum> {} - abstract static class Three implements Five {} + abstract static class Three implements Five {} - enum Four {} + enum Four {} - interface Five> extends Comparable {} + interface Five> extends Comparable {} - interface Six { - , I extends Enum> Seven e(Class entity); - } + interface Six { + , I extends Enum> Seven e(Class entity); + } - interface Seven, E extends Two> extends Eight {} + interface Seven, E extends Two> extends Eight {} - interface Eight, R, E extends Two> extends Nine {} + interface Eight, R, E extends Two> extends Nine {} - interface Nine, R> { - T d(); + interface Nine, R> { + T d(); - Iterable q(); - } + Iterable q(); + } - public Iterable f(Six e) { - return g(e.e(One.class).d().q()); - } + public Iterable f(Six e) { + return g(e.e(One.class).d().q()); + } - abstract Iterable g(Iterable r); + abstract Iterable g(Iterable r); } diff --git a/framework/tests/all-systems/Issue1690.java b/framework/tests/all-systems/Issue1690.java index e275c8cc792..bf7ffe90205 100644 --- a/framework/tests/all-systems/Issue1690.java +++ b/framework/tests/all-systems/Issue1690.java @@ -5,10 +5,10 @@ public class Issue1690 { - public Issue1690() {} + public Issue1690() {} - // Can be an inner type or in its own file, shouldn't matter. - public static interface Issue16902 { - public R getR(); - } + // Can be an inner type or in its own file, shouldn't matter. + public static interface Issue16902 { + public R getR(); + } } diff --git a/framework/tests/all-systems/Issue1696.java b/framework/tests/all-systems/Issue1696.java index 9c8aee47fee..289f1ffc005 100644 --- a/framework/tests/all-systems/Issue1696.java +++ b/framework/tests/all-systems/Issue1696.java @@ -2,7 +2,7 @@ // https://github.com/typetools/checker-framework/issues/1696 public class Issue1696 { - interface I, S> {} + interface I, S> {} - void f(I x) {} + void f(I x) {} } diff --git a/framework/tests/all-systems/Issue1697.java b/framework/tests/all-systems/Issue1697.java index a70d04f160a..001bddf7915 100644 --- a/framework/tests/all-systems/Issue1697.java +++ b/framework/tests/all-systems/Issue1697.java @@ -3,29 +3,29 @@ public class Issue1697 { - interface G { - A h(byte[] l); - } + interface G { + A h(byte[] l); + } - interface F {} + interface F {} - interface E extends F { - interface M extends F {} - } + interface E extends F { + interface M extends F {} + } - abstract static class D, B extends D.M> implements E { - abstract static class M, B extends M> implements E.M {} - } + abstract static class D, B extends D.M> implements E { + abstract static class M, B extends M> implements E.M {} + } - abstract static class C, B extends C.M> extends D { - abstract static class M, B extends M> extends D.M {} - } + abstract static class C, B extends C.M> extends D { + abstract static class M, B extends M> extends D.M {} + } - static class W> { - private W(T proto) {} - } + static class W> { + private W(T proto) {} + } - > W i(G j, byte[] k) { - return new W<>(j.h(k)); - } + > W i(G j, byte[] k) { + return new W<>(j.h(k)); + } } diff --git a/framework/tests/all-systems/Issue1698.java b/framework/tests/all-systems/Issue1698.java index d0809bd6e51..ac759342671 100644 --- a/framework/tests/all-systems/Issue1698.java +++ b/framework/tests/all-systems/Issue1698.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/1698 public class Issue1698 { - static class B { - static C f() { - return new B().new C(); - } - - class C {} + static class B { + static C f() { + return new B().new C(); } + + class C {} + } } diff --git a/framework/tests/all-systems/Issue1708.java b/framework/tests/all-systems/Issue1708.java index e7b98be68a6..eea4466b511 100644 --- a/framework/tests/all-systems/Issue1708.java +++ b/framework/tests/all-systems/Issue1708.java @@ -6,42 +6,42 @@ import java.util.List; @SuppressWarnings({ - "unchecked", - "ainfertest", - "value" + "unchecked", + "ainfertest", + "value" }) // Don't issue warnings during ainfer tests, because more than one round of inference is required // for the value checker public class Issue1708 { - static class A {} + static class A {} - static class B {} + static class B {} - static class C {} + static class C {} - static class D {} + static class D {} - static class E {} + static class E {} - static class F {} + static class F {} - static class B1 extends B {} + static class B1 extends B {} - static class B2 extends B {} + static class B2 extends B {} - static class B3 extends B {} + static class B3 extends B {} - public static class Example extends A> { - private final List> f; + public static class Example extends A> { + private final List> f; - public Example(B1 b1, B2 b2, B3 b3) { - f = ListUtil.of(b1, b2, b3); - } + public Example(B1 b1, B2 b2, B3 b3) { + f = ListUtil.of(b1, b2, b3); } + } - public static class ListUtil { - public static List of(T... elems) { - return new ArrayList<>(Arrays.asList(elems)); - } + public static class ListUtil { + public static List of(T... elems) { + return new ArrayList<>(Arrays.asList(elems)); } + } } diff --git a/framework/tests/all-systems/Issue1709.java b/framework/tests/all-systems/Issue1709.java index dd90537e311..807817b4821 100644 --- a/framework/tests/all-systems/Issue1709.java +++ b/framework/tests/all-systems/Issue1709.java @@ -5,7 +5,7 @@ import java.util.List; public class Issue1709 { - public static void m(final List l) { - Iterator it = l.iterator(); - } + public static void m(final List l) { + Iterator it = l.iterator(); + } } diff --git a/framework/tests/all-systems/Issue1738.java b/framework/tests/all-systems/Issue1738.java index 2a6321f9895..2e63033a67d 100644 --- a/framework/tests/all-systems/Issue1738.java +++ b/framework/tests/all-systems/Issue1738.java @@ -5,29 +5,29 @@ @SuppressWarnings("all") // Only check for crashes public class Issue1738 { - static class TwoParamIterator implements Iterator { - @Override - public boolean hasNext() { - return false; - } + static class TwoParamIterator implements Iterator { + @Override + public boolean hasNext() { + return false; + } - @Override - public T next() { - return null; - } + @Override + public T next() { + return null; } + } - static class TwoParamCollection implements Iterable { - @Override - public TwoParamIterator iterator() { - return new TwoParamIterator(); - } + static class TwoParamCollection implements Iterable { + @Override + public TwoParamIterator iterator() { + return new TwoParamIterator(); } + } - static void test() { - TwoParamCollection c = new TwoParamCollection<>(); - for (String s : c) { - s.hashCode(); - } + static void test() { + TwoParamCollection c = new TwoParamCollection<>(); + for (String s : c) { + s.hashCode(); } + } } diff --git a/framework/tests/all-systems/Issue1749.java b/framework/tests/all-systems/Issue1749.java index bc9605f7d44..71efbc83517 100644 --- a/framework/tests/all-systems/Issue1749.java +++ b/framework/tests/all-systems/Issue1749.java @@ -2,15 +2,15 @@ // https://github.com/typetools/checker-framework/issues/1749 abstract class Issue1749 { - public interface A {} + public interface A {} - interface B extends A {} + interface B extends A {} - public class I {} + public class I {} - abstract I f(Class x); + abstract I f(Class x); - void f() { - I x = f(A.class); - } + void f() { + I x = f(A.class); + } } diff --git a/framework/tests/all-systems/Issue1809.java b/framework/tests/all-systems/Issue1809.java index dfeabe6fd37..7d2af47705c 100644 --- a/framework/tests/all-systems/Issue1809.java +++ b/framework/tests/all-systems/Issue1809.java @@ -12,25 +12,25 @@ @SuppressWarnings("unchecked") abstract class Issue1809 { - abstract Stream concat(Stream... streams); + abstract Stream concat(Stream... streams); - abstract Optional f(); + abstract Optional f(); - private static class A {} + private static class A {} - interface B { - List g(); - } + interface B { + List g(); + } - interface C { - List h(); - } + interface C { + List h(); + } - interface S {} + interface S {} - private Stream xrefsFor(B b) { - return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) - .filter(Optional::isPresent) - .map(Optional::get); - } + private Stream xrefsFor(B b) { + return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) + .filter(Optional::isPresent) + .map(Optional::get); + } } diff --git a/framework/tests/all-systems/Issue1865.java b/framework/tests/all-systems/Issue1865.java index f0a7e55f415..701c8328ef6 100644 --- a/framework/tests/all-systems/Issue1865.java +++ b/framework/tests/all-systems/Issue1865.java @@ -3,21 +3,21 @@ abstract class Issue1865 { - // Widening conversion + // Widening conversion - abstract int f(); + abstract int f(); - abstract int max(int... array); + abstract int max(int... array); - void g() { - long l = max(f(), f()); - } + void g() { + long l = max(f(), f()); + } - // String conversion + // String conversion - abstract Object h(Object... args); + abstract Object h(Object... args); - void i() { - Object o = "" + h(); - } + void i() { + Object o = "" + h(); + } } diff --git a/framework/tests/all-systems/Issue1867.java b/framework/tests/all-systems/Issue1867.java index 9b93af43cf2..6fbc00f696c 100644 --- a/framework/tests/all-systems/Issue1867.java +++ b/framework/tests/all-systems/Issue1867.java @@ -4,17 +4,17 @@ import java.util.List; public abstract class Issue1867 { - interface AInterface {} + interface AInterface {} - interface BInterface { - List g(); - } + interface BInterface { + List g(); + } - abstract List> h(); + abstract List> h(); - void f() { - for (BInterface x : h()) { - for (AInterface y : x.g()) {} - } + void f() { + for (BInterface x : h()) { + for (AInterface y : x.g()) {} } + } } diff --git a/framework/tests/all-systems/Issue1920.java b/framework/tests/all-systems/Issue1920.java index 0d8bb0f7a19..3fa235fd729 100644 --- a/framework/tests/all-systems/Issue1920.java +++ b/framework/tests/all-systems/Issue1920.java @@ -6,25 +6,25 @@ @SuppressWarnings("all") // Only check for crashes public class Issue1920 { - static class Foo implements Iterable { - public Iterator iterator() { - return new Iterator() { - @Override - public boolean hasNext() { - return false; - } + static class Foo implements Iterable { + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return false; + } - @Override - public Object next() { - throw new NoSuchElementException(); - } - }; + @Override + public Object next() { + throw new NoSuchElementException(); } + }; } + } - static void testErasedIterator(Foo foo) { - for (Object x : foo) { - x.hashCode(); - } + static void testErasedIterator(Foo foo) { + for (Object x : foo) { + x.hashCode(); } + } } diff --git a/framework/tests/all-systems/Issue1948.java b/framework/tests/all-systems/Issue1948.java index 2512b786253..93615260504 100644 --- a/framework/tests/all-systems/Issue1948.java +++ b/framework/tests/all-systems/Issue1948.java @@ -11,152 +11,149 @@ @SuppressWarnings("all") // ensure no crash public class Issue1948< - K, V, E extends Issue1948.MyEntry, S extends Issue1948.MyClass> - implements ConcurrentMap { + K, V, E extends Issue1948.MyEntry, S extends Issue1948.MyClass> + implements ConcurrentMap { - private Issue1948(MapMaker builder, InternalEntryHelper entryHelper) {} + private Issue1948(MapMaker builder, InternalEntryHelper entryHelper) {} - /** Returns a fresh {@link Issue1948} as specified by the given {@code builder}. */ - static Issue1948, ?> create(MapMaker builder) { - return new Issue1948<>(builder, Helper.instance()); - } + /** Returns a fresh {@link Issue1948} as specified by the given {@code builder}. */ + static Issue1948, ?> create(MapMaker builder) { + return new Issue1948<>(builder, Helper.instance()); + } - interface MyEntry> {} + interface MyEntry> {} - abstract static class MyClass< - K, V, E extends MyEntry, S extends MyClass> {} + abstract static class MyClass, S extends MyClass> {} - static final class MapMaker {} + static final class MapMaker {} - static final class Helper - implements InternalEntryHelper< - K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueMyClass> { - static Helper instance() { - return null; - } + static final class Helper + implements InternalEntryHelper< + K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueMyClass> { + static Helper instance() { + return null; } + } - interface InternalEntryHelper, S> {} - - abstract static class StrongKeyStrongValueEntry - extends AbstractStrongKeyEntry> - implements StrongValueEntry> {} - - abstract static class AbstractStrongKeyEntry> - implements MyEntry {} + interface InternalEntryHelper, S> {} - interface StrongValueEntry> extends MyEntry {} + abstract static class StrongKeyStrongValueEntry + extends AbstractStrongKeyEntry> + implements StrongValueEntry> {} - abstract static class StrongKeyStrongValueMyClass - extends MyClass< - K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueMyClass> {} + abstract static class AbstractStrongKeyEntry> + implements MyEntry {} - @Override - public int size() { - return 0; - } + interface StrongValueEntry> extends MyEntry {} - @Override - public boolean isEmpty() { - return false; - } + abstract static class StrongKeyStrongValueMyClass + extends MyClass, StrongKeyStrongValueMyClass> {} - @Override - public boolean containsKey(Object key) { - return false; - } + @Override + public int size() { + return 0; + } - @Override - public boolean containsValue(Object value) { - return false; - } - - @Override - public V get(Object key) { - return null; - } - - @Override - public V put(K key, V value) { - return null; - } - - @Override - public V remove(Object key) { - return null; - } - - @Override - public void putAll(Map m) {} - - @Override - public void clear() {} - - @Override - public Set keySet() { - return null; - } - - @Override - public Collection values() { - return null; - } - - @Override - public Set> entrySet() { - return null; - } - - @Override - public V getOrDefault(Object key, V defaultValue) { - return null; - } - - @Override - public void forEach(BiConsumer action) {} - - @Override - public V putIfAbsent(K key, V value) { - return null; - } - - @Override - public boolean remove(Object key, Object value) { - return false; - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - return false; - } - - @Override - public V replace(K key, V value) { - return null; - } - - @Override - public void replaceAll(BiFunction function) {} - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return null; - } - - @Override - public V computeIfPresent( - K key, BiFunction remappingFunction) { - return null; - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - return null; - } - - @Override - public V merge( - K key, V value, BiFunction remappingFunction) { - return null; - } + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsKey(Object key) { + return false; + } + + @Override + public boolean containsValue(Object value) { + return false; + } + + @Override + public V get(Object key) { + return null; + } + + @Override + public V put(K key, V value) { + return null; + } + + @Override + public V remove(Object key) { + return null; + } + + @Override + public void putAll(Map m) {} + + @Override + public void clear() {} + + @Override + public Set keySet() { + return null; + } + + @Override + public Collection values() { + return null; + } + + @Override + public Set> entrySet() { + return null; + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + return null; + } + + @Override + public void forEach(BiConsumer action) {} + + @Override + public V putIfAbsent(K key, V value) { + return null; + } + + @Override + public boolean remove(Object key, Object value) { + return false; + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return false; + } + + @Override + public V replace(K key, V value) { + return null; + } + + @Override + public void replaceAll(BiFunction function) {} + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return null; + } + + @Override + public V computeIfPresent( + K key, BiFunction remappingFunction) { + return null; + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return null; + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return null; + } } diff --git a/framework/tests/all-systems/Issue1991.java b/framework/tests/all-systems/Issue1991.java index 707a46b346b..32208aed70b 100644 --- a/framework/tests/all-systems/Issue1991.java +++ b/framework/tests/all-systems/Issue1991.java @@ -3,13 +3,13 @@ @SuppressWarnings("all") // Check for crashes only public class Issue1991 { - interface Comp> {} + interface Comp> {} - interface C> {} + interface C> {} - class D implements Comp {} + class D implements Comp {} - void f(C p) { - C x = p; - } + void f(C p) { + C x = p; + } } diff --git a/framework/tests/all-systems/Issue1991Full.java b/framework/tests/all-systems/Issue1991Full.java index 017235e9fab..9b89b24d88b 100644 --- a/framework/tests/all-systems/Issue1991Full.java +++ b/framework/tests/all-systems/Issue1991Full.java @@ -6,21 +6,21 @@ @SuppressWarnings("all") // Check for crashes only abstract class Issue1991Full { - abstract void g(A obj); + abstract void g(A obj); - static class A { - A(C c) {} - } + static class A { + A(C c) {} + } - interface B extends C {} + interface B extends C {} - interface C, Y extends Serializable> {} + interface C, Y extends Serializable> {} - public class E implements Serializable {} + public class E implements Serializable {} - abstract static class D implements Comparable, Serializable {} + abstract static class D implements Comparable, Serializable {} - void f(B b) { - g(new A(b)); - } + void f(B b) { + g(new A(b)); + } } diff --git a/framework/tests/all-systems/Issue1992.java b/framework/tests/all-systems/Issue1992.java index 5cc6b5d7ba2..0673d75b760 100644 --- a/framework/tests/all-systems/Issue1992.java +++ b/framework/tests/all-systems/Issue1992.java @@ -7,23 +7,23 @@ @SuppressWarnings("all") // Check for crashes only public class Issue1992 { - interface A {} + interface A {} - static class B { - C a; - T b; - } + static class B { + C a; + T b; + } - static class C { - Function c; + static class C { + Function c; - enum E { - NONE - } + enum E { + NONE } + } - boolean f(List> x) { - B d = x.get(x.size() - 1); - return d.a.c.apply(d.b) != C.E.NONE; - } + boolean f(List> x) { + B d = x.get(x.size() - 1); + return d.a.c.apply(d.b) != C.E.NONE; + } } diff --git a/framework/tests/all-systems/Issue2048.java b/framework/tests/all-systems/Issue2048.java index a0a7f6e7771..00aa40d853d 100644 --- a/framework/tests/all-systems/Issue2048.java +++ b/framework/tests/all-systems/Issue2048.java @@ -6,12 +6,12 @@ // checker/tests/nullness public class Issue2048 { - interface Foo {} + interface Foo {} - interface Fooer {} + interface Fooer {} - class Use { - @SuppressWarnings("all") // Check for crashes. - void foo(Fooer fooer) {} - } + class Use { + @SuppressWarnings("all") // Check for crashes. + void foo(Fooer fooer) {} + } } diff --git a/framework/tests/all-systems/Issue2082.java b/framework/tests/all-systems/Issue2082.java index be1ba6c0327..380667a09ad 100644 --- a/framework/tests/all-systems/Issue2082.java +++ b/framework/tests/all-systems/Issue2082.java @@ -4,5 +4,5 @@ import java.util.concurrent.Callable; public class Issue2082 { - Callable foo = () -> 0; + Callable foo = () -> 0; } diff --git a/framework/tests/all-systems/Issue2088.java b/framework/tests/all-systems/Issue2088.java index 364fbbb90ec..1a1859f0797 100644 --- a/framework/tests/all-systems/Issue2088.java +++ b/framework/tests/all-systems/Issue2088.java @@ -7,25 +7,25 @@ @SuppressWarnings({"unchecked", "all"}) // Check for crashes only abstract class Issue2088 { - interface A> {} + interface A> {} - interface B extends A {} + interface B extends A {} - interface C

          { - interface F> {} - } + interface C

          { + interface F> {} + } - interface D {} + interface D {} - static class Key { - static Key get(Type type) { - return null; - } + static class Key { + static Key get(Type type) { + return null; } + } - abstract ParameterizedType n(Type o, Class r, Type... a); + abstract ParameterizedType n(Type o, Class r, Type... a); - , Z extends Y> void f(Class c) { - Key> f = (Key>) Key.get(n(C.class, C.F.class, c)); - } + , Z extends Y> void f(Class c) { + Key> f = (Key>) Key.get(n(C.class, C.F.class, c)); + } } diff --git a/framework/tests/all-systems/Issue2190.java b/framework/tests/all-systems/Issue2190.java index 3341136043e..b75dc3517cf 100644 --- a/framework/tests/all-systems/Issue2190.java +++ b/framework/tests/all-systems/Issue2190.java @@ -1,24 +1,24 @@ // Test case for Issue 2190. public class Issue2190 { - interface A {} + interface A {} - abstract class B implements C {} + abstract class B implements C {} - interface C {} + interface C {} - class D {} + class D {} - interface I { - I to(D x); - } + interface I { + I to(D x); + } - abstract class Z { - void f(I> x, D> y) { - x.to(y); - } + abstract class Z { + void f(I> x, D> y) { + x.to(y); + } - void g(I> x, D> y) { - x.to(y); - } + void g(I> x, D> y) { + x.to(y); } + } } diff --git a/framework/tests/all-systems/Issue2195.java b/framework/tests/all-systems/Issue2195.java index 0e8f4f00e3e..4bee9a2c1d6 100644 --- a/framework/tests/all-systems/Issue2195.java +++ b/framework/tests/all-systems/Issue2195.java @@ -1,17 +1,17 @@ // Test case for Issue 2195. @SuppressWarnings("unchecked") public class Issue2195 { - interface A {} + interface A {} - interface B {} + interface B {} - class C { - C(T t) {} - } + class C { + C(T t) {} + } - class X { - X(B b) { - new C(b); - } + class X { + X(B b) { + new C(b); } + } } diff --git a/framework/tests/all-systems/Issue2196.java b/framework/tests/all-systems/Issue2196.java index 4344e7cccbf..4b45057c741 100644 --- a/framework/tests/all-systems/Issue2196.java +++ b/framework/tests/all-systems/Issue2196.java @@ -1,20 +1,20 @@ // Test case for Issue 2196. @SuppressWarnings("unchecked") public class Issue2196 { - interface A {} + interface A {} - interface B {} + interface B {} - interface C {} + interface C {} - abstract class X { + abstract class X { - class D implements B {} + class D implements B {} - abstract void f(B b); + abstract void f(B b); - private void g() { - f(new D()); - } + private void g() { + f(new D()); } + } } diff --git a/framework/tests/all-systems/Issue2198.java b/framework/tests/all-systems/Issue2198.java index 59ff0f4c3fa..6d6d20b97a2 100644 --- a/framework/tests/all-systems/Issue2198.java +++ b/framework/tests/all-systems/Issue2198.java @@ -1,17 +1,17 @@ // Test case for Issue 2198. @SuppressWarnings("unchecked") public class Issue2198 { - interface A {} + interface A {} - class B {} + class B {} - class C { - C(T t) {} - } + class C { + C(T t) {} + } - class X { - X(B b) { - new C(b); - } + class X { + X(B b) { + new C(b); } + } } diff --git a/framework/tests/all-systems/Issue2199.java b/framework/tests/all-systems/Issue2199.java index d97c94662ab..bcddf4b0ed7 100644 --- a/framework/tests/all-systems/Issue2199.java +++ b/framework/tests/all-systems/Issue2199.java @@ -1,15 +1,15 @@ // Test case for Issue 2199. @SuppressWarnings("unchecked") public class Issue2199 { - static class StrangeConstructorTypeArgs { - public StrangeConstructorTypeArgs(Abstract abs) {} - } + static class StrangeConstructorTypeArgs { + public StrangeConstructorTypeArgs(Abstract abs) {} + } - abstract static class Abstract {} + abstract static class Abstract {} - static class Concrete extends Abstract {} + static class Concrete extends Abstract {} - static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { - return new StrangeConstructorTypeArgs(new Concrete<>()); - } + static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { + return new StrangeConstructorTypeArgs(new Concrete<>()); + } } diff --git a/framework/tests/all-systems/Issue2234.java b/framework/tests/all-systems/Issue2234.java index 1d06e3f76fc..bc7c1bec225 100644 --- a/framework/tests/all-systems/Issue2234.java +++ b/framework/tests/all-systems/Issue2234.java @@ -5,12 +5,12 @@ import java.util.List; class Issue2234Super { - Issue2234Super(List p) {} + Issue2234Super(List p) {} } @SuppressWarnings("unchecked") // raw supertype class Issue2234Sub extends Issue2234Super { - Issue2234Sub() { - super(new LinkedList<>()); - } + Issue2234Sub() { + super(new LinkedList<>()); + } } diff --git a/framework/tests/all-systems/Issue2302.java b/framework/tests/all-systems/Issue2302.java index 3ea4636d86f..081a12f2d1a 100644 --- a/framework/tests/all-systems/Issue2302.java +++ b/framework/tests/all-systems/Issue2302.java @@ -3,23 +3,23 @@ @SuppressWarnings("unchecked") public class Issue2302 { - static class StrangeConstructorTypeArgs { - // The constructor does not use the type parameter V. - public StrangeConstructorTypeArgs(MyClass abs) {} - } + static class StrangeConstructorTypeArgs { + // The constructor does not use the type parameter V. + public StrangeConstructorTypeArgs(MyClass abs) {} + } - static class MyClass {} + static class MyClass {} - static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { - // Crash with the diamond operator. - // Type inference chooses `Object` as the type argument. - // That is a bug, since it should choose exactly `byte[]`. - return new StrangeConstructorTypeArgs(new MyClass<>()); + static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { + // Crash with the diamond operator. + // Type inference chooses `Object` as the type argument. + // That is a bug, since it should choose exactly `byte[]`. + return new StrangeConstructorTypeArgs(new MyClass<>()); - // No crash with an explicit type argument (no diamond operator), no matter what it is. - // return new StrangeConstructorTypeArgs(new MyClass()); - // return new StrangeConstructorTypeArgs(new MyClass()); - // return new StrangeConstructorTypeArgs(new MyClass()); - // return new StrangeConstructorTypeArgs(new MyClass<@Tainted Object>()); - } + // No crash with an explicit type argument (no diamond operator), no matter what it is. + // return new StrangeConstructorTypeArgs(new MyClass()); + // return new StrangeConstructorTypeArgs(new MyClass()); + // return new StrangeConstructorTypeArgs(new MyClass()); + // return new StrangeConstructorTypeArgs(new MyClass<@Tainted Object>()); + } } diff --git a/framework/tests/all-systems/Issue2370.java b/framework/tests/all-systems/Issue2370.java index e451ebfdda4..5fbc63f3586 100644 --- a/framework/tests/all-systems/Issue2370.java +++ b/framework/tests/all-systems/Issue2370.java @@ -4,30 +4,30 @@ @SuppressWarnings("all") public class Issue2370 { - private Stream getAction2370s(final State2370 state) { - return Stream.of( - toStream(state.getOnExit()).flatMap(t -> t.getAction2370s().stream()), - toStream(state.getOnSignal()).flatMap(t -> t.getAction2370s().stream()), - toStream(state.getOnEnter()).flatMap(t -> t.getAction2370s().stream())) - .flatMap(actionStream -> actionStream); - } + private Stream getAction2370s(final State2370 state) { + return Stream.of( + toStream(state.getOnExit()).flatMap(t -> t.getAction2370s().stream()), + toStream(state.getOnSignal()).flatMap(t -> t.getAction2370s().stream()), + toStream(state.getOnEnter()).flatMap(t -> t.getAction2370s().stream())) + .flatMap(actionStream -> actionStream); + } - private Stream toStream(final Collection obj) { - return Optional.ofNullable(obj) - .map(Stream::of) - .orElseGet(Stream::empty) - .flatMap(Collection::stream); - } + private Stream toStream(final Collection obj) { + return Optional.ofNullable(obj) + .map(Stream::of) + .orElseGet(Stream::empty) + .flatMap(Collection::stream); + } } interface Action2370 { - public Collection getAction2370s(); + public Collection getAction2370s(); } interface State2370 { - public Collection getOnExit(); + public Collection getOnExit(); - public Collection getOnSignal(); + public Collection getOnSignal(); - public Collection getOnEnter(); + public Collection getOnEnter(); } diff --git a/framework/tests/all-systems/Issue2371.java b/framework/tests/all-systems/Issue2371.java index da6ab66cde5..9abe3188b72 100644 --- a/framework/tests/all-systems/Issue2371.java +++ b/framework/tests/all-systems/Issue2371.java @@ -1,8 +1,8 @@ @SuppressWarnings("all") public class Issue2371> { - void method(Issue2371 i) { - other(i); - } + void method(Issue2371 i) { + other(i); + } - void other(Issue2371 e) {} + void other(Issue2371 e) {} } diff --git a/framework/tests/all-systems/Issue2446.java b/framework/tests/all-systems/Issue2446.java index 2375f562585..99ebd21743d 100644 --- a/framework/tests/all-systems/Issue2446.java +++ b/framework/tests/all-systems/Issue2446.java @@ -1,13 +1,13 @@ public class Issue2446 { - static class One {} + static class One {} - static class Two> extends One {} + static class Two> extends One {} - static class Three> extends Two {} + static class Three> extends Two {} - static > Three f() { - throw new AssertionError(); - } + static > Three f() { + throw new AssertionError(); + } - static final Three F = f(); + static final Three F = f(); } diff --git a/framework/tests/all-systems/Issue2480.java b/framework/tests/all-systems/Issue2480.java index e78a90a726f..c1b65d94e88 100644 --- a/framework/tests/all-systems/Issue2480.java +++ b/framework/tests/all-systems/Issue2480.java @@ -5,9 +5,9 @@ @SuppressWarnings({"unchecked", "all"}) // check for crashes only abstract class Issue2480 { - void testCase() { - for (Class wrapperType : of(Character.class, Boolean.class)) {} - } + void testCase() { + for (Class wrapperType : of(Character.class, Boolean.class)) {} + } - abstract List of(E... e1); + abstract List of(E... e1); } diff --git a/framework/tests/all-systems/Issue263.java b/framework/tests/all-systems/Issue263.java index c873ce143c2..121f24bdedf 100644 --- a/framework/tests/all-systems/Issue263.java +++ b/framework/tests/all-systems/Issue263.java @@ -3,29 +3,29 @@ abstract class Outer { - public class Inner { - private T t; + public class Inner { + private T t; - public Inner(T t) { - this.t = t; - } + public Inner(T t) { + this.t = t; + } - T get() { - return t; - } + T get() { + return t; } + } - public abstract Inner getInner(); + public abstract Inner getInner(); } public class Issue263 { - public Issue263(Outer outer) { - this.outer = outer; - } + public Issue263(Outer outer) { + this.outer = outer; + } - Outer outer; + Outer outer; - public void context() { - String s = outer.getInner().get(); - } + public void context() { + String s = outer.getInner().get(); + } } diff --git a/framework/tests/all-systems/Issue2678.java b/framework/tests/all-systems/Issue2678.java index c4cb96796f7..2fffc3aaf04 100644 --- a/framework/tests/all-systems/Issue2678.java +++ b/framework/tests/all-systems/Issue2678.java @@ -1,6 +1,6 @@ public class Issue2678 { - @SuppressWarnings({"index:array.access.unsafe.low", "index:array.access.unsafe.high"}) - public synchronized void incrementPushed(long[] pushed, int operationType) { - ++(pushed[operationType]); - } + @SuppressWarnings({"index:array.access.unsafe.low", "index:array.access.unsafe.high"}) + public synchronized void incrementPushed(long[] pushed, int operationType) { + ++(pushed[operationType]); + } } diff --git a/framework/tests/all-systems/Issue2717.java b/framework/tests/all-systems/Issue2717.java index c917d3c9ddd..573faad77b8 100644 --- a/framework/tests/all-systems/Issue2717.java +++ b/framework/tests/all-systems/Issue2717.java @@ -1,26 +1,26 @@ @SuppressWarnings("all") public class Issue2717 { - interface Tree {} + interface Tree {} - interface ExpressionTree extends Tree {} + interface ExpressionTree extends Tree {} - interface BinaryTree extends ExpressionTree {} + interface BinaryTree extends ExpressionTree {} - interface Matcher {} + interface Matcher {} - public static void test(Matcher m) {} + public static void test(Matcher m) {} - public static void caller() { - test(toType(BinaryTree.class, Issue2717.allOf())); - } + public static void caller() { + test(toType(BinaryTree.class, Issue2717.allOf())); + } - public static Matcher allOf() { - return null; - } + public static Matcher allOf() { + return null; + } - public static Matcher toType( - Class type, Matcher matcher) { - return null; - } + public static Matcher toType( + Class type, Matcher matcher) { + return null; + } } diff --git a/framework/tests/all-systems/Issue2739.java b/framework/tests/all-systems/Issue2739.java index 4bf545de5e8..ddacf4361eb 100644 --- a/framework/tests/all-systems/Issue2739.java +++ b/framework/tests/all-systems/Issue2739.java @@ -1,9 +1,9 @@ public class Issue2739 { - public interface EppEnum { - String getXmlName(); - } + public interface EppEnum { + String getXmlName(); + } - void method(E e) { - String s = e.getXmlName(); - } + void method(E e) { + String s = e.getXmlName(); + } } diff --git a/framework/tests/all-systems/Issue2779.java b/framework/tests/all-systems/Issue2779.java index 9199a6ca59a..e61cd02d312 100644 --- a/framework/tests/all-systems/Issue2779.java +++ b/framework/tests/all-systems/Issue2779.java @@ -4,23 +4,23 @@ // @below-java9-jdk-skip-test @SuppressWarnings("all") // Just check for crashes. interface Issue2779 { - S get(); + S get(); - static Issue2779 wrap2(T val) { - return new Issue2779() { - @Override - public T get() { - return val; - } - }; - } + static Issue2779 wrap2(T val) { + return new Issue2779() { + @Override + public T get() { + return val; + } + }; + } - static Issue2779 wrap(T val) { - return new Issue2779<>() { - @Override - public T get() { - return val; - } - }; - } + static Issue2779 wrap(T val) { + return new Issue2779<>() { + @Override + public T get() { + return val; + } + }; + } } diff --git a/framework/tests/all-systems/Issue2781.java b/framework/tests/all-systems/Issue2781.java index 003652baa67..5213c82fd43 100644 --- a/framework/tests/all-systems/Issue2781.java +++ b/framework/tests/all-systems/Issue2781.java @@ -5,18 +5,18 @@ import java.util.stream.Stream; public class Issue2781 { - class Wrapper { - Wrapper(T t) {} - } + class Wrapper { + Wrapper(T t) {} + } - Stream>> getStreamOfWrappedFunctions1() { - // inferred type in new - return Stream.>>of(new Wrapper<>(e -> e)); - } + Stream>> getStreamOfWrappedFunctions1() { + // inferred type in new + return Stream.>>of(new Wrapper<>(e -> e)); + } - Stream>> getStreamOfWrappedFunctions2() { - // explicit type in new - return Stream.>>of( - new Wrapper>(e -> e)); - } + Stream>> getStreamOfWrappedFunctions2() { + // explicit type in new + return Stream.>>of( + new Wrapper>(e -> e)); + } } diff --git a/framework/tests/all-systems/Issue301.java b/framework/tests/all-systems/Issue301.java index b8ed2243195..f77023e4893 100644 --- a/framework/tests/all-systems/Issue301.java +++ b/framework/tests/all-systems/Issue301.java @@ -2,1462 +2,1462 @@ // https://github.com/typetools/checker-framework/issues/301 public class Issue301 { - { - java.util.Vector v = new java.util.Vector(); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - } + { + java.util.Vector v = new java.util.Vector(); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + } } diff --git a/framework/tests/all-systems/Issue3021.java b/framework/tests/all-systems/Issue3021.java index 9b6428a956a..c8ad7b0198e 100644 --- a/framework/tests/all-systems/Issue3021.java +++ b/framework/tests/all-systems/Issue3021.java @@ -5,9 +5,9 @@ import org.checkerframework.common.aliasing.qual.MaybeAliased; public class Issue3021 { - void make() { - new Lib<@MaybeAliased T>() {}; - } + void make() { + new Lib<@MaybeAliased T>() {}; + } - class Lib {} + class Lib {} } diff --git a/framework/tests/all-systems/Issue3055.java b/framework/tests/all-systems/Issue3055.java index 7d85929cffe..d3e0078b890 100644 --- a/framework/tests/all-systems/Issue3055.java +++ b/framework/tests/all-systems/Issue3055.java @@ -1,14 +1,14 @@ public class Issue3055 { - class C1.Bound> { - class Bound {} - } + class C1.Bound> { + class Bound {} + } - class C2 { - class Bound {} - } + class C2 { + class Bound {} + } - class C3.Bound> { - class Bound {} - } + class C3.Bound> { + class Bound {} + } } diff --git a/framework/tests/all-systems/Issue3120.java b/framework/tests/all-systems/Issue3120.java index 431bf1a9551..2a623d899a9 100644 --- a/framework/tests/all-systems/Issue3120.java +++ b/framework/tests/all-systems/Issue3120.java @@ -1,22 +1,22 @@ @SuppressWarnings("all") // Ensure no crashes public class Issue3120 { - CharSequence foo() { - return bar(); - } + CharSequence foo() { + return bar(); + } - > CharSequence bar() { - return null; - } + > CharSequence bar() { + return null; + } - CharSequence foo0() { - return bar0(null); - } + CharSequence foo0() { + return bar0(null); + } - & AnotherType> CharSequence bar0(SomeType type) { - return null; - } + & AnotherType> CharSequence bar0(SomeType type) { + return null; + } - class SomeType {} + class SomeType {} - interface AnotherType {} + interface AnotherType {} } diff --git a/framework/tests/all-systems/Issue3128.java b/framework/tests/all-systems/Issue3128.java index e9aa730e0d5..fd72ec17985 100644 --- a/framework/tests/all-systems/Issue3128.java +++ b/framework/tests/all-systems/Issue3128.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/3128 public class Issue3128 { - class One {} + class One {} - class Two> {} + class Two> {} - One> foo() { - return new One<>(); - } + One> foo() { + return new One<>(); + } } diff --git a/framework/tests/all-systems/Issue3232.java b/framework/tests/all-systems/Issue3232.java index 22079628fa4..be29842c912 100644 --- a/framework/tests/all-systems/Issue3232.java +++ b/framework/tests/all-systems/Issue3232.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/3232 class Issue3232A { - @SuppressWarnings("unchecked") - void foo(B... values) {} + @SuppressWarnings("unchecked") + void foo(B... values) {} } class Issue3232C extends Issue3232A { - void bar(int value) { - foo(value); - } + void bar(int value) { + foo(value); + } } diff --git a/framework/tests/all-systems/Issue3277.java b/framework/tests/all-systems/Issue3277.java index eb8fdcfa196..877eb24b63c 100644 --- a/framework/tests/all-systems/Issue3277.java +++ b/framework/tests/all-systems/Issue3277.java @@ -5,15 +5,15 @@ import org.checkerframework.common.aliasing.qual.MaybeAliased; public class Issue3277 { - void f() { - Object o = new @MaybeAliased Generic[0]; - o = new Generic<@MaybeAliased ?>[0]; - } + void f() { + Object o = new @MaybeAliased Generic[0]; + o = new Generic<@MaybeAliased ?>[0]; + } - // TODO: Having the same code in fields crashes javac - // with an AssertionError. - // Object o1 = new @MaybeAliased Generic[0]; - // Object o2 = new Generic<@MaybeAliased ?>[0]; + // TODO: Having the same code in fields crashes javac + // with an AssertionError. + // Object o1 = new @MaybeAliased Generic[0]; + // Object o2 = new Generic<@MaybeAliased ?>[0]; - class Generic {} + class Generic {} } diff --git a/framework/tests/all-systems/Issue3295.java b/framework/tests/all-systems/Issue3295.java index 3e2497f9229..991f8128ad3 100644 --- a/framework/tests/all-systems/Issue3295.java +++ b/framework/tests/all-systems/Issue3295.java @@ -1,17 +1,17 @@ import java.util.List; public class Issue3295 { - interface P { + interface P { - Class h(); + Class h(); - List> g(); - } + List> g(); + } - interface Q {} + interface Q {} - @SuppressWarnings("interning:unnecessary.equals") // This warning is expected. - static void f(P t) { - t.g().stream().filter(x -> x.h().equals(Q.class)); - } + @SuppressWarnings("interning:unnecessary.equals") // This warning is expected. + static void f(P t) { + t.g().stream().filter(x -> x.h().equals(Q.class)); + } } diff --git a/framework/tests/all-systems/Issue3302.java b/framework/tests/all-systems/Issue3302.java index c11b2591d74..fa1bbf4e27b 100644 --- a/framework/tests/all-systems/Issue3302.java +++ b/framework/tests/all-systems/Issue3302.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/3302 public class Issue3302 { - void foo(Bar b) {} + void foo(Bar b) {} - interface Bar & A> {} + interface Bar & A> {} - interface A {} + interface A {} - interface Box {} + interface Box {} } diff --git a/framework/tests/all-systems/Issue3377.java b/framework/tests/all-systems/Issue3377.java index 3265d19429c..3022ae4e700 100644 --- a/framework/tests/all-systems/Issue3377.java +++ b/framework/tests/all-systems/Issue3377.java @@ -1,15 +1,15 @@ // @below-java11-jdk-skip-test @SuppressWarnings("all") // Check for crashes. public class Issue3377 { - static class Box {} + static class Box {} - interface Unboxer { - T unbox(Box p); - } + interface Unboxer { + T unbox(Box p); + } - static class Crash { - Box crash(Unboxer ub) { - return ub.unbox(new Box<>() {}); - } + static class Crash { + Box crash(Unboxer ub) { + return ub.unbox(new Box<>() {}); } + } } diff --git a/framework/tests/all-systems/Issue3569.java b/framework/tests/all-systems/Issue3569.java index 670ff161dea..211e75dc6eb 100644 --- a/framework/tests/all-systems/Issue3569.java +++ b/framework/tests/all-systems/Issue3569.java @@ -1,9 +1,9 @@ public abstract class Issue3569 { - public interface MyInterface {} + public interface MyInterface {} - public abstract T getT(); + public abstract T getT(); - protected K getK(Issue3569 ab) { - return ab.getT(); - } + protected K getK(Issue3569 ab) { + return ab.getT(); + } } diff --git a/framework/tests/all-systems/Issue3570.java b/framework/tests/all-systems/Issue3570.java index 4bc9753ee5a..c64cbe4db32 100644 --- a/framework/tests/all-systems/Issue3570.java +++ b/framework/tests/all-systems/Issue3570.java @@ -1,64 +1,63 @@ @SuppressWarnings("all") // Check for crashes. public class Issue3570 { - public interface Freezable extends Cloneable {} + public interface Freezable extends Cloneable {} - public static final class Key extends Iced> implements Comparable { - @Override - public int compareTo(Object o) { - return 0; - } + public static final class Key extends Iced> implements Comparable { + @Override + public int compareTo(Object o) { + return 0; } + } - public abstract static class Keyed extends Iced {} + public abstract static class Keyed extends Iced {} - public abstract static class Lockable> extends Keyed {} + public abstract static class Lockable> extends Keyed {} - public abstract static class Iced - implements Freezable, java.io.Externalizable { - @Override - public void readExternal(java.io.ObjectInput ois) - throws java.io.IOException, ClassNotFoundException {} + public abstract static class Iced + implements Freezable, java.io.Externalizable { + @Override + public void readExternal(java.io.ObjectInput ois) + throws java.io.IOException, ClassNotFoundException {} - @Override - public void writeExternal(java.io.ObjectOutput oos) throws java.io.IOException {} - } - - public abstract static class Model< - M extends Model, P extends Model.Parameters, O extends Model.Output> - extends Lockable { - public P _parms; + @Override + public void writeExternal(java.io.ObjectOutput oos) throws java.io.IOException {} + } - public abstract static class Parameters extends Iced { - public Key _train; - } + public abstract static class Model< + M extends Model, P extends Model.Parameters, O extends Model.Output> + extends Lockable { + public P _parms; - public abstract static class Output extends Iced {} + public abstract static class Parameters extends Iced { + public Key _train; } - public static class Frame extends Lockable {} + public abstract static class Output extends Iced {} + } + + public static class Frame extends Lockable {} - public abstract static class Schema> extends Iced {} + public abstract static class Schema> extends Iced {} - public static class SchemaV3> extends Schema {} + public static class SchemaV3> extends Schema {} - public static class KeyV3, K extends Keyed> - extends SchemaV3> { - public KeyV3(Key key) {} + public static class KeyV3, K extends Keyed> + extends SchemaV3> { + public KeyV3(Key key) {} - public static class FrameKeyV3 extends KeyV3 { - public FrameKeyV3(Key key) { - super(key); - } - } + public static class FrameKeyV3 extends KeyV3 { + public FrameKeyV3(Key key) { + super(key); + } } + } - public static class ModelSchemaBaseV3< - M extends Model, S extends ModelSchemaBaseV3> - extends SchemaV3 { - public KeyV3.FrameKeyV3 data_frame; + public static class ModelSchemaBaseV3, S extends ModelSchemaBaseV3> + extends SchemaV3 { + public KeyV3.FrameKeyV3 data_frame; - public ModelSchemaBaseV3(M m) { - this.data_frame = new KeyV3.FrameKeyV3(m._parms._train); - } + public ModelSchemaBaseV3(M m) { + this.data_frame = new KeyV3.FrameKeyV3(m._parms._train); } + } } diff --git a/framework/tests/all-systems/Issue3598.java b/framework/tests/all-systems/Issue3598.java index eeb4b20aaa3..e7e06d144b3 100644 --- a/framework/tests/all-systems/Issue3598.java +++ b/framework/tests/all-systems/Issue3598.java @@ -2,24 +2,24 @@ public class Issue3598 { - static class DClass extends EClass {} + static class DClass extends EClass {} - static class EClass {} + static class EClass {} - // Must be Function, can't use interface defined in this class. - static class XClass

          implements Function { + // Must be Function, can't use interface defined in this class. + static class XClass

          implements Function { - @Override - public P apply(P protoT) { - return protoT; - } - - // DClass extends a raw class. - static Function f(DClass k) { - // Crash on this line. - return new XClass<>(k); - } + @Override + public P apply(P protoT) { + return protoT; + } - XClass(P p) {} + // DClass extends a raw class. + static Function f(DClass k) { + // Crash on this line. + return new XClass<>(k); } + + XClass(P p) {} + } } diff --git a/framework/tests/all-systems/Issue3785.java b/framework/tests/all-systems/Issue3785.java index ad1742b009c..3384d029b36 100644 --- a/framework/tests/all-systems/Issue3785.java +++ b/framework/tests/all-systems/Issue3785.java @@ -1,29 +1,29 @@ import java.util.List; public class Issue3785 { - public Issue3785(List l) {} + public Issue3785(List l) {} - void method(L l) {} + void method(L l) {} - @SuppressWarnings("unchecked") - void use(Issue3785 p, Object o, List list) { - // This type-checks in Java, but probably shouldn't. - p.method(o); - p.method(o); - new Issue3785(list); - } + @SuppressWarnings("unchecked") + void use(Issue3785 p, Object o, List list) { + // This type-checks in Java, but probably shouldn't. + p.method(o); + p.method(o); + new Issue3785(list); + } - /* - L method2(L l) { return l;} + /* + L method2(L l) { return l;} - @SuppressWarnings("unchecked") - void use2(Issue3785 p, Object o) { - // javac issues: "error: incompatible types: Object cannot be converted to Void" - p.method(o); - // javac issues: "error: incompatible types: Object cannot be converted to Void" - Void s = p.method2(o); - } - */ + @SuppressWarnings("unchecked") + void use2(Issue3785 p, Object o) { + // javac issues: "error: incompatible types: Object cannot be converted to Void" + p.method(o); + // javac issues: "error: incompatible types: Object cannot be converted to Void" + Void s = p.method2(o); + } + */ } /* diff --git a/framework/tests/all-systems/Issue3791.java b/framework/tests/all-systems/Issue3791.java index e66066f8b12..046ee244b44 100644 --- a/framework/tests/all-systems/Issue3791.java +++ b/framework/tests/all-systems/Issue3791.java @@ -1,19 +1,19 @@ public class Issue3791 { - interface MyInterface {} + interface MyInterface {} - abstract static class MyClass {} + abstract static class MyClass {} - static class SubMyClass extends MyClass> {} + static class SubMyClass extends MyClass> {} - static class Generic implements MyInterface {} + static class Generic implements MyInterface {} - abstract static class MyInterfaceMyClass> {} + abstract static class MyInterfaceMyClass> {} - void method(MyInterfaceMyClass param) { - // TODO: Should we open an issue for this? - // This code is reject by Eclipse and should be reject by javac. - // See the javac bug report https://bugs.openjdk.org/browse/JDK-8265255. - @SuppressWarnings({"unchecked", "type.argument"}) - MyInterfaceMyClass> local = (MyInterfaceMyClass>) param; - } + void method(MyInterfaceMyClass param) { + // TODO: Should we open an issue for this? + // This code is reject by Eclipse and should be reject by javac. + // See the javac bug report https://bugs.openjdk.org/browse/JDK-8265255. + @SuppressWarnings({"unchecked", "type.argument"}) + MyInterfaceMyClass> local = (MyInterfaceMyClass>) param; + } } diff --git a/framework/tests/all-systems/Issue3826.java b/framework/tests/all-systems/Issue3826.java index dd8cbf8e31b..3f38c908a79 100644 --- a/framework/tests/all-systems/Issue3826.java +++ b/framework/tests/all-systems/Issue3826.java @@ -1,27 +1,27 @@ public class Issue3826 { - public static void getOption(Class cls, B[] opts) {} + public static void getOption(Class cls, B[] opts) {} - public static class ClassA { - public static class InnerClassA { - interface InnerInnerClassA {} - } + public static class ClassA { + public static class InnerClassA { + interface InnerInnerClassA {} } + } - public static class ClassB { - public abstract static class InnerClassB {} - } + public static class ClassB { + public abstract static class InnerClassB {} + } - public static class ClassC { - interface InterfaceClassC extends ClassA.InnerClassA.InnerInnerClassA {} + public static class ClassC { + interface InterfaceClassC extends ClassA.InnerClassA.InnerInnerClassA {} - private static class InnerClassC extends ClassA.InnerClassA implements InterfaceClassC {} + private static class InnerClassC extends ClassA.InnerClassA implements InterfaceClassC {} - public ClassC(ClassA.InnerClassA.InnerInnerClassA... opts) { - // Does not crash - Issue3826.getOption( - InnerClassC.class, opts); - // Crashes - Issue3826.getOption(InnerClassC.class, opts); - } + public ClassC(ClassA.InnerClassA.InnerInnerClassA... opts) { + // Does not crash + Issue3826.getOption( + InnerClassC.class, opts); + // Crashes + Issue3826.getOption(InnerClassC.class, opts); } + } } diff --git a/framework/tests/all-systems/Issue392.java b/framework/tests/all-systems/Issue392.java index 7639e4ec1bc..385c526ccef 100644 --- a/framework/tests/all-systems/Issue392.java +++ b/framework/tests/all-systems/Issue392.java @@ -3,7 +3,7 @@ public class Issue392 { - public void getFields(T t) { - Object o = new Object[] {t, t}; - } + public void getFields(T t) { + Object o = new Object[] {t, t}; + } } diff --git a/framework/tests/all-systems/Issue3929.java b/framework/tests/all-systems/Issue3929.java index 2042ab42b5e..9eec1ffb759 100644 --- a/framework/tests/all-systems/Issue3929.java +++ b/framework/tests/all-systems/Issue3929.java @@ -3,13 +3,13 @@ public class Issue3929 { - public void endElement(DefaultKeyedValues3929 arg) { - for (Object o : arg.getKeys()) {} - } + public void endElement(DefaultKeyedValues3929 arg) { + for (Object o : arg.getKeys()) {} + } } class DefaultKeyedValues3929> { - public List getKeys() { - return new ArrayList<>(); - } + public List getKeys() { + return new ArrayList<>(); + } } diff --git a/framework/tests/all-systems/Issue393.java b/framework/tests/all-systems/Issue393.java index 262d50dd867..436a033ff1a 100644 --- a/framework/tests/all-systems/Issue393.java +++ b/framework/tests/all-systems/Issue393.java @@ -3,9 +3,9 @@ abstract class TypeVarTaintCheck { - void test() { - wrap(new Object()); - } + void test() { + wrap(new Object()); + } - abstract void wrap(U u); + abstract void wrap(U u); } diff --git a/framework/tests/all-systems/Issue395.java b/framework/tests/all-systems/Issue395.java index 8368a46c8ff..9fa8bb5f3bf 100644 --- a/framework/tests/all-systems/Issue395.java +++ b/framework/tests/all-systems/Issue395.java @@ -5,7 +5,7 @@ public class Issue395 { - Object[] testMethod() { - return new Object[] {new ArrayList<>()}; - } + Object[] testMethod() { + return new Object[] {new ArrayList<>()}; + } } diff --git a/framework/tests/all-systems/Issue396.java b/framework/tests/all-systems/Issue396.java index 61dd18ea2e8..64b1f981077 100644 --- a/framework/tests/all-systems/Issue396.java +++ b/framework/tests/all-systems/Issue396.java @@ -1,11 +1,11 @@ // Test case for Issue 396: // https://github.com/typetools/checker-framework/issues/396 public class Issue396 { - void b() { - try { + void b() { + try { - } catch (LinkageError | AssertionError e) { - throw e; - } + } catch (LinkageError | AssertionError e) { + throw e; } + } } diff --git a/framework/tests/all-systems/Issue3994.java b/framework/tests/all-systems/Issue3994.java index 90396885ab7..c91c07a2640 100644 --- a/framework/tests/all-systems/Issue3994.java +++ b/framework/tests/all-systems/Issue3994.java @@ -1,7 +1,7 @@ public class Issue3994 { - interface MyInterface {} + interface MyInterface {} - interface OkRecursive {} + interface OkRecursive {} - interface Recursive {} + interface Recursive {} } diff --git a/framework/tests/all-systems/Issue4083.java b/framework/tests/all-systems/Issue4083.java index 2f346a5d3fe..d5aaa2b77b6 100644 --- a/framework/tests/all-systems/Issue4083.java +++ b/framework/tests/all-systems/Issue4083.java @@ -1,16 +1,16 @@ @SuppressWarnings("all") // Just check for crashes. abstract class Issue4083 { - abstract void use(Predicate predicate); + abstract void use(Predicate predicate); - void go() { - use(Annotation::b); - } + void go() { + use(Annotation::b); + } - @interface Annotation { - boolean b() default false; - } + @interface Annotation { + boolean b() default false; + } - interface Predicate { - boolean apply(T t); - } + interface Predicate { + boolean apply(T t); + } } diff --git a/framework/tests/all-systems/Issue4115.java b/framework/tests/all-systems/Issue4115.java index 3d2e3c11b6c..ac21d3f7780 100644 --- a/framework/tests/all-systems/Issue4115.java +++ b/framework/tests/all-systems/Issue4115.java @@ -3,13 +3,13 @@ abstract class Issue4115 { - interface V {} + interface V {} - abstract Iterable transform(Iterable i, Function f); + abstract Iterable transform(Iterable i, Function f); - abstract List copyOf(Iterable e); + abstract List copyOf(Iterable e); - List generateAppSuggestions(List xs) { - return copyOf(transform(xs, x -> new V() {})); - } + List generateAppSuggestions(List xs) { + return copyOf(transform(xs, x -> new V() {})); + } } diff --git a/framework/tests/all-systems/Issue4170.java b/framework/tests/all-systems/Issue4170.java index 3d5432c5ddc..ab787799896 100644 --- a/framework/tests/all-systems/Issue4170.java +++ b/framework/tests/all-systems/Issue4170.java @@ -1,28 +1,27 @@ // @below-java10-jdk-skip-test -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; public class Issue4170 { - public void loadSequentially(Iterable keys) { - Map result = new LinkedHashMap<>(); - for (K key : keys) { - result.put(key, null); - } - for (var iter = result.entrySet().iterator(); iter.hasNext(); ) { - var entry = iter.next(); - } + public void loadSequentially(Iterable keys) { + Map result = new LinkedHashMap<>(); + for (K key : keys) { + result.put(key, null); } - - public void method1() { - var s = new ArrayList<@Nullable String>(); - for (var str : s) {} + for (var iter = result.entrySet().iterator(); iter.hasNext(); ) { + var entry = iter.next(); } + } - public void method2() { - var s = new ArrayList<@Nullable ArrayList<@Nullable String>>(); - } + public void method1() { + var s = new ArrayList<@Nullable String>(); + for (var str : s) {} + } + + public void method2() { + var s = new ArrayList<@Nullable ArrayList<@Nullable String>>(); + } } diff --git a/framework/tests/all-systems/Issue437.java b/framework/tests/all-systems/Issue437.java index 121994e445e..f0dd4d0495e 100644 --- a/framework/tests/all-systems/Issue437.java +++ b/framework/tests/all-systems/Issue437.java @@ -2,27 +2,27 @@ // https://github.com/typetools/checker-framework/issues/437 abstract class I437Bar { - private final T t; + private final T t; - class Norf { - T getT() { - return t; - } + class Norf { + T getT() { + return t; } + } - I437Bar(T t) { - this.t = t; - } + I437Bar(T t) { + this.t = t; + } - abstract void quux(Norf norf); + abstract void quux(Norf norf); } class I437Foo extends I437Bar { - I437Foo(Integer i) { - super(i); - } + I437Foo(Integer i) { + super(i); + } - void quux(Norf norf) { - Integer i = norf.getT(); - } + void quux(Norf norf) { + Integer i = norf.getT(); + } } diff --git a/framework/tests/all-systems/Issue438.java b/framework/tests/all-systems/Issue438.java index a104adf4f4b..9f7707e09f1 100644 --- a/framework/tests/all-systems/Issue438.java +++ b/framework/tests/all-systems/Issue438.java @@ -5,15 +5,15 @@ import java.util.List; public class Issue438 { - boolean foo(List list) { - if (list.isEmpty()) { - return new HashSet<>(list).isEmpty(); - } else { - return new HashSet<>(list).contains("test"); - } + boolean foo(List list) { + if (list.isEmpty()) { + return new HashSet<>(list).isEmpty(); + } else { + return new HashSet<>(list).contains("test"); } + } - int bar(List list) { - return new HashSet<>(list).size(); - } + int bar(List list) { + return new HashSet<>(list).size(); + } } diff --git a/framework/tests/all-systems/Issue4384.java b/framework/tests/all-systems/Issue4384.java index b1cbf1f0e63..4abb436c465 100644 --- a/framework/tests/all-systems/Issue4384.java +++ b/framework/tests/all-systems/Issue4384.java @@ -1,21 +1,21 @@ public abstract class Issue4384 { - interface A> extends C, D {} + interface A> extends C, D {} - interface C {} + interface C {} - interface D> { - void f(); - } + interface D> { + void f(); + } - interface B> extends E {} + interface B> extends E {} - interface E> {} + interface E> {} - void g(A t) { - t.f(); - h(t); - } + void g(A t) { + t.f(); + h(t); + } - abstract void h(A> t); + abstract void h(A> t); } diff --git a/framework/tests/all-systems/Issue457.java b/framework/tests/all-systems/Issue457.java index fac0e68ca05..a1819160772 100644 --- a/framework/tests/all-systems/Issue457.java +++ b/framework/tests/all-systems/Issue457.java @@ -2,14 +2,14 @@ // for more information on what was going wrong here public class Issue457 { - @SuppressWarnings("unused") - public void f(T t) { - final T obj = t; + @SuppressWarnings("unused") + public void f(T t) { + final T obj = t; - @SuppressWarnings("signedness:assignment.type.incompatible") // cast - Float objFloat = (obj instanceof Float) ? (Float) obj : null; + @SuppressWarnings("signedness:assignment.type.incompatible") // cast + Float objFloat = (obj instanceof Float) ? (Float) obj : null; - // An error will be emitted on this line before the fix for Issue457 - t = obj; - } + // An error will be emitted on this line before the fix for Issue457 + t = obj; + } } diff --git a/framework/tests/all-systems/Issue478.java b/framework/tests/all-systems/Issue478.java index 162c7f9cf0a..81c7ae424bc 100644 --- a/framework/tests/all-systems/Issue478.java +++ b/framework/tests/all-systems/Issue478.java @@ -5,7 +5,7 @@ import java.util.Comparator; public class Issue478 { - public static Comparator allTheSame() { - return (Comparator & Serializable) (c1, c2) -> 0; - } + public static Comparator allTheSame() { + return (Comparator & Serializable) (c1, c2) -> 0; + } } diff --git a/framework/tests/all-systems/Issue4829.java b/framework/tests/all-systems/Issue4829.java index 6bf9e566253..00b215349c4 100644 --- a/framework/tests/all-systems/Issue4829.java +++ b/framework/tests/all-systems/Issue4829.java @@ -1,19 +1,18 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.concurrent.Future; +import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("all") // Just check for crashes. public class Issue4829 { - interface ListenableFuture extends Future {} + interface ListenableFuture extends Future {} - enum E {} + enum E {} - ListenableFuture f(E e) { - return g(e); - } + ListenableFuture f(E e) { + return g(e); + } - ListenableFuture g(E e) { - throw new AssertionError(); - } + ListenableFuture g(E e) { + throw new AssertionError(); + } } diff --git a/framework/tests/all-systems/Issue4849.java b/framework/tests/all-systems/Issue4849.java index 2a0c04c12be..972bb4dea11 100644 --- a/framework/tests/all-systems/Issue4849.java +++ b/framework/tests/all-systems/Issue4849.java @@ -4,18 +4,18 @@ final class Issue4849F {} abstract class Issue4849C extends Issue4849G> {} abstract class Issue4849G> { - abstract M e(); + abstract M e(); } @SuppressWarnings("all") // Just check for crashes. abstract class Issue4849 { - enum MyEnum {} + enum MyEnum {} - abstract Issue4849C n(Issue4849F field1); + abstract Issue4849C n(Issue4849F field1); - abstract > Issue4849F of(Class e); + abstract > Issue4849F of(Class e); - void method() { - Issue4849C c = this.n(this.of(MyEnum.class)).e(); - } + void method() { + Issue4849C c = this.n(this.of(MyEnum.class)).e(); + } } diff --git a/framework/tests/all-systems/Issue4852.java b/framework/tests/all-systems/Issue4852.java index 75449d494b6..df423761342 100644 --- a/framework/tests/all-systems/Issue4852.java +++ b/framework/tests/all-systems/Issue4852.java @@ -1,10 +1,10 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4852 { - interface Class1, B extends Class1.Class2> { - abstract class Class2, B extends Class2> {} - } + interface Class1, B extends Class1.Class2> { + abstract class Class2, B extends Class2> {} + } - class Class3 { - private void f(Class1 x) {} - } + class Class3 { + private void f(Class1 x) {} + } } diff --git a/framework/tests/all-systems/Issue4853.java b/framework/tests/all-systems/Issue4853.java index b871cf91500..7445725eeb1 100644 --- a/framework/tests/all-systems/Issue4853.java +++ b/framework/tests/all-systems/Issue4853.java @@ -1,16 +1,16 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4853 { - interface Interface {} + interface Interface {} - static class MyClass { - class InnerMyClass implements Interface {} - } - - abstract static class SubMyClass extends MyClass { - protected void f() { - method(new InnerMyClass()); - } + static class MyClass { + class InnerMyClass implements Interface {} + } - abstract void method(Interface callback); + abstract static class SubMyClass extends MyClass { + protected void f() { + method(new InnerMyClass()); } + + abstract void method(Interface callback); + } } diff --git a/framework/tests/all-systems/Issue4876.java b/framework/tests/all-systems/Issue4876.java index 0cc2488878e..dfb75a3f3f5 100644 --- a/framework/tests/all-systems/Issue4876.java +++ b/framework/tests/all-systems/Issue4876.java @@ -2,17 +2,17 @@ public class Issue4876 { - interface FFunction extends Function {} + interface FFunction extends Function {} - interface DInterface {} + interface DInterface {} - interface MInterface

          {} + interface MInterface

          {} - interface QInterface, P> {} + interface QInterface, P> {} - FFunction> r; + FFunction> r; - Issue4876(FFunction, DInterface>> r) { - this.r = r; - } + Issue4876(FFunction, DInterface>> r) { + this.r = r; + } } diff --git a/framework/tests/all-systems/Issue4877.java b/framework/tests/all-systems/Issue4877.java index 1c60f8b0dbc..1d530c2ade9 100644 --- a/framework/tests/all-systems/Issue4877.java +++ b/framework/tests/all-systems/Issue4877.java @@ -1,15 +1,15 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4877 { - interface NInterface> {} + interface NInterface> {} - interface OInterface> {} + interface OInterface> {} - interface XInterface {} + interface XInterface {} - static class QClass & XInterface> {} + static class QClass & XInterface> {} - static final class PClass> implements OInterface { - QClass f; - } + static final class PClass> implements OInterface { + QClass f; + } } diff --git a/framework/tests/all-systems/Issue4878.java b/framework/tests/all-systems/Issue4878.java index 3d062306bb0..a9dfce4df39 100644 --- a/framework/tests/all-systems/Issue4878.java +++ b/framework/tests/all-systems/Issue4878.java @@ -1,13 +1,13 @@ import java.util.List; class Issue4878 { - void f(S4878 s, Q v) { - s.p().forEach(param -> {}); - } + void f(S4878 s, Q v) { + s.p().forEach(param -> {}); + } } class A4878 {} abstract class S4878 { - abstract List> p(); + abstract List> p(); } diff --git a/framework/tests/all-systems/Issue4878Orig.java b/framework/tests/all-systems/Issue4878Orig.java index 173522bebf5..eb83315ac52 100644 --- a/framework/tests/all-systems/Issue4878Orig.java +++ b/framework/tests/all-systems/Issue4878Orig.java @@ -3,22 +3,23 @@ interface M4878Orig {} class A4878Orig { - void p(T v) {} + void p(T v) {} } abstract class S { - abstract List> p(); + abstract List> p(); } class Issue4878Orig { - @SuppressWarnings("unchecked") - void f(S s, T v) { - s.p().forEach( - field -> { - Object o = field; - A4878Orig typedField = (A4878Orig) field; - typedField.p(v); - }); - } + @SuppressWarnings("unchecked") + void f(S s, T v) { + s.p() + .forEach( + field -> { + Object o = field; + A4878Orig typedField = (A4878Orig) field; + typedField.p(v); + }); + } } diff --git a/framework/tests/all-systems/Issue4879.java b/framework/tests/all-systems/Issue4879.java index 341f158c126..4bb71281ff7 100644 --- a/framework/tests/all-systems/Issue4879.java +++ b/framework/tests/all-systems/Issue4879.java @@ -1,21 +1,21 @@ abstract class L4879 { - protected L4879(A4879 c) {} + protected L4879(A4879 c) {} } class A4879 { - static class B4879 { - public B4879() {} + static class B4879 { + public B4879() {} - A4879 build() { - throw new AssertionError(); - } + A4879 build() { + throw new AssertionError(); } + } } class Issue4879 { - private final class I4879 extends L4879 { - private I4879() { - super(new A4879.B4879<>().build()); - } + private final class I4879 extends L4879 { + private I4879() { + super(new A4879.B4879<>().build()); } + } } diff --git a/framework/tests/all-systems/Issue4890.java b/framework/tests/all-systems/Issue4890.java index 610c1f8b6a8..3f543d2f642 100644 --- a/framework/tests/all-systems/Issue4890.java +++ b/framework/tests/all-systems/Issue4890.java @@ -1,26 +1,25 @@ -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.NonNull; @SuppressWarnings("all") // Just check for crashes. public class Issue4890 { - class R

          , K> {} + class R

          , K> {} - interface PK {} + interface PK {} - interface N {} + interface N {} - interface Q, P extends @NonNull Object> extends N {} + interface Q, P extends @NonNull Object> extends N {} - interface S

          extends PhysicalPK {} + interface S

          extends PhysicalPK {} - interface PhysicalPK extends PK {} + interface PhysicalPK extends PK {} - interface I extends Function {} + interface I extends Function {} - class T { + class T { - I, ? extends Q, ?>> reader; - } + I, ? extends Q, ?>> reader; + } } diff --git a/framework/tests/all-systems/Issue4890Interfaces.java b/framework/tests/all-systems/Issue4890Interfaces.java index f2ba3001653..dcf54ab6edf 100644 --- a/framework/tests/all-systems/Issue4890Interfaces.java +++ b/framework/tests/all-systems/Issue4890Interfaces.java @@ -1,31 +1,31 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4890Interfaces { - // The capture of BigClass2> is BigClass - // where cap1 is a fresh type variable with upper bound of - // SubInterface2 & Interface2 and lower bound of nulltype. - BigClass2> r; - // The type of r.getI2() is cap1 extends SubInterface2 & Interface2 so - // both of the following assignments should be legal. - SubInterface2 s = r.getI2(); - // Interface2 s2 = r.getI2(); // javac error here, but no error with IntelliJ and - // Eclipse. + // The capture of BigClass2> is BigClass + // where cap1 is a fresh type variable with upper bound of + // SubInterface2 & Interface2 and lower bound of nulltype. + BigClass2> r; + // The type of r.getI2() is cap1 extends SubInterface2 & Interface2 so + // both of the following assignments should be legal. + SubInterface2 s = r.getI2(); + // Interface2 s2 = r.getI2(); // javac error here, but no error with IntelliJ and + // Eclipse. - BigClass2> t; - SubInterface2 s3 = t.getI2(); + BigClass2> t; + SubInterface2 s3 = t.getI2(); - // Interface2 s4 = t.getI2(); // javac error here, but no error with IntelliJ and - // Eclipse. + // Interface2 s4 = t.getI2(); // javac error here, but no error with IntelliJ and + // Eclipse. - abstract static class BigClass2> { - abstract I2 getI2(); - } + abstract static class BigClass2> { + abstract I2 getI2(); + } - interface Interface1 {} + interface Interface1 {} - interface SubInterface1 extends Interface1 {} + interface SubInterface1 extends Interface1 {} - interface Interface2 {} + interface Interface2 {} - interface SubInterface2 extends Interface2 {} + interface SubInterface2 extends Interface2 {} } diff --git a/framework/tests/all-systems/Issue4924.java b/framework/tests/all-systems/Issue4924.java index 0f25c91ffba..d6391c30dd8 100644 --- a/framework/tests/all-systems/Issue4924.java +++ b/framework/tests/all-systems/Issue4924.java @@ -1,15 +1,15 @@ public class Issue4924 { - interface Callback {} + interface Callback {} - static class Template { - class Adapter implements Callback {} - } + static class Template { + class Adapter implements Callback {} + } - static class Super extends Template {} + static class Super extends Template {} - static class Issue extends Super { - void foo() { - Callback f = new Adapter(); - } + static class Issue extends Super { + void foo() { + Callback f = new Adapter(); } + } } diff --git a/framework/tests/all-systems/Issue4996.java b/framework/tests/all-systems/Issue4996.java index 67b48ef04ee..6716c29fd86 100644 --- a/framework/tests/all-systems/Issue4996.java +++ b/framework/tests/all-systems/Issue4996.java @@ -1,21 +1,21 @@ public class Issue4996 { - abstract static class CaptureOuter { - abstract T get(); + abstract static class CaptureOuter { + abstract T get(); - abstract class Inner { - abstract T get(); - } + abstract class Inner { + abstract T get(); } + } - static class Client { + static class Client { - CharSequence getFrom(CaptureOuter o) { - return o.get(); - } + CharSequence getFrom(CaptureOuter o) { + return o.get(); + } - CharSequence getFrom(CaptureOuter.Inner i) { - return i.get(); - } + CharSequence getFrom(CaptureOuter.Inner i) { + return i.get(); } + } } diff --git a/framework/tests/all-systems/Issue5008.java b/framework/tests/all-systems/Issue5008.java index e5231616e42..535ee02634c 100644 --- a/framework/tests/all-systems/Issue5008.java +++ b/framework/tests/all-systems/Issue5008.java @@ -1,11 +1,11 @@ class Issue5008 { - Issue5008(L l) {} + Issue5008(L l) {} - static class B extends Issue5008 { - B() { - super(new L<>()); - } + static class B extends Issue5008 { + B() { + super(new L<>()); } + } - static class L {} + static class L {} } diff --git a/framework/tests/all-systems/Issue5042.java b/framework/tests/all-systems/Issue5042.java index ce6ca709c7c..e4d3a29b621 100644 --- a/framework/tests/all-systems/Issue5042.java +++ b/framework/tests/all-systems/Issue5042.java @@ -1,69 +1,68 @@ -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("initializedfields") // The fields are initialized. public class Issue5042 { - interface PromptViewModel { - boolean isPending(); + interface PromptViewModel { + boolean isPending(); - @Nullable PromptButtonViewModel getConfirmationButton(); - } + @Nullable PromptButtonViewModel getConfirmationButton(); + } - interface PromptButtonViewModel { - @Nullable ConfirmationPopupViewModel getConfirmationPopup(); - } + interface PromptButtonViewModel { + @Nullable ConfirmationPopupViewModel getConfirmationPopup(); + } - interface ConfirmationPopupViewModel { - boolean isShowingConfirmation(); - } + interface ConfirmationPopupViewModel { + boolean isShowingConfirmation(); + } - boolean f(PromptViewModel viewModel) { - PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - } + boolean f(PromptViewModel viewModel) { + PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + } - static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = - (viewModel) -> { - @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - promptLambda != null ? promptLambda.getConfirmationPopup() : null; - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = + (viewModel) -> { + @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + promptLambda != null ? promptLambda.getConfirmationPopup() : null; + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; - final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = - (viewModel) -> { - @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - prompt == null ? null : prompt.getConfirmationPopup(); - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = + (viewModel) -> { + @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + prompt == null ? null : prompt.getConfirmationPopup(); + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; - @Nullable PromptButtonViewModel promptfield; - Producer o = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield == null ? null : promptfield.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + @Nullable PromptButtonViewModel promptfield; + Producer o = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield == null ? null : promptfield.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - static @Nullable PromptButtonViewModel promptfield2; + static @Nullable PromptButtonViewModel promptfield2; - static Producer o2 = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield2 == null ? null : promptfield2.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + static Producer o2 = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield2 == null ? null : promptfield2.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - interface Producer { - Object apply(); - } + interface Producer { + Object apply(); + } - Issue5042(int i, int i2) {} + Issue5042(int i, int i2) {} - Issue5042(int i) {} + Issue5042(int i) {} - Issue5042() {} + Issue5042() {} } diff --git a/framework/tests/all-systems/Issue5436.java b/framework/tests/all-systems/Issue5436.java index 3d4b543a7b0..0bfaf251819 100644 --- a/framework/tests/all-systems/Issue5436.java +++ b/framework/tests/all-systems/Issue5436.java @@ -4,23 +4,23 @@ @SuppressWarnings("unchecked") public class Issue5436 { - static class BoundedWindow {} + static class BoundedWindow {} - static class Accessor { - public Accessor(Supplier s) {} + static class Accessor { + public Accessor(Supplier s) {} - Accessor() {} - } + Accessor() {} + } - private final Accessor stateAccessor; - private List currentWindows; + private final Accessor stateAccessor; + private List currentWindows; - Issue5436() { - currentWindows = new ArrayList<>(); - stateAccessor = new Accessor(() -> currentWindows); - } + Issue5436() { + currentWindows = new ArrayList<>(); + stateAccessor = new Accessor(() -> currentWindows); + } - private Object getCurrentKey() { - return new Object(); - } + private Object getCurrentKey() { + return new Object(); + } } diff --git a/framework/tests/all-systems/Issue577.java b/framework/tests/all-systems/Issue577.java index 552ac760a15..df27c3a9a6f 100644 --- a/framework/tests/all-systems/Issue577.java +++ b/framework/tests/all-systems/Issue577.java @@ -2,75 +2,75 @@ // Test with expected errors: checker/tests/nullness/Issue577.java // https://github.com/typetools/checker-framework/issues/577 class Banana extends Apple { - @Override - void fooIssue577Outer(int[] array) {} + @Override + void fooIssue577Outer(int[] array) {} - class InnerBanana extends InnerApple { - @Override - void foo(int[] array, long[] array2, F2 param3) {} - } + class InnerBanana extends InnerApple { + @Override + void foo(int[] array, long[] array2, F2 param3) {} + } } class Apple { - void fooIssue577Outer(T param) {} + void fooIssue577Outer(T param) {} - class InnerApple { - void foo(T param, E param2, F param3) {} - } + class InnerApple { + void foo(T param, E param2, F param3) {} + } } class Pineapple extends Apple { - @Override - void fooIssue577Outer(E array) {} + @Override + void fooIssue577Outer(E array) {} } class IntersectionAsMemberOf { - interface MyGenericInterface { - F getF(); - } + interface MyGenericInterface { + F getF(); + } - > void foo(T param) { - String s = param.getF(); - } + > void foo(T param) { + String s = param.getF(); + } } class UnionAsMemberOf { - interface MyInterface { - T getT(); - } + interface MyInterface { + T getT(); + } - class MyExceptionA extends Throwable implements Cloneable, MyInterface { - public String getT() { - return "t"; - } + class MyExceptionA extends Throwable implements Cloneable, MyInterface { + public String getT() { + return "t"; } + } - class MyExceptionB extends Throwable implements Cloneable, MyInterface { - public String getT() { - return "t"; - } + class MyExceptionB extends Throwable implements Cloneable, MyInterface { + public String getT() { + return "t"; } + } - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - @SuppressWarnings("ainfertest") // only check WPI for crashes - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - String s = ex1.getT(); - } + @SuppressWarnings("ainfertest") // only check WPI for crashes + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + String s = ex1.getT(); } + } } final class Issue577Outer { - private Inner createInner(ReferenceQueue q) { - return new Inner(q); - } + private Inner createInner(ReferenceQueue q) { + return new Inner(q); + } - private final class Inner { - private Inner(ReferenceQueue q) {} - } + private final class Inner { + private Inner(ReferenceQueue q) {} + } } class ReferenceQueue {} diff --git a/framework/tests/all-systems/Issue6019.java b/framework/tests/all-systems/Issue6019.java index 4b0d7738d21..e838650c060 100644 --- a/framework/tests/all-systems/Issue6019.java +++ b/framework/tests/all-systems/Issue6019.java @@ -1,6 +1,6 @@ // @below-java10-jdk-skip-test public class Issue6019 { - void test() { - var magic = new byte[2]; - } + void test() { + var magic = new byte[2]; + } } diff --git a/framework/tests/all-systems/Issue6025.java b/framework/tests/all-systems/Issue6025.java index 18cb1a3c1b6..bbb9777fa67 100644 --- a/framework/tests/all-systems/Issue6025.java +++ b/framework/tests/all-systems/Issue6025.java @@ -1,16 +1,16 @@ public class Issue6025 { - public interface A {} + public interface A {} - private static final class B {} + private static final class B {} - public interface C, T2 extends A> {} + public interface C, T2 extends A> {} - private final B, ? extends A>> one = new B<>(); - private final B, ? extends A>> two = new B<>(); + private final B, ? extends A>> one = new B<>(); + private final B, ? extends A>> two = new B<>(); - void f(boolean b) { - B> three1 = one; - B> three = b ? two : one; - } + void f(boolean b) { + B> three1 = one; + B> three = b ? two : one; + } } diff --git a/framework/tests/all-systems/Issue6076.java b/framework/tests/all-systems/Issue6076.java index b0040874eb3..6a95b2b72c0 100644 --- a/framework/tests/all-systems/Issue6076.java +++ b/framework/tests/all-systems/Issue6076.java @@ -5,64 +5,62 @@ public class Issue6076 { - public SSTableReaderLoadingBuilder loadingBuilder( - Descriptor descriptor, TableMetadataRef tableMetadataRef, Set components) { - return new BtiTableReaderLoadingBuilder( - new SSTable.Builder<>(descriptor) - .setTableMetadataRef(tableMetadataRef) - .setComponents(components)); + public SSTableReaderLoadingBuilder loadingBuilder( + Descriptor descriptor, TableMetadataRef tableMetadataRef, Set components) { + return new BtiTableReaderLoadingBuilder( + new SSTable.Builder<>(descriptor) + .setTableMetadataRef(tableMetadataRef) + .setComponents(components)); + } + + public static class Component {} + + public static class Descriptor {} + + public static final class TableMetadataRef {} + + public static class BtiTableReaderLoadingBuilder + extends SortedTableReaderLoadingBuilder { + public BtiTableReaderLoadingBuilder(SSTable.Builder builder) {} + } + + public static class BtiTableReader extends SSTableReaderWithFilter { + public static class Builder extends SSTableReaderWithFilter.Builder {} + } + + public abstract static class SSTableReaderWithFilter extends SSTableReader { + public abstract static class Builder> + extends SSTableReader.Builder {} + } + + @SuppressWarnings("all") // Just check for crashes. + public abstract static class SSTable { + public static class Builder> { + public Builder() {} + + public Builder(Descriptor descriptor) {} + + @SuppressWarnings("unchecked") + public B setTableMetadataRef(TableMetadataRef ref) { + return (B) this; + } + + @SuppressWarnings("unchecked") + public B setComponents(Collection components) { + return (B) this; + } } + } - public static class Component {} + public abstract static class SSTableReaderLoadingBuilder< + R extends SSTableReader, B extends SSTableReader.Builder> {} - public static class Descriptor {} + public abstract static class SortedTableReaderLoadingBuilder< + R extends SSTableReader, B extends SSTableReader.Builder> + extends SSTableReaderLoadingBuilder {} - public static final class TableMetadataRef {} - - public static class BtiTableReaderLoadingBuilder - extends SortedTableReaderLoadingBuilder { - public BtiTableReaderLoadingBuilder(SSTable.Builder builder) {} - } - - public static class BtiTableReader extends SSTableReaderWithFilter { - public static class Builder - extends SSTableReaderWithFilter.Builder {} - } - - public abstract static class SSTableReaderWithFilter extends SSTableReader { - public abstract static class Builder< - R extends SSTableReaderWithFilter, B extends Builder> - extends SSTableReader.Builder {} - } - - @SuppressWarnings("all") // Just check for crashes. - public abstract static class SSTable { - public static class Builder> { - public Builder() {} - - public Builder(Descriptor descriptor) {} - - @SuppressWarnings("unchecked") - public B setTableMetadataRef(TableMetadataRef ref) { - return (B) this; - } - - @SuppressWarnings("unchecked") - public B setComponents(Collection components) { - return (B) this; - } - } - } - - public abstract static class SSTableReaderLoadingBuilder< - R extends SSTableReader, B extends SSTableReader.Builder> {} - - public abstract static class SortedTableReaderLoadingBuilder< - R extends SSTableReader, B extends SSTableReader.Builder> - extends SSTableReaderLoadingBuilder {} - - public abstract static class SSTableReader extends SSTable { - public abstract static class Builder> - extends SSTable.Builder {} - } + public abstract static class SSTableReader extends SSTable { + public abstract static class Builder> + extends SSTable.Builder {} + } } diff --git a/framework/tests/all-systems/Issue6078.java b/framework/tests/all-systems/Issue6078.java index 6f9df77907d..6e0d2c27b66 100644 --- a/framework/tests/all-systems/Issue6078.java +++ b/framework/tests/all-systems/Issue6078.java @@ -1,14 +1,14 @@ import java.lang.invoke.MethodHandle; public class Issue6078 { - static void call(MethodHandle methodHandle) throws Throwable { - methodHandle.invoke(); - } + static void call(MethodHandle methodHandle) throws Throwable { + methodHandle.invoke(); + } - void use() { - foo(); - } + void use() { + foo(); + } - @SafeVarargs - private final void foo(T... ts) {} + @SafeVarargs + private final void foo(T... ts) {} } diff --git a/framework/tests/all-systems/Issue6098.java b/framework/tests/all-systems/Issue6098.java index 84d2f5661f8..9f64ad149ba 100644 --- a/framework/tests/all-systems/Issue6098.java +++ b/framework/tests/all-systems/Issue6098.java @@ -1,11 +1,11 @@ // @below-java10-jdk-skip-test public class Issue6098 { - T method(T t) { - return t; - } + T method(T t) { + return t; + } - void use() { - var c = method(""); - } + void use() { + var c = method(""); + } } diff --git a/framework/tests/all-systems/Issue6104.java b/framework/tests/all-systems/Issue6104.java index 9b3c572e1cc..8f5dd26ec71 100644 --- a/framework/tests/all-systems/Issue6104.java +++ b/framework/tests/all-systems/Issue6104.java @@ -1,9 +1,9 @@ public class Issue6104 { - public void m() { - try { - } catch (Exception e) { - // Because the try block is empty, this lambda is unreachable. - Runnable r = () -> {}; - } + public void m() { + try { + } catch (Exception e) { + // Because the try block is empty, this lambda is unreachable. + Runnable r = () -> {}; } + } } diff --git a/framework/tests/all-systems/Issue6259.java b/framework/tests/all-systems/Issue6259.java index 1603fede829..7e53c2fe9c0 100644 --- a/framework/tests/all-systems/Issue6259.java +++ b/framework/tests/all-systems/Issue6259.java @@ -2,40 +2,36 @@ @SuppressWarnings("unchecked") public class Issue6259 { - public interface A { - interface Builder { - AT build(); - } + public interface A { + interface Builder { + AT build(); } + } - public abstract static class B implements C { - public abstract static class Builder implements C.Builder {} - } + public abstract static class B implements C { + public abstract static class Builder implements C.Builder {} + } - public interface I> {} + public interface I> {} - public interface C { - interface Builder {} - } + public interface C { + interface Builder {} + } - public static final class L implements I> {} + public static final class L implements I> {} - static class S> { + static class S> { - public static < - ET extends A, - E2 extends A.Builder, - CT, - IT extends C, - I2 extends C.Builder> - S assemble(I... xs) { - throw new AssertionError(); - } + public static < + ET extends A, E2 extends A.Builder, CT, IT extends C, I2 extends C.Builder> + S assemble(I... xs) { + throw new AssertionError(); } + } - void f(L f) { - S> storeStack = S.assemble(f); - var storeStackVar = S.assemble(f); - var storeStackVarExplicit = S., String, B, B.Builder>assemble(f); - } + void f(L f) { + S> storeStack = S.assemble(f); + var storeStackVar = S.assemble(f); + var storeStackVarExplicit = S., String, B, B.Builder>assemble(f); + } } diff --git a/framework/tests/all-systems/Issue6282.java b/framework/tests/all-systems/Issue6282.java index 8878bd29fe2..96ebb03671a 100644 --- a/framework/tests/all-systems/Issue6282.java +++ b/framework/tests/all-systems/Issue6282.java @@ -3,18 +3,18 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class Issue6282 { - public static final MethodHandle setAccessible0_Method = setAccessible0_Method(); + public static final MethodHandle setAccessible0_Method = setAccessible0_Method(); - public static final MethodHandle setAccessible0_Method() { - throw new RuntimeException(); - } + public static final MethodHandle setAccessible0_Method() { + throw new RuntimeException(); + } - public static void setAccessible(final AccessibleObject accessibleObject) { - try { - boolean newFlag = (boolean) setAccessible0_Method.invokeExact(accessibleObject, true); - assert newFlag; - } catch (Throwable throwable) { - throw new AssertionError(throwable); - } + public static void setAccessible(final AccessibleObject accessibleObject) { + try { + boolean newFlag = (boolean) setAccessible0_Method.invokeExact(accessibleObject, true); + assert newFlag; + } catch (Throwable throwable) { + throw new AssertionError(throwable); } + } } diff --git a/framework/tests/all-systems/Issue6319.java b/framework/tests/all-systems/Issue6319.java index f2dacb34dbe..eeb4f362b7f 100644 --- a/framework/tests/all-systems/Issue6319.java +++ b/framework/tests/all-systems/Issue6319.java @@ -3,10 +3,8 @@ import java.util.stream.Collectors; class Issue6319 { - void f(List> list) { - for (Enum value : - list.stream() - .sorted(Comparator.comparing(Enum::name)) - .collect(Collectors.toList())) {} - } + void f(List> list) { + for (Enum value : + list.stream().sorted(Comparator.comparing(Enum::name)).collect(Collectors.toList())) {} + } } diff --git a/framework/tests/all-systems/Issue6433.java b/framework/tests/all-systems/Issue6433.java index df7f0be9757..2ee8d30f6a4 100644 --- a/framework/tests/all-systems/Issue6433.java +++ b/framework/tests/all-systems/Issue6433.java @@ -1,17 +1,17 @@ // Test case for Issue 6433: // https://github.com/typetools/checker-framework/issues/6433 class Issue6433 { - void foo() {} + void foo() {} - void bar() { - foo(); - while (true) {} - } + void bar() { + foo(); + while (true) {} + } - void baz() { - foo(); - while (true) { - break; - } + void baz() { + foo(); + while (true) { + break; } + } } diff --git a/framework/tests/all-systems/Issue6438.java b/framework/tests/all-systems/Issue6438.java index 00541197cd7..8950ff9eb00 100644 --- a/framework/tests/all-systems/Issue6438.java +++ b/framework/tests/all-systems/Issue6438.java @@ -4,13 +4,13 @@ import java.util.Optional; abstract class Issue6438 { - Optional a(boolean b, Class clazz) { - return b ? Optional.empty() : Optional.of(b(clazz)); - } + Optional a(boolean b, Class clazz) { + return b ? Optional.empty() : Optional.of(b(clazz)); + } - abstract T b(Class clazz); + abstract T b(Class clazz); - interface First {} + interface First {} - interface Second {} + interface Second {} } diff --git a/framework/tests/all-systems/Issue671.java b/framework/tests/all-systems/Issue671.java index 76f3e87967b..2308aafb149 100644 --- a/framework/tests/all-systems/Issue671.java +++ b/framework/tests/all-systems/Issue671.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/671 public class Issue671 { - void foo() { - byte var = 0; - boolean f = (var == (method() ? 2 : 0)); - } + void foo() { + byte var = 0; + boolean f = (var == (method() ? 2 : 0)); + } - boolean method() { - return false; - } + boolean method() { + return false; + } } diff --git a/framework/tests/all-systems/Issue689.java b/framework/tests/all-systems/Issue689.java index eeeb9f44273..cb713b1c8dd 100644 --- a/framework/tests/all-systems/Issue689.java +++ b/framework/tests/all-systems/Issue689.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/689 public class Issue689 { - public int initializerFodder() { - return 3; - } + public int initializerFodder() { + return 3; + } - public Runnable trigger() { - return new Runnable() { - // Issue 689 triggers when examining a method invocation inside a field initializer of - // an anonymous inner class - public final int val = initializerFodder(); + public Runnable trigger() { + return new Runnable() { + // Issue 689 triggers when examining a method invocation inside a field initializer of + // an anonymous inner class + public final int val = initializerFodder(); - public void run() { - // do nothing - } - }; - } + public void run() { + // do nothing + } + }; + } } diff --git a/framework/tests/all-systems/Issue691.java b/framework/tests/all-systems/Issue691.java index de789193412..28a31431ee6 100644 --- a/framework/tests/all-systems/Issue691.java +++ b/framework/tests/all-systems/Issue691.java @@ -8,5 +8,5 @@ interface MyInterface {} // issue a valid type checking error for this code, so suppress any warnings. @SuppressWarnings("all") public class Issue691 implements MyInterface { - MyInterface mi = new Issue691<>(); + MyInterface mi = new Issue691<>(); } diff --git a/framework/tests/all-systems/Issue692.java b/framework/tests/all-systems/Issue692.java index 906242f73c8..0d5a2b106df 100644 --- a/framework/tests/all-systems/Issue692.java +++ b/framework/tests/all-systems/Issue692.java @@ -2,8 +2,8 @@ // https://github.com/typetools/checker-framework/issues/692 public class Issue692> { - private boolean method(Object param, Class tClass) { - Class paramClass = param.getClass(); - return paramClass == tClass || paramClass.getSuperclass() == tClass; - } + private boolean method(Object param, Class tClass) { + Class paramClass = param.getClass(); + return paramClass == tClass || paramClass.getSuperclass() == tClass; + } } diff --git a/framework/tests/all-systems/Issue696.java b/framework/tests/all-systems/Issue696.java index 8f6113eb72f..a9529f5d4a9 100644 --- a/framework/tests/all-systems/Issue696.java +++ b/framework/tests/all-systems/Issue696.java @@ -5,5 +5,5 @@ import java.util.Map; public class Issue696 { - public static void test(final List> input) {} + public static void test(final List> input) {} } diff --git a/framework/tests/all-systems/Issue703.java b/framework/tests/all-systems/Issue703.java index 19b4bf472f2..36b6d4a7688 100644 --- a/framework/tests/all-systems/Issue703.java +++ b/framework/tests/all-systems/Issue703.java @@ -1,18 +1,17 @@ // Test case for Issue 703: // https://github.com/eisop/checker-framework/issues/703 -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; abstract class Issue703 { - void foo() { - List attrs = or(x(), y()); - } + void foo() { + List attrs = or(x(), y()); + } - abstract @NonNull T or(T nullableValue, T defaultValue); + abstract @NonNull T or(T nullableValue, T defaultValue); - abstract List x(); + abstract List x(); - abstract List y(); + abstract List y(); } diff --git a/framework/tests/all-systems/Issue717.java b/framework/tests/all-systems/Issue717.java index c58a1e8de64..fdb7f6dc20b 100644 --- a/framework/tests/all-systems/Issue717.java +++ b/framework/tests/all-systems/Issue717.java @@ -2,17 +2,17 @@ // https://github.com/typetools/checker-framework/issues/717 public class Issue717 { - public static > void foo2(T a, T b) { - a.compareTo(b); - } + public static > void foo2(T a, T b) { + a.compareTo(b); + } - public static > void foo(T a, T b) { - // asSuper doesn't find Interface, so the type variable F is not substituted - // causing isSuptype to be called between Object & Interface and F. - a.compareTo(b); - } + public static > void foo(T a, T b) { + // asSuper doesn't find Interface, so the type variable F is not substituted + // causing isSuptype to be called between Object & Interface and F. + a.compareTo(b); + } - interface Interface { - void compareTo(F t); - } + interface Interface { + void compareTo(F t); + } } diff --git a/framework/tests/all-systems/Issue738.java b/framework/tests/all-systems/Issue738.java index d820d1366d3..499a68a3f15 100644 --- a/framework/tests/all-systems/Issue738.java +++ b/framework/tests/all-systems/Issue738.java @@ -3,12 +3,12 @@ // Also, see checker/tests/nullness/Issue738.java @SuppressWarnings("all") // This testcase is checking for crashes. public class Issue738 { - public static void methodA() { - methodB(0, new Object()); // This compiles fine. - methodB(new int[0], new Object[0]); // This crashes. - } + public static void methodA() { + methodB(0, new Object()); // This compiles fine. + methodB(new int[0], new Object[0]); // This crashes. + } - private static void methodB(T paramA, T paramB) { - // Do nothing. - } + private static void methodB(T paramA, T paramB) { + // Do nothing. + } } diff --git a/framework/tests/all-systems/Issue759.java b/framework/tests/all-systems/Issue759.java index df3eaec101e..b6d23961e5b 100644 --- a/framework/tests/all-systems/Issue759.java +++ b/framework/tests/all-systems/Issue759.java @@ -1,38 +1,38 @@ // Testcase for Issue759 // https://github.com/typetools/checker-framework/issues/759 @SuppressWarnings({ - "nullness", - "unchecked", - "ainfertest", - "value" + "nullness", + "unchecked", + "ainfertest", + "value" }) // See checker/test/nullness/Issue759.java; ainfertest and value are suppressed because WPI // errors shouldn't be issued here, just checked for crashes public class Issue759 { - void possibleValues(final Class enumType) { - lowercase(enumType.getEnumConstants()); - lowercase2(enumType.getEnumConstants()); - lowercase3(enumType.getEnumConstants()); - } + void possibleValues(final Class enumType) { + lowercase(enumType.getEnumConstants()); + lowercase2(enumType.getEnumConstants()); + lowercase3(enumType.getEnumConstants()); + } - > void lowercase(final T... items) {} + > void lowercase(final T... items) {} - > void lowercase2(final T[] items) {} + > void lowercase2(final T[] items) {} - void lowercase3(final T items) {} + void lowercase3(final T items) {} } @SuppressWarnings("nullness") class Gen> { - T[] getConstants() { - return null; - } + T[] getConstants() { + return null; + } } @SuppressWarnings("nullness") class IncompatibleTypes { - void possibleValues(final Gen genType) { - lowercase(genType.getConstants()); - } + void possibleValues(final Gen genType) { + lowercase(genType.getConstants()); + } - void lowercase(final S items) {} + void lowercase(final S items) {} } diff --git a/framework/tests/all-systems/Issue807.java b/framework/tests/all-systems/Issue807.java index 8b6b487e905..376e3031b9a 100644 --- a/framework/tests/all-systems/Issue807.java +++ b/framework/tests/all-systems/Issue807.java @@ -5,16 +5,16 @@ public class Issue807 { - class MyEntry { - MyEntry(MyEntry e) {} - } + class MyEntry { + MyEntry(MyEntry e) {} + } - Consumer> entryConsumer(Consumer> action) { - // The "new MyEntry" isn't a subtype of "? super MyEntry" in - // most type systems. Suppress that error, as it's not the - // point of this test. - @SuppressWarnings("all") - Consumer> res = e -> action.accept(new MyEntry<>(e)); - return res; - } + Consumer> entryConsumer(Consumer> action) { + // The "new MyEntry" isn't a subtype of "? super MyEntry" in + // most type systems. Suppress that error, as it's not the + // point of this test. + @SuppressWarnings("all") + Consumer> res = e -> action.accept(new MyEntry<>(e)); + return res; + } } diff --git a/framework/tests/all-systems/Issue808.java b/framework/tests/all-systems/Issue808.java index 187399f8d4b..87142f723b0 100644 --- a/framework/tests/all-systems/Issue808.java +++ b/framework/tests/all-systems/Issue808.java @@ -5,19 +5,19 @@ import java.util.List; public class Issue808 { - void f() { - Arrays.asList(0, 0, "", Arrays.asList(new Object[0])); - foo(new Object(), bar()); - new Issue808(bar()); - foo(bar()); - List list = Arrays.asList(new Object[0]); - } + void f() { + Arrays.asList(0, 0, "", Arrays.asList(new Object[0])); + foo(new Object(), bar()); + new Issue808(bar()); + foo(bar()); + List list = Arrays.asList(new Object[0]); + } - T bar() { - throw new RuntimeException(); - } + T bar() { + throw new RuntimeException(); + } - void foo(Object... param) {} + void foo(Object... param) {} - Issue808(Object... param) {} + Issue808(Object... param) {} } diff --git a/framework/tests/all-systems/Issue810.java b/framework/tests/all-systems/Issue810.java index 561b2879b61..a0d2a3ecf1a 100644 --- a/framework/tests/all-systems/Issue810.java +++ b/framework/tests/all-systems/Issue810.java @@ -6,6 +6,6 @@ import java.util.Set; public class Issue810 { - Map m = new HashMap<>(); - Set n = m.keySet(); + Map m = new HashMap<>(); + Set n = m.keySet(); } diff --git a/framework/tests/all-systems/Issue887.java b/framework/tests/all-systems/Issue887.java index 4a567ad2cc0..1a66012919c 100644 --- a/framework/tests/all-systems/Issue887.java +++ b/framework/tests/all-systems/Issue887.java @@ -5,12 +5,12 @@ import java.util.List; public abstract class Issue887 { - @SuppressWarnings("nullness") // See checker/tests/nullness/Issue887.java - void test() { - method(foo(null).get(0)); - } + @SuppressWarnings("nullness") // See checker/tests/nullness/Issue887.java + void test() { + method(foo(null).get(0)); + } - void method(Number o) {} + void method(Number o) {} - abstract List foo(T t); + abstract List foo(T t); } diff --git a/framework/tests/all-systems/Issue888.java b/framework/tests/all-systems/Issue888.java index 216359ea732..c3a8dbbaa4d 100644 --- a/framework/tests/all-systems/Issue888.java +++ b/framework/tests/all-systems/Issue888.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/888 public class Issue888 { - T foo(T t) { - return t; - } + T foo(T t) { + return t; + } - void bar(int i) { - foo(i).toString(); - } + void bar(int i) { + foo(i).toString(); + } } diff --git a/framework/tests/all-systems/Issue913.java b/framework/tests/all-systems/Issue913.java index 165f4966b3b..4f5c441ad8a 100644 --- a/framework/tests/all-systems/Issue913.java +++ b/framework/tests/all-systems/Issue913.java @@ -2,15 +2,15 @@ // https://github.com/typetools/checker-framework/issues/913 public class Issue913 { - void test(Ordering o) { - Multimap newMap = create(o); - } + void test(Ordering o) { + Multimap newMap = create(o); + } - static Multimap create(Ordering valueComparator) { - throw new RuntimeException(); - } + static Multimap create(Ordering valueComparator) { + throw new RuntimeException(); + } - interface Multimap {} + interface Multimap {} - class Ordering {} + class Ordering {} } diff --git a/framework/tests/all-systems/Issue953.java b/framework/tests/all-systems/Issue953.java index 1aebe8ec45b..106e2570b50 100644 --- a/framework/tests/all-systems/Issue953.java +++ b/framework/tests/all-systems/Issue953.java @@ -4,21 +4,21 @@ import java.util.List; public class Issue953 { - class MyCollector {} + class MyCollector {} - class MyStream { - F collect(MyCollector param) { - throw new RuntimeException(); - } + class MyStream { + F collect(MyCollector param) { + throw new RuntimeException(); } + } - public static void test(MyStream y) { - // Type argument inference fails, so a checker may report a type checking error. - @SuppressWarnings("all") - List counts = y.collect(toList()); - } + public static void test(MyStream y) { + // Type argument inference fails, so a checker may report a type checking error. + @SuppressWarnings("all") + List counts = y.collect(toList()); + } - static MyCollector> toList() { - throw new RuntimeException(); - } + static MyCollector> toList() { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/Issue953b.java b/framework/tests/all-systems/Issue953b.java index 3b411dc0b4b..64a570b45d3 100644 --- a/framework/tests/all-systems/Issue953b.java +++ b/framework/tests/all-systems/Issue953b.java @@ -5,21 +5,21 @@ import java.util.List; public class Issue953b { - class MyCollector {} + class MyCollector {} - class MyStream { - F collect(MyCollector param) { - throw new RuntimeException(); - } + class MyStream { + F collect(MyCollector param) { + throw new RuntimeException(); } + } - public static void test(MyStream y) { - // Type argument inference fails, so a checker may report a type checking error. - @SuppressWarnings("all") - List counts = y.collect(toList()); - } + public static void test(MyStream y) { + // Type argument inference fails, so a checker may report a type checking error. + @SuppressWarnings("all") + List counts = y.collect(toList()); + } - static MyCollector> toList() { - throw new RuntimeException(); - } + static MyCollector> toList() { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/Issue988.java b/framework/tests/all-systems/Issue988.java index 193dc92f5ea..b9388524a2c 100644 --- a/framework/tests/all-systems/Issue988.java +++ b/framework/tests/all-systems/Issue988.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/988 abstract class Issue988 { - abstract Class getRawClass(); + abstract Class getRawClass(); - abstract Class getGenericClass(); + abstract Class getGenericClass(); - Class getWithArg(boolean generic) { - return generic ? getGenericClass() : getRawClass(); - } + Class getWithArg(boolean generic) { + return generic ? getGenericClass() : getRawClass(); + } } diff --git a/framework/tests/all-systems/LightWeightCache.java b/framework/tests/all-systems/LightWeightCache.java index d42d9b54e65..f8b66c2ad2a 100644 --- a/framework/tests/all-systems/LightWeightCache.java +++ b/framework/tests/all-systems/LightWeightCache.java @@ -6,58 +6,58 @@ @SuppressWarnings("allcheckers") // Only check for crashes class LightWeightCache extends LightWeightGSet { - @Override - public Iterator iterator() { - final Iterator iter = super.iterator(); - return new Iterator() { - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public E next() { - return iter.next(); - } - - @Override - public void remove() {} - }; - } + @Override + public Iterator iterator() { + final Iterator iter = super.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public E next() { + return iter.next(); + } + + @Override + public void remove() {} + }; + } } @SuppressWarnings("allcheckers") // Only check for crashes class LightWeightGSet implements GSet { - interface LinkedElement {} + interface LinkedElement {} + + protected LinkedElement[] entries; + + @Override + public Iterator iterator() { + return new SetIterator(); + } - protected LinkedElement[] entries; + private class SetIterator implements Iterator { + private int index = -1; + private LinkedElement next = nextNonemptyEntry(); + + private LinkedElement nextNonemptyEntry() { + for (index++; index < entries.length && entries[index] == null; index++) + ; + return index < entries.length ? entries[index] : null; + } @Override - public Iterator iterator() { - return new SetIterator(); + public E next() { + return null; } - private class SetIterator implements Iterator { - private int index = -1; - private LinkedElement next = nextNonemptyEntry(); - - private LinkedElement nextNonemptyEntry() { - for (index++; index < entries.length && entries[index] == null; index++) - ; - return index < entries.length ? entries[index] : null; - } - - @Override - public E next() { - return null; - } - - @Override - public boolean hasNext() { - return false; - } + @Override + public boolean hasNext() { + return false; } + } } interface GSet extends Iterable {} diff --git a/framework/tests/all-systems/LongDouble.java b/framework/tests/all-systems/LongDouble.java index fb154df5b65..95f85d4fb5e 100644 --- a/framework/tests/all-systems/LongDouble.java +++ b/framework/tests/all-systems/LongDouble.java @@ -2,26 +2,26 @@ public abstract class LongDouble { - public abstract T getMaxValue(); + public abstract T getMaxValue(); - public class LongLongDouble extends LongDouble { - @java.lang.Override - public Long getMaxValue() { - return Long.MAX_VALUE; - } + public class LongLongDouble extends LongDouble { + @java.lang.Override + public Long getMaxValue() { + return Long.MAX_VALUE; } + } - public class DoubleLongDouble extends LongDouble { - @java.lang.Override - public Double getMaxValue() { - return Double.MAX_VALUE; - } + public class DoubleLongDouble extends LongDouble { + @java.lang.Override + public Double getMaxValue() { + return Double.MAX_VALUE; } + } - public class FloatLongDouble extends LongDouble { - @java.lang.Override - public Float getMaxValue() { - return Float.MAX_VALUE; - } + public class FloatLongDouble extends LongDouble { + @java.lang.Override + public Float getMaxValue() { + return Float.MAX_VALUE; } + } } diff --git a/framework/tests/all-systems/LubRawTypes.java b/framework/tests/all-systems/LubRawTypes.java index ec7d0cc0ad8..d7483160a4c 100644 --- a/framework/tests/all-systems/LubRawTypes.java +++ b/framework/tests/all-systems/LubRawTypes.java @@ -2,19 +2,19 @@ @SuppressWarnings("unchecked") public class LubRawTypes { - public static boolean flag = false; + public static boolean flag = false; - class MyGen {} + class MyGen {} - MyGen>> test(MyGen myGen1, MyGen myGen2) { - return flag ? myGen1 : myGen2; - } + MyGen>> test(MyGen myGen1, MyGen myGen2) { + return flag ? myGen1 : myGen2; + } - MyGen>> test2(MyGen myGen1, MyGen>> myGen2) { - return flag ? myGen1 : myGen2; - } + MyGen>> test2(MyGen myGen1, MyGen>> myGen2) { + return flag ? myGen1 : myGen2; + } - MyGen>> test3(MyGen myGen1, MyGen>> myGen2) { - return flag ? myGen2 : myGen1; - } + MyGen>> test3(MyGen myGen1, MyGen>> myGen2) { + return flag ? myGen2 : myGen1; + } } diff --git a/framework/tests/all-systems/MapCrashTest.java b/framework/tests/all-systems/MapCrashTest.java index 05f8277c333..cb08d513367 100644 --- a/framework/tests/all-systems/MapCrashTest.java +++ b/framework/tests/all-systems/MapCrashTest.java @@ -2,13 +2,13 @@ @SuppressWarnings("all") // Just check for crashes public class MapCrashTest { - public static class ImmutableSetMultimap { - public ImmutableSetMultimap(MapCrashTest> map) {} - } + public static class ImmutableSetMultimap { + public ImmutableSetMultimap(MapCrashTest> map) {} + } - private final class MapSet extends MapCrashTest> {} + private final class MapSet extends MapCrashTest> {} - public void asMultimap() { - new ImmutableSetMultimap(new MapSet()); - } + public void asMultimap() { + new ImmutableSetMultimap(new MapSet()); + } } diff --git a/framework/tests/all-systems/MethodTypeVars.java b/framework/tests/all-systems/MethodTypeVars.java index ad9b593c44f..4749f8feb4a 100644 --- a/framework/tests/all-systems/MethodTypeVars.java +++ b/framework/tests/all-systems/MethodTypeVars.java @@ -3,19 +3,19 @@ import java.util.Map; public class MethodTypeVars { - private void addToBindingList(Map> map, T key, String value) {} + private void addToBindingList(Map> map, T key, String value) {} - void call1() { - LinkedHashMap> multiMap = new LinkedHashMap<>(); - String s = "s"; - Object o = new Object(); - addToBindingList(multiMap, o, s); - } + void call1() { + LinkedHashMap> multiMap = new LinkedHashMap<>(); + String s = "s"; + Object o = new Object(); + addToBindingList(multiMap, o, s); + } - void call2() { - LinkedHashMap> multiMap = new LinkedHashMap<>(); - String s = "s"; - Integer n = 5; - addToBindingList(multiMap, n, s); - } + void call2() { + LinkedHashMap> multiMap = new LinkedHashMap<>(); + String s = "s"; + Integer n = 5; + addToBindingList(multiMap, n, s); + } } diff --git a/framework/tests/all-systems/MissingBoundAnnotations.java b/framework/tests/all-systems/MissingBoundAnnotations.java index d0f90f649ea..b0c85b75c6a 100644 --- a/framework/tests/all-systems/MissingBoundAnnotations.java +++ b/framework/tests/all-systems/MissingBoundAnnotations.java @@ -4,10 +4,10 @@ import java.util.Map; public final class MissingBoundAnnotations { - @SuppressWarnings("nullness:type.argument.type.incompatible") - public static , V> Collection sortedKeySet(Map m) { - ArrayList theKeys = new ArrayList<>(m.keySet()); - Collections.sort(theKeys); - return theKeys; - } + @SuppressWarnings("nullness:type.argument.type.incompatible") + public static , V> Collection sortedKeySet(Map m) { + ArrayList theKeys = new ArrayList<>(m.keySet()); + Collections.sort(theKeys); + return theKeys; + } } diff --git a/framework/tests/all-systems/MultipleUnions.java b/framework/tests/all-systems/MultipleUnions.java index 3de444b1f17..aafe839d92f 100644 --- a/framework/tests/all-systems/MultipleUnions.java +++ b/framework/tests/all-systems/MultipleUnions.java @@ -1,39 +1,39 @@ public class MultipleUnions { - public static boolean flag = false; - - @SuppressWarnings("ainfertest") // only check WPI for crashes - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - try { - bar(); - } catch (SubMyExceptionA | MyExceptionB ex2) { - - Throwable t = flag ? ex1 : ex2; - typeVar(ex1, ex1); - typeVar(ex2, ex2); - // See UnionCrash for version that crashes - // typeVar(ex1, ex2); - } - } + public static boolean flag = false; + + @SuppressWarnings("ainfertest") // only check WPI for crashes + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + try { + bar(); + } catch (SubMyExceptionA | MyExceptionB ex2) { + + Throwable t = flag ? ex1 : ex2; + typeVar(ex1, ex1); + typeVar(ex2, ex2); + // See UnionCrash for version that crashes + // typeVar(ex1, ex2); + } } + } - > void typeVarIntersection(T param) {} + > void typeVarIntersection(T param) {} - void typeVar(T param, T param2) {} + void typeVar(T param, T param2) {} - void typeVarWildcard(T param, MyInterface myInterface) {} + void typeVarWildcard(T param, MyInterface myInterface) {} - void typeVarWildcard2(T param, MyInterface myInterface) {} + void typeVarWildcard2(T param, MyInterface myInterface) {} - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - interface MyInterface {} + interface MyInterface {} - class MyExceptionA extends Throwable implements Cloneable, MyInterface {} + class MyExceptionA extends Throwable implements Cloneable, MyInterface {} - class MyExceptionB extends Throwable implements Cloneable, MyInterface {} + class MyExceptionB extends Throwable implements Cloneable, MyInterface {} - class SubMyExceptionA extends MyExceptionA {} + class SubMyExceptionA extends MyExceptionA {} } diff --git a/framework/tests/all-systems/Options.java b/framework/tests/all-systems/Options.java index a9af59dc96a..c203e94d31f 100644 --- a/framework/tests/all-systems/Options.java +++ b/framework/tests/all-systems/Options.java @@ -1,8 +1,8 @@ public class Options { - private Class main_class; + private Class main_class; - public Options() { - throw new Error("" + main_class); - } + public Options() { + throw new Error("" + main_class); + } } diff --git a/framework/tests/all-systems/PQueue.java b/framework/tests/all-systems/PQueue.java index 371f642bd7b..81951b0c275 100644 --- a/framework/tests/all-systems/PQueue.java +++ b/framework/tests/all-systems/PQueue.java @@ -1,11 +1,11 @@ public class PQueue

          { - public static PQueue create(Iterable p, Builder b) { - return b.create(p); - } + public static PQueue create(Iterable p, Builder b) { + return b.create(p); + } - public static final class Builder { - public PQueue create(Iterable p) { - throw new RuntimeException(); - } + public static final class Builder { + public PQueue create(Iterable p) { + throw new RuntimeException(); } + } } diff --git a/framework/tests/all-systems/PolyCollectorTypeVars.java b/framework/tests/all-systems/PolyCollectorTypeVars.java index f544232a267..4804af54399 100644 --- a/framework/tests/all-systems/PolyCollectorTypeVars.java +++ b/framework/tests/all-systems/PolyCollectorTypeVars.java @@ -3,27 +3,27 @@ class MyGen {} abstract class Ordering implements Comparator { - // Natural order + // Natural order - public static Ordering natural() { - return new Ordering() { - @Override - public int compare(C o1, C o2) { - return 0; - } - }; - } + public static Ordering natural() { + return new Ordering() { + @Override + public int compare(C o1, C o2) { + return 0; + } + }; + } } public class PolyCollectorTypeVars { - // Both of these come from the extends Comparable on line 9 - @SuppressWarnings({"rawtypes", "type.argument.type.incompatible"}) - public static MyGen treeKeys2() { - // See Limitation in DefaultTypeArgumentInference on interdependent methods - return treeKeys(Ordering.natural()); - } + // Both of these come from the extends Comparable on line 9 + @SuppressWarnings({"rawtypes", "type.argument.type.incompatible"}) + public static MyGen treeKeys2() { + // See Limitation in DefaultTypeArgumentInference on interdependent methods + return treeKeys(Ordering.natural()); + } - public static MyGen treeKeys(final Comparator comparator) { - return new MyGen(); - } + public static MyGen treeKeys(final Comparator comparator) { + return new MyGen(); + } } diff --git a/framework/tests/all-systems/PrintArray.java b/framework/tests/all-systems/PrintArray.java index a31cdf2ea8b..74d34ee2cdc 100644 --- a/framework/tests/all-systems/PrintArray.java +++ b/framework/tests/all-systems/PrintArray.java @@ -1,14 +1,14 @@ public class PrintArray { - // the I18n checker correctly issues an error and Nullness org.checkerframework.checker - // correctly issue a warning below, but we would like to keep this test in all-systems. - @SuppressWarnings({"i18n", "nullness:nulltest.redundant"}) - public static final void print(java.io.PrintStream ps, Object[][] a) { - if (a == null) { - ps.println("null"); - return; - } - // When analyzing this call, we see an exception about taking the LUB - // of ATMs with different numbers of qualifiers. - ps.print('7'); + // the I18n checker correctly issues an error and Nullness org.checkerframework.checker + // correctly issue a warning below, but we would like to keep this test in all-systems. + @SuppressWarnings({"i18n", "nullness:nulltest.redundant"}) + public static final void print(java.io.PrintStream ps, Object[][] a) { + if (a == null) { + ps.println("null"); + return; } + // When analyzing this call, we see an exception about taking the LUB + // of ATMs with different numbers of qualifiers. + ps.print('7'); + } } diff --git a/framework/tests/all-systems/RawSuper.java b/framework/tests/all-systems/RawSuper.java index 41df28daee9..c5c544d77d8 100644 --- a/framework/tests/all-systems/RawSuper.java +++ b/framework/tests/all-systems/RawSuper.java @@ -2,16 +2,16 @@ import java.util.List; public class RawSuper { - static class MySuper { - public MySuper(List p) {} + static class MySuper { + public MySuper(List p) {} - void method(List o) {} - } + void method(List o) {} + } - @SuppressWarnings("unchecked") // raw extends - static class SubRaw extends MySuper { - public SubRaw() { - super(new ArrayList>()); - } + @SuppressWarnings("unchecked") // raw extends + static class SubRaw extends MySuper { + public SubRaw() { + super(new ArrayList>()); } + } } diff --git a/framework/tests/all-systems/RawTypeAssignment.java b/framework/tests/all-systems/RawTypeAssignment.java index 176bc3bd782..ba99804e584 100644 --- a/framework/tests/all-systems/RawTypeAssignment.java +++ b/framework/tests/all-systems/RawTypeAssignment.java @@ -9,15 +9,15 @@ class Components extends ArrayList {} // class Components extends ArrayList {} public class RawTypeAssignment { - static Components getComponents() { - return new Components(); - } + static Components getComponents() { + return new Components(); + } - static void addTimes(Calendar calendar) { - // Type systems may issue an error below because of a mismatch between the type arguments. - @SuppressWarnings("assignment.type.incompatible") - // :: warning: [unchecked] unchecked conversion - ArrayList clist = getComponents(); - clist.get(0); - } + static void addTimes(Calendar calendar) { + // Type systems may issue an error below because of a mismatch between the type arguments. + @SuppressWarnings("assignment.type.incompatible") + // :: warning: [unchecked] unchecked conversion + ArrayList clist = getComponents(); + clist.get(0); + } } diff --git a/framework/tests/all-systems/RawTypes.java b/framework/tests/all-systems/RawTypes.java index c798988ec3f..0a3c8d2de03 100644 --- a/framework/tests/all-systems/RawTypes.java +++ b/framework/tests/all-systems/RawTypes.java @@ -1,15 +1,15 @@ import java.util.List; public class RawTypes { - public void m(ClassLoader cl) throws ClassNotFoundException { - Class clazz = cl.loadClass("java.lang.Object"); - } + public void m(ClassLoader cl) throws ClassNotFoundException { + Class clazz = cl.loadClass("java.lang.Object"); + } } interface I { - public void m(List l); + public void m(List l); } class OtherClass implements I { - public void m(List l) {} + public void m(List l) {} } diff --git a/framework/tests/all-systems/ResourceVariables.java b/framework/tests/all-systems/ResourceVariables.java index add322044fa..53d33294c7d 100644 --- a/framework/tests/all-systems/ResourceVariables.java +++ b/framework/tests/all-systems/ResourceVariables.java @@ -3,9 +3,9 @@ // Tests related to resource variables in try-with-resources statements. public class ResourceVariables { - void foo(InputStream arg) { - try (InputStream in = arg) { - } catch (IOException e) { - } + void foo(InputStream arg) { + try (InputStream in = arg) { + } catch (IOException e) { } + } } diff --git a/framework/tests/all-systems/SameBoundsCrazy.java b/framework/tests/all-systems/SameBoundsCrazy.java index da9b652ec80..975a527cb30 100644 --- a/framework/tests/all-systems/SameBoundsCrazy.java +++ b/framework/tests/all-systems/SameBoundsCrazy.java @@ -2,17 +2,17 @@ @SuppressWarnings("all") // the assignment is not legal in most checkers. public class SameBoundsCrazy { - static class Simple {} + static class Simple {} - static class Gen> {} + static class Gen> {} - void use(Gen, ? super Simple>> g) { - Gen, Simple>> s = g; - } + void use(Gen, ? super Simple>> g) { + Gen, Simple>> s = g; + } - static class Gen2 {} + static class Gen2 {} - void use2(Gen2 g) { - Gen2 f = g; - } + void use2(Gen2 g) { + Gen2 f = g; + } } diff --git a/framework/tests/all-systems/SelfRef.java b/framework/tests/all-systems/SelfRef.java index fac424abce6..cbb5d139191 100644 --- a/framework/tests/all-systems/SelfRef.java +++ b/framework/tests/all-systems/SelfRef.java @@ -1,8 +1,8 @@ @SuppressWarnings("allcheckers") // Only check for crashes. class SelfRef, V> { - public SelfRef parent, left, right; + public SelfRef parent, left, right; - public SelfRef(SelfRef parent) { - this.parent = parent; - } + public SelfRef(SelfRef parent) { + this.parent = parent; + } } diff --git a/framework/tests/all-systems/SetOfSet.java b/framework/tests/all-systems/SetOfSet.java index ffbcb83d4cf..a6778907148 100644 --- a/framework/tests/all-systems/SetOfSet.java +++ b/framework/tests/all-systems/SetOfSet.java @@ -1,14 +1,14 @@ package wildcards; @SuppressWarnings({ - "initialization", - "initializedfields:contracts.postcondition" + "initialization", + "initializedfields:contracts.postcondition" }) // field isn't set public class SetOfSet { - public void addAll(SetOfSet s) { - E[] svalues = s.values; - } + public void addAll(SetOfSet s) { + E[] svalues = s.values; + } - protected E[] values; + protected E[] values; } diff --git a/framework/tests/all-systems/SimpleLog.java b/framework/tests/all-systems/SimpleLog.java index 155fad32b3a..c6fee147d51 100644 --- a/framework/tests/all-systems/SimpleLog.java +++ b/framework/tests/all-systems/SimpleLog.java @@ -1,10 +1,10 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class SimpleLog { - public SimpleLog() { - try { - int i = 0; - } catch (Exception e) { - throw new RuntimeException("", e); - } + public SimpleLog() { + try { + int i = 0; + } catch (Exception e) { + throw new RuntimeException("", e); } + } } diff --git a/framework/tests/all-systems/StateMatch.java b/framework/tests/all-systems/StateMatch.java index 07ad4ecbc9d..6546b6a1831 100644 --- a/framework/tests/all-systems/StateMatch.java +++ b/framework/tests/all-systems/StateMatch.java @@ -1,26 +1,26 @@ public class StateMatch { - private int num_elts = 0; + private int num_elts = 0; - @SuppressWarnings("nullness") - private double[][] elts = null; + @SuppressWarnings("nullness") + private double[][] elts = null; - @SuppressWarnings({ - "interning", - "index" - }) // This code is inherently unsafe for the index checker, but adding index annotations - // produces warnings for other checkers (fenum). - public boolean state_match(Object state) { - if (!(state instanceof double[][])) { - System.out.println(""); - } + @SuppressWarnings({ + "interning", + "index" + }) // This code is inherently unsafe for the index checker, but adding index annotations + // produces warnings for other checkers (fenum). + public boolean state_match(Object state) { + if (!(state instanceof double[][])) { + System.out.println(""); + } - double[][] e = (double[][]) state; - boolean match = false; - if (elts[0] == e[0]) { - // When analyzing this statement, we get an exception about taking - // the LUB of ATMs with empty sets of qualifiers. - match = true; - } - return (true); + double[][] e = (double[][]) state; + boolean match = false; + if (elts[0] == e[0]) { + // When analyzing this statement, we get an exception about taking + // the LUB of ATMs with empty sets of qualifiers. + match = true; } + return (true); + } } diff --git a/framework/tests/all-systems/SuperThis.java b/framework/tests/all-systems/SuperThis.java index 247bb9ee954..28e4176d8fc 100644 --- a/framework/tests/all-systems/SuperThis.java +++ b/framework/tests/all-systems/SuperThis.java @@ -1,15 +1,15 @@ @SuppressWarnings("all") public class SuperThis { - class Super {} + class Super {} - // Test super() and this() - class Inner extends Super { - public Inner() { - super(); - } + // Test super() and this() + class Inner extends Super { + public Inner() { + super(); + } - public Inner(int i) { - this(); - } + public Inner(int i) { + this(); } + } } diff --git a/framework/tests/all-systems/Ternary.java b/framework/tests/all-systems/Ternary.java index f5a4573a7cd..f9928555984 100644 --- a/framework/tests/all-systems/Ternary.java +++ b/framework/tests/all-systems/Ternary.java @@ -1,74 +1,74 @@ import java.lang.ref.WeakReference; public class Ternary { - void m1(boolean b) { - String s = b ? new String("foo") : null; - } + void m1(boolean b) { + String s = b ? new String("foo") : null; + } - void m2(boolean b) { - String s = b ? null : new String("foo"); - } + void m2(boolean b) { + String s = b ? null : new String("foo"); + } - @SuppressWarnings("nullness") // Don't want to depend on @Nullable - String m3(boolean b) { - return b ? new String("foo") : null; - } + @SuppressWarnings("nullness") // Don't want to depend on @Nullable + String m3(boolean b) { + return b ? new String("foo") : null; + } - void m4(boolean b) { - String[] s = b ? new String[] {""} : null; - } + void m4(boolean b) { + String[] s = b ? new String[] {""} : null; + } - void m5(boolean b) { - Object o = new Object(); - String s = b ? (String) o : null; - } + void m5(boolean b) { + Object o = new Object(); + String s = b ? (String) o : null; + } - void m6(boolean b) { - String p = "x*((("; - String s = b ? p : null; - } + void m6(boolean b) { + String p = "x*((("; + String s = b ? p : null; + } - class Generic { - void cond(boolean b, T p1, T p2) { - p1 = b ? p1 : p2; - } + class Generic { + void cond(boolean b, T p1, T p2) { + p1 = b ? p1 : p2; } + } - void array(boolean b) { - String[] s = b ? new String[] {""} : null; - } + void array(boolean b) { + String[] s = b ? new String[] {""} : null; + } - void generic(boolean b, Generic p) { - Generic s = b ? p : null; - } + void generic(boolean b, Generic p) { + Generic s = b ? p : null; + } - void primarray(boolean b) { - long[] result = b ? null : new long[10]; - } + void primarray(boolean b) { + long[] result = b ? null : new long[10]; + } - void vars() { - // String and Integer generate an intersection type. - String c = null; - Integer m = null; - Object s = (m != null) ? m : c; - } + void vars() { + // String and Integer generate an intersection type. + String c = null; + Integer m = null; + Object s = (m != null) ? m : c; + } - void vars2() { - // String and Integer generate an intersection type. - String c = null; - Integer m = null; - Object s = (m != null) ? m : c; - } + void vars2() { + // String and Integer generate an intersection type. + String c = null; + Integer m = null; + Object s = (m != null) ? m : c; + } - public void test(MyWeakRef existingRef) { - @SuppressWarnings("nulltest.redundant") - F existing = existingRef == null ? null : existingRef.get(); - } + public void test(MyWeakRef existingRef) { + @SuppressWarnings("nulltest.redundant") + F existing = existingRef == null ? null : existingRef.get(); + } - private static final class MyWeakRef extends WeakReference { + private static final class MyWeakRef extends WeakReference { - public MyWeakRef(L referent) { - super(referent); - } + public MyWeakRef(L referent) { + super(referent); } + } } diff --git a/framework/tests/all-systems/Throw.java b/framework/tests/all-systems/Throw.java index 3e76a261e6b..0edcff67cb7 100644 --- a/framework/tests/all-systems/Throw.java +++ b/framework/tests/all-systems/Throw.java @@ -1,18 +1,18 @@ import java.util.List; public class Throw { - void throwTypeVar(E ex) { - try { - throw ex; - } catch (Exception e) { - } + void throwTypeVar(E ex) { + try { + throw ex; + } catch (Exception e) { } + } - void throwWildcard(List list) { - try { - throw list.get(0); - } catch (Exception e) { + void throwWildcard(List list) { + try { + throw list.get(0); + } catch (Exception e) { - } } + } } diff --git a/framework/tests/all-systems/TypeVarAndArrayRefinement.java b/framework/tests/all-systems/TypeVarAndArrayRefinement.java index 8a7e68e8515..d162e44ff48 100644 --- a/framework/tests/all-systems/TypeVarAndArrayRefinement.java +++ b/framework/tests/all-systems/TypeVarAndArrayRefinement.java @@ -1,20 +1,20 @@ public class TypeVarAndArrayRefinement { - private > T getEnumValue(Class enumType, String name) { - T[] constants = enumType.getEnumConstants(); - if (constants == null) { - throw new IllegalArgumentException(enumType.getName() + " is not an enum type"); - } - // previously the constants method was considered nullable mainly because it was an invalid - // type because when lubbing type variables we didn't copy the declared types on the bounds - // over to the lub - for (T constant : constants) { - if (constant.name().equalsIgnoreCase(name.replace('-', '_'))) { - return constant; - } - } - // same error that's thrown by Enum.valueOf() - throw new IllegalArgumentException( - "No enum constant " + enumType.getCanonicalName() + "." + name); + private > T getEnumValue(Class enumType, String name) { + T[] constants = enumType.getEnumConstants(); + if (constants == null) { + throw new IllegalArgumentException(enumType.getName() + " is not an enum type"); } + // previously the constants method was considered nullable mainly because it was an invalid + // type because when lubbing type variables we didn't copy the declared types on the bounds + // over to the lub + for (T constant : constants) { + if (constant.name().equalsIgnoreCase(name.replace('-', '_'))) { + return constant; + } + } + // same error that's thrown by Enum.valueOf() + throw new IllegalArgumentException( + "No enum constant " + enumType.getCanonicalName() + "." + name); + } } diff --git a/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java b/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java index cba41782a9d..2fe1aeea64c 100644 --- a/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java +++ b/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java @@ -1,8 +1,8 @@ public class TypeVarAndArrayRefinementSmall { - private > T getEnumValue(T[] constants) { - for (T constant : constants) { - return constant; - } - throw new Error(); + private > T getEnumValue(T[] constants) { + for (T constant : constants) { + return constant; } + throw new Error(); + } } diff --git a/framework/tests/all-systems/TypeVarInstanceOf.java b/framework/tests/all-systems/TypeVarInstanceOf.java index 4442b71fb74..990e8074d91 100644 --- a/framework/tests/all-systems/TypeVarInstanceOf.java +++ b/framework/tests/all-systems/TypeVarInstanceOf.java @@ -1,5 +1,5 @@ public class TypeVarInstanceOf { - public static void clone(final T obj) { - if (obj instanceof Cloneable) {} - } + public static void clone(final T obj) { + if (obj instanceof Cloneable) {} + } } diff --git a/framework/tests/all-systems/TypeVarPrimitives.java b/framework/tests/all-systems/TypeVarPrimitives.java index 02a82e6960c..1ad25d008ba 100644 --- a/framework/tests/all-systems/TypeVarPrimitives.java +++ b/framework/tests/all-systems/TypeVarPrimitives.java @@ -2,11 +2,11 @@ // checker/tests/nullness/TypeVarPrimitivesNullness.java and // checker/tests/interning/TypeVarPrimitivesInterning.java public class TypeVarPrimitives { - void method(T tLong) { - long l = tLong; - } + void method(T tLong) { + long l = tLong; + } - void methodIntersection(T tLong) { - long l = tLong; - } + void methodIntersection(T tLong) { + long l = tLong; + } } diff --git a/framework/tests/all-systems/TypeVarVarargs.java b/framework/tests/all-systems/TypeVarVarargs.java index ffc6eceed53..9a928cccfd6 100644 --- a/framework/tests/all-systems/TypeVarVarargs.java +++ b/framework/tests/all-systems/TypeVarVarargs.java @@ -1,9 +1,9 @@ abstract class TypeVarVarargs { - public abstract T foo(); + public abstract T foo(); - public abstract void bar(Integer... s); + public abstract void bar(Integer... s); - public void m() { - bar(foo(), foo()); - } + public void m() { + bar(foo(), foo()); + } } diff --git a/framework/tests/all-systems/TypeVars.java b/framework/tests/all-systems/TypeVars.java index cc2442f17ab..ef748e5fc16 100644 --- a/framework/tests/all-systems/TypeVars.java +++ b/framework/tests/all-systems/TypeVars.java @@ -1,22 +1,22 @@ public class TypeVars { - class Test1 { - void m() { - @SuppressWarnings("unchecked") - T x = (T) new Object(); + class Test1 { + void m() { + @SuppressWarnings("unchecked") + T x = (T) new Object(); - Object o = x; - } + Object o = x; + } - class Inner1 {} + class Inner1 {} - public Inner1 method1() { - return new Inner1<>(); - } + public Inner1 method1() { + return new Inner1<>(); } + } - // It's difficult to add more test cases that - // should work for all type systems. - // Ensure that for the different type systems, annotations - // on the type variable are propagated correctly. + // It's difficult to add more test cases that + // should work for all type systems. + // Ensure that for the different type systems, annotations + // on the type variable are propagated correctly. } diff --git a/framework/tests/all-systems/UnionCrash.java b/framework/tests/all-systems/UnionCrash.java index 3bb5c12e08d..3ea9d253a66 100644 --- a/framework/tests/all-systems/UnionCrash.java +++ b/framework/tests/all-systems/UnionCrash.java @@ -3,28 +3,28 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class UnionCrash { - void foo(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - try { - bar(); - } catch (SubMyExceptionA | MyExceptionB ex2) { - // This call cause a crash - typeVar(ex1, ex2); - } - } + void foo(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + try { + bar(); + } catch (SubMyExceptionA | MyExceptionB ex2) { + // This call cause a crash + typeVar(ex1, ex2); + } } + } - void typeVar(T param, T param2) {} + void typeVar(T param, T param2) {} - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - interface MyInterface {} + interface MyInterface {} - class MyExceptionA extends Throwable implements Cloneable, MyInterface {} + class MyExceptionA extends Throwable implements Cloneable, MyInterface {} - class MyExceptionB extends Throwable implements Cloneable, MyInterface {} + class MyExceptionB extends Throwable implements Cloneable, MyInterface {} - class SubMyExceptionA extends MyExceptionA {} + class SubMyExceptionA extends MyExceptionA {} } diff --git a/framework/tests/all-systems/UnionTypes.java b/framework/tests/all-systems/UnionTypes.java index ce3f3329aad..4fc1717e433 100644 --- a/framework/tests/all-systems/UnionTypes.java +++ b/framework/tests/all-systems/UnionTypes.java @@ -1,12 +1,12 @@ // Test case for Issue 145 // https://github.com/typetools/checker-framework/issues/145 public class UnionTypes { - public void TryCatch() { - try { - int[] arr = new int[10]; - arr[4] = 1; - } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException exc) { - Exception e = exc; - } + public void TryCatch() { + try { + int[] arr = new int[10]; + arr[4] = 1; + } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException exc) { + Exception e = exc; } + } } diff --git a/framework/tests/all-systems/Unions.java b/framework/tests/all-systems/Unions.java index e9d133d99f3..3837a84e49c 100644 --- a/framework/tests/all-systems/Unions.java +++ b/framework/tests/all-systems/Unions.java @@ -1,50 +1,50 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class Unions { - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex) { - typeVar(ex); - typeVarIntersection(ex); - - typeVarWildcard(ex, param); - typeVarWildcard2(ex, param); - } + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex) { + typeVar(ex); + typeVarIntersection(ex); + + typeVarWildcard(ex, param); + typeVarWildcard2(ex, param); } + } - void foo2(MyInterface param) throws Throwable { - try { - bar(); - } catch (SubMyExceptionA | SubMyExceptionA2 ex) { - typeVar(ex); - typeVar2(ex, ex); + void foo2(MyInterface param) throws Throwable { + try { + bar(); + } catch (SubMyExceptionA | SubMyExceptionA2 ex) { + typeVar(ex); + typeVar2(ex, ex); - typeVarIntersection(ex); + typeVarIntersection(ex); - typeVarWildcard(ex, param); - typeVarWildcard2(ex, param); - } + typeVarWildcard(ex, param); + typeVarWildcard2(ex, param); } + } - > void typeVarIntersection(T param) {} + > void typeVarIntersection(T param) {} - void typeVar(T param) {} + void typeVar(T param) {} - void typeVar2(T param, T param2) {} + void typeVar2(T param, T param2) {} - void typeVarWildcard(T param, MyInterface myInterface) {} + void typeVarWildcard(T param, MyInterface myInterface) {} - void typeVarWildcard2(T param, MyInterface myInterface) {} + void typeVarWildcard2(T param, MyInterface myInterface) {} - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - interface MyInterface {} + interface MyInterface {} - class MyExceptionA extends Throwable implements Cloneable, MyInterface {} + class MyExceptionA extends Throwable implements Cloneable, MyInterface {} - class MyExceptionB extends Throwable implements Cloneable, MyInterface {} + class MyExceptionB extends Throwable implements Cloneable, MyInterface {} - class SubMyExceptionA extends MyExceptionA {} + class SubMyExceptionA extends MyExceptionA {} - class SubMyExceptionA2 extends MyExceptionA {} + class SubMyExceptionA2 extends MyExceptionA {} } diff --git a/framework/tests/all-systems/VarKeyword.java b/framework/tests/all-systems/VarKeyword.java index daf26ee405e..2d605a28a69 100644 --- a/framework/tests/all-systems/VarKeyword.java +++ b/framework/tests/all-systems/VarKeyword.java @@ -2,14 +2,14 @@ // @below-java11-jdk-skip-test public class VarKeyword { - @SuppressWarnings("dereference.of.nullable") - void method(List list) { - var s = "Hello!"; - s = null; - s.toString(); - for (var i : list) {} + @SuppressWarnings("dereference.of.nullable") + void method(List list) { + var s = "Hello!"; + s = null; + s.toString(); + for (var i : list) {} - var listVar = list; - method((listVar)); - } + var listVar = list; + method((listVar)); + } } diff --git a/framework/tests/all-systems/Viz.java b/framework/tests/all-systems/Viz.java index e0af141697d..417a1cc7770 100644 --- a/framework/tests/all-systems/Viz.java +++ b/framework/tests/all-systems/Viz.java @@ -2,18 +2,18 @@ public class Viz { - static class AbstractValue> {} + static class AbstractValue> {} - public interface Store> {} + public interface Store> {} - public interface TransferFunction, D extends Store> {} + public interface TransferFunction, D extends Store> {} - public interface CFGVisualizer< - E extends AbstractValue, F extends Store, G extends TransferFunction> {} + public interface CFGVisualizer< + E extends AbstractValue, F extends Store, G extends TransferFunction> {} - static class CFAbstractStore, X extends CFAbstractStore> - implements Store { + static class CFAbstractStore, X extends CFAbstractStore> + implements Store { - void test(CFGVisualizer param) {} - } + void test(CFGVisualizer param) {} + } } diff --git a/framework/tests/all-systems/WildCardCrash.java b/framework/tests/all-systems/WildCardCrash.java index 615cab04761..aefb31f5cda 100644 --- a/framework/tests/all-systems/WildCardCrash.java +++ b/framework/tests/all-systems/WildCardCrash.java @@ -1,40 +1,40 @@ public class WildCardCrash {} abstract class AbstractTransfer123< - IndexStore extends CFAbstractStore123, - MySelf extends AbstractTransfer123> - extends CFAbstractTransfer123 { - void method() { - // There was a crash when checking the assignment of analysis to the formal parameter. - SomeGen rfi = new SomeGen<>(analysis); - } + IndexStore extends CFAbstractStore123, + MySelf extends AbstractTransfer123> + extends CFAbstractTransfer123 { + void method() { + // There was a crash when checking the assignment of analysis to the formal parameter. + SomeGen rfi = new SomeGen<>(analysis); + } } class SomeGen> { - public SomeGen(CFAbstractAnalysis123 analysis) {} + public SomeGen(CFAbstractAnalysis123 analysis) {} } class CFValue123 extends CFAbstractValue123 {} @SuppressWarnings({"initialization", "initializedfields:contracts.postcondition.not.satisfied"}) class CFAbstractTransfer123< - V extends CFAbstractValue123, - S extends CFAbstractStore123, - T extends CFAbstractTransfer123> - implements TransferFunction123 { - protected CFAbstractAnalysis123 analysis; + V extends CFAbstractValue123, + S extends CFAbstractStore123, + T extends CFAbstractTransfer123> + implements TransferFunction123 { + protected CFAbstractAnalysis123 analysis; } class CFAbstractValue123> implements AbstractValue123 {} class CFAbstractStore123, S extends CFAbstractStore123> - implements Store123 {} + implements Store123 {} abstract class CFAbstractAnalysis123< - V extends CFAbstractValue123, - S extends CFAbstractStore123, - T extends CFAbstractTransfer123> - extends Analysis123 {} + V extends CFAbstractValue123, + S extends CFAbstractStore123, + T extends CFAbstractTransfer123> + extends Analysis123 {} interface AbstractValue123> {} @@ -43,6 +43,4 @@ interface Store123> {} interface TransferFunction123, S extends Store123> {} class Analysis123< - A extends AbstractValue123, - S extends Store123, - T extends TransferFunction123> {} + A extends AbstractValue123, S extends Store123, T extends TransferFunction123> {} diff --git a/framework/tests/all-systems/WildcardBounds.java b/framework/tests/all-systems/WildcardBounds.java index 9e31b3ce1a6..2e648929cd5 100644 --- a/framework/tests/all-systems/WildcardBounds.java +++ b/framework/tests/all-systems/WildcardBounds.java @@ -1,15 +1,15 @@ import java.io.Serializable; public class WildcardBounds { - class BoundedGeneric {} + class BoundedGeneric {} - class BoundedGeneric2 {} + class BoundedGeneric2 {} - class BoundedGeneric3 {} + class BoundedGeneric3 {} - void use() { - BoundedGeneric a; - BoundedGeneric b; - BoundedGeneric c; - } + void use() { + BoundedGeneric a; + BoundedGeneric b; + BoundedGeneric c; + } } diff --git a/framework/tests/all-systems/WildcardCharPrimitive.java b/framework/tests/all-systems/WildcardCharPrimitive.java index 77cfbc93b80..1a053ef07ff 100644 --- a/framework/tests/all-systems/WildcardCharPrimitive.java +++ b/framework/tests/all-systems/WildcardCharPrimitive.java @@ -1,24 +1,24 @@ public class WildcardCharPrimitive { - static interface Predicate { - boolean apply(T t); - } + static interface Predicate { + boolean apply(T t); + } - abstract static class Matcher { + abstract static class Matcher { - public abstract boolean matches(char character); - } + public abstract boolean matches(char character); + } - public static void forPredicate(final Predicate predicate) { + public static void forPredicate(final Predicate predicate) { - new Matcher() { - // this tests default type hierarchy visitPrimitive_Wildcard - public boolean matches(char c) { - // this will happen when a type system does not have an EXPLICIT_LOWER_BOUND - // that matches the default for char c - @SuppressWarnings("argument.type.incompatible") - boolean value = predicate.apply(c); - return value; - } - }; - } + new Matcher() { + // this tests default type hierarchy visitPrimitive_Wildcard + public boolean matches(char c) { + // this will happen when a type system does not have an EXPLICIT_LOWER_BOUND + // that matches the default for char c + @SuppressWarnings("argument.type.incompatible") + boolean value = predicate.apply(c); + return value; + } + }; + } } diff --git a/framework/tests/all-systems/WildcardCon.java b/framework/tests/all-systems/WildcardCon.java index ea139919ab1..c16b31682b5 100644 --- a/framework/tests/all-systems/WildcardCon.java +++ b/framework/tests/all-systems/WildcardCon.java @@ -2,15 +2,15 @@ @SuppressWarnings("all") // Only testing for crashes public class WildcardCon { - ComparatorClass> RANGE_LEX_ORDERING = null; + ComparatorClass> RANGE_LEX_ORDERING = null; - WildcardCon(Comparator comparator) {} + WildcardCon(Comparator comparator) {} - void use() { - new WildcardCon>(RANGE_LEX_ORDERING); - } + void use() { + new WildcardCon>(RANGE_LEX_ORDERING); + } - class ComparableClass {} + class ComparableClass {} - abstract class ComparatorClass implements Comparator {} + abstract class ComparatorClass implements Comparator {} } diff --git a/framework/tests/all-systems/WildcardForEach.java b/framework/tests/all-systems/WildcardForEach.java index 69ad678452c..3db717b3695 100644 --- a/framework/tests/all-systems/WildcardForEach.java +++ b/framework/tests/all-systems/WildcardForEach.java @@ -1,11 +1,11 @@ import java.util.List; public class WildcardForEach { - static class Gen> {} + static class Gen> {} - void test(List> x) { - // This used to cause a crash in - // org.checkerframework.framework.flow.CFTreeBuilder#buildAnnotatedType - for (Gen a : x) {} - } + void test(List> x) { + // This used to cause a crash in + // org.checkerframework.framework.flow.CFTreeBuilder#buildAnnotatedType + for (Gen a : x) {} + } } diff --git a/framework/tests/all-systems/WildcardInReturn.java b/framework/tests/all-systems/WildcardInReturn.java index 7c9c785e820..c9327b1ef8a 100644 --- a/framework/tests/all-systems/WildcardInReturn.java +++ b/framework/tests/all-systems/WildcardInReturn.java @@ -12,14 +12,14 @@ abstract class WildcardInReturn { - abstract WildcardInReturn of(String key); + abstract WildcardInReturn of(String key); - abstract WildcardInReturn getSubtype(Class subclass); + abstract WildcardInReturn getSubtype(Class subclass); - WildcardInReturn getSubtypeFromLowerBounds(Class subclass, String key) { - @SuppressWarnings("unchecked") // T's lower bound is - WildcardInReturn bound = (WildcardInReturn) of(key); - // Java supports only one lowerbound anyway. - return bound.getSubtype(subclass); - } + WildcardInReturn getSubtypeFromLowerBounds(Class subclass, String key) { + @SuppressWarnings("unchecked") // T's lower bound is + WildcardInReturn bound = (WildcardInReturn) of(key); + // Java supports only one lowerbound anyway. + return bound.getSubtype(subclass); + } } diff --git a/framework/tests/all-systems/WildcardIterable.java b/framework/tests/all-systems/WildcardIterable.java index 2222863a26a..1249a6a9348 100644 --- a/framework/tests/all-systems/WildcardIterable.java +++ b/framework/tests/all-systems/WildcardIterable.java @@ -2,18 +2,18 @@ import java.util.List; public class WildcardIterable { - private static List catListAndIterable( - final List list, final Iterable iterable) { - final List newList = new ArrayList<>(); + private static List catListAndIterable( + final List list, final Iterable iterable) { + final List newList = new ArrayList<>(); - for (T listObject : list) { - newList.add(listObject); - } - - for (T iterObject : iterable) { - newList.add(iterObject); - } + for (T listObject : list) { + newList.add(listObject); + } - return newList; + for (T iterObject : iterable) { + newList.add(iterObject); } + + return newList; + } } diff --git a/framework/tests/all-systems/WildcardSuper.java b/framework/tests/all-systems/WildcardSuper.java index 4bb7386da9d..4fc25cd2274 100644 --- a/framework/tests/all-systems/WildcardSuper.java +++ b/framework/tests/all-systems/WildcardSuper.java @@ -1,13 +1,13 @@ public class WildcardSuper { - interface Consumer { - void consume(T object); - } + interface Consumer { + void consume(T object); + } - Consumer testCast(Consumer consumer) { - return cast(consumer); - } + Consumer testCast(Consumer consumer) { + return cast(consumer); + } - private static Consumer cast(final Consumer consumer) { - throw new RuntimeException(); - } + private static Consumer cast(final Consumer consumer) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/WildcardSuper2.java b/framework/tests/all-systems/WildcardSuper2.java index 4cead900274..29a87bcbf55 100644 --- a/framework/tests/all-systems/WildcardSuper2.java +++ b/framework/tests/all-systems/WildcardSuper2.java @@ -3,14 +3,14 @@ // See also checker/nullness/generics/WildcardOverride.java interface AInterface { - public abstract int transform(List function); + public abstract int transform(List function); } class B implements AInterface { - // This shouldn't work for nullness as the function won't take possibly nullable values. - @SuppressWarnings({"nullness", "fenum:override.param.invalid", "aliasing"}) - @Override - public int transform(List function) { - return 0; - } + // This shouldn't work for nullness as the function won't take possibly nullable values. + @SuppressWarnings({"nullness", "fenum:override.param.invalid", "aliasing"}) + @Override + public int transform(List function) { + return 0; + } } diff --git a/framework/tests/all-systems/java17/BindingVariable.java b/framework/tests/all-systems/java17/BindingVariable.java index ed082ab1e1d..ffaa68ea6f1 100644 --- a/framework/tests/all-systems/java17/BindingVariable.java +++ b/framework/tests/all-systems/java17/BindingVariable.java @@ -2,11 +2,11 @@ // This is a test case for https://github.com/typetools/checker-framework/issues/5013. public class BindingVariable { - public static int bar(Object o) { - if (o instanceof String s) { - return s.length(); - } else { - return 0; - } + public static int bar(Object o) { + if (o instanceof String s) { + return s.length(); + } else { + return 0; } + } } diff --git a/framework/tests/all-systems/java17/Issue5749.java b/framework/tests/all-systems/java17/Issue5749.java index bef9f94f5f3..c756d3687f7 100644 --- a/framework/tests/all-systems/java17/Issue5749.java +++ b/framework/tests/all-systems/java17/Issue5749.java @@ -3,11 +3,11 @@ public record Issue5749() { - public enum Bar { - A, - B, - C - } + public enum Bar { + A, + B, + C + } - public static final Stream BAR = Stream.of(Bar.values()).map(b -> ""); + public static final Stream BAR = Stream.of(Bar.values()).map(b -> ""); } diff --git a/framework/tests/all-systems/java17/Issue5930.java b/framework/tests/all-systems/java17/Issue5930.java index 0f6ce3b2aa7..2d4f9a228a8 100644 --- a/framework/tests/all-systems/java17/Issue5930.java +++ b/framework/tests/all-systems/java17/Issue5930.java @@ -3,18 +3,18 @@ import java.util.function.Supplier; public final class Issue5930 { - enum TestEnum { - FIRST, - SECOND - }; + enum TestEnum { + FIRST, + SECOND + }; - public static void main(String[] args) { - TestEnum testEnum = TestEnum.FIRST; - Supplier supplier = - switch (testEnum) { - case FIRST -> () -> 1; - case SECOND -> () -> 2; - }; - System.out.println(supplier.get()); - } + public static void main(String[] args) { + TestEnum testEnum = TestEnum.FIRST; + Supplier supplier = + switch (testEnum) { + case FIRST -> () -> 1; + case SECOND -> () -> 2; + }; + System.out.println(supplier.get()); + } } diff --git a/framework/tests/all-systems/java17/Issue6069.java b/framework/tests/all-systems/java17/Issue6069.java index 21422fad1c0..ade59f5717b 100644 --- a/framework/tests/all-systems/java17/Issue6069.java +++ b/framework/tests/all-systems/java17/Issue6069.java @@ -5,21 +5,21 @@ package issue6069; public class Issue6069 { - public interface MyInterface { - Record foo(); - } + public interface MyInterface { + Record foo(); + } - public static class MyClass implements MyInterface { + public static class MyClass implements MyInterface { - public MyRecord foo() { - return new MyRecord(42); - } + public MyRecord foo() { + return new MyRecord(42); + } - record MyRecord(int bar) {} + record MyRecord(int bar) {} - public static void main(String[] args) { - MyClass f = new MyClass(); - System.out.println(f.foo()); - } + public static void main(String[] args) { + MyClass f = new MyClass(); + System.out.println(f.foo()); } + } } diff --git a/framework/tests/all-systems/java17/Issue6100.java b/framework/tests/all-systems/java17/Issue6100.java index 88c9fcf9e57..82c5c7e5c55 100644 --- a/framework/tests/all-systems/java17/Issue6100.java +++ b/framework/tests/all-systems/java17/Issue6100.java @@ -1,16 +1,15 @@ // @below-java17-jdk-skip-test // @infer-jaifs-skip-test The AFU's JAIF reading/writing libraries don't support records. -import org.checkerframework.checker.index.qual.NonNegative; - import java.util.List; +import org.checkerframework.checker.index.qual.NonNegative; @SuppressWarnings("signedness") public record Issue6100(List<@NonNegative Integer> bar) { - public Issue6100 { - if (bar.size() < 0) { - throw new IllegalArgumentException(); - } + public Issue6100 { + if (bar.size() < 0) { + throw new IllegalArgumentException(); } + } } diff --git a/framework/tests/all-systems/java17/SimpleRecord.java b/framework/tests/all-systems/java17/SimpleRecord.java index b218f29f600..f5996944945 100644 --- a/framework/tests/all-systems/java17/SimpleRecord.java +++ b/framework/tests/all-systems/java17/SimpleRecord.java @@ -3,8 +3,8 @@ import java.util.function.Predicate; record SimpleRecord>(T root) implements Predicate { - @Override - public boolean test(T t) { - return root().compareTo(t) < 0; - } + @Override + public boolean test(T t) { + return root().compareTo(t) < 0; + } } diff --git a/framework/tests/all-systems/java17/SwitchTests.java b/framework/tests/all-systems/java17/SwitchTests.java index faad3e33300..08b6dde21c7 100644 --- a/framework/tests/all-systems/java17/SwitchTests.java +++ b/framework/tests/all-systems/java17/SwitchTests.java @@ -9,163 +9,163 @@ @SuppressWarnings("all") // Just check for crashes. class SwitchTests { - String statementRule(DayOfWeek day) { - var today = ""; - switch (day) { - case SATURDAY, SUNDAY -> today = "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> today = "workday"; - default -> throw new IllegalArgumentException("Invalid day: " + day.name()); - } - return today; + String statementRule(DayOfWeek day) { + var today = ""; + switch (day) { + case SATURDAY, SUNDAY -> today = "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> today = "workday"; + default -> throw new IllegalArgumentException("Invalid day: " + day.name()); } + return today; + } - String expressionRule1(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY -> "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; - default -> throw new IllegalArgumentException("Invalid day: " + day.name()); - }; + String expressionRule1(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY -> "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; + default -> throw new IllegalArgumentException("Invalid day: " + day.name()); + }; - return today; - } + return today; + } - int expressionRule2(DayOfWeek day) { - int numLetters = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> 6; - case TUESDAY -> 7; - case THURSDAY, SATURDAY -> 8; - case WEDNESDAY -> 9; - }; - return numLetters; - } + int expressionRule2(DayOfWeek day) { + int numLetters = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> 6; + case TUESDAY -> 7; + case THURSDAY, SATURDAY -> 8; + case WEDNESDAY -> 9; + }; + return numLetters; + } + + String expressionLabel(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY: + yield "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: + yield "workday"; + default: + throw new IllegalArgumentException("Invalid day: " + day.name()); + }; + + return today; + } + + String expressionLabelBlock(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY: + yield "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: + { + var kind = "workday"; + yield kind; + } + default: + { + var kind = day.name(); + System.out.println(kind); + throw new IllegalArgumentException("Invalid day: " + kind); + } + }; - String expressionLabel(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY: - yield "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: - yield "workday"; - default: - throw new IllegalArgumentException("Invalid day: " + day.name()); - }; - - return today; - } + return today; + } - String expressionLabelBlock(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY: - yield "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: - { - var kind = "workday"; - yield kind; - } - default: - { - var kind = day.name(); - System.out.println(kind); - throw new IllegalArgumentException("Invalid day: " + kind); - } - }; - - return today; + String expressionRuleYield(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY -> "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; + default -> { + var len = day.name().length(); + yield len; + } + }; + return today.toString(); + } + + void fallthrough1(int myInt) { + switch (myInt) { + case 1 -> System.out.println("One"); + case 2 -> { + System.out.println("Two"); + System.out.println("No fall through"); + } + default -> System.out.println("Something else"); } - - String expressionRuleYield(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY -> "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; - default -> { - var len = day.name().length(); - yield len; - } - }; - return today.toString(); + } + + void fallthrough2(int myInt) { + switch (myInt) { + case 1: + System.out.println("One"); + break; + case 2: + System.out.println("Two"); + System.out.println("Falling through"); + default: + System.out.println("Something else"); } - - void fallthrough1(int myInt) { - switch (myInt) { - case 1 -> System.out.println("One"); - case 2 -> { - System.out.println("Two"); - System.out.println("No fall through"); - } - default -> System.out.println("Something else"); - } + } + + void fallthrough3(int myInt) { + switch (myInt) { + case 1, 2, 3: + System.out.println("One, two or three"); + break; + default: + System.out.println("Something else"); } - - void fallthrough2(int myInt) { - switch (myInt) { - case 1: - System.out.println("One"); - break; - case 2: - System.out.println("Two"); - System.out.println("Falling through"); - default: - System.out.println("Something else"); - } + } + + void fallthrough4(int myInt) { + switch (myInt) { + case 1: + System.out.print("Hello "); + // Fall through to next case + case 2: + System.out.println("World"); + break; + default: + System.out.println("Not executed"); } - - void fallthrough3(int myInt) { - switch (myInt) { - case 1, 2, 3: - System.out.println("One, two or three"); - break; - default: - System.out.println("Something else"); + } + + int multipleLabels(int month, int year) { + int numDays = 0; + + switch (month) { + case 1, 3, 5, 7, 8, 10, 12 -> { + numDays = 31; + } + case 4, 6, 9, 11 -> { + numDays = 30; + } + case 2 -> { + if (((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0)) { + numDays = 29; + } else { + numDays = 28; } + } + default -> { + System.out.println("Invalid month."); + } } + return numDays; + } - void fallthrough4(int myInt) { - switch (myInt) { - case 1: - System.out.print("Hello "); - // Fall through to next case - case 2: - System.out.println("World"); - break; - default: - System.out.println("Not executed"); - } + void switchStatementsWithExpression(int selector) { + switch (selector) { + case 1 -> "hello".toString(); + default -> "goodbye".toString(); } - - int multipleLabels(int month, int year) { - int numDays = 0; - - switch (month) { - case 1, 3, 5, 7, 8, 10, 12 -> { - numDays = 31; - } - case 4, 6, 9, 11 -> { - numDays = 30; - } - case 2 -> { - if (((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0)) { - numDays = 29; - } else { - numDays = 28; - } - } - default -> { - System.out.println("Invalid month."); - } - } - return numDays; - } - - void switchStatementsWithExpression(int selector) { - switch (selector) { - case 1 -> "hello".toString(); - default -> "goodbye".toString(); - } - switch (selector) { - } + switch (selector) { } + } } diff --git a/framework/tests/all-systems/java21/Issue6173.java b/framework/tests/all-systems/java21/Issue6173.java index 46e52e81f72..687b90d0dd3 100644 --- a/framework/tests/all-systems/java21/Issue6173.java +++ b/framework/tests/all-systems/java21/Issue6173.java @@ -9,20 +9,20 @@ public class Issue6173 { - static Object toGroupByQueryWithExtractor2(@Nullable GroupBy groupBy) { - return switch (groupBy) { - case OWNER -> new Object(); - case CHANNEL -> new Object(); - case TOPIC -> new Object(); - case TEAM -> new Object(); - case null -> throw new IllegalArgumentException("GroupBy parameter is required"); - }; - } + static Object toGroupByQueryWithExtractor2(@Nullable GroupBy groupBy) { + return switch (groupBy) { + case OWNER -> new Object(); + case CHANNEL -> new Object(); + case TOPIC -> new Object(); + case TEAM -> new Object(); + case null -> throw new IllegalArgumentException("GroupBy parameter is required"); + }; + } - public enum GroupBy { - OWNER, - CHANNEL, - TOPIC, - TEAM; - } + public enum GroupBy { + OWNER, + CHANNEL, + TOPIC, + TEAM; + } } diff --git a/framework/tests/all-systems/java21/JEP440.java b/framework/tests/all-systems/java21/JEP440.java index 50ba17dd312..707bb176fe1 100644 --- a/framework/tests/all-systems/java21/JEP440.java +++ b/framework/tests/all-systems/java21/JEP440.java @@ -13,63 +13,63 @@ @SuppressWarnings("i18n") // true postives. public class JEP440 { - record Point(int x, int y) {} + record Point(int x, int y) {} - static void printSum(Object obj) { - if (obj instanceof Point(int x, int y)) { - System.out.println(x + y); - } + static void printSum(Object obj) { + if (obj instanceof Point(int x, int y)) { + System.out.println(x + y); } + } - enum Color { - RED, - GREEN, - BLUE - } + enum Color { + RED, + GREEN, + BLUE + } - record ColoredPoint(Point p, Color c) {} + record ColoredPoint(Point p, Color c) {} - record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} + record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} - static void printUpperLeftColoredPoint(Rectangle r) { - if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { - System.out.println(ul.c()); - } + static void printUpperLeftColoredPoint(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { + System.out.println(ul.c()); } + } - static void printColorOfUpperLeftPoint(Rectangle r) { - if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) { - System.out.println(c); - } + static void printColorOfUpperLeftPoint(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) { + System.out.println(c); } + } - static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) { - if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) { - System.out.println("Upper-left corner: " + x); - } + static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) { + System.out.println("Upper-left corner: " + x); } + } - void failToMatch() { - record Pair(Object x, Object y) {} - Pair p = new Pair(42, 42); - if (p instanceof Pair(String s, String t)) { - System.out.println(s + ", " + t); - } else { - System.out.println("Not a pair of strings"); - } + void failToMatch() { + record Pair(Object x, Object y) {} + Pair p = new Pair(42, 42); + if (p instanceof Pair(String s, String t)) { + System.out.println(s + ", " + t); + } else { + System.out.println("Not a pair of strings"); } + } - record Box(T t) {} + record Box(T t) {} - static void test1(Box> bbs) { - if (bbs instanceof Box>(Box(var s))) { - System.out.println("String " + s); - } + static void test1(Box> bbs) { + if (bbs instanceof Box>(Box(var s))) { + System.out.println("String " + s); } + } - static void test2(Box> bbs) { - if (bbs instanceof Box(Box(var s))) { - System.out.println("String " + s); - } + static void test2(Box> bbs) { + if (bbs instanceof Box(Box(var s))) { + System.out.println("String " + s); } + } } diff --git a/framework/tests/all-systems/java21/JEP441.java b/framework/tests/all-systems/java21/JEP441.java index b15a3559c3a..9711b7f4e40 100644 --- a/framework/tests/all-systems/java21/JEP441.java +++ b/framework/tests/all-systems/java21/JEP441.java @@ -14,240 +14,240 @@ @SuppressWarnings({"i18n"}) // true postives. public class JEP441 { - // JEP 441 enhances switch statements and expressions in four ways: - // * Improve enum constant case labels - // * Extend case labels to include patterns and null in addition to constants - // * Broaden the range of types permitted for the selector expressions of both switch - // statements and switch expressions (along with the required richer analysis of - // exhaustiveness of switch blocks) - // * Allow optional when clauses to follow case labels. - - // Prior to Java 21 - static String formatter(Object obj) { - String formatted = "unknown"; - if (obj instanceof Integer i) { - formatted = String.format("int %d", i); - } else if (obj instanceof Long l) { - formatted = String.format("long %d", l); - } else if (obj instanceof Double d) { - formatted = String.format("double %f", d); - } else if (obj instanceof String s) { - formatted = String.format("String %s", s); - } - return formatted; - } - - static void formatterPatternSwitchStatement(Object obj) { - switch (obj) { - case Integer i: - String.format("int %d", i); - break; - case Long l: - String.format("long %d", l); - break; - case Double d: - String.format("double %f", d); - break; - case String s: - String.format("String %s", s); - break; - default: - obj.toString(); - } - } - - static String formatterPatternSwitch(Object obj) { - return switch (obj) { - case Integer i -> String.format("int %d", i); - case Long l -> String.format("long %d", l); - case Double d -> String.format("double %f", d); - case String s -> String.format("String %s", s); - default -> obj.toString(); - }; - } - - // As of Java 21 - static void testFooBarNew(@Nullable String s) { - switch (s) { - case null -> System.out.println("Oops"); - case "Foo", "Bar" -> System.out.println("Great"); - default -> System.out.println("Ok"); - } - } - - static void testStringOld(@Nullable String response) { - switch (response) { - case null -> {} - case String s -> { - if (s.equalsIgnoreCase("YES")) System.out.println("You got it"); - else if (s.equalsIgnoreCase("NO")) System.out.println("Shame"); - else System.out.println("Sorry?"); - } - } - } - - static void testStringNew(@Nullable String response) { - switch (response) { - case null -> {} - case String s when s.equalsIgnoreCase("YES") -> { - System.out.println("You got it"); - } - case String s when s.equalsIgnoreCase("NO") -> { - System.out.println("Shame"); - } - case String s -> { - System.out.println("Sorry?"); - } - } - } - - static void testStringEnhanced(@Nullable String response) { - switch (response) { - case null -> {} - case "y", "Y" -> { - System.out.println("You got it"); - } - case "n", "N" -> { - System.out.println("Shame"); - } - case String s when s.equalsIgnoreCase("YES") -> { - System.out.println("You got it"); - } - case String s when s.equalsIgnoreCase("NO") -> { - System.out.println("Shame"); - } - case String s -> { - System.out.println("Sorry?"); - } - } - } - - sealed interface CardClassification permits Suit, Tarot {} - - public enum Suit implements CardClassification { - CLUBS, - DIAMONDS, - HEARTS, - SPADES - } - - final class Tarot implements CardClassification {} - - static void exhaustiveSwitchWithoutEnumSupport(CardClassification c) { - switch (c) { - case Suit s when s == Suit.CLUBS -> { - System.out.println("It's clubs"); - } - case Suit s when s == Suit.DIAMONDS -> { - System.out.println("It's diamonds"); - } - case Suit s when s == Suit.HEARTS -> { - System.out.println("It's hearts"); - } - case Suit s -> { - System.out.println("It's spades"); - } - case Tarot t -> { - System.out.println("It's a tarot"); - } - } - } - - static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) { - switch (c) { - case Suit.CLUBS -> { - System.out.println("It's clubs"); - } - case Suit.DIAMONDS -> { - System.out.println("It's diamonds"); - } - case Suit.HEARTS -> { - System.out.println("It's hearts"); - } - case Suit.SPADES -> { - System.out.println("It's spades"); - } - case Tarot t -> { - System.out.println("It's a tarot"); - } - } - } - - sealed interface Currency permits Coin {} - - enum Coin implements Currency { - HEADS, - TAILS - } - - static void goodEnumSwitch1(Currency c) { - switch (c) { - case Coin.HEADS -> { // Qualified name of enum constant as a label - System.out.println("Heads"); - } - case Coin.TAILS -> { - System.out.println("Tails"); - } - } - } - - static void goodEnumSwitch2(Coin c) { - switch (c) { - case HEADS -> { - System.out.println("Heads"); - } - case Coin.TAILS -> { // Unnecessary qualification but allowed - System.out.println("Tails"); - } - } - } - - record Point(int i, int j) {} - - enum Color { - RED, - GREEN, - BLUE; - } - - static void typeTester(@Nullable Object obj) { - switch (obj) { - case null -> System.out.println("null"); - case String s -> System.out.println("String"); - case Color c -> System.out.println("Color: " + c.toString()); - case Point p -> System.out.println("Record class: " + p.toString()); - case int[] ia -> System.out.println("Array of ints of length" + ia.length); - default -> System.out.println("Something else"); - } - } - - static void first(Object obj) { - switch (obj) { - case String s -> System.out.println("A string: " + s); - case CharSequence cs -> System.out.println("A sequence of length " + cs.length()); - default -> { - break; - } - } - } - - record MyPair(S fst, T snd) {} - - static void recordInference(MyPair pair) { - switch (pair) { - case MyPair(var f, var s) -> { - String ff = f; - Integer ss = s; - } - } - } - - void fragment(Integer i) { - // TODO: This would be a good test case for the Value Checker. - // switch (i) { - // case -1, 1 -> ... // Special cases - // case Integer j when j > 0 -> ... // Positive integer cases - // case Integer j -> ... // All the remaining integers - // } - } + // JEP 441 enhances switch statements and expressions in four ways: + // * Improve enum constant case labels + // * Extend case labels to include patterns and null in addition to constants + // * Broaden the range of types permitted for the selector expressions of both switch + // statements and switch expressions (along with the required richer analysis of + // exhaustiveness of switch blocks) + // * Allow optional when clauses to follow case labels. + + // Prior to Java 21 + static String formatter(Object obj) { + String formatted = "unknown"; + if (obj instanceof Integer i) { + formatted = String.format("int %d", i); + } else if (obj instanceof Long l) { + formatted = String.format("long %d", l); + } else if (obj instanceof Double d) { + formatted = String.format("double %f", d); + } else if (obj instanceof String s) { + formatted = String.format("String %s", s); + } + return formatted; + } + + static void formatterPatternSwitchStatement(Object obj) { + switch (obj) { + case Integer i: + String.format("int %d", i); + break; + case Long l: + String.format("long %d", l); + break; + case Double d: + String.format("double %f", d); + break; + case String s: + String.format("String %s", s); + break; + default: + obj.toString(); + } + } + + static String formatterPatternSwitch(Object obj) { + return switch (obj) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + default -> obj.toString(); + }; + } + + // As of Java 21 + static void testFooBarNew(@Nullable String s) { + switch (s) { + case null -> System.out.println("Oops"); + case "Foo", "Bar" -> System.out.println("Great"); + default -> System.out.println("Ok"); + } + } + + static void testStringOld(@Nullable String response) { + switch (response) { + case null -> {} + case String s -> { + if (s.equalsIgnoreCase("YES")) System.out.println("You got it"); + else if (s.equalsIgnoreCase("NO")) System.out.println("Shame"); + else System.out.println("Sorry?"); + } + } + } + + static void testStringNew(@Nullable String response) { + switch (response) { + case null -> {} + case String s when s.equalsIgnoreCase("YES") -> { + System.out.println("You got it"); + } + case String s when s.equalsIgnoreCase("NO") -> { + System.out.println("Shame"); + } + case String s -> { + System.out.println("Sorry?"); + } + } + } + + static void testStringEnhanced(@Nullable String response) { + switch (response) { + case null -> {} + case "y", "Y" -> { + System.out.println("You got it"); + } + case "n", "N" -> { + System.out.println("Shame"); + } + case String s when s.equalsIgnoreCase("YES") -> { + System.out.println("You got it"); + } + case String s when s.equalsIgnoreCase("NO") -> { + System.out.println("Shame"); + } + case String s -> { + System.out.println("Sorry?"); + } + } + } + + sealed interface CardClassification permits Suit, Tarot {} + + public enum Suit implements CardClassification { + CLUBS, + DIAMONDS, + HEARTS, + SPADES + } + + final class Tarot implements CardClassification {} + + static void exhaustiveSwitchWithoutEnumSupport(CardClassification c) { + switch (c) { + case Suit s when s == Suit.CLUBS -> { + System.out.println("It's clubs"); + } + case Suit s when s == Suit.DIAMONDS -> { + System.out.println("It's diamonds"); + } + case Suit s when s == Suit.HEARTS -> { + System.out.println("It's hearts"); + } + case Suit s -> { + System.out.println("It's spades"); + } + case Tarot t -> { + System.out.println("It's a tarot"); + } + } + } + + static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) { + switch (c) { + case Suit.CLUBS -> { + System.out.println("It's clubs"); + } + case Suit.DIAMONDS -> { + System.out.println("It's diamonds"); + } + case Suit.HEARTS -> { + System.out.println("It's hearts"); + } + case Suit.SPADES -> { + System.out.println("It's spades"); + } + case Tarot t -> { + System.out.println("It's a tarot"); + } + } + } + + sealed interface Currency permits Coin {} + + enum Coin implements Currency { + HEADS, + TAILS + } + + static void goodEnumSwitch1(Currency c) { + switch (c) { + case Coin.HEADS -> { // Qualified name of enum constant as a label + System.out.println("Heads"); + } + case Coin.TAILS -> { + System.out.println("Tails"); + } + } + } + + static void goodEnumSwitch2(Coin c) { + switch (c) { + case HEADS -> { + System.out.println("Heads"); + } + case Coin.TAILS -> { // Unnecessary qualification but allowed + System.out.println("Tails"); + } + } + } + + record Point(int i, int j) {} + + enum Color { + RED, + GREEN, + BLUE; + } + + static void typeTester(@Nullable Object obj) { + switch (obj) { + case null -> System.out.println("null"); + case String s -> System.out.println("String"); + case Color c -> System.out.println("Color: " + c.toString()); + case Point p -> System.out.println("Record class: " + p.toString()); + case int[] ia -> System.out.println("Array of ints of length" + ia.length); + default -> System.out.println("Something else"); + } + } + + static void first(Object obj) { + switch (obj) { + case String s -> System.out.println("A string: " + s); + case CharSequence cs -> System.out.println("A sequence of length " + cs.length()); + default -> { + break; + } + } + } + + record MyPair(S fst, T snd) {} + + static void recordInference(MyPair pair) { + switch (pair) { + case MyPair(var f, var s) -> { + String ff = f; + Integer ss = s; + } + } + } + + void fragment(Integer i) { + // TODO: This would be a good test case for the Value Checker. + // switch (i) { + // case -1, 1 -> ... // Special cases + // case Integer j when j > 0 -> ... // Positive integer cases + // case Integer j -> ... // All the remaining integers + // } + } } diff --git a/framework/tests/all-systems/java8/DefaultMethods.java b/framework/tests/all-systems/java8/DefaultMethods.java index 916b868109d..5aab68f7fd1 100644 --- a/framework/tests/all-systems/java8/DefaultMethods.java +++ b/framework/tests/all-systems/java8/DefaultMethods.java @@ -1,17 +1,17 @@ interface DefaultMethods { - // Test that abstract methods are still ignored. - void abstractMethod(); + // Test that abstract methods are still ignored. + void abstractMethod(); - default String method(String s) { - return s.toString(); - } + default String method(String s) { + return s.toString(); + } } interface DefaultMethods2 extends DefaultMethods { - @Override - default String method(String s) { - return s; - } + @Override + default String method(String s) { + return s; + } } diff --git a/framework/tests/all-systems/java8/lambda/Issue1681.java b/framework/tests/all-systems/java8/lambda/Issue1681.java index bcbf9a39382..fed088288aa 100644 --- a/framework/tests/all-systems/java8/lambda/Issue1681.java +++ b/framework/tests/all-systems/java8/lambda/Issue1681.java @@ -2,18 +2,18 @@ // https://github.com/typetools/checker-framework/issues/1681 public class Issue1681 { - @FunctionalInterface - interface StrReturn { - String op(); - } + @FunctionalInterface + interface StrReturn { + String op(); + } - StrReturn[] v1 = new StrReturn[] {() -> "hello"}; - StrReturn[] v2 = new StrReturn[] {() -> "hello", () -> "world"}; - StrReturn[] v3 = {() -> "test"}; + StrReturn[] v1 = new StrReturn[] {() -> "hello"}; + StrReturn[] v2 = new StrReturn[] {() -> "hello", () -> "world"}; + StrReturn[] v3 = {() -> "test"}; - void foo(StrReturn[] p) {} + void foo(StrReturn[] p) {} - void bar() { - foo(new StrReturn[] {() -> "test", () -> "boo"}); - } + void bar() { + foo(new StrReturn[] {() -> "test", () -> "boo"}); + } } diff --git a/framework/tests/all-systems/java8/lambda/Issue1817.java b/framework/tests/all-systems/java8/lambda/Issue1817.java index ac73fc903e2..68d7d21bc1a 100644 --- a/framework/tests/all-systems/java8/lambda/Issue1817.java +++ b/framework/tests/all-systems/java8/lambda/Issue1817.java @@ -6,9 +6,9 @@ @SuppressWarnings("all") // only check for crashes public class Issue1817 { - { - Consumer> c = values -> values.forEach(value -> f(value)); - } + { + Consumer> c = values -> values.forEach(value -> f(value)); + } - void f(Object o) {} + void f(Object o) {} } diff --git a/framework/tests/all-systems/java8/lambda/Issue450.java b/framework/tests/all-systems/java8/lambda/Issue450.java index d6bf95927cc..a73d3f85634 100644 --- a/framework/tests/all-systems/java8/lambda/Issue450.java +++ b/framework/tests/all-systems/java8/lambda/Issue450.java @@ -1,37 +1,37 @@ public class Issue450 { - Issue450(int i, Runnable... runnables) {} + Issue450(int i, Runnable... runnables) {} - Issue450(Consumer consumer) { - consumer.consume("hello"); // Use lambda as a constructor argument - } + Issue450(Consumer consumer) { + consumer.consume("hello"); // Use lambda as a constructor argument + } - interface Top { - public void consume(String s); - } + interface Top { + public void consume(String s); + } - interface Sub extends Top { - public default void otherMethod() {} - } + interface Sub extends Top { + public default void otherMethod() {} + } - interface Consumer { - void consume(T t); - } + interface Consumer { + void consume(T t); + } - void varargs(Runnable... runnables) {} + void varargs(Runnable... runnables) {} - public static void consumeStr(String str) {} + public static void consumeStr(String str) {} - public static void consumeStr2(String str) {} + public static void consumeStr2(String str) {} - > void context(E e, Sub s) { - new Issue450(Issue450::consumeStr); + > void context(E e, Sub s) { + new Issue450(Issue450::consumeStr); - Consumer cs1 = (false) ? Issue450::consumeStr2 : Issue450::consumeStr; - Consumer cs2 = (false) ? e : Issue450::consumeStr; - Top t = (false) ? s : Issue450::consumeStr; + Consumer cs1 = (false) ? Issue450::consumeStr2 : Issue450::consumeStr; + Consumer cs2 = (false) ? e : Issue450::consumeStr; + Top t = (false) ? s : Issue450::consumeStr; - new Issue450(42, new Thread()::start); // Use lambda as a constructor argument - varargs(new Thread()::start, new Thread()::start); // Use lambda in a var arg list of method - } + new Issue450(42, new Thread()::start); // Use lambda as a constructor argument + varargs(new Thread()::start, new Thread()::start); // Use lambda in a var arg list of method + } } diff --git a/framework/tests/all-systems/java8/lambda/Issue573.java b/framework/tests/all-systems/java8/lambda/Issue573.java index f9952c51256..c3572aed67f 100644 --- a/framework/tests/all-systems/java8/lambda/Issue573.java +++ b/framework/tests/all-systems/java8/lambda/Issue573.java @@ -8,9 +8,9 @@ import java.util.Comparator; public abstract class Issue573 implements Chronology { - Object o = - (Comparator> & Serializable) - (dateTime1, dateTime2) -> { - return 0; - }; + Object o = + (Comparator> & Serializable) + (dateTime1, dateTime2) -> { + return 0; + }; } diff --git a/framework/tests/all-systems/java8/lambda/Lambda.java b/framework/tests/all-systems/java8/lambda/Lambda.java index bfa46844f41..384cca6f4ae 100644 --- a/framework/tests/all-systems/java8/lambda/Lambda.java +++ b/framework/tests/all-systems/java8/lambda/Lambda.java @@ -2,95 +2,94 @@ // Test file for lambda syntax interface Supplier { - R supply(); + R supply(); } interface Function { - R apply(T t); + R apply(T t); } interface Consumer { - void consume(T t); + void consume(T t); } interface BiFunction { - R apply(T t, U u); + R apply(T t, U u); } interface Noop { - void noop(); + void noop(); } public class Lambda { - public static void consumeStr(String str) {} + public static void consumeStr(String str) {} - Lambda(Consumer consumer) { - consumer.consume("hello"); - } + Lambda(Consumer consumer) { + consumer.consume("hello"); + } - // No parameters; result is void - Noop f1 = () -> {}; - // No parameters, expression body - Supplier f2 = () -> 42; - // No parameters, expression body - // Supplier f3 = () -> null; - // No parameters, block body with return - Supplier f4 = - () -> { - return 42; - }; - Noop f5 = - () -> { - System.gc(); - }; // No parameters, void block body + // No parameters; result is void + Noop f1 = () -> {}; + // No parameters, expression body + Supplier f2 = () -> 42; + // No parameters, expression body + // Supplier f3 = () -> null; + // No parameters, block body with return + Supplier f4 = + () -> { + return 42; + }; + Noop f5 = + () -> { + System.gc(); + }; // No parameters, void block body - // Complex block body with returns - Supplier f6 = - () -> { - if (true) { - return 12; - } else { - int result = 15; - for (int i = 1; i < 10; i++) { - result *= i; - } - // conditional expression - Consumer consumer = - result > 100 ? Lambda::consumeStr : Lambda::consumeStr; - return result; - } - }; + // Complex block body with returns + Supplier f6 = + () -> { + if (true) { + return 12; + } else { + int result = 15; + for (int i = 1; i < 10; i++) { + result *= i; + } + // conditional expression + Consumer consumer = result > 100 ? Lambda::consumeStr : Lambda::consumeStr; + return result; + } + }; - // Single declared-type parameter - Function f7 = (Integer x) -> x + 1; - // Single declared-type parameter - Function f9 = - (Integer x) -> { - return (Integer) x + 1; - }; - // Single inferred-type parameter - Function f10 = (x) -> x + 1; - // Parentheses optional for single inferred-type parameter - Function f11 = x -> x + 1; + // Single declared-type parameter + Function f7 = (Integer x) -> x + 1; + // Single declared-type parameter + Function f9 = + (Integer x) -> { + return (Integer) x + 1; + }; + // Single inferred-type parameter + Function f10 = (x) -> x + 1; + // Parentheses optional for single inferred-type parameter + Function f11 = x -> x + 1; - // Single declared-type parameter - Function f12 = (String s) -> s.length(); - // Single declared-type parameter - Consumer f13 = - (Thread t) -> { - t.start(); - }; - // Single inferred-type parameter - Consumer f14 = s -> s.length(); - // Single inferred-type parameter - Consumer f15 = - t -> { - t.start(); - }; + // Single declared-type parameter + Function f12 = (String s) -> s.length(); + // Single declared-type parameter + Consumer f13 = + (Thread t) -> { + t.start(); + }; + // Single inferred-type parameter + Consumer f14 = s -> s.length(); + // Single inferred-type parameter + Consumer f15 = + t -> { + t.start(); + }; - // Multiple declared-type parameters - BiFunction f16 = (Integer x, final Integer y) -> x + y; - // Multiple inferred-type parameters - BiFunction f18 = (x, y) -> x + y; + // Multiple declared-type parameters + BiFunction f16 = (Integer x, final Integer y) -> x + y; + // Multiple inferred-type parameters + BiFunction f18 = (x, y) -> x + y; } diff --git a/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java b/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java index c6256bb9de0..45c69bbbd68 100644 --- a/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java +++ b/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java @@ -1,21 +1,21 @@ interface FunctionAC { - String apply(String s); + String apply(String s); } public class AssignmentContextFunction { - // Test assign - FunctionAC f1 = String::toString; + // Test assign + FunctionAC f1 = String::toString; - // Test casts - Object o1 = (Object) (FunctionAC) String::toString; + // Test casts + Object o1 = (Object) (FunctionAC) String::toString; - void take(FunctionAC f) { - // Test argument assingment - take(String::toString); - } + void take(FunctionAC f) { + // Test argument assingment + take(String::toString); + } - FunctionAC supply() { - // Test return assingment - return String::toString; - } + FunctionAC supply() { + // Test return assingment + return String::toString; + } } diff --git a/framework/tests/all-systems/java8/memberref/FromByteCode.java b/framework/tests/all-systems/java8/memberref/FromByteCode.java index ec564389793..786bf433318 100644 --- a/framework/tests/all-systems/java8/memberref/FromByteCode.java +++ b/framework/tests/all-systems/java8/memberref/FromByteCode.java @@ -1,7 +1,7 @@ interface FunctionFromByteCode { - R apply(T t); + R apply(T t); } public class FromByteCode { - FunctionFromByteCode f1 = String::toString; + FunctionFromByteCode f1 = String::toString; } diff --git a/framework/tests/all-systems/java8/memberref/Issue871.java b/framework/tests/all-systems/java8/memberref/Issue871.java index 593ce4a80a3..b5c12de5f60 100644 --- a/framework/tests/all-systems/java8/memberref/Issue871.java +++ b/framework/tests/all-systems/java8/memberref/Issue871.java @@ -5,9 +5,9 @@ import java.util.function.Predicate; interface Issue871 { - default Iterable a() { - return f(Files::isRegularFile); - } + default Iterable a() { + return f(Files::isRegularFile); + } - Iterable f(Predicate condition); + Iterable f(Predicate condition); } diff --git a/framework/tests/all-systems/java8/memberref/Issue946.java b/framework/tests/all-systems/java8/memberref/Issue946.java index cdc5d352917..09417293d95 100644 --- a/framework/tests/all-systems/java8/memberref/Issue946.java +++ b/framework/tests/all-systems/java8/memberref/Issue946.java @@ -2,37 +2,37 @@ // https://github.com/typetools/checker-framework/issues/946 interface Supply946 { - R supply(); + R supply(); } public class Issue946 { - class MethodRefInnerA { - Supply946 constructorReferenceField = MethodRefInnerB::new; - - MethodRefInnerA(Issue946 Issue946.this) { - Supply946 constructorReference = MethodRefInnerB::new; - } - - void method() { - Supply946 constructorReference = MethodRefInnerB::new; - } - - class MethodRefInnerAInner { - void method() { - Supply946 constructorReference = MethodRefInnerB::new; - } - } + class MethodRefInnerA { + Supply946 constructorReferenceField = MethodRefInnerB::new; + + MethodRefInnerA(Issue946 Issue946.this) { + Supply946 constructorReference = MethodRefInnerB::new; } - class MethodRefInnerB { - MethodRefInnerB(Issue946 Issue946.this) {} + void method() { + Supply946 constructorReference = MethodRefInnerB::new; + } - void method() { - Supply946 constructorReference = MethodRefInnerB::new; - } + class MethodRefInnerAInner { + void method() { + Supply946 constructorReference = MethodRefInnerB::new; + } } + } + + class MethodRefInnerB { + MethodRefInnerB(Issue946 Issue946.this) {} void method() { - Supply946 constructorReference = MethodRefInnerB::new; + Supply946 constructorReference = MethodRefInnerB::new; } + } + + void method() { + Supply946 constructorReference = MethodRefInnerB::new; + } } diff --git a/framework/tests/all-systems/java8/memberref/MemberReferences.java b/framework/tests/all-systems/java8/memberref/MemberReferences.java index ab2480150ba..190ebaadb98 100644 --- a/framework/tests/all-systems/java8/memberref/MemberReferences.java +++ b/framework/tests/all-systems/java8/memberref/MemberReferences.java @@ -2,184 +2,184 @@ // "TODO: Issue 802", will issue a methodref.inference.unimplemented warning. interface Supplier { - R supply(); + R supply(); } interface FunctionMR { - R apply(T t); + R apply(T t); } interface Consumer { - void consume(T t); + void consume(T t); } interface BiFunctionMR { - R apply(T t, U u); + R apply(T t, U u); } /** super # instMethod */ // SUPER(ReferenceMode.INVOKE, false), class Super { - Object func1(Object o) { - return o; - } + Object func1(Object o) { + return o; + } - T func2(T o) { - return o; - } + T func2(T o) { + return o; + } - class Sub extends Super { - void context() { - FunctionMR f1 = super::func1; - // TODO: Issue 802: type argument inference - FunctionMR f2 = super::func2; - // Top level wildcards are ignored when type checking - FunctionMR f3 = super::func2; - } + class Sub extends Super { + void context() { + FunctionMR f1 = super::func1; + // TODO: Issue 802: type argument inference + FunctionMR f2 = super::func2; + // Top level wildcards are ignored when type checking + FunctionMR f3 = super::func2; } + } } class SuperWithArg { - void func1(U o) {} + void func1(U o) {} - class Sub extends SuperWithArg { - void context() { - Consumer f1 = super::func1; - } + class Sub extends SuperWithArg { + void context() { + Consumer f1 = super::func1; } + } } /** Type # instMethod. */ // UNBOUNDED(ReferenceMode.INVOKE, true), class Unbound { - T func1(T o) { - return o; - } + T func1(T o) { + return o; + } - void context() { - FunctionMR f1 = String::toString; - // TODO: Issue 802: type argument inference - BiFunctionMR f2 = Unbound::func1; - @SuppressWarnings("nullness:type.argument.type.incompatible") - BiFunctionMR f3 = - Unbound::func1; - } + void context() { + FunctionMR f1 = String::toString; + // TODO: Issue 802: type argument inference + BiFunctionMR f2 = Unbound::func1; + @SuppressWarnings("nullness:type.argument.type.incompatible") + BiFunctionMR f3 = + Unbound::func1; + } } abstract class UnboundWithArg { - abstract U func1(); + abstract U func1(); - void context() { - // TODO: Issue 802: type argument inference - FunctionMR, String> f1 = UnboundWithArg::func1; - FunctionMR, String> f2 = UnboundWithArg::func1; - // TODO: Issue 802: type argument inference - FunctionMR, String> f3 = UnboundWithArg::func1; - FunctionMR, String> f4 = UnboundWithArg::func1; - } + void context() { + // TODO: Issue 802: type argument inference + FunctionMR, String> f1 = UnboundWithArg::func1; + FunctionMR, String> f2 = UnboundWithArg::func1; + // TODO: Issue 802: type argument inference + FunctionMR, String> f3 = UnboundWithArg::func1; + FunctionMR, String> f4 = UnboundWithArg::func1; + } } /** Type # staticMethod. */ // STATIC(ReferenceMode.INVOKE, false), class Static { - static T func1(T o) { - return o; - } + static T func1(T o) { + return o; + } - void context() { - // TODO: Issue 802: type argument inference - FunctionMR f1 = Static::func1; - FunctionMR f2 = Static::func1; - } + void context() { + // TODO: Issue 802: type argument inference + FunctionMR f1 = Static::func1; + FunctionMR f2 = Static::func1; + } } /** Expr # instMethod. */ // BOUND(ReferenceMode.INVOKE, false), class Bound { - T func1(T o) { - return o; - } + T func1(T o) { + return o; + } - void context(Bound bound) { - // TODO: Issue 802: type argument inference - FunctionMR f1 = bound::func1; - // TODO: Issue 802: type argument inference - FunctionMR f2 = this::func1; - FunctionMR f3 = this::func1; - FunctionMR f4 = this::func1; - } + void context(Bound bound) { + // TODO: Issue 802: type argument inference + FunctionMR f1 = bound::func1; + // TODO: Issue 802: type argument inference + FunctionMR f2 = this::func1; + FunctionMR f3 = this::func1; + FunctionMR f4 = this::func1; + } } class BoundWithArg { - void func1(U param) {} + void func1(U param) {} - void context(BoundWithArg bound) { - Consumer f1 = bound::func1; - Consumer f2 = bound::func1; - } + void context(BoundWithArg bound) { + Consumer f1 = bound::func1; + Consumer f2 = bound::func1; + } } /** Inner # new. */ // IMPLICIT_INNER(ReferenceMode.NEW, false), class Outer { - void context(Outer other) { - Supplier f1 = Inner::new; - } + void context(Outer other) { + Supplier f1 = Inner::new; + } - class Inner extends Outer {} + class Inner extends Outer {} } class OuterWithArg { - void context() { - // TODO: Issue 802: type argument inference - Supplier> f1 = Inner::new; - Supplier> f2 = Inner::new; - Supplier> f3 = Inner::new; - } + void context() { + // TODO: Issue 802: type argument inference + Supplier> f1 = Inner::new; + Supplier> f2 = Inner::new; + Supplier> f3 = Inner::new; + } - class Inner extends OuterWithArg {} + class Inner extends OuterWithArg {} } /** Toplevel # new. */ // TOPLEVEL(ReferenceMode.NEW, false), class TopLevel { - TopLevel() {} + TopLevel() {} - TopLevel(T s) {} + TopLevel(T s) {} - void context() { - Supplier f1 = TopLevel::new; - // TODO: Issue 802: type argument inference - FunctionMR f2 = TopLevel::new; - FunctionMR f3 = TopLevel::new; - } + void context() { + Supplier f1 = TopLevel::new; + // TODO: Issue 802: type argument inference + FunctionMR f2 = TopLevel::new; + FunctionMR f3 = TopLevel::new; + } } class TopLevelWithArg { - TopLevelWithArg() {} + TopLevelWithArg() {} - TopLevelWithArg(U s) {} + TopLevelWithArg(U s) {} - void context() { - // TODO: Issue 802: type argument inference - Supplier> f1 = TopLevelWithArg::new; - Supplier> f2 = TopLevelWithArg::new; - FunctionMR> f3 = TopLevelWithArg::new; - } + void context() { + // TODO: Issue 802: type argument inference + Supplier> f1 = TopLevelWithArg::new; + Supplier> f2 = TopLevelWithArg::new; + FunctionMR> f3 = TopLevelWithArg::new; + } } /** ArrayType # new. */ // ARRAY_CTOR(ReferenceMode.NEW, false); class ArrayType { - void context() { - // TODO: Signedness Checker does not default boxed primitives correctly. - // See Issue #797: https://github.com/typetools/checker-framework/issues/797 - @SuppressWarnings({"signedness"}) - FunctionMR string = String[]::new; - FunctionMR clone = String[]::clone; - FunctionMR toString = String[]::toString; - } + void context() { + // TODO: Signedness Checker does not default boxed primitives correctly. + // See Issue #797: https://github.com/typetools/checker-framework/issues/797 + @SuppressWarnings({"signedness"}) + FunctionMR string = String[]::new; + FunctionMR clone = String[]::clone; + FunctionMR toString = String[]::toString; + } } diff --git a/framework/tests/all-systems/java8/memberref/Purity.java b/framework/tests/all-systems/java8/memberref/Purity.java index c0182010c75..e7c7ef91134 100644 --- a/framework/tests/all-systems/java8/memberref/Purity.java +++ b/framework/tests/all-systems/java8/memberref/Purity.java @@ -1,24 +1,24 @@ import org.checkerframework.dataflow.qual.*; interface PureFunc { - @Pure - String doNothing(); + @Pure + String doNothing(); } class TestPure { - static String myMethod() { - return ""; - } + static String myMethod() { + return ""; + } - @Pure - static String myPureMethod() { - return ""; - } + @Pure + static String myPureMethod() { + return ""; + } - void context() { - PureFunc f1 = TestPure::myPureMethod; - // :: error: (purity.invalid.methodref) - PureFunc f2 = TestPure::myMethod; - } + void context() { + PureFunc f1 = TestPure::myPureMethod; + // :: error: (purity.invalid.methodref) + PureFunc f2 = TestPure::myMethod; + } } diff --git a/framework/tests/all-systems/java8/memberref/Receivers.java b/framework/tests/all-systems/java8/memberref/Receivers.java index 53437bdc9f1..d20c39ccccd 100644 --- a/framework/tests/all-systems/java8/memberref/Receivers.java +++ b/framework/tests/all-systems/java8/memberref/Receivers.java @@ -1,53 +1,53 @@ /** BoundR and unbound constraints. */ interface UnboundR { - void consume(/*1*/ UnboundR this, /*2*/ MyClass my, String s); + void consume(/*1*/ UnboundR this, /*2*/ MyClass my, String s); } interface BoundR { - void consume(/*4*/ BoundR this, String s); + void consume(/*4*/ BoundR this, String s); } interface SupplierR { - R supply(); + R supply(); } class MyClass { - void take(/*6*/ MyClass this, String s) {} + void take(/*6*/ MyClass this, String s) {} - void context(/*7*/ MyClass my) { - /*8*/ UnboundR u1 = /*9*/ MyClass::take; - // 2 <: 6 -- like an override - // No relation to 1 or 2 - // No relationship or check for 8 / 9? - // Need to check on this. + void context(/*7*/ MyClass my) { + /*8*/ UnboundR u1 = /*9*/ MyClass::take; + // 2 <: 6 -- like an override + // No relation to 1 or 2 + // No relationship or check for 8 / 9? + // Need to check on this. - u1.consume(my, ""); - // 7 <: 2 - // 8 <: 1 + u1.consume(my, ""); + // 7 <: 2 + // 8 <: 1 - /*10*/ BoundR b1 = /*11*/ my::take; - // 7 <: 6 -- like an invocation - // No Relationship for 10 / 11? + /*10*/ BoundR b1 = /*11*/ my::take; + // 7 <: 6 -- like an invocation + // No Relationship for 10 / 11? - b1.consume(""); - // 10 <: 4 - } + b1.consume(""); + // 10 <: 4 + } } /** Constraints for implicit inner constraints and super. */ @SuppressWarnings("lock") class OuterR { - class Inner { - Inner(/*1*/ OuterR OuterR.this) {} + class Inner { + Inner(/*1*/ OuterR OuterR.this) {} - void context() { - SupplierR o = OuterR.super::toString; - } + void context() { + SupplierR o = OuterR.super::toString; } + } - void context(/*2*/ OuterR this) { - // This one is unbound and needs an OuterR as a param - SupplierR f = /*4*/ Inner::new; - } + void context(/*2*/ OuterR this) { + // This one is unbound and needs an OuterR as a param + SupplierR f = /*4*/ Inner::new; + } } diff --git a/framework/tests/all-systems/java8/memberref/VarArgs.java b/framework/tests/all-systems/java8/memberref/VarArgs.java index 4ed2022712e..9ecc432b38f 100644 --- a/framework/tests/all-systems/java8/memberref/VarArgs.java +++ b/framework/tests/all-systems/java8/memberref/VarArgs.java @@ -1,40 +1,40 @@ interface VarArgsFunc { - void take(String... in); + void take(String... in); } interface ArrayFunc { - void take(String[] in); + void take(String[] in); } class VarArgsTest { - static void myMethod(String... in) {} + static void myMethod(String... in) {} - static void myMethodArray(String[] in) {} + static void myMethodArray(String[] in) {} - VarArgsFunc v1 = VarArgsTest::myMethod; - VarArgsFunc v2 = VarArgsTest::myMethodArray; + VarArgsFunc v1 = VarArgsTest::myMethod; + VarArgsFunc v2 = VarArgsTest::myMethodArray; - ArrayFunc v3 = VarArgsTest::myMethod; - ArrayFunc v4 = VarArgsTest::myMethodArray; + ArrayFunc v3 = VarArgsTest::myMethod; + ArrayFunc v4 = VarArgsTest::myMethodArray; } interface RegularFunc { - void take(Object o); + void take(Object o); } interface RegularFunc2 { - void take(Object o, String s); + void take(Object o, String s); } interface RegularFunc3 { - void take(Object o, String s, String s2); + void take(Object o, String s, String s2); } class MoreVarAgrgsTest { - static void myObjectArgArg(Object o, String... vararg) {} + static void myObjectArgArg(Object o, String... vararg) {} - RegularFunc v1 = MoreVarAgrgsTest::myObjectArgArg; - RegularFunc2 v2 = MoreVarAgrgsTest::myObjectArgArg; - RegularFunc3 v4 = MoreVarAgrgsTest::myObjectArgArg; + RegularFunc v1 = MoreVarAgrgsTest::myObjectArgArg; + RegularFunc2 v2 = MoreVarAgrgsTest::myObjectArgArg; + RegularFunc3 v4 = MoreVarAgrgsTest::myObjectArgArg; } diff --git a/framework/tests/all-systems/java8inference/ArrayInits.java b/framework/tests/all-systems/java8inference/ArrayInits.java index 0893f416f76..c3a0474a915 100644 --- a/framework/tests/all-systems/java8inference/ArrayInits.java +++ b/framework/tests/all-systems/java8inference/ArrayInits.java @@ -3,7 +3,7 @@ import java.util.Arrays; public class ArrayInits { - void method() { - Object[] objects = new Object[] {Arrays.asList(1, 2, 3)}; - } + void method() { + Object[] objects = new Object[] {Arrays.asList(1, 2, 3)}; + } } diff --git a/framework/tests/all-systems/java8inference/Bug1.java b/framework/tests/all-systems/java8inference/Bug1.java index 473ed3f8579..856f0f85b78 100644 --- a/framework/tests/all-systems/java8inference/Bug1.java +++ b/framework/tests/all-systems/java8inference/Bug1.java @@ -5,15 +5,15 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug1 { - @SuppressWarnings("type.inference.not.same") - public void method1(Map, ? extends B> map) { - Map, B> copy = new LinkedHashMap<>(map); - for (Map.Entry, B> entry : copy.entrySet()) { - cast(entry.getKey(), entry.getValue()); - } + @SuppressWarnings("type.inference.not.same") + public void method1(Map, ? extends B> map) { + Map, B> copy = new LinkedHashMap<>(map); + for (Map.Entry, B> entry : copy.entrySet()) { + cast(entry.getKey(), entry.getValue()); } + } - private static T cast(Class type, X value) { - throw new RuntimeException(); - } + private static T cast(Class type, X value) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug10.java b/framework/tests/all-systems/java8inference/Bug10.java index cc48d706049..6b9ad5053f0 100644 --- a/framework/tests/all-systems/java8inference/Bug10.java +++ b/framework/tests/all-systems/java8inference/Bug10.java @@ -6,44 +6,44 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug10 { - public static Collector> least(int k, Comparator comparator) { - return Collector.of( - () -> TopKSelector.least(k, comparator), - TopKSelector::offer, - TopKSelector::combine, - TopKSelector::topK, - Collector.Characteristics.UNORDERED); + public static Collector> least(int k, Comparator comparator) { + return Collector.of( + () -> TopKSelector.least(k, comparator), + TopKSelector::offer, + TopKSelector::combine, + TopKSelector::topK, + Collector.Characteristics.UNORDERED); + } + + static final class TopKSelector { + TopKSelector combine(TopKSelector other) { + throw new RuntimeException(); } - static final class TopKSelector { - TopKSelector combine(TopKSelector other) { - throw new RuntimeException(); - } - - public static > TopKSelector least(int k) { - throw new RuntimeException(); - } + public static > TopKSelector least(int k) { + throw new RuntimeException(); + } - public static > TopKSelector greatest(int k) { - throw new RuntimeException(); - } + public static > TopKSelector greatest(int k) { + throw new RuntimeException(); + } - public static TopKSelector least(int k, Comparator comparator) { - return new TopKSelector(comparator, k); - } + public static TopKSelector least(int k, Comparator comparator) { + return new TopKSelector(comparator, k); + } - public static TopKSelector greatest(int k, Comparator comparator) { - throw new RuntimeException(); - } + public static TopKSelector greatest(int k, Comparator comparator) { + throw new RuntimeException(); + } - private TopKSelector(Comparator comparator, int k) {} + private TopKSelector(Comparator comparator, int k) {} - public void offer(T elem) { - throw new RuntimeException(); - } + public void offer(T elem) { + throw new RuntimeException(); + } - public List topK() { - throw new RuntimeException(); - } + public List topK() { + throw new RuntimeException(); } + } } diff --git a/framework/tests/all-systems/java8inference/Bug11.java b/framework/tests/all-systems/java8inference/Bug11.java index f167c5d66b2..ddc2bfb22af 100644 --- a/framework/tests/all-systems/java8inference/Bug11.java +++ b/framework/tests/all-systems/java8inference/Bug11.java @@ -7,16 +7,16 @@ @SuppressWarnings("all") // Check for crashes. public class Bug11 { - public static MyMap copyOf(Map map) { - @SuppressWarnings("unchecked") - MyMap kvMap = (MyMap) copyOfEnumMap((EnumMap) map); - return kvMap; - } + public static MyMap copyOf(Map map) { + @SuppressWarnings("unchecked") + MyMap kvMap = (MyMap) copyOfEnumMap((EnumMap) map); + return kvMap; + } - private static , V2> MyMap copyOfEnumMap( - EnumMap original) { - throw new RuntimeException(); - } + private static , V2> MyMap copyOfEnumMap( + EnumMap original) { + throw new RuntimeException(); + } - public abstract static class MyMap implements Map, Serializable {} + public abstract static class MyMap implements Map, Serializable {} } diff --git a/framework/tests/all-systems/java8inference/Bug12.java b/framework/tests/all-systems/java8inference/Bug12.java index 0f8fcb5924c..69402ad5299 100644 --- a/framework/tests/all-systems/java8inference/Bug12.java +++ b/framework/tests/all-systems/java8inference/Bug12.java @@ -4,15 +4,14 @@ public class Bug12 { - private static Map getOrCreateNodes( - Map existing, - Map created) { - return firstNonNull(existing, created); - } + private static Map getOrCreateNodes( + Map existing, Map created) { + return firstNonNull(existing, created); + } - public static T firstNonNull(T first, T second) { - throw new RuntimeException(); - } + public static T firstNonNull(T first, T second) { + throw new RuntimeException(); + } - private static class LockGraphNode {} + private static class LockGraphNode {} } diff --git a/framework/tests/all-systems/java8inference/Bug13.java b/framework/tests/all-systems/java8inference/Bug13.java index 4a8ac0aeb04..2f5c99417e8 100644 --- a/framework/tests/all-systems/java8inference/Bug13.java +++ b/framework/tests/all-systems/java8inference/Bug13.java @@ -6,31 +6,31 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug13 { - public static class MyClass extends MySuperClass { - static Stream stream(MyClass s, Iterable iterable) { - throw new RuntimeException(); - } - - public void method(final Iterable iterable) { - Spliterator x = Stream.generate(() -> iterable).flatMap(super::stream).spliterator(); - } + public static class MyClass extends MySuperClass { + static Stream stream(MyClass s, Iterable iterable) { + throw new RuntimeException(); } - public static class MySuperClass { - Stream stream(Iterable iterable) { - throw new RuntimeException(); - } - - static Stream stream(MySuperClass s, Iterable iterable) { - throw new RuntimeException(); - } + public void method(final Iterable iterable) { + Spliterator x = Stream.generate(() -> iterable).flatMap(super::stream).spliterator(); } + } - public static void method(final Iterable iterable, MyClass myClass) { - Spliterator x = Stream.generate(() -> iterable).flatMap(myClass::stream).spliterator(); + public static class MySuperClass { + Stream stream(Iterable iterable) { + throw new RuntimeException(); } - public static Stream stream(Iterable iterable) { - throw new RuntimeException(); + static Stream stream(MySuperClass s, Iterable iterable) { + throw new RuntimeException(); } + } + + public static void method(final Iterable iterable, MyClass myClass) { + Spliterator x = Stream.generate(() -> iterable).flatMap(myClass::stream).spliterator(); + } + + public static Stream stream(Iterable iterable) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug14.java b/framework/tests/all-systems/java8inference/Bug14.java index 5aeac3db099..04ac6bb26d6 100644 --- a/framework/tests/all-systems/java8inference/Bug14.java +++ b/framework/tests/all-systems/java8inference/Bug14.java @@ -7,43 +7,43 @@ @SuppressWarnings({"unchecked", "all"}) public class Bug14 { - private static final Collector> TO_IMMUTABLE_LIST = - Collector.of( - ImmutableList::builder, - ImmutableList.Builder::add, - ImmutableList.Builder::combine, - ImmutableList.Builder::build); - - public abstract static class ImmutableList extends ImmutableCollection - implements List, RandomAccess { - public static final class Builder { - - public Builder add(E element) { - return this; - } - - public Builder add(E... elements) { - return this; - } - - public Builder addAll(Iterator elements) { - return this; - } - - public ImmutableList build() { - throw new RuntimeException(); - } - - Builder combine(Builder builder) { - return this; - } - } - - public static Builder builder() { - return new Builder(); - } + private static final Collector> TO_IMMUTABLE_LIST = + Collector.of( + ImmutableList::builder, + ImmutableList.Builder::add, + ImmutableList.Builder::combine, + ImmutableList.Builder::build); + + public abstract static class ImmutableList extends ImmutableCollection + implements List, RandomAccess { + public static final class Builder { + + public Builder add(E element) { + return this; + } + + public Builder add(E... elements) { + return this; + } + + public Builder addAll(Iterator elements) { + return this; + } + + public ImmutableList build() { + throw new RuntimeException(); + } + + Builder combine(Builder builder) { + return this; + } } - public abstract static class ImmutableCollection extends AbstractCollection - implements Serializable {} + public static Builder builder() { + return new Builder(); + } + } + + public abstract static class ImmutableCollection extends AbstractCollection + implements Serializable {} } diff --git a/framework/tests/all-systems/java8inference/Bug15.java b/framework/tests/all-systems/java8inference/Bug15.java index 14a9be01312..43484fabf2c 100644 --- a/framework/tests/all-systems/java8inference/Bug15.java +++ b/framework/tests/all-systems/java8inference/Bug15.java @@ -4,11 +4,11 @@ @SuppressWarnings("all") // check for crashes public class Bug15 { - public void putAll(Entry, B> entry) { - cast(entry.getKey(), entry.getValue()); - } + public void putAll(Entry, B> entry) { + cast(entry.getKey(), entry.getValue()); + } - private static T cast(Class type, F value) { - throw new RuntimeException(); - } + private static T cast(Class type, F value) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug16.java b/framework/tests/all-systems/java8inference/Bug16.java index 70e3cd1e2ad..c1e368d2952 100644 --- a/framework/tests/all-systems/java8inference/Bug16.java +++ b/framework/tests/all-systems/java8inference/Bug16.java @@ -2,19 +2,19 @@ public class Bug16 { - public interface Interface {} + public interface Interface {} - private static class Implementation implements Interface { - Implementation(Interface delegate, Interface inverse) {} + private static class Implementation implements Interface { + Implementation(Interface delegate, Interface inverse) {} - Implementation(Interface inverse, int o) {} + Implementation(Interface inverse, int o) {} - Implementation(Interface delegate) {} + Implementation(Interface delegate) {} - void test(Interface param) { - Interface inverse1 = new Implementation<>(param, this); - Interface inverse2 = new Implementation<>(param); - Interface inverse3 = new Implementation<>(this, 1); - } + void test(Interface param) { + Interface inverse1 = new Implementation<>(param, this); + Interface inverse2 = new Implementation<>(param); + Interface inverse3 = new Implementation<>(this, 1); } + } } diff --git a/framework/tests/all-systems/java8inference/Bug2.java b/framework/tests/all-systems/java8inference/Bug2.java index 09146939724..933e3f74a1e 100644 --- a/framework/tests/all-systems/java8inference/Bug2.java +++ b/framework/tests/all-systems/java8inference/Bug2.java @@ -6,21 +6,21 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug2 { - public ConcurrentMap makeMap() { - return Bug2.create(this); - } + public ConcurrentMap makeMap() { + return Bug2.create(this); + } - static MyMap, ?> create(Bug2 builder) { - throw new RuntimeException(); - } + static MyMap, ?> create(Bug2 builder) { + throw new RuntimeException(); + } - abstract static class MyMap< - E, F, G extends MyMap.MyEntry, H extends MyMap.MyLock> - extends AbstractMap implements ConcurrentMap, Serializable { + abstract static class MyMap< + E, F, G extends MyMap.MyEntry, H extends MyMap.MyLock> + extends AbstractMap implements ConcurrentMap, Serializable { - interface MyEntry> {} + interface MyEntry> {} - abstract static class MyLock, O extends MyLock> - extends ReentrantLock {} - } + abstract static class MyLock, O extends MyLock> + extends ReentrantLock {} + } } diff --git a/framework/tests/all-systems/java8inference/Bug3.java b/framework/tests/all-systems/java8inference/Bug3.java index 1051e12747c..453da07b7eb 100644 --- a/framework/tests/all-systems/java8inference/Bug3.java +++ b/framework/tests/all-systems/java8inference/Bug3.java @@ -6,23 +6,23 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug3 { - public abstract static class MySet implements Set { + public abstract static class MySet implements Set { - public static MySet of() { - throw new RuntimeException(""); - } - - public static MySet of(E e) { - throw new RuntimeException(""); - } + public static MySet of() { + throw new RuntimeException(""); } - @SuppressWarnings({"rawtypes", "unchecked"}) - static MySet asMySet(EnumSet set) { - return MySet.of(getElement(set)); + public static MySet of(E e) { + throw new RuntimeException(""); } + } - public static T getElement(Iterable iterable) { - throw new RuntimeException(); - } + @SuppressWarnings({"rawtypes", "unchecked"}) + static MySet asMySet(EnumSet set) { + return MySet.of(getElement(set)); + } + + public static T getElement(Iterable iterable) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug4.java b/framework/tests/all-systems/java8inference/Bug4.java index 828055c86ac..1f8d2442c36 100644 --- a/framework/tests/all-systems/java8inference/Bug4.java +++ b/framework/tests/all-systems/java8inference/Bug4.java @@ -6,11 +6,11 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug4 { - Type resolveInternal(TypeVariable var, Type[] types) { - return method(var.getGenericDeclaration(), var.getName(), types); - } + Type resolveInternal(TypeVariable var, Type[] types) { + return method(var.getGenericDeclaration(), var.getName(), types); + } - static TypeVariable method(D d, String n, Type... bounds) { - throw new RuntimeException(); - } + static TypeVariable method(D d, String n, Type... bounds) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug5.java b/framework/tests/all-systems/java8inference/Bug5.java index 8b242783f6c..e609304206b 100644 --- a/framework/tests/all-systems/java8inference/Bug5.java +++ b/framework/tests/all-systems/java8inference/Bug5.java @@ -6,17 +6,17 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug5 { - boolean apply(Object key, V value, MyPredicate> predicate) { - @SuppressWarnings("unchecked") - K k = (K) key; - return predicate.apply(immutableEntry(k, value)); - } + boolean apply(Object key, V value, MyPredicate> predicate) { + @SuppressWarnings("unchecked") + K k = (K) key; + return predicate.apply(immutableEntry(k, value)); + } - public static Map.Entry immutableEntry(K key, V value) { - throw new RuntimeException(); - } + public static Map.Entry immutableEntry(K key, V value) { + throw new RuntimeException(); + } - public interface MyPredicate extends Predicate { - boolean apply(T input); - } + public interface MyPredicate extends Predicate { + boolean apply(T input); + } } diff --git a/framework/tests/all-systems/java8inference/Bug6.java b/framework/tests/all-systems/java8inference/Bug6.java index 473a8922924..9a6e41d6c2f 100644 --- a/framework/tests/all-systems/java8inference/Bug6.java +++ b/framework/tests/all-systems/java8inference/Bug6.java @@ -7,26 +7,26 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug6 { - public static Iterable method(final Iterable iterable) { - return new Iterable() { - @Override - public Iterator iterator() { - throw new RuntimeException(); - } + public static Iterable method(final Iterable iterable) { + return new Iterable() { + @Override + public Iterator iterator() { + throw new RuntimeException(); + } - @Override - public void forEach(Consumer action) { - throw new RuntimeException(); - } + @Override + public void forEach(Consumer action) { + throw new RuntimeException(); + } - @Override - public Spliterator spliterator() { - return Stream.generate(() -> iterable).flatMap(Bug6::stream).spliterator(); - } - }; - } + @Override + public Spliterator spliterator() { + return Stream.generate(() -> iterable).flatMap(Bug6::stream).spliterator(); + } + }; + } - public static Stream stream(Iterable iterable) { - throw new RuntimeException(); - } + public static Stream stream(Iterable iterable) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug7.java b/framework/tests/all-systems/java8inference/Bug7.java index 496b516eca3..d646627a69d 100644 --- a/framework/tests/all-systems/java8inference/Bug7.java +++ b/framework/tests/all-systems/java8inference/Bug7.java @@ -7,33 +7,32 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug7 { - static Collector> toMap( - Function keyFunction, - Function valueFunction) { - return Collector.of( - MyMap.Builder::new, - (builder, input) -> - builder.put(keyFunction.apply(input), valueFunction.apply(input)), - MyMap.Builder::combine, - MyMap.Builder::build); - } + static Collector> toMap( + Function keyFunction, + Function valueFunction) { + return Collector.of( + MyMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + MyMap.Builder::combine, + MyMap.Builder::build); + } - public abstract static class MyMap implements Map, Serializable { - public static class Builder { + public abstract static class MyMap implements Map, Serializable { + public static class Builder { - public Builder() {} + public Builder() {} - public Builder put(K key, V value) { - throw new RuntimeException(); - } + public Builder put(K key, V value) { + throw new RuntimeException(); + } - Builder combine(Builder other) { - throw new RuntimeException(); - } + Builder combine(Builder other) { + throw new RuntimeException(); + } - public MyMap build() { - throw new RuntimeException(); - } - } + public MyMap build() { + throw new RuntimeException(); + } } + } } diff --git a/framework/tests/all-systems/java8inference/Bug8.java b/framework/tests/all-systems/java8inference/Bug8.java index c60497acfa8..7b6104c0348 100644 --- a/framework/tests/all-systems/java8inference/Bug8.java +++ b/framework/tests/all-systems/java8inference/Bug8.java @@ -8,70 +8,68 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug8 { - static Collector> toImmutableMap( - Function keyFunction, - Function valueFunction) { - return Collector.of( - MyMap.Builder::new, - (builder, input) -> - builder.put(keyFunction.apply(input), valueFunction.apply(input)), - MyMap.Builder::combine, - MyMap.Builder::build); - } + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + return Collector.of( + MyMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + MyMap.Builder::combine, + MyMap.Builder::build); + } - static Collector> toImmutableBiMap( - Function keyFunction, - Function valueFunction) { - return Collector.of( - MyBiMap.Builder::new, - (builder, input) -> - builder.put(keyFunction.apply(input), valueFunction.apply(input)), - MyBiMap.Builder::combine, - MyBiMap.Builder::build, - new Collector.Characteristics[0]); - } + static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + return Collector.of( + MyBiMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + MyBiMap.Builder::combine, + MyBiMap.Builder::build, + new Collector.Characteristics[0]); + } - abstract static class ShimMap extends MyMap {} + abstract static class ShimMap extends MyMap {} - public interface BiMap extends Map {} + public interface BiMap extends Map {} - public abstract static class MyBiMap extends ShimMap implements BiMap { - public static final class Builder extends MyMap.Builder { - public Builder() {} + public abstract static class MyBiMap extends ShimMap implements BiMap { + public static final class Builder extends MyMap.Builder { + public Builder() {} - @Override - public MyBiMap.Builder put(K key, V value) { - throw new RuntimeException(); - } + @Override + public MyBiMap.Builder put(K key, V value) { + throw new RuntimeException(); + } - @Override - MyBiMap.Builder combine(MyMap.Builder builder) { - super.combine(builder); - return this; - } + @Override + MyBiMap.Builder combine(MyMap.Builder builder) { + super.combine(builder); + return this; + } - @Override - public MyBiMap build() { - throw new RuntimeException(); - } - } + @Override + public MyBiMap build() { + throw new RuntimeException(); + } } + } - public abstract static class MyMap implements Map, Serializable { - public static class Builder { - public Builder() {} + public abstract static class MyMap implements Map, Serializable { + public static class Builder { + public Builder() {} - public MyMap.Builder put(K key, V value) { - throw new RuntimeException(); - } + public MyMap.Builder put(K key, V value) { + throw new RuntimeException(); + } - MyMap.Builder combine(MyMap.Builder other) { - throw new RuntimeException(); - } + MyMap.Builder combine(MyMap.Builder other) { + throw new RuntimeException(); + } - public MyMap build() { - throw new RuntimeException(); - } - } + public MyMap build() { + throw new RuntimeException(); + } } + } } diff --git a/framework/tests/all-systems/java8inference/Bug9.java b/framework/tests/all-systems/java8inference/Bug9.java index 9ef255ed81a..88f052359a2 100644 --- a/framework/tests/all-systems/java8inference/Bug9.java +++ b/framework/tests/all-systems/java8inference/Bug9.java @@ -7,22 +7,21 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug9 { - private transient Map> map; + private transient Map> map; - Spliterator valueSpliterator() { - return flatMap( - map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size()); - } + Spliterator valueSpliterator() { + return flatMap(map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size()); + } - static Spliterator flatMap( - Spliterator fromSpliterator, - Function> function, - int topCharacteristics, - long topSize) { - throw new RuntimeException(); - } + static Spliterator flatMap( + Spliterator fromSpliterator, + Function> function, + int topCharacteristics, + long topSize) { + throw new RuntimeException(); + } - public int size() { - throw new RuntimeException(); - } + public int size() { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/CollectorsToList.java b/framework/tests/all-systems/java8inference/CollectorsToList.java index a3eba350847..3d915e73d34 100644 --- a/framework/tests/all-systems/java8inference/CollectorsToList.java +++ b/framework/tests/all-systems/java8inference/CollectorsToList.java @@ -3,28 +3,27 @@ // @skip-test until the bug is fixed -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.Nullable; public class CollectorsToList { - void m(List strings) { - Stream s = strings.stream(); + void m(List strings) { + Stream s = strings.stream(); - // This works: - List collectedStrings1 = s.collect(Collectors.toList()); - // This works: - List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); - // This works: - @SuppressWarnings("nullness") - List collectedStrings3 = s.collect(Collectors.toList()); + // This works: + List collectedStrings1 = s.collect(Collectors.toList()); + // This works: + List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); + // This works: + @SuppressWarnings("nullness") + List collectedStrings3 = s.collect(Collectors.toList()); - // This assignment issues a warning due to incompatible types: - List collectedStrings = s.collect(Collectors.toList()); + // This assignment issues a warning due to incompatible types: + List collectedStrings = s.collect(Collectors.toList()); - collectedStrings.forEach(System.out::println); - } + collectedStrings.forEach(System.out::println); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1308.java b/framework/tests/all-systems/java8inference/Issue1308.java index 20a6cdbfbf2..01fe0160467 100644 --- a/framework/tests/all-systems/java8inference/Issue1308.java +++ b/framework/tests/all-systems/java8inference/Issue1308.java @@ -9,21 +9,21 @@ class Map1308 {} @SuppressWarnings("all") // check for crashes public class Issue1308 { - void bar(Stream stream) { - new Inner(stream.collect(transform(data -> convert(data), Function.identity()))); - } + void bar(Stream stream) { + new Inner(stream.collect(transform(data -> convert(data), Function.identity()))); + } - String convert(Number entry) { - return ""; - } + String convert(Number entry) { + return ""; + } - class Inner { - Inner(Map1308 data) {} - } + class Inner { + Inner(Map1308 data) {} + } - static Collector> transform( - Function keyFunction, - Function valueFunction) { - return null; - } + static Collector> transform( + Function keyFunction, + Function valueFunction) { + return null; + } } diff --git a/framework/tests/all-systems/java8inference/Issue1312.java b/framework/tests/all-systems/java8inference/Issue1312.java index c33dc644bfc..80daf7eeaee 100644 --- a/framework/tests/all-systems/java8inference/Issue1312.java +++ b/framework/tests/all-systems/java8inference/Issue1312.java @@ -6,13 +6,13 @@ import java.util.stream.*; class SimpleEntry1312 { - SimpleEntry1312(K k, V v) {} + SimpleEntry1312(K k, V v) {} } @SuppressWarnings("all") // check for crashes public class Issue1312 { - Map> x = - Stream.of(Stream.of(new SimpleEntry1312<>("A", "B"))) - .flatMap(Function.identity()) - .collect(Collectors.groupingBy(e -> e)); + Map> x = + Stream.of(Stream.of(new SimpleEntry1312<>("A", "B"))) + .flatMap(Function.identity()) + .collect(Collectors.groupingBy(e -> e)); } diff --git a/framework/tests/all-systems/java8inference/Issue1313.java b/framework/tests/all-systems/java8inference/Issue1313.java index c9650f64cc1..3fadb8e28fa 100644 --- a/framework/tests/all-systems/java8inference/Issue1313.java +++ b/framework/tests/all-systems/java8inference/Issue1313.java @@ -8,10 +8,10 @@ interface MyList1313 extends Iterable {} @SuppressWarnings({"all", "type.inference.not.same"}) // check for crashes public class Issue1313 { - Stream s; - Iterable i = s.collect(toMyList1313()); + Stream s; + Iterable i = s.collect(toMyList1313()); - Collector> toMyList1313() { - return null; - } + Collector> toMyList1313() { + return null; + } } diff --git a/framework/tests/all-systems/java8inference/Issue1331.java b/framework/tests/all-systems/java8inference/Issue1331.java index 34abd099f6e..1f201e592a5 100644 --- a/framework/tests/all-systems/java8inference/Issue1331.java +++ b/framework/tests/all-systems/java8inference/Issue1331.java @@ -6,10 +6,10 @@ @SuppressWarnings("all") // check for crashes public class Issue1331 { - List ll; - long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); + List ll; + long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); - static T getOnlyElement(Iterable iterable) { - return null; - } + static T getOnlyElement(Iterable iterable) { + return null; + } } diff --git a/framework/tests/all-systems/java8inference/Issue1332.java b/framework/tests/all-systems/java8inference/Issue1332.java index 17c53502c11..41dffed8092 100644 --- a/framework/tests/all-systems/java8inference/Issue1332.java +++ b/framework/tests/all-systems/java8inference/Issue1332.java @@ -8,22 +8,22 @@ @SuppressWarnings("all") // check for crashes abstract class Issue1332 { - void foo(List ll) { - Function test = - s -> { - long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); - return result; - }; - } + void foo(List ll) { + Function test = + s -> { + long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); + return result; + }; + } - abstract T getOnlyElement(Iterable iterable); + abstract T getOnlyElement(Iterable iterable); - private void test1() { - byte[][] byteArrayArray = new byte[][] {}; - Stream stream = Stream.of(byteArrayArray); - } + private void test1() { + byte[][] byteArrayArray = new byte[][] {}; + Stream stream = Stream.of(byteArrayArray); + } - private void test2() { - Stream stream = Stream.of(new byte[][] {}); - } + private void test2() { + Stream stream = Stream.of(new byte[][] {}); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1334.java b/framework/tests/all-systems/java8inference/Issue1334.java index 4c11b5d9efa..fbb9b537866 100644 --- a/framework/tests/all-systems/java8inference/Issue1334.java +++ b/framework/tests/all-systems/java8inference/Issue1334.java @@ -4,7 +4,7 @@ import java.util.stream.Stream; public class Issue1334 { - private void test() { - Stream stream = Stream.of(new byte[][] {}).map(b -> 1); - } + private void test() { + Stream stream = Stream.of(new byte[][] {}).map(b -> 1); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1377.java b/framework/tests/all-systems/java8inference/Issue1377.java index ea23c02e0ce..3485978cf42 100644 --- a/framework/tests/all-systems/java8inference/Issue1377.java +++ b/framework/tests/all-systems/java8inference/Issue1377.java @@ -2,23 +2,23 @@ // https://github.com/typetools/checker-framework/issues/1377 interface Func1377 { - R apply(P p); + R apply(P p); } @SuppressWarnings("all") // just check for crashes interface Issue1377 { - static Issue1377 of(Issue1377 in) { - return in; - } + static Issue1377 of(Issue1377 in) { + return in; + } - Issue1377 m1(Func1377 f); + Issue1377 m1(Func1377 f); - Issue1377 m2(Func1377 f); + Issue1377 m2(Func1377 f); } @SuppressWarnings("all") // just check for crashes class Crash1377 { - void foo(Issue1377 p) { - Issue1377.of(p.m1(in -> p)).m2(empty -> 5); - } + void foo(Issue1377 p) { + Issue1377.of(p.m1(in -> p)).m2(empty -> 5); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1379.java b/framework/tests/all-systems/java8inference/Issue1379.java index 6c539160b14..dec2dc2264d 100644 --- a/framework/tests/all-systems/java8inference/Issue1379.java +++ b/framework/tests/all-systems/java8inference/Issue1379.java @@ -4,18 +4,18 @@ interface Box1379 {} interface Trans1379 { - Box1379 apply(I in); + Box1379 apply(I in); } @SuppressWarnings("all") // just check for crashes abstract class Issue1379 { - abstract Box1379 app(Box1379 in, Trans1379 t); + abstract Box1379 app(Box1379 in, Trans1379 t); - abstract Trans1379 pass(Trans1379 t); + abstract Trans1379 pass(Trans1379 t); - abstract Box1379 box(Number p); + abstract Box1379 box(Number p); - void foo(Box1379 p) { - app(p, pass(this::box)); - } + void foo(Box1379 p) { + app(p, pass(this::box)); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1397.java b/framework/tests/all-systems/java8inference/Issue1397.java index 3416327c214..74d879396fd 100644 --- a/framework/tests/all-systems/java8inference/Issue1397.java +++ b/framework/tests/all-systems/java8inference/Issue1397.java @@ -5,17 +5,17 @@ public class Issue1397 { - class Box {} + class Box {} - abstract class CrashCompound { - abstract T chk(T in); + abstract class CrashCompound { + abstract T chk(T in); - abstract T unbox(Box p); + abstract T unbox(Box p); - @SuppressWarnings("units") - void foo(Box bb) { - boolean res = false; - res |= chk(unbox(bb)); - } + @SuppressWarnings("units") + void foo(Box bb) { + boolean res = false; + res |= chk(unbox(bb)); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue1398.java b/framework/tests/all-systems/java8inference/Issue1398.java index f80914458b5..caf4e4cdb06 100644 --- a/framework/tests/all-systems/java8inference/Issue1398.java +++ b/framework/tests/all-systems/java8inference/Issue1398.java @@ -3,29 +3,29 @@ public class Issue1398 { - interface Pair {} + interface Pair {} - interface Triple {} + interface Triple {} - interface Quadruple {} + interface Quadruple {} - interface Box { - Pair doTriple(Triple t); + interface Box { + Pair doTriple(Triple t); - > BA doPair(Pair p, BoxMaker bm); - } + > BA doPair(Pair p, BoxMaker bm); + } - class BoxMaker> {} + class BoxMaker> {} - abstract class Crash7 { - abstract Pair bar(Pair in); + abstract class Crash7 { + abstract Pair bar(Pair in); - void foo( - Box bs, - BoxMaker> bm, - Pair psn, - Triple t) { - bs.doPair(bar(psn), bm).doTriple(t); - } + void foo( + Box bs, + BoxMaker> bm, + Pair psn, + Triple t) { + bs.doPair(bar(psn), bm).doTriple(t); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue1399.java b/framework/tests/all-systems/java8inference/Issue1399.java index 021401ee778..d4eb9032f91 100644 --- a/framework/tests/all-systems/java8inference/Issue1399.java +++ b/framework/tests/all-systems/java8inference/Issue1399.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/1399 public class Issue1399 { - static class Box { - static Box box(Class type) { - return new Box(); - } - - void act(T instance) {} + static class Box { + static Box box(Class type) { + return new Box(); } - abstract class Math { - abstract Box id(Box in); + void act(T instance) {} + } + + abstract class Math { + abstract Box id(Box in); - void foo(Math m) { - m.id(Box.box(Long.class)).act(10L); - } + void foo(Math m) { + m.id(Box.box(Long.class)).act(10L); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue1407.java b/framework/tests/all-systems/java8inference/Issue1407.java index ca51a7c2127..676235a515e 100644 --- a/framework/tests/all-systems/java8inference/Issue1407.java +++ b/framework/tests/all-systems/java8inference/Issue1407.java @@ -4,12 +4,12 @@ // @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 abstract class Issue1407 { - abstract T foo(T p1, T p2); + abstract T foo(T p1, T p2); - abstract T bar(int p1, T p2); + abstract T bar(int p1, T p2); - @SuppressWarnings({"interning", "signedness"}) - int demo() { - return foo(bar(5, 3), 3); - } + @SuppressWarnings({"interning", "signedness"}) + int demo() { + return foo(bar(5, 3), 3); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1408.java b/framework/tests/all-systems/java8inference/Issue1408.java index 85aeb2d83a5..5cfae7dd158 100644 --- a/framework/tests/all-systems/java8inference/Issue1408.java +++ b/framework/tests/all-systems/java8inference/Issue1408.java @@ -1,15 +1,15 @@ // Test case for Issue 1408. // https://github.com/typetools/checker-framework/issues/1408 abstract class Issue1408 { - interface Demo {} + interface Demo {} - interface SubDemo extends Demo {} + interface SubDemo extends Demo {} - abstract S foo(S p1, S p2); + abstract S foo(S p1, S p2); - abstract T bar(T p2); + abstract T bar(T p2); - SubDemo demo(SubDemo p) { - return foo(bar(p), p); - } + SubDemo demo(SubDemo p) { + return foo(bar(p), p); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1415.java b/framework/tests/all-systems/java8inference/Issue1415.java index fedc3b61679..5af1832b7ad 100644 --- a/framework/tests/all-systems/java8inference/Issue1415.java +++ b/framework/tests/all-systems/java8inference/Issue1415.java @@ -3,24 +3,24 @@ @SuppressWarnings("all") // Check for crashes. public class Issue1415 { - static class Optional { - static Optional absent() { - return null; - } - - static Optional of(T p) { - return null; - } + static class Optional { + static Optional absent() { + return null; } - static class Box { - void box(T p) {} + static Optional of(T p) { + return null; } + } + + static class Box { + void box(T p) {} + } - static class Crash9 { - > void foo(boolean b, Box> box, Class enumClass) { - box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); - box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); - } + static class Crash9 { + > void foo(boolean b, Box> box, Class enumClass) { + box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); + box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue1416.java b/framework/tests/all-systems/java8inference/Issue1416.java index ca9c44bfe11..c7cae68ff39 100644 --- a/framework/tests/all-systems/java8inference/Issue1416.java +++ b/framework/tests/all-systems/java8inference/Issue1416.java @@ -5,8 +5,8 @@ import java.util.stream.Stream; public class Issue1416 { - @SuppressWarnings("signedness") - long order(Stream sl) { - return sl.max(Comparator.naturalOrder()).orElse(0L); - } + @SuppressWarnings("signedness") + long order(Stream sl) { + return sl.max(Comparator.naturalOrder()).orElse(0L); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1417.java b/framework/tests/all-systems/java8inference/Issue1417.java index 854ef83857d..52f86b0de5b 100644 --- a/framework/tests/all-systems/java8inference/Issue1417.java +++ b/framework/tests/all-systems/java8inference/Issue1417.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/1417 public class Issue1417 { - interface Bar {} + interface Bar {} - interface SubBar extends Bar {} + interface SubBar extends Bar {} - interface Barber { - S call(S s); - } + interface Barber { + S call(S s); + } - abstract class Crash12 { - abstract void foo(Barber b); + abstract class Crash12 { + abstract void foo(Barber b); - void crash() { - foo((SubBar p) -> p); - } + void crash() { + foo((SubBar p) -> p); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue1419.java b/framework/tests/all-systems/java8inference/Issue1419.java index 4a895570221..8cc1b24460a 100644 --- a/framework/tests/all-systems/java8inference/Issue1419.java +++ b/framework/tests/all-systems/java8inference/Issue1419.java @@ -2,14 +2,14 @@ // https://github.com/typetools/checker-framework/issues/1419 abstract class Issue1419 { - class Map {} + class Map {} - class EnumMap> extends Map {} + class EnumMap> extends Map {} - abstract > Map foo(Map map); + abstract > Map foo(Map map); - @SuppressWarnings("unchecked") - Map bar(Map map) { - return foo((EnumMap) map); - } + @SuppressWarnings("unchecked") + Map bar(Map map) { + return foo((EnumMap) map); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1424.java b/framework/tests/all-systems/java8inference/Issue1424.java index 31a3106f412..5c9672df489 100644 --- a/framework/tests/all-systems/java8inference/Issue1424.java +++ b/framework/tests/all-systems/java8inference/Issue1424.java @@ -3,23 +3,23 @@ @SuppressWarnings("unchecked") abstract class Issue1424 { - class Box {} + class Box {} - interface Callable { - V call() throws Exception; - } + interface Callable { + V call() throws Exception; + } - class MyCallable implements Callable { - MyCallable(Callable delegate) {} + class MyCallable implements Callable { + MyCallable(Callable delegate) {} - public T call() throws Exception { - throw new RuntimeException(); - } + public T call() throws Exception { + throw new RuntimeException(); } + } - abstract Box submit(Callable t); + abstract Box submit(Callable t); - Box foo() { - return submit(new MyCallable(() -> true)); - } + Box foo() { + return submit(new MyCallable(() -> true)); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1715.java b/framework/tests/all-systems/java8inference/Issue1715.java index f5e1aed8478..948329fb757 100644 --- a/framework/tests/all-systems/java8inference/Issue1715.java +++ b/framework/tests/all-systems/java8inference/Issue1715.java @@ -3,30 +3,30 @@ public class Issue1715 { - static final class A { + static final class A { - public A() {} + public A() {} - public Object foo(Object o) { - return o; - } + public Object foo(Object o) { + return o; } + } - private Observable>> test(A a) { - return Observable.just(ImmutableList.of(a::foo)); - } + private Observable>> test(A a) { + return Observable.just(ImmutableList.of(a::foo)); + } - static class Observable { + static class Observable { - static Observable just(T param) { - throw new RuntimeException(); - } + static Observable just(T param) { + throw new RuntimeException(); } + } - public abstract static class ImmutableList implements List { + public abstract static class ImmutableList implements List { - public static ImmutableList of(E element) { - throw new RuntimeException(); - } + public static ImmutableList of(E element) { + throw new RuntimeException(); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue1775.java b/framework/tests/all-systems/java8inference/Issue1775.java index faf56655108..d4994a76d5d 100644 --- a/framework/tests/all-systems/java8inference/Issue1775.java +++ b/framework/tests/all-systems/java8inference/Issue1775.java @@ -3,15 +3,15 @@ @SuppressWarnings("all") // just check for crashes public class Issue1775 { - interface Box { - B get(); - } + interface Box { + B get(); + } - Box getBox() { - return null; - } + Box getBox() { + return null; + } - void m() { - for (String s : getBox().get()) {} - } + void m() { + for (String s : getBox().get()) {} + } } diff --git a/framework/tests/all-systems/java8inference/Issue1815.java b/framework/tests/all-systems/java8inference/Issue1815.java index e8f05e4bfd7..8b29216f524 100644 --- a/framework/tests/all-systems/java8inference/Issue1815.java +++ b/framework/tests/all-systems/java8inference/Issue1815.java @@ -7,15 +7,15 @@ @SuppressWarnings("all") // just check for crashes abstract class Issue1815 { - class A extends B {} + class A extends B {} - static class B, Two extends B.I> { - static class I, Four extends I> {} - } + static class B, Two extends B.I> { + static class I, Four extends I> {} + } - abstract A f(Integer x); + abstract A f(Integer x); - void test(List xs) { - Stream.of(xs.stream().map(x -> f(x)), xs.stream().map(x -> f(x))).flatMap(stream -> stream); - } + void test(List xs) { + Stream.of(xs.stream().map(x -> f(x)), xs.stream().map(x -> f(x))).flatMap(stream -> stream); + } } diff --git a/framework/tests/all-systems/java8inference/Issue2975.java b/framework/tests/all-systems/java8inference/Issue2975.java index 5e644ef8f7d..570cfc4e0db 100644 --- a/framework/tests/all-systems/java8inference/Issue2975.java +++ b/framework/tests/all-systems/java8inference/Issue2975.java @@ -2,19 +2,19 @@ import java.util.function.Consumer; public class Issue2975 { - static class Child extends Issue2975 { - Wrapper y = new Wrapper(Child::takesCloseable); + static class Child extends Issue2975 { + Wrapper y = new Wrapper(Child::takesCloseable); - private static void takesCloseable(Closeable rhs) {} - } + private static void takesCloseable(Closeable rhs) {} + } - protected class Wrapper { - protected Wrapper() {} + protected class Wrapper { + protected Wrapper() {} - protected Wrapper(Consumer makeExpression) {} + protected Wrapper(Consumer makeExpression) {} - protected Wrapper method(Consumer makeExpression) { - throw new RuntimeException(); - } + protected Wrapper method(Consumer makeExpression) { + throw new RuntimeException(); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue3032.java b/framework/tests/all-systems/java8inference/Issue3032.java index f8e49fe603f..b2054db6ccb 100644 --- a/framework/tests/all-systems/java8inference/Issue3032.java +++ b/framework/tests/all-systems/java8inference/Issue3032.java @@ -1,47 +1,46 @@ import java.io.Serializable; public class Issue3032 { - public static class PCollection implements PValue { - public OutputT apply( - String name, PTransform, OutputT> t) { - throw new RuntimeException(); - } + public static class PCollection implements PValue { + public OutputT apply( + String name, PTransform, OutputT> t) { + throw new RuntimeException(); } + } - public abstract static class PTransform {} + public abstract static class PTransform {} - interface PInput {} + interface PInput {} - interface POutput {} + interface POutput {} - interface PValue extends PInput, POutput {} + interface PValue extends PInput, POutput {} - static class BillingEvent { - InvoiceGroupingKey getInvoiceGroupingKey() { - throw new RuntimeException(); - } + static class BillingEvent { + InvoiceGroupingKey getInvoiceGroupingKey() { + throw new RuntimeException(); } + } - public static class MapElements - extends PTransform, PCollection> { - public static MapElements via( - final ProcessFunction fn) { - throw new RuntimeException(); - } + public static class MapElements + extends PTransform, PCollection> { + public static MapElements via( + final ProcessFunction fn) { + throw new RuntimeException(); } + } - static class InvoiceGroupingKey {} + static class InvoiceGroupingKey {} - @FunctionalInterface - public interface ProcessFunction extends Serializable { - OutputT apply(InputT input) throws Exception; - } + @FunctionalInterface + public interface ProcessFunction extends Serializable { + OutputT apply(InputT input) throws Exception; + } - private static class GenerateInvoiceRows - extends PTransform, PCollection> { - public void expand(PCollection input) { - input.apply( - "Map to invoicing key", MapElements.via(BillingEvent::getInvoiceGroupingKey)); - } + private static class GenerateInvoiceRows + extends PTransform, PCollection> { + public void expand(PCollection input) { + input.apply("Map to invoicing key", MapElements.via(BillingEvent::getInvoiceGroupingKey)); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue3036.java b/framework/tests/all-systems/java8inference/Issue3036.java index a654e884838..0a5eefae745 100644 --- a/framework/tests/all-systems/java8inference/Issue3036.java +++ b/framework/tests/all-systems/java8inference/Issue3036.java @@ -9,40 +9,40 @@ public class Issue3036 { - public Set getDsData() { - throw new RuntimeException(); - } + public Set getDsData() { + throw new RuntimeException(); + } - public static class MyInnerClass { - public int getKeyTag() { - return 5; - } + public static class MyInnerClass { + public int getKeyTag() { + return 5; + } - public String getDigest() { - return ""; - } + public String getDigest() { + return ""; } + } - private void write(Stream stream) { - Function> mapper = - dsData1 -> - ImmutableMap.of( - "keyTag", dsData1.getKeyTag(), - "digest", dsData1.getDigest()); + private void write(Stream stream) { + Function> mapper = + dsData1 -> + ImmutableMap.of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest()); - List> dsData = - getDsData().stream() - .map( - dsData1 -> - ImmutableMap.of( - "keyTag", dsData1.getKeyTag(), - "digest", dsData1.getDigest())) - .collect(Collectors.toList()); - } + List> dsData = + getDsData().stream() + .map( + dsData1 -> + ImmutableMap.of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest())) + .collect(Collectors.toList()); + } - public static class ImmutableMap extends HashMap { - public static ImmutableMap of(K k1, V v1, K k2, V v2) { - throw new RuntimeException(); - } + public static class ImmutableMap extends HashMap { + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + throw new RuntimeException(); } + } } diff --git a/framework/tests/all-systems/java8inference/Issue404.java b/framework/tests/all-systems/java8inference/Issue404.java index bc0d3ced327..a0c1b1d9a78 100644 --- a/framework/tests/all-systems/java8inference/Issue404.java +++ b/framework/tests/all-systems/java8inference/Issue404.java @@ -11,7 +11,7 @@ // Eventually, the test should work when executed on >= Java 8. // @skip-test public final class Issue404 { - public Set uniqueTrimmed(final Collection strings) { - return strings.stream().map(String::trim).collect(Collectors.toSet()); - } + public Set uniqueTrimmed(final Collection strings) { + return strings.stream().map(String::trim).collect(Collectors.toSet()); + } } diff --git a/framework/tests/all-systems/java8inference/Issue953.java b/framework/tests/all-systems/java8inference/Issue953.java index 8c8a1d62512..e08b75ef636 100644 --- a/framework/tests/all-systems/java8inference/Issue953.java +++ b/framework/tests/all-systems/java8inference/Issue953.java @@ -7,13 +7,13 @@ import java.util.stream.Collectors; public class Issue953 { - public static void test() { - List initial = new ArrayList<>(); - List counts = - initial.stream().skip(1).map(l -> count("", l)).collect(Collectors.toList()); - } + public static void test() { + List initial = new ArrayList<>(); + List counts = + initial.stream().skip(1).map(l -> count("", l)).collect(Collectors.toList()); + } - private static int count(String s, String l) { - return 0; - } + private static int count(String s, String l) { + return 0; + } } diff --git a/framework/tests/all-systems/java8inference/MemRefInfere.java b/framework/tests/all-systems/java8inference/MemRefInfere.java index 29b2b6a4752..225c70ae7ef 100644 --- a/framework/tests/all-systems/java8inference/MemRefInfere.java +++ b/framework/tests/all-systems/java8inference/MemRefInfere.java @@ -9,17 +9,17 @@ import java.util.stream.Collectors; public abstract class MemRefInfere implements Map, Serializable { - public static MemRefInfere copyOf(Map map) { - throw new RuntimeException(); - } + public static MemRefInfere copyOf(Map map) { + throw new RuntimeException(); + } - public static Collector> toImmutableMap( - Function keyFunction, - Function valueFunction, - BinaryOperator mergeFunction) { + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { - return Collectors.collectingAndThen( - Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), - MemRefInfere::copyOf); - } + return Collectors.collectingAndThen( + Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), + MemRefInfere::copyOf); + } } diff --git a/framework/tests/all-systems/java8inference/Misc.java b/framework/tests/all-systems/java8inference/Misc.java index 008855d75a1..a780b2058dc 100644 --- a/framework/tests/all-systems/java8inference/Misc.java +++ b/framework/tests/all-systems/java8inference/Misc.java @@ -1,20 +1,20 @@ @SuppressWarnings({"unchecked", "all"}) // check for crashes public class Misc { - Misc forward; + Misc forward; - public E min(E a, E b) { - return forward.max(a, b); - } + public E min(E a, E b) { + return forward.max(a, b); + } - public E min(E a, E b, E c, E... rest) { - return forward.max(a, b, c, rest); - } + public E min(E a, E b, E c, E... rest) { + return forward.max(a, b, c, rest); + } - public E max(E a, E b) { - return forward.min(a, b); - } + public E max(E a, E b) { + return forward.min(a, b); + } - public E max(E a, E b, E c, E... rest) { - return forward.min(a, b, c, rest); - } + public E max(E a, E b, E c, E... rest) { + return forward.min(a, b, c, rest); + } } diff --git a/framework/tests/annotationclassloader/LoaderTest.java b/framework/tests/annotationclassloader/LoaderTest.java index 02b4b8b6c91..69e50fdc585 100644 --- a/framework/tests/annotationclassloader/LoaderTest.java +++ b/framework/tests/annotationclassloader/LoaderTest.java @@ -1,9 +1,9 @@ import org.checkerframework.common.aliasing.qual.Unique; public class LoaderTest { - void foo() { - @Unique Object o = new Object(); - // :: error: (unique.leaked) - Object[] ar = new Object[] {o}; - } + void foo() { + @Unique Object o = new Object(); + // :: error: (unique.leaked) + Object[] ar = new Object[] {o}; + } } diff --git a/framework/tests/classval/ClassNameTest.java b/framework/tests/classval/ClassNameTest.java index f2a6ea5412c..a43c51f3236 100644 --- a/framework/tests/classval/ClassNameTest.java +++ b/framework/tests/classval/ClassNameTest.java @@ -1,22 +1,22 @@ import org.checkerframework.common.reflection.qual.ClassVal; public class ClassNameTest { - void test() throws Exception { - @ClassVal("Class$Inner") Object o; - @ClassVal("java.lang.String") Object o1; - @ClassVal("java.lang.String[]") Object o2; - @ClassVal("java.lang.String[][][]") Object o3; - @ClassVal("Class$Inner._") Object o8; + void test() throws Exception { + @ClassVal("Class$Inner") Object o; + @ClassVal("java.lang.String") Object o1; + @ClassVal("java.lang.String[]") Object o2; + @ClassVal("java.lang.String[][][]") Object o3; + @ClassVal("Class$Inner._") Object o8; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][]]") Object o4; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][][") Object o5; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][][]s") Object o6; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][][].") Object o7; - // :: error: (illegal.classname) - @ClassVal("java.lang..String") Object o9; - } + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][]]") Object o4; + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][][") Object o5; + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][][]s") Object o6; + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][][].") Object o7; + // :: error: (illegal.classname) + @ClassVal("java.lang..String") Object o9; + } } diff --git a/framework/tests/classval/ClassValInferenceTest.java b/framework/tests/classval/ClassValInferenceTest.java index 5c2543f8629..4017a1bbddb 100644 --- a/framework/tests/classval/ClassValInferenceTest.java +++ b/framework/tests/classval/ClassValInferenceTest.java @@ -1,70 +1,69 @@ -import org.checkerframework.common.reflection.qual.ClassBound; -import org.checkerframework.common.reflection.qual.ClassVal; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.common.reflection.qual.ClassBound; +import org.checkerframework.common.reflection.qual.ClassVal; public class ClassValInferenceTest { - class Inner { - Inner() { - @ClassBound("ClassValInferenceTest$Inner") Class c1 = this.getClass(); - @ClassBound("ClassValInferenceTest") Class c2 = ClassValInferenceTest.this.getClass(); - } + class Inner { + Inner() { + @ClassBound("ClassValInferenceTest$Inner") Class c1 = this.getClass(); + @ClassBound("ClassValInferenceTest") Class c2 = ClassValInferenceTest.this.getClass(); } + } - public void classLiterals() { - @ClassVal("java.lang.Object") Class c1 = Object.class; - @ClassVal("java.lang.Object[]") Class c2 = Object[].class; - @ClassVal("java.lang.Object[][][]") Class c3 = Object[][][].class; - @ClassVal("ClassValInferenceTest$Inner") Class c4 = Inner.class; - @ClassVal("byte") Class c5 = byte.class; - } + public void classLiterals() { + @ClassVal("java.lang.Object") Class c1 = Object.class; + @ClassVal("java.lang.Object[]") Class c2 = Object[].class; + @ClassVal("java.lang.Object[][][]") Class c3 = Object[][][].class; + @ClassVal("ClassValInferenceTest$Inner") Class c4 = Inner.class; + @ClassVal("byte") Class c5 = byte.class; + } - public void classForName() throws ClassNotFoundException { - @ClassVal("ClassValInferenceTest$Inner") Class c = Class.forName("ClassValInferenceTest$Inner"); - @ClassVal("java.lang.Object") Class c1 = Class.forName("java.lang.Object"); - } + public void classForName() throws ClassNotFoundException { + @ClassVal("ClassValInferenceTest$Inner") Class c = Class.forName("ClassValInferenceTest$Inner"); + @ClassVal("java.lang.Object") Class c1 = Class.forName("java.lang.Object"); + } - boolean flag = true; + boolean flag = true; - public void classForNameStringVal() throws ClassNotFoundException { - Class c2; - if (flag) { - c2 = Class.forName("java.lang.Byte"); - } else { - c2 = Class.forName("java.lang.Integer"); - } - @ClassVal({"java.lang.Byte", "java.lang.Integer"}) Class c3 = c2; + public void classForNameStringVal() throws ClassNotFoundException { + Class c2; + if (flag) { + c2 = Class.forName("java.lang.Byte"); + } else { + c2 = Class.forName("java.lang.Integer"); } + @ClassVal({"java.lang.Byte", "java.lang.Integer"}) Class c3 = c2; + } - public > void testGetClass( - T typeVar, I intersect) { - @ClassBound("ClassValInferenceTest") Class c1 = this.getClass(); - @ClassBound("ClassValInferenceTest") Class c2 = getClass(); - String[] array = {"hello"}; - @ClassBound("java.lang.String[]") Class c3 = array.getClass(); - String[][][][] arrayMulti = null; - @ClassBound("java.lang.String[][][][]") Class c4 = arrayMulti.getClass(); - @ClassBound("java.lang.String") Class c5 = array[0].getClass(); - List list = null; - // TODO: reinstate this line. - // The checker issues an error under JDK 18, probably due to issue #979 - // found : Class> - // @ClassBound("java.util.List") Class c6 = list.getClass(); - @ClassBound("java.lang.Number") Class c7 = typeVar.getClass(); - @ClassBound("java.util.ArrayList") Class c8 = new ArrayList().getClass(); - List wildCardListLB = null; - List wildCardListUB = null; - @ClassBound("java.lang.Object") Class c9 = wildCardListLB.get(0).getClass(); - @ClassBound("java.lang.Number") Class c10 = wildCardListUB.get(0).getClass(); - Integer i = 0; - @ClassBound("java.lang.Integer") Class c11 = i.getClass(); - @ClassBound("java.lang.Object") Class c12 = intersect.getClass(); + public > void testGetClass( + T typeVar, I intersect) { + @ClassBound("ClassValInferenceTest") Class c1 = this.getClass(); + @ClassBound("ClassValInferenceTest") Class c2 = getClass(); + String[] array = {"hello"}; + @ClassBound("java.lang.String[]") Class c3 = array.getClass(); + String[][][][] arrayMulti = null; + @ClassBound("java.lang.String[][][][]") Class c4 = arrayMulti.getClass(); + @ClassBound("java.lang.String") Class c5 = array[0].getClass(); + List list = null; + // TODO: reinstate this line. + // The checker issues an error under JDK 18, probably due to issue #979 + // found : Class> + // @ClassBound("java.util.List") Class c6 = list.getClass(); + @ClassBound("java.lang.Number") Class c7 = typeVar.getClass(); + @ClassBound("java.util.ArrayList") Class c8 = new ArrayList().getClass(); + List wildCardListLB = null; + List wildCardListUB = null; + @ClassBound("java.lang.Object") Class c9 = wildCardListLB.get(0).getClass(); + @ClassBound("java.lang.Number") Class c10 = wildCardListUB.get(0).getClass(); + Integer i = 0; + @ClassBound("java.lang.Integer") Class c11 = i.getClass(); + @ClassBound("java.lang.Object") Class c12 = intersect.getClass(); - try { - } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) { - @ClassBound("java.lang.RuntimeException") Class c = ex.getClass(); - } + try { + } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) { + @ClassBound("java.lang.RuntimeException") Class c = ex.getClass(); } + } } diff --git a/framework/tests/classval/ClassValSubtypingTest.java b/framework/tests/classval/ClassValSubtypingTest.java index 1af41449e05..53d8ed3097c 100644 --- a/framework/tests/classval/ClassValSubtypingTest.java +++ b/framework/tests/classval/ClassValSubtypingTest.java @@ -2,115 +2,115 @@ import org.checkerframework.common.reflection.qual.ClassVal; public class ClassValSubtypingTest { - @ClassVal("a") Object a = null; - - @ClassVal({"a", "b"}) Object ab = null; - - @ClassVal("c") Object c = null; - - @ClassVal({"c", "d"}) Object cd = null; - - Object unknown = null; - - void assignToUnknown() { - unknown = a; - unknown = ab; - unknown = c; - unknown = cd; - } - - void assignUnknown() { - // :: error: (assignment.type.incompatible) - a = unknown; - // :: error: (assignment.type.incompatible) - ab = unknown; - // :: error: (assignment.type.incompatible) - c = unknown; - // :: error: (assignment.type.incompatible) - cd = unknown; - } - - void assignments() { - // :: error: (assignment.type.incompatible) - a = ab; - ab = a; - // :: error: (assignment.type.incompatible) - a = c; - // :: error: (assignment.type.incompatible) - ab = c; - // :: error: (assignment.type.incompatible) - ab = cd; - } + @ClassVal("a") Object a = null; + + @ClassVal({"a", "b"}) Object ab = null; + + @ClassVal("c") Object c = null; + + @ClassVal({"c", "d"}) Object cd = null; + + Object unknown = null; + + void assignToUnknown() { + unknown = a; + unknown = ab; + unknown = c; + unknown = cd; + } + + void assignUnknown() { + // :: error: (assignment.type.incompatible) + a = unknown; + // :: error: (assignment.type.incompatible) + ab = unknown; + // :: error: (assignment.type.incompatible) + c = unknown; + // :: error: (assignment.type.incompatible) + cd = unknown; + } + + void assignments() { + // :: error: (assignment.type.incompatible) + a = ab; + ab = a; + // :: error: (assignment.type.incompatible) + a = c; + // :: error: (assignment.type.incompatible) + ab = c; + // :: error: (assignment.type.incompatible) + ab = cd; + } } class ClassBoundSubtypingTest { - @ClassBound("a") Object a = null; - - @ClassBound({"a", "b"}) Object ab = null; - - @ClassBound("c") Object c = null; - - @ClassBound({"c", "d"}) Object cd = null; - - Object unknown = null; - - void assignToUnknown() { - unknown = a; - unknown = ab; - unknown = c; - unknown = cd; - } - - void assignUnknown() { - // :: error: (assignment.type.incompatible) - a = unknown; - // :: error: (assignment.type.incompatible) - ab = unknown; - // :: error: (assignment.type.incompatible) - c = unknown; - // :: error: (assignment.type.incompatible) - cd = unknown; - } - - void assignments() { - // :: error: (assignment.type.incompatible) - a = ab; - ab = a; - // :: error: (assignment.type.incompatible) - a = c; - // :: error: (assignment.type.incompatible) - ab = c; - // :: error: (assignment.type.incompatible) - ab = cd; - } + @ClassBound("a") Object a = null; + + @ClassBound({"a", "b"}) Object ab = null; + + @ClassBound("c") Object c = null; + + @ClassBound({"c", "d"}) Object cd = null; + + Object unknown = null; + + void assignToUnknown() { + unknown = a; + unknown = ab; + unknown = c; + unknown = cd; + } + + void assignUnknown() { + // :: error: (assignment.type.incompatible) + a = unknown; + // :: error: (assignment.type.incompatible) + ab = unknown; + // :: error: (assignment.type.incompatible) + c = unknown; + // :: error: (assignment.type.incompatible) + cd = unknown; + } + + void assignments() { + // :: error: (assignment.type.incompatible) + a = ab; + ab = a; + // :: error: (assignment.type.incompatible) + a = c; + // :: error: (assignment.type.incompatible) + ab = c; + // :: error: (assignment.type.incompatible) + ab = cd; + } } class ClassValClassBoundSubtypingTest { - @ClassVal("a") Object a = null; + @ClassVal("a") Object a = null; - @ClassVal({"a", "b"}) Object ab = null; + @ClassVal({"a", "b"}) Object ab = null; - @ClassBound("a") Object aBound = null; + @ClassBound("a") Object aBound = null; - @ClassBound({"a", "b"}) Object abBound = null; + @ClassBound({"a", "b"}) Object abBound = null; - void assignments1() { - // :: error: (assignment.type.incompatible) - a = aBound; - // :: error: (assignment.type.incompatible) - ab = aBound; - // :: error: (assignment.type.incompatible) - a = abBound; - // :: error: (assignment.type.incompatible) - ab = abBound; - } + void assignments1() { + // :: error: (assignment.type.incompatible) + a = aBound; + // :: error: (assignment.type.incompatible) + ab = aBound; + // :: error: (assignment.type.incompatible) + a = abBound; + // :: error: (assignment.type.incompatible) + ab = abBound; + } - void assignments2() { - aBound = a; - // :: error: (assignment.type.incompatible) - aBound = ab; + void assignments2() { + aBound = a; + // :: error: (assignment.type.incompatible) + aBound = ab; - abBound = a; - abBound = ab; - } + abBound = a; + abBound = ab; + } } diff --git a/framework/tests/classval/GLBTest.java b/framework/tests/classval/GLBTest.java index a536ccfc3b9..70fbb5aded2 100644 --- a/framework/tests/classval/GLBTest.java +++ b/framework/tests/classval/GLBTest.java @@ -2,24 +2,24 @@ import org.checkerframework.common.reflection.qual.ClassVal; public class GLBTest<@ClassVal({"A", "B"}) T extends Object> { - // This code is intented to cover the more complex branchs for the GLB calcuation. - // This only triggers the GLB calculation because of a hack in - // org.checkerframework.framework.util.AnnotatedTypes.addAnnotationsImpl() - // If that code changes, this code may not test GLB anymore. - // This code does not test correctness. Because no expresion is given the GLB as a type, - // it is impossible to test GLB for correctness. - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - GLBTest<@ClassVal({"A", "B", "C"}) ?> f1 = new GLBTest<@ClassVal({"A", "E"}) Object>(); - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - GLBTest<@ClassVal({"A", "B", "C"}) ?> f2 = new GLBTest<@ClassBound({"A", "E"}) Object>(); - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - GLBTest<@ClassBound({"A", "B", "C"}) ?> f3 = new GLBTest<@ClassBound({"A", "E"}) Object>(); + // This code is intented to cover the more complex branchs for the GLB calcuation. + // This only triggers the GLB calculation because of a hack in + // org.checkerframework.framework.util.AnnotatedTypes.addAnnotationsImpl() + // If that code changes, this code may not test GLB anymore. + // This code does not test correctness. Because no expresion is given the GLB as a type, + // it is impossible to test GLB for correctness. + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + GLBTest<@ClassVal({"A", "B", "C"}) ?> f1 = new GLBTest<@ClassVal({"A", "E"}) Object>(); + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + GLBTest<@ClassVal({"A", "B", "C"}) ?> f2 = new GLBTest<@ClassBound({"A", "E"}) Object>(); + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + GLBTest<@ClassBound({"A", "B", "C"}) ?> f3 = new GLBTest<@ClassBound({"A", "E"}) Object>(); - < - @ClassVal({"A", "B", "C"}) CLASSVAL extends Object, - @ClassBound({"A", "B", "C"}) CLASSBOUND extends Object> - void test() { - GLBTest f1 = new GLBTest(); - GLBTest f2 = new GLBTest(); - } + < + @ClassVal({"A", "B", "C"}) CLASSVAL extends Object, + @ClassBound({"A", "B", "C"}) CLASSBOUND extends Object> + void test() { + GLBTest f1 = new GLBTest(); + GLBTest f2 = new GLBTest(); + } } diff --git a/framework/tests/compound-checker/CompoundBasicTest.java b/framework/tests/compound-checker/CompoundBasicTest.java index e396fdaea67..807e13907b4 100644 --- a/framework/tests/compound-checker/CompoundBasicTest.java +++ b/framework/tests/compound-checker/CompoundBasicTest.java @@ -1,12 +1,12 @@ public class CompoundBasicTest { - // Random code just to make sure that the compound design pattern - // does not throw any exceptions. - Object field = new Object(); - String[] array = {"hello", "world"}; + // Random code just to make sure that the compound design pattern + // does not throw any exceptions. + Object field = new Object(); + String[] array = {"hello", "world"}; - void foo(int arg) { - field = array[0]; - field.toString(); - int a = 1 + arg; - } + void foo(int arg) { + field = array[0]; + field.toString(); + int a = 1 + arg; + } } diff --git a/framework/tests/compound-checker/MultiError.java b/framework/tests/compound-checker/MultiError.java index 231846ac592..6e35ac719c7 100644 --- a/framework/tests/compound-checker/MultiError.java +++ b/framework/tests/compound-checker/MultiError.java @@ -2,11 +2,11 @@ import org.checkerframework.common.value.qual.StringVal; public class MultiError { - // Testing that errors from multiple checkers are issued - // on the same compilation unit - // :: error: (unique.location.forbidden) - @Unique String[] array; + // Testing that errors from multiple checkers are issued + // on the same compilation unit + // :: error: (unique.location.forbidden) + @Unique String[] array; - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "goodbye"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "goodbye"; } diff --git a/framework/tests/compound-checker/javac-error/JavaErrorTest.java b/framework/tests/compound-checker/javac-error/JavaErrorTest.java index 478fe489666..eb8776fa3e5 100644 --- a/framework/tests/compound-checker/javac-error/JavaErrorTest.java +++ b/framework/tests/compound-checker/javac-error/JavaErrorTest.java @@ -1,12 +1,12 @@ public class JavaErrorTest { - // Checking that if one checker finds a Java error - // and therefor does not set a root, then - // checkers relying on that checker should also not run. - // otherwise, it might access a subchecker whose "root" has not been set. - // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) - void foo() { - Object a; - // :: error: variable a is already defined in method foo() - Object a; - } + // Checking that if one checker finds a Java error + // and therefor does not set a root, then + // checkers relying on that checker should also not run. + // otherwise, it might access a subchecker whose "root" has not been set. + // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) + void foo() { + Object a; + // :: error: variable a is already defined in method foo() + Object a; + } } diff --git a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java index b73ea47242b..5172497a42b 100644 --- a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java +++ b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java @@ -3,248 +3,248 @@ import org.checkerframework.framework.testchecker.util.SuperQual; public class AnnotatedForTest { - /* - Test a mix of @SuppressWarnings with @AnnotatedFor. @SuppressWarnings should win, but only within the kinds of warnings it promises to suppress. It should win because - it is a specific intent of suppressing warnings, whereas NOT suppressing warnings using AnnotatedFor is a default behavior, and SW is a user-specified behavior. - */ + /* + Test a mix of @SuppressWarnings with @AnnotatedFor. @SuppressWarnings should win, but only within the kinds of warnings it promises to suppress. It should win because + it is a specific intent of suppressing warnings, whereas NOT suppressing warnings using AnnotatedFor is a default behavior, and SW is a user-specified behavior. + */ + + // Test unannotated class initializer - no warnings should be issued + @SuperQual Object o1 = annotatedMethod(new Object()); + @SubQual Object o2 = annotatedMethod(new Object()); + Object o3 = unannotatedMethod(o2); + Object o4 = unannotatedMethod(o1); + + static @SuperQual Object so1; + static @SubQual Object so2; + static Object so3, so4; + + // Test unannotated static initializer block - no warnings should be issued + static { + so1 = staticAnnotatedMethod(new Object()); + so2 = staticAnnotatedMethod(new Object()); + so3 = staticUnannotatedMethod(so2); + so4 = staticUnannotatedMethod(so1); + } + + @SuperQual Object o5; + @SubQual Object o6; + Object o7, o8; + + // Test unannotated nonstatic initializer block - no warnings should be issued + { + o5 = annotatedMethod(new Object()); + o6 = annotatedMethod(new Object()); + o7 = unannotatedMethod(o6); + o8 = unannotatedMethod(o5); + } + + // This method is @AnnotatedFor("subtyping") so it can cause errors to be issued by calling + // other methods. + @AnnotatedFor("subtyping") + void method1() { + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. + @SuperQual Object o1 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = annotatedMethod(new Object()); - // Test unannotated class initializer - no warnings should be issued + // When calling unannotatedMethod, we expect the conservative defaults. + Object o3 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o4 = unannotatedMethod(o1); + + // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor + // annotation. + Object o5 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o6 = unannotatedMethod(o1); + + // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) + Object o7 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o8 = unannotatedMethod(o1); + } + + @SuppressWarnings("all") + @AnnotatedFor( + "subtyping") // Same as method1, but the @SuppressWarnings overrides the @AnnotatedFor. + void method2() { + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. @SuperQual Object o1 = annotatedMethod(new Object()); @SubQual Object o2 = annotatedMethod(new Object()); + + // When calling unannotatedMethod, we expect the conservative defaults. Object o3 = unannotatedMethod(o2); Object o4 = unannotatedMethod(o1); - static @SuperQual Object so1; - static @SubQual Object so2; - static Object so3, so4; + // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor + // annotation. + Object o5 = unannotatedMethod(o2); + Object o6 = unannotatedMethod(o1); + + // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) + Object o7 = unannotatedMethod(o2); + Object o8 = unannotatedMethod(o1); + } + + @SuppressWarnings("nullness") + @AnnotatedFor("subtyping") // Similar to method1. The @SuppressWarnings does not override the + // @AnnotatedFor because it suppressing warnings for a different typesystem. + void method3() { + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. + @SuperQual Object o1 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = annotatedMethod(new Object()); + } + + @AnnotatedFor("subtyping") + Object annotatedMethod(Object p) { + return new Object(); + } + + Object unannotatedMethod(Object p) { + return new Object(); + } + + @AnnotatedFor("subtyping") + static Object staticAnnotatedMethod(Object p) { + return new Object(); + } + + static Object staticUnannotatedMethod(Object p) { + return new Object(); + } + + @AnnotatedFor({}) + Object unannotatedMethod2(Object p) { + return new Object(); + } + + @AnnotatedFor("nullness") + Object annotatedForADifferentTypeSystemMethod(Object p) { + return new Object(); + } + + // Annotated for more than one type system + @AnnotatedFor({"nullness", "subtyping"}) + void method4() { + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); + } + + // Different way of writing the checker name + @AnnotatedFor("SubtypingChecker") + void method5() { + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); + } + + // Different way of writing the checker name + @AnnotatedFor("org.checkerframework.common.subtyping.SubtypingChecker") + void method6() { + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); + } + + // Every method in this class should issue warnings for subtyping even if it's not marked with + // @AnnotatedFor, unless it's marked with @SuppressWarnings. + @AnnotatedFor("subtyping") + class annotatedClass { + // Test annotated class initializer + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. + @SuperQual Object o1 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = annotatedMethod(new Object()); - // Test unannotated static initializer block - no warnings should be issued - static { - so1 = staticAnnotatedMethod(new Object()); - so2 = staticAnnotatedMethod(new Object()); - so3 = staticUnannotatedMethod(so2); - so4 = staticUnannotatedMethod(so1); - } + // When calling unannotatedMethod, we expect the conservative defaults. + Object o3 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o4 = unannotatedMethod(o1); @SuperQual Object o5; @SubQual Object o6; Object o7, o8; - // Test unannotated nonstatic initializer block - no warnings should be issued + // Test annotated nonstatic initializer block { - o5 = annotatedMethod(new Object()); - o6 = annotatedMethod(new Object()); - o7 = unannotatedMethod(o6); - o8 = unannotatedMethod(o5); + o5 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + o6 = annotatedMethod(new Object()); + o7 = unannotatedMethod(o6); + // :: error: (argument.type.incompatible) + o8 = unannotatedMethod(o5); } - // This method is @AnnotatedFor("subtyping") so it can cause errors to be issued by calling - // other methods. - @AnnotatedFor("subtyping") void method1() { - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. - @SuperQual Object o1 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = annotatedMethod(new Object()); - - // When calling unannotatedMethod, we expect the conservative defaults. - Object o3 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o4 = unannotatedMethod(o1); - - // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor - // annotation. - Object o5 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o6 = unannotatedMethod(o1); - - // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) - Object o7 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o8 = unannotatedMethod(o1); + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); } @SuppressWarnings("all") - @AnnotatedFor( - "subtyping") // Same as method1, but the @SuppressWarnings overrides the @AnnotatedFor. void method2() { - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. - @SuperQual Object o1 = annotatedMethod(new Object()); - @SubQual Object o2 = annotatedMethod(new Object()); - - // When calling unannotatedMethod, we expect the conservative defaults. - Object o3 = unannotatedMethod(o2); - Object o4 = unannotatedMethod(o1); - - // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor - // annotation. - Object o5 = unannotatedMethod(o2); - Object o6 = unannotatedMethod(o1); - - // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) - Object o7 = unannotatedMethod(o2); - Object o8 = unannotatedMethod(o1); - } - - @SuppressWarnings("nullness") - @AnnotatedFor("subtyping") // Similar to method1. The @SuppressWarnings does not override the - // @AnnotatedFor because it suppressing warnings for a different typesystem. - void method3() { - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. - @SuperQual Object o1 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = annotatedMethod(new Object()); - } - - @AnnotatedFor("subtyping") - Object annotatedMethod(Object p) { - return new Object(); - } - - Object unannotatedMethod(Object p) { - return new Object(); - } - - @AnnotatedFor("subtyping") - static Object staticAnnotatedMethod(Object p) { - return new Object(); - } - - static Object staticUnannotatedMethod(Object p) { - return new Object(); + @SubQual Object o2 = new @SuperQual Object(); } + } - @AnnotatedFor({}) - Object unannotatedMethod2(Object p) { - return new Object(); - } - - @AnnotatedFor("nullness") - Object annotatedForADifferentTypeSystemMethod(Object p) { - return new Object(); - } - - // Annotated for more than one type system - @AnnotatedFor({"nullness", "subtyping"}) - void method4() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); - } + @AnnotatedFor("subtyping") + static class staticAnnotatedForClass { + static @SuperQual Object so1; + static @SubQual Object so2; + static Object so3, so4; - // Different way of writing the checker name - @AnnotatedFor("SubtypingChecker") - void method5() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); - } + // Test annotated static initializer block + static { + so1 = staticAnnotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + so2 = staticAnnotatedMethod(new Object()); + so3 = staticUnannotatedMethod(so2); + // :: error: (argument.type.incompatible) + so4 = staticUnannotatedMethod(so1); + } + } + + @SuppressWarnings("all") // @SuppressWarnings("all") overrides @AnnotatedFor("subtyping") + @AnnotatedFor("subtyping") + class annotatedAndWarningsSuppressedClass { + // Test annotated class initializer whose warnings are suppressed. + @SuperQual Object o1 = annotatedMethod(new Object()); + @SubQual Object o2 = annotatedMethod(new Object()); + Object o3 = unannotatedMethod(o2); + Object o4 = unannotatedMethod(o1); - // Different way of writing the checker name - @AnnotatedFor("org.checkerframework.common.subtyping.SubtypingChecker") - void method6() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); - } + @SuperQual Object o5; + @SubQual Object o6; + Object o7, o8; - // Every method in this class should issue warnings for subtyping even if it's not marked with - // @AnnotatedFor, unless it's marked with @SuppressWarnings. - @AnnotatedFor("subtyping") - class annotatedClass { - // Test annotated class initializer - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. - @SuperQual Object o1 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = annotatedMethod(new Object()); - - // When calling unannotatedMethod, we expect the conservative defaults. - Object o3 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o4 = unannotatedMethod(o1); - - @SuperQual Object o5; - @SubQual Object o6; - Object o7, o8; - - // Test annotated nonstatic initializer block - { - o5 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - o6 = annotatedMethod(new Object()); - o7 = unannotatedMethod(o6); - // :: error: (argument.type.incompatible) - o8 = unannotatedMethod(o5); - } - - void method1() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); - } - - @SuppressWarnings("all") - void method2() { - @SubQual Object o2 = new @SuperQual Object(); - } + // Test annotated nonstatic initializer block whose warnings are suppressed. + { + o5 = annotatedMethod(new Object()); + o6 = annotatedMethod(new Object()); + o7 = unannotatedMethod(o6); + o8 = unannotatedMethod(o5); } - @AnnotatedFor("subtyping") - static class staticAnnotatedForClass { - static @SuperQual Object so1; - static @SubQual Object so2; - static Object so3, so4; - - // Test annotated static initializer block - static { - so1 = staticAnnotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - so2 = staticAnnotatedMethod(new Object()); - so3 = staticUnannotatedMethod(so2); - // :: error: (argument.type.incompatible) - so4 = staticUnannotatedMethod(so1); - } + void method1() { + @SubQual Object o2 = new @SuperQual Object(); } + } - @SuppressWarnings("all") // @SuppressWarnings("all") overrides @AnnotatedFor("subtyping") - @AnnotatedFor("subtyping") - class annotatedAndWarningsSuppressedClass { - // Test annotated class initializer whose warnings are suppressed. - @SuperQual Object o1 = annotatedMethod(new Object()); - @SubQual Object o2 = annotatedMethod(new Object()); - Object o3 = unannotatedMethod(o2); - Object o4 = unannotatedMethod(o1); - - @SuperQual Object o5; - @SubQual Object o6; - Object o7, o8; - - // Test annotated nonstatic initializer block whose warnings are suppressed. - { - o5 = annotatedMethod(new Object()); - o6 = annotatedMethod(new Object()); - o7 = unannotatedMethod(o6); - o8 = unannotatedMethod(o5); - } - - void method1() { - @SubQual Object o2 = new @SuperQual Object(); - } - } + @SuppressWarnings("all") + @AnnotatedFor("subtyping") + static class staticAnnotatedAndWarningsSuppressedClass { + static @SuperQual Object so1; + static @SubQual Object so2; + static Object so3, so4; - @SuppressWarnings("all") - @AnnotatedFor("subtyping") - static class staticAnnotatedAndWarningsSuppressedClass { - static @SuperQual Object so1; - static @SubQual Object so2; - static Object so3, so4; - - // Test annotated static initializer block whose warnings are suppressed. - static { - so1 = staticAnnotatedMethod(new Object()); - so2 = staticAnnotatedMethod(new Object()); - so3 = staticUnannotatedMethod(so2); - so4 = staticUnannotatedMethod(so1); - } + // Test annotated static initializer block whose warnings are suppressed. + static { + so1 = staticAnnotatedMethod(new Object()); + so2 = staticAnnotatedMethod(new Object()); + so3 = staticUnannotatedMethod(so2); + so4 = staticUnannotatedMethod(so1); } + } } diff --git a/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java b/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java index 17ee82675c0..6c63f77d9a7 100644 --- a/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java +++ b/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java @@ -11,57 +11,57 @@ class MyExplicitArray {} public class LowerBoundDefaulting { - // IMP1 is of type IMP1 [extends @LbTop super @LbImplicit] - public void implicitsTypeVar() { + // IMP1 is of type IMP1 [extends @LbTop super @LbImplicit] + public void implicitsTypeVar() { - // should fail because @LbImplicit is below @LbTop - @LbTop MyArrayList<@LbTop ? extends @LbTop String> itLowerBoundIncompatible = - // :: error: (assignment.type.incompatible) - new MyArrayList(); + // should fail because @LbImplicit is below @LbTop + @LbTop MyArrayList<@LbTop ? extends @LbTop String> itLowerBoundIncompatible = + // :: error: (assignment.type.incompatible) + new MyArrayList(); - @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> itLowerBoundStillIncompatible = - // :: error: (assignment.type.incompatible) - new MyArrayList(); + @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> itLowerBoundStillIncompatible = + // :: error: (assignment.type.incompatible) + new MyArrayList(); - @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> itLowerBoundCompatible = - new MyArrayList(); - } + @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> itLowerBoundCompatible = + new MyArrayList(); + } - public void implicitsWildcard(MyArrayList myArrayList) { + public void implicitsWildcard(MyArrayList myArrayList) { - // should fail because @LbImplicit is below @LbTop - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbTop ? extends @LbTop CharSequence> iwLowerBoundIncompatible = myArrayList; + // should fail because @LbImplicit is below @LbTop + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbTop ? extends @LbTop CharSequence> iwLowerBoundIncompatible = myArrayList; - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbExplicit ? extends @LbTop CharSequence> iwLowerBoundCompatible = myArrayList; + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbExplicit ? extends @LbTop CharSequence> iwLowerBoundCompatible = myArrayList; - @LbTop MyArrayList<@LbImplicit ? extends @LbTop CharSequence> iwLowerBoundStillCompatible = - myArrayList; - } + @LbTop MyArrayList<@LbImplicit ? extends @LbTop CharSequence> iwLowerBoundStillCompatible = + myArrayList; + } - public void implicitExtendBoundedWildcard(MyArrayList iebList) { + public void implicitExtendBoundedWildcard(MyArrayList iebList) { - // should fail because @LbImplicit is below @LbTop - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbTop ? extends @LbTop String> iebLowerBoundIncompatible = iebList; + // should fail because @LbImplicit is below @LbTop + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbTop ? extends @LbTop String> iebLowerBoundIncompatible = iebList; - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> iebLowerBoundStillIncompatible = iebList; + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> iebLowerBoundStillIncompatible = iebList; - @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> iebLowerBoundCompatible = iebList; - } + @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> iebLowerBoundCompatible = iebList; + } - // MyArrayList<@LbTop MAL extends @LbTop CharSequence> - // elbLsit is MyArrayList<@LbTop ? super @LbExplicit String> - // capture is MyArrayList - // The super bound is the LUB of @LbTop and @LbExplicit. - public void explicitLowerBoundedWildcard(MyArrayList elbList) { - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbBottom ? super @LbBottom String> iebLowerBoundIncompatible = elbList; + // MyArrayList<@LbTop MAL extends @LbTop CharSequence> + // elbLsit is MyArrayList<@LbTop ? super @LbExplicit String> + // capture is MyArrayList + // The super bound is the LUB of @LbTop and @LbExplicit. + public void explicitLowerBoundedWildcard(MyArrayList elbList) { + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbBottom ? super @LbBottom String> iebLowerBoundIncompatible = elbList; - @LbTop MyArrayList<@LbTop ? super @LbExplicit String> iebLowerBoundStillIncompatible = elbList; + @LbTop MyArrayList<@LbTop ? super @LbExplicit String> iebLowerBoundStillIncompatible = elbList; - @LbTop MyArrayList<@LbTop ? super @LbImplicit String> iebLowerBoundCompatible = elbList; - } + @LbTop MyArrayList<@LbTop ? super @LbImplicit String> iebLowerBoundCompatible = elbList; + } } diff --git a/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java b/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java index 3f46afe6594..6e3767b96ac 100644 --- a/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java +++ b/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java @@ -13,48 +13,47 @@ class MyExplicitArray {} public class UpperBoundDefaulting { - public void explicitUpperBoundTypeVar() { - MyArrayList<@UbBottom ? extends @UbBottom Object> eubBottomToBottom = - // :: error: (assignment.type.incompatible) - new MyArrayList(); + public void explicitUpperBoundTypeVar() { + MyArrayList<@UbBottom ? extends @UbBottom Object> eubBottomToBottom = + // :: error: (assignment.type.incompatible) + new MyArrayList(); - MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = - new MyArrayList(); + MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = + new MyArrayList(); - MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = - // :: error: (assignment.type.incompatible) - new MyArrayList(); - } + MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = + // :: error: (assignment.type.incompatible) + new MyArrayList(); + } - public void implicitsWildcard(MyArrayList myArrayList) { + public void implicitsWildcard(MyArrayList myArrayList) { - @UbTop MyArrayList<@UbBottom ? extends @UbTop CharSequence> iwLowerBoundIncompatible = myArrayList; + @UbTop MyArrayList<@UbBottom ? extends @UbTop CharSequence> iwLowerBoundIncompatible = myArrayList; - @UbTop MyArrayList<@UbBottom ? extends @UbExplicit CharSequence> iwLowerBoundCompatible = - myArrayList; + @UbTop MyArrayList<@UbBottom ? extends @UbExplicit CharSequence> iwLowerBoundCompatible = myArrayList; - @UbTop MyArrayList<@UbBottom ? extends @UbImplicit CharSequence> iwLowerBoundStillCompatible = - // :: error: (assignment.type.incompatible) - myArrayList; - } + @UbTop MyArrayList<@UbBottom ? extends @UbImplicit CharSequence> iwLowerBoundStillCompatible = + // :: error: (assignment.type.incompatible) + myArrayList; + } - public void implicitExtendBoundedWildcard(MyArrayList iebList) { + public void implicitExtendBoundedWildcard(MyArrayList iebList) { - @UbTop MyArrayList<@UbBottom ? extends @UbTop String> iebLowerBoundIncompatible = iebList; + @UbTop MyArrayList<@UbBottom ? extends @UbTop String> iebLowerBoundIncompatible = iebList; - MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = iebList; + MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = iebList; - // :: error: (assignment.type.incompatible) - MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = iebList; - } + // :: error: (assignment.type.incompatible) + MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = iebList; + } - public void explicitLowerBoundedWildcard(MyArrayList elbList) { - @UbTop MyArrayList<@UbTop ? super @UbBottom String> iebLowerBoundIncompatible = elbList; + public void explicitLowerBoundedWildcard(MyArrayList elbList) { + @UbTop MyArrayList<@UbTop ? super @UbBottom String> iebLowerBoundIncompatible = elbList; - // Upper bound: GLB(@UbExplicit, @UbImplicit), Lower bound: @UbBottom. - // :: error: (assignment.type.incompatible) - @UbTop MyArrayList<@UbImplicit ? super @UbBottom String> iebLowerBoundStillIncompatible = elbList; + // Upper bound: GLB(@UbExplicit, @UbImplicit), Lower bound: @UbBottom. + // :: error: (assignment.type.incompatible) + @UbTop MyArrayList<@UbImplicit ? super @UbBottom String> iebLowerBoundStillIncompatible = elbList; - @UbTop MyArrayList<@UbExplicit ? super @UbBottom String> iebLowerBoundCompatible = elbList; - } + @UbTop MyArrayList<@UbExplicit ? super @UbBottom String> iebLowerBoundCompatible = elbList; + } } diff --git a/framework/tests/flow/AnnotationAliasing.java b/framework/tests/flow/AnnotationAliasing.java index df32dc0b22f..f75bda997e4 100644 --- a/framework/tests/flow/AnnotationAliasing.java +++ b/framework/tests/flow/AnnotationAliasing.java @@ -5,42 +5,42 @@ /** Various tests for annotation aliasing. */ public class AnnotationAliasing { - String f1, f2, f3; - - @Pure - int pure1() { - return 1; - } - - @org.jmlspecs.annotation.Pure - int pure2() { - return 1; - } - - // a method that is not pure (no annotation) - void nonpure() {} - - @Pure - String t1() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - nonpure(); - return ""; - } - - @org.jmlspecs.annotation.Pure - String t2() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - nonpure(); - return ""; - } - - // check aliasing of Pure - void t1(@Odd String p1, String p2) { - f1 = p1; - @Odd String l2 = f1; - pure1(); - @Odd String l3 = f1; - pure2(); - @Odd String l4 = f1; - } + String f1, f2, f3; + + @Pure + int pure1() { + return 1; + } + + @org.jmlspecs.annotation.Pure + int pure2() { + return 1; + } + + // a method that is not pure (no annotation) + void nonpure() {} + + @Pure + String t1() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + nonpure(); + return ""; + } + + @org.jmlspecs.annotation.Pure + String t2() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + nonpure(); + return ""; + } + + // check aliasing of Pure + void t1(@Odd String p1, String p2) { + f1 = p1; + @Odd String l2 = f1; + pure1(); + @Odd String l3 = f1; + pure2(); + @Odd String l4 = f1; + } } diff --git a/framework/tests/flow/ArrayFlow.java b/framework/tests/flow/ArrayFlow.java index f4a7e8948f2..321dc31872b 100644 --- a/framework/tests/flow/ArrayFlow.java +++ b/framework/tests/flow/ArrayFlow.java @@ -3,22 +3,22 @@ public class ArrayFlow { - // array accesses - void t1(@Odd String a1[], String a2[], @Odd String odd) { - String l1 = a1[0]; + // array accesses + void t1(@Odd String a1[], String a2[], @Odd String odd) { + String l1 = a1[0]; - // :: error: (assignment.type.incompatible) - @Odd String l2 = a2[0]; + // :: error: (assignment.type.incompatible) + @Odd String l2 = a2[0]; - if (a2[0] == odd) { - @Odd String l3 = a2[0]; - } - - int i = 1; - a2[i] = odd; - @Odd String l4 = a2[i]; - i = 2; - // :: error: (assignment.type.incompatible) - @Odd String l5 = a2[i]; + if (a2[0] == odd) { + @Odd String l3 = a2[0]; } + + int i = 1; + a2[i] = odd; + @Odd String l4 = a2[i]; + i = 2; + // :: error: (assignment.type.incompatible) + @Odd String l5 = a2[i]; + } } diff --git a/framework/tests/flow/Basic.java b/framework/tests/flow/Basic.java index e04f30286d3..87f1c1bd3ab 100644 --- a/framework/tests/flow/Basic.java +++ b/framework/tests/flow/Basic.java @@ -2,48 +2,48 @@ public class Basic { - @Odd String field; - - void test(@Odd String param) { - String local = ""; - local = param; - field = local; - - String r = field; + @Odd String field; + + void test(@Odd String param) { + String local = ""; + local = param; + field = local; + + String r = field; + } + + void testIf(@Odd String ifParam) { + String local = ""; + if (field != null) { + local = ifParam; + } else { + local = ifParam; } - void testIf(@Odd String ifParam) { - String local = ""; - if (field != null) { - local = ifParam; - } else { - local = ifParam; - } + String r = local; + } - String r = local; + void testWhile(@Odd String whileParam) { + String local = whileParam; + while (local != "foo") { + local = ""; } - void testWhile(@Odd String whileParam) { - String local = whileParam; - while (local != "foo") { - local = ""; - } + String r = local; + } - String r = local; + void testWhile2(@Odd String whileParam) { + String local = ""; + while (local != "foo") { + local = whileParam; } - void testWhile2(@Odd String whileParam) { - String local = ""; - while (local != "foo") { - local = whileParam; - } + String r = local; + } - String r = local; - } - - void testCompountAssignment(@Odd String odd) { - String nonOdd = odd; - nonOdd += "kj"; // nonOdd as rValue is not Odd necessarily! - nonOdd = "m"; - } + void testCompountAssignment(@Odd String odd) { + String nonOdd = odd; + nonOdd += "kj"; // nonOdd as rValue is not Odd necessarily! + nonOdd = "m"; + } } diff --git a/framework/tests/flow/Basic2.java b/framework/tests/flow/Basic2.java index 2827906bf00..6b38304cabb 100644 --- a/framework/tests/flow/Basic2.java +++ b/framework/tests/flow/Basic2.java @@ -1,235 +1,234 @@ +import java.util.List; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.framework.test.*; import org.checkerframework.framework.testchecker.util.*; -import java.util.List; - public class Basic2 { - // basic tests to make sure everything works - void t1(@Odd String p1, String p2) { - String l1 = p1; - // :: error: (assignment.type.incompatible) - @Odd String l2 = p2; - } - - // basic local variable flow sensitivity - void t2(@Odd String p1, String p2, boolean b1) { - String l1 = p1; - if (b1) { - l1 = p2; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = l1; - - l1 = p1; - while (b1) { - l1 = p2; - } - // :: error: (assignment.type.incompatible) - @Odd String l4 = l1; - } - - void t2b(@Odd String p1, String p2, @Odd String p3, boolean b1) { - String l1 = p1; - - if (b1) { - l1 = p3; - } - @Odd String l3 = l1; - - while (b1) { - l1 = p3; - } - @Odd String l4 = l1; - } - - // return statement - void t3(@Odd String p1, String p2, boolean b1) { - String l1 = p1; - if (b1) { - l1 = p2; - return; - } - @Odd String l3 = l1; - } - - // simple throw statement - void t4(@Odd String p1, String p2, boolean b1) { - String l1 = p1; - if (b1) { - l1 = p2; - throw new RuntimeException(); - } - @Odd String l3 = l1; - } - - class C { - C c; - String f1, f2, f3; - @Odd String g1, g2, g3; - } - - // fields - void t5(@Odd String p1, String p2, boolean b1, C c1, C c2) { - c1.f1 = p1; - @Odd String l1 = c1.f1; - c2.f2 = p2; // assignment to f2 does not change knowledge about f1 - c1.f2 = p2; - @Odd String l2 = c1.f1; - c2.f1 = p2; - // :: error: (assignment.type.incompatible) - @Odd String l3 = c1.f1; - } - - // fields - void t6(@Odd String p1, String p2, boolean b1, C c1, C c2) { - c1.f1 = p1; - c1.c.f1 = p1; - @Odd String l2 = c1.c.f1; - c1.f1 = p2; - // :: error: (assignment.type.incompatible) - @Odd String l3 = c1.f1; - - c1.f1 = p1; - c1.c.f1 = p1; - @Odd String l4 = c1.c.f1; - c2.c = c2; - // :: error: (assignment.type.incompatible) - @Odd String l5 = c1.f1; - // :: error: (assignment.type.incompatible) - @Odd String l6 = c1.c.f1; - } - - // fields - void t6b(@Odd String p1, String p2, boolean b1, C c1, C c2) { - if (b1) { - c1.f1 = p1; - } - // :: error: (assignment.type.incompatible) - @Odd String l1 = c1.f1; - - if (b1) { - c1.f1 = p1; - } else { - c1.f1 = p1; - } - @Odd String l2 = c1.f1; - } + // basic tests to make sure everything works + void t1(@Odd String p1, String p2) { + String l1 = p1; + // :: error: (assignment.type.incompatible) + @Odd String l2 = p2; + } + + // basic local variable flow sensitivity + void t2(@Odd String p1, String p2, boolean b1) { + String l1 = p1; + if (b1) { + l1 = p2; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = l1; + + l1 = p1; + while (b1) { + l1 = p2; + } + // :: error: (assignment.type.incompatible) + @Odd String l4 = l1; + } + + void t2b(@Odd String p1, String p2, @Odd String p3, boolean b1) { + String l1 = p1; + + if (b1) { + l1 = p3; + } + @Odd String l3 = l1; + + while (b1) { + l1 = p3; + } + @Odd String l4 = l1; + } + + // return statement + void t3(@Odd String p1, String p2, boolean b1) { + String l1 = p1; + if (b1) { + l1 = p2; + return; + } + @Odd String l3 = l1; + } + + // simple throw statement + void t4(@Odd String p1, String p2, boolean b1) { + String l1 = p1; + if (b1) { + l1 = p2; + throw new RuntimeException(); + } + @Odd String l3 = l1; + } + + class C { + C c; + String f1, f2, f3; + @Odd String g1, g2, g3; + } + + // fields + void t5(@Odd String p1, String p2, boolean b1, C c1, C c2) { + c1.f1 = p1; + @Odd String l1 = c1.f1; + c2.f2 = p2; // assignment to f2 does not change knowledge about f1 + c1.f2 = p2; + @Odd String l2 = c1.f1; + c2.f1 = p2; + // :: error: (assignment.type.incompatible) + @Odd String l3 = c1.f1; + } + + // fields + void t6(@Odd String p1, String p2, boolean b1, C c1, C c2) { + c1.f1 = p1; + c1.c.f1 = p1; + @Odd String l2 = c1.c.f1; + c1.f1 = p2; + // :: error: (assignment.type.incompatible) + @Odd String l3 = c1.f1; + + c1.f1 = p1; + c1.c.f1 = p1; + @Odd String l4 = c1.c.f1; + c2.c = c2; + // :: error: (assignment.type.incompatible) + @Odd String l5 = c1.f1; + // :: error: (assignment.type.incompatible) + @Odd String l6 = c1.c.f1; + } + + // fields + void t6b(@Odd String p1, String p2, boolean b1, C c1, C c2) { + if (b1) { + c1.f1 = p1; + } + // :: error: (assignment.type.incompatible) + @Odd String l1 = c1.f1; + + if (b1) { + c1.f1 = p1; + } else { + c1.f1 = p1; + } + @Odd String l2 = c1.f1; + } + + // method calls + void nonpure() {} + + @Pure + int pure() { + return 1; + } + + void t7(@Odd String p1, String p2, boolean b1, C c1, C c2) { + c1.f1 = p1; + nonpure(); + // :: error: (assignment.type.incompatible) + @Odd String l1 = c1.f1; + + c1.f1 = p1; + pure(); + @Odd String l2 = c1.f1; + } + + // array accesses + void t8(@Odd String a1[], String a2[], String p3) { + String l1 = a1[0]; + + // :: error: (assignment.type.incompatible) + @Odd String l2 = a2[0]; + + @Odd String l3 = l1; + + a1[0] = l1; + a1[1] = l3; + + // :: error: (assignment.type.incompatible) + a1[2] = p3; + + a2[0] = l1; + a2[1] = l3; + a2[2] = p3; + } + + // self type + void t9(@Odd Basic2 this) { + @Odd Basic2 l1 = this; + } + + // generics + public void t10a(T p1, @Odd T p2) { + T l1 = p1; + p1 = l1; + T l2 = p2; + p2 = l2; + // :: error: (assignment.type.incompatible) + @Odd T l3 = p1; + @Odd T l4 = p2; + } + + public void t10b(T p1, @Odd T p2) { + T l1 = p1; + p1 = l1; + T l2 = p2; + p2 = l2; + @Odd T l3 = p1; + @Odd T l4 = p2; + } + + // for-each loop + void t11(@Odd String p1, String p2, List list, List<@Odd String> oddList) { + // :: error: (enhancedfor.type.incompatible) + for (@Odd String i : list) {} + for (@Odd String i : oddList) { + @Odd String l1 = i; + } + for (@Odd String i : oddList) { + // :: error: (assignment.type.incompatible) + i = p2; + } + for (String i : oddList) { + // @Odd String l3 = i; + } + } + + // cast without annotations + void t12(@Odd String p1, String p2, boolean b1) { + @Odd String l1 = (String) p1; + } + + // final fields + class CF { + final String f1; + CF c; - // method calls void nonpure() {} - @Pure - int pure() { - return 1; + CF(@Odd String p1) { + f1 = p1; + nonpure(); + @Odd String l1 = f1; } - void t7(@Odd String p1, String p2, boolean b1, C c1, C c2) { - c1.f1 = p1; + void CF_t1(@Odd String p1, CF o) { + if (f1 == p1) { nonpure(); - // :: error: (assignment.type.incompatible) - @Odd String l1 = c1.f1; - - c1.f1 = p1; - pure(); - @Odd String l2 = c1.f1; - } - - // array accesses - void t8(@Odd String a1[], String a2[], String p3) { - String l1 = a1[0]; - - // :: error: (assignment.type.incompatible) - @Odd String l2 = a2[0]; - - @Odd String l3 = l1; - - a1[0] = l1; - a1[1] = l3; - - // :: error: (assignment.type.incompatible) - a1[2] = p3; - - a2[0] = l1; - a2[1] = l3; - a2[2] = p3; - } - - // self type - void t9(@Odd Basic2 this) { - @Odd Basic2 l1 = this; - } - - // generics - public void t10a(T p1, @Odd T p2) { - T l1 = p1; - p1 = l1; - T l2 = p2; - p2 = l2; - // :: error: (assignment.type.incompatible) - @Odd T l3 = p1; - @Odd T l4 = p2; - } - - public void t10b(T p1, @Odd T p2) { - T l1 = p1; - p1 = l1; - T l2 = p2; - p2 = l2; - @Odd T l3 = p1; - @Odd T l4 = p2; - } - - // for-each loop - void t11(@Odd String p1, String p2, List list, List<@Odd String> oddList) { - // :: error: (enhancedfor.type.incompatible) - for (@Odd String i : list) {} - for (@Odd String i : oddList) { - @Odd String l1 = i; - } - for (@Odd String i : oddList) { - // :: error: (assignment.type.incompatible) - i = p2; - } - for (String i : oddList) { - // @Odd String l3 = i; - } - } - - // cast without annotations - void t12(@Odd String p1, String p2, boolean b1) { - @Odd String l1 = (String) p1; - } - - // final fields - class CF { - final String f1; - CF c; - - void nonpure() {} - - CF(@Odd String p1) { - f1 = p1; - nonpure(); - @Odd String l1 = f1; - } - - void CF_t1(@Odd String p1, CF o) { - if (f1 == p1) { - nonpure(); - @Odd String l1 = f1; - } - } + @Odd String l1 = f1; + } } + } - // final fields with initializer - class A { - private final @Odd String f1 = null; - private final String f2 = f1; + // final fields with initializer + class A { + private final @Odd String f1 = null; + private final String f2 = f1; - void A_t1() { - @Odd String l1 = f2; - } + void A_t1() { + @Odd String l1 = f2; } + } } diff --git a/framework/tests/flow/ContractsOverriding.java b/framework/tests/flow/ContractsOverriding.java index b3a94eec230..d47363d2a7c 100644 --- a/framework/tests/flow/ContractsOverriding.java +++ b/framework/tests/flow/ContractsOverriding.java @@ -7,184 +7,184 @@ import org.checkerframework.framework.testchecker.util.RequiresOdd; public class ContractsOverriding { - static class Super { - String f, g; + static class Super { + String f, g; - void m1() {} + void m1() {} - void m2() {} + void m2() {} - @RequiresOdd("g") - void m3() {} + @RequiresOdd("g") + void m3() {} - @RequiresOdd("g") - void m3b() {} + @RequiresOdd("g") + void m3b() {} - @RequiresOdd("f") - void m4() {} + @RequiresOdd("f") + void m4() {} + } + + static class Sub extends Super { + String g; + + @Override + @RequiresOdd("f") + // :: error: (contracts.precondition.override.invalid) + void m1() {} + + @Override + @RequiresQualifier(expression = "f", qualifier = Odd.class) + // :: error: (contracts.precondition.override.invalid) + void m2() {} + + @Override + // g is a different field than in the supertype + @RequiresOdd("g") + // :: error: (contracts.precondition.override.invalid) + void m3() {} + + @Override + @RequiresOdd("super.g") + void m3b() {} + + @Override + // use different string to refer to 'f' and RequiresQualifier instead of RequiresOdd + @RequiresQualifier(expression = "this.f", qualifier = Odd.class) + void m4() {} + } + + static class Sub2 extends Super2 { + String g; + + @Override + // :: error: (contracts.postcondition.not.satisfied) + void m1() {} + + @Override + // :: error: (contracts.postcondition.not.satisfied) + void m2() {} + + @Override + @EnsuresOdd("g") + // :: error: (contracts.postcondition.override.invalid) + void m3() { + g = odd; + } + + @Override + @EnsuresOdd("f") + void m4() { + super.m4(); + } + } + + static class Super2 { + String f, g; + @Odd String odd; + + @EnsuresOdd("f") + void m1() { + f = odd; } - static class Sub extends Super { - String g; + @EnsuresQualifier(expression = "f", qualifier = Odd.class) + void m2() { + f = odd; + } - @Override - @RequiresOdd("f") - // :: error: (contracts.precondition.override.invalid) - void m1() {} + @EnsuresOdd("g") + void m3() { + g = odd; + } + + @EnsuresQualifier(expression = "this.f", qualifier = Odd.class) + void m4() { + f = odd; + } + } + + static class Sub3 extends Super3 { + String g; + + @Override + boolean m1() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @Override + boolean m2() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @Override + @EnsuresOddIf(expression = "g", result = true) + // :: error: (contracts.conditional.postcondition.true.override.invalid) + boolean m3() { + g = odd; + return true; + } + + @Override + @EnsuresOddIf(expression = "f", result = true) + boolean m4() { + return super.m4(); + } + + @Override + // invalid result + @EnsuresOddIf(expression = "f", result = false) + boolean m5() { + f = odd; + return true; + } + + @EnsuresOddIf(expression = "f", result = false) + boolean m6() { + f = odd; + return true; + } + + @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) + boolean m7() { + f = odd; + return true; + } + } + + static class Super3 { + String f, g; + @Odd String odd; + + @EnsuresOddIf(expression = "f", result = true) + boolean m1() { + f = odd; + return true; + } + + @EnsuresQualifierIf(expression = "f", qualifier = Odd.class, result = true) + boolean m2() { + f = odd; + return true; + } + + @EnsuresOddIf(expression = "g", result = true) + boolean m3() { + g = odd; + return true; + } + + @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) + boolean m4() { + f = odd; + return true; + } - @Override - @RequiresQualifier(expression = "f", qualifier = Odd.class) - // :: error: (contracts.precondition.override.invalid) - void m2() {} - - @Override - // g is a different field than in the supertype - @RequiresOdd("g") - // :: error: (contracts.precondition.override.invalid) - void m3() {} - - @Override - @RequiresOdd("super.g") - void m3b() {} - - @Override - // use different string to refer to 'f' and RequiresQualifier instead of RequiresOdd - @RequiresQualifier(expression = "this.f", qualifier = Odd.class) - void m4() {} - } - - static class Sub2 extends Super2 { - String g; - - @Override - // :: error: (contracts.postcondition.not.satisfied) - void m1() {} - - @Override - // :: error: (contracts.postcondition.not.satisfied) - void m2() {} - - @Override - @EnsuresOdd("g") - // :: error: (contracts.postcondition.override.invalid) - void m3() { - g = odd; - } - - @Override - @EnsuresOdd("f") - void m4() { - super.m4(); - } - } - - static class Super2 { - String f, g; - @Odd String odd; - - @EnsuresOdd("f") - void m1() { - f = odd; - } - - @EnsuresQualifier(expression = "f", qualifier = Odd.class) - void m2() { - f = odd; - } - - @EnsuresOdd("g") - void m3() { - g = odd; - } - - @EnsuresQualifier(expression = "this.f", qualifier = Odd.class) - void m4() { - f = odd; - } - } - - static class Sub3 extends Super3 { - String g; - - @Override - boolean m1() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @Override - boolean m2() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @Override - @EnsuresOddIf(expression = "g", result = true) - // :: error: (contracts.conditional.postcondition.true.override.invalid) - boolean m3() { - g = odd; - return true; - } - - @Override - @EnsuresOddIf(expression = "f", result = true) - boolean m4() { - return super.m4(); - } - - @Override - // invalid result - @EnsuresOddIf(expression = "f", result = false) - boolean m5() { - f = odd; - return true; - } - - @EnsuresOddIf(expression = "f", result = false) - boolean m6() { - f = odd; - return true; - } - - @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) - boolean m7() { - f = odd; - return true; - } - } - - static class Super3 { - String f, g; - @Odd String odd; - - @EnsuresOddIf(expression = "f", result = true) - boolean m1() { - f = odd; - return true; - } - - @EnsuresQualifierIf(expression = "f", qualifier = Odd.class, result = true) - boolean m2() { - f = odd; - return true; - } - - @EnsuresOddIf(expression = "g", result = true) - boolean m3() { - g = odd; - return true; - } - - @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) - boolean m4() { - f = odd; - return true; - } - - @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) - boolean m5() { - f = odd; - return true; - } + @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) + boolean m5() { + f = odd; + return true; } + } } diff --git a/framework/tests/flow/ContractsOverridingSubtyping.java b/framework/tests/flow/ContractsOverridingSubtyping.java index c520c3ffb20..c9f9f5ebfcf 100644 --- a/framework/tests/flow/ContractsOverridingSubtyping.java +++ b/framework/tests/flow/ContractsOverridingSubtyping.java @@ -5,73 +5,73 @@ import org.checkerframework.framework.testchecker.util.Odd; public class ContractsOverridingSubtyping { - static class Base { - String f; - @Odd String g; + static class Base { + String f; + @Odd String g; - @RequiresQualifier(expression = "f", qualifier = Odd.class) - void requiresOdd() {} + @RequiresQualifier(expression = "f", qualifier = Odd.class) + void requiresOdd() {} - @RequiresQualifier(expression = "f", qualifier = Unqualified.class) - void requiresUnqual() {} + @RequiresQualifier(expression = "f", qualifier = Unqualified.class) + void requiresUnqual() {} - @EnsuresQualifier(expression = "f", qualifier = Odd.class) - void ensuresOdd() { - f = g; - } + @EnsuresQualifier(expression = "f", qualifier = Odd.class) + void ensuresOdd() { + f = g; + } - @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) - void ensuresUnqual() {} + @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) + void ensuresUnqual() {} - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) - boolean ensuresIfOdd() { - f = g; - return true; - } + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) + boolean ensuresIfOdd() { + f = g; + return true; + } - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) - boolean ensuresIfUnqual() { - return true; - } + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) + boolean ensuresIfUnqual() { + return true; } + } - static class Derived extends Base { + static class Derived extends Base { - @Override - @RequiresQualifier(expression = "f", qualifier = Unqualified.class) - void requiresOdd() {} + @Override + @RequiresQualifier(expression = "f", qualifier = Unqualified.class) + void requiresOdd() {} - @Override - @RequiresQualifier(expression = "f", qualifier = Odd.class) - // :: error: (contracts.precondition.override.invalid) - void requiresUnqual() {} + @Override + @RequiresQualifier(expression = "f", qualifier = Odd.class) + // :: error: (contracts.precondition.override.invalid) + void requiresUnqual() {} - @Override - @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) - // :: error: (contracts.postcondition.override.invalid) - void ensuresOdd() { - f = g; - } + @Override + @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) + // :: error: (contracts.postcondition.override.invalid) + void ensuresOdd() { + f = g; + } - @Override - @EnsuresQualifier(expression = "f", qualifier = Odd.class) - void ensuresUnqual() { - f = g; - } + @Override + @EnsuresQualifier(expression = "f", qualifier = Odd.class) + void ensuresUnqual() { + f = g; + } - @Override - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) - // :: error: (contracts.conditional.postcondition.true.override.invalid) - boolean ensuresIfOdd() { - f = g; - return true; - } + @Override + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) + // :: error: (contracts.conditional.postcondition.true.override.invalid) + boolean ensuresIfOdd() { + f = g; + return true; + } - @Override - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) - boolean ensuresIfUnqual() { - f = g; - return true; - } + @Override + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) + boolean ensuresIfUnqual() { + f = g; + return true; } + } } diff --git a/framework/tests/flow/CustomContractWithArgs.java b/framework/tests/flow/CustomContractWithArgs.java index a22f00a42cf..c1d482a1c90 100644 --- a/framework/tests/flow/CustomContractWithArgs.java +++ b/framework/tests/flow/CustomContractWithArgs.java @@ -5,80 +5,80 @@ import org.checkerframework.framework.testchecker.util.ValueTypeAnno; public class CustomContractWithArgs { - // Postcondition for Value - @PostconditionAnnotation(qualifier = ValueTypeAnno.class) - @interface EnsuresValue { - public String[] value(); + // Postcondition for Value + @PostconditionAnnotation(qualifier = ValueTypeAnno.class) + @interface EnsuresValue { + public String[] value(); - @QualifierArgument("value") - public int targetValue(); - } + @QualifierArgument("value") + public int targetValue(); + } - // Conditional postcondition for LTLengthOf - @ConditionalPostconditionAnnotation(qualifier = ValueTypeAnno.class) - @interface EnsuresValueIf { - public boolean result(); + // Conditional postcondition for LTLengthOf + @ConditionalPostconditionAnnotation(qualifier = ValueTypeAnno.class) + @interface EnsuresValueIf { + public boolean result(); - public String[] expression(); + public String[] expression(); - @QualifierArgument("value") - public int targetValue(); - } + @QualifierArgument("value") + public int targetValue(); + } - // Precondition for LTLengthOf - @PreconditionAnnotation(qualifier = ValueTypeAnno.class) - @interface RequiresValue { - public String[] value(); + // Precondition for LTLengthOf + @PreconditionAnnotation(qualifier = ValueTypeAnno.class) + @interface RequiresValue { + public String[] value(); - @QualifierArgument("value") - public int targetValue(); - } + @QualifierArgument("value") + public int targetValue(); + } - class Base { - Object o; + class Base { + Object o; - @ValueTypeAnno(10) Object o10; + @ValueTypeAnno(10) Object o10; - @EnsuresValue(value = "o", targetValue = 10) - void ensures() { - o = o10; - } + @EnsuresValue(value = "o", targetValue = 10) + void ensures() { + o = o10; + } - @EnsuresValue(value = "o", targetValue = 9) - // :: error: (contracts.postcondition.not.satisfied) - void ensuresWrong() { - o = o10; - } + @EnsuresValue(value = "o", targetValue = 9) + // :: error: (contracts.postcondition.not.satisfied) + void ensuresWrong() { + o = o10; + } - void ensuresUse() { - ensures(); - o10 = o; - } + void ensuresUse() { + ensures(); + o10 = o; + } - @EnsuresValueIf(expression = "o", targetValue = 10, result = true) - boolean ensuresIf(boolean b) { - if (b) { - o = o10; - return true; - } else { - return false; - } - } + @EnsuresValueIf(expression = "o", targetValue = 10, result = true) + boolean ensuresIf(boolean b) { + if (b) { + o = o10; + return true; + } else { + return false; + } + } - @RequiresValue(value = "o", targetValue = 10) - void requires() { - o10 = o; - } + @RequiresValue(value = "o", targetValue = 10) + void requires() { + o10 = o; + } - void use(boolean b) { - if (ensuresIf(b)) { - o10 = o; - requires(); - } - // :: error: (assignment.type.incompatible) - o10 = o; - // :: error: (contracts.precondition.not.satisfied) - requires(); - } + void use(boolean b) { + if (ensuresIf(b)) { + o10 = o; + requires(); + } + // :: error: (assignment.type.incompatible) + o10 = o; + // :: error: (contracts.precondition.not.satisfied) + requires(); } + } } diff --git a/framework/tests/flow/Equal.java b/framework/tests/flow/Equal.java index 049a70f57a9..a73f74e9b36 100644 --- a/framework/tests/flow/Equal.java +++ b/framework/tests/flow/Equal.java @@ -2,42 +2,42 @@ public class Equal { - String f1, f2, f3; + String f1, f2, f3; - // annotation inference for equality - void t1(@Odd String p1, String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (f1 == p1) { - @Odd String l2 = f1; - } else { - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; - } + // annotation inference for equality + void t1(@Odd String p1, String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (f1 == p1) { + @Odd String l2 = f1; + } else { + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; } + } - // annotation inference for equality - void t2(@Odd String p1, String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (f1 != p1) { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } else { - @Odd String l3 = f1; - } + // annotation inference for equality + void t2(@Odd String p1, String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (f1 != p1) { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } else { + @Odd String l3 = f1; } + } - void t3(@Odd Long p1) { - // :: error: (assignment.type.incompatible) - @Odd Long l1 = Long.valueOf(2L); - if (Long.valueOf(2L) == p1) { - // The result of valueOf is not deterministic, so it can't be refined. - // :: error: (assignment.type.incompatible) - @Odd Long l2 = Long.valueOf(2L); - } else { - // :: error: (assignment.type.incompatible) - @Odd Long l3 = Long.valueOf(2L); - } + void t3(@Odd Long p1) { + // :: error: (assignment.type.incompatible) + @Odd Long l1 = Long.valueOf(2L); + if (Long.valueOf(2L) == p1) { + // The result of valueOf is not deterministic, so it can't be refined. + // :: error: (assignment.type.incompatible) + @Odd Long l2 = Long.valueOf(2L); + } else { + // :: error: (assignment.type.incompatible) + @Odd Long l3 = Long.valueOf(2L); } + } } diff --git a/framework/tests/flow/FieldShadowing.java b/framework/tests/flow/FieldShadowing.java index 0d86164d906..e9078bd4ff1 100644 --- a/framework/tests/flow/FieldShadowing.java +++ b/framework/tests/flow/FieldShadowing.java @@ -6,43 +6,43 @@ // various tests for the precondition mechanism public class FieldShadowing { - String f; + String f; - class Sub extends FieldShadowing { - String f; + class Sub extends FieldShadowing { + String f; - @Pure - @RequiresQualifier(expression = "f", qualifier = Odd.class) - int reqSub() { - @Odd String l2 = f; - // :: error: (assignment.type.incompatible) - @Odd String l1 = super.f; - int i; - i = 1; - return 1; - } + @Pure + @RequiresQualifier(expression = "f", qualifier = Odd.class) + int reqSub() { + @Odd String l2 = f; + // :: error: (assignment.type.incompatible) + @Odd String l1 = super.f; + int i; + i = 1; + return 1; + } - @Pure - @RequiresQualifier(expression = "super.f", qualifier = Odd.class) - int reqSuper() { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f; - @Odd String l1 = super.f; - return 1; - } + @Pure + @RequiresQualifier(expression = "super.f", qualifier = Odd.class) + int reqSuper() { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f; + @Odd String l1 = super.f; + return 1; + } - void t1(@Odd String p1) { - f = p1; - // :: error: (contracts.precondition.not.satisfied) - reqSuper(); - reqSub(); - } + void t1(@Odd String p1) { + f = p1; + // :: error: (contracts.precondition.not.satisfied) + reqSuper(); + reqSub(); + } - void t2(@Odd String p1) { - super.f = p1; - // :: error: (contracts.precondition.not.satisfied) - reqSub(); - reqSuper(); - } + void t2(@Odd String p1) { + super.f = p1; + // :: error: (contracts.precondition.not.satisfied) + reqSub(); + reqSuper(); } + } } diff --git a/framework/tests/flow/Fields.java b/framework/tests/flow/Fields.java index ebd57af8afe..1bc30a08f0b 100644 --- a/framework/tests/flow/Fields.java +++ b/framework/tests/flow/Fields.java @@ -1,18 +1,18 @@ public class Fields { - // Not final field - String s = null; + // Not final field + String s = null; - void setString(String s) { - this.s = s; - } + void setString(String s) { + this.s = s; + } - // Test flow for fields but in different receivers - void others() { - Fields withOddField = null; - Fields notOddField = null; + // Test flow for fields but in different receivers + void others() { + Fields withOddField = null; + Fields notOddField = null; - withOddField.s = null; - notOddField.s = "m"; - } + withOddField.s = null; + notOddField.s = "m"; + } } diff --git a/framework/tests/flow/Issue4449.java b/framework/tests/flow/Issue4449.java index 81b3447d1b6..32ad27dfe06 100644 --- a/framework/tests/flow/Issue4449.java +++ b/framework/tests/flow/Issue4449.java @@ -2,32 +2,32 @@ public class Issue4449 { - @SideEffectFree - public void test1(long[] x) { - // :: error: (purity.not.sideeffectfree.assign.array) - x[0] = 1; + @SideEffectFree + public void test1(long[] x) { + // :: error: (purity.not.sideeffectfree.assign.array) + x[0] = 1; - long y; + long y; - // :: error: (purity.not.sideeffectfree.assign.array) - ++x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - --x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - x[0]++; - // :: error: (purity.not.sideeffectfree.assign.array) - x[0]--; + // :: error: (purity.not.sideeffectfree.assign.array) + ++x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + --x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + x[0]++; + // :: error: (purity.not.sideeffectfree.assign.array) + x[0]--; - // :: error: (purity.not.sideeffectfree.assign.array) - y = ++x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - y = --x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - y = x[0]++; - // :: error: (purity.not.sideeffectfree.assign.array) - y = x[0]--; + // :: error: (purity.not.sideeffectfree.assign.array) + y = ++x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + y = --x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + y = x[0]++; + // :: error: (purity.not.sideeffectfree.assign.array) + y = x[0]--; - y = +x[0]; - y = -x[0]; - } + y = +x[0]; + y = -x[0]; + } } diff --git a/framework/tests/flow/Issue951.java b/framework/tests/flow/Issue951.java index 2c05b3ca9e8..55d1f4fcfd1 100644 --- a/framework/tests/flow/Issue951.java +++ b/framework/tests/flow/Issue951.java @@ -7,126 +7,126 @@ public class Issue951 { - @Pure - public static int min(int[] a) { - if (a.length == 0) { - throw new MyExceptionSefConstructor("Empty array passed to min(int[])"); + @Pure + public static int min(int[] a) { + if (a.length == 0) { + throw new MyExceptionSefConstructor("Empty array passed to min(int[])"); + } + int result = a[0]; + for (int i = 1; i < a.length; i++) { + result = min(result, a[i]); + } + return result; + } + + @Pure + public static int arbitraryExceptionArg1() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + arbitraryMethod()); + } + + @Pure + public static int arbitraryExceptionArg2() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + throw new MyExceptionSefConstructor("" + arbitraryMethod()); + } + + @Pure + public static int sefExceptionArg1() { + // The method is safe, so this is a false positive warning; + // in the future the Purity Checker may not issue this warning. + // :: error: (purity.not.deterministic.call) + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + sefMethod()); + } + + @Pure + public static int sefExceptionArg2() { + // The method is safe, so this is a false positive warning; + // in the future the Purity Checker may not issue this warning. + // :: error: (purity.not.deterministic.call) + throw new MyExceptionSefConstructor("" + sefMethod()); + } + + @Pure + public static int detExceptionArg1() { + // :: error: (purity.not.sideeffectfree.call) + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + detMethod()); + } + + @Pure + public static int detExceptionArg2() { + // :: error: (purity.not.sideeffectfree.call) + throw new MyExceptionSefConstructor("" + detMethod()); + } + + @Pure + public static int pureExceptionArg1(int a, int b) { + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + min(a, b)); + } + + @Pure + public static int pureExceptionArg2(int a, int b) { + throw new MyExceptionSefConstructor("" + min(a, b)); + } + + @Pure + int throwNewWithinTry() { + for (int i = 0; i < 10; i++) { + try { + for (int j = 0; j < 10; j++) { + throw new MyExceptionSefConstructor("foo"); } - int result = a[0]; - for (int i = 1; i < a.length; i++) { - result = min(result, a[i]); - } - return result; - } - - @Pure - public static int arbitraryExceptionArg1() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + arbitraryMethod()); - } - - @Pure - public static int arbitraryExceptionArg2() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - throw new MyExceptionSefConstructor("" + arbitraryMethod()); - } - - @Pure - public static int sefExceptionArg1() { - // The method is safe, so this is a false positive warning; - // in the future the Purity Checker may not issue this warning. - // :: error: (purity.not.deterministic.call) - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + sefMethod()); + // :: error: (purity.not.deterministic.catch) + } catch (MyExceptionSefConstructor e) { + return -1; + } } + return 22; + } - @Pure - public static int sefExceptionArg2() { - // The method is safe, so this is a false positive warning; - // in the future the Purity Checker may not issue this warning. - // :: error: (purity.not.deterministic.call) - throw new MyExceptionSefConstructor("" + sefMethod()); - } - - @Pure - public static int detExceptionArg1() { - // :: error: (purity.not.sideeffectfree.call) - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + detMethod()); - } - - @Pure - public static int detExceptionArg2() { - // :: error: (purity.not.sideeffectfree.call) - throw new MyExceptionSefConstructor("" + detMethod()); - } - - @Pure - public static int pureExceptionArg1(int a, int b) { - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + min(a, b)); - } + // Helper methods - @Pure - public static int pureExceptionArg2(int a, int b) { - throw new MyExceptionSefConstructor("" + min(a, b)); - } + // Not deterministic or side-effect-free + static int arbitraryMethod() { + return 22; + } - @Pure - int throwNewWithinTry() { - for (int i = 0; i < 10; i++) { - try { - for (int j = 0; j < 10; j++) { - throw new MyExceptionSefConstructor("foo"); - } - // :: error: (purity.not.deterministic.catch) - } catch (MyExceptionSefConstructor e) { - return -1; - } - } - return 22; - } + // Not deterministic + @SideEffectFree + static int sefMethod() { + return 22; + } - // Helper methods + @Deterministic + // Not side-effect-free + static int detMethod() { + return 22; + } - // Not deterministic or side-effect-free - static int arbitraryMethod() { - return 22; + @Pure + static int min(int a, int b) { + if (a < b) { + return a; + } else { + return b; } + } - // Not deterministic - @SideEffectFree - static int sefMethod() { - return 22; - } + // Constructors - @Deterministic + static class MyException extends Error { // Not side-effect-free - static int detMethod() { - return 22; - } - - @Pure - static int min(int a, int b) { - if (a < b) { - return a; - } else { - return b; - } - } + MyException(String message) {} + } - // Constructors - - static class MyException extends Error { - // Not side-effect-free - MyException(String message) {} - } - - static class MyExceptionSefConstructor extends Error { - // Side-effect-free - @SuppressWarnings("purity.not.sideeffectfree.call") // until java.util.Error is annotated - @SideEffectFree - MyExceptionSefConstructor(String message) {} - } + static class MyExceptionSefConstructor extends Error { + // Side-effect-free + @SuppressWarnings("purity.not.sideeffectfree.call") // until java.util.Error is annotated + @SideEffectFree + MyExceptionSefConstructor(String message) {} + } } diff --git a/framework/tests/flow/MetaPostcondition.java b/framework/tests/flow/MetaPostcondition.java index 9fb9a5951ae..4467a592e0d 100644 --- a/framework/tests/flow/MetaPostcondition.java +++ b/framework/tests/flow/MetaPostcondition.java @@ -3,170 +3,170 @@ public class MetaPostcondition { - String f1, f2, f3; - MetaPostcondition p; - - /** *** normal postcondition ***** */ - @EnsuresOdd("f1") - void oddF1() { - f1 = null; - } - - @EnsuresOdd("p.f1") - void oddF1_1() { - p.f1 = null; - } - - @EnsuresOdd("#1.f1") - void oddF1_2(final MetaPostcondition param) { - param.f1 = null; - } - - @EnsuresOdd("f1") - // :: error: (contracts.postcondition.not.satisfied) - void oddF1_error() {} - - @EnsuresOdd("---") - // :: error: (flowexpr.parse.error) - void error() {} - - @EnsuresOdd("#1.#2") - // :: error: (flowexpr.parse.error) - void error2(final String p1, final String p2) {} - - @EnsuresOdd("f1") - void exception() { - throw new RuntimeException(); - } - - @EnsuresOdd("#1") - void param1(final @Odd String f) {} - - @EnsuresOdd({"#1", "#2"}) - // :: error: (flowexpr.parameter.not.final) - void param2(@Odd String f, @Odd String g) { - f = g; - } - - @EnsuresOdd("#1") + String f1, f2, f3; + MetaPostcondition p; + + /** *** normal postcondition ***** */ + @EnsuresOdd("f1") + void oddF1() { + f1 = null; + } + + @EnsuresOdd("p.f1") + void oddF1_1() { + p.f1 = null; + } + + @EnsuresOdd("#1.f1") + void oddF1_2(final MetaPostcondition param) { + param.f1 = null; + } + + @EnsuresOdd("f1") + // :: error: (contracts.postcondition.not.satisfied) + void oddF1_error() {} + + @EnsuresOdd("---") + // :: error: (flowexpr.parse.error) + void error() {} + + @EnsuresOdd("#1.#2") + // :: error: (flowexpr.parse.error) + void error2(final String p1, final String p2) {} + + @EnsuresOdd("f1") + void exception() { + throw new RuntimeException(); + } + + @EnsuresOdd("#1") + void param1(final @Odd String f) {} + + @EnsuresOdd({"#1", "#2"}) + // :: error: (flowexpr.parameter.not.final) + void param2(@Odd String f, @Odd String g) { + f = g; + } + + @EnsuresOdd("#1") + // :: error: (flowexpr.parse.index.too.big) + void param3() {} + + // basic postcondition test + void t1(@Odd String p1, String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + oddF1(); + @Odd String l2 = f1; + + // :: error: (flowexpr.parse.error.postcondition) + error(); + } + + // test parameter syntax + void t2(@Odd String p1, String p2) { // :: error: (flowexpr.parse.index.too.big) - void param3() {} - - // basic postcondition test - void t1(@Odd String p1, String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - oddF1(); - @Odd String l2 = f1; - - // :: error: (flowexpr.parse.error.postcondition) - error(); - } - - // test parameter syntax - void t2(@Odd String p1, String p2) { - // :: error: (flowexpr.parse.index.too.big) - param3(); - } - - // postcondition with more complex expression - void tn1(boolean b) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p.f1; - oddF1_1(); - @Odd String l2 = p.f1; - } - - // postcondition with more complex expression - void tn2(boolean b) { - MetaPostcondition param = null; - // :: error: (assignment.type.incompatible) - @Odd String l1 = param.f1; - oddF1_2(param); - @Odd String l2 = param.f1; - } - - // basic postcondition test - void tnm1(@Odd String p1, @ValueTypeAnno String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = f2; - - // :: error: (flowexpr.parse.error.postcondition) - error2(p1, p2); - } - - /** conditional postcondition */ - @EnsuresOddIf(result = true, expression = "f1") - boolean condOddF1(boolean b) { - if (b) { - f1 = null; - return true; - } - return false; - } - - @EnsuresOddIf(result = false, expression = "f1") - boolean condOddF1False(boolean b) { - if (b) { - return true; - } - f1 = null; - return false; - } - - @EnsuresOddIf(result = false, expression = "f1") - boolean condOddF1Invalid(boolean b) { - if (b) { - f1 = null; - return true; - } - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - - @EnsuresOddIf(result = false, expression = "f1") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - void wrongReturnType() {} - - @EnsuresOddIf(result = false, expression = "f1") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - String wrongReturnType2() { - f1 = null; - return ""; - } - - // basic conditional postcondition test - void t3(@Odd String p1, String p2) { - condOddF1(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (condOddF1(false)) { - @Odd String l2 = f1; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; - } - - // basic conditional postcondition test (inverted) - void t4(@Odd String p1, String p2) { - condOddF1False(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (!condOddF1False(false)) { - @Odd String l2 = f1; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; - } - - // basic conditional postcondition test 2 - void t5(boolean b) { - condOddF1(true); - if (b) { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } - } + param3(); + } + + // postcondition with more complex expression + void tn1(boolean b) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p.f1; + oddF1_1(); + @Odd String l2 = p.f1; + } + + // postcondition with more complex expression + void tn2(boolean b) { + MetaPostcondition param = null; + // :: error: (assignment.type.incompatible) + @Odd String l1 = param.f1; + oddF1_2(param); + @Odd String l2 = param.f1; + } + + // basic postcondition test + void tnm1(@Odd String p1, @ValueTypeAnno String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = f2; + + // :: error: (flowexpr.parse.error.postcondition) + error2(p1, p2); + } + + /** conditional postcondition */ + @EnsuresOddIf(result = true, expression = "f1") + boolean condOddF1(boolean b) { + if (b) { + f1 = null; + return true; + } + return false; + } + + @EnsuresOddIf(result = false, expression = "f1") + boolean condOddF1False(boolean b) { + if (b) { + return true; + } + f1 = null; + return false; + } + + @EnsuresOddIf(result = false, expression = "f1") + boolean condOddF1Invalid(boolean b) { + if (b) { + f1 = null; + return true; + } + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + + @EnsuresOddIf(result = false, expression = "f1") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + void wrongReturnType() {} + + @EnsuresOddIf(result = false, expression = "f1") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + String wrongReturnType2() { + f1 = null; + return ""; + } + + // basic conditional postcondition test + void t3(@Odd String p1, String p2) { + condOddF1(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (condOddF1(false)) { + @Odd String l2 = f1; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test (inverted) + void t4(@Odd String p1, String p2) { + condOddF1False(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (!condOddF1False(false)) { + @Odd String l2 = f1; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test 2 + void t5(boolean b) { + condOddF1(true); + if (b) { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } + } } diff --git a/framework/tests/flow/MetaPrecondition.java b/framework/tests/flow/MetaPrecondition.java index d04bb08461c..ffaea8f5dd0 100644 --- a/framework/tests/flow/MetaPrecondition.java +++ b/framework/tests/flow/MetaPrecondition.java @@ -5,78 +5,78 @@ // Tests for the meta-annotations for contracts. public class MetaPrecondition { - String f1, f2, f3; - MetaPrecondition p; + String f1, f2, f3; + MetaPrecondition p; - @RequiresOdd("f1") - void requiresF1() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - } + @RequiresOdd("f1") + void requiresF1() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + } - @Pure - @RequiresOdd("f1") - int requiresF1AndPure() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - return 1; - } + @Pure + @RequiresOdd("f1") + int requiresF1AndPure() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + return 1; + } - @RequiresOdd("---") - // :: error: (flowexpr.parse.error) - void error() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } + @RequiresOdd("---") + // :: error: (flowexpr.parse.error) + void error() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } - @RequiresOdd("#1") - void requiresParam(String p) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p; - @Odd String l2 = p; - } + @RequiresOdd("#1") + void requiresParam(String p) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p; + @Odd String l2 = p; + } - @RequiresOdd({"#1", "#2"}) - void requiresParams(String p1, String p2) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = p2; - @Odd String l3 = p1; - @Odd String l4 = p2; - } + @RequiresOdd({"#1", "#2"}) + void requiresParams(String p1, String p2) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = p2; + @Odd String l3 = p1; + @Odd String l4 = p2; + } - @RequiresOdd("#1") - // :: error: (flowexpr.parse.index.too.big) - void param3() {} + @RequiresOdd("#1") + // :: error: (flowexpr.parse.index.too.big) + void param3() {} - void t1(@Odd String p1, String p2) { - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresParam(p2); - // :: error: (contracts.precondition.not.satisfied) - requiresParams(p1, p2); - } + void t1(@Odd String p1, String p2) { + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresParam(p2); + // :: error: (contracts.precondition.not.satisfied) + requiresParams(p1, p2); + } - void t2(@Odd String p1, String p2) { - f1 = p1; - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - } + void t2(@Odd String p1, String p2) { + f1 = p1; + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + } - void t3(@Odd String p1, String p2) { - f1 = p1; - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - } + void t3(@Odd String p1, String p2) { + f1 = p1; + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + } } diff --git a/framework/tests/flow/MethodCallFlowExpr.java b/framework/tests/flow/MethodCallFlowExpr.java index 229f89163e5..0015ca90402 100644 --- a/framework/tests/flow/MethodCallFlowExpr.java +++ b/framework/tests/flow/MethodCallFlowExpr.java @@ -4,194 +4,194 @@ public class MethodCallFlowExpr { - @Pure - String p1(int i) { - return ""; - } - - @Pure - String p1b(Long i) { - return ""; - } - - @Pure - String p1(String s) { - return ""; - } - - String nonpure() { - return ""; - } - - @Pure - String p2(String s, long l, String s2) { - return ""; - } - - @Pure - String p1c(T i) { - return ""; - } - - @Pure - static String p1d(int i) { - return ""; - } - - @EnsuresQualifier(expression = "p1c(\"abc\")", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e0() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e1() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1(\"abc\")", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e2() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p2(\"abc\", 2L, p1(1))", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e4() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1b(2L)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e5() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1b(null)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e6() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1d(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e7a() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "this.p1d(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e7b() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "MethodCallFlowExpr.p1d(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e7c() { - // don't bother with implementation - } - - void t1() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1(1); - e1(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1(2); - @Odd String l3 = p1(1); - } - - void t2() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1("abc"); - e2(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1("def"); - // :: error: (assignment.type.incompatible) - @Odd String l2b = p1(1); - @Odd String l3 = p1("abc"); - } - - void t3() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p2("abc", 2L, p1(1)); - e4(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p2("abc", 2L, p1(2)); - // :: error: (assignment.type.incompatible) - @Odd String l2b = p2("abc", 1L, p1(1)); - @Odd String l3 = p2("abc", 2L, p1(1)); - } - - void t4() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1b(2L); - e5(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1b(null); - // :: error: (assignment.type.incompatible) - @Odd String l2b = p1b(1L); - // FIXME: the following line shouldn't give an error - // (or at least it didn't give an error earlier) - // @Odd String l3 = p1b(2L); - } - - void t5() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1b(null); - e6(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1b(1L); - // FIXME: the following line shouldn't give an error - // (or at least it didn't give an error earlier) - // @Odd String l3 = p1b(null); - } - - void t6() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1c("abc"); - e0(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1c("def"); - @Odd String l3 = p1c("abc"); - } - - void t7() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1b = MethodCallFlowExpr.p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1c = this.p1d(1); - e7a(); - @Odd String l2 = p1d(1); - @Odd String l2b = MethodCallFlowExpr.p1d(1); - @Odd String l2c = this.p1d(1); - } - - void t8() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1b = MethodCallFlowExpr.p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1c = this.p1d(1); - e7b(); - @Odd String l2 = p1d(1); - @Odd String l2b = MethodCallFlowExpr.p1d(1); - @Odd String l2c = this.p1d(1); - } - - void t9() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1b = MethodCallFlowExpr.p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1c = this.p1d(1); - e7c(); - @Odd String l2 = p1d(1); - @Odd String l2b = MethodCallFlowExpr.p1d(1); - @Odd String l2c = this.p1d(1); - } + @Pure + String p1(int i) { + return ""; + } + + @Pure + String p1b(Long i) { + return ""; + } + + @Pure + String p1(String s) { + return ""; + } + + String nonpure() { + return ""; + } + + @Pure + String p2(String s, long l, String s2) { + return ""; + } + + @Pure + String p1c(T i) { + return ""; + } + + @Pure + static String p1d(int i) { + return ""; + } + + @EnsuresQualifier(expression = "p1c(\"abc\")", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e0() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e1() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1(\"abc\")", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e2() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p2(\"abc\", 2L, p1(1))", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e4() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1b(2L)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e5() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1b(null)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e6() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1d(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e7a() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "this.p1d(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e7b() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "MethodCallFlowExpr.p1d(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e7c() { + // don't bother with implementation + } + + void t1() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1(1); + e1(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1(2); + @Odd String l3 = p1(1); + } + + void t2() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1("abc"); + e2(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1("def"); + // :: error: (assignment.type.incompatible) + @Odd String l2b = p1(1); + @Odd String l3 = p1("abc"); + } + + void t3() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p2("abc", 2L, p1(1)); + e4(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p2("abc", 2L, p1(2)); + // :: error: (assignment.type.incompatible) + @Odd String l2b = p2("abc", 1L, p1(1)); + @Odd String l3 = p2("abc", 2L, p1(1)); + } + + void t4() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1b(2L); + e5(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1b(null); + // :: error: (assignment.type.incompatible) + @Odd String l2b = p1b(1L); + // FIXME: the following line shouldn't give an error + // (or at least it didn't give an error earlier) + // @Odd String l3 = p1b(2L); + } + + void t5() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1b(null); + e6(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1b(1L); + // FIXME: the following line shouldn't give an error + // (or at least it didn't give an error earlier) + // @Odd String l3 = p1b(null); + } + + void t6() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1c("abc"); + e0(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1c("def"); + @Odd String l3 = p1c("abc"); + } + + void t7() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1b = MethodCallFlowExpr.p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1c = this.p1d(1); + e7a(); + @Odd String l2 = p1d(1); + @Odd String l2b = MethodCallFlowExpr.p1d(1); + @Odd String l2c = this.p1d(1); + } + + void t8() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1b = MethodCallFlowExpr.p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1c = this.p1d(1); + e7b(); + @Odd String l2 = p1d(1); + @Odd String l2b = MethodCallFlowExpr.p1d(1); + @Odd String l2c = this.p1d(1); + } + + void t9() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1b = MethodCallFlowExpr.p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1c = this.p1d(1); + e7c(); + @Odd String l2 = p1d(1); + @Odd String l2b = MethodCallFlowExpr.p1d(1); + @Odd String l2c = this.p1d(1); + } } diff --git a/framework/tests/flow/Monotonic.java b/framework/tests/flow/Monotonic.java index 6e45bbb7950..dbe11546b92 100644 --- a/framework/tests/flow/Monotonic.java +++ b/framework/tests/flow/Monotonic.java @@ -3,46 +3,46 @@ public class Monotonic { - String f1; - @MonotonicOdd String f2; - @MonotonicOdd String f2b; - @Odd String f3; - Monotonic[] ms; + String f1; + @MonotonicOdd String f2; + @MonotonicOdd String f2b; + @Odd String f3; + Monotonic[] ms; - void nonpure() {} + void nonpure() {} - void t1(@Odd String p1) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f2; - if (f2 == p1) { - @Odd String l2 = f2; - nonpure(); - @Odd String l3 = f2; - } + void t1(@Odd String p1) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f2; + if (f2 == p1) { + @Odd String l2 = f2; + nonpure(); + @Odd String l3 = f2; } + } - void t2(@Odd String p1) { - // :: error: (assignment.type.incompatible) - f2 = f1; - // :: error: (monotonic.type.incompatible) - f2 = f2b; // assigning @MonotonicOdd to @MonotonicOdd is not allowed - } + void t2(@Odd String p1) { + // :: error: (assignment.type.incompatible) + f2 = f1; + // :: error: (monotonic.type.incompatible) + f2 = f2b; // assigning @MonotonicOdd to @MonotonicOdd is not allowed + } - void t3(@Odd String p1) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f2; - f2 = p1; - @Odd String l2 = f2; - nonpure(); - @Odd String l3 = f2; - } + void t3(@Odd String p1) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f2; + f2 = p1; + @Odd String l2 = f2; + nonpure(); + @Odd String l3 = f2; + } - void t4(@Odd String p1) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f2; - f2 = p1; - @Odd String l2 = f2; - ms[0].f2 = p1; - @Odd String l3 = f2; - } + void t4(@Odd String p1) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f2; + f2 = p1; + @Odd String l2 = f2; + ms[0].f2 = p1; + @Odd String l3 = f2; + } } diff --git a/framework/tests/flow/MoreFields.java b/framework/tests/flow/MoreFields.java index 2825ab82ce6..b17f4694d93 100644 --- a/framework/tests/flow/MoreFields.java +++ b/framework/tests/flow/MoreFields.java @@ -1,9 +1,9 @@ public class MoreFields { - void testOtherFieds(Test other, int f) { - other.f = f; - } + void testOtherFieds(Test other, int f) { + other.f = f; + } } class Test { - int f; + int f; } diff --git a/framework/tests/flow/NonMethodCode.java b/framework/tests/flow/NonMethodCode.java index 5e2590015bd..18fbca89827 100644 --- a/framework/tests/flow/NonMethodCode.java +++ b/framework/tests/flow/NonMethodCode.java @@ -3,34 +3,34 @@ public class NonMethodCode { - @Odd String f1 = null; - String g1 = "def"; + @Odd String f1 = null; + String g1 = "def"; - static @Odd String sf1 = null; - static String sg1 = "def"; + static @Odd String sf1 = null; + static String sg1 = "def"; - // test flow for field initializer - @Odd String f2 = g1 == f1 ? g1 : f1; + // test flow for field initializer + @Odd String f2 = g1 == f1 ? g1 : f1; - // test flow for initializer blocks - { - String l1 = f1; - @Odd String l2 = l1; - if (g1 == f1) { - @Odd String l3 = g1; - } - // :: error: (assignment.type.incompatible) - @Odd String l4 = g1; + // test flow for initializer blocks + { + String l1 = f1; + @Odd String l2 = l1; + if (g1 == f1) { + @Odd String l3 = g1; } + // :: error: (assignment.type.incompatible) + @Odd String l4 = g1; + } - // test flow for static initializer blocks - static { - String l1 = sf1; - @Odd String l2 = l1; - if (sg1 == sf1) { - @Odd String l3 = sg1; - } - // :: error: (assignment.type.incompatible) - @Odd String l4 = sg1; + // test flow for static initializer blocks + static { + String l1 = sf1; + @Odd String l2 = l1; + if (sg1 == sf1) { + @Odd String l3 = sg1; } + // :: error: (assignment.type.incompatible) + @Odd String l4 = sg1; + } } diff --git a/framework/tests/flow/ParamFlowExpr.java b/framework/tests/flow/ParamFlowExpr.java index 61627213cf8..8cec0cb4621 100644 --- a/framework/tests/flow/ParamFlowExpr.java +++ b/framework/tests/flow/ParamFlowExpr.java @@ -4,27 +4,27 @@ public class ParamFlowExpr { - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - void t1(String p1) { - String l1 = p1; - } + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + void t1(String p1) { + String l1 = p1; + } - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - // :: error: (flowexpr.parameter.not.final) - void t2(String p1) { - p1 = ""; - } + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + // :: error: (flowexpr.parameter.not.final) + void t2(String p1) { + p1 = ""; + } - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - public static boolean eltsNonNull(Object[] seq1) { - if (seq1 == null) { - return false; - } - for (int i = 0; i < seq1.length; i++) { - if (seq1[i] == null) { - return false; - } - } - return true; + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + public static boolean eltsNonNull(Object[] seq1) { + if (seq1 == null) { + return false; + } + for (int i = 0; i < seq1.length; i++) { + if (seq1[i] == null) { + return false; + } } + return true; + } } diff --git a/framework/tests/flow/Postcondition.java b/framework/tests/flow/Postcondition.java index f238750157f..74a3a52f5a7 100644 --- a/framework/tests/flow/Postcondition.java +++ b/framework/tests/flow/Postcondition.java @@ -6,310 +6,310 @@ public class Postcondition { - String f1, f2, f3; - Postcondition p; - - @Pure - String p1() { - return null; - } - - /** *** normal postcondition ***** */ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class) - void oddF1() { - f1 = null; - } - - @EnsuresQualifier(expression = "p.f1", qualifier = Odd.class) - void oddF1_1() { - p.f1 = null; - } - - @EnsuresQualifier(expression = "#1.f1", qualifier = Odd.class) - void oddF1_2(final Postcondition param) { - param.f1 = null; - } - - @EnsuresQualifier(expression = "p.p1()", qualifier = Odd.class) - void oddF1_3() { - if (p.p1() == null) { - return; - } - throw new RuntimeException(); - } - - @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) - // :: error: (contracts.postcondition.not.satisfied) - void valueF1() {} - - @EnsuresQualifier(expression = "---", qualifier = ValueTypeAnno.class) - // :: error: (flowexpr.parse.error) - void error() {} - - @EnsuresQualifier(expression = "#1.#2", qualifier = ValueTypeAnno.class) - // :: error: (flowexpr.parse.error) - void error2(final String p1, final String p2) {} - - @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) - void exception() { - throw new RuntimeException(); - } - - @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) - void param1(final @ValueTypeAnno String f) {} - - @EnsuresQualifier( - expression = {"#1", "#2"}, - qualifier = ValueTypeAnno.class) - // :: error: (flowexpr.parameter.not.final) - void param2(@ValueTypeAnno String f, @ValueTypeAnno String g) { - f = g; + String f1, f2, f3; + Postcondition p; + + @Pure + String p1() { + return null; + } + + /** *** normal postcondition ***** */ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class) + void oddF1() { + f1 = null; + } + + @EnsuresQualifier(expression = "p.f1", qualifier = Odd.class) + void oddF1_1() { + p.f1 = null; + } + + @EnsuresQualifier(expression = "#1.f1", qualifier = Odd.class) + void oddF1_2(final Postcondition param) { + param.f1 = null; + } + + @EnsuresQualifier(expression = "p.p1()", qualifier = Odd.class) + void oddF1_3() { + if (p.p1() == null) { + return; } - - @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) + throw new RuntimeException(); + } + + @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + // :: error: (contracts.postcondition.not.satisfied) + void valueF1() {} + + @EnsuresQualifier(expression = "---", qualifier = ValueTypeAnno.class) + // :: error: (flowexpr.parse.error) + void error() {} + + @EnsuresQualifier(expression = "#1.#2", qualifier = ValueTypeAnno.class) + // :: error: (flowexpr.parse.error) + void error2(final String p1, final String p2) {} + + @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + void exception() { + throw new RuntimeException(); + } + + @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) + void param1(final @ValueTypeAnno String f) {} + + @EnsuresQualifier( + expression = {"#1", "#2"}, + qualifier = ValueTypeAnno.class) + // :: error: (flowexpr.parameter.not.final) + void param2(@ValueTypeAnno String f, @ValueTypeAnno String g) { + f = g; + } + + @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) + // :: error: (flowexpr.parse.index.too.big) + void param3() {} + + // basic postcondition test + void t1(@Odd String p1, String p2) { + valueF1(); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + oddF1(); + @Odd String l2 = f1; + + // :: error: (flowexpr.parse.error.postcondition) + error(); + } + + // test parameter syntax + void t2(@Odd String p1, String p2) { // :: error: (flowexpr.parse.index.too.big) - void param3() {} - - // basic postcondition test - void t1(@Odd String p1, String p2) { - valueF1(); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - oddF1(); - @Odd String l2 = f1; - - // :: error: (flowexpr.parse.error.postcondition) - error(); - } - - // test parameter syntax - void t2(@Odd String p1, String p2) { - // :: error: (flowexpr.parse.index.too.big) - param3(); - } - - // postcondition with more complex expression - void tn1(boolean b) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p.f1; - oddF1_1(); - @Odd String l2 = p.f1; - } - - // postcondition with more complex expression - void tn2(boolean b) { - Postcondition param = null; - // :: error: (assignment.type.incompatible) - @Odd String l1 = param.f1; - oddF1_2(param); - @Odd String l2 = param.f1; - } - - // postcondition with more complex expression - void tn3(boolean b) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p.p1(); - oddF1_3(); - @Odd String l2 = p.p1(); - } - - /** *** many postcondition ***** */ - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class), - @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - }) - void oddValueF1(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier(expression = "f1", qualifier = Odd.class) + param3(); + } + + // postcondition with more complex expression + void tn1(boolean b) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p.f1; + oddF1_1(); + @Odd String l2 = p.f1; + } + + // postcondition with more complex expression + void tn2(boolean b) { + Postcondition param = null; + // :: error: (assignment.type.incompatible) + @Odd String l1 = param.f1; + oddF1_2(param); + @Odd String l2 = param.f1; + } + + // postcondition with more complex expression + void tn3(boolean b) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p.p1(); + oddF1_3(); + @Odd String l2 = p.p1(); + } + + /** *** many postcondition ***** */ + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class), @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - void oddValueF1_repeated1(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class), - }) + }) + void oddValueF1(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; + } + + @EnsuresQualifier(expression = "f1", qualifier = Odd.class) + @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) + void oddValueF1_repeated1(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; + } + + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class), + }) + @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) + void oddValueF1_repeated2(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; + } + + @EnsuresQualifier(expression = "f1", qualifier = Odd.class) + @EnsuresQualifier.List({@EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class)}) + void oddValueF1_repeated3(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; + } + + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class), @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - void oddValueF1_repeated2(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier(expression = "f1", qualifier = Odd.class) - @EnsuresQualifier.List({@EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class)}) - void oddValueF1_repeated3(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class), - @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - }) - // :: error: (contracts.postcondition.not.satisfied) - void oddValueF1_invalid(@ValueTypeAnno String p1) {} - - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "--", qualifier = Odd.class), - }) - // :: error: (flowexpr.parse.error) - void error2() {} - - // basic postcondition test - void tnm1(@Odd String p1, @ValueTypeAnno String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = f2; - oddValueF1(p2); - @Odd String l3 = f1; - @ValueTypeAnno String l4 = f2; - - // :: error: (flowexpr.parse.error.postcondition) - error2(); - } - - /** *** conditional postcondition ***** */ - @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class) - boolean condOddF1(boolean b) { - if (b) { - f1 = null; - return true; - } - return false; - } - - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - boolean condOddF1False(boolean b) { - if (b) { - return true; - } - f1 = null; - return false; - } - - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - boolean condOddF1Invalid(boolean b) { - if (b) { - f1 = null; - return true; - } - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - // :: error: (contracts.conditional.postcondition.invalid.returntype) - void wrongReturnType() {} - - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - // :: error: (contracts.conditional.postcondition.invalid.returntype) - String wrongReturnType2() { - f1 = null; - return ""; + }) + // :: error: (contracts.postcondition.not.satisfied) + void oddValueF1_invalid(@ValueTypeAnno String p1) {} + + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "--", qualifier = Odd.class), + }) + // :: error: (flowexpr.parse.error) + void error2() {} + + // basic postcondition test + void tnm1(@Odd String p1, @ValueTypeAnno String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = f2; + oddValueF1(p2); + @Odd String l3 = f1; + @ValueTypeAnno String l4 = f2; + + // :: error: (flowexpr.parse.error.postcondition) + error2(); + } + + /** *** conditional postcondition ***** */ + @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class) + boolean condOddF1(boolean b) { + if (b) { + f1 = null; + return true; } + return false; + } - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) - boolean isOdd(final String p1) { - return isOdd(p1, 0); + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + boolean condOddF1False(boolean b) { + if (b) { + return true; } - - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) - boolean isOdd(final String p1, int p2) { - return p1 == null; + f1 = null; + return false; + } + + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + boolean condOddF1Invalid(boolean b) { + if (b) { + f1 = null; + return true; } - - @EnsuresQualifierIf(result = false, expression = "#1", qualifier = Odd.class) - boolean isNotOdd(final String p1) { - return !isOdd(p1); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + // :: error: (contracts.conditional.postcondition.invalid.returntype) + void wrongReturnType() {} + + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + // :: error: (contracts.conditional.postcondition.invalid.returntype) + String wrongReturnType2() { + f1 = null; + return ""; + } + + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) + boolean isOdd(final String p1) { + return isOdd(p1, 0); + } + + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) + boolean isOdd(final String p1, int p2) { + return p1 == null; + } + + @EnsuresQualifierIf(result = false, expression = "#1", qualifier = Odd.class) + boolean isNotOdd(final String p1) { + return !isOdd(p1); + } + + // basic conditional postcondition test + void t3(@Odd String p1, String p2) { + condOddF1(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (condOddF1(false)) { + @Odd String l2 = f1; } - - // basic conditional postcondition test - void t3(@Odd String p1, String p2) { - condOddF1(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (condOddF1(false)) { - @Odd String l2 = f1; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test (inverted) + void t4(@Odd String p1, String p2) { + condOddF1False(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (!condOddF1False(false)) { + @Odd String l2 = f1; } - - // basic conditional postcondition test (inverted) - void t4(@Odd String p1, String p2) { - condOddF1False(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (!condOddF1False(false)) { - @Odd String l2 = f1; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test 2 + void t5(boolean b) { + condOddF1(true); + if (b) { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; } - - // basic conditional postcondition test 2 - void t5(boolean b) { - condOddF1(true); - if (b) { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } - } - - /** *** many conditional postcondition ***** */ - @EnsuresQualifierIf.List({ - @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) - }) - boolean condsOddF1(boolean b, @ValueTypeAnno String p1) { - if (b) { - f1 = null; - return true; - } - f1 = p1; - return false; + } + + /** *** many conditional postcondition ***** */ + @EnsuresQualifierIf.List({ + @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) + }) + boolean condsOddF1(boolean b, @ValueTypeAnno String p1) { + if (b) { + f1 = null; + return true; } - - @EnsuresQualifierIf.List({ - @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) - }) - boolean condsOddF1_invalid(boolean b, @ValueTypeAnno String p1) { - if (b) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; + f1 = p1; + return false; + } + + @EnsuresQualifierIf.List({ + @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) + }) + boolean condsOddF1_invalid(boolean b, @ValueTypeAnno String p1) { + if (b) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; } + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } - @EnsuresQualifierIf.List({ - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - }) - // :: error: (contracts.conditional.postcondition.invalid.returntype) - String wrongReturnType3() { - return ""; - } - - void t6(@Odd String p1, @ValueTypeAnno String p2) { - condsOddF1(true, p2); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = f1; - if (condsOddF1(false, p2)) { - @Odd String l3 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l4 = f1; - } else { - @ValueTypeAnno String l5 = f1; - // :: error: (assignment.type.incompatible) - @Odd String l6 = f1; - } + @EnsuresQualifierIf.List({ + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + }) + // :: error: (contracts.conditional.postcondition.invalid.returntype) + String wrongReturnType3() { + return ""; + } + + void t6(@Odd String p1, @ValueTypeAnno String p2) { + condsOddF1(true, p2); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = f1; + if (condsOddF1(false, p2)) { + @Odd String l3 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l4 = f1; + } else { + @ValueTypeAnno String l5 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l6 = f1; } + } } diff --git a/framework/tests/flow/Precondition.java b/framework/tests/flow/Precondition.java index 7de4313ff7e..572fc8a1330 100644 --- a/framework/tests/flow/Precondition.java +++ b/framework/tests/flow/Precondition.java @@ -6,159 +6,157 @@ // various tests for the precondition mechanism public class Precondition { - String f1, f2, f3; - Precondition p; - - @RequiresQualifier(expression = "f1", qualifier = Odd.class) - void requiresF1() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - } - - @Pure + String f1, f2, f3; + Precondition p; + + @RequiresQualifier(expression = "f1", qualifier = Odd.class) + void requiresF1() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + } + + @Pure + @RequiresQualifier(expression = "f1", qualifier = Odd.class) + int requiresF1AndPure() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + return 1; + } + + @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + void requiresF1Value() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + @ValueTypeAnno String l2 = f1; + } + + @RequiresQualifier(expression = "---", qualifier = Odd.class) + // :: error: (flowexpr.parse.error) + void error() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } + + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + void requiresParam(String p) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p; + @Odd String l2 = p; + } + + @RequiresQualifier( + expression = {"#1", "#2"}, + qualifier = Odd.class) + void requiresParams(String p1, String p2) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = p2; + @Odd String l3 = p1; + @Odd String l4 = p2; + } + + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + // :: error: (flowexpr.parse.index.too.big) + void param3() {} + + void t1(@Odd String p1, String p2) { + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1Value(); + // :: error: (contracts.precondition.not.satisfied) + requiresParam(p2); + // :: error: (contracts.precondition.not.satisfied) + requiresParams(p1, p2); + } + + void t2(@Odd String p1, String p2) { + f1 = p1; + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1Value(); + } + + void t3(@Odd String p1, String p2) { + f1 = p1; + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + } + + void t4(@Odd String p1, String p2, @ValueTypeAnno String p3) { + f1 = p1; + requiresF1(); + f1 = p3; + requiresF1Value(); + requiresParam(p1); + requiresParams(p1, p1); + } + + class Inner { @RequiresQualifier(expression = "f1", qualifier = Odd.class) - int requiresF1AndPure() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - return 1; - } - - @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) - void requiresF1Value() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - @ValueTypeAnno String l2 = f1; - } - - @RequiresQualifier(expression = "---", qualifier = Odd.class) - // :: error: (flowexpr.parse.error) - void error() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } - - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - void requiresParam(String p) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p; - @Odd String l2 = p; - } - - @RequiresQualifier( - expression = {"#1", "#2"}, - qualifier = Odd.class) - void requiresParams(String p1, String p2) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = p2; - @Odd String l3 = p1; - @Odd String l4 = p2; - } - - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - // :: error: (flowexpr.parse.index.too.big) - void param3() {} - - void t1(@Odd String p1, String p2) { - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1Value(); - // :: error: (contracts.precondition.not.satisfied) - requiresParam(p2); - // :: error: (contracts.precondition.not.satisfied) - requiresParams(p1, p2); - } - - void t2(@Odd String p1, String p2) { - f1 = p1; - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1Value(); - } - - void t3(@Odd String p1, String p2) { - f1 = p1; - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - } - - void t4(@Odd String p1, String p2, @ValueTypeAnno String p3) { - f1 = p1; - requiresF1(); - f1 = p3; - requiresF1Value(); - requiresParam(p1); - requiresParams(p1, p1); - } - - class Inner { - @RequiresQualifier(expression = "f1", qualifier = Odd.class) - void foo() {} - } - - @Odd String f4; - - @RequiresQualifier(expression = "f4", qualifier = Odd.class) - void requiresF4() {} - - void tt1() { - requiresF4(); - } - - /** *** multiple preconditions ***** */ - @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + void foo() {} + } + + @Odd String f4; + + @RequiresQualifier(expression = "f4", qualifier = Odd.class) + void requiresF4() {} + + void tt1() { + requiresF4(); + } + + /** *** multiple preconditions ***** */ + @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + @RequiresQualifier(expression = "f2", qualifier = Odd.class) + void multi() { + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f2; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l3 = f2; + // :: error: (assignment.type.incompatible) + @Odd String l4 = f1; + } + + @RequiresQualifier.List({ + @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class), @RequiresQualifier(expression = "f2", qualifier = Odd.class) - void multi() { - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f2; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l3 = f2; - // :: error: (assignment.type.incompatible) - @Odd String l4 = f1; - } - - @RequiresQualifier.List({ - @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class), - @RequiresQualifier(expression = "f2", qualifier = Odd.class) - }) - void multi_explicit_requiresqualifierlist() { - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f2; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l3 = f2; - // :: error: (assignment.type.incompatible) - @Odd String l4 = f1; - } - - @RequiresQualifier.List({ - @RequiresQualifier(expression = "--", qualifier = ValueTypeAnno.class) - }) + }) + void multi_explicit_requiresqualifierlist() { + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f2; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l3 = f2; + // :: error: (assignment.type.incompatible) + @Odd String l4 = f1; + } + + @RequiresQualifier.List({@RequiresQualifier(expression = "--", qualifier = ValueTypeAnno.class)}) + // :: error: (flowexpr.parse.error) + void error2() {} + + void t5(@Odd String p1, String p2, @ValueTypeAnno String p3) { + // :: error: (contracts.precondition.not.satisfied) + multi(); + f1 = p3; + // :: error: (contracts.precondition.not.satisfied) + multi(); + f1 = p3; + f2 = p1; + multi(); + // :: error: (flowexpr.parse.error) - void error2() {} - - void t5(@Odd String p1, String p2, @ValueTypeAnno String p3) { - // :: error: (contracts.precondition.not.satisfied) - multi(); - f1 = p3; - // :: error: (contracts.precondition.not.satisfied) - multi(); - f1 = p3; - f2 = p1; - multi(); - - // :: error: (flowexpr.parse.error) - error2(); - } + error2(); + } } diff --git a/framework/tests/flow/Purity.java b/framework/tests/flow/Purity.java index bb6d5d1f9bd..731bc8cf627 100644 --- a/framework/tests/flow/Purity.java +++ b/framework/tests/flow/Purity.java @@ -7,280 +7,280 @@ // various tests for the @Pure annotation public class Purity { - String f1, f2, f3; - String[] a; + String f1, f2, f3; + String[] a; - // class with a (potentially) non-pure constructor - private static class NonPureClass {} - - // class with a pure constructor - private static class PureClass { - @Pure - // :: warning: (purity.deterministic.constructor) - public PureClass() {} - } - - // class with a side-effect-free constructor - private static class SEClass { - @SideEffectFree - public SEClass() {} - } - - // a method that is not pure (no annotation) - void nonpure() {} - - @Pure - String pure() { - return ""; - } + // class with a (potentially) non-pure constructor + private static class NonPureClass {} + // class with a pure constructor + private static class PureClass { @Pure - // :: warning: (purity.deterministic.void.method) - void t1() {} + // :: warning: (purity.deterministic.constructor) + public PureClass() {} + } + // class with a side-effect-free constructor + private static class SEClass { @SideEffectFree - void t1b() {} - - @Deterministic - // :: warning: (purity.deterministic.void.method) - void t1c() {} - - @Pure - String t2() { - return ""; - } - - @Pure - String t3() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - nonpure(); - // :: error: (purity.not.deterministic.call) - t16b(); // Calling a @SideEffectFree method - // :: error: (purity.not.sideeffectfree.call) - t16c(); // Calling a @Deterministic method - return ""; + public SEClass() {} + } + + // a method that is not pure (no annotation) + void nonpure() {} + + @Pure + String pure() { + return ""; + } + + @Pure + // :: warning: (purity.deterministic.void.method) + void t1() {} + + @SideEffectFree + void t1b() {} + + @Deterministic + // :: warning: (purity.deterministic.void.method) + void t1c() {} + + @Pure + String t2() { + return ""; + } + + @Pure + String t3() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + nonpure(); + // :: error: (purity.not.deterministic.call) + t16b(); // Calling a @SideEffectFree method + // :: error: (purity.not.sideeffectfree.call) + t16c(); // Calling a @Deterministic method + return ""; + } + + @Pure + String t4() { + pure(); + return ""; + } + + @Pure + int t5() { + int i = 1; + return i; + } + + @Pure + int t6() { + int j = 0; + for (int i = 0; i < 10; i++) { + j = j - i; } + return j; + } - @Pure - String t4() { - pure(); - return ""; - } - - @Pure - int t5() { - int i = 1; - return i; - } - - @Pure - int t6() { - int j = 0; - for (int i = 0; i < 10; i++) { - j = j - i; - } - return j; + @Pure + String t7() { + if (true) { + return "a"; } - - @Pure - String t7() { - if (true) { - return "a"; - } - return ""; - } - - @Pure - int t8() { - return 1 - 2 / 3 * 2 % 2; - } - - @Pure - String t9() { - return "b" + "a"; + return ""; + } + + @Pure + int t8() { + return 1 - 2 / 3 * 2 % 2; + } + + @Pure + String t9() { + return "b" + "a"; + } + + @Pure + String t10() { + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) + f1 = ""; + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) + f2 = ""; + return ""; + } + + @Pure + String t11(Purity l) { + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) + l.a[0] = ""; + return ""; + } + + @Pure + String t12(String[] s) { + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) + s[0] = ""; + return ""; + } + + @Pure + String t13() { + // No "purity.not.deterministic.object.creation" error; an error was issued at the + // constructor. + PureClass p = new PureClass(); + return ""; + } + + @SideEffectFree + String t13b() { + PureClass p = new PureClass(); + return ""; + } + + @SideEffectFree + String t13d() { + SEClass p = new SEClass(); + return ""; + } + + @Deterministic + String t13c() { + // No "purity.not.deterministic.object.creation" error; an error was issued at the + // constructor. + PureClass p = new PureClass(); + return ""; + } + + @Pure + String t14() { + String i = ""; + i = "a"; + return i; + } + + @Pure + String t15() { + String[] s = new String[1]; + return s[0]; + } + + @Pure + String t16() { + try { + int i = 1 / 0; + // :: error: (purity.not.deterministic.catch) + } catch (Throwable t) { + // ... } - - @Pure - String t10() { - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) - f1 = ""; - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) - f2 = ""; - return ""; + return ""; + } + + @SideEffectFree + String t16b() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... } - - @Pure - String t11(Purity l) { - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) - l.a[0] = ""; - return ""; + return ""; + } + + @Deterministic + String t16c() { + try { + int i = 1 / 0; + // :: error: (purity.not.deterministic.catch) + } catch (Throwable t) { + // ... } - + return ""; + } + + @Pure + String t12() { + // :: error: (purity.not.sideeffectfree.call) + // :: error: (purity.not.deterministic.object.creation) + NonPureClass p = new NonPureClass(); + return ""; + } + + @Deterministic + String t17a(Purity l) { + // :: error: (purity.not.deterministic.assign.field) + f1 = ""; + // :: error: (purity.not.deterministic.assign.array) + l.a[0] = ""; + // :: error: (purity.not.deterministic.call) + nonpure(); + // :: error: (purity.not.deterministic.call) + return t16b(); // Calling a @SideEffectFree method + } + + @SideEffectFree + String t17b() { + // :: error: (purity.not.sideeffectfree.assign.field) + f1 = ""; + // :: error: (purity.not.sideeffectfree.call) + NonPureClass p = new NonPureClass(); + // :: error: (purity.not.sideeffectfree.call) + nonpure(); + // :: error: (purity.not.sideeffectfree.call) + return t16c(); // Calling a @Deterministic method + } + + // @Pure annotations on the overridden implementation. + class Super { @Pure - String t12(String[] s) { - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) - s[0] = ""; - return ""; + int m1(int arg) { + return 0; } @Pure - String t13() { - // No "purity.not.deterministic.object.creation" error; an error was issued at the - // constructor. - PureClass p = new PureClass(); - return ""; - } - - @SideEffectFree - String t13b() { - PureClass p = new PureClass(); - return ""; + int m2(int arg) { + return 0; } - @SideEffectFree - String t13d() { - SEClass p = new SEClass(); - return ""; + int m3(int arg) { + return 0; } - @Deterministic - String t13c() { - // No "purity.not.deterministic.object.creation" error; an error was issued at the - // constructor. - PureClass p = new PureClass(); - return ""; + int m4(int arg) { + return 0; } + } + class Sub extends Super { @Pure - String t14() { - String i = ""; - i = "a"; - return i; + int m1(int arg) { + return 0; } - @Pure - String t15() { - String[] s = new String[1]; - return s[0]; + int m2(int arg) { + return 0; } @Pure - String t16() { - try { - int i = 1 / 0; - // :: error: (purity.not.deterministic.catch) - } catch (Throwable t) { - // ... - } - return ""; - } - - @SideEffectFree - String t16b() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... - } - return ""; + int m3(int arg) { + return 0; } - @Deterministic - String t16c() { - try { - int i = 1 / 0; - // :: error: (purity.not.deterministic.catch) - } catch (Throwable t) { - // ... - } - return ""; + int m4(int arg) { + return 0; } + } - @Pure - String t12() { - // :: error: (purity.not.sideeffectfree.call) - // :: error: (purity.not.deterministic.object.creation) - NonPureClass p = new NonPureClass(); - return ""; + class MyClass extends Object { + public int hashCode() { + return 42; } + } - @Deterministic - String t17a(Purity l) { - // :: error: (purity.not.deterministic.assign.field) - f1 = ""; - // :: error: (purity.not.deterministic.assign.array) - l.a[0] = ""; - // :: error: (purity.not.deterministic.call) - nonpure(); - // :: error: (purity.not.deterministic.call) - return t16b(); // Calling a @SideEffectFree method - } + class Wrapper { + Object key; @SideEffectFree - String t17b() { - // :: error: (purity.not.sideeffectfree.assign.field) - f1 = ""; - // :: error: (purity.not.sideeffectfree.call) - NonPureClass p = new NonPureClass(); - // :: error: (purity.not.sideeffectfree.call) - nonpure(); - // :: error: (purity.not.sideeffectfree.call) - return t16c(); // Calling a @Deterministic method - } - - // @Pure annotations on the overridden implementation. - class Super { - @Pure - int m1(int arg) { - return 0; - } - - @Pure - int m2(int arg) { - return 0; - } - - int m3(int arg) { - return 0; - } - - int m4(int arg) { - return 0; - } - } - - class Sub extends Super { - @Pure - int m1(int arg) { - return 0; - } - - int m2(int arg) { - return 0; - } - - @Pure - int m3(int arg) { - return 0; - } - - int m4(int arg) { - return 0; - } - } - - class MyClass extends Object { - public int hashCode() { - return 42; - } - } - - class Wrapper { - Object key; - - @SideEffectFree - public Wrapper(Object key) { - this.key = key; - } + public Wrapper(Object key) { + this.key = key; } + } } diff --git a/framework/tests/flow/StorePure.java b/framework/tests/flow/StorePure.java index 22baa6d141d..81795553119 100644 --- a/framework/tests/flow/StorePure.java +++ b/framework/tests/flow/StorePure.java @@ -6,143 +6,143 @@ // various tests about keeping information in the store about pure method calls public class StorePure { - String f1, f2; - - // various pure methods - - @Pure - String pure1() { - return null; - } - - @Pure - String pure1b() { - return null; - } - - @Deterministic - String pure1c() { - return null; - } - - @Pure - String pure2(int i) { - return null; - } - - @Pure - String pure3(boolean b) { - return null; + String f1, f2; + + // various pure methods + + @Pure + String pure1() { + return null; + } + + @Pure + String pure1b() { + return null; + } + + @Deterministic + String pure1c() { + return null; + } + + @Pure + String pure2(int i) { + return null; + } + + @Pure + String pure3(boolean b) { + return null; + } + + @Pure + String pure4(String o) { + return null; + } + + void nonpure() {} + + void t1(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure1() == p1) { + @Odd String l1 = pure1(); + l0 = "a"; // does not remove information + @Odd String l1b = pure1(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = pure1b(); + nonpure(); // non-pure method call might change the return value of pure1 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure1(); } + } - @Pure - String pure4(String o) { - return null; + // check that it only works for deterministic methods + void t1b(@Odd String p1, String p2, boolean b1) { + if (pure1c() == p1) { + @Odd String l1 = pure1c(); } - - void nonpure() {} - - void t1(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure1() == p1) { - @Odd String l1 = pure1(); - l0 = "a"; // does not remove information - @Odd String l1b = pure1(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = pure1b(); - nonpure(); // non-pure method call might change the return value of pure1 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure1(); - } - } - - // check that it only works for deterministic methods - void t1b(@Odd String p1, String p2, boolean b1) { - if (pure1c() == p1) { - @Odd String l1 = pure1c(); - } - } - - void t2(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure1() == p1) { - @Odd String l1 = pure1(); - l0 = "a"; // does not remove information - @Odd String l1b = pure1(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = pure1b(); - f1 = ""; // field update might change the return value of pure1 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure1(); - } + } + + void t2(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure1() == p1) { + @Odd String l1 = pure1(); + l0 = "a"; // does not remove information + @Odd String l1b = pure1(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = pure1b(); + f1 = ""; // field update might change the return value of pure1 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure1(); } - - void t3(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure2(1) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure2(0); - @Odd String l1 = pure2(1); - l0 = "a"; // does not remove information - @Odd String l1b = pure2(1); - nonpure(); // non-pure method call might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure2(1); - } + } + + void t3(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure2(1) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure2(0); + @Odd String l1 = pure2(1); + l0 = "a"; // does not remove information + @Odd String l1b = pure2(1); + nonpure(); // non-pure method call might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure2(1); } - - void t4(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure2(1) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure2(0); - @Odd String l1 = pure2(1); - l0 = "a"; // does not remove information - @Odd String l1b = pure2(1); - f1 = ""; // field update might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure2(1); - } + } + + void t4(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure2(1) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure2(0); + @Odd String l1 = pure2(1); + l0 = "a"; // does not remove information + @Odd String l1b = pure2(1); + f1 = ""; // field update might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure2(1); } - - void t5(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure3(true) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure3(false); - @Odd String l1 = pure3(true); - l0 = "a"; // does not remove information - @Odd String l1b = pure3(true); - nonpure(); // non-pure method call might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure3(true); - } + } + + void t5(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure3(true) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure3(false); + @Odd String l1 = pure3(true); + l0 = "a"; // does not remove information + @Odd String l1b = pure3(true); + nonpure(); // non-pure method call might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure3(true); } - - void t6(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure3(true) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure3(false); - @Odd String l1 = pure3(true); - l0 = "a"; // does not remove information - @Odd String l1b = pure3(true); - f1 = ""; // field update might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure3(true); - } + } + + void t6(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure3(true) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure3(false); + @Odd String l1 = pure3(true); + l0 = "a"; // does not remove information + @Odd String l1b = pure3(true); + f1 = ""; // field update might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure3(true); } - - // local variable as argument - void t7(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure4(l0) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure4("jk"); - @Odd String l1 = pure4(l0); - l0 = "a"; // remove information (!) - // :: error: (assignment.type.incompatible) - @Odd String l1b = pure4(l0); - } + } + + // local variable as argument + void t7(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure4(l0) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure4("jk"); + @Odd String l1 = pure4(l0); + l0 = "a"; // remove information (!) + // :: error: (assignment.type.incompatible) + @Odd String l1b = pure4(l0); } + } } diff --git a/framework/tests/flow/Termination.java b/framework/tests/flow/Termination.java index 76f92cf9707..51dc1334e00 100644 --- a/framework/tests/flow/Termination.java +++ b/framework/tests/flow/Termination.java @@ -5,16 +5,16 @@ // various tests for @TerminatesExecution public class Termination { - @TerminatesExecution - void exit() {} + @TerminatesExecution + void exit() {} - void t1(@Odd String p1, String p2, boolean b1) { - String l1 = p2; - if (b1) { - l1 = p1; - } else { - exit(); - } - @Odd String l3 = l1; + void t1(@Odd String p1, String p2, boolean b1) { + String l1 = p2; + if (b1) { + l1 = p1; + } else { + exit(); } + @Odd String l3 = l1; + } } diff --git a/framework/tests/flow/Values.java b/framework/tests/flow/Values.java index 244ec754cd3..0169d552aa8 100644 --- a/framework/tests/flow/Values.java +++ b/framework/tests/flow/Values.java @@ -1,70 +1,69 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.Collection; +import org.checkerframework.framework.testchecker.util.*; public class Values { - void test() { - Object o = get(); - Object o1 = get1(); - Object o2 = get2(); - foo1(o1); - foo2(o2); + void test() { + Object o = get(); + Object o1 = get1(); + Object o2 = get2(); + foo1(o1); + foo2(o2); - // :: error: (argument.type.incompatible) - foo1(o); - // :: error: (argument.type.incompatible) - foo2(o1); - // :: error: (argument.type.incompatible) - foo1(o2); - // :: error: (argument.type.incompatible) - foo(o2); + // :: error: (argument.type.incompatible) + foo1(o); + // :: error: (argument.type.incompatible) + foo2(o1); + // :: error: (argument.type.incompatible) + foo1(o2); + // :: error: (argument.type.incompatible) + foo(o2); - o1 = o2; - foo2(o1); - // :: error: (argument.type.incompatible) - foo1(o1); + o1 = o2; + foo2(o1); + // :: error: (argument.type.incompatible) + foo1(o1); - o2 = get1(); - foo1(o2); - // :: error: (argument.type.incompatible) - foo2(o2); - } + o2 = get1(); + foo1(o2); + // :: error: (argument.type.incompatible) + foo2(o2); + } - void andlubTest(Collection c) { - for (Object obj : c) { - Object o = get1(); - } + void andlubTest(Collection c) { + for (Object obj : c) { + Object o = get1(); } + } - void orlubTest(boolean b1, boolean b2) { - if (b1) { - Object o = get1(); - return; - } else if (b2) { - Object o = get2(); - return; - } + void orlubTest(boolean b1, boolean b2) { + if (b1) { + Object o = get1(); + return; + } else if (b2) { + Object o = get2(); + return; } + } - void foo(@ValueTypeAnno Object o) {} + void foo(@ValueTypeAnno Object o) {} - void foo1(@ValueTypeAnno(1) Object o) {} + void foo1(@ValueTypeAnno(1) Object o) {} - void foo2(@ValueTypeAnno(2) Object o) {} + void foo2(@ValueTypeAnno(2) Object o) {} - @SuppressWarnings("flowtest:return.type.incompatible") - @ValueTypeAnno Object get() { - return null; - } + @SuppressWarnings("flowtest:return.type.incompatible") + @ValueTypeAnno Object get() { + return null; + } - @SuppressWarnings("flowtest:return.type.incompatible") - @ValueTypeAnno(1) Object get1() { - return null; - } + @SuppressWarnings("flowtest:return.type.incompatible") + @ValueTypeAnno(1) Object get1() { + return null; + } - @SuppressWarnings("flowtest:return.type.incompatible") - @ValueTypeAnno(2) Object get2() { - return null; - } + @SuppressWarnings("flowtest:return.type.incompatible") + @ValueTypeAnno(2) Object get2() { + return null; + } } diff --git a/framework/tests/flow/flowexpression-scope/Class1.java b/framework/tests/flow/flowexpression-scope/Class1.java index 8292aeb6cf3..c7fe2ca8084 100644 --- a/framework/tests/flow/flowexpression-scope/Class1.java +++ b/framework/tests/flow/flowexpression-scope/Class1.java @@ -1,5 +1,5 @@ package pkg1; public class Class1 { - public static Object field; + public static Object field; } diff --git a/framework/tests/flow/flowexpression-scope/Class2.java b/framework/tests/flow/flowexpression-scope/Class2.java index 24e0473cb54..4b9f6ae2918 100644 --- a/framework/tests/flow/flowexpression-scope/Class2.java +++ b/framework/tests/flow/flowexpression-scope/Class2.java @@ -3,43 +3,42 @@ import org.checkerframework.framework.testchecker.util.EnsuresOdd; import org.checkerframework.framework.testchecker.util.Odd; import org.checkerframework.framework.testchecker.util.RequiresOdd; - import pkg1.Class1; public class Class2 { - @RequiresOdd("Class1.field") - // :: error: (flowexpr.parse.error) - public void requiresOddParseError() { - // :: error: (assignment.type.incompatible) - @Odd Object odd = Class1.field; - } - - @RequiresOdd("pkg1.Class1.field") - public void requiresOdd() { - @Odd Object odd = Class1.field; - } - - @EnsuresOdd("Class1.field") - // :: error: (flowexpr.parse.error) - public void ensuresOddParseError() { - // :: warning: (cast.unsafe.constructor.invocation) - Class1.field = new @Odd Object(); - } - - @EnsuresOdd("pkg1.Class1.field") - public void ensuresOdd() { - // :: warning: (cast.unsafe.constructor.invocation) - Class1.field = new @Odd Object(); - } - - void illegalUse() { - // :: error: (contracts.precondition.not.satisfied) - requiresOdd(); - } - - void legalUse() { - // :: warning: (cast.unsafe.constructor.invocation) - Class1.field = new @Odd Object(); - requiresOdd(); - } + @RequiresOdd("Class1.field") + // :: error: (flowexpr.parse.error) + public void requiresOddParseError() { + // :: error: (assignment.type.incompatible) + @Odd Object odd = Class1.field; + } + + @RequiresOdd("pkg1.Class1.field") + public void requiresOdd() { + @Odd Object odd = Class1.field; + } + + @EnsuresOdd("Class1.field") + // :: error: (flowexpr.parse.error) + public void ensuresOddParseError() { + // :: warning: (cast.unsafe.constructor.invocation) + Class1.field = new @Odd Object(); + } + + @EnsuresOdd("pkg1.Class1.field") + public void ensuresOdd() { + // :: warning: (cast.unsafe.constructor.invocation) + Class1.field = new @Odd Object(); + } + + void illegalUse() { + // :: error: (contracts.precondition.not.satisfied) + requiresOdd(); + } + + void legalUse() { + // :: warning: (cast.unsafe.constructor.invocation) + Class1.field = new @Odd Object(); + requiresOdd(); + } } diff --git a/framework/tests/flow/flowexpression-scope/Issue862.java b/framework/tests/flow/flowexpression-scope/Issue862.java index 86f0a3e5838..7237cb643c4 100644 --- a/framework/tests/flow/flowexpression-scope/Issue862.java +++ b/framework/tests/flow/flowexpression-scope/Issue862.java @@ -6,13 +6,13 @@ import pkg2.Class2; public class Issue862 { - void illegalUse(Class2 class2) { - // :: error: (contracts.precondition.not.satisfied) - class2.requiresOdd(); - } + void illegalUse(Class2 class2) { + // :: error: (contracts.precondition.not.satisfied) + class2.requiresOdd(); + } - void legalUse(Class2 class2) { - class2.ensuresOdd(); - class2.requiresOdd(); - } + void legalUse(Class2 class2) { + class2.ensuresOdd(); + class2.requiresOdd(); + } } diff --git a/framework/tests/flowexpression/ArrayCreationParsing.java b/framework/tests/flowexpression/ArrayCreationParsing.java index bc097b131b5..a46864a4bad 100644 --- a/framework/tests/flowexpression/ArrayCreationParsing.java +++ b/framework/tests/flowexpression/ArrayCreationParsing.java @@ -3,32 +3,32 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ArrayCreationParsing { - @FlowExp("new int[2]") Object value1; + @FlowExp("new int[2]") Object value1; - @FlowExp("new int[2][2]") Object value2; + @FlowExp("new int[2][2]") Object value2; - @FlowExp("new String[2]") Object value3; + @FlowExp("new String[2]") Object value3; - @FlowExp("new String[] {\"a\", \"b\"}") - Object value4; + @FlowExp("new String[] {\"a\", \"b\"}") + Object value4; - int i; + int i; - @FlowExp("new int[i]") Object value5; + @FlowExp("new int[i]") Object value5; - @FlowExp("new int[this.i]") Object value6; + @FlowExp("new int[this.i]") Object value6; - @FlowExp("new int[getI()]") Object value7; + @FlowExp("new int[getI()]") Object value7; - @FlowExp("new int[] {i, this.i, getI()}") Object value8; + @FlowExp("new int[] {i, this.i, getI()}") Object value8; - int getI() { - return i; - } + int getI() { + return i; + } - void method(@FlowExp("new java.lang.String[2]") Object param) { - value3 = param; - // :: error: (assignment.type.incompatible) - value1 = param; - } + void method(@FlowExp("new java.lang.String[2]") Object param) { + value3 = param; + // :: error: (assignment.type.incompatible) + value1 = param; + } } diff --git a/framework/tests/flowexpression/BinaryOperations.java b/framework/tests/flowexpression/BinaryOperations.java index b43d64fd5c2..8b36de6977a 100644 --- a/framework/tests/flowexpression/BinaryOperations.java +++ b/framework/tests/flowexpression/BinaryOperations.java @@ -4,7 +4,7 @@ public class BinaryOperations { - void method(int i, int j, @FlowExp("#1+#2") String s) { - @FlowExp("i+j") String q = s; - } + void method(int i, int j, @FlowExp("#1+#2") String s) { + @FlowExp("i+j") String q = s; + } } diff --git a/framework/tests/flowexpression/Canonicalization.java b/framework/tests/flowexpression/Canonicalization.java index 06e7e7f1a1c..d476c002b26 100644 --- a/framework/tests/flowexpression/Canonicalization.java +++ b/framework/tests/flowexpression/Canonicalization.java @@ -2,40 +2,40 @@ public class Canonicalization { - class LockExample { - protected final Object myLock = new Object(); - protected @FlowExp("myLock") Object locked; - protected @FlowExp("this.myLock") Object locked2; - - public @FlowExp("myLock") Object getLocked() { - return locked; - } + class LockExample { + protected final Object myLock = new Object(); + protected @FlowExp("myLock") Object locked; + protected @FlowExp("this.myLock") Object locked2; + + public @FlowExp("myLock") Object getLocked() { + return locked; } + } - class Use { - final LockExample lockExample1 = new LockExample(); - final Object myLock = new Object(); + class Use { + final LockExample lockExample1 = new LockExample(); + final Object myLock = new Object(); - @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; + @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; - @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; + @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object o3 = lockExample1.locked; + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object o3 = lockExample1.locked; - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object o4 = lockExample1.locked2; + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object o4 = lockExample1.locked2; - @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); + @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object oM2 = lockExample1.getLocked(); + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object oM2 = lockExample1.getLocked(); - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object oM3 = lockExample1.getLocked(); - } + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object oM3 = lockExample1.getLocked(); + } } diff --git a/framework/tests/flowexpression/CharAndDoubleParsing.java b/framework/tests/flowexpression/CharAndDoubleParsing.java index a9a8226ef62..01341ba1faf 100644 --- a/framework/tests/flowexpression/CharAndDoubleParsing.java +++ b/framework/tests/flowexpression/CharAndDoubleParsing.java @@ -3,11 +3,11 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class CharAndDoubleParsing { - void doubleParsing(@FlowExp("1.0") Object doubleValue) { - @FlowExp("1.0") Object value = doubleValue; - } + void doubleParsing(@FlowExp("1.0") Object doubleValue) { + @FlowExp("1.0") Object value = doubleValue; + } - void CharParsing(@FlowExp("'c'") Object charValue) { - @FlowExp("'c'") Object value = charValue; - } + void CharParsing(@FlowExp("'c'") Object charValue) { + @FlowExp("'c'") Object value = charValue; + } } diff --git a/framework/tests/flowexpression/ClassLiterals.java b/framework/tests/flowexpression/ClassLiterals.java index 276407d856b..9324f0cc021 100644 --- a/framework/tests/flowexpression/ClassLiterals.java +++ b/framework/tests/flowexpression/ClassLiterals.java @@ -3,30 +3,30 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ClassLiterals { - static class String {} + static class String {} - void method( - @FlowExp("String.class") Object p1, - @FlowExp("String.class") Object p2, - @FlowExp("java.lang.String.class") Object p3) { - @FlowExp("String.class") Object l1 = p1; - @FlowExp("String.class") Object l2 = p2; - // :: error: (assignment.type.incompatible) - @FlowExp("String.class") Object l3 = p3; - // :: error: (assignment.type.incompatible) - @FlowExp("java.lang.String.class") Object l4 = p1; - // :: error: (assignment.type.incompatible) - @FlowExp("java.lang.String.class") Object l5 = p2; - @FlowExp("java.lang.String.class") Object l6 = p3; - } + void method( + @FlowExp("String.class") Object p1, + @FlowExp("String.class") Object p2, + @FlowExp("java.lang.String.class") Object p3) { + @FlowExp("String.class") Object l1 = p1; + @FlowExp("String.class") Object l2 = p2; + // :: error: (assignment.type.incompatible) + @FlowExp("String.class") Object l3 = p3; + // :: error: (assignment.type.incompatible) + @FlowExp("java.lang.String.class") Object l4 = p1; + // :: error: (assignment.type.incompatible) + @FlowExp("java.lang.String.class") Object l5 = p2; + @FlowExp("java.lang.String.class") Object l6 = p3; + } - @FlowExp("void.class") String s0; + @FlowExp("void.class") String s0; - @FlowExp("int.class") String s1; + @FlowExp("int.class") String s1; - @FlowExp("int[].class") String s2; + @FlowExp("int[].class") String s2; - @FlowExp("String[].class") String s3; + @FlowExp("String[].class") String s3; - @FlowExp("java.lang.String[].class") String s4; + @FlowExp("java.lang.String[].class") String s4; } diff --git a/framework/tests/flowexpression/Complex.java b/framework/tests/flowexpression/Complex.java index 71dedd56773..576bd0d617e 100644 --- a/framework/tests/flowexpression/Complex.java +++ b/framework/tests/flowexpression/Complex.java @@ -1,40 +1,39 @@ package flowexpression; -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; - import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Complex { - class DocCategory { - public Map fields = new HashMap<>(); - } + class DocCategory { + public Map fields = new HashMap<>(); + } - protected DocCategory[] categories = new DocCategory[2]; + protected DocCategory[] categories = new DocCategory[2]; - void test() { - for (int c = 0; c < categories.length; c++) { + void test() { + for (int c = 0; c < categories.length; c++) { - for (@FlowExp("categories[c].fields") String field : sortedKeySet(categories[c].fields)) { - @FlowExp("categories[c].fields") String f = field; - } - } + for (@FlowExp("categories[c].fields") String field : sortedKeySet(categories[c].fields)) { + @FlowExp("categories[c].fields") String f = field; + } } + } - public static , V> Collection<@FlowExp("#1") K> sortedKeySet( - Map m) { - throw new RuntimeException(); - } + public static , V> Collection<@FlowExp("#1") K> sortedKeySet( + Map m) { + throw new RuntimeException(); + } - private static Map> succs1 = new HashMap<>(); + private static Map> succs1 = new HashMap<>(); - void method() { - Map> dom1post = dominators(succs1); - } + void method() { + Map> dom1post = dominators(succs1); + } - public static Map> dominators(Map> predecessors) { - throw new RuntimeException(); - } + public static Map> dominators(Map> predecessors) { + throw new RuntimeException(); + } } diff --git a/framework/tests/flowexpression/Constructor.java b/framework/tests/flowexpression/Constructor.java index 0da30b2c530..d1d88ea00bb 100644 --- a/framework/tests/flowexpression/Constructor.java +++ b/framework/tests/flowexpression/Constructor.java @@ -2,26 +2,26 @@ public class Constructor { - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - static class MyClass { - String field; + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + static class MyClass { + String field; - @FlowExp("field") MyClass() {} - } + @FlowExp("field") MyClass() {} + } - static class MyClass2 { - String field; - String field2; + static class MyClass2 { + String field; + String field2; - @SuppressWarnings("cast.unsafe.constructor.invocation") - void method() { - // TODO: This should be an error. - MyClass c = new @FlowExp("field") MyClass(); - // :: error: (expression.unparsable.type.invalid) :: error: - // (constructor.invocation.invalid) - MyClass c2 = new @FlowExp("bad") MyClass(); - // :: error: (constructor.invocation.invalid) - MyClass c3 = new @FlowExp("field2") MyClass(); - } + @SuppressWarnings("cast.unsafe.constructor.invocation") + void method() { + // TODO: This should be an error. + MyClass c = new @FlowExp("field") MyClass(); + // :: error: (expression.unparsable.type.invalid) :: error: + // (constructor.invocation.invalid) + MyClass c2 = new @FlowExp("bad") MyClass(); + // :: error: (constructor.invocation.invalid) + MyClass c3 = new @FlowExp("field2") MyClass(); } + } } diff --git a/framework/tests/flowexpression/Fields.java b/framework/tests/flowexpression/Fields.java index e712db03ac6..64896030aa9 100644 --- a/framework/tests/flowexpression/Fields.java +++ b/framework/tests/flowexpression/Fields.java @@ -3,17 +3,16 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Fields { - static class String { - public static final java.lang.String HELLO = "hello"; - } + static class String { + public static final java.lang.String HELLO = "hello"; + } - void method( - // :: error: (expression.unparsable.type.invalid) - @FlowExp("java.lang.String.HELLO") Object p1, - @FlowExp("Fields.String.HELLO") Object p2) { - // :: error: (assignment.type.incompatible) - @FlowExp("String.HELLO") Object l1 = p1; - @FlowExp("String.HELLO") Object l2 = p2; - @FlowExp("flowexpression.Fields.String.HELLO") Object l3 = p2; - } + void method( + // :: error: (expression.unparsable.type.invalid) + @FlowExp("java.lang.String.HELLO") Object p1, @FlowExp("Fields.String.HELLO") Object p2) { + // :: error: (assignment.type.incompatible) + @FlowExp("String.HELLO") Object l1 = p1; + @FlowExp("String.HELLO") Object l2 = p2; + @FlowExp("flowexpression.Fields.String.HELLO") Object l3 = p2; + } } diff --git a/framework/tests/flowexpression/InnerClasses.java b/framework/tests/flowexpression/InnerClasses.java index 45585e3ff36..383acfec05f 100644 --- a/framework/tests/flowexpression/InnerClasses.java +++ b/framework/tests/flowexpression/InnerClasses.java @@ -3,36 +3,36 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class InnerClasses { - public String outerInstanceField = ""; - public static String outerStaticField = ""; + public String outerInstanceField = ""; + public static String outerStaticField = ""; - static class InnerClass { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("outerInstanceField") Object o = null; + static class InnerClass { + // :: error: (expression.unparsable.type.invalid) + @FlowExp("outerInstanceField") Object o = null; - @FlowExp("outerStaticField") Object o2 = null; - } + @FlowExp("outerStaticField") Object o2 = null; + } - class NonStaticInnerClass { - @FlowExp("outerInstanceField") Object o = null; + class NonStaticInnerClass { + @FlowExp("outerInstanceField") Object o = null; - @FlowExp("outerStaticField") Object o2 = null; - } + @FlowExp("outerStaticField") Object o2 = null; + } - static class InnerClass2 { - public String outerInstanceField = ""; + static class InnerClass2 { + public String outerInstanceField = ""; - @FlowExp("outerInstanceField") Object o = null; - } + @FlowExp("outerInstanceField") Object o = null; + } - class TestUses { - void method(InnerClass innerClass, InnerClass2 innerClass2) { - // :: error: (expression.unparsable.type.invalid) :: error: - // (assignment.type.incompatible) - @FlowExp("innerClass.outerInstanceField") Object o = innerClass.o; - @FlowExp("InnerClasses.outerStaticField") Object o2 = innerClass.o2; + class TestUses { + void method(InnerClass innerClass, InnerClass2 innerClass2) { + // :: error: (expression.unparsable.type.invalid) :: error: + // (assignment.type.incompatible) + @FlowExp("innerClass.outerInstanceField") Object o = innerClass.o; + @FlowExp("InnerClasses.outerStaticField") Object o2 = innerClass.o2; - @FlowExp("innerClass2.outerInstanceField") Object o3 = innerClass2.o; - } + @FlowExp("innerClass2.outerInstanceField") Object o3 = innerClass2.o; } + } } diff --git a/framework/tests/flowexpression/Issue1609.java b/framework/tests/flowexpression/Issue1609.java index 8e0607d602b..be71f4647b5 100644 --- a/framework/tests/flowexpression/Issue1609.java +++ b/framework/tests/flowexpression/Issue1609.java @@ -4,207 +4,207 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Issue1609 { - void method(@FlowExp("\"" + s + "\"") String x) {} + void method(@FlowExp("\"" + s + "\"") String x) {} - public static final String s = - "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" - + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" - + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" - + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" - + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" - + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" - + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" - + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" - + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" - + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" - + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" - + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" - + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" - + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" - + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" - + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" - + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" - + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" - + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" - + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" - + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" - + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" - + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" - + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" - + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" - + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" - + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" - + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" - + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" - + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" - + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" - + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" - + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" - + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" - + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" - + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" - + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" - + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" - + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" - + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" - + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" - + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" - + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" - + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" - + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" - + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" - + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" - + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" - + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" - + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" - + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" - + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" - + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" - + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" - + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" - + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" - + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" - + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" - + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" - + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" - + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" - + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" - + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" - + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" - + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" - + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" - + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" - + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" - + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" - + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" - + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" - + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" - + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" - + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" - + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" - + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" - + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" - + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" - + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" - + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" - + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" - + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" - + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" - + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" - + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" - + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" - + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" - + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" - + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" - + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" - + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" - + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" - + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" - + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" - + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" - + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" - + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" - + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" - + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" - + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" - + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" - + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" - + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" - + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" - + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" - + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" - + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" - + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" - + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" - + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" - + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" - + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" - + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" - + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" - + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" - + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" - + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" - + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" - + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" - + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" - + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" - + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" - + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" - + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" - + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" - + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" - + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" - + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" - + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" - + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" - + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" - + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" - + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" - + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" - + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" - + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" - + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" - + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" - + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" - + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" - + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" - + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" - + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" - + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" - + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" - + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" - + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" - + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" - + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" - + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" - + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" - + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" - + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" - + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" - + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" - + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" - + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" - + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" - + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" - + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" - + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" - + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" - + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" - + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" - + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" - + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" - + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" - + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" - + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" - + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" - + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" - + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" - + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" - + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" - + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" - + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" - + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" - + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" - + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" - + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" - + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" - + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" - + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" - + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" - + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" - + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" - + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" - + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" - + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" - + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" - + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" - + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" - + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" - + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" - + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" - + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" - + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" - + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" - + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" - + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; + public static final String s = + "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" + + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" + + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" + + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" + + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" + + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" + + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" + + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" + + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" + + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" + + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" + + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" + + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" + + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" + + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" + + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" + + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" + + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" + + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" + + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" + + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" + + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" + + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" + + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" + + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" + + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" + + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" + + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" + + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" + + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" + + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" + + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" + + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" + + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" + + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" + + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" + + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" + + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" + + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" + + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" + + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" + + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" + + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" + + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" + + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" + + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" + + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" + + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" + + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" + + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" + + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" + + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" + + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" + + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" + + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" + + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" + + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" + + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" + + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" + + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" + + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" + + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" + + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" + + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" + + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" + + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" + + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" + + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" + + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" + + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" + + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" + + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" + + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" + + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" + + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" + + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" + + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" + + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" + + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" + + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" + + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" + + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" + + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" + + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" + + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" + + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" + + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" + + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" + + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" + + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" + + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" + + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" + + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" + + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" + + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" + + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" + + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" + + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" + + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" + + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" + + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" + + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" + + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" + + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" + + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" + + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" + + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" + + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" + + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" + + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" + + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" + + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" + + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" + + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" + + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" + + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" + + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" + + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" + + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" + + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" + + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" + + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" + + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" + + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" + + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" + + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" + + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" + + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" + + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" + + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" + + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" + + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" + + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" + + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" + + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" + + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" + + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" + + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" + + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" + + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" + + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" + + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" + + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" + + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" + + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" + + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" + + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" + + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" + + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" + + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" + + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" + + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" + + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" + + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" + + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" + + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" + + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" + + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" + + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" + + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" + + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" + + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" + + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" + + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" + + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" + + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" + + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" + + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" + + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" + + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" + + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" + + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" + + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" + + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" + + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" + + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" + + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" + + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" + + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" + + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" + + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" + + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" + + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" + + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" + + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" + + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" + + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" + + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" + + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" + + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" + + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" + + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" + + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" + + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" + + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" + + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" + + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" + + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" + + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" + + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; } diff --git a/framework/tests/flowexpression/LambdaParameter.java b/framework/tests/flowexpression/LambdaParameter.java index 5736b799b20..c974e95d5d8 100644 --- a/framework/tests/flowexpression/LambdaParameter.java +++ b/framework/tests/flowexpression/LambdaParameter.java @@ -1,67 +1,66 @@ -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; - import java.util.function.Function; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class LambdaParameter { - void method(String methodParam) { - Function func1 = - ( - // :: error: (lambda.param.type.incompatible) - @FlowExp("methodParam") String lambdaParam) -> { - return ""; - }; - Function func2 = - ( - // :: error: (lambda.param.type.incompatible) :: error: - // (expression.unparsable.type.invalid) - @FlowExp("lambdaParam") String lambdaParam) -> { - return ""; - }; - Function func3 = - ( - // :: error: (lambda.param.type.incompatible) - @FlowExp("#1") String lambdaParam) -> { - @FlowExp("lambdaParam") String s = lambdaParam; - return ""; - }; - Function<@FlowExp("methodParam") String, String> func4 = - (@FlowExp("methodParam") String lambdaParam) -> { - return ""; - }; - } + void method(String methodParam) { + Function func1 = + ( + // :: error: (lambda.param.type.incompatible) + @FlowExp("methodParam") String lambdaParam) -> { + return ""; + }; + Function func2 = + ( + // :: error: (lambda.param.type.incompatible) :: error: + // (expression.unparsable.type.invalid) + @FlowExp("lambdaParam") String lambdaParam) -> { + return ""; + }; + Function func3 = + ( + // :: error: (lambda.param.type.incompatible) + @FlowExp("#1") String lambdaParam) -> { + @FlowExp("lambdaParam") String s = lambdaParam; + return ""; + }; + Function<@FlowExp("methodParam") String, String> func4 = + (@FlowExp("methodParam") String lambdaParam) -> { + return ""; + }; + } - void method2(String methodParam, @FlowExp("#1") String methodParam2) { - Function<@FlowExp("methodParam") String, String> func1 = - (@FlowExp("methodParam") String lambdaParam) -> { - @FlowExp("methodParam") String a = methodParam2; - @FlowExp("methodParam") String b = lambdaParam; - return ""; - }; - } + void method2(String methodParam, @FlowExp("#1") String methodParam2) { + Function<@FlowExp("methodParam") String, String> func1 = + (@FlowExp("methodParam") String lambdaParam) -> { + @FlowExp("methodParam") String a = methodParam2; + @FlowExp("methodParam") String b = lambdaParam; + return ""; + }; + } - void method3() { - String local = ""; - Function func1 = - ( - // :: error: (lambda.param.type.incompatible) - @FlowExp("local") String lambdaParam) -> { - return ""; - }; - Function<@FlowExp("local") String, String> func2 = - (@FlowExp("local") String lambdaParam) -> { - return ""; - }; - } + void method3() { + String local = ""; + Function func1 = + ( + // :: error: (lambda.param.type.incompatible) + @FlowExp("local") String lambdaParam) -> { + return ""; + }; + Function<@FlowExp("local") String, String> func2 = + (@FlowExp("local") String lambdaParam) -> { + return ""; + }; + } - void method4() { - String local = ""; - @FlowExp("local") String otherLocal = null; - Function<@FlowExp("local") String, String> func1 = - (@FlowExp("local") String lambdaParam) -> { - @FlowExp("local") String a = otherLocal; - @FlowExp("local") String b = lambdaParam; - return ""; - }; - } + void method4() { + String local = ""; + @FlowExp("local") String otherLocal = null; + Function<@FlowExp("local") String, String> func1 = + (@FlowExp("local") String lambdaParam) -> { + @FlowExp("local") String a = otherLocal; + @FlowExp("local") String b = lambdaParam; + return ""; + }; + } } diff --git a/framework/tests/flowexpression/Private.java b/framework/tests/flowexpression/Private.java index 5f8161146ed..95c714b62a7 100644 --- a/framework/tests/flowexpression/Private.java +++ b/framework/tests/flowexpression/Private.java @@ -1,15 +1,14 @@ package flowexpression; -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; - import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Private { - private final Map nameToPpt = new LinkedHashMap<>(); + private final Map nameToPpt = new LinkedHashMap<>(); - public Collection<@FlowExp("nameToPpt") String> nameStringSet() { - throw new RuntimeException(); - } + public Collection<@FlowExp("nameToPpt") String> nameStringSet() { + throw new RuntimeException(); + } } diff --git a/framework/tests/flowexpression/SimpleVPA.java b/framework/tests/flowexpression/SimpleVPA.java index 93fe2e262c6..de4e1e4c133 100644 --- a/framework/tests/flowexpression/SimpleVPA.java +++ b/framework/tests/flowexpression/SimpleVPA.java @@ -4,17 +4,17 @@ public class SimpleVPA { - class MyClass { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("this.bad") Object field; - } + class MyClass { + // :: error: (expression.unparsable.type.invalid) + @FlowExp("this.bad") Object field; + } - class Use { - Object bad = new Object(); - MyClass myClass = new MyClass(); + class Use { + Object bad = new Object(); + MyClass myClass = new MyClass(); - @FlowExp("bad") - // :: error: (assignment.type.incompatible) - Object o = myClass.field; - } + @FlowExp("bad") + // :: error: (assignment.type.incompatible) + Object o = myClass.field; + } } diff --git a/framework/tests/flowexpression/Standardize.java b/framework/tests/flowexpression/Standardize.java index f23c7a88453..0b6c65dfaed 100644 --- a/framework/tests/flowexpression/Standardize.java +++ b/framework/tests/flowexpression/Standardize.java @@ -1,106 +1,104 @@ package flowexpression; -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; - import java.io.FileInputStream; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Standardize { - Object field; - - @SuppressWarnings("assignment.type.incompatible") - @FlowExp("field") Object fieldField = null; + Object field; - void variableDecls(@FlowExp("field") Standardize this, @FlowExp("field") Object paramField) { + @SuppressWarnings("assignment.type.incompatible") + @FlowExp("field") Object fieldField = null; - @FlowExp("field") Object localField = fieldField; + void variableDecls(@FlowExp("field") Standardize this, @FlowExp("field") Object paramField) { - @FlowExp("field") Object o1 = fieldField; - @FlowExp("this.field") Object o2 = fieldField; + @FlowExp("field") Object localField = fieldField; - @FlowExp("field") Object o3 = paramField; - @FlowExp("this.field") Object o4 = paramField; + @FlowExp("field") Object o1 = fieldField; + @FlowExp("this.field") Object o2 = fieldField; - @FlowExp("field") Object o5 = localField; - @FlowExp("this.field") Object o6 = localField; - - try (@SuppressWarnings("assignment.type.incompatible") - @FlowExp("field") FileInputStream in = new FileInputStream("")) { - in.read(); - @FlowExp("field") Object o7 = in; - @FlowExp("this.field") Object o8 = in; - } catch ( - @SuppressWarnings("exception.parameter.invalid") - @FlowExp("field") Exception ex) { - @FlowExp("field") Object o9 = ex; - @FlowExp("this.field") Object o10 = ex; - } - - @FlowExp("field") Object o11 = this; - @FlowExp("this.field") Object o12 = this; - } + @FlowExp("field") Object o3 = paramField; + @FlowExp("this.field") Object o4 = paramField; - class MyGen {} - - - void typeVariables(X x) { - MyGen o1; - MyGen o2; - MyGen<@FlowExp("this.field") String> o3; - MyGen<@FlowExp("field") String> o4; - - @FlowExp("field") Object o5 = x; - @FlowExp("this.field") Object o6 = x; - } - - void typeVariable2(A a, @FlowExp("#1") A a2) {} - - void callTypeVariable2(@FlowExp("field") Object param) { - typeVariable2(field, param); - typeVariable2(this.field, param); - } + @FlowExp("field") Object o5 = localField; + @FlowExp("this.field") Object o6 = localField; - @FlowExp("field") Object testReturn(@FlowExp("this.field") Object param) { - @FlowExp("this.field") Object o = testReturn(param); - return param; + try (@SuppressWarnings("assignment.type.incompatible") + @FlowExp("field") FileInputStream in = new FileInputStream("")) { + in.read(); + @FlowExp("field") Object o7 = in; + @FlowExp("this.field") Object o8 = in; + } catch ( + @SuppressWarnings("exception.parameter.invalid") + @FlowExp("field") Exception ex) { + @FlowExp("field") Object o9 = ex; + @FlowExp("this.field") Object o10 = ex; } - void testCasts() { - @SuppressWarnings("cast.unsafe") - @FlowExp("this.field") Object o = (@FlowExp("field") Object) new Object(); - @FlowExp("this.field") Object o2 = (@FlowExp("field") Object) o; - } - - void testNewClassTree() { - // :: warning: (cast.unsafe.constructor.invocation) - @FlowExp("this.field") Object o = new @FlowExp("field") Object(); - } - - void list(List<@FlowExp("field") Object> list) { - Object field = new Object(); - // "field" is local variable, but list.get(1) type is @FlowExp("this.field") - @FlowExp("field") - // :: error: (assignment.type.incompatible) - Object o1 = list.get(1); - @FlowExp("this.field") Object o2 = list.get(1); - } - - Object dict = new Object(); - - void typvar() { - // TODO: Why doesn't the diamond operator work? - // Map<@FlowExp("this.dict") String, String> that = new HashMap<>(); - Map<@FlowExp("this.dict") String, String> that = - new HashMap<@FlowExp("dict") String, String>(); - } - - void newArray() { - @FlowExp("this.dict") String[] s = new @FlowExp("dict") String[1]; - } - - void clasLiteral(@FlowExp("java.lang.String.class") String param) { - @FlowExp("String.class") String s = param; - } + @FlowExp("field") Object o11 = this; + @FlowExp("this.field") Object o12 = this; + } + + class MyGen {} + + void typeVariables( + X x) { + MyGen o1; + MyGen o2; + MyGen<@FlowExp("this.field") String> o3; + MyGen<@FlowExp("field") String> o4; + + @FlowExp("field") Object o5 = x; + @FlowExp("this.field") Object o6 = x; + } + + void typeVariable2(A a, @FlowExp("#1") A a2) {} + + void callTypeVariable2(@FlowExp("field") Object param) { + typeVariable2(field, param); + typeVariable2(this.field, param); + } + + @FlowExp("field") Object testReturn(@FlowExp("this.field") Object param) { + @FlowExp("this.field") Object o = testReturn(param); + return param; + } + + void testCasts() { + @SuppressWarnings("cast.unsafe") + @FlowExp("this.field") Object o = (@FlowExp("field") Object) new Object(); + @FlowExp("this.field") Object o2 = (@FlowExp("field") Object) o; + } + + void testNewClassTree() { + // :: warning: (cast.unsafe.constructor.invocation) + @FlowExp("this.field") Object o = new @FlowExp("field") Object(); + } + + void list(List<@FlowExp("field") Object> list) { + Object field = new Object(); + // "field" is local variable, but list.get(1) type is @FlowExp("this.field") + @FlowExp("field") + // :: error: (assignment.type.incompatible) + Object o1 = list.get(1); + @FlowExp("this.field") Object o2 = list.get(1); + } + + Object dict = new Object(); + + void typvar() { + // TODO: Why doesn't the diamond operator work? + // Map<@FlowExp("this.dict") String, String> that = new HashMap<>(); + Map<@FlowExp("this.dict") String, String> that = new HashMap<@FlowExp("dict") String, String>(); + } + + void newArray() { + @FlowExp("this.dict") String[] s = new @FlowExp("dict") String[1]; + } + + void clasLiteral(@FlowExp("java.lang.String.class") String param) { + @FlowExp("String.class") String s = param; + } } diff --git a/framework/tests/flowexpression/TestParsing.java b/framework/tests/flowexpression/TestParsing.java index dd658c0bf67..b898ba3acb3 100644 --- a/framework/tests/flowexpression/TestParsing.java +++ b/framework/tests/flowexpression/TestParsing.java @@ -1,12 +1,12 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class TestParsing { - int[] a = {1, 2}; + int[] a = {1, 2}; - void test(@FlowExp("a.length") Object a, @FlowExp("this.a.length") Object b) {} + void test(@FlowExp("a.length") Object a, @FlowExp("this.a.length") Object b) {} - void test2(@FlowExp("a.clone()") Object a, @FlowExp("this.a.clone()") Object b) {} + void test2(@FlowExp("a.clone()") Object a, @FlowExp("this.a.clone()") Object b) {} - // :: error: (expression.unparsable.type.invalid) - void test3(@FlowExp("a.leng") Object a) {} + // :: error: (expression.unparsable.type.invalid) + void test3(@FlowExp("a.leng") Object a) {} } diff --git a/framework/tests/flowexpression/ThisStaticContext.java b/framework/tests/flowexpression/ThisStaticContext.java index 409788f1db2..77b941742db 100644 --- a/framework/tests/flowexpression/ThisStaticContext.java +++ b/framework/tests/flowexpression/ThisStaticContext.java @@ -1,40 +1,39 @@ -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; - import java.util.Map; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ThisStaticContext { - public static Map staticField; - public Map instanceField; + public static Map staticField; + public Map instanceField; - static void staticMethod1( - // :: error: (expression.unparsable.type.invalid) - @FlowExp("this.staticField") Object p1, - @FlowExp("ThisStaticContext.staticField") Object p2, - @FlowExp("staticField") Object p3) { - p2 = p3; - } + static void staticMethod1( + // :: error: (expression.unparsable.type.invalid) + @FlowExp("this.staticField") Object p1, + @FlowExp("ThisStaticContext.staticField") Object p2, + @FlowExp("staticField") Object p3) { + p2 = p3; + } - static void staticMethod2( - // :: error: (expression.unparsable.type.invalid) - @FlowExp("this.instanceField") Object p1, - // :: error: (expression.unparsable.type.invalid) - @FlowExp("ThisStaticContext.instanceField") Object p2, - // :: error: (expression.unparsable.type.invalid) - @FlowExp("instanceField") Object p3) {} + static void staticMethod2( + // :: error: (expression.unparsable.type.invalid) + @FlowExp("this.instanceField") Object p1, + // :: error: (expression.unparsable.type.invalid) + @FlowExp("ThisStaticContext.instanceField") Object p2, + // :: error: (expression.unparsable.type.invalid) + @FlowExp("instanceField") Object p3) {} - void instanceMethod1( - @FlowExp("this.staticField") Object p1, - @FlowExp("ThisStaticContext.staticField") Object p2, - @FlowExp("staticField") Object p3) { - p2 = p3; - p2 = p1; - } + void instanceMethod1( + @FlowExp("this.staticField") Object p1, + @FlowExp("ThisStaticContext.staticField") Object p2, + @FlowExp("staticField") Object p3) { + p2 = p3; + p2 = p1; + } - void instanceMethod2( - @FlowExp("this.instanceField") Object p1, - // :: error: (expression.unparsable.type.invalid) - @FlowExp("ThisStaticContext.instanceField") Object p2, - @FlowExp("instanceField") Object p3) { - p1 = p3; - } + void instanceMethod2( + @FlowExp("this.instanceField") Object p1, + // :: error: (expression.unparsable.type.invalid) + @FlowExp("ThisStaticContext.instanceField") Object p2, + @FlowExp("instanceField") Object p3) { + p1 = p3; + } } diff --git a/framework/tests/flowexpression/ThisSuper.java b/framework/tests/flowexpression/ThisSuper.java index 5c2c940a790..06018639688 100644 --- a/framework/tests/flowexpression/ThisSuper.java +++ b/framework/tests/flowexpression/ThisSuper.java @@ -4,41 +4,41 @@ // @skip-test public class ThisSuper { - static class SuperClass { - protected final Object field = new Object(); + static class SuperClass { + protected final Object field = new Object(); - private @FlowExp("field") Object superField; - } + private @FlowExp("field") Object superField; + } - static class SubClass extends SuperClass { - /* Hides SuperClass.field */ - private final Object field = new Object(); + static class SubClass extends SuperClass { + /* Hides SuperClass.field */ + private final Object field = new Object(); - private @FlowExp("field") Object subField; + private @FlowExp("field") Object subField; - void method() { - // super.superField : @FlowExp("super.field") - // this.subField : @FlowExp("this.field") - // :: error: (assignment.type.incompatible) - this.subField = super.superField; - // :: error: (assignment.type.incompatible) - super.superField = this.subField; + void method() { + // super.superField : @FlowExp("super.field") + // this.subField : @FlowExp("this.field") + // :: error: (assignment.type.incompatible) + this.subField = super.superField; + // :: error: (assignment.type.incompatible) + super.superField = this.subField; - @FlowExp("super.field") Object o1 = super.superField; - @FlowExp("this.field") Object o2 = this.subField; - } + @FlowExp("super.field") Object o1 = super.superField; + @FlowExp("this.field") Object o2 = this.subField; } + } - class OuterClass { - private final Object lock = new Object(); + class OuterClass { + private final Object lock = new Object(); - @FlowExp("this.lock") Object field; + @FlowExp("this.lock") Object field; - class InnerClass { - private final Object lock = new Object(); + class InnerClass { + private final Object lock = new Object(); - // :: error: (assignment.type.incompatible) - @FlowExp("this.lock") Object field2 = field; - } + // :: error: (assignment.type.incompatible) + @FlowExp("this.lock") Object field2 = field; } + } } diff --git a/framework/tests/flowexpression/UnaryOperations.java b/framework/tests/flowexpression/UnaryOperations.java index ec9c038f584..9c36ecd52d4 100644 --- a/framework/tests/flowexpression/UnaryOperations.java +++ b/framework/tests/flowexpression/UnaryOperations.java @@ -4,8 +4,8 @@ public class UnaryOperations { - void method(int i, @FlowExp("+#1") String s) { - @FlowExp("i") String q = s; - @FlowExp("-9223372036854775808L") String s0; - } + void method(int i, @FlowExp("+#1") String s) { + @FlowExp("i") String q = s; + @FlowExp("-9223372036854775808L") String s0; + } } diff --git a/framework/tests/flowexpression/Unparsable.java b/framework/tests/flowexpression/Unparsable.java index 33cc368b8ee..dda2d443cb6 100644 --- a/framework/tests/flowexpression/Unparsable.java +++ b/framework/tests/flowexpression/Unparsable.java @@ -3,13 +3,13 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Unparsable { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("lsdjf") Object o3 = null; + // :: error: (expression.unparsable.type.invalid) + @FlowExp("lsdjf") Object o3 = null; - void method() { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("new Object()") Object o1 = null; - // :: error: (expression.unparsable.type.invalid) - @FlowExp("int x = 0") Object o2 = null; - } + void method() { + // :: error: (expression.unparsable.type.invalid) + @FlowExp("new Object()") Object o1 = null; + // :: error: (expression.unparsable.type.invalid) + @FlowExp("int x = 0") Object o2 = null; + } } diff --git a/framework/tests/flowexpression/UnsupportJavaCode.java b/framework/tests/flowexpression/UnsupportJavaCode.java index 3376197f313..08ea18c0590 100644 --- a/framework/tests/flowexpression/UnsupportJavaCode.java +++ b/framework/tests/flowexpression/UnsupportJavaCode.java @@ -4,12 +4,12 @@ public class UnsupportJavaCode { - void method() { + void method() { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("new Object()") String s0; + // :: error: (expression.unparsable.type.invalid) + @FlowExp("new Object()") String s0; - // :: error: (expression.unparsable.type.invalid) - @FlowExp("List list;") String s1; - } + // :: error: (expression.unparsable.type.invalid) + @FlowExp("List list;") String s1; + } } diff --git a/framework/tests/flowexpression/UsePrivate.java b/framework/tests/flowexpression/UsePrivate.java index aae3113e392..aef6f624be1 100644 --- a/framework/tests/flowexpression/UsePrivate.java +++ b/framework/tests/flowexpression/UsePrivate.java @@ -1,14 +1,12 @@ package flowexpression; -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; - import java.util.Collection; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class UsePrivate { - void test(Private app_ppts, Private test_ppts) { + void test(Private app_ppts, Private test_ppts) { - Collection<@FlowExp("app_ppts.nameToPpt") String> app_ppt_names = app_ppts.nameStringSet(); - Collection<@FlowExp("test_ppts.nameToPpt") String> test_ppt_names = - test_ppts.nameStringSet(); - } + Collection<@FlowExp("app_ppts.nameToPpt") String> app_ppt_names = app_ppts.nameStringSet(); + Collection<@FlowExp("test_ppts.nameToPpt") String> test_ppt_names = test_ppts.nameStringSet(); + } } diff --git a/framework/tests/flowexpression/ValueLiterals.java b/framework/tests/flowexpression/ValueLiterals.java index 44608e4a01c..5acc3b0d17f 100644 --- a/framework/tests/flowexpression/ValueLiterals.java +++ b/framework/tests/flowexpression/ValueLiterals.java @@ -1,13 +1,13 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ValueLiterals { - void test(@FlowExp("0") Object a, @FlowExp("0L") Object b) {} + void test(@FlowExp("0") Object a, @FlowExp("0L") Object b) {} - void test2(@FlowExp("1000") Object a, @FlowExp("100L") Object b) {} + void test2(@FlowExp("1000") Object a, @FlowExp("100L") Object b) {} - void test3(@FlowExp("01000") Object a) {} + void test3(@FlowExp("01000") Object a) {} - void test4(@FlowExp("0100L") Object b) {} + void test4(@FlowExp("0100L") Object b) {} - void test5(@FlowExp("0100l") Object b) {} + void test5(@FlowExp("0100l") Object b) {} } diff --git a/framework/tests/flowexpression/ViewPointAdaptMethods.java b/framework/tests/flowexpression/ViewPointAdaptMethods.java index f32635cd004..0594dcd2f04 100644 --- a/framework/tests/flowexpression/ViewPointAdaptMethods.java +++ b/framework/tests/flowexpression/ViewPointAdaptMethods.java @@ -3,37 +3,37 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ViewPointAdaptMethods { - Object param1; + Object param1; - void method1(Object param1, @FlowExp("#1") Object param2) { - @FlowExp("param1") Object local = param2; - @FlowExp("this.param1") - // :: error: (assignment.type.incompatible) - Object local2 = param2; - @FlowExp("#1") Object local3 = param2; - } + void method1(Object param1, @FlowExp("#1") Object param2) { + @FlowExp("param1") Object local = param2; + @FlowExp("this.param1") + // :: error: (assignment.type.incompatible) + Object local2 = param2; + @FlowExp("#1") Object local3 = param2; + } - Object field; + Object field; - void callMethod1(@FlowExp("this.field") Object param, @FlowExp("#1") Object param2) { - method1(field, param); - // :: error: (argument.type.incompatible) - method1(field, param2); - } + void callMethod1(@FlowExp("this.field") Object param, @FlowExp("#1") Object param2) { + method1(field, param); + // :: error: (argument.type.incompatible) + method1(field, param2); + } - @FlowExp("#2") Object method2(@FlowExp("#2") Object param1, Object param2, boolean flag) { - if (flag) { - return param1; - } else if (param1 == param2) { - @FlowExp("#2") - // :: error: (assignment.type.incompatible) - Object o = new Object(); - return o; - } else { - @FlowExp("param2") - // :: error: (assignment.type.incompatible) - Object o = new Object(); - return o; - } + @FlowExp("#2") Object method2(@FlowExp("#2") Object param1, Object param2, boolean flag) { + if (flag) { + return param1; + } else if (param1 == param2) { + @FlowExp("#2") + // :: error: (assignment.type.incompatible) + Object o = new Object(); + return o; + } else { + @FlowExp("param2") + // :: error: (assignment.type.incompatible) + Object o = new Object(); + return o; } + } } diff --git a/framework/tests/flowexpression/ViewpointAdaptation.java b/framework/tests/flowexpression/ViewpointAdaptation.java index 3557e616770..f0fb40f3919 100644 --- a/framework/tests/flowexpression/ViewpointAdaptation.java +++ b/framework/tests/flowexpression/ViewpointAdaptation.java @@ -2,40 +2,40 @@ public class ViewpointAdaptation { - class MyClass { - protected final Object field = new Object(); + class MyClass { + protected final Object field = new Object(); - protected @FlowExp("field") Object annotatedField1; + protected @FlowExp("field") Object annotatedField1; - protected @FlowExp("this.field") Object annotatedField2; + protected @FlowExp("this.field") Object annotatedField2; - public @FlowExp("field") Object getAnnotatedField1() { - return annotatedField1; - } + public @FlowExp("field") Object getAnnotatedField1() { + return annotatedField1; } + } - class Use { - final MyClass myClass1 = new MyClass(); - final Object field = new Object(); + class Use { + final MyClass myClass1 = new MyClass(); + final Object field = new Object(); - @FlowExp("this.myClass1.field") Object o1 = myClass1.annotatedField1; + @FlowExp("this.myClass1.field") Object o1 = myClass1.annotatedField1; - @FlowExp("this.myClass1.field") Object o2 = myClass1.annotatedField2; + @FlowExp("this.myClass1.field") Object o2 = myClass1.annotatedField2; - @FlowExp("field") - // :: error: (assignment.type.incompatible) - Object o3 = myClass1.annotatedField1; + @FlowExp("field") + // :: error: (assignment.type.incompatible) + Object o3 = myClass1.annotatedField1; - @FlowExp("this.field") - // :: error: (assignment.type.incompatible) - Object o4 = myClass1.annotatedField2; + @FlowExp("this.field") + // :: error: (assignment.type.incompatible) + Object o4 = myClass1.annotatedField2; - @FlowExp("field") - // :: error: (assignment.type.incompatible) - Object oM2 = myClass1.getAnnotatedField1(); + @FlowExp("field") + // :: error: (assignment.type.incompatible) + Object oM2 = myClass1.getAnnotatedField1(); - @FlowExp("this.field") - // :: error: (assignment.type.incompatible) - Object oM3 = myClass1.getAnnotatedField1(); - } + @FlowExp("this.field") + // :: error: (assignment.type.incompatible) + Object oM3 = myClass1.getAnnotatedField1(); + } } diff --git a/framework/tests/flowexpression/ViewpointAdaptation2.java b/framework/tests/flowexpression/ViewpointAdaptation2.java index 15442e0bd4f..c9f69ef4cc9 100644 --- a/framework/tests/flowexpression/ViewpointAdaptation2.java +++ b/framework/tests/flowexpression/ViewpointAdaptation2.java @@ -1,48 +1,48 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ViewpointAdaptation2 { - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @FlowExp("myLock") Object locked; + protected @FlowExp("myLock") Object locked; - protected @FlowExp("this.myLock") Object locked2; + protected @FlowExp("this.myLock") Object locked2; - public @FlowExp("myLock") Object getLocked() { - return locked; - } + public @FlowExp("myLock") Object getLocked() { + return locked; } + } - class Use { - final LockExample lockExample1 = new LockExample(); - final Object myLock = new Object(); + class Use { + final LockExample lockExample1 = new LockExample(); + final Object myLock = new Object(); - @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; + @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; - @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; + @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object o3 = lockExample1.locked; + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object o3 = lockExample1.locked; - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object o4 = lockExample1.locked2; + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object o4 = lockExample1.locked2; - @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); + @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object oM2 = lockExample1.getLocked(); + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object oM2 = lockExample1.getLocked(); - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object oM3 = lockExample1.getLocked(); + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object oM3 = lockExample1.getLocked(); - void uses() { - lockExample1.locked = o1; - // :: error: (assignment.type.incompatible) - lockExample1.locked = o3; - } + void uses() { + lockExample1.locked = o1; + // :: error: (assignment.type.incompatible) + lockExample1.locked = o3; } + } } diff --git a/framework/tests/framework-javac-errors/Issue346.java b/framework/tests/framework-javac-errors/Issue346.java index 1f6df583e1f..aae98334658 100644 --- a/framework/tests/framework-javac-errors/Issue346.java +++ b/framework/tests/framework-javac-errors/Issue346.java @@ -4,6 +4,6 @@ class Before {} class Context { - // :: error: cannot find symbol - Unknown f; + // :: error: cannot find symbol + Unknown f; } diff --git a/framework/tests/framework-javac-errors/MissingSymbolCrash.java b/framework/tests/framework-javac-errors/MissingSymbolCrash.java index 5a99cb8521d..8ff7d0d2993 100644 --- a/framework/tests/framework-javac-errors/MissingSymbolCrash.java +++ b/framework/tests/framework-javac-errors/MissingSymbolCrash.java @@ -1,6 +1,6 @@ public class MissingSymbolCrash { - public void test() { - // :: error: cannot find symbol - lst.add(s); - } + public void test() { + // :: error: cannot find symbol + lst.add(s); + } } diff --git a/framework/tests/framework-javac-errors/ResolveError.java b/framework/tests/framework-javac-errors/ResolveError.java index 2b55b481016..8199dd0ef0b 100644 --- a/framework/tests/framework-javac-errors/ResolveError.java +++ b/framework/tests/framework-javac-errors/ResolveError.java @@ -1,6 +1,6 @@ public class ResolveError { - void m() { - // :: error: cannot find symbol - Unresolved.foo(); - } + void m() { + // :: error: cannot find symbol + Unresolved.foo(); + } } diff --git a/framework/tests/framework-javac-errors/UnimportedExtends2.java b/framework/tests/framework-javac-errors/UnimportedExtends2.java index f30b4883261..2ff426e76a5 100644 --- a/framework/tests/framework-javac-errors/UnimportedExtends2.java +++ b/framework/tests/framework-javac-errors/UnimportedExtends2.java @@ -1,4 +1,4 @@ public class UnimportedExtends2 { - // :: error: cannot find symbol - class Inner extends UnimportedClass {} + // :: error: cannot find symbol + class Inner extends UnimportedClass {} } diff --git a/framework/tests/framework/AnnotatedAnnotation.java b/framework/tests/framework/AnnotatedAnnotation.java index ebbab9d4d48..210f3886256 100644 --- a/framework/tests/framework/AnnotatedAnnotation.java +++ b/framework/tests/framework/AnnotatedAnnotation.java @@ -1,69 +1,68 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.testchecker.util.*; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface OddInt { - @Odd int value(); + @Odd int value(); } @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface OddIntArr { - @Odd int[] value(); + @Odd int[] value(); } @interface OddRec { - OddIntArr[] value(); + OddIntArr[] value(); } class Const { - @SuppressWarnings("evenodd") - public static final @Odd int ok1 = 5; + @SuppressWarnings("evenodd") + public static final @Odd int ok1 = 5; - @SuppressWarnings("evenodd") - public static final @Odd int ok2 = 5; + @SuppressWarnings("evenodd") + public static final @Odd int ok2 = 5; - public static final int notodd = 4; + public static final int notodd = 4; } class Uses { - @OddInt(Const.ok1) - Object good1; + @OddInt(Const.ok1) + Object good1; - // :: error: (annotation.type.incompatible) - @OddInt(4) - Object bad1; + // :: error: (annotation.type.incompatible) + @OddInt(4) + Object bad1; - // :: error: (annotation.type.incompatible) - @OddInt(Const.notodd) - Object bad2; + // :: error: (annotation.type.incompatible) + @OddInt(Const.notodd) + Object bad2; - @OddIntArr(Const.ok1) - Object good2; + @OddIntArr(Const.ok1) + Object good2; - @OddIntArr({Const.ok1, Const.ok2}) - Object good3; + @OddIntArr({Const.ok1, Const.ok2}) + Object good3; - // :: error: (annotation.type.incompatible) - @OddIntArr(4) - Object bada1; + // :: error: (annotation.type.incompatible) + @OddIntArr(4) + Object bada1; - // :: error: (annotation.type.incompatible) - @OddIntArr({Const.ok1, 4}) - Object bada2; + // :: error: (annotation.type.incompatible) + @OddIntArr({Const.ok1, 4}) + Object bada2; - @OddRec(@OddIntArr({Const.ok1, Const.ok2})) - void goodrec1() {} + @OddRec(@OddIntArr({Const.ok1, Const.ok2})) + void goodrec1() {} - @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({Const.ok1, Const.ok2})}) - void goodrec2() {} + @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({Const.ok1, Const.ok2})}) + void goodrec2() {} - // :: error: (annotation.type.incompatible) - @OddRec(@OddIntArr({Const.ok1, 4})) - void badrec1() {} + // :: error: (annotation.type.incompatible) + @OddRec(@OddIntArr({Const.ok1, 4})) + void badrec1() {} - // :: error: (annotation.type.incompatible) - @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({3, Const.ok2})}) - void badrec2() {} + // :: error: (annotation.type.incompatible) + @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({3, Const.ok2})}) + void badrec2() {} } diff --git a/framework/tests/framework/AnnotatedGenerics.java b/framework/tests/framework/AnnotatedGenerics.java index bfd0207a2f9..b696cac5a34 100644 --- a/framework/tests/framework/AnnotatedGenerics.java +++ b/framework/tests/framework/AnnotatedGenerics.java @@ -2,134 +2,134 @@ public class AnnotatedGenerics { - public static void testNullableTypeVariable() { - class Test { - @Odd T get() { - return null; - } - } - Test l = null; - String l1 = l.get(); - @Odd String l2 = l.get(); - - Test<@Odd String> n = null; - String n1 = n.get(); - @Odd String n2 = n.get(); + public static void testNullableTypeVariable() { + class Test { + @Odd T get() { + return null; + } } - - // Tests the type of the constructed class is correctly inferred for generics. - public void testConstructors() { - // Variant without annotated type parameters - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>(); - - // Variant with annotated type parameters - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass innerClass3 = new @Odd MyClass(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass normal3 = new @Odd NormalClass(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass innerClass4 = new MyClass(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass normal4 = new NormalClass(); - } - - // Tests the type of the constructed class is correctly inferred when the - // diamond operator is used. - public void testConstructorsWithTypeParameterInferrence() { - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass<@Odd String> innerClass2 = new MyClass<>(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass<@Odd String> normal2 = new NormalClass<>(); - - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass innerClass3 = new @Odd MyClass<>(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass normal3 = new @Odd NormalClass<>(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass innerClass4 = new MyClass<>(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass normal4 = new NormalClass<>(); + Test l = null; + String l1 = l.get(); + @Odd String l2 = l.get(); + + Test<@Odd String> n = null; + String n1 = n.get(); + @Odd String n2 = n.get(); + } + + // Tests the type of the constructed class is correctly inferred for generics. + public void testConstructors() { + // Variant without annotated type parameters + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>(); + + // Variant with annotated type parameters + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass innerClass3 = new @Odd MyClass(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass normal3 = new @Odd NormalClass(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass innerClass4 = new MyClass(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass normal4 = new NormalClass(); + } + + // Tests the type of the constructed class is correctly inferred when the + // diamond operator is used. + public void testConstructorsWithTypeParameterInferrence() { + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass<@Odd String> innerClass2 = new MyClass<>(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass<@Odd String> normal2 = new NormalClass<>(); + + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass innerClass3 = new @Odd MyClass<>(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass normal3 = new @Odd NormalClass<>(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass innerClass4 = new MyClass<>(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass normal4 = new NormalClass<>(); + } + + // Tests the type of the constructor is appropriately inferred for anonymous classes + // N.B. This does not / cannot assert that the RHS is infact a subtype of the LHS. + public void testAnonymousConstructors() { + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>() {}; + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>() {}; + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>() {}; + // :: error: (assignment.type.incompatible) + @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>() {}; + + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass innerClass3 = new @Odd MyClass() {}; + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass normal3 = new @Odd NormalClass() {}; + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass innerClass4 = new MyClass() {}; + // :: error: (assignment.type.incompatible) + @Odd NormalClass normal4 = new NormalClass() {}; + } + + // The following test cases are not included because the Java compiler currently does + // not seem to support the diamond operator in conjunction with anonymous classes. + // + // public void testAnonymousConstructorsWithTypeParameterInferrence() { + // @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>() {}; + // @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>() {}; + // + // // Should error because the RHS isn't annotated as '@Odd' + // @Odd MyClass<@Odd String> innerClass2 = new MyClass<>() {}; + // @Odd NormalClass<@Odd String> normal2 = new NormalClass<>() {}; + // + // @Odd MyClass innerClass3 = new @Odd MyClass<>() {}; + // @Odd NormalClass normal3 = new @Odd NormalClass<>() {}; + // + // // Should error because the RHS isn't annotated as '@Odd' + // @Odd MyClass innerClass4 = new MyClass<>() {}; + // @Odd NormalClass normal4 = new NormalClass<>() {}; + // } + + static class NormalClass { + @Odd T get() { + return null; } + } - // Tests the type of the constructor is appropriately inferred for anonymous classes - // N.B. This does not / cannot assert that the RHS is infact a subtype of the LHS. - public void testAnonymousConstructors() { - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>() {}; - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>() {}; - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>() {}; - // :: error: (assignment.type.incompatible) - @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>() {}; - - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass innerClass3 = new @Odd MyClass() {}; - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass normal3 = new @Odd NormalClass() {}; - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass innerClass4 = new MyClass() {}; - // :: error: (assignment.type.incompatible) - @Odd NormalClass normal4 = new NormalClass() {}; + class MyClass implements java.util.Iterator<@Odd T> { + public boolean hasNext() { + return true; } - // The following test cases are not included because the Java compiler currently does - // not seem to support the diamond operator in conjunction with anonymous classes. - // - // public void testAnonymousConstructorsWithTypeParameterInferrence() { - // @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>() {}; - // @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>() {}; - // - // // Should error because the RHS isn't annotated as '@Odd' - // @Odd MyClass<@Odd String> innerClass2 = new MyClass<>() {}; - // @Odd NormalClass<@Odd String> normal2 = new NormalClass<>() {}; - // - // @Odd MyClass innerClass3 = new @Odd MyClass<>() {}; - // @Odd NormalClass normal3 = new @Odd NormalClass<>() {}; - // - // // Should error because the RHS isn't annotated as '@Odd' - // @Odd MyClass innerClass4 = new MyClass<>() {}; - // @Odd NormalClass normal4 = new NormalClass<>() {}; - // } - - static class NormalClass { - @Odd T get() { - return null; - } + public @Odd T next() { + return null; } - class MyClass implements java.util.Iterator<@Odd T> { - public boolean hasNext() { - return true; - } - - public @Odd T next() { - return null; - } - - public void remove() {} - } + public void remove() {} + } } diff --git a/framework/tests/framework/AnnotationWithComponents.java b/framework/tests/framework/AnnotationWithComponents.java index eb6da592e90..a5c89e6b3cb 100644 --- a/framework/tests/framework/AnnotationWithComponents.java +++ b/framework/tests/framework/AnnotationWithComponents.java @@ -3,14 +3,14 @@ * classes and fields. */ @interface Anno { - class Inner {} + class Inner {} - int con = 5; + int con = 5; - int value(); + int value(); } class Use { - @Anno(0) - Object o; + @Anno(0) + Object o; } diff --git a/framework/tests/framework/AnonymousClasses.java b/framework/tests/framework/AnonymousClasses.java index 58c1b4c160a..7d5e5633b3a 100644 --- a/framework/tests/framework/AnonymousClasses.java +++ b/framework/tests/framework/AnonymousClasses.java @@ -2,19 +2,19 @@ public class AnonymousClasses { - void test() { - new Object() { - // TODO: the right hand side is - // @Unqualified @Unqualified Object - // We should make sure that the qualifier is only present once. + void test() { + new Object() { + // TODO: the right hand side is + // @Unqualified @Unqualified Object + // We should make sure that the qualifier is only present once. - // :: error: (assignment.type.incompatible) - @Odd Object o = this; // error - }; + // :: error: (assignment.type.incompatible) + @Odd Object o = this; // error + }; - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd Object() { - @Odd Object o = this; - }; - } + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd Object() { + @Odd Object o = this; + }; + } } diff --git a/framework/tests/framework/ArraySubtyping.java b/framework/tests/framework/ArraySubtyping.java index da5b87e8c46..b3c087e9516 100644 --- a/framework/tests/framework/ArraySubtyping.java +++ b/framework/tests/framework/ArraySubtyping.java @@ -2,29 +2,29 @@ // @skip-test public class ArraySubtyping { - Object[] obj1 = new Object[1]; - @Odd Object[] obj2 = new @Odd Object[1]; + Object[] obj1 = new Object[1]; + @Odd Object[] obj2 = new @Odd Object[1]; - String[] str1 = new String[1]; - @Odd String[] str2 = new @Odd String[1]; + String[] str1 = new String[1]; + @Odd String[] str2 = new @Odd String[1]; - void m() { - // :: error: (assignment.type.incompatible) - obj1 = obj2; - // :: error: (assignment.type.incompatible) - obj2 = obj1; + void m() { + // :: error: (assignment.type.incompatible) + obj1 = obj2; + // :: error: (assignment.type.incompatible) + obj2 = obj1; - // :: error: (assignment.type.incompatible) - str1 = str2; - // :: error: (assignment.type.incompatible) - str2 = str1; + // :: error: (assignment.type.incompatible) + str1 = str2; + // :: error: (assignment.type.incompatible) + str2 = str1; - obj1 = str1; - obj2 = str2; + obj1 = str1; + obj2 = str2; - // :: error: (assignment.type.incompatible) - obj1 = str2; - // :: error: (assignment.type.incompatible) - obj2 = str1; - } + // :: error: (assignment.type.incompatible) + obj1 = str2; + // :: error: (assignment.type.incompatible) + obj2 = str1; + } } diff --git a/framework/tests/framework/Arrays.java b/framework/tests/framework/Arrays.java index a2509c016bb..1bf5f8dee1c 100644 --- a/framework/tests/framework/Arrays.java +++ b/framework/tests/framework/Arrays.java @@ -1,127 +1,126 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.testchecker.util.*; public class Arrays { - Object[] @Odd [] objB1 = new Object[] @Odd [] {}; - Object[][] @Odd [] objB1a = new Object[][] @Odd [] {}; - Object @Odd [][][] objB1b = new Object @Odd [][][] {}; - @Odd Object[][][] objB1c = new @Odd Object[][][] {}; - - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface A {} - - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface B {} - - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface C {} - - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface D {} - - class Cell {} + Object[] @Odd [] objB1 = new Object[] @Odd [] {}; + Object[][] @Odd [] objB1a = new Object[][] @Odd [] {}; + Object @Odd [][][] objB1b = new Object @Odd [][][] {}; + @Odd Object[][][] objB1c = new @Odd Object[][][] {}; - // (This part is actually for the parser, not the framework; it should - // be moved to the JSR 308 compiler test suite eventually.) - void test() { + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface A {} - Object z = new @A String[] {}; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface B {} - // 308 only: - Cell<@D Object @C [] @B [] @A []> o1; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface C {} - // w/new: - Object o2a = new @D Object @C [] @B [] @A [] {}; - Object o2b = new @D Object @C [1] @B [2] @A [3]; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface D {} - // w/175: - @D Object @C [] @B [] @A [] o3; - } + class Cell {} - void moreTest() { - // Assignments: + // (This part is actually for the parser, not the framework; it should + // be moved to the JSR 308 compiler test suite eventually.) + void test() { - String[] s = null; - String[] t = null; + Object z = new @A String[] {}; - s[0] = null; - t[0] = null; + // 308 only: + Cell<@D Object @C [] @B [] @A []> o1; - (new String[1])[0] = null; - (new String[1])[0] = null; + // w/new: + Object o2a = new @D Object @C [] @B [] @A [] {}; + Object o2b = new @D Object @C [1] @B [2] @A [3]; - (new String[] {"foo"})[0] = null; - (new String[] {"foo"})[0] = null; - } + // w/175: + @D Object @C [] @B [] @A [] o3; + } - void test2() { + void moreTest() { + // Assignments: - Object[][] objA1 = new Object[][] {}; - Object[][] objA2 = new Object[1][2]; - Object[][] objA3 = new Object[1][]; + String[] s = null; + String[] t = null; - Object[] @Odd [] objB1 = new Object[] @Odd [] {}; - Object[] @Odd [] objB2 = new Object[1] @Odd [2]; - Object[] @Odd [] objB3 = new Object[1] @Odd []; + s[0] = null; + t[0] = null; - Object @Odd [][] objC1 = new Object @Odd [][] {}; - Object @Odd [][] objC2 = new Object @Odd [1][2]; - Object @Odd [][] objC3 = new Object @Odd [1][]; + (new String[1])[0] = null; + (new String[1])[0] = null; - @Odd Object[][] objD1 = new @Odd Object[][] {}; - @Odd Object[][] objD2 = new @Odd Object[1][2]; - @Odd Object[][] objD3 = new @Odd Object[1][]; + (new String[] {"foo"})[0] = null; + (new String[] {"foo"})[0] = null; + } - Object @Odd [] @Odd [] objE1 = new Object @Odd [] @Odd [] {}; - Object @Odd [] @Odd [] objE2 = new Object @Odd [1] @Odd [2]; - Object @Odd [] @Odd [] objE3 = new Object @Odd [1] @Odd []; + void test2() { - @Odd Object[] @Odd [] objF1 = new @Odd Object[] @Odd [] {}; - @Odd Object[] @Odd [] objF2 = new @Odd Object[1] @Odd [2]; - @Odd Object[] @Odd [] objF3 = new @Odd Object[1] @Odd []; + Object[][] objA1 = new Object[][] {}; + Object[][] objA2 = new Object[1][2]; + Object[][] objA3 = new Object[1][]; - @Odd Object @Odd [][] objG1 = new @Odd Object @Odd [][] {}; - @Odd Object @Odd [][] objG2 = new @Odd Object @Odd [1][2]; - @Odd Object @Odd [][] objG3 = new @Odd Object @Odd [1][]; - - @Odd Object @Odd [] @Odd [] objH1 = new @Odd Object @Odd [] @Odd [] {}; - @Odd Object @Odd [] @Odd [] objH2 = new @Odd Object @Odd [1] @Odd [2]; - @Odd Object @Odd [] @Odd [] objH3 = new @Odd Object @Odd [1] @Odd []; - } - - void test3() { - @Odd Object o1 = new @Odd Object @Odd [] @Odd [] {}; - // :: error: (assignment.type.incompatible) - @Odd Object o2 = new @Odd Object[] @Odd [] {}; // ERROR - - @Odd Object @Odd [] o3 = (new @Odd Object[] @Odd [] {})[0]; - // :: error: (assignment.type.incompatible) - @Odd Object @Odd [] o4 = (new Object @Odd [][] {})[0]; // ERROR - // :: error: (assignment.type.incompatible) - @Odd Object @Odd [] o5 = (new @Odd Object[][] {})[0]; // ERROR - - Object @Odd [] o6 = (new Object[] @Odd [] {})[0]; - @Odd Object[] o7 = (new @Odd Object[][] {})[0]; - - @Odd Object o8 = (new @Odd Object[][] {})[0][0]; - } - - void test4() { - @Odd Object @Odd [] @Odd [] o1 = new @Odd Object @Odd [] @Odd [] {}; - @Odd Object @Odd [] @Odd [] @Odd [] o2 = new @Odd Object @Odd [1] @Odd [2] @Odd [3]; - @Odd Object @Odd [] @Odd [] o3 = new @Odd Object @Odd [1] @Odd [2] @Odd []; - @Odd Object @Odd [] @Odd [] o4 = new @Odd Object @Odd [1] @Odd [] @Odd []; - } - - void testInitializers() { - // @Odd String [] ara1 = { null, null }; - @Odd String[] ara2 = new @Odd String[] {null, null}; - - // // xx:: error: (assignment.type.incompatible) - // @Odd String [] arb1 = { null, "m" }; - // :: error: (array.initializer.type.incompatible) - @Odd String[] arb2 = new @Odd String[] {null, "m"}; - } + Object[] @Odd [] objB1 = new Object[] @Odd [] {}; + Object[] @Odd [] objB2 = new Object[1] @Odd [2]; + Object[] @Odd [] objB3 = new Object[1] @Odd []; + + Object @Odd [][] objC1 = new Object @Odd [][] {}; + Object @Odd [][] objC2 = new Object @Odd [1][2]; + Object @Odd [][] objC3 = new Object @Odd [1][]; + + @Odd Object[][] objD1 = new @Odd Object[][] {}; + @Odd Object[][] objD2 = new @Odd Object[1][2]; + @Odd Object[][] objD3 = new @Odd Object[1][]; + + Object @Odd [] @Odd [] objE1 = new Object @Odd [] @Odd [] {}; + Object @Odd [] @Odd [] objE2 = new Object @Odd [1] @Odd [2]; + Object @Odd [] @Odd [] objE3 = new Object @Odd [1] @Odd []; + + @Odd Object[] @Odd [] objF1 = new @Odd Object[] @Odd [] {}; + @Odd Object[] @Odd [] objF2 = new @Odd Object[1] @Odd [2]; + @Odd Object[] @Odd [] objF3 = new @Odd Object[1] @Odd []; + + @Odd Object @Odd [][] objG1 = new @Odd Object @Odd [][] {}; + @Odd Object @Odd [][] objG2 = new @Odd Object @Odd [1][2]; + @Odd Object @Odd [][] objG3 = new @Odd Object @Odd [1][]; + + @Odd Object @Odd [] @Odd [] objH1 = new @Odd Object @Odd [] @Odd [] {}; + @Odd Object @Odd [] @Odd [] objH2 = new @Odd Object @Odd [1] @Odd [2]; + @Odd Object @Odd [] @Odd [] objH3 = new @Odd Object @Odd [1] @Odd []; + } + + void test3() { + @Odd Object o1 = new @Odd Object @Odd [] @Odd [] {}; + // :: error: (assignment.type.incompatible) + @Odd Object o2 = new @Odd Object[] @Odd [] {}; // ERROR + + @Odd Object @Odd [] o3 = (new @Odd Object[] @Odd [] {})[0]; + // :: error: (assignment.type.incompatible) + @Odd Object @Odd [] o4 = (new Object @Odd [][] {})[0]; // ERROR + // :: error: (assignment.type.incompatible) + @Odd Object @Odd [] o5 = (new @Odd Object[][] {})[0]; // ERROR + + Object @Odd [] o6 = (new Object[] @Odd [] {})[0]; + @Odd Object[] o7 = (new @Odd Object[][] {})[0]; + + @Odd Object o8 = (new @Odd Object[][] {})[0][0]; + } + + void test4() { + @Odd Object @Odd [] @Odd [] o1 = new @Odd Object @Odd [] @Odd [] {}; + @Odd Object @Odd [] @Odd [] @Odd [] o2 = new @Odd Object @Odd [1] @Odd [2] @Odd [3]; + @Odd Object @Odd [] @Odd [] o3 = new @Odd Object @Odd [1] @Odd [2] @Odd []; + @Odd Object @Odd [] @Odd [] o4 = new @Odd Object @Odd [1] @Odd [] @Odd []; + } + + void testInitializers() { + // @Odd String [] ara1 = { null, null }; + @Odd String[] ara2 = new @Odd String[] {null, null}; + + // // xx:: error: (assignment.type.incompatible) + // @Odd String [] arb1 = { null, "m" }; + // :: error: (array.initializer.type.incompatible) + @Odd String[] arb2 = new @Odd String[] {null, "m"}; + } } diff --git a/framework/tests/framework/Assignments.java b/framework/tests/framework/Assignments.java index a09de05f91f..bd55e33d131 100644 --- a/framework/tests/framework/Assignments.java +++ b/framework/tests/framework/Assignments.java @@ -2,65 +2,65 @@ public class Assignments { - public void testAssignment() { - @Odd String s; - @Odd String t; - s = null; - t = s; + public void testAssignment() { + @Odd String s; + @Odd String t; + s = null; + t = s; - String z = ""; - z = s; - } + String z = ""; + z = s; + } - public void testCompound() { - // :: warning: (cast.unsafe) - @Odd String s = (@Odd String) "foo"; - // :: warning: (cast.unsafe) - @Odd String t = (@Odd String) "bar"; - s += t; + public void testCompound() { + // :: warning: (cast.unsafe) + @Odd String s = (@Odd String) "foo"; + // :: warning: (cast.unsafe) + @Odd String t = (@Odd String) "bar"; + s += t; - String z = ""; - z += s; - } + String z = ""; + z += s; + } - public void testEnhancedForLoop() { - // TODO - } + public void testEnhancedForLoop() { + // TODO + } - public void testMethod() { - // Nothing to do here. - } + public void testMethod() { + // Nothing to do here. + } - public void testMethodInvocation() { - // TODO anonymous constructor - // TODO isEnumSuperCall - // @see Varargs + public void testMethodInvocation() { + // TODO anonymous constructor + // TODO isEnumSuperCall + // @see Varargs - @Odd String s = null; - methodA(s); - methodB(s); - } + @Odd String s = null; + methodA(s); + methodB(s); + } - public @Odd String testReturn() { - @Odd String s = null; - return s; - } + public @Odd String testReturn() { + @Odd String s = null; + return s; + } - public void testReturnVoid() { - return; - } + public void testReturnVoid() { + return; + } - public void testVariable() { - @Odd String s = null; - // :: warning: (cast.unsafe) - @Odd String t = (@Odd String) "foo"; - @Odd String u = s; - String v = s; - } + public void testVariable() { + @Odd String s = null; + // :: warning: (cast.unsafe) + @Odd String t = (@Odd String) "foo"; + @Odd String u = s; + String v = s; + } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ - public void methodA(@Odd String s) {} + public void methodA(@Odd String s) {} - public void methodB(String s) {} + public void methodB(String s) {} } diff --git a/framework/tests/framework/AssignmentsGeneric.java b/framework/tests/framework/AssignmentsGeneric.java index c1df6727be4..df4f7b65b22 100644 --- a/framework/tests/framework/AssignmentsGeneric.java +++ b/framework/tests/framework/AssignmentsGeneric.java @@ -1,57 +1,54 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.framework.testchecker.util.*; public class AssignmentsGeneric { - private static final Map< - @Odd List<@Odd String>, - @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>> - complex; - - static { - complex = - new HashMap< - @Odd List<@Odd String>, - @Odd Map< - @Odd Set<@Odd List<@Odd String>>, - @Odd List<@Odd Set<@Odd String>>>>(); - } + private static final Map< + @Odd List<@Odd String>, + @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>> + complex; - public void testAssignment() { - // :: warning: (cast.unsafe) - @Odd String s = (@Odd String) ""; + static { + complex = + new HashMap< + @Odd List<@Odd String>, + @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>>(); + } - List<@Odd String> lst = new LinkedList<>(); - lst = new ArrayList<@Odd String>(); + public void testAssignment() { + // :: warning: (cast.unsafe) + @Odd String s = (@Odd String) ""; - methodA(lst); - } + List<@Odd String> lst = new LinkedList<>(); + lst = new ArrayList<@Odd String>(); - public void testEnhancedForLoop() { - List<@Odd String> lst = new LinkedList<>(); - for (@Odd String str : lst) { - System.out.println(str); - } - } + methodA(lst); + } - public void testGenericInvocation() { - List<@Odd String> lst = new LinkedList<>(); - // :: warning: (cast.unsafe) - @Odd String s = (@Odd String) ""; - lst.add(s); + public void testEnhancedForLoop() { + List<@Odd String> lst = new LinkedList<>(); + for (@Odd String str : lst) { + System.out.println(str); } + } - public List<@Odd String> testReturn() { - return new LinkedList<@Odd String>(); - } + public void testGenericInvocation() { + List<@Odd String> lst = new LinkedList<>(); + // :: warning: (cast.unsafe) + @Odd String s = (@Odd String) ""; + lst.add(s); + } + + public List<@Odd String> testReturn() { + return new LinkedList<@Odd String>(); + } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ - public void methodA(List<@Odd String> lst) {} + public void methodA(List<@Odd String> lst) {} } diff --git a/framework/tests/framework/BridgeMethods.java b/framework/tests/framework/BridgeMethods.java index 9a53e6acd91..5fb0c0e913f 100644 --- a/framework/tests/framework/BridgeMethods.java +++ b/framework/tests/framework/BridgeMethods.java @@ -2,27 +2,27 @@ import org.checkerframework.framework.testchecker.util.Odd; abstract class C { - abstract T id(T x); + abstract T id(T x); } class D extends C<@Odd String> { - @Odd String id(@Odd String x) { - return x; - } + @Odd String id(@Odd String x) { + return x; + } } class Usage { - void use() { - C c = new D(); // C<@Odd String>(); - // Oddness is OK, will fail with ClassCastException - // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C - // :: warning: (cast.unsafe.constructor.invocation) - c.id(new @Odd Object()); + void use() { + C c = new D(); // C<@Odd String>(); + // Oddness is OK, will fail with ClassCastException + // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C + // :: warning: (cast.unsafe.constructor.invocation) + c.id(new @Odd Object()); - // Oddness is wrong! Would also fail with ClassCastException. - // :: error: (argument.type.incompatible) - // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C - // :: warning: (cast.unsafe.constructor.invocation) - c.id(new @Even Object()); - } + // Oddness is wrong! Would also fail with ClassCastException. + // :: error: (argument.type.incompatible) + // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C + // :: warning: (cast.unsafe.constructor.invocation) + c.id(new @Even Object()); + } } diff --git a/framework/tests/framework/ClassAnnotations.java b/framework/tests/framework/ClassAnnotations.java index c0aa93a0ea9..b427c8fef9c 100644 --- a/framework/tests/framework/ClassAnnotations.java +++ b/framework/tests/framework/ClassAnnotations.java @@ -3,9 +3,9 @@ // ::warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) public @Odd class ClassAnnotations { - ClassAnnotations c; + ClassAnnotations c; - public void test() { - @Odd ClassAnnotations d = c; - } + public void test() { + @Odd ClassAnnotations d = c; + } } diff --git a/framework/tests/framework/Compound.java b/framework/tests/framework/Compound.java index f83c544a596..7559f9f5b71 100644 --- a/framework/tests/framework/Compound.java +++ b/framework/tests/framework/Compound.java @@ -6,13 +6,13 @@ * method type argument inference. */ public class Compound { - public static void one() { - long total = 0; - total = two(); - total += two(); - } + public static void one() { + long total = 0; + total = two(); + total += two(); + } - private static long two() { - return 0; - } + private static long two() { + return 0; + } } diff --git a/framework/tests/framework/Constructors.java b/framework/tests/framework/Constructors.java index 0f655c94866..68eac5cd454 100644 --- a/framework/tests/framework/Constructors.java +++ b/framework/tests/framework/Constructors.java @@ -1,50 +1,50 @@ import org.checkerframework.framework.testchecker.util.Odd; public class Constructors { - public Constructors(Constructors con) {} + public Constructors(Constructors con) {} - public void testConstructors() { - Constructors c = null; - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd Constructors(c); - } - - // Test anonymous constructors - public Constructors(@Odd String s, int i) {} - - public void testStaticAnonymousConstructor() { - String notOdd = "m"; - - // :: error: (argument.type.incompatible) - new Constructors(notOdd, 0); // error - // :: error: (argument.type.incompatible) - new Constructors(notOdd, 0) {}; // error - } - - private class MyConstructors extends Constructors { - public MyConstructors(@Odd String s) { - super(s, 0); - } - } + public void testConstructors() { + Constructors c = null; + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd Constructors(c); + } - public static void testAnonymousConstructor() { - Constructors m = new Constructors(null) {}; - String notOdd = "m"; - // :: error: (argument.type.incompatible) - m.new MyConstructors(notOdd); // error - // :: error: (argument.type.incompatible) - m.new MyConstructors(notOdd) {}; // error - } + // Test anonymous constructors + public Constructors(@Odd String s, int i) {} - // Tests that should pass - public void testPassingTests() { - @Odd String odd = null; + public void testStaticAnonymousConstructor() { + String notOdd = "m"; - new Constructors(odd, 0); - new Constructors(odd, 0) {}; + // :: error: (argument.type.incompatible) + new Constructors(notOdd, 0); // error + // :: error: (argument.type.incompatible) + new Constructors(notOdd, 0) {}; // error + } - Constructors m = new Constructors(null) {}; - m.new MyConstructors(odd); - m.new MyConstructors(odd) {}; + private class MyConstructors extends Constructors { + public MyConstructors(@Odd String s) { + super(s, 0); } + } + + public static void testAnonymousConstructor() { + Constructors m = new Constructors(null) {}; + String notOdd = "m"; + // :: error: (argument.type.incompatible) + m.new MyConstructors(notOdd); // error + // :: error: (argument.type.incompatible) + m.new MyConstructors(notOdd) {}; // error + } + + // Tests that should pass + public void testPassingTests() { + @Odd String odd = null; + + new Constructors(odd, 0); + new Constructors(odd, 0) {}; + + Constructors m = new Constructors(null) {}; + m.new MyConstructors(odd); + m.new MyConstructors(odd) {}; + } } diff --git a/framework/tests/framework/DeepOverride.java b/framework/tests/framework/DeepOverride.java index 0a5d0c992f0..c08aaf2cf7b 100644 --- a/framework/tests/framework/DeepOverride.java +++ b/framework/tests/framework/DeepOverride.java @@ -1,19 +1,19 @@ import org.checkerframework.framework.testchecker.util.*; public class DeepOverride { - public static class A { - public @Odd String method() { - return null; - } + public static class A { + public @Odd String method() { + return null; } + } - public static class B extends A {} + public static class B extends A {} - public static class C extends B { - @Override - // :: error: (override.return.invalid) - public String method() { - return ""; - } + public static class C extends B { + @Override + // :: error: (override.return.invalid) + public String method() { + return ""; } + } } diff --git a/framework/tests/framework/DeepOverrideAbstract.java b/framework/tests/framework/DeepOverrideAbstract.java index d23cb047d48..e2dc12b50c9 100644 --- a/framework/tests/framework/DeepOverrideAbstract.java +++ b/framework/tests/framework/DeepOverrideAbstract.java @@ -2,24 +2,24 @@ public class DeepOverrideAbstract { - public static interface I { - @Odd String interfaceMethod(); - } + public static interface I { + @Odd String interfaceMethod(); + } - public abstract static class A { - public abstract @Odd String abstractMethod(); - } + public abstract static class A { + public abstract @Odd String abstractMethod(); + } - public abstract static class B extends A implements I {} + public abstract static class B extends A implements I {} - public static class C extends B { - public @Odd String interfaceMethod() { - return null; - } + public static class C extends B { + public @Odd String interfaceMethod() { + return null; + } - // :: error: (override.return.invalid) - public String abstractMethod() { - return ""; - } + // :: error: (override.return.invalid) + public String abstractMethod() { + return ""; } + } } diff --git a/framework/tests/framework/DeepOverrideBug.java b/framework/tests/framework/DeepOverrideBug.java index 6610f760558..c0af5a64f5b 100644 --- a/framework/tests/framework/DeepOverrideBug.java +++ b/framework/tests/framework/DeepOverrideBug.java @@ -3,29 +3,29 @@ // TODO: the output have a "missing return statement"? public class DeepOverrideBug { - public static interface I { - @Odd String interfaceMethod(); + public static interface I { + @Odd String interfaceMethod(); - String abstractMethod(); - } + String abstractMethod(); + } - public abstract static class A { - public abstract @Odd String abstractMethod(); + public abstract static class A { + public abstract @Odd String abstractMethod(); - public abstract String interfaceMethod(); - } + public abstract String interfaceMethod(); + } - public abstract static class B extends A implements I {} + public abstract static class B extends A implements I {} - public static class C extends B { - // :: error: (override.return.invalid) - public String interfaceMethod() { // should emit error - return null; - } + public static class C extends B { + // :: error: (override.return.invalid) + public String interfaceMethod() { // should emit error + return null; + } - // :: error: (override.return.invalid) - public String abstractMethod() { // should emit error - return null; - } + // :: error: (override.return.invalid) + public String abstractMethod() { // should emit error + return null; } + } } diff --git a/framework/tests/framework/DeepOverrideInterface.java b/framework/tests/framework/DeepOverrideInterface.java index aec5d9f64fc..ff498df044c 100644 --- a/framework/tests/framework/DeepOverrideInterface.java +++ b/framework/tests/framework/DeepOverrideInterface.java @@ -2,24 +2,24 @@ public class DeepOverrideInterface { - public static interface I { - @Odd String interfaceMethod(); - } + public static interface I { + @Odd String interfaceMethod(); + } - public abstract static class A { - public abstract @Odd String abstractMethod(); - } + public abstract static class A { + public abstract @Odd String abstractMethod(); + } - public abstract static class B extends A implements I {} + public abstract static class B extends A implements I {} - public static class C extends B { - // :: error: (override.return.invalid) - public String interfaceMethod() { - return ""; - } + public static class C extends B { + // :: error: (override.return.invalid) + public String interfaceMethod() { + return ""; + } - public @Odd String abstractMethod() { - return null; - } + public @Odd String abstractMethod() { + return null; } + } } diff --git a/framework/tests/framework/ExtendsDefault.java b/framework/tests/framework/ExtendsDefault.java index 6f532f345c2..ff98573036f 100644 --- a/framework/tests/framework/ExtendsDefault.java +++ b/framework/tests/framework/ExtendsDefault.java @@ -4,21 +4,21 @@ public class ExtendsDefault { - @DefaultQualifier( - value = Odd.class, - locations = {TypeUseLocation.UPPER_BOUND}) - class MyOddDefault {} + @DefaultQualifier( + value = Odd.class, + locations = {TypeUseLocation.UPPER_BOUND}) + class MyOddDefault {} - class MyNonOddDefault {} + class MyNonOddDefault {} - void testNonOdd() { - // :: error: (type.argument.type.incompatible) - MyOddDefault s1; - MyNonOddDefault s2; - } + void testNonOdd() { + // :: error: (type.argument.type.incompatible) + MyOddDefault s1; + MyNonOddDefault s2; + } - void testOdd() { - MyOddDefault<@Odd String> s1; - MyNonOddDefault<@Odd String> s2; - } + void testOdd() { + MyOddDefault<@Odd String> s1; + MyNonOddDefault<@Odd String> s2; + } } diff --git a/framework/tests/framework/GenericAlias.java b/framework/tests/framework/GenericAlias.java index c8423910835..3c9538c1a60 100644 --- a/framework/tests/framework/GenericAlias.java +++ b/framework/tests/framework/GenericAlias.java @@ -1,26 +1,24 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.framework.testchecker.util.*; public class GenericAlias { - public static class SuperSetOne - extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} + public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} - public void test() { - Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); - @Odd Map<@Odd List<@Odd String>, @Odd String> mapA = - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd HashMap<@Odd List<@Odd String>, @Odd String>(); - s.add(mapA); - } + public void test() { + Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); + @Odd Map<@Odd List<@Odd String>, @Odd String> mapA = + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd HashMap<@Odd List<@Odd String>, @Odd String>(); + s.add(mapA); + } - public void regularGenerics() { - Set set = new HashSet<@Odd String>(); - Set set2 = new HashSet<@Odd String>(); - } + public void regularGenerics() { + Set set = new HashSet<@Odd String>(); + Set set2 = new HashSet<@Odd String>(); + } } diff --git a/framework/tests/framework/GenericAliasInvalid.java b/framework/tests/framework/GenericAliasInvalid.java index 983f832f2aa..2d35d47179b 100644 --- a/framework/tests/framework/GenericAliasInvalid.java +++ b/framework/tests/framework/GenericAliasInvalid.java @@ -1,17 +1,15 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.framework.testchecker.util.*; public class GenericAliasInvalid { - public static class SuperSetOne - extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} + public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} - public void test() { - // :: error: (assignment.type.incompatible) - Set, @Odd String>> t = new SuperSetOne(); - } + public void test() { + // :: error: (assignment.type.incompatible) + Set, @Odd String>> t = new SuperSetOne(); + } } diff --git a/framework/tests/framework/GenericAliasInvalidCall.java b/framework/tests/framework/GenericAliasInvalidCall.java index 3f3d229eb6a..a3f9438eab3 100644 --- a/framework/tests/framework/GenericAliasInvalidCall.java +++ b/framework/tests/framework/GenericAliasInvalidCall.java @@ -1,22 +1,20 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.framework.testchecker.util.*; public class GenericAliasInvalidCall { - public static class SuperSetOne - extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} + public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} - public void test() { - Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); - @Odd Map, @Odd String> mapA = - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd HashMap, @Odd String>(); - // :: error: (argument.type.incompatible) - s.add(mapA); - } + public void test() { + Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); + @Odd Map, @Odd String> mapA = + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd HashMap, @Odd String>(); + // :: error: (argument.type.incompatible) + s.add(mapA); + } } diff --git a/framework/tests/framework/GenericEnum.java b/framework/tests/framework/GenericEnum.java index 1512bde4ee1..fd06e834a40 100644 --- a/framework/tests/framework/GenericEnum.java +++ b/framework/tests/framework/GenericEnum.java @@ -2,7 +2,7 @@ public class GenericEnum { - void test() { - T.format(""); - } + void test() { + T.format(""); + } } diff --git a/framework/tests/framework/GenericTest1.java b/framework/tests/framework/GenericTest1.java index 134c3477e05..91ac1a53545 100644 --- a/framework/tests/framework/GenericTest1.java +++ b/framework/tests/framework/GenericTest1.java @@ -3,17 +3,17 @@ // Test case for Issue 131: // https://github.com/typetools/checker-framework/issues/131 public class GenericTest1 { - public interface Foo {} + public interface Foo {} - public interface Bar> extends Foo {} + public interface Bar> extends Foo {} - public void test(Foo foo) { - Bar bar = - foo instanceof Bar - // TODO flow: support instanceof / cast flow. - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - ? (Bar) foo - : null; - } + public void test(Foo foo) { + Bar bar = + foo instanceof Bar + // TODO flow: support instanceof / cast flow. + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + ? (Bar) foo + : null; + } } diff --git a/framework/tests/framework/GenericTest10.java b/framework/tests/framework/GenericTest10.java index 60e8fded648..c602ff2aeb2 100644 --- a/framework/tests/framework/GenericTest10.java +++ b/framework/tests/framework/GenericTest10.java @@ -1,25 +1,25 @@ // Test case for (a part of) Issue 142: // https://github.com/typetools/checker-framework/issues/142 public class GenericTest10 { - abstract static class Bijection { - abstract B apply(A a); + abstract static class Bijection { + abstract B apply(A a); - abstract A invert(B b); + abstract A invert(B b); - Bijection inverse() { - return new Bijection() { - A apply(B b) { - return Bijection.this.invert(b); - } + Bijection inverse() { + return new Bijection() { + A apply(B b) { + return Bijection.this.invert(b); + } - B invert(A a) { - return Bijection.this.apply(a); - } + B invert(A a) { + return Bijection.this.apply(a); + } - Bijection inverse() { - return Bijection.this; - } - }; + Bijection inverse() { + return Bijection.this; } + }; } + } } diff --git a/framework/tests/framework/GenericTest11.java b/framework/tests/framework/GenericTest11.java index a741d8bae4d..ac4341e9d85 100644 --- a/framework/tests/framework/GenericTest11.java +++ b/framework/tests/framework/GenericTest11.java @@ -1,21 +1,21 @@ package com.example; public class GenericTest11 { - public void m(BeanManager beanManager) { - Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); - CreationalContext context = beanManager.createCreationalContext(bean); - } + public void m(BeanManager beanManager) { + Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); + CreationalContext context = beanManager.createCreationalContext(bean); + } - static interface BeanManager { - java.util.Set> getBeans( - java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); + static interface BeanManager { + java.util.Set> getBeans( + java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); - CreationalContext createCreationalContext(Contextual arg0); - } + CreationalContext createCreationalContext(Contextual arg0); + } - static interface Contextual {} + static interface Contextual {} - static interface Bean extends Contextual {} + static interface Bean extends Contextual {} - static interface CreationalContext {} + static interface CreationalContext {} } diff --git a/framework/tests/framework/GenericTest12.java b/framework/tests/framework/GenericTest12.java index 84592046c71..1822abc743f 100644 --- a/framework/tests/framework/GenericTest12.java +++ b/framework/tests/framework/GenericTest12.java @@ -1,10 +1,10 @@ import java.lang.annotation.Annotation; public class GenericTest12 { - @interface Anno {} + @interface Anno {} - void foo(Class qual) { - Annotation a = qual.getAnnotation(Anno.class); - Anno an = qual.getAnnotation(Anno.class); - } + void foo(Class qual) { + Annotation a = qual.getAnnotation(Anno.class); + Anno an = qual.getAnnotation(Anno.class); + } } diff --git a/framework/tests/framework/GenericTest2.java b/framework/tests/framework/GenericTest2.java index a61402d0a45..41cf2ff1136 100644 --- a/framework/tests/framework/GenericTest2.java +++ b/framework/tests/framework/GenericTest2.java @@ -2,13 +2,13 @@ // https://github.com/typetools/checker-framework/issues/132 // Method type argument inference test case. public class GenericTest2 { - public interface Data {} + public interface Data {} - public interface DataUtils { - Data makeData(T value); - } + public interface DataUtils { + Data makeData(T value); + } - public void test(U value, DataUtils utils) { - Data data = utils.makeData(value); - } + public void test(U value, DataUtils utils) { + Data data = utils.makeData(value); + } } diff --git a/framework/tests/framework/GenericTest3.java b/framework/tests/framework/GenericTest3.java index 94d30740ec3..94f0aefa863 100644 --- a/framework/tests/framework/GenericTest3.java +++ b/framework/tests/framework/GenericTest3.java @@ -2,13 +2,13 @@ // https://github.com/typetools/checker-framework/issues/133 // Upper bound of wildcard depends on declared bound of type variable. public class GenericTest3 { - interface Foo {} + interface Foo {} - interface Bar { - T get(); - } + interface Bar { + T get(); + } - public void test(Bar bar) { - Foo foo = bar.get(); - } + public void test(Bar bar) { + Foo foo = bar.get(); + } } diff --git a/framework/tests/framework/GenericTest4.java b/framework/tests/framework/GenericTest4.java index 6b4cc9ec9d9..6aec19a45b0 100644 --- a/framework/tests/framework/GenericTest4.java +++ b/framework/tests/framework/GenericTest4.java @@ -1,60 +1,59 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.Map; +import org.checkerframework.framework.testchecker.util.*; // Test case for Issue 134: // https://github.com/typetools/checker-framework/issues/134 // Handling of generics from different enclosing classes. public class GenericTest4 { - public interface Foo {} - - class Outer { - O getOuter() { - return null; - } - - class Inner { - O getInner() { - return null; - } - - I setter1(O p) { - return null; - } - - O setter2(I p) { - return null; - } - - Map wow(Map p) { - return null; - } - } + public interface Foo {} + + class Outer { + O getOuter() { + return null; + } + + class Inner { + O getInner() { + return null; + } + + I setter1(O p) { + return null; + } + + O setter2(I p) { + return null; + } + + Map wow(Map p) { + return null; + } + } + } + + class OuterImpl extends Outer { + void test() { + Foo foo = getOuter(); } - class OuterImpl extends Outer { - void test() { - Foo foo = getOuter(); - } - - class InnerImpl extends Inner<@Odd String> { - void test() { - Foo foo = getInner(); - String s = setter1(foo); - foo = setter2(s); - } - - void testWow(Map p) { - p = wow(p); - } - - void testWow2(Map p) { - // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) - p = wow(p); - } - } + class InnerImpl extends Inner<@Odd String> { + void test() { + Foo foo = getInner(); + String s = setter1(foo); + foo = setter2(s); + } + + void testWow(Map p) { + p = wow(p); + } + + void testWow2(Map p) { + // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) + p = wow(p); + } } + } - // Add uses from outside of both classes. + // Add uses from outside of both classes. } diff --git a/framework/tests/framework/GenericTest5.java b/framework/tests/framework/GenericTest5.java index 369607d848b..4acd5229971 100644 --- a/framework/tests/framework/GenericTest5.java +++ b/framework/tests/framework/GenericTest5.java @@ -4,23 +4,23 @@ // https://github.com/typetools/checker-framework/issues/135 // Method type argument substitution needs to consider arrays correctly. public class GenericTest5 { - interface Foo { - T[] id(T[] a); - } + interface Foo { + T[] id(T[] a); + } - public void test(Foo foo, U[] a) { - U[] array = foo.id(a); - } + public void test(Foo foo, U[] a) { + U[] array = foo.id(a); + } - public void test1(Foo foo, T[] a) { - T[] array = foo.id(a); - } + public void test1(Foo foo, T[] a) { + T[] array = foo.id(a); + } - public void test2(Foo foo, S[] a) { - S[] array = foo.id(a); - } + public void test2(Foo foo, S[] a) { + S[] array = foo.id(a); + } - public void test3(Foo foo, T[] a) { - T[] array = foo.id(a); - } + public void test3(Foo foo, T[] a) { + T[] array = foo.id(a); + } } diff --git a/framework/tests/framework/GenericTest6.java b/framework/tests/framework/GenericTest6.java index f1ade30c343..07f18df4fd8 100644 --- a/framework/tests/framework/GenericTest6.java +++ b/framework/tests/framework/GenericTest6.java @@ -3,32 +3,32 @@ // Test case for Issue 136: // https://github.com/typetools/checker-framework/issues/136 public class GenericTest6 { - interface Foo> {} + interface Foo> {} - class Strange implements Foo {} + class Strange implements Foo {} - void test(Foo p) {} + void test(Foo p) {} - void call(Foo p) { - test(p); - } + void call(Foo p) { + test(p); + } - void test2(Foo> p) {} + void test2(Foo> p) {} - void call2(Foo> p) { - test2(p); - } + void call2(Foo> p) { + test2(p); + } - void test3(Foo>> p) {} + void test3(Foo>> p) {} - void call3(Foo>> p) { - // :: error: (argument.type.incompatible) - test3(p); - } + void call3(Foo>> p) { + // :: error: (argument.type.incompatible) + test3(p); + } - void testRaw(Foo p) {} + void testRaw(Foo p) {} - void callRaw(Foo> p) { - testRaw(p); - } + void callRaw(Foo> p) { + testRaw(p); + } } diff --git a/framework/tests/framework/GenericTest7.java b/framework/tests/framework/GenericTest7.java index 7329cca7203..e53e0c0345f 100644 --- a/framework/tests/framework/GenericTest7.java +++ b/framework/tests/framework/GenericTest7.java @@ -5,50 +5,50 @@ * https://github.com/typetools/checker-framework/issues/137 */ public class GenericTest7 { - interface A {} - - interface B {} - - interface C {} - - public & C> void one(I i) { - B i1 = i; - C i2 = i; - } - - public & C> void oneA(I i) { - // :: error: (assignment.type.incompatible) - @Odd B i1 = i; - // :: error: (assignment.type.incompatible) - @Odd C i2 = i; - } - - public & @Odd C> void oneB(I i) { - @Odd B i1 = i; - @Odd C i2 = i; - } - - public & C> void two(I i) { - B i1 = i; - C i2 = i; - } - - public & C> void twoA(I i) { - // :: error: (assignment.type.incompatible) - @Odd B i1 = i; - // :: error: (assignment.type.incompatible) - @Odd C i2 = i; - } - - public & @Odd C> void twoB(I i) { - @Odd B i1 = i; - @Odd C i2 = i; - } - - public & C> void twoC(I i) { - B i1 = i; - C i2 = i; - B i3 = i; - C i4 = i; - } + interface A {} + + interface B {} + + interface C {} + + public & C> void one(I i) { + B i1 = i; + C i2 = i; + } + + public & C> void oneA(I i) { + // :: error: (assignment.type.incompatible) + @Odd B i1 = i; + // :: error: (assignment.type.incompatible) + @Odd C i2 = i; + } + + public & @Odd C> void oneB(I i) { + @Odd B i1 = i; + @Odd C i2 = i; + } + + public & C> void two(I i) { + B i1 = i; + C i2 = i; + } + + public & C> void twoA(I i) { + // :: error: (assignment.type.incompatible) + @Odd B i1 = i; + // :: error: (assignment.type.incompatible) + @Odd C i2 = i; + } + + public & @Odd C> void twoB(I i) { + @Odd B i1 = i; + @Odd C i2 = i; + } + + public & C> void twoC(I i) { + B i1 = i; + C i2 = i; + B i3 = i; + C i4 = i; + } } diff --git a/framework/tests/framework/GenericTest8.java b/framework/tests/framework/GenericTest8.java index fe9f14379aa..059fcf99a6a 100644 --- a/framework/tests/framework/GenericTest8.java +++ b/framework/tests/framework/GenericTest8.java @@ -1,17 +1,17 @@ // Test case for Issue 139: // https://github.com/typetools/checker-framework/issues/139 abstract class GenericTest8 { - interface A {} + interface A {} - void foo1(A a) { - foo2(a); - } + void foo1(A a) { + foo2(a); + } - abstract A foo2(A a); + abstract A foo2(A a); - void bar1(A> a) { - bar2(a); - } + void bar1(A> a) { + bar2(a); + } - abstract A> bar2(A> a); + abstract A> bar2(A> a); } diff --git a/framework/tests/framework/GenericTest9.java b/framework/tests/framework/GenericTest9.java index d9c9b396ab9..b83f24c95f7 100644 --- a/framework/tests/framework/GenericTest9.java +++ b/framework/tests/framework/GenericTest9.java @@ -3,70 +3,70 @@ // Test case for Issue 140: // https://github.com/typetools/checker-framework/issues/140 public class GenericTest9 { - // Make sure that substitutions on classes work correctly - - class C {} - - C> f = new C<>(); - - interface MyEntry {} - - void testclass() { - // :: error: (type.argument.type.incompatible) - C<@Odd Object, MyEntry> c1 = new C<>(); - C<@Odd Object, @Odd MyEntry> c2 = new C<>(); - } - - // Make sure that substitutions on method type variables work correctly - - interface Ordering1 { - U sort(U iterable); - } - - void test(Ordering1> o, MyEntry e) { - MyEntry e1 = o.sort(e); - MyEntry e2 = o.>sort(e); - } - - interface Ordering2 { - U sort(U iterable); - } - - void test(Ordering2<@Odd MyEntry> ord, MyEntry e, @Odd MyEntry o) { - // :: error: (type.argument.type.incompatible) - MyEntry e1 = ord.sort(e); - // :: error: (type.argument.type.incompatible) - MyEntry e2 = ord.>sort(e); - MyEntry e3 = ord.sort(o); - MyEntry e4 = ord.<@Odd MyEntry>sort(o); - } - - interface Ordering3<@Odd T> { - U sort(U iterable); - } - - void test(Ordering3<@Odd MyEntry> o, @Odd MyEntry e) { - MyEntry e1 = o.sort(e); - MyEntry e2 = o.<@Odd MyEntry>sort(e); - // :: error: (type.argument.type.incompatible) :: error: (argument.type.incompatible) - MyEntry e3 = o.<@Even MyEntry>sort(e); - } - - interface Comparator4 {} - - interface Ordering4 extends Comparator4 { - Ordering4 reverse(); - } - - Ordering4 from4(Comparator4 comparator) { - return null; - } - - Comparator4 reverseComparator4(Comparator4 comparator) { - // Making the method type argument explicit: - // from4(comparator).reverse(); - // has the same result. - from4(comparator).reverse(); - return from4(comparator).reverse(); - } + // Make sure that substitutions on classes work correctly + + class C {} + + C> f = new C<>(); + + interface MyEntry {} + + void testclass() { + // :: error: (type.argument.type.incompatible) + C<@Odd Object, MyEntry> c1 = new C<>(); + C<@Odd Object, @Odd MyEntry> c2 = new C<>(); + } + + // Make sure that substitutions on method type variables work correctly + + interface Ordering1 { + U sort(U iterable); + } + + void test(Ordering1> o, MyEntry e) { + MyEntry e1 = o.sort(e); + MyEntry e2 = o.>sort(e); + } + + interface Ordering2 { + U sort(U iterable); + } + + void test(Ordering2<@Odd MyEntry> ord, MyEntry e, @Odd MyEntry o) { + // :: error: (type.argument.type.incompatible) + MyEntry e1 = ord.sort(e); + // :: error: (type.argument.type.incompatible) + MyEntry e2 = ord.>sort(e); + MyEntry e3 = ord.sort(o); + MyEntry e4 = ord.<@Odd MyEntry>sort(o); + } + + interface Ordering3<@Odd T> { + U sort(U iterable); + } + + void test(Ordering3<@Odd MyEntry> o, @Odd MyEntry e) { + MyEntry e1 = o.sort(e); + MyEntry e2 = o.<@Odd MyEntry>sort(e); + // :: error: (type.argument.type.incompatible) :: error: (argument.type.incompatible) + MyEntry e3 = o.<@Even MyEntry>sort(e); + } + + interface Comparator4 {} + + interface Ordering4 extends Comparator4 { + Ordering4 reverse(); + } + + Ordering4 from4(Comparator4 comparator) { + return null; + } + + Comparator4 reverseComparator4(Comparator4 comparator) { + // Making the method type argument explicit: + // from4(comparator).reverse(); + // has the same result. + from4(comparator).reverse(); + return from4(comparator).reverse(); + } } diff --git a/framework/tests/framework/GetReceiverLoop.java b/framework/tests/framework/GetReceiverLoop.java index 3c953934f9c..278a788a25b 100644 --- a/framework/tests/framework/GetReceiverLoop.java +++ b/framework/tests/framework/GetReceiverLoop.java @@ -2,21 +2,21 @@ public class GetReceiverLoop { - void test() { - String s = Collections.emptyList().toString(); - } + void test() { + String s = Collections.emptyList().toString(); + } - /* - * getAnnotatedType( emptyList().toString ) - * -> TypeFromExpression.visitMemberSelect( emptyList().toString ) - * -> TypeFromExpression.visitMethodInvocation( emptyList() ) - * -> AnnotatedTypes.findTypeParameters( emptyList() ) - * -> AnnotatedTypes.assignedTo( emptyList() ) - * [the assignment context is emptyList().toString(), so then:] - * -> AnnotatedTypeFactory.getReceiver( emptyList() ) - * -> getAnnotatedType( emtpyList() ) - * -> TypeFromExpression.visitMethodInvocation( emptyList() ) - * ... - */ + /* + * getAnnotatedType( emptyList().toString ) + * -> TypeFromExpression.visitMemberSelect( emptyList().toString ) + * -> TypeFromExpression.visitMethodInvocation( emptyList() ) + * -> AnnotatedTypes.findTypeParameters( emptyList() ) + * -> AnnotatedTypes.assignedTo( emptyList() ) + * [the assignment context is emptyList().toString(), so then:] + * -> AnnotatedTypeFactory.getReceiver( emptyList() ) + * -> getAnnotatedType( emtpyList() ) + * -> TypeFromExpression.visitMethodInvocation( emptyList() ) + * ... + */ } diff --git a/framework/tests/framework/InnerGenerics.java b/framework/tests/framework/InnerGenerics.java index d368cb16eec..d4677322555 100644 --- a/framework/tests/framework/InnerGenerics.java +++ b/framework/tests/framework/InnerGenerics.java @@ -3,31 +3,31 @@ class ListOuter {} public class InnerGenerics { - class ListInner {} + class ListInner {} - void testInner1() { - // :: warning: (cast.unsafe.constructor.invocation) - @Odd ListOuter o = new @Odd ListOuter(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd ListInner i = new @Odd ListInner(); - } + void testInner1() { + // :: warning: (cast.unsafe.constructor.invocation) + @Odd ListOuter o = new @Odd ListOuter(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd ListInner i = new @Odd ListInner(); + } - void testInner2() { - // :: error: (assignment.type.incompatible) - @Odd ListOuter o = new ListOuter<>(); - // :: error: (assignment.type.incompatible) - @Odd ListInner i = new ListInner<>(); - } + void testInner2() { + // :: error: (assignment.type.incompatible) + @Odd ListOuter o = new ListOuter<>(); + // :: error: (assignment.type.incompatible) + @Odd ListInner i = new ListInner<>(); + } - void testInner3() { - ListOuter<@Odd String> o = new ListOuter<>(); - ListInner<@Odd String> i = new ListInner<>(); - } + void testInner3() { + ListOuter<@Odd String> o = new ListOuter<>(); + ListInner<@Odd String> i = new ListInner<>(); + } - void testInner4() { - // :: error: (assignment.type.incompatible) - ListOuter<@Odd String> o = new ListOuter(); - // :: error: (assignment.type.incompatible) - ListInner<@Odd String> i = new ListInner(); - } + void testInner4() { + // :: error: (assignment.type.incompatible) + ListOuter<@Odd String> o = new ListOuter(); + // :: error: (assignment.type.incompatible) + ListInner<@Odd String> i = new ListInner(); + } } diff --git a/framework/tests/framework/MatrixBug.java b/framework/tests/framework/MatrixBug.java index 91496fb886d..382b2dacdfb 100644 --- a/framework/tests/framework/MatrixBug.java +++ b/framework/tests/framework/MatrixBug.java @@ -2,5 +2,5 @@ public class MatrixBug { - public char[][] chars = new char[][] {new char[] {'*', '*', '*'}, new char[] {'*', '*', '*'}}; + public char[][] chars = new char[][] {new char[] {'*', '*', '*'}, new char[] {'*', '*', '*'}}; } diff --git a/framework/tests/framework/MethodOverrideBadParam.java b/framework/tests/framework/MethodOverrideBadParam.java index 1db418c2f37..e8fdee4d6ef 100644 --- a/framework/tests/framework/MethodOverrideBadParam.java +++ b/framework/tests/framework/MethodOverrideBadParam.java @@ -2,10 +2,10 @@ public abstract class MethodOverrideBadParam { - public abstract void method(String s); + public abstract void method(String s); - public static class SubclassA extends MethodOverrideBadParam { - // :: error: (override.param.invalid) - public void method(@Odd String s) {} - } + public static class SubclassA extends MethodOverrideBadParam { + // :: error: (override.param.invalid) + public void method(@Odd String s) {} + } } diff --git a/framework/tests/framework/MethodOverrideBadReceiver.java b/framework/tests/framework/MethodOverrideBadReceiver.java index d595ba45e49..bcaf41c365f 100644 --- a/framework/tests/framework/MethodOverrideBadReceiver.java +++ b/framework/tests/framework/MethodOverrideBadReceiver.java @@ -2,12 +2,12 @@ public abstract class MethodOverrideBadReceiver { - public abstract String method(); + public abstract String method(); - public static class SubclassA extends MethodOverrideBadReceiver { - // :: error: (override.receiver.invalid) - public String method(@Odd SubclassA this) { - return ""; - } + public static class SubclassA extends MethodOverrideBadReceiver { + // :: error: (override.receiver.invalid) + public String method(@Odd SubclassA this) { + return ""; } + } } diff --git a/framework/tests/framework/MethodOverrideBadReturn.java b/framework/tests/framework/MethodOverrideBadReturn.java index 74aea948732..2e48a373c4f 100644 --- a/framework/tests/framework/MethodOverrideBadReturn.java +++ b/framework/tests/framework/MethodOverrideBadReturn.java @@ -2,12 +2,12 @@ public abstract class MethodOverrideBadReturn { - public abstract @Odd String method(); + public abstract @Odd String method(); - public static class SubclassA extends MethodOverrideBadReturn { - // :: error: (override.return.invalid) - public String method() { - return ""; - } + public static class SubclassA extends MethodOverrideBadReturn { + // :: error: (override.return.invalid) + public String method() { + return ""; } + } } diff --git a/framework/tests/framework/MethodOverrides.java b/framework/tests/framework/MethodOverrides.java index 96c26b021d4..1318a6c26bd 100644 --- a/framework/tests/framework/MethodOverrides.java +++ b/framework/tests/framework/MethodOverrides.java @@ -2,91 +2,91 @@ public abstract class MethodOverrides { - public abstract @Odd String method(); + public abstract @Odd String method(); - public abstract String methodSub(); + public abstract String methodSub(); - public abstract void param(@Odd String s); + public abstract void param(@Odd String s); - public abstract void paramSup(@Odd String s); + public abstract void paramSup(@Odd String s); - public abstract void receiver(@Odd MethodOverrides this); + public abstract void receiver(@Odd MethodOverrides this); - public abstract void receiverSub(@Odd MethodOverrides this); + public abstract void receiverSub(@Odd MethodOverrides this); - public static class SubclassA extends MethodOverrides { + public static class SubclassA extends MethodOverrides { - public @Odd String method() { - // :: error: (assignment.type.incompatible) - @Odd String s = ""; - return s; - } + public @Odd String method() { + // :: error: (assignment.type.incompatible) + @Odd String s = ""; + return s; + } - public @Odd String methodSub() { - // :: error: (assignment.type.incompatible) - @Odd String s = ""; - return s; - } + public @Odd String methodSub() { + // :: error: (assignment.type.incompatible) + @Odd String s = ""; + return s; + } - public void param(@Odd String s) {} + public void param(@Odd String s) {} - public void paramSup(String s) {} + public void paramSup(String s) {} - public void receiver(@Odd SubclassA this) {} + public void receiver(@Odd SubclassA this) {} - public void receiverSub() {} - } + public void receiverSub() {} + } - static class X { - T @Odd [] method(T @Odd [] t) { - return null; - } + static class X { + T @Odd [] method(T @Odd [] t) { + return null; } + } - static class Y extends X { - @Override - S @Odd [] method(S @Odd [] s) { - return null; - } + static class Y extends X { + @Override + S @Odd [] method(S @Odd [] s) { + return null; } - - static class Z extends X { - @Override - // return type is an incorrect override, as it's a supertype - // :: error: (override.return.invalid) - A[] method(A[] s) { - return null; - } + } + + static class Z extends X { + @Override + // return type is an incorrect override, as it's a supertype + // :: error: (override.return.invalid) + A[] method(A[] s) { + return null; } + } - static class Z2 extends X { - @Override - // :: error: (override.return.invalid) :: error: (override.param.invalid) - @Odd A[] method(@Odd A[] s) { - return null; - } + static class Z2 extends X { + @Override + // :: error: (override.return.invalid) :: error: (override.param.invalid) + @Odd A[] method(@Odd A[] s) { + return null; } + } - static class ClX { - T @Odd [] method(T @Odd [] t) { - return null; - } + static class ClX { + T @Odd [] method(T @Odd [] t) { + return null; } + } - static class ClY extends ClX { - @Override - S @Odd [] method(S @Odd [] s) { - return null; - } + static class ClY extends ClX { + @Override + S @Odd [] method(S @Odd [] s) { + return null; } + } - static class ClZ extends ClX { - @Override - // :: error: (override.return.invalid) :: error: (override.param.invalid) - @Odd S[] method(@Odd S[] s) { - return null; - } + static class ClZ extends ClX { + @Override + // :: error: (override.return.invalid) :: error: (override.param.invalid) + @Odd S[] method(@Odd S[] s) { + return null; } + } - // TODO others... + // TODO others... } diff --git a/framework/tests/framework/MoreVarargs.java b/framework/tests/framework/MoreVarargs.java index 9200417dd50..8f01d4e831d 100644 --- a/framework/tests/framework/MoreVarargs.java +++ b/framework/tests/framework/MoreVarargs.java @@ -1,15 +1,15 @@ public class MoreVarargs { - // :: warning: [unchecked] Possible heap pollution from parameterized vararg type T - T[] genericVararg(T... args) { - return args; - } + // :: warning: [unchecked] Possible heap pollution from parameterized vararg type T + T[] genericVararg(T... args) { + return args; + } - void testGenericVararg() { - genericVararg("m"); - genericVararg(new String[] {}); - genericVararg(3); - genericVararg(new Integer[] {}); - genericVararg(new int[] {}); - } + void testGenericVararg() { + genericVararg("m"); + genericVararg(new String[] {}); + genericVararg(3); + genericVararg(new Integer[] {}); + genericVararg(new int[] {}); + } } diff --git a/framework/tests/framework/MultiBoundTypeVar.java b/framework/tests/framework/MultiBoundTypeVar.java index 32fe6aad90f..ef25cc5a2f1 100644 --- a/framework/tests/framework/MultiBoundTypeVar.java +++ b/framework/tests/framework/MultiBoundTypeVar.java @@ -2,15 +2,15 @@ public class MultiBoundTypeVar { - void test(T t) { - Number n1 = t; - @Odd Number n2 = t; + void test(T t) { + Number n1 = t; + @Odd Number n2 = t; - Cloneable c1 = t; + Cloneable c1 = t; - @Odd Cloneable c2 = t; + @Odd Cloneable c2 = t; - Appendable d1 = t; - @Odd Appendable d2 = t; - } + Appendable d1 = t; + @Odd Appendable d2 = t; + } } diff --git a/framework/tests/framework/OverrideCrash.java b/framework/tests/framework/OverrideCrash.java index 77a99d44020..751613b1418 100644 --- a/framework/tests/framework/OverrideCrash.java +++ b/framework/tests/framework/OverrideCrash.java @@ -1,8 +1,8 @@ import java.util.ArrayList; public class OverrideCrash extends ArrayList { - @Override - public Object[] toArray(Object[] o) { - return null; - } + @Override + public Object[] toArray(Object[] o) { + return null; + } } diff --git a/framework/tests/framework/PrimitiveDotClass.java b/framework/tests/framework/PrimitiveDotClass.java index 1501249c9cf..658bd429cfc 100644 --- a/framework/tests/framework/PrimitiveDotClass.java +++ b/framework/tests/framework/PrimitiveDotClass.java @@ -1,9 +1,9 @@ public class PrimitiveDotClass { - void test() { - doStuff(int.class); - doStuff(int[].class); - } + void test() { + doStuff(int.class); + doStuff(int[].class); + } - void doStuff(Class cl) {} + void doStuff(Class cl) {} } diff --git a/framework/tests/framework/RandomTests.java b/framework/tests/framework/RandomTests.java index 1ccd5e58875..e688dae1698 100644 --- a/framework/tests/framework/RandomTests.java +++ b/framework/tests/framework/RandomTests.java @@ -2,13 +2,13 @@ import java.util.List; public class RandomTests { - // Test that boxing occurs even if the varable (assigned to value) is not a declared type - void testBoxing() { - int i = 0; - Collections.singleton(i); - } + // Test that boxing occurs even if the varable (assigned to value) is not a declared type + void testBoxing() { + int i = 0; + Collections.singleton(i); + } - void testWildcards() { - List l = null; - } + void testWildcards() { + List l = null; + } } diff --git a/framework/tests/framework/RecursiveDef.java b/framework/tests/framework/RecursiveDef.java index fe3c4fc24c6..d953b5eef66 100644 --- a/framework/tests/framework/RecursiveDef.java +++ b/framework/tests/framework/RecursiveDef.java @@ -1,6 +1,6 @@ public class RecursiveDef implements Comparable { - @org.checkerframework.dataflow.qual.Pure - public int compareTo(T t) { - return 0; - } + @org.checkerframework.dataflow.qual.Pure + public int compareTo(T t) { + return 0; + } } diff --git a/framework/tests/framework/Supertypes.java b/framework/tests/framework/Supertypes.java index 736fdff5f03..fef30cf0eda 100644 --- a/framework/tests/framework/Supertypes.java +++ b/framework/tests/framework/Supertypes.java @@ -1,87 +1,86 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.framework.testchecker.util.*; public class Supertypes { - static interface Inter {} + static interface Inter {} - static class A extends ArrayList implements Inter<@Odd String> {} + static class A extends ArrayList implements Inter<@Odd String> {} - static class B extends ArrayList<@Odd String> implements Inter {} + static class B extends ArrayList<@Odd String> implements Inter {} - A a1; - @Odd A a2; + A a1; + @Odd A a2; - B b1; - @Odd B b2; + B b1; + @Odd B b2; - void testSelf() { - // :: error: (assignment.type.incompatible) - @Odd A t1 = a1; // should emit error - @Odd A t2 = a2; - // :: error: (assignment.type.incompatible) - @Odd B t3 = b1; // should emit error - @Odd B t4 = b2; - } + void testSelf() { + // :: error: (assignment.type.incompatible) + @Odd A t1 = a1; // should emit error + @Odd A t2 = a2; + // :: error: (assignment.type.incompatible) + @Odd B t3 = b1; // should emit error + @Odd B t4 = b2; + } - void testList() { - List l1 = a1; - List l2 = a2; - // :: error: (assignment.type.incompatible) - List l3 = b1; // should emit error - // :: error: (assignment.type.incompatible) - List l4 = b2; // should emit error + void testList() { + List l1 = a1; + List l2 = a2; + // :: error: (assignment.type.incompatible) + List l3 = b1; // should emit error + // :: error: (assignment.type.incompatible) + List l4 = b2; // should emit error - // :: error: (assignment.type.incompatible) - List<@Odd String> l5 = a1; // should emit error - // :: error: (assignment.type.incompatible) - List<@Odd String> l6 = a2; // should emit error - List<@Odd String> l7 = b1; - List<@Odd String> l8 = b2; - } + // :: error: (assignment.type.incompatible) + List<@Odd String> l5 = a1; // should emit error + // :: error: (assignment.type.incompatible) + List<@Odd String> l6 = a2; // should emit error + List<@Odd String> l7 = b1; + List<@Odd String> l8 = b2; + } - void testInter() { - // :: error: (assignment.type.incompatible) - Inter l1 = a1; // should emit error - // :: error: (assignment.type.incompatible) - Inter l2 = a2; // should emit error - Inter l3 = b1; - Inter l4 = b2; + void testInter() { + // :: error: (assignment.type.incompatible) + Inter l1 = a1; // should emit error + // :: error: (assignment.type.incompatible) + Inter l2 = a2; // should emit error + Inter l3 = b1; + Inter l4 = b2; - Inter<@Odd String> l5 = a1; - Inter<@Odd String> l6 = a2; - // :: error: (assignment.type.incompatible) - Inter<@Odd String> l7 = b1; // should emit error - // :: error: (assignment.type.incompatible) - Inter<@Odd String> l8 = b2; // should emit error - } + Inter<@Odd String> l5 = a1; + Inter<@Odd String> l6 = a2; + // :: error: (assignment.type.incompatible) + Inter<@Odd String> l7 = b1; // should emit error + // :: error: (assignment.type.incompatible) + Inter<@Odd String> l8 = b2; // should emit error + } - void testListOp() { - String s1 = a1.get(0); - String s2 = a2.get(0); - String s3 = b1.get(0); - String s4 = b2.get(0); + void testListOp() { + String s1 = a1.get(0); + String s2 = a2.get(0); + String s3 = b1.get(0); + String s4 = b2.get(0); - // :: error: (assignment.type.incompatible) - @Odd String s5 = a1.get(0); // should emit error - // :: error: (assignment.type.incompatible) - @Odd String s6 = a2.get(0); // should emit error - @Odd String s7 = b1.get(0); - @Odd String s8 = b2.get(0); - } + // :: error: (assignment.type.incompatible) + @Odd String s5 = a1.get(0); // should emit error + // :: error: (assignment.type.incompatible) + @Odd String s6 = a2.get(0); // should emit error + @Odd String s7 = b1.get(0); + @Odd String s8 = b2.get(0); + } - void ListIterable() { - for (String s : a1) {} - for (String s : a2) {} - for (String s : b1) {} - for (String s : b2) {} + void ListIterable() { + for (String s : a1) {} + for (String s : a2) {} + for (String s : b1) {} + for (String s : b2) {} - // :: error: (enhancedfor.type.incompatible) - for (@Odd String s : a1) {} - // :: error: (enhancedfor.type.incompatible) - for (@Odd String s : a2) {} - for (@Odd String s : b1) {} - for (@Odd String s : b2) {} - } + // :: error: (enhancedfor.type.incompatible) + for (@Odd String s : a1) {} + // :: error: (enhancedfor.type.incompatible) + for (@Odd String s : a2) {} + for (@Odd String s : b1) {} + for (@Odd String s : b2) {} + } } diff --git a/framework/tests/framework/SymbolError.java b/framework/tests/framework/SymbolError.java index 48154a435df..469c4d3d3f1 100644 --- a/framework/tests/framework/SymbolError.java +++ b/framework/tests/framework/SymbolError.java @@ -3,7 +3,7 @@ public class SymbolError { - void test() { - List lst = new LinkedList(null) {}; - } + void test() { + List lst = new LinkedList(null) {}; + } } diff --git a/framework/tests/framework/TypeInference.java b/framework/tests/framework/TypeInference.java index e11961fbe51..29e5d614212 100644 --- a/framework/tests/framework/TypeInference.java +++ b/framework/tests/framework/TypeInference.java @@ -1,37 +1,36 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.checkerframework.framework.testchecker.util.*; public class TypeInference { - void test() { - Collection<@Odd String> lst1 = Collections.<@Odd String>emptyList(); - // :: error: (assignment.type.incompatible) - Collection<@Odd String> lst2 = Collections.emptyList(); // should emit error - // :: error: (assignment.type.incompatible) - Collection lst3 = Collections.<@Odd String>emptyList(); // should emit error - Collection<@Odd String> lst4 = Collections.emptyList(); - Map lst5 = Collections.emptyMap(); - Map lst6 = Collections.emptyMap(); - } + void test() { + Collection<@Odd String> lst1 = Collections.<@Odd String>emptyList(); + // :: error: (assignment.type.incompatible) + Collection<@Odd String> lst2 = Collections.emptyList(); // should emit error + // :: error: (assignment.type.incompatible) + Collection lst3 = Collections.<@Odd String>emptyList(); // should emit error + Collection<@Odd String> lst4 = Collections.emptyList(); + Map lst5 = Collections.emptyMap(); + Map lst6 = Collections.emptyMap(); + } - static class MyMap extends HashMap {} + static class MyMap extends HashMap {} - static MyMap getMap() { - return null; - } + static MyMap getMap() { + return null; + } - void testSuper() { - MyMap<@Odd String> m1 = TypeInference.<@Odd String>getMap(); - MyMap<@Odd String> m2 = getMap(); - // :: error: (assignment.type.incompatible) - MyMap m3 = TypeInference.<@Odd String>getMap(); // should emit error - MyMap m4 = getMap(); + void testSuper() { + MyMap<@Odd String> m1 = TypeInference.<@Odd String>getMap(); + MyMap<@Odd String> m2 = getMap(); + // :: error: (assignment.type.incompatible) + MyMap m3 = TypeInference.<@Odd String>getMap(); // should emit error + MyMap m4 = getMap(); - Map m5 = getMap(); - Map m6 = getMap(); - } + Map m5 = getMap(); + Map m6 = getMap(); + } } diff --git a/framework/tests/framework/Unboxing.java b/framework/tests/framework/Unboxing.java index a4ec7bd2c30..8aa1169524f 100644 --- a/framework/tests/framework/Unboxing.java +++ b/framework/tests/framework/Unboxing.java @@ -1,16 +1,16 @@ public class Unboxing { - boolean b = Boolean.TRUE; + boolean b = Boolean.TRUE; - T foo(Class expectedType) { - return null; - } + T foo(Class expectedType) { + return null; + } - boolean b2 = foo(Boolean.class); - boolean b3 = foo(null); + boolean b2 = foo(Boolean.class); + boolean b3 = foo(null); - T bar() { - return null; - } + T bar() { + return null; + } - boolean b4 = bar(); + boolean b4 = bar(); } diff --git a/framework/tests/framework/Varargs.java b/framework/tests/framework/Varargs.java index 30b123648e1..299a2badb25 100644 --- a/framework/tests/framework/Varargs.java +++ b/framework/tests/framework/Varargs.java @@ -1,43 +1,43 @@ import org.checkerframework.framework.testchecker.util.*; public class Varargs { - public void testVarargsInvocation() { - @Odd String s = null; - aVarargsMethod(s); + public void testVarargsInvocation() { + @Odd String s = null; + aVarargsMethod(s); + // :: error: (argument.type.incompatible) + aVarargsMethod(s, ""); + aVarargsMethod(s, s); + + moreVarargs(new @Odd String[1]); + // The assignment context infers @Odd for the component type. With invariant array + // subtyping, this will fail, as the main type is a subtype. + moreVarargs(new String @Odd [1]); + // :: warning: (cast.unsafe.constructor.invocation) + moreVarargs(new @Odd String(), new @Odd String()); + // :: error: (argument.type.incompatible) + // :: warning: (cast.unsafe.constructor.invocation) + moreVarargs(new String(), new @Odd String()); + moreVarargs( // :: error: (argument.type.incompatible) - aVarargsMethod(s, ""); - aVarargsMethod(s, s); - - moreVarargs(new @Odd String[1]); - // The assignment context infers @Odd for the component type. With invariant array - // subtyping, this will fail, as the main type is a subtype. - moreVarargs(new String @Odd [1]); - // :: warning: (cast.unsafe.constructor.invocation) - moreVarargs(new @Odd String(), new @Odd String()); + new String(), // :: error: (argument.type.incompatible) - // :: warning: (cast.unsafe.constructor.invocation) - moreVarargs(new String(), new @Odd String()); - moreVarargs( - // :: error: (argument.type.incompatible) - new String(), - // :: error: (argument.type.incompatible) - new String()); - } + new String()); + } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ - public void aVarargsMethod(@Odd String s, @Odd String... more) {} + public void aVarargsMethod(@Odd String s, @Odd String... more) {} - public void moreVarargs(@Odd String... args) {} + public void moreVarargs(@Odd String... args) {} - Varargs(String... args) {} + Varargs(String... args) {} - void test() { - new Varargs("m", "n"); - new Varargs(); - } + void test() { + new Varargs("m", "n"); + new Varargs(); + } - void testVarargsConstructor() { - new ProcessBuilder("hello"); - } + void testVarargsConstructor() { + new ProcessBuilder("hello"); + } } diff --git a/framework/tests/framework/WildcardSuper.java b/framework/tests/framework/WildcardSuper.java index afb2754f2f9..3feb54bb22f 100644 --- a/framework/tests/framework/WildcardSuper.java +++ b/framework/tests/framework/WildcardSuper.java @@ -1,10 +1,9 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.List; +import org.checkerframework.framework.testchecker.util.*; public class WildcardSuper { - void test(List list) { - // :: error: (assignment.type.incompatible) - @Odd Object odd = list.get(0); - } + void test(List list) { + // :: error: (assignment.type.incompatible) + @Odd Object odd = list.get(0); + } } diff --git a/framework/tests/framework/Wildcards.java b/framework/tests/framework/Wildcards.java index cdac316625f..10f34f89623 100644 --- a/framework/tests/framework/Wildcards.java +++ b/framework/tests/framework/Wildcards.java @@ -1,13 +1,12 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.Date; import java.util.List; +import org.checkerframework.framework.testchecker.util.*; public class Wildcards { - void process(List arg) {} + void process(List arg) {} - void test() { - List myList = null; - process(myList); - } + void test() { + List myList = null; + process(myList); + } } diff --git a/framework/tests/h1h2checker/AnonymousClasses.java b/framework/tests/h1h2checker/AnonymousClasses.java index a3e511eb66a..f328ff970d2 100644 --- a/framework/tests/h1h2checker/AnonymousClasses.java +++ b/framework/tests/h1h2checker/AnonymousClasses.java @@ -1,21 +1,20 @@ +import java.util.Comparator; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S1; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S2; -import java.util.Comparator; - public class AnonymousClasses { - private <@H1S1 T extends @H1S1 Comparator> void testGenericAnonymous() { - // :: error: (type.argument.type.incompatible) :: error: (constructor.invocation.invalid) - new @H1S1 Gen() {}; - // :: error: (type.argument.type.incompatible) :: warning: - // (cast.unsafe.constructor.invocation) - new @H1S1 GenInter() {}; - } + private <@H1S1 T extends @H1S1 Comparator> void testGenericAnonymous() { + // :: error: (type.argument.type.incompatible) :: error: (constructor.invocation.invalid) + new @H1S1 Gen() {}; + // :: error: (type.argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + new @H1S1 GenInter() {}; + } } class Gen<@H1S2 F extends @H1S2 Object> { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - public @H1S2 Gen() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + public @H1S2 Gen() {} } interface GenInter<@H1S2 F extends @H1S2 Object> {} diff --git a/framework/tests/h1h2checker/Catch.java b/framework/tests/h1h2checker/Catch.java index 4cfc376a7c1..3a615264f8f 100644 --- a/framework/tests/h1h2checker/Catch.java +++ b/framework/tests/h1h2checker/Catch.java @@ -1,53 +1,53 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Catch { - void defaultUnionType() throws Throwable { - try { - throw new Throwable(); - } catch (IndexOutOfBoundsException | NullPointerException ex) { + void defaultUnionType() throws Throwable { + try { + throw new Throwable(); + } catch (IndexOutOfBoundsException | NullPointerException ex) { - } } + } - void defaultDeclaredType() throws Throwable { - try { - throw new Throwable(); - } catch (RuntimeException ex) { + void defaultDeclaredType() throws Throwable { + try { + throw new Throwable(); + } catch (RuntimeException ex) { - } } + } - void explictlyTopUnionType() throws Throwable { - try { - throw new Throwable(); - } catch (@H1Top @H2Top IndexOutOfBoundsException | @H1Top @H2Top NullPointerException ex) { + void explictlyTopUnionType() throws Throwable { + try { + throw new Throwable(); + } catch (@H1Top @H2Top IndexOutOfBoundsException | @H1Top @H2Top NullPointerException ex) { - } } + } - void explictlyNotTopUnionType() throws Throwable { - try { - throw new Throwable(); - // :: error: (exception.parameter.invalid) - } catch (@H1S1 @H2Top IndexOutOfBoundsException | @H1S1 @H2Top NullPointerException ex) { + void explictlyNotTopUnionType() throws Throwable { + try { + throw new Throwable(); + // :: error: (exception.parameter.invalid) + } catch (@H1S1 @H2Top IndexOutOfBoundsException | @H1S1 @H2Top NullPointerException ex) { - } } + } - void explictlyTopDeclaredType() throws Throwable { - try { - throw new Throwable(); - } catch (@H1Top @H2Top NullPointerException ex) { + void explictlyTopDeclaredType() throws Throwable { + try { + throw new Throwable(); + } catch (@H1Top @H2Top NullPointerException ex) { - } } + } - void explictlyNotTopDeclaredType() throws Throwable { - try { - throw new Throwable(); - // :: error: (exception.parameter.invalid) - } catch (@H1S1 @H2Top RuntimeException ex) { + void explictlyNotTopDeclaredType() throws Throwable { + try { + throw new Throwable(); + // :: error: (exception.parameter.invalid) + } catch (@H1S1 @H2Top RuntimeException ex) { - } } + } } diff --git a/framework/tests/h1h2checker/CompoundStringAssignment.java b/framework/tests/h1h2checker/CompoundStringAssignment.java index 89edada1470..4ecaddaa816 100644 --- a/framework/tests/h1h2checker/CompoundStringAssignment.java +++ b/framework/tests/h1h2checker/CompoundStringAssignment.java @@ -1,43 +1,43 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class CompoundStringAssignment { - @H1S1 @H2S1 String getSib1() { - return null; - } - - void test1() { - String local = null; - // There was a bug in data flow where - // the type of local was @H1Bot @H2Bot after the - // StringConcatenateNode and AssignmentNode, - // but only if the RHS was a method call. - local += getSib1(); - - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String isBot = local; - @H1S1 @H2S1 String isSib1 = local; - } - - @H1Top @H2Top String top; - - void test2() { - String local2 = top; - local2 += getSib1(); - - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String isBot2 = local2; - // :: error: (assignment.type.incompatible) - @H1S1 @H2S1 String isSib12 = local2; - } - - @H1S1 @H2S1 String sib1; - - void test3() { - String local3 = null; - local3 += sib1; - - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String isBot3 = local3; - @H1S1 @H2S1 String isSib13 = local3; - } + @H1S1 @H2S1 String getSib1() { + return null; + } + + void test1() { + String local = null; + // There was a bug in data flow where + // the type of local was @H1Bot @H2Bot after the + // StringConcatenateNode and AssignmentNode, + // but only if the RHS was a method call. + local += getSib1(); + + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String isBot = local; + @H1S1 @H2S1 String isSib1 = local; + } + + @H1Top @H2Top String top; + + void test2() { + String local2 = top; + local2 += getSib1(); + + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String isBot2 = local2; + // :: error: (assignment.type.incompatible) + @H1S1 @H2S1 String isSib12 = local2; + } + + @H1S1 @H2S1 String sib1; + + void test3() { + String local3 = null; + local3 += sib1; + + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String isBot3 = local3; + @H1S1 @H2S1 String isSib13 = local3; + } } diff --git a/framework/tests/h1h2checker/Constructors.java b/framework/tests/h1h2checker/Constructors.java index a8abec327b1..b608fd6dd82 100644 --- a/framework/tests/h1h2checker/Constructors.java +++ b/framework/tests/h1h2checker/Constructors.java @@ -1,67 +1,67 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Constructors { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S2 @H2S2 Constructors() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S2 @H2S2 Constructors() {} - void test1() { - // All quals from constructor - @H1S2 @H2S2 Constructors c1 = new Constructors(); - // Can still specify my own - @H1S2 @H2Top Constructors c2 = new @H1S2 @H2Top Constructors(); - // Can only specify some of the qualifiers, rest comes - // from constructor - @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(); + void test1() { + // All quals from constructor + @H1S2 @H2S2 Constructors c1 = new Constructors(); + // Can still specify my own + @H1S2 @H2Top Constructors c2 = new @H1S2 @H2Top Constructors(); + // Can only specify some of the qualifiers, rest comes + // from constructor + @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e1 = new Constructors(); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e2 = new @H1S2 Constructors(); - } + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e1 = new Constructors(); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e2 = new @H1S2 Constructors(); + } - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S2 @H2Poly Constructors(@H1S1 @H2Poly int i) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S2 @H2Poly Constructors(@H1S1 @H2Poly int i) {} - void test2(@H1S1 @H2S2 int p) { - @H1S2 @H2S2 Constructors c1 = new Constructors(p); - @H1S2 @H2S2 Constructors c2 = new @H1S2 @H2S2 Constructors(p); - @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(p); + void test2(@H1S1 @H2S2 int p) { + @H1S2 @H2S2 Constructors c1 = new Constructors(p); + @H1S2 @H2S2 Constructors c2 = new @H1S2 @H2S2 Constructors(p); + @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e1 = new Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); - } + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e1 = new Constructors(p); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); + } - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s) {} - void test3(@H1S1 @H2S2 String p) { - @H1S1 @H2S2 Constructors c1 = new Constructors(p); - @H1S1 @H2S2 Constructors c2 = new @H1S1 @H2S2 Constructors(p); - @H1S1 @H2S2 Constructors c3 = new @H1S1 Constructors(p); + void test3(@H1S1 @H2S2 String p) { + @H1S1 @H2S2 Constructors c1 = new Constructors(p); + @H1S1 @H2S2 Constructors c2 = new @H1S1 @H2S2 Constructors(p); + @H1S1 @H2S2 Constructors c3 = new @H1S1 Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e1 = new Constructors(p); - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e1 = new Constructors(p); + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); - // :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S2 Constructors e4 = new @H1S2 @H2S2 Constructors(p); - // :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S2 Constructors e5 = new @H1S2 Constructors(p); - } + // :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S2 Constructors e4 = new @H1S2 @H2S2 Constructors(p); + // :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S2 Constructors e5 = new @H1S2 Constructors(p); + } - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @org.checkerframework.framework.testchecker.util.Encrypted @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s, int i) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @org.checkerframework.framework.testchecker.util.Encrypted @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s, int i) {} - void test4(@H1S1 @H2S2 String p) { - @H1S1 @H2S2 Constructors c1 = new Constructors(p, 4); - @H1S1 @H2S2 Constructors c2 = - new @org.checkerframework.framework.testchecker.util.Encrypted Constructors(p); - } + void test4(@H1S1 @H2S2 String p) { + @H1S1 @H2S2 Constructors c1 = new Constructors(p, 4); + @H1S1 @H2S2 Constructors c2 = + new @org.checkerframework.framework.testchecker.util.Encrypted Constructors(p); + } } diff --git a/framework/tests/h1h2checker/Defaulting.java b/framework/tests/h1h2checker/Defaulting.java index 5c811144882..653788ad3be 100644 --- a/framework/tests/h1h2checker/Defaulting.java +++ b/framework/tests/h1h2checker/Defaulting.java @@ -7,161 +7,161 @@ // are separately annotated. public class Defaulting { - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - class TestLocal { - void m(@H1S1 Object p1, @H1S2 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + class TestLocal { + void m(@H1S1 Object p1, @H1S2 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } + } + + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.UPPER_BOUND}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) + // Type of x is <@H1S2 X extends @H1S1 Object>, these annotations are siblings + // and should not be in the same bound + // :: warning: (inconsistent.constructor.type) :: error: (bound.type.incompatible) :: error: + // (super.invocation.invalid) + class TestUpperBound { + void m(X p) { + @H1S1 Object l1 = p; + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = p; + Object l3 = p; + } + } + + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.PARAMETER}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) + // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) + class TestParameter { + void m(Object p) { + @H1S1 Object l1 = p; + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = p; + Object l3 = p; } - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.UPPER_BOUND}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) - // Type of x is <@H1S2 X extends @H1S1 Object>, these annotations are siblings - // and should not be in the same bound - // :: warning: (inconsistent.constructor.type) :: error: (bound.type.incompatible) :: error: - // (super.invocation.invalid) - class TestUpperBound { - void m(X p) { - @H1S1 Object l1 = p; - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = p; - Object l3 = p; - } + void call() { + // :: warning: (cast.unsafe.constructor.invocation) + m(new @H1S1 Object()); + // :: error: (argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + m(new @H1S2 Object()); + // :: error: (argument.type.incompatible) + m(new Object()); } + } + + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.PARAMETER}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) + class TestConstructorParameter { - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.PARAMETER}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) - class TestParameter { - void m(Object p) { - @H1S1 Object l1 = p; - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = p; - Object l3 = p; - } - - void call() { - // :: warning: (cast.unsafe.constructor.invocation) - m(new @H1S1 Object()); - // :: error: (argument.type.incompatible) :: warning: - // (cast.unsafe.constructor.invocation) - m(new @H1S2 Object()); - // :: error: (argument.type.incompatible) - m(new Object()); - } + TestConstructorParameter(Object p) { + @H1S1 Object l1 = p; + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = p; + Object l3 = p; } - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.PARAMETER}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) - class TestConstructorParameter { - - // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) - TestConstructorParameter(Object p) { - @H1S1 Object l1 = p; - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = p; - Object l3 = p; - } - - void call() { - // :: warning: (cast.unsafe.constructor.invocation) - new TestConstructorParameter(new @H1S1 Object()); - // :: error: (argument.type.incompatible) :: warning: - // (cast.unsafe.constructor.invocation) - new TestConstructorParameter(new @H1S2 Object()); - // :: error: (argument.type.incompatible) - new TestConstructorParameter(new Object()); - } + void call() { + // :: warning: (cast.unsafe.constructor.invocation) + new TestConstructorParameter(new @H1S1 Object()); + // :: error: (argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + new TestConstructorParameter(new @H1S2 Object()); + // :: error: (argument.type.incompatible) + new TestConstructorParameter(new Object()); } - - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.RETURN}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) - // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) - class TestReturns { - Object res() { - // :: warning: (cast.unsafe.constructor.invocation) - return new @H1S1 Object(); - } - - void m() { - @H1S1 Object l1 = res(); - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = res(); - Object l3 = res(); - } - - Object res2() { - // :: error: (return.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - return new @H1S2 Object(); - } - - Object res3() { - // :: error: (return.type.incompatible) - return new Object(); - } + } + + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.RETURN}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) + // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) + class TestReturns { + Object res() { + // :: warning: (cast.unsafe.constructor.invocation) + return new @H1S1 Object(); } - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.RECEIVER}) - public class ReceiverDefaulting { - public ReceiverDefaulting() {} + void m() { + @H1S1 Object l1 = res(); + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = res(); + Object l3 = res(); + } - public void m() {} + Object res2() { + // :: error: (return.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + return new @H1S2 Object(); } - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - class TestReceiver { - - void call() { - // :: warning: (cast.unsafe.constructor.invocation) - @H1S1 ReceiverDefaulting r2 = new @H1S1 ReceiverDefaulting(); - // :: warning: (cast.unsafe.constructor.invocation) - @H1S2 ReceiverDefaulting r3 = new @H1S2 ReceiverDefaulting(); - ReceiverDefaulting r = new ReceiverDefaulting(); - - r2.m(); - // :: error: (method.invocation.invalid) - r3.m(); - // :: error: (method.invocation.invalid) - r.m(); - } + Object res3() { + // :: error: (return.type.incompatible) + return new Object(); + } + } + + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.RECEIVER}) + public class ReceiverDefaulting { + public ReceiverDefaulting() {} + + public void m() {} + } + + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + class TestReceiver { + + void call() { + // :: warning: (cast.unsafe.constructor.invocation) + @H1S1 ReceiverDefaulting r2 = new @H1S1 ReceiverDefaulting(); + // :: warning: (cast.unsafe.constructor.invocation) + @H1S2 ReceiverDefaulting r3 = new @H1S2 ReceiverDefaulting(); + ReceiverDefaulting r = new ReceiverDefaulting(); + + r2.m(); + // :: error: (method.invocation.invalid) + r3.m(); + // :: error: (method.invocation.invalid) + r.m(); } + } } diff --git a/framework/tests/h1h2checker/EnforceTargetLocation.java b/framework/tests/h1h2checker/EnforceTargetLocation.java index cfcf38117ea..66db250652a 100644 --- a/framework/tests/h1h2checker/EnforceTargetLocation.java +++ b/framework/tests/h1h2checker/EnforceTargetLocation.java @@ -1,30 +1,29 @@ -import org.checkerframework.framework.testchecker.h1h2checker.quals.*; - import java.util.List; +import org.checkerframework.framework.testchecker.h1h2checker.quals.*; // :: error: (type.invalid.annotations.on.location) public class EnforceTargetLocation { - @H2S1 Object right; + @H2S1 Object right; - // :: error: (type.invalid.annotations.on.location) - @H2OnlyOnLB Object wrong; + // :: error: (type.invalid.annotations.on.location) + @H2OnlyOnLB Object wrong; - @H2S1 Object correctUse(@H2S1 Object p1) { - // :: warning: (cast.unsafe.constructor.invocation) - @H2S1 Object o = new @H2S1 Object(); - List l; - return o; - } + @H2S1 Object correctUse(@H2S1 Object p1) { + // :: warning: (cast.unsafe.constructor.invocation) + @H2S1 Object o = new @H2S1 Object(); + List l; + return o; + } - @H2OnlyOnLB + @H2OnlyOnLB + // :: error: (type.invalid.annotations.on.location) + Object incorrect() { + // :: warning: (cast.unsafe.constructor.invocation) // :: error: (type.invalid.annotations.on.location) - Object incorrect() { - // :: warning: (cast.unsafe.constructor.invocation) - // :: error: (type.invalid.annotations.on.location) - @H2OnlyOnLB Object o = new @H2OnlyOnLB Object(); - return o; - } + @H2OnlyOnLB Object o = new @H2OnlyOnLB Object(); + return o; + } - // :: error: (type.invalid.annotations.on.location) - void incorrectUse2(@H2OnlyOnLB Object p1) {} + // :: error: (type.invalid.annotations.on.location) + void incorrectUse2(@H2OnlyOnLB Object p1) {} } diff --git a/framework/tests/h1h2checker/ForEach.java b/framework/tests/h1h2checker/ForEach.java index 9de90c1b6e4..6a04fbce39a 100644 --- a/framework/tests/h1h2checker/ForEach.java +++ b/framework/tests/h1h2checker/ForEach.java @@ -2,91 +2,91 @@ public class ForEach { - Object arrayAccess1(Object[] constants) { - Object constant = constants[0]; - return constant; - } + Object arrayAccess1(Object[] constants) { + Object constant = constants[0]; + return constant; + } - @H1S2 Object arrayAccessBad1(@H1S1 Object[] constants) { - Object constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + @H1S2 Object arrayAccessBad1(@H1S1 Object[] constants) { + Object constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - // Return type defaults to H1Top - @H2S1 Object arrayAccessBad2(@H1S1 @H2S2 Object[] constants) { - Object constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + // Return type defaults to H1Top + @H2S1 Object arrayAccessBad2(@H1S1 @H2S2 Object[] constants) { + Object constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - Object iterateFor(Object[] constants) { - for (int i = 0; i < constants.length; ++i) { - Object constant = constants[i]; - return constant; - } - return null; + Object iterateFor(Object[] constants) { + for (int i = 0; i < constants.length; ++i) { + Object constant = constants[i]; + return constant; } + return null; + } - Object iterateForEach(Object[] constants) { - for (Object constant : constants) { - return constant; - } - return null; + Object iterateForEach(Object[] constants) { + for (Object constant : constants) { + return constant; } + return null; + } - @H2S2 Object iterateForEachBad(@H2S1 Object[] constants) { - for (Object constant : constants) { - // :: error: (return.type.incompatible) - return constant; - } - return null; + @H2S2 Object iterateForEachBad(@H2S1 Object[] constants) { + for (Object constant : constants) { + // :: error: (return.type.incompatible) + return constant; } + return null; + } - // Now with a method type variable + // Now with a method type variable - T garrayAccess1(T[] constants) { - return constants[0]; - } + T garrayAccess1(T[] constants) { + return constants[0]; + } - T garrayAccess1(T p) { - T constant = p; - return constant; - } + T garrayAccess1(T p) { + T constant = p; + return constant; + } - @H1S2 T garrayAccessBad1(@H1S1 T[] constants) { - T constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + @H1S2 T garrayAccessBad1(@H1S1 T[] constants) { + T constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - // Return type defaults to H1Top - @H2S1 T garrayAccessBad2(@H1S1 @H2S2 T[] constants) { - T constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + // Return type defaults to H1Top + @H2S1 T garrayAccessBad2(@H1S1 @H2S2 T[] constants) { + T constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - T giterateFor(T[] constants) { - for (int i = 0; i < constants.length; ++i) { - T constant = constants[i]; - return constant; - } - return null; + T giterateFor(T[] constants) { + for (int i = 0; i < constants.length; ++i) { + T constant = constants[i]; + return constant; } + return null; + } - T giterateForEach(T[] constants) { - for (T constant : constants) { - return constant; - } - return null; + T giterateForEach(T[] constants) { + for (T constant : constants) { + return constant; } + return null; + } - @H2S2 T giterateForEachBad(@H2S1 T[] constants) { - for (T constant : constants) { - // :: error: (return.type.incompatible) - return constant; - } - return null; + @H2S2 T giterateForEachBad(@H2S1 T[] constants) { + for (T constant : constants) { + // :: error: (return.type.incompatible) + return constant; } + return null; + } } diff --git a/framework/tests/h1h2checker/Generics.java b/framework/tests/h1h2checker/Generics.java index 44312a52f1f..b825978aaf6 100644 --- a/framework/tests/h1h2checker/Generics.java +++ b/framework/tests/h1h2checker/Generics.java @@ -2,38 +2,38 @@ public class Generics { - class Generics1 { + class Generics1 { - T m(@H1S2 @H2S2 T p) { - T l = p; - // :: error: (return.type.incompatible) - return l; - } + T m(@H1S2 @H2S2 T p) { + T l = p; + // :: error: (return.type.incompatible) + return l; + } - void unsound(Generics1<@H1S1 @H2S1 Object> p, @H1S2 @H2S2 Object p2) { - @H1S1 @H2S1 Object o = p.m(p2); - } + void unsound(Generics1<@H1S1 @H2S1 Object> p, @H1S2 @H2S2 Object p2) { + @H1S1 @H2S1 Object o = p.m(p2); } + } - class Generics2 { + class Generics2 { - T m(@H1S2 T p) { - T l = p; - // :: error: (return.type.incompatible) - return l; - } + T m(@H1S2 T p) { + T l = p; + // :: error: (return.type.incompatible) + return l; + } - void unsound(Generics2<@H1S1 Object> p, @H1S2 Object p2) { - @H1S1 Object o = p.m(p2); - } + void unsound(Generics2<@H1S1 Object> p, @H1S2 Object p2) { + @H1S1 Object o = p.m(p2); } + } - class Generics3 { + class Generics3 { - // See comments in BaseTypeVisitor about type variable checks. - // The currently desired behavior is that the annotation on the - // type variable overrides the bound. - // TODO?:: error: (type.invalid) - void m(@H1S2 T p) {} - } + // See comments in BaseTypeVisitor about type variable checks. + // The currently desired behavior is that the annotation on the + // type variable overrides the bound. + // TODO?:: error: (type.invalid) + void m(@H1S2 T p) {} + } } diff --git a/framework/tests/h1h2checker/GetClassStubTest.java b/framework/tests/h1h2checker/GetClassStubTest.java index 51568253f23..749631c8dfc 100644 --- a/framework/tests/h1h2checker/GetClassStubTest.java +++ b/framework/tests/h1h2checker/GetClassStubTest.java @@ -4,18 +4,18 @@ public class GetClassStubTest { - // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver - void context() { - Integer i = 4; - Class a = i.getClass(); + // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver + void context() { + Integer i = 4; + Class a = i.getClass(); - Class<@H1Bot ? extends @H1S1 Object> succeed1 = i.getClass(); - Class<@H1Bot ? extends @H1S1 Integer> succeed2 = i.getClass(); + Class<@H1Bot ? extends @H1S1 Object> succeed1 = i.getClass(); + Class<@H1Bot ? extends @H1S1 Integer> succeed2 = i.getClass(); - // :: error: (assignment.type.incompatible) - Class<@H1Bot ? extends @H1Bot Object> fail1 = i.getClass(); + // :: error: (assignment.type.incompatible) + Class<@H1Bot ? extends @H1Bot Object> fail1 = i.getClass(); - // :: error: (assignment.type.incompatible) - Class<@H1Bot ?> fail2 = i.getClass(); - } + // :: error: (assignment.type.incompatible) + Class<@H1Bot ?> fail2 = i.getClass(); + } } diff --git a/framework/tests/h1h2checker/IncompatibleBounds.java b/framework/tests/h1h2checker/IncompatibleBounds.java index ec086642f5c..8f3b2528c28 100644 --- a/framework/tests/h1h2checker/IncompatibleBounds.java +++ b/framework/tests/h1h2checker/IncompatibleBounds.java @@ -13,50 +13,50 @@ // set the defaults in the H2 hierarchy such that do not report errors in this test @DefaultQualifier( - value = H2Top.class, - locations = {TypeUseLocation.UPPER_BOUND}) + value = H2Top.class, + locations = {TypeUseLocation.UPPER_BOUND}) @DefaultQualifier( - value = H2Bot.class, - locations = {TypeUseLocation.LOWER_BOUND}) + value = H2Bot.class, + locations = {TypeUseLocation.LOWER_BOUND}) public class IncompatibleBounds { - // The bounds below are valid - class TopToBottom<@H1Bot T extends @H1Top Object> {} + // The bounds below are valid + class TopToBottom<@H1Bot T extends @H1Top Object> {} - class TopToH1S1<@H1S1 TT extends @H1Top Object> {} + class TopToH1S1<@H1S1 TT extends @H1Top Object> {} - class H1S1ToBot<@H1Bot TTT extends @H1S1 Object> {} + class H1S1ToBot<@H1Bot TTT extends @H1S1 Object> {} - class H1S1ToH1S1<@H1S1 TTTT extends @H1S1 Object> {} + class H1S1ToH1S1<@H1S1 TTTT extends @H1S1 Object> {} - class ValidContext { - TopToBottom<@H1Bot ? extends @H1Top Object> topToBot; - TopToH1S1<@H1S1 ? extends @H1Top Object> topToH1S1; - H1S1ToBot<@H1Bot ? extends @H1S1 Object> h1S1ToBot; - H1S1ToH1S1<@H1S1 ? extends @H1S1 Object> h1S1ToH1S1; - } + class ValidContext { + TopToBottom<@H1Bot ? extends @H1Top Object> topToBot; + TopToH1S1<@H1S1 ? extends @H1Top Object> topToH1S1; + H1S1ToBot<@H1Bot ? extends @H1S1 Object> h1S1ToBot; + H1S1ToH1S1<@H1S1 ? extends @H1S1 Object> h1S1ToH1S1; + } - // invalid combinations - // :: error: (bound.type.incompatible) - class BottomToTop<@H1Top U extends @H1Bot Object> {} + // invalid combinations + // :: error: (bound.type.incompatible) + class BottomToTop<@H1Top U extends @H1Bot Object> {} - // :: error: (bound.type.incompatible) - class H1S1ToTop<@H1Top UU extends @H1S1 Object> {} + // :: error: (bound.type.incompatible) + class H1S1ToTop<@H1Top UU extends @H1S1 Object> {} - // :: error: (bound.type.incompatible) - class BottomToH1S1<@H1S1 UUU extends @H1Bot Object> {} + // :: error: (bound.type.incompatible) + class BottomToH1S1<@H1S1 UUU extends @H1Bot Object> {} + + // :: error: (bound.type.incompatible) + class H1S2ToH1S1<@H1S1 UUUU extends @H1S2 Object> {} + class InvalidContext { + // :: error: (bound.type.incompatible) + BottomToTop<@H1Top ? extends @H1Bot Object> bottomToTop; + // :: error: (bound.type.incompatible) + H1S1ToTop<@H1Top ? extends @H1S1 Object> h1S1ToTop; + // :: error: (bound.type.incompatible) + BottomToH1S1<@H1S1 ? extends @H1Bot Object> bottomToH1S1; // :: error: (bound.type.incompatible) - class H1S2ToH1S1<@H1S1 UUUU extends @H1S2 Object> {} - - class InvalidContext { - // :: error: (bound.type.incompatible) - BottomToTop<@H1Top ? extends @H1Bot Object> bottomToTop; - // :: error: (bound.type.incompatible) - H1S1ToTop<@H1Top ? extends @H1S1 Object> h1S1ToTop; - // :: error: (bound.type.incompatible) - BottomToH1S1<@H1S1 ? extends @H1Bot Object> bottomToH1S1; - // :: error: (bound.type.incompatible) - H1S2ToH1S1<@H1S1 ? extends @H1S2 Object> h1S2ToH1S1; - } + H1S2ToH1S1<@H1S1 ? extends @H1S2 Object> h1S2ToH1S1; + } } diff --git a/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java index d006bab8a43..e5541fa3494 100644 --- a/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java +++ b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java @@ -1,175 +1,174 @@ -import org.checkerframework.framework.testchecker.h1h2checker.quals.*; - import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class InferTypeArgsPolyChecker { - // ---------------------------------------------------------- - // Test Case - A - A methodA(@H2Top A a1, @H2Top A a2) { - return null; - } - - void contextA(@H1S1 @H2Bot String str, @H1Bot @H2Bot List<@H1S2 String> s) { - @H2Bot Object a = methodA(str, s); - @H1Top @H2Bot Object aTester = a; - } - - // ---------------------------------------------------------- - // Test Case - B - B methodB(List<@H2S2 B> b1, List<@H1S2 B> b2) { - return null; - } - - void contextB(List<@H1S1 @H2S2 String> l1, List<@H1S2 @H2S1 String> l2) { - @H1S1 @H2S1 String str = methodB(l1, l2); - } - - // ---------------------------------------------------------- - // Test Case - C - > C methodC(C c1, C c2) { - return null; - } - - void contextC(List<@H1S1 @H2S2 ? extends @H1S1 @H2S2 String> l1, List<@H1S1 @H2S2 String> l2) { - List<@H1S1 @H2S2 ?> str = methodC(l1, l2); - } - - // ---------------------------------------------------------- - // Test Case - D - - D methodD(D d1, D d2, DD dd) { - return null; - } - - DD methodD2(D d1, D d2, DD dd) { - return null; - } - - void contextD(OUTER_SCOPE_TV os1, @H1S1 @H2S1 OUTER_SCOPE_TV os2) { - OUTER_SCOPE_TV osNaked1 = methodD(os1, os1, os2); - - // So for the next failure we correctly infer that for methodD to take both os1 and os2 as - // arguments D must be @H1Top @H2Top OUTER_SCOPE_TV. However, the UPPER_BOUND of D is - // <@H1Bottom @H2Bottom OUTER_SCOPE_TV extends @H1Top @H2Top Object> notice that our - // inferred type for D is above this bound. - // - // A similar, more useful example in the Nullness type system would be: - /* - class Gen { - public List listo; - - void addToListo(T t1, T T2) { - listo.add(t1); - listo.add(t2); - } - - void launder(@NonNull OUTER arg1, @Nullable OUTER arg2) { - addToListo(arg1, arg2); // T is inferred to be <@Nullable OUTER> - // if we did not mark this as type.argument.type.incompatible - // then we would have no idea that in the last - // line of this example we are putting a null value into - // a List of @NonNull Strings - } - - } - - Gen<@NonNull String> g = ...; - g.listo = new ArrayList<@NonNull String>(); - g.launder("", null); // during this method call null would be added to g.listo - */ - // :: error: (type.argument.type.incompatible) - OUTER_SCOPE_TV osNaked2 = methodD(os1, os2, ""); - - // :: error: (type.argument.type.incompatible) - OUTER_SCOPE_TV osAnnoed = methodD(os2, os2, ""); - - // :: error: (type.argument.type.incompatible) - String str = methodD2(os2, os1, ""); - OUTER_SCOPE_TV osNaked3 = methodD2(os1, os1, os2); - } - - // ---------------------------------------------------------- - // Test Case - E - E methodE(E e1, E[] aos2) { - return null; - } // pass an array to one of these to cover A2FReducer.visitArray_Typevar - - void contextE(String[] strArr, String[][] strArrArr, OUTER_SCOPE_TV os, OUTER_SCOPE_TV[] aos) { - String[] strArrLocal = methodE(strArr, strArrArr); - OUTER_SCOPE_TV osLocal = methodE(os, aos); - } - - // ---------------------------------------------------------- - // Test Case - C - List methodF(List lExtF, List lSupF) { - return null; - } - - void contextF( - // :: error: (type.invalid.annotations.on.location) - List<@H1Bot @H2Bot ? extends @H1Top @H2S1 String> l1, - List l2, - List<@H1S1 @H2S2 ? extends @H1Top @H2Top String> l3) { - - List lstr1 = methodF(l1, l2); - - List lstr2 = methodF(l3, l2); - } - - , H extends List> List methodG(G g1, G G2) { - return null; - } - - class NodeList extends ArrayList<@H1Top @H2Top NodeList> {} - - void contextG(NodeList l1, NodeList l2) { - List<@H1Top @H2Top NodeList> a = methodG(l1, l2); - } - - // add test case for Array vs. String[] - // add test case of E extends F, F extends G, H extends List and other craziness - - Map method() { - return null; - } - - void contextMN() { - - // so I am not exactly sure how to create a meaningful test for this case - // what occurs is the SubtypeSolver.propagateGlbs forces N to be a subtype of M - // and it works (at least while I was debugging) but I don't know how to create - // a check that fails or succeeds because of this - Map mnl = method(); - } - - // class Pair { - // PONE _1; - // PTWO _2; - // } - - @H1Top @H2Top P methodOP(@H1Top @H2Top P p, O o) { - return null; - } - - void contextOP(@H1S1 @H2S1 String s1, @H1Bot @H2Bot String s2) { - // This test is actually here to test that the constraint P :> O is implied on p - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String loc = methodOP(s1, s2); - } - - // class Triplet { - // L l; - // M m; - // N n; - // } - // - // Triplet methodXYZ() { - // return null; - // } + // ---------------------------------------------------------- + // Test Case - A + A methodA(@H2Top A a1, @H2Top A a2) { + return null; + } + + void contextA(@H1S1 @H2Bot String str, @H1Bot @H2Bot List<@H1S2 String> s) { + @H2Bot Object a = methodA(str, s); + @H1Top @H2Bot Object aTester = a; + } + + // ---------------------------------------------------------- + // Test Case - B + B methodB(List<@H2S2 B> b1, List<@H1S2 B> b2) { + return null; + } + + void contextB(List<@H1S1 @H2S2 String> l1, List<@H1S2 @H2S1 String> l2) { + @H1S1 @H2S1 String str = methodB(l1, l2); + } + + // ---------------------------------------------------------- + // Test Case - C + > C methodC(C c1, C c2) { + return null; + } + + void contextC(List<@H1S1 @H2S2 ? extends @H1S1 @H2S2 String> l1, List<@H1S1 @H2S2 String> l2) { + List<@H1S1 @H2S2 ?> str = methodC(l1, l2); + } + + // ---------------------------------------------------------- + // Test Case - D + + D methodD(D d1, D d2, DD dd) { + return null; + } + + DD methodD2(D d1, D d2, DD dd) { + return null; + } + + void contextD(OUTER_SCOPE_TV os1, @H1S1 @H2S1 OUTER_SCOPE_TV os2) { + OUTER_SCOPE_TV osNaked1 = methodD(os1, os1, os2); + + // So for the next failure we correctly infer that for methodD to take both os1 and os2 as + // arguments D must be @H1Top @H2Top OUTER_SCOPE_TV. However, the UPPER_BOUND of D is + // <@H1Bottom @H2Bottom OUTER_SCOPE_TV extends @H1Top @H2Top Object> notice that our + // inferred type for D is above this bound. // - // void contextXYZ() { - // Triplet<@H1S> - // } + // A similar, more useful example in the Nullness type system would be: + /* + class Gen { + public List listo; + + void addToListo(T t1, T T2) { + listo.add(t1); + listo.add(t2); + } + + void launder(@NonNull OUTER arg1, @Nullable OUTER arg2) { + addToListo(arg1, arg2); // T is inferred to be <@Nullable OUTER> + // if we did not mark this as type.argument.type.incompatible + // then we would have no idea that in the last + // line of this example we are putting a null value into + // a List of @NonNull Strings + } + + } + + Gen<@NonNull String> g = ...; + g.listo = new ArrayList<@NonNull String>(); + g.launder("", null); // during this method call null would be added to g.listo + */ + // :: error: (type.argument.type.incompatible) + OUTER_SCOPE_TV osNaked2 = methodD(os1, os2, ""); + + // :: error: (type.argument.type.incompatible) + OUTER_SCOPE_TV osAnnoed = methodD(os2, os2, ""); + + // :: error: (type.argument.type.incompatible) + String str = methodD2(os2, os1, ""); + OUTER_SCOPE_TV osNaked3 = methodD2(os1, os1, os2); + } + + // ---------------------------------------------------------- + // Test Case - E + E methodE(E e1, E[] aos2) { + return null; + } // pass an array to one of these to cover A2FReducer.visitArray_Typevar + + void contextE(String[] strArr, String[][] strArrArr, OUTER_SCOPE_TV os, OUTER_SCOPE_TV[] aos) { + String[] strArrLocal = methodE(strArr, strArrArr); + OUTER_SCOPE_TV osLocal = methodE(os, aos); + } + + // ---------------------------------------------------------- + // Test Case - C + List methodF(List lExtF, List lSupF) { + return null; + } + + void contextF( + // :: error: (type.invalid.annotations.on.location) + List<@H1Bot @H2Bot ? extends @H1Top @H2S1 String> l1, + List l2, + List<@H1S1 @H2S2 ? extends @H1Top @H2Top String> l3) { + + List lstr1 = methodF(l1, l2); + + List lstr2 = methodF(l3, l2); + } + + , H extends List> List methodG(G g1, G G2) { + return null; + } + + class NodeList extends ArrayList<@H1Top @H2Top NodeList> {} + + void contextG(NodeList l1, NodeList l2) { + List<@H1Top @H2Top NodeList> a = methodG(l1, l2); + } + + // add test case for Array vs. String[] + // add test case of E extends F, F extends G, H extends List and other craziness + + Map method() { + return null; + } + + void contextMN() { + + // so I am not exactly sure how to create a meaningful test for this case + // what occurs is the SubtypeSolver.propagateGlbs forces N to be a subtype of M + // and it works (at least while I was debugging) but I don't know how to create + // a check that fails or succeeds because of this + Map mnl = method(); + } + + // class Pair { + // PONE _1; + // PTWO _2; + // } + + @H1Top @H2Top P methodOP(@H1Top @H2Top P p, O o) { + return null; + } + + void contextOP(@H1S1 @H2S1 String s1, @H1Bot @H2Bot String s2) { + // This test is actually here to test that the constraint P :> O is implied on p + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String loc = methodOP(s1, s2); + } + + // class Triplet { + // L l; + // M m; + // N n; + // } + // + // Triplet methodXYZ() { + // return null; + // } + // + // void contextXYZ() { + // Triplet<@H1S> + // } } diff --git a/framework/tests/h1h2checker/Inheritance.java b/framework/tests/h1h2checker/Inheritance.java index 26ff10171df..8c61a50f23d 100644 --- a/framework/tests/h1h2checker/Inheritance.java +++ b/framework/tests/h1h2checker/Inheritance.java @@ -2,17 +2,17 @@ // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) @H1S1 class Inheritance { - void bar1(@H1Bot Inheritance param) {} + void bar1(@H1Bot Inheritance param) {} - void bar2(@H1S1 Inheritance param) {} + void bar2(@H1S1 Inheritance param) {} - // :: error: (type.invalid.annotations.on.use) - void bar3(@H1Top Inheritance param) {} + // :: error: (type.invalid.annotations.on.use) + void bar3(@H1Top Inheritance param) {} - void foo1(@H1Bot Inheritance[] param) {} + void foo1(@H1Bot Inheritance[] param) {} - void foo2(@H1S1 Inheritance[] param) {} + void foo2(@H1S1 Inheritance[] param) {} - // :: error: (type.invalid.annotations.on.use) - void foo3(@H1Top Inheritance[] param) {} + // :: error: (type.invalid.annotations.on.use) + void foo3(@H1Top Inheritance[] param) {} } diff --git a/framework/tests/h1h2checker/Issue2163Final.java b/framework/tests/h1h2checker/Issue2163Final.java index 17d5c6687da..4bbf468e32f 100644 --- a/framework/tests/h1h2checker/Issue2163Final.java +++ b/framework/tests/h1h2checker/Issue2163Final.java @@ -6,64 +6,64 @@ // Testing Rule 1 (constructor declaration type <: class type) @H1Top class Issue2163FinalAA { - @H1Top Issue2163FinalAA() {} + @H1Top Issue2163FinalAA() {} } @H1Top class Issue2163FinalAB { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S1 Issue2163FinalAB() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S1 Issue2163FinalAB() {} } @H1Top class Issue2163FinalAC { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2163FinalAC() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2163FinalAC() {} } @H1S1 class Issue2163FinalBA { - // :: error: (type.invalid.annotations.on.use) - @H1Top Issue2163FinalBA() {} + // :: error: (type.invalid.annotations.on.use) + @H1Top Issue2163FinalBA() {} } @H1S1 class Issue2163FinalBB { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S1 Issue2163FinalBB() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S1 Issue2163FinalBB() {} } @H1S1 class Issue2163FinalBC { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2163FinalBC() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2163FinalBC() {} } @H1Bot class Issue2163FinalCA { - // :: error: (type.invalid.annotations.on.use) - @H1Top Issue2163FinalCA() {} + // :: error: (type.invalid.annotations.on.use) + @H1Top Issue2163FinalCA() {} } @H1Bot class Issue2163FinalCB { - // :: error: (type.invalid.annotations.on.use) :: warning: (inconsistent.constructor.type) - // :: error: (super.invocation.invalid) - @H1S1 Issue2163FinalCB() {} + // :: error: (type.invalid.annotations.on.use) :: warning: (inconsistent.constructor.type) + // :: error: (super.invocation.invalid) + @H1S1 Issue2163FinalCB() {} } @H1Bot class Issue2163FinalCC { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2163FinalCC() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2163FinalCC() {} } // Testing Rule 2 (Issue type cast warning if constructor declaration type <: invocation type) @SuppressWarnings("anno.on.irrelevant") class Issue2163FinalAAClient { - void test() { - new @H1Top Issue2163FinalAA(); - // :: warning: (cast.unsafe.constructor.invocation) - new @H1S1 Issue2163FinalAA(); - // :: warning: (cast.unsafe.constructor.invocation) - new @H1Bot Issue2163FinalAA(); - } + void test() { + new @H1Top Issue2163FinalAA(); + // :: warning: (cast.unsafe.constructor.invocation) + new @H1S1 Issue2163FinalAA(); + // :: warning: (cast.unsafe.constructor.invocation) + new @H1Bot Issue2163FinalAA(); + } } // Testing Default @SuppressWarnings("anno.on.irrelevant") class Issue2163FinalBCClient { - @H1Bot Issue2163FinalBC obj = new Issue2163FinalBC(); + @H1Bot Issue2163FinalBC obj = new Issue2163FinalBC(); } diff --git a/framework/tests/h1h2checker/Issue2186.java b/framework/tests/h1h2checker/Issue2186.java index cd5af40f8f0..ac6e51922d7 100644 --- a/framework/tests/h1h2checker/Issue2186.java +++ b/framework/tests/h1h2checker/Issue2186.java @@ -1,27 +1,26 @@ // Test case for issue #2186 // https://github.com/typetools/checker-framework/issues/2186 +import java.util.ArrayList; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Bot; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S1; -import java.util.ArrayList; - @SuppressWarnings("anno.on.irrelevant") @H1S1 class Issue2186 { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - Issue2186() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + Issue2186() {} - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2186(int x) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2186(int x) {} - void test() { - @H1S1 Issue2186 obj = new Issue2186(); - @H1Bot Issue2186 obj1 = new Issue2186(9); - } + void test() { + @H1S1 Issue2186 obj = new Issue2186(); + @H1Bot Issue2186 obj1 = new Issue2186(9); + } - void testDiamond() { - @H1Bot ArrayList<@H1Bot String> list = - // :: warning: (cast.unsafe.constructor.invocation) - new @H1Bot ArrayList<@H1Bot String>(); - } + void testDiamond() { + @H1Bot ArrayList<@H1Bot String> list = + // :: warning: (cast.unsafe.constructor.invocation) + new @H1Bot ArrayList<@H1Bot String>(); + } } diff --git a/framework/tests/h1h2checker/Issue2264.java b/framework/tests/h1h2checker/Issue2264.java index 0ef2605d902..81b9bc21a42 100644 --- a/framework/tests/h1h2checker/Issue2264.java +++ b/framework/tests/h1h2checker/Issue2264.java @@ -5,23 +5,23 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Top; public class Issue2264 extends SuperClass { - // :: warning: (inconsistent.constructor.type) - @H1S1 Issue2264() { - // :: error: (super.invocation.invalid) - super(9); - } + // :: warning: (inconsistent.constructor.type) + @H1S1 Issue2264() { + // :: error: (super.invocation.invalid) + super(9); + } } class ImplicitSuperCall { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S1 ImplicitSuperCall() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S1 ImplicitSuperCall() {} } class SuperClass { - @H1Top SuperClass(int x) {} + @H1Top SuperClass(int x) {} } @H1S1 class TestClass { - // :: error: (type.invalid.annotations.on.use) - @H1Top TestClass() {} + // :: error: (type.invalid.annotations.on.use) + @H1Top TestClass() {} } diff --git a/framework/tests/h1h2checker/Issue282.java b/framework/tests/h1h2checker/Issue282.java index c699877351d..bb7c5540874 100644 --- a/framework/tests/h1h2checker/Issue282.java +++ b/framework/tests/h1h2checker/Issue282.java @@ -1,90 +1,90 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Issue282 { - // Declared constructor type is not consistent with default from class. - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @H1S1 Issue282() {} - - public class Inner { - Inner(@H2S2 Issue282 Issue282.this) {} - - // Test anonymous constructor without varargs - Inner(@H2S2 Issue282 Issue282.this, String s) {} - - Inner(@H2S2 Issue282 Issue282.this, int... i) {} - - Inner(@H2S2 Issue282 Issue282.this, Issue282 o) {} - - Inner(@H2S2 Issue282 Issue282.this, Issue282... o) {} - } - - @SuppressWarnings({"cast.unsafe.constructor.invocation"}) - public void test1() { - Inner inner1 = new @H2S2 Issue282().new Inner() {}; - Inner inner2 = new @H2S2 Issue282().new Inner(); - // The enclosing type is @H1S1 @H2Top, while the required type is @H1Top @H2S2 - // :: error: (enclosingexpr.type.incompatible) - Inner inner3 = new Issue282().new Inner() {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner4 = new Issue282().new Inner(); - - // test non-varargs - Inner inner5 = new @H2S2 Issue282().new Inner("s") {}; - Inner inner6 = new @H2S2 Issue282().new Inner("s"); - // :: error: (enclosingexpr.type.incompatible) - Inner inner7 = new Issue282().new Inner("s") {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner8 = new Issue282().new Inner("s"); - - // test varargs - Inner inner9 = new @H2S2 Issue282().new Inner(1, 2, 3) {}; - Inner inner10 = new @H2S2 Issue282().new Inner(1, 2, 3); - // :: error: (enclosingexpr.type.incompatible) - Inner inner11 = new Issue282().new Inner(1, 2, 3) {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner12 = new Issue282().new Inner(1, 2, 3); - - // test varargs with the same type of receiver - Inner inner13 = new @H2S2 Issue282().new Inner(this, this, this) {}; - Inner inner14 = new @H2S2 Issue282().new Inner(this, this, this); - // :: error: (enclosingexpr.type.incompatible) - Inner inner15 = new Issue282().new Inner(this, this, this) {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner16 = new Issue282().new Inner(this, this, this); - } - - class Issue282Sub extends Issue282 {} - - public void test2() { - // found: @H1Top @H2Top Issue282.@H1Top @H2Top Issue282Sub. required: @H1Top @H2S2 Issue282 - // :: error: (enclosingexpr.type.incompatible) - Inner inner = new Issue282Sub().new Inner(); - } - - public static void testStatic() { - // :: error: (enclosingexpr.type.incompatible) - new Issue282().new Inner() {}; - } - - class InnerGeneric { - @SuppressWarnings("unchecked") - InnerGeneric(T... t) {} - } - - public void test3(@H1S1 String a, @H1S1 String b, @H1S2 String c) { - new InnerGeneric<@H1S1 String>(a, b); - // found: @H1S2 @H2Top String. required: @H1S1 @H2Top String - // :: error: (argument.type.incompatible) - new InnerGeneric<@H1S1 String>(a, c); - } + // Declared constructor type is not consistent with default from class. + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @H1S1 Issue282() {} + + public class Inner { + Inner(@H2S2 Issue282 Issue282.this) {} + + // Test anonymous constructor without varargs + Inner(@H2S2 Issue282 Issue282.this, String s) {} + + Inner(@H2S2 Issue282 Issue282.this, int... i) {} + + Inner(@H2S2 Issue282 Issue282.this, Issue282 o) {} + + Inner(@H2S2 Issue282 Issue282.this, Issue282... o) {} + } + + @SuppressWarnings({"cast.unsafe.constructor.invocation"}) + public void test1() { + Inner inner1 = new @H2S2 Issue282().new Inner() {}; + Inner inner2 = new @H2S2 Issue282().new Inner(); + // The enclosing type is @H1S1 @H2Top, while the required type is @H1Top @H2S2 + // :: error: (enclosingexpr.type.incompatible) + Inner inner3 = new Issue282().new Inner() {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner4 = new Issue282().new Inner(); + + // test non-varargs + Inner inner5 = new @H2S2 Issue282().new Inner("s") {}; + Inner inner6 = new @H2S2 Issue282().new Inner("s"); + // :: error: (enclosingexpr.type.incompatible) + Inner inner7 = new Issue282().new Inner("s") {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner8 = new Issue282().new Inner("s"); + + // test varargs + Inner inner9 = new @H2S2 Issue282().new Inner(1, 2, 3) {}; + Inner inner10 = new @H2S2 Issue282().new Inner(1, 2, 3); + // :: error: (enclosingexpr.type.incompatible) + Inner inner11 = new Issue282().new Inner(1, 2, 3) {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner12 = new Issue282().new Inner(1, 2, 3); + + // test varargs with the same type of receiver + Inner inner13 = new @H2S2 Issue282().new Inner(this, this, this) {}; + Inner inner14 = new @H2S2 Issue282().new Inner(this, this, this); + // :: error: (enclosingexpr.type.incompatible) + Inner inner15 = new Issue282().new Inner(this, this, this) {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner16 = new Issue282().new Inner(this, this, this); + } + + class Issue282Sub extends Issue282 {} + + public void test2() { + // found: @H1Top @H2Top Issue282.@H1Top @H2Top Issue282Sub. required: @H1Top @H2S2 Issue282 + // :: error: (enclosingexpr.type.incompatible) + Inner inner = new Issue282Sub().new Inner(); + } + + public static void testStatic() { + // :: error: (enclosingexpr.type.incompatible) + new Issue282().new Inner() {}; + } + + class InnerGeneric { + @SuppressWarnings("unchecked") + InnerGeneric(T... t) {} + } + + public void test3(@H1S1 String a, @H1S1 String b, @H1S2 String c) { + new InnerGeneric<@H1S1 String>(a, b); + // found: @H1S2 @H2Top String. required: @H1S1 @H2Top String + // :: error: (argument.type.incompatible) + new InnerGeneric<@H1S1 String>(a, c); + } } class Top { - void test(@H1Top @H2S2 Issue282 outer) { - outer.new Inner() {}; - outer.new Inner("s") {}; - outer.new Inner(1, 2, 3) {}; - outer.new Inner(outer) {}; - outer.new Inner(outer, outer, outer) {}; - } + void test(@H1Top @H2S2 Issue282 outer) { + outer.new Inner() {}; + outer.new Inner("s") {}; + outer.new Inner(1, 2, 3) {}; + outer.new Inner(outer) {}; + outer.new Inner(outer, outer, outer) {}; + } } diff --git a/framework/tests/h1h2checker/Issue681.java b/framework/tests/h1h2checker/Issue681.java index 7cef99ccd0a..56ce83a938f 100644 --- a/framework/tests/h1h2checker/Issue681.java +++ b/framework/tests/h1h2checker/Issue681.java @@ -38,14 +38,14 @@ * */ public class Issue681 { - class Inner { - @H1S2 Inner explicitH1S2; - Issue681.@H1S2 Inner explicitNestedH1S2; - @H1S2 Issue681.Inner explicitOneOuterH1S2; - Inner addH1S2; + class Inner { + @H1S2 Inner explicitH1S2; + Issue681.@H1S2 Inner explicitNestedH1S2; + @H1S2 Issue681.Inner explicitOneOuterH1S2; + Inner addH1S2; - @H1S2 Inner method(@H1S2 Inner paramExplicit, Inner nonAnno) { - return paramExplicit; - } + @H1S2 Inner method(@H1S2 Inner paramExplicit, Inner nonAnno) { + return paramExplicit; } + } } diff --git a/framework/tests/h1h2checker/Issue798.java b/framework/tests/h1h2checker/Issue798.java index db9a6ce286f..2c7fc54849d 100644 --- a/framework/tests/h1h2checker/Issue798.java +++ b/framework/tests/h1h2checker/Issue798.java @@ -4,17 +4,17 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Issue798 { - void test1(String format, @H1S1 Object @H1S2 ... args) { - String.format(format, args); - } + void test1(String format, @H1S1 Object @H1S2 ... args) { + String.format(format, args); + } - void test2(String format, @H1S1 Object @H1S1 ... args) { - // :: error: (argument.type.incompatible) - String.format(format, args); - } + void test2(String format, @H1S1 Object @H1S1 ... args) { + // :: error: (argument.type.incompatible) + String.format(format, args); + } - void test3(String format, @H1S2 Object @H1S2 ... args) { - // :: error: (argument.type.incompatible) - String.format(format, args); - } + void test3(String format, @H1S2 Object @H1S2 ... args) { + // :: error: (argument.type.incompatible) + String.format(format, args); + } } diff --git a/framework/tests/h1h2checker/Issue849.java b/framework/tests/h1h2checker/Issue849.java index 37707ce1635..6695e72165c 100644 --- a/framework/tests/h1h2checker/Issue849.java +++ b/framework/tests/h1h2checker/Issue849.java @@ -5,10 +5,10 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Top; public class Issue849 { - class Gen {} + class Gen {} - void polyAll(Gen> genGenNonNull) { - // :: error: (assignment.type.incompatible) - Gen<@H1Top ? extends @H1Top Gen<@H1Top Object>> a = genGenNonNull; - } + void polyAll(Gen> genGenNonNull) { + // :: error: (assignment.type.incompatible) + Gen<@H1Top ? extends @H1Top Gen<@H1Top Object>> a = genGenNonNull; + } } diff --git a/framework/tests/h1h2checker/Primitive.java b/framework/tests/h1h2checker/Primitive.java index c002cd43a21..a31f500fc27 100644 --- a/framework/tests/h1h2checker/Primitive.java +++ b/framework/tests/h1h2checker/Primitive.java @@ -1,19 +1,19 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Primitive { - @SuppressWarnings("type.incompatible") - @H1S2 int o = 4; + @SuppressWarnings("type.incompatible") + @H1S2 int o = 4; - @H1S2 @H2Poly int m(@H1S2 @H2Poly int p) { - return p; - } + @H1S2 @H2Poly int m(@H1S2 @H2Poly int p) { + return p; + } - void use1(@H1S2 @H2S1 int p) { - @H1S2 @H2S1 int l = m(p); - } + void use1(@H1S2 @H2S1 int p) { + @H1S2 @H2S1 int l = m(p); + } - void use2(@H1S2 @H2S2 int p) { - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 int l = m(p); - } + void use2(@H1S2 @H2S2 int p) { + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 int l = m(p); + } } diff --git a/framework/tests/h1h2checker/TypeRefinement.java b/framework/tests/h1h2checker/TypeRefinement.java index b821903d1bf..0fabe4ffaff 100644 --- a/framework/tests/h1h2checker/TypeRefinement.java +++ b/framework/tests/h1h2checker/TypeRefinement.java @@ -2,17 +2,17 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Invalid; public class TypeRefinement { - // :: warning: (cast.unsafe.constructor.invocation) - @H1Top Object o = new @H1S1 Object(); - // :: error: (h1h2checker.h1invalid.forbidden) :: warning: (cast.unsafe.constructor.invocation) - @H1Top Object o2 = new @H1Invalid Object(); - // :: error: (h1h2checker.h1invalid.forbidden) - @H1Top Object o3 = getH1Invalid(); + // :: warning: (cast.unsafe.constructor.invocation) + @H1Top Object o = new @H1S1 Object(); + // :: error: (h1h2checker.h1invalid.forbidden) :: warning: (cast.unsafe.constructor.invocation) + @H1Top Object o2 = new @H1Invalid Object(); + // :: error: (h1h2checker.h1invalid.forbidden) + @H1Top Object o3 = getH1Invalid(); - // :: error: (h1h2checker.h1invalid.forbidden) - @H1Invalid Object getH1Invalid() { - // :: error: (h1h2checker.h1invalid.forbidden) :: warning: - // (cast.unsafe.constructor.invocation) - return new @H1Invalid Object(); - } + // :: error: (h1h2checker.h1invalid.forbidden) + @H1Invalid Object getH1Invalid() { + // :: error: (h1h2checker.h1invalid.forbidden) :: warning: + // (cast.unsafe.constructor.invocation) + return new @H1Invalid Object(); + } } diff --git a/framework/tests/h1h2checker/WildcardBounds.java b/framework/tests/h1h2checker/WildcardBounds.java index e2fa5f62bb4..cbc0e62b6f0 100644 --- a/framework/tests/h1h2checker/WildcardBounds.java +++ b/framework/tests/h1h2checker/WildcardBounds.java @@ -3,126 +3,123 @@ class WildcardBounds { - abstract class OuterTop { - abstract T get(); - - abstract class Inner { - abstract U get(); - - abstract class Chain { - abstract W get(); - } - - @H1S1 Object m0(Chain p) { - return p.get(); - } - - @H1S1 Object m1(Chain p) { - return p.get(); - } - - @H1S1 Object m2(Chain p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m3(Chain p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m4(Chain<@H1S1 ?, @H1S1 ?> p) { - return p.get(); - } - - void callsS1( - OuterTop<@H1S1 Object>.Inner<@H1S1 Number> i, - OuterTop<@H1S1 Object>.Inner<@H1S1 Number>.Chain<@H1S1 Integer, @H1S1 Integer> - n) { - i.m0(n); - i.m1(n); - i.m2(n); - i.m3(n); - i.m4(n); - } - - void callsTop( - OuterTop<@H1Top Object>.Inner<@H1Top Number> i, - OuterTop<@H1Top Object>.Inner<@H1Top Number>.Chain< - @H1Top Integer, @H1Top Integer> - n) { - // :: error: (argument.type.incompatible) - i.m0(n); - // :: error: (argument.type.incompatible) - i.m1(n); - // OK - i.m2(n); - // OK - i.m3(n); - // :: error: (argument.type.incompatible) - i.m4(n); - } - } - - @H1S1 Object m0(Inner p) { - return p.get(); - } - - @H1S1 Object m1(Inner p) { - return p.get(); - } - - @H1S1 Object m2(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m3(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m4(Inner<@H1S1 ?> p) { - return p.get(); - } - - // We could add calls for these methods. - } + abstract class OuterTop { + abstract T get(); + + abstract class Inner { + abstract U get(); + + abstract class Chain { + abstract W get(); + } - @H1S1 Object m0(OuterTop p) { + @H1S1 Object m0(Chain p) { return p.get(); - } + } - @H1S1 Object m1(OuterTop p) { - // :: error: (return.type.incompatible) + @H1S1 Object m1(Chain p) { return p.get(); - } + } - @H1S1 Object m2(OuterTop p) { + @H1S1 Object m2(Chain p) { // :: error: (return.type.incompatible) return p.get(); - } + } - @H1S1 Object m3(OuterTop<@H1S1 ?> p) { + @H1S1 Object m3(Chain p) { + // :: error: (return.type.incompatible) return p.get(); - } - - void callsOuter(OuterTop<@H1S1 String> s, OuterTop<@H1Top String> ns) { - m0(s); - m1(s); - m2(s); - m3(s); + } + @H1S1 Object m4(Chain<@H1S1 ?, @H1S1 ?> p) { + return p.get(); + } + + void callsS1( + OuterTop<@H1S1 Object>.Inner<@H1S1 Number> i, + OuterTop<@H1S1 Object>.Inner<@H1S1 Number>.Chain<@H1S1 Integer, @H1S1 Integer> n) { + i.m0(n); + i.m1(n); + i.m2(n); + i.m3(n); + i.m4(n); + } + + void callsTop( + OuterTop<@H1Top Object>.Inner<@H1Top Number> i, + OuterTop<@H1Top Object>.Inner<@H1Top Number>.Chain<@H1Top Integer, @H1Top Integer> n) { // :: error: (argument.type.incompatible) - m0(ns); + i.m0(n); + // :: error: (argument.type.incompatible) + i.m1(n); // OK - m1(ns); + i.m2(n); // OK - m2(ns); + i.m3(n); // :: error: (argument.type.incompatible) - m3(ns); + i.m4(n); + } + } + + @H1S1 Object m0(Inner p) { + return p.get(); + } + + @H1S1 Object m1(Inner p) { + return p.get(); + } + + @H1S1 Object m2(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m3(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m4(Inner<@H1S1 ?> p) { + return p.get(); } - // We could add an OuterS1 to also test with a non-top upper bound. - // But we probably already test that enough. + // We could add calls for these methods. + } + + @H1S1 Object m0(OuterTop p) { + return p.get(); + } + + @H1S1 Object m1(OuterTop p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m2(OuterTop p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m3(OuterTop<@H1S1 ?> p) { + return p.get(); + } + + void callsOuter(OuterTop<@H1S1 String> s, OuterTop<@H1Top String> ns) { + m0(s); + m1(s); + m2(s); + m3(s); + + // :: error: (argument.type.incompatible) + m0(ns); + // OK + m1(ns); + // OK + m2(ns); + // :: error: (argument.type.incompatible) + m3(ns); + } + + // We could add an OuterS1 to also test with a non-top upper bound. + // But we probably already test that enough. } diff --git a/framework/tests/h1h2checker/pkg1/PackageDefaulting.java b/framework/tests/h1h2checker/pkg1/PackageDefaulting.java index 33cf24071a5..81c9bc75d79 100644 --- a/framework/tests/h1h2checker/pkg1/PackageDefaulting.java +++ b/framework/tests/h1h2checker/pkg1/PackageDefaulting.java @@ -7,17 +7,17 @@ /* Defaults from package pkg1 apply within the package. */ public class PackageDefaulting { - // Test H1 hierarchy - void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } + // Test H1 hierarchy + void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } - // Test H2 hierarchy - void m2(@H1S1 @H2S1 Object p1, @H1S1 @H2S2 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } + // Test H2 hierarchy + void m2(@H1S1 @H2S1 Object p1, @H1S1 @H2S2 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } } diff --git a/framework/tests/h1h2checker/pkg1/package-info.java b/framework/tests/h1h2checker/pkg1/package-info.java index cdcca1c228a..616b07aef5f 100644 --- a/framework/tests/h1h2checker/pkg1/package-info.java +++ b/framework/tests/h1h2checker/pkg1/package-info.java @@ -1,11 +1,11 @@ @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}, - applyToSubpackages = false) + value = H1S1.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}, + applyToSubpackages = false) @DefaultQualifier( - value = H2S1.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}, - applyToSubpackages = true) + value = H2S1.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}, + applyToSubpackages = true) package pkg1; import org.checkerframework.framework.qual.DefaultQualifier; diff --git a/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java b/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java index 511f2aa0a70..8f0f50dba07 100644 --- a/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java +++ b/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java @@ -7,16 +7,16 @@ /* Default from package pkg1 for H1 is not applied to subpackages, whereas H2 is applied. */ public class PackageDefaulting { - // Test H1 hierarchy - void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { - Object l1 = p1; - Object l2 = p2; - } + // Test H1 hierarchy + void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { + Object l1 = p1; + Object l2 = p2; + } - // Test H2 hierarchy - void m2(@H2S1 Object p1, @H2S2 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } + // Test H2 hierarchy + void m2(@H2S1 Object p1, @H2S2 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } } diff --git a/framework/tests/initialized-fields-value/ClassInitializer.java b/framework/tests/initialized-fields-value/ClassInitializer.java index 3af64347d7d..7f816813af7 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer.java +++ b/framework/tests/initialized-fields-value/ClassInitializer.java @@ -2,22 +2,22 @@ public class ClassInitializer { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - y = 2; - } + { + y = 2; + } - ClassInitializer() { - x = 1; - } + ClassInitializer() { + x = 1; + } - ClassInitializer(boolean ignore) { - x = 1; - z = 3; - } + ClassInitializer(boolean ignore) { + x = 1; + z = 3; + } } diff --git a/framework/tests/initialized-fields-value/ClassInitializer2.java b/framework/tests/initialized-fields-value/ClassInitializer2.java index ca5530c414e..a25c53506c9 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer2.java +++ b/framework/tests/initialized-fields-value/ClassInitializer2.java @@ -2,19 +2,19 @@ public class ClassInitializer2 { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - x = 1; - } + { + x = 1; + } - { - y = 2; - } + { + y = 2; + } - ClassInitializer2() {} + ClassInitializer2() {} } diff --git a/framework/tests/initialized-fields-value/ClassInitializer2a.java b/framework/tests/initialized-fields-value/ClassInitializer2a.java index 1071109f163..f905f517726 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer2a.java +++ b/framework/tests/initialized-fields-value/ClassInitializer2a.java @@ -2,16 +2,16 @@ public class ClassInitializer2a { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - x = 1; - } + { + x = 1; + } - // :: error: (contracts.postcondition.not.satisfied) - ClassInitializer2a() {} + // :: error: (contracts.postcondition.not.satisfied) + ClassInitializer2a() {} } diff --git a/framework/tests/initialized-fields-value/ClassInitializer3.java b/framework/tests/initialized-fields-value/ClassInitializer3.java index d830451ff40..bf9da2a22b7 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer3.java +++ b/framework/tests/initialized-fields-value/ClassInitializer3.java @@ -2,23 +2,23 @@ public class ClassInitializer3 { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - if (Math.random() < 0) { - x = 1; - } else { - x = 1; - } + { + if (Math.random() < 0) { + x = 1; + } else { + x = 1; } + } - { - y = 2; - } + { + y = 2; + } - ClassInitializer3() {} + ClassInitializer3() {} } diff --git a/framework/tests/initialized-fields-value/DeclaredType.java b/framework/tests/initialized-fields-value/DeclaredType.java index 4a758cc6a78..41bf1a08e46 100644 --- a/framework/tests/initialized-fields-value/DeclaredType.java +++ b/framework/tests/initialized-fields-value/DeclaredType.java @@ -1,7 +1,6 @@ -import org.checkerframework.common.value.qual.IntVal; - import java.util.List; +import org.checkerframework.common.value.qual.IntVal; public class DeclaredType { - List field; + List field; } diff --git a/framework/tests/initialized-fields-value/ManualConstructor.java b/framework/tests/initialized-fields-value/ManualConstructor.java index ee22d1da59f..a041ddb4623 100644 --- a/framework/tests/initialized-fields-value/ManualConstructor.java +++ b/framework/tests/initialized-fields-value/ManualConstructor.java @@ -2,31 +2,31 @@ public class ManualConstructor { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - // :: error: (contracts.postcondition.not.satisfied) - ManualConstructor() { - x = 1; - } + // :: error: (contracts.postcondition.not.satisfied) + ManualConstructor() { + x = 1; + } - // :: error: (contracts.postcondition.not.satisfied) - ManualConstructor(boolean ignore) { - x = 1; - z = 3; - } + // :: error: (contracts.postcondition.not.satisfied) + ManualConstructor(boolean ignore) { + x = 1; + z = 3; + } - ManualConstructor(float ignore) { - x = 1; - y = 2; - } + ManualConstructor(float ignore) { + x = 1; + y = 2; + } - ManualConstructor(double ignore) { - x = 1; - y = 2; - z = 3; - } + ManualConstructor(double ignore) { + x = 1; + y = 2; + z = 3; + } } diff --git a/framework/tests/initialized-fields-value/PrimitiveField.java b/framework/tests/initialized-fields-value/PrimitiveField.java index 7759409a520..d7e46e0f038 100644 --- a/framework/tests/initialized-fields-value/PrimitiveField.java +++ b/framework/tests/initialized-fields-value/PrimitiveField.java @@ -3,68 +3,68 @@ import org.checkerframework.common.value.qual.IntVal; public class PrimitiveField { - boolean b; - byte by; - char c; - int i; - short s; - float f; - double d; - long l; + boolean b; + byte by; + char c; + int i; + short s; + float f; + double d; + long l; - @BoolVal(false) boolean b2; + @BoolVal(false) boolean b2; - @IntVal(0) byte by2; + @IntVal(0) byte by2; - @IntVal(0) char c2; + @IntVal(0) char c2; - @IntVal(0) int i2; + @IntVal(0) int i2; - @IntVal(0) short s2; + @IntVal(0) short s2; - @DoubleVal(0.0) float f2; + @DoubleVal(0.0) float f2; - @DoubleVal(0.0) double d2; + @DoubleVal(0.0) double d2; - @IntVal(0) long l2; + @IntVal(0) long l2; - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk1 { - @BoolVal(true) boolean b2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk1 { + @BoolVal(true) boolean b2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk2 { - @IntVal(1) byte by2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk2 { + @IntVal(1) byte by2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk3 { - @IntVal(1) char c2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk3 { + @IntVal(1) char c2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk4 { - @IntVal(1) int i2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk4 { + @IntVal(1) int i2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk5 { - @IntVal(1) short s2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk5 { + @IntVal(1) short s2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk6 { - @DoubleVal(1.0) float f2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk6 { + @DoubleVal(1.0) float f2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk7 { - @DoubleVal(1.0) double d2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk7 { + @DoubleVal(1.0) double d2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk8 { - @IntVal(2) long l2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk8 { + @IntVal(2) long l2; + } } diff --git a/framework/tests/initialized-fields/ConstructorPostcondition.java b/framework/tests/initialized-fields/ConstructorPostcondition.java index 740addef96b..93d5446078a 100644 --- a/framework/tests/initialized-fields/ConstructorPostcondition.java +++ b/framework/tests/initialized-fields/ConstructorPostcondition.java @@ -2,19 +2,19 @@ public class ConstructorPostcondition { - int x; - int y; - int z; + int x; + int y; + int z; - ConstructorPostcondition() { - x = 1; - y = 2; - z = 3; - } + ConstructorPostcondition() { + x = 1; + y = 2; + z = 3; + } - // :: error: (contracts.postcondition.not.satisfied) - ConstructorPostcondition(int ignore) { - x = 1; - y = 2; - } + // :: error: (contracts.postcondition.not.satisfied) + ConstructorPostcondition(int ignore) { + x = 1; + y = 2; + } } diff --git a/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java b/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java index 5619a96a081..8561f59162c 100644 --- a/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java +++ b/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java @@ -4,76 +4,76 @@ public class EnsuresInitializedFieldsTest { - int x; - int y; - int z; + int x; + int y; + int z; - EnsuresInitializedFieldsTest() { - x = 1; - y = 2; - z = 3; - } + EnsuresInitializedFieldsTest() { + x = 1; + y = 2; + z = 3; + } - @EnsuresInitializedFields( - value = "this", - fields = {"x", "y"}) - // :: error: (contracts.postcondition.not.satisfied) - void setsX() { - x = 1; - } + @EnsuresInitializedFields( + value = "this", + fields = {"x", "y"}) + // :: error: (contracts.postcondition.not.satisfied) + void setsX() { + x = 1; + } - @EnsuresInitializedFields(fields = {"x", "y"}) - // :: error: (contracts.postcondition.not.satisfied) - void setsX2() { - x = 1; - } + @EnsuresInitializedFields(fields = {"x", "y"}) + // :: error: (contracts.postcondition.not.satisfied) + void setsX2() { + x = 1; + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - // :: error: (contracts.postcondition.not.satisfied) - void setsX(EnsuresInitializedFieldsTest eift) { - eift.x = 1; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + // :: error: (contracts.postcondition.not.satisfied) + void setsX(EnsuresInitializedFieldsTest eift) { + eift.x = 1; + } - @EnsuresInitializedFields( - value = "this", - fields = {"x", "y"}) - void setsXY() { - x = 1; - y = 2; - } + @EnsuresInitializedFields( + value = "this", + fields = {"x", "y"}) + void setsXY() { + x = 1; + y = 2; + } - @EnsuresInitializedFields(fields = {"x", "y"}) - void setsXY2() { - x = 1; - y = 2; - } + @EnsuresInitializedFields(fields = {"x", "y"}) + void setsXY2() { + x = 1; + y = 2; + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - void setsXY(EnsuresInitializedFieldsTest eift) { - eift.x = 1; - eift.y = 2; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + void setsXY(EnsuresInitializedFieldsTest eift) { + eift.x = 1; + eift.y = 2; + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - void setsXY2(EnsuresInitializedFieldsTest eift) { - setsXY(eift); - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + void setsXY2(EnsuresInitializedFieldsTest eift) { + setsXY(eift); + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - @EnsuresInitializedFields( - value = "#2", - fields = {"x", "z"}) - void setsXY2(EnsuresInitializedFieldsTest eift1, EnsuresInitializedFieldsTest eift2) { - setsXY(eift1); - setsX(eift2); - eift2.z = 3; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + @EnsuresInitializedFields( + value = "#2", + fields = {"x", "z"}) + void setsXY2(EnsuresInitializedFieldsTest eift1, EnsuresInitializedFieldsTest eift2) { + setsXY(eift1); + setsX(eift2); + eift2.z = 3; + } } diff --git a/framework/tests/initialized-fields/HelperMethodInitializesFields.java b/framework/tests/initialized-fields/HelperMethodInitializesFields.java index 239bdfc9a2b..608537b06e2 100644 --- a/framework/tests/initialized-fields/HelperMethodInitializesFields.java +++ b/framework/tests/initialized-fields/HelperMethodInitializesFields.java @@ -3,186 +3,186 @@ public class HelperMethodInitializesFields { - int x; - int y; - int z; - - HelperMethodInitializesFields(int ignore) { - helperMethodXY(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore, String ignore2) { - helperMethodXY2(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(long ignore) { - this.helperMethodXY(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(long ignore, String ignore2) { - this.helperMethodXY2(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(float ignore) { - staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(double ignore) { - this.staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(boolean ignore) { - new OtherClass().helperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(char ignore) { - new OtherClass().helperMethodXY2(0, this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, byte ignore2) { - new OtherClass().staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, short ignore2) { - new OtherClass().staticHelperMethodXY2(0, this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, int ignore2) { - OtherClass.staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, long ignore2) { - OtherClass.staticHelperMethodXY2(0, this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - // Simple tests of LUB - - HelperMethodInitializesFields(boolean ignore1, int ignore) { - z = 3; - helperMethodXY(); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, char ignore) { - z = 3; - helperMethodXY2(); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, float ignore) { - z = 3; - staticHelperMethodXY(this); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, boolean ignore) { - z = 3; - new OtherClass().helperMethodXY(this); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, short ignore2) { - z = 3; - OtherClass.staticHelperMethodXY(this); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - // More complex tests of LUB - - HelperMethodInitializesFields(byte ignore1, int ignore) { - y = 2; - z = 3; - helperMethodXY(); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(byte ignore1, long ignore) { - y = 2; - helperMethodXY(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - // The helper methods - - @EnsuresInitializedFields( - value = "this", - fields = {"x", "y"}) - void helperMethodXY() { - x = 1; - this.y = 1; - } - - @EnsuresInitializedFields(fields = {"x", "y"}) - void helperMethodXY2() { - x = 1; - this.y = 1; - } - - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } + int x; + int y; + int z; + + HelperMethodInitializesFields(int ignore) { + helperMethodXY(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore, String ignore2) { + helperMethodXY2(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(long ignore) { + this.helperMethodXY(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(long ignore, String ignore2) { + this.helperMethodXY2(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(float ignore) { + staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(double ignore) { + this.staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(boolean ignore) { + new OtherClass().helperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(char ignore) { + new OtherClass().helperMethodXY2(0, this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, byte ignore2) { + new OtherClass().staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, short ignore2) { + new OtherClass().staticHelperMethodXY2(0, this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, int ignore2) { + OtherClass.staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, long ignore2) { + OtherClass.staticHelperMethodXY2(0, this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + // Simple tests of LUB + + HelperMethodInitializesFields(boolean ignore1, int ignore) { + z = 3; + helperMethodXY(); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, char ignore) { + z = 3; + helperMethodXY2(); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, float ignore) { + z = 3; + staticHelperMethodXY(this); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, boolean ignore) { + z = 3; + new OtherClass().helperMethodXY(this); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, short ignore2) { + z = 3; + OtherClass.staticHelperMethodXY(this); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + // More complex tests of LUB + + HelperMethodInitializesFields(byte ignore1, int ignore) { + y = 2; + z = 3; + helperMethodXY(); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(byte ignore1, long ignore) { + y = 2; + helperMethodXY(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + // The helper methods + + @EnsuresInitializedFields( + value = "this", + fields = {"x", "y"}) + void helperMethodXY() { + x = 1; + this.y = 1; + } + + @EnsuresInitializedFields(fields = {"x", "y"}) + void helperMethodXY2() { + x = 1; + this.y = 1; + } + + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } } class OtherClass { - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - void helperMethodXY(HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } - - @EnsuresInitializedFields( - value = "#2", - fields = {"x", "y"}) - void helperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } - - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } - - @EnsuresInitializedFields( - value = "#2", - fields = {"x", "y"}) - static void staticHelperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + void helperMethodXY(HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } + + @EnsuresInitializedFields( + value = "#2", + fields = {"x", "y"}) + void helperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } + + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } + + @EnsuresInitializedFields( + value = "#2", + fields = {"x", "y"}) + static void staticHelperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } } diff --git a/framework/tests/initialized-fields/SimpleConstructor.java b/framework/tests/initialized-fields/SimpleConstructor.java index b2caebacfec..7563549c594 100644 --- a/framework/tests/initialized-fields/SimpleConstructor.java +++ b/framework/tests/initialized-fields/SimpleConstructor.java @@ -2,28 +2,28 @@ public class SimpleConstructor { - int x; - int y; - int z; + int x; + int y; + int z; - SimpleConstructor() { - // :: error: (assignment.type.incompatible) - @InitializedFields({"x", "y", "z"}) SimpleConstructor sc1 = this; - @InitializedFields() SimpleConstructor sc2 = this; + SimpleConstructor() { + // :: error: (assignment.type.incompatible) + @InitializedFields({"x", "y", "z"}) SimpleConstructor sc1 = this; + @InitializedFields() SimpleConstructor sc2 = this; - x = 1; + x = 1; - // :: error: (assignment.type.incompatible) - @InitializedFields({"x", "y", "z"}) SimpleConstructor sc3 = this; - @InitializedFields({"x"}) SimpleConstructor sc4 = this; + // :: error: (assignment.type.incompatible) + @InitializedFields({"x", "y", "z"}) SimpleConstructor sc3 = this; + @InitializedFields({"x"}) SimpleConstructor sc4 = this; - this.y = 1; + this.y = 1; - // :: error: (assignment.type.incompatible) - @InitializedFields({"x", "y", "z"}) SimpleConstructor sc5 = this; - @InitializedFields({"x", "y"}) SimpleConstructor sc6 = this; - @InitializedFields({"y", "x"}) SimpleConstructor sc7 = this; + // :: error: (assignment.type.incompatible) + @InitializedFields({"x", "y", "z"}) SimpleConstructor sc5 = this; + @InitializedFields({"x", "y"}) SimpleConstructor sc6 = this; + @InitializedFields({"y", "x"}) SimpleConstructor sc7 = this; - z = 3; - } + z = 3; + } } diff --git a/framework/tests/lubglb/Dummy.java b/framework/tests/lubglb/Dummy.java index 41aedead81c..edd55f3231f 100644 --- a/framework/tests/lubglb/Dummy.java +++ b/framework/tests/lubglb/Dummy.java @@ -1,4 +1,4 @@ public class Dummy { - // We don't need any code here. - // The actual tests are performed within the LubglbChecker using assert statements. + // We don't need any code here. + // The actual tests are performed within the LubglbChecker using assert statements. } diff --git a/framework/tests/lubglb/IntersectionTypes.java b/framework/tests/lubglb/IntersectionTypes.java index d4521434e35..8d4680d6e20 100644 --- a/framework/tests/lubglb/IntersectionTypes.java +++ b/framework/tests/lubglb/IntersectionTypes.java @@ -7,25 +7,25 @@ interface Bar {} class Baz implements Foo, Bar {} public class IntersectionTypes { - // :: warning: (explicit.annotation.ignored) - void call1(S p) {} - - // :: warning: (explicit.annotation.ignored) - void call2(T p) {} - - void foo1(@LubglbD Baz baz1) { - call1(baz1); - call2(baz1); - } - - void foo2(@LubglbF Baz baz2) { - call1(baz2); - call2(baz2); - } - - void foo3(@LubglbB Baz baz3) { - call1(baz3); - // :: error: (type.argument.type.incompatible) - call2(baz3); - } + // :: warning: (explicit.annotation.ignored) + void call1(S p) {} + + // :: warning: (explicit.annotation.ignored) + void call2(T p) {} + + void foo1(@LubglbD Baz baz1) { + call1(baz1); + call2(baz1); + } + + void foo2(@LubglbF Baz baz2) { + call1(baz2); + call2(baz2); + } + + void foo3(@LubglbB Baz baz3) { + call1(baz3); + // :: error: (type.argument.type.incompatible) + call2(baz3); + } } diff --git a/framework/tests/lubglb/Issue2432Constructor.java b/framework/tests/lubglb/Issue2432Constructor.java index 33c8713fbd2..8aefab6190a 100644 --- a/framework/tests/lubglb/Issue2432Constructor.java +++ b/framework/tests/lubglb/Issue2432Constructor.java @@ -5,93 +5,92 @@ class Issue2432C { - // reason for suppressing: - // super.invocation: Object is @LubglbA by default and it is unreasonable to change jdk stub - // just because of this - // inconsistent.constructor.type: the qualifier on returning type is expected not to be top + // reason for suppressing: + // super.invocation: Object is @LubglbA by default and it is unreasonable to change jdk stub + // just because of this + // inconsistent.constructor.type: the qualifier on returning type is expected not to be top + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + Issue2432C(@PolyLubglb Object dummy) {} + + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + Issue2432C(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} + + // class for test cases using type parameter + static class TypeParamClass { + + // @PolyLubglb on T shouldn't be in the poly resolving process @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) @PolyLubglb - Issue2432C(@PolyLubglb Object dummy) {} + TypeParamClass(@PolyLubglb Object dummy, T t) {} + // 2 poly param for testing lub @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) @PolyLubglb - Issue2432C(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} - - // class for test cases using type parameter - static class TypeParamClass { - - // @PolyLubglb on T shouldn't be in the poly resolving process - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - TypeParamClass(@PolyLubglb Object dummy, T t) {} - - // 2 poly param for testing lub - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - TypeParamClass(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2, T t) {} - } - - // class for test cases using type parameter - class ReceiverClass { - - // if the qualifier on receiver is @PolyLubglb, it should not be involved in poly resolve - // process - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - ReceiverClass(Issue2432C Issue2432C.this, @PolyLubglb Object dummy) {} - - // 2 poly param for testing lub - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - ReceiverClass( - Issue2432C Issue2432C.this, @PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} - } - - void invokeConstructors(@LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { - // :: error: (assignment.type.incompatible) - @LubglbF Issue2432C bottomOuter = new Issue2432C(top); - @LubglbA Issue2432C topOuter = new Issue2432C(top); - - // lub test - @LubglbA Issue2432C bottomOuter2 = new Issue2432C(top, bottom); - // :: error: (assignment.type.incompatible) - @LubglbB Issue2432C bottomOuter3 = new Issue2432C(top, bottom); - - @LubglbF Issue2432C bottomOuter4 = new Issue2432C(bottom, bottom); - } - - // invoke constructors with a receiver to test poly resolving - // note: seems CF already works well on these before changes - void invokeReceiverConstructors( - @LubglbA Issue2432C topOuter, - @PolyLubglb Issue2432C polyOuter, - @LubglbF Object bottom, - @LubglbA Object top) { - Issue2432C.@LubglbF ReceiverClass ref1 = polyOuter.new ReceiverClass(bottom); - // :: error: (assignment.type.incompatible) - Issue2432C.@LubglbB ReceiverClass ref2 = polyOuter.new ReceiverClass(top); - - // lub tests - Issue2432C.@LubglbA ReceiverClass ref3 = polyOuter.new ReceiverClass(top, bottom); - // :: error: (assignment.type.incompatible) - Issue2432C.@LubglbB ReceiverClass ref4 = polyOuter.new ReceiverClass(top, bottom); - - Issue2432C.@LubglbF ReceiverClass ref5 = polyOuter.new ReceiverClass(bottom, bottom); - } - - // invoke constructors with a type parameter to test poly resolving - void invokeTypeVarConstructors( - @LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { - @LubglbF TypeParamClass<@PolyLubglb Object> ref1 = new TypeParamClass<>(bottom, poly); - // :: error: (assignment.type.incompatible) - @LubglbB TypeParamClass<@PolyLubglb Object> ref2 = new TypeParamClass<>(top, poly); - - // lub tests - @LubglbA TypeParamClass<@PolyLubglb Object> ref3 = new TypeParamClass<>(bottom, top, poly); - // :: error: (assignment.type.incompatible) - @LubglbB TypeParamClass<@PolyLubglb Object> ref4 = new TypeParamClass<>(bottom, top, poly); - - @LubglbF - TypeParamClass<@PolyLubglb Object> ref5 = new TypeParamClass<>(bottom, bottom, poly); - } + TypeParamClass(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2, T t) {} + } + + // class for test cases using type parameter + class ReceiverClass { + + // if the qualifier on receiver is @PolyLubglb, it should not be involved in poly resolve + // process + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + ReceiverClass(Issue2432C Issue2432C.this, @PolyLubglb Object dummy) {} + + // 2 poly param for testing lub + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + ReceiverClass( + Issue2432C Issue2432C.this, @PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} + } + + void invokeConstructors(@LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { + // :: error: (assignment.type.incompatible) + @LubglbF Issue2432C bottomOuter = new Issue2432C(top); + @LubglbA Issue2432C topOuter = new Issue2432C(top); + + // lub test + @LubglbA Issue2432C bottomOuter2 = new Issue2432C(top, bottom); + // :: error: (assignment.type.incompatible) + @LubglbB Issue2432C bottomOuter3 = new Issue2432C(top, bottom); + + @LubglbF Issue2432C bottomOuter4 = new Issue2432C(bottom, bottom); + } + + // invoke constructors with a receiver to test poly resolving + // note: seems CF already works well on these before changes + void invokeReceiverConstructors( + @LubglbA Issue2432C topOuter, + @PolyLubglb Issue2432C polyOuter, + @LubglbF Object bottom, + @LubglbA Object top) { + Issue2432C.@LubglbF ReceiverClass ref1 = polyOuter.new ReceiverClass(bottom); + // :: error: (assignment.type.incompatible) + Issue2432C.@LubglbB ReceiverClass ref2 = polyOuter.new ReceiverClass(top); + + // lub tests + Issue2432C.@LubglbA ReceiverClass ref3 = polyOuter.new ReceiverClass(top, bottom); + // :: error: (assignment.type.incompatible) + Issue2432C.@LubglbB ReceiverClass ref4 = polyOuter.new ReceiverClass(top, bottom); + + Issue2432C.@LubglbF ReceiverClass ref5 = polyOuter.new ReceiverClass(bottom, bottom); + } + + // invoke constructors with a type parameter to test poly resolving + void invokeTypeVarConstructors( + @LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { + @LubglbF TypeParamClass<@PolyLubglb Object> ref1 = new TypeParamClass<>(bottom, poly); + // :: error: (assignment.type.incompatible) + @LubglbB TypeParamClass<@PolyLubglb Object> ref2 = new TypeParamClass<>(top, poly); + + // lub tests + @LubglbA TypeParamClass<@PolyLubglb Object> ref3 = new TypeParamClass<>(bottom, top, poly); + // :: error: (assignment.type.incompatible) + @LubglbB TypeParamClass<@PolyLubglb Object> ref4 = new TypeParamClass<>(bottom, top, poly); + + @LubglbF TypeParamClass<@PolyLubglb Object> ref5 = new TypeParamClass<>(bottom, bottom, poly); + } } diff --git a/framework/tests/methodval/MethodNameTest.java b/framework/tests/methodval/MethodNameTest.java index f1cb5eaa241..0c6ae9507d9 100644 --- a/framework/tests/methodval/MethodNameTest.java +++ b/framework/tests/methodval/MethodNameTest.java @@ -1,41 +1,41 @@ import org.checkerframework.common.reflection.qual.MethodVal; public class MethodNameTest { - @MethodVal(className = "", methodName = "_MethodName", params = 0) Object o; + @MethodVal(className = "", methodName = "_MethodName", params = 0) Object o; - @MethodVal(className = "", methodName = "$methname", params = 0) Object o1; + @MethodVal(className = "", methodName = "$methname", params = 0) Object o1; - @MethodVal(className = "", methodName = "Method_Name", params = 0) Object o2; + @MethodVal(className = "", methodName = "Method_Name", params = 0) Object o2; - @MethodVal(className = "", methodName = "", params = 0) Object o3; + @MethodVal(className = "", methodName = "", params = 0) Object o3; - // :: error: (illegal.methodname) - @MethodVal(className = "", methodName = "[]MethodName", params = 0) Object o4; + // :: error: (illegal.methodname) + @MethodVal(className = "", methodName = "[]MethodName", params = 0) Object o4; - // :: error: (illegal.methodname) - @MethodVal(className = "", methodName = "Meht.name", params = 0) Object o5; + // :: error: (illegal.methodname) + @MethodVal(className = "", methodName = "Meht.name", params = 0) Object o5; - // :: error: (illegal.methodname) - @MethodVal(className = "", methodName = ".emethos", params = 0) Object o6; + // :: error: (illegal.methodname) + @MethodVal(className = "", methodName = ".emethos", params = 0) Object o6; - @MethodVal( - className = "c", - methodName = "m", - params = {0, 0}) - // :: error: (invalid.methodval) - Object o7; + @MethodVal( + className = "c", + methodName = "m", + params = {0, 0}) + // :: error: (invalid.methodval) + Object o7; - @MethodVal( - className = "c", - methodName = {"m", "m"}, - params = {0, 0}) - // :: error: (invalid.methodval) - Object o8; + @MethodVal( + className = "c", + methodName = {"m", "m"}, + params = {0, 0}) + // :: error: (invalid.methodval) + Object o8; - @MethodVal( - className = "c", - methodName = {"m", "m"}, - params = {0}) - // :: error: (invalid.methodval) - Object o9; + @MethodVal( + className = "c", + methodName = {"m", "m"}, + params = {0}) + // :: error: (invalid.methodval) + Object o9; } diff --git a/framework/tests/methodval/MethodValInferenceTest.java b/framework/tests/methodval/MethodValInferenceTest.java index 60c8ea64221..56fdb17f936 100644 --- a/framework/tests/methodval/MethodValInferenceTest.java +++ b/framework/tests/methodval/MethodValInferenceTest.java @@ -1,104 +1,103 @@ +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import org.checkerframework.common.reflection.qual.ClassBound; import org.checkerframework.common.reflection.qual.ClassVal; import org.checkerframework.common.reflection.qual.MethodVal; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.StringVal; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - public class MethodValInferenceTest { - boolean flag = true; - - public void testGetMethodParamLen( - Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { - @StringVal("someMethod") String str = "someMethod"; - @ClassVal("java.lang.Object") Class c = Object.class; - - @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = c.getMethod("getA", new Class[] {}); - - @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m2 = c.getMethod("getA", (Class[]) null); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m3 = c.getMethod("someMethod", new Class[] {Integer.class}); - - @MethodVal(className = "java.lang.Object", methodName = "equals", params = 1) Method m4 = c.getMethod("equals", Object.class); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m5 = c.getMethod(str, int.class); - - @MethodVal(className = "java.lang.Object", methodName = "getB", params = 0) Method m6 = c.getMethod("getB", new Class[0]); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m7 = c.getMethod(str, new Class[] {int.class}); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m8 = c.getMethod(str, new Class[] {Integer.class, Integer.class}); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m10 = c.getMethod(str, int.class, int.class); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m11 = c.getMethod(str, classArray2); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = -1) Method m12 = c.getMethod(str, classArrayUnknown); - } - - public void testGetMethodMultiClassAndMethodNames( - @ClassVal({"java.lang.Object", "java.lang.String"}) Class twoClasses, - @ClassVal({"java.lang.Object"}) Class oneClass, - @StringVal({"method1"}) String oneName, - @StringVal({"method1", "method2"}) String twoNames, - Class @ArrayLen(2) [] classArray2, - Class[] classArrayUnknown) - throws Exception { - - @MethodVal( - className = {"java.lang.Object"}, - methodName = {"method1"}, - params = -1) - Method m1 = oneClass.getMethod(oneName, classArrayUnknown); - @MethodVal( - className = {"java.lang.Object", "java.lang.Object"}, - methodName = {"method1", "method2"}, - params = {-1, -1}) - Method m2 = oneClass.getMethod(twoNames, classArrayUnknown); - @MethodVal( - className = {"java.lang.Object", "java.lang.String"}, - methodName = {"method1", "method1"}, - params = {-1, -1}) - Method m3 = twoClasses.getMethod(oneName, classArrayUnknown); - @MethodVal( - className = { - "java.lang.Object", - "java.lang.String", - "java.lang.Object", - "java.lang.String" - }, - methodName = {"method1", "method2", "method2", "method1"}, - params = {-1, -1, -1, -1}) - Method m4 = twoClasses.getMethod(twoNames, classArrayUnknown); - } - - @ClassBound("java.lang.Object") Class classBound = Object.class; - - public void testGetConstructorClassBound() throws Exception { - @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = classBound.getMethod("getA", new Class[] {}); - } - - public void testGetConstructorClassBoundFail() throws Exception { - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) - // :: error: (assignment.type.incompatible) - Constructor con1 = classBound.getConstructor(new Class[] {}); // Should be @UnknownMethod - } - - public void testGetConstructorParamLen( - Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { - @ClassVal("java.lang.Object") Class c = Object.class; - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con1 = c.getConstructor(new Class[] {}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con2 = c.getConstructor((Class[]) null); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con3 = c.getConstructor(new Class[] {Integer.class}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con4 = c.getConstructor(Object.class); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con5 = c.getConstructor(int.class); - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con6 = c.getConstructor(new Class[0]); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con7 = c.getConstructor(new Class[] {int.class}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con8 = c.getConstructor(new Class[] {Integer.class, Integer.class}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con9 = c.getConstructor(int.class, int.class); - @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con10 = c.getConstructor(classArray2); - @MethodVal(className = "java.lang.Object", methodName = "", params = -1) Constructor con11 = c.getConstructor(classArrayUnknown); - } + boolean flag = true; + + public void testGetMethodParamLen( + Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { + @StringVal("someMethod") String str = "someMethod"; + @ClassVal("java.lang.Object") Class c = Object.class; + + @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = c.getMethod("getA", new Class[] {}); + + @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m2 = c.getMethod("getA", (Class[]) null); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m3 = c.getMethod("someMethod", new Class[] {Integer.class}); + + @MethodVal(className = "java.lang.Object", methodName = "equals", params = 1) Method m4 = c.getMethod("equals", Object.class); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m5 = c.getMethod(str, int.class); + + @MethodVal(className = "java.lang.Object", methodName = "getB", params = 0) Method m6 = c.getMethod("getB", new Class[0]); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m7 = c.getMethod(str, new Class[] {int.class}); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m8 = c.getMethod(str, new Class[] {Integer.class, Integer.class}); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m10 = c.getMethod(str, int.class, int.class); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m11 = c.getMethod(str, classArray2); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = -1) Method m12 = c.getMethod(str, classArrayUnknown); + } + + public void testGetMethodMultiClassAndMethodNames( + @ClassVal({"java.lang.Object", "java.lang.String"}) Class twoClasses, + @ClassVal({"java.lang.Object"}) Class oneClass, + @StringVal({"method1"}) String oneName, + @StringVal({"method1", "method2"}) String twoNames, + Class @ArrayLen(2) [] classArray2, + Class[] classArrayUnknown) + throws Exception { + + @MethodVal( + className = {"java.lang.Object"}, + methodName = {"method1"}, + params = -1) + Method m1 = oneClass.getMethod(oneName, classArrayUnknown); + @MethodVal( + className = {"java.lang.Object", "java.lang.Object"}, + methodName = {"method1", "method2"}, + params = {-1, -1}) + Method m2 = oneClass.getMethod(twoNames, classArrayUnknown); + @MethodVal( + className = {"java.lang.Object", "java.lang.String"}, + methodName = {"method1", "method1"}, + params = {-1, -1}) + Method m3 = twoClasses.getMethod(oneName, classArrayUnknown); + @MethodVal( + className = { + "java.lang.Object", + "java.lang.String", + "java.lang.Object", + "java.lang.String" + }, + methodName = {"method1", "method2", "method2", "method1"}, + params = {-1, -1, -1, -1}) + Method m4 = twoClasses.getMethod(twoNames, classArrayUnknown); + } + + @ClassBound("java.lang.Object") Class classBound = Object.class; + + public void testGetConstructorClassBound() throws Exception { + @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = classBound.getMethod("getA", new Class[] {}); + } + + public void testGetConstructorClassBoundFail() throws Exception { + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) + // :: error: (assignment.type.incompatible) + Constructor con1 = classBound.getConstructor(new Class[] {}); // Should be @UnknownMethod + } + + public void testGetConstructorParamLen( + Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { + @ClassVal("java.lang.Object") Class c = Object.class; + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con1 = c.getConstructor(new Class[] {}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con2 = c.getConstructor((Class[]) null); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con3 = c.getConstructor(new Class[] {Integer.class}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con4 = c.getConstructor(Object.class); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con5 = c.getConstructor(int.class); + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con6 = c.getConstructor(new Class[0]); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con7 = c.getConstructor(new Class[] {int.class}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con8 = c.getConstructor(new Class[] {Integer.class, Integer.class}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con9 = c.getConstructor(int.class, int.class); + @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con10 = c.getConstructor(classArray2); + @MethodVal(className = "java.lang.Object", methodName = "", params = -1) Constructor con11 = c.getConstructor(classArrayUnknown); + } } diff --git a/framework/tests/methodval/MethodValLUBTest.java b/framework/tests/methodval/MethodValLUBTest.java index 3f0a70328e6..690403dc500 100644 --- a/framework/tests/methodval/MethodValLUBTest.java +++ b/framework/tests/methodval/MethodValLUBTest.java @@ -1,103 +1,102 @@ -import org.checkerframework.common.reflection.qual.MethodVal; - import java.lang.reflect.Method; +import org.checkerframework.common.reflection.qual.MethodVal; public class MethodValLUBTest { - Object unknown = null; - boolean flag = false; + Object unknown = null; + boolean flag = false; - @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10 = null; + @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10 = null; - @MethodVal(className = "c2", methodName = "m2", params = 1) Object c2m21 = null; + @MethodVal(className = "c2", methodName = "m2", params = 1) Object c2m21 = null; - void basicLub() { - if (flag) { - unknown = c1m10; - } else { - unknown = c2m21; - } - @MethodVal( - className = {"c1", "c2"}, - methodName = {"m1", "m2"}, - params = {0, 1}) - Object lub = unknown; - // :: error: (assignment.type.incompatible) - c1m10 = unknown; - // :: error: (assignment.type.incompatible) - c2m21 = unknown; + void basicLub() { + if (flag) { + unknown = c1m10; + } else { + unknown = c2m21; } + @MethodVal( + className = {"c1", "c2"}, + methodName = {"m1", "m2"}, + params = {0, 1}) + Object lub = unknown; + // :: error: (assignment.type.incompatible) + c1m10 = unknown; + // :: error: (assignment.type.incompatible) + c2m21 = unknown; + } - @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10duplicate = null; + @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10duplicate = null; - void lubSameType() { - if (flag) { - unknown = c1m10; - } else { - unknown = c1m10duplicate; - } - @MethodVal(className = "c1", methodName = "m1", params = 0) Object lub = unknown; + void lubSameType() { + if (flag) { + unknown = c1m10; + } else { + unknown = c1m10duplicate; } + @MethodVal(className = "c1", methodName = "m1", params = 0) Object lub = unknown; + } - @MethodVal(className = "c1", methodName = "m1", params = 1) Object c1m11 = null; + @MethodVal(className = "c1", methodName = "m1", params = 1) Object c1m11 = null; - void simalarSigLub() { - if (flag) { - unknown = c1m10; - } else { - unknown = c1m11; - } - @MethodVal( - className = {"c1", "c1"}, - methodName = {"m1", "m1"}, - params = {0, 1}) - Object lub = unknown; - // :: error: (assignment.type.incompatible) - c1m10 = unknown; - // :: error: (assignment.type.incompatible) - c1m11 = unknown; + void simalarSigLub() { + if (flag) { + unknown = c1m10; + } else { + unknown = c1m11; } - @MethodVal( - className = {"class", "class2"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object classClass2Method0 = null; + className = {"c1", "c1"}, + methodName = {"m1", "m1"}, + params = {0, 1}) + Object lub = unknown; + // :: error: (assignment.type.incompatible) + c1m10 = unknown; + // :: error: (assignment.type.incompatible) + c1m11 = unknown; + } - @MethodVal( - className = {"class2", "class"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object class2classMethod0 = null; + @MethodVal( + className = {"class", "class2"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object classClass2Method0 = null; + + @MethodVal( + className = {"class2", "class"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object class2classMethod0 = null; - void setsLub() { - if (flag) { - unknown = classClass2Method0; - } else { - unknown = class2classMethod0; - } - @MethodVal( - className = {"class2", "class", "class", "class2"}, - methodName = {"method", "method2", "method", "method2"}, - params = {0, 1, 0, 1}) - Object lub = unknown; - // :: error: (assignment.type.incompatible) - classClass2Method0 = unknown; - // :: error: (assignment.type.incompatible) - class2classMethod0 = unknown; + void setsLub() { + if (flag) { + unknown = classClass2Method0; + } else { + unknown = class2classMethod0; } + @MethodVal( + className = {"class2", "class", "class", "class2"}, + methodName = {"method", "method2", "method", "method2"}, + params = {0, 1, 0, 1}) + Object lub = unknown; + // :: error: (assignment.type.incompatible) + classClass2Method0 = unknown; + // :: error: (assignment.type.incompatible) + class2classMethod0 = unknown; + } - void inferedlubTest() throws Exception { - Class c = MethodValInferenceTest.class; - Method m; - if (flag) { - m = c.getMethod("getA", new Class[0]); - } else { - m = c.getMethod("getB", new Class[0]); - } - @MethodVal( - className = {"MethodValInferenceTest", "MethodValInferenceTest"}, - methodName = {"getA", "getB"}, - params = {0, 0}) - Method lub = m; + void inferedlubTest() throws Exception { + Class c = MethodValInferenceTest.class; + Method m; + if (flag) { + m = c.getMethod("getA", new Class[0]); + } else { + m = c.getMethod("getB", new Class[0]); } + @MethodVal( + className = {"MethodValInferenceTest", "MethodValInferenceTest"}, + methodName = {"getA", "getB"}, + params = {0, 0}) + Method lub = m; + } } diff --git a/framework/tests/methodval/MethodValSubtypingTest.java b/framework/tests/methodval/MethodValSubtypingTest.java index 3aeb24716b8..ebdafd802d1 100644 --- a/framework/tests/methodval/MethodValSubtypingTest.java +++ b/framework/tests/methodval/MethodValSubtypingTest.java @@ -1,69 +1,69 @@ import org.checkerframework.common.reflection.qual.MethodVal; public class MethodValSubtypingTest { - @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0 = null; + @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0 = null; - @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0Dup = null; + @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0Dup = null; - @MethodVal( - className = {"class", "class2"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object classClass2Method0 = null; + @MethodVal( + className = {"class", "class2"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object classClass2Method0 = null; - @MethodVal( - className = {"class2", "class"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object class2classMethod0 = null; + @MethodVal( + className = {"class2", "class"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object class2classMethod0 = null; - Object unknown = null; + Object unknown = null; - void methodValSubtyping() { - classMethod0 = classMethod0Dup; - // :: error: (assignment.type.incompatible) - classMethod0 = classClass2Method0; - // :: error: (assignment.type.incompatible) - classClass2Method0 = class2classMethod0; - classClass2Method0 = classMethod0; - } + void methodValSubtyping() { + classMethod0 = classMethod0Dup; + // :: error: (assignment.type.incompatible) + classMethod0 = classClass2Method0; + // :: error: (assignment.type.incompatible) + classClass2Method0 = class2classMethod0; + classClass2Method0 = classMethod0; + } - void bottomMethodVal() { - classMethod0 = null; - classClass2Method0 = null; - } + void bottomMethodVal() { + classMethod0 = null; + classClass2Method0 = null; + } - void unknownMethodVal1() { - unknown = class2classMethod0; - } + void unknownMethodVal1() { + unknown = class2classMethod0; + } - void unknownMethodVal2() { - // :: error: (assignment.type.incompatible) - class2classMethod0 = unknown; - } + void unknownMethodVal2() { + // :: error: (assignment.type.incompatible) + class2classMethod0 = unknown; + } - @MethodVal( - className = {"aclass", "aclass", "aclass"}, - methodName = {"amethod", "amethod", "amethod"}, - params = {0, 1, 2}) - Object triple = null; + @MethodVal( + className = {"aclass", "aclass", "aclass"}, + methodName = {"amethod", "amethod", "amethod"}, + params = {0, 1, 2}) + Object triple = null; - @MethodVal( - className = {"aclass", "aclass", "aclass"}, - methodName = {"amethod", "amethod", "amethod"}, - params = {2, 1, 0}) - Object tripleAgain = null; + @MethodVal( + className = {"aclass", "aclass", "aclass"}, + methodName = {"amethod", "amethod", "amethod"}, + params = {2, 1, 0}) + Object tripleAgain = null; - @MethodVal( - className = {"aclass"}, - methodName = {"amethod"}, - params = {2}) - Object one = null; + @MethodVal( + className = {"aclass"}, + methodName = {"amethod"}, + params = {2}) + Object one = null; - void test() { - tripleAgain = triple; - // :: error: (assignment.type.incompatible) - one = triple; - triple = one; - } + void test() { + tripleAgain = triple; + // :: error: (assignment.type.incompatible) + one = triple; + triple = one; + } } diff --git a/framework/tests/nontopdefault/NTDTest.java b/framework/tests/nontopdefault/NTDTest.java index 420bee634ff..1bec385d19e 100644 --- a/framework/tests/nontopdefault/NTDTest.java +++ b/framework/tests/nontopdefault/NTDTest.java @@ -14,57 +14,57 @@ public class NTDConstructorReceiverTest { - // default method receiver is @NTDTop - void DefaultMethodReceiver() { + // default method receiver is @NTDTop + void DefaultMethodReceiver() { - // this line produces a methodref.receiver.bound.invalid error, but it shouldn't if the - // receiver for inner class constructors are properly applied - Demand constructorReference = InnerDefaultReceiver::new; + // this line produces a methodref.receiver.bound.invalid error, but it shouldn't if the + // receiver for inner class constructors are properly applied + Demand constructorReference = InnerDefaultReceiver::new; - // this line does not as the receiver is explicitly declared to be @NTDTop - Demand constructorReference2 = InnerExplicitReceiver::new; - } + // this line does not as the receiver is explicitly declared to be @NTDTop + Demand constructorReference2 = InnerExplicitReceiver::new; + } - class InnerDefaultReceiver { - // takes on the default receiver for inner class constructor methods - InnerDefaultReceiver(NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { - // The type of NTDConstructorReceiverTest.this should be @NTDTop. - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; - } + class InnerDefaultReceiver { + // takes on the default receiver for inner class constructor methods + InnerDefaultReceiver(NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { + // The type of NTDConstructorReceiverTest.this should be @NTDTop. + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; + } - void method() { - // The TypeUseLocation.RECEIVER only applies to the outermost type, so - // NTDConstructorReceiverTest.this is given the - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - // :: error: (assignment.type.incompatible) - @NTDMiddle InnerDefaultReceiver thatInner = this; - } + void method() { + // The TypeUseLocation.RECEIVER only applies to the outermost type, so + // NTDConstructorReceiverTest.this is given the + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + // :: error: (assignment.type.incompatible) + @NTDMiddle InnerDefaultReceiver thatInner = this; + } - void explicit(@NTDTop NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver this) { - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; - } + void explicit(@NTDTop NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver this) { + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; } + } - class InnerExplicitReceiver { - // explicitly set the receiver to be @NTDTop - InnerExplicitReceiver(@NTDTop NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - } + class InnerExplicitReceiver { + // explicitly set the receiver to be @NTDTop + InnerExplicitReceiver(@NTDTop NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + } - InnerExplicitReceiver( - @NTDMiddle NTDConstructorReceiverTest NTDConstructorReceiverTest.this, int i) { - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - } + InnerExplicitReceiver( + @NTDMiddle NTDConstructorReceiverTest NTDConstructorReceiverTest.this, int i) { + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; } + } } interface Demand { - R supply(); + R supply(); } diff --git a/framework/tests/nontopdefault/TestCasting.java b/framework/tests/nontopdefault/TestCasting.java index 06e659cfd55..d506e2b7aa6 100644 --- a/framework/tests/nontopdefault/TestCasting.java +++ b/framework/tests/nontopdefault/TestCasting.java @@ -2,19 +2,19 @@ @SuppressWarnings("inconsistent.constructor.type") // Not the point of this test public class TestCasting { - void repro(@NTDMiddle long startTime) { - try { - System.out.println("Inside try"); - return; - } catch (Exception ex) { - long timeTaken = startTime; - @NTDMiddle double dblTimeTaken = timeTaken; + void repro(@NTDMiddle long startTime) { + try { + System.out.println("Inside try"); + return; + } catch (Exception ex) { + long timeTaken = startTime; + @NTDMiddle double dblTimeTaken = timeTaken; - throw new IllegalArgumentException(); - } finally { - long timeTaken2 = startTime; - // This assignment used to fail. - @NTDMiddle double dblTimeTaken2 = timeTaken2; - } + throw new IllegalArgumentException(); + } finally { + long timeTaken2 = startTime; + // This assignment used to fail. + @NTDMiddle double dblTimeTaken2 = timeTaken2; } + } } diff --git a/framework/tests/purity-suggestions/PuritySuggestionsClass.java b/framework/tests/purity-suggestions/PuritySuggestionsClass.java index f3f17b1b142..f1e1d70702b 100644 --- a/framework/tests/purity-suggestions/PuritySuggestionsClass.java +++ b/framework/tests/purity-suggestions/PuritySuggestionsClass.java @@ -6,169 +6,169 @@ public class PuritySuggestionsClass { - String f1, f2, f3; - String[] a; - static String staticString; + String f1, f2, f3; + String[] a; + static String staticString; - // class with a (potentially) non-pure constructor - private static class NonPureClass { - String t; + // class with a (potentially) non-pure constructor + private static class NonPureClass { + String t; - public NonPureClass() { - staticString = ""; - } - } - - // class with a pure constructor - private static class PureClass { - // :: warning: (purity.more.sideeffectfree) - public PureClass() {} - } - - // class with a pure constructor - private static class PureClass2 { - String t; - - // :: warning: (purity.more.sideeffectfree) - public PureClass2() { - t = ""; - } - } - - // :: warning: (purity.more.sideeffectfree) - void nonpure() {} - - @Pure - String pure() { - return ""; - } - - String t3() { - nonpure(); - return ""; - } - - // :: warning: (purity.more.pure) - String t4() { - pure(); - return ""; - } - - // :: warning: (purity.more.pure) - int t5() { - int i = 1; - return i; - } - - // :: warning: (purity.more.pure) - int t6() { - int j = 0; - for (int i = 0; i < 10; i++) { - j = j - i; - } - return j; - } - - // :: warning: (purity.more.pure) - String t7() { - if (true) { - return "a"; - } - return ""; - } - - // :: warning: (purity.more.pure) - int t8() { - return 1 - 2 / 3 * 2 % 2; - } - - // :: warning: (purity.more.pure) - String t9() { - return "b" + "a"; - } - - String t10() { - f1 = ""; - f2 = ""; - return ""; - } - - String t11(PuritySuggestionsClass l) { - l.a[0] = ""; - return ""; - } - - String t12(String[] s) { - s[0] = ""; - return ""; - } - - String t13() { - PureClass p = new PureClass(); - return ""; - } - - // :: warning: (purity.more.pure) - String t14() { - String i = ""; - i = "a"; - return i; - } - - // :: warning: (purity.more.pure) - String t15() { - String[] s = new String[1]; - return s[0]; - } - - // :: warning: (purity.more.sideeffectfree) - String t16() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... - } - return ""; - } - - // :: warning: (purity.more.sideeffectfree) - String t16b() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... - } - return ""; + public NonPureClass() { + staticString = ""; } + } + // class with a pure constructor + private static class PureClass { // :: warning: (purity.more.sideeffectfree) - String t16c() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... - } - return ""; - } + public PureClass() {} + } - // :: warning: (purity.more.pure) - String t17() { - return ""; - } + // class with a pure constructor + private static class PureClass2 { + String t; - @Deterministic // :: warning: (purity.more.sideeffectfree) - String t18() { - return ""; - } - - // :: warning: (purity.more.deterministic) - String t19() { - return t18(); - } - - String t12() { - NonPureClass p = new NonPureClass(); - return ""; - } + public PureClass2() { + t = ""; + } + } + + // :: warning: (purity.more.sideeffectfree) + void nonpure() {} + + @Pure + String pure() { + return ""; + } + + String t3() { + nonpure(); + return ""; + } + + // :: warning: (purity.more.pure) + String t4() { + pure(); + return ""; + } + + // :: warning: (purity.more.pure) + int t5() { + int i = 1; + return i; + } + + // :: warning: (purity.more.pure) + int t6() { + int j = 0; + for (int i = 0; i < 10; i++) { + j = j - i; + } + return j; + } + + // :: warning: (purity.more.pure) + String t7() { + if (true) { + return "a"; + } + return ""; + } + + // :: warning: (purity.more.pure) + int t8() { + return 1 - 2 / 3 * 2 % 2; + } + + // :: warning: (purity.more.pure) + String t9() { + return "b" + "a"; + } + + String t10() { + f1 = ""; + f2 = ""; + return ""; + } + + String t11(PuritySuggestionsClass l) { + l.a[0] = ""; + return ""; + } + + String t12(String[] s) { + s[0] = ""; + return ""; + } + + String t13() { + PureClass p = new PureClass(); + return ""; + } + + // :: warning: (purity.more.pure) + String t14() { + String i = ""; + i = "a"; + return i; + } + + // :: warning: (purity.more.pure) + String t15() { + String[] s = new String[1]; + return s[0]; + } + + // :: warning: (purity.more.sideeffectfree) + String t16() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... + } + return ""; + } + + // :: warning: (purity.more.sideeffectfree) + String t16b() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... + } + return ""; + } + + // :: warning: (purity.more.sideeffectfree) + String t16c() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... + } + return ""; + } + + // :: warning: (purity.more.pure) + String t17() { + return ""; + } + + @Deterministic + // :: warning: (purity.more.sideeffectfree) + String t18() { + return ""; + } + + // :: warning: (purity.more.deterministic) + String t19() { + return t18(); + } + + String t12() { + NonPureClass p = new NonPureClass(); + return ""; + } } diff --git a/framework/tests/reflection/AnonymousClassTest.java b/framework/tests/reflection/AnonymousClassTest.java index 51eb0a3e2bc..c814fdad717 100644 --- a/framework/tests/reflection/AnonymousClassTest.java +++ b/framework/tests/reflection/AnonymousClassTest.java @@ -1,129 +1,128 @@ +import java.lang.reflect.Method; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectBottom; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling1; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling2; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectTop; -import java.lang.reflect.Method; - public class AnonymousClassTest { - /** - * To build/run outside of the JUnit tests: - * - *

          Build with $CHECKERFRAMEWOKR/framework/tests/build/ on the classpath. Need to either use - * Java 8 or the langtools compiler, because annotations on cast are used. - * - *

          java AnonymousClassTest MyClass$1.getSib1() MyClass$1.setSib1() MyClass$1.setSib1() - * MyClass$1.setSib2() MyClass$1.setSib2() MyClass$1.getSib2() - */ - public static void main(String[] args) { - AnonymousClassTest act = new AnonymousClassTest(); - act.returnTypePass(); - act.argumentTypePass(); - act.argumentTypeFail(); - act.returnTypeFail(); - } - - @TestReflectSibling1 int sibling1; - @TestReflectSibling2 int sibling2; - - public void returnTypePass() { - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod("getSib1", new Class[] {}); - // TODO: Can we resolve anonymous classes? - // :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - ignore.printStackTrace(); - } + /** + * To build/run outside of the JUnit tests: + * + *

          Build with $CHECKERFRAMEWOKR/framework/tests/build/ on the classpath. Need to either use + * Java 8 or the langtools compiler, because annotations on cast are used. + * + *

          java AnonymousClassTest MyClass$1.getSib1() MyClass$1.setSib1() MyClass$1.setSib1() + * MyClass$1.setSib2() MyClass$1.setSib2() MyClass$1.getSib2() + */ + public static void main(String[] args) { + AnonymousClassTest act = new AnonymousClassTest(); + act.returnTypePass(); + act.argumentTypePass(); + act.argumentTypeFail(); + act.returnTypeFail(); + } + + @TestReflectSibling1 int sibling1; + @TestReflectSibling2 int sibling2; + + public void returnTypePass() { + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod("getSib1", new Class[] {}); + // TODO: Can we resolve anonymous classes? + // :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + ignore.printStackTrace(); } - - public void argumentTypePass() { - String str = "setSib1"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod(str, new Class[] {int.class}); - // TODO: Can we resolve anonymous classes? - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val1); - // TODO: Can we resolve anonymous classes? - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val2); - } catch (Exception ignore) { - ignore.printStackTrace(); - } + } + + public void argumentTypePass() { + String str = "setSib1"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod(str, new Class[] {int.class}); + // TODO: Can we resolve anonymous classes? + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val1); + // TODO: Can we resolve anonymous classes? + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val2); + } catch (Exception ignore) { + ignore.printStackTrace(); } - - public void argumentTypeFail() { - String str = "setSib2"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod(str, new Class[] {int.class}); - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val1); - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val2); - } catch (Exception ignore) { - ignore.printStackTrace(); - } + } + + public void argumentTypeFail() { + String str = "setSib2"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod(str, new Class[] {int.class}); + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val1); + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val2); + } catch (Exception ignore) { + ignore.printStackTrace(); } - - public void returnTypeFail() { - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod("getSib2", new Class[] {}); - // :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - ignore.printStackTrace(); - } + } + + public void returnTypeFail() { + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod("getSib2", new Class[] {}); + // :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + ignore.printStackTrace(); } + } - public @TestReflectBottom MyClass anonymous = - // :: warning: (cast.unsafe.constructor.invocation) - new @TestReflectBottom MyClass() { + public @TestReflectBottom MyClass anonymous = + // :: warning: (cast.unsafe.constructor.invocation) + new @TestReflectBottom MyClass() { - public @TestReflectSibling1 int getSib1() { - System.out.println("MyClass$1.getSib1()"); - return 1; - } + public @TestReflectSibling1 int getSib1() { + System.out.println("MyClass$1.getSib1()"); + return 1; + } - public @TestReflectSibling2 int getSib2() { - System.out.println("MyClass$1.getSib2()"); - return 1; - } + public @TestReflectSibling2 int getSib2() { + System.out.println("MyClass$1.getSib2()"); + return 1; + } - public void setSib1(@TestReflectSibling1 int a) { - System.out.println("MyClass$1.setSib1()"); - } + public void setSib1(@TestReflectSibling1 int a) { + System.out.println("MyClass$1.setSib1()"); + } - public void setSib2(@TestReflectSibling2 int a) { - System.out.println("MyClass$1.setSib2()"); - } - }; + public void setSib2(@TestReflectSibling2 int a) { + System.out.println("MyClass$1.setSib2()"); + } + }; - class MyClass { + class MyClass { - public @TestReflectTop int getSib1() { - System.out.println("MyClass.getSib1()"); - return 1; - } + public @TestReflectTop int getSib1() { + System.out.println("MyClass.getSib1()"); + return 1; + } - public @TestReflectTop int getSib2() { - System.out.println("MyClass.getSib1()"); - return 1; - } + public @TestReflectTop int getSib2() { + System.out.println("MyClass.getSib1()"); + return 1; + } - public void setSib1(@TestReflectBottom int a) { - System.out.println("MyClass.setSib1()"); - } + public void setSib1(@TestReflectBottom int a) { + System.out.println("MyClass.setSib1()"); + } - public void setSib2(@TestReflectBottom int a) { - System.out.println("MyClass.setSib2()"); - } + public void setSib2(@TestReflectBottom int a) { + System.out.println("MyClass.setSib2()"); } + } } diff --git a/framework/tests/reflection/MethodTest.java b/framework/tests/reflection/MethodTest.java index 9c3ae0cc441..bb8c98fa0ab 100644 --- a/framework/tests/reflection/MethodTest.java +++ b/framework/tests/reflection/MethodTest.java @@ -1,426 +1,409 @@ +import java.lang.reflect.Method; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectBottom; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling1; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling2; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectTop; -import java.lang.reflect.Method; - public class MethodTest { - @TestReflectSibling1 int sibling1; - @TestReflectSibling2 int sibling2; - @TestReflectBottom SuperClass superClass; - - public void real_class() { - try { - Class c = Object.class; - Method m = c.getMethod("equals", Object.class); - Object rec = new Object(); - Object param = new Object(); - Boolean other = (Boolean) rec.equals(param); - Boolean equals = (Boolean) m.invoke(rec, param); - } catch (Exception ignore) { - } - } - - public void pass1() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod("getA", new Class[] {}); - @TestReflectSibling1 - Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass1b() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod("getA", (Class[]) null); - @TestReflectSibling1 - Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass2() { - String str = "get" + "A"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {}); - @TestReflectSibling1 - Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass3() { - String str = "get"; - str += "A"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {}); - @TestReflectSibling1 - Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass4() { - String str = "setA"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - m.invoke(superClass, val1); - m.invoke(superClass, val2); - } catch (Exception ignore) { - } - } - - public void pass4b() { - String str = "setA"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - // - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, int.class); - m.invoke(superClass, val1); - m.invoke(superClass, val2); - } catch (Exception ignore) { - } - } - - @TestReflectBottom SubClass subClass; - - // Test resolution of methods declared in super class - public void pass5() { - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod("getB", new Class[0]); - @TestReflectSibling2 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - // Test resolution of static methods - public void pass6() { - try { - Class c = MethodTest.class; - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", - new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - // Test primitives - public void pass7() { - try { - Class c = MethodTest.class; - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", - new Class[] {int.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - public void pass8() { - String str = "setA"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - m.invoke(superClass, sibling1); - } catch (Exception ignore) { - } - } - - public void pass9() { - String str = "getA"; - if (true) { - str = "getB"; - } - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[0]); - @TestReflectTop Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - // Test getClass() - public void pass10() { - SuperClass inst = new SubClass(); - try { - Class c = inst.getClass(); - Method m = c.getMethod("getA", new Class[0]); - @TestReflectSibling1 Object o = m.invoke(inst, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass11() { - try { - Class c = this.getClass(); - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", - new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - public void pass11b() { - try { - Class c = getClass(); - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", - new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - // Test .class on inner class - public void pass12() { - try { - Class c = SuperClass.class; - Method m = c.getMethod("getA", new Class[0]); - @TestReflectSibling1 - Object o = - m.invoke( - new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); - } catch (Exception ignore) { - } - } - - boolean flag = false; - - // Test lub of return types - public void testLubReturnPass() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m; - if (flag) { - m = c.getMethod("getA", new Class[0]); - } else { - m = c.getMethod("getB", new Class[0]); - } - @TestReflectTop - Object o = - m.invoke( - new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); - } catch (Exception ignore) { - } - } - - public void testLubReturnFail() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m; - if (flag) { - m = c.getMethod("getA", new Class[0]); - } else { - m = c.getMethod("getB", new Class[0]); - } - @TestReflectBottom - Object o = - // :: error: (assignment.type.incompatible) - m.invoke( - new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); - } catch (Exception ignore) { - } - } - - public void test() {} - - public void fail1() { - try { - Class c = MethodTest.class; - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", - new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - Object o = m.invoke(null, sibling1); - } catch (Exception ignore) { - } - } - - // Test unresolvable methods - public void fail2(String str) { - try { - Class c = Class.forName(str); - Method m = c.getMethod("getA", new Class[] {Integer.class}); - // :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void fail3() { - String str = "setB"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, sibling1); - } catch (Exception ignore) { - } - } - - public void fail4() { - String str = "setA"; - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, new Object[] {sibling2}); - } catch (Exception ignore) { - } - } - - public void fail5() { - String str = "setAB"; - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[] {Integer.class, Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, new Object[] {sibling1, sibling2}); - } catch (Exception ignore) { - } - } - - public void fail6() { - String str = "setA"; - if (true) { - str = "setB"; - } - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, new Object[] {sibling1}); - } catch (Exception ignore) { - } - } - - public void fail7() { - // :: warning: (cast.unsafe.constructor.invocation) - @TestReflectSibling2 MethodTest inst = new @TestReflectSibling2 MethodTest(); - try { - Class c = MethodTest.class; - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", - new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(inst, sibling2); - } catch (Exception ignore) { - } - } - - // Test method call that cannot be uniquely resolved - public void fail8() { - try { - Class c = SuperClass.class; - Method m = c.getMethod("setC", new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - Object o = m.invoke(new SuperClass(), new Object[] {sibling2}); - } catch (Exception ignore) { - } - } - - public void bug() { - String str = "setA"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Object[] args = new Object[] {val1}; - try { - // - Class c = Class.forName("MethodTest$SuperClass"); - - Method m = c.getMethod(str, int.class); - // This error is a bug. - // See DefaultReflectionResolver.resolveMethodCall(...) - // for details. - // :: error: (argument.type.incompatible) - m.invoke(this, args); - } catch (Exception ignore) { - } - } - - public void bug2() { - String str = "setAB"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling2 int val2 = sibling2; - - Object[] args = new Object[] {val1, val2}; - try { - // - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, int.class, int.class); - // This error is a bug. - // See DefaultReflectionResolver.resolveMethodCall(...) - // for details. - // :: error: (argument.type.incompatible) - m.invoke(this, args); - } catch (Exception ignore) { - } - } - - public static @TestReflectSibling1 int convertTestReflectSibling2ToTestReflectSibling1( - @TestReflectSibling2 int a) { - return (@TestReflectSibling1 int) 1; - } - - // TODO: Does the testing framework somehow support the compilation of - // multiple files at the same time? - private class SubClass extends SuperClass {} - - private class SuperClass { - private @TestReflectSibling1 int a; - private @TestReflectSibling2 int b; - private @TestReflectSibling1 Integer c; - - public SuperClass() { - this.a = sibling1; - this.b = sibling2; - } - - public @TestReflectSibling1 int getA() { - return a; - } - - public void setA(@TestReflectSibling1 int a) { - this.a = a; - } - - public @TestReflectSibling2 int getB() { - return b; - } - - public void setB(@TestReflectSibling2 int b) { - this.b = b; - } - - public void setAB(@TestReflectSibling1 int a, @TestReflectSibling2 int b) { - this.a = a; - this.b = b; - } - - public void setC(@TestReflectSibling1 int c) { - this.c = c; - } - - public void setC(@TestReflectSibling1 Integer c) { - this.c = c; - } + @TestReflectSibling1 int sibling1; + @TestReflectSibling2 int sibling2; + @TestReflectBottom SuperClass superClass; + + public void real_class() { + try { + Class c = Object.class; + Method m = c.getMethod("equals", Object.class); + Object rec = new Object(); + Object param = new Object(); + Boolean other = (Boolean) rec.equals(param); + Boolean equals = (Boolean) m.invoke(rec, param); + } catch (Exception ignore) { + } + } + + public void pass1() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod("getA", new Class[] {}); + @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass1b() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod("getA", (Class[]) null); + @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass2() { + String str = "get" + "A"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {}); + @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass3() { + String str = "get"; + str += "A"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {}); + @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass4() { + String str = "setA"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + m.invoke(superClass, val1); + m.invoke(superClass, val2); + } catch (Exception ignore) { + } + } + + public void pass4b() { + String str = "setA"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + // + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, int.class); + m.invoke(superClass, val1); + m.invoke(superClass, val2); + } catch (Exception ignore) { + } + } + + @TestReflectBottom SubClass subClass; + + // Test resolution of methods declared in super class + public void pass5() { + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod("getB", new Class[0]); + @TestReflectSibling2 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + // Test resolution of static methods + public void pass6() { + try { + Class c = MethodTest.class; + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + // Test primitives + public void pass7() { + try { + Class c = MethodTest.class; + Method m = + c.getMethod("convertTestReflectSibling2ToTestReflectSibling1", new Class[] {int.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + public void pass8() { + String str = "setA"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + m.invoke(superClass, sibling1); + } catch (Exception ignore) { + } + } + + public void pass9() { + String str = "getA"; + if (true) { + str = "getB"; + } + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[0]); + @TestReflectTop Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + // Test getClass() + public void pass10() { + SuperClass inst = new SubClass(); + try { + Class c = inst.getClass(); + Method m = c.getMethod("getA", new Class[0]); + @TestReflectSibling1 Object o = m.invoke(inst, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass11() { + try { + Class c = this.getClass(); + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + public void pass11b() { + try { + Class c = getClass(); + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + // Test .class on inner class + public void pass12() { + try { + Class c = SuperClass.class; + Method m = c.getMethod("getA", new Class[0]); + @TestReflectSibling1 + Object o = m.invoke(new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); + } catch (Exception ignore) { + } + } + + boolean flag = false; + + // Test lub of return types + public void testLubReturnPass() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m; + if (flag) { + m = c.getMethod("getA", new Class[0]); + } else { + m = c.getMethod("getB", new Class[0]); + } + @TestReflectTop + Object o = m.invoke(new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); + } catch (Exception ignore) { + } + } + + public void testLubReturnFail() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m; + if (flag) { + m = c.getMethod("getA", new Class[0]); + } else { + m = c.getMethod("getB", new Class[0]); + } + @TestReflectBottom + Object o = + // :: error: (assignment.type.incompatible) + m.invoke(new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); + } catch (Exception ignore) { + } + } + + public void test() {} + + public void fail1() { + try { + Class c = MethodTest.class; + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + Object o = m.invoke(null, sibling1); + } catch (Exception ignore) { + } + } + + // Test unresolvable methods + public void fail2(String str) { + try { + Class c = Class.forName(str); + Method m = c.getMethod("getA", new Class[] {Integer.class}); + // :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void fail3() { + String str = "setB"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, sibling1); + } catch (Exception ignore) { + } + } + + public void fail4() { + String str = "setA"; + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, new Object[] {sibling2}); + } catch (Exception ignore) { + } + } + + public void fail5() { + String str = "setAB"; + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[] {Integer.class, Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, new Object[] {sibling1, sibling2}); + } catch (Exception ignore) { + } + } + + public void fail6() { + String str = "setA"; + if (true) { + str = "setB"; + } + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, new Object[] {sibling1}); + } catch (Exception ignore) { + } + } + + public void fail7() { + // :: warning: (cast.unsafe.constructor.invocation) + @TestReflectSibling2 MethodTest inst = new @TestReflectSibling2 MethodTest(); + try { + Class c = MethodTest.class; + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(inst, sibling2); + } catch (Exception ignore) { + } + } + + // Test method call that cannot be uniquely resolved + public void fail8() { + try { + Class c = SuperClass.class; + Method m = c.getMethod("setC", new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + Object o = m.invoke(new SuperClass(), new Object[] {sibling2}); + } catch (Exception ignore) { + } + } + + public void bug() { + String str = "setA"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Object[] args = new Object[] {val1}; + try { + // + Class c = Class.forName("MethodTest$SuperClass"); + + Method m = c.getMethod(str, int.class); + // This error is a bug. + // See DefaultReflectionResolver.resolveMethodCall(...) + // for details. + // :: error: (argument.type.incompatible) + m.invoke(this, args); + } catch (Exception ignore) { + } + } + + public void bug2() { + String str = "setAB"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling2 int val2 = sibling2; + + Object[] args = new Object[] {val1, val2}; + try { + // + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, int.class, int.class); + // This error is a bug. + // See DefaultReflectionResolver.resolveMethodCall(...) + // for details. + // :: error: (argument.type.incompatible) + m.invoke(this, args); + } catch (Exception ignore) { + } + } + + public static @TestReflectSibling1 int convertTestReflectSibling2ToTestReflectSibling1( + @TestReflectSibling2 int a) { + return (@TestReflectSibling1 int) 1; + } + + // TODO: Does the testing framework somehow support the compilation of + // multiple files at the same time? + private class SubClass extends SuperClass {} + + private class SuperClass { + private @TestReflectSibling1 int a; + private @TestReflectSibling2 int b; + private @TestReflectSibling1 Integer c; + + public SuperClass() { + this.a = sibling1; + this.b = sibling2; + } + + public @TestReflectSibling1 int getA() { + return a; + } + + public void setA(@TestReflectSibling1 int a) { + this.a = a; + } + + public @TestReflectSibling2 int getB() { + return b; + } + + public void setB(@TestReflectSibling2 int b) { + this.b = b; + } + + public void setAB(@TestReflectSibling1 int a, @TestReflectSibling2 int b) { + this.a = a; + this.b = b; + } + + public void setC(@TestReflectSibling1 int c) { + this.c = c; + } + + public void setC(@TestReflectSibling1 Integer c) { + this.c = c; } + } } diff --git a/framework/tests/reflection/ReflectionConstructorTest.java b/framework/tests/reflection/ReflectionConstructorTest.java index 250283dc9d3..e0e33f00bce 100644 --- a/framework/tests/reflection/ReflectionConstructorTest.java +++ b/framework/tests/reflection/ReflectionConstructorTest.java @@ -1,71 +1,70 @@ +import java.lang.reflect.Constructor; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling1; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling2; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectTop; -import java.lang.reflect.Constructor; - public class ReflectionConstructorTest { - @TestReflectSibling1 int sibling1; - @TestReflectSibling2 int sibling2; + @TestReflectSibling1 int sibling1; + @TestReflectSibling2 int sibling2; - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - public @TestReflectSibling1 ReflectionConstructorTest(@TestReflectSibling1 int a) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + public @TestReflectSibling1 ReflectionConstructorTest(@TestReflectSibling1 int a) {} - // :: warning: (inconsistent.constructor.type) - public @TestReflectSibling2 ReflectionConstructorTest( - // :: error: (super.invocation.invalid) - @TestReflectSibling2 int a, @TestReflectSibling2 int b) {} + // :: warning: (inconsistent.constructor.type) + public @TestReflectSibling2 ReflectionConstructorTest( + // :: error: (super.invocation.invalid) + @TestReflectSibling2 int a, @TestReflectSibling2 int b) {} - public void pass1() { - try { - Class c = Class.forName("ReflectionConstructorTest"); - Constructor init = c.getConstructor(new Class[] {Integer.class}); - @TestReflectSibling1 int i = sibling1; - @TestReflectSibling1 Object o = init.newInstance(i); - } catch (Exception ignore) { - } + public void pass1() { + try { + Class c = Class.forName("ReflectionConstructorTest"); + Constructor init = c.getConstructor(new Class[] {Integer.class}); + @TestReflectSibling1 int i = sibling1; + @TestReflectSibling1 Object o = init.newInstance(i); + } catch (Exception ignore) { } + } - public void pass2() { - try { - Class c = Class.forName("ReflectionConstructorTest"); - Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); - @TestReflectSibling2 int a = sibling2; - int b = a; - @TestReflectTop Object inst = init.newInstance(a, b); - } catch (Exception ignore) { - } + public void pass2() { + try { + Class c = Class.forName("ReflectionConstructorTest"); + Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); + @TestReflectSibling2 int a = sibling2; + int b = a; + @TestReflectTop Object inst = init.newInstance(a, b); + } catch (Exception ignore) { } + } - public void fail1() { - try { - Class c = ReflectionConstructorTest.class; - Constructor init = c.getConstructor(new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - Object o = init.newInstance(sibling2); - } catch (Exception ignore) { - } + public void fail1() { + try { + Class c = ReflectionConstructorTest.class; + Constructor init = c.getConstructor(new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + Object o = init.newInstance(sibling2); + } catch (Exception ignore) { } + } - public void fail2() { - try { - Class c = ReflectionConstructorTest.class; - Constructor init = c.getConstructor(new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object o = init.newInstance(new Object[] {sibling2}); - } catch (Exception ignore) { - } + public void fail2() { + try { + Class c = ReflectionConstructorTest.class; + Constructor init = c.getConstructor(new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object o = init.newInstance(new Object[] {sibling2}); + } catch (Exception ignore) { } + } - public void fail3() { - try { - Class c = Class.forName("ReflectionConstructorTest"); - Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); - @TestReflectSibling2 int a = sibling2; - @TestReflectSibling1 int b = sibling1; - // :: error: (argument.type.incompatible) - @TestReflectSibling2 Object inst = init.newInstance(a, b); - } catch (Exception ignore) { - } + public void fail3() { + try { + Class c = Class.forName("ReflectionConstructorTest"); + Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); + @TestReflectSibling2 int a = sibling2; + @TestReflectSibling1 int b = sibling1; + // :: error: (argument.type.incompatible) + @TestReflectSibling2 Object inst = init.newInstance(a, b); + } catch (Exception ignore) { } + } } diff --git a/framework/tests/report/Accesses.java b/framework/tests/report/Accesses.java index 38825288d48..15385c603a1 100644 --- a/framework/tests/report/Accesses.java +++ b/framework/tests/report/Accesses.java @@ -1,68 +1,68 @@ import org.checkerframework.common.util.report.qual.*; public class Accesses { - class Demo { - @ReportReadWrite Object read; + class Demo { + @ReportReadWrite Object read; - @ReportWrite Object write; + @ReportWrite Object write; - @ReportCall - Object foo(Object p) { - return null; - } - - void implicitRead() { - // :: error: (fieldreadwrite) - Object o = read; - // A read counts as access - // :: error: (fieldreadwrite) - read = null; - // :: error: (fieldreadwrite) - read.toString(); - } - - void implicitWrite() { - Object o = write; - // :: error: (fieldwrite) - write = null; - write.toString(); - } - - void implicitMethod() { - // :: error: (methodcall) - foo(null); - // :: error: (methodcall) - equals(foo(null)); - } + @ReportCall + Object foo(Object p) { + return null; } - void accessesRead(Demo d) { - // :: error: (fieldreadwrite) - Object o = d.read; - // A read counts as access - // :: error: (fieldreadwrite) - d.read = null; - // :: error: (fieldreadwrite) - d.read.toString(); + void implicitRead() { + // :: error: (fieldreadwrite) + Object o = read; + // A read counts as access + // :: error: (fieldreadwrite) + read = null; + // :: error: (fieldreadwrite) + read.toString(); } - void accessesWrite(Demo d) { - Object o = d.write; - // :: error: (fieldwrite) - d.write = null; - d.write.toString(); + void implicitWrite() { + Object o = write; + // :: error: (fieldwrite) + write = null; + write.toString(); } - void accessesMethod(Demo d) { - // :: error: (methodcall) - d.foo(null); - // :: error: (methodcall) - d.equals(d.foo(null)); + void implicitMethod() { + // :: error: (methodcall) + foo(null); + // :: error: (methodcall) + equals(foo(null)); } + } - Object[] array = new Object[] {1, 2, 3}; + void accessesRead(Demo d) { + // :: error: (fieldreadwrite) + Object o = d.read; + // A read counts as access + // :: error: (fieldreadwrite) + d.read = null; + // :: error: (fieldreadwrite) + d.read.toString(); + } - void accessArray() { - array[0] = 1; - } + void accessesWrite(Demo d) { + Object o = d.write; + // :: error: (fieldwrite) + d.write = null; + d.write.toString(); + } + + void accessesMethod(Demo d) { + // :: error: (methodcall) + d.foo(null); + // :: error: (methodcall) + d.equals(d.foo(null)); + } + + Object[] array = new Object[] {1, 2, 3}; + + void accessArray() { + array[0] = 1; + } } diff --git a/framework/tests/report/CallOverrides.java b/framework/tests/report/CallOverrides.java index 823c204d16d..d50e2891015 100644 --- a/framework/tests/report/CallOverrides.java +++ b/framework/tests/report/CallOverrides.java @@ -1,33 +1,33 @@ import org.checkerframework.common.util.report.qual.*; public class CallOverrides { - class A { - void m() {} - } + class A { + void m() {} + } - class B extends A { - @ReportCall - void m() {} - } + class B extends A { + @ReportCall + void m() {} + } - class C extends B {} + class C extends B {} - void test() { - C c = new C(); + void test() { + C c = new C(); - // :: error: (methodcall) - c.m(); + // :: error: (methodcall) + c.m(); - B b = c; + B b = c; - // :: error: (methodcall) - b.m(); + // :: error: (methodcall) + b.m(); - A a = c; + A a = c; - // This call is not reported, because we statically - // don't know that one of the subtypes has the ReportCall - // annotation. - a.m(); - } + // This call is not reported, because we statically + // don't know that one of the subtypes has the ReportCall + // annotation. + a.m(); + } } diff --git a/framework/tests/report/Creation.java b/framework/tests/report/Creation.java index 5aa9c816973..e5ce5f0d980 100644 --- a/framework/tests/report/Creation.java +++ b/framework/tests/report/Creation.java @@ -1,35 +1,35 @@ import org.checkerframework.common.util.report.qual.*; public class Creation { - class TestOne { - TestOne() {} - - @ReportCreation - TestOne(int i) {} - } + class TestOne { + TestOne() {} @ReportCreation - class TestAll { - TestAll() {} + TestOne(int i) {} + } + + @ReportCreation + class TestAll { + TestAll() {} - TestAll(int i) {} - } + TestAll(int i) {} + } - void test() { - // :: error: (creation) - new TestAll(); - // :: error: (creation) - new TestAll(4); + void test() { + // :: error: (creation) + new TestAll(); + // :: error: (creation) + new TestAll(4); - new TestOne(); - // :: error: (creation) - new TestOne(4); - } + new TestOne(); + // :: error: (creation) + new TestOne(4); + } - class TestSub extends TestAll {} + class TestSub extends TestAll {} - void testSub() { - // :: error: (creation) - new TestSub(); - } + void testSub() { + // :: error: (creation) + new TestSub(); + } } diff --git a/framework/tests/report/Inherit.java b/framework/tests/report/Inherit.java index bdf8876267a..f413bb51c45 100644 --- a/framework/tests/report/Inherit.java +++ b/framework/tests/report/Inherit.java @@ -1,14 +1,14 @@ import org.checkerframework.common.util.report.qual.*; public class Inherit { - @ReportInherit - interface A {} + @ReportInherit + interface A {} - class B {} + class B {} - // :: error: (inherit) - class C extends B implements A {} + // :: error: (inherit) + class C extends B implements A {} - // :: error: (inherit) - class D extends C {} + // :: error: (inherit) + class D extends C {} } diff --git a/framework/tests/report/Interface.java b/framework/tests/report/Interface.java index 397450a8eff..4475f986129 100644 --- a/framework/tests/report/Interface.java +++ b/framework/tests/report/Interface.java @@ -5,38 +5,38 @@ import org.checkerframework.common.util.report.qual.*; public class Interface { - interface A { - @ReportCall - boolean equals(Object o); - - @ReportCall - void mine(); - } - - class B implements A { - public void mine() {} - } - - interface C extends A {} - - void foo(A a, B b, C c, Object o) { - // :: error: (methodcall) - if (a.equals(o)) {} - // :: error: (methodcall) - if (b.equals(o)) {} - // :: error: (methodcall) - if (c.equals(o)) {} - - // Don't report this call. - if (o.equals(a)) {} - } - - void bar(A a, B b, C c, Object o) { - // :: error: (methodcall) - a.mine(); - // :: error: (methodcall) - b.mine(); - // :: error: (methodcall) - c.mine(); - } + interface A { + @ReportCall + boolean equals(Object o); + + @ReportCall + void mine(); + } + + class B implements A { + public void mine() {} + } + + interface C extends A {} + + void foo(A a, B b, C c, Object o) { + // :: error: (methodcall) + if (a.equals(o)) {} + // :: error: (methodcall) + if (b.equals(o)) {} + // :: error: (methodcall) + if (c.equals(o)) {} + + // Don't report this call. + if (o.equals(a)) {} + } + + void bar(A a, B b, C c, Object o) { + // :: error: (methodcall) + a.mine(); + // :: error: (methodcall) + b.mine(); + // :: error: (methodcall) + c.mine(); + } } diff --git a/framework/tests/report/Overrides.java b/framework/tests/report/Overrides.java index eaaac3eeccc..e018c69e44b 100644 --- a/framework/tests/report/Overrides.java +++ b/framework/tests/report/Overrides.java @@ -1,26 +1,26 @@ import org.checkerframework.common.util.report.qual.*; public class Overrides { - class A { - void m() {} - } + class A { + void m() {} + } - class B extends A { - @ReportOverride - void m() {} - } + class B extends A { + @ReportOverride + void m() {} + } - class C extends B { - // :: error: (override) - void m() {} - } + class C extends B { + // :: error: (override) + void m() {} + } - // No explicit override -> no message. - class D extends B {} + // No explicit override -> no message. + class D extends B {} - class E extends A { - // Overrides method on same level as B.m - // -> no message. - void m() {} - } + class E extends A { + // Overrides method on same level as B.m + // -> no message. + void m() {} + } } diff --git a/framework/tests/report/Package.java b/framework/tests/report/Package.java index 801755f5b17..3fdd14d92e7 100644 --- a/framework/tests/report/Package.java +++ b/framework/tests/report/Package.java @@ -5,45 +5,45 @@ // :: error: (usage) public class Package extends PatternSyntaxException { - public Package(String desc, String regex, int index) { - // :: error: (usage) - super(desc, regex, index); - } + public Package(String desc, String regex, int index) { + // :: error: (usage) + super(desc, regex, index); + } - @Override - @org.checkerframework.dataflow.qual.Pure - public String getPattern() { - // :: error: (usage) - return super.getPattern(); - } + @Override + @org.checkerframework.dataflow.qual.Pure + public String getPattern() { + // :: error: (usage) + return super.getPattern(); + } + // :: error: (usage) + void m(Pattern p) { + // Access to a constant. // :: error: (usage) - void m(Pattern p) { - // Access to a constant. - // :: error: (usage) - int i = Pattern.CANON_EQ; + int i = Pattern.CANON_EQ; - // Use of inherited method. - // :: error: (usage) - String msg = getMessage(); + // Use of inherited method. + // :: error: (usage) + String msg = getMessage(); - // No report for use of overridden method - - // we get a message when we call super in the overriding method. - // TODO: Would we want "transitive" behavior? I.e. a few levels higher - // in the inheritance hierarchy we could see the class to report. - String pat = this.getPattern(); + // No report for use of overridden method - + // we get a message when we call super in the overriding method. + // TODO: Would we want "transitive" behavior? I.e. a few levels higher + // in the inheritance hierarchy we could see the class to report. + String pat = this.getPattern(); - try { - // :: error: (usage) - p.compile("test((("); - } catch (Package pe) { - // We don't look at supertypes of the types we analyze. - // TODO: Should we? - System.out.println("OK!"); - // :: error: (usage) - } catch (PatternSyntaxException pse) { - // We do get a report for direct uses. - System.out.println("Ha!"); - } + try { + // :: error: (usage) + p.compile("test((("); + } catch (Package pe) { + // We don't look at supertypes of the types we analyze. + // TODO: Should we? + System.out.println("OK!"); + // :: error: (usage) + } catch (PatternSyntaxException pse) { + // We do get a report for direct uses. + System.out.println("Ha!"); } + } } diff --git a/framework/tests/report/TestStub.java b/framework/tests/report/TestStub.java index 452e40ab09f..72493853557 100644 --- a/framework/tests/report/TestStub.java +++ b/framework/tests/report/TestStub.java @@ -1,9 +1,9 @@ public class TestStub { - void demo() { - try { - // :: error: (methodcall) - Class.forName("Evil"); - } catch (Exception e) { - } + void demo() { + try { + // :: error: (methodcall) + Class.forName("Evil"); + } catch (Exception e) { } + } } diff --git a/framework/tests/reportmodifiers/TestModifiers.java b/framework/tests/reportmodifiers/TestModifiers.java index b253d9d7ebe..f645645aa39 100644 --- a/framework/tests/reportmodifiers/TestModifiers.java +++ b/framework/tests/reportmodifiers/TestModifiers.java @@ -3,10 +3,10 @@ * org.checkerframework.checker/tests/src/tests/ReportModifiers.java */ public class TestModifiers { - void test() { - class Inner { - // :: error: (Modifier.native) - native void bad(); - } + void test() { + class Inner { + // :: error: (Modifier.native) + native void bad(); } + } } diff --git a/framework/tests/reporttreekinds/TestTreeKinds.java b/framework/tests/reporttreekinds/TestTreeKinds.java index c58e465b86b..dcf18aeb01f 100644 --- a/framework/tests/reporttreekinds/TestTreeKinds.java +++ b/framework/tests/reporttreekinds/TestTreeKinds.java @@ -3,9 +3,9 @@ * org.checkerframework.checker/tests/src/tests/ReportTreeKindsTest.java */ public class TestTreeKinds { - void test(boolean a, boolean b) { - // :: error: (Tree.Kind.WHILE_LOOP) :: error: (Tree.Kind.CONDITIONAL_AND) - while (a && b) {} - if (b) {} - } + void test(boolean a, boolean b) { + // :: error: (Tree.Kind.WHILE_LOOP) :: error: (Tree.Kind.CONDITIONAL_AND) + while (a && b) {} + if (b) {} + } } diff --git a/framework/tests/returnsreceiver/GenericReturn.java b/framework/tests/returnsreceiver/GenericReturn.java index 88325c3c277..6262bd9672a 100644 --- a/framework/tests/returnsreceiver/GenericReturn.java +++ b/framework/tests/returnsreceiver/GenericReturn.java @@ -2,32 +2,32 @@ public class GenericReturn { - abstract static class Builder> { - abstract @This B setFoo(String foo); - - @SuppressWarnings("unchecked") - @This B retThis() { - return (@This B) this; - } - - @This B dontRetThis() { - // :: error: return.type.incompatible - return null; - } + abstract static class Builder> { + abstract @This B setFoo(String foo); + + @SuppressWarnings("unchecked") + @This B retThis() { + return (@This B) this; + } + + @This B dontRetThis() { + // :: error: return.type.incompatible + return null; } + } - static class Builder1 extends Builder { + static class Builder1 extends Builder { - @This Builder1 setFoo(String foo) { - return this; - } + @This Builder1 setFoo(String foo) { + return this; } + } - static class Builder2 extends Builder { + static class Builder2 extends Builder { - @This Builder2 setFoo(String foo) { - // :: error: return.type.incompatible - return null; - } + @This Builder2 setFoo(String foo) { + // :: error: return.type.incompatible + return null; } + } } diff --git a/framework/tests/returnsreceiver/MethodRef.java b/framework/tests/returnsreceiver/MethodRef.java index 1ca14bf6bdd..95b2a1871d5 100644 --- a/framework/tests/returnsreceiver/MethodRef.java +++ b/framework/tests/returnsreceiver/MethodRef.java @@ -2,25 +2,25 @@ public class MethodRef { - @This MethodRef set(Object o) { - return this; - } + @This MethodRef set(Object o) { + return this; + } - interface Setter { - @This Object consume(Object p); - } + interface Setter { + @This Object consume(Object p); + } - // :: error: methodref.receiver.bound.invalid - Setter co = this::set; + // :: error: methodref.receiver.bound.invalid + Setter co = this::set; - void doNothing(@This MethodRef this) {} + void doNothing(@This MethodRef this) {} - interface Fun { - void run(@This Fun this); - } + interface Fun { + void run(@This Fun this); + } - // The error here is a false positive, due to - // https://github.com/typetools/checker-framework/issues/2931 - // :: error: methodref.receiver.bound.invalid - Fun f = this::doNothing; + // The error here is a false positive, due to + // https://github.com/typetools/checker-framework/issues/2931 + // :: error: methodref.receiver.bound.invalid + Fun f = this::doNothing; } diff --git a/framework/tests/returnsreceiver/NullsAndGenerics.java b/framework/tests/returnsreceiver/NullsAndGenerics.java index ffe2ac22c7f..6ddceec0288 100644 --- a/framework/tests/returnsreceiver/NullsAndGenerics.java +++ b/framework/tests/returnsreceiver/NullsAndGenerics.java @@ -2,33 +2,33 @@ public class NullsAndGenerics { - private boolean enableProtoAnnotations; + private boolean enableProtoAnnotations; - @SuppressWarnings("unchecked") - private T getProtoExtension( - E element, GeneratedExtension extension) { - // Use this method as the chokepoint for all field annotations processing, so we can - // toggle on/off annotations processing in one place. - if (!enableProtoAnnotations) { - return null; - } - return (T) element.getOptionFields().get(extension.getDescriptor()); + @SuppressWarnings("unchecked") + private T getProtoExtension( + E element, GeneratedExtension extension) { + // Use this method as the chokepoint for all field annotations processing, so we can + // toggle on/off annotations processing in one place. + if (!enableProtoAnnotations) { + return null; } + return (T) element.getOptionFields().get(extension.getDescriptor()); + } - // stubs of relevant classes - private class Message {} + // stubs of relevant classes + private class Message {} - private class ProtoElement { - public Map getOptionFields() { - return null; - } + private class ProtoElement { + public Map getOptionFields() { + return null; } + } - private class FieldDescriptor {} + private class FieldDescriptor {} - private class GeneratedExtension { - public FieldDescriptor getDescriptor() { - return null; - } + private class GeneratedExtension { + public FieldDescriptor getDescriptor() { + return null; } + } } diff --git a/framework/tests/returnsreceiver/OverrideTest.java b/framework/tests/returnsreceiver/OverrideTest.java index 7e7d01adcca..43b726072e8 100644 --- a/framework/tests/returnsreceiver/OverrideTest.java +++ b/framework/tests/returnsreceiver/OverrideTest.java @@ -3,39 +3,39 @@ // Test basic subtyping relationships for the Returns Receiver Checker. public class OverrideTest { - static class Super { + static class Super { - @This Super retThis() { - return this; - } + @This Super retThis() { + return this; + } + + Super retWhatever() { + return null; + } + } + + static class Sub extends Super { - Super retWhatever() { - return null; - } + @Override + // :: error: override.return.invalid + Super retThis() { + return null; } - static class Sub extends Super { - - @Override - // :: error: override.return.invalid - Super retThis() { - return null; - } - - @Override - // we do not support this case for now; would need to write explicit @This on receiver in - // superclass - // :: error: override.receiver.invalid - @This Super retWhatever() { - return this; - } + @Override + // we do not support this case for now; would need to write explicit @This on receiver in + // superclass + // :: error: override.receiver.invalid + @This Super retWhatever() { + return this; } + } - static class Sub2 extends Super { + static class Sub2 extends Super { - @Override - @This Sub2 retThis() { - return this; - } + @Override + @This Sub2 retThis() { + return this; } + } } diff --git a/framework/tests/returnsreceiver/SimpleTest.java b/framework/tests/returnsreceiver/SimpleTest.java index 29c448e9afb..46eeb557131 100644 --- a/framework/tests/returnsreceiver/SimpleTest.java +++ b/framework/tests/returnsreceiver/SimpleTest.java @@ -3,66 +3,66 @@ // Test basic subtyping relationships for the Returns Receiver Checker. public class SimpleTest { - @This SimpleTest retNull() { - // :: error: return.type.incompatible - return null; - } + @This SimpleTest retNull() { + // :: error: return.type.incompatible + return null; + } - @This SimpleTest retThis() { - return this; - } + @This SimpleTest retThis() { + return this; + } - @This SimpleTest retThisWrapper(@UnknownThis SimpleTest other, boolean flag) { - if (flag) { - // :: error: return.type.incompatible - return other.retThis(); - } else { - return this.retThis(); - } + @This SimpleTest retThisWrapper(@UnknownThis SimpleTest other, boolean flag) { + if (flag) { + // :: error: return.type.incompatible + return other.retThis(); + } else { + return this.retThis(); } + } - @This SimpleTest retLocalThis() { - SimpleTest x = this; - return x; - } + @This SimpleTest retLocalThis() { + SimpleTest x = this; + return x; + } - @This SimpleTest retNewLocal() { - SimpleTest x = new SimpleTest(); - // :: error: return.type.incompatible - return x; - } + @This SimpleTest retNewLocal() { + SimpleTest x = new SimpleTest(); + // :: error: return.type.incompatible + return x; + } - // :: error: type.invalid.this.location - @This SimpleTest thisOnParam(@This SimpleTest x) { - return x; - } + // :: error: type.invalid.this.location + @This SimpleTest thisOnParam(@This SimpleTest x) { + return x; + } - void thisOnLocal() { - // :: error: type.invalid.this.location - // :: error: assignment.type.incompatible - @This SimpleTest x = new SimpleTest(); + void thisOnLocal() { + // :: error: type.invalid.this.location + // :: error: assignment.type.incompatible + @This SimpleTest x = new SimpleTest(); - // :: error: type.invalid.this.location - // :: error: type.argument.type.incompatible - java.util.List<@This String> l = null; - } + // :: error: type.invalid.this.location + // :: error: type.argument.type.incompatible + java.util.List<@This String> l = null; + } - // can write @This on receiver - void thisOnReceiver(@This SimpleTest this) {} + // can write @This on receiver + void thisOnReceiver(@This SimpleTest this) {} - // :: error: type.invalid.this.location :: error: invalid.polymorphic.qualifier.use - @This Object f; + // :: error: type.invalid.this.location :: error: invalid.polymorphic.qualifier.use + @This Object f; - interface I { + interface I { - Object foo(); + Object foo(); - SimpleTest.@This I setBar(); - } + SimpleTest.@This I setBar(); + } - // :: error: type.invalid.this.location - static @This Object thisOnStatic() { - // :: error: return.type.incompatible - return new Object(); - } + // :: error: type.invalid.this.location + static @This Object thisOnStatic() { + // :: error: return.type.incompatible + return new Object(); + } } diff --git a/framework/tests/returnsreceiver/SubtypingTest.java b/framework/tests/returnsreceiver/SubtypingTest.java index 37c92cc9bb9..d22a371b7e3 100644 --- a/framework/tests/returnsreceiver/SubtypingTest.java +++ b/framework/tests/returnsreceiver/SubtypingTest.java @@ -2,11 +2,11 @@ // Test basic subtyping relationships for the Returns Receiver Checker. public class SubtypingTest { - void allSubtypingRelationships(@UnknownThis int x, @BottomThis int y) { - @UnknownThis int a = x; - @UnknownThis int b = y; - // :: error: assignment.type.incompatible - @BottomThis int c = x; // expected error on this line - @BottomThis int d = y; - } + void allSubtypingRelationships(@UnknownThis int x, @BottomThis int y) { + @UnknownThis int a = x; + @UnknownThis int b = y; + // :: error: assignment.type.incompatible + @BottomThis int c = x; // expected error on this line + @BottomThis int d = y; + } } diff --git a/framework/tests/returnsreceiverautovalue/Animal.java b/framework/tests/returnsreceiverautovalue/Animal.java index 7a6081a8298..806c5552354 100644 --- a/framework/tests/returnsreceiverautovalue/Animal.java +++ b/framework/tests/returnsreceiverautovalue/Animal.java @@ -1,5 +1,4 @@ import com.google.auto.value.AutoValue; - import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; @@ -9,67 +8,67 @@ */ @AutoValue abstract class Animal { - abstract String name(); - - abstract @Nullable String habitat(); - - abstract int numberOfLegs(); - - static Builder builder() { - return new AutoValue_Animal.Builder(); - } + abstract String name(); - @AutoValue.Builder - abstract static class Builder { + abstract @Nullable String habitat(); - abstract Builder setName(String value); + abstract int numberOfLegs(); - abstract Builder setNumberOfLegs(int value); + static Builder builder() { + return new AutoValue_Animal.Builder(); + } - abstract Builder setHabitat(String value); + @AutoValue.Builder + abstract static class Builder { - abstract Animal build(); + abstract Builder setName(String value); - // wrapper methods to ensure @This annotations are getting added properly - @This Builder wrapperSetName() { - return setName("dummy"); - } + abstract Builder setNumberOfLegs(int value); - @This Builder wrapperSetNumberOfLegs() { - return setNumberOfLegs(3); - } + abstract Builder setHabitat(String value); - @This Builder wrapperSetHabitat() { - return setHabitat("dummy"); - } - } - - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - b.build(); - } - - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.build(); - } + abstract Animal build(); - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHabitat("jungle"); - b.build(); + // wrapper methods to ensure @This annotations are getting added properly + @This Builder wrapperSetName() { + return setName("dummy"); } - public static void buildSomethingWrongFluent() { - builder().setName("Frank").build(); + @This Builder wrapperSetNumberOfLegs() { + return setNumberOfLegs(3); } - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).build(); + @This Builder wrapperSetHabitat() { + return setHabitat("dummy"); } + } + + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + b.build(); + } + + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.build(); + } + + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHabitat("jungle"); + b.build(); + } + + public static void buildSomethingWrongFluent() { + builder().setName("Frank").build(); + } + + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).build(); + } } diff --git a/framework/tests/returnsreceiverlombok/BuilderMethodRef.java b/framework/tests/returnsreceiverlombok/BuilderMethodRef.java index 8cdfd4c28a3..ff3807d68f4 100644 --- a/framework/tests/returnsreceiverlombok/BuilderMethodRef.java +++ b/framework/tests/returnsreceiverlombok/BuilderMethodRef.java @@ -1,32 +1,30 @@ +import java.util.Optional; import lombok.Builder; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; - import org.checkerframework.common.returnsreceiver.qual.*; -import java.util.Optional; - @Builder @Accessors(fluent = true) public class BuilderMethodRef { - @Getter @Setter @lombok.NonNull String foo; - @Getter @Setter Object bar; + @Getter @Setter @lombok.NonNull String foo; + @Getter @Setter Object bar; - public static void test(Optional opt) { - BuilderMethodRefBuilder b = builder().foo("Hello"); - opt.ifPresent(b::bar); - b.build(); - } + public static void test(Optional opt) { + BuilderMethodRefBuilder b = builder().foo("Hello"); + opt.ifPresent(b::bar); + b.build(); + } } class CustomBuilderMethodRefBuilder extends BuilderMethodRef.BuilderMethodRefBuilder { - // wrapper methods to ensure @This annotations are getting added properly - BuilderMethodRef.@This BuilderMethodRefBuilder wrapperFoo() { - return foo("dummy"); - } + // wrapper methods to ensure @This annotations are getting added properly + BuilderMethodRef.@This BuilderMethodRefBuilder wrapperFoo() { + return foo("dummy"); + } - BuilderMethodRef.@This BuilderMethodRefBuilder wrapperBar() { - return bar(new Object()); - } + BuilderMethodRef.@This BuilderMethodRefBuilder wrapperBar() { + return bar(new Object()); + } } diff --git a/framework/tests/returnsreceiverlombok/BuilderTest.java b/framework/tests/returnsreceiverlombok/BuilderTest.java index 3368d7de93e..4e18078386c 100644 --- a/framework/tests/returnsreceiverlombok/BuilderTest.java +++ b/framework/tests/returnsreceiverlombok/BuilderTest.java @@ -3,41 +3,40 @@ import lombok.NonNull; import lombok.Setter; import lombok.experimental.Accessors; - import org.checkerframework.common.returnsreceiver.qual.*; @Builder @Accessors(fluent = true) public class BuilderTest { - @Getter @Setter private Integer x; - @Getter @Setter @NonNull private Integer y; - @Getter @Setter @NonNull private Integer z; + @Getter @Setter private Integer x; + @Getter @Setter @NonNull private Integer y; + @Getter @Setter @NonNull private Integer z; - public static void test_simplePattern() { - BuilderTest.builder().x(0).y(0).build(); - BuilderTest.builder().y(0).build(); - BuilderTest.builder().y(0).z(5).build(); - } + public static void test_simplePattern() { + BuilderTest.builder().x(0).y(0).build(); + BuilderTest.builder().y(0).build(); + BuilderTest.builder().y(0).z(5).build(); + } - public static void test_builderVar() { - final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); - goodBuilder.x(0); - goodBuilder.y(0); - goodBuilder.build(); - } + public static void test_builderVar() { + final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); + goodBuilder.x(0); + goodBuilder.y(0); + goodBuilder.build(); + } } class CustomBuilderTestBuilder extends BuilderTest.BuilderTestBuilder { - // wrapper methods to ensure @This annotations are getting added properly - BuilderTest.@This BuilderTestBuilder wrapperX() { - return x(0); - } + // wrapper methods to ensure @This annotations are getting added properly + BuilderTest.@This BuilderTestBuilder wrapperX() { + return x(0); + } - BuilderTest.@This BuilderTestBuilder wrapperY() { - return y(1); - } + BuilderTest.@This BuilderTestBuilder wrapperY() { + return y(1); + } - BuilderTest.@This BuilderTestBuilder wrapperZ() { - return z(2); - } + BuilderTest.@This BuilderTestBuilder wrapperZ() { + return z(2); + } } diff --git a/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java b/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java index a222f9f4298..684317a590e 100644 --- a/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java +++ b/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java @@ -2,87 +2,87 @@ public class StringPatternsUsage { - void requiresA(@PatternA String arg) {} + void requiresA(@PatternA String arg) {} - void requiresB(@PatternB String arg) {} + void requiresB(@PatternB String arg) {} - void requiresC(@PatternC String arg) {} + void requiresC(@PatternC String arg) {} - void requiresAB(@PatternAB String arg) {} + void requiresAB(@PatternAB String arg) {} - void requiresBC(@PatternBC String arg) {} + void requiresBC(@PatternBC String arg) {} - void requiresAC(@PatternAC String arg) {} + void requiresAC(@PatternAC String arg) {} - void requiresAny(String arg) {} + void requiresAny(String arg) {} - void m() { + void m() { - String a = "A"; - String b = "B"; - String c = "C"; - String d = "D"; - String e = ""; + String a = "A"; + String b = "B"; + String c = "C"; + String d = "D"; + String e = ""; - requiresA(a); - // :: error: (argument.type.incompatible) - requiresB(a); - // :: error: (argument.type.incompatible) - requiresC(a); - requiresAB(a); - // :: error: (argument.type.incompatible) - requiresBC(a); - requiresAC(a); - requiresAny(a); + requiresA(a); + // :: error: (argument.type.incompatible) + requiresB(a); + // :: error: (argument.type.incompatible) + requiresC(a); + requiresAB(a); + // :: error: (argument.type.incompatible) + requiresBC(a); + requiresAC(a); + requiresAny(a); - // :: error: (argument.type.incompatible) - requiresA(b); - requiresB(b); - // :: error: (argument.type.incompatible) - requiresC(b); - requiresAB(b); - requiresBC(b); - // :: error: (argument.type.incompatible) - requiresAC(b); - requiresAny(b); + // :: error: (argument.type.incompatible) + requiresA(b); + requiresB(b); + // :: error: (argument.type.incompatible) + requiresC(b); + requiresAB(b); + requiresBC(b); + // :: error: (argument.type.incompatible) + requiresAC(b); + requiresAny(b); - // :: error: (argument.type.incompatible) - requiresA(c); - // :: error: (argument.type.incompatible) - requiresB(c); - requiresC(c); - // :: error: (argument.type.incompatible) - requiresAB(c); - requiresBC(c); - requiresAC(c); - requiresAny(c); + // :: error: (argument.type.incompatible) + requiresA(c); + // :: error: (argument.type.incompatible) + requiresB(c); + requiresC(c); + // :: error: (argument.type.incompatible) + requiresAB(c); + requiresBC(c); + requiresAC(c); + requiresAny(c); - // :: error: (argument.type.incompatible) - requiresA(d); - // :: error: (argument.type.incompatible) - requiresB(d); - // :: error: (argument.type.incompatible) - requiresC(d); - // :: error: (argument.type.incompatible) - requiresAB(d); - // :: error: (argument.type.incompatible) - requiresBC(d); - // :: error: (argument.type.incompatible) - requiresAC(d); - requiresAny(d); + // :: error: (argument.type.incompatible) + requiresA(d); + // :: error: (argument.type.incompatible) + requiresB(d); + // :: error: (argument.type.incompatible) + requiresC(d); + // :: error: (argument.type.incompatible) + requiresAB(d); + // :: error: (argument.type.incompatible) + requiresBC(d); + // :: error: (argument.type.incompatible) + requiresAC(d); + requiresAny(d); - // :: error: (argument.type.incompatible) - requiresA(e); - // :: error: (argument.type.incompatible) - requiresB(e); - // :: error: (argument.type.incompatible) - requiresC(e); - // :: error: (argument.type.incompatible) - requiresAB(e); - // :: error: (argument.type.incompatible) - requiresBC(e); - // :: error: (argument.type.incompatible) - requiresAC(e); - requiresAny(e); - } + // :: error: (argument.type.incompatible) + requiresA(e); + // :: error: (argument.type.incompatible) + requiresB(e); + // :: error: (argument.type.incompatible) + requiresC(e); + // :: error: (argument.type.incompatible) + requiresAB(e); + // :: error: (argument.type.incompatible) + requiresBC(e); + // :: error: (argument.type.incompatible) + requiresAC(e); + requiresAny(e); + } } diff --git a/framework/tests/subtyping/InvariantArrays.java b/framework/tests/subtyping/InvariantArrays.java index c652d3b9628..38cd7b42a6f 100644 --- a/framework/tests/subtyping/InvariantArrays.java +++ b/framework/tests/subtyping/InvariantArrays.java @@ -1,54 +1,53 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.LinkedList; import java.util.List; +import org.checkerframework.framework.testchecker.util.*; public class InvariantArrays { - Object[] oa; - @Encrypted Object[] eoa; + Object[] oa; + @Encrypted Object[] eoa; - String[] sa; - @Encrypted String[] esa; + String[] sa; + @Encrypted String[] esa; - void tests() { - // TODOINVARR:: error: (assignment.type.incompatible) - oa = eoa; - // This error only occurs with the Encrypted type system; - // other type systems don't suffer an error here. - // :: error: (assignment.type.incompatible) - eoa = oa; - // TODOINVARR:: error: (assignment.type.incompatible) - oa = esa; - // OK - oa = sa; - eoa = esa; - } + void tests() { + // TODOINVARR:: error: (assignment.type.incompatible) + oa = eoa; + // This error only occurs with the Encrypted type system; + // other type systems don't suffer an error here. + // :: error: (assignment.type.incompatible) + eoa = oa; + // TODOINVARR:: error: (assignment.type.incompatible) + oa = esa; + // OK + oa = sa; + eoa = esa; + } - List[] loa; - LinkedList[] llra; - List[] leoa; - LinkedList[] llera; - @Encrypted List[] eloa; - @Encrypted LinkedList[] ellra; - @Encrypted List[] eleoa; - @Encrypted LinkedList[] ellera; + List[] loa; + LinkedList[] llra; + List[] leoa; + LinkedList[] llera; + @Encrypted List[] eloa; + @Encrypted LinkedList[] ellra; + @Encrypted List[] eleoa; + @Encrypted LinkedList[] ellera; - void genericTests() { - // OK - loa = llra; - loa = leoa; - loa = llera; - eloa = ellra; - leoa = llera; - eloa = ellera; + void genericTests() { + // OK + loa = llra; + loa = leoa; + loa = llera; + eloa = ellra; + leoa = llera; + eloa = ellera; - // TODOINVARR:: error: (assignment.type.incompatible) - loa = eloa; - // TODOINVARR:: error: (assignment.type.incompatible) - loa = ellra; - // :: error: (assignment.type.incompatible) - eleoa = eloa; - // TODOINVARR:: error: (assignment.type.incompatible) - leoa = eleoa; - } + // TODOINVARR:: error: (assignment.type.incompatible) + loa = eloa; + // TODOINVARR:: error: (assignment.type.incompatible) + loa = ellra; + // :: error: (assignment.type.incompatible) + eleoa = eloa; + // TODOINVARR:: error: (assignment.type.incompatible) + leoa = eleoa; + } } diff --git a/framework/tests/subtyping/Poly.java b/framework/tests/subtyping/Poly.java index 3b572cd24a5..b369f9a181b 100644 --- a/framework/tests/subtyping/Poly.java +++ b/framework/tests/subtyping/Poly.java @@ -1,89 +1,88 @@ -import org.checkerframework.framework.testchecker.util.*; - import java.util.HashMap; import java.util.List; import java.util.Map; +import org.checkerframework.framework.testchecker.util.*; public class Poly { - void test() { - - @Encrypted String s = encrypt("as0d78f9(*#4j"); - String t = "foo"; - - @Encrypted String x1 = id(s); // valid - // :: error: (assignment.type.incompatible) - @Encrypted String x2 = id(t); // error - String x3 = id(s); // valid - String x4 = id(t); // valid - - @Encrypted String y01 = combine(s, s); // valid - // :: error: (assignment.type.incompatible) - @Encrypted String y02 = combine(s, t); // error - // :: error: (assignment.type.incompatible) - @Encrypted String y03 = combine(t, t); // error - - String y11 = combine(s, s); // valid - String y12 = combine(s, t); // valid - String y13 = combine(t, t); // valid - } - - @PolyEncrypted String id(@PolyEncrypted String s) { - return s; - } - - @PolyEncrypted String combine(@PolyEncrypted String s, @PolyEncrypted String t) { - // :: error: (argument.type.incompatible) - sendOverNet(s); // error - return s; - } - - void sendOverNet(@Encrypted String msg) {} - - List<@PolyEncrypted String> duplicate(@PolyEncrypted String s) { - return null; + void test() { + + @Encrypted String s = encrypt("as0d78f9(*#4j"); + String t = "foo"; + + @Encrypted String x1 = id(s); // valid + // :: error: (assignment.type.incompatible) + @Encrypted String x2 = id(t); // error + String x3 = id(s); // valid + String x4 = id(t); // valid + + @Encrypted String y01 = combine(s, s); // valid + // :: error: (assignment.type.incompatible) + @Encrypted String y02 = combine(s, t); // error + // :: error: (assignment.type.incompatible) + @Encrypted String y03 = combine(t, t); // error + + String y11 = combine(s, s); // valid + String y12 = combine(s, t); // valid + String y13 = combine(t, t); // valid + } + + @PolyEncrypted String id(@PolyEncrypted String s) { + return s; + } + + @PolyEncrypted String combine(@PolyEncrypted String s, @PolyEncrypted String t) { + // :: error: (argument.type.incompatible) + sendOverNet(s); // error + return s; + } + + void sendOverNet(@Encrypted String msg) {} + + List<@PolyEncrypted String> duplicate(@PolyEncrypted String s) { + return null; + } + + @PolyEncrypted String[] duplicateAsArray(@PolyEncrypted String s) { + return null; + } + + void test2() { + @Encrypted String s = encrypt("p9aS*7dfa0w9e84r"); + List<@Encrypted String> lst = duplicate(s); + @Encrypted String[] arr = duplicateAsArray(s); + } + + @PolyEncrypted String substitute(Map map) { + return encrypt(null); + } + + @PolyEncrypted String substituteSuper(Map map) { + return encrypt(null); + } + + void test3() { + // :: error: (assignment.type.incompatible) + @Encrypted String s = substitute(new HashMap()); + @Encrypted String t = substitute(new HashMap()); + + // :: error: (assignment.type.incompatible) + @Encrypted String q = substituteSuper(new HashMap()); + @Encrypted String r = substituteSuper(new HashMap()); + } + + // Test assignment to poly + @PolyEncrypted String test4(@PolyEncrypted String s) { + if (s == null) { + return encrypt(null); // valid + } else { + // :: error: (return.type.incompatible) + return "m"; // invalid } + } - @PolyEncrypted String[] duplicateAsArray(@PolyEncrypted String s) { - return null; - } - - void test2() { - @Encrypted String s = encrypt("p9aS*7dfa0w9e84r"); - List<@Encrypted String> lst = duplicate(s); - @Encrypted String[] arr = duplicateAsArray(s); - } - - @PolyEncrypted String substitute(Map map) { - return encrypt(null); - } - - @PolyEncrypted String substituteSuper(Map map) { - return encrypt(null); - } - - void test3() { - // :: error: (assignment.type.incompatible) - @Encrypted String s = substitute(new HashMap()); - @Encrypted String t = substitute(new HashMap()); - - // :: error: (assignment.type.incompatible) - @Encrypted String q = substituteSuper(new HashMap()); - @Encrypted String r = substituteSuper(new HashMap()); - } - - // Test assignment to poly - @PolyEncrypted String test4(@PolyEncrypted String s) { - if (s == null) { - return encrypt(null); // valid - } else { - // :: error: (return.type.incompatible) - return "m"; // invalid - } - } - - @SuppressWarnings("encrypted") - static @Encrypted String encrypt(String s) { - return (@Encrypted String) s; - } + @SuppressWarnings("encrypted") + static @Encrypted String encrypt(String s) { + return (@Encrypted String) s; + } } diff --git a/framework/tests/subtyping/Simple.java b/framework/tests/subtyping/Simple.java index ac0a4249da6..47480a4c3dc 100644 --- a/framework/tests/subtyping/Simple.java +++ b/framework/tests/subtyping/Simple.java @@ -1,35 +1,34 @@ -import org.checkerframework.framework.testchecker.util.Encrypted; - import java.util.LinkedList; import java.util.List; +import org.checkerframework.framework.testchecker.util.Encrypted; abstract class BasicFunctionality { - @Encrypted String encrypt(String s) { - byte[] b = s.getBytes(); - for (int i = 0; i < b.length; b[i++]++) {} - // :: warning: (cast.unsafe) - return (@Encrypted String) new String(b); - } - - abstract void sendOverTheInternet(@Encrypted String s); + @Encrypted String encrypt(String s) { + byte[] b = s.getBytes(); + for (int i = 0; i < b.length; b[i++]++) {} + // :: warning: (cast.unsafe) + return (@Encrypted String) new String(b); + } - void test() { - @Encrypted String s = encrypt("foo"); // valid - sendOverTheInternet(s); // valid + abstract void sendOverTheInternet(@Encrypted String s); - String t = encrypt("bar"); // valid (subtype) - sendOverTheInternet(t); // valid (flow) + void test() { + @Encrypted String s = encrypt("foo"); // valid + sendOverTheInternet(s); // valid - List<@Encrypted String> lst = new LinkedList<>(); - lst.add(s); - lst.add(t); + String t = encrypt("bar"); // valid (subtype) + sendOverTheInternet(t); // valid (flow) - for (@Encrypted String str : lst) { - sendOverTheInternet(str); - } + List<@Encrypted String> lst = new LinkedList<>(); + lst.add(s); + lst.add(t); - // for (String str : lst) - // sendOverTheInternet(str); // should be valid! + for (@Encrypted String str : lst) { + sendOverTheInternet(str); } + + // for (String str : lst) + // sendOverTheInternet(str); // should be valid! + } } diff --git a/framework/tests/subtyping/ThisType.java b/framework/tests/subtyping/ThisType.java index a4e253ca36e..89cd2c5ad4c 100644 --- a/framework/tests/subtyping/ThisType.java +++ b/framework/tests/subtyping/ThisType.java @@ -1,16 +1,16 @@ import org.checkerframework.framework.testchecker.util.*; public class ThisType { - void t1(@Encrypted ThisType this) { - @Encrypted ThisType l1 = this; - ThisType l2 = this; - // Type of l2 is refined by flow -> legal - l1 = l2; - } + void t1(@Encrypted ThisType this) { + @Encrypted ThisType l1 = this; + ThisType l2 = this; + // Type of l2 is refined by flow -> legal + l1 = l2; + } - void t2(ThisType this) { - ThisType l1 = this; - // :: error: (assignment.type.incompatible) - @Encrypted ThisType l2 = this; - } + void t2(ThisType this) { + ThisType l1 = this; + // :: error: (assignment.type.incompatible) + @Encrypted ThisType l2 = this; + } } diff --git a/framework/tests/subtyping/ThrowCatch.java b/framework/tests/subtyping/ThrowCatch.java index b5331d3f78b..71a0402b525 100644 --- a/framework/tests/subtyping/ThrowCatch.java +++ b/framework/tests/subtyping/ThrowCatch.java @@ -8,34 +8,34 @@ */ abstract class ThrowCatch { - void throwsNoncritical() throws Exception { - throw new Exception(); - } + void throwsNoncritical() throws Exception { + throw new Exception(); + } - void throwsCritical() throws @Critical Exception { - throw new @Critical Exception(); - } + void throwsCritical() throws @Critical Exception { + throw new @Critical Exception(); + } - void catches() { - try { - throwsNoncritical(); - } catch (Exception e) { - } + void catches() { + try { + throwsNoncritical(); + } catch (Exception e) { + } - try { - throwsNoncritical(); - // :: error: (type.incompatible) - } catch (@Critical Exception e) { - } + try { + throwsNoncritical(); + // :: error: (type.incompatible) + } catch (@Critical Exception e) { + } - try { - throwsCritical(); - } catch (Exception e) { - } + try { + throwsCritical(); + } catch (Exception e) { + } - try { - throwsCritical(); - } catch (@Critical Exception e) { - } + try { + throwsCritical(); + } catch (@Critical Exception e) { } + } } diff --git a/framework/tests/subtyping/UnusedTypes.java b/framework/tests/subtyping/UnusedTypes.java index 640e6702a30..dee5a71e31a 100644 --- a/framework/tests/subtyping/UnusedTypes.java +++ b/framework/tests/subtyping/UnusedTypes.java @@ -1,22 +1,21 @@ -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.Unused; - import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.Unused; // This test case is quite meaningless, as it's not run with the // Nullness Checker. See nullness/UnusedNullness.java instead. public class UnusedTypes { - @SubtypeOf({}) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public @interface Prototype {} + @SubtypeOf({}) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public @interface Prototype {} - @Unused(when = Prototype.class) - public Object ppt; + @Unused(when = Prototype.class) + public Object ppt; - protected @Prototype UnusedTypes() { - // It should be legal to initialize an unused field to null in the constructor. - this.ppt = null; - } + protected @Prototype UnusedTypes() { + // It should be legal to initialize an unused field to null in the constructor. + this.ppt = null; + } } diff --git a/framework/tests/typedeclbounds/BooleanExpression.java b/framework/tests/typedeclbounds/BooleanExpression.java index 60a85726054..a74c69881c2 100644 --- a/framework/tests/typedeclbounds/BooleanExpression.java +++ b/framework/tests/typedeclbounds/BooleanExpression.java @@ -3,51 +3,51 @@ // Test the @S2 upperbound for boolean expression class BooleanExpression { - boolean equal; - boolean notEqual; - boolean lessThan; - boolean greaterThan; - boolean lessThanEqual; - boolean greaterThanEqual; - - // ensures @Top is not within the bounds for boolean - // :: error: (type.invalid.annotations.on.use) - @Top boolean topBoolean; - - // ensures @S1 is not within the bounds for boolean - // :: error: (type.invalid.annotations.on.use) - @S1 boolean s1Boolean; - - @Bottom boolean bottomBoolean; - - // ensures the @S2 upperbound is applied to binary comparison - void compareTop(@Top Integer x, @Top Integer y) { - equal = x == y; - notEqual = x != y; - lessThan = x < y; - greaterThan = x > y; - lessThanEqual = x <= y; - greaterThanEqual = x >= y; - } - - // ensures the @S2 upperbound is applied to binary comparison - void compareBottom(@Bottom Integer x, @Bottom Integer y) { - equal = x == y; - notEqual = x != y; - lessThan = x < y; - greaterThan = x > y; - lessThanEqual = x <= y; - greaterThanEqual = x >= y; - } - - // ensures the default type is not @Bottom - void assignBottom() { - // :: error: (assignment.type.incompatible) - bottomBoolean = equal; - } - - // ensures the @S2 upperbound is applied to instanceof - static @S2 boolean isNumber(@Top Object obj) { - return obj instanceof Number; - } + boolean equal; + boolean notEqual; + boolean lessThan; + boolean greaterThan; + boolean lessThanEqual; + boolean greaterThanEqual; + + // ensures @Top is not within the bounds for boolean + // :: error: (type.invalid.annotations.on.use) + @Top boolean topBoolean; + + // ensures @S1 is not within the bounds for boolean + // :: error: (type.invalid.annotations.on.use) + @S1 boolean s1Boolean; + + @Bottom boolean bottomBoolean; + + // ensures the @S2 upperbound is applied to binary comparison + void compareTop(@Top Integer x, @Top Integer y) { + equal = x == y; + notEqual = x != y; + lessThan = x < y; + greaterThan = x > y; + lessThanEqual = x <= y; + greaterThanEqual = x >= y; + } + + // ensures the @S2 upperbound is applied to binary comparison + void compareBottom(@Bottom Integer x, @Bottom Integer y) { + equal = x == y; + notEqual = x != y; + lessThan = x < y; + greaterThan = x > y; + lessThanEqual = x <= y; + greaterThanEqual = x >= y; + } + + // ensures the default type is not @Bottom + void assignBottom() { + // :: error: (assignment.type.incompatible) + bottomBoolean = equal; + } + + // ensures the @S2 upperbound is applied to instanceof + static @S2 boolean isNumber(@Top Object obj) { + return obj instanceof Number; + } } diff --git a/framework/tests/typedeclbounds/StringConcatConversion.java b/framework/tests/typedeclbounds/StringConcatConversion.java index 3a411c6f97d..8b59bed5869 100644 --- a/framework/tests/typedeclbounds/StringConcatConversion.java +++ b/framework/tests/typedeclbounds/StringConcatConversion.java @@ -1,30 +1,29 @@ -import org.checkerframework.framework.testchecker.typedeclbounds.quals.*; - import java.util.ArrayList; import java.util.List; +import org.checkerframework.framework.testchecker.typedeclbounds.quals.*; // Test the @S1 upperbound applied to string conversion public class StringConcatConversion { - @Top List ts = new ArrayList<>(); + @Top List ts = new ArrayList<>(); - // :: error: (type.invalid.annotations.on.use) - @Top String topString; + // :: error: (type.invalid.annotations.on.use) + @Top String topString; - @Bottom String bottomString; + @Bottom String bottomString; - void foo(@Top T topT, @Bottom T bottomT) { - throwException("test normal top to bottom conversion" + ts); - throwException("test type variable" + topT); - throwException("test wildcard" + ts.get(0)); + void foo(@Top T topT, @Bottom T bottomT) { + throwException("test normal top to bottom conversion" + ts); + throwException("test type variable" + topT); + throwException("test wildcard" + ts.get(0)); - // the converted string of topT has type @S1 - // :: error: (compound.assignment.type.incompatible) - bottomString += topT; + // the converted string of topT has type @S1 + // :: error: (compound.assignment.type.incompatible) + bottomString += topT; - // the converted string of bottomT has type @Bottom - bottomString += bottomT; - } + // the converted string of bottomT has type @Bottom + bottomString += bottomT; + } - void throwException(@S1 String s) {} + void throwException(@S1 String s) {} } diff --git a/framework/tests/typedecldefault/BoundsAndDefaults.java b/framework/tests/typedecldefault/BoundsAndDefaults.java index 8efc5ab3846..3bdc026516a 100644 --- a/framework/tests/typedecldefault/BoundsAndDefaults.java +++ b/framework/tests/typedecldefault/BoundsAndDefaults.java @@ -4,28 +4,27 @@ // @TypeDeclDefaultBottom is the default qualifier in hierarchy. @SuppressWarnings("inconsistent.constructor.type") public class BoundsAndDefaults { - static @TypeDeclDefaultMiddle class MiddleClass {} + static @TypeDeclDefaultMiddle class MiddleClass {} - @TypeDeclDefaultBottom MiddleClass method(@TypeDeclDefaultMiddle MiddleClass middle, MiddleClass noAnno) { - noAnno = middle; - // :: error: (return.type.incompatible) - return noAnno; - } + @TypeDeclDefaultBottom MiddleClass method(@TypeDeclDefaultMiddle MiddleClass middle, MiddleClass noAnno) { + noAnno = middle; + // :: error: (return.type.incompatible) + return noAnno; + } - // :: error: (type.invalid.annotations.on.use) - void tops(@TypeDeclDefaultTop MiddleClass invalid) { - @TypeDeclDefaultTop MiddleClass local = null; - } + // :: error: (type.invalid.annotations.on.use) + void tops(@TypeDeclDefaultTop MiddleClass invalid) { + @TypeDeclDefaultTop MiddleClass local = null; + } - @NoDefaultQualifierForUse(TypeDeclDefaultTop.class) - static @TypeDeclDefaultMiddle class MiddleBoundClass { - @TypeDeclDefaultMiddle MiddleBoundClass() {} - } + @NoDefaultQualifierForUse(TypeDeclDefaultTop.class) + static @TypeDeclDefaultMiddle class MiddleBoundClass { + @TypeDeclDefaultMiddle MiddleBoundClass() {} + } - @TypeDeclDefaultBottom MiddleBoundClass method( - @TypeDeclDefaultMiddle MiddleBoundClass middle, MiddleBoundClass noAnno) { - // :: error: (assignment.type.incompatible) - noAnno = middle; - return noAnno; - } + @TypeDeclDefaultBottom MiddleBoundClass method(@TypeDeclDefaultMiddle MiddleBoundClass middle, MiddleBoundClass noAnno) { + // :: error: (assignment.type.incompatible) + noAnno = middle; + return noAnno; + } } diff --git a/framework/tests/typedecldefault/TestDefaultForTypeDecl.java b/framework/tests/typedecldefault/TestDefaultForTypeDecl.java index 35d198995e9..1794da4bf89 100644 --- a/framework/tests/typedecldefault/TestDefaultForTypeDecl.java +++ b/framework/tests/typedecldefault/TestDefaultForTypeDecl.java @@ -5,20 +5,20 @@ // @TypeDeclDefaultTop is the default for type declarations. @NoDefaultQualifierForUse(TypeDeclDefaultTop.class) public @TypeDeclDefaultTop class TestDefaultForTypeDecl { - void test(@TypeDeclDefaultTop TestDefaultForTypeDecl arg) {} + void test(@TypeDeclDefaultTop TestDefaultForTypeDecl arg) {} - void testUnannotated(TestDefaultForTypeDecl arg) {} + void testUnannotated(TestDefaultForTypeDecl arg) {} - void testOtherQual( - @TypeDeclDefaultBottom TestDefaultForTypeDecl arg, TestDefaultForTypeDecl arg1) { - arg = arg1; - } + void testOtherQual( + @TypeDeclDefaultBottom TestDefaultForTypeDecl arg, TestDefaultForTypeDecl arg1) { + arg = arg1; + } - void method() { - Object @TypeDeclDefaultBottom [] object = new Object[] {null}; - this.genericMethod(); - new TestDefaultForTypeDecl() {}; - } + void method() { + Object @TypeDeclDefaultBottom [] object = new Object[] {null}; + this.genericMethod(); + new TestDefaultForTypeDecl() {}; + } - <@TypeDeclDefaultBottom T extends @TypeDeclDefaultBottom Object> void genericMethod() {} + <@TypeDeclDefaultBottom T extends @TypeDeclDefaultBottom Object> void genericMethod() {} } diff --git a/framework/tests/value-ignore-range-overflow/Index117.java b/framework/tests/value-ignore-range-overflow/Index117.java index ace3ee9bf44..5fc0dac2826 100644 --- a/framework/tests/value-ignore-range-overflow/Index117.java +++ b/framework/tests/value-ignore-range-overflow/Index117.java @@ -2,10 +2,10 @@ public class Index117 { - public static void foo(boolean includeIndex, String[] roots) { - @IntRange(from = 2, to = Integer.MAX_VALUE) int x = (includeIndex ? 2 : 1) * roots.length + 2; - @IntRange(from = 2, to = Integer.MAX_VALUE) int y = 2 * roots.length + 2; - @IntRange(from = 2, to = Integer.MAX_VALUE) int z = roots.length + 2; - @IntRange(from = 2, to = 2) int w = 0 + 2; - } + public static void foo(boolean includeIndex, String[] roots) { + @IntRange(from = 2, to = Integer.MAX_VALUE) int x = (includeIndex ? 2 : 1) * roots.length + 2; + @IntRange(from = 2, to = Integer.MAX_VALUE) int y = 2 * roots.length + 2; + @IntRange(from = 2, to = Integer.MAX_VALUE) int z = roots.length + 2; + @IntRange(from = 2, to = 2) int w = 0 + 2; + } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementEq.java b/framework/tests/value-ignore-range-overflow/RefinementEq.java index 73a533c1eed..451f9966787 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementEq.java +++ b/framework/tests/value-ignore-range-overflow/RefinementEq.java @@ -2,27 +2,27 @@ public class RefinementEq { - void test_equal(int a, int j, int s) { + void test_equal(int a, int j, int s) { - if (-1 == a) { - @IntRange(from = -1) int b = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int c = a; - } + if (-1 == a) { + @IntRange(from = -1) int b = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int c = a; + } - if (0 == j) { - @IntRange(from = 0) int k = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int l = j; - } + if (0 == j) { + @IntRange(from = 0) int k = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int l = j; + } - if (1 == s) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; - } + if (1 == s) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; } + } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementGT.java b/framework/tests/value-ignore-range-overflow/RefinementGT.java index 79b7a6695a8..8ddd30c8819 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementGT.java +++ b/framework/tests/value-ignore-range-overflow/RefinementGT.java @@ -2,64 +2,64 @@ public class RefinementGT { - void test_forward(int a, int j, int s) { - /** forwards greater than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (a > -1) { - /** a is NN now */ - @IntRange(from = 0) int b = a; - @IntRange(from = -1) int b1 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int b2 = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (a > -1) { + /** a is NN now */ + @IntRange(from = 0) int b = a; + @IntRange(from = -1) int b1 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int b2 = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int c = a; + } - if (j > 0) { - /** j is POS now */ - @IntRange(from = 1) int k = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = j; - } + if (j > 0) { + /** j is POS now */ + @IntRange(from = 1) int k = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = j; + } - if (s > 1) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; - } + if (s > 1) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; } + } - void test_backwards(int a, int j, int s) { - /** backwards greater than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 > a) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b = a; - } else { - @IntRange(from = -1) int c = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int c1 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int c2 = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 > a) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b = a; + } else { + @IntRange(from = -1) int c = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int c1 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int c2 = a; + } - if (0 > j) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = j; - } else { - @IntRange(from = 0) int l = j; - } + if (0 > j) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = j; + } else { + @IntRange(from = 0) int l = j; + } - if (1 > s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; - } + if (1 > s) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; } + } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementGTE.java b/framework/tests/value-ignore-range-overflow/RefinementGTE.java index 25a65d15f77..944ffa3e9b8 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementGTE.java +++ b/framework/tests/value-ignore-range-overflow/RefinementGTE.java @@ -2,53 +2,53 @@ public class RefinementGTE { - void test_forward(int a, int j, int s) { - /** forwards greater than or equals */ - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int aa = a; - if (a >= -1) { - @IntRange(from = -1) int b = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than or equals */ + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int aa = a; + if (a >= -1) { + @IntRange(from = -1) int b = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int c = a; + } - if (j >= 0) { - @IntRange(from = 0) int k = j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k1 = j; - @IntRange(from = -1) int k2 = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int l = j; - } + if (j >= 0) { + @IntRange(from = 0) int k = j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k1 = j; + @IntRange(from = -1) int k2 = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int l = j; } + } - void test_backwards(int a, int j, int s) { - /** backwards greater than or equal */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 >= a) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int b = a; - } else { - @IntRange(from = 0) int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than or equal */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 >= a) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int b = a; + } else { + @IntRange(from = 0) int c = a; + } - if (0 >= j) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k = j; - } else { - @IntRange(from = 1) int l = j; - @IntRange(from = -1) int l1 = j; - @IntRange(from = 0) int l2 = j; - } + if (0 >= j) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k = j; + } else { + @IntRange(from = 1) int l = j; + @IntRange(from = -1) int l1 = j; + @IntRange(from = 0) int l2 = j; + } - if (1 >= s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; - } + if (1 >= s) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; } + } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementLT.java b/framework/tests/value-ignore-range-overflow/RefinementLT.java index e0044d596dd..353b7957609 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementLT.java +++ b/framework/tests/value-ignore-range-overflow/RefinementLT.java @@ -2,61 +2,61 @@ public class RefinementLT { - void test_backwards(int a, int j, int s) { - /** backwards less than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 < a) { - @IntRange(from = 0) int b = a; - @IntRange(from = -1) int b1 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int b2 = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 < a) { + @IntRange(from = 0) int b = a; + @IntRange(from = -1) int b1 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int b2 = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int c = a; + } - if (0 < j) { - @IntRange(from = 1) int k = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = j; - } + if (0 < j) { + @IntRange(from = 1) int k = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = j; + } - if (1 < s) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; - } + if (1 < s) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; } + } - void test_forwards(int a, int j, int s) { - /** forwards less than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (a < -1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b = a; - } else { - @IntRange(from = -1) int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (a < -1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b = a; + } else { + @IntRange(from = -1) int c = a; + } - if (j < 0) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = j; - } else { - @IntRange(from = 0) int l = j; - @IntRange(from = -1) int l1 = j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l2 = j; - } + if (j < 0) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = j; + } else { + @IntRange(from = 0) int l = j; + @IntRange(from = -1) int l1 = j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l2 = j; + } - if (s < 1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; - } + if (s < 1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; } + } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementLTE.java b/framework/tests/value-ignore-range-overflow/RefinementLTE.java index fa23e930835..fac940caad3 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementLTE.java +++ b/framework/tests/value-ignore-range-overflow/RefinementLTE.java @@ -2,62 +2,62 @@ public class RefinementLTE { - void test_backwards(int a, int j, int s) { - /** backwards less than or equals */ - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int aa = a; - if (-1 <= a) { - @IntRange(from = -1) int b = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than or equals */ + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int aa = a; + if (-1 <= a) { + @IntRange(from = -1) int b = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int c = a; + } - if (0 <= j) { - @IntRange(from = 0) int k = j; - @IntRange(from = -1) int k1 = j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k2 = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int l = j; - } + if (0 <= j) { + @IntRange(from = 0) int k = j; + @IntRange(from = -1) int k1 = j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k2 = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int l = j; + } - if (1 <= s) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; - } + if (1 <= s) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; } + } - void test_forwards(int a, int j, int s) { - /** forwards less than or equal */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (a <= -1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b0 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int b = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int b2 = a; - } else { - @IntRange(from = 0) int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than or equal */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (a <= -1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b0 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int b = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int b2 = a; + } else { + @IntRange(from = 0) int c = a; + } - if (j <= 0) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k = j; - } else { - @IntRange(from = 1) int l = j; - } + if (j <= 0) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k = j; + } else { + @IntRange(from = 1) int l = j; + } - if (s <= 1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; - } + if (s <= 1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; } + } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementNEq.java b/framework/tests/value-ignore-range-overflow/RefinementNEq.java index 2535c55d3dd..8e44cf6e54c 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementNEq.java +++ b/framework/tests/value-ignore-range-overflow/RefinementNEq.java @@ -2,29 +2,29 @@ public class RefinementNEq { - void test_not_equal(int a, int j, int s) { + void test_not_equal(int a, int j, int s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 != a) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b = a; - } else { - @IntRange(from = -1) int c = a; - } + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 != a) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b = a; + } else { + @IntRange(from = -1) int c = a; + } - if (0 != j) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = j; - } else { - @IntRange(from = 0) int l = j; - } + if (0 != j) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = j; + } else { + @IntRange(from = 0) int l = j; + } - if (1 != s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; - } + if (1 != s) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; } + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferAdd.java b/framework/tests/value-ignore-range-overflow/TransferAdd.java index 2df432cb55a..a8d326ea571 100644 --- a/framework/tests/value-ignore-range-overflow/TransferAdd.java +++ b/framework/tests/value-ignore-range-overflow/TransferAdd.java @@ -2,81 +2,81 @@ public class TransferAdd { - void test() { + void test() { - // adding zero and one and two + // adding zero and one and two - int a = -1; + int a = -1; - @IntRange(from = 1) int a1 = a + 2; + @IntRange(from = 1) int a1 = a + 2; - @IntRange(from = 0) int b = a + 1; - @IntRange(from = 0) int c = 1 + a; + @IntRange(from = 0) int b = a + 1; + @IntRange(from = 0) int c = 1 + a; - @IntRange(from = -1) int d = a + 0; - @IntRange(from = -1) int e = 0 + a; + @IntRange(from = -1) int d = a + 0; + @IntRange(from = -1) int e = 0 + a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int f = a + 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int f = a + 1; - @IntRange(from = 0) int g = b + 0; + @IntRange(from = 0) int g = b + 0; - @IntRange(from = 1) int h = b + 1; + @IntRange(from = 1) int h = b + 1; - @IntRange(from = 1) int i = h + 1; - @IntRange(from = 1) int j = h + 0; + @IntRange(from = 1) int i = h + 1; + @IntRange(from = 1) int j = h + 0; - // adding values + // adding values - @IntRange(from = 1) int k = i + j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = b + c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int m = d + c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int n = d + e; + @IntRange(from = 1) int k = i + j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = b + c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int m = d + c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int n = d + e; - @IntRange(from = 1) int o = h + g; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int p = h + d; + @IntRange(from = 1) int o = h + g; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int p = h + d; - @IntRange(from = 0) int q = b + c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int r = q + d; + @IntRange(from = 0) int q = b + c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int r = q + d; - @IntRange(from = 0) int s = k + d; - @IntRange(from = -1) int t = s + d; + @IntRange(from = 0) int s = k + d; + @IntRange(from = -1) int t = s + d; - // increments + // increments - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = b++; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = b++; - @IntRange(from = 1) int u1 = b; + @IntRange(from = 1) int u1 = b; - @IntRange(from = 1) int v = ++c; + @IntRange(from = 1) int v = ++c; - @IntRange(from = 1) int v1 = c; + @IntRange(from = 1) int v1 = c; - int n1p1 = -1, n1p2 = -1; + int n1p1 = -1, n1p2 = -1; - @IntRange(from = 0) int w = ++n1p1; + @IntRange(from = 0) int w = ++n1p1; - @IntRange(from = 0) int w1 = n1p1; + @IntRange(from = 0) int w1 = n1p1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int w2 = n1p1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int w3 = n1p1++; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int w2 = n1p1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int w3 = n1p1++; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int x = n1p2++; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int x = n1p2++; - @IntRange(from = 0) int x1 = n1p2; + @IntRange(from = 0) int x1 = n1p2; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int y = ++d; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int z = e++; - } + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int y = ++d; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int z = e++; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferDivide.java b/framework/tests/value-ignore-range-overflow/TransferDivide.java index 81ee9b1500b..c6c2accc41a 100644 --- a/framework/tests/value-ignore-range-overflow/TransferDivide.java +++ b/framework/tests/value-ignore-range-overflow/TransferDivide.java @@ -2,57 +2,57 @@ public class TransferDivide { - void test() { - int a = -1; - int b = 0; - int c = 1; - int d = 2; - - /** literals */ - @IntRange(from = 1) int e = -1 / -1; - - /** 0 / * -> NN */ - @IntRange(from = 0) int f = 0 / a; - @IntRange(from = 0) int g = 0 / d; - - /** * / 1 -> * */ - @IntRange(from = -1) int h = a / 1; - @IntRange(from = 0) int i = b / 1; - @IntRange(from = 1) int j = c / 1; - @IntRange(from = 1) int k = d / 1; - - /** pos / pos -> nn */ - @IntRange(from = 0) int l = d / c; - @IntRange(from = 0) int m = c / d; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int n = c / d; - - /** nn / pos -> nn */ - @IntRange(from = 0) int o = b / c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int p = b / d; - - /** pos / nn -> nn */ - @IntRange(from = 0) int q = d / l; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int r = c / l; - - /** nn / nn -> nn */ - @IntRange(from = 0) int s = b / q; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = b / q; - - /** n1p / pos -> n1p */ - @IntRange(from = -1) int u = a / d; - @IntRange(from = -1) int v = a / c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int w = a / c; - - /** n1p / nn -> n1p */ - @IntRange(from = -1) int x = a / l; - } - - void testDivideByTwo(@IntRange(from = 0) int x) { - @IntRange(from = 0) int y = x / 2; - } + void test() { + int a = -1; + int b = 0; + int c = 1; + int d = 2; + + /** literals */ + @IntRange(from = 1) int e = -1 / -1; + + /** 0 / * -> NN */ + @IntRange(from = 0) int f = 0 / a; + @IntRange(from = 0) int g = 0 / d; + + /** * / 1 -> * */ + @IntRange(from = -1) int h = a / 1; + @IntRange(from = 0) int i = b / 1; + @IntRange(from = 1) int j = c / 1; + @IntRange(from = 1) int k = d / 1; + + /** pos / pos -> nn */ + @IntRange(from = 0) int l = d / c; + @IntRange(from = 0) int m = c / d; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int n = c / d; + + /** nn / pos -> nn */ + @IntRange(from = 0) int o = b / c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int p = b / d; + + /** pos / nn -> nn */ + @IntRange(from = 0) int q = d / l; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int r = c / l; + + /** nn / nn -> nn */ + @IntRange(from = 0) int s = b / q; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = b / q; + + /** n1p / pos -> n1p */ + @IntRange(from = -1) int u = a / d; + @IntRange(from = -1) int v = a / c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int w = a / c; + + /** n1p / nn -> n1p */ + @IntRange(from = -1) int x = a / l; + } + + void testDivideByTwo(@IntRange(from = 0) int x) { + @IntRange(from = 0) int y = x / 2; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferMod.java b/framework/tests/value-ignore-range-overflow/TransferMod.java index 6bad75f900f..1f4f44cc798 100644 --- a/framework/tests/value-ignore-range-overflow/TransferMod.java +++ b/framework/tests/value-ignore-range-overflow/TransferMod.java @@ -2,30 +2,30 @@ public class TransferMod { - void test() { - int aa = -100; - int a = -1; - int b = 0; - int c = 1; - int d = 2; + void test() { + int aa = -100; + int a = -1; + int b = 0; + int c = 1; + int d = 2; - @IntRange(from = 1) int e = 5 % 3; - @IntRange(from = 0) int f = -100 % 1; + @IntRange(from = 1) int e = 5 % 3; + @IntRange(from = 0) int f = -100 % 1; - @IntRange(from = 0) int g = aa % -1; - @IntRange(from = 0) int h = aa % 1; - @IntRange(from = 0) int i = d % -1; - @IntRange(from = 0) int j = d % 1; + @IntRange(from = 0) int g = aa % -1; + @IntRange(from = 0) int h = aa % 1; + @IntRange(from = 0) int i = d % -1; + @IntRange(from = 0) int j = d % 1; - @IntRange(from = 0) int k = d % c; - @IntRange(from = 0) int l = b % c; - @IntRange(from = 0) int m = c % d; + @IntRange(from = 0) int k = d % c; + @IntRange(from = 0) int l = b % c; + @IntRange(from = 0) int m = c % d; - @IntRange(from = 0) int n = c % a; - @IntRange(from = 0) int o = b % a; + @IntRange(from = 0) int n = c % a; + @IntRange(from = 0) int o = b % a; - @IntRange(from = -1) int p = a % a; - @IntRange(from = -1) int q = a % d; - @IntRange(from = -1) int r = a % c; - } + @IntRange(from = -1) int p = a % a; + @IntRange(from = -1) int q = a % d; + @IntRange(from = -1) int r = a % c; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferSub.java b/framework/tests/value-ignore-range-overflow/TransferSub.java index 93b66f7290a..b1bccc2887a 100644 --- a/framework/tests/value-ignore-range-overflow/TransferSub.java +++ b/framework/tests/value-ignore-range-overflow/TransferSub.java @@ -2,67 +2,67 @@ public class TransferSub { - void test() { - // zero, one, and two - int a = 1; + void test() { + // zero, one, and two + int a = 1; - @IntRange(from = 0) int b = a - 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int c = a - 1; - @IntRange(from = -1) int d = a - 2; + @IntRange(from = 0) int b = a - 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int c = a - 1; + @IntRange(from = -1) int d = a - 2; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int e = a - 2; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int e = a - 2; - @IntRange(from = -1) int f = b - 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int g = b - 1; + @IntRange(from = -1) int f = b - 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int g = b - 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int h = f - 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int h = f - 1; - @IntRange(from = -1) int i = f - 0; - @IntRange(from = 0) int j = b - 0; - @IntRange(from = 1) int k = a - 0; + @IntRange(from = -1) int i = f - 0; + @IntRange(from = 0) int j = b - 0; + @IntRange(from = 1) int k = a - 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = j - 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int m = i - 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = j - 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int m = i - 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int n = a - k; - // this would be an error if the values of b and j (both zero) weren't known at compile time - @IntRange(from = 0) int o = b - j; - /* i and d both have compile time value -1, so this is legal. - The general case of GTEN1 - GTEN1 is not, though. */ - @IntRange(from = -1) int p = i - d; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int n = a - k; + // this would be an error if the values of b and j (both zero) weren't known at compile time + @IntRange(from = 0) int o = b - j; + /* i and d both have compile time value -1, so this is legal. + The general case of GTEN1 - GTEN1 is not, though. */ + @IntRange(from = -1) int p = i - d; - // decrements + // decrements - // :: error: (unary.decrement.type.incompatible) - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int q = --k; // k = 0 + // :: error: (unary.decrement.type.incompatible) + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int q = --k; // k = 0 - // :: error: (unary.decrement.type.incompatible) - @IntRange(from = 0) int r = k--; // after this k = -1 + // :: error: (unary.decrement.type.incompatible) + @IntRange(from = 0) int r = k--; // after this k = -1 - int k1 = 0; - @IntRange(from = 0) int s = k1--; + int k1 = 0; + @IntRange(from = 0) int s = k1--; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int s1 = k1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int s1 = k1; - k1 = 1; - @IntRange(from = 0) int t = --k1; + k1 = 1; + @IntRange(from = 0) int t = --k1; - k1 = 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t1 = --k1; + k1 = 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t1 = --k1; - int u1 = -1; - @IntRange(from = -1) int x = u1--; - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int x1 = u1; - } + int u1 = -1; + @IntRange(from = -1) int x = u1--; + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int x1 = u1; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferTimes.java b/framework/tests/value-ignore-range-overflow/TransferTimes.java index 7654a1782e1..e89ba40fab3 100644 --- a/framework/tests/value-ignore-range-overflow/TransferTimes.java +++ b/framework/tests/value-ignore-range-overflow/TransferTimes.java @@ -2,25 +2,25 @@ public class TransferTimes { - void test() { - int a = 1; - @IntRange(from = 1) int b = a * 1; - @IntRange(from = 1) int c = 1 * a; - @IntRange(from = 0) int d = 0 * a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int e = -1 * a; + void test() { + int a = 1; + @IntRange(from = 1) int b = a * 1; + @IntRange(from = 1) int c = 1 * a; + @IntRange(from = 0) int d = 0 * a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int e = -1 * a; - int g = -1; - @IntRange(from = 0) int h = g * 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int i = g * 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int j = g * a; + int g = -1; + @IntRange(from = 0) int h = g * 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int i = g * 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int j = g * a; - int k = 0; - int l = 1; - @IntRange(from = 1) int m = a * l; - @IntRange(from = 0) int n = k * l; - @IntRange(from = 0) int o = k * k; - } + int k = 0; + int l = 1; + @IntRange(from = 1) int m = a * l; + @IntRange(from = 0) int n = k * l; + @IntRange(from = 0) int o = k * k; + } } diff --git a/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java b/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java index ef9cb5b3458..3406339bdc4 100644 --- a/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java +++ b/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java @@ -2,94 +2,94 @@ public class ValueNoOverflow { - // This file contains a series of simple smoke tests for IntRange and ArrayLenRange with no - // overflow. - - void test_plus(@IntRange(from = 0) int x, @IntRange(from = -1) int z) { - @IntRange(from = 1) int y = x + 1; // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) int w = z + 1; // GTEN1 to NN - } - - void test_minus(@IntRange(to = 0) int x, @IntRange(to = 1) int z) { - @IntRange(to = -1) int y = x - 1; // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) int w = z - 1; // Pos to NN - } - - void test_mult(@IntRange(from = 0) int x, @IntRange(from = 1) int z) { - @IntRange(from = 0) int y = x * z; - @IntRange(from = 1) int w = z * z; - } - - void testLong_plus(@IntRange(from = 0) long x, @IntRange(from = -1) long z) { - @IntRange(from = 1) long y = x + 1; // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) long w = z + 1; // GTEN1 to NN - } - - void testLong_minus(@IntRange(to = 0) long x, @IntRange(to = 1) long z) { - @IntRange(to = -1) long y = x - 1; // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) long w = z - 1; // Pos to NN - } - - void testLong_mult(@IntRange(from = 0) long x, @IntRange(from = 1) long z) { - @IntRange(from = 0) long y = x * z; - @IntRange(from = 1) long w = z * z; - } - - void testShort_plus(@IntRange(from = 0) short x, @IntRange(from = -1) short z) { - @IntRange(from = 1) short y = (short) (x + 1); // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) short w = (short) (z + 1); // GTEN1 to NN - } - - void testShort_minus(@IntRange(to = 0) short x, @IntRange(to = 1) short z) { - @IntRange(to = -1) short y = (short) (x - 1); // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) short w = (short) (z - 1); // Pos to NN - } - - void testShort_mult(@IntRange(from = 0) short x, @IntRange(from = 1) short z) { - @IntRange(from = 0) short y = (short) (x * z); - @IntRange(from = 1) short w = (short) (z * z); - } - - void testChar_plus(@IntRange(from = 0) char x) { - @IntRange(from = 1) char y = (char) (x + 1); // IntRange(from = 0) to IntRange(from = 1) - } - - void testChar_minus(@IntRange(to = 65534) char z) { - @IntRange(to = 65533) char w = (char) (z - 1); // IntRange(to = 65535) to IntRange(to = 65535) - } - - void testChar_mult(@IntRange(from = 0) char x, @IntRange(from = 1) char z) { - @IntRange(from = 0) char y = (char) (x * z); - @IntRange(from = 1) char w = (char) (z * z); - } - - void testByte_plus(@IntRange(from = 0) byte x, @IntRange(from = -1) byte z) { - @IntRange(from = 1) byte y = (byte) (x + 1); // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) byte w = (byte) (z + 1); // GTEN1 to NN - } - - void testByte_minus(@IntRange(to = 0) byte x, @IntRange(to = 1) byte z) { - @IntRange(to = -1) byte y = (byte) (x - 1); // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) byte w = (byte) (z - 1); // Pos to NN - } - - void testByte_mult(@IntRange(from = 0) byte x, @IntRange(from = 1) byte z) { - @IntRange(from = 0) byte y = (byte) (x * z); - @IntRange(from = 1) byte w = (byte) (z * z); - } - - void test_casting(@IntRange(from = 0) int i) { - @IntRange(from = 1, to = ((long) Integer.MAX_VALUE) + 1) - long x = i + 1; - } - - // Include ArrayLenRange tests once ArrayLenRange is merged. - - void arraylenrange_test(int @ArrayLenRange(from = 5) [] a) { - int @ArrayLenRange(from = 7) [] b = new int[a.length + 2]; - } - - void arraylenrange_test2(int @ArrayLenRange(to = 5) [] a) { - int @ArrayLenRange(from = 0, to = 0) [] b = new int[a.length - 5]; - } + // This file contains a series of simple smoke tests for IntRange and ArrayLenRange with no + // overflow. + + void test_plus(@IntRange(from = 0) int x, @IntRange(from = -1) int z) { + @IntRange(from = 1) int y = x + 1; // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) int w = z + 1; // GTEN1 to NN + } + + void test_minus(@IntRange(to = 0) int x, @IntRange(to = 1) int z) { + @IntRange(to = -1) int y = x - 1; // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) int w = z - 1; // Pos to NN + } + + void test_mult(@IntRange(from = 0) int x, @IntRange(from = 1) int z) { + @IntRange(from = 0) int y = x * z; + @IntRange(from = 1) int w = z * z; + } + + void testLong_plus(@IntRange(from = 0) long x, @IntRange(from = -1) long z) { + @IntRange(from = 1) long y = x + 1; // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) long w = z + 1; // GTEN1 to NN + } + + void testLong_minus(@IntRange(to = 0) long x, @IntRange(to = 1) long z) { + @IntRange(to = -1) long y = x - 1; // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) long w = z - 1; // Pos to NN + } + + void testLong_mult(@IntRange(from = 0) long x, @IntRange(from = 1) long z) { + @IntRange(from = 0) long y = x * z; + @IntRange(from = 1) long w = z * z; + } + + void testShort_plus(@IntRange(from = 0) short x, @IntRange(from = -1) short z) { + @IntRange(from = 1) short y = (short) (x + 1); // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) short w = (short) (z + 1); // GTEN1 to NN + } + + void testShort_minus(@IntRange(to = 0) short x, @IntRange(to = 1) short z) { + @IntRange(to = -1) short y = (short) (x - 1); // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) short w = (short) (z - 1); // Pos to NN + } + + void testShort_mult(@IntRange(from = 0) short x, @IntRange(from = 1) short z) { + @IntRange(from = 0) short y = (short) (x * z); + @IntRange(from = 1) short w = (short) (z * z); + } + + void testChar_plus(@IntRange(from = 0) char x) { + @IntRange(from = 1) char y = (char) (x + 1); // IntRange(from = 0) to IntRange(from = 1) + } + + void testChar_minus(@IntRange(to = 65534) char z) { + @IntRange(to = 65533) char w = (char) (z - 1); // IntRange(to = 65535) to IntRange(to = 65535) + } + + void testChar_mult(@IntRange(from = 0) char x, @IntRange(from = 1) char z) { + @IntRange(from = 0) char y = (char) (x * z); + @IntRange(from = 1) char w = (char) (z * z); + } + + void testByte_plus(@IntRange(from = 0) byte x, @IntRange(from = -1) byte z) { + @IntRange(from = 1) byte y = (byte) (x + 1); // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) byte w = (byte) (z + 1); // GTEN1 to NN + } + + void testByte_minus(@IntRange(to = 0) byte x, @IntRange(to = 1) byte z) { + @IntRange(to = -1) byte y = (byte) (x - 1); // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) byte w = (byte) (z - 1); // Pos to NN + } + + void testByte_mult(@IntRange(from = 0) byte x, @IntRange(from = 1) byte z) { + @IntRange(from = 0) byte y = (byte) (x * z); + @IntRange(from = 1) byte w = (byte) (z * z); + } + + void test_casting(@IntRange(from = 0) int i) { + @IntRange(from = 1, to = ((long) Integer.MAX_VALUE) + 1) + long x = i + 1; + } + + // Include ArrayLenRange tests once ArrayLenRange is merged. + + void arraylenrange_test(int @ArrayLenRange(from = 5) [] a) { + int @ArrayLenRange(from = 7) [] b = new int[a.length + 2]; + } + + void arraylenrange_test2(int @ArrayLenRange(to = 5) [] a) { + int @ArrayLenRange(from = 0, to = 0) [] b = new int[a.length - 5]; + } } diff --git a/framework/tests/value-ignore-range-overflow/Widen.java b/framework/tests/value-ignore-range-overflow/Widen.java index 024fbef066b..13bf9fcccec 100644 --- a/framework/tests/value-ignore-range-overflow/Widen.java +++ b/framework/tests/value-ignore-range-overflow/Widen.java @@ -3,22 +3,22 @@ import org.checkerframework.common.value.qual.*; public class Widen { - public class Loops { - void test(int c, int max) { - int decexp = 0; - int seendot = 0; - int i = 0; - while (true) { - if (c == '.' && seendot == 0) { - seendot = 1; - } else if ('0' <= c && c <= '9') { - decexp += seendot; - } + public class Loops { + void test(int c, int max) { + int decexp = 0; + int seendot = 0; + int i = 0; + while (true) { + if (c == '.' && seendot == 0) { + seendot = 1; + } else if ('0' <= c && c <= '9') { + decexp += seendot; + } - if (max < i++) { - break; - } - } + if (max < i++) { + break; } + } } + } } diff --git a/framework/tests/value-non-null-strings-concatenation/Binaries.java b/framework/tests/value-non-null-strings-concatenation/Binaries.java index 35546dc65d2..906327a11ef 100644 --- a/framework/tests/value-non-null-strings-concatenation/Binaries.java +++ b/framework/tests/value-non-null-strings-concatenation/Binaries.java @@ -1,381 +1,380 @@ -import org.checkerframework.common.value.qual.*; - import java.util.BitSet; +import org.checkerframework.common.value.qual.*; public class Binaries { - private BitSet bitmap; - - public void test() { - int length = bitmap.length(); - for (int i = 0, t = 0; i < length; i++) { - t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); - if (i % 8 == 7 || i == length - 1) { - write(t); - t = 0; - } - } + private BitSet bitmap; + + public void test() { + int length = bitmap.length(); + for (int i = 0, t = 0; i < length; i++) { + t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); + if (i % 8 == 7 || i == length - 1) { + write(t); + t = 0; + } } - - void write(int t) {} - - // Test widenedUpperBound is working. - public void loop(int c) { - double v = 0; - int decexp = 0; - int seendot = 0; - while (true) { - if (c == '.' && seendot == 0) seendot = 1; - else if ('0' <= c && c <= '9') { - v = v * 10 + (c - '0'); - decexp += seendot; - } else { - break; - } - } + } + + void write(int t) {} + + // Test widenedUpperBound is working. + public void loop(int c) { + double v = 0; + int decexp = 0; + int seendot = 0; + while (true) { + if (c == '.' && seendot == 0) seendot = 1; + else if ('0' <= c && c <= '9') { + v = v * 10 + (c - '0'); + decexp += seendot; + } else { + break; + } } - - public void testIntRange( - @IntVal({1, 2}) int values, - @IntRange(from = 3, to = 4) int range1, - @IntRange(from = 5, to = 20) int range2, - @BottomVal int bottom, - @UnknownVal int top) { - - /* IntRange + IntRange */ - @IntRange(from = 8, to = 24) int a = range1 + range2; - - /* IntRange * IntVal */ - @IntRange(from = 3, to = 8) int b = values * range1; - - /* IntRange * BottomVal */ - int c = range1 * bottom; - - /* IntRange * UnknownVal */ - @IntRange(from = 0) - // :: error: (assignment.type.incompatible) - int d = range1 + top; + } + + public void testIntRange( + @IntVal({1, 2}) int values, + @IntRange(from = 3, to = 4) int range1, + @IntRange(from = 5, to = 20) int range2, + @BottomVal int bottom, + @UnknownVal int top) { + + /* IntRange + IntRange */ + @IntRange(from = 8, to = 24) int a = range1 + range2; + + /* IntRange * IntVal */ + @IntRange(from = 3, to = 8) int b = values * range1; + + /* IntRange * BottomVal */ + int c = range1 * bottom; + + /* IntRange * UnknownVal */ + @IntRange(from = 0) + // :: error: (assignment.type.incompatible) + int d = range1 + top; + } + + public void add() { + int a = 1; + if (true) { + a = 2; } + @IntVal({3, 4}) int b = a + 2; - public void add() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({3, 4}) int b = a + 2; - - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({3.0, 4.0}) double d = c + 2; - - char e = '1'; - if (true) { - e = '2'; - } - @IntVal({'3', '4'}) char f = (char) (e + 2); - - String g = "A"; - if (true) { - g = "B"; - } - @StringVal({"AC", "BC"}) String h = g + "C"; + double c = 1.0; + if (true) { + c = 2.0; } + @DoubleVal({3.0, 4.0}) double d = c + 2; - public void subtract() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({-1, 0}) int b = a - 2; - - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({-1.0, 0.0}) double d = c - 2; - - char e = '2'; - if (true) { - e = '3'; - } - - @IntVal({'0', '1'}) char f = (char) (e - 2); + char e = '1'; + if (true) { + e = '2'; } + @IntVal({'3', '4'}) char f = (char) (e + 2); - public void multiply() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({2, 4}) int b = a * 2; - - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - - char e = (char) 25; - if (true) { - - e = (char) 26; - } - - @IntVal({'2', '4'}) char f = (char) (e * 2); - - @DoubleVal(0.75) float g = 1 * 0.75f; + String g = "A"; + if (true) { + g = "B"; } + @StringVal({"AC", "BC"}) String h = g + "C"; + } - public void divide() { - int a = 2; - if (true) { - a = 4; - } - @IntVal({1, 2}) int b = a / 2; + public void subtract() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({-1, 0}) int b = a - 2; - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({0.5, 1.0}) double d = c / 2; + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({-1.0, 0.0}) double d = c - 2; - char e = (char) 96; - if (true) { - e = (char) 98; - } + char e = '2'; + if (true) { + e = '3'; + } - @IntVal({'0', '1'}) char f = (char) (e / 2); + @IntVal({'0', '1'}) char f = (char) (e - 2); + } - @IntVal(0) int g = 2 / 3; - @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; - @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; + public void multiply() { + int a = 1; + if (true) { + a = 2; } + @IntVal({2, 4}) int b = a * 2; - public void remainder() { - int a = 4; - if (true) { - a = 5; - } - @IntVal({1, 2}) int b = a % 3; - - double c = 4.0; - if (true) { - c = 5.0; - } - @DoubleVal({1.0, 2.0}) double d = c % 3; + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - char e = (char) 98; - if (true) { - e = (char) 99; - } + char e = (char) 25; + if (true) { - @IntVal({'0', '1'}) char f = (char) (e % 50); + e = (char) 26; } - public boolean flag = true; - - public void and() { - boolean a = true; - if (flag) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a & true; + @IntVal({'2', '4'}) char f = (char) (e * 2); - int c = 4; - if (true) { - c = 5; - } - @IntVal({0, 1}) int d = c & 3; + @DoubleVal(0.75) float g = 1 * 0.75f; + } - char e = (char) 48; - if (true) { + public void divide() { + int a = 2; + if (true) { + a = 4; + } + @IntVal({1, 2}) int b = a / 2; - e = (char) 51; - } + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({0.5, 1.0}) double d = c / 2; - @IntVal({'0', '2'}) char f = (char) (e & 50); + char e = (char) 96; + if (true) { + e = (char) 98; } - public void or() { - boolean a = true; - if (true) { - a = false; - } - // TODO: we could detect this case - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a | true; + @IntVal({'0', '1'}) char f = (char) (e / 2); - int c = 4; - if (true) { - c = 5; - } - @IntVal({7}) int d = c | 3; + @IntVal(0) int g = 2 / 3; + @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; + @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; + } - char e = (char) 48; - if (true) { - e = (char) 51; - } + public void remainder() { + int a = 4; + if (true) { + a = 5; + } + @IntVal({1, 2}) int b = a % 3; - @IntVal({'1', '3'}) char f = (char) (e | 1); + double c = 4.0; + if (true) { + c = 5.0; } + @DoubleVal({1.0, 2.0}) double d = c % 3; - public void xor() { - boolean a = true; - if (true) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a ^ true; + char e = (char) 98; + if (true) { + e = (char) 99; + } - int c = 4; - if (true) { - c = 5; - } - @IntVal({7, 6}) int d = c ^ 3; + @IntVal({'0', '1'}) char f = (char) (e % 50); + } - char e = (char) 48; - if (true) { + public boolean flag = true; - e = (char) 51; - } + public void and() { + boolean a = true; + if (flag) { + a = false; + } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a & true; - @IntVal({'1', '2'}) char f = (char) (e ^ 1); - } - - public void boolAnd() { - @BoolVal({false}) boolean a = true && false; - @BoolVal({true}) boolean b = false || true; - } - - public void conditionals() { - @BoolVal({false}) boolean a = 1.0f == '1'; - @BoolVal({true}) boolean b = 1 != 2.0; - @BoolVal({true}) boolean c = 1 > 0.5; - @BoolVal({true}) boolean d = 1 >= 1.0; - @BoolVal({true}) boolean e = 1 < 1.1f; - @BoolVal({true}) boolean f = (char) 2 <= 2.0; - @IntVal('!') Character BANG = '!'; - @BoolVal(true) boolean g = (BANG == '!'); - char bangChar = '!'; - @BoolVal(true) boolean h = (BANG == bangChar); - - Character bang = '!'; - // Reference equalitiy is used - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean i = (BANG == bang); - } - - public void loop() throws InterruptedException { - int spurious_count = 0; - while (true) { - wait(); - if (System.currentTimeMillis() == 0) { - spurious_count++; - if (spurious_count > 1024) { - break; - } - } - } + int c = 4; + if (true) { + c = 5; } + @IntVal({0, 1}) int d = c & 3; - public void shifts() { - int a = -8; - if (true) { - a = 4; - } - @IntVal({1, -2}) int b = a >> 2; + char e = (char) 48; + if (true) { - int c = 1; - if (true) { - c = 2; - } - @IntVal({4, 8}) int d = c << 2; + e = (char) 51; + } - int e = -8; - if (true) { - e = 4; - } - @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; + @IntVal({'0', '2'}) char f = (char) (e & 50); + } - char g = (char) 24; - if (true) { - g = (char) 25; - } + public void or() { + boolean a = true; + if (true) { + a = false; + } + // TODO: we could detect this case + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a | true; - @IntVal({'0', '2'}) char h = (char) (g << 1); + int c = 4; + if (true) { + c = 5; } + @IntVal({7}) int d = c | 3; - public void chains() { - char a = 2; - int b = 3; - double c = 5; + char e = (char) 48; + if (true) { + e = (char) 51; + } - @DoubleVal({1}) double d = a * b - c; + @IntVal({'1', '3'}) char f = (char) (e | 1); + } - @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; + public void xor() { + boolean a = true; + if (true) { + a = false; } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a ^ true; - public void compareWithNull() { - String s = "1"; - // TODO - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean b = (s != null); + int c = 4; + if (true) { + c = 5; } + @IntVal({7, 6}) int d = c ^ 3; - public void nullConcatenation(@StringVal({"a", "b"}) String arg) { - String n1 = null; - String n2 = "null"; - String k = "const"; + char e = (char) 48; + if (true) { - // @StringVal("nullnull") String a1 = n1 + null; - @StringVal("nullnull") String a2 = n1 + "null"; - // @StringVal("nullnull") String a3 = n1 + n1; - @StringVal("nullnull") String a4 = n1 + n2; - @StringVal("nullconst") String a5 = n1 + k; - @StringVal("nullconst") String a6 = n1 + "const"; - - @StringVal("nullnull") String b1 = n2 + null; - @StringVal("nullnull") String b2 = n2 + "null"; - @StringVal("null") String b3 = n2 + n1; - @StringVal("nullnull") String b4 = n2 + n2; - @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; - @StringVal("nullconst") String b6 = n2 + "const"; + e = (char) 51; + } - @StringVal({"anull", "bnull"}) String c1 = arg + null; - @StringVal({"anull", "bnull"}) String c2 = arg + "null"; - @StringVal({"anull", "bnull"}) String c3 = arg + n1; - @StringVal({"anull", "bnull"}) String c4 = arg + n2; - @StringVal({"aconst", "bconst"}) String c5 = arg + k; - @StringVal({"aconst", "bconst"}) String c6 = arg + "const"; - @StringVal({"a2147483647", "b2147483647"}) String c7 = arg + Integer.MAX_VALUE; + @IntVal({'1', '2'}) char f = (char) (e ^ 1); + } + + public void boolAnd() { + @BoolVal({false}) boolean a = true && false; + @BoolVal({true}) boolean b = false || true; + } + + public void conditionals() { + @BoolVal({false}) boolean a = 1.0f == '1'; + @BoolVal({true}) boolean b = 1 != 2.0; + @BoolVal({true}) boolean c = 1 > 0.5; + @BoolVal({true}) boolean d = 1 >= 1.0; + @BoolVal({true}) boolean e = 1 < 1.1f; + @BoolVal({true}) boolean f = (char) 2 <= 2.0; + @IntVal('!') Character BANG = '!'; + @BoolVal(true) boolean g = (BANG == '!'); + char bangChar = '!'; + @BoolVal(true) boolean h = (BANG == bangChar); + + Character bang = '!'; + // Reference equalitiy is used + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean i = (BANG == bang); + } + + public void loop() throws InterruptedException { + int spurious_count = 0; + while (true) { + wait(); + if (System.currentTimeMillis() == 0) { + spurious_count++; + if (spurious_count > 1024) { + break; + } + } } + } - public void conditionalComparisions() { - @BoolVal(true) boolean a1 = true || false; - @BoolVal(true) boolean a2 = true || true; - @BoolVal(false) boolean a3 = false || false; - @BoolVal(true) boolean a4 = false || true; + public void shifts() { + int a = -8; + if (true) { + a = 4; + } + @IntVal({1, -2}) int b = a >> 2; - @BoolVal(false) boolean a5 = true && false; - @BoolVal(true) boolean a6 = true && true; - @BoolVal(false) boolean a7 = false && false; - @BoolVal(false) boolean a8 = false && true; + int c = 1; + if (true) { + c = 2; + } + @IntVal({4, 8}) int d = c << 2; - boolean unknown = flag ? true : false; - @BoolVal(true) boolean a9 = true || unknown; - @BoolVal(true) boolean a11 = unknown || true; - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean a12 = unknown || false; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a13 = false || unknown; + int e = -8; + if (true) { + e = 4; + } + @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a14 = true && unknown; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a15 = unknown && true; - @BoolVal(false) boolean a16 = unknown && false; - @BoolVal(false) boolean a17 = false && unknown; + char g = (char) 24; + if (true) { + g = (char) 25; } + + @IntVal({'0', '2'}) char h = (char) (g << 1); + } + + public void chains() { + char a = 2; + int b = 3; + double c = 5; + + @DoubleVal({1}) double d = a * b - c; + + @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; + } + + public void compareWithNull() { + String s = "1"; + // TODO + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean b = (s != null); + } + + public void nullConcatenation(@StringVal({"a", "b"}) String arg) { + String n1 = null; + String n2 = "null"; + String k = "const"; + + // @StringVal("nullnull") String a1 = n1 + null; + @StringVal("nullnull") String a2 = n1 + "null"; + // @StringVal("nullnull") String a3 = n1 + n1; + @StringVal("nullnull") String a4 = n1 + n2; + @StringVal("nullconst") String a5 = n1 + k; + @StringVal("nullconst") String a6 = n1 + "const"; + + @StringVal("nullnull") String b1 = n2 + null; + @StringVal("nullnull") String b2 = n2 + "null"; + @StringVal("null") String b3 = n2 + n1; + @StringVal("nullnull") String b4 = n2 + n2; + @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; + @StringVal("nullconst") String b6 = n2 + "const"; + + @StringVal({"anull", "bnull"}) String c1 = arg + null; + @StringVal({"anull", "bnull"}) String c2 = arg + "null"; + @StringVal({"anull", "bnull"}) String c3 = arg + n1; + @StringVal({"anull", "bnull"}) String c4 = arg + n2; + @StringVal({"aconst", "bconst"}) String c5 = arg + k; + @StringVal({"aconst", "bconst"}) String c6 = arg + "const"; + @StringVal({"a2147483647", "b2147483647"}) String c7 = arg + Integer.MAX_VALUE; + } + + public void conditionalComparisions() { + @BoolVal(true) boolean a1 = true || false; + @BoolVal(true) boolean a2 = true || true; + @BoolVal(false) boolean a3 = false || false; + @BoolVal(true) boolean a4 = false || true; + + @BoolVal(false) boolean a5 = true && false; + @BoolVal(true) boolean a6 = true && true; + @BoolVal(false) boolean a7 = false && false; + @BoolVal(false) boolean a8 = false && true; + + boolean unknown = flag ? true : false; + @BoolVal(true) boolean a9 = true || unknown; + @BoolVal(true) boolean a11 = unknown || true; + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean a12 = unknown || false; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a13 = false || unknown; + + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a14 = true && unknown; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a15 = unknown && true; + @BoolVal(false) boolean a16 = unknown && false; + @BoolVal(false) boolean a17 = false && unknown; + } } diff --git a/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java b/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java index 2f83443dcb6..e3ef8b28a68 100644 --- a/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java +++ b/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java @@ -7,60 +7,60 @@ public class CompoundAssignment { - @StringVal("hello") String field; + @StringVal("hello") String field; - public void refinements() { - field = "hello"; - // :: error: (compound.assignment.type.incompatible) - field += method(); - // :: error: (assignment.type.incompatible) - // :: error: (compound.assignment.type.incompatible) - @StringVal("hellohellohello") String test = field += method(); - } + public void refinements() { + field = "hello"; + // :: error: (compound.assignment.type.incompatible) + field += method(); + // :: error: (assignment.type.incompatible) + // :: error: (compound.assignment.type.incompatible) + @StringVal("hellohellohello") String test = field += method(); + } - @StringVal("hello") String method() { - // :: error: (assignment.type.incompatible) - field = "goodbye"; - return "hello"; - } + @StringVal("hello") String method() { + // :: error: (assignment.type.incompatible) + field = "goodbye"; + return "hello"; + } - void value() { - @StringVal("hello") String s = "hello"; - // :: error: (compound.assignment.type.incompatible) - s += "hello"; + void value() { + @StringVal("hello") String s = "hello"; + // :: error: (compound.assignment.type.incompatible) + s += "hello"; - @IntVal(1) int i = 1; - // :: error: (compound.assignment.type.incompatible) - i += 1; + @IntVal(1) int i = 1; + // :: error: (compound.assignment.type.incompatible) + i += 1; - @IntVal(2) int j = 2; - // :: error: (compound.assignment.type.incompatible) - j += 2; + @IntVal(2) int j = 2; + // :: error: (compound.assignment.type.incompatible) + j += 2; - // :: error: (assignment.type.incompatible) - @IntVal(4) int four = j; - } + // :: error: (assignment.type.incompatible) + @IntVal(4) int four = j; + } - void value2() { - @StringVal("hello") String s = "hello"; - // :: error: (assignment.type.incompatible) - s = s + "hello"; + void value2() { + @StringVal("hello") String s = "hello"; + // :: error: (assignment.type.incompatible) + s = s + "hello"; - @IntVal(1) int i = 1; - // :: error: (assignment.type.incompatible) - i = i + 1; - } + @IntVal(1) int i = 1; + // :: error: (assignment.type.incompatible) + i = i + 1; + } - void noErrorCompoundAssignments() { - @IntVal(0) int zero = 0; - zero *= 12; + void noErrorCompoundAssignments() { + @IntVal(0) int zero = 0; + zero *= 12; - @StringVal("null") String s = "null"; - s += ""; - } + @StringVal("null") String s = "null"; + s += ""; + } - void errorCompundAssignments() { - @StringVal("hello") String s = "hello"; - s += ""; - } + void errorCompundAssignments() { + @StringVal("hello") String s = "hello"; + s += ""; + } } diff --git a/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java b/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java index f2741832dc4..88afcb0c5bb 100644 --- a/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java +++ b/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java @@ -7,113 +7,112 @@ public class StringLenConcats { - void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { - @ArrayLen(8) String ab = a + b; - @ArrayLen(7) String bxx = b + "xx"; - } - - void stringLenRangeConcat( - @ArrayLenRange(from = 3, to = 5) String a, - @ArrayLenRange(from = 11, to = 19) String b) { - @ArrayLenRange(from = 14, to = 24) String ab = a + b; - @ArrayLenRange(from = 13, to = 21) String bxx = b + "xx"; - } - - void stringLenLenRangeConcat( - @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { - @ArrayLenRange(from = 13, to = 105) String ab = a + b; - } - - void stringValLenConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLen(11) String d) { - - @ArrayLen(19) String ad = a + d; - @ArrayLen(12) String bd = b + d; - @ArrayLenRange(from = 12, to = 14) String cd = c + d; - } - - void stringValLenRangeConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLenRange(from = 11, to = 19) String d) { - - @ArrayLenRange(from = 19, to = 27) String ad = a + d; - @ArrayLenRange(from = 12, to = 20) String bd = b + d; - @ArrayLenRange(from = 12, to = 22) String cd = c + d; - } - - void tooManyStringValConcat( - @StringVal({"a", "b", "c", "d"}) String a, - @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { - @ArrayLen(2) String aa = a + a; - @ArrayLen(3) String ab = a + b; - } - - void charConversions( - char c, - @IntVal({1, 100, 10000}) char d, - @ArrayLen({100, 200}) String s, - @ArrayLenRange(from = 100, to = 200) String t, - @StringVal({"a", "bb", "ccc", "dddd"}) String u) { - @ArrayLen({101, 201}) String sc = s + c; - @ArrayLen({101, 201}) String sd = s + d; - - @ArrayLenRange(from = 101, to = 201) String tc = t + c; - - @ArrayLen({2, 3, 4, 5}) String uc = u + c; - @ArrayLen({2, 3, 4, 5}) String ud = u + d; - } - - void intConversions( - @IntVal(123) int intConst, - @IntRange(from = -100000, to = 100) int intRange, - @IntRange(from = 100, to = 100000) int positiveRange, - int unknownInt, - @ArrayLen(10) String a, - @ArrayLenRange(from = 10, to = 20) String b, - @StringVal({"aaa", "bbbbb"}) String c) { - @ArrayLen(13) String aConst = a + intConst; - @ArrayLen({11, 12, 13, 14, 15, 16, 17}) String aRange = a + intRange; - @ArrayLen({13, 14, 15, 16}) String aPositive = a + positiveRange; - @ArrayLenRange(from = 11, to = 21) String aUnknown = a + unknownInt; - - @ArrayLenRange(from = 13, to = 23) String bConst = b + intConst; - @ArrayLenRange(from = 11, to = 27) String bRange = b + intRange; - @ArrayLenRange(from = 13, to = 26) String bPositive = b + positiveRange; - @ArrayLenRange(from = 11, to = 31) String bUnknown = b + unknownInt; - - @StringVal({"aaa123", "bbbbb123"}) String cConst = c + intConst; - @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; - @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; - } - - void longConversions( - @IntVal(1000000000000l) long longConst, - @IntRange(from = 10, to = 1000000000000l) long longRange, - long unknownLong, - @ArrayLen(10) String a) { - - @ArrayLen(23) String aConst = a + longConst; - @ArrayLenRange(from = 12, to = 23) String aRange = a + longRange; - @ArrayLenRange(from = 11, to = 30) String aUnknown = a + unknownLong; - } - - void byteConversions( - @IntVal(100) byte byteConst, - @IntRange(from = 2, to = 10) byte byteRange, - byte unknownByte, - @ArrayLen(10) String a) { - - @ArrayLen(13) String aConst = a + byteConst; - @ArrayLenRange(from = 11, to = 12) String aRange = a + byteRange; - @ArrayLenRange(from = 11, to = 14) String aUnknown = a + unknownByte; - } - - void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { - @MinLen(12) String st = s + t; - } + void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { + @ArrayLen(8) String ab = a + b; + @ArrayLen(7) String bxx = b + "xx"; + } + + void stringLenRangeConcat( + @ArrayLenRange(from = 3, to = 5) String a, @ArrayLenRange(from = 11, to = 19) String b) { + @ArrayLenRange(from = 14, to = 24) String ab = a + b; + @ArrayLenRange(from = 13, to = 21) String bxx = b + "xx"; + } + + void stringLenLenRangeConcat( + @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { + @ArrayLenRange(from = 13, to = 105) String ab = a + b; + } + + void stringValLenConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLen(11) String d) { + + @ArrayLen(19) String ad = a + d; + @ArrayLen(12) String bd = b + d; + @ArrayLenRange(from = 12, to = 14) String cd = c + d; + } + + void stringValLenRangeConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLenRange(from = 11, to = 19) String d) { + + @ArrayLenRange(from = 19, to = 27) String ad = a + d; + @ArrayLenRange(from = 12, to = 20) String bd = b + d; + @ArrayLenRange(from = 12, to = 22) String cd = c + d; + } + + void tooManyStringValConcat( + @StringVal({"a", "b", "c", "d"}) String a, + @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { + @ArrayLen(2) String aa = a + a; + @ArrayLen(3) String ab = a + b; + } + + void charConversions( + char c, + @IntVal({1, 100, 10000}) char d, + @ArrayLen({100, 200}) String s, + @ArrayLenRange(from = 100, to = 200) String t, + @StringVal({"a", "bb", "ccc", "dddd"}) String u) { + @ArrayLen({101, 201}) String sc = s + c; + @ArrayLen({101, 201}) String sd = s + d; + + @ArrayLenRange(from = 101, to = 201) String tc = t + c; + + @ArrayLen({2, 3, 4, 5}) String uc = u + c; + @ArrayLen({2, 3, 4, 5}) String ud = u + d; + } + + void intConversions( + @IntVal(123) int intConst, + @IntRange(from = -100000, to = 100) int intRange, + @IntRange(from = 100, to = 100000) int positiveRange, + int unknownInt, + @ArrayLen(10) String a, + @ArrayLenRange(from = 10, to = 20) String b, + @StringVal({"aaa", "bbbbb"}) String c) { + @ArrayLen(13) String aConst = a + intConst; + @ArrayLen({11, 12, 13, 14, 15, 16, 17}) String aRange = a + intRange; + @ArrayLen({13, 14, 15, 16}) String aPositive = a + positiveRange; + @ArrayLenRange(from = 11, to = 21) String aUnknown = a + unknownInt; + + @ArrayLenRange(from = 13, to = 23) String bConst = b + intConst; + @ArrayLenRange(from = 11, to = 27) String bRange = b + intRange; + @ArrayLenRange(from = 13, to = 26) String bPositive = b + positiveRange; + @ArrayLenRange(from = 11, to = 31) String bUnknown = b + unknownInt; + + @StringVal({"aaa123", "bbbbb123"}) String cConst = c + intConst; + @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; + @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; + } + + void longConversions( + @IntVal(1000000000000l) long longConst, + @IntRange(from = 10, to = 1000000000000l) long longRange, + long unknownLong, + @ArrayLen(10) String a) { + + @ArrayLen(23) String aConst = a + longConst; + @ArrayLenRange(from = 12, to = 23) String aRange = a + longRange; + @ArrayLenRange(from = 11, to = 30) String aUnknown = a + unknownLong; + } + + void byteConversions( + @IntVal(100) byte byteConst, + @IntRange(from = 2, to = 10) byte byteRange, + byte unknownByte, + @ArrayLen(10) String a) { + + @ArrayLen(13) String aConst = a + byteConst; + @ArrayLenRange(from = 11, to = 12) String aRange = a + byteRange; + @ArrayLenRange(from = 11, to = 14) String aUnknown = a + unknownByte; + } + + void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { + @MinLen(12) String st = s + t; + } } diff --git a/framework/tests/value/Alias.java b/framework/tests/value/Alias.java index 3258700ef9c..0979baf94b0 100644 --- a/framework/tests/value/Alias.java +++ b/framework/tests/value/Alias.java @@ -1,10 +1,10 @@ import android.support.annotation.IntRange; public class Alias { - public void androidIntRange() { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = 10) int j = 13; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = -1; - } + public void androidIntRange() { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = 10) int j = 13; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = -1; + } } diff --git a/framework/tests/value/AnnotationUse.java b/framework/tests/value/AnnotationUse.java index f26af8509b0..a243a5af2d3 100644 --- a/framework/tests/value/AnnotationUse.java +++ b/framework/tests/value/AnnotationUse.java @@ -1,12 +1,12 @@ import org.checkerframework.common.value.qual.IntRange; public class AnnotationUse { - // :: error: (annotation.intrange.on.noninteger) - @IntRange(to = 0) String s1; + // :: error: (annotation.intrange.on.noninteger) + @IntRange(to = 0) String s1; - // :: error: (annotation.intrange.on.noninteger) - @IntRange(from = 0) String s2; + // :: error: (annotation.intrange.on.noninteger) + @IntRange(from = 0) String s2; - // Allowed on j.l.Object, because of possible boxing - @IntRange(to = 0) Object o; + // Allowed on j.l.Object, because of possible boxing + @IntRange(to = 0) Object o; } diff --git a/framework/tests/value/ArrayInit.java b/framework/tests/value/ArrayInit.java index 85ac91390fb..b4bce068bec 100644 --- a/framework/tests/value/ArrayInit.java +++ b/framework/tests/value/ArrayInit.java @@ -1,188 +1,186 @@ import org.checkerframework.common.value.qual.*; public class ArrayInit { - public void raggedArrays() { - int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] alpha = - new int[][][] { - {{1, 1}, {1, 1, 1}, {1}, {1}}, - {{1}, {1}, {1}, {1, 1}}, - {{1, 2, 3, 4, 5, 6, 7}}, - {{1}, {1}, {1, 1, 1, 1}} - }; - - int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLenRange(from = 1, to = 12) [] gamma = - new int[][][] { - {{1, 1}, {1, 1, 1}, {1}, {1}}, - {{1}, {1}, {1}, {1, 1}}, - {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, - {{1}, {1}, {1, 1, 1, 1}} - }; - - int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a = {{1, 1}, {1, 1, 1}, {1}, {1}}; - int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b = {{1}, {1}, {1}, {1, 1}}; - int @ArrayLen(1) [] @ArrayLen(7) [] c = {{1, 2, 3, 4, 5, 6, 7}}; - int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d = {{1}, {1}, {1, 1, 1, 1}}; - - int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] beta = { - a, b, c, d + public void raggedArrays() { + int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] alpha = + new int[][][] { + {{1, 1}, {1, 1, 1}, {1}, {1}}, + {{1}, {1}, {1}, {1, 1}}, + {{1, 2, 3, 4, 5, 6, 7}}, + {{1}, {1}, {1, 1, 1, 1}} }; - int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a1 = {{1, 1}, {1, 1, 1}, {1}, {1}}; - int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b1 = {{1}, {1}, {1}, {1, 1}}; - int @ArrayLen(1) [] @ArrayLen(11) [] c1 = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}; - int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d1 = {{1}, {1}, {1, 1, 1, 1}}; - - int @ArrayLen(4) [] @ArrayLenRange(from = 1, to = 4) [] @ArrayLenRange(from = 1, to = 11) [] - delta = {a1, b1, c1, d1}; - } - - public void numberInit() { - int @ArrayLen({1}) [] a = new int[1]; - } - - public void listInit() { - int @ArrayLen({1}) [] a = new int[] {4}; - } - - public void varInit() { - int i = 1; - int @ArrayLen({1}) [] a = new int[i]; - } - - public void rangeInit( - @IntRange(from = 1, to = 2) int shortLength, - @IntRange(from = 1, to = 20) int longLength, - @BottomVal int bottom) { - int @ArrayLen({1, 2}) [] a = new int[shortLength]; - // :: error: (assignment.type.incompatible) - int @ArrayLen({1, 2}) [] b = new int[longLength]; - int @ArrayLenRange(from = 1, to = 20) [] d = new int[longLength]; - int @ArrayLen({0}) [] c = new int[bottom]; - } - - public void multiDim() { - int i = 2; - int j = 3; - int @ArrayLen({2}) [] @ArrayLen({3}) [] a = new int[2][3]; - int @ArrayLen({2}) [] @ArrayLen({3}) [] b = new int[i][j]; - - int @ArrayLen({2}) [][] c = new int[][] {{2}, {3}}; - } - - public void initilizer() { - int @ArrayLen(3) [] ints = new int[] {2, 2, 2}; - char @StringVal("-A%") [] chars = new char[] {45, 'A', '%'}; - int @ArrayLen(3) [] ints2 = {2, 2, 2}; - } - - public void initializerString() { - byte @ArrayLen(2) [] bytes = new byte[] {100, '%'}; - char @ArrayLen(3) [] chars = new char[] {45, 'A', '%'}; - } - - public void vargsTest() { - // type of arg should be @UnknownVal Object @BottomVal[] - vargs((Object[]) null); - - // type of arg should be @UnknownVal int @BottomVal[] - vargs((int[]) null); - - // type of arg should be @UnknownVal byte @BottomVal[] - vargs((byte[]) null); - } - - public void vargs(Object @ArrayLen(0) ... objs) {} - - public void vargs(int @ArrayLen(0) ... ints) {} - - public void vargs(byte @ArrayLen(0) ... bytes) {} - - public void nullableArrays() { - Object @ArrayLen(2) [] @ArrayLen(1) [] o = new Object[][] {new Object[] {null}, null}; - Object @ArrayLen(1) [][] o2 = new Object[][] {null}; - Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; - } - - public void subtyping1(int @ArrayLen({1, 5}) [] a) { - int @ArrayLenRange(from = 1, to = 5) [] b = a; - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 2, to = 5) [] c = a; - } - - public void subtyping2(int @ArrayLenRange(from = 1, to = 5) [] a) { - int @ArrayLen({1, 2, 3, 4, 5}) [] b = a; - // :: error: (assignment.type.incompatible) - int @ArrayLen({1, 5}) [] c = a; - } - - public void subtyping3(int @ArrayLenRange(from = 1, to = 17) [] a) { - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 1, to = 12) [] b = a; - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 5, to = 18) [] c = a; - int @ArrayLenRange(from = 0, to = 20) [] d = a; - } - - public void lub1(boolean flag, @IntRange(from = 1, to = 200) int x) { - int[] a; - if (flag) { - a = new int[x]; - } else { - a = new int[250]; - } - - int @ArrayLenRange(from = 1, to = 250) [] b = a; - } - - public void lub2( - boolean flag, @IntRange(from = 1, to = 20) int x, @IntRange(from = 50, to = 75) int y) { - int[] a; - if (flag) { - a = new int[x]; - } else { - a = new int[y]; - } - - int @ArrayLenRange(from = 1, to = 75) [] b = a; - } - - public void lub3( - boolean flag, @IntRange(from = 1, to = 5) int x, @IntRange(from = 3, to = 7) int y) { - int[] a; - if (flag) { - a = new int[x]; - } else { - a = new int[y]; - } - - int @ArrayLenRange(from = 1, to = 7) [] b = a; - int @ArrayLen({1, 2, 3, 4, 5, 6, 7}) [] c = a; - } - - public void refine(int[] q) { - if (q.length < 20) { - @IntRange(from = 0, to = 19) int x = q.length; - int @ArrayLenRange(from = 0, to = 19) [] b = q; - if (q.length < 5) { - int @ArrayLen({0, 1, 2, 3, 4}) [] c = q; - } - } - } - - // The argument is an arraylen with too many values. - // :: warning: (too.many.values.given) - public void coerce(int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 36}) [] a) { - int @ArrayLenRange(from = 1, to = 36) [] b = a; - if (a.length < 15) { - // :: error: (assignment.type.incompatible) - int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) [] c = a; - } - } + int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLenRange(from = 1, to = 12) [] gamma = + new int[][][] { + {{1, 1}, {1, 1, 1}, {1}, {1}}, + {{1}, {1}, {1}, {1, 1}}, + {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {{1}, {1}, {1, 1, 1, 1}} + }; - public void warnings() { - // :: warning: (negative.arraylen) - int @ArrayLenRange(from = -1, to = 5) [] a; - // :: error: (from.greater.than.to) - int @ArrayLenRange(from = 10, to = 3) [] b; - } + int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a = {{1, 1}, {1, 1, 1}, {1}, {1}}; + int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b = {{1}, {1}, {1}, {1, 1}}; + int @ArrayLen(1) [] @ArrayLen(7) [] c = {{1, 2, 3, 4, 5, 6, 7}}; + int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d = {{1}, {1}, {1, 1, 1, 1}}; + + int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] beta = {a, b, c, d}; + + int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a1 = {{1, 1}, {1, 1, 1}, {1}, {1}}; + int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b1 = {{1}, {1}, {1}, {1, 1}}; + int @ArrayLen(1) [] @ArrayLen(11) [] c1 = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}; + int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d1 = {{1}, {1}, {1, 1, 1, 1}}; + + int @ArrayLen(4) [] @ArrayLenRange(from = 1, to = 4) [] @ArrayLenRange(from = 1, to = 11) [] + delta = {a1, b1, c1, d1}; + } + + public void numberInit() { + int @ArrayLen({1}) [] a = new int[1]; + } + + public void listInit() { + int @ArrayLen({1}) [] a = new int[] {4}; + } + + public void varInit() { + int i = 1; + int @ArrayLen({1}) [] a = new int[i]; + } + + public void rangeInit( + @IntRange(from = 1, to = 2) int shortLength, + @IntRange(from = 1, to = 20) int longLength, + @BottomVal int bottom) { + int @ArrayLen({1, 2}) [] a = new int[shortLength]; + // :: error: (assignment.type.incompatible) + int @ArrayLen({1, 2}) [] b = new int[longLength]; + int @ArrayLenRange(from = 1, to = 20) [] d = new int[longLength]; + int @ArrayLen({0}) [] c = new int[bottom]; + } + + public void multiDim() { + int i = 2; + int j = 3; + int @ArrayLen({2}) [] @ArrayLen({3}) [] a = new int[2][3]; + int @ArrayLen({2}) [] @ArrayLen({3}) [] b = new int[i][j]; + + int @ArrayLen({2}) [][] c = new int[][] {{2}, {3}}; + } + + public void initilizer() { + int @ArrayLen(3) [] ints = new int[] {2, 2, 2}; + char @StringVal("-A%") [] chars = new char[] {45, 'A', '%'}; + int @ArrayLen(3) [] ints2 = {2, 2, 2}; + } + + public void initializerString() { + byte @ArrayLen(2) [] bytes = new byte[] {100, '%'}; + char @ArrayLen(3) [] chars = new char[] {45, 'A', '%'}; + } + + public void vargsTest() { + // type of arg should be @UnknownVal Object @BottomVal[] + vargs((Object[]) null); + + // type of arg should be @UnknownVal int @BottomVal[] + vargs((int[]) null); + + // type of arg should be @UnknownVal byte @BottomVal[] + vargs((byte[]) null); + } + + public void vargs(Object @ArrayLen(0) ... objs) {} + + public void vargs(int @ArrayLen(0) ... ints) {} + + public void vargs(byte @ArrayLen(0) ... bytes) {} + + public void nullableArrays() { + Object @ArrayLen(2) [] @ArrayLen(1) [] o = new Object[][] {new Object[] {null}, null}; + Object @ArrayLen(1) [][] o2 = new Object[][] {null}; + Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; + } + + public void subtyping1(int @ArrayLen({1, 5}) [] a) { + int @ArrayLenRange(from = 1, to = 5) [] b = a; + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 2, to = 5) [] c = a; + } + + public void subtyping2(int @ArrayLenRange(from = 1, to = 5) [] a) { + int @ArrayLen({1, 2, 3, 4, 5}) [] b = a; + // :: error: (assignment.type.incompatible) + int @ArrayLen({1, 5}) [] c = a; + } + + public void subtyping3(int @ArrayLenRange(from = 1, to = 17) [] a) { + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 1, to = 12) [] b = a; + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 5, to = 18) [] c = a; + int @ArrayLenRange(from = 0, to = 20) [] d = a; + } + + public void lub1(boolean flag, @IntRange(from = 1, to = 200) int x) { + int[] a; + if (flag) { + a = new int[x]; + } else { + a = new int[250]; + } + + int @ArrayLenRange(from = 1, to = 250) [] b = a; + } + + public void lub2( + boolean flag, @IntRange(from = 1, to = 20) int x, @IntRange(from = 50, to = 75) int y) { + int[] a; + if (flag) { + a = new int[x]; + } else { + a = new int[y]; + } + + int @ArrayLenRange(from = 1, to = 75) [] b = a; + } + + public void lub3( + boolean flag, @IntRange(from = 1, to = 5) int x, @IntRange(from = 3, to = 7) int y) { + int[] a; + if (flag) { + a = new int[x]; + } else { + a = new int[y]; + } + + int @ArrayLenRange(from = 1, to = 7) [] b = a; + int @ArrayLen({1, 2, 3, 4, 5, 6, 7}) [] c = a; + } + + public void refine(int[] q) { + if (q.length < 20) { + @IntRange(from = 0, to = 19) int x = q.length; + int @ArrayLenRange(from = 0, to = 19) [] b = q; + if (q.length < 5) { + int @ArrayLen({0, 1, 2, 3, 4}) [] c = q; + } + } + } + + // The argument is an arraylen with too many values. + // :: warning: (too.many.values.given) + public void coerce(int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 36}) [] a) { + int @ArrayLenRange(from = 1, to = 36) [] b = a; + if (a.length < 15) { + // :: error: (assignment.type.incompatible) + int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) [] c = a; + } + } + + public void warnings() { + // :: warning: (negative.arraylen) + int @ArrayLenRange(from = -1, to = 5) [] a; + // :: error: (from.greater.than.to) + int @ArrayLenRange(from = 10, to = 3) [] b; + } } diff --git a/framework/tests/value/ArrayIntro.java b/framework/tests/value/ArrayIntro.java index ab1216b541d..03e8b8c2572 100644 --- a/framework/tests/value/ArrayIntro.java +++ b/framework/tests/value/ArrayIntro.java @@ -1,18 +1,18 @@ import org.checkerframework.common.value.qual.*; public class ArrayIntro { - void test() { - int @MinLen(5) [] arr = new int[5]; - int a = 9; - a += 5; - a -= 2; - int @MinLen(12) [] arr1 = new int[a]; - int @MinLen(3) [] arr2 = {1, 2, 3}; - // :: error: (assignment.type.incompatible) - int @MinLen(4) [] arr3 = {4, 5, 6}; - // :: error: (assignment.type.incompatible) - int @MinLen(7) [] arr4 = new int[4]; - // :: error: (assignment.type.incompatible) - int @MinLen(16) [] arr5 = new int[a]; - } + void test() { + int @MinLen(5) [] arr = new int[5]; + int a = 9; + a += 5; + a -= 2; + int @MinLen(12) [] arr1 = new int[a]; + int @MinLen(3) [] arr2 = {1, 2, 3}; + // :: error: (assignment.type.incompatible) + int @MinLen(4) [] arr3 = {4, 5, 6}; + // :: error: (assignment.type.incompatible) + int @MinLen(7) [] arr4 = new int[4]; + // :: error: (assignment.type.incompatible) + int @MinLen(16) [] arr5 = new int[a]; + } } diff --git a/framework/tests/value/Basics.java b/framework/tests/value/Basics.java index 05787674e97..ff0898dbd04 100644 --- a/framework/tests/value/Basics.java +++ b/framework/tests/value/Basics.java @@ -4,291 +4,291 @@ @SuppressWarnings({"deprecation", "removal"}) public class Basics { - public void boolTest() { - boolean a = false; - if (true) { - a = true; - } - @BoolVal({true, false}) boolean b = a; - - // :: error: (assignment.type.incompatible) - @BoolVal({false}) boolean c = a; + public void boolTest() { + boolean a = false; + if (true) { + a = true; } + @BoolVal({true, false}) boolean b = a; - public void CharacterTest() { - Character a = 'a'; - if (true) { - a = 'b'; - } - @IntVal({'a', 'b'}) Character b = a; + // :: error: (assignment.type.incompatible) + @BoolVal({false}) boolean c = a; + } - // :: error: (assignment.type.incompatible) - @IntVal({'a'}) Character c = a; + public void CharacterTest() { + Character a = 'a'; + if (true) { + a = 'b'; } + @IntVal({'a', 'b'}) Character b = a; - public void charTest() { - char a = 'a'; - if (true) { - a = 'b'; - } - @IntVal({'a', 'b'}) char b = a; + // :: error: (assignment.type.incompatible) + @IntVal({'a'}) Character c = a; + } - // :: error: (assignment.type.incompatible) - @IntVal({'a'}) char c = a; + public void charTest() { + char a = 'a'; + if (true) { + a = 'b'; } + @IntVal({'a', 'b'}) char b = a; - public void DoubleTest() { - Double a = new Double(0.0); - if (true) { - a = 2.0; - } - @DoubleVal({0, 2}) Double b = a; + // :: error: (assignment.type.incompatible) + @IntVal({'a'}) char c = a; + } - // :: error: (assignment.type.incompatible) - @DoubleVal({0}) Double c = a; + public void DoubleTest() { + Double a = new Double(0.0); + if (true) { + a = 2.0; } + @DoubleVal({0, 2}) Double b = a; - public void doubleTest() { - double a = 0.0; - if (true) { - a = 2.0; - } - @DoubleVal({0, 2}) double b = a; + // :: error: (assignment.type.incompatible) + @DoubleVal({0}) Double c = a; + } - // :: error: (assignment.type.incompatible) - @DoubleVal({0}) double c = a; + public void doubleTest() { + double a = 0.0; + if (true) { + a = 2.0; } + @DoubleVal({0, 2}) double b = a; - public void FloatTest() { - Float a = new Float(0.0f); - if (true) { - a = 2.0f; - } - @DoubleVal({0, 2}) Float b = a; + // :: error: (assignment.type.incompatible) + @DoubleVal({0}) double c = a; + } - // :: error: (assignment.type.incompatible) - @DoubleVal({0}) Float c = a; + public void FloatTest() { + Float a = new Float(0.0f); + if (true) { + a = 2.0f; } + @DoubleVal({0, 2}) Float b = a; - public void floatTest() { - float a = 0.0f; - if (true) { - a = 2.0f; - } - @DoubleVal({0, 2}) float b = a; + // :: error: (assignment.type.incompatible) + @DoubleVal({0}) Float c = a; + } - // :: error: (assignment.type.incompatible) - @DoubleVal({'a'}) float c = a; + public void floatTest() { + float a = 0.0f; + if (true) { + a = 2.0f; } + @DoubleVal({0, 2}) float b = a; - public void IntegerTest( - @IntRange(from = 3, to = 4) Integer x, @IntRange(from = 20, to = 30) Integer y) { - Integer a; - - /* IntVal + IntVal */ - a = Integer.valueOf(0); - if (true) { - a = 2; - } - // :: error: (assignment.type.incompatible) - @IntVal({0}) Integer test1 = a; - @IntVal({0, 2}) Integer test2 = a; - - /* IntRange + IntRange */ - a = x; - @IntVal({3, 4}) Integer test3 = a; - if (true) { - a = y; - } - @IntRange(from = 15, to = 30) - // :: error: (assignment.type.incompatible) - Integer test4 = a; - @IntRange(from = 3, to = 30) Integer test5 = a; - - /* IntRange + IntVal */ - a = Integer.valueOf(0); - if (true) { - a = x; - } - @IntRange(from = 0, to = 4) Integer test7 = a; - - /* IntRange (Wider than 10) + IntVal */ - a = Integer.valueOf(0); - if (true) { - a = y; - } - @IntRange(from = 1, to = 30) - // :: error: (assignment.type.incompatible) - Integer test8 = a; - @IntRange(from = 0, to = 30) Integer test9 = a; - } + // :: error: (assignment.type.incompatible) + @DoubleVal({'a'}) float c = a; + } - public void intTest(@IntRange(from = 3, to = 4) int x, @IntRange(from = 20, to = 30) int y) { - int a; - - /* IntVal + IntVal */ - a = 0; - if (true) { - a = 2; - } - // :: error: (assignment.type.incompatible) - @IntVal({0}) int test1 = a; - @IntVal({0, 2}) int test2 = a; - - /* IntRange + IntRange */ - a = x; - @IntVal({3, 4}) int test3 = a; - if (true) { - a = y; - } - @IntRange(from = 15, to = 30) - // :: error: (assignment.type.incompatible) - int test4 = a; - @IntRange(from = 3, to = 30) int test5 = a; - - /* IntRange + IntVal */ - a = 0; - if (true) { - a = x; - } - @IntRange(from = 0, to = 4) int test7 = a; - - /* IntRange (Wider than 10) + IntVal */ - a = 0; - if (true) { - a = y; - } - @IntRange(from = 1, to = 30) - // :: error: (assignment.type.incompatible) - int test8 = a; - @IntRange(from = 0, to = 30) int test9 = a; - } + public void IntegerTest( + @IntRange(from = 3, to = 4) Integer x, @IntRange(from = 20, to = 30) Integer y) { + Integer a; - public void intCastTest(@IntVal({0, 1}) int input) { - @IntVal({0, 1}) int c = (int) input; - @IntVal({0, 1}) int ac = (@IntVal({0, 1}) int) input; - @IntVal({0, 1, 2}) int sc = (@IntVal({0, 1, 2}) int) input; - // :: warning: (cast.unsafe) - @IntVal({1}) int uc = (@IntVal({1}) int) input; - // :: warning: (cast.unsafe) - @IntVal({2}) int bc = (@IntVal({2}) int) input; + /* IntVal + IntVal */ + a = Integer.valueOf(0); + if (true) { + a = 2; } - - public void IntDoubleTest( - @IntVal({0, 1}) int iv, - @IntRange(from = 2, to = 3) int ir, - @IntRange(from = 2, to = 20) int irw, - @DoubleVal({4.0, 5.0}) double dv1) { - double a; - - /* IntVal + DoubleVal */ - a = iv; - if (true) { - a = dv1; - } - // :: error: (assignment.type.incompatible) - @DoubleVal({4.0, 5.0}) double test1 = a; - @DoubleVal({0.0, 1.0, 2.0, 3.0, 4.0, 5.0}) double test2 = a; - - /* IntRange + DoubleVal */ - a = ir; - // :: error: (assignment.type.incompatible) - @DoubleVal({2.0}) double test3 = a; - @DoubleVal({2.0, 3.0}) double test4 = a; - if (true) { - a = dv1; - } - // :: error: (assignment.type.incompatible) - test1 = a; - test2 = a; - - /* IntRange (Wider than 10) + DoubleVal */ - a = irw; - if (true) { - a = dv1; - } - // :: error: (assignment.type.incompatible) - @DoubleVal({4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double test5 = a; - @UnknownVal double test6 = a; + // :: error: (assignment.type.incompatible) + @IntVal({0}) Integer test1 = a; + @IntVal({0, 2}) Integer test2 = a; + + /* IntRange + IntRange */ + a = x; + @IntVal({3, 4}) Integer test3 = a; + if (true) { + a = y; } + @IntRange(from = 15, to = 30) + // :: error: (assignment.type.incompatible) + Integer test4 = a; + @IntRange(from = 3, to = 30) Integer test5 = a; + + /* IntRange + IntVal */ + a = Integer.valueOf(0); + if (true) { + a = x; + } + @IntRange(from = 0, to = 4) Integer test7 = a; - public void stringTest() { - String a = "test1"; - if (true) { - a = "test2"; - } - @StringVal({"test1", "test2"}) String b = a; - - // :: error: (assignment.type.incompatible) - @StringVal({"test1"}) String c = a; + /* IntRange (Wider than 10) + IntVal */ + a = Integer.valueOf(0); + if (true) { + a = y; } + @IntRange(from = 1, to = 30) + // :: error: (assignment.type.incompatible) + Integer test8 = a; + @IntRange(from = 0, to = 30) Integer test9 = a; + } + + public void intTest(@IntRange(from = 3, to = 4) int x, @IntRange(from = 20, to = 30) int y) { + int a; + + /* IntVal + IntVal */ + a = 0; + if (true) { + a = 2; + } + // :: error: (assignment.type.incompatible) + @IntVal({0}) int test1 = a; + @IntVal({0, 2}) int test2 = a; + + /* IntRange + IntRange */ + a = x; + @IntVal({3, 4}) int test3 = a; + if (true) { + a = y; + } + @IntRange(from = 15, to = 30) + // :: error: (assignment.type.incompatible) + int test4 = a; + @IntRange(from = 3, to = 30) int test5 = a; + + /* IntRange + IntVal */ + a = 0; + if (true) { + a = x; + } + @IntRange(from = 0, to = 4) int test7 = a; - public void stringCastTest() { - Object a = "test1"; - @StringVal({"test1"}) String b = (String) a; - @StringVal({"test1"}) String c = (java.lang.String) b; + /* IntRange (Wider than 10) + IntVal */ + a = 0; + if (true) { + a = y; + } + @IntRange(from = 1, to = 30) + // :: error: (assignment.type.incompatible) + int test8 = a; + @IntRange(from = 0, to = 30) int test9 = a; + } + + public void intCastTest(@IntVal({0, 1}) int input) { + @IntVal({0, 1}) int c = (int) input; + @IntVal({0, 1}) int ac = (@IntVal({0, 1}) int) input; + @IntVal({0, 1, 2}) int sc = (@IntVal({0, 1, 2}) int) input; + // :: warning: (cast.unsafe) + @IntVal({1}) int uc = (@IntVal({1}) int) input; + // :: warning: (cast.unsafe) + @IntVal({2}) int bc = (@IntVal({2}) int) input; + } + + public void IntDoubleTest( + @IntVal({0, 1}) int iv, + @IntRange(from = 2, to = 3) int ir, + @IntRange(from = 2, to = 20) int irw, + @DoubleVal({4.0, 5.0}) double dv1) { + double a; + + /* IntVal + DoubleVal */ + a = iv; + if (true) { + a = dv1; } + // :: error: (assignment.type.incompatible) + @DoubleVal({4.0, 5.0}) double test1 = a; + @DoubleVal({0.0, 1.0, 2.0, 3.0, 4.0, 5.0}) double test2 = a; + + /* IntRange + DoubleVal */ + a = ir; + // :: error: (assignment.type.incompatible) + @DoubleVal({2.0}) double test3 = a; + @DoubleVal({2.0, 3.0}) double test4 = a; + if (true) { + a = dv1; + } + // :: error: (assignment.type.incompatible) + test1 = a; + test2 = a; + + /* IntRange (Wider than 10) + DoubleVal */ + a = irw; + if (true) { + a = dv1; + } + // :: error: (assignment.type.incompatible) + @DoubleVal({4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double test5 = a; + @UnknownVal double test6 = a; + } + + public void stringTest() { + String a = "test1"; + if (true) { + a = "test2"; + } + @StringVal({"test1", "test2"}) String b = a; - void tooManyValuesInt() { - // :: warning: (too.many.values.given.int) - @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 100}) int a = 20; // This should succeed if a is treated as @IntRange(from=1, to=100) + // :: error: (assignment.type.incompatible) + @StringVal({"test1"}) String c = a; + } - // :: warning: (too.many.values.given.int) - @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) - // :: error: (assignment.type.incompatible) - int b = 20; // d is @IntRange(from=1, to=12) + public void stringCastTest() { + Object a = "test1"; + @StringVal({"test1"}) String b = (String) a; + @StringVal({"test1"}) String c = (java.lang.String) b; + } - @UnknownVal int c = a; // This should always succeed - } + void tooManyValuesInt() { + // :: warning: (too.many.values.given.int) + @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 100}) int a = 20; // This should succeed if a is treated as @IntRange(from=1, to=100) - void fromGreaterThanTo() { - // :: error: (from.greater.than.to) - @IntRange(from = 2, to = 0) - // :: error: (assignment.type.incompatible) - int a = 1; // a should be @BottomVal + // :: warning: (too.many.values.given.int) + @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) + // :: error: (assignment.type.incompatible) + int b = 20; // d is @IntRange(from=1, to=12) - @IntRange(from = 1) int b = 2; + @UnknownVal int c = a; // This should always succeed + } - @IntRange(to = 2) int c = 1; + void fromGreaterThanTo() { + // :: error: (from.greater.than.to) + @IntRange(from = 2, to = 0) + // :: error: (assignment.type.incompatible) + int a = 1; // a should be @BottomVal - @IntRange(to = 2, from = 0) int d = 1; - } + @IntRange(from = 1) int b = 2; - void tooManyValuesDouble() { - // :: warning: (too.many.values.given) - @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double a = 8.0; + @IntRange(to = 2) int c = 1; - @UnknownVal double b = a; // This should always succeed + @IntRange(to = 2, from = 0) int d = 1; + } - @UnknownVal double c = 0; + void tooManyValuesDouble() { + // :: warning: (too.many.values.given) + @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double a = 8.0; - a = c; // This should succeed if a is treated as @UnknownVal + @UnknownVal double b = a; // This should always succeed - // :: warning: (too.many.values.given) - @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double d = 8.0; + @UnknownVal double c = 0; - d = 2.0 * d; // This should succeed since d is @UnknownVal - } + a = c; // This should succeed if a is treated as @UnknownVal - void tooManyValuesString() { - // :: warning: (too.many.values.given) - @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String a = "h"; + // :: warning: (too.many.values.given) + @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double d = 8.0; - @UnknownVal String b = a; // This should always succeed + d = 2.0 * d; // This should succeed since d is @UnknownVal + } - @UnknownVal String c = ""; + void tooManyValuesString() { + // :: warning: (too.many.values.given) + @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String a = "h"; - // :: error: (assignment.type.incompatible) - a = c; // This should not succeed if a is treated as @ArrayLen(1) + @UnknownVal String b = a; // This should always succeed - @ArrayLen(1) String al = a; // a is @ArrayLen(1) + @UnknownVal String c = ""; - // :: warning: (too.many.values.given) - @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String d = "h"; + // :: error: (assignment.type.incompatible) + a = c; // This should not succeed if a is treated as @ArrayLen(1) - // :: error: (assignment.type.incompatible) - d = "b" + d; // This should not succeed since d is @ArrayLen(1) + @ArrayLen(1) String al = a; // a is @ArrayLen(1) - @ArrayLen(1) String dl = d; // d is @ArrayLen(1) - } + // :: warning: (too.many.values.given) + @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String d = "h"; + + // :: error: (assignment.type.incompatible) + d = "b" + d; // This should not succeed since d is @ArrayLen(1) + + @ArrayLen(1) String dl = d; // d is @ArrayLen(1) + } } diff --git a/framework/tests/value/BigIntegerTest.java b/framework/tests/value/BigIntegerTest.java index 4bc59641569..19ce9666f8e 100644 --- a/framework/tests/value/BigIntegerTest.java +++ b/framework/tests/value/BigIntegerTest.java @@ -1,38 +1,37 @@ +import java.math.BigInteger; import org.checkerframework.common.value.qual.IntRange; import org.checkerframework.common.value.qual.PolyValue; -import java.math.BigInteger; - public class BigIntegerTest { - void construct1(@IntRange(from = -1, to = 1) int signum, byte[] magnitude) { - BigInteger val = new BigInteger(signum, magnitude); - } + void construct1(@IntRange(from = -1, to = 1) int signum, byte[] magnitude) { + BigInteger val = new BigInteger(signum, magnitude); + } - void construct2(String val, @IntRange(from = 2, to = 36) int radix) { - BigInteger value = new BigInteger(val, radix); - } + void construct2(String val, @IntRange(from = 2, to = 36) int radix) { + BigInteger value = new BigInteger(val, radix); + } - @PolyValue double getDoubleVal(@PolyValue BigInteger val) { - return val.doubleValue(); - } + @PolyValue double getDoubleVal(@PolyValue BigInteger val) { + return val.doubleValue(); + } - @PolyValue int getIntVal(@PolyValue BigInteger val) { - return val.intValue(); - } + @PolyValue int getIntVal(@PolyValue BigInteger val) { + return val.intValue(); + } - @PolyValue float getFloatVal(@PolyValue BigInteger val) { - return val.floatValue(); - } + @PolyValue float getFloatVal(@PolyValue BigInteger val) { + return val.floatValue(); + } - @PolyValue long getLongVal(@PolyValue BigInteger val) { - return val.longValue(); - } + @PolyValue long getLongVal(@PolyValue BigInteger val) { + return val.longValue(); + } - void compareTo(BigInteger val, BigInteger to) { - @IntRange(from = -1, to = 1) int compared = val.compareTo(to); - } + void compareTo(BigInteger val, BigInteger to) { + @IntRange(from = -1, to = 1) int compared = val.compareTo(to); + } - void signum(BigInteger val) { - @IntRange(from = -1, to = 1) int signum = val.signum(); - } + void signum(BigInteger val) { + @IntRange(from = -1, to = 1) int signum = val.signum(); + } } diff --git a/framework/tests/value/Binaries.java b/framework/tests/value/Binaries.java index 9b1eec7f28f..8fc57dfa6b7 100644 --- a/framework/tests/value/Binaries.java +++ b/framework/tests/value/Binaries.java @@ -1,381 +1,380 @@ -import org.checkerframework.common.value.qual.*; - import java.util.BitSet; +import org.checkerframework.common.value.qual.*; public class Binaries { - private BitSet bitmap; - - public void test() { - int length = bitmap.length(); - for (int i = 0, t = 0; i < length; i++) { - t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); - if (i % 8 == 7 || i == length - 1) { - write(t); - t = 0; - } - } + private BitSet bitmap; + + public void test() { + int length = bitmap.length(); + for (int i = 0, t = 0; i < length; i++) { + t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); + if (i % 8 == 7 || i == length - 1) { + write(t); + t = 0; + } } - - void write(int t) {} - - // Test widenedUpperBound is working. - public void loop(int c) { - double v = 0; - int decexp = 0; - int seendot = 0; - while (true) { - if (c == '.' && seendot == 0) seendot = 1; - else if ('0' <= c && c <= '9') { - v = v * 10 + (c - '0'); - decexp += seendot; - } else { - break; - } - } + } + + void write(int t) {} + + // Test widenedUpperBound is working. + public void loop(int c) { + double v = 0; + int decexp = 0; + int seendot = 0; + while (true) { + if (c == '.' && seendot == 0) seendot = 1; + else if ('0' <= c && c <= '9') { + v = v * 10 + (c - '0'); + decexp += seendot; + } else { + break; + } } - - public void testIntRange( - @IntVal({1, 2}) int values, - @IntRange(from = 3, to = 4) int range1, - @IntRange(from = 5, to = 20) int range2, - @BottomVal int bottom, - @UnknownVal int top) { - - /* IntRange + IntRange */ - @IntRange(from = 8, to = 24) int a = range1 + range2; - - /* IntRange * IntVal */ - @IntRange(from = 3, to = 8) int b = values * range1; - - /* IntRange * BottomVal */ - int c = range1 * bottom; - - /* IntRange * UnknownVal */ - @IntRange(from = 0) - // :: error: (assignment.type.incompatible) - int d = range1 + top; + } + + public void testIntRange( + @IntVal({1, 2}) int values, + @IntRange(from = 3, to = 4) int range1, + @IntRange(from = 5, to = 20) int range2, + @BottomVal int bottom, + @UnknownVal int top) { + + /* IntRange + IntRange */ + @IntRange(from = 8, to = 24) int a = range1 + range2; + + /* IntRange * IntVal */ + @IntRange(from = 3, to = 8) int b = values * range1; + + /* IntRange * BottomVal */ + int c = range1 * bottom; + + /* IntRange * UnknownVal */ + @IntRange(from = 0) + // :: error: (assignment.type.incompatible) + int d = range1 + top; + } + + public void add() { + int a = 1; + if (true) { + a = 2; } + @IntVal({3, 4}) int b = a + 2; - public void add() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({3, 4}) int b = a + 2; - - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({3.0, 4.0}) double d = c + 2; - - char e = '1'; - if (true) { - e = '2'; - } - @IntVal({'3', '4'}) char f = (char) (e + 2); - - String g = "A"; - if (true) { - g = "B"; - } - @StringVal({"nullC", "AC", "BC"}) String h = g + "C"; + double c = 1.0; + if (true) { + c = 2.0; } + @DoubleVal({3.0, 4.0}) double d = c + 2; - public void subtract() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({-1, 0}) int b = a - 2; - - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({-1.0, 0.0}) double d = c - 2; - - char e = '2'; - if (true) { - e = '3'; - } - - @IntVal({'0', '1'}) char f = (char) (e - 2); + char e = '1'; + if (true) { + e = '2'; } + @IntVal({'3', '4'}) char f = (char) (e + 2); - public void multiply() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({2, 4}) int b = a * 2; - - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - - char e = (char) 25; - if (true) { - - e = (char) 26; - } - - @IntVal({'2', '4'}) char f = (char) (e * 2); - - @DoubleVal(0.75) float g = 1 * 0.75f; + String g = "A"; + if (true) { + g = "B"; } + @StringVal({"nullC", "AC", "BC"}) String h = g + "C"; + } - public void divide() { - int a = 2; - if (true) { - a = 4; - } - @IntVal({1, 2}) int b = a / 2; + public void subtract() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({-1, 0}) int b = a - 2; - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({0.5, 1.0}) double d = c / 2; + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({-1.0, 0.0}) double d = c - 2; - char e = (char) 96; - if (true) { - e = (char) 98; - } + char e = '2'; + if (true) { + e = '3'; + } - @IntVal({'0', '1'}) char f = (char) (e / 2); + @IntVal({'0', '1'}) char f = (char) (e - 2); + } - @IntVal(0) int g = 2 / 3; - @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; - @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; + public void multiply() { + int a = 1; + if (true) { + a = 2; } + @IntVal({2, 4}) int b = a * 2; - public void remainder() { - int a = 4; - if (true) { - a = 5; - } - @IntVal({1, 2}) int b = a % 3; - - double c = 4.0; - if (true) { - c = 5.0; - } - @DoubleVal({1.0, 2.0}) double d = c % 3; + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - char e = (char) 98; - if (true) { - e = (char) 99; - } + char e = (char) 25; + if (true) { - @IntVal({'0', '1'}) char f = (char) (e % 50); + e = (char) 26; } - public boolean flag = true; - - public void and() { - boolean a = true; - if (flag) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a & true; + @IntVal({'2', '4'}) char f = (char) (e * 2); - int c = 4; - if (true) { - c = 5; - } - @IntVal({0, 1}) int d = c & 3; + @DoubleVal(0.75) float g = 1 * 0.75f; + } - char e = (char) 48; - if (true) { + public void divide() { + int a = 2; + if (true) { + a = 4; + } + @IntVal({1, 2}) int b = a / 2; - e = (char) 51; - } + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({0.5, 1.0}) double d = c / 2; - @IntVal({'0', '2'}) char f = (char) (e & 50); + char e = (char) 96; + if (true) { + e = (char) 98; } - public void or() { - boolean a = true; - if (true) { - a = false; - } - // TODO: we could detect this case - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a | true; + @IntVal({'0', '1'}) char f = (char) (e / 2); - int c = 4; - if (true) { - c = 5; - } - @IntVal({7}) int d = c | 3; + @IntVal(0) int g = 2 / 3; + @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; + @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; + } - char e = (char) 48; - if (true) { - e = (char) 51; - } + public void remainder() { + int a = 4; + if (true) { + a = 5; + } + @IntVal({1, 2}) int b = a % 3; - @IntVal({'1', '3'}) char f = (char) (e | 1); + double c = 4.0; + if (true) { + c = 5.0; } + @DoubleVal({1.0, 2.0}) double d = c % 3; - public void xor() { - boolean a = true; - if (true) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a ^ true; + char e = (char) 98; + if (true) { + e = (char) 99; + } - int c = 4; - if (true) { - c = 5; - } - @IntVal({7, 6}) int d = c ^ 3; + @IntVal({'0', '1'}) char f = (char) (e % 50); + } - char e = (char) 48; - if (true) { + public boolean flag = true; - e = (char) 51; - } + public void and() { + boolean a = true; + if (flag) { + a = false; + } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a & true; - @IntVal({'1', '2'}) char f = (char) (e ^ 1); - } - - public void boolAnd() { - @BoolVal({false}) boolean a = true && false; - @BoolVal({true}) boolean b = false || true; - } - - public void conditionals() { - @BoolVal({false}) boolean a = 1.0f == '1'; - @BoolVal({true}) boolean b = 1 != 2.0; - @BoolVal({true}) boolean c = 1 > 0.5; - @BoolVal({true}) boolean d = 1 >= 1.0; - @BoolVal({true}) boolean e = 1 < 1.1f; - @BoolVal({true}) boolean f = (char) 2 <= 2.0; - @IntVal('!') Character BANG = '!'; - @BoolVal(true) boolean g = (BANG == '!'); - char bangChar = '!'; - @BoolVal(true) boolean h = (BANG == bangChar); - - Character bang = '!'; - // Reference equalitiy is used - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean i = (BANG == bang); - } - - public void loop() throws InterruptedException { - int spurious_count = 0; - while (true) { - wait(); - if (System.currentTimeMillis() == 0) { - spurious_count++; - if (spurious_count > 1024) { - break; - } - } - } + int c = 4; + if (true) { + c = 5; } + @IntVal({0, 1}) int d = c & 3; - public void shifts() { - int a = -8; - if (true) { - a = 4; - } - @IntVal({1, -2}) int b = a >> 2; + char e = (char) 48; + if (true) { - int c = 1; - if (true) { - c = 2; - } - @IntVal({4, 8}) int d = c << 2; + e = (char) 51; + } - int e = -8; - if (true) { - e = 4; - } - @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; + @IntVal({'0', '2'}) char f = (char) (e & 50); + } - char g = (char) 24; - if (true) { - g = (char) 25; - } + public void or() { + boolean a = true; + if (true) { + a = false; + } + // TODO: we could detect this case + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a | true; - @IntVal({'0', '2'}) char h = (char) (g << 1); + int c = 4; + if (true) { + c = 5; } + @IntVal({7}) int d = c | 3; - public void chains() { - char a = 2; - int b = 3; - double c = 5; + char e = (char) 48; + if (true) { + e = (char) 51; + } - @DoubleVal({1}) double d = a * b - c; + @IntVal({'1', '3'}) char f = (char) (e | 1); + } - @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; + public void xor() { + boolean a = true; + if (true) { + a = false; } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a ^ true; - public void compareWithNull() { - String s = "1"; - // TODO - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean b = (s != null); + int c = 4; + if (true) { + c = 5; } + @IntVal({7, 6}) int d = c ^ 3; - public void nullConcatenation(@StringVal({"a", "b"}) String arg) { - String n1 = null; - String n2 = "null"; - String k = "const"; + char e = (char) 48; + if (true) { - // @StringVal("nullnull") String a1 = n1 + null; - @StringVal("nullnull") String a2 = n1 + "null"; - // @StringVal("nullnull") String a3 = n1 + n1; - @StringVal("nullnull") String a4 = n1 + n2; - @StringVal({"nullconst", "nullnull"}) String a5 = n1 + k; - @StringVal("nullconst") String a6 = n1 + "const"; - - @StringVal("nullnull") String b1 = n2 + null; - @StringVal("nullnull") String b2 = n2 + "null"; - @StringVal("nullnull") String b3 = n2 + n1; - @StringVal("nullnull") String b4 = n2 + n2; - @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; - @StringVal("nullconst") String b6 = n2 + "const"; + e = (char) 51; + } - @StringVal({"anull", "bnull", "nullnull"}) String c1 = arg + null; - @StringVal({"anull", "bnull", "nullnull"}) String c2 = arg + "null"; - @StringVal({"anull", "bnull", "nullnull"}) String c3 = arg + n1; - @StringVal({"anull", "bnull", "nullnull"}) String c4 = arg + n2; - @StringVal({"aconst", "anull", "bconst", "bnull", "nullconst", "nullnull"}) String c5 = arg + k; - @StringVal({"aconst", "bconst", "nullconst"}) String c6 = arg + "const"; - @StringVal({"a2147483647", "b2147483647", "null2147483647"}) String c7 = arg + Integer.MAX_VALUE; + @IntVal({'1', '2'}) char f = (char) (e ^ 1); + } + + public void boolAnd() { + @BoolVal({false}) boolean a = true && false; + @BoolVal({true}) boolean b = false || true; + } + + public void conditionals() { + @BoolVal({false}) boolean a = 1.0f == '1'; + @BoolVal({true}) boolean b = 1 != 2.0; + @BoolVal({true}) boolean c = 1 > 0.5; + @BoolVal({true}) boolean d = 1 >= 1.0; + @BoolVal({true}) boolean e = 1 < 1.1f; + @BoolVal({true}) boolean f = (char) 2 <= 2.0; + @IntVal('!') Character BANG = '!'; + @BoolVal(true) boolean g = (BANG == '!'); + char bangChar = '!'; + @BoolVal(true) boolean h = (BANG == bangChar); + + Character bang = '!'; + // Reference equalitiy is used + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean i = (BANG == bang); + } + + public void loop() throws InterruptedException { + int spurious_count = 0; + while (true) { + wait(); + if (System.currentTimeMillis() == 0) { + spurious_count++; + if (spurious_count > 1024) { + break; + } + } } + } - public void conditionalComparisions() { - @BoolVal(true) boolean a1 = true || false; - @BoolVal(true) boolean a2 = true || true; - @BoolVal(false) boolean a3 = false || false; - @BoolVal(true) boolean a4 = false || true; + public void shifts() { + int a = -8; + if (true) { + a = 4; + } + @IntVal({1, -2}) int b = a >> 2; - @BoolVal(false) boolean a5 = true && false; - @BoolVal(true) boolean a6 = true && true; - @BoolVal(false) boolean a7 = false && false; - @BoolVal(false) boolean a8 = false && true; + int c = 1; + if (true) { + c = 2; + } + @IntVal({4, 8}) int d = c << 2; - boolean unknown = flag ? true : false; - @BoolVal(true) boolean a9 = true || unknown; - @BoolVal(true) boolean a11 = unknown || true; - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean a12 = unknown || false; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a13 = false || unknown; + int e = -8; + if (true) { + e = 4; + } + @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a14 = true && unknown; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a15 = unknown && true; - @BoolVal(false) boolean a16 = unknown && false; - @BoolVal(false) boolean a17 = false && unknown; + char g = (char) 24; + if (true) { + g = (char) 25; } + + @IntVal({'0', '2'}) char h = (char) (g << 1); + } + + public void chains() { + char a = 2; + int b = 3; + double c = 5; + + @DoubleVal({1}) double d = a * b - c; + + @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; + } + + public void compareWithNull() { + String s = "1"; + // TODO + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean b = (s != null); + } + + public void nullConcatenation(@StringVal({"a", "b"}) String arg) { + String n1 = null; + String n2 = "null"; + String k = "const"; + + // @StringVal("nullnull") String a1 = n1 + null; + @StringVal("nullnull") String a2 = n1 + "null"; + // @StringVal("nullnull") String a3 = n1 + n1; + @StringVal("nullnull") String a4 = n1 + n2; + @StringVal({"nullconst", "nullnull"}) String a5 = n1 + k; + @StringVal("nullconst") String a6 = n1 + "const"; + + @StringVal("nullnull") String b1 = n2 + null; + @StringVal("nullnull") String b2 = n2 + "null"; + @StringVal("nullnull") String b3 = n2 + n1; + @StringVal("nullnull") String b4 = n2 + n2; + @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; + @StringVal("nullconst") String b6 = n2 + "const"; + + @StringVal({"anull", "bnull", "nullnull"}) String c1 = arg + null; + @StringVal({"anull", "bnull", "nullnull"}) String c2 = arg + "null"; + @StringVal({"anull", "bnull", "nullnull"}) String c3 = arg + n1; + @StringVal({"anull", "bnull", "nullnull"}) String c4 = arg + n2; + @StringVal({"aconst", "anull", "bconst", "bnull", "nullconst", "nullnull"}) String c5 = arg + k; + @StringVal({"aconst", "bconst", "nullconst"}) String c6 = arg + "const"; + @StringVal({"a2147483647", "b2147483647", "null2147483647"}) String c7 = arg + Integer.MAX_VALUE; + } + + public void conditionalComparisions() { + @BoolVal(true) boolean a1 = true || false; + @BoolVal(true) boolean a2 = true || true; + @BoolVal(false) boolean a3 = false || false; + @BoolVal(true) boolean a4 = false || true; + + @BoolVal(false) boolean a5 = true && false; + @BoolVal(true) boolean a6 = true && true; + @BoolVal(false) boolean a7 = false && false; + @BoolVal(false) boolean a8 = false && true; + + boolean unknown = flag ? true : false; + @BoolVal(true) boolean a9 = true || unknown; + @BoolVal(true) boolean a11 = unknown || true; + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean a12 = unknown || false; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a13 = false || unknown; + + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a14 = true && unknown; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a15 = unknown && true; + @BoolVal(false) boolean a16 = unknown && false; + @BoolVal(false) boolean a17 = false && unknown; + } } diff --git a/framework/tests/value/BitsMethodsIntRange.java b/framework/tests/value/BitsMethodsIntRange.java index 547ee314b96..6929a7e82c1 100644 --- a/framework/tests/value/BitsMethodsIntRange.java +++ b/framework/tests/value/BitsMethodsIntRange.java @@ -1,13 +1,13 @@ import org.checkerframework.common.value.qual.IntRange; public class BitsMethodsIntRange { - void caseInteger(int integerIndex) { - @IntRange(from = 0, to = 32) int leadingZeros = Integer.numberOfLeadingZeros(integerIndex); - @IntRange(from = 0, to = 32) int trailingZeros = Integer.numberOfLeadingZeros(integerIndex); - } + void caseInteger(int integerIndex) { + @IntRange(from = 0, to = 32) int leadingZeros = Integer.numberOfLeadingZeros(integerIndex); + @IntRange(from = 0, to = 32) int trailingZeros = Integer.numberOfLeadingZeros(integerIndex); + } - void caseLong(long longIndex) { - @IntRange(from = 0, to = 64) int leadingZeros = Long.numberOfLeadingZeros(longIndex); - @IntRange(from = 0, to = 64) int trailingZeros = Long.numberOfLeadingZeros(longIndex); - } + void caseLong(long longIndex) { + @IntRange(from = 0, to = 64) int leadingZeros = Long.numberOfLeadingZeros(longIndex); + @IntRange(from = 0, to = 64) int trailingZeros = Long.numberOfLeadingZeros(longIndex); + } } diff --git a/framework/tests/value/BitwiseAnd.java b/framework/tests/value/BitwiseAnd.java index 888f9ac5931..f40c5272772 100644 --- a/framework/tests/value/BitwiseAnd.java +++ b/framework/tests/value/BitwiseAnd.java @@ -2,35 +2,35 @@ public class BitwiseAnd { - public static void Case11(@IntRange(from = 0) byte b) { - @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; - } - - public static void Case12(@IntRange(to = -1) byte b) { - @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; - } - - public static void Case13(byte b) { - @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; - } - - public static void Case21(@IntRange(from = 0) byte b) { - @IntRange(from = 0, to = 0x0f) long i1 = b & 0x800000000000000fL; - } - - public static void Case22(@IntRange(to = -1) byte b) { - @IntRange(from = 0x8000000000000000L, to = 0x80000000ffffffffL) long i1 = b & 0x80000000ffffffffL; - } - - public static void Case23(byte b) { - @IntRange(from = 0x8000000000000000L, to = 0xf0) long i1 = b & 0x80000000000000f0L; - } - - public static void Issue1623(byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - byte b = bytes[i]; - @IntRange(from = 0, to = 15) int i1 = (b & 0xf0) >> 4; - @IntRange(from = 0, to = 15) int i2 = b & 0x0f; - } + public static void Case11(@IntRange(from = 0) byte b) { + @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; + } + + public static void Case12(@IntRange(to = -1) byte b) { + @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; + } + + public static void Case13(byte b) { + @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; + } + + public static void Case21(@IntRange(from = 0) byte b) { + @IntRange(from = 0, to = 0x0f) long i1 = b & 0x800000000000000fL; + } + + public static void Case22(@IntRange(to = -1) byte b) { + @IntRange(from = 0x8000000000000000L, to = 0x80000000ffffffffL) long i1 = b & 0x80000000ffffffffL; + } + + public static void Case23(byte b) { + @IntRange(from = 0x8000000000000000L, to = 0xf0) long i1 = b & 0x80000000000000f0L; + } + + public static void Issue1623(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + @IntRange(from = 0, to = 15) int i1 = (b & 0xf0) >> 4; + @IntRange(from = 0, to = 15) int i2 = b & 0x0f; } + } } diff --git a/framework/tests/value/Boxing.java b/framework/tests/value/Boxing.java index cdefae6530e..05121e91b4f 100644 --- a/framework/tests/value/Boxing.java +++ b/framework/tests/value/Boxing.java @@ -2,22 +2,22 @@ public class Boxing { - void simpleTest1(@BottomVal Integer x, int y) { - @BottomVal int f = x.intValue(); - if (x.intValue() == y) { - @BottomVal int z = y; - } + void simpleTest1(@BottomVal Integer x, int y) { + @BottomVal int f = x.intValue(); + if (x.intValue() == y) { + @BottomVal int z = y; } + } - void simpleTest2(@BottomVal Integer x, int y) { - if (x == y) { - @BottomVal int z = y; - } + void simpleTest2(@BottomVal Integer x, int y) { + if (x == y) { + @BottomVal int z = y; } + } - void simpleTest3(@BottomVal Integer x, Integer y) { - if (x == y) { - @BottomVal int z = y; - } + void simpleTest3(@BottomVal Integer x, Integer y) { + if (x == y) { + @BottomVal int z = y; } + } } diff --git a/framework/tests/value/CharArrayWithNonLiteralConstants.java b/framework/tests/value/CharArrayWithNonLiteralConstants.java index fba9d8da2a1..d0af0fb48d7 100644 --- a/framework/tests/value/CharArrayWithNonLiteralConstants.java +++ b/framework/tests/value/CharArrayWithNonLiteralConstants.java @@ -3,9 +3,9 @@ public class CharArrayWithNonLiteralConstants { - public static void main(String[] args) { - char @StringVal("hello") [] greeting1 = {'h', 'e', 'l', 'l', 'o'}; - @IntVal('e') char e = 'e'; - char @StringVal("hello") [] greeting2 = {'h', e, 'l', 'l', 'o'}; - } + public static void main(String[] args) { + char @StringVal("hello") [] greeting1 = {'h', 'e', 'l', 'l', 'o'}; + @IntVal('e') char e = 'e'; + char @StringVal("hello") [] greeting2 = {'h', e, 'l', 'l', 'o'}; + } } diff --git a/framework/tests/value/CharacterToString.java b/framework/tests/value/CharacterToString.java index 2dd0c8f0be9..a0f34bc99de 100644 --- a/framework/tests/value/CharacterToString.java +++ b/framework/tests/value/CharacterToString.java @@ -2,7 +2,7 @@ // two annotations from the same hierarchy. // https://github.com/typetools/checker-framework/issues/1356 public class CharacterToString { - void m() { - String s = Character.toString('a'); - } + void m() { + String s = Character.toString('a'); + } } diff --git a/framework/tests/value/ClassNotFound.java b/framework/tests/value/ClassNotFound.java index ebca93c6bc7..86c3d827806 100644 --- a/framework/tests/value/ClassNotFound.java +++ b/framework/tests/value/ClassNotFound.java @@ -3,15 +3,15 @@ public class ClassNotFound { - @StaticallyExecutable - @Pure - public static int foo(int a) { - return a + 2; - } + @StaticallyExecutable + @Pure + public static int foo(int a) { + return a + 2; + } - public void bar() { - int a = 0; - // :: warning: (class.find.failed) - foo(a); - } + public void bar() { + int a = 0; + // :: warning: (class.find.failed) + foo(a); + } } diff --git a/framework/tests/value/CompoundAssignment.java b/framework/tests/value/CompoundAssignment.java index 0d7eb533fee..406e9d47311 100644 --- a/framework/tests/value/CompoundAssignment.java +++ b/framework/tests/value/CompoundAssignment.java @@ -8,94 +8,94 @@ public class CompoundAssignment { - @StringVal("hello") String field; - - public void refinements() { - field = "hello"; - // :: error: (compound.assignment.type.incompatible) - field += method(); - // :: error: (assignment.type.incompatible) - // :: error: (compound.assignment.type.incompatible) - @StringVal("hellohellohello") String test = field += method(); + @StringVal("hello") String field; + + public void refinements() { + field = "hello"; + // :: error: (compound.assignment.type.incompatible) + field += method(); + // :: error: (assignment.type.incompatible) + // :: error: (compound.assignment.type.incompatible) + @StringVal("hellohellohello") String test = field += method(); + } + + @StringVal("hello") String method() { + // :: error: (assignment.type.incompatible) + field = "goodbye"; + return "hello"; + } + + void value() { + @StringVal("hello") String s = "hello"; + // :: error: (compound.assignment.type.incompatible) + s += "hello"; + + @IntVal(1) int i = 1; + // :: error: (compound.assignment.type.incompatible) + i += 1; + + @IntVal(2) int j = 2; + // :: error: (compound.assignment.type.incompatible) + j += 2; + + // :: error: (assignment.type.incompatible) + @IntVal(4) int four = j; + } + + void value2() { + @StringVal("hello") String s = "hello"; + // :: error: (assignment.type.incompatible) + s = s + "hello"; + + @IntVal(1) int i = 1; + // :: error: (assignment.type.incompatible) + i = i + 1; + } + + @IntRange(from = 5, to = 10) int afield; + + void afield() { + if (afield == 5) { + afield += 5; } + // :: error: (compound.assignment.type.incompatible) + afield += 2; + } - @StringVal("hello") String method() { - // :: error: (assignment.type.incompatible) - field = "goodbye"; - return "hello"; + void aparam(@IntRange(from = 5, to = 10) int aparam) { + if (aparam == 5) { + aparam += 5; } - - void value() { - @StringVal("hello") String s = "hello"; - // :: error: (compound.assignment.type.incompatible) - s += "hello"; - - @IntVal(1) int i = 1; - // :: error: (compound.assignment.type.incompatible) - i += 1; - - @IntVal(2) int j = 2; - // :: error: (compound.assignment.type.incompatible) - j += 2; - - // :: error: (assignment.type.incompatible) - @IntVal(4) int four = j; - } - - void value2() { - @StringVal("hello") String s = "hello"; - // :: error: (assignment.type.incompatible) - s = s + "hello"; - - @IntVal(1) int i = 1; - // :: error: (assignment.type.incompatible) - i = i + 1; - } - - @IntRange(from = 5, to = 10) int afield; - - void afield() { - if (afield == 5) { - afield += 5; - } - // :: error: (compound.assignment.type.incompatible) - afield += 2; - } - - void aparam(@IntRange(from = 5, to = 10) int aparam) { - if (aparam == 5) { - aparam += 5; - } - // :: error: (compound.assignment.type.incompatible) - aparam += 2; - } - - void alocal() { - @IntRange(from = 5, to = 10) int alocal; - if (this.hashCode() > 100) { - alocal = 5; - } else { - alocal = 10; - } - - if (alocal == 5) { - alocal += 5; - } - // :: error: (compound.assignment.type.incompatible) - alocal += 2; - } - - void noErrorCompoundAssignments() { - @IntVal(0) int zero = 0; - zero *= 12; - - @StringVal("null") String s = "null"; - s += ""; + // :: error: (compound.assignment.type.incompatible) + aparam += 2; + } + + void alocal() { + @IntRange(from = 5, to = 10) int alocal; + if (this.hashCode() > 100) { + alocal = 5; + } else { + alocal = 10; } - void errorCompundAssignments() { - @StringVal("hello") String s = "hello"; - // :: error: (compound.assignment.type.incompatible) - s += ""; + if (alocal == 5) { + alocal += 5; } + // :: error: (compound.assignment.type.incompatible) + alocal += 2; + } + + void noErrorCompoundAssignments() { + @IntVal(0) int zero = 0; + zero *= 12; + + @StringVal("null") String s = "null"; + s += ""; + } + + void errorCompundAssignments() { + @StringVal("hello") String s = "hello"; + // :: error: (compound.assignment.type.incompatible) + s += ""; + } } diff --git a/framework/tests/value/DivideByZero.java b/framework/tests/value/DivideByZero.java index 20cf9115c2e..e26901f0b7f 100644 --- a/framework/tests/value/DivideByZero.java +++ b/framework/tests/value/DivideByZero.java @@ -5,71 +5,71 @@ import org.checkerframework.common.value.qual.IntVal; public class DivideByZero { - void divideNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { - @DoubleVal(Float.POSITIVE_INFINITY) float a = f / 0; - @DoubleVal(Double.POSITIVE_INFINITY) double b = d / 0l; - @DoubleVal(Double.POSITIVE_INFINITY) double c = i / 0.0; - @DoubleVal(Float.POSITIVE_INFINITY) float e = i / 0.0f; + void divideNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { + @DoubleVal(Float.POSITIVE_INFINITY) float a = f / 0; + @DoubleVal(Double.POSITIVE_INFINITY) double b = d / 0l; + @DoubleVal(Double.POSITIVE_INFINITY) double c = i / 0.0; + @DoubleVal(Float.POSITIVE_INFINITY) float e = i / 0.0f; - // :: error: (assignment.type.incompatible) - @BottomVal float a2 = f / 0; - // :: error: (assignment.type.incompatible) - @BottomVal double b2 = d / 0L; - // :: error: (assignment.type.incompatible) - @BottomVal double c2 = i / 0.0; - // :: error: (assignment.type.incompatible) - @BottomVal float e2 = i / 0.0f; - } + // :: error: (assignment.type.incompatible) + @BottomVal float a2 = f / 0; + // :: error: (assignment.type.incompatible) + @BottomVal double b2 = d / 0L; + // :: error: (assignment.type.incompatible) + @BottomVal double c2 = i / 0.0; + // :: error: (assignment.type.incompatible) + @BottomVal float e2 = i / 0.0f; + } - void remainderNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { - @DoubleVal(Float.NaN) float a = f % 0; - @DoubleVal(Double.NaN) double b = d % 0L; - @DoubleVal(Double.NaN) double c = i % 0.0; - @DoubleVal(Float.NaN) float e = i % 0.0f; + void remainderNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { + @DoubleVal(Float.NaN) float a = f % 0; + @DoubleVal(Double.NaN) double b = d % 0L; + @DoubleVal(Double.NaN) double c = i % 0.0; + @DoubleVal(Float.NaN) float e = i % 0.0f; - // :: error: (assignment.type.incompatible) - @BottomVal float a2 = f % 0; - // :: error: (assignment.type.incompatible) - @BottomVal double b2 = d % 0l; - // :: error: (assignment.type.incompatible) - @BottomVal double c2 = i % 0.0; - // :: error: (assignment.type.incompatible) - @BottomVal float e2 = i % 0.0f; - } + // :: error: (assignment.type.incompatible) + @BottomVal float a2 = f % 0; + // :: error: (assignment.type.incompatible) + @BottomVal double b2 = d % 0l; + // :: error: (assignment.type.incompatible) + @BottomVal double c2 = i % 0.0; + // :: error: (assignment.type.incompatible) + @BottomVal float e2 = i % 0.0f; + } - void integerDivision( - @IntVal(1) long l, - @IntVal(1) int i, - @IntVal(0) byte bZero, - @IntVal(0) short sZero, - @IntVal(0L) long lZero, - @IntVal(0) int iZero) { - @BottomVal long a = l / bZero; - @BottomVal long b = l / sZero; - @BottomVal long c = l / iZero; - @BottomVal long d = l / lZero; + void integerDivision( + @IntVal(1) long l, + @IntVal(1) int i, + @IntVal(0) byte bZero, + @IntVal(0) short sZero, + @IntVal(0L) long lZero, + @IntVal(0) int iZero) { + @BottomVal long a = l / bZero; + @BottomVal long b = l / sZero; + @BottomVal long c = l / iZero; + @BottomVal long d = l / lZero; - @BottomVal long e = i / bZero; - @BottomVal long f = i / sZero; - @BottomVal long g = i / iZero; - @BottomVal long h = i / lZero; - } + @BottomVal long e = i / bZero; + @BottomVal long f = i / sZero; + @BottomVal long g = i / iZero; + @BottomVal long h = i / lZero; + } - void integerRemainder( - @IntVal(1) long l, - @IntVal(1) int i, - @IntVal(0) byte bZero, - @IntVal(0) short sZero, - @IntVal(0L) long lZero, - @IntVal(0) int iZero) { - @BottomVal long a = l % bZero; - @BottomVal long b = l % sZero; - @BottomVal long c = l % iZero; - @BottomVal long d = l % lZero; + void integerRemainder( + @IntVal(1) long l, + @IntVal(1) int i, + @IntVal(0) byte bZero, + @IntVal(0) short sZero, + @IntVal(0L) long lZero, + @IntVal(0) int iZero) { + @BottomVal long a = l % bZero; + @BottomVal long b = l % sZero; + @BottomVal long c = l % iZero; + @BottomVal long d = l % lZero; - @BottomVal long e = i % bZero; - @BottomVal long f = i % sZero; - @BottomVal long g = i % iZero; - @BottomVal long h = i % lZero; - } + @BottomVal long e = i % bZero; + @BottomVal long f = i % sZero; + @BottomVal long g = i % iZero; + @BottomVal long h = i % lZero; + } } diff --git a/framework/tests/value/DoubleRounding.java b/framework/tests/value/DoubleRounding.java index 1baff0c41ac..81bb57a2a39 100644 --- a/framework/tests/value/DoubleRounding.java +++ b/framework/tests/value/DoubleRounding.java @@ -3,9 +3,9 @@ public class DoubleRounding { - final double FLOATING_POINT_DELTA = 1e-15; + final double FLOATING_POINT_DELTA = 1e-15; - void round() { - float f = (float) FLOATING_POINT_DELTA; - } + void round() { + float f = (float) FLOATING_POINT_DELTA; + } } diff --git a/framework/tests/value/EmptyAnnotationArgument.java b/framework/tests/value/EmptyAnnotationArgument.java index 9df39c20b1c..4cc53bc49a6 100644 --- a/framework/tests/value/EmptyAnnotationArgument.java +++ b/framework/tests/value/EmptyAnnotationArgument.java @@ -6,18 +6,18 @@ public class EmptyAnnotationArgument { - // :: warning: (no.values.given) - void mArray(int @ArrayLen({}) [] a) {} + // :: warning: (no.values.given) + void mArray(int @ArrayLen({}) [] a) {} - // :: warning: (no.values.given) - void mBool(@BoolVal({}) boolean arg) {} + // :: warning: (no.values.given) + void mBool(@BoolVal({}) boolean arg) {} - // :: warning: (no.values.given) - void mDouble(@DoubleVal({}) double arg) {} + // :: warning: (no.values.given) + void mDouble(@DoubleVal({}) double arg) {} - // :: warning: (no.values.given) - void mInt(@IntVal({}) int arg) {} + // :: warning: (no.values.given) + void mInt(@IntVal({}) int arg) {} - // :: warning: (no.values.given) - void mString(@StringVal({}) String arg) {} + // :: warning: (no.values.given) + void mString(@StringVal({}) String arg) {} } diff --git a/framework/tests/value/EnclosingClass.java b/framework/tests/value/EnclosingClass.java index a6a0bf576e1..4efea86ea28 100644 --- a/framework/tests/value/EnclosingClass.java +++ b/framework/tests/value/EnclosingClass.java @@ -4,38 +4,38 @@ import java.util.concurrent.Future; public class EnclosingClass { - private MyMap myMap = null; + private MyMap myMap = null; - // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with - // V. - private EnclosingClass catchingMoreGeneric( - final MyFunction fallback) { - AFunction applyFallback = - new AFunction() { - @Override - public MyFuture apply(B exception) throws Exception { - return myMap.apply(fallback, exception); - } + // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with + // V. + private EnclosingClass catchingMoreGeneric( + final MyFunction fallback) { + AFunction applyFallback = + new AFunction() { + @Override + public MyFuture apply(B exception) throws Exception { + return myMap.apply(fallback, exception); + } - @Override - public String toString() { - return fallback.toString(); - } - }; - return null; - } + @Override + public String toString() { + return fallback.toString(); + } + }; + return null; + } - private abstract class MyMap extends IdentityHashMap - implements Closeable { - abstract MyFuture apply(MyFunction transformation, D input) - throws Exception; - } + private abstract class MyMap extends IdentityHashMap + implements Closeable { + abstract MyFuture apply(MyFunction transformation, D input) + throws Exception; + } - public interface MyFunction {} + public interface MyFunction {} - interface AFunction { - MyFuture apply(H input) throws Exception; - } + interface AFunction { + MyFuture apply(H input) throws Exception; + } - interface MyFuture extends Future {} + interface MyFuture extends Future {} } diff --git a/framework/tests/value/EnumConstants.java b/framework/tests/value/EnumConstants.java index 748ea0a8487..10307f3c905 100644 --- a/framework/tests/value/EnumConstants.java +++ b/framework/tests/value/EnumConstants.java @@ -4,172 +4,169 @@ import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.CREATE; -import org.checkerframework.common.value.qual.*; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import org.checkerframework.common.value.qual.*; public class EnumConstants { - enum MyEnum { - VALUE, - OTHER_VALUE, - THIRD_VALUE - } - - static void subtyping1(@EnumVal("VALUE") MyEnum value) { - @EnumVal("VALUE") MyEnum value2 = value; - // :: error: (assignment.type.incompatible) - @EnumVal("OTHER_VALUE") MyEnum value3 = value; - @UnknownVal MyEnum value4 = value; - @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; - } - - static void subtyping2(@EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value) { - // :: error: (assignment.type.incompatible) - @EnumVal("VALUE") MyEnum value2 = value; - // :: error: (assignment.type.incompatible) - @EnumVal("OTHER_VALUE") MyEnum value3 = value; - @UnknownVal MyEnum value4 = value; - @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; - @EnumVal({"VALUE", "OTHER_VALUE", "THIRD_VALUE"}) MyEnum value6 = value; - } - - static void enumConstants() { - @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; - @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum v2 = MyEnum.VALUE; - // :: error: (assignment.type.incompatible) - @EnumVal("OTHER_VALUE") MyEnum v3 = MyEnum.VALUE; - } - - static void enumToString() { - @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; - // NOT toString(), because programmers can override that. .name() is final. - @StringVal("VALUE") String s1 = v1.name(); - } - - // These are just paranoia based on the implementation strategy for enum constant defaulting. - static void nonConstantEnum(MyEnum m) { - // :: error: (assignment.type.incompatible) - @EnumVal("m") MyEnum m2 = m; - // :: error: (assignment.type.incompatible) - @EnumVal("m3") MyEnum m3 = m; - } - - static void enums(@EnumVal("VALUE") MyEnum... enums) {} - - static void testEnums() { - enums(); - enums(MyEnum.VALUE); - // :: error: (argument.type.incompatible) - enums(MyEnum.OTHER_VALUE); - } - - static void testEnumArraysInConditional(boolean append, String filename) throws IOException { - Files.newBufferedWriter( - Paths.get(filename), - UTF_8, - append - ? new StandardOpenOption[] {CREATE, APPEND} - : new StandardOpenOption[] {CREATE}); - } - - public static String unescapeJava(String orig, char c) { - StringBuilder sb = new StringBuilder(); - // The previous escape character was seen just before this position. - int postEsc = 0; - int thisEsc = 0; // orig.indexOf('\\'); - while (thisEsc != -1) { - switch (c) { - case 'n': - sb.append(orig.substring(postEsc, thisEsc)); - sb.append('\n'); // not lineSep - postEsc = thisEsc + 2; - break; - case 'r': - sb.append(orig.substring(postEsc, thisEsc)); - sb.append('\r'); - postEsc = thisEsc + 2; - break; - case 't': - sb.append(orig.substring(postEsc, thisEsc)); - sb.append('\t'); - postEsc = thisEsc + 2; - break; - case '\\': - // This is not in the default case because the search would find - // the quoted backslash. Here we include the first backslash in - // the output, but not the first. - sb.append(orig.substring(postEsc, thisEsc + 1)); - postEsc = thisEsc + 2; - break; - - case 'u': - // Unescape Unicode characters. - sb.append(orig.substring(postEsc, thisEsc)); - char unicodeChar = 0; - int ii = thisEsc + 2; - // The specification permits one or more 'u' characters. - while (ii < orig.length() && orig.charAt(ii) == 'u') { - ii++; - } - // The specification requires exactly 4 hexadecimal characters. - // This is more liberal. (Should it be?) - int limit = Math.min(ii + 4, orig.length()); - while (ii < limit) { - int thisDigit = Character.digit(orig.charAt(ii), 16); - if (thisDigit == -1) { - break; - } - unicodeChar = (char) ((unicodeChar * 16) + thisDigit); - ii++; - } - sb.append(unicodeChar); - postEsc = ii; - break; - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - // Unescape octal characters. - sb.append(orig.substring(postEsc, thisEsc)); - char octalChar = 0; - int iii = thisEsc + 1; - while (iii < Math.min(thisEsc + 4, orig.length())) { - int thisDigit = Character.digit(orig.charAt(iii), 8); - if (thisDigit == -1) { - break; - } - int newValue = (octalChar * 8) + thisDigit; - if (newValue > 0377) { - break; - } - octalChar = (char) newValue; - iii++; - } - sb.append(octalChar); - postEsc = iii; - break; - - default: - // In the default case, retain the character following the backslash, - // but discard the backslash itself. "\*" is just a one-character string. - sb.append(orig.substring(postEsc, thisEsc)); - postEsc = thisEsc + 1; - break; + enum MyEnum { + VALUE, + OTHER_VALUE, + THIRD_VALUE + } + + static void subtyping1(@EnumVal("VALUE") MyEnum value) { + @EnumVal("VALUE") MyEnum value2 = value; + // :: error: (assignment.type.incompatible) + @EnumVal("OTHER_VALUE") MyEnum value3 = value; + @UnknownVal MyEnum value4 = value; + @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; + } + + static void subtyping2(@EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value) { + // :: error: (assignment.type.incompatible) + @EnumVal("VALUE") MyEnum value2 = value; + // :: error: (assignment.type.incompatible) + @EnumVal("OTHER_VALUE") MyEnum value3 = value; + @UnknownVal MyEnum value4 = value; + @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; + @EnumVal({"VALUE", "OTHER_VALUE", "THIRD_VALUE"}) MyEnum value6 = value; + } + + static void enumConstants() { + @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; + @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum v2 = MyEnum.VALUE; + // :: error: (assignment.type.incompatible) + @EnumVal("OTHER_VALUE") MyEnum v3 = MyEnum.VALUE; + } + + static void enumToString() { + @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; + // NOT toString(), because programmers can override that. .name() is final. + @StringVal("VALUE") String s1 = v1.name(); + } + + // These are just paranoia based on the implementation strategy for enum constant defaulting. + static void nonConstantEnum(MyEnum m) { + // :: error: (assignment.type.incompatible) + @EnumVal("m") MyEnum m2 = m; + // :: error: (assignment.type.incompatible) + @EnumVal("m3") MyEnum m3 = m; + } + + static void enums(@EnumVal("VALUE") MyEnum... enums) {} + + static void testEnums() { + enums(); + enums(MyEnum.VALUE); + // :: error: (argument.type.incompatible) + enums(MyEnum.OTHER_VALUE); + } + + static void testEnumArraysInConditional(boolean append, String filename) throws IOException { + Files.newBufferedWriter( + Paths.get(filename), + UTF_8, + append ? new StandardOpenOption[] {CREATE, APPEND} : new StandardOpenOption[] {CREATE}); + } + + public static String unescapeJava(String orig, char c) { + StringBuilder sb = new StringBuilder(); + // The previous escape character was seen just before this position. + int postEsc = 0; + int thisEsc = 0; // orig.indexOf('\\'); + while (thisEsc != -1) { + switch (c) { + case 'n': + sb.append(orig.substring(postEsc, thisEsc)); + sb.append('\n'); // not lineSep + postEsc = thisEsc + 2; + break; + case 'r': + sb.append(orig.substring(postEsc, thisEsc)); + sb.append('\r'); + postEsc = thisEsc + 2; + break; + case 't': + sb.append(orig.substring(postEsc, thisEsc)); + sb.append('\t'); + postEsc = thisEsc + 2; + break; + case '\\': + // This is not in the default case because the search would find + // the quoted backslash. Here we include the first backslash in + // the output, but not the first. + sb.append(orig.substring(postEsc, thisEsc + 1)); + postEsc = thisEsc + 2; + break; + + case 'u': + // Unescape Unicode characters. + sb.append(orig.substring(postEsc, thisEsc)); + char unicodeChar = 0; + int ii = thisEsc + 2; + // The specification permits one or more 'u' characters. + while (ii < orig.length() && orig.charAt(ii) == 'u') { + ii++; + } + // The specification requires exactly 4 hexadecimal characters. + // This is more liberal. (Should it be?) + int limit = Math.min(ii + 4, orig.length()); + while (ii < limit) { + int thisDigit = Character.digit(orig.charAt(ii), 16); + if (thisDigit == -1) { + break; } - thisEsc = orig.indexOf('\\', postEsc); - } - if (postEsc == 0) { - return orig; - } - sb.append(orig.substring(postEsc)); - return sb.toString(); + unicodeChar = (char) ((unicodeChar * 16) + thisDigit); + ii++; + } + sb.append(unicodeChar); + postEsc = ii; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + // Unescape octal characters. + sb.append(orig.substring(postEsc, thisEsc)); + char octalChar = 0; + int iii = thisEsc + 1; + while (iii < Math.min(thisEsc + 4, orig.length())) { + int thisDigit = Character.digit(orig.charAt(iii), 8); + if (thisDigit == -1) { + break; + } + int newValue = (octalChar * 8) + thisDigit; + if (newValue > 0377) { + break; + } + octalChar = (char) newValue; + iii++; + } + sb.append(octalChar); + postEsc = iii; + break; + + default: + // In the default case, retain the character following the backslash, + // but discard the backslash itself. "\*" is just a one-character string. + sb.append(orig.substring(postEsc, thisEsc)); + postEsc = thisEsc + 1; + break; + } + thisEsc = orig.indexOf('\\', postEsc); + } + if (postEsc == 0) { + return orig; } + sb.append(orig.substring(postEsc)); + return sb.toString(); + } } diff --git a/framework/tests/value/EnumValue.java b/framework/tests/value/EnumValue.java index 823986e232d..eefab78314a 100644 --- a/framework/tests/value/EnumValue.java +++ b/framework/tests/value/EnumValue.java @@ -2,70 +2,70 @@ public class EnumValue { - enum Direction { - NORTH, - WEST, - SOUTH, - EAST - }; + enum Direction { + NORTH, + WEST, + SOUTH, + EAST + }; - public enum Color { - BLUE, - RED, - GREEN - }; + public enum Color { + BLUE, + RED, + GREEN + }; - private enum Fruit { - APPLE, - ORANGE, - PEAR - }; + private enum Fruit { + APPLE, + ORANGE, + PEAR + }; - void simpleTest() { - Direction @ArrayLen(4) [] myCompass = Direction.values(); - Color @ArrayLen(3) [] myColors = Color.values(); - Fruit @ArrayLen(3) [] myFruitBasket = Fruit.values(); + void simpleTest() { + Direction @ArrayLen(4) [] myCompass = Direction.values(); + Color @ArrayLen(3) [] myColors = Color.values(); + Fruit @ArrayLen(3) [] myFruitBasket = Fruit.values(); - // :: error: (assignment.type.incompatible) - Direction @ArrayLen(7) [] badCompass = Direction.values(); + // :: error: (assignment.type.incompatible) + Direction @ArrayLen(7) [] badCompass = Direction.values(); - // :: error: (assignment.type.incompatible) - Color @ArrayLen(4) [] badColors = Color.values(); + // :: error: (assignment.type.incompatible) + Color @ArrayLen(4) [] badColors = Color.values(); - // :: error: (assignment.type.incompatible) - Fruit @ArrayLen(2) [] badFruit = Fruit.values(); - } + // :: error: (assignment.type.incompatible) + Fruit @ArrayLen(2) [] badFruit = Fruit.values(); + } - public enum AdvDirection { - ANORTH { - public AdvDirection getOpposite() { - return ASOUTH; - } - }, - AEAST { - public AdvDirection getOpposite() { - return AWEST; - } - }, - ASOUTH { - public AdvDirection getOpposite() { - return ANORTH; - } - }, - AWEST { - public AdvDirection getOpposite() { - return AEAST; - } - }; + public enum AdvDirection { + ANORTH { + public AdvDirection getOpposite() { + return ASOUTH; + } + }, + AEAST { + public AdvDirection getOpposite() { + return AWEST; + } + }, + ASOUTH { + public AdvDirection getOpposite() { + return ANORTH; + } + }, + AWEST { + public AdvDirection getOpposite() { + return AEAST; + } + }; - public abstract AdvDirection getOpposite(); - } + public abstract AdvDirection getOpposite(); + } - void advTest() { - AdvDirection @ArrayLen(4) [] myCompass = AdvDirection.values(); - // :: error: (assignment.type.incompatible) - AdvDirection @ArrayLen(3) [] badCompass = AdvDirection.values(); - // :: error: (assignment.type.incompatible) - AdvDirection @ArrayLen(5) [] badCompass2 = AdvDirection.values(); - } + void advTest() { + AdvDirection @ArrayLen(4) [] myCompass = AdvDirection.values(); + // :: error: (assignment.type.incompatible) + AdvDirection @ArrayLen(3) [] badCompass = AdvDirection.values(); + // :: error: (assignment.type.incompatible) + AdvDirection @ArrayLen(5) [] badCompass2 = AdvDirection.values(); + } } diff --git a/framework/tests/value/ExceptionTest.java b/framework/tests/value/ExceptionTest.java index 5a7cdf29b27..6771207a6c2 100644 --- a/framework/tests/value/ExceptionTest.java +++ b/framework/tests/value/ExceptionTest.java @@ -2,10 +2,10 @@ public class ExceptionTest { - public void foo() { - int indexTooBig = 5; - String s = "hello"; - // :: warning: (method.evaluation.exception) - char c = s.charAt(indexTooBig); - } + public void foo() { + int indexTooBig = 5; + String s = "hello"; + // :: warning: (method.evaluation.exception) + char c = s.charAt(indexTooBig); + } } diff --git a/framework/tests/value/Fields.java b/framework/tests/value/Fields.java index c88492978e4..4de154bdd0d 100644 --- a/framework/tests/value/Fields.java +++ b/framework/tests/value/Fields.java @@ -1,55 +1,54 @@ -import org.checkerframework.common.value.qual.*; - import javax.swing.plaf.BorderUIResource; +import org.checkerframework.common.value.qual.*; public class Fields { - static final int field = 1; + static final int field = 1; - public void innerClassFields() { - @IntVal({9}) int x = java.util.zip.Deflater.BEST_COMPRESSION; - @IntVal({4}) int a = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; - // :: error: (assignment.type.incompatible) - @IntVal({0}) int b = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; - } + public void innerClassFields() { + @IntVal({9}) int x = java.util.zip.Deflater.BEST_COMPRESSION; + @IntVal({4}) int a = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; + // :: error: (assignment.type.incompatible) + @IntVal({0}) int b = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; + } - public void inClassFields() { - @IntVal({1}) int a = field; - // :: error: (assignment.type.incompatible) - @IntVal({0}) int b = field; - } + public void inClassFields() { + @IntVal({1}) int a = field; + // :: error: (assignment.type.incompatible) + @IntVal({0}) int b = field; + } - public void otherClassFields() { - @IntVal({56319}) char x = Character.MAX_HIGH_SURROGATE; - @IntVal({16}) byte y = Character.FORMAT; + public void otherClassFields() { + @IntVal({56319}) char x = Character.MAX_HIGH_SURROGATE; + @IntVal({16}) byte y = Character.FORMAT; - @BoolVal({false}) boolean a = Boolean.FALSE; - // :: error: (assignment.type.incompatible) - a = Boolean.TRUE; + @BoolVal({false}) boolean a = Boolean.FALSE; + // :: error: (assignment.type.incompatible) + a = Boolean.TRUE; - @IntVal({4}) int b = java.util.Calendar.MAY; - // :: error: (assignment.type.incompatible) - b = java.util.Calendar.APRIL; + @IntVal({4}) int b = java.util.Calendar.MAY; + // :: error: (assignment.type.incompatible) + b = java.util.Calendar.APRIL; - @IntVal({9}) int c = java.util.zip.Deflater.BEST_COMPRESSION; - // :: error: (assignment.type.incompatible) - c = java.util.zip.Deflater.BEST_SPEED; + @IntVal({9}) int c = java.util.zip.Deflater.BEST_COMPRESSION; + // :: error: (assignment.type.incompatible) + c = java.util.zip.Deflater.BEST_SPEED; - @IntVal({1024}) int d = java.awt.GridBagConstraints.ABOVE_BASELINE; - // :: error: (assignment.type.incompatible) - d = java.awt.GridBagConstraints.LAST_LINE_END; - } + @IntVal({1024}) int d = java.awt.GridBagConstraints.ABOVE_BASELINE; + // :: error: (assignment.type.incompatible) + d = java.awt.GridBagConstraints.LAST_LINE_END; + } - void innerFieldTest() { - @StringVal("section_number") String a = InnerStaticClass.INNER_STATIC_FIELD; + void innerFieldTest() { + @StringVal("section_number") String a = InnerStaticClass.INNER_STATIC_FIELD; - // :: error: (assignment.type.incompatible) - @StringVal("") String b = InnerStaticClass.INNER_STATIC_FIELD; - } + // :: error: (assignment.type.incompatible) + @StringVal("") String b = InnerStaticClass.INNER_STATIC_FIELD; + } - static final int fieldDeclAtBottom = 1; + static final int fieldDeclAtBottom = 1; - public static class InnerStaticClass { - public static final String INNER_STATIC_FIELD = "section_number"; - } + public static class InnerStaticClass { + public static final String INNER_STATIC_FIELD = "section_number"; + } } diff --git a/framework/tests/value/GTETransferBug.java b/framework/tests/value/GTETransferBug.java index 8a62d0bb961..e9de46bd58e 100644 --- a/framework/tests/value/GTETransferBug.java +++ b/framework/tests/value/GTETransferBug.java @@ -1,12 +1,12 @@ import org.checkerframework.common.value.qual.*; public class GTETransferBug { - void gte_bad_check(int[] a) { - if (a.length >= 1) { - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 2) [] b = a; + void gte_bad_check(int[] a) { + if (a.length >= 1) { + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 2) [] b = a; - int @ArrayLenRange(from = 1) [] c = a; - } + int @ArrayLenRange(from = 1) [] c = a; } + } } diff --git a/framework/tests/value/Issue1214.java b/framework/tests/value/Issue1214.java index bbe2d39bb2e..13ef11b3fd6 100644 --- a/framework/tests/value/Issue1214.java +++ b/framework/tests/value/Issue1214.java @@ -5,66 +5,66 @@ public class Issue1214 { - static void noException() { - int n = 0; - try { - } catch (Exception e) { - n = 1; - } - @IntVal(0) int ok = n; + static void noException() { + int n = 0; + try { + } catch (Exception e) { + n = 1; } + @IntVal(0) int ok = n; + } - static void arrayAccess(String[] array) { - int n = 0; - try { - String s = array[0]; - } catch (NullPointerException e) { - n = 1; - } catch (ArrayIndexOutOfBoundsException e) { - n = 2; - } catch (Exception e) { - n = 3; - } - @IntVal({0, 1, 2}) int ok = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 1}) int ng1 = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 2}) int ng2 = n; - // :: error: (assignment.type.incompatible) - @IntVal(0) int ng3 = n; + static void arrayAccess(String[] array) { + int n = 0; + try { + String s = array[0]; + } catch (NullPointerException e) { + n = 1; + } catch (ArrayIndexOutOfBoundsException e) { + n = 2; + } catch (Exception e) { + n = 3; } + @IntVal({0, 1, 2}) int ok = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 1}) int ng1 = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 2}) int ng2 = n; + // :: error: (assignment.type.incompatible) + @IntVal(0) int ng3 = n; + } - static void forArray(String[] array) { - int n = 0; - try { - for (String s : array) {} - } catch (NullPointerException e) { - n = 1; - } catch (ArrayIndexOutOfBoundsException e) { - n = 2; - } catch (Exception e) { - n = 3; - } - @IntVal({0, 1}) int ok = n; - // :: error: (assignment.type.incompatible) - @IntVal(0) int ng = n; + static void forArray(String[] array) { + int n = 0; + try { + for (String s : array) {} + } catch (NullPointerException e) { + n = 1; + } catch (ArrayIndexOutOfBoundsException e) { + n = 2; + } catch (Exception e) { + n = 3; } + @IntVal({0, 1}) int ok = n; + // :: error: (assignment.type.incompatible) + @IntVal(0) int ng = n; + } - static void forIterable(Iterable itr) { - int n = 0; - try { - for (String s : itr) {} - } catch (NullPointerException e) { - n = 1; - } catch (Exception e) { - n = 2; - } - @IntVal({0, 1, 2}) int ok = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 1}) int ng1 = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 2}) int ng2 = n; - // :: error: (assignment.type.incompatible) - @IntVal(0) int ng3 = n; + static void forIterable(Iterable itr) { + int n = 0; + try { + for (String s : itr) {} + } catch (NullPointerException e) { + n = 1; + } catch (Exception e) { + n = 2; } + @IntVal({0, 1, 2}) int ok = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 1}) int ng1 = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 2}) int ng2 = n; + // :: error: (assignment.type.incompatible) + @IntVal(0) int ng3 = n; + } } diff --git a/framework/tests/value/Issue1218.java b/framework/tests/value/Issue1218.java index 111def83191..54e421070b4 100644 --- a/framework/tests/value/Issue1218.java +++ b/framework/tests/value/Issue1218.java @@ -1,145 +1,144 @@ // Test case for Issue 1218: // https://github.com/typetools/checker-framework/issues/1218 -import org.checkerframework.common.value.qual.*; - import java.io.Serializable; +import org.checkerframework.common.value.qual.*; public class Issue1218 { - enum MyEnum { - A, - B, - C; - } - - class ForString { - ForString(String @MinLen(2) ... strs) {} - } - - class ForInt { - ForInt(@IntVal({1, 2, 3}) int @MinLen(2) ... strs) {} - } - - class ForEnum> { - @SafeVarargs - ForEnum(E @MinLen(2) ... enums) {} - } - - class ForAny { - @SafeVarargs - ForAny(T @MinLen(3) ... anys) {} - } + enum MyEnum { + A, + B, + C; + } - void strs(String @MinLen(2) ... strs) {} + class ForString { + ForString(String @MinLen(2) ... strs) {} + } - void ints(@IntVal({1, 2, 3}) int @MinLen(2) ... ints) {} + class ForInt { + ForInt(@IntVal({1, 2, 3}) int @MinLen(2) ... strs) {} + } + class ForEnum> { @SafeVarargs - final > void enums(E @MinLen(2) ... enums) {} + ForEnum(E @MinLen(2) ... enums) {} + } + class ForAny { @SafeVarargs - final void anys(T @MinLen(3) ... anys) {} - - void testMethodCall() { - // :: error: (varargs.type.incompatible) - strs(); - // :: error: (varargs.type.incompatible) - strs(""); - strs("", ""); - // type of arg should be @UnknownVal String @BottomVal [] - strs((String[]) null); - - String[] args0 = {""}; - String[] args1 = {""}; - String[] args2 = {"", ""}; - - // :: error: (argument.type.incompatible) - strs(args0); - // :: error: (argument.type.incompatible) - strs(args1); - strs(args2); - - ints(1, 2); - // :: error: (argument.type.incompatible) - ints(0, 0, 0); - // :: error: (varargs.type.incompatible) - ints(3); - // type of arg should be @IntVal(1) int @BottomVal [] - ints((@IntVal(1) int[]) null); - } - - // Inferred enumval types are incompatible with >. Similar code - // works if the type is a specific enum; see the test file Enums.java for an example. - @SuppressWarnings("type.argument.type.incompatible") - void testMethodCallTypeInferred() { - // :: error: (varargs.type.incompatible) - enums(); - // :: error: (varargs.type.incompatible) - enums(MyEnum.A); - enums(MyEnum.A, MyEnum.B); - enums(MyEnum.A, MyEnum.B, MyEnum.C); - } - - & Serializable> void testMethodCallTypeInferredIntersection() { - T t = null; - - // :: error: (varargs.type.incompatible) - anys(1, 1.0); - // :: error: (varargs.type.incompatible) - anys(1, ""); - anys(1, 1.0, ""); - // :: error: (varargs.type.incompatible) - anys(1, t); - anys(1, t, ""); - } - - void testConstructorCall() { - // :: error: (varargs.type.incompatible) - new ForString(); - // :: error: (varargs.type.incompatible) - new ForString(""); - new ForString("", ""); - // type of arg should be @UnknownVal String @BottomVal [] - new ForString((String[]) null); - - String[] args0 = {""}; - String[] args1 = {""}; - String[] args2 = {"", ""}; - - // :: error: (argument.type.incompatible) - new ForString(args0); - // :: error: (argument.type.incompatible) - new ForString(args1); - new ForString(args2); - - new ForInt(1, 2); - // :: error: (argument.type.incompatible) - new ForInt(0, 0, 0); - // :: error: (varargs.type.incompatible) - new ForInt(3); - // type of arg should be @IntVal(1) int @BottomVal [] - ints((@IntVal(1) int[]) null); - } - - void testConstructorCallTypeInferred() { - // :: error: (varargs.type.incompatible) - new ForEnum<>(MyEnum.A); - new ForEnum<>(MyEnum.A, MyEnum.B); - new ForEnum<>(MyEnum.A, MyEnum.B, MyEnum.C); - } - - @SuppressWarnings("unchecked") - & Serializable> void testConstructorCallTypeInferredIntersection() { - T t = null; - - // :: error: (varargs.type.incompatible) - new ForAny<>(1, 1.0); - // :: error: (varargs.type.incompatible) - new ForAny<>(1, ""); - new ForAny<>(1, 1.0, ""); - // :: error: (varargs.type.incompatible) - new ForAny<>(1, t); - new ForAny<>(1, t, ""); - } + ForAny(T @MinLen(3) ... anys) {} + } + + void strs(String @MinLen(2) ... strs) {} + + void ints(@IntVal({1, 2, 3}) int @MinLen(2) ... ints) {} + + @SafeVarargs + final > void enums(E @MinLen(2) ... enums) {} + + @SafeVarargs + final void anys(T @MinLen(3) ... anys) {} + + void testMethodCall() { + // :: error: (varargs.type.incompatible) + strs(); + // :: error: (varargs.type.incompatible) + strs(""); + strs("", ""); + // type of arg should be @UnknownVal String @BottomVal [] + strs((String[]) null); + + String[] args0 = {""}; + String[] args1 = {""}; + String[] args2 = {"", ""}; + + // :: error: (argument.type.incompatible) + strs(args0); + // :: error: (argument.type.incompatible) + strs(args1); + strs(args2); + + ints(1, 2); + // :: error: (argument.type.incompatible) + ints(0, 0, 0); + // :: error: (varargs.type.incompatible) + ints(3); + // type of arg should be @IntVal(1) int @BottomVal [] + ints((@IntVal(1) int[]) null); + } + + // Inferred enumval types are incompatible with >. Similar code + // works if the type is a specific enum; see the test file Enums.java for an example. + @SuppressWarnings("type.argument.type.incompatible") + void testMethodCallTypeInferred() { + // :: error: (varargs.type.incompatible) + enums(); + // :: error: (varargs.type.incompatible) + enums(MyEnum.A); + enums(MyEnum.A, MyEnum.B); + enums(MyEnum.A, MyEnum.B, MyEnum.C); + } + + & Serializable> void testMethodCallTypeInferredIntersection() { + T t = null; + + // :: error: (varargs.type.incompatible) + anys(1, 1.0); + // :: error: (varargs.type.incompatible) + anys(1, ""); + anys(1, 1.0, ""); + // :: error: (varargs.type.incompatible) + anys(1, t); + anys(1, t, ""); + } + + void testConstructorCall() { + // :: error: (varargs.type.incompatible) + new ForString(); + // :: error: (varargs.type.incompatible) + new ForString(""); + new ForString("", ""); + // type of arg should be @UnknownVal String @BottomVal [] + new ForString((String[]) null); + + String[] args0 = {""}; + String[] args1 = {""}; + String[] args2 = {"", ""}; + + // :: error: (argument.type.incompatible) + new ForString(args0); + // :: error: (argument.type.incompatible) + new ForString(args1); + new ForString(args2); + + new ForInt(1, 2); + // :: error: (argument.type.incompatible) + new ForInt(0, 0, 0); + // :: error: (varargs.type.incompatible) + new ForInt(3); + // type of arg should be @IntVal(1) int @BottomVal [] + ints((@IntVal(1) int[]) null); + } + + void testConstructorCallTypeInferred() { + // :: error: (varargs.type.incompatible) + new ForEnum<>(MyEnum.A); + new ForEnum<>(MyEnum.A, MyEnum.B); + new ForEnum<>(MyEnum.A, MyEnum.B, MyEnum.C); + } + + @SuppressWarnings("unchecked") + & Serializable> void testConstructorCallTypeInferredIntersection() { + T t = null; + + // :: error: (varargs.type.incompatible) + new ForAny<>(1, 1.0); + // :: error: (varargs.type.incompatible) + new ForAny<>(1, ""); + new ForAny<>(1, 1.0, ""); + // :: error: (varargs.type.incompatible) + new ForAny<>(1, t); + new ForAny<>(1, t, ""); + } } diff --git a/framework/tests/value/Issue1229.java b/framework/tests/value/Issue1229.java index 6069c91464b..93371938a96 100644 --- a/framework/tests/value/Issue1229.java +++ b/framework/tests/value/Issue1229.java @@ -2,18 +2,18 @@ public class Issue1229 { - Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; + Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; - @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; + @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; - void test() { + void test() { - @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; - } + @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; + } } diff --git a/framework/tests/value/Issue1423.java b/framework/tests/value/Issue1423.java index f56ffe71f37..571d39045ff 100644 --- a/framework/tests/value/Issue1423.java +++ b/framework/tests/value/Issue1423.java @@ -4,11 +4,11 @@ import org.checkerframework.common.value.qual.IntRange; public class Issue1423 { - void loop(int i) { - int a = 0; - while (i >= 2) { - @IntRange(from = 2) int i2 = i; - ++a; - } + void loop(int i) { + int a = 0; + while (i >= 2) { + @IntRange(from = 2) int i2 = i; + ++a; } + } } diff --git a/framework/tests/value/Issue1579.java b/framework/tests/value/Issue1579.java index f0b01b64370..fda84f1c533 100644 --- a/framework/tests/value/Issue1579.java +++ b/framework/tests/value/Issue1579.java @@ -2,9 +2,9 @@ // https://github.com/typetools/checker-framework/issues/1579 public class Issue1579 { - public int[][] method(int[] array1, int[] array2) { - // Required for crash - for (int i = 0; i < array1.length; i++) {} - return new int[][] {array1, array2}; - } + public int[][] method(int[] array1, int[] array2) { + // Required for crash + for (int i = 0; i < array1.length; i++) {} + return new int[][] {array1, array2}; + } } diff --git a/framework/tests/value/Issue1580.java b/framework/tests/value/Issue1580.java index e522d877969..c1d5de029d0 100644 --- a/framework/tests/value/Issue1580.java +++ b/framework/tests/value/Issue1580.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/1580 public class Issue1580> { - protected final Gen field; + protected final Gen field; - protected Issue1580(K parent) { - field = parent.field; - } + protected Issue1580(K parent) { + field = parent.field; + } - static class Gen {} + static class Gen {} } diff --git a/framework/tests/value/Issue1655.java b/framework/tests/value/Issue1655.java index 162dc79354e..6bc027c3ce8 100644 --- a/framework/tests/value/Issue1655.java +++ b/framework/tests/value/Issue1655.java @@ -5,9 +5,9 @@ public class Issue1655 { - public void test(int a) { - @IntRange(from = 0, to = 255) int b = a & 0xff; - @IntRange(from = 0, to = 15) int c1 = b >> 4; - @IntRange(from = 0, to = 15) int c2 = b >>> 4; - } + public void test(int a) { + @IntRange(from = 0, to = 255) int b = a & 0xff; + @IntRange(from = 0, to = 15) int c1 = b >> 4; + @IntRange(from = 0, to = 15) int c2 = b >>> 4; + } } diff --git a/framework/tests/value/Issue2353.java b/framework/tests/value/Issue2353.java index bc66be2763c..bcc53c046a6 100644 --- a/framework/tests/value/Issue2353.java +++ b/framework/tests/value/Issue2353.java @@ -1,7 +1,7 @@ @SuppressWarnings({"deprecation", "removal"}) // `new Integer() is deprecated in Java 9. public class Issue2353 { - public static void play() { - Integer a = new Integer("2"); - } + public static void play() { + Integer a = new Integer("2"); + } } diff --git a/framework/tests/value/Issue2367.java b/framework/tests/value/Issue2367.java index 8ab6c16d8d0..b165f6ddb15 100644 --- a/framework/tests/value/Issue2367.java +++ b/framework/tests/value/Issue2367.java @@ -2,23 +2,23 @@ public class Issue2367 { - // Within the signed byte range + // Within the signed byte range - byte b1 = 75; - byte b2 = (byte) 75; - byte b3 = (byte) -120; + byte b1 = 75; + byte b2 = (byte) 75; + byte b3 = (byte) -120; - // Outside the signed byte range + // Outside the signed byte range - // Without the `(byte)` cast, all of these produce the following javac error: - // error: incompatible types: possible lossy conversion from int to byte - // The Value Checker's `cast.unsafe` error is analogous and is desirable. + // Without the `(byte)` cast, all of these produce the following javac error: + // error: incompatible types: possible lossy conversion from int to byte + // The Value Checker's `cast.unsafe` error is analogous and is desirable. - byte b4 = (byte) 139; // b4 == -117 - byte b5 = (byte) -240; - byte b6 = (byte) 251; + byte b4 = (byte) 139; // b4 == -117 + byte b5 = (byte) -240; + byte b6 = (byte) 251; - // Outside the signed byte range, but written as a hexadecimal literal. + // Outside the signed byte range, but written as a hexadecimal literal. - byte b7 = (byte) 0x8B; // 0x8B == 137, and b4 == -117 + byte b7 = (byte) 0x8B; // 0x8B == 137, and b4 == -117 } diff --git a/framework/tests/value/Issue3001.java b/framework/tests/value/Issue3001.java index 2b3257af62b..ea1090af28a 100644 --- a/framework/tests/value/Issue3001.java +++ b/framework/tests/value/Issue3001.java @@ -1,7 +1,7 @@ public class Issue3001 { - private T getMember(Class type) { - T sym = getMember(type); - return sym; - } + private T getMember(Class type) { + T sym = getMember(type); + return sym; + } } diff --git a/framework/tests/value/Issue3105.java b/framework/tests/value/Issue3105.java index fb6119a5b6c..fe68f84f848 100644 --- a/framework/tests/value/Issue3105.java +++ b/framework/tests/value/Issue3105.java @@ -6,27 +6,27 @@ import org.checkerframework.framework.testchecker.lib.Issue3105Fields; public class Issue3105 { - class Demo1 { - @StringVal("foo") String m() { - return Issue3105Fields.FIELD1; - } + class Demo1 { + @StringVal("foo") String m() { + return Issue3105Fields.FIELD1; } + } - class Demo2 extends Issue3105Fields { - @StringVal("foo") String m() { - return FIELD1; - } + class Demo2 extends Issue3105Fields { + @StringVal("foo") String m() { + return FIELD1; } + } - class Demo3 { - @StringVal("bar") String m() { - return Issue3105Fields.FIELD2; - } + class Demo3 { + @StringVal("bar") String m() { + return Issue3105Fields.FIELD2; } + } - class Demo4 extends Issue3105Fields { - @StringVal("bar") String m() { - return FIELD2; - } + class Demo4 extends Issue3105Fields { + @StringVal("bar") String m() { + return FIELD2; } + } } diff --git a/framework/tests/value/Issue3105FieldInSameClass.java b/framework/tests/value/Issue3105FieldInSameClass.java index ac8ab24fa58..b2c01f82bd3 100644 --- a/framework/tests/value/Issue3105FieldInSameClass.java +++ b/framework/tests/value/Issue3105FieldInSameClass.java @@ -3,17 +3,17 @@ import org.checkerframework.common.value.qual.StringVal; public class Issue3105FieldInSameClass { - public static final String FIELD1 = "foo"; + public static final String FIELD1 = "foo"; } class Demo1 { - @StringVal("foo") String m() { - return Issue3105FieldInSameClass.FIELD1; - } + @StringVal("foo") String m() { + return Issue3105FieldInSameClass.FIELD1; + } } class Demo2 extends Issue3105FieldInSameClass { - @StringVal("foo") String m() { - return FIELD1; - } + @StringVal("foo") String m() { + return FIELD1; + } } diff --git a/framework/tests/value/Issue3105StaticImport.java b/framework/tests/value/Issue3105StaticImport.java index 782d7daa52f..a750c488534 100644 --- a/framework/tests/value/Issue3105StaticImport.java +++ b/framework/tests/value/Issue3105StaticImport.java @@ -6,7 +6,7 @@ public class Issue3105StaticImport { - @StringVal("bar") String m2() { - return FIELD2; - } + @StringVal("bar") String m2() { + return FIELD2; + } } diff --git a/framework/tests/value/Issue3307.java b/framework/tests/value/Issue3307.java index 6446a2e42d7..ab007bbef22 100644 --- a/framework/tests/value/Issue3307.java +++ b/framework/tests/value/Issue3307.java @@ -1,17 +1,17 @@ // Test case for https://github.com/typetools/checker-framework/issues/3307 public class Issue3307 extends A { - private int a = 0; + private int a = 0; - void bar(int value) { - if (a != value) { - a = value; - foo(value); - } + void bar(int value) { + if (a != value) { + a = value; + foo(value); } + } } @SuppressWarnings("unchecked") abstract class A { - protected final void foo(B... values) {} + protected final void foo(B... values) {} } diff --git a/framework/tests/value/Issue867.java b/framework/tests/value/Issue867.java index 3732c1568a6..7e11b028cd8 100644 --- a/framework/tests/value/Issue867.java +++ b/framework/tests/value/Issue867.java @@ -4,65 +4,65 @@ import org.checkerframework.common.value.qual.*; public class Issue867 { - void test1() { - @IntVal({0, 1}) int x = 0; - @IntVal(0) int zero = x++; - @IntVal(1) int one = x; - // :: error: (unary.increment.type.incompatible) - x++; + void test1() { + @IntVal({0, 1}) int x = 0; + @IntVal(0) int zero = x++; + @IntVal(1) int one = x; + // :: error: (unary.increment.type.incompatible) + x++; - x = 1; - one = x--; - zero = x; - // :: error: (unary.decrement.type.incompatible) - x--; - } + x = 1; + one = x--; + zero = x; + // :: error: (unary.decrement.type.incompatible) + x--; + } - void test2() { - @IntVal({0, 1, 2}) int x = 0; - @IntVal(1) int one = x++ + x++; - @IntVal(2) int two = x; - // :: error: (unary.increment.type.incompatible) - x++; + void test2() { + @IntVal({0, 1, 2}) int x = 0; + @IntVal(1) int one = x++ + x++; + @IntVal(2) int two = x; + // :: error: (unary.increment.type.incompatible) + x++; - x = 2; - @IntVal(3) int three = x-- + x--; - @IntVal(0) int zero = x; - // :: error: (unary.decrement.type.incompatible) - x--; - } + x = 2; + @IntVal(3) int three = x-- + x--; + @IntVal(0) int zero = x; + // :: error: (unary.decrement.type.incompatible) + x--; + } - void test3() { - @IntVal({0, 1, 2}) int x = 0; - @IntVal(2) int two = x++ + ++x; - two = x; - // :: error: (unary.increment.type.incompatible) - x++; + void test3() { + @IntVal({0, 1, 2}) int x = 0; + @IntVal(2) int two = x++ + ++x; + two = x; + // :: error: (unary.increment.type.incompatible) + x++; - x = 2; - two = x-- + --x; - @IntVal(0) int zero = x; - // :: error: (unary.decrement.type.incompatible) - x--; - } + x = 2; + two = x-- + --x; + @IntVal(0) int zero = x; + // :: error: (unary.decrement.type.incompatible) + x--; + } - void test4() { - @IntVal({0, 1}) int x = 0; - m0(x++); - // :: error: (argument.type.incompatible) - m0(x); - // :: error: (unary.increment.type.incompatible) - m1(x++); + void test4() { + @IntVal({0, 1}) int x = 0; + m0(x++); + // :: error: (argument.type.incompatible) + m0(x); + // :: error: (unary.increment.type.incompatible) + m1(x++); - x = 1; - m1(x--); - // :: error: (argument.type.incompatible) - m1(x); - // :: error: (unary.decrement.type.incompatible) - m0(x--); - } + x = 1; + m1(x--); + // :: error: (argument.type.incompatible) + m1(x); + // :: error: (unary.decrement.type.incompatible) + m0(x--); + } - void m0(@IntVal(0) int x) {} + void m0(@IntVal(0) int x) {} - void m1(@IntVal(1) int x) {} + void m1(@IntVal(1) int x) {} } diff --git a/framework/tests/value/LengthTransferForMinLen.java b/framework/tests/value/LengthTransferForMinLen.java index 172200e510b..00066a408eb 100644 --- a/framework/tests/value/LengthTransferForMinLen.java +++ b/framework/tests/value/LengthTransferForMinLen.java @@ -1,23 +1,23 @@ import org.checkerframework.common.value.qual.*; public class LengthTransferForMinLen { - void exceptional_control_flow(int[] a) { - if (a.length == 0) { - throw new IllegalArgumentException(); - } - int @MinLen(1) [] b = a; + void exceptional_control_flow(int[] a) { + if (a.length == 0) { + throw new IllegalArgumentException(); } + int @MinLen(1) [] b = a; + } - void equal_to_return(int[] a) { - if (a.length == 0) { - return; - } - int @MinLen(1) [] b = a; + void equal_to_return(int[] a) { + if (a.length == 0) { + return; } + int @MinLen(1) [] b = a; + } - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; - } + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; } + } } diff --git a/framework/tests/value/LiteralArray.java b/framework/tests/value/LiteralArray.java index 71d6ae75a93..7251f4420a4 100644 --- a/framework/tests/value/LiteralArray.java +++ b/framework/tests/value/LiteralArray.java @@ -2,11 +2,11 @@ public class LiteralArray { - private static final String[] timeFormat = { - ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), - }; + private static final String[] timeFormat = { + ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), + }; - public void test() { - String @ArrayLen(5) [] array = timeFormat; - } + public void test() { + String @ArrayLen(5) [] array = timeFormat; + } } diff --git a/framework/tests/value/LongMax.java b/framework/tests/value/LongMax.java index be4a68edb71..d0ad214fe25 100644 --- a/framework/tests/value/LongMax.java +++ b/framework/tests/value/LongMax.java @@ -8,8 +8,8 @@ // infinitely growing the list of values. public class LongMax { - public void longMaxRange() { - @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long i = 9223372036854775807l; - @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long j = i; - } + public void longMaxRange() { + @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long i = 9223372036854775807l; + @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long j = i; + } } diff --git a/framework/tests/value/Loop.java b/framework/tests/value/Loop.java index 8f81799c0a1..ded95548684 100644 --- a/framework/tests/value/Loop.java +++ b/framework/tests/value/Loop.java @@ -1,37 +1,36 @@ +import java.util.Iterator; +import java.util.Set; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.BottomVal; import org.checkerframework.common.value.qual.IntVal; -import java.util.Iterator; -import java.util.Set; - public class Loop { - void test1(final double @ArrayLen(20) [] f2) { - int x; - for (int g = 0; g < f2.length; g++) { - x = g; - } - // :: error: (assignment.type.incompatible) - double @BottomVal [] test = f2; - // :: error: (assignment.type.incompatible) - @BottomVal int q = f2.length; + void test1(final double @ArrayLen(20) [] f2) { + int x; + for (int g = 0; g < f2.length; g++) { + x = g; } + // :: error: (assignment.type.incompatible) + double @BottomVal [] test = f2; + // :: error: (assignment.type.incompatible) + @BottomVal int q = f2.length; + } - void test2(final @IntVal(20) int param) { - int x; - for (int g = 0; g < param; g++) { - x = g; - } - // :: error: (assignment.type.incompatible) - @BottomVal int q = param; + void test2(final @IntVal(20) int param) { + int x; + for (int g = 0; g < param; g++) { + x = g; } + // :: error: (assignment.type.incompatible) + @BottomVal int q = param; + } - private void test3(Set set) { - String[] array = new String[set.size()]; - int i = 0; - for (Iterator iter = set.iterator(); iter.hasNext(); ) { - String key = iter.next(); - array[i++] = key; - } + private void test3(Set set) { + String[] array = new String[set.size()]; + int i = 0; + for (Iterator iter = set.iterator(); iter.hasNext(); ) { + String key = iter.next(); + array[i++] = key; } + } } diff --git a/framework/tests/value/LubToRange.java b/framework/tests/value/LubToRange.java index dcbb697626c..d83c7647617 100644 --- a/framework/tests/value/LubToRange.java +++ b/framework/tests/value/LubToRange.java @@ -6,13 +6,13 @@ import org.checkerframework.common.value.qual.IntVal; public class LubToRange { - public static boolean flag = false; + public static boolean flag = false; - void test(@IntVal({1, 2, 3, 4, 5}) int x, @IntVal({6, 7, 8, 9, 10, 11}) int y) { - @IntRange(from = 1, to = 11) int z = flag ? x : y; - } + void test(@IntVal({1, 2, 3, 4, 5}) int x, @IntVal({6, 7, 8, 9, 10, 11}) int y) { + @IntRange(from = 1, to = 11) int z = flag ? x : y; + } - void test2(int @ArrayLen({1, 2, 3, 4, 5}) [] x, int @ArrayLen({6, 7, 8, 9, 10, 11}) [] y) { - int @ArrayLenRange(from = 1, to = 11) [] z = flag ? x : y; - } + void test2(int @ArrayLen({1, 2, 3, 4, 5}) [] x, int @ArrayLen({6, 7, 8, 9, 10, 11}) [] y) { + int @ArrayLenRange(from = 1, to = 11) [] z = flag ? x : y; + } } diff --git a/framework/tests/value/MLEqualTo.java b/framework/tests/value/MLEqualTo.java index 27463b19d80..abfd04d8d4b 100644 --- a/framework/tests/value/MLEqualTo.java +++ b/framework/tests/value/MLEqualTo.java @@ -2,9 +2,9 @@ public class MLEqualTo { - public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { - if (r == m) { - int @MinLen(2) [] j = r; - } + public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { + if (r == m) { + int @MinLen(2) [] j = r; } + } } diff --git a/framework/tests/value/MathMinMax.java b/framework/tests/value/MathMinMax.java index 11cb5303a14..917df83a35d 100644 --- a/framework/tests/value/MathMinMax.java +++ b/framework/tests/value/MathMinMax.java @@ -2,34 +2,34 @@ import org.checkerframework.common.value.qual.IntVal; public class MathMinMax { - void mathTest1(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 15) int y) { - @IntRange(from = 0, to = 10) int min = Math.min(x, y); - @IntRange(from = 5, to = 15) int max = Math.max(x, y); - } + void mathTest1(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 15) int y) { + @IntRange(from = 0, to = 10) int min = Math.min(x, y); + @IntRange(from = 5, to = 15) int max = Math.max(x, y); + } - void mathTest2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 11, to = 15) int y) { - @IntRange(from = 0, to = 10) int min = Math.min(x, y); - @IntRange(from = 11, to = 15) int max = Math.max(x, y); - } + void mathTest2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 11, to = 15) int y) { + @IntRange(from = 0, to = 10) int min = Math.min(x, y); + @IntRange(from = 11, to = 15) int max = Math.max(x, y); + } - void mathTest3(@IntRange(from = 0, to = 20) int x, @IntRange(from = 5, to = 15) int y) { - @IntRange(from = 0, to = 15) int min = Math.min(x, y); - @IntRange(from = 5, to = 20) int max = Math.max(x, y); - @IntVal(1) int minConst = Math.min(1, 2); - @IntVal(2) int maxConst = Math.max(-1, 2); - } + void mathTest3(@IntRange(from = 0, to = 20) int x, @IntRange(from = 5, to = 15) int y) { + @IntRange(from = 0, to = 15) int min = Math.min(x, y); + @IntRange(from = 5, to = 20) int max = Math.max(x, y); + @IntVal(1) int minConst = Math.min(1, 2); + @IntVal(2) int maxConst = Math.max(-1, 2); + } - void mathTest(long x, long y) { - long min = Math.min(x, y); - long max = Math.max(x, y); - } + void mathTest(long x, long y) { + long min = Math.min(x, y); + long max = Math.max(x, y); + } - void mathTest(double x, double y) { - double min = Math.min(x, y); - double max = Math.max(x, y); - } + void mathTest(double x, double y) { + double min = Math.min(x, y); + double max = Math.max(x, y); + } - void mathTetMax(@IntRange int x, @IntRange int y) { - @IntRange int z = Math.min(x, y); - } + void mathTetMax(@IntRange int x, @IntRange int y) { + @IntRange int z = Math.min(x, y); + } } diff --git a/framework/tests/value/Methods.java b/framework/tests/value/Methods.java index 7b7dd378052..feaad1cb2cd 100644 --- a/framework/tests/value/Methods.java +++ b/framework/tests/value/Methods.java @@ -2,83 +2,83 @@ public class Methods { - static int i = 3; - static final int k = 3; - - public static void Length() { - String a = "hello"; - @IntVal({5}) int b = a.length(); - - String e = "hello"; - int f = 2; - if (true) { - f = 1; - e = "world"; - } - @IntVal({'e', 'l', 'o', 'r'}) char g = e.charAt(f); - - // :: error: (assignment.type.incompatible) - @IntVal({'l'}) char h = e.charAt(i); - - @IntVal({'l'}) char j = e.charAt(k); - } - - public static void Boolean() { - String a = "true"; - @BoolVal({true}) boolean b = Boolean.valueOf(a); - } - - public static void Byte() { - @IntVal({127}) byte a = Byte.MAX_VALUE; - @IntVal({-128}) byte b = Byte.MIN_VALUE; - - String c = "59"; - int d = 10; - @IntVal({59}) byte e = Byte.valueOf(c, d); - } - - public static void Character() { - @IntVal({'c'}) char a = Character.toLowerCase('C'); - - // :: error: (assignment.type.incompatible) - @BoolVal({false}) boolean b = Character.isWhitespace('\t'); - } - - public static void Double() { - @DoubleVal({Double.MAX_VALUE}) double a = Double.MAX_VALUE; - String b = "59.32"; - @DoubleVal({59.32}) double c = Double.valueOf(b); - } - - public static void Float() { - @IntVal({Float.MIN_EXPONENT}) int a = Float.MIN_EXPONENT; - String b = "59.32"; - @DoubleVal({59.32f}) float c = Float.valueOf(b); - } - - public static void Integer() { - @IntVal({Integer.SIZE}) int a = Integer.SIZE; - String b = "0"; - @IntVal({0}) int c = Integer.valueOf(b); - } - - public static void Long() { - @IntVal({Long.MAX_VALUE}) long a = Long.MAX_VALUE; - String b = "53"; - @IntVal({53L}) long c = Long.valueOf(53L); - } - - public static void Short() { - @IntVal({Short.MIN_VALUE}) short a = Short.MIN_VALUE; - - String b = "53"; - @IntVal({53}) short c = Short.valueOf(b); - } - - public static void String() { - - @StringVal({"herro"}) String a = "hello".replace('l', 'r'); - // :: error: (assignment.type.incompatible) - @StringVal({"hello"}) String b = "hello".replace('l', 'r'); + static int i = 3; + static final int k = 3; + + public static void Length() { + String a = "hello"; + @IntVal({5}) int b = a.length(); + + String e = "hello"; + int f = 2; + if (true) { + f = 1; + e = "world"; } + @IntVal({'e', 'l', 'o', 'r'}) char g = e.charAt(f); + + // :: error: (assignment.type.incompatible) + @IntVal({'l'}) char h = e.charAt(i); + + @IntVal({'l'}) char j = e.charAt(k); + } + + public static void Boolean() { + String a = "true"; + @BoolVal({true}) boolean b = Boolean.valueOf(a); + } + + public static void Byte() { + @IntVal({127}) byte a = Byte.MAX_VALUE; + @IntVal({-128}) byte b = Byte.MIN_VALUE; + + String c = "59"; + int d = 10; + @IntVal({59}) byte e = Byte.valueOf(c, d); + } + + public static void Character() { + @IntVal({'c'}) char a = Character.toLowerCase('C'); + + // :: error: (assignment.type.incompatible) + @BoolVal({false}) boolean b = Character.isWhitespace('\t'); + } + + public static void Double() { + @DoubleVal({Double.MAX_VALUE}) double a = Double.MAX_VALUE; + String b = "59.32"; + @DoubleVal({59.32}) double c = Double.valueOf(b); + } + + public static void Float() { + @IntVal({Float.MIN_EXPONENT}) int a = Float.MIN_EXPONENT; + String b = "59.32"; + @DoubleVal({59.32f}) float c = Float.valueOf(b); + } + + public static void Integer() { + @IntVal({Integer.SIZE}) int a = Integer.SIZE; + String b = "0"; + @IntVal({0}) int c = Integer.valueOf(b); + } + + public static void Long() { + @IntVal({Long.MAX_VALUE}) long a = Long.MAX_VALUE; + String b = "53"; + @IntVal({53L}) long c = Long.valueOf(53L); + } + + public static void Short() { + @IntVal({Short.MIN_VALUE}) short a = Short.MIN_VALUE; + + String b = "53"; + @IntVal({53}) short c = Short.valueOf(b); + } + + public static void String() { + + @StringVal({"herro"}) String a = "hello".replace('l', 'r'); + // :: error: (assignment.type.incompatible) + @StringVal({"hello"}) String b = "hello".replace('l', 'r'); + } } diff --git a/framework/tests/value/MinLenConstants.java b/framework/tests/value/MinLenConstants.java index 2b2f103041e..1a277633089 100644 --- a/framework/tests/value/MinLenConstants.java +++ b/framework/tests/value/MinLenConstants.java @@ -2,7 +2,7 @@ public class MinLenConstants { - void test() { - int @MinLen(3) [] arr = {1, 2, 3}; - } + void test() { + int @MinLen(3) [] arr = {1, 2, 3}; + } } diff --git a/framework/tests/value/MinLenEqTransfer.java b/framework/tests/value/MinLenEqTransfer.java index b7318503587..4ba8b4a28af 100644 --- a/framework/tests/value/MinLenEqTransfer.java +++ b/framework/tests/value/MinLenEqTransfer.java @@ -1,31 +1,31 @@ import org.checkerframework.common.value.qual.*; public class MinLenEqTransfer { - void eq_check(int[] a) { - if (1 == a.length) { - int @MinLen(1) [] b = a; - } - if (a.length == 1) { - int @MinLen(1) [] b = a; - } + void eq_check(int[] a) { + if (1 == a.length) { + int @MinLen(1) [] b = a; } + if (a.length == 1) { + int @MinLen(1) [] b = a; + } + } - void eq_bad_check(int[] a) { - if (1 == a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void eq_bad_check(int[] a) { + if (1 == a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } - int @MinLen(2) [] test(int[] a) { - if (a.length == 100 || a.length == 3) { - int x = a.length; - return a; - } else if (a.length == 0 || a.length == 1) { - int x = a.length; - // :: error: (return.type.incompatible) - return a; - } - return new int[] {1, 2}; + int @MinLen(2) [] test(int[] a) { + if (a.length == 100 || a.length == 3) { + int x = a.length; + return a; + } else if (a.length == 0 || a.length == 1) { + int x = a.length; + // :: error: (return.type.incompatible) + return a; } + return new int[] {1, 2}; + } } diff --git a/framework/tests/value/MinLenFieldInvar.java b/framework/tests/value/MinLenFieldInvar.java index 5fca71a606f..66e8420ed98 100644 --- a/framework/tests/value/MinLenFieldInvar.java +++ b/framework/tests/value/MinLenFieldInvar.java @@ -2,58 +2,58 @@ import org.checkerframework.framework.qual.FieldInvariant; public class MinLenFieldInvar { - class Super { - public final int @MinLen(2) [] minlen2; + class Super { + public final int @MinLen(2) [] minlen2; - public Super(int @MinLen(2) [] minlen2) { - this.minlen2 = minlen2; - } + public Super(int @MinLen(2) [] minlen2) { + this.minlen2 = minlen2; } + } - // :: error: (field.invariant.not.subtype) - @MinLenFieldInvariant(field = "minlen2", minLen = 1) - class InvalidSub extends Super { - public InvalidSub() { - super(new int[] {1, 2}); - } + // :: error: (field.invariant.not.subtype) + @MinLenFieldInvariant(field = "minlen2", minLen = 1) + class InvalidSub extends Super { + public InvalidSub() { + super(new int[] {1, 2}); } + } - @MinLenFieldInvariant(field = "minlen2", minLen = 4) - class ValidSub extends Super { - public final int[] validSubField; + @MinLenFieldInvariant(field = "minlen2", minLen = 4) + class ValidSub extends Super { + public final int[] validSubField; - public ValidSub(int[] validSubField) { - super(new int[] {1, 2, 3, 4}); - this.validSubField = validSubField; - } + public ValidSub(int[] validSubField) { + super(new int[] {1, 2, 3, 4}); + this.validSubField = validSubField; } + } - // :: error: (field.invariant.not.found.superclass) - @MinLenFieldInvariant(field = "validSubField", minLen = 3) - class InvalidSubSub1 extends ValidSub { - public InvalidSubSub1() { - super(new int[] {1, 2}); - } + // :: error: (field.invariant.not.found.superclass) + @MinLenFieldInvariant(field = "validSubField", minLen = 3) + class InvalidSubSub1 extends ValidSub { + public InvalidSubSub1() { + super(new int[] {1, 2}); } + } - // :: error: (field.invariant.not.subtype.superclass) - @MinLenFieldInvariant(field = "minlen2", minLen = 3) - class InvalidSubSub2 extends ValidSub { - public InvalidSubSub2() { - super(new int[] {1, 2}); - } + // :: error: (field.invariant.not.subtype.superclass) + @MinLenFieldInvariant(field = "minlen2", minLen = 3) + class InvalidSubSub2 extends ValidSub { + public InvalidSubSub2() { + super(new int[] {1, 2}); } + } - @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) - @MinLenFieldInvariant(field = "validSubField", minLen = 4) - class ValidSubSub extends ValidSub { - public ValidSubSub() { - super(null); - } - - void test() { - int @BottomVal [] bot = minlen2; - int @MinLen(4) [] four = validSubField; - } + @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) + @MinLenFieldInvariant(field = "validSubField", minLen = 4) + class ValidSubSub extends ValidSub { + public ValidSubSub() { + super(null); } + + void test() { + int @BottomVal [] bot = minlen2; + int @MinLen(4) [] four = validSubField; + } + } } diff --git a/framework/tests/value/MinLenFieldInvar2.java b/framework/tests/value/MinLenFieldInvar2.java index 75600466ee9..6e58400fd41 100644 --- a/framework/tests/value/MinLenFieldInvar2.java +++ b/framework/tests/value/MinLenFieldInvar2.java @@ -6,26 +6,26 @@ import org.checkerframework.common.value.qual.MinLenFieldInvariant; public class MinLenFieldInvar2 { - class Super { - public final int @MinLen(2) [] minlen2; + class Super { + public final int @MinLen(2) [] minlen2; - public Super(int @MinLen(2) [] minlen2) { - this.minlen2 = minlen2; - } + public Super(int @MinLen(2) [] minlen2) { + this.minlen2 = minlen2; } + } - @MinLenFieldInvariant(field = "minlen2", minLen = 4) - class ValidSub extends Super { - public ValidSub(int[] validSubField) { - super(new int[] {1, 2, 3, 4}); - } + @MinLenFieldInvariant(field = "minlen2", minLen = 4) + class ValidSub extends Super { + public ValidSub(int[] validSubField) { + super(new int[] {1, 2, 3, 4}); } + } - void useAtValidSub(Super s) { - if (s instanceof ValidSub) { - System.out.println(s.minlen2[3]); - ValidSub vs = (ValidSub) s; - System.out.println(vs.minlen2[3]); - } + void useAtValidSub(Super s) { + if (s instanceof ValidSub) { + System.out.println(s.minlen2[3]); + ValidSub vs = (ValidSub) s; + System.out.println(vs.minlen2[3]); } + } } diff --git a/framework/tests/value/MinLenGTETransfer.java b/framework/tests/value/MinLenGTETransfer.java index 7347cefdfb6..e58a367a687 100644 --- a/framework/tests/value/MinLenGTETransfer.java +++ b/framework/tests/value/MinLenGTETransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenGTETransfer { - void gte_check(int[] a) { - if (a.length >= 1) { - int @MinLen(1) [] b = a; - } + void gte_check(int[] a) { + if (a.length >= 1) { + int @MinLen(1) [] b = a; } + } - void gte_bad_check(int[] a) { - if (a.length >= 1) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void gte_bad_check(int[] a) { + if (a.length >= 1) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/framework/tests/value/MinLenGTTransfer.java b/framework/tests/value/MinLenGTTransfer.java index 95293af59f4..af419fd4fc9 100644 --- a/framework/tests/value/MinLenGTTransfer.java +++ b/framework/tests/value/MinLenGTTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenGTTransfer { - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; - } + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; } + } - void gt_bad_check(int[] a) { - if (a.length > 0) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void gt_bad_check(int[] a) { + if (a.length > 0) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/framework/tests/value/MinLenLTETransfer.java b/framework/tests/value/MinLenLTETransfer.java index d09f0b6d520..f9ead3a4f10 100644 --- a/framework/tests/value/MinLenLTETransfer.java +++ b/framework/tests/value/MinLenLTETransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenLTETransfer { - void lte_check(int[] a) { - if (1 <= a.length) { - int @MinLen(1) [] b = a; - } + void lte_check(int[] a) { + if (1 <= a.length) { + int @MinLen(1) [] b = a; } + } - void lte_bad_check(int[] a) { - if (1 <= a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void lte_bad_check(int[] a) { + if (1 <= a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/framework/tests/value/MinLenLTTransfer.java b/framework/tests/value/MinLenLTTransfer.java index e47c766f9e5..fe2d7cf2b29 100644 --- a/framework/tests/value/MinLenLTTransfer.java +++ b/framework/tests/value/MinLenLTTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenLTTransfer { - void lt_check(int[] a) { - if (0 < a.length) { - int @MinLen(1) [] b = a; - } + void lt_check(int[] a) { + if (0 < a.length) { + int @MinLen(1) [] b = a; } + } - void lt_bad_check(int[] a) { - if (0 < a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void lt_bad_check(int[] a) { + if (0 < a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } } diff --git a/framework/tests/value/MinLenLUB.java b/framework/tests/value/MinLenLUB.java index c8e71267682..2b8e575d149 100644 --- a/framework/tests/value/MinLenLUB.java +++ b/framework/tests/value/MinLenLUB.java @@ -2,42 +2,42 @@ public class MinLenLUB { - public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; - } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; + public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; + } - public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; - } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; + public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; + } - public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; - } - int @MinLen(10) [] res = arr; - int @BottomVal [] res2 = arr; + public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; } + int @MinLen(10) [] res = arr; + int @BottomVal [] res2 = arr; + } } diff --git a/framework/tests/value/MinLenNEqTransfer.java b/framework/tests/value/MinLenNEqTransfer.java index 938246afcde..15677e020fe 100644 --- a/framework/tests/value/MinLenNEqTransfer.java +++ b/framework/tests/value/MinLenNEqTransfer.java @@ -1,26 +1,26 @@ import org.checkerframework.common.value.qual.*; public class MinLenNEqTransfer { - void neq_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - int @MinLen(1) [] b = a; - } + void neq_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + int @MinLen(1) [] b = a; } + } - void neq_bad_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; - } + void neq_bad_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; } + } - void neq_zero_special_case(int[] a) { - if (a.length != 0) { - int @MinLen(1) [] b = a; - } + void neq_zero_special_case(int[] a) { + if (a.length != 0) { + int @MinLen(1) [] b = a; } + } } diff --git a/framework/tests/value/MinLenPostcondition.java b/framework/tests/value/MinLenPostcondition.java index a3d36f3df5d..b38f75f5a68 100644 --- a/framework/tests/value/MinLenPostcondition.java +++ b/framework/tests/value/MinLenPostcondition.java @@ -1,9 +1,9 @@ import org.checkerframework.common.value.qual.*; public class MinLenPostcondition { - public void m(String a) { - if (!a.isEmpty()) { - char c = a.charAt(0); - } + public void m(String a) { + if (!a.isEmpty()) { + char c = a.charAt(0); } + } } diff --git a/framework/tests/value/MinLenVarargs.java b/framework/tests/value/MinLenVarargs.java index 14f3baa7c36..e9501b83ebd 100644 --- a/framework/tests/value/MinLenVarargs.java +++ b/framework/tests/value/MinLenVarargs.java @@ -3,18 +3,18 @@ // @skip-test public class MinLenVarargs { - static void check(String @MinLen(3) ... var) { - System.out.println(var[0] + " " + var[1]); - } + static void check(String @MinLen(3) ... var) { + System.out.println(var[0] + " " + var[1]); + } - public static void main(String[] args) { - // :: error: (argument.type.incompatible) - check(new String[] {"goodbye"}); - // :: error: (argument.type.incompatible) - check("goodbye"); - // :: error: (argument.type.incompatible) - check(); - // :: error: (argument.type.incompatible) - check("hello", "goodbye"); - } + public static void main(String[] args) { + // :: error: (argument.type.incompatible) + check(new String[] {"goodbye"}); + // :: error: (argument.type.incompatible) + check("goodbye"); + // :: error: (argument.type.incompatible) + check(); + // :: error: (argument.type.incompatible) + check("hello", "goodbye"); + } } diff --git a/framework/tests/value/MultipleBinaryExpressions.java b/framework/tests/value/MultipleBinaryExpressions.java index 31af8b203dd..e9f1f294d3a 100644 --- a/framework/tests/value/MultipleBinaryExpressions.java +++ b/framework/tests/value/MultipleBinaryExpressions.java @@ -3,70 +3,70 @@ public class MultipleBinaryExpressions { - private final String ONE_STRING = "1"; - private final String TWO_STRING = "2"; - private final String THREE_STRING = "3"; - private final String FOUR_STRING = "4"; - private final String FIVE_STRING = "5"; - private final String SIX_STRING = "6"; - private final String SEVEN_STRING = "7"; - private final String EIGHT_STRING = "8"; - private final String NINE_STRING = "9"; + private final String ONE_STRING = "1"; + private final String TWO_STRING = "2"; + private final String THREE_STRING = "3"; + private final String FOUR_STRING = "4"; + private final String FIVE_STRING = "5"; + private final String SIX_STRING = "6"; + private final String SEVEN_STRING = "7"; + private final String EIGHT_STRING = "8"; + private final String NINE_STRING = "9"; - public final @StringVal("123456789123456789") String concat1 = - ONE_STRING - + TWO_STRING - + THREE_STRING - + FOUR_STRING - + FIVE_STRING - + SIX_STRING - + SEVEN_STRING - + EIGHT_STRING - + NINE_STRING - + ONE_STRING - + TWO_STRING - + THREE_STRING - + FOUR_STRING - + FIVE_STRING - + SIX_STRING - + SEVEN_STRING - + EIGHT_STRING - + NINE_STRING; + public final @StringVal("123456789123456789") String concat1 = + ONE_STRING + + TWO_STRING + + THREE_STRING + + FOUR_STRING + + FIVE_STRING + + SIX_STRING + + SEVEN_STRING + + EIGHT_STRING + + NINE_STRING + + ONE_STRING + + TWO_STRING + + THREE_STRING + + FOUR_STRING + + FIVE_STRING + + SIX_STRING + + SEVEN_STRING + + EIGHT_STRING + + NINE_STRING; - public final @StringVal("112233445566778899") String concat2 = - ONE_STRING - + "1" - + TWO_STRING - + "2" - + THREE_STRING - + "3" - + FOUR_STRING - + "4" - + FIVE_STRING - + "5" - + "6" - + SIX_STRING - + "7" - + SEVEN_STRING - + "8" - + EIGHT_STRING - + "9" - + NINE_STRING; + public final @StringVal("112233445566778899") String concat2 = + ONE_STRING + + "1" + + TWO_STRING + + "2" + + THREE_STRING + + "3" + + FOUR_STRING + + "4" + + FIVE_STRING + + "5" + + "6" + + SIX_STRING + + "7" + + SEVEN_STRING + + "8" + + EIGHT_STRING + + "9" + + NINE_STRING; - private final int ONE = 1; - private final int TWO = 2; - private final int THREE = 3; - private final int FOUR = 4; - private final int FIVE = 5; - private final int SIX = 6; - private final int SEVEN = 7; - private final int EIGHT = 8; - private final int NINE = 9; + private final int ONE = 1; + private final int TWO = 2; + private final int THREE = 3; + private final int FOUR = 4; + private final int FIVE = 5; + private final int SIX = 6; + private final int SEVEN = 7; + private final int EIGHT = 8; + private final int NINE = 9; - public final @IntVal(90) int plus1 = - ONE + TWO + THREE + FOUR + FIVE + SIX + SEVEN + EIGHT + NINE + ONE + TWO + THREE + FOUR - + FIVE + SIX + SEVEN + EIGHT + NINE; - public final @IntVal(90) int plus2 = - ONE + 1 + TWO + 2 + THREE + 3 + FOUR + 4 + FIVE + 5 + SIX + 6 + SEVEN + 7 + EIGHT + 8 - + NINE + 9; + public final @IntVal(90) int plus1 = + ONE + TWO + THREE + FOUR + FIVE + SIX + SEVEN + EIGHT + NINE + ONE + TWO + THREE + FOUR + FIVE + + SIX + SEVEN + EIGHT + NINE; + public final @IntVal(90) int plus2 = + ONE + 1 + TWO + 2 + THREE + 3 + FOUR + 4 + FIVE + 5 + SIX + 6 + SEVEN + 7 + EIGHT + 8 + NINE + + 9; } diff --git a/framework/tests/value/MyTree.java b/framework/tests/value/MyTree.java index 8d9ee790ceb..1440766b54f 100644 --- a/framework/tests/value/MyTree.java +++ b/framework/tests/value/MyTree.java @@ -8,35 +8,35 @@ public class MyTree { - public static MyTree newTree(V value) { - throw new Error("body doesn't matter"); - } - - public MyTree put(Value newValue) { - throw new Error("body doesn't matter"); - } - - void uses() { - newTree("hello").put("bye"); - - MyTree<@UnknownVal String> myTree1 = newTree("hello").put("bye"); - // :: error: (assignment.type.incompatible) - MyTree<@StringVal("hello") String> myTree1b = newTree("hello").put("bye"); - - // Note: This is a false positive: the type of newTree("hello").put("hello") should be - // inferred as MyTree<@StringVal("hello") String> and the assignment should therefore pass. - // :: error: (assignment.type.incompatible) - MyTree<@StringVal("hello") String> myTree2 = newTree("hello").put("hello"); - MyTree<@StringVal("hello") String> myTree2b = - MyTree.<@StringVal("hello") String>newTree("hello").put("hello"); - // :: error: (assignment.type.incompatible) - MyTree<@StringVal("error") String> myTree2c = newTree("hello").put("hello"); - - MyTree<@UnknownVal String> myTree3 = newTree("hello"); - myTree3.put("bye"); - - MyTree<@StringVal("hello") String> myTree4 = newTree("hello"); - // :: error: (argument.type.incompatible) - myTree4.put("bye"); - } + public static MyTree newTree(V value) { + throw new Error("body doesn't matter"); + } + + public MyTree put(Value newValue) { + throw new Error("body doesn't matter"); + } + + void uses() { + newTree("hello").put("bye"); + + MyTree<@UnknownVal String> myTree1 = newTree("hello").put("bye"); + // :: error: (assignment.type.incompatible) + MyTree<@StringVal("hello") String> myTree1b = newTree("hello").put("bye"); + + // Note: This is a false positive: the type of newTree("hello").put("hello") should be + // inferred as MyTree<@StringVal("hello") String> and the assignment should therefore pass. + // :: error: (assignment.type.incompatible) + MyTree<@StringVal("hello") String> myTree2 = newTree("hello").put("hello"); + MyTree<@StringVal("hello") String> myTree2b = + MyTree.<@StringVal("hello") String>newTree("hello").put("hello"); + // :: error: (assignment.type.incompatible) + MyTree<@StringVal("error") String> myTree2c = newTree("hello").put("hello"); + + MyTree<@UnknownVal String> myTree3 = newTree("hello"); + myTree3.put("bye"); + + MyTree<@StringVal("hello") String> myTree4 = newTree("hello"); + // :: error: (argument.type.incompatible) + myTree4.put("bye"); + } } diff --git a/framework/tests/value/MyTree2.java b/framework/tests/value/MyTree2.java index bfbfb6bfadc..9a79bd2184a 100644 --- a/framework/tests/value/MyTree2.java +++ b/framework/tests/value/MyTree2.java @@ -5,26 +5,25 @@ public class MyTree2 { - public static MyTree2 newTree(V value) { - throw new Error("body doesn't matter"); - } + public static MyTree2 newTree(V value) { + throw new Error("body doesn't matter"); + } - public MyTree2 put(String newKey, Value newValue) { - throw new Error("body doesn't matter"); - } + public MyTree2 put(String newKey, Value newValue) { + throw new Error("body doesn't matter"); + } - public void client1() { - MyTree2 root2 = MyTree2.newTree("constructorarg").put("key1", "value1"); - } + public void client1() { + MyTree2 root2 = MyTree2.newTree("constructorarg").put("key1", "value1"); + } - public void client2() { - MyTree2 root2 = - MyTree2.newTree((@UnknownVal String) "constructorarg").put("key1", "value1"); - } + public void client2() { + MyTree2 root2 = + MyTree2.newTree((@UnknownVal String) "constructorarg").put("key1", "value1"); + } - public void client3() { - MyTree2 root2 = - MyTree2.newTree((@UnknownVal String) "constructorarg") - .put("key1", "value1"); - } + public void client3() { + MyTree2 root2 = + MyTree2.newTree((@UnknownVal String) "constructorarg").put("key1", "value1"); + } } diff --git a/framework/tests/value/NegativeArrayLen.java b/framework/tests/value/NegativeArrayLen.java index 76e9b63b4de..3994776f539 100644 --- a/framework/tests/value/NegativeArrayLen.java +++ b/framework/tests/value/NegativeArrayLen.java @@ -2,19 +2,19 @@ import org.checkerframework.common.value.qual.BottomVal; public class NegativeArrayLen { - void test1() { - int @BottomVal [] a = new int[-1]; - int @BottomVal [] b = new int[Integer.MAX_VALUE + 1]; - // :: warning: (negative.arraylen) - int @ArrayLen(-1) [] c = new int[-1]; - } + void test1() { + int @BottomVal [] a = new int[-1]; + int @BottomVal [] b = new int[Integer.MAX_VALUE + 1]; + // :: warning: (negative.arraylen) + int @ArrayLen(-1) [] c = new int[-1]; + } - void test2( - // :: warning: (negative.arraylen) - int @ArrayLen(Integer.MIN_VALUE) [] a, - // :: warning: (negative.arraylen) - int @ArrayLen({-1, 2, 0}) [] b) { - int @BottomVal [] y = a; - int @BottomVal [] x = b; - } + void test2( + // :: warning: (negative.arraylen) + int @ArrayLen(Integer.MIN_VALUE) [] a, + // :: warning: (negative.arraylen) + int @ArrayLen({-1, 2, 0}) [] b) { + int @BottomVal [] y = a; + int @BottomVal [] x = b; + } } diff --git a/framework/tests/value/NestedArrayLengthInference.java b/framework/tests/value/NestedArrayLengthInference.java index 964029c9da6..c17f2f05c79 100644 --- a/framework/tests/value/NestedArrayLengthInference.java +++ b/framework/tests/value/NestedArrayLengthInference.java @@ -1,12 +1,12 @@ public class NestedArrayLengthInference { - public void doStuff(int r) { + public void doStuff(int r) { - int[] length16array = new int[16]; + int[] length16array = new int[16]; - int[] unknownLengthArray = new int[r]; + int[] unknownLengthArray = new int[r]; - // CF seems to think that if one array has constant length, all do?? - int[][] myNewArray = new int[][] {unknownLengthArray, length16array}; - int[][] myNewArray2 = new int[][] {length16array, unknownLengthArray}; - } + // CF seems to think that if one array has constant length, all do?? + int[][] myNewArray = new int[][] {unknownLengthArray, length16array}; + int[][] myNewArray2 = new int[][] {length16array, unknownLengthArray}; + } } diff --git a/framework/tests/value/Overflows.java b/framework/tests/value/Overflows.java index ae18c26bb92..94b7928fd75 100644 --- a/framework/tests/value/Overflows.java +++ b/framework/tests/value/Overflows.java @@ -2,43 +2,43 @@ public class Overflows { - static void bytes() { - byte max = Byte.MAX_VALUE; - @IntVal(-128) byte maxPlus1 = (byte) (max + 1); - // :: error: (assignment.type.incompatible) - @IntVal(-128) short maxPlus1Short = (short) (max + 1); - } - - static void chars() { - char max = Character.MAX_VALUE; - // :: warning: (cast.unsafe) - @IntVal(0) char maxPlus1 = (char) (max + 1); - } - - static void shorts() { - short max = Short.MAX_VALUE; - @IntVal(-32768) short maxPlus1 = (short) (max + 1); - // :: error: (assignment.type.incompatible) - @IntVal(-32768) int maxPlus1Int = (int) (max + 1); - } - - static void ints() { - int max = Integer.MAX_VALUE; - @IntVal(-2147483648) int maxPlus1 = max + 1; - } - - static void longs() { - long max = Long.MAX_VALUE; - @IntVal(-9223372036854775808L) long maxPlus1 = max + 1; - } - - static void doubles() { - double max = Double.MAX_VALUE; - @DoubleVal(1.7976931348623157E308) double maxPlus1 = max + 1.0; - } - - static void floats() { - float max = Float.MAX_VALUE; - @DoubleVal(3.4028235E38f) float maxPlus1 = max + 1.0f; - } + static void bytes() { + byte max = Byte.MAX_VALUE; + @IntVal(-128) byte maxPlus1 = (byte) (max + 1); + // :: error: (assignment.type.incompatible) + @IntVal(-128) short maxPlus1Short = (short) (max + 1); + } + + static void chars() { + char max = Character.MAX_VALUE; + // :: warning: (cast.unsafe) + @IntVal(0) char maxPlus1 = (char) (max + 1); + } + + static void shorts() { + short max = Short.MAX_VALUE; + @IntVal(-32768) short maxPlus1 = (short) (max + 1); + // :: error: (assignment.type.incompatible) + @IntVal(-32768) int maxPlus1Int = (int) (max + 1); + } + + static void ints() { + int max = Integer.MAX_VALUE; + @IntVal(-2147483648) int maxPlus1 = max + 1; + } + + static void longs() { + long max = Long.MAX_VALUE; + @IntVal(-9223372036854775808L) long maxPlus1 = max + 1; + } + + static void doubles() { + double max = Double.MAX_VALUE; + @DoubleVal(1.7976931348623157E308) double maxPlus1 = max + 1.0; + } + + static void floats() { + float max = Float.MAX_VALUE; + @DoubleVal(3.4028235E38f) float maxPlus1 = max + 1.0f; + } } diff --git a/framework/tests/value/Polymorphic.java b/framework/tests/value/Polymorphic.java index c85c9b247f0..17ba3eb3de2 100644 --- a/framework/tests/value/Polymorphic.java +++ b/framework/tests/value/Polymorphic.java @@ -2,31 +2,31 @@ public class Polymorphic { - // Identity functions + // Identity functions - int @PolyValue [] identity_array(int @PolyValue [] a) { - return a; - } + int @PolyValue [] identity_array(int @PolyValue [] a) { + return a; + } - @PolyValue int identity_int(@PolyValue int a) { - return a; - } + @PolyValue int identity_int(@PolyValue int a) { + return a; + } - void minlen_id(int @MinLen(5) [] a) { - int @MinLen(5) [] b = identity_array(a); - // :: error: (assignment.type.incompatible) - int @MinLen(6) [] c = identity_array(b); - } + void minlen_id(int @MinLen(5) [] a) { + int @MinLen(5) [] b = identity_array(a); + // :: error: (assignment.type.incompatible) + int @MinLen(6) [] c = identity_array(b); + } - void use(int @ArrayLenRange(from = 5, to = 25) [] a) { - int @ArrayLenRange(from = 5, to = 25) [] b = identity_array(a); - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 1, to = 13) [] c = identity_array(a); - } + void use(int @ArrayLenRange(from = 5, to = 25) [] a) { + int @ArrayLenRange(from = 5, to = 25) [] b = identity_array(a); + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 1, to = 13) [] c = identity_array(a); + } - void use2(@IntRange(from = 5, to = 25) int a) { - @IntRange(from = 5, to = 25) int b = identity_int(a); - // :: error: (assignment.type.incompatible) - @IntVal(3) int x = identity_int(a); - } + void use2(@IntRange(from = 5, to = 25) int a) { + @IntRange(from = 5, to = 25) int b = identity_int(a); + // :: error: (assignment.type.incompatible) + @IntVal(3) int x = identity_int(a); + } } diff --git a/framework/tests/value/Polymorphic2.java b/framework/tests/value/Polymorphic2.java index 58a5f2205bb..932ba383b7a 100644 --- a/framework/tests/value/Polymorphic2.java +++ b/framework/tests/value/Polymorphic2.java @@ -1,37 +1,37 @@ import org.checkerframework.common.value.qual.*; public class Polymorphic2 { - public static boolean flag = false; - - @PolyValue int mergeValue(@PolyValue int a, @PolyValue int b) { - return flag ? a : b; - } - - int @PolyValue [] mergeValue(int @PolyValue [] a, int @PolyValue [] b) { - return flag ? a : b; - } - - void testMinLen(int @MinLen(2) [] a, int @MinLen(5) [] b) { - int @MinLen(2) [] z = mergeValue(a, b); - // :: error: (assignment.type.incompatible) - int @MinLen(5) [] zz = mergeValue(a, b); - } - - void testArrayLen(int @ArrayLen(2) [] a, int @ArrayLen(5) [] b) { - // :: error: (assignment.type.incompatible) - int @ArrayLen(2) [] z = mergeValue(a, b); - // :: error: (assignment.type.incompatible) - int @ArrayLen(5) [] zz = mergeValue(a, b); - - int @ArrayLen({2, 5}) [] zzz = mergeValue(a, b); - } - - void testIntVal(@IntVal(2) int a, @IntVal(5) int b) { - // :: error: (assignment.type.incompatible) - @IntVal(2) int z = mergeValue(a, b); - // :: error: (assignment.type.incompatible) - @IntVal(5) int zz = mergeValue(a, b); - - @IntVal({2, 5}) int zzz = mergeValue(a, b); - } + public static boolean flag = false; + + @PolyValue int mergeValue(@PolyValue int a, @PolyValue int b) { + return flag ? a : b; + } + + int @PolyValue [] mergeValue(int @PolyValue [] a, int @PolyValue [] b) { + return flag ? a : b; + } + + void testMinLen(int @MinLen(2) [] a, int @MinLen(5) [] b) { + int @MinLen(2) [] z = mergeValue(a, b); + // :: error: (assignment.type.incompatible) + int @MinLen(5) [] zz = mergeValue(a, b); + } + + void testArrayLen(int @ArrayLen(2) [] a, int @ArrayLen(5) [] b) { + // :: error: (assignment.type.incompatible) + int @ArrayLen(2) [] z = mergeValue(a, b); + // :: error: (assignment.type.incompatible) + int @ArrayLen(5) [] zz = mergeValue(a, b); + + int @ArrayLen({2, 5}) [] zzz = mergeValue(a, b); + } + + void testIntVal(@IntVal(2) int a, @IntVal(5) int b) { + // :: error: (assignment.type.incompatible) + @IntVal(2) int z = mergeValue(a, b); + // :: error: (assignment.type.incompatible) + @IntVal(5) int zz = mergeValue(a, b); + + @IntVal({2, 5}) int zzz = mergeValue(a, b); + } } diff --git a/framework/tests/value/RefineBoolean.java b/framework/tests/value/RefineBoolean.java index 5c47ab4ba87..bc54cfb5dc3 100644 --- a/framework/tests/value/RefineBoolean.java +++ b/framework/tests/value/RefineBoolean.java @@ -2,51 +2,51 @@ public class RefineBoolean { - void test1(boolean x) { - if (x == false) { - @BoolVal(false) boolean y = x; - } + void test1(boolean x) { + if (x == false) { + @BoolVal(false) boolean y = x; } + } - void test2(boolean x) { - if (false == x) { - @BoolVal(false) boolean y = x; - } + void test2(boolean x) { + if (false == x) { + @BoolVal(false) boolean y = x; } + } - void test3(boolean x) { - if (x != true) { - @BoolVal(false) boolean y = x; - } + void test3(boolean x) { + if (x != true) { + @BoolVal(false) boolean y = x; } + } - void test4(boolean x) { - if (true != x) { - @BoolVal(false) boolean y = x; - } + void test4(boolean x) { + if (true != x) { + @BoolVal(false) boolean y = x; } + } - void test5(boolean x) { - if (x == true) { - @BoolVal(true) boolean y = x; - } + void test5(boolean x) { + if (x == true) { + @BoolVal(true) boolean y = x; } + } - void test6(boolean x) { - if (true == x) { - @BoolVal(true) boolean y = x; - } + void test6(boolean x) { + if (true == x) { + @BoolVal(true) boolean y = x; } + } - void test7(boolean x) { - if (false != x) { - @BoolVal(true) boolean y = x; - } + void test7(boolean x) { + if (false != x) { + @BoolVal(true) boolean y = x; } + } - void test8(boolean x) { - if (x != false) { - @BoolVal(true) boolean y = x; - } + void test8(boolean x) { + if (x != false) { + @BoolVal(true) boolean y = x; } + } } diff --git a/framework/tests/value/RefineUnknownToIntRange.java b/framework/tests/value/RefineUnknownToIntRange.java index 3f0547b05af..f0b787b28bd 100644 --- a/framework/tests/value/RefineUnknownToIntRange.java +++ b/framework/tests/value/RefineUnknownToIntRange.java @@ -2,48 +2,48 @@ import org.checkerframework.common.value.qual.IntRange; public class RefineUnknownToIntRange { - void test1(int x) { - if (x > 1) { - @IntRange(from = 2) int z = x; - } - - if (x < 1) { - @IntRange(to = 0) int z = x; - } + void test1(int x) { + if (x > 1) { + @IntRange(from = 2) int z = x; + } - if (1 < x) { - @IntRange(from = 2) int z = x; - } + if (x < 1) { + @IntRange(to = 0) int z = x; + } - if (1 > x) { - @IntRange(to = 0) int z = x; - } + if (1 < x) { + @IntRange(from = 2) int z = x; + } - if (x >= 1) { - @IntRange(from = 1) int z = x; - } + if (1 > x) { + @IntRange(to = 0) int z = x; + } - if (x <= 1) { - @IntRange(to = 1) int z = x; - } + if (x >= 1) { + @IntRange(from = 1) int z = x; + } - if (x < 100 && x > 2) { - @IntRange(from = 3, to = 99) int z = x; - } + if (x <= 1) { + @IntRange(to = 1) int z = x; } - void test3(boolean x) { - // Make sure non int values are ignored. - if (x == false) { - @BoolVal(false) boolean y = x; - } + if (x < 100 && x > 2) { + @IntRange(from = 3, to = 99) int z = x; + } + } - if (x != true) { - @BoolVal(false) boolean y = x; - } + void test3(boolean x) { + // Make sure non int values are ignored. + if (x == false) { + @BoolVal(false) boolean y = x; + } - Object o = new Object(); - Object o2 = new Object(); - if (o == o2) {} + if (x != true) { + @BoolVal(false) boolean y = x; } + + Object o = new Object(); + Object o2 = new Object(); + if (o == o2) {} + } } diff --git a/framework/tests/value/Refinement.java b/framework/tests/value/Refinement.java index 3a17b7ea95f..47b5639638e 100644 --- a/framework/tests/value/Refinement.java +++ b/framework/tests/value/Refinement.java @@ -2,312 +2,311 @@ public class Refinement { - void eq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x == 1) { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } - - if (x == w) { - @IntVal({1}) int y = x; - @IntVal({1}) int z = w; - } else { - // These two assignments are illegal because one of x or w could be 1, - // while the other is not. So no refinement can be applied. - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } - } + void eq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x == 1) { + @IntVal({1}) int y = x; - void lt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x < 2) { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } - - if (x < w) { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - - @IntVal({5, 6}) int z = w; - } else { - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; - } - } + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; - void lte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x <= 1) { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } - - if (x <= w) { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } else { - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; - } + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; } - void neq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x != 1) { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } else { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } - - if (x != w) { - // These two assignments are illegal because one of x or w could be 1, - // while the other is not. So no refinement can be applied. - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } else { - @IntVal({1}) int y = x; - @IntVal({1}) int z = w; - } + if (x == w) { + @IntVal({1}) int y = x; + @IntVal({1}) int z = w; + } else { + // These two assignments are illegal because one of x or w could be 1, + // while the other is not. So no refinement can be applied. + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; } + } - void gte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x >= 2) { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } else { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } - - if (x >= w) { - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; - } else { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - - @IntVal({5, 6}) int z = w; - } - } + void lt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x < 2) { + @IntVal({1}) int y = x; - void gt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x > 1) { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } else { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } - - if (x > w) { - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; - } else { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } - } + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; - void boolean_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - @BoolVal({true}) boolean b = x >= 0; - @BoolVal({false}) boolean c = w == 3; - @BoolVal({true, false}) boolean d = x < w; + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; } - void test_intrange_eq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { - if (x == y) { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 3, to = 5) int b = y; - } else { - @IntRange(from = 6, to = 10) - // :: error: (assignment.type.incompatible) - int a = x; - @IntRange(from = 1, to = 2) - // :: error: (assignment.type.incompatible) - int b = y; - } - } + if (x < w) { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; - void test_intrange_eq2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 0, to = 0) int y) { - if (x == y) { - @IntRange(from = 0, to = 0) int a = x; - @IntRange(from = 0, to = 0) int b = y; - } else { - @IntRange(from = 1, to = 10) int a = x; - @IntRange(from = 0, to = 0) int b = y; - } + @IntVal({5, 6}) int z = w; + } else { + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; } + } - void test_intrange_eq3(@IntRange(from = 0, to = 10) int x, @IntVal(0) int y) { - if (x == y) { - @IntVal(0) int a = x; - @IntVal(0) int b = y; - } else { - @IntRange(from = 1, to = 10) int a = x; - @IntVal(0) int b = y; - } - - if (y != x) { - @IntRange(from = 1, to = 10) int a = x; - @IntVal(0) int b = y; - } - } + void lte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x <= 1) { + @IntVal({1}) int y = x; - void test_intrange_neq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { - if (x != y) { - @IntRange(from = 6, to = 10) - // :: error: (assignment.type.incompatible) - int a = x; - @IntRange(from = 1, to = 2) - // :: error: (assignment.type.incompatible) - int b = y; - } else { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 3, to = 5) int b = y; - } - } + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; - void test_intrange_neq2( - @IntRange(from = 3, to = 10) int x, @IntRange(from = 10, to = 10) int y) { - if (x != y) { - @IntRange(from = 3, to = 9) int a = x; - @IntRange(from = 10, to = 10) int b = y; - } else { - @IntRange(from = 10, to = 10) int a = x; - @IntRange(from = 10, to = 10) int b = y; - } + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; } - void test_intrange_gt(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 20) int y) { - if (x > y) { - @IntRange(from = 6, to = 10) int a = x; - @IntRange(from = 5, to = 9) int b = y; - } else { - @IntRange(from = 0, to = 10) int a = x; - @IntRange(from = 5, to = 20) int b = y; - } + if (x <= w) { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } else { + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; } + } - void test_intrange_gt2(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x > y) { - @IntRange(from = 5, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; - - @IntRange(from = 5, to = 7) - // :: error: (assignment.type.incompatible) - int c = x; - @IntRange(from = 5, to = 7) - // :: error: (assignment.type.incompatible) - int d = y; - } else { - @IntRange(from = 5, to = 7) int a = x; - @IntRange(from = 5, to = 7) int b = y; - } + void neq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x != 1) { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } else { + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; } - void test_intrange_lte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x <= y) { - @IntRange(from = 0, to = 7) int a = x; - @IntRange(from = 2, to = 7) int b = y; - } else { - @IntRange(from = 3, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; - } + if (x != w) { + // These two assignments are illegal because one of x or w could be 1, + // while the other is not. So no refinement can be applied. + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } else { + @IntVal({1}) int y = x; + @IntVal({1}) int z = w; } + } + + void gte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x >= 2) { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } else { + @IntVal({1}) int y = x; - void test_intrange_lte2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { - if (x <= y) { - @IntRange(from = 2, to = 7) int a = x; - @IntRange(from = 2, to = 10) int b = y; - } else { - @IntRange(from = 6, to = 7) int a = x; - @IntRange(from = 5, to = 6) int b = y; - } + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; } - void test_intrange_lt(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x < y) { - @IntRange(from = 5, to = 6) int a = x; - @IntRange(from = 6, to = 7) int b = y; - } else { - @IntRange(from = 5, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; - } + if (x >= w) { + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; + } else { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + + @IntVal({5, 6}) int z = w; } + } + + void gt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x > 1) { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } else { + @IntVal({1}) int y = x; - void test_intrange_lt2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { - if (x < y) { - @IntRange(from = 2, to = 7) int a = x; - @IntRange(from = 2, to = 10) int b = y; - } else { - @IntRange(from = 5, to = 7) int a = x; - @IntRange(from = 5, to = 7) int b = y; - } + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; } - void test_intrange_gte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x >= y) { - @IntRange(from = 2, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; - } else { - @IntRange(from = 0, to = 6) int a = x; - @IntRange(from = 2, to = 7) int b = y; - } + if (x > w) { + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; + } else { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } + } + + void boolean_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + @BoolVal({true}) boolean b = x >= 0; + @BoolVal({false}) boolean c = w == 3; + @BoolVal({true, false}) boolean d = x < w; + } + + void test_intrange_eq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { + if (x == y) { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 3, to = 5) int b = y; + } else { + @IntRange(from = 6, to = 10) + // :: error: (assignment.type.incompatible) + int a = x; + @IntRange(from = 1, to = 2) + // :: error: (assignment.type.incompatible) + int b = y; + } + } + + void test_intrange_eq2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 0, to = 0) int y) { + if (x == y) { + @IntRange(from = 0, to = 0) int a = x; + @IntRange(from = 0, to = 0) int b = y; + } else { + @IntRange(from = 1, to = 10) int a = x; + @IntRange(from = 0, to = 0) int b = y; + } + } + + void test_intrange_eq3(@IntRange(from = 0, to = 10) int x, @IntVal(0) int y) { + if (x == y) { + @IntVal(0) int a = x; + @IntVal(0) int b = y; + } else { + @IntRange(from = 1, to = 10) int a = x; + @IntVal(0) int b = y; } - void test_intrange_gte2(@IntRange(from = 3, to = 5) int x, @IntRange(from = 2, to = 6) int y) { - if (x >= y) { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 2, to = 6) int b = y; - } else { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 4, to = 6) int b = y; - } + if (y != x) { + @IntRange(from = 1, to = 10) int a = x; + @IntVal(0) int b = y; + } + } + + void test_intrange_neq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { + if (x != y) { + @IntRange(from = 6, to = 10) + // :: error: (assignment.type.incompatible) + int a = x; + @IntRange(from = 1, to = 2) + // :: error: (assignment.type.incompatible) + int b = y; + } else { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 3, to = 5) int b = y; + } + } + + void test_intrange_neq2(@IntRange(from = 3, to = 10) int x, @IntRange(from = 10, to = 10) int y) { + if (x != y) { + @IntRange(from = 3, to = 9) int a = x; + @IntRange(from = 10, to = 10) int b = y; + } else { + @IntRange(from = 10, to = 10) int a = x; + @IntRange(from = 10, to = 10) int b = y; + } + } + + void test_intrange_gt(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 20) int y) { + if (x > y) { + @IntRange(from = 6, to = 10) int a = x; + @IntRange(from = 5, to = 9) int b = y; + } else { + @IntRange(from = 0, to = 10) int a = x; + @IntRange(from = 5, to = 20) int b = y; + } + } + + void test_intrange_gt2(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x > y) { + @IntRange(from = 5, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + + @IntRange(from = 5, to = 7) + // :: error: (assignment.type.incompatible) + int c = x; + @IntRange(from = 5, to = 7) + // :: error: (assignment.type.incompatible) + int d = y; + } else { + @IntRange(from = 5, to = 7) int a = x; + @IntRange(from = 5, to = 7) int b = y; + } + } + + void test_intrange_lte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x <= y) { + @IntRange(from = 0, to = 7) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } else { + @IntRange(from = 3, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } + } + + void test_intrange_lte2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { + if (x <= y) { + @IntRange(from = 2, to = 7) int a = x; + @IntRange(from = 2, to = 10) int b = y; + } else { + @IntRange(from = 6, to = 7) int a = x; + @IntRange(from = 5, to = 6) int b = y; + } + } + + void test_intrange_lt(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x < y) { + @IntRange(from = 5, to = 6) int a = x; + @IntRange(from = 6, to = 7) int b = y; + } else { + @IntRange(from = 5, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } + } + + void test_intrange_lt2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { + if (x < y) { + @IntRange(from = 2, to = 7) int a = x; + @IntRange(from = 2, to = 10) int b = y; + } else { + @IntRange(from = 5, to = 7) int a = x; + @IntRange(from = 5, to = 7) int b = y; + } + } + + void test_intrange_gte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x >= y) { + @IntRange(from = 2, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } else { + @IntRange(from = 0, to = 6) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } + } + + void test_intrange_gte2(@IntRange(from = 3, to = 5) int x, @IntRange(from = 2, to = 6) int y) { + if (x >= y) { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 2, to = 6) int b = y; + } else { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 4, to = 6) int b = y; } + } } diff --git a/framework/tests/value/Refinement2.java b/framework/tests/value/Refinement2.java index 2b2b25b4b32..8065c506912 100644 --- a/framework/tests/value/Refinement2.java +++ b/framework/tests/value/Refinement2.java @@ -2,124 +2,124 @@ public class Refinement2 { - void eq_test(int x, @IntVal({1, 5, 6}) int w) { - if (x == 1) { - // :: error: (assignment.type.incompatible) - @BottomVal int q = x; - - } else if (x == 2) { - } else { - return; - } - - if (x != 1) { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - @IntVal({2}) int z = x; - } - - if (x == 1) { - - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } - - if (x == w) { - @IntVal({1}) int y = x; - @IntVal({1}) int z = w; - } else { - // These two assignments are illegal because one of x or w could be 1, - // while the other is not. So no refinement can be applied. - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } + void eq_test(int x, @IntVal({1, 5, 6}) int w) { + if (x == 1) { + // :: error: (assignment.type.incompatible) + @BottomVal int q = x; + + } else if (x == 2) { + } else { + return; } - void testDeadCode(int x) { - if (x == 4 || x == 5) { - @IntVal({4, 5}) int a = x; - // :: error: (assignment.type.incompatible) - @BottomVal int a2 = x; - } - if (x == 4 && x == 5) { - // This is dead, so x should be bottom. - @IntVal({4, 5}) int a = x; - @BottomVal int a2 = x; - } - if (x != 1 || x != 2) { - return; - } - if (x != 2) { - @IntVal(1) int a = x; - } - - if (x == 3) { - // This is actually dead code, so x is treated as bottom. - @IntVal(3) int y = x; - @IntVal(33) int v = x; - @BottomVal int z = x; - } + if (x != 1) { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + @IntVal({2}) int z = x; } - void simpleNull(@IntVal({1, 2, 3}) Integer x) { - if (x == null) { - @BottomVal int y = x; - } + if (x == 1) { + + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } + + if (x == w) { + @IntVal({1}) int y = x; + @IntVal({1}) int z = w; + } else { + // These two assignments are illegal because one of x or w could be 1, + // while the other is not. So no refinement can be applied. + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } + } + + void testDeadCode(int x) { + if (x == 4 || x == 5) { + @IntVal({4, 5}) int a = x; + // :: error: (assignment.type.incompatible) + @BottomVal int a2 = x; + } + if (x == 4 && x == 5) { + // This is dead, so x should be bottom. + @IntVal({4, 5}) int a = x; + @BottomVal int a2 = x; + } + if (x != 1 || x != 2) { + return; + } + if (x != 2) { + @IntVal(1) int a = x; + } + + if (x == 3) { + // This is actually dead code, so x is treated as bottom. + @IntVal(3) int y = x; + @IntVal(33) int v = x; + @BottomVal int z = x; + } + } + + void simpleNull(@IntVal({1, 2, 3}) Integer x) { + if (x == null) { + @BottomVal int y = x; + } + } + + void moreTests(@IntVal({1, 2, 3}) Integer x, Integer y) { + if (x == null) { + if (y == x) { + // x and y should be @BottomVal + @BottomVal int z1 = x; + @BottomVal int z2 = y; + } else { + // y should be @UnknownVal here. + // :: error: (assignment.type.incompatible) + @IntVal({1, 2, 3}) int z = y; + } + } + + if (x == null) { + if (y < x) { + // x should be @BottomVal + @BottomVal int z1 = x; + // This is dead code since the unboxing of x in the if statement + // will throw a NPE, so the type of y doesn't matter. + // @BottomVal int z2 = y; + } else { + // This is dead code, too. + } + } + } + + void testArrayLen(boolean flag) { + int[] a; + if (flag) { + a = new int[] {1}; + } else { + a = new int[] {1, 2}; } - void moreTests(@IntVal({1, 2, 3}) Integer x, Integer y) { - if (x == null) { - if (y == x) { - // x and y should be @BottomVal - @BottomVal int z1 = x; - @BottomVal int z2 = y; - } else { - // y should be @UnknownVal here. - // :: error: (assignment.type.incompatible) - @IntVal({1, 2, 3}) int z = y; - } - } - - if (x == null) { - if (y < x) { - // x should be @BottomVal - @BottomVal int z1 = x; - // This is dead code since the unboxing of x in the if statement - // will throw a NPE, so the type of y doesn't matter. - // @BottomVal int z2 = y; - } else { - // This is dead code, too. - } - } + if (a.length != 1) { + int @ArrayLen(2) [] b = a; } - void testArrayLen(boolean flag) { - int[] a; - if (flag) { - a = new int[] {1}; - } else { - a = new int[] {1, 2}; - } - - if (a.length != 1) { - int @ArrayLen(2) [] b = a; - } - - if (a.length == 3) { - // Dead code - int @ArrayLen(3) [] b = a; - int @ArrayLen(5) [] c = a; - int @BottomVal [] bot = a; - } + if (a.length == 3) { + // Dead code + int @ArrayLen(3) [] b = a; + int @ArrayLen(5) [] c = a; + int @BottomVal [] bot = a; } + } } diff --git a/framework/tests/value/RegexMatching.java b/framework/tests/value/RegexMatching.java index 58646a426e1..e3c7fa84abe 100644 --- a/framework/tests/value/RegexMatching.java +++ b/framework/tests/value/RegexMatching.java @@ -3,134 +3,134 @@ import org.checkerframework.common.value.qual.*; public class RegexMatching { - void stringConstants() { - @MatchesRegex("a*") String a = "a"; - @MatchesRegex("a*") String blank = ""; - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String b = "b"; - - // NOTE: these tests show that there are implicit anchors in the regular expressions - // used by @MatchesRegex. - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String ab = "ab"; - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String baa = "baa"; - - @MatchesRegex("a") String a1 = "a"; - // :: error: assignment.type.incompatible - @MatchesRegex("a") String blank1 = ""; - // :: error: assignment.type.incompatible - @MatchesRegex("a") String b1 = "b"; - - @MatchesRegex("\\s") String space = " "; - @MatchesRegex("\\s+") String severalSpaces = " "; - // :: error: assignment.type.incompatible - @MatchesRegex("\\s") String b2 = "b"; - - @MatchesRegex("[^abc]") String d = "d"; - @MatchesRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); - // :: error: assignment.type.incompatible - @MatchesRegex("[^abc]") String c = "c"; + void stringConstants() { + @MatchesRegex("a*") String a = "a"; + @MatchesRegex("a*") String blank = ""; + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String b = "b"; + + // NOTE: these tests show that there are implicit anchors in the regular expressions + // used by @MatchesRegex. + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String ab = "ab"; + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String baa = "baa"; + + @MatchesRegex("a") String a1 = "a"; + // :: error: assignment.type.incompatible + @MatchesRegex("a") String blank1 = ""; + // :: error: assignment.type.incompatible + @MatchesRegex("a") String b1 = "b"; + + @MatchesRegex("\\s") String space = " "; + @MatchesRegex("\\s+") String severalSpaces = " "; + // :: error: assignment.type.incompatible + @MatchesRegex("\\s") String b2 = "b"; + + @MatchesRegex("[^abc]") String d = "d"; + @MatchesRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); + // :: error: assignment.type.incompatible + @MatchesRegex("[^abc]") String c = "c"; + } + + void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + @MatchesRegex("a*") String a = aaa; + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String a1 = aab; + + @MatchesRegex("a+") String a2 = aaa; + // :: error: assignment.type.incompatible + @MatchesRegex("a+") String a3 = aab; + } + + void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + @MatchesRegex({"a*", "b*"}) String a = aaa; + @MatchesRegex({"a*", "b*"}) String a1 = aab; + + // :: error: assignment.type.incompatible + @MatchesRegex({"aa", "b*"}) String a2 = aaa; + @MatchesRegex({"aa", "b*"}) String a3 = aab; + } + + void regexSubtypingConstant(@MatchesRegex({"a", "b"}) String ab) { + // :: error: assignment.type.incompatible + @MatchesRegex("a") String a = ab; + @MatchesRegex({"a", "b"}) String ab1 = ab; + @MatchesRegex({"a", "b", "c"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b", "c"}) String abc1 = ab; + } + + void regexSubtyping2(@MatchesRegex({"a*", "b*"}) String ab) { + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String a = ab; + @MatchesRegex({"a*", "b*"}) String ab1 = ab; + @MatchesRegex({"a*", "b*", "c*"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a*") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*", "c*"}) String abc1 = ab; + } + + void lubRegexes( + @MatchesRegex({"a*"}) String astar, @MatchesRegex({"b*"}) String bstar, boolean b) { + String s; + if (b) { + s = astar; + } else { + s = bstar; } - - void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - @MatchesRegex("a*") String a = aaa; - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String a1 = aab; - - @MatchesRegex("a+") String a2 = aaa; - // :: error: assignment.type.incompatible - @MatchesRegex("a+") String a3 = aab; - } - - void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - @MatchesRegex({"a*", "b*"}) String a = aaa; - @MatchesRegex({"a*", "b*"}) String a1 = aab; - - // :: error: assignment.type.incompatible - @MatchesRegex({"aa", "b*"}) String a2 = aaa; - @MatchesRegex({"aa", "b*"}) String a3 = aab; - } - - void regexSubtypingConstant(@MatchesRegex({"a", "b"}) String ab) { - // :: error: assignment.type.incompatible - @MatchesRegex("a") String a = ab; - @MatchesRegex({"a", "b"}) String ab1 = ab; - @MatchesRegex({"a", "b", "c"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b", "c"}) String abc1 = ab; - } - - void regexSubtyping2(@MatchesRegex({"a*", "b*"}) String ab) { - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String a = ab; - @MatchesRegex({"a*", "b*"}) String ab1 = ab; - @MatchesRegex({"a*", "b*", "c*"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a*") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*", "c*"}) String abc1 = ab; - } - - void lubRegexes( - @MatchesRegex({"a*"}) String astar, @MatchesRegex({"b*"}) String bstar, boolean b) { - String s; - if (b) { - s = astar; - } else { - s = bstar; - } - @MatchesRegex({"a*", "b*"}) String s1 = s; - // :: error: assignment.type.incompatible - @MatchesRegex({"a*"}) String s2 = s; - // :: error: assignment.type.incompatible - @MatchesRegex({"b*"}) String s3 = s; - } - - void lubRegexWithStringVal( - @MatchesRegex({"a*"}) String astar, @StringVal({"b"}) String bval, boolean b) { - String s; - if (b) { - s = astar; - } else { - s = bval; - } - // NOTE: This depends on the internal implementation. Semantically identical code like this - // yields an error: - // @MatchesRegex({"a*", "b"}) String s0 = s; - @MatchesRegex({"a*", "\\Qb\\E"}) String s1 = s; - // :: error: assignment.type.incompatible - @MatchesRegex({"a*"}) String s2 = s; - // :: error: assignment.type.incompatible - @StringVal({"b"}) String s3 = s; - // :: error: assignment.type.incompatible - @MatchesRegex("b") String s4 = s; - // :: error: assignment.type.incompatible - @MatchesRegex("^b$") String s5 = s; - } - - void stringToRegex1(@StringVal({"(a)"}) String a) { - // :: error: assignment.type.incompatible - @MatchesRegex("(a)") String a2 = a; - } - - void stringToRegex2(@StringVal({"a"}) String a) { - @MatchesRegex("(a)") String a2 = a; - } - - void stringToRegex3(@StringVal({"a"}) String a) { - @MatchesRegex("^a$") String a2 = a; - } - - void regexToString(@MatchesRegex("^a$") String a) { - // TODO: This is a false positive. In the future, eliminate it. - // :: error: assignment.type.incompatible - @StringVal({"a"}) String a2 = a; + @MatchesRegex({"a*", "b*"}) String s1 = s; + // :: error: assignment.type.incompatible + @MatchesRegex({"a*"}) String s2 = s; + // :: error: assignment.type.incompatible + @MatchesRegex({"b*"}) String s3 = s; + } + + void lubRegexWithStringVal( + @MatchesRegex({"a*"}) String astar, @StringVal({"b"}) String bval, boolean b) { + String s; + if (b) { + s = astar; + } else { + s = bval; } + // NOTE: This depends on the internal implementation. Semantically identical code like this + // yields an error: + // @MatchesRegex({"a*", "b"}) String s0 = s; + @MatchesRegex({"a*", "\\Qb\\E"}) String s1 = s; + // :: error: assignment.type.incompatible + @MatchesRegex({"a*"}) String s2 = s; + // :: error: assignment.type.incompatible + @StringVal({"b"}) String s3 = s; + // :: error: assignment.type.incompatible + @MatchesRegex("b") String s4 = s; + // :: error: assignment.type.incompatible + @MatchesRegex("^b$") String s5 = s; + } + + void stringToRegex1(@StringVal({"(a)"}) String a) { + // :: error: assignment.type.incompatible + @MatchesRegex("(a)") String a2 = a; + } + + void stringToRegex2(@StringVal({"a"}) String a) { + @MatchesRegex("(a)") String a2 = a; + } + + void stringToRegex3(@StringVal({"a"}) String a) { + @MatchesRegex("^a$") String a2 = a; + } + + void regexToString(@MatchesRegex("^a$") String a) { + // TODO: This is a false positive. In the future, eliminate it. + // :: error: assignment.type.incompatible + @StringVal({"a"}) String a2 = a; + } } diff --git a/framework/tests/value/RegexNonMatching.java b/framework/tests/value/RegexNonMatching.java index 05d1dd5c4a9..dd9d2435537 100644 --- a/framework/tests/value/RegexNonMatching.java +++ b/framework/tests/value/RegexNonMatching.java @@ -4,248 +4,246 @@ public class RegexNonMatching { - void stringConstants() { - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a*") String aStar = "a"; - // :: error: assignment.type.incompatible - aStar = ""; - aStar = "b"; - @DoesNotMatchRegex({"a+"}) String aPlus = "b"; - // @DoesNotMatchRegex("a+") String aPlus = "b"; - aStar = "ab"; - aStar = "baa"; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a") String a1 = "a"; - @DoesNotMatchRegex("a") String blank1 = ""; - @DoesNotMatchRegex("a") String b1 = "b"; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("\\s") String space = " "; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("\\s+") String severalSpaces = " "; - // TODO: this should work - @DoesNotMatchRegex("\\s") String b2 = "b"; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("[^abc]") String d = "d"; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); - // TODO: this should work, shouldn't it? - @DoesNotMatchRegex("[^abc]") String c = "c"; - } - - void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a*") String a = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a*") String a1 = aab; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a+") String a2 = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a+") String a3 = aab; - } - - void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*"}) String a = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*"}) String a1 = aab; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"aa", "b*"}) String a2 = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"aa", "b*"}) String a3 = aab; - } - - void regexSubtypingConstant(@DoesNotMatchRegex({"a", "b"}) String ab) { - @DoesNotMatchRegex("a") String a = ab; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("c") String c = ab; - @DoesNotMatchRegex({"a", "b"}) String ab1 = ab; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a", "b", "c"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b", "c"}) String abc1 = ab; - } - - void regexSubtyping2(@DoesNotMatchRegex({"a*", "b*"}) String ab) { - @DoesNotMatchRegex("a*") String a = ab; - @DoesNotMatchRegex({"a*", "b*"}) String ab1 = ab; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*", "c*"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a*") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*", "c*"}) String abc1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"c*"}) String c = ab; - } - - void lubRegexes( - @DoesNotMatchRegex({"a*"}) String astar, - @DoesNotMatchRegex({"b*"}) String bstar, - boolean b) { - String s; - if (b) { - s = astar; - } else { - s = bstar; - } - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*"}) String s1 = s; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*"}) String s2 = s; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"b*"}) String s3 = s; - s3 = s1; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({}) String s4 = s; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({".*"}) String s5 = s; - @UnknownVal() String s6 = s; - } - - void lubRegexWithStringVal( - @DoesNotMatchRegex({"a*"}) String astar, - @StringVal({"a", "aa", "b", "bb", "c", "cc"}) String dnmsNone, - @StringVal({"aa", "b", "bb", "c", "cc"}) String dnmsA, - @StringVal({"a", "aa", "bb", "c", "cc"}) String dnmsB, - @StringVal({"a", "aa", "b", "bb", "cc"}) String dnmsC, - @StringVal({"aa", "bb", "c", "cc"}) String dnmsAB, - @StringVal({"aa", "b", "bb", "cc"}) String dnmsAC, - @StringVal({"a", "aa", "bb", "cc"}) String dnmsBC, - @StringVal({"aa", "bb", "cc"}) String dnmsABC) { - - @DoesNotMatchRegex({}) String dnmNone; - @DoesNotMatchRegex({"a"}) String dnmA; - @DoesNotMatchRegex({"b"}) String dnmB; - @DoesNotMatchRegex({"c"}) String dnmC; - @DoesNotMatchRegex({"a", "b"}) String dnmAB; - @DoesNotMatchRegex({"a", "c"}) String dnmAC; - @DoesNotMatchRegex({"b", "c"}) String dnmBC; - @DoesNotMatchRegex({"a", "b", "c"}) String dnmABC; - - dnmNone = dnmsNone; - // :: error: assignment.type.incompatible - dnmA = dnmsNone; - // :: error: assignment.type.incompatible - dnmB = dnmsNone; - // :: error: assignment.type.incompatible - dnmC = dnmsNone; - // :: error: assignment.type.incompatible - dnmAB = dnmsNone; - // :: error: assignment.type.incompatible - dnmAC = dnmsNone; - // :: error: assignment.type.incompatible - dnmBC = dnmsNone; - // :: error: assignment.type.incompatible - dnmABC = dnmsNone; - - dnmNone = dnmsA; - dnmA = dnmsA; - // :: error: assignment.type.incompatible - dnmB = dnmsA; - // :: error: assignment.type.incompatible - dnmC = dnmsA; - // :: error: assignment.type.incompatible - dnmAB = dnmsA; - // :: error: assignment.type.incompatible - dnmAC = dnmsA; - // :: error: assignment.type.incompatible - dnmBC = dnmsA; - // :: error: assignment.type.incompatible - dnmABC = dnmsA; - - dnmNone = dnmsB; - // :: error: assignment.type.incompatible - dnmA = dnmsB; - dnmB = dnmsB; - // :: error: assignment.type.incompatible - dnmC = dnmsB; - // :: error: assignment.type.incompatible - dnmAB = dnmsB; - // :: error: assignment.type.incompatible - dnmAC = dnmsB; - // :: error: assignment.type.incompatible - dnmBC = dnmsB; - // :: error: assignment.type.incompatible - dnmABC = dnmsB; - - dnmNone = dnmsC; - // :: error: assignment.type.incompatible - dnmA = dnmsC; - // :: error: assignment.type.incompatible - dnmB = dnmsC; - dnmC = dnmsC; - // :: error: assignment.type.incompatible - dnmAB = dnmsC; - // :: error: assignment.type.incompatible - dnmAC = dnmsC; - // :: error: assignment.type.incompatible - dnmBC = dnmsC; - // :: error: assignment.type.incompatible - dnmABC = dnmsC; - - dnmNone = dnmsAC; - dnmA = dnmsAC; - // :: error: assignment.type.incompatible - dnmB = dnmsAC; - dnmC = dnmsAC; - // :: error: assignment.type.incompatible - dnmAB = dnmsAC; - dnmAC = dnmsAC; - // :: error: assignment.type.incompatible - dnmBC = dnmsAC; - // :: error: assignment.type.incompatible - dnmABC = dnmsAC; - - dnmNone = dnmsAB; - dnmA = dnmsAB; - dnmB = dnmsAB; - // :: error: assignment.type.incompatible - dnmC = dnmsAB; - dnmAB = dnmsAB; - // :: error: assignment.type.incompatible - dnmAC = dnmsAB; - // :: error: assignment.type.incompatible - dnmBC = dnmsAB; - // :: error: assignment.type.incompatible - dnmABC = dnmsAB; - - dnmNone = dnmsBC; - // :: error: assignment.type.incompatible - dnmA = dnmsBC; - dnmB = dnmsBC; - dnmC = dnmsBC; - // :: error: assignment.type.incompatible - dnmAB = dnmsBC; - // :: error: assignment.type.incompatible - dnmAC = dnmsBC; - dnmBC = dnmsBC; - // :: error: assignment.type.incompatible - dnmABC = dnmsBC; - - dnmNone = dnmsABC; - dnmA = dnmsABC; - dnmB = dnmsABC; - dnmC = dnmsABC; - dnmAB = dnmsABC; - dnmAC = dnmsABC; - dnmBC = dnmsABC; - dnmABC = dnmsABC; - } - - void stringToRegex1(@StringVal({"(a)"}) String a) { - @DoesNotMatchRegex("(a)") String a2 = a; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("\\(a\\)") String a3 = a; + void stringConstants() { + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a*") String aStar = "a"; + // :: error: assignment.type.incompatible + aStar = ""; + aStar = "b"; + @DoesNotMatchRegex({"a+"}) String aPlus = "b"; + // @DoesNotMatchRegex("a+") String aPlus = "b"; + aStar = "ab"; + aStar = "baa"; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a") String a1 = "a"; + @DoesNotMatchRegex("a") String blank1 = ""; + @DoesNotMatchRegex("a") String b1 = "b"; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("\\s") String space = " "; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("\\s+") String severalSpaces = " "; + // TODO: this should work + @DoesNotMatchRegex("\\s") String b2 = "b"; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("[^abc]") String d = "d"; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); + // TODO: this should work, shouldn't it? + @DoesNotMatchRegex("[^abc]") String c = "c"; + } + + void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a*") String a = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a*") String a1 = aab; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a+") String a2 = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a+") String a3 = aab; + } + + void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*"}) String a = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*"}) String a1 = aab; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"aa", "b*"}) String a2 = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"aa", "b*"}) String a3 = aab; + } + + void regexSubtypingConstant(@DoesNotMatchRegex({"a", "b"}) String ab) { + @DoesNotMatchRegex("a") String a = ab; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("c") String c = ab; + @DoesNotMatchRegex({"a", "b"}) String ab1 = ab; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a", "b", "c"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b", "c"}) String abc1 = ab; + } + + void regexSubtyping2(@DoesNotMatchRegex({"a*", "b*"}) String ab) { + @DoesNotMatchRegex("a*") String a = ab; + @DoesNotMatchRegex({"a*", "b*"}) String ab1 = ab; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*", "c*"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a*") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*", "c*"}) String abc1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"c*"}) String c = ab; + } + + void lubRegexes( + @DoesNotMatchRegex({"a*"}) String astar, @DoesNotMatchRegex({"b*"}) String bstar, boolean b) { + String s; + if (b) { + s = astar; + } else { + s = bstar; } + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*"}) String s1 = s; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*"}) String s2 = s; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"b*"}) String s3 = s; + s3 = s1; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({}) String s4 = s; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({".*"}) String s5 = s; + @UnknownVal() String s6 = s; + } + + void lubRegexWithStringVal( + @DoesNotMatchRegex({"a*"}) String astar, + @StringVal({"a", "aa", "b", "bb", "c", "cc"}) String dnmsNone, + @StringVal({"aa", "b", "bb", "c", "cc"}) String dnmsA, + @StringVal({"a", "aa", "bb", "c", "cc"}) String dnmsB, + @StringVal({"a", "aa", "b", "bb", "cc"}) String dnmsC, + @StringVal({"aa", "bb", "c", "cc"}) String dnmsAB, + @StringVal({"aa", "b", "bb", "cc"}) String dnmsAC, + @StringVal({"a", "aa", "bb", "cc"}) String dnmsBC, + @StringVal({"aa", "bb", "cc"}) String dnmsABC) { + + @DoesNotMatchRegex({}) String dnmNone; + @DoesNotMatchRegex({"a"}) String dnmA; + @DoesNotMatchRegex({"b"}) String dnmB; + @DoesNotMatchRegex({"c"}) String dnmC; + @DoesNotMatchRegex({"a", "b"}) String dnmAB; + @DoesNotMatchRegex({"a", "c"}) String dnmAC; + @DoesNotMatchRegex({"b", "c"}) String dnmBC; + @DoesNotMatchRegex({"a", "b", "c"}) String dnmABC; + + dnmNone = dnmsNone; + // :: error: assignment.type.incompatible + dnmA = dnmsNone; + // :: error: assignment.type.incompatible + dnmB = dnmsNone; + // :: error: assignment.type.incompatible + dnmC = dnmsNone; + // :: error: assignment.type.incompatible + dnmAB = dnmsNone; + // :: error: assignment.type.incompatible + dnmAC = dnmsNone; + // :: error: assignment.type.incompatible + dnmBC = dnmsNone; + // :: error: assignment.type.incompatible + dnmABC = dnmsNone; + + dnmNone = dnmsA; + dnmA = dnmsA; + // :: error: assignment.type.incompatible + dnmB = dnmsA; + // :: error: assignment.type.incompatible + dnmC = dnmsA; + // :: error: assignment.type.incompatible + dnmAB = dnmsA; + // :: error: assignment.type.incompatible + dnmAC = dnmsA; + // :: error: assignment.type.incompatible + dnmBC = dnmsA; + // :: error: assignment.type.incompatible + dnmABC = dnmsA; + + dnmNone = dnmsB; + // :: error: assignment.type.incompatible + dnmA = dnmsB; + dnmB = dnmsB; + // :: error: assignment.type.incompatible + dnmC = dnmsB; + // :: error: assignment.type.incompatible + dnmAB = dnmsB; + // :: error: assignment.type.incompatible + dnmAC = dnmsB; + // :: error: assignment.type.incompatible + dnmBC = dnmsB; + // :: error: assignment.type.incompatible + dnmABC = dnmsB; + + dnmNone = dnmsC; + // :: error: assignment.type.incompatible + dnmA = dnmsC; + // :: error: assignment.type.incompatible + dnmB = dnmsC; + dnmC = dnmsC; + // :: error: assignment.type.incompatible + dnmAB = dnmsC; + // :: error: assignment.type.incompatible + dnmAC = dnmsC; + // :: error: assignment.type.incompatible + dnmBC = dnmsC; + // :: error: assignment.type.incompatible + dnmABC = dnmsC; + + dnmNone = dnmsAC; + dnmA = dnmsAC; + // :: error: assignment.type.incompatible + dnmB = dnmsAC; + dnmC = dnmsAC; + // :: error: assignment.type.incompatible + dnmAB = dnmsAC; + dnmAC = dnmsAC; + // :: error: assignment.type.incompatible + dnmBC = dnmsAC; + // :: error: assignment.type.incompatible + dnmABC = dnmsAC; + + dnmNone = dnmsAB; + dnmA = dnmsAB; + dnmB = dnmsAB; + // :: error: assignment.type.incompatible + dnmC = dnmsAB; + dnmAB = dnmsAB; + // :: error: assignment.type.incompatible + dnmAC = dnmsAB; + // :: error: assignment.type.incompatible + dnmBC = dnmsAB; + // :: error: assignment.type.incompatible + dnmABC = dnmsAB; + + dnmNone = dnmsBC; + // :: error: assignment.type.incompatible + dnmA = dnmsBC; + dnmB = dnmsBC; + dnmC = dnmsBC; + // :: error: assignment.type.incompatible + dnmAB = dnmsBC; + // :: error: assignment.type.incompatible + dnmAC = dnmsBC; + dnmBC = dnmsBC; + // :: error: assignment.type.incompatible + dnmABC = dnmsBC; + + dnmNone = dnmsABC; + dnmA = dnmsABC; + dnmB = dnmsABC; + dnmC = dnmsABC; + dnmAB = dnmsABC; + dnmAC = dnmsABC; + dnmBC = dnmsABC; + dnmABC = dnmsABC; + } + + void stringToRegex1(@StringVal({"(a)"}) String a) { + @DoesNotMatchRegex("(a)") String a2 = a; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("\\(a\\)") String a3 = a; + } } diff --git a/framework/tests/value/RegexPatternSyntaxException.java b/framework/tests/value/RegexPatternSyntaxException.java index 35b73ee39ee..9f279ae0745 100644 --- a/framework/tests/value/RegexPatternSyntaxException.java +++ b/framework/tests/value/RegexPatternSyntaxException.java @@ -3,9 +3,9 @@ import org.checkerframework.common.value.qual.*; public class RegexPatternSyntaxException { - // :: warning: (invalid.matches.regex) - void stringConstants1(@MatchesRegex("(a*") String a) {} + // :: warning: (invalid.matches.regex) + void stringConstants1(@MatchesRegex("(a*") String a) {} - // :: warning: (invalid.doesnotmatch.regex) - void stringConstants2(@DoesNotMatchRegex("(a*") String a) {} + // :: warning: (invalid.doesnotmatch.regex) + void stringConstants2(@DoesNotMatchRegex("(a*") String a) {} } diff --git a/framework/tests/value/RepeatMinLenIf.java b/framework/tests/value/RepeatMinLenIf.java index 0dcb2213ba1..af9f2f89da8 100644 --- a/framework/tests/value/RepeatMinLenIf.java +++ b/framework/tests/value/RepeatMinLenIf.java @@ -2,56 +2,56 @@ public class RepeatMinLenIf { - protected String a; - protected String b; - protected String c; + protected String a; + protected String b; + protected String c; - public boolean func1() { - a = "checker"; - c = "framework"; - b = "opensource"; - return true; - } + public boolean func1() { + a = "checker"; + c = "framework"; + b = "opensource"; + return true; + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean client2() { - return withcondpostconditionfunc1(); - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean client2() { + return withcondpostconditionfunc1(); + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean withcondpostconditionsfunc1() { - a = "checker"; - c = "framework"; - b = "opensource"; - return true; - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean withcondpostconditionsfunc1() { + a = "checker"; + c = "framework"; + b = "opensource"; + return true; + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean withcondpostconditionfunc1() { - a = "checker"; - c = "framework"; - b = "opensource"; - return true; - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean withcondpostconditionfunc1() { + a = "checker"; + c = "framework"; + b = "opensource"; + return true; + } } diff --git a/framework/tests/value/RepeatMinLenIfWithError.java b/framework/tests/value/RepeatMinLenIfWithError.java index 62c392ec06d..e189875df6f 100644 --- a/framework/tests/value/RepeatMinLenIfWithError.java +++ b/framework/tests/value/RepeatMinLenIfWithError.java @@ -2,58 +2,58 @@ public class RepeatMinLenIfWithError { - protected String a; - protected String b; - protected String c; + protected String a; + protected String b; + protected String c; - public boolean func1() { - a = "checker"; - c = "framework"; - b = "hello"; - return true; - } + public boolean func1() { + a = "checker"; + c = "framework"; + b = "hello"; + return true; + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean client2() { - return withcondpostconditionfunc1(); - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean client2() { + return withcondpostconditionfunc1(); + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean withcondpostconditionsfunc1() { - a = "checker"; - c = "framework"; - b = "hello"; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean withcondpostconditionsfunc1() { + a = "checker"; + c = "framework"; + b = "hello"; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean withcondpostconditionfunc1() { - a = "checker"; - c = "framework"; - b = "hello"; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean withcondpostconditionfunc1() { + a = "checker"; + c = "framework"; + b = "hello"; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/framework/tests/value/Repo.java b/framework/tests/value/Repo.java index 8caa5f2a17b..f4adf00c3cf 100644 --- a/framework/tests/value/Repo.java +++ b/framework/tests/value/Repo.java @@ -1,16 +1,15 @@ -import org.checkerframework.common.value.qual.*; - import java.util.BitSet; +import org.checkerframework.common.value.qual.*; public class Repo { - private BitSet bitmap; - boolean flag = true; + private BitSet bitmap; + boolean flag = true; - void testLoop() { - for (int i = 0; i < 20; i++) { - // :: error: (assignment.type.incompatible) - @IntVal(0) int x = i; - int j = flag ? i : 3; - } + void testLoop() { + for (int i = 0; i < 20; i++) { + // :: error: (assignment.type.incompatible) + @IntVal(0) int x = i; + int j = flag ? i : 3; } + } } diff --git a/framework/tests/value/SplitAssignments.java b/framework/tests/value/SplitAssignments.java index 55498f5da19..1aba24fd946 100644 --- a/framework/tests/value/SplitAssignments.java +++ b/framework/tests/value/SplitAssignments.java @@ -1,19 +1,19 @@ import org.checkerframework.common.value.qual.*; public class SplitAssignments { - void foo(@IntRange(from = 5, to = 200) int x) { - int z; - if ((z = x) == 5) { - @IntRange(from = 5, to = 5) int w = x; - @IntRange(from = 5, to = 5) int q = z; - } + void foo(@IntRange(from = 5, to = 200) int x) { + int z; + if ((z = x) == 5) { + @IntRange(from = 5, to = 5) int w = x; + @IntRange(from = 5, to = 5) int q = z; } + } - void bar(@IntVal({1, 2}) int x) { - int z; - if ((z = x) == 1) { - @IntVal(1) int w = x; - @IntVal(1) int q = z; - } + void bar(@IntVal({1, 2}) int x) { + int z; + if ((z = x) == 1) { + @IntVal(1) int w = x; + @IntVal(1) int q = z; } + } } diff --git a/framework/tests/value/StartsEndsWith.java b/framework/tests/value/StartsEndsWith.java index 85659b9261b..117b07d8065 100644 --- a/framework/tests/value/StartsEndsWith.java +++ b/framework/tests/value/StartsEndsWith.java @@ -6,57 +6,57 @@ public class StartsEndsWith { - void refineStartsWith(String str) { - if (str.startsWith("prefix")) { - @MinLen(6) String s6 = str; - // :: error: (assignment.type.incompatible) - @MinLen(7) String s7 = str; - } else { - // :: error: (assignment.type.incompatible) - @MinLen(6) String s6 = str; - } + void refineStartsWith(String str) { + if (str.startsWith("prefix")) { + @MinLen(6) String s6 = str; + // :: error: (assignment.type.incompatible) + @MinLen(7) String s7 = str; + } else { + // :: error: (assignment.type.incompatible) + @MinLen(6) String s6 = str; } - - void refineEndsWith(String str) { - if (str.endsWith("suffix")) { - @MinLen(6) String s6 = str; - // :: error: (assignment.type.incompatible) - @MinLen(7) String s7 = str; - } else { - // :: error: (assignment.type.incompatible) - @MinLen(6) String s6 = str; - } + } + + void refineEndsWith(String str) { + if (str.endsWith("suffix")) { + @MinLen(6) String s6 = str; + // :: error: (assignment.type.incompatible) + @MinLen(7) String s7 = str; + } else { + // :: error: (assignment.type.incompatible) + @MinLen(6) String s6 = str; } + } - void refineStartsEndsWith(String str) { - if (str.startsWith("longprefix") && str.endsWith("prefix")) { - @MinLen(10) String s10 = str; - // :: error: (assignment.type.incompatible) - @MinLen(11) String s11 = str; - } + void refineStartsEndsWith(String str) { + if (str.startsWith("longprefix") && str.endsWith("prefix")) { + @MinLen(10) String s10 = str; + // :: error: (assignment.type.incompatible) + @MinLen(11) String s11 = str; } + } - void refineStartsArrayLen(String str, @ArrayLen(10) String prefix) { - if (str.startsWith(prefix)) { - @MinLen(10) String sg10 = str; - // :: error: (assignment.type.incompatible) - @ArrayLen(10) String s10 = str; - } + void refineStartsArrayLen(String str, @ArrayLen(10) String prefix) { + if (str.startsWith(prefix)) { + @MinLen(10) String sg10 = str; + // :: error: (assignment.type.incompatible) + @ArrayLen(10) String s10 = str; } + } - void noRefinement(@ArrayLen(10) String str) { - if (str.startsWith("x")) { - @ArrayLen(10) String s10 = str; - } + void noRefinement(@ArrayLen(10) String str) { + if (str.startsWith("x")) { + @ArrayLen(10) String s10 = str; } + } - void refineStartsStaticFinal(String str) { - if (str.startsWith(StartsEndsWithExternal.staticFinalField)) { - @MinLen(3) String s3 = str; - } + void refineStartsStaticFinal(String str) { + if (str.startsWith(StartsEndsWithExternal.staticFinalField)) { + @MinLen(3) String s3 = str; } + } } class StartsEndsWithExternal { - public static final String staticFinalField = "str"; + public static final String staticFinalField = "str"; } diff --git a/framework/tests/value/StaticExTest.java b/framework/tests/value/StaticExTest.java index 6180eadca10..dd1b7cd0a2d 100644 --- a/framework/tests/value/StaticExTest.java +++ b/framework/tests/value/StaticExTest.java @@ -1,45 +1,45 @@ import org.checkerframework.common.value.qual.*; public class StaticExTest { - boolean flag; + boolean flag; - void test1() { - String s = "helloworlod"; - @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; - @IntVal({5, 0, 9}) int start = flag ? 9 : flag ? 5 : 0; - // flag?1:flag?6: - @IntVal({-1, 8, 9, 2, 4, 6}) int result = s.indexOf(subString, start); - } + void test1() { + String s = "helloworlod"; + @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; + @IntVal({5, 0, 9}) int start = flag ? 9 : flag ? 5 : 0; + // flag?1:flag?6: + @IntVal({-1, 8, 9, 2, 4, 6}) int result = s.indexOf(subString, start); + } - void test2() { - String s = flag ? "helloworlod" : "lololxxolxxxol"; - @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; - @IntVal({0, 9}) int start = flag ? 9 : 0; - // flag?1:flag?6: - @IntVal({-1, 0, 1, 2, 4, 9, 12, 13}) int result3 = s.indexOf(subString, start); - } + void test2() { + String s = flag ? "helloworlod" : "lololxxolxxxol"; + @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; + @IntVal({0, 9}) int start = flag ? 9 : 0; + // flag?1:flag?6: + @IntVal({-1, 0, 1, 2, 4, 9, 12, 13}) int result3 = s.indexOf(subString, start); + } - void test3() { - @IntVal({0, 1}) int offset = flag ? 0 : 1; - char[] data = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; - @IntVal({5, 6}) int charCount = flag ? 5 : 6; - @StringVal({"hello", "ellob", "hellob", "elloby"}) String s = new String(data, offset, charCount); - } + void test3() { + @IntVal({0, 1}) int offset = flag ? 0 : 1; + char[] data = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; + @IntVal({5, 6}) int charCount = flag ? 5 : 6; + @StringVal({"hello", "ellob", "hellob", "elloby"}) String s = new String(data, offset, charCount); + } - void test4() { - @IntVal({0, 1}) int offset = flag ? 0 : 1; - char[] data1 = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; - char[] data2 = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; - char @StringVal({"hellobyeto", "abcdefghij"}) [] data = flag ? data1 : data2; - @IntVal({5, 6}) int charCount = flag ? 5 : 6; - @StringVal({"hello", "ellob", "hellob", "elloby", "abcde", "bcdef", "abcdef", "bcdefg"}) String s = new String(data, offset, charCount); - } + void test4() { + @IntVal({0, 1}) int offset = flag ? 0 : 1; + char[] data1 = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; + char[] data2 = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + char @StringVal({"hellobyeto", "abcdefghij"}) [] data = flag ? data1 : data2; + @IntVal({5, 6}) int charCount = flag ? 5 : 6; + @StringVal({"hello", "ellob", "hellob", "elloby", "abcde", "bcdef", "abcdef", "bcdefg"}) String s = new String(data, offset, charCount); + } - static byte[] b = new byte[0]; + static byte[] b = new byte[0]; - void constructorsArrays() { - char @ArrayLen(100) [] c = new char[100]; - String s = new String(c); - new String(b); - } + void constructorsArrays() { + char @ArrayLen(100) [] c = new char[100]; + String s = new String(c); + new String(b); + } } diff --git a/framework/tests/value/StaticallyExecutableWarnings.java b/framework/tests/value/StaticallyExecutableWarnings.java index d149d08a2a0..cdf662be4ac 100644 --- a/framework/tests/value/StaticallyExecutableWarnings.java +++ b/framework/tests/value/StaticallyExecutableWarnings.java @@ -3,43 +3,43 @@ public class StaticallyExecutableWarnings { - @StaticallyExecutable - // :: warning: (statically.executable.not.pure) - static int addNotPure(int a, int b) { - return a + b; - } + @StaticallyExecutable + // :: warning: (statically.executable.not.pure) + static int addNotPure(int a, int b) { + return a + b; + } - @StaticallyExecutable - @Pure - static int add(Integer a, Integer b) { - return a + b; - } + @StaticallyExecutable + @Pure + static int add(Integer a, Integer b) { + return a + b; + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.parameter.type) - int receiverCannotBeConstant(int a, int b) { - return a + b; - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.parameter.type) + int receiverCannotBeConstant(int a, int b) { + return a + b; + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.parameter.type) - int explicitReceiverCannotBeConstant(StaticallyExecutableWarnings this, int a, int b) { - return a + b; - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.parameter.type) + int explicitReceiverCannotBeConstant(StaticallyExecutableWarnings this, int a, int b) { + return a + b; + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.return.type) - static StaticallyExecutableWarnings returnTypeCannotBeConstant(int a, int b) { - return new StaticallyExecutableWarnings(); - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.return.type) + static StaticallyExecutableWarnings returnTypeCannotBeConstant(int a, int b) { + return new StaticallyExecutableWarnings(); + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.parameter.type) - static int parameterCannotBeConstant(int a, int b, Object o) { - return a + b; - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.parameter.type) + static int parameterCannotBeConstant(int a, int b, Object o) { + return a + b; + } } diff --git a/framework/tests/value/StringConcats.java b/framework/tests/value/StringConcats.java index 7187f1914f2..7c50e316c5c 100644 --- a/framework/tests/value/StringConcats.java +++ b/framework/tests/value/StringConcats.java @@ -3,44 +3,44 @@ import org.checkerframework.common.value.qual.StringVal; public class StringConcats { - void stringConcat() { - @StringVal("helloa11.01.020truenull2626") String everything = "hello" + 'a' + 1 + 1.0 + 1.0f + 20L + true + null + 0x1a + 0b11010; + void stringConcat() { + @StringVal("helloa11.01.020truenull2626") String everything = "hello" + 'a' + 1 + 1.0 + 1.0f + 20L + true + null + 0x1a + 0b11010; - @StringVal("true") String bool = "" + true; - @StringVal("null") String nullV = "" + null; - // :: error: (assignment.type.incompatible) - @BottomVal String bottom = "" + null; - @StringVal("1") String intL = "" + 1; - @StringVal("$") String charL = "" + '$'; - @StringVal("1.0") String doubleDefault = "" + 1.0; - @StringVal("1.0") String doubleL = "" + 1.0d; - @StringVal("26") String hexVal = "" + 0x1a; - @StringVal("26") String binaryVal = "" + 0b11010; - @StringVal("12.3") String floatVal = "" + 12.3f; - @StringVal("123.0") String science = "" + 1.23e2; - } + @StringVal("true") String bool = "" + true; + @StringVal("null") String nullV = "" + null; + // :: error: (assignment.type.incompatible) + @BottomVal String bottom = "" + null; + @StringVal("1") String intL = "" + 1; + @StringVal("$") String charL = "" + '$'; + @StringVal("1.0") String doubleDefault = "" + 1.0; + @StringVal("1.0") String doubleL = "" + 1.0d; + @StringVal("26") String hexVal = "" + 0x1a; + @StringVal("26") String binaryVal = "" + 0b11010; + @StringVal("12.3") String floatVal = "" + 12.3f; + @StringVal("123.0") String science = "" + 1.23e2; + } - void compoundStringAssignement() { - String s = ""; - s += "hello"; - s += 'a'; - s += 1; - s += 1.0; - s += 1.0f; - s += 20L; - s += true; - s += null; - s += 0x1a; - s += 0b11010; - // TODO: this should pass - // compound assignments have not been implemented. - // :: error: (assignment.type.incompatible) - @StringVal("helloa11.01.020truenull2626") String all = s; - } + void compoundStringAssignement() { + String s = ""; + s += "hello"; + s += 'a'; + s += 1; + s += 1.0; + s += 1.0f; + s += 20L; + s += true; + s += null; + s += 0x1a; + s += 0b11010; + // TODO: this should pass + // compound assignments have not been implemented. + // :: error: (assignment.type.incompatible) + @StringVal("helloa11.01.020truenull2626") String all = s; + } - void stringIntRangeConcat( - @IntRange(from = 0, to = 1) int num, @IntRange(from = 'A', to = 'B') char letter) { - @StringVal({"num0", "num1"}) String numV = "num" + num; - @StringVal({"letterA", "letterB"}) String letterV = "letter" + letter; - } + void stringIntRangeConcat( + @IntRange(from = 0, to = 1) int num, @IntRange(from = 'A', to = 'B') char letter) { + @StringVal({"num0", "num1"}) String numV = "num" + num; + @StringVal({"letterA", "letterB"}) String letterV = "letter" + letter; + } } diff --git a/framework/tests/value/StringLen.java b/framework/tests/value/StringLen.java index 9b08002271f..eb98d591be2 100644 --- a/framework/tests/value/StringLen.java +++ b/framework/tests/value/StringLen.java @@ -6,107 +6,107 @@ import org.checkerframework.common.value.qual.StringVal; public class StringLen { - void stringValArrayLen( - @StringVal("") String empty, - @StringVal("const") String constant, - @StringVal({"s", "longconstant"}) String values, - String unknown) { - - // Compatibility with ArrayLen - @ArrayLen(0) String len0 = empty; - @ArrayLen(5) String len5 = constant; - @ArrayLen({1, 12}) String len1_12 = values; - - // Compatibility with ArrayLenRange - @ArrayLenRange(from = 0, to = 0) String rng0 = empty; - @ArrayLenRange(from = 5, to = 5) String rng5 = constant; - @ArrayLenRange(from = 1, to = 12) String rng1_12 = values; - - // :: error: (assignment.type.incompatible) - @ArrayLen(4) String len4 = constant; - // :: error: (assignment.type.incompatible) - @ArrayLenRange(from = 1, to = 11) String rng1_10 = values; - } - - void stringValLubToArrayLen( - boolean flag, - @StringVal({"a", "b", "c", "d", "e"}) String ae, - @StringVal({"f", "g", "h", "i", "j", "k"}) String fk, - @StringVal({"ffff", "gggg", "hhhh", "iiii", "jjjj", "kkkkkkk"}) String fkR) { - - @ArrayLen(1) String ak = flag ? ae : fk; - @ArrayLen({1, 4, 7}) String akR = flag ? ae : fkR; - } - - void stringValLubToArrayLenRange( - boolean flag, - @StringVal({"a", "bb", "ccc", "dddd", "eeeee"}) String ae, - @StringVal({"ffffff", "ggggggg", "hhhhhhhh", "iiiiiiiii", "jjjjjjjjjj", "kkkkkkkkkkk"}) String fk) { - - @ArrayLenRange(from = 1, to = 11) String ak = flag ? ae : fk; - } - - void arrayLenStringVal( - @ArrayLen(0) String len0, - @ArrayLenRange(from = 0, to = 0) String rng0, - @ArrayLen({0, 1}) String nonEmpty) { - @StringVal("") String emptyLen = len0; - @StringVal("") String emptyRng = rng0; - - // :: error: (assignment.type.incompatible) - @StringVal("") String emptyError = nonEmpty; - // :: error: (assignment.type.incompatible) - @StringVal("a") String nonEmptyError = nonEmpty; - } - - void stringValLength( - @StringVal("") String empty, - @StringVal("const") String constant, - @StringVal({"s", "longconstant"}) String values, - String unknown) { - - @IntVal(0) int len0 = empty.length(); - @IntVal(5) int len5 = constant.length(); - @IntVal({1, 12}) int len1_12 = values.length(); - - // :: error: (assignment.type.incompatible) - @IntVal({1, 11}) int len1_11 = values.length(); - } - - void arrayLenLength( - @ArrayLen(0) String empty, - @ArrayLen(5) String constant, - @ArrayLen({1, 12}) String values, - String unknown) { - - @IntVal(0) int len0 = empty.length(); - @IntVal(5) int len5 = constant.length(); - @IntVal({1, 12}) int len1_12 = values.length(); - - // :: error: (assignment.type.incompatible) - @IntVal({1, 11}) int len1_11 = values.length(); - } - - void arrayLenRangeLength( - @ArrayLenRange(from = 0, to = 0) String empty, - @ArrayLenRange(from = 5, to = 5) String constant, - @ArrayLenRange(from = 1, to = 12) String values, - String unknown) { - - @IntRange(from = 0, to = 0) int len0 = empty.length(); - @IntRange(from = 5, to = 5) int len5 = constant.length(); - @IntRange(from = 1, to = 12) int len1_12 = values.length(); - - // :: error: (assignment.type.incompatible) - @IntRange(from = 1, to = 11) int len1_11 = values.length(); - } - - void minLenLength(@MinLen(5) String s) { - @IntRange(from = 5) int l = s.length(); - } - - void arrayCast(@ArrayLen(1) String array) { - @ArrayLen(1) String cast1 = (String) array; - @ArrayLen(1) String cast2 = (@ArrayLen(1) String) array; - } + void stringValArrayLen( + @StringVal("") String empty, + @StringVal("const") String constant, + @StringVal({"s", "longconstant"}) String values, + String unknown) { + + // Compatibility with ArrayLen + @ArrayLen(0) String len0 = empty; + @ArrayLen(5) String len5 = constant; + @ArrayLen({1, 12}) String len1_12 = values; + + // Compatibility with ArrayLenRange + @ArrayLenRange(from = 0, to = 0) String rng0 = empty; + @ArrayLenRange(from = 5, to = 5) String rng5 = constant; + @ArrayLenRange(from = 1, to = 12) String rng1_12 = values; + + // :: error: (assignment.type.incompatible) + @ArrayLen(4) String len4 = constant; + // :: error: (assignment.type.incompatible) + @ArrayLenRange(from = 1, to = 11) String rng1_10 = values; + } + + void stringValLubToArrayLen( + boolean flag, + @StringVal({"a", "b", "c", "d", "e"}) String ae, + @StringVal({"f", "g", "h", "i", "j", "k"}) String fk, + @StringVal({"ffff", "gggg", "hhhh", "iiii", "jjjj", "kkkkkkk"}) String fkR) { + + @ArrayLen(1) String ak = flag ? ae : fk; + @ArrayLen({1, 4, 7}) String akR = flag ? ae : fkR; + } + + void stringValLubToArrayLenRange( + boolean flag, + @StringVal({"a", "bb", "ccc", "dddd", "eeeee"}) String ae, + @StringVal({"ffffff", "ggggggg", "hhhhhhhh", "iiiiiiiii", "jjjjjjjjjj", "kkkkkkkkkkk"}) String fk) { + + @ArrayLenRange(from = 1, to = 11) String ak = flag ? ae : fk; + } + + void arrayLenStringVal( + @ArrayLen(0) String len0, + @ArrayLenRange(from = 0, to = 0) String rng0, + @ArrayLen({0, 1}) String nonEmpty) { + @StringVal("") String emptyLen = len0; + @StringVal("") String emptyRng = rng0; + + // :: error: (assignment.type.incompatible) + @StringVal("") String emptyError = nonEmpty; + // :: error: (assignment.type.incompatible) + @StringVal("a") String nonEmptyError = nonEmpty; + } + + void stringValLength( + @StringVal("") String empty, + @StringVal("const") String constant, + @StringVal({"s", "longconstant"}) String values, + String unknown) { + + @IntVal(0) int len0 = empty.length(); + @IntVal(5) int len5 = constant.length(); + @IntVal({1, 12}) int len1_12 = values.length(); + + // :: error: (assignment.type.incompatible) + @IntVal({1, 11}) int len1_11 = values.length(); + } + + void arrayLenLength( + @ArrayLen(0) String empty, + @ArrayLen(5) String constant, + @ArrayLen({1, 12}) String values, + String unknown) { + + @IntVal(0) int len0 = empty.length(); + @IntVal(5) int len5 = constant.length(); + @IntVal({1, 12}) int len1_12 = values.length(); + + // :: error: (assignment.type.incompatible) + @IntVal({1, 11}) int len1_11 = values.length(); + } + + void arrayLenRangeLength( + @ArrayLenRange(from = 0, to = 0) String empty, + @ArrayLenRange(from = 5, to = 5) String constant, + @ArrayLenRange(from = 1, to = 12) String values, + String unknown) { + + @IntRange(from = 0, to = 0) int len0 = empty.length(); + @IntRange(from = 5, to = 5) int len5 = constant.length(); + @IntRange(from = 1, to = 12) int len1_12 = values.length(); + + // :: error: (assignment.type.incompatible) + @IntRange(from = 1, to = 11) int len1_11 = values.length(); + } + + void minLenLength(@MinLen(5) String s) { + @IntRange(from = 5) int l = s.length(); + } + + void arrayCast(@ArrayLen(1) String array) { + @ArrayLen(1) String cast1 = (String) array; + @ArrayLen(1) String cast2 = (@ArrayLen(1) String) array; + } } diff --git a/framework/tests/value/StringLenConcats.java b/framework/tests/value/StringLenConcats.java index 4d7b51319e3..777a3ffde89 100644 --- a/framework/tests/value/StringLenConcats.java +++ b/framework/tests/value/StringLenConcats.java @@ -7,113 +7,112 @@ public class StringLenConcats { - void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { - @ArrayLen({7, 8, 9}) String ab = a + b; - @ArrayLen({6, 7}) String bxx = b + "xx"; - } - - void stringLenRangeConcat( - @ArrayLenRange(from = 3, to = 5) String a, - @ArrayLenRange(from = 11, to = 19) String b) { - @ArrayLenRange(from = 7, to = 24) String ab = a + b; - @ArrayLenRange(from = 6, to = 21) String bxx = b + "xx"; - } - - void stringLenLenRangeConcat( - @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { - @ArrayLenRange(from = 7, to = 105) String ab = a + b; - } - - void stringValLenConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLen(11) String d) { - - @ArrayLen({8, 12, 15, 19}) String ad = a + d; - @ArrayLen({5, 8, 12, 15}) String bd = b + d; - @ArrayLenRange(from = 4, to = 15) String cd = c + d; - } - - void stringValLenRangeConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLenRange(from = 11, to = 19) String d) { - - @ArrayLenRange(from = 8, to = 27) String ad = a + d; - @ArrayLenRange(from = 5, to = 23) String bd = b + d; - @ArrayLenRange(from = 5, to = 23) String cd = c + d; - } - - void tooManyStringValConcat( - @StringVal({"a", "b", "c", "d"}) String a, - @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { - @ArrayLen({2, 5, 8}) String aa = a + a; - @ArrayLen({3, 5, 6, 8}) String ab = a + b; - } - - void charConversions( - char c, - @IntVal({1, 100, 10000}) char d, - @ArrayLen({100, 200}) String s, - @ArrayLenRange(from = 100, to = 200) String t, - @StringVal({"a", "bb", "ccc", "dddd"}) String u) { - @ArrayLen({5, 101, 201}) String sc = s + c; - @ArrayLen({5, 101, 201}) String sd = s + d; - - @ArrayLenRange(from = 5, to = 201) String tc = t + c; - - @ArrayLen({2, 3, 4, 5}) String uc = u + c; - @ArrayLen({2, 3, 4, 5}) String ud = u + d; - } - - void intConversions( - @IntVal(123) int intConst, - @IntRange(from = -100000, to = 100) int intRange, - @IntRange(from = 100, to = 100000) int positiveRange, - int unknownInt, - @ArrayLen(10) String a, - @ArrayLenRange(from = 10, to = 20) String b, - @StringVal({"aaa", "bbbbb"}) String c) { - @ArrayLen({7, 13}) String aConst = a + intConst; - @ArrayLenRange(from = 5, to = 17) String aRange = a + intRange; - @ArrayLen({7, 8, 9, 10, 13, 14, 15, 16}) String aPositive = a + positiveRange; - @ArrayLenRange(from = 5, to = 21) String aUnknown = a + unknownInt; - - @ArrayLenRange(from = 5, to = 23) String bConst = b + intConst; - @ArrayLenRange(from = 5, to = 27) String bRange = b + intRange; - @ArrayLenRange(from = 7, to = 26) String bPositive = b + positiveRange; - @ArrayLenRange(from = 5, to = 31) String bUnknown = b + unknownInt; - - @StringVal({"aaa123", "bbbbb123", "null123"}) String cConst = c + intConst; - @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; - @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; - } - - void longConversions( - @IntVal(1000000000000l) long longConst, - @IntRange(from = 10, to = 1000000000000l) long longRange, - long unknownLong, - @ArrayLen(10) String a) { - - @ArrayLen({17, 23}) String aConst = a + longConst; - @ArrayLenRange(from = 6, to = 23) String aRange = a + longRange; - @ArrayLenRange(from = 5, to = 30) String aUnknown = a + unknownLong; - } - - void byteConversions( - @IntVal(100) byte byteConst, - @IntRange(from = 2, to = 10) byte byteRange, - byte unknownByte, - @ArrayLen(10) String a) { - - @ArrayLen({7, 13}) String aConst = a + byteConst; - @ArrayLenRange(from = 5, to = 12) String aRange = a + byteRange; - @ArrayLenRange(from = 5, to = 14) String aUnknown = a + unknownByte; - } - - void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { - @MinLen(8) String st = s + t; - } + void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { + @ArrayLen({7, 8, 9}) String ab = a + b; + @ArrayLen({6, 7}) String bxx = b + "xx"; + } + + void stringLenRangeConcat( + @ArrayLenRange(from = 3, to = 5) String a, @ArrayLenRange(from = 11, to = 19) String b) { + @ArrayLenRange(from = 7, to = 24) String ab = a + b; + @ArrayLenRange(from = 6, to = 21) String bxx = b + "xx"; + } + + void stringLenLenRangeConcat( + @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { + @ArrayLenRange(from = 7, to = 105) String ab = a + b; + } + + void stringValLenConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLen(11) String d) { + + @ArrayLen({8, 12, 15, 19}) String ad = a + d; + @ArrayLen({5, 8, 12, 15}) String bd = b + d; + @ArrayLenRange(from = 4, to = 15) String cd = c + d; + } + + void stringValLenRangeConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLenRange(from = 11, to = 19) String d) { + + @ArrayLenRange(from = 8, to = 27) String ad = a + d; + @ArrayLenRange(from = 5, to = 23) String bd = b + d; + @ArrayLenRange(from = 5, to = 23) String cd = c + d; + } + + void tooManyStringValConcat( + @StringVal({"a", "b", "c", "d"}) String a, + @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { + @ArrayLen({2, 5, 8}) String aa = a + a; + @ArrayLen({3, 5, 6, 8}) String ab = a + b; + } + + void charConversions( + char c, + @IntVal({1, 100, 10000}) char d, + @ArrayLen({100, 200}) String s, + @ArrayLenRange(from = 100, to = 200) String t, + @StringVal({"a", "bb", "ccc", "dddd"}) String u) { + @ArrayLen({5, 101, 201}) String sc = s + c; + @ArrayLen({5, 101, 201}) String sd = s + d; + + @ArrayLenRange(from = 5, to = 201) String tc = t + c; + + @ArrayLen({2, 3, 4, 5}) String uc = u + c; + @ArrayLen({2, 3, 4, 5}) String ud = u + d; + } + + void intConversions( + @IntVal(123) int intConst, + @IntRange(from = -100000, to = 100) int intRange, + @IntRange(from = 100, to = 100000) int positiveRange, + int unknownInt, + @ArrayLen(10) String a, + @ArrayLenRange(from = 10, to = 20) String b, + @StringVal({"aaa", "bbbbb"}) String c) { + @ArrayLen({7, 13}) String aConst = a + intConst; + @ArrayLenRange(from = 5, to = 17) String aRange = a + intRange; + @ArrayLen({7, 8, 9, 10, 13, 14, 15, 16}) String aPositive = a + positiveRange; + @ArrayLenRange(from = 5, to = 21) String aUnknown = a + unknownInt; + + @ArrayLenRange(from = 5, to = 23) String bConst = b + intConst; + @ArrayLenRange(from = 5, to = 27) String bRange = b + intRange; + @ArrayLenRange(from = 7, to = 26) String bPositive = b + positiveRange; + @ArrayLenRange(from = 5, to = 31) String bUnknown = b + unknownInt; + + @StringVal({"aaa123", "bbbbb123", "null123"}) String cConst = c + intConst; + @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; + @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; + } + + void longConversions( + @IntVal(1000000000000l) long longConst, + @IntRange(from = 10, to = 1000000000000l) long longRange, + long unknownLong, + @ArrayLen(10) String a) { + + @ArrayLen({17, 23}) String aConst = a + longConst; + @ArrayLenRange(from = 6, to = 23) String aRange = a + longRange; + @ArrayLenRange(from = 5, to = 30) String aUnknown = a + unknownLong; + } + + void byteConversions( + @IntVal(100) byte byteConst, + @IntRange(from = 2, to = 10) byte byteRange, + byte unknownByte, + @ArrayLen(10) String a) { + + @ArrayLen({7, 13}) String aConst = a + byteConst; + @ArrayLenRange(from = 5, to = 12) String aRange = a + byteRange; + @ArrayLenRange(from = 5, to = 14) String aUnknown = a + unknownByte; + } + + void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { + @MinLen(8) String st = s + t; + } } diff --git a/framework/tests/value/StringLenMethods.java b/framework/tests/value/StringLenMethods.java index 329c7d03cef..c7dd22c1783 100644 --- a/framework/tests/value/StringLenMethods.java +++ b/framework/tests/value/StringLenMethods.java @@ -4,47 +4,47 @@ import org.checkerframework.common.value.qual.StringVal; public class StringLenMethods { - void toString(boolean b, char c, byte y, short s, int i, long l) { - - @StringVal({"true", "false"}) String bs = Boolean.toString(b); - @ArrayLen(1) String cs = Character.toString(c); - @ArrayLen({1, 2, 3, 4}) String ys = Byte.toString(y); - @ArrayLen({1, 2, 3, 4, 5, 6}) String ss = Short.toString(s); - @ArrayLenRange(from = 1, to = 11) String is = Integer.toString(i); - @ArrayLenRange(from = 1, to = 20) String ls = Long.toString(l); - - @StringVal({"true", "false"}) String bbs = Boolean.valueOf(b).toString(); - @ArrayLen(1) String bcs = Character.valueOf(c).toString(); - @ArrayLen({1, 2, 3, 4}) String bys = Byte.valueOf(y).toString(); - @ArrayLen({1, 2, 3, 4, 5, 6}) String bss = Short.valueOf(s).toString(); - @ArrayLenRange(from = 1, to = 11) String bis = Integer.valueOf(i).toString(); - @ArrayLenRange(from = 1, to = 20) String bls = Long.valueOf(l).toString(); - - // Added in 1.8 - // @ArrayLenRange(from = 1, to = 10) String iu = Integer.toUnsignedString(i); - // @ArrayLenRange(from = 1, to = 19) String lu = Long.toUnsignedString(l); - - @StringVal({"true", "false"}) String sbs = String.valueOf(b); - @ArrayLen(1) String scs = String.valueOf(c); - @ArrayLenRange(from = 1, to = 11) String sis = String.valueOf(i); - @ArrayLenRange(from = 1, to = 20) String sls = String.valueOf(l); - } - - void toStringRadix(int i, long l, @IntRange(from = 2, to = 36) int radix) { - @ArrayLenRange(from = 1) String is = Integer.toString(i, radix); - @ArrayLenRange(from = 1) String ls = Long.toString(l, radix); - - // Added in 1.8 - // @ArrayLenRange(from = 1) String iu = Integer.toUnsignedString(i, radix); - // @ArrayLenRange(from = 1) String lu = Long.toUnsignedString(l, radix); - - @ArrayLenRange(from = 1, to = 32) String ib = Integer.toBinaryString(i); - @ArrayLenRange(from = 1, to = 64) String lb = Long.toBinaryString(l); - - @ArrayLenRange(from = 1, to = 8) String ix = Integer.toHexString(i); - @ArrayLenRange(from = 1, to = 16) String lx = Long.toHexString(l); - - @ArrayLenRange(from = 1, to = 11) String io = Integer.toOctalString(i); - @ArrayLenRange(from = 1, to = 22) String lo = Long.toOctalString(l); - } + void toString(boolean b, char c, byte y, short s, int i, long l) { + + @StringVal({"true", "false"}) String bs = Boolean.toString(b); + @ArrayLen(1) String cs = Character.toString(c); + @ArrayLen({1, 2, 3, 4}) String ys = Byte.toString(y); + @ArrayLen({1, 2, 3, 4, 5, 6}) String ss = Short.toString(s); + @ArrayLenRange(from = 1, to = 11) String is = Integer.toString(i); + @ArrayLenRange(from = 1, to = 20) String ls = Long.toString(l); + + @StringVal({"true", "false"}) String bbs = Boolean.valueOf(b).toString(); + @ArrayLen(1) String bcs = Character.valueOf(c).toString(); + @ArrayLen({1, 2, 3, 4}) String bys = Byte.valueOf(y).toString(); + @ArrayLen({1, 2, 3, 4, 5, 6}) String bss = Short.valueOf(s).toString(); + @ArrayLenRange(from = 1, to = 11) String bis = Integer.valueOf(i).toString(); + @ArrayLenRange(from = 1, to = 20) String bls = Long.valueOf(l).toString(); + + // Added in 1.8 + // @ArrayLenRange(from = 1, to = 10) String iu = Integer.toUnsignedString(i); + // @ArrayLenRange(from = 1, to = 19) String lu = Long.toUnsignedString(l); + + @StringVal({"true", "false"}) String sbs = String.valueOf(b); + @ArrayLen(1) String scs = String.valueOf(c); + @ArrayLenRange(from = 1, to = 11) String sis = String.valueOf(i); + @ArrayLenRange(from = 1, to = 20) String sls = String.valueOf(l); + } + + void toStringRadix(int i, long l, @IntRange(from = 2, to = 36) int radix) { + @ArrayLenRange(from = 1) String is = Integer.toString(i, radix); + @ArrayLenRange(from = 1) String ls = Long.toString(l, radix); + + // Added in 1.8 + // @ArrayLenRange(from = 1) String iu = Integer.toUnsignedString(i, radix); + // @ArrayLenRange(from = 1) String lu = Long.toUnsignedString(l, radix); + + @ArrayLenRange(from = 1, to = 32) String ib = Integer.toBinaryString(i); + @ArrayLenRange(from = 1, to = 64) String lb = Long.toBinaryString(l); + + @ArrayLenRange(from = 1, to = 8) String ix = Integer.toHexString(i); + @ArrayLenRange(from = 1, to = 16) String lx = Long.toHexString(l); + + @ArrayLenRange(from = 1, to = 11) String io = Integer.toOctalString(i); + @ArrayLenRange(from = 1, to = 22) String lo = Long.toOctalString(l); + } } diff --git a/framework/tests/value/StringLenWidening.java b/framework/tests/value/StringLenWidening.java index fde94023901..e3cfafb6b5e 100644 --- a/framework/tests/value/StringLenWidening.java +++ b/framework/tests/value/StringLenWidening.java @@ -2,18 +2,18 @@ public class StringLenWidening { - // Minimized example from java.util.logging.Logger.entering - public void entering(Object params[]) { - String msg = "ENTRY"; - for (int i = 0; i < params.length; i++) { - msg = msg + i; - } + // Minimized example from java.util.logging.Logger.entering + public void entering(Object params[]) { + String msg = "ENTRY"; + for (int i = 0; i < params.length; i++) { + msg = msg + i; } + } - public void repeat(int a) { - String str = ""; - for (int i = 0; i < a; i++) { - str += "a"; - } + public void repeat(int a) { + String str = ""; + for (int i = 0; i < a; i++) { + str += "a"; } + } } diff --git a/framework/tests/value/StringPolyValue.java b/framework/tests/value/StringPolyValue.java index e4771c6dc7c..a67e9eaefa3 100644 --- a/framework/tests/value/StringPolyValue.java +++ b/framework/tests/value/StringPolyValue.java @@ -1,13 +1,13 @@ import org.checkerframework.common.value.qual.StringVal; public class StringPolyValue { - void stringValArrayLen(@StringVal({"a", "b", "c"}) String abc) { + void stringValArrayLen(@StringVal({"a", "b", "c"}) String abc) { - @StringVal({"a", "b", "c"}) String ns = new String(abc); - @StringVal({"a", "b", "c"}) String ts = abc.toString(); - @StringVal({"a", "b", "c"}) String i = abc.intern(); - @StringVal({"a", "b", "c"}) String nstca = new String(abc.toCharArray()); - @StringVal({"a", "b", "c"}) String votca = String.valueOf(abc.toCharArray()); - @StringVal({"a", "b", "c"}) String cvotca = String.copyValueOf(abc.toCharArray()); - } + @StringVal({"a", "b", "c"}) String ns = new String(abc); + @StringVal({"a", "b", "c"}) String ts = abc.toString(); + @StringVal({"a", "b", "c"}) String i = abc.intern(); + @StringVal({"a", "b", "c"}) String nstca = new String(abc.toCharArray()); + @StringVal({"a", "b", "c"}) String votca = String.valueOf(abc.toCharArray()); + @StringVal({"a", "b", "c"}) String cvotca = String.copyValueOf(abc.toCharArray()); + } } diff --git a/framework/tests/value/StringSplit.java b/framework/tests/value/StringSplit.java index aba7942c278..05706ac3c30 100644 --- a/framework/tests/value/StringSplit.java +++ b/framework/tests/value/StringSplit.java @@ -3,17 +3,17 @@ public class StringSplit { - void needsALR1(String @ArrayLenRange(from = 1) [] arg) {} + void needsALR1(String @ArrayLenRange(from = 1) [] arg) {} - void g(String compiler) { - needsALR1(compiler.trim().split(" +")); - } + void g(String compiler) { + needsALR1(compiler.trim().split(" +")); + } - void g2(String compiler) { - needsALR1(mySplit(compiler.trim(), " +")); - } + void g2(String compiler) { + needsALR1(mySplit(compiler.trim(), " +")); + } - String @MinLen(1) [] mySplit(String receiver, String regex) { - return null; - } + String @MinLen(1) [] mySplit(String receiver, String regex) { + return null; + } } diff --git a/framework/tests/value/StringValCrash.java b/framework/tests/value/StringValCrash.java index 65ba0b4b90b..f96b91c831c 100644 --- a/framework/tests/value/StringValCrash.java +++ b/framework/tests/value/StringValCrash.java @@ -2,8 +2,8 @@ public class StringValCrash { - void foo() { - List path = null; - System.out.print(path.size() + "..."); - } + void foo() { + List path = null; + System.out.print(path.size() + "..."); + } } diff --git a/framework/tests/value/StringValNull.java b/framework/tests/value/StringValNull.java index a0b8daa0bd0..2adf1eae3f2 100644 --- a/framework/tests/value/StringValNull.java +++ b/framework/tests/value/StringValNull.java @@ -4,64 +4,63 @@ public class StringValNull { - public static void main(String[] args) { - @StringVal("itsValue") String nbleString = null; - @StringVal("itsValue") String nnString = "itsValue"; - - System.out.println(toString1(nbleString)); - System.out.println(toString2(nbleString)); - - System.out.println(toString1(nnString)); - System.out.println(toString2(nnString)); - // System.out.println(toString3(nnString)); - - @IntVal(22) Integer nbleInteger = null; - @IntVal(22) Integer nnInteger = 22; - - System.out.println(toString4(nbleInteger)); - System.out.println(toString5(nbleInteger)); - - System.out.println(toString4(nnInteger)); - System.out.println(toString5(nnInteger)); - System.out.println(toString6(nnInteger)); - } - - static @StringVal("arg=itsValue") String toString1( - @Nullable @StringVal("itsValue") String arg) { - // :: error: (return.type.incompatible) - return "arg=" + arg; - } - - static @StringVal({"arg=itsValue", "arg=null"}) String toString2( - @Nullable @StringVal("itsValue") String arg) { - return "arg=" + arg; - } - - /* static @StringVal("arg=itsValue") String toString3(@StringVal("itsValue") String arg) { - return "arg=" + arg; - } */ - - static @StringVal("arg=22") String toString4(@Nullable @IntVal(22) Integer arg) { - // :: error: (return.type.incompatible) - return "arg=" + arg; - } - - static @StringVal({"arg=22", "arg=null"}) String toString5(@Nullable @IntVal(22) Integer arg) { - return "arg=" + arg; - } - - static @StringVal("arg=22") String toString6(@IntVal(22) int arg) { - return "arg=" + arg; - } - - final @StringVal("hello") String s = null; - - @StringVal("hello") String s2 = null; - - void method2(StringValNull obj) { - // :: error: (assignment.type.incompatible) - @StringVal("hello") String l1 = "" + obj.s; - // :: error: (assignment.type.incompatible) - @StringVal("hello") String l2 = "" + obj.s2; - } + public static void main(String[] args) { + @StringVal("itsValue") String nbleString = null; + @StringVal("itsValue") String nnString = "itsValue"; + + System.out.println(toString1(nbleString)); + System.out.println(toString2(nbleString)); + + System.out.println(toString1(nnString)); + System.out.println(toString2(nnString)); + // System.out.println(toString3(nnString)); + + @IntVal(22) Integer nbleInteger = null; + @IntVal(22) Integer nnInteger = 22; + + System.out.println(toString4(nbleInteger)); + System.out.println(toString5(nbleInteger)); + + System.out.println(toString4(nnInteger)); + System.out.println(toString5(nnInteger)); + System.out.println(toString6(nnInteger)); + } + + static @StringVal("arg=itsValue") String toString1(@Nullable @StringVal("itsValue") String arg) { + // :: error: (return.type.incompatible) + return "arg=" + arg; + } + + static @StringVal({"arg=itsValue", "arg=null"}) String toString2( + @Nullable @StringVal("itsValue") String arg) { + return "arg=" + arg; + } + + /* static @StringVal("arg=itsValue") String toString3(@StringVal("itsValue") String arg) { + return "arg=" + arg; + } */ + + static @StringVal("arg=22") String toString4(@Nullable @IntVal(22) Integer arg) { + // :: error: (return.type.incompatible) + return "arg=" + arg; + } + + static @StringVal({"arg=22", "arg=null"}) String toString5(@Nullable @IntVal(22) Integer arg) { + return "arg=" + arg; + } + + static @StringVal("arg=22") String toString6(@IntVal(22) int arg) { + return "arg=" + arg; + } + + final @StringVal("hello") String s = null; + + @StringVal("hello") String s2 = null; + + void method2(StringValNull obj) { + // :: error: (assignment.type.incompatible) + @StringVal("hello") String l1 = "" + obj.s; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String l2 = "" + obj.s2; + } } diff --git a/framework/tests/value/StringValNullConcatLength.java b/framework/tests/value/StringValNullConcatLength.java index e6f0443eff6..0ef5cc8f0c7 100644 --- a/framework/tests/value/StringValNullConcatLength.java +++ b/framework/tests/value/StringValNullConcatLength.java @@ -3,25 +3,25 @@ import org.checkerframework.common.value.qual.StringVal; public class StringValNullConcatLength { - @StringVal("a") String string1; + @StringVal("a") String string1; - @StringVal("b") String string2; + @StringVal("b") String string2; - @ArrayLen({1, 2}) String string3; + @ArrayLen({1, 2}) String string3; - @ArrayLen({2, 3}) String string4; + @ArrayLen({2, 3}) String string4; - @ArrayLenRange(from = 1, to = 3) String string5; + @ArrayLenRange(from = 1, to = 3) String string5; - @StringVal({"anull", "ab", "nullb", "nullnull"}) String string6 = string1 + string2; + @StringVal({"anull", "ab", "nullb", "nullnull"}) String string6 = string1 + string2; - @ArrayLen({2, 3, 5, 6, 8}) String string7 = string1 + string3; + @ArrayLen({2, 3, 5, 6, 8}) String string7 = string1 + string3; - @ArrayLen({3, 4, 5, 6, 7, 8}) String string8 = string3 + string4; + @ArrayLen({3, 4, 5, 6, 7, 8}) String string8 = string3 + string4; - @ArrayLenRange(from = 2, to = 8) String string10 = string1 + string5; + @ArrayLenRange(from = 2, to = 8) String string10 = string1 + string5; - // Omitting that string2 can be null - // :: error: (assignment.type.incompatible) - @ArrayLen({3, 4}) String string9 = string2 + string4; + // Omitting that string2 can be null + // :: error: (assignment.type.incompatible) + @ArrayLen({3, 4}) String string9 = string2 + string4; } diff --git a/framework/tests/value/StringValOfArrays.java b/framework/tests/value/StringValOfArrays.java index 6076a7a9510..564e1895db7 100644 --- a/framework/tests/value/StringValOfArrays.java +++ b/framework/tests/value/StringValOfArrays.java @@ -1,9 +1,9 @@ import org.checkerframework.common.value.qual.StringVal; public class StringValOfArrays { - void chars() { - String s = "$-hello@"; - char @StringVal("$-hello@") [] chars = s.toCharArray(); - @StringVal("$-hello@") String s2 = new String(chars); - } + void chars() { + String s = "$-hello@"; + char @StringVal("$-hello@") [] chars = s.toCharArray(); + @StringVal("$-hello@") String s2 = new String(chars); + } } diff --git a/framework/tests/value/Switch.java b/framework/tests/value/Switch.java index 408a0f8e3cc..b93de0e2a45 100644 --- a/framework/tests/value/Switch.java +++ b/framework/tests/value/Switch.java @@ -4,190 +4,190 @@ // whether the semantics of switch are correct in general), but I needed some // checker to try it out on. public class Switch { - void test1(@IntVal({1, 2, 3, 4, 5}) int x) { - - // easy version, no fall through - switch (x) { - case 1: - @IntVal({1}) int y = x; - break; - case 2: - @IntVal({2}) int w = x; - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - break; - default: - @IntVal({3, 4, 5}) int q = x; - break; - } + void test1(@IntVal({1, 2, 3, 4, 5}) int x) { + + // easy version, no fall through + switch (x) { + case 1: + @IntVal({1}) int y = x; + break; + case 2: + @IntVal({2}) int w = x; + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + break; + default: + @IntVal({3, 4, 5}) int q = x; + break; } + } - void test2(@IntVal({1, 2, 3, 4, 5}) int x) { - - // harder version, fall through - switch (x) { - case 1: - @IntVal({1}) int y = x; - case 2: - case 3: - @IntVal({1, 2, 3}) int w = x; - // :: error: (assignment.type.incompatible) - @IntVal({2, 3}) int z = x; - // :: error: (assignment.type.incompatible) - @IntVal({3}) int z1 = x; - break; - default: - @IntVal({4, 5}) int q = x; - - // :: error: (assignment.type.incompatible) - @IntVal(5) int q2 = x; - break; - } - } + void test2(@IntVal({1, 2, 3, 4, 5}) int x) { - void test3(@IntVal({1, 2, 3, 4, 5}) int x) { - - // harder version, fall through - switch (x) { - case 1: - @IntVal({1}) int y = x; - case 2: - case 3: - @IntVal({1, 2, 3}) int w = x; - // :: error: (assignment.type.incompatible) - @IntVal({2, 3}) int z = x; - // :: error: (assignment.type.incompatible) - @IntVal({3}) int z1 = x; - break; - case 4: - default: - @IntVal({4, 5}) int q = x; - - // :: error: (assignment.type.incompatible) - @IntVal(5) int q2 = x; - break; - } - } + // harder version, fall through + switch (x) { + case 1: + @IntVal({1}) int y = x; + case 2: + case 3: + @IntVal({1, 2, 3}) int w = x; + // :: error: (assignment.type.incompatible) + @IntVal({2, 3}) int z = x; + // :: error: (assignment.type.incompatible) + @IntVal({3}) int z1 = x; + break; + default: + @IntVal({4, 5}) int q = x; - void test4(int x) { - switch (x) { - case 1: - @IntVal({1}) int y = x; - break; - case 2: - case 3: - @IntVal({2, 3}) int z = x; - break; - case 4: - default: - return; - } - @IntVal({1, 2, 3}) int y = x; // :: error: (assignment.type.incompatible) - @IntVal(4) int y2 = x; + @IntVal(5) int q2 = x; + break; } + } - void test5(@IntVal({0, 1, 2, 3, 4}) int x) { - @IntVal({0, 1, 2, 3, 4, 5}) int y = x; - switch (y = y + 1) { - case 1: - @IntVal({1}) int a = y; - // :: error: (assignment.type.incompatible) - @IntVal({2}) int b = y; - case 2: - case 3: - @IntVal({1, 2, 3}) int c = y; - break; - default: - // :: error: (assignment.type.incompatible) - @IntVal({4}) int d = y; - // :: error: (assignment.type.incompatible) - @IntVal({5}) int e = y; - @IntVal({4, 5}) int f = y; - break; - } - } + void test3(@IntVal({1, 2, 3, 4, 5}) int x) { - void testInts1(@IntRange(from = 0, to = 100) int x) { - switch (x) { - case 0: - case 1: - case 2: - @IntVal({0, 1, 2}) int z = x; - return; - default: - } - - @IntRange(from = 3, to = 100) int z = x; - } + // harder version, fall through + switch (x) { + case 1: + @IntVal({1}) int y = x; + case 2: + case 3: + @IntVal({1, 2, 3}) int w = x; + // :: error: (assignment.type.incompatible) + @IntVal({2, 3}) int z = x; + // :: error: (assignment.type.incompatible) + @IntVal({3}) int z1 = x; + break; + case 4: + default: + @IntVal({4, 5}) int q = x; - void testInts2(@IntRange(from = 0, to = 100) int x) { - - // harder version, fall through - switch (x) { - case 0: - @IntVal(0) int a = x; - break; - case 1: - @IntVal(1) int b = x; - break; - case 2: - @IntVal(2) int c = x; - default: - @IntRange(from = 2, to = 100) int d = x; - break; - } + // :: error: (assignment.type.incompatible) + @IntVal(5) int q2 = x; + break; } - - void testChars(char x) { - switch (x) { - case 'a': - case 2: - @IntVal({'a', 2}) int z = x; - break; - case 'b': - @IntVal('b') int v = x; - break; - default: - return; - } - @IntVal({'a', 2, 'b'}) int y = x; + } + + void test4(int x) { + switch (x) { + case 1: + @IntVal({1}) int y = x; + break; + case 2: + case 3: + @IntVal({2, 3}) int z = x; + break; + case 4: + default: + return; } - - void testStrings1(String s) { - switch (s) { - case "Good": - @StringVal("Good") String x = s; - case "Bye": - @StringVal({"Good", "Bye"}) String y = s; - break; - case "Hello": - @StringVal("Hello") String z = s; - break; - default: - return; - } - @StringVal({"Good", "Bye", "Hello"}) String q = s; + @IntVal({1, 2, 3}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal(4) int y2 = x; + } + + void test5(@IntVal({0, 1, 2, 3, 4}) int x) { + @IntVal({0, 1, 2, 3, 4, 5}) int y = x; + switch (y = y + 1) { + case 1: + @IntVal({1}) int a = y; + // :: error: (assignment.type.incompatible) + @IntVal({2}) int b = y; + case 2: + case 3: + @IntVal({1, 2, 3}) int c = y; + break; + default: + // :: error: (assignment.type.incompatible) + @IntVal({4}) int d = y; + // :: error: (assignment.type.incompatible) + @IntVal({5}) int e = y; + @IntVal({4, 5}) int f = y; + break; + } + } + + void testInts1(@IntRange(from = 0, to = 100) int x) { + switch (x) { + case 0: + case 1: + case 2: + @IntVal({0, 1, 2}) int z = x; + return; + default: } - void testStrings2(String s) { - String a; - switch (a = s) { - case "Good": - @StringVal("Good") String x1 = a; - @StringVal("Good") String x2 = s; - case "Bye": - @StringVal({"Good", "Bye"}) String y1 = a; - @StringVal({"Good", "Bye"}) String y2 = s; - break; - case "Hello": - @StringVal("Hello") String z1 = a; - @StringVal("Hello") String z2 = s; - break; - default: - return; - } - @StringVal({"Good", "Bye", "Hello"}) String q1 = a; - @StringVal({"Good", "Bye", "Hello"}) String q2 = s; + @IntRange(from = 3, to = 100) int z = x; + } + + void testInts2(@IntRange(from = 0, to = 100) int x) { + + // harder version, fall through + switch (x) { + case 0: + @IntVal(0) int a = x; + break; + case 1: + @IntVal(1) int b = x; + break; + case 2: + @IntVal(2) int c = x; + default: + @IntRange(from = 2, to = 100) int d = x; + break; + } + } + + void testChars(char x) { + switch (x) { + case 'a': + case 2: + @IntVal({'a', 2}) int z = x; + break; + case 'b': + @IntVal('b') int v = x; + break; + default: + return; + } + @IntVal({'a', 2, 'b'}) int y = x; + } + + void testStrings1(String s) { + switch (s) { + case "Good": + @StringVal("Good") String x = s; + case "Bye": + @StringVal({"Good", "Bye"}) String y = s; + break; + case "Hello": + @StringVal("Hello") String z = s; + break; + default: + return; + } + @StringVal({"Good", "Bye", "Hello"}) String q = s; + } + + void testStrings2(String s) { + String a; + switch (a = s) { + case "Good": + @StringVal("Good") String x1 = a; + @StringVal("Good") String x2 = s; + case "Bye": + @StringVal({"Good", "Bye"}) String y1 = a; + @StringVal({"Good", "Bye"}) String y2 = s; + break; + case "Hello": + @StringVal("Hello") String z1 = a; + @StringVal("Hello") String z2 = s; + break; + default: + return; } + @StringVal({"Good", "Bye", "Hello"}) String q1 = a; + @StringVal({"Good", "Bye", "Hello"}) String q2 = s; + } } diff --git a/framework/tests/value/TooWideRange.java b/framework/tests/value/TooWideRange.java index e6fb2d0b2e5..16766853c07 100644 --- a/framework/tests/value/TooWideRange.java +++ b/framework/tests/value/TooWideRange.java @@ -1,30 +1,30 @@ // test case for https://github.com/typetools/checker-framework/issues/5486 public class TooWideRange { - // From StringsPlume - @org.checkerframework.dataflow.qual.Pure - public static @org.checkerframework.common.value.qual.IntRange( - from = -2147483648, - to = 2147483647) int count(String s, int ch) { - int result = 0; - int pos = s.indexOf(ch); - while (pos > -1) { - result++; - pos = s.indexOf(ch, pos + 1); - } - return result; + // From StringsPlume + @org.checkerframework.dataflow.qual.Pure + public static @org.checkerframework.common.value.qual.IntRange( + from = -2147483648, + to = 2147483647) int count(String s, int ch) { + int result = 0; + int pos = s.indexOf(ch); + while (pos > -1) { + result++; + pos = s.indexOf(ch, pos + 1); } + return result; + } - // From ArraysPlume - @org.checkerframework.dataflow.qual.Pure - public static @org.checkerframework.common.value.qual.IntRange( - from = -2147483648, - to = 2147483647) int indexOfEq(Object[] a, Object elt) { - for (int i = 0; i < a.length; i++) { - if (elt == a[i]) { - return i; - } - } - return -1; + // From ArraysPlume + @org.checkerframework.dataflow.qual.Pure + public static @org.checkerframework.common.value.qual.IntRange( + from = -2147483648, + to = 2147483647) int indexOfEq(Object[] a, Object elt) { + for (int i = 0; i < a.length; i++) { + if (elt == a[i]) { + return i; + } } + return -1; + } } diff --git a/framework/tests/value/TypeCast.java b/framework/tests/value/TypeCast.java index 959bb4d7c78..268df8f3ac9 100644 --- a/framework/tests/value/TypeCast.java +++ b/framework/tests/value/TypeCast.java @@ -2,55 +2,55 @@ public class TypeCast { - public void charIntDoubleTest() { - int a = 98; - long b = 98; - double c = 98.0; - float d = 98.0f; - char e = 'b'; - short f = 98; - byte g = 98; - - @IntVal({'b'}) char h = (char) a; - h = (char) b; - // :: warning: (cast.unsafe) - h = (char) c; - // :: warning: (cast.unsafe) - h = (char) d; - h = (char) f; - h = (char) g; - - @IntVal({98}) int i = (int) b; - // :: warning: (cast.unsafe) - i = (int) c; - // :: warning: (cast.unsafe) - i = (int) d; - i = (int) e; - i = (int) f; - i = (int) g; - - @DoubleVal({98.0}) double j = (double) a; - j = (double) b; - j = (double) d; - j = (double) e; - j = (double) f; - j = (double) g; - } - - void otherCast() { - - byte[] b = (byte[]) null; - @BoolVal(true) boolean bool = (boolean) true; - } - - void rangeCast(@IntRange(from = 127, to = 128) int a, @IntRange(from = 128, to = 129) int b) { - @IntRange(from = 0, to = 128) - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe) - byte c = (byte) a; - // (byte) a is @IntRange(from = -128, to = 127) because of casting - - @IntRange(from = -128, to = -127) - // :: warning: (cast.unsafe) - byte d = (byte) b; - } + public void charIntDoubleTest() { + int a = 98; + long b = 98; + double c = 98.0; + float d = 98.0f; + char e = 'b'; + short f = 98; + byte g = 98; + + @IntVal({'b'}) char h = (char) a; + h = (char) b; + // :: warning: (cast.unsafe) + h = (char) c; + // :: warning: (cast.unsafe) + h = (char) d; + h = (char) f; + h = (char) g; + + @IntVal({98}) int i = (int) b; + // :: warning: (cast.unsafe) + i = (int) c; + // :: warning: (cast.unsafe) + i = (int) d; + i = (int) e; + i = (int) f; + i = (int) g; + + @DoubleVal({98.0}) double j = (double) a; + j = (double) b; + j = (double) d; + j = (double) e; + j = (double) f; + j = (double) g; + } + + void otherCast() { + + byte[] b = (byte[]) null; + @BoolVal(true) boolean bool = (boolean) true; + } + + void rangeCast(@IntRange(from = 127, to = 128) int a, @IntRange(from = 128, to = 129) int b) { + @IntRange(from = 0, to = 128) + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe) + byte c = (byte) a; + // (byte) a is @IntRange(from = -128, to = 127) because of casting + + @IntRange(from = -128, to = -127) + // :: warning: (cast.unsafe) + byte d = (byte) b; + } } diff --git a/framework/tests/value/TypeVars.java b/framework/tests/value/TypeVars.java index b0b97386637..f9ce7923b4b 100644 --- a/framework/tests/value/TypeVars.java +++ b/framework/tests/value/TypeVars.java @@ -1,49 +1,49 @@ public class TypeVars { - private void test(K key, V value) { - String s = "Negative size: " + key + "=" + value; - } + private void test(K key, V value) { + String s = "Negative size: " + key + "=" + value; + } - class MyClass { - public T myMethod() { - return null; - } + class MyClass { + public T myMethod() { + return null; } + } - public class TypeVarDefaults { - class ImplicitUpperBound {} + public class TypeVarDefaults { + class ImplicitUpperBound {} - class ExplicitUpperBound {} + class ExplicitUpperBound {} - void useImplicit() { - ImplicitUpperBound bottom; - } + void useImplicit() { + ImplicitUpperBound bottom; + } - void useExplicit() { - ExplicitUpperBound bottom; - } + void useExplicit() { + ExplicitUpperBound bottom; + } - void wildCardImplicit() { - ImplicitUpperBound bottom; - } + void wildCardImplicit() { + ImplicitUpperBound bottom; + } - void wildCardExplicit() { - ExplicitUpperBound bottom; - } + void wildCardExplicit() { + ExplicitUpperBound bottom; + } - void wildCardUpperBoundImplicit() { - ImplicitUpperBound bottom; - } + void wildCardUpperBoundImplicit() { + ImplicitUpperBound bottom; + } - void wildCardUpperBoundExplicit() { - ExplicitUpperBound bottom; - } + void wildCardUpperBoundExplicit() { + ExplicitUpperBound bottom; + } - void wildCardLowerImplicit() { - ImplicitUpperBound bottom; - } + void wildCardLowerImplicit() { + ImplicitUpperBound bottom; + } - void wildCardLowerBoundExplicit() { - ExplicitUpperBound bottom; - } + void wildCardLowerBoundExplicit() { + ExplicitUpperBound bottom; } + } } diff --git a/framework/tests/value/Unaries.java b/framework/tests/value/Unaries.java index 74404e776c7..032c8cf1ad5 100644 --- a/framework/tests/value/Unaries.java +++ b/framework/tests/value/Unaries.java @@ -2,59 +2,59 @@ public class Unaries { - public void complement() { - boolean a = false; - @BoolVal({true}) boolean b = !a; - - @IntVal({-5}) int c = ~4; - - @IntVal({-123456789}) long d = ~123456788; - } - - public void prefix() { - byte a = 1; - @IntVal({2}) byte b = ++a; - - @IntVal({3}) short c = ++a; - - @IntVal({4}) int d = ++a; - - @IntVal({5}) long e = ++a; - ++a; - e = --a; - d = --a; - c = --a; - b = --a; - } - - public void postfix() { - int a = 0; - @IntVal({0}) int b = a++; - @IntVal({1}) int c = a--; - b = a++; - - @IntVal({1}) long d = a--; - - double e = 0.25; - @DoubleVal({0.25}) double f = e++; - @DoubleVal({1.25}) double g = e--; - f = e; - } - - public void plusminus() { - @IntVal({48}) int a = +48; - @IntVal({-49}) int b = -49; - - @IntVal({34}) long c = +34; - @IntVal({-34}) long d = -34; - } - - public void intRange(@IntRange(from = 0, to = 2) int val) { - int a = val; - @IntRange(from = -2, to = 0) int b = -a; - @IntRange(from = 0, to = 2) int c = +a; - @IntRange(from = -3, to = -1) int d = ~a; - @IntRange(from = 1, to = 3) int e = ++a; - @IntRange(from = 1, to = 3) int f = a++; - } + public void complement() { + boolean a = false; + @BoolVal({true}) boolean b = !a; + + @IntVal({-5}) int c = ~4; + + @IntVal({-123456789}) long d = ~123456788; + } + + public void prefix() { + byte a = 1; + @IntVal({2}) byte b = ++a; + + @IntVal({3}) short c = ++a; + + @IntVal({4}) int d = ++a; + + @IntVal({5}) long e = ++a; + ++a; + e = --a; + d = --a; + c = --a; + b = --a; + } + + public void postfix() { + int a = 0; + @IntVal({0}) int b = a++; + @IntVal({1}) int c = a--; + b = a++; + + @IntVal({1}) long d = a--; + + double e = 0.25; + @DoubleVal({0.25}) double f = e++; + @DoubleVal({1.25}) double g = e--; + f = e; + } + + public void plusminus() { + @IntVal({48}) int a = +48; + @IntVal({-49}) int b = -49; + + @IntVal({34}) long c = +34; + @IntVal({-34}) long d = -34; + } + + public void intRange(@IntRange(from = 0, to = 2) int val) { + int a = val; + @IntRange(from = -2, to = 0) int b = -a; + @IntRange(from = 0, to = 2) int c = +a; + @IntRange(from = -3, to = -1) int d = ~a; + @IntRange(from = 1, to = 3) int e = ++a; + @IntRange(from = 1, to = 3) int f = a++; + } } diff --git a/framework/tests/value/UncheckedMinLen.java b/framework/tests/value/UncheckedMinLen.java index e69f53b543f..c75a0376883 100644 --- a/framework/tests/value/UncheckedMinLen.java +++ b/framework/tests/value/UncheckedMinLen.java @@ -4,15 +4,15 @@ // test case for kelloggm#183: https://github.com/kelloggm/checker-framework/issues/183 public class UncheckedMinLen { - void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } } diff --git a/framework/tests/value/Underflows.java b/framework/tests/value/Underflows.java index 70eddf4cdf4..581d491cd85 100644 --- a/framework/tests/value/Underflows.java +++ b/framework/tests/value/Underflows.java @@ -1,43 +1,43 @@ import org.checkerframework.common.value.qual.*; public class Underflows { - static void bytes() { - byte min = Byte.MIN_VALUE; - @IntVal(127) byte maxPlus1 = (byte) (min - 1); - // :: error: (assignment.type.incompatible) - @IntVal(127) short maxPlus1Short = (short) (min - 1); - } + static void bytes() { + byte min = Byte.MIN_VALUE; + @IntVal(127) byte maxPlus1 = (byte) (min - 1); + // :: error: (assignment.type.incompatible) + @IntVal(127) short maxPlus1Short = (short) (min - 1); + } - static void chars() { - char min = Character.MIN_VALUE; - // :: warning: (cast.unsafe) - @IntVal(65535) char maxPlus1 = (char) (min - 1); - } + static void chars() { + char min = Character.MIN_VALUE; + // :: warning: (cast.unsafe) + @IntVal(65535) char maxPlus1 = (char) (min - 1); + } - static void shorts() { - short min = Short.MIN_VALUE; - @IntVal(32767) short maxPlus1 = (short) (min - 1); - // :: error: (assignment.type.incompatible) - @IntVal(32767) int maxPlus1Int = (int) (min - 1); - } + static void shorts() { + short min = Short.MIN_VALUE; + @IntVal(32767) short maxPlus1 = (short) (min - 1); + // :: error: (assignment.type.incompatible) + @IntVal(32767) int maxPlus1Int = (int) (min - 1); + } - static void ints() { - int min = Integer.MIN_VALUE; - @IntVal(2147483647) int maxPlus1 = min - 1; - } + static void ints() { + int min = Integer.MIN_VALUE; + @IntVal(2147483647) int maxPlus1 = min - 1; + } - static void longs() { - long min = Long.MIN_VALUE; - @IntVal(9223372036854775807L) long maxPlus1 = min - 1; - } + static void longs() { + long min = Long.MIN_VALUE; + @IntVal(9223372036854775807L) long maxPlus1 = min - 1; + } - static void doubles() { - double min = Double.MIN_VALUE; - @DoubleVal(-1.0) double maxPlus1 = min - 1.0; - } + static void doubles() { + double min = Double.MIN_VALUE; + @DoubleVal(-1.0) double maxPlus1 = min - 1.0; + } - static void floats() { - float min = Float.MIN_VALUE; - @DoubleVal(-1.0F) float maxPlus1 = min - 1.0f; - } + static void floats() { + float min = Float.MIN_VALUE; + @DoubleVal(-1.0F) float maxPlus1 = min - 1.0f; + } } diff --git a/framework/tests/value/ValueCast.java b/framework/tests/value/ValueCast.java index fb13b006839..6c6e34cf039 100644 --- a/framework/tests/value/ValueCast.java +++ b/framework/tests/value/ValueCast.java @@ -3,57 +3,57 @@ import org.checkerframework.common.value.qual.*; public class ValueCast { - void testShort_plus(@IntRange(from = 0) short x) { - @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; - } - - void testIntFrom(@IntRange(from = 0) int x) { - @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; - } - - void testShortFrom(@IntRange(from = 0) short x) { - @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; - } - - void testCharFrom(@IntRange(from = 0) char x) { - @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; - } - - void testByteFrom(@IntRange(from = 0) byte x) { - @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; - } - - void testIntTo(@IntRange(to = 0) int x) { - @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; - } - - void testShortTo(@IntRange(to = 0) short x) { - @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; - } - - void testCharTo(@IntRange(to = 1) char x) { - @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; - } - - void testByteTo(@IntRange(to = 0) byte x) { - @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; - } + void testShort_plus(@IntRange(from = 0) short x) { + @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; + } + + void testIntFrom(@IntRange(from = 0) int x) { + @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; + } + + void testShortFrom(@IntRange(from = 0) short x) { + @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; + } + + void testCharFrom(@IntRange(from = 0) char x) { + @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; + } + + void testByteFrom(@IntRange(from = 0) byte x) { + @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; + } + + void testIntTo(@IntRange(to = 0) int x) { + @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; + } + + void testShortTo(@IntRange(to = 0) short x) { + @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; + } + + void testCharTo(@IntRange(to = 1) char x) { + @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; + } + + void testByteTo(@IntRange(to = 0) byte x) { + @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; + } } diff --git a/framework/tests/value/ValueCast2.java b/framework/tests/value/ValueCast2.java index cdeaee697b3..be2dd26fe63 100644 --- a/framework/tests/value/ValueCast2.java +++ b/framework/tests/value/ValueCast2.java @@ -3,23 +3,23 @@ import org.checkerframework.common.value.qual.*; public class ValueCast2 { - byte foo(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { - return (byte) x; - } + byte foo(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { + return (byte) x; + } - byte bar(@IntRange(from = -1000, to = 500) int x) { - return (byte) x; - } + byte bar(@IntRange(from = -1000, to = 500) int x) { + return (byte) x; + } - short foo1(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { - return (short) x; - } + short foo1(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { + return (short) x; + } - int foo2(@IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long x) { - return (int) x; - } + int foo2(@IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long x) { + return (int) x; + } - int baz(@IntRange(from = Long.MIN_VALUE, to = 0) long x) { - return (int) x; - } + int baz(@IntRange(from = Long.MIN_VALUE, to = 0) long x) { + return (int) x; + } } diff --git a/framework/tests/value/ValueOpt.java b/framework/tests/value/ValueOpt.java index 462174df30f..602ee47ec05 100644 --- a/framework/tests/value/ValueOpt.java +++ b/framework/tests/value/ValueOpt.java @@ -1,16 +1,14 @@ +import java.util.Optional; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.common.value.qual.IntVal; -import java.util.Optional; - public class ValueOpt { - Optional<@NonNegative Long> method(Optional<@IntVal(Long.MAX_VALUE) Long> opt1) { - @NonNegative Long l = Long.MAX_VALUE; - @NonNegative long l2 = -1l; - Optional<@NonNegative Long> opt2 = opt1; - Optional<@NonNegative Long> opt3 = - Optional.<@IntVal(Long.MAX_VALUE) Long>of(Long.MAX_VALUE); - return Optional.of(Long.MAX_VALUE); - } + Optional<@NonNegative Long> method(Optional<@IntVal(Long.MAX_VALUE) Long> opt1) { + @NonNegative Long l = Long.MAX_VALUE; + @NonNegative long l2 = -1l; + Optional<@NonNegative Long> opt2 = opt1; + Optional<@NonNegative Long> opt3 = Optional.<@IntVal(Long.MAX_VALUE) Long>of(Long.MAX_VALUE); + return Optional.of(Long.MAX_VALUE); + } } diff --git a/framework/tests/value/ValueWrapperCast.java b/framework/tests/value/ValueWrapperCast.java index 994ec1cdbbd..26b239adc7b 100644 --- a/framework/tests/value/ValueWrapperCast.java +++ b/framework/tests/value/ValueWrapperCast.java @@ -3,57 +3,57 @@ import org.checkerframework.common.value.qual.*; public class ValueWrapperCast { - void testShort_plus(@IntRange(from = 0) Short x) { - @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; - } - - void testIntFrom(@IntRange(from = 0) Integer x) { - @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; - } - - void testShortFrom(@IntRange(from = 0) Short x) { - @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; - } - - void testCharFrom(@IntRange(from = 0) Character x) { - @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; - } - - void testByteFrom(@IntRange(from = 0) Byte x) { - @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; - } - - void testIntTo(@IntRange(to = 0) Integer x) { - @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; - } - - void testShortTo(@IntRange(to = 0) Short x) { - @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; - } - - void testCharTo(@IntRange(to = 1) Character x) { - @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; - } - - void testByteTo(@IntRange(to = 0) Byte x) { - @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; - } + void testShort_plus(@IntRange(from = 0) Short x) { + @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; + } + + void testIntFrom(@IntRange(from = 0) Integer x) { + @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; + } + + void testShortFrom(@IntRange(from = 0) Short x) { + @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; + } + + void testCharFrom(@IntRange(from = 0) Character x) { + @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; + } + + void testByteFrom(@IntRange(from = 0) Byte x) { + @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; + } + + void testIntTo(@IntRange(to = 0) Integer x) { + @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; + } + + void testShortTo(@IntRange(to = 0) Short x) { + @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; + } + + void testCharTo(@IntRange(to = 1) Character x) { + @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; + } + + void testByteTo(@IntRange(to = 0) Byte x) { + @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; + } } diff --git a/framework/tests/value/VarArgRe.java b/framework/tests/value/VarArgRe.java index 7542f128665..02a99fae6dc 100644 --- a/framework/tests/value/VarArgRe.java +++ b/framework/tests/value/VarArgRe.java @@ -2,24 +2,24 @@ import org.checkerframework.framework.testchecker.lib.VarArgMethods; public class VarArgRe { - // VarArgMethods is declarded in - // framework/tests/src/org/checkerframework/framework/testchecker/lib. - // All the methods return the length of the vararg. - public void use0() { - @IntVal(0) int i1 = VarArgMethods.test0(); - @IntVal(1) int i2 = VarArgMethods.test0(-1); - @IntVal(5) int i3 = VarArgMethods.test0(0, "sldfj", 0, 234, 234); - } + // VarArgMethods is declarded in + // framework/tests/src/org/checkerframework/framework/testchecker/lib. + // All the methods return the length of the vararg. + public void use0() { + @IntVal(0) int i1 = VarArgMethods.test0(); + @IntVal(1) int i2 = VarArgMethods.test0(-1); + @IntVal(5) int i3 = VarArgMethods.test0(0, "sldfj", 0, 234, 234); + } - public void use1() { - @IntVal(0) int i1 = VarArgMethods.test1("13"); - @IntVal(1) int i2 = VarArgMethods.test1("13", -1); - @IntVal(5) int i3 = VarArgMethods.test1("13", 0, "sldfj", 0, 234, 234); - } + public void use1() { + @IntVal(0) int i1 = VarArgMethods.test1("13"); + @IntVal(1) int i2 = VarArgMethods.test1("13", -1); + @IntVal(5) int i3 = VarArgMethods.test1("13", 0, "sldfj", 0, 234, 234); + } - public void use2() { - @IntVal(0) int i1 = VarArgMethods.test2("", ""); - @IntVal(1) int i2 = VarArgMethods.test2("", "", -1); - @IntVal(5) int i3 = VarArgMethods.test2("", "", 0, "sldfj", 0, 234, 234); - } + public void use2() { + @IntVal(0) int i1 = VarArgMethods.test2("", ""); + @IntVal(1) int i2 = VarArgMethods.test2("", "", -1); + @IntVal(5) int i3 = VarArgMethods.test2("", "", 0, "sldfj", 0, 234, 234); + } } diff --git a/framework/tests/value/WildcardIn.java b/framework/tests/value/WildcardIn.java index d7a468b253e..000950908d4 100644 --- a/framework/tests/value/WildcardIn.java +++ b/framework/tests/value/WildcardIn.java @@ -1,10 +1,10 @@ public class WildcardIn { - void foo(GenericObject gen) { - Integer i = (Integer) gen.get(); - } + void foo(GenericObject gen) { + Integer i = (Integer) gen.get(); + } } interface GenericObject { - public abstract T get(); + public abstract T get(); } diff --git a/framework/tests/value/java17/MultiCaseConst.java b/framework/tests/value/java17/MultiCaseConst.java index b9ae2b4b12e..552e7e069ed 100644 --- a/framework/tests/value/java17/MultiCaseConst.java +++ b/framework/tests/value/java17/MultiCaseConst.java @@ -3,43 +3,43 @@ public class MultiCaseConst { - void method(int selector) { - switch (selector) { - case 1, 2, 3: - // :: error: (assignment.type.incompatible) - @IntVal(0) int o = selector; - @IntVal({1, 2, 3}) int tmp = selector; - case 4, 5: - // :: error: (assignment.type.incompatible) - @IntVal({4, 5}) int tmp2 = selector; - @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; - } + void method(int selector) { + switch (selector) { + case 1, 2, 3: + // :: error: (assignment.type.incompatible) + @IntVal(0) int o = selector; + @IntVal({1, 2, 3}) int tmp = selector; + case 4, 5: + // :: error: (assignment.type.incompatible) + @IntVal({4, 5}) int tmp2 = selector; + @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; } + } - void method2(int selector) { - switch (selector) { - case 1: - // :: error: (assignment.type.incompatible) - @IntVal(0) int o = selector; - @IntVal({1, 2, 3}) int tmp = selector; - break; - case 4, 5: - @IntVal({4, 5}) int tmp2 = selector; - @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; - } + void method2(int selector) { + switch (selector) { + case 1: + // :: error: (assignment.type.incompatible) + @IntVal(0) int o = selector; + @IntVal({1, 2, 3}) int tmp = selector; + break; + case 4, 5: + @IntVal({4, 5}) int tmp2 = selector; + @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; } + } - void method3(int selector) { - switch (selector) { - case 1 -> { - // :: error: (assignment.type.incompatible) - @IntVal(0) int o = selector; - @IntVal({1, 2, 3}) int tmp = selector; - } - case 4, 5 -> { - @IntVal({4, 5}) int tmp2 = selector; - @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; - } - } + void method3(int selector) { + switch (selector) { + case 1 -> { + // :: error: (assignment.type.incompatible) + @IntVal(0) int o = selector; + @IntVal({1, 2, 3}) int tmp = selector; + } + case 4, 5 -> { + @IntVal({4, 5}) int tmp2 = selector; + @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; + } } + } } diff --git a/framework/tests/value/java17/SwitchExpressionTyping.java b/framework/tests/value/java17/SwitchExpressionTyping.java index 0c508ab3814..2bc5a49dd0e 100644 --- a/framework/tests/value/java17/SwitchExpressionTyping.java +++ b/framework/tests/value/java17/SwitchExpressionTyping.java @@ -2,99 +2,99 @@ import org.checkerframework.common.value.qual.IntVal; public class SwitchExpressionTyping { - public static boolean flag = false; + public static boolean flag = false; - void method0(String s) { - @IntVal({0, 1, 2, 3}) int o = - switch (s) { - case "Hello?" -> { - throw new RuntimeException(); - } - case "Hello" -> 0; - case "Bye" -> 1; - case "Later" -> 2; - case "What?" -> throw new RuntimeException(); - default -> 3; - }; - } + void method0(String s) { + @IntVal({0, 1, 2, 3}) int o = + switch (s) { + case "Hello?" -> { + throw new RuntimeException(); + } + case "Hello" -> 0; + case "Bye" -> 1; + case "Later" -> 2; + case "What?" -> throw new RuntimeException(); + default -> 3; + }; + } - void method1(String s) { - @IntVal({1, 2, 3}) int o = - switch (s) { - case "Hello?" -> 1; - case "Hello" -> 1; - case "Bye" -> 1; - case "Later" -> 1; - case "What?" -> { - if (flag) { - yield 2; - } - yield 3; - } - default -> 1; - }; + void method1(String s) { + @IntVal({1, 2, 3}) int o = + switch (s) { + case "Hello?" -> 1; + case "Hello" -> 1; + case "Bye" -> 1; + case "Later" -> 1; + case "What?" -> { + if (flag) { + yield 2; + } + yield 3; + } + default -> 1; + }; - @IntVal(1) int o2 = - // :: error: (assignment.type.incompatible) - switch (s) { - case "Hello?" -> 1; - case "Hello" -> 1; - case "Bye" -> 1; - case "Later" -> 1; - case "What?" -> { - if (flag) { - yield 2; - } - yield 3; - } - default -> 1; - }; - } + @IntVal(1) int o2 = + // :: error: (assignment.type.incompatible) + switch (s) { + case "Hello?" -> 1; + case "Hello" -> 1; + case "Bye" -> 1; + case "Later" -> 1; + case "What?" -> { + if (flag) { + yield 2; + } + yield 3; + } + default -> 1; + }; + } - void method2(String s, String r) { - @IntVal({0, 1, 2, 3}) int o = - switch (s) { - case "Hello?" -> { - if (flag) { - throw new RuntimeException(); - } - yield 2; + void method2(String s, String r) { + @IntVal({0, 1, 2, 3}) int o = + switch (s) { + case "Hello?" -> { + if (flag) { + throw new RuntimeException(); + } + yield 2; + } + case "Hello" -> { + int i = + switch (r) { + case "Hello" -> 4; + case "Bye" -> 5; + case "Later" -> 6; + default -> 42; + }; + yield 0; + } + case "Bye" -> 1; + case "Later" -> { + int i = + switch (r) { + case "Hello": + { + yield 4; + } + case "Bye": + { + yield 5; } - case "Hello" -> { - int i = - switch (r) { - case "Hello" -> 4; - case "Bye" -> 5; - case "Later" -> 6; - default -> 42; - }; - yield 0; + case "Later": + { + yield 6; } - case "Bye" -> 1; - case "Later" -> { - int i = - switch (r) { - case "Hello": - { - yield 4; - } - case "Bye": - { - yield 5; - } - case "Later": - { - yield 6; - } - default: - { - yield 42; - } - }; - yield 2; + default: + { + yield 42; } - case "What?" -> throw new RuntimeException(); - default -> 3; }; - } + yield 2; + } + case "What?" -> throw new RuntimeException(); + default -> 3; + }; + } } diff --git a/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java b/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java index 38cb26e6bd4..c38f1e1f547 100644 --- a/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java +++ b/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java @@ -3,69 +3,69 @@ public class ValueSwitchExprNeedsDataflow { - void method(int selector) { - @IntVal({2, 3}) int value1 = - switch (selector) { - case 1: - yield 1 + 2; - default: - yield 1 + 1; - }; - @IntVal({2, 3}) int value2 = - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 1; - }; + void method(int selector) { + @IntVal({2, 3}) int value1 = + switch (selector) { + case 1: + yield 1 + 2; + default: + yield 1 + 1; + }; + @IntVal({2, 3}) int value2 = + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 1; + }; - int tmp = - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 1; - }; - @IntVal({2, 3}) int value3 = tmp; - } + int tmp = + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 1; + }; + @IntVal({2, 3}) int value3 = tmp; + } - void method1(int selector) { + void method1(int selector) { - @IntVal(3) int value1 = - // :: error: (assignment.type.incompatible) - switch (selector) { - case 1: - yield 1 + 2; - default: - yield 1 + 1; - }; + @IntVal(3) int value1 = + // :: error: (assignment.type.incompatible) + switch (selector) { + case 1: + yield 1 + 2; + default: + yield 1 + 1; + }; - @IntVal(3) int value2 = - // :: error: (assignment.type.incompatible) - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 1; - }; - } + @IntVal(3) int value2 = + // :: error: (assignment.type.incompatible) + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 1; + }; + } - void method2(int selector, int selector2) { + void method2(int selector, int selector2) { - @IntVal({2, 3}) int value2 = - switch (selector) { - case 1 -> { - yield 1 + 2; - } - default -> { - yield switch (selector2) { - case 1: - { - @IntVal(3) int inner = - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 2; - }; - yield 1 + 2; - } - default: - yield 1 + 1; - }; - } - }; - } + @IntVal({2, 3}) int value2 = + switch (selector) { + case 1 -> { + yield 1 + 2; + } + default -> { + yield switch (selector2) { + case 1: + { + @IntVal(3) int inner = + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 2; + }; + yield 1 + 2; + } + default: + yield 1 + 1; + }; + } + }; + } } diff --git a/framework/tests/value/java17/ValueSwitchStatementRules.java b/framework/tests/value/java17/ValueSwitchStatementRules.java index ac9947ddf3f..ca728d6de9a 100644 --- a/framework/tests/value/java17/ValueSwitchStatementRules.java +++ b/framework/tests/value/java17/ValueSwitchStatementRules.java @@ -2,28 +2,28 @@ import org.checkerframework.common.value.qual.IntVal; public class ValueSwitchStatementRules { - private int field; + private int field; - void method(int selector) { - field = 300; - switch (selector) { - case 1: - field = 42; - @IntVal(42) int copyField = field; - case 2: - // :: error: (assignment.type.incompatible) - @IntVal(300) int copyField2 = field; - } + void method(int selector) { + field = 300; + switch (selector) { + case 1: + field = 42; + @IntVal(42) int copyField = field; + case 2: + // :: error: (assignment.type.incompatible) + @IntVal(300) int copyField2 = field; + } - field = 300; - switch (selector) { - case 1 -> { - field = 42; - @IntVal(42) int copyField = field; - } - case 2 -> { - @IntVal(300) int copyField = field; - } - } + field = 300; + switch (selector) { + case 1 -> { + field = 42; + @IntVal(42) int copyField = field; + } + case 2 -> { + @IntVal(300) int copyField = field; + } } + } } diff --git a/framework/tests/value/loops/DoWhile.java b/framework/tests/value/loops/DoWhile.java index 5e5d4e77fc3..1b1f6eeedde 100644 --- a/framework/tests/value/loops/DoWhile.java +++ b/framework/tests/value/loops/DoWhile.java @@ -7,33 +7,33 @@ @SuppressWarnings("value") public class DoWhile { - void doWhile() { - int d = 0; - do { - d++; - } while (d < 399); - @IntRange(from = 399) int after = d; - } + void doWhile() { + int d = 0; + do { + d++; + } while (d < 399); + @IntRange(from = 399) int after = d; + } - void another() { - int d = 0; - do { - d++; - if (d > 444) { - break; - } - @IntRange(from = 1, to = 444) int z = d; - } while (true); - } + void another() { + int d = 0; + do { + d++; + if (d > 444) { + break; + } + @IntRange(from = 1, to = 444) int z = d; + } while (true); + } - void fromAnno(@IntVal(2222) int param) { - int d = 0; - do { - d++; - if (d > param) { - break; - } - @IntRange(from = 1, to = 2222) int z = d; - } while (true); - } + void fromAnno(@IntVal(2222) int param) { + int d = 0; + do { + d++; + if (d > param) { + break; + } + @IntRange(from = 1, to = 2222) int z = d; + } while (true); + } } diff --git a/framework/tests/value/loops/NestedLoops.java b/framework/tests/value/loops/NestedLoops.java index a8ee9035920..822bc4a36a5 100644 --- a/framework/tests/value/loops/NestedLoops.java +++ b/framework/tests/value/loops/NestedLoops.java @@ -5,52 +5,52 @@ // to make sure that dataflow reaches a fixed point. @SuppressWarnings("value") public class NestedLoops { - void test1() { - int doWhileIndex = 0; - do { - for (int forIndex = 0; forIndex < doWhileIndex; forIndex++) { - System.out.print("Hello"); - int whileIndex = 0; - while (whileIndex < forIndex) { - whileIndex++; - } - } - doWhileIndex++; - } while (doWhileIndex < Integer.MAX_VALUE); - } - - void test2() { - int doWhileIndex = 0; - do { - for (int forIndex = 0; forIndex < Integer.MAX_VALUE; forIndex++) { - System.out.print("Hello"); - int whileIndex = 0; - while (whileIndex < Integer.MAX_VALUE) { - whileIndex++; - } - } - doWhileIndex++; - } while (doWhileIndex < Integer.MAX_VALUE); - } + void test1() { + int doWhileIndex = 0; + do { + for (int forIndex = 0; forIndex < doWhileIndex; forIndex++) { + System.out.print("Hello"); + int whileIndex = 0; + while (whileIndex < forIndex) { + whileIndex++; + } + } + doWhileIndex++; + } while (doWhileIndex < Integer.MAX_VALUE); + } - void test3() { - int doWhileIndex = 0; - int forIndex; + void test2() { + int doWhileIndex = 0; + do { + for (int forIndex = 0; forIndex < Integer.MAX_VALUE; forIndex++) { + System.out.print("Hello"); int whileIndex = 0; + while (whileIndex < Integer.MAX_VALUE) { + whileIndex++; + } + } + doWhileIndex++; + } while (doWhileIndex < Integer.MAX_VALUE); + } + + void test3() { + int doWhileIndex = 0; + int forIndex; + int whileIndex = 0; - do { - @IntRange(to = 2999) int a = doWhileIndex; - for (forIndex = 0; forIndex < 4000; forIndex++) { + do { + @IntRange(to = 2999) int a = doWhileIndex; + for (forIndex = 0; forIndex < 4000; forIndex++) { - @IntRange(to = 3999) int b = forIndex; - System.out.print("Hello"); - whileIndex = 0; - while (whileIndex < 5000) { - @IntRange(to = 4999) int c = whileIndex; - whileIndex++; - } - } - doWhileIndex++; - } while (doWhileIndex < 3000); - } + @IntRange(to = 3999) int b = forIndex; + System.out.print("Hello"); + whileIndex = 0; + while (whileIndex < 5000) { + @IntRange(to = 4999) int c = whileIndex; + whileIndex++; + } + } + doWhileIndex++; + } while (doWhileIndex < 3000); + } } diff --git a/framework/tests/value/loops/OscillatingLoops.java b/framework/tests/value/loops/OscillatingLoops.java index 92bbd97a6ca..bf4458b7694 100644 --- a/framework/tests/value/loops/OscillatingLoops.java +++ b/framework/tests/value/loops/OscillatingLoops.java @@ -7,60 +7,60 @@ @SuppressWarnings("value") public class OscillatingLoops { - void oscillatesDoWhile() { - int i = 0; - int d = 0; - do { - i++; - if (d > 4566) { - d = 0; - } else { - d++; - } - } while (i < Integer.MAX_VALUE); - @IntRange(from = 0, to = 4567) int after = d; - @IntVal(Integer.MAX_VALUE) int afterI = i; - } + void oscillatesDoWhile() { + int i = 0; + int d = 0; + do { + i++; + if (d > 4566) { + d = 0; + } else { + d++; + } + } while (i < Integer.MAX_VALUE); + @IntRange(from = 0, to = 4567) int after = d; + @IntVal(Integer.MAX_VALUE) int afterI = i; + } - void oscillatesWhile() { - int i = 0; - int d = 1; - while (i < Integer.MAX_VALUE) { - i++; - if (d > 4566) { - d = 0; - } else { - d++; - } - } - @IntRange(from = 0, to = 4567) int after = d; - @IntVal(Integer.MAX_VALUE) int afterI = i; + void oscillatesWhile() { + int i = 0; + int d = 1; + while (i < Integer.MAX_VALUE) { + i++; + if (d > 4566) { + d = 0; + } else { + d++; + } } + @IntRange(from = 0, to = 4567) int after = d; + @IntVal(Integer.MAX_VALUE) int afterI = i; + } - void oscillatesDoWhile2() { - int i = 0; - int d = 0; - do { - if (d > 4566) { - d = 0; - } else { - d++; - } - i++; - } while (i < Integer.MAX_VALUE); - @IntRange(from = -128, to = 32767) int after = d; - @IntVal(Integer.MAX_VALUE) int afterI = i; - } + void oscillatesDoWhile2() { + int i = 0; + int d = 0; + do { + if (d > 4566) { + d = 0; + } else { + d++; + } + i++; + } while (i < Integer.MAX_VALUE); + @IntRange(from = -128, to = 32767) int after = d; + @IntVal(Integer.MAX_VALUE) int afterI = i; + } - void oscillatesFor() { - int d = 0; - for (int i = 0; i < Integer.MAX_VALUE; i++) { - if (d > 4566) { - d = 0; - } else { - d++; - } - } - @IntRange(from = -128, to = 32767) int after = d; + void oscillatesFor() { + int d = 0; + for (int i = 0; i < Integer.MAX_VALUE; i++) { + if (d > 4566) { + d = 0; + } else { + d++; + } } + @IntRange(from = -128, to = 32767) int after = d; + } } diff --git a/framework/tests/value/loops/WidenedUpperBound.java b/framework/tests/value/loops/WidenedUpperBound.java index ebc257c7b6e..1f0a374bd2a 100644 --- a/framework/tests/value/loops/WidenedUpperBound.java +++ b/framework/tests/value/loops/WidenedUpperBound.java @@ -1,6 +1,5 @@ -import org.checkerframework.common.value.qual.IntRange; - import java.util.List; +import org.checkerframework.common.value.qual.IntRange; // Because the analysis of loops isn't precise enough, the Value Checker issues // warnings on this test case. So, suppress those warnings, but run the tests @@ -10,191 +9,191 @@ @SuppressWarnings("value") public class WidenedUpperBound { - void increment() { - int forIndex; - for (forIndex = 0; forIndex < 4323; forIndex++) { - @IntRange(from = 0, to = 4322) int x = forIndex; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 0, to = 4322) int x = forIndex; - @IntRange(from = 4323) int y = forIndex; - - int whileIndex = 0; - while (whileIndex < 1234) { - @IntRange(from = 0, to = 1233) int z = whileIndex; - whileIndex++; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 0, to = 1233) int a = whileIndex; - @IntRange(from = 1234) int b = whileIndex; - - int doWhileIndex = 0; - do { - @IntRange(from = 0, to = 2344) int c = doWhileIndex; - doWhileIndex++; - } while (doWhileIndex < 2345); - //// ::error: (assignment.type.incompatible) - @IntRange(from = 0, to = 2344) int d = doWhileIndex; - @IntRange(from = 2345) int e = doWhileIndex; - } - - void decrement() { - int forIndex; - for (forIndex = 4323; forIndex > 0; forIndex--) { - @IntRange(from = 1, to = 4323) int x = forIndex; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 1, to = 4323) int x = forIndex; - @IntRange(to = 0) int y = forIndex; - - int whileIndex = 1234; - while (whileIndex > 0) { - @IntRange(from = 1, to = 1234) int z = whileIndex; - whileIndex--; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 1, to = 1234) int a = whileIndex; - @IntRange(to = 0) int b = whileIndex; - - int doWhileIndex = 2344; - do { - @IntRange(from = 1, to = 2344) int c = doWhileIndex; - doWhileIndex--; - } while (doWhileIndex > 0); - //// ::error: (assignment.type.incompatible) - @IntRange(from = 1, to = 2344) int d = doWhileIndex; - @IntRange(to = 0) int e = doWhileIndex; - } - - static void test1() { - for (int i = 40; i > 0; i--) { - @IntRange(from = 1, to = 40) int x = i; - } - } - - static void test1Explicit() { - for (@IntRange(from = 1, to = 40) int i = 40; i > 0; i--) { - @IntRange(from = 1, to = 40) int x = i; - } - } - - static void test2() { - for (int i = 0; i < 18; i++) { - @IntRange(from = 0, to = 17) int x = i; - } - } - - static void test2Explicit() { - for (@IntRange(from = 0, to = 17) int i = 0; i < 18; i++) { - @IntRange(from = 0, to = 17) int x = i; - } - } - - static void test3() { - for (int i = 0; i < 18; i++) { - @IntRange(from = 0, to = 17) int x = i; - } - } - - static void evenOdd(int param) { - for (int i = 0; i < 12; i++) { - if (i % 2 == 0) { - @IntRange(from = 0, to = 11) int z = i; - } - @IntRange(from = 0, to = 11) int x = i; - } - - for (int i = 0; i < 12; i++) { - if (param == 0) { - @IntRange(from = 0, to = 11) int z = i; - } - @IntRange(from = 0, to = 11) int x = i; - } - } - - static void evenOdd2(int param) { - for (int i = 0; i < 300; i++) { - if (i % 2 == 0) { - @IntRange(from = 0, to = 299) int z = i; - } - @IntRange(from = 0, to = 299) int x = i; - } - - for (int i = 0; i < 399; i++) { - if (param == 0) { - @IntRange(from = 0, to = 398) int z = i; - } - @IntRange(from = 0, to = 398) int x = i; - } - } - - void ifBlock(int param) { - int x = 10; - if (x < param) { - x = param; - } - int z = 40; - if (z < param) { - param = 40; - } - } - - void doWhile() { - int d = 0; - do { - @IntRange(from = 0, to = 399) int x = d; - if (d % 2 == 0) { - @IntRange(from = 0, to = 399) int y = d; - } + void increment() { + int forIndex; + for (forIndex = 0; forIndex < 4323; forIndex++) { + @IntRange(from = 0, to = 4322) int x = forIndex; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 0, to = 4322) int x = forIndex; + @IntRange(from = 4323) int y = forIndex; + + int whileIndex = 0; + while (whileIndex < 1234) { + @IntRange(from = 0, to = 1233) int z = whileIndex; + whileIndex++; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 0, to = 1233) int a = whileIndex; + @IntRange(from = 1234) int b = whileIndex; + + int doWhileIndex = 0; + do { + @IntRange(from = 0, to = 2344) int c = doWhileIndex; + doWhileIndex++; + } while (doWhileIndex < 2345); + //// ::error: (assignment.type.incompatible) + @IntRange(from = 0, to = 2344) int d = doWhileIndex; + @IntRange(from = 2345) int e = doWhileIndex; + } + + void decrement() { + int forIndex; + for (forIndex = 4323; forIndex > 0; forIndex--) { + @IntRange(from = 1, to = 4323) int x = forIndex; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 1, to = 4323) int x = forIndex; + @IntRange(to = 0) int y = forIndex; + + int whileIndex = 1234; + while (whileIndex > 0) { + @IntRange(from = 1, to = 1234) int z = whileIndex; + whileIndex--; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 1, to = 1234) int a = whileIndex; + @IntRange(to = 0) int b = whileIndex; + + int doWhileIndex = 2344; + do { + @IntRange(from = 1, to = 2344) int c = doWhileIndex; + doWhileIndex--; + } while (doWhileIndex > 0); + //// ::error: (assignment.type.incompatible) + @IntRange(from = 1, to = 2344) int d = doWhileIndex; + @IntRange(to = 0) int e = doWhileIndex; + } + + static void test1() { + for (int i = 40; i > 0; i--) { + @IntRange(from = 1, to = 40) int x = i; + } + } - d++; - } while (d < 399); - - @IntRange(from = 399) int z = d; + static void test1Explicit() { + for (@IntRange(from = 1, to = 40) int i = 40; i > 0; i--) { + @IntRange(from = 1, to = 40) int x = i; } + } - void doWhileMax() { - int d = 0; - do { - @IntRange(from = 0) int x = d; - if (d % 2 == 0) { - @IntRange(from = 0) int y = d; - } + static void test2() { + for (int i = 0; i < 18; i++) { + @IntRange(from = 0, to = 17) int x = i; + } + } - d++; - } while (d < Integer.MAX_VALUE); + static void test2Explicit() { + for (@IntRange(from = 0, to = 17) int i = 0; i < 18; i++) { + @IntRange(from = 0, to = 17) int x = i; + } + } - @IntRange(from = 399) int z = d; + static void test3() { + for (int i = 0; i < 18; i++) { + @IntRange(from = 0, to = 17) int x = i; + } + } + + static void evenOdd(int param) { + for (int i = 0; i < 12; i++) { + if (i % 2 == 0) { + @IntRange(from = 0, to = 11) int z = i; + } + @IntRange(from = 0, to = 11) int x = i; } - void whileLoop() { - int i = 0; - while (i < 399) { - @IntRange(from = 0, to = 399) int x = i; - if (i % 2 == 0) { - @IntRange(from = 0, to = 399) int y = i; - } + for (int i = 0; i < 12; i++) { + if (param == 0) { + @IntRange(from = 0, to = 11) int z = i; + } + @IntRange(from = 0, to = 11) int x = i; + } + } + + static void evenOdd2(int param) { + for (int i = 0; i < 300; i++) { + if (i % 2 == 0) { + @IntRange(from = 0, to = 299) int z = i; + } + @IntRange(from = 0, to = 299) int x = i; + } - i++; - } + for (int i = 0; i < 399; i++) { + if (param == 0) { + @IntRange(from = 0, to = 398) int z = i; + } + @IntRange(from = 0, to = 398) int x = i; + } + } - @IntRange(from = 399) int z = i; + void ifBlock(int param) { + int x = 10; + if (x < param) { + x = param; + } + int z = 40; + if (z < param) { + param = 40; + } + } + + void doWhile() { + int d = 0; + do { + @IntRange(from = 0, to = 399) int x = d; + if (d % 2 == 0) { + @IntRange(from = 0, to = 399) int y = d; + } + + d++; + } while (d < 399); + + @IntRange(from = 399) int z = d; + } + + void doWhileMax() { + int d = 0; + do { + @IntRange(from = 0) int x = d; + if (d % 2 == 0) { + @IntRange(from = 0) int y = d; + } + + d++; + } while (d < Integer.MAX_VALUE); + + @IntRange(from = 399) int z = d; + } + + void whileLoop() { + int i = 0; + while (i < 399) { + @IntRange(from = 0, to = 399) int x = i; + if (i % 2 == 0) { + @IntRange(from = 0, to = 399) int y = i; + } + + i++; } - static void testMax() { - for (int i = 0; i < Integer.MAX_VALUE; i++) { - @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int x = i; - } - } + @IntRange(from = 399) int z = i; + } - void exceptionLoop(List list) { - int x = 0; - for (short z = 0; z < list.size(); z++) { - x = z; - if (z == 100) { - break; - } - } - @IntRange(from = 0, to = 127) int result = x; + static void testMax() { + for (int i = 0; i < Integer.MAX_VALUE; i++) { + @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int x = i; + } + } + + void exceptionLoop(List list) { + int x = 0; + for (short z = 0; z < list.size(); z++) { + x = z; + if (z == 100) { + break; + } } + @IntRange(from = 0, to = 127) int result = x; + } } diff --git a/framework/tests/variablenamedefault/TestVariableNameDefault.java b/framework/tests/variablenamedefault/TestVariableNameDefault.java index 8fdbe683902..6ac84587372 100644 --- a/framework/tests/variablenamedefault/TestVariableNameDefault.java +++ b/framework/tests/variablenamedefault/TestVariableNameDefault.java @@ -2,204 +2,204 @@ public class TestVariableNameDefault { - int top; - - int middle; - int middlevar; - int mymiddle; - int notmiddle; - int notmiddlevar; - @VariableNameDefaultMiddle int namedbottombutnot; - - int bottom; - int bottomvar; - int mybottom; - int notbottom; - int notbottomvar; - @VariableNameDefaultBottom int namedmiddlebutnot; - - void testFields() { - - @VariableNameDefaultTop int t; - - t = top; - - t = middle; - t = middlevar; - t = mymiddle; - t = notmiddle; - t = notmiddlevar; - t = namedbottombutnot; - - t = bottom; - t = bottomvar; - t = mybottom; - t = notbottom; - t = notbottomvar; - t = namedmiddlebutnot; - - @VariableNameDefaultMiddle int m; - - // :: error: (assignment.type.incompatible) - m = top; - - m = middle; - m = middlevar; - m = mymiddle; - // :: error: (assignment.type.incompatible) - m = notmiddle; - // :: error: (assignment.type.incompatible) - m = notmiddlevar; - m = namedbottombutnot; - - m = bottom; - m = bottomvar; - m = mybottom; - // :: error: (assignment.type.incompatible) - m = notbottom; - // :: error: (assignment.type.incompatible) - m = notbottomvar; - m = namedmiddlebutnot; - - @VariableNameDefaultBottom int b; - - // :: error: (assignment.type.incompatible) - b = top; - - // :: error: (assignment.type.incompatible) - b = middle; - // :: error: (assignment.type.incompatible) - b = middlevar; - // :: error: (assignment.type.incompatible) - b = mymiddle; - // :: error: (assignment.type.incompatible) - b = notmiddle; - // :: error: (assignment.type.incompatible) - b = notmiddlevar; - // :: error: (assignment.type.incompatible) - b = namedbottombutnot; - - b = bottom; - b = bottomvar; - b = mybottom; - // :: error: (assignment.type.incompatible) - b = notbottom; - // :: error: (assignment.type.incompatible) - b = notbottomvar; - b = namedmiddlebutnot; - } - - void testFormals(int middle, int notmiddle, int bottom, int notbottom) { - - @VariableNameDefaultTop int t; - - t = middle; - t = notmiddle; - t = bottom; - t = notbottom; - - @VariableNameDefaultMiddle int m; - - m = middle; - // :: error: (assignment.type.incompatible) - m = notmiddle; - m = bottom; - // :: error: (assignment.type.incompatible) - m = notbottom; - - @VariableNameDefaultBottom int b; - - // :: error: (assignment.type.incompatible) - b = middle; - // :: error: (assignment.type.incompatible) - b = notmiddle; - b = bottom; - // :: error: (assignment.type.incompatible) - b = notbottom; - } - - int middlemethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int mymiddlemethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int notmiddlemethod() { - return 0; - } - - int mynotmiddlemethod() { - return 0; - } - - int bottommethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int mybottommethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int notbottommethod() { - return 0; - } - - int mynotbottommethod() { - return 0; - } - - void testMethods() { - - @VariableNameDefaultTop int t; - - t = middlemethod(); - t = mymiddlemethod(); - t = notmiddlemethod(); - t = mynotmiddlemethod(); - - t = bottommethod(); - t = mybottommethod(); - t = notbottommethod(); - t = mynotbottommethod(); - - @VariableNameDefaultMiddle int m; - - m = middlemethod(); - m = mymiddlemethod(); - // :: error: (assignment.type.incompatible) - m = notmiddlemethod(); - // :: error: (assignment.type.incompatible) - m = mynotmiddlemethod(); - - m = bottommethod(); - m = mybottommethod(); - // :: error: (assignment.type.incompatible) - m = notbottommethod(); - // :: error: (assignment.type.incompatible) - m = mynotbottommethod(); - - @VariableNameDefaultBottom int b; - - // :: error: (assignment.type.incompatible) - b = middlemethod(); - // :: error: (assignment.type.incompatible) - b = mymiddlemethod(); - // :: error: (assignment.type.incompatible) - b = notmiddlemethod(); - // :: error: (assignment.type.incompatible) - b = mynotmiddlemethod(); - - b = bottommethod(); - b = mybottommethod(); - // :: error: (assignment.type.incompatible) - b = notbottommethod(); - // :: error: (assignment.type.incompatible) - b = mynotbottommethod(); - } + int top; + + int middle; + int middlevar; + int mymiddle; + int notmiddle; + int notmiddlevar; + @VariableNameDefaultMiddle int namedbottombutnot; + + int bottom; + int bottomvar; + int mybottom; + int notbottom; + int notbottomvar; + @VariableNameDefaultBottom int namedmiddlebutnot; + + void testFields() { + + @VariableNameDefaultTop int t; + + t = top; + + t = middle; + t = middlevar; + t = mymiddle; + t = notmiddle; + t = notmiddlevar; + t = namedbottombutnot; + + t = bottom; + t = bottomvar; + t = mybottom; + t = notbottom; + t = notbottomvar; + t = namedmiddlebutnot; + + @VariableNameDefaultMiddle int m; + + // :: error: (assignment.type.incompatible) + m = top; + + m = middle; + m = middlevar; + m = mymiddle; + // :: error: (assignment.type.incompatible) + m = notmiddle; + // :: error: (assignment.type.incompatible) + m = notmiddlevar; + m = namedbottombutnot; + + m = bottom; + m = bottomvar; + m = mybottom; + // :: error: (assignment.type.incompatible) + m = notbottom; + // :: error: (assignment.type.incompatible) + m = notbottomvar; + m = namedmiddlebutnot; + + @VariableNameDefaultBottom int b; + + // :: error: (assignment.type.incompatible) + b = top; + + // :: error: (assignment.type.incompatible) + b = middle; + // :: error: (assignment.type.incompatible) + b = middlevar; + // :: error: (assignment.type.incompatible) + b = mymiddle; + // :: error: (assignment.type.incompatible) + b = notmiddle; + // :: error: (assignment.type.incompatible) + b = notmiddlevar; + // :: error: (assignment.type.incompatible) + b = namedbottombutnot; + + b = bottom; + b = bottomvar; + b = mybottom; + // :: error: (assignment.type.incompatible) + b = notbottom; + // :: error: (assignment.type.incompatible) + b = notbottomvar; + b = namedmiddlebutnot; + } + + void testFormals(int middle, int notmiddle, int bottom, int notbottom) { + + @VariableNameDefaultTop int t; + + t = middle; + t = notmiddle; + t = bottom; + t = notbottom; + + @VariableNameDefaultMiddle int m; + + m = middle; + // :: error: (assignment.type.incompatible) + m = notmiddle; + m = bottom; + // :: error: (assignment.type.incompatible) + m = notbottom; + + @VariableNameDefaultBottom int b; + + // :: error: (assignment.type.incompatible) + b = middle; + // :: error: (assignment.type.incompatible) + b = notmiddle; + b = bottom; + // :: error: (assignment.type.incompatible) + b = notbottom; + } + + int middlemethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int mymiddlemethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int notmiddlemethod() { + return 0; + } + + int mynotmiddlemethod() { + return 0; + } + + int bottommethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int mybottommethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int notbottommethod() { + return 0; + } + + int mynotbottommethod() { + return 0; + } + + void testMethods() { + + @VariableNameDefaultTop int t; + + t = middlemethod(); + t = mymiddlemethod(); + t = notmiddlemethod(); + t = mynotmiddlemethod(); + + t = bottommethod(); + t = mybottommethod(); + t = notbottommethod(); + t = mynotbottommethod(); + + @VariableNameDefaultMiddle int m; + + m = middlemethod(); + m = mymiddlemethod(); + // :: error: (assignment.type.incompatible) + m = notmiddlemethod(); + // :: error: (assignment.type.incompatible) + m = mynotmiddlemethod(); + + m = bottommethod(); + m = mybottommethod(); + // :: error: (assignment.type.incompatible) + m = notbottommethod(); + // :: error: (assignment.type.incompatible) + m = mynotbottommethod(); + + @VariableNameDefaultBottom int b; + + // :: error: (assignment.type.incompatible) + b = middlemethod(); + // :: error: (assignment.type.incompatible) + b = mymiddlemethod(); + // :: error: (assignment.type.incompatible) + b = notmiddlemethod(); + // :: error: (assignment.type.incompatible) + b = mynotmiddlemethod(); + + b = bottommethod(); + b = mybottommethod(); + // :: error: (assignment.type.incompatible) + b = notbottommethod(); + // :: error: (assignment.type.incompatible) + b = mynotbottommethod(); + } } diff --git a/framework/tests/viewpointtest/LostNonReflexive.java b/framework/tests/viewpointtest/LostNonReflexive.java index eacc4c9b132..f09c4acaf99 100644 --- a/framework/tests/viewpointtest/LostNonReflexive.java +++ b/framework/tests/viewpointtest/LostNonReflexive.java @@ -1,36 +1,36 @@ import viewpointtest.quals.*; public class LostNonReflexive { - @ReceiverDependentQual Object f; - - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - @ReceiverDependentQual LostNonReflexive(@ReceiverDependentQual Object args) {} - - @ReceiverDependentQual Object get() { - return null; - } - - void set(@ReceiverDependentQual Object o) {} - - void test(@Top LostNonReflexive obj, @Bottom Object bottomObj) { - // :: error: (assignment.type.incompatible) - this.f = obj.f; - this.f = bottomObj; - - // :: error: (assignment.type.incompatible) - @A Object aObj = obj.get(); - // :: error: (assignment.type.incompatible) - @B Object bObj = obj.get(); - // :: error: (assignment.type.incompatible) - @Bottom Object botObj = obj.get(); - - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - new LostNonReflexive(obj.f); - // :: error: (new.class.type.invalid) - new LostNonReflexive(bottomObj); - - // :: error: (argument.type.incompatible) - this.set(obj.f); - this.set(bottomObj); - } + @ReceiverDependentQual Object f; + + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @ReceiverDependentQual LostNonReflexive(@ReceiverDependentQual Object args) {} + + @ReceiverDependentQual Object get() { + return null; + } + + void set(@ReceiverDependentQual Object o) {} + + void test(@Top LostNonReflexive obj, @Bottom Object bottomObj) { + // :: error: (assignment.type.incompatible) + this.f = obj.f; + this.f = bottomObj; + + // :: error: (assignment.type.incompatible) + @A Object aObj = obj.get(); + // :: error: (assignment.type.incompatible) + @B Object bObj = obj.get(); + // :: error: (assignment.type.incompatible) + @Bottom Object botObj = obj.get(); + + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + new LostNonReflexive(obj.f); + // :: error: (new.class.type.invalid) + new LostNonReflexive(bottomObj); + + // :: error: (argument.type.incompatible) + this.set(obj.f); + this.set(bottomObj); + } } diff --git a/framework/tests/viewpointtest/PolyConstructor.java b/framework/tests/viewpointtest/PolyConstructor.java index eec0533b8ae..c66f17dd03e 100644 --- a/framework/tests/viewpointtest/PolyConstructor.java +++ b/framework/tests/viewpointtest/PolyConstructor.java @@ -2,53 +2,53 @@ public class PolyConstructor { - static class MyClass { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @PolyVP MyClass(@PolyVP Object o) { - // :: error: (new.class.type.invalid) - throw new RuntimeException(" * You are filled with DETERMINATION."); // stub - } - - void throwTopException() { - // :: error: (new.class.type.invalid) - throw new @Top RuntimeException(); - } - - void throwBottomException() { - // :: warning: (cast.unsafe.constructor.invocation) - throw new @Bottom RuntimeException(); - } - - void throwAException() { - // :: warning: (cast.unsafe.constructor.invocation) - throw new @A RuntimeException(); - } - - void throwBException() { - // :: warning: (cast.unsafe.constructor.invocation) - throw new @B RuntimeException(); - } - - void throwLostException() { - // :: error: (new.class.type.invalid) :: warning: (cast.unsafe.constructor.invocation) - throw new @Lost RuntimeException(); - } + static class MyClass { + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @PolyVP MyClass(@PolyVP Object o) { + // :: error: (new.class.type.invalid) + throw new RuntimeException(" * You are filled with DETERMINATION."); // stub } - void tests(@A Object ao) { - // After poly resolution, the invocation resolved to @A MyClass - @A MyClass myA = new MyClass(ao); - // :: error: (assignment.type.incompatible) - @B MyClass myB = new MyClass(ao); - - // Both argument "ao" and @B are parts of poly resolution - // After poly resolution, the invocation resolved to @Top MyClass then casted to @B - // The @B acts as a downcasting and will issue a warning - // :: warning: (cast.unsafe.constructor.invocation) - MyClass myTop = new @B MyClass(ao); - // :: warning: (cast.unsafe.constructor.invocation) - myB = new @B MyClass(ao); - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - myA = new @B MyClass(ao); + void throwTopException() { + // :: error: (new.class.type.invalid) + throw new @Top RuntimeException(); } + + void throwBottomException() { + // :: warning: (cast.unsafe.constructor.invocation) + throw new @Bottom RuntimeException(); + } + + void throwAException() { + // :: warning: (cast.unsafe.constructor.invocation) + throw new @A RuntimeException(); + } + + void throwBException() { + // :: warning: (cast.unsafe.constructor.invocation) + throw new @B RuntimeException(); + } + + void throwLostException() { + // :: error: (new.class.type.invalid) :: warning: (cast.unsafe.constructor.invocation) + throw new @Lost RuntimeException(); + } + } + + void tests(@A Object ao) { + // After poly resolution, the invocation resolved to @A MyClass + @A MyClass myA = new MyClass(ao); + // :: error: (assignment.type.incompatible) + @B MyClass myB = new MyClass(ao); + + // Both argument "ao" and @B are parts of poly resolution + // After poly resolution, the invocation resolved to @Top MyClass then casted to @B + // The @B acts as a downcasting and will issue a warning + // :: warning: (cast.unsafe.constructor.invocation) + MyClass myTop = new @B MyClass(ao); + // :: warning: (cast.unsafe.constructor.invocation) + myB = new @B MyClass(ao); + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + myA = new @B MyClass(ao); + } } diff --git a/framework/tests/viewpointtest/PolyWithVPA.java b/framework/tests/viewpointtest/PolyWithVPA.java index 7b61dd50005..27ddad6b37f 100644 --- a/framework/tests/viewpointtest/PolyWithVPA.java +++ b/framework/tests/viewpointtest/PolyWithVPA.java @@ -1,20 +1,20 @@ import viewpointtest.quals.*; public class PolyWithVPA { - static class PolyClass { - @ReceiverDependentQual Object foo(@PolyVP Object o) { - return null; - } + static class PolyClass { + @ReceiverDependentQual Object foo(@PolyVP Object o) { + return null; } + } - static void test1(@A PolyClass a, @B Object bObj) { - @A Object aObj = a.foo(bObj); - } + static void test1(@A PolyClass a, @B Object bObj) { + @A Object aObj = a.foo(bObj); + } - // only poly annos in decl are resolved - static void test2(@PolyVP PolyClass poly, @B Object bObj) { - @PolyVP Object polyObj = poly.foo(bObj); - // :: error: (assignment.type.incompatible) - @B Object anotherBObj = poly.foo(bObj); - } + // only poly annos in decl are resolved + static void test2(@PolyVP PolyClass poly, @B Object bObj) { + @PolyVP Object polyObj = poly.foo(bObj); + // :: error: (assignment.type.incompatible) + @B Object anotherBObj = poly.foo(bObj); + } } diff --git a/framework/tests/viewpointtest/SuperConstructorCalls.java b/framework/tests/viewpointtest/SuperConstructorCalls.java index fc0e5a66f83..a55a2281796 100644 --- a/framework/tests/viewpointtest/SuperConstructorCalls.java +++ b/framework/tests/viewpointtest/SuperConstructorCalls.java @@ -4,42 +4,42 @@ public class SuperConstructorCalls { - public SuperConstructorCalls() {} - - public SuperConstructorCalls(@ReceiverDependentQual Object obj) {} - - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - public @ReceiverDependentQual SuperConstructorCalls( - @ReceiverDependentQual Object obj, int dummy) {} - - class Inner extends SuperConstructorCalls { - public Inner() { - super(); - } - - // The constructor's return type is implicitly @Top by default. - // When calling the super constructor, @Top becomes @Lost in the super constructor's - // signature, causing a type mismatch with the expected @ReceiverDependentQual parameter. - public Inner(@Top Object objTop) { - // :: error: (argument.type.incompatible) - super(objTop); - } - - @SuppressWarnings("inconsistent.constructor.type") - public @A Inner(@A Object objA, int dummy) { - super(objA, 0); - } - - @SuppressWarnings("inconsistent.constructor.type") - public @A Inner(@A Object objA, @B Object objB) { - // :: error: (super.invocation.invalid) - super(objA); - } - - @SuppressWarnings("inconsistent.constructor.type") - public @A Inner(@A Object objA, @B Object objB, int dummy) { - // :: error: (argument.type.incompatible) - super(objB, 0); - } + public SuperConstructorCalls() {} + + public SuperConstructorCalls(@ReceiverDependentQual Object obj) {} + + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + public @ReceiverDependentQual SuperConstructorCalls( + @ReceiverDependentQual Object obj, int dummy) {} + + class Inner extends SuperConstructorCalls { + public Inner() { + super(); + } + + // The constructor's return type is implicitly @Top by default. + // When calling the super constructor, @Top becomes @Lost in the super constructor's + // signature, causing a type mismatch with the expected @ReceiverDependentQual parameter. + public Inner(@Top Object objTop) { + // :: error: (argument.type.incompatible) + super(objTop); + } + + @SuppressWarnings("inconsistent.constructor.type") + public @A Inner(@A Object objA, int dummy) { + super(objA, 0); + } + + @SuppressWarnings("inconsistent.constructor.type") + public @A Inner(@A Object objA, @B Object objB) { + // :: error: (super.invocation.invalid) + super(objA); + } + + @SuppressWarnings("inconsistent.constructor.type") + public @A Inner(@A Object objA, @B Object objB, int dummy) { + // :: error: (argument.type.incompatible) + super(objB, 0); } + } } diff --git a/framework/tests/viewpointtest/TestGetAnnotatedLhs.java b/framework/tests/viewpointtest/TestGetAnnotatedLhs.java index bf2ee83a54c..807f61f75bb 100644 --- a/framework/tests/viewpointtest/TestGetAnnotatedLhs.java +++ b/framework/tests/viewpointtest/TestGetAnnotatedLhs.java @@ -4,41 +4,41 @@ import viewpointtest.quals.Top; @ReceiverDependentQual class TestGetAnnotatedLhs { - @ReceiverDependentQual Object f; + @ReceiverDependentQual Object f; - @SuppressWarnings({ - "inconsistent.constructor.type", - "super.invocation.invalid", - "cast.unsafe.constructor.invocation" - }) - @ReceiverDependentQual TestGetAnnotatedLhs() { - this.f = new @ReceiverDependentQual Object(); - } + @SuppressWarnings({ + "inconsistent.constructor.type", + "super.invocation.invalid", + "cast.unsafe.constructor.invocation" + }) + @ReceiverDependentQual TestGetAnnotatedLhs() { + this.f = new @ReceiverDependentQual Object(); + } - @SuppressWarnings({"cast.unsafe.constructor.invocation"}) - void topWithRefinement() { - TestGetAnnotatedLhs a = new @A TestGetAnnotatedLhs(); - // :: error: (new.class.type.invalid) - TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); - top = a; - // When checking the below assignment, GenericAnnotatedTypeFactory#getAnnotatedTypeLhs() - // will be called to get the type of the lhs tree (top.f). - // Previously this method will completely disable flow refinment, and top.f will have type - // @Top and thus accept the below assingment. But we should reject it, as top - // is refined to be @A before. As long as top is still pointing to the @A object, top.f - // will have type @A, which is not the supertype of @B. - // :: error: (assignment.type.incompatible) - top.f = new @B Object(); - top.f = new @A Object(); // no error here - } + @SuppressWarnings({"cast.unsafe.constructor.invocation"}) + void topWithRefinement() { + TestGetAnnotatedLhs a = new @A TestGetAnnotatedLhs(); + // :: error: (new.class.type.invalid) + TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); + top = a; + // When checking the below assignment, GenericAnnotatedTypeFactory#getAnnotatedTypeLhs() + // will be called to get the type of the lhs tree (top.f). + // Previously this method will completely disable flow refinment, and top.f will have type + // @Top and thus accept the below assingment. But we should reject it, as top + // is refined to be @A before. As long as top is still pointing to the @A object, top.f + // will have type @A, which is not the supertype of @B. + // :: error: (assignment.type.incompatible) + top.f = new @B Object(); + top.f = new @A Object(); // no error here + } - @SuppressWarnings({"cast.unsafe.constructor.invocation"}) - void topWithoutRefinement() { - // :: error: (new.class.type.invalid) - TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); - // :: error: (assignment.type.incompatible) - top.f = new @B Object(); - // :: error: (assignment.type.incompatible) - top.f = new @A Object(); - } + @SuppressWarnings({"cast.unsafe.constructor.invocation"}) + void topWithoutRefinement() { + // :: error: (new.class.type.invalid) + TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); + // :: error: (assignment.type.incompatible) + top.f = new @B Object(); + // :: error: (assignment.type.incompatible) + top.f = new @A Object(); + } } diff --git a/framework/tests/viewpointtest/ThisConstructorCalls.java b/framework/tests/viewpointtest/ThisConstructorCalls.java index 0c7567059e7..0dad4a9d0ad 100644 --- a/framework/tests/viewpointtest/ThisConstructorCalls.java +++ b/framework/tests/viewpointtest/ThisConstructorCalls.java @@ -3,28 +3,28 @@ // Test case for EISOP issue #782: // https://github.com/eisop/checker-framework/issues/782 public class ThisConstructorCalls { - public ThisConstructorCalls() {} + public ThisConstructorCalls() {} - public ThisConstructorCalls(@ReceiverDependentQual Object obj) {} + public ThisConstructorCalls(@ReceiverDependentQual Object obj) {} - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - public @ReceiverDependentQual ThisConstructorCalls( - @ReceiverDependentQual Object obj, int dummy) {} + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + public @ReceiverDependentQual ThisConstructorCalls( + @ReceiverDependentQual Object obj, int dummy) {} - @SuppressWarnings("inconsistent.constructor.type") - public @A ThisConstructorCalls(@A Object objA, int dummy1, int dummy2) { - this(objA, 0); - } + @SuppressWarnings("inconsistent.constructor.type") + public @A ThisConstructorCalls(@A Object objA, int dummy1, int dummy2) { + this(objA, 0); + } - @SuppressWarnings("inconsistent.constructor.type") - public @A ThisConstructorCalls(@B Object objB, int dummy, int dummy2, int dummy3) { - // :: error: (argument.type.incompatible) - this(objB, 0); - } + @SuppressWarnings("inconsistent.constructor.type") + public @A ThisConstructorCalls(@B Object objB, int dummy, int dummy2, int dummy3) { + // :: error: (argument.type.incompatible) + this(objB, 0); + } - @SuppressWarnings("inconsistent.constructor.type") - public @A ThisConstructorCalls(@A Object objA, @B Object objB) { - // :: error: (this.invocation.invalid) - this(objA); - } + @SuppressWarnings("inconsistent.constructor.type") + public @A ThisConstructorCalls(@A Object objA, @B Object objB) { + // :: error: (this.invocation.invalid) + this(objA); + } } diff --git a/framework/tests/viewpointtest/VPAExamples.java b/framework/tests/viewpointtest/VPAExamples.java index 8cc915c2a5e..438f40efa78 100644 --- a/framework/tests/viewpointtest/VPAExamples.java +++ b/framework/tests/viewpointtest/VPAExamples.java @@ -2,36 +2,36 @@ public class VPAExamples { - static class RDContainer { - @ReceiverDependentQual Object get() { - return null; - } + static class RDContainer { + @ReceiverDependentQual Object get() { + return null; + } - void set(@ReceiverDependentQual Object o) {} + void set(@ReceiverDependentQual Object o) {} - @ReceiverDependentQual Object field; - } + @ReceiverDependentQual Object field; + } - void tests(@A RDContainer a, @B RDContainer b, @Top RDContainer top) { - @A Object aObj = a.get(); - @B Object bObj = b.get(); - @Top Object tObj = top.get(); - // :: error: (assignment.type.incompatible) - bObj = a.get(); - // :: error: (assignment.type.incompatible) - aObj = top.get(); - // :: error: (assignment.type.incompatible) - bObj = top.get(); + void tests(@A RDContainer a, @B RDContainer b, @Top RDContainer top) { + @A Object aObj = a.get(); + @B Object bObj = b.get(); + @Top Object tObj = top.get(); + // :: error: (assignment.type.incompatible) + bObj = a.get(); + // :: error: (assignment.type.incompatible) + aObj = top.get(); + // :: error: (assignment.type.incompatible) + bObj = top.get(); - a.set(aObj); - // :: error: (argument.type.incompatible) - a.set(bObj); - // :: error: (argument.type.incompatible) - b.set(aObj); - b.set(bObj); - // :: error: (argument.type.incompatible) - top.set(aObj); - // :: error: (argument.type.incompatible) - top.set(bObj); - } + a.set(aObj); + // :: error: (argument.type.incompatible) + a.set(bObj); + // :: error: (argument.type.incompatible) + b.set(aObj); + b.set(bObj); + // :: error: (argument.type.incompatible) + top.set(aObj); + // :: error: (argument.type.incompatible) + top.set(bObj); + } } diff --git a/framework/tests/viewpointtest/VarargsConstructor.java b/framework/tests/viewpointtest/VarargsConstructor.java index bacd874f592..63436b7d91a 100644 --- a/framework/tests/viewpointtest/VarargsConstructor.java +++ b/framework/tests/viewpointtest/VarargsConstructor.java @@ -4,70 +4,70 @@ public class VarargsConstructor { - VarargsConstructor(String str, Object... args) {} + VarargsConstructor(String str, Object... args) {} - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - @ReceiverDependentQual VarargsConstructor(@ReceiverDependentQual Object... args) {} + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @ReceiverDependentQual VarargsConstructor(@ReceiverDependentQual Object... args) {} - void foo() { - // :: warning: (cast.unsafe.constructor.invocation) - VarargsConstructor a = new @A VarargsConstructor("testStr", new @A Object()); - } + void foo() { + // :: warning: (cast.unsafe.constructor.invocation) + VarargsConstructor a = new @A VarargsConstructor("testStr", new @A Object()); + } - void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { - @A Object a = new @A VarargsConstructor(aObj); - @B Object b = new @B VarargsConstructor(bObj); - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - @Top Object top = new @Top VarargsConstructor(topObj); - // :: error: (argument.type.incompatible) - new @A VarargsConstructor(bObj); - // :: error: (argument.type.incompatible) - new @B VarargsConstructor(aObj); - } + void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { + @A Object a = new @A VarargsConstructor(aObj); + @B Object b = new @B VarargsConstructor(bObj); + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + @Top Object top = new @Top VarargsConstructor(topObj); + // :: error: (argument.type.incompatible) + new @A VarargsConstructor(bObj); + // :: error: (argument.type.incompatible) + new @B VarargsConstructor(aObj); + } - class Inner { - // :: warning: (inconsistent.constructor.type) :: error:(super.invocation.invalid) - @ReceiverDependentQual Inner(@ReceiverDependentQual Object... args) {} + class Inner { + // :: warning: (inconsistent.constructor.type) :: error:(super.invocation.invalid) + @ReceiverDependentQual Inner(@ReceiverDependentQual Object... args) {} - void foo() { - // :: error: (new.class.type.invalid) - Inner a = new Inner(); - // :: warning: (cast.unsafe.constructor.invocation) - Inner b = new @A Inner(new @A Object()); - Inner c = VarargsConstructor.this.new @A Inner(); - // :: warning: (cast.unsafe.constructor.invocation) - Inner d = VarargsConstructor.this.new @A Inner(new @A Object()); - } + void foo() { + // :: error: (new.class.type.invalid) + Inner a = new Inner(); + // :: warning: (cast.unsafe.constructor.invocation) + Inner b = new @A Inner(new @A Object()); + Inner c = VarargsConstructor.this.new @A Inner(); + // :: warning: (cast.unsafe.constructor.invocation) + Inner d = VarargsConstructor.this.new @A Inner(new @A Object()); + } - void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { - @A Object a = new @A Inner(aObj); - @B Object b = new @B Inner(bObj); - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - @Top Object top = new @Top Inner(topObj); - // :: error: (argument.type.incompatible) - new @A Inner(bObj); - // :: error: (argument.type.incompatible) - new @B Inner(aObj); - } + void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { + @A Object a = new @A Inner(aObj); + @B Object b = new @B Inner(bObj); + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + @Top Object top = new @Top Inner(topObj); + // :: error: (argument.type.incompatible) + new @A Inner(bObj); + // :: error: (argument.type.incompatible) + new @B Inner(aObj); } + } - void testAnonymousClass(@A Object aObj, @B Object bObj, @Top Object topObj) { - Object o = + void testAnonymousClass(@A Object aObj, @B Object bObj, @Top Object topObj) { + Object o = + // :: warning: (cast.unsafe.constructor.invocation) + new @A VarargsConstructor("testStr", new @A Object()) { + void foo() { + VarargsConstructor a = // :: warning: (cast.unsafe.constructor.invocation) - new @A VarargsConstructor("testStr", new @A Object()) { - void foo() { - VarargsConstructor a = - // :: warning: (cast.unsafe.constructor.invocation) - new @A VarargsConstructor("testStr", new @A Object()); - } - }; - @A Object a = new @A VarargsConstructor(aObj) {}; - @B Object b = new @B VarargsConstructor(bObj) {}; - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - @Top Object top = new @Top VarargsConstructor(topObj) {}; - // :: error: (argument.type.incompatible) - new @A VarargsConstructor(bObj) {}; - // :: error: (argument.type.incompatible) - new @B VarargsConstructor(aObj) {}; - } + new @A VarargsConstructor("testStr", new @A Object()); + } + }; + @A Object a = new @A VarargsConstructor(aObj) {}; + @B Object b = new @B VarargsConstructor(bObj) {}; + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + @Top Object top = new @Top VarargsConstructor(topObj) {}; + // :: error: (argument.type.incompatible) + new @A VarargsConstructor(bObj) {}; + // :: error: (argument.type.incompatible) + new @B VarargsConstructor(aObj) {}; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java b/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java index d100ef4afd9..cf611cf9762 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java @@ -11,12 +11,8 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; - -import org.checkerframework.dataflow.qual.SideEffectFree; - import java.util.HashSet; import java.util.Set; - import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; @@ -24,6 +20,7 @@ import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * This class is an abstract annotation processor designed to be a convenient superclass for @@ -64,141 +61,139 @@ * declaration annotation phase before classes are analyzed. */ public abstract class AbstractTypeProcessor extends AbstractProcessor { - /** - * The set of fully-qualified element names that should be type-checked. We store the names of - * the elements, in order to prevent possible confusion between different Element - * instantiations. - */ - private final Set elements = new HashSet<>(); - - /** - * Method {@link #typeProcessingStart()} must be invoked exactly once, before any invocation of - * {@link #typeProcess(TypeElement, TreePath)}. - */ - private boolean hasInvokedTypeProcessingStart = false; - - /** - * Method {@link #typeProcessingOver} must be invoked exactly once, after the last invocation of - * {@link #typeProcess(TypeElement, TreePath)}. - */ - private boolean hasInvokedTypeProcessingOver = false; - - /** The TaskListener registered for completion of attribution. */ - private final AttributionTaskListener listener = new AttributionTaskListener(); - - /** Constructor for subclasses to call. */ - protected AbstractTypeProcessor() {} - - /** - * {@inheritDoc} - * - *

          Register a TaskListener that will get called after FLOW. - */ - @Override - public synchronized void init(ProcessingEnvironment env) { - super.init(env); - JavacTask.instance(env).addTaskListener(listener); - Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext(); - JavaCompiler compiler = JavaCompiler.instance(ctx); - compiler.shouldStopPolicyIfNoError = - CompileState.max(compiler.shouldStopPolicyIfNoError, CompileState.FLOW); - compiler.shouldStopPolicyIfError = - CompileState.max(compiler.shouldStopPolicyIfError, CompileState.FLOW); + /** + * The set of fully-qualified element names that should be type-checked. We store the names of the + * elements, in order to prevent possible confusion between different Element instantiations. + */ + private final Set elements = new HashSet<>(); + + /** + * Method {@link #typeProcessingStart()} must be invoked exactly once, before any invocation of + * {@link #typeProcess(TypeElement, TreePath)}. + */ + private boolean hasInvokedTypeProcessingStart = false; + + /** + * Method {@link #typeProcessingOver} must be invoked exactly once, after the last invocation of + * {@link #typeProcess(TypeElement, TreePath)}. + */ + private boolean hasInvokedTypeProcessingOver = false; + + /** The TaskListener registered for completion of attribution. */ + private final AttributionTaskListener listener = new AttributionTaskListener(); + + /** Constructor for subclasses to call. */ + protected AbstractTypeProcessor() {} + + /** + * {@inheritDoc} + * + *

          Register a TaskListener that will get called after FLOW. + */ + @Override + public synchronized void init(ProcessingEnvironment env) { + super.init(env); + JavacTask.instance(env).addTaskListener(listener); + Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext(); + JavaCompiler compiler = JavaCompiler.instance(ctx); + compiler.shouldStopPolicyIfNoError = + CompileState.max(compiler.shouldStopPolicyIfNoError, CompileState.FLOW); + compiler.shouldStopPolicyIfError = + CompileState.max(compiler.shouldStopPolicyIfError, CompileState.FLOW); + } + + /** + * The use of this method is obsolete in type processors. The method is called during declaration + * annotation processing phase only. It registers the names of elements to process. + */ + @Override + public final boolean process(Set annotations, RoundEnvironment roundEnv) { + for (TypeElement elem : ElementFilter.typesIn(roundEnv.getRootElements())) { + elements.add(elem.getQualifiedName()); } + return false; + } + + /** + * A method to be called once before the first call to typeProcess. + * + *

          Subclasses may override this method to do any initialization work. + */ + public void typeProcessingStart() {} + + /** + * Processes a fully-analyzed class that contains a supported annotation (see {@link + * #getSupportedAnnotationTypes()}). + * + *

          The passed class is always valid type-checked Java code. + * + * @param element element of the analyzed class + * @param tree the tree path to the element, with the leaf being a {@link ClassTree} + */ + public abstract void typeProcess(TypeElement element, TreePath tree); + + /** + * A method to be called once all the classes are processed. + * + *

          Subclasses may override this method to do any aggregate analysis (e.g. generate report, + * persistence) or resource deallocation. + * + *

          Method {@link #getCompilerLog()} can be used to access the number of compiler errors. + */ + public void typeProcessingOver() {} + + /** + * Return the compiler log, which contains errors and warnings. + * + * @return the compiler log, which contains errors and warnings + */ + @SideEffectFree + public Log getCompilerLog() { + return Log.instance(((JavacProcessingEnvironment) processingEnv).getContext()); + } + + /** A task listener that invokes the processor whenever a class is fully analyzed. */ + private final class AttributionTaskListener implements TaskListener { - /** - * The use of this method is obsolete in type processors. The method is called during - * declaration annotation processing phase only. It registers the names of elements to process. - */ @Override - public final boolean process( - Set annotations, RoundEnvironment roundEnv) { - for (TypeElement elem : ElementFilter.typesIn(roundEnv.getRootElements())) { - elements.add(elem.getQualifiedName()); - } - return false; - } - - /** - * A method to be called once before the first call to typeProcess. - * - *

          Subclasses may override this method to do any initialization work. - */ - public void typeProcessingStart() {} - - /** - * Processes a fully-analyzed class that contains a supported annotation (see {@link - * #getSupportedAnnotationTypes()}). - * - *

          The passed class is always valid type-checked Java code. - * - * @param element element of the analyzed class - * @param tree the tree path to the element, with the leaf being a {@link ClassTree} - */ - public abstract void typeProcess(TypeElement element, TreePath tree); - - /** - * A method to be called once all the classes are processed. - * - *

          Subclasses may override this method to do any aggregate analysis (e.g. generate report, - * persistence) or resource deallocation. - * - *

          Method {@link #getCompilerLog()} can be used to access the number of compiler errors. - */ - public void typeProcessingOver() {} - - /** - * Return the compiler log, which contains errors and warnings. - * - * @return the compiler log, which contains errors and warnings - */ - @SideEffectFree - public Log getCompilerLog() { - return Log.instance(((JavacProcessingEnvironment) processingEnv).getContext()); + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ANALYZE) { + return; + } + + if (!hasInvokedTypeProcessingStart) { + typeProcessingStart(); + hasInvokedTypeProcessingStart = true; + } + + if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { + typeProcessingOver(); + hasInvokedTypeProcessingOver = true; + } + + if (e.getTypeElement() == null) { + throw new BugInCF("event task without a type element"); + } + if (e.getCompilationUnit() == null) { + throw new BugInCF("event task without compilation unit"); + } + + if (!elements.remove(e.getTypeElement().getQualifiedName())) { + return; + } + + TypeElement elem = e.getTypeElement(); + TreePath p = Trees.instance(processingEnv).getPath(elem); + + typeProcess(elem, p); + + if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { + typeProcessingOver(); + hasInvokedTypeProcessingOver = true; + } } - /** A task listener that invokes the processor whenever a class is fully analyzed. */ - private final class AttributionTaskListener implements TaskListener { - - @Override - public void finished(TaskEvent e) { - if (e.getKind() != TaskEvent.Kind.ANALYZE) { - return; - } - - if (!hasInvokedTypeProcessingStart) { - typeProcessingStart(); - hasInvokedTypeProcessingStart = true; - } - - if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { - typeProcessingOver(); - hasInvokedTypeProcessingOver = true; - } - - if (e.getTypeElement() == null) { - throw new BugInCF("event task without a type element"); - } - if (e.getCompilationUnit() == null) { - throw new BugInCF("event task without compilation unit"); - } - - if (!elements.remove(e.getTypeElement().getQualifiedName())) { - return; - } - - TypeElement elem = e.getTypeElement(); - TreePath p = Trees.instance(processingEnv).getPath(elem); - - typeProcess(elem, p); - - if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { - typeProcessingOver(); - hasInvokedTypeProcessingOver = true; - } - } - - @Override - public void started(TaskEvent e) {} - } + @Override + public void started(TaskEvent e) {} + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java index 0cfe6d2a6bb..c2e421375f5 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java @@ -1,15 +1,5 @@ package org.checkerframework.javacutil; -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.plumelib.reflection.ReflectionPlume; -import org.plumelib.util.ArrayMap; -import org.plumelib.util.StringsPlume; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; @@ -18,7 +8,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -37,6 +26,15 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.plumelib.reflection.ReflectionPlume; +import org.plumelib.util.ArrayMap; +import org.plumelib.util.StringsPlume; /** * Builds an annotation mirror that may have some values. @@ -59,805 +57,784 @@ */ public class AnnotationBuilder { - /** The element utilities to use. */ - private final Elements elements; - - /** The type utilities to use. */ - private final Types types; - - /** The type element of the annotation. */ - private final TypeElement annotationElt; - - /** The type of the annotation. */ + /** The element utilities to use. */ + private final Elements elements; + + /** The type utilities to use. */ + private final Types types; + + /** The type element of the annotation. */ + private final TypeElement annotationElt; + + /** The type of the annotation. */ + private final DeclaredType annotationType; + + /** A mapping from element to AnnotationValue. */ + private final Map elementValues; + + /** + * Create a new AnnotationBuilder for the given annotation and environment (with no + * elements/fields, but they can be added later). + * + * @param env the processing environment + * @param anno the class of the annotation to build + */ + @SuppressWarnings("nullness") // getCanonicalName expected to be non-null + public AnnotationBuilder(ProcessingEnvironment env, Class anno) { + this(env, anno.getCanonicalName()); + } + + /** + * Create a new AnnotationBuilder for the given annotation name (with no elements/fields, but they + * can be added later). + * + * @param env the processing environment + * @param name the canonical name of the annotation to build + */ + public AnnotationBuilder(ProcessingEnvironment env, @FullyQualifiedName CharSequence name) { + this.elements = env.getElementUtils(); + this.types = env.getTypeUtils(); + this.annotationElt = elements.getTypeElement(name); + if (annotationElt == null) { + throw new UserError("Could not find annotation: " + name + ". Is it on the classpath?"); + } + assert annotationElt.getKind() == ElementKind.ANNOTATION_TYPE; + this.annotationType = (DeclaredType) annotationElt.asType(); + this.elementValues = new ArrayMap<>(2); // most annotations have few elements + } + + /** + * Create a new AnnotationBuilder that copies the given annotation, including its elements/fields. + * + * @param env the processing environment + * @param annotation the annotation to copy + */ + public AnnotationBuilder(ProcessingEnvironment env, AnnotationMirror annotation) { + this.elements = env.getElementUtils(); + this.types = env.getTypeUtils(); + + this.annotationType = annotation.getAnnotationType(); + this.annotationElt = (TypeElement) annotationType.asElement(); + this.elementValues = new ArrayMap<>(annotation.getElementValues()); + } + + /** + * Returns the type element of the annotation that is being built. + * + * @return the type element of the annotation that is being built + */ + public TypeElement getAnnotationElt() { + return annotationElt; + } + + /** + * Creates a mapping between element/field names and values. + * + * @param elementName the name of an element/field to initialize + * @param elementValue the initial value for the element/field + * @return a mapping from the element name to the element value + */ + public static Map elementNamesValues( + String elementName, Object elementValue) { + return Collections.singletonMap(elementName, createValue(elementValue)); + } + + /** + * Creates an {@link AnnotationMirror} that uses default values for elements/fields. + * getElementValues on the result returns default values. If any element does not have a default, + * this method throws an exception. + * + *

          Most clients should use {@link #fromName}, using a Name created by the compiler. This method + * is provided as a convenience to create an AnnotationMirror from scratch in a checker's code. + * + * @param elements the element utilities to use + * @param aClass the annotation class + * @return an {@link AnnotationMirror} of the given type + * @throws UserError if the annotation corresponding to the class could not be loaded + */ + public static AnnotationMirror fromClass(Elements elements, Class aClass) { + return fromClass(elements, aClass, Collections.emptyMap()); + } + + /** + * Creates an {@link AnnotationMirror} given by a particular annotation class and a name-to-value + * mapping for the elements/fields. + * + *

          For other elements, getElementValues on the result returns default values. If any such + * element does not have a default, this method throws an exception. + * + *

          Most clients should use {@link #fromName}, using a Name created by the compiler. This method + * is provided as a convenience to create an AnnotationMirror from scratch in a checker's code. + * + * @param elements the element utilities to use + * @param aClass the annotation class + * @param elementNamesValues the values for the annotation's elements/fields + * @return an {@link AnnotationMirror} of the given type + */ + public static AnnotationMirror fromClass( + Elements elements, + Class aClass, + Map elementNamesValues) { + String name = aClass.getCanonicalName(); + assert name != null : "@AssumeAssertion(nullness): assumption"; + AnnotationMirror res = fromName(elements, name, elementNamesValues); + if (res == null) { + String extra = + name.startsWith("org.checkerframework.") + ? "Is the class in checker-qual.jar?" + : "Is the class on the compilation classpath, which is:" + + System.lineSeparator() + + ReflectionPlume.classpathToString(); + throw new UserError("AnnotationBuilder: fromClass can't load class %s%n" + extra, name); + } + return res; + } + + /** + * Creates an {@link AnnotationMirror} given by a particular fully-qualified name. + * getElementValues on the result returns default values. If any element does not have a default, + * this method throws an exception. + * + *

          This method returns null if the annotation corresponding to the name could not be loaded. + * + * @param elements the element utilities to use + * @param name the name of the annotation to create + * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't be + * loaded + */ + public static @Nullable AnnotationMirror fromName( + Elements elements, @FullyQualifiedName CharSequence name) { + return fromName(elements, name, Collections.emptyMap()); + } + + /** + * Creates an {@link AnnotationMirror} given by a particular fully-qualified name and + * element/field values. If any element is not specified by the {@code elementValues} argument, + * the default value is used. If any such element does not have a default, this method throws an + * exception. + * + *

          This method returns null if the annotation corresponding to the name could not be loaded. + * + * @param elements the element utilities to use + * @param name the name of the annotation to create + * @param elementNamesValues the values for the annotation's elements/fields + * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't be + * loaded + */ + public static @Nullable AnnotationMirror fromName( + Elements elements, + @FullyQualifiedName CharSequence name, + Map elementNamesValues) { + TypeElement annoElt = elements.getTypeElement(name); + if (annoElt == null) { + return null; + } + if (annoElt.getKind() != ElementKind.ANNOTATION_TYPE) { + throw new BugInCF(annoElt + " is not an annotation"); + } + + DeclaredType annoType = (DeclaredType) annoElt.asType(); + if (annoType == null) { + return null; + } + + List methods = ElementFilter.methodsIn(annoElt.getEnclosedElements()); + Map elementValues = new ArrayMap<>(methods.size()); + for (ExecutableElement annoElement : methods) { + AnnotationValue elementValue = elementNamesValues.get(annoElement.getSimpleName().toString()); + if (elementValue == null) { + AnnotationValue defaultValue = annoElement.getDefaultValue(); + if (defaultValue == null) { + throw new BugInCF( + "AnnotationBuilder.fromName: no value for element %s of %s", annoElement, name); + } else { + elementValue = defaultValue; + } + } + elementValues.put(annoElement, elementValue); + } + + AnnotationMirror result = new CheckerFrameworkAnnotationMirror(annoType, elementValues); + return result; + } + + /** Whether or not {@link #build()} has been called. */ + private boolean wasBuilt = false; + + private void assertNotBuilt() { + if (wasBuilt) { + throw new BugInCF("AnnotationBuilder: error: type was already built"); + } + } + + public AnnotationMirror build() { + assertNotBuilt(); + wasBuilt = true; + return new CheckerFrameworkAnnotationMirror(annotationType, elementValues); + } + + /** + * Copies every element value from the given annotation. If an element in the given annotation + * doesn't exist in the annotation to be built, an error is raised unless the element is specified + * in {@code ignorableElements}. + * + * @param other the annotation that holds the values to be copied; need not be an annotation of + * the same type of the one being built + * @param ignorableElements the names of elements of {@code other} that can be safely dropped + */ + public void copyElementValuesFromAnnotation(AnnotationMirror other, String... ignorableElements) { + List ignorableElementsList = Arrays.asList(ignorableElements); + for (Map.Entry eltValToCopy : + other.getElementValues().entrySet()) { + Name eltNameToCopy = eltValToCopy.getKey().getSimpleName(); + if (ignorableElementsList.contains(eltNameToCopy.toString())) { + continue; + } + elementValues.put(findElement(eltNameToCopy), eltValToCopy.getValue()); + } + } + + /** + * Copies every element value from the given annotation. If an element in the given annotation + * doesn't exist in the annotation to be built, an error is raised unless the element is specified + * in {@code ignorableElements}. + * + * @param valueHolder the annotation that holds the values to be copied; must be the same type as + * the annotation being built + * @param ignorableElements the elements that can be safely dropped + */ + public void copyElementValuesFromAnnotation( + AnnotationMirror valueHolder, Collection ignorableElements) { + for (Map.Entry entry : + valueHolder.getElementValues().entrySet()) { + if (ignorableElements.contains(entry.getKey())) { + continue; + } + elementValues.put(entry.getKey(), entry.getValue()); + } + } + + /** + * Copies the specified element values from the given annotation, using the specified renaming + * map. Each value in the map must be an element name in the annotation being built. If an element + * from the given annotation is not a key in the map, it is ignored. + * + * @param valueHolder the annotation that holds the values to be copied + * @param elementNameRenaming a map from element names in {@code valueHolder} to element names of + * the annotation being built + */ + public void copyRenameElementValuesFromAnnotation( + AnnotationMirror valueHolder, Map elementNameRenaming) { + + for (Map.Entry eltValToCopy : + valueHolder.getElementValues().entrySet()) { + + String sourceName = eltValToCopy.getKey().getSimpleName().toString(); + String targetName = elementNameRenaming.get(sourceName); + if (targetName == null) { + continue; + } + elementValues.put(findElement(targetName), eltValToCopy.getValue()); + } + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, AnnotationMirror value) { + setValue(elementName, (Object) value); + return this; + } + + /** + * Set the element/field with the given name, to the given value. + * + * @param elementName the element/field name + * @param values the new value for the element/field + * @return this + */ + public AnnotationBuilder setValue(CharSequence elementName, List values) { + assertNotBuilt(); + ExecutableElement var = findElement(elementName); + return setValue(var, values); + } + + /** + * Set the element to the given value. + * + * @param element the element + * @param values the new value for the element + * @return this + */ + public AnnotationBuilder setValue( + ExecutableElement element, List values) { + assertNotBuilt(); + TypeMirror expectedType = element.getReturnType(); + if (expectedType.getKind() != TypeKind.ARRAY) { + throw new BugInCF("value is an array while expected type is not"); + } + expectedType = ((ArrayType) expectedType).getComponentType(); + + List avalues = new ArrayList<>(values.size()); + for (Object v : values) { + checkSubtype(expectedType, v); + avalues.add(createValue(v)); + } + AnnotationValue aval = createValue(avalues); + elementValues.put(element, aval); + return this; + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Object[] values) { + return setValue(elementName, Arrays.asList(values)); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Boolean value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Character value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Double value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Float value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Integer value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Long value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Short value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, String value) { + return setValue(elementName, (Object) value); + } + + /** + * Remove the element/field with the given name. Does not err if no such element/field is present. + */ + public AnnotationBuilder removeElement(CharSequence elementName) { + assertNotBuilt(); + ExecutableElement var = findElement(elementName); + elementValues.remove(var); + return this; + } + + private TypeMirror getErasedOrBoxedType(TypeMirror type) { + // See com.sun.tools.javac.code.Attribute.Class.makeClassType() + return type.getKind().isPrimitive() + ? types.boxedClass((PrimitiveType) type).asType() + : types.erasure(type); + } + + public AnnotationBuilder setValue(CharSequence elementName, TypeMirror value) { + assertNotBuilt(); + value = getErasedOrBoxedType(value); + AnnotationValue val = createValue(value); + ExecutableElement var = findElement(elementName); + // Check subtyping + if (!TypesUtils.isClass(var.getReturnType())) { + throw new BugInCF("expected " + var.getReturnType()); + } + + elementValues.put(var, val); + return this; + } + + /** + * Given a class, return the corresponding TypeMirror. + * + * @param clazz a class + * @return the TypeMirror corresponding to the given class + */ + private TypeMirror typeFromClass(Class clazz) { + return TypesUtils.typeFromClass(clazz, types, elements); + } + + public AnnotationBuilder setValue(CharSequence elementName, Class value) { + TypeMirror type = typeFromClass(value); + return setValue(elementName, getErasedOrBoxedType(type)); + } + + public AnnotationBuilder setValue(CharSequence elementName, Enum value) { + assertNotBuilt(); + VariableElement enumElt = findEnumElement(value); + return setValue(elementName, enumElt); + } + + public AnnotationBuilder setValue(CharSequence elementName, VariableElement value) { + ExecutableElement var = findElement(elementName); + if (var.getReturnType().getKind() != TypeKind.DECLARED) { + throw new BugInCF("expected a non enum: " + var.getReturnType()); + } + if (!((DeclaredType) var.getReturnType()).asElement().equals(value.getEnclosingElement())) { + throw new BugInCF("expected a different type of enum: " + value.getEnclosingElement()); + } + elementValues.put(var, createValue(value)); + return this; + } + + // Keep this version synchronized with the VariableElement[] version below + public AnnotationBuilder setValue(CharSequence elementName, Enum[] values) { + assertNotBuilt(); + + if (values.length == 0) { + setValue(elementName, Collections.emptyList()); + return this; + } + + VariableElement enumElt = findEnumElement(values[0]); + ExecutableElement var = findElement(elementName); + + TypeMirror expectedType = var.getReturnType(); + if (expectedType.getKind() != TypeKind.ARRAY) { + throw new BugInCF("expected a non array: " + var.getReturnType()); + } + + expectedType = ((ArrayType) expectedType).getComponentType(); + if (expectedType.getKind() != TypeKind.DECLARED) { + throw new BugInCF("expected a non enum component type: " + var.getReturnType()); + } + if (!((DeclaredType) expectedType).asElement().equals(enumElt.getEnclosingElement())) { + throw new BugInCF("expected a different type of enum: " + enumElt.getEnclosingElement()); + } + + List res = new ArrayList<>(values.length); + for (Enum ev : values) { + checkSubtype(expectedType, ev); + enumElt = findEnumElement(ev); + res.add(createValue(enumElt)); + } + AnnotationValue val = createValue(res); + elementValues.put(var, val); + return this; + } + + // Keep this version synchronized with the Enum[] version above. + // Which one is more useful/general? Unifying adds overhead of creating + // another array. + public AnnotationBuilder setValue(CharSequence elementName, VariableElement[] values) { + assertNotBuilt(); + ExecutableElement var = findElement(elementName); + + TypeMirror expectedType = var.getReturnType(); + if (expectedType.getKind() != TypeKind.ARRAY) { + throw new BugInCF("expected an array, but found: " + expectedType); + } + + expectedType = ((ArrayType) expectedType).getComponentType(); + if (expectedType.getKind() != TypeKind.DECLARED) { + throw new BugInCF( + "expected a declared component type, but found: " + + expectedType + + " kind: " + + expectedType.getKind()); + } + if (!types.isSameType((DeclaredType) expectedType, values[0].asType())) { + throw new BugInCF( + "expected a different declared component type: " + expectedType + " vs. " + values[0]); + } + + List res = new ArrayList<>(values.length); + for (VariableElement ev : values) { + checkSubtype(expectedType, ev); + // Is there a better way to distinguish between enums and + // references to constants? + if (ev.getConstantValue() != null) { + res.add(createValue(ev.getConstantValue())); + } else { + res.add(createValue(ev)); + } + } + AnnotationValue val = createValue(res); + elementValues.put(var, val); + return this; + } + + /** Find the VariableElement for the given enum. */ + private VariableElement findEnumElement(Enum value) { + String enumClass = value.getDeclaringClass().getCanonicalName(); + assert enumClass != null : "@AssumeAssertion(nullness): assumption"; + TypeElement enumClassElt = elements.getTypeElement(enumClass); + assert enumClassElt != null; + for (Element enumElt : enumClassElt.getEnclosedElements()) { + if (enumElt.getSimpleName().contentEquals(value.name())) { + return (VariableElement) enumElt; + } + } + throw new BugInCF("cannot be here"); + } + + private AnnotationBuilder setValue(CharSequence key, Object value) { + assertNotBuilt(); + AnnotationValue val = createValue(value); + ExecutableElement var = findElement(key); + checkSubtype(var.getReturnType(), value); + elementValues.put(var, val); + return this; + } + + public ExecutableElement findElement(CharSequence key) { + for (ExecutableElement elt : ElementFilter.methodsIn(annotationElt.getEnclosedElements())) { + if (elt.getSimpleName().contentEquals(key)) { + return elt; + } + } + throw new BugInCF("Couldn't find " + key + " element in " + annotationElt); + } + + /** + * @param expected the expected type + * @param givenValue the object whose run-time class to check + * @throws BugInCF if the type of {@code givenValue} is not the same as {@code expected} + */ + private void checkSubtype(TypeMirror expected, Object givenValue) { + if (expected.getKind().isPrimitive()) { + expected = types.boxedClass((PrimitiveType) expected).asType(); + } + + if (expected.getKind() == TypeKind.DECLARED + && TypesUtils.isClass(expected) + && givenValue instanceof TypeMirror) { + return; + } + + TypeMirror found; + boolean isSubtype; + + if (expected.getKind() == TypeKind.DECLARED + && ((DeclaredType) expected).asElement().getKind() == ElementKind.ANNOTATION_TYPE + && givenValue instanceof AnnotationMirror) { + found = ((AnnotationMirror) givenValue).getAnnotationType(); + isSubtype = ((DeclaredType) expected).asElement().equals(((DeclaredType) found).asElement()); + } else if (givenValue instanceof AnnotationMirror) { + found = ((AnnotationMirror) givenValue).getAnnotationType(); + // TODO: why is this always failing??? + isSubtype = false; + } else if (givenValue instanceof VariableElement) { + found = ((VariableElement) givenValue).asType(); + if (expected.getKind() == TypeKind.DECLARED) { + isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); + } else { + isSubtype = false; + } + } else { + String name = givenValue.getClass().getCanonicalName(); + assert name != null : "@AssumeAssertion(nullness): assumption"; + found = elements.getTypeElement(name).asType(); + isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); + } + if (!isSubtype) { + // Annotations in stub files sometimes are the same type, but Types#isSubtype fails + // anyway. + isSubtype = found.toString().equals(expected.toString()); + } + + if (!isSubtype) { + throw new BugInCF( + "given value differs from expected; " + "found: " + found + "; expected: " + expected); + } + } + + /** + * Create an AnnotationValue -- a value for an annotation element/field. + * + * @param obj the value to be stored in an annotation element/field + * @return an AnnotationValue for the given Java value + */ + private static AnnotationValue createValue(Object obj) { + return new CheckerFrameworkAnnotationValue(obj); + } + + /** Implementation of AnnotationMirror used by the Checker Framework. */ + /* default visibility to allow access from within package. */ + static class CheckerFrameworkAnnotationMirror implements AnnotationMirror { + /** The interned toString value. */ + private @Nullable @Interned String toStringVal; + + /** The annotation type. */ private final DeclaredType annotationType; - /** A mapping from element to AnnotationValue. */ + /** The element values. */ private final Map elementValues; - /** - * Create a new AnnotationBuilder for the given annotation and environment (with no - * elements/fields, but they can be added later). - * - * @param env the processing environment - * @param anno the class of the annotation to build - */ - @SuppressWarnings("nullness") // getCanonicalName expected to be non-null - public AnnotationBuilder(ProcessingEnvironment env, Class anno) { - this(env, anno.getCanonicalName()); - } - - /** - * Create a new AnnotationBuilder for the given annotation name (with no elements/fields, but - * they can be added later). - * - * @param env the processing environment - * @param name the canonical name of the annotation to build - */ - public AnnotationBuilder(ProcessingEnvironment env, @FullyQualifiedName CharSequence name) { - this.elements = env.getElementUtils(); - this.types = env.getTypeUtils(); - this.annotationElt = elements.getTypeElement(name); - if (annotationElt == null) { - throw new UserError("Could not find annotation: " + name + ". Is it on the classpath?"); - } - assert annotationElt.getKind() == ElementKind.ANNOTATION_TYPE; - this.annotationType = (DeclaredType) annotationElt.asType(); - this.elementValues = new ArrayMap<>(2); // most annotations have few elements - } - - /** - * Create a new AnnotationBuilder that copies the given annotation, including its - * elements/fields. - * - * @param env the processing environment - * @param annotation the annotation to copy - */ - public AnnotationBuilder(ProcessingEnvironment env, AnnotationMirror annotation) { - this.elements = env.getElementUtils(); - this.types = env.getTypeUtils(); - - this.annotationType = annotation.getAnnotationType(); - this.annotationElt = (TypeElement) annotationType.asElement(); - this.elementValues = new ArrayMap<>(annotation.getElementValues()); - } - - /** - * Returns the type element of the annotation that is being built. - * - * @return the type element of the annotation that is being built - */ - public TypeElement getAnnotationElt() { - return annotationElt; - } - - /** - * Creates a mapping between element/field names and values. - * - * @param elementName the name of an element/field to initialize - * @param elementValue the initial value for the element/field - * @return a mapping from the element name to the element value - */ - public static Map elementNamesValues( - String elementName, Object elementValue) { - return Collections.singletonMap(elementName, createValue(elementValue)); - } - - /** - * Creates an {@link AnnotationMirror} that uses default values for elements/fields. - * getElementValues on the result returns default values. If any element does not have a - * default, this method throws an exception. - * - *

          Most clients should use {@link #fromName}, using a Name created by the compiler. This - * method is provided as a convenience to create an AnnotationMirror from scratch in a checker's - * code. - * - * @param elements the element utilities to use - * @param aClass the annotation class - * @return an {@link AnnotationMirror} of the given type - * @throws UserError if the annotation corresponding to the class could not be loaded - */ - public static AnnotationMirror fromClass( - Elements elements, Class aClass) { - return fromClass(elements, aClass, Collections.emptyMap()); - } - - /** - * Creates an {@link AnnotationMirror} given by a particular annotation class and a - * name-to-value mapping for the elements/fields. - * - *

          For other elements, getElementValues on the result returns default values. If any such - * element does not have a default, this method throws an exception. - * - *

          Most clients should use {@link #fromName}, using a Name created by the compiler. This - * method is provided as a convenience to create an AnnotationMirror from scratch in a checker's - * code. - * - * @param elements the element utilities to use - * @param aClass the annotation class - * @param elementNamesValues the values for the annotation's elements/fields - * @return an {@link AnnotationMirror} of the given type - */ - public static AnnotationMirror fromClass( - Elements elements, - Class aClass, - Map elementNamesValues) { - String name = aClass.getCanonicalName(); - assert name != null : "@AssumeAssertion(nullness): assumption"; - AnnotationMirror res = fromName(elements, name, elementNamesValues); - if (res == null) { - String extra = - name.startsWith("org.checkerframework.") - ? "Is the class in checker-qual.jar?" - : "Is the class on the compilation classpath, which is:" - + System.lineSeparator() - + ReflectionPlume.classpathToString(); - throw new UserError("AnnotationBuilder: fromClass can't load class %s%n" + extra, name); - } - return res; - } - - /** - * Creates an {@link AnnotationMirror} given by a particular fully-qualified name. - * getElementValues on the result returns default values. If any element does not have a - * default, this method throws an exception. - * - *

          This method returns null if the annotation corresponding to the name could not be loaded. - * - * @param elements the element utilities to use - * @param name the name of the annotation to create - * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't - * be loaded - */ - public static @Nullable AnnotationMirror fromName( - Elements elements, @FullyQualifiedName CharSequence name) { - return fromName(elements, name, Collections.emptyMap()); - } - - /** - * Creates an {@link AnnotationMirror} given by a particular fully-qualified name and - * element/field values. If any element is not specified by the {@code elementValues} argument, - * the default value is used. If any such element does not have a default, this method throws an - * exception. - * - *

          This method returns null if the annotation corresponding to the name could not be loaded. - * - * @param elements the element utilities to use - * @param name the name of the annotation to create - * @param elementNamesValues the values for the annotation's elements/fields - * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't - * be loaded - */ - public static @Nullable AnnotationMirror fromName( - Elements elements, - @FullyQualifiedName CharSequence name, - Map elementNamesValues) { - TypeElement annoElt = elements.getTypeElement(name); - if (annoElt == null) { - return null; - } - if (annoElt.getKind() != ElementKind.ANNOTATION_TYPE) { - throw new BugInCF(annoElt + " is not an annotation"); - } - - DeclaredType annoType = (DeclaredType) annoElt.asType(); - if (annoType == null) { - return null; - } - - List methods = ElementFilter.methodsIn(annoElt.getEnclosedElements()); - Map elementValues = new ArrayMap<>(methods.size()); - for (ExecutableElement annoElement : methods) { - AnnotationValue elementValue = - elementNamesValues.get(annoElement.getSimpleName().toString()); - if (elementValue == null) { - AnnotationValue defaultValue = annoElement.getDefaultValue(); - if (defaultValue == null) { - throw new BugInCF( - "AnnotationBuilder.fromName: no value for element %s of %s", - annoElement, name); - } else { - elementValue = defaultValue; - } - } - elementValues.put(annoElement, elementValue); - } - - AnnotationMirror result = new CheckerFrameworkAnnotationMirror(annoType, elementValues); - return result; - } - - /** Whether or not {@link #build()} has been called. */ - private boolean wasBuilt = false; - - private void assertNotBuilt() { - if (wasBuilt) { - throw new BugInCF("AnnotationBuilder: error: type was already built"); - } - } - - public AnnotationMirror build() { - assertNotBuilt(); - wasBuilt = true; - return new CheckerFrameworkAnnotationMirror(annotationType, elementValues); - } - - /** - * Copies every element value from the given annotation. If an element in the given annotation - * doesn't exist in the annotation to be built, an error is raised unless the element is - * specified in {@code ignorableElements}. - * - * @param other the annotation that holds the values to be copied; need not be an annotation of - * the same type of the one being built - * @param ignorableElements the names of elements of {@code other} that can be safely dropped - */ - public void copyElementValuesFromAnnotation( - AnnotationMirror other, String... ignorableElements) { - List ignorableElementsList = Arrays.asList(ignorableElements); - for (Map.Entry eltValToCopy : - other.getElementValues().entrySet()) { - Name eltNameToCopy = eltValToCopy.getKey().getSimpleName(); - if (ignorableElementsList.contains(eltNameToCopy.toString())) { - continue; - } - elementValues.put(findElement(eltNameToCopy), eltValToCopy.getValue()); - } - } - - /** - * Copies every element value from the given annotation. If an element in the given annotation - * doesn't exist in the annotation to be built, an error is raised unless the element is - * specified in {@code ignorableElements}. - * - * @param valueHolder the annotation that holds the values to be copied; must be the same type - * as the annotation being built - * @param ignorableElements the elements that can be safely dropped - */ - public void copyElementValuesFromAnnotation( - AnnotationMirror valueHolder, Collection ignorableElements) { - for (Map.Entry entry : - valueHolder.getElementValues().entrySet()) { - if (ignorableElements.contains(entry.getKey())) { - continue; - } - elementValues.put(entry.getKey(), entry.getValue()); - } - } - - /** - * Copies the specified element values from the given annotation, using the specified renaming - * map. Each value in the map must be an element name in the annotation being built. If an - * element from the given annotation is not a key in the map, it is ignored. - * - * @param valueHolder the annotation that holds the values to be copied - * @param elementNameRenaming a map from element names in {@code valueHolder} to element names - * of the annotation being built - */ - public void copyRenameElementValuesFromAnnotation( - AnnotationMirror valueHolder, Map elementNameRenaming) { - - for (Map.Entry eltValToCopy : - valueHolder.getElementValues().entrySet()) { - - String sourceName = eltValToCopy.getKey().getSimpleName().toString(); - String targetName = elementNameRenaming.get(sourceName); - if (targetName == null) { - continue; - } - elementValues.put(findElement(targetName), eltValToCopy.getValue()); - } - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, AnnotationMirror value) { - setValue(elementName, (Object) value); - return this; - } - - /** - * Set the element/field with the given name, to the given value. - * - * @param elementName the element/field name - * @param values the new value for the element/field - * @return this - */ - public AnnotationBuilder setValue(CharSequence elementName, List values) { - assertNotBuilt(); - ExecutableElement var = findElement(elementName); - return setValue(var, values); - } - - /** - * Set the element to the given value. - * - * @param element the element - * @param values the new value for the element - * @return this - */ - public AnnotationBuilder setValue( - ExecutableElement element, List values) { - assertNotBuilt(); - TypeMirror expectedType = element.getReturnType(); - if (expectedType.getKind() != TypeKind.ARRAY) { - throw new BugInCF("value is an array while expected type is not"); - } - expectedType = ((ArrayType) expectedType).getComponentType(); - - List avalues = new ArrayList<>(values.size()); - for (Object v : values) { - checkSubtype(expectedType, v); - avalues.add(createValue(v)); - } - AnnotationValue aval = createValue(avalues); - elementValues.put(element, aval); - return this; - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Object[] values) { - return setValue(elementName, Arrays.asList(values)); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Boolean value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Character value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Double value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Float value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Integer value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Long value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Short value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, String value) { - return setValue(elementName, (Object) value); - } - - /** - * Remove the element/field with the given name. Does not err if no such element/field is - * present. - */ - public AnnotationBuilder removeElement(CharSequence elementName) { - assertNotBuilt(); - ExecutableElement var = findElement(elementName); - elementValues.remove(var); - return this; - } - - private TypeMirror getErasedOrBoxedType(TypeMirror type) { - // See com.sun.tools.javac.code.Attribute.Class.makeClassType() - return type.getKind().isPrimitive() - ? types.boxedClass((PrimitiveType) type).asType() - : types.erasure(type); - } - - public AnnotationBuilder setValue(CharSequence elementName, TypeMirror value) { - assertNotBuilt(); - value = getErasedOrBoxedType(value); - AnnotationValue val = createValue(value); - ExecutableElement var = findElement(elementName); - // Check subtyping - if (!TypesUtils.isClass(var.getReturnType())) { - throw new BugInCF("expected " + var.getReturnType()); - } - - elementValues.put(var, val); - return this; - } + /** The annotation name. */ + // default visibility to allow access from within package. + final @Interned @CanonicalName String annotationName; /** - * Given a class, return the corresponding TypeMirror. + * Create a CheckerFrameworkAnnotationMirror. * - * @param clazz a class - * @return the TypeMirror corresponding to the given class + * @param annotationType the annotation type + * @param elementValues the element values */ - private TypeMirror typeFromClass(Class clazz) { - return TypesUtils.typeFromClass(clazz, types, elements); - } - - public AnnotationBuilder setValue(CharSequence elementName, Class value) { - TypeMirror type = typeFromClass(value); - return setValue(elementName, getErasedOrBoxedType(type)); - } - - public AnnotationBuilder setValue(CharSequence elementName, Enum value) { - assertNotBuilt(); - VariableElement enumElt = findEnumElement(value); - return setValue(elementName, enumElt); - } - - public AnnotationBuilder setValue(CharSequence elementName, VariableElement value) { - ExecutableElement var = findElement(elementName); - if (var.getReturnType().getKind() != TypeKind.DECLARED) { - throw new BugInCF("expected a non enum: " + var.getReturnType()); - } - if (!((DeclaredType) var.getReturnType()).asElement().equals(value.getEnclosingElement())) { - throw new BugInCF("expected a different type of enum: " + value.getEnclosingElement()); - } - elementValues.put(var, createValue(value)); - return this; - } - - // Keep this version synchronized with the VariableElement[] version below - public AnnotationBuilder setValue(CharSequence elementName, Enum[] values) { - assertNotBuilt(); - - if (values.length == 0) { - setValue(elementName, Collections.emptyList()); - return this; - } - - VariableElement enumElt = findEnumElement(values[0]); - ExecutableElement var = findElement(elementName); - - TypeMirror expectedType = var.getReturnType(); - if (expectedType.getKind() != TypeKind.ARRAY) { - throw new BugInCF("expected a non array: " + var.getReturnType()); - } - - expectedType = ((ArrayType) expectedType).getComponentType(); - if (expectedType.getKind() != TypeKind.DECLARED) { - throw new BugInCF("expected a non enum component type: " + var.getReturnType()); - } - if (!((DeclaredType) expectedType).asElement().equals(enumElt.getEnclosingElement())) { - throw new BugInCF( - "expected a different type of enum: " + enumElt.getEnclosingElement()); - } - - List res = new ArrayList<>(values.length); - for (Enum ev : values) { - checkSubtype(expectedType, ev); - enumElt = findEnumElement(ev); - res.add(createValue(enumElt)); - } - AnnotationValue val = createValue(res); - elementValues.put(var, val); - return this; - } - - // Keep this version synchronized with the Enum[] version above. - // Which one is more useful/general? Unifying adds overhead of creating - // another array. - public AnnotationBuilder setValue(CharSequence elementName, VariableElement[] values) { - assertNotBuilt(); - ExecutableElement var = findElement(elementName); - - TypeMirror expectedType = var.getReturnType(); - if (expectedType.getKind() != TypeKind.ARRAY) { - throw new BugInCF("expected an array, but found: " + expectedType); - } - - expectedType = ((ArrayType) expectedType).getComponentType(); - if (expectedType.getKind() != TypeKind.DECLARED) { - throw new BugInCF( - "expected a declared component type, but found: " - + expectedType - + " kind: " - + expectedType.getKind()); - } - if (!types.isSameType((DeclaredType) expectedType, values[0].asType())) { - throw new BugInCF( - "expected a different declared component type: " - + expectedType - + " vs. " - + values[0]); - } - - List res = new ArrayList<>(values.length); - for (VariableElement ev : values) { - checkSubtype(expectedType, ev); - // Is there a better way to distinguish between enums and - // references to constants? - if (ev.getConstantValue() != null) { - res.add(createValue(ev.getConstantValue())); - } else { - res.add(createValue(ev)); - } - } - AnnotationValue val = createValue(res); - elementValues.put(var, val); - return this; - } - - /** Find the VariableElement for the given enum. */ - private VariableElement findEnumElement(Enum value) { - String enumClass = value.getDeclaringClass().getCanonicalName(); - assert enumClass != null : "@AssumeAssertion(nullness): assumption"; - TypeElement enumClassElt = elements.getTypeElement(enumClass); - assert enumClassElt != null; - for (Element enumElt : enumClassElt.getEnclosedElements()) { - if (enumElt.getSimpleName().contentEquals(value.name())) { - return (VariableElement) enumElt; - } - } - throw new BugInCF("cannot be here"); - } - - private AnnotationBuilder setValue(CharSequence key, Object value) { - assertNotBuilt(); - AnnotationValue val = createValue(value); - ExecutableElement var = findElement(key); - checkSubtype(var.getReturnType(), value); - elementValues.put(var, val); - return this; - } - - public ExecutableElement findElement(CharSequence key) { - for (ExecutableElement elt : ElementFilter.methodsIn(annotationElt.getEnclosedElements())) { - if (elt.getSimpleName().contentEquals(key)) { - return elt; - } - } - throw new BugInCF("Couldn't find " + key + " element in " + annotationElt); - } - - /** - * @param expected the expected type - * @param givenValue the object whose run-time class to check - * @throws BugInCF if the type of {@code givenValue} is not the same as {@code expected} - */ - private void checkSubtype(TypeMirror expected, Object givenValue) { - if (expected.getKind().isPrimitive()) { - expected = types.boxedClass((PrimitiveType) expected).asType(); - } - - if (expected.getKind() == TypeKind.DECLARED - && TypesUtils.isClass(expected) - && givenValue instanceof TypeMirror) { - return; - } - - TypeMirror found; - boolean isSubtype; - - if (expected.getKind() == TypeKind.DECLARED - && ((DeclaredType) expected).asElement().getKind() == ElementKind.ANNOTATION_TYPE - && givenValue instanceof AnnotationMirror) { - found = ((AnnotationMirror) givenValue).getAnnotationType(); - isSubtype = - ((DeclaredType) expected) - .asElement() - .equals(((DeclaredType) found).asElement()); - } else if (givenValue instanceof AnnotationMirror) { - found = ((AnnotationMirror) givenValue).getAnnotationType(); - // TODO: why is this always failing??? - isSubtype = false; - } else if (givenValue instanceof VariableElement) { - found = ((VariableElement) givenValue).asType(); - if (expected.getKind() == TypeKind.DECLARED) { - isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); - } else { - isSubtype = false; - } - } else { - String name = givenValue.getClass().getCanonicalName(); - assert name != null : "@AssumeAssertion(nullness): assumption"; - found = elements.getTypeElement(name).asType(); - isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); - } - if (!isSubtype) { - // Annotations in stub files sometimes are the same type, but Types#isSubtype fails - // anyway. - isSubtype = found.toString().equals(expected.toString()); - } - - if (!isSubtype) { - throw new BugInCF( - "given value differs from expected; " - + "found: " - + found - + "; expected: " - + expected); - } - } - - /** - * Create an AnnotationValue -- a value for an annotation element/field. - * - * @param obj the value to be stored in an annotation element/field - * @return an AnnotationValue for the given Java value - */ - private static AnnotationValue createValue(Object obj) { - return new CheckerFrameworkAnnotationValue(obj); - } - - /** Implementation of AnnotationMirror used by the Checker Framework. */ - /* default visibility to allow access from within package. */ - static class CheckerFrameworkAnnotationMirror implements AnnotationMirror { - /** The interned toString value. */ - private @Nullable @Interned String toStringVal; - - /** The annotation type. */ - private final DeclaredType annotationType; - - /** The element values. */ - private final Map elementValues; - - /** The annotation name. */ - // default visibility to allow access from within package. - final @Interned @CanonicalName String annotationName; - - /** - * Create a CheckerFrameworkAnnotationMirror. - * - * @param annotationType the annotation type - * @param elementValues the element values - */ - @SuppressWarnings("signature:assignment.type.incompatible") // needs JDK annotations - CheckerFrameworkAnnotationMirror( - DeclaredType annotationType, - Map elementValues) { - this.annotationType = annotationType; - TypeElement elm = (TypeElement) annotationType.asElement(); - this.annotationName = elm.getQualifiedName().toString().intern(); - this.elementValues = elementValues; - } - - @Override - public DeclaredType getAnnotationType() { - return annotationType; - } - - @Override - public Map getElementValues() { - return Collections.unmodifiableMap(elementValues); - } - - @SideEffectFree - @Override - public String toString() { - if (toStringVal != null) { - return toStringVal; - } - StringBuilder buf = new StringBuilder(); - buf.append("@"); - buf.append(annotationName); - int len = elementValues.size(); - if (len > 0) { - buf.append('('); - boolean first = true; - for (Map.Entry pair : - elementValues.entrySet()) { - if (!first) { - buf.append(", "); - } - first = false; - - String name = pair.getKey().getSimpleName().toString(); - if (len > 1 || !name.equals("value")) { - buf.append(name); - buf.append('='); - } - buf.append(pair.getValue()); - } - buf.append(')'); - } - toStringVal = buf.toString().intern(); - return toStringVal; - - // return "@" + annotationType + "(" + elementValues + ")"; - } - } - - /** Implementation of AnnotationValue used by the Checker Framework. */ - private static class CheckerFrameworkAnnotationValue implements AnnotationValue { - /** The value. */ - private final Object value; - - /** The interned value of toString. */ - private @Nullable @Interned String toStringVal; - - /** Create an annotation value. */ - CheckerFrameworkAnnotationValue(Object obj) { - this.value = obj; - } - - @Override - public Object getValue() { - return value; - } - - @SideEffectFree - @Override - public String toString() { - if (this.toStringVal != null) { - return this.toStringVal; - } - String toStringVal; - if (value instanceof String) { - toStringVal = "\"" + value + "\""; - } else if (value instanceof Character) { - toStringVal = "\'" + value + "\'"; - } else if (value instanceof List) { - List list = (List) value; - toStringVal = "{" + StringsPlume.join(", ", list) + "}"; - } else if (value instanceof VariableElement) { - // for Enums - VariableElement var = (VariableElement) value; - String encl = var.getEnclosingElement().toString(); - if (!encl.isEmpty()) { - encl = encl + '.'; - } - toStringVal = encl + var; - } else if (value instanceof TypeMirror && TypesUtils.isClassType((TypeMirror) value)) { - toStringVal = value.toString() + ".class"; - } else { - toStringVal = value.toString(); - } - this.toStringVal = toStringVal.intern(); - return this.toStringVal; - } - - @SuppressWarnings("unchecked") - @Override - public R accept(AnnotationValueVisitor v, P p) { - if (value instanceof AnnotationMirror) { - return v.visitAnnotation((AnnotationMirror) value, p); - } else if (value instanceof List) { - return v.visitArray((List) value, p); - } else if (value instanceof Boolean) { - return v.visitBoolean((Boolean) value, p); - } else if (value instanceof Character) { - return v.visitChar((Character) value, p); - } else if (value instanceof Double) { - return v.visitDouble((Double) value, p); - } else if (value instanceof VariableElement) { - return v.visitEnumConstant((VariableElement) value, p); - } else if (value instanceof Float) { - return v.visitFloat((Float) value, p); - } else if (value instanceof Integer) { - return v.visitInt((Integer) value, p); - } else if (value instanceof Long) { - return v.visitLong((Long) value, p); - } else if (value instanceof Short) { - return v.visitShort((Short) value, p); - } else if (value instanceof String) { - return v.visitString((String) value, p); - } else if (value instanceof TypeMirror) { - return v.visitType((TypeMirror) value, p); - } else { - assert false : " unknown type : " + v.getClass(); - return v.visitUnknown(this, p); - } - } - - @Override - public boolean equals(@Nullable Object obj) { - // System.out.printf("Calling CFAV.equals()%n"); - if (!(obj instanceof AnnotationValue)) { - return false; - } - AnnotationValue other = (AnnotationValue) obj; - return Objects.equals(this.getValue(), other.getValue()); - } - - @Override - public int hashCode() { - return Objects.hashCode(this.value); - } - } + @SuppressWarnings("signature:assignment.type.incompatible") // needs JDK annotations + CheckerFrameworkAnnotationMirror( + DeclaredType annotationType, Map elementValues) { + this.annotationType = annotationType; + TypeElement elm = (TypeElement) annotationType.asElement(); + this.annotationName = elm.getQualifiedName().toString().intern(); + this.elementValues = elementValues; + } + + @Override + public DeclaredType getAnnotationType() { + return annotationType; + } + + @Override + public Map getElementValues() { + return Collections.unmodifiableMap(elementValues); + } + + @SideEffectFree + @Override + public String toString() { + if (toStringVal != null) { + return toStringVal; + } + StringBuilder buf = new StringBuilder(); + buf.append("@"); + buf.append(annotationName); + int len = elementValues.size(); + if (len > 0) { + buf.append('('); + boolean first = true; + for (Map.Entry pair : elementValues.entrySet()) { + if (!first) { + buf.append(", "); + } + first = false; + + String name = pair.getKey().getSimpleName().toString(); + if (len > 1 || !name.equals("value")) { + buf.append(name); + buf.append('='); + } + buf.append(pair.getValue()); + } + buf.append(')'); + } + toStringVal = buf.toString().intern(); + return toStringVal; + + // return "@" + annotationType + "(" + elementValues + ")"; + } + } + + /** Implementation of AnnotationValue used by the Checker Framework. */ + private static class CheckerFrameworkAnnotationValue implements AnnotationValue { + /** The value. */ + private final Object value; + + /** The interned value of toString. */ + private @Nullable @Interned String toStringVal; + + /** Create an annotation value. */ + CheckerFrameworkAnnotationValue(Object obj) { + this.value = obj; + } + + @Override + public Object getValue() { + return value; + } + + @SideEffectFree + @Override + public String toString() { + if (this.toStringVal != null) { + return this.toStringVal; + } + String toStringVal; + if (value instanceof String) { + toStringVal = "\"" + value + "\""; + } else if (value instanceof Character) { + toStringVal = "\'" + value + "\'"; + } else if (value instanceof List) { + List list = (List) value; + toStringVal = "{" + StringsPlume.join(", ", list) + "}"; + } else if (value instanceof VariableElement) { + // for Enums + VariableElement var = (VariableElement) value; + String encl = var.getEnclosingElement().toString(); + if (!encl.isEmpty()) { + encl = encl + '.'; + } + toStringVal = encl + var; + } else if (value instanceof TypeMirror && TypesUtils.isClassType((TypeMirror) value)) { + toStringVal = value.toString() + ".class"; + } else { + toStringVal = value.toString(); + } + this.toStringVal = toStringVal.intern(); + return this.toStringVal; + } + + @SuppressWarnings("unchecked") + @Override + public R accept(AnnotationValueVisitor v, P p) { + if (value instanceof AnnotationMirror) { + return v.visitAnnotation((AnnotationMirror) value, p); + } else if (value instanceof List) { + return v.visitArray((List) value, p); + } else if (value instanceof Boolean) { + return v.visitBoolean((Boolean) value, p); + } else if (value instanceof Character) { + return v.visitChar((Character) value, p); + } else if (value instanceof Double) { + return v.visitDouble((Double) value, p); + } else if (value instanceof VariableElement) { + return v.visitEnumConstant((VariableElement) value, p); + } else if (value instanceof Float) { + return v.visitFloat((Float) value, p); + } else if (value instanceof Integer) { + return v.visitInt((Integer) value, p); + } else if (value instanceof Long) { + return v.visitLong((Long) value, p); + } else if (value instanceof Short) { + return v.visitShort((Short) value, p); + } else if (value instanceof String) { + return v.visitString((String) value, p); + } else if (value instanceof TypeMirror) { + return v.visitType((TypeMirror) value, p); + } else { + assert false : " unknown type : " + v.getClass(); + return v.visitUnknown(this, p); + } + } + + @Override + public boolean equals(@Nullable Object obj) { + // System.out.printf("Calling CFAV.equals()%n"); + if (!(obj instanceof AnnotationValue)) { + return false; + } + AnnotationValue other = (AnnotationValue) obj; + return Objects.equals(this.getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.value); + } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java index a7517bce8e0..b683b3862e6 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java @@ -1,16 +1,14 @@ package org.checkerframework.javacutil; -import org.checkerframework.checker.nullness.qual.KeyFor; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; - import java.util.Collection; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; - import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.checker.nullness.qual.KeyFor; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; /** * The Map interface defines some of its methods with respect to the equals method. This @@ -28,153 +26,153 @@ */ public class AnnotationMirrorMap implements Map<@KeyFor("this") AnnotationMirror, V> { - /** The actual map to which all work is delegated. */ - private final NavigableMap<@KeyFor("this") AnnotationMirror, V> shadowMap = - new TreeMap<>(AnnotationUtils::compareAnnotationMirrors); - - /** Default constructor. */ - public AnnotationMirrorMap() {} - - /** - * Creates an annotation mirror map and adds all the mappings in {@code copy}. - * - * @param copy a map whose contents should be copied to the newly created map - */ - @SuppressWarnings("nullness:method.invocation") // initialization in constructor - public AnnotationMirrorMap(Map copy) { - this(); - this.putAll(copy); - } - - @Override - public int size() { - return shadowMap.size(); - } - - @Override - public boolean isEmpty() { - return shadowMap.isEmpty(); - } - - @SuppressWarnings("keyfor:contracts.conditional.postcondition") // delegation - @Override - public boolean containsKey(Object key) { - if (key instanceof AnnotationMirror) { - return AnnotationUtils.containsSame(shadowMap.keySet(), (AnnotationMirror) key); + /** The actual map to which all work is delegated. */ + private final NavigableMap<@KeyFor("this") AnnotationMirror, V> shadowMap = + new TreeMap<>(AnnotationUtils::compareAnnotationMirrors); + + /** Default constructor. */ + public AnnotationMirrorMap() {} + + /** + * Creates an annotation mirror map and adds all the mappings in {@code copy}. + * + * @param copy a map whose contents should be copied to the newly created map + */ + @SuppressWarnings("nullness:method.invocation") // initialization in constructor + public AnnotationMirrorMap(Map copy) { + this(); + this.putAll(copy); + } + + @Override + public int size() { + return shadowMap.size(); + } + + @Override + public boolean isEmpty() { + return shadowMap.isEmpty(); + } + + @SuppressWarnings("keyfor:contracts.conditional.postcondition") // delegation + @Override + public boolean containsKey(Object key) { + if (key instanceof AnnotationMirror) { + return AnnotationUtils.containsSame(shadowMap.keySet(), (AnnotationMirror) key); + } else { + return false; + } + } + + @Override + public boolean containsValue(Object value) { + return shadowMap.containsValue(value); + } + + @Override + @Pure + public @Nullable V get(Object key) { + if (key instanceof AnnotationMirror) { + AnnotationMirror keyAnno = + AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); + if (keyAnno != null) { + return shadowMap.get(keyAnno); + } + } + return null; + } + + @SuppressWarnings({ + "keyfor:contracts.postcondition", + "keyfor:contracts.postcondition", + "keyfor:argument" + }) // delegation + @Override + public @Nullable V put(AnnotationMirror key, V value) { + V pre = get(key); + remove(key); + shadowMap.put(key, value); + return pre; + } + + @Override + public @Nullable V remove(Object key) { + if (key instanceof AnnotationMirror) { + AnnotationMirror keyAnno = + AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); + if (keyAnno != null) { + return shadowMap.remove(keyAnno); + } + } + return null; + } + + @Override + public void putAll(Map m) { + for (Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + shadowMap.clear(); + } + + @Override + public AnnotationMirrorSet keySet() { + return new AnnotationMirrorSet(shadowMap.keySet()); + } + + @Override + public Collection values() { + return shadowMap.values(); + } + + @SuppressWarnings("keyfor:return") // delegation + @Override + public Set> entrySet() { + return shadowMap.entrySet(); + } + + @Override + public String toString() { + return shadowMap.toString(); + } + + @Override + @Pure + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (!(o instanceof AnnotationMirrorMap)) return false; + AnnotationMirrorMap m = (AnnotationMirrorMap) o; + if (m.size() != size()) { + return false; + } + + try { + for (Entry e : entrySet()) { + AnnotationMirror key = e.getKey(); + V value = e.getValue(); + if (value == null) { + if (!(m.get(key) == null && m.containsKey(key))) return false; } else { - return false; - } - } - - @Override - public boolean containsValue(Object value) { - return shadowMap.containsValue(value); - } - - @Override - @Pure - public @Nullable V get(Object key) { - if (key instanceof AnnotationMirror) { - AnnotationMirror keyAnno = - AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); - if (keyAnno != null) { - return shadowMap.get(keyAnno); - } - } - return null; - } - - @SuppressWarnings({ - "keyfor:contracts.postcondition", - "keyfor:contracts.postcondition", - "keyfor:argument" - }) // delegation - @Override - public @Nullable V put(AnnotationMirror key, V value) { - V pre = get(key); - remove(key); - shadowMap.put(key, value); - return pre; - } - - @Override - public @Nullable V remove(Object key) { - if (key instanceof AnnotationMirror) { - AnnotationMirror keyAnno = - AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); - if (keyAnno != null) { - return shadowMap.remove(keyAnno); - } - } - return null; - } - - @Override - public void putAll(Map m) { - for (Map.Entry entry : m.entrySet()) { - put(entry.getKey(), entry.getValue()); + if (!value.equals(m.get(key))) return false; } + } + } catch (ClassCastException | NullPointerException unused) { + return false; } - @Override - public void clear() { - shadowMap.clear(); - } - - @Override - public AnnotationMirrorSet keySet() { - return new AnnotationMirrorSet(shadowMap.keySet()); - } - - @Override - public Collection values() { - return shadowMap.values(); - } - - @SuppressWarnings("keyfor:return") // delegation - @Override - public Set> entrySet() { - return shadowMap.entrySet(); - } - - @Override - public String toString() { - return shadowMap.toString(); - } - - @Override - @Pure - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (!(o instanceof AnnotationMirrorMap)) return false; - AnnotationMirrorMap m = (AnnotationMirrorMap) o; - if (m.size() != size()) { - return false; - } + return true; + } - try { - for (Entry e : entrySet()) { - AnnotationMirror key = e.getKey(); - V value = e.getValue(); - if (value == null) { - if (!(m.get(key) == null && m.containsKey(key))) return false; - } else { - if (!value.equals(m.get(key))) return false; - } - } - } catch (ClassCastException | NullPointerException unused) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int result = 0; - for (Entry entry : entrySet()) result += entry.hashCode(); - return result; - } + @Override + public int hashCode() { + int result = 0; + for (Entry entry : entrySet()) result += entry.hashCode(); + return result; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java index 608e90d8be7..dcd11fdcff2 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java @@ -1,21 +1,19 @@ package org.checkerframework.javacutil; -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.checkerframework.checker.nullness.qual.KeyFor; -import org.checkerframework.checker.nullness.qual.KeyForBottom; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.PolyNull; -import org.checkerframework.common.returnsreceiver.qual.This; -import org.plumelib.util.DeepCopyable; - import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.NavigableSet; import java.util.TreeSet; - import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.nullness.qual.KeyFor; +import org.checkerframework.checker.nullness.qual.KeyForBottom; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; +import org.checkerframework.common.returnsreceiver.qual.This; +import org.plumelib.util.DeepCopyable; /** * The Set interface defines many methods with respect to the equals method. This implementation of @@ -33,341 +31,339 @@ */ // TODO: Could extend AbstractSet to eliminate the need to implement a few methods. public class AnnotationMirrorSet - implements NavigableSet<@KeyFor("this") AnnotationMirror>, - DeepCopyable { - - /** Backing set. */ - // Not final because makeUnmodifiable() can reassign it. - private NavigableSet<@KeyFor("this") AnnotationMirror> shadowSet = - new TreeSet<>(AnnotationUtils::compareAnnotationMirrors); - - /** The canonical unmodifiable empty set. */ - private static final AnnotationMirrorSet emptySet = unmodifiableSet(Collections.emptySet()); - - /// Constructors and factory methods - - /** Default constructor. */ - public AnnotationMirrorSet() {} - - // TODO: Should this be an unmodifiable set? - /** - * Creates a new {@link AnnotationMirrorSet} that contains {@code value}. - * - * @param value the AnnotationMirror to put in the set - */ - public AnnotationMirrorSet(AnnotationMirror value) { - this.add(value); - } - - /** - * Returns a new {@link AnnotationMirrorSet} that contains the given annotation mirrors. - * - * @param annos the AnnotationMirrors to put in the set - */ - public AnnotationMirrorSet(Collection annos) { - this.addAll(annos); - } - - @SuppressWarnings("keyfor:argument") // transferring keys from one map to another - @Override - public AnnotationMirrorSet deepCopy() { - AnnotationMirrorSet result = new AnnotationMirrorSet(); - result.shadowSet.addAll(shadowSet); - return result; - } - - /** - * Make this set unmodifiable. - * - * @return this set - */ - public @This AnnotationMirrorSet makeUnmodifiable() { - shadowSet = Collections.unmodifiableNavigableSet(shadowSet); - return this; - } - - /** - * Returns a new unmodifiable {@link AnnotationMirrorSet} that contains {@code value}. - * - * @param value the AnnotationMirror to put in the set - * @return a new unmodifiable {@link AnnotationMirrorSet} that contains only {@code value} - */ - public static AnnotationMirrorSet singleton(AnnotationMirror value) { - // The implementation could be more efficient if Collections.singleton returned a - // NavigableSet. - AnnotationMirrorSet result = new AnnotationMirrorSet(); - result.add(value); - result.makeUnmodifiable(); - return result; - } - - /** - * Returns an unmodifiable AnnotationMirrorSet with the given elements. - * - * @param annos the annotation mirrors that will constitute the new unmodifiable set - * @return an unmodifiable AnnotationMirrorSet with the given elements - */ - public static AnnotationMirrorSet unmodifiableSet( - Collection annos) { - AnnotationMirrorSet result = new AnnotationMirrorSet(annos); - result.makeUnmodifiable(); - return result; - } - - /** - * Returns an empty set. - * - * @return an empty set - */ - public static AnnotationMirrorSet emptySet() { - return emptySet; - } - - /// Set methods - - @Override - public int size() { - return shadowSet.size(); - } - - @Override - public boolean isEmpty() { - return shadowSet.isEmpty(); - } - - @Override - public boolean contains( - @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, - @Nullable Object o) { - return o instanceof AnnotationMirror - && AnnotationUtils.containsSame(shadowSet, (AnnotationMirror) o); - } - - @Override - public Iterator<@KeyFor("this") AnnotationMirror> iterator() { - return shadowSet.iterator(); - } - - @Override - public Object[] toArray() { - return shadowSet.toArray(); - } - - @SuppressWarnings("nullness:toarray.nullable.elements.not.newarray") // delegation - @Override - public <@KeyForBottom T> @Nullable T[] toArray(@PolyNull T[] a) { - return shadowSet.toArray(a); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public boolean add( - @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, - AnnotationMirror annotationMirror) { - if (contains(annotationMirror)) { - return false; - } - shadowSet.add(annotationMirror); - return true; - } - - @Override - public boolean remove(@Nullable Object o) { - if (o instanceof AnnotationMirror) { - AnnotationMirror found = AnnotationUtils.getSame(shadowSet, (AnnotationMirror) o); - return found != null && shadowSet.remove(found); - } + implements NavigableSet<@KeyFor("this") AnnotationMirror>, DeepCopyable { + + /** Backing set. */ + // Not final because makeUnmodifiable() can reassign it. + private NavigableSet<@KeyFor("this") AnnotationMirror> shadowSet = + new TreeSet<>(AnnotationUtils::compareAnnotationMirrors); + + /** The canonical unmodifiable empty set. */ + private static final AnnotationMirrorSet emptySet = unmodifiableSet(Collections.emptySet()); + + /// Constructors and factory methods + + /** Default constructor. */ + public AnnotationMirrorSet() {} + + // TODO: Should this be an unmodifiable set? + /** + * Creates a new {@link AnnotationMirrorSet} that contains {@code value}. + * + * @param value the AnnotationMirror to put in the set + */ + public AnnotationMirrorSet(AnnotationMirror value) { + this.add(value); + } + + /** + * Returns a new {@link AnnotationMirrorSet} that contains the given annotation mirrors. + * + * @param annos the AnnotationMirrors to put in the set + */ + public AnnotationMirrorSet(Collection annos) { + this.addAll(annos); + } + + @SuppressWarnings("keyfor:argument") // transferring keys from one map to another + @Override + public AnnotationMirrorSet deepCopy() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.shadowSet.addAll(shadowSet); + return result; + } + + /** + * Make this set unmodifiable. + * + * @return this set + */ + public @This AnnotationMirrorSet makeUnmodifiable() { + shadowSet = Collections.unmodifiableNavigableSet(shadowSet); + return this; + } + + /** + * Returns a new unmodifiable {@link AnnotationMirrorSet} that contains {@code value}. + * + * @param value the AnnotationMirror to put in the set + * @return a new unmodifiable {@link AnnotationMirrorSet} that contains only {@code value} + */ + public static AnnotationMirrorSet singleton(AnnotationMirror value) { + // The implementation could be more efficient if Collections.singleton returned a + // NavigableSet. + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.add(value); + result.makeUnmodifiable(); + return result; + } + + /** + * Returns an unmodifiable AnnotationMirrorSet with the given elements. + * + * @param annos the annotation mirrors that will constitute the new unmodifiable set + * @return an unmodifiable AnnotationMirrorSet with the given elements + */ + public static AnnotationMirrorSet unmodifiableSet(Collection annos) { + AnnotationMirrorSet result = new AnnotationMirrorSet(annos); + result.makeUnmodifiable(); + return result; + } + + /** + * Returns an empty set. + * + * @return an empty set + */ + public static AnnotationMirrorSet emptySet() { + return emptySet; + } + + /// Set methods + + @Override + public int size() { + return shadowSet.size(); + } + + @Override + public boolean isEmpty() { + return shadowSet.isEmpty(); + } + + @Override + public boolean contains( + @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, + @Nullable Object o) { + return o instanceof AnnotationMirror + && AnnotationUtils.containsSame(shadowSet, (AnnotationMirror) o); + } + + @Override + public Iterator<@KeyFor("this") AnnotationMirror> iterator() { + return shadowSet.iterator(); + } + + @Override + public Object[] toArray() { + return shadowSet.toArray(); + } + + @SuppressWarnings("nullness:toarray.nullable.elements.not.newarray") // delegation + @Override + public <@KeyForBottom T> @Nullable T[] toArray(@PolyNull T[] a) { + return shadowSet.toArray(a); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public boolean add( + @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, + AnnotationMirror annotationMirror) { + if (contains(annotationMirror)) { + return false; + } + shadowSet.add(annotationMirror); + return true; + } + + @Override + public boolean remove(@Nullable Object o) { + if (o instanceof AnnotationMirror) { + AnnotationMirror found = AnnotationUtils.getSame(shadowSet, (AnnotationMirror) o); + return found != null && shadowSet.remove(found); + } + return false; + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { return false; - } - - @Override - public boolean containsAll(Collection c) { - for (Object o : c) { - if (!contains(o)) { - return false; - } - } - return true; - } - - @Override - public boolean addAll( - @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, - Collection c) { - boolean result = true; - for (AnnotationMirror a : c) { - if (!add(a)) { - result = false; - } - } - return result; - } - - @Override - public boolean retainAll(Collection c) { - AnnotationMirrorSet newSet = new AnnotationMirrorSet(); - for (Object o : c) { - if (contains(o)) { - assert o != null - : "@AssumeAssertion(nullness): after contains, the argument should have" - + " the element type of the set"; - newSet.add((AnnotationMirror) o); - } - } - if (newSet.size() != shadowSet.size()) { - shadowSet = newSet; - return true; - } - return false; - } - - @Override - public boolean removeAll(Collection c) { - boolean result = true; - for (Object a : c) { - if (!remove(a)) { - result = false; - } - } - return result; - } - - @Override - public void clear() { - shadowSet.clear(); - } - - @Override - public String toString() { - return shadowSet.toString(); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (!(o instanceof AnnotationMirrorSet)) { - return false; - } - AnnotationMirrorSet s = (AnnotationMirrorSet) o; - if (this.size() != s.size()) { - return false; - } - return containsAll(s); - } - - @Override - public int hashCode() { - int result = 0; - Iterator i = iterator(); - while (i.hasNext()) { - AnnotationMirror am = i.next(); - if (am != null) { - result += am.hashCode(); - } - } - return result; - } - - /// NavigableSet methods - - @SuppressWarnings({ - "interning:override.return", // looks like a bug (in interning checker) - "signature:override.return", // " - "nullness:return", // wildcard types - "keyfor:return" // comparator wildcard - }) - @Override - public Comparator comparator() { - return shadowSet.comparator(); - } - - @Override - public @KeyFor("this") AnnotationMirror first() { - return shadowSet.first(); - } - - @Override - public @KeyFor("this") AnnotationMirror last() { - return shadowSet.last(); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror lower(AnnotationMirror e) { - return shadowSet.lower(e); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror floor(AnnotationMirror e) { - return shadowSet.floor(e); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror ceiling(AnnotationMirror e) { - return shadowSet.ceiling(e); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror higher(AnnotationMirror e) { - return shadowSet.higher(e); - } - - @Override - public @Nullable @KeyFor("this") AnnotationMirror pollFirst() { - return shadowSet.pollFirst(); - } - - @Override - public @Nullable @KeyFor("this") AnnotationMirror pollLast() { - return shadowSet.pollLast(); - } - - @Override - public AnnotationMirrorSet descendingSet() { - throw new Error("Not yet implemented."); - } - - @Override - public Iterator<@KeyFor("this") AnnotationMirror> descendingIterator() { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet subSet( - AnnotationMirror fromElement, - boolean fromInclusive, - AnnotationMirror toElement, - boolean toInclusive) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet headSet(AnnotationMirror toElement, boolean inclusive) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet tailSet(AnnotationMirror fromElement, boolean inclusive) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet subSet(AnnotationMirror fromElement, AnnotationMirror toElement) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet headSet(AnnotationMirror toElement) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet tailSet(AnnotationMirror fromElement) { - throw new Error("Not yet implemented."); - } + } + } + return true; + } + + @Override + public boolean addAll( + @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, + Collection c) { + boolean result = true; + for (AnnotationMirror a : c) { + if (!add(a)) { + result = false; + } + } + return result; + } + + @Override + public boolean retainAll(Collection c) { + AnnotationMirrorSet newSet = new AnnotationMirrorSet(); + for (Object o : c) { + if (contains(o)) { + assert o != null + : "@AssumeAssertion(nullness): after contains, the argument should have" + + " the element type of the set"; + newSet.add((AnnotationMirror) o); + } + } + if (newSet.size() != shadowSet.size()) { + shadowSet = newSet; + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + boolean result = true; + for (Object a : c) { + if (!remove(a)) { + result = false; + } + } + return result; + } + + @Override + public void clear() { + shadowSet.clear(); + } + + @Override + public String toString() { + return shadowSet.toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (!(o instanceof AnnotationMirrorSet)) { + return false; + } + AnnotationMirrorSet s = (AnnotationMirrorSet) o; + if (this.size() != s.size()) { + return false; + } + return containsAll(s); + } + + @Override + public int hashCode() { + int result = 0; + Iterator i = iterator(); + while (i.hasNext()) { + AnnotationMirror am = i.next(); + if (am != null) { + result += am.hashCode(); + } + } + return result; + } + + /// NavigableSet methods + + @SuppressWarnings({ + "interning:override.return", // looks like a bug (in interning checker) + "signature:override.return", // " + "nullness:return", // wildcard types + "keyfor:return" // comparator wildcard + }) + @Override + public Comparator comparator() { + return shadowSet.comparator(); + } + + @Override + public @KeyFor("this") AnnotationMirror first() { + return shadowSet.first(); + } + + @Override + public @KeyFor("this") AnnotationMirror last() { + return shadowSet.last(); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror lower(AnnotationMirror e) { + return shadowSet.lower(e); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror floor(AnnotationMirror e) { + return shadowSet.floor(e); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror ceiling(AnnotationMirror e) { + return shadowSet.ceiling(e); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror higher(AnnotationMirror e) { + return shadowSet.higher(e); + } + + @Override + public @Nullable @KeyFor("this") AnnotationMirror pollFirst() { + return shadowSet.pollFirst(); + } + + @Override + public @Nullable @KeyFor("this") AnnotationMirror pollLast() { + return shadowSet.pollLast(); + } + + @Override + public AnnotationMirrorSet descendingSet() { + throw new Error("Not yet implemented."); + } + + @Override + public Iterator<@KeyFor("this") AnnotationMirror> descendingIterator() { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet subSet( + AnnotationMirror fromElement, + boolean fromInclusive, + AnnotationMirror toElement, + boolean toInclusive) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet headSet(AnnotationMirror toElement, boolean inclusive) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet tailSet(AnnotationMirror fromElement, boolean inclusive) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet subSet(AnnotationMirror fromElement, AnnotationMirror toElement) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet headSet(AnnotationMirror toElement) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet tailSet(AnnotationMirror fromElement) { + throw new Error("Not yet implemented."); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java index 7208c724e93..ff026b753fb 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java @@ -1,63 +1,59 @@ package org.checkerframework.javacutil; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.lang.annotation.Annotation; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.Nullable; // This class exists to break a circular dependency between the dataflow framework and // type-checkers. /** An implementation of AnnotationProvider returns annotations on Java AST elements. */ public interface AnnotationProvider { - /** - * Returns the AnnotationMirror, of the given class or an alias of it, used to annotate the - * element. Returns null if no annotation equivalent to {@code anno} exists on {@code elt}. - * - * @param elt the element - * @param anno annotation class - * @return an annotation mirror of class {@code anno} on {@code elt}, or an equivalent one, or - * null if none exists on {@code anno} - */ - @Nullable AnnotationMirror getDeclAnnotation(Element elt, Class anno); - - /** - * Return the annotation on {@code tree} that is in the hierarchy that contains the qualifier - * {@code target}. Returns null if none exists. - * - * @param tree the tree of which the annotation is returned - * @param target the class of the annotation - * @return the annotation on {@code tree} that has the class {@code target}, or null - */ - @Nullable AnnotationMirror getAnnotationMirror(Tree tree, Class target); - - /** - * Returns true if the given method is side-effect-free according to this AnnotationProvider - * — that is, if a call to the given method does not undo flow-sensitive type refinement. - * - *

          Note that this method takes account of this AnnotationProvider's semantics, whereas {@code - * org.checkerframework.dataflow.util.PurityUtils#isSideEffectFree} does not. - * - * @param methodElement a method - * @return true if a call to the method does not undo flow-sensitive type refinement - */ - boolean isSideEffectFree(ExecutableElement methodElement); - - /** - * Returns true if the given method is deterministic according to this AnnotationProvider - * — that is, if multiple calls to the given method (with the same arguments) return the - * same value. - * - *

          Note that this method takes account of this AnnotationProvider's semantics, whereas {@code - * org.checkerframework.dataflow.util.PurityUtils#isDeterministic} does not. - * - * @param methodElement a method - * @return true if multiple calls to the method (with the same arguments) return the same value - */ - boolean isDeterministic(ExecutableElement methodElement); + /** + * Returns the AnnotationMirror, of the given class or an alias of it, used to annotate the + * element. Returns null if no annotation equivalent to {@code anno} exists on {@code elt}. + * + * @param elt the element + * @param anno annotation class + * @return an annotation mirror of class {@code anno} on {@code elt}, or an equivalent one, or + * null if none exists on {@code anno} + */ + @Nullable AnnotationMirror getDeclAnnotation(Element elt, Class anno); + + /** + * Return the annotation on {@code tree} that is in the hierarchy that contains the qualifier + * {@code target}. Returns null if none exists. + * + * @param tree the tree of which the annotation is returned + * @param target the class of the annotation + * @return the annotation on {@code tree} that has the class {@code target}, or null + */ + @Nullable AnnotationMirror getAnnotationMirror(Tree tree, Class target); + + /** + * Returns true if the given method is side-effect-free according to this AnnotationProvider + * — that is, if a call to the given method does not undo flow-sensitive type refinement. + * + *

          Note that this method takes account of this AnnotationProvider's semantics, whereas {@code + * org.checkerframework.dataflow.util.PurityUtils#isSideEffectFree} does not. + * + * @param methodElement a method + * @return true if a call to the method does not undo flow-sensitive type refinement + */ + boolean isSideEffectFree(ExecutableElement methodElement); + + /** + * Returns true if the given method is deterministic according to this AnnotationProvider — + * that is, if multiple calls to the given method (with the same arguments) return the same value. + * + *

          Note that this method takes account of this AnnotationProvider's semantics, whereas {@code + * org.checkerframework.dataflow.util.PurityUtils#isDeterministic} does not. + * + * @param methodElement a method + * @return true if multiple calls to the method (with the same arguments) return the same value + */ + boolean isDeterministic(ExecutableElement methodElement); } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java index 106e53cc7cf..c48165ea4b0 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java @@ -5,20 +5,6 @@ import com.sun.source.tree.ModifiersTree; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.model.JavacElements; - -import org.checkerframework.checker.interning.qual.CompareToMethod; -import org.checkerframework.checker.interning.qual.EqualsMethod; -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror; -import org.plumelib.util.ArrayMap; -import org.plumelib.util.CollectionsPlume; - import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -35,7 +21,6 @@ import java.util.Set; import java.util.StringJoiner; import java.util.TreeSet; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ElementKind; @@ -45,6 +30,18 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.util.ElementFilter; +import org.checkerframework.checker.interning.qual.CompareToMethod; +import org.checkerframework.checker.interning.qual.EqualsMethod; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror; +import org.plumelib.util.ArrayMap; +import org.plumelib.util.CollectionsPlume; /** * A utility class for working with annotations. @@ -53,1590 +50,1543 @@ */ public class AnnotationUtils { - // Class cannot be instantiated. - private AnnotationUtils() { - throw new AssertionError("Class AnnotationUtils cannot be instantiated."); - } - - // ********************************************************************** - // Helper methods to handle annotations. mainly workaround - // AnnotationMirror.equals undesired property - // (I think the undesired property is that it's reference equality.) - // ********************************************************************** - - /** - * Returns the fully-qualified name of an annotation as a String. - * - * @param annotation the annotation whose name to return - * @return the fully-qualified name of an annotation as a String - */ - public static final @CanonicalName String annotationName(AnnotationMirror annotation) { - if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { - return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; - } - DeclaredType annoType = annotation.getAnnotationType(); - TypeElement elm = (TypeElement) annoType.asElement(); - @SuppressWarnings("signature:assignment.type.incompatible") // JDK needs annotations - @CanonicalName String name = elm.getQualifiedName().toString(); - return name; - } - - /** - * Returns the binary name of an annotation as a String. - * - * @param annotation the annotation whose binary name to return - * @return the binary name of an annotation as a String - */ - public static final @BinaryName String annotationBinaryName(AnnotationMirror annotation) { - DeclaredType annoType = annotation.getAnnotationType(); - TypeElement elm = (TypeElement) annoType.asElement(); - return ElementUtils.getBinaryName(elm); - } - - /** - * Returns true iff both annotations are of the same type and have the same annotation values. - * - *

          This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method - * returns true iff both annotations are the same and annotate the same annotation target (e.g. - * field, variable, etc) -- that is, if its arguments are the same annotation instance. - * - * @param a1 the first AnnotationMirror to compare - * @param a2 the second AnnotationMirror to compare - * @return true iff a1 and a2 are the same annotation - */ - @EqualsMethod - public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == a2) { - return true; - } - - if (!areSameByName(a1, a2)) { - return false; - } - - return sameElementValues(a1, a2); - } - - /** - * Return -1, 0, or 1 depending on whether the name of a1 is less, equal to, or greater than - * that of a2 (lexicographically). - * - * @param a1 the first AnnotationMirror to compare - * @param a2 the second AnnotationMirror to compare - * @return true iff a1 and a2 have the same annotation name - * @see #areSame(AnnotationMirror, AnnotationMirror) - */ - @EqualsMethod - public static int compareByName(AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == a2) { - return 0; - } - if (a1 == null || a2 == null) { - throw new BugInCF("Unexpected null argument: compareByName(%s, %s)", a1, a2); - } - - if (a1 instanceof CheckerFrameworkAnnotationMirror - && a2 instanceof CheckerFrameworkAnnotationMirror) { - @Interned @CanonicalName String name1 = ((CheckerFrameworkAnnotationMirror) a1).annotationName; - @Interned @CanonicalName String name2 = ((CheckerFrameworkAnnotationMirror) a2).annotationName; - if (name1 == name2) { - return 0; - } else { - return name1.compareTo(name2); - } - } - - return annotationName(a1).compareTo(annotationName(a2)); - } - - /** - * Return true iff a1 and a2 have the same annotation type. - * - * @param a1 the first AnnotationMirror to compare - * @param a2 the second AnnotationMirror to compare - * @return true iff a1 and a2 have the same annotation name - * @see #areSame(AnnotationMirror, AnnotationMirror) - */ - @EqualsMethod - public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) { - return compareByName(a1, a2) == 0; - } - - /** - * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type - * name). Values are ignored. - * - * @param am the AnnotationMirror whose name to compare - * @param aname the string to compare - * @return true if aname is the name of am - */ - public static boolean areSameByName(AnnotationMirror am, String aname) { - return aname.equals(annotationName(am)); - } - - /** - * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. - * - *

          This method is not very efficient. It is more efficient to use {@code - * AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}. - * - * @param am the AnnotationMirror whose class to compare - * @param annoClass the class to compare - * @return true if annoclass is the class of am - * @deprecated use {@code AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName} - */ - @Deprecated // for use only by the framework - public static boolean areSameByClass( - AnnotationMirror am, Class annoClass) { - String canonicalName = annoClass.getCanonicalName(); - assert canonicalName != null : "@AssumeAssertion(nullness): assumption"; - return areSameByName(am, canonicalName); - } - - /** - * Checks that two collections contain the same annotations. - * - * @param c1 the first collection to compare - * @param c2 the second collection to compare - * @return true iff c1 and c2 contain the same annotations, according to {@link - * #areSame(AnnotationMirror, AnnotationMirror)} - */ - public static boolean areSame( - Collection c1, Collection c2) { - if (c1.size() != c2.size()) { - return false; - } - if (c1.size() == 1) { - return areSame(c1.iterator().next(), c2.iterator().next()); - } - - // while loop depends on NavigableSet implementation. - AnnotationMirrorSet s1 = new AnnotationMirrorSet(); - AnnotationMirrorSet s2 = new AnnotationMirrorSet(); - s1.addAll(c1); - s2.addAll(c2); - Iterator iter1 = s1.iterator(); - Iterator iter2 = s2.iterator(); - - while (iter1.hasNext()) { - AnnotationMirror anno1 = iter1.next(); - AnnotationMirror anno2 = iter2.next(); - if (!areSame(anno1, anno2)) { - return false; - } - } - return true; - } - - /** - * Checks that the collection contains the annotation. Using Collection.contains does not always - * work, because it does not use areSame for comparison. - * - * @param c a collection of AnnotationMirrors - * @param anno the AnnotationMirror to search for in c - * @return true iff c contains anno, according to areSame - */ - public static boolean containsSame( - Collection c, AnnotationMirror anno) { - return getSame(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}. - * - * @param c a collection of AnnotationMirrors - * @param anno the AnnotationMirror to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according - * to areSame; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getSame( - Collection c, AnnotationMirror anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSame(an, anno)) { - return an; - } - } - return null; - } - - /** - * Checks that the collection contains the annotation. Using Collection.contains does not always - * work, because it does not use areSame for comparison. - * - *

          This method is not very efficient. It is more efficient to use {@code - * AnnotatedTypeFactory#containsSameByClass} or {@link #containsSameByName}. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation class to search for in c - * @return true iff c contains anno, according to areSameByClass - */ - public static boolean containsSameByClass( - Collection c, Class anno) { - return getAnnotationByClass(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. - * - *

          This method is not very efficient. It is more efficient to use {@code - * AnnotatedTypeFactory#getAnnotationByClass} or {@link #getAnnotationByName}. - * - * @param c a collection of AnnotationMirrors - * @param anno the class to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according - * to areSameByClass; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getAnnotationByClass( - Collection c, Class anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSameByClass(an, anno)) { - return an; - } - } - return null; - } - - /** - * Checks that the collection contains an annotation of the given name. Differs from using - * Collection.contains, which does not use areSameByName for comparison. - * - * @param c a collection of AnnotationMirrors - * @param anno the name to search for in c - * @return true iff c contains anno, according to areSameByName - */ - public static boolean containsSameByName( - Collection c, String anno) { - return getAnnotationByName(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that has the same name as {@code anno}. - * - * @param c a collection of AnnotationMirrors - * @param anno the name to search for in c - * @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to - * areSameByName; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getAnnotationByName( - Collection c, String anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSameByName(an, anno)) { - return an; - } - } - return null; - } - - /** - * Checks that the collection contains an annotation of the given name. Differs from using - * Collection.contains, which does not use areSameByName for comparison. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation whose name to search for in c - * @return true iff c contains anno, according to areSameByName - */ - public static boolean containsSameByName( - Collection c, AnnotationMirror anno) { - return getSameByName(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno} - * ignoring values. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation whose name to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according - * to areSameByName; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getSameByName( - Collection c, AnnotationMirror anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSameByName(an, anno)) { - return an; - } - } - return null; - } - - /** - * Provide an ordering for {@link AnnotationMirror}s. AnnotationMirrors are first compared by - * their fully-qualified names, then by their element values in order of the name of the - * element. - * - * @param a1 the first annotation - * @param a2 the second annotation - * @return an ordering over AnnotationMirrors based on their name and values - */ - public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror a2) { - int nameComparison = compareByName(a1, a2); - if (nameComparison != 0) { - return nameComparison; - } - - // The annotations have the same name, but different values, so compare values. - Map vals1 = a1.getElementValues(); - Map vals2 = a2.getElementValues(); - Set sortedElements = - new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleSignature)); - sortedElements.addAll( - ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements())); - - // getDefaultValue() returns null if the method is not an annotation interface element. - for (ExecutableElement meth : sortedElements) { - AnnotationValue aval1 = vals1.get(meth); - if (aval1 == null) { - aval1 = meth.getDefaultValue(); - } - AnnotationValue aval2 = vals2.get(meth); - if (aval2 == null) { - aval2 = meth.getDefaultValue(); - } - int result = compareAnnotationValue(aval1, aval2); - if (result != 0) { - return result; - } - } + // Class cannot be instantiated. + private AnnotationUtils() { + throw new AssertionError("Class AnnotationUtils cannot be instantiated."); + } + + // ********************************************************************** + // Helper methods to handle annotations. mainly workaround + // AnnotationMirror.equals undesired property + // (I think the undesired property is that it's reference equality.) + // ********************************************************************** + + /** + * Returns the fully-qualified name of an annotation as a String. + * + * @param annotation the annotation whose name to return + * @return the fully-qualified name of an annotation as a String + */ + public static final @CanonicalName String annotationName(AnnotationMirror annotation) { + if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { + return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; + } + DeclaredType annoType = annotation.getAnnotationType(); + TypeElement elm = (TypeElement) annoType.asElement(); + @SuppressWarnings("signature:assignment.type.incompatible") // JDK needs annotations + @CanonicalName String name = elm.getQualifiedName().toString(); + return name; + } + + /** + * Returns the binary name of an annotation as a String. + * + * @param annotation the annotation whose binary name to return + * @return the binary name of an annotation as a String + */ + public static final @BinaryName String annotationBinaryName(AnnotationMirror annotation) { + DeclaredType annoType = annotation.getAnnotationType(); + TypeElement elm = (TypeElement) annoType.asElement(); + return ElementUtils.getBinaryName(elm); + } + + /** + * Returns true iff both annotations are of the same type and have the same annotation values. + * + *

          This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method + * returns true iff both annotations are the same and annotate the same annotation target (e.g. + * field, variable, etc) -- that is, if its arguments are the same annotation instance. + * + * @param a1 the first AnnotationMirror to compare + * @param a2 the second AnnotationMirror to compare + * @return true iff a1 and a2 are the same annotation + */ + @EqualsMethod + public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == a2) { + return true; + } + + if (!areSameByName(a1, a2)) { + return false; + } + + return sameElementValues(a1, a2); + } + + /** + * Return -1, 0, or 1 depending on whether the name of a1 is less, equal to, or greater than that + * of a2 (lexicographically). + * + * @param a1 the first AnnotationMirror to compare + * @param a2 the second AnnotationMirror to compare + * @return true iff a1 and a2 have the same annotation name + * @see #areSame(AnnotationMirror, AnnotationMirror) + */ + @EqualsMethod + public static int compareByName(AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == a2) { + return 0; + } + if (a1 == null || a2 == null) { + throw new BugInCF("Unexpected null argument: compareByName(%s, %s)", a1, a2); + } + + if (a1 instanceof CheckerFrameworkAnnotationMirror + && a2 instanceof CheckerFrameworkAnnotationMirror) { + @Interned @CanonicalName String name1 = ((CheckerFrameworkAnnotationMirror) a1).annotationName; + @Interned @CanonicalName String name2 = ((CheckerFrameworkAnnotationMirror) a2).annotationName; + if (name1 == name2) { return 0; - } - - /** - * Return 0 iff the two AnnotationValue objects are the same. - * - * @param av1 the first AnnotationValue to compare - * @param av2 the second AnnotationValue to compare - * @return 0 if the two annotation values are the same - */ - @CompareToMethod - private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) { - if (av1 == av2) { - return 0; - } else if (av1 == null) { - return -1; - } else if (av2 == null) { - return 1; - } - return compareAnnotationValueValue(av1.getValue(), av2.getValue()); - } - - /** - * Compares two annotation values for order. - * - * @param val1 a value returned by {@code AnnotationValue.getValue()} - * @param val2 a value returned by {@code AnnotationValue.getValue()} - * @return a negative integer, zero, or a positive integer as the first annotation value is less - * than, equal to, or greater than the second annotation value - */ - @CompareToMethod - private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) { - if (val1 == val2) { - return 0; - } else if (val1 == null) { - return -1; - } else if (val2 == null) { - return 1; - } - // Can't use deepEquals() to compare val1 and val2, because they might have mismatched - // AnnotationValue vs. CheckerFrameworkAnnotationValue, and AnnotationValue doesn't override - // equals(). So, write my own version of deepEquals(). - if ((val1 instanceof List) && (val2 instanceof List)) { - List list1 = (List) val1; - List list2 = (List) val2; - if (list1.size() != list2.size()) { - return list1.size() - list2.size(); - } - // Don't compare setwise, because order can matter. These mean different things: - // @LTLengthOf(value={"a1","a2"}, offest={"0", "1"}) - // @LTLengthOf(value={"a2","a1"}, offest={"0", "1"}) - for (int i = 0; i < list1.size(); i++) { - Object v1 = list1.get(i); - Object v2 = list2.get(i); - int result = compareAnnotationValueValue(v1, v2); - if (result != 0) { - return result; - } - } - return 0; - } else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) { - return compareAnnotationMirrors((AnnotationMirror) val1, (AnnotationMirror) val2); - } else if ((val1 instanceof AnnotationValue) && (val2 instanceof AnnotationValue)) { - // This case occurs because of the recursive call when comparing arrays of annotation - // values. - return compareAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2); - } - - if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) { - // Type.ClassType does not override equals - if (TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2)) { - return 0; - } - } - if (Objects.equals(val1, val2)) { - return 0; - } - int result = val1.toString().compareTo(val2.toString()); - if (result == 0) { - result = -1; - } + } else { + return name1.compareTo(name2); + } + } + + return annotationName(a1).compareTo(annotationName(a2)); + } + + /** + * Return true iff a1 and a2 have the same annotation type. + * + * @param a1 the first AnnotationMirror to compare + * @param a2 the second AnnotationMirror to compare + * @return true iff a1 and a2 have the same annotation name + * @see #areSame(AnnotationMirror, AnnotationMirror) + */ + @EqualsMethod + public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) { + return compareByName(a1, a2) == 0; + } + + /** + * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type name). + * Values are ignored. + * + * @param am the AnnotationMirror whose name to compare + * @param aname the string to compare + * @return true if aname is the name of am + */ + public static boolean areSameByName(AnnotationMirror am, String aname) { + return aname.equals(annotationName(am)); + } + + /** + * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. + * + *

          This method is not very efficient. It is more efficient to use {@code + * AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}. + * + * @param am the AnnotationMirror whose class to compare + * @param annoClass the class to compare + * @return true if annoclass is the class of am + * @deprecated use {@code AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName} + */ + @Deprecated // for use only by the framework + public static boolean areSameByClass(AnnotationMirror am, Class annoClass) { + String canonicalName = annoClass.getCanonicalName(); + assert canonicalName != null : "@AssumeAssertion(nullness): assumption"; + return areSameByName(am, canonicalName); + } + + /** + * Checks that two collections contain the same annotations. + * + * @param c1 the first collection to compare + * @param c2 the second collection to compare + * @return true iff c1 and c2 contain the same annotations, according to {@link + * #areSame(AnnotationMirror, AnnotationMirror)} + */ + public static boolean areSame( + Collection c1, Collection c2) { + if (c1.size() != c2.size()) { + return false; + } + if (c1.size() == 1) { + return areSame(c1.iterator().next(), c2.iterator().next()); + } + + // while loop depends on NavigableSet implementation. + AnnotationMirrorSet s1 = new AnnotationMirrorSet(); + AnnotationMirrorSet s2 = new AnnotationMirrorSet(); + s1.addAll(c1); + s2.addAll(c2); + Iterator iter1 = s1.iterator(); + Iterator iter2 = s2.iterator(); + + while (iter1.hasNext()) { + AnnotationMirror anno1 = iter1.next(); + AnnotationMirror anno2 = iter2.next(); + if (!areSame(anno1, anno2)) { + return false; + } + } + return true; + } + + /** + * Checks that the collection contains the annotation. Using Collection.contains does not always + * work, because it does not use areSame for comparison. + * + * @param c a collection of AnnotationMirrors + * @param anno the AnnotationMirror to search for in c + * @return true iff c contains anno, according to areSame + */ + public static boolean containsSame( + Collection c, AnnotationMirror anno) { + return getSame(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}. + * + * @param c a collection of AnnotationMirrors + * @param anno the AnnotationMirror to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to + * areSame; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getSame( + Collection c, AnnotationMirror anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSame(an, anno)) { + return an; + } + } + return null; + } + + /** + * Checks that the collection contains the annotation. Using Collection.contains does not always + * work, because it does not use areSame for comparison. + * + *

          This method is not very efficient. It is more efficient to use {@code + * AnnotatedTypeFactory#containsSameByClass} or {@link #containsSameByName}. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation class to search for in c + * @return true iff c contains anno, according to areSameByClass + */ + public static boolean containsSameByClass( + Collection c, Class anno) { + return getAnnotationByClass(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. + * + *

          This method is not very efficient. It is more efficient to use {@code + * AnnotatedTypeFactory#getAnnotationByClass} or {@link #getAnnotationByName}. + * + * @param c a collection of AnnotationMirrors + * @param anno the class to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to + * areSameByClass; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getAnnotationByClass( + Collection c, Class anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameByClass(an, anno)) { + return an; + } + } + return null; + } + + /** + * Checks that the collection contains an annotation of the given name. Differs from using + * Collection.contains, which does not use areSameByName for comparison. + * + * @param c a collection of AnnotationMirrors + * @param anno the name to search for in c + * @return true iff c contains anno, according to areSameByName + */ + public static boolean containsSameByName(Collection c, String anno) { + return getAnnotationByName(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that has the same name as {@code anno}. + * + * @param c a collection of AnnotationMirrors + * @param anno the name to search for in c + * @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to + * areSameByName; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getAnnotationByName( + Collection c, String anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameByName(an, anno)) { + return an; + } + } + return null; + } + + /** + * Checks that the collection contains an annotation of the given name. Differs from using + * Collection.contains, which does not use areSameByName for comparison. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation whose name to search for in c + * @return true iff c contains anno, according to areSameByName + */ + public static boolean containsSameByName( + Collection c, AnnotationMirror anno) { + return getSameByName(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno} ignoring + * values. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation whose name to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to + * areSameByName; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getSameByName( + Collection c, AnnotationMirror anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameByName(an, anno)) { + return an; + } + } + return null; + } + + /** + * Provide an ordering for {@link AnnotationMirror}s. AnnotationMirrors are first compared by + * their fully-qualified names, then by their element values in order of the name of the element. + * + * @param a1 the first annotation + * @param a2 the second annotation + * @return an ordering over AnnotationMirrors based on their name and values + */ + public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror a2) { + int nameComparison = compareByName(a1, a2); + if (nameComparison != 0) { + return nameComparison; + } + + // The annotations have the same name, but different values, so compare values. + Map vals1 = a1.getElementValues(); + Map vals2 = a2.getElementValues(); + Set sortedElements = + new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleSignature)); + sortedElements.addAll( + ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements())); + + // getDefaultValue() returns null if the method is not an annotation interface element. + for (ExecutableElement meth : sortedElements) { + AnnotationValue aval1 = vals1.get(meth); + if (aval1 == null) { + aval1 = meth.getDefaultValue(); + } + AnnotationValue aval2 = vals2.get(meth); + if (aval2 == null) { + aval2 = meth.getDefaultValue(); + } + int result = compareAnnotationValue(aval1, aval2); + if (result != 0) { return result; - } - - /** - * Returns true if the given annotation has a @Inherited meta-annotation. - * - * @param anno the annotation to check for an @Inherited meta-annotation - * @return true if the given annotation has a @Inherited meta-annotation - */ - public static boolean hasInheritedMeta(AnnotationMirror anno) { - return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; - } - - /** - * Returns the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE. - * - * @param target a location where an annotation can be written - * @return the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE - */ - public static EnumSet getElementKindsForTarget(@Nullable Target target) { - if (target == null) { - // A missing @Target implies that the annotation can be written everywhere. - return EnumSet.allOf(ElementKind.class); - } - EnumSet eleKinds = EnumSet.noneOf(ElementKind.class); - for (ElementType elementType : target.value()) { - eleKinds.addAll(getElementKindsForElementType(elementType)); - } - return eleKinds; - } - - /** - * Returns the set of {@link ElementKind}s corresponding to {@code elementType}. If the element - * type is TYPE_USE, then ElementKinds returned should be the same as those returned for TYPE - * and TYPE_PARAMETER, but this method returns the empty set instead. - * - * @param elementType the elementType to find ElementKinds for - * @return the set of {@link ElementKind}s corresponding to {@code elementType} - */ - public static EnumSet getElementKindsForElementType(ElementType elementType) { - switch (elementType) { - case TYPE: - return EnumSet.copyOf(ElementUtils.typeElementKinds()); - case FIELD: - return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT); - case METHOD: - return EnumSet.of(ElementKind.METHOD); - case PARAMETER: - return EnumSet.of(ElementKind.PARAMETER); - case CONSTRUCTOR: - return EnumSet.of(ElementKind.CONSTRUCTOR); - case LOCAL_VARIABLE: - return EnumSet.of( - ElementKind.LOCAL_VARIABLE, - ElementKind.RESOURCE_VARIABLE, - ElementKind.EXCEPTION_PARAMETER); - case ANNOTATION_TYPE: - return EnumSet.of(ElementKind.ANNOTATION_TYPE); - case PACKAGE: - return EnumSet.of(ElementKind.PACKAGE); - case TYPE_PARAMETER: - return EnumSet.of(ElementKind.TYPE_PARAMETER); - case TYPE_USE: - return EnumSet.noneOf(ElementKind.class); - default: - // TODO: Use MODULE enum constants directly instead of looking them up by name. - // (Java 11) - if (elementType.name().equals("MODULE")) { - return EnumSet.of(ElementKind.valueOf("MODULE")); - } - if (elementType.name().equals("RECORD_COMPONENT")) { - return EnumSet.of(ElementKind.valueOf("RECORD_COMPONENT")); - } - throw new BugInCF("Unrecognized ElementType: " + elementType); + } + } + return 0; + } + + /** + * Return 0 iff the two AnnotationValue objects are the same. + * + * @param av1 the first AnnotationValue to compare + * @param av2 the second AnnotationValue to compare + * @return 0 if the two annotation values are the same + */ + @CompareToMethod + private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) { + if (av1 == av2) { + return 0; + } else if (av1 == null) { + return -1; + } else if (av2 == null) { + return 1; + } + return compareAnnotationValueValue(av1.getValue(), av2.getValue()); + } + + /** + * Compares two annotation values for order. + * + * @param val1 a value returned by {@code AnnotationValue.getValue()} + * @param val2 a value returned by {@code AnnotationValue.getValue()} + * @return a negative integer, zero, or a positive integer as the first annotation value is less + * than, equal to, or greater than the second annotation value + */ + @CompareToMethod + private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) { + if (val1 == val2) { + return 0; + } else if (val1 == null) { + return -1; + } else if (val2 == null) { + return 1; + } + // Can't use deepEquals() to compare val1 and val2, because they might have mismatched + // AnnotationValue vs. CheckerFrameworkAnnotationValue, and AnnotationValue doesn't override + // equals(). So, write my own version of deepEquals(). + if ((val1 instanceof List) && (val2 instanceof List)) { + List list1 = (List) val1; + List list2 = (List) val2; + if (list1.size() != list2.size()) { + return list1.size() - list2.size(); + } + // Don't compare setwise, because order can matter. These mean different things: + // @LTLengthOf(value={"a1","a2"}, offest={"0", "1"}) + // @LTLengthOf(value={"a2","a1"}, offest={"0", "1"}) + for (int i = 0; i < list1.size(); i++) { + Object v1 = list1.get(i); + Object v2 = list2.get(i); + int result = compareAnnotationValueValue(v1, v2); + if (result != 0) { + return result; } - } - - // ********************************************************************** - // Annotation values: inefficient extractors that take an element name - // ********************************************************************** - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}. The result - * has type {@code expectedType}. If there is no value for {@code elementName}, {@code - * defaultValue} is returned - * - *

          This method is intended only for use when the class of the annotation is not on the user's - * classpath. This is for users of the Dataflow Framework that do not use the rest of the - * Checker Framework. Type-checkers can assume that checker-qual.jar is on the classpath and - * should use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link - * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the type of the element and the return value - * @param defaultValue the value to return if the element is not present - * @param the class of the type - * @return the value of the element with the given name - */ - public static T getElementValueNotOnClasspath( - AnnotationMirror anno, - CharSequence elementName, - Class expectedType, - T defaultValue) { - Map valmap = - anno.getElementValues(); - - for (Map.Entry entry : - valmap.entrySet()) { - ExecutableElement elem = entry.getKey(); - if (elem.getSimpleName().contentEquals(elementName)) { - AnnotationValue val = entry.getValue(); - try { - return expectedType.cast(val.getValue()); - } catch (ClassCastException e) { - throw new BugInCF( - "getElementValueNotOnClasspath(%s, %s, %s): val=%s, val.getValue()=%s [%s]", - anno, - elementName, - expectedType, - val, - val.getValue(), - val.getValue().getClass()); - } - } - } - return defaultValue; - } - - /** - * Returns the values of an annotation's elements, including defaults. The method with the same - * name in JavacElements cannot be used directly, because it includes a cast to - * Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework. - * - *

          This method is intended for use only by the framework. Clients should use a method that - * takes an {@link ExecutableElement}. - * - * @see AnnotationMirror#getElementValues() - * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror) - * @param ad annotation to examine - * @return the values of the annotation's elements, including defaults - */ - private static Map - getElementValuesWithDefaults(AnnotationMirror ad) { - // Most annotations have no elements. - Map valMap = new ArrayMap<>(0); - if (ad.getElementValues() != null) { - valMap.putAll(ad.getElementValues()); - } - for (ExecutableElement meth : - ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) { - AnnotationValue defaultValue = meth.getDefaultValue(); - if (defaultValue != null) { - valMap.putIfAbsent(meth, defaultValue); - } - } - return valMap; - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}. The result - * has type {@code expectedType}. - * - *

          If the return type is an array, use {@link #getElementValueArray} instead. - * - *

          If the return type is an enum, use {@link #getElementValueEnum} instead. - * - *

          This method is intended only for use by the framework. A checker implementation should use - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link - * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the type of the element and the return value - * @param the class of the type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name - * @deprecated use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)} - */ - @Deprecated // for use only by the framework, not by clients - public static T getElementValue( - AnnotationMirror anno, - CharSequence elementName, - Class expectedType, - boolean useDefaults) { - Map valmap; - if (useDefaults) { - Map valmapTmp = - getElementValuesWithDefaults(anno); - valmap = valmapTmp; - } else { - valmap = anno.getElementValues(); - } - for (Map.Entry entry : - valmap.entrySet()) { - ExecutableElement elem = entry.getKey(); - if (elem.getSimpleName().contentEquals(elementName)) { - AnnotationValue val = entry.getValue(); - try { - return expectedType.cast(val.getValue()); - } catch (ClassCastException e) { - throw new BugInCF( - "getElementValue(%s, %s, %s, %s): val=%s, val.getValue()=%s [%s]", - anno, - elementName, - expectedType, - useDefaults, - val, - val.getValue(), - val.getValue().getClass()); - } - } + } + return 0; + } else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) { + return compareAnnotationMirrors((AnnotationMirror) val1, (AnnotationMirror) val2); + } else if ((val1 instanceof AnnotationValue) && (val2 instanceof AnnotationValue)) { + // This case occurs because of the recursive call when comparing arrays of annotation + // values. + return compareAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2); + } + + if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) { + // Type.ClassType does not override equals + if (TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2)) { + return 0; + } + } + if (Objects.equals(val1, val2)) { + return 0; + } + int result = val1.toString().compareTo(val2.toString()); + if (result == 0) { + result = -1; + } + return result; + } + + /** + * Returns true if the given annotation has a @Inherited meta-annotation. + * + * @param anno the annotation to check for an @Inherited meta-annotation + * @return true if the given annotation has a @Inherited meta-annotation + */ + public static boolean hasInheritedMeta(AnnotationMirror anno) { + return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; + } + + /** + * Returns the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE. + * + * @param target a location where an annotation can be written + * @return the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE + */ + public static EnumSet getElementKindsForTarget(@Nullable Target target) { + if (target == null) { + // A missing @Target implies that the annotation can be written everywhere. + return EnumSet.allOf(ElementKind.class); + } + EnumSet eleKinds = EnumSet.noneOf(ElementKind.class); + for (ElementType elementType : target.value()) { + eleKinds.addAll(getElementKindsForElementType(elementType)); + } + return eleKinds; + } + + /** + * Returns the set of {@link ElementKind}s corresponding to {@code elementType}. If the element + * type is TYPE_USE, then ElementKinds returned should be the same as those returned for TYPE and + * TYPE_PARAMETER, but this method returns the empty set instead. + * + * @param elementType the elementType to find ElementKinds for + * @return the set of {@link ElementKind}s corresponding to {@code elementType} + */ + public static EnumSet getElementKindsForElementType(ElementType elementType) { + switch (elementType) { + case TYPE: + return EnumSet.copyOf(ElementUtils.typeElementKinds()); + case FIELD: + return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT); + case METHOD: + return EnumSet.of(ElementKind.METHOD); + case PARAMETER: + return EnumSet.of(ElementKind.PARAMETER); + case CONSTRUCTOR: + return EnumSet.of(ElementKind.CONSTRUCTOR); + case LOCAL_VARIABLE: + return EnumSet.of( + ElementKind.LOCAL_VARIABLE, + ElementKind.RESOURCE_VARIABLE, + ElementKind.EXCEPTION_PARAMETER); + case ANNOTATION_TYPE: + return EnumSet.of(ElementKind.ANNOTATION_TYPE); + case PACKAGE: + return EnumSet.of(ElementKind.PACKAGE); + case TYPE_PARAMETER: + return EnumSet.of(ElementKind.TYPE_PARAMETER); + case TYPE_USE: + return EnumSet.noneOf(ElementKind.class); + default: + // TODO: Use MODULE enum constants directly instead of looking them up by name. + // (Java 11) + if (elementType.name().equals("MODULE")) { + return EnumSet.of(ElementKind.valueOf("MODULE")); } - throw new NoSuchElementException( - String.format( - "No element with name \'%s\' in annotation %s; useDefaults=%s," - + " valmap.keySet()=%s", - elementName, anno, useDefaults, valmap.keySet())); - } - - /** - * Differentiates NoSuchElementException from other BugInCF, for use by getElementValueOrNull. - */ - @SuppressWarnings("serial") - private static class NoSuchElementException extends BugInCF { - /** - * Constructs a new NoSuchElementException. - * - * @param message the detail message - */ - @Pure - public NoSuchElementException(String message) { - super(message); + if (elementType.name().equals("RECORD_COMPONENT")) { + return EnumSet.of(ElementKind.valueOf("RECORD_COMPONENT")); } - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}, or return - * null if no such element exists. - * - *

          This method is intended only for use by the framework. A checker implementation should use - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the type of the element and the return value - * @param the class of the type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name, or null - */ - public static @Nullable T getElementValueOrNull( - AnnotationMirror anno, - CharSequence elementName, - Class expectedType, - boolean useDefaults) { - // This implementation permits getElementValue to give a more detailed error message than if - // getElementValue called getElementValueOrNull and threw an error if the result was null. + throw new BugInCF("Unrecognized ElementType: " + elementType); + } + } + + // ********************************************************************** + // Annotation values: inefficient extractors that take an element name + // ********************************************************************** + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}. The result + * has type {@code expectedType}. If there is no value for {@code elementName}, {@code + * defaultValue} is returned + * + *

          This method is intended only for use when the class of the annotation is not on the user's + * classpath. This is for users of the Dataflow Framework that do not use the rest of the Checker + * Framework. Type-checkers can assume that checker-qual.jar is on the classpath and should use + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link + * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the type of the element and the return value + * @param defaultValue the value to return if the element is not present + * @param the class of the type + * @return the value of the element with the given name + */ + public static T getElementValueNotOnClasspath( + AnnotationMirror anno, CharSequence elementName, Class expectedType, T defaultValue) { + Map valmap = anno.getElementValues(); + + for (Map.Entry entry : + valmap.entrySet()) { + ExecutableElement elem = entry.getKey(); + if (elem.getSimpleName().contentEquals(elementName)) { + AnnotationValue val = entry.getValue(); try { - return getElementValue(anno, elementName, expectedType, useDefaults); - } catch (NoSuchElementException e) { - return null; + return expectedType.cast(val.getValue()); + } catch (ClassCastException e) { + throw new BugInCF( + "getElementValueNotOnClasspath(%s, %s, %s): val=%s, val.getValue()=%s [%s]", + anno, elementName, expectedType, val, val.getValue(), val.getValue().getClass()); } - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}, or return - * null if no such element exists. One element of the result has type {@code expectedType}. - * - *

          This method is intended only for use by the framework. A checker implementation should use - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the component type of the element and of the return value - * @param the class of the component type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name, or null - */ - public static @Nullable List getElementValueArrayOrNull( - AnnotationMirror anno, - CharSequence elementName, - Class expectedType, - boolean useDefaults) { - // This implementation permits getElementValue to give a more detailed error message than if - // getElementValue called getElementValueOrNull and threw an error if the result was null. + } + } + return defaultValue; + } + + /** + * Returns the values of an annotation's elements, including defaults. The method with the same + * name in JavacElements cannot be used directly, because it includes a cast to + * Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework. + * + *

          This method is intended for use only by the framework. Clients should use a method that + * takes an {@link ExecutableElement}. + * + * @see AnnotationMirror#getElementValues() + * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror) + * @param ad annotation to examine + * @return the values of the annotation's elements, including defaults + */ + private static Map + getElementValuesWithDefaults(AnnotationMirror ad) { + // Most annotations have no elements. + Map valMap = new ArrayMap<>(0); + if (ad.getElementValues() != null) { + valMap.putAll(ad.getElementValues()); + } + for (ExecutableElement meth : + ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) { + AnnotationValue defaultValue = meth.getDefaultValue(); + if (defaultValue != null) { + valMap.putIfAbsent(meth, defaultValue); + } + } + return valMap; + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}. The result + * has type {@code expectedType}. + * + *

          If the return type is an array, use {@link #getElementValueArray} instead. + * + *

          If the return type is an enum, use {@link #getElementValueEnum} instead. + * + *

          This method is intended only for use by the framework. A checker implementation should use + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link + * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the type of the element and the return value + * @param the class of the type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name + * @deprecated use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link + * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)} + */ + @Deprecated // for use only by the framework, not by clients + public static T getElementValue( + AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { + Map valmap; + if (useDefaults) { + Map valmapTmp = + getElementValuesWithDefaults(anno); + valmap = valmapTmp; + } else { + valmap = anno.getElementValues(); + } + for (Map.Entry entry : + valmap.entrySet()) { + ExecutableElement elem = entry.getKey(); + if (elem.getSimpleName().contentEquals(elementName)) { + AnnotationValue val = entry.getValue(); try { - return getElementValueArray(anno, elementName, expectedType, useDefaults); - } catch (NoSuchElementException e) { - return null; - } - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}, where the - * element has an array type. One element of the result has type {@code expectedType}. - * - *

          Parameter useDefaults is used to determine whether default values should be used for - * annotation values. Finding defaults requires more computation, so should be false when no - * defaulting is needed. - * - *

          This method is intended only for use by the framework. A checker implementation should use - * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or {@code - * #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation to disassemble - * @param elementName the name of the element to access - * @param expectedType the component type of the element and of the return type - * @param the class of the type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name; it is a new list, so it is safe for - * clients to side-effect - * @deprecated use {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or - * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)} - */ - @Deprecated // for use only by the framework - public static List getElementValueArray( - AnnotationMirror anno, - CharSequence elementName, - Class expectedType, - boolean useDefaults) { - @SuppressWarnings("unchecked") - List la = getElementValue(anno, elementName, List.class, useDefaults); - List result = new ArrayList<>(la.size()); - for (AnnotationValue a : la) { - try { - result.add(expectedType.cast(a.getValue())); - } catch (Throwable t) { - String err1 = - String.format( - "getElementValueArray(%n" - + " anno=%s,%n" - + " elementName=%s,%n" - + " expectedType=%s,%n" - + " useDefaults=%s)%n", - anno, elementName, expectedType, useDefaults); - String err2 = - String.format( - "Error in cast:%n expectedType=%s%n a=%s [%s]%n a.getValue()=%s" - + " [%s]", - expectedType, - a, - a.getClass(), - a.getValue(), - a.getValue().getClass()); - throw new BugInCF(err1 + "; " + err2, t); - } - } - return result; - } - - /** - * Get the Name of the class that is referenced by element {@code elementName}. - * - *

          This is a convenience method for the most common use-case. It is like {@code - * getElementValue(anno, elementName, ClassType.class).getQualifiedName()}, but this method - * ensures consistent use of the qualified name. - * - *

          This method is intended only for use by the framework. A checker implementation should use - * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. - * - * @param anno the annotation to disassemble - * @param elementName the name of the element to access; it must be present in the annotation - * @param useDefaults whether to apply default values to the element - * @return the name of the class that is referenced by element with the given name; may be an - * empty name, for a local or anonymous class - * @deprecated use an ExecutableElement - */ - @Deprecated // for use only by the framework - public static @CanonicalName Name getElementValueClassName( - AnnotationMirror anno, CharSequence elementName, boolean useDefaults) { - Type.ClassType ct = getElementValue(anno, elementName, Type.ClassType.class, useDefaults); - // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? - @CanonicalName Name result = ct.asElement().getQualifiedName(); - return result; - } - - // ********************************************************************** - // Annotation values: efficient extractors that take an ExecutableElement - // ********************************************************************** - - /** - * Get the given element of the annotation {@code anno}. The result has type {@code - * expectedType}. - * - *

          If the return type is primitive, use {@link #getElementValueInt} or {@link - * #getElementValueLong} instead. - * - *

          If the return type is an array, use {@link #getElementValueArray} instead. - * - *

          If the return type is an enum, use {@link #getElementValueEnum} instead. - * - * @param anno the annotation whose element to access - * @param element the element to access; it must be present in the annotation - * @param expectedType the type of the element and the return value - * @param the class of the type - * @return the value of the element with the given name - */ - public static T getElementValue( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValue(%s, %s, ...)", anno, element); - } - return expectedType.cast(av.getValue()); - } - - /** - * Get the given element of the annotation {@code anno}. The result has type {@code - * expectedType}. - * - *

          If the return type is primitive, use {@link #getElementValueInt} or {@link - * #getElementValueLong} instead. - * - *

          If the return type is an array, use {@link #getElementValueArray} instead. - * - *

          If the return type is an enum, use {@link #getElementValueEnum} instead. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param expectedType the type of the element and the return value - * @param the class of the type - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static T getElementValue( - AnnotationMirror anno, - ExecutableElement element, - Class expectedType, - T defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return expectedType.cast(av.getValue()); - } - } - - /** - * Get the given boolean element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static boolean getElementValueBoolean( - AnnotationMirror anno, ExecutableElement element, boolean defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return (boolean) av.getValue(); - } - } - - /** - * Get the given integer element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @return the value of the element with the given name - */ - public static int getElementValueInt(AnnotationMirror anno, ExecutableElement element) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueInt(%s, %s, ...)", anno, element); - } else { - return (int) av.getValue(); - } - } - - /** - * Get the given integer element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static int getElementValueInt( - AnnotationMirror anno, ExecutableElement element, int defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return (int) av.getValue(); - } - } - - /** - * Get the given long element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static long getElementValueLong( - AnnotationMirror anno, ExecutableElement element, long defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return (long) av.getValue(); - } - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * enum of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @param expectedType the type of the element and the return value, an enum - * @param the class of the type - * @return the value of the element with the given name - */ - public static > T getElementValueEnum( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueEnum(%s, %s, ...)", anno, element); - } - VariableElement ve = (VariableElement) av.getValue(); - return Enum.valueOf(expectedType, ve.getSimpleName().toString()); - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * enum of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access - * @param expectedType the type of the element and the return value, an enum - * @param the class of the type - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static > T getElementValueEnum( - AnnotationMirror anno, - ExecutableElement element, - Class expectedType, - T defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - VariableElement ve = (VariableElement) av.getValue(); - return Enum.valueOf(expectedType, ve.getSimpleName().toString()); - } - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * array of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @param expectedType the component type of the element and of the return value, an enum - * @param the enum class of the component type - * @return the value of the element with the given name - */ - public static > T[] getElementValueEnumArray( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueEnumArray(%s, %s, ...)", anno, element); - } - return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * array of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access - * @param expectedType the component type of the element and of the return type - * @param the enum class of the component type - * @param defaultValue the value to return if the annotation does not have the element - * @return the value of the element with the given name - */ - public static > T[] getElementValueEnumArray( - AnnotationMirror anno, - ExecutableElement element, - Class expectedType, - T[] defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); - } - } - - /** - * Get the given element of the annotation {@code anno}, where the element has an array type. - * One element of the result has type {@code expectedType}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @param expectedType the component type of the element and of the return type - * @param the class of the component type - * @return the value of the element with the given name; it is a new list, so it is safe for - * clients to side-effect - */ - public static List getElementValueArray( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueArray(%s, %s, ...)", anno, element); - } - return annotationValueToList(av, expectedType); - } - - /** - * Get the given element of the annotation {@code anno}, where the element has an array type. - * One element of the result has type {@code expectedType}. - * - * @param anno the annotation to disassemble - * @param element the element to access - * @param expectedType the component type of the element and of the return type - * @param the class of the component type - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name; it is a new list, so it is safe for - * clients to side-effect - */ - public static List getElementValueArray( - AnnotationMirror anno, - ExecutableElement element, - Class expectedType, - List defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return annotationValueToList(av, expectedType); - } - } - - /** - * Converts a list of AnnotationValue to an array of enum. - * - * @param the element type of the enum array - * @param avList a list of AnnotationValue - * @param expectedType the component type of the element and of the return type, an enum - * @return an array of enum, converted from the input list - */ - public static > T[] annotationValueListToEnumArray( - AnnotationValue avList, Class expectedType) { - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueListToEnumArray(list, expectedType); - } - - /** - * Converts a list of AnnotationValue to an array of enum. - * - * @param the element type of the enum array - * @param la a list of AnnotationValue - * @param expectedType the component type of the element and of the return type, an enum - * @return an array of enum, converted from the input list - */ - public static > T[] annotationValueListToEnumArray( - List la, Class expectedType) { - int size = la.size(); - @SuppressWarnings("unchecked") - T[] result = (T[]) Array.newInstance(expectedType, size); - for (int i = 0; i < size; i++) { - AnnotationValue a = la.get(i); - T value = Enum.valueOf(expectedType, a.getValue().toString()); - result[i] = value; - } - return result; - } - - /** - * Get the Name of the class that is referenced by element {@code element}. - * - *

          This is a convenience method for the most common use-case. It is like {@code - * getElementValue(anno, element, ClassType.class).getQualifiedName()}, but this method ensures - * consistent use of the qualified name. - * - *

          This method is intended only for use by the framework. A checker implementation should use - * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @return the name of the class that is referenced by element with the given name; may be an - * empty name, for a local or anonymous class - */ - public static @CanonicalName Name getElementValueClassName( - AnnotationMirror anno, ExecutableElement element) { - Type.ClassType ct = getElementValue(anno, element, Type.ClassType.class); - if (ct == null) { - throw new BugInCF("getElementValueClassName(%s, %s, ...)", anno, element); - } - // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? - @CanonicalName Name result = ct.asElement().getQualifiedName(); - return result; - } - - /** - * Get the list of Names of the classes that are referenced by element {@code element}. It fails - * if the class wasn't found. - * - * @param anno the annotation whose field to access; it must be present in the annotation - * @param element the element/field of {@code anno} whose content is a list of classes - * @return the names of classes in {@code anno.annoElement} - */ - public static List<@CanonicalName Name> getElementValueClassNames( - AnnotationMirror anno, ExecutableElement element) { - List la = getElementValueArray(anno, element, Type.ClassType.class); - return CollectionsPlume.mapList( - (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la); - } - - // ********************************************************************** - // Annotation values: other methods (e.g., testing and transforming) - // ********************************************************************** - - /** - * Returns true if the two annotations have the same elements (fields). The arguments {@code - * am1} and {@code am2} must be the same type of annotation. - * - * @param am1 the first AnnotationMirror to compare - * @param am2 the second AnnotationMirror to compare - * @return true if the two annotations have the same elements (fields) - */ - @EqualsMethod - public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { - if (am1 == am2) { - return true; - } - - Map vals1 = am1.getElementValues(); - Map vals2 = am2.getElementValues(); - for (ExecutableElement meth : - ElementFilter.methodsIn( - am1.getAnnotationType().asElement().getEnclosedElements())) { - AnnotationValue aval1 = vals1.get(meth); - AnnotationValue aval2 = vals2.get(meth); - @SuppressWarnings("interning:not.interned") // optimization via equality test - boolean identical = aval1 == aval2; - if (identical) { - // Handles when both aval1 and aval2 are null, and maybe other cases too. - continue; - } - if (aval1 == null) { - aval1 = meth.getDefaultValue(); - } - if (aval2 == null) { - aval2 = meth.getDefaultValue(); - } - if (!sameAnnotationValue(aval1, aval2)) { - return false; - } - } - return true; - } - - /** - * Return true iff the two AnnotationValue objects are the same. Use this instead of - * CheckerFrameworkAnnotationValue.equals, which wouldn't get called if the receiver is some - * AnnotationValue other than CheckerFrameworkAnnotationValue. - * - * @param av1 the first AnnotationValue to compare - * @param av2 the second AnnotationValue to compare - * @return true if the two annotation values are the same - */ - public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) { - return compareAnnotationValue(av1, av2) == 0; - } - - /** - * Returns true if an AnnotationValue list contains the given value. - * - *

          Using this method is slightly cheaper than creating a new {@code List} just for - * the purpose of testing containment within it. - * - * @param avList an AnnotationValue that is null or a list of Strings - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContains(@Nullable AnnotationValue avList, String s) { - if (avList == null) { - return false; - } - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueContains(list, s); - } - - /** - * Returns true if an AnnotationValue list contains the given value. - * - *

          Using this method is slightly cheaper than creating a new {@code List} just for - * the purpose of testing containment within it. - * - * @param avList a list of Strings (as {@code AnnotationValue}s) - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContains( - List avList, String s) { - for (AnnotationValue av : avList) { - if (av.getValue().equals(s)) { - return true; - } - } - return false; - } - - /** - * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the - * given string. - * - *

          Using this method is slightly cheaper than creating a new {@code List} just for the - * purpose of testing containment within it. - * - * @param avList an AnnotationValue that is null or a list - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContainsToString( - @Nullable AnnotationValue avList, String s) { - if (avList == null) { - return false; - } - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueContainsToString(list, s); - } - - /** - * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the - * given string. - * - *

          Using this method is slightly cheaper than creating a new {@code List} just for the - * purpose of testing containment within it. - * - * @param avList a list of Strings (as {@code AnnotationValue}s) - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContainsToString( - List avList, String s) { - for (AnnotationValue av : avList) { - if (av.getValue().toString().equals(s)) { - return true; - } + return expectedType.cast(val.getValue()); + } catch (ClassCastException e) { + throw new BugInCF( + "getElementValue(%s, %s, %s, %s): val=%s, val.getValue()=%s [%s]", + anno, + elementName, + expectedType, + useDefaults, + val, + val.getValue(), + val.getValue().getClass()); } + } + } + throw new NoSuchElementException( + String.format( + "No element with name \'%s\' in annotation %s; useDefaults=%s," + " valmap.keySet()=%s", + elementName, anno, useDefaults, valmap.keySet())); + } + + /** Differentiates NoSuchElementException from other BugInCF, for use by getElementValueOrNull. */ + @SuppressWarnings("serial") + private static class NoSuchElementException extends BugInCF { + /** + * Constructs a new NoSuchElementException. + * + * @param message the detail message + */ + @Pure + public NoSuchElementException(String message) { + super(message); + } + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}, or return + * null if no such element exists. + * + *

          This method is intended only for use by the framework. A checker implementation should use + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the type of the element and the return value + * @param the class of the type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name, or null + */ + public static @Nullable T getElementValueOrNull( + AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { + // This implementation permits getElementValue to give a more detailed error message than if + // getElementValue called getElementValueOrNull and threw an error if the result was null. + try { + return getElementValue(anno, elementName, expectedType, useDefaults); + } catch (NoSuchElementException e) { + return null; + } + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}, or return + * null if no such element exists. One element of the result has type {@code expectedType}. + * + *

          This method is intended only for use by the framework. A checker implementation should use + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the component type of the element and of the return value + * @param the class of the component type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name, or null + */ + public static @Nullable List getElementValueArrayOrNull( + AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { + // This implementation permits getElementValue to give a more detailed error message than if + // getElementValue called getElementValueOrNull and threw an error if the result was null. + try { + return getElementValueArray(anno, elementName, expectedType, useDefaults); + } catch (NoSuchElementException e) { + return null; + } + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}, where the + * element has an array type. One element of the result has type {@code expectedType}. + * + *

          Parameter useDefaults is used to determine whether default values should be used for + * annotation values. Finding defaults requires more computation, so should be false when no + * defaulting is needed. + * + *

          This method is intended only for use by the framework. A checker implementation should use + * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or {@code + * #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation to disassemble + * @param elementName the name of the element to access + * @param expectedType the component type of the element and of the return type + * @param the class of the type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name; it is a new list, so it is safe for + * clients to side-effect + * @deprecated use {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or + * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)} + */ + @Deprecated // for use only by the framework + public static List getElementValueArray( + AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { + @SuppressWarnings("unchecked") + List la = getElementValue(anno, elementName, List.class, useDefaults); + List result = new ArrayList<>(la.size()); + for (AnnotationValue a : la) { + try { + result.add(expectedType.cast(a.getValue())); + } catch (Throwable t) { + String err1 = + String.format( + "getElementValueArray(%n" + + " anno=%s,%n" + + " elementName=%s,%n" + + " expectedType=%s,%n" + + " useDefaults=%s)%n", + anno, elementName, expectedType, useDefaults); + String err2 = + String.format( + "Error in cast:%n expectedType=%s%n a=%s [%s]%n a.getValue()=%s" + " [%s]", + expectedType, a, a.getClass(), a.getValue(), a.getValue().getClass()); + throw new BugInCF(err1 + "; " + err2, t); + } + } + return result; + } + + /** + * Get the Name of the class that is referenced by element {@code elementName}. + * + *

          This is a convenience method for the most common use-case. It is like {@code + * getElementValue(anno, elementName, ClassType.class).getQualifiedName()}, but this method + * ensures consistent use of the qualified name. + * + *

          This method is intended only for use by the framework. A checker implementation should use + * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. + * + * @param anno the annotation to disassemble + * @param elementName the name of the element to access; it must be present in the annotation + * @param useDefaults whether to apply default values to the element + * @return the name of the class that is referenced by element with the given name; may be an + * empty name, for a local or anonymous class + * @deprecated use an ExecutableElement + */ + @Deprecated // for use only by the framework + public static @CanonicalName Name getElementValueClassName( + AnnotationMirror anno, CharSequence elementName, boolean useDefaults) { + Type.ClassType ct = getElementValue(anno, elementName, Type.ClassType.class, useDefaults); + // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? + @CanonicalName Name result = ct.asElement().getQualifiedName(); + return result; + } + + // ********************************************************************** + // Annotation values: efficient extractors that take an ExecutableElement + // ********************************************************************** + + /** + * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}. + * + *

          If the return type is primitive, use {@link #getElementValueInt} or {@link + * #getElementValueLong} instead. + * + *

          If the return type is an array, use {@link #getElementValueArray} instead. + * + *

          If the return type is an enum, use {@link #getElementValueEnum} instead. + * + * @param anno the annotation whose element to access + * @param element the element to access; it must be present in the annotation + * @param expectedType the type of the element and the return value + * @param the class of the type + * @return the value of the element with the given name + */ + public static T getElementValue( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValue(%s, %s, ...)", anno, element); + } + return expectedType.cast(av.getValue()); + } + + /** + * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}. + * + *

          If the return type is primitive, use {@link #getElementValueInt} or {@link + * #getElementValueLong} instead. + * + *

          If the return type is an array, use {@link #getElementValueArray} instead. + * + *

          If the return type is an enum, use {@link #getElementValueEnum} instead. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param expectedType the type of the element and the return value + * @param the class of the type + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static T getElementValue( + AnnotationMirror anno, ExecutableElement element, Class expectedType, T defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return expectedType.cast(av.getValue()); + } + } + + /** + * Get the given boolean element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static boolean getElementValueBoolean( + AnnotationMirror anno, ExecutableElement element, boolean defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return (boolean) av.getValue(); + } + } + + /** + * Get the given integer element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @return the value of the element with the given name + */ + public static int getElementValueInt(AnnotationMirror anno, ExecutableElement element) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueInt(%s, %s, ...)", anno, element); + } else { + return (int) av.getValue(); + } + } + + /** + * Get the given integer element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static int getElementValueInt( + AnnotationMirror anno, ExecutableElement element, int defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return (int) av.getValue(); + } + } + + /** + * Get the given long element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static long getElementValueLong( + AnnotationMirror anno, ExecutableElement element, long defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return (long) av.getValue(); + } + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * enum of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @param expectedType the type of the element and the return value, an enum + * @param the class of the type + * @return the value of the element with the given name + */ + public static > T getElementValueEnum( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueEnum(%s, %s, ...)", anno, element); + } + VariableElement ve = (VariableElement) av.getValue(); + return Enum.valueOf(expectedType, ve.getSimpleName().toString()); + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * enum of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access + * @param expectedType the type of the element and the return value, an enum + * @param the class of the type + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static > T getElementValueEnum( + AnnotationMirror anno, ExecutableElement element, Class expectedType, T defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + VariableElement ve = (VariableElement) av.getValue(); + return Enum.valueOf(expectedType, ve.getSimpleName().toString()); + } + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * array of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @param expectedType the component type of the element and of the return value, an enum + * @param the enum class of the component type + * @return the value of the element with the given name + */ + public static > T[] getElementValueEnumArray( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueEnumArray(%s, %s, ...)", anno, element); + } + return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * array of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access + * @param expectedType the component type of the element and of the return type + * @param the enum class of the component type + * @param defaultValue the value to return if the annotation does not have the element + * @return the value of the element with the given name + */ + public static > T[] getElementValueEnumArray( + AnnotationMirror anno, ExecutableElement element, Class expectedType, T[] defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); + } + } + + /** + * Get the given element of the annotation {@code anno}, where the element has an array type. One + * element of the result has type {@code expectedType}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @param expectedType the component type of the element and of the return type + * @param the class of the component type + * @return the value of the element with the given name; it is a new list, so it is safe for + * clients to side-effect + */ + public static List getElementValueArray( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueArray(%s, %s, ...)", anno, element); + } + return annotationValueToList(av, expectedType); + } + + /** + * Get the given element of the annotation {@code anno}, where the element has an array type. One + * element of the result has type {@code expectedType}. + * + * @param anno the annotation to disassemble + * @param element the element to access + * @param expectedType the component type of the element and of the return type + * @param the class of the component type + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name; it is a new list, so it is safe for + * clients to side-effect + */ + public static List getElementValueArray( + AnnotationMirror anno, + ExecutableElement element, + Class expectedType, + List defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return annotationValueToList(av, expectedType); + } + } + + /** + * Converts a list of AnnotationValue to an array of enum. + * + * @param the element type of the enum array + * @param avList a list of AnnotationValue + * @param expectedType the component type of the element and of the return type, an enum + * @return an array of enum, converted from the input list + */ + public static > T[] annotationValueListToEnumArray( + AnnotationValue avList, Class expectedType) { + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueListToEnumArray(list, expectedType); + } + + /** + * Converts a list of AnnotationValue to an array of enum. + * + * @param the element type of the enum array + * @param la a list of AnnotationValue + * @param expectedType the component type of the element and of the return type, an enum + * @return an array of enum, converted from the input list + */ + public static > T[] annotationValueListToEnumArray( + List la, Class expectedType) { + int size = la.size(); + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(expectedType, size); + for (int i = 0; i < size; i++) { + AnnotationValue a = la.get(i); + T value = Enum.valueOf(expectedType, a.getValue().toString()); + result[i] = value; + } + return result; + } + + /** + * Get the Name of the class that is referenced by element {@code element}. + * + *

          This is a convenience method for the most common use-case. It is like {@code + * getElementValue(anno, element, ClassType.class).getQualifiedName()}, but this method ensures + * consistent use of the qualified name. + * + *

          This method is intended only for use by the framework. A checker implementation should use + * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @return the name of the class that is referenced by element with the given name; may be an + * empty name, for a local or anonymous class + */ + public static @CanonicalName Name getElementValueClassName( + AnnotationMirror anno, ExecutableElement element) { + Type.ClassType ct = getElementValue(anno, element, Type.ClassType.class); + if (ct == null) { + throw new BugInCF("getElementValueClassName(%s, %s, ...)", anno, element); + } + // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? + @CanonicalName Name result = ct.asElement().getQualifiedName(); + return result; + } + + /** + * Get the list of Names of the classes that are referenced by element {@code element}. It fails + * if the class wasn't found. + * + * @param anno the annotation whose field to access; it must be present in the annotation + * @param element the element/field of {@code anno} whose content is a list of classes + * @return the names of classes in {@code anno.annoElement} + */ + public static List<@CanonicalName Name> getElementValueClassNames( + AnnotationMirror anno, ExecutableElement element) { + List la = getElementValueArray(anno, element, Type.ClassType.class); + return CollectionsPlume.mapList( + (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la); + } + + // ********************************************************************** + // Annotation values: other methods (e.g., testing and transforming) + // ********************************************************************** + + /** + * Returns true if the two annotations have the same elements (fields). The arguments {@code am1} + * and {@code am2} must be the same type of annotation. + * + * @param am1 the first AnnotationMirror to compare + * @param am2 the second AnnotationMirror to compare + * @return true if the two annotations have the same elements (fields) + */ + @EqualsMethod + public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { + if (am1 == am2) { + return true; + } + + Map vals1 = am1.getElementValues(); + Map vals2 = am2.getElementValues(); + for (ExecutableElement meth : + ElementFilter.methodsIn(am1.getAnnotationType().asElement().getEnclosedElements())) { + AnnotationValue aval1 = vals1.get(meth); + AnnotationValue aval2 = vals2.get(meth); + @SuppressWarnings("interning:not.interned") // optimization via equality test + boolean identical = aval1 == aval2; + if (identical) { + // Handles when both aval1 and aval2 are null, and maybe other cases too. + continue; + } + if (aval1 == null) { + aval1 = meth.getDefaultValue(); + } + if (aval2 == null) { + aval2 = meth.getDefaultValue(); + } + if (!sameAnnotationValue(aval1, aval2)) { return false; - } - - /** - * Converts an annotation value to a list. - * - *

          To test containment, use {@link #annotationValueContains(AnnotationValue, String)} or - * {@link #annotationValueContainsToString(AnnotationValue, String)}. - * - * @param avList an AnnotationValue that is a list of Strings - * @param expectedType the component type of the argument and of the return type, an enum - * @param the class of the type - * @return the annotation value, converted to a list - */ - public static List annotationValueToList(AnnotationValue avList, Class expectedType) { - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueToList(list, expectedType); - } - - /** - * Converts an annotation value to a list. - * - *

          To test containment, use {@link #annotationValueContains(List, String)} or {@link - * #annotationValueContainsToString(List, String)}. - * - * @param avList a list of Strings (as {@code AnnotationValue}s) - * @param expectedType the component type of the argument and of the return type, an enum - * @param the class of the type - * @return the annotation value, converted to a list - */ - public static List annotationValueToList( - List avList, Class expectedType) { - List result = new ArrayList<>(avList.size()); - for (AnnotationValue a : avList) { - try { - result.add(expectedType.cast(a.getValue())); - } catch (Throwable t) { - String err1 = String.format("annotationValueToList(%s, %s)", avList, expectedType); - String err2 = - String.format( - "a=%s [%s]%n a.getValue()=%s [%s]", - a, a.getClass(), a.getValue(), a.getValue().getClass()); - throw new BugInCF(err1 + " " + err2, t); - } - } - return result; - } - - // ********************************************************************** - // Other methods - // ********************************************************************** - - // The Javadoc doesn't use @link because framework is a different project than this one - // (javacutil). - /** - * Update a map, to add {@code newQual} to the set that {@code key} maps to. The mapped-to - * element is an unmodifiable set. - * - *

          See - * org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, - * Map, Object, AnnotationMirror). - * - * @param map the map to update - * @param key the key whose value to update - * @param newQual the element to add to the given key's value - * @param the key type - */ - public static void updateMappingToImmutableSet( - Map map, T key, AnnotationMirrorSet newQual) { - - AnnotationMirrorSet result = new AnnotationMirrorSet(); - // TODO: if T is also an AnnotationMirror, should we use areSame? - if (!map.containsKey(key)) { - result.addAll(newQual); - } else { - result.addAll(map.get(key)); - result.addAll(newQual); - } - result.makeUnmodifiable(); - map.put(key, result); - } - - /** - * Returns the annotations explicitly written on a constructor result. Callers should check that - * {@code constructorDeclaration} is in fact a declaration of a constructor. - * - * @param constructorDeclaration declaration tree of constructor - * @return set of annotations explicit on the resulting type of the constructor - */ - public static AnnotationMirrorSet getExplicitAnnotationsOnConstructorResult( - MethodTree constructorDeclaration) { - AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); - ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); - if (modifiersTree != null) { - List annotationTrees = modifiersTree.getAnnotations(); - annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); - } - return annotationSet; - } - - /** - * Returns true if {@code anno} is not a type use annotation, that is, it cannot be written on - * uses of types. - * - * @param anno the AnnotationMirror - * @return true if anno is a declaration annotation - * @deprecated use {@link #isTypeUseAnnotation(AnnotationMirror)} instead - */ - @Deprecated // 2024-01-06 - public static boolean isDeclarationAnnotation(AnnotationMirror anno) { - return !isTypeUseAnnotation(anno); - } - - /** - * Returns true if {@code anno} is a type use annotation, that is, it can be written on uses of - * types. - * - * @param anno the AnnotationMirror - * @return true if anno is a declaration annotation - */ - public static boolean isTypeUseAnnotation(AnnotationMirror anno) { - TypeElement elem = (TypeElement) anno.getAnnotationType().asElement(); - Target t = elem.getAnnotation(Target.class); - if (t == null) { - return false; - } - - for (ElementType elementType : t.value()) { - if (elementType == ElementType.TYPE_USE) { - return true; - } - } - return false; - } - - /** - * Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise. - * - * @param elements an array of {@link ElementType} values - * @param cls the annotation class being tested; used for diagnostic messages only - * @return true iff the give array contains {@link ElementType#TYPE_USE} - * @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and - * something besides {@link ElementType#TYPE_PARAMETER} - */ - public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class cls) { - // True if the array contains TYPE_USE - boolean hasTypeUse = false; - // Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER - ElementType otherElementType = null; - - for (ElementType element : elements) { - if (element == ElementType.TYPE_USE) { - hasTypeUse = true; - } else if (element != ElementType.TYPE_PARAMETER) { - otherElementType = element; - } - if (hasTypeUse && otherElementType != null) { - throw new BugInCF( - "@Target meta-annotation should not contain both TYPE_USE and " - + otherElementType - + ", for annotation " - + cls.getName()); - } - } - - return hasTypeUse; - } - - /** - * Returns a string representation of the annotation mirrors, using simple (not fully-qualified) - * names. - * - * @param annos annotations to format - * @return the string representation, using simple (not fully-qualified) names - */ - @SideEffectFree - public static String toStringSimple(AnnotationMirrorSet annos) { - StringJoiner result = new StringJoiner(" "); - for (AnnotationMirror am : annos) { - result.add(toStringSimple(am)); - } - return result.toString(); - } - - /** - * Returns a string representation of the annotation mirror, using simple (not fully-qualified) - * names. - * - * @param am annotation to format - * @return the string representation, using simple (not fully-qualified) names - */ - @SideEffectFree - public static String toStringSimple(AnnotationMirror am) { - StringBuilder sb = new StringBuilder(); - toStringSimple(am, sb); - return sb.toString(); - } - - /** - * Appends a string representation of the annotation mirror, using simple (not fully-qualified) - * names, to the StringBuilder. - * - * @param am annotation to format - * @param sb StringBuilder to which the string representation of am, using simple (not - * fully-qualified) names, is appended - */ - public static void toStringSimple(AnnotationMirror am, StringBuilder sb) { - sb.append("@"); - sb.append(am.getAnnotationType().asElement().getSimpleName()); - Map args = removeDefaultValues(am.getElementValues()); - if (!args.isEmpty()) { - sb.append("("); - boolean oneValue = false; - if (args.size() == 1) { - Map.Entry first = - args.entrySet().iterator().next(); - if (first.getKey().getSimpleName().contentEquals("value")) { - formatAnnotationMirrorArg(first.getValue(), sb); - oneValue = true; - } - } - if (!oneValue) { - boolean notfirst = false; - for (Map.Entry arg : args.entrySet()) { - if (!"{}".equals(arg.getValue().toString())) { - if (notfirst) { - sb.append(", "); - } - notfirst = true; - sb.append(arg.getKey().getSimpleName() + "="); - formatAnnotationMirrorArg(arg.getValue(), sb); - } - } - } - sb.append(")"); + } + } + return true; + } + + /** + * Return true iff the two AnnotationValue objects are the same. Use this instead of + * CheckerFrameworkAnnotationValue.equals, which wouldn't get called if the receiver is some + * AnnotationValue other than CheckerFrameworkAnnotationValue. + * + * @param av1 the first AnnotationValue to compare + * @param av2 the second AnnotationValue to compare + * @return true if the two annotation values are the same + */ + public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) { + return compareAnnotationValue(av1, av2) == 0; + } + + /** + * Returns true if an AnnotationValue list contains the given value. + * + *

          Using this method is slightly cheaper than creating a new {@code List} just for the + * purpose of testing containment within it. + * + * @param avList an AnnotationValue that is null or a list of Strings + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContains(@Nullable AnnotationValue avList, String s) { + if (avList == null) { + return false; + } + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueContains(list, s); + } + + /** + * Returns true if an AnnotationValue list contains the given value. + * + *

          Using this method is slightly cheaper than creating a new {@code List} just for the + * purpose of testing containment within it. + * + * @param avList a list of Strings (as {@code AnnotationValue}s) + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContains(List avList, String s) { + for (AnnotationValue av : avList) { + if (av.getValue().equals(s)) { + return true; + } + } + return false; + } + + /** + * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given + * string. + * + *

          Using this method is slightly cheaper than creating a new {@code List} just for the purpose + * of testing containment within it. + * + * @param avList an AnnotationValue that is null or a list + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContainsToString( + @Nullable AnnotationValue avList, String s) { + if (avList == null) { + return false; + } + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueContainsToString(list, s); + } + + /** + * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given + * string. + * + *

          Using this method is slightly cheaper than creating a new {@code List} just for the purpose + * of testing containment within it. + * + * @param avList a list of Strings (as {@code AnnotationValue}s) + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContainsToString( + List avList, String s) { + for (AnnotationValue av : avList) { + if (av.getValue().toString().equals(s)) { + return true; + } + } + return false; + } + + /** + * Converts an annotation value to a list. + * + *

          To test containment, use {@link #annotationValueContains(AnnotationValue, String)} or {@link + * #annotationValueContainsToString(AnnotationValue, String)}. + * + * @param avList an AnnotationValue that is a list of Strings + * @param expectedType the component type of the argument and of the return type, an enum + * @param the class of the type + * @return the annotation value, converted to a list + */ + public static List annotationValueToList(AnnotationValue avList, Class expectedType) { + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueToList(list, expectedType); + } + + /** + * Converts an annotation value to a list. + * + *

          To test containment, use {@link #annotationValueContains(List, String)} or {@link + * #annotationValueContainsToString(List, String)}. + * + * @param avList a list of Strings (as {@code AnnotationValue}s) + * @param expectedType the component type of the argument and of the return type, an enum + * @param the class of the type + * @return the annotation value, converted to a list + */ + public static List annotationValueToList( + List avList, Class expectedType) { + List result = new ArrayList<>(avList.size()); + for (AnnotationValue a : avList) { + try { + result.add(expectedType.cast(a.getValue())); + } catch (Throwable t) { + String err1 = String.format("annotationValueToList(%s, %s)", avList, expectedType); + String err2 = + String.format( + "a=%s [%s]%n a.getValue()=%s [%s]", + a, a.getClass(), a.getValue(), a.getValue().getClass()); + throw new BugInCF(err1 + " " + err2, t); + } + } + return result; + } + + // ********************************************************************** + // Other methods + // ********************************************************************** + + // The Javadoc doesn't use @link because framework is a different project than this one + // (javacutil). + /** + * Update a map, to add {@code newQual} to the set that {@code key} maps to. The mapped-to element + * is an unmodifiable set. + * + *

          See + * org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, + * Map, Object, AnnotationMirror). + * + * @param map the map to update + * @param key the key whose value to update + * @param newQual the element to add to the given key's value + * @param the key type + */ + public static void updateMappingToImmutableSet( + Map map, T key, AnnotationMirrorSet newQual) { + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + // TODO: if T is also an AnnotationMirror, should we use areSame? + if (!map.containsKey(key)) { + result.addAll(newQual); + } else { + result.addAll(map.get(key)); + result.addAll(newQual); + } + result.makeUnmodifiable(); + map.put(key, result); + } + + /** + * Returns the annotations explicitly written on a constructor result. Callers should check that + * {@code constructorDeclaration} is in fact a declaration of a constructor. + * + * @param constructorDeclaration declaration tree of constructor + * @return set of annotations explicit on the resulting type of the constructor + */ + public static AnnotationMirrorSet getExplicitAnnotationsOnConstructorResult( + MethodTree constructorDeclaration) { + AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); + ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); + if (modifiersTree != null) { + List annotationTrees = modifiersTree.getAnnotations(); + annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); + } + return annotationSet; + } + + /** + * Returns true if {@code anno} is not a type use annotation, that is, it cannot be written on + * uses of types. + * + * @param anno the AnnotationMirror + * @return true if anno is a declaration annotation + * @deprecated use {@link #isTypeUseAnnotation(AnnotationMirror)} instead + */ + @Deprecated // 2024-01-06 + public static boolean isDeclarationAnnotation(AnnotationMirror anno) { + return !isTypeUseAnnotation(anno); + } + + /** + * Returns true if {@code anno} is a type use annotation, that is, it can be written on uses of + * types. + * + * @param anno the AnnotationMirror + * @return true if anno is a declaration annotation + */ + public static boolean isTypeUseAnnotation(AnnotationMirror anno) { + TypeElement elem = (TypeElement) anno.getAnnotationType().asElement(); + Target t = elem.getAnnotation(Target.class); + if (t == null) { + return false; + } + + for (ElementType elementType : t.value()) { + if (elementType == ElementType.TYPE_USE) { + return true; + } + } + return false; + } + + /** + * Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise. + * + * @param elements an array of {@link ElementType} values + * @param cls the annotation class being tested; used for diagnostic messages only + * @return true iff the give array contains {@link ElementType#TYPE_USE} + * @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and something + * besides {@link ElementType#TYPE_PARAMETER} + */ + public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class cls) { + // True if the array contains TYPE_USE + boolean hasTypeUse = false; + // Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER + ElementType otherElementType = null; + + for (ElementType element : elements) { + if (element == ElementType.TYPE_USE) { + hasTypeUse = true; + } else if (element != ElementType.TYPE_PARAMETER) { + otherElementType = element; + } + if (hasTypeUse && otherElementType != null) { + throw new BugInCF( + "@Target meta-annotation should not contain both TYPE_USE and " + + otherElementType + + ", for annotation " + + cls.getName()); + } + } + + return hasTypeUse; + } + + /** + * Returns a string representation of the annotation mirrors, using simple (not fully-qualified) + * names. + * + * @param annos annotations to format + * @return the string representation, using simple (not fully-qualified) names + */ + @SideEffectFree + public static String toStringSimple(AnnotationMirrorSet annos) { + StringJoiner result = new StringJoiner(" "); + for (AnnotationMirror am : annos) { + result.add(toStringSimple(am)); + } + return result.toString(); + } + + /** + * Returns a string representation of the annotation mirror, using simple (not fully-qualified) + * names. + * + * @param am annotation to format + * @return the string representation, using simple (not fully-qualified) names + */ + @SideEffectFree + public static String toStringSimple(AnnotationMirror am) { + StringBuilder sb = new StringBuilder(); + toStringSimple(am, sb); + return sb.toString(); + } + + /** + * Appends a string representation of the annotation mirror, using simple (not fully-qualified) + * names, to the StringBuilder. + * + * @param am annotation to format + * @param sb StringBuilder to which the string representation of am, using simple (not + * fully-qualified) names, is appended + */ + public static void toStringSimple(AnnotationMirror am, StringBuilder sb) { + sb.append("@"); + sb.append(am.getAnnotationType().asElement().getSimpleName()); + Map args = removeDefaultValues(am.getElementValues()); + if (!args.isEmpty()) { + sb.append("("); + boolean oneValue = false; + if (args.size() == 1) { + Map.Entry first = args.entrySet().iterator().next(); + if (first.getKey().getSimpleName().contentEquals("value")) { + formatAnnotationMirrorArg(first.getValue(), sb); + oneValue = true; } - } - - /** - * Returns a new map that only has the values in {@code elementValues} that are not the same as - * the default value. - * - * @param elementValues a mapping of annotation element to annotation value - * @return a new map with only the non-default values of {@code elementValues} - */ - private static Map removeDefaultValues( - Map elementValues) { - // Most annotations have no elements. - Map nonDefaults = new ArrayMap<>(0); - elementValues.forEach( - (element, value) -> { - if (element.getDefaultValue() == null - || !Objects.equals( - value.getValue(), element.getDefaultValue().getValue())) { - nonDefaults.put(element, value); - } - }); - return nonDefaults; - } - - /** - * A helper method to print AnnotationValues (annotation arguments), without showing full - * package names. - * - * @param av AnnotationValue to print - * @param sb StringBuilder to modify - */ - private static void formatAnnotationMirrorArg(AnnotationValue av, StringBuilder sb) { - Object val = av.getValue(); - if (List.class.isAssignableFrom(val.getClass())) { - @SuppressWarnings("unchecked") - List vallist = (List) val; - if (vallist.size() == 1) { - formatAnnotationMirrorArg(vallist.get(0), sb); - } else { - sb.append('{'); - boolean notfirst = false; - for (AnnotationValue nav : vallist) { - if (notfirst) { - sb.append(", "); - } - notfirst = true; - formatAnnotationMirrorArg(nav, sb); - } - sb.append('}'); + } + if (!oneValue) { + boolean notfirst = false; + for (Map.Entry arg : args.entrySet()) { + if (!"{}".equals(arg.getValue().toString())) { + if (notfirst) { + sb.append(", "); } - } else if (VariableElement.class.isAssignableFrom(val.getClass())) { - VariableElement ve = (VariableElement) val; - sb.append(ve.getEnclosingElement().getSimpleName() + "." + ve.getSimpleName()); - } else { - sb.append(av.toString()); + notfirst = true; + sb.append(arg.getKey().getSimpleName() + "="); + formatAnnotationMirrorArg(arg.getValue(), sb); + } } - } - - /** - * Converts an AnnotationMirror to a Class. Throws an exception if it is not able to do so. - * - * @param am an AnnotationMirror - * @return the Class corresponding to the given AnnotationMirror - */ - public static Class annotationMirrorToClass(AnnotationMirror am) { - try { - return Class.forName(AnnotationUtils.annotationBinaryName(am)); - } catch (ClassNotFoundException e) { - throw new BugInCF(e); + } + sb.append(")"); + } + } + + /** + * Returns a new map that only has the values in {@code elementValues} that are not the same as + * the default value. + * + * @param elementValues a mapping of annotation element to annotation value + * @return a new map with only the non-default values of {@code elementValues} + */ + private static Map removeDefaultValues( + Map elementValues) { + // Most annotations have no elements. + Map nonDefaults = new ArrayMap<>(0); + elementValues.forEach( + (element, value) -> { + if (element.getDefaultValue() == null + || !Objects.equals(value.getValue(), element.getDefaultValue().getValue())) { + nonDefaults.put(element, value); + } + }); + return nonDefaults; + } + + /** + * A helper method to print AnnotationValues (annotation arguments), without showing full package + * names. + * + * @param av AnnotationValue to print + * @param sb StringBuilder to modify + */ + private static void formatAnnotationMirrorArg(AnnotationValue av, StringBuilder sb) { + Object val = av.getValue(); + if (List.class.isAssignableFrom(val.getClass())) { + @SuppressWarnings("unchecked") + List vallist = (List) val; + if (vallist.size() == 1) { + formatAnnotationMirrorArg(vallist.get(0), sb); + } else { + sb.append('{'); + boolean notfirst = false; + for (AnnotationValue nav : vallist) { + if (notfirst) { + sb.append(", "); + } + notfirst = true; + formatAnnotationMirrorArg(nav, sb); } - } + sb.append('}'); + } + } else if (VariableElement.class.isAssignableFrom(val.getClass())) { + VariableElement ve = (VariableElement) val; + sb.append(ve.getEnclosingElement().getSimpleName() + "." + ve.getSimpleName()); + } else { + sb.append(av.toString()); + } + } + + /** + * Converts an AnnotationMirror to a Class. Throws an exception if it is not able to do so. + * + * @param am an AnnotationMirror + * @return the Class corresponding to the given AnnotationMirror + */ + public static Class annotationMirrorToClass(AnnotationMirror am) { + try { + return Class.forName(AnnotationUtils.annotationBinaryName(am)); + } catch (ClassNotFoundException e) { + throw new BugInCF(e); + } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java b/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java index 7893f6363cd..a160fd8a800 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java @@ -1,91 +1,86 @@ package org.checkerframework.javacutil; import com.sun.source.tree.Tree; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.lang.annotation.Annotation; import java.util.List; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.Nullable; /** An AnnotationProvider that is independent of any type hierarchy. */ public class BasicAnnotationProvider implements AnnotationProvider { - /** - * Returns the AnnotationMirror, of the given class, used to annotate the element. Returns null - * if no such annotation exists. - */ - @Override - public @Nullable AnnotationMirror getDeclAnnotation( - Element elt, Class anno) { - List annotationMirrors = elt.getAnnotationMirrors(); - - for (AnnotationMirror am : annotationMirrors) { - @SuppressWarnings("deprecation") // method intended for use by the hierarchy - boolean found = AnnotationUtils.areSameByClass(am, anno); - if (found) { - return am; - } - } + /** + * Returns the AnnotationMirror, of the given class, used to annotate the element. Returns null if + * no such annotation exists. + */ + @Override + public @Nullable AnnotationMirror getDeclAnnotation( + Element elt, Class anno) { + List annotationMirrors = elt.getAnnotationMirrors(); - return null; + for (AnnotationMirror am : annotationMirrors) { + @SuppressWarnings("deprecation") // method intended for use by the hierarchy + boolean found = AnnotationUtils.areSameByClass(am, anno); + if (found) { + return am; + } } - /** - * {@inheritDoc} - * - *

          This implementation always returns null, because it has no access to any type hierarchy. - */ - @Override - public @Nullable AnnotationMirror getAnnotationMirror( - Tree tree, Class target) { - return null; - } + return null; + } - /** - * {@inheritDoc} - * - *

          This implementation returns true if the {@code @SideEffectFree} annotation is present on - * the given method. - */ - @Override - public boolean isSideEffectFree(ExecutableElement methodElement) { - List annotationMirrors = methodElement.getAnnotationMirrors(); + /** + * {@inheritDoc} + * + *

          This implementation always returns null, because it has no access to any type hierarchy. + */ + @Override + public @Nullable AnnotationMirror getAnnotationMirror( + Tree tree, Class target) { + return null; + } - for (AnnotationMirror am : annotationMirrors) { - boolean found = - AnnotationUtils.areSameByName( - am, "org.checkerframework.dataflow.qual.SideEffectFree"); - if (found) { - return true; - } - } + /** + * {@inheritDoc} + * + *

          This implementation returns true if the {@code @SideEffectFree} annotation is present on the + * given method. + */ + @Override + public boolean isSideEffectFree(ExecutableElement methodElement) { + List annotationMirrors = methodElement.getAnnotationMirrors(); - return false; + for (AnnotationMirror am : annotationMirrors) { + boolean found = + AnnotationUtils.areSameByName(am, "org.checkerframework.dataflow.qual.SideEffectFree"); + if (found) { + return true; + } } - /** - * {@inheritDoc} - * - *

          This implementation returns true if the {@code @Deterministic} annotation is present on - * the given method. - */ - @Override - public boolean isDeterministic(ExecutableElement methodElement) { - List annotationMirrors = methodElement.getAnnotationMirrors(); + return false; + } - for (AnnotationMirror am : annotationMirrors) { - boolean found = - AnnotationUtils.areSameByName( - am, "org.checkerframework.dataflow.qual.Deterministic"); - if (found) { - return true; - } - } + /** + * {@inheritDoc} + * + *

          This implementation returns true if the {@code @Deterministic} annotation is present on the + * given method. + */ + @Override + public boolean isDeterministic(ExecutableElement methodElement) { + List annotationMirrors = methodElement.getAnnotationMirrors(); - return false; + for (AnnotationMirror am : annotationMirrors) { + boolean found = + AnnotationUtils.areSameByName(am, "org.checkerframework.dataflow.qual.Deterministic"); + if (found) { + return true; + } } + + return false; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java b/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java index 8467edbe5ef..f157b360f27 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java @@ -3,43 +3,41 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; - import javax.lang.model.element.TypeElement; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Process the types in an AST in a trivial manner, with hooks for derived classes to actually do * something. */ public abstract class BasicTypeProcessor extends AbstractTypeProcessor { - /** The source tree that's being scanned. */ - protected @MonotonicNonNull CompilationUnitTree currentRoot; + /** The source tree that's being scanned. */ + protected @MonotonicNonNull CompilationUnitTree currentRoot; - /** - * Create a TreePathScanner at the given root. - * - * @param root where to start the tree traversal - * @return a TreePathScanner at the given root - */ - protected abstract TreePathScanner createTreePathScanner(CompilationUnitTree root); + /** + * Create a TreePathScanner at the given root. + * + * @param root where to start the tree traversal + * @return a TreePathScanner at the given root + */ + protected abstract TreePathScanner createTreePathScanner(CompilationUnitTree root); - /** Visit the tree path for the type element. */ - @Override - public void typeProcess(TypeElement e, TreePath p) { - currentRoot = p.getCompilationUnit(); + /** Visit the tree path for the type element. */ + @Override + public void typeProcess(TypeElement e, TreePath p) { + currentRoot = p.getCompilationUnit(); - TreePathScanner scanner = null; - try { - scanner = createTreePathScanner(currentRoot); - scanner.scan(p, null); - } catch (Throwable t) { - System.err.println( - "BasicTypeProcessor.typeProcess: unexpected Throwable (" - + t.getClass().getSimpleName() - + ") when processing " - + currentRoot.getSourceFile().getName() - + (t.getMessage() != null ? "; message: " + t.getMessage() : "")); - } + TreePathScanner scanner = null; + try { + scanner = createTreePathScanner(currentRoot); + scanner.scan(p, null); + } catch (Throwable t) { + System.err.println( + "BasicTypeProcessor.typeProcess: unexpected Throwable (" + + t.getClass().getSimpleName() + + ") when processing " + + currentRoot.getSourceFile().getName() + + (t.getMessage() != null ? "; message: " + t.getMessage() : "")); } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java b/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java index 370201f683b..fb9daab8934 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java @@ -12,64 +12,64 @@ @SuppressWarnings("serial") public class BugInCF extends RuntimeException { - /** - * Constructs a new BugInCF with the specified detail message and no cause (use this at the root - * cause). - * - * @param message the detail message - */ - public BugInCF(String message) { - this(message, new Throwable()); - } + /** + * Constructs a new BugInCF with the specified detail message and no cause (use this at the root + * cause). + * + * @param message the detail message + */ + public BugInCF(String message) { + this(message, new Throwable()); + } - /** - * Constructs a new BugInCF with a detail message composed from the given arguments, and with no - * cause (use the current callstack as the root cause). - * - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public BugInCF(String fmt, @Nullable Object... args) { - this(String.format(fmt, args), new Throwable()); - } + /** + * Constructs a new BugInCF with a detail message composed from the given arguments, and with no + * cause (use the current callstack as the root cause). + * + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public BugInCF(String fmt, @Nullable Object... args) { + this(String.format(fmt, args), new Throwable()); + } - /** - * Constructs a new BugInCF with the specified cause. - * - * @param cause the cause; its detail message will be used and must be non-null - */ - @SuppressWarnings("nullness") - public BugInCF(Throwable cause) { - this(cause.getMessage(), new Throwable()); - } + /** + * Constructs a new BugInCF with the specified cause. + * + * @param cause the cause; its detail message will be used and must be non-null + */ + @SuppressWarnings("nullness") + public BugInCF(Throwable cause) { + this(cause.getMessage(), new Throwable()); + } - /** - * Constructs a new BugInCF with the specified cause and with a detail message composed from the - * given arguments. - * - * @param cause the cause - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public BugInCF(Throwable cause, String fmt, @Nullable Object... args) { - this(String.format(fmt, args), cause); - } + /** + * Constructs a new BugInCF with the specified cause and with a detail message composed from the + * given arguments. + * + * @param cause the cause + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public BugInCF(Throwable cause, String fmt, @Nullable Object... args) { + this(String.format(fmt, args), cause); + } - /** - * Constructs a new BugInCF with the specified detail message and cause. - * - * @param message the detail message - * @param cause the cause - */ - public BugInCF(String message, Throwable cause) { - super(message, cause); - if (message == null) { - throw new BugInCF("Must have a detail message."); - } - if (cause == null) { - throw new BugInCF("Must have a cause throwable."); - } + /** + * Constructs a new BugInCF with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause + */ + public BugInCF(String message, Throwable cause) { + super(message, cause); + if (message == null) { + throw new BugInCF("Must have a detail message."); + } + if (cause == null) { + throw new BugInCF("Must have a cause throwable."); } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java index f1783877b1f..fd07e7f1240 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java @@ -1,225 +1,222 @@ package org.checkerframework.javacutil; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.plumelib.util.DeepCopyable; import org.plumelib.util.UtilPlume; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - /** Utility methods related to Java Collections. */ public class CollectionUtils { - /** Do not instantiate. */ - private CollectionUtils() { - throw new Error("Do not instantiate"); + /** Do not instantiate. */ + private CollectionUtils() { + throw new Error("Do not instantiate"); + } + + /// + /// Deprecated utility methods + /// + + /** + * Creates a LRU cache. + * + * @param size size of the cache + * @return a new cache with the provided size + * @deprecated use org.plumelib.util.CollectionsPlume.createLruCache + */ + @Deprecated // 2023-06-02 + public static Map createLRUCache(int size) { + return new LinkedHashMap(size, .75F, true) { + + private static final long serialVersionUID = 5261489276168775084L; + + @Override + protected boolean removeEldestEntry(Map.Entry entry) { + return size() > size; + } + }; + } + + // A "deep copy" uses the deepCopy() method of the DeepCopyable interface. + + /** + * Returns a copy of {@code orig}, where each element of the result is a clone of the + * corresponding element of {@code orig}. + * + * @param the type of elements of the collection + * @param the type of the collection + * @param orig a collection + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements + */ + @SuppressWarnings({ + "signedness", // problem with clone() + "nullness" // generics problem + }) + @Deprecated // 2023-06-02 + public static > + @PolyNull C cloneElements(@PolyNull C orig) { + if (orig == null) { + return null; } - - /// - /// Deprecated utility methods - /// - - /** - * Creates a LRU cache. - * - * @param size size of the cache - * @return a new cache with the provided size - * @deprecated use org.plumelib.util.CollectionsPlume.createLruCache - */ - @Deprecated // 2023-06-02 - public static Map createLRUCache(int size) { - return new LinkedHashMap(size, .75F, true) { - - private static final long serialVersionUID = 5261489276168775084L; - - @Override - protected boolean removeEldestEntry(Map.Entry entry) { - return size() > size; - } - }; + C result = UtilPlume.clone(orig); + result.clear(); + for (T elt : orig) { + result.add(UtilPlume.clone(elt)); } - - // A "deep copy" uses the deepCopy() method of the DeepCopyable interface. - - /** - * Returns a copy of {@code orig}, where each element of the result is a clone of the - * corresponding element of {@code orig}. - * - * @param the type of elements of the collection - * @param the type of the collection - * @param orig a collection - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements - */ - @SuppressWarnings({ - "signedness", // problem with clone() - "nullness" // generics problem - }) - @Deprecated // 2023-06-02 - public static > - @PolyNull C cloneElements(@PolyNull C orig) { - if (orig == null) { - return null; - } - C result = UtilPlume.clone(orig); - result.clear(); - for (T elt : orig) { - result.add(UtilPlume.clone(elt)); - } - return result; + return result; + } + + /** + * Returns a copy of {@code orig}, where each key and value in the result is a clone of the + * corresponding element of {@code orig}. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static > @PolyNull M cloneElements(@PolyNull M orig) { + return cloneElements(orig, true); + } + + /** + * Returns a copy of {@code orig}, where each value of the result is a clone of the corresponding + * value of {@code orig}, but the keys are the same objects. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneValues + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static > @PolyNull M cloneValues(@PolyNull M orig) { + return cloneElements(orig, false); + } + + /** + * Returns a copy of {@code orig}, where each key and value in the result is a clone of the + * corresponding element of {@code orig}. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @param cloneKeys if true, clone keys; otherwise, re-use them + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + private static > @PolyNull M cloneElements( + @PolyNull M orig, boolean cloneKeys) { + if (orig == null) { + return null; } - - /** - * Returns a copy of {@code orig}, where each key and value in the result is a clone of the - * corresponding element of {@code orig}. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static > @PolyNull M cloneElements( - @PolyNull M orig) { - return cloneElements(orig, true); + M result = UtilPlume.clone(orig); + result.clear(); + for (Map.Entry mapEntry : orig.entrySet()) { + K oldKey = mapEntry.getKey(); + K newKey = cloneKeys ? UtilPlume.clone(oldKey) : oldKey; + result.put(newKey, UtilPlume.clone(mapEntry.getValue())); } - - /** - * Returns a copy of {@code orig}, where each value of the result is a clone of the - * corresponding value of {@code orig}, but the keys are the same objects. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneValues - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static > @PolyNull M cloneValues(@PolyNull M orig) { - return cloneElements(orig, false); + return result; + } + + /** + * Returns a copy of {@code orig}, where each element of the result is a deep copy (according to + * the {@code DeepCopyable} interface) of the corresponding element of {@code orig}. + * + * @param the type of elements of the collection + * @param the type of the collection + * @param orig a collection + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"signedness", "nullness:argument"}) // problem with clone() + public static , C extends @Nullable Collection> + @PolyNull C deepCopy(@PolyNull C orig) { + if (orig == null) { + return null; } - - /** - * Returns a copy of {@code orig}, where each key and value in the result is a clone of the - * corresponding element of {@code orig}. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @param cloneKeys if true, clone keys; otherwise, re-use them - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - private static > @PolyNull M cloneElements( - @PolyNull M orig, boolean cloneKeys) { - if (orig == null) { - return null; - } - M result = UtilPlume.clone(orig); - result.clear(); - for (Map.Entry mapEntry : orig.entrySet()) { - K oldKey = mapEntry.getKey(); - K newKey = cloneKeys ? UtilPlume.clone(oldKey) : oldKey; - result.put(newKey, UtilPlume.clone(mapEntry.getValue())); - } - return result; + C result = UtilPlume.clone(orig); + result.clear(); + for (T elt : orig) { + result.add(DeepCopyable.deepCopyOrNull(elt)); } - - /** - * Returns a copy of {@code orig}, where each element of the result is a deep copy (according to - * the {@code DeepCopyable} interface) of the corresponding element of {@code orig}. - * - * @param the type of elements of the collection - * @param the type of the collection - * @param orig a collection - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"signedness", "nullness:argument"}) // problem with clone() - public static , C extends @Nullable Collection> - @PolyNull C deepCopy(@PolyNull C orig) { - if (orig == null) { - return null; - } - C result = UtilPlume.clone(orig); - result.clear(); - for (T elt : orig) { - result.add(DeepCopyable.deepCopyOrNull(elt)); - } - return result; + return result; + } + + // The following two methods cannot share an implementation because their generic bounds differ. + + /** + * Returns a copy of {@code orig}, where each key and value in the result is a deep copy + * (according to the {@code DeepCopyable} interface) of the corresponding element of {@code orig}. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static < + K extends @Nullable DeepCopyable, + V extends @Nullable DeepCopyable, + M extends @Nullable Map> + @PolyNull M deepCopy(@PolyNull M orig) { + if (orig == null) { + return null; } - - // The following two methods cannot share an implementation because their generic bounds differ. - - /** - * Returns a copy of {@code orig}, where each key and value in the result is a deep copy - * (according to the {@code DeepCopyable} interface) of the corresponding element of {@code - * orig}. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static < - K extends @Nullable DeepCopyable, - V extends @Nullable DeepCopyable, - M extends @Nullable Map> - @PolyNull M deepCopy(@PolyNull M orig) { - if (orig == null) { - return null; - } - M result = UtilPlume.clone(orig); - result.clear(); - for (Map.Entry mapEntry : orig.entrySet()) { - K oldKey = mapEntry.getKey(); - V oldValue = mapEntry.getValue(); - result.put(DeepCopyable.deepCopyOrNull(oldKey), DeepCopyable.deepCopyOrNull(oldValue)); - } - return result; + M result = UtilPlume.clone(orig); + result.clear(); + for (Map.Entry mapEntry : orig.entrySet()) { + K oldKey = mapEntry.getKey(); + V oldValue = mapEntry.getValue(); + result.put(DeepCopyable.deepCopyOrNull(oldKey), DeepCopyable.deepCopyOrNull(oldValue)); } - - /** - * Returns a copy of {@code orig}, where each value of the result is a deep copy (according to - * the {@code DeepCopyable} interface) of the corresponding value of {@code orig}, but the keys - * are the same objects. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.deepCopyValues - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static , M extends @Nullable Map> - @PolyNull M deepCopyValues(@PolyNull M orig) { - if (orig == null) { - return null; - } - M result = UtilPlume.clone(orig); - result.clear(); - for (Map.Entry mapEntry : orig.entrySet()) { - K oldKey = mapEntry.getKey(); - V oldValue = mapEntry.getValue(); - result.put(oldKey, DeepCopyable.deepCopyOrNull(oldValue)); - } - return result; + return result; + } + + /** + * Returns a copy of {@code orig}, where each value of the result is a deep copy (according to the + * {@code DeepCopyable} interface) of the corresponding value of {@code orig}, but the keys are + * the same objects. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.deepCopyValues + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static , M extends @Nullable Map> + @PolyNull M deepCopyValues(@PolyNull M orig) { + if (orig == null) { + return null; + } + M result = UtilPlume.clone(orig); + result.clear(); + for (Map.Entry mapEntry : orig.entrySet()) { + K oldKey = mapEntry.getKey(); + V oldValue = mapEntry.getValue(); + result.put(oldKey, DeepCopyable.deepCopyOrNull(oldValue)); } + return result; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java b/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java index affdebdad2e..c892c67a11f 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java @@ -12,30 +12,29 @@ @Deprecated // 2023-06-02 public interface DeepCopyable { - /** - * Returns a deep copy of this. A deep copy is equal to the original, but side effects to either - * object are not visible in the other. A deep copy may share immutable state with the original. - * - *

          The run-time class of the result is identical to the run-time class of this. The deep copy - * is equal to {@code this} (per {@code equals()} if the object's class does not use reference - * equality as {@code Object.equals()} does). - * - * @return a deep copy of this - */ - T deepCopy(); + /** + * Returns a deep copy of this. A deep copy is equal to the original, but side effects to either + * object are not visible in the other. A deep copy may share immutable state with the original. + * + *

          The run-time class of the result is identical to the run-time class of this. The deep copy + * is equal to {@code this} (per {@code equals()} if the object's class does not use reference + * equality as {@code Object.equals()} does). + * + * @return a deep copy of this + */ + T deepCopy(); - /** - * Returns the deep copy of a non-null argument, or {@code null} for a {@code null} argument. - * - * @param object object to copy - * @return the deep copy of a non-null argument, or {@code null} for a {@code null} argument - * @param the type of the object - */ - static > @PolyNull T2 deepCopyOrNull( - @PolyNull T2 object) { - if (object == null) { - return null; - } - return object.deepCopy(); + /** + * Returns the deep copy of a non-null argument, or {@code null} for a {@code null} argument. + * + * @param object object to copy + * @return the deep copy of a non-null argument, or {@code null} for a {@code null} argument + * @param the type of the object + */ + static > @PolyNull T2 deepCopyOrNull(@PolyNull T2 object) { + if (object == null) { + return null; } + return object.deepCopy(); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java index 831fa8ac721..ffcd027bb94 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java @@ -8,14 +8,6 @@ import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.plumelib.util.ArraySet; -import org.plumelib.util.CollectionsPlume; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayDeque; @@ -28,7 +20,6 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -46,6 +37,12 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.JavaFileObject; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.plumelib.util.ArraySet; +import org.plumelib.util.CollectionsPlume; /** * Utility methods for analyzing {@code Element}s. This complements {@link Elements}, providing @@ -53,1133 +50,1120 @@ */ public class ElementUtils { - // Class cannot be instantiated. - private ElementUtils() { - throw new AssertionError("Class ElementUtils cannot be instantiated."); + // Class cannot be instantiated. + private ElementUtils() { + throw new AssertionError("Class ElementUtils cannot be instantiated."); + } + + /** The value of Flags.COMPACT_RECORD_CONSTRUCTOR which does not exist in Java 9 or 11. */ + private static final long Flags_COMPACT_RECORD_CONSTRUCTOR = 1L << 51; + + /** The value of Flags.GENERATED_MEMBER which does not exist in Java 9 or 11. */ + private static final long Flags_GENERATED_MEMBER = 16777216; + + /** + * Returns the innermost type element that is, or encloses, the given element. + * + *

          Note that in this code: + * + *

          {@code
          +   * class Outer {
          +   *   static class Inner {  }
          +   * }
          +   * }
          + * + * {@code Inner} has no enclosing type, but this method returns {@code Outer}. + * + * @param elem the enclosed element of a class + * @return the innermost type element (possibly the argument itself), or null if {@code elem} is + * not, and is not enclosed by, a type element + */ + public static @Nullable TypeElement enclosingTypeElement(Element elem) { + Element result = elem; + while (result != null && !isTypeElement(result)) { + result = result.getEnclosingElement(); } - - /** The value of Flags.COMPACT_RECORD_CONSTRUCTOR which does not exist in Java 9 or 11. */ - private static final long Flags_COMPACT_RECORD_CONSTRUCTOR = 1L << 51; - - /** The value of Flags.GENERATED_MEMBER which does not exist in Java 9 or 11. */ - private static final long Flags_GENERATED_MEMBER = 16777216; - - /** - * Returns the innermost type element that is, or encloses, the given element. - * - *

          Note that in this code: - * - *

          {@code
          -     * class Outer {
          -     *   static class Inner {  }
          -     * }
          -     * }
          - * - * {@code Inner} has no enclosing type, but this method returns {@code Outer}. - * - * @param elem the enclosed element of a class - * @return the innermost type element (possibly the argument itself), or null if {@code elem} is - * not, and is not enclosed by, a type element - */ - public static @Nullable TypeElement enclosingTypeElement(Element elem) { - Element result = elem; - while (result != null && !isTypeElement(result)) { - result = result.getEnclosingElement(); - } - return (TypeElement) result; + return (TypeElement) result; + } + + /** + * Returns the innermost type element enclosing the given element, that is different from the + * element itself. By contrast, {@link #enclosingTypeElement} returns its argument if the argument + * is a type element. + * + * @param elem the enclosed element of a class + * @return the innermost type element, or null if no type element encloses {@code elem} + */ + public static @Nullable TypeElement strictEnclosingTypeElement(Element elem) { + Element enclosingElement = elem.getEnclosingElement(); + if (enclosingElement == null) { + return null; } - /** - * Returns the innermost type element enclosing the given element, that is different from the - * element itself. By contrast, {@link #enclosingTypeElement} returns its argument if the - * argument is a type element. - * - * @param elem the enclosed element of a class - * @return the innermost type element, or null if no type element encloses {@code elem} - */ - public static @Nullable TypeElement strictEnclosingTypeElement(Element elem) { - Element enclosingElement = elem.getEnclosingElement(); - if (enclosingElement == null) { - return null; - } - - return enclosingTypeElement(enclosingElement); + return enclosingTypeElement(enclosingElement); + } + + /** + * Returns the top-level type element that contains {@code element}. + * + * @param element the element whose enclosing tye element to find + * @return a type element containing {@code element} that isn't contained in another class + */ + public static TypeElement toplevelEnclosingTypeElement(Element element) { + TypeElement result = enclosingTypeElement(element); + if (result == null) { + return (TypeElement) element; } - /** - * Returns the top-level type element that contains {@code element}. - * - * @param element the element whose enclosing tye element to find - * @return a type element containing {@code element} that isn't contained in another class - */ - public static TypeElement toplevelEnclosingTypeElement(Element element) { - TypeElement result = enclosingTypeElement(element); - if (result == null) { - return (TypeElement) element; - } - - TypeElement enclosing = strictEnclosingTypeElement(result); - while (enclosing != null) { - result = enclosing; - enclosing = strictEnclosingTypeElement(enclosing); - } - - return result; + TypeElement enclosing = strictEnclosingTypeElement(result); + while (enclosing != null) { + result = enclosing; + enclosing = strictEnclosingTypeElement(enclosing); } - /** - * Returns the binary name of the class enclosing {@code executableElement}. - * - * @param executableElement the ExecutableElement - * @return the binary name of the class enclosing {@code executableElement} - */ - public static @BinaryName String getEnclosingClassName(ExecutableElement executableElement) { - return getBinaryName(((MethodSymbol) executableElement).enclClass()); + return result; + } + + /** + * Returns the binary name of the class enclosing {@code executableElement}. + * + * @param executableElement the ExecutableElement + * @return the binary name of the class enclosing {@code executableElement} + */ + public static @BinaryName String getEnclosingClassName(ExecutableElement executableElement) { + return getBinaryName(((MethodSymbol) executableElement).enclClass()); + } + + /** + * Returns the binary name of the class enclosing {@code variableElement}. + * + * @param variableElement the VariableElement + * @return the binary name of the class enclosing {@code variableElement} + */ + public static @BinaryName String getEnclosingClassName(VariableElement variableElement) { + TypeElement enclosingType = enclosingTypeElement(variableElement); + if (enclosingType == null) { + throw new BugInCF("enclosingTypeElement(%s) is null", variableElement); } - - /** - * Returns the binary name of the class enclosing {@code variableElement}. - * - * @param variableElement the VariableElement - * @return the binary name of the class enclosing {@code variableElement} - */ - public static @BinaryName String getEnclosingClassName(VariableElement variableElement) { - TypeElement enclosingType = enclosingTypeElement(variableElement); - if (enclosingType == null) { - throw new BugInCF("enclosingTypeElement(%s) is null", variableElement); - } - return getBinaryName(enclosingType); + return getBinaryName(enclosingType); + } + + /** + * Returns the innermost package element enclosing the given element. The same effect as {@link + * javax.lang.model.util.Elements#getPackageOf(Element)}. Returns the element itself if it is a + * package. + * + * @param elem the enclosed element of a package + * @return the innermost package element + */ + public static PackageElement enclosingPackage(Element elem) { + Element result = elem; + while (result != null && result.getKind() != ElementKind.PACKAGE) { + result = result.getEnclosingElement(); } - - /** - * Returns the innermost package element enclosing the given element. The same effect as {@link - * javax.lang.model.util.Elements#getPackageOf(Element)}. Returns the element itself if it is a - * package. - * - * @param elem the enclosed element of a package - * @return the innermost package element - */ - public static PackageElement enclosingPackage(Element elem) { - Element result = elem; - while (result != null && result.getKind() != ElementKind.PACKAGE) { - result = result.getEnclosingElement(); - } - return (PackageElement) result; + return (PackageElement) result; + } + + /** + * Returns the "parent" package element for the given package element. For package "A.B" it gives + * "A". For package "A" it gives the default package. For the default package it returns null. + * + *

          Note that packages are not enclosed within each other, we have to manually climb the + * namespaces. Calling "enclosingPackage" on a package element returns the package element itself + * again. + * + * @param elem the package to start from + * @param elements the element + * @return the parent package element or {@code null} + */ + public static @Nullable PackageElement parentPackage(PackageElement elem, Elements elements) { + // The following might do the same thing: + // ((Symbol) elt).owner; + // TODO: verify and see whether the change is worth it. + String fqnstart = elem.getQualifiedName().toString(); + String fqn = fqnstart; + if (fqn != null && !fqn.isEmpty()) { + int dotPos = fqn.lastIndexOf('.'); + if (dotPos != -1) { + return elements.getPackageElement(fqn.substring(0, dotPos)); + } } - - /** - * Returns the "parent" package element for the given package element. For package "A.B" it - * gives "A". For package "A" it gives the default package. For the default package it returns - * null. - * - *

          Note that packages are not enclosed within each other, we have to manually climb the - * namespaces. Calling "enclosingPackage" on a package element returns the package element - * itself again. - * - * @param elem the package to start from - * @param elements the element - * @return the parent package element or {@code null} - */ - public static @Nullable PackageElement parentPackage(PackageElement elem, Elements elements) { - // The following might do the same thing: - // ((Symbol) elt).owner; - // TODO: verify and see whether the change is worth it. - String fqnstart = elem.getQualifiedName().toString(); - String fqn = fqnstart; - if (fqn != null && !fqn.isEmpty()) { - int dotPos = fqn.lastIndexOf('.'); - if (dotPos != -1) { - return elements.getPackageElement(fqn.substring(0, dotPos)); - } - } - return null; + return null; + } + + /** + * Returns true if the element is a static element: whether it is a static field, static method, + * or static class. + * + * @return true if element is static + */ + public static boolean isStatic(Element element) { + return element.getModifiers().contains(Modifier.STATIC); + } + + /** + * Returns true if the element is a final element: a final field, final method, or final class. + * + * @return true if the element is final + */ + public static boolean isFinal(Element element) { + return element.getModifiers().contains(Modifier.FINAL); + } + + /** + * Returns true if the element is a effectively final element. + * + * @return true if the element is effectively final + */ + public static boolean isEffectivelyFinal(Element element) { + Symbol sym = (Symbol) element; + if (sym.getEnclosingElement().getKind() == ElementKind.METHOD + && (sym.getEnclosingElement().flags() & Flags.ABSTRACT) != 0) { + return true; } - - /** - * Returns true if the element is a static element: whether it is a static field, static method, - * or static class. - * - * @return true if element is static - */ - public static boolean isStatic(Element element) { - return element.getModifiers().contains(Modifier.STATIC); + return (sym.flags() & (Flags.FINAL | Flags.EFFECTIVELY_FINAL)) != 0; + } + + /** + * Returns the {@code TypeMirror} for usage of Element as a value. It returns the return type of a + * method element, the class type of a constructor, or simply the type mirror of the element + * itself. + * + * @param element the element whose type to obtain + * @return the type for the element used as a value + */ + @SuppressWarnings("nullness:dereference.of.nullable") // a constructor has an enclosing class + public static TypeMirror getType(Element element) { + if (element.getKind() == ElementKind.METHOD) { + return ((ExecutableElement) element).getReturnType(); + } else if (element.getKind() == ElementKind.CONSTRUCTOR) { + return enclosingTypeElement(element).asType(); + } else { + return element.asType(); } - - /** - * Returns true if the element is a final element: a final field, final method, or final class. - * - * @return true if the element is final - */ - public static boolean isFinal(Element element) { - return element.getModifiers().contains(Modifier.FINAL); + } + + /** + * Returns the qualified name of the innermost class enclosing the provided {@code Element}. + * + * @param element an element enclosed by a class, or a {@code TypeElement} + * @return the qualified {@code Name} of the innermost class enclosing the element + */ + public static @Nullable Name getQualifiedClassName(Element element) { + if (element.getKind() == ElementKind.PACKAGE) { + PackageElement elem = (PackageElement) element; + return elem.getQualifiedName(); } - /** - * Returns true if the element is a effectively final element. - * - * @return true if the element is effectively final - */ - public static boolean isEffectivelyFinal(Element element) { - Symbol sym = (Symbol) element; - if (sym.getEnclosingElement().getKind() == ElementKind.METHOD - && (sym.getEnclosingElement().flags() & Flags.ABSTRACT) != 0) { - return true; - } - return (sym.flags() & (Flags.FINAL | Flags.EFFECTIVELY_FINAL)) != 0; + TypeElement elem = enclosingTypeElement(element); + if (elem == null) { + return null; } - /** - * Returns the {@code TypeMirror} for usage of Element as a value. It returns the return type of - * a method element, the class type of a constructor, or simply the type mirror of the element - * itself. - * - * @param element the element whose type to obtain - * @return the type for the element used as a value - */ - @SuppressWarnings("nullness:dereference.of.nullable") // a constructor has an enclosing class - public static TypeMirror getType(Element element) { - if (element.getKind() == ElementKind.METHOD) { - return ((ExecutableElement) element).getReturnType(); - } else if (element.getKind() == ElementKind.CONSTRUCTOR) { - return enclosingTypeElement(element).asType(); - } else { - return element.asType(); - } + return elem.getQualifiedName(); + } + + /** + * Returns a verbose name that identifies the element. + * + * @param elt the element whose name to obtain + * @return the qualified name of the given element + */ + public static String getQualifiedName(Element elt) { + if (elt.getKind() == ElementKind.PACKAGE || isTypeElement(elt)) { + Name n = getQualifiedClassName(elt); + if (n == null) { + return "Unexpected element: " + elt; + } + return n.toString(); + } else { + return getQualifiedName(elt.getEnclosingElement()) + "." + elt; } - - /** - * Returns the qualified name of the innermost class enclosing the provided {@code Element}. - * - * @param element an element enclosed by a class, or a {@code TypeElement} - * @return the qualified {@code Name} of the innermost class enclosing the element - */ - public static @Nullable Name getQualifiedClassName(Element element) { - if (element.getKind() == ElementKind.PACKAGE) { - PackageElement elem = (PackageElement) element; - return elem.getQualifiedName(); - } - - TypeElement elem = enclosingTypeElement(element); - if (elem == null) { - return null; - } - - return elem.getQualifiedName(); + } + + /** + * Returns the binary name of the given type. + * + * @param te a type + * @return the binary name of the type + */ + @SuppressWarnings("signature:return.type.incompatible") // string manipulation + public static @BinaryName String getBinaryName(TypeElement te) { + Element enclosing = te.getEnclosingElement(); + String simpleName = te.getSimpleName().toString(); + if (enclosing == null) { // is this possible? + return simpleName; } - - /** - * Returns a verbose name that identifies the element. - * - * @param elt the element whose name to obtain - * @return the qualified name of the given element - */ - public static String getQualifiedName(Element elt) { - if (elt.getKind() == ElementKind.PACKAGE || isTypeElement(elt)) { - Name n = getQualifiedClassName(elt); - if (n == null) { - return "Unexpected element: " + elt; - } - return n.toString(); - } else { - return getQualifiedName(elt.getEnclosingElement()) + "." + elt; - } + if (ElementUtils.isTypeElement(enclosing)) { + return getBinaryName((TypeElement) enclosing) + "$" + simpleName; + } else if (enclosing.getKind() == ElementKind.PACKAGE) { + PackageElement pe = (PackageElement) enclosing; + if (pe.isUnnamed()) { + return simpleName; + } else { + return pe.getQualifiedName() + "." + simpleName; + } + } else { + // This case occurs for anonymous inner classes. Fall back to the flatname method. + return ((ClassSymbol) te).flatName().toString(); } - - /** - * Returns the binary name of the given type. - * - * @param te a type - * @return the binary name of the type - */ - @SuppressWarnings("signature:return.type.incompatible") // string manipulation - public static @BinaryName String getBinaryName(TypeElement te) { - Element enclosing = te.getEnclosingElement(); - String simpleName = te.getSimpleName().toString(); - if (enclosing == null) { // is this possible? - return simpleName; - } - if (ElementUtils.isTypeElement(enclosing)) { - return getBinaryName((TypeElement) enclosing) + "$" + simpleName; - } else if (enclosing.getKind() == ElementKind.PACKAGE) { - PackageElement pe = (PackageElement) enclosing; - if (pe.isUnnamed()) { - return simpleName; - } else { - return pe.getQualifiedName() + "." + simpleName; - } - } else { - // This case occurs for anonymous inner classes. Fall back to the flatname method. - return ((ClassSymbol) te).flatName().toString(); - } + } + + /** + * Returns the canonical representation of the method declaration, which contains simple names of + * the types only. + * + * @param element a method declaration + * @return the simple name of the method, followed by the simple names of the formal parameter + * types + */ + public static String getSimpleSignature(ExecutableElement element) { + // note: constructor simple name is + StringJoiner sj = new StringJoiner(",", element.getSimpleName() + "(", ")"); + for (Iterator i = element.getParameters().iterator(); + i.hasNext(); ) { + sj.add(TypesUtils.simpleTypeName(i.next().asType())); } - - /** - * Returns the canonical representation of the method declaration, which contains simple names - * of the types only. - * - * @param element a method declaration - * @return the simple name of the method, followed by the simple names of the formal parameter - * types - */ - public static String getSimpleSignature(ExecutableElement element) { - // note: constructor simple name is - StringJoiner sj = new StringJoiner(",", element.getSimpleName() + "(", ")"); - for (Iterator i = element.getParameters().iterator(); - i.hasNext(); ) { - sj.add(TypesUtils.simpleTypeName(i.next().asType())); - } - return sj.toString(); + return sj.toString(); + } + + /** + * Returns a user-friendly name for the given method. Does not return {@code ""} or {@code + * ""} as ExecutableElement.getSimpleName() does. + * + * @param element a method declaration + * @return a user-friendly name for the method + * @deprecated use {@link #getSimpleDescription} + */ + @Deprecated // 2023-06-01 + public static CharSequence getSimpleNameOrDescription(ExecutableElement element) { + Name result = element.getSimpleName(); + switch (result.toString()) { + case "": + return element.getEnclosingElement().getSimpleName(); + case "": + return "class initializer"; + default: + return result; } - - /** - * Returns a user-friendly name for the given method. Does not return {@code ""} or {@code - * ""} as ExecutableElement.getSimpleName() does. - * - * @param element a method declaration - * @return a user-friendly name for the method - * @deprecated use {@link #getSimpleDescription} - */ - @Deprecated // 2023-06-01 - public static CharSequence getSimpleNameOrDescription(ExecutableElement element) { - Name result = element.getSimpleName(); - switch (result.toString()) { - case "": - return element.getEnclosingElement().getSimpleName(); - case "": - return "class initializer"; - default: - return result; - } + } + + /** + * Returns a user-friendly name for the given method, which includes the name of the enclosing + * type. Does not return {@code ""} or {@code ""} as + * ExecutableElement.getSimpleName() does. + * + * @param element a method declaration + * @return a user-friendly name for the method + */ + public static CharSequence getSimpleDescription(ExecutableElement element) { + String enclosingTypeName = + ((TypeElement) element.getEnclosingElement()).getSimpleName().toString(); + Name methodName = element.getSimpleName(); + switch (methodName.toString()) { + case "": + return enclosingTypeName + " constructor"; + case "": + return "class initializer for " + enclosingTypeName; + default: + return enclosingTypeName + "." + methodName; } - - /** - * Returns a user-friendly name for the given method, which includes the name of the enclosing - * type. Does not return {@code ""} or {@code ""} as - * ExecutableElement.getSimpleName() does. - * - * @param element a method declaration - * @return a user-friendly name for the method - */ - public static CharSequence getSimpleDescription(ExecutableElement element) { - String enclosingTypeName = - ((TypeElement) element.getEnclosingElement()).getSimpleName().toString(); - Name methodName = element.getSimpleName(); - switch (methodName.toString()) { - case "": - return enclosingTypeName + " constructor"; - case "": - return "class initializer for " + enclosingTypeName; - default: - return enclosingTypeName + "." + methodName; - } + } + + /** + * Check if the element is an element for 'java.lang.Object' + * + * @param element the type element + * @return true iff the element is java.lang.Object element + */ + public static boolean isObject(TypeElement element) { + return element.getQualifiedName().contentEquals("java.lang.Object"); + } + + /** + * Check if the element is an element for 'java.lang.String' + * + * @param element the type element + * @return true iff the element is java.lang.String element + */ + public static boolean isString(TypeElement element) { + return element.getQualifiedName().contentEquals("java.lang.String"); + } + + /** + * Returns true if the element is a reference to a compile-time constant. + * + * @param elt an element + * @return true if the element is a reference to a compile-time constant + */ + public static boolean isCompileTimeConstant(@Nullable Element elt) { + return elt != null + && (elt.getKind() == ElementKind.FIELD || elt.getKind() == ElementKind.LOCAL_VARIABLE) + && ((VariableElement) elt).getConstantValue() != null; + } + + /** + * Checks whether a given element came from a source file. + * + *

          By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a + * classfile for the given element, even if there is also a source file. + * + * @param element the element to check, or null + * @return true if a source file containing the element is being compiled + */ + public static boolean isElementFromSourceCode(@Nullable Element element) { + if (element == null) { + return false; } - - /** - * Check if the element is an element for 'java.lang.Object' - * - * @param element the type element - * @return true iff the element is java.lang.Object element - */ - public static boolean isObject(TypeElement element) { - return element.getQualifiedName().contentEquals("java.lang.Object"); + TypeElement enclosingTypeElement = enclosingTypeElement(element); + if (enclosingTypeElement == null) { + throw new BugInCF("enclosingTypeElement(%s) is null", element); } - - /** - * Check if the element is an element for 'java.lang.String' - * - * @param element the type element - * @return true iff the element is java.lang.String element - */ - public static boolean isString(TypeElement element) { - return element.getQualifiedName().contentEquals("java.lang.String"); + return isElementFromSourceCodeImpl((Symbol.ClassSymbol) enclosingTypeElement); + } + + /** + * Checks whether a given ClassSymbol came from a source file. + * + *

          By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a + * classfile for the given element, even if there is also a source file. + * + * @param symbol the class to check + * @return true if a source file containing the class is being compiled + */ + private static boolean isElementFromSourceCodeImpl(Symbol.ClassSymbol symbol) { + // This is a bit of a hack to avoid treating JDK as source files. JDK files' toUri() method + // returns just the name of the file (e.g. "Object.java"), but any file actually being + // compiled returns a file URI to the source file. + return symbol.sourcefile != null + && symbol.sourcefile.getKind() == JavaFileObject.Kind.SOURCE + && symbol.sourcefile.toUri().toString().startsWith("file:"); + } + + /** + * Returns true if the element is declared in ByteCode. Always return false if elt is a package. + * + * @param elt some element + * @return true if the element is declared in ByteCode + */ + public static boolean isElementFromByteCode(@Nullable Element elt) { + if (elt == null) { + return false; } - /** - * Returns true if the element is a reference to a compile-time constant. - * - * @param elt an element - * @return true if the element is a reference to a compile-time constant - */ - public static boolean isCompileTimeConstant(@Nullable Element elt) { - return elt != null - && (elt.getKind() == ElementKind.FIELD - || elt.getKind() == ElementKind.LOCAL_VARIABLE) - && ((VariableElement) elt).getConstantValue() != null; + if (elt instanceof Symbol.ClassSymbol) { + Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt; + if (null != clss.classfile) { + // The class file could be a .java file + return clss.classfile.getKind() == JavaFileObject.Kind.CLASS; + } else { + return elt.asType().getKind().isPrimitive(); + } } - - /** - * Checks whether a given element came from a source file. - * - *

          By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is - * a classfile for the given element, even if there is also a source file. - * - * @param element the element to check, or null - * @return true if a source file containing the element is being compiled - */ - public static boolean isElementFromSourceCode(@Nullable Element element) { - if (element == null) { - return false; - } - TypeElement enclosingTypeElement = enclosingTypeElement(element); - if (enclosingTypeElement == null) { - throw new BugInCF("enclosingTypeElement(%s) is null", element); - } - return isElementFromSourceCodeImpl((Symbol.ClassSymbol) enclosingTypeElement); + return isElementFromByteCode(elt.getEnclosingElement()); + } + + /** + * Returns the path to the source file containing {@code element}, which must be from source code. + * + * @param element the type element to look at + * @return path to the source file containing {@code element} + */ + public static String getSourceFilePath(TypeElement element) { + String path = ((ClassSymbol) element).sourcefile.toUri().getPath(); + if (path == null) { + throw new BugInCF("Unexpected null path for TypeElement: " + element); } - - /** - * Checks whether a given ClassSymbol came from a source file. - * - *

          By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is - * a classfile for the given element, even if there is also a source file. - * - * @param symbol the class to check - * @return true if a source file containing the class is being compiled - */ - private static boolean isElementFromSourceCodeImpl(Symbol.ClassSymbol symbol) { - // This is a bit of a hack to avoid treating JDK as source files. JDK files' toUri() method - // returns just the name of the file (e.g. "Object.java"), but any file actually being - // compiled returns a file URI to the source file. - return symbol.sourcefile != null - && symbol.sourcefile.getKind() == JavaFileObject.Kind.SOURCE - && symbol.sourcefile.toUri().toString().startsWith("file:"); + return path; + } + + /** + * Returns the field of the class or {@code null} if not found. + * + * @param type the TypeElement to search + * @param name name of a field + * @return the VariableElement for the field if it was found, null otherwise + */ + public static @Nullable VariableElement findFieldInType(TypeElement type, String name) { + for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { + if (field.getSimpleName().contentEquals(name)) { + return field; + } } - - /** - * Returns true if the element is declared in ByteCode. Always return false if elt is a package. - * - * @param elt some element - * @return true if the element is declared in ByteCode - */ - public static boolean isElementFromByteCode(@Nullable Element elt) { - if (elt == null) { - return false; - } - - if (elt instanceof Symbol.ClassSymbol) { - Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt; - if (null != clss.classfile) { - // The class file could be a .java file - return clss.classfile.getKind() == JavaFileObject.Kind.CLASS; - } else { - return elt.asType().getKind().isPrimitive(); - } - } - return isElementFromByteCode(elt.getEnclosingElement()); + return null; + } + + /** + * Returns the elements of the fields whose simple names are in {@code names} and are declared in + * {@code type}. + * + *

          If a field isn't declared in {@code type}, its element isn't included in the returned set. + * If none of the fields is declared in {@code type}, the empty set is returned. + * + * @param type where to look for fields + * @param names simple names of fields that might be declared in {@code type} + * @return the elements of the fields whose simple names are {@code names} and are declared in + * {@code type} + */ + public static Set findFieldsInType(TypeElement type, Collection names) { + Set results = ArraySet.newArraySetOrHashSet(names.size()); + for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { + if (names.contains(field.getSimpleName().toString())) { + results.add(field); + } } - - /** - * Returns the path to the source file containing {@code element}, which must be from source - * code. - * - * @param element the type element to look at - * @return path to the source file containing {@code element} - */ - public static String getSourceFilePath(TypeElement element) { - String path = ((ClassSymbol) element).sourcefile.toUri().getPath(); - if (path == null) { - throw new BugInCF("Unexpected null path for TypeElement: " + element); - } - return path; + return results; + } + + /** + * Returns non-private field elements, and side-effects {@code names} to remove them. For every + * field name in {@code names} that is declared in {@code type} or a supertype, add its element to + * the returned set and remove it from {@code names}. + * + *

          When this routine returns, the combination of the return value and {@code names} has the + * same cardinality, and represents the same fields, as {@code names} did when the method was + * called. + * + * @param type where to look for fields + * @param names simple names of fields that might be declared in {@code type} or a supertype. + * Names that are found are removed from this list. + * @return the {@code VariableElement}s for non-private fields that are declared in {@code type} + * whose simple names were in {@code names} when the method was called. + */ + public static Set findFieldsInTypeOrSuperType( + TypeMirror type, Collection names) { + int origCardinality = names.size(); + Set elements = ArraySet.newArraySetOrHashSet(origCardinality); + findFieldsInTypeOrSuperType(type, names, elements); + // Since `names` may contain duplicates, I don't trust the claim in the documentation about + // cardinality. (Does any code depend on the invariant, though?) + if (origCardinality != names.size() + elements.size()) { + throw new BugInCF( + "Bad sizes: %d != %d + %d ; names=%s elements=%s", + origCardinality, names.size(), elements.size(), names, elements); } - - /** - * Returns the field of the class or {@code null} if not found. - * - * @param type the TypeElement to search - * @param name name of a field - * @return the VariableElement for the field if it was found, null otherwise - */ - public static @Nullable VariableElement findFieldInType(TypeElement type, String name) { - for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { - if (field.getSimpleName().contentEquals(name)) { - return field; - } - } - return null; + return elements; + } + + /** + * Side-effects both {@code foundFields} (which starts empty) and {@code notFound}, conceptually + * moving elements from {@code notFound} to {@code foundFields}. + */ + private static void findFieldsInTypeOrSuperType( + TypeMirror type, Collection notFound, Set foundFields) { + if (TypesUtils.isObject(type)) { + return; } - - /** - * Returns the elements of the fields whose simple names are in {@code names} and are declared - * in {@code type}. - * - *

          If a field isn't declared in {@code type}, its element isn't included in the returned set. - * If none of the fields is declared in {@code type}, the empty set is returned. - * - * @param type where to look for fields - * @param names simple names of fields that might be declared in {@code type} - * @return the elements of the fields whose simple names are {@code names} and are declared in - * {@code type} - */ - public static Set findFieldsInType( - TypeElement type, Collection names) { - Set results = ArraySet.newArraySetOrHashSet(names.size()); - for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { - if (names.contains(field.getSimpleName().toString())) { - results.add(field); - } - } - return results; + TypeElement elt = TypesUtils.getTypeElement(type); + assert elt != null : "@AssumeAssertion(nullness): assumption"; + Set fieldElts = findFieldsInType(elt, notFound); + // Use a new list to avoid a ConcurrentModificationException. + for (VariableElement field : new ArrayList<>(fieldElts)) { + if (!field.getModifiers().contains(Modifier.PRIVATE)) { + notFound.remove(field.getSimpleName().toString()); + } else { + fieldElts.remove(field); + } } + foundFields.addAll(fieldElts); - /** - * Returns non-private field elements, and side-effects {@code names} to remove them. For every - * field name in {@code names} that is declared in {@code type} or a supertype, add its element - * to the returned set and remove it from {@code names}. - * - *

          When this routine returns, the combination of the return value and {@code names} has the - * same cardinality, and represents the same fields, as {@code names} did when the method was - * called. - * - * @param type where to look for fields - * @param names simple names of fields that might be declared in {@code type} or a supertype. - * Names that are found are removed from this list. - * @return the {@code VariableElement}s for non-private fields that are declared in {@code type} - * whose simple names were in {@code names} when the method was called. - */ - public static Set findFieldsInTypeOrSuperType( - TypeMirror type, Collection names) { - int origCardinality = names.size(); - Set elements = ArraySet.newArraySetOrHashSet(origCardinality); - findFieldsInTypeOrSuperType(type, names, elements); - // Since `names` may contain duplicates, I don't trust the claim in the documentation about - // cardinality. (Does any code depend on the invariant, though?) - if (origCardinality != names.size() + elements.size()) { - throw new BugInCF( - "Bad sizes: %d != %d + %d ; names=%s elements=%s", - origCardinality, names.size(), elements.size(), names, elements); - } - return elements; + if (!notFound.isEmpty()) { + findFieldsInTypeOrSuperType(elt.getSuperclass(), notFound, foundFields); } - - /** - * Side-effects both {@code foundFields} (which starts empty) and {@code notFound}, conceptually - * moving elements from {@code notFound} to {@code foundFields}. - */ - private static void findFieldsInTypeOrSuperType( - TypeMirror type, Collection notFound, Set foundFields) { - if (TypesUtils.isObject(type)) { - return; - } - TypeElement elt = TypesUtils.getTypeElement(type); - assert elt != null : "@AssumeAssertion(nullness): assumption"; - Set fieldElts = findFieldsInType(elt, notFound); - // Use a new list to avoid a ConcurrentModificationException. - for (VariableElement field : new ArrayList<>(fieldElts)) { - if (!field.getModifiers().contains(Modifier.PRIVATE)) { - notFound.remove(field.getSimpleName().toString()); - } else { - fieldElts.remove(field); - } - } - foundFields.addAll(fieldElts); - - if (!notFound.isEmpty()) { - findFieldsInTypeOrSuperType(elt.getSuperclass(), notFound, foundFields); - } + } + + /** + * Returns true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError". + * + * @param element the element to test + * @return true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError" + */ + public static boolean isError(Element element) { + return element.getClass().getName() + == "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"; // interned + } + + /** + * Does the given element need a receiver for accesses? For example, an access to a local variable + * does not require a receiver. + * + * @param element the element to test + * @return whether the element requires a receiver for accesses + */ + public static boolean hasReceiver(Element element) { + if (element.getKind() == ElementKind.CONSTRUCTOR) { + // The enclosing element of a constructor is the class it creates. + // A constructor can only have a receiver if the class it creates has an outer type. + TypeMirror t = element.getEnclosingElement().asType(); + return TypesUtils.hasEnclosingType(t); + } else if (element.getKind() == ElementKind.FIELD) { + if (ElementUtils.isStatic(element) + // Artificial fields in interfaces are not marked as static, so check that + // the field is not declared in an interface. + || element.getEnclosingElement().getKind().isInterface()) { + return false; + } else { + // In constructors, the element for "this" is a non-static field, but that field + // does not have a receiver. + return !element.getSimpleName().contentEquals("this"); + } } - - /** - * Returns true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError". - * - * @param element the element to test - * @return true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError" - */ - public static boolean isError(Element element) { - return element.getClass().getName() - == "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"; // interned + return element.getKind() == ElementKind.METHOD && !ElementUtils.isStatic(element); + } + + /** + * Returns a type's superclass, or null if it does not have a superclass (it is object or an + * interface, or the superclass is not on the classpath). + * + * @param typeElt a type element + * @return the superclass of {@code typeElt} + */ + public static @Nullable TypeElement getSuperClass(TypeElement typeElt) { + TypeMirror superTypeMirror; + try { + superTypeMirror = typeElt.getSuperclass(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Looking up a supertype failed. This sometimes happens when transitive dependencies + // are not on the classpath. As javac didn't complain, let's also not complain. + return null; } - /** - * Does the given element need a receiver for accesses? For example, an access to a local - * variable does not require a receiver. - * - * @param element the element to test - * @return whether the element requires a receiver for accesses - */ - public static boolean hasReceiver(Element element) { - if (element.getKind() == ElementKind.CONSTRUCTOR) { - // The enclosing element of a constructor is the class it creates. - // A constructor can only have a receiver if the class it creates has an outer type. - TypeMirror t = element.getEnclosingElement().asType(); - return TypesUtils.hasEnclosingType(t); - } else if (element.getKind() == ElementKind.FIELD) { - if (ElementUtils.isStatic(element) - // Artificial fields in interfaces are not marked as static, so check that - // the field is not declared in an interface. - || element.getEnclosingElement().getKind().isInterface()) { - return false; - } else { - // In constructors, the element for "this" is a non-static field, but that field - // does not have a receiver. - return !element.getSimpleName().contentEquals("this"); - } - } - return element.getKind() == ElementKind.METHOD && !ElementUtils.isStatic(element); + if (superTypeMirror == null || superTypeMirror.getKind() == TypeKind.NONE) { + return null; + } else { + return (TypeElement) ((DeclaredType) superTypeMirror).asElement(); } - - /** - * Returns a type's superclass, or null if it does not have a superclass (it is object or an - * interface, or the superclass is not on the classpath). - * - * @param typeElt a type element - * @return the superclass of {@code typeElt} - */ - public static @Nullable TypeElement getSuperClass(TypeElement typeElt) { - TypeMirror superTypeMirror; - try { - superTypeMirror = typeElt.getSuperclass(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Looking up a supertype failed. This sometimes happens when transitive dependencies - // are not on the classpath. As javac didn't complain, let's also not complain. - return null; - } - - if (superTypeMirror == null || superTypeMirror.getKind() == TypeKind.NONE) { - return null; - } else { - return (TypeElement) ((DeclaredType) superTypeMirror).asElement(); - } + } + + /** + * Determine all type elements for the supertypes of the given type element. This is the + * transitive closure of the extends and implements clauses. + * + *

          TODO: can we learn from the implementation of + * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)? + * + * @param type the type whose supertypes to return + * @param elements the Element utilities + * @return supertypes of {@code type} + */ + public static List getSuperTypes(TypeElement type, Elements elements) { + + if (type == null) { + return Collections.emptyList(); } - /** - * Determine all type elements for the supertypes of the given type element. This is the - * transitive closure of the extends and implements clauses. - * - *

          TODO: can we learn from the implementation of - * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)? - * - * @param type the type whose supertypes to return - * @param elements the Element utilities - * @return supertypes of {@code type} - */ - public static List getSuperTypes(TypeElement type, Elements elements) { - - if (type == null) { - return Collections.emptyList(); - } + List superElems = new ArrayList<>(); - List superElems = new ArrayList<>(); - - // Set up a stack containing `type`, which is our starting point. - Deque stack = new ArrayDeque<>(); - stack.push(type); - - while (!stack.isEmpty()) { - TypeElement current = stack.pop(); - - // For each direct supertype of the current type element, if it - // hasn't already been visited, push it onto the stack and - // add it to our superElems set. - TypeElement supercls = ElementUtils.getSuperClass(current); - if (supercls != null) { - if (!superElems.contains(supercls)) { - stack.push(supercls); - superElems.add(supercls); - } - } - - for (TypeMirror supertypeitf : current.getInterfaces()) { - TypeElement superitf = (TypeElement) ((DeclaredType) supertypeitf).asElement(); - if (!superElems.contains(superitf)) { - stack.push(superitf); - superElems.add(superitf); - } - } - } + // Set up a stack containing `type`, which is our starting point. + Deque stack = new ArrayDeque<>(); + stack.push(type); - // Include java.lang.Object as implicit superclass for all classes and interfaces. - TypeElement jlobject = elements.getTypeElement("java.lang.Object"); - if (!superElems.contains(jlobject)) { - superElems.add(jlobject); - } + while (!stack.isEmpty()) { + TypeElement current = stack.pop(); - return Collections.unmodifiableList(superElems); - } - - /** - * Determine all type elements for the direct supertypes of the given type element. This is the - * union of the extends and implements clauses. - * - * @param type the type whose supertypes to return - * @param elements the Element utilities - * @return direct supertypes of {@code type} - */ - public static List getDirectSuperTypeElements( - TypeElement type, Elements elements) { - TypeMirror superclass = type.getSuperclass(); - List interfaces = type.getInterfaces(); - List result = new ArrayList(interfaces.size() + 1); - if (superclass.getKind() != TypeKind.NONE) { - @SuppressWarnings("nullness:assignment") // Not null because the TypeKind is not NONE. - @NonNull TypeElement superclassElement = TypesUtils.getTypeElement(superclass); - result.add(superclassElement); - } - for (TypeMirror interfac : interfaces) { - @SuppressWarnings("nullness:assignment") // every interface is a type - @NonNull TypeElement interfaceElt = TypesUtils.getTypeElement(interfac); - result.add(interfaceElt); + // For each direct supertype of the current type element, if it + // hasn't already been visited, push it onto the stack and + // add it to our superElems set. + TypeElement supercls = ElementUtils.getSuperClass(current); + if (supercls != null) { + if (!superElems.contains(supercls)) { + stack.push(supercls); + superElems.add(supercls); } - return result; - } + } - /** - * Return all fields declared in the given type or any superclass/interface. - * - *

          TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of - * our own getSuperTypes? - * - * @param type the type whose fields to return - * @param elements the Element utilities - * @return fields of {@code type} - */ - public static List getAllFieldsIn(TypeElement type, Elements elements) { - // ElementFilter.fieldsIn returns a new list - List fields = ElementFilter.fieldsIn(type.getEnclosedElements()); - List alltypes = getSuperTypes(type, elements); - for (TypeElement atype : alltypes) { - fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements())); + for (TypeMirror supertypeitf : current.getInterfaces()) { + TypeElement superitf = (TypeElement) ((DeclaredType) supertypeitf).asElement(); + if (!superElems.contains(superitf)) { + stack.push(superitf); + superElems.add(superitf); } - return Collections.unmodifiableList(fields); + } } - /** - * Returns all enum constants declared in the given enumeration. - * - * @param type an Enum type - * @return all enum constants declared in the given enumeration - */ - public static List getEnumConstants(TypeElement type) { - List enclosedElements = type.getEnclosedElements(); - List enumConstants = new ArrayList<>(enclosedElements.size()); - for (Element e : enclosedElements) { - if (e.getKind() == ElementKind.ENUM_CONSTANT) { - enumConstants.add((VariableElement) e); - } - } - return enumConstants; + // Include java.lang.Object as implicit superclass for all classes and interfaces. + TypeElement jlobject = elements.getTypeElement("java.lang.Object"); + if (!superElems.contains(jlobject)) { + superElems.add(jlobject); } - /** - * Return all methods declared in the given type or any superclass/interface. Note that no - * constructors will be returned. - * - *

          TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of - * our own getSuperTypes? - * - * @param type the type whose methods to return - * @param elements the Element utilities - * @return methods of {@code type} - */ - public static List getAllMethodsIn(TypeElement type, Elements elements) { - // ElementFilter.fieldsIn returns a new list - List meths = ElementFilter.methodsIn(type.getEnclosedElements()); - - List alltypes = getSuperTypes(type, elements); - for (TypeElement atype : alltypes) { - meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements())); - } - return Collections.unmodifiableList(meths); + return Collections.unmodifiableList(superElems); + } + + /** + * Determine all type elements for the direct supertypes of the given type element. This is the + * union of the extends and implements clauses. + * + * @param type the type whose supertypes to return + * @param elements the Element utilities + * @return direct supertypes of {@code type} + */ + public static List getDirectSuperTypeElements(TypeElement type, Elements elements) { + TypeMirror superclass = type.getSuperclass(); + List interfaces = type.getInterfaces(); + List result = new ArrayList(interfaces.size() + 1); + if (superclass.getKind() != TypeKind.NONE) { + @SuppressWarnings("nullness:assignment") // Not null because the TypeKind is not NONE. + @NonNull TypeElement superclassElement = TypesUtils.getTypeElement(superclass); + result.add(superclassElement); } - - /** - * Return all nested/inner classes/interfaces declared in the given type. - * - * @param type a type - * @return all nested/inner classes/interfaces declared in {@code type} - */ - public static List getAllTypeElementsIn(TypeElement type) { - return ElementFilter.typesIn(type.getEnclosedElements()); + for (TypeMirror interfac : interfaces) { + @SuppressWarnings("nullness:assignment") // every interface is a type + @NonNull TypeElement interfaceElt = TypesUtils.getTypeElement(interfac); + result.add(interfaceElt); } - - /** The set of kinds that represent types. */ - private static final Set typeElementKinds; - - static { - typeElementKinds = EnumSet.noneOf(ElementKind.class); - for (ElementKind kind : ElementKind.values()) { - if (kind.isClass() || kind.isInterface()) { - typeElementKinds.add(kind); - } - } + return result; + } + + /** + * Return all fields declared in the given type or any superclass/interface. + * + *

          TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of + * our own getSuperTypes? + * + * @param type the type whose fields to return + * @param elements the Element utilities + * @return fields of {@code type} + */ + public static List getAllFieldsIn(TypeElement type, Elements elements) { + // ElementFilter.fieldsIn returns a new list + List fields = ElementFilter.fieldsIn(type.getEnclosedElements()); + List alltypes = getSuperTypes(type, elements); + for (TypeElement atype : alltypes) { + fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements())); } - - /** - * Return the set of kinds that represent classes. - * - * @return the set of kinds that represent classes - */ - public static Set typeElementKinds() { - return typeElementKinds; + return Collections.unmodifiableList(fields); + } + + /** + * Returns all enum constants declared in the given enumeration. + * + * @param type an Enum type + * @return all enum constants declared in the given enumeration + */ + public static List getEnumConstants(TypeElement type) { + List enclosedElements = type.getEnclosedElements(); + List enumConstants = new ArrayList<>(enclosedElements.size()); + for (Element e : enclosedElements) { + if (e.getKind() == ElementKind.ENUM_CONSTANT) { + enumConstants.add((VariableElement) e); + } } - - /** - * Is the given element kind a type, i.e., a class, enum, interface, or annotation type. - * - * @param element the element to test - * @return true, iff the given kind is a class kind - */ - public static boolean isTypeElement(Element element) { - return typeElementKinds().contains(element.getKind()); + return enumConstants; + } + + /** + * Return all methods declared in the given type or any superclass/interface. Note that no + * constructors will be returned. + * + *

          TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of + * our own getSuperTypes? + * + * @param type the type whose methods to return + * @param elements the Element utilities + * @return methods of {@code type} + */ + public static List getAllMethodsIn(TypeElement type, Elements elements) { + // ElementFilter.fieldsIn returns a new list + List meths = ElementFilter.methodsIn(type.getEnclosedElements()); + + List alltypes = getSuperTypes(type, elements); + for (TypeElement atype : alltypes) { + meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements())); } - - /** - * Return true if the element is a type declaration. - * - * @param elt the element to test - * @return true if the argument is a type declaration - */ - public static boolean isTypeDeclaration(Element elt) { - return isTypeElement(elt) || elt.getKind() == ElementKind.TYPE_PARAMETER; + return Collections.unmodifiableList(meths); + } + + /** + * Return all nested/inner classes/interfaces declared in the given type. + * + * @param type a type + * @return all nested/inner classes/interfaces declared in {@code type} + */ + public static List getAllTypeElementsIn(TypeElement type) { + return ElementFilter.typesIn(type.getEnclosedElements()); + } + + /** The set of kinds that represent types. */ + private static final Set typeElementKinds; + + static { + typeElementKinds = EnumSet.noneOf(ElementKind.class); + for (ElementKind kind : ElementKind.values()) { + if (kind.isClass() || kind.isInterface()) { + typeElementKinds.add(kind); + } } - - /** The set of kinds that represent local variables. */ - private static final Set localVariableElementKinds = - EnumSet.of( - ElementKind.LOCAL_VARIABLE, - ElementKind.RESOURCE_VARIABLE, - ElementKind.EXCEPTION_PARAMETER); - - /** - * Return true if the element is a local variable. - * - * @param elt the element to test - * @return true if the argument is a local variable - */ - public static boolean isLocalVariable(Element elt) { - return localVariableElementKinds.contains(elt.getKind()); - } - - /** - * Return true if the element is a binding variable. - * - *

          This implementation compiles and runs under JDK 8 and 11 as well as versions that contain - * {@code ElementKind.BINDING_VARIABLE}. - * - * @param element the element to test - * @return true if the element is a binding variable - */ - public static boolean isBindingVariable(Element element) { - return SystemUtil.jreVersion >= 16 && "BINDING_VARIABLE".equals(element.getKind().name()); + } + + /** + * Return the set of kinds that represent classes. + * + * @return the set of kinds that represent classes + */ + public static Set typeElementKinds() { + return typeElementKinds; + } + + /** + * Is the given element kind a type, i.e., a class, enum, interface, or annotation type. + * + * @param element the element to test + * @return true, iff the given kind is a class kind + */ + public static boolean isTypeElement(Element element) { + return typeElementKinds().contains(element.getKind()); + } + + /** + * Return true if the element is a type declaration. + * + * @param elt the element to test + * @return true if the argument is a type declaration + */ + public static boolean isTypeDeclaration(Element elt) { + return isTypeElement(elt) || elt.getKind() == ElementKind.TYPE_PARAMETER; + } + + /** The set of kinds that represent local variables. */ + private static final Set localVariableElementKinds = + EnumSet.of( + ElementKind.LOCAL_VARIABLE, + ElementKind.RESOURCE_VARIABLE, + ElementKind.EXCEPTION_PARAMETER); + + /** + * Return true if the element is a local variable. + * + * @param elt the element to test + * @return true if the argument is a local variable + */ + public static boolean isLocalVariable(Element elt) { + return localVariableElementKinds.contains(elt.getKind()); + } + + /** + * Return true if the element is a binding variable. + * + *

          This implementation compiles and runs under JDK 8 and 11 as well as versions that contain + * {@code ElementKind.BINDING_VARIABLE}. + * + * @param element the element to test + * @return true if the element is a binding variable + */ + public static boolean isBindingVariable(Element element) { + return SystemUtil.jreVersion >= 16 && "BINDING_VARIABLE".equals(element.getKind().name()); + } + + /** + * Returns true if the element is a record accessor method. + * + * @param methodElement a method element + * @return true if the element is a record accessor method + */ + public static boolean isRecordAccessor(ExecutableElement methodElement) { + // Can only be a record accessor if it has no parameters. + if (!methodElement.getParameters().isEmpty()) { + return false; } - /** - * Returns true if the element is a record accessor method. - * - * @param methodElement a method element - * @return true if the element is a record accessor method - */ - public static boolean isRecordAccessor(ExecutableElement methodElement) { - // Can only be a record accessor if it has no parameters. - if (!methodElement.getParameters().isEmpty()) { - return false; - } - - TypeElement enclosing = (TypeElement) methodElement.getEnclosingElement(); - if (isRecordElement(enclosing)) { - String methodName = methodElement.getSimpleName().toString(); - List encloseds = enclosing.getEnclosedElements(); - for (Element enclosed : encloseds) { - if (isRecordComponentElement(enclosed) - && enclosed.getSimpleName().toString().equals(methodName)) { - return true; - } - } + TypeElement enclosing = (TypeElement) methodElement.getEnclosingElement(); + if (isRecordElement(enclosing)) { + String methodName = methodElement.getSimpleName().toString(); + List encloseds = enclosing.getEnclosedElements(); + for (Element enclosed : encloseds) { + if (isRecordComponentElement(enclosed) + && enclosed.getSimpleName().toString().equals(methodName)) { + return true; } - return false; + } } - - /** - * Returns true if the given {@link Element} is part of a record that has been automatically - * generated by the compiler. This can be a field that is derived from the record's header field - * list, or an automatically generated canonical constructor. - * - * @param e the {@link Element} for a member of a record - * @return true if the given element is generated by the compiler - */ - public static boolean isAutoGeneratedRecordMember(Element e) { - if (!(e instanceof Symbol)) { - return false; - } - // Generated constructors seem to get GENERATEDCONSTR even though the documentation - // seems to imply they would get GENERATED_MEMBER like the fields do. - return (((Symbol) e).flags() & (Flags_GENERATED_MEMBER | Flags.GENERATEDCONSTR)) != 0; + return false; + } + + /** + * Returns true if the given {@link Element} is part of a record that has been automatically + * generated by the compiler. This can be a field that is derived from the record's header field + * list, or an automatically generated canonical constructor. + * + * @param e the {@link Element} for a member of a record + * @return true if the given element is generated by the compiler + */ + public static boolean isAutoGeneratedRecordMember(Element e) { + if (!(e instanceof Symbol)) { + return false; } - - /** - * Check that a method Element matches a signature. - * - *

          Note: Matching the receiver type must be done elsewhere as the Element receiver type is - * only populated when annotated. - * - * @param method the method Element to be tested - * @param methodName the goal method name - * @param parameters the goal formal parameter Classes - * @return true if the method matches the methodName and parameters - */ - public static boolean matchesElement( - ExecutableElement method, String methodName, Class... parameters) { - - if (!method.getSimpleName().contentEquals(methodName)) { - return false; - } - - if (method.getParameters().size() != parameters.length) { - return false; - } else { - for (int i = 0; i < method.getParameters().size(); i++) { - if (!method.getParameters() - .get(i) - .asType() - .toString() - .equals(parameters[i].getName())) { - - return false; - } - } - } - - return true; + // Generated constructors seem to get GENERATEDCONSTR even though the documentation + // seems to imply they would get GENERATED_MEMBER like the fields do. + return (((Symbol) e).flags() & (Flags_GENERATED_MEMBER | Flags.GENERATEDCONSTR)) != 0; + } + + /** + * Check that a method Element matches a signature. + * + *

          Note: Matching the receiver type must be done elsewhere as the Element receiver type is only + * populated when annotated. + * + * @param method the method Element to be tested + * @param methodName the goal method name + * @param parameters the goal formal parameter Classes + * @return true if the method matches the methodName and parameters + */ + public static boolean matchesElement( + ExecutableElement method, String methodName, Class... parameters) { + + if (!method.getSimpleName().contentEquals(methodName)) { + return false; } - /** - * Returns true if the given element is, or overrides, {@code method}. - * - * @param questioned an element that might override {@code method} - * @param method a method that might be overridden - * @param env the processing environment - * @return true if {@code questioned} is, or overrides, {@code method} - */ - public static boolean isMethod( - ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) { - return questioned.equals(method) - || env.getElementUtils() - .overrides( - questioned, method, (TypeElement) questioned.getEnclosingElement()); - } + if (method.getParameters().size() != parameters.length) { + return false; + } else { + for (int i = 0; i < method.getParameters().size(); i++) { + if (!method.getParameters().get(i).asType().toString().equals(parameters[i].getName())) { - /** - * Given an annotation name, return true if the element has the annotation of that name. - * - *

          It is more efficient to use {@code Element#getAnnotation(Class)}, but note that both - * methods ignore types from annotation files, such as stub or ajava files. - * - *

          To include types from annotation files, use {@code AnnotatedTypeFactory#fromElement} or - * {@code AnnotatedTypeFactory#getDeclAnnotations}. - * - * @param element the element - * @param annotName name of the annotation - * @return true if the element has the annotation of that name - */ - public static boolean hasAnnotation(Element element, String annotName) { - for (AnnotationMirror anm : element.getAnnotationMirrors()) { - if (AnnotationUtils.areSameByName(anm, annotName)) { - return true; - } + return false; } - return false; + } } - /** - * Returns the TypeElement for the given class. - * - * @param processingEnv the processing environment - * @param clazz a class - * @return the TypeElement for the class - */ - public static TypeElement getTypeElement(ProcessingEnvironment processingEnv, Class clazz) { - @CanonicalName String className = clazz.getCanonicalName(); - if (className == null) { - throw new BugInCF("Anonymous class " + clazz + " has no canonical name"); - } - return processingEnv.getElementUtils().getTypeElement(className); - } - - /** - * Get all the supertypes of a given type, including the type itself. The result includes both - * superclasses and implemented interfaces. - * - * @param type a type - * @param env the processing environment - * @return list including the type and all its supertypes, with a guarantee that direct - * supertypes (i.e. those that appear in extends or implements clauses) appear before - * indirect supertypes - */ - public static List getAllSupertypes(TypeElement type, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); - return CollectionsPlume.mapList( - t -> (TypeElement) t.tsym, javacTypes.closure(((Symbol) type).type)); - } - - /** - * Returns the methods that are overridden or implemented by a given method. - * - * @param m a method - * @param types the type utilities - * @return the methods that {@code m} overrides or implements - */ - public static Set getOverriddenMethods( - ExecutableElement m, Types types) { - JavacTypes t = (JavacTypes) types; - return t.getOverriddenMethods(m); - } - - /** - * Returns true if the two elements are in the same class. The two elements should be class - * members, such as methods or fields. - * - * @param e1 an element - * @param e2 an element - * @return true if the two elements are in the same class - */ - public static boolean inSameClass(Element e1, Element e2) { - return e1.getEnclosingElement().equals(e2.getEnclosingElement()); + return true; + } + + /** + * Returns true if the given element is, or overrides, {@code method}. + * + * @param questioned an element that might override {@code method} + * @param method a method that might be overridden + * @param env the processing environment + * @return true if {@code questioned} is, or overrides, {@code method} + */ + public static boolean isMethod( + ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) { + return questioned.equals(method) + || env.getElementUtils() + .overrides(questioned, method, (TypeElement) questioned.getEnclosingElement()); + } + + /** + * Given an annotation name, return true if the element has the annotation of that name. + * + *

          It is more efficient to use {@code Element#getAnnotation(Class)}, but note that both methods + * ignore types from annotation files, such as stub or ajava files. + * + *

          To include types from annotation files, use {@code AnnotatedTypeFactory#fromElement} or + * {@code AnnotatedTypeFactory#getDeclAnnotations}. + * + * @param element the element + * @param annotName name of the annotation + * @return true if the element has the annotation of that name + */ + public static boolean hasAnnotation(Element element, String annotName) { + for (AnnotationMirror anm : element.getAnnotationMirrors()) { + if (AnnotationUtils.areSameByName(anm, annotName)) { + return true; + } } - - /** - * Determine whether the given element is of Kind RECORD, in a way that works on all versions of - * Java. - * - * @param elt the element to test - * @return whether the element is of the kind RECORD - */ - public static boolean isRecordElement(Element elt) { - ElementKind kind = elt.getKind(); - return kind.name().equals("RECORD"); + return false; + } + + /** + * Returns the TypeElement for the given class. + * + * @param processingEnv the processing environment + * @param clazz a class + * @return the TypeElement for the class + */ + public static TypeElement getTypeElement(ProcessingEnvironment processingEnv, Class clazz) { + @CanonicalName String className = clazz.getCanonicalName(); + if (className == null) { + throw new BugInCF("Anonymous class " + clazz + " has no canonical name"); } - - /** - * Determine whether the given element is of Kind RECORD_COMPONENT, in a way that works on all - * versions of Java. - * - * @param elt the element to test - * @return whether the element is of the kind RECORD_COMPONENT - */ - public static boolean isRecordComponentElement(Element elt) { - ElementKind kind = elt.getKind(); - return kind.name().equals("RECORD_COMPONENT"); + return processingEnv.getElementUtils().getTypeElement(className); + } + + /** + * Get all the supertypes of a given type, including the type itself. The result includes both + * superclasses and implemented interfaces. + * + * @param type a type + * @param env the processing environment + * @return list including the type and all its supertypes, with a guarantee that direct supertypes + * (i.e. those that appear in extends or implements clauses) appear before indirect supertypes + */ + public static List getAllSupertypes(TypeElement type, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return CollectionsPlume.mapList( + t -> (TypeElement) t.tsym, javacTypes.closure(((Symbol) type).type)); + } + + /** + * Returns the methods that are overridden or implemented by a given method. + * + * @param m a method + * @param types the type utilities + * @return the methods that {@code m} overrides or implements + */ + public static Set getOverriddenMethods( + ExecutableElement m, Types types) { + JavacTypes t = (JavacTypes) types; + return t.getOverriddenMethods(m); + } + + /** + * Returns true if the two elements are in the same class. The two elements should be class + * members, such as methods or fields. + * + * @param e1 an element + * @param e2 an element + * @return true if the two elements are in the same class + */ + public static boolean inSameClass(Element e1, Element e2) { + return e1.getEnclosingElement().equals(e2.getEnclosingElement()); + } + + /** + * Determine whether the given element is of Kind RECORD, in a way that works on all versions of + * Java. + * + * @param elt the element to test + * @return whether the element is of the kind RECORD + */ + public static boolean isRecordElement(Element elt) { + ElementKind kind = elt.getKind(); + return kind.name().equals("RECORD"); + } + + /** + * Determine whether the given element is of Kind RECORD_COMPONENT, in a way that works on all + * versions of Java. + * + * @param elt the element to test + * @return whether the element is of the kind RECORD_COMPONENT + */ + public static boolean isRecordComponentElement(Element elt) { + ElementKind kind = elt.getKind(); + return kind.name().equals("RECORD_COMPONENT"); + } + + /** + * Calls getKind() on the given Element, but returns CLASS if the ElementKind is RECORD. This is + * needed because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD + * can't be used in case statements, and usually we want to treat them the same as classes. + * + * @param elt the element to get the kind for + * @return the kind of the element, but CLASS if the kind was RECORD + */ + public static ElementKind getKindRecordAsClass(Element elt) { + if (isRecordElement(elt)) { + return ElementKind.CLASS; } - - /** - * Calls getKind() on the given Element, but returns CLASS if the ElementKind is RECORD. This is - * needed because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD - * can't be used in case statements, and usually we want to treat them the same as classes. - * - * @param elt the element to get the kind for - * @return the kind of the element, but CLASS if the kind was RECORD - */ - public static ElementKind getKindRecordAsClass(Element elt) { - if (isRecordElement(elt)) { - return ElementKind.CLASS; - } - return elt.getKind(); + return elt.getKind(); + } + + /** The {@code TypeElement.getRecordComponents()} method. */ + private static final @Nullable Method TYPEELEMENT_GETRECORDCOMPONENTS; + + static { + if (SystemUtil.jreVersion >= 16) { + try { + TYPEELEMENT_GETRECORDCOMPONENTS = TypeElement.class.getMethod("getRecordComponents"); + } catch (NoSuchMethodException e) { + throw new BugInCF("Cannot access TypeElement.getRecordComponents()", e); + } + } else { + TYPEELEMENT_GETRECORDCOMPONENTS = null; } - - /** The {@code TypeElement.getRecordComponents()} method. */ - private static final @Nullable Method TYPEELEMENT_GETRECORDCOMPONENTS; - - static { - if (SystemUtil.jreVersion >= 16) { - try { - TYPEELEMENT_GETRECORDCOMPONENTS = - TypeElement.class.getMethod("getRecordComponents"); - } catch (NoSuchMethodException e) { - throw new BugInCF("Cannot access TypeElement.getRecordComponents()", e); - } - } else { - TYPEELEMENT_GETRECORDCOMPONENTS = null; - } + } + + /** + * Calls getRecordComponents on the given TypeElement. Uses reflection because this method is not + * available before JDK 16. On earlier JDKs, which don't support records anyway, an exception is + * thrown. + * + * @param element the type element to call getRecordComponents on + * @return the return value of calling getRecordComponents, or empty list if the method is not + * available + */ + @SuppressWarnings({"unchecked", "nullness"}) // because of cast from reflection + public static List getRecordComponents(TypeElement element) { + try { + return (@NonNull List) TYPEELEMENT_GETRECORDCOMPONENTS.invoke(element); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new Error("Cannot call TypeElement.getRecordComponents()", e); } - - /** - * Calls getRecordComponents on the given TypeElement. Uses reflection because this method is - * not available before JDK 16. On earlier JDKs, which don't support records anyway, an - * exception is thrown. - * - * @param element the type element to call getRecordComponents on - * @return the return value of calling getRecordComponents, or empty list if the method is not - * available - */ - @SuppressWarnings({"unchecked", "nullness"}) // because of cast from reflection - public static List getRecordComponents(TypeElement element) { - try { - return (@NonNull List) - TYPEELEMENT_GETRECORDCOMPONENTS.invoke(element); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new Error("Cannot call TypeElement.getRecordComponents()", e); - } + } + + /** + * Check if the given element is a compact canonical record constructor. + * + * @param elt the element to check + * @return true if the element is a compact canonical constructor of a record + */ + public static boolean isCompactCanonicalRecordConstructor(Element elt) { + return elt.getKind() == ElementKind.CONSTRUCTOR + && (((Symbol) elt).flags() & Flags_COMPACT_RECORD_CONSTRUCTOR) != 0; + } + + /** + * Returns true iff the given element is a resource variable. + * + * @param elt an element; may be null, in which case this method always returns false + * @return true iff the given element represents a resource variable + */ + public static boolean isResourceVariable(@Nullable Element elt) { + return elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE; + } + + /** + * Returns true if the given element is a getter method. A getter method is an instance method + * with no formal parameters, whose name starts with "get", "is", "not", or "has" followed by an + * upper-case letter. + * + * @param methodElt a method + * @return true if the given element is a getter method + */ + public static boolean isGetter(@Nullable ExecutableElement methodElt) { + if (methodElt == null) { + return false; } - - /** - * Check if the given element is a compact canonical record constructor. - * - * @param elt the element to check - * @return true if the element is a compact canonical constructor of a record - */ - public static boolean isCompactCanonicalRecordConstructor(Element elt) { - return elt.getKind() == ElementKind.CONSTRUCTOR - && (((Symbol) elt).flags() & Flags_COMPACT_RECORD_CONSTRUCTOR) != 0; + if (isStatic(methodElt)) { + return false; } - - /** - * Returns true iff the given element is a resource variable. - * - * @param elt an element; may be null, in which case this method always returns false - * @return true iff the given element represents a resource variable - */ - public static boolean isResourceVariable(@Nullable Element elt) { - return elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE; + if (!methodElt.getParameters().isEmpty()) { + return false; } - /** - * Returns true if the given element is a getter method. A getter method is an instance method - * with no formal parameters, whose name starts with "get", "is", "not", or "has" followed by an - * upper-case letter. - * - * @param methodElt a method - * @return true if the given element is a getter method - */ - public static boolean isGetter(@Nullable ExecutableElement methodElt) { - if (methodElt == null) { - return false; - } - if (isStatic(methodElt)) { - return false; - } - if (!methodElt.getParameters().isEmpty()) { - return false; - } - - // I could check that the method has a non-void return type, - // and that methods with prefix "is", "has", and "not" return boolean. + // I could check that the method has a non-void return type, + // and that methods with prefix "is", "has", and "not" return boolean. - // Constructors and initializers don't have a name starting with a character. - String name = methodElt.getSimpleName().toString(); - // I expect this code is more efficient than use of a regular expression. - boolean nameOk = - nameStartsWith(name, "get") - || nameStartsWith(name, "is") - || nameStartsWith(name, "not") - || nameStartsWith(name, "has"); + // Constructors and initializers don't have a name starting with a character. + String name = methodElt.getSimpleName().toString(); + // I expect this code is more efficient than use of a regular expression. + boolean nameOk = + nameStartsWith(name, "get") + || nameStartsWith(name, "is") + || nameStartsWith(name, "not") + || nameStartsWith(name, "has"); - if (!nameOk) { - return false; - } - - return true; + if (!nameOk) { + return false; } - /** - * Returns true if the name starts with the given prefix, followed by an upper-case letter. - * - * @param name a name - * @param prefix a prefix - * @return true if the name starts with the given prefix, followed by an upper-case letter - */ - private static boolean nameStartsWith(String name, String prefix) { - return name.startsWith(prefix) - && name.length() > prefix.length() - && Character.isUpperCase(name.charAt(prefix.length())); - } + return true; + } + + /** + * Returns true if the name starts with the given prefix, followed by an upper-case letter. + * + * @param name a name + * @param prefix a prefix + * @return true if the name starts with the given prefix, followed by an upper-case letter + */ + private static boolean nameStartsWith(String name, String prefix) { + return name.startsWith(prefix) + && name.length() > prefix.length() + && Character.isUpperCase(name.charAt(prefix.length())); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java index 4ec70420485..4d5a94633ce 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java @@ -4,55 +4,53 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; - -import org.checkerframework.checker.nullness.qual.Nullable; - import javax.annotation.processing.ProcessingEnvironment; +import org.checkerframework.checker.nullness.qual.Nullable; /** Miscellaneous static utility methods. */ public class InternalUtils { - // Class cannot be instantiated. - private InternalUtils() { - throw new AssertionError("Class InternalUtils cannot be instantiated."); - } - - /** - * Helper function to extract the javac Context from the javac processing environment. - * - * @param env the processing environment - * @return the javac Context - */ - public static Context getJavacContext(ProcessingEnvironment env) { - return ((JavacProcessingEnvironment) env).getContext(); + // Class cannot be instantiated. + private InternalUtils() { + throw new AssertionError("Class InternalUtils cannot be instantiated."); + } + + /** + * Helper function to extract the javac Context from the javac processing environment. + * + * @param env the processing environment + * @return the javac Context + */ + public static Context getJavacContext(ProcessingEnvironment env) { + return ((JavacProcessingEnvironment) env).getContext(); + } + + /** + * Obtain the class loader for {@code clazz}. If that is not available, return the system class + * loader. + * + * @param clazz the class whose class loader to find + * @return the class loader used to {@code clazz}, or the system class loader, or null if both are + * unavailable + */ + public static @Nullable ClassLoader getClassLoaderForClass(Class clazz) { + ClassLoader classLoader = clazz.getClassLoader(); + return classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader; + } + + /** + * Compares tree1 to tree2 by the position at which a diagnostic (e.g., an error message) for the + * tree should be printed. + */ + public static int compareDiagnosticPosition(Tree tree1, Tree tree2) { + DiagnosticPosition pos1 = (DiagnosticPosition) tree1; + DiagnosticPosition pos2 = (DiagnosticPosition) tree2; + + int preferred = Integer.compare(pos1.getPreferredPosition(), pos2.getPreferredPosition()); + if (preferred != 0) { + return preferred; } - /** - * Obtain the class loader for {@code clazz}. If that is not available, return the system class - * loader. - * - * @param clazz the class whose class loader to find - * @return the class loader used to {@code clazz}, or the system class loader, or null if both - * are unavailable - */ - public static @Nullable ClassLoader getClassLoaderForClass(Class clazz) { - ClassLoader classLoader = clazz.getClassLoader(); - return classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader; - } - - /** - * Compares tree1 to tree2 by the position at which a diagnostic (e.g., an error message) for - * the tree should be printed. - */ - public static int compareDiagnosticPosition(Tree tree1, Tree tree2) { - DiagnosticPosition pos1 = (DiagnosticPosition) tree1; - DiagnosticPosition pos2 = (DiagnosticPosition) tree2; - - int preferred = Integer.compare(pos1.getPreferredPosition(), pos2.getPreferredPosition()); - if (preferred != 0) { - return preferred; - } - - return Integer.compare(pos1.getStartPosition(), pos2.getStartPosition()); - } + return Integer.compare(pos1.getStartPosition(), pos2.getStartPosition()); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java index 73b00185d2d..5e72a391922 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java @@ -1,12 +1,11 @@ package org.checkerframework.javacutil; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.plumelib.util.UtilPlume; -import java.util.Objects; - // The class type variables are called V1 and V2 so that T1 and T2 can be used for method type // variables. /** @@ -19,124 +18,123 @@ @Deprecated // 2023-06-02 // TODO: as class is immutable, use @Covariant annotation. public class Pair { - /** The first element of the pair. */ - public final V1 first; - - /** The second element of the pair. */ - public final V2 second; - - private Pair(V1 first, V2 second) { - this.first = first; - this.second = second; - } - - public static Pair of(T1 first, T2 second) { - return new Pair<>(first, second); - } - - // The typical way to make a copy is to first call super.clone() and then modify it. - // That implementation strategy does not work for Pair because its fields are final, so the - // clone - // and deepCopy() methods use of() instead. - - /** - * Returns a copy of this in which each element is a clone of the corresponding element of this. - * {@code clone()} may or may not itself make a deep copy of the elements. - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a copy of {@code orig}, with all elements cloned - */ - // This method is static so that the pair element types can be constrained to be Cloneable. - @SuppressWarnings("nullness") // generics problem with deepCopy() - public static Pair cloneElements( - Pair orig) { - - T1 oldFirst = orig.first; - T1 newFirst = oldFirst == null ? oldFirst : UtilPlume.clone(oldFirst); - T2 oldSecond = orig.second; - T2 newSecond = oldSecond == null ? oldSecond : UtilPlume.clone(oldSecond); - return of(newFirst, newSecond); + /** The first element of the pair. */ + public final V1 first; + + /** The second element of the pair. */ + public final V2 second; + + private Pair(V1 first, V2 second) { + this.first = first; + this.second = second; + } + + public static Pair of(T1 first, T2 second) { + return new Pair<>(first, second); + } + + // The typical way to make a copy is to first call super.clone() and then modify it. + // That implementation strategy does not work for Pair because its fields are final, so the + // clone + // and deepCopy() methods use of() instead. + + /** + * Returns a copy of this in which each element is a clone of the corresponding element of this. + * {@code clone()} may or may not itself make a deep copy of the elements. + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a copy of {@code orig}, with all elements cloned + */ + // This method is static so that the pair element types can be constrained to be Cloneable. + @SuppressWarnings("nullness") // generics problem with deepCopy() + public static Pair cloneElements( + Pair orig) { + + T1 oldFirst = orig.first; + T1 newFirst = oldFirst == null ? oldFirst : UtilPlume.clone(oldFirst); + T2 oldSecond = orig.second; + T2 newSecond = oldSecond == null ? oldSecond : UtilPlume.clone(oldSecond); + return of(newFirst, newSecond); + } + + /** + * Returns a deep copy of this: each element is a deep copy (according to the {@code DeepCopyable} + * interface) of the corresponding element of this. + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a deep copy of {@code orig} + */ + @SuppressWarnings("nullness") // generics problem with deepCopy() + // This method is static so that the pair element types can be constrained to be DeepCopyable. + public static , T2 extends DeepCopyable> Pair deepCopy( + Pair orig) { + return of(DeepCopyable.deepCopyOrNull(orig.first), DeepCopyable.deepCopyOrNull(orig.second)); + } + + /** + * Returns a copy, where the {@code first} element is deep: the {@code first} element is a deep + * copy (according to the {@code DeepCopyable} interface), and the {@code second} element is + * identical to the argument. + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a copy of {@code orig}, where the first element is a deep copy + */ + @SuppressWarnings("nullness") // generics problem with deepCopy() + public static , T2> Pair deepCopyFirst(Pair orig) { + return of(DeepCopyable.deepCopyOrNull(orig.first), orig.second); + } + + /** + * Returns a copy, where the {@code second} element is deep: the {@code first} element is + * identical to the argument, and the {@code second} element is a deep copy (according to the + * {@code DeepCopyable} interface). + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a copy of {@code orig}, where the second element is a deep copy + */ + @SuppressWarnings("nullness") // generics problem with deepCopy() + public static > Pair deepCopySecond(Pair orig) { + return of(orig.first, DeepCopyable.deepCopyOrNull(orig.second)); + } + + @Override + @Pure + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; } - - /** - * Returns a deep copy of this: each element is a deep copy (according to the {@code - * DeepCopyable} interface) of the corresponding element of this. - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a deep copy of {@code orig} - */ - @SuppressWarnings("nullness") // generics problem with deepCopy() - // This method is static so that the pair element types can be constrained to be DeepCopyable. - public static , T2 extends DeepCopyable> Pair deepCopy( - Pair orig) { - return of( - DeepCopyable.deepCopyOrNull(orig.first), DeepCopyable.deepCopyOrNull(orig.second)); - } - - /** - * Returns a copy, where the {@code first} element is deep: the {@code first} element is a deep - * copy (according to the {@code DeepCopyable} interface), and the {@code second} element is - * identical to the argument. - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a copy of {@code orig}, where the first element is a deep copy - */ - @SuppressWarnings("nullness") // generics problem with deepCopy() - public static , T2> Pair deepCopyFirst(Pair orig) { - return of(DeepCopyable.deepCopyOrNull(orig.first), orig.second); + if (!(obj instanceof Pair)) { + return false; } - - /** - * Returns a copy, where the {@code second} element is deep: the {@code first} element is - * identical to the argument, and the {@code second} element is a deep copy (according to the - * {@code DeepCopyable} interface). - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a copy of {@code orig}, where the second element is a deep copy - */ - @SuppressWarnings("nullness") // generics problem with deepCopy() - public static > Pair deepCopySecond(Pair orig) { - return of(orig.first, DeepCopyable.deepCopyOrNull(orig.second)); - } - - @Override - @Pure - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Pair)) { - return false; - } - // generics are not checked at run time! - @SuppressWarnings("unchecked") - Pair other = (Pair) obj; - return Objects.equals(this.first, other.first) && Objects.equals(this.second, other.second); - } - - /** The cached hash code. -1 means it needs to be computed. */ - private int hashCode = -1; - - @Pure - @Override - public int hashCode() { - if (hashCode == -1) { - hashCode = Objects.hash(first, second); - } - return hashCode; - } - - @SideEffectFree - @Override - public String toString() { - return "Pair(" + first + ", " + second + ")"; + // generics are not checked at run time! + @SuppressWarnings("unchecked") + Pair other = (Pair) obj; + return Objects.equals(this.first, other.first) && Objects.equals(this.second, other.second); + } + + /** The cached hash code. -1 means it needs to be computed. */ + private int hashCode = -1; + + @Pure + @Override + public int hashCode() { + if (hashCode == -1) { + hashCode = Objects.hash(first, second); } + return hashCode; + } + + @SideEffectFree + @Override + public String toString() { + return "Pair(" + first + ", " + second + ")"; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java index a58399afcc8..627d44fee85 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java @@ -21,16 +21,11 @@ import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; @@ -38,6 +33,8 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** A utility class to find symbols corresponding to string references (identifiers). */ // This class reflectively accesses jdk.compiler/com.sun.tools.javac.comp. @@ -45,563 +42,526 @@ // running the Checker Framework. If this class is re-written, then that --add-opens should be // removed. public class Resolver { - private final Resolve resolve; - private final Names names; - private final Trees trees; - private final Log log; - - private static final Method FIND_METHOD; - private static final Method FIND_VAR; - private static final Method FIND_IDENT; - private static final Method FIND_IDENT_IN_TYPE; - private static final Method FIND_IDENT_IN_PACKAGE; - private static final Method FIND_TYPE; - - private static final Class ACCESSERROR; - // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError - private static final Method ACCESSERROR_ACCESS; - - /** Whether we are running on at least Java 13. */ - private static final boolean atLeastJava13; - - /** Whether we are running on at least Java 23. */ - private static final boolean atLeastJava23; - - /** - * Determines whether the given {@link SourceVersion} release version string is supported. - * - * @param release the {@link SourceVersion} release version - * @return whether the given version is supported - */ - private static boolean atLeastJava(String release) { - final SourceVersion latestSource = SourceVersion.latest(); - SourceVersion javaVersion; - try { - javaVersion = SourceVersion.valueOf(release); - } catch (IllegalArgumentException e) { - javaVersion = null; - } - @SuppressWarnings("EnumOrdinal") // No better way to compare. - boolean atLeastJava = - javaVersion != null && latestSource.ordinal() >= javaVersion.ordinal(); - return atLeastJava; + private final Resolve resolve; + private final Names names; + private final Trees trees; + private final Log log; + + private static final Method FIND_METHOD; + private static final Method FIND_VAR; + private static final Method FIND_IDENT; + private static final Method FIND_IDENT_IN_TYPE; + private static final Method FIND_IDENT_IN_PACKAGE; + private static final Method FIND_TYPE; + + private static final Class ACCESSERROR; + // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError + private static final Method ACCESSERROR_ACCESS; + + /** Whether we are running on at least Java 13. */ + private static final boolean atLeastJava13; + + /** Whether we are running on at least Java 23. */ + private static final boolean atLeastJava23; + + /** + * Determines whether the given {@link SourceVersion} release version string is supported. + * + * @param release the {@link SourceVersion} release version + * @return whether the given version is supported + */ + private static boolean atLeastJava(String release) { + final SourceVersion latestSource = SourceVersion.latest(); + SourceVersion javaVersion; + try { + javaVersion = SourceVersion.valueOf(release); + } catch (IllegalArgumentException e) { + javaVersion = null; } - - static { - try { - atLeastJava13 = atLeastJava("RELEASE_13"); - atLeastJava23 = atLeastJava("RELEASE_23"); - - FIND_METHOD = - Resolve.class.getDeclaredMethod( - "findMethod", - Env.class, - Type.class, - Name.class, - List.class, - List.class, - boolean.class, - boolean.class); - FIND_METHOD.setAccessible(true); - - if (atLeastJava23) { - // Changed in - // https://github.com/openjdk/jdk/commit/e227c7e37d4de0656f013f3a936b1acfa56cc2e0 - FIND_VAR = - Resolve.class.getDeclaredMethod( - "findVar", DiagnosticPosition.class, Env.class, Name.class); - } else { - FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class); - } - FIND_VAR.setAccessible(true); - - if (atLeastJava13) { - FIND_IDENT = - Resolve.class.getDeclaredMethod( - "findIdent", - DiagnosticPosition.class, - Env.class, - Name.class, - KindSelector.class); - } else { - FIND_IDENT = - Resolve.class.getDeclaredMethod( - "findIdent", Env.class, Name.class, KindSelector.class); - } - FIND_IDENT.setAccessible(true); - - if (atLeastJava13) { - FIND_IDENT_IN_TYPE = - Resolve.class.getDeclaredMethod( - "findIdentInType", - DiagnosticPosition.class, - Env.class, - Type.class, - Name.class, - KindSelector.class); - } else { - FIND_IDENT_IN_TYPE = - Resolve.class.getDeclaredMethod( - "findIdentInType", - Env.class, - Type.class, - Name.class, - KindSelector.class); - } - FIND_IDENT_IN_TYPE.setAccessible(true); - - if (atLeastJava13) { - FIND_IDENT_IN_PACKAGE = - Resolve.class.getDeclaredMethod( - "findIdentInPackage", - DiagnosticPosition.class, - Env.class, - TypeSymbol.class, - Name.class, - KindSelector.class); - } else { - FIND_IDENT_IN_PACKAGE = - Resolve.class.getDeclaredMethod( - "findIdentInPackage", - Env.class, - TypeSymbol.class, - Name.class, - KindSelector.class); - } - FIND_IDENT_IN_PACKAGE.setAccessible(true); - - FIND_TYPE = Resolve.class.getDeclaredMethod("findType", Env.class, Name.class); - FIND_TYPE.setAccessible(true); - } catch (Exception e) { - Error err = - new AssertionError( - "Compiler 'Resolve' class doesn't contain required 'find' method"); - err.initCause(e); - throw err; - } - - try { - ACCESSERROR = Class.forName("com.sun.tools.javac.comp.Resolve$AccessError"); - ACCESSERROR_ACCESS = ACCESSERROR.getMethod("access", Name.class, TypeSymbol.class); - ACCESSERROR_ACCESS.setAccessible(true); - } catch (ClassNotFoundException e) { - throw new BugInCF("Compiler 'Resolve$AccessError' class could not be retrieved.", e); - } catch (NoSuchMethodException e) { - throw new BugInCF( - "Compiler 'Resolve$AccessError' class doesn't contain required 'access' method", - e); - } + @SuppressWarnings("EnumOrdinal") // No better way to compare. + boolean atLeastJava = javaVersion != null && latestSource.ordinal() >= javaVersion.ordinal(); + return atLeastJava; + } + + static { + try { + atLeastJava13 = atLeastJava("RELEASE_13"); + atLeastJava23 = atLeastJava("RELEASE_23"); + + FIND_METHOD = + Resolve.class.getDeclaredMethod( + "findMethod", + Env.class, + Type.class, + Name.class, + List.class, + List.class, + boolean.class, + boolean.class); + FIND_METHOD.setAccessible(true); + + if (atLeastJava23) { + // Changed in + // https://github.com/openjdk/jdk/commit/e227c7e37d4de0656f013f3a936b1acfa56cc2e0 + FIND_VAR = + Resolve.class.getDeclaredMethod( + "findVar", DiagnosticPosition.class, Env.class, Name.class); + } else { + FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class); + } + FIND_VAR.setAccessible(true); + + if (atLeastJava13) { + FIND_IDENT = + Resolve.class.getDeclaredMethod( + "findIdent", DiagnosticPosition.class, Env.class, Name.class, KindSelector.class); + } else { + FIND_IDENT = + Resolve.class.getDeclaredMethod("findIdent", Env.class, Name.class, KindSelector.class); + } + FIND_IDENT.setAccessible(true); + + if (atLeastJava13) { + FIND_IDENT_IN_TYPE = + Resolve.class.getDeclaredMethod( + "findIdentInType", + DiagnosticPosition.class, + Env.class, + Type.class, + Name.class, + KindSelector.class); + } else { + FIND_IDENT_IN_TYPE = + Resolve.class.getDeclaredMethod( + "findIdentInType", Env.class, Type.class, Name.class, KindSelector.class); + } + FIND_IDENT_IN_TYPE.setAccessible(true); + + if (atLeastJava13) { + FIND_IDENT_IN_PACKAGE = + Resolve.class.getDeclaredMethod( + "findIdentInPackage", + DiagnosticPosition.class, + Env.class, + TypeSymbol.class, + Name.class, + KindSelector.class); + } else { + FIND_IDENT_IN_PACKAGE = + Resolve.class.getDeclaredMethod( + "findIdentInPackage", Env.class, TypeSymbol.class, Name.class, KindSelector.class); + } + FIND_IDENT_IN_PACKAGE.setAccessible(true); + + FIND_TYPE = Resolve.class.getDeclaredMethod("findType", Env.class, Name.class); + FIND_TYPE.setAccessible(true); + } catch (Exception e) { + Error err = + new AssertionError("Compiler 'Resolve' class doesn't contain required 'find' method"); + err.initCause(e); + throw err; } - public Resolver(ProcessingEnvironment env) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - this.resolve = Resolve.instance(context); - this.names = Names.instance(context); - this.trees = Trees.instance(env); - this.log = Log.instance(context); + try { + ACCESSERROR = Class.forName("com.sun.tools.javac.comp.Resolve$AccessError"); + ACCESSERROR_ACCESS = ACCESSERROR.getMethod("access", Name.class, TypeSymbol.class); + ACCESSERROR_ACCESS.setAccessible(true); + } catch (ClassNotFoundException e) { + throw new BugInCF("Compiler 'Resolve$AccessError' class could not be retrieved.", e); + } catch (NoSuchMethodException e) { + throw new BugInCF( + "Compiler 'Resolve$AccessError' class doesn't contain required 'access' method", e); } - - /** - * Determine the environment for the given path. - * - * @param path the tree path to the local scope - * @return the corresponding attribution environment - */ - public Env getEnvForPath(TreePath path) { - TreePath iter = path; - JavacScope scope = null; - while (scope == null && iter != null) { - try { - scope = (JavacScope) trees.getScope(iter); - } catch (NullPointerException t) { - // This statement fixes https://github.com/typetools/checker-framework/issues/1059 . - // It work around the crash by skipping through the TreePath until something doesn't - // crash. This probably returns the class scope, so users might not get the - // variables they expect. But that is better than crashing. - iter = iter.getParentPath(); - } - } - if (scope != null) { - return scope.getEnv(); - } else { - throw new BugInCF("Could not determine any possible scope for path: " + path.getLeaf()); - } + } + + public Resolver(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + this.resolve = Resolve.instance(context); + this.names = Names.instance(context); + this.trees = Trees.instance(env); + this.log = Log.instance(context); + } + + /** + * Determine the environment for the given path. + * + * @param path the tree path to the local scope + * @return the corresponding attribution environment + */ + public Env getEnvForPath(TreePath path) { + TreePath iter = path; + JavacScope scope = null; + while (scope == null && iter != null) { + try { + scope = (JavacScope) trees.getScope(iter); + } catch (NullPointerException t) { + // This statement fixes https://github.com/typetools/checker-framework/issues/1059 . + // It work around the crash by skipping through the TreePath until something doesn't + // crash. This probably returns the class scope, so users might not get the + // variables they expect. But that is better than crashing. + iter = iter.getParentPath(); + } } - - /** - * Finds the package with name {@code name}. - * - * @param name the name of the package - * @param path the tree path to the local scope - * @return the {@code PackageSymbol} for the package if it is found, {@code null} otherwise - */ - public @Nullable PackageSymbol findPackage(String name, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - final Element res; - if (atLeastJava13) { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT, - null, - env, - names.fromString(name), - Kinds.KindSelector.PCK); - } else { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT, env, names.fromString(name), Kinds.KindSelector.PCK); - } - - // findIdent will return a PackageSymbol even for a symbol that is not a package, - // such as a.b.c.MyClass.myStaticField. "exists()" must be called on it to ensure - // that it exists. - if (res.getKind() == ElementKind.PACKAGE) { - PackageSymbol ps = (PackageSymbol) res; - return ps.exists() ? ps : null; - } else { - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); - } + if (scope != null) { + return scope.getEnv(); + } else { + throw new BugInCF("Could not determine any possible scope for path: " + path.getLeaf()); } - - /** - * Finds the field with name {@code name} in {@code type} or a superclass or superinterface of - * {@code type}. - * - *

          The method adheres to all the rules of Java's scoping (while also considering the imports) - * for name resolution. - * - * @param name the name of the field - * @param type the type of the receiver (i.e., the type in which to look for the field) - * @param path the tree path to the local scope - * @return the element for the field, {@code null} otherwise - */ - public @Nullable VariableElement findField(String name, TypeMirror type, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - final Element res; - if (atLeastJava13) { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_TYPE, - null, - env, - type, - names.fromString(name), - Kinds.KindSelector.VAR); - } else { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_TYPE, - env, - type, - names.fromString(name), - Kinds.KindSelector.VAR); - } - - if (res.getKind().isField()) { - return (VariableElement) res; - } else if (res.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(res)) { - // Return the inaccessible field that was found - return (VariableElement) wrapInvocation(res, ACCESSERROR_ACCESS, null, null); - } else { - // Most likely didn't find the field and the Element is a SymbolNotFoundError - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); - } + } + + /** + * Finds the package with name {@code name}. + * + * @param name the name of the package + * @param path the tree path to the local scope + * @return the {@code PackageSymbol} for the package if it is found, {@code null} otherwise + */ + public @Nullable PackageSymbol findPackage(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + final Element res; + if (atLeastJava13) { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT, null, env, names.fromString(name), Kinds.KindSelector.PCK); + } else { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT, env, names.fromString(name), Kinds.KindSelector.PCK); + } + + // findIdent will return a PackageSymbol even for a symbol that is not a package, + // such as a.b.c.MyClass.myStaticField. "exists()" must be called on it to ensure + // that it exists. + if (res.getKind() == ElementKind.PACKAGE) { + PackageSymbol ps = (PackageSymbol) res; + return ps.exists() ? ps : null; + } else { + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); } - - /** - * Finds the local variable (including formal parameters) with name {@code name} in the given - * scope. - * - * @param name the name of the local variable - * @param path the tree path to the local scope - * @return the element for the local variable, {@code null} otherwise - */ - public @Nullable VariableElement findLocalVariableOrParameter(String name, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - // Either a VariableElement or a SymbolNotFoundError. - Element res; - if (atLeastJava23) { - DiagnosticPosition pos = (DiagnosticPosition) path.getLeaf(); - res = wrapInvocationOnResolveInstance(FIND_VAR, pos, env, names.fromString(name)); - } else { - res = wrapInvocationOnResolveInstance(FIND_VAR, env, names.fromString(name)); - } - // Every kind in the documentation of Element.getKind() is explicitly tested, possibly - // in the "default:" case. - switch (res.getKind()) { - case EXCEPTION_PARAMETER: - case LOCAL_VARIABLE: - case PARAMETER: - case RESOURCE_VARIABLE: - return (VariableElement) res; - case ENUM_CONSTANT: - case FIELD: - return null; - default: - if (ElementUtils.isBindingVariable(res)) { - return (VariableElement) res; - } - if (res instanceof VariableElement) { - throw new BugInCF("unhandled variable ElementKind " + res.getKind()); - } - // The Element might be a SymbolNotFoundError. - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); - } - } - - /** - * Finds the class literal with name {@code name}. - * - *

          The method adheres to all the rules of Java's scoping (while also considering the imports) - * for name resolution. - * - * @param name the name of the class - * @param path the tree path to the local scope - * @return the element for the class - */ - public Element findClass(String name, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - return wrapInvocationOnResolveInstance(FIND_TYPE, env, names.fromString(name)); - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); - } + } + + /** + * Finds the field with name {@code name} in {@code type} or a superclass or superinterface of + * {@code type}. + * + *

          The method adheres to all the rules of Java's scoping (while also considering the imports) + * for name resolution. + * + * @param name the name of the field + * @param type the type of the receiver (i.e., the type in which to look for the field) + * @param path the tree path to the local scope + * @return the element for the field, {@code null} otherwise + */ + public @Nullable VariableElement findField(String name, TypeMirror type, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + final Element res; + if (atLeastJava13) { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_TYPE, + null, + env, + type, + names.fromString(name), + Kinds.KindSelector.VAR); + } else { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_TYPE, env, type, names.fromString(name), Kinds.KindSelector.VAR); + } + + if (res.getKind().isField()) { + return (VariableElement) res; + } else if (res.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(res)) { + // Return the inaccessible field that was found + return (VariableElement) wrapInvocation(res, ACCESSERROR_ACCESS, null, null); + } else { + // Most likely didn't find the field and the Element is a SymbolNotFoundError + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); } - - /** - * Finds the class with name {@code name} in a given package. - * - * @param name the name of the class - * @param pck the PackageSymbol for the package - * @param path the tree path to the local scope - * @return the {@code ClassSymbol} for the class if it is found, {@code null} otherwise - */ - public @Nullable ClassSymbol findClassInPackage(String name, PackageSymbol pck, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - final Element res; - if (atLeastJava13) { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_PACKAGE, - null, - env, - pck, - names.fromString(name), - Kinds.KindSelector.TYP); - } else { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_PACKAGE, - env, - pck, - names.fromString(name), - Kinds.KindSelector.TYP); - } - - if (ElementUtils.isTypeElement(res)) { - return (ClassSymbol) res; - } else { - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); - } + } + + /** + * Finds the local variable (including formal parameters) with name {@code name} in the given + * scope. + * + * @param name the name of the local variable + * @param path the tree path to the local scope + * @return the element for the local variable, {@code null} otherwise + */ + public @Nullable VariableElement findLocalVariableOrParameter(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + // Either a VariableElement or a SymbolNotFoundError. + Element res; + if (atLeastJava23) { + DiagnosticPosition pos = (DiagnosticPosition) path.getLeaf(); + res = wrapInvocationOnResolveInstance(FIND_VAR, pos, env, names.fromString(name)); + } else { + res = wrapInvocationOnResolveInstance(FIND_VAR, env, names.fromString(name)); + } + // Every kind in the documentation of Element.getKind() is explicitly tested, possibly + // in the "default:" case. + switch (res.getKind()) { + case EXCEPTION_PARAMETER: + case LOCAL_VARIABLE: + case PARAMETER: + case RESOURCE_VARIABLE: + return (VariableElement) res; + case ENUM_CONSTANT: + case FIELD: + return null; + default: + if (ElementUtils.isBindingVariable(res)) { + return (VariableElement) res; + } + if (res instanceof VariableElement) { + throw new BugInCF("unhandled variable ElementKind " + res.getKind()); + } + // The Element might be a SymbolNotFoundError. + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); } - - /** - * Finds the method element for a given name and list of expected parameter types. - * - *

          The method adheres to all the rules of Java's scoping (while also considering the imports) - * for name resolution. - * - *

          (This method takes into account autoboxing.) - * - *

          This method is a wrapper around {@code com.sun.tools.javac.comp.Resolve.findMethod}. - * - * @param methodName name of the method to find - * @param receiverType type of the receiver of the method - * @param path tree path - * @param argumentTypes types of arguments passed to the method call - * @return the method element (if found) - */ - public @Nullable ExecutableElement findMethod( - String methodName, - TypeMirror receiverType, - TreePath path, - java.util.List argumentTypes) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - - Type site = (Type) receiverType; - Name name = names.fromString(methodName); - List argtypes = List.nil(); - for (TypeMirror a : argumentTypes) { - argtypes = argtypes.append((Type) a); - } - List typeargtypes = List.nil(); - boolean allowBoxing = true; - boolean useVarargs = false; - - try { - // For some reason we have to set our own method context, which is rather ugly. - // TODO: find a nicer way to do this. - Object methodContext = buildMethodContext(); - Object oldContext = getField(resolve, "currentResolutionContext"); - setField(resolve, "currentResolutionContext", methodContext); - Element resolveResult = - wrapInvocationOnResolveInstance( - FIND_METHOD, - env, - site, - name, - argtypes, - typeargtypes, - allowBoxing, - useVarargs); - setField(resolve, "currentResolutionContext", oldContext); - ExecutableElement methodResult; - if (resolveResult.getKind() == ElementKind.METHOD - || resolveResult.getKind() == ElementKind.CONSTRUCTOR) { - methodResult = (ExecutableElement) resolveResult; - } else if (resolveResult.getKind() == ElementKind.OTHER - && ACCESSERROR.isInstance(resolveResult)) { - // Return the inaccessible method that was found. - methodResult = - (ExecutableElement) - wrapInvocation(resolveResult, ACCESSERROR_ACCESS, null, null); - } else { - methodResult = null; - } - return methodResult; - } catch (Throwable t) { - Error err = - new AssertionError( - String.format( - "Unexpected reflection error in findMethod(%s, %s, ...," - + " %s)", - methodName, - receiverType, - // path - argumentTypes)); - err.initCause(t); - throw err; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); - } + } + + /** + * Finds the class literal with name {@code name}. + * + *

          The method adheres to all the rules of Java's scoping (while also considering the imports) + * for name resolution. + * + * @param name the name of the class + * @param path the tree path to the local scope + * @return the element for the class + */ + public Element findClass(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + return wrapInvocationOnResolveInstance(FIND_TYPE, env, names.fromString(name)); + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); } - - /** - * Build an instance of {@code Resolve$MethodResolutionContext}. - * - * @return a MethodResolutionContext - * @throws ClassNotFoundException if there is trouble constructing the instance - * @throws InstantiationException if there is trouble constructing the instance - * @throws IllegalAccessException if there is trouble constructing the instance - * @throws InvocationTargetException if there is trouble constructing the instance - * @throws NoSuchFieldException if there is trouble constructing the instance - */ - protected Object buildMethodContext() - throws ClassNotFoundException, - InstantiationException, - IllegalAccessException, - InvocationTargetException, - NoSuchFieldException { - // Class is not accessible, instantiate reflectively. - Class methCtxClss = - Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext"); - Constructor constructor = methCtxClss.getDeclaredConstructors()[0]; - constructor.setAccessible(true); - Object methodContext = constructor.newInstance(resolve); - // we need to also initialize the fields attrMode and step - setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK); - @SuppressWarnings("rawtypes") - List phases = (List) getField(resolve, "methodResolutionSteps"); - assert phases != null : "@AssumeAssertion(nullness): assumption"; - setField(methodContext, "step", phases.get(1)); - return methodContext; + } + + /** + * Finds the class with name {@code name} in a given package. + * + * @param name the name of the class + * @param pck the PackageSymbol for the package + * @param path the tree path to the local scope + * @return the {@code ClassSymbol} for the class if it is found, {@code null} otherwise + */ + public @Nullable ClassSymbol findClassInPackage(String name, PackageSymbol pck, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + final Element res; + if (atLeastJava13) { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_PACKAGE, + null, + env, + pck, + names.fromString(name), + Kinds.KindSelector.TYP); + } else { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_PACKAGE, env, pck, names.fromString(name), Kinds.KindSelector.TYP); + } + + if (ElementUtils.isTypeElement(res)) { + return (ClassSymbol) res; + } else { + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); } - - /** - * Reflectively set a field. - * - * @param receiver the receiver in which to set the field - * @param fieldName name of field to set - * @param value new value for field - * @throws NoSuchFieldException if the field does not exist in the receiver - * @throws IllegalAccessException if the field is not accessible - */ - @SuppressWarnings({ - "nullness:argument.type.incompatible", - "interning:argument.type.incompatible" - }) // assume that the fields all accept null and uninterned values - private void setField(Object receiver, String fieldName, @Nullable Object value) - throws NoSuchFieldException, IllegalAccessException { - Field f = receiver.getClass().getDeclaredField(fieldName); - f.setAccessible(true); - f.set(receiver, value); - } - - /** Reflectively get the value of a field. */ - private @Nullable Object getField(Object receiver, String fieldName) - throws NoSuchFieldException, IllegalAccessException { - Field f = receiver.getClass().getDeclaredField(fieldName); - f.setAccessible(true); - return f.get(receiver); - } - - /** - * Wrap a method invocation on the {@code resolve} object. - * - * @param method the method to called - * @param args the arguments to the call - * @return the result of invoking the method on {@code resolve} (as the receiver) and the - * arguments - */ - private Symbol wrapInvocationOnResolveInstance(Method method, @Nullable Object... args) { - return wrapInvocation(resolve, method, args); - } - - /** - * Invoke a method reflectively. This is like {@code Method.invoke()}, but it throws no checked - * exceptions. - * - * @param receiver the receiver - * @param method the method to called - * @param args the arguments to the call - * @return the result of invoking the method on the receiver and arguments - */ - private Symbol wrapInvocation(Object receiver, Method method, @Nullable Object... args) { - try { - @SuppressWarnings("nullness") // assume arguments are OK - @NonNull Symbol res = (Symbol) method.invoke(receiver, args); - return res; - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new BugInCF( - e, - "Unexpected reflection error in wrapInvocation(%s, %s, %s)", - receiver, - method, - Arrays.toString(args)); + } + + /** + * Finds the method element for a given name and list of expected parameter types. + * + *

          The method adheres to all the rules of Java's scoping (while also considering the imports) + * for name resolution. + * + *

          (This method takes into account autoboxing.) + * + *

          This method is a wrapper around {@code com.sun.tools.javac.comp.Resolve.findMethod}. + * + * @param methodName name of the method to find + * @param receiverType type of the receiver of the method + * @param path tree path + * @param argumentTypes types of arguments passed to the method call + * @return the method element (if found) + */ + public @Nullable ExecutableElement findMethod( + String methodName, + TypeMirror receiverType, + TreePath path, + java.util.List argumentTypes) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + + Type site = (Type) receiverType; + Name name = names.fromString(methodName); + List argtypes = List.nil(); + for (TypeMirror a : argumentTypes) { + argtypes = argtypes.append((Type) a); + } + List typeargtypes = List.nil(); + boolean allowBoxing = true; + boolean useVarargs = false; + + try { + // For some reason we have to set our own method context, which is rather ugly. + // TODO: find a nicer way to do this. + Object methodContext = buildMethodContext(); + Object oldContext = getField(resolve, "currentResolutionContext"); + setField(resolve, "currentResolutionContext", methodContext); + Element resolveResult = + wrapInvocationOnResolveInstance( + FIND_METHOD, env, site, name, argtypes, typeargtypes, allowBoxing, useVarargs); + setField(resolve, "currentResolutionContext", oldContext); + ExecutableElement methodResult; + if (resolveResult.getKind() == ElementKind.METHOD + || resolveResult.getKind() == ElementKind.CONSTRUCTOR) { + methodResult = (ExecutableElement) resolveResult; + } else if (resolveResult.getKind() == ElementKind.OTHER + && ACCESSERROR.isInstance(resolveResult)) { + // Return the inaccessible method that was found. + methodResult = + (ExecutableElement) wrapInvocation(resolveResult, ACCESSERROR_ACCESS, null, null); + } else { + methodResult = null; } + return methodResult; + } catch (Throwable t) { + Error err = + new AssertionError( + String.format( + "Unexpected reflection error in findMethod(%s, %s, ...," + " %s)", + methodName, + receiverType, + // path + argumentTypes)); + err.initCause(t); + throw err; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } + } + + /** + * Build an instance of {@code Resolve$MethodResolutionContext}. + * + * @return a MethodResolutionContext + * @throws ClassNotFoundException if there is trouble constructing the instance + * @throws InstantiationException if there is trouble constructing the instance + * @throws IllegalAccessException if there is trouble constructing the instance + * @throws InvocationTargetException if there is trouble constructing the instance + * @throws NoSuchFieldException if there is trouble constructing the instance + */ + protected Object buildMethodContext() + throws ClassNotFoundException, + InstantiationException, + IllegalAccessException, + InvocationTargetException, + NoSuchFieldException { + // Class is not accessible, instantiate reflectively. + Class methCtxClss = + Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext"); + Constructor constructor = methCtxClss.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + Object methodContext = constructor.newInstance(resolve); + // we need to also initialize the fields attrMode and step + setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK); + @SuppressWarnings("rawtypes") + List phases = (List) getField(resolve, "methodResolutionSteps"); + assert phases != null : "@AssumeAssertion(nullness): assumption"; + setField(methodContext, "step", phases.get(1)); + return methodContext; + } + + /** + * Reflectively set a field. + * + * @param receiver the receiver in which to set the field + * @param fieldName name of field to set + * @param value new value for field + * @throws NoSuchFieldException if the field does not exist in the receiver + * @throws IllegalAccessException if the field is not accessible + */ + @SuppressWarnings({ + "nullness:argument.type.incompatible", + "interning:argument.type.incompatible" + }) // assume that the fields all accept null and uninterned values + private void setField(Object receiver, String fieldName, @Nullable Object value) + throws NoSuchFieldException, IllegalAccessException { + Field f = receiver.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(receiver, value); + } + + /** Reflectively get the value of a field. */ + private @Nullable Object getField(Object receiver, String fieldName) + throws NoSuchFieldException, IllegalAccessException { + Field f = receiver.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(receiver); + } + + /** + * Wrap a method invocation on the {@code resolve} object. + * + * @param method the method to called + * @param args the arguments to the call + * @return the result of invoking the method on {@code resolve} (as the receiver) and the + * arguments + */ + private Symbol wrapInvocationOnResolveInstance(Method method, @Nullable Object... args) { + return wrapInvocation(resolve, method, args); + } + + /** + * Invoke a method reflectively. This is like {@code Method.invoke()}, but it throws no checked + * exceptions. + * + * @param receiver the receiver + * @param method the method to called + * @param args the arguments to the call + * @return the result of invoking the method on the receiver and arguments + */ + private Symbol wrapInvocation(Object receiver, Method method, @Nullable Object... args) { + try { + @SuppressWarnings("nullness") // assume arguments are OK + @NonNull Symbol res = (Symbol) method.invoke(receiver, args); + return res; + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new BugInCF( + e, + "Unexpected reflection error in wrapInvocation(%s, %s, %s)", + receiver, + method, + Arrays.toString(args)); } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java b/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java index 6ff077b7b70..29ed927630c 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java @@ -5,16 +5,14 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreeScanner; - +import java.util.List; +import java.util.function.BiFunction; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; -import java.util.List; -import java.util.function.BiFunction; - /** * A class that visits each result expression of a switch expression and calls {@link * #visitSwitchResultExpression(ExpressionTree, Object)} on each result expression. The results of @@ -31,158 +29,153 @@ */ public abstract class SwitchExpressionScanner extends TreeScanner { - /** - * This method is called for each result expression of the switch expression passed in {@link - * #scanSwitchExpression(Tree, Object)}. - * - * @param resultExpressionTree a result expression of the switch expression currently being - * scanned - * @param p a parameter - * @return the result of visiting the result expression - */ - protected abstract R visitSwitchResultExpression(ExpressionTree resultExpressionTree, P p); - - /** - * This method combines the result of two calls to {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} or {@code null} and the result of one - * call to {@link #visitSwitchResultExpression(ExpressionTree, Object)}. - * - * @param r1 a possibly null result returned by {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} - * @param r2 a possibly null result returned by {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} - * @return the combination of {@code r1} and {@code r2} - */ - protected abstract R combineResults(@Nullable R r1, @Nullable R r2); - - /** - * Scans the given switch expression and calls {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} on each result expression of the switch - * expression. {@link #combineResults(Object, Object)} is called to combine the results of - * visiting multiple switch result expressions. - * - * @param switchExpression a switch expression tree - * @param p the parameter to pass to {@link #visitSwitchResultExpression(ExpressionTree, - * Object)} - * @return the result of calling {@link #visitSwitchResultExpression(ExpressionTree, Object)} on - * each result expression of {@code switchExpression} and combining the results using {@link - * #combineResults(Object, Object)} - */ - public R scanSwitchExpression(Tree switchExpression, P p) { - // TODO: use JCP to add version-specific behavior - assert SystemUtil.jreVersion >= 14 - && switchExpression.getKind().name().equals("SWITCH_EXPRESSION"); - List caseTrees = SwitchExpressionUtils.getCases(switchExpression); - R result = null; - for (CaseTree caseTree : caseTrees) { - if (caseTree.getStatements() != null) { - // This case is a switch labeled statement group, so scan the statements for yield - // statements. - result = combineResults(result, yieldVisitor.scan(caseTree.getStatements(), p)); - } else { - @SuppressWarnings( - "nullness:assignment") // caseTree.getStatements() == null, so the case has - // a body. - @NonNull Tree body = CaseUtils.getBody(caseTree); - // This case is a switch rule, so its body is either an expression, block, or throw. - // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.28.2. - if (body.getKind() == Tree.Kind.BLOCK) { - // Scan for yield statements. - result = - combineResults( - result, - yieldVisitor.scan(((BlockTree) body).getStatements(), p)); - } else if (body.getKind() != Tree.Kind.THROW) { - // The expression is the result expression. - ExpressionTree expressionTree = (ExpressionTree) body; - result = combineResults(result, visitSwitchResultExpression(expressionTree, p)); - } - } - } + /** + * This method is called for each result expression of the switch expression passed in {@link + * #scanSwitchExpression(Tree, Object)}. + * + * @param resultExpressionTree a result expression of the switch expression currently being + * scanned + * @param p a parameter + * @return the result of visiting the result expression + */ + protected abstract R visitSwitchResultExpression(ExpressionTree resultExpressionTree, P p); + + /** + * This method combines the result of two calls to {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} or {@code null} and the result of one + * call to {@link #visitSwitchResultExpression(ExpressionTree, Object)}. + * + * @param r1 a possibly null result returned by {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} + * @param r2 a possibly null result returned by {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} + * @return the combination of {@code r1} and {@code r2} + */ + protected abstract R combineResults(@Nullable R r1, @Nullable R r2); + + /** + * Scans the given switch expression and calls {@link #visitSwitchResultExpression(ExpressionTree, + * Object)} on each result expression of the switch expression. {@link #combineResults(Object, + * Object)} is called to combine the results of visiting multiple switch result expressions. + * + * @param switchExpression a switch expression tree + * @param p the parameter to pass to {@link #visitSwitchResultExpression(ExpressionTree, Object)} + * @return the result of calling {@link #visitSwitchResultExpression(ExpressionTree, Object)} on + * each result expression of {@code switchExpression} and combining the results using {@link + * #combineResults(Object, Object)} + */ + public R scanSwitchExpression(Tree switchExpression, P p) { + // TODO: use JCP to add version-specific behavior + assert SystemUtil.jreVersion >= 14 + && switchExpression.getKind().name().equals("SWITCH_EXPRESSION"); + List caseTrees = SwitchExpressionUtils.getCases(switchExpression); + R result = null; + for (CaseTree caseTree : caseTrees) { + if (caseTree.getStatements() != null) { + // This case is a switch labeled statement group, so scan the statements for yield + // statements. + result = combineResults(result, yieldVisitor.scan(caseTree.getStatements(), p)); + } else { @SuppressWarnings( - "nullness:assignment" // switch expressions must have at least one case that results - // in a value, so {@code result} must be nonnull. - ) - @NonNull R nonNullResult = result; - return nonNullResult; - } - - /** - * A scanner that visits all the yield trees in a given tree and calls {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. - * It does not descend into switch expressions. - */ - protected final YieldVisitor yieldVisitor = new YieldVisitor(); - - /** - * A scanner that visits all the yield trees in a given tree and calls {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. - * It does not descend into switch expressions. - */ - protected class YieldVisitor extends TreeScanner<@Nullable R, P> { - - // TODO: use JCP to add version-specific behavior - @Override - public @Nullable R scan(Tree tree, P p) { - if (tree == null) { - return null; - } - if (tree.getKind().name().equals("SWITCH_EXPRESSION")) { - // Don't scan nested switch expressions. - return null; - } else if (tree.getKind().name().equals("YIELD")) { - ExpressionTree value = YieldUtils.getValue(tree); - return visitSwitchResultExpression(value, p); - } - return super.scan(tree, p); + "nullness:assignment") // caseTree.getStatements() == null, so the case has + // a body. + @NonNull Tree body = CaseUtils.getBody(caseTree); + // This case is a switch rule, so its body is either an expression, block, or throw. + // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.28.2. + if (body.getKind() == Tree.Kind.BLOCK) { + // Scan for yield statements. + result = combineResults(result, yieldVisitor.scan(((BlockTree) body).getStatements(), p)); + } else if (body.getKind() != Tree.Kind.THROW) { + // The expression is the result expression. + ExpressionTree expressionTree = (ExpressionTree) body; + result = combineResults(result, visitSwitchResultExpression(expressionTree, p)); } + } + } + @SuppressWarnings( + "nullness:assignment" // switch expressions must have at least one case that results + // in a value, so {@code result} must be nonnull. + ) + @NonNull R nonNullResult = result; + return nonNullResult; + } + + /** + * A scanner that visits all the yield trees in a given tree and calls {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. It + * does not descend into switch expressions. + */ + protected final YieldVisitor yieldVisitor = new YieldVisitor(); + + /** + * A scanner that visits all the yield trees in a given tree and calls {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. It + * does not descend into switch expressions. + */ + protected class YieldVisitor extends TreeScanner<@Nullable R, P> { + + // TODO: use JCP to add version-specific behavior + @Override + public @Nullable R scan(Tree tree, P p) { + if (tree == null) { + return null; + } + if (tree.getKind().name().equals("SWITCH_EXPRESSION")) { + // Don't scan nested switch expressions. + return null; + } else if (tree.getKind().name().equals("YIELD")) { + ExpressionTree value = YieldUtils.getValue(tree); + return visitSwitchResultExpression(value, p); + } + return super.scan(tree, p); + } - @Override - public R reduce(R r1, R r2) { - return combineResults(r1, r2); - } + @Override + public R reduce(R r1, R r2) { + return combineResults(r1, r2); } + } + + /** + * An implementation of {@link SwitchExpressionScanner} that uses functions passed to the + * constructor for {@link #visitSwitchResultExpression(ExpressionTree, Object)} and {@link + * #combineResults(Object, Object)}. + * + * @param the type result of {@link #visitSwitchResultExpression(ExpressionTree, Object)} + * @param the type of the parameter to pass to {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} + */ + public static class FunctionalSwitchExpressionScanner + extends SwitchExpressionScanner { + + /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ + private final BiFunction switchValueExpressionFunction; + + /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ + private final BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc; /** - * An implementation of {@link SwitchExpressionScanner} that uses functions passed to the - * constructor for {@link #visitSwitchResultExpression(ExpressionTree, Object)} and {@link - * #combineResults(Object, Object)}. + * Creates a {@link FunctionalSwitchExpressionScanner} that uses the given functions. * - * @param the type result of {@link #visitSwitchResultExpression(ExpressionTree, Object)} - * @param the type of the parameter to pass to {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} + * @param switchValueExpressionFunc the function called on each switch result expression + * @param combineResultFunc the function used to combine the result of multiple calls to {@code + * switchValueExpressionFunc} */ - public static class FunctionalSwitchExpressionScanner - extends SwitchExpressionScanner { - - /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ - private final BiFunction switchValueExpressionFunction; - - /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ - private final BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc; - - /** - * Creates a {@link FunctionalSwitchExpressionScanner} that uses the given functions. - * - * @param switchValueExpressionFunc the function called on each switch result expression - * @param combineResultFunc the function used to combine the result of multiple calls to - * {@code switchValueExpressionFunc} - */ - public FunctionalSwitchExpressionScanner( - BiFunction switchValueExpressionFunc, - BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc) { - this.switchValueExpressionFunction = switchValueExpressionFunc; - this.combineResultFunc = combineResultFunc; - } + public FunctionalSwitchExpressionScanner( + BiFunction switchValueExpressionFunc, + BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc) { + this.switchValueExpressionFunction = switchValueExpressionFunc; + this.combineResultFunc = combineResultFunc; + } - @Override - protected R1 visitSwitchResultExpression(ExpressionTree resultExpressionTree, P1 p1) { - return switchValueExpressionFunction.apply(resultExpressionTree, p1); - } + @Override + protected R1 visitSwitchResultExpression(ExpressionTree resultExpressionTree, P1 p1) { + return switchValueExpressionFunction.apply(resultExpressionTree, p1); + } - @Override - protected R1 combineResults(@Nullable R1 r1, @Nullable R1 r2) { - return combineResultFunc.apply(r1, r2); - } + @Override + protected R1 combineResults(@Nullable R1 r1, @Nullable R1 r2) { + return combineResultFunc.apply(r1, r2); } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java index 19e78a87a25..6477dc895d8 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java @@ -5,140 +5,133 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Options; - -import org.checkerframework.checker.nullness.qual.Nullable; - import java.io.File; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.annotation.processing.ProcessingEnvironment; +import org.checkerframework.checker.nullness.qual.Nullable; /** This file contains basic utility functions. */ public class SystemUtil { - /** Do not instantiate. */ - private SystemUtil() { - throw new AssertionError("Class SystemUtil cannot be instantiated."); + /** Do not instantiate. */ + private SystemUtil() { + throw new AssertionError("Class SystemUtil cannot be instantiated."); + } + + /** A splitter that splits on periods. The result contains no empty strings. */ + public static final Splitter DOT_SPLITTER = Splitter.on('.').omitEmptyStrings(); + + /** A splitter that splits on commas. The result contains no empty strings. */ + public static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings(); + + /** A splitter that splits on colons. The result contains no empty strings. */ + public static final Splitter COLON_SPLITTER = Splitter.on(':').omitEmptyStrings(); + + /** A splitter that splits on {@code File.pathSeparator}. The result contains no empty strings. */ + public static final Splitter PATH_SEPARATOR_SPLITTER = + Splitter.on(File.pathSeparator).omitEmptyStrings(); + + /** + * Like {@code System.getProperty}, but splits on the path separator and never returns null. + * + * @param propName a system property name + * @return the paths in the system property; may be an empty array + */ + public static final List getPathsProperty(String propName) { + String propValue = System.getProperty(propName); + if (propValue == null) { + return Collections.emptyList(); + } else { + return PATH_SEPARATOR_SPLITTER.splitToList(propValue); } - - /** A splitter that splits on periods. The result contains no empty strings. */ - public static final Splitter DOT_SPLITTER = Splitter.on('.').omitEmptyStrings(); - - /** A splitter that splits on commas. The result contains no empty strings. */ - public static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings(); - - /** A splitter that splits on colons. The result contains no empty strings. */ - public static final Splitter COLON_SPLITTER = Splitter.on(':').omitEmptyStrings(); - - /** - * A splitter that splits on {@code File.pathSeparator}. The result contains no empty strings. - */ - public static final Splitter PATH_SEPARATOR_SPLITTER = - Splitter.on(File.pathSeparator).omitEmptyStrings(); - - /** - * Like {@code System.getProperty}, but splits on the path separator and never returns null. - * - * @param propName a system property name - * @return the paths in the system property; may be an empty array - */ - public static final List getPathsProperty(String propName) { - String propValue = System.getProperty(propName); - if (propValue == null) { - return Collections.emptyList(); - } else { - return PATH_SEPARATOR_SPLITTER.splitToList(propValue); - } + } + + /** The major version number of the Java runtime (JRE), such as 8, 11, or 17. */ + public static final int jreVersion = getJreVersion(); + + // Keep in sync with BCELUtil.java (in the bcel-util project). + /** + * Returns the major version number from the "java.version" system property, such as 8, 11, or 17. + * + *

          This is different from the version passed to the compiler via {@code --release}; use {@link + * #getReleaseValue(ProcessingEnvironment)} to get that version. + * + *

          Two possible formats of the "java.version" system property are considered. Up to Java 8, + * from a version string like `1.8.whatever`, this method extracts 8. Since Java 9, from a version + * string like `11.0.1`, this method extracts 11. + * + *

          Starting in Java 9, there is the int {@code Runtime.version().feature()}, but that does not + * exist on JDK 8. + * + *

          External users should use field {@link #jreVersion} instead. + * + * @return the major version of the Java runtime + */ + private static int getJreVersion() { + String version = System.getProperty("java.version"); + + // Up to Java 8, from a version string like "1.8.whatever", extract "8". + if (version.startsWith("1.")) { + return Integer.parseInt(version.substring(2, 3)); } - /** The major version number of the Java runtime (JRE), such as 8, 11, or 17. */ - public static final int jreVersion = getJreVersion(); - - // Keep in sync with BCELUtil.java (in the bcel-util project). - /** - * Returns the major version number from the "java.version" system property, such as 8, 11, or - * 17. - * - *

          This is different from the version passed to the compiler via {@code --release}; use - * {@link #getReleaseValue(ProcessingEnvironment)} to get that version. - * - *

          Two possible formats of the "java.version" system property are considered. Up to Java 8, - * from a version string like `1.8.whatever`, this method extracts 8. Since Java 9, from a - * version string like `11.0.1`, this method extracts 11. - * - *

          Starting in Java 9, there is the int {@code Runtime.version().feature()}, but that does - * not exist on JDK 8. - * - *

          External users should use field {@link #jreVersion} instead. - * - * @return the major version of the Java runtime - */ - private static int getJreVersion() { - String version = System.getProperty("java.version"); - - // Up to Java 8, from a version string like "1.8.whatever", extract "8". - if (version.startsWith("1.")) { - return Integer.parseInt(version.substring(2, 3)); - } - - // Since Java 9, from a version string like "11.0.1" or "11-ea" or "11u25", extract "11". - // The format is described at http://openjdk.org/jeps/223 . - Pattern newVersionPattern = Pattern.compile("^(\\d+).*$"); - Matcher newVersionMatcher = newVersionPattern.matcher(version); - if (newVersionMatcher.matches()) { - String v = newVersionMatcher.group(1); - assert v != null : "@AssumeAssertion(nullness): inspection"; - return Integer.parseInt(v); - } - - throw new RuntimeException( - "Could not determine version from property java.version=" + version); + // Since Java 9, from a version string like "11.0.1" or "11-ea" or "11u25", extract "11". + // The format is described at http://openjdk.org/jeps/223 . + Pattern newVersionPattern = Pattern.compile("^(\\d+).*$"); + Matcher newVersionMatcher = newVersionPattern.matcher(version); + if (newVersionMatcher.matches()) { + String v = newVersionMatcher.group(1); + assert v != null : "@AssumeAssertion(nullness): inspection"; + return Integer.parseInt(v); } - /** - * Returns the release value passed to the compiler or null if release was not passed. - * - * @param env the ProcessingEnvironment - * @return the release value or null if none was passed - */ - public static @Nullable String getReleaseValue(ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - Options options = Options.instance(ctx); - return options.get(Option.RELEASE); + throw new RuntimeException("Could not determine version from property java.version=" + version); + } + + /** + * Returns the release value passed to the compiler or null if release was not passed. + * + * @param env the ProcessingEnvironment + * @return the release value or null if none was passed + */ + public static @Nullable String getReleaseValue(ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + Options options = Options.instance(ctx); + return options.get(Option.RELEASE); + } + + /** + * Returns the pathname to the tools.jar file, or null if it does not exist. Returns null on Java + * 9 and later. + * + * @return the pathname to the tools.jar file, or null + */ + public static @Nullable String getToolsJar() { + + if (jreVersion > 8) { + return null; } - /** - * Returns the pathname to the tools.jar file, or null if it does not exist. Returns null on - * Java 9 and later. - * - * @return the pathname to the tools.jar file, or null - */ - public static @Nullable String getToolsJar() { - - if (jreVersion > 8) { - return null; - } - - String javaHome = System.getenv("JAVA_HOME"); - if (javaHome == null) { - String javaHomeProperty = System.getProperty("java.home"); - if (javaHomeProperty.endsWith(File.separator + "jre")) { - javaHome = javaHomeProperty.substring(javaHomeProperty.length() - 4); - } else { - // Could also determine the location of javac on the path... - throw new Error("Can't infer Java home; java.home=" + javaHomeProperty); - } - } - File toolsJarFile = new File(new File(javaHome, "lib"), "tools.jar"); - if (!toolsJarFile.exists()) { - throw new Error( - String.format( - "File does not exist: %s ; JAVA_HOME=%s ; java.home=%s", - toolsJarFile, javaHome, System.getProperty("java.home"))); - } - return toolsJarFile.toString(); + String javaHome = System.getenv("JAVA_HOME"); + if (javaHome == null) { + String javaHomeProperty = System.getProperty("java.home"); + if (javaHomeProperty.endsWith(File.separator + "jre")) { + javaHome = javaHomeProperty.substring(javaHomeProperty.length() - 4); + } else { + // Could also determine the location of javac on the path... + throw new Error("Can't infer Java home; java.home=" + javaHomeProperty); + } + } + File toolsJarFile = new File(new File(javaHome, "lib"), "tools.jar"); + if (!toolsJarFile.exists()) { + throw new Error( + String.format( + "File does not exist: %s ; JAVA_HOME=%s ; java.home=%s", + toolsJarFile, javaHome, System.getProperty("java.home"))); } + return toolsJarFile.toString(); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java index 4a672491cf7..d692fb3d342 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java @@ -8,17 +8,14 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.IPair; - import java.util.EnumSet; import java.util.Iterator; import java.util.Set; import java.util.StringJoiner; - import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.IPair; /** * Utility methods for obtaining or analyzing a javac {@code TreePath}. @@ -27,417 +24,416 @@ */ public final class TreePathUtil { - /** Do not instantiate; this class is a collection of static methods. */ - private TreePathUtil() { - throw new BugInCF("Class TreeUtils cannot be instantiated."); + /** Do not instantiate; this class is a collection of static methods. */ + private TreePathUtil() { + throw new BugInCF("Class TreeUtils cannot be instantiated."); + } + + /// + /// Retrieving a path (from another path) + /// + + /** + * Gets path to the first (innermost) enclosing tree of the given kind. May return {@code path} + * itself. + * + * @param path the path defining the tree node + * @param kind the kind of the desired tree + * @return the path to the enclosing tree of the given type, {@code null} otherwise + */ + public static @Nullable TreePath pathTillOfKind(TreePath path, Tree.Kind kind) { + return pathTillOfKind(path, EnumSet.of(kind)); + } + + /** + * Gets path to the first (innermost) enclosing tree with any one of the given kinds. May return + * {@code path} itself. + * + * @param path the path defining the tree node + * @param kinds the set of kinds of the desired tree + * @return the path to the enclosing tree of the given type, {@code null} otherwise + */ + public static @Nullable TreePath pathTillOfKind(TreePath path, Set kinds) { + for (TreePath p = path; p != null; p = p.getParentPath()) { + if (kinds.contains(p.getLeaf().getKind())) { + return p; + } } - - /// - /// Retrieving a path (from another path) - /// - - /** - * Gets path to the first (innermost) enclosing tree of the given kind. May return {@code path} - * itself. - * - * @param path the path defining the tree node - * @param kind the kind of the desired tree - * @return the path to the enclosing tree of the given type, {@code null} otherwise - */ - public static @Nullable TreePath pathTillOfKind(TreePath path, Tree.Kind kind) { - return pathTillOfKind(path, EnumSet.of(kind)); + return null; + } + + /** + * Gets path to the first (innermost) enclosing class tree, where class is defined by the {@link + * TreeUtils#classTreeKinds()} method. May return {@code path} itself. + * + * @param path the path defining the tree node + * @return the path to the enclosing class tree, {@code null} otherwise + */ + public static @Nullable TreePath pathTillClass(TreePath path) { + return pathTillOfKind(path, TreeUtils.classTreeKinds()); + } + + /** + * Gets path to the first (innermost) enclosing method tree. May return {@code path} itself. + * + * @param path the path defining the tree node + * @return the path to the enclosing class tree, {@code null} otherwise + */ + public static @Nullable TreePath pathTillMethod(TreePath path) { + return pathTillOfKind(path, Tree.Kind.METHOD); + } + + /// + /// Retrieving a tree (from a path) + /// + + /** + * Gets the first (innermost) enclosing tree in path, of the given kind. May return the leaf of + * {@code path} itself. + * + * @param path the path defining the tree node + * @param kind the kind of the desired tree + * @return the enclosing tree of the given type as given by the path, {@code null} otherwise + */ + public static @Nullable Tree enclosingOfKind(TreePath path, Tree.Kind kind) { + return enclosingOfKind(path, EnumSet.of(kind)); + } + + /** + * Gets the first (innermost) enclosing tree in path, with any one of the given kinds. May return + * the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @param kinds the set of kinds of the desired tree + * @return the enclosing tree of the given type as given by the path, {@code null} otherwise + */ + public static @Nullable Tree enclosingOfKind(TreePath path, Set kinds) { + TreePath p = pathTillOfKind(path, kinds); + return (p == null) ? null : p.getLeaf(); + } + + /** + * Gets the first (innermost) enclosing tree in path, of the given class. May return the leaf of + * {@code path} itself. + * + * @param the type of {@code treeClass} + * @param path the path defining the tree node + * @param treeClass the class of the desired tree + * @return the enclosing tree of the given type as given by the path, {@code null} otherwise + */ + public static @Nullable T enclosingOfClass(TreePath path, Class treeClass) { + TreePath p = path; + + while (p != null) { + Tree leaf = p.getLeaf(); + if (treeClass.isInstance(leaf)) { + return treeClass.cast(leaf); + } + p = p.getParentPath(); } - /** - * Gets path to the first (innermost) enclosing tree with any one of the given kinds. May return - * {@code path} itself. - * - * @param path the path defining the tree node - * @param kinds the set of kinds of the desired tree - * @return the path to the enclosing tree of the given type, {@code null} otherwise - */ - public static @Nullable TreePath pathTillOfKind(TreePath path, Set kinds) { - for (TreePath p = path; p != null; p = p.getParentPath()) { - if (kinds.contains(p.getLeaf().getKind())) { - return p; - } - } - return null; + return null; + } + + /** + * Gets the path to nearest enclosing declaration (class, method, or variable) of the tree node + * defined by the given {@link TreePath}. May return the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return path to the nearest enclosing class/method/variable in the path, or {@code null} if one + * does not exist + */ + public static @Nullable TreePath enclosingDeclarationPath(TreePath path) { + return pathTillOfKind(path, TreeUtils.declarationTreeKinds()); + } + + /** + * Gets the enclosing class of the tree node defined by the given {@link TreePath}. It returns a + * {@link Tree}, from which {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be + * obtained. May return the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return the enclosing class (or interface) as given by the path, or {@code null} if one does + * not exist + */ + public static @Nullable ClassTree enclosingClass(TreePath path) { + return (ClassTree) enclosingOfKind(path, TreeUtils.classTreeKinds()); + } + + /** + * Gets the enclosing variable of a tree node defined by the given {@link TreePath}. May return + * the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return the enclosing variable as given by the path, or {@code null} if one does not exist + */ + public static @Nullable VariableTree enclosingVariable(TreePath path) { + return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE); + } + + /** + * Gets the enclosing method of the tree node defined by the given {@link TreePath}. It returns a + * {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can + * be obtained. May return the leaf of {@code path} itself. + * + *

          Also see {@code AnnotatedTypeFactory#getEnclosingMethod} and {@code + * AnnotatedTypeFactory#getEnclosingClassOrMethod}, which do not require a TreePath. + * + * @param path the path defining the tree node + * @return the enclosing method as given by the path, or {@code null} if one does not exist + */ + public static @Nullable MethodTree enclosingMethod(TreePath path) { + return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD); + } + + /** + * Gets the enclosing method or lambda expression of the tree node defined by the given {@link + * TreePath}. It returns a {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} + * or {@link Element} can be obtained. May return the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return the enclosing method or lambda as given by the path, or {@code null} if one does not + * exist + */ + public static @Nullable Tree enclosingMethodOrLambda(TreePath path) { + return enclosingOfKind(path, EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); + } + + /** + * Returns the top-level block that encloses the given path, or null if none does. Never returns + * the leaf of {@code path} itself. + * + * @param path a path + * @return the top-level block that encloses the given path, or null if none does + */ + public static @Nullable BlockTree enclosingTopLevelBlock(TreePath path) { + TreePath parentPath = path.getParentPath(); + while (parentPath != null + && !TreeUtils.classTreeKinds().contains(parentPath.getLeaf().getKind())) { + path = parentPath; + parentPath = parentPath.getParentPath(); } - - /** - * Gets path to the first (innermost) enclosing class tree, where class is defined by the {@link - * TreeUtils#classTreeKinds()} method. May return {@code path} itself. - * - * @param path the path defining the tree node - * @return the path to the enclosing class tree, {@code null} otherwise - */ - public static @Nullable TreePath pathTillClass(TreePath path) { - return pathTillOfKind(path, TreeUtils.classTreeKinds()); + if (path.getLeaf().getKind() == Tree.Kind.BLOCK) { + return (BlockTree) path.getLeaf(); } - - /** - * Gets path to the first (innermost) enclosing method tree. May return {@code path} itself. - * - * @param path the path defining the tree node - * @return the path to the enclosing class tree, {@code null} otherwise - */ - public static @Nullable TreePath pathTillMethod(TreePath path) { - return pathTillOfKind(path, Tree.Kind.METHOD); + return null; + } + + /** + * Gets the first (innermost) enclosing tree in path, that is not a parenthesis. Never returns the + * leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return a pair of a non-parenthesis tree that contains the argument, and its child that is the + * argument or is a parenthesized version of it + */ + public static IPair enclosingNonParen(TreePath path) { + TreePath parentPath = path.getParentPath(); + Tree enclosing = parentPath.getLeaf(); + Tree enclosingChild = path.getLeaf(); + while (enclosing.getKind() == Tree.Kind.PARENTHESIZED) { + parentPath = parentPath.getParentPath(); + enclosingChild = enclosing; + enclosing = parentPath.getLeaf(); } - - /// - /// Retrieving a tree (from a path) - /// - - /** - * Gets the first (innermost) enclosing tree in path, of the given kind. May return the leaf of - * {@code path} itself. - * - * @param path the path defining the tree node - * @param kind the kind of the desired tree - * @return the enclosing tree of the given type as given by the path, {@code null} otherwise - */ - public static @Nullable Tree enclosingOfKind(TreePath path, Tree.Kind kind) { - return enclosingOfKind(path, EnumSet.of(kind)); + return IPair.of(enclosing, enclosingChild); + } + + /** + * Returns the "assignment context" for the leaf of {@code treePath}, which is often the leaf of + * the parent of {@code treePath}. (Does not handle pseudo-assignment of an argument to a + * parameter or a receiver expression to a receiver.) This is not the same as {@code + * org.checkerframework.dataflow.cfg.node.AssignmentContext}, which represents the left-hand side + * rather than the assignment itself. + * + *

          The assignment context for {@code treePath} is the leaf of its parent, if that leaf is one + * of the following trees: + * + *

            + *
          • AssignmentTree + *
          • CompoundAssignmentTree + *
          • MethodInvocationTree + *
          • NewArrayTree + *
          • NewClassTree + *
          • ReturnTree + *
          • VariableTree + *
          + * + * If the parent is a ConditionalExpressionTree we need to distinguish two cases: If the leaf is + * either the then or else branch of the ConditionalExpressionTree, then recurse on the parent. If + * the leaf is the condition of the ConditionalExpressionTree, then return null to not consider + * this assignment context. + * + *

          If the leaf is a ParenthesizedTree, then recurse on the parent. + * + *

          Otherwise, null is returned. + * + * @param treePath a path + * @return the assignment context as described, {@code null} otherwise + */ + public static @Nullable Tree getAssignmentContext(TreePath treePath) { + TreePath parentPath = treePath.getParentPath(); + + if (parentPath == null) { + return null; } - /** - * Gets the first (innermost) enclosing tree in path, with any one of the given kinds. May - * return the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @param kinds the set of kinds of the desired tree - * @return the enclosing tree of the given type as given by the path, {@code null} otherwise - */ - public static @Nullable Tree enclosingOfKind(TreePath path, Set kinds) { - TreePath p = pathTillOfKind(path, kinds); - return (p == null) ? null : p.getLeaf(); - } - - /** - * Gets the first (innermost) enclosing tree in path, of the given class. May return the leaf of - * {@code path} itself. - * - * @param the type of {@code treeClass} - * @param path the path defining the tree node - * @param treeClass the class of the desired tree - * @return the enclosing tree of the given type as given by the path, {@code null} otherwise - */ - public static @Nullable T enclosingOfClass(TreePath path, Class treeClass) { - TreePath p = path; - - while (p != null) { - Tree leaf = p.getLeaf(); - if (treeClass.isInstance(leaf)) { - return treeClass.cast(leaf); - } - p = p.getParentPath(); + Tree parent = parentPath.getLeaf(); + switch (parent.getKind()) { + case ASSIGNMENT: // See below for CompoundAssignmentTree. + case METHOD_INVOCATION: + case NEW_ARRAY: + case NEW_CLASS: + case RETURN: + case VARIABLE: + return parent; + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree cet = (ConditionalExpressionTree) parent; + @SuppressWarnings("interning:not.interned") // AST node comparison + boolean conditionIsLeaf = (cet.getCondition() == treePath.getLeaf()); + if (conditionIsLeaf) { + // The assignment context for the condition is simply boolean. + // No point in going on. + return null; + } + // Otherwise use the context of the ConditionalExpressionTree. + return getAssignmentContext(parentPath); + case PARENTHESIZED: + return getAssignmentContext(parentPath); + default: + // 11 Tree.Kinds are CompoundAssignmentTrees, + // so use instanceof rather than listing all 11. + if (parent instanceof CompoundAssignmentTree) { + return parent; } - return null; } - - /** - * Gets the path to nearest enclosing declaration (class, method, or variable) of the tree node - * defined by the given {@link TreePath}. May return the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return path to the nearest enclosing class/method/variable in the path, or {@code null} if - * one does not exist - */ - public static @Nullable TreePath enclosingDeclarationPath(TreePath path) { - return pathTillOfKind(path, TreeUtils.declarationTreeKinds()); - } - - /** - * Gets the enclosing class of the tree node defined by the given {@link TreePath}. It returns a - * {@link Tree}, from which {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be - * obtained. May return the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return the enclosing class (or interface) as given by the path, or {@code null} if one does - * not exist - */ - public static @Nullable ClassTree enclosingClass(TreePath path) { - return (ClassTree) enclosingOfKind(path, TreeUtils.classTreeKinds()); - } - - /** - * Gets the enclosing variable of a tree node defined by the given {@link TreePath}. May return - * the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return the enclosing variable as given by the path, or {@code null} if one does not exist - */ - public static @Nullable VariableTree enclosingVariable(TreePath path) { - return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE); - } - - /** - * Gets the enclosing method of the tree node defined by the given {@link TreePath}. It returns - * a {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} or {@link Element} - * can be obtained. May return the leaf of {@code path} itself. - * - *

          Also see {@code AnnotatedTypeFactory#getEnclosingMethod} and {@code - * AnnotatedTypeFactory#getEnclosingClassOrMethod}, which do not require a TreePath. - * - * @param path the path defining the tree node - * @return the enclosing method as given by the path, or {@code null} if one does not exist - */ - public static @Nullable MethodTree enclosingMethod(TreePath path) { - return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD); - } - - /** - * Gets the enclosing method or lambda expression of the tree node defined by the given {@link - * TreePath}. It returns a {@link Tree}, from which an {@code - * checkers.types.AnnotatedTypeMirror} or {@link Element} can be obtained. May return the leaf - * of {@code path} itself. - * - * @param path the path defining the tree node - * @return the enclosing method or lambda as given by the path, or {@code null} if one does not - * exist - */ - public static @Nullable Tree enclosingMethodOrLambda(TreePath path) { - return enclosingOfKind(path, EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); + } + + /// + /// Predicates + /// + + /** + * Returns true if the tree is in a constructor or an initializer block. + * + * @param path the path to test + * @return true if the path is in a constructor or an initializer block + */ + public static boolean inConstructor(TreePath path) { + MethodTree method = enclosingMethod(path); + // If method is null, this is an initializer block. + return method == null || TreeUtils.isConstructor(method); + } + + /** + * Returns true if the leaf of the tree path is in a static scope. + * + * @param path a TreePath whose leaf may or may not be in static scope + * @return true if the leaf of the tree path is in a static scope + */ + public static boolean isTreeInStaticScope(TreePath path) { + MethodTree enclosingMethod = enclosingMethod(path); + + if (enclosingMethod != null) { + return enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC); } - - /** - * Returns the top-level block that encloses the given path, or null if none does. Never returns - * the leaf of {@code path} itself. - * - * @param path a path - * @return the top-level block that encloses the given path, or null if none does - */ - public static @Nullable BlockTree enclosingTopLevelBlock(TreePath path) { - TreePath parentPath = path.getParentPath(); - while (parentPath != null - && !TreeUtils.classTreeKinds().contains(parentPath.getLeaf().getKind())) { - path = parentPath; - parentPath = parentPath.getParentPath(); - } - if (path.getLeaf().getKind() == Tree.Kind.BLOCK) { - return (BlockTree) path.getLeaf(); - } - return null; + // no enclosing method, check for static or initializer block + BlockTree block = enclosingTopLevelBlock(path); + if (block != null) { + return block.isStatic(); } - /** - * Gets the first (innermost) enclosing tree in path, that is not a parenthesis. Never returns - * the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return a pair of a non-parenthesis tree that contains the argument, and its child that is - * the argument or is a parenthesized version of it - */ - public static IPair enclosingNonParen(TreePath path) { - TreePath parentPath = path.getParentPath(); - Tree enclosing = parentPath.getLeaf(); - Tree enclosingChild = path.getLeaf(); - while (enclosing.getKind() == Tree.Kind.PARENTHESIZED) { - parentPath = parentPath.getParentPath(); - enclosingChild = enclosing; - enclosing = parentPath.getLeaf(); - } - return IPair.of(enclosing, enclosingChild); + // check if it's in a variable initializer + Tree t = enclosingVariable(path); + if (t != null) { + return ((VariableTree) t).getModifiers().getFlags().contains(Modifier.STATIC); } - - /** - * Returns the "assignment context" for the leaf of {@code treePath}, which is often the leaf of - * the parent of {@code treePath}. (Does not handle pseudo-assignment of an argument to a - * parameter or a receiver expression to a receiver.) This is not the same as {@code - * org.checkerframework.dataflow.cfg.node.AssignmentContext}, which represents the left-hand - * side rather than the assignment itself. - * - *

          The assignment context for {@code treePath} is the leaf of its parent, if that leaf is one - * of the following trees: - * - *

            - *
          • AssignmentTree - *
          • CompoundAssignmentTree - *
          • MethodInvocationTree - *
          • NewArrayTree - *
          • NewClassTree - *
          • ReturnTree - *
          • VariableTree - *
          - * - * If the parent is a ConditionalExpressionTree we need to distinguish two cases: If the leaf is - * either the then or else branch of the ConditionalExpressionTree, then recurse on the parent. - * If the leaf is the condition of the ConditionalExpressionTree, then return null to not - * consider this assignment context. - * - *

          If the leaf is a ParenthesizedTree, then recurse on the parent. - * - *

          Otherwise, null is returned. - * - * @param treePath a path - * @return the assignment context as described, {@code null} otherwise - */ - public static @Nullable Tree getAssignmentContext(TreePath treePath) { - TreePath parentPath = treePath.getParentPath(); - - if (parentPath == null) { - return null; - } - - Tree parent = parentPath.getLeaf(); - switch (parent.getKind()) { - case ASSIGNMENT: // See below for CompoundAssignmentTree. - case METHOD_INVOCATION: - case NEW_ARRAY: - case NEW_CLASS: - case RETURN: - case VARIABLE: - return parent; - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree cet = (ConditionalExpressionTree) parent; - @SuppressWarnings("interning:not.interned") // AST node comparison - boolean conditionIsLeaf = (cet.getCondition() == treePath.getLeaf()); - if (conditionIsLeaf) { - // The assignment context for the condition is simply boolean. - // No point in going on. - return null; - } - // Otherwise use the context of the ConditionalExpressionTree. - return getAssignmentContext(parentPath); - case PARENTHESIZED: - return getAssignmentContext(parentPath); - default: - // 11 Tree.Kinds are CompoundAssignmentTrees, - // so use instanceof rather than listing all 11. - if (parent instanceof CompoundAssignmentTree) { - return parent; - } - return null; - } + ClassTree classTree = enclosingClass(path); + if (classTree != null) { + return classTree.getModifiers().getFlags().contains(Modifier.STATIC); } - - /// - /// Predicates - /// - - /** - * Returns true if the tree is in a constructor or an initializer block. - * - * @param path the path to test - * @return true if the path is in a constructor or an initializer block - */ - public static boolean inConstructor(TreePath path) { - MethodTree method = enclosingMethod(path); - // If method is null, this is an initializer block. - return method == null || TreeUtils.isConstructor(method); + return false; + } + + /** + * Returns true if the path is to a top-level (not within a loop) assignment within an initializer + * block. The initializer block might be instance or static. Will return true for a re-assignment + * even if there is another initialization (within this initializer block, another initializer + * block, a constructor, or the variable declaration). + * + * @param path the path to test + * @return true if the path is to an initialization within an initializer block + */ + public static boolean isTopLevelAssignmentInInitializerBlock(TreePath path) { + TreePath origPath = path; + if (path.getLeaf().getKind() != Tree.Kind.ASSIGNMENT) { + return false; } - - /** - * Returns true if the leaf of the tree path is in a static scope. - * - * @param path a TreePath whose leaf may or may not be in static scope - * @return true if the leaf of the tree path is in a static scope - */ - public static boolean isTreeInStaticScope(TreePath path) { - MethodTree enclosingMethod = enclosingMethod(path); - - if (enclosingMethod != null) { - return enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC); - } - // no enclosing method, check for static or initializer block - BlockTree block = enclosingTopLevelBlock(path); - if (block != null) { - return block.isStatic(); - } - - // check if it's in a variable initializer - Tree t = enclosingVariable(path); - if (t != null) { - return ((VariableTree) t).getModifiers().getFlags().contains(Modifier.STATIC); - } - ClassTree classTree = enclosingClass(path); - if (classTree != null) { - return classTree.getModifiers().getFlags().contains(Modifier.STATIC); - } - return false; + path = path.getParentPath(); + if (path.getLeaf().getKind() != Tree.Kind.EXPRESSION_STATEMENT) { + return false; } - - /** - * Returns true if the path is to a top-level (not within a loop) assignment within an - * initializer block. The initializer block might be instance or static. Will return true for a - * re-assignment even if there is another initialization (within this initializer block, another - * initializer block, a constructor, or the variable declaration). - * - * @param path the path to test - * @return true if the path is to an initialization within an initializer block - */ - public static boolean isTopLevelAssignmentInInitializerBlock(TreePath path) { - TreePath origPath = path; - if (path.getLeaf().getKind() != Tree.Kind.ASSIGNMENT) { - return false; - } - path = path.getParentPath(); - if (path.getLeaf().getKind() != Tree.Kind.EXPRESSION_STATEMENT) { - return false; - } - Tree prevLeaf = path.getLeaf(); - path = path.getParentPath(); - - for (Iterator itor = path.iterator(); itor.hasNext(); ) { - Tree leaf = itor.next(); - switch (leaf.getKind()) { - case CLASS: - case ENUM: - case PARAMETERIZED_TYPE: - return prevLeaf.getKind() == Tree.Kind.BLOCK; - - case COMPILATION_UNIT: - throw new BugInCF("found COMPILATION_UNIT in " + toString(origPath)); - - case DO_WHILE_LOOP: - case ENHANCED_FOR_LOOP: - case FOR_LOOP: - case LAMBDA_EXPRESSION: - case METHOD: - return false; - - default: - prevLeaf = leaf; - } - } - throw new BugInCF("path did not contain method or class: " + toString(origPath)); + Tree prevLeaf = path.getLeaf(); + path = path.getParentPath(); + + for (Iterator itor = path.iterator(); itor.hasNext(); ) { + Tree leaf = itor.next(); + switch (leaf.getKind()) { + case CLASS: + case ENUM: + case PARAMETERIZED_TYPE: + return prevLeaf.getKind() == Tree.Kind.BLOCK; + + case COMPILATION_UNIT: + throw new BugInCF("found COMPILATION_UNIT in " + toString(origPath)); + + case DO_WHILE_LOOP: + case ENHANCED_FOR_LOOP: + case FOR_LOOP: + case LAMBDA_EXPRESSION: + case METHOD: + return false; + + default: + prevLeaf = leaf; + } } - - /// - /// Formatting - /// - - /** - * Return a printed representation of a TreePath. - * - * @param path a TreePath - * @return a printed representation of the given TreePath - */ - public static String toString(TreePath path) { - StringJoiner result = new StringJoiner(System.lineSeparator() + " "); - result.add("TreePath:"); - for (Tree t : path) { - result.add(TreeUtils.toStringTruncated(t, 65) + " " + t.getKind()); - } - return result.toString(); + throw new BugInCF("path did not contain method or class: " + toString(origPath)); + } + + /// + /// Formatting + /// + + /** + * Return a printed representation of a TreePath. + * + * @param path a TreePath + * @return a printed representation of the given TreePath + */ + public static String toString(TreePath path) { + StringJoiner result = new StringJoiner(System.lineSeparator() + " "); + result.add("TreePath:"); + for (Tree t : path) { + result.add(TreeUtils.toStringTruncated(t, 65) + " " + t.getKind()); } - - /** - * Returns a string representation of the leaf of the given path, using {@link - * TreeUtils#toStringTruncated}. - * - * @param path a path - * @param length the maximum length for the result; must be at least 6 - * @return a one-line string representation of the leaf of the given path that is no longer than - * {@code length} characters long - */ - public static String leafToStringTruncated(@Nullable TreePath path, int length) { - if (path == null) { - return "null"; - } - return TreeUtils.toStringTruncated(path.getLeaf(), length); + return result.toString(); + } + + /** + * Returns a string representation of the leaf of the given path, using {@link + * TreeUtils#toStringTruncated}. + * + * @param path a path + * @param length the maximum length for the result; must be at least 6 + * @return a one-line string representation of the leaf of the given path that is no longer than + * {@code length} characters long + */ + public static String leafToStringTruncated(@Nullable TreePath path, int length) { + if (path == null) { + return "null"; } + return TreeUtils.toStringTruncated(path.getLeaf(), length); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 19a2985fc7f..033b8878dbc 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -62,20 +62,6 @@ import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Position; - -import org.checkerframework.checker.interning.qual.PolyInterned; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.InstanceOfUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.UniqueIdMap; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -85,7 +71,6 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; @@ -102,6 +87,18 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; +import org.checkerframework.checker.interning.qual.PolyInterned; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.InstanceOfUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.UniqueIdMap; /** * Utility methods for analyzing a javac {@code Tree}. @@ -111,2575 +108,2551 @@ // TODO: use JCP to add version-specific behavior public final class TreeUtils { - // Class cannot be instantiated. - private TreeUtils() { - throw new AssertionError("Class TreeUtils cannot be instantiated."); - } - - /** Unique IDs for trees. Used instead of hash codes, so output is deterministic. */ - public static final UniqueIdMap treeUids = new UniqueIdMap<>(); - - /** The latest source version supported by this compiler. */ - private static final int sourceVersionNumber = - Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); - - /** Whether we are running on at least Java 21. */ - private static final boolean atLeastJava21 = sourceVersionNumber >= 21; - - /** - * The {@code TreeMaker.Select(JCExpression, Symbol)} method. Return type changes for JDK21+. - * Only needs to be used while the code is compiled with JDK below 21. - */ - private static final @Nullable Method TREEMAKER_SELECT; - - /** The value of Flags.RECORD which does not exist in Java 9 or 11. */ - private static final long Flags_RECORD = 2305843009213693952L; - - /** Tree kinds that represent a binary comparison. */ - private static final Set BINARY_COMPARISON_TREE_KINDS = - EnumSet.of( - Tree.Kind.EQUAL_TO, - Tree.Kind.NOT_EQUAL_TO, - Tree.Kind.LESS_THAN, - Tree.Kind.GREATER_THAN, - Tree.Kind.LESS_THAN_EQUAL, - Tree.Kind.GREATER_THAN_EQUAL); - - static { - try { - TREEMAKER_SELECT = - TreeMaker.class.getMethod("Select", JCExpression.class, Symbol.class); - } catch (NoSuchMethodException e) { - throw new AssertionError("Unexpected error in TreeUtils static initializer", e); - } - } - - /** - * Checks if the provided method is a constructor method or no. - * - * @param tree a tree defining the method - * @return true iff tree describes a constructor - */ - public static boolean isConstructor(MethodTree tree) { - return tree.getName().contentEquals(""); - } - - /** - * Checks if the method invocation is a call to super. - * - * @param tree a tree defining a method invocation - * @return true iff tree describes a call to super - */ - public static boolean isSuperConstructorCall(MethodInvocationTree tree) { - return isNamedMethodCall("super", tree); - } - - /** - * Checks if the method invocation is a call to "this". - * - * @param tree a tree defining a method invocation - * @return true iff tree describes a call to this - */ - public static boolean isThisConstructorCall(MethodInvocationTree tree) { - return isNamedMethodCall("this", tree); - } - - /** - * Checks if the method call is a call to the given method name. - * - * @param name a method name - * @param tree a tree defining a method invocation - * @return true iff tree is a call to the given method - */ - private static boolean isNamedMethodCall(String name, MethodInvocationTree tree) { - return getMethodName(tree.getMethodSelect()).contentEquals(name); - } - - /** - * Returns true if the tree is a tree that 'looks like' either an access of a field or an - * invocation of a method that are owned by the same accessing instance. - * - *

          It would only return true if the access tree is of the form: - * - *

          -     *   field
          -     *   this.field
          -     *
          -     *   method()
          -     *   this.method()
          -     * 
          - * - * It does not perform any semantical check to differentiate between fields and local variables; - * local methods or imported static methods. - * - * @param tree expression tree representing an access to object member - * @return {@code true} iff the member is a member of {@code this} instance - */ - public static boolean isSelfAccess(ExpressionTree tree) { - ExpressionTree tr = TreeUtils.withoutParens(tree); - // If method invocation check the method select - if (tr.getKind() == Tree.Kind.ARRAY_ACCESS) { - return false; - } - - if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) { - tr = ((MethodInvocationTree) tree).getMethodSelect(); - } - tr = TreeUtils.withoutParens(tr); - if (tr.getKind() == Tree.Kind.TYPE_CAST) { - tr = ((TypeCastTree) tr).getExpression(); - } - tr = TreeUtils.withoutParens(tr); - - if (tr.getKind() == Tree.Kind.IDENTIFIER) { - return true; - } - - if (tr.getKind() == Tree.Kind.MEMBER_SELECT) { - tr = ((MemberSelectTree) tr).getExpression(); - if (tr.getKind() == Tree.Kind.IDENTIFIER) { - Name ident = ((IdentifierTree) tr).getName(); - return ident.contentEquals("this") || ident.contentEquals("super"); - } - } - + // Class cannot be instantiated. + private TreeUtils() { + throw new AssertionError("Class TreeUtils cannot be instantiated."); + } + + /** Unique IDs for trees. Used instead of hash codes, so output is deterministic. */ + public static final UniqueIdMap treeUids = new UniqueIdMap<>(); + + /** The latest source version supported by this compiler. */ + private static final int sourceVersionNumber = + Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); + + /** Whether we are running on at least Java 21. */ + private static final boolean atLeastJava21 = sourceVersionNumber >= 21; + + /** + * The {@code TreeMaker.Select(JCExpression, Symbol)} method. Return type changes for JDK21+. Only + * needs to be used while the code is compiled with JDK below 21. + */ + private static final @Nullable Method TREEMAKER_SELECT; + + /** The value of Flags.RECORD which does not exist in Java 9 or 11. */ + private static final long Flags_RECORD = 2305843009213693952L; + + /** Tree kinds that represent a binary comparison. */ + private static final Set BINARY_COMPARISON_TREE_KINDS = + EnumSet.of( + Tree.Kind.EQUAL_TO, + Tree.Kind.NOT_EQUAL_TO, + Tree.Kind.LESS_THAN, + Tree.Kind.GREATER_THAN, + Tree.Kind.LESS_THAN_EQUAL, + Tree.Kind.GREATER_THAN_EQUAL); + + static { + try { + TREEMAKER_SELECT = TreeMaker.class.getMethod("Select", JCExpression.class, Symbol.class); + } catch (NoSuchMethodException e) { + throw new AssertionError("Unexpected error in TreeUtils static initializer", e); + } + } + + /** + * Checks if the provided method is a constructor method or no. + * + * @param tree a tree defining the method + * @return true iff tree describes a constructor + */ + public static boolean isConstructor(MethodTree tree) { + return tree.getName().contentEquals(""); + } + + /** + * Checks if the method invocation is a call to super. + * + * @param tree a tree defining a method invocation + * @return true iff tree describes a call to super + */ + public static boolean isSuperConstructorCall(MethodInvocationTree tree) { + return isNamedMethodCall("super", tree); + } + + /** + * Checks if the method invocation is a call to "this". + * + * @param tree a tree defining a method invocation + * @return true iff tree describes a call to this + */ + public static boolean isThisConstructorCall(MethodInvocationTree tree) { + return isNamedMethodCall("this", tree); + } + + /** + * Checks if the method call is a call to the given method name. + * + * @param name a method name + * @param tree a tree defining a method invocation + * @return true iff tree is a call to the given method + */ + private static boolean isNamedMethodCall(String name, MethodInvocationTree tree) { + return getMethodName(tree.getMethodSelect()).contentEquals(name); + } + + /** + * Returns true if the tree is a tree that 'looks like' either an access of a field or an + * invocation of a method that are owned by the same accessing instance. + * + *

          It would only return true if the access tree is of the form: + * + *

          +   *   field
          +   *   this.field
          +   *
          +   *   method()
          +   *   this.method()
          +   * 
          + * + * It does not perform any semantical check to differentiate between fields and local variables; + * local methods or imported static methods. + * + * @param tree expression tree representing an access to object member + * @return {@code true} iff the member is a member of {@code this} instance + */ + public static boolean isSelfAccess(ExpressionTree tree) { + ExpressionTree tr = TreeUtils.withoutParens(tree); + // If method invocation check the method select + if (tr.getKind() == Tree.Kind.ARRAY_ACCESS) { + return false; + } + + if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) { + tr = ((MethodInvocationTree) tree).getMethodSelect(); + } + tr = TreeUtils.withoutParens(tr); + if (tr.getKind() == Tree.Kind.TYPE_CAST) { + tr = ((TypeCastTree) tr).getExpression(); + } + tr = TreeUtils.withoutParens(tr); + + if (tr.getKind() == Tree.Kind.IDENTIFIER) { + return true; + } + + if (tr.getKind() == Tree.Kind.MEMBER_SELECT) { + tr = ((MemberSelectTree) tr).getExpression(); + if (tr.getKind() == Tree.Kind.IDENTIFIER) { + Name ident = ((IdentifierTree) tr).getName(); + return ident.contentEquals("this") || ident.contentEquals("super"); + } + } + + return false; + } + + /** + * If the given tree is a parenthesized tree, return the enclosed non-parenthesized tree. + * Otherwise, return the same tree. + * + * @param tree an expression tree + * @return the outermost non-parenthesized tree enclosed by the given tree + */ + @SuppressWarnings("interning:return.type.incompatible") // pol ymorphism implementation + public static @PolyInterned ExpressionTree withoutParens(@PolyInterned ExpressionTree tree) { + ExpressionTree t = tree; + while (t.getKind() == Tree.Kind.PARENTHESIZED) { + t = ((ParenthesizedTree) t).getExpression(); + } + return t; + } + + /** + * If the given tree is a parenthesized tree or cast tree, return the enclosed non-parenthesized, + * non-cast tree. Otherwise, return the same tree. + * + * @param tree an expression tree + * @return the outermost non-parenthesized non-cast tree enclosed by the given tree + */ + @SuppressWarnings("interning:return.type.incompatible") // polymorphism implementation + public static @PolyInterned ExpressionTree withoutParensOrCasts( + @PolyInterned ExpressionTree tree) { + ExpressionTree t = withoutParens(tree); + while (t.getKind() == Tree.Kind.TYPE_CAST) { + t = withoutParens(((TypeCastTree) t).getExpression()); + } + return t; + } + + // Obtaining Elements from Trees. + // There are three sets of methods: + // * use elementFromDeclaration whenever the tree is a declaration + // * use elementFromUse when the tree is a use + // * use elementFromTree in other cases; note that it may return null + // This section of the file groups methods by their receiver type; that is, it puts all + // `elementFrom*(FooTree)` methods together. + + /** + * Return the package element corresponding to the given package declaration. + * + * @param tree package declaration + * @return the package element for the given package + */ + public static PackageElement elementFromDeclaration(PackageTree tree) { + PackageElement result = (PackageElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for package tree %s", tree); + } + return result; + } + + // TODO: Document when this may return null. + /** + * Returns the type element corresponding to the given class declaration. + * + * @param tree class declaration + * @return the element for the given class + */ + public static TypeElement elementFromDeclaration(ClassTree tree) { + TypeElement result = (TypeElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for class tree %s", tree); + } + return result; + } + + /** + * Returns the type element corresponding to the given class declaration. + * + *

          The TypeElement may be null for an anonymous class. + * + * @param tree the {@link ClassTree} node to get the element for + * @return the {@link TypeElement} for the given tree + * @deprecated use {@link #elementFromDeclaration(ClassTree)} + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static TypeElement elementFromTree(ClassTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the type element corresponding to the given class declaration. + * + * @param tree the {@link ClassTree} node to get the element for + * @return the {@link TypeElement} for the given tree + * @deprecated use {@link #elementFromDeclaration(ClassTree)} + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static TypeElement elementFromUse(ClassTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the element corresponding to the given tree. + * + * @param tree the tree corresponding to a use of an element + * @return the element for the corresponding declaration, {@code null} otherwise + * @deprecated use {@link #elementFromUse(ExpressionTree)} or {@link + * #elementFromTree(ExpressionTree)} + */ + @Pure + @Deprecated // not for removal; retain to prevent calls to this overload + public static @Nullable Element elementFromDeclaration(ExpressionTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the element corresponding to the given tree. + * + * @param tree the tree corresponding to a use of an element + * @return the element for the corresponding declaration, {@code null} otherwise + */ + @Pure + public static @Nullable Element elementFromTree(ExpressionTree tree) { + return TreeUtils.elementFromTree((Tree) tree); + } + + /** + * Gets the element for the declaration corresponding to this use of an element. To get the + * element for a declaration, use {@link #elementFromDeclaration(ClassTree)}, {@link + * #elementFromDeclaration(MethodTree)}, or {@link #elementFromDeclaration(VariableTree)} instead. + * + *

          This method is just a wrapper around {@link TreeUtils#elementFromTree(Tree)}, but this class + * might be the first place someone looks for this functionality. + * + * @param tree the tree, which must be a use of an element + * @return the element for the corresponding declaration + */ + @Pure + public static Element elementFromUse(ExpressionTree tree) { + Element result = TreeUtils.elementFromTree(tree); + if (result == null) { + throw new BugInCF( + "argument to elementFromUse() has no element: %s [%s]", tree, tree.getClass()); + } + return result; + } + + /** + * Returns the VariableElement corresponding to the given use. + * + * @param tree the tree corresponding to a use of a VariableElement + * @return the element for the corresponding declaration + */ + @Pure + public static VariableElement variableElementFromUse(ExpressionTree tree) { + VariableElement result = TreeUtils.variableElementFromTree(tree); + if (result == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + } + return result; + } + + /** + * Returns the element for the given expression. + * + * @param tree the {@link Tree} node to get the element for + * @return the element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable Element elementFromDeclaration(MemberSelectTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the element for the given expression. + * + * @param tree the {@link Tree} node to get the element for + * @return the element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable Element elementFromTree(MemberSelectTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the element for the given expression. + * + * @param tree a method call + * @return the element for the called method + */ + @Pure + public static Element elementFromUse(MemberSelectTree tree) { + Element result = TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("tree = " + tree); + } + return result; + } + + /** + * Returns the ExecutableElement for the called method. + * + * @param tree the {@link Tree} node to get the element for + * @return the Element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable ExecutableElement elementFromDeclaration(MethodInvocationTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the ExecutableElement for the called method. + * + * @param tree the {@link Tree} node to get the element for + * @return the Element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable ExecutableElement elementFromTree(MethodInvocationTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the ExecutableElement for the called method. + * + * @param tree a method call + * @return the ExecutableElement for the called method + */ + @Pure + public static ExecutableElement elementFromUse(MethodInvocationTree tree) { + Element result = TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("tree = %s [%s]", tree, tree.getClass()); + } + if (!(result instanceof ExecutableElement)) { + throw new BugInCF( + "Method elements should be ExecutableElement. Found: %s [%s]", result, result.getClass()); + } + return (ExecutableElement) result; + } + + /** + * Returns the ExecutableElement for the method reference. + * + * @param tree a method reference + * @return the ExecutableElement for the method reference + */ + @Pure + public static ExecutableElement elementFromUse(MemberReferenceTree tree) { + Element result = elementFromUse((ExpressionTree) tree); + if (!(result instanceof ExecutableElement)) { + throw new BugInCF( + "Method reference elements should be ExecutableElement. Found: %s [%s]", + result, result.getClass()); + } + return (ExecutableElement) result; + } + + /** + * Returns the ExecutableElement for the given method declaration. + * + *

          The result can be null, when {@code tree} is a method in an anonymous class and that class + * has not been processed yet. An exception will be raised. Adapt your processing order. + * + * @param tree a method declaration + * @return the element for the given method + */ + public static ExecutableElement elementFromDeclaration(MethodTree tree) { + ExecutableElement result = (ExecutableElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for method tree %s", tree); + } + return result; + } + + /** + * Returns the ExecutableElement for the given method declaration. + * + * @param tree the {@link MethodTree} node to get the element for + * @return the Element for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromTree(MethodTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the ExecutableElement for the given method declaration. + * + * @param tree the {@link MethodTree} node to get the element for + * @return the Element for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromUse(MethodTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the ExecutableElement for the given constructor invocation. + * + * @param tree the {@link NewClassTree} node to get the element for + * @return the {@link ExecutableElement} for the given tree, or null if one could not be found + * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal tree + * (JCTree) + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromDeclaration(NewClassTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Gets the ExecutableElement for the called constructor, from a constructor invocation. + * + * @param tree the {@link NewClassTree} node to get the element for + * @return the {@link ExecutableElement} for the given tree, or null if one could not be found + * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal tree + * (JCTree) + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromTree(NewClassTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Gets the ExecutableElement for the called constructor, from a constructor invocation. + * + * @param tree a constructor invocation + * @return the ExecutableElement for the called constructor + * @see #elementFromUse(NewClassTree) + */ + @Pure + public static ExecutableElement elementFromUse(NewClassTree tree) { + Element result = TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for %s", tree); + } + if (!(result instanceof ExecutableElement)) { + throw new BugInCF( + "Constructor elements should be ExecutableElement. Found: %s [%s]", + result, result.getClass()); + } + return (ExecutableElement) result; + } + + /** + * Returns the VariableElement corresponding to the given variable declaration. + * + * @param tree the variable + * @return the element for the given variable + */ + public static VariableElement elementFromDeclaration(VariableTree tree) { + VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); + // `result` can be null, for example for this variable declaration: + // PureFunc f1 = TestPure1::myPureMethod; + // TODO: check claim above. Initializer expression should have no impact on variable. + if (result == null) { + throw new BugInCF("null element for variable tree %s", tree); + } + return result; + } + + /** + * Returns the VariableElement corresponding to the given variable declaration. + * + * @param tree the {@link VariableTree} node to get the element for + * @return the {@link VariableElement} for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static VariableElement elementFromTree(VariableTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the VariableElement corresponding to the given variable declaration. + * + * @param tree the {@link VariableTree} node to get the element for + * @return the {@link VariableElement} for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static VariableElement elementFromUse(VariableTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the {@link VariableElement} for the given Tree API node. + * + * @param tree the {@link Tree} node to get the element for + * @return the {@link VariableElement} for the given tree + * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal tree + * (JCTree) + */ + @Pure + public static VariableElement variableElementFromTree(Tree tree) { + VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + } + return result; + } + + /** + * Returns the {@link Element} for the given Tree API node. For an object instantiation returns + * the value of the {@link JCNewClass#constructor} field. + * + *

          Use this only when you do not statically know whether the tree is a declaration or a use of + * an element. + * + * @param tree the {@link Tree} node to get the element for + * @return the {@link Element} for the given tree, or null if one could not be found + * @throws BugInCF if {@code tree} is null or is not a valid javac-internal tree (JCTree) + */ + @Pure + public static @Nullable Element elementFromTree(Tree tree) { + if (tree == null) { + throw new BugInCF("TreeUtils.elementFromTree: tree is null"); + } + + if (!(tree instanceof JCTree)) { + throw new BugInCF( + "TreeUtils.elementFromTree: tree is not a valid Javac tree but a " + tree.getClass()); + } + + if (isExpressionTree(tree)) { + tree = withoutParensOrCasts((ExpressionTree) tree); + } + + switch (tree.getKind()) { + // symbol() only works on MethodSelects, so we need to get it manually + // for method invocations. + case METHOD_INVOCATION: + return TreeInfo.symbol(((JCMethodInvocation) tree).getMethodSelect()); + + case ASSIGNMENT: + return TreeInfo.symbol((JCTree) ((AssignmentTree) tree).getVariable()); + + case ARRAY_ACCESS: + return elementFromTree(((ArrayAccessTree) tree).getExpression()); + + case NEW_CLASS: + return ((JCNewClass) tree).constructor; + + case MEMBER_REFERENCE: + // TreeInfo.symbol, which is used in the default case, didn't handle + // member references until JDK8u20. So handle it here. + ExecutableElement memberResult = (ExecutableElement) ((JCMemberReference) tree).sym; + return memberResult; + + default: + Element defaultResult; + if (isTypeDeclaration(tree) + || tree.getKind() == Tree.Kind.VARIABLE + || tree.getKind() == Tree.Kind.METHOD) { + defaultResult = TreeInfo.symbolFor((JCTree) tree); + } else { + defaultResult = TreeInfo.symbol((JCTree) tree); + } + return defaultResult; + } + } + + /** + * Returns the constructor invoked by {@code newClassTree} unless {@code newClassTree} is creating + * an anonymous class. In which case, the super constructor is returned. + * + * @param newClassTree the constructor invocation + * @return the super constructor invoked in the body of the anonymous constructor; or {@link + * #elementFromUse(NewClassTree)} if {@code newClassTree} is not creating an anonymous class + */ + public static ExecutableElement getSuperConstructor(NewClassTree newClassTree) { + if (newClassTree.getClassBody() == null) { + return elementFromUse(newClassTree); + } + JCNewClass jcNewClass = (JCNewClass) newClassTree; + // Anonymous constructor bodies, which are always synthetic, contain exactly one statement + // in the form: + // super(arg1, ...) + // or + // o.super(arg1, ...) + // + // which is a method invocation of the super constructor. + + // The method call is guaranteed to return nonnull. + JCMethodDecl anonConstructor = + (JCMethodDecl) TreeInfo.declarationFor(jcNewClass.constructor, jcNewClass); + assert anonConstructor != null; + assert anonConstructor.body.stats.size() == 1; + JCExpressionStatement stmt = (JCExpressionStatement) anonConstructor.body.stats.head; + JCMethodInvocation superInvok = (JCMethodInvocation) stmt.expr; + return (ExecutableElement) TreeInfo.symbol(superInvok.meth); + } + + /** + * Determine whether the given ExpressionTree has an underlying element. + * + * @param tree the ExpressionTree to test + * @return whether the tree refers to an identifier, member select, or method invocation + */ + @EnsuresNonNullIf(result = true, expression = "elementFromTree(#1)") + @EnsuresNonNullIf(result = true, expression = "elementFromUse(#1)") + @Pure + public static boolean isUseOfElement(ExpressionTree tree) { + ExpressionTree realnode = TreeUtils.withoutParens(tree); + switch (realnode.getKind()) { + case IDENTIFIER: + case MEMBER_SELECT: + case METHOD_INVOCATION: + case NEW_CLASS: + assert elementFromTree(tree) != null : "@AssumeAssertion(nullness): inspection"; + assert elementFromUse(tree) != null : "@AssumeAssertion(nullness): inspection"; + return true; + default: return false; } - - /** - * If the given tree is a parenthesized tree, return the enclosed non-parenthesized tree. - * Otherwise, return the same tree. - * - * @param tree an expression tree - * @return the outermost non-parenthesized tree enclosed by the given tree - */ - @SuppressWarnings("interning:return.type.incompatible") // pol ymorphism implementation - public static @PolyInterned ExpressionTree withoutParens(@PolyInterned ExpressionTree tree) { - ExpressionTree t = tree; - while (t.getKind() == Tree.Kind.PARENTHESIZED) { - t = ((ParenthesizedTree) t).getExpression(); - } - return t; - } - - /** - * If the given tree is a parenthesized tree or cast tree, return the enclosed - * non-parenthesized, non-cast tree. Otherwise, return the same tree. - * - * @param tree an expression tree - * @return the outermost non-parenthesized non-cast tree enclosed by the given tree - */ - @SuppressWarnings("interning:return.type.incompatible") // polymorphism implementation - public static @PolyInterned ExpressionTree withoutParensOrCasts( - @PolyInterned ExpressionTree tree) { - ExpressionTree t = withoutParens(tree); - while (t.getKind() == Tree.Kind.TYPE_CAST) { - t = withoutParens(((TypeCastTree) t).getExpression()); - } - return t; - } - - // Obtaining Elements from Trees. - // There are three sets of methods: - // * use elementFromDeclaration whenever the tree is a declaration - // * use elementFromUse when the tree is a use - // * use elementFromTree in other cases; note that it may return null - // This section of the file groups methods by their receiver type; that is, it puts all - // `elementFrom*(FooTree)` methods together. - - /** - * Return the package element corresponding to the given package declaration. - * - * @param tree package declaration - * @return the package element for the given package - */ - public static PackageElement elementFromDeclaration(PackageTree tree) { - PackageElement result = (PackageElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for package tree %s", tree); - } - return result; - } - - // TODO: Document when this may return null. - /** - * Returns the type element corresponding to the given class declaration. - * - * @param tree class declaration - * @return the element for the given class - */ - public static TypeElement elementFromDeclaration(ClassTree tree) { - TypeElement result = (TypeElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for class tree %s", tree); - } - return result; - } - - /** - * Returns the type element corresponding to the given class declaration. - * - *

          The TypeElement may be null for an anonymous class. - * - * @param tree the {@link ClassTree} node to get the element for - * @return the {@link TypeElement} for the given tree - * @deprecated use {@link #elementFromDeclaration(ClassTree)} - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static TypeElement elementFromTree(ClassTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the type element corresponding to the given class declaration. - * - * @param tree the {@link ClassTree} node to get the element for - * @return the {@link TypeElement} for the given tree - * @deprecated use {@link #elementFromDeclaration(ClassTree)} - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static TypeElement elementFromUse(ClassTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the element corresponding to the given tree. - * - * @param tree the tree corresponding to a use of an element - * @return the element for the corresponding declaration, {@code null} otherwise - * @deprecated use {@link #elementFromUse(ExpressionTree)} or {@link - * #elementFromTree(ExpressionTree)} - */ - @Pure - @Deprecated // not for removal; retain to prevent calls to this overload - public static @Nullable Element elementFromDeclaration(ExpressionTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the element corresponding to the given tree. - * - * @param tree the tree corresponding to a use of an element - * @return the element for the corresponding declaration, {@code null} otherwise - */ - @Pure - public static @Nullable Element elementFromTree(ExpressionTree tree) { - return TreeUtils.elementFromTree((Tree) tree); - } - - /** - * Gets the element for the declaration corresponding to this use of an element. To get the - * element for a declaration, use {@link #elementFromDeclaration(ClassTree)}, {@link - * #elementFromDeclaration(MethodTree)}, or {@link #elementFromDeclaration(VariableTree)} - * instead. - * - *

          This method is just a wrapper around {@link TreeUtils#elementFromTree(Tree)}, but this - * class might be the first place someone looks for this functionality. - * - * @param tree the tree, which must be a use of an element - * @return the element for the corresponding declaration - */ - @Pure - public static Element elementFromUse(ExpressionTree tree) { - Element result = TreeUtils.elementFromTree(tree); - if (result == null) { - throw new BugInCF( - "argument to elementFromUse() has no element: %s [%s]", tree, tree.getClass()); - } - return result; - } - - /** - * Returns the VariableElement corresponding to the given use. - * - * @param tree the tree corresponding to a use of a VariableElement - * @return the element for the corresponding declaration - */ - @Pure - public static VariableElement variableElementFromUse(ExpressionTree tree) { - VariableElement result = TreeUtils.variableElementFromTree(tree); - if (result == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); - } - return result; - } - - /** - * Returns the element for the given expression. - * - * @param tree the {@link Tree} node to get the element for - * @return the element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable Element elementFromDeclaration(MemberSelectTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the element for the given expression. - * - * @param tree the {@link Tree} node to get the element for - * @return the element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable Element elementFromTree(MemberSelectTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the element for the given expression. - * - * @param tree a method call - * @return the element for the called method - */ - @Pure - public static Element elementFromUse(MemberSelectTree tree) { - Element result = TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("tree = " + tree); - } - return result; - } - - /** - * Returns the ExecutableElement for the called method. - * - * @param tree the {@link Tree} node to get the element for - * @return the Element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable ExecutableElement elementFromDeclaration(MethodInvocationTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the ExecutableElement for the called method. - * - * @param tree the {@link Tree} node to get the element for - * @return the Element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable ExecutableElement elementFromTree(MethodInvocationTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the ExecutableElement for the called method. - * - * @param tree a method call - * @return the ExecutableElement for the called method - */ - @Pure - public static ExecutableElement elementFromUse(MethodInvocationTree tree) { - Element result = TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("tree = %s [%s]", tree, tree.getClass()); - } - if (!(result instanceof ExecutableElement)) { - throw new BugInCF( - "Method elements should be ExecutableElement. Found: %s [%s]", - result, result.getClass()); - } - return (ExecutableElement) result; - } - - /** - * Returns the ExecutableElement for the method reference. - * - * @param tree a method reference - * @return the ExecutableElement for the method reference - */ - @Pure - public static ExecutableElement elementFromUse(MemberReferenceTree tree) { - Element result = elementFromUse((ExpressionTree) tree); - if (!(result instanceof ExecutableElement)) { - throw new BugInCF( - "Method reference elements should be ExecutableElement. Found: %s [%s]", - result, result.getClass()); - } - return (ExecutableElement) result; - } - - /** - * Returns the ExecutableElement for the given method declaration. - * - *

          The result can be null, when {@code tree} is a method in an anonymous class and that class - * has not been processed yet. An exception will be raised. Adapt your processing order. - * - * @param tree a method declaration - * @return the element for the given method - */ - public static ExecutableElement elementFromDeclaration(MethodTree tree) { - ExecutableElement result = (ExecutableElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for method tree %s", tree); - } - return result; - } - - /** - * Returns the ExecutableElement for the given method declaration. - * - * @param tree the {@link MethodTree} node to get the element for - * @return the Element for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromTree(MethodTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the ExecutableElement for the given method declaration. - * - * @param tree the {@link MethodTree} node to get the element for - * @return the Element for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromUse(MethodTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the ExecutableElement for the given constructor invocation. - * - * @param tree the {@link NewClassTree} node to get the element for - * @return the {@link ExecutableElement} for the given tree, or null if one could not be found - * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal - * tree (JCTree) - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromDeclaration(NewClassTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Gets the ExecutableElement for the called constructor, from a constructor invocation. - * - * @param tree the {@link NewClassTree} node to get the element for - * @return the {@link ExecutableElement} for the given tree, or null if one could not be found - * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal - * tree (JCTree) - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromTree(NewClassTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Gets the ExecutableElement for the called constructor, from a constructor invocation. - * - * @param tree a constructor invocation - * @return the ExecutableElement for the called constructor - * @see #elementFromUse(NewClassTree) - */ - @Pure - public static ExecutableElement elementFromUse(NewClassTree tree) { - Element result = TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for %s", tree); - } - if (!(result instanceof ExecutableElement)) { - throw new BugInCF( - "Constructor elements should be ExecutableElement. Found: %s [%s]", - result, result.getClass()); - } - return (ExecutableElement) result; - } - - /** - * Returns the VariableElement corresponding to the given variable declaration. - * - * @param tree the variable - * @return the element for the given variable - */ - public static VariableElement elementFromDeclaration(VariableTree tree) { - VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); - // `result` can be null, for example for this variable declaration: - // PureFunc f1 = TestPure1::myPureMethod; - // TODO: check claim above. Initializer expression should have no impact on variable. - if (result == null) { - throw new BugInCF("null element for variable tree %s", tree); - } - return result; - } - - /** - * Returns the VariableElement corresponding to the given variable declaration. - * - * @param tree the {@link VariableTree} node to get the element for - * @return the {@link VariableElement} for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static VariableElement elementFromTree(VariableTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the VariableElement corresponding to the given variable declaration. - * - * @param tree the {@link VariableTree} node to get the element for - * @return the {@link VariableElement} for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static VariableElement elementFromUse(VariableTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the {@link VariableElement} for the given Tree API node. - * - * @param tree the {@link Tree} node to get the element for - * @return the {@link VariableElement} for the given tree - * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal - * tree (JCTree) - */ - @Pure - public static VariableElement variableElementFromTree(Tree tree) { - VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); - } - return result; - } - - /** - * Returns the {@link Element} for the given Tree API node. For an object instantiation returns - * the value of the {@link JCNewClass#constructor} field. - * - *

          Use this only when you do not statically know whether the tree is a declaration or a use - * of an element. - * - * @param tree the {@link Tree} node to get the element for - * @return the {@link Element} for the given tree, or null if one could not be found - * @throws BugInCF if {@code tree} is null or is not a valid javac-internal tree (JCTree) - */ - @Pure - public static @Nullable Element elementFromTree(Tree tree) { - if (tree == null) { - throw new BugInCF("TreeUtils.elementFromTree: tree is null"); - } - - if (!(tree instanceof JCTree)) { - throw new BugInCF( - "TreeUtils.elementFromTree: tree is not a valid Javac tree but a " - + tree.getClass()); - } - - if (isExpressionTree(tree)) { - tree = withoutParensOrCasts((ExpressionTree) tree); - } - - switch (tree.getKind()) { - // symbol() only works on MethodSelects, so we need to get it manually - // for method invocations. - case METHOD_INVOCATION: - return TreeInfo.symbol(((JCMethodInvocation) tree).getMethodSelect()); - - case ASSIGNMENT: - return TreeInfo.symbol((JCTree) ((AssignmentTree) tree).getVariable()); - - case ARRAY_ACCESS: - return elementFromTree(((ArrayAccessTree) tree).getExpression()); - - case NEW_CLASS: - return ((JCNewClass) tree).constructor; - - case MEMBER_REFERENCE: - // TreeInfo.symbol, which is used in the default case, didn't handle - // member references until JDK8u20. So handle it here. - ExecutableElement memberResult = (ExecutableElement) ((JCMemberReference) tree).sym; - return memberResult; - - default: - Element defaultResult; - if (isTypeDeclaration(tree) - || tree.getKind() == Tree.Kind.VARIABLE - || tree.getKind() == Tree.Kind.METHOD) { - defaultResult = TreeInfo.symbolFor((JCTree) tree); - } else { - defaultResult = TreeInfo.symbol((JCTree) tree); - } - return defaultResult; - } - } - - /** - * Returns the constructor invoked by {@code newClassTree} unless {@code newClassTree} is - * creating an anonymous class. In which case, the super constructor is returned. - * - * @param newClassTree the constructor invocation - * @return the super constructor invoked in the body of the anonymous constructor; or {@link - * #elementFromUse(NewClassTree)} if {@code newClassTree} is not creating an anonymous class - */ - public static ExecutableElement getSuperConstructor(NewClassTree newClassTree) { - if (newClassTree.getClassBody() == null) { - return elementFromUse(newClassTree); - } - JCNewClass jcNewClass = (JCNewClass) newClassTree; - // Anonymous constructor bodies, which are always synthetic, contain exactly one statement - // in the form: - // super(arg1, ...) - // or - // o.super(arg1, ...) - // - // which is a method invocation of the super constructor. - - // The method call is guaranteed to return nonnull. - JCMethodDecl anonConstructor = - (JCMethodDecl) TreeInfo.declarationFor(jcNewClass.constructor, jcNewClass); - assert anonConstructor != null; - assert anonConstructor.body.stats.size() == 1; - JCExpressionStatement stmt = (JCExpressionStatement) anonConstructor.body.stats.head; - JCMethodInvocation superInvok = (JCMethodInvocation) stmt.expr; - return (ExecutableElement) TreeInfo.symbol(superInvok.meth); + } + + /** + * Returns true if {@code tree} has a synthetic argument. + * + *

          For some anonymous classes with an explicit enclosing expression, javac creates a synthetic + * argument to the constructor that is the enclosing expression of the NewClassTree. Suppose a + * programmer writes: + * + *

          {@code class Outer {
          +   *   class Inner { }
          +   *     void method() {
          +   *       this.new Inner(){};
          +   *     }
          +   * }}
          + * + * Java 9 javac creates the following synthetic tree for {@code this.new Inner(){}}: + * + *
          {@code new Inner(this) {
          +   *   (.Outer x0) {
          +   *     x0.super();
          +   *   }
          +   * }}
          + * + * Java 11 javac creates a different tree without the synthetic argument for {@code this.new + * Inner(){}}; the first line in the below code differs: + * + *
          {@code this.new Inner() {
          +   *   (.Outer x0) {
          +   *     x0.super();
          +   *   }
          +   * }}
          + * + * @param tree a new class tree + * @return true if {@code tree} has a synthetic argument + */ + public static boolean hasSyntheticArgument(NewClassTree tree) { + if (tree.getClassBody() == null || tree.getEnclosingExpression() != null) { + return false; + } + for (Tree member : tree.getClassBody().getMembers()) { + if (member.getKind() == Tree.Kind.METHOD && isConstructor((MethodTree) member)) { + MethodTree methodTree = (MethodTree) member; + StatementTree f = methodTree.getBody().getStatements().get(0); + return TreeUtils.getReceiverTree(((ExpressionStatementTree) f).getExpression()) != null; + } + } + return false; + } + + /** + * Returns the name of the invoked method. + * + * @param tree the method invocation + * @return the name of the invoked method + */ + public static Name methodName(MethodInvocationTree tree) { + ExpressionTree expr = tree.getMethodSelect(); + if (expr.getKind() == Tree.Kind.IDENTIFIER) { + return ((IdentifierTree) expr).getName(); + } else if (expr.getKind() == Tree.Kind.MEMBER_SELECT) { + return ((MemberSelectTree) expr).getIdentifier(); + } + throw new BugInCF("TreeUtils.methodName: cannot be here: " + tree); + } + + /** + * Returns true if the first statement in the body is a self constructor invocation within a + * constructor. + * + * @param tree the method declaration + * @return true if the first statement in the body is a self constructor invocation within a + * constructor + */ + public static boolean containsThisConstructorInvocation(MethodTree tree) { + if (!TreeUtils.isConstructor(tree) || tree.getBody().getStatements().isEmpty()) { + return false; + } + + StatementTree st = tree.getBody().getStatements().get(0); + if (!(st instanceof ExpressionStatementTree) + || !(((ExpressionStatementTree) st).getExpression() instanceof MethodInvocationTree)) { + return false; + } + + MethodInvocationTree invocation = + (MethodInvocationTree) ((ExpressionStatementTree) st).getExpression(); + + return "this".contentEquals(TreeUtils.methodName(invocation)); + } + + /** + * Returns the first statement of the tree if it is a block. If it is not a block or an empty + * block, tree is returned. + * + * @param tree any kind of tree + * @return the first statement of the tree if it is a block. If it is not a block or an empty + * block, tree is returned. + */ + public static Tree firstStatement(Tree tree) { + Tree first; + if (tree.getKind() == Tree.Kind.BLOCK) { + BlockTree block = (BlockTree) tree; + if (block.getStatements().isEmpty()) { + first = block; + } else { + first = block.getStatements().iterator().next(); + } + } else { + first = tree; + } + return first; + } + + /** + * Determine whether the given class contains an explicit constructor. + * + * @param tree a class tree + * @return true iff there is an explicit constructor + */ + public static boolean hasExplicitConstructor(ClassTree tree) { + TypeElement elem = TreeUtils.elementFromDeclaration(tree); + for (ExecutableElement constructorElt : + ElementFilter.constructorsIn(elem.getEnclosedElements())) { + if (!isSynthetic(constructorElt)) { + return true; + } + } + return false; + } + + /** + * Returns true if the given method is synthetic. Also returns true if the method is a generated + * default constructor, which does not appear in source code but is not considered synthetic. + * + * @param ee a method or constructor element + * @return true iff the given method is synthetic + */ + public static boolean isSynthetic(ExecutableElement ee) { + MethodSymbol ms = (MethodSymbol) ee; + long mod = ms.flags(); + // GENERATEDCONSTR is for generated constructors, which do not have SYNTHETIC set. + return (mod & (Flags.SYNTHETIC | Flags.GENERATEDCONSTR)) != 0; + } + + /** + * Returns true if the given method is synthetic. + * + * @param tree a method declaration tree + * @return true iff the given method is synthetic + */ + public static boolean isSynthetic(MethodTree tree) { + ExecutableElement ee = TreeUtils.elementFromDeclaration(tree); + return isSynthetic(ee); + } + + /** + * Returns true if the tree is of a diamond type. In contrast to the implementation in TreeInfo, + * this version works on Trees. + * + * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree) + */ + public static boolean isDiamondTree(Tree tree) { + switch (tree.getKind()) { + case ANNOTATED_TYPE: + return isDiamondTree(((AnnotatedTypeTree) tree).getUnderlyingType()); + case PARAMETERIZED_TYPE: + return ((ParameterizedTypeTree) tree).getTypeArguments().isEmpty(); + case NEW_CLASS: + return isDiamondTree(((NewClassTree) tree).getIdentifier()); + default: + return false; } - - /** - * Determine whether the given ExpressionTree has an underlying element. - * - * @param tree the ExpressionTree to test - * @return whether the tree refers to an identifier, member select, or method invocation - */ - @EnsuresNonNullIf(result = true, expression = "elementFromTree(#1)") - @EnsuresNonNullIf(result = true, expression = "elementFromUse(#1)") - @Pure - public static boolean isUseOfElement(ExpressionTree tree) { - ExpressionTree realnode = TreeUtils.withoutParens(tree); - switch (realnode.getKind()) { - case IDENTIFIER: - case MEMBER_SELECT: - case METHOD_INVOCATION: - case NEW_CLASS: - assert elementFromTree(tree) != null : "@AssumeAssertion(nullness): inspection"; - assert elementFromUse(tree) != null : "@AssumeAssertion(nullness): inspection"; - return true; - default: - return false; - } + } + + /** Returns true if the tree represents a {@code String} concatenation operation. */ + public static boolean isStringConcatenation(Tree tree) { + return (tree.getKind() == Tree.Kind.PLUS && TypesUtils.isString(TreeUtils.typeOf(tree))); + } + + /** Returns true if the compound assignment tree is a string concatenation. */ + public static boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) { + return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT + && TypesUtils.isString(TreeUtils.typeOf(tree))); + } + + /** + * Is this method's declared return type "void"? + * + * @param tree a method declaration + * @return true iff method's declared return type is "void" + */ + public static boolean isVoidReturn(MethodTree tree) { + return typeOf(tree.getReturnType()).getKind() == TypeKind.VOID; + } + + /** + * Returns true if the tree is a constant-time expression. + * + *

          A tree is a constant-time expression if it is: + * + *

            + *
          1. a literal tree + *
          2. a reference to a final variable initialized with a compile time constant + *
          3. a String concatenation of two compile time constants + *
          + * + * @param tree the tree to check + * @return true if the tree is a constant-time expression + */ + public static boolean isCompileTimeString(ExpressionTree tree) { + tree = TreeUtils.withoutParens(tree); + if (tree instanceof LiteralTree) { + return true; + } + + if (TreeUtils.isUseOfElement(tree)) { + Element elt = TreeUtils.elementFromUse(tree); + return ElementUtils.isCompileTimeConstant(elt); + } else if (TreeUtils.isStringConcatenation(tree)) { + BinaryTree binOp = (BinaryTree) tree; + return isCompileTimeString(binOp.getLeftOperand()) + && isCompileTimeString(binOp.getRightOperand()); + } else { + return false; + } + } + + /** + * Returns the receiver tree of a field access or a method invocation. + * + * @param expression a field access or a method invocation + * @return the expression's receiver tree, or null if it does not have an explicit receiver + */ + public static @Nullable ExpressionTree getReceiverTree(ExpressionTree expression) { + ExpressionTree receiver; + switch (expression.getKind()) { + case METHOD_INVOCATION: + // Trying to handle receiver calls to trees of the form + // ((m).getArray()) + // returns the type of 'm' in this case + receiver = ((MethodInvocationTree) expression).getMethodSelect(); + + if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { + receiver = ((MemberSelectTree) receiver).getExpression(); + } else { + // It's a method call "m(foo)" without an explicit receiver + return null; + } + break; + case NEW_CLASS: + receiver = ((NewClassTree) expression).getEnclosingExpression(); + break; + case ARRAY_ACCESS: + receiver = ((ArrayAccessTree) expression).getExpression(); + break; + case MEMBER_SELECT: + receiver = ((MemberSelectTree) expression).getExpression(); + // Avoid int.class + if (receiver instanceof PrimitiveTypeTree) { + return null; + } + break; + case IDENTIFIER: + // It's a field access on implicit this or a local variable/parameter. + return null; + default: + return null; } - - /** - * Returns true if {@code tree} has a synthetic argument. - * - *

          For some anonymous classes with an explicit enclosing expression, javac creates a - * synthetic argument to the constructor that is the enclosing expression of the NewClassTree. - * Suppose a programmer writes: - * - *

          {@code class Outer {
          -     *   class Inner { }
          -     *     void method() {
          -     *       this.new Inner(){};
          -     *     }
          -     * }}
          - * - * Java 9 javac creates the following synthetic tree for {@code this.new Inner(){}}: - * - *
          {@code new Inner(this) {
          -     *   (.Outer x0) {
          -     *     x0.super();
          -     *   }
          -     * }}
          - * - * Java 11 javac creates a different tree without the synthetic argument for {@code this.new - * Inner(){}}; the first line in the below code differs: - * - *
          {@code this.new Inner() {
          -     *   (.Outer x0) {
          -     *     x0.super();
          -     *   }
          -     * }}
          - * - * @param tree a new class tree - * @return true if {@code tree} has a synthetic argument - */ - public static boolean hasSyntheticArgument(NewClassTree tree) { - if (tree.getClassBody() == null || tree.getEnclosingExpression() != null) { - return false; - } - for (Tree member : tree.getClassBody().getMembers()) { - if (member.getKind() == Tree.Kind.METHOD && isConstructor((MethodTree) member)) { - MethodTree methodTree = (MethodTree) member; - StatementTree f = methodTree.getBody().getStatements().get(0); - return TreeUtils.getReceiverTree(((ExpressionStatementTree) f).getExpression()) - != null; - } - } - return false; - } - - /** - * Returns the name of the invoked method. - * - * @param tree the method invocation - * @return the name of the invoked method - */ - public static Name methodName(MethodInvocationTree tree) { - ExpressionTree expr = tree.getMethodSelect(); - if (expr.getKind() == Tree.Kind.IDENTIFIER) { - return ((IdentifierTree) expr).getName(); - } else if (expr.getKind() == Tree.Kind.MEMBER_SELECT) { - return ((MemberSelectTree) expr).getIdentifier(); - } - throw new BugInCF("TreeUtils.methodName: cannot be here: " + tree); - } - - /** - * Returns true if the first statement in the body is a self constructor invocation within a - * constructor. - * - * @param tree the method declaration - * @return true if the first statement in the body is a self constructor invocation within a - * constructor - */ - public static boolean containsThisConstructorInvocation(MethodTree tree) { - if (!TreeUtils.isConstructor(tree) || tree.getBody().getStatements().isEmpty()) { - return false; - } - - StatementTree st = tree.getBody().getStatements().get(0); - if (!(st instanceof ExpressionStatementTree) - || !(((ExpressionStatementTree) st).getExpression() - instanceof MethodInvocationTree)) { - return false; - } - - MethodInvocationTree invocation = - (MethodInvocationTree) ((ExpressionStatementTree) st).getExpression(); - - return "this".contentEquals(TreeUtils.methodName(invocation)); - } - - /** - * Returns the first statement of the tree if it is a block. If it is not a block or an empty - * block, tree is returned. - * - * @param tree any kind of tree - * @return the first statement of the tree if it is a block. If it is not a block or an empty - * block, tree is returned. - */ - public static Tree firstStatement(Tree tree) { - Tree first; - if (tree.getKind() == Tree.Kind.BLOCK) { - BlockTree block = (BlockTree) tree; - if (block.getStatements().isEmpty()) { - first = block; - } else { - first = block.getStatements().iterator().next(); - } - } else { - first = tree; - } - return first; - } - - /** - * Determine whether the given class contains an explicit constructor. - * - * @param tree a class tree - * @return true iff there is an explicit constructor - */ - public static boolean hasExplicitConstructor(ClassTree tree) { - TypeElement elem = TreeUtils.elementFromDeclaration(tree); - for (ExecutableElement constructorElt : - ElementFilter.constructorsIn(elem.getEnclosedElements())) { - if (!isSynthetic(constructorElt)) { - return true; - } - } - return false; - } - - /** - * Returns true if the given method is synthetic. Also returns true if the method is a generated - * default constructor, which does not appear in source code but is not considered synthetic. - * - * @param ee a method or constructor element - * @return true iff the given method is synthetic - */ - public static boolean isSynthetic(ExecutableElement ee) { - MethodSymbol ms = (MethodSymbol) ee; - long mod = ms.flags(); - // GENERATEDCONSTR is for generated constructors, which do not have SYNTHETIC set. - return (mod & (Flags.SYNTHETIC | Flags.GENERATEDCONSTR)) != 0; - } - - /** - * Returns true if the given method is synthetic. - * - * @param tree a method declaration tree - * @return true iff the given method is synthetic - */ - public static boolean isSynthetic(MethodTree tree) { - ExecutableElement ee = TreeUtils.elementFromDeclaration(tree); - return isSynthetic(ee); - } - - /** - * Returns true if the tree is of a diamond type. In contrast to the implementation in TreeInfo, - * this version works on Trees. - * - * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree) - */ - public static boolean isDiamondTree(Tree tree) { - switch (tree.getKind()) { - case ANNOTATED_TYPE: - return isDiamondTree(((AnnotatedTypeTree) tree).getUnderlyingType()); - case PARAMETERIZED_TYPE: - return ((ParameterizedTypeTree) tree).getTypeArguments().isEmpty(); - case NEW_CLASS: - return isDiamondTree(((NewClassTree) tree).getIdentifier()); - default: - return false; - } - } - - /** Returns true if the tree represents a {@code String} concatenation operation. */ - public static boolean isStringConcatenation(Tree tree) { - return (tree.getKind() == Tree.Kind.PLUS && TypesUtils.isString(TreeUtils.typeOf(tree))); - } - - /** Returns true if the compound assignment tree is a string concatenation. */ - public static boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) { - return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT - && TypesUtils.isString(TreeUtils.typeOf(tree))); - } - - /** - * Is this method's declared return type "void"? - * - * @param tree a method declaration - * @return true iff method's declared return type is "void" - */ - public static boolean isVoidReturn(MethodTree tree) { - return typeOf(tree.getReturnType()).getKind() == TypeKind.VOID; - } - - /** - * Returns true if the tree is a constant-time expression. - * - *

          A tree is a constant-time expression if it is: - * - *

            - *
          1. a literal tree - *
          2. a reference to a final variable initialized with a compile time constant - *
          3. a String concatenation of two compile time constants - *
          - * - * @param tree the tree to check - * @return true if the tree is a constant-time expression - */ - public static boolean isCompileTimeString(ExpressionTree tree) { - tree = TreeUtils.withoutParens(tree); - if (tree instanceof LiteralTree) { - return true; - } - - if (TreeUtils.isUseOfElement(tree)) { - Element elt = TreeUtils.elementFromUse(tree); - return ElementUtils.isCompileTimeConstant(elt); - } else if (TreeUtils.isStringConcatenation(tree)) { - BinaryTree binOp = (BinaryTree) tree; - return isCompileTimeString(binOp.getLeftOperand()) - && isCompileTimeString(binOp.getRightOperand()); - } else { - return false; - } - } - - /** - * Returns the receiver tree of a field access or a method invocation. - * - * @param expression a field access or a method invocation - * @return the expression's receiver tree, or null if it does not have an explicit receiver - */ - public static @Nullable ExpressionTree getReceiverTree(ExpressionTree expression) { - ExpressionTree receiver; - switch (expression.getKind()) { - case METHOD_INVOCATION: - // Trying to handle receiver calls to trees of the form - // ((m).getArray()) - // returns the type of 'm' in this case - receiver = ((MethodInvocationTree) expression).getMethodSelect(); - - if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { - receiver = ((MemberSelectTree) receiver).getExpression(); - } else { - // It's a method call "m(foo)" without an explicit receiver - return null; - } - break; - case NEW_CLASS: - receiver = ((NewClassTree) expression).getEnclosingExpression(); - break; - case ARRAY_ACCESS: - receiver = ((ArrayAccessTree) expression).getExpression(); - break; - case MEMBER_SELECT: - receiver = ((MemberSelectTree) expression).getExpression(); - // Avoid int.class - if (receiver instanceof PrimitiveTypeTree) { - return null; - } - break; - case IDENTIFIER: - // It's a field access on implicit this or a local variable/parameter. - return null; - default: - return null; - } - if (receiver == null) { - return null; - } - - return TreeUtils.withoutParens(receiver); - } - - // TODO: What about anonymous classes? - // Adding Tree.Kind.NEW_CLASS here doesn't work, because then a - // tree gets cast to ClassTree when it is actually a NewClassTree, - // for example in enclosingClass above. - /** The kinds that represent classes. */ - private static final Set classTreeKinds; - - static { - classTreeKinds = EnumSet.noneOf(Tree.Kind.class); - for (Tree.Kind kind : Tree.Kind.values()) { - if (kind.asInterface() == ClassTree.class) { - classTreeKinds.add(kind); - } - } - } - - /** Kinds that represent a class or method tree. */ - private static final Set classAndMethodTreeKinds; - - static { - classAndMethodTreeKinds = EnumSet.copyOf(classTreeKinds()); - classAndMethodTreeKinds.add(Tree.Kind.METHOD); - } - - /** - * Returns the set of kinds that represent classes and methods. - * - * @return the set of kinds that represent classes and methods - */ - public static Set classAndMethodTreeKinds() { - return classAndMethodTreeKinds; - } - - /** - * Return the set of kinds that represent classes. - * - * @return the set of kinds that represent classes - */ - public static Set classTreeKinds() { - return classTreeKinds; - } - - /** - * Is the given tree kind a class, i.e. a class, enum, interface, or annotation type. - * - * @param tree the tree to test - * @return true, iff the given kind is a class kind - */ - public static boolean isClassTree(Tree tree) { - return classTreeKinds().contains(tree.getKind()); - } - - /** - * The kinds that represent declarations that might have {@code @SuppressWarnings} written on - * them: classes, methods, and variables. - */ - private static final Set declarationTreeKinds; - - static { - declarationTreeKinds = EnumSet.noneOf(Tree.Kind.class); - declarationTreeKinds.addAll(classTreeKinds); - declarationTreeKinds.add(Tree.Kind.METHOD); - declarationTreeKinds.add(Tree.Kind.VARIABLE); - } - - /** - * Return the set of kinds that represent declarations: classes, methods, and variables. - * - * @return the set of kinds that represent declarations - */ - public static Set declarationTreeKinds() { - return declarationTreeKinds; - } - - /** - * Returns true if the given tree is a declaration. - * - * @param tree the tree to test - * @return true if the given tree is a declaration - */ - public static boolean isDeclarationTree(Tree tree) { - return declarationTreeKinds.contains(tree.getKind()); - } - - /** The kinds that represent types. */ - private static final Set typeTreeKinds = - EnumSet.of( - Tree.Kind.PRIMITIVE_TYPE, - Tree.Kind.PARAMETERIZED_TYPE, - Tree.Kind.TYPE_PARAMETER, - Tree.Kind.ARRAY_TYPE, - Tree.Kind.UNBOUNDED_WILDCARD, - Tree.Kind.EXTENDS_WILDCARD, - Tree.Kind.SUPER_WILDCARD, - Tree.Kind.ANNOTATED_TYPE); - - /** - * Return the set of kinds that represent types. - * - * @return the set of kinds that represent types - */ - public static Set typeTreeKinds() { - return typeTreeKinds; - } - - /** - * Is the given tree a type instantiation? - * - *

          TODO: this is an under-approximation: e.g. an identifier could be either a type use or an - * expression. How can we distinguish. - * - * @param tree the tree to test - * @return true, iff the given tree is a type - */ - public static boolean isTypeTree(Tree tree) { - return typeTreeKinds().contains(tree.getKind()); - } - - /** - * Returns true if the given element is an invocation of the method, or of any method that - * overrides that one. - */ - public static boolean isMethodInvocation( - Tree tree, ExecutableElement method, ProcessingEnvironment env) { - if (!(tree instanceof MethodInvocationTree)) { - return false; - } - MethodInvocationTree methInvok = (MethodInvocationTree) tree; - ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); - if (invoked == null) { - return false; - } - return ElementUtils.isMethod(invoked, method, env); - } - - /** - * Returns true if the argument is an invocation of one of the given methods, or of any method - * that overrides them. - * - * @param tree a tree that might be a method invocation - * @param methods the methods to check for - * @param processingEnv the processing environment - * @return true if the argument is an invocation of one of the given methods, or of any method - * that overrides them - */ - public static boolean isMethodInvocation( - Tree tree, List methods, ProcessingEnvironment processingEnv) { - if (!(tree instanceof MethodInvocationTree)) { - return false; - } - MethodInvocationTree methInvok = (MethodInvocationTree) tree; - ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); - if (invoked == null) { - return false; - } - for (ExecutableElement method : methods) { - if (ElementUtils.isMethod(invoked, method, processingEnv)) { - return true; - } - } - return false; - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one - * matching method. If more than one method takes the same number of formal parameters, then use - * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. - * - * @param type the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - Class type, String methodName, int params, ProcessingEnvironment env) { - String typeName = type.getCanonicalName(); - if (typeName == null) { - throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); - } - return getMethod(typeName, methodName, params, env); - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one - * matching method. If more than one method takes the same number of formal parameters, then use - * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - @FullyQualifiedName String typeName, - String methodName, - int params, - ProcessingEnvironment env) { - List methods = getMethods(typeName, methodName, params, env); - if (methods.size() == 1) { - return methods.get(0); - } - throw new BugInCF( - "TreeUtils.getMethod(%s, %s, %d): expected 1 match, found %d: %s", - typeName, methodName, params, methods.size(), methods); - } - - /** - * Returns the ExecutableElement for a method declaration. Returns null there is no matching - * method. Errs if there is more than one matching method. If more than one method takes the - * same number of formal parameters, then use {@link #getMethod(String, String, - * ProcessingEnvironment, String...)}. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElement for the specified method, or null - */ - public static @Nullable ExecutableElement getMethodOrNull( - @FullyQualifiedName String typeName, - String methodName, - int params, - ProcessingEnvironment env) { - List methods = getMethods(typeName, methodName, params, env); - if (methods.size() == 0) { - return null; - } else if (methods.size() == 1) { - return methods.get(0); - } else { - throw new BugInCF( - "TreeUtils.getMethod(%s, %s, %d): expected 0 or 1 match, found %d", - typeName, methodName, params, methods.size()); - } - } - - /** - * Returns all ExecutableElements for method declarations of methodName, in class typeName, with - * params formal parameters. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElements for all matching methods - */ - public static List getMethods( - @FullyQualifiedName String typeName, - String methodName, - int params, - ProcessingEnvironment env) { - List methods = new ArrayList<>(1); - TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); - if (typeElt == null) { - throw new UserError("Configuration problem! Could not load type: " + typeName); - } - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (exec.getSimpleName().contentEquals(methodName) - && exec.getParameters().size() == params) { - methods.add(exec); - } - } - return methods; - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. - * - * @param type the class that contains the method - * @param methodName the name of the method - * @param env the processing environment - * @param paramTypes the method's formal parameter types - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - Class type, String methodName, ProcessingEnvironment env, String... paramTypes) { - String typeName = type.getCanonicalName(); - if (typeName == null) { - throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); - } - return getMethod(typeName, methodName, env, paramTypes); - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param env the processing environment - * @param paramTypes the method's formal parameter types - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - @FullyQualifiedName String typeName, - String methodName, - ProcessingEnvironment env, - String... paramTypes) { - TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (exec.getSimpleName().contentEquals(methodName) - && exec.getParameters().size() == paramTypes.length) { - boolean typesMatch = true; - List params = exec.getParameters(); - for (int i = 0; i < paramTypes.length; i++) { - VariableElement ve = params.get(i); - TypeMirror tm = TypeAnnotationUtils.unannotatedType(ve.asType()); - if (!tm.toString().equals(paramTypes[i])) { - typesMatch = false; - break; - } - } - if (typesMatch) { - return exec; - } - } - } - - // Didn't find an answer. Compose an error message. - List candidates = new ArrayList<>(); - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (exec.getSimpleName().contentEquals(methodName)) { - candidates.add(executableElementToString(exec)); - } - } - if (candidates.isEmpty()) { - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - candidates.add(executableElementToString(exec)); - } - } - throw new BugInCF( - "TreeUtils.getMethod: found no match for %s.%s(%s); candidates: %s", - typeName, methodName, Arrays.toString(paramTypes), candidates); - } - - /** - * Formats the ExecutableElement in the way that getMethod() expects it. - * - * @param exec an executable element - * @return the ExecutableElement, formatted in the way that getMethod() expects it - */ - private static String executableElementToString(ExecutableElement exec) { - StringJoiner result = new StringJoiner(", ", exec.getSimpleName() + "(", ")"); - for (VariableElement param : exec.getParameters()) { - result.add(TypeAnnotationUtils.unannotatedType(param.asType()).toString()); - } - return result.toString(); - } - - /** - * Determine whether the given expression is either "this" or an outer "C.this". - * - *

          TODO: Should this also handle "super"? - */ - public static boolean isExplicitThisDereference(ExpressionTree tree) { - if (tree.getKind() == Tree.Kind.IDENTIFIER - && ((IdentifierTree) tree).getName().contentEquals("this")) { - // Explicit this reference "this" - return true; - } - - if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; - } - - MemberSelectTree memSelTree = (MemberSelectTree) tree; - if (memSelTree.getIdentifier().contentEquals("this")) { - // Outer this reference "C.this" - return true; - } - return false; - } - - /** - * Determine whether {@code tree} is a class literal, such as - * - *

          -     *   Object . class
          -     * 
          - * - * @return true iff if tree is a class literal - */ - public static boolean isClassLiteral(Tree tree) { - if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; - } - return "class".equals(((MemberSelectTree) tree).getIdentifier().toString()); - } - - /** - * Determine whether {@code tree} is a field access expression, such as - * - *
          -     *   f
          -     *   obj . f
          -     * 
          - * - * This method currently also returns true for class literals and qualified this. - * - * @param tree a tree that might be a field access - * @return true iff if tree is a field access expression (implicit or explicit) - */ - public static boolean isFieldAccess(Tree tree) { - return asFieldAccess(tree) != null; - } - - /** - * Return the field that {@code tree} is a field access expression for, or null. - * - *
          -     *   f
          -     *   obj . f
          -     * 
          - * - * This method currently also returns a non-null value for class literals and qualified this. - * - * @param tree a tree that might be a field access - * @return the element if tree is a field access expression (implicit or explicit); null - * otherwise - */ - // TODO: fix value for class literals and qualified this, which are not field accesses. - public static @Nullable VariableElement asFieldAccess(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - // explicit member access (or a class literal or a qualified this) - MemberSelectTree memberSelect = (MemberSelectTree) tree; - assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(memberSelect); - if (el.getKind().isField()) { - return (VariableElement) el; - } - } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { - // implicit field access - IdentifierTree ident = (IdentifierTree) tree; - assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(ident); - if (el.getKind().isField() - && !ident.getName().contentEquals("this") - && !ident.getName().contentEquals("super")) { - return (VariableElement) el; - } - } - return null; - } - - /** - * Compute the name of the field that the field access {@code tree} accesses. Requires {@code - * tree} to be a field access, as determined by {@code isFieldAccess} (which currently also - * returns true for class literals and qualified this). - * - * @param tree a field access tree - * @return the name of the field accessed by {@code tree} - */ - public static String getFieldName(Tree tree) { - assert isFieldAccess(tree); - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree mtree = (MemberSelectTree) tree; - return mtree.getIdentifier().toString(); - } else { - IdentifierTree itree = (IdentifierTree) tree; - return itree.getName().toString(); - } - } - - /** - * Determine whether {@code tree} refers to a method element, such as. - * - *
          -     *   m(...)
          -     *   obj . m(...)
          -     * 
          - * - * @return true iff if tree is a method access expression (implicit or explicit) - */ - public static boolean isMethodAccess(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - // explicit method access - MemberSelectTree memberSelect = (MemberSelectTree) tree; - assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(memberSelect); - return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; - } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { - // implicit method access - IdentifierTree ident = (IdentifierTree) tree; - // The field "super" and "this" are also legal methods - if (ident.getName().contentEquals("super") || ident.getName().contentEquals("this")) { - return true; - } - assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(ident); - return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; - } - return false; - } - - /** - * Compute the name of the method that the method access {@code tree} accesses. Requires {@code - * tree} to be a method access, as determined by {@code isMethodAccess}. - * - * @param tree a method access tree - * @return the name of the method accessed by {@code tree} - */ - public static String getMethodName(Tree tree) { - assert isMethodAccess(tree); - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree mtree = (MemberSelectTree) tree; - return mtree.getIdentifier().toString(); - } else { - IdentifierTree itree = (IdentifierTree) tree; - return itree.getName().toString(); - } - } - - /** - * Return {@code true} if and only if {@code tree} can have a type annotation. - * - * @return {@code true} if and only if {@code tree} can have a type annotation - */ - // TODO: is this implementation precise enough? E.g. does a .class literal work correctly? - public static boolean canHaveTypeAnnotation(Tree tree) { - return ((JCTree) tree).type != null; - } - - /** - * Returns true if and only if the given {@code tree} represents a field access of the given - * {@link VariableElement}. - */ - public static boolean isSpecificFieldAccess(Tree tree, VariableElement var) { - if (tree instanceof MemberSelectTree) { - MemberSelectTree memSel = (MemberSelectTree) tree; - assert isUseOfElement(memSel) : "@AssumeAssertion(nullness): tree kind"; - Element field = TreeUtils.elementFromUse(memSel); - return field.equals(var); - } else if (tree instanceof IdentifierTree) { - IdentifierTree idTree = (IdentifierTree) tree; - assert isUseOfElement(idTree) : "@AssumeAssertion(nullness): tree kind"; - Element field = TreeUtils.elementFromUse(idTree); - return field.equals(var); - } else { - return false; - } - } - - /** - * Returns the VariableElement for a field declaration. - * - * @param typeName the class where the field is declared - * @param fieldName the name of the field - * @param env the processing environment - * @return the VariableElement for typeName.fieldName - */ - public static VariableElement getField( - @FullyQualifiedName String typeName, String fieldName, ProcessingEnvironment env) { - TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); - for (VariableElement var : ElementFilter.fieldsIn(mapElt.getEnclosedElements())) { - if (var.getSimpleName().contentEquals(fieldName)) { - return var; - } - } - throw new BugInCF("TreeUtils.getField: shouldn't be here"); - } - - /** - * Determine whether the given tree represents an ExpressionTree. - * - * @param tree the Tree to test - * @return whether the tree is an ExpressionTree - */ - public static boolean isExpressionTree(Tree tree) { - return tree instanceof ExpressionTree; - } - - /** - * Returns true if this is a super call to the {@link Enum} constructor. - * - * @param tree the method invocation to check - * @return true if this is a super call to the {@link Enum} constructor - */ - public static boolean isEnumSuperCall(MethodInvocationTree tree) { - ExecutableElement ex = TreeUtils.elementFromUse(tree); - assert ex != null : "@AssumeAssertion(nullness): tree kind"; - Name name = ElementUtils.getQualifiedClassName(ex); - assert name != null : "@AssumeAssertion(nullness): assumption"; - boolean correctClass = "java.lang.Enum".contentEquals(name); - boolean correctMethod = "".contentEquals(ex.getSimpleName()); - return correctClass && correctMethod; - } - - /** - * Determine whether the given tree represents a declaration of a type (including type - * parameters). - * - * @param tree the Tree to test - * @return true if the tree is a type declaration - */ - public static boolean isTypeDeclaration(Tree tree) { - return isClassTree(tree) || tree.getKind() == Tree.Kind.TYPE_PARAMETER; - } - - /** - * Returns true if tree is an access of array length. - * - * @param tree tree to check - * @return true if tree is an access of array length - */ - public static boolean isArrayLengthAccess(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT - && isFieldAccess(tree) - && getFieldName(tree).equals("length")) { - ExpressionTree expressionTree = ((MemberSelectTree) tree).getExpression(); - if (TreeUtils.typeOf(expressionTree).getKind() == TypeKind.ARRAY) { - return true; - } - } - return false; - } - - /** - * Returns true if the given {@link MethodTree} is an anonymous constructor (the constructor for - * an anonymous class). - * - * @param method a method tree that may be an anonymous constructor - * @return true if the given path points to an anonymous constructor, false if it does not - */ - public static boolean isAnonymousConstructor(MethodTree method) { - Element e = elementFromTree(method); - if (e == null || e.getKind() != ElementKind.CONSTRUCTOR) { - return false; - } - TypeElement typeElement = (TypeElement) e.getEnclosingElement(); - return typeElement.getNestingKind() == NestingKind.ANONYMOUS; - } - - /** - * Returns true if the passed constructor is anonymous and has an explicit enclosing expression. - * - * @param con an ExecutableElement of a constructor declaration - * @param tree the NewClassTree of a constructor declaration - * @return true if there is an extra enclosing expression - */ - public static boolean isAnonymousConstructorWithExplicitEnclosingExpression( - ExecutableElement con, NewClassTree tree) { - - return (tree.getEnclosingExpression() != null) - && con.getKind() == ElementKind.CONSTRUCTOR - && ((TypeElement) con.getEnclosingElement()).getNestingKind() - == NestingKind.ANONYMOUS; - } - - /** - * Returns true if the given {@link MethodTree} is a compact canonical constructor (the - * constructor for a record where the parameters are implicitly declared and implicitly assigned - * to the record's fields). This may be an explicitly declared compact canonical constructor or - * an implicitly generated one. - * - * @param method a method tree that may be a compact canonical constructor - * @return true if the given method is a compact canonical constructor - */ - public static boolean isCompactCanonicalRecordConstructor(MethodTree method) { - Symbol s = (Symbol) elementFromTree(method); - if (s == null) { - throw new BugInCF( - "TreeUtils.isCompactCanonicalRecordConstructor: null symbol for method tree: " - + method); - } - return (s.flags() & Flags_RECORD) != 0; - } - - /** - * Returns true if the given {@link Tree} is part of a record that has been automatically - * generated by the compiler. This can be a field that is derived from the record's header field - * list, or an automatically generated canonical constructor. - * - * @param member the {@link Tree} for a member of a record - * @return true if the given path is generated by the compiler - */ - public static boolean isAutoGeneratedRecordMember(Tree member) { - Element e = elementFromTree(member); - return e != null && ElementUtils.isAutoGeneratedRecordMember(e); - } - - /** - * Converts the given AnnotationTrees to AnnotationMirrors. - * - * @param annoTrees list of annotation trees to convert to annotation mirrors - * @return list of annotation mirrors that represent the given annotation trees - */ - public static List annotationsFromTypeAnnotationTrees( - List annoTrees) { - return CollectionsPlume.mapList(TreeUtils::annotationFromAnnotationTree, annoTrees); - } - - /** - * Converts the given AnnotationTree to an AnnotationMirror. - * - * @param tree annotation tree to convert to an annotation mirror - * @return annotation mirror that represent the given annotation tree - */ - public static AnnotationMirror annotationFromAnnotationTree(AnnotationTree tree) { - return ((JCAnnotation) tree).attribute; - } - - /** - * Converts the given AnnotatedTypeTree to a list of AnnotationMirrors. - * - * @param tree annotated type tree to convert - * @return list of AnnotationMirrors from the tree - */ - public static List annotationsFromTree(AnnotatedTypeTree tree) { - return annotationsFromTypeAnnotationTrees(((JCAnnotatedType) tree).annotations); - } - - /** - * Converts the given TypeParameterTree to a list of AnnotationMirrors. - * - * @param tree type parameter tree to convert - * @return list of AnnotationMirrors from the tree - */ - public static List annotationsFromTree(TypeParameterTree tree) { - return annotationsFromTypeAnnotationTrees(((JCTypeParameter) tree).annotations); - } - - /** - * Converts the given NewArrayTree to a list of AnnotationMirrors. - * - * @param tree new array tree - * @return list of AnnotationMirrors from the tree - */ - public static List annotationsFromArrayCreation( - NewArrayTree tree, int level) { - - assert tree instanceof JCNewArray; - JCNewArray newArray = ((JCNewArray) tree); - - if (level == -1) { - return annotationsFromTypeAnnotationTrees(newArray.annotations); - } - - if (newArray.dimAnnotations.length() > 0 - && (level >= 0) - && (level < newArray.dimAnnotations.size())) { - return annotationsFromTypeAnnotationTrees(newArray.dimAnnotations.get(level)); - } - - return Collections.emptyList(); - } - - /** - * Returns true if the tree is the declaration or use of a local variable. - * - * @param tree the tree to check - * @return true if the tree is the declaration or use of a local variable - */ - public static boolean isLocalVariable(Tree tree) { - if (tree.getKind() == Tree.Kind.VARIABLE) { - VariableElement varElt = elementFromDeclaration((VariableTree) tree); - return ElementUtils.isLocalVariable(varElt); - } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { - ExpressionTree etree = (ExpressionTree) tree; - assert isUseOfElement(etree) : "@AssumeAssertion(nullness): tree kind"; - return ElementUtils.isLocalVariable(elementFromUse(etree)); - } - return false; - } - - /** - * Returns the type as a TypeMirror of {@code tree}. To obtain {@code tree}'s - * AnnotatedTypeMirror, call {@code AnnotatedTypeFactory.getAnnotatedType()}. - * - * @return the type as a TypeMirror of {@code tree} - */ - public static TypeMirror typeOf(Tree tree) { - return ((JCTree) tree).type; - } - - /** - * Determines the type for a method invocation at its call site, which has all type variables - * substituted with the type arguments at the call site. - * - *

          {@link javax.lang.model.type.TypeVariable} in the returned type should be compared using - * {@link TypesUtils#areSame(TypeVariable, TypeVariable)} because the {@code TypeVariable} will - * be freshly created by this method and will not be the same using {@link - * Object#equals(Object)} or {@link javax.lang.model.util.Types#isSameType(TypeMirror, - * TypeMirror)}. - * - * @param tree the method invocation - * @return the {@link ExecutableType} corresponding to the method invocation at its call site - */ - @Pure - public static ExecutableType typeFromUse(MethodInvocationTree tree) { - TypeMirror type = TreeUtils.typeOf(tree.getMethodSelect()); - if (!(type instanceof ExecutableType)) { - throw new BugInCF( - "TreeUtils.typeFromUse(MethodInvocationTree): type of method select in method" - + " invocation should be ExecutableType. Found: %s", - type); - } - ExecutableType executableType = (ExecutableType) type; - ExecutableElement element = elementFromUse(tree); - if (executableType.getParameterTypes().size() != element.getParameters().size()) { - // Sometimes when the method type is viewpoint-adapted, the vararg parameter disappears, - // just return the declared type. - // For example, - // static void call(MethodHandle methodHandle) throws Throwable { - // methodHandle.invoke(); - // } - return (ExecutableType) element.asType(); - } - return executableType; - } - - /** - * Determines the type for a constructor at its call site given an invocation via {@code new}, - * which has all type variables substituted with the type arguments at the call site. - * - * @param tree the constructor invocation - * @return the {@link ExecutableType} corresponding to the constructor call (i.e., the given - * {@code tree}) at its call site - */ - @Pure - public static ExecutableType typeFromUse(NewClassTree tree) { - if (!(tree instanceof JCTree.JCNewClass)) { - throw new BugInCF("TreeUtils.typeFromUse(NewClassTree): not a javac internal tree"); - } - - JCNewClass newClassTree = (JCNewClass) tree; - TypeMirror type = newClassTree.constructorType; - - if (!(type instanceof ExecutableType)) { - throw new BugInCF( - "TreeUtils.typeFromUse(NewClassTree): type of constructor in new class tree" - + " should be ExecutableType. Found: %s", - type); - } - return (ExecutableType) type; - } - - /** - * The type of the lambda or method reference tree is a functional interface type. This method - * returns the single abstract method declared by that functional interface. (The type of this - * method is referred to as the function type.) - * - * @param tree lambda or member reference tree - * @param env the processing environment - * @return the single abstract method declared by the type of the tree - */ - public static ExecutableElement findFunction(Tree tree, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - Types javacTypes = Types.instance(ctx); - return (ExecutableElement) - javacTypes.findDescriptorSymbol(((Type) typeOf(tree)).asElement()); - } - - /** - * Returns true if {@code tree} is an implicitly typed lambda. - * - *

          A lambda expression whose formal type parameters have inferred types is an implicitly - * typed lambda. (See JLS 15.27.1) - * - * @param tree any kind of tree - * @return true iff {@code tree} is an implicitly typed lambda - */ - public static boolean isImplicitlyTypedLambda(Tree tree) { - return tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION - && ((JCLambda) tree).paramKind == ParameterKind.IMPLICIT; - } - - /** - * This is a duplication of {@code - * com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind}, which is not part of the - * supported javac API. - */ - public enum MemberReferenceKind { - /** super # instMethod */ - SUPER(ReferenceMode.INVOKE, false), - /** Type # instMethod */ - UNBOUND(ReferenceMode.INVOKE, true), - /** Type # staticMethod */ - STATIC(ReferenceMode.INVOKE, false), - /** Expr # instMethod */ - BOUND(ReferenceMode.INVOKE, false), - /** Inner # new */ - IMPLICIT_INNER(ReferenceMode.NEW, false), - /** Toplevel # new */ - TOPLEVEL(ReferenceMode.NEW, false), - /** ArrayType # new */ - ARRAY_CTOR(ReferenceMode.NEW, false); - - /** Whether this kind is a method reference or a constructor reference. */ - final ReferenceMode mode; - - /** Whether this kind is unbound. */ - final boolean unbound; - - /** - * Creates a MemberReferenceKind. - * - * @param mode whether this kind is a method reference or a constructor reference - * @param unbound whether the kind is not bound - */ - MemberReferenceKind(ReferenceMode mode, boolean unbound) { - this.mode = mode; - this.unbound = unbound; - } - - /** - * Whether this kind is unbound. - * - * @return Whether this kind is unbound - */ - public boolean isUnbound() { - return unbound; - } - - /** - * Returns whether this kind is a constructor reference. - * - * @return whether this kind is a constructor reference - */ - public boolean isConstructorReference() { - return mode == ReferenceMode.NEW; - } - - /** - * Returns the kind of member reference {@code tree} is. - * - * @param tree a member reference tree - * @return the kind of member reference {@code tree} is - */ - public static MemberReferenceKind getMemberReferenceKind(MemberReferenceTree tree) { - JCMemberReference memberTree = (JCMemberReference) tree; - switch (memberTree.kind) { - case SUPER: - return SUPER; - case UNBOUND: - return UNBOUND; - case STATIC: - return STATIC; - case BOUND: - return BOUND; - case IMPLICIT_INNER: - return IMPLICIT_INNER; - case TOPLEVEL: - return TOPLEVEL; - case ARRAY_CTOR: - return ARRAY_CTOR; - default: - throw new BugInCF("Unexpected ReferenceKind: %s", memberTree.kind); - } - } - } - - /** - * Determine whether an expression {@link ExpressionTree} has the constant value true, according - * to the compiler logic. - * - * @param tree the expression to be checked - * @return true if {@code tree} has the constant value true - */ - public static boolean isExprConstTrue(ExpressionTree tree) { - assert tree instanceof JCExpression; - if (((JCExpression) tree).type.isTrue()) { - return true; - } - tree = TreeUtils.withoutParens(tree); - if (tree instanceof JCTree.JCBinary) { - JCBinary binTree = (JCBinary) tree; - JCExpression ltree = binTree.lhs; - JCExpression rtree = binTree.rhs; - switch (binTree.getTag()) { - case AND: - return isExprConstTrue(ltree) && isExprConstTrue(rtree); - case OR: - return isExprConstTrue(ltree) || isExprConstTrue(rtree); - default: - break; - } - } - return false; - } - - /** - * Return toString(), but without line separators. - * - * @param tree a tree - * @return a one-line string representation of the tree - */ - public static String toStringOneLine(Tree tree) { - return tree.toString().trim().replaceAll("\\s+", " "); - } - - /** - * Return either {@link #toStringOneLine} if it is no more than {@code length} characters, or - * {@link #toStringOneLine} quoted and truncated. - * - * @param tree a tree - * @param length the maximum length for the result; must be at least 6 - * @return a one-line string representation of the tree that is no longer than {@code length} - * characters long - */ - public static String toStringTruncated(Tree tree, int length) { - if (length < 6) { - throw new BugInCF("TreeUtils.toStringTruncated: bad length " + length); - } - String result = toStringOneLine(tree); - if (result.length() > length) { - // The quoting increases the likelihood that all delimiters are balanced in the result. - // That makes it easier to manipulate the result (such as skipping over it) in an - // editor. The quoting also makes clear that the value is truncated. - result = "\"" + result.substring(0, length - 5) + "...\""; - } - return result; - } - - /** - * Given a javac ExpressionTree representing a fully qualified name such as "java.lang.Object", - * creates a String containing the name. - * - * @param nameExpr an ExpressionTree representing a fully qualified name - * @return a String representation of the fully qualified name - */ - public static String nameExpressionToString(ExpressionTree nameExpr) { - TreeVisitor visitor = - new SimpleTreeVisitor() { - @Override - public String visitIdentifier(IdentifierTree tree, Void p) { - return tree.toString(); - } - - @Override - public String visitMemberSelect(MemberSelectTree tree, Void p) { - return tree.getExpression().accept(this, null) - + "." - + tree.getIdentifier().toString(); - } - }; - return nameExpr.accept(visitor, null); - } - - /** - * Returns true if the binary operator may do a widening primitive conversion. See JLS chapter 5. - * - * @param tree a binary tree - * @return true if the tree's operator does numeric promotion on its arguments - */ - public static boolean isWideningBinary(BinaryTree tree) { - switch (tree.getKind()) { - case LEFT_SHIFT: - case LEFT_SHIFT_ASSIGNMENT: - case RIGHT_SHIFT: - case RIGHT_SHIFT_ASSIGNMENT: - case UNSIGNED_RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - // Strictly speaking, these operators do unary promotion on each argument - // separately. - return true; - - case MULTIPLY: - case MULTIPLY_ASSIGNMENT: - case DIVIDE: - case DIVIDE_ASSIGNMENT: - case REMAINDER: - case REMAINDER_ASSIGNMENT: - case PLUS: - case PLUS_ASSIGNMENT: - case MINUS: - case MINUS_ASSIGNMENT: - - case LESS_THAN: - case LESS_THAN_EQUAL: - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case EQUAL_TO: - case NOT_EQUAL_TO: - - case AND: - case XOR: - case OR: - // These operators do binary promotion on the two arguments together. - return true; - - // TODO: CONDITIONAL_EXPRESSION (?:) sometimes does numeric promotion. - - default: - return false; - } - } - - /** - * Returns the annotations explicitly written on the given type. - * - * @param annoTrees annotations written before a variable/method declaration; null if this type - * is not from such a location. This might contain type annotations that the Java parser - * attached to the declaration rather than to the type. - * @param typeTree the type whose annotations to return - * @return the annotations explicitly written on the given type - */ - public static List getExplicitAnnotationTrees( - @Nullable List annoTrees, Tree typeTree) { - while (true) { - switch (typeTree.getKind()) { - case IDENTIFIER: - case PRIMITIVE_TYPE: - if (annoTrees == null) { - return Collections.emptyList(); - } - return annoTrees; - case ANNOTATED_TYPE: - return ((AnnotatedTypeTree) typeTree).getAnnotations(); - case ARRAY_TYPE: - case TYPE_PARAMETER: - case UNBOUNDED_WILDCARD: - case EXTENDS_WILDCARD: - case SUPER_WILDCARD: - return Collections.emptyList(); - case MEMBER_SELECT: - if (annoTrees == null) { - return Collections.emptyList(); - } - typeTree = ((MemberSelectTree) typeTree).getExpression(); - break; - case PARAMETERIZED_TYPE: - typeTree = ((ParameterizedTypeTree) typeTree).getType(); - break; - case UNION_TYPE: - List alternatives = - ((UnionTypeTree) typeTree).getTypeAlternatives(); - List result = new ArrayList<>(alternatives.size()); - for (Tree alternative : alternatives) { - result.addAll(getExplicitAnnotationTrees(null, alternative)); - } - return result; - default: - throw new BugInCF( - "TreeUtils.getExplicitAnnotationTrees: what typeTree? %s %s %s", - typeTree.getKind(), typeTree.getClass(), typeTree); - } - } - } - - /** - * Return a tree for the default value of the given type. The default value is 0, false, or - * null. - * - * @param typeMirror a type - * @param processingEnv the processing environment - * @return a tree for {@code type}'s default value - */ - public static LiteralTree getDefaultValueTree( - TypeMirror typeMirror, ProcessingEnvironment processingEnv) { - typeMirror = TypeAnnotationUtils.unannotatedType(typeMirror); - switch (typeMirror.getKind()) { - case BYTE: - case SHORT: - case INT: - // Byte should be (byte) 0, but this probably doesn't matter so just use int 0; - // Short should be (short) 0, but this probably doesn't matter so just use int 0; - return TreeUtils.createLiteral(TypeTag.INT, 0, typeMirror, processingEnv); - case CHAR: - // Value of a char literal needs to be stored as an integer because - // LiteralTree#getValue converts it from an integer to a char before being - // returned. - return TreeUtils.createLiteral( - TypeTag.CHAR, (int) '\u0000', typeMirror, processingEnv); - case LONG: - return TreeUtils.createLiteral(TypeTag.LONG, 0L, typeMirror, processingEnv); - case FLOAT: - return TreeUtils.createLiteral(TypeTag.FLOAT, 0.0f, typeMirror, processingEnv); - case DOUBLE: - return TreeUtils.createLiteral(TypeTag.DOUBLE, 0.0d, typeMirror, processingEnv); - case BOOLEAN: - // Value of a boolean literal needs to be stored as an integer because - // LiteralTree#getValue converts it from an integer to a boolean before being - // returned. - return TreeUtils.createLiteral(TypeTag.BOOLEAN, 0, typeMirror, processingEnv); - default: - return TreeUtils.createLiteral( - TypeTag.BOT, - null, - processingEnv.getTypeUtils().getNullType(), - processingEnv); - } - } - - /** - * Creates a LiteralTree for the given value. - * - * @param typeTag the literal's type tag - * @param value a wrapped primitive, null, or a String - * @param typeMirror the typeMirror for the literal - * @param processingEnv the processing environment - * @return a LiteralTree for the given type tag and value - */ - public static LiteralTree createLiteral( - TypeTag typeTag, - @Nullable Object value, - TypeMirror typeMirror, - ProcessingEnvironment processingEnv) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - TreeMaker maker = TreeMaker.instance(context); - LiteralTree result = maker.Literal(typeTag, value); - ((JCLiteral) result).type = (Type) typeMirror; - return result; - } - - /** - * Returns true if the given tree evaluates to {@code null}. - * - * @param t a tree - * @return true if the given tree evaluates to {@code null} - */ - public static boolean isNullExpression(Tree t) { - while (true) { - switch (t.getKind()) { - case PARENTHESIZED: - t = ((ParenthesizedTree) t).getExpression(); - break; - case TYPE_CAST: - t = ((TypeCastTree) t).getExpression(); - break; - case NULL_LITERAL: - return true; - default: - return false; - } - } - } - - /** - * Returns true if two expressions originating from the same scope are identical, i.e. they are - * syntactically represented in the same way (modulo parentheses) and represent the same value. - * - *

          If the expression includes one or more method calls, assumes the method calls are - * deterministic. - * - * @param expr1 the first expression to compare - * @param expr2 the second expression to compare; expr2 must originate from the same scope as - * expr1 - * @return true if the expressions expr1 and expr2 are syntactically identical - */ - public static boolean sameTree(ExpressionTree expr1, ExpressionTree expr2) { - expr1 = TreeUtils.withoutParens(expr1); - expr2 = TreeUtils.withoutParens(expr2); - // Converting to a string in order to compare is somewhat inefficient, and it doesn't handle - // internal parentheses. We could create a visitor instead. - return expr1.getKind() == expr2.getKind() && expr1.toString().equals(expr2.toString()); - } - - /** - * Returns true if this is the default case for a switch statement or expression. (Also, returns - * true if {@code caseTree} is {@code case null, default:}.) - * - * @param caseTree a case tree - * @return true if {@code caseTree} is the default case for a switch statement or expression - * @deprecated use {@link CaseUtils#isDefaultCaseTree(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static boolean isDefaultCaseTree(CaseTree caseTree) { - return CaseUtils.isDefaultCaseTree(caseTree); - } - - /** - * Returns true if this is a case rule (as opposed to a case statement). - * - * @param caseTree a case tree - * @return true if {@code caseTree} is a case rule - * @deprecated use {@link CaseUtils#isCaseRule(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static boolean isCaseRule(CaseTree caseTree) { - return CaseUtils.isCaseRule(caseTree); - } - - /** - * Get the list of expressions from a case expression. For the default case, this is empty. - * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can be - * multiple expressions per case. - * - * @param caseTree the case expression to get the expressions from - * @return the list of expressions in the case - * @deprecated use {@link CaseUtils#getExpressions(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static List caseTreeGetExpressions(CaseTree caseTree) { - return CaseUtils.getExpressions(caseTree); - } - - /** - * Returns the body of the case statement if it is of the form {@code case -> - * }. This method should only be called if {@link CaseTree#getStatements()} returns - * null. - * - * @param caseTree the case expression to get the body from - * @return the body of the case tree - * @deprecated use {@link CaseUtils#getBody(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static @Nullable Tree caseTreeGetBody(CaseTree caseTree) { - return CaseUtils.getBody(caseTree); - } - - /** - * Returns true if {@code tree} is a {@code BindingPatternTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code BindingPatternTree} - */ - public static boolean isBindingPatternTree(Tree tree) { - return tree.getKind().name().contentEquals("BINDING_PATTERN"); - } - - /** - * Returns the binding variable of {@code bindingPatternTree}. - * - * @param bindingPatternTree the BindingPatternTree whose binding variable is returned - * @return the binding variable of {@code bindingPatternTree} - * @deprecated use {@link BindingPatternUtils#getVariable(Tree)} - */ - @Deprecated // 2023-09-26 - public static VariableTree bindingPatternTreeGetVariable(Tree bindingPatternTree) { - return BindingPatternUtils.getVariable(bindingPatternTree); - } - - /** - * Returns true if {@code tree} is a {@code DeconstructionPatternTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code DeconstructionPatternTree} - */ - public static boolean isDeconstructionPatternTree(Tree tree) { - return tree.getKind().name().contentEquals("DECONSTRUCTION_PATTERN"); - } - - /** - * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does not - * have a pattern, including if the JDK version does not support instance-of patterns. - * - * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned - * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist - * @deprecated use {@link InstanceOfUtils#getPattern(InstanceOfTree)} - */ - @Deprecated // 2023-09-26 - public static @Nullable Tree instanceOfTreeGetPattern(InstanceOfTree instanceOfTree) { - return InstanceOfUtils.getPattern(instanceOfTree); - } - - /** - * Returns the selector expression of {@code switchExpressionTree}. For example - * - *

          -     *   switch ( expression ) { ... }
          -     * 
          - * - * @param switchExpressionTree the switch expression whose selector expression is returned - * @return the selector expression of {@code switchExpressionTree} - * @deprecated use {@link SwitchExpressionUtils#getExpression(Tree)} - */ - @Deprecated // 2023-09-26 - public static ExpressionTree switchExpressionTreeGetExpression(Tree switchExpressionTree) { - return SwitchExpressionUtils.getExpression(switchExpressionTree); - } - - /** - * Returns the cases of {@code switchExpressionTree}. For example - * - *
          -     *   switch ( expression ) {
          -     *     cases
          -     *   }
          -     * 
          - * - * @param switchExpressionTree the switch expression whose cases are returned - * @return the cases of {@code switchExpressionTree} - * @deprecated use {@link SwitchExpressionUtils#getCases(Tree)} - */ - @Deprecated // 2023-09-26 - public static List switchExpressionTreeGetCases(Tree switchExpressionTree) { - return SwitchExpressionUtils.getCases(switchExpressionTree); - } - - /** - * Returns true if {@code switchTree} has a null case label. - * - * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} - * @return true if {@code switchTree} has a null case label - */ - public static boolean hasNullCaseLabel(Tree switchTree) { - if (!atLeastJava21) { - return false; - } - List cases; - if (isSwitchStatement(switchTree)) { - cases = ((SwitchTree) switchTree).getCases(); - } else { - cases = SwitchExpressionUtils.getCases(switchTree); - } - for (CaseTree caseTree : cases) { - List labels = CaseUtils.getLabels(caseTree); - for (Tree label : labels) { - if (label.getKind() == Kind.NULL_LITERAL) { - return true; - } - } - } + if (receiver == null) { + return null; + } + + return TreeUtils.withoutParens(receiver); + } + + // TODO: What about anonymous classes? + // Adding Tree.Kind.NEW_CLASS here doesn't work, because then a + // tree gets cast to ClassTree when it is actually a NewClassTree, + // for example in enclosingClass above. + /** The kinds that represent classes. */ + private static final Set classTreeKinds; + + static { + classTreeKinds = EnumSet.noneOf(Tree.Kind.class); + for (Tree.Kind kind : Tree.Kind.values()) { + if (kind.asInterface() == ClassTree.class) { + classTreeKinds.add(kind); + } + } + } + + /** Kinds that represent a class or method tree. */ + private static final Set classAndMethodTreeKinds; + + static { + classAndMethodTreeKinds = EnumSet.copyOf(classTreeKinds()); + classAndMethodTreeKinds.add(Tree.Kind.METHOD); + } + + /** + * Returns the set of kinds that represent classes and methods. + * + * @return the set of kinds that represent classes and methods + */ + public static Set classAndMethodTreeKinds() { + return classAndMethodTreeKinds; + } + + /** + * Return the set of kinds that represent classes. + * + * @return the set of kinds that represent classes + */ + public static Set classTreeKinds() { + return classTreeKinds; + } + + /** + * Is the given tree kind a class, i.e. a class, enum, interface, or annotation type. + * + * @param tree the tree to test + * @return true, iff the given kind is a class kind + */ + public static boolean isClassTree(Tree tree) { + return classTreeKinds().contains(tree.getKind()); + } + + /** + * The kinds that represent declarations that might have {@code @SuppressWarnings} written on + * them: classes, methods, and variables. + */ + private static final Set declarationTreeKinds; + + static { + declarationTreeKinds = EnumSet.noneOf(Tree.Kind.class); + declarationTreeKinds.addAll(classTreeKinds); + declarationTreeKinds.add(Tree.Kind.METHOD); + declarationTreeKinds.add(Tree.Kind.VARIABLE); + } + + /** + * Return the set of kinds that represent declarations: classes, methods, and variables. + * + * @return the set of kinds that represent declarations + */ + public static Set declarationTreeKinds() { + return declarationTreeKinds; + } + + /** + * Returns true if the given tree is a declaration. + * + * @param tree the tree to test + * @return true if the given tree is a declaration + */ + public static boolean isDeclarationTree(Tree tree) { + return declarationTreeKinds.contains(tree.getKind()); + } + + /** The kinds that represent types. */ + private static final Set typeTreeKinds = + EnumSet.of( + Tree.Kind.PRIMITIVE_TYPE, + Tree.Kind.PARAMETERIZED_TYPE, + Tree.Kind.TYPE_PARAMETER, + Tree.Kind.ARRAY_TYPE, + Tree.Kind.UNBOUNDED_WILDCARD, + Tree.Kind.EXTENDS_WILDCARD, + Tree.Kind.SUPER_WILDCARD, + Tree.Kind.ANNOTATED_TYPE); + + /** + * Return the set of kinds that represent types. + * + * @return the set of kinds that represent types + */ + public static Set typeTreeKinds() { + return typeTreeKinds; + } + + /** + * Is the given tree a type instantiation? + * + *

          TODO: this is an under-approximation: e.g. an identifier could be either a type use or an + * expression. How can we distinguish. + * + * @param tree the tree to test + * @return true, iff the given tree is a type + */ + public static boolean isTypeTree(Tree tree) { + return typeTreeKinds().contains(tree.getKind()); + } + + /** + * Returns true if the given element is an invocation of the method, or of any method that + * overrides that one. + */ + public static boolean isMethodInvocation( + Tree tree, ExecutableElement method, ProcessingEnvironment env) { + if (!(tree instanceof MethodInvocationTree)) { + return false; + } + MethodInvocationTree methInvok = (MethodInvocationTree) tree; + ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); + if (invoked == null) { + return false; + } + return ElementUtils.isMethod(invoked, method, env); + } + + /** + * Returns true if the argument is an invocation of one of the given methods, or of any method + * that overrides them. + * + * @param tree a tree that might be a method invocation + * @param methods the methods to check for + * @param processingEnv the processing environment + * @return true if the argument is an invocation of one of the given methods, or of any method + * that overrides them + */ + public static boolean isMethodInvocation( + Tree tree, List methods, ProcessingEnvironment processingEnv) { + if (!(tree instanceof MethodInvocationTree)) { + return false; + } + MethodInvocationTree methInvok = (MethodInvocationTree) tree; + ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); + if (invoked == null) { + return false; + } + for (ExecutableElement method : methods) { + if (ElementUtils.isMethod(invoked, method, processingEnv)) { + return true; + } + } + return false; + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one + * matching method. If more than one method takes the same number of formal parameters, then use + * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. + * + * @param type the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + Class type, String methodName, int params, ProcessingEnvironment env) { + String typeName = type.getCanonicalName(); + if (typeName == null) { + throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); + } + return getMethod(typeName, methodName, params, env); + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one + * matching method. If more than one method takes the same number of formal parameters, then use + * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + @FullyQualifiedName String typeName, + String methodName, + int params, + ProcessingEnvironment env) { + List methods = getMethods(typeName, methodName, params, env); + if (methods.size() == 1) { + return methods.get(0); + } + throw new BugInCF( + "TreeUtils.getMethod(%s, %s, %d): expected 1 match, found %d: %s", + typeName, methodName, params, methods.size(), methods); + } + + /** + * Returns the ExecutableElement for a method declaration. Returns null there is no matching + * method. Errs if there is more than one matching method. If more than one method takes the same + * number of formal parameters, then use {@link #getMethod(String, String, ProcessingEnvironment, + * String...)}. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElement for the specified method, or null + */ + public static @Nullable ExecutableElement getMethodOrNull( + @FullyQualifiedName String typeName, + String methodName, + int params, + ProcessingEnvironment env) { + List methods = getMethods(typeName, methodName, params, env); + if (methods.size() == 0) { + return null; + } else if (methods.size() == 1) { + return methods.get(0); + } else { + throw new BugInCF( + "TreeUtils.getMethod(%s, %s, %d): expected 0 or 1 match, found %d", + typeName, methodName, params, methods.size()); + } + } + + /** + * Returns all ExecutableElements for method declarations of methodName, in class typeName, with + * params formal parameters. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElements for all matching methods + */ + public static List getMethods( + @FullyQualifiedName String typeName, + String methodName, + int params, + ProcessingEnvironment env) { + List methods = new ArrayList<>(1); + TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); + if (typeElt == null) { + throw new UserError("Configuration problem! Could not load type: " + typeName); + } + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName) && exec.getParameters().size() == params) { + methods.add(exec); + } + } + return methods; + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. + * + * @param type the class that contains the method + * @param methodName the name of the method + * @param env the processing environment + * @param paramTypes the method's formal parameter types + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + Class type, String methodName, ProcessingEnvironment env, String... paramTypes) { + String typeName = type.getCanonicalName(); + if (typeName == null) { + throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); + } + return getMethod(typeName, methodName, env, paramTypes); + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param env the processing environment + * @param paramTypes the method's formal parameter types + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + @FullyQualifiedName String typeName, + String methodName, + ProcessingEnvironment env, + String... paramTypes) { + TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName) + && exec.getParameters().size() == paramTypes.length) { + boolean typesMatch = true; + List params = exec.getParameters(); + for (int i = 0; i < paramTypes.length; i++) { + VariableElement ve = params.get(i); + TypeMirror tm = TypeAnnotationUtils.unannotatedType(ve.asType()); + if (!tm.toString().equals(paramTypes[i])) { + typesMatch = false; + break; + } + } + if (typesMatch) { + return exec; + } + } + } + + // Didn't find an answer. Compose an error message. + List candidates = new ArrayList<>(); + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName)) { + candidates.add(executableElementToString(exec)); + } + } + if (candidates.isEmpty()) { + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + candidates.add(executableElementToString(exec)); + } + } + throw new BugInCF( + "TreeUtils.getMethod: found no match for %s.%s(%s); candidates: %s", + typeName, methodName, Arrays.toString(paramTypes), candidates); + } + + /** + * Formats the ExecutableElement in the way that getMethod() expects it. + * + * @param exec an executable element + * @return the ExecutableElement, formatted in the way that getMethod() expects it + */ + private static String executableElementToString(ExecutableElement exec) { + StringJoiner result = new StringJoiner(", ", exec.getSimpleName() + "(", ")"); + for (VariableElement param : exec.getParameters()) { + result.add(TypeAnnotationUtils.unannotatedType(param.asType()).toString()); + } + return result.toString(); + } + + /** + * Determine whether the given expression is either "this" or an outer "C.this". + * + *

          TODO: Should this also handle "super"? + */ + public static boolean isExplicitThisDereference(ExpressionTree tree) { + if (tree.getKind() == Tree.Kind.IDENTIFIER + && ((IdentifierTree) tree).getName().contentEquals("this")) { + // Explicit this reference "this" + return true; + } + + if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + + MemberSelectTree memSelTree = (MemberSelectTree) tree; + if (memSelTree.getIdentifier().contentEquals("this")) { + // Outer this reference "C.this" + return true; + } + return false; + } + + /** + * Determine whether {@code tree} is a class literal, such as + * + *

          +   *   Object . class
          +   * 
          + * + * @return true iff if tree is a class literal + */ + public static boolean isClassLiteral(Tree tree) { + if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + return "class".equals(((MemberSelectTree) tree).getIdentifier().toString()); + } + + /** + * Determine whether {@code tree} is a field access expression, such as + * + *
          +   *   f
          +   *   obj . f
          +   * 
          + * + * This method currently also returns true for class literals and qualified this. + * + * @param tree a tree that might be a field access + * @return true iff if tree is a field access expression (implicit or explicit) + */ + public static boolean isFieldAccess(Tree tree) { + return asFieldAccess(tree) != null; + } + + /** + * Return the field that {@code tree} is a field access expression for, or null. + * + *
          +   *   f
          +   *   obj . f
          +   * 
          + * + * This method currently also returns a non-null value for class literals and qualified this. + * + * @param tree a tree that might be a field access + * @return the element if tree is a field access expression (implicit or explicit); null otherwise + */ + // TODO: fix value for class literals and qualified this, which are not field accesses. + public static @Nullable VariableElement asFieldAccess(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + // explicit member access (or a class literal or a qualified this) + MemberSelectTree memberSelect = (MemberSelectTree) tree; + assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(memberSelect); + if (el.getKind().isField()) { + return (VariableElement) el; + } + } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { + // implicit field access + IdentifierTree ident = (IdentifierTree) tree; + assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(ident); + if (el.getKind().isField() + && !ident.getName().contentEquals("this") + && !ident.getName().contentEquals("super")) { + return (VariableElement) el; + } + } + return null; + } + + /** + * Compute the name of the field that the field access {@code tree} accesses. Requires {@code + * tree} to be a field access, as determined by {@code isFieldAccess} (which currently also + * returns true for class literals and qualified this). + * + * @param tree a field access tree + * @return the name of the field accessed by {@code tree} + */ + public static String getFieldName(Tree tree) { + assert isFieldAccess(tree); + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree mtree = (MemberSelectTree) tree; + return mtree.getIdentifier().toString(); + } else { + IdentifierTree itree = (IdentifierTree) tree; + return itree.getName().toString(); + } + } + + /** + * Determine whether {@code tree} refers to a method element, such as. + * + *
          +   *   m(...)
          +   *   obj . m(...)
          +   * 
          + * + * @return true iff if tree is a method access expression (implicit or explicit) + */ + public static boolean isMethodAccess(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + // explicit method access + MemberSelectTree memberSelect = (MemberSelectTree) tree; + assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(memberSelect); + return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; + } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { + // implicit method access + IdentifierTree ident = (IdentifierTree) tree; + // The field "super" and "this" are also legal methods + if (ident.getName().contentEquals("super") || ident.getName().contentEquals("this")) { + return true; + } + assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(ident); + return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; + } + return false; + } + + /** + * Compute the name of the method that the method access {@code tree} accesses. Requires {@code + * tree} to be a method access, as determined by {@code isMethodAccess}. + * + * @param tree a method access tree + * @return the name of the method accessed by {@code tree} + */ + public static String getMethodName(Tree tree) { + assert isMethodAccess(tree); + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree mtree = (MemberSelectTree) tree; + return mtree.getIdentifier().toString(); + } else { + IdentifierTree itree = (IdentifierTree) tree; + return itree.getName().toString(); + } + } + + /** + * Return {@code true} if and only if {@code tree} can have a type annotation. + * + * @return {@code true} if and only if {@code tree} can have a type annotation + */ + // TODO: is this implementation precise enough? E.g. does a .class literal work correctly? + public static boolean canHaveTypeAnnotation(Tree tree) { + return ((JCTree) tree).type != null; + } + + /** + * Returns true if and only if the given {@code tree} represents a field access of the given + * {@link VariableElement}. + */ + public static boolean isSpecificFieldAccess(Tree tree, VariableElement var) { + if (tree instanceof MemberSelectTree) { + MemberSelectTree memSel = (MemberSelectTree) tree; + assert isUseOfElement(memSel) : "@AssumeAssertion(nullness): tree kind"; + Element field = TreeUtils.elementFromUse(memSel); + return field.equals(var); + } else if (tree instanceof IdentifierTree) { + IdentifierTree idTree = (IdentifierTree) tree; + assert isUseOfElement(idTree) : "@AssumeAssertion(nullness): tree kind"; + Element field = TreeUtils.elementFromUse(idTree); + return field.equals(var); + } else { + return false; + } + } + + /** + * Returns the VariableElement for a field declaration. + * + * @param typeName the class where the field is declared + * @param fieldName the name of the field + * @param env the processing environment + * @return the VariableElement for typeName.fieldName + */ + public static VariableElement getField( + @FullyQualifiedName String typeName, String fieldName, ProcessingEnvironment env) { + TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); + for (VariableElement var : ElementFilter.fieldsIn(mapElt.getEnclosedElements())) { + if (var.getSimpleName().contentEquals(fieldName)) { + return var; + } + } + throw new BugInCF("TreeUtils.getField: shouldn't be here"); + } + + /** + * Determine whether the given tree represents an ExpressionTree. + * + * @param tree the Tree to test + * @return whether the tree is an ExpressionTree + */ + public static boolean isExpressionTree(Tree tree) { + return tree instanceof ExpressionTree; + } + + /** + * Returns true if this is a super call to the {@link Enum} constructor. + * + * @param tree the method invocation to check + * @return true if this is a super call to the {@link Enum} constructor + */ + public static boolean isEnumSuperCall(MethodInvocationTree tree) { + ExecutableElement ex = TreeUtils.elementFromUse(tree); + assert ex != null : "@AssumeAssertion(nullness): tree kind"; + Name name = ElementUtils.getQualifiedClassName(ex); + assert name != null : "@AssumeAssertion(nullness): assumption"; + boolean correctClass = "java.lang.Enum".contentEquals(name); + boolean correctMethod = "".contentEquals(ex.getSimpleName()); + return correctClass && correctMethod; + } + + /** + * Determine whether the given tree represents a declaration of a type (including type + * parameters). + * + * @param tree the Tree to test + * @return true if the tree is a type declaration + */ + public static boolean isTypeDeclaration(Tree tree) { + return isClassTree(tree) || tree.getKind() == Tree.Kind.TYPE_PARAMETER; + } + + /** + * Returns true if tree is an access of array length. + * + * @param tree tree to check + * @return true if tree is an access of array length + */ + public static boolean isArrayLengthAccess(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT + && isFieldAccess(tree) + && getFieldName(tree).equals("length")) { + ExpressionTree expressionTree = ((MemberSelectTree) tree).getExpression(); + if (TreeUtils.typeOf(expressionTree).getKind() == TypeKind.ARRAY) { + return true; + } + } + return false; + } + + /** + * Returns true if the given {@link MethodTree} is an anonymous constructor (the constructor for + * an anonymous class). + * + * @param method a method tree that may be an anonymous constructor + * @return true if the given path points to an anonymous constructor, false if it does not + */ + public static boolean isAnonymousConstructor(MethodTree method) { + Element e = elementFromTree(method); + if (e == null || e.getKind() != ElementKind.CONSTRUCTOR) { + return false; + } + TypeElement typeElement = (TypeElement) e.getEnclosingElement(); + return typeElement.getNestingKind() == NestingKind.ANONYMOUS; + } + + /** + * Returns true if the passed constructor is anonymous and has an explicit enclosing expression. + * + * @param con an ExecutableElement of a constructor declaration + * @param tree the NewClassTree of a constructor declaration + * @return true if there is an extra enclosing expression + */ + public static boolean isAnonymousConstructorWithExplicitEnclosingExpression( + ExecutableElement con, NewClassTree tree) { + + return (tree.getEnclosingExpression() != null) + && con.getKind() == ElementKind.CONSTRUCTOR + && ((TypeElement) con.getEnclosingElement()).getNestingKind() == NestingKind.ANONYMOUS; + } + + /** + * Returns true if the given {@link MethodTree} is a compact canonical constructor (the + * constructor for a record where the parameters are implicitly declared and implicitly assigned + * to the record's fields). This may be an explicitly declared compact canonical constructor or an + * implicitly generated one. + * + * @param method a method tree that may be a compact canonical constructor + * @return true if the given method is a compact canonical constructor + */ + public static boolean isCompactCanonicalRecordConstructor(MethodTree method) { + Symbol s = (Symbol) elementFromTree(method); + if (s == null) { + throw new BugInCF( + "TreeUtils.isCompactCanonicalRecordConstructor: null symbol for method tree: " + method); + } + return (s.flags() & Flags_RECORD) != 0; + } + + /** + * Returns true if the given {@link Tree} is part of a record that has been automatically + * generated by the compiler. This can be a field that is derived from the record's header field + * list, or an automatically generated canonical constructor. + * + * @param member the {@link Tree} for a member of a record + * @return true if the given path is generated by the compiler + */ + public static boolean isAutoGeneratedRecordMember(Tree member) { + Element e = elementFromTree(member); + return e != null && ElementUtils.isAutoGeneratedRecordMember(e); + } + + /** + * Converts the given AnnotationTrees to AnnotationMirrors. + * + * @param annoTrees list of annotation trees to convert to annotation mirrors + * @return list of annotation mirrors that represent the given annotation trees + */ + public static List annotationsFromTypeAnnotationTrees( + List annoTrees) { + return CollectionsPlume.mapList(TreeUtils::annotationFromAnnotationTree, annoTrees); + } + + /** + * Converts the given AnnotationTree to an AnnotationMirror. + * + * @param tree annotation tree to convert to an annotation mirror + * @return annotation mirror that represent the given annotation tree + */ + public static AnnotationMirror annotationFromAnnotationTree(AnnotationTree tree) { + return ((JCAnnotation) tree).attribute; + } + + /** + * Converts the given AnnotatedTypeTree to a list of AnnotationMirrors. + * + * @param tree annotated type tree to convert + * @return list of AnnotationMirrors from the tree + */ + public static List annotationsFromTree(AnnotatedTypeTree tree) { + return annotationsFromTypeAnnotationTrees(((JCAnnotatedType) tree).annotations); + } + + /** + * Converts the given TypeParameterTree to a list of AnnotationMirrors. + * + * @param tree type parameter tree to convert + * @return list of AnnotationMirrors from the tree + */ + public static List annotationsFromTree(TypeParameterTree tree) { + return annotationsFromTypeAnnotationTrees(((JCTypeParameter) tree).annotations); + } + + /** + * Converts the given NewArrayTree to a list of AnnotationMirrors. + * + * @param tree new array tree + * @return list of AnnotationMirrors from the tree + */ + public static List annotationsFromArrayCreation( + NewArrayTree tree, int level) { + + assert tree instanceof JCNewArray; + JCNewArray newArray = ((JCNewArray) tree); + + if (level == -1) { + return annotationsFromTypeAnnotationTrees(newArray.annotations); + } + + if (newArray.dimAnnotations.length() > 0 + && (level >= 0) + && (level < newArray.dimAnnotations.size())) { + return annotationsFromTypeAnnotationTrees(newArray.dimAnnotations.get(level)); + } + + return Collections.emptyList(); + } + + /** + * Returns true if the tree is the declaration or use of a local variable. + * + * @param tree the tree to check + * @return true if the tree is the declaration or use of a local variable + */ + public static boolean isLocalVariable(Tree tree) { + if (tree.getKind() == Tree.Kind.VARIABLE) { + VariableElement varElt = elementFromDeclaration((VariableTree) tree); + return ElementUtils.isLocalVariable(varElt); + } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { + ExpressionTree etree = (ExpressionTree) tree; + assert isUseOfElement(etree) : "@AssumeAssertion(nullness): tree kind"; + return ElementUtils.isLocalVariable(elementFromUse(etree)); + } + return false; + } + + /** + * Returns the type as a TypeMirror of {@code tree}. To obtain {@code tree}'s AnnotatedTypeMirror, + * call {@code AnnotatedTypeFactory.getAnnotatedType()}. + * + * @return the type as a TypeMirror of {@code tree} + */ + public static TypeMirror typeOf(Tree tree) { + return ((JCTree) tree).type; + } + + /** + * Determines the type for a method invocation at its call site, which has all type variables + * substituted with the type arguments at the call site. + * + *

          {@link javax.lang.model.type.TypeVariable} in the returned type should be compared using + * {@link TypesUtils#areSame(TypeVariable, TypeVariable)} because the {@code TypeVariable} will be + * freshly created by this method and will not be the same using {@link Object#equals(Object)} or + * {@link javax.lang.model.util.Types#isSameType(TypeMirror, TypeMirror)}. + * + * @param tree the method invocation + * @return the {@link ExecutableType} corresponding to the method invocation at its call site + */ + @Pure + public static ExecutableType typeFromUse(MethodInvocationTree tree) { + TypeMirror type = TreeUtils.typeOf(tree.getMethodSelect()); + if (!(type instanceof ExecutableType)) { + throw new BugInCF( + "TreeUtils.typeFromUse(MethodInvocationTree): type of method select in method" + + " invocation should be ExecutableType. Found: %s", + type); + } + ExecutableType executableType = (ExecutableType) type; + ExecutableElement element = elementFromUse(tree); + if (executableType.getParameterTypes().size() != element.getParameters().size()) { + // Sometimes when the method type is viewpoint-adapted, the vararg parameter disappears, + // just return the declared type. + // For example, + // static void call(MethodHandle methodHandle) throws Throwable { + // methodHandle.invoke(); + // } + return (ExecutableType) element.asType(); + } + return executableType; + } + + /** + * Determines the type for a constructor at its call site given an invocation via {@code new}, + * which has all type variables substituted with the type arguments at the call site. + * + * @param tree the constructor invocation + * @return the {@link ExecutableType} corresponding to the constructor call (i.e., the given + * {@code tree}) at its call site + */ + @Pure + public static ExecutableType typeFromUse(NewClassTree tree) { + if (!(tree instanceof JCTree.JCNewClass)) { + throw new BugInCF("TreeUtils.typeFromUse(NewClassTree): not a javac internal tree"); + } + + JCNewClass newClassTree = (JCNewClass) tree; + TypeMirror type = newClassTree.constructorType; + + if (!(type instanceof ExecutableType)) { + throw new BugInCF( + "TreeUtils.typeFromUse(NewClassTree): type of constructor in new class tree" + + " should be ExecutableType. Found: %s", + type); + } + return (ExecutableType) type; + } + + /** + * The type of the lambda or method reference tree is a functional interface type. This method + * returns the single abstract method declared by that functional interface. (The type of this + * method is referred to as the function type.) + * + * @param tree lambda or member reference tree + * @param env the processing environment + * @return the single abstract method declared by the type of the tree + */ + public static ExecutableElement findFunction(Tree tree, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + Types javacTypes = Types.instance(ctx); + return (ExecutableElement) javacTypes.findDescriptorSymbol(((Type) typeOf(tree)).asElement()); + } + + /** + * Returns true if {@code tree} is an implicitly typed lambda. + * + *

          A lambda expression whose formal type parameters have inferred types is an implicitly typed + * lambda. (See JLS 15.27.1) + * + * @param tree any kind of tree + * @return true iff {@code tree} is an implicitly typed lambda + */ + public static boolean isImplicitlyTypedLambda(Tree tree) { + return tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION + && ((JCLambda) tree).paramKind == ParameterKind.IMPLICIT; + } + + /** + * This is a duplication of {@code + * com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind}, which is not part of the + * supported javac API. + */ + public enum MemberReferenceKind { + /** super # instMethod */ + SUPER(ReferenceMode.INVOKE, false), + /** Type # instMethod */ + UNBOUND(ReferenceMode.INVOKE, true), + /** Type # staticMethod */ + STATIC(ReferenceMode.INVOKE, false), + /** Expr # instMethod */ + BOUND(ReferenceMode.INVOKE, false), + /** Inner # new */ + IMPLICIT_INNER(ReferenceMode.NEW, false), + /** Toplevel # new */ + TOPLEVEL(ReferenceMode.NEW, false), + /** ArrayType # new */ + ARRAY_CTOR(ReferenceMode.NEW, false); + + /** Whether this kind is a method reference or a constructor reference. */ + final ReferenceMode mode; + + /** Whether this kind is unbound. */ + final boolean unbound; + + /** + * Creates a MemberReferenceKind. + * + * @param mode whether this kind is a method reference or a constructor reference + * @param unbound whether the kind is not bound + */ + MemberReferenceKind(ReferenceMode mode, boolean unbound) { + this.mode = mode; + this.unbound = unbound; + } + + /** + * Whether this kind is unbound. + * + * @return Whether this kind is unbound + */ + public boolean isUnbound() { + return unbound; + } + + /** + * Returns whether this kind is a constructor reference. + * + * @return whether this kind is a constructor reference + */ + public boolean isConstructorReference() { + return mode == ReferenceMode.NEW; + } + + /** + * Returns the kind of member reference {@code tree} is. + * + * @param tree a member reference tree + * @return the kind of member reference {@code tree} is + */ + public static MemberReferenceKind getMemberReferenceKind(MemberReferenceTree tree) { + JCMemberReference memberTree = (JCMemberReference) tree; + switch (memberTree.kind) { + case SUPER: + return SUPER; + case UNBOUND: + return UNBOUND; + case STATIC: + return STATIC; + case BOUND: + return BOUND; + case IMPLICIT_INNER: + return IMPLICIT_INNER; + case TOPLEVEL: + return TOPLEVEL; + case ARRAY_CTOR: + return ARRAY_CTOR; + default: + throw new BugInCF("Unexpected ReferenceKind: %s", memberTree.kind); + } + } + } + + /** + * Determine whether an expression {@link ExpressionTree} has the constant value true, according + * to the compiler logic. + * + * @param tree the expression to be checked + * @return true if {@code tree} has the constant value true + */ + public static boolean isExprConstTrue(ExpressionTree tree) { + assert tree instanceof JCExpression; + if (((JCExpression) tree).type.isTrue()) { + return true; + } + tree = TreeUtils.withoutParens(tree); + if (tree instanceof JCTree.JCBinary) { + JCBinary binTree = (JCBinary) tree; + JCExpression ltree = binTree.lhs; + JCExpression rtree = binTree.rhs; + switch (binTree.getTag()) { + case AND: + return isExprConstTrue(ltree) && isExprConstTrue(rtree); + case OR: + return isExprConstTrue(ltree) || isExprConstTrue(rtree); + default: + break; + } + } + return false; + } + + /** + * Return toString(), but without line separators. + * + * @param tree a tree + * @return a one-line string representation of the tree + */ + public static String toStringOneLine(Tree tree) { + return tree.toString().trim().replaceAll("\\s+", " "); + } + + /** + * Return either {@link #toStringOneLine} if it is no more than {@code length} characters, or + * {@link #toStringOneLine} quoted and truncated. + * + * @param tree a tree + * @param length the maximum length for the result; must be at least 6 + * @return a one-line string representation of the tree that is no longer than {@code length} + * characters long + */ + public static String toStringTruncated(Tree tree, int length) { + if (length < 6) { + throw new BugInCF("TreeUtils.toStringTruncated: bad length " + length); + } + String result = toStringOneLine(tree); + if (result.length() > length) { + // The quoting increases the likelihood that all delimiters are balanced in the result. + // That makes it easier to manipulate the result (such as skipping over it) in an + // editor. The quoting also makes clear that the value is truncated. + result = "\"" + result.substring(0, length - 5) + "...\""; + } + return result; + } + + /** + * Given a javac ExpressionTree representing a fully qualified name such as "java.lang.Object", + * creates a String containing the name. + * + * @param nameExpr an ExpressionTree representing a fully qualified name + * @return a String representation of the fully qualified name + */ + public static String nameExpressionToString(ExpressionTree nameExpr) { + TreeVisitor visitor = + new SimpleTreeVisitor() { + @Override + public String visitIdentifier(IdentifierTree tree, Void p) { + return tree.toString(); + } + + @Override + public String visitMemberSelect(MemberSelectTree tree, Void p) { + return tree.getExpression().accept(this, null) + "." + tree.getIdentifier().toString(); + } + }; + return nameExpr.accept(visitor, null); + } + + /** + * Returns true if the binary operator may do a widening primitive conversion. See JLS chapter 5. + * + * @param tree a binary tree + * @return true if the tree's operator does numeric promotion on its arguments + */ + public static boolean isWideningBinary(BinaryTree tree) { + switch (tree.getKind()) { + case LEFT_SHIFT: + case LEFT_SHIFT_ASSIGNMENT: + case RIGHT_SHIFT: + case RIGHT_SHIFT_ASSIGNMENT: + case UNSIGNED_RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + // Strictly speaking, these operators do unary promotion on each argument + // separately. + return true; + + case MULTIPLY: + case MULTIPLY_ASSIGNMENT: + case DIVIDE: + case DIVIDE_ASSIGNMENT: + case REMAINDER: + case REMAINDER_ASSIGNMENT: + case PLUS: + case PLUS_ASSIGNMENT: + case MINUS: + case MINUS_ASSIGNMENT: + + case LESS_THAN: + case LESS_THAN_EQUAL: + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case EQUAL_TO: + case NOT_EQUAL_TO: + + case AND: + case XOR: + case OR: + // These operators do binary promotion on the two arguments together. + return true; + + // TODO: CONDITIONAL_EXPRESSION (?:) sometimes does numeric promotion. + + default: return false; } - - /** - * Returns true if the given tree is a switch statement (as opposed to a switch expression). - * - * @param tree the switch statement or expression to check - * @return true if the given tree is a switch statement (as opposed to a switch expression) - */ - public static boolean isSwitchStatement(Tree tree) { - return tree.getKind() == Tree.Kind.SWITCH; - } - - /** - * Returns true if the given switch statement tree is an enhanced switch statement, as described - * in JSL - * 14.11.2. - * - * @param switchTree the switch statement to check - * @return true if the given tree is an enhanced switch statement - */ - public static boolean isEnhancedSwitchStatement(SwitchTree switchTree) { - TypeMirror exprType = typeOf(switchTree.getExpression()); - // TODO: this should be only char, byte, short, int, Character, Byte, Short, Integer. Is the - // over-approximation a problem? - Element exprElem = TypesUtils.getTypeElement(exprType); - boolean isNotEnum = exprElem == null || exprElem.getKind() != ElementKind.ENUM; - if (!TypesUtils.isPrimitiveOrBoxed(exprType) - && !TypesUtils.isString(exprType) - && isNotEnum) { - return true; - } - - for (CaseTree caseTree : switchTree.getCases()) { - for (Tree caseLabel : CaseUtils.getLabels(caseTree)) { - if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL - || TreeUtils.isBindingPatternTree(caseLabel) - || TreeUtils.isDeconstructionPatternTree(caseLabel)) { - return true; - } - } - } - - return false; - } - - /** - * Returns the value (expression) for {@code yieldTree}. - * - * @param yieldTree the yield tree - * @return the value (expression) for {@code yieldTree} - * @deprecated use {@link YieldUtils#getValue(Tree)} - */ - @Deprecated // 2023-09-26 - public static ExpressionTree yieldTreeGetValue(Tree yieldTree) { - return YieldUtils.getValue(yieldTree); - } - - /** - * Returns true if the {@code variableTree} is declared using the {@code var} Java keyword. - * - * @param variableTree the variableTree to check - * @return true if the variableTree is declared using the {@code var} Java keyword - */ - public static boolean isVariableTreeDeclaredUsingVar(VariableTree variableTree) { - JCExpression type = (JCExpression) variableTree.getType(); - return type != null && type.pos == Position.NOPOS; - } - - /** - * Returns true if the given method/constructor invocation is a varargs invocation. - * - * @param tree a method/constructor invocation - * @return true if the given method/constructor invocation is a varargs invocation - */ - public static boolean isVarArgs(Tree tree) { - switch (tree.getKind()) { - case METHOD_INVOCATION: - return isVarArgs((MethodInvocationTree) tree); - case NEW_CLASS: - return isVarArgs((NewClassTree) tree); - default: - throw new BugInCF("TreeUtils.isVarArgs: unexpected kind of tree: " + tree); - } - } - - /** - * Returns true if the given method invocation is a varargs invocation. - * - * @param invok the method invocation - * @return true if the given method invocation is a varargs invocation - */ - public static boolean isVarArgs(MethodInvocationTree invok) { - return isVarArgs(elementFromUse(invok), invok.getArguments()); - } - - /** - * Returns true if the given constructor invocation is a varargs invocation. - * - * @param newClassTree the constructor invocation - * @return true if the given method invocation is a varargs invocation - */ - public static boolean isVarArgs(NewClassTree newClassTree) { - return isVarArgs(elementFromUse(newClassTree), newClassTree.getArguments()); - } - - /** - * Returns true if a method/constructor invocation is a varargs invocation. - * - * @param method the method or constructor - * @param args the arguments passed at the invocation - * @return true if the given method/constructor invocation is a varargs invocation - */ - private static boolean isVarArgs( - ExecutableElement method, List args) { - if (!method.isVarArgs()) { - return false; - } - - List parameters = method.getParameters(); - if (parameters.size() != args.size()) { - return true; - } - - TypeMirror lastArgType = typeOf(args.get(args.size() - 1)); - if (lastArgType.getKind() == TypeKind.NULL) { - return false; - } - if (lastArgType.getKind() != TypeKind.ARRAY) { - return true; - } - - TypeMirror varargsParamType = parameters.get(parameters.size() - 1).asType(); - return TypesUtils.getArrayDepth(varargsParamType) != TypesUtils.getArrayDepth(lastArgType); - } - - /** - * Determine whether the given tree is of Kind RECORD, in a way that works on all versions of - * Java. - * - * @param tree the tree to get the kind for - * @return whether the tree is of the kind RECORD - */ - public static boolean isRecordTree(Tree tree) { - Tree.Kind kind = tree.getKind(); - // Must use String comparison because we may be on an older JDK: - return kind.name().equals("RECORD"); - } - - /** - * Calls getKind() on the given tree, but returns CLASS if the Kind is RECORD. This is needed - * because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD can't - * be used in case statements, and usually we want to treat them the same as classes. - * - * @param tree the tree to get the kind for - * @return the kind of the tree, but CLASS if the kind was RECORD - */ - public static Tree.Kind getKindRecordAsClass(Tree tree) { - if (isRecordTree(tree)) { - return Tree.Kind.CLASS; - } - return tree.getKind(); - } - - /** - * Returns true if the {@code tree} is a binary tree that performs a comparison. - * - * @param tree the tree to check - * @return whether the tree represents a binary comparison - */ - public static boolean isBinaryComparison(BinaryTree tree) { - return BINARY_COMPARISON_TREE_KINDS.contains(tree.getKind()); - } - - /** - * Returns the result of {@code treeMaker.Select(base, sym)}. - * - * @param treeMaker the TreeMaker to use - * @param base the expression for the select - * @param sym the symbol to select - * @return the JCFieldAccess tree to select sym in base - */ - public static JCFieldAccess Select(TreeMaker treeMaker, Tree base, Symbol sym) { - // The return type of TreeMaker.Select changed in - // https://github.com/openjdk/jdk/commit/a917fb3fcf0fe1a4c4de86c08ae4041462848b82#diff-0f1b4da56622ccb5ff716ce5a9532819fc5573179a1eb2c803d053196824891aR726 - // When the ECF is compiled with Java 21+, even with `--source/target 8`, this will lead to - // a java.lang.NoSuchMethodError: 'com.sun.tools.javac.tree.JCTree$JCFieldAccess - // com.sun.tools.javac.tree.TreeMaker.Select(com.sun.tools.javac.tree.JCTree$JCExpression, - // com.sun.tools.javac.code.Symbol)' - // when executed on Java <21. - // Therefore, always use reflection to access TreeMaker.Select. - // Hopefully, the JVM optimizes the reflective access quickly. - try { - assert TREEMAKER_SELECT != null : "@AssumeAssertion(nullness): initialization"; - JCFieldAccess jfa = (JCFieldAccess) TREEMAKER_SELECT.invoke(treeMaker, base, sym); - if (jfa != null) { - return jfa; - } else { - throw new BugInCF( - "TreeUtils.Select: TreeMaker.Select returned null for tree: %s", base); - } - } catch (InvocationTargetException | IllegalAccessException e) { - throw new BugInCF("TreeUtils.Select: reflection failed for tree: %s", base, e); - } - } - - /** - * Returns the result of {@code treeMaker.Select(base, name)}. - * - * @param treeMaker the TreeMaker to use - * @param base the expression for the select - * @param name the name to select - * @return the JCFieldAccess tree to select sym in base - */ - public static JCFieldAccess Select( - TreeMaker treeMaker, Tree base, com.sun.tools.javac.util.Name name) { - /* - * There's no need for reflection here. The only reason we even declare this method is so that - * callers don't have to remember which overload we provide a wrapper around. - */ - return treeMaker.Select((JCExpression) base, name); - } + } + + /** + * Returns the annotations explicitly written on the given type. + * + * @param annoTrees annotations written before a variable/method declaration; null if this type is + * not from such a location. This might contain type annotations that the Java parser attached + * to the declaration rather than to the type. + * @param typeTree the type whose annotations to return + * @return the annotations explicitly written on the given type + */ + public static List getExplicitAnnotationTrees( + @Nullable List annoTrees, Tree typeTree) { + while (true) { + switch (typeTree.getKind()) { + case IDENTIFIER: + case PRIMITIVE_TYPE: + if (annoTrees == null) { + return Collections.emptyList(); + } + return annoTrees; + case ANNOTATED_TYPE: + return ((AnnotatedTypeTree) typeTree).getAnnotations(); + case ARRAY_TYPE: + case TYPE_PARAMETER: + case UNBOUNDED_WILDCARD: + case EXTENDS_WILDCARD: + case SUPER_WILDCARD: + return Collections.emptyList(); + case MEMBER_SELECT: + if (annoTrees == null) { + return Collections.emptyList(); + } + typeTree = ((MemberSelectTree) typeTree).getExpression(); + break; + case PARAMETERIZED_TYPE: + typeTree = ((ParameterizedTypeTree) typeTree).getType(); + break; + case UNION_TYPE: + List alternatives = ((UnionTypeTree) typeTree).getTypeAlternatives(); + List result = new ArrayList<>(alternatives.size()); + for (Tree alternative : alternatives) { + result.addAll(getExplicitAnnotationTrees(null, alternative)); + } + return result; + default: + throw new BugInCF( + "TreeUtils.getExplicitAnnotationTrees: what typeTree? %s %s %s", + typeTree.getKind(), typeTree.getClass(), typeTree); + } + } + } + + /** + * Return a tree for the default value of the given type. The default value is 0, false, or null. + * + * @param typeMirror a type + * @param processingEnv the processing environment + * @return a tree for {@code type}'s default value + */ + public static LiteralTree getDefaultValueTree( + TypeMirror typeMirror, ProcessingEnvironment processingEnv) { + typeMirror = TypeAnnotationUtils.unannotatedType(typeMirror); + switch (typeMirror.getKind()) { + case BYTE: + case SHORT: + case INT: + // Byte should be (byte) 0, but this probably doesn't matter so just use int 0; + // Short should be (short) 0, but this probably doesn't matter so just use int 0; + return TreeUtils.createLiteral(TypeTag.INT, 0, typeMirror, processingEnv); + case CHAR: + // Value of a char literal needs to be stored as an integer because + // LiteralTree#getValue converts it from an integer to a char before being + // returned. + return TreeUtils.createLiteral(TypeTag.CHAR, (int) '\u0000', typeMirror, processingEnv); + case LONG: + return TreeUtils.createLiteral(TypeTag.LONG, 0L, typeMirror, processingEnv); + case FLOAT: + return TreeUtils.createLiteral(TypeTag.FLOAT, 0.0f, typeMirror, processingEnv); + case DOUBLE: + return TreeUtils.createLiteral(TypeTag.DOUBLE, 0.0d, typeMirror, processingEnv); + case BOOLEAN: + // Value of a boolean literal needs to be stored as an integer because + // LiteralTree#getValue converts it from an integer to a boolean before being + // returned. + return TreeUtils.createLiteral(TypeTag.BOOLEAN, 0, typeMirror, processingEnv); + default: + return TreeUtils.createLiteral( + TypeTag.BOT, null, processingEnv.getTypeUtils().getNullType(), processingEnv); + } + } + + /** + * Creates a LiteralTree for the given value. + * + * @param typeTag the literal's type tag + * @param value a wrapped primitive, null, or a String + * @param typeMirror the typeMirror for the literal + * @param processingEnv the processing environment + * @return a LiteralTree for the given type tag and value + */ + public static LiteralTree createLiteral( + TypeTag typeTag, + @Nullable Object value, + TypeMirror typeMirror, + ProcessingEnvironment processingEnv) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + TreeMaker maker = TreeMaker.instance(context); + LiteralTree result = maker.Literal(typeTag, value); + ((JCLiteral) result).type = (Type) typeMirror; + return result; + } + + /** + * Returns true if the given tree evaluates to {@code null}. + * + * @param t a tree + * @return true if the given tree evaluates to {@code null} + */ + public static boolean isNullExpression(Tree t) { + while (true) { + switch (t.getKind()) { + case PARENTHESIZED: + t = ((ParenthesizedTree) t).getExpression(); + break; + case TYPE_CAST: + t = ((TypeCastTree) t).getExpression(); + break; + case NULL_LITERAL: + return true; + default: + return false; + } + } + } + + /** + * Returns true if two expressions originating from the same scope are identical, i.e. they are + * syntactically represented in the same way (modulo parentheses) and represent the same value. + * + *

          If the expression includes one or more method calls, assumes the method calls are + * deterministic. + * + * @param expr1 the first expression to compare + * @param expr2 the second expression to compare; expr2 must originate from the same scope as + * expr1 + * @return true if the expressions expr1 and expr2 are syntactically identical + */ + public static boolean sameTree(ExpressionTree expr1, ExpressionTree expr2) { + expr1 = TreeUtils.withoutParens(expr1); + expr2 = TreeUtils.withoutParens(expr2); + // Converting to a string in order to compare is somewhat inefficient, and it doesn't handle + // internal parentheses. We could create a visitor instead. + return expr1.getKind() == expr2.getKind() && expr1.toString().equals(expr2.toString()); + } + + /** + * Returns true if this is the default case for a switch statement or expression. (Also, returns + * true if {@code caseTree} is {@code case null, default:}.) + * + * @param caseTree a case tree + * @return true if {@code caseTree} is the default case for a switch statement or expression + * @deprecated use {@link CaseUtils#isDefaultCaseTree(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static boolean isDefaultCaseTree(CaseTree caseTree) { + return CaseUtils.isDefaultCaseTree(caseTree); + } + + /** + * Returns true if this is a case rule (as opposed to a case statement). + * + * @param caseTree a case tree + * @return true if {@code caseTree} is a case rule + * @deprecated use {@link CaseUtils#isCaseRule(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static boolean isCaseRule(CaseTree caseTree) { + return CaseUtils.isCaseRule(caseTree); + } + + /** + * Get the list of expressions from a case expression. For the default case, this is empty. + * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can be + * multiple expressions per case. + * + * @param caseTree the case expression to get the expressions from + * @return the list of expressions in the case + * @deprecated use {@link CaseUtils#getExpressions(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static List caseTreeGetExpressions(CaseTree caseTree) { + return CaseUtils.getExpressions(caseTree); + } + + /** + * Returns the body of the case statement if it is of the form {@code case -> + * }. This method should only be called if {@link CaseTree#getStatements()} returns + * null. + * + * @param caseTree the case expression to get the body from + * @return the body of the case tree + * @deprecated use {@link CaseUtils#getBody(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static @Nullable Tree caseTreeGetBody(CaseTree caseTree) { + return CaseUtils.getBody(caseTree); + } + + /** + * Returns true if {@code tree} is a {@code BindingPatternTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code BindingPatternTree} + */ + public static boolean isBindingPatternTree(Tree tree) { + return tree.getKind().name().contentEquals("BINDING_PATTERN"); + } + + /** + * Returns the binding variable of {@code bindingPatternTree}. + * + * @param bindingPatternTree the BindingPatternTree whose binding variable is returned + * @return the binding variable of {@code bindingPatternTree} + * @deprecated use {@link BindingPatternUtils#getVariable(Tree)} + */ + @Deprecated // 2023-09-26 + public static VariableTree bindingPatternTreeGetVariable(Tree bindingPatternTree) { + return BindingPatternUtils.getVariable(bindingPatternTree); + } + + /** + * Returns true if {@code tree} is a {@code DeconstructionPatternTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code DeconstructionPatternTree} + */ + public static boolean isDeconstructionPatternTree(Tree tree) { + return tree.getKind().name().contentEquals("DECONSTRUCTION_PATTERN"); + } + + /** + * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does not + * have a pattern, including if the JDK version does not support instance-of patterns. + * + * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned + * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist + * @deprecated use {@link InstanceOfUtils#getPattern(InstanceOfTree)} + */ + @Deprecated // 2023-09-26 + public static @Nullable Tree instanceOfTreeGetPattern(InstanceOfTree instanceOfTree) { + return InstanceOfUtils.getPattern(instanceOfTree); + } + + /** + * Returns the selector expression of {@code switchExpressionTree}. For example + * + *

          +   *   switch ( expression ) { ... }
          +   * 
          + * + * @param switchExpressionTree the switch expression whose selector expression is returned + * @return the selector expression of {@code switchExpressionTree} + * @deprecated use {@link SwitchExpressionUtils#getExpression(Tree)} + */ + @Deprecated // 2023-09-26 + public static ExpressionTree switchExpressionTreeGetExpression(Tree switchExpressionTree) { + return SwitchExpressionUtils.getExpression(switchExpressionTree); + } + + /** + * Returns the cases of {@code switchExpressionTree}. For example + * + *
          +   *   switch ( expression ) {
          +   *     cases
          +   *   }
          +   * 
          + * + * @param switchExpressionTree the switch expression whose cases are returned + * @return the cases of {@code switchExpressionTree} + * @deprecated use {@link SwitchExpressionUtils#getCases(Tree)} + */ + @Deprecated // 2023-09-26 + public static List switchExpressionTreeGetCases(Tree switchExpressionTree) { + return SwitchExpressionUtils.getCases(switchExpressionTree); + } + + /** + * Returns true if {@code switchTree} has a null case label. + * + * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} + * @return true if {@code switchTree} has a null case label + */ + public static boolean hasNullCaseLabel(Tree switchTree) { + if (!atLeastJava21) { + return false; + } + List cases; + if (isSwitchStatement(switchTree)) { + cases = ((SwitchTree) switchTree).getCases(); + } else { + cases = SwitchExpressionUtils.getCases(switchTree); + } + for (CaseTree caseTree : cases) { + List labels = CaseUtils.getLabels(caseTree); + for (Tree label : labels) { + if (label.getKind() == Kind.NULL_LITERAL) { + return true; + } + } + } + return false; + } + + /** + * Returns true if the given tree is a switch statement (as opposed to a switch expression). + * + * @param tree the switch statement or expression to check + * @return true if the given tree is a switch statement (as opposed to a switch expression) + */ + public static boolean isSwitchStatement(Tree tree) { + return tree.getKind() == Tree.Kind.SWITCH; + } + + /** + * Returns true if the given switch statement tree is an enhanced switch statement, as described + * in JSL + * 14.11.2. + * + * @param switchTree the switch statement to check + * @return true if the given tree is an enhanced switch statement + */ + public static boolean isEnhancedSwitchStatement(SwitchTree switchTree) { + TypeMirror exprType = typeOf(switchTree.getExpression()); + // TODO: this should be only char, byte, short, int, Character, Byte, Short, Integer. Is the + // over-approximation a problem? + Element exprElem = TypesUtils.getTypeElement(exprType); + boolean isNotEnum = exprElem == null || exprElem.getKind() != ElementKind.ENUM; + if (!TypesUtils.isPrimitiveOrBoxed(exprType) && !TypesUtils.isString(exprType) && isNotEnum) { + return true; + } + + for (CaseTree caseTree : switchTree.getCases()) { + for (Tree caseLabel : CaseUtils.getLabels(caseTree)) { + if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL + || TreeUtils.isBindingPatternTree(caseLabel) + || TreeUtils.isDeconstructionPatternTree(caseLabel)) { + return true; + } + } + } + + return false; + } + + /** + * Returns the value (expression) for {@code yieldTree}. + * + * @param yieldTree the yield tree + * @return the value (expression) for {@code yieldTree} + * @deprecated use {@link YieldUtils#getValue(Tree)} + */ + @Deprecated // 2023-09-26 + public static ExpressionTree yieldTreeGetValue(Tree yieldTree) { + return YieldUtils.getValue(yieldTree); + } + + /** + * Returns true if the {@code variableTree} is declared using the {@code var} Java keyword. + * + * @param variableTree the variableTree to check + * @return true if the variableTree is declared using the {@code var} Java keyword + */ + public static boolean isVariableTreeDeclaredUsingVar(VariableTree variableTree) { + JCExpression type = (JCExpression) variableTree.getType(); + return type != null && type.pos == Position.NOPOS; + } + + /** + * Returns true if the given method/constructor invocation is a varargs invocation. + * + * @param tree a method/constructor invocation + * @return true if the given method/constructor invocation is a varargs invocation + */ + public static boolean isVarArgs(Tree tree) { + switch (tree.getKind()) { + case METHOD_INVOCATION: + return isVarArgs((MethodInvocationTree) tree); + case NEW_CLASS: + return isVarArgs((NewClassTree) tree); + default: + throw new BugInCF("TreeUtils.isVarArgs: unexpected kind of tree: " + tree); + } + } + + /** + * Returns true if the given method invocation is a varargs invocation. + * + * @param invok the method invocation + * @return true if the given method invocation is a varargs invocation + */ + public static boolean isVarArgs(MethodInvocationTree invok) { + return isVarArgs(elementFromUse(invok), invok.getArguments()); + } + + /** + * Returns true if the given constructor invocation is a varargs invocation. + * + * @param newClassTree the constructor invocation + * @return true if the given method invocation is a varargs invocation + */ + public static boolean isVarArgs(NewClassTree newClassTree) { + return isVarArgs(elementFromUse(newClassTree), newClassTree.getArguments()); + } + + /** + * Returns true if a method/constructor invocation is a varargs invocation. + * + * @param method the method or constructor + * @param args the arguments passed at the invocation + * @return true if the given method/constructor invocation is a varargs invocation + */ + private static boolean isVarArgs(ExecutableElement method, List args) { + if (!method.isVarArgs()) { + return false; + } + + List parameters = method.getParameters(); + if (parameters.size() != args.size()) { + return true; + } + + TypeMirror lastArgType = typeOf(args.get(args.size() - 1)); + if (lastArgType.getKind() == TypeKind.NULL) { + return false; + } + if (lastArgType.getKind() != TypeKind.ARRAY) { + return true; + } + + TypeMirror varargsParamType = parameters.get(parameters.size() - 1).asType(); + return TypesUtils.getArrayDepth(varargsParamType) != TypesUtils.getArrayDepth(lastArgType); + } + + /** + * Determine whether the given tree is of Kind RECORD, in a way that works on all versions of + * Java. + * + * @param tree the tree to get the kind for + * @return whether the tree is of the kind RECORD + */ + public static boolean isRecordTree(Tree tree) { + Tree.Kind kind = tree.getKind(); + // Must use String comparison because we may be on an older JDK: + return kind.name().equals("RECORD"); + } + + /** + * Calls getKind() on the given tree, but returns CLASS if the Kind is RECORD. This is needed + * because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD can't be + * used in case statements, and usually we want to treat them the same as classes. + * + * @param tree the tree to get the kind for + * @return the kind of the tree, but CLASS if the kind was RECORD + */ + public static Tree.Kind getKindRecordAsClass(Tree tree) { + if (isRecordTree(tree)) { + return Tree.Kind.CLASS; + } + return tree.getKind(); + } + + /** + * Returns true if the {@code tree} is a binary tree that performs a comparison. + * + * @param tree the tree to check + * @return whether the tree represents a binary comparison + */ + public static boolean isBinaryComparison(BinaryTree tree) { + return BINARY_COMPARISON_TREE_KINDS.contains(tree.getKind()); + } + + /** + * Returns the result of {@code treeMaker.Select(base, sym)}. + * + * @param treeMaker the TreeMaker to use + * @param base the expression for the select + * @param sym the symbol to select + * @return the JCFieldAccess tree to select sym in base + */ + public static JCFieldAccess Select(TreeMaker treeMaker, Tree base, Symbol sym) { + // The return type of TreeMaker.Select changed in + // https://github.com/openjdk/jdk/commit/a917fb3fcf0fe1a4c4de86c08ae4041462848b82#diff-0f1b4da56622ccb5ff716ce5a9532819fc5573179a1eb2c803d053196824891aR726 + // When the ECF is compiled with Java 21+, even with `--source/target 8`, this will lead to + // a java.lang.NoSuchMethodError: 'com.sun.tools.javac.tree.JCTree$JCFieldAccess + // com.sun.tools.javac.tree.TreeMaker.Select(com.sun.tools.javac.tree.JCTree$JCExpression, + // com.sun.tools.javac.code.Symbol)' + // when executed on Java <21. + // Therefore, always use reflection to access TreeMaker.Select. + // Hopefully, the JVM optimizes the reflective access quickly. + try { + assert TREEMAKER_SELECT != null : "@AssumeAssertion(nullness): initialization"; + JCFieldAccess jfa = (JCFieldAccess) TREEMAKER_SELECT.invoke(treeMaker, base, sym); + if (jfa != null) { + return jfa; + } else { + throw new BugInCF("TreeUtils.Select: TreeMaker.Select returned null for tree: %s", base); + } + } catch (InvocationTargetException | IllegalAccessException e) { + throw new BugInCF("TreeUtils.Select: reflection failed for tree: %s", base, e); + } + } + + /** + * Returns the result of {@code treeMaker.Select(base, name)}. + * + * @param treeMaker the TreeMaker to use + * @param base the expression for the select + * @param name the name to select + * @return the JCFieldAccess tree to select sym in base + */ + public static JCFieldAccess Select( + TreeMaker treeMaker, Tree base, com.sun.tools.javac.util.Name name) { + /* + * There's no need for reflection here. The only reason we even declare this method is so that + * callers don't have to remember which overload we provide a wrapper around. + */ + return treeMaker.Select((JCExpression) base, name); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java index 6b37401301d..5e62ba65131 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java @@ -5,18 +5,15 @@ import com.sun.source.tree.InstanceOfTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.ClassGetName; -import org.checkerframework.dataflow.qual.Pure; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; - import javax.lang.model.SourceVersion; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.ClassGetName; +import org.checkerframework.dataflow.qual.Pure; /** * This class contains util methods for reflective accessing Tree classes and methods that were @@ -24,569 +21,556 @@ */ public class TreeUtilsAfterJava11 { - /** Don't use. */ - private TreeUtilsAfterJava11() { - throw new AssertionError("Cannot be instantiated."); - } + /** Don't use. */ + private TreeUtilsAfterJava11() { + throw new AssertionError("Cannot be instantiated."); + } - /** The latest source version supported by this compiler. */ - private static final int sourceVersionNumber = - Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); + /** The latest source version supported by this compiler. */ + private static final int sourceVersionNumber = + Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); - /** Utility methods for accessing {@code BindingPatternTree} methods. */ - public static class BindingPatternUtils { + /** Utility methods for accessing {@code BindingPatternTree} methods. */ + public static class BindingPatternUtils { - /** Don't use. */ - private BindingPatternUtils() { - throw new AssertionError("Cannot be instantiated."); - } - - /** - * The {@code BindingPatternTree.getVariable} method for Java 16 and higher; null otherwise. - */ - private static @Nullable Method GET_VARIABLE = null; - - /** - * Returns the binding variable of {@code bindingPatternTree}. - * - * @param bindingPatternTree the BindingPatternTree whose binding variable is returned - * @return the binding variable of {@code bindingPatternTree} - */ - public static VariableTree getVariable(Tree bindingPatternTree) { - assertVersionAtLeast(16); - if (GET_VARIABLE == null) { - Class bindingPatternClass = - classForName("com.sun.source.tree.BindingPatternTree"); - GET_VARIABLE = getMethod(bindingPatternClass, "getVariable"); - } - return (VariableTree) invokeNonNullResult(GET_VARIABLE, bindingPatternTree); - } + /** Don't use. */ + private BindingPatternUtils() { + throw new AssertionError("Cannot be instantiated."); } - /** Utility methods for accessing {@code CaseTree} methods. */ - public static class CaseUtils { + /** The {@code BindingPatternTree.getVariable} method for Java 16 and higher; null otherwise. */ + private static @Nullable Method GET_VARIABLE = null; - /** Don't use. */ - private CaseUtils() { - throw new AssertionError("Cannot be instantiated."); - } + /** + * Returns the binding variable of {@code bindingPatternTree}. + * + * @param bindingPatternTree the BindingPatternTree whose binding variable is returned + * @return the binding variable of {@code bindingPatternTree} + */ + public static VariableTree getVariable(Tree bindingPatternTree) { + assertVersionAtLeast(16); + if (GET_VARIABLE == null) { + Class bindingPatternClass = classForName("com.sun.source.tree.BindingPatternTree"); + GET_VARIABLE = getMethod(bindingPatternClass, "getVariable"); + } + return (VariableTree) invokeNonNullResult(GET_VARIABLE, bindingPatternTree); + } + } - /** The {@code CaseTree.getExpressions} method for Java 12 and higher; null otherwise. */ - private static @Nullable Method GET_EXPRESSIONS = null; + /** Utility methods for accessing {@code CaseTree} methods. */ + public static class CaseUtils { - /** The {@code CaseTree.getBody} method for Java 12 and higher; null otherwise. */ - private static @Nullable Method GET_BODY = null; + /** Don't use. */ + private CaseUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** The {@code CaseTree.getKind()} method for Java 12 and higher; null otherwise. */ - private static @Nullable Method GET_KIND = null; + /** The {@code CaseTree.getExpressions} method for Java 12 and higher; null otherwise. */ + private static @Nullable Method GET_EXPRESSIONS = null; - /** The {@code CaseTree.getLabels} method for Java 21 and higher; null otherwise. */ - private static @Nullable Method GET_LABELS = null; + /** The {@code CaseTree.getBody} method for Java 12 and higher; null otherwise. */ + private static @Nullable Method GET_BODY = null; - /** The {@code CaseTree.getGuard} method for Java 21 and higher; null otherwise. */ - private static @Nullable Method GET_GUARD = null; + /** The {@code CaseTree.getKind()} method for Java 12 and higher; null otherwise. */ + private static @Nullable Method GET_KIND = null; - /** - * Returns true if this is a case rule (as opposed to a case statement). - * - * @param caseTree a case tree - * @return true if {@code caseTree} is a case rule - */ - public static boolean isCaseRule(CaseTree caseTree) { - if (sourceVersionNumber < 12) { - return false; - } + /** The {@code CaseTree.getLabels} method for Java 21 and higher; null otherwise. */ + private static @Nullable Method GET_LABELS = null; - if (GET_KIND == null) { - GET_KIND = getMethod(CaseTree.class, "getCaseKind"); - } - Enum kind = (Enum) invokeNonNullResult(GET_KIND, caseTree); - return kind.name().contentEquals("RULE"); - } + /** The {@code CaseTree.getGuard} method for Java 21 and higher; null otherwise. */ + private static @Nullable Method GET_GUARD = null; - /** - * Returns the body of the case statement if it is of the form {@code case -> - * }. This method should only be called if {@link CaseTree#getStatements()} - * returns null. - * - * @param caseTree the case expression to get the body from - * @return the body of the case tree - */ - public static @Nullable Tree getBody(CaseTree caseTree) { - assertVersionAtLeast(12); - if (GET_BODY == null) { - GET_BODY = getMethod(CaseTree.class, "getBody"); - } - return (Tree) invoke(GET_BODY, caseTree); - } + /** + * Returns true if this is a case rule (as opposed to a case statement). + * + * @param caseTree a case tree + * @return true if {@code caseTree} is a case rule + */ + public static boolean isCaseRule(CaseTree caseTree) { + if (sourceVersionNumber < 12) { + return false; + } + + if (GET_KIND == null) { + GET_KIND = getMethod(CaseTree.class, "getCaseKind"); + } + Enum kind = (Enum) invokeNonNullResult(GET_KIND, caseTree); + return kind.name().contentEquals("RULE"); + } - /** - * Returns true if this is the default case for a switch statement or expression. (Also, - * returns true if {@code caseTree} is {@code case null, default:}.) - * - * @param caseTree a case tree - * @return true if {@code caseTree} is the default case for a switch statement or expression - */ - public static boolean isDefaultCaseTree(CaseTree caseTree) { - if (sourceVersionNumber >= 21) { - for (Tree label : getLabels(caseTree, true)) { - if (isDefaultCaseLabelTree(label)) { - return true; - } - } - return false; - } else { - return getExpressions(caseTree).isEmpty(); - } - } + /** + * Returns the body of the case statement if it is of the form {@code case -> + * }. This method should only be called if {@link CaseTree#getStatements()} returns + * null. + * + * @param caseTree the case expression to get the body from + * @return the body of the case tree + */ + public static @Nullable Tree getBody(CaseTree caseTree) { + assertVersionAtLeast(12); + if (GET_BODY == null) { + GET_BODY = getMethod(CaseTree.class, "getBody"); + } + return (Tree) invoke(GET_BODY, caseTree); + } - /** - * Returns true if {@code tree} is a {@code DefaultCaseLabelTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code DefaultCaseLabelTree} - */ - public static boolean isDefaultCaseLabelTree(Tree tree) { - return tree.getKind().name().contentEquals("DEFAULT_CASE_LABEL"); - } + /** + * Returns true if this is the default case for a switch statement or expression. (Also, returns + * true if {@code caseTree} is {@code case null, default:}.) + * + * @param caseTree a case tree + * @return true if {@code caseTree} is the default case for a switch statement or expression + */ + public static boolean isDefaultCaseTree(CaseTree caseTree) { + if (sourceVersionNumber >= 21) { + for (Tree label : getLabels(caseTree, true)) { + if (isDefaultCaseLabelTree(label)) { + return true; + } + } + return false; + } else { + return getExpressions(caseTree).isEmpty(); + } + } - /** - * Get the list of labels from a case expression. For {@code default}, this is empty. For - * {@code case null, default}, the list contains {@code null}. Otherwise, in JDK 11 and - * earlier, this is a list of a single expression tree. In JDK 12+, the list may have - * multiple expression trees. In JDK 21+, the list might contain a single pattern tree. - * - * @param caseTree the case expression to get the labels from - * @return the list of case labels in the case - */ - public static List getLabels(CaseTree caseTree) { - return getLabels(caseTree, false); - } + /** + * Returns true if {@code tree} is a {@code DefaultCaseLabelTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code DefaultCaseLabelTree} + */ + public static boolean isDefaultCaseLabelTree(Tree tree) { + return tree.getKind().name().contentEquals("DEFAULT_CASE_LABEL"); + } - /** - * Get the list of labels from a case expression. - * - *

          For JDKs before 21, if {@code caseTree} is the default case, then the returned list is - * empty. - * - *

          For 21+ JDK, if {@code useDefaultCaseLabelTree} is false, then if {@code caseTree} is - * the default case or {@code case null, default}, then the returned list is empty. If - * {@code useDefaultCaseLabelTree} is true, then if {@code caseTree} is the default case the - * returned contains just a {@code DefaultCaseLabelTree}. If {@code useDefaultCaseLabelTree} - * is false, then if {@code caseTree} is {@code case null, default} the returned list is a - * {@code DefaultCaseLabelTree} and the expression tree for {@code null}. - * - *

          Otherwise, in JDK 11 and earlier, this is a list of a single expression tree. In JDK - * 12+, the list may have multiple expression trees. In JDK 21+, the list might contain a - * single pattern tree. - * - * @param caseTree the case expression to get the labels from - * @param useDefaultCaseLabelTree weather the result should contain a {@code - * DefaultCaseLabelTree}. - * @return the list of case labels in the case - */ - private static List getLabels( - CaseTree caseTree, boolean useDefaultCaseLabelTree) { - if (sourceVersionNumber >= 21) { - if (GET_LABELS == null) { - GET_LABELS = getMethod(CaseTree.class, "getLabels"); - } - @SuppressWarnings("unchecked") - List caseLabelTrees = - (List) invokeNonNullResult(GET_LABELS, caseTree); - List labels = new ArrayList<>(); - for (Tree caseLabel : caseLabelTrees) { - if (isDefaultCaseLabelTree(caseLabel)) { - if (useDefaultCaseLabelTree) { - labels.add(caseLabel); - } - } else if (ConstantCaseLabelUtils.isConstantCaseLabelTree(caseLabel)) { - labels.add(ConstantCaseLabelUtils.getConstantExpression(caseLabel)); - } else if (PatternCaseLabelUtils.isPatternCaseLabelTree(caseLabel)) { - labels.add(PatternCaseLabelUtils.getPattern(caseLabel)); - } - } - return labels; - } - return getExpressions(caseTree); - } + /** + * Get the list of labels from a case expression. For {@code default}, this is empty. For {@code + * case null, default}, the list contains {@code null}. Otherwise, in JDK 11 and earlier, this + * is a list of a single expression tree. In JDK 12+, the list may have multiple expression + * trees. In JDK 21+, the list might contain a single pattern tree. + * + * @param caseTree the case expression to get the labels from + * @return the list of case labels in the case + */ + public static List getLabels(CaseTree caseTree) { + return getLabels(caseTree, false); + } - /** - * Get the list of expressions from a case expression. For the default case, this is empty. - * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can - * be multiple expressions per case. - * - * @param caseTree the case expression to get the expressions from - * @return the list of expressions in the case - */ - @SuppressWarnings("unchecked") - public static List getExpressions(CaseTree caseTree) { - if (sourceVersionNumber >= 12) { - if (GET_EXPRESSIONS == null) { - GET_EXPRESSIONS = getMethod(CaseTree.class, "getExpressions"); - } - return (List) - invokeNonNullResult(GET_EXPRESSIONS, caseTree); - } - @SuppressWarnings("deprecation") // getExpression is deprecated in Java 21 - ExpressionTree expression = caseTree.getExpression(); - if (expression == null) { - return Collections.emptyList(); - } - return Collections.singletonList(expression); + /** + * Get the list of labels from a case expression. + * + *

          For JDKs before 21, if {@code caseTree} is the default case, then the returned list is + * empty. + * + *

          For 21+ JDK, if {@code useDefaultCaseLabelTree} is false, then if {@code caseTree} is the + * default case or {@code case null, default}, then the returned list is empty. If {@code + * useDefaultCaseLabelTree} is true, then if {@code caseTree} is the default case the returned + * contains just a {@code DefaultCaseLabelTree}. If {@code useDefaultCaseLabelTree} is false, + * then if {@code caseTree} is {@code case null, default} the returned list is a {@code + * DefaultCaseLabelTree} and the expression tree for {@code null}. + * + *

          Otherwise, in JDK 11 and earlier, this is a list of a single expression tree. In JDK 12+, + * the list may have multiple expression trees. In JDK 21+, the list might contain a single + * pattern tree. + * + * @param caseTree the case expression to get the labels from + * @param useDefaultCaseLabelTree weather the result should contain a {@code + * DefaultCaseLabelTree}. + * @return the list of case labels in the case + */ + private static List getLabels( + CaseTree caseTree, boolean useDefaultCaseLabelTree) { + if (sourceVersionNumber >= 21) { + if (GET_LABELS == null) { + GET_LABELS = getMethod(CaseTree.class, "getLabels"); } - - /** - * Returns the guard, the expression after {@code when}, of {@code caseTree}. Wrapper around - * {@code CaseTree#getGuard} that can be called on any version of Java. - * - * @param caseTree the case tree - * @return the guard on the case tree or null if one does not exist - */ - public static @Nullable ExpressionTree getGuard(CaseTree caseTree) { - if (sourceVersionNumber < 21) { - return null; - } - if (GET_GUARD == null) { - GET_GUARD = getMethod(CaseTree.class, "getGuard"); + @SuppressWarnings("unchecked") + List caseLabelTrees = + (List) invokeNonNullResult(GET_LABELS, caseTree); + List labels = new ArrayList<>(); + for (Tree caseLabel : caseLabelTrees) { + if (isDefaultCaseLabelTree(caseLabel)) { + if (useDefaultCaseLabelTree) { + labels.add(caseLabel); } - return (ExpressionTree) invoke(GET_GUARD, caseTree); - } + } else if (ConstantCaseLabelUtils.isConstantCaseLabelTree(caseLabel)) { + labels.add(ConstantCaseLabelUtils.getConstantExpression(caseLabel)); + } else if (PatternCaseLabelUtils.isPatternCaseLabelTree(caseLabel)) { + labels.add(PatternCaseLabelUtils.getPattern(caseLabel)); + } + } + return labels; + } + return getExpressions(caseTree); } - /** Utility methods for accessing {@code ConstantCaseLabelTree} methods. */ - public static class ConstantCaseLabelUtils { - - /** Don't use. */ - private ConstantCaseLabelUtils() { - throw new AssertionError("Cannot be instantiated."); - } - - /** - * The {@code ConstantCaseLabelTree.getConstantExpression} method for Java 21 and higher; - * null otherwise. - */ - private static @Nullable Method GET_CONSTANT_EXPRESSION = null; - - /** - * Returns true if {@code tree} is a {@code ConstantCaseLabelTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code ConstantCaseLabelTree} - */ - public static boolean isConstantCaseLabelTree(Tree tree) { - return tree.getKind().name().contentEquals("CONSTANT_CASE_LABEL"); - } + /** + * Get the list of expressions from a case expression. For the default case, this is empty. + * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can be + * multiple expressions per case. + * + * @param caseTree the case expression to get the expressions from + * @return the list of expressions in the case + */ + @SuppressWarnings("unchecked") + public static List getExpressions(CaseTree caseTree) { + if (sourceVersionNumber >= 12) { + if (GET_EXPRESSIONS == null) { + GET_EXPRESSIONS = getMethod(CaseTree.class, "getExpressions"); + } + return (List) invokeNonNullResult(GET_EXPRESSIONS, caseTree); + } + @SuppressWarnings("deprecation") // getExpression is deprecated in Java 21 + ExpressionTree expression = caseTree.getExpression(); + if (expression == null) { + return Collections.emptyList(); + } + return Collections.singletonList(expression); + } - /** - * Wrapper around {@code ConstantCaseLabelTree#getConstantExpression}. - * - * @param constantCaseLabelTree a ConstantCaseLabelTree tree - * @return the expression in the {@code constantCaseLabelTree} - */ - public static ExpressionTree getConstantExpression(Tree constantCaseLabelTree) { - assertVersionAtLeast(21); - if (GET_CONSTANT_EXPRESSION == null) { - Class constantCaseLabelTreeClass = - classForName("com.sun.source.tree.ConstantCaseLabelTree"); - GET_CONSTANT_EXPRESSION = - getMethod(constantCaseLabelTreeClass, "getConstantExpression"); - } - return (ExpressionTree) - invokeNonNullResult(GET_CONSTANT_EXPRESSION, constantCaseLabelTree); - } + /** + * Returns the guard, the expression after {@code when}, of {@code caseTree}. Wrapper around + * {@code CaseTree#getGuard} that can be called on any version of Java. + * + * @param caseTree the case tree + * @return the guard on the case tree or null if one does not exist + */ + public static @Nullable ExpressionTree getGuard(CaseTree caseTree) { + if (sourceVersionNumber < 21) { + return null; + } + if (GET_GUARD == null) { + GET_GUARD = getMethod(CaseTree.class, "getGuard"); + } + return (ExpressionTree) invoke(GET_GUARD, caseTree); } + } - /** Utility methods for accessing {@code DeconstructionPatternTree} methods. */ - public static class DeconstructionPatternUtils { + /** Utility methods for accessing {@code ConstantCaseLabelTree} methods. */ + public static class ConstantCaseLabelUtils { - /** Don't use. */ - private DeconstructionPatternUtils() { - throw new AssertionError("Cannot be instantiated."); - } + /** Don't use. */ + private ConstantCaseLabelUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** - * The {@code DeconstructionPatternTree.getDeconstructor} method for Java 21 and higher; - * null otherwise. - */ - private static @Nullable Method GET_DECONSTRUCTOR = null; - - /** - * The {@code DeconstructionPatternTree.getNestedPatterns} method for Java 21 and higher; - * null otherwise. - */ - private static @Nullable Method GET_NESTED_PATTERNS = null; - - /** - * Returns the deconstruction type of {@code tree}. Wrapper around {@code - * DeconstructionPatternTree#getDeconstructor}. - * - * @param tree the DeconstructionPatternTree - * @return the deconstructor of {@code DeconstructionPatternTree} - */ - public static ExpressionTree getDeconstructor(Tree tree) { - assertVersionAtLeast(21); - if (GET_DECONSTRUCTOR == null) { - Class deconstructionPatternClass = - classForName("com.sun.source.tree.DeconstructionPatternTree"); - GET_DECONSTRUCTOR = getMethod(deconstructionPatternClass, "getDeconstructor"); - } - return (ExpressionTree) invokeNonNullResult(GET_DECONSTRUCTOR, tree); - } + /** + * The {@code ConstantCaseLabelTree.getConstantExpression} method for Java 21 and higher; null + * otherwise. + */ + private static @Nullable Method GET_CONSTANT_EXPRESSION = null; - /** - * Wrapper around {@code DeconstructionPatternTree#getNestedPatterns}. - * - * @param tree the DeconstructionPatternTree - * @return the nested patterns of {@code DeconstructionPatternTree} - */ - @SuppressWarnings("unchecked") - public static List getNestedPatterns(Tree tree) { - assertVersionAtLeast(21); - if (GET_NESTED_PATTERNS == null) { - Class deconstructionPatternClass = - classForName("com.sun.source.tree.DeconstructionPatternTree"); - GET_NESTED_PATTERNS = getMethod(deconstructionPatternClass, "getNestedPatterns"); - } - return (List) invokeNonNullResult(GET_NESTED_PATTERNS, tree); - } + /** + * Returns true if {@code tree} is a {@code ConstantCaseLabelTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code ConstantCaseLabelTree} + */ + public static boolean isConstantCaseLabelTree(Tree tree) { + return tree.getKind().name().contentEquals("CONSTANT_CASE_LABEL"); } - /** Utility methods for accessing {@code PatternCaseLabelTree} methods. */ - public static class PatternCaseLabelUtils { - - /** Don't use. */ - private PatternCaseLabelUtils() { - throw new AssertionError("Cannot be instantiated."); - } + /** + * Wrapper around {@code ConstantCaseLabelTree#getConstantExpression}. + * + * @param constantCaseLabelTree a ConstantCaseLabelTree tree + * @return the expression in the {@code constantCaseLabelTree} + */ + public static ExpressionTree getConstantExpression(Tree constantCaseLabelTree) { + assertVersionAtLeast(21); + if (GET_CONSTANT_EXPRESSION == null) { + Class constantCaseLabelTreeClass = + classForName("com.sun.source.tree.ConstantCaseLabelTree"); + GET_CONSTANT_EXPRESSION = getMethod(constantCaseLabelTreeClass, "getConstantExpression"); + } + return (ExpressionTree) invokeNonNullResult(GET_CONSTANT_EXPRESSION, constantCaseLabelTree); + } + } - /** The PatternCaseLabelTree.getPattern method for Java 21 and higher; null otherwise. */ - private static @Nullable Method GET_PATTERN = null; - - /** - * Returns whether {@code tree} is a {@code PatternCaseLabelTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code PatternCaseLabelTree} - */ - public static boolean isPatternCaseLabelTree(Tree tree) { - return tree.getKind().name().contentEquals("PATTERN_CASE_LABEL"); - } + /** Utility methods for accessing {@code DeconstructionPatternTree} methods. */ + public static class DeconstructionPatternUtils { - /** - * Wrapper around {@code PatternCaseLabelTree#getPattern}. - * - * @param patternCaseLabelTree a PatternCaseLabelTree tree - * @return the {@code PatternTree} in the {@code patternCaseLabelTree} - */ - public static Tree getPattern(Tree patternCaseLabelTree) { - assertVersionAtLeast(21); - if (GET_PATTERN == null) { - Class patternCaseLabelClass = - classForName("com.sun.source.tree.PatternCaseLabelTree"); - GET_PATTERN = getMethod(patternCaseLabelClass, "getPattern"); - } - return (Tree) invokeNonNullResult(GET_PATTERN, patternCaseLabelTree); - } + /** Don't use. */ + private DeconstructionPatternUtils() { + throw new AssertionError("Cannot be instantiated."); } - /** Utility methods for accessing {@code SwitchExpressionTree} methods. */ - public static class SwitchExpressionUtils { - - /** Don't use. */ - private SwitchExpressionUtils() { - throw new AssertionError("Cannot be instantiated."); - } + /** + * The {@code DeconstructionPatternTree.getDeconstructor} method for Java 21 and higher; null + * otherwise. + */ + private static @Nullable Method GET_DECONSTRUCTOR = null; - /** - * The {@code SwitchExpressionTree.getExpression} method for Java 12 and higher; null - * otherwise. - */ - private static @Nullable Method GET_EXPRESSION = null; - - /** - * The {@code SwitchExpressionTree.getCases} method for Java 12 and higher; null otherwise. - */ - private static @Nullable Method GET_CASES = null; - - /** - * Returns the cases of {@code switchExpressionTree}. For example - * - *

          -         *   switch ( expression ) {
          -         *     cases
          -         *   }
          -         * 
          - * - * @param switchExpressionTree the switch expression whose cases are returned - * @return the cases of {@code switchExpressionTree} - */ - @SuppressWarnings("unchecked") - public static List getCases(Tree switchExpressionTree) { - assertVersionAtLeast(12); - if (GET_CASES == null) { - Class switchExpressionClass = - classForName("com.sun.source.tree.SwitchExpressionTree"); - GET_CASES = getMethod(switchExpressionClass, "getCases"); - } - return (List) invokeNonNullResult(GET_CASES, switchExpressionTree); - } + /** + * The {@code DeconstructionPatternTree.getNestedPatterns} method for Java 21 and higher; null + * otherwise. + */ + private static @Nullable Method GET_NESTED_PATTERNS = null; - /** - * Returns the selector expression of {@code switchExpressionTree}. For example - * - *
          -         *   switch ( expression ) { ... }
          -         * 
          - * - * @param switchExpressionTree the switch expression whose selector expression is returned - * @return the selector expression of {@code switchExpressionTree} - */ - public static ExpressionTree getExpression(Tree switchExpressionTree) { - assertVersionAtLeast(12); - if (GET_EXPRESSION == null) { - Class switchExpressionClass = - classForName("com.sun.source.tree.SwitchExpressionTree"); - GET_EXPRESSION = getMethod(switchExpressionClass, "getExpression"); - } - return (ExpressionTree) invokeNonNullResult(GET_EXPRESSION, switchExpressionTree); - } + /** + * Returns the deconstruction type of {@code tree}. Wrapper around {@code + * DeconstructionPatternTree#getDeconstructor}. + * + * @param tree the DeconstructionPatternTree + * @return the deconstructor of {@code DeconstructionPatternTree} + */ + public static ExpressionTree getDeconstructor(Tree tree) { + assertVersionAtLeast(21); + if (GET_DECONSTRUCTOR == null) { + Class deconstructionPatternClass = + classForName("com.sun.source.tree.DeconstructionPatternTree"); + GET_DECONSTRUCTOR = getMethod(deconstructionPatternClass, "getDeconstructor"); + } + return (ExpressionTree) invokeNonNullResult(GET_DECONSTRUCTOR, tree); } - /** Utility methods for accessing {@code YieldTree} methods. */ - public static class YieldUtils { + /** + * Wrapper around {@code DeconstructionPatternTree#getNestedPatterns}. + * + * @param tree the DeconstructionPatternTree + * @return the nested patterns of {@code DeconstructionPatternTree} + */ + @SuppressWarnings("unchecked") + public static List getNestedPatterns(Tree tree) { + assertVersionAtLeast(21); + if (GET_NESTED_PATTERNS == null) { + Class deconstructionPatternClass = + classForName("com.sun.source.tree.DeconstructionPatternTree"); + GET_NESTED_PATTERNS = getMethod(deconstructionPatternClass, "getNestedPatterns"); + } + return (List) invokeNonNullResult(GET_NESTED_PATTERNS, tree); + } + } - /** Don't use. */ - private YieldUtils() { - throw new AssertionError("Cannot be instantiated."); - } + /** Utility methods for accessing {@code PatternCaseLabelTree} methods. */ + public static class PatternCaseLabelUtils { - /** The {@code YieldTree.getValue} method for Java 13 and higher; null otherwise. */ - private static @Nullable Method GET_VALUE = null; - - /** - * Returns the value (expression) for {@code yieldTree}. - * - * @param yieldTree the yield tree - * @return the value (expression) for {@code yieldTree} - */ - public static ExpressionTree getValue(Tree yieldTree) { - assertVersionAtLeast(13); - if (GET_VALUE == null) { - Class yieldTreeClass = classForName("com.sun.source.tree.YieldTree"); - GET_VALUE = getMethod(yieldTreeClass, "getValue"); - } - return (ExpressionTree) invokeNonNullResult(GET_VALUE, yieldTree); - } + /** Don't use. */ + private PatternCaseLabelUtils() { + throw new AssertionError("Cannot be instantiated."); } - /** Utility methods for accessing {@code InstanceOfTree} methods. */ - public static class InstanceOfUtils { + /** The PatternCaseLabelTree.getPattern method for Java 21 and higher; null otherwise. */ + private static @Nullable Method GET_PATTERN = null; - /** Don't use. */ - private InstanceOfUtils() { - throw new AssertionError("Cannot be instantiated."); - } - - /** The {@code InstanceOfTree.getPattern} method for Java 16 and higher; null otherwise. */ - private static @Nullable Method GET_PATTERN = null; - - /** - * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does - * not have a pattern, including if the JDK version does not support instance-of patterns. - * - * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned - * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist - */ - @Pure - public static @Nullable Tree getPattern(InstanceOfTree instanceOfTree) { - if (sourceVersionNumber < 16) { - return null; - } - if (GET_PATTERN == null) { - GET_PATTERN = getMethod(InstanceOfTree.class, "getPattern"); - } - return (Tree) invoke(GET_PATTERN, instanceOfTree); - } + /** + * Returns whether {@code tree} is a {@code PatternCaseLabelTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code PatternCaseLabelTree} + */ + public static boolean isPatternCaseLabelTree(Tree tree) { + return tree.getKind().name().contentEquals("PATTERN_CASE_LABEL"); } /** - * Asserts that the latest source version is at least {@code version}. + * Wrapper around {@code PatternCaseLabelTree#getPattern}. * - * @param version version to check - * @throws BugInCF if the latest version is smaller than {@code version} + * @param patternCaseLabelTree a PatternCaseLabelTree tree + * @return the {@code PatternTree} in the {@code patternCaseLabelTree} */ - private static void assertVersionAtLeast(int version) { - if (sourceVersionNumber < version) { - throw new BugInCF( - "Method call requires at least Java version %s, but the current version is %s", - version, sourceVersionNumber); - } + public static Tree getPattern(Tree patternCaseLabelTree) { + assertVersionAtLeast(21); + if (GET_PATTERN == null) { + Class patternCaseLabelClass = classForName("com.sun.source.tree.PatternCaseLabelTree"); + GET_PATTERN = getMethod(patternCaseLabelClass, "getPattern"); + } + return (Tree) invokeNonNullResult(GET_PATTERN, patternCaseLabelTree); + } + } + + /** Utility methods for accessing {@code SwitchExpressionTree} methods. */ + public static class SwitchExpressionUtils { + + /** Don't use. */ + private SwitchExpressionUtils() { + throw new AssertionError("Cannot be instantiated."); } /** - * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as - * {@code BugInCF} exceptions. If the results is {@code null} a {@code BugInCF} is thrown. + * The {@code SwitchExpressionTree.getExpression} method for Java 12 and higher; null otherwise. + */ + private static @Nullable Method GET_EXPRESSION = null; + + /** The {@code SwitchExpressionTree.getCases} method for Java 12 and higher; null otherwise. */ + private static @Nullable Method GET_CASES = null; + + /** + * Returns the cases of {@code switchExpressionTree}. For example + * + *
          +     *   switch ( expression ) {
          +     *     cases
          +     *   }
          +     * 
          * - * @param method a method - * @param receiver the receiver for the method - * @return the result of invoking {@code method} on {@code receiver} + * @param switchExpressionTree the switch expression whose cases are returned + * @return the cases of {@code switchExpressionTree} */ - private static Object invokeNonNullResult(Method method, Tree receiver) { - Object result = invoke(method, receiver); - if (result != null) { - return result; - } - throw new BugInCF( - "Expected nonnull result for method invocation: %s for tree: %s", - method.getName(), receiver); + @SuppressWarnings("unchecked") + public static List getCases(Tree switchExpressionTree) { + assertVersionAtLeast(12); + if (GET_CASES == null) { + Class switchExpressionClass = classForName("com.sun.source.tree.SwitchExpressionTree"); + GET_CASES = getMethod(switchExpressionClass, "getCases"); + } + return (List) invokeNonNullResult(GET_CASES, switchExpressionTree); } /** - * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as - * {@code BugInCF} exceptions. + * Returns the selector expression of {@code switchExpressionTree}. For example + * + *
          +     *   switch ( expression ) { ... }
          +     * 
          * - * @param method a method - * @param receiver the receiver for the method - * @return the result of invoking {@code method} on {@code receiver} + * @param switchExpressionTree the switch expression whose selector expression is returned + * @return the selector expression of {@code switchExpressionTree} */ - private static @Nullable Object invoke(Method method, Tree receiver) { - try { - return method.invoke(receiver); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new BugInCF( - e, "Reflection failed for method: %s for tree: %s", method.getName(), receiver); - } + public static ExpressionTree getExpression(Tree switchExpressionTree) { + assertVersionAtLeast(12); + if (GET_EXPRESSION == null) { + Class switchExpressionClass = classForName("com.sun.source.tree.SwitchExpressionTree"); + GET_EXPRESSION = getMethod(switchExpressionClass, "getExpression"); + } + return (ExpressionTree) invokeNonNullResult(GET_EXPRESSION, switchExpressionTree); + } + } + + /** Utility methods for accessing {@code YieldTree} methods. */ + public static class YieldUtils { + + /** Don't use. */ + private YieldUtils() { + throw new AssertionError("Cannot be instantiated."); } + /** The {@code YieldTree.getValue} method for Java 13 and higher; null otherwise. */ + private static @Nullable Method GET_VALUE = null; + /** - * Returns the {@link Method} object for the method with name {@code name} in class {@code - * clazz}. Rethrowing any exceptions as {@code BugInCF} exceptions. + * Returns the value (expression) for {@code yieldTree}. * - * @param clazz a class - * @param name a method name - * @return the {@link Method} object for the method with name {@code name} in class {@code - * clazz} + * @param yieldTree the yield tree + * @return the value (expression) for {@code yieldTree} */ - private static Method getMethod(Class clazz, String name) { - try { - return clazz.getMethod(name); - } catch (NoSuchMethodException e) { - throw new BugInCF("Method %s not found in class %s", name, clazz); - } + public static ExpressionTree getValue(Tree yieldTree) { + assertVersionAtLeast(13); + if (GET_VALUE == null) { + Class yieldTreeClass = classForName("com.sun.source.tree.YieldTree"); + GET_VALUE = getMethod(yieldTreeClass, "getValue"); + } + return (ExpressionTree) invokeNonNullResult(GET_VALUE, yieldTree); + } + } + + /** Utility methods for accessing {@code InstanceOfTree} methods. */ + public static class InstanceOfUtils { + + /** Don't use. */ + private InstanceOfUtils() { + throw new AssertionError("Cannot be instantiated."); } + /** The {@code InstanceOfTree.getPattern} method for Java 16 and higher; null otherwise. */ + private static @Nullable Method GET_PATTERN = null; + /** - * Returns the class named {@code name}. Rethrows any exceptions as {@code BugInCF} exceptions. + * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does not + * have a pattern, including if the JDK version does not support instance-of patterns. * - * @param name a class name - * @return the class named {@code name} + * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned + * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist */ - private static Class classForName(@ClassGetName String name) { - try { - return Class.forName(name); - } catch (ClassNotFoundException e) { - throw new BugInCF("Class not found " + name); - } + @Pure + public static @Nullable Tree getPattern(InstanceOfTree instanceOfTree) { + if (sourceVersionNumber < 16) { + return null; + } + if (GET_PATTERN == null) { + GET_PATTERN = getMethod(InstanceOfTree.class, "getPattern"); + } + return (Tree) invoke(GET_PATTERN, instanceOfTree); + } + } + + /** + * Asserts that the latest source version is at least {@code version}. + * + * @param version version to check + * @throws BugInCF if the latest version is smaller than {@code version} + */ + private static void assertVersionAtLeast(int version) { + if (sourceVersionNumber < version) { + throw new BugInCF( + "Method call requires at least Java version %s, but the current version is %s", + version, sourceVersionNumber); + } + } + + /** + * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as {@code + * BugInCF} exceptions. If the results is {@code null} a {@code BugInCF} is thrown. + * + * @param method a method + * @param receiver the receiver for the method + * @return the result of invoking {@code method} on {@code receiver} + */ + private static Object invokeNonNullResult(Method method, Tree receiver) { + Object result = invoke(method, receiver); + if (result != null) { + return result; + } + throw new BugInCF( + "Expected nonnull result for method invocation: %s for tree: %s", + method.getName(), receiver); + } + + /** + * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as {@code + * BugInCF} exceptions. + * + * @param method a method + * @param receiver the receiver for the method + * @return the result of invoking {@code method} on {@code receiver} + */ + private static @Nullable Object invoke(Method method, Tree receiver) { + try { + return method.invoke(receiver); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new BugInCF( + e, "Reflection failed for method: %s for tree: %s", method.getName(), receiver); + } + } + + /** + * Returns the {@link Method} object for the method with name {@code name} in class {@code clazz}. + * Rethrowing any exceptions as {@code BugInCF} exceptions. + * + * @param clazz a class + * @param name a method name + * @return the {@link Method} object for the method with name {@code name} in class {@code clazz} + */ + private static Method getMethod(Class clazz, String name) { + try { + return clazz.getMethod(name); + } catch (NoSuchMethodException e) { + throw new BugInCF("Method %s not found in class %s", name, clazz); + } + } + + /** + * Returns the class named {@code name}. Rethrows any exceptions as {@code BugInCF} exceptions. + * + * @param name a class name + * @return the class named {@code name} + */ + private static Class classForName(@ClassGetName String name) { + try { + return Class.forName(name); + } catch (ClassNotFoundException e) { + throw new BugInCF("Class not found " + name); } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java index e62289abf47..844a182660b 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java @@ -11,13 +11,9 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Pair; - -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.Arrays; import java.util.Iterator; import java.util.Map; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -31,6 +27,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.NonNull; /** * A collection of helper methods related to type annotation handling. @@ -39,646 +36,629 @@ */ public class TypeAnnotationUtils { - // Class cannot be instantiated. - private TypeAnnotationUtils() { - throw new AssertionError("Class TypeAnnotationUtils cannot be instantiated."); + // Class cannot be instantiated. + private TypeAnnotationUtils() { + throw new AssertionError("Class TypeAnnotationUtils cannot be instantiated."); + } + + /** + * Check whether a TypeCompound is contained in a list of TypeCompounds. + * + * @param list the input list of TypeCompounds + * @param tc the TypeCompound to find + * @param types type utilities + * @return true, iff a TypeCompound equal to tc is contained in list + */ + public static boolean isTypeCompoundContained( + List list, TypeCompound tc, Types types) { + for (Attribute.TypeCompound rawat : list) { + if (typeCompoundEquals(rawat, tc, types)) { + return true; + } } - - /** - * Check whether a TypeCompound is contained in a list of TypeCompounds. - * - * @param list the input list of TypeCompounds - * @param tc the TypeCompound to find - * @param types type utilities - * @return true, iff a TypeCompound equal to tc is contained in list - */ - public static boolean isTypeCompoundContained( - List list, TypeCompound tc, Types types) { - for (Attribute.TypeCompound rawat : list) { - if (typeCompoundEquals(rawat, tc, types)) { - return true; - } - } - return false; + return false; + } + + /** + * Compares two TypeCompound objects (e.g., annotations). + * + * @param tc1 the first TypeCompound to compare + * @param tc2 the second TypeCompound to compare + * @param types type utilities + * @return true if the TypeCompounds represent the same compound element value + */ + private static boolean typeCompoundEquals(TypeCompound tc1, TypeCompound tc2, Types types) { + // For the first conjunct, both of these forms fail in some cases: + // tc1.type == tc2.type + // types.isSameType(tc1.type, tc2.type) + return contentEquals(tc1.type.tsym.name, tc2.type.tsym.name) + && typeCompoundValuesEquals(tc1.values, tc2.values, types) + && isSameTAPositionExceptTreePos(tc1.position, tc2.position); + } + + /** + * Returns true if the two names represent the same string. + * + * @param n1 the first Name to compare + * @param n2 the second Name to compare + * @return true if the two names represent the same string + */ + @SuppressWarnings( + "interning:unnecessary.equals" // Name is interned within a single instance of javac, + // but call equals anyway out of paranoia. + ) + private static boolean contentEquals(Name n1, Name n2) { + if (n1.getClass() == n2.getClass()) { + return n1.equals(n2); + } else { + // Slightly less efficient because it makes a copy. + return n1.contentEquals(n2); } - - /** - * Compares two TypeCompound objects (e.g., annotations). - * - * @param tc1 the first TypeCompound to compare - * @param tc2 the second TypeCompound to compare - * @param types type utilities - * @return true if the TypeCompounds represent the same compound element value - */ - private static boolean typeCompoundEquals(TypeCompound tc1, TypeCompound tc2, Types types) { - // For the first conjunct, both of these forms fail in some cases: - // tc1.type == tc2.type - // types.isSameType(tc1.type, tc2.type) - return contentEquals(tc1.type.tsym.name, tc2.type.tsym.name) - && typeCompoundValuesEquals(tc1.values, tc2.values, types) - && isSameTAPositionExceptTreePos(tc1.position, tc2.position); + } + + /** + * Compares the {@code values} fields of two TypeCompound objects (e.g., annotations). Is more + * lenient than {@code List.equals}, which uses {@code Object.equals} on list elements. + * + * @param values1 the first {@code values} field + * @param values2 the second {@code values} field + * @param types type utilities + * @return true if the two {@code values} fields represent the same name-to-value mapping, in the + * same order + */ + @SuppressWarnings("InvalidParam") // Error Prone tries to be clever, but it is not + private static boolean typeCompoundValuesEquals( + List> values1, + List> values2, + Types types) { + if (values1.size() != values2.size()) { + return false; } - /** - * Returns true if the two names represent the same string. - * - * @param n1 the first Name to compare - * @param n2 the second Name to compare - * @return true if the two names represent the same string - */ - @SuppressWarnings( - "interning:unnecessary.equals" // Name is interned within a single instance of javac, - // but call equals anyway out of paranoia. - ) - private static boolean contentEquals(Name n1, Name n2) { - if (n1.getClass() == n2.getClass()) { - return n1.equals(n2); - } else { - // Slightly less efficient because it makes a copy. - return n1.contentEquals(n2); - } + for (Iterator> iter1 = values1.iterator(), + iter2 = values2.iterator(); + iter1.hasNext(); ) { + Pair pair1 = iter1.next(); + Pair pair2 = iter2.next(); + if (!(pair1.fst.equals(pair2.fst) && attributeEquals(pair1.snd, pair2.snd, types))) { + return false; + } } - - /** - * Compares the {@code values} fields of two TypeCompound objects (e.g., annotations). Is more - * lenient than {@code List.equals}, which uses {@code Object.equals} on list elements. - * - * @param values1 the first {@code values} field - * @param values2 the second {@code values} field - * @param types type utilities - * @return true if the two {@code values} fields represent the same name-to-value mapping, in - * the same order - */ - @SuppressWarnings("InvalidParam") // Error Prone tries to be clever, but it is not - private static boolean typeCompoundValuesEquals( - List> values1, - List> values2, - Types types) { - if (values1.size() != values2.size()) { - return false; - } - - for (Iterator> iter1 = values1.iterator(), - iter2 = values2.iterator(); - iter1.hasNext(); ) { - Pair pair1 = iter1.next(); - Pair pair2 = iter2.next(); - if (!(pair1.fst.equals(pair2.fst) && attributeEquals(pair1.snd, pair2.snd, types))) { - return false; - } + return true; + } + + /** + * Compares two attributes. Is more lenient for constants than {@code Attribute.equals}, which is + * reference equality. + * + * @param a1 the first attribute to compare + * @param a2 the second attribute to compare + * @param types type utilities + * @return true if the two attributes are the same + */ + private static boolean attributeEquals(Attribute a1, Attribute a2, Types types) { + if (a1 instanceof Attribute.Array && a2 instanceof Attribute.Array) { + List list1 = ((Attribute.Array) a1).getValue(); + List list2 = ((Attribute.Array) a2).getValue(); + if (list1.size() != list2.size()) { + return false; + } + // This requires the array elements to be in the same order. Is that the right thing? + for (int i = 0; i < list1.size(); i++) { + if (!attributeEquals(list1.get(i), list2.get(i), types)) { + return false; } - return true; - } - - /** - * Compares two attributes. Is more lenient for constants than {@code Attribute.equals}, which - * is reference equality. - * - * @param a1 the first attribute to compare - * @param a2 the second attribute to compare - * @param types type utilities - * @return true if the two attributes are the same - */ - private static boolean attributeEquals(Attribute a1, Attribute a2, Types types) { - if (a1 instanceof Attribute.Array && a2 instanceof Attribute.Array) { - List list1 = ((Attribute.Array) a1).getValue(); - List list2 = ((Attribute.Array) a2).getValue(); - if (list1.size() != list2.size()) { - return false; - } - // This requires the array elements to be in the same order. Is that the right thing? - for (int i = 0; i < list1.size(); i++) { - if (!attributeEquals(list1.get(i), list2.get(i), types)) { - return false; - } - } - return true; - } else if (a1 instanceof Attribute.Class && a2 instanceof Attribute.Class) { - Type t1 = ((Attribute.Class) a1).getValue(); - Type t2 = ((Attribute.Class) a2).getValue(); - return types.isSameType(t1, t2); - } else if (a1 instanceof Attribute.Constant && a2 instanceof Attribute.Constant) { - Object v1 = ((Attribute.Constant) a1).getValue(); - Object v2 = ((Attribute.Constant) a2).getValue(); - return v1.equals(v2); - } else if (a1 instanceof Attribute.Compound && a2 instanceof Attribute.Compound) { - // The annotation value is another annotation. `a1` and `a2` implement - // AnnotationMirror. - DeclaredType t1 = ((Attribute.Compound) a1).getAnnotationType(); - DeclaredType t2 = ((Attribute.Compound) a2).getAnnotationType(); - if (!types.isSameType(t1, t2)) { - return false; - } - Map map1 = ((Attribute.Compound) a1).getElementValues(); - Map map2 = ((Attribute.Compound) a2).getElementValues(); - // Is this test, which uses equals() for the keys, too strict? - if (!map1.keySet().equals(map2.keySet())) { - return false; - } - for (Map.Entry map1entry : map1.entrySet()) { - Attribute attr1 = map1entry.getValue(); - @SuppressWarnings( - "nullness:assignment.type.incompatible") // same keys in map1 & map2 - @NonNull Attribute attr2 = map2.get(map1entry.getKey()); - if (!attributeEquals(attr1, attr2, types)) { - return false; - } - } - return true; - } else if (a1 instanceof Attribute.Enum && a2 instanceof Attribute.Enum) { - Symbol.VarSymbol s1 = ((Attribute.Enum) a1).getValue(); - Symbol.VarSymbol s2 = ((Attribute.Enum) a2).getValue(); - // VarSymbol.equals() is reference equality. - return s1.equals(s2) || s1.toString().equals(s2.toString()); - } else if (a1 instanceof Attribute.Error && a2 instanceof Attribute.Error) { - String s1 = ((Attribute.Error) a1).getValue(); - String s2 = ((Attribute.Error) a2).getValue(); - return s1.equals(s2); - } else { - return a1.equals(a2); + } + return true; + } else if (a1 instanceof Attribute.Class && a2 instanceof Attribute.Class) { + Type t1 = ((Attribute.Class) a1).getValue(); + Type t2 = ((Attribute.Class) a2).getValue(); + return types.isSameType(t1, t2); + } else if (a1 instanceof Attribute.Constant && a2 instanceof Attribute.Constant) { + Object v1 = ((Attribute.Constant) a1).getValue(); + Object v2 = ((Attribute.Constant) a2).getValue(); + return v1.equals(v2); + } else if (a1 instanceof Attribute.Compound && a2 instanceof Attribute.Compound) { + // The annotation value is another annotation. `a1` and `a2` implement + // AnnotationMirror. + DeclaredType t1 = ((Attribute.Compound) a1).getAnnotationType(); + DeclaredType t2 = ((Attribute.Compound) a2).getAnnotationType(); + if (!types.isSameType(t1, t2)) { + return false; + } + Map map1 = ((Attribute.Compound) a1).getElementValues(); + Map map2 = ((Attribute.Compound) a2).getElementValues(); + // Is this test, which uses equals() for the keys, too strict? + if (!map1.keySet().equals(map2.keySet())) { + return false; + } + for (Map.Entry map1entry : map1.entrySet()) { + Attribute attr1 = map1entry.getValue(); + @SuppressWarnings("nullness:assignment.type.incompatible") // same keys in map1 & map2 + @NonNull Attribute attr2 = map2.get(map1entry.getKey()); + if (!attributeEquals(attr1, attr2, types)) { + return false; } + } + return true; + } else if (a1 instanceof Attribute.Enum && a2 instanceof Attribute.Enum) { + Symbol.VarSymbol s1 = ((Attribute.Enum) a1).getValue(); + Symbol.VarSymbol s2 = ((Attribute.Enum) a2).getValue(); + // VarSymbol.equals() is reference equality. + return s1.equals(s2) || s1.toString().equals(s2.toString()); + } else if (a1 instanceof Attribute.Error && a2 instanceof Attribute.Error) { + String s1 = ((Attribute.Error) a1).getValue(); + String s2 = ((Attribute.Error) a2).getValue(); + return s1.equals(s2); + } else { + return a1.equals(a2); } - - /** - * Compare two TypeAnnotationPositions for equality. - * - * @param p1 the first position - * @param p2 the second position - * @return true, iff the two positions are equal - */ - public static boolean isSameTAPosition(TypeAnnotationPosition p1, TypeAnnotationPosition p2) { - return isSameTAPositionExceptTreePos(p1, p2) && p1.pos == p2.pos; + } + + /** + * Compare two TypeAnnotationPositions for equality. + * + * @param p1 the first position + * @param p2 the second position + * @return true, iff the two positions are equal + */ + public static boolean isSameTAPosition(TypeAnnotationPosition p1, TypeAnnotationPosition p2) { + return isSameTAPositionExceptTreePos(p1, p2) && p1.pos == p2.pos; + } + + /** + * Compare two TypeAnnotationPositions for equality, ignoring the source tree position. + * + * @param p1 the first position + * @param p2 the second position + * @return true, iff the two positions are equal except for the source tree position + */ + @SuppressWarnings("interning:not.interned") // reference equality for onLambda field + public static boolean isSameTAPositionExceptTreePos( + TypeAnnotationPosition p1, TypeAnnotationPosition p2) { + return p1.type == p2.type + && p1.type_index == p2.type_index + && p1.bound_index == p2.bound_index + && p1.onLambda == p2.onLambda + && p1.parameter_index == p2.parameter_index + && p1.isValidOffset == p2.isValidOffset + && p1.offset == p2.offset + && p1.location.equals(p2.location) + && Arrays.equals(p1.lvarIndex, p2.lvarIndex) + && Arrays.equals(p1.lvarLength, p2.lvarLength) + && Arrays.equals(p1.lvarOffset, p2.lvarOffset) + && (!p1.hasExceptionIndex() + || !p2.hasExceptionIndex() + || (p1.getExceptionIndex() == p2.getExceptionIndex())); + } + + /** + * Returns a newly created Attribute.Compound corresponding to an argument AnnotationMirror. + * + * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass + * @return a new Attribute.Compound corresponding to the AnnotationMirror + */ + public static Attribute.Compound createCompoundFromAnnotationMirror( + AnnotationMirror am, ProcessingEnvironment env) { + // Create a new Attribute to match the AnnotationMirror. + List> values = List.nil(); + for (Map.Entry entry : + am.getElementValues().entrySet()) { + Attribute attribute = attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); + values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); } - - /** - * Compare two TypeAnnotationPositions for equality, ignoring the source tree position. - * - * @param p1 the first position - * @param p2 the second position - * @return true, iff the two positions are equal except for the source tree position - */ - @SuppressWarnings("interning:not.interned") // reference equality for onLambda field - public static boolean isSameTAPositionExceptTreePos( - TypeAnnotationPosition p1, TypeAnnotationPosition p2) { - return p1.type == p2.type - && p1.type_index == p2.type_index - && p1.bound_index == p2.bound_index - && p1.onLambda == p2.onLambda - && p1.parameter_index == p2.parameter_index - && p1.isValidOffset == p2.isValidOffset - && p1.offset == p2.offset - && p1.location.equals(p2.location) - && Arrays.equals(p1.lvarIndex, p2.lvarIndex) - && Arrays.equals(p1.lvarLength, p2.lvarLength) - && Arrays.equals(p1.lvarOffset, p2.lvarOffset) - && (!p1.hasExceptionIndex() - || !p2.hasExceptionIndex() - || (p1.getExceptionIndex() == p2.getExceptionIndex())); + return new Attribute.Compound((Type.ClassType) am.getAnnotationType(), values); + } + + /** + * Returns a newly created Attribute.TypeCompound corresponding to an argument AnnotationMirror. + * + * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass + * @param tapos the type annotation position to use + * @return a new Attribute.TypeCompound corresponding to the AnnotationMirror + */ + public static Attribute.TypeCompound createTypeCompoundFromAnnotationMirror( + AnnotationMirror am, TypeAnnotationPosition tapos, ProcessingEnvironment env) { + // Create a new Attribute to match the AnnotationMirror. + List> values = List.nil(); + for (Map.Entry entry : + am.getElementValues().entrySet()) { + Attribute attribute = attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); + values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); } - - /** - * Returns a newly created Attribute.Compound corresponding to an argument AnnotationMirror. - * - * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass - * @return a new Attribute.Compound corresponding to the AnnotationMirror - */ - public static Attribute.Compound createCompoundFromAnnotationMirror( - AnnotationMirror am, ProcessingEnvironment env) { - // Create a new Attribute to match the AnnotationMirror. - List> values = List.nil(); - for (Map.Entry entry : - am.getElementValues().entrySet()) { - Attribute attribute = - attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); - values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); - } - return new Attribute.Compound((Type.ClassType) am.getAnnotationType(), values); + return new Attribute.TypeCompound((Type.ClassType) am.getAnnotationType(), values, tapos); + } + + /** + * Returns a newly created Attribute corresponding to an argument AnnotationValue. + * + * @param meth the ExecutableElement that is assigned the value, needed for empty arrays + * @param av an AnnotationValue, which may be part of an AST or an internally created subclass + * @return a new Attribute corresponding to the AnnotationValue + */ + public static Attribute attributeFromAnnotationValue( + ExecutableElement meth, AnnotationValue av, ProcessingEnvironment env) { + return av.accept(new AttributeCreator(env, meth), null); + } + + private static class AttributeCreator implements AnnotationValueVisitor { + private final ProcessingEnvironment processingEnv; + private final Types modelTypes; + private final Elements elements; + private final com.sun.tools.javac.code.Types javacTypes; + + private final ExecutableElement meth; + + public AttributeCreator(ProcessingEnvironment env, ExecutableElement meth) { + this.processingEnv = env; + Context context = ((JavacProcessingEnvironment) env).getContext(); + this.elements = env.getElementUtils(); + this.modelTypes = env.getTypeUtils(); + this.javacTypes = com.sun.tools.javac.code.Types.instance(context); + + this.meth = meth; } - /** - * Returns a newly created Attribute.TypeCompound corresponding to an argument AnnotationMirror. - * - * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass - * @param tapos the type annotation position to use - * @return a new Attribute.TypeCompound corresponding to the AnnotationMirror - */ - public static Attribute.TypeCompound createTypeCompoundFromAnnotationMirror( - AnnotationMirror am, TypeAnnotationPosition tapos, ProcessingEnvironment env) { - // Create a new Attribute to match the AnnotationMirror. - List> values = List.nil(); - for (Map.Entry entry : - am.getElementValues().entrySet()) { - Attribute attribute = - attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); - values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); - } - return new Attribute.TypeCompound((Type.ClassType) am.getAnnotationType(), values, tapos); + @Override + public Attribute visit(AnnotationValue av, Void p) { + return av.accept(this, p); } - /** - * Returns a newly created Attribute corresponding to an argument AnnotationValue. - * - * @param meth the ExecutableElement that is assigned the value, needed for empty arrays - * @param av an AnnotationValue, which may be part of an AST or an internally created subclass - * @return a new Attribute corresponding to the AnnotationValue - */ - public static Attribute attributeFromAnnotationValue( - ExecutableElement meth, AnnotationValue av, ProcessingEnvironment env) { - return av.accept(new AttributeCreator(env, meth), null); + @Override + public Attribute visit(AnnotationValue av) { + return visit(av, null); } - private static class AttributeCreator implements AnnotationValueVisitor { - private final ProcessingEnvironment processingEnv; - private final Types modelTypes; - private final Elements elements; - private final com.sun.tools.javac.code.Types javacTypes; - - private final ExecutableElement meth; - - public AttributeCreator(ProcessingEnvironment env, ExecutableElement meth) { - this.processingEnv = env; - Context context = ((JavacProcessingEnvironment) env).getContext(); - this.elements = env.getElementUtils(); - this.modelTypes = env.getTypeUtils(); - this.javacTypes = com.sun.tools.javac.code.Types.instance(context); - - this.meth = meth; - } - - @Override - public Attribute visit(AnnotationValue av, Void p) { - return av.accept(this, p); - } - - @Override - public Attribute visit(AnnotationValue av) { - return visit(av, null); - } - - @Override - public Attribute visitBoolean(boolean b, Void p) { - TypeMirror booleanType = modelTypes.getPrimitiveType(TypeKind.BOOLEAN); - return new Attribute.Constant((Type) booleanType, b ? 1 : 0); - } - - @Override - public Attribute visitByte(byte b, Void p) { - TypeMirror byteType = modelTypes.getPrimitiveType(TypeKind.BYTE); - return new Attribute.Constant((Type) byteType, b); - } - - @Override - public Attribute visitChar(char c, Void p) { - TypeMirror charType = modelTypes.getPrimitiveType(TypeKind.CHAR); - return new Attribute.Constant((Type) charType, c); - } - - @Override - public Attribute visitDouble(double d, Void p) { - TypeMirror doubleType = modelTypes.getPrimitiveType(TypeKind.DOUBLE); - return new Attribute.Constant((Type) doubleType, d); - } - - @Override - public Attribute visitFloat(float f, Void p) { - TypeMirror floatType = modelTypes.getPrimitiveType(TypeKind.FLOAT); - return new Attribute.Constant((Type) floatType, f); - } - - @Override - public Attribute visitInt(int i, Void p) { - TypeMirror intType = modelTypes.getPrimitiveType(TypeKind.INT); - return new Attribute.Constant((Type) intType, i); - } - - @Override - public Attribute visitLong(long i, Void p) { - TypeMirror longType = modelTypes.getPrimitiveType(TypeKind.LONG); - return new Attribute.Constant((Type) longType, i); - } - - @Override - public Attribute visitShort(short s, Void p) { - TypeMirror shortType = modelTypes.getPrimitiveType(TypeKind.SHORT); - return new Attribute.Constant((Type) shortType, s); - } - - @Override - public Attribute visitString(String s, Void p) { - TypeMirror stringType = elements.getTypeElement("java.lang.String").asType(); - return new Attribute.Constant((Type) stringType, s); - } - - @Override - public Attribute visitType(TypeMirror t, Void p) { - if (t instanceof Type) { - return new Attribute.Class(javacTypes, (Type) t); - } else { - throw new BugInCF("Unexpected type of TypeMirror: " + t.getClass()); - } - } - - @Override - public Attribute visitEnumConstant(VariableElement c, Void p) { - if (c instanceof Symbol.VarSymbol) { - Symbol.VarSymbol sym = (Symbol.VarSymbol) c; - if (sym.getKind() == ElementKind.ENUM_CONSTANT) { - return new Attribute.Enum(sym.type, sym); - } - } - throw new BugInCF("Unexpected type of VariableElement: " + c.getClass()); - } - - @Override - public Attribute visitAnnotation(AnnotationMirror a, Void p) { - return createCompoundFromAnnotationMirror(a, processingEnv); - } - - @Override - public Attribute visitArray(java.util.List vals, Void p) { - if (!vals.isEmpty()) { - List valAttrs = List.nil(); - for (AnnotationValue av : vals) { - valAttrs = valAttrs.append(av.accept(this, p)); - } - ArrayType arrayType = modelTypes.getArrayType(valAttrs.get(0).type); - return new Attribute.Array((Type) arrayType, valAttrs); - } else { - return new Attribute.Array((Type) meth.getReturnType(), List.nil()); - } - } - - @Override - public Attribute visitUnknown(AnnotationValue av, Void p) { - throw new BugInCF("Unexpected type of AnnotationValue: " + av.getClass()); - } + @Override + public Attribute visitBoolean(boolean b, Void p) { + TypeMirror booleanType = modelTypes.getPrimitiveType(TypeKind.BOOLEAN); + return new Attribute.Constant((Type) booleanType, b ? 1 : 0); } - /** - * Create an unknown TypeAnnotationPosition. - * - * @return an unkown TypeAnnotationPosition - */ - public static TypeAnnotationPosition unknownTAPosition() { - return TypeAnnotationPosition.unknown; + @Override + public Attribute visitByte(byte b, Void p) { + TypeMirror byteType = modelTypes.getPrimitiveType(TypeKind.BYTE); + return new Attribute.Constant((Type) byteType, b); } - /** - * Create a method return TypeAnnotationPosition. - * - * @param pos the source tree position - * @return a method return TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodReturnTAPosition(int pos) { - return TypeAnnotationPosition.methodReturn(pos); + @Override + public Attribute visitChar(char c, Void p) { + TypeMirror charType = modelTypes.getPrimitiveType(TypeKind.CHAR); + return new Attribute.Constant((Type) charType, c); } - /** - * Create a method receiver TypeAnnotationPosition. - * - * @param pos the source tree position - * @return a method receiver TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodReceiverTAPosition(int pos) { - return TypeAnnotationPosition.methodReceiver(pos); + @Override + public Attribute visitDouble(double d, Void p) { + TypeMirror doubleType = modelTypes.getPrimitiveType(TypeKind.DOUBLE); + return new Attribute.Constant((Type) doubleType, d); } - /** - * Create a method parameter TypeAnnotationPosition. - * - * @param pidx the parameter index - * @param pos the source tree position - * @return a method parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodParameterTAPosition(int pidx, int pos) { - return TypeAnnotationPosition.methodParameter(pidx, pos); + @Override + public Attribute visitFloat(float f, Void p) { + TypeMirror floatType = modelTypes.getPrimitiveType(TypeKind.FLOAT); + return new Attribute.Constant((Type) floatType, f); } - /** - * Create a method throws TypeAnnotationPosition. - * - * @param tidx the throws index - * @param pos the source tree position - * @return a method throws TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodThrowsTAPosition(int tidx, int pos) { - return TypeAnnotationPosition.methodThrows( - TypeAnnotationPosition.emptyPath, null, tidx, pos); + @Override + public Attribute visitInt(int i, Void p) { + TypeMirror intType = modelTypes.getPrimitiveType(TypeKind.INT); + return new Attribute.Constant((Type) intType, i); } - /** - * Create a field TypeAnnotationPosition. - * - * @param pos the source tree position - * @return a field TypeAnnotationPosition - */ - public static TypeAnnotationPosition fieldTAPosition(int pos) { - return TypeAnnotationPosition.field(pos); + @Override + public Attribute visitLong(long i, Void p) { + TypeMirror longType = modelTypes.getPrimitiveType(TypeKind.LONG); + return new Attribute.Constant((Type) longType, i); } - /** - * Create a class extends TypeAnnotationPosition. - * - * @param implidx the class extends index - * @param pos the source tree position - * @return a class extends TypeAnnotationPosition - */ - public static TypeAnnotationPosition classExtendsTAPosition(int implidx, int pos) { - return TypeAnnotationPosition.classExtends(implidx, pos); + @Override + public Attribute visitShort(short s, Void p) { + TypeMirror shortType = modelTypes.getPrimitiveType(TypeKind.SHORT); + return new Attribute.Constant((Type) shortType, s); } - /** - * Create a type parameter TypeAnnotationPosition. - * - * @param tpidx the type parameter index - * @param pos the source tree position - * @return a type parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition typeParameterTAPosition(int tpidx, int pos) { - return TypeAnnotationPosition.typeParameter( - TypeAnnotationPosition.emptyPath, null, tpidx, pos); + @Override + public Attribute visitString(String s, Void p) { + TypeMirror stringType = elements.getTypeElement("java.lang.String").asType(); + return new Attribute.Constant((Type) stringType, s); } - /** - * Create a method type parameter TypeAnnotationPosition. - * - * @param tpidx the method type parameter index - * @param pos the source tree position - * @return a method type parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodTypeParameterTAPosition(int tpidx, int pos) { - return TypeAnnotationPosition.methodTypeParameter( - TypeAnnotationPosition.emptyPath, null, tpidx, pos); + @Override + public Attribute visitType(TypeMirror t, Void p) { + if (t instanceof Type) { + return new Attribute.Class(javacTypes, (Type) t); + } else { + throw new BugInCF("Unexpected type of TypeMirror: " + t.getClass()); + } } - /** - * Create a type parameter bound TypeAnnotationPosition. - * - * @param tpidx the type parameter index - * @param bndidx the bound index - * @param pos the source tree position - * @return a method parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition typeParameterBoundTAPosition( - int tpidx, int bndidx, int pos) { - return TypeAnnotationPosition.typeParameterBound( - TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); + @Override + public Attribute visitEnumConstant(VariableElement c, Void p) { + if (c instanceof Symbol.VarSymbol) { + Symbol.VarSymbol sym = (Symbol.VarSymbol) c; + if (sym.getKind() == ElementKind.ENUM_CONSTANT) { + return new Attribute.Enum(sym.type, sym); + } + } + throw new BugInCF("Unexpected type of VariableElement: " + c.getClass()); } - /** - * Create a method type parameter bound TypeAnnotationPosition. - * - * @param tpidx the type parameter index - * @param bndidx the bound index - * @param pos the source tree position - * @return a method parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodTypeParameterBoundTAPosition( - int tpidx, int bndidx, int pos) { - return TypeAnnotationPosition.methodTypeParameterBound( - TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); + @Override + public Attribute visitAnnotation(AnnotationMirror a, Void p) { + return createCompoundFromAnnotationMirror(a, processingEnv); } - /** - * Copy a TypeAnnotationPosition. - * - * @param tapos the input TypeAnnotationPosition - * @return a copied TypeAnnotationPosition - */ - public static TypeAnnotationPosition copyTAPosition(TypeAnnotationPosition tapos) { - TypeAnnotationPosition res; - switch (tapos.type) { - case CAST: - res = - TypeAnnotationPosition.typeCast( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case CLASS_EXTENDS: - res = - TypeAnnotationPosition.classExtends( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case CLASS_TYPE_PARAMETER: - res = - TypeAnnotationPosition.typeParameter( - tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); - break; - case CLASS_TYPE_PARAMETER_BOUND: - res = - TypeAnnotationPosition.typeParameterBound( - tapos.location, - tapos.onLambda, - tapos.parameter_index, - tapos.bound_index, - tapos.pos); - break; - case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.constructorInvocationTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case CONSTRUCTOR_REFERENCE: - res = - TypeAnnotationPosition.constructorRef( - tapos.location, tapos.onLambda, tapos.pos); - break; - case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.constructorRefTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case EXCEPTION_PARAMETER: - res = - TypeAnnotationPosition.exceptionParameter( - tapos.location, tapos.onLambda, tapos.pos); - break; - case FIELD: - res = TypeAnnotationPosition.field(tapos.location, tapos.onLambda, tapos.pos); - break; - case INSTANCEOF: - res = TypeAnnotationPosition.instanceOf(tapos.location, tapos.onLambda, tapos.pos); - break; - case LOCAL_VARIABLE: - res = - TypeAnnotationPosition.localVariable( - tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_FORMAL_PARAMETER: - res = - TypeAnnotationPosition.methodParameter( - tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); - break; - case METHOD_INVOCATION_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.methodInvocationTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case METHOD_RECEIVER: - res = - TypeAnnotationPosition.methodReceiver( - tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_REFERENCE: - res = TypeAnnotationPosition.methodRef(tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_REFERENCE_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.methodRefTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case METHOD_RETURN: - res = - TypeAnnotationPosition.methodReturn( - tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_TYPE_PARAMETER: - res = - TypeAnnotationPosition.methodTypeParameter( - tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); - break; - case METHOD_TYPE_PARAMETER_BOUND: - res = - TypeAnnotationPosition.methodTypeParameterBound( - tapos.location, - tapos.onLambda, - tapos.parameter_index, - tapos.bound_index, - tapos.pos); - break; - case NEW: - res = TypeAnnotationPosition.newObj(tapos.location, tapos.onLambda, tapos.pos); - break; - case RESOURCE_VARIABLE: - res = - TypeAnnotationPosition.resourceVariable( - tapos.location, tapos.onLambda, tapos.pos); - break; - case THROWS: - res = - TypeAnnotationPosition.methodThrows( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case UNKNOWN: - default: - throw new BugInCF("Unexpected target type: " + tapos + " at " + tapos.type); + @Override + public Attribute visitArray(java.util.List vals, Void p) { + if (!vals.isEmpty()) { + List valAttrs = List.nil(); + for (AnnotationValue av : vals) { + valAttrs = valAttrs.append(av.accept(this, p)); } - return res; + ArrayType arrayType = modelTypes.getArrayType(valAttrs.get(0).type); + return new Attribute.Array((Type) arrayType, valAttrs); + } else { + return new Attribute.Array((Type) meth.getReturnType(), List.nil()); + } } - /** - * Remove type annotations from the given type. - * - * @param in the input type - * @return the same underlying type, but without type annotations - */ - public static Type unannotatedType(TypeMirror in) { - Type impl = (Type) in; - if (impl.isPrimitive()) { - // TODO: file an issue that stripMetadata doesn't work for primitives. - // See eisop/checker-framework issue #21. - return impl.baseType(); - } else { - return impl.stripMetadata(); - } + @Override + public Attribute visitUnknown(AnnotationValue av, Void p) { + throw new BugInCF("Unexpected type of AnnotationValue: " + av.getClass()); + } + } + + /** + * Create an unknown TypeAnnotationPosition. + * + * @return an unkown TypeAnnotationPosition + */ + public static TypeAnnotationPosition unknownTAPosition() { + return TypeAnnotationPosition.unknown; + } + + /** + * Create a method return TypeAnnotationPosition. + * + * @param pos the source tree position + * @return a method return TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodReturnTAPosition(int pos) { + return TypeAnnotationPosition.methodReturn(pos); + } + + /** + * Create a method receiver TypeAnnotationPosition. + * + * @param pos the source tree position + * @return a method receiver TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodReceiverTAPosition(int pos) { + return TypeAnnotationPosition.methodReceiver(pos); + } + + /** + * Create a method parameter TypeAnnotationPosition. + * + * @param pidx the parameter index + * @param pos the source tree position + * @return a method parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodParameterTAPosition(int pidx, int pos) { + return TypeAnnotationPosition.methodParameter(pidx, pos); + } + + /** + * Create a method throws TypeAnnotationPosition. + * + * @param tidx the throws index + * @param pos the source tree position + * @return a method throws TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodThrowsTAPosition(int tidx, int pos) { + return TypeAnnotationPosition.methodThrows(TypeAnnotationPosition.emptyPath, null, tidx, pos); + } + + /** + * Create a field TypeAnnotationPosition. + * + * @param pos the source tree position + * @return a field TypeAnnotationPosition + */ + public static TypeAnnotationPosition fieldTAPosition(int pos) { + return TypeAnnotationPosition.field(pos); + } + + /** + * Create a class extends TypeAnnotationPosition. + * + * @param implidx the class extends index + * @param pos the source tree position + * @return a class extends TypeAnnotationPosition + */ + public static TypeAnnotationPosition classExtendsTAPosition(int implidx, int pos) { + return TypeAnnotationPosition.classExtends(implidx, pos); + } + + /** + * Create a type parameter TypeAnnotationPosition. + * + * @param tpidx the type parameter index + * @param pos the source tree position + * @return a type parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition typeParameterTAPosition(int tpidx, int pos) { + return TypeAnnotationPosition.typeParameter(TypeAnnotationPosition.emptyPath, null, tpidx, pos); + } + + /** + * Create a method type parameter TypeAnnotationPosition. + * + * @param tpidx the method type parameter index + * @param pos the source tree position + * @return a method type parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodTypeParameterTAPosition(int tpidx, int pos) { + return TypeAnnotationPosition.methodTypeParameter( + TypeAnnotationPosition.emptyPath, null, tpidx, pos); + } + + /** + * Create a type parameter bound TypeAnnotationPosition. + * + * @param tpidx the type parameter index + * @param bndidx the bound index + * @param pos the source tree position + * @return a method parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition typeParameterBoundTAPosition( + int tpidx, int bndidx, int pos) { + return TypeAnnotationPosition.typeParameterBound( + TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); + } + + /** + * Create a method type parameter bound TypeAnnotationPosition. + * + * @param tpidx the type parameter index + * @param bndidx the bound index + * @param pos the source tree position + * @return a method parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodTypeParameterBoundTAPosition( + int tpidx, int bndidx, int pos) { + return TypeAnnotationPosition.methodTypeParameterBound( + TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); + } + + /** + * Copy a TypeAnnotationPosition. + * + * @param tapos the input TypeAnnotationPosition + * @return a copied TypeAnnotationPosition + */ + public static TypeAnnotationPosition copyTAPosition(TypeAnnotationPosition tapos) { + TypeAnnotationPosition res; + switch (tapos.type) { + case CAST: + res = + TypeAnnotationPosition.typeCast( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case CLASS_EXTENDS: + res = + TypeAnnotationPosition.classExtends( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case CLASS_TYPE_PARAMETER: + res = + TypeAnnotationPosition.typeParameter( + tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); + break; + case CLASS_TYPE_PARAMETER_BOUND: + res = + TypeAnnotationPosition.typeParameterBound( + tapos.location, + tapos.onLambda, + tapos.parameter_index, + tapos.bound_index, + tapos.pos); + break; + case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.constructorInvocationTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case CONSTRUCTOR_REFERENCE: + res = TypeAnnotationPosition.constructorRef(tapos.location, tapos.onLambda, tapos.pos); + break; + case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.constructorRefTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case EXCEPTION_PARAMETER: + res = TypeAnnotationPosition.exceptionParameter(tapos.location, tapos.onLambda, tapos.pos); + break; + case FIELD: + res = TypeAnnotationPosition.field(tapos.location, tapos.onLambda, tapos.pos); + break; + case INSTANCEOF: + res = TypeAnnotationPosition.instanceOf(tapos.location, tapos.onLambda, tapos.pos); + break; + case LOCAL_VARIABLE: + res = TypeAnnotationPosition.localVariable(tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_FORMAL_PARAMETER: + res = + TypeAnnotationPosition.methodParameter( + tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); + break; + case METHOD_INVOCATION_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.methodInvocationTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case METHOD_RECEIVER: + res = TypeAnnotationPosition.methodReceiver(tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_REFERENCE: + res = TypeAnnotationPosition.methodRef(tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_REFERENCE_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.methodRefTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case METHOD_RETURN: + res = TypeAnnotationPosition.methodReturn(tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_TYPE_PARAMETER: + res = + TypeAnnotationPosition.methodTypeParameter( + tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); + break; + case METHOD_TYPE_PARAMETER_BOUND: + res = + TypeAnnotationPosition.methodTypeParameterBound( + tapos.location, + tapos.onLambda, + tapos.parameter_index, + tapos.bound_index, + tapos.pos); + break; + case NEW: + res = TypeAnnotationPosition.newObj(tapos.location, tapos.onLambda, tapos.pos); + break; + case RESOURCE_VARIABLE: + res = TypeAnnotationPosition.resourceVariable(tapos.location, tapos.onLambda, tapos.pos); + break; + case THROWS: + res = + TypeAnnotationPosition.methodThrows( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case UNKNOWN: + default: + throw new BugInCF("Unexpected target type: " + tapos + " at " + tapos.type); + } + return res; + } + + /** + * Remove type annotations from the given type. + * + * @param in the input type + * @return the same underlying type, but without type annotations + */ + public static Type unannotatedType(TypeMirror in) { + Type impl = (Type) in; + if (impl.isPrimitive()) { + // TODO: file an issue that stripMetadata doesn't work for primitives. + // See eisop/checker-framework issue #21. + return impl.baseType(); + } else { + return impl.stripMetadata(); } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java index 211610614b3..bef718488a1 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java @@ -1,309 +1,306 @@ package org.checkerframework.javacutil; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; - import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; - import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; /** A utility class that helps with {@link TypeKind}s. */ public final class TypeKindUtils { - /** Map of a boxed primitive type's fully-qualified name to its primitive {@link TypeKind}. */ - private static final Map<@FullyQualifiedName String, TypeKind> boxedToPrimitiveType; + /** Map of a boxed primitive type's fully-qualified name to its primitive {@link TypeKind}. */ + private static final Map<@FullyQualifiedName String, TypeKind> boxedToPrimitiveType; - static { - Map<@FullyQualifiedName String, TypeKind> map = new LinkedHashMap<>(); - map.put("java.lang.Byte", TypeKind.BYTE); - map.put("java.lang.Boolean", TypeKind.BOOLEAN); - map.put("java.lang.Character", TypeKind.CHAR); - map.put("java.lang.Double", TypeKind.DOUBLE); - map.put("java.lang.Float", TypeKind.FLOAT); - map.put("java.lang.Integer", TypeKind.INT); - map.put("java.lang.Long", TypeKind.LONG); - map.put("java.lang.Short", TypeKind.SHORT); - boxedToPrimitiveType = Collections.unmodifiableMap(map); - } + static { + Map<@FullyQualifiedName String, TypeKind> map = new LinkedHashMap<>(); + map.put("java.lang.Byte", TypeKind.BYTE); + map.put("java.lang.Boolean", TypeKind.BOOLEAN); + map.put("java.lang.Character", TypeKind.CHAR); + map.put("java.lang.Double", TypeKind.DOUBLE); + map.put("java.lang.Float", TypeKind.FLOAT); + map.put("java.lang.Integer", TypeKind.INT); + map.put("java.lang.Long", TypeKind.LONG); + map.put("java.lang.Short", TypeKind.SHORT); + boxedToPrimitiveType = Collections.unmodifiableMap(map); + } - /** This class cannot be instantiated. */ - private TypeKindUtils() { - throw new AssertionError("Class TypeKindUtils cannot be instantiated."); - } + /** This class cannot be instantiated. */ + private TypeKindUtils() { + throw new AssertionError("Class TypeKindUtils cannot be instantiated."); + } - /** - * Return true if the argument is one of INT, SHORT, BYTE, CHAR, LONG. - * - * @param typeKind the TypeKind to inspect - * @return true if typeKind is a primitive integral type kind - */ - public static boolean isIntegral(TypeKind typeKind) { - switch (typeKind) { - case INT: - case SHORT: - case BYTE: - case CHAR: - case LONG: - return true; - default: - return false; - } + /** + * Return true if the argument is one of INT, SHORT, BYTE, CHAR, LONG. + * + * @param typeKind the TypeKind to inspect + * @return true if typeKind is a primitive integral type kind + */ + public static boolean isIntegral(TypeKind typeKind) { + switch (typeKind) { + case INT: + case SHORT: + case BYTE: + case CHAR: + case LONG: + return true; + default: + return false; } + } - /** - * Return true if the argument is one of FLOAT, DOUBLE. - * - * @param typeKind the TypeKind to inspect - * @return true if typeKind is a primitive floating point type kind - */ - public static boolean isFloatingPoint(TypeKind typeKind) { - switch (typeKind) { - case FLOAT: - case DOUBLE: - return true; - default: - return false; - } + /** + * Return true if the argument is one of FLOAT, DOUBLE. + * + * @param typeKind the TypeKind to inspect + * @return true if typeKind is a primitive floating point type kind + */ + public static boolean isFloatingPoint(TypeKind typeKind) { + switch (typeKind) { + case FLOAT: + case DOUBLE: + return true; + default: + return false; } + } - /** - * Returns true iff the argument is a primitive numeric type kind. - * - * @param typeKind a type kind - * @return true if the argument is a primitive numeric type kind - */ - public static boolean isNumeric(TypeKind typeKind) { - switch (typeKind) { - case BYTE: - case CHAR: - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - return true; - default: - return false; - } + /** + * Returns true iff the argument is a primitive numeric type kind. + * + * @param typeKind a type kind + * @return true if the argument is a primitive numeric type kind + */ + public static boolean isNumeric(TypeKind typeKind) { + switch (typeKind) { + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + return true; + default: + return false; } + } - // Cannot create an overload that takes an AnnotatedTypeMirror because the javacutil - // package must not depend on the framework package. - /** - * Given a primitive type, return its kind. Given a boxed primitive type, return the - * corresponding primitive type kind. Otherwise, return null. - * - * @param type a primitive or boxed primitive type - * @return a primitive type kind, or null - */ - public static @Nullable TypeKind primitiveOrBoxedToTypeKind(TypeMirror type) { - TypeKind typeKind = type.getKind(); - if (typeKind.isPrimitive()) { - return typeKind; - } - - return boxedToTypeKind(type); + // Cannot create an overload that takes an AnnotatedTypeMirror because the javacutil + // package must not depend on the framework package. + /** + * Given a primitive type, return its kind. Given a boxed primitive type, return the corresponding + * primitive type kind. Otherwise, return null. + * + * @param type a primitive or boxed primitive type + * @return a primitive type kind, or null + */ + public static @Nullable TypeKind primitiveOrBoxedToTypeKind(TypeMirror type) { + TypeKind typeKind = type.getKind(); + if (typeKind.isPrimitive()) { + return typeKind; } - /** - * Given a boxed primitive type, return the corresponding primitive type kind. Otherwise, return - * null. - * - * @param type a boxed primitive type - * @return a primitive type kind, or null - */ - public static @Nullable TypeKind boxedToTypeKind(TypeMirror type) { - if (type.getKind() != TypeKind.DECLARED) { - return null; - } - - String typeString = TypesUtils.getQualifiedName((DeclaredType) type); - return boxedToPrimitiveType.get(typeString); - } + return boxedToTypeKind(type); + } - // No overload that takes AnnotatedTypeMirror because javacutil cannot depend on framework. - /** - * Returns the widened numeric type for an arithmetic operation performed on a value of the left - * type and the right type. Defined in JLS 5.6.2. We return a {@link TypeKind} because creating - * a {@link TypeMirror} requires a {@link javax.lang.model.util.Types} object from the {@link - * javax.annotation.processing.ProcessingEnvironment}. - * - * @param left a type mirror - * @param right a type mirror - * @return the result of widening numeric conversion, or NONE when the conversion cannot be - * performed - */ - public static TypeKind widenedNumericType(TypeMirror left, TypeMirror right) { - return widenedNumericType(left.getKind(), right.getKind()); + /** + * Given a boxed primitive type, return the corresponding primitive type kind. Otherwise, return + * null. + * + * @param type a boxed primitive type + * @return a primitive type kind, or null + */ + public static @Nullable TypeKind boxedToTypeKind(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return null; } - /** - * Given two type kinds, return the type kind they are widened to, when an arithmetic operation - * is performed on them. Defined in JLS 5.6.2. - * - * @param a a type kind - * @param b a type kind - * @return the type kind to which they are widened, when an operation is performed on them - */ - public static TypeKind widenedNumericType(TypeKind a, TypeKind b) { - if (!isNumeric(a) || !isNumeric(b)) { - return TypeKind.NONE; - } + String typeString = TypesUtils.getQualifiedName((DeclaredType) type); + return boxedToPrimitiveType.get(typeString); + } - if (a == TypeKind.DOUBLE || b == TypeKind.DOUBLE) { - return TypeKind.DOUBLE; - } + // No overload that takes AnnotatedTypeMirror because javacutil cannot depend on framework. + /** + * Returns the widened numeric type for an arithmetic operation performed on a value of the left + * type and the right type. Defined in JLS 5.6.2. We return a {@link TypeKind} because creating a + * {@link TypeMirror} requires a {@link javax.lang.model.util.Types} object from the {@link + * javax.annotation.processing.ProcessingEnvironment}. + * + * @param left a type mirror + * @param right a type mirror + * @return the result of widening numeric conversion, or NONE when the conversion cannot be + * performed + */ + public static TypeKind widenedNumericType(TypeMirror left, TypeMirror right) { + return widenedNumericType(left.getKind(), right.getKind()); + } - if (a == TypeKind.FLOAT || b == TypeKind.FLOAT) { - return TypeKind.FLOAT; - } + /** + * Given two type kinds, return the type kind they are widened to, when an arithmetic operation is + * performed on them. Defined in JLS 5.6.2. + * + * @param a a type kind + * @param b a type kind + * @return the type kind to which they are widened, when an operation is performed on them + */ + public static TypeKind widenedNumericType(TypeKind a, TypeKind b) { + if (!isNumeric(a) || !isNumeric(b)) { + return TypeKind.NONE; + } - if (a == TypeKind.LONG || b == TypeKind.LONG) { - return TypeKind.LONG; - } + if (a == TypeKind.DOUBLE || b == TypeKind.DOUBLE) { + return TypeKind.DOUBLE; + } - return TypeKind.INT; + if (a == TypeKind.FLOAT || b == TypeKind.FLOAT) { + return TypeKind.FLOAT; } - /** The type of primitive conversion: narrowing, widening, or same. */ - public enum PrimitiveConversionKind { - /** The two primitive kinds are the same. */ - SAME, - /** - * The conversion is a widening primitive conversion. - * - *

          This includes byte to char, even though that is strictly a "widening and narrowing - * primitive conversion", according to JLS 5.1.4. - */ - WIDENING, - /** The conversion is a narrowing primitive conversion. */ - NARROWING + if (a == TypeKind.LONG || b == TypeKind.LONG) { + return TypeKind.LONG; } + return TypeKind.INT; + } + + /** The type of primitive conversion: narrowing, widening, or same. */ + public enum PrimitiveConversionKind { + /** The two primitive kinds are the same. */ + SAME, /** - * Return the type of primitive conversion between {@code from} and {@code to}. + * The conversion is a widening primitive conversion. * - *

          The narrowing conversions include both short to char and char to short. - * - * @param from a primitive type - * @param to a primitive type - * @return the type of primitive conversion between {@code from} and {@code to} + *

          This includes byte to char, even though that is strictly a "widening and narrowing + * primitive conversion", according to JLS 5.1.4. */ - public static PrimitiveConversionKind getPrimitiveConversionKind(TypeKind from, TypeKind to) { - if (from == TypeKind.BOOLEAN && to == TypeKind.BOOLEAN) { - return PrimitiveConversionKind.SAME; - } + WIDENING, + /** The conversion is a narrowing primitive conversion. */ + NARROWING + } - assert (isIntegral(from) || isFloatingPoint(from)) - && (isIntegral(to) || isFloatingPoint(to)) - : "getPrimitiveConversionKind " + from + " " + to; + /** + * Return the type of primitive conversion between {@code from} and {@code to}. + * + *

          The narrowing conversions include both short to char and char to short. + * + * @param from a primitive type + * @param to a primitive type + * @return the type of primitive conversion between {@code from} and {@code to} + */ + public static PrimitiveConversionKind getPrimitiveConversionKind(TypeKind from, TypeKind to) { + if (from == TypeKind.BOOLEAN && to == TypeKind.BOOLEAN) { + return PrimitiveConversionKind.SAME; + } - if (from == to) { - return PrimitiveConversionKind.SAME; - } + assert (isIntegral(from) || isFloatingPoint(from)) && (isIntegral(to) || isFloatingPoint(to)) + : "getPrimitiveConversionKind " + from + " " + to; - boolean fromIntegral = isIntegral(from); - boolean toFloatingPoint = isFloatingPoint(to); - if (fromIntegral && toFloatingPoint) { - return PrimitiveConversionKind.WIDENING; - } + if (from == to) { + return PrimitiveConversionKind.SAME; + } - boolean toIntegral = isIntegral(to); - boolean fromFloatingPoint = isFloatingPoint(from); - if (fromFloatingPoint && toIntegral) { - return PrimitiveConversionKind.NARROWING; - } + boolean fromIntegral = isIntegral(from); + boolean toFloatingPoint = isFloatingPoint(to); + if (fromIntegral && toFloatingPoint) { + return PrimitiveConversionKind.WIDENING; + } - if (numBits(from) < numBits(to)) { - return PrimitiveConversionKind.WIDENING; - } else { - // If same number of bits (char to short or short to char), it is a narrowing. - return PrimitiveConversionKind.NARROWING; - } + boolean toIntegral = isIntegral(to); + boolean fromFloatingPoint = isFloatingPoint(from); + if (fromFloatingPoint && toIntegral) { + return PrimitiveConversionKind.NARROWING; } - /** - * Returns the number of bits in the representation of a primitive type. Returns -1 if the type - * is not a primitive type. - * - * @param tk a primitive type kind - * @return the number of bits in its representation, or -1 if not integral - */ - private static int numBits(TypeKind tk) { - switch (tk) { - case BYTE: - return 8; - case SHORT: - return 16; - case CHAR: - return 16; - case INT: - return 32; - case LONG: - return 64; - case FLOAT: - return 32; - case DOUBLE: - return 64; - case BOOLEAN: - default: - return -1; - } + if (numBits(from) < numBits(to)) { + return PrimitiveConversionKind.WIDENING; + } else { + // If same number of bits (char to short or short to char), it is a narrowing. + return PrimitiveConversionKind.NARROWING; } + } - /** - * Returns the minimum value representable by the given integral primitive type. - * - * @param tk a primitive type kind - * @return the minimum value representable by the given integral primitive type - */ - public static long minValue(TypeKind tk) { - switch (tk) { - case BYTE: - return Byte.MIN_VALUE; - case SHORT: - return Short.MIN_VALUE; - case CHAR: - return Character.MIN_VALUE; - case INT: - return Integer.MIN_VALUE; - case LONG: - return Long.MIN_VALUE; - case FLOAT: - case DOUBLE: - case BOOLEAN: - default: - throw new BugInCF(tk + " does not have a minimum value"); - } + /** + * Returns the number of bits in the representation of a primitive type. Returns -1 if the type is + * not a primitive type. + * + * @param tk a primitive type kind + * @return the number of bits in its representation, or -1 if not integral + */ + private static int numBits(TypeKind tk) { + switch (tk) { + case BYTE: + return 8; + case SHORT: + return 16; + case CHAR: + return 16; + case INT: + return 32; + case LONG: + return 64; + case FLOAT: + return 32; + case DOUBLE: + return 64; + case BOOLEAN: + default: + return -1; } + } - /** - * Returns the maximum value representable by the given integral primitive type. - * - * @param tk a primitive type kind - * @return the maximum value representable by the given integral primitive type - */ - public static long maxValue(TypeKind tk) { - switch (tk) { - case BYTE: - return Byte.MAX_VALUE; - case SHORT: - return Short.MAX_VALUE; - case CHAR: - return Character.MAX_VALUE; - case INT: - return Integer.MAX_VALUE; - case LONG: - return Long.MAX_VALUE; - case FLOAT: - case DOUBLE: - case BOOLEAN: - default: - throw new BugInCF(tk + " does not have a maximum value"); - } + /** + * Returns the minimum value representable by the given integral primitive type. + * + * @param tk a primitive type kind + * @return the minimum value representable by the given integral primitive type + */ + public static long minValue(TypeKind tk) { + switch (tk) { + case BYTE: + return Byte.MIN_VALUE; + case SHORT: + return Short.MIN_VALUE; + case CHAR: + return Character.MIN_VALUE; + case INT: + return Integer.MIN_VALUE; + case LONG: + return Long.MIN_VALUE; + case FLOAT: + case DOUBLE: + case BOOLEAN: + default: + throw new BugInCF(tk + " does not have a minimum value"); + } + } + + /** + * Returns the maximum value representable by the given integral primitive type. + * + * @param tk a primitive type kind + * @return the maximum value representable by the given integral primitive type + */ + public static long maxValue(TypeKind tk) { + switch (tk) { + case BYTE: + return Byte.MAX_VALUE; + case SHORT: + return Short.MAX_VALUE; + case CHAR: + return Character.MAX_VALUE; + case INT: + return Integer.MAX_VALUE; + case LONG: + return Long.MAX_VALUE; + case FLOAT: + case DOUBLE: + case BOOLEAN: + default: + throw new BugInCF(tk + " does not have a maximum value"); } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java index 8f3b1b4a855..a6de9d49c70 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java @@ -13,26 +13,26 @@ @SuppressWarnings("serial") public class TypeSystemError extends RuntimeException { - /** - * Constructs a new TypeSystemError with the specified detail message. - * - * @param message the detail message - */ - public TypeSystemError(String message) { - super(message); - if (message == null) { - throw new BugInCF("Must have a detail message."); - } + /** + * Constructs a new TypeSystemError with the specified detail message. + * + * @param message the detail message + */ + public TypeSystemError(String message) { + super(message); + if (message == null) { + throw new BugInCF("Must have a detail message."); } + } - /** - * Constructs a new TypeSystemError with a detail message composed from the given arguments. - * - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public TypeSystemError(String fmt, @Nullable Object... args) { - this(String.format(fmt, args)); - } + /** + * Constructs a new TypeSystemError with a detail message composed from the given arguments. + * + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public TypeSystemError(String fmt, @Nullable Object... args) { + this(String.format(fmt, args)); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index 05297920d5a..c91573c5021 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -10,22 +10,11 @@ import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; - -import org.checkerframework.checker.interning.qual.EqualsMethod; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; -import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.ImmutableTypes; -import org.plumelib.util.StringsPlume; - import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.StringJoiner; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.Name; @@ -43,6 +32,14 @@ import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.EqualsMethod; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; +import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.ImmutableTypes; +import org.plumelib.util.StringsPlume; /** * A utility class that helps with {@link TypeMirror}s. It complements {@link Types}, providing @@ -50,1215 +47,1208 @@ */ public final class TypesUtils { - /** Class cannot be instantiated. */ - private TypesUtils() { - throw new AssertionError("Class TypesUtils cannot be instantiated."); - } - - /// Creating types - - /** - * Returns the {@link TypeMirror} for a given {@link Class}. - * - * @param clazz a class - * @param types the type utilities - * @param elements the element utilities - * @return the TypeMirror for {@code clazz} - */ - public static TypeMirror typeFromClass(Class clazz, Types types, Elements elements) { - if (clazz == void.class) { - return types.getNoType(TypeKind.VOID); - } else if (clazz.isPrimitive()) { - String primitiveName = clazz.getName().toUpperCase(Locale.ROOT); - TypeKind primitiveKind = TypeKind.valueOf(primitiveName); - return types.getPrimitiveType(primitiveKind); - } else if (clazz.isArray()) { - TypeMirror componentType = typeFromClass(clazz.getComponentType(), types, elements); - return types.getArrayType(componentType); - } else { - String name = clazz.getCanonicalName(); - assert name != null : "@AssumeAssertion(nullness): assumption"; - TypeElement element = elements.getTypeElement(name); - if (element == null) { - throw new BugInCF("No element for: " + clazz); - } - return element.asType(); - } - } - - /** - * Returns an {@link ArrayType} with elements of type {@code componentType}. - * - * @param componentType the component type of the created array type - * @param types the type utilities - * @return an {@link ArrayType} whose elements have type {@code componentType} - */ - public static ArrayType createArrayType(TypeMirror componentType, Types types) { - JavacTypes t = (JavacTypes) types; - return t.getArrayType(componentType); - } - - /// Creating a Class - - /** - * Returns the {@link Class} for a given {@link TypeMirror}. Returns {@code Object.class} if it - * cannot determine anything more specific. - * - * @param typeMirror a TypeMirror - * @return the class for {@code typeMirror} - */ - public static Class getClassFromType(TypeMirror typeMirror) { - - switch (typeMirror.getKind()) { - case INT: - return int.class; - case LONG: - return long.class; - case SHORT: - return short.class; - case BYTE: - return byte.class; - case CHAR: - return char.class; - case DOUBLE: - return double.class; - case FLOAT: - return float.class; - case BOOLEAN: - return boolean.class; - - case ARRAY: - Class componentClass = - getClassFromType(((ArrayType) typeMirror).getComponentType()); - // In Java 12, use this instead: - // return fooClass.arrayType(); - return java.lang.reflect.Array.newInstance(componentClass, 0).getClass(); - - case DECLARED: - // BUG: need to compute a @ClassGetName, but this code computes a - // @CanonicalNameOrEmpty. They are different for inner classes. - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Names.toString - @DotSeparatedIdentifiers String typeString = TypesUtils.getQualifiedName((DeclaredType) typeMirror); - if (typeString.equals("")) { - return void.class; - } - - try { - return Class.forName(typeString); - } catch (ClassNotFoundException | LinkageError e) { - return Object.class; - } - - default: - return Object.class; - } - } - - /// Getters - - /** - * Gets the fully qualified name for a provided type. It returns an empty name if type is an - * anonymous type. - * - * @param type the declared type - * @return the name corresponding to that type - */ - @SuppressWarnings("signature:return") // todo: add fake override of Name.toString. - public static @CanonicalNameOrEmpty String getQualifiedName(DeclaredType type) { - TypeElement element = (TypeElement) type.asElement(); - @CanonicalNameOrEmpty Name name = element.getQualifiedName(); - return name.toString(); - } - - /** - * Returns the simple type name, without annotations but including array brackets. - * - * @param type a type - * @return the simple type name - */ - public static String simpleTypeName(TypeMirror type) { - switch (type.getKind()) { - case ARRAY: - return simpleTypeName(((ArrayType) type).getComponentType()) + "[]"; - case TYPEVAR: - return ((TypeVariable) type).asElement().getSimpleName().toString(); - case DECLARED: - return ((DeclaredType) type).asElement().getSimpleName().toString(); - case INTERSECTION: - StringJoiner sjI = new StringJoiner(" & "); - for (TypeMirror bound : ((IntersectionType) type).getBounds()) { - sjI.add(simpleTypeName(bound)); - } - return sjI.toString(); - case NULL: - return ""; - case VOID: - return "void"; - case WILDCARD: - WildcardType wildcard = (WildcardType) type; - TypeMirror extendsBound = wildcard.getExtendsBound(); - TypeMirror superBound = wildcard.getSuperBound(); - return "?" - + (extendsBound != null ? " extends " + simpleTypeName(extendsBound) : "") - + (superBound != null ? " super " + simpleTypeName(superBound) : ""); - case UNION: - StringJoiner sj = new StringJoiner(" | "); - for (TypeMirror alternative : ((UnionType) type).getAlternatives()) { - sj.add(simpleTypeName(alternative)); - } - return sj.toString(); - case PACKAGE: - return "PACKAGE:" + type; - default: - if (type.getKind().isPrimitive()) { - return TypeAnnotationUtils.unannotatedType(type).toString(); - } else { - throw new BugInCF( - "simpleTypeName: unhandled type kind: %s, type: %s", - type.getKind(), type); - } - } - } - - /** - * Returns the binary name of a type. - * - * @param type a type - * @return its binary name - */ - public static @BinaryName String binaryName(TypeMirror type) { - if (type.getKind() != TypeKind.DECLARED) { - throw new BugInCF("Only declared types have a binary name"); + /** Class cannot be instantiated. */ + private TypesUtils() { + throw new AssertionError("Class TypesUtils cannot be instantiated."); + } + + /// Creating types + + /** + * Returns the {@link TypeMirror} for a given {@link Class}. + * + * @param clazz a class + * @param types the type utilities + * @param elements the element utilities + * @return the TypeMirror for {@code clazz} + */ + public static TypeMirror typeFromClass(Class clazz, Types types, Elements elements) { + if (clazz == void.class) { + return types.getNoType(TypeKind.VOID); + } else if (clazz.isPrimitive()) { + String primitiveName = clazz.getName().toUpperCase(Locale.ROOT); + TypeKind primitiveKind = TypeKind.valueOf(primitiveName); + return types.getPrimitiveType(primitiveKind); + } else if (clazz.isArray()) { + TypeMirror componentType = typeFromClass(clazz.getComponentType(), types, elements); + return types.getArrayType(componentType); + } else { + String name = clazz.getCanonicalName(); + assert name != null : "@AssumeAssertion(nullness): assumption"; + TypeElement element = elements.getTypeElement(name); + if (element == null) { + throw new BugInCF("No element for: " + clazz); + } + return element.asType(); + } + } + + /** + * Returns an {@link ArrayType} with elements of type {@code componentType}. + * + * @param componentType the component type of the created array type + * @param types the type utilities + * @return an {@link ArrayType} whose elements have type {@code componentType} + */ + public static ArrayType createArrayType(TypeMirror componentType, Types types) { + JavacTypes t = (JavacTypes) types; + return t.getArrayType(componentType); + } + + /// Creating a Class + + /** + * Returns the {@link Class} for a given {@link TypeMirror}. Returns {@code Object.class} if it + * cannot determine anything more specific. + * + * @param typeMirror a TypeMirror + * @return the class for {@code typeMirror} + */ + public static Class getClassFromType(TypeMirror typeMirror) { + + switch (typeMirror.getKind()) { + case INT: + return int.class; + case LONG: + return long.class; + case SHORT: + return short.class; + case BYTE: + return byte.class; + case CHAR: + return char.class; + case DOUBLE: + return double.class; + case FLOAT: + return float.class; + case BOOLEAN: + return boolean.class; + + case ARRAY: + Class componentClass = getClassFromType(((ArrayType) typeMirror).getComponentType()); + // In Java 12, use this instead: + // return fooClass.arrayType(); + return java.lang.reflect.Array.newInstance(componentClass, 0).getClass(); + + case DECLARED: + // BUG: need to compute a @ClassGetName, but this code computes a + // @CanonicalNameOrEmpty. They are different for inner classes. + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Names.toString + @DotSeparatedIdentifiers String typeString = TypesUtils.getQualifiedName((DeclaredType) typeMirror); + if (typeString.equals("")) { + return void.class; } - return ElementUtils.getBinaryName((TypeElement) ((DeclaredType) type).asElement()); - } - /** - * Returns the type element for {@code type} if {@code type} is a class, interface, annotation - * type, or enum. Otherwise, returns null. - * - * @param type whose element is returned - * @return the type element for {@code type} if {@code type} is a class, interface, annotation - * type, or enum; otherwise, returns {@code null} - */ - public static @Nullable TypeElement getTypeElement(TypeMirror type) { - Element element = ((Type) type).asElement(); - if (element == null) { - return null; - } - if (ElementUtils.isTypeElement(element)) { - return (TypeElement) element; + try { + return Class.forName(typeString); + } catch (ClassNotFoundException | LinkageError e) { + return Object.class; } - return null; - } - /** - * Given an array type, returns the type with all array levels stripped off. - * - * @param at an array type - * @return the type with all array levels stripped off - */ - public static TypeMirror getInnermostComponentType(ArrayType at) { - TypeMirror result = at; - while (result.getKind() == TypeKind.ARRAY) { - result = ((ArrayType) result).getComponentType(); + default: + return Object.class; + } + } + + /// Getters + + /** + * Gets the fully qualified name for a provided type. It returns an empty name if type is an + * anonymous type. + * + * @param type the declared type + * @return the name corresponding to that type + */ + @SuppressWarnings("signature:return") // todo: add fake override of Name.toString. + public static @CanonicalNameOrEmpty String getQualifiedName(DeclaredType type) { + TypeElement element = (TypeElement) type.asElement(); + @CanonicalNameOrEmpty Name name = element.getQualifiedName(); + return name.toString(); + } + + /** + * Returns the simple type name, without annotations but including array brackets. + * + * @param type a type + * @return the simple type name + */ + public static String simpleTypeName(TypeMirror type) { + switch (type.getKind()) { + case ARRAY: + return simpleTypeName(((ArrayType) type).getComponentType()) + "[]"; + case TYPEVAR: + return ((TypeVariable) type).asElement().getSimpleName().toString(); + case DECLARED: + return ((DeclaredType) type).asElement().getSimpleName().toString(); + case INTERSECTION: + StringJoiner sjI = new StringJoiner(" & "); + for (TypeMirror bound : ((IntersectionType) type).getBounds()) { + sjI.add(simpleTypeName(bound)); } - return result; - } - - /// Equality - - /** - * Returns true iff the arguments are both the same declared types. - * - *

          This is needed because class {@code Type.ClassType} does not override equals. - * - * @param t1 the first type to test - * @param t2 the second type to test - * @return whether the arguments are the same declared types - */ - public static boolean areSameDeclaredTypes(Type.ClassType t1, Type.ClassType t2) { - // Do a cheaper test first - if (t1.tsym.name != t2.tsym.name) { - return false; + return sjI.toString(); + case NULL: + return ""; + case VOID: + return "void"; + case WILDCARD: + WildcardType wildcard = (WildcardType) type; + TypeMirror extendsBound = wildcard.getExtendsBound(); + TypeMirror superBound = wildcard.getSuperBound(); + return "?" + + (extendsBound != null ? " extends " + simpleTypeName(extendsBound) : "") + + (superBound != null ? " super " + simpleTypeName(superBound) : ""); + case UNION: + StringJoiner sj = new StringJoiner(" | "); + for (TypeMirror alternative : ((UnionType) type).getAlternatives()) { + sj.add(simpleTypeName(alternative)); } - return t1.toString().equals(t2.toString()); - } - - /** - * Returns true iff the arguments are both the same primitive type. - * - * @param left a type - * @param right a type - * @return whether the arguments are the same primitive type - */ - public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) { - if (!isPrimitive(left) || !isPrimitive(right)) { - return false; + return sj.toString(); + case PACKAGE: + return "PACKAGE:" + type; + default: + if (type.getKind().isPrimitive()) { + return TypeAnnotationUtils.unannotatedType(type).toString(); + } else { + throw new BugInCF( + "simpleTypeName: unhandled type kind: %s, type: %s", type.getKind(), type); } - - return (left.getKind() == right.getKind()); } - - /// Predicates - - /** - * Returns true iff the type represents a java.lang.Object declared type. - * - * @param type the type to check - * @return true iff type represents java.lang.Object - */ - public static boolean isObject(TypeMirror type) { - return isDeclaredOfName(type, "java.lang.Object"); - } - - /** - * Return true iff the type represents a java.lang.Class declared type. - * - * @param type the type to check - * @return true iff type represents java.lang.Class - */ - public static boolean isClass(TypeMirror type) { - return isDeclaredOfName(type, "java.lang.Class"); - } - - /** - * Returns true iff the type represents a java.lang.String declared type. - * - * @param type the type to check - * @return true iff type represents java.lang.String - */ - public static boolean isString(TypeMirror type) { - return isDeclaredOfName(type, "java.lang.String"); - } - - /** - * Returns true if the type is either {@code boolean} (primitive type) or {@code - * java.lang.Boolean}. - * - * @param type the type to check - * @return true iff type represents a boolean type - */ - public static boolean isBooleanType(TypeMirror type) { - return type.getKind() == TypeKind.BOOLEAN || isDeclaredOfName(type, "java.lang.Boolean"); - } - - /** - * Returns true if the type is {@code char} or {@code Character}. - * - * @param type a type - * @return true if the type is {@code char} or {@code Character} - */ - public static boolean isCharType(TypeMirror type) { - return type.getKind() == TypeKind.CHAR || isDeclaredOfName(type, "java.lang.Character"); - } - - /** - * Returns true iff the type represents a declared type of the given qualified name. - * - * @param type the type - * @param qualifiedName the name to check {@code type} against - * @return true iff type represents a declared type of the qualified name - */ - public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) { - return type.getKind() == TypeKind.DECLARED - && getQualifiedName((DeclaredType) type).contentEquals(qualifiedName); - } - - /** - * Returns true iff the type represents a declared type whose fully-qualified name is any of the - * given names. - * - * @param type the type - * @param qualifiedNames fully-qualified type names to check for - * @return true iff type represents a declared type whose fully-qualified name is one of the - * given names - */ - public static boolean isDeclaredOfName(TypeMirror type, Collection qualifiedNames) { - return type.getKind() == TypeKind.DECLARED - && qualifiedNames.contains(getQualifiedName((DeclaredType) type)); - } - - /** - * Returns true iff the {@code type} represents a boxed primitive type. - * - * @param type the type to check - * @return true iff type represents a boxed primitive type - */ - public static boolean isBoxedPrimitive(TypeMirror type) { - return TypeKindUtils.boxedToTypeKind(type) != null; - } - - /** - * Returns true iff this is an immutable type in the JDK. - * - *

          This does not use immutability annotations and always returns false for user-defined - * classes. - * - * @param type the type to check - * @return true iff this is an immutable type in the JDK - */ - public static boolean isImmutableTypeInJdk(TypeMirror type) { - return isPrimitive(type) - || (type.getKind() == TypeKind.DECLARED - && ImmutableTypes.isImmutable(getQualifiedName((DeclaredType) type))); - } - - /** - * Returns true iff type represents a Throwable type (e.g. Exception, Error). - * - * @param type the type to check - * @return true iff type represents a Throwable type (e.g. Exception, Error) - */ - public static boolean isThrowable(TypeMirror type) { - while (type != null && type.getKind() == TypeKind.DECLARED) { - DeclaredType dt = (DeclaredType) type; - TypeElement elem = (TypeElement) dt.asElement(); - Name name = elem.getQualifiedName(); - if ("java.lang.Throwable".contentEquals(name)) { - return true; - } - type = elem.getSuperclass(); - } + } + + /** + * Returns the binary name of a type. + * + * @param type a type + * @return its binary name + */ + public static @BinaryName String binaryName(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + throw new BugInCF("Only declared types have a binary name"); + } + return ElementUtils.getBinaryName((TypeElement) ((DeclaredType) type).asElement()); + } + + /** + * Returns the type element for {@code type} if {@code type} is a class, interface, annotation + * type, or enum. Otherwise, returns null. + * + * @param type whose element is returned + * @return the type element for {@code type} if {@code type} is a class, interface, annotation + * type, or enum; otherwise, returns {@code null} + */ + public static @Nullable TypeElement getTypeElement(TypeMirror type) { + Element element = ((Type) type).asElement(); + if (element == null) { + return null; + } + if (ElementUtils.isTypeElement(element)) { + return (TypeElement) element; + } + return null; + } + + /** + * Given an array type, returns the type with all array levels stripped off. + * + * @param at an array type + * @return the type with all array levels stripped off + */ + public static TypeMirror getInnermostComponentType(ArrayType at) { + TypeMirror result = at; + while (result.getKind() == TypeKind.ARRAY) { + result = ((ArrayType) result).getComponentType(); + } + return result; + } + + /// Equality + + /** + * Returns true iff the arguments are both the same declared types. + * + *

          This is needed because class {@code Type.ClassType} does not override equals. + * + * @param t1 the first type to test + * @param t2 the second type to test + * @return whether the arguments are the same declared types + */ + public static boolean areSameDeclaredTypes(Type.ClassType t1, Type.ClassType t2) { + // Do a cheaper test first + if (t1.tsym.name != t2.tsym.name) { + return false; + } + return t1.toString().equals(t2.toString()); + } + + /** + * Returns true iff the arguments are both the same primitive type. + * + * @param left a type + * @param right a type + * @return whether the arguments are the same primitive type + */ + public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) { + if (!isPrimitive(left) || !isPrimitive(right)) { + return false; + } + + return (left.getKind() == right.getKind()); + } + + /// Predicates + + /** + * Returns true iff the type represents a java.lang.Object declared type. + * + * @param type the type to check + * @return true iff type represents java.lang.Object + */ + public static boolean isObject(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.Object"); + } + + /** + * Return true iff the type represents a java.lang.Class declared type. + * + * @param type the type to check + * @return true iff type represents java.lang.Class + */ + public static boolean isClass(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.Class"); + } + + /** + * Returns true iff the type represents a java.lang.String declared type. + * + * @param type the type to check + * @return true iff type represents java.lang.String + */ + public static boolean isString(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.String"); + } + + /** + * Returns true if the type is either {@code boolean} (primitive type) or {@code + * java.lang.Boolean}. + * + * @param type the type to check + * @return true iff type represents a boolean type + */ + public static boolean isBooleanType(TypeMirror type) { + return type.getKind() == TypeKind.BOOLEAN || isDeclaredOfName(type, "java.lang.Boolean"); + } + + /** + * Returns true if the type is {@code char} or {@code Character}. + * + * @param type a type + * @return true if the type is {@code char} or {@code Character} + */ + public static boolean isCharType(TypeMirror type) { + return type.getKind() == TypeKind.CHAR || isDeclaredOfName(type, "java.lang.Character"); + } + + /** + * Returns true iff the type represents a declared type of the given qualified name. + * + * @param type the type + * @param qualifiedName the name to check {@code type} against + * @return true iff type represents a declared type of the qualified name + */ + public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) { + return type.getKind() == TypeKind.DECLARED + && getQualifiedName((DeclaredType) type).contentEquals(qualifiedName); + } + + /** + * Returns true iff the type represents a declared type whose fully-qualified name is any of the + * given names. + * + * @param type the type + * @param qualifiedNames fully-qualified type names to check for + * @return true iff type represents a declared type whose fully-qualified name is one of the given + * names + */ + public static boolean isDeclaredOfName(TypeMirror type, Collection qualifiedNames) { + return type.getKind() == TypeKind.DECLARED + && qualifiedNames.contains(getQualifiedName((DeclaredType) type)); + } + + /** + * Returns true iff the {@code type} represents a boxed primitive type. + * + * @param type the type to check + * @return true iff type represents a boxed primitive type + */ + public static boolean isBoxedPrimitive(TypeMirror type) { + return TypeKindUtils.boxedToTypeKind(type) != null; + } + + /** + * Returns true iff this is an immutable type in the JDK. + * + *

          This does not use immutability annotations and always returns false for user-defined + * classes. + * + * @param type the type to check + * @return true iff this is an immutable type in the JDK + */ + public static boolean isImmutableTypeInJdk(TypeMirror type) { + return isPrimitive(type) + || (type.getKind() == TypeKind.DECLARED + && ImmutableTypes.isImmutable(getQualifiedName((DeclaredType) type))); + } + + /** + * Returns true iff type represents a Throwable type (e.g. Exception, Error). + * + * @param type the type to check + * @return true iff type represents a Throwable type (e.g. Exception, Error) + */ + public static boolean isThrowable(TypeMirror type) { + while (type != null && type.getKind() == TypeKind.DECLARED) { + DeclaredType dt = (DeclaredType) type; + TypeElement elem = (TypeElement) dt.asElement(); + Name name = elem.getQualifiedName(); + if ("java.lang.Throwable".contentEquals(name)) { + return true; + } + type = elem.getSuperclass(); + } + return false; + } + + /** + * Returns true iff the argument is an anonymous type. + * + * @param type the type to check + * @return whether the argument is an anonymous type + */ + public static boolean isAnonymous(TypeMirror type) { + return (type instanceof DeclaredType) + && ((TypeElement) ((DeclaredType) type).asElement()).getNestingKind() + == NestingKind.ANONYMOUS; + } + + /** + * Returns true iff the argument is a primitive type. + * + * @param type the type to check + * @return whether the argument is a primitive type + */ + public static boolean isPrimitive(TypeMirror type) { + return type.getKind().isPrimitive(); + } + + /** + * Returns true iff the argument is a primitive type or a boxed primitive type. + * + * @param type a type + * @return true if the argument is a primitive type or a boxed primitive type + */ + public static boolean isPrimitiveOrBoxed(TypeMirror type) { + return isPrimitive(type) || isBoxedPrimitive(type); + } + + /** + * Returns true iff the argument is a primitive numeric type. + * + * @param type a type + * @return true if the argument is a primitive numeric type + */ + public static boolean isNumeric(TypeMirror type) { + return TypeKindUtils.isNumeric(type.getKind()); + } + + /** + * Returns true iff the argument is a boxed numeric type. + * + * @param type a type + * @return true if the argument is a boxed numeric type + */ + public static boolean isNumericBoxed(TypeMirror type) { + TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); + return boxedPrimitiveKind != null && TypeKindUtils.isNumeric(boxedPrimitiveKind); + } + + /** + * Returns true iff the argument is an integral primitive type. + * + * @param type a type + * @return whether the argument is an integral primitive type + */ + public static boolean isIntegralPrimitive(TypeMirror type) { + return TypeKindUtils.isIntegral(type.getKind()); + } + + /** + * Return true if the argument TypeMirror is a (possibly boxed) integral type. + * + * @param type the type to inspect + * @return true if type is an integral type + */ + public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) { + TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + return kind != null && TypeKindUtils.isIntegral(kind); + } + + /** + * Returns true if declaredType is a Class that is used to box primitive type (e.g. + * declaredType=java.lang.Double and primitiveType=22.5d ) + * + * @param declaredType a type that might be a boxed type + * @param primitiveType a type that might be a primitive type + * @return true if {@code declaredType} is a box of {@code primitiveType} + */ + public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) { + TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(declaredType); + return boxedPrimitiveKind == primitiveType.getKind(); + } + + /** + * Returns true iff the argument is a boxed floating point type. + * + * @param type type to test + * @return whether the argument is a boxed floating point type + */ + public static boolean isBoxedFloating(TypeMirror type) { + TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); + return boxedPrimitiveKind != null && TypeKindUtils.isFloatingPoint(boxedPrimitiveKind); + } + + /** + * Returns true iff the argument is a primitive floating point type. + * + * @param type type mirror + * @return whether the argument is a primitive floating point type + */ + public static boolean isFloatingPrimitive(TypeMirror type) { + return TypeKindUtils.isFloatingPoint(type.getKind()); + } + + /** + * Return true if the argument TypeMirror is a (possibly boxed) floating point type. + * + * @param type the type to inspect + * @return true if type is a floating point type + */ + public static boolean isFloatingPoint(TypeMirror type) { + TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + return kind != null && TypeKindUtils.isFloatingPoint(kind); + } + + /** + * Returns whether a TypeMirror represents a class type. + * + * @param type a type that might be a class type + * @return true if {@code} is a class type + */ + public static boolean isClassType(TypeMirror type) { + return (type instanceof Type.ClassType); + } + + /** + * Returns whether or not {@code type} is a functional interface type (as defined in JLS 9.8). + * + * @param type possible functional interface type + * @param env the processing environment + * @return whether or not {@code type} is a functional interface type (as defined in JLS 9.8) + */ + public static boolean isFunctionalInterface(TypeMirror type, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return javacTypes.isFunctionalInterface((Type) type); + } + + /** + * Returns true if the given type is a compound type. + * + * @param type a type + * @return true if the given type is a compound type + */ + public static boolean isCompoundType(TypeMirror type) { + switch (type.getKind()) { + case ARRAY: + case EXECUTABLE: + case INTERSECTION: + case UNION: + case TYPEVAR: + case WILDCARD: + return true; + + case DECLARED: + DeclaredType declaredType = (DeclaredType) type; + return !declaredType.getTypeArguments().isEmpty(); + + default: return false; } - - /** - * Returns true iff the argument is an anonymous type. - * - * @param type the type to check - * @return whether the argument is an anonymous type - */ - public static boolean isAnonymous(TypeMirror type) { - return (type instanceof DeclaredType) - && ((TypeElement) ((DeclaredType) type).asElement()).getNestingKind() - == NestingKind.ANONYMOUS; - } - - /** - * Returns true iff the argument is a primitive type. - * - * @param type the type to check - * @return whether the argument is a primitive type - */ - public static boolean isPrimitive(TypeMirror type) { - return type.getKind().isPrimitive(); - } - - /** - * Returns true iff the argument is a primitive type or a boxed primitive type. - * - * @param type a type - * @return true if the argument is a primitive type or a boxed primitive type - */ - public static boolean isPrimitiveOrBoxed(TypeMirror type) { - return isPrimitive(type) || isBoxedPrimitive(type); - } - - /** - * Returns true iff the argument is a primitive numeric type. - * - * @param type a type - * @return true if the argument is a primitive numeric type - */ - public static boolean isNumeric(TypeMirror type) { - return TypeKindUtils.isNumeric(type.getKind()); - } - - /** - * Returns true iff the argument is a boxed numeric type. - * - * @param type a type - * @return true if the argument is a boxed numeric type - */ - public static boolean isNumericBoxed(TypeMirror type) { - TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); - return boxedPrimitiveKind != null && TypeKindUtils.isNumeric(boxedPrimitiveKind); - } - - /** - * Returns true iff the argument is an integral primitive type. - * - * @param type a type - * @return whether the argument is an integral primitive type - */ - public static boolean isIntegralPrimitive(TypeMirror type) { - return TypeKindUtils.isIntegral(type.getKind()); - } - - /** - * Return true if the argument TypeMirror is a (possibly boxed) integral type. - * - * @param type the type to inspect - * @return true if type is an integral type - */ - public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) { - TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - return kind != null && TypeKindUtils.isIntegral(kind); - } - - /** - * Returns true if declaredType is a Class that is used to box primitive type (e.g. - * declaredType=java.lang.Double and primitiveType=22.5d ) - * - * @param declaredType a type that might be a boxed type - * @param primitiveType a type that might be a primitive type - * @return true if {@code declaredType} is a box of {@code primitiveType} - */ - public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) { - TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(declaredType); - return boxedPrimitiveKind == primitiveType.getKind(); - } - - /** - * Returns true iff the argument is a boxed floating point type. - * - * @param type type to test - * @return whether the argument is a boxed floating point type - */ - public static boolean isBoxedFloating(TypeMirror type) { - TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); - return boxedPrimitiveKind != null && TypeKindUtils.isFloatingPoint(boxedPrimitiveKind); - } - - /** - * Returns true iff the argument is a primitive floating point type. - * - * @param type type mirror - * @return whether the argument is a primitive floating point type - */ - public static boolean isFloatingPrimitive(TypeMirror type) { - return TypeKindUtils.isFloatingPoint(type.getKind()); - } - - /** - * Return true if the argument TypeMirror is a (possibly boxed) floating point type. - * - * @param type the type to inspect - * @return true if type is a floating point type - */ - public static boolean isFloatingPoint(TypeMirror type) { - TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - return kind != null && TypeKindUtils.isFloatingPoint(kind); - } - - /** - * Returns whether a TypeMirror represents a class type. - * - * @param type a type that might be a class type - * @return true if {@code} is a class type - */ - public static boolean isClassType(TypeMirror type) { - return (type instanceof Type.ClassType); - } - - /** - * Returns whether or not {@code type} is a functional interface type (as defined in JLS 9.8). - * - * @param type possible functional interface type - * @param env the processing environment - * @return whether or not {@code type} is a functional interface type (as defined in JLS 9.8) - */ - public static boolean isFunctionalInterface(TypeMirror type, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); - return javacTypes.isFunctionalInterface((Type) type); - } - - /** - * Returns true if the given type is a compound type. - * - * @param type a type - * @return true if the given type is a compound type - */ - public static boolean isCompoundType(TypeMirror type) { - switch (type.getKind()) { - case ARRAY: - case EXECUTABLE: - case INTERSECTION: - case UNION: - case TYPEVAR: - case WILDCARD: - return true; - - case DECLARED: - DeclaredType declaredType = (DeclaredType) type; - return !declaredType.getTypeArguments().isEmpty(); - - default: - return false; - } - } - - /** - * Returns true if {@code type} has an enclosing type. - * - * @param type type to checker - * @return true if {@code type} has an enclosing type - */ - public static boolean hasEnclosingType(TypeMirror type) { - Type e = ((Type) type).getEnclosingType(); - return e.getKind() != TypeKind.NONE; - } - - /// Type variables and wildcards - - /** - * If the argument is a bounded TypeVariable or WildcardType, return its non-variable, - * non-wildcard upper bound. Otherwise, return the type itself. - * - * @param type a type - * @return the non-variable, non-wildcard upper bound of a type, if it has one, or itself if it - * has no bounds - */ - public static TypeMirror upperBound(TypeMirror type) { - do { - if (type instanceof TypeVariable) { - TypeVariable tvar = (TypeVariable) type; - if (tvar.getUpperBound() != null) { - type = tvar.getUpperBound(); - } else { - break; - } - } else if (type instanceof WildcardType) { - WildcardType wc = (WildcardType) type; - if (wc.getExtendsBound() != null) { - type = wc.getExtendsBound(); - } else { - break; - } - } else { - break; - } - } while (true); - return type; - } - - /** - * Get the type parameter for this wildcard from the underlying type's bound field. This field - * is sometimes null, in that case this method will return null. - * - * @param wildcard wildcard type - * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise - */ - public static @Nullable TypeParameterElement wildcardToTypeParam(WildcardType wildcard) { - return wildcardToTypeParam((Type.WildcardType) wildcard); - } - - /** - * Get the type parameter for this wildcard from the underlying type's bound field. This field - * is sometimes null, in that case this method will return null. - * - * @param wildcard wildcard type - * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise - */ - public static @Nullable TypeParameterElement wildcardToTypeParam(Type.WildcardType wildcard) { - - final Element typeParamElement; - if (wildcard.bound != null) { - typeParamElement = wildcard.bound.asElement(); + } + + /** + * Returns true if {@code type} has an enclosing type. + * + * @param type type to checker + * @return true if {@code type} has an enclosing type + */ + public static boolean hasEnclosingType(TypeMirror type) { + Type e = ((Type) type).getEnclosingType(); + return e.getKind() != TypeKind.NONE; + } + + /// Type variables and wildcards + + /** + * If the argument is a bounded TypeVariable or WildcardType, return its non-variable, + * non-wildcard upper bound. Otherwise, return the type itself. + * + * @param type a type + * @return the non-variable, non-wildcard upper bound of a type, if it has one, or itself if it + * has no bounds + */ + public static TypeMirror upperBound(TypeMirror type) { + do { + if (type instanceof TypeVariable) { + TypeVariable tvar = (TypeVariable) type; + if (tvar.getUpperBound() != null) { + type = tvar.getUpperBound(); } else { - typeParamElement = null; + break; } - - return (TypeParameterElement) typeParamElement; - } - - /** - * Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) that works with both jdk8 - * (called upperBound there) and jdk8u. - */ - // TODO: contrast to upperBound. - public static Type wildUpperBound(TypeMirror tm, ProcessingEnvironment env) { - Type t = (Type) tm; - if (t.hasTag(TypeTag.WILDCARD)) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); - if (w.isSuperBound()) { // returns true if w is unbound - Symtab syms = Symtab.instance(context); - // w.bound is null if the wildcard is from bytecode. - return w.bound == null ? syms.objectType : w.bound.getUpperBound(); - } else { - return wildUpperBound(w.type, env); - } + } else if (type instanceof WildcardType) { + WildcardType wc = (WildcardType) type; + if (wc.getExtendsBound() != null) { + type = wc.getExtendsBound(); } else { - return TypeAnnotationUtils.unannotatedType(t); + break; } - } - - /** - * Returns the {@code DeclaredType} for {@code java.lang.Object}. - * - * @param env {@link ProcessingEnvironment} - * @return the {@code DeclaredType} for {@code java.lang.Object} - */ - public static DeclaredType getObjectTypeMirror(ProcessingEnvironment env) { - Context context = ((JavacProcessingEnvironment) env).getContext(); + } else { + break; + } + } while (true); + return type; + } + + /** + * Get the type parameter for this wildcard from the underlying type's bound field. This field is + * sometimes null, in that case this method will return null. + * + * @param wildcard wildcard type + * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise + */ + public static @Nullable TypeParameterElement wildcardToTypeParam(WildcardType wildcard) { + return wildcardToTypeParam((Type.WildcardType) wildcard); + } + + /** + * Get the type parameter for this wildcard from the underlying type's bound field. This field is + * sometimes null, in that case this method will return null. + * + * @param wildcard wildcard type + * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise + */ + public static @Nullable TypeParameterElement wildcardToTypeParam(Type.WildcardType wildcard) { + + final Element typeParamElement; + if (wildcard.bound != null) { + typeParamElement = wildcard.bound.asElement(); + } else { + typeParamElement = null; + } + + return (TypeParameterElement) typeParamElement; + } + + /** + * Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) that works with both jdk8 + * (called upperBound there) and jdk8u. + */ + // TODO: contrast to upperBound. + public static Type wildUpperBound(TypeMirror tm, ProcessingEnvironment env) { + Type t = (Type) tm; + if (t.hasTag(TypeTag.WILDCARD)) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); + if (w.isSuperBound()) { // returns true if w is unbound Symtab syms = Symtab.instance(context); - return (DeclaredType) syms.objectType; - } - - /** - * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8 - * (called upperBound there) and jdk8u. - */ - public static Type wildLowerBound(TypeMirror tm, ProcessingEnvironment env) { - Type t = (Type) tm; - if (t.hasTag(TypeTag.WILDCARD)) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - Symtab syms = Symtab.instance(context); - Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); - return w.isExtendsBound() ? syms.botType : wildLowerBound(w.type, env); - } else { - return TypeAnnotationUtils.unannotatedType(t); - } - } - - /** - * Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If the - * bounded type extends other bounded types, this method will iterate through their bounds until - * a class, interface, or intersection is found. - * - * @return a type that is not a wildcard or typevar, or {@code null} if this type is an - * unbounded wildcard - */ - public static @Nullable TypeMirror findConcreteUpperBound(TypeMirror boundedType) { - TypeMirror effectiveUpper = boundedType; - outerLoop: - while (true) { - switch (effectiveUpper.getKind()) { - case WILDCARD: - effectiveUpper = - ((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound(); - if (effectiveUpper == null) { - return null; - } - break; - - case TYPEVAR: - effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound(); - break; - - default: - break outerLoop; - } - } - return effectiveUpper; - } - - // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. - // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". - - /** - * Returns true if {@code type} is an unbounded wildcard. - * - * @param type the type to check - * @return true if the given type is an unbounded wildcard - */ - public static boolean hasNoExplicitBound(TypeMirror type) { - return type.getKind() == TypeKind.WILDCARD - && ((Type.WildcardType) type).kind == BoundKind.UNBOUND; - } - - /** - * Returns true if {@code type} is a wildcard with an explicit super bound. - * - * @param type the {@code type} to test - * @return true if {@code type} is explicitly super bounded - */ - public static boolean hasExplicitSuperBound(TypeMirror type) { - return type.getKind() == TypeKind.WILDCARD - && !hasNoExplicitBound(type) - && ((Type.WildcardType) type).isSuperBound(); - } - - /** - * Returns true if {@code type} is a wildcard with an explicit extends bound. - * - * @param type the type to test - * @return true if {@code type} is a wildcard with an explicit extends bound - */ - public static boolean hasExplicitExtendsBound(TypeMirror type) { - return type.getKind() == TypeKind.WILDCARD - && !hasNoExplicitBound(type) - && ((Type.WildcardType) type).isExtendsBound(); - } - - /** - * Returns true if this type is super bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is super bounded or unbounded - */ - public static boolean isUnboundedOrSuperBounded(WildcardType wildcardType) { - return ((Type.WildcardType) wildcardType).isSuperBound(); - } - - /** - * Returns true if this type is extends bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is extends bounded or unbounded - */ - public static boolean isUnboundedOrExtendsBounded(WildcardType wildcardType) { - return ((Type.WildcardType) wildcardType).isExtendsBound(); - } - - /** - * Returns true if the erased type of {@code subtype} is a subtype of the erased type of {@code - * supertype}. - * - * @param subtype possible subtype - * @param supertype possible supertype - * @param types a Types object - * @return true if the erased type of subtype is a subtype of the erased type of supertype - */ - public static boolean isErasedSubtype(TypeMirror subtype, TypeMirror supertype, Types types) { - return types.isSubtype(types.erasure(subtype), types.erasure(supertype)); - } - - /** - * Returns true if {@code type} is a type variable created during capture conversion. - * - * @param type a type mirror - * @return true if {@code type} is a type variable created during capture conversion - */ - public static boolean isCapturedTypeVariable(TypeMirror type) { - if (type.getKind() != TypeKind.TYPEVAR) { - return false; - } - return ((Type.TypeVar) TypeAnnotationUtils.unannotatedType(type)).isCaptured(); - } - - /** - * If {@code typeVar} is a captured type variable, then returns its underlying wildcard; - * otherwise returns {@code null}. - * - * @param typeVar a type variable that might be a captured type variable - * @return {@code typeVar} is a captured type variable, then returns its underlying wildcard; - * otherwise returns {@code null} - */ - public static @Nullable WildcardType getCapturedWildcard(TypeVariable typeVar) { - if (isCapturedTypeVariable(typeVar)) { - return ((CapturedType) TypeAnnotationUtils.unannotatedType(typeVar)).wildcard; - } - return null; - } - - /// Least upper bound and greatest lower bound - - /** - * Returns the least upper bound of two {@link TypeMirror}s, ignoring any annotations on the - * types. - * - *

          Wrapper around Types.lub to add special handling for null types, primitives, and - * wildcards. - * - * @param tm1 a {@link TypeMirror} - * @param tm2 a {@link TypeMirror} - * @param processingEnv the {@link ProcessingEnvironment} to use - * @return the least upper bound of {@code tm1} and {@code tm2} - */ - public static TypeMirror leastUpperBound( - TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { - Type t1 = TypeAnnotationUtils.unannotatedType(tm1); - Type t2 = TypeAnnotationUtils.unannotatedType(tm2); - // Handle the 'null' type manually (not done by types.lub). - if (t1.getKind() == TypeKind.NULL) { - return t2; - } - if (t2.getKind() == TypeKind.NULL) { - return t1; - } - if (t1.getKind() == TypeKind.WILDCARD) { - WildcardType wc1 = (WildcardType) t1; - t1 = (Type) wc1.getExtendsBound(); - if (t1 == null) { - // Implicit upper bound of java.lang.Object - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - if (t2.getKind() == TypeKind.WILDCARD) { - WildcardType wc2 = (WildcardType) t2; - t2 = (Type) wc2.getExtendsBound(); - if (t2 == null) { - // Implicit upper bound of java.lang.Object - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - if (types.isSameType(t1, t2)) { - // Special case if the two types are equal. - return t1; - } - // Special case for primitives. - if (isPrimitive(t1) || isPrimitive(t2)) { - // NOTE: we need to know which type is primitive because e.g. int and Integer - // are assignable to each other. - if (isPrimitive(t1) && types.isAssignable(t1, t2)) { - return t2; - } else if (isPrimitive(t2) && types.isAssignable(t2, t1)) { - return t1; - } else { - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - - try { - return types.lub(t1, t2); - } catch (Exception e) { - // typetools issue #3025: In at least Java 8/9, types.lub throws an NPE - // on capture/wildcard combinations, see test case - // checker/tests/nullness/generics/Issue3025.java. - // Using j.l.Object is too coarse in case the type actually matters. - // This problem doesn't exist anymore in Java 11+, so let's - // see whether this is a problem for anyone in practice. - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - - /** - * Returns the greatest lower bound of two {@link TypeMirror}s, ignoring any annotations on the - * types. - * - *

          Wrapper around Types.glb to add special handling for null types, primitives, and - * wildcards. - * - * @param tm1 a {@link TypeMirror} - * @param tm2 a {@link TypeMirror} - * @param processingEnv the {@link ProcessingEnvironment} to use - * @return the greatest lower bound of {@code tm1} and {@code tm2} - */ - public static TypeMirror greatestLowerBound( - TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { - Type t1 = TypeAnnotationUtils.unannotatedType(tm1); - Type t2 = TypeAnnotationUtils.unannotatedType(tm2); - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - if (types.isSameType(t1, t2)) { - // Special case if the two types are equal. - return t1; - } - // Handle the 'null' type manually. - if (t1.getKind() == TypeKind.NULL) { - return t1; - } - if (t2.getKind() == TypeKind.NULL) { - return t2; - } - // Special case for primitives. - if (isPrimitive(t1) || isPrimitive(t2)) { - if (types.isAssignable(t1, t2)) { - return t1; - } else if (types.isAssignable(t2, t1)) { - return t2; - } else { - // Javac types.glb returns TypeKind.Error when the GLB does - // not exist, but we can't create one. Use TypeKind.NONE - // instead. - return processingEnv.getTypeUtils().getNoType(TypeKind.NONE); - } - } - if (t1.getKind() == TypeKind.WILDCARD) { - return t2; - } - if (t2.getKind() == TypeKind.WILDCARD) { - return t1; - } - - // If neither type is a primitive type, null type, or wildcard - // and if the types are not the same, use javac types.glb - return types.glb(t1, t2); - } - - /** - * Returns the most specific type from the list, or null if none exists. - * - * @param typeMirrors a list of types - * @param processingEnv the {@link ProcessingEnvironment} to use - * @return the most specific of the types, or null if none exists - */ - public static @Nullable TypeMirror mostSpecific( - List typeMirrors, ProcessingEnvironment processingEnv) { - if (typeMirrors.size() == 1) { - return typeMirrors.get(0); - } else { - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - com.sun.tools.javac.util.List typeList = typeMirrorListToTypeList(typeMirrors); - Type glb = types.glb(typeList); - for (Type candidate : typeList) { - if (types.isSameType(glb, candidate)) { - return candidate; - } - } + // w.bound is null if the wildcard is from bytecode. + return w.bound == null ? syms.objectType : w.bound.getUpperBound(); + } else { + return wildUpperBound(w.type, env); + } + } else { + return TypeAnnotationUtils.unannotatedType(t); + } + } + + /** + * Returns the {@code DeclaredType} for {@code java.lang.Object}. + * + * @param env {@link ProcessingEnvironment} + * @return the {@code DeclaredType} for {@code java.lang.Object} + */ + public static DeclaredType getObjectTypeMirror(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Symtab syms = Symtab.instance(context); + return (DeclaredType) syms.objectType; + } + + /** + * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8 + * (called upperBound there) and jdk8u. + */ + public static Type wildLowerBound(TypeMirror tm, ProcessingEnvironment env) { + Type t = (Type) tm; + if (t.hasTag(TypeTag.WILDCARD)) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Symtab syms = Symtab.instance(context); + Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); + return w.isExtendsBound() ? syms.botType : wildLowerBound(w.type, env); + } else { + return TypeAnnotationUtils.unannotatedType(t); + } + } + + /** + * Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If the + * bounded type extends other bounded types, this method will iterate through their bounds until a + * class, interface, or intersection is found. + * + * @return a type that is not a wildcard or typevar, or {@code null} if this type is an unbounded + * wildcard + */ + public static @Nullable TypeMirror findConcreteUpperBound(TypeMirror boundedType) { + TypeMirror effectiveUpper = boundedType; + outerLoop: + while (true) { + switch (effectiveUpper.getKind()) { + case WILDCARD: + effectiveUpper = ((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound(); + if (effectiveUpper == null) { return null; + } + break; + + case TYPEVAR: + effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound(); + break; + + default: + break outerLoop; + } + } + return effectiveUpper; + } + + // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. + // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". + + /** + * Returns true if {@code type} is an unbounded wildcard. + * + * @param type the type to check + * @return true if the given type is an unbounded wildcard + */ + public static boolean hasNoExplicitBound(TypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && ((Type.WildcardType) type).kind == BoundKind.UNBOUND; + } + + /** + * Returns true if {@code type} is a wildcard with an explicit super bound. + * + * @param type the {@code type} to test + * @return true if {@code type} is explicitly super bounded + */ + public static boolean hasExplicitSuperBound(TypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && !hasNoExplicitBound(type) + && ((Type.WildcardType) type).isSuperBound(); + } + + /** + * Returns true if {@code type} is a wildcard with an explicit extends bound. + * + * @param type the type to test + * @return true if {@code type} is a wildcard with an explicit extends bound + */ + public static boolean hasExplicitExtendsBound(TypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && !hasNoExplicitBound(type) + && ((Type.WildcardType) type).isExtendsBound(); + } + + /** + * Returns true if this type is super bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is super bounded or unbounded + */ + public static boolean isUnboundedOrSuperBounded(WildcardType wildcardType) { + return ((Type.WildcardType) wildcardType).isSuperBound(); + } + + /** + * Returns true if this type is extends bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is extends bounded or unbounded + */ + public static boolean isUnboundedOrExtendsBounded(WildcardType wildcardType) { + return ((Type.WildcardType) wildcardType).isExtendsBound(); + } + + /** + * Returns true if the erased type of {@code subtype} is a subtype of the erased type of {@code + * supertype}. + * + * @param subtype possible subtype + * @param supertype possible supertype + * @param types a Types object + * @return true if the erased type of subtype is a subtype of the erased type of supertype + */ + public static boolean isErasedSubtype(TypeMirror subtype, TypeMirror supertype, Types types) { + return types.isSubtype(types.erasure(subtype), types.erasure(supertype)); + } + + /** + * Returns true if {@code type} is a type variable created during capture conversion. + * + * @param type a type mirror + * @return true if {@code type} is a type variable created during capture conversion + */ + public static boolean isCapturedTypeVariable(TypeMirror type) { + if (type.getKind() != TypeKind.TYPEVAR) { + return false; + } + return ((Type.TypeVar) TypeAnnotationUtils.unannotatedType(type)).isCaptured(); + } + + /** + * If {@code typeVar} is a captured type variable, then returns its underlying wildcard; otherwise + * returns {@code null}. + * + * @param typeVar a type variable that might be a captured type variable + * @return {@code typeVar} is a captured type variable, then returns its underlying wildcard; + * otherwise returns {@code null} + */ + public static @Nullable WildcardType getCapturedWildcard(TypeVariable typeVar) { + if (isCapturedTypeVariable(typeVar)) { + return ((CapturedType) TypeAnnotationUtils.unannotatedType(typeVar)).wildcard; + } + return null; + } + + /// Least upper bound and greatest lower bound + + /** + * Returns the least upper bound of two {@link TypeMirror}s, ignoring any annotations on the + * types. + * + *

          Wrapper around Types.lub to add special handling for null types, primitives, and wildcards. + * + * @param tm1 a {@link TypeMirror} + * @param tm2 a {@link TypeMirror} + * @param processingEnv the {@link ProcessingEnvironment} to use + * @return the least upper bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror leastUpperBound( + TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + // Handle the 'null' type manually (not done by types.lub). + if (t1.getKind() == TypeKind.NULL) { + return t2; + } + if (t2.getKind() == TypeKind.NULL) { + return t1; + } + if (t1.getKind() == TypeKind.WILDCARD) { + WildcardType wc1 = (WildcardType) t1; + t1 = (Type) wc1.getExtendsBound(); + if (t1 == null) { + // Implicit upper bound of java.lang.Object + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + if (t2.getKind() == TypeKind.WILDCARD) { + WildcardType wc2 = (WildcardType) t2; + t2 = (Type) wc2.getExtendsBound(); + if (t2 == null) { + // Implicit upper bound of java.lang.Object + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + if (types.isSameType(t1, t2)) { + // Special case if the two types are equal. + return t1; + } + // Special case for primitives. + if (isPrimitive(t1) || isPrimitive(t2)) { + // NOTE: we need to know which type is primitive because e.g. int and Integer + // are assignable to each other. + if (isPrimitive(t1) && types.isAssignable(t1, t2)) { + return t2; + } else if (isPrimitive(t2) && types.isAssignable(t2, t1)) { + return t1; + } else { + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + + try { + return types.lub(t1, t2); + } catch (Exception e) { + // typetools issue #3025: In at least Java 8/9, types.lub throws an NPE + // on capture/wildcard combinations, see test case + // checker/tests/nullness/generics/Issue3025.java. + // Using j.l.Object is too coarse in case the type actually matters. + // This problem doesn't exist anymore in Java 11+, so let's + // see whether this is a problem for anyone in practice. + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + + /** + * Returns the greatest lower bound of two {@link TypeMirror}s, ignoring any annotations on the + * types. + * + *

          Wrapper around Types.glb to add special handling for null types, primitives, and wildcards. + * + * @param tm1 a {@link TypeMirror} + * @param tm2 a {@link TypeMirror} + * @param processingEnv the {@link ProcessingEnvironment} to use + * @return the greatest lower bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror greatestLowerBound( + TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + if (types.isSameType(t1, t2)) { + // Special case if the two types are equal. + return t1; + } + // Handle the 'null' type manually. + if (t1.getKind() == TypeKind.NULL) { + return t1; + } + if (t2.getKind() == TypeKind.NULL) { + return t2; + } + // Special case for primitives. + if (isPrimitive(t1) || isPrimitive(t2)) { + if (types.isAssignable(t1, t2)) { + return t1; + } else if (types.isAssignable(t2, t1)) { + return t2; + } else { + // Javac types.glb returns TypeKind.Error when the GLB does + // not exist, but we can't create one. Use TypeKind.NONE + // instead. + return processingEnv.getTypeUtils().getNoType(TypeKind.NONE); + } + } + if (t1.getKind() == TypeKind.WILDCARD) { + return t2; + } + if (t2.getKind() == TypeKind.WILDCARD) { + return t1; + } + + // If neither type is a primitive type, null type, or wildcard + // and if the types are not the same, use javac types.glb + return types.glb(t1, t2); + } + + /** + * Returns the most specific type from the list, or null if none exists. + * + * @param typeMirrors a list of types + * @param processingEnv the {@link ProcessingEnvironment} to use + * @return the most specific of the types, or null if none exists + */ + public static @Nullable TypeMirror mostSpecific( + List typeMirrors, ProcessingEnvironment processingEnv) { + if (typeMirrors.size() == 1) { + return typeMirrors.get(0); + } else { + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + com.sun.tools.javac.util.List typeList = typeMirrorListToTypeList(typeMirrors); + Type glb = types.glb(typeList); + for (Type candidate : typeList) { + if (types.isSameType(glb, candidate)) { + return candidate; } - } - - /** - * Given a list of TypeMirror, return a list of Type. - * - * @param typeMirrors a list of TypeMirrors - * @return the argument, converted to a javac list - */ - private static com.sun.tools.javac.util.List typeMirrorListToTypeList( - List typeMirrors) { - List typeList = CollectionsPlume.mapList(Type.class::cast, typeMirrors); - return com.sun.tools.javac.util.List.from(typeList); - } - - /// Substitutions - - /** - * Returns the return type of a method, given the receiver of the method call. - * - * @param methodElement a method - * @param substitutedReceiverType the receiver type, after substitution - * @param env the environment - * @return the return type of the method - */ - public static TypeMirror substituteMethodReturnType( - Element methodElement, TypeMirror substitutedReceiverType, ProcessingEnvironment env) { - - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(InternalUtils.getJavacContext(env)); - - Type substitutedMethodType = - types.memberType((Type) substitutedReceiverType, (Symbol) methodElement); - return substitutedMethodType.getReturnType(); - } - - /** - * Returns {@code type} as {@code superType} if {@code superType} is a super type of {@code - * type}; otherwise, null. - * - * @return {@code type} as {@code superType} if {@code superType} is a super type of {@code - * type}; otherwise, null - */ - public static @Nullable TypeMirror asSuper( - TypeMirror type, TypeMirror superType, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); - return javacTypes.asSuper((Type) type, ((Type) superType).tsym); - } - - /** - * Returns the superclass of the given class. Returns null if there is not one. - * - * @param type a type - * @param types type utilities - * @return the superclass of the given class, or null - */ - public static @Nullable TypeMirror getSuperclass(TypeMirror type, Types types) { - List superTypes = types.directSupertypes(type); - for (TypeMirror t : superTypes) { - // ignore interface types - if (!(t instanceof ClassType)) { - continue; - } - ClassType tt = (ClassType) t; - if (!tt.isInterface()) { - return t; - } + } + return null; + } + } + + /** + * Given a list of TypeMirror, return a list of Type. + * + * @param typeMirrors a list of TypeMirrors + * @return the argument, converted to a javac list + */ + private static com.sun.tools.javac.util.List typeMirrorListToTypeList( + List typeMirrors) { + List typeList = CollectionsPlume.mapList(Type.class::cast, typeMirrors); + return com.sun.tools.javac.util.List.from(typeList); + } + + /// Substitutions + + /** + * Returns the return type of a method, given the receiver of the method call. + * + * @param methodElement a method + * @param substitutedReceiverType the receiver type, after substitution + * @param env the environment + * @return the return type of the method + */ + public static TypeMirror substituteMethodReturnType( + Element methodElement, TypeMirror substitutedReceiverType, ProcessingEnvironment env) { + + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(InternalUtils.getJavacContext(env)); + + Type substitutedMethodType = + types.memberType((Type) substitutedReceiverType, (Symbol) methodElement); + return substitutedMethodType.getReturnType(); + } + + /** + * Returns {@code type} as {@code superType} if {@code superType} is a super type of {@code type}; + * otherwise, null. + * + * @return {@code type} as {@code superType} if {@code superType} is a super type of {@code type}; + * otherwise, null + */ + public static @Nullable TypeMirror asSuper( + TypeMirror type, TypeMirror superType, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return javacTypes.asSuper((Type) type, ((Type) superType).tsym); + } + + /** + * Returns the superclass of the given class. Returns null if there is not one. + * + * @param type a type + * @param types type utilities + * @return the superclass of the given class, or null + */ + public static @Nullable TypeMirror getSuperclass(TypeMirror type, Types types) { + List superTypes = types.directSupertypes(type); + for (TypeMirror t : superTypes) { + // ignore interface types + if (!(t instanceof ClassType)) { + continue; + } + ClassType tt = (ClassType) t; + if (!tt.isInterface()) { + return t; + } + } + return null; + } + + /** + * Returns the superclass the given type. If there is no superclass the first interface returned + * by {@link Types#directSupertypes(TypeMirror)} is returned. If the type has neither a superclass + * nor a superinterface, then null is returned. + * + * @param type a type + * @param types type utilities + * @return the superclass or super interface of the given type, or null + */ + public static @Nullable DeclaredType getSuperClassOrInterface(TypeMirror type, Types types) { + List superTypes = types.directSupertypes(type); + for (TypeMirror t : superTypes) { + if (t.getKind() == TypeKind.DECLARED) { + return (DeclaredType) t; + } + } + return null; + } + + /** + * Returns the type of primitive conversion from {@code from} to {@code to}. + * + * @param from a primitive type + * @param to a primitive type + * @return the type of primitive conversion from {@code from} to {@code to} + */ + public static TypeKindUtils.PrimitiveConversionKind getPrimitiveConversionKind( + PrimitiveType from, PrimitiveType to) { + return TypeKindUtils.getPrimitiveConversionKind(from.getKind(), to.getKind()); + } + + /** + * Returns a new type mirror with the same type as {@code type} where all the type variables in + * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs}. + * + *

          This is a wrapper around {@link com.sun.tools.javac.code.Types#subst(Type, + * com.sun.tools.javac.util.List, com.sun.tools.javac.util.List)}. + * + * @param type type to do substitution in + * @param typeVariables type variables that should be replaced with the type mirror at the same + * index of {@code typeArgs} + * @param typeArgs type mirrors that should replace the type variable at the same index of {@code + * typeVariables} + * @param env processing environment + * @return a new type mirror with the same type as {@code type} where all the type variables in + * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs} + */ + public static TypeMirror substitute( + TypeMirror type, + List typeVariables, + List typeArgs, + ProcessingEnvironment env) { + + List newP = CollectionsPlume.mapList(Type.class::cast, typeVariables); + + List newT = CollectionsPlume.mapList(Type.class::cast, typeArgs); + + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + return types.subst( + (Type) type, + com.sun.tools.javac.util.List.from(newP), + com.sun.tools.javac.util.List.from(newT)); + } + + /** + * Returns the depth of an array type. + * + * @param arrayType an array type + * @return the depth of {@code arrayType} + */ + public static int getArrayDepth(TypeMirror arrayType) { + int counter = 0; + TypeMirror type = arrayType; + while (type.getKind() == TypeKind.ARRAY) { + counter++; + type = ((ArrayType) type).getComponentType(); + } + return counter; + } + + /** + * If {@code typeMirror} is a wildcard, returns a fresh type variable that will be used as a + * captured type variable for it. If {@code typeMirror} is not a wildcard, returns {@code + * typeMirror}. + * + * @param typeMirror a type + * @param env processing environment + * @return a fresh type variable if {@code typeMirror} is a wildcard, otherwise {@code typeMirror} + */ + public static TypeMirror freshTypeVariable(TypeMirror typeMirror, ProcessingEnvironment env) { + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + return types.freshTypeVariables(com.sun.tools.javac.util.List.of((Type) typeMirror)).head; + } + + /** + * Returns the list of type variables such that a type variable in the list only references type + * variables at a lower index than itself. + * + * @param collection a collection of type variables + * @param types type utilities + * @return the type variables ordered so that each type variable only references earlier type + * variables + */ + public static List order(Collection collection, Types types) { + List list = new ArrayList<>(collection); + List ordered = new ArrayList<>(list.size()); + while (!list.isEmpty()) { + TypeVariable free = doesNotContainOthers(list, types); + list.remove(free); + ordered.add(free); + } + return ordered; + } + + /** + * Returns the first TypeVariable in {@code collection} that does not contain any other type in + * the collection. + * + * @param collection a collection of type variables + * @param types types + * @return the first TypeVariable in {@code collection} that does not contain any other type in + * the collection, but maybe itsself + */ + @SuppressWarnings("interning:not.interned") // must be the same object from collection + private static TypeVariable doesNotContainOthers( + Collection collection, Types types) { + for (TypeVariable candidate : collection) { + boolean doesNotContain = true; + for (TypeVariable other : collection) { + if (candidate != other && types.contains(candidate, other)) { + doesNotContain = false; + break; } - return null; - } - - /** - * Returns the superclass the given type. If there is no superclass the first interface returned - * by {@link Types#directSupertypes(TypeMirror)} is returned. If the type has neither a - * superclass nor a superinterface, then null is returned. - * - * @param type a type - * @param types type utilities - * @return the superclass or super interface of the given type, or null - */ - public static @Nullable DeclaredType getSuperClassOrInterface(TypeMirror type, Types types) { - List superTypes = types.directSupertypes(type); - for (TypeMirror t : superTypes) { - if (t.getKind() == TypeKind.DECLARED) { - return (DeclaredType) t; - } - } - return null; - } - - /** - * Returns the type of primitive conversion from {@code from} to {@code to}. - * - * @param from a primitive type - * @param to a primitive type - * @return the type of primitive conversion from {@code from} to {@code to} - */ - public static TypeKindUtils.PrimitiveConversionKind getPrimitiveConversionKind( - PrimitiveType from, PrimitiveType to) { - return TypeKindUtils.getPrimitiveConversionKind(from.getKind(), to.getKind()); - } - - /** - * Returns a new type mirror with the same type as {@code type} where all the type variables in - * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs}. - * - *

          This is a wrapper around {@link com.sun.tools.javac.code.Types#subst(Type, - * com.sun.tools.javac.util.List, com.sun.tools.javac.util.List)}. - * - * @param type type to do substitution in - * @param typeVariables type variables that should be replaced with the type mirror at the same - * index of {@code typeArgs} - * @param typeArgs type mirrors that should replace the type variable at the same index of - * {@code typeVariables} - * @param env processing environment - * @return a new type mirror with the same type as {@code type} where all the type variables in - * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs} - */ - public static TypeMirror substitute( - TypeMirror type, - List typeVariables, - List typeArgs, - ProcessingEnvironment env) { - - List newP = CollectionsPlume.mapList(Type.class::cast, typeVariables); - - List newT = CollectionsPlume.mapList(Type.class::cast, typeArgs); - - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - return types.subst( - (Type) type, - com.sun.tools.javac.util.List.from(newP), - com.sun.tools.javac.util.List.from(newT)); - } - - /** - * Returns the depth of an array type. - * - * @param arrayType an array type - * @return the depth of {@code arrayType} - */ - public static int getArrayDepth(TypeMirror arrayType) { - int counter = 0; - TypeMirror type = arrayType; - while (type.getKind() == TypeKind.ARRAY) { - counter++; - type = ((ArrayType) type).getComponentType(); - } - return counter; - } - - /** - * If {@code typeMirror} is a wildcard, returns a fresh type variable that will be used as a - * captured type variable for it. If {@code typeMirror} is not a wildcard, returns {@code - * typeMirror}. - * - * @param typeMirror a type - * @param env processing environment - * @return a fresh type variable if {@code typeMirror} is a wildcard, otherwise {@code - * typeMirror} - */ - public static TypeMirror freshTypeVariable(TypeMirror typeMirror, ProcessingEnvironment env) { - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - return types.freshTypeVariables(com.sun.tools.javac.util.List.of((Type) typeMirror)).head; - } - - /** - * Returns the list of type variables such that a type variable in the list only references type - * variables at a lower index than itself. - * - * @param collection a collection of type variables - * @param types type utilities - * @return the type variables ordered so that each type variable only references earlier type - * variables - */ - public static List order(Collection collection, Types types) { - List list = new ArrayList<>(collection); - List ordered = new ArrayList<>(list.size()); - while (!list.isEmpty()) { - TypeVariable free = doesNotContainOthers(list, types); - list.remove(free); - ordered.add(free); - } - return ordered; - } - - /** - * Returns the first TypeVariable in {@code collection} that does not contain any other type in - * the collection. - * - * @param collection a collection of type variables - * @param types types - * @return the first TypeVariable in {@code collection} that does not contain any other type in - * the collection, but maybe itsself - */ - @SuppressWarnings("interning:not.interned") // must be the same object from collection - private static TypeVariable doesNotContainOthers( - Collection collection, Types types) { - for (TypeVariable candidate : collection) { - boolean doesNotContain = true; - for (TypeVariable other : collection) { - if (candidate != other && types.contains(candidate, other)) { - doesNotContain = false; - break; - } - } - if (doesNotContain) { - return candidate; - } - } - throw new BugInCF("Not found: %s", StringsPlume.join(",", collection)); - } - - /** - * Returns true if the type is byte, short, char, Byte, Short, or Character. All other - * narrowings require a cast. See JLS 5.1.3. - * - * @param type a type - * @param types the type utilities - * @return true if assignment to the type may be a narrowing - */ - public static boolean canBeNarrowingPrimitiveConversion(TypeMirror type, Types types) { - // See CFGBuilder.CFGTranslationPhaseOne#conversionRequiresNarrowing() - TypeMirror unboxedType = isBoxedPrimitive(type) ? types.unboxedType(type) : type; - TypeKind unboxedKind = unboxedType.getKind(); - return unboxedKind == TypeKind.BYTE - || unboxedKind == TypeKind.SHORT - || unboxedKind == TypeKind.CHAR; - } - - /** - * Returns true if the two type variables are the same type variable. Meaning they have the same - * name and the same enclosing element. Unlike {@link Types#isSameType(TypeMirror, TypeMirror)}, - * they do not have to be the same object. - * - *

          This method is needed when a type has gone through type variable substitution, but only - * some of the type variables were substituted. Also, a new {@link TypeVariable} object is - * created as the type of a tree created by {@link - * org.checkerframework.javacutil.trees.TreeBuilder}. - * - * @param typeVariable1 a type variable - * @param typeVariable2 a type variable - * @return if the two type variables are the same type variable - */ - @EqualsMethod - public static boolean areSame(TypeVariable typeVariable1, TypeVariable typeVariable2) { - if (typeVariable1 == typeVariable2) { - return true; - } - Name otherName = typeVariable2.asElement().getSimpleName(); - Element otherEnclosingElement = typeVariable2.asElement().getEnclosingElement(); - - return typeVariable1.asElement().getSimpleName().contentEquals(otherName) - && otherEnclosingElement.equals(typeVariable1.asElement().getEnclosingElement()); - } + } + if (doesNotContain) { + return candidate; + } + } + throw new BugInCF("Not found: %s", StringsPlume.join(",", collection)); + } + + /** + * Returns true if the type is byte, short, char, Byte, Short, or Character. All other narrowings + * require a cast. See JLS 5.1.3. + * + * @param type a type + * @param types the type utilities + * @return true if assignment to the type may be a narrowing + */ + public static boolean canBeNarrowingPrimitiveConversion(TypeMirror type, Types types) { + // See CFGBuilder.CFGTranslationPhaseOne#conversionRequiresNarrowing() + TypeMirror unboxedType = isBoxedPrimitive(type) ? types.unboxedType(type) : type; + TypeKind unboxedKind = unboxedType.getKind(); + return unboxedKind == TypeKind.BYTE + || unboxedKind == TypeKind.SHORT + || unboxedKind == TypeKind.CHAR; + } + + /** + * Returns true if the two type variables are the same type variable. Meaning they have the same + * name and the same enclosing element. Unlike {@link Types#isSameType(TypeMirror, TypeMirror)}, + * they do not have to be the same object. + * + *

          This method is needed when a type has gone through type variable substitution, but only some + * of the type variables were substituted. Also, a new {@link TypeVariable} object is created as + * the type of a tree created by {@link org.checkerframework.javacutil.trees.TreeBuilder}. + * + * @param typeVariable1 a type variable + * @param typeVariable2 a type variable + * @return if the two type variables are the same type variable + */ + @EqualsMethod + public static boolean areSame(TypeVariable typeVariable1, TypeVariable typeVariable2) { + if (typeVariable1 == typeVariable2) { + return true; + } + Name otherName = typeVariable2.asElement().getSimpleName(); + Element otherEnclosingElement = typeVariable2.asElement().getEnclosingElement(); + + return typeVariable1.asElement().getSimpleName().contentEquals(otherName) + && otherEnclosingElement.equals(typeVariable1.asElement().getEnclosingElement()); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java b/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java index 440f0933b80..5e5d71b105a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java @@ -13,26 +13,26 @@ @SuppressWarnings("serial") public class UserError extends RuntimeException { - /** - * Constructs a new CheckerError with the specified detail message. - * - * @param message the detail message - */ - public UserError(String message) { - super(message); - if (message == null) { - throw new BugInCF("Must have a detail message."); - } + /** + * Constructs a new CheckerError with the specified detail message. + * + * @param message the detail message + */ + public UserError(String message) { + super(message); + if (message == null) { + throw new BugInCF("Must have a detail message."); } + } - /** - * Constructs a new CheckerError with a detail message composed from the given arguments. - * - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public UserError(String fmt, @Nullable Object... args) { - this(String.format(fmt, args)); - } + /** + * Constructs a new CheckerError with a detail message composed from the given arguments. + * + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public UserError(String fmt, @Nullable Object... args) { + this(String.format(fmt, args)); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java index 2edcbed71fd..2b08359b30a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java @@ -4,7 +4,6 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.util.Name; - import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,21 +13,21 @@ */ public class DetachedVarSymbol extends Symbol.VarSymbol { - protected @Nullable VariableTree decl; + protected @Nullable VariableTree decl; - /** Construct a detached variable symbol, given its flags, name, type and owner. */ - public DetachedVarSymbol(long flags, Name name, Type type, Symbol owner) { - super(flags, name, type, owner); - this.decl = null; - } + /** Construct a detached variable symbol, given its flags, name, type and owner. */ + public DetachedVarSymbol(long flags, Name name, Type type, Symbol owner) { + super(flags, name, type, owner); + this.decl = null; + } - /** Set the declaration tree for the variable. */ - public void setDeclaration(VariableTree decl) { - this.decl = decl; - } + /** Set the declaration tree for the variable. */ + public void setDeclaration(VariableTree decl) { + this.decl = decl; + } - /** Get the declaration tree for the variable. */ - public @Nullable VariableTree getDeclaration() { - return decl; - } + /** Get the declaration tree for the variable. */ + public @Nullable VariableTree getDeclaration() { + return decl; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java index de79021075f..87c3d7047be 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java @@ -23,14 +23,7 @@ import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Names; - -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; - import java.util.List; - import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -43,664 +36,653 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; /** * The TreeBuilder permits the creation of new AST Trees using the non-public Java compiler API * TreeMaker. */ public class TreeBuilder { - protected final Elements elements; - protected final Types modelTypes; - protected final com.sun.tools.javac.code.Types javacTypes; - protected final TreeMaker maker; - protected final Names names; - protected final Symtab symtab; - protected final ProcessingEnvironment env; - - public TreeBuilder(ProcessingEnvironment env) { - this.env = env; - Context context = ((JavacProcessingEnvironment) env).getContext(); - elements = env.getElementUtils(); - modelTypes = env.getTypeUtils(); - javacTypes = com.sun.tools.javac.code.Types.instance(context); - maker = TreeMaker.instance(context); - names = Names.instance(context); - symtab = Symtab.instance(context); + protected final Elements elements; + protected final Types modelTypes; + protected final com.sun.tools.javac.code.Types javacTypes; + protected final TreeMaker maker; + protected final Names names; + protected final Symtab symtab; + protected final ProcessingEnvironment env; + + public TreeBuilder(ProcessingEnvironment env) { + this.env = env; + Context context = ((JavacProcessingEnvironment) env).getContext(); + elements = env.getElementUtils(); + modelTypes = env.getTypeUtils(); + javacTypes = com.sun.tools.javac.code.Types.instance(context); + maker = TreeMaker.instance(context); + names = Names.instance(context); + symtab = Symtab.instance(context); + } + + /** + * Builds an AST Tree to access the iterator() method of some iterable expression. + * + * @param iterableExpr an expression whose type is a subtype of Iterable + * @return a MemberSelectTree that accesses the iterator() method of the expression + */ + public MemberSelectTree buildIteratorMethodAccess(ExpressionTree iterableExpr) { + DeclaredType exprType = (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(iterableExpr)); + assert exprType != null : "expression must be of declared type Iterable<>"; + + TypeElement exprElement = (TypeElement) exprType.asElement(); + + // Find the iterator() method of the iterable type + Symbol.MethodSymbol iteratorMethod = null; + + for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("iterator")) { + iteratorMethod = (Symbol.MethodSymbol) method; + } } - /** - * Builds an AST Tree to access the iterator() method of some iterable expression. - * - * @param iterableExpr an expression whose type is a subtype of Iterable - * @return a MemberSelectTree that accesses the iterator() method of the expression - */ - public MemberSelectTree buildIteratorMethodAccess(ExpressionTree iterableExpr) { - DeclaredType exprType = - (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(iterableExpr)); - assert exprType != null : "expression must be of declared type Iterable<>"; - - TypeElement exprElement = (TypeElement) exprType.asElement(); - - // Find the iterator() method of the iterable type - Symbol.MethodSymbol iteratorMethod = null; - - for (ExecutableElement method : - ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() - && method.getSimpleName().contentEquals("iterator")) { - iteratorMethod = (Symbol.MethodSymbol) method; - } - } - - assert iteratorMethod != null - : "@AssumeAssertion(nullness): no iterator method declared for expression type"; - - Type.MethodType methodType = (Type.MethodType) iteratorMethod.asType(); - Symbol.TypeSymbol methodClass = methodType.asElement(); - DeclaredType iteratorType = (DeclaredType) methodType.getReturnType(); - iteratorType = - (DeclaredType) - javacTypes.asSuper((Type) iteratorType, symtab.iteratorType.asElement()); - - int numIterTypeArgs = iteratorType.getTypeArguments().size(); - assert numIterTypeArgs <= 1 : "expected at most one type argument for Iterator"; - - if (numIterTypeArgs == 1) { - TypeMirror elementType = iteratorType.getTypeArguments().get(0); - // Remove captured type variable from a wildcard. - if (elementType instanceof Type.CapturedType) { - elementType = ((Type.CapturedType) elementType).wildcard; - TypeElement iteratorElt = (TypeElement) modelTypes.asElement(iteratorType); - assert iteratorElt != null - : "@AssumeAssertion(nullness): the iterator type always has an element"; - - iteratorType = modelTypes.getDeclaredType(iteratorElt, elementType); - } - } - - // Replace the iterator method's generic return type with - // the actual element type of the expression. - Type.MethodType updatedMethodType = - new Type.MethodType( - com.sun.tools.javac.util.List.nil(), - (Type) iteratorType, - com.sun.tools.javac.util.List.nil(), - methodClass); - - JCTree.JCFieldAccess iteratorAccess = TreeUtils.Select(maker, iterableExpr, iteratorMethod); - iteratorAccess.setType(updatedMethodType); - - return iteratorAccess; + assert iteratorMethod != null + : "@AssumeAssertion(nullness): no iterator method declared for expression type"; + + Type.MethodType methodType = (Type.MethodType) iteratorMethod.asType(); + Symbol.TypeSymbol methodClass = methodType.asElement(); + DeclaredType iteratorType = (DeclaredType) methodType.getReturnType(); + iteratorType = + (DeclaredType) javacTypes.asSuper((Type) iteratorType, symtab.iteratorType.asElement()); + + int numIterTypeArgs = iteratorType.getTypeArguments().size(); + assert numIterTypeArgs <= 1 : "expected at most one type argument for Iterator"; + + if (numIterTypeArgs == 1) { + TypeMirror elementType = iteratorType.getTypeArguments().get(0); + // Remove captured type variable from a wildcard. + if (elementType instanceof Type.CapturedType) { + elementType = ((Type.CapturedType) elementType).wildcard; + TypeElement iteratorElt = (TypeElement) modelTypes.asElement(iteratorType); + assert iteratorElt != null + : "@AssumeAssertion(nullness): the iterator type always has an element"; + + iteratorType = modelTypes.getDeclaredType(iteratorElt, elementType); + } } - /** - * Build a {@link MemberSelectTree} for accessing the {@code close} method of an expression that - * implements {@link AutoCloseable}. This method is used when desugaring try-with-resources - * statements during CFG construction. - * - * @param autoCloseableExpr the expression - * @return the member select tree - */ - public MemberSelectTree buildCloseMethodAccess(ExpressionTree autoCloseableExpr) { - DeclaredType exprType = - (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(autoCloseableExpr)); - assert exprType != null - : "expression must be of declared type AutoCloseable: " + autoCloseableExpr; - - TypeElement exprElement = (TypeElement) exprType.asElement(); - - // Find the close() method - Symbol.MethodSymbol closeMethod = null; - - for (ExecutableElement method : - ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("close")) { - closeMethod = (Symbol.MethodSymbol) method; - break; - } - } - - assert closeMethod != null - : "@AssumeAssertion(nullness): no close method declared for expression type"; - - JCTree.JCFieldAccess closeAccess = TreeUtils.Select(maker, autoCloseableExpr, closeMethod); - - return closeAccess; + // Replace the iterator method's generic return type with + // the actual element type of the expression. + Type.MethodType updatedMethodType = + new Type.MethodType( + com.sun.tools.javac.util.List.nil(), + (Type) iteratorType, + com.sun.tools.javac.util.List.nil(), + methodClass); + + JCTree.JCFieldAccess iteratorAccess = TreeUtils.Select(maker, iterableExpr, iteratorMethod); + iteratorAccess.setType(updatedMethodType); + + return iteratorAccess; + } + + /** + * Build a {@link MemberSelectTree} for accessing the {@code close} method of an expression that + * implements {@link AutoCloseable}. This method is used when desugaring try-with-resources + * statements during CFG construction. + * + * @param autoCloseableExpr the expression + * @return the member select tree + */ + public MemberSelectTree buildCloseMethodAccess(ExpressionTree autoCloseableExpr) { + DeclaredType exprType = + (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(autoCloseableExpr)); + assert exprType != null + : "expression must be of declared type AutoCloseable: " + autoCloseableExpr; + + TypeElement exprElement = (TypeElement) exprType.asElement(); + + // Find the close() method + Symbol.MethodSymbol closeMethod = null; + + for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("close")) { + closeMethod = (Symbol.MethodSymbol) method; + break; + } } - /** - * Builds an AST Tree to access the hasNext() method of an iterator. - * - * @param iteratorExpr an expression whose type is a subtype of Iterator - * @return a MemberSelectTree that accesses the hasNext() method of the expression - */ - public MemberSelectTree buildHasNextMethodAccess(ExpressionTree iteratorExpr) { - DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); - assert exprType != null : "expression must be of declared type Iterator<>"; - - TypeElement exprElement = (TypeElement) exprType.asElement(); - - // Find the hasNext() method of the iterator type - Symbol.MethodSymbol hasNextMethod = null; - - for (ExecutableElement method : - ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() - && method.getSimpleName().contentEquals("hasNext")) { - hasNextMethod = (Symbol.MethodSymbol) method; - break; - } - } + assert closeMethod != null + : "@AssumeAssertion(nullness): no close method declared for expression type"; - if (hasNextMethod == null) { - throw new BugInCF("no hasNext method declared for " + exprElement); - } + JCTree.JCFieldAccess closeAccess = TreeUtils.Select(maker, autoCloseableExpr, closeMethod); - JCTree.JCFieldAccess hasNextAccess = TreeUtils.Select(maker, iteratorExpr, hasNextMethod); - hasNextAccess.setType(hasNextMethod.asType()); + return closeAccess; + } - return hasNextAccess; - } + /** + * Builds an AST Tree to access the hasNext() method of an iterator. + * + * @param iteratorExpr an expression whose type is a subtype of Iterator + * @return a MemberSelectTree that accesses the hasNext() method of the expression + */ + public MemberSelectTree buildHasNextMethodAccess(ExpressionTree iteratorExpr) { + DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); + assert exprType != null : "expression must be of declared type Iterator<>"; - /** - * Builds an AST Tree to access the next() method of an iterator. - * - * @param iteratorExpr an expression whose type is a subtype of Iterator - * @return a MemberSelectTree that accesses the next() method of the expression - */ - public MemberSelectTree buildNextMethodAccess(ExpressionTree iteratorExpr) { - DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); - assert exprType != null : "expression must be of declared type Iterator<>"; - - TypeElement exprElement = (TypeElement) exprType.asElement(); - - // Find the next() method of the iterator type - Symbol.MethodSymbol nextMethod = null; - - for (ExecutableElement method : - ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("next")) { - nextMethod = (Symbol.MethodSymbol) method; - } - } - - assert nextMethod != null - : "@AssumeAssertion(nullness): no next method declared for expression type"; + TypeElement exprElement = (TypeElement) exprType.asElement(); - Type.MethodType methodType = (Type.MethodType) nextMethod.asType(); - Symbol.TypeSymbol methodClass = methodType.asElement(); - Type elementType; - - if (exprType.getTypeArguments().isEmpty()) { - elementType = symtab.objectType; - } else { - elementType = (Type) exprType.getTypeArguments().get(0); - } + // Find the hasNext() method of the iterator type + Symbol.MethodSymbol hasNextMethod = null; - // Replace the next method's generic return type with - // the actual element type of the expression. - Type.MethodType updatedMethodType = - new Type.MethodType( - com.sun.tools.javac.util.List.nil(), - elementType, - com.sun.tools.javac.util.List.nil(), - methodClass); - - JCTree.JCFieldAccess nextAccess = TreeUtils.Select(maker, iteratorExpr, nextMethod); - nextAccess.setType(updatedMethodType); - - return nextAccess; + for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("hasNext")) { + hasNextMethod = (Symbol.MethodSymbol) method; + break; + } } - /** - * Builds an AST Tree to dereference the length field of an array. - * - * @param expression the array expression whose length is being accessed - * @return a MemberSelectTree to dereference the length of the array - */ - public MemberSelectTree buildArrayLengthAccess(ExpressionTree expression) { - return TreeUtils.Select(maker, expression, symtab.lengthVar); + if (hasNextMethod == null) { + throw new BugInCF("no hasNext method declared for " + exprElement); } - /** - * Builds an AST Tree to call a method designated by the argument expression. - * - * @param methodExpr an expression denoting a method with no arguments - * @return a MethodInvocationTree to call the argument method - */ - public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr) { - return maker.App((JCTree.JCExpression) methodExpr); - } + JCTree.JCFieldAccess hasNextAccess = TreeUtils.Select(maker, iteratorExpr, hasNextMethod); + hasNextAccess.setType(hasNextMethod.asType()); - /** - * Builds an AST Tree to call a method designated by methodExpr, with one argument designated by - * argExpr. - * - * @param methodExpr an expression denoting a method with one argument - * @param argExpr an expression denoting an argument to the method - * @return a MethodInvocationTree to call the argument method - */ - public MethodInvocationTree buildMethodInvocation( - ExpressionTree methodExpr, ExpressionTree argExpr) { - return maker.App( - (JCTree.JCExpression) methodExpr, - com.sun.tools.javac.util.List.of((JCTree.JCExpression) argExpr)); - } - - /** - * Builds an AST Tree to declare and initialize a variable, with no modifiers. - * - * @param type the type of the variable - * @param name the name of the variable - * @param owner the element containing the new symbol - * @param initializer the initializer expression - * @return a VariableDeclTree declaring the new variable - */ - public VariableTree buildVariableDecl( - TypeMirror type, String name, Element owner, ExpressionTree initializer) { - DetachedVarSymbol sym = - new DetachedVarSymbol(0, names.fromString(name), (Type) type, (Symbol) owner); - VariableTree tree = maker.VarDef(sym, (JCTree.JCExpression) initializer); - sym.setDeclaration(tree); - return tree; - } - - /** - * Builds an AST Tree to declare and initialize a variable. The type of the variable is - * specified by a Tree. - * - * @param type the type of the variable, as a Tree - * @param name the name of the variable - * @param owner the element containing the new symbol - * @param initializer the initializer expression - * @return a VariableDeclTree declaring the new variable - */ - public VariableTree buildVariableDecl( - Tree type, String name, Element owner, ExpressionTree initializer) { - Type typeMirror = (Type) TreeUtils.typeOf(type); - DetachedVarSymbol sym = - new DetachedVarSymbol(0, names.fromString(name), typeMirror, (Symbol) owner); - JCTree.JCModifiers mods = maker.Modifiers(0); - JCTree.JCVariableDecl decl = - maker.VarDef( - mods, - sym.name, - (JCTree.JCExpression) type, - (JCTree.JCExpression) initializer); - decl.setType(typeMirror); - decl.sym = sym; - sym.setDeclaration(decl); - return decl; - } - - /** - * Builds an AST Tree to refer to a variable. - * - * @param decl the declaration of the variable - * @return an IdentifierTree to refer to the variable - */ - public IdentifierTree buildVariableUse(VariableTree decl) { - return (IdentifierTree) maker.Ident((JCTree.JCVariableDecl) decl); - } - - /** - * Builds an AST Tree to cast the type of an expression. - * - * @param type the type to cast to - * @param expr the expression to be cast - * @return a cast of the expression to the type - */ - public TypeCastTree buildTypeCast(TypeMirror type, ExpressionTree expr) { - return maker.TypeCast((Type) type, (JCTree.JCExpression) expr); - } - - /** - * Builds an AST Tree to assign an expression to a variable. - * - * @param variable the declaration of the variable to assign to - * @param expr the expression to be assigned - * @return a statement assigning the expression to the variable - */ - public StatementTree buildAssignment(VariableTree variable, ExpressionTree expr) { - return maker.Assignment(TreeInfo.symbolFor((JCTree) variable), (JCTree.JCExpression) expr); - } - - /** - * Builds an AST Tree to assign an RHS expression to an LHS expression. - * - * @param lhs the expression to be assigned to - * @param rhs the expression to be assigned - * @return a statement assigning the expression to the variable - */ - public AssignmentTree buildAssignment(ExpressionTree lhs, ExpressionTree rhs) { - JCTree.JCAssign assign = maker.Assign((JCTree.JCExpression) lhs, (JCTree.JCExpression) rhs); - assign.setType((Type) TreeUtils.typeOf(lhs)); - return assign; - } - - /** Builds an AST Tree representing a literal value of primitive or String type. */ - public LiteralTree buildLiteral(Object value) { - return maker.Literal(value); - } + return hasNextAccess; + } - /** - * Builds an AST Tree to compare two operands with less than. - * - * @param left the left operand tree - * @param right the right operand tree - * @return a Tree representing "left < right" - */ - public BinaryTree buildLessThan(ExpressionTree left, ExpressionTree right) { - JCTree.JCBinary binary = - maker.Binary( - JCTree.Tag.LT, (JCTree.JCExpression) left, (JCTree.JCExpression) right); - binary.setType((Type) modelTypes.getPrimitiveType(TypeKind.BOOLEAN)); - return binary; - } - - /** - * Builds an AST Tree to dereference an array. - * - * @param array the array to dereference - * @param index the index at which to dereference - * @return a Tree representing the dereference - */ - public ArrayAccessTree buildArrayAccess(ExpressionTree array, ExpressionTree index) { - ArrayType arrayType = (ArrayType) TreeUtils.typeOf(array); - JCTree.JCArrayAccess access = - maker.Indexed((JCTree.JCExpression) array, (JCTree.JCExpression) index); - access.setType((Type) arrayType.getComponentType()); - return access; - } - - /** - * Builds an AST Tree to refer to a class name. - * - * @param elt an element representing the class - * @return an IdentifierTree referring to the class - */ - public IdentifierTree buildClassUse(Element elt) { - return maker.Ident((Symbol) elt); - } - - /** - * Builds an AST Tree to access the valueOf() method of boxed type such as Short or Float. - * - * @param expr an expression whose type is a boxed type - * @return a MemberSelectTree that accesses the valueOf() method of the expression - */ - public MemberSelectTree buildValueOfMethodAccess(Tree expr) { - TypeMirror boxedType = TreeUtils.typeOf(expr); - - assert TypesUtils.isBoxedPrimitive(boxedType); - - // Find the valueOf(unboxedType) method of the boxed type - Symbol.MethodSymbol valueOfMethod = getValueOfMethod(env, boxedType); + /** + * Builds an AST Tree to access the next() method of an iterator. + * + * @param iteratorExpr an expression whose type is a subtype of Iterator + * @return a MemberSelectTree that accesses the next() method of the expression + */ + public MemberSelectTree buildNextMethodAccess(ExpressionTree iteratorExpr) { + DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); + assert exprType != null : "expression must be of declared type Iterator<>"; - Type.MethodType methodType = (Type.MethodType) valueOfMethod.asType(); + TypeElement exprElement = (TypeElement) exprType.asElement(); - JCTree.JCFieldAccess valueOfAccess = TreeUtils.Select(maker, expr, valueOfMethod); - valueOfAccess.setType(methodType); + // Find the next() method of the iterator type + Symbol.MethodSymbol nextMethod = null; - return valueOfAccess; + for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("next")) { + nextMethod = (Symbol.MethodSymbol) method; + } } - /** Returns the valueOf method of a boxed type such as Short or Float. */ - public static Symbol.MethodSymbol getValueOfMethod( - ProcessingEnvironment env, TypeMirror boxedType) { - Symbol.MethodSymbol valueOfMethod = null; - - TypeMirror unboxedType = env.getTypeUtils().unboxedType(boxedType); - TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); - for (ExecutableElement method : - ElementFilter.methodsIn(env.getElementUtils().getAllMembers(boxedElement))) { - if (method.getSimpleName().contentEquals("valueOf")) { - List params = method.getParameters(); - if (params.size() == 1 - && env.getTypeUtils().isSameType(params.get(0).asType(), unboxedType)) { - valueOfMethod = (Symbol.MethodSymbol) method; - } - } - } - - assert valueOfMethod != null - : "@AssumeAssertion(nullness): no valueOf method declared for boxed type"; - return valueOfMethod; - } - - /** - * Builds an AST Tree to access the *Value() method of a boxed type such as Short or Float, - * where * is the corresponding primitive type (i.e. shortValue or floatValue). - * - * @param expr an expression whose type is a boxed type - * @return a MemberSelectTree that accesses the *Value() method of the expression - */ - public MemberSelectTree buildPrimValueMethodAccess(Tree expr) { - TypeMirror boxedType = TreeUtils.typeOf(expr); - TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); - - assert TypesUtils.isBoxedPrimitive(boxedType); - TypeMirror unboxedType = modelTypes.unboxedType(boxedType); - - // Find the *Value() method of the boxed type - String primValueName = unboxedType.toString() + "Value"; - Symbol.MethodSymbol primValueMethod = null; - - for (ExecutableElement method : - ElementFilter.methodsIn(elements.getAllMembers(boxedElement))) { - if (method.getSimpleName().contentEquals(primValueName) - && method.getParameters().isEmpty()) { - primValueMethod = (Symbol.MethodSymbol) method; - } - } - - assert primValueMethod != null - : "@AssumeAssertion(nullness): no *Value method declared for boxed type"; - - Type.MethodType methodType = (Type.MethodType) primValueMethod.asType(); + assert nextMethod != null + : "@AssumeAssertion(nullness): no next method declared for expression type"; - JCTree.JCFieldAccess primValueAccess = TreeUtils.Select(maker, expr, primValueMethod); - primValueAccess.setType(methodType); + Type.MethodType methodType = (Type.MethodType) nextMethod.asType(); + Symbol.TypeSymbol methodClass = methodType.asElement(); + Type elementType; - return primValueAccess; + if (exprType.getTypeArguments().isEmpty()) { + elementType = symtab.objectType; + } else { + elementType = (Type) exprType.getTypeArguments().get(0); } - /** Map public AST Tree.Kinds to internal javac JCTree.Tags. */ - public JCTree.Tag kindToTag(Tree.Kind kind) { - switch (kind) { - case AND: - return JCTree.Tag.BITAND; - case AND_ASSIGNMENT: - return JCTree.Tag.BITAND_ASG; - case ANNOTATION: - return JCTree.Tag.ANNOTATION; - case ANNOTATION_TYPE: - return JCTree.Tag.TYPE_ANNOTATION; - case ARRAY_ACCESS: - return JCTree.Tag.INDEXED; - case ARRAY_TYPE: - return JCTree.Tag.TYPEARRAY; - case ASSERT: - return JCTree.Tag.ASSERT; - case ASSIGNMENT: - return JCTree.Tag.ASSIGN; - case BITWISE_COMPLEMENT: - return JCTree.Tag.COMPL; - case BLOCK: - return JCTree.Tag.BLOCK; - case BREAK: - return JCTree.Tag.BREAK; - case CASE: - return JCTree.Tag.CASE; - case CATCH: - return JCTree.Tag.CATCH; - case CLASS: - return JCTree.Tag.CLASSDEF; - case CONDITIONAL_AND: - return JCTree.Tag.AND; - case CONDITIONAL_EXPRESSION: - return JCTree.Tag.CONDEXPR; - case CONDITIONAL_OR: - return JCTree.Tag.OR; - case CONTINUE: - return JCTree.Tag.CONTINUE; - case DIVIDE: - return JCTree.Tag.DIV; - case DIVIDE_ASSIGNMENT: - return JCTree.Tag.DIV_ASG; - case DO_WHILE_LOOP: - return JCTree.Tag.DOLOOP; - case ENHANCED_FOR_LOOP: - return JCTree.Tag.FOREACHLOOP; - case EQUAL_TO: - return JCTree.Tag.EQ; - case EXPRESSION_STATEMENT: - return JCTree.Tag.EXEC; - case FOR_LOOP: - return JCTree.Tag.FORLOOP; - case GREATER_THAN: - return JCTree.Tag.GT; - case GREATER_THAN_EQUAL: - return JCTree.Tag.GE; - case IDENTIFIER: - return JCTree.Tag.IDENT; - case IF: - return JCTree.Tag.IF; - case IMPORT: - return JCTree.Tag.IMPORT; - case INSTANCE_OF: - return JCTree.Tag.TYPETEST; - case LABELED_STATEMENT: - return JCTree.Tag.LABELLED; - case LEFT_SHIFT: - return JCTree.Tag.SL; - case LEFT_SHIFT_ASSIGNMENT: - return JCTree.Tag.SL_ASG; - case LESS_THAN: - return JCTree.Tag.LT; - case LESS_THAN_EQUAL: - return JCTree.Tag.LE; - case LOGICAL_COMPLEMENT: - return JCTree.Tag.NOT; - case MEMBER_SELECT: - return JCTree.Tag.SELECT; - case METHOD: - return JCTree.Tag.METHODDEF; - case METHOD_INVOCATION: - return JCTree.Tag.APPLY; - case MINUS: - return JCTree.Tag.MINUS; - case MINUS_ASSIGNMENT: - return JCTree.Tag.MINUS_ASG; - case MODIFIERS: - return JCTree.Tag.MODIFIERS; - case MULTIPLY: - return JCTree.Tag.MUL; - case MULTIPLY_ASSIGNMENT: - return JCTree.Tag.MUL_ASG; - case NEW_ARRAY: - return JCTree.Tag.NEWARRAY; - case NEW_CLASS: - return JCTree.Tag.NEWCLASS; - case NOT_EQUAL_TO: - return JCTree.Tag.NE; - case OR: - return JCTree.Tag.BITOR; - case OR_ASSIGNMENT: - return JCTree.Tag.BITOR_ASG; - case PARENTHESIZED: - return JCTree.Tag.PARENS; - case PLUS: - return JCTree.Tag.PLUS; - case PLUS_ASSIGNMENT: - return JCTree.Tag.PLUS_ASG; - case POSTFIX_DECREMENT: - return JCTree.Tag.POSTDEC; - case POSTFIX_INCREMENT: - return JCTree.Tag.POSTINC; - case PREFIX_DECREMENT: - return JCTree.Tag.PREDEC; - case PREFIX_INCREMENT: - return JCTree.Tag.PREINC; - case REMAINDER: - return JCTree.Tag.MOD; - case REMAINDER_ASSIGNMENT: - return JCTree.Tag.MOD_ASG; - case RETURN: - return JCTree.Tag.RETURN; - case RIGHT_SHIFT: - return JCTree.Tag.SR; - case RIGHT_SHIFT_ASSIGNMENT: - return JCTree.Tag.SR_ASG; - case SWITCH: - return JCTree.Tag.SWITCH; - case SYNCHRONIZED: - return JCTree.Tag.SYNCHRONIZED; - case THROW: - return JCTree.Tag.THROW; - case TRY: - return JCTree.Tag.TRY; - case TYPE_CAST: - return JCTree.Tag.TYPECAST; - case TYPE_PARAMETER: - return JCTree.Tag.TYPEPARAMETER; - case UNARY_MINUS: - return JCTree.Tag.NEG; - case UNARY_PLUS: - return JCTree.Tag.POS; - case UNION_TYPE: - return JCTree.Tag.TYPEUNION; - case UNSIGNED_RIGHT_SHIFT: - return JCTree.Tag.USR; - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - return JCTree.Tag.USR_ASG; - case VARIABLE: - return JCTree.Tag.VARDEF; - case WHILE_LOOP: - return JCTree.Tag.WHILELOOP; - case XOR: - return JCTree.Tag.BITXOR; - case XOR_ASSIGNMENT: - return JCTree.Tag.BITXOR_ASG; - default: - return JCTree.Tag.NO_TAG; + // Replace the next method's generic return type with + // the actual element type of the expression. + Type.MethodType updatedMethodType = + new Type.MethodType( + com.sun.tools.javac.util.List.nil(), + elementType, + com.sun.tools.javac.util.List.nil(), + methodClass); + + JCTree.JCFieldAccess nextAccess = TreeUtils.Select(maker, iteratorExpr, nextMethod); + nextAccess.setType(updatedMethodType); + + return nextAccess; + } + + /** + * Builds an AST Tree to dereference the length field of an array. + * + * @param expression the array expression whose length is being accessed + * @return a MemberSelectTree to dereference the length of the array + */ + public MemberSelectTree buildArrayLengthAccess(ExpressionTree expression) { + return TreeUtils.Select(maker, expression, symtab.lengthVar); + } + + /** + * Builds an AST Tree to call a method designated by the argument expression. + * + * @param methodExpr an expression denoting a method with no arguments + * @return a MethodInvocationTree to call the argument method + */ + public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr) { + return maker.App((JCTree.JCExpression) methodExpr); + } + + /** + * Builds an AST Tree to call a method designated by methodExpr, with one argument designated by + * argExpr. + * + * @param methodExpr an expression denoting a method with one argument + * @param argExpr an expression denoting an argument to the method + * @return a MethodInvocationTree to call the argument method + */ + public MethodInvocationTree buildMethodInvocation( + ExpressionTree methodExpr, ExpressionTree argExpr) { + return maker.App( + (JCTree.JCExpression) methodExpr, + com.sun.tools.javac.util.List.of((JCTree.JCExpression) argExpr)); + } + + /** + * Builds an AST Tree to declare and initialize a variable, with no modifiers. + * + * @param type the type of the variable + * @param name the name of the variable + * @param owner the element containing the new symbol + * @param initializer the initializer expression + * @return a VariableDeclTree declaring the new variable + */ + public VariableTree buildVariableDecl( + TypeMirror type, String name, Element owner, ExpressionTree initializer) { + DetachedVarSymbol sym = + new DetachedVarSymbol(0, names.fromString(name), (Type) type, (Symbol) owner); + VariableTree tree = maker.VarDef(sym, (JCTree.JCExpression) initializer); + sym.setDeclaration(tree); + return tree; + } + + /** + * Builds an AST Tree to declare and initialize a variable. The type of the variable is specified + * by a Tree. + * + * @param type the type of the variable, as a Tree + * @param name the name of the variable + * @param owner the element containing the new symbol + * @param initializer the initializer expression + * @return a VariableDeclTree declaring the new variable + */ + public VariableTree buildVariableDecl( + Tree type, String name, Element owner, ExpressionTree initializer) { + Type typeMirror = (Type) TreeUtils.typeOf(type); + DetachedVarSymbol sym = + new DetachedVarSymbol(0, names.fromString(name), typeMirror, (Symbol) owner); + JCTree.JCModifiers mods = maker.Modifiers(0); + JCTree.JCVariableDecl decl = + maker.VarDef(mods, sym.name, (JCTree.JCExpression) type, (JCTree.JCExpression) initializer); + decl.setType(typeMirror); + decl.sym = sym; + sym.setDeclaration(decl); + return decl; + } + + /** + * Builds an AST Tree to refer to a variable. + * + * @param decl the declaration of the variable + * @return an IdentifierTree to refer to the variable + */ + public IdentifierTree buildVariableUse(VariableTree decl) { + return (IdentifierTree) maker.Ident((JCTree.JCVariableDecl) decl); + } + + /** + * Builds an AST Tree to cast the type of an expression. + * + * @param type the type to cast to + * @param expr the expression to be cast + * @return a cast of the expression to the type + */ + public TypeCastTree buildTypeCast(TypeMirror type, ExpressionTree expr) { + return maker.TypeCast((Type) type, (JCTree.JCExpression) expr); + } + + /** + * Builds an AST Tree to assign an expression to a variable. + * + * @param variable the declaration of the variable to assign to + * @param expr the expression to be assigned + * @return a statement assigning the expression to the variable + */ + public StatementTree buildAssignment(VariableTree variable, ExpressionTree expr) { + return maker.Assignment(TreeInfo.symbolFor((JCTree) variable), (JCTree.JCExpression) expr); + } + + /** + * Builds an AST Tree to assign an RHS expression to an LHS expression. + * + * @param lhs the expression to be assigned to + * @param rhs the expression to be assigned + * @return a statement assigning the expression to the variable + */ + public AssignmentTree buildAssignment(ExpressionTree lhs, ExpressionTree rhs) { + JCTree.JCAssign assign = maker.Assign((JCTree.JCExpression) lhs, (JCTree.JCExpression) rhs); + assign.setType((Type) TreeUtils.typeOf(lhs)); + return assign; + } + + /** Builds an AST Tree representing a literal value of primitive or String type. */ + public LiteralTree buildLiteral(Object value) { + return maker.Literal(value); + } + + /** + * Builds an AST Tree to compare two operands with less than. + * + * @param left the left operand tree + * @param right the right operand tree + * @return a Tree representing "left < right" + */ + public BinaryTree buildLessThan(ExpressionTree left, ExpressionTree right) { + JCTree.JCBinary binary = + maker.Binary(JCTree.Tag.LT, (JCTree.JCExpression) left, (JCTree.JCExpression) right); + binary.setType((Type) modelTypes.getPrimitiveType(TypeKind.BOOLEAN)); + return binary; + } + + /** + * Builds an AST Tree to dereference an array. + * + * @param array the array to dereference + * @param index the index at which to dereference + * @return a Tree representing the dereference + */ + public ArrayAccessTree buildArrayAccess(ExpressionTree array, ExpressionTree index) { + ArrayType arrayType = (ArrayType) TreeUtils.typeOf(array); + JCTree.JCArrayAccess access = + maker.Indexed((JCTree.JCExpression) array, (JCTree.JCExpression) index); + access.setType((Type) arrayType.getComponentType()); + return access; + } + + /** + * Builds an AST Tree to refer to a class name. + * + * @param elt an element representing the class + * @return an IdentifierTree referring to the class + */ + public IdentifierTree buildClassUse(Element elt) { + return maker.Ident((Symbol) elt); + } + + /** + * Builds an AST Tree to access the valueOf() method of boxed type such as Short or Float. + * + * @param expr an expression whose type is a boxed type + * @return a MemberSelectTree that accesses the valueOf() method of the expression + */ + public MemberSelectTree buildValueOfMethodAccess(Tree expr) { + TypeMirror boxedType = TreeUtils.typeOf(expr); + + assert TypesUtils.isBoxedPrimitive(boxedType); + + // Find the valueOf(unboxedType) method of the boxed type + Symbol.MethodSymbol valueOfMethod = getValueOfMethod(env, boxedType); + + Type.MethodType methodType = (Type.MethodType) valueOfMethod.asType(); + + JCTree.JCFieldAccess valueOfAccess = TreeUtils.Select(maker, expr, valueOfMethod); + valueOfAccess.setType(methodType); + + return valueOfAccess; + } + + /** Returns the valueOf method of a boxed type such as Short or Float. */ + public static Symbol.MethodSymbol getValueOfMethod( + ProcessingEnvironment env, TypeMirror boxedType) { + Symbol.MethodSymbol valueOfMethod = null; + + TypeMirror unboxedType = env.getTypeUtils().unboxedType(boxedType); + TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); + for (ExecutableElement method : + ElementFilter.methodsIn(env.getElementUtils().getAllMembers(boxedElement))) { + if (method.getSimpleName().contentEquals("valueOf")) { + List params = method.getParameters(); + if (params.size() == 1 + && env.getTypeUtils().isSameType(params.get(0).asType(), unboxedType)) { + valueOfMethod = (Symbol.MethodSymbol) method; } + } } - /** - * Builds an AST Tree to perform a binary operation. - * - * @param type result type of the operation - * @param op an AST Tree operator - * @param left the left operand tree - * @param right the right operand tree - * @return a Tree representing "left < right" - */ - public BinaryTree buildBinary( - TypeMirror type, Tree.Kind op, ExpressionTree left, ExpressionTree right) { - JCTree.Tag jcOp = kindToTag(op); - JCTree.JCBinary binary = - maker.Binary(jcOp, (JCTree.JCExpression) left, (JCTree.JCExpression) right); - binary.setType((Type) type); - return binary; + assert valueOfMethod != null + : "@AssumeAssertion(nullness): no valueOf method declared for boxed type"; + return valueOfMethod; + } + + /** + * Builds an AST Tree to access the *Value() method of a boxed type such as Short or Float, where + * * is the corresponding primitive type (i.e. shortValue or floatValue). + * + * @param expr an expression whose type is a boxed type + * @return a MemberSelectTree that accesses the *Value() method of the expression + */ + public MemberSelectTree buildPrimValueMethodAccess(Tree expr) { + TypeMirror boxedType = TreeUtils.typeOf(expr); + TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); + + assert TypesUtils.isBoxedPrimitive(boxedType); + TypeMirror unboxedType = modelTypes.unboxedType(boxedType); + + // Find the *Value() method of the boxed type + String primValueName = unboxedType.toString() + "Value"; + Symbol.MethodSymbol primValueMethod = null; + + for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(boxedElement))) { + if (method.getSimpleName().contentEquals(primValueName) && method.getParameters().isEmpty()) { + primValueMethod = (Symbol.MethodSymbol) method; + } } - /** - * Builds an AST Tree to create a new array with initializers. - * - * @param componentType component type of the new array - * @param elems expression trees of initializers - * @return a NewArrayTree to create a new array with initializers - */ - public NewArrayTree buildNewArray(TypeMirror componentType, List elems) { - List exprs = CollectionsPlume.mapList(JCExpression.class::cast, elems); - - JCTree.JCNewArray newArray = - maker.NewArray( - (JCTree.JCExpression) buildClassUse(((Type) componentType).tsym), - com.sun.tools.javac.util.List.nil(), - com.sun.tools.javac.util.List.from(exprs)); - newArray.setType(javacTypes.makeArrayType((Type) componentType)); - return newArray; + assert primValueMethod != null + : "@AssumeAssertion(nullness): no *Value method declared for boxed type"; + + Type.MethodType methodType = (Type.MethodType) primValueMethod.asType(); + + JCTree.JCFieldAccess primValueAccess = TreeUtils.Select(maker, expr, primValueMethod); + primValueAccess.setType(methodType); + + return primValueAccess; + } + + /** Map public AST Tree.Kinds to internal javac JCTree.Tags. */ + public JCTree.Tag kindToTag(Tree.Kind kind) { + switch (kind) { + case AND: + return JCTree.Tag.BITAND; + case AND_ASSIGNMENT: + return JCTree.Tag.BITAND_ASG; + case ANNOTATION: + return JCTree.Tag.ANNOTATION; + case ANNOTATION_TYPE: + return JCTree.Tag.TYPE_ANNOTATION; + case ARRAY_ACCESS: + return JCTree.Tag.INDEXED; + case ARRAY_TYPE: + return JCTree.Tag.TYPEARRAY; + case ASSERT: + return JCTree.Tag.ASSERT; + case ASSIGNMENT: + return JCTree.Tag.ASSIGN; + case BITWISE_COMPLEMENT: + return JCTree.Tag.COMPL; + case BLOCK: + return JCTree.Tag.BLOCK; + case BREAK: + return JCTree.Tag.BREAK; + case CASE: + return JCTree.Tag.CASE; + case CATCH: + return JCTree.Tag.CATCH; + case CLASS: + return JCTree.Tag.CLASSDEF; + case CONDITIONAL_AND: + return JCTree.Tag.AND; + case CONDITIONAL_EXPRESSION: + return JCTree.Tag.CONDEXPR; + case CONDITIONAL_OR: + return JCTree.Tag.OR; + case CONTINUE: + return JCTree.Tag.CONTINUE; + case DIVIDE: + return JCTree.Tag.DIV; + case DIVIDE_ASSIGNMENT: + return JCTree.Tag.DIV_ASG; + case DO_WHILE_LOOP: + return JCTree.Tag.DOLOOP; + case ENHANCED_FOR_LOOP: + return JCTree.Tag.FOREACHLOOP; + case EQUAL_TO: + return JCTree.Tag.EQ; + case EXPRESSION_STATEMENT: + return JCTree.Tag.EXEC; + case FOR_LOOP: + return JCTree.Tag.FORLOOP; + case GREATER_THAN: + return JCTree.Tag.GT; + case GREATER_THAN_EQUAL: + return JCTree.Tag.GE; + case IDENTIFIER: + return JCTree.Tag.IDENT; + case IF: + return JCTree.Tag.IF; + case IMPORT: + return JCTree.Tag.IMPORT; + case INSTANCE_OF: + return JCTree.Tag.TYPETEST; + case LABELED_STATEMENT: + return JCTree.Tag.LABELLED; + case LEFT_SHIFT: + return JCTree.Tag.SL; + case LEFT_SHIFT_ASSIGNMENT: + return JCTree.Tag.SL_ASG; + case LESS_THAN: + return JCTree.Tag.LT; + case LESS_THAN_EQUAL: + return JCTree.Tag.LE; + case LOGICAL_COMPLEMENT: + return JCTree.Tag.NOT; + case MEMBER_SELECT: + return JCTree.Tag.SELECT; + case METHOD: + return JCTree.Tag.METHODDEF; + case METHOD_INVOCATION: + return JCTree.Tag.APPLY; + case MINUS: + return JCTree.Tag.MINUS; + case MINUS_ASSIGNMENT: + return JCTree.Tag.MINUS_ASG; + case MODIFIERS: + return JCTree.Tag.MODIFIERS; + case MULTIPLY: + return JCTree.Tag.MUL; + case MULTIPLY_ASSIGNMENT: + return JCTree.Tag.MUL_ASG; + case NEW_ARRAY: + return JCTree.Tag.NEWARRAY; + case NEW_CLASS: + return JCTree.Tag.NEWCLASS; + case NOT_EQUAL_TO: + return JCTree.Tag.NE; + case OR: + return JCTree.Tag.BITOR; + case OR_ASSIGNMENT: + return JCTree.Tag.BITOR_ASG; + case PARENTHESIZED: + return JCTree.Tag.PARENS; + case PLUS: + return JCTree.Tag.PLUS; + case PLUS_ASSIGNMENT: + return JCTree.Tag.PLUS_ASG; + case POSTFIX_DECREMENT: + return JCTree.Tag.POSTDEC; + case POSTFIX_INCREMENT: + return JCTree.Tag.POSTINC; + case PREFIX_DECREMENT: + return JCTree.Tag.PREDEC; + case PREFIX_INCREMENT: + return JCTree.Tag.PREINC; + case REMAINDER: + return JCTree.Tag.MOD; + case REMAINDER_ASSIGNMENT: + return JCTree.Tag.MOD_ASG; + case RETURN: + return JCTree.Tag.RETURN; + case RIGHT_SHIFT: + return JCTree.Tag.SR; + case RIGHT_SHIFT_ASSIGNMENT: + return JCTree.Tag.SR_ASG; + case SWITCH: + return JCTree.Tag.SWITCH; + case SYNCHRONIZED: + return JCTree.Tag.SYNCHRONIZED; + case THROW: + return JCTree.Tag.THROW; + case TRY: + return JCTree.Tag.TRY; + case TYPE_CAST: + return JCTree.Tag.TYPECAST; + case TYPE_PARAMETER: + return JCTree.Tag.TYPEPARAMETER; + case UNARY_MINUS: + return JCTree.Tag.NEG; + case UNARY_PLUS: + return JCTree.Tag.POS; + case UNION_TYPE: + return JCTree.Tag.TYPEUNION; + case UNSIGNED_RIGHT_SHIFT: + return JCTree.Tag.USR; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + return JCTree.Tag.USR_ASG; + case VARIABLE: + return JCTree.Tag.VARDEF; + case WHILE_LOOP: + return JCTree.Tag.WHILELOOP; + case XOR: + return JCTree.Tag.BITXOR; + case XOR_ASSIGNMENT: + return JCTree.Tag.BITXOR_ASG; + default: + return JCTree.Tag.NO_TAG; } + } + + /** + * Builds an AST Tree to perform a binary operation. + * + * @param type result type of the operation + * @param op an AST Tree operator + * @param left the left operand tree + * @param right the right operand tree + * @return a Tree representing "left < right" + */ + public BinaryTree buildBinary( + TypeMirror type, Tree.Kind op, ExpressionTree left, ExpressionTree right) { + JCTree.Tag jcOp = kindToTag(op); + JCTree.JCBinary binary = + maker.Binary(jcOp, (JCTree.JCExpression) left, (JCTree.JCExpression) right); + binary.setType((Type) type); + return binary; + } + + /** + * Builds an AST Tree to create a new array with initializers. + * + * @param componentType component type of the new array + * @param elems expression trees of initializers + * @return a NewArrayTree to create a new array with initializers + */ + public NewArrayTree buildNewArray(TypeMirror componentType, List elems) { + List exprs = CollectionsPlume.mapList(JCExpression.class::cast, elems); + + JCTree.JCNewArray newArray = + maker.NewArray( + (JCTree.JCExpression) buildClassUse(((Type) componentType).tsym), + com.sun.tools.javac.util.List.nil(), + com.sun.tools.javac.util.List.from(exprs)); + newArray.setType(javacTypes.makeArrayType((Type) componentType)); + return newArray; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java index 0f76852c86a..d5bdb4d9dee 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java @@ -8,13 +8,10 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Names; - -import org.checkerframework.javacutil.TreeUtils; -import org.plumelib.util.IPair; - import java.util.StringTokenizer; - import javax.annotation.processing.ProcessingEnvironment; +import org.checkerframework.javacutil.TreeUtils; +import org.plumelib.util.IPair; /** * A utility class for parsing Java expression snippets, and converting them to proper Javac AST @@ -37,131 +34,131 @@ *

          It's implemented via a Recursive-Descend parser. */ public class TreeParser { - /** Valid delimiters. */ - private static final String DELIMS = ".[](),"; - - /** A sentinel value. */ - private static final String SENTINEL = ""; - - /** The TreeMaker instance. */ - private final TreeMaker maker; - - /** The names instance. */ - private final Names names; - - /** Create a TreeParser. */ - public TreeParser(ProcessingEnvironment env) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - maker = TreeMaker.instance(context); - names = Names.instance(context); + /** Valid delimiters. */ + private static final String DELIMS = ".[](),"; + + /** A sentinel value. */ + private static final String SENTINEL = ""; + + /** The TreeMaker instance. */ + private final TreeMaker maker; + + /** The names instance. */ + private final Names names; + + /** Create a TreeParser. */ + public TreeParser(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + maker = TreeMaker.instance(context); + names = Names.instance(context); + } + + /** + * Parses the snippet in the string as an internal Javac AST expression node. + * + * @param s the Java snippet + * @return the AST corresponding to the snippet + */ + public ExpressionTree parseTree(String s) { + StringTokenizer tokenizer = new StringTokenizer(s, DELIMS, true); + String token = tokenizer.nextToken(); + + try { + return parseExpression(tokenizer, token).first; + } catch (Exception e) { + throw new ParseError(e); + } finally { + tokenizer = null; + token = null; } - - /** - * Parses the snippet in the string as an internal Javac AST expression node. - * - * @param s the Java snippet - * @return the AST corresponding to the snippet - */ - public ExpressionTree parseTree(String s) { - StringTokenizer tokenizer = new StringTokenizer(s, DELIMS, true); - String token = tokenizer.nextToken(); - - try { - return parseExpression(tokenizer, token).first; - } catch (Exception e) { - throw new ParseError(e); - } finally { - tokenizer = null; - token = null; - } + } + + /** The next token from the tokenizer, or the {@code SENTINEL} if none is available. */ + private String nextToken(StringTokenizer tokenizer) { + return tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINEL; + } + + /** The parsed expression tree for the given token. */ + private JCExpression fromToken(String token) { + // Optimization + if ("true".equals(token)) { + return maker.Literal(true); + } else if ("false".equals(token)) { + return maker.Literal(false); } - /** The next token from the tokenizer, or the {@code SENTINEL} if none is available. */ - private String nextToken(StringTokenizer tokenizer) { - return tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINEL; + if (Character.isLetter(token.charAt(0))) { + return maker.Ident(names.fromString(token)); } - /** The parsed expression tree for the given token. */ - private JCExpression fromToken(String token) { - // Optimization - if ("true".equals(token)) { - return maker.Literal(true); - } else if ("false".equals(token)) { - return maker.Literal(false); - } - - if (Character.isLetter(token.charAt(0))) { - return maker.Ident(names.fromString(token)); - } - - Object value; - try { - value = Integer.valueOf(token); - } catch (Exception e2) { - try { - value = Double.valueOf(token); - } catch (Exception ef) { - throw new Error("Can't parse as integer or double: " + token); - } - } - return maker.Literal(value); + Object value; + try { + value = Integer.valueOf(token); + } catch (Exception e2) { + try { + value = Double.valueOf(token); + } catch (Exception ef) { + throw new Error("Can't parse as integer or double: " + token); + } } - - /** - * Parse an expression. - * - * @param tokenizer the tokenizer - * @param token the first token - * @return a pair of a parsed expression and the next token - */ - private IPair parseExpression(StringTokenizer tokenizer, String token) { - JCExpression tree = fromToken(token); - - while (tokenizer.hasMoreTokens()) { - String delim = nextToken(tokenizer); - token = delim; - if (".".equals(delim)) { - token = nextToken(tokenizer); - tree = TreeUtils.Select(maker, tree, names.fromString(token)); - } else if ("(".equals(delim)) { - token = nextToken(tokenizer); - ListBuffer args = new ListBuffer<>(); - while (!")".equals(token)) { - IPair p = parseExpression(tokenizer, token); - JCExpression arg = p.first; - token = p.second; - args.append(arg); - if (",".equals(token)) { - token = nextToken(tokenizer); - } - } - // For now, handle empty args only - assert ")".equals(token) : "Unexpected token: " + token; - tree = maker.Apply(List.nil(), tree, args.toList()); - } else if ("[".equals(token)) { - token = nextToken(tokenizer); - IPair p = parseExpression(tokenizer, token); - JCExpression index = p.first; - token = p.second; - assert "]".equals(token) : "Unexpected token: " + token; - tree = maker.Indexed(tree, index); - } else { - return IPair.of(tree, token); - } - assert tokenizer != null : "@AssumeAssertion(nullness): side effects"; + return maker.Literal(value); + } + + /** + * Parse an expression. + * + * @param tokenizer the tokenizer + * @param token the first token + * @return a pair of a parsed expression and the next token + */ + private IPair parseExpression(StringTokenizer tokenizer, String token) { + JCExpression tree = fromToken(token); + + while (tokenizer.hasMoreTokens()) { + String delim = nextToken(tokenizer); + token = delim; + if (".".equals(delim)) { + token = nextToken(tokenizer); + tree = TreeUtils.Select(maker, tree, names.fromString(token)); + } else if ("(".equals(delim)) { + token = nextToken(tokenizer); + ListBuffer args = new ListBuffer<>(); + while (!")".equals(token)) { + IPair p = parseExpression(tokenizer, token); + JCExpression arg = p.first; + token = p.second; + args.append(arg); + if (",".equals(token)) { + token = nextToken(tokenizer); + } } - + // For now, handle empty args only + assert ")".equals(token) : "Unexpected token: " + token; + tree = maker.Apply(List.nil(), tree, args.toList()); + } else if ("[".equals(token)) { + token = nextToken(tokenizer); + IPair p = parseExpression(tokenizer, token); + JCExpression index = p.first; + token = p.second; + assert "]".equals(token) : "Unexpected token: " + token; + tree = maker.Indexed(tree, index); + } else { return IPair.of(tree, token); + } + assert tokenizer != null : "@AssumeAssertion(nullness): side effects"; } - /** An internal error. */ - private static class ParseError extends RuntimeException { - /** The serial version UID. */ - private static final long serialVersionUID = 1887754619522101929L; + return IPair.of(tree, token); + } - /** Create a ParseError. */ - ParseError(Throwable cause) { - super(cause); - } + /** An internal error. */ + private static class ParseError extends RuntimeException { + /** The serial version UID. */ + private static final long serialVersionUID = 1887754619522101929L; + + /** Create a ParseError. */ + ParseError(Throwable cause) { + super(cause); } + } } From 4114375d0fa1223bd6580885f0eb7e46744b7f58 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 15 Dec 2023 15:49:29 -0800 Subject: [PATCH 002/173] Prep for next release. --- build.gradle | 2 +- docs/CHANGELOG.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 13f919ccc83..ae28cdbd5c4 100644 --- a/build.gradle +++ b/build.gradle @@ -159,7 +159,7 @@ allprojects { // * any new checkers have been added, or // * backward-incompatible changes have been made to APIs or elsewhere. // To make a snapshot release: ./gradlew publish - version = '3.42.0-eisop6-SNAPSHOT' + version '3.42.1-SNAPSHOT' tasks.withType(JavaCompile).configureEach { options.fork = true diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5a852f11a74..ffdf71d0eaa 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,13 @@ +Version 3.43.0 (January 2, 2024) +-------------------------------- + +**User-visible changes:** + +**Implementation details:** + +**Closed issues:** + + Version 3.42.0-eisop6 (January ??, 2025) ---------------------------------------- From 255c1e2c96addcfe9d05d8056eca339d4d51a9b2 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 15 Dec 2023 19:35:49 -0800 Subject: [PATCH 003/173] Fix Issue 6317 (#6372) --- .../MustCallConsistencyAnalyzer.java | 62 +++++++++++++++---- .../resourceleak/MultipleReturnStmts.java | 24 +++++++ 2 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 checker/tests/resourceleak/MultipleReturnStmts.java diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java index 9a792884250..950c3c67e8d 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java @@ -53,6 +53,7 @@ import org.checkerframework.dataflow.cfg.UnderlyingAST.Kind; import org.checkerframework.dataflow.cfg.block.Block; import org.checkerframework.dataflow.cfg.block.Block.BlockType; +import org.checkerframework.dataflow.cfg.block.ConditionalBlock; import org.checkerframework.dataflow.cfg.block.ExceptionBlock; import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlock; import org.checkerframework.dataflow.cfg.node.AssignmentNode; @@ -77,6 +78,7 @@ import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; @@ -2098,16 +2100,19 @@ private void propagateObligationsToSuccessorBlock( continue; } - // Which stores from the called-methods and must-call checkers are used in - // the consistency check varies depending on the context. The rules are: - // 1. if the current block has no nodes (and therefore the store must come from - // a block - // rather than a node): + // Which stores from the called-methods and must-call checkers are used in the consistency + // check varies depending on the context. Generally speaking, we would like to use the + // store propagated along the CFG edge from currentBlock to successor. But, there are + // special cases to consider. The rules are: + // 1. if the current block has no nodes, it is either a ConditionalBlock or a SpecialBlock. + // For the called-methods store, we obtain the exact CFG edge store that we need (see + // getStoreForEdgeFromEmptyBlock()). For the must-call store, due to API limitations, + // we use the following heuristics: // 1a. if there is information about any alias in the resource alias set - // in the successor store, use the successor's CM and MC stores, which - // contain whatever information is true after this block finishes. + // in the successor store, use the successor's MC store, which + // contains whatever information is true after this block finishes. // 1b. if there is not any information about any alias in the resource alias - // set in the successor store, use the current blocks' CM and MC stores, + // set in the successor store, use the current block's MC store, // which contain whatever information is true before this (empty) block. // 2. if the current block has one or more nodes, always use the CM store after // the last node. To decide which MC store to use: @@ -2124,10 +2129,12 @@ private void propagateObligationsToSuccessorBlock( CFStore mcStore; AccumulationStore cmStore; if (currentBlockNodes.size() == 0 /* currentBlock is special or conditional */) { - cmStore = - obligationGoesOutOfScopeBeforeSuccessor - ? analysis.getInput(currentBlock).getRegularStore() // 1a. (CM) - : regularStoreOfSuccessor; // 1b. (CM) + cmStore = getStoreForEdgeFromEmptyBlock(currentBlock, successor); // 1. (CM) + // For the Must Call Checker, we currently apply a less precise handling and do not get + // the store for the specific CFG edge from currentBlock to successor. We do not believe + // this will impact precision except in convoluted and uncommon cases. If we find that + // we need more precision, we can revisit this, but it will require additional API support + // in the AnalysisResult type to get the information that we need. mcStore = mcAtf.getStoreForBlock( obligationGoesOutOfScopeBeforeSuccessor, @@ -2180,6 +2187,33 @@ private void propagateObligationsToSuccessorBlock( propagate(new BlockWithObligations(successor, successorObligations), visited, worklist); } + /** + * Gets the store propagated by the {@link ResourceLeakAnalysis} (containing called methods + * information) along a particular CFG edge during local type inference. The source {@link Block} + * of the edge must contain no {@link Node}s. + * + * @param currentBlock source block of the CFG edge. Must contain no {@link Node}s. + * @param successor target block of the CFG edge. + * @return store propagated by the {@link ResourceLeakAnalysis} along the CFG edge. + */ + private AccumulationStore getStoreForEdgeFromEmptyBlock(Block currentBlock, Block successor) { + switch (currentBlock.getType()) { + case CONDITIONAL_BLOCK: + ConditionalBlock condBlock = (ConditionalBlock) currentBlock; + if (condBlock.getThenSuccessor().equals(successor)) { + return analysis.getInput(currentBlock).getThenStore(); + } else if (condBlock.getElseSuccessor().equals(successor)) { + return analysis.getInput(currentBlock).getElseStore(); + } else { + throw new BugInCF("successor not found"); + } + case SPECIAL_BLOCK: + return analysis.getInput(successor).getRegularStore(); + default: + throw new BugInCF("unexpected block type " + currentBlock.getType()); + } + } + /** * Returns true if {@code alias.reference} is definitely in-scope in the successor store: that is, * there is a value for it in {@code successorStore}. @@ -2342,7 +2376,9 @@ private void checkMustCall( } else { for (AnnotationMirror anno : cmValue.getAnnotations()) { if (AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { + anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods") + || AnnotationUtils.areSameByName( + anno, "org.checkerframework.checker.calledmethods.qual.CalledMethodsBottom")) { cmAnno = anno; } } diff --git a/checker/tests/resourceleak/MultipleReturnStmts.java b/checker/tests/resourceleak/MultipleReturnStmts.java new file mode 100644 index 00000000000..5fda1f4eff6 --- /dev/null +++ b/checker/tests/resourceleak/MultipleReturnStmts.java @@ -0,0 +1,24 @@ +import java.io.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; +import org.checkerframework.checker.nullness.qual.*; + +abstract class MultipleReturnStmts { + + abstract @Nullable Closeable alloc(); + + abstract boolean arbitraryChoice(); + + void method() throws IOException { + + if (arbitraryChoice()) { + return; + } + + Closeable r1 = alloc(); + if (r1 == null) { + return; + } + r1.close(); + } +} From 404a60476f0e3b6b8f514dad8479967b12735175 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 18 Dec 2023 07:19:46 -0800 Subject: [PATCH 004/173] Update an RLC regression test (#6375) --- .../resourceleak/MustCallAliasNormalExit.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/checker/tests/resourceleak/MustCallAliasNormalExit.java b/checker/tests/resourceleak/MustCallAliasNormalExit.java index 5982d3c56eb..679ded3f8d2 100644 --- a/checker/tests/resourceleak/MustCallAliasNormalExit.java +++ b/checker/tests/resourceleak/MustCallAliasNormalExit.java @@ -21,7 +21,7 @@ public void close() throws IOException { this.is.close(); } - public static @MustCallAlias MustCallAliasNormalExit mcaneFactory(InputStream is) + public static @MustCallAlias MustCallAliasNormalExit mcaneFactory(@MustCallAlias InputStream is) throws Exception { return new MustCallAliasNormalExit(is, false); } @@ -50,15 +50,23 @@ public static void testUse2(@Owning InputStream inputStream) throws IOException } } - // :: error: required.method.not.called public static void testUse3(@Owning InputStream inputStream) throws Exception { - // if mcaneFactory throws, then inputStream goes out of scope w/o being closed + // If mcaneFactory throws, then inputStream goes out of scope w/o being closed. But, @Owning + // only requires that the resource be closed at normal exit, so this is OK. MustCallAliasNormalExit mcane = mcaneFactory(inputStream); mcane.close(); } - // TODO: this appears to be a false positive, but the RLC doesn't handle it correctly because - // close() is called on different aliases on different branches. + // TODO: this is a false positive due to imprecision in our analysis. At the program point + // before the call to mcane.close(), the inferred @CalledMethods type of inputStream is + // @CalledMethods(""), due to the control-flow merge. Further, this program point may be reached + // with mcane _not_ being a resource alias of inputStream, if mcaneFactory throws an exception. + // In this scenario, the analysis reasons that the exit may be reached without close() being + // called on inputStream. But, if mcane is not a resource alias of inputStream, then + // the inputStream.close() call in the catch block must have executed, so this is a false + // positive. Removing this false positive would require greater path sensitivity in the + // consistency analyzer, by tracking both resource aliases and @CalledMethods types for each path + // in an Obligation. See https://github.com/typetools/checker-framework/issues/5658 // :: error: required.method.not.called public static void testUse4(@Owning InputStream inputStream) throws Exception { MustCallAliasNormalExit mcane = null; From bec5baa4d58307ad4b492c2edf29ae962d3d765d Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 18 Dec 2023 14:41:12 -0800 Subject: [PATCH 005/173] Implement type argument inference based on the latest JLS (#6138) Fixes #979. --- build.gradle | 2 +- checker/jtreg/nullness/Issue1809.java | 1 + checker/jtreg/nullness/issue824/Class2.out | 22 +- checker/jtreg/stubs/issue1456/WithStub.out | 2 +- .../InitializationAnnotatedTypeFactory.java | 19 + ...tializationParentAnnotatedTypeFactory.java | 6 + .../lock/LockAnnotatedTypeFactory.java | 8 +- .../MustCallAnnotatedTypeFactory.java | 9 +- .../nullness/KeyForAnnotatedTypeFactory.java | 5 +- .../checker/nullness/KeyForPropagator.java | 2 +- .../NullnessNoInitAnnotatedTypeFactory.java | 5 +- .../MustCallConsistencyAnalyzer.java | 2 +- .../resourceleak/ResourceLeakChecker.java | 2 +- .../tests/fenum/UpperBoundsInByteCode.java | 4 +- checker/tests/guieffect/Java8Lambdas.java | 5 - checker/tests/i18n/I18nCollectorsToList.java | 25 + checker/tests/index/IndexIssue6046.java | 44 + checker/tests/interning/Issue2809.java | 2 +- checker/tests/mustcall/CommandResponse.java | 2 - .../tests/mustcall/EditLogInputStream.java | 3 - checker/tests/mustcall/ListOfMustCall.java | 2 + checker/tests/nullness/ArrayRefs.java | 2 +- .../nullness/DependentTypeTypeInference.java | 13 + checker/tests/nullness/InferNullType.java | 8 +- .../InferTypeArgsConditionalExpression.java | 2 +- checker/tests/nullness/Issue1102.java | 2 +- checker/tests/nullness/Issue2048.java | 1 + checker/tests/nullness/Issue3150.java | 4 +- checker/tests/nullness/Issue3443.java | 1 - checker/tests/nullness/Issue3754.java | 2 - checker/tests/nullness/Issue3764.java | 27 + checker/tests/nullness/Issue4523.java | 2 + checker/tests/nullness/Issue531.java | 12 +- checker/tests/nullness/Issue579Error.java | 2 +- checker/tests/nullness/Issue738.java | 4 +- checker/tests/nullness/Issue759.java | 2 +- checker/tests/nullness/Iterate.java | 2 - checker/tests/nullness/MethodTypeVars4.java | 2 +- checker/tests/nullness/RawTypesNullness.java | 2 - .../tests/nullness/flow/TestNullnessUtil.java | 2 + checker/tests/nullness/flow/TestOpt.java | 4 +- .../generics/CollectionsAnnotations.java | 4 +- .../generics/CollectionsAnnotationsMin.java | 2 +- .../tests/nullness/generics/GenericArgs.java | 9 +- .../nullness/generics/GenericsBounds5.java | 4 +- checker/tests/nullness/generics/Issue329.java | 5 +- .../nullness/generics/MethodTypeVars.java | 4 +- .../nullness/generics/MethodTypeVars5.java | 3 + .../nullness/generics/MethodTypeVars6.java | 6 +- .../nullness/generics/WildcardSubtyping2.java | 1 + .../nullness/java17/InferSwitchExpr.java | 37 + .../SwitchExpressionTypeArgInference.java | 37 + checker/tests/nullness/java8/Issue1098.java | 3 - .../tests/nullness/java8/Issue1098NoJdk.java | 3 - checker/tests/nullness/java8/Issue1633.java | 10 +- .../nullness/java8/lambda/TypeVarAssign.java | 2 - .../methodref/ClassTypeArgInference.java | 2 +- .../java8/methodref/GenericArity.java | 25 - .../nullness/java8/methodref/TestGenFunc.java | 41 + .../java8inference/InLambdaAnnotated.java | 2 - .../nullness/java8inference/Issue1032.java | 2 - .../nullness/java8inference/Issue1630.java | 12 +- .../nullness/java8inference/Issue2235.java | 6 +- .../nullness/java8inference/Issue2719.java | 2 +- .../nullness/java8inference/Issue402.java | 4 +- .../nullness/java8inference/Issue887.java | 2 +- .../nullness/java8inference/Issue980.java | 2 - .../java8inference/NullnessBeamCrash3.java | 33 + .../tests/nullness/java8inference/OneOf.java | 5 +- .../nullness/java8inference/SimpleLambda.java | 2 - .../jdkannotations/HashtableTest.java | 2 + .../nullness/jdkannotations/Issue1142.java | 1 + checker/tests/optional/Base.java | 17 + checker/tests/optional/Marks5.java | 1 + checker/tests/regex/InvariantTypes.java | 6 +- checker/tests/resourceleak/SneakyDrop.java | 4 +- .../tests/tainting/ClassQPTypeVarTest.java | 2 +- checker/tests/tainting/Issue1111.java | 8 +- checker/tests/tainting/Issue3036Tainting.java | 49 + checker/tests/tainting/Issue6110.java | 2 +- .../tainting/MemberReferenceInference.java | 35 + .../tests/tainting/SubtypingConstraint.java | 17 + .../tainting/TaintingPrimitiveTarget.java | 17 + .../cfg/builder/CFGTranslationPhaseOne.java | 4 +- .../cfg/visualize/CFGVisualizeLauncher.java | 3 +- .../expression/JavaExpressionConverter.java | 2 +- docs/CHANGELOG.md | 4 + docs/examples/units-extension/Expected.txt | 8 +- docs/manual/creating-a-checker.tex | 4 - docs/manual/introduction.tex | 4 +- .../CheckerFrameworkPerDirectoryTest.java | 5 +- .../test/TestConfigurationBuilder.java | 5 +- .../framework/test/TypecheckResult.java | 4 - .../common/basetype/BaseTypeValidator.java | 7 - .../common/basetype/BaseTypeVisitor.java | 280 ++-- .../common/basetype/messages.properties | 7 +- .../value/ValueAnnotatedTypeFactory.java | 16 +- ...holeProgramInferenceJavaParserStorage.java | 6 +- .../ajava/JointJavacJavaParserVisitor.java | 5 +- .../framework/flow/CFAbstractValue.java | 2 +- .../framework/source/SourceChecker.java | 8 - .../framework/stub/AnnotationFileParser.java | 4 +- .../framework/type/AnnotatedTypeCopier.java | 4 +- .../framework/type/AnnotatedTypeFactory.java | 490 +++---- .../framework/type/AnnotatedTypeMirror.java | 54 +- .../framework/type/AsSuperVisitor.java | 58 +- .../framework/type/BoundsInitializer.java | 18 +- .../type/DefaultAnnotatedTypeFormatter.java | 2 +- .../framework/type/DefaultTypeHierarchy.java | 88 +- .../framework/type/EqualityAtmComparer.java | 4 +- .../type/GenericAnnotatedTypeFactory.java | 29 +- .../framework/type/QualifierHierarchy.java | 170 ++- .../type/StructuralEqualityComparer.java | 44 +- .../framework/type/SupertypeFinder.java | 17 - .../type/TypeFromExpressionVisitor.java | 15 +- .../framework/type/TypeFromMemberVisitor.java | 12 +- .../framework/type/TypeHierarchy.java | 10 + .../poly/AbstractQualifierPolymorphism.java | 14 +- .../type/poly/QualifierPolymorphism.java | 8 + .../PropagationTypeAnnotator.java | 3 +- .../framework/util/AnnotatedTypes.java | 126 +- .../framework/util/AtmLubVisitor.java | 4 +- .../util/defaults/QualifierDefaults.java | 38 +- .../DefaultTypeArgumentInference.java | 867 ------------ .../framework/util/typeinference/GlbUtil.java | 202 --- .../typeinference/TypeArgInferenceUtil.java | 722 ---------- .../util/typeinference/constraint/A2F.java | 32 - .../typeinference/constraint/A2FReducer.java | 71 - .../constraint/AFConstraint.java | 120 -- .../typeinference/constraint/AFReducer.java | 27 - .../constraint/AFReducingVisitor.java | 555 -------- .../util/typeinference/constraint/F2A.java | 32 - .../typeinference/constraint/F2AReducer.java | 80 -- .../util/typeinference/constraint/FIsA.java | 33 - .../typeinference/constraint/FIsAReducer.java | 256 ---- .../util/typeinference/constraint/TIsU.java | 25 - .../util/typeinference/constraint/TSubU.java | 26 - .../typeinference/constraint/TSuperU.java | 26 - .../constraint/TUConstraint.java | 82 -- .../typeinference/solver/ConstraintMap.java | 186 --- .../solver/ConstraintMapBuilder.java | 207 --- .../solver/EqualitiesSolver.java | 474 ------- .../typeinference/solver/InferenceResult.java | 170 --- .../typeinference/solver/InferredValue.java | 53 - .../typeinference/solver/SubtypesSolver.java | 176 --- .../solver/SupertypesSolver.java | 416 ------ .../solver/TargetConstraints.java | 123 -- .../DefaultTypeArgumentInference.java | 259 ++++ .../util/typeinference8/InferenceResult.java | 163 +++ .../InvocationTypeInference.java | 611 +++++++++ .../TypeArgumentInference.java | 24 +- .../util/typeinference8/bound/BoundSet.java | 373 ++++++ .../typeinference8/bound/CaptureBound.java | 238 ++++ .../constraint/AdditionalArgument.java | 66 + .../CheckedExceptionConstraint.java | 96 ++ .../typeinference8/constraint/Constraint.java | 69 + .../constraint/ConstraintSet.java | 337 +++++ .../typeinference8/constraint/Expression.java | 462 +++++++ .../constraint/QualifierTyping.java | 146 +++ .../constraint/ReductionResult.java | 57 + .../constraint/TypeConstraint.java | 188 +++ .../typeinference8/constraint/Typing.java | 482 +++++++ .../types/AbstractQualifier.java | 157 +++ .../typeinference8/types/AbstractType.java | 646 +++++++++ .../AnnotatedContainsInferenceVariable.java | 162 +++ .../typeinference8/types/CaptureVariable.java | 56 + .../types/ContainsInferenceVariable.java | 198 +++ .../typeinference8/types/Dependencies.java | 95 ++ .../types/InferenceFactory.java | 1166 +++++++++++++++++ .../typeinference8/types/InferenceType.java | 303 +++++ .../typeinference8/types/InvocationType.java | 251 ++++ .../util/typeinference8/types/ProperType.java | 275 ++++ .../util/typeinference8/types/Qualifier.java | 41 + .../typeinference8/types/QualifierVar.java | 194 +++ .../typeinference8/types/UseOfVariable.java | 243 ++++ .../util/typeinference8/types/Variable.java | 220 ++++ .../typeinference8/types/VariableBounds.java | 650 +++++++++ .../util/CheckedExceptionsUtil.java | 310 +++++ .../util/FalseBoundException.java | 21 + .../util/Java8InferenceContext.java | 163 +++ .../util/typeinference8/util/Resolution.java | 424 ++++++ .../util/typeinference8/util/Theta.java | 49 + .../tests/all-systems/BeamCrash2Full.java | 53 + ...Crash.java => CaptureConversionCrash.java} | 4 +- .../tests/all-systems/DiamondLambda.java | 9 + .../tests/all-systems/InferNullType.java | 8 +- framework/tests/all-systems/Issue1809.java | 3 + framework/tests/all-systems/Issue1948.java | 4 +- framework/tests/all-systems/Issue2082.java | 1 + framework/tests/all-systems/Issue2370.java | 4 +- framework/tests/all-systems/Issue4879.java | 2 + framework/tests/all-systems/Issue5436.java | 2 +- framework/tests/all-systems/Issue6319.java | 1 + framework/tests/all-systems/LambdaParams.java | 21 + framework/tests/all-systems/LubRawTypes.java | 2 - framework/tests/all-systems/MyMapCrash.java | 14 + framework/tests/all-systems/StateMatch.java | 2 +- .../tests/all-systems/UncheckedCrash.java | 15 + .../all-systems/java8/lambda/Issue436All.java | 19 + .../lambda/{Lambda.java => ManyLambda.java} | 7 +- .../java8/memberref/MemberReferences.java | 29 +- .../java8inference/BeamCrash1.java | 30 + .../java8inference/BeamCrash3.java | 25 + .../java8inference/BeamCrash4.java | 52 + .../all-systems/java8inference/Bug1.java | 1 - .../all-systems/java8inference/Bug16.java | 1 + .../all-systems/java8inference/Bug17.java | 88 ++ .../java8inference/CollectorCollect.java | 105 ++ .../java8inference/CollectorsToList.java | 8 +- .../CrashWithSuperWildcard.java | 57 + .../java8inference/GuavaCrash.java | 125 ++ .../all-systems/java8inference/Issue1313.java | 2 +- .../all-systems/java8inference/Issue1397.java | 2 - .../all-systems/java8inference/Issue1407.java | 2 - .../all-systems/java8inference/Issue1419.java | 2 +- .../all-systems/java8inference/Issue1424.java | 2 +- .../all-systems/java8inference/Issue3036.java | 2 + .../all-systems/java8inference/Issue404.java | 6 - .../all-systems/java8inference/Issue6046.java | 31 + .../all-systems/java8inference/Issue6346.java | 91 ++ .../{Issue953.java => Issue953Inference.java} | 7 +- .../java8inference/IteratorInference.java | 13 + .../java8inference/MemRefInfere.java | 1 + .../java8inference/MethodRefSuperCrash | 26 + .../all-systems/java8inference/Misc.java | 6 +- .../java8inference/PrimitiveTarget.java | 8 + .../java8inference/TooManyConstraints.java | 29 + .../UncheckedConversionInference.java | 57 + .../tests/classval/ClassValInferenceTest.java | 5 +- framework/tests/framework/GenericTest9.java | 3 +- .../h1h2checker/InferTypeArgsPolyChecker.java | 10 +- framework/tests/lubglb/IntersectionTypes.java | 2 +- .../javacutil/AnnotationMirrorMap.java | 43 +- .../javacutil/AnnotationUtils.java | 18 + .../javacutil/TreePathUtil.java | 88 +- .../checkerframework/javacutil/TreeUtils.java | 224 +++- .../javacutil/TypesUtils.java | 168 ++- .../javacutil/trees/TreeBuilder.java | 1 + 238 files changed, 11977 insertions(+), 6005 deletions(-) create mode 100644 checker/tests/i18n/I18nCollectorsToList.java create mode 100644 checker/tests/index/IndexIssue6046.java create mode 100644 checker/tests/nullness/DependentTypeTypeInference.java create mode 100644 checker/tests/nullness/Issue3764.java create mode 100644 checker/tests/nullness/java17/InferSwitchExpr.java create mode 100644 checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java delete mode 100644 checker/tests/nullness/java8/methodref/GenericArity.java create mode 100644 checker/tests/nullness/java8/methodref/TestGenFunc.java create mode 100644 checker/tests/nullness/java8inference/NullnessBeamCrash3.java create mode 100644 checker/tests/optional/Base.java create mode 100644 checker/tests/tainting/Issue3036Tainting.java create mode 100644 checker/tests/tainting/MemberReferenceInference.java create mode 100644 checker/tests/tainting/SubtypingConstraint.java create mode 100644 checker/tests/tainting/TaintingPrimitiveTarget.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java delete mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java rename framework/src/main/java/org/checkerframework/framework/util/{typeinference => typeinference8}/TypeArgumentInference.java (57%) create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java create mode 100644 framework/tests/all-systems/BeamCrash2Full.java rename framework/tests/all-systems/{Crash.java => CaptureConversionCrash.java} (85%) create mode 100644 framework/tests/all-systems/DiamondLambda.java create mode 100644 framework/tests/all-systems/LambdaParams.java create mode 100644 framework/tests/all-systems/MyMapCrash.java create mode 100644 framework/tests/all-systems/UncheckedCrash.java create mode 100644 framework/tests/all-systems/java8/lambda/Issue436All.java rename framework/tests/all-systems/java8/lambda/{Lambda.java => ManyLambda.java} (92%) create mode 100644 framework/tests/all-systems/java8inference/BeamCrash1.java create mode 100644 framework/tests/all-systems/java8inference/BeamCrash3.java create mode 100644 framework/tests/all-systems/java8inference/BeamCrash4.java create mode 100644 framework/tests/all-systems/java8inference/Bug17.java create mode 100644 framework/tests/all-systems/java8inference/CollectorCollect.java create mode 100644 framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java create mode 100644 framework/tests/all-systems/java8inference/GuavaCrash.java create mode 100644 framework/tests/all-systems/java8inference/Issue6046.java create mode 100644 framework/tests/all-systems/java8inference/Issue6346.java rename framework/tests/all-systems/java8inference/{Issue953.java => Issue953Inference.java} (83%) create mode 100644 framework/tests/all-systems/java8inference/IteratorInference.java create mode 100644 framework/tests/all-systems/java8inference/MethodRefSuperCrash create mode 100644 framework/tests/all-systems/java8inference/PrimitiveTarget.java create mode 100644 framework/tests/all-systems/java8inference/TooManyConstraints.java create mode 100644 framework/tests/all-systems/java8inference/UncheckedConversionInference.java diff --git a/build.gradle b/build.gradle index ae28cdbd5c4..f6cd5aff37f 100644 --- a/build.gradle +++ b/build.gradle @@ -159,7 +159,7 @@ allprojects { // * any new checkers have been added, or // * backward-incompatible changes have been made to APIs or elsewhere. // To make a snapshot release: ./gradlew publish - version '3.42.1-SNAPSHOT' + version '3.43.0-SNAPSHOT' tasks.withType(JavaCompile).configureEach { options.fork = true diff --git a/checker/jtreg/nullness/Issue1809.java b/checker/jtreg/nullness/Issue1809.java index 94d03d624d6..c93491dd063 100644 --- a/checker/jtreg/nullness/Issue1809.java +++ b/checker/jtreg/nullness/Issue1809.java @@ -32,6 +32,7 @@ interface C { interface S {} + @SuppressWarnings("nullness") private Stream xrefsFor(B b) { return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) .filter(Optional::isPresent) diff --git a/checker/jtreg/nullness/issue824/Class2.out b/checker/jtreg/nullness/issue824/Class2.out index d2ef92fcde8..bc751a73c8b 100644 --- a/checker/jtreg/nullness/issue824/Class2.out +++ b/checker/jtreg/nullness/issue824/Class2.out @@ -1,10 +1,12 @@ -Class2.java:14:39: compiler.err.proc.messager: (type.argument.type.incompatible) -Class2.java:15:22: compiler.err.proc.messager: (type.argument.type.incompatible) -Class2.java:15:47: compiler.err.proc.messager: (type.argument.type.incompatible) -Class2.java:16:31: compiler.err.proc.messager: (type.argument.type.incompatible) -Class2.java:19:31: compiler.err.proc.messager: (type.argument.type.incompatible) -Class2.java:20:29: compiler.err.proc.messager: (type.argument.type.incompatible) -Class2.java:20:30: compiler.err.proc.messager: (argument.type.incompatible) -Class2.java:24:16: compiler.err.proc.messager: (override.return.invalid) -Class2.java:25:37: compiler.err.proc.messager: (type.argument.type.incompatible) -9 errors +- compiler.warn.proc.messager: junit-assertions.astub:(line 1,col 1): Package not found: org.junit.jupiter.api +- compiler.warn.proc.messager: junit-assertions.astub:(line 14,col 1): Type not found: org.junit.jupiter.api.Assertions +Class2.java:14:39: compiler.err.proc.messager: (type.argument) +Class2.java:15:20: compiler.err.proc.messager: (type.argument) +Class2.java:15:45: compiler.err.proc.messager: (type.argument) +Class2.java:16:27: compiler.err.proc.messager: (type.arguments.not.inferred) +Class2.java:19:27: compiler.err.proc.messager: (type.arguments.not.inferred) +Class2.java:20:26: compiler.err.proc.messager: (argument) +Class2.java:24:14: compiler.err.proc.messager: (override.return) +Class2.java:25:33: compiler.err.proc.messager: (type.arguments.not.inferred) +8 errors +2 warnings diff --git a/checker/jtreg/stubs/issue1456/WithStub.out b/checker/jtreg/stubs/issue1456/WithStub.out index 1ad5923e630..12bd6656d36 100644 --- a/checker/jtreg/stubs/issue1456/WithStub.out +++ b/checker/jtreg/stubs/issue1456/WithStub.out @@ -1,2 +1,2 @@ -Main.java:27:17: compiler.err.proc.messager: (type.argument.type.incompatible) +Main.java:26:13: compiler.err.proc.messager: (type.arguments.not.inferred) 1 error diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java index 2d2dbe2e9ce..eac335f6abd 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java @@ -1,8 +1,12 @@ package org.checkerframework.checker.initialization; import com.sun.source.tree.ClassTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; @@ -286,4 +290,19 @@ public static boolean isInitialized( return true; } + + @Override + protected ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + ParameterizedExecutableType x = + super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); + if (tree.getKind() == Kind.MEMBER_REFERENCE + && ((MemberReferenceTree) tree).getMode() == ReferenceMode.NEW) { + x.executableType.getReturnType().replaceAnnotation(INITIALIZED); + } + return x; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java index 649dee78940..cee7a0c313f 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java @@ -734,7 +734,13 @@ public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror p) { boolean allInitialized = true; Type type = ((JCTree) tree).type; for (ExpressionTree a : tree.getArguments()) { + if (!TreeUtils.isStandaloneExpression(a)) { + continue; + } + boolean oldShouldCache = shouldCache; + shouldCache = false; AnnotatedTypeMirror t = getAnnotatedType(a); + shouldCache = oldShouldCache; allInitialized &= (isInitialized(t) || isFbcBottom(t)); } if (!allInitialized) { diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java index 043f2ff907a..2de0553dc1a 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java @@ -573,8 +573,12 @@ public static SideEffectAnnotation weakest() { @Override public ParameterizedExecutableType methodFromUse( - ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - ParameterizedExecutableType mType = super.methodFromUse(tree, methodElt, receiverType); + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + ParameterizedExecutableType mType = + super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { return mType; diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java index f3ad4bb5eed..8574f004cd1 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java @@ -204,7 +204,8 @@ public AnnotationMirror withoutClose(@Nullable AnnotationMirror anno) { /** Treat non-owning method parameters as @MustCallUnknown (top) when the method is called. */ @Override - public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { + public void methodFromUsePreSubstitution( + ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { ExecutableElement declaration; if (tree instanceof MethodInvocationTree) { declaration = TreeUtils.elementFromUse((MethodInvocationTree) tree); @@ -214,15 +215,15 @@ public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutabl throw new TypeSystemError("unexpected type of method tree: " + tree.getKind()); } changeNonOwningParameterTypesToTop(declaration, type); - super.methodFromUsePreSubstitution(tree, type); + super.methodFromUsePreSubstitution(tree, type, resolvePolyQuals); } @Override protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type) { + NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { ExecutableElement declaration = TreeUtils.elementFromUse(tree); changeNonOwningParameterTypesToTop(declaration, type); - super.constructorFromUsePreSubstitution(tree, type); + super.constructorFromUsePreSubstitution(tree, type, resolvePolyQuals); } /** diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java index f90b51b077e..690526ca9f1 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java @@ -101,8 +101,9 @@ protected Set> createSupportedTypeQualifiers() { } @Override - public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { - ParameterizedExecutableType result = super.constructorFromUse(tree); + protected ParameterizedExecutableType constructorFromUse( + NewClassTree tree, boolean inferTypeArgs) { + ParameterizedExecutableType result = super.constructorFromUse(tree, inferTypeArgs); keyForPropagator.propagateNewClassTree(tree, result.executableType.getReturnType(), this); return result; } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java index 32e73394207..efd5827ba3c 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java @@ -166,7 +166,7 @@ public void propagateNewClassTree( if (path == null) { return; } - Tree assignmentContext = TreePathUtil.getAssignmentContext(path); + Tree assignmentContext = TreePathUtil.getContextForPolyExpression(path); AnnotatedTypeMirror assignedTo; if (assignmentContext instanceof VariableTree) { if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) assignmentContext)) { diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java index 8cf8b6c16b6..4eae71b13f6 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java @@ -484,8 +484,9 @@ protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { } @Override - public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { - ParameterizedExecutableType mType = super.methodFromUse(tree); + protected ParameterizedExecutableType methodFromUse( + MethodInvocationTree tree, boolean inferTypeArgs) { + ParameterizedExecutableType mType = super.methodFromUse(tree, inferTypeArgs); AnnotatedExecutableType method = mType.executableType; // Special cases for method invocations with specific arguments. diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java index 950c3c67e8d..27739ea9ece 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java @@ -2299,7 +2299,7 @@ private static boolean varTrackedInObligations( * Gets the Obligation whose resource aliase set contains the given local variable, if one exists * in {@code obligations}. * - * @param obligations set of Obligations + * @param obligations a set of Obligations * @param node variable of interest * @return the Obligation in {@code obligations} whose resource alias set contains {@code node}, * or {@code null} if there is no such Obligation diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java index 241a2fd06e8..3acc526b50e 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java @@ -231,7 +231,7 @@ protected SetOfTypes parseIgnoredExceptions(String ignoredExceptionsOptionValue) @SuppressWarnings({ // user input might not be a legal @CanonicalName, but it should be safe to pass to // `SetOfTypes.anyOfTheseNames` - "signature:argument", + "signature:type.arguments.not.inferred", }) protected @Nullable SetOfTypes parseExceptionSpecifier( String exceptionSpecifier, String ignoredExceptionsOptionValue) { diff --git a/checker/tests/fenum/UpperBoundsInByteCode.java b/checker/tests/fenum/UpperBoundsInByteCode.java index babf913ea03..6d1ae033ea5 100644 --- a/checker/tests/fenum/UpperBoundsInByteCode.java +++ b/checker/tests/fenum/UpperBoundsInByteCode.java @@ -1,5 +1,3 @@ -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - import org.checkerframework.checker.fenum.qual.Fenum; import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; @@ -8,7 +6,7 @@ public class UpperBoundsInByteCode { UncheckedByteCode<@Fenum("Bar") Object> bar; void typeVarWithNonObjectUpperBound(@Fenum("A") int a) { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) UncheckedByteCode.methodWithTypeVarBoundedByNumber(a); UncheckedByteCode.methodWithTypeVarBoundedByNumber(1); } diff --git a/checker/tests/guieffect/Java8Lambdas.java b/checker/tests/guieffect/Java8Lambdas.java index 1d12133c36e..d3892127154 100644 --- a/checker/tests/guieffect/Java8Lambdas.java +++ b/checker/tests/guieffect/Java8Lambdas.java @@ -101,10 +101,6 @@ public static void safeContextTestCases(UIElement elem) { runner.doUISafely(e -> e.dangerous()); @AlwaysSafe PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); safePolymorphicLambdaRunner.doEither(e -> e.repaint()); - // This next two are ok for this patch since the behavior is the same (no report) for - // lambdas as for annon classes. However, shouldn't this be (argument.type.incompatible) - // just because safePolymorphicLambdaRunner is not an @UI PolymorphicLambdaRunner ? Or, - // failing that (call.invalid.ui) since doEither is @PolyUIEffect ? safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); safePolymorphicLambdaRunner.doEither( new @UI PolymorphicFunctionalInterface() { @@ -147,7 +143,6 @@ public static void uiContextTestCases(UIElement elem) { runner.doUI(e -> e.repaint()); runner.doUI(e -> e.dangerous()); PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); - // No error, why? :: error: (argument.type.incompatible) safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); diff --git a/checker/tests/i18n/I18nCollectorsToList.java b/checker/tests/i18n/I18nCollectorsToList.java new file mode 100644 index 00000000000..c1dd9eb1050 --- /dev/null +++ b/checker/tests/i18n/I18nCollectorsToList.java @@ -0,0 +1,25 @@ +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.checkerframework.checker.i18n.qual.Localized; + +public class I18nCollectorsToList { + + void m(List strings) { + Stream s = strings.stream(); + + List collectedStrings1 = s.collect(Collectors.toList()); + List collectedStrings = s.collect(Collectors.toList()); + + // :: error: (methodref.param) + collectedStrings.forEach(System.out::println); + } + + void m2(List<@Localized String> strings) { + Stream<@Localized String> s = strings.stream(); + + List<@Localized String> collectedStrings = s.collect(Collectors.toList()); + + collectedStrings.forEach(System.out::println); + } +} diff --git a/checker/tests/index/IndexIssue6046.java b/checker/tests/index/IndexIssue6046.java new file mode 100644 index 00000000000..3c486b147e8 --- /dev/null +++ b/checker/tests/index/IndexIssue6046.java @@ -0,0 +1,44 @@ +import java.util.Formattable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.checkerframework.common.value.qual.ArrayLen; + +public class IndexIssue6046 { + + public interface Record extends Comparable, Formattable {} + + public interface Result extends List, Formattable {} + + @SuppressWarnings("unchecked") + public static + Collector>> intoResultGroups( + Function keyMapper) { + + return Collectors.groupingBy( + keyMapper, + LinkedHashMap::new, + Collector.[], Result>of( + // :: error: (array.access.unsafe.high.constant) + () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); + } + + @SuppressWarnings("unchecked") + public static + Collector>> intoResultGroups2( + Function keyMapper) { + + return Collectors.groupingBy( + keyMapper, + LinkedHashMap::new, + Collector. @ArrayLen(1) [], Result>of( + () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); + } + + public static Result result(R record) { + throw new RuntimeException(); + } +} diff --git a/checker/tests/interning/Issue2809.java b/checker/tests/interning/Issue2809.java index dd2e9777f3e..90a7f67024b 100644 --- a/checker/tests/interning/Issue2809.java +++ b/checker/tests/interning/Issue2809.java @@ -19,7 +19,7 @@ void new3(MyType<@Interned MyType> t, @Interned MyType non) { } void newFail(MyType t, int @UnknownInterned [] non) { - // :: error: (argument.type.incompatible) + // :: error: (type.arguments.not.inferred) t.self(new MyType<>(non)); } diff --git a/checker/tests/mustcall/CommandResponse.java b/checker/tests/mustcall/CommandResponse.java index 39309ec5f9f..d36f2885cba 100644 --- a/checker/tests/mustcall/CommandResponse.java +++ b/checker/tests/mustcall/CommandResponse.java @@ -1,7 +1,5 @@ // Based on a false positive in Zookeeper. -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - import java.util.Map; class CommandResponse { diff --git a/checker/tests/mustcall/EditLogInputStream.java b/checker/tests/mustcall/EditLogInputStream.java index 4757aeb6736..c8ad96750a9 100644 --- a/checker/tests/mustcall/EditLogInputStream.java +++ b/checker/tests/mustcall/EditLogInputStream.java @@ -7,8 +7,5 @@ abstract class EditLogInputStream implements Closeable { interface JournalSet extends Closeable { static final Comparator LOCAL_LOG_PREFERENCE_COMPARATOR = - // This is an undesirable false positive that occurs because of the defaulting - // that the Must Call Checker uses for generics. - // :: error: (type.argument.type.incompatible) Comparator.comparing(EditLogInputStream::isLocalLog).reversed(); } diff --git a/checker/tests/mustcall/ListOfMustCall.java b/checker/tests/mustcall/ListOfMustCall.java index 9ec53159f89..cd32d530fbb 100644 --- a/checker/tests/mustcall/ListOfMustCall.java +++ b/checker/tests/mustcall/ListOfMustCall.java @@ -13,6 +13,7 @@ class ListOfMustCall { static void test(ListOfMustCall lm) { // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) List l = new ArrayList<>(); // add(E e) takes an object of the type argument's type l.add(lm); @@ -22,6 +23,7 @@ static void test(ListOfMustCall lm) { static void test2(ListOfMustCall lm) { // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) List<@MustCall("a") ListOfMustCall> l = new ArrayList<>(); l.add(lm); l.remove(lm); diff --git a/checker/tests/nullness/ArrayRefs.java b/checker/tests/nullness/ArrayRefs.java index 32f206fbc99..10991fb96f5 100644 --- a/checker/tests/nullness/ArrayRefs.java +++ b/checker/tests/nullness/ArrayRefs.java @@ -19,7 +19,7 @@ public static void test2() { takeNNList(Arrays.asList(new Object[] {a})); takeNNList(Arrays.asList(new Object[] {a})); takeNNList(Arrays.asList(a, a, a)); - // :: error: (argument.type.incompatible) + // :: error: (type.arguments.not.inferred) takeNNList(Arrays.asList(a, a, null)); } diff --git a/checker/tests/nullness/DependentTypeTypeInference.java b/checker/tests/nullness/DependentTypeTypeInference.java new file mode 100644 index 00000000000..bdcb48b248c --- /dev/null +++ b/checker/tests/nullness/DependentTypeTypeInference.java @@ -0,0 +1,13 @@ +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.KeyFor; + +public class DependentTypeTypeInference { + private final Map nameToPpt = new LinkedHashMap<>(); + + public Collection<@KeyFor("nameToPpt") String> nameStringSet() { + return Collections.unmodifiableSet(nameToPpt.keySet()); + } +} diff --git a/checker/tests/nullness/InferNullType.java b/checker/tests/nullness/InferNullType.java index b1722fa68e4..1e0a4c91ed7 100644 --- a/checker/tests/nullness/InferNullType.java +++ b/checker/tests/nullness/InferNullType.java @@ -18,7 +18,7 @@ T toInfer4(T input, S p2) { } void x() { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Object m = toInfer(null); Object m2 = toInfer2(null); @@ -26,11 +26,11 @@ void x() { Object m4 = toInfer3(1, null); Object m5 = toInfer3(null, 1); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Object m6 = toInfer4(null, null); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Object m7 = toInfer4(1, null); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Object m8 = toInfer4(null, 1); } } diff --git a/checker/tests/nullness/InferTypeArgsConditionalExpression.java b/checker/tests/nullness/InferTypeArgsConditionalExpression.java index 32b43ff8da3..cac9a5784a2 100644 --- a/checker/tests/nullness/InferTypeArgsConditionalExpression.java +++ b/checker/tests/nullness/InferTypeArgsConditionalExpression.java @@ -9,7 +9,7 @@ public class InferTypeArgsConditionalExpression { public void foo(Generic real, Generic other, boolean flag) { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) bar(flag ? real : other); } diff --git a/checker/tests/nullness/Issue1102.java b/checker/tests/nullness/Issue1102.java index 56bcb47a16b..3270b9efb06 100644 --- a/checker/tests/nullness/Issue1102.java +++ b/checker/tests/nullness/Issue1102.java @@ -22,7 +22,7 @@ class Issue1102Use { void bar() { Issue1102Decl d = Issue1102Decl.newInstance(f); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) d = Issue1102Decl.newInstance(g); } } diff --git a/checker/tests/nullness/Issue2048.java b/checker/tests/nullness/Issue2048.java index ad9d477639d..034fc05f771 100644 --- a/checker/tests/nullness/Issue2048.java +++ b/checker/tests/nullness/Issue2048.java @@ -17,6 +17,7 @@ void foo(Fooer fooer) {} } // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Fooer<@Nullable Foo> nblFooer = new Fooer<>(); Fooer<@NonNull Foo> nnFooer = new Fooer<>(); diff --git a/checker/tests/nullness/Issue3150.java b/checker/tests/nullness/Issue3150.java index c6b1639113e..45e9902734b 100644 --- a/checker/tests/nullness/Issue3150.java +++ b/checker/tests/nullness/Issue3150.java @@ -5,9 +5,9 @@ public class Issue3150 { void foo(@Nullable Object nble, @NonNull Object nn) { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) requireNonNull1(null); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) requireNonNull1(nble); requireNonNull1("hello"); requireNonNull1(nn); diff --git a/checker/tests/nullness/Issue3443.java b/checker/tests/nullness/Issue3443.java index 8dd2d14be55..0e369b6a8bb 100644 --- a/checker/tests/nullness/Issue3443.java +++ b/checker/tests/nullness/Issue3443.java @@ -8,7 +8,6 @@ public class Issue3443 { public static void main(String[] args) { Supplier3443<@Nullable String> s1 = () -> null; - // TODO: passThrough(s1) should cause an error. #979. Supplier3443 s2 = passThrough(s1); s2.get().toString(); } diff --git a/checker/tests/nullness/Issue3754.java b/checker/tests/nullness/Issue3754.java index 3b91fff0aea..b26d7263265 100644 --- a/checker/tests/nullness/Issue3754.java +++ b/checker/tests/nullness/Issue3754.java @@ -1,5 +1,3 @@ -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - import org.checkerframework.checker.nullness.qual.Nullable; class Issue3754 { diff --git a/checker/tests/nullness/Issue3764.java b/checker/tests/nullness/Issue3764.java new file mode 100644 index 00000000000..3c704c9a378 --- /dev/null +++ b/checker/tests/nullness/Issue3764.java @@ -0,0 +1,27 @@ +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collector; +import org.checkerframework.checker.nullness.qual.KeyForBottom; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.checkerframework.framework.qual.TypeUseLocation; + +@DefaultQualifier(locations = TypeUseLocation.LOWER_BOUND, value = KeyForBottom.class) +class Issue3764 { + static void use(@Nullable Collector collector) {} + + static @Nullable Collector> calc( + Function keyFunction, + Function valueFunction, + BiFunction mergeFunction) { + return null; + } + + void foo(Function f) { + // No error when using a lambda + use(calc(f, f, (a, b) -> Math.max(a, b))); + // Error when using a method reference + use(calc(f, f, Math::max)); + } +} diff --git a/checker/tests/nullness/Issue4523.java b/checker/tests/nullness/Issue4523.java index bf1ba98bc09..7864c7c0ee5 100644 --- a/checker/tests/nullness/Issue4523.java +++ b/checker/tests/nullness/Issue4523.java @@ -1,10 +1,12 @@ import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; public class Issue4523 { interface InterfaceA> extends InterfaceB {} interface InterfaceB> { + @Pure @Nullable T g(); } diff --git a/checker/tests/nullness/Issue531.java b/checker/tests/nullness/Issue531.java index 2aab9d9d054..a1251875126 100644 --- a/checker/tests/nullness/Issue531.java +++ b/checker/tests/nullness/Issue531.java @@ -1,7 +1,5 @@ // Test case for issue #979: https://github.com/typetools/checker-framework/issues/979 -// @skip-test - import org.checkerframework.checker.nullness.qual.*; public class Issue531 { @@ -14,12 +12,12 @@ void foo(MyStream stream) {} static MyCollector> toList() { return new MyCollector<>(); } -} -class MyList {} + static class MyList {} -class MyCollector {} + static class MyCollector {} -abstract class MyStream { - public abstract R collect(MyCollector c); + abstract static class MyStream { + public abstract R collect(MyCollector c); + } } diff --git a/checker/tests/nullness/Issue579Error.java b/checker/tests/nullness/Issue579Error.java index eeebdcf1051..d54efa36c6b 100644 --- a/checker/tests/nullness/Issue579Error.java +++ b/checker/tests/nullness/Issue579Error.java @@ -6,7 +6,7 @@ public class Issue579Error { public void foo(Generic real, Generic other, boolean flag) { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) bar(flag ? real : other); } diff --git a/checker/tests/nullness/Issue738.java b/checker/tests/nullness/Issue738.java index 1e94be90366..c5103bc1be0 100644 --- a/checker/tests/nullness/Issue738.java +++ b/checker/tests/nullness/Issue738.java @@ -19,11 +19,11 @@ void methodA(int[] is, Object @Nullable [] os, int i) { methodB2(is, os); break; case 3: - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) methodB3(is, os); break; case 4: - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) methodB4(is, os); break; } diff --git a/checker/tests/nullness/Issue759.java b/checker/tests/nullness/Issue759.java index 291e77a6f4b..eea87d3a8f9 100644 --- a/checker/tests/nullness/Issue759.java +++ b/checker/tests/nullness/Issue759.java @@ -35,7 +35,7 @@ void lowercase(final S items) {} void possibleValues2(final Gen genType) { lowercase2(genType.getConstants()); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) lowercase2(genType.getNullableConstants()); } diff --git a/checker/tests/nullness/Iterate.java b/checker/tests/nullness/Iterate.java index 7cbc6c1f8ba..d36975723f8 100644 --- a/checker/tests/nullness/Iterate.java +++ b/checker/tests/nullness/Iterate.java @@ -1,5 +1,3 @@ -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - package wildcards; public class Iterate { diff --git a/checker/tests/nullness/MethodTypeVars4.java b/checker/tests/nullness/MethodTypeVars4.java index fd95e400c6d..a0ff26e25ef 100644 --- a/checker/tests/nullness/MethodTypeVars4.java +++ b/checker/tests/nullness/MethodTypeVars4.java @@ -23,7 +23,7 @@ void f1(I i) { void f2(I i) { @NonNull String s = i.doit(); s = i.doit3(); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) List<@Nullable String> ls = i.doit2(); } } diff --git a/checker/tests/nullness/RawTypesNullness.java b/checker/tests/nullness/RawTypesNullness.java index 20991ebb4f2..8040fea02bf 100644 --- a/checker/tests/nullness/RawTypesNullness.java +++ b/checker/tests/nullness/RawTypesNullness.java @@ -1,5 +1,3 @@ -// @above-java17-jdk-skip-test TODO: reinstate, false negatives may be due to issue #979 - import org.checkerframework.checker.nullness.qual.Nullable; class Generic {} diff --git a/checker/tests/nullness/flow/TestNullnessUtil.java b/checker/tests/nullness/flow/TestNullnessUtil.java index 92146f6fe83..09c26af066e 100644 --- a/checker/tests/nullness/flow/TestNullnessUtil.java +++ b/checker/tests/nullness/flow/TestNullnessUtil.java @@ -24,6 +24,7 @@ void testArr1(@Nullable Object @NonNull [] a) { @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); // Careful, the non-deep version only casts the main modifier. // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) @NonNull Object[] l2b = NullnessUtil.castNonNull(a); // OK @Nullable Object[] l2c = NullnessUtil.castNonNull(a); @@ -34,6 +35,7 @@ void testArr1b(@Nullable Object @Nullable [] a) { @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); // Careful, the non-deep version only casts the main modifier. // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) @NonNull Object[] l2b = NullnessUtil.castNonNull(a); // OK @Nullable Object[] l2c = NullnessUtil.castNonNull(a); diff --git a/checker/tests/nullness/flow/TestOpt.java b/checker/tests/nullness/flow/TestOpt.java index 2b66952f205..72cf3c667be 100644 --- a/checker/tests/nullness/flow/TestOpt.java +++ b/checker/tests/nullness/flow/TestOpt.java @@ -65,8 +65,8 @@ void foo7(Object p) { void foo7b(@Nullable Object p) { try { - // :: error: (assignment.type.incompatible) :: error: (type.argument.type.incompatible) - // :: error: (return.type.incompatible) + // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) @NonNull Object o = Opt.orElseThrow(p, () -> null); } catch (Throwable t) { // p was null diff --git a/checker/tests/nullness/generics/CollectionsAnnotations.java b/checker/tests/nullness/generics/CollectionsAnnotations.java index c583d81b869..cd6cdbd0ad6 100644 --- a/checker/tests/nullness/generics/CollectionsAnnotations.java +++ b/checker/tests/nullness/generics/CollectionsAnnotations.java @@ -48,7 +48,7 @@ static void bad1() { } static void bad2() { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) addNull2(new PriorityQueue1<@NonNull Object>()); } @@ -57,7 +57,7 @@ public static void main(String[] args) { } static void bad3() { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) addNull2(new PriorityQueue2<@NonNull Object>()); } diff --git a/checker/tests/nullness/generics/CollectionsAnnotationsMin.java b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java index 5f328cd0b5c..75c4cdf44e1 100644 --- a/checker/tests/nullness/generics/CollectionsAnnotationsMin.java +++ b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java @@ -49,7 +49,7 @@ static void bad() { addNull2(new PriorityQueue1<@NonNull Object>()); addNull2b(new PriorityQueue1<@NonNull Object>(), new Object()); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) addNull3(new PriorityQueue1<@NonNull Object>()); // :: error: (argument.type.incompatible) diff --git a/checker/tests/nullness/generics/GenericArgs.java b/checker/tests/nullness/generics/GenericArgs.java index f116774fcf3..a89f9bc5b76 100644 --- a/checker/tests/nullness/generics/GenericArgs.java +++ b/checker/tests/nullness/generics/GenericArgs.java @@ -1,9 +1,10 @@ -import java.io.*; import java.util.Comparator; import java.util.HashSet; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.dataflow.qual.*; +import org.checkerframework.checker.nullness.qual.KeyForBottom; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class GenericArgs { @@ -48,7 +49,7 @@ void test5() { } void testRecursiveDeclarations() { - class MyComparator<@NonNull T extends @NonNull Comparable> + class MyComparator<@NonNull @KeyForBottom T extends @NonNull Comparable> implements Comparator { @Pure public int compare(T[] a, T[] b) { diff --git a/checker/tests/nullness/generics/GenericsBounds5.java b/checker/tests/nullness/generics/GenericsBounds5.java index 89858b72aae..204019bf253 100644 --- a/checker/tests/nullness/generics/GenericsBounds5.java +++ b/checker/tests/nullness/generics/GenericsBounds5.java @@ -34,13 +34,13 @@ void addNull2(Collection1<@Nullable ? extends @Nullable Object> l) { void bad(Collection1<@NonNull Integer> nnarg) { // These have to be forbidden, because f1 might refer to a // collection that has NonNull as type argument. - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) addNull1(nnarg); // :: error: (argument.type.incompatible) addNull2(nnarg); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) addNull3(nnarg, Integer.valueOf(4)); } } diff --git a/checker/tests/nullness/generics/Issue329.java b/checker/tests/nullness/generics/Issue329.java index 04d20232bf4..de42a45acc9 100644 --- a/checker/tests/nullness/generics/Issue329.java +++ b/checker/tests/nullness/generics/Issue329.java @@ -28,13 +28,12 @@ interface Flag {} void f1(Flag<@Nullable String> flag) { String s = getValue(flag); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) setExtension(s); } void f2(Flag<@Nullable String> flag) { - // TODO: false negative. See #979. - //// :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) setExtension(getValue(flag)); } } diff --git a/checker/tests/nullness/generics/MethodTypeVars.java b/checker/tests/nullness/generics/MethodTypeVars.java index 5839d93fc80..15c8bd01d5c 100644 --- a/checker/tests/nullness/generics/MethodTypeVars.java +++ b/checker/tests/nullness/generics/MethodTypeVars.java @@ -6,11 +6,11 @@ */ public class MethodTypeVars { void m() { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Object a = A.badMethod(null); Object b = A.badMethod(new Object()); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) A.goodMethod(null); A.goodMethod(new Object()); } diff --git a/checker/tests/nullness/generics/MethodTypeVars5.java b/checker/tests/nullness/generics/MethodTypeVars5.java index e8cb11b61c4..f0255c2223a 100644 --- a/checker/tests/nullness/generics/MethodTypeVars5.java +++ b/checker/tests/nullness/generics/MethodTypeVars5.java @@ -49,11 +49,13 @@ String doit3b2() { String doit3c() { // :: error: (return.type.incompatible) + // :: error: (type.arguments.not.inferred) return doit3(new B<@Nullable String>("Hi")); } void doit3d() { // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) @NonNull String s = doit3(new B<@Nullable String>("Hi")); } @@ -80,6 +82,7 @@ String doit4() { // Passing the null argument has an impact on the inferred type argument: // the type variable appears as the top-level type. // :: error: (return.type.incompatible) + // :: error: (type.arguments.not.inferred) return doit4("Ha!", null); } diff --git a/checker/tests/nullness/generics/MethodTypeVars6.java b/checker/tests/nullness/generics/MethodTypeVars6.java index eb8dd0edcb4..f93070b41e8 100644 --- a/checker/tests/nullness/generics/MethodTypeVars6.java +++ b/checker/tests/nullness/generics/MethodTypeVars6.java @@ -45,7 +45,7 @@ class Test2 { class Test3 { APair<@NonNull X, @NonNull X> test1(@Nullable X p) { - // :: error: (return.type.incompatible) + // :: error: (type.arguments.not.inferred) return APair.of(p, (X) null); } } @@ -53,10 +53,10 @@ class Test3 { class Test4 { APair<@Nullable String, Integer> psi = PairSub.of("Hi", 42); APair<@Nullable String, Integer> psi2 = PairSub.of(null, 42); - // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) APair psi3 = PairSub.of(null, 42); APair<@Nullable String, Integer> psisw = PairSubSwitching.ofPSS(42, null); - // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) APair psisw2 = PairSubSwitching.ofPSS(42, null); } diff --git a/checker/tests/nullness/generics/WildcardSubtyping2.java b/checker/tests/nullness/generics/WildcardSubtyping2.java index ef9fb6138cb..0f01445e41f 100644 --- a/checker/tests/nullness/generics/WildcardSubtyping2.java +++ b/checker/tests/nullness/generics/WildcardSubtyping2.java @@ -20,6 +20,7 @@ class MyGenericEB<@NonNull T extends @NonNull MyClass> {} class UseMyGenericEB { MyGenericEB<@NonNull MyCloneClass> nonNull = new MyGenericEB<>(); // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) MyGenericEB<@Nullable MyCloneClass> nullable = new MyGenericEB<>(); MyGenericEB interfaceNN = nonNull; diff --git a/checker/tests/nullness/java17/InferSwitchExpr.java b/checker/tests/nullness/java17/InferSwitchExpr.java new file mode 100644 index 00000000000..dd6933b84b3 --- /dev/null +++ b/checker/tests/nullness/java17/InferSwitchExpr.java @@ -0,0 +1,37 @@ +// @below-java17-jdk-skip-test + +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class InferSwitchExpr { + enum Letter { + A, + B, + C, + D; + } + + List singletonList(T t) { + throw new RuntimeException(); + } + + @Nullable List method(Letter letter, boolean a, boolean b) { + return switch (letter) { + case A -> { + if (a) { + if (b) { + // :: error: (type.arguments.not.inferred) + yield singletonList(null); + } + // :: error: (type.arguments.not.inferred) + yield singletonList(null); + } + // :: error: (type.arguments.not.inferred) + yield singletonList(null); + } + case B -> null; + case C -> null; + case D -> null; + }; + } +} diff --git a/checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java b/checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java new file mode 100644 index 00000000000..58ca103d5b7 --- /dev/null +++ b/checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java @@ -0,0 +1,37 @@ +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +// @below-java17-jdk-skip-test +public class SwitchExpressionTypeArgInference { + T method(T t) { + return t; + } + + void test1(int i, @Nullable String nullable) { + @NonNull String s = + // :: error: (assignment) + // :: error: (type.arguments.not.inferred) + method( + switch (i) { + case 0: + yield method(nullable); + case 1: + yield ""; + default: + yield ""; + }); + } + + void test2(int i, @Nullable String nullable) { + @NonNull String s = + method( + switch (i) { + case 0: + yield method("nullable"); + case 1: + yield ""; + default: + yield ""; + }); + } +} diff --git a/checker/tests/nullness/java8/Issue1098.java b/checker/tests/nullness/java8/Issue1098.java index 987132be6ed..b6af741597e 100644 --- a/checker/tests/nullness/java8/Issue1098.java +++ b/checker/tests/nullness/java8/Issue1098.java @@ -11,9 +11,6 @@ void cls(Class p1, T p2) {} @SuppressWarnings("keyfor:type.argument") void use() { opt(Optional.empty(), null); - // TODO: false positive, because type argument inference does not account for @Covariant. - // See https://github.com/typetools/checker-framework/issues/979. - // :: error: (argument.type.incompatible) cls(this.getClass(), null); } } diff --git a/checker/tests/nullness/java8/Issue1098NoJdk.java b/checker/tests/nullness/java8/Issue1098NoJdk.java index 642bc9276a2..c4a61677770 100644 --- a/checker/tests/nullness/java8/Issue1098NoJdk.java +++ b/checker/tests/nullness/java8/Issue1098NoJdk.java @@ -12,9 +12,6 @@ class Issue1098NoJdk { void cls2(Class p1, T p2) {} void use2(MyObject ths) { - // TODO: false positive, because type argument inference does not account for @Covariant. - // See https://github.com/typetools/checker-framework/issues/979. - // :: error: (argument.type.incompatible) cls2(ths.getMyClass(), null); } } diff --git a/checker/tests/nullness/java8/Issue1633.java b/checker/tests/nullness/java8/Issue1633.java index 846616dcb28..4a880bb2148 100644 --- a/checker/tests/nullness/java8/Issue1633.java +++ b/checker/tests/nullness/java8/Issue1633.java @@ -39,12 +39,14 @@ void foo4nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { } void foo41(Optional1633 o) { - @SuppressWarnings("return.type.incompatible") // https://tinyurl.com/cfissue/979 + // This is a false postive because inference doesn't work with poly qualifiers. + // :: error: (return.type.incompatible) @Nullable String str3 = o.orElseGetPolyNull(() -> null); } void foo41nw(Optional1633 o) { - @SuppressWarnings("return.type.incompatible") // https://tinyurl.com/cfissue/979 + // This is a false postive because inference doesn't work with poly qualifiers. + // :: error: (return.type.incompatible) @Nullable String str3 = o.orElseGetPolyNullNoWildcard(() -> null); } @@ -97,7 +99,7 @@ public T orElseGetUnannotated(Supplier other) { } public @Nullable T orElseGetNullableNoWildcard(Supplier other) { - // The commented-out line fails to typecheck due to issue #979 + // The commented-out line fails to typecheck. // return value != null ? value : other.get(); if (value != null) { return value; @@ -115,7 +117,7 @@ public T orElseGetUnannotated(Supplier other) { } public @PolyNull T orElseGetPolyNullNoWildcard(Supplier other) { - // The commented-out line fails to typecheck due to issue #979 + // The commented-out line fails to typecheck. // return value != null ? value : other.get(); if (value != null) { return value; diff --git a/checker/tests/nullness/java8/lambda/TypeVarAssign.java b/checker/tests/nullness/java8/lambda/TypeVarAssign.java index 8e24949783b..729dc9afffb 100644 --- a/checker/tests/nullness/java8/lambda/TypeVarAssign.java +++ b/checker/tests/nullness/java8/lambda/TypeVarAssign.java @@ -1,7 +1,5 @@ import org.checkerframework.checker.nullness.qual.*; -// @skip-test We can only handle this after we get better method inference. - interface Fn { T func(T t); } diff --git a/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java index f0b7e7b4f04..480f2185f6b 100644 --- a/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java +++ b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java @@ -3,7 +3,7 @@ public class ClassTypeArgInference { public static void main(String[] args) { Gen o = new Gen<>(""); - // :: error: (methodref.param.invalid) + // :: error: (type.arguments.not.inferred) Factory f = Gen::make; // :: error: (methodref.param.invalid) Factory f2 = Gen::make; diff --git a/checker/tests/nullness/java8/methodref/GenericArity.java b/checker/tests/nullness/java8/methodref/GenericArity.java deleted file mode 100644 index 7ee4ba4d6d9..00000000000 --- a/checker/tests/nullness/java8/methodref/GenericArity.java +++ /dev/null @@ -1,25 +0,0 @@ -// Test case for Issue #803 -// https://github.com/typetools/checker-framework/issues/803 -// @skip-test - -import org.checkerframework.checker.nullness.qual.*; - -interface GenFunc { - T apply(U u); -} - -interface GenFunc2 { - T apply(U u); -} - -class TestGenFunc { - static V apply(P u) { - throw new RuntimeException(""); - } - - void context() { - GenFunc f = TestGenFunc::apply; - // :: error: (methodref.param.invalid) - GenFunc2 f2 = TestGenFunc::apply; - } -} diff --git a/checker/tests/nullness/java8/methodref/TestGenFunc.java b/checker/tests/nullness/java8/methodref/TestGenFunc.java new file mode 100644 index 00000000000..9b5d9440fad --- /dev/null +++ b/checker/tests/nullness/java8/methodref/TestGenFunc.java @@ -0,0 +1,41 @@ +import org.checkerframework.checker.nullness.qual.*; + +public class TestGenFunc { + interface FuncNullableParam { + T nullableParam(U u); + } + + interface FuncNonNullParam { + T nonNullParam(U u); + } + + static V nonNullReturn(P u) { + throw new RuntimeException(""); + } + + static V nonNullParameter(P u) { + throw new RuntimeException(""); + } + + static V allNullable(P u) { + throw new RuntimeException(""); + } + + void context() { + // :: error: (type.arguments.not.inferred) + FuncNullableParam f = TestGenFunc::nonNullReturn; + // :: error: (type.arguments.not.inferred) + FuncNonNullParam f2 = TestGenFunc::nonNullReturn; + } + + void context2() { + // :: error: (type.arguments.not.inferred) + FuncNullableParam f = TestGenFunc::nonNullParameter; + FuncNonNullParam f2 = TestGenFunc::nonNullParameter; + } + + void context3() { + FuncNullableParam f = TestGenFunc::allNullable; + FuncNonNullParam f2 = TestGenFunc::allNullable; + } +} diff --git a/checker/tests/nullness/java8inference/InLambdaAnnotated.java b/checker/tests/nullness/java8inference/InLambdaAnnotated.java index d4aa703844a..973a04b553c 100644 --- a/checker/tests/nullness/java8inference/InLambdaAnnotated.java +++ b/checker/tests/nullness/java8inference/InLambdaAnnotated.java @@ -24,8 +24,6 @@ static Box transform(Function function) { class Infer { // The nested Mine.some() needs to infer the right type. Box> g = - // TODO: This is a false positive. - // :: error: (assignment.type.incompatible) Boxes.transform( el -> { return Mine.some(); diff --git a/checker/tests/nullness/java8inference/Issue1032.java b/checker/tests/nullness/java8inference/Issue1032.java index 62351cef87e..bf2661bb3b8 100644 --- a/checker/tests/nullness/java8inference/Issue1032.java +++ b/checker/tests/nullness/java8inference/Issue1032.java @@ -25,8 +25,6 @@ public class Issue1032 { } Stream<@NonNull T> mapTCast(Stream<@Nullable T> arg) { - // TODO: false postive - // :: error: (return.type.incompatible) return arg.map(Issue1032::castTToNonNull); } } diff --git a/checker/tests/nullness/java8inference/Issue1630.java b/checker/tests/nullness/java8inference/Issue1630.java index c242af258d2..ec587374da2 100644 --- a/checker/tests/nullness/java8inference/Issue1630.java +++ b/checker/tests/nullness/java8inference/Issue1630.java @@ -9,10 +9,18 @@ public class Issue1630 { return null; } - @SuppressWarnings("nullness") // Issue 979 - public static List f(List xs) { + public static List<@Nullable String> f(@Nullable List xs) { return xs != null ? xs.stream().map(Issue1630::toString).filter(Objects::nonNull).collect(Collectors.toList()) : Collections.emptyList(); } + + public static List f2(@Nullable List xs) { + return xs != null + // TODO: we could refine the type of filter is the postconditions of the predicate is + // @EnsuresNonNull("#1"). + // :: error: (type.arguments.not.inferred) + ? xs.stream().map(Issue1630::toString).filter(Objects::nonNull).collect(Collectors.toList()) + : Collections.emptyList(); + } } diff --git a/checker/tests/nullness/java8inference/Issue2235.java b/checker/tests/nullness/java8inference/Issue2235.java index 4d0eb00ed73..6ce8743b642 100644 --- a/checker/tests/nullness/java8inference/Issue2235.java +++ b/checker/tests/nullness/java8inference/Issue2235.java @@ -1,10 +1,6 @@ // Test case that was submitted in Issue 2235, but is caused // by a false negative from Issue 979 // https://github.com/typetools/checker-framework/issues/979 - -// Skip until correct error is issued. -// @skip-test - public class Issue2235 { // Simple wrapper class with a public generic method // to make an instance: @@ -22,7 +18,7 @@ public static Holder make(T t) { public static void main(String[] args) throws Exception { // Null is hidden via nested calls, but assigned to a non-null type: - // :: error: (TODO) + // :: error: (type.arguments.not.inferred) Holder> h = Holder.make(Holder.make(null)); // NullPointerException will fire here: h.t.t.toString(); diff --git a/checker/tests/nullness/java8inference/Issue2719.java b/checker/tests/nullness/java8inference/Issue2719.java index 5b5fdfc3554..ca85681148d 100644 --- a/checker/tests/nullness/java8inference/Issue2719.java +++ b/checker/tests/nullness/java8inference/Issue2719.java @@ -7,7 +7,7 @@ public class Issue2719 { public static void main(String[] args) { List iList = asList(0); List<@Nullable Integer> jList = asList((Integer) null); - // TODO:: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) List> both = passThrough(asList(iList, jList)); System.out.println(both.get(1).get(0).intValue()); } diff --git a/checker/tests/nullness/java8inference/Issue402.java b/checker/tests/nullness/java8inference/Issue402.java index 8aa18c8cd70..da63eb9d52b 100644 --- a/checker/tests/nullness/java8inference/Issue402.java +++ b/checker/tests/nullness/java8inference/Issue402.java @@ -5,9 +5,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; -// Type argument inference does not infer the correct types. -// Once Issue 979 is fixed, this suppression should be removed. -@SuppressWarnings({"nullness", "keyfor"}) // Issue 979 +// @skip-test 979 public final class Issue402 { static final Comparator COMPARATOR = Comparator.comparing(Issue402::getStr1, Comparator.nullsFirst(Comparator.naturalOrder())) diff --git a/checker/tests/nullness/java8inference/Issue887.java b/checker/tests/nullness/java8inference/Issue887.java index 6f331fc8c60..1d18314607b 100644 --- a/checker/tests/nullness/java8inference/Issue887.java +++ b/checker/tests/nullness/java8inference/Issue887.java @@ -7,7 +7,7 @@ public abstract class Issue887 { void test() { - // :: error: (argument.type.incompatible) :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) method(foo(null).get(0)); methodNullable(fooNullable(null).get(0)); } diff --git a/checker/tests/nullness/java8inference/Issue980.java b/checker/tests/nullness/java8inference/Issue980.java index 570a3dd1f46..9fa8abe1557 100644 --- a/checker/tests/nullness/java8inference/Issue980.java +++ b/checker/tests/nullness/java8inference/Issue980.java @@ -1,8 +1,6 @@ // Test case that was submitted in Issue 402, but was combined with Issue 979 // https://github.com/typetools/checker-framework/issues/979 -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/checker/tests/nullness/java8inference/NullnessBeamCrash3.java b/checker/tests/nullness/java8inference/NullnessBeamCrash3.java new file mode 100644 index 00000000000..81186e44b7e --- /dev/null +++ b/checker/tests/nullness/java8inference/NullnessBeamCrash3.java @@ -0,0 +1,33 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + +public class NullnessBeamCrash3 { + + @SuppressWarnings({"unchecked", "rawtypes"}) + private CoderOrFailure inferCoderOrFail(PInput input, PTransform transform) { + return new CoderOrFailure<>(((PTransform) transform).getDefaultOutputCoder(input, this), null); + } + + private static class CoderOrFailure { + + private final @Nullable Coder coder; + private final @Nullable String failure; + + public CoderOrFailure(@Nullable Coder coder, @Nullable String failure) { + this.coder = coder; + this.failure = failure; + } + } + + public interface PInput {} + + public abstract static class Coder {} + + public abstract static class PTransform { + + public Coder getDefaultOutputCoder(InputT input, NullnessBeamCrash3 output) { + throw new RuntimeException(); + } + } + + public interface POutput {} +} diff --git a/checker/tests/nullness/java8inference/OneOf.java b/checker/tests/nullness/java8inference/OneOf.java index 352459c785d..0bc7f253539 100644 --- a/checker/tests/nullness/java8inference/OneOf.java +++ b/checker/tests/nullness/java8inference/OneOf.java @@ -1,16 +1,17 @@ // Test case for Issue 979: // https://github.com/typetools/checker-framework/issues/979 +import java.util.ArrayList; import java.util.List; -@SuppressWarnings({"initialization", "nullness"}) // don't bother with implementations public class OneOf { - static List alist; + static List alist = new ArrayList<>(); static V oneof(V v1, V v2) { return v1; } + @SuppressWarnings("nullness") // don't bother with implementations static List empty() { return null; } diff --git a/checker/tests/nullness/java8inference/SimpleLambda.java b/checker/tests/nullness/java8inference/SimpleLambda.java index 59f64277c58..cbd0c55371a 100644 --- a/checker/tests/nullness/java8inference/SimpleLambda.java +++ b/checker/tests/nullness/java8inference/SimpleLambda.java @@ -1,5 +1,3 @@ -// @skip-test until Issue 979 is fixed. - import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/checker/tests/nullness/jdkannotations/HashtableTest.java b/checker/tests/nullness/jdkannotations/HashtableTest.java index 84ba3d724ac..221d49840e2 100644 --- a/checker/tests/nullness/jdkannotations/HashtableTest.java +++ b/checker/tests/nullness/jdkannotations/HashtableTest.java @@ -6,12 +6,14 @@ public class HashtableTest { public static void main(String[] args) { // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Hashtable<@Nullable Integer, String> ht1 = new Hashtable<>(); // Suffers null pointer exception ht1.put(null, "hello"); // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) Hashtable ht2 = new Hashtable<>(); // Suffers null pointer exception diff --git a/checker/tests/nullness/jdkannotations/Issue1142.java b/checker/tests/nullness/jdkannotations/Issue1142.java index a6ebd43380c..88e99d78daa 100644 --- a/checker/tests/nullness/jdkannotations/Issue1142.java +++ b/checker/tests/nullness/jdkannotations/Issue1142.java @@ -7,6 +7,7 @@ public class Issue1142 { void foo() { // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) ConcurrentHashMap chm1 = new ConcurrentHashMap<>(); chm1.put(1, null); } diff --git a/checker/tests/optional/Base.java b/checker/tests/optional/Base.java new file mode 100644 index 00000000000..4e16e1c2b9d --- /dev/null +++ b/checker/tests/optional/Base.java @@ -0,0 +1,17 @@ +public class Base { + + static class OneClass, B extends OneClass> { + TwoClass get() { + return null; + } + } + + static class TwoClass< + C extends ThreeClass, D extends SubOneClass, E extends TwoClass> {} + + static class ThreeClass, G extends OneClass> {} + + class SubOneClass< + H extends ThreeClass, I extends SubOneClass, J extends TwoClass> + extends OneClass {} +} diff --git a/checker/tests/optional/Marks5.java b/checker/tests/optional/Marks5.java index c2f1b971c7b..2bd2e2fae37 100644 --- a/checker/tests/optional/Marks5.java +++ b/checker/tests/optional/Marks5.java @@ -37,6 +37,7 @@ Optional clever2(Optional first, Optional se // there. Optional moreClever(Optional first, Optional second) { Optional result = + // :: error: (argument) first.map(b -> second.map(b::add).orElse(b)).map(Optional::of).orElse(second); return result; } diff --git a/checker/tests/regex/InvariantTypes.java b/checker/tests/regex/InvariantTypes.java index 1ee10103b57..0546fb75ea6 100644 --- a/checker/tests/regex/InvariantTypes.java +++ b/checker/tests/regex/InvariantTypes.java @@ -22,7 +22,7 @@ public class InvariantTypes { List ls = Arrays.asList("alice", "bob", "carol"); List<@Regex String> lrs = Arrays.asList("alice", "bob", "carol"); List lnrs = Arrays.asList("(alice", "bob", "carol"); - // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) List<@Regex String> lrserr = Arrays.asList("(alice", "bob", "carol"); void unqm(String[] sa) {} @@ -78,7 +78,7 @@ void listcalls() { lunqm(Arrays.asList("alice", "bob", "carol")); lrem(Arrays.asList("alice", "bob", "carol")); lunqm(Arrays.asList("(alice", "bob", "carol")); - // :: error: (argument.type.incompatible) + // :: error: (type.arguments.not.inferred) lrem(Arrays.asList("(alice", "bob", "carol")); } @@ -92,7 +92,7 @@ void listctrs() { new ReTests(Arrays.asList("alice", "bob", "carol"), 0); new ReTests(Arrays.asList("alice", "bob", "carol")); new ReTests(Arrays.asList("(alice", "bob", "carol"), 0); - // :: error: (argument.type.incompatible) + // :: error: (type.arguments.not.inferred) new ReTests(Arrays.asList("(alice", "bob", "carol")); } diff --git a/checker/tests/resourceleak/SneakyDrop.java b/checker/tests/resourceleak/SneakyDrop.java index 05255cdeb5e..a4e793757e7 100644 --- a/checker/tests/resourceleak/SneakyDrop.java +++ b/checker/tests/resourceleak/SneakyDrop.java @@ -13,7 +13,7 @@ public static void sneakyDrop(@Owning T value) {} public static void main(String[] args) throws Exception { Resource x = new Resource(); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) sneakyDrop(x); } @@ -37,7 +37,7 @@ public static void sneakyDrop4(@Owning T value) {} public static void main4(String[] args) throws Exception { Resource x = new Resource(); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) sneakyDrop4(x); } diff --git a/checker/tests/tainting/ClassQPTypeVarTest.java b/checker/tests/tainting/ClassQPTypeVarTest.java index 1be952d2d77..e0a75b0a496 100644 --- a/checker/tests/tainting/ClassQPTypeVarTest.java +++ b/checker/tests/tainting/ClassQPTypeVarTest.java @@ -30,7 +30,7 @@ void bug(@Untainted Buffer b, @Tainted String s) { } void use(@Untainted Buffer ub, @Tainted Buffer tb) { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) identity(ub); identity(tb); // ok } diff --git a/checker/tests/tainting/Issue1111.java b/checker/tests/tainting/Issue1111.java index 8e382454b85..7937c049e43 100644 --- a/checker/tests/tainting/Issue1111.java +++ b/checker/tests/tainting/Issue1111.java @@ -7,12 +7,16 @@ public class Issue1111 { void foo(Box box, List list) { - // :: error: (argument.type.incompatible) + // :: error: (type.arguments.not.inferred) bar(box, list); } void foo2(Box<@Untainted ? super Integer> box, List list) { - // :: error: (argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + bar(box, list); + } + + void foo3(Box<@Untainted ? super Integer> box, List<@Untainted Integer> list) { bar(box, list); } diff --git a/checker/tests/tainting/Issue3036Tainting.java b/checker/tests/tainting/Issue3036Tainting.java new file mode 100644 index 00000000000..1e56f3f0809 --- /dev/null +++ b/checker/tests/tainting/Issue3036Tainting.java @@ -0,0 +1,49 @@ +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.checkerframework.checker.tainting.qual.Tainted; + +public class Issue3036Tainting { + + public Set getDsData() { + throw new RuntimeException(); + } + + public static class MyInnerClass { + public int getKeyTag() { + return 5; + } + + public String getDigest() { + return ""; + } + } + + private void write(Stream stream) { + Function> mapper = + dsData1 -> + ImmutableMap.of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest()); + + List> dsData = + getDsData().stream() + .map( + dsData1 -> + ImmutableMap.<@Tainted String, Object>of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest())) + .collect(Collectors.toList()); + } + + public static class ImmutableMap extends HashMap { + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + throw new RuntimeException(); + } + } +} diff --git a/checker/tests/tainting/Issue6110.java b/checker/tests/tainting/Issue6110.java index 74ba3afd389..c2dda0fb061 100644 --- a/checker/tests/tainting/Issue6110.java +++ b/checker/tests/tainting/Issue6110.java @@ -15,7 +15,7 @@ static void test(Enum<@Untainted TestEnum> o) { o.compareTo(TestEnum.TWO); EnumSet<@Tainted TestEnum> s1 = EnumSet.of(TestEnum.ONE); - // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) EnumSet<@Untainted TestEnum> s2 = EnumSet.of(TestEnum.ONE); EnumSet<@Untainted TestEnum> s3 = EnumSet.of(TestEnum.TWO); } diff --git a/checker/tests/tainting/MemberReferenceInference.java b/checker/tests/tainting/MemberReferenceInference.java new file mode 100644 index 00000000000..367826da91d --- /dev/null +++ b/checker/tests/tainting/MemberReferenceInference.java @@ -0,0 +1,35 @@ +import java.math.BigDecimal; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; +import org.checkerframework.checker.tainting.qual.Tainted; +import org.checkerframework.checker.tainting.qual.Untainted; + +public class MemberReferenceInference { + void clever2( + Stream> taintedStream, + Stream> untaintedStream) { + // :: error: (type.arguments.not.inferred) + Stream<@Untainted BigDecimal> s = taintedStream.map(Optional::get); + Stream<@Untainted BigDecimal> s2 = untaintedStream.map(Optional::get); + Stream<@Tainted BigDecimal> s3 = taintedStream.map(Optional::get); + Stream<@Tainted BigDecimal> s4 = untaintedStream.map(Optional::get); + } + + interface MyClass { + String getName(); + } + + void method( + MyClass clazz, + Map, @Untainted String> annotationClassNames) { + // :: error: (type.arguments.not.inferred) + String canonicalName = annotationClassNames.computeIfAbsent(clazz, MyClass::getName); + } + + void method2( + MyClass clazz, + Map, String> annotationClassNames) { + String canonicalName = annotationClassNames.computeIfAbsent(clazz, MyClass::getName); + } +} diff --git a/checker/tests/tainting/SubtypingConstraint.java b/checker/tests/tainting/SubtypingConstraint.java new file mode 100644 index 00000000000..2a3eed2cfd0 --- /dev/null +++ b/checker/tests/tainting/SubtypingConstraint.java @@ -0,0 +1,17 @@ +import java.util.Arrays; +import java.util.List; +import org.checkerframework.checker.tainting.qual.Untainted; + +public class SubtypingConstraint { + void test() { + Object[] o = new Object[0]; + // :: error: (type.arguments.not.inferred) + asList(0, 0, "", Arrays.asList(o)); + } + + @SafeVarargs + @SuppressWarnings("varargs") + public static List asList(T... a) { + throw new RuntimeException(""); + } +} diff --git a/checker/tests/tainting/TaintingPrimitiveTarget.java b/checker/tests/tainting/TaintingPrimitiveTarget.java new file mode 100644 index 00000000000..eef0ccba062 --- /dev/null +++ b/checker/tests/tainting/TaintingPrimitiveTarget.java @@ -0,0 +1,17 @@ +import java.util.Collections; +import java.util.List; +import org.checkerframework.checker.tainting.qual.Untainted; + +public class TaintingPrimitiveTarget { + void method(List list) { + long l = Collections.min(list); + // :: error: (assignment) + // :: error: (type.arguments.not.inferred) + @Untainted long l2 = Collections.min(list); + } + + void method2(List<@Untainted Integer> list) { + long l = Collections.min(list); + @Untainted long l2 = Collections.min(list); + } +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index 7f80592de1a..8eccbb698a8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -741,7 +741,7 @@ protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMi * exceptions in {@code causes}. * * @param node the node to add - * @param causes set of exceptions that the node might throw + * @param causes the set of exceptions that the node might throw * @return the node holder */ protected NodeWithExceptionsHolder extendWithNodeWithExceptions( @@ -802,7 +802,7 @@ protected T insertNodeAfter(T node, Node pred) { * the list of extended nodes, or append to the list if {@code pred} is not present. * * @param node the node to add - * @param causes set of exceptions that the node might throw + * @param causes the set of exceptions that the node might throw * @param pred the desired predecessor of node * @return the node holder */ diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java index f95f6d83fff..ada0bc9d270 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java @@ -223,7 +223,8 @@ public static ControlFlowGraph generateMethodCFG(String file, String clas, Strin JavaCompiler javac = new JavaCompiler(context); JavaFileObject l; - try (@SuppressWarnings("mustcall:type.argument") // Context isn't annotated for the Must Call + try (@SuppressWarnings( + "mustcall:type.arguments.not.inferred") // Context isn't annotated for the Must Call // Checker. JavacFileManager fileManager = (JavacFileManager) context.get(JavaFileManager.class)) { l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next(); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java index 091228e9b9e..1b811ec4443 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java @@ -35,7 +35,7 @@ public JavaExpression convert(JavaExpression javaExpr) { */ public List<@PolyNull JavaExpression> convert(List<@PolyNull JavaExpression> list) { return CollectionsPlume.mapList( - (@PolyNull JavaExpression expression) -> { + expression -> { // Can't use a ternary operator because of: // https://github.com/typetools/checker-framework/issues/1170 if (expression == null) { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ffdf71d0eaa..8b1440a1c40 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,10 @@ Version 3.43.0 (January 2, 2024) **User-visible changes:** +Method, constructor, lambda, and method reference type inference has been +greatly improved. The `-AconservativeUninferredTypeArguments` option is +no longer necessary and has been removed. + **Implementation details:** **Closed issues:** diff --git a/docs/examples/units-extension/Expected.txt b/docs/examples/units-extension/Expected.txt index 7cd1074cddb..10c4db2195d 100644 --- a/docs/examples/units-extension/Expected.txt +++ b/docs/examples/units-extension/Expected.txt @@ -3,9 +3,9 @@ UnitsExtensionDemo.java:15: error: [assignment.type.incompatible] incompatible t ^ found : @UnknownUnits int required: @Hz int -UnitsExtensionDemo.java:68: error: [assignment.type.incompatible] incompatible types in assignment. - @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz; - ^ - found : @Frequency int +UnitsExtensionDemo.java:67: error: [assignment.type.incompatible] incompatible types in assignment. + @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz; + ^ + found : @Hz(Prefix.kilo) int required: @Hz int 2 errors diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index e3c578bf1fa..a85bbe83fec 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -2045,10 +2045,6 @@ \refclass{common/basetype}{BaseTypeVisitor}; see Section~\ref{creating-extending-visitor}. -\item \code{-AshowInferenceSteps}: print debugging information -about intermediate steps in method type argument inference -(as performed by \refclass{framework/util/typeinference}{DefaultTypeArgumentInference}). - \item \code{-AshowWpiFailedInferences}: print debugging information about failed inference steps during whole-program inference. Must be used with \code{-Ainfer}; see Section~\ref{whole-program-inference}. diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index 4d53afaceb1..75731671c18 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -654,8 +654,7 @@ 979}. \item \<-AignoreRawTypeArguments=false> Do not ignore subtype tests for type arguments that were inferred for a - raw type. Must also use \<-AconservativeUninferredTypeArguments>. See - Section~\ref{generics-raw-types}. + raw type. See Section~\ref{generics-raw-types}. \item \<-processor org.checkerframework.common.initializedfields.InitializedFieldsChecker,...> Ensure that all fields are initialized by the constructor. See \chapterpageref{initialized-fields-checker}. @@ -799,7 +798,6 @@ \item \<-Afilenames>, \<-Ashowchecks>, - \<-AshowInferenceSteps>, \<-AshowWpiFailedInferences> Progress tracing; see Section~\ref{creating-debugging-options-progress}. diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java index 49bb7679e55..8891041925a 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java @@ -96,8 +96,7 @@ protected CheckerFrameworkPerDirectoryTest( * @param checkerOptions options to pass to the compiler when running tests */ @SuppressWarnings( - "signature:argument.type.incompatible" // for non-array non-primitive class, getName(): - // @BinaryName + "signature:cast.unsafe" // for non-array non-primitive class, getName(): @BinaryName ) protected CheckerFrameworkPerDirectoryTest( List testFiles, @@ -107,7 +106,7 @@ protected CheckerFrameworkPerDirectoryTest( String... checkerOptions) { this( testFiles, - Collections.singletonList(checker.getName()), + Collections.singletonList((@BinaryName String) checker.getName()), testDir, classpathExtra, checkerOptions); diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java index 323eee4f98e..c4877ea5f1d 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java @@ -104,8 +104,7 @@ public static TestConfigurationBuilder getDefaultConfigurationBuilder( * compiler, and file manager used by Checker Framework tests */ @SuppressWarnings( - "signature:argument.type.incompatible" // for non-array non-primitive class, getName(): - // @BinaryName + "signature:cast.unsafe" // for non-array non-primitive class, getName(): @BinaryName ) public static TestConfiguration buildDefaultConfiguration( String testSourcePath, @@ -117,7 +116,7 @@ public static TestConfiguration buildDefaultConfiguration( testSourcePath, Arrays.asList(testFile), Collections.emptyList(), - Arrays.asList(processor.getName()), + Arrays.asList((@BinaryName String) processor.getName()), options, shouldEmitDebugInfo); } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java index b686b62d1fd..1b4cd2c16e6 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java @@ -129,10 +129,6 @@ public String summarize() { summaryBuilder.add(" " + missing.toString()); } } - - summaryBuilder.add( - "While type-checking " - + TestUtilities.summarizeSourceFiles(configuration.getTestSourceFiles())); return summaryBuilder.toString(); } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java index a8446ace691..4f88bf26b0a 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -293,13 +293,6 @@ public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { } boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement()); - if (type.containsUninferredTypeArguments()) { - if (!atypeFactory.ignoreUninferredTypeArguments) { - // TODO: document the logic here. - isValid = true; - } - return null; - } if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) { // Ensure that type use is a subtype of the element type diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 40f50113837..67a61d8543d 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -31,6 +31,7 @@ import com.sun.source.tree.ReturnTree; import com.sun.source.tree.ThrowTree; import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.UnaryTree; @@ -114,7 +115,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.AnnotatedTypeParameterBounds; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; @@ -131,6 +131,7 @@ import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; import org.checkerframework.framework.util.JavaParserUtil; import org.checkerframework.framework.util.StringToJavaExpression; +import org.checkerframework.framework.util.typeinference8.InferenceResult; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; @@ -275,9 +276,6 @@ public class BaseTypeVisitor typeargs = mType.typeArgs; - if (!atypeFactory.ignoreUninferredTypeArguments) { - for (AnnotatedTypeMirror typearg : typeargs) { - if (typearg.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) typearg).isUninferredTypeArgument()) { - checker.reportError( - tree, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName()); - break; // only issue error once per method - } - } - } - List paramBounds = CollectionsPlume.mapList( AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables()); ExecutableElement method = invokedMethod.getElement(); CharSequence methodName = ElementUtils.getSimpleDescription(method); - try { - checkTypeArguments( - tree, - paramBounds, - typeargs, - tree.getTypeArguments(), - methodName, - invokedMethod.getTypeVariables()); - List params = invokedMethod.getParameterTypes(); - checkArguments(params, tree.getArguments(), methodName, method.getParameters()); - checkVarargs(invokedMethod, tree); - - if (ElementUtils.isMethod( - invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) { - typeCheckVectorCopyIntoArgument(tree, params); - } + checkTypeArguments( + tree, + paramBounds, + typeargs, + tree.getTypeArguments(), + methodName, + invokedMethod.getTypeVariables()); + List params = + AnnotatedTypes.adaptParameters(atypeFactory, invokedMethod, tree.getArguments()); + checkArguments(params, tree.getArguments(), methodName, method.getParameters()); + checkVarargs(invokedMethod, tree); - ExecutableElement invokedMethodElement = invokedMethod.getElement(); - if (!ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperConstructorCall(tree)) { - checkMethodInvocability(invokedMethod, tree); - } + if (ElementUtils.isMethod( + invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) { + typeCheckVectorCopyIntoArgument(tree, params); + } - // check precondition annotations - checkPreconditions( - tree, atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement)); + ExecutableElement invokedMethodElement = invokedMethod.getElement(); + if (!ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperConstructorCall(tree)) { + checkMethodInvocability(invokedMethod, tree); + } - if (TreeUtils.isSuperConstructorCall(tree)) { - checkSuperConstructorCall(tree); - } else if (TreeUtils.isThisConstructorCall(tree)) { - checkThisConstructorCall(tree); - } - } catch (RuntimeException t) { - // Sometimes the type arguments are inferred incorrectly, which causes crashes. Once - // #979 is fixed this should be removed and crashes should be reported normally. - if (tree.getTypeArguments().size() == typeargs.size()) { - // They type arguments were explicitly written. - throw t; - } - if (!atypeFactory.ignoreUninferredTypeArguments) { - checker.reportError( - tree, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName()); - } // else ignore the crash. + // check precondition annotations + checkPreconditions( + tree, atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement)); + + if (TreeUtils.isSuperConstructorCall(tree)) { + checkSuperConstructorCall(tree); + } else if (TreeUtils.isThisConstructorCall(tree)) { + checkThisConstructorCall(tree); } // Do not call super, as that would observe the arguments without @@ -2014,6 +1986,29 @@ public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { return null; // super.visitMethodInvocation(tree, p); } + /** + * Reports a "type.arguments.not.inferred" error if type argument inference fails and returns + * false if inference fails. + * + * @param tree a tree that requires type argument inference + * @param methodType the type of the method before type argument substitution + * @return whether type argument inference succeeds + */ + private boolean checkTypeArgumentInference( + ExpressionTree tree, AnnotatedExecutableType methodType) { + InferenceResult args = + atypeFactory.getTypeArgumentInference().inferTypeArgs(atypeFactory, tree, methodType); + if (args != null && !args.inferenceFailed()) { + return true; + } + checker.reportError( + tree, + "type.arguments.not.inferred", + ElementUtils.getSimpleDescription(methodType.getElement()), + args == null ? "" : args.getErrorMsg()); + return false; + } + /** * Checks that the following rule is satisfied: The type on a constructor declaration must be a * supertype of the return type of "this()" invocation within that constructor. @@ -2251,6 +2246,15 @@ public Void visitNewClass(NewClassTree tree, Void p) { return super.visitNewClass(tree, p); } + ParameterizedExecutableType preInference = + atypeFactory.constructorFromUseWithoutTypeArgInference(tree); + if (!preInference.executableType.getElement().getTypeParameters().isEmpty() + || TreeUtils.isDiamondTree(tree)) { + if (!checkTypeArgumentInference(tree, preInference.executableType)) { + return null; + } + } + ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(tree); AnnotatedExecutableType constructorType = fromUse.executableType; List typeargs = fromUse.typeArgs; @@ -2453,6 +2457,14 @@ public Void visitAnnotation(AnnotationTree tree, Void p) { */ @Override public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + if (TreeUtils.isPolyExpression(tree)) { + // From the JLS: + // A poly reference conditional expression is compatible with a target type T if its second + // and third operand expressions are compatible with T. In the Checker Framework this check + // happens in #commonAssignmentCheck. + return super.visitConditionalExpression(tree, p); + } + AnnotatedTypeMirror cond = atypeFactory.getAnnotatedType(tree); this.commonAssignmentCheck(cond, tree.getTrueExpression(), "conditional.type.incompatible"); this.commonAssignmentCheck(cond, tree.getFalseExpression(), "conditional.type.incompatible"); @@ -3080,6 +3092,15 @@ protected boolean commonAssignmentCheck( ExpressionTree valueExpTree, @CompilerMessageKey String errorKey, Object... extraArgs) { + if (valueExpTree.getKind() == Kind.CONDITIONAL_EXPRESSION) { + ConditionalExpressionTree condExprTree = (ConditionalExpressionTree) valueExpTree; + boolean trueResult = + commonAssignmentCheck(varTree, condExprTree.getTrueExpression(), "assignment"); + boolean falseResult = + commonAssignmentCheck(varTree, condExprTree.getFalseExpression(), "assignment"); + return trueResult && falseResult; + } + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(varTree); assert varType != null : "no variable found for tree: " + varTree; @@ -3549,7 +3570,8 @@ protected void checkTypeArguments( AnnotatedTypeParameterBounds bounds = paramBounds.get(i); AnnotatedTypeMirror typeArg = typeargs.get(i); - if (isIgnoredUninferredWildcard(bounds.getUpperBound())) { + if (atypeFactory.ignoreRawTypeArguments + && AnnotatedTypes.isTypeArgOfRawType(bounds.getUpperBound())) { continue; } @@ -3605,12 +3627,6 @@ private void checkHasQualifierParameterAsTypeArgument( } } - private boolean isIgnoredUninferredWildcard(AnnotatedTypeMirror type) { - return atypeFactory.ignoreUninferredTypeArguments - && type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument(); - } - /** * Indicates whether to skip subtype checks on the receiver when checking method invocability. A * visitor may, for example, allow a method to be invoked even if the receivers are siblings in a @@ -3766,7 +3782,7 @@ protected void checkEnclosingExpr(NewClassTree node, AnnotatedExecutableType con * like var args. * * @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree) - * @param requiredArgs the required types. This may differ from the formal parameter types, + * @param requiredTypes the required types. This may differ from the formal parameter types, * because it replaces a varargs parameter by multiple parameters with the vararg's element * type. * @param passedArgs the expressions passed to the corresponding types @@ -3774,14 +3790,14 @@ protected void checkEnclosingExpr(NewClassTree node, AnnotatedExecutableType con * @param paramNames the names of the callee's formal parameters */ protected void checkArguments( - List requiredArgs, + List requiredTypes, List passedArgs, CharSequence executableName, List paramNames) { - int size = requiredArgs.size(); + int size = requiredTypes.size(); assert size == passedArgs.size() - : "mismatch between required args (" - + requiredArgs + : "size mismatch between required args (" + + requiredTypes + ") and passed args (" + passedArgs + ")"; @@ -3793,21 +3809,23 @@ protected void checkArguments( size, passedArgs.size(), paramNames.size(), - listToString(requiredArgs), + listToString(requiredTypes), listToString(passedArgs), executableName, listToString(paramNames)); for (int i = 0; i < size; ++i) { + AnnotatedTypeMirror requiredType = requiredTypes.get(i); + ExpressionTree passedArg = passedArgs.get(i); + Object paramName = paramNames.get(Math.min(i, maxParamNamesIndex)); commonAssignmentCheck( - requiredArgs.get(i), - passedArgs.get(i), + requiredType, + passedArg, "argument.type.incompatible", // TODO: for expanded varargs parameters, maybe adjust the name - paramNames.get(Math.min(i, maxParamNamesIndex)), + paramName, executableName); - // Also descend into the argument within the correct assignment context. - scan(passedArgs.get(i), null); + scan(passedArg, null); } } @@ -3974,43 +3992,35 @@ protected boolean checkMethodReferenceAsOverride( // enclosing method. // That is handled separately in method receiver check. - // The type of the expression or type use, ::method or ::method. - ExpressionTree qualifierExpression = memberReferenceTree.getQualifierExpression(); + // The tree before :: is an expression or type use. + ExpressionTree preColonTree = memberReferenceTree.getQualifierExpression(); MemberReferenceKind memRefKind = MemberReferenceKind.getMemberReferenceKind(memberReferenceTree); AnnotatedTypeMirror enclosingType; - if (memberReferenceTree.getMode() == ReferenceMode.NEW + if (TreeUtils.isLikeDiamondMemberReference(memberReferenceTree)) { + TypeElement typeElt = TypesUtils.getTypeElement(TreeUtils.typeOf(preColonTree)); + enclosingType = atypeFactory.getAnnotatedType(typeElt); + } else if (memberReferenceTree.getMode() == ReferenceMode.NEW || memRefKind == MemberReferenceKind.UNBOUND || memRefKind == MemberReferenceKind.STATIC) { - // The "qualifier expression" is a type tree. - enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(qualifierExpression); + // The tree before :: is a type tree. + enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(preColonTree); } else { - // The "qualifier expression" is an expression. - enclosingType = atypeFactory.getAnnotatedType(qualifierExpression); + // The tree before :: is an expression. + enclosingType = atypeFactory.getAnnotatedType(preColonTree); } // ========= Overriding Executable ========= // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference ExecutableElement compileTimeDeclaration = TreeUtils.elementFromUse(memberReferenceTree); - if (enclosingType.getKind() == TypeKind.DECLARED - && ((AnnotatedDeclaredType) enclosingType).isUnderlyingTypeRaw()) { - if (memRefKind == MemberReferenceKind.UNBOUND) { - // The method reference is of the form: Type # instMethod and Type is a raw type. - // If the first parameter of the function type, p1, is a subtype of type, then type - // should be p1. This has the effect of "inferring" the class type parameter. - AnnotatedTypeMirror p1 = functionType.getParameterTypes().get(0); - TypeMirror asSuper = - TypesUtils.asSuper( - p1.getUnderlyingType(), - enclosingType.getUnderlyingType(), - atypeFactory.getProcessingEnv()); - if (asSuper != null) { - enclosingType = AnnotatedTypes.asSuper(atypeFactory, p1, enclosingType); - } + + ParameterizedExecutableType preInference = + atypeFactory.methodFromUseWithoutTypeArgInference( + memberReferenceTree, compileTimeDeclaration, enclosingType); + if (TreeUtils.needsTypeArgInference(memberReferenceTree)) { + if (!checkTypeArgumentInference(memberReferenceTree, preInference.executableType)) { + return true; } - // else method reference is something like ArrayList::new - // TODO: Use diamond, <>, inference to infer the class type arguments. - // For now this case is skipped below in checkMethodReferenceInference. } // The type of the compileTimeDeclaration if it were invoked with a receiver expression @@ -4019,12 +4029,6 @@ protected boolean checkMethodReferenceAsOverride( atypeFactory.methodFromUse(memberReferenceTree, compileTimeDeclaration, enclosingType) .executableType; - if (checkMethodReferenceInference(memberReferenceTree, invocationType, enclosingType)) { - // Type argument inference is required, skip check. - // #checkMethodReferenceInference issued a warning. - return true; - } - // This needs to be done before invocationType.getReturnType() and // functionType.getReturnType() if (invocationType.getTypeVariables().isEmpty() && !functionType.getTypeVariables().isEmpty()) { @@ -4069,48 +4073,12 @@ protected boolean checkMethodReferenceAsOverride( functionTypeReturnType); return overrideChecker.checkOverride(); } else { - // If the functionalInterface is not a declared type, it must be an uninferred wildcard. - // In that case, only return false if uninferred type arguments should not be ignored. - return !atypeFactory.ignoreUninferredTypeArguments; + // If the functionalInterface is not a declared type, it must be from a wildcard from a raw + // type. In that case, only return false if raw types should not be ignored. + return !atypeFactory.ignoreRawTypeArguments; } } - /** Check if method reference type argument inference is required. Issue an error if it is. */ - private boolean checkMethodReferenceInference( - MemberReferenceTree memberReferenceTree, - AnnotatedExecutableType invocationType, - AnnotatedTypeMirror type) { - // TODO: Issue #802 - // TODO: https://github.com/typetools/checker-framework/issues/802 - // TODO: Method type argument inference - // TODO: Enable checks for method reference with inferred type arguments. - // For now, error on mismatch of class or method type arguments. - boolean requiresInference = false; - // If the function to which the member reference refers is generic, but the member - // reference does not provide method type arguments, then Java 8 inference is required. - // Issue 979. - if (!invocationType.getTypeVariables().isEmpty() - && (memberReferenceTree.getTypeArguments() == null - || memberReferenceTree.getTypeArguments().isEmpty())) { - // Method type args - requiresInference = true; - } else if (memberReferenceTree.getMode() == ReferenceMode.NEW - || MemberReferenceKind.getMemberReferenceKind(memberReferenceTree).isUnbound()) { - if (type.getKind() == TypeKind.DECLARED - && ((AnnotatedDeclaredType) type).isUnderlyingTypeRaw()) { - // Class type args - requiresInference = true; - } - } - if (requiresInference) { - if (conservativeUninferredTypeArguments) { - checker.reportWarning(memberReferenceTree, "methodref.inference.unimplemented"); - } - return true; - } - return false; - } - /** * Class to perform method override and method reference checks. * diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 87fcc85c9ac..3d91b4bfe4d 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -56,7 +56,7 @@ super.invocation.invalid=Constructor of type %s cannot call %s of type %s. this.invocation.invalid=Constructor of type %s cannot call %s of type %s. method.invocation.invalid=call to %s not allowed on the given receiver.%nfound : %s%nrequired: %s constructor.invocation.invalid=creation of %s not allowed with given receiver.%nfound : %s%nrequired: %s -type.arguments.not.inferred=Could not infer type arguments for %s. +type.arguments.not.inferred=Could not infer type arguments for %s%nunsatisfiable constraint: %s type.argument.invalid.hasqualparam=Types with qualifier parameters are not allowed as type arguments.%nfound qualifier parameter of %s hierarchy. declaration.inconsistent.with.extends.clause=Class with annotation %s cannot extend %s declaration.inconsistent.with.implements.clause=Class with annotation %s cannot implement %s @@ -103,9 +103,6 @@ contracts.postcondition.methodref.invalid=Subclass postcondition is weaker for ' contracts.conditional.postcondition.true.methodref.invalid=Subclass postcondition with result=true is weaker for '%s' in %s.%n In superclass %s: %s%n In subclass %s: %s contracts.conditional.postcondition.false.methodref.invalid=Subclass postcondition with result=false is weaker for '%s' in %s.%n In superclass %s: %s%n In subclass %s: %s -lambda.unimplemented=This version of the Checker Framework does not type-check lambda expressions. -methodref.inference.unimplemented=This version of the Checker Framework does not type-check method references with implicit type arguments. - invalid.polymorphic.qualifier=Cannot use polymorphic qualifier %s %s invalid.polymorphic.qualifier.use=Cannot use polymorphic qualifier %s on a field unless the class is annotated with @HasQualifierParameter missing.has.qual.param=Missing @HasQualifierParameter for hierarchy %s.%nClass extends or implements a type that has a qualifier parameter. @@ -126,3 +123,5 @@ instanceof.pattern.unsafe=instanceof pattern binding '%s' to '%s' cannot be stat # Third arg is optional explanation anno.on.irrelevant=Annotation %s is not applicable to %s%s redundant.anno=Annotation %s is redundant: it is the same as the default at this location + +type.inference.failed=type inference crashed: %s diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index 05be499f21e..0e144af7151 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -351,7 +351,7 @@ protected TypeHierarchy createTypeHierarchy() { return new DefaultTypeHierarchy( checker, getQualifierHierarchy(), - checker.getBooleanOption("ignoreRawTypeArguments", true), + ignoreRawTypeArguments, checker.hasOption("invariantArrays")) { @Override public StructuralEqualityComparer createEqualityComparer() { @@ -430,10 +430,14 @@ protected Set> getFieldInvariantDeclarationAnnotatio * number of possible values of the enum. */ @Override - public ParameterizedExecutableType methodFromUse( - ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - - ParameterizedExecutableType superPair = super.methodFromUse(tree, methodElt, receiverType); + protected ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + + ParameterizedExecutableType superPair = + super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); if (ElementUtils.matchesElement(methodElt, "values") && methodElt.getEnclosingElement().getKind() == ElementKind.ENUM && ElementUtils.isStatic(methodElt)) { @@ -785,7 +789,7 @@ private AnnotationMirror convertSpecialIntRangeToStandardIntRange( } else if (TypesUtils.getClassFromType(resultType) == char[].class) { List stringVals = CollectionsPlume.mapList( - (Object o) -> { + o -> { if (o instanceof char[]) { return new String((char[]) o); } else { diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index 82b9bc1824a..191f0054c12 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -766,12 +766,10 @@ private void addClass(ClassTree tree, @Nullable TypeDeclaration javaParserNod for (TypeElement supertypeElement : ElementUtils.getSuperTypes(classElt, elements)) { String supertypeName = ElementUtils.getBinaryName(supertypeElement); Set<@BinaryName String> supertypeSet = - supertypesMap.computeIfAbsent( - className, k -> new TreeSet<@BinaryName String>()); + supertypesMap.computeIfAbsent(className, k -> new TreeSet<>()); supertypeSet.add(supertypeName); Set<@BinaryName String> subtypeSet = - subtypesMap.computeIfAbsent( - supertypeName, k -> new TreeSet<@BinaryName String>()); + subtypesMap.computeIfAbsent(supertypeName, k -> new TreeSet<>()); subtypeSet.add(className); } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java index e7d6451ffd6..531cf6fa8c7 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java @@ -913,10 +913,11 @@ public Void visitLiteral(LiteralTree javacTree, Node javaParserNode) { public Void visitMemberReference(MemberReferenceTree javacTree, Node javaParserNode) { MethodReferenceExpr node = castNode(MethodReferenceExpr.class, javaParserNode, javacTree); processMemberReference(javacTree, node); + Tree preColonTree = javacTree.getQualifierExpression(); if (node.getScope().isTypeExpr()) { - javacTree.getQualifierExpression().accept(this, node.getScope().asTypeExpr().getType()); + preColonTree.accept(this, node.getScope().asTypeExpr().getType()); } else { - javacTree.getQualifierExpression().accept(this, node.getScope()); + preColonTree.accept(this, node.getScope()); } assert (javacTree.getTypeArguments() != null) == node.getTypeArguments().isPresent(); diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java index ad5c1bf499a..e24fb4b5e20 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java @@ -88,7 +88,7 @@ assert validateSet(this.getAnnotations(), this.getUnderlyingType(), analysis.get * Returns true if the set has an annotation from every hierarchy (or if it doesn't need to); * returns false if the set is missing an annotation from some hierarchy. * - * @param annos set of annotations + * @param annos a set of annotations * @param typeMirror where the annotations are written * @param atypeFactory the type factory * @return true if no annotations are missing diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 8712ff5e2d3..c73ff578399 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -190,10 +190,6 @@ // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics "concurrentSemantics", - // Whether to use a conservative value for type arguments that could not be inferred. - // See Issue 979. - "conservativeUninferredTypeArguments", - // Issues a "redundant.anno" warning if the annotation explicitly written on the type is // the same as the default annotation for this type and location. "warnRedundantAnnotations", @@ -382,10 +378,6 @@ // org.checkerframework.common.basetype.BaseTypeVisitor "showchecks", - // Output information about intermediate steps in method type argument inference - // org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference - "showInferenceSteps", - // Output a stack trace when reporting errors or warnings // org.checkerframework.common.basetype.SourceChecker.printStackTrace() "dumpOnErrors", diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java index 243dfdebbb4..0cae4b8fac4 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java @@ -1657,6 +1657,8 @@ private void annotate( atypeFactory.replaceAnnotations(typePar.getLowerBound(), typeVarUse.getLowerBound()); } } + // Add back the primary annotations. + annotate(atype, primaryAnnotations, astNode); break; default: // No additional annotations to add. @@ -1794,7 +1796,7 @@ private void annotate( * {@code elt} is a field declaration, the type annotation will be ignored. * * @param elt the element to be annotated - * @param annotations set of annotations that may be applicable to elt + * @param annotations the set of annotations that may be applicable to elt * @param astNode where to report errors */ private void recordDeclAnnotation( diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java index 83bfa641173..57ef0e8a9d3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java @@ -308,8 +308,8 @@ public AnnotatedTypeMirror visitWildcard( AnnotatedWildcardType copy = makeOrReturnCopy(original, originalToCopy); - if (original.isUninferredTypeArgument()) { - copy.setUninferredTypeArgument(); + if (original.isTypeArgOfRawType()) { + copy.setTypeArgOfRawType(); } if (original.getExtendsBoundField() != null) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 8967e398d39..80fbeb1b686 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -67,7 +67,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.checkerframework.checker.initialization.qual.UnderInitialization; @@ -112,9 +111,8 @@ import org.checkerframework.framework.util.DefaultAnnotationFormatter; import org.checkerframework.framework.util.FieldInvariants; import org.checkerframework.framework.util.TreePathCacher; -import org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; -import org.checkerframework.framework.util.typeinference.TypeArgumentInference; +import org.checkerframework.framework.util.typeinference8.DefaultTypeArgumentInference; +import org.checkerframework.framework.util.typeinference8.TypeArgumentInference; import org.checkerframework.framework.util.visualize.LspTypeInformationPresenter; import org.checkerframework.framework.util.visualize.TypeInformationPresenter; import org.checkerframework.javacutil.AnnotationBuilder; @@ -381,7 +379,7 @@ public class AnnotatedTypeFactory implements AnnotationProvider { * * @param type annotated type mirror */ - /*package-private*/ void initializeAtm(AnnotatedTypeMirror type) { + public void initializeAtm(AnnotatedTypeMirror type) { atmInitializer.visit(type); } @@ -529,10 +527,11 @@ void checkRep(String aliasName) { /** Mapping from a Tree to its TreePath. Shared between all instances. */ private final TreePathCacher treePathCache; - /** - * Whether to ignore uninferred type arguments. This is a temporary flag to work around Issue 979. - */ - public final boolean ignoreUninferredTypeArguments; + /** Mapping from CFG-generated trees to their enclosing elements. */ + protected final Map artificialTreeToEnclosingElementMap; + + /** Whether to ignore type arguments from raw types. */ + public final boolean ignoreRawTypeArguments; /** The Object.getClass method. */ protected final ExecutableElement objectGetClass; @@ -693,7 +692,7 @@ public AnnotatedTypeFactory(BaseTypeChecker checker) { } */ - ignoreUninferredTypeArguments = !checker.hasOption("conservativeUninferredTypeArguments"); + ignoreRawTypeArguments = checker.getBooleanOption("ignoreRawTypeArguments", true); objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv); @@ -1135,7 +1134,7 @@ protected TypeHierarchy createTypeHierarchy() { return new DefaultTypeHierarchy( checker, getQualifierHierarchy(), - checker.getBooleanOption("ignoreRawTypeArguments", true), + ignoreRawTypeArguments, checker.hasOption("invariantArrays")); } @@ -1163,10 +1162,12 @@ public TypeVariableSubstitutor getTypeVarSubstitutor() { } /** - * TypeArgumentInference infers the method type arguments when they are not explicitly written. + * Creates the object that infers type arguments. + * + * @return the object that infers type arguments */ protected TypeArgumentInference createTypeArgumentInference() { - return new DefaultTypeArgumentInference(this); + return new DefaultTypeArgumentInference(); } public TypeArgumentInference getTypeArgumentInference() { @@ -1475,6 +1476,12 @@ public AnnotatedTypeMirror getAnnotatedType(Tree tree) { } addComputedTypeAnnotations(tree, type); + if (tree.getKind() == Kind.TYPE_CAST) { + type = applyCaptureConversion(type); + } + logGat( + "getAnnotatedType(%s): after addComputedTypeAnnotations, type=%s%n", + TreeUtils.toStringTruncated(tree, 60), type); if (shouldCache && (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD)) { // Don't cache VARIABLE @@ -1895,8 +1902,8 @@ protected void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) /** * Adds default annotations to {@code type}. This method should only be used in places where the - * correct annotations cannot be computed because of uninferred type arguments. (See {@link - * AnnotatedWildcardType#isUninferredTypeArgument()}.) + * correct annotations cannot be computed because of type argument of raw types. (See {@link + * AnnotatedWildcardType#isTypeArgOfRawType()}.) * * @param type annotated type to which default annotations are added */ @@ -2157,7 +2164,7 @@ public List typeVariablesFromUse( /** * Creates and returns an AnnotatedNullType qualified with {@code annotations}. * - * @param annotations set of AnnotationMirrors to qualify the returned type with + * @param annotations the set of AnnotationMirrors to qualify the returned type with * @return AnnotatedNullType qualified with {@code annotations} */ public AnnotatedNullType getAnnotatedNullType(Set annotations) { @@ -2454,9 +2461,35 @@ public String toString() { * expression. * * @param tree the method invocation tree - * @return the method type being invoked with tree and the (inferred) type arguments + * @return the type of the invoked method and any (explict or inferred) type arguments + */ + public final ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { + return methodFromUse(tree, true); + } + + /** + * Returns the same as {@link #methodFromUse(MethodInvocationTree)}, but without inferred type + * arguments. + * + * @param tree a method invocation tree + * @return the type of the invoked method and any explicit type arguments + */ + public ParameterizedExecutableType methodFromUseWithoutTypeArgInference( + MethodInvocationTree tree) { + return methodFromUse(tree, false); + } + + /** + * The implementation of {@link #methodFromUse(MethodInvocationTree)} and {@link + * #methodFromUseWithoutTypeArgInference(MethodInvocationTree)}. + * + * @param tree a method invocation tree + * @param inferTypeArgs whether type arguments should be inferred + * @return the type of the invoked method, any explicit type arguments, and if {@code + * inferTypeArgs} is true, any inferred type arguments */ - public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { + protected ParameterizedExecutableType methodFromUse( + MethodInvocationTree tree, boolean inferTypeArgs) { ExecutableElement methodElt = TreeUtils.elementFromUse(tree); AnnotatedTypeMirror receiverType = getReceiverType(tree); if (receiverType == null @@ -2469,22 +2502,22 @@ public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { receiverType = applyCaptureConversion(receiverType); } - ParameterizedExecutableType result = methodFromUse(tree, methodElt, receiverType); + ParameterizedExecutableType result = + methodFromUse(tree, methodElt, receiverType, inferTypeArgs); if (checker.shouldResolveReflection() && reflectionResolver.isReflectiveMethodInvocation(tree)) { result = reflectionResolver.resolveReflectiveCall(this, tree, result); } AnnotatedExecutableType method = result.executableType; - if (method.getReturnType().getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) method.getReturnType()).isUninferredTypeArgument()) { + if (AnnotatedTypes.isTypeArgOfRawType(method.getReturnType())) { // Get the correct Java type from the tree and use it as the upper bound of the // wildcard. TypeMirror tm = TreeUtils.typeOf(tree); AnnotatedTypeMirror t = toAnnotatedType(tm, false); AnnotatedWildcardType wildcard = (AnnotatedWildcardType) method.getReturnType(); - if (ignoreUninferredTypeArguments) { + if (ignoreRawTypeArguments) { // Remove the annotations so that default annotations are used instead. // (See call to addDefaultAnnotations below.) t.clearAnnotations(); @@ -2513,17 +2546,50 @@ public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { * @param tree either a MethodInvocationTree or a MemberReferenceTree * @param methodElt the element of the referenced method * @param receiverType the type of the receiver - * @return the method type being invoked with tree and the (inferred) type arguments + * @return the type of the method being invoked with tree and the (inferred) type arguments * @see #methodFromUse(MethodInvocationTree) */ - public ParameterizedExecutableType methodFromUse( + public final ParameterizedExecutableType methodFromUse( ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { + return methodFromUse(tree, methodElt, receiverType, true); + } + + /** + * Returns the same as {@link #methodFromUse(ExpressionTree, ExecutableElement, + * AnnotatedTypeMirror)}, but without inferred type arguments. + * + * @param tree either a MethodInvocationTree or a MemberReferenceTree + * @param methodElt the element of the referenced method + * @param receiverType the type of the receiver + * @return the type of the method being invoked with tree without inferring type arguments + */ + public final ParameterizedExecutableType methodFromUseWithoutTypeArgInference( + ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { + return methodFromUse(tree, methodElt, receiverType, false); + } + + /** + * The implementation of {@link #methodFromUse(ExpressionTree, ExecutableElement, + * AnnotatedTypeMirror)} and {@link #methodFromUseWithoutTypeArgInference(ExpressionTree, + * ExecutableElement, AnnotatedTypeMirror)}. + * + * @param tree either a MethodInvocationTree or a MemberReferenceTree + * @param methodElt the element of the referenced method + * @param receiverType the type of the receiver + * @param inferTypeArgs whether type arguments should be inferred + * @return the type of the invoked method + */ + protected ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { AnnotatedExecutableType memberTypeWithoutOverrides = getAnnotatedType(methodElt); // get unsubstituted type AnnotatedExecutableType memberTypeWithOverrides = applyFakeOverrides(receiverType, methodElt, memberTypeWithoutOverrides); memberTypeWithOverrides = applyRecordTypesToAccessors(methodElt, memberTypeWithOverrides); - methodFromUsePreSubstitution(tree, memberTypeWithOverrides); + methodFromUsePreSubstitution(tree, memberTypeWithOverrides, inferTypeArgs); // Perform viewpoint adaption before type argument substitution. if (viewpointAdapter != null) { @@ -2532,24 +2598,23 @@ public ParameterizedExecutableType methodFromUse( AnnotatedExecutableType methodType = AnnotatedTypes.asMemberOf(types, this, receiverType, methodElt, memberTypeWithOverrides); - List typeargs = new ArrayList<>(methodType.getTypeVariables().size()); - - Map typeParamToTypeArg = - AnnotatedTypes.findTypeArguments(processingEnv, this, tree, methodElt, methodType); + List typeargs = new ArrayList<>(methodElt.getTypeParameters().size()); + IPair, Boolean> pair = + AnnotatedTypes.findTypeArguments(this, tree, methodElt, methodType, inferTypeArgs); + Map typeParamToTypeArg = pair.first; if (!typeParamToTypeArg.isEmpty()) { - typeParamToTypeArg = - captureMethodTypeArgs(typeParamToTypeArg, memberTypeWithOverrides.getTypeVariables()); for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { if (typeParamToTypeArg.get(tv.getUnderlyingType()) == null) { - throw new BugInCF( - "AnnotatedTypeFactory.methodFromUse: mismatch between" - + " declared method type variables and the inferred method type arguments." - + " Method type variables: " - + methodType.getTypeVariables() - + "; " - + "Inferred method type arguments: " - + typeParamToTypeArg); + // throw new BugInCF( + // "AnnotatedTypeFactory.methodFromUse:mismatch between" + // + " declared method type variables and the inferred method type + // arguments." + // + " Method type variables: " + // + methodType.getTypeVariables() + // + "; " + // + "Inferred method type arguments: " + // + typeParamToTypeArg); } typeargs.add(typeParamToTypeArg.get(tv.getUnderlyingType())); } @@ -2557,6 +2622,10 @@ public ParameterizedExecutableType methodFromUse( (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, methodType); } + if (pair.second) { + methodType.setReturnType(methodType.getReturnType().getErased()); + } + if (tree.getKind() == Tree.Kind.METHOD_INVOCATION && TreeUtils.isMethodInvocation(tree, objectGetClass, processingEnv)) { adaptGetClassReturnTypeToReceiver(methodType, receiverType, tree); @@ -2566,68 +2635,6 @@ public ParameterizedExecutableType methodFromUse( return new ParameterizedExecutableType(methodType, typeargs); } - /** - * Apply capture conversion to the type arguments of a method invocation. - * - * @param typeVarToAnnotatedTypeArg mapping from type variable in the method declaration to the - * corresponding (annotated) type argument at the method invocation - * @param declTypeVar list of the (annotated) type variable declarations in the method - * @return a mapping from type variable in the method declaration to its captured type argument. - * Its keys are the same as in {@code typeVarToAnnotatedTypeArg}, and the values are their - * captures (for a non-wildcard, capture conversion is the identity). - */ - // TODO: This should happen as part of Java 8 inference and this method should be removed when - // #979 is fixed. - private Map captureMethodTypeArgs( - Map typeVarToAnnotatedTypeArg, - List declTypeVar) { - Map typeParameter = new HashMap<>(); - for (AnnotatedTypeVariable t : declTypeVar) { - typeParameter.put(t.getUnderlyingType(), t); - } - // `newTypeVarToAnnotatedTypeArg` is the result of this method. - Map newTypeVarToAnnotatedTypeArg = new HashMap<>(); - Map capturedTypeVarToAnnotatedTypeVar = new HashMap<>(); - - // The first loop replaces each wildcard by a fresh type variable. - for (Map.Entry entry : - typeVarToAnnotatedTypeArg.entrySet()) { - TypeVariable typeVariable = entry.getKey(); - AnnotatedTypeMirror originalTypeArg = entry.getValue(); - if (originalTypeArg.containsUninferredTypeArguments()) { - // Don't capture uninferred type arguments; return the argument. - return typeVarToAnnotatedTypeArg; - } - if (originalTypeArg.getKind() == TypeKind.WILDCARD) { - TypeMirror cap = - TypesUtils.freshTypeVariable(originalTypeArg.getUnderlyingType(), processingEnv); - AnnotatedTypeMirror capturedArg = AnnotatedTypeMirror.createType(cap, this, false); - newTypeVarToAnnotatedTypeArg.put(typeVariable, capturedArg); - capturedTypeVarToAnnotatedTypeVar.put( - (TypeVariable) cap, (AnnotatedTypeVariable) capturedArg); - } else { - newTypeVarToAnnotatedTypeArg.put(typeVariable, originalTypeArg); - } - } - - // The second loop captures: it side-effects the new type variables. - List order = TypesUtils.order(typeVarToAnnotatedTypeArg.keySet(), types); - for (TypeVariable typeVariable : order) { - AnnotatedTypeMirror originalTypeArg = typeVarToAnnotatedTypeArg.get(typeVariable); - AnnotatedTypeMirror newTypeArg = newTypeVarToAnnotatedTypeArg.get(typeVariable); - if (TypesUtils.isCapturedTypeVariable(newTypeArg.getUnderlyingType()) - && originalTypeArg.getKind() == TypeKind.WILDCARD) { - annotateCapturedTypeVar( - newTypeVarToAnnotatedTypeArg, - capturedTypeVarToAnnotatedTypeVar, - (AnnotatedWildcardType) originalTypeArg, - typeParameter.get(typeVariable), - (AnnotatedTypeVariable) newTypeArg); - } - } - return newTypeVarToAnnotatedTypeArg; - } - /** * Given a member and its type, returns the type with fake overrides applied to it. * @@ -2678,8 +2685,10 @@ private AnnotatedExecutableType applyRecordTypesToAccessors( * * @param tree either a method invocation or a member reference tree * @param type declared method type before type variable substitution + * @param resolvePolyQuals whether to resolve polymorphic qualifiers */ - protected void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { + protected void methodFromUsePreSubstitution( + ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { assert tree instanceof MethodInvocationTree || tree instanceof MemberReferenceTree; } @@ -2834,6 +2843,31 @@ protected AnnotatedTypeMirror getIterableElementType( * (inferred) type arguments */ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + return constructorFromUse(tree, true); + } + + /** + * The same as {@link #constructorFromUse(NewClassTree)}, but no type arguments are inferred. + * + * @param tree the constructor invocation tree + * @return the annotated type of the invoked constructor (as an executable type) and the explicit + * type arguments + */ + public ParameterizedExecutableType constructorFromUseWithoutTypeArgInference(NewClassTree tree) { + return constructorFromUse(tree, false); + } + + /** + * The implementation of {@link #constructorFromUse(NewClassTree)} and {@link + * #constructorFromUseWithoutTypeArgInference(NewClassTree)}. + * + * @param tree the constructor invocation tree + * @param inferTypeArgs whether the type arguments should be inferred + * @return the annotated type of the invoked constructor (as an executable type) and the type + * arguments + */ + protected ParameterizedExecutableType constructorFromUse( + NewClassTree tree, boolean inferTypeArgs) { // Get the annotations written on the new class tree. AnnotatedDeclaredType type = (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false); @@ -2860,7 +2894,7 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { ExecutableElement ctor = TreeUtils.elementFromUse(tree); AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type - constructorFromUsePreSubstitution(tree, con); + constructorFromUsePreSubstitution(tree, con, inferTypeArgs); if (tree.getClassBody() != null) { // Because the anonymous constructor can't have explicit annotations on its parameters, @@ -2872,7 +2906,7 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { // 4. copy the parameters to the anonymous constructor, `con`. // 5. copy annotations on the return type to `con`. AnnotatedExecutableType superCon = getAnnotatedType(TreeUtils.getSuperConstructor(tree)); - constructorFromUsePreSubstitution(tree, superCon); + constructorFromUsePreSubstitution(tree, superCon, inferTypeArgs); // no viewpoint adaptation needed for super invocation superCon = AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon); con.computeVarargType(superCon); @@ -2911,8 +2945,9 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); } - Map typeParamToTypeArg = - AnnotatedTypes.findTypeArguments(processingEnv, this, tree, ctor, con); + IPair, Boolean> pair = + AnnotatedTypes.findTypeArguments(this, tree, ctor, con, inferTypeArgs); + Map typeParamToTypeArg = new HashMap<>(pair.first); List typeargs; if (typeParamToTypeArg.isEmpty()) { typeargs = Collections.emptyList(); @@ -2922,15 +2957,7 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { (AnnotatedTypeVariable tv) -> typeParamToTypeArg.get(tv.getUnderlyingType()), con.getTypeVariables()); } - if (TreeUtils.isDiamondTree(tree)) { - // TODO: This should be done at the same time as type argument inference. - List classTypeArgs = inferDiamondType(tree); - int i = 0; - for (AnnotatedTypeMirror typeParam : type.getTypeArguments()) { - typeParamToTypeArg.put((TypeVariable) typeParam.getUnderlyingType(), classTypeArgs.get(i)); - i++; - } - } + con = (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, con); stubTypes.injectRecordComponentType(types, ctor, con); @@ -2938,7 +2965,9 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { // Reset the enclosing type because it can be substituted incorrectly. ((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(enclosingType); } - + if (type.isUnderlyingTypeRaw() || TypesUtils.isRaw(TreeUtils.typeOf(tree))) { + ((AnnotatedDeclaredType) con.getReturnType()).setIsUnderlyingTypeRaw(); + } if (ctor.getEnclosingElement().getKind() == ElementKind.ENUM) { AnnotationMirrorSet enumAnnos = getEnumConstructorQualifiers(); con.getReturnType().replaceAnnotations(enumAnnos); @@ -2964,61 +2993,6 @@ protected AnnotationMirrorSet getEnumConstructorQualifiers() { return new AnnotationMirrorSet(); } - /** - * Creates an AnnotatedDeclaredType for a NewClassTree. Only adds explicit annotations, unless - * newClassTree has a diamond operator. In that case, the annotations on the type arguments are - * inferred using the assignment context and contain defaults. - * - *

          (Subclass beside {@link GenericAnnotatedTypeFactory} should not override this method.) - * - * @param newClassTree a NewClassTree - * @return the AnnotatedDeclaredType - * @deprecated Use {@link #getExplicitNewClassAnnos(NewClassTree)}, {@link - * #getExplicitNewClassClassTypeArgs(NewClassTree)}, or {@link #getAnnotatedType(ClassTree)} - * instead. - */ - @Deprecated // This should be removed when #979 is fixed and the remaining use is removed. - public AnnotatedDeclaredType fromNewClass(NewClassTree newClassTree) { - AnnotatedDeclaredType type = - (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false); - if (!TreeUtils.isDiamondTree(newClassTree)) { - if (newClassTree.getClassBody() == null) { - type.setTypeArguments(getExplicitNewClassClassTypeArgs(newClassTree)); - } - } else { - assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree"; - TreePath p = getPath(newClassTree); - AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p); - if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype; - if (type.getTypeArguments().size() == adctx.getTypeArguments().size()) { - // Try to simply take the type arguments from LHS. - List oldArgs = type.getTypeArguments(); - List newArgs = adctx.getTypeArguments(); - for (int i = 0; i < type.getTypeArguments().size(); ++i) { - if (!types.isSubtype(newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) { - // One of the underlying types doesn't match. Give up. - newArgs = oldArgs; - break; - } - } - type.setTypeArguments(newArgs); - } - } - } - - AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(newClassTree); - // Type may already have explicit dependent type annotations that have not yet been vpa. - type.clearAnnotations(); - type.addAnnotations(explicitAnnos); - // Use the receiver type as enclosing type, if present. - AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(newClassTree); - if (enclosingType != null) { - type.setEnclosingType(enclosingType); - } - return type; - } - /** * Returns the annotations explicitly written on a NewClassTree. * @@ -3064,55 +3038,16 @@ protected List getExplicitNewClassClassTypeArgs(NewClassTre return Collections.emptyList(); } - /** - * Infer the class type arguments for the diamond operator. - * - *

          If {@code newClassTree} is assigned to the same type (not a supertype), then the type - * arguments are inferred to be the same as the assignment. Otherwise, the type arguments are - * annotated by {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. - * - * @param newClassTree a diamond new class tree - * @return the class type arguments for {@code newClassTree} - */ - private List inferDiamondType(NewClassTree newClassTree) { - assert TreeUtils.isDiamondTree(newClassTree) : "Expected diamond new class tree"; - AnnotatedDeclaredType diamondType = - (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(newClassTree), false); - - TreePath p = getPath(newClassTree); - AnnotatedTypeMirror ctxtype = TypeArgInferenceUtil.assignedTo(this, p); - if (ctxtype != null && ctxtype.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adctx = (AnnotatedDeclaredType) ctxtype; - if (diamondType.getTypeArguments().size() == adctx.getTypeArguments().size()) { - // Try to simply take the type arguments from LHS. - List oldArgs = diamondType.getTypeArguments(); - List newArgs = adctx.getTypeArguments(); - boolean useLhs = true; - for (int i = 0; i < diamondType.getTypeArguments().size(); ++i) { - if (!types.isSubtype(newArgs.get(i).underlyingType, oldArgs.get(i).underlyingType)) { - // One of the underlying types doesn't match. Give up. - useLhs = false; - break; - } - } - if (useLhs) { - return newArgs; - } - } - } - addComputedTypeAnnotations(newClassTree, diamondType); - return diamondType.getTypeArguments(); - } - /** * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the * declared constructor type before type variable substitution. * * @param tree a NewClassTree from constructorFromUse() * @param type declared method type before type variable substitution + * @param resolvePolyQuals whether to resolve polymorphic qualifiers */ protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type) {} + NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) {} /** * Returns the return type of the method {@code m}. @@ -4240,7 +4175,7 @@ public AnnotationMirrorSet getDeclAnnotations(Element elt) { * super types of {@code typeMirror}. (Both superclasses and superinterfaces.) * * @param typeMirror type - * @param results set of AnnotationMirrors to which this method adds declarations annotations + * @param results the set of AnnotationMirrors to which this method adds declarations annotations */ private void inheritOverriddenDeclAnnosFromTypeDecl( TypeMirror typeMirror, AnnotationMirrorSet results) { @@ -4670,64 +4605,21 @@ public void replaceAnnotations( annotatedTypeReplacer.setTop(null); } - /** The implementation of the visitor for #containsUninferredTypeArguments. */ - private final SimpleAnnotatedTypeScanner uninferredTypeArgumentScanner = + /** The implementation of the visitor for #containsCapturedTypes. */ + private final SimpleAnnotatedTypeScanner containsCapturedTypes = new SimpleAnnotatedTypeScanner<>( - (type, p) -> - type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument(), + (type, p) -> TypesUtils.isCapturedTypeVariable(type.getUnderlyingType()), Boolean::logicalOr, false); /** - * Returns whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType. + * Returns true if {@code type} contains any captured type variables. * * @param type type to check - * @return whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient + * @return true if {@code type} contains any captured type variables */ - public boolean containsUninferredTypeArguments(AnnotatedTypeMirror type) { - return uninferredTypeArgumentScanner.visit(type); - } - - /** - * Returns a wildcard type to be used as a type argument when the correct type could not be - * inferred. The wildcard will be marked as an uninferred wildcard so that {@link - * AnnotatedWildcardType#isUninferredTypeArgument()} returns true. - * - *

          This method should only be used by type argument inference. - * org.checkerframework.framework.util.AnnotatedTypes.inferTypeArguments(ProcessingEnvironment, - * AnnotatedTypeFactory, ExpressionTree, ExecutableElement) - * - * @param typeVar the TypeVariable that could not be inferred - * @return a wildcard that is marked as an uninferred type argument - */ - public AnnotatedWildcardType getUninferredWildcardType(AnnotatedTypeVariable typeVar) { - final boolean intersectionType; - final TypeMirror boundType; - if (typeVar.getUpperBound().getKind() == TypeKind.INTERSECTION) { - boundType = typeVar.getUpperBound().directSupertypes().get(0).getUnderlyingType(); - intersectionType = true; - } else { - boundType = typeVar.getUnderlyingType().getUpperBound(); - intersectionType = false; - } - - WildcardType wc = types.getWildcardType(boundType, null); - AnnotatedWildcardType wctype = - (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wc, this, false); - wctype.setTypeVariable(typeVar.getUnderlyingType()); - if (!intersectionType) { - wctype.setExtendsBound(typeVar.getUpperBound().deepCopy()); - } else { - wctype.getExtendsBound().addAnnotations(typeVar.getUpperBound().getAnnotations()); - } - wctype.setSuperBound(typeVar.getLowerBound().deepCopy()); - wctype.addAnnotations(typeVar.getAnnotations()); - addDefaultAnnotations(wctype); - wctype.setUninferredTypeArgument(); - return wctype; + public boolean containsCapturedTypes(AnnotatedTypeMirror type) { + return containsCapturedTypes.visit(type); } /** @@ -4776,6 +4668,7 @@ public AnnotatedExecutableType getFunctionTypeFromTree(LambdaExpressionTree tree */ public IPair getFnInterfaceFromTree(Tree tree) { // Functional interface + // This is the target type of `tree`. AnnotatedTypeMirror functionalInterfaceType = getFunctionalInterfaceType(tree); if (functionalInterfaceType.getKind() == TypeKind.DECLARED) { functionalInterfaceType = @@ -4801,7 +4694,7 @@ public IPair getFnInterfaceFromTre * recursively search for lambdas nested in lambdas. * * @param tree the tree of the lambda or method reference - * @return the functional interface type or an uninferred type argument + * @return the functional interface type or a type argument from a raw type */ private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) { TreePath parentPath = getPath(tree).getParentPath(); @@ -4853,19 +4746,13 @@ private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) { ParameterizedExecutableType exe = this.methodFromUse(method); AnnotatedTypeMirror param = AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(exe.executableType, index); - if (param.getKind() == TypeKind.WILDCARD) { - // param is an uninferred wildcard. - TypeMirror typeMirror = TreeUtils.typeOf(tree); - param = AnnotatedTypeMirror.createType(typeMirror, this, false); - addDefaultAnnotations(param); - } assertIsFunctionalInterface(param.getUnderlyingType(), parentTree, tree); return param; case VARIABLE: VariableTree varTree = (VariableTree) parentTree; assertIsFunctionalInterface(TreeUtils.typeOf(varTree), parentTree, tree); - return getAnnotatedType(varTree.getType()); + return getAnnotatedTypeFromTypeTree(varTree.getType()); case ASSIGNMENT: AssignmentTree assignmentTree = (AssignmentTree) parentTree; @@ -4944,7 +4831,7 @@ private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) { */ private void assertIsFunctionalInterface(TypeMirror typeMirror, Tree contextTree, Tree tree) { if (typeMirror.getKind() == TypeKind.WILDCARD) { - // Ignore wildcards, because they are uninferred type arguments. + // Ignore wildcards, because they are type arguments from raw types. return; } Type type = (Type) typeMirror; @@ -5007,8 +4894,8 @@ private AnnotatedDeclaredType makeGroundTargetType( TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType(); - if (wildcardType.isUninferredTypeArgument()) { - // Keep the uninferred type so that it is ignored by later subtyping and + if (wildcardType.isTypeArgOfRawType()) { + // Keep the type arguments from raw types so that it is ignored by later subtyping and // containment checks. typeVarToTypeArg.put(typeVariable, wildcardType); } else if (isExtendsWildcard(wildcardType)) { @@ -5074,8 +4961,7 @@ private AnnotatedDeclaredType makeGroundTargetType( * *

            *
          • {@code type} and {@code typeMirror} are both declared types. - *
          • {@code type} does not have an uninferred type argument and its underlying type is not - * raw. + *
          • {@code type} its underlying type is not raw. *
          • {@code type} has a wildcard as a type argument and {@code typeMirror} has a captured type * variable as the corresponding type argument. *
          @@ -5095,10 +4981,16 @@ private boolean shouldCapture(AnnotatedTypeMirror type, TypeMirror typeMirror) { return false; } - if (uncapturedType.isUnderlyingTypeRaw() || uncapturedType.containsUninferredTypeArguments()) { + if (uncapturedType.isUnderlyingTypeRaw()) { return false; } + for (AnnotatedTypeMirror typeArg : uncapturedType.getTypeArguments()) { + if (AnnotatedTypes.isTypeArgOfRawType(typeArg)) { + return false; + } + } + if (capturedTypeMirror.getTypeArguments().size() != uncapturedType.getTypeArguments().size()) { throw new BugInCF( "Not the same number of type arguments: capturedTypeMirror: %s uncapturedType:" + " %s", @@ -5154,23 +5046,30 @@ public AnnotatedTypeMirror applyCaptureConversion(AnnotatedTypeMirror typeToCapt */ public AnnotatedTypeMirror applyCaptureConversion( AnnotatedTypeMirror type, TypeMirror typeMirror) { - // If the type contains uninferred type arguments, don't capture, but mark all wildcards - // that should have been captured as "uninferred" before it is returned. - if (type.containsUninferredTypeArguments() - && typeMirror.getKind() == TypeKind.DECLARED - && type.getKind() == TypeKind.DECLARED) { + // If the type contains type arguments of raw types, don't capture, but mark all wildcards that + // should have been captured as "raw" before it is returned. + if (typeMirror.getKind() == TypeKind.DECLARED && type.getKind() == TypeKind.DECLARED) { + boolean fromRawType = false; AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; - DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; - for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); - if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD - && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) - || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { - ((AnnotatedWildcardType) uncapturedTypeArg).setUninferredTypeArgument(); + for (AnnotatedTypeMirror typeArg : uncapturedType.getTypeArguments()) { + if (AnnotatedTypes.isTypeArgOfRawType(typeArg)) { + fromRawType = true; + break; } } - return type; + if (fromRawType) { + DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; + for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); + if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD + && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) + || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { + ((AnnotatedWildcardType) uncapturedTypeArg).setTypeArgOfRawType(); + } + } + return type; + } } if (!shouldCapture(type, typeMirror)) { @@ -5467,7 +5366,7 @@ private void annotateCapturedTypeVar( *

          Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code * substitute} to use. */ - private final CapturedTypeVarSubstitutor capturedTypeVarSubstitutor = + public final CapturedTypeVarSubstitutor capturedTypeVarSubstitutor = new CapturedTypeVarSubstitutor(); /** @@ -5477,7 +5376,10 @@ private void annotateCapturedTypeVar( *

          Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code * substitute} to use. */ - private static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier { + public static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier { + + /** Creates a CapturedTypeVarSubstitutor. */ + public CapturedTypeVarSubstitutor() {} /** A mapping from a captured type variable to its AnnotatedTypeVariable. */ private Map capturedTypeVarToAnnotatedTypeVar; @@ -5493,7 +5395,7 @@ private static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier { * @param capturedTypeVarToAnnotatedTypeVar mapping from a TypeVariable (which is a captured * type variable) to an AnnotatedTypeVariable */ - private void substitute( + public void substitute( AnnotatedTypeVariable type, Map capturedTypeVarToAnnotatedTypeVar) { this.capturedTypeVarToAnnotatedTypeVar = capturedTypeVarToAnnotatedTypeVar; diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index a55eb9926be..ae8da45f731 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -885,14 +885,12 @@ public AnnotatedTypeMirror getErased() { public abstract AnnotatedTypeMirror shallowCopy(); /** - * Returns whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient. See issue 979, or the documentation on AnnotatedWildcardType. + * Whether this contains any captured type variables. * - * @return whether this type or any component type is a wildcard type for which Java 7 type - * inference is insufficient + * @return whether the {@code type} contains any captured type variables */ - public boolean containsUninferredTypeArguments() { - return atypeFactory.containsUninferredTypeArguments(this); + public boolean containsCapturedTypes() { + return atypeFactory.containsCapturedTypes(this); } /** @@ -1082,7 +1080,7 @@ public List getTypeArguments() { if (typeArgs != null) { return typeArgs; } else if (isUnderlyingTypeRaw()) { - // Initialize the type arguments with uninferred wildcards. + // Initialize the type arguments with wildcards marks as type arguments from raw types. BoundsInitializer.initializeTypeArgs(this); return typeArgs; } else if (getUnderlyingType().getTypeArguments().isEmpty()) { @@ -1421,7 +1419,7 @@ public AnnotatedTypeMirror getReturnType() { * * @param receiverType the receiver type */ - /*package-private*/ void setReceiverType(@Nullable AnnotatedDeclaredType receiverType) { + public void setReceiverType(@Nullable AnnotatedDeclaredType receiverType) { this.receiverType = receiverType; receiverTypeComputed = true; } @@ -1832,7 +1830,7 @@ public TypeVariable getUnderlyingType() { * * @param type the lower bound type */ - /*package-private*/ void setLowerBound(AnnotatedTypeMirror type) { + public void setLowerBound(AnnotatedTypeMirror type) { checkBound("Lower", type, this); this.lowerBound = type; fixupBoundAnnotations(); @@ -1898,7 +1896,7 @@ private void fixupBoundAnnotations() { * * @param type the upper bound type */ - /*package-private*/ void setUpperBound(AnnotatedTypeMirror type) { + public void setUpperBound(AnnotatedTypeMirror type) { checkBound("Upper", type, this); this.upperBound = type; fixupBoundAnnotations(); @@ -2141,6 +2139,13 @@ public static class AnnotatedWildcardType extends AnnotatedTypeMirror { /** Upper ({@code extends} bound. */ private AnnotatedTypeMirror extendsBound; + /** + * Whether this is a type argument for a type whose {@code #underlyingType} is raw. The Checker + * Framework gives raw types wildcard type arguments so that the annotated type can be used as + * if the annotated type was not raw. + */ + private boolean typeArgOfRawType = false; + /** * The type variable to which this wildcard is an argument. Used to initialize the upper bound * of unbounded wildcards and wildcards in raw types. @@ -2175,7 +2180,7 @@ public boolean removeAnnotation(AnnotationMirror a) { * * @param type the type of the lower bound */ - /*package-private*/ void setSuperBound(AnnotatedTypeMirror type) { + public void setSuperBound(AnnotatedTypeMirror type) { checkBound("Super", type, this); this.superBound = type; fixupBoundAnnotations(); @@ -2205,7 +2210,7 @@ public AnnotatedTypeMirror getSuperBound() { * * @param type the type of the upper bound */ - /*package-private*/ void setExtendsBound(AnnotatedTypeMirror type) { + public void setExtendsBound(AnnotatedTypeMirror type) { checkBound("Extends", type, this); this.extendsBound = type; fixupBoundAnnotations(); @@ -2317,27 +2322,20 @@ public AnnotatedTypeMirror getErased() { return getExtendsBound().getErased(); } - // Remove the uninferredTypeArgument once method type - // argument inference and raw type handling is improved. - private boolean uninferredTypeArgument = false; - - /** - * Set that this wildcard is from an uninferred type argument. This method should only be used - * within the framework. Once issues that depend on this hack, in particular Issue 979, are - * fixed, this must be removed. - */ - public void setUninferredTypeArgument() { - uninferredTypeArgument = true; + /** Set that this wildcard is a type argument of a raw type. */ + public void setTypeArgOfRawType() { + typeArgOfRawType = true; } /** - * Returns whether or not this wildcard is a type argument for which inference failed to infer a - * type. + * Whether this is a type argument to a type whose {@code #underlyingType} is raw. The Checker + * Framework gives raw types wildcard type arguments so that the annotated type can be used as + * if the annotated type was not raw. * - * @return true if this wildcard is a type argument for which inference failed + * @return whether this is a type argument to a type whose {@code #underlyingType} is raw */ - public boolean isUninferredTypeArgument() { - return uninferredTypeArgument; + public boolean isTypeArgOfRawType() { + return typeArgOfRawType; } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java index 1bf94dcd1f1..e82c1b810c8 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java @@ -39,10 +39,10 @@ public class AsSuperVisitor extends AbstractAtmComboVisitor T asSuper(AnnotatedTypeMirror type, T sup /** Resets this. */ private void reset() { - isUninferredTypeArgument = false; + isTypeArgumentFromRawType = false; } @Override @@ -142,7 +142,7 @@ private AnnotatedTypeMirror errorTypeNotErasedSubtypeOfSuperType( // Any type can be converted to String return visit(atypeFactory.getStringType(type), superType, p); } - if (isUninferredTypeArgument) { + if (isTypeArgumentFromRawType) { return copyPrimaryAnnos(type, superType); } throw new BugInCF( @@ -717,14 +717,22 @@ public AnnotatedTypeMirror visitUnion_Wildcard( // - private AnnotatedTypeMirror visitWildcard_NotTypvarNorWildcard( - AnnotatedWildcardType type, AnnotatedTypeMirror superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; - if (type.isUninferredTypeArgument()) { - isUninferredTypeArgument = true; + /** + * Implementation of asSuper for converting wildcards to super types that are not type variables + * or wildcards. + * + * @param type the type + * @param superType the super type + * @return {@code type} converted to {@code superType} + */ + private AnnotatedTypeMirror visitWildcard_NotTypevarNorWildcard( + AnnotatedWildcardType type, AnnotatedTypeMirror superType) { + boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; + if (type.isTypeArgOfRawType()) { + isTypeArgumentFromRawType = true; } - AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, p); - isUninferredTypeArgument = oldIsUninferredTypeArgument; + AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, null); + isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; atypeFactory.addDefaultAnnotations(superType); return copyPrimaryAnnos(type, asSuper); @@ -733,33 +741,33 @@ private AnnotatedTypeMirror visitWildcard_NotTypvarNorWildcard( @Override public AnnotatedTypeMirror visitWildcard_Array( AnnotatedWildcardType type, AnnotatedArrayType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); + return visitWildcard_NotTypevarNorWildcard(type, superType); } @Override public AnnotatedTypeMirror visitWildcard_Declared( AnnotatedWildcardType type, AnnotatedDeclaredType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); + return visitWildcard_NotTypevarNorWildcard(type, superType); } @Override public AnnotatedTypeMirror visitWildcard_Intersection( AnnotatedWildcardType type, AnnotatedIntersectionType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); + return visitWildcard_NotTypevarNorWildcard(type, superType); } @Override public AnnotatedTypeMirror visitWildcard_Primitive( AnnotatedWildcardType type, AnnotatedPrimitiveType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); + return visitWildcard_NotTypevarNorWildcard(type, superType); } @Override public AnnotatedTypeMirror visitWildcard_Typevar( AnnotatedWildcardType type, AnnotatedTypeVariable superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; - if (type.isUninferredTypeArgument()) { - isUninferredTypeArgument = true; + boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; + if (type.isTypeArgOfRawType()) { + isTypeArgumentFromRawType = true; } AnnotatedTypeMirror upperBound = visit(type.getExtendsBound(), superType.getUpperBound(), p); superType.setUpperBound(upperBound); @@ -774,7 +782,7 @@ public AnnotatedTypeMirror visitWildcard_Typevar( lowerBound = asSuperTypevarLowerBound(type.getSuperBound(), superType, p); } superType.setLowerBound(lowerBound); - isUninferredTypeArgument = oldIsUninferredTypeArgument; + isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; atypeFactory.addDefaultAnnotations(superType); return copyPrimaryAnnos(type, superType); @@ -783,16 +791,16 @@ public AnnotatedTypeMirror visitWildcard_Typevar( @Override public AnnotatedTypeMirror visitWildcard_Union( AnnotatedWildcardType type, AnnotatedUnionType superType, Void p) { - return visitWildcard_NotTypvarNorWildcard(type, superType, p); + return visitWildcard_NotTypevarNorWildcard(type, superType); } @Override public AnnotatedTypeMirror visitWildcard_Wildcard( AnnotatedWildcardType type, AnnotatedWildcardType superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; - if (type.isUninferredTypeArgument()) { - isUninferredTypeArgument = true; - superType.setUninferredTypeArgument(); + boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; + if (type.isTypeArgOfRawType()) { + isTypeArgumentFromRawType = true; + superType.setTypeArgOfRawType(); } if (types.isSubtype( type.getExtendsBound().getUnderlyingType(), @@ -825,7 +833,7 @@ public AnnotatedTypeMirror visitWildcard_Wildcard( lowerBound = asSuperWildcardLowerBound(type.getSuperBound(), superType, p); } superType.setSuperBound(lowerBound); - isUninferredTypeArgument = oldIsUninferredTypeArgument; + isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; atypeFactory.addDefaultAnnotations(superType); return copyPrimaryAnnos(type, superType); diff --git a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java index 7248e34b65c..89af6fc9449 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java @@ -51,7 +51,8 @@ public class BoundsInitializer { /** * Initializes the type arguments of {@code declaredType}. The upper bound of unbound wildcards is * set to the upper bound of the type parameter for which it is an argument. If {@code - * declaredType} is raw, then the type arguments are uninferred wildcards. + * declaredType} is raw, then the type arguments are wildcards marked as from raw type ({@link + * AnnotatedWildcardType#isTypeArgOfRawType()}). * * @param declaredType type whose arguments are initialized */ @@ -86,7 +87,7 @@ public static void initializeTypeArgs(AnnotatedDeclaredType declaredType) { AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg; wildcardType.setTypeVariable(typeElement.getTypeParameters().get(i)); if (declaredType.isUnderlyingTypeRaw()) { - wildcardType.setUninferredTypeArgument(); + wildcardType.setTypeArgOfRawType(); } } typeArgs.add(typeArg); @@ -152,7 +153,7 @@ private static WildcardType getUpperBoundAsWildcard( * @param typeVar the type variable whose lower bound is being initialized */ public static void initializeBounds(AnnotatedTypeVariable typeVar) { - initializeBounds(typeVar, null); + initializeBounds(typeVar, Collections.singletonMap(typeVar.getUnderlyingType(), typeVar)); } /** @@ -589,7 +590,7 @@ public void initializeExtendsBound(AnnotatedWildcardType wildcard) { javaExtendsBound = TypesUtils.getObjectTypeMirror(typeFactory.processingEnv); } - if (wildcard.isUninferredTypeArgument()) { + if (wildcard.isTypeArgOfRawType()) { rawTypeWildcards.put(wildcard.getTypeVariable(), wildcard.getUnderlyingType()); } @@ -626,7 +627,7 @@ private void initializeTypeArgs(AnnotatedDeclaredType declaredType) { AnnotatedTypeMirror.createType(javaTypeArg, declaredType.atypeFactory, false); typeArgs.add(atmArg); if (atmArg.getKind() == TypeKind.WILDCARD && declaredType.isUnderlyingTypeRaw()) { - ((AnnotatedWildcardType) atmArg).setUninferredTypeArgument(); + ((AnnotatedWildcardType) atmArg).setTypeArgOfRawType(); } } } else { @@ -704,16 +705,13 @@ public void resolveTypeVarReferences(AnnotatedTypeMirror type) { for (AnnotatedTypeVariable atv : annotatedTypeVars) { TypeVariableStructure list = typeVarToStructure.get(atv.getUnderlyingType()); list.replaceTypeVariablesInType(atv); + list.annotatedTypeVar = atv; } if (type.getKind() == TypeKind.WILDCARD) { // Do the "top level" replacements. AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; topLevelStructure.findAllReplacements(typeVarToStructure); - for (AnnotatedTypeVariable typeVar : topLevelStructure.getAnnotatedTypeVars()) { - TypeVariableStructure list = typeVarToStructure.get(typeVar.getUnderlyingType()); - list.replaceTypeVariablesInType(typeVar); - } topLevelStructure.replaceTypeVariablesInType(wildcard); } } @@ -869,7 +867,7 @@ private static class TypeVariableStructure extends RecursiveTypeStructure { * typeVar. It is expanded during visitation and it is later used as a template for other uses * of typeVar */ - public final AnnotatedTypeVariable annotatedTypeVar; + public AnnotatedTypeVariable annotatedTypeVar; /** * Creates an {@link TypeVariableStructure} diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java index 400f4c4fd39..d7687e3e935 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java @@ -451,7 +451,7 @@ public String visitNull(AnnotatedNullType type, Set visitin @Override public String visitWildcard(AnnotatedWildcardType type, Set visiting) { StringBuilder sb = new StringBuilder(); - if (type.isUninferredTypeArgument()) { + if (type.isTypeArgOfRawType()) { if (currentlyPrintingRaw) { sb.append("/*RAW TYPE ARGUMENT:*/ "); } else { diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index cfcf886bf26..4e1f8218a6c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -1,6 +1,7 @@ package org.checkerframework.framework.type; import java.util.Collection; +import java.util.Collections; import java.util.List; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ElementKind; @@ -367,7 +368,7 @@ protected boolean isContainedBy( return previousResult; } - if (shouldIgnoreUninferredTypeArgs(inside) || shouldIgnoreUninferredTypeArgs(outside)) { + if (shouldIgnoreRawTypeArgs(inside) || shouldIgnoreRawTypeArgs(outside)) { areEqualVisitHistory.put(inside, outside, currentTop, true); return true; } @@ -387,25 +388,7 @@ protected boolean isContainedBy( areEqualVisitHistory.put(inside, outside, currentTop, result); return result; } - if (TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType()) - && !TypesUtils.isCapturedTypeVariable(inside.getUnderlyingType())) { - // TODO: This branch should be removed after #979 is fixed. - // This workaround is only needed when outside is a captured type variable, - // but inside is not. - AnnotatedTypeVariable outsideTypeVar = (AnnotatedTypeVariable) outside; - // Add a placeholder in case of recursion, to prevent infinite regress. - areEqualVisitHistory.put(inside, outside, currentTop, true); - boolean result = - isContainedWithinBounds( - inside, - outsideTypeVar.getLowerBound(), - outsideTypeVar.getUpperBound(), - canBeCovariant); - - areEqualVisitHistory.put(inside, outside, currentTop, result); - return result; - } // The remainder of the method is bullet 6, "T <= T". if (canBeCovariant) { return isSubtype(inside, outside, currentTop); @@ -458,17 +441,15 @@ protected boolean isContainedWithinBounds( } /** - * Returns true if {@code type} is an uninferred type argument and if the checker should not issue - * warnings about uninferred type arguments. + * Returns true if {@code type} is a type argument from a raw type and if the checker should not + * issue warnings about such type arguments. * * @param type type to check - * @return true if {@code type} is an uninferred type argument and if the checker should not issue - * warnings about uninferred type arguments + * @return true if {@code type} is a type argument from a raw type and if the checker should not + * issue warnings about such type arguments. */ - private boolean shouldIgnoreUninferredTypeArgs(AnnotatedTypeMirror type) { - return type.atypeFactory.ignoreUninferredTypeArguments - && type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument(); + private boolean shouldIgnoreRawTypeArgs(AnnotatedTypeMirror type) { + return this.ignoreRawTypes && AnnotatedTypes.isTypeArgOfRawType(type); } // ------------------------------------------------------------------------ @@ -533,14 +514,6 @@ public Boolean visitDeclared_Declared( if (!isPrimarySubtype(subtype, supertype)) { return false; } - AnnotatedTypeFactory factory = subtype.atypeFactory; - if (factory.ignoreUninferredTypeArguments - && (factory.containsUninferredTypeArguments(subtype) - || factory.containsUninferredTypeArguments(supertype))) { - // Calling castedAsSuper may cause the uninferredTypeArguments to be lost. So, just - // return true here. - return true; - } if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { return true; @@ -592,14 +565,7 @@ protected boolean visitTypeArgs( return true; } - TypeElement supertypeElem = (TypeElement) supertype.getUnderlyingType().asElement(); - AnnotationMirror covariantAnno = typeFactory.getDeclAnnotation(supertypeElem, Covariant.class); - - List covariantArgIndexes = - (covariantAnno == null) - ? null - : AnnotationUtils.getElementValueArray( - covariantAnno, covariantValueElement, Integer.class); + List covariantArgIndexes = getCovariantArgIndexes(supertype); // JLS 11: 4.10.2. Subtyping among Class and Interface Types // 4th paragraph, bullet 2 @@ -625,6 +591,19 @@ protected boolean visitTypeArgs( capturedSubtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes); } + @Override + public List getCovariantArgIndexes(AnnotatedDeclaredType type) { + TypeElement supertypeElem = (TypeElement) type.getUnderlyingType().asElement(); + AnnotationMirror covariantAnno = + type.atypeFactory.getDeclAnnotation(supertypeElem, Covariant.class); + if (covariantAnno == null) { + return Collections.emptyList(); + } + + return AnnotationUtils.getElementValueArray( + covariantAnno, covariantValueElement, Integer.class); + } + /** * Calls {@link #isContainedBy(AnnotatedTypeMirror, AnnotatedTypeMirror, boolean)} on the two * lists of type arguments. Returns true if every type argument in {@code supertypeTypeArgs} @@ -643,7 +622,7 @@ protected boolean isContainedMany( for (int i = 0; i < supertypeTypeArgs.size(); i++) { AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i); AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i); - boolean covariant = covariantArgIndexes != null && covariantArgIndexes.contains(i); + boolean covariant = covariantArgIndexes.contains(i); if (!isContainedBy(subTypeArg, superTypeArg, covariant)) { return false; } @@ -856,8 +835,7 @@ public Boolean visitPrimitive_Typevar( @Override public Boolean visitPrimitive_Wildcard( AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, Void p) { - if (supertype.atypeFactory.ignoreUninferredTypeArguments - && supertype.isUninferredTypeArgument()) { + if (shouldIgnoreRawTypeArgs(supertype)) { return true; } // this can occur when passing a primitive to a method on a raw type (see test @@ -1015,7 +993,6 @@ public Boolean visitTypevar_Typevar( } if (TypesUtils.isCapturedTypeVariable(subTM) && TypesUtils.isCapturedTypeVariable(superTM)) { - // This should be removed when 979 is fixed. // This case happens when the captured type variables should be the same type, but // aren't because type argument inference isn't implemented correctly. if (isContainedWithinBounds( @@ -1055,11 +1032,11 @@ public Boolean visitWildcard_Array( @Override public Boolean visitWildcard_Declared( AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, Void p) { - if (subtype.isUninferredTypeArgument()) { - if (subtype.atypeFactory.ignoreUninferredTypeArguments) { + if (subtype.isTypeArgOfRawType()) { + if (ignoreRawTypes) { return true; } else if (supertype.getTypeArguments().isEmpty()) { - // visitWildcard_Type doesn't check uninferred type arguments, because the + // visitWildcard_Type doesn't check type arguments from raw types, because the // underlying Java types may not be in the correct relationship. But, if the // declared type does not have type arguments, then checking primary annotations is // sufficient. @@ -1082,7 +1059,7 @@ public Boolean visitWildcard_Intersection( @Override public Boolean visitWildcard_Primitive( AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, Void p) { - if (subtype.isUninferredTypeArgument()) { + if (subtype.isTypeArgOfRawType()) { return isSubtypeShallowEffective(subtype, supertype, currentTop); } return visitWildcard_Type(subtype, supertype); @@ -1260,9 +1237,8 @@ protected boolean visitUnion_Type(AnnotatedUnionType subtype, AnnotatedTypeMirro */ protected boolean visitType_Wildcard( AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype) { - if (supertype.isUninferredTypeArgument()) { // TODO: REMOVE WHEN WE FIX TYPE ARG INFERENCE - // Can't call isSubtype because underlying Java types won't be subtypes. - return supertype.atypeFactory.ignoreUninferredTypeArguments; + if (supertype.isTypeArgOfRawType()) { + return ignoreRawTypes; } return isSubtype(subtype, supertype.getSuperBound(), currentTop); } @@ -1276,8 +1252,8 @@ protected boolean visitType_Wildcard( */ protected boolean visitWildcard_Type( AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype) { - if (subtype.isUninferredTypeArgument()) { - return subtype.atypeFactory.ignoreUninferredTypeArguments; + if (subtype.isTypeArgOfRawType()) { + return ignoreRawTypes; } if (supertype.getKind() == TypeKind.WILDCARD) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java index 5f504114efd..d1e5e1fd4ea 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java @@ -8,8 +8,8 @@ * Compares two annotated type mirrors for structural equality using only the primary annotations * and underlying types of the two input types and their component types. Note, this leaves out * other fields specific to some AnnotatedTypeMirrors (like directSupertypes, isUnderlyingTypeRaw, - * isUninferredTypeArgument etc...). Ideally, both EqualityAtmComparer and HashcodeAtmVisitor would - * visit relevant fields. + * isTypeArgOfRawType etc...). Ideally, both EqualityAtmComparer and HashcodeAtmVisitor would visit + * relevant fields. * *

          This class is used by AnnotatedTypeMirror#equals * diff --git a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java index 5dd9c9f7044..0969de784f0 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -111,7 +111,6 @@ import org.checkerframework.framework.util.defaults.QualifierDefaults; import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper; import org.checkerframework.framework.util.dependenttypes.DependentTypesTreeAnnotator; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; @@ -1726,8 +1725,9 @@ protected void handleCFGViz(ControlFlowGraph cfg) { * this default is too conservative. So this method is used instead of {@link * GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree)}. * - *

          {@link TypeArgInferenceUtil#assignedToVariable(AnnotatedTypeFactory, VariableTree)} explains - * why a different type is used. + *

          {@link + * org.checkerframework.framework.util.typeinference8.types.InferenceFactory#assignedToVariable(AnnotatedTypeFactory, + * Tree)} explains why a different type is used. * * @param lhsTree left-hand side of an assignment * @return AnnotatedTypeMirror of {@code lhsTree} @@ -1943,8 +1943,9 @@ public AnnotatedTypeMirror getAnnotatedTypeRhsUnaryAssign(UnaryTree tree) { } @Override - public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { - ParameterizedExecutableType mType = super.constructorFromUse(tree); + protected ParameterizedExecutableType constructorFromUse( + NewClassTree tree, boolean inferTypeArgs) { + ParameterizedExecutableType mType = super.constructorFromUse(tree, inferTypeArgs); AnnotatedExecutableType method = mType.executableType; dependentTypesHelper.atConstructorInvocation(method, tree); return mType; @@ -1952,8 +1953,10 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { @Override protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type) { - poly.resolve(tree, type); + NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { + if (resolvePolyQuals) { + poly.resolve(tree, type); + } } @Override @@ -2301,17 +2304,19 @@ public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { } @Override - public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { - ParameterizedExecutableType mType = super.methodFromUse(tree); + protected ParameterizedExecutableType methodFromUse( + MethodInvocationTree tree, boolean inferTypeArg) { + ParameterizedExecutableType mType = super.methodFromUse(tree, inferTypeArg); AnnotatedExecutableType method = mType.executableType; dependentTypesHelper.atMethodInvocation(method, tree); return mType; } @Override - public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) { - super.methodFromUsePreSubstitution(tree, type); - if (tree instanceof MethodInvocationTree) { + public void methodFromUsePreSubstitution( + ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { + super.methodFromUsePreSubstitution(tree, type, resolvePolyQuals); + if (tree instanceof MethodInvocationTree && resolvePolyQuals) { poly.resolve((MethodInvocationTree) tree, type); } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java index e78f60aed97..daa1950ddde 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java @@ -243,12 +243,13 @@ public final boolean isSubtypeShallow( * only used by this method for special cases when qualifier subtyping depends on the Java * basetype. * - *

          Subtypes more often override {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, - * AnnotationMirror, TypeMirror)} than this method. + *

          Subtypes of {@code QualifierHierarchy} more often override {@link + * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this + * method. * - * @param subQualifiers set of qualifiers; exactly one per hierarchy + * @param subQualifiers a set of qualifiers; exactly one per hierarchy * @param subType the type associated with {@code subQualifiers} - * @param superQualifiers set of qualifiers; exactly one per hierarchy + * @param superQualifiers a set of qualifiers; exactly one per hierarchy * @param superType the type associated with {@code superQualifiers} * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the * qualifier in the same hierarchy in {@code superQualifiers} @@ -273,13 +274,48 @@ public final boolean isSubtypeShallow( return true; } + /** + * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers}. The types {@code subType} and + * {@code superType} are not necessarily in a Java subtyping relationship with one another and are + * only used by this method for special cases when qualifier subtyping depends on the Java + * basetype. + * + *

          Subtypes of {@code QualifierHierarchy} more often override {@link + * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this + * method. + * + * @param subQualifiers a set of qualifiers; exactly one per hierarchy + * @param superQualifiers a set of qualifiers; exactly one per hierarchy + * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers} + */ + public boolean isSubtypeQualifiersOnly( + Collection subQualifiers, + Collection superQualifiers) { + assertSameSize(subQualifiers, superQualifiers); + for (AnnotationMirror subQual : subQualifiers) { + AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual); + if (superQual == null) { + throw new BugInCF( + "QualifierHierarchy: missing annotation in hierarchy %s. found: %s", + subQual, StringsPlume.join(",", superQualifiers)); + } + if (!isSubtypeQualifiersOnly(subQual, superQual)) { + return false; + } + } + return true; + } + /** * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier of or equal to the * qualifier in the same hierarchy in {@code superQualifiers}. The type {@code typeMirror} is only * used by this method for special cases when qualifier subtyping depends on the Java basetype. * - *

          Subtypes more often override {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, - * AnnotationMirror, TypeMirror)} than this method. + *

          Subtypes of {@code QualifierHierarchy} more often override {@link + * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this + * method. * * @param subQualifiers a set of qualifiers; exactly one per hierarchy * @param superQualifiers a set of qualifiers; exactly one per hierarchy @@ -315,6 +351,30 @@ public final boolean isSubtypeShallow( protected abstract @Nullable AnnotationMirror leastUpperBoundQualifiers( AnnotationMirror qualifier1, AnnotationMirror qualifier2); + /** + * Returns the least upper bound of all the collections of qualifiers. The result is the lub of + * the qualifier for the same hierarchy in each set. + * + * @param qualifiers a collection of collections of qualifiers. Each inner collection has exactly + * one qualifier per hierarchy. + * @return the least upper bound of the collections of qualifiers + */ + public Set leastUpperBoundsQualifiersOnly( + Collection> qualifiers) { + if (qualifiers.isEmpty()) { + return AnnotationMirrorSet.emptySet(); + } + Set result = null; + for (Collection annos : qualifiers) { + if (result == null) { + result = new AnnotationMirrorSet(annos); + } else { + result = leastUpperBoundsQualifiersOnly(result, annos); + } + } + return result; + } + /** * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. @@ -338,6 +398,37 @@ public final boolean isSubtypeShallow( return leastUpperBoundQualifiers(qualifier1, qualifier2); } + /** + * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy + * @return the least upper bound of the two sets of qualifiers + */ + public Set leastUpperBoundsQualifiersOnly( + Collection qualifiers1, + Collection qualifiers2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); + } + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror lub = leastUpperBoundQualifiersOnly(a1, a2); + if (lub != null) { + result.add(lub); + } + } + } + + assertSameSize(result, qualifiers1); + return result; + } + /** * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. @@ -375,9 +466,9 @@ public final boolean isSubtypeShallow( * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the * qualifier for the same hierarchy in each set. * - * @param qualifiers1 set of qualifiers; exactly one per hierarchy + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy * @param tm1 the type on which qualifiers1 appear - * @param qualifiers2 set of qualifiers; exactly one per hierarchy + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy * @param tm2 the type on which qualifiers2 appear * @return the least upper bound of the two sets of qualifiers */ @@ -508,9 +599,40 @@ public AnnotationMirror widenedUpperBound( * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the * qualifier for the same hierarchy in each set. * - * @param qualifiers1 set of qualifiers; exactly one per hierarchy + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy + * @return the greatest lower bound of the two sets of qualifiers + */ + public Set greatestLowerBoundsQualifiersOnly( + Collection qualifiers1, + Collection qualifiers2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty sets"); + } + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror glb = greatestLowerBoundQualifiersOnly(a1, a2); + if (glb != null) { + result.add(glb); + } + } + } + + assertSameSize(qualifiers1, qualifiers2, result); + return result; + } + + /** + * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy * @param tm1 the type that is annotated by qualifier1 - * @param qualifiers2 set of qualifiers; exactly one per hierarchy + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy * @param tm2 the type that is annotated by qualifier2 * @return the greatest lower bound of the two sets of qualifiers */ @@ -539,6 +661,30 @@ public final Set greatestLowerBoundsShallow( return result; } + /** + * Returns the greatest lower bound the all the collections of qualifiers. The result is the glb + * of the qualifier for the same hierarchy in each set. + * + * @param qualifiers a collection of collections of qualifiers. Each inner collection has exactly + * one qualifier per hierarchy. + * @return the greatest lower bound of the collections of qualifiers + */ + public Set greatestLowerBoundsQualifiersOnly( + Collection> qualifiers) { + if (qualifiers.isEmpty()) { + return AnnotationMirrorSet.emptySet(); + } + Set result = null; + for (Collection annos : qualifiers) { + if (result == null) { + result = new AnnotationMirrorSet(annos); + } else { + result = greatestLowerBoundsQualifiersOnly(result, annos); + } + } + return result; + } + /** * Returns true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set with * fewer qualifiers than the width of the QualifierHierarchy. @@ -565,7 +711,7 @@ public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { * top qualifier, then call {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)} * directly is faster. * - * @param qualifiers set of annotations to search + * @param qualifiers the set of annotations to search * @param qualifier annotation that is in the same hierarchy as the returned annotation * @return annotation in the same hierarchy as qualifier, or null if one is not found */ @@ -579,7 +725,7 @@ public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { * Returns the annotation in {@code qualifiers} that is in the hierarchy for which {@code top} is * top. * - * @param qualifiers set of annotations to search + * @param qualifiers the set of annotations to search * @param top the top annotation in the hierarchy to which the returned annotation belongs * @return annotation in the same hierarchy as annotationMirror, or null if one is not found */ diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java index 6b394cee812..731d746bad3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java @@ -10,6 +10,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; @@ -44,14 +45,6 @@ public StructuralEqualityComparer(StructuralEqualityVisitHistory typeargVisitHis @Override public Boolean defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { - if (type1.getKind() == TypeKind.NULL || type2.getKind() == TypeKind.NULL) { - // If one of the types is the NULL type, compare main qualifiers only. - return arePrimaryAnnosEqual(type1, type2); - } - - if (type1.containsUninferredTypeArguments() || type2.containsUninferredTypeArguments()) { - return type1.atypeFactory.ignoreUninferredTypeArguments; - } return super.defaultAction(type1, type2, p); } @@ -247,9 +240,8 @@ public Boolean visitDeclared_Declared( } else { AnnotatedWildcardType wildcardType1 = (AnnotatedWildcardType) type1Arg; AnnotatedWildcardType wildcardType2 = (AnnotatedWildcardType) type2Arg; - if (type1.atypeFactory.ignoreUninferredTypeArguments - && (wildcardType1.isUninferredTypeArgument() - || wildcardType2.isUninferredTypeArgument())) { + if (type1.atypeFactory.ignoreRawTypeArguments + && (wildcardType1.isTypeArgOfRawType() || wildcardType2.isTypeArgOfRawType())) { result = true; } else { AnnotatedTypeMirror capturedType1Arg = capturedType1Args.get(i); @@ -301,6 +293,11 @@ public Boolean visitPrimitive_Primitive( return arePrimaryAnnosEqual(type1, type2); } + @Override + public Boolean visitNull_Null(AnnotatedNullType type1, AnnotatedNullType type2, Void unused) { + return arePrimaryAnnosEqual(type1, type2); + } + /** * Two type variables are equal if: * @@ -342,8 +339,8 @@ public Boolean visitWildcard_Wildcard( return pastResult; } - if (type1.atypeFactory.ignoreUninferredTypeArguments - && (type1.isUninferredTypeArgument() || type2.isUninferredTypeArgument())) { + if (type1.atypeFactory.ignoreRawTypeArguments + && (type1.isTypeArgOfRawType() || type2.isTypeArgOfRawType())) { return true; } @@ -354,27 +351,6 @@ public Boolean visitWildcard_Wildcard( return result; } - @Override - public Boolean visitWildcard_Typevar( - AnnotatedWildcardType type1, AnnotatedTypeVariable type2, Void p) { - // Once #979 is completed, this should be removed. - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; - } - - if (type1.atypeFactory.ignoreUninferredTypeArguments && type1.isUninferredTypeArgument()) { - return true; - } - - Boolean result = - areEqual(type1.getExtendsBound(), type2.getUpperBound()) - && areEqual(type1.getSuperBound(), type2.getLowerBound()); - - visitHistory.put(type1, type2, currentTop, result); - return result; - } - // Since we don't do a boxing conversion between primitive and declared types, in some cases // we must compare primitives with their boxed counterparts. @Override diff --git a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java index 41315a24b02..32b19e2c47a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java @@ -28,8 +28,6 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; /** * Finds the direct supertypes of an input AnnotatedTypeMirror. See supertypesFromTree( for (Tree implemented : classTree.getImplementsClause()) { AnnotatedDeclaredType adt = (AnnotatedDeclaredType) atypeFactory.getAnnotatedTypeFromTypeTree(implemented); - if (adt.getTypeArguments().size() != adt.getUnderlyingType().getTypeArguments().size() - && classTree.getSimpleName().contentEquals("")) { - // classTree is an anonymous class with a diamond. - List args = - CollectionsPlume.mapList( - (TypeParameterElement element) -> { - AnnotatedTypeMirror arg = - AnnotatedTypeMirror.createType(element.asType(), atypeFactory, false); - // TODO: After #979 is fixed, calculate the correct type - // using inference. - return atypeFactory.getUninferredWildcardType((AnnotatedTypeVariable) arg); - }, - TypesUtils.getTypeElement(adt.getUnderlyingType()).getTypeParameters()); - adt.setTypeArguments(args); - } supertypes.add(adt); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java index 1a586353090..b47aa998392 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java @@ -299,14 +299,9 @@ public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTyp public AnnotatedTypeMirror visitArrayAccess(ArrayAccessTree tree, AnnotatedTypeFactory f) { AnnotatedTypeMirror type = f.getAnnotatedType(tree.getExpression()); if (type.getKind() == TypeKind.ARRAY) { - return ((AnnotatedArrayType) type).getComponentType(); - } else if (type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isUninferredTypeArgument()) { - // Clean-up after Issue #979. - AnnotatedTypeMirror wcbound = ((AnnotatedWildcardType) type).getExtendsBound(); - if (wcbound instanceof AnnotatedArrayType) { - return ((AnnotatedArrayType) wcbound).getComponentType(); - } + AnnotatedTypeMirror t = ((AnnotatedArrayType) type).getComponentType(); + t = f.applyCaptureConversion(t); + return t; } throw new BugInCF("Unexpected type: " + type); } @@ -411,6 +406,10 @@ public AnnotatedTypeMirror visitMethodInvocation( // this case and match the annotated type to the Java type. returnT = ((AnnotatedTypeVariable) returnT).getUpperBound(); } + + if (TypesUtils.isRaw(TreeUtils.typeOf(tree))) { + return returnT.getErased(); + } return f.applyCaptureConversion(returnT); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java index 84394cfa31f..e0884a70d72 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java @@ -172,15 +172,17 @@ public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) int index = lambdaDecl.getParameters().indexOf(f.declarationFromElement(paramElement)); AnnotatedExecutableType functionType = f.getFunctionTypeFromTree(lambdaDecl); AnnotatedTypeMirror funcTypeParam = functionType.getParameterTypes().get(index); - // The Java types should be exactly the same, but because invocation type - // inference (#979) isn't implement, check first. Use the erased types because the - // type arguments are not substituted when the annotated type arguments are. + // During type argument inference, the type of the parameters is assumed to be the same as + // the function parameter. + // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-18.html#jls-18.2.1). + // So if the underlying types are not the same type, then assume the lambda parameter is the + // same as the function type. (Use the erased types because the + // type arguments are not substituted when the annotated type arguments are.) if (TypesUtils.isErasedSubtype( funcTypeParam.underlyingType, lambdaParam.underlyingType, f.types)) { return AnnotatedTypes.asSuper(f, funcTypeParam, lambdaParam); } - lambdaParam.addMissingAnnotations(funcTypeParam.getAnnotations()); - return lambdaParam; + return funcTypeParam; } return null; } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java index 9c07741e0d4..0d86838a8b9 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java @@ -1,7 +1,9 @@ package org.checkerframework.framework.type; import java.util.Collection; +import java.util.List; import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.util.AnnotatedTypes; /** Compares AnnotatedTypeMirrors for subtype relationships. See also {@link QualifierHierarchy}. */ @@ -161,4 +163,12 @@ boolean isSubtypeShallowEffective( * {@code supertype} in the same hierarchy as {@code subQualifier} */ boolean isSubtypeShallowEffective(AnnotationMirror subQualifier, AnnotatedTypeMirror supertype); + + /** + * Returns a list of the indices of the type arguments that are covariant. + * + * @param type a type + * @return a list of the indices of the type arguments that are covariant + */ + List getCovariantArgIndexes(AnnotatedDeclaredType type); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java index 541dd923e45..63c3d15d8bb 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java @@ -156,13 +156,8 @@ protected void reset() { polyInstantiationForQualifierParameter.clear(); } - /** - * Returns true if {@code type} has any polymorphic qualifiers - * - * @param type a type that might have polymorphic qualifiers - * @return true if {@code type} has any polymorphic qualifiers - */ - protected boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type) { + @Override + public boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type) { return polyScanner.visit(type); } @@ -218,7 +213,8 @@ public void resolve(NewClassTree tree, AnnotatedExecutableType type) { if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { return; } - List parameters = type.getParameterTypes(); + List parameters = + AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments()); List arguments = CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); @@ -493,7 +489,7 @@ private AnnotationMirrorMap visit( if (wildcardType.getExtendsBound().getKind() == TypeKind.WILDCARD) { wildcardType = (AnnotatedWildcardType) wildcardType.getExtendsBound(); } - if (wildcardType.isUninferredTypeArgument()) { + if (wildcardType.isTypeArgOfRawType()) { return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java index a0944c66701..4283cc75909 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java @@ -16,6 +16,14 @@ */ public interface QualifierPolymorphism { + /** + * Returns true if {@code type} has any polymorphic qualifiers + * + * @param type a type that might have polymorphic qualifiers + * @return true if {@code type} has any polymorphic qualifiers + */ + boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type); + /** * Resolves polymorphism annotations for the given type. * diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java index 3cf36a84d4a..37bcd489197 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java @@ -92,8 +92,7 @@ public Void visitDeclared(AnnotatedDeclaredType declaredType, Void aVoid) { atypeFactory.fromElement(declaredType.getUnderlyingType().asElement()); List typeArgs = declaredType.getTypeArguments(); for (int i = 0; i < typeArgs.size(); i++) { - if (typeArgs.get(i).getKind() != TypeKind.WILDCARD - || !((AnnotatedWildcardType) typeArgs.get(i)).isUninferredTypeArgument()) { + if (!AnnotatedTypes.isTypeArgOfRawType(typeArgs.get(i))) { // Sometimes the framework infers a more precise type argument, so just use it. continue; } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index b2930b9df5b..b8af68e5cf3 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -5,6 +5,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; @@ -20,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -47,6 +47,7 @@ import org.checkerframework.framework.type.AsSuperVisitor; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.SyntheticArrays; +import org.checkerframework.framework.util.typeinference8.InferenceResult; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; @@ -402,8 +403,8 @@ private static AnnotatedTypeMirror asMemberOfImpl( member, memberType); case WILDCARD: - if (((AnnotatedWildcardType) receiverType).isUninferredTypeArgument()) { - return substituteUninferredTypeArgs(atypeFactory, member, memberType); + if (AnnotatedTypes.isTypeArgOfRawType(receiverType)) { + return substituteTypeArgsFromRawTypes(atypeFactory, member, memberType); } return asMemberOf( types, @@ -523,6 +524,21 @@ private static AnnotatedTypeMirror substituteTypeVariables( memberType = atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); } + if (receiverType.getKind() == TypeKind.DECLARED && member.getKind() == ElementKind.METHOD) { + AnnotatedDeclaredType capturedReceiver = + ((AnnotatedExecutableType) memberType).getReceiverType(); + TypeMirror s = types.asMemberOf(capturedReceiver.getUnderlyingType(), member); + AnnotatedExecutableType t = + (AnnotatedExecutableType) + AnnotatedTypeMirror.createType(s, atypeFactory, memberType.isDeclaration()); + t.setReceiverType(capturedReceiver.deepCopy()); + t.setElement((ExecutableElement) member); + + atypeFactory.initializeAtm(t); + atypeFactory.replaceAnnotations(memberType, t); + return t; + } + return memberType; } @@ -574,14 +590,14 @@ private static void addTypeVarMappings( } /** - * Substitutes uninferred type arguments for type variables in {@code memberType}. + * Substitutes type arguments from raw types for type variables in {@code memberType}. * * @param atypeFactory the type factory * @param member the element with type {@code memberType}; used to obtain the enclosing type * @param memberType the type to side-effect * @return memberType, with type arguments substituted for type variables */ - private static AnnotatedTypeMirror substituteUninferredTypeArgs( + private static AnnotatedTypeMirror substituteTypeArgsFromRawTypes( AnnotatedTypeFactory atypeFactory, Element member, AnnotatedTypeMirror memberType) { TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); Map mappings = new HashMap<>(); @@ -589,11 +605,14 @@ private static AnnotatedTypeMirror substituteUninferredTypeArgs( while (enclosingClassOfMember != null) { if (!enclosingClassOfMember.getTypeParameters().isEmpty()) { AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfMember); - for (AnnotatedTypeMirror type : enclosingType.getTypeArguments()) { + AnnotatedDeclaredType erasedEnclosingType = + atypeFactory.getAnnotatedType(enclosingClassOfMember); + List typeArguments = enclosingType.getTypeArguments(); + for (int i = 0; i < typeArguments.size(); i++) { + AnnotatedTypeMirror type = typeArguments.get(i); + AnnotatedTypeMirror enclosedTypeArg = erasedEnclosingType.getTypeArguments().get(i); AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type; - mappings.put( - typeParameter.getUnderlyingType(), - atypeFactory.getUninferredWildcardType(typeParameter)); + mappings.put(typeParameter.getUnderlyingType(), enclosedTypeArg); } } enclosingClassOfMember = @@ -687,6 +706,14 @@ public static Map overriddenMethods( return Collections.unmodifiableMap(overrides); } + /** + * A pair of an empty map and false. Used in {@link #findTypeArguments(AnnotatedTypeFactory, + * ExpressionTree, ExecutableElement, AnnotatedExecutableType, boolean)}. + */ + private static final IPair, Boolean> emptyFalsePair = + IPair.of(Collections.emptyMap(), false); + ; + /** * Given a method or constructor invocation, return a mapping of the type variables to their type * arguments, if any exist. @@ -701,19 +728,20 @@ public static Map overriddenMethods( * @param elt the element corresponding to the tree * @param preType the (partially annotated) type corresponding to the tree - the result of * AnnotatedTypes.asMemberOf with the receiver and elt - * @return the mapping of the type variables to type arguments for this method or constructor - * invocation + * @param inferTypeArgs whether the type argument should be inferred + * @return the mapping of type variables to type arguments for this method or constructor + * invocation, and whether unchecked conversion was required to infer the type arguments */ - public static Map findTypeArguments( - ProcessingEnvironment processingEnv, + public static IPair, Boolean> findTypeArguments( AnnotatedTypeFactory atypeFactory, ExpressionTree expr, ExecutableElement elt, - AnnotatedExecutableType preType) { - - // Is the method a generic method? - if (elt.getTypeParameters().isEmpty()) { - return new HashMap<>(); + AnnotatedExecutableType preType, + boolean inferTypeArgs) { + if (expr.getKind() != Kind.MEMBER_REFERENCE + && elt.getTypeParameters().isEmpty() + && !TreeUtils.isDiamondTree(expr)) { + return emptyFalsePair; } List targs; @@ -722,22 +750,37 @@ public static Map findTypeArguments( } else if (expr instanceof NewClassTree) { targs = ((NewClassTree) expr).getTypeArguments(); } else if (expr instanceof MemberReferenceTree) { - targs = ((MemberReferenceTree) expr).getTypeArguments(); - if (targs == null) { - // TODO: Add type argument inference as part of fix for #979 - return new HashMap<>(); + MemberReferenceTree memRef = ((MemberReferenceTree) expr); + if (inferTypeArgs && TreeUtils.needsTypeArgInference(memRef)) { + InferenceResult inferenceResult = + atypeFactory.getTypeArgumentInference().inferTypeArgs(atypeFactory, expr, preType); + return IPair.of( + inferenceResult.getTypeArgumentsForExpression(expr), + inferenceResult.isUncheckedConversion()); + } + targs = memRef.getTypeArguments(); + if (memRef.getTypeArguments() == null) { + return emptyFalsePair; } } else { // This case should never happen. throw new BugInCF("AnnotatedTypes.findTypeArguments: unexpected tree: " + expr); } + if (preType.getReceiverType() != null) { + DeclaredType receiverTypeMirror = preType.getReceiverType().getUnderlyingType(); + if (TypesUtils.isRaw(receiverTypeMirror) + && elt.getEnclosingElement().equals(receiverTypeMirror.asElement())) { + return emptyFalsePair; + } + } + // Has the user supplied type arguments? - if (!targs.isEmpty()) { + if (!targs.isEmpty() && !TreeUtils.isDiamondTree(expr)) { List tvars = preType.getTypeVariables(); if (tvars.isEmpty()) { // This happens when the method is invoked with a raw receiver. - return Collections.emptyMap(); + return emptyFalsePair; } Map typeArguments = new HashMap<>(); @@ -748,11 +791,17 @@ public static Map findTypeArguments( // already should be a declaration. typeArguments.put(typeVar.getUnderlyingType(), typeArg); } - return typeArguments; + return IPair.of(typeArguments, false); } else { - return atypeFactory - .getTypeArgumentInference() - .inferTypeArgs(atypeFactory, expr, elt, preType); + if (inferTypeArgs) { + InferenceResult inferenceResult = + atypeFactory.getTypeArgumentInference().inferTypeArgs(atypeFactory, expr, preType); + return IPair.of( + inferenceResult.getTypeArgumentsForExpression(expr), + inferenceResult.isUncheckedConversion()); + } else { + return emptyFalsePair; + } } } @@ -963,14 +1012,8 @@ private static AnnotatedTypeMirror glbSubtype( public static List adaptParameters( AnnotatedTypeFactory atypeFactory, AnnotatedExecutableType method, - List args, - @Nullable NewClassTree tree) { + List args) { List parameters = method.getParameterTypes(); - - if (parameters.isEmpty()) { - return parameters; - } - // Handle anonymous constructors that extend a class with an enclosing type. // There is a mismatch between the number of parameters and arguments when // the following conditions are met: @@ -1639,4 +1682,17 @@ public static void applyAnnotationsFromDeclaredType( underlyingTypeMirror = ((DeclaredType) underlyingTypeMirror).getEnclosingType(); } } + + /** + * Returns whether {@code type} is a type argument to a type whose {@code #underlyingType} is raw. + * The Checker Framework gives raw types wildcard type arguments so that the annotated type can be + * used as if the annotated type was not raw. + * + * @param type an annotated type + * @return whether this is a type argument to a type whose {@code #underlyingType} is raw + */ + public static boolean isTypeArgOfRawType(AnnotatedTypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type).isTypeArgOfRawType(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java index e7aa6e7ada4..e191fd7985f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java @@ -254,8 +254,8 @@ private void lubTypeArgument( AnnotatedWildcardType type1Wildcard = (AnnotatedWildcardType) type1AsLub; AnnotatedWildcardType type2Wildcard = (AnnotatedWildcardType) type2AsLub; AnnotatedWildcardType lubWildcard = (AnnotatedWildcardType) lub; - if (type1Wildcard.isUninferredTypeArgument() || type2Wildcard.isUninferredTypeArgument()) { - lubWildcard.setUninferredTypeArgument(); + if (type1Wildcard.isTypeArgOfRawType() || type2Wildcard.isTypeArgOfRawType()) { + lubWildcard.setTypeArgOfRawType(); } lubWildcard( type1Wildcard.getSuperBound(), diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 935658ae169..bcfd6319d4d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -680,8 +680,42 @@ private DefaultSet defaultsAt(Element elt) { return elementDefaults.get(elt); } - DefaultSet qualifiers = defaultsAtDirect(elt); - DefaultSet parentDefaults; + DefaultSet qualifiers = null; + + { + AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); + + if (dqAnno != null) { + qualifiers = new DefaultSet(); + Set p = fromDefaultQualifier(dqAnno); + + if (p != null) { + qualifiers.addAll(p); + } + } + } + + { + AnnotationMirror dqListAnno = + atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); + if (dqListAnno != null) { + if (qualifiers == null) { + qualifiers = new DefaultSet(); + } + + List values = + AnnotationUtils.getElementValueArray( + dqListAnno, defaultQualifierListValueElement, AnnotationMirror.class); + for (AnnotationMirror dqAnno : values) { + Set p = fromDefaultQualifier(dqAnno); + if (p != null) { + qualifiers.addAll(p); + } + } + } + } + + Element parent; if (elt.getKind() == ElementKind.PACKAGE) { Element parent = ElementUtils.parentPackage((PackageElement) elt, elements); DefaultSet origParentDefaults = defaultsAt(parent); diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java deleted file mode 100644 index b5106d0e4cb..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/DefaultTypeArgumentInference.java +++ /dev/null @@ -1,867 +0,0 @@ -package org.checkerframework.framework.util.typeinference; - -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.Tree; -import com.sun.source.util.TreePath; -import com.sun.tools.javac.code.Symbol.MethodSymbol; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.TypeHierarchy; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.framework.util.typeinference.constraint.A2F; -import org.checkerframework.framework.util.typeinference.constraint.A2FReducer; -import org.checkerframework.framework.util.typeinference.constraint.AFConstraint; -import org.checkerframework.framework.util.typeinference.constraint.AFReducer; -import org.checkerframework.framework.util.typeinference.constraint.F2A; -import org.checkerframework.framework.util.typeinference.constraint.F2AReducer; -import org.checkerframework.framework.util.typeinference.constraint.FIsA; -import org.checkerframework.framework.util.typeinference.constraint.FIsAReducer; -import org.checkerframework.framework.util.typeinference.constraint.TSubU; -import org.checkerframework.framework.util.typeinference.constraint.TSuperU; -import org.checkerframework.framework.util.typeinference.constraint.TUConstraint; -import org.checkerframework.framework.util.typeinference.solver.ConstraintMap; -import org.checkerframework.framework.util.typeinference.solver.ConstraintMapBuilder; -import org.checkerframework.framework.util.typeinference.solver.EqualitiesSolver; -import org.checkerframework.framework.util.typeinference.solver.InferenceResult; -import org.checkerframework.framework.util.typeinference.solver.InferredValue; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; -import org.checkerframework.framework.util.typeinference.solver.SubtypesSolver; -import org.checkerframework.framework.util.typeinference.solver.SupertypesSolver; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreePathUtil; -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.IPair; -import org.plumelib.util.StringsPlume; - -/** - * An implementation of TypeArgumentInference that mostly follows the process outlined in JLS7 See - * the JLS 7: JLS - * §5.12.2.7 - * - *

          Note, there are some deviations JLS 7 for the following cases: - * - *

            - *
          • Places where the JLS is vague. For these cases, first the OpenJDK implementation was - * consulted and then we favored the behavior we desire rather than the implied behavior of - * the JLS or JDK implementation. - *
          • The fact that any given type variable type may or may not have annotations for multiple - * hierarchies means that constraints are more complicated than their Java equivalents. Every - * constraint must identify the hierarchies to which they apply. This makes solving the - * constraint sets more complicated. - *
          • If an argument to a method is null, then the JLS says that it does not constrain the type - * argument. However, null may constrain the qualifiers on the type argument, so it is - * included in the constraints but is not used as the underlying type of the type argument. - *
          - * - * TODO: The following limitations need to be fixed, as at the time of this writing we do not have - * the time to handle them: - * - *
            - *
          • The GlbUtil does not correctly handled wildcards/typevars when the glb result should be a - * wildcard or typevar - *
          • Interdependent Method Invocations -- Currently we do not correctly handle the case where - * two methods need to have their arguments inferred and one is the argument to the other. - * E.g. - *
            {@code
            - *  T get()
            - *  void set(S s)
            - * set(get())
            - * }
            - * Presumably, we want to detect these situations and combine the set of constraints with - * {@code T <: S}. - *
          - */ -public class DefaultTypeArgumentInference implements TypeArgumentInference { - private final EqualitiesSolver equalitiesSolver = new EqualitiesSolver(); - private final SupertypesSolver supertypesSolver = new SupertypesSolver(); - private final SubtypesSolver subtypesSolver = new SubtypesSolver(); - private final ConstraintMapBuilder constraintMapBuilder = new ConstraintMapBuilder(); - - private final boolean showInferenceSteps; - - public DefaultTypeArgumentInference(AnnotatedTypeFactory typeFactory) { - this.showInferenceSteps = typeFactory.getChecker().hasOption("showInferenceSteps"); - } - - @Override - public Map inferTypeArgs( - AnnotatedTypeFactory typeFactory, - ExpressionTree expressionTree, - ExecutableElement methodElem, - AnnotatedExecutableType methodType) { - - List argTypes = - TypeArgInferenceUtil.getArgumentTypes(expressionTree, typeFactory); - TreePath pathToExpression = typeFactory.getPath(expressionTree); - assert pathToExpression != null; - AnnotatedTypeMirror assignedTo = TypeArgInferenceUtil.assignedTo(typeFactory, pathToExpression); - - SourceChecker checker = typeFactory.getChecker(); - - if (showInferenceSteps) { - checker.message( - Diagnostic.Kind.NOTE, - "DTAI: expression: %s%n argTypes: %s%n assignedTo: %s", - expressionTree.toString().replace(System.lineSeparator(), " "), - argTypes, - assignedTo); - } - - Set targets = TypeArgInferenceUtil.methodTypeToTargets(methodType); - - if (TreePathUtil.enclosingNonParen(pathToExpression).first.getKind() - == Tree.Kind.LAMBDA_EXPRESSION - || (assignedTo == null && TreePathUtil.getAssignmentContext(pathToExpression) != null)) { - // If the type of the assignment context isn't found, but the expression is assigned, - // then don't attempt to infer type arguments, because the Java type inferred will be - // incorrect. The assignment type is null when it includes uninferred type arguments. - // For example: - // T outMethod() - // void inMethod(U u); - // inMethod(outMethod()) - // would require solving the constraints for both type argument inferences - // simultaneously - // Also, if the parent of the expression is a lambda, then the type arguments cannot be - // inferred. - Map inferredArgs = new LinkedHashMap<>(); - handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); - return inferredArgs; - } - if (assignedTo == null) { - assignedTo = typeFactory.getDummyAssignedTo(expressionTree); - } - Map inferredArgs; - try { - inferredArgs = - infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, true); - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " after infer: %s", inferredArgs); - } - handleNullTypeArguments( - typeFactory, methodElem, methodType, argTypes, assignedTo, targets, inferredArgs); - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " after handleNull: %s", inferredArgs); - } - } catch (Exception ex) { - // Catch any errors thrown by inference. - inferredArgs = new LinkedHashMap<>(); - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " exception: %s", ex.getLocalizedMessage()); - } - } - - handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); - - if (showInferenceSteps) { - checker.message(Diagnostic.Kind.NOTE, " results: %s", inferredArgs); - } - try { - return TypeArgInferenceUtil.correctResults( - inferredArgs, expressionTree, (ExecutableType) methodElem.asType(), typeFactory); - } catch (Throwable ex) { - // Ignore any exceptions - return inferredArgs; - } - } - - /** - * If one of the inferredArgs are NullType, then re-run inference ignoring null method arguments. - * Then lub the result of the second inference with the NullType and put the new result back into - * inferredArgs. - * - * @param typeFactory type factory - * @param methodElem element of the method - * @param methodType annotated type of the method - * @param argTypes annotated types of arguments to the method - * @param assignedTo annotated type to which the result of the method invocation is assigned - * @param targets set of type variables to infer - * @param inferredArgs map of type variables to the annotated types of their type arguments - */ - private void handleNullTypeArguments( - AnnotatedTypeFactory typeFactory, - ExecutableElement methodElem, - AnnotatedExecutableType methodType, - List argTypes, - AnnotatedTypeMirror assignedTo, - Set targets, - Map inferredArgs) { - if (!hasNullType(inferredArgs)) { - return; - } - Map inferredArgsWithoutNull = - infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, false); - for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) { - TypeVariable typeVar = atv.getUnderlyingType(); - AnnotatedTypeMirror result = inferredArgs.get(typeVar); - if (result == null) { - AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar); - if (withoutNullResult != null) { - inferredArgs.put(typeVar, withoutNullResult); - } - } else if (result.getKind() == TypeKind.NULL) { - AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar); - if (withoutNullResult == null) { - // withoutNullResult is null when the only constraint on a type argument is - // where a method argument is null. - withoutNullResult = atv.getUpperBound().deepCopy(); - } - AnnotatedTypeMirror lub = - AnnotatedTypes.leastUpperBound(typeFactory, withoutNullResult, result); - inferredArgs.put(typeVar, lub); - } - } - } - - private boolean hasNullType(Map inferredArgs) { - for (AnnotatedTypeMirror atm : inferredArgs.values()) { - if (atm.getKind() == TypeKind.NULL) { - return true; - } - } - return false; - } - - /** - * This algorithm works as follows: - * - *
            - * - *
          • 1. Build Argument Constraints -- create a set of constraints using the arguments to the - * type parameter declarations, the formal parameters, and the arguments to the method call - *
          • 2. Solve Argument Constraints -- Create two solutions from the arguments. - *
              - *
            1. Equality Arg Solution: Solution inferred from arguments used in an invariant - * position (i.e. from equality constraints) - *
            2. Supertypes Arg Solution: Solution inferred from constraints in which the parameter - * is a supertype of argument types. These are kept separate and merged later. - *
            - * Note: If there is NO assignment context we just combine the results from 2.a and 2.b, - * giving preference to those in 2.a, and return the result. - *
          • 3. Build and Solve Initial Assignment Constraints -- Create a set of constraints from the - * assignment context WITHOUT substituting either solution from step 2. - *
          • 4. Combine the solutions from steps 2.b and 3. This handles cases like the following: - *
            {@code
            -   *  List method(T t1) {}
            -   * List<@Nullable String> nl = method("");
            -   * }
            - * If we use just the arguments to infer T we will infer @NonNull String (since the lub of - * all arguments would be @NonNull String). However, this would cause the assignment to - * fail. Instead, since {@literal @NonNull String <: @Nullable String}, we can safely infer - * T to be @Nullable String and both the argument types and the assignment types are - * compatible. In step 4, we combine the results of Step 2.b (which came from lubbing - * argument and argument component types) with the solution from equality constraints via - * the assignment context. - *

            Note, we always give preference to the results inferred from method arguments if there - * is a conflict between the steps 2 and 4. For example: - *

            {@code
            -   *  List method(T t1) {}
            -   * List<@NonNull String> nl = method(null);
            -   * }
            - * In the above example, the null argument requires that T must be @Nullable String. But the - * assignment context requires that the T must be @NonNull String. But, in this case if we - * use @NonNull String the argument "null" is invalid. In this case, we use @Nullable String - * and report an assignment.type.incompatible because we ALWAYS favor the arguments over the - * assignment context. - *
          • 5. Combine the result from 2.a and step 4, if there is a conflict use the result from - * step 2.a - *

            Suppose we have the following: - *

            {@code
            -   *  void method(List<@NonNull T> t, @Initialized Tt) { ... }
            -   * List<@FBCBottom String> lBottom = ...;
            -   * method( lbBottom, "nonNullString" );
            -   * }
            - * From the first argument we can infer that T must be exactly @FBCBottom String but we - * cannot infer anything for the Nullness hierarchy. For the second argument we can infer - * that T is at most @NonNull String but we can infer nothing in the initialization - * hierarchy. In this step we combine these two results, always favoring the equality - * constraints if there is a conflict. For the above example we would infer the following: - *
            {@code
            -   * T => @FBCBottom @NonNull String
            -   * }
            - * Another case covered in this step is: - *
            {@code
            -   *  List method(List t1) {}
            -   * List<@NonNull String> nonNullList = new ArrayList<>();
            -   * List<@Nullable String> nl = method(nonNullList);
            -   * }
            - * The above assignment should fail because T is forced to be both @NonNull and @Nullable. - * In cases like these, we use @NonNull String because we always favor constraints from the - * arguments over the assignment context. - *
          • 6. Infer from Assignment Context Finally, the JLS states that we should substitute the - * types we have inferred up until this point back into the original argument constraints. - * We should then combine the constraints we get from the assignment context and solve using - * the greatest lower bounds of all of the constraints of the form: {@literal F :> U} (these - * are referred to as "subtypes" in the ConstraintMap.TargetConstraints). - *
          • 7. Merge the result from steps 5 and 6 giving preference to 5 (the argument constraints). - * Return the result. - *
          - */ - private Map infer( - AnnotatedTypeFactory typeFactory, - List argumentTypes, - AnnotatedTypeMirror assignedTo, - ExecutableElement methodElem, - AnnotatedExecutableType methodType, - Set targets, - boolean useNullArguments) { - - // 1. Step 1 - Build up argument constraints - // The AFConstraints for arguments are used also in the - Set afArgumentConstraints = - createArgumentAFConstraints( - typeFactory, argumentTypes, methodType, targets, useNullArguments); - - // 2. Step 2 - Solve the constraints. - IPair argInference = - inferFromArguments(typeFactory, afArgumentConstraints, targets); - - InferenceResult fromArgEqualities = argInference.first; // result 2.a - InferenceResult fromArgSubandSupers = argInference.second; // result 2.b - - clampToLowerBound(fromArgSubandSupers, methodType.getTypeVariables(), typeFactory); - - // if this method invocation's has a return type and it is assigned/pseudo-assigned to - // a variable, assignedTo is the type of that variable - if (assignedTo == null) { - fromArgEqualities.mergeSubordinate(fromArgSubandSupers); - - return fromArgEqualities.toAtmMap(); - } // else - - AnnotatedTypeMirror declaredReturnType = methodType.getReturnType(); - AnnotatedTypeMirror boxedReturnType; - if (declaredReturnType == null) { - boxedReturnType = null; - } else if (declaredReturnType.getKind().isPrimitive()) { - boxedReturnType = typeFactory.getBoxedType((AnnotatedPrimitiveType) declaredReturnType); - } else { - boxedReturnType = declaredReturnType; - } - - InferenceResult fromArguments = fromArgEqualities; - if (!((MethodSymbol) methodElem).isConstructor()) { - // Step 3 - Infer a solution from the equality constraints in the assignment context - InferenceResult fromAssignmentEqualities = - inferFromAssignmentEqualities(assignedTo, boxedReturnType, targets, typeFactory); - - // Step 4 - Combine the results from 2.b and step 3 - InferenceResult combinedSupertypesAndAssignment = - combineSupertypeAndAssignmentResults( - targets, typeFactory, fromAssignmentEqualities, fromArgSubandSupers); - - // Step 5 - Combine the result from 2.a and step 4, if there is a conflict use the - // result from step 2.a - fromArgEqualities.mergeSubordinate(combinedSupertypesAndAssignment); - - // if we don't have a result for all type arguments - // Step 6 - Infer the type arguments from the greatest-lower-bounds of all "subtype" - // constraints - if (!fromArguments.isComplete(targets)) { - InferenceResult fromAssignment = - inferFromAssignment( - assignedTo, - boxedReturnType, - methodType, - afArgumentConstraints, - fromArguments, - targets, - typeFactory); - - // Step 7 - Merge the argument and the assignment constraints - fromArguments.mergeSubordinate(fromAssignment); - } - - } else { - - fromArguments.mergeSubordinate(fromArgSubandSupers); - } - - return fromArguments.toAtmMap(); - } - - /** - * If we have inferred a type argument from the supertype constraints and this type argument is - * BELOW the lower bound, make it AT the lower bound. - * - *

          e.g. - * - *

          {@code
          -   * <@Initialized T extends @Initialized Object> void id(T t) { return t; }
          -   * id(null);
          -   *
          -   * // The invocation of id will result in a type argument with primary annotations of @FBCBottom @Nullable
          -   * // but this is below the lower bound of T in the initialization hierarchy so instead replace
          -   * // @FBCBottom with @Initialized
          -   *
          -   * // This should happen ONLY with supertype constraints because raising the primary annotation would still
          -   * // be valid for these constraints (since we just LUB the arguments involved) but would violate any
          -   * // equality constraints
          -   * }
          - * - * TODO: NOTE WE ONLY DO THIS FOR InferredType results for now but we should probably include - * targets as well - * - * @param fromArgSupertypes types inferred from LUBbing types from the arguments to the formal - * parameters - * @param targetDeclarations the declared types of the type parameters whose arguments are being - * inferred - */ - private void clampToLowerBound( - InferenceResult fromArgSupertypes, - List targetDeclarations, - AnnotatedTypeFactory typeFactory) { - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - - for (AnnotatedTypeVariable targetDecl : targetDeclarations) { - InferredValue inferred = fromArgSupertypes.get(targetDecl.getUnderlyingType()); - if (inferred instanceof InferredType) { - AnnotatedTypeMirror lowerBoundAsArgument = targetDecl.getLowerBound(); - for (AnnotationMirror top : tops) { - AnnotationMirror lowerBoundAnno = - lowerBoundAsArgument.getEffectiveAnnotationInHierarchy(top); - AnnotatedTypeMirror inferredType = ((InferredType) inferred).type; - AnnotationMirror argAnno = inferredType.getEffectiveAnnotationInHierarchy(top); - if (qualHierarchy.isSubtypeQualifiersOnly(argAnno, lowerBoundAnno)) { - inferredType.replaceAnnotation(lowerBoundAnno); - } - } - } - } - } - - /** - * Step 1: Create a constraint {@code Ai << Fi} for each Argument(Ai) to formal parameter(Fi). - * Remove any constraint that does not involve a type parameter to be inferred. Reduce the - * remaining constraints so that Fi = Tj where Tj is a type parameter with an argument to be - * inferred. Return the resulting constraint set. - * - * @param typeFactory the type factory - * @param argTypes list of annotated types corresponding to the arguments to the method - * @param methodType annotated type of the method - * @param targets type variables to be inferred - * @param useNullArguments whether or not null method arguments should be considered - * @return a set of argument constraints - */ - protected Set createArgumentAFConstraints( - AnnotatedTypeFactory typeFactory, - List argTypes, - AnnotatedExecutableType methodType, - Set targets, - boolean useNullArguments) { - List paramTypes = - AnnotatedTypes.expandVarArgsParametersFromTypes(methodType, argTypes); - - if (argTypes.size() != paramTypes.size()) { - throw new BugInCF( - StringsPlume.joinLines( - "Mismatch between formal parameter count and argument count.", - "paramTypes=" + StringsPlume.join(",", paramTypes), - "argTypes=" + StringsPlume.join(",", argTypes))); - } - - int numberOfParams = paramTypes.size(); - ArrayDeque afConstraints = new ArrayDeque<>(numberOfParams); - for (int i = 0; i < numberOfParams; i++) { - if (!useNullArguments && argTypes.get(i).getKind() == TypeKind.NULL) { - continue; - } - afConstraints.add(new A2F(argTypes.get(i), paramTypes.get(i))); - } - - Set reducedConstraints = new LinkedHashSet<>(); - - reduceAfConstraints(typeFactory, reducedConstraints, afConstraints, targets); - return reducedConstraints; - } - - /** - * Step 2. Infer type arguments from the equality (TisU) and the supertype (TSuperU) constraints - * of the methods arguments. - */ - private IPair inferFromArguments( - AnnotatedTypeFactory typeFactory, - Set afArgumentConstraints, - Set targets) { - Set tuArgConstraints = afToTuConstraints(afArgumentConstraints, targets); - addConstraintsBetweenTargets(tuArgConstraints, targets, false, typeFactory); - - ConstraintMap argConstraints = - constraintMapBuilder.build(targets, tuArgConstraints, typeFactory); - - InferenceResult inferredFromArgEqualities = - equalitiesSolver.solveEqualities(targets, argConstraints, typeFactory); - - Set remainingTargets = - inferredFromArgEqualities.getRemainingTargets(targets, true); - InferenceResult fromSupertypes = - supertypesSolver.solveFromSupertypes(remainingTargets, argConstraints, typeFactory); - - InferenceResult fromSubtypes = - subtypesSolver.solveFromSubtypes(remainingTargets, argConstraints, typeFactory); - fromSupertypes.mergeSubordinate(fromSubtypes); - - return IPair.of(inferredFromArgEqualities, fromSupertypes); - } - - /** Step 3. Infer type arguments from the equality constraints of the assignment context. */ - private InferenceResult inferFromAssignmentEqualities( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - Set targets, - AnnotatedTypeFactory typeFactory) { - Set afInitialAssignmentConstraints = - createInitialAssignmentConstraints(assignedTo, boxedReturnType, typeFactory, targets); - - Set tuInitialAssignmentConstraints = - afToTuConstraints(afInitialAssignmentConstraints, targets); - ConstraintMap initialAssignmentConstraints = - constraintMapBuilder.build(targets, tuInitialAssignmentConstraints, typeFactory); - return equalitiesSolver.solveEqualities(targets, initialAssignmentConstraints, typeFactory); - } - - /** - * Create a set of constraints between return type and any type to which it is assigned. Reduce - * these set of constraints and remove any that is not an equality (FIsA) constraint. - */ - protected Set createInitialAssignmentConstraints( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - AnnotatedTypeFactory typeFactory, - Set targets) { - Set result = new LinkedHashSet<>(); - - if (assignedTo != null) { - Set reducedConstraints = new LinkedHashSet<>(); - - Queue constraints = new ArrayDeque<>(); - constraints.add(new F2A(boxedReturnType, assignedTo)); - - reduceAfConstraints(typeFactory, reducedConstraints, constraints, targets); - - for (AFConstraint reducedConstraint : reducedConstraints) { - if (reducedConstraint instanceof FIsA) { - result.add((FIsA) reducedConstraint); - } - } - } - - return result; - } - - /** - * The first half of Step 6. - * - *

          This method creates constraints: - * - *

            - *
          • between the bounds of types that are already inferred and their inferred arguments - *
          • between the assignment context and the return type of the method (with the previously - * inferred arguments substituted into these constraints) - *
          - */ - public ConstraintMap createAssignmentConstraints( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - AnnotatedExecutableType methodType, - Set afArgumentConstraints, - Map inferredArgs, - Set targets, - AnnotatedTypeFactory typeFactory) { - - ArrayDeque assignmentAfs = - new ArrayDeque<>(2 * methodType.getTypeVariables().size() + afArgumentConstraints.size()); - for (AnnotatedTypeVariable typeParam : methodType.getTypeVariables()) { - TypeVariable target = typeParam.getUnderlyingType(); - AnnotatedTypeMirror inferredType = inferredArgs.get(target); - // for all inferred types Ti: Ti >> Bi where Bi is upper bound and Ti << Li where Li is - // the lower bound for all uninferred types Tu: Tu >> Bi and Lu >> Tu - if (inferredType != null) { - assignmentAfs.add(new A2F(inferredType, typeParam.getUpperBound())); - assignmentAfs.add(new F2A(typeParam.getLowerBound(), inferredType)); - } else { - assignmentAfs.add(new F2A(typeParam, typeParam.getUpperBound())); - assignmentAfs.add(new A2F(typeParam.getLowerBound(), typeParam)); - } - } - - for (AFConstraint argConstraint : afArgumentConstraints) { - if (argConstraint instanceof F2A) { - assignmentAfs.add(argConstraint); - } - } - - ArrayDeque substitutedAssignmentConstraints = - new ArrayDeque<>(assignmentAfs.size() + 1); - for (AFConstraint afConstraint : assignmentAfs) { - substitutedAssignmentConstraints.add(afConstraint.substitute(inferredArgs)); - } - - AnnotatedTypeMirror substitutedReturnType = - TypeArgInferenceUtil.substitute(inferredArgs, boxedReturnType); - substitutedAssignmentConstraints.add(new F2A(substitutedReturnType, assignedTo)); - - Set reducedConstraints = new LinkedHashSet<>(); - reduceAfConstraints(typeFactory, reducedConstraints, substitutedAssignmentConstraints, targets); - Set tuAssignmentConstraints = afToTuConstraints(reducedConstraints, targets); - addConstraintsBetweenTargets(tuAssignmentConstraints, targets, true, typeFactory); - return constraintMapBuilder.build(targets, tuAssignmentConstraints, typeFactory); - } - - /** The Second half of step 6. Use the assignment context to infer a result. */ - private InferenceResult inferFromAssignment( - AnnotatedTypeMirror assignedTo, - AnnotatedTypeMirror boxedReturnType, - AnnotatedExecutableType methodType, - Set afArgumentConstraints, - InferenceResult inferredArgs, - Set targets, - AnnotatedTypeFactory typeFactory) { - ConstraintMap assignmentConstraints = - createAssignmentConstraints( - assignedTo, - boxedReturnType, - methodType, - afArgumentConstraints, - inferredArgs.toAtmMap(), - targets, - typeFactory); - - InferenceResult equalitiesResult = - equalitiesSolver.solveEqualities(targets, assignmentConstraints, typeFactory); - - Set remainingTargets = equalitiesResult.getRemainingTargets(targets, true); - InferenceResult subtypesResult = - subtypesSolver.solveFromSubtypes(remainingTargets, assignmentConstraints, typeFactory); - - equalitiesResult.mergeSubordinate(subtypesResult); - return equalitiesResult; - } - - /** - * Step 4. Combine the results from using the Supertype constraints the Equality constraints from - * the assignment context. - */ - private InferenceResult combineSupertypeAndAssignmentResults( - Set targets, - AnnotatedTypeFactory typeFactory, - InferenceResult equalityResult, - InferenceResult supertypeResult) { - TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy(); - - InferenceResult result = new InferenceResult(); - for (TypeVariable target : targets) { - InferredValue equalityInferred = equalityResult.get(target); - InferredValue supertypeInferred = supertypeResult.get(target); - - InferredValue outputValue; - if (equalityInferred instanceof InferredType) { - - if (supertypeInferred instanceof InferredType) { - AnnotatedTypeMirror superATM = ((InferredType) supertypeInferred).type; - AnnotatedTypeMirror equalityATM = ((InferredType) equalityInferred).type; - if (TypesUtils.isErasedSubtype( - equalityATM.getUnderlyingType(), - superATM.getUnderlyingType(), - typeFactory.getChecker().getTypeUtils())) { - // If the underlying type of equalityATM is a subtype of the underlying - // type of superATM, then the call to isSubtype below will issue an error. - // So call asSuper so that the isSubtype call below works correctly. - equalityATM = AnnotatedTypes.asSuper(typeFactory, equalityATM, superATM); - } - if (typeHierarchy.isSubtype(superATM, equalityATM)) { - outputValue = equalityInferred; - } else { - outputValue = supertypeInferred; - } - - } else { - outputValue = equalityInferred; - } - } else { - if (supertypeInferred != null) { - outputValue = supertypeInferred; - } else { - outputValue = null; - } - } - - if (outputValue != null) { - result.put(target, outputValue); - } - } - - return result; - } - - /** - * For any types we have not inferred, use a wildcard with the bounds from the original type - * parameter. - */ - private void handleUninferredTypeVariables( - AnnotatedTypeFactory typeFactory, - AnnotatedExecutableType methodType, - Set targets, - Map inferredArgs) { - - for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) { - TypeVariable typeVar = atv.getUnderlyingType(); - if (targets.contains((TypeVariable) TypeAnnotationUtils.unannotatedType(typeVar))) { - AnnotatedTypeMirror inferredType = inferredArgs.get(typeVar); - if (inferredType == null) { - AnnotatedTypeMirror dummy = typeFactory.getUninferredWildcardType(atv); - inferredArgs.put(atv.getUnderlyingType(), dummy); - } else { - typeFactory.addDefaultAnnotations(inferredType); - } - } - } - } - - /** - * Given a set of AFConstraints, remove all constraints that are not relevant to inference and - * return a set of AFConstraints in which the F is a use of one of the type parameters to infer. - */ - protected void reduceAfConstraints( - AnnotatedTypeFactory typeFactory, - Set outgoing, - Queue toProcess, - Set targets) { - - Set visited = new HashSet<>(); - - List reducers = - Arrays.asList( - new A2FReducer(typeFactory), new F2AReducer(typeFactory), new FIsAReducer(typeFactory)); - - Set newConstraints = new HashSet<>(10); - while (!toProcess.isEmpty()) { - newConstraints.clear(); - AFConstraint constraint = toProcess.remove(); - - if (!visited.contains(constraint)) { - if (constraint.isIrreducible(targets)) { - outgoing.add(constraint); - } else { - - Iterator reducerIterator = reducers.iterator(); - boolean handled = false; - while (!handled && reducerIterator.hasNext()) { - handled = reducerIterator.next().reduce(constraint, newConstraints); - } - - if (!handled) { - throw new BugInCF("Unhandled constraint type: " + constraint); - } - - toProcess.addAll(newConstraints); - } - visited.add(constraint); - } - } - } - - /** Convert AFConstraints to TUConstraints. */ - protected Set afToTuConstraints( - Set afConstraints, Set targets) { - Set outgoing = new LinkedHashSet<>(); - for (AFConstraint afConstraint : afConstraints) { - if (!afConstraint.isIrreducible(targets)) { - throw new BugInCF( - StringsPlume.joinLines( - "All afConstraints should be irreducible before conversion.", - "afConstraints=[ " + StringsPlume.join(", ", afConstraints) + " ]", - "targets=[ " + StringsPlume.join(", ", targets) + "]")); - } - - outgoing.add(afConstraint.toTUConstraint()); - } - - return outgoing; - } - - /** - * Declarations of the form: {@code } implies a TUConstraint of {@code B <: A}. - * Add these to the constraint list. - */ - public void addConstraintsBetweenTargets( - Set constraints, - Set targets, - boolean asSubtype, - AnnotatedTypeFactory typeFactory) { - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - List targetList = new ArrayList<>(targets); - - Map paramDeclarations = new HashMap<>(); - - for (int i = 0; i < targetList.size(); i++) { - TypeVariable earlierTarget = targetList.get(i); - - for (int j = i + 1; j < targetList.size(); j++) { - TypeVariable laterTarget = targetList.get(j); - if (types.isSameType(earlierTarget.getUpperBound(), laterTarget)) { - AnnotatedTypeVariable headDecl = - addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); - AnnotatedTypeVariable nextDecl = - addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations); - - if (asSubtype) { - constraints.add(new TSubU(headDecl, nextDecl)); - - } else { - constraints.add(new TSuperU(nextDecl, headDecl)); - } - } else if (types.isSameType(laterTarget.getUpperBound(), earlierTarget)) { - AnnotatedTypeVariable headDecl = - addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); - AnnotatedTypeVariable nextDecl = - addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations); - - if (asSubtype) { - constraints.add(new TSubU(nextDecl, headDecl)); - - } else { - constraints.add(new TSuperU(headDecl, nextDecl)); - } - } - } - } - } - - public AnnotatedTypeVariable addOrGetDeclarations( - TypeVariable target, - AnnotatedTypeFactory typeFactory, - Map declarations) { - AnnotatedTypeVariable atv = - declarations.computeIfAbsent( - target, __ -> (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement())); - return atv; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java deleted file mode 100644 index a4ea90f7ea6..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/GlbUtil.java +++ /dev/null @@ -1,202 +0,0 @@ -package org.checkerframework.framework.util.typeinference; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.TypeHierarchy; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.TypesUtils; - -/** A class used to determine the greatest lower bounds for a set of AnnotatedTypeMirrors. */ -public class GlbUtil { - - /** - * Returns the greatest lower bound of the given {@code TypeMirror}s. If any of the type mirrors - * are incomparable, Returns an AnnotatedNullType that contains the greatest lower bounds of the - * primary annotations of typeMirrors. - * - *

          Note: This method can be improved for wildcards and type variables. - * - * @param typeMirrors the types to glb - * @param atypeFactory the type factory - * @return the greatest lower bound of typeMirrors - */ - public static @Nullable AnnotatedTypeMirror glbAll( - Map typeMirrors, - AnnotatedTypeFactory atypeFactory) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - if (typeMirrors.isEmpty()) { - return null; - } - - // determine the greatest lower bounds for the primary annotations - AnnotationMirrorMap glbPrimaries = new AnnotationMirrorMap<>(); - for (Map.Entry tmEntry : typeMirrors.entrySet()) { - AnnotationMirrorSet typeAnnoHierarchies = tmEntry.getValue(); - AnnotatedTypeMirror type = tmEntry.getKey(); - - for (AnnotationMirror top : typeAnnoHierarchies) { - // TODO: When all of the typeMirrors are either wildcards or type variables than the - // greatest lower bound should involve handling the bounds individually rather than - // using the effective annotation. We are doing this for expediency. - AnnotationMirror typeAnno = type.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror currentAnno = glbPrimaries.get(top); - if (typeAnno != null && currentAnno != null) { - glbPrimaries.put( - top, qualHierarchy.greatestLowerBoundQualifiersOnly(currentAnno, typeAnno)); - } else if (typeAnno != null) { - glbPrimaries.put(top, typeAnno); - } - } - } - - List glbTypes = new ArrayList<>(); - TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy(); - - // create a copy of all of the types and apply the glb primary annotation - AnnotationMirrorSet values = new AnnotationMirrorSet(glbPrimaries.values()); - for (AnnotatedTypeMirror atm : typeMirrors.keySet()) { - if (atm.getKind() != TypeKind.TYPEVAR - || !typeHierarchy.isSubtypeShallowEffective(atm, values)) { - AnnotatedTypeMirror copy = atm.deepCopy(); - copy.replaceAnnotations(values); - glbTypes.add(copy); - - } else { - // if the annotations came from the upper bound of this typevar - // we do NOT want to place them as primary annotations (and destroy the - // type vars lower bound) - glbTypes.add(atm); - } - } - - // sort placing supertypes first - sortForGlb(glbTypes, atypeFactory); - - // find the lowest type in the list that is not an AnnotatedNullType - AnnotatedTypeMirror glbType = glbTypes.get(0); - int index = 1; - while (index < glbTypes.size()) { - // avoid using null if possible, since constraints form the lower bound will often have - // NULL types - if (glbType.getKind() != TypeKind.NULL) { - glbType = glbTypes.get(index); - } - index += 1; - } - - // if the lowest type is a subtype of all glbTypes then it is the GLB, otherwise there are - // two types in glbTypes that are incomparable and we need to use bottom (AnnotatedNullType) - boolean incomparable = false; - for (AnnotatedTypeMirror type : glbTypes) { - if (!incomparable - && type.getKind() != TypeKind.NULL - && (!TypesUtils.isErasedSubtype( - glbType.getUnderlyingType(), - type.getUnderlyingType(), - atypeFactory.getChecker().getTypeUtils()) - || !typeHierarchy.isSubtype(glbType, type))) { - incomparable = true; - } - } - - // we had two incomparable types in glbTypes - if (incomparable) { - return createBottom(atypeFactory, glbType.getEffectiveAnnotations()); - } - - return glbType; - } - - /** Returns an AnnotatedNullType with the given annotations as primaries. */ - private static AnnotatedNullType createBottom( - AnnotatedTypeFactory atypeFactory, Set annos) { - return atypeFactory.getAnnotatedNullType(annos); - } - - /** - * Sort the list of type mirrors, placing supertypes first and subtypes last. - * - *

          E.g. the list: {@code ArrayList, List, AbstractList} becomes: {@code - * List, AbstractList, ArrayList} - * - * @param typeMirrors the list to sort in place - * @param atypeFactory the type factory - */ - public static void sortForGlb( - List typeMirrors, AnnotatedTypeFactory atypeFactory) { - Collections.sort(typeMirrors, new GlbSortComparator(atypeFactory)); - } - - /** A comparator for {@link #sortForGlb}. */ - private static final class GlbSortComparator implements Comparator { - - /** The qualifier hierarchy. */ - private final QualifierHierarchy qualHierarchy; - - /** The type utiliites. */ - private final Types types; - - /** - * Creates a new GlbSortComparator. - * - * @param atypeFactory the type factory - */ - public GlbSortComparator(AnnotatedTypeFactory atypeFactory) { - qualHierarchy = atypeFactory.getQualifierHierarchy(); - types = atypeFactory.getProcessingEnv().getTypeUtils(); - } - - @Override - public int compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - TypeMirror underlyingType1 = type1.getUnderlyingType(); - TypeMirror underlyingType2 = type2.getUnderlyingType(); - - if (types.isSameType(underlyingType1, underlyingType2)) { - return compareAnnotations(type1, type2); - } else if (types.isSubtype(underlyingType1, underlyingType2)) { - return 1; - } else { - // if they're incomparable or type2 is a subtype of type1 - return -1; - } - } - - /** - * Returns -1, 0, or 1 depending on whether anno1 is a supertype, same as, or a subtype of - * annos2. - * - * @param type1 a type whose annotations to compare - * @param type2 a type whose annotations to compare - * @return the comparison of type1 and type2 - */ - private int compareAnnotations(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - AnnotationMirrorSet annos1 = type1.getAnnotations(); - AnnotationMirrorSet annos2 = type2.getAnnotations(); - if (AnnotationUtils.areSame(annos1, annos2)) { - return 0; - } - TypeMirror tm1 = type1.getUnderlyingType(); - TypeMirror tm2 = type2.getUnderlyingType(); - if (qualHierarchy.isSubtypeShallow(annos1, tm1, annos2, tm2)) { - return 1; - } else { - return -1; - } - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java deleted file mode 100644 index ba2c560b171..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java +++ /dev/null @@ -1,722 +0,0 @@ -package org.checkerframework.framework.util.typeinference; - -import com.sun.source.tree.AssignmentTree; -import com.sun.source.tree.CompoundAssignmentTree; -import com.sun.source.tree.ConditionalExpressionTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.LambdaExpressionTree; -import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.MethodInvocationTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.NewArrayTree; -import com.sun.source.tree.NewClassTree; -import com.sun.source.tree.ReturnTree; -import com.sun.source.tree.Tree; -import com.sun.source.tree.VariableTree; -import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ErrorType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.IntersectionType; -import javax.lang.model.type.NoType; -import javax.lang.model.type.NullType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.TypeVisitor; -import javax.lang.model.type.UnionType; -import javax.lang.model.util.Types; -import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.TypeVariableSubstitutor; -import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreePathUtil; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; - -/** Miscellaneous utilities to help in type argument inference. */ -public class TypeArgInferenceUtil { - - /** - * Returns a list of boxed annotated types corresponding to the arguments in {@code - * methodInvocation}. - * - * @param methodInvocation {@link MethodInvocationTree} or {@link NewClassTree} - * @param typeFactory type factory - * @return a list of boxed annotated types corresponding to the arguments in {@code - * methodInvocation}. - */ - public static List getArgumentTypes( - ExpressionTree methodInvocation, AnnotatedTypeFactory typeFactory) { - final List argTrees; - - if (methodInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { - argTrees = ((MethodInvocationTree) methodInvocation).getArguments(); - - } else if (methodInvocation.getKind() == Tree.Kind.NEW_CLASS) { - argTrees = ((NewClassTree) methodInvocation).getArguments(); - - } else { - throw new BugInCF( - "TypeArgumentInference.relationsFromMethodArguments:%n" - + "couldn't determine arguments from tree: %s", - methodInvocation); - } - - List argTypes = - CollectionsPlume.mapList( - (Tree arg) -> { - AnnotatedTypeMirror argType = typeFactory.getAnnotatedType(arg); - if (TypesUtils.isPrimitive(argType.getUnderlyingType())) { - return typeFactory.getBoxedType((AnnotatedPrimitiveType) argType); - } else { - return argType; - } - }, - argTrees); - return argTypes; - } - - /** - * Given a set of type variables for which we are inferring a type, returns true if type is a use - * of a type variable in the list of targetTypeVars. - */ - public static boolean isATarget(AnnotatedTypeMirror type, Set targetTypeVars) { - return type.getKind() == TypeKind.TYPEVAR - && targetTypeVars.contains( - (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType())); - } - - /** - * Given an AnnotatedExecutableType return a set of type variables that represents the generic - * type parameters of that method. - */ - public static Set methodTypeToTargets(AnnotatedExecutableType methodType) { - List annotatedTypeVars = methodType.getTypeVariables(); - Set targets = new LinkedHashSet<>(annotatedTypeVars.size()); - - for (AnnotatedTypeVariable atv : annotatedTypeVars) { - targets.add((TypeVariable) TypeAnnotationUtils.unannotatedType(atv.getUnderlyingType())); - } - - return targets; - } - - /** - * Returns the annotated type that the leaf of path is assigned to, if it is within an assignment - * context. Returns the annotated type that the method invocation at the leaf is assigned to. If - * the result is a primitive, return the boxed version. - * - * @param atypeFactory the type factory, for looking up types - * @param path the path whole leaf to look up a type for - * @return the type of path's leaf - */ - @SuppressWarnings("interning:not.interned") // AST node comparisons - public static @Nullable AnnotatedTypeMirror assignedTo( - AnnotatedTypeFactory atypeFactory, TreePath path) { - Tree assignmentContext = TreePathUtil.getAssignmentContext(path); - AnnotatedTypeMirror res; - if (assignmentContext == null) { - res = null; - } else if (assignmentContext instanceof AssignmentTree) { - ExpressionTree variable = ((AssignmentTree) assignmentContext).getVariable(); - res = atypeFactory.getAnnotatedType(variable); - } else if (assignmentContext instanceof CompoundAssignmentTree) { - ExpressionTree variable = ((CompoundAssignmentTree) assignmentContext).getVariable(); - res = atypeFactory.getAnnotatedType(variable); - } else if (assignmentContext instanceof MethodInvocationTree) { - MethodInvocationTree methodInvocation = (MethodInvocationTree) assignmentContext; - // TODO move to getAssignmentContext - if (methodInvocation.getMethodSelect() instanceof MemberSelectTree - && ((MemberSelectTree) methodInvocation.getMethodSelect()).getExpression() - == path.getLeaf()) { - return null; - } - ExecutableElement methodElt = TreeUtils.elementFromUse(methodInvocation); - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(methodInvocation); - if (TreeUtils.isSuperConstructorCall(methodInvocation)) { - receiver = atypeFactory.getSelfType(methodInvocation); - } - res = - assignedToExecutable( - atypeFactory, path, methodElt, receiver, methodInvocation.getArguments()); - } else if (assignmentContext instanceof NewArrayTree) { - // TODO: I left the previous implementation below, it definitely caused infinite loops - // TODO: if you called it from places like the TreeAnnotator. - res = null; - - // TODO: This may cause infinite loop - // AnnotatedTypeMirror type = - // atypeFactory.getAnnotatedType((NewArrayTree)assignmentContext); - // type = AnnotatedTypes.innerMostType(type); - // return type; - - } else if (assignmentContext instanceof NewClassTree) { - // This need to be basically like MethodTree - NewClassTree newClassTree = (NewClassTree) assignmentContext; - if (newClassTree.getEnclosingExpression() instanceof NewClassTree - && (newClassTree.getEnclosingExpression() == path.getLeaf())) { - return null; - } - ExecutableElement constructorElt = TreeUtils.elementFromUse(newClassTree); - // TODO: This call should be removed once #979 is implemented. - // Change this to atypeFactory.getAnnotatedType(newClassTree) causes infinite recursion - // in the InitializationAnnotatedTypeFactory.CommitmentTreeAnnotator.visitNewClass. - @SuppressWarnings("deprecation") - AnnotatedTypeMirror receiver = atypeFactory.fromNewClass(newClassTree); - res = - assignedToExecutable( - atypeFactory, path, constructorElt, receiver, newClassTree.getArguments()); - } else if (assignmentContext instanceof ReturnTree) { - HashSet kinds = - new HashSet<>(Arrays.asList(Tree.Kind.LAMBDA_EXPRESSION, Tree.Kind.METHOD)); - Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds); - - if (enclosing.getKind() == Tree.Kind.METHOD) { - res = atypeFactory.getAnnotatedType((MethodTree) enclosing).getReturnType(); - } else { - AnnotatedExecutableType fninf = - atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); - res = fninf.getReturnType(); - } - - } else if (assignmentContext instanceof VariableTree) { - res = assignedToVariable(atypeFactory, (VariableTree) assignmentContext); - } else { - throw new BugInCF("AnnotatedTypes.assignedTo: shouldn't be here"); - } - - if (res != null && TypesUtils.isPrimitive(res.getUnderlyingType())) { - return atypeFactory.getBoxedType((AnnotatedPrimitiveType) res); - } else { - return res; - } - } - - private static @Nullable AnnotatedTypeMirror assignedToExecutable( - AnnotatedTypeFactory atypeFactory, - TreePath path, - ExecutableElement methodElt, - AnnotatedTypeMirror receiver, - List arguments) { - AnnotatedExecutableType method = - AnnotatedTypes.asMemberOf( - atypeFactory.getChecker().getTypeUtils(), atypeFactory, receiver, methodElt); - int treeIndex = -1; - for (int i = 0; i < arguments.size(); ++i) { - ExpressionTree argumentTree = arguments.get(i); - if (isArgument(path, argumentTree)) { - treeIndex = i; - break; - } - } - final AnnotatedTypeMirror paramType; - if (treeIndex == -1) { - // The tree wasn't found as an argument, so it has to be the receiver. - // This can happen for inner class constructors that take an outer class argument. - paramType = method.getReceiverType(); - } else if (treeIndex + 1 >= method.getParameterTypes().size() && methodElt.isVarArgs()) { - AnnotatedTypeMirror varArgsType = - method.getParameterTypes().get(method.getParameterTypes().size() - 1); - paramType = ((AnnotatedArrayType) varArgsType).getComponentType(); - } else { - paramType = method.getParameterTypes().get(treeIndex); - } - - // Examples like this: - // T outMethod() - // void inMethod(U u); - // inMethod(outMethod()) - // would require solving the constraints for both type argument inferences simultaneously - if (paramType == null || containsUninferredTypeParameter(paramType, method)) { - return null; - } - - return paramType; - } - - /** - * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional - * expression, isArgument is called recursively on the true and false expressions. - * - * @param path the path whose leaf to test - * @param argumentTree the expression that might be path's leaf - * @return true if {@code argumentTree} is the leaf of {@code path} - */ - private static boolean isArgument(TreePath path, @FindDistinct ExpressionTree argumentTree) { - argumentTree = TreeUtils.withoutParens(argumentTree); - if (argumentTree == path.getLeaf()) { - return true; - } else if (argumentTree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { - ConditionalExpressionTree conditionalExpressionTree = - (ConditionalExpressionTree) argumentTree; - return isArgument(path, conditionalExpressionTree.getTrueExpression()) - || isArgument(path, conditionalExpressionTree.getFalseExpression()); - } - return false; - } - - /** - * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree). - * Rationale: - * - *

          For example: - * - *

          {@code
          -   *  S bar () {...}
          -   *
          -   *  T foo(T p) {
          -   *     T local = bar();
          -   *     return local;
          -   *   }
          -   * }
          - * - * During type argument inference of {@code bar}, the assignment context is {@code local}. If the - * local variable default is used, then the type of assignment context type is {@code @Nullable T} - * and the type argument inferred for {@code bar()} is {@code @Nullable T}. And an incompatible - * types in return error is issued. - * - *

          If instead, the local variable default is not applied, then the assignment context type is - * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) and - * the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of {@code - * local} is refined to {@code T} and the return is legal. - * - *

          If the assignment context type was a declared type, for example: - * - *

          {@code
          -   *  S bar () {...}
          -   * Object foo() {
          -   *     Object local = bar();
          -   *     return local;
          -   * }
          -   * }
          - * - * The local variable default must be used or else the assignment context type is missing an - * annotation. So, an incompatible types in return error is issued in the above code. We could - * improve type argument inference in this case and by using the lower bound of {@code S} instead - * of the local variable default. - * - * @param atypeFactory the type factory - * @param assignmentContext VariableTree - * @return AnnotatedTypeMirror of Assignment context - */ - public static AnnotatedTypeMirror assignedToVariable( - AnnotatedTypeFactory atypeFactory, VariableTree assignmentContext) { - if (TreeUtils.isVariableTreeDeclaredUsingVar(assignmentContext)) { - return null; - } - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory gatf = - ((GenericAnnotatedTypeFactory) atypeFactory); - return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext); - } else { - return atypeFactory.getAnnotatedType(assignmentContext); - } - } - - /** - * Returns true if the type contains a use of a type variable from methodType. - * - * @return true if the type contains a use of a type variable from methodType - */ - private static boolean containsUninferredTypeParameter( - AnnotatedTypeMirror type, AnnotatedExecutableType methodType) { - List annotatedTypeVars = methodType.getTypeVariables(); - List typeVars = - CollectionsPlume.mapList( - (AnnotatedTypeVariable annotatedTypeVar) -> - (TypeVariable) - TypeAnnotationUtils.unannotatedType(annotatedTypeVar.getUnderlyingType()), - annotatedTypeVars); - - return containsTypeParameter(type, typeVars); - } - - /** - * Returns true if {@code type} contains a use of a type variable in {@code typeVariables}. - * - * @param type type to search - * @param typeVariables collection of type variables - * @return true if {@code type} contains a use of a type variable in {@code typeVariables} - */ - public static boolean containsTypeParameter( - AnnotatedTypeMirror type, Collection typeVariables) { - // note NULL values creep in because the underlying visitor uses them in various places - Boolean result = type.accept(new TypeVariableFinder(), typeVariables); - return result != null && result; - } - - /** - * Take a set of annotations and separate them into a mapping of {@code hierarchy top => - * annotations in hierarchy}. - */ - public static AnnotationMirrorMap createHierarchyMap( - AnnotationMirrorSet annos, QualifierHierarchy qualHierarchy) { - AnnotationMirrorMap result = new AnnotationMirrorMap<>(); - - for (AnnotationMirror anno : annos) { - result.put(qualHierarchy.getTopAnnotation(anno), anno); - } - - return result; - } - - /** - * Throws an exception if the type is an uninferred type argument. - * - *

          The error will be caught in DefaultTypeArgumentInference#infer and inference will be - * aborted, but type-checking will continue. - */ - public static void checkForUninferredTypes(AnnotatedTypeMirror type) { - if (type.getKind() != TypeKind.WILDCARD) { - return; - } - if (((AnnotatedWildcardType) type).isUninferredTypeArgument()) { - throw new BugInCF("Can't make a constraint that includes an uninferred type argument."); - } - } - - /** - * Used to detect if the visited type contains one of the type variables in the typeVars - * parameter. - */ - private static class TypeVariableFinder - extends AnnotatedTypeScanner> { - - /** Create TypeVariableFinder. */ - protected TypeVariableFinder() { - super(Boolean::logicalOr, false); - } - - @Override - public Boolean visitTypeVariable( - AnnotatedTypeVariable type, Collection typeVars) { - if (typeVars.contains( - (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()))) { - return true; - } else { - return super.visitTypeVariable(type, typeVars); - } - } - } - - /* - * Various TypeArgumentInference steps require substituting types for type arguments that have already been - * inferred into constraints that are used infer other type arguments. Substituter is used in - * the utility methods to do this. - */ - private static final TypeVariableSubstitutor substitutor = new TypeVariableSubstitutor(); - - // Substituter requires an input map that the substitute methods build. We just reuse the same - // map rather than recreate it each time. - private static final Map substituteMap = new HashMap<>(5); - - /** - * Replace all uses of typeVariable with substitution in a copy of toModify using the normal - * substitution rules. Return the copy - * - * @see TypeVariableSubstitutor - */ - public static AnnotatedTypeMirror substitute( - TypeVariable typeVariable, AnnotatedTypeMirror substitution, AnnotatedTypeMirror toModify) { - substituteMap.clear(); - substituteMap.put(typeVariable, substitution.deepCopy()); - - AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); - substitutor.substitute(substituteMap, toModifyCopy); - return toModifyCopy; - } - - /** - * Create a copy of toModify. In the copy, for each pair {@code typeVariable => annotated type} - * replace uses of typeVariable with the corresponding annotated type using normal substitution - * rules (@see TypeVariableSubstitutor). Return the copy. - */ - public static AnnotatedTypeMirror substitute( - Map substitutions, AnnotatedTypeMirror toModify) { - final AnnotatedTypeMirror substitution; - if (toModify.getKind() == TypeKind.TYPEVAR) { - substitution = - substitutions.get( - (TypeVariable) TypeAnnotationUtils.unannotatedType(toModify.getUnderlyingType())); - } else { - substitution = null; - } - if (substitution != null) { - return substitution.deepCopy(); - } - - AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); - substitutor.substitute(substitutions, toModifyCopy); - return toModifyCopy; - } - - /** - * Successively calls least upper bound on the elements of types. Unlike leastUpperBound, this - * method will box primitives if necessary - */ - public static AnnotatedTypeMirror leastUpperBound( - AnnotatedTypeFactory typeFactory, Iterable types) { - Iterator typesIter = types.iterator(); - if (!typesIter.hasNext()) { - throw new BugInCF("Calling LUB on empty list"); - } - AnnotatedTypeMirror lubType = typesIter.next(); - AnnotatedTypeMirror nextType = null; - while (typesIter.hasNext()) { - nextType = typesIter.next(); - - if (lubType.getKind().isPrimitive()) { - if (!nextType.getKind().isPrimitive()) { - lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType); - } - } else if (nextType.getKind().isPrimitive()) { - if (!lubType.getKind().isPrimitive()) { - nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType); - } - } - lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType); - } - - return lubType; - } - - /** - * If the type arguments computed by DefaultTypeArgumentInference don't match the return type - * mirror of {@code invocation}, then replace those type arguments with an uninferred wildcard. - */ - protected static Map correctResults( - Map result, - ExpressionTree invocation, - ExecutableType methodType, - AnnotatedTypeFactory factory) { - ProcessingEnvironment env = factory.getProcessingEnv(); - Types types = env.getTypeUtils(); - Map fromReturn = - getMappingFromReturnType(invocation, methodType, env); - for (Map.Entry entry : - // result is side-effected by this loop, so iterate over a copy - new ArrayList<>(result.entrySet())) { - TypeVariable typeVariable = entry.getKey(); - if (!fromReturn.containsKey(typeVariable)) { - continue; - } - TypeMirror correctType = fromReturn.get(typeVariable); - TypeMirror inferredType = entry.getValue().getUnderlyingType(); - if (types.isSameType(types.erasure(correctType), types.erasure(inferredType))) { - if (areSameCapture(correctType, inferredType)) { - continue; - } - } - if (!types.isSameType(correctType, inferredType)) { - AnnotatedWildcardType wt = - factory.getUninferredWildcardType( - (AnnotatedTypeVariable) - AnnotatedTypeMirror.createType(typeVariable, factory, false)); - wt.replaceAnnotations(entry.getValue().getAnnotations()); - result.put(typeVariable, wt); - } - } - return result; - } - - /** - * Returns true if actual and inferred are captures of the same wildcard or declared type. - * - * @param actual the actual type - * @param inferred the inferred type - * @return true if actual and inferred are captures of the same wildcard or declared type - */ - private static boolean areSameCapture(TypeMirror actual, TypeMirror inferred) { - if (TypesUtils.isCapturedTypeVariable(actual) && TypesUtils.isCapturedTypeVariable(inferred)) { - return true; - } else if (TypesUtils.isCapturedTypeVariable(actual) - && inferred.getKind() == TypeKind.WILDCARD) { - return true; - } else if (actual.getKind() == TypeKind.DECLARED && inferred.getKind() == TypeKind.DECLARED) { - DeclaredType actualDT = (DeclaredType) actual; - DeclaredType inferredDT = (DeclaredType) inferred; - if (actualDT.getTypeArguments().size() == inferredDT.getTypeArguments().size()) { - for (int i = 0; i < actualDT.getTypeArguments().size(); i++) { - if (!areSameCapture( - actualDT.getTypeArguments().get(i), inferredDT.getTypeArguments().get(i))) { - return false; - } - } - return true; - } - } - return false; - } - - /** - * Returns a mapping of type variable to type argument computed using the type of {@code - * methodInvocationTree} and the return type of {@code methodType}. - */ - private static Map getMappingFromReturnType( - ExpressionTree methodInvocationTree, ExecutableType methodType, ProcessingEnvironment env) { - TypeMirror methodCallType = TreeUtils.typeOf(methodInvocationTree); - Types types = env.getTypeUtils(); - GetMapping mapping = new GetMapping(methodType.getTypeVariables(), types); - mapping.visit(methodType.getReturnType(), methodCallType); - return mapping.subs; - } - - /** - * Helper class for {@link #getMappingFromReturnType(ExpressionTree, ExecutableType, - * ProcessingEnvironment)} - */ - private static class GetMapping implements TypeVisitor { - - final Map subs = new HashMap<>(); - final List typeVariables; - final Types types; - - private GetMapping(List typeVariables, Types types) { - this.typeVariables = typeVariables; - this.types = types; - } - - @Override - public Void visit(TypeMirror t, TypeMirror mirror) { - if (t == null || mirror == null) { - return null; - } - return t.accept(this, mirror); - } - - @Override - public Void visit(TypeMirror t) { - return null; - } - - @Override - public Void visitPrimitive(PrimitiveType t, TypeMirror mirror) { - return null; - } - - @Override - public Void visitNull(NullType t, TypeMirror mirror) { - return null; - } - - @Override - public Void visitArray(ArrayType t, TypeMirror mirror) { - assert mirror.getKind() == TypeKind.ARRAY : mirror; - return visit(t.getComponentType(), ((ArrayType) mirror).getComponentType()); - } - - @Override - public Void visitDeclared(DeclaredType t, TypeMirror mirror) { - assert mirror.getKind() == TypeKind.DECLARED : mirror; - DeclaredType param = (DeclaredType) mirror; - if (types.isSubtype(mirror, param)) { - // param = (DeclaredType) types.asSuper((Type) mirror, ((Type) - // param).asElement()); - } - if (t.getTypeArguments().size() == param.getTypeArguments().size()) { - for (int i = 0; i < t.getTypeArguments().size(); i++) { - visit(t.getTypeArguments().get(i), param.getTypeArguments().get(i)); - } - } - return null; - } - - @Override - public Void visitError(ErrorType t, TypeMirror mirror) { - return null; - } - - @Override - public Void visitTypeVariable(TypeVariable t, TypeMirror mirror) { - if (typeVariables.contains(t)) { - subs.put(t, mirror); - } else if (mirror.getKind() == TypeKind.TYPEVAR) { - TypeVariable param = (TypeVariable) mirror; - visit(t.getUpperBound(), param.getUpperBound()); - visit(t.getLowerBound(), param.getLowerBound()); - } - // else it's not a method type variable - return null; - } - - @Override - public Void visitWildcard(javax.lang.model.type.WildcardType t, TypeMirror mirror) { - if (mirror.getKind() == TypeKind.WILDCARD) { - javax.lang.model.type.WildcardType param = (javax.lang.model.type.WildcardType) mirror; - visit(t.getExtendsBound(), param.getExtendsBound()); - visit(t.getSuperBound(), param.getSuperBound()); - } else if (mirror.getKind() == TypeKind.TYPEVAR) { - TypeVariable param = (TypeVariable) mirror; - visit(t.getExtendsBound(), param.getUpperBound()); - visit(t.getSuperBound(), param.getLowerBound()); - } else { - assert false : mirror; - } - return null; - } - - @Override - public Void visitExecutable(ExecutableType t, TypeMirror mirror) { - return null; - } - - @Override - public Void visitNoType(NoType t, TypeMirror mirror) { - return null; - } - - @Override - public Void visitUnknown(TypeMirror t, TypeMirror mirror) { - return null; - } - - @Override - public Void visitUnion(UnionType t, TypeMirror mirror) { - return null; - } - - @Override - public Void visitIntersection(IntersectionType t, TypeMirror mirror) { - assert mirror.getKind() == TypeKind.INTERSECTION : mirror; - IntersectionType param = (IntersectionType) mirror; - assert t.getBounds().size() == param.getBounds().size(); - - for (int i = 0; i < t.getBounds().size(); i++) { - visit(t.getBounds().get(i), param.getBounds().get(i)); - } - - return null; - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java deleted file mode 100644 index d6c96de0c09..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2F.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; - -/** - * A constraint of the form: A 《 F or F 》 A - * - * @see org.checkerframework.framework.util.typeinference.constraint.AFConstraint - */ -public class A2F extends AFConstraint { - - /** Create a constraint with an argument less than a formal. */ - public A2F(AnnotatedTypeMirror argument, AnnotatedTypeMirror formalParameter) { - super(argument, formalParameter); - } - - @Override - public TUConstraint toTUConstraint() { - return new TSuperU((AnnotatedTypeVariable) formalParameter, argument, true); - } - - @Override - protected A2F construct(AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { - return new A2F(newArgument, newFormalParameter); - } - - @Override - public String toString() { - return "A2F( " + argument + " << " + formalParameter + " )"; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java deleted file mode 100644 index 25ef25ee601..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/A2FReducer.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import java.util.Set; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; - -/** - * A2FReducer takes an A2F constraint that is not irreducible (@see AFConstraint.isIrreducible) and - * reduces it by one step. The resulting constraint may still be reducible. - * - *

          Generally reductions should map to corresponding rules in - * https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.12.2.7 - */ -public class A2FReducer implements AFReducer { - - protected final A2FReducingVisitor visitor; - - public A2FReducer(AnnotatedTypeFactory typeFactory) { - this.visitor = new A2FReducingVisitor(typeFactory); - } - - @Override - public boolean reduce(AFConstraint constraint, Set newConstraints) { - if (constraint instanceof A2F) { - A2F a2f = (A2F) constraint; - visitor.visit(a2f.argument, a2f.formalParameter, newConstraints); - return true; - - } else { - return false; - } - } - - /** - * Given an A2F constraint of the form: A2F( typeFromMethodArgument, typeFromFormalParameter ) - * - *

          A2FReducingVisitor visits the constraint as follows: visit ( typeFromMethodArgument, - * typeFromFormalParameter, newConstraints ) - * - *

          The visit method will determine if the given constraint should either: - * - *

            - *
          • be discarded -- in this case, the visitor just returns - *
          • reduced to a simpler constraint or set of constraints -- in this case, the new constraint - * or set of constraints is added to newConstraints - *
          - */ - private static class A2FReducingVisitor extends AFReducingVisitor { - - public A2FReducingVisitor(AnnotatedTypeFactory typeFactory) { - super(A2F.class, typeFactory); - } - - @Override - public AFConstraint makeConstraint(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new A2F(subtype, supertype); - } - - @Override - public AFConstraint makeInverseConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new F2A(subtype, supertype); - } - - @Override - public AFConstraint makeEqualityConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new FIsA(supertype, subtype); - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java deleted file mode 100644 index 5165289d9de..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import javax.lang.model.type.TypeVariable; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; - -/** - * AFConstraint represent the initial constraints used to infer type arguments for method - * invocations and new class invocations. These constraints are simplified then converted to - * TUConstraints during type argument inference. - * - *

          Subclasses of AFConstraint represent the following types of - * constraints: - * - *

          A 《 F and F 》 A both imply that A is convertible to F. F 《 A and A 》 F both imply that F is - * convertible to A (this may happen due to wildcard/typevar bounds and recursive types) A = F - * implies that A is exactly F - * - *

          In the Checker Framework a type, A will be convertible to another type F, if - * AnnotatedTypes.asSuper will return a non-null value when A is passed as a subtype and F the - * supertype to the method. - * - *

          In Java type A will be convertible to another type F if there exists a conversion - * context/method that transforms the one type into the other. - * - *

          A 《 F and F 》 A are represented by class A2F F 《 A and A 》 F are represented by class F2A F = - * A is represented by class FIsA - */ -public abstract class AFConstraint { - /** The argument type. */ - public final AnnotatedTypeMirror argument; - - /** The formal parameter type. */ - public final AnnotatedTypeMirror formalParameter; - - /** Create a constraint for type arguments for a method invocation or new class invocation. */ - protected AFConstraint(AnnotatedTypeMirror argument, AnnotatedTypeMirror formalParameter) { - this.argument = argument; - this.formalParameter = formalParameter; - TypeArgInferenceUtil.checkForUninferredTypes(argument); - } - - /** - * Returns true if this constraint can't be broken up into other constraints or further - * simplified. In general, if either argument or formal parameter is a use of the type parameters - * we are inferring over then the constraint cannot be reduced further. - * - * @param targets the type parameters whose arguments we are trying to solve for - * @return true if this constraint can't be broken up into other constraints or further simplified - */ - public boolean isIrreducible(Set targets) { - return TypeArgInferenceUtil.isATarget(argument, targets) - || TypeArgInferenceUtil.isATarget(formalParameter, targets); - } - - @Override - public boolean equals(@Nullable Object thatObject) { - if (this == thatObject) { - return true; - } // else - - if (thatObject == null || this.getClass() != thatObject.getClass()) { - return false; - } - - AFConstraint that = (AFConstraint) thatObject; - - return this.argument.equals(that.argument) && this.formalParameter.equals(that.formalParameter); - } - - @Override - public int hashCode() { - return Objects.hash(this.getClass(), formalParameter, argument); - } - - /** - * Once AFConstraints are irreducible it can be converted to a TU constraint, constraints between - * individual type parameters for which we are inferring an argument (T) and Java types (U). - * - * @return a TUConstraint that represents this AFConstraint - */ - public abstract TUConstraint toTUConstraint(); - - /** - * Given a partial solution to our type argument inference, replace any uses of type parameters - * that have been solved with their arguments. - * - *

          That is: Let S be a partial solution to our inference (i.e. we have inferred type arguments - * for some types) Let S be a map {@code (T0 => A0, T1 => A1, ..., TN => AN)} where Ti is a type - * parameter and Ai is its solved argument. For all uses of Ti in this constraint, replace them - * with Ai. - * - *

          For the mapping {@code (T0 => A0)}, the following constraint: {@code ArrayList << - * List} - * - *

          Becomes: {@code ArrayList << List} - * - *

          A constraint: {@code T0 << T1} - * - *

          Becomes: {@code A0 << T1} - * - * @param substitutions a mapping of target type parameter to the type argument to - * @return a new constraint that contains no use of the keys in substitutions - */ - public AFConstraint substitute(Map substitutions) { - AnnotatedTypeMirror newArgument = TypeArgInferenceUtil.substitute(substitutions, argument); - AnnotatedTypeMirror newFormalParameter = - TypeArgInferenceUtil.substitute(substitutions, formalParameter); - return construct(newArgument, newFormalParameter); - } - - /** Used to create a new constraint of the same subclass of AFConstraint. */ - protected abstract AFConstraint construct( - AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter); -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java deleted file mode 100644 index 5871c884027..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducer.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import java.util.Set; - -/** - * AFReducer implementations reduce AFConstraints into one or more "simpler" AFConstraints until - * these constraints are irreducible. - * - * @see - * org.checkerframework.framework.util.typeinference.constraint.AFConstraint#isIrreducible(java.util.Set) - *

          There is one AFReducer for each type of AFConstraint. - */ -public interface AFReducer { - - /** - * Determines if the input constraint should be handled by this reducer. If so: Reduces the - * constraint into one or more new constraints. Any new constraint that can still be reduced is - * placed in newConstraints. New irreducible constraints are placed in finish. Return true Return - * false (indicating that some other reducer needs to handle this constraint) If false is - * returned, the reducer should NOT place any constraints in newConstraints or finished - * - * @param constraint the constraint to reduce - * @param newConstraints the new constraints that may still need to be reduced - * @return true if the input constraint was handled by this reducer, false otherwise - */ - public boolean reduce(AFConstraint constraint, Set newConstraints); -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java deleted file mode 100644 index 895449f02d2..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFReducingVisitor.java +++ /dev/null @@ -1,555 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import java.util.List; -import java.util.Set; -import javax.lang.model.type.TypeKind; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.StringsPlume; - -/** - * Takes a single step in reducing a AFConstraint. - * - *

          The visit method will determine if the given constraint should either: - * - *

            - *
          • be discarded - in this case, the visitor just returns - *
          • reduced to a simpler constraint or set of constraints - in this case, the new constraint or - * set of constraints is added to newConstraints - *
          - * - * Sprinkled throughout this class are comments of the form: - * - *
          {@code
          - * // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
          - * }
          - * - * These are excerpts from the JLS, if you search for them you will find the corresponding JLS - * description of the case being covered. - */ -/*package-private*/ abstract class AFReducingVisitor - extends AbstractAtmComboVisitor> { - - protected final Class reducerType; - protected final AnnotatedTypeFactory atypeFactory; - - protected AFReducingVisitor( - Class reducerType, AnnotatedTypeFactory atypeFactory) { - this.reducerType = reducerType; - this.atypeFactory = atypeFactory; - } - - public abstract AFConstraint makeConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - - public abstract AFConstraint makeInverseConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - - public abstract AFConstraint makeEqualityConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - - public void addConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { - constraints.add(makeConstraint(subtype, supertype)); - } - - public void addInverseConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { - constraints.add(makeInverseConstraint(subtype, supertype)); - } - - public void addEqualityConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { - constraints.add(makeEqualityConstraint(subtype, supertype)); - } - - /** - * Called when we encounter an AF constraint on a type combination that we did not think is - * possible. This either implies that the type combination is possible, we accidentally created an - * invalid A2F or F2A Constraint, or we called the visit method on two AnnotatedTypeMirrors that - * do not appear together in a constraint. - */ - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Set constraints) { - return super.defaultErrorMessage(subtype, supertype, constraints) - + System.lineSeparator() - + " constraints = " - + StringsPlume.join(", ", constraints); - } - - // ------------------------------------------------------------------------ - // Arrays as arguments - // From the JLS: - // If F = U[], where the type U involves Tj, then if A is an array type V[], or a type - // variable with an upper bound that is an array type V[], where V is a reference type, this - // algorithm is applied recursively to the constraint V << U or U << V (depending on the - // constraint type). - - @Override - public Void visitArray_Array( - AnnotatedArrayType subtype, AnnotatedArrayType supertype, Set constraints) { - addConstraint(subtype.getComponentType(), supertype.getComponentType(), constraints); - return null; - } - - @Override - public Void visitArray_Declared( - AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Set constraints) { - return null; - } - - @Override - public Void visitArray_Null( - AnnotatedArrayType subtype, AnnotatedNullType supertype, Set constraints) { - return null; - } - - @Override - public Void visitArray_Wildcard( - AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Set constraints) { - visitWildcardAsSuperType(subtype, supertype, constraints); - return null; - } - - // Despite the above the comment at the beginning of the "array as arguments" section, a type - // variable cannot actually have an array type as its upper bound (e.g. is - // not allowed). - // So the only cases in which we visitArray_Typevar would be cases in which either: - // 1) Typevar is a type parameter for which we are inferring an argument, in which case the - // combination is already irreducible and we would not pass it to this class. - // 2) Typevar is an outer scope type variable, in which case it could NOT reference any of the - // type parameters for which we are inferring arguments and therefore will not lead to any - // meaningful AFConstraints. - // public void visitArray_Typevar - - // ------------------------------------------------------------------------ - // Declared as argument - - /** - * I believe there should be only 1 way to have a constraint of this form: {@code visit (Array, - * T [])} At this point, I don't think that's a valid argument for a formal parameter. If this - * occurs it is because of idiosyncrasies with the Checker Framework . We're going to skip this - * case for now. - */ - @Override - public Void visitDeclared_Array( - AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Set constraints) { - return null; - } - - // From the JLS Spec: - // If F has the form G<..., Yk-1,U, Yk+1, ...>, where U involves Tj - @Override - public Void visitDeclared_Declared( - AnnotatedDeclaredType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - if (subtype.isUnderlyingTypeRaw() || supertype.isUnderlyingTypeRaw()) { - // The error will be caught in {@link DefaultTypeArgumentInference#infer} and - // inference will be aborted, but type-checking will continue. - throw new BugInCF("Can't infer type arguments when raw types are involved."); - } - - if (!TypesUtils.isErasedSubtype( - subtype.getUnderlyingType(), - supertype.getUnderlyingType(), - atypeFactory.getChecker().getTypeUtils())) { - return null; - } - AnnotatedDeclaredType subAsSuper = - AnnotatedTypes.castedAsSuper(atypeFactory, subtype, supertype); - - List subTypeArgs = subAsSuper.getTypeArguments(); - List superTypeArgs = supertype.getTypeArguments(); - for (int i = 0; i < subTypeArgs.size(); i++) { - AnnotatedTypeMirror subTypeArg = subTypeArgs.get(i); - AnnotatedTypeMirror superTypeArg = superTypeArgs.get(i); - - // If F has the form G<..., Yk-1, ? extends U, Yk+1, ...>, where U involves Tj - // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj - // Since we always have both bounds in the checker framework we always compare both - if (superTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType superWc = (AnnotatedWildcardType) superTypeArg; - - if (subTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType subWc = (AnnotatedWildcardType) subTypeArg; - TypeArgInferenceUtil.checkForUninferredTypes(subWc); - addConstraint(subWc.getExtendsBound(), superWc.getExtendsBound(), constraints); - addInverseConstraint(superWc.getSuperBound(), subWc.getSuperBound(), constraints); - } else { - addConstraint(subTypeArg, superWc.getExtendsBound(), constraints); - addInverseConstraint(superWc.getSuperBound(), subTypeArg, constraints); - } - - } else { - // If F has the form G<..., Yk-1, U, Yk+1, ...>, where U is a type expression that - // involves Tj. - addEqualityConstraint(subTypeArg, superTypeArg, constraints); - } - } - - return null; - } - - @Override - public Void visitDeclared_Intersection( - AnnotatedDeclaredType subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - - // Note: AnnotatedIntersectionTypes cannot have a type variable as one of the direct - // parameters but a type variable may be the type subtype to an intersection bound > - for (AnnotatedTypeMirror intersectionBound : supertype.getBounds()) { - if (intersectionBound instanceof AnnotatedDeclaredType - && !((AnnotatedDeclaredType) intersectionBound).getTypeArguments().isEmpty()) { - addConstraint(subtype, supertype, constraints); - } - } - - return null; - } - - // Remember that NULL types can come from lower bounds - @Override - public Void visitDeclared_Null( - AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Set constraints) { - return null; - } - - // a primitive supertype provides us no information on the type of any type parameters for that - // method - @Override - public Void visitDeclared_Primitive( - AnnotatedDeclaredType subtype, - AnnotatedPrimitiveType supertype, - Set constraints) { - return null; - } - - @Override - public Void visitDeclared_Typevar( - AnnotatedDeclaredType subtype, - AnnotatedTypeVariable supertype, - Set constraints) { - // Note: We expect the A2F constraints where F == a targeted type supertype to already be - // removed. Therefore, supertype should NOT be a target. - addConstraint(subtype, supertype, constraints); - return null; - } - - @Override - public Void visitDeclared_Union( - AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } - - @Override - public Void visitDeclared_Wildcard( - AnnotatedDeclaredType subtype, - AnnotatedWildcardType supertype, - Set constraints) { - visitWildcardAsSuperType(subtype, supertype, constraints); - return null; - } - - // ------------------------------------------------------------------------ - // Intersection as subtype - @Override - public Void visitIntersection_Declared( - AnnotatedIntersectionType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - - // at least one of the intersection bound types must be convertible to the param type - AnnotatedDeclaredType subtypeAsParam = - AnnotatedTypes.castedAsSuper(atypeFactory, subtype, supertype); - if (subtypeAsParam != null && !subtypeAsParam.equals(supertype)) { - addConstraint(subtypeAsParam, supertype, constraints); - } - - return null; - } - - @Override - public Void visitIntersection_Intersection( - AnnotatedIntersectionType argument, - AnnotatedIntersectionType parameter, - Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } - - // provides no information as the AnnotatedNullType cannot refer to a type parameter - @Override - public Void visitIntersection_Null( - AnnotatedIntersectionType argument, - AnnotatedNullType parameter, - Set constraints) { - return null; - } - - // ------------------------------------------------------------------------ - // Null as argument - - /** - * NULL types only have primary annotations. A type parameter could only appear as a component of - * the parameter type and therefore has no relationship to these primary annotations - */ - @Override - public Void visitNull_Array( - AnnotatedNullType argument, AnnotatedArrayType parameter, Set constraints) { - return null; - } - - /** - * NULL types only have primary annotations. A type parameter could only appear as a component of - * the parameter type and therefore has no relationship to these primary annotations - */ - @Override - public Void visitNull_Declared( - AnnotatedNullType argument, AnnotatedDeclaredType parameter, Set constraints) { - return null; - } - - /** - * TODO: PERHAPS FOR ALL OF THESE WHERE WE COMPARE AGAINST THE LOWER BOUND, WE SHOULD INSTEAD - * COMPARE TODO: against the UPPER_BOUND with the LOWER_BOUND's PRIMARY ANNOTATIONS For captured - * types, the lower bound might be interesting so we compare against the lower bound but for most - * types the constraint added in this method is probably discarded in the next round of reduction - * (especially since we don't implement capture at the moment). - */ - @Override - public Void visitNull_Typevar( - AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Set constraints) { - // Note: We would expect that parameter is not one of the targets or else it would already - // be removed. Therefore we compare NULL against its bound. - addConstraint(subtype, supertype.getLowerBound(), constraints); - return null; - } - - @Override - public Void visitNull_Wildcard( - AnnotatedNullType subtype, AnnotatedWildcardType supertype, Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(supertype); - // we don't use visitSupertype because Null types won't have interesting components - constraints.add(new A2F(subtype, supertype.getSuperBound())); - return null; - } - - @Override - public Void visitNull_Null( - AnnotatedNullType argument, AnnotatedNullType parameter, Set constraints) { - return null; - } - - @Override - public Void visitNull_Union( - AnnotatedNullType argument, AnnotatedUnionType parameter, Set constraints) { - return null; // TODO: UNIONS ARE NOT YET SUPPORTED - } - - // Despite the fact that intersections are not yet supported, this is the right implementation. - // NULL types only have primary annotations. Since type parameters cannot be a member of the - // intersection's bounds (though they can be component types), we do not need to do anything - // further. - @Override - public Void visitNull_Intersection( - AnnotatedNullType argument, - AnnotatedIntersectionType parameter, - Set constraints) { - return null; - } - - // Primitive parameter types tell us nothing about the type parameters - @Override - public Void visitNull_Primitive( - AnnotatedNullType argument, AnnotatedPrimitiveType parameter, Set constraints) { - return null; - } - - // ------------------------------------------------------------------------ - // Primitive as argument - - @Override - public Void visitPrimitive_Declared( - AnnotatedPrimitiveType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - // we may be able to eliminate this case, since I believe the corresponding constraint will - // just be discarded as the parameter must be a boxed primitive - addConstraint(atypeFactory.getBoxedType(subtype), supertype, constraints); - return null; - } - - // Primitive parameter types tell us nothing about the type parameters - @Override - public Void visitPrimitive_Primitive( - AnnotatedPrimitiveType subtype, - AnnotatedPrimitiveType supertype, - Set constraints) { - return null; - } - - @Override - public Void visitPrimitive_Intersection( - AnnotatedPrimitiveType subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - addConstraint(atypeFactory.getBoxedType(subtype), supertype, constraints); - return null; - } - - // ------------------------------------------------------------------------ - // Union as argument - @Override - public Void visitUnion_Declared( - AnnotatedUnionType argument, AnnotatedDeclaredType parameter, Set constraints) { - return null; // TODO: UNIONS ARE NOT CURRENTLY SUPPORTED - } - - // ------------------------------------------------------------------------ - // typevar as argument - // If we've reached this point, the typevar is NOT one of the types we are inferring. - - @Override - public Void visitTypevar_Declared( - AnnotatedTypeVariable subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - addConstraint(subtype.getUpperBound(), supertype, constraints); - return null; - } - - @Override - public Void visitTypevar_Typevar( - AnnotatedTypeVariable subtype, - AnnotatedTypeVariable supertype, - Set constraints) { - // if we've reached this point and the two are corresponding type variables, then they are - // NOT ones that may have a type variable we are inferring types for and therefore we can - // discard this constraint - if (!AnnotatedTypes.areCorrespondingTypeVariables( - atypeFactory.getElementUtils(), subtype, supertype)) { - addConstraint(subtype.getUpperBound(), supertype.getLowerBound(), constraints); - } - - return null; - } - - @Override - public Void visitTypevar_Null( - AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Set constraints) { - addConstraint(subtype.getUpperBound(), supertype, constraints); - return null; - } - - @Override - public Void visitTypevar_Wildcard( - AnnotatedTypeVariable subtype, - AnnotatedWildcardType supertype, - Set constraints) { - visitWildcardAsSuperType(subtype, supertype, constraints); - return null; - } - - @Override - public Void visitTypevar_Intersection( - AnnotatedTypeVariable subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - addConstraint(subtype.getUpperBound(), supertype, constraints); - return null; - } - - // ------------------------------------------------------------------------ - // wildcard as subtype - @Override - public Void visitWildcard_Array( - AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Declared( - AnnotatedWildcardType subtype, - AnnotatedDeclaredType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Intersection( - AnnotatedWildcardType subtype, - AnnotatedIntersectionType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Primitive( - AnnotatedWildcardType subtype, - AnnotatedPrimitiveType supertype, - Set constraints) { - return null; - } - - @Override - public Void visitWildcard_Typevar( - AnnotatedWildcardType subtype, - AnnotatedTypeVariable supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - addConstraint(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - @Override - public Void visitWildcard_Wildcard( - AnnotatedWildcardType subtype, - AnnotatedWildcardType supertype, - Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(subtype); - // since wildcards are handled in visitDeclared_Declared this could only occur if two - // wildcards were passed to type subtype inference at the top level. This can only occur - // because we do not implement capture conversion. - visitWildcardAsSuperType(subtype.getExtendsBound(), supertype, constraints); - return null; - } - - // should the same logic apply to typevars? - public void visitWildcardAsSuperType( - AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype, Set constraints) { - TypeArgInferenceUtil.checkForUninferredTypes(supertype); - // this case occur only when supertype should actually be capture converted (which we don't - // do) because all other wildcard cases would be handled via Declared_Declared - addConstraint(subtype, supertype.getSuperBound(), constraints); - - // if type1 is below the superbound then it is necessarily below the extends bound - // BUT the extends bound may have interesting component types (like the array component) - // to which we also want to apply constraints - // e.g. visitArray_Wildcard(@I String[], ? extends @A String[]) - // if @I is an annotation we are trying to infer then we still want to infer that @I <: @A - // in fact - addInverseConstraint(subtype, supertype.getExtendsBound(), constraints); - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java deleted file mode 100644 index 74ce873229c..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2A.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; - -/** - * A constraint of the form: F 《 A or A 》 F - * - * @see org.checkerframework.framework.util.typeinference.constraint.AFConstraint - */ -public class F2A extends AFConstraint { - - /** Create a constraint with an argument greater than a formal. */ - public F2A(AnnotatedTypeMirror formalParameter, AnnotatedTypeMirror argument) { - super(argument, formalParameter); - } - - @Override - public TUConstraint toTUConstraint() { - return new TSubU((AnnotatedTypeVariable) formalParameter, argument, true); - } - - @Override - protected F2A construct(AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { - return new F2A(newFormalParameter, newArgument); - } - - @Override - public String toString() { - return "F2A( " + formalParameter + " << " + argument + " )"; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java deleted file mode 100644 index 61882df9610..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/F2AReducer.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import java.util.Set; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; - -/** - * F2AReducer takes an F2A constraint that is not irreducible (@see AFConstraint.isIrreducible) and - * reduces it by one step. The resulting constraint may still be reducible. - * - *

          Generally reductions should map to corresponding rules in - * https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.12.2.7 - */ -public class F2AReducer implements AFReducer { - - protected final F2AReducingVisitor visitor; - - public F2AReducer(AnnotatedTypeFactory typeFactory) { - this.visitor = new F2AReducingVisitor(typeFactory); - } - - @Override - public boolean reduce(AFConstraint constraint, Set newConstraints) { - if (constraint instanceof F2A) { - F2A f2A = (F2A) constraint; - visitor.visit(f2A.formalParameter, f2A.argument, newConstraints); - return true; - - } else { - return false; - } - } - - /** - * Given an F2A constraint of the form: F2A( typeFromFormalParameter, typeFromMethodArgument ) - * - *

          F2AReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter, - * typeFromMethodArgument, newConstraints ) - * - *

          The visit method will determine if the given constraint should either: - * - *

            - *
          • be discarded -- in this case, the visitor just returns - *
          • reduced to a simpler constraint or set of constraints -- in this case, the new constraint - * or set of constraints is added to newConstraints - *
          - * - * Sprinkled throughout this class are comments of the form: - * - *
          {@code
          -   * // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
          -   * }
          - * - * These are excerpts from the JLS, if you search for them you will find the corresponding JLS - * description of the case being covered. - */ - private static class F2AReducingVisitor extends AFReducingVisitor { - - public F2AReducingVisitor(AnnotatedTypeFactory typeFactory) { - super(F2A.class, typeFactory); - } - - @Override - public AFConstraint makeConstraint(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new F2A(subtype, supertype); - } - - @Override - public AFConstraint makeInverseConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new A2F(subtype, supertype); - } - - @Override - public AFConstraint makeEqualityConstraint( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - return new FIsA(subtype, supertype); - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java deleted file mode 100644 index dbfec18ffe0..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsA.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; - -/** - * A constraint of the form: F = A or A = F - * - * @see org.checkerframework.framework.util.typeinference.constraint.AFConstraint - */ -public class FIsA extends AFConstraint { - - /** Create a constraint with an argument equal to a formal. */ - public FIsA(AnnotatedTypeMirror parameter, AnnotatedTypeMirror argument) { - super(argument, parameter); - } - - @Override - public TUConstraint toTUConstraint() { - return new TIsU((AnnotatedTypeVariable) formalParameter, argument, true); - } - - @Override - protected FIsA construct( - AnnotatedTypeMirror newArgument, AnnotatedTypeMirror newFormalParameter) { - return new FIsA(newFormalParameter, newArgument); - } - - @Override - public String toString() { - return "FisA( " + formalParameter + " = " + argument + " )"; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java deleted file mode 100644 index 571330ca1fd..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/FIsAReducer.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import java.util.List; -import java.util.Set; -import javax.lang.model.type.TypeKind; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.plumelib.util.StringsPlume; - -/** - * FIsAReducer takes an FIsA constraint that is not irreducible (@see AFConstraint.isIrreducible) - * and reduces it by one step. The resulting constraint may still be reducible. - * - *

          Generally reductions should map to corresponding rules in - * https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.12.2.7 - */ -public class FIsAReducer implements AFReducer { - - protected final FIsAReducingVisitor visitor; - private final AnnotatedTypeFactory atypeFactory; - - public FIsAReducer(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.visitor = new FIsAReducingVisitor(); - } - - @Override - public boolean reduce(AFConstraint constraint, Set newConstraints) { - if (constraint instanceof FIsA) { - FIsA fIsA = (FIsA) constraint; - visitor.visit(fIsA.formalParameter, fIsA.argument, newConstraints); - return true; - - } else { - return false; - } - } - - /** - * Given an FIsA constraint of the form: FIsA( typeFromFormalParameter, typeFromMethodArgument ) - * - *

          FIsAReducingVisitor visits the constraint as follows: visit ( typeFromFormalParameter, - * typeFromMethodArgument, newConstraints ) - * - *

          The visit method will determine if the given constraint should either: - * - *

            - *
          • be discarded -- in this case, the visitor just returns - *
          • reduced to a simpler constraint or set of constraints -- in this case, the new constraint - * or set of constraints is added to newConstraints - *
          - * - * From the JLS, in general there are 2 rules that govern F = A constraints: If F = Tj, then the - * constraint Tj = A is implied. If F = U[], where the type U involves Tj, then if A is an array - * type V[], or a type variable with an upper bound that is an array type V[], where V is a - * reference type, this algorithm is applied recursively to the constraint {@code V >> U}. - * Otherwise, no constraint is implied on Tj. - * - *

          Since both F and A may have component types this visitor delves into their components and - * applies these rules to the components. However, only one step is taken at a time (i.e. this is - * not a scanner) - */ - private class FIsAReducingVisitor extends AbstractAtmComboVisitor> { - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror argument, - AnnotatedTypeMirror parameter, - Set afConstraints) { - return super.defaultErrorMessage(argument, parameter, afConstraints) - + System.lineSeparator() - + " constraints = [" - + StringsPlume.join(", ", afConstraints) - + "]"; - } - - // ------------------------------------------------------------------------ - // Arrays as arguments - - @Override - public Void visitArray_Array( - AnnotatedArrayType parameter, AnnotatedArrayType argument, Set constraints) { - constraints.add(new FIsA(parameter.getComponentType(), argument.getComponentType())); - return null; - } - - @Override - public Void visitArray_Declared( - AnnotatedArrayType parameter, - AnnotatedDeclaredType argument, - Set constraints) { - return null; - } - - @Override - public Void visitArray_Null( - AnnotatedArrayType parameter, AnnotatedNullType argument, Set constraints) { - return null; - } - - @Override - public Void visitArray_Wildcard( - AnnotatedArrayType parameter, - AnnotatedWildcardType argument, - Set constraints) { - constraints.add(new FIsA(parameter, argument.getExtendsBound())); - return null; - } - - // ------------------------------------------------------------------------ - // Declared as argument - @Override - public Void visitDeclared_Array( - AnnotatedDeclaredType parameter, - AnnotatedArrayType argument, - Set constraints) { - // should this be Array - T[] the new A2F(String, T) - return null; - } - - @Override - public Void visitDeclared_Declared( - AnnotatedDeclaredType parameter, - AnnotatedDeclaredType argument, - Set constraints) { - if (argument.isUnderlyingTypeRaw() || parameter.isUnderlyingTypeRaw()) { - return null; - } - - AnnotatedDeclaredType argumentAsParam = - AnnotatedTypes.castedAsSuper(atypeFactory, argument, parameter); - if (argumentAsParam == null) { - return null; - } - - List argTypeArgs = argumentAsParam.getTypeArguments(); - List paramTypeArgs = parameter.getTypeArguments(); - for (int i = 0; i < argTypeArgs.size(); i++) { - AnnotatedTypeMirror argTypeArg = argTypeArgs.get(i); - AnnotatedTypeMirror paramTypeArg = paramTypeArgs.get(i); - - if (paramTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType paramWc = (AnnotatedWildcardType) paramTypeArg; - - if (argTypeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType argWc = (AnnotatedWildcardType) argTypeArg; - constraints.add(new FIsA(paramWc.getExtendsBound(), argWc.getExtendsBound())); - constraints.add(new FIsA(paramWc.getSuperBound(), argWc.getSuperBound())); - } - - } else { - constraints.add(new FIsA(paramTypeArgs.get(i), argTypeArgs.get(i))); - } - } - - return null; - } - - @Override - public Void visitDeclared_Null( - AnnotatedDeclaredType parameter, - AnnotatedNullType argument, - Set constraints) { - return null; - } - - @Override - public Void visitDeclared_Primitive( - AnnotatedDeclaredType parameter, - AnnotatedPrimitiveType argument, - Set constraints) { - return null; - } - - @Override - public Void visitDeclared_Union( - AnnotatedDeclaredType parameter, - AnnotatedUnionType argument, - Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } - - @Override - public Void visitIntersection_Intersection( - AnnotatedIntersectionType parameter, - AnnotatedIntersectionType argument, - Set constraints) { - return null; // TODO: NOT SUPPORTED AT THE MOMENT - } - - @Override - public Void visitIntersection_Null( - AnnotatedIntersectionType parameter, - AnnotatedNullType argument, - Set constraints) { - return null; - } - - @Override - public Void visitNull_Null( - AnnotatedNullType parameter, AnnotatedNullType argument, Set afConstraints) { - // we sometimes get these when we have captured type variables passed as arguments - // regardless they don't give any information - return null; - } - - // ------------------------------------------------------------------------ - // Primitive as argument - @Override - public Void visitPrimitive_Declared( - AnnotatedPrimitiveType parameter, - AnnotatedDeclaredType argument, - Set constraints) { - // we may be able to eliminate this case, since I believe the corresponding constraint - // will just be discarded as the parameter must be a boxed primitive - constraints.add(new FIsA(atypeFactory.getBoxedType(parameter), argument)); - return null; - } - - @Override - public Void visitPrimitive_Primitive( - AnnotatedPrimitiveType parameter, - AnnotatedPrimitiveType argument, - Set constraints) { - return null; - } - - @Override - public Void visitTypevar_Typevar( - AnnotatedTypeVariable parameter, - AnnotatedTypeVariable argument, - Set constraints) { - // if we've reached this point and the two are corresponding type variables, then they - // are NOT ones that may have a type variable we are inferring types for and therefore - // we can discard this constraint - return null; - } - - @Override - public Void visitTypevar_Null( - AnnotatedTypeVariable argument, - AnnotatedNullType parameter, - Set constraints) { - return null; - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java deleted file mode 100644 index 9343baf8374..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TIsU.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; - -/** - * A constraint of the form: T = U - * - * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint - */ -public class TIsU extends TUConstraint { - public TIsU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { - this(typeVariable, relatedType, false); - } - - /** Create a constraint with a variable equal to a type. */ - public TIsU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - super(typeVariable, relatedType, uIsArg); - } - - @Override - public String toString() { - return "TIsU( " + typeVariable + ", " + relatedType + " )"; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java deleted file mode 100644 index 9e5bad537ac..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSubU.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; - -/** - * A constraint of the form: {@code T <: U} - * - * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint - */ -public class TSubU extends TUConstraint { - public TSubU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { - this(typeVariable, relatedType, false); - } - - /** Create a constraint with a variable less than a type. */ - public TSubU( - AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - super(typeVariable, relatedType, uIsArg); - } - - @Override - public String toString() { - return "TSubU( " + typeVariable + " <: " + relatedType + " )"; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java deleted file mode 100644 index 44703ca099c..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TSuperU.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; - -/** - * A constraint of the form: {@code T :> U} - * - * @see org.checkerframework.framework.util.typeinference.constraint.TUConstraint - */ -public class TSuperU extends TUConstraint { - public TSuperU(AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType) { - this(typeVariable, relatedType, false); - } - - /** Create a constraint with a variable greater than a type. */ - public TSuperU( - AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - super(typeVariable, relatedType, uIsArg); - } - - @Override - public String toString() { - return "TSuperU( " + typeVariable + " :> " + relatedType + " )"; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java deleted file mode 100644 index 70633a075fa..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/TUConstraint.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.checkerframework.framework.util.typeinference.constraint; - -import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; - -/** - * Subclasses of TUConstraint represent constraints between a type parameter, whose type arguments - * are being inferred, and the types used to do that inference. These constraints are used by the - * TASolver to infer arguments. - * - *

          TU constraints come in the classic form of subtype, supertype, and equality constraints.
          - * - *

            - *
          • {@code T <: U} -- implies T is a subtype of U, it is represented by TSubU
            - *
          • {@code T >: U} -- implies T is a supertype of U, it is represented by TSuperU
            - *
          • {@code T = U} -- implies T is equal to U, it is represented by TIsU
            - *
          - * - *

          Note, it is important that the type parameter is represented by an AnnotatedTypeVariable - * because if a use of the type parameter has a primary annotation, then the two types represented - * in by a TUConstraint are NOT constrained in the hierarchy of that annotation. e.g. - * - *

          {@code
          - *  void method(List<@NonNull T> t1, T t2)
          - * method(new ArrayList<@NonNull String>(), null);
          - * }
          - * - * The above method call would eventually be reduced to constraints: {@code [@NonNull String - * == @NonNull T, @Nullable null <: T]} - * - *

          In this example, if we did not ignore the first constraint then the type argument would be - * exactly @NonNull String and the second argument would be invalid. However, the correct inference - * would be @Nullable String and both arguments would be valid. - */ -public abstract class TUConstraint { - /** - * An AnnotatedTypeVariable representing a target type parameter for which we are inferring a type - * argument. This is the T in the TUConstraints. - */ - public final AnnotatedTypeVariable typeVariable; - - /** - * A type used to infer an argument for the typeVariable T. This would be the U in the - * TUConstraints. - */ - public final AnnotatedTypeMirror relatedType; - - /** Whether or not U is a type from an argument to the method. */ - public final boolean uIsArg; - - protected TUConstraint( - AnnotatedTypeVariable typeVariable, AnnotatedTypeMirror relatedType, boolean uIsArg) { - this.typeVariable = typeVariable; - this.relatedType = relatedType; - this.uIsArg = uIsArg; - - TypeArgInferenceUtil.checkForUninferredTypes(relatedType); - } - - @Override - public boolean equals(@Nullable Object thatObject) { - if (this == thatObject) { - return true; - } // else - - if (thatObject == null || this.getClass() != thatObject.getClass()) { - return false; - } - - TUConstraint that = (TUConstraint) thatObject; - - return this.typeVariable.equals(that.typeVariable) && this.relatedType.equals(that.relatedType); - } - - @Override - public int hashCode() { - return Objects.hash(this.getClass(), typeVariable, relatedType); - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java deleted file mode 100644 index 71ec6a08d2b..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMap.java +++ /dev/null @@ -1,186 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Equalities; -import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Subtypes; -import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Supertypes; -import org.checkerframework.javacutil.AnnotationMirrorSet; - -/** - * ConstraintMap holds simplified versions of the TUConstraints for ALL type variable for which we - * are inferring an argument. The ConstraintMap is edited on the fly as the various solvers work - * (unlike the AF/TU Constraints which are immutable). - * - *

          This really consists of these things: - * - *

            - *
          1. a Map({@code target => constraints for target}) - *
          2. Methods to easily build up the constraints in the map - *
          3. A getter for the constraints of individual targets. - *
          - * - * Note: This class, along with TargetConstraints, uses a lot of mutable state and few - * setters/getters be careful. This choice was made as it makes the resulting code more readable. - */ -public class ConstraintMap { - - private final Map targetToRecords = new LinkedHashMap<>(); - - public ConstraintMap(Set targets) { - for (TypeVariable target : targets) { - targetToRecords.put(target, new TargetConstraints(target)); - } - } - - public ConstraintMap(ConstraintMap toCopy) { - this.targetToRecords.putAll(toCopy.targetToRecords); - } - - /** Gets the equality, subtypes, and supertypes constraints for a particular target. */ - public TargetConstraints getConstraints(TypeVariable target) { - return targetToRecords.get(target); - } - - /** - * Returns the set of all targets passed to the constructor of this constraint map (a target will - * appear in this list whether or not it has any constraints added). - * - * @return the set of all targets passed to the constructor of this constraint map (a target will - * appear in this list whether or not it has any constraints added) - */ - public Set getTargets() { - return targetToRecords.keySet(); - } - - /** - * Add a constraint indicating that the equivalent is equal to target in the given qualifier - * hierarchies. - */ - public void addTargetEquality( - TypeVariable target, TypeVariable equivalent, AnnotationMirrorSet hierarchies) { - Equalities equalities = targetToRecords.get(target).equalities; - AnnotationMirrorSet equivalentTops = - equalities.targets.computeIfAbsent(equivalent, __ -> new AnnotationMirrorSet()); - equivalentTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target has primary annotations equal to the given annotations. - */ - public void addPrimaryEqualities( - TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { - Equalities equalities = targetToRecords.get(target).equalities; - - for (AnnotationMirror anno : annos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - if (!equalities.primaries.containsKey(top)) { - equalities.primaries.put(top, anno); - } - } - } - - /** - * Add a constraint indicating that target is a supertype of subtype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTargetSupertype( - TypeVariable target, TypeVariable subtype, AnnotationMirrorSet hierarchies) { - Supertypes supertypes = targetToRecords.get(target).supertypes; - AnnotationMirrorSet supertypeTops = - supertypes.targets.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet()); - supertypeTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target is a supertype of subtype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTypeSupertype( - TypeVariable target, AnnotatedTypeMirror subtype, AnnotationMirrorSet hierarchies) { - Supertypes supertypes = targetToRecords.get(target).supertypes; - AnnotationMirrorSet supertypeTops = - supertypes.types.computeIfAbsent(subtype, __ -> new AnnotationMirrorSet()); - supertypeTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target's primary annotations are subtypes of the given - * annotations. - */ - public void addPrimarySupertype( - TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { - Supertypes supertypes = targetToRecords.get(target).supertypes; - for (AnnotationMirror anno : annos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - AnnotationMirrorSet entries = - supertypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet()); - entries.add(anno); - } - } - - /** - * Add a constraint indicating that target is a subtype of supertype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTargetSubtype( - TypeVariable target, TypeVariable supertype, AnnotationMirrorSet hierarchies) { - Subtypes subtypes = targetToRecords.get(target).subtypes; - AnnotationMirrorSet subtypesTops = - subtypes.targets.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet()); - subtypesTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target is a subtype of supertype in the given qualifier - * hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTypeSubtype( - TypeVariable target, AnnotatedTypeMirror supertype, AnnotationMirrorSet hierarchies) { - Subtypes subtypes = targetToRecords.get(target).subtypes; - AnnotationMirrorSet subtypesTops = - subtypes.types.computeIfAbsent(supertype, __ -> new AnnotationMirrorSet()); - subtypesTops.addAll(hierarchies); - } - - /** - * Add a constraint indicating that target's primary annotations are subtypes of the given - * annotations. - */ - public void addPrimarySubtypes( - TypeVariable target, QualifierHierarchy qualHierarchy, AnnotationMirrorSet annos) { - Subtypes subtypes = targetToRecords.get(target).subtypes; - for (AnnotationMirror anno : annos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - AnnotationMirrorSet entries = - subtypes.primaries.computeIfAbsent(top, __ -> new AnnotationMirrorSet()); - entries.add(anno); - } - } - - /** - * Add a constraint indicating that target is equal to type in the given hierarchies. - * - * @param hierarchies a set of TOP annotations - */ - public void addTypeEqualities( - TypeVariable target, AnnotatedTypeMirror type, AnnotationMirrorSet hierarchies) { - Equalities equalities = targetToRecords.get(target).equalities; - AnnotationMirrorSet equalityTops = - equalities.types.computeIfAbsent(type, __ -> new AnnotationMirrorSet()); - equalityTops.addAll(hierarchies); - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java deleted file mode 100644 index 97c88971a39..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/ConstraintMapBuilder.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.util.typeinference.constraint.TIsU; -import org.checkerframework.framework.util.typeinference.constraint.TSuperU; -import org.checkerframework.framework.util.typeinference.constraint.TUConstraint; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.TypeAnnotationUtils; - -/** Converts a set of TUConstraints into a ConstraintMap. */ -public class ConstraintMapBuilder { - - /** - * Let Ti be a the ith target being inferred Let ATV(i) be the annotated type variable that - * represents as use of Ti which may or may not have primary annotations. Let ATM be an annotated - * type mirror that may or may not be target Tx, or have a component target Tx Let Ai be the type - * argument we are trying to infer for Ti - * - *

          We have a set of constraints of the form: {@code ATV(i) ATM} - * - *

          Where {@code } is either a subtype ({@code <:}), supertype ({@code :>}), or equality - * relationship ({@code =}). - * - *

          Regardless of what {@code } is, a constraint will only imply constraints on Ai in a given - * hierarchy if ATV(i) does NOT have a primary annotation in that hierarchy. That is: - * - *

          E.g. Let ATV(i) be @NonNull Ti, the constraints @NonNull Ti = @NonNull @Initialized String - * does not imply any primary annotation in the Nullness hierarchy for type argument Ai because - * the Annotated type mirror has a primary annotation in the NUllness hierarchy. - * - *

          However, it does imply that Ai has a primary annotation of @Initialized since ATV(i) has no - * primary annotation in the initialization hierarchy. - * - *

          Note, constraints come in 2 forms: - * - *

            - *
          • between a target and a concrete AnnotatedTypeMirror. E.g., As seen above {@code (@NonNull - * Ti = @NonNull @Initialized String)} - *
          • between two targets E.g., {@code (@NonNull Ti = Tj)} - *
          - */ - public ConstraintMap build( - Set targets, Set constraints, AnnotatedTypeFactory typeFactory) { - - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - ConstraintMap result = new ConstraintMap(targets); - - AnnotationMirrorSet tAnnos = new AnnotationMirrorSet(); - AnnotationMirrorSet uAnnos = new AnnotationMirrorSet(); - AnnotationMirrorSet hierarchiesInRelation = new AnnotationMirrorSet(); - - for (TUConstraint constraint : constraints) { - tAnnos.clear(); - uAnnos.clear(); - hierarchiesInRelation.clear(); - - AnnotatedTypeVariable typeT = constraint.typeVariable; - AnnotatedTypeMirror typeU = constraint.relatedType; - - // If typeU is from an argument to the method, then treat typeU as an ordinary type even - // if it is a target type variable. This is for the case where the inferred type is the - // declared type parameter. For example, - // public T get(T t) { - // return this.get(t); - // } - // The inferred type of T should be T. - if (!constraint.uIsArg - && typeU.getKind() == TypeKind.TYPEVAR - && targets.contains( - (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()))) { - if (typeT.getAnnotations().isEmpty() && typeU.getAnnotations().isEmpty()) { - hierarchiesInRelation.addAll(tops); - - } else { - - for (AnnotationMirror top : tops) { - AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top); - AnnotationMirror uAnno = typeU.getAnnotationInHierarchy(top); - - if (tAnno == null) { - if (uAnno == null) { - hierarchiesInRelation.add(top); - - } else { - tAnnos.add(uAnno); - } - } else { - if (uAnno == null) { - uAnnos.add(tAnno); - - } else { - // This tells us nothing, they both should be equal but either way - // we gain no information if both type vars have annotations - } - } - } - - // If we have a case where Ti = @NonNull Tj we know that for the @Initialization - // hierarchy Ti = TJ and we know that for the @Nullable hierarchy Ti = @NonNull - // . - // This step saves @NonNull annotation. - // This case also covers the case where i = j. - if (!tAnnos.isEmpty()) { - addToPrimaryRelationship( - (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), - constraint, - result, - tAnnos, - qualHierarchy); - } - - if (!uAnnos.isEmpty()) { - addToPrimaryRelationship( - (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()), - constraint, - result, - uAnnos, - qualHierarchy); - } - } - - // This is the case where we have a relationship between two different targets (Ti - // Tj and i != j) - if (!typeFactory.types.isSameType( - TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), - TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()))) { - addToTargetRelationship( - (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), - (TypeVariable) TypeAnnotationUtils.unannotatedType(typeU.getUnderlyingType()), - result, - constraint, - hierarchiesInRelation); - } - } else { - for (AnnotationMirror top : tops) { - AnnotationMirror tAnno = typeT.getAnnotationInHierarchy(top); - - if (tAnno == null) { - hierarchiesInRelation.add(top); - } - } - - addToTypeRelationship( - (TypeVariable) TypeAnnotationUtils.unannotatedType(typeT.getUnderlyingType()), - typeU, - result, - constraint, - hierarchiesInRelation); - } - } - - return result; - } - - private void addToTargetRelationship( - TypeVariable typeT, - TypeVariable typeU, - ConstraintMap result, - TUConstraint constraint, - AnnotationMirrorSet hierarchiesInRelation) { - if (constraint instanceof TIsU) { - result.addTargetEquality(typeT, typeU, hierarchiesInRelation); - } else if (constraint instanceof TSuperU) { - result.addTargetSupertype(typeT, typeU, hierarchiesInRelation); - } else { - result.addTargetSubtype(typeT, typeU, hierarchiesInRelation); - } - } - - public void addToPrimaryRelationship( - TypeVariable typeVariable, - TUConstraint constraint, - ConstraintMap result, - AnnotationMirrorSet annotationMirrors, - QualifierHierarchy qualHierarchy) { - if (constraint instanceof TIsU) { - result.addPrimaryEqualities(typeVariable, qualHierarchy, annotationMirrors); - } else if (constraint instanceof TSuperU) { - result.addPrimarySupertype(typeVariable, qualHierarchy, annotationMirrors); - } else { - result.addPrimarySubtypes(typeVariable, qualHierarchy, annotationMirrors); - } - } - - public void addToTypeRelationship( - TypeVariable target, - AnnotatedTypeMirror type, - ConstraintMap result, - TUConstraint constraint, - AnnotationMirrorSet hierarchies) { - if (constraint instanceof TIsU) { - result.addTypeEqualities(target, type, hierarchies); - } else if (constraint instanceof TSuperU) { - result.addTypeSupertype(target, type, hierarchies); - } else { - result.addTypeSubtype(target, type, hierarchies); - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java deleted file mode 100644 index 4d160d4361c..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java +++ /dev/null @@ -1,474 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; -import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredTarget; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; -import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Equalities; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; - -/** - * EqualitiesSolver infers type arguments for targets using the equality constraints in - * ConstraintMap. When a type is inferred, it rewrites the remaining equality/supertype constraints - */ -public class EqualitiesSolver { - private boolean dirty = false; - - /** - * For each target, if there is one or more equality constraints involving concrete types that - * lets us infer a primary annotation in all qualifier hierarchies then infer a concrete type - * argument. else if there is one or more equality constraints involving other targets that lets - * us infer a primary annotation in all qualifier hierarchies then infer that type argument is the - * other type argument - * - *

          if we have inferred either a concrete type or another target as type argument rewrite all of - * the constraints for the current target to instead use the inferred type/target - * - *

          We do this iteratively until NO new inferred type argument is found - * - * @param targets the list of type parameters for which we are inferring type arguments - * @param constraintMap the set of constraints over the set of targets - * @return a Map from target to (inferred type or target) - */ - public InferenceResult solveEqualities( - Set targets, ConstraintMap constraintMap, AnnotatedTypeFactory typeFactory) { - InferenceResult solution = new InferenceResult(); - - do { - dirty = false; - for (TypeVariable target : targets) { - if (solution.containsKey(target)) { - continue; - } - - Equalities equalities = constraintMap.getConstraints(target).equalities; - - InferredValue inferred = - mergeConstraints(target, equalities, solution, constraintMap, typeFactory); - if (inferred != null) { - if (inferred instanceof InferredType) { - rewriteWithInferredType(target, ((InferredType) inferred).type, constraintMap); - } else { - rewriteWithInferredTarget( - target, ((InferredTarget) inferred).target, constraintMap, typeFactory); - } - - solution.put(target, inferred); - } - } - } while (dirty); - - solution.resolveChainedTargets(); - - return solution; - } - - /** - * Let Ti be a target type parameter. When we reach this method we have inferred an argument, Ai, - * for Ti. - * - *

          However, there still may be constraints of the form {@literal Ti = Tj}, {@literal Ti <: Tj}, - * {@literal Tj <: Ti} in the constraint map. In this case we need to replace Ti with the type. - * That is, they become {@literal Ai = Tj}, {@literal Ai <: Tj}, and {@literal Tj <: Ai} - * - *

          To do this, we find the TargetConstraints for Tj and add these constraints to the - * appropriate map in TargetConstraints. We can then clear the constraints for the current target - * since we have inferred a type. - * - * @param target the target for which we have inferred a concrete type argument - * @param type the type inferred - * @param constraints the constraints that are side-effected by this method - */ - private void rewriteWithInferredType( - @FindDistinct TypeVariable target, AnnotatedTypeMirror type, ConstraintMap constraints) { - - TargetConstraints targetRecord = constraints.getConstraints(target); - Map equivalentTargets = targetRecord.equalities.targets; - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred type - for (Map.Entry eqEntry : equivalentTargets.entrySet()) { - constraints.addTypeEqualities(eqEntry.getKey(), type, eqEntry.getValue()); - } - - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred type - AnnotationMirrorSet hierarchies = record.equalities.targets.get(target); - if (hierarchies != null) { - record.equalities.targets.remove(target); - constraints.addTypeEqualities(otherTarget, type, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.equalities.types); - record.equalities.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = TypeArgInferenceUtil.substitute(target, type, otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.equalities.types.put(copy, otherHierarchies); - } - } - } - - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred type - AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target); - if (hierarchies != null) { - record.supertypes.targets.remove(target); - constraints.addTypeEqualities(otherTarget, type, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.supertypes.types); - record.supertypes.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = TypeArgInferenceUtil.substitute(target, type, otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.supertypes.types.put(copy, otherHierarchies); - } - } - } - - targetRecord.equalities.clear(); - targetRecord.supertypes.clear(); - } - - /** - * Let Ti be a target type parameter. When we reach this method we have inferred that Ti has the - * exact same argument as another target Tj - * - *

          Therefore, we want to stop solving for Ti and instead wait till we solve for Tj and use that - * result. - * - *

          Let ATM be any annotated type mirror and Tk be a target type parameter where k != i and k != - * j Even though we've inferred Ti = Tj, there still may be constraints of the form Ti = ATM or - * {@literal Ti <: Tk} These constraints are still useful for inferring a argument for Ti/Tj. So, - * we replace Ti in these constraints with Tj and place those constraints in the TargetConstraints - * object for Tj. - * - *

          We then clear the constraints for Ti. - * - * @param target the target for which we know another target is exactly equal to this target - * @param inferredTarget the other target inferred to be equal - * @param constraints the constraints that are side-effected by this method - * @param typeFactory type factory - */ - private void rewriteWithInferredTarget( - @FindDistinct TypeVariable target, - @FindDistinct TypeVariable inferredTarget, - ConstraintMap constraints, - AnnotatedTypeFactory typeFactory) { - TargetConstraints targetRecord = constraints.getConstraints(target); - Map equivalentTypes = targetRecord.equalities.types; - Map supertypes = targetRecord.supertypes.types; - - // each type that was equivalent to this one needs to be equivalent in the same hierarchies - // to the inferred target - for (Map.Entry eqEntry : equivalentTypes.entrySet()) { - constraints.addTypeEqualities(inferredTarget, eqEntry.getKey(), eqEntry.getValue()); - } - - for (Map.Entry superEntry : supertypes.entrySet()) { - constraints.addTypeSupertype(inferredTarget, superEntry.getKey(), superEntry.getValue()); - } - - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target && otherTarget != inferredTarget) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - // each target that was equivalent to this one needs to be equivalent in the same - // hierarchies as the inferred target - AnnotationMirrorSet hierarchies = record.equalities.targets.get(target); - if (hierarchies != null) { - record.equalities.targets.remove(target); - constraints.addTargetEquality(otherTarget, inferredTarget, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.equalities.types); - record.equalities.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = - TypeArgInferenceUtil.substitute( - target, createAnnotatedTypeVar(target, typeFactory), otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.equalities.types.put(copy, otherHierarchies); - } - } - } - - for (TypeVariable otherTarget : constraints.getTargets()) { - if (otherTarget != target && otherTarget != inferredTarget) { - TargetConstraints record = constraints.getConstraints(otherTarget); - - AnnotationMirrorSet hierarchies = record.supertypes.targets.get(target); - if (hierarchies != null) { - record.supertypes.targets.remove(target); - constraints.addTargetSupertype(otherTarget, inferredTarget, hierarchies); - } - - // otherTypes may have AnnotatedTypeVariables of type target, run substitution on - // these with type - Map toIterate = - new LinkedHashMap<>(record.supertypes.types); - record.supertypes.types.clear(); - for (AnnotatedTypeMirror otherType : toIterate.keySet()) { - AnnotatedTypeMirror copy = - TypeArgInferenceUtil.substitute( - target, createAnnotatedTypeVar(target, typeFactory), otherType); - AnnotationMirrorSet otherHierarchies = toIterate.get(otherType); - record.supertypes.types.put(copy, otherHierarchies); - } - } - } - - targetRecord.equalities.clear(); - targetRecord.supertypes.clear(); - } - - /** Creates a declaration AnnotatedTypeVariable for TypeVariable. */ - private AnnotatedTypeVariable createAnnotatedTypeVar( - TypeVariable typeVariable, AnnotatedTypeFactory typeFactory) { - return (AnnotatedTypeVariable) typeFactory.getAnnotatedType(typeVariable.asElement()); - } - - /** - * Returns a concrete type argument or null if there was not enough information to infer one. - * - * @param typesToHierarchies a mapping of (types → hierarchies) that indicate that the - * argument being inferred is equal to the types in each of the hierarchies - * @param primaries a map (hierarchy → annotation in hierarchy) where the annotation in - * hierarchy is equal to the primary annotation on the argument being inferred - * @param tops the set of top annotations in the qualifier hierarchy - * @return a concrete type argument or null if there was not enough information to infer one - */ - private @Nullable InferredType mergeTypesAndPrimaries( - Map typesToHierarchies, - AnnotationMirrorMap primaries, - AnnotationMirrorSet tops, - AnnotatedTypeFactory typeFactory) { - AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet(tops); - - Iterator> entryIterator = - typesToHierarchies.entrySet().iterator(); - if (!entryIterator.hasNext()) { - throw new BugInCF("Merging a list of empty types."); - } - - Map.Entry head = entryIterator.next(); - - AnnotatedTypeMirror mergedType = head.getKey(); - missingAnnos.removeAll(head.getValue()); - - // 1. if there are multiple equality constraints in a ConstraintMap then the types better - // have the same underlying type or Javac will complain and we won't be here. When building - // ConstraintMaps constraints involving AnnotatedTypeMirrors that are exactly equal are - // combined so there must be some difference between two types being merged here. - // - // 2. Otherwise, we might have the same underlying type but conflicting annotations, then we - // take the first set of annotations and show an error for the argument/return type that - // caused the second differing constraint. - // - // 3. Finally, we expect the following types to be involved in equality constraints: - // AnnotatedDeclaredTypes, AnnotatedTypeVariables, and AnnotatedArrayTypes - while (entryIterator.hasNext() && !missingAnnos.isEmpty()) { - Map.Entry current = entryIterator.next(); - AnnotatedTypeMirror currentType = current.getKey(); - AnnotationMirrorSet currentHierarchies = current.getValue(); - - AnnotationMirrorSet found = new AnnotationMirrorSet(); - for (AnnotationMirror top : missingAnnos) { - if (currentHierarchies.contains(top)) { - AnnotationMirror newAnno = currentType.getAnnotationInHierarchy(top); - if (newAnno != null) { - mergedType.replaceAnnotation(newAnno); - found.add(top); - - } else if (mergedType.getKind() == TypeKind.TYPEVAR - && typeFactory.types.isSameType( - currentType.getUnderlyingType(), mergedType.getUnderlyingType())) { - // the options here are we are merging with the same typevar, in which case - // we can just remove the annotation from the missing list - found.add(top); - - } else { - // otherwise the other type is missing an annotation - throw new BugInCF( - "Missing annotation.%nmergedType=%s%ncurrentType=%s", mergedType, currentType); - } - } - } - - missingAnnos.removeAll(found); - } - - // add all the annotations from the primaries - for (AnnotationMirror top : missingAnnos) { - AnnotationMirror anno = primaries.get(top); - if (anno != null) { - mergedType.replaceAnnotation(anno); - } - } - - typesToHierarchies.clear(); - - if (missingAnnos.isEmpty()) { - return new InferredType(mergedType); - } - - // TODO: we probably can do more with this information than just putting it back into the - // TODO: ConstraintMap (which is what's happening here) - AnnotationMirrorSet hierarchies = new AnnotationMirrorSet(tops); - hierarchies.removeAll(missingAnnos); - typesToHierarchies.put(mergedType, hierarchies); - - return null; - } - - public InferredValue mergeConstraints( - TypeVariable target, - Equalities equalities, - InferenceResult solution, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - AnnotationMirrorSet tops = - new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); - InferredValue inferred = null; - if (!equalities.types.isEmpty()) { - inferred = mergeTypesAndPrimaries(equalities.types, equalities.primaries, tops, typeFactory); - } - - if (inferred != null) { - return inferred; - } // else - - // We did not have enough information to infer an annotation in all hierarchies for one - // concrete type. - // However, we have a "partial solution", one in which we know the type in some but not all - // qualifier hierarchies. - // Update our set of constraints with this information - dirty |= updateTargetsWithPartiallyInferredType(equalities, constraintMap, typeFactory); - inferred = findEqualTarget(equalities, tops); - - if (inferred == null && equalities.types.size() == 1) { - // Still could not find an inferred type in all hierarchies, so just use what type is - // known. - AnnotatedTypeMirror type = equalities.types.keySet().iterator().next(); - inferred = new InferredType(type); - } - return inferred; - } - - // If we determined that this target T1 is equal to a type ATM in hierarchies @A,@B,@C - // for each of those hierarchies, if a target is equal to T1 in that hierarchy it is also equal - // to ATM. - // e.g. - // if : T1 == @A @B @C ATM in only the A,B hierarchies - // and T1 == T2 only in @A hierarchy - // - // then T2 == @A @B @C only in the @A hierarchy - // - public boolean updateTargetsWithPartiallyInferredType( - Equalities equalities, ConstraintMap constraintMap, AnnotatedTypeFactory typeFactory) { - - boolean updated = false; - - if (!equalities.types.isEmpty()) { - if (equalities.types.size() != 1) { - throw new BugInCF("Equalities should have at most 1 constraint."); - } - - Map.Entry remainingTypeEquality; - remainingTypeEquality = equalities.types.entrySet().iterator().next(); - - AnnotatedTypeMirror remainingType = remainingTypeEquality.getKey(); - AnnotationMirrorSet remainingHierarchies = remainingTypeEquality.getValue(); - - // update targets - for (Map.Entry targetToHierarchies : - equalities.targets.entrySet()) { - TypeVariable equalTarget = targetToHierarchies.getKey(); - AnnotationMirrorSet hierarchies = targetToHierarchies.getValue(); - - AnnotationMirrorSet equalTypeHierarchies = new AnnotationMirrorSet(remainingHierarchies); - equalTypeHierarchies.retainAll(hierarchies); - - Map otherTargetsEqualTypes = - constraintMap.getConstraints(equalTarget).equalities.types; - - AnnotationMirrorSet equalHierarchies = otherTargetsEqualTypes.get(remainingType); - if (equalHierarchies == null) { - equalHierarchies = new AnnotationMirrorSet(equalTypeHierarchies); - otherTargetsEqualTypes.put(remainingType, equalHierarchies); - updated = true; - - } else { - int size = equalHierarchies.size(); - equalHierarchies.addAll(equalTypeHierarchies); - updated = size == equalHierarchies.size(); - } - } - } - - return updated; - } - - /** - * Attempt to find a target which is equal to this target. - * - * @return a target equal to this target in all hierarchies, or null - */ - public @Nullable InferredTarget findEqualTarget(Equalities equalities, AnnotationMirrorSet tops) { - for (Map.Entry targetToHierarchies : - equalities.targets.entrySet()) { - TypeVariable equalTarget = targetToHierarchies.getKey(); - AnnotationMirrorSet hierarchies = targetToHierarchies.getValue(); - - // Now see if target is equal to equalTarget in all hierarchies - boolean targetIsEqualInAllHierarchies = hierarchies.size() == tops.size(); - if (targetIsEqualInAllHierarchies) { - return new InferredTarget(equalTarget, new AnnotationMirrorSet()); - - } else { - // annos in primaries that are not covered by the target's list of equal hierarchies - AnnotationMirrorSet requiredPrimaries = - new AnnotationMirrorSet(equalities.primaries.keySet()); - requiredPrimaries.removeAll(hierarchies); - - boolean typeWithPrimariesIsEqual = - (requiredPrimaries.size() + hierarchies.size()) == tops.size(); - if (typeWithPrimariesIsEqual) { - return new InferredTarget(equalTarget, requiredPrimaries); - } - } - } - - return null; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java deleted file mode 100644 index 49950439d73..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferenceResult.java +++ /dev/null @@ -1,170 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredTarget; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; - -/** - * Represents the result from inferring type arguments. InferenceResult is a map from: target type - * variable to (inferred type or target). - */ -public class InferenceResult extends LinkedHashMap { - private static final long serialVersionUID = 6911459752070485818L; - - /** - * Returns the set of targets that still don't have an inferred argument. - * - * @return the set of targets that still don't have an inferred argument - */ - public Set getRemainingTargets( - Set allTargets, boolean inferredTypesOnly) { - Set remainingTargets = new LinkedHashSet<>(allTargets); - - if (inferredTypesOnly) { - - for (TypeVariable target : keySet()) { - if (this.get(target) instanceof InferredType) { - remainingTargets.remove(target); - } - } - - } else { - remainingTargets.removeAll(this.keySet()); - } - - return remainingTargets; - } - - /** - * Returns true if we have inferred a concrete type for all targets. - * - * @param targets type variables to check - * @return true if we have inferred a concrete type for all targets - */ - public boolean isComplete(Set targets) { - for (TypeVariable target : targets) { - InferredValue inferred = this.get(target); - - if (inferred == null || inferred instanceof InferredTarget) { - return false; - } else if (inferred instanceof InferredType - && ((InferredType) inferred).type.getKind() == TypeKind.NULL) { - // NullType is not a valid type argument, so continue looking for the correct type. - return false; - } - } - return this.keySet().containsAll(targets); - } - - /** - * If we had a set of inferred results, (e.g. T1 = T2, T2 = T3, T3 = String) propagate any results - * we have (the above constraints become T1 = String, T2 = String, T3 = String) - */ - public void resolveChainedTargets() { - Map inferredTypes = new LinkedHashMap<>(this.size()); - - // TODO: we can probably make this a bit more efficient - boolean grew = true; - while (grew) { - grew = false; - for (Map.Entry inferred : this.entrySet()) { - TypeVariable target = inferred.getKey(); - InferredValue value = inferred.getValue(); - - if (value instanceof InferredType) { - inferredTypes.put(target, value); - - } else { - InferredTarget currentTarget = (InferredTarget) value; - InferredType equivalentType = - (InferredType) inferredTypes.get(((InferredTarget) value).target); - - if (equivalentType != null) { - grew = true; - AnnotatedTypeMirror type = equivalentType.type.deepCopy(); - type.replaceAnnotations(currentTarget.additionalAnnotations); - - InferredType newConstraint = new InferredType(type); - inferredTypes.put(currentTarget.target, newConstraint); - } - } - } - } - - this.putAll(inferredTypes); - } - - public Map toAtmMap() { - Map result = new LinkedHashMap<>(this.size()); - for (Map.Entry entry : this.entrySet()) { - InferredValue inferredValue = entry.getValue(); - if (inferredValue instanceof InferredType) { - result.put(entry.getKey(), ((InferredType) inferredValue).type); - } - } - - return result; - } - - /** - * Merges values in subordinate into this result, keeping the results form any type arguments that - * were already contained by this InferenceResult. - * - * @param subordinate a result which we wish to merge into this result - */ - public void mergeSubordinate(InferenceResult subordinate) { - Set previousKeySet = new LinkedHashSet<>(this.keySet()); - Set remainingSubKeys = new LinkedHashSet<>(subordinate.keySet()); - remainingSubKeys.removeAll(keySet()); - - for (TypeVariable target : previousKeySet) { - mergeTarget(target, subordinate); - } - - for (TypeVariable target : remainingSubKeys) { - this.put(target, subordinate.get(target)); - } - - resolveChainedTargets(); - } - - /** Performs a merge for a specific target, we keep only results that lead to a concrete type. */ - protected @Nullable InferredType mergeTarget(TypeVariable target, InferenceResult subordinate) { - InferredValue inferred = this.get(target); - if (inferred instanceof InferredTarget) { - InferredType newType = mergeTarget(((InferredTarget) inferred).target, subordinate); - - if (newType == null) { - InferredValue subValue = subordinate.get(target); - if (subValue instanceof InferredType) { - this.put(target, subValue); - return null; - } - } else { - if (newType.type.getKind() == TypeKind.NULL) { - // If the newType is null, then use the subordinate type, but with the - // primary annotations on null. - InferredValue subValue = subordinate.get(target); - if (subValue instanceof InferredType) { - AnnotatedTypeMirror copy = ((InferredType) subValue).type.deepCopy(); - copy.replaceAnnotations(newType.type.getAnnotations()); - newType = new InferredType(copy); - } - } - this.put(target, newType); - return newType; - } - - return null; - } // else - - return (InferredType) inferred; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java deleted file mode 100644 index f8e53d551ec..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/InferredValue.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.Collection; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.javacutil.AnnotationMirrorSet; - -/** - * When one of the constraint solvers infers that a the target has a given type/target in ALL - * qualifier hierarchies or that given an additional set of annotations that we know the target must - * hold we have covered all hierarchies then it creates an InferredValue to represent this - * inference. - * - *

          There are subclasses to represent two cases: - * - *

            - *
          • The target was inferred to be an AnnotatedTypeMirror - *
          • The target was inferred to be equal to another target - *
          - */ -public class InferredValue { - /** - * Indicates that a corresponding target was inferred to be the field "type" in all hierarchies. - */ - public static class InferredType extends InferredValue { - public final AnnotatedTypeMirror type; - - public InferredType(AnnotatedTypeMirror type) { - this.type = type; - } - } - - /** - * Indicates that a corresponding target was inferred to be the field "target" in the hierarchies - * not overridden by additionalAnnotations. - */ - public static class InferredTarget extends InferredValue { - public final TypeVariable target; - - /** - * Indicates that the inferred type should have these primary annotations and the remainder - * should come from the annotations inferred for target. - */ - public final AnnotationMirrorSet additionalAnnotations; - - public InferredTarget( - TypeVariable target, Collection additionalAnnotations) { - this.target = target; - this.additionalAnnotations = new AnnotationMirrorSet(additionalAnnotations); - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java deleted file mode 100644 index ad772acfd0f..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SubtypesSolver.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.util.typeinference.GlbUtil; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; -import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Subtypes; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; - -/** - * Infers type arguments by using the Greatest Lower Bound computation on the subtype relationships - * in a constraint map. - */ -public class SubtypesSolver { - - /** - * Infers type arguments using subtype constraints. - * - * @param remainingTargets targets for which we still need to infer a value - * @param constraints the set of constraints for all targets - * @return a mapping from target to inferred type. Note this class always infers concrete types - * and will not infer that the target is equivalent to another target. - */ - public InferenceResult solveFromSubtypes( - Set remainingTargets, - ConstraintMap constraints, - AnnotatedTypeFactory typeFactory) { - return glbSubtypes(remainingTargets, constraints, typeFactory); - } - - public InferenceResult glbSubtypes( - Set remainingTargets, - ConstraintMap constraints, - AnnotatedTypeFactory typeFactory) { - InferenceResult inferenceResult = new InferenceResult(); - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - - List targetsSubtypesLast = new ArrayList<>(remainingTargets); - - // If we have two type variables order them A then B - // this is required because we will use the fact that B must be below A - // when determining the glb of B - Collections.sort( - targetsSubtypesLast, - (o1, o2) -> { - if (types.isSubtype(o1, o2)) { - return 1; - } else if (types.isSubtype(o2, o1)) { - return -1; - } - return 0; - }); - - for (TypeVariable target : targetsSubtypesLast) { - Subtypes subtypes = constraints.getConstraints(target).subtypes; - - if (subtypes.types.isEmpty()) { - continue; - } - - propagatePreviousGlbs(subtypes, inferenceResult, subtypes.types); - - // if the subtypes size is only 1 then we need not do any GLBing on the underlying types - // but we may have primary annotations that need to be GLBed - AnnotationMirrorMap primaries = subtypes.primaries; - if (subtypes.types.size() == 1) { - Map.Entry entry = - subtypes.types.entrySet().iterator().next(); - AnnotatedTypeMirror supertype = entry.getKey().deepCopy(); - - for (AnnotationMirror top : entry.getValue()) { - AnnotationMirrorSet superAnnos = primaries.get(top); - // if it is null we're just going to use the anno already on supertype - if (superAnnos != null) { - AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(top); - superAnnos.add(supertypeAnno); - } - } - - if (!primaries.isEmpty()) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror glb = greatestLowerBound(subtypes.primaries.get(top), qualHierarchy); - supertype.replaceAnnotation(glb); - } - } - - inferenceResult.put(target, new InferredType(supertype)); - - } else { - - // GLB all of the types than combine this with the GLB of primary annotation - // constraints - AnnotatedTypeMirror glbType = GlbUtil.glbAll(subtypes.types, typeFactory); - if (glbType != null) { - if (!primaries.isEmpty()) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror glb = greatestLowerBound(subtypes.primaries.get(top), qualHierarchy); - AnnotationMirror currentAnno = glbType.getAnnotationInHierarchy(top); - - if (currentAnno == null) { - glbType.addAnnotation(glb); - } else if (glb != null) { - glbType.replaceAnnotation( - qualHierarchy.greatestLowerBoundQualifiersOnly(glb, currentAnno)); - } - } - } - - inferenceResult.put(target, new InferredType(glbType)); - } - } - } - - return inferenceResult; - } - - /** - * /** If the target corresponding to targetRecord must be a subtype of another target for which - * we have already determined a GLB, add that target's GLB to the list of subtypes to be GLBed for - * this target. - */ - protected static void propagatePreviousGlbs( - Subtypes targetSubtypes, - InferenceResult solution, - Map subtypesOfTarget) { - - for (Map.Entry subtypeTarget : - targetSubtypes.targets.entrySet()) { - InferredValue subtargetInferredGlb = solution.get(subtypeTarget.getKey()); - - if (subtargetInferredGlb != null) { - AnnotatedTypeMirror subtargetGlbType = ((InferredType) subtargetInferredGlb).type; - AnnotationMirrorSet subtargetAnnos = subtypesOfTarget.get(subtargetGlbType); - if (subtargetAnnos != null) { - // there is already an equivalent type in the list of subtypes, just add - // any hierarchies that are not in its list but are in the supertarget's list - subtargetAnnos.addAll(subtypeTarget.getValue()); - } else { - subtypesOfTarget.put(subtargetGlbType, subtypeTarget.getValue()); - } - } - } - } - - /** - * Returns the GLB of annos. - * - * @param annos a set of annotations in the same annotation hierarchy - * @param qualHierarchy the qualifier of the annotation hierarchy - * @return the GLB of annos - */ - private static AnnotationMirror greatestLowerBound( - Iterable annos, QualifierHierarchy qualHierarchy) { - Iterator annoIter = annos.iterator(); - AnnotationMirror glb = annoIter.next(); - - while (annoIter.hasNext()) { - glb = qualHierarchy.greatestLowerBoundQualifiersOnly(glb, annoIter.next()); - } - - return glb; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java deleted file mode 100644 index b8db39958e9..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/SupertypesSolver.java +++ /dev/null @@ -1,416 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; -import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType; -import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Equalities; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; - -/** - * Infers type arguments by using the Least Upper Bound computation on the supertype relationships - * in a constraint map. - */ -public class SupertypesSolver { - - /** - * Infers type arguments using supertype constraints. - * - * @param remainingTargets targets for which we still need to infer a value - * @param constraintMap the set of constraints for all targets - * @return a mapping from target to inferred type. Note this class always infers concrete types - * and will not infer that the target is equivalent to another target. - */ - public InferenceResult solveFromSupertypes( - Set remainingTargets, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - // infer a type for all targets that have supertype constraints - Lubs lubs = targetToTypeLubs(remainingTargets, constraintMap, typeFactory); - - // add the lub types to the outgoing solution - InferenceResult solution = new InferenceResult(); - for (TypeVariable target : remainingTargets) { - AnnotatedTypeMirror lub = lubs.getType(target); - AnnotationMirrorMap lubAnnos = lubs.getPrimaries(target); - - // we may have a partial solution present in the equality constraints, override - // any annotations found in the lub with annotations from the equality constraints - final InferredValue inferred; - if (lub != null) { - inferred = mergeLubTypeWithEqualities(target, lub, constraintMap, typeFactory); - } else if (lubAnnos != null) { - inferred = mergeLubAnnosWithEqualities(target, lubAnnos, constraintMap, typeFactory); - } else { - inferred = null; - } - - if (inferred != null) { - solution.put(target, inferred); - } - } - - return solution; - } - - /** - * We previously found a type that is equal to target but not in all hierarchies. Use the primary - * annotations from the lub type to fill in the missing annotations in this type. Use that type as - * the inferred argument. - * - *

          If we failed to infer any annotation for a given hierarchy, either previously from - * equalities or from the lub, return null. - */ - protected @Nullable InferredType mergeLubTypeWithEqualities( - TypeVariable target, - AnnotatedTypeMirror lub, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - Equalities equalities = constraintMap.getConstraints(target).equalities; - AnnotationMirrorSet tops = - new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); - - if (!equalities.types.isEmpty()) { - // there should be only one equality type if any at this point - Map.Entry eqEntry = - equalities.types.entrySet().iterator().next(); - AnnotatedTypeMirror equalityType = eqEntry.getKey(); - AnnotationMirrorSet equalityAnnos = eqEntry.getValue(); - - boolean failed = false; - for (AnnotationMirror top : tops) { - if (!equalityAnnos.contains(top)) { - AnnotationMirror lubAnno = lub.getAnnotationInHierarchy(top); - if (lubAnno == null) { - // If the LUB and the Equality were the SAME typevar, and the lub was - // unannotated then "NO ANNOTATION" is the correct choice. - if (lub.getKind() == TypeKind.TYPEVAR - && typeFactory.types.isSameType( - equalityType.getUnderlyingType(), lub.getUnderlyingType())) { - equalityAnnos.add(top); - } else { - failed = true; - } - - } else { - equalityType.replaceAnnotation(lubAnno); - equalityAnnos.add(top); - } - } - } - - if (!failed) { - return new InferredType(equalityType); - } - } - - return new InferredType(lub); - } - - /** - * We previously found a type that is equal to target but not in all hierarchies. Use the primary - * annotations from the lub annos to fill in the missing annotations in this type. Use that type - * as the inferred argument. - * - *

          If we failed to infer any annotation for a given hierarchy, either previously from - * equalities or from the lub, return null. - */ - protected @Nullable InferredType mergeLubAnnosWithEqualities( - TypeVariable target, - AnnotationMirrorMap lubAnnos, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - Equalities equalities = constraintMap.getConstraints(target).equalities; - AnnotationMirrorSet tops = - new AnnotationMirrorSet(typeFactory.getQualifierHierarchy().getTopAnnotations()); - - if (!equalities.types.isEmpty()) { - // there should be only equality type if any at this point - Map.Entry eqEntry = - equalities.types.entrySet().iterator().next(); - AnnotatedTypeMirror equalityType = eqEntry.getKey(); - AnnotationMirrorSet equalityAnnos = eqEntry.getValue(); - - boolean failed = false; - for (AnnotationMirror top : tops) { - if (!equalityAnnos.contains(top)) { - AnnotationMirror lubAnno = lubAnnos.get(top); - if (lubAnno == null) { - failed = true; - - } else { - equalityType.replaceAnnotation(lubAnno); - equalityAnnos.add(top); - } - } - } - - if (!failed) { - return new InferredType(equalityType); - } - } - - return null; - } - - /** Holds the least upper bounds for every target type parameter. */ - static class Lubs { - public final Map types = new LinkedHashMap<>(); - public final Map> primaries = - new LinkedHashMap<>(); - - public void addPrimaries(TypeVariable target, AnnotationMirrorMap primaries) { - this.primaries.put(target, new AnnotationMirrorMap<>(primaries)); - } - - public void addType(TypeVariable target, AnnotatedTypeMirror type) { - types.put(target, type); - } - - public AnnotationMirrorMap getPrimaries(TypeVariable target) { - return primaries.get(target); - } - - public AnnotatedTypeMirror getType(TypeVariable target) { - return types.get(target); - } - } - - /** - * For each target, lub all of the types/annotations in its supertypes constraints and return the - * lubs. - * - * @param remainingTargets targets that do not already have an inferred type argument - * @param constraintMap the set of constraints for all targets - * @return the lub determined for each target that has at least 1 supertype constraint - */ - private Lubs targetToTypeLubs( - Set remainingTargets, - ConstraintMap constraintMap, - AnnotatedTypeFactory typeFactory) { - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - - Lubs solution = new Lubs(); - - AnnotationMirrorMap lubOfPrimaries = new AnnotationMirrorMap<>(); - - List targetsSupertypesLast = new ArrayList<>(remainingTargets); - - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - // If we have two type variables order them B then A - // this is required because we will use the fact that A must be above B - // when determining the LUB of A - Collections.sort( - targetsSupertypesLast, - (o1, o2) -> { - if (types.isSubtype(o1, o2)) { - return -1; - } else if (types.isSubtype(o2, o1)) { - return 1; - } - return 0; - }); - - for (TypeVariable target : targetsSupertypesLast) { - TargetConstraints targetRecord = constraintMap.getConstraints(target); - AnnotationMirrorMap subtypeAnnos = targetRecord.supertypes.primaries; - Map subtypesOfTarget = - targetRecord.supertypes.types; - - // If this target is a supertype of other targets and those targets have already been - // lubbed add that LUB to the list of lubs for this target (as it must be above this - // target). - propagatePreviousLubs(targetRecord, solution, subtypesOfTarget); - - // lub all the primary annotations and put them in lubOfPrimaries - lubPrimaries(lubOfPrimaries, subtypeAnnos, tops, qualHierarchy); - solution.addPrimaries(target, lubOfPrimaries); - - if (!subtypesOfTarget.isEmpty()) { - AnnotatedTypeMirror lub = leastUpperBound(target, typeFactory, subtypesOfTarget); - AnnotationMirrorSet effectiveLubAnnos = - new AnnotationMirrorSet(lub.getEffectiveAnnotations()); - - for (AnnotationMirror lubAnno : effectiveLubAnnos) { - AnnotationMirror hierarchy = qualHierarchy.getTopAnnotation(lubAnno); - AnnotationMirror primaryLub = lubOfPrimaries.get(hierarchy); - - if (primaryLub != null) { - if (qualHierarchy.isSubtypeQualifiersOnly(lubAnno, primaryLub) - && !AnnotationUtils.areSame(lubAnno, primaryLub)) { - lub.replaceAnnotation(primaryLub); - } - } - } - - solution.addType(target, lub); - } - } - return solution; - } - - /** - * If the target corresponding to targetRecord must be a supertype of another target for which we - * have already determined a lub, add that target's lub to this list. - */ - protected static void propagatePreviousLubs( - TargetConstraints targetRecord, - Lubs solution, - Map subtypesOfTarget) { - - for (Map.Entry supertypeTarget : - targetRecord.supertypes.targets.entrySet()) { - AnnotatedTypeMirror supertargetLub = solution.getType(supertypeTarget.getKey()); - if (supertargetLub != null) { - AnnotationMirrorSet supertargetTypeAnnos = subtypesOfTarget.get(supertargetLub); - if (supertargetTypeAnnos != null) { - // there is already an equivalent type in the list of subtypes, just add - // any hierarchies that are not in its list but are in the supertarget's list - supertargetTypeAnnos.addAll(supertypeTarget.getValue()); - } else { - subtypesOfTarget.put(supertargetLub, supertypeTarget.getValue()); - } - } - } - } - - /** - * For each qualifier hierarchy in tops, take the lub of the annos in subtypeAnnos that correspond - * to that hierarchy place the lub in lubOfPrimaries. - */ - protected static void lubPrimaries( - AnnotationMirrorMap lubOfPrimaries, - AnnotationMirrorMap subtypeAnnos, - AnnotationMirrorSet tops, - QualifierHierarchy qualHierarchy) { - - lubOfPrimaries.clear(); - for (AnnotationMirror top : tops) { - AnnotationMirrorSet annosInHierarchy = subtypeAnnos.get(top); - if (annosInHierarchy != null && !annosInHierarchy.isEmpty()) { - lubOfPrimaries.put(top, leastUpperBound(annosInHierarchy, qualHierarchy)); - } else { - // If there are no annotations for this hierarchy, add bottom. This happens - // when the only constraint for a type variable is a use that is annotated in - // this hierarchy. Calls to the method below have this property. - // void method(@NonNull T t) {} - lubOfPrimaries.put(top, qualHierarchy.getBottomAnnotation(top)); - } - } - } - - /** - * For each type in typeToHierarchies, if that type does not have a corresponding annotation for a - * given hierarchy replace it with the corresponding value in lowerBoundAnnos. - */ - public static AnnotatedTypeMirror groundMissingHierarchies( - Map.Entry typeToHierarchies, - AnnotationMirrorMap lowerBoundAnnos) { - AnnotationMirrorSet presentHierarchies = typeToHierarchies.getValue(); - AnnotationMirrorSet missingAnnos = new AnnotationMirrorSet(); - for (AnnotationMirror top : lowerBoundAnnos.keySet()) { - if (!presentHierarchies.contains(top)) { - missingAnnos.add(lowerBoundAnnos.get(top)); - } - } - - if (!missingAnnos.isEmpty()) { - AnnotatedTypeMirror copy = typeToHierarchies.getKey().deepCopy(); - copy.replaceAnnotations(missingAnnos); - - return copy; - } - - return typeToHierarchies.getKey(); - } - - /** - * Successively calls least upper bound on the elements of types. Unlike - * AnnotatedTypes.leastUpperBound, this method will box primitives if necessary - */ - public static AnnotatedTypeMirror leastUpperBound( - TypeVariable target, - AnnotatedTypeFactory typeFactory, - Map types) { - - QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); - AnnotatedTypeVariable targetsDeclaredType = - (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement()); - AnnotationMirrorMap lowerBoundAnnos = - TypeArgInferenceUtil.createHierarchyMap( - new AnnotationMirrorSet(targetsDeclaredType.getLowerBound().getEffectiveAnnotations()), - qualHierarchy); - - Iterator> typesIter = - types.entrySet().iterator(); - if (!typesIter.hasNext()) { - throw new BugInCF("Calling LUB on empty list."); - } - - // If a constraint implies that a type parameter Ti is a supertype of an annotated type - // mirror Ai but only in a subset of all qualifier hierarchies then for all other qualifier - // hierarchies replace the primary annotation on Ai with the lowest possible annotation - // (ensuring that it won't be the LUB unless there are no other constraints, or all other - // constraints imply the bottom annotation is the LUB). Note: Even if we choose bottom as - // the lub here, the assignment context may raise this annotation. - Map.Entry head = typesIter.next(); - - AnnotatedTypeMirror lubType = groundMissingHierarchies(head, lowerBoundAnnos); - AnnotatedTypeMirror nextType = null; - while (typesIter.hasNext()) { - nextType = groundMissingHierarchies(typesIter.next(), lowerBoundAnnos); - - if (lubType.getKind().isPrimitive()) { - if (!nextType.getKind().isPrimitive()) { - lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType); - } - } else if (nextType.getKind().isPrimitive()) { - if (!lubType.getKind().isPrimitive()) { - nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType); - } - } - lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType); - } - - return lubType; - } - - /** - * Returns the lub of all the annotations in annos. - * - * @param annos a set of annotations in the same annotation hierarchy - * @param qualHierarchy the qualifier hierarchy that contains each annotation - * @return the lub of all the annotations in annos - */ - private static AnnotationMirror leastUpperBound( - Iterable annos, QualifierHierarchy qualHierarchy) { - Iterator annoIter = annos.iterator(); - AnnotationMirror lub = annoIter.next(); - - while (annoIter.hasNext()) { - lub = qualHierarchy.leastUpperBoundQualifiersOnly(lub, annoIter.next()); - } - - return lub; - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java deleted file mode 100644 index e405dcb8177..00000000000 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/TargetConstraints.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.checkerframework.framework.util.typeinference.solver; - -import java.util.LinkedHashMap; -import java.util.Map; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; - -/** - * TargetConstraints represents the set of all TUConstraints for which target was the type - * parameter, i.e. the T in the TUConstraint. Unlike AF/TU Constraints, this class holds multiple - * constraints and is mutated during solving (where the TU/AF Constraints are immutable). - * - * @see org.checkerframework.framework.util.typeinference.solver.ConstraintMap - */ -public class TargetConstraints { - /** - * The type parameter for which we are inferring a type argument. All constraints in this object - * are related to this target. - */ - public final TypeVariable target; - - public final Equalities equalities; - - /** - * The target is the supertype in this case, that these are supertype constraints in which target - * is the supertype. These are NOT supertypes of the target. - */ - public final Supertypes supertypes; - - /** - * The target is the supertype in this case, that these are subtype constraints in which target is - * the subtype. These are NOT subtypes of the target. - */ - public final Subtypes subtypes; - - public TargetConstraints(TypeVariable target) { - this.target = target; - this.equalities = new Equalities(); - this.supertypes = new Supertypes(); - this.subtypes = new Subtypes(); - } - - protected static class Equalities { - // Map( hierarchy top -> exact annotation in hierarchy) - public final AnnotationMirrorMap primaries = new AnnotationMirrorMap<>(); - - // Map( type -> hierarchy top for which the primary annotation of type is equal to the - // primary annotation of the target) - // note all components and underlying types are EXACTLY equal to the key to this map - public final Map types = new LinkedHashMap<>(); - - // Map( type -> hierarchy top for which the primary annotation of target is equal to the - // primary annotation of the target) - // note all components and underlying types are EXACTLY equal to the key to this map - public final Map targets = new LinkedHashMap<>(); - - public void clear() { - primaries.clear(); - types.clear(); - targets.clear(); - } - } - - // remember these are constraint in which target is the supertype - protected static class Supertypes { - // Map( hierarchy top -> annotations that are subtypes to target in hierarchy) - public final AnnotationMirrorMap primaries = new AnnotationMirrorMap<>(); - - // Map( type -> hierarchy tops for which the primary annotations of type are subtypes of the - // primary annotations of the target) - // note all components and underlying types must uphold the supertype relationship in all - // hierarchies - public final Map types = new LinkedHashMap<>(); - - // Map( otherTarget -> hierarchy tops for which the primary annotations of otherTarget are - // subtypes of the primary annotations of the target) - // note all components and underlying types must uphold the subtype relationship in all - // hierarchies - public final Map targets = new LinkedHashMap<>(); - - public void clear() { - primaries.clear(); - types.clear(); - targets.clear(); - } - } - - /** Remember these are constraints in which the target is the subtype. */ - protected static class Subtypes { - /** Create a new Subtypes. */ - public Subtypes() {} - - /** Map from hierarchy top to annotations that are supertypes to target in hierarchy. */ - public final AnnotationMirrorMap primaries = new AnnotationMirrorMap<>(); - - /** - * Map from type to hierarchy tops for which the primary annotations of type are supertypes of - * the primary annotations of the target. - * - *

          Note all components and underlying types must uphold the supertype relationship in all - * hierarchies. - */ - public final Map types = new LinkedHashMap<>(); - - /** - * Map from otherTarget to hierarchy tops for which the primary annotations of otherTarget are - * supertypes of the primary annotations of the target. - * - *

          Note all components and underlying types must uphold the subtype relationship in all - * hierarchies. - */ - public final Map targets = new LinkedHashMap<>(); - - public void clear() { - primaries.clear(); - types.clear(); - targets.clear(); - } - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java new file mode 100644 index 00000000000..e2a96905afe --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java @@ -0,0 +1,259 @@ +package org.checkerframework.framework.util.typeinference8; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.TreePath; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.util.typeinference8.types.ContainsInferenceVariable; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; + +/** Implementation of type argument inference. */ +public class DefaultTypeArgumentInference implements TypeArgumentInference { + + /** Current inference problem that is being solved. */ + private InvocationTypeInference java8Inference = null; + + /** Stack of all inference problems currently being solved. */ + private final ArrayDeque java8InferenceStack = new ArrayDeque<>(); + + /** Creates a DefaultTypeArgumentInference. */ + public DefaultTypeArgumentInference() {} + + @SuppressWarnings("interning:not.interned") + @Override + public InferenceResult inferTypeArgs( + AnnotatedTypeFactory typeFactory, + ExpressionTree expressionTree, + AnnotatedExecutableType methodType) { + TreePath pathToExpression = typeFactory.getPath(expressionTree); + + // In order to find the type arguments for expressionTree, type arguments for outer method calls + // may need be inferred, too. + // So, first find the outermost tree that is required to infer the type arguments for + // expressionTree + ExpressionTree outerTree = outerInference(expressionTree, pathToExpression.getParentPath()); + + for (InvocationTypeInference i : java8InferenceStack) { + if (i.getInferenceExpression() == outerTree) { + // Inference is running and is asking for the type of the method before type arguments are + // substituted. So don't infer any type arguments. This happens when getting the type of a + // lambda's returned expression. + List instantiated = new ArrayList<>(); + Theta m = i.context.maps.get(expressionTree); + if (m == null) { + return InferenceResult.emptyResult(); + } + m.values() + .forEach( + var -> { + if (var.getInstantiation() != null) { + instantiated.add(var); + } + }); + if (instantiated.isEmpty()) { + return InferenceResult.emptyResult(); + } + return new InferenceResult(instantiated, false, false, ""); + } + } + AnnotatedExecutableType outerMethodType; + if (outerTree != expressionTree) { + if (outerTree.getKind() == Tree.Kind.METHOD_INVOCATION) { + pathToExpression = typeFactory.getPath(outerTree); + outerMethodType = + typeFactory.methodFromUseWithoutTypeArgInference((MethodInvocationTree) outerTree) + .executableType; + } else if (outerTree.getKind() == Tree.Kind.NEW_CLASS) { + pathToExpression = typeFactory.getPath(outerTree); + outerMethodType = + typeFactory.constructorFromUseWithoutTypeArgInference((NewClassTree) outerTree) + .executableType; + } else if (outerTree.getKind() == Kind.MEMBER_REFERENCE) { + pathToExpression = typeFactory.getPath(outerTree); + outerMethodType = null; + } else { + throw new BugInCF( + "Unexpected kind of outer expression to infer type arguments: %s", outerTree.getKind()); + } + } else { + outerMethodType = methodType; + } + if (java8Inference != null) { + java8InferenceStack.push(java8Inference); + } + try { + java8Inference = new InvocationTypeInference(typeFactory, pathToExpression); + if (outerTree.getKind() == Kind.MEMBER_REFERENCE) { + return java8Inference.infer((MemberReferenceTree) outerTree); + } else { + InferenceResult result = java8Inference.infer(outerTree, outerMethodType); + if (!result.getResults().containsKey(expressionTree) + && expressionTree.getKind() == Kind.MEMBER_REFERENCE) { + java8Inference.context.pathToExpression = typeFactory.getPath(expressionTree); + return java8Inference.infer((MemberReferenceTree) expressionTree); + } + return result.swapTypeVariables(methodType, expressionTree); + } + } catch (Exception ex) { + // This should never happen, if javac infers type arguments so should the Checker + // Framework. However, given how buggy javac inference is, this probably will, so deal with it + // gracefully. + return new InferenceResult( + Collections.emptyList(), + false, + true, + "An exception occurred: " + ex.getLocalizedMessage()); + } finally { + if (!java8InferenceStack.isEmpty()) { + java8Inference = java8InferenceStack.pop(); + } else { + java8Inference = null; + } + } + } + + /** + * Returns the outermost tree required to find the type of {@code tree}. + * + * @param tree tree that may need an outer tree to find the type + * @param parentPath path to the parent of {@code tree} or null if no such parent exists + * @return the outermost tree required to find the type of {@code tree} + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public static ExpressionTree outerInference(ExpressionTree tree, @Nullable TreePath parentPath) { + if (parentPath == null) { + return tree; + } + if (!TreeUtils.isPolyExpression(tree)) { + return tree; + } + + Tree parentTree = parentPath.getLeaf(); + switch (parentTree.getKind()) { + case PARENTHESIZED: + case CONDITIONAL_EXPRESSION: + ExpressionTree outer = + outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + if (outer == parentTree) { + return tree; + } + return outer; + case METHOD_INVOCATION: + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) parentTree; + if (!methodInvocationTree.getTypeArguments().isEmpty()) { + return tree; + } + ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); + if (methodElement.getTypeParameters().isEmpty()) { + return tree; + } + if (argumentNeedsInference( + methodElement, methodInvocationTree.getArguments(), tree, null)) { + return outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + } + return tree; + case NEW_CLASS: + NewClassTree newClassTree = (NewClassTree) parentTree; + if (!newClassTree.getTypeArguments().isEmpty()) { + return tree; + } + ExecutableElement constructor = TreeUtils.elementFromUse(newClassTree); + if (argumentNeedsInference(constructor, newClassTree.getArguments(), tree, newClassTree)) { + return outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + } + return tree; + case RETURN: + TreePath parentParentPath = parentPath.getParentPath(); + if (parentParentPath.getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + return outerInference( + (ExpressionTree) parentParentPath.getLeaf(), parentParentPath.getParentPath()); + } + return tree; + default: + if (TreeUtils.isYield(parentTree)) { + parentPath = parentPath.getParentPath(); + // The first parent is a case statement. + parentPath = parentPath.getParentPath(); + parentTree = parentPath.getLeaf(); + } + if (TreeUtils.isSwitchExpression(parentTree)) { + // case SWITCH_EXPRESSION: + ExpressionTree outerTree = + outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + if (outerTree == parentTree) { + return tree; + } + return outerTree; + } + return tree; + } + } + + /** + * Returns true if {@code argTree} is pseudo-assigned to a parameter in {@code executableElement} + * that contains a type variable that needs to be inferred. + * + * @param executableElement symbol of method or constructor + * @param argTrees all the arguments of the method or constructor call + * @param argTree the argument of interest + * @param newClassTree the new class tree or {@code null} if {@code executableElement} is a method + * @return true if {@code argTree} is pseudo-assigned to a parameter in {@code executableElement} + * that contains a type variable that needs to be inferred. + */ + private static boolean argumentNeedsInference( + ExecutableElement executableElement, + List argTrees, + Tree argTree, + @Nullable NewClassTree newClassTree) { + int index = -1; + for (int i = 0; i < argTrees.size(); i++) { + @SuppressWarnings("interning") // looking for exact argTree. + boolean found = argTrees.get(i) == argTree; + if (found) { + index = i; + } + } + if (index == -1) { + throw new BugInCF("Argument argTree not found in list of arguments."); + } + + ExecutableType executableType = (ExecutableType) executableElement.asType(); + // There are fewer parameters than arguments if this is a var args method. + if (executableType.getParameterTypes().size() <= index) { + index = executableType.getParameterTypes().size() - 1; + } + TypeMirror param = executableType.getParameterTypes().get(index); + + if (executableElement.getKind() == ElementKind.CONSTRUCTOR) { + List list = new ArrayList<>(executableType.getTypeVariables()); + if (newClassTree != null && TreeUtils.isDiamondTree(newClassTree)) { + DeclaredType declaredType = + (DeclaredType) ((DeclaredType) TreeUtils.typeOf(newClassTree)).asElement().asType(); + for (TypeMirror typeVar : declaredType.getTypeArguments()) { + list.add((TypeVariable) typeVar); + } + } + return ContainsInferenceVariable.hasAnyTypeVariable(list, param); + } + return ContainsInferenceVariable.hasAnyTypeVariable(executableType.getTypeVariables(), param); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java new file mode 100644 index 00000000000..59d2259cd9c --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java @@ -0,0 +1,163 @@ +package org.checkerframework.framework.util.typeinference8; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.Tree; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.javacutil.TypesUtils; + +/** The result of type argument inferrece. */ +public class InferenceResult { + + /** An empty inference result. */ + @SuppressWarnings("interning:assignment") + private static final @InternedDistinct InferenceResult emptyResult = + new InferenceResult(Collections.emptyList(), false, false, ""); + + /** + * Returns an empty inference result. + * + * @return an empty inference result + */ + public static InferenceResult emptyResult() { + return emptyResult; + } + + /** + * A mapping from a tree that needs type argument inference to a map from type parameter to its + * inferred annotated type argument. If inference failed, this map will be empty. + */ + private final Map> results; + + /** + * If true, then type argument inference failed because an annotated type could not be inferred. + */ + private final boolean annoInferenceFailed; + + /** Whether unchecked conversion was necessary to infer the type arguments. */ + private final boolean uncheckedConversion; + + /** If {@code annoInferenceFailed}, then this is the error message to report to the user. */ + private final String errorMsg; + + /** + * Creates an inference result. + * + * @param variables instantiated variables + * @param uncheckedConversion where unchecked conversion was required to infer the type arguments + * @param annoInferenceFailed whether inference failed because of annotations + * @param errorMsg message to report to users if inference failed + */ + public InferenceResult( + Collection variables, + boolean uncheckedConversion, + boolean annoInferenceFailed, + String errorMsg) { + this.results = convert(variables); + this.uncheckedConversion = uncheckedConversion; + this.annoInferenceFailed = annoInferenceFailed; + this.errorMsg = errorMsg; + } + + /** + * A mapping from a tree that needs type argument inference to a map from type parameter to its + * inferred annotated type argument. If inference failed, this map will be empty. + * + * @return mapping from a tree that needs type argument inference to a map from type parameter to + * its inferred annotated type argument + */ + public Map> getResults() { + return results; + } + + /** + * Whether unchecked conversion was necessary to infer the type arguments. + * + * @return whether unchecked conversion was necessary to infer the type arguments + */ + public boolean isUncheckedConversion() { + return uncheckedConversion; + } + + /** + * Whether type argument inference failed because an annotated type could not be inferred. + * + * @return Whether type argument inference failed because an annotated type could not be inferred + */ + public boolean inferenceFailed() { + return annoInferenceFailed; + } + + /** + * Convert the instantiated variables to a map from expression tree to a map from type variable to + * its type argument. + * + * @param variables instantiated variables + * @return a map from expression tree to a map from type variable to its type argument + */ + private static Map> convert( + Collection variables) { + Map> map = new HashMap<>(); + for (Variable variable : variables) { + Map typeMap = + map.computeIfAbsent(variable.getInvocation(), k -> new HashMap<>()); + typeMap.put(variable.getJavaType(), variable.getInstantiation().getAnnotatedType()); + } + return map; + } + + /** + * Returns a mapping from type variable to its type argument for the {@code expressionTree}. + * + * @param expressionTree a tree for which type arguments were inferred + * @return a mapping from type variable to its type argument for the {@code expressionTree} + */ + public Map getTypeArgumentsForExpression( + ExpressionTree expressionTree) { + if (this == emptyResult || results.isEmpty()) { + return Collections.emptyMap(); + } + return results.get(expressionTree); + } + + /** + * An error message to report to the user. + * + * @return an error message to report to the user + */ + public String getErrorMsg() { + return errorMsg; + } + + /** + * Switch the {@link TypeVariable}s in {@code results} with the {@code TypeVariable}s in {@code + * methodType} so that the {@code TypeVariable}s in the result are {@code .equals}. {@link + * TypesUtils#areSame(TypeVariable, TypeVariable)} is used to decide which type variables to swap. + * + * @param methodType annotated method type + * @param tree method invocation tree + * @return this + */ + /* package-private */ InferenceResult swapTypeVariables( + AnnotatedExecutableType methodType, ExpressionTree tree) { + Map map = results.get(tree); + for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { + TypeVariable typeVariable = tv.getUnderlyingType(); + for (TypeVariable t : new HashSet<>(map.keySet())) { + if (TypesUtils.areSame(t, typeVariable)) { + map.put(typeVariable, map.remove(t)); + } + } + } + return this; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java new file mode 100644 index 00000000000..a7da602c1b4 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java @@ -0,0 +1,611 @@ +package org.checkerframework.framework.util.typeinference8; + +import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.util.typeinference8.bound.BoundSet; +import org.checkerframework.framework.util.typeinference8.bound.CaptureBound; +import org.checkerframework.framework.util.typeinference8.constraint.AdditionalArgument; +import org.checkerframework.framework.util.typeinference8.constraint.CheckedExceptionConstraint; +import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.Expression; +import org.checkerframework.framework.util.typeinference8.constraint.TypeConstraint; +import org.checkerframework.framework.util.typeinference8.constraint.Typing; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.InferenceType; +import org.checkerframework.framework.util.typeinference8.types.InvocationType; +import org.checkerframework.framework.util.typeinference8.types.ProperType; +import org.checkerframework.framework.util.typeinference8.types.UseOfVariable; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.util.FalseBoundException; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Resolution; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.SwitchExpressionScanner; +import org.checkerframework.javacutil.SwitchExpressionScanner.FunctionalSwitchExpressionScanner; +import org.checkerframework.javacutil.TreeUtils; + +/** + * Performs invocation type inference as described in JLS Section + * 18.5.2. Main entry point is {@link InvocationTypeInference#infer(ExpressionTree, + * AnnotatedExecutableType)} + * + *

          Invocation type inference is the process by which method type arguments are inferred for a + * given method invocation. An overview of the process is given below. + * + *

          1. Inference creates an inference variable for each method type argument for a given method + * invocation. Each inference variable may have zero or more upper, lower, and equal bounds. The + * bounds of an inference variable are initially the bounds on the type argument. More bounds may be + * infered in later steps. + * + *

          Bounds are between an inference variable and an abstract type. {@link AbstractType}s are + * type-like structures that might include inference variables. Abstract types might also be an + * inference variable or a type without any inference variables, which is also know as a proper + * type. + * + *

          An inference variable is represented by a {@link Variable} object which holds bounds for the + * inference variable, in an {@link + * org.checkerframework.framework.util.typeinference8.types.VariableBounds} object. Additional + * inference variables may be created in later steps if any subexpression of the method invocation + * requires type inference. + * + *

          2. Next, inference creates constraints between the arguments to the method invocation and its + * formal parameters. Also, for non-void methods, a constraint between the declared return type and + * the "target type" of the method invocation is created. "Target types" are defined in JLS Chapter 5. For + * example, the target type of a method invocation assigned to a variable is the type of the + * variable. + * + *

          Constraints are represented by {@link TypeConstraint} objects and are between abstract types + * (see {@link AbstractType}) and either expressions (see {@link Expression}) or other abstract + * types. A constraint might also be an abstract type that might be thrown by the method invocation + * (see {@link CheckedExceptionConstraint}). Groups of constraints are stored in {@link + * ConstraintSet}s. + * + *

          3. Next, these constraints are "reduced" producing bounds on the inference variables. + * Reduction depends on the kind of constraint and is defined in JLS section + * 18.2. In this code base, constraints are reduced via {@link + * ConstraintSet#reduce(Java8InferenceContext)}. + * + *

          4. The inference variables' bounds are then "incorporated" which produces more bounds and/or + * constraints that must then be "reduced" or "incorporated". Incorporation and reduction continue + * until no new bounds or constraints are produced. Bounds are incorporated via {@link + * BoundSet#incorporateToFixedPoint(BoundSet)}. Incorporation in defined in JLS section + * 18.3. + * + *

          5. Finally, a type for each inference variable is computed by "resolving" the bounds. + * Variables are resolved via {@link Resolution#resolve(Collection, BoundSet, + * Java8InferenceContext)}. Resolution is defined in the JLS section + * 18.4. + * + *

          An object of this class stores information about some particular invocation that requires + * inference. + */ +public class InvocationTypeInference { + + /** Checker used to issue errors/warnings. */ + protected final SourceChecker checker; + + /** Stores information about the current inference problem being solved. */ + protected final Java8InferenceContext context; + + /** Tree for which type arguments are being inferred. */ + protected final Tree inferenceExpression; + + /** + * Creates an inference problem. + * + * @param factory the annotated type factory to use + * @param pathToExpression path to the expression for which inference is preformed + */ + public InvocationTypeInference(AnnotatedTypeFactory factory, TreePath pathToExpression) { + this.checker = factory.getChecker(); + this.context = new Java8InferenceContext(factory, pathToExpression, this); + this.inferenceExpression = pathToExpression.getLeaf(); + } + + /** + * Returns the tree for which inference is being inferred. + * + * @return the tree for which inference is being inferred + */ + public Tree getInferenceExpression() { + return inferenceExpression; + } + + /** + * Perform invocation type inference on {@code invocation}. See JLS + * 18.5.2. + * + * @param invocation invocation which needs inference + * @param methodType type of the method invocation + * @return the result of inference + * @throws FalseBoundException if inference fails because of the java types + */ + public InferenceResult infer(ExpressionTree invocation, AnnotatedExecutableType methodType) + throws FalseBoundException { + ExecutableType e = methodType.getUnderlyingType(); + InvocationType invocationType = new InvocationType(methodType, e, invocation, context); + ProperType target = context.inferenceTypeFactory.getTargetType(); + List args; + if (invocation.getKind() == Tree.Kind.METHOD_INVOCATION) { + args = ((MethodInvocationTree) invocation).getArguments(); + } else { + args = ((NewClassTree) invocation).getArguments(); + } + + Theta map = + context.inferenceTypeFactory.createThetaForInvocation(invocation, invocationType, context); + BoundSet b2 = createB2(invocationType, args, map); + BoundSet b3; + if (target != null && TreeUtils.isPolyExpression(invocation)) { + b3 = createB3(b2, invocation, invocationType, target, map); + } else { + b3 = b2; + } + ConstraintSet c = createC(invocationType, args, map); + + BoundSet b4 = getB4(b3, c); + b4.resolve(); + return new InferenceResult( + b4.getInstantiatedVariables(), + b4.isUncheckedConversion(), + b4.annoInferenceFailed, + b4.errorMsg); + } + + /** + * Perform invocation type inference on {@code invocation}. See JLS + * 18.5.2. + * + * @param invocation member reference tree + * @return the result of inference + * @throws FalseBoundException if inference fails because of the java types + */ + public InferenceResult infer(MemberReferenceTree invocation) throws FalseBoundException { + + ProperType target = context.inferenceTypeFactory.getTargetType(); + AbstractType target1 = + InferenceType.create( + target.getAnnotatedType(), + target.getJavaType(), + context.maps.get(context.pathToExpression.getParentPath().getLeaf()), + context); + target = (ProperType) target1.applyInstantiations(); + if (target == null) { + throw new BugInCF("Target of method reference should not be null: %s", invocation); + } + + InvocationType compileTimeDecl = + context.inferenceTypeFactory.compileTimeDeclarationType(invocation); + Theta map = + context.inferenceTypeFactory.createThetaForMethodReference( + invocation, compileTimeDecl, context); + BoundSet b2 = createB2MethodRef(compileTimeDecl, target.getFunctionTypeParameterTypes(), map); + AbstractType r = target.getFunctionTypeReturnType(); + BoundSet b3; + if (r == null || r.getTypeKind() == TypeKind.VOID) { + b3 = b2; + } else { + b3 = createB3(b2, invocation, compileTimeDecl, r, map); + } + + List thetaPrime = b3.resolve(); + + return new InferenceResult( + thetaPrime, b3.isUncheckedConversion(), b3.annoInferenceFailed, b3.errorMsg); + } + + /** + * Creates the bound set used to determine whether a method is applicable. This method is called + * B2 in JLS + * Section 18.5.1. + * + *

          It does this by: + * + *

            + *
          1. Creating the inference variables and initializing their bounds based on the + * type parameter declaration. + *
          2. Adding any bounds implied by the throws clause of {@code methodType}. + *
          3. Constructing constraints between formal parameters and arguments that are + * "pertinent to applicability" (See JLS + * Section 15.12.2.2). Generally, all arguments are applicable except: inexact method + * reference, implicitly typed lambdas, or explicitly typed lambda whose return + * expression(s) are not pertinent. + *
          4. Reducing and incorporating those constraints which finally produces B2. + *
          + * + * @param methodType the type of the method or constructor invoked + * @param args argument expression tress + * @param map map of type variables to (inference) variables + * @return bound set used to determine whether a method is applicable + */ + public BoundSet createB2( + InvocationType methodType, List args, Theta map) { + BoundSet b0 = BoundSet.initialBounds(map, context); + + // For all i (1 <= i <= p), if Pi appears in the throws clause of m, then the bound throws + // alphai is implied. These bounds, if any, are incorporated with B0 to produce a new bound + // set, B1. + for (AbstractType thrownType : methodType.getThrownTypes(map)) { + if (thrownType.isUseOfVariable()) { + ((UseOfVariable) thrownType).setHasThrowsBound(true); + } + } + + BoundSet b1 = b0; + ConstraintSet c = new ConstraintSet(); + List formals = methodType.getParameterTypes(map, args.size()); + + for (int i = 0; i < formals.size(); i++) { + ExpressionTree ei = args.get(i); + AbstractType fi = formals.get(i); + + if (!notPertinentToApplicability(ei, fi.isUseOfVariable())) { + c.add(new Expression(ei, fi)); + } + } + + BoundSet newBounds = c.reduce(context); + assert !newBounds.containsFalse(); + b1.incorporateToFixedPoint(newBounds); + + return b1; + } + + /** + * Same as {@link #createB2(InvocationType, List, Theta)}, but for method references. A list of + * types is used instead of a list of arguments. These types are the types of the formal + * parameters of function type of target type of the method reference. + * + * @param methodType the type of the method or constructor invoked + * @param args types to use as arguments + * @param map map of type variables to (inference) variables + * @return bound set used to determine whether a method is applicable + */ + public BoundSet createB2MethodRef(InvocationType methodType, List args, Theta map) { + BoundSet b0 = BoundSet.initialBounds(map, context); + + // For all i (1 <= i <= p), if Pi appears in the throws clause of m, then the bound throws + // alphai is implied. These bounds, if any, are incorporated with B0 to produce a new bound + // set, B1. + for (AbstractType thrownType : methodType.getThrownTypes(map)) { + if (thrownType.isUseOfVariable()) { + ((UseOfVariable) thrownType).setHasThrowsBound(true); + } + } + + BoundSet b1 = b0; + ConstraintSet c = new ConstraintSet(); + List formals = methodType.getParameterTypes(map, args.size()); + if (TreeUtils.isLikeDiamondMemberReference(methodType.getInvocation())) { + // https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.13.1 + // If ReferenceType is a raw type, and there exists a parameterization of this type, G<.. + // .>, that is a supertype of P1, the type to search is the result of capture conversion + // (§5.1.10) applied to G<...>; otherwise, the type to search is the same as the type of + // the first search. Type arguments, if any, are given by the method reference expression. + AbstractType receiver = args.remove(0); + args.add(0, receiver.capture(context)); + } + + for (int i = 0; i < formals.size(); i++) { + AbstractType ei = args.get(i); + AbstractType fi = formals.get(i); + c.add(new Typing(ei, fi, Kind.TYPE_COMPATIBILITY)); + } + + BoundSet newBounds = c.reduce(context); + assert !newBounds.containsFalse(); + b1.incorporateToFixedPoint(newBounds); + + return b1; + } + + /** + * Creates constraints against the target type of {@code invocation} and then reduces and + * incorporates those constraints with {@code b2}. (See JLS + * 18.5.2.1.) + * + * @param b2 BoundSet created by {@link #createB2(InvocationType, List, Theta)} + * @param invocation a method or constructor invocation + * @param methodType the type of the method or constructor invoked by expression + * @param target target type of the invocation + * @param map map of type variables to (inference) variables + * @return bound set created by constraints against the target type of the invocation + */ + public BoundSet createB3( + BoundSet b2, + ExpressionTree invocation, + InvocationType methodType, + AbstractType target, + Theta map) { + AbstractType r = methodType.getReturnType(map); + if (b2.isUncheckedConversion()) { + // If unchecked conversion was necessary for the method to be applicable during + // constraint set reduction in 18.5.1, the constraint formula <|R| -> T> is reduced and + // incorporated with B2. + BoundSet b = + new ConstraintSet(new Typing(r.getErased(), target, Kind.TYPE_COMPATIBILITY)) + .reduce(context); + b2.incorporateToFixedPoint(b); + return b2; + + } else if (r.isWildcardParameterizedType()) { + // Otherwise, if r is a parameterized type, G, and one of A1, ..., + // An is a wildcard, then, for fresh inference variables B1, ..., Bn, the constraint + // formula -> T> is reduced and incorporated, along with the bound + // G = capture(G), with B2. + BoundSet b = + CaptureBound.createAndIncorporateCaptureConstraint(r, target, invocation, context); + b2.incorporateToFixedPoint(b); + return b2; + } else if (r.isUseOfVariable()) { + Variable alpha = ((UseOfVariable) r).getVariable(); + // Should a type compatibility constraint be added? + boolean compatibility = false; + // If the target type is a reference type, but is not a wildcard-parameterized type. + if (!target.isWildcardParameterizedType()) { + // i) B2 contains a bound of one of the forms alpha = S or S <: alpha, where S is a + // wildcard-parameterized type, or + compatibility = alpha.getBounds().hasWildcardParameterizedLowerOrEqualBound(); + // ii) B2 contains two bounds of the forms S1 <: alpha and S2 <: alpha, where S1 + // and S2 have supertypes that are two different parameterizations of the same + // generic class or interface. + compatibility |= alpha.getBounds().hasLowerBoundDifferentParam(); + } else if (target.isParameterizedType()) { + // The target type is a parameterization of a generic class or interface, G, and B2 + // contains a + // bound of one of the forms alpha = S or S <: alpha, where there exists no type of + // the form G<...> that is a supertype of S, but the raw type |G<...>| is a + // supertype of S. + compatibility = alpha.getBounds().hasRawTypeLowerOrEqualBound(target); + } else if (target.getTypeKind().isPrimitive()) { + // The target is a primitive type, and one of the primitive wrapper classes + // mentioned in + // 5.1.7 is an instantiation, upper bound, or lower bound for alpha in B2. + compatibility = alpha.getBounds().hasPrimitiveWrapperBound(); + } + if (compatibility) { + BoundSet resolve = Resolution.resolve(alpha, b2, context); + ProperType u = (ProperType) alpha.getBounds().getInstantiation().capture(context); + ConstraintSet constraintSet = + new ConstraintSet(new Typing(u, target, Kind.TYPE_COMPATIBILITY)); + BoundSet newBounds = constraintSet.reduce(context); + resolve.incorporateToFixedPoint(newBounds); + return resolve; + } + if (target.isProper()) { + // From the JLS: + // "T is a primitive type, and one of the primitive wrapper classes mentioned in 5.1.7 is + // an instantiation, upper bound, or lower bound for [the variable] in B2." + ConstraintSet constraintSet = new ConstraintSet(new Typing(r, target, Kind.SUBTYPE)); + BoundSet newBounds = constraintSet.reduce(context); + b2.incorporateToFixedPoint(newBounds); + return b2; + } + } + + ConstraintSet constraintSet = new ConstraintSet(new Typing(r, target, Kind.TYPE_COMPATIBILITY)); + BoundSet newBounds = constraintSet.reduce(context); + b2.incorporateToFixedPoint(newBounds); + return b2; + } + + /** + * Creates the constraints between the formal parameters and arguments that are not pertinent to + * applicability. (See JLS + * 18.5.2.2.) + * + * @param methodType type of method invoked + * @param args argument expression trees + * @param map map from type variable to inference variable + * @return the constraints between the formal parameters and arguments that are not pertinent to + * applicability + */ + public ConstraintSet createC( + InvocationType methodType, List args, Theta map) { + ConstraintSet c = new ConstraintSet(); + List formals = methodType.getParameterTypes(map, args.size()); + + for (int i = 0; i < formals.size(); i++) { + ExpressionTree ei = args.get(i); + AbstractType fi = formals.get(i); + if (notPertinentToApplicability(ei, fi.isUseOfVariable())) { + c.add(new Expression(ei, fi)); + } + if (ei.getKind() == Tree.Kind.METHOD_INVOCATION || ei.getKind() == Tree.Kind.NEW_CLASS) { + if (TreeUtils.isPolyExpression(ei)) { + AdditionalArgument aa = new AdditionalArgument(ei); + c.addAll(aa.reduce(context)); + } + } else { + // Wait to reduce additional argument constraints from lambdas and method references because + // the additional constraints might require other inference variables to be resolved before + // the constraint can be created. + c.addAll(createAdditionalArgConstraints(ei, fi, map)); + } + } + + return c; + } + + /** + * Adds argument constraints for the argument {@code ei} and its subexpressions. These are in + * addition to the constraints added in {@link #createC(InvocationType, List, Theta)}. + * + *

          It does this by traversing {@code ei} if it is a method reference, lambda, method + * invocation, new class tree, conditional expression, switch expression, or parenthesized + * expression. + * + *

          If {@code ei} is a method invocation or new class tree, that expression might require type + * argument inference. In that case the additional variables, bounds, and constraints are added + * here. + * + *

          (See JLS + * 18.5.2.2) + * + * @param ei expression that is an argument to a method that corresponds to the formal parameter + * whose type is {@code fi} + * @param fi type that is the formal parameter to a method whose corresponding argument is {@code + * ei} + * @param map map from type variable to inference variable + * @return the additional argument constraints + */ + private ConstraintSet createAdditionalArgConstraints( + ExpressionTree ei, AbstractType fi, Theta map) { + ConstraintSet c = new ConstraintSet(); + + switch (ei.getKind()) { + case MEMBER_REFERENCE: + c.add(new CheckedExceptionConstraint(ei, fi, map)); + break; + case LAMBDA_EXPRESSION: + c.add(new CheckedExceptionConstraint(ei, fi, map)); + LambdaExpressionTree lambda = (LambdaExpressionTree) ei; + for (ExpressionTree expression : TreeUtils.getReturnedExpressions(lambda)) { + c.addAll(createAdditionalArgConstraints(expression, fi, map)); + } + break; + case METHOD_INVOCATION: + case NEW_CLASS: + if (TreeUtils.isPolyExpression(ei)) { + c.add(new AdditionalArgument(ei)); + } + break; + case PARENTHESIZED: + c.addAll(createAdditionalArgConstraints(TreeUtils.withoutParens(ei), fi, map)); + break; + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) ei; + c.addAll(createAdditionalArgConstraints(conditional.getTrueExpression(), fi, map)); + c.addAll(createAdditionalArgConstraints(conditional.getFalseExpression(), fi, map)); + break; + default: + if (TreeUtils.isSwitchExpression(ei)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree tree, Void unused) -> { + c.addAll(createAdditionalArgConstraints(tree, fi, map)); + return null; + }, + (c1, c2) -> null); + scanner.scanSwitchExpression(ei, null); + } + // no constraints + } + + return c; + } + + /** + * JLS + * 15.12.2.2 (Assuming the method is a generic method and the method invocation does not + * provide explicit type arguments) + * + * @param expressionTree expression tree + * @param isTargetVariable whether the corresponding target type (as derived from the signature of + * m) is a type parameter of m and therefore a variable + * @return whether {@code expressionTree} is pertinent to applicability + */ + private boolean notPertinentToApplicability( + ExpressionTree expressionTree, boolean isTargetVariable) { + switch (expressionTree.getKind()) { + case LAMBDA_EXPRESSION: + LambdaExpressionTree lambda = (LambdaExpressionTree) expressionTree; + if (TreeUtils.isImplicitlyTypedLambda(lambda) || isTargetVariable) { + // An implicitly typed lambda expression. + return true; + } else { + // An explicitly typed lambda expression whose body is a block, + // where at least one result expression is not pertinent to applicability. + // An explicitly typed lambda expression whose body is an expression that is + // not pertinent to applicability. + for (ExpressionTree result : TreeUtils.getReturnedExpressions(lambda)) { + if (notPertinentToApplicability(result, isTargetVariable)) { + return true; + } + } + return false; + } + case MEMBER_REFERENCE: + // An inexact method reference expression. + return isTargetVariable + || !TreeUtils.isExactMethodReference((MemberReferenceTree) expressionTree); + case PARENTHESIZED: + // A parenthesized expression whose contained expression is not pertinent to + // applicability. + return notPertinentToApplicability( + TreeUtils.withoutParens(expressionTree), isTargetVariable); + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) expressionTree; + // A conditional expression whose second or third operand is not pertinent to + // applicability. + return notPertinentToApplicability(conditional.getTrueExpression(), isTargetVariable) + || notPertinentToApplicability(conditional.getFalseExpression(), isTargetVariable); + default: + if (TreeUtils.isSwitchExpression(expressionTree)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree tree, Void unused) -> + notPertinentToApplicability(tree, isTargetVariable), + (r1, r2) -> (r1 != null && r1) || (r2 != null && r2)); + ; + return scanner.scanSwitchExpression(expressionTree, null); + } + return false; + } + } + + /** + * Returns the result of reducing and incorporating the set of constraints, {@code c}. The + * constraints must be reduced in a particular order. See JLS + * 18.5.2.2. + * + * @param b3 bound set created by previous inference step that is sideeffect and returned + * @param c constraints that are reduced and incorporated + * @return the result of reducing and incorporating the set of constraints + */ + private BoundSet getB4(BoundSet b3, ConstraintSet c) { + // C might contain new variables that have not yet been added to the b3 bound set. + Set newVariables = c.getAllInferenceVariables(); + while (!c.isEmpty()) { + + ConstraintSet subset = c.getClosedSubset(b3.getDependencies(newVariables)); + Set alphas = subset.getAllInputVariables(); + if (!alphas.isEmpty()) { + Resolution.resolve(alphas, b3, context); + c.applyInstantiations(); + } + c.remove(subset); + BoundSet newBounds = subset.reduce(context); + b3.incorporateToFixedPoint(newBounds); + } + return b3; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/TypeArgumentInference.java similarity index 57% rename from framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java rename to framework/src/main/java/org/checkerframework/framework/util/typeinference8/TypeArgumentInference.java index 7bd41884284..80eb2c8d43b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/TypeArgumentInference.java @@ -1,11 +1,7 @@ -package org.checkerframework.framework.util.typeinference; +package org.checkerframework.framework.util.typeinference8; import com.sun.source.tree.ExpressionTree; -import java.util.Map; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeVariable; import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; /** @@ -28,12 +24,8 @@ * Java, if T(A) = the type argument for a, in the above example T(A) == String and T(B) == Integer * *

          For the Checker Framework we also need to infer reasonable annotations for these type - * arguments. For information on inferring type arguments see the documentation in JLS7 and JLS8: + * arguments. For information on inferring type arguments see the documentation in JJLS8: * https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html - * https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.7 - * - *

          Note: It appears that Java 8 greatly improved the type argument inference and related error - * messaging but I have found it useful to consult the JLS 7 as well. */ public interface TypeArgumentInference { @@ -43,16 +35,12 @@ public interface TypeArgumentInference { * @param typeFactory the type factory used to create methodType * @param invocation a tree representing the method or constructor invocation for which we are * inferring type arguments - * @param methodElem the element for the declaration of the method being invoked * @param methodType the declaration type of method elem - * @return a mapping between the Java type parameter and the annotated type that was inferred for - * it. Note: We use the Java TypeVariable type because this uniquely identifies a declaration - * where as two uses of an AnnotatedTypeVariable may be uses of the same declaration but are - * not .equals to each other. + * @return the result which includes the inferred type arguments or an error message if they were + * not inferred. */ - public Map inferTypeArgs( + InferenceResult inferTypeArgs( AnnotatedTypeFactory typeFactory, ExpressionTree invocation, - ExecutableElement methodElem, - final AnnotatedExecutableType methodType); + AnnotatedExecutableType methodType); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java new file mode 100644 index 00000000000..a07d0b71c55 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java @@ -0,0 +1,373 @@ +package org.checkerframework.framework.util.typeinference8.bound; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.checkerframework.framework.util.typeinference8.constraint.ReductionResult; +import org.checkerframework.framework.util.typeinference8.types.CaptureVariable; +import org.checkerframework.framework.util.typeinference8.types.Dependencies; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Resolution; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.plumelib.util.StringsPlume; + +/** + * Manages a set of bounds. Bounds are stored in the variable to which they apply, except for + * capture bounds which are stored in this class. + */ +public class BoundSet implements ReductionResult { + /** + * Max number of incorporation loops. Use same constant as {@link + * com.sun.tools.javac.comp.Infer#MAX_INCORPORATION_STEPS} + */ + // TODO: revert to com.sun.tools.javac.comp.Infer#MAX_INCORPORATION_STEPS + public static final int MAX_INCORPORATION_STEPS = 1000; + + /** All inference variables in this bound set. */ + private final LinkedHashSet variables; + + /** All capture bounds. */ + private final LinkedHashSet captures; + + /** The context. */ + private final Java8InferenceContext context; + + /** + * If true, then type argument inference failed because an annotated type could not be inferred. + */ + public boolean annoInferenceFailed = false; + + /** The error message to report to users. */ + public String errorMsg = ""; + + /** Whether this bounds set contains the false bound. */ + private boolean containsFalse; + + /** Whether unchecked conversion was necessary to reduce and incorporate this bound set. */ + private boolean uncheckedConversion; + + /** + * Creates a bound set. + * + * @param context the context + */ + public BoundSet(Java8InferenceContext context) { + assert context != null; + this.variables = new LinkedHashSet<>(); + this.captures = new LinkedHashSet<>(); + this.context = context; + this.containsFalse = false; + this.uncheckedConversion = false; + } + + /** + * Copy constructor. + * + * @param toCopy bound set to copy + */ + public BoundSet(BoundSet toCopy) { + this.context = toCopy.context; + this.containsFalse = toCopy.containsFalse; + this.captures = new LinkedHashSet<>(toCopy.captures); + this.variables = new LinkedHashSet<>(toCopy.variables); + this.uncheckedConversion = toCopy.uncheckedConversion; + } + + /** + * Save the current state of the variables so they can be restored if the first attempt at + * resolution fails. + */ + public void saveBounds() { + for (Variable v : variables) { + v.save(); + } + } + + /** + * Restore the bounds to the last saved state. This method is called if the first attempt at + * resolution fails. + */ + public void restore() { + for (Variable v : variables) { + v.restore(); + } + } + + /** + * Creates a new bound set for the variables in theta. (The initial bounds for the variables were + * added to the variables when theta was created.) + * + * @param theta a Map from type variable to inference variable + * @param context inference context + * @return initial bounds + */ + public static BoundSet initialBounds(Theta theta, Java8InferenceContext context) { + BoundSet boundSet = new BoundSet(context); + boundSet.variables.addAll(theta.values()); + return boundSet; + } + + /** + * Merges {@code newSet} into this bound set. + * + * @param newSet bound set to merge + * @return whether the merge changed this bound set + */ + public boolean merge(BoundSet newSet) { + boolean changed = captures.addAll(newSet.captures); + changed |= variables.addAll(newSet.variables); + containsFalse |= newSet.containsFalse; + uncheckedConversion |= newSet.uncheckedConversion; + annoInferenceFailed |= newSet.annoInferenceFailed; + if (this.errorMsg.isEmpty()) { + this.errorMsg = newSet.errorMsg; + } else if (!newSet.errorMsg.isEmpty()) { + this.errorMsg += " " + newSet.errorMsg; + } + return changed; + } + + /** Adds the false bound to this bound set. */ + public void addFalse() { + containsFalse = true; + } + + /** + * Return whether this bound set contains false. + * + * @return whether this bound set contains false + */ + public boolean containsFalse() { + return containsFalse; + } + + /** + * Return whether unchecked conversion was necessary to reduce and incorporate this bound set + * + * @return whether unchecked conversion was necessary to reduce and incorporate this bound set + */ + public boolean isUncheckedConversion() { + return uncheckedConversion; + } + + /** + * Sets whether unchecked conversion was necessary to reduce and incorporate this bound set. + * + * @param uncheckedConversion whether unchecked conversion was necessary to reduce and incorporate + * this bound set + */ + public void setUncheckedConversion(boolean uncheckedConversion) { + this.uncheckedConversion = uncheckedConversion; + } + + /** + * Adds {@code capture} to this bound set. + * + * @param capture a capture bound + */ + public void addCapture(CaptureBound capture) { + captures.add(capture); + variables.addAll(capture.getAllVariablesOnLHS()); + } + + /** + * Does the bound set contain a bound of the form {@code G<..., ai, ...> = capture(G<...>)} for + * any variable in {@code as}? + * + * @param as a collection of varialbes + * @return whether the bound set contain a bound of the form {@code G<..., ai, ...> = + * capture(G<...>)} for any variable in {@code as} + */ + public boolean containsCapture(Collection as) { + List list = new ArrayList<>(); + for (CaptureBound c : captures) { + list.addAll(c.getAllVariablesOnLHS()); + } + for (Variable ai : as) { + if (list.contains(ai)) { + return true; + } + } + return false; + } + + /** + * Returns a list of variables in {@code alphas} that are instantiated. + * + * @param alphas a list of variables + * @return a list of variables in {@code alphas} that are instantiated + */ + public List getInstantiationsInAlphas(Collection alphas) { + List list = new ArrayList<>(); + for (Variable var : alphas) { + if (var.getBounds().hasInstantiation()) { + list.add(var); + } + } + return list; + } + + /** + * Returns a list of all variables in this bound set that are instantiated. + * + * @return a list of all variables in this bound set that are instantiated + */ + public List getInstantiatedVariables() { + List list = new ArrayList<>(); + for (Variable var : variables) { + if (var.getBounds().hasInstantiation()) { + list.add(var); + } + } + return list; + } + + /** + * Resolve all inference variables mentioned in any bound. + * + * @return a list of resolved variables in this bounds set + */ + public List resolve() { + BoundSet b = Resolution.resolve(new ArrayList<>(variables), this, context); + return b.getInstantiationsInAlphas(variables); + } + + /** + * Returns the dependencies between variables. + * + * @return the dependencies between variables + */ + public Dependencies getDependencies() { + return getDependencies(new ArrayList<>()); + } + + /** + * Adds the {@code additionalVars} to this bound set and returns the dependencies between all + * variables in this bound set. + * + * @param additionalVars variables to add to this bound set + * @return the dependencies between all variables in this bound set + */ + public Dependencies getDependencies(Collection additionalVars) { + variables.addAll(additionalVars); + Dependencies dependencies = new Dependencies(); + + for (CaptureBound capture : captures) { + List lhsVars = capture.getAllVariablesOnLHS(); + Set rhsVars = capture.getAllVariablesOnRHS(); + for (Variable var : lhsVars) { + // An inference variable alpha appearing on the left-hand side of a bound of the + // form G<..., alpha, ...> = capture(G<...>) depends on the resolution of every + // other inference variable mentioned in this bound (on both sides of the = sign). + dependencies.putOrAddAll(var, rhsVars); + dependencies.putOrAddAll(var, lhsVars); + } + } + Set allVariables = new LinkedHashSet<>(variables); + allVariables.addAll(additionalVars); + for (Variable alpha : allVariables) { + LinkedHashSet alphaDependencies = new LinkedHashSet<>(); + // An inference variable alpha depends on the resolution of itself. + alphaDependencies.add(alpha); + alphaDependencies.addAll(alpha.getBounds().getVariablesMentionedInBounds()); + + if (alpha.isCaptureVariable()) { + // If alpha appears on the left-hand side of another bound of the form + // G<..., alpha, ...> = capture(G<...>), then beta depends on the resolution of + // alpha. + for (Variable beta : alphaDependencies) { + dependencies.putOrAdd(beta, alpha); + } + } else { + for (Variable beta : alphaDependencies) { + if (!beta.isCaptureVariable()) { + // Otherwise, alpha depends on the resolution of beta. + dependencies.putOrAdd(alpha, beta); + } + } + } + } + + // Add transitive dependencies + dependencies.calculateTransitiveDependencies(); + + return dependencies; + } + + /** + * Incorporates {@code newBounds} into this bounds set. + * + *

          Incorporation creates new constraints that are then reduced to a bound set which is further + * incorporated into this bound set. Incorporation terminates when the bounds set has reached a + * fixed point. JLS 18 .1 + * defines this fixed point and further explains incorporation. + * + * @param newBounds bounds to incorporate + */ + public void incorporateToFixedPoint(final BoundSet newBounds) { + this.containsFalse |= newBounds.containsFalse; + if (this.containsFalse()) { + return; + } + merge(newBounds); + int count = 0; + do { + count++; + List instantiations = getInstantiatedVariables(); + boolean boundsChangeInst = false; + if (!instantiations.isEmpty()) { + for (Variable var : variables) { + boundsChangeInst = var.getBounds().applyInstantiationsToBounds(); + } + } + boundsChangeInst |= captures.addAll(newBounds.captures); + for (Variable alpha : variables) { + boundsChangeInst = alpha.getBounds().applyInstantiationsToBounds(); + + while (!alpha.getBounds().constraints.isEmpty()) { + boundsChangeInst = true; + merge(alpha.getBounds().constraints.reduceOneStep(context)); + alpha.getBounds().applyInstantiationsToBounds(); + } + } + if (newBounds.isUncheckedConversion()) { + this.setUncheckedConversion(true); + } + + if (!boundsChangeInst) { + return; + } + + containsFalse |= newBounds.containsFalse; + assert count < MAX_INCORPORATION_STEPS : "Max incorporation steps reached."; + } while (!containsFalse && count < MAX_INCORPORATION_STEPS); + } + + /** + * Remove any capture bound that mentions any variable in {@code as}. + * + * @param as a set of variables + */ + public void removeCaptures(Set as) { + captures.removeIf((CaptureBound c) -> c.isCaptureMentionsAny(as)); + } + + @Override + public String toString() { + if (containsFalse) { + return "FALSE"; + } else if (variables.isEmpty()) { + return "EMPTY"; + } + String vars = StringsPlume.join(", ", getInstantiatedVariables()); + if (vars.isEmpty()) { + return "No instantiated variables"; + } else { + return "Instantiated variables: " + vars; + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java new file mode 100644 index 00000000000..55ee4e7ded0 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java @@ -0,0 +1,238 @@ +package org.checkerframework.framework.util.typeinference8.bound; + +import com.sun.source.tree.ExpressionTree; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.Typing; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.CaptureVariable; +import org.checkerframework.framework.util.typeinference8.types.InferenceType; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.TypesUtils; + +/** + * A bound of the form: {@code G = capture(G)}. The variables a1, ..., an + * represent the result of capture conversion applied to {@code G} (where A1, ..., An + * may be types or wildcards and may mention inference variables). + */ +public class CaptureBound { + /** {@code G} sometimes called the right hand side */ + private final AbstractType capturedType; + + /** + * The substitution [P1 := alpha1, ..., Pn := alphan] where P1, ..., Pn are the type parameters of + * the underlying type, G. + */ + private final Theta map; + + /** {@code G} */ + private final InferenceType lhs; + + /** A list of {@link CaptureTuple}s. */ + private final List tuples = new ArrayList<>(); + + /** + * All capture variables in this capture. For example, a1 in {@code G = capture(G)}. + */ + private final List captureVariables = new ArrayList<>(); + + /** + * Creates a captured bound. + * + * @param capturedType a capture type + * @param invocation invocation a method or constructor invocation; used to create fresh inference + * variables + * @param context the context + */ + private CaptureBound( + AbstractType capturedType, ExpressionTree invocation, Java8InferenceContext context) { + this.capturedType = capturedType; + DeclaredType underlying = (DeclaredType) capturedType.getJavaType(); + TypeElement ele = TypesUtils.getTypeElement(underlying); + this.map = context.inferenceTypeFactory.createThetaForCapture(invocation, capturedType); + + lhs = (InferenceType) context.inferenceTypeFactory.getTypeOfElement(ele, map); + + Iterator alphas = this.map.values().iterator(); + Iterator args = capturedType.getTypeArguments().iterator(); + for (TypeParameterElement pEle : ele.getTypeParameters()) { + AbstractType Bi = context.inferenceTypeFactory.getTypeOfBound(pEle, map); + AbstractType Ai = args.next(); + + CaptureVariable alphai = (CaptureVariable) alphas.next(); + captureVariables.add(alphai); + alphai.initialBounds(map); + + tuples.add(CaptureTuple.of(alphai, Ai, Bi)); + } + } + + /** + * Given {@code r}, a parameterized type, {@code G}}, and one of {@code A1, ..., An} + * is a wildcard, then, for fresh inference variables {@code B1, ..., Bn}, the constraint formula + * {@code -> T>} is reduced and incorporated, along with the bound {@code G = capture(G)}, with B2. + * + * @param r a parameterized type, {@code G}, and one of {@code A1, ..., An} is a + * wildcard + * @param target target of the constraint + * @param invocation invocation a method or constructor invocation; used to create fresh inference + * variables + * @param context the context + * @return the result of incorporating the created capture constraint + */ + public static BoundSet createAndIncorporateCaptureConstraint( + AbstractType r, + AbstractType target, + ExpressionTree invocation, + Java8InferenceContext context) { + CaptureBound capture = new CaptureBound(r, invocation, context); + return capture.incorporate(target, context); + } + + /** + * Incorporate this capture bound. See JLS 18.3.1. + * + *

          Also, reduces and incorporates the constraint {@code G -> target}. See JLS + * 18.5.2.1. + * + * @param target the target type of + * @param context the context + * @return the result of incorporation + */ + private BoundSet incorporate(AbstractType target, Java8InferenceContext context) { + // First add the non-wildcard bounds. + for (CaptureTuple t : tuples) { + if (t.capturedTypeArg.getTypeKind() != TypeKind.WILDCARD) { + // If Ai is not a wildcard, then the bound alphai = Ai is implied. + t.alpha.getBounds().addBound(VariableBounds.BoundKind.EQUAL, t.capturedTypeArg); + } + } + + ConstraintSet set = new ConstraintSet(new Typing(lhs, target, Kind.TYPE_COMPATIBILITY)); + // Reduce and incorporate so that the capture variables bounds are set. + BoundSet b1 = set.reduce(context); + b1.incorporateToFixedPoint(new BoundSet(context)); + + // Then create constraints implied by captured type args that are wildcards. + boolean containsFalse = false; + for (CaptureTuple t : tuples) { + if (t.capturedTypeArg.getTypeKind() == TypeKind.WILDCARD) { + ConstraintSet newCon = t.alpha.getWildcardConstraints(t.capturedTypeArg, t.bound); + if (newCon == null) { + containsFalse = true; + } else { + set.addAll(newCon); + } + } + } + + // Reduce and incorporate again. + BoundSet b2 = set.reduce(context); + b2.addCapture(this); + if (containsFalse) { + b2.addFalse(); + } + b1.incorporateToFixedPoint(b2); + return b1; + } + + /** + * Return all variables on the left-hand side of this capture. + * + * @return all variables on the left-hand side of this capture + */ + public List getAllVariablesOnLHS() { + return captureVariables; + } + + /** + * Return all variables on the right-hand side of this capture. + * + * @return all variables on the right-hand side of this capture + */ + public Set getAllVariablesOnRHS() { + return new LinkedHashSet<>(capturedType.getInferenceVariables()); + } + + /** + * Returns whether this bound contains any {@code variables}. + * + * @param variables inference variables + * @return whether this bound contains any {@code variables} + */ + public boolean isCaptureMentionsAny(Collection variables) { + for (Variable a : variables) { + if (map.containsValue(a)) { + return true; + } + } + return false; + } + + /** + * For a capture of the form: {@code G = capture(G)}, a capture tuple + * groups ai, Ai, and the upper bound of the corresponding type variable. + */ + private static class CaptureTuple { + + /** + * Fresh inference variable (in the left hand side of the capture). (Also referred to as beta in + * the some places in the JLS.) For example {@code a1} in {@code G = capture(G)}. + */ + public final CaptureVariable alpha; + + /** + * Type argument in the right hand side for the capture. For example {@code A1} in {@code G = capture(G)}. + */ + public final AbstractType capturedTypeArg; + + /** + * Upper bound of one of the type parameters of G that has been substituted using the fresh + * inference variables. + */ + public final AbstractType bound; + + /** + * Creates a tuple. + * + * @param alpha capture variable + * @param capturedTypeArg captured type argument + * @param bound the bound of the type parameter + */ + private CaptureTuple(CaptureVariable alpha, AbstractType capturedTypeArg, AbstractType bound) { + this.alpha = alpha; + this.capturedTypeArg = capturedTypeArg; + this.bound = bound; + } + + /** + * Creates a tuple. + * + * @param alpha capture variable + * @param capturedTypeArg captured type argument + * @param bound the bound of the type parameter + * @return a tuple + */ + public static CaptureTuple of( + CaptureVariable alpha, AbstractType capturedTypeArg, AbstractType bound) { + return new CaptureTuple(alpha, capturedTypeArg, bound); + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java new file mode 100644 index 00000000000..9275a9a99d2 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java @@ -0,0 +1,66 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import org.checkerframework.framework.util.typeinference8.types.InvocationType; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; + +/** + * A constraint the represent additional argument constraints generated from a method or constructor + * invocation that is a part of a larger inference problem. When this constraint is reduced it will + * generate more constraints from the invocaton. This is because created the constraints might use + * the type of an implicit lambda parameter for which the larger inference problem has not yet found + * a type. So, the additional constraints can be created until after the implicit lambda parameter + * has a type. + */ +public class AdditionalArgument implements Constraint { + + /** The tree for the method or constructor invocation for this constraint. */ + private ExpressionTree methodOrConstructorInvocation; + + /** + * Creates a new constraint. + * + * @param methodOrConstructorInvocation tree for the method or constructor invocation for this + * constraint + */ + public AdditionalArgument(ExpressionTree methodOrConstructorInvocation) { + this.methodOrConstructorInvocation = methodOrConstructorInvocation; + } + + @Override + public Kind getKind() { + return Kind.ADDITIONAL_ARG; + } + + @Override + public ConstraintSet reduce(Java8InferenceContext context) { + if (methodOrConstructorInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree methodInvocation = (MethodInvocationTree) methodOrConstructorInvocation; + InvocationType methodType = + context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(methodInvocation); + Theta newMap = + context.inferenceTypeFactory.createThetaForInvocation( + methodInvocation, methodType, context); + ConstraintSet set = + context.inference.createC(methodType, methodInvocation.getArguments(), newMap); + set.applyInstantiations(); + return set; + } else { + NewClassTree newClassTree = (NewClassTree) methodOrConstructorInvocation; + InvocationType methodType = + context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(newClassTree); + + Theta newMap = + context.inferenceTypeFactory.createThetaForInvocation(newClassTree, methodType, context); + ConstraintSet set = + context.inference.createC(methodType, newClassTree.getArguments(), newMap); + set.applyInstantiations(); + return set; + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java new file mode 100644 index 00000000000..d82bd82a565 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java @@ -0,0 +1,96 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.Tree; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; + +/** + * <LambdaExpression →throws T>: The checked exceptions thrown by the body of the + * LambdaExpression are declared by the throws clause of the function type derived from T. + * + *

          <MethodReference →throws T>: The checked exceptions thrown by the referenced method + * are declared by the throws clause of the function type derived from T. + */ +public class CheckedExceptionConstraint extends TypeConstraint { + + /** + * {@link com.sun.source.tree.LambdaExpressionTree} or {@link + * com.sun.source.tree.MemberReferenceTree} for this constraint. + */ + protected final ExpressionTree expression; + + /** The mapping from type variable to inference variable to use with this constraint. */ + protected final Theta map; + + /** + * Creates a {@code CheckedExceptionConstraint}. + * + * @param expression {@link com.sun.source.tree.LambdaExpressionTree} or {@link + * com.sun.source.tree.MemberReferenceTree} for this constraint + * @param t a function type + * @param map The mapping from type variable to inference variable to use with this constraint + */ + public CheckedExceptionConstraint(ExpressionTree expression, AbstractType t, Theta map) { + super(t); + assert expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION + || expression.getKind() == Tree.Kind.MEMBER_REFERENCE; + this.expression = expression; + this.map = map; + } + + @Override + public Kind getKind() { + return expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION + ? Kind.LAMBDA_EXCEPTION + : Kind.METHOD_REF_EXCEPTION; + } + + @Override + public List getInputVariables() { + return getInputVariablesForExpression(expression, getT()); + } + + @Override + public List getOutputVariables() { + List input = getInputVariables(); + List output = new ArrayList<>(getT().getInferenceVariables()); + output.removeAll(input); + return output; + } + + @Override + public ReductionResult reduce(Java8InferenceContext context) { + // See JLS 18.2.5 + return context.inferenceTypeFactory.getCheckedExceptionConstraints(expression, T, map); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + CheckedExceptionConstraint that = (CheckedExceptionConstraint) o; + + return Objects.equals(expression, that.expression); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (expression != null ? expression.hashCode() : 0); + return result; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java new file mode 100644 index 00000000000..cdc63ecc94c --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java @@ -0,0 +1,69 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; + +/** + * A constraint. See JLS. + */ +public interface Constraint extends ReductionResult { + + /** + * Return the kind of constraint. + * + * @return the kind of constraint + */ + Kind getKind(); + + /** + * Reduce this constraint what this means depends on the kind of constraint. Reduction can produce + * new bounds and/or new constraints. + * + *

          Reduction is documented in JLS section + * 18.2 + * + * @param context Java8InferenceContext + * @return the result of reducing this constraint + */ + ReductionResult reduce(Java8InferenceContext context); + + /** A kind of Constraint. */ + enum Kind { + /** + * {@code < Expression -> T >}: An expression is compatible in a loose invocation context with + * type T + */ + EXPRESSION, + /** {@code < S -> T >}: A type S is compatible in a loose invocation context with type T */ + TYPE_COMPATIBILITY, + /** {@code < S <: T >}: A reference type S is a subtype of a reference type T */ + SUBTYPE, + /** {@code < S <= T >}: A type argument S is contained by a type argument T. */ + CONTAINED, + /** + * {@code < S = T >}: A type S is the same as a type T, or a type argument S is the same as type + * argument T. + */ + TYPE_EQUALITY, + /** + * {@code < LambdaExpression -> throws T>}: The checked exceptions thrown by the body of the + * LambdaExpression are declared by the throws clause of the function type derived from T. + */ + LAMBDA_EXCEPTION, + /** + * {@code < MethodReferenceExpression -> throws T>}: The checked exceptions thrown by the + * referenced method are declared by the throws clause of the function type derived from T. + */ + METHOD_REF_EXCEPTION, + + /** {@code < Q <: R >}: A qualifier Q is a subtype of a qualifier R. */ + QUALIFIER_SUBTYPE, + + /** {@code < Q = R >}: A qualifier R is the same as a qualifier R. */ + QUALIFIER_EQUALITY, + + /** A single constraint, that when reduced, generates additional argument constraints. */ + ADDITIONAL_ARG, + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java new file mode 100644 index 00000000000..2ef8416be3c --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java @@ -0,0 +1,337 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.framework.util.typeinference8.bound.BoundSet; +import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; +import org.checkerframework.framework.util.typeinference8.types.Dependencies; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.util.FalseBoundException; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.BugInCF; + +/** A set of constraints and the operations that can be performed on them. */ +public class ConstraintSet implements ReductionResult { + + /** The result given when a constraint set reduces to true. */ + @SuppressWarnings("interning:assignment") + public static final @InternedDistinct ConstraintSet TRUE = + new ConstraintSet() { + @Override + public String toString() { + return "TRUE"; + } + }; + + /** + * The Java types are correct, but the qualifiers are not in the correct relationship. Return this + * rather than throwing an exception so that type arguments with the correct Java type are still + * inferred. + */ + @SuppressWarnings("interning:assignment") + public static final @InternedDistinct ConstraintSet TRUE_ANNO_FAIL = + new ConstraintSet(true) { + @Override + public String toString() { + return "TRUE_ANNO_FAIL"; + } + }; + + /** The result given when a constraint set reduces to false. */ + @SuppressWarnings("interning:assignment") + public static final @InternedDistinct ReductionResult FALSE = + new ReductionResult() { + @Override + public String toString() { + return "FALSE"; + } + }; + + /** + * A list of constraints in this set. It does not contain constraints that are equal. This needs + * to be kept in the order created, which should be lexically left to right. This is so the {@link + * #getClosedSubset(Dependencies)} is computed correctly. + */ + private final List list; + + /** Whether inference failed because the qualifiers where not in the correct relationship. */ + private boolean annotationFailure = false; + + /** + * Creates a new constraint set. + * + * @param annotationFailure inference failed because the qualifiers where not in the correct + * relationship + */ + private ConstraintSet(boolean annotationFailure) { + this(); + this.annotationFailure = annotationFailure; + } + + /** + * Creates a constraint set with {@code constraints}. + * + * @param constraints constraints to add to the newly created set + */ + public ConstraintSet(Constraint... constraints) { + if (constraints != null) { + list = new ArrayList<>(constraints.length); + list.addAll(Arrays.asList(constraints)); + } else { + list = new ArrayList<>(); + } + } + + /** + * Adds {@code c} to this set, if c isn't already in the list. + * + * @param c a constraint to add to this set + */ + public void add(Constraint c) { + if (c != null && !list.contains(c)) { + list.add(c); + } + } + + /** + * Adds all constraints in {@code constraintSet} to this constraint set. + * + * @param constraintSet a set of constraints to add to this set + */ + public void addAll(ConstraintSet constraintSet) { + if (constraintSet.annotationFailure) { + this.annotationFailure = true; + } + constraintSet.list.forEach(this::add); + } + + /** + * Adds all constraints in {@code constraintSet} to this constraint set. + * + * @param constraintSet a collection of constraints to add to this set + */ + public void addAll(Collection constraintSet) { + list.addAll(constraintSet); + } + + /** + * Return whether or not this constraint set is empty. + * + * @return whether or not this constraint set is empty + */ + public boolean isEmpty() { + return list.isEmpty(); + } + + /** + * Removes and returns the first constraint that was added to this set. + * + * @return first constraint that was added to this set + */ + public Constraint pop() { + assert !isEmpty(); + return list.remove(0); + } + + /** + * Remove all constraints in {@code subset} from this constraint set. + * + * @param subset the set of constraints to remove from this set + */ + @SuppressWarnings("interning:not.interned") + public void remove(ConstraintSet subset) { + if (this == subset) { + list.clear(); + } + list.removeAll(subset.list); + } + + /** + * A subset of constraints is selected in this constraint set, satisfying the property that, for + * each constraint, no input variable can influence an output variable of another constraint in + * this constraint set. (See JLS 18.5.2.2) + * + * @param dependencies an object describing the dependencies of inference variables + * @return s a subset of constraints is this constraint set + */ + public ConstraintSet getClosedSubset(Dependencies dependencies) { + ConstraintSet subset = new ConstraintSet(); + Set inputDependencies = new LinkedHashSet<>(); + Set outDependencies = new LinkedHashSet<>(); + for (Constraint constraint : list) { + if (constraint.getKind() == Kind.EXPRESSION + || constraint.getKind() == Kind.LAMBDA_EXCEPTION + || constraint.getKind() == Kind.METHOD_REF_EXCEPTION) { + TypeConstraint c = (TypeConstraint) constraint; + Set newInputs = dependencies.get(c.getInputVariables()); + Set newOutputs = dependencies.get(c.getOutputVariables()); + if (Collections.disjoint(newInputs, outDependencies) + && Collections.disjoint(newOutputs, inputDependencies)) { + inputDependencies.addAll(newInputs); + outDependencies.addAll(newOutputs); + subset.add(c); + } else { + // A cycle (or cycles) in the graph of dependencies between constraints exists. + subset = new ConstraintSet(); + break; + } + } else { + subset.add(constraint); + } + } + + if (!subset.isEmpty()) { + return subset; + } + + outDependencies.clear(); + inputDependencies.clear(); + // If this subset is empty, then there is a cycle (or cycles) in the graph of dependencies + // between constraints. + List consideredConstraints = new ArrayList<>(); + for (Constraint constraint : list) { + if (!(constraint instanceof TypeConstraint)) { + continue; + } + TypeConstraint c = (TypeConstraint) constraint; + Set newInputs = dependencies.get(c.getInputVariables()); + Set newOutputs = dependencies.get(c.getOutputVariables()); + if (inputDependencies.isEmpty() + || !Collections.disjoint(newInputs, outDependencies) + || !Collections.disjoint(newOutputs, inputDependencies)) { + inputDependencies.addAll(newInputs); + outDependencies.addAll(newOutputs); + consideredConstraints.add(c); + } + } + + // A single constraint is selected from the considered constraints, as follows: + + // If any of the considered constraints have the form T>, then the selected + // constraint is the considered constraint of this form that contains the expression to the + // left (3.5) of the expression of every other considered constraint of this form. + + // If no considered constraint has the form T>, then the selected constraint + // is the considered constraint that contains the expression to the left of the expression + // of every other considered constraint. + + for (Constraint c : consideredConstraints) { + if (c.getKind() == Kind.EXPRESSION) { + return new ConstraintSet(c); + } + } + + return new ConstraintSet(consideredConstraints.get(0)); + } + + /** + * Return all variables mentioned by any constraint in this set. + * + * @return all variables mentioned by any constraint in this set + */ + public Set getAllInferenceVariables() { + Set vars = new LinkedHashSet<>(); + for (Constraint c : list) { + if (c instanceof TypeConstraint) { + vars.addAll(((TypeConstraint) c).getInferenceVariables()); + } + } + return vars; + } + + /** + * Return all input variables for all constraints in this set. + * + * @return all input variables for all constraints in this set + */ + public Set getAllInputVariables() { + Set vars = new LinkedHashSet<>(); + for (Constraint constraint : list) { + if (constraint instanceof TypeConstraint) { + vars.addAll(((TypeConstraint) constraint).getInputVariables()); + } + } + return vars; + } + + /** Applies the instantiations to all the constraints in this set. */ + public void applyInstantiations() { + for (Constraint constraint : list) { + if (constraint instanceof TypeConstraint) { + ((TypeConstraint) constraint).applyInstantiations(); + } + } + } + + @Override + public String toString() { + return "Size: " + list.size(); + } + + /** + * Reduces all the constraints in this set. (See JLS 18.2) + * + * @param context the context + * @return the bound set produced by reducing this constraint set + */ + public BoundSet reduce(Java8InferenceContext context) { + BoundSet boundSet = new BoundSet(context); + while (!this.isEmpty()) { + if (this.list.size() > BoundSet.MAX_INCORPORATION_STEPS) { + throw new BugInCF("TO MANY CONSTRAINTS: %s", context.pathToExpression.getLeaf()); + } + boundSet.merge(reduceOneStep(context)); + } + + return boundSet; + } + + /** + * Reduce one constraint in this set. + * + * @param context the context + * @return the result of reducing one constraint in this set + */ + public BoundSet reduceOneStep(Java8InferenceContext context) { + boolean alreadyFailed = this.annotationFailure; + BoundSet boundSet = new BoundSet(context); + + Constraint constraint = this.pop(); + ReductionResult result = constraint.reduce(context); + if (result instanceof ReductionResultPair) { + boundSet.merge(((ReductionResultPair) result).boundSet); + if (boundSet.containsFalse()) { + throw new FalseBoundException(constraint, result); + } + this.addAll(((ReductionResultPair) result).constraintSet); + } else if (result instanceof TypeConstraint) { + this.add((Constraint) result); + } else if (result instanceof ConstraintSet) { + this.addAll((ConstraintSet) result); + } else if (result instanceof BoundSet) { + boundSet.merge((BoundSet) result); + if (boundSet.containsFalse()) { + throw new FalseBoundException(constraint, result); + } + } else if (result == null || result == ConstraintSet.FALSE) { + throw new FalseBoundException(constraint, result); + } else if (result == UNCHECKED_CONVERSION) { + boundSet.setUncheckedConversion(true); + } else { + throw new RuntimeException("Not found " + result); + } + if (this.annotationFailure) { + boundSet.annoInferenceFailed = true; + if (!alreadyFailed && boundSet.errorMsg.isEmpty()) { + boundSet.errorMsg = constraint.toString(); + } + } + return boundSet; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java new file mode 100644 index 00000000000..69b4fa1d7ff --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java @@ -0,0 +1,462 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.VariableTree; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.typeinference8.bound.BoundSet; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.InferenceType; +import org.checkerframework.framework.util.typeinference8.types.InvocationType; +import org.checkerframework.framework.util.typeinference8.types.ProperType; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.SwitchExpressionScanner; +import org.checkerframework.javacutil.SwitchExpressionScanner.FunctionalSwitchExpressionScanner; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TreeUtils.MemberReferenceKind; +import org.plumelib.util.IPair; + +/** + * <Expression → T> An expression is compatible in a loose invocation context with type T + */ +public class Expression extends TypeConstraint { + + /** Expression that is compatible in a loose invocation context with {@link #T}. */ + private final ExpressionTree expression; + + /** + * Creates an expression constraint. + * + * @param expressionTree the expression for the constraint + * @param t the type that the expression is compatible in a loose invocation context + */ + public Expression(ExpressionTree expressionTree, AbstractType t) { + super(t); + this.expression = expressionTree; + assert expression != null; + } + + @Override + public Kind getKind() { + return Kind.EXPRESSION; + } + + @Override + public List getInputVariables() { + return getInputVariablesForExpression(expression, getT()); + } + + @Override + public List getOutputVariables() { + List input = getInputVariables(); + List output = new ArrayList<>(getT().getInferenceVariables()); + output.removeAll(input); + return output; + } + + @Override + public ReductionResult reduce(Java8InferenceContext context) { + // See JLS 18.2.1 + if (getT().isProper()) { + return reduceProperType(); + } else if (TreeUtils.isStandaloneExpression(expression)) { + AbstractType s; + if (!context.isLambdaParam(expression)) { + s = new ProperType(expression, context); + } else { + AnnotatedTypeMirror atm = context.typeFactory.getAnnotatedType(expression); + s = getT().create(atm, atm.getUnderlyingType()); + } + return new Typing(s, T, TypeConstraint.Kind.TYPE_COMPATIBILITY); + } + switch (expression.getKind()) { + case PARENTHESIZED: + return new Expression(TreeUtils.withoutParens(expression), T); + case NEW_CLASS: + case METHOD_INVOCATION: + return reduceMethodInvocation(context); + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) expression; + TypeConstraint trueConstraint = new Expression(conditional.getTrueExpression(), T); + Constraint falseConstraint = new Expression(conditional.getFalseExpression(), T); + return new ConstraintSet(trueConstraint, falseConstraint); + case LAMBDA_EXPRESSION: + return reduceLambda(context); + case MEMBER_REFERENCE: + return reduceMethodRef(context); + default: + if (TreeUtils.isSwitchExpression(expression)) { + ConstraintSet set = new ConstraintSet(); + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree valueTree, Void unused) -> { + Constraint c = new Expression(valueTree, T); + set.add(c); + return null; + }, + (c1, c2) -> null); + scanner.scanSwitchExpression(expression, null); + return set; + } + throw new BugInCF( + "Unexpected expression kind: %s, Expression: %s", expression.getKind(), expression); + } + } + + /** + * JSL 18.2.1: "If T is a proper type, the constraint reduces to true if the expression is + * compatible in a loose invocation context with T (5.3), and false otherwise." + * + * @return the result of reducing a proper type + */ + private ReductionResult reduceProperType() { + // Assume the constraint reduces to TRUE, if it did not the code wouldn't compile with + // javac. + + // TODO: This should return false in some cases. + // com.sun.tools.javac.code.Types.isConvertible(com.sun.tools.javac.code.Type, + // com.sun.tools.javac.code.Type) + return new ConstraintSet(); + } + + /** + * Text from JLS 18.2.1: If the expression is a class instance creation expression or a method + * invocation expression, the constraint reduces to the bound set B3 which would be used to + * determine the expression's invocation type when targeting T, as defined in 18.5.2. (For a class + * instance creation expression, the corresponding "method" used for inference is defined in + * 15.9.3). + * + *

          This bound set may contain new inference variables, as well as dependencies between these + * new variables and the inference variables in T. + * + * @param context the context + * @return the result of reducing this constraint + */ + private BoundSet reduceMethodInvocation(Java8InferenceContext context) { + ExpressionTree expressionTree = expression; + List args; + if (expressionTree.getKind() == Tree.Kind.NEW_CLASS) { + NewClassTree newClassTree = (NewClassTree) expressionTree; + args = newClassTree.getArguments(); + } else { + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) expressionTree; + args = methodInvocationTree.getArguments(); + } + + InvocationType methodType = + context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(expressionTree); + Theta map = + context.inferenceTypeFactory.createThetaForInvocation(expressionTree, methodType, context); + BoundSet b2 = context.inference.createB2(methodType, args, map); + return context.inference.createB3(b2, expressionTree, methodType, T, map); + } + + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300 + private ReductionResult reduceMethodRef(Java8InferenceContext context) { + MemberReferenceTree memRef = (MemberReferenceTree) expression; + if (TreeUtils.isExactMethodReference(memRef)) { + InvocationType typeOfPoAppMethod = + context.inferenceTypeFactory.compileTimeDeclarationType(memRef); + + ConstraintSet constraintSet = new ConstraintSet(); + List ps = T.getFunctionTypeParameterTypes(); + List fs = typeOfPoAppMethod.getParameterTypes(null); + + if (ps.size() == fs.size() + 1) { + AbstractType targetReference = ps.remove(0); + ExpressionTree preColonTree = memRef.getQualifierExpression(); + AbstractType referenceType; + if (context.isLambdaParam(preColonTree)) { + AnnotatedTypeMirror atm = context.typeFactory.getAnnotatedType(preColonTree); + referenceType = T.create(atm, atm.getUnderlyingType()); + } else { + if (MemberReferenceKind.getMemberReferenceKind(memRef).isUnbound()) { + AnnotatedTypeMirror atm = + context.typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + referenceType = new ProperType(atm, atm.getUnderlyingType(), context); + } else { + referenceType = new ProperType(preColonTree, context); + } + } + constraintSet.add(new Typing(targetReference, referenceType, TypeConstraint.Kind.SUBTYPE)); + } + for (int i = 0; i < ps.size(); i++) { + constraintSet.add(new Typing(ps.get(i), fs.get(i), TypeConstraint.Kind.SUBTYPE)); + } + AbstractType r = T.getFunctionTypeReturnType(); + if (r != null && r.getTypeKind() != TypeKind.VOID) { + AbstractType rPrime = typeOfPoAppMethod.getReturnType(null).capture(context); + constraintSet.add(new Typing(rPrime, r, TypeConstraint.Kind.TYPE_COMPATIBILITY)); + } + return constraintSet; + } + // else the method reference is inexact. + + // Compile-time declaration of the member reference expression + InvocationType compileTimeDecl = + context.inferenceTypeFactory.compileTimeDeclarationType(memRef); + if (compileTimeDecl.isVoid()) { + return ConstraintSet.TRUE; + } + AbstractType r = T.getFunctionTypeReturnType(); + if (r.getTypeKind() == TypeKind.VOID) { + return ConstraintSet.TRUE; + } + + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300-D-B-BC + // Otherwise, if the method reference expression elides TypeArguments, and the + // compile-time declaration is a generic method, and + // the return type of the compile-time declaration mentions at least one of the method's + // type parameters, the constraint reduces to the bound set B3 which would be used to + // determine the method reference's invocation type when targeting the return type of the + // function type, as defined in 18.5.2. B3 may contain new inference variables, as well as + // dependencies between these new variables and the inference variables in T. + Theta map = + context.inferenceTypeFactory.createThetaForMethodReference( + memRef, compileTimeDecl, context); + AbstractType compileTimeReturn = compileTimeDecl.getReturnType(map); + if (TreeUtils.needsTypeArgInference(memRef) && !compileTimeReturn.isProper()) { + BoundSet b2 = + context.inference.createB2MethodRef( + compileTimeDecl, T.getFunctionTypeParameterTypes(), map); + return context.inference.createB3(b2, memRef, compileTimeDecl, r, map); + } + + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300-D-B-C + // Otherwise, let R be the return type of the function type, and let R' be the result + // of applying capture conversion (5.1.10) to the return type of the invocation type + // (15.12.2.6) of the compile-time declaration. If R' is void, the constraint reduces + // to false; otherwise, the constraint reduces to R>. + return ReductionResultPair.of( + new ConstraintSet( + new Typing( + compileTimeReturn.capture(context), r, TypeConstraint.Kind.TYPE_COMPATIBILITY)), + new BoundSet(context)); + } + + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-200 + private ReductionResultPair reduceLambda(Java8InferenceContext context) { + LambdaExpressionTree lambda = (LambdaExpressionTree) expression; + IPair pair = getGroundTargetType(T, lambda, context); + AbstractType tPrime = pair.first; + BoundSet boundSet = pair.second == null ? new BoundSet(context) : pair.second; + + ConstraintSet constraintSet = new ConstraintSet(); + + if (!TreeUtils.isImplicitlyTypedLambda(lambda)) { + // Explicitly typed lambda + List parameters = lambda.getParameters(); + List gs = T.getFunctionTypeParameterTypes(); + assert parameters.size() == gs.size(); + + for (int i = 0; i < gs.size(); i++) { + VariableTree parameter = parameters.get(i); + AbstractType fi = new ProperType(parameter, context); + AbstractType gi = gs.get(i); + constraintSet.add(new Typing(fi, gi, TypeConstraint.Kind.TYPE_EQUALITY)); + } + constraintSet.add(new Typing(tPrime, T, TypeConstraint.Kind.SUBTYPE)); + } else { + context.addLambdaParms(lambda.getParameters()); + } + + AbstractType R = tPrime.getFunctionTypeReturnType(); + if (R != null && R.getTypeKind() != TypeKind.VOID) { + for (ExpressionTree e : TreeUtils.getReturnedExpressions(lambda)) { + if (R.isProper()) { + if (!context.env.getTypeUtils().isAssignable(TreeUtils.typeOf(e), R.getJavaType())) { + boundSet.addFalse(); + return ReductionResultPair.of(constraintSet, boundSet); + } + } else { + constraintSet.add(new Expression(e, R)); + } + } + } + return ReductionResultPair.of(constraintSet, boundSet); + } + + /** + * This method sets up functional interface parameterization inference for {@code lambda} as + * defined in JLS 18.5.3. + * + *

          Computes the ground target type of {@code t}. Returned as the first in the pair. This + * process might create additional bounds, if so the second in the returned pair will be non-null. + * + * @param t the target type of {@code lambda} + * @param lambda a lambda to infer functional interface parameterization + * @param context the context + * @return the ground target type + */ + private IPair getGroundTargetType( + AbstractType t, LambdaExpressionTree lambda, Java8InferenceContext context) { + if (!t.isWildcardParameterizedType()) { + return IPair.of(t, null); + } + // 15.27.3: + // If T is a wildcard-parameterized functional interface type and the lambda expression is + // explicitly typed, then the ground target type is inferred as described in 18.5.3. + if (TreeUtils.isExplicitlyTypeLambda(lambda)) { + return explicitlyTypedLambdaWithWildcard(t, lambda, context); + } else { + // If T is a wildcard-parameterized functional interface type and the lambda expression + // is implicitly typed, then the ground target type is the non-wildcard parameterization + // (9.9) of T. + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.9-200-C + return IPair.of(nonWildcardParameterization(t, context), null); + } + } + + /** + * Returns the non-wildcard parameterization of {@code t} as defined in JLS 9.9. + * + * @param t a type + * @param context the context + * @return the non-wildcard parameterization of {@code t} + */ + private AbstractType nonWildcardParameterization(AbstractType t, Java8InferenceContext context) { + List As = t.getTypeArguments(); + Iterator Bs = t.getTypeParameterBounds().iterator(); + List Ts = new ArrayList<>(); + for (AbstractType Ai : As) { + ProperType bi = Bs.next(); + if (Ai.getTypeKind() != TypeKind.WILDCARD) { + Ts.add(Ai); + } else if (Ai.isUnboundWildcard()) { + Ts.add(bi); + } else if (Ai.isUpperBoundedWildcard()) { + AbstractType Ui = Ai.getWildcardUpperBound(); + AbstractType glb = context.inferenceTypeFactory.glb(Ui, bi); + Ts.add(glb); + } else { + // Lower bounded wildcard + Ts.add(Ai.getWildcardLowerBound()); + } + } + return t.replaceTypeArgs(Ts); + } + + /** + * Infers the type of {@code lambda} which may create a bounds set that needs to be resolved as + * part of a larger inference problem. See 18.5.3: Functional Interface Parameterization Inference + * + * @param t the target type of the lambda + * @param lambda a lambda expression + * @param context the context + * @return a pair of the type of the lambda and the bound set that needs to be resolved + */ + private IPair explicitlyTypedLambdaWithWildcard( + AbstractType t, LambdaExpressionTree lambda, Java8InferenceContext context) { + // Where a lambda expression with explicit parameter types P1, ..., Pn targets a functional + // interface type F with at least one wildcard type argument, then a + // parameterization of F may be derived as the ground target type of the lambda expression + // as follows. + List ps = new ArrayList<>(); + for (VariableTree paramTree : lambda.getParameters()) { + ps.add(new ProperType(paramTree, context)); + } + + // Let Q1, ..., Qk be the parameter types of the function type of the type F, where alpha1, ..., alpham are fresh inference variables. + Theta map = context.inferenceTypeFactory.createThetaForLambda(lambda, t); + List alphas = new ArrayList<>(map.values()); + AbstractType tprime = InferenceType.create(t.getAnnotatedType(), t.getJavaType(), map, context); + + List qs = tprime.getFunctionTypeParameterTypes(); + assert qs.size() == ps.size(); + + // A set of constraint formulas is formed with, for all i (1 <= i <= n), . + ConstraintSet constraintSet = new ConstraintSet(); + for (int i = 0; i < ps.size(); i++) { + ProperType pi = ps.get(i); + AbstractType qi = qs.get(i); + constraintSet.add(new Typing(pi, qi, TypeConstraint.Kind.TYPE_EQUALITY)); + } + // This constraint formula set is reduced to form the bound set B. + BoundSet b = constraintSet.reduce(context); + assert !b.containsFalse() + : "Bound set contains false during Functional Interface Parameterization Inference"; + + // A new parameterization of the functional interface type, F, is constructed + // as follows, for 1 <= i <= m: + List APrimes = new ArrayList<>(); + Iterator alphaIter = alphas.iterator(); + boolean hasWildcard = false; + for (AbstractType Ai : t.getTypeArguments()) { + Variable alphaI = alphaIter.next(); + // If B contains an instantiation (18.1.3) for alphai, T, then A'i = T. + AbstractType AiPrime = alphaI.getBounds().getInstantiation(); + if (AiPrime == null) { + AiPrime = Ai; + } + APrimes.add(AiPrime); + if (AiPrime.getTypeKind() == TypeKind.WILDCARD) { + hasWildcard = true; + } + } + + // The inferred parameterization is either F, if all the type arguments + // are types, or the non-wildcard parameterization (9.9) of F, if one or more + // type arguments are still wildcards. + + AbstractType target = t.replaceTypeArgs(APrimes); + if (hasWildcard) { + return IPair.of(nonWildcardParameterization(target, context), b); + } + return IPair.of(target, b); + } + + @Override + public String toString() { + return expression + " -> " + T; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + Expression that = (Expression) o; + + return expression.equals(that.expression); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + expression.hashCode(); + return result; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java new file mode 100644 index 00000000000..a3dd38ca19a --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java @@ -0,0 +1,146 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.util.typeinference8.types.AbstractQualifier; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.Qualifier; +import org.checkerframework.framework.util.typeinference8.types.QualifierVar; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.BugInCF; + +/** + * Represents a constraint between two {@link AbstractType}. One of: + * + *

            + *
          • {@link Kind#QUALIFIER_SUBTYPE} {@code < Q <: R >}: A qualifier Q is a subtype of a + * qualifier R. + *
          • {@link Kind#QUALIFIER_EQUALITY} {@code < Q = R >}: A qualifier Q is the same as a qualifier + * R. + *
          + */ +public class QualifierTyping implements Constraint { + + /** The qualifier on the left hand side of the constraint. */ + private final AbstractQualifier Q; + + /** The qualifier on the right hand side of the constraint. */ + private final AbstractQualifier R; + + /** + * Kind of constraint. One of: {@link Kind#QUALIFIER_SUBTYPE} or {@link Kind#QUALIFIER_EQUALITY}. + */ + private final Kind kind; + + /** + * Creates a qualifier typing constraint. + * + * @param Q the qualifiers on the left hand side of the constraint + * @param R the qualifiers on the right hand side of the constraint + * @param kind the kind of qualifier constraint + */ + public QualifierTyping(AbstractQualifier Q, AbstractQualifier R, Kind kind) { + assert Q != null && R != null; + switch (kind) { + case QUALIFIER_SUBTYPE: + case QUALIFIER_EQUALITY: + break; + default: + throw new BugInCF("Unexpected kind: " + kind); + } + this.R = R; + this.Q = Q; + this.kind = kind; + } + + @Override + public Kind getKind() { + return kind; + } + + @Override + public ReductionResult reduce(Java8InferenceContext context) { + switch (getKind()) { + case QUALIFIER_EQUALITY: + return reduceEquality(context); + case QUALIFIER_SUBTYPE: + return reduceSubtyping(context); + default: + throw new BugInCF("Unexpected kind: " + getKind()); + } + } + + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + private ReductionResult reduceSubtyping(Java8InferenceContext context) { + if (Q instanceof Qualifier && R instanceof Qualifier) { + AnnotationMirror qAnno = ((Qualifier) Q).getAnnotation(); + AnnotationMirror rAnno = ((Qualifier) R).getAnnotation(); + if (context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(qAnno, rAnno)) { + return ConstraintSet.TRUE; + } + return ConstraintSet.TRUE_ANNO_FAIL; + } + + ConstraintSet constraintSet = new ConstraintSet(); + if (Q instanceof QualifierVar) { + // Q <: R + QualifierVar var = (QualifierVar) Q; + constraintSet.addAll(var.addBound(BoundKind.UPPER, R)); + } + if (R instanceof QualifierVar) { + // Q <: R + QualifierVar var = (QualifierVar) R; + constraintSet.addAll(var.addBound(BoundKind.LOWER, Q)); + } + return constraintSet; + } + + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + private ReductionResult reduceEquality(Java8InferenceContext context) { + if (Q instanceof Qualifier && R instanceof Qualifier) { + AnnotationMirror qAnno = ((Qualifier) Q).getAnnotation(); + AnnotationMirror rAnno = ((Qualifier) R).getAnnotation(); + if (context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(qAnno, rAnno) + && context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(rAnno, qAnno)) { + return ConstraintSet.TRUE; + } + return ConstraintSet.TRUE_ANNO_FAIL; + } + ConstraintSet constraintSet = new ConstraintSet(); + if (Q instanceof QualifierVar) { + // Q == R + QualifierVar var = (QualifierVar) Q; + constraintSet.addAll(var.addBound(BoundKind.EQUAL, R)); + } + if (R instanceof QualifierVar) { + // Q == R + QualifierVar var = (QualifierVar) R; + constraintSet.addAll(var.addBound(BoundKind.EQUAL, Q)); + } + return constraintSet; + } + + @Override + public String toString() { + switch (kind) { + case QUALIFIER_SUBTYPE: + return Q + " <: " + R; + + case QUALIFIER_EQUALITY: + return Q + " = " + R; + default: + assert false; + return super.toString(); + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java new file mode 100644 index 00000000000..c6e3ef481d1 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java @@ -0,0 +1,57 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.framework.util.typeinference8.bound.BoundSet; + +/** + * A result of reduction. One of: {@link TypeConstraint},{@link ConstraintSet},{@link BoundSet}, or + * {@link ReductionResultPair}. + */ +public interface ReductionResult { + + /** + * Indicates that the constraint reduced to true, but unchecked conversion is required for the + * method to be applicable. + */ + @SuppressWarnings("interning:assignment") + @InternedDistinct ReductionResult UNCHECKED_CONVERSION = + new ReductionResult() { + @Override + public String toString() { + return "UNCHECKED_CONVERSION"; + } + }; + + /** A reduction result that contains a bound set and a constraint set. */ + class ReductionResultPair implements ReductionResult { + + /** A constraint set. */ + public final ConstraintSet constraintSet; + + /** A bound set. */ + public final BoundSet boundSet; + + /** + * Creates a reduction result pair. + * + * @param constraintSet a constraint set + * @param boundSet a bound set + */ + private ReductionResultPair(ConstraintSet constraintSet, BoundSet boundSet) { + this.constraintSet = constraintSet; + this.boundSet = boundSet; + } + + /** + * Creates a reduction result pair. + * + * @param constraintSet a constraint set + * @param boundSet a bound set + * @return a reduction result pair + */ + public static ReductionResultPair of(ConstraintSet constraintSet, BoundSet boundSet) { + ReductionResultPair pair = new ReductionResultPair(constraintSet, boundSet); + return pair; + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java new file mode 100644 index 00000000000..6d6de5b7373 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java @@ -0,0 +1,188 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.UseOfVariable; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.javacutil.SwitchExpressionScanner; +import org.checkerframework.javacutil.SwitchExpressionScanner.FunctionalSwitchExpressionScanner; +import org.checkerframework.javacutil.TreeUtils; + +/** + * Constraints are between either an expression and a type, two types, or an expression and a thrown + * type. Defined in JLS section + * 18.1.2 + */ +public abstract class TypeConstraint implements Constraint { + + /** T, the type on the right hand side of the constraint; may contain inference variables. */ + protected AbstractType T; + + /** + * Creates a type constraint + * + * @param T the type of the right hand side of the constraint + */ + protected TypeConstraint(AbstractType T) { + assert T != null : "Can't create a constraint with a null type."; + this.T = T; + } + + /** + * Returns T which is the type on the right hand side of the constraint. + * + * @return T, that is the type on the right hand side of the constraint + */ + public AbstractType getT() { + return T; + } + + /** + * Returns a collection of all inference variables mentioned by this constraint. + * + * @return a collection of all inference variables mentioned by this constraint + */ + public Collection getInferenceVariables() { + return T.getInferenceVariables(); + } + + /** + * For lambda and method references constraints, input variables are roughly the inference + * variables mentioned by they function type's parameter types and return types. For conditional + * expression constraints and switch expression constraints, input variables are the union of the + * input variables of its subexpressions. For all other constraints, no input variables exist. + * + *

          Defined in JLS section + * 18.5.2.2 + * + * @return input variables for this constraint + */ + public abstract List getInputVariables(); + + /** + * "The output variables of [expression] constraints are all inference variables mentioned by the + * type on the right-hand side of the constraint, T, that are not input variables." + * + *

          As defined in JLS section + * 18.5.2.2 + * + * @return output variables for this constraint + */ + public abstract List getOutputVariables(); + + /** + * Implementation of {@link #getInputVariables()} that is used both by expressions constraints and + * checked exception constraints + * https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.5.2-200 + * + * @param tree an expression tree + * @param T the type of the right hand side of the constraint + * @return the input variables for this constraint + */ + protected List getInputVariablesForExpression(ExpressionTree tree, AbstractType T) { + switch (tree.getKind()) { + case LAMBDA_EXPRESSION: + if (T.isUseOfVariable()) { + return Collections.singletonList(((UseOfVariable) T).getVariable()); + } else { + LambdaExpressionTree lambdaTree = (LambdaExpressionTree) tree; + List inputs = new ArrayList<>(); + if (TreeUtils.isImplicitlyTypedLambda(lambdaTree)) { + List params = this.T.getFunctionTypeParameterTypes(); + if (params == null) { + // T is not a function type. + return Collections.emptyList(); + } + for (AbstractType param : params) { + inputs.addAll(param.getInferenceVariables()); + } + } + AbstractType R = this.T.getFunctionTypeReturnType(); + if (R == null || R.getTypeKind() == TypeKind.NONE) { + return inputs; + } + for (ExpressionTree e : TreeUtils.getReturnedExpressions(lambdaTree)) { + TypeConstraint c = new Expression(e, R); + inputs.addAll(c.getInputVariables()); + } + return inputs; + } + case MEMBER_REFERENCE: + if (T.isUseOfVariable()) { + return Collections.singletonList(((UseOfVariable) T).getVariable()); + } else if (TreeUtils.isExactMethodReference((MemberReferenceTree) tree)) { + return Collections.emptyList(); + } else { + List params = this.T.getFunctionTypeParameterTypes(); + if (params == null) { + // T is not a function type. + return Collections.emptyList(); + } + List inputs = new ArrayList<>(); + for (AbstractType param : params) { + inputs.addAll(param.getInferenceVariables()); + } + return inputs; + } + case PARENTHESIZED: + return getInputVariablesForExpression(TreeUtils.withoutParens(tree), T); + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) tree; + List inputs = new ArrayList<>(); + inputs.addAll(getInputVariablesForExpression(conditional.getTrueExpression(), T)); + inputs.addAll(getInputVariablesForExpression(conditional.getFalseExpression(), T)); + return inputs; + default: + if (TreeUtils.isSwitchExpression(tree)) { + List inputs2 = new ArrayList<>(); + + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree exTree, Void unused) -> + inputs2.addAll(getInputVariablesForExpression(exTree, T)), + (r1, r2) -> null); + scanner.scanSwitchExpression(tree, null); + return inputs2; + } + return Collections.emptyList(); + } + } + + /** + * Apply the given instantiations to any type mentioned in this constraint -- meaning replace any + * mention of a variable in {@code instantiations} with its proper type. + */ + public void applyInstantiations() { + T = T.applyInstantiations(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TypeConstraint that = (TypeConstraint) o; + + return T.equals(that.T); + } + + @Override + public int hashCode() { + return T.hashCode(); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java new file mode 100644 index 00000000000..841f0f5dcb8 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java @@ -0,0 +1,482 @@ +package org.checkerframework.framework.util.typeinference8.constraint; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.InferenceType; +import org.checkerframework.framework.util.typeinference8.types.ProperType; +import org.checkerframework.framework.util.typeinference8.types.UseOfVariable; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypesUtils; + +/** + * Represents a constraint between two {@link AbstractType}. One of: + * + *

            + *
          • {@link Kind#TYPE_COMPATIBILITY} {@code < S -> T >}: A type S is compatible in a loose + * invocation context with type T + *
          • {@link Kind#SUBTYPE} {@code < S <: T >}: A reference type S is a subtype of a reference + * type T + *
          • {@link Kind#CONTAINED} {@code < S <= T >}: A type argument S is contained by a type + * argument T. + *
          • {@link Kind#TYPE_EQUALITY} {@code < S = T >}: A type S is the same as a type T, or a type + * argument S is the same as type argument T. + *
          + */ +public class Typing extends TypeConstraint { + + /** One of the abstract types in this constraint. {@link #T} is the other. */ + private AbstractType S; + + /** + * Kind of constraint. One of: {@link Kind#TYPE_COMPATIBILITY}, {@link Kind#SUBTYPE}, {@link + * Kind#CONTAINED}, or {@link Kind#TYPE_EQUALITY} + */ + private final Kind kind; + + /** Whether this constraint is for a covariant type argument. */ + private boolean isCovarTypeArg; + + /** + * Creates a typing constraint. + * + * @param S left hand side type + * @param t right hand side type + * @param kind the kind of constraint + */ + public Typing(AbstractType S, AbstractType t, Kind kind) { + this(S, t, kind, false); + } + + /** + * Creates a typing constraint. + * + * @param S left hand side type + * @param t right hand side type + * @param kind the kind of constraint + * @param covarTypeArg whether the constraint is for a covariant type argument + */ + public Typing(AbstractType S, AbstractType t, Kind kind, boolean covarTypeArg) { + super(t); + assert S != null; + switch (kind) { + case TYPE_COMPATIBILITY: + case SUBTYPE: + case CONTAINED: + case TYPE_EQUALITY: + break; + default: + throw new BugInCF("Unexpected kind: " + kind); + } + this.S = S; + this.kind = kind; + this.isCovarTypeArg = covarTypeArg; + } + + /** + * Return one of the abstract types in this constraint. + * + * @return one of the abstract types in this constraint + */ + public AbstractType getS() { + return S; + } + + @Override + public Kind getKind() { + return kind; + } + + @Override + public List getInputVariables() { + return Collections.emptyList(); + } + + @Override + public List getOutputVariables() { + return Collections.emptyList(); + } + + @Override + public List getInferenceVariables() { + Set vars = new HashSet<>(); + vars.addAll(T.getInferenceVariables()); + vars.addAll(S.getInferenceVariables()); + return new ArrayList<>(vars); + } + + @Override + public void applyInstantiations() { + super.applyInstantiations(); + S = S.applyInstantiations(); + } + + @Override + public ReductionResult reduce(Java8InferenceContext context) { + + switch (getKind()) { + case TYPE_COMPATIBILITY: + return reduceCompatible(); + case SUBTYPE: + return reduceSubtyping(context); + case CONTAINED: + return reduceContained(); + case TYPE_EQUALITY: + return reduceEquality(); + default: + throw new BugInCF("Unexpected kind: " + getKind()); + } + } + + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint. See JLS + * 18.2.3. + * + * @param context the context + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtyping(Java8InferenceContext context) { + if (S.isProper() && T.isProper()) { + ReductionResult isSubtype = ((ProperType) S).isSubType((ProperType) T); + if (isSubtype == ConstraintSet.TRUE) { + return ConstraintSet.TRUE; + } else if (((ProperType) S).isSubTypeUnchecked((ProperType) T) == ConstraintSet.TRUE) { + return ReductionResult.UNCHECKED_CONVERSION; + } + return isSubtype; + } else if (S.getTypeKind() == TypeKind.NULL) { + if (T.isUseOfVariable()) { + UseOfVariable tUseOf = (UseOfVariable) T; + tUseOf.addQualifierBound(BoundKind.LOWER, S.getQualifiers()); + } + return ConstraintSet.TRUE; + } else if (T.getTypeKind() == TypeKind.NULL) { + return ConstraintSet.FALSE; + } + + if (S.isUseOfVariable() || T.isUseOfVariable()) { + if (S.isUseOfVariable()) { + if (T.getTypeKind() == TypeKind.TYPEVAR && T.isLowerBoundTypeVariable()) { + ((UseOfVariable) S).addBound(VariableBounds.BoundKind.UPPER, T.getTypeVarLowerBound()); + } else { + ((UseOfVariable) S).addBound(VariableBounds.BoundKind.UPPER, T); + } + } + if (T.isUseOfVariable()) { + if (TypesUtils.isCapturedTypeVariable(S.getJavaType())) { + ((UseOfVariable) T).addBound(VariableBounds.BoundKind.LOWER, S.getTypeVarUpperBound()); + } + ((UseOfVariable) T).addBound(VariableBounds.BoundKind.LOWER, S); + } + return ConstraintSet.TRUE; + } + + switch (T.getTypeKind()) { + case DECLARED: + return reduceSubtypeClass(context); + case ARRAY: + return reduceSubtypeArray(); + case WILDCARD: + case TYPEVAR: + return reduceSubtypeTypeVariable(); + case INTERSECTION: + return reduceSubtypingIntersection(); + default: + return ConstraintSet.FALSE; + } + } + + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is a class type. See JLS 18.2.3. + * + * @param context the context + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypeClass(Java8InferenceContext context) { + if (T.isParameterizedType()) { + // let A1, ..., An be the type arguments of T. Among the supertypes of S, a + // corresponding class or interface type is identified, with type arguments B1, ..., + // Bn. If no such type exists, the constraint reduces to false. Otherwise, the + // constraint reduces to the following new constraints: + // for all i (1 <= i <= n), . + + AbstractType sAsSuper = S.asSuper(T.getJavaType()); + if (sAsSuper == null) { + return ConstraintSet.FALSE; + } else if (sAsSuper.isRaw() || T.isRaw()) { + return ReductionResult.UNCHECKED_CONVERSION; + } + + List Bs = sAsSuper.getTypeArguments(); + Iterator As = T.getTypeArguments().iterator(); + List covariantArgIndexes = + context + .typeFactory + .getTypeHierarchy() + .getCovariantArgIndexes((AnnotatedDeclaredType) T.getAnnotatedType()); + ConstraintSet set = new ConstraintSet(); + int index = 0; + for (AbstractType b : Bs) { + AbstractType a = As.next(); + boolean convarArg = covariantArgIndexes.contains(index); + set.add(new Typing(b, a, Kind.CONTAINED, convarArg)); + index++; + } + + return set; + } else { + // The constraint reduces to true if T is among the supertypes of S, and false otherwise. + return ((InferenceType) S).isSubType((ProperType) T); + } + } + + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is an array type. See JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypeArray() { + AbstractType msArrayType = S.getMostSpecificArrayType(); + if (msArrayType == null) { + return ConstraintSet.FALSE; + } + if (msArrayType.isPrimitiveArray() && T.isPrimitiveArray()) { + return ConstraintSet.TRUE; + } else { + return new Typing(msArrayType.getComponentType(), T.getComponentType(), Kind.SUBTYPE); + } + } + + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is a type variable. See JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypeTypeVariable() { + if (S.getTypeKind() == TypeKind.INTERSECTION) { + return ConstraintSet.TRUE; + } else if (T.getTypeKind() == TypeKind.TYPEVAR && T.isLowerBoundTypeVariable()) { + return new Typing(S, T.getTypeVarLowerBound(), Kind.SUBTYPE); + } else if (T.getTypeKind() == TypeKind.WILDCARD && T.isLowerBoundedWildcard()) { + return new Typing(S, T.getWildcardLowerBound(), Kind.SUBTYPE); + } else { + return ConstraintSet.FALSE; + } + } + + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is an intersection type. See JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypingIntersection() { + ConstraintSet constraintSet = new ConstraintSet(); + for (AbstractType bound : T.getIntersectionBounds()) { + constraintSet.add(new Typing(S, bound, Kind.SUBTYPE)); + } + return constraintSet; + } + + /** + * Returns the result of reducing this constraint, assuming it is a containment constraint. See + * JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceContained() { + if (T.getTypeKind() != TypeKind.WILDCARD) { + if (S.getTypeKind() != TypeKind.WILDCARD) { + if (isCovarTypeArg) { + return new Typing(S, T, Kind.SUBTYPE); + } + return new Typing(S, T, Kind.TYPE_EQUALITY); + } else { + return ConstraintSet.FALSE; + } + } else if (T.isUnboundWildcard()) { + return ConstraintSet.TRUE; + } else if (T.isUpperBoundedWildcard()) { + AbstractType bound = T.getWildcardUpperBound(); + if (S.getTypeKind() == TypeKind.WILDCARD) { + if (S.isUnboundWildcard() || S.isUpperBoundedWildcard()) { + return new Typing(S.getWildcardUpperBound(), bound, Kind.SUBTYPE); + } else { + return new Typing(S.getWildcardLowerBound(), bound, Kind.TYPE_EQUALITY); + } + } else { + return new Typing(S, bound, Kind.SUBTYPE); + } + } else { // T is lower bounded wildcard + AbstractType tPrime = T.getWildcardLowerBound(); + if (S.getTypeKind() != TypeKind.WILDCARD) { + return new Typing(tPrime, S, Kind.SUBTYPE); + } else if (S.isLowerBoundedWildcard()) { + return new Typing(tPrime, S.getWildcardLowerBound(), Kind.SUBTYPE); + } else { + return ConstraintSet.FALSE; + } + } + } + + /** + * Returns the result of reducing this constraint, assume it is a type compatibility constraint. + * See JLS 18.2.2 + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceCompatible() { + if (T.isProper() && S.isProper()) { + // the constraint reduces to true if S is compatible in a loose invocation context + // with T (5.3), and false otherwise. + ReductionResult r = ((ProperType) S).isSubTypeUnchecked((ProperType) T); + if (ConstraintSet.TRUE == r) { + return ConstraintSet.TRUE; + } + return ((ProperType) S).isAssignable((ProperType) T); + } else if (S.isProper() && S.getTypeKind().isPrimitive()) { + return new Typing(((ProperType) S).boxType(), T, Kind.TYPE_COMPATIBILITY); + } else if (T.isProper() && T.getTypeKind().isPrimitive()) { + return new Typing(S, ((ProperType) T).boxType(), Kind.TYPE_EQUALITY); + } else if (T.isParameterizedType() && !S.isUseOfVariable()) { + // Otherwise, if T is a parameterized type of the form G, + // and there exists no type of the form G<...> that is a supertype of S, + // but the raw type G is a supertype of S, then the constraint reduces to true. + AbstractType superS = S.asSuper(T.getJavaType()); + if (superS != null && superS.isRaw()) { + return ReductionResult.UNCHECKED_CONVERSION; + } + } else if (T.getTypeKind() == TypeKind.ARRAY && T.getComponentType().isParameterizedType()) { + AbstractType superS = S.asSuper(T.getJavaType()); + if (superS != null && superS.getComponentType().isRaw()) { + return ReductionResult.UNCHECKED_CONVERSION; + } + } + + return new Typing(S, T, Kind.SUBTYPE); + } + + /** + * Returns the result of reducing this constraint, assume it is an equality constraint. See JLS + * 18.2.4 + * + * @return the result of reducing the constraint + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private ReductionResult reduceEquality() { + if (S.isProper()) { + if (T.isProper()) { + // If S and T are proper types, the constraint reduces to true if S is the same + // as T (4.3.4), and false otherwise. + return ConstraintSet.TRUE; + } + ProperType sProper = (ProperType) S; + if (sProper.getTypeKind() == TypeKind.NULL || sProper.getTypeKind().isPrimitive()) { + // if S or T is the null type, the constraint reduces to false. + return ConstraintSet.FALSE; + } + } else if (T.isProper()) { + ProperType tProper = (ProperType) T; + if (tProper.getTypeKind() == TypeKind.NULL || tProper.getTypeKind().isPrimitive()) { + // if S or T is the null type, the constraint reduces to false. + return ConstraintSet.FALSE; + } + } + + if (S.isUseOfVariable() || T.isUseOfVariable()) { + if (S.isUseOfVariable()) { + ((UseOfVariable) S).addBound(VariableBounds.BoundKind.EQUAL, T); + } + if (T.isUseOfVariable()) { + ((UseOfVariable) T).addBound(VariableBounds.BoundKind.EQUAL, S); + } + return ConstraintSet.TRUE; + } + + List sTypeArgs = S.getTypeArguments(); + List tTypeArgs = T.getTypeArguments(); + if (sTypeArgs != null && tTypeArgs != null && sTypeArgs.size() == tTypeArgs.size()) { + // Assume if both have type arguments, then S and T are class or interface types with + // the same erasure + ConstraintSet constraintSet = new ConstraintSet(); + for (int i = 0; i < tTypeArgs.size(); i++) { + if (tTypeArgs.get(i) != sTypeArgs.get(i)) { + constraintSet.add(new Typing(tTypeArgs.get(i), sTypeArgs.get(i), Kind.TYPE_EQUALITY)); + } + } + return constraintSet; + } + + AbstractType sComponentType = S.getComponentType(); + AbstractType tComponentType = T.getComponentType(); + if (sComponentType != null && tComponentType != null) { + return new Typing(sComponentType, tComponentType, Kind.TYPE_EQUALITY); + } + + if (T.getTypeKind() == TypeKind.WILDCARD && S.getTypeKind() == TypeKind.WILDCARD) { + if (T.isUnboundWildcard() && S.isUnboundWildcard()) { + return ConstraintSet.TRUE; + } else if (!S.isLowerBoundedWildcard() && !T.isLowerBoundedWildcard()) { + return new Typing(S.getWildcardUpperBound(), T.getWildcardUpperBound(), Kind.TYPE_EQUALITY); + } else if (T.isLowerBoundedWildcard() && S.isLowerBoundedWildcard()) { + return new Typing(T.getWildcardLowerBound(), S.getWildcardLowerBound(), Kind.TYPE_EQUALITY); + } + } + return ConstraintSet.FALSE; + } + + @Override + public String toString() { + switch (kind) { + case TYPE_COMPATIBILITY: + return S + " -> " + T; + case SUBTYPE: + return S + " <: " + T; + case CONTAINED: + return S + " <= " + T; + case TYPE_EQUALITY: + return S + " = " + T; + default: + assert false; + return super.toString(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + Typing typing = (Typing) o; + + return S.equals(typing.S) && kind == typing.kind; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + S.hashCode(); + result = 31 * result + kind.hashCode(); + return result; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java new file mode 100644 index 00000000000..be405b73202 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java @@ -0,0 +1,157 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.BinaryOperator; +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; + +/** + * This is the super class for a qualifier, {@link Qualifier} or a qualifier variable, {@link + * QualifierVar}. A {@link Qualifier} is a wrapper around {@code AnnotationMirror}. A {@code + * QualifierVar} is a variable for a polymorphic qualifier that needs to be viewpoint adapted at a + * call site. + */ +public abstract class AbstractQualifier { + + /** The (interned) name of the top qualifier in the same hierarchy as the qualifier. */ + protected final @Interned String hierarchyName; + + /** The context. */ + protected final Java8InferenceContext context; + + /** + * Creates an {@code AbstractQualifier}. + * + * @param anno an annotation mirror + * @param context the context + */ + AbstractQualifier(AnnotationMirror anno, Java8InferenceContext context) { + AnnotationMirror top = context.typeFactory.getQualifierHierarchy().getTopAnnotation(anno); + hierarchyName = AnnotationUtils.annotationNameInterned(top); + this.context = context; + } + + /** + * Returns whether {@code other} is in the same hierarchy as this. + * + * @param other another abstract qualifier + * @return whether {@code other} is in the same hierarchy as this. + */ + public boolean sameHierarchy(AbstractQualifier other) { + return this.hierarchyName == other.hierarchyName; + } + + /** + * Returns the instantiation of this. + * + * @return the instantiation of this. + */ + abstract AnnotationMirror getInstantiation(); + + /** + * Returns the least upper bounds of {@code quals}. + * + * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies and + * multiple qualifiers for a hierarchy + * @param context a context + * @return the least upper bounds of {@code quals} + */ + public static Set lub( + Set quals, Java8InferenceContext context) { + return combine( + quals, context.typeFactory.getQualifierHierarchy()::leastUpperBoundQualifiersOnly); + } + + /** + * Returns the greatest lower bounds of {@code quals}. + * + * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies and + * multiple qualifiers for a hierarchy + * @param context a context + * @return the greatest lowest bounds of {@code quals} + */ + public static Set glb( + Set quals, Java8InferenceContext context) { + return combine( + quals, context.typeFactory.getQualifierHierarchy()::greatestLowerBoundQualifiersOnly); + } + + /** + * Returns the result of applying the {@code combine} function on {@code quals}. + * + * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies and + * multiple qualifiers for a hierarchy + * @param combine a functions that combines two {@code AnnotationMirror}s and returns a single + * {@code AnnotationMirror} + * @return the result of applying the {@code combine} function on {@code quals} + */ + private static Set combine( + Set quals, BinaryOperator combine) { + Map m = new HashMap<>(); + + for (AbstractQualifier qual : quals) { + AnnotationMirror lub = m.get(qual.hierarchyName); + if (lub != null) { + lub = combine.apply(lub, qual.getInstantiation()); + } else { + lub = qual.getInstantiation(); + } + m.put(qual.hierarchyName, lub); + } + return new AnnotationMirrorSet(m.values()); + } + + /** + * Creates an {@code AbstractQualifier} for each {@code AnnotationMirror} in {@code annos}. If an + * annotation mirror is a polymorphic qualifier in {@code qualifierVars}, the {@code QualifierVar} + * it maps to in {@code qualifierVars} is added to the returned set. Otherwise, a new {@code + * Qualifier} is added. + * + * @param annos a set of annotation mirrors + * @param qualifierVars a map from polymorphic qualifiers to {@link QualifierVar} + * @param context a context + * @return a set containing an {@code AbstractQualifier} for each annotation in {@code + * qualifierVars} + */ + public static Set create( + Set annos, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + if (qualifierVars.isEmpty()) { + return create(annos, context); + } + + Set quals = new HashSet<>(); + for (AnnotationMirror anno : annos) { + if (qualifierVars.containsKey(anno)) { + quals.add(qualifierVars.get(anno)); + } else { + quals.add(new Qualifier(anno, context)); + } + } + return quals; + } + + /** + * Creates new {@code Qualifier} is added for each annotation in {@code annos}. + * + * @param annos a set of annotation mirrors + * @param context a context + * @return new {@code Qualifier} is added for each annotation in {@code annos} + */ + private static Set create( + Set annos, Java8InferenceContext context) { + Set quals = new HashSet<>(); + for (AnnotationMirror anno : annos) { + quals.add(new Qualifier(anno, context)); + } + return quals; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java new file mode 100644 index 00000000000..7f858f6ae88 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java @@ -0,0 +1,646 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Type.WildcardType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.AnnotatedTypeParameterBounds; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.IPair; + +/** + * As explained in section 18.1, + * the JLS Chapter on type inference use the term "type" to "include type-like syntax that contains + * inference variables". This class represents this types. Three subclasses of this class are: + * + *
            + *
          • {@link ProperType}: types that do not contain inference variables + *
          • {@link Variable}: inference variables + *
          • {@link InferenceType}: type-like syntax that contain at least one inference variable + *
          + */ +public abstract class AbstractType { + + /** The kind of {@link AbstractType}. */ + public enum Kind { + /** {@link ProperType},a type that contains no inference variables* */ + PROPER, + /** {@link UseOfVariable}, a use of an inference variable. */ + USE_OF_VARIABLE, + /** + * {@link InferenceType}, a type that contains inference variables, but is not an inference + * variable. + */ + INFERENCE_TYPE + } + + /** The context object. */ + protected final Java8InferenceContext context; + + /** The {@link AnnotatedTypeFactory}. */ + protected final AnnotatedTypeFactory typeFactory; + + /** + * Creates an {@link AbstractType}. + * + * @param context the context object + */ + protected AbstractType(Java8InferenceContext context) { + this.context = context; + this.typeFactory = context.typeFactory; + } + + /** + * Returns the kind of {@link AbstractType}. + * + * @return the kind of {@link AbstractType} + */ + public abstract Kind getKind(); + + /** + * Return true if this type is a proper type. + * + * @return true if this type is a proper type + */ + public boolean isProper() { + return getKind() == Kind.PROPER; + } + + /** + * Return true if this type is a use of an inference variable. + * + * @return true if this type is a use of an inference variable + */ + public boolean isUseOfVariable() { + return getKind() == Kind.USE_OF_VARIABLE; + } + + /** + * Return true if this type contains inference variables, but is not an inference variable. + * + * @return true if this type contains inference variables, but is not an inference variable + */ + public boolean isInferenceType() { + return getKind() == Kind.INFERENCE_TYPE; + } + + /** + * Returns the TypeKind of the underlying Java type. + * + * @return the TypeKind of the underlying Java type + */ + public final TypeKind getTypeKind() { + return getJavaType().getKind(); + } + + /** + * Creates a type using the given types. + * + * @param atm annotated type mirror + * @param type type mirror + * @return the new type + */ + public abstract AbstractType create(AnnotatedTypeMirror atm, TypeMirror type); + + /** + * Return the underlying Java type without inference variables. + * + * @return the underlying Java type without inference variables + */ + public abstract TypeMirror getJavaType(); + + /** + * Return the underlying Java type without inference variables. + * + * @return the underlying Java type without inference variables + */ + public abstract AnnotatedTypeMirror getAnnotatedType(); + + /** + * Return a collection of all inference variables referenced by this type. + * + * @return a collection of all inference variables referenced by this type + */ + public abstract Collection getInferenceVariables(); + + /** + * Return a new type that is the same as this one except the variables in {@code instantiations} + * have been replaced by their instantiation. + * + * @return a new type that is the same as this one except the variables in {@code instantiations} + * have been replaced by their instantiation + */ + public abstract AbstractType applyInstantiations(); + + /** + * Return true if this type is java.lang.Object. + * + * @return true if this type is java.lang.Object + */ + public abstract boolean isObject(); + + /** + * Assuming the type is a declared type, this method returns the upper bounds of its type + * parameters. (A type parameter of a declared type, can't refer to any type being inferred, so + * they are proper types.) + * + * @return the upper bounds of the type parameter of this type + */ + public List getTypeParameterBounds() { + TypeElement typeelem = (TypeElement) ((DeclaredType) getJavaType()).asElement(); + List bounds = new ArrayList<>(); + List typeVars = + typeFactory.typeVariablesFromUse((AnnotatedDeclaredType) getAnnotatedType(), typeelem); + Iterator javaEle = typeelem.getTypeParameters().iterator(); + + for (AnnotatedTypeParameterBounds bound : typeVars) { + TypeVariable typeVariable = (TypeVariable) javaEle.next().asType(); + bounds.add(new ProperType(bound.getUpperBound(), typeVariable.getUpperBound(), context)); + } + return bounds; + } + + /** + * Return a new type that is the capture of this type. + * + * @param context the context object + * @return a new type that is the capture of this type + */ + public AbstractType capture(Java8InferenceContext context) { + AnnotatedTypeMirror capturedType = + context.typeFactory.applyCaptureConversion(getAnnotatedType()); + return create(capturedType, capturedType.getUnderlyingType()); + } + + /** + * If {@code superType} is a super type of this type, then this method returns the super type of + * this type that is the same class as {@code superType}. Otherwise, it returns null + * + * @param superType a type, need not be a super type of this type + * @return super type of this type that is the same class as {@code superType} or null if one + * doesn't exist + */ + public AbstractType asSuper(TypeMirror superType) { + TypeMirror typeJava = getJavaType(); + if (typeJava.getKind() == TypeKind.WILDCARD) { + typeJava = ((WildcardType) typeJava).getExtendsBound(); + } + TypeMirror asSuperJava = context.types.asSuper((Type) typeJava, ((Type) superType).asElement()); + if (asSuperJava == null) { + return null; + } + + AnnotatedTypeMirror type = getAnnotatedType(); + + if (type.getKind() == TypeKind.WILDCARD) { + type = ((AnnotatedWildcardType) type).getExtendsBound(); + } + + AnnotatedTypeMirror superAnnotatedType = + AnnotatedTypeMirror.createType(superType, typeFactory, type.isDeclaration()); + typeFactory.initializeAtm(superAnnotatedType); + AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(typeFactory, type, superAnnotatedType); + return create(asSuper, asSuper.getUnderlyingType()); + } + + /** + * If this {@link AbstractType} is a functional interface type, then {@code functionType} is its + * function type. Otherwise, {@code functionType} is null. Initialized by {@link + * #getFunctionType()}. + */ + private IPair functionType = null; + + /** + * If this {@link AbstractType} is a functional interface type, then its function type is + * returned. Otherwise, returns null. + * + * @return this {@link AbstractType} is a functional interface type, then its function type is + * returned; otherwise, returns null + */ + IPair getFunctionType() { + if (functionType == null) { + ExecutableElement element = TypesUtils.findFunction(getJavaType(), context.env); + AnnotatedDeclaredType groundType = + makeGround((AnnotatedDeclaredType) getAnnotatedType(), typeFactory); + AnnotatedExecutableType aet = + AnnotatedTypes.asMemberOf(context.modelTypes, typeFactory, groundType, element); + functionType = IPair.of(aet, aet.getUnderlyingType()); + } + return functionType; + } + + /** + * If this type is a functional interface, then this method returns the return type of the + * function type of that functional interface. Otherwise, returns null. + * + * @return the return type of the function type of this type or null if one doesn't exist + */ + public AbstractType getFunctionTypeReturnType() { + if (TypesUtils.isFunctionalInterface(getJavaType(), context.env)) { + IPair pair = getFunctionType(); + ExecutableType elementType = pair.second; + TypeMirror returnTypeJava = elementType.getReturnType(); + if (returnTypeJava.getKind() == TypeKind.VOID) { + return null; + } + + AnnotatedExecutableType aet = pair.first; + AnnotatedTypeMirror returnType = aet.getReturnType(); + if (returnType.getKind() == TypeKind.VOID) { + return null; + } + return create(returnType, returnTypeJava); + } else { + return null; + } + } + + /** + * If this type is a functional interface, then this method returns the parameter types of the + * function type of that functional interface. Otherwise, it returns null. + * + * @return the parameter types of the function type of this type or null if no function type + * exists + */ + public List getFunctionTypeParameterTypes() { + if (TypesUtils.isFunctionalInterface(getJavaType(), context.env)) { + IPair pair = getFunctionType(); + List paramsTypeMirror = pair.second.getParameterTypes(); + List params = new ArrayList<>(); + Iterator iter = paramsTypeMirror.iterator(); + for (AnnotatedTypeMirror param : pair.first.getParameterTypes()) { + params.add(create(param, iter.next())); + } + return params; + } else { + return null; + } + } + + /** + * Make {@code type} ground, which is basically changing any wildcards to their bounds. JLS section + * 15.27.3 + * + * @param type a type to ground + * @param typeFactory type factory + * @return the ground type + */ + // TODO: This method is named make ground, but is actually implements non-wildcard + // parameterization as defined in + // https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.9 + // https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.13.2 + static AnnotatedDeclaredType makeGround( + AnnotatedDeclaredType type, AnnotatedTypeFactory typeFactory) { + Element e = type.getUnderlyingType().asElement(); + AnnotatedDeclaredType decl = typeFactory.getAnnotatedType((TypeElement) e); + Iterator bounds = decl.getTypeArguments().iterator(); + + // typeFactory.getTypeVarSubstitutor().substitute() + Map typeVarToTypeArg = new HashMap<>(); + for (AnnotatedTypeMirror pn : type.getTypeArguments()) { + AnnotatedTypeVariable typeVariable = (AnnotatedTypeVariable) bounds.next(); + if (pn.getKind() != TypeKind.WILDCARD) { + typeVarToTypeArg.put(typeVariable.getUnderlyingType(), pn); + continue; + } + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) pn; + if (wildcardType.getSuperBound().getKind() == TypeKind.NULL) { + // › If Ai is a upper-bounded wildcard ? extends Ui, then Ti = glb(Ui, Bi) + typeVarToTypeArg.put( + typeVariable.getUnderlyingType(), + AnnotatedTypes.annotatedGLB( + typeFactory, typeVariable.getUpperBound(), wildcardType.getExtendsBound())); + } else { + typeVarToTypeArg.put(typeVariable.getUnderlyingType(), wildcardType.getSuperBound()); + } + } + return (AnnotatedDeclaredType) + typeFactory.getTypeVarSubstitutor().substitute(typeVarToTypeArg, decl.asUse()); + } + + /** + * Return true if the type is a raw type. + * + * @return true if the type is a raw type + */ + public boolean isRaw() { + if (getAnnotatedType().getKind() == TypeKind.DECLARED) { + return ((AnnotatedDeclaredType) getAnnotatedType()).isUnderlyingTypeRaw(); + } + return false; + } + + /** + * Return a new type that is the same type as this one, but whose type arguments are {@code args}. + * + * @param args a list of type arguments + * @return a new type that is the same type as this one, but whose type arguments are {@code args} + */ + public AbstractType replaceTypeArgs(List args) { + DeclaredType declaredType = (DeclaredType) getJavaType(); + TypeMirror[] newArgs = new TypeMirror[args.size()]; + int i = 0; + for (AbstractType t : args) { + newArgs[i++] = t.getJavaType(); + } + TypeMirror newTypeJava = + context.env.getTypeUtils().getDeclaredType((TypeElement) declaredType.asElement(), newArgs); + + AnnotatedDeclaredType newType = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + newTypeJava, typeFactory, getAnnotatedType().isDeclaration()); + List argTypes = new ArrayList<>(); + for (AbstractType arg : args) { + argTypes.add(arg.getAnnotatedType()); + } + newType.setTypeArguments(argTypes); + newType.replaceAnnotations(getAnnotatedType().getPrimaryAnnotations()); + return create(newType, newTypeJava); + } + + /** + * Whether the proper type is a parameterized class or interface type, or an inner class type of a + * parameterized class or interface type (directly or indirectly) + * + * @return whether T is a parameterized type + */ + public boolean isParameterizedType() { + // TODO this isn't matching the JavaDoc. + return ((Type) getJavaType()).isParameterized() || ((Type) getJavaType()).isRaw(); + } + + /** + * Return the most specific array type that is a super type of this type or null if one doesn't + * exist. + * + * @return the most specific array type that is a super type of this type or null if one doesn't + * exist + */ + public AbstractType getMostSpecificArrayType() { + if (getTypeKind() == TypeKind.ARRAY) { + return this; + } else { + AnnotatedTypeMirror msat = mostSpecificArrayType(getAnnotatedType()); + TypeMirror typeMirror = + TypesUtils.getMostSpecificArrayType(getJavaType(), context.modelTypes); + if (msat != null) { + return create(msat, typeMirror); + } + return null; + } + } + + /** + * Return the most specific array type, that is the first super type of {@code type} that is not + * an array. + * + * @param type annotated type mirror + * @return the first supertype of {@code type} that is an array + */ + private AnnotatedTypeMirror mostSpecificArrayType(AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.ARRAY) { + return type; + } else { + for (AnnotatedTypeMirror superType : this.getAnnotatedType().directSupertypes()) { + AnnotatedTypeMirror arrayType = mostSpecificArrayType(superType); + if (arrayType != null) { + return arrayType; + } + } + return null; + } + } + + /** + * Return true if this type is a primitive array. + * + * @return true if this type is a primitive array + */ + public boolean isPrimitiveArray() { + return getJavaType().getKind() == TypeKind.ARRAY + && ((ArrayType) getJavaType()).getComponentType().getKind().isPrimitive(); + } + + /** + * Return assuming type is an intersection type, this method returns the bounds in this type. + * + * @return assuming type is an intersection type, this method returns the bounds in this type + */ + public List getIntersectionBounds() { + List boundsJava = ((IntersectionType) getJavaType()).getBounds(); + Iterator iter = boundsJava.iterator(); + List bounds = new ArrayList<>(); + for (AnnotatedTypeMirror bound : + ((AnnotatedIntersectionType) getAnnotatedType()).directSupertypes()) { + bounds.add(create(bound, iter.next())); + } + return bounds; + } + + /** + * Return assuming this type is a type variable, this method returns the upper bound of this type. + * + * @return assuming this type is a type variable, this method returns the upper bound of this type + */ + public AbstractType getTypeVarUpperBound() { + TypeMirror javaUpperBound = ((TypeVariable) getJavaType()).getUpperBound(); + return create(((AnnotatedTypeVariable) getAnnotatedType()).getUpperBound(), javaUpperBound); + } + + /** + * Return assuming this type is a type variable that has a lower bound, this method returns the + * lower bound of this type. + * + * @return assuming this type is a type variable that has a lower bound, this method returns the + * lower bound of this type + */ + public AbstractType getTypeVarLowerBound() { + TypeMirror lowerBound = ((TypeVariable) getJavaType()).getLowerBound(); + return create(((AnnotatedTypeVariable) getAnnotatedType()).getLowerBound(), lowerBound); + } + + /** + * Return true if this type is a type variable with a lower bound. + * + * @return true if this type is a type variable with a lower bound + */ + public boolean isLowerBoundTypeVariable() { + return ((TypeVariable) getJavaType()).getLowerBound().getKind() != TypeKind.NULL; + } + + /** + * Return true if this type is a parameterized type whose has at least one wildcard as a type + * argument. + * + * @return true if this type is a parameterized type whose has at least one wildcard as a type + * argument + */ + public boolean isWildcardParameterizedType() { + return TypesUtils.isWildcardParameterized(getJavaType()); + } + + /** + * Return this type's type arguments or null if this type isn't a declared type. + * + * @return this type's type arguments or null this type isn't a declared type + */ + public List getTypeArguments() { + if (getJavaType().getKind() != TypeKind.DECLARED) { + return null; + } + if (((AnnotatedDeclaredType) getAnnotatedType()).isUnderlyingTypeRaw()) { + return Collections.emptyList(); + } + List javaTypeArgs = ((DeclaredType) getJavaType()).getTypeArguments(); + Iterator iter = javaTypeArgs.iterator(); + List list = new ArrayList<>(); + for (AnnotatedTypeMirror typeArg : + ((AnnotatedDeclaredType) getAnnotatedType()).getTypeArguments()) { + list.add(create(typeArg, iter.next())); + } + return list; + } + + /** + * Return true if the type is an unbound wildcard. + * + * @return true if the type is an unbound wildcard + */ + public boolean isUnboundWildcard() { + return TypesUtils.hasNoExplicitBound(getJavaType()); + } + + /** + * Return true if the type is a wildcard with an upper bound. + * + * @return true if the type is a wildcard with an upper bound + */ + public boolean isUpperBoundedWildcard() { + return TypesUtils.hasExplicitExtendsBound(getJavaType()); + } + + /** + * Return true if the type is a wildcard with a lower bound. + * + * @return true if the type is a wildcard with a lower bound + */ + public boolean isLowerBoundedWildcard() { + return TypesUtils.hasExplicitSuperBound(getJavaType()); + } + + /** + * Return if this type is a wildcard return its lower bound; otherwise, return null. + * + * @return if this type is a wildcard return its lower bound; otherwise, return null + */ + public AbstractType getWildcardLowerBound() { + if (getJavaType().getKind() == TypeKind.WILDCARD) { + WildcardType wild = (WildcardType) getJavaType(); + return create( + ((AnnotatedWildcardType) getAnnotatedType()).getSuperBound(), wild.getSuperBound()); + } + return null; + } + + /** + * Return if this type is a wildcard return its upper bound; otherwise, return null. + * + * @return if this type is a wildcard return its upper bound; otherwise, return null + */ + public AbstractType getWildcardUpperBound() { + if (getJavaType().getKind() == TypeKind.WILDCARD) { + TypeMirror upperBoundJava = ((WildcardType) getJavaType()).getExtendsBound(); + if (upperBoundJava == null) { + upperBoundJava = context.object.getJavaType(); + } + return create(((AnnotatedWildcardType) getAnnotatedType()).getExtendsBound(), upperBoundJava); + } else { + return null; + } + } + + /** + * Return new type whose Java type is the erasure of this type. + * + * @return a new type whose Java type is the erasure of this type + */ + public AbstractType getErased() { + TypeMirror typeMirror = context.env.getTypeUtils().erasure(getJavaType()); + return create(getAnnotatedType().getErased(), typeMirror); + } + + /** + * Return the array component type fo this type or null if on does not exist. + * + * @return the array component type of this type or null if one does not exist + */ + public final AbstractType getComponentType() { + if (getJavaType().getKind() == TypeKind.ARRAY) { + TypeMirror javaType = ((ArrayType) getJavaType()).getComponentType(); + return create(((AnnotatedArrayType) getAnnotatedType()).getComponentType(), javaType); + } else { + return null; + } + } + + /** + * Returns the primary qualifiers on this type. + * + * @return the primary qualifiers on this type + */ + public abstract Set getQualifiers(); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AbstractType that = (AbstractType) o; + + if (!context.equals(that.context)) { + return false; + } + return typeFactory.equals(that.typeFactory); + } + + @Override + public int hashCode() { + int result = context.hashCode(); + result = 31 * result + typeFactory.hashCode(); + return result; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java new file mode 100644 index 00000000000..59faa24cb00 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java @@ -0,0 +1,162 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; + +/** Helper class for determining if a type contains an inference variable. */ +public class AnnotatedContainsInferenceVariable { + + /** Creates an AnnotatedContainsInferenceVariable. */ + public AnnotatedContainsInferenceVariable() {} + + /** + * Returns true if {@code type} contains any of the type variables in {@code typeVariables}. + * + * @param typeVariables a collection of type variables + * @param type a type to check + * @return true if {@code type} contains any of the type variables in {@code typeVariables} + */ + public static boolean hasAnyTypeVariable( + Collection typeVariables, AnnotatedTypeMirror type) { + return new Visitor(typeVariables).visit(type); + } + + /** A helper class to find type variables mentioned by a type. */ + private static class Visitor implements AnnotatedTypeVisitor { + + /** Type variables for which to search. */ + private final Collection typeVariables; + + /** A set of types that have been visited. Used to prevent infinite recursion. */ + private final Set visitedTypes = new HashSet<>(); + + /** + * Creates the visitor. + * + * @param variables a collection of type variables that should be treated as inference variables + */ + Visitor(Collection variables) { + typeVariables = variables; + } + + /** + * Returns true if {@code typeVar} is a type variable in {@code typeVariables} + * + * @param typeVar a type variable + * @return true if {@code typeVar} is a type variable in {@code typeVariables} + */ + private boolean isTypeVariableOfInterest(AnnotatedTypeVariable typeVar) { + return typeVariables.contains(typeVar.getUnderlyingType()); + } + + @Override + public Boolean visit(AnnotatedTypeMirror t, Void aVoid) { + return t != null && t.accept(this, aVoid); + } + + @Override + public Boolean visit(AnnotatedTypeMirror t) { + return t != null && t.accept(this, null); + } + + @Override + public Boolean visitPrimitive(AnnotatedPrimitiveType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitNoType(AnnotatedNoType type, Void unused) { + return false; + } + + @Override + public Boolean visitNull(AnnotatedNullType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitArray(AnnotatedArrayType t, Void aVoid) { + return visit(t.getComponentType()); + } + + @Override + public Boolean visitDeclared(AnnotatedDeclaredType t, Void aVoid) { + boolean found = false; + for (AnnotatedTypeMirror typeArg : t.getTypeArguments()) { + if (visit(typeArg)) { + found = true; + } + } + return found; + } + + @Override + public Boolean visitTypeVariable(AnnotatedTypeVariable t, Void aVoid) { + if (visitedTypes.contains(t)) { + // t has visited before. If it contained an inference variable, + // then true would have been returned, so it must not contain an inference variable. + return false; + } + visitedTypes.add(t); + if (isTypeVariableOfInterest(t)) { + return true; + } + if (visit(t.getLowerBound())) { + return true; + } + return visit(t.getUpperBound()); + } + + @Override + public Boolean visitWildcard(AnnotatedWildcardType t, Void aVoid) { + if (visitedTypes.contains(t)) { + return false; + } + visitedTypes.add(t); + + if (visit(t.getSuperBound())) { + return true; + } + return visit(t.getExtendsBound()); + } + + @Override + public Boolean visitExecutable(AnnotatedExecutableType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitUnion(AnnotatedUnionType t, Void aVoid) { + for (AnnotatedTypeMirror altern : t.getAlternatives()) { + if (visit(altern)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitIntersection(AnnotatedIntersectionType t, Void aVoid) { + for (AnnotatedTypeMirror bound : t.getBounds()) { + if (visit(bound)) { + return true; + } + } + return false; + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java new file mode 100644 index 00000000000..04acd7ddc42 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java @@ -0,0 +1,56 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.source.tree.ExpressionTree; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; + +/** Variables created as a part of a capture bound. */ +@Interned public class CaptureVariable extends Variable { + + /** + * Creates a captured variable + * + * @param type the annotated type variable that is captured + * @param typeVariableJava the type variable that is captured + * @param invocation invocation expression for the variable + * @param context the context + * @param map a mapping from type variable to inference variable + */ + CaptureVariable( + AnnotatedTypeVariable type, + TypeVariable typeVariableJava, + ExpressionTree invocation, + Java8InferenceContext context, + Theta map) { + super(type, typeVariableJava, invocation, context, map, context.getNextCaptureVariableId()); + } + + @Override + public String toString() { + // Use "b" instead of "a" like super so it is apparent that this is a capture variable. + if (variableBounds.hasInstantiation()) { + return "b" + id + " := " + variableBounds.getInstantiation(); + } + return "b" + id; + } + + /** + * Returns the constraints generated when incorporating a capture bound. See JLS 18.3.2. + * + * @param Ai the captured type argument + * @param Bi the bound of the type variable + * @return constraints generated when incorporating a capture bound + */ + public ConstraintSet getWildcardConstraints(AbstractType Ai, AbstractType Bi) { + return variableBounds.getWildcardConstraints(Ai, Bi); + } + + @Override + public boolean isCaptureVariable() { + return true; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java new file mode 100644 index 00000000000..c171def5edb --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java @@ -0,0 +1,198 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.TypeVisitor; +import javax.lang.model.type.UnionType; +import javax.lang.model.type.WildcardType; +import org.checkerframework.javacutil.TypesUtils; + +/** Helper class for determining if a type contains an inference variable. */ +public class ContainsInferenceVariable { + + /** Don't use. */ + private ContainsInferenceVariable() {} + + /** + * Returns true if {@code type} contains any of the type variables in {@code typeVariables}. + * + * @param typeVariables a collection of type variables + * @param type a type to check + * @return true if {@code type} contains any of the type variables in {@code typeVariables} + */ + public static boolean hasAnyTypeVariable( + Collection typeVariables, TypeMirror type) { + return new Visitor(typeVariables).visit(type); + } + + /** + * Returns the type variables in {@code typeVariables} that appear in {@code type}. + * + * @param typeVariables a collection of type variables + * @param type a type to check + * @return the type variables in {@code typeVariables} that appear in {@code type} + */ + public static Set getMentionedTypeVariables( + Collection typeVariables, TypeMirror type) { + Visitor visitor = new Visitor(typeVariables); + visitor.visit(type); + return visitor.foundVariables; + } + + /** A helper class to find type variables mentioned by a type. */ + static class Visitor implements TypeVisitor { + + /** Type variables for which to search. */ + private final Collection typeVariables; + + /** Type variables in {@code typeVariables} that have been found. */ + // default visibility to allow direct access from getMentionedTypeVariables + final LinkedHashSet foundVariables = new LinkedHashSet<>(); + + /** A set of types that have been visited. Used to prevent infinite recursion. */ + private final Set visitedTypes = new HashSet<>(); + + /** + * Creates the visitor. + * + * @param variables a collection of type variables that should be treated as inference variables + */ + Visitor(Collection variables) { + typeVariables = variables; + } + + /** + * Returns true if {@code typeVar} is a type variable in {@code typeVariables}. + * + * @param typeVar a type variable + * @return true if {@code typeVar} is a type variable in {@code typeVariables} + */ + private boolean isTypeVariableOfInterest(TypeVariable typeVar) { + for (TypeVariable tv : typeVariables) { + if (TypesUtils.areSame(tv, typeVar)) { + foundVariables.add(tv); + return true; + } + } + return false; + } + + @Override + public Boolean visit(TypeMirror t, Void aVoid) { + return t != null && t.accept(this, aVoid); + } + + @Override + public Boolean visit(TypeMirror t) { + return t != null && t.accept(this, null); + } + + @Override + public Boolean visitPrimitive(PrimitiveType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitNull(NullType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitArray(ArrayType t, Void aVoid) { + return visit(t.getComponentType()); + } + + @Override + public Boolean visitDeclared(DeclaredType t, Void aVoid) { + boolean found = false; + for (TypeMirror typeArg : t.getTypeArguments()) { + if (visit(typeArg)) { + found = true; + } + } + return found; + } + + @Override + public Boolean visitError(ErrorType t, Void aVoid) { + return null; + } + + @Override + public Boolean visitTypeVariable(TypeVariable t, Void aVoid) { + if (visitedTypes.contains(t)) { + // t has visited before. If it contained an inference variable, + // then true would have been returned, so it must not contain an inference variable. + return false; + } + visitedTypes.add(t); + if (isTypeVariableOfInterest(t)) { + return true; + } + if (visit(t.getLowerBound())) { + return true; + } + return visit(t.getUpperBound()); + } + + @Override + public Boolean visitWildcard(WildcardType t, Void aVoid) { + if (visitedTypes.contains(t)) { + return false; + } + visitedTypes.add(t); + + if (visit(t.getSuperBound())) { + return true; + } + return visit(t.getExtendsBound()); + } + + @Override + public Boolean visitExecutable(ExecutableType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitNoType(NoType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitUnknown(TypeMirror t, Void aVoid) { + return false; + } + + @Override + public Boolean visitUnion(UnionType t, Void aVoid) { + for (TypeMirror altern : t.getAlternatives()) { + if (visit(altern)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitIntersection(IntersectionType t, Void aVoid) { + for (TypeMirror bound : t.getBounds()) { + if (visit(bound)) { + return true; + } + } + return false; + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java new file mode 100644 index 00000000000..8ab2b743777 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java @@ -0,0 +1,95 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A data structure to hold the dependencies between variables. Dependencies are defined in JLS section + * 18.4 and impact the order in which variables are resolved. + */ +public class Dependencies { + + /** Creates Dependencies. */ + public Dependencies() {} + + /** A map from a variable to the variables, including itself, on which it depends. */ + private final Map> map = new LinkedHashMap<>(); + + /** + * Add {@code value} as a dependency of {@code key}. + * + * @param key a key to add + * @param value a value to add + */ + public void putOrAdd(Variable key, Variable value) { + LinkedHashSet set = map.computeIfAbsent(key, k -> new LinkedHashSet<>()); + set.add(value); + } + + /** + * Add {@code values} as dependencies of {@code key}. + * + * @param key a key to add + * @param values values to add + */ + public void putOrAddAll(Variable key, Collection values) { + LinkedHashSet set = map.computeIfAbsent(key, k -> new LinkedHashSet<>()); + set.addAll(values); + } + + /** + * Calculate and add transitive dependencies. + * + *

          JLS 18.4 "An inference variable alpha depends on the resolution of an inference variable + * beta if there exists an inference variable gamma such that alpha depends on the resolution of + * gamma and gamma depends on the resolution of beta." + */ + public void calculateTransitiveDependencies() { + boolean changed = true; + while (changed) { + changed = false; + for (Map.Entry> entry : map.entrySet()) { + Variable alpha = entry.getKey(); + LinkedHashSet gammas = entry.getValue(); + LinkedHashSet betas = new LinkedHashSet<>(); + for (Variable gamma : gammas) { + if (gamma == alpha) { + continue; + } + betas.addAll(map.get(gamma)); + } + changed |= gammas.addAll(betas); + } + } + } + + /** + * Returns the set of dependencies of {@code alpha}. + * + * @param alpha a variable + * @return the set of dependencies of {@code alpha} + */ + public Set get(Variable alpha) { + return new LinkedHashSet<>(map.get(alpha)); + } + + /** + * Returns the set of dependencies for all variables in {@code variables}. + * + * @param variables list of variables + * @return the set of dependencies for all variables in {@code variables} + */ + public Set get(List variables) { + LinkedHashSet set = new LinkedHashSet<>(); + for (Variable v : variables) { + Set get = get(v); + set.addAll(get); + } + return set; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java new file mode 100644 index 00000000000..8ab07a901b6 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java @@ -0,0 +1,1166 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberReferenceTree.ReferenceMode; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.type.TypeVariableSubstitutor; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.TypeConstraint; +import org.checkerframework.framework.util.typeinference8.constraint.Typing; +import org.checkerframework.framework.util.typeinference8.util.CheckedExceptionsUtil; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SwitchExpressionScanner; +import org.checkerframework.javacutil.SwitchExpressionScanner.FunctionalSwitchExpressionScanner; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TreeUtils.MemberReferenceKind; +import org.checkerframework.javacutil.TypeAnnotationUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.IPair; + +/** Factory that creates AbstractTypes. */ +public class InferenceFactory { + + /** AnnotatedTypeFactory used to get annotated types. */ + private final AnnotatedTypeFactory typeFactory; + + /** Stores information about the current inference problem being solved. */ + private Java8InferenceContext context; + + /** + * Creates an inference factory + * + * @param context the context + */ + public InferenceFactory(Java8InferenceContext context) { + this.context = context; + this.typeFactory = context.typeFactory; + } + + /** + * Gets the target type for the expression for which type arguments are being inferred. + * + * @return target type for the expression for which type arguments are being inferred + */ + public @Nullable ProperType getTargetType() { + GenericAnnotatedTypeFactory factory = + (GenericAnnotatedTypeFactory) context.typeFactory; + TreePath path = context.pathToExpression; + Tree context = TreePathUtil.getContextForPolyExpression(path); + if (context == null) { + AnnotatedTypeMirror dummy = factory.getDummyAssignedTo((ExpressionTree) path.getLeaf()); + if (dummy == null || dummy.containsCapturedTypes()) { + return null; + } + return new ProperType(dummy, dummy.getUnderlyingType(), this.context); + } + + switch (context.getKind()) { + case ASSIGNMENT: + ExpressionTree variable = ((AssignmentTree) context).getVariable(); + AnnotatedTypeMirror atm = factory.getAnnotatedTypeLhs(variable); + return new ProperType(atm, TreeUtils.typeOf(variable), this.context); + case TYPE_CAST: + Tree cast = ((TypeCastTree) context).getType(); + AnnotatedTypeMirror castType = factory.getAnnotatedTypeFromTypeTree(cast); + return new ProperType(castType, TreeUtils.typeOf(cast), this.context); + case VARIABLE: + VariableTree variableTree = (VariableTree) context; + AnnotatedTypeMirror variableAtm = assignedToVariable(factory, context); + return new ProperType(variableAtm, TreeUtils.typeOf(variableTree.getType()), this.context); + case METHOD_INVOCATION: + MethodInvocationTree methodInvocation = (MethodInvocationTree) context; + + AnnotatedExecutableType methodType = + factory.methodFromUseWithoutTypeArgInference(methodInvocation).executableType; + + AnnotatedTypeMirror paramType = + assignedToExecutable( + path, methodInvocation, methodInvocation.getArguments(), methodType); + return new ProperType( + paramType, + assignedToExecutable( + path, methodInvocation, methodInvocation.getArguments(), this.context), + this.context); + case NEW_CLASS: + NewClassTree newClassTree = (NewClassTree) context; + AnnotatedExecutableType constructorType = + factory.constructorFromUseWithoutTypeArgInference(newClassTree).executableType; + AnnotatedTypeMirror constATM = + assignedToExecutable(path, newClassTree, newClassTree.getArguments(), constructorType); + return new ProperType( + constATM, + assignedToExecutable(path, newClassTree, newClassTree.getArguments(), this.context), + this.context); + case NEW_ARRAY: + NewArrayTree newArrayTree = (NewArrayTree) context; + ArrayType arrayType = (ArrayType) TreeUtils.typeOf(newArrayTree); + AnnotatedArrayType type = factory.getAnnotatedType((NewArrayTree) context); + AnnotatedTypeMirror component = type.getComponentType(); + return new ProperType(component, arrayType.getComponentType(), this.context); + case LAMBDA_EXPRESSION: + { + LambdaExpressionTree lambdaTree = (LambdaExpressionTree) context; + AnnotatedExecutableType fninf = factory.getFunctionTypeFromTree(lambdaTree); + AnnotatedTypeMirror res = fninf.getReturnType(); + if (res.getKind() == TypeKind.VOID) { + return null; + } + return new ProperType(res, res.getUnderlyingType(), this.context); + } + case RETURN: + HashSet kinds = + new HashSet<>(Arrays.asList(Tree.Kind.LAMBDA_EXPRESSION, Tree.Kind.METHOD)); + Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds); + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) enclosing; + AnnotatedTypeMirror res = factory.getMethodReturnType(methodTree); + return new ProperType(res, TreeUtils.typeOf(methodTree.getReturnType()), this.context); + } else { + LambdaExpressionTree lambdaTree = (LambdaExpressionTree) enclosing; + AnnotatedExecutableType fninf = factory.getFunctionTypeFromTree(lambdaTree); + AnnotatedTypeMirror res = fninf.getReturnType(); + return new ProperType(res, res.getUnderlyingType(), this.context); + } + default: + if (context.getKind().asInterface() == CompoundAssignmentTree.class) { + // 11 Tree kinds are compound assignments, so don't use it in the switch + ExpressionTree var = ((CompoundAssignmentTree) context).getVariable(); + AnnotatedTypeMirror res = factory.getAnnotatedTypeLhs(var); + + return new ProperType(res, TreeUtils.typeOf(var), this.context); + } else { + throw new BugInCF( + "Unexpected assignment context.\nKind: %s\nTree: %s", context.getKind(), context); + } + } + } + + /** + * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree). + * Rational: + * + *

          For example: + * + *

          {@code
          +   *  S bar () {...}
          +   *
          +   *  T foo(T p) {
          +   *     T local = bar();
          +   *     return local;
          +   *   }
          +   * }
          + * + * During type argument inference of {@code bar}, the assignment context is {@code local}. If the + * local variable default is used, then the type of assignment context type is {@code @Nullable T} + * and the type argument inferred for {@code bar()} is {@code @Nullable T}. And an incompatible + * types in return error is issued. + * + *

          If instead, the local variable default is not applied, then the assignment context type is + * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) and + * the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of {@code + * local} is refined to {@code T} and the return is legal. + * + *

          If the assignment context type was a declared type, for example: + * + *

          {@code
          +   *  S bar () {...}
          +   * Object foo() {
          +   *     Object local = bar();
          +   *     return local;
          +   * }
          +   * }
          + * + * The local variable default must be used or else the assignment context type is missing an + * annotation. So, an incompatible types in return error is issued in the above code. We could + * improve type argument inference in this case and by using the lower bound of {@code S} instead + * of the local variable default. + * + * @param atypeFactory AnnotatedTypeFactory + * @param assignmentContext VariableTree + * @return AnnotatedTypeMirror of Assignment context + */ + public static AnnotatedTypeMirror assignedToVariable( + AnnotatedTypeFactory atypeFactory, Tree assignmentContext) { + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + final GenericAnnotatedTypeFactory gatf = + ((GenericAnnotatedTypeFactory) atypeFactory); + return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext); + } else { + return atypeFactory.getAnnotatedType(assignmentContext); + } + } + + /** + * Return the rhs of the assignment of an argument and its formal parameter. + * + * @param path path to the argument + * @param invocation a method or constructor invocation + * @param arguments the argument expression tress + * @param context the context + * @return the rhs of the assignment of an argument and its formal parameter + */ + private static TypeMirror assignedToExecutable( + TreePath path, + ExpressionTree invocation, + List arguments, + Java8InferenceContext context) { + int treeIndex = -1; + for (int i = 0; i < arguments.size(); ++i) { + ExpressionTree argumentTree = arguments.get(i); + if (isArgument(path, argumentTree)) { + treeIndex = i; + break; + } + } + + ExecutableType methodType = getTypeOfMethodAdaptedToUse(invocation, context); + if (treeIndex >= methodType.getParameterTypes().size() - 1 + && TreeUtils.isVarArgMethodCall(invocation)) { + treeIndex = methodType.getParameterTypes().size() - 1; + TypeMirror typeMirror = methodType.getParameterTypes().get(treeIndex); + return ((ArrayType) typeMirror).getComponentType(); + } + + return methodType.getParameterTypes().get(treeIndex); + } + + /** + * Return the rhs of the assignment of an argument and its formal parameter. + * + * @param path path to the argument + * @param invocation a method or constructor invocation + * @param arguments the argument expression tress + * @param methodType the type of the method or constructor + * @return the rhs of the assignment of an argument and its formal parameter + */ + private static AnnotatedTypeMirror assignedToExecutable( + TreePath path, + ExpressionTree invocation, + List arguments, + AnnotatedExecutableType methodType) { + int treeIndex = -1; + for (int i = 0; i < arguments.size(); ++i) { + ExpressionTree argumentTree = arguments.get(i); + if (isArgument(path, argumentTree)) { + treeIndex = i; + break; + } + } + + if (treeIndex >= methodType.getParameterTypes().size() - 1 + && TreeUtils.isVarArgMethodCall(invocation)) { + treeIndex = methodType.getParameterTypes().size() - 1; + AnnotatedTypeMirror typeMirror = methodType.getParameterTypes().get(treeIndex); + return ((AnnotatedArrayType) typeMirror).getComponentType(); + } + + return methodType.getParameterTypes().get(treeIndex); + } + + /** + * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional + * expression, isArgument is called recursively on the true and false expressions. If tree is a + * switch expression isArgument is called recursively on all yielded expressions. + * + * @param path tree path might contain {@code argumentTree} + * @param argumentTree an expression tree that might be in {@code path} + * @return whether argumentTree is the tree at the leaf of path + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private static boolean isArgument(TreePath path, ExpressionTree argumentTree) { + argumentTree = TreeUtils.withoutParens(argumentTree); + if (argumentTree == path.getLeaf()) { + return true; + } else if (argumentTree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { + ConditionalExpressionTree conditionalExpressionTree = + (ConditionalExpressionTree) argumentTree; + return isArgument(path, conditionalExpressionTree.getTrueExpression()) + || isArgument(path, conditionalExpressionTree.getFalseExpression()); + } else if (TreeUtils.isSwitchExpression(argumentTree)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (tree, unused) -> isArgument(path, tree), + (r1, r2) -> (r1 != null && r1) || (r2 != null && r2)); + return scanner.scanSwitchExpression(argumentTree, null); + } + return false; + } + + /** + * Returns the type of the receiver of {@code tree} or null if {@code tree} does not have a + * receiver. + * + * @param tree an expression tree + * @return the type of the receiver of {@code tree} or null if {@code tree} does not have a + * receiver + */ + private static @Nullable DeclaredType getReceiverType(ExpressionTree tree) { + Tree receiverTree; + if (tree.getKind() == Tree.Kind.NEW_CLASS) { + receiverTree = ((NewClassTree) tree).getEnclosingExpression(); + if (receiverTree == null && ((NewClassTree) tree).getClassBody() == null) { + TypeMirror t = TreeUtils.elementFromUse((NewClassTree) tree).getReceiverType(); + if (t instanceof DeclaredType) { + return (DeclaredType) t; + } + return null; + } + } else { + receiverTree = TreeUtils.getReceiverTree(tree); + } + + if (receiverTree == null) { + return null; + } + TypeMirror type = TreeUtils.typeOf(receiverTree); + if (type.getKind() == TypeKind.TYPEVAR) { + return (DeclaredType) ((TypeVariable) type).getUpperBound(); + } + return type.getKind() == TypeKind.DECLARED ? (DeclaredType) type : null; + } + + /** + * Return ExecutableType of the method invocation or new class tree adapted to the call site. + * + * @param expressionTree a method invocation or new class tree + * @param context the context + * @return ExecutableType of the method invocation or new class tree adapted to the call site + */ + public static ExecutableType getTypeOfMethodAdaptedToUse( + ExpressionTree expressionTree, Java8InferenceContext context) { + assert expressionTree.getKind() == Kind.NEW_CLASS + || expressionTree.getKind() == Kind.METHOD_INVOCATION; + + ExecutableElement ele = (ExecutableElement) TreeUtils.elementFromUse(expressionTree); + ExecutableType executableType = null; + // First adapt to receiver + if (!ElementUtils.isStatic(ele)) { + DeclaredType receiverType = getReceiverType(expressionTree); + if (receiverType == null && expressionTree.getKind() == Kind.METHOD_INVOCATION) { + receiverType = context.enclosingType; + } else if (receiverType != null) { + receiverType = (DeclaredType) context.types.capture((Type) receiverType); + } + + while (receiverType != null + && context.types.asSuper((Type) receiverType, (Symbol) ele.getEnclosingElement()) + == null) { + TypeMirror enclosing = receiverType.getEnclosingType(); + if (enclosing == null || enclosing.getKind() != TypeKind.DECLARED) { + if (expressionTree.getKind() == Tree.Kind.NEW_CLASS) { + // No receiver for the constructor. + executableType = (ExecutableType) ele.asType(); + } else { + throw new BugInCF("Method not found"); + } + } + receiverType = (DeclaredType) enclosing; + } + if (receiverType == null) { + executableType = (ExecutableType) ele.asType(); + } + if (executableType == null) { + javax.lang.model.util.Types types = context.env.getTypeUtils(); + executableType = (ExecutableType) types.asMemberOf(receiverType, ele); + } + } else { + executableType = (ExecutableType) TreeUtils.elementFromUse(expressionTree).asType(); + } + + // Adapt to class type arguments. + if (expressionTree.getKind() == Tree.Kind.NEW_CLASS + && !TreeUtils.isDiamondTree(expressionTree)) { + NewClassTree newClassTree = (NewClassTree) expressionTree; + List typeArgs = TreeUtils.getTypeArgumentsToNewClassTree(newClassTree); + if (!typeArgs.isEmpty()) { + ExecutableElement e = TreeUtils.elementFromUse(newClassTree); + List typeParams = + ElementUtils.enclosingTypeElement(e).getTypeParameters(); + List typeVariables = new ArrayList<>(); + for (TypeParameterElement typeParam : typeParams) { + typeVariables.add((TypeVariable) typeParam.asType()); + } + List args = new ArrayList<>(); + for (Tree arg : typeArgs) { + args.add(TreeUtils.typeOf(arg)); + } + executableType = + (ExecutableType) + TypesUtils.substitute(executableType, typeVariables, args, context.env); + } else if (TypesUtils.isRaw(TreeUtils.typeOf(newClassTree))) { + executableType = (ExecutableType) context.types.erasure((Type) executableType); + } + } + // Adapt to explicit method type arguments. + List typeArgs; + if (expressionTree.getKind() == Kind.METHOD_INVOCATION) { + typeArgs = ((MethodInvocationTree) expressionTree).getTypeArguments(); + } else { + typeArgs = ((NewClassTree) expressionTree).getTypeArguments(); + } + if (typeArgs.isEmpty()) { + return executableType; + } + List typeParams = ele.getTypeParameters(); + List typeVariables = new ArrayList<>(); + for (TypeParameterElement typeParam : typeParams) { + typeVariables.add((TypeVariable) typeParam.asType()); + } + + List args = new ArrayList<>(); + for (Tree arg : typeArgs) { + args.add(TreeUtils.typeOf(arg)); + } + + return (ExecutableType) TypesUtils.substitute(executableType, typeVariables, args, context.env); + } + + /** + * Returns the least upper bound of {@code tm1} and {@code tm2}. + * + * @param processingEnv the processing environment + * @param tm1 a type + * @param tm2 a type + * @return the least upper bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror lub( + ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + Types types = Types.instance(javacEnv.getContext()); + + return types.lub(t1, t2); + } + + /** + * Returns the greatest lower bound of {@code tm1} and {@code tm2}. + * + * @param processingEnv the processing environment + * @param tm1 a type + * @param tm2 a type + * @return the greatest lower bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror glb( + ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + Types types = Types.instance(javacEnv.getContext()); + + return types.glb(t1, t2); + } + + /** + * If a mapping, theta, for {@code invocation} doesn't exist create it by: + * + *

          Creates inference variables for the type parameters to {@code methodType} for a particular + * {@code invocation}. Initializes the bounds of the variables. Returns a mapping from type + * variables to newly created variables. + * + *

          Otherwise, returns the previously created mapping. + * + * @param invocation method or constructor invocation + * @param methodType type of generic method + * @param context Java8InferenceContext + * @return a mapping of the type variables of {@code methodType} to inference variables + */ + public Theta createThetaForInvocation( + ExpressionTree invocation, InvocationType methodType, Java8InferenceContext context) { + if (context.maps.containsKey(invocation)) { + return context.maps.get(invocation); + } + Theta map = new Theta(); + + // Create inference variables for the type parameters to methodType + + for (AnnotatedTypeVariable pl : methodType.getAnnotatedTypeVariables()) { + @SuppressWarnings("interning:interned.object.creation") + Variable al = new @Interned Variable(pl, pl.getUnderlyingType(), invocation, context, map); + map.put(pl.getUnderlyingType(), al); + } + if (TreeUtils.isDiamondTree(invocation)) { + // If the invocation is a diamondTree, such as new List<>(...), then create variables + // for the class type parameters, too. + Element classEle = + ElementUtils.enclosingTypeElement(TreeUtils.elementFromUse((NewClassTree) invocation)); + if (classEle.getSimpleName().contentEquals("")) { + classEle = + ((DeclaredType) TreeUtils.typeOf(((NewClassTree) invocation).getIdentifier())) + .asElement(); + } + DeclaredType classTypeMirror = (DeclaredType) classEle.asType(); + + AnnotatedDeclaredType classType = + (AnnotatedDeclaredType) typeFactory.getAnnotatedType(classEle); + + Iterator iter = classType.getTypeArguments().iterator(); + + for (TypeMirror typeMirror : classTypeMirror.getTypeArguments()) { + if (typeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Expected type variable, found: %s", typeMirror); + } + TypeVariable pl = (TypeVariable) typeMirror; + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + Variable al = new @Interned Variable(atv, pl, invocation, context, map); + map.put(pl, al); + } + } + + // Initialize variable bounds. + for (Variable v : map.values()) { + v.initialBounds(map); + } + context.maps.put(invocation, map); + return map; + } + + /** + * If a mapping, theta, for {@code memRef} doesn't exist create it by: + * + *

          Creates inference variables for the type parameters to {@code compileTimeDecl} for a + * particular method reference. Initializes the bounds of the variables. Returns a mapping from + * type variables to newly created variables. + * + *

          Otherwise, returns the previously created mapping. + * + * @param memRef method reference tree + * @param compileTimeDecl type of generic method + * @param context Java8InferenceContext + * @return a mapping of the type variables of {@code compileTimeDecl} to inference variables + */ + public Theta createThetaForMethodReference( + MemberReferenceTree memRef, InvocationType compileTimeDecl, Java8InferenceContext context) { + if (context.maps.containsKey(memRef)) { + return context.maps.get(memRef); + } + + Theta map = new Theta(); + TypeMirror preColonTreeType = TreeUtils.typeOf(memRef.getQualifierExpression()); + if (TreeUtils.isDiamondMemberReference(memRef) + || TreeUtils.isLikeDiamondMemberReference(memRef)) { + // If memRef is a constructor or method of a generic class whose type argument isn't specified + // such as HashSet::new or HashSet::put + // then add variables for the type arguments to the class. + TypeElement classEle = (TypeElement) ((Type) preColonTreeType).asElement(); + DeclaredType classTypeMirror = (DeclaredType) classEle.asType(); + + AnnotatedDeclaredType classType = + (AnnotatedDeclaredType) typeFactory.getAnnotatedType(classTypeMirror.asElement()); + + if (((Type) preColonTreeType).getTypeArguments().isEmpty()) { + Iterator iter = classType.getTypeArguments().iterator(); + for (TypeMirror typeMirror : classTypeMirror.getTypeArguments()) { + if (typeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Expected type variable, found: %s", typeMirror); + } + TypeVariable pl = (TypeVariable) typeMirror; + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + Variable al = new @Interned Variable(atv, pl, memRef, context, map); + map.put(pl, al); + } + } + } + + // Create inference variables for the type parameters to compileTypeDecl + if (memRef.getTypeArguments() == null && compileTimeDecl.hasTypeVariables()) { + Iterator iter1 = + compileTimeDecl.getAnnotatedTypeVariables().iterator(); + for (TypeVariable pl : compileTimeDecl.getTypeVariables()) { + @SuppressWarnings("interning:interned.object.creation") + Variable al = new @Interned Variable(iter1.next(), pl, memRef, context, map); + map.put(pl, al); + } + } + for (Variable v : map.values()) { + v.initialBounds(map); + } + context.maps.put(memRef, map); + return map; + } + + /** + * If a mapping, theta, for {@code lambda} doesn't exist create it by: + * + *

          Creates inference variables for the type parameters to the functional inference of the + * lambda. Initializes the bounds of the variables. Returns a mapping from type variables to newly + * created variables. + * + *

          Otherwise, returns the previously created mapping. + * + * @param lambda lambda expression tree + * @param functionalInterface functional interface of the lambda + * @return a mapping of the type variables of {@code compileTimeDecl} to inference variables + */ + public Theta createThetaForLambda(LambdaExpressionTree lambda, AbstractType functionalInterface) { + if (context.maps.containsKey(lambda)) { + return context.maps.get(lambda); + } + TypeElement typeEle = + (TypeElement) ((DeclaredType) functionalInterface.getJavaType()).asElement(); + AnnotatedDeclaredType classType = typeFactory.getAnnotatedType(typeEle); + + Iterator iter = classType.getTypeArguments().iterator(); + Theta map = new Theta(); + for (TypeParameterElement param : typeEle.getTypeParameters()) { + TypeVariable typeVar = (TypeVariable) param.asType(); + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + Variable ai = new @Interned Variable(atv, typeVar, lambda, context, map); + map.put(typeVar, ai); + } + for (Variable v : map.values()) { + v.initialBounds(map); + } + context.maps.put(lambda, map); + return map; + } + + /** + * Creates capture variables for variables introduced by a capture bounds. The new variables + * correspond to the type parameters of {@code capturedType}. + * + * @param tree invocation tree that created the capture bound + * @param capturedType type that should be captured + * @return a mapping of the type variables of {@code capturedType} to capture inference variables + */ + public Theta createThetaForCapture(ExpressionTree tree, AbstractType capturedType) { + // Don't save this theta, because there is also a noncapture theta for this tree. + DeclaredType underlying = (DeclaredType) capturedType.getJavaType(); + TypeElement ele = TypesUtils.getTypeElement(underlying); + AnnotatedDeclaredType classType = typeFactory.getAnnotatedType(ele); + Iterator iter = classType.getTypeArguments().iterator(); + Theta map = new Theta(); + for (TypeParameterElement pEle : ele.getTypeParameters()) { + TypeVariable pl = (TypeVariable) pEle.asType(); + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + CaptureVariable al = new @Interned CaptureVariable(atv, pl, tree, context, map); + map.put(pl, al); + } + for (Variable v : map.values()) { + v.initialBounds(map); + } + return map; + } + + /** + * Returns the type of the method or constructor invocation adapted to its arguments. This type + * may include inference variables. + * + * @param invocation method or constructor invocation + * @return the type of the method or constructor invocation adapted to its arguments + */ + public InvocationType getTypeOfMethodAdaptedToUse(ExpressionTree invocation) { + AnnotatedExecutableType executableType; + if (invocation.getKind() == Kind.METHOD_INVOCATION) { + executableType = + typeFactory.methodFromUseWithoutTypeArgInference((MethodInvocationTree) invocation) + .executableType; + } else { + executableType = + typeFactory.constructorFromUseWithoutTypeArgInference((NewClassTree) invocation) + .executableType; + } + return new InvocationType( + executableType, getTypeOfMethodAdaptedToUse(invocation, context), invocation, context); + } + + /** + * Returns the compile-time declaration of the method reference that is the method to which the + * expression refers. See JLS section + * 15.13.1 for a complete definition. + * + *

          The type of a member reference is a functional interface. The function type of a member + * reference is the type of the single abstract method declared by the functional interface. The + * compile-time declaration type is the type of the actual method referenced by the method + * reference, i.e. the method that is actually being referenced. + * + *

          For example, + * + *

          {@code
          +   * static class MyClass {
          +   *   String field;
          +   *   public static int compareByField(MyClass a, MyClass b) { ... }
          +   * }
          +   * Comparator func = MyClass::compareByField;
          +   * }
          + * + *

          The function type is {@code compare(Comparator this, MyClass o1, MyClass o2)} where + * as the compile-time declaration type is {@code compareByField(MyClass a, MyClass b)}. + * + * @param memRef method reference tree + * @return the compile-time declaration of the method reference + */ + public InvocationType compileTimeDeclarationType(MemberReferenceTree memRef) { + // The tree before :: is an expression or type use. + final ExpressionTree preColonTree = memRef.getQualifierExpression(); + final MemberReferenceKind memRefKind = MemberReferenceKind.getMemberReferenceKind(memRef); + AnnotatedTypeMirror enclosingType; + + if (memRef.getMode() == ReferenceMode.NEW) { + enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + if (TreeUtils.isDiamondMemberReference(memRef)) { + // The member reference is HashMap::new so the type arguments for HashMap must be + // inferred. + // So use the type declared type. + TypeElement typeEle = TypesUtils.getTypeElement(enclosingType.getUnderlyingType()); + enclosingType = typeFactory.getAnnotatedType(typeEle); + } + } else if (memRefKind == MemberReferenceKind.UNBOUND) { + enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + if (enclosingType.getKind() == TypeKind.DECLARED + && ((AnnotatedDeclaredType) enclosingType).isUnderlyingTypeRaw()) { + TypeElement typeEle = TypesUtils.getTypeElement(enclosingType.getUnderlyingType()); + enclosingType = typeFactory.getAnnotatedType(typeEle); + } + } else if (memRefKind == MemberReferenceKind.STATIC) { + // The tree before :: is a type tree. + enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + } else { // memRefKind == MemberReferenceKind.BOUND + // The tree before :: is an expression. + enclosingType = typeFactory.getAnnotatedType(preColonTree); + } + + // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference + ExecutableElement compileTimeDeclaration = + (ExecutableElement) TreeUtils.elementFromTree(memRef); + + if (enclosingType.getKind() == TypeKind.DECLARED) { + enclosingType = AbstractType.makeGround((AnnotatedDeclaredType) enclosingType, typeFactory); + } + // The type of the compileTimeDeclaration if it were invoked with a receiver expression + // of type {@code type} + AnnotatedExecutableType compileTimeType = + typeFactory.methodFromUseWithoutTypeArgInference( + memRef, compileTimeDeclaration, enclosingType) + .executableType; + + return new InvocationType( + compileTimeType, compileTimeType.getUnderlyingType(), memRef, context); + } + + /** + * Returns the pair of {@code a} as the least upper bound of {@code a} and {@code b} and {@code b} + * as the least upper bound of {@code a} and {@code b}. + * + * @param a type + * @param b type + * @return the pair of {@code a} as the least upper bound of {@code a} and {@code b} and * {@code + * b} as the least upper bound of {@code a} and {@code b} + */ + public IPair getParameterizedSupers(AbstractType a, AbstractType b) { + TypeMirror aTypeMirror = a.getJavaType(); + TypeMirror bTypeMirror = b.getJavaType(); + // com.sun.tools.javac.comp.Infer#getParameterizedSupers + TypeMirror lubResult = lub(context.env, aTypeMirror, bTypeMirror); + if (!TypesUtils.isParameterizedType(lubResult) || lubResult.getKind() == TypeKind.ARRAY) { + return null; + } + + Type asSuperOfA = context.types.asSuper((Type) aTypeMirror, ((Type) lubResult).asElement()); + Type asSuperOfB = context.types.asSuper((Type) bTypeMirror, ((Type) lubResult).asElement()); + + return IPair.of(a.asSuper(asSuperOfA), b.asSuper(asSuperOfB)); + } + + /** + * Returns the type of {@code element} using the type variable to inference variable mapping, + * {@code map}. + * + * @param element some element + * @param map type parameter to inference variables to use + * @return the type of {@code element} + */ + public AbstractType getTypeOfElement(Element element, Theta map) { + AnnotatedTypeMirror atm = typeFactory.getAnnotatedType(element).asUse(); + return InferenceType.create(atm, element.asType(), map, context); + } + + /** + * Returns the type of {@code pEle} using the type variable to inference variable mapping, {@code + * map}. + * + * @param pEle some element + * @param map type parameter to inference variables to use + * @return the type of {@code pEle} + */ + public AbstractType getTypeOfBound(TypeParameterElement pEle, Theta map) { + AnnotatedTypeVariable atm = (AnnotatedTypeVariable) typeFactory.getAnnotatedType(pEle); + return InferenceType.create( + atm.getUpperBound(), ((TypeVariable) pEle.asType()).getUpperBound(), map, context); + } + + /** + * Return the proper type for object. + * + * @return the proper type for object + */ + public ProperType getObject() { + TypeMirror objectTypeMirror = + TypesUtils.typeFromClass(Object.class, context.modelTypes, context.env.getElementUtils()); + AnnotatedTypeMirror object = + AnnotatedTypeMirror.createType(objectTypeMirror, typeFactory, false); + object.addMissingAnnotations(typeFactory.getQualifierHierarchy().getTopAnnotations()); + return new ProperType(object, objectTypeMirror, context); + } + + /** + * Return the least upper bounds of {@code properTypes}. + * + * @param properTypes types to lub + * @return the least upper bounds of {@code properTypes} + */ + public ProperType lub(Set properTypes) { + if (properTypes.isEmpty()) { + return null; + } + TypeMirror tiTypeMirror = null; + AnnotatedTypeMirror ti = null; + for (ProperType liProperType : properTypes) { + AnnotatedTypeMirror li = liProperType.getAnnotatedType(); + TypeMirror liTypeMirror = liProperType.getJavaType(); + if (ti == null) { + ti = li; + tiTypeMirror = liTypeMirror; + } else { + tiTypeMirror = lub(context.env, tiTypeMirror, liTypeMirror); + ti = AnnotatedTypes.leastUpperBound(typeFactory, ti, li, tiTypeMirror); + } + } + return new ProperType(ti, tiTypeMirror, context); + } + + /** + * Returns the greatest lower bound of {@code abstractTypes}. + * + * @param abstractTypes types to glb + * @return the greatest upper bounds of {@code abstractTypes} + */ + public AbstractType glb(Set abstractTypes) { + AbstractType ti = null; + for (AbstractType liProperType : abstractTypes) { + AbstractType li = liProperType; + if (ti == null) { + ti = li; + } else { + ti = glb(ti, li); + } + } + return ti; + } + + /** + * Returns the greatest lower bound of {@code a} and {@code b}. + * + * @param a type to glb + * @param b type to glb + * @return the greatest lower bound of {@code a} and {@code b} + */ + public AbstractType glb(AbstractType a, AbstractType b) { + Type aJavaType = (Type) a.getJavaType(); + Type bJavaType = (Type) b.getJavaType(); + TypeMirror glb = TypesUtils.greatestLowerBound(aJavaType, bJavaType, context.env); + + AnnotatedTypeMirror aAtm = a.getAnnotatedType(); + AnnotatedTypeMirror bAtm = b.getAnnotatedType(); + AnnotatedTypeMirror glbATM = AnnotatedTypes.annotatedGLB(typeFactory, aAtm, bAtm); + if (context.types.isSameType(aJavaType, (Type) glb)) { + return a.create(glbATM, glb); + } + + if (context.types.isSameType(bJavaType, (Type) glb)) { + return b.create(glbATM, glb); + } + + if (a.isInferenceType()) { + return a.create(glbATM, glb); + } else if (b.isInferenceType()) { + return b.create(glbATM, glb); + } + + assert a.isProper() && b.isProper(); + return new ProperType(glbATM, glb, context); + } + + /** + * Return the proper type for RuntimeException. + * + * @return the proper type for RuntimeException + */ + public ProperType getRuntimeException() { + AnnotatedTypeMirror runtimeEx = + AnnotatedTypeMirror.createType(context.runtimeEx, typeFactory, false); + runtimeEx.addMissingAnnotations(typeFactory.getQualifierHierarchy().getTopAnnotations()); + return new ProperType(runtimeEx, context.runtimeEx, context); + } + + /** + * Creates and returns the set of checked exception constraints for the given lambda or method + * reference. + * + * @param expression a lambda or method reference expression + * @param targetType the target type of {@code expression} + * @param map theta + * @return the set of checked exception constraints for the given lambda or method reference + */ + public ConstraintSet getCheckedExceptionConstraints( + ExpressionTree expression, AbstractType targetType, Theta map) { + ConstraintSet constraintSet = new ConstraintSet(); + ExecutableElement ele = TreeUtils.findFunction(expression, context.env); + // The types in the function type's throws clause that are not proper types. + List es = new ArrayList<>(); + List properTypes = new ArrayList<>(); + + AnnotatedExecutableType functionType = + AnnotatedTypes.asMemberOf( + context.modelTypes, context.typeFactory, targetType.getAnnotatedType(), ele); + Iterator iter = functionType.getThrownTypes().iterator(); + for (TypeMirror thrownType : ele.getThrownTypes()) { + AbstractType ei = InferenceType.create(iter.next(), thrownType, map, context); + if (ei.isProper()) { + properTypes.add((ProperType) ei); + } else { + es.add((UseOfVariable) ei); + } + } + if (es.isEmpty()) { + return ConstraintSet.TRUE; + } + List thrownTypes; + List thrownTypeMirrors; + if (expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + thrownTypeMirrors = + CheckedExceptionsUtil.thrownCheckedExceptions((LambdaExpressionTree) expression, context); + thrownTypes = + CheckedExceptionsUtil.thrownCheckedExceptionsATM( + (LambdaExpressionTree) expression, context); + } else { + thrownTypeMirrors = + TypesUtils.findFunctionType(TreeUtils.typeOf(expression), context.env).getThrownTypes(); + thrownTypes = + compileTimeDeclarationType((MemberReferenceTree) expression) + .getAnnotatedType() + .getThrownTypes(); + } + + Iterator iter2 = thrownTypes.iterator(); + for (TypeMirror xi : thrownTypeMirrors) { + boolean isSubtypeOfProper = false; + for (ProperType properType : properTypes) { + if (context.env.getTypeUtils().isSubtype(xi, properType.getJavaType())) { + isSubtypeOfProper = true; + } + } + if (!isSubtypeOfProper) { + for (UseOfVariable ei : es) { + constraintSet.add( + new Typing( + new ProperType(iter2.next(), xi, context), ei, TypeConstraint.Kind.SUBTYPE)); + ei.setHasThrowsBound(true); + } + } + } + + return constraintSet; + } + + /** + * Creates a wildcard using the upper and lower bounds provided. + * + * @param lowerBound a proper type or null + * @param upperBound an abstract type or null + * @return a wildcard with the provided upper and lower bounds + */ + public ProperType createWildcard(ProperType lowerBound, AbstractType upperBound) { + TypeMirror wildcard = + TypesUtils.createWildcard( + lowerBound == null ? null : lowerBound.getJavaType(), + upperBound == null ? null : upperBound.getJavaType(), + context.env.getTypeUtils()); + AnnotatedWildcardType wildcardAtm = + (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wildcard, typeFactory, false); + if (lowerBound != null) { + wildcardAtm.setSuperBound(lowerBound.getAnnotatedType()); + } + if (upperBound != null) { + wildcardAtm.setExtendsBound(upperBound.getAnnotatedType()); + } + return new ProperType(wildcardAtm, wildcard, context); + } + + /** + * Creates a fresh type variable using the upper and lower bounds provided. + * + * @param lowerBound a proper type or null + * @param lowerBoundAnnos annotations to use if {@code lowerBound} is null + * @param upperBound an abstract type or null + * @param upperBoundAnnos annotations to use if {@code upperBound} is null + * @return a fresh type variable with the provided upper and lower bounds + */ + public AbstractType createFreshTypeVariable( + ProperType lowerBound, + Set lowerBoundAnnos, + AbstractType upperBound, + Set upperBoundAnnos) { + TypeMirror freshTypeVariable = + TypesUtils.freshTypeVariable( + upperBound == null ? null : upperBound.getJavaType(), + lowerBound == null ? null : lowerBound.getJavaType(), + context.env); + AnnotatedTypeVariable typeVariable = + (AnnotatedTypeVariable) + AnnotatedTypeMirror.createType(freshTypeVariable, typeFactory, false); + // Initialize bounds. + typeVariable.getUpperBound(); + typeVariable.getLowerBound(); + if (lowerBound != null) { + typeVariable.setLowerBound(lowerBound.getAnnotatedType()); + } else { + typeVariable.getLowerBound().addAnnotations(lowerBoundAnnos); + } + if (upperBound != null) { + typeVariable.setUpperBound(upperBound.getAnnotatedType()); + } else { + typeVariable.getUpperBound().addAnnotations(upperBoundAnnos); + } + context.typeFactory.capturedTypeVarSubstitutor.substitute( + typeVariable, Collections.singletonMap(typeVariable.getUnderlyingType(), typeVariable)); + return upperBound.create(typeVariable, freshTypeVariable); + } + + /** + * Returns the result of substituting {@code typeArg} for {@code typeVar} in {@code types}. + * + * @param typeVar type variables + * @param typeArg type arguments + * @param types types + * @return the result of substituting {@code typeArg} for {@code typeVar} in {@code types} + */ + public List getSubsTypeArgs( + List typeVar, List typeArg, List types) { + List javaTypeArgs = new ArrayList<>(); + // Recursive types: + for (int i = 0; i < typeArg.size(); i++) { + Variable ai = types.get(i); + TypeMirror inst = typeArg.get(i).getJavaType(); + TypeVariable typeVariableI = ai.getJavaType(); + if (ContainsInferenceVariable.hasAnyTypeVariable( + Collections.singleton(typeVariableI), inst)) { + // If the instantiation of ai includes a reference to ai, + // then substitute ai with an unbound wildcard. This isn't quite right but I'm not + // sure how to make recursive types Java types. + // TODO: This causes problems when incorporating the bounds. + TypeMirror unbound = context.env.getTypeUtils().getWildcardType(null, null); + inst = + TypesUtils.substitute( + inst, + Collections.singletonList(typeVariableI), + Collections.singletonList(unbound), + context.env); + javaTypeArgs.add(inst); + } else { + javaTypeArgs.add(inst); + } + } + + for (int i = 0; i < typeVar.size(); i++) { + TypeMirror javaTypeArg = javaTypeArgs.get(i); + TypeMirror x = TypesUtils.substitute(javaTypeArg, typeVar, javaTypeArgs, context.env); + javaTypeArgs.remove(i); + javaTypeArgs.add(i, x); + } + + Map map = new HashMap<>(); + + List typeArgsATM = new ArrayList<>(); + // Recursive types: + for (int i = 0; i < typeArg.size(); i++) { + Variable ai = types.get(i); + AbstractType inst = typeArg.get(i); + typeArgsATM.add(inst.getAnnotatedType()); + TypeVariable typeVariableI = ai.getJavaType(); + map.put(typeVariableI, inst.getAnnotatedType()); + } + + Iterator iter = javaTypeArgs.iterator(); + // Instantiations that refer to another variable + List subsTypeArg = new ArrayList<>(); + for (AnnotatedTypeMirror type : typeArgsATM) { + TypeVariableSubstitutor typeVarSubstitutor = typeFactory.getTypeVarSubstitutor(); + AnnotatedTypeMirror subs; + if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { + AnnotatedTypeVariable capTypeVar = (AnnotatedTypeVariable) type; + AnnotatedTypeMirror upperBound = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + map, capTypeVar.getUpperBound()); + AnnotatedTypeMirror lowerBound = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + map, capTypeVar.getLowerBound()); + capTypeVar.setUpperBound(upperBound); + capTypeVar.setLowerBound(lowerBound); + subs = capTypeVar; + } else { + subs = typeVarSubstitutor.substituteWithoutCopyingTypeArguments(map, type); + } + subsTypeArg.add(new ProperType(subs, iter.next(), context)); + } + return subsTypeArg; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java new file mode 100644 index 00000000000..1f4921b73ef --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java @@ -0,0 +1,303 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.tools.javac.code.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.ReductionResult; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.TypesUtils; + +/** + * A type-like structure that contains at least one inference variable, but is not an inference + * variable. + */ +public class InferenceType extends AbstractType { + + /** + * The underlying Java type. It contains type variables that are mapped to inference variables in + * {@code map}. + */ + private final TypeMirror typeMirror; + + /** + * The AnnotatedTypeMirror. It contains type variables that are mapped to inference variables in + * {@code map}. + */ + private final AnnotatedTypeMirror type; + + /** A mapping of type variables to inference variables. */ + private final Theta map; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates an inference type. + * + * @param type the annotated type mirror + * @param typeMirror the type mirror + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param map a mapping from type variable to inference variablef + * @param context the context + */ + private InferenceType( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + super(context); + assert type.getKind() == typeMirror.getKind(); + this.type = type.asUse(); + this.typeMirror = typeMirror; + this.qualifierVars = qualifierVars; + this.map = map; + } + + @Override + public Kind getKind() { + return Kind.INFERENCE_TYPE; + } + + /** + * Creates an abstract type for the given TypeMirror. The created type is an {@link InferenceType} + * if {@code type} contains any type variables that are mapped to inference variables as specified + * by {@code map}. Or if {@code type} is a type variable that is mapped to an inference variable, + * that {@link Variable} is returned. Or if {@code type} contains no type variables that are + * mapped in an inference variable, a {@link ProperType} is returned. + * + * @param type the annotated type mirror + * @param typeMirror the java type + * @param map a mapping from type variable to inference variable + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static AbstractType create( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + @Nullable Theta map, + Java8InferenceContext context) { + + return create(type, typeMirror, map, AnnotationMirrorMap.emptyMap(), context); + } + + /** + * Creates an abstract type for the given TypeMirror. The created type is an {@link InferenceType} + * if {@code type} contains any type variables that are mapped to inference variables as specified + * by {@code map}. Or if {@code type} is a type variable that is mapped to an inference variable, + * that {@link Variable} is returned. Or if {@code type} contains no type variables that are + * mapped in an inference variable, a {@link ProperType} is returned. + * + * @param type the annotated type mirror + * @param typeMirror the java type + * @param map a mapping from type variable to inference variable + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static AbstractType create( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + @Nullable Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + assert type != null; + if (map == null) { + return new ProperType(type, typeMirror, qualifierVars, context); + } + + if (typeMirror.getKind() == TypeKind.TYPEVAR && map.containsKey(type.getUnderlyingType())) { + return new UseOfVariable( + (AnnotatedTypeVariable) type, map.get(type.getUnderlyingType()), qualifierVars, context); + } else if (AnnotatedContainsInferenceVariable.hasAnyTypeVariable(map.keySet(), type)) { + return new InferenceType(type, typeMirror, map, qualifierVars, context); + } else { + return new ProperType(type, typeMirror, qualifierVars, context); + } + } + + /** + * Creates abstract types for each TypeMirror. The created type is an {@link InferenceType} if it + * contains any type variables that are mapped to inference variables as specified by {@code map}. + * Or if the type is a type variable that is mapped to an inference variable, it will return that + * {@link Variable}. Or if the type contains no type variables that are mapped in an inference + * variable, a {@link ProperType} is returned. + * + * @param types the annotated type mirrors + * @param typeMirrors the java types + * @param map a mapping from type variable to inference variable + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static List create( + List types, + List typeMirrors, + Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + List abstractTypes = new ArrayList<>(); + Iterator iter = typeMirrors.iterator(); + for (AnnotatedTypeMirror type : types) { + abstractTypes.add(create(type, iter.next(), map, qualifierVars, context)); + } + return abstractTypes; + } + + @Override + public AbstractType create(AnnotatedTypeMirror type, TypeMirror typeMirror) { + return create(type, typeMirror, map, qualifierVars, context); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InferenceType variable = (InferenceType) o; + if (!type.equals(variable.type)) { + return false; + } + if (typeMirror.getKind() == TypeKind.TYPEVAR) { + if (variable.typeMirror.getKind() == TypeKind.TYPEVAR) { + return TypesUtils.areSame((TypeVariable) typeMirror, (TypeVariable) variable.typeMirror); + } + return false; + } + return context.modelTypes.isSameType(typeMirror, variable.typeMirror); + } + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + Kind.INFERENCE_TYPE.hashCode(); + return result; + } + + @Override + public TypeMirror getJavaType() { + return typeMirror; + } + + @Override + public AnnotatedTypeMirror getAnnotatedType() { + return type; + } + + @Override + public boolean isObject() { + return false; + } + + /** + * Returns all inference variables mentioned in this type. + * + * @return all inference variables mentioned in this type + */ + @Override + public Collection getInferenceVariables() { + LinkedHashSet variables = new LinkedHashSet<>(); + for (TypeVariable typeVar : + ContainsInferenceVariable.getMentionedTypeVariables(map.keySet(), typeMirror)) { + variables.add(map.get(typeVar)); + } + return variables; + } + + @Override + public AbstractType applyInstantiations() { + List typeVariables = new ArrayList<>(); + List arguments = new ArrayList<>(); + List instantiations = new ArrayList<>(); + + for (Variable alpha : map.values()) { + if (alpha.getInstantiation() != null) { + typeVariables.add(alpha.getJavaType()); + arguments.add(alpha.getBounds().getInstantiation().getJavaType()); + instantiations.add(alpha); + } + } + if (typeVariables.isEmpty()) { + return this; + } + + TypeMirror newTypeJava = + TypesUtils.substitute(typeMirror, typeVariables, arguments, context.env); + + Map mapping = new LinkedHashMap<>(); + + for (Variable alpha : instantiations) { + AnnotatedTypeMirror instantiation = alpha.getBounds().getInstantiation().getAnnotatedType(); + context.typeFactory.initializeAtm(instantiation); + mapping.put(alpha.getJavaType(), instantiation); + } + if (map.isEmpty()) { + return this; + } + + AnnotatedTypeMirror newType = typeFactory.getTypeVarSubstitutor().substitute(mapping, type); + return create(newType, newTypeJava, map, context); + } + + @Override + public String toString() { + return "inference type: " + typeMirror; + } + + @Override + public Set getQualifiers() { + return AbstractQualifier.create( + getAnnotatedType().getPrimaryAnnotations(), qualifierVars, context); + } + + /** + * Is {@code this} a subtype of {@code superType}? + * + * @param superType the potential supertype; is a declared type with no parameters + * @return if {@code this} is a subtype of {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isSubType(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); + if (subType.getKind() == TypeKind.WILDCARD) { + if (((WildcardType) subType).getExtendsBound() != null) { + subType = ((WildcardType) subType).getExtendsBound(); + } else { + subType = context.types.erasure((Type) subType); + } + } + + if (context.types.isSubtype((Type) subType, (Type) superJavaType)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java new file mode 100644 index 00000000000..f86b459835d --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java @@ -0,0 +1,251 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberReferenceTree.ReferenceMode; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TreeUtils.MemberReferenceKind; + +/** A method type for an invocation of a method or constructor. */ +public class InvocationType { + + /** A method or constructor invocation. */ + private final ExpressionTree invocation; + + /** The annotated method type. */ + private final AnnotatedExecutableType annotatedExecutableType; + + /** The Java method type. */ + private final ExecutableType methodType; + + /** The context. */ + private final Java8InferenceContext context; + + /** The annotated type factory. */ + private final AnnotatedTypeFactory typeFactory; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates an invocation type. + * + * @param annotatedExecutableType annotated method type + * @param methodType java method type + * @param invocation a method or constructor invocation + * @param context the context + */ + public InvocationType( + AnnotatedExecutableType annotatedExecutableType, + ExecutableType methodType, + ExpressionTree invocation, + Java8InferenceContext context) { + assert annotatedExecutableType != null && methodType != null; + this.annotatedExecutableType = annotatedExecutableType; + this.methodType = methodType; + this.invocation = invocation; + this.context = context; + this.typeFactory = context.typeFactory; + + SimpleAnnotatedTypeScanner> s = + new SimpleAnnotatedTypeScanner<>( + (type, polys) -> { + for (AnnotationMirror a : type.getPrimaryAnnotations()) { + if (typeFactory.getQualifierHierarchy().isPolymorphicQualifier(a)) { + polys.add(a); + } + } + return null; + }); + Set polys = new AnnotationMirrorSet(); + s.visit(annotatedExecutableType, polys); + AnnotationMirrorMap qualifierVars = new AnnotationMirrorMap<>(); + for (AnnotationMirror poly : polys) { + qualifierVars.put(poly, new QualifierVar(invocation, poly, context)); + } + this.qualifierVars = qualifierVars; + } + + /** + * Returns the method or constructor invocation. + * + * @return the method or constructor invocation + */ + public ExpressionTree getInvocation() { + return invocation; + } + + /** + * Returns the java method type. + * + * @return the java method type + */ + public ExecutableType getJavaType() { + return annotatedExecutableType.getUnderlyingType(); + } + + /** + * Returns the thrown types. + * + * @param map a mapping from type variable to inference variable + * @return the thrown types + */ + public List getThrownTypes(Theta map) { + List thrown = new ArrayList<>(); + Iterator iter = methodType.getThrownTypes().iterator(); + for (AnnotatedTypeMirror t : annotatedExecutableType.getThrownTypes()) { + thrown.add(InferenceType.create(t, iter.next(), map, context)); + } + return thrown; + } + + /** + * Returns the return type. + * + * @param map a mapping from type variable to inference variable + * @return the return type + */ + public AbstractType getReturnType(Theta map) { + TypeMirror returnTypeJava; + AnnotatedTypeMirror returnType; + + if (TreeUtils.isDiamondTree(invocation)) { + Element e = ElementUtils.enclosingTypeElement(TreeUtils.elementFromUse(invocation)); + returnTypeJava = e.asType(); + returnType = typeFactory.getAnnotatedType(e); + } else if (invocation.getKind() == Tree.Kind.METHOD_INVOCATION + || invocation.getKind() == Tree.Kind.MEMBER_REFERENCE) { + returnType = annotatedExecutableType.getReturnType(); + if (invocation.getKind() == Kind.MEMBER_REFERENCE + && ((MemberReferenceTree) invocation).getMode() == ReferenceMode.NEW) { + returnTypeJava = returnType.getUnderlyingType(); + } else { + returnTypeJava = methodType.getReturnType(); + } + + } else { + returnTypeJava = TreeUtils.typeOf(invocation); + returnType = typeFactory.getAnnotatedType(invocation); + } + + if (map == null) { + return new ProperType(returnType, returnTypeJava, context); + } + return InferenceType.create(returnType, returnTypeJava, map, context); + } + + /** + * Returns a list of the parameter types of {@code InvocationType} where the vararg parameter has + * been modified to match the arguments in {@code expression}. + * + * @param map a mapping from type variable to inference variable + * @param size the number of parameters to return; used to expand the vararg + * @return a list of the parameter types of {@code InvocationType} where the vararg parameter has + * been modified to match the arguments in {@code expression} + */ + public List getParameterTypes(Theta map, int size) { + List params = new ArrayList<>(annotatedExecutableType.getParameterTypes()); + + if (TreeUtils.isVarArgMethodCall(invocation)) { + AnnotatedArrayType vararg = (AnnotatedArrayType) params.remove(params.size() - 1); + for (int i = params.size(); i < size; i++) { + params.add(vararg.getComponentType()); + } + } + + List paramsJava = new ArrayList<>(methodType.getParameterTypes()); + + if (TreeUtils.isVarArgMethodCall(invocation)) { + ArrayType vararg = (ArrayType) paramsJava.remove(paramsJava.size() - 1); + for (int i = paramsJava.size(); i < size; i++) { + paramsJava.add(vararg.getComponentType()); + } + } + if (invocation.getKind() == Kind.MEMBER_REFERENCE + && MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) invocation) + .isUnbound()) { + params.add(0, annotatedExecutableType.getReceiverType()); + paramsJava.add(0, annotatedExecutableType.getReceiverType().getUnderlyingType()); + } + return InferenceType.create(params, paramsJava, map, qualifierVars, context); + } + + /** + * Returns the parameter types. (Varags are not expanded.) + * + * @param map a mapping from type variable to inference variable + * @return the parameter types + */ + public List getParameterTypes(Theta map) { + return getParameterTypes(map, annotatedExecutableType.getParameterTypes().size()); + } + + /** + * Whether this method has type variables. + * + * @return whether this method has type variables. + */ + public boolean hasTypeVariables() { + return !annotatedExecutableType.getTypeVariables().isEmpty(); + } + + /** + * Returns the annotated type variables. + * + * @return the annotated type variables + */ + public List getAnnotatedTypeVariables() { + return annotatedExecutableType.getTypeVariables(); + } + + /** + * Returns the type variables. + * + * @return the type variables + */ + public List getTypeVariables() { + return methodType.getTypeVariables(); + } + + /** + * Whether this method is void. + * + * @return whether this method is void + */ + public boolean isVoid() { + return annotatedExecutableType.getReturnType().getKind() == TypeKind.VOID; + } + + /** + * Returns the annotated method type. + * + * @return the annotated method type + */ + public AnnotatedExecutableType getAnnotatedType() { + return annotatedExecutableType; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java new file mode 100644 index 00000000000..0b59c324c02 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java @@ -0,0 +1,275 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.code.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.ReductionResult; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; + +/** A type that does not contain any inference variables. */ +public class ProperType extends AbstractType { + + /** The annotated type mirror. */ + private final AnnotatedTypeMirror type; + + /** The Java type. */ + private final TypeMirror properType; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates a proper type. + * + * @param type the annotated type + * @param properType the java type + * @param context the context + */ + public ProperType( + AnnotatedTypeMirror type, TypeMirror properType, Java8InferenceContext context) { + this(type, properType, AnnotationMirrorMap.emptyMap(), context); + } + + /** + * Creates a proper type. + * + * @param type the annotated type + * @param properType the java type + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + */ + public ProperType( + AnnotatedTypeMirror type, + TypeMirror properType, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + super(context); + this.properType = properType; + this.type = type; + this.qualifierVars = qualifierVars; + verifyTypeKinds(type, properType); + } + + /** + * Creates a proper type from the type of the expression. + * + * @param tree an expression tree + * @param context the context + */ + public ProperType(ExpressionTree tree, Java8InferenceContext context) { + super(context); + this.type = context.typeFactory.getAnnotatedType(tree); + this.properType = type.getUnderlyingType(); + this.qualifierVars = AnnotationMirrorMap.emptyMap(); + verifyTypeKinds(type, properType); + } + + /** + * Creates a proper type from the type of the variable. + * + * @param varTree a variable tree + * @param context the context + */ + public ProperType(VariableTree varTree, Java8InferenceContext context) { + super(context); + this.type = context.typeFactory.getAnnotatedType(varTree); + this.properType = TreeUtils.typeOf(varTree); + this.qualifierVars = AnnotationMirrorMap.emptyMap(); + verifyTypeKinds(type, properType); + } + + /** + * Asserts that the underlying type of {@code atm} is the same kind as {@code typeMirror}. + * + * @param atm annotated type mirror + * @param typeMirror java type + */ + private static void verifyTypeKinds(AnnotatedTypeMirror atm, TypeMirror typeMirror) { + assert typeMirror != null && typeMirror.getKind() != TypeKind.VOID && atm != null; + + if (typeMirror.getKind() != atm.getKind()) { + // throw new BugInCF("type: %s annotated type: %s", typeMirror, atm.getUnderlyingType()); + } + } + + @Override + public Kind getKind() { + return Kind.PROPER; + } + + @Override + public AbstractType create(AnnotatedTypeMirror atm, TypeMirror type) { + return new ProperType(atm, type, qualifierVars, context); + } + + /** + * If this is a primitive type, then the proper type corresponding to its wrapper is returned. + * Otherwise, this object is return. + * + * @return the proper type that is the wrapper type for this type or this if no such wrapper + * exists + */ + public ProperType boxType() { + if (properType.getKind().isPrimitive()) { + return new ProperType( + typeFactory.getBoxedType((AnnotatedPrimitiveType) getAnnotatedType()), + context.types.boxedClass((Type) properType).asType(), + context); + } + return this; + } + + /** + * Is {@code this} a subtype of {@code superType}? + * + * @param superType super type + * @return if {@code this} is a subtype of {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isSubType(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); + + if (context.typeFactory.types.isAssignable(subType, superJavaType) + || TypesUtils.isErasedSubtype(subType, superJavaType, context.typeFactory.types)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } + } + + /** + * Is {@code this} an unchecked subtype of {@code superType}? + * + * @param superType super type + * @return if {@code this} is an unchecked subtype of {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isSubTypeUnchecked(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); + + if (context.types.isSubtypeUnchecked((Type) subType, (Type) superJavaType)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } + } + + /** + * Is {@code this} assignable to {@code superType}? + * + * @param superType super type + * @return if {@code this} assignable to {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isAssignable(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); + + if (context.types.isAssignable((Type) subType, (Type) superJavaType)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } + } + + @SuppressWarnings("interning:not.interned") // Checking for exact object. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ProperType otherProperType = (ProperType) o; + + if (!type.equals(otherProperType.type)) { + return false; + } + if (properType.getKind() == TypeKind.TYPEVAR) { + if (otherProperType.properType.getKind() == TypeKind.TYPEVAR) { + return TypesUtils.areSame( + (TypeVariable) properType, (TypeVariable) otherProperType.properType); + } + return false; + } + return properType == otherProperType.properType // faster + || context.env.getTypeUtils().isSameType(properType, otherProperType.properType); // slower + } + + @Override + public int hashCode() { + int result = properType.toString().hashCode(); + result = 31 * result + Kind.PROPER.hashCode(); + return result; + } + + @Override + public TypeMirror getJavaType() { + return properType; + } + + @Override + public AnnotatedTypeMirror getAnnotatedType() { + return type; + } + + @Override + public boolean isObject() { + return TypesUtils.isObject(properType); + } + + @Override + public Collection getInferenceVariables() { + return Collections.emptyList(); + } + + @Override + public AbstractType applyInstantiations() { + return this; + } + + @Override + public Set getQualifiers() { + return AbstractQualifier.create( + getAnnotatedType().getPrimaryAnnotations(), qualifierVars, context); + } + + @Override + public String toString() { + return type.toString(); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java new file mode 100644 index 00000000000..58af2bd5eb9 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java @@ -0,0 +1,41 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; + +/** A wrapper around an {@link AnnotationMirror}. */ +public class Qualifier extends AbstractQualifier { + + /** The annotation. */ + private final AnnotationMirror annotation; + + /** + * A wrapper around an {@link AnnotationMirror}. + * + * @param annotation the annotation + * @param context the context + */ + protected Qualifier(AnnotationMirror annotation, Java8InferenceContext context) { + super(annotation, context); + this.annotation = annotation; + } + + /** + * Returns the annotation + * + * @return the annotation + */ + public AnnotationMirror getAnnotation() { + return annotation; + } + + @Override + public AnnotationMirror getInstantiation() { + return annotation; + } + + @Override + public String toString() { + return annotation.toString(); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java new file mode 100644 index 00000000000..27b4ff6b0e5 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java @@ -0,0 +1,194 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.source.tree.ExpressionTree; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.QualifierTyping; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; + +/** + * A {@code QualifierVar} is a variable for a polymorphic qualifier that needs to be viewpoint + * adapted at a call site. + */ +public class QualifierVar extends AbstractQualifier { + + /** Identification number. Used only to make debugging easier. */ + protected final int id; + + /** + * The expression for which this variable is being solved. Used to differentiate qualifier + * variables for two different invocations of the same method or constructor. + */ + protected final ExpressionTree invocation; + + /** The polymorphic qualifier associated with this var. */ + protected final AnnotationMirror polyQualifier; + + /** A mapping from a {@link BoundKind} to a set of abstract qualifiers. */ + public final EnumMap> qualifierBounds = + new EnumMap<>(BoundKind.class); + + /** The instantiation of this variable. This is set during inference. */ + protected AnnotationMirror instantiation; + + /** + * Creates a {@link QualifierVar}. + * + * @param invocation the expression for which this variable is being solved + * @param polyQualifier polymorphic qualifier associated with this var + * @param context the context + */ + public QualifierVar( + ExpressionTree invocation, AnnotationMirror polyQualifier, Java8InferenceContext context) { + super(polyQualifier, context); + this.id = context.getNextVariableId(); + this.invocation = invocation; + this.polyQualifier = polyQualifier; + qualifierBounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.UPPER, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.LOWER, new LinkedHashSet<>()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QualifierVar that = (QualifierVar) o; + return id == that.id + && Objects.equals(invocation, that.invocation) + && Objects.equals(polyQualifier, that.polyQualifier); + } + + @Override + public int hashCode() { + return Objects.hash(id, invocation, polyQualifier); + } + + @Override + public String toString() { + return "@QV" + id; + } + + /** + * Add a bound for this qualifier variable + * + * @param kind a bound kind + * @param otherQual the bound to add + * @return a set of constraints generated from adding this bound. + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public ConstraintSet addBound(BoundKind kind, AbstractQualifier otherQual) { + if (otherQual == this) { + return ConstraintSet.TRUE; + } + if (kind == BoundKind.EQUAL && otherQual instanceof Qualifier) { + instantiation = ((Qualifier) otherQual).getAnnotation(); + } + if (qualifierBounds.get(kind).add(otherQual)) { + return addConstraintsFromComplementaryBounds(kind, otherQual); + } + return ConstraintSet.TRUE; + } + + /** + * Returns the constraints generated by adding the given bound. + * + * @param kind bound kind + * @param s other abstract qualifier + * @return the constraints + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private ConstraintSet addConstraintsFromComplementaryBounds(BoundKind kind, AbstractQualifier s) { + ConstraintSet constraints = new ConstraintSet(); + switch (kind) { + case EQUAL: + for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new QualifierTyping(s, t, Kind.TYPE_EQUALITY)); + } + } + break; + case LOWER: + for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new QualifierTyping(s, t, Kind.SUBTYPE)); + } + } + break; + case UPPER: + for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new QualifierTyping(t, s, Kind.SUBTYPE)); + } + } + break; + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + for (AbstractQualifier t : qualifierBounds.get(BoundKind.LOWER)) { + if (s != t) { + constraints.add(new QualifierTyping(t, s, Kind.SUBTYPE)); + } + } + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + for (AbstractQualifier t : qualifierBounds.get(BoundKind.UPPER)) { + if (s != t) { + constraints.add(new QualifierTyping(s, t, Kind.SUBTYPE)); + } + } + } + return constraints; + } + + @Override + AnnotationMirror getInstantiation() { + if (instantiation == null) { + AnnotationMirror lub = null; + for (AbstractQualifier lower : qualifierBounds.get(BoundKind.LOWER)) { + if (lower instanceof Qualifier) { + if (lub != null) { + lub = + context + .typeFactory + .getQualifierHierarchy() + .leastUpperBoundQualifiersOnly(lub, ((Qualifier) lower).getAnnotation()); + } else { + lub = ((Qualifier) lower).getAnnotation(); + } + } + } + if (lub != null) { + instantiation = lub; + return instantiation; + } + AnnotationMirror glb = null; + for (AbstractQualifier upper : qualifierBounds.get(BoundKind.UPPER)) { + if (upper instanceof Qualifier) { + if (glb != null) { + glb = + context + .typeFactory + .getQualifierHierarchy() + .greatestLowerBoundQualifiersOnly(glb, ((Qualifier) upper).getAnnotation()); + } else { + glb = ((Qualifier) upper).getAnnotation(); + } + } + } + return glb; + } + return instantiation; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java new file mode 100644 index 00000000000..e4ec904b501 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java @@ -0,0 +1,243 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; + +/** + * A use of an inference variable. This class keeps track of whether the use of this variable has a + * primary annotation. + */ +public class UseOfVariable extends AbstractType { + + /** The variable that this is a use of. */ + private final Variable variable; + + /** Whether this use has a primary annotation. */ + private final boolean hasPrimaryAnno; + + /** The bottom annotations for each hierarchy that has a primary annotation on this use. */ + private final Set bots; + + /** The top annotations for each hierarchy that has a primary annotation on this use. */ + private final Set tops; + + /** The annotated type variable for this use. */ + private final AnnotatedTypeVariable type; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates a use of a variable. + * + * @param type annotated type variable for this use + * @param variable variable that this is a use of + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + */ + public UseOfVariable( + AnnotatedTypeVariable type, + Variable variable, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + super(context); + QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); + this.qualifierVars = qualifierVars; + this.variable = variable; + this.type = type.deepCopy(); + this.hasPrimaryAnno = !type.getPrimaryAnnotations().isEmpty(); + this.bots = new AnnotationMirrorSet(); + this.tops = new AnnotationMirrorSet(); + if (hasPrimaryAnno) { + for (AnnotationMirror anno : type.getPrimaryAnnotations()) { + bots.add(qh.getBottomAnnotation(anno)); + tops.add(qh.getTopAnnotation(anno)); + } + } + } + + @Override + public AbstractType create(AnnotatedTypeMirror atm, TypeMirror type) { + return InferenceType.create(atm, type, variable.map, qualifierVars, context); + } + + @Override + public boolean isObject() { + return false; + } + + @Override + public List getTypeParameterBounds() { + return null; + } + + @Override + public UseOfVariable capture(Java8InferenceContext context) { + return this; + } + + @Override + public UseOfVariable getErased() { + return this; + } + + @Override + public TypeVariable getJavaType() { + return variable.typeVariableJava; + } + + @Override + public AnnotatedTypeVariable getAnnotatedType() { + return type; + } + + @Override + public Kind getKind() { + return Kind.USE_OF_VARIABLE; + } + + @Override + public Collection getInferenceVariables() { + return Collections.singleton(variable); + } + + @Override + public AbstractType applyInstantiations() { + if (this.variable.getInstantiation() != null) { + return this.variable.getInstantiation(); + } + + return this; + } + + /** + * Returns the variable that this is a use of. + * + * @return the variable that this is a use of + */ + public Variable getVariable() { + return variable; + } + + /** + * Set whether this use has a throws bound. + * + * @param hasThrowsBound whether this use has a throws bound + */ + public void setHasThrowsBound(boolean hasThrowsBound) { + variable.getBounds().setHasThrowsBound(hasThrowsBound); + } + + /** + * Adds a qualifier bound for this variable, is this use does not have a primary annotation. + * + * @param kind the kind of bound + * @param annotations the qualifiers to add + */ + public void addQualifierBound(BoundKind kind, Set annotations) { + if (!hasPrimaryAnno) { + variable.getBounds().addQualifierBound(kind, annotations); + } + } + + /** + * Adds a bound for this variable, is this use does not have a primary annotation. + * + * @param kind the kind of bound + * @param bound the type of the bound + */ + public void addBound(BoundKind kind, AbstractType bound) { + if (!hasPrimaryAnno) { + variable.getBounds().addBound(kind, bound); + } else { + // If the use has a primary annotation, then add the bound but with that annotations set to + // bottom + // or top. This makes it so that the java type is still a bound, but the qualifiers do not + // change the results of inference. + if (kind == BoundKind.LOWER) { + bound.getAnnotatedType().replaceAnnotations(bots); + variable.getBounds().addBound(kind, bound); + } else if (kind == BoundKind.UPPER) { + bound.getAnnotatedType().replaceAnnotations(tops); + variable.getBounds().addBound(kind, bound); + } else { + AnnotatedTypeMirror copyATM = bound.getAnnotatedType().deepCopy(); + AbstractType boundCopy = bound.create(copyATM, bound.getJavaType()); + + bound.getAnnotatedType().replaceAnnotations(tops); + variable.getBounds().addBound(BoundKind.UPPER, bound); + + boundCopy.getAnnotatedType().replaceAnnotations(bots); + variable.getBounds().addBound(BoundKind.LOWER, boundCopy); + } + } + } + + @Override + public Set getQualifiers() { + if (hasPrimaryAnno) { + return AbstractQualifier.create( + getAnnotatedType().getPrimaryAnnotations(), qualifierVars, context); + } else { + return Collections.emptySet(); + } + } + + @Override + public String toString() { + return "use of " + variable + (hasPrimaryAnno ? " with primary" : ""); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + UseOfVariable that = (UseOfVariable) o; + + if (hasPrimaryAnno != that.hasPrimaryAnno) { + return false; + } + if (variable != that.variable) { + return false; + } + if (!bots.equals(that.bots)) { + return false; + } + if (!tops.equals(that.tops)) { + return false; + } + + return type.equals(that.type); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + variable.hashCode(); + result = 31 * result + (hasPrimaryAnno ? 1 : 0); + result = 31 * result + bots.hashCode(); + result = 31 * result + tops.hashCode(); + result = 31 * result + type.hashCode(); + return result; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java new file mode 100644 index 00000000000..6a839ca172a --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java @@ -0,0 +1,220 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import com.sun.source.tree.ExpressionTree; +import java.util.Iterator; +import java.util.Set; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.typeinference8.types.AbstractType.Kind; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.TypesUtils; + +/** + * An inference variable. It corresponds to a type argument for a particular method invocation, new + * class tree or method reference that needs to be inferred. + */ +@Interned public class Variable { + + /** Bounds of this variable. */ + protected final VariableBounds variableBounds; + + /** Identification number. Used only to make debugging easier. */ + protected final int id; + + /** + * The expression for which this variable is being solved. Used to differentiate inference + * variables for two different invocations of the same method or constructor. This is set during + * inference. + */ + protected final ExpressionTree invocation; + + /** Type variable for which the instantiation of this variable is a type argument, */ + protected final TypeVariable typeVariableJava; + + /** Type variable for which the instantiation of this variable is a type argument, */ + protected final AnnotatedTypeVariable typeVariable; + + /** A mapping from type variable to inference variable. */ + protected final Theta map; + + /** The context. */ + protected final Java8InferenceContext context; + + /** + * Creates a variable. + * + * @param typeVariable an annotated type variable + * @param typeVariableJava a java type variable + * @param invocation the invocation for which this variable is a type argument for + * @param context the context + * @param map a mapping from type variable to inference variable + */ + Variable( + AnnotatedTypeVariable typeVariable, + TypeVariable typeVariableJava, + ExpressionTree invocation, + Java8InferenceContext context, + Theta map) { + this(typeVariable, typeVariableJava, invocation, context, map, context.getNextVariableId()); + } + + /** + * Creates a variable. + * + * @param typeVariable an annotated type variable + * @param typeVariableJava a java type variable + * @param invocation the invocation for which this variable is a type argument for + * @param context the context + * @param map a mapping from type variable to inference variable + * @param id a unique number for this variable + */ + @SuppressWarnings("interning:argument") // "this" is interned + protected Variable( + AnnotatedTypeVariable typeVariable, + TypeVariable typeVariableJava, + ExpressionTree invocation, + Java8InferenceContext context, + Theta map, + int id) { + this.context = context; + assert typeVariable != null; + this.variableBounds = new VariableBounds(this, context); + this.typeVariableJava = typeVariableJava; + this.typeVariable = typeVariable; + this.invocation = invocation; + this.map = map; + this.id = id; + } + + /** + * Return this variable's current bounds. + * + * @return this variable's current bounds + */ + public VariableBounds getBounds() { + return variableBounds; + } + + /** + * Adds the initial bounds to this variable. These are the bounds implied by the upper bounds of + * the type variable. See end of JLS 18.1.3. + * + * @param map used to determine if the bounds refer to another variable + */ + public void initialBounds(Theta map) { + TypeMirror upperBound = typeVariableJava.getUpperBound(); + // If Pl has no TypeBound, the bound {@literal al <: Object} appears in the set. Otherwise, + // for each type T delimited by & in the TypeBound, the bound {@literal al <: T[P1:=a1,..., + // Pp:=ap]} appears in the set; if this results in no proper upper bounds for al (only + // dependencies), then the bound {@literal al <: Object} also appears in the set. + switch (upperBound.getKind()) { + case INTERSECTION: + Iterator iter = + ((IntersectionType) upperBound).getBounds().iterator(); + for (AnnotatedTypeMirror bound : typeVariable.getUpperBound().directSupertypes()) { + AbstractType t1 = InferenceType.create(bound, iter.next(), map, context); + variableBounds.addBound(BoundKind.UPPER, t1); + } + break; + default: + AbstractType t1 = + InferenceType.create(typeVariable.getUpperBound(), upperBound, map, context); + variableBounds.addBound(BoundKind.UPPER, t1); + break; + } + + Set quals = + AbstractQualifier.create( + typeVariable.getLowerBound().getPrimaryAnnotations(), + AnnotationMirrorMap.emptyMap(), + context); + variableBounds.addQualifierBound(BoundKind.LOWER, quals); + } + + /** + * Returns the invocation tree. + * + * @return the invocation tree + */ + public ExpressionTree getInvocation() { + return invocation; + } + + @SuppressWarnings("interning:not.interned") // Checking for exact object. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Variable variable = (Variable) o; + return TypesUtils.areSame(typeVariableJava, variable.typeVariableJava) + && invocation == variable.invocation; + } + + @Override + public int hashCode() { + int result = typeVariableJava.toString().hashCode(); + result = 31 * result + Kind.USE_OF_VARIABLE.hashCode(); + result = 31 * result + invocation.hashCode(); + return result; + } + + @Override + public String toString() { + if (variableBounds.hasInstantiation()) { + return "a" + id + " := " + variableBounds.getInstantiation(); + } + return "a" + id; + } + + /** + * Returns the instantiation for this variable. + * + * @return the instantiation for this variable + */ + public ProperType getInstantiation() { + return variableBounds.getInstantiation(); + } + + /** in case the first attempt at resolution fails. */ + public void save() { + variableBounds.save(); + } + + /** + * Restore the bounds to the state previously saved. This method is called if the first attempt at + * resolution fails. + */ + public void restore() { + variableBounds.restore(); + } + + /** + * Returns whether this variable was created for a capture bound. + * + * @return whether this variable was created for a capture bound + */ + public boolean isCaptureVariable() { + return false; + } + + /** + * The Java type variable. + * + * @return the Java type variable + */ + public TypeVariable getJavaType() { + return typeVariableJava; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java new file mode 100644 index 00000000000..7e7e244cbfe --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java @@ -0,0 +1,650 @@ +package org.checkerframework.framework.util.typeinference8.types; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.QualifierTyping; +import org.checkerframework.framework.util.typeinference8.constraint.TypeConstraint; +import org.checkerframework.framework.util.typeinference8.constraint.Typing; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.IPair; + +/** Data structure to stores the bounds of a variable. */ +public class VariableBounds { + + /** Kind of bound. */ + public enum BoundKind { + /** {@code other type <: this } */ + LOWER, + /** {@code this <: other type } */ + UPPER, + /** {@code this = other type } */ + EQUAL; + } + + /** The variable whose bounds this class represents. */ + private final Variable variable; + + /** The context. */ + private final Java8InferenceContext context; + + /** The type to which this variable is instantiated. */ + private ProperType instantiation = null; + + /** + * Bounds on this variable. Stored as a map from kind of bound (upper, lower, equal) to a set of + * {@link AbstractType}s. + */ + public final EnumMap> bounds = new EnumMap<>(BoundKind.class); + + /** + * Qualifier bounds on this variable. Stored as a map from kind of bound (upper, lower, equal) to + * a set of {@link AnnotationMirror}s. A qualifier bound is a bound on the primary annotation of + * this variable. + */ + public final EnumMap> qualifierBounds = + new EnumMap<>(BoundKind.class); + + /** Constraints implied by complementary pairs of bounds found during incorporation. */ + public final ConstraintSet constraints = new ConstraintSet(); + + /** Whether this variable has a throws bounds. */ + public boolean hasThrowsBound = false; + + /** Saved bounds used in the event the first attempt at resolution fails. */ + public EnumMap> savedBounds = null; + + /** Saved qualifier bounds used in the event the first attempt at resolution fails. */ + public EnumMap> savedQualifierBounds = null; + + /** + * Creates bounds for {@code variable}. + * + * @param variable a variable + * @param context the context + */ + public VariableBounds(Variable variable, Java8InferenceContext context) { + this.variable = variable; + this.context = context; + bounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); + bounds.put(BoundKind.UPPER, new LinkedHashSet<>()); + bounds.put(BoundKind.LOWER, new LinkedHashSet<>()); + + qualifierBounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.UPPER, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.LOWER, new LinkedHashSet<>()); + } + + /** Save the current bounds in case the first attempt at resolution fails. */ + public void save() { + savedBounds = new EnumMap<>(BoundKind.class); + savedBounds.put(BoundKind.EQUAL, new LinkedHashSet<>(bounds.get(BoundKind.EQUAL))); + savedBounds.put(BoundKind.UPPER, new LinkedHashSet<>(bounds.get(BoundKind.UPPER))); + savedBounds.put(BoundKind.LOWER, new LinkedHashSet<>(bounds.get(BoundKind.LOWER))); + + savedQualifierBounds = new EnumMap<>(BoundKind.class); + savedQualifierBounds.put( + BoundKind.EQUAL, new LinkedHashSet<>(qualifierBounds.get(BoundKind.EQUAL))); + savedQualifierBounds.put( + BoundKind.UPPER, new LinkedHashSet<>(qualifierBounds.get(BoundKind.UPPER))); + savedQualifierBounds.put( + BoundKind.LOWER, new LinkedHashSet<>(qualifierBounds.get(BoundKind.LOWER))); + } + + /** + * Restore the bounds to the state previously saved. This method is called if the first attempt at + * resolution fails. + */ + public void restore() { + assert savedBounds != null; + instantiation = null; + bounds.clear(); + bounds.put(BoundKind.EQUAL, new LinkedHashSet<>(savedBounds.get(BoundKind.EQUAL))); + bounds.put(BoundKind.UPPER, new LinkedHashSet<>(savedBounds.get(BoundKind.UPPER))); + bounds.put(BoundKind.LOWER, new LinkedHashSet<>(savedBounds.get(BoundKind.LOWER))); + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (t.isProper()) { + instantiation = (ProperType) t; + } + } + qualifierBounds.clear(); + qualifierBounds.put( + BoundKind.EQUAL, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.EQUAL))); + qualifierBounds.put( + BoundKind.UPPER, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.UPPER))); + qualifierBounds.put( + BoundKind.LOWER, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.LOWER))); + } + + /** + * Return true if this has a throws bound. + * + * @return true if this has a throws bound + */ + public boolean hasThrowsBound() { + return hasThrowsBound; + } + + /** + * Set has throws bound + * + * @param b has thrown bound + */ + public void setHasThrowsBound(boolean b) { + hasThrowsBound = b; + } + + /** + * Adds {@code otherType} as bound against this variable. + * + * @param kind the kind of bound + * @param otherType the bound type + * @return if a new bound was added + */ + public boolean addBound(BoundKind kind, AbstractType otherType) { + if (otherType.isUseOfVariable() && ((UseOfVariable) otherType).getVariable() == variable) { + return false; + } + if (kind == BoundKind.EQUAL && otherType.isProper()) { + instantiation = ((ProperType) otherType).boxType(); + } + if (bounds.get(kind).add(otherType)) { + addConstraintsFromComplementaryBounds(kind, otherType); + Set aQuals = otherType.getQualifiers(); + addConstraintsFromComplementaryQualifierBounds(kind, aQuals); + return true; + } + return false; + } + + /** + * Adds {@code qualifiers} as a qualifier bound against this variable. + * + * @param kind the kind of bound + * @param qualifiers the qualifiers + */ + public void addQualifierBound(BoundKind kind, Set qualifiers) { + addConstraintsFromComplementaryQualifierBounds(kind, qualifiers); + addConstraintsFromComplementaryBounds(kind, qualifiers); + qualifierBounds.get(kind).addAll(qualifiers); + } + + /** + * Add constraints created via incorporation of the bound. See JLS 18.3.1. + * + * @param kind the kind of bound + * @param qualifiers the qualifiers + */ + public void addConstraintsFromComplementaryQualifierBounds( + BoundKind kind, Set qualifiers) { + Set equalBounds = qualifierBounds.get(BoundKind.EQUAL); + if (kind == BoundKind.EQUAL) { + addQualifierConstraint(qualifiers, equalBounds, Kind.QUALIFIER_EQUALITY); + } else if (kind == BoundKind.LOWER) { + addQualifierConstraint(qualifiers, equalBounds, Kind.QUALIFIER_SUBTYPE); + } else { // UPPER + addQualifierConstraint(equalBounds, qualifiers, Kind.QUALIFIER_SUBTYPE); + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + addQualifierConstraint( + qualifierBounds.get(BoundKind.LOWER), qualifiers, Kind.QUALIFIER_SUBTYPE); + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + addQualifierConstraint( + qualifiers, qualifierBounds.get(BoundKind.UPPER), Kind.QUALIFIER_SUBTYPE); + } + } + + /** + * Add a {@link QualifierTyping} constraint for a qualifier in {@code setT} and the qualifier in + * {@code setS} in the same hierarchy. + * + * @param setT a set of abstract qualifiers on the left side of the constraint + * @param setS a set of abstract qualifiers on the right side of the constraint + * @param kind the kind of constraint + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private void addQualifierConstraint( + Set setT, Set setS, Kind kind) { + for (AbstractQualifier t : setT) { + for (AbstractQualifier s : setS) { + if (s != t && s.sameHierarchy(t)) { + constraints.add(new QualifierTyping(t, s, kind)); + } + } + } + } + + /** + * Add constraints created via incorporation of the bound. See JLS 18.3.1. + * + * @param kind the kind of bound + * @param s the type of the bound + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public void addConstraintsFromComplementaryBounds(BoundKind kind, AbstractType s) { + switch (kind) { + case EQUAL: + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new Typing(s, t, Kind.TYPE_EQUALITY)); + } + } + break; + case LOWER: + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new Typing(s, t, Kind.SUBTYPE)); + } + } + break; + case UPPER: + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new Typing(t, s, Kind.SUBTYPE)); + } + } + break; + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + for (AbstractType t : bounds.get(BoundKind.LOWER)) { + if (s != t) { + constraints.add(new Typing(t, s, Kind.SUBTYPE)); + } + } + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + for (AbstractType t : bounds.get(BoundKind.UPPER)) { + if (s != t) { + constraints.add(new Typing(s, t, Kind.SUBTYPE)); + } + } + } + + if (kind == BoundKind.UPPER) { + // When a bound set contains a pair of bounds var <: S and var <: T, and there exists + // a supertype of S of the form G and + // a supertype of T of the form G (for some generic class or interface, G), + // then for all i (1 <= i <= n), if Si and Ti are types (not wildcards), + // the constraint formula is implied. + if (s.isInferenceType() || s.isProper()) { + for (AbstractType t : bounds.get(BoundKind.LOWER)) { + if (t.isProper() || t.isInferenceType()) { + constraints.addAll(getConstraintsFromParameterized(s, t)); + } + } + } + } + } + + /** + * Adds constraints from complementary bounds. + * + * @param kind kind of bound + * @param s qualifiers + */ + public void addConstraintsFromComplementaryBounds( + BoundKind kind, Set s) { + // Copy bound to equal variables + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (t.isUseOfVariable()) { + VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); + otherBounds.qualifierBounds.get(kind).addAll(s); + } + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + for (AbstractType t : bounds.get(BoundKind.LOWER)) { + if (t.isUseOfVariable()) { + VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); + otherBounds.qualifierBounds.get(BoundKind.UPPER).addAll(s); + } + } + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + for (AbstractType t : bounds.get(BoundKind.UPPER)) { + if (t.isUseOfVariable()) { + VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); + otherBounds.qualifierBounds.get(BoundKind.LOWER).addAll(s); + } + } + } + } + + /** + * Returns the constraints between the type arguments to {@code s} and {@code t}. + * + *

          If the there exists a supertype of S of the form {@code G} and a supertype of T + * of the form {@code G} (for some generic class or interface, G), then for all i + * ({@code 1 <= i <= n}), if Si and Ti are types (not wildcards), the constraint formula {@code + * } is implied. + * + * @param s a type argument + * @param t a type argument + * @return the constraints between the type arguments to {@code s} and {@code t} + */ + private List getConstraintsFromParameterized(AbstractType s, AbstractType t) { + IPair pair = + context.inferenceTypeFactory.getParameterizedSupers(s, t); + + if (pair == null) { + return new ArrayList<>(); + } + + List ss = pair.first.getTypeArguments(); + List ts = pair.second.getTypeArguments(); + assert ss.size() == ts.size(); + + List constraints = new ArrayList<>(); + for (int i = 0; i < ss.size(); i++) { + AbstractType si = ss.get(i); + AbstractType ti = ts.get(i); + if (si.getTypeKind() != TypeKind.WILDCARD && ti.getTypeKind() != TypeKind.WILDCARD) { + constraints.add(new Typing(si, ti, Kind.TYPE_EQUALITY)); + } + } + return constraints; + } + + /** + * Return all lower bounds that are proper types. + * + * @return all lower bounds that are proper types + */ + public Set findProperLowerBounds() { + LinkedHashSet set = new LinkedHashSet<>(); + for (AbstractType bound : bounds.get(BoundKind.LOWER)) { + if (bound.isProper()) { + set.add((ProperType) bound); + } + } + return set; + } + + /** + * Returns all upper bounds that proper types. + * + * @return all upper bounds that are proper types + */ + public Set findProperUpperBounds() { + LinkedHashSet set = new LinkedHashSet<>(); + for (AbstractType bound : bounds.get(BoundKind.UPPER)) { + if (bound.isProper()) { + set.add((ProperType) bound); + } + } + return set; + } + + /** + * Returns all upper bounds. + * + * @return all upper bounds + */ + public Set upperBounds() { + LinkedHashSet set = new LinkedHashSet<>(); + for (AbstractType bound : bounds.get(BoundKind.UPPER)) { + if (!bound.isUseOfVariable()) { + set.add(bound); + } + } + return set; + } + + /** + * Apply instantiations to all bounds and constraints of this variable. + * + * @return whether any of the bounds changed + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public boolean applyInstantiationsToBounds() { + boolean changed = false; + for (Set boundList : bounds.values()) { + LinkedHashSet newBounds = new LinkedHashSet<>(boundList.size()); + for (AbstractType bound : boundList) { + AbstractType newBound = bound.applyInstantiations(); + if (newBound != bound && !boundList.contains(newBound)) { + changed = true; + } + newBounds.add(newBound); + } + boundList.clear(); + boundList.addAll(newBounds); + } + constraints.applyInstantiations(); + + if (changed && instantiation == null) { + for (AbstractType bound : bounds.get(BoundKind.EQUAL)) { + if (bound.isProper()) { + instantiation = ((ProperType) bound).boxType(); + } + } + } + return changed; + } + + /** + * Return all variables mentioned in a bound against this variable. + * + * @return all variables mentioned in a bound against this variable + */ + public Collection getVariablesMentionedInBounds() { + List mentioned = new ArrayList<>(); + for (Set boundList : bounds.values()) { + for (AbstractType bound : boundList) { + mentioned.addAll(bound.getInferenceVariables()); + } + } + return mentioned; + } + + /** + * Returns the instantiation of this variable. + * + * @return the instantiation of this variable + */ + public ProperType getInstantiation() { + return instantiation; + } + + /** + * Return true if this has an instantiation. + * + * @return true if this has an instantiation + */ + public boolean hasInstantiation() { + return instantiation != null; + } + + /** + * Returns true if any bound mentions a primitive wrapper type. + * + * @return true if any bound mentions a primitive wrapper type + */ + public boolean hasPrimitiveWrapperBound() { + for (Set boundList : bounds.values()) { + for (AbstractType bound : boundList) { + if (bound.isProper() && TypesUtils.isBoxedPrimitive(bound.getJavaType())) { + return true; + } + } + } + return false; + } + + /** + * Returns true if any lower or equal bound is a parameterized type with at least one wildcard as + * a type argument. + * + * @return true if any lower or equal bound is a parameterized type with at least one wildcard for + * a type argument + */ + public boolean hasWildcardParameterizedLowerOrEqualBound() { + for (AbstractType type : bounds.get(BoundKind.EQUAL)) { + if (!type.isUseOfVariable() && type.isWildcardParameterizedType()) { + return true; + } + } + for (AbstractType type : bounds.get(BoundKind.LOWER)) { + if (!type.isUseOfVariable() && type.isWildcardParameterizedType()) { + return true; + } + } + return false; + } + + /** + * Does this bound set contain two bounds of the forms {@code S1 <: var} and {@code S2 <: var}, + * where S1 and S2 have supertypes that are two different parameterizations of the same generic + * class or interface? + * + * @return whether this bound set contain two bounds of the forms {@code S1 <: var} and {@code S2 + * <: var}, where S1 and S2 have supertypes that are two different parameterizations of the + * same generic class or interface + */ + public boolean hasLowerBoundDifferentParam() { + List parameteredTypes = new ArrayList<>(); + for (AbstractType type : bounds.get(BoundKind.LOWER)) { + if (!type.isUseOfVariable() && type.isParameterizedType()) { + parameteredTypes.add(type); + } + } + for (int i = 0; i < parameteredTypes.size(); i++) { + AbstractType s1 = parameteredTypes.get(i); + for (int j = i + 1; j < parameteredTypes.size(); j++) { + AbstractType s2 = parameteredTypes.get(j); + IPair supers = + context.inferenceTypeFactory.getParameterizedSupers(s1, s2); + if (supers == null) { + continue; + } + List s1TypeArgs = supers.first.getTypeArguments(); + List s2TypeArgs = supers.second.getTypeArguments(); + if (!s1TypeArgs.equals(s2TypeArgs)) { + return true; + } + } + } + + return false; + } + + /** + * Returns true if there exists an equal or lower bound against a type, S, such that S is not a + * subtype of {@code G<...>}, but S is a subtype of the raw type {@code |G<...>|}, where {@code G} + * a generic class or interface for which the parameter of this method, {@code t}, is a + * parameterization. + * + * @param t a parameterization of a generic class or interface, {@code G} + * @return true if there exists an equal or lower bound against a type, S, such that S is not a + * subtype of {@code G<...>}, but S is a subtype of the raw type {@code |G<...>|}, where + * {@code G} a generic class or interface for which the parameter of this method, {@code t}, + * is a parameterization. + */ + public boolean hasRawTypeLowerOrEqualBound(AbstractType t) { + for (AbstractType type : bounds.get(BoundKind.LOWER)) { + if (type.isUseOfVariable()) { + continue; + } + AbstractType superTypeOfS = type.asSuper(t.getJavaType()); + if (superTypeOfS != null && superTypeOfS.isRaw()) { + return true; + } + } + + for (AbstractType type : bounds.get(BoundKind.EQUAL)) { + if (type.isUseOfVariable()) { + continue; + } + AbstractType superTypeOfS = type.asSuper(t.getJavaType()); + if (superTypeOfS != null && superTypeOfS.isRaw()) { + return true; + } + } + return false; + } + + /** + * Returns the constraints generated when incorporating a capture bound. See JLS 18.3.2. + * + * @param Ai the captured type argument + * @param Bi the bound of the type variable + * @return constraints generated when incorporating a capture bound + */ + public ConstraintSet getWildcardConstraints(AbstractType Ai, AbstractType Bi) { + ConstraintSet constraintSet = new ConstraintSet(); + + // Only concerned with bounds against proper types or inference types. + List upperBoundsNonVar = new ArrayList<>(); + for (AbstractType bound : bounds.get(VariableBounds.BoundKind.UPPER)) { + if (bound.isProper() || bound.isInferenceType()) { + upperBoundsNonVar.add(bound); + } + } + List lowerBoundsNonVar = new ArrayList<>(); + for (AbstractType bound : bounds.get(VariableBounds.BoundKind.LOWER)) { + if (bound.isProper() || bound.isInferenceType()) { + lowerBoundsNonVar.add(bound); + } + } + + for (AbstractType bound : bounds.get(VariableBounds.BoundKind.EQUAL)) { + if (bound.isProper() || bound.isInferenceType()) { + // var = R implies the bound false + return null; + } + } + + if (Ai.isUnboundWildcard()) { + // R <: var implies the bound false + if (!lowerBoundsNonVar.isEmpty()) { + return null; + } + // var <: R implies the constraint formula + } else if (Ai.isUpperBoundedWildcard()) { + // R <: var implies the bound false + if (!lowerBoundsNonVar.isEmpty()) { + return null; + } + AbstractType T = Ai.getWildcardUpperBound(); + if (Bi.isObject()) { + // If Bi is Object, then var <: R implies the constraint formula + for (AbstractType r : upperBoundsNonVar) { + constraintSet.add(new Typing(T, r, TypeConstraint.Kind.SUBTYPE)); + } + } else if (T.isObject()) { + // If T is Object, then var <: R implies the constraint formula + for (AbstractType r : upperBoundsNonVar) { + constraintSet.add(new Typing(Bi, r, TypeConstraint.Kind.SUBTYPE)); + } + } + // else no constraint + } else { + // Super bounded wildcard + // var <: R implies the constraint formula + for (AbstractType r : upperBoundsNonVar) { + constraintSet.add(new Typing(Bi, r, TypeConstraint.Kind.SUBTYPE)); + } + + // R <: var implies the constraint formula + AbstractType T = Ai.getWildcardLowerBound(); + for (AbstractType r : lowerBoundsNonVar) { + constraintSet.add(new Typing(r, T, TypeConstraint.Kind.SUBTYPE)); + } + } + return constraintSet; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java new file mode 100644 index 00000000000..cd8226b4f72 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java @@ -0,0 +1,310 @@ +package org.checkerframework.framework.util.typeinference8.util; + +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ThrowTree; +import com.sun.source.tree.TryTree; +import com.sun.source.util.TreeScanner; +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.UnionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.TreeUtils; + +/** Util for checked exception constraints. */ +public class CheckedExceptionsUtil { + + /** Don't use. */ + private CheckedExceptionsUtil() {} + + /** + * Returns a list of checked exception types that can be thrown by the lambda. + * + * @param lambda an expression + * @param context inference context + * @return a list of types of checked exceptions that can be thrown by the lambda + */ + public static List thrownCheckedExceptions( + LambdaExpressionTree lambda, Java8InferenceContext context) { + return new CheckedExceptionVisitor(context).scan(lambda, null); + } + + /** + * Helper class for gathering the types of checked exceptions in a lambda. See + * https://docs.oracle.com/javase/specs/jls/se9/html/jls-11.html#jls-11.2.2 + */ + private static class CheckedExceptionVisitor extends TreeScanner, Void> { + + /** the context. */ + private final Java8InferenceContext context; + + /** + * Creates the visitor. + * + * @param context the context + */ + private CheckedExceptionVisitor(Java8InferenceContext context) { + this.context = context; + } + + @Override + public List reduce(List r1, List r2) { + if (r1 == null) { + return r2; + } + if (r2 == null) { + return r1; + } + r1.addAll(r2); + return r1; + } + + @Override + public List visitThrow(ThrowTree node, Void aVoid) { + List result = super.visitThrow(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + TypeMirror type = TreeUtils.typeOf(node); + if (isCheckedException(type, context)) { + result.add(type); + } + return result; + } + + @Override + public List visitMethodInvocation(MethodInvocationTree node, Void aVoid) { + List result = super.visitMethodInvocation(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + for (TypeMirror type : TreeUtils.elementFromUse(node).getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; + } + + @Override + public List visitNewClass(NewClassTree node, Void aVoid) { + List result = super.visitNewClass(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + for (TypeMirror type : TreeUtils.elementFromUse(node).getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; + } + + @Override + public List visitTry(TryTree node, Void aVoid) { + List results = scan(node.getBlock(), aVoid); + if (results == null) { + results = new ArrayList<>(); + } + + if (!results.isEmpty()) { + for (CatchTree catchTree : node.getCatches()) { + // Remove any type that would be caught. + removeAssignable(TreeUtils.typeOf(catchTree.getParameter()), results); + } + } + results.addAll(scan(node.getResources(), aVoid)); + results.addAll(scan(node.getCatches(), aVoid)); + results.addAll(scan(node.getFinallyBlock(), aVoid)); + + return results; + } + + /** + * If any type in {@code thrownExceptionTypes} is assignable to {@code type}, then remove it + * from the list. + * + * @param type a type + * @param thrownExceptionTypes the type of the exceptions + */ + private void removeAssignable(TypeMirror type, List thrownExceptionTypes) { + if (thrownExceptionTypes.isEmpty()) { + return; + } + if (type.getKind() == TypeKind.UNION) { + for (TypeMirror altern : ((UnionType) type).getAlternatives()) { + removeAssignable(altern, thrownExceptionTypes); + } + } else { + for (TypeMirror thrownType : new ArrayList<>(thrownExceptionTypes)) { + if (context.env.getTypeUtils().isAssignable(thrownType, type)) { + thrownExceptionTypes.remove(thrownType); + } + } + } + } + } + + /** + * Returns true iff {@code type} is a checked exception. + * + * @param type at ype to check + * @param context the context + * @return true iff {@code type} is a checked exception + */ + private static boolean isCheckedException(TypeMirror type, Java8InferenceContext context) { + TypeMirror runtimeEx = context.runtimeEx; + return context.env.getTypeUtils().isSubtype(type, runtimeEx); + } + + /** + * Returns a list of checked exception types that can be thrown by the lambda. + * + * @param lambda an expression + * @param context inference context + * @return a list of types of checked exceptions that can be thrown by the lambda + */ + public static List thrownCheckedExceptionsATM( + LambdaExpressionTree lambda, Java8InferenceContext context) { + return new CheckedExceptionATMVisitor(context).scan(lambda, null); + } + + /** + * Helper class for gathering the types of checked exceptions in a lambda. See + * https://docs.oracle.com/javase/specs/jls/se9/html/jls-11.html#jls-11.2.2 + */ + private static class CheckedExceptionATMVisitor + extends TreeScanner, Void> { + + /** The context. */ + private final Java8InferenceContext context; + + /** + * Creates the visitor. + * + * @param context the context + */ + private CheckedExceptionATMVisitor(Java8InferenceContext context) { + this.context = context; + } + + @Override + public List reduce( + List r1, List r2) { + if (r1 == null) { + return r2; + } + if (r2 == null) { + return r1; + } + r1.addAll(r2); + return r1; + } + + @Override + public List visitThrow(ThrowTree node, Void aVoid) { + List result = super.visitThrow(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + AnnotatedTypeMirror type = context.typeFactory.getAnnotatedType(node); + if (isCheckedException(type, context)) { + result.add(type); + } + return result; + } + + @Override + public List visitMethodInvocation(MethodInvocationTree node, Void aVoid) { + List result = super.visitMethodInvocation(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + AnnotatedExecutableType method = context.typeFactory.methodFromUse(node).executableType; + for (AnnotatedTypeMirror type : method.getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; + } + + @Override + public List visitNewClass(NewClassTree node, Void aVoid) { + List result = super.visitNewClass(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + AnnotatedExecutableType method = context.typeFactory.constructorFromUse(node).executableType; + + for (AnnotatedTypeMirror type : method.getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; + } + + @Override + public List visitTry(TryTree node, Void aVoid) { + List results = scan(node.getBlock(), aVoid); + if (results == null) { + results = new ArrayList<>(); + } + + if (!results.isEmpty()) { + for (CatchTree catchTree : node.getCatches()) { + // Remove any type that would be caught. + removeAssignable(TreeUtils.typeOf(catchTree.getParameter()), results); + } + } + results.addAll(scan(node.getResources(), aVoid)); + results.addAll(scan(node.getCatches(), aVoid)); + results.addAll(scan(node.getFinallyBlock(), aVoid)); + + return results; + } + + /** + * If any type in {@code thrownExceptionTypes} is assignable to {@code type}, then remove it + * from the list. + * + * @param type an exception type + * @param thrownExceptionTypes a list of thrown exception types; side-effected by this method + */ + private void removeAssignable(TypeMirror type, List thrownExceptionTypes) { + if (thrownExceptionTypes.isEmpty()) { + return; + } + if (type.getKind() == TypeKind.UNION) { + for (TypeMirror altern : ((UnionType) type).getAlternatives()) { + removeAssignable(altern, thrownExceptionTypes); + } + } else { + for (AnnotatedTypeMirror thrownType : new ArrayList<>(thrownExceptionTypes)) { + if (context.env.getTypeUtils().isAssignable(thrownType.getUnderlyingType(), type)) { + thrownExceptionTypes.remove(thrownType); + } + } + } + } + } + + /** + * Returns true iff {@code type} is a checked exception. + * + * @param type a type to check + * @param context the context + * @return true iff {@code type} is a checked exception + */ + private static boolean isCheckedException( + AnnotatedTypeMirror type, Java8InferenceContext context) { + TypeMirror runtimeEx = context.runtimeEx; + return context.env.getTypeUtils().isSubtype(type.getUnderlyingType(), runtimeEx); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java new file mode 100644 index 00000000000..be468d38568 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java @@ -0,0 +1,21 @@ +package org.checkerframework.framework.util.typeinference8.util; + +import org.checkerframework.framework.util.typeinference8.constraint.Constraint; +import org.checkerframework.framework.util.typeinference8.constraint.ReductionResult; + +/** Exception thrown when the Java types make it so that false is inferred. */ +public class FalseBoundException extends RuntimeException { + + /** serialVersionUID */ + private static final long serialVersionUID = 1; + + /** + * Creates a false bound exception + * + * @param constraint the constraint the was not resolved + * @param result the result of reduction + */ + public FalseBoundException(Constraint constraint, ReductionResult result) { + super(" False bound for: Constraint: " + constraint + " Result: " + result); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java new file mode 100644 index 00000000000..6652415fb6f --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java @@ -0,0 +1,163 @@ +package org.checkerframework.framework.util.typeinference8.util; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.util.typeinference8.InvocationTypeInference; +import org.checkerframework.framework.util.typeinference8.types.InferenceFactory; +import org.checkerframework.framework.util.typeinference8.types.ProperType; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; + +/** + * An object to pass around for use during invocation type inference. One context is created per + * top-level invocation expression. + */ +public class Java8InferenceContext { + + /** Path to the top level expression whose type arguments are inferred. */ + public TreePath pathToExpression; + + /** javax.annotation.processing.ProcessingEnvironment */ + public final ProcessingEnvironment env; + + /** ProperType for java.lang.Object. */ + public final ProperType object; + + /** Invocation type inference object. */ + public final InvocationTypeInference inference; + + /** com.sun.tools.javac.code.Types */ + public final Types types; + + /** javax.lang.model.util.Types */ + public final javax.lang.model.util.Types modelTypes; + + /** The type of class that encloses the top level expression whose type arguments are inferred. */ + public final DeclaredType enclosingType; + + /** + * Store previously created type variable to inference variable maps as a map from invocation + * expression to Theta. + */ + public final Map maps; + + /** Number of non-capture variables in this inference problem. */ + private int variableCount = 1; + + /** Number of capture variables in this inference problem. */ + private int captureVariableCount = 1; + + /** Number of qualifier variables in this inference problem. */ + private int qualifierVarCount = 1; + + /** TypeMirror for java.lang.RuntimeException. */ + public final TypeMirror runtimeEx; + + /** The inference factory. */ + public final InferenceFactory inferenceTypeFactory; + + /** The annotated type factory. */ + public final AnnotatedTypeFactory typeFactory; + + /** There's no way to tell if an element is a parameter of a lambda, so keep track of them. */ + public final Set lambdaParms = new HashSet<>(); + + /** + * Creates a context + * + * @param factory type factory + * @param pathToExpression path to the expression whose type arguments are inferred + * @param inference inference object + */ + public Java8InferenceContext( + AnnotatedTypeFactory factory, TreePath pathToExpression, InvocationTypeInference inference) { + this.typeFactory = factory; + this.pathToExpression = pathToExpression; + this.env = factory.getProcessingEnv(); + this.inference = inference; + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + this.types = Types.instance(javacEnv.getContext()); + this.modelTypes = factory.getProcessingEnv().getTypeUtils(); + ClassTree clazz = TreePathUtil.enclosingClass(pathToExpression); + this.enclosingType = (DeclaredType) TreeUtils.typeOf(clazz); + this.maps = new HashMap<>(); + this.runtimeEx = + TypesUtils.typeFromClass(RuntimeException.class, env.getTypeUtils(), env.getElementUtils()); + this.inferenceTypeFactory = new InferenceFactory(this); + this.object = inferenceTypeFactory.getObject(); + } + + /** + * Returns the next number to use as the id for a non-capture variable. This id is only unique for + * this inference problem. + * + * @return the next number to use as the id for a non-capture variable + */ + public int getNextVariableId() { + return variableCount++; + } + + /** + * Return the next number to use as the id for a capture variable. This id is only unique for this + * inference problem. + * + * @return the next number to use as the id for a capture variable + */ + public int getNextCaptureVariableId() { + return captureVariableCount++; + } + + /** + * Returns the next number to use as the id for a qualifier variable. This id is only unique for + * this inference problem. + * + * @return the next number to use as the id for a qualifier variable + */ + public int getNextQualifierVariableId() { + return qualifierVarCount++; + } + + /** + * Adds the parameters to the list of trees that are lambda parameters. + * + *

          There's no way to tell if a tree is a parameter of a lambda, so keep track of them. + * + * @param parameters list of lambda parameters + */ + public void addLambdaParms(List parameters) { + for (VariableTree tree : parameters) { + lambdaParms.add(TreeUtils.elementFromDeclaration(tree)); + } + } + + /** + * Return whether the {@code expression} is a lambda parameter. + * + * @param expression an expression + * @return whether the {@code expression} is a lambda parameter + */ + public boolean isLambdaParam(ExpressionTree expression) { + Element element = TreeUtils.elementFromTree(expression); + if (element == null || element.getKind() != ElementKind.PARAMETER) { + return false; + } + return lambdaParms.contains((VariableElement) element); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java new file mode 100644 index 00000000000..c6b1a2e77d0 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java @@ -0,0 +1,424 @@ +package org.checkerframework.framework.util.typeinference8.util; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.util.typeinference8.bound.BoundSet; +import org.checkerframework.framework.util.typeinference8.types.AbstractQualifier; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.Dependencies; +import org.checkerframework.framework.util.typeinference8.types.ProperType; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; + +/** + * Resolution finds an instantiation for each variable in a given set of variables. It does this + * using all the bounds on a variable. Because a bound on a variable by be another unresolved + * variable, the order in which the variables must be computed before resolution. If the set of + * variables contains any captured variables, then a different resolution algorthim is used. If a + * set of variables does not contain a captured variable, but the resolution fails, then the + * resolution algorithm for captured variables is used. + * + *

          Resolution is discussed in JLS Section + * 18.4. + * + *

          Entry point is two static methods, {@link #resolveSmallestSet(Set, BoundSet)} and {@link + * #resolve(Variable, BoundSet, Java8InferenceContext)}, which create {@link Resolution} objects + * that actually preform the resolution. + */ +public class Resolution { + + /** + * Instantiates a set of variables, {@code as}. + * + * @param as the set of variables to resolve + * @param boundSet the bound set that includes {@code as} + * @param context Java8InferenceContext + * @return bound set where {@code as} have instantiations + */ + public static BoundSet resolve( + Collection as, BoundSet boundSet, Java8InferenceContext context) { + + // Remove any variables that already have instantiations + List resolvedVars = boundSet.getInstantiatedVariables(); + as.removeAll(resolvedVars); + if (as.isEmpty()) { + return boundSet; + } + // Calculate the dependencies between variables. (A variable depends on another if it is + // included in one of its bounds.) + Dependencies dependencies = boundSet.getDependencies(); + Queue unresolvedVars = new ArrayDeque<>(as); + for (Variable var : as) { + for (Variable dep : dependencies.get(var)) { + if (!unresolvedVars.contains(dep)) { + unresolvedVars.add(dep); + } + } + } + + // Remove any variables that already have instantiations + unresolvedVars.removeAll(resolvedVars); + if (unresolvedVars.isEmpty()) { + return boundSet; + } + + // Resolve the variables + Resolution resolution = new Resolution(context, dependencies); + boundSet = resolution.resolve(boundSet, unresolvedVars); + assert !boundSet.containsFalse(); + return boundSet; + } + + /** + * Instantiates the variable {@code a}. + * + * @param a the variable to resolve + * @param boundSet the bound set that includes {@code a} + * @param context Java8InferenceContext + * @return bound set where {@code a} is instantiated + */ + public static BoundSet resolve(Variable a, BoundSet boundSet, Java8InferenceContext context) { + if (a.getBounds().hasInstantiation()) { + return boundSet; + } + Dependencies dependencies = boundSet.getDependencies(); + + LinkedHashSet unresolvedVars = new LinkedHashSet<>(); + unresolvedVars.add(a); + Resolution resolution = new Resolution(context, dependencies); + boundSet = resolution.resolveSmallestSet(unresolvedVars, boundSet); + assert !boundSet.containsFalse(); + return boundSet; + } + + /** The context. */ + private final Java8InferenceContext context; + + /** The set of dependencies between the variables. */ + private final Dependencies dependencies; + + /** + * Creates a resolution. + * + * @param context the context + * @param dependencies the dependencies + */ + private Resolution(Java8InferenceContext context, Dependencies dependencies) { + this.context = context; + this.dependencies = dependencies; + } + + /** + * Resolve all the variables in {@code unresolvedVars}. + * + * @param boundSet current bound set + * @param unresolvedVars a set of unresolved variables that includes all dependencies + * @return the bounds set with the resolved bounds + */ + private BoundSet resolve(BoundSet boundSet, Queue unresolvedVars) { + List resolvedVars = boundSet.getInstantiatedVariables(); + + while (!unresolvedVars.isEmpty()) { + assert !boundSet.containsFalse(); + + Set smallestDependencySet = getSmallestDependecySet(resolvedVars, unresolvedVars); + + // Resolve the smallest unresolved dependency set. + boundSet = resolveSmallestSet(smallestDependencySet, boundSet); + + resolvedVars = boundSet.getInstantiatedVariables(); + unresolvedVars.removeAll(resolvedVars); + } + return boundSet; + } + + /** + * Returns the smallest set of unresolved variables that includes any variable on which a variable + * in the set depends. + * + * @param resolvedVars variables that have been resolved + * @param unresolvedVars variables that have not been resolved + * @return the smallest set of unresolved variable + */ + private Set getSmallestDependecySet( + List resolvedVars, Queue unresolvedVars) { + Set smallestDependencySet = null; + // This loop is looking for the smallest set of dependencies that have not been resolved. + for (Variable alpha : unresolvedVars) { + Set alphasDependencySet = dependencies.get(alpha); + alphasDependencySet.removeAll(resolvedVars); + + if (smallestDependencySet == null + || alphasDependencySet.size() < smallestDependencySet.size()) { + smallestDependencySet = alphasDependencySet; + } + + if (smallestDependencySet.size() == 1) { + // If the size is 1, then alpha has the smallest possible set of unresolved + // dependencies. + // (A variable is always dependent on itself.) So, stop looking for smaller ones. + break; + } + } + return smallestDependencySet; + } + + /** + * Resolves {@code as} + * + * @param as the smallest set of unresolved variables that includes all any variable on which a + * variable in the set depends + * @param boundSet current bounds set + * @return current bound set + */ + private BoundSet resolveSmallestSet(Set as, BoundSet boundSet) { + assert !boundSet.containsFalse(); + + if (boundSet.containsCapture(as)) { + resolveNoCapturesFirst(new ArrayList<>(as)); + boundSet.getInstantiatedVariables().forEach(as::remove); + // Then resolve the capture variables + return resolveWithCapture(as, boundSet, context); + } else { + BoundSet copy = new BoundSet(boundSet); + // Save the current bounds in case the first attempt at resolution fails. + copy.saveBounds(); + try { + BoundSet resolvedBounds = resolveNoCapture(as, boundSet); + if (!resolvedBounds.containsFalse()) { + return resolvedBounds; + } + } catch (FalseBoundException ex) { + // Try with capture. + } + boundSet = copy; + // If resolveNoCapture fails, then undo all resolved variables from the failed attempt. + boundSet.restore(); + return resolveWithCapture(as, boundSet, context); + } + } + + /** + * Resolves all the non-capture variables in {@code variables}. + * + * @param variables the variables + */ + private void resolveNoCapturesFirst(List variables) { + Variable smallV; + do { + smallV = null; + int smallest = Integer.MAX_VALUE; + for (Variable v : variables) { + v.getBounds().applyInstantiationsToBounds(); + if (v.getBounds().hasInstantiation()) { + variables.remove(v); + // loop again because a new instantiation has been found. + // (Also avoids concurrent modification exception.) + break; + } + if (!v.isCaptureVariable()) { + int size = v.getBounds().getVariablesMentionedInBounds().size(); + if (size < smallest) { + smallest = size; + smallV = v; + } + } + } + if (smallV != null) { + resolveNoCapture(smallV); + variables.remove(smallV); + } + } while (smallV != null); + } + + /** + * Resolves all variables in {@code as} by instantiating each to the greatest lower bound of its + * proper upper bounds. This may fail and resolveWithCapture will need to be used instead. + * + * @param as variables to resolve + * @param boundSet the bound set to use + * @return the resolved bound st + */ + private BoundSet resolveNoCapture(Set as, BoundSet boundSet) { + BoundSet resolvedBoundSet = new BoundSet(context); + for (Variable ai : as) { + assert !ai.getBounds().hasInstantiation(); + resolveNoCapture(ai); + if (!ai.getBounds().hasInstantiation()) { + resolvedBoundSet.addFalse(); + break; + } + } + boundSet.incorporateToFixedPoint(resolvedBoundSet); + return boundSet; + } + + /** + * Resolves {@code ai} by instantiating it to the greatest lower bound of its proper upper bounds. + * + * @param ai variable to resolve + */ + private void resolveNoCapture(Variable ai) { + assert !ai.getBounds().hasInstantiation(); + Set lowerBounds = ai.getBounds().findProperLowerBounds(); + + if (!lowerBounds.isEmpty()) { + ProperType lubProperType = context.inferenceTypeFactory.lub(lowerBounds); + Set qualifierLowerBounds = + ai.getBounds().qualifierBounds.get(BoundKind.LOWER); + if (!qualifierLowerBounds.isEmpty()) { + QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); + Set lubAnnos = AbstractQualifier.lub(qualifierLowerBounds, context); + if (lubProperType.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lubAnnos, lubProperType.getAnnotatedType().getPrimaryAnnotations()); + lubProperType.getAnnotatedType().replaceAnnotations(newLubAnnos); + } else { + + AnnotatedTypeVariable lubTV = (AnnotatedTypeVariable) lubProperType.getAnnotatedType(); + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lubAnnos, lubTV.getLowerBound().getPrimaryAnnotations()); + lubTV.getLowerBound().replaceAnnotations(newLubAnnos); + } + } + ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, lubProperType); + return; + } + + Set upperBounds = ai.getBounds().findProperUpperBounds(); + if (!upperBounds.isEmpty()) { + ProperType ti = null; + boolean useRuntimeEx = false; + for (ProperType liProperType : upperBounds) { + TypeMirror li = liProperType.getJavaType(); + if (ai.getBounds().hasThrowsBound() + && context.env.getTypeUtils().isSubtype(context.runtimeEx, li)) { + useRuntimeEx = true; + } + if (ti == null) { + ti = liProperType; + } else { + ti = (ProperType) context.inferenceTypeFactory.glb(ti, liProperType); + } + } + if (useRuntimeEx) { + ai.getBounds() + .addBound( + VariableBounds.BoundKind.EQUAL, context.inferenceTypeFactory.getRuntimeException()); + } else { + ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, ti); + } + } + } + + /** + * Instantiates the variables in {@code as} by creating fresh type variables using the bounds of + * the variables. + * + * @param as a set of variables to resolve + * @param boundSet the bounds set to use + * @param context the contest + * @return the resolved bound set + */ + private static BoundSet resolveWithCapture( + Set as, BoundSet boundSet, Java8InferenceContext context) { + assert !boundSet.containsFalse(); + boundSet.removeCaptures(as); + BoundSet resolvedBoundSet = new BoundSet(context); + List asList = new ArrayList<>(); + List typeVar = new ArrayList<>(); + List typeArg = new ArrayList<>(); + + for (Variable ai : as) { + ai.getBounds().applyInstantiationsToBounds(); + if (ai.getBounds().hasInstantiation()) { + // If ai is equal to a variable that was resolved previously, + // ai would now have an instantiation. + continue; + } + asList.add(ai); + Set lowerBounds = ai.getBounds().findProperLowerBounds(); + ProperType lowerBound = context.inferenceTypeFactory.lub(lowerBounds); + + Set lowerBoundAnnos; + Set qualifierLowerBounds = + ai.getBounds().qualifierBounds.get(BoundKind.LOWER); + if (!qualifierLowerBounds.isEmpty()) { + QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); + lowerBoundAnnos = AbstractQualifier.lub(qualifierLowerBounds, context); + if (lowerBound != null) { + if (lowerBound.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lowerBoundAnnos, lowerBound.getAnnotatedType().getPrimaryAnnotations()); + lowerBound.getAnnotatedType().replaceAnnotations(newLubAnnos); + lowerBoundAnnos = newLubAnnos; + } else { + AnnotatedTypeVariable lubTV = (AnnotatedTypeVariable) lowerBound.getAnnotatedType(); + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lowerBoundAnnos, lubTV.getLowerBound().getPrimaryAnnotations()); + lubTV.getLowerBound().replaceAnnotations(newLubAnnos); + lowerBoundAnnos = newLubAnnos; + } + } + } else { + lowerBoundAnnos = Collections.emptySet(); + } + + Set upperBounds = ai.getBounds().upperBounds(); + AbstractType upperBound = context.inferenceTypeFactory.glb(upperBounds); + Set upperBoundAnnos; + Set qualifierUpperBounds = + ai.getBounds().qualifierBounds.get(BoundKind.UPPER); + if (!qualifierUpperBounds.isEmpty()) { + upperBoundAnnos = AbstractQualifier.glb(qualifierUpperBounds, context); + if (upperBound != null) { + upperBoundAnnos = + context + .typeFactory + .getQualifierHierarchy() + .greatestLowerBoundsQualifiersOnly( + upperBoundAnnos, upperBound.getAnnotatedType().getPrimaryAnnotations()); + upperBound.getAnnotatedType().replaceAnnotations(upperBoundAnnos); + } + } else { + upperBoundAnnos = Collections.emptySet(); + } + + typeVar.add(ai.getJavaType()); + AbstractType freshTypeVar = + context.inferenceTypeFactory.createFreshTypeVariable( + lowerBound, lowerBoundAnnos, upperBound, upperBoundAnnos); + typeArg.add(freshTypeVar); + } + + List subsTypeArg = + context.inferenceTypeFactory.getSubsTypeArgs(typeVar, typeArg, asList); + + // Create the new bounds. + for (int i = 0; i < asList.size(); i++) { + Variable ai = asList.get(i); + ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, subsTypeArg.get(i)); + } + + boundSet.incorporateToFixedPoint(resolvedBoundSet); + return boundSet; + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java new file mode 100644 index 00000000000..84523f8b073 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java @@ -0,0 +1,49 @@ +package org.checkerframework.framework.util.typeinference8.util; + +import java.util.LinkedHashMap; +import javax.lang.model.type.TypeVariable; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.javacutil.TypesUtils; + +/** A mapping from type variables to inference variables. */ +public class Theta extends LinkedHashMap { + + /** serialVersionUID */ + private static final long serialVersionUID = 42L; + + /** Creates Theta. */ + public Theta() {} + + /** + * Returns the type variable in the key set that is {@link TypesUtils#areSame(TypeVariable, + * TypeVariable)} as {@code typeVariable}. + * + * @param typeVariable a type variable + * @return the type variable in the key set that is {@link TypesUtils#areSame(TypeVariable, + * TypeVariable)} as {@code typeVariable} + */ + private TypeVariable getTypeVariable(TypeVariable typeVariable) { + for (TypeVariable key : keySet()) { + if (TypesUtils.areSame(key, typeVariable)) { + return key; + } + } + return typeVariable; + } + + @Override + public boolean containsKey(Object key) { + if (key instanceof TypeVariable) { + return super.containsKey(getTypeVariable((TypeVariable) key)); + } + return false; + } + + @Override + public Variable get(Object key) { + if (key instanceof TypeVariable) { + return super.get(getTypeVariable((TypeVariable) key)); + } + return super.get(key); + } +} diff --git a/framework/tests/all-systems/BeamCrash2Full.java b/framework/tests/all-systems/BeamCrash2Full.java new file mode 100644 index 00000000000..61aee00eb55 --- /dev/null +++ b/framework/tests/all-systems/BeamCrash2Full.java @@ -0,0 +1,53 @@ +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.SortedSet; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@SuppressWarnings("all") // Just check for crashes. +public class BeamCrash2Full { + + private static void validateGettersHaveConsistentAnnotation( + List descriptors, + final AnnotationPredicates annotationPredicates, + SortedSet gettersWithTheAnnotation) { + for (final PropertyDescriptor descriptor : descriptors) { + throw new IllegalArgumentException( + String.format( + "Property [%s] is marked with contradictory annotations. Found [%s].", + descriptor.getName(), + gettersWithTheAnnotation.stream() + .flatMap( + method -> + Arrays.stream(method.getAnnotations()) + .filter(annotationPredicates.forAnnotation) + .map( + annotation -> + String.format( + "[%s on %s]", + formatAnnotation(annotation), + formatMethodWithClass(method)))) + .collect(Collectors.joining(", ")))); + } + } + + public static String formatAnnotation(Annotation annotation) { + return ""; + } + + public static String formatMethodWithClass(Method input) { + return ""; + } + + static class AnnotationPredicates { + + Predicate forAnnotation; + + AnnotationPredicates(Predicate forAnnotation) { + this.forAnnotation = forAnnotation; + } + } +} diff --git a/framework/tests/all-systems/Crash.java b/framework/tests/all-systems/CaptureConversionCrash.java similarity index 85% rename from framework/tests/all-systems/Crash.java rename to framework/tests/all-systems/CaptureConversionCrash.java index 89ab119340b..3d9c65163de 100644 --- a/framework/tests/all-systems/Crash.java +++ b/framework/tests/all-systems/CaptureConversionCrash.java @@ -1,8 +1,8 @@ package wildcards; -import wildcards.Crash.GenericAnnotatedTypeFactoryCrash; +import wildcards.CaptureConversionCrash.GenericAnnotatedTypeFactoryCrash; -public class Crash> { +public class CaptureConversionCrash> { public abstract static class GenericAnnotatedTypeFactoryCrash< Value extends CFAbstractValue, Store extends CFAbstractStore, diff --git a/framework/tests/all-systems/DiamondLambda.java b/framework/tests/all-systems/DiamondLambda.java new file mode 100644 index 00000000000..e66dfceb910 --- /dev/null +++ b/framework/tests/all-systems/DiamondLambda.java @@ -0,0 +1,9 @@ +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class DiamondLambda { + void foo(Map> map, String sequence) { + Set set = map.computeIfAbsent(sequence, __ -> new HashSet<>()); + } +} diff --git a/framework/tests/all-systems/InferNullType.java b/framework/tests/all-systems/InferNullType.java index 313d818f207..7b3bfb3d1e4 100644 --- a/framework/tests/all-systems/InferNullType.java +++ b/framework/tests/all-systems/InferNullType.java @@ -19,7 +19,7 @@ T toInfer4(T input, S p2) { } void x() { - @SuppressWarnings("nullness:type.argument.type.incompatible") + @SuppressWarnings("nullness:type.arguments.not.inferred") Object m = toInfer(null); Object m2 = toInfer2(null); @@ -27,11 +27,11 @@ void x() { Object m4 = toInfer3(1, null); Object m5 = toInfer3(null, 1); - @SuppressWarnings("nullness:type.argument.type.incompatible") + @SuppressWarnings("nullness:type.arguments.not.inferred") Object m6 = toInfer4(null, null); - @SuppressWarnings("nullness:type.argument.type.incompatible") + @SuppressWarnings("nullness:type.arguments.not.inferred") Object m7 = toInfer4(1, null); - @SuppressWarnings("nullness:type.argument.type.incompatible") + @SuppressWarnings("nullness:type.arguments.not.inferred") Object m8 = toInfer4(null, 1); } } diff --git a/framework/tests/all-systems/Issue1809.java b/framework/tests/all-systems/Issue1809.java index 7d2af47705c..fc3f89d7403 100644 --- a/framework/tests/all-systems/Issue1809.java +++ b/framework/tests/all-systems/Issue1809.java @@ -28,6 +28,9 @@ interface C { interface S {} + // The Checker Framework does not refine the type of Stream#filter based on post conditions of + // the passed function. + @SuppressWarnings({"nullness", "optional"}) private Stream xrefsFor(B b) { return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) .filter(Optional::isPresent) diff --git a/framework/tests/all-systems/Issue1948.java b/framework/tests/all-systems/Issue1948.java index 93615260504..24a453afe88 100644 --- a/framework/tests/all-systems/Issue1948.java +++ b/framework/tests/all-systems/Issue1948.java @@ -17,8 +17,8 @@ public class Issue1948< private Issue1948(MapMaker builder, InternalEntryHelper entryHelper) {} /** Returns a fresh {@link Issue1948} as specified by the given {@code builder}. */ - static Issue1948, ?> create(MapMaker builder) { - return new Issue1948<>(builder, Helper.instance()); + static Issue1948, ?> create(MapMaker builder) { + return new Issue1948<>(builder, Helper.instance()); } interface MyEntry> {} diff --git a/framework/tests/all-systems/Issue2082.java b/framework/tests/all-systems/Issue2082.java index 380667a09ad..6374fb657c3 100644 --- a/framework/tests/all-systems/Issue2082.java +++ b/framework/tests/all-systems/Issue2082.java @@ -4,5 +4,6 @@ import java.util.concurrent.Callable; public class Issue2082 { + @SuppressWarnings("all") // Callable is a raw type. Callable foo = () -> 0; } diff --git a/framework/tests/all-systems/Issue2370.java b/framework/tests/all-systems/Issue2370.java index 5fbc63f3586..ff1cd063e8b 100644 --- a/framework/tests/all-systems/Issue2370.java +++ b/framework/tests/all-systems/Issue2370.java @@ -2,6 +2,8 @@ import java.util.Optional; import java.util.stream.Stream; +// @skip-test this is crashing because inference the new inference isn't used throught the +// framework. @SuppressWarnings("all") public class Issue2370 { private Stream getAction2370s(final State2370 state) { @@ -12,7 +14,7 @@ private Stream getAction2370s(final State2370 state) { .flatMap(actionStream -> actionStream); } - private Stream toStream(final Collection obj) { + private Stream toStream(final Collection obj) { return Optional.ofNullable(obj) .map(Stream::of) .orElseGet(Stream::empty) diff --git a/framework/tests/all-systems/Issue4879.java b/framework/tests/all-systems/Issue4879.java index 4bb71281ff7..d1eb46f8235 100644 --- a/framework/tests/all-systems/Issue4879.java +++ b/framework/tests/all-systems/Issue4879.java @@ -14,6 +14,8 @@ A4879 build() { class Issue4879 { private final class I4879 extends L4879 { + // Any non-top default checker will issue a true postive error. + @SuppressWarnings("argument") private I4879() { super(new A4879.B4879<>().build()); } diff --git a/framework/tests/all-systems/Issue5436.java b/framework/tests/all-systems/Issue5436.java index 0bfaf251819..72329658979 100644 --- a/framework/tests/all-systems/Issue5436.java +++ b/framework/tests/all-systems/Issue5436.java @@ -2,7 +2,7 @@ import java.util.List; import java.util.function.Supplier; -@SuppressWarnings("unchecked") +@SuppressWarnings({"unchecked", "all"}) // just check for crashes. public class Issue5436 { static class BoundedWindow {} diff --git a/framework/tests/all-systems/Issue6319.java b/framework/tests/all-systems/Issue6319.java index eeb4f362b7f..e2453bb9738 100644 --- a/framework/tests/all-systems/Issue6319.java +++ b/framework/tests/all-systems/Issue6319.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.stream.Collectors; +@SuppressWarnings("all") // just check for crashes. class Issue6319 { void f(List> list) { for (Enum value : diff --git a/framework/tests/all-systems/LambdaParams.java b/framework/tests/all-systems/LambdaParams.java new file mode 100644 index 00000000000..5e71bff34ff --- /dev/null +++ b/framework/tests/all-systems/LambdaParams.java @@ -0,0 +1,21 @@ +import java.lang.reflect.Method; +import java.util.function.Function; + +public class LambdaParams { + interface Stream { + Stream map(Function mapper); + } + + static Z identity(Z p) { + throw new RuntimeException(); + } + + static Stream stream(Q[] array) { + throw new RuntimeException(); + } + + static void method() { + Function> mapper = + identity(f -> stream(f.getAnnotations()).map(annotation -> "")); + } +} diff --git a/framework/tests/all-systems/LubRawTypes.java b/framework/tests/all-systems/LubRawTypes.java index d7483160a4c..913d8f013b9 100644 --- a/framework/tests/all-systems/LubRawTypes.java +++ b/framework/tests/all-systems/LubRawTypes.java @@ -1,5 +1,3 @@ -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - @SuppressWarnings("unchecked") public class LubRawTypes { public static boolean flag = false; diff --git a/framework/tests/all-systems/MyMapCrash.java b/framework/tests/all-systems/MyMapCrash.java new file mode 100644 index 00000000000..7eefe0e8b2e --- /dev/null +++ b/framework/tests/all-systems/MyMapCrash.java @@ -0,0 +1,14 @@ +import java.util.Set; + +@SuppressWarnings("all") // Just check for crashes +public class MyMapCrash { + public static class ImmutableSetMultimap { + public ImmutableSetMultimap(MyMapCrash> map) {} + } + + private final class MapSet extends MyMapCrash> {} + + public void asMultimap() { + new ImmutableSetMultimap(new MapSet()); + } +} diff --git a/framework/tests/all-systems/StateMatch.java b/framework/tests/all-systems/StateMatch.java index 6546b6a1831..94bdbdc0e15 100644 --- a/framework/tests/all-systems/StateMatch.java +++ b/framework/tests/all-systems/StateMatch.java @@ -21,6 +21,6 @@ public boolean state_match(Object state) { // the LUB of ATMs with empty sets of qualifiers. match = true; } - return (true); + return true; } } diff --git a/framework/tests/all-systems/UncheckedCrash.java b/framework/tests/all-systems/UncheckedCrash.java new file mode 100644 index 00000000000..433171d6540 --- /dev/null +++ b/framework/tests/all-systems/UncheckedCrash.java @@ -0,0 +1,15 @@ +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + +public class UncheckedCrash { + void method(AnnotationMirror anno, ExecutableElement ele) { + @SuppressWarnings("unchecked") + List values = getElementValue(anno, ele, List.class); + } + + public static T getElementValue( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + throw new RuntimeException(); + } +} diff --git a/framework/tests/all-systems/java8/lambda/Issue436All.java b/framework/tests/all-systems/java8/lambda/Issue436All.java new file mode 100644 index 00000000000..55e595053a9 --- /dev/null +++ b/framework/tests/all-systems/java8/lambda/Issue436All.java @@ -0,0 +1,19 @@ +import static java.util.Arrays.asList; + +import java.util.List; +import java.util.function.Supplier; + +public class Issue436All { + public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) { + // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the + // lambda return for those return statements below + Supplier> supplier = + () -> { + if (makeAll) { + return asList("beer", "peanuts"); + } else { + return asList("cheese", "wine"); + } + }; + } +} diff --git a/framework/tests/all-systems/java8/lambda/Lambda.java b/framework/tests/all-systems/java8/lambda/ManyLambda.java similarity index 92% rename from framework/tests/all-systems/java8/lambda/Lambda.java rename to framework/tests/all-systems/java8/lambda/ManyLambda.java index 384cca6f4ae..7d5a5517f15 100644 --- a/framework/tests/all-systems/java8/lambda/Lambda.java +++ b/framework/tests/all-systems/java8/lambda/ManyLambda.java @@ -21,11 +21,11 @@ interface Noop { void noop(); } -public class Lambda { +public class ManyLambda { public static void consumeStr(String str) {} - Lambda(Consumer consumer) { + ManyLambda(Consumer consumer) { consumer.consume("hello"); } @@ -56,7 +56,8 @@ public static void consumeStr(String str) {} result *= i; } // conditional expression - Consumer consumer = result > 100 ? Lambda::consumeStr : Lambda::consumeStr; + Consumer consumer = + result > 100 ? ManyLambda::consumeStr : ManyLambda::consumeStr; return result; } }; diff --git a/framework/tests/all-systems/java8/memberref/MemberReferences.java b/framework/tests/all-systems/java8/memberref/MemberReferences.java index 190ebaadb98..ba0271d1681 100644 --- a/framework/tests/all-systems/java8/memberref/MemberReferences.java +++ b/framework/tests/all-systems/java8/memberref/MemberReferences.java @@ -1,6 +1,3 @@ -// If conservativeUninferredTypeArguments option is used, then the lines marked -// "TODO: Issue 802", will issue a methodref.inference.unimplemented warning. - interface Supplier { R supply(); } @@ -19,6 +16,7 @@ interface BiFunctionMR { /** super # instMethod */ // SUPER(ReferenceMode.INVOKE, false), +@SuppressWarnings("all") class Super { Object func1(Object o) { @@ -32,7 +30,6 @@ T func2(T o) { class Sub extends Super { void context() { FunctionMR f1 = super::func1; - // TODO: Issue 802: type argument inference FunctionMR f2 = super::func2; // Top level wildcards are ignored when type checking FunctionMR f3 = super::func2; @@ -40,6 +37,7 @@ void context() { } } +@SuppressWarnings("all") class SuperWithArg { void func1(U o) {} @@ -53,6 +51,7 @@ void context() { /** Type # instMethod. */ // UNBOUNDED(ReferenceMode.INVOKE, true), +@SuppressWarnings("all") class Unbound { T func1(T o) { return o; @@ -60,22 +59,19 @@ T func1(T o) { void context() { FunctionMR f1 = String::toString; - // TODO: Issue 802: type argument inference BiFunctionMR f2 = Unbound::func1; - @SuppressWarnings("nullness:type.argument.type.incompatible") BiFunctionMR f3 = Unbound::func1; } } +@SuppressWarnings("all") abstract class UnboundWithArg { abstract U func1(); void context() { - // TODO: Issue 802: type argument inference FunctionMR, String> f1 = UnboundWithArg::func1; FunctionMR, String> f2 = UnboundWithArg::func1; - // TODO: Issue 802: type argument inference FunctionMR, String> f3 = UnboundWithArg::func1; FunctionMR, String> f4 = UnboundWithArg::func1; } @@ -83,13 +79,13 @@ void context() { /** Type # staticMethod. */ // STATIC(ReferenceMode.INVOKE, false), +@SuppressWarnings("all") class Static { static T func1(T o) { return o; } void context() { - // TODO: Issue 802: type argument inference FunctionMR f1 = Static::func1; FunctionMR f2 = Static::func1; } @@ -97,21 +93,21 @@ void context() { /** Expr # instMethod. */ // BOUND(ReferenceMode.INVOKE, false), +@SuppressWarnings("all") class Bound { T func1(T o) { return o; } void context(Bound bound) { - // TODO: Issue 802: type argument inference FunctionMR f1 = bound::func1; - // TODO: Issue 802: type argument inference FunctionMR f2 = this::func1; FunctionMR f3 = this::func1; FunctionMR f4 = this::func1; } } +@SuppressWarnings("all") class BoundWithArg { void func1(U param) {} @@ -123,6 +119,7 @@ void context(BoundWithArg bound) { /** Inner # new. */ // IMPLICIT_INNER(ReferenceMode.NEW, false), +@SuppressWarnings("all") class Outer { void context(Outer other) { Supplier f1 = Inner::new; @@ -131,9 +128,9 @@ void context(Outer other) { class Inner extends Outer {} } +@SuppressWarnings("all") class OuterWithArg { void context() { - // TODO: Issue 802: type argument inference Supplier> f1 = Inner::new; Supplier> f2 = Inner::new; Supplier> f3 = Inner::new; @@ -144,6 +141,7 @@ class Inner extends OuterWithArg {} /** Toplevel # new. */ // TOPLEVEL(ReferenceMode.NEW, false), +@SuppressWarnings("all") class TopLevel { TopLevel() {} @@ -151,19 +149,18 @@ TopLevel(T s) {} void context() { Supplier f1 = TopLevel::new; - // TODO: Issue 802: type argument inference FunctionMR f2 = TopLevel::new; FunctionMR f3 = TopLevel::new; } } +@SuppressWarnings("all") class TopLevelWithArg { TopLevelWithArg() {} TopLevelWithArg(U s) {} void context() { - // TODO: Issue 802: type argument inference Supplier> f1 = TopLevelWithArg::new; Supplier> f2 = TopLevelWithArg::new; FunctionMR> f3 = TopLevelWithArg::new; @@ -173,11 +170,9 @@ void context() { /** ArrayType # new. */ // ARRAY_CTOR(ReferenceMode.NEW, false); +@SuppressWarnings("all") class ArrayType { void context() { - // TODO: Signedness Checker does not default boxed primitives correctly. - // See Issue #797: https://github.com/typetools/checker-framework/issues/797 - @SuppressWarnings({"signedness"}) FunctionMR string = String[]::new; FunctionMR clone = String[]::clone; FunctionMR toString = String[]::toString; diff --git a/framework/tests/all-systems/java8inference/BeamCrash1.java b/framework/tests/all-systems/java8inference/BeamCrash1.java new file mode 100644 index 00000000000..97446ff8818 --- /dev/null +++ b/framework/tests/all-systems/java8inference/BeamCrash1.java @@ -0,0 +1,30 @@ +@SuppressWarnings("unchecked") +public abstract class BeamCrash1 { + + abstract Builder toBuilder(); + + public void via( + Contextful> outputFn, + Contextful>> sinkFn) { + toBuilder().setSinkFn((Contextful) sinkFn).setOutputFn(outputFn); + } + + abstract static class Builder { + + abstract Builder setSinkFn( + Contextful>> sink); + + abstract Builder setOutputFn(Contextful> outputFn); + + abstract Write build(); + } + + public interface Sink {} + + public abstract static class Write {} + + public static final class Contextful { + + public interface Fn {} + } +} diff --git a/framework/tests/all-systems/java8inference/BeamCrash3.java b/framework/tests/all-systems/java8inference/BeamCrash3.java new file mode 100644 index 00000000000..d13fb8e34e8 --- /dev/null +++ b/framework/tests/all-systems/java8inference/BeamCrash3.java @@ -0,0 +1,25 @@ +@SuppressWarnings("all") // Just check for crashes. +public class BeamCrash3 { + + @SuppressWarnings({"unchecked", "rawtypes"}) + private CoderOrFailure inferCoderOrFail(PInput input, PTransform transform) { + return new CoderOrFailure<>(((PTransform) transform).getDefaultOutputCoder(input, this), null); + } + + private static class CoderOrFailure { + public CoderOrFailure(Coder coder, String failure) {} + } + + public interface PInput {} + + public abstract static class Coder {} + + public abstract static class PTransform { + + public Coder getDefaultOutputCoder(InputT input, BeamCrash3 output) { + throw new RuntimeException(); + } + } + + public interface POutput {} +} diff --git a/framework/tests/all-systems/java8inference/BeamCrash4.java b/framework/tests/all-systems/java8inference/BeamCrash4.java new file mode 100644 index 00000000000..d030d2e608c --- /dev/null +++ b/framework/tests/all-systems/java8inference/BeamCrash4.java @@ -0,0 +1,52 @@ +@SuppressWarnings("unchecked") +public class BeamCrash4 { + public abstract static class Write { + abstract static class Builder { + abstract Builder setSinkFn( + Contextful>> sink); + + abstract Builder setOutputFn( + Contextful> outputFn); + + abstract Write build(); + } + + abstract Builder toBuilder(); + + public Write via( + Contextful>> sinkFn) { + return toBuilder() + .setSinkFn((Contextful) sinkFn) + .setOutputFn(fn(BeamCrash4.identity())) + .build(); + } + } + + public interface SerializableFunction { + + OutputT apply(InputT input); + } + + public static SerializableFunction identity() { + return new Identity<>(); + } + + private static class Identity implements SerializableFunction { + @Override + public T apply(T input) { + return input; + } + } + + public interface Sink {} + + public static final class Contextful { + + public interface Fn {} + } + + public static Contextful> fn( + final SerializableFunction fn) { + throw new RuntimeException(); + } +} diff --git a/framework/tests/all-systems/java8inference/Bug1.java b/framework/tests/all-systems/java8inference/Bug1.java index 856f0f85b78..483e0795f00 100644 --- a/framework/tests/all-systems/java8inference/Bug1.java +++ b/framework/tests/all-systems/java8inference/Bug1.java @@ -5,7 +5,6 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug1 { - @SuppressWarnings("type.inference.not.same") public void method1(Map, ? extends B> map) { Map, B> copy = new LinkedHashMap<>(map); for (Map.Entry, B> entry : copy.entrySet()) { diff --git a/framework/tests/all-systems/java8inference/Bug16.java b/framework/tests/all-systems/java8inference/Bug16.java index c1e368d2952..6260616307f 100644 --- a/framework/tests/all-systems/java8inference/Bug16.java +++ b/framework/tests/all-systems/java8inference/Bug16.java @@ -13,6 +13,7 @@ private static class Implementation implements Interface { void test(Interface param) { Interface inverse1 = new Implementation<>(param, this); + Interface inverse1b = new Implementation(param, this); Interface inverse2 = new Implementation<>(param); Interface inverse3 = new Implementation<>(this, 1); } diff --git a/framework/tests/all-systems/java8inference/Bug17.java b/framework/tests/all-systems/java8inference/Bug17.java new file mode 100644 index 00000000000..8c7eec07ab6 --- /dev/null +++ b/framework/tests/all-systems/java8inference/Bug17.java @@ -0,0 +1,88 @@ +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings("all") // Just check for crashes. +public abstract class Bug17 implements Map { + public MapBug17 asMultimap(MapBug17 multimapView) { + if (isEmpty()) { + return MapBug17.of(); + } + MapBug17 result = multimapView; + return (result == null) + ? (multimapView = new MapBug17<>(new SubSubBug17(), size(), null)) + : result; + } + + public static class MapBug17 { + MapBug17(Bug17> map, int size, Comparator valueComparator) {} + + public static MapBug17 of() { + throw new RuntimeException(); + } + } + + abstract static class SubBug17 extends Bug17 {} + + static class MyClass {} + + private class SubSubBug17 extends SubBug17> { + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsKey(Object key) { + return false; + } + + @Override + public boolean containsValue(Object value) { + return false; + } + + @Override + public MyClass get(Object key) { + return null; + } + + @Override + public MyClass put(KK key, MyClass value) { + return null; + } + + @Override + public MyClass remove(Object key) { + return null; + } + + @Override + public void putAll(Map> m) {} + + @Override + public void clear() {} + + @Override + public Set keySet() { + return null; + } + + @Override + public Collection> values() { + return null; + } + + @Override + public Set>> entrySet() { + return null; + } + } +} diff --git a/framework/tests/all-systems/java8inference/CollectorCollect.java b/framework/tests/all-systems/java8inference/CollectorCollect.java new file mode 100644 index 00000000000..8cf91e61438 --- /dev/null +++ b/framework/tests/all-systems/java8inference/CollectorCollect.java @@ -0,0 +1,105 @@ +import java.util.Collection; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.Nullable; + +@SuppressWarnings("all") // just check for crashes. +public class CollectorCollect { + + static + Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + return Collectors.collectingAndThen( + flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(CollectorCollect::checkNotNull), + MultimapBuilder.linkedHashKeys().arrayListValues()::build), + ImmutableListMultimap::copyOf); + } + + static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends MultimapBuilder.Multimap> + Collector flatteningToMultimap( + Function keyFunction, + Function> valueFunction, + Supplier multimapSupplier) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> { + K key = keyFunction.apply(input); + Collection valuesForKey = multimap.get(key); + valueFunction.apply(input).forEachOrdered(valuesForKey::add); + }, + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); + } + + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + public abstract static class MultimapBuilder< + K0 extends @Nullable Object, V0 extends @Nullable Object> { + + public static MultimapBuilderWithKeys<@Nullable Object> linkedHashKeys() { + throw new RuntimeException(); + } + + public abstract Multimap build(); + + public interface ListMultimap + extends Multimap {} + + public interface Multimap { + + Collection get(K key); + + void putAll(Multimap multimap2); + } + + public abstract static class ListMultimapBuilder< + K0 extends @Nullable Object, V0 extends @Nullable Object> + extends MultimapBuilder {} + + public abstract static class MultimapBuilderWithKeys { + + public ListMultimapBuilder arrayListValues() { + throw new RuntimeException(); + } + } + } + + public static class ImmutableListMultimap extends ImmutableMultimap + implements CollectorCollect.MultimapBuilder.ListMultimap { + + public static ImmutableListMultimap copyOf( + CollectorCollect.MultimapBuilder.Multimap multimap) { + throw new RuntimeException(); + } + + @Override + public Collection get(K key) { + return null; + } + + @Override + public void putAll(MultimapBuilder.Multimap multimap2) {} + } + + public abstract static class ImmutableMultimap {} +} diff --git a/framework/tests/all-systems/java8inference/CollectorsToList.java b/framework/tests/all-systems/java8inference/CollectorsToList.java index 3d915e73d34..ea8c8cee5c2 100644 --- a/framework/tests/all-systems/java8inference/CollectorsToList.java +++ b/framework/tests/all-systems/java8inference/CollectorsToList.java @@ -1,8 +1,6 @@ // Test case for issue #979: // https://github.com/typetools/checker-framework/issues/979 -// @skip-test until the bug is fixed - import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -10,6 +8,12 @@ public class CollectorsToList { + // See checker/tests/i18n-formatter/I18nFormatCollectorsToList.java + @SuppressWarnings({ + "i18n:methodref.param", // true postive, see + // checker/tests/i18n-formatter/I18nFormatCollectorsToList.java + "lock:methodref.receiver.bound" + }) void m(List strings) { Stream s = strings.stream(); diff --git a/framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java b/framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java new file mode 100644 index 00000000000..356efce9656 --- /dev/null +++ b/framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java @@ -0,0 +1,57 @@ +import java.util.Comparator; +import java.util.Map; +import java.util.Set; + +public class CrashWithSuperWildcard { + ImmutableList method(Map map) { + return sortKeysByValue(map, Ordering1.natural().reverse()); + } + + private static ImmutableList sortKeysByValue( + Map map, Comparator valueComparator) { + throw new RuntimeException(); + } + + static class ImmutableList {} + + static class Ordering1 implements Comparator { + + public static Ordering1 natural() { + throw new RuntimeException(); + } + + public Ordering1 reverse() { + throw new RuntimeException(); + } + + @Override + public int compare(T o1, T o2) { + return 0; + } + } + + private static ImmutableList sortKeysByValue( + Set map, Comparator valueComparator) { + throw new RuntimeException(); + } + + ImmutableList method2(Set map) { + return sortKeysByValue(map, Ordering2.natural().reverse()); + } + + static class Ordering2 implements Comparator { + + public static > Ordering2 natural() { + throw new RuntimeException(); + } + + public Ordering2 reverse() { + throw new RuntimeException(); + } + + @Override + public int compare(T o1, T o2) { + return 0; + } + } +} diff --git a/framework/tests/all-systems/java8inference/GuavaCrash.java b/framework/tests/all-systems/java8inference/GuavaCrash.java new file mode 100644 index 00000000000..99d6d17c91d --- /dev/null +++ b/framework/tests/all-systems/java8inference/GuavaCrash.java @@ -0,0 +1,125 @@ +import java.lang.reflect.Method; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import org.checkerframework.checker.nullness.qual.Nullable; + +@SuppressWarnings("all") // Just check for crashes. +public class GuavaCrash { + static class ImmutableMap { + public static ImmutableMap copyOf(Map map) { + throw new RuntimeException(); + } + } + + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + Function, ImmutableMap> finisher = ImmutableMap::copyOf; + Supplier> mapFactory = LinkedHashMap::new; + return collectingAndThen( + toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), ImmutableMap::copyOf); + } + + public static > Collector toMap( + Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapFactory) { + throw new RuntimeException(); + } + + public static Collector collectingAndThen( + Collector downstream, Function finisher) { + throw new RuntimeException(); + } + + static class Table {} + + private static < + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + void mergeTables( + Table table, R row, C column, V value, BinaryOperator mergeFunction) { + throw new RuntimeException(); + } + + static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V extends @Nullable Object, + I extends Table> + Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction, + java.util.function.Supplier tableSupplier) { + + return Collector.of( + tableSupplier, + (table, input) -> + mergeTables( + table, + rowFunction.apply(input), + columnFunction.apply(input), + valueFunction.apply(input), + mergeFunction), + (table1, table2) -> { + // for (Table.Cell cell2 : table2.cellSet()) { + // mergeTables( + // table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), + // mergeFunction); + // } + return table1; + }); + } + + void subcrash(Method method, Class p) { + + checkArgument( + !p.isPrimitive(), + "@Subscribe method %s's parameter is %s. " + + "Subscriber methods cannot accept primitives. " + + "Consider changing the parameter to %s.", + method, + p.getName(), + wrap(p).getSimpleName()); + } + + void crash(Method method) { + Class[] parameterTypes = method.getParameterTypes(); + checkArgument( + !parameterTypes[0].isPrimitive(), + "@Subscribe method %s's parameter is %s. " + + "Subscriber methods cannot accept primitives. " + + "Consider changing the parameter to %s.", + method, + parameterTypes[0].getName(), + wrap(parameterTypes[0]).getSimpleName()); + } + + public static Class wrap(Class type) { + throw new RuntimeException(); + } + + public static void checkArgument( + boolean expression, String errorMessageTemplate, @Nullable Object... errorMessageArgs) {} + + public static void difference( + SortedMap left, Map right) { + Comparator comparator = orNaturalOrder(left.comparator()); + } + + static Comparator orNaturalOrder( + Comparator comparator) { + throw new RuntimeException(); + } +} diff --git a/framework/tests/all-systems/java8inference/Issue1313.java b/framework/tests/all-systems/java8inference/Issue1313.java index 3fadb8e28fa..b9b46c1c04e 100644 --- a/framework/tests/all-systems/java8inference/Issue1313.java +++ b/framework/tests/all-systems/java8inference/Issue1313.java @@ -6,7 +6,7 @@ interface MyList1313 extends Iterable {} -@SuppressWarnings({"all", "type.inference.not.same"}) // check for crashes +@SuppressWarnings("all") // check for crashes public class Issue1313 { Stream s; Iterable i = s.collect(toMyList1313()); diff --git a/framework/tests/all-systems/java8inference/Issue1397.java b/framework/tests/all-systems/java8inference/Issue1397.java index 74d879396fd..8ad4572e0cb 100644 --- a/framework/tests/all-systems/java8inference/Issue1397.java +++ b/framework/tests/all-systems/java8inference/Issue1397.java @@ -1,8 +1,6 @@ // Test case for Issue 1397. // https://github.com/typetools/checker-framework/issues/1397 -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - public class Issue1397 { class Box {} diff --git a/framework/tests/all-systems/java8inference/Issue1407.java b/framework/tests/all-systems/java8inference/Issue1407.java index 676235a515e..375845d9bf3 100644 --- a/framework/tests/all-systems/java8inference/Issue1407.java +++ b/framework/tests/all-systems/java8inference/Issue1407.java @@ -1,8 +1,6 @@ // Test case for Issue 1407. // https://github.com/typetools/checker-framework/issues/1407 -// @above-java17-jdk-skip-test TODO: reinstate, false positives may be due to issue #979 - abstract class Issue1407 { abstract T foo(T p1, T p2); diff --git a/framework/tests/all-systems/java8inference/Issue1419.java b/framework/tests/all-systems/java8inference/Issue1419.java index 8cc1b24460a..0bc996530d4 100644 --- a/framework/tests/all-systems/java8inference/Issue1419.java +++ b/framework/tests/all-systems/java8inference/Issue1419.java @@ -8,7 +8,7 @@ class EnumMap> extends Map {} abstract > Map foo(Map map); - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "all"}) // Just check for crashes. Map bar(Map map) { return foo((EnumMap) map); } diff --git a/framework/tests/all-systems/java8inference/Issue1424.java b/framework/tests/all-systems/java8inference/Issue1424.java index 5c9672df489..b79e0dbcec5 100644 --- a/framework/tests/all-systems/java8inference/Issue1424.java +++ b/framework/tests/all-systems/java8inference/Issue1424.java @@ -1,7 +1,7 @@ // Test case for Issue 1424. // https://github.com/typetools/checker-framework/issues/1424 -@SuppressWarnings("unchecked") +@SuppressWarnings({"unchecked", "all"}) // Just check for crashes. abstract class Issue1424 { class Box {} diff --git a/framework/tests/all-systems/java8inference/Issue3036.java b/framework/tests/all-systems/java8inference/Issue3036.java index 0a5eefae745..504f6aa8695 100644 --- a/framework/tests/all-systems/java8inference/Issue3036.java +++ b/framework/tests/all-systems/java8inference/Issue3036.java @@ -30,6 +30,8 @@ private void write(Stream stream) { "keyTag", dsData1.getKeyTag(), "digest", dsData1.getDigest()); + // Any checker where string literals are not top will error here. + @SuppressWarnings("type.arguments.not.inferred") List> dsData = getDsData().stream() .map( diff --git a/framework/tests/all-systems/java8inference/Issue404.java b/framework/tests/all-systems/java8inference/Issue404.java index a0c1b1d9a78..b686bb80991 100644 --- a/framework/tests/all-systems/java8inference/Issue404.java +++ b/framework/tests/all-systems/java8inference/Issue404.java @@ -1,15 +1,9 @@ // Test case that was submitted in Issue 404, but was combined with Issue 979 // https://github.com/typetools/checker-framework/issues/979 - -// @skip-test until the bug is fixed - import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; -// TODO: inference problem with dependencies between multiple methods. -// Eventually, the test should work when executed on >= Java 8. -// @skip-test public final class Issue404 { public Set uniqueTrimmed(final Collection strings) { return strings.stream().map(String::trim).collect(Collectors.toSet()); diff --git a/framework/tests/all-systems/java8inference/Issue6046.java b/framework/tests/all-systems/java8inference/Issue6046.java new file mode 100644 index 00000000000..55481d3e698 --- /dev/null +++ b/framework/tests/all-systems/java8inference/Issue6046.java @@ -0,0 +1,31 @@ +import java.util.Formattable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +@SuppressWarnings("all") +public class Issue6046 { + + public interface Record extends Comparable, Formattable {} + + public interface Result extends List, Formattable {} + + @SuppressWarnings({"unchecked", "nullness:new.array", "index:array.access.unsafe.high.constant"}) + public static + Collector>> intoResultGroups( + Function keyMapper) { + + return Collectors.groupingBy( + keyMapper, + LinkedHashMap::new, + Collector.[], Result>of( + () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); + } + + public static Result result(R record) { + throw new RuntimeException(); + } +} diff --git a/framework/tests/all-systems/java8inference/Issue6346.java b/framework/tests/all-systems/java8inference/Issue6346.java new file mode 100644 index 00000000000..190583104f0 --- /dev/null +++ b/framework/tests/all-systems/java8inference/Issue6346.java @@ -0,0 +1,91 @@ +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class Issue6346 { + + public abstract static class A, Y extends A.Builder> extends B { + + public abstract static class Builder, Y1 extends Builder> + extends B.Builder {} + } + + public abstract static class B, Y2 extends B.Builder> implements C { + + public abstract static class Builder, Y3 extends Builder> + implements C.Builder {} + } + + public interface C extends D { + interface Builder extends D, Cloneable {} + } + + public interface D {} + + abstract static class E, I, O> extends F { + + public abstract static class Builder, I, O> { + public Builder g(Function x, BiConsumer y) { + throw new AssertionError(); + } + + public abstract Builder version(int version); + } + + public static < + L extends A, + B extends A.Builder, + I extends A, + O extends A.Builder> + Builder f(L x, Function> y, Supplier z, BiConsumer z1) { + throw new AssertionError(); + } + } + + public interface G extends D {} + + abstract static class H extends A implements G { + + public List h() { + throw new AssertionError(); + } + + public static final class Builder extends A.Builder implements G { + + private Builder i(I value) { + return this; + } + } + + public static H j() { + throw new AssertionError(); + } + } + + public interface J extends D {} + + public static final class I extends A implements J { + + public long k() { + return 0L; + } + + public static final class Builder extends A.Builder implements J { + + Builder l(long value) { + return this; + } + } + + public static Builder n() { + throw new AssertionError(); + } + } + + abstract static class F> {} + + void f() { + var x = E.f(H.j(), H::h, I::n, H.Builder::i).g(I::k, I.Builder::l); + } +} diff --git a/framework/tests/all-systems/java8inference/Issue953.java b/framework/tests/all-systems/java8inference/Issue953Inference.java similarity index 83% rename from framework/tests/all-systems/java8inference/Issue953.java rename to framework/tests/all-systems/java8inference/Issue953Inference.java index e08b75ef636..677fe719059 100644 --- a/framework/tests/all-systems/java8inference/Issue953.java +++ b/framework/tests/all-systems/java8inference/Issue953Inference.java @@ -1,12 +1,11 @@ // Test case that was submitted in Issue 953, but was combined with Issue 979 // https://github.com/typetools/checker-framework/issues/979 -// @skip-test until the bug is fixed - -import java.util.*; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; -public class Issue953 { +public class Issue953Inference { public static void test() { List initial = new ArrayList<>(); List counts = diff --git a/framework/tests/all-systems/java8inference/IteratorInference.java b/framework/tests/all-systems/java8inference/IteratorInference.java new file mode 100644 index 00000000000..2e8f636e3b3 --- /dev/null +++ b/framework/tests/all-systems/java8inference/IteratorInference.java @@ -0,0 +1,13 @@ +import java.util.Iterator; +import org.checkerframework.checker.nullness.qual.Nullable; + +@SuppressWarnings({"unchecked", "all"}) +public class IteratorInference { + static void concatNoDefensiveCopy(Iterator... inputs) { + for (Iterator input : checkNotNull(inputs)) {} + } + + public static T checkNotNull(T reference) { + return reference; + } +} diff --git a/framework/tests/all-systems/java8inference/MemRefInfere.java b/framework/tests/all-systems/java8inference/MemRefInfere.java index 225c70ae7ef..0ddd3bd9060 100644 --- a/framework/tests/all-systems/java8inference/MemRefInfere.java +++ b/framework/tests/all-systems/java8inference/MemRefInfere.java @@ -13,6 +13,7 @@ public static MemRefInfere copyOf(Map Collector> toImmutableMap( Function keyFunction, Function valueFunction, diff --git a/framework/tests/all-systems/java8inference/MethodRefSuperCrash b/framework/tests/all-systems/java8inference/MethodRefSuperCrash new file mode 100644 index 00000000000..2a21d8ed192 --- /dev/null +++ b/framework/tests/all-systems/java8inference/MethodRefSuperCrash @@ -0,0 +1,26 @@ +import java.util.Collections; +import java.util.List; + +public class MethodRefSuperCrash { + @FunctionalInterface + public interface FuncInter { + R func(R r1, R r2); + } + + public static class MyClass { + public static List mergeLists(List list1, List list2) { + throw new RuntimeException(); + } + } + + public static class SuperClass { + protected SuperClass(FuncInter funcInter, R defaultResult) {} + } + + static class SubClass extends SuperClass> { + + private SubClass() { + super(MyClass::mergeLists, Collections.emptyList()); + } + } +} diff --git a/framework/tests/all-systems/java8inference/Misc.java b/framework/tests/all-systems/java8inference/Misc.java index a780b2058dc..fc4d56efff9 100644 --- a/framework/tests/all-systems/java8inference/Misc.java +++ b/framework/tests/all-systems/java8inference/Misc.java @@ -6,15 +6,15 @@ public E min(E a, E b) { return forward.max(a, b); } - public E min(E a, E b, E c, E... rest) { + public F min(F a, F b, F c, F... rest) { return forward.max(a, b, c, rest); } - public E max(E a, E b) { + public G max(G a, G b) { return forward.min(a, b); } - public E max(E a, E b, E c, E... rest) { + public H max(H a, H b, H c, H... rest) { return forward.min(a, b, c, rest); } } diff --git a/framework/tests/all-systems/java8inference/PrimitiveTarget.java b/framework/tests/all-systems/java8inference/PrimitiveTarget.java new file mode 100644 index 00000000000..71712e1250c --- /dev/null +++ b/framework/tests/all-systems/java8inference/PrimitiveTarget.java @@ -0,0 +1,8 @@ +import java.util.Collections; +import java.util.List; + +public class PrimitiveTarget { + void method(List list) { + long l = Collections.min(list); + } +} diff --git a/framework/tests/all-systems/java8inference/TooManyConstraints.java b/framework/tests/all-systems/java8inference/TooManyConstraints.java new file mode 100644 index 00000000000..e12c49fa6e0 --- /dev/null +++ b/framework/tests/all-systems/java8inference/TooManyConstraints.java @@ -0,0 +1,29 @@ +import java.lang.ref.ReferenceQueue; + +@SuppressWarnings("all") // Just check for crashes. +public class TooManyConstraints { + + static final class ThisClass { + private final ReferenceQueue queue = new ReferenceQueue(); + + public InterfaceB> test(Entry e, V value) { + return new ClassA<>(queue, value, cast(e)); + } + + public EntryC cast(Entry entry) { + throw new RuntimeException(); + } + } + + static final class ClassA> implements InterfaceB { + ClassA(ReferenceQueue queue, V referent, E entry) {} + } + + interface InterfaceB> {} + + static final class EntryC implements EntryB> {} + + interface EntryB> extends Entry {} + + interface Entry> {} +} diff --git a/framework/tests/all-systems/java8inference/UncheckedConversionInference.java b/framework/tests/all-systems/java8inference/UncheckedConversionInference.java new file mode 100644 index 00000000000..8407acb5c51 --- /dev/null +++ b/framework/tests/all-systems/java8inference/UncheckedConversionInference.java @@ -0,0 +1,57 @@ +import java.util.Iterator; +import java.util.Set; +import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class UncheckedConversionInference { + + private static class TransposeTable< + C extends @Nullable Object, R extends @Nullable Object, V extends @Nullable Object> + extends AbstractTable { + + private static final Function, Cell> TRANSPOSE_CELL = + new Function, Cell>() { + @Override + public Cell apply(Cell cell) { + throw new RuntimeException(); + } + }; + final Table original; + + TransposeTable(Table original) { + this.original = original; + } + + @SuppressWarnings("unchecked") + @Override + Iterator> cellIterator() { + return transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); + } + + public static Iterator transform( + Iterator fromIterator, Function function) { + throw new RuntimeException(); + } + + @Override + public Set> cellSet() { + throw new RuntimeException(); + } + } + + abstract static class AbstractTable< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + implements Table { + + abstract Iterator> cellIterator(); + } + + public interface Table< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> { + + Set> cellSet(); + + interface Cell< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> {} + } +} diff --git a/framework/tests/classval/ClassValInferenceTest.java b/framework/tests/classval/ClassValInferenceTest.java index 4017a1bbddb..efa5aa68475 100644 --- a/framework/tests/classval/ClassValInferenceTest.java +++ b/framework/tests/classval/ClassValInferenceTest.java @@ -47,10 +47,7 @@ public > void testGetClass( @ClassBound("java.lang.String[][][][]") Class c4 = arrayMulti.getClass(); @ClassBound("java.lang.String") Class c5 = array[0].getClass(); List list = null; - // TODO: reinstate this line. - // The checker issues an error under JDK 18, probably due to issue #979 - // found : Class> - // @ClassBound("java.util.List") Class c6 = list.getClass(); + @ClassBound("java.util.List") Class c6 = list.getClass(); @ClassBound("java.lang.Number") Class c7 = typeVar.getClass(); @ClassBound("java.util.ArrayList") Class c8 = new ArrayList().getClass(); List wildCardListLB = null; diff --git a/framework/tests/framework/GenericTest9.java b/framework/tests/framework/GenericTest9.java index b83f24c95f7..28f21caacdb 100644 --- a/framework/tests/framework/GenericTest9.java +++ b/framework/tests/framework/GenericTest9.java @@ -13,6 +13,7 @@ interface MyEntry {} void testclass() { // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) C<@Odd Object, MyEntry> c1 = new C<>(); C<@Odd Object, @Odd MyEntry> c2 = new C<>(); } @@ -33,7 +34,7 @@ interface Ordering2 { } void test(Ordering2<@Odd MyEntry> ord, MyEntry e, @Odd MyEntry o) { - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) MyEntry e1 = ord.sort(e); // :: error: (type.argument.type.incompatible) MyEntry e2 = ord.>sort(e); diff --git a/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java index e5541fa3494..63b16651217 100644 --- a/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java +++ b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java @@ -78,14 +78,15 @@ void launder(@NonNull OUTER arg1, @Nullable OUTER arg2) { g.listo = new ArrayList<@NonNull String>(); g.launder("", null); // during this method call null would be added to g.listo */ - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) OUTER_SCOPE_TV osNaked2 = methodD(os1, os2, ""); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) OUTER_SCOPE_TV osAnnoed = methodD(os2, os2, ""); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) String str = methodD2(os2, os1, ""); + // :: error: (type.arguments.not.inferred) OUTER_SCOPE_TV osNaked3 = methodD2(os1, os1, os2); } @@ -112,8 +113,9 @@ void contextF( List l2, List<@H1S1 @H2S2 ? extends @H1Top @H2Top String> l3) { + // :: error: (type.arguments.not.inferred) List lstr1 = methodF(l1, l2); - + // :: error: (type.arguments.not.inferred) List lstr2 = methodF(l3, l2); } diff --git a/framework/tests/lubglb/IntersectionTypes.java b/framework/tests/lubglb/IntersectionTypes.java index 8d4680d6e20..d4c3490c25b 100644 --- a/framework/tests/lubglb/IntersectionTypes.java +++ b/framework/tests/lubglb/IntersectionTypes.java @@ -25,7 +25,7 @@ void foo2(@LubglbF Baz baz2) { void foo3(@LubglbB Baz baz3) { call1(baz3); - // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) call2(baz3); } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java index b683b3862e6..3640e98bc2a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java @@ -1,6 +1,7 @@ package org.checkerframework.javacutil; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.NavigableMap; import java.util.Set; @@ -8,6 +9,7 @@ import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.returnsreceiver.qual.This; import org.checkerframework.dataflow.qual.Pure; /** @@ -27,9 +29,13 @@ public class AnnotationMirrorMap implements Map<@KeyFor("this") AnnotationMirror, V> { /** The actual map to which all work is delegated. */ - private final NavigableMap<@KeyFor("this") AnnotationMirror, V> shadowMap = + // Not final because makeUnmodifiable() can reassign it. + private NavigableMap<@KeyFor("this") AnnotationMirror, V> shadowMap = new TreeMap<>(AnnotationUtils::compareAnnotationMirrors); + /** The canonical unmodifiable empty set. */ + private static AnnotationMirrorMap emptyMap = unmodifiableSet(Collections.emptyMap()); + /** Default constructor. */ public AnnotationMirrorMap() {} @@ -44,6 +50,41 @@ public AnnotationMirrorMap(Map copy) { this.putAll(copy); } + /** + * Returns an unmodifiable AnnotationMirrorSet with the given elements. + * + * @param annos the annotation mirrors that will constitute the new unmodifable set + * @return an unmodifiable AnnotationMirrorSet with the given elements + * @param the type of the values in the map + */ + public static AnnotationMirrorMap unmodifiableSet( + Map annos) { + AnnotationMirrorMap result = new AnnotationMirrorMap<>(annos); + result.makeUnmodifiable(); + return result; + } + + /** + * Returns an empty set. + * + * @return an empty set + * @param the type of the values in the map + */ + @SuppressWarnings("unchecked") + public static AnnotationMirrorMap emptyMap() { + return (AnnotationMirrorMap) emptyMap; + } + + /** + * Make this set unmodifiable. + * + * @return this set + */ + public @This AnnotationMirrorMap makeUnmodifiable() { + shadowMap = Collections.unmodifiableNavigableMap(shadowMap); + return this; + } + @Override public int size() { return shadowMap.size(); diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java index c48165ea4b0..e819b017ee4 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java @@ -78,6 +78,24 @@ private AnnotationUtils() { return name; } + /** + * Returns the fully-qualified name of an annotation as a String. + * + * @param annotation the annotation whose name to return + * @return the fully-qualified name of an annotation as a String + */ + public static final @CanonicalName @Interned String annotationNameInterned( + AnnotationMirror annotation) { + if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { + return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; + } + DeclaredType annoType = annotation.getAnnotationType(); + TypeElement elm = (TypeElement) annoType.asElement(); + @SuppressWarnings("signature:assignment") // JDK needs annotations + @CanonicalName String name = elm.getQualifiedName().toString(); + return name.intern(); + } + /** * Returns the binary name of an annotation as a String. * diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java index d692fb3d342..821071ca62e 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java @@ -6,6 +6,7 @@ import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import java.util.EnumSet; @@ -15,6 +16,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; import org.plumelib.util.IPair; /** @@ -240,38 +242,34 @@ public static IPair enclosingNonParen(TreePath path) { } /** - * Returns the "assignment context" for the leaf of {@code treePath}, which is often the leaf of - * the parent of {@code treePath}. (Does not handle pseudo-assignment of an argument to a - * parameter or a receiver expression to a receiver.) This is not the same as {@code - * org.checkerframework.dataflow.cfg.node.AssignmentContext}, which represents the left-hand side - * rather than the assignment itself. + * Returns the tree representing the context for the poly expression which is the leaf of {@code + * treePath}. The context then can be used to find the target type of the poly expression. Returns + * null if the leaf of {@code treePath} is not a poly expression. * - *

          The assignment context for {@code treePath} is the leaf of its parent, if that leaf is one - * of the following trees: - * - *

            - *
          • AssignmentTree - *
          • CompoundAssignmentTree - *
          • MethodInvocationTree - *
          • NewArrayTree - *
          • NewClassTree - *
          • ReturnTree - *
          • VariableTree - *
          - * - * If the parent is a ConditionalExpressionTree we need to distinguish two cases: If the leaf is - * either the then or else branch of the ConditionalExpressionTree, then recurse on the parent. If - * the leaf is the condition of the ConditionalExpressionTree, then return null to not consider - * this assignment context. - * - *

          If the leaf is a ParenthesizedTree, then recurse on the parent. - * - *

          Otherwise, null is returned. + * @param treePath a path. If the leaf of the path is a poly expression, then its context is + * returned. + * @return the tree representing the context for the poly expression which is the leaf of {@code + * treePath}; or null if the leaf is not a poly expression + */ + public static @Nullable Tree getContextForPolyExpression(TreePath treePath) { + // If a lambda or a method reference is the expression in a type cast, then the type cast is + // the context. If a method or constructor invocation is the expression in a type cast, then + // the invocation has no context. + boolean isLambdaOrMethodRef = + treePath.getLeaf().getKind() == Kind.LAMBDA_EXPRESSION + || treePath.getLeaf().getKind() == Kind.MEMBER_REFERENCE; + return getContextForPolyExpression(treePath, isLambdaOrMethodRef); + } + + /** + * Implementation of {@link #getContextForPolyExpression(TreePath)}. * * @param treePath a path + * @param isLambdaOrMethodRef if the call is getting the context of a lambda or method reference * @return the assignment context as described, {@code null} otherwise */ - public static @Nullable Tree getAssignmentContext(TreePath treePath) { + private static @Nullable Tree getContextForPolyExpression( + TreePath treePath, boolean isLambdaOrMethodRef) { TreePath parentPath = treePath.getParentPath(); if (parentPath == null) { @@ -281,11 +279,22 @@ public static IPair enclosingNonParen(TreePath path) { Tree parent = parentPath.getLeaf(); switch (parent.getKind()) { case ASSIGNMENT: // See below for CompoundAssignmentTree. + case LAMBDA_EXPRESSION: case METHOD_INVOCATION: case NEW_ARRAY: case NEW_CLASS: case RETURN: + return parent; + case TYPE_CAST: + if (isLambdaOrMethodRef) { + return parent; + } else { + return null; + } case VARIABLE: + if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) parent)) { + return null; + } return parent; case CONDITIONAL_EXPRESSION: ConditionalExpressionTree cet = (ConditionalExpressionTree) parent; @@ -297,10 +306,31 @@ public static IPair enclosingNonParen(TreePath path) { return null; } // Otherwise use the context of the ConditionalExpressionTree. - return getAssignmentContext(parentPath); + return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); case PARENTHESIZED: - return getAssignmentContext(parentPath); + return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); default: + if (TreeUtils.isYield(parent)) { + // A yield statement is only legal within a switch expression. Walk up the path to the + // case tree instead of the switch expression tree so the code remains backward + // compatible. + TreePath pathToCase = pathTillOfKind(parentPath, Kind.CASE); + assert pathToCase != null + : "@AssumeAssertion(nullness): yield statements must be enclosed in a CaseTree"; + parentPath = pathToCase.getParentPath(); + parent = parentPath.getLeaf(); + } + if (TreeUtils.isSwitchExpression(parent)) { + @SuppressWarnings("interning:not.interned") // AST node comparison + boolean switchIsLeaf = SwitchExpressionUtils.getExpression(parent) == treePath.getLeaf(); + if (switchIsLeaf) { + // The assignment context for the switch selector expression is simply boolean. + // No point in going on. + return null; + } + // Otherwise use the context of the ConditionalExpressionTree. + return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); + } // 11 Tree.Kinds are CompoundAssignmentTrees, // so use instanceof rather than listing all 11. if (parent instanceof CompoundAssignmentTree) { diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 033b8878dbc..a4fb85b08c5 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -13,6 +13,8 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.InstanceOfTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.LambdaExpressionTree.BodyKind; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.MemberReferenceTree; import com.sun.source.tree.MemberReferenceTree.ReferenceMode; @@ -25,6 +27,7 @@ import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.ParenthesizedTree; import com.sun.source.tree.PrimitiveTypeTree; +import com.sun.source.tree.ReturnTree; import com.sun.source.tree.StatementTree; import com.sun.source.tree.SwitchTree; import com.sun.source.tree.Tree; @@ -35,6 +38,7 @@ import com.sun.source.tree.UnionTypeTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.SimpleTreeVisitor; +import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; @@ -53,6 +57,7 @@ import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind; import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMemberReference; +import com.sun.tools.javac.tree.JCTree.JCMemberReference.OverloadKind; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCNewArray; @@ -82,6 +87,7 @@ import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -931,6 +937,7 @@ public static boolean isSynthetic(MethodTree tree) { * Returns true if the tree is of a diamond type. In contrast to the implementation in TreeInfo, * this version works on Trees. * + * @param tree a tree * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree) */ public static boolean isDiamondTree(Tree tree) { @@ -946,7 +953,30 @@ public static boolean isDiamondTree(Tree tree) { } } - /** Returns true if the tree represents a {@code String} concatenation operation. */ + /** + * Returns the type arguments to the given new class tree. + * + * @param tree a new class tree + * @return the type arguments to the given new class tree + */ + public static List getTypeArgumentsToNewClassTree(NewClassTree tree) { + Tree typeTree = tree.getIdentifier(); + if (typeTree.getKind() == Kind.ANNOTATED_TYPE) { + typeTree = ((AnnotatedTypeTree) typeTree).getUnderlyingType(); + } + + if (typeTree.getKind() == Kind.PARAMETERIZED_TYPE) { + return ((ParameterizedTypeTree) typeTree).getTypeArguments(); + } + return Collections.emptyList(); + } + + /** + * Returns true if the tree represents a {@code String} concatenation operation. + * + * @param tree a tree + * @return true if the tree represents a {@code String} concatenation operation + */ public static boolean isStringConcatenation(Tree tree) { return (tree.getKind() == Tree.Kind.PLUS && TypesUtils.isString(TreeUtils.typeOf(tree))); } @@ -2233,8 +2263,8 @@ public static LiteralTree createLiteral( ProcessingEnvironment processingEnv) { Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); TreeMaker maker = TreeMaker.instance(context); - LiteralTree result = maker.Literal(typeTag, value); - ((JCLiteral) result).type = (Type) typeMirror; + JCLiteral result = maker.Literal(typeTag, value); + result.type = (Type) typeMirror; return result; } @@ -2481,6 +2511,26 @@ public static boolean isEnhancedSwitchStatement(SwitchTree switchTree) { return false; } + /** + * Returns true if the given tree is a switch expression. + * + * @param tree a tree to check + * @return true if the given tree is a switch expression + */ + public static boolean isSwitchExpression(Tree tree) { + return tree.getKind().name().equals("SWITCH_EXPRESSION"); + } + + /** + * Returns true if the given tree is a yield expression. + * + * @param tree a tree to check + * @return true if the given tree is a yield expression + */ + public static boolean isYield(Tree tree) { + return tree.getKind().name().equals("YIELD"); + } + /** * Returns the value (expression) for {@code yieldTree}. * @@ -2655,4 +2705,172 @@ public static JCFieldAccess Select( */ return treeMaker.Select((JCExpression) base, name); } + + /** + * Returns true if {@code tree} is an explicitly typed lambda. + * + *

          An lambda whose formal type parameters have declared types or with no parameters is an + * explicitly typed lambda. (See JLS 15.27.1) + * + * @param tree any kind of tree + * @return true iff {@code tree} is an implicitly typed lambda + */ + public static boolean isExplicitlyTypeLambda(Tree tree) { + return tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION + && ((JCLambda) tree).paramKind == ParameterKind.EXPLICIT; + } + + /** + * Returns all expressions that might be the result of {@code lambda}. + * + * @param lambda a lambda with or without a body + * @return a list of expressions that are returned by {@code lambda} + */ + public static List getReturnedExpressions(LambdaExpressionTree lambda) { + if (lambda.getBodyKind() == BodyKind.EXPRESSION) { + return Collections.singletonList((ExpressionTree) lambda.getBody()); + } + + List returnExpressions = new ArrayList<>(); + TreeScanner scanner = + new TreeScanner() { + @Override + public Void visitReturn(ReturnTree tree, Void o) { + returnExpressions.add(tree.getExpression()); + return super.visitReturn(tree, o); + } + }; + scanner.scan(lambda, null); + return returnExpressions; + } + + /** + * Returns whether or not {@code ref} is an exact method reference. + * + *

          From JLS 15.13.1 "If there is only one possible compile-time declaration with only one + * possible invocation, it is said to be exact." + * + * @param ref a method reference + * @return whether or not {@code ref} is an exact method reference + */ + public static boolean isExactMethodReference(MemberReferenceTree ref) { + // Seems like overloaded means the same thing as inexact. + // overloadKind is set + // com.sun.tools.javac.comp.DeferredAttr.DeferredChecker.visitReference() + // IsExact: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.1-400 + // Treat OverloadKind.ERROR as overloaded. + return ((JCMemberReference) ref).getOverloadKind() == OverloadKind.UNOVERLOADED; + } + + /** + * Returns whether or not {@code expression} is a poly expression as defined in JLS 15.2. + * + * @param expression expression + * @return whether or not {@code expression} is a poly expression + */ + public static boolean isPolyExpression(ExpressionTree expression) { + return !isStandaloneExpression(expression); + } + + /** + * Returns whether or not {@code expression} is a standalone expression as defined in JLS 15.2. + * + * @param expression expression + * @return whether or not {@code expression} is a standalone expression + */ + public static boolean isStandaloneExpression(ExpressionTree expression) { + if (expression instanceof JCTree.JCExpression) { + if (((JCTree.JCExpression) expression).isStandalone()) { + return true; + } + if (expression.getKind() == Tree.Kind.METHOD_INVOCATION) { + // This seems to be a bug in at least Java 11. If a method has type arguments, then it is + // a standalone expression. + return !((MethodInvocationTree) expression).getTypeArguments().isEmpty(); + } + } + return false; + } + + /** + * Was applicability by variable arity invocation necessary to determine the method signature? + * + *

          This isn't the same as {@link ExecutableElement#isVarArgs()}. That method returns true if + * the method accepts a variable number of arguments. This method returns true if the method + * invocation actually used that fact to invoke the method. + * + * @param methodInvocation a method or constructor invocation + * @return whether applicability by variable arity invocation is necessary to determine the method + * signature + */ + public static boolean isVarArgMethodCall(ExpressionTree methodInvocation) { + if (methodInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { + return ((JCMethodInvocation) methodInvocation).varargsElement != null; + } else if (methodInvocation.getKind() == Tree.Kind.NEW_CLASS) { + return ((JCNewClass) methodInvocation).varargsElement != null; + } else { + return false; + } + } + + /** + * Is the tree a reference to a constructor of a generic class whose type argument isn't + * specified? For example, {@code HashSet::new)}. + * + * @param tree may or may not be a {@link MemberReferenceTree} + * @return true if tree is a reference to a constructor of a generic class whose type argument + * isn't specified + */ + public static boolean isDiamondMemberReference(ExpressionTree tree) { + if (tree.getKind() != Tree.Kind.MEMBER_REFERENCE) { + return false; + } + MemberReferenceTree memRef = (MemberReferenceTree) tree; + TypeMirror type = TreeUtils.typeOf(memRef.getQualifierExpression()); + if (memRef.getMode() == ReferenceMode.NEW && type.getKind() == TypeKind.DECLARED) { + // No need to check array::new because the generic arrays can't be created. + TypeElement classElt = (TypeElement) ((Type) type).asElement(); + DeclaredType classTypeMirror = (DeclaredType) classElt.asType(); + return !classTypeMirror.getTypeArguments().isEmpty() + && ((Type) type).getTypeArguments().isEmpty(); + } + return false; + } + + /** + * Return whether {@code tree} is a method reference with a raw type to the left of {@code ::}. + * For example, {@code Class::getName}. + * + * @param tree a tree + * @return whether {@code tree} is a method reference with a raw type to the left of {@code ::} + */ + public static boolean isLikeDiamondMemberReference(ExpressionTree tree) { + if (tree.getKind() != Tree.Kind.MEMBER_REFERENCE) { + return false; + } + MemberReferenceTree memberReferenceTree = (MemberReferenceTree) tree; + if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree).isUnbound()) { + TypeMirror preColonTreeType = typeOf(memberReferenceTree.getQualifierExpression()); + return TypesUtils.isRaw(preColonTreeType); + } + return false; + } + + /** + * Returns whether the method reference tree needs type argument inference. + * + * @param memberReferenceTree a method reference tree + * @return whether the method reference tree needs type argument inference + */ + public static boolean needsTypeArgInference(MemberReferenceTree memberReferenceTree) { + if (isDiamondMemberReference(memberReferenceTree) + || isLikeDiamondMemberReference(memberReferenceTree)) { + return true; + } + + ExecutableElement element = TreeUtils.elementFromUse(memberReferenceTree); + return !element.getTypeParameters().isEmpty() + && (memberReferenceTree.getTypeArguments() == null + || memberReferenceTree.getTypeArguments().isEmpty()); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index c91573c5021..60457e513d6 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -10,6 +10,7 @@ import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Names; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -17,12 +18,14 @@ import java.util.StringJoiner; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.NestingKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; import javax.lang.model.type.IntersectionType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; @@ -1004,6 +1007,7 @@ public static TypeMirror greatestLowerBound( */ private static com.sun.tools.javac.util.List typeMirrorListToTypeList( List typeMirrors) { + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. List typeList = CollectionsPlume.mapList(Type.class::cast, typeMirrors); return com.sun.tools.javac.util.List.from(typeList); } @@ -1117,9 +1121,9 @@ public static TypeMirror substitute( List typeVariables, List typeArgs, ProcessingEnvironment env) { - + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. List newP = CollectionsPlume.mapList(Type.class::cast, typeVariables); - + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. List newT = CollectionsPlume.mapList(Type.class::cast, typeArgs); JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; @@ -1163,6 +1167,45 @@ public static TypeMirror freshTypeVariable(TypeMirror typeMirror, ProcessingEnvi return types.freshTypeVariables(com.sun.tools.javac.util.List.of((Type) typeMirror)).head; } + /** + * Creates a fresh type variable with bounds {@code upper} and {@code lower}. + * + * @param upper the upper bound to use, or if {@code null}, then {@code Object} is the upper bound + * @param lower the lower bound to use, or if {@code null}, then {@code NullType} is the lower + * bound + * @param env processing environment + * @return a fresh type variable + */ + public static TypeMirror freshTypeVariable( + @Nullable TypeMirror upper, @Nullable TypeMirror lower, ProcessingEnvironment env) { + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + Names names = Names.instance(javacEnv.getContext()); + Symtab syms = Symtab.instance(javacEnv.getContext()); + com.sun.tools.javac.util.Name capturedName = names.fromString(""); + WildcardType wildcardType = null; + if (lower != null + && (lower.getKind() == TypeKind.ARRAY + || lower.getKind() == TypeKind.DECLARED + || lower.getKind() == TypeKind.TYPEVAR)) { + wildcardType = env.getTypeUtils().getWildcardType(null, lower); + } else if (upper != null + && (upper.getKind() == TypeKind.ARRAY + || upper.getKind() == TypeKind.DECLARED + || upper.getKind() == TypeKind.TYPEVAR)) { + wildcardType = env.getTypeUtils().getWildcardType(upper, null); + } else { + wildcardType = env.getTypeUtils().getWildcardType(null, null); + } + if (lower == null) { + lower = syms.botType; + } + if (upper == null) { + upper = syms.objectType; + } + return new CapturedType( + capturedName, syms.noSymbol, (Type) upper, (Type) lower, (Type.WildcardType) wildcardType); + } + /** * Returns the list of type variables such that a type variable in the list only references type * variables at a lower index than itself. @@ -1210,6 +1253,127 @@ private static TypeVariable doesNotContainOthers( throw new BugInCF("Not found: %s", StringsPlume.join(",", collection)); } + /** + * This method returns the single abstract method declared by {@code functionalInterfaceType}. + * (The type of this method is referred to as the function type.) + * + * @param functionalInterfaceType a functional interface type + * @param env the processing environment + * @return the single abstract method declared by the type + */ + public static ExecutableElement findFunction( + TypeMirror functionalInterfaceType, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return (ExecutableElement) + javacTypes.findDescriptorSymbol(((Type) functionalInterfaceType).asElement()); + } + + /** + * This method returns the type of the single abstract method declared by {@code + * functionalInterfaceType}. + * + * @param functionalInterfaceType functional interface + * @param env ProcessingEnvironment + * @return the single abstract method declared by the type of the tree + */ + public static ExecutableType findFunctionType( + TypeMirror functionalInterfaceType, ProcessingEnvironment env) { + return (ExecutableType) findFunction(functionalInterfaceType, env).asType(); + } + + /** + * Return whether or not {@code type} is raw. + * + * @param type the type to check + * @return whether or not {@code type} is raw + */ + public static boolean isRaw(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + TypeElement typeelem = (TypeElement) ((DeclaredType) type).asElement(); + DeclaredType declType = (DeclaredType) typeelem.asType(); + return !declType.getTypeArguments().isEmpty() + && ((DeclaredType) type).getTypeArguments().isEmpty(); + } + + /** + * Returns the most specific supertype of {@code type} that is an array, or null if {@code type} + * is not a subtype of an array. + * + * @param type a type + * @param types TypesUtils + * @return the most specific supertype of {@code type} that is an array, or null if {@code type} + * is not a subtype of an array + */ + public static @Nullable TypeMirror getMostSpecificArrayType(TypeMirror type, Types types) { + if (type.getKind() == TypeKind.ARRAY) { + return type; + } else { + for (TypeMirror superType : types.directSupertypes(type)) { + TypeMirror arrayType = getMostSpecificArrayType(superType, types); + if (arrayType != null) { + // Only one of the types can be an array type, so return the first one found. + return arrayType; + } + } + return null; + } + } + + /** + * Returns true if {@code type} is a parameterized type. A declared type is parameterized if it + * has parameters. An array type is parameterized if the inner-most component type has parameters. + * + * @param type type to check + * @return true if {@code type} is a parameterized declared type or array type + */ + public static boolean isParameterizedType(TypeMirror type) { + return ((Type) type).isParameterized(); + } + + /** + * Return true if {@code typeMirror} is a declared type that has at least one wildcard as a type + * argument. + * + * @param typeMirror type to check + * @return true if {@code typeMirror} is a declared type that has at least one wildcard as a type + * argument + */ + public static boolean isWildcardParameterized(TypeMirror typeMirror) { + if (isParameterizedType(typeMirror) && typeMirror.getKind() == TypeKind.DECLARED) { + for (TypeMirror t : ((DeclaredType) typeMirror).getTypeArguments()) { + if (t.getKind() == TypeKind.WILDCARD) { + return true; + } + } + } + return false; + } + + /** + * Creates a wildcard with the given bounds. If {@code lowerBound} is non-null, the {@code + * upperBound} must be {@code null} or {@code Object}. If {@code upperBound} is non-null and not + * {@code Object}, then {@code lowerBound} must be {@code null}; + * + * @param lowerBound the lower bound for the wildcard + * @param upperBound the upper bound for the wildcard + * @param types TypesUtils + * @return a wildcard with the given bounds + */ + public static TypeMirror createWildcard( + TypeMirror lowerBound, TypeMirror upperBound, Types types) { + TypeMirror nonObjectUpperBound = upperBound; + if (isObject(upperBound)) { + nonObjectUpperBound = null; + } + + assert lowerBound == null || nonObjectUpperBound == null; + WildcardType wildcardType = types.getWildcardType(nonObjectUpperBound, lowerBound); + return com.sun.tools.javac.util.List.of((Type) wildcardType).head; + } + /** * Returns true if the type is byte, short, char, Byte, Short, or Character. All other narrowings * require a cast. See JLS 5.1.3. diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java index 87c3d7047be..c380dc4c292 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java @@ -675,6 +675,7 @@ public BinaryTree buildBinary( * @return a NewArrayTree to create a new array with initializers */ public NewArrayTree buildNewArray(TypeMirror componentType, List elems) { + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. List exprs = CollectionsPlume.mapList(JCExpression.class::cast, elems); JCTree.JCNewArray newArray = From 69d52f5f620882da96796c92c83e9ee0b98539cd Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Wed, 3 Jan 2024 11:11:30 -0500 Subject: [PATCH 006/173] Speed up Spotless formatting (#6383) Fixes #6297 We make two key changes: 1. Split formatting of `.java` source files among individual sub-projects, so formatting / checking for each sub-project can run in parallel. 2. Remove exclusion patterns that start in `**`, which are expensive to evaluate. With these changes, `./gradlew spotlessCheck` (which runs in the pre-commit script) runs much faster for me (on my M1 Mac the running time is reduced from ~9.2 seconds before this PR to ~2 seconds after). To test, we add tasks named `printSpotlessTaskInputs` that print which files are being formatted. Using these tasks I confirmed that exactly the same files are being checked and formatted before and after this PR. --- build.gradle | 2210 +++++++++++++++++++++++++------------------------- 1 file changed, 1121 insertions(+), 1089 deletions(-) diff --git a/build.gradle b/build.gradle index f6cd5aff37f..180249ef6c1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,569 +1,601 @@ import de.undercouch.gradle.tasks.download.Download buildscript { - dependencies { - if (JavaVersion.current() >= JavaVersion.VERSION_11) { - // Code formatting; defines targets "spotlessApply" and "spotlessCheck". - // https://github.com/diffplug/spotless/tags ; see tags starting "gradle/" - // Only works on JDK 11+. - classpath 'com.diffplug.spotless:spotless-plugin-gradle:7.0.2' - } + dependencies { + if (JavaVersion.current() >= JavaVersion.VERSION_11) { + // Code formatting; defines targets "spotlessApply" and "spotlessCheck". + // https://github.com/diffplug/spotless/tags ; see tags starting "gradle/" + // Only works on JDK 11+. + classpath 'com.diffplug.spotless:spotless-plugin-gradle:7.0.2' } + } } plugins { - // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow - id 'com.github.johnrengelman.shadow' version '8.1.1' - // https://plugins.gradle.org/plugin/de.undercouch.download - id 'de.undercouch.download' version '5.6.0' - id 'java' - // https://github.com/tbroyer/gradle-errorprone-plugin - id 'net.ltgt.errorprone' version '4.1.0' - // https://docs.gradle.org/current/userguide/eclipse_plugin.html - id 'eclipse' - // To show task list as a tree, run: ./gradlew taskTree - id 'com.dorongold.task-tree' version '4.0.0' + // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow + id 'com.github.johnrengelman.shadow' version '8.1.1' + // https://plugins.gradle.org/plugin/de.undercouch.download + id 'de.undercouch.download' version '5.6.0' + id 'java' + // https://github.com/tbroyer/gradle-errorprone-plugin + id 'net.ltgt.errorprone' version '4.1.0' + // https://docs.gradle.org/current/userguide/eclipse_plugin.html + id 'eclipse' + // To show task list as a tree, run: ./gradlew taskTree + id 'com.dorongold.task-tree' version '4.0.0' } apply plugin: 'de.undercouch.download' // There is another `repositories { ... }` block below; if you change this one, change that one as well. repositories { - maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} - mavenCentral() + maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} + mavenCentral() } def majorVersionToInt(majorVersionString) { - if (majorVersionString.endsWith("-ea")) { - majorVersionString = majorVersionString.substring(0, majorVersionString.length() - 3) - } - return Integer.valueOf(majorVersionString) + if (majorVersionString.endsWith("-ea")) { + majorVersionString = majorVersionString.substring(0, majorVersionString.length() - 3) + } + return Integer.valueOf(majorVersionString) } ext { - release = false - - // On a Java 8 JVM, use error-prone javac and source/target 8. - // On a Java 9+ JVM, use the host javac, default source/target, and required module flags. - isJava8 = JavaVersion.current() == JavaVersion.VERSION_1_8 - - // The int corresponding to the major version of the current JVM. - currentRuntimeJavaVersion = majorVersionToInt(JavaVersion.current().getMajorVersion()) - - // As of 2024-12-24, delombok doesn't yet support JDK 24; see https://projectlombok.org/changelog . - // Keep in sync with check in docs/examples/lombok/Makefile - skipDelombok = currentRuntimeJavaVersion >= 24 - - parentDir = file("${rootDir}/../").absolutePath - - // NO-AFU - // annotationTools = "${parentDir}/annotation-tools" - // afu = "${annotationTools}/annotation-file-utilities" - - jtregHome = "${parentDir}/jtreg" - gitScriptsHome = "${project(':checker').projectDir}/bin-devel/.git-scripts" - plumeScriptsHome = "${project(':checker').projectDir}/bin-devel/.plume-scripts" - htmlToolsHome = "${project(':checker').projectDir}/bin-devel/.html-tools" - doLikeJavacHome = "${project(':checker').projectDir}/bin/.do-like-javac" - - javadocMemberLevel = JavadocMemberLevel.PROTECTED - - // The local git repository, typically in the .git directory, but not for worktrees. - // This value is always overwritten, but Gradle needs the variable to be initialized. - localRepo = '.git' - - versions = [ - autoValue : '1.11.0', - errorprone : '2.36.0', - hashmapUtil : '0.0.1', - junit : '4.13.2', - lombok : '1.18.36', - // plume-util includes a version of reflection-util. When updating ensure the versions are consistent. - plumeUtil : '1.9.0', - reflectionUtil : '1.1.3', - ] + release = false + + // On a Java 8 JVM, use error-prone javac and source/target 8. + // On a Java 9+ JVM, use the host javac, default source/target, and required module flags. + isJava8 = JavaVersion.current() == JavaVersion.VERSION_1_8 + + // The int corresponding to the major version of the current JVM. + currentRuntimeJavaVersion = majorVersionToInt(JavaVersion.current().getMajorVersion()) + + // As of 2024-12-24, delombok doesn't yet support JDK 24; see https://projectlombok.org/changelog . + // Keep in sync with check in docs/examples/lombok/Makefile + skipDelombok = currentRuntimeJavaVersion >= 24 + + parentDir = file("${rootDir}/../").absolutePath + + // NO-AFU + // annotationTools = "${parentDir}/annotation-tools" + // afu = "${annotationTools}/annotation-file-utilities" + + jtregHome = "${parentDir}/jtreg" + gitScriptsHome = "${project(':checker').projectDir}/bin-devel/.git-scripts" + plumeScriptsHome = "${project(':checker').projectDir}/bin-devel/.plume-scripts" + htmlToolsHome = "${project(':checker').projectDir}/bin-devel/.html-tools" + doLikeJavacHome = "${project(':checker').projectDir}/bin/.do-like-javac" + + javadocMemberLevel = JavadocMemberLevel.PROTECTED + + // The local git repository, typically in the .git directory, but not for worktrees. + // This value is always overwritten, but Gradle needs the variable to be initialized. + localRepo = '.git' + + versions = [ + autoValue : '1.11.0', + errorprone : '2.36.0', + hashmapUtil : '0.0.1', + junit : '4.13.2', + lombok : '1.18.36', + // plume-util includes a version of reflection-util. When updating ensure the versions are consistent. + plumeUtil : '1.9.0', + reflectionUtil : '1.1.3', + ] } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } // Keep in sync with check in // framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java . switch (JavaVersion.current()) { - case JavaVersion.VERSION_1_8: - case JavaVersion.VERSION_11: - case JavaVersion.VERSION_17: - case JavaVersion.VERSION_21: - break; // Supported versions - default: - logger.info('The Checker Framework has only been tested with JDK 8, 11, 17, and 21.' + - ' You are using JDK ' + JavaVersion.current().majorVersion + '.'); - break; + case JavaVersion.VERSION_1_8: + case JavaVersion.VERSION_11: + case JavaVersion.VERSION_17: + case JavaVersion.VERSION_21: + break; // Supported versions + default: + logger.info('The Checker Framework has only been tested with JDK 8, 11, 17, and 21.' + + ' You are using JDK ' + JavaVersion.current().majorVersion + '.'); + break; } task setLocalRepo(type:Exec) { - commandLine 'git', 'worktree', 'list' - standardOutput = new ByteArrayOutputStream() - doLast { - String worktreeList = standardOutput.toString() - localRepo = worktreeList.substring(0, worktreeList.indexOf(' ')) + '/.git' - } + commandLine 'git', 'worktree', 'list' + standardOutput = new ByteArrayOutputStream() + doLast { + String worktreeList = standardOutput.toString() + localRepo = worktreeList.substring(0, worktreeList.indexOf(' ')) + '/.git' + } } // No group so it does not show up in the output of `gradlew tasks` task installGitHooks(type: Copy, dependsOn: 'setLocalRepo') { - description = 'Copies git hooks to .git directory' - from files('checker/bin-devel/git.post-merge', 'checker/bin-devel/git.pre-commit') - rename('git\\.(.*)', '$1') - into localRepo + '/hooks' + description = 'Copies git hooks to .git directory' + from files('checker/bin-devel/git.post-merge', 'checker/bin-devel/git.pre-commit') + rename('git\\.(.*)', '$1') + into localRepo + '/hooks' } if (currentRuntimeJavaVersion >= 11) { - apply plugin: 'com.diffplug.spotless' - spotless { - // Resolve the Spotless plugin dependencies from the buildscript repositories rather than the - // project repositories. That way the spotless plugin does not use the locally built version of - // checker-qual as a dependency. Without this, errors like the follow are issued when running - // a spotless task without a locally-built version of checker-qual.jar: - // Could not determine the dependencies of task ':checker-qual:spotlessCheck'. - // > Could not create task ':checker-qual:spotlessJavaCheck'. - // > Could not create task ':checker-qual:spotlessJava'. - // > File signature can only be created for existing regular files, given: - // .../checker-framework/checker-qual/build/libs/checker-qual-3.25.1-SNAPSHOT.jar - predeclareDepsFromBuildscript() + apply plugin: 'com.diffplug.spotless' + spotless { + // Resolve the Spotless plugin dependencies from the buildscript repositories rather than the + // project repositories. That way the spotless plugin does not use the locally built version of + // checker-qual as a dependency. Without this, errors like the follow are issued when running + // a spotless task without a locally-built version of checker-qual.jar: + // Could not determine the dependencies of task ':checker-qual:spotlessCheck'. + // > Could not create task ':checker-qual:spotlessJavaCheck'. + // > Could not create task ':checker-qual:spotlessJava'. + // > File signature can only be created for existing regular files, given: + // .../checker-framework/checker-qual/build/libs/checker-qual-3.25.1-SNAPSHOT.jar + predeclareDepsFromBuildscript() + } + + spotlessPredeclare { + // Put all the formatters that have dependencies here. Without this, errors like the following + // will happen: + // Could not determine the dependencies of task ':spotlessCheck'. + // > Could not create task ':spotlessJavaCheck'. + // > Could not create task ':spotlessJava'. + // > Add a step with [com.google.googlejavaformat:google-java-format:1.15.0] into the `spotlessPredeclare` block in the root project. + java { + googleJavaFormat() } - - spotlessPredeclare { - // Put all the formatters that have dependencies here. Without this, errors like the following - // will happen: - // Could not determine the dependencies of task ':spotlessCheck'. - // > Could not create task ':spotlessJavaCheck'. - // > Could not create task ':spotlessJava'. - // > Add a step with [com.google.googlejavaformat:google-java-format:1.15.0] into the `spotlessPredeclare` block in the root project. - java { - googleJavaFormat() - } - groovyGradle { - greclipse() - } + groovyGradle { + greclipse() } + } } -allprojects { - // Increment the minor version (second number) rather than just the patch - // level (third number) if: - // * any new checkers have been added, or - // * backward-incompatible changes have been made to APIs or elsewhere. - // To make a snapshot release: ./gradlew publish - version '3.43.0-SNAPSHOT' +allprojects { currentProj -> + // Increment the minor version (second number) rather than just the patch + // level (third number) if: + // * any new checkers have been added, or + // * backward-incompatible changes have been made to APIs or elsewhere. + // To make a snapshot release: ./gradlew publish + version '3.43.0-SNAPSHOT' - tasks.withType(JavaCompile).configureEach { - options.fork = true - } + tasks.withType(JavaCompile).configureEach { + options.fork = true + } - apply plugin: 'java' - apply plugin: 'eclipse' - apply plugin: 'com.github.johnrengelman.shadow' - apply plugin: 'de.undercouch.download' - apply plugin: 'net.ltgt.errorprone' + apply plugin: 'java' + apply plugin: 'eclipse' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'de.undercouch.download' + apply plugin: 'net.ltgt.errorprone' - group = 'io.github.eisop' + group = 'io.github.eisop' - // Keep in sync with "repositories { ... }" block above. - repositories { - maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} - mavenCentral() - } + // Keep in sync with "repositories { ... }" block above. + repositories { + maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} + mavenCentral() + } - configurations { - // This is required to run the Checker Framework on JDK 8. - javacJar + configurations { + // This is required to run the Checker Framework on JDK 8. + javacJar - // Holds the combined classpath of all subprojects including the subprojects themselves. - allProjects + // Holds the combined classpath of all subprojects including the subprojects themselves. + allProjects - // Exclude checker-qual dependency added by Error Prone to avoid a circular dependency. - annotationProcessor.exclude group:'org.checkerframework', module:'checker-qual' - } + // Exclude checker-qual dependency added by Error Prone to avoid a circular dependency. + annotationProcessor.exclude group:'org.checkerframework', module:'checker-qual' + } - dependencies { - javacJar group: 'com.google.errorprone', name: 'javac', version: "9+181-r4173-1" + dependencies { + javacJar group: 'com.google.errorprone', name: 'javac', version: "9+181-r4173-1" - errorproneJavac("com.google.errorprone:javac:9+181-r4173-1") + errorproneJavac("com.google.errorprone:javac:9+181-r4173-1") - allProjects subprojects - } + allProjects subprojects + } - eclipse.classpath { - defaultOutputDir = file("build/default") - file.whenMerged { cp -> - cp.entries.forEach { cpe -> - if (cpe instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder) { - cpe.output = cpe.output.replace "bin/", "build/classes/java/" - } - if (cpe instanceof org.gradle.plugins.ide.eclipse.model.Output) { - cpe.path = cpe.path.replace "bin/", "build/" - } - } + eclipse.classpath { + defaultOutputDir = file("build/default") + file.whenMerged { cp -> + cp.entries.forEach { cpe -> + if (cpe instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder) { + cpe.output = cpe.output.replace "bin/", "build/classes/java/" } + if (cpe instanceof org.gradle.plugins.ide.eclipse.model.Output) { + cpe.path = cpe.path.replace "bin/", "build/" + } + } } + } + + ext { + // A list of add-export and add-open arguments to be used when running the Checker Framework. + // Keep this list in sync with the lists in CheckerMain#getExecArguments, + // the sections with labels "javac-jdk11-non-modularized", "maven", and "sbt" in the manual + // and in the checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject + compilerArgsForRunningCF = [ + // These are required in Java 16+ because the --illegal-access option is set to deny + // by default. None of these packages are accessed via reflection, so the module + // only needs to be exported, but not opened. + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + // Required because the Checker Framework reflectively accesses private members in com.sun.tools.javac.comp. + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + ] + } - ext { - // A list of add-export and add-open arguments to be used when running the Checker Framework. - // Keep this list in sync with the lists in CheckerMain#getExecArguments, - // the sections with labels "javac-jdk11-non-modularized", "maven", and "sbt" in the manual - // and in the checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject - compilerArgsForRunningCF = [ - // These are required in Java 16+ because the --illegal-access option is set to deny - // by default. None of these packages are accessed via reflection, so the module - // only needs to be exported, but not opened. - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - // Required because the Checker Framework reflectively accesses private members in com.sun.tools.javac.comp. - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - ] - } + if (currentRuntimeJavaVersion >= 11) { + apply plugin: 'com.diffplug.spotless' + // patterns of files to skip for individual sub-projects + def perProjectDoNotFormat = [ + 'checker': [ + 'bin-devel/.plume-scripts/**', + 'tests/ainfer-*/annotated/*', + 'tests/nullness-javac-errors/*', + 'tests/calledmethods-delomboked/*', + 'tests/build/**', + 'dist/**' + ], + 'dataflow': ['manual/examples/'], + 'framework': [ + 'tests/returnsrcvrdelomboked/*', + 'tests/build/**' + ], + ] + spotless { + // If you add any formatters to this block that require dependencies, then you must also + // add them to spotlessPredeclare block. + + // Always skip formatting of files under build directory + def doNotFormat = ['build/**'] + if (currentProj != project.rootProject) { + if (perProjectDoNotFormat.containsKey(currentProj.name)) { + doNotFormat += perProjectDoNotFormat[currentProj.name] + } + if (currentRuntimeJavaVersion < 16) { + doNotFormat += [ + 'tests/**/java17/', + 'tests/*record*/' + ] + } + // As of 2023-09-24, google-java-format cannot parse Java 21 language features. + // See https://github.com/google/google-java-format/releases. + if (true) { + doNotFormat += ['tests/**/java21/'] + } - if (currentRuntimeJavaVersion >= 11) { - apply plugin: 'com.diffplug.spotless' - spotless { - // If you add any formatters to this block that require dependencies, then you must also - // add them to spotlessPredeclare block. - def doNotFormat = [ - 'checker/bin-devel/.git-scripts/**', - 'checker/bin-devel/.plume-scripts/**', - 'checker/tests/ainfer-*/annotated/*', - 'dataflow/manual/examples/', - '**/nullness-javac-errors/*', - '**/calledmethods-delomboked/*', - '**/returnsreceiverdelomboked/*', - '**/build/**', - '*/dist/**', + if (currentRuntimeJavaVersion < 21) { + doNotFormat += ['tests/**/java21/'] + } + } + + format 'misc', { + // define the files to apply `misc` to + target '*.md', '*.tex', '.gitignore', 'Makefile' + targetExclude doNotFormat + // define the steps to apply to those files + leadingTabsToSpaces(2) + trimTrailingWhitespace() + // endWithNewline() // Don't want to end empty files with a newline + } + + java { + if (currentProj == currentProj.rootProject) { + // format .java files outside of Gradle sub-projects + def targets = [ + 'docs/examples', + 'docs/tutorial', + ] + targets = targets.collectMany { + [ + // must call toString() to convert GString to String + "${it}/**/*.java".toString(), + // Not .ajava files because formatting would remove import statements. ] - if (currentRuntimeJavaVersion < 14) { - doNotFormat += ['**/*record*/'] - } - if (currentRuntimeJavaVersion < 16) { - // TODO: directories should be renamed `-switchexpr` or some such, - // as they only contain examples for switch expressions, which were - // added in Java 14, not Java 17. - doNotFormat += ['**/java17/'] - } - if (currentRuntimeJavaVersion < 21) { - doNotFormat += ['**/java21/'] - } + } + target targets + } else { + // not the root project; format all .java files in the sub-project + target '**/*.java' + } + targetExclude doNotFormat - format 'misc', { - // define the files to apply `misc` to - target '*.md', '*.tex', '.gitignore', 'Makefile' - targetExclude doNotFormat - // define the steps to apply to those files - leadingTabsToSpaces(2) - trimTrailingWhitespace() - // endWithNewline() // Don't want to end empty files with a newline - } + googleJavaFormat()// .aosp() + // importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') + formatAnnotations().addTypeAnnotation("PolyInitialized").addTypeAnnotation("PolyVP").addTypeAnnotation("ReceiverDependentQual") + } - java { - def targets = [ - // add target folders here - 'checker', - 'checker-qual', - 'checker-util', - 'dataflow', - 'docs/examples', - 'docs/tutorial', - 'framework', - 'framework-test', - 'javacutil', - ] - targets = targets.collectMany { - [ - // must call toString() to convert GString to String - "${it}/**/*.java".toString(), - "${it}/**/*.ajava".toString() - ] - } - target targets - targetExclude doNotFormat - - googleJavaFormat()// .aosp() - // importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') - formatAnnotations().addTypeAnnotation("PolyInitialized").addTypeAnnotation("PolyVP").addTypeAnnotation("ReceiverDependentQual") - } + // Only define a groovyGradle task on the root project, for simplicity in setting the target pattern + if (currentProj == currentProj.rootProject) { + groovyGradle { + target '**/*.gradle' + targetExclude doNotFormat + greclipse() // which formatter Spotless should use to format .gradle files. + indentWithSpaces(2) + trimTrailingWhitespace() + // endWithNewline() // Don't want to end empty files with a newline + } + } + + // a useful task for debugging; prints exactly which files are getting formatted by spotless + tasks.register('printSpotlessTaskInputs') { + doLast { + project.tasks.forEach { task -> + if (task.name.contains('spotless')) { + println "Inputs for task '${task.name}':" - groovyGradle { - target '**/*.gradle' - targetExclude doNotFormat - greclipse() // which formatter Spotless should use to format .gradle files. - leadingTabsToSpaces(4) - trimTrailingWhitespace() - // endWithNewline() // Don't want to end empty files with a newline + task.inputs.files.each { inputFile -> + println " Input: $inputFile" + } } + } } + } } + } + + test { + minHeapSize = "256m" // initial heap size + maxHeapSize = "4g" // maximum heap size + } - test { - minHeapSize = "256m" // initial heap size - maxHeapSize = "4g" // maximum heap size + // configurations.errorprone is no longer resolvable, work around this. + def errorproneProcessorCustom = configurations.resolvable("errorproneProcessorCustom") { + extendsFrom(configurations.errorprone) + } + + // After all the tasks have been created, modify some of them. + afterEvaluate { + configurations { + checkerFatJar { + canBeConsumed = false + canBeResolved = true + } } - // configurations.errorprone is no longer resolvable, work around this. - def errorproneProcessorCustom = configurations.resolvable("errorproneProcessorCustom") { - extendsFrom(configurations.errorprone) + dependencies { + checkerFatJar(project(path: ':checker', configuration: 'fatJar')) } - // After all the tasks have been created, modify some of them. - afterEvaluate { - configurations { - checkerFatJar { - canBeConsumed = false - canBeResolved = true - } - } + // Add the fat checker.jar to the classpath of every Javadoc task. This allows Javadoc in + // any module to reference classes in any other module. + // Also, build and use ManualTaglet as a taglet. + tasks.withType(Javadoc) { + // Similar test in framework-test/build.gradle + def tagletVersion = isJava8 ? 'tagletJdk8' : 'taglet' - dependencies { - checkerFatJar(project(path: ':checker', configuration: 'fatJar')) - } + dependsOn(':checker:shadowJar') + dependsOn(":framework-test:${tagletVersion}Classes") - // Add the fat checker.jar to the classpath of every Javadoc task. This allows Javadoc in - // any module to reference classes in any other module. - // Also, build and use ManualTaglet as a taglet. - tasks.withType(Javadoc) { - // Similar test in framework-test/build.gradle - def tagletVersion = isJava8 ? 'tagletJdk8' : 'taglet' - - dependsOn(':checker:shadowJar') - dependsOn(":framework-test:${tagletVersion}Classes") - - doFirst { - options.encoding = 'UTF-8' - if (!name.equals('javadocDoclintAll')) { - options.memberLevel = javadocMemberLevel - } - classpath += configurations.getByName('checkerFatJar').asFileTree - if (isJava8) { - classpath += configurations.javacJar - } - options.taglets 'org.checkerframework.taglet.ManualTaglet' - options.tagletPath(project(':framework-test').sourceSets."${tagletVersion}".output.classesDirs.getFiles() as File[]) - - // This file is looked for by Javadoc. - file("${destinationDir}/resources/fonts/").mkdirs() - ant.touch(file: "${destinationDir}/resources/fonts/dejavu.css") - - if (!isJava8) { - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', true) - } - - // "-Xwerror" requires Javadoc everywhere. Currently, CI jobs require Javadoc only - // on changed lines. Enable -Xwerror in the future when all Javadoc exists. - // options.addBooleanOption('Xwerror', true) - options.addStringOption('Xmaxwarns', '99999') - } + doFirst { + options.encoding = 'UTF-8' + if (!name.equals('javadocDoclintAll')) { + options.memberLevel = javadocMemberLevel + } + classpath += configurations.getByName('checkerFatJar').asFileTree + if (isJava8) { + classpath += configurations.javacJar + } + options.taglets 'org.checkerframework.taglet.ManualTaglet' + options.tagletPath(project(':framework-test').sourceSets."${tagletVersion}".output.classesDirs.getFiles() as File[]) + + // This file is looked for by Javadoc. + file("${destinationDir}/resources/fonts/").mkdirs() + ant.touch(file: "${destinationDir}/resources/fonts/dejavu.css") + + if (!isJava8) { + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', true) } - // Add standard javac options - tasks.withType(JavaCompile) { compilationTask -> - dependsOn(':installGitHooks') - String useJdkCompilerProp = project.getProperties().get('useJdkCompiler') - int useJdkCompiler - if (useJdkCompilerProp == null) { - // If the property is not given, use the same version as the runtime. - useJdkCompiler = currentRuntimeJavaVersion - } else { - useJdkCompiler = majorVersionToInt(useJdkCompilerProp) - boolean useToolchains = (currentRuntimeJavaVersion != useJdkCompiler) - if (!isJava8 && useToolchains) { - // This uses the requested Java compiler to compile all code. - // CI test test-cftests-junit-jdk21 runs the JUnit tests on the different JDK versions, - // to ensure there is no version mismatch between compiled-against javac APIs and runtime APIs. - // https://docs.gradle.org/current/userguide/toolchains.html - // This property is final on Java 8, so don't set it then. - javaCompiler = javaToolchains.compilerFor { - languageVersion = JavaLanguageVersion.of(useJdkCompiler) - } - } - } - - // Sorting is commented out because it disables incremental compilation. - // Uncomment when needed. - // // Put source files in deterministic order, for debugging. - // compilationTask.source = compilationTask.source.sort() - - // This test is for whether the Checker Framework supports (runs under) Java 8. - // Currently, the Checker Framework does support Java 8. - if (true) { - // Using `options.release.set(8)` here leads to compilation - // errors such as "package com.sun.source.tree does not exist". - sourceCompatibility = 8 - targetCompatibility = 8 - // Because the target is 8, all of the public compiler classes are accessible, so - // --add-exports are not required (nor are they allowed with target 8). See - // https://openjdk.org/jeps/247 for details on compiling for older versions. - } else { - // This makes the class files Java 11, and then the Checker Framework would not run under Java 8. - options.release.set(11) - options.compilerArgs += [ - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - ] - // This is equivalent to writing "exports jdk.compiler/... to ALL-UNNAMED" in the - // module-info.java of jdk.compiler, so corresponding --add-opens are only required for - // reflective access to private members. - // - // From https://openjdk.org/jeps/261, Section titled: "Breaking encapsulation" - // "The effect of each instance [of --add-exports] is to add a qualified export of the - // named package from the source module to the target module. This is, essentially, a - // command-line form of an exports clause in a module declaration[...]. - // [...] - // The --add-exports option enables access to the public types of a specified package. - // It is sometimes necessary to go further and enable access to all non-public elements - // via the setAccessible method of the core reflection API. The --add-opens option can - // be used, at run time, to do this." - } - - options.failOnError = true - options.deprecation = true - // -options: To not get a warning about missing bootstrap classpath (when using Java 9 and `-source 8`). - // -fallthrough: Don't check fallthroughs. Instead, use Error Prone. Its - // warnings are suppressible with a "// fall through" comment. - // -classfile: classgraph jar file and https://bugs.openjdk.org/browse/JDK-8190452 - String lint = '-Xlint:-options,-fallthrough,-classfile' - // Java 8 uses the Error Prone javac, not what is requested with useJdkCompiler. - // So there is no need to set additional lint options. - if (!isJava8) { - if (useJdkCompiler >= 21) { - // TODO: Ignore this-escape for now, we may want to review and suppress each one later. - lint +=',-this-escape' - } - if (useJdkCompiler >= 23) { - // TODO: Ignore dangling-doc-comments for now, we may want to fix them later. - lint +=',-dangling-doc-comments' - } - } - - options.compilerArgs += [ - '-g', - '-Werror', - lint, - '-Xlint', - ] - - options.encoding = 'UTF-8' - options.fork = true - if (isJava8) { - options.forkOptions.jvmArgs += [ - "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() - ] - } + // "-Xwerror" requires Javadoc everywhere. Currently, CI jobs require Javadoc only + // on changed lines. Enable -Xwerror in the future when all Javadoc exists. + // options.addBooleanOption('Xwerror', true) + options.addStringOption('Xmaxwarns', '99999') + } + } - // Error Prone depends on checker-qual.jar, so don't run it on that project to avoid a circular dependency. - if ((compilationTask.name.equals('compileJava') || compilationTask.name.equals('compileTestJava')) && !project.name.startsWith('checker-qual')) { - // Error Prone must be available in the annotation processor path - options.annotationProcessorPath = errorproneProcessorCustom.get() - // Enable Error Prone - options.errorprone.enabled = (useJdkCompiler >= 17) && (useJdkCompiler <= 23) - options.errorprone.disableWarningsInGeneratedCode = true - options.errorprone.errorproneArgs = [ - // Many compiler classes are interned. - '-Xep:ReferenceEquality:OFF', - // These might be worth fixing. - '-Xep:DefaultCharset:OFF', - // Not useful to suggest Splitter; maybe clean up. - '-Xep:StringSplitter:OFF', - // Too broad, rejects seemingly-correct code. - '-Xep:EqualsGetClass:OFF', - // Not a real problem - '-Xep:MixedMutabilityReturnType:OFF', - // Don't want to add a dependency to ErrorProne. - '-Xep:AnnotateFormatMethod:OFF', - // Warns for every use of "@checker_framework.manual" - '-Xep:InvalidBlockTag:OFF', - // Recommends writing @InlineMe which is an Error-Prone-specific annotation - '-Xep:InlineMeSuggester:OFF', - // Recommends writing @CanIgnoreReturnValue which is an Error-Prone-specific annotation. - // It would be great if Error Prone recognized the @This annotation. - '-Xep:CanIgnoreReturnValueSuggester:OFF', - // Should be turned off when using the Checker Framework. - '-Xep:ExtendsObject:OFF', - // For Visitors it is convenient to just pass a Void parameter. - '-Xep:VoidUsed:OFF', - // -Werror halts the build if Error Prone issues a warning, which ensures that - // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) - // stops as soon as it issues one warning, rather than outputting them all. - // https://github.com/google/error-prone/issues/436 - '-Werror', - ] - if (!isJava8) { - // Options needed for Error Prone on Java 16+, but don't hurt on Java 9+ - options.forkOptions.jvmArgs += [ - '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - '--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - ] - } - } else { - options.errorprone.enabled = false - } + // Add standard javac options + tasks.withType(JavaCompile) { compilationTask -> + dependsOn(':installGitHooks') + String useJdkCompilerProp = project.getProperties().get('useJdkCompiler') + int useJdkCompiler + if (useJdkCompilerProp == null) { + // If the property is not given, use the same version as the runtime. + useJdkCompiler = currentRuntimeJavaVersion + } else { + useJdkCompiler = majorVersionToInt(useJdkCompilerProp) + boolean useToolchains = (currentRuntimeJavaVersion != useJdkCompiler) + if (!isJava8 && useToolchains) { + // This uses the requested Java compiler to compile all code. + // CI test test-cftests-junit-jdk21 runs the JUnit tests on the different JDK versions, + // to ensure there is no version mismatch between compiled-against javac APIs and runtime APIs. + // https://docs.gradle.org/current/userguide/toolchains.html + // This property is final on Java 8, so don't set it then. + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(useJdkCompiler) + } } - } // end afterEvaluate + } + + // Sorting is commented out because it disables incremental compilation. + // Uncomment when needed. + // // Put source files in deterministic order, for debugging. + // compilationTask.source = compilationTask.source.sort() + + // This test is for whether the Checker Framework supports (runs under) Java 8. + // Currently, the Checker Framework does support Java 8. + if (true) { + // Using `options.release.set(8)` here leads to compilation + // errors such as "package com.sun.source.tree does not exist". + sourceCompatibility = 8 + targetCompatibility = 8 + // Because the target is 8, all of the public compiler classes are accessible, so + // --add-exports are not required (nor are they allowed with target 8). See + // https://openjdk.org/jeps/247 for details on compiling for older versions. + } else { + // This makes the class files Java 11, and then the Checker Framework would not run under Java 8. + options.release.set(11) + options.compilerArgs += [ + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + ] + // This is equivalent to writing "exports jdk.compiler/... to ALL-UNNAMED" in the + // module-info.java of jdk.compiler, so corresponding --add-opens are only required for + // reflective access to private members. + // + // From https://openjdk.org/jeps/261, Section titled: "Breaking encapsulation" + // "The effect of each instance [of --add-exports] is to add a qualified export of the + // named package from the source module to the target module. This is, essentially, a + // command-line form of an exports clause in a module declaration[...]. + // [...] + // The --add-exports option enables access to the public types of a specified package. + // It is sometimes necessary to go further and enable access to all non-public elements + // via the setAccessible method of the core reflection API. The --add-opens option can + // be used, at run time, to do this." + } + + options.failOnError = true + options.deprecation = true + // -options: To not get a warning about missing bootstrap classpath (when using Java 9 and `-source 8`). + // -fallthrough: Don't check fallthroughs. Instead, use Error Prone. Its + // warnings are suppressible with a "// fall through" comment. + // -classfile: classgraph jar file and https://bugs.openjdk.org/browse/JDK-8190452 + String lint = '-Xlint:-options,-fallthrough,-classfile' + // Java 8 uses the Error Prone javac, not what is requested with useJdkCompiler. + // So there is no need to set additional lint options. + if (!isJava8) { + if (useJdkCompiler >= 21) { + // TODO: Ignore this-escape for now, we may want to review and suppress each one later. + lint +=',-this-escape' + } + if (useJdkCompiler >= 23) { + // TODO: Ignore dangling-doc-comments for now, we may want to fix them later. + lint +=',-dangling-doc-comments' + } + } + + options.compilerArgs += [ + '-g', + '-Werror', + lint, + '-Xlint', + ] + + options.encoding = 'UTF-8' + options.fork = true + if (isJava8) { + options.forkOptions.jvmArgs += [ + "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() + ] + } + + // Error Prone depends on checker-qual.jar, so don't run it on that project to avoid a circular dependency. + if ((compilationTask.name.equals('compileJava') || compilationTask.name.equals('compileTestJava')) && !project.name.startsWith('checker-qual')) { + // Error Prone must be available in the annotation processor path + options.annotationProcessorPath = errorproneProcessorCustom.get() + // Enable Error Prone + options.errorprone.enabled = (useJdkCompiler >= 17) && (useJdkCompiler <= 23) + options.errorprone.disableWarningsInGeneratedCode = true + options.errorprone.errorproneArgs = [ + // Many compiler classes are interned. + '-Xep:ReferenceEquality:OFF', + // These might be worth fixing. + '-Xep:DefaultCharset:OFF', + // Not useful to suggest Splitter; maybe clean up. + '-Xep:StringSplitter:OFF', + // Too broad, rejects seemingly-correct code. + '-Xep:EqualsGetClass:OFF', + // Not a real problem + '-Xep:MixedMutabilityReturnType:OFF', + // Don't want to add a dependency to ErrorProne. + '-Xep:AnnotateFormatMethod:OFF', + // Warns for every use of "@checker_framework.manual" + '-Xep:InvalidBlockTag:OFF', + // Recommends writing @InlineMe which is an Error-Prone-specific annotation + '-Xep:InlineMeSuggester:OFF', + // Recommends writing @CanIgnoreReturnValue which is an Error-Prone-specific annotation. + // It would be great if Error Prone recognized the @This annotation. + '-Xep:CanIgnoreReturnValueSuggester:OFF', + // Should be turned off when using the Checker Framework. + '-Xep:ExtendsObject:OFF', + // For Visitors it is convenient to just pass a Void parameter. + '-Xep:VoidUsed:OFF', + // -Werror halts the build if Error Prone issues a warning, which ensures that + // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) + // stops as soon as it issues one warning, rather than outputting them all. + // https://github.com/google/error-prone/issues/436 + '-Werror', + ] + if (!isJava8) { + // Options needed for Error Prone on Java 16+, but don't hurt on Java 9+ + options.forkOptions.jvmArgs += [ + '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + '--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + ] + } + } else { + options.errorprone.enabled = false + } + } + } // end afterEvaluate } // end allProjects task version(group: 'Documentation') { - description = 'Print Checker Framework version' - doLast { - println version - } + description = 'Print Checker Framework version' + doLast { + println version + } } /** @@ -576,83 +608,83 @@ task version(group: 'Documentation') { * @param args list of arguments to pass to the checker */ def createCheckTypeTask(projectName, taskName, checker, args = []) { - project("${projectName}").tasks.create(name: "check${taskName}", type: JavaCompile, dependsOn: ':checker:shadowJar') { - description = "Run the ${taskName} Checker on the main sources." - group = 'Verification' - // Always run the task. - outputs.upToDateWhen { false } - source = project("${projectName}").sourceSets.main.java - classpath = files(project("${projectName}").compileJava.classpath,project(':checker-qual').sourceSets.main.output) - destinationDirectory = file("${buildDir}") - - options.annotationProcessorPath = files(project(':checker').tasks.shadowJar.archiveFile) - options.compilerArgs += [ - '-processor', - "${checker}", - '-proc:only', - '-Xlint:-processing', - '-Xmaxerrs', - '10000', - '-Xmaxwarns', - '10000', - '-ArequirePrefixInWarningSuppressions', - '-AwarnUnneededSuppressions', - '-AwarnRedundantAnnotations', - '-AnoJreVersionCheck', - ] - options.compilerArgs += args - options.forkOptions.jvmArgs += ['-Xmx2g'] + project("${projectName}").tasks.create(name: "check${taskName}", type: JavaCompile, dependsOn: ':checker:shadowJar') { + description = "Run the ${taskName} Checker on the main sources." + group = 'Verification' + // Always run the task. + outputs.upToDateWhen { false } + source = project("${projectName}").sourceSets.main.java + classpath = files(project("${projectName}").compileJava.classpath,project(':checker-qual').sourceSets.main.output) + destinationDirectory = file("${buildDir}") + + options.annotationProcessorPath = files(project(':checker').tasks.shadowJar.archiveFile) + options.compilerArgs += [ + '-processor', + "${checker}", + '-proc:only', + '-Xlint:-processing', + '-Xmaxerrs', + '10000', + '-Xmaxwarns', + '10000', + '-ArequirePrefixInWarningSuppressions', + '-AwarnUnneededSuppressions', + '-AwarnRedundantAnnotations', + '-AnoJreVersionCheck', + ] + options.compilerArgs += args + options.forkOptions.jvmArgs += ['-Xmx2g'] - if (isJava8) { - options.compilerArgs += [ - '-source', - '8', - '-target', - '8' - ] - } else { - options.fork = true - options.forkOptions.jvmArgs += compilerArgsForRunningCF - } + if (isJava8) { + options.compilerArgs += [ + '-source', + '8', + '-target', + '8' + ] + } else { + options.fork = true + options.forkOptions.jvmArgs += compilerArgsForRunningCF } + } } task htmlValidate(type: Exec, group: 'Format') { - description = 'Validate that HTML files are well-formed' - executable 'html5validator' - args = [ - '--ignore', - '/api/', - '/build/', - '/docs/manual/manual.html', - '/docs/manual/plume-bib/docs/index.html', - '/checker/jdk/nullness/src/java/lang/ref/package.html' - ] + description = 'Validate that HTML files are well-formed' + executable 'html5validator' + args = [ + '--ignore', + '/api/', + '/build/', + '/docs/manual/manual.html', + '/docs/manual/plume-bib/docs/index.html', + '/checker/jdk/nullness/src/java/lang/ref/package.html' + ] } def javadocDirs = [ - project(':checker').sourceSets.main.allJava, - project(':checker').sourceSets.test.allJava, - project(':checker-qual').sourceSets.main.allJava, - project(':checker-util').sourceSets.main.allJava, - project(':checker-util').sourceSets.test.allJava, - project(':dataflow').sourceSets.main.allJava, - project(':dataflow').sourceSets.test.allJava, - project(':framework').sourceSets.main.allJava, - project(':framework').sourceSets.test.allJava, - project(':framework-test').sourceSets.main.allJava, - project(':framework-test').sourceSets.test.allJava, - project(':javacutil').sourceSets.main.allJava + project(':checker').sourceSets.main.allJava, + project(':checker').sourceSets.test.allJava, + project(':checker-qual').sourceSets.main.allJava, + project(':checker-util').sourceSets.main.allJava, + project(':checker-util').sourceSets.test.allJava, + project(':dataflow').sourceSets.main.allJava, + project(':dataflow').sourceSets.test.allJava, + project(':framework').sourceSets.main.allJava, + project(':framework').sourceSets.test.allJava, + project(':framework-test').sourceSets.main.allJava, + project(':framework-test').sourceSets.test.allJava, + project(':javacutil').sourceSets.main.allJava ] def requireJavadocDirs = javadocDirs project(':checker').afterEvaluate { - requireJavadocDirs += project(':checker').sourceSets.testannotations.allJava + requireJavadocDirs += project(':checker').sourceSets.testannotations.allJava } project(':framework').afterEvaluate { - requireJavadocDirs += project(':framework').sourceSets.testannotations.allJava + requireJavadocDirs += project(':framework').sourceSets.testannotations.allJava } // `gradle allJavadoc` builds the Javadoc for all modules in `docs/api`. @@ -662,57 +694,57 @@ project(':framework').afterEvaluate { // To make javadoc for only one subproject, run `./gradlew javadoc` // in the subproject or `./gradlew :checker:javadoc` at the top level. task allJavadoc(type: Javadoc, group: 'Documentation') { - description = 'Generates API documentation that includes all the modules.' - dependsOn(':checker:shadowJar', 'getPlumeScripts', 'getHtmlTools') - destinationDir = file("${rootDir}/docs/api") - source javadocDirs - - def injected = project.objects.newInstance(InjectedExecOps) - - doFirst { - source( - project(':framework-test').sourceSets."${isJava8 ? 'tagletJdk8' : 'taglet'}".allJava - ) - } - - classpath = configurations.allProjects - if (isJava8) { - classpath += configurations.javacJar + description = 'Generates API documentation that includes all the modules.' + dependsOn(':checker:shadowJar', 'getPlumeScripts', 'getHtmlTools') + destinationDir = file("${rootDir}/docs/api") + source javadocDirs + + def injected = project.objects.newInstance(InjectedExecOps) + + doFirst { + source( + project(':framework-test').sourceSets."${isJava8 ? 'tagletJdk8' : 'taglet'}".allJava + ) + } + + classpath = configurations.allProjects + if (isJava8) { + classpath += configurations.javacJar + } + doLast { + copy { + from 'docs/logo/Checkmark/CFCheckmark_favicon.png' + rename('CFCheckmark_favicon.png', 'favicon-checkerframework.png') + into "${rootDir}/docs/api" } - doLast { - copy { - from 'docs/logo/Checkmark/CFCheckmark_favicon.png' - rename('CFCheckmark_favicon.png', 'favicon-checkerframework.png') - into "${rootDir}/docs/api" - } - injected.execOps.exec { - workingDir file("${rootDir}/docs/api") - executable "${htmlToolsHome}/html-add-favicon" - args += [ - '.', - 'favicon-checkerframework.png' - ] - } + injected.execOps.exec { + workingDir file("${rootDir}/docs/api") + executable "${htmlToolsHome}/html-add-favicon" + args += [ + '.', + 'favicon-checkerframework.png' + ] } + } } // See documentation for allJavadoc task. javadoc.dependsOn(allJavadoc) configurations { - requireJavadoc + requireJavadoc } dependencies { - requireJavadoc 'org.plumelib:require-javadoc:1.0.9' + requireJavadoc 'org.plumelib:require-javadoc:1.0.9' } task requireJavadoc(type: JavaExec, group: 'Documentation') { - description = 'Ensures that Javadoc documentation exists in source code.' - mainClass = 'org.plumelib.javadoc.RequireJavadoc' - classpath = configurations.requireJavadoc - // Convert each srcDir to its absolute path and flatten the list - args requireJavadocDirs.collect { it.srcDirs*.absolutePath }.flatten() + description = 'Ensures that Javadoc documentation exists in source code.' + mainClass = 'org.plumelib.javadoc.RequireJavadoc' + classpath = configurations.requireJavadoc + // Convert each srcDir to its absolute path and flatten the list + args requireJavadocDirs.collect { it.srcDirs*.absolutePath }.flatten() } @@ -725,66 +757,66 @@ task requireJavadoc(type: JavaExec, group: 'Documentation') { * @return the new task */ def createJavadocTask(taskName, taskDescription, memberLevel) { - tasks.create(name: taskName, type: Javadoc) { - description = taskDescription - destinationDir = file("${rootDir}/docs/tmpapi") - destinationDir.mkdirs() - subprojects.forEach { - if (!it.name.startsWith('checker-qual-android')) { - source += it.sourceSets.main.allJava - } - } + tasks.create(name: taskName, type: Javadoc) { + description = taskDescription + destinationDir = file("${rootDir}/docs/tmpapi") + destinationDir.mkdirs() + subprojects.forEach { + if (!it.name.startsWith('checker-qual-android')) { + source += it.sourceSets.main.allJava + } + } - classpath = configurations.allProjects + classpath = configurations.allProjects - destinationDir.deleteDir() - options.memberLevel = memberLevel - options.addBooleanOption('Xdoclint:all', true) - options.addStringOption('Xmaxwarns', '99999') + destinationDir.deleteDir() + options.memberLevel = memberLevel + options.addBooleanOption('Xdoclint:all', true) + options.addStringOption('Xmaxwarns', '99999') - // options.addStringOption('skip', 'ClassNotToCheck|OtherClass') - } + // options.addStringOption('skip', 'ClassNotToCheck|OtherClass') + } } createJavadocTask('javadocDoclintAll', 'Runs javadoc with -Xdoclint:all option.', JavadocMemberLevel.PRIVATE) task manual(group: 'Documentation') { - description = 'Build the manual' - def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - workingDir = file('docs/manual') - commandLine 'make', 'all' - } + description = 'Build the manual' + def injected = project.objects.newInstance(InjectedExecOps) + doLast { + injected.execOps.exec { + workingDir = file('docs/manual') + commandLine 'make', 'all' } + } } // No group so it does not show up in the output of `gradlew tasks` task downloadJtreg(type: Download) { - description = 'Downloads and unpacks jtreg.' - onlyIf { !(new File("${jtregHome}/lib/jtreg.jar").exists()) } - // src 'https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz' - // If ci.adoptopenjdk.net is down, use this copy. - // src 'https://checkerframework.org/jtreg-4.2.0-tip.tar.gz' - // dest new File(buildDir, 'jtreg-4.2.0-tip.tar.gz') - // src 'https://builds.shipilev.net/jtreg/jtreg4.2-b16.zip' - src 'https://builds.shipilev.net/jtreg/jtreg-7.5+1.zip' - dest new File(buildDir, 'jtreg.zip') - overwrite true - retries 3 - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - copy { - // Use 'tarTree' when downloading a .tar.gz file - from zipTree(dest) - into "${jtregHome}/.." - } - injected.execOps.exec { - commandLine('chmod', '+x', "${jtregHome}/bin/jtdiff", "${jtregHome}/bin/jtreg") - } + description = 'Downloads and unpacks jtreg.' + onlyIf { !(new File("${jtregHome}/lib/jtreg.jar").exists()) } + // src 'https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz' + // If ci.adoptopenjdk.net is down, use this copy. + // src 'https://checkerframework.org/jtreg-4.2.0-tip.tar.gz' + // dest new File(buildDir, 'jtreg-4.2.0-tip.tar.gz') + // src 'https://builds.shipilev.net/jtreg/jtreg4.2-b16.zip' + src 'https://builds.shipilev.net/jtreg/jtreg-7.5+1.zip' + dest new File(buildDir, 'jtreg.zip') + overwrite true + retries 3 + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + copy { + // Use 'tarTree' when downloading a .tar.gz file + from zipTree(dest) + into "${jtregHome}/.." } + injected.execOps.exec { + commandLine('chmod', '+x', "${jtregHome}/bin/jtdiff", "${jtregHome}/bin/jtreg") + } + } } /** @@ -796,22 +828,22 @@ task downloadJtreg(type: Download) { * @param extraArgs any extra arguments to pass to git */ void clone(url, directory, ignoreError, extraArgs = []){ - def injected = project.objects.newInstance(InjectedExecOps) - injected.execOps.exec { - workingDir file("${directory}/../") - executable 'git' - args = [ - 'clone', - '-q', - '--filter=blob:none', - url, - file(directory).toPath().last() - ] - args += extraArgs - ignoreExitValue = ignoreError - } - // TODO: not sure this does what it is supposed to. - // timeout = Duration.ofSeconds(60) + def injected = project.objects.newInstance(InjectedExecOps) + injected.execOps.exec { + workingDir file("${directory}/../") + executable 'git' + args = [ + 'clone', + '-q', + '--filter=blob:none', + url, + file(directory).toPath().last() + ] + args += extraArgs + ignoreExitValue = ignoreError + } + // TODO: not sure this does what it is supposed to. + // timeout = Duration.ofSeconds(60) } /** @@ -825,39 +857,39 @@ void clone(url, directory, ignoreError, extraArgs = []){ * @param extraArgs arguments to pass to the git command */ def createCloneTask(taskName, url, directory, extraArgs = []) { - tasks.create(name: taskName) { - description = "Obtain or update ${url}" + tasks.create(name: taskName) { + description = "Obtain or update ${url}" - // Always run. - outputs.upToDateWhen { false } + // Always run. + outputs.upToDateWhen { false } - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - if (file(directory).exists()) { - injected.execOps.exec { - workingDir file(directory) - executable 'git' - args = ['pull', '-q'] - ignoreExitValue = true - } - // TODO: not sure this does what it is supposed to. - // timeout = Duration.ofSeconds(60) - } else { - try { - clone(url, directory, true, extraArgs) - } catch (Throwable t) { - println "Exception while cloning ${url}" - t.printStackTrace() - } - if (!file(directory).exists()) { - println "Cloning failed, will try again in 1 minute: clone(${url}, ${directory}, true, ${extraArgs})" - sleep(60000) // wait 1 minute, then try again - clone(url, directory, false, extraArgs) - } - } + doLast { + if (file(directory).exists()) { + injected.execOps.exec { + workingDir file(directory) + executable 'git' + args = ['pull', '-q'] + ignoreExitValue = true + } + // TODO: not sure this does what it is supposed to. + // timeout = Duration.ofSeconds(60) + } else { + try { + clone(url, directory, true, extraArgs) + } catch (Throwable t) { + println "Exception while cloning ${url}" + t.printStackTrace() + } + if (!file(directory).exists()) { + println "Cloning failed, will try again in 1 minute: clone(${url}, ${directory}, true, ${extraArgs})" + sleep(60000) // wait 1 minute, then try again + clone(url, directory, false, extraArgs) } + } } + } } @@ -869,407 +901,407 @@ createCloneTask('getDoLikeJavac', 'https://github.com/opprop/do-like-javac.git', // No group so it does not show up in the output of `gradlew tasks` task pythonIsInstalled(type: Exec) { - description = 'Check that the python3 executable is installed.' - executable = 'python3' - args '--version' + description = 'Check that the python3 executable is installed.' + executable = 'python3' + args '--version' } task tags { - group = 'Emacs' - description = 'Create Emacs TAGS table' + group = 'Emacs' + description = 'Create Emacs TAGS table' - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - commandLine 'etags', '-i', 'checker/TAGS', '-i', 'checker-qual/TAGS', '-i', 'checker-util/TAGS', '-i', 'dataflow/TAGS', '-i', 'framework/TAGS', '-i', 'framework-test/TAGS', '-i', 'javacutil/TAGS', '-i', 'docs/manual/TAGS' - } - injected.execOps.exec { - commandLine 'make', '-C', 'docs/manual', 'tags' - } + doLast { + injected.execOps.exec { + commandLine 'etags', '-i', 'checker/TAGS', '-i', 'checker-qual/TAGS', '-i', 'checker-util/TAGS', '-i', 'dataflow/TAGS', '-i', 'framework/TAGS', '-i', 'framework-test/TAGS', '-i', 'javacutil/TAGS', '-i', 'docs/manual/TAGS' } + injected.execOps.exec { + commandLine 'make', '-C', 'docs/manual', 'tags' + } + } } subprojects { - configurations { - errorprone - annotatedGuava + configurations { + errorprone + annotatedGuava + } + + dependencies { + // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core + // If you update this: + // * Temporarily comment out "-Werror" elsewhere in this file + // * Repeatedly run `./gradlew clean compileJava` and fix all errors + // * Uncomment "-Werror" + if (currentRuntimeJavaVersion >= 17) { + errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: versions.errorprone + } else { + // EP 2.31.0 is the last release that works on Java < 17. + errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: '2.31.0' } - dependencies { - // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core - // If you update this: - // * Temporarily comment out "-Werror" elsewhere in this file - // * Repeatedly run `./gradlew clean compileJava` and fix all errors - // * Uncomment "-Werror" - if (currentRuntimeJavaVersion >= 17) { - errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: versions.errorprone - } else { - // EP 2.31.0 is the last release that works on Java < 17. - errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: '2.31.0' - } + // TODO: it's a bug that annotatedlib:guava requires the error_prone_annotations dependency. + annotatedGuava "com.google.errorprone:error_prone_annotations:${versions.errorprone}" + annotatedGuava ('org.checkerframework.annotatedlib:guava:33.1.0.2-jre') { + // So long as Guava only uses annotations from checker-qual, excluding it should not cause problems. + exclude group: 'org.checkerframework' + } + } + + shadowJar { + // If you add an external dependency, then do the following: + // * On the master branch and on the modified branch, run: + // ./gradlew assembleForJavac && jar tf checker/dist/checker.jar | grep -v '^annotated-jdk/' | sort > checker-jar-contents.txt + // * Compare the files, and add relocate lines below. + // * Repeat until no new classes appear (all are under org/checkerframework/). + + // Note that string literals are also relocated. Therefore, when the original + // names should be used, e.g. to load the original classes, one needs to work + // around the relocation. When adding a new external dependency, make + // sure no existing string literals are accidentally relocated. + // For an example work-around see NullnessAnnotatedTypeFactory#NONNULL_ALIASES. + + // Relocate packages that might conflict with user's classpath. + relocate 'org.apache', 'org.checkerframework.org.apache' + relocate 'org.relaxng', 'org.checkerframework.org.relaxng' + relocate 'org.plumelib', 'org.checkerframework.org.plumelib' + relocate 'org.codehaus', 'org.checkerframework.org.codehaus' + relocate 'org.objectweb.asm', 'org.checkerframework.org.objectweb.asm' + // Add the classgraph relocations if it is included in releases. + // relocate 'io.github.classgraph', 'org.checkerframework.io.github.classgraph' + // relocate 'nonapi.io.github.classgraph', 'org.checkerframework.nonapi.io.github.classgraph' + // relocate 'sun', 'org.checkerframework.sun' + relocate 'com.google', 'org.checkerframework.com.google' + + exclude '**/module-info.class' - // TODO: it's a bug that annotatedlib:guava requires the error_prone_annotations dependency. - annotatedGuava "com.google.errorprone:error_prone_annotations:${versions.errorprone}" - annotatedGuava ('org.checkerframework.annotatedlib:guava:33.1.0.2-jre') { - // So long as Guava only uses annotations from checker-qual, excluding it should not cause problems. - exclude group: 'org.checkerframework' - } + doFirst { + if (release) { + // Only relocate JavaParser during a release: + relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser' + } } - shadowJar { - // If you add an external dependency, then do the following: - // * On the master branch and on the modified branch, run: - // ./gradlew assembleForJavac && jar tf checker/dist/checker.jar | grep -v '^annotated-jdk/' | sort > checker-jar-contents.txt - // * Compare the files, and add relocate lines below. - // * Repeat until no new classes appear (all are under org/checkerframework/). - - // Note that string literals are also relocated. Therefore, when the original - // names should be used, e.g. to load the original classes, one needs to work - // around the relocation. When adding a new external dependency, make - // sure no existing string literals are accidentally relocated. - // For an example work-around see NullnessAnnotatedTypeFactory#NONNULL_ALIASES. - - // Relocate packages that might conflict with user's classpath. - relocate 'org.apache', 'org.checkerframework.org.apache' - relocate 'org.relaxng', 'org.checkerframework.org.relaxng' - relocate 'org.plumelib', 'org.checkerframework.org.plumelib' - relocate 'org.codehaus', 'org.checkerframework.org.codehaus' - relocate 'org.objectweb.asm', 'org.checkerframework.org.objectweb.asm' - // Add the classgraph relocations if it is included in releases. - // relocate 'io.github.classgraph', 'org.checkerframework.io.github.classgraph' - // relocate 'nonapi.io.github.classgraph', 'org.checkerframework.nonapi.io.github.classgraph' - // relocate 'sun', 'org.checkerframework.sun' - relocate 'com.google', 'org.checkerframework.com.google' - - exclude '**/module-info.class' - - doFirst { - if (release) { - // Only relocate JavaParser during a release: - relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser' - } - } + minimize() + } - minimize() + if (!project.name.startsWith('checker-qual-android')) { + task tags(type: Exec) { + description = 'Create Emacs TAGS table' + commandLine 'bash', '-c', "find . \\( -name build -o -name jtreg -o -name tests \\) -prune -o -name '*.java' -print | sort-directory-order | xargs ctags -e -f TAGS" } - - if (!project.name.startsWith('checker-qual-android')) { - task tags(type: Exec) { - description = 'Create Emacs TAGS table' - commandLine 'bash', '-c', "find . \\( -name build -o -name jtreg -o -name tests \\) -prune -o -name '*.java' -print | sort-directory-order | xargs ctags -e -f TAGS" + } + + java { + withJavadocJar() + withSourcesJar() + } + + // Things in this block reference definitions in the subproject that do not exist, + // until the project is evaluated. + afterEvaluate { + // Adds manifest to all Jar files + tasks.withType(Jar) { + includeEmptyDirs = false + if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { + metaInf { + from './LICENSE.txt' + } + } else { + metaInf { + from "${rootDir}/LICENSE.txt" + } + } + manifest { + attributes('Implementation-Version': "${project.version}") + attributes('Implementation-URL': 'https://eisop.github.io/') + if (! archiveFileName.get().endsWith('source.jar')) { + attributes('Automatic-Module-Name': 'org.checkerframework.' + project.name.replaceAll('-', '.')) + } + if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { + attributes('Bundle-License': 'MIT') + } else { + attributes('Bundle-License': '(GPL-2.0-only WITH Classpath-exception-2.0)') } + } } - java { - withJavadocJar() - withSourcesJar() + // Tasks such as `checkResourceLeak` to run various checkers on all the main source sets. + // These pass and are run by the `typecheck` task. + // When you add one here, also update a dependsOn item for the 'typecheck' task. + createCheckTypeTask(project.name, 'Formatter', + 'org.checkerframework.checker.formatter.FormatterChecker') + createCheckTypeTask(project.name, 'Interning', + 'org.checkerframework.checker.interning.InterningChecker', + [ + '-Astubs=javax-lang-model-element-name.astub' + ]) + createCheckTypeTask(project.name, 'Optional', + 'org.checkerframework.checker.optional.OptionalChecker', + [ + // to avoid having to annotate JavaParser + '-AassumePureGetters', + '-AassumeAssertionsAreEnabled', + ]) + createCheckTypeTask(project.name, 'Purity', + 'org.checkerframework.framework.util.PurityChecker') + createCheckTypeTask(project.name, 'ResourceLeak', + 'org.checkerframework.checker.resourceleak.ResourceLeakChecker') + createCheckTypeTask(project.name, 'Signature', + 'org.checkerframework.checker.signature.SignatureChecker') + + // The checkNullness task runs on all code, but it only *checks* the following code: + // * All files outside the 'framework' and 'checker' subprojects. + // * In the 'framework' and 'checker' subprojects, files with `@AnnotatedFor("nullness")`. + if (project.name.is('framework') || project.name.is('checker')) { + createCheckTypeTask(project.name, 'Nullness', + 'org.checkerframework.checker.nullness.NullnessChecker', + [ + '-AskipUses=com\\.sun\\.*', + // If a file does not contain @AnnotatedFor("nullness"), all its routines are assumed to return @Nullable. + '-AuseConservativeDefaultsForUncheckedCode=source', + '-AconservativeArgumentNullnessAfterInvocation=true', + ]) + } else { + createCheckTypeTask(project.name, 'Nullness', + 'org.checkerframework.checker.nullness.NullnessChecker', + [ + '-AskipUses=com\\.sun\\.*', + '-AconservativeArgumentNullnessAfterInvocation=true' + ]) } - // Things in this block reference definitions in the subproject that do not exist, - // until the project is evaluated. - afterEvaluate { - // Adds manifest to all Jar files - tasks.withType(Jar) { - includeEmptyDirs = false - if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { - metaInf { - from './LICENSE.txt' - } - } else { - metaInf { - from "${rootDir}/LICENSE.txt" - } - } - manifest { - attributes('Implementation-Version': "${project.version}") - attributes('Implementation-URL': 'https://eisop.github.io/') - if (! archiveFileName.get().endsWith('source.jar')) { - attributes('Automatic-Module-Name': 'org.checkerframework.' + project.name.replaceAll('-', '.')) - } - if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { - attributes('Bundle-License': 'MIT') - } else { - attributes('Bundle-License': '(GPL-2.0-only WITH Classpath-exception-2.0)') - } - } - } - // Tasks such as `checkResourceLeak` to run various checkers on all the main source sets. - // These pass and are run by the `typecheck` task. - // When you add one here, also update a dependsOn item for the 'typecheck' task. - createCheckTypeTask(project.name, 'Formatter', - 'org.checkerframework.checker.formatter.FormatterChecker') - createCheckTypeTask(project.name, 'Interning', - 'org.checkerframework.checker.interning.InterningChecker', - [ - '-Astubs=javax-lang-model-element-name.astub' - ]) - createCheckTypeTask(project.name, 'Optional', - 'org.checkerframework.checker.optional.OptionalChecker', - [ - // to avoid having to annotate JavaParser - '-AassumePureGetters', - '-AassumeAssertionsAreEnabled', - ]) - createCheckTypeTask(project.name, 'Purity', - 'org.checkerframework.framework.util.PurityChecker') - createCheckTypeTask(project.name, 'ResourceLeak', - 'org.checkerframework.checker.resourceleak.ResourceLeakChecker') - createCheckTypeTask(project.name, 'Signature', - 'org.checkerframework.checker.signature.SignatureChecker') - - // The checkNullness task runs on all code, but it only *checks* the following code: - // * All files outside the 'framework' and 'checker' subprojects. - // * In the 'framework' and 'checker' subprojects, files with `@AnnotatedFor("nullness")`. - if (project.name.is('framework') || project.name.is('checker')) { - createCheckTypeTask(project.name, 'Nullness', - 'org.checkerframework.checker.nullness.NullnessChecker', - [ - '-AskipUses=com\\.sun\\.*', - // If a file does not contain @AnnotatedFor("nullness"), all its routines are assumed to return @Nullable. - '-AuseConservativeDefaultsForUncheckedCode=source', - '-AconservativeArgumentNullnessAfterInvocation=true', - ]) - } else { - createCheckTypeTask(project.name, 'Nullness', - 'org.checkerframework.checker.nullness.NullnessChecker', - [ - '-AskipUses=com\\.sun\\.*', - '-AconservativeArgumentNullnessAfterInvocation=true' - ]) + // Add jtregTests to framework and checker modules + if (project.name.is('framework') || project.name.is('checker')) { + tasks.create(name: 'jtregTests', group: 'Verification') { + description = 'Run the jtreg tests.' + + if (currentRuntimeJavaVersion < 11) { + // jtreg only works on JDK 11+ + return } + dependsOn(':downloadJtreg') + dependsOn('compileJava') + dependsOn('compileTestJava') + dependsOn('shadowJar') - // Add jtregTests to framework and checker modules - if (project.name.is('framework') || project.name.is('checker')) { - tasks.create(name: 'jtregTests', group: 'Verification') { - description = 'Run the jtreg tests.' - - if (currentRuntimeJavaVersion < 11) { - // jtreg only works on JDK 11+ - return - } - - dependsOn(':downloadJtreg') - dependsOn('compileJava') - dependsOn('compileTestJava') - dependsOn('shadowJar') - - def injected = project.objects.newInstance(InjectedExecOps) - - def isFramework = project.name.is('framework') - def isChecker = project.name.is('checker') - - String jtregOutput = "${buildDir}/jtreg" - String name = 'all' - String tests = '.' - - doLast { - try { - injected.execOps.exec { - executable "${jtregHome}/bin/jtreg" - args = [ - "-dir:${projectDir}/jtreg", - "-workDir:${jtregOutput}/${name}/work", - "-reportDir:${jtregOutput}/${name}/report", - '-verbose:error,fail,nopass', - // Don't add debugging information - // '-javacoptions:-g', - '-keywords:!ignore', - '-samevm', - "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - // Required for checker/jtreg/nullness/PersistUtil.java and other tests - "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - ] - args += [ - // checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java - // uses the jdk.jdeps module. - '-javacoptions:--add-modules jdk.jdeps', - '-javacoptions:--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - ] - if (isFramework) { - // Do not check for the annotated JDK - args += [ - '-javacoptions:-ApermitMissingJdk' - ] - } else if (isChecker) { - args += [ - "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", - ] - } - - // Allow running on any JRE version. - args += [ - "-javacoptions:-AnoJreVersionCheck" - ] - - // Location of jtreg tests - args += "${tests}" - } - } catch (Exception ex) { - if (ex.getCause() != null && ex.getCause().getCause()!= null) { - String msg = ex.getCause().getLocalizedMessage() + ':\n' - msg += ex.getCause().getCause().getLocalizedMessage() + '\n' - msg += 'Have you installed jtreg?' - println msg - } - throw ex - } - } - } - } + def injected = project.objects.newInstance(InjectedExecOps) - // Create a task for each JUnit test class whose name is the same as the JUnit class name. - // Regex [\\\\/] matches Unix and Windows directory separators. - sourceSets.test.allJava.filter {it.path.matches('.*test[\\\\/]junit.*')}.forEach { file -> - String junitClassName = file.name.replaceAll('.java', '') - tasks.create(name: "${junitClassName}", type: Test) { - description = "Run ${junitClassName} tests." - include "**/${name}.class" - testClassesDirs = testing.suites.test.sources.output.classesDirs - classpath = testing.suites.test.sources.runtimeClasspath - } - } + def isFramework = project.name.is('framework') + def isChecker = project.name.is('checker') - // Configure JUnit tests - tasks.withType(Test) { - if (isJava8) { - jvmArgs "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() - } else { - jvmArgs += compilerArgsForRunningCF - } + String jtregOutput = "${buildDir}/jtreg" + String name = 'all' + String tests = '.' - maxParallelForks = Integer.MAX_VALUE + doLast { + try { + injected.execOps.exec { + executable "${jtregHome}/bin/jtreg" + args = [ + "-dir:${projectDir}/jtreg", + "-workDir:${jtregOutput}/${name}/work", + "-reportDir:${jtregOutput}/${name}/report", + '-verbose:error,fail,nopass', + // Don't add debugging information + // '-javacoptions:-g', + '-keywords:!ignore', + '-samevm', + "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + // Required for checker/jtreg/nullness/PersistUtil.java and other tests + "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + ] + args += [ + // checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java + // uses the jdk.jdeps module. + '-javacoptions:--add-modules jdk.jdeps', + '-javacoptions:--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + ] + if (isFramework) { + // Do not check for the annotated JDK + args += [ + '-javacoptions:-ApermitMissingJdk' + ] + } else if (isChecker) { + args += [ + "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", + ] + } - if (project.name.is('checker')) { - dependsOn('assembleForJavac') - } + // Allow running on any JRE version. + args += [ + "-javacoptions:-AnoJreVersionCheck" + ] - if (project.hasProperty('emit.test.debug')) { - systemProperties += ['emit.test.debug': 'true'] + // Location of jtreg tests + args += "${tests}" } + } catch (Exception ex) { + if (ex.getCause() != null && ex.getCause().getCause()!= null) { + String msg = ex.getCause().getLocalizedMessage() + ':\n' + msg += ex.getCause().getCause().getLocalizedMessage() + '\n' + msg += 'Have you installed jtreg?' + println msg + } + throw ex + } + } + } + } - testLogging { - showStandardStreams = true - // Always run the tests - outputs.upToDateWhen { false } + // Create a task for each JUnit test class whose name is the same as the JUnit class name. + // Regex [\\\\/] matches Unix and Windows directory separators. + sourceSets.test.allJava.filter {it.path.matches('.*test[\\\\/]junit.*')}.forEach { file -> + String junitClassName = file.name.replaceAll('.java', '') + tasks.create(name: "${junitClassName}", type: Test) { + description = "Run ${junitClassName} tests." + include "**/${name}.class" + testClassesDirs = testing.suites.test.sources.output.classesDirs + classpath = testing.suites.test.sources.runtimeClasspath + } + } - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'failed' + // Configure JUnit tests + tasks.withType(Test) { + if (isJava8) { + jvmArgs "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() + } else { + jvmArgs += compilerArgsForRunningCF + } - // Don't show the uninteresting stack traces from the exceptions. - showStackTraces = false - } + maxParallelForks = Integer.MAX_VALUE - // After each test, print a summary. - afterSuite { desc, result -> - if (desc.getClassName() != null) { - long mils = result.getEndTime() - result.getStartTime() - double seconds = mils / 1000.0 - - println "Testsuite: ${desc.getClassName()}\n" + - "Tests run: ${result.testCount}, " + - "Failures: ${result.failedTestCount}, " + - "Skipped: ${result.skippedTestCount}, " + - "Time elapsed: ${seconds} sec\n" - } - } - } + if (project.name.is('checker')) { + dependsOn('assembleForJavac') + } - // Create a nonJunitTests task per project - tasks.create(name: 'nonJunitTests', group: 'Verification') { - description = 'Run all Checker Framework tests except for the JUnit tests and inference tests.' - if (project.name.is('framework') || project.name.is('checker')) { - dependsOn('jtregTests') - } - if (project.name.is('framework')) { - dependsOn('loaderTests') - } + if (project.hasProperty('emit.test.debug')) { + systemProperties += ['emit.test.debug': 'true'] + } - if (project.name.is('checker')) { - if (!isJava8) { - dependsOn('jtregJdk11Tests') - } - dependsOn('nullnessExtraTests', 'commandLineTests', 'tutorialTests') - } + testLogging { + showStandardStreams = true + // Always run the tests + outputs.upToDateWhen { false } - if (project.name.is('dataflow')) { - dependsOn('allDataflowTests') - } + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'failed' + + // Don't show the uninteresting stack traces from the exceptions. + showStackTraces = false + } + + // After each test, print a summary. + afterSuite { desc, result -> + if (desc.getClassName() != null) { + long mils = result.getEndTime() - result.getStartTime() + double seconds = mils / 1000.0 + + println "Testsuite: ${desc.getClassName()}\n" + + "Tests run: ${result.testCount}, " + + "Failures: ${result.failedTestCount}, " + + "Skipped: ${result.skippedTestCount}, " + + "Time elapsed: ${seconds} sec\n" } + } + } - // Create an inferenceTests task per project - tasks.create(name: 'inferenceTests', group: 'Verification') { - description = 'Run inference tests.' - if (project.name.is('checker')) { - // NO-AFU: Needs to depend on future AFU version of CF - // dependsOn('inferenceTests-part1', 'inferenceTests-part1') - } - } - tasks.create(name: 'inferenceTests-part1', group: 'Verification') { - description = 'Run inference tests (part 1).' - if (project.name.is('checker')) { - // NO-AFU: Needs to depend on future AFU version of CF - // dependsOn('ainferTest', 'wpiManyTest') - } - } - tasks.create(name: 'inferenceTests-part2', group: 'Verification') { - description = 'Run inference tests (part 2).' - if (project.name.is('checker')) { - // NO-AFU: Needs to depend on future AFU version of CF - // dependsOn('wpiPlumeLibTest') - } + // Create a nonJunitTests task per project + tasks.create(name: 'nonJunitTests', group: 'Verification') { + description = 'Run all Checker Framework tests except for the JUnit tests and inference tests.' + if (project.name.is('framework') || project.name.is('checker')) { + dependsOn('jtregTests') + } + if (project.name.is('framework')) { + dependsOn('loaderTests') + } + + if (project.name.is('checker')) { + if (!isJava8) { + dependsOn('jtregJdk11Tests') } + dependsOn('nullnessExtraTests', 'commandLineTests', 'tutorialTests') + } - // Create a typecheck task per project (dogfooding the Checker Framework on itself). - // This isn't a test of the Checker Framework as the test and nonJunitTests tasks are. - // Tasks such as 'checkInterning' are constructed by createCheckTypeTask. - tasks.create(name: 'typecheck', group: 'Verification') { - description = 'Run the Checker Framework on itself' - dependsOn('typecheck-part1', 'typecheck-part2') - } - tasks.create(name: 'typecheck-part1', group: 'Verification') { - description = 'Run the Checker Framework on itself (part 1)' - dependsOn('checkFormatter', 'checkInterning', 'checkOptional', 'checkPurity') - } - tasks.create(name: 'typecheck-part2', group: 'Verification') { - description = 'Run the Checker Framework on itself (part 2)' - dependsOn('checkResourceLeak', 'checkSignature') - if (project.name.is('framework') || project.name.is('checker')) { - dependsOn('checkCompilerMessages') - } - dependsOn('checkNullness') - } + if (project.name.is('dataflow')) { + dependsOn('allDataflowTests') + } + } - // Create an allTests task per project. - // allTests = test + nonJunitTests + inferenceTests + typecheck - tasks.create(name: 'allTests', group: 'Verification') { - description = 'Run all Checker Framework tests' - // The 'test' target is just the JUnit tests. - dependsOn('test', 'nonJunitTests', 'inferenceTests', 'typecheck') - } + // Create an inferenceTests task per project + tasks.create(name: 'inferenceTests', group: 'Verification') { + description = 'Run inference tests.' + if (project.name.is('checker')) { + // NO-AFU: Needs to depend on future AFU version of CF + // dependsOn('inferenceTests-part1', 'inferenceTests-part1') + } + } + tasks.create(name: 'inferenceTests-part1', group: 'Verification') { + description = 'Run inference tests (part 1).' + if (project.name.is('checker')) { + // NO-AFU: Needs to depend on future AFU version of CF + // dependsOn('ainferTest', 'wpiManyTest') + } + } + tasks.create(name: 'inferenceTests-part2', group: 'Verification') { + description = 'Run inference tests (part 2).' + if (project.name.is('checker')) { + // NO-AFU: Needs to depend on future AFU version of CF + // dependsOn('wpiPlumeLibTest') + } + } - task javadocPrivate(dependsOn: javadoc) { - doFirst { - javadocMemberLevel = JavadocMemberLevel.PRIVATE - } - doLast { - javadocMemberLevel = JavadocMemberLevel.PROTECTED - } - } + // Create a typecheck task per project (dogfooding the Checker Framework on itself). + // This isn't a test of the Checker Framework as the test and nonJunitTests tasks are. + // Tasks such as 'checkInterning' are constructed by createCheckTypeTask. + tasks.create(name: 'typecheck', group: 'Verification') { + description = 'Run the Checker Framework on itself' + dependsOn('typecheck-part1', 'typecheck-part2') + } + tasks.create(name: 'typecheck-part1', group: 'Verification') { + description = 'Run the Checker Framework on itself (part 1)' + dependsOn('checkFormatter', 'checkInterning', 'checkOptional', 'checkPurity') + } + tasks.create(name: 'typecheck-part2', group: 'Verification') { + description = 'Run the Checker Framework on itself (part 2)' + dependsOn('checkResourceLeak', 'checkSignature') + if (project.name.is('framework') || project.name.is('checker')) { + dependsOn('checkCompilerMessages') + } + dependsOn('checkNullness') + } + + // Create an allTests task per project. + // allTests = test + nonJunitTests + inferenceTests + typecheck + tasks.create(name: 'allTests', group: 'Verification') { + description = 'Run all Checker Framework tests' + // The 'test' target is just the JUnit tests. + dependsOn('test', 'nonJunitTests', 'inferenceTests', 'typecheck') + } + + task javadocPrivate(dependsOn: javadoc) { + doFirst { + javadocMemberLevel = JavadocMemberLevel.PRIVATE + } + doLast { + javadocMemberLevel = JavadocMemberLevel.PROTECTED + } } + } } // The `assembleForJavac` task is faster than the `assemble` task, if you only @@ -1282,34 +1314,34 @@ assemble.dependsOn(':checker:assembleForJavac') assemble.mustRunAfter(clean) task buildAll(group: 'Build') { - description = 'Build all jar files, including source and javadoc jars' - dependsOn(allJavadoc) - subprojects { Project subproject -> - dependsOn("${subproject.name}:assemble") - dependsOn("${subproject.name}:javadocJar") - dependsOn("${subproject.name}:sourcesJar") - } - dependsOn('framework:allJavadocJar', 'framework:allSourcesJar', 'checker:allJavadocJar', 'checker:allSourcesJar', 'checker-qual:jar', 'checker-util:jar') + description = 'Build all jar files, including source and javadoc jars' + dependsOn(allJavadoc) + subprojects { Project subproject -> + dependsOn("${subproject.name}:assemble") + dependsOn("${subproject.name}:javadocJar") + dependsOn("${subproject.name}:sourcesJar") + } + dependsOn('framework:allJavadocJar', 'framework:allSourcesJar', 'checker:allJavadocJar', 'checker:allSourcesJar', 'checker-qual:jar', 'checker-util:jar') } task releaseBuild(group: 'Build') { - description = 'Build everything required for a release' - dependsOn(clean) - doFirst { - release = true - } - // Use finalizedBy rather than dependsOn so that release is set to true before any of the tasks are run. - finalizedBy(buildAll) - finalizedBy('checker:assembleForJavac') + description = 'Build everything required for a release' + dependsOn(clean) + doFirst { + release = true + } + // Use finalizedBy rather than dependsOn so that release is set to true before any of the tasks are run. + finalizedBy(buildAll) + finalizedBy('checker:assembleForJavac') } // No group so it does not show up in the output of `gradlew tasks` task releaseAndTest { - description = 'Build everything required for a release and run allTests' - dependsOn(releaseBuild) - subprojects { Project subproject -> - dependsOn("${subproject.name}:allTests") - } + description = 'Build everything required for a release and run allTests' + dependsOn(releaseBuild) + subprojects { Project subproject -> + dependsOn("${subproject.name}:allTests") + } } // Don't create an empty checker-framework-VERSION.jar @@ -1320,43 +1352,43 @@ jar.onlyIf {false} * @param publication the MavenPublication */ final sharedPublicationConfiguration(publication) { - publication.pom { - url = 'https://eisop.github.io/' - developers { - // These are the lead developers/maintainers, not all the developers or contributors. - - // For eisop, previously also typetools - developer { - id = 'wmdietl' - name = 'Werner M. Dietl' - email = 'wdietl@gmail.com' - url = 'https://ece.uwaterloo.ca/~wdietl/' - organization = 'University of Waterloo' - organizationUrl = 'https://uwaterloo.ca/' - } - - // For typetools - developer { - id = 'mernst' - name = 'Michael Ernst' - email = 'mernst@cs.washington.edu' - url = 'https://homes.cs.washington.edu/~mernst/' - organization = 'University of Washington' - organizationUrl = 'https://www.cs.washington.edu/' - } - developer { - id = 'smillst' - name = 'Suzanne Millstein' - email = 'smillst@cs.washington.edu' - organization = 'University of Washington' - organizationUrl = 'https://www.cs.washington.edu/' - } - } + publication.pom { + url = 'https://eisop.github.io/' + developers { + // These are the lead developers/maintainers, not all the developers or contributors. + + // For eisop, previously also typetools + developer { + id = 'wmdietl' + name = 'Werner M. Dietl' + email = 'wdietl@gmail.com' + url = 'https://ece.uwaterloo.ca/~wdietl/' + organization = 'University of Waterloo' + organizationUrl = 'https://uwaterloo.ca/' + } + + // For typetools + developer { + id = 'mernst' + name = 'Michael Ernst' + email = 'mernst@cs.washington.edu' + url = 'https://homes.cs.washington.edu/~mernst/' + organization = 'University of Washington' + organizationUrl = 'https://www.cs.washington.edu/' + } + developer { + id = 'smillst' + name = 'Suzanne Millstein' + email = 'smillst@cs.washington.edu' + organization = 'University of Washington' + organizationUrl = 'https://www.cs.washington.edu/' + } + } - scm { - url = 'https://github.com/eisop/checker-framework.git' - connection = 'scm:git:https://github.com/eisop/checker-framework.git' - developerConnection = 'scm:git:ssh://git@github.com/eisop/checker-framework.git' - } + scm { + url = 'https://github.com/eisop/checker-framework.git' + connection = 'scm:git:https://github.com/eisop/checker-framework.git' + developerConnection = 'scm:git:ssh://git@github.com/eisop/checker-framework.git' } + } } From 277b85893e5ce94d95edabcbc36bce6eefa9be48 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Mon, 8 Jan 2024 08:59:20 -0800 Subject: [PATCH 007/173] `@MustCallAlias` annotation must appear in both return type and parameter type (or not at all) (#6377) --- .../resourceleak/ResourceLeakVisitor.java | 110 ++++++++++++++++++ .../checker/resourceleak/messages.properties | 1 + .../MustCallAliasNoAnnoOnConstructor.java | 24 ++++ .../MustCallAliasPassthroughWrong3.java | 1 + .../MustCallAliasReturnAndParamSimple.java | 59 ++++++++++ 5 files changed, 195 insertions(+) create mode 100644 checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java create mode 100644 checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java index 5decce0ce9e..5b72bc66aa4 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java @@ -23,10 +23,13 @@ import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.NotOwning; import org.checkerframework.checker.mustcall.qual.Owning; +import org.checkerframework.checker.mustcall.qual.PolyMustCall; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.expression.FieldAccess; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.util.JavaExpressionParseUtil; import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.AnnotationMirrorSet; @@ -92,6 +95,11 @@ public Void visitMethod(MethodTree tree, Void p) { checkCreatesMustCallForTargetsHaveNonEmptyMustCall(tree, mcAtf); } checkOwningOverrides(tree, elt, mcAtf); + if (TreeUtils.isConstructor(tree)) { + checkMustCallAliasAnnotationForConstructor(tree); + } else { + checkMustCallAliasAnnotationForMethod(tree, mcAtf); + } return super.visitMethod(tree, p); } @@ -202,6 +210,108 @@ private void checkOwningOverrides( } } + /** + * If a {@code @MustCallAlias} annotation appears in a method declaration, it must appear as an + * annotation on both the return type, and a parameter type. + * + *

          The return type is checked if it is annotated with {@code @PolyMustCall} because the Must + * Call Checker treats {@code @MustCallAlias} as an alias of {@code @PolyMustCall}. + * + * @param tree the method declaration + * @param mcAtf the MustCallAnnotatedTypeFactory + */ + private void checkMustCallAliasAnnotationForMethod( + MethodTree tree, MustCallAnnotatedTypeFactory mcAtf) { + + Element paramWithMustCallAliasAnno = getParameterWithMustCallAliasAnno(tree); + boolean isMustCallAliasAnnoOnParameter = paramWithMustCallAliasAnno != null; + + if (TreeUtils.isVoidReturn(tree) && isMustCallAliasAnnoOnParameter) { + checker.reportWarning( + tree, "mustcallalias.method.return.and.param", "this method has a void return"); + return; + } + + AnnotatedTypeMirror returnType = mcAtf.getMethodReturnType(tree); + boolean isMustCallAliasAnnoOnReturnType = returnType.hasPrimaryAnnotation(PolyMustCall.class); + checkMustCallAliasAnnoMismatch( + paramWithMustCallAliasAnno, isMustCallAliasAnnoOnReturnType, tree); + } + + /** + * Given a constructor, a {@code @MustCallAlias} must appear in both the list of parameters and as + * an annotation on the constructor itself, if it is to appear at all. + * + *

          That is, a {@code @MustCallAlias} annotation must appear on both the constructor and its + * parameter list, or not at all. + * + * @param tree the constructor + */ + private void checkMustCallAliasAnnotationForConstructor(MethodTree tree) { + ExecutableElement constructorDecl = TreeUtils.elementFromDeclaration(tree); + boolean isMustCallAliasAnnoOnConstructor = + constructorDecl != null && rlTypeFactory.hasMustCallAlias(constructorDecl); + Element paramWithMustCallAliasAnno = getParameterWithMustCallAliasAnno(tree); + checkMustCallAliasAnnoMismatch( + paramWithMustCallAliasAnno, isMustCallAliasAnnoOnConstructor, tree); + } + + /** + * Construct the warning message for the case where a {@code @MustCallAlias} annotation does not + * appear in pairs in a method or constructor declaration. + * + *

          If a parameter of a method or a constructor is annotated with a {@code @MustCallAlias} + * annotation, the return type (for a method) should also be annotated with + * {@code @MustCallAlias}. In the case of a constructor, which has no return type, a + * {@code @MustCallAlias} annotation must appear on its declaration. + * + * @param paramWithMustCallAliasAnno a parameter with a {@code @MustCallAlias} annotation, null if + * there are none + * @param isMustCallAliasAnnoOnMethodOrConstructorDecl true if and only if a + * {@code @MustCallAlias} annotation appears on a method or constructor declaration + * @param tree the method or constructor declaration + */ + private void checkMustCallAliasAnnoMismatch( + @Nullable Element paramWithMustCallAliasAnno, + boolean isMustCallAliasAnnoOnMethodOrConstructorDecl, + MethodTree tree) { + boolean isMustCallAliasAnnotationOnParameter = paramWithMustCallAliasAnno != null; + if (isMustCallAliasAnnotationOnParameter != isMustCallAliasAnnoOnMethodOrConstructorDecl) { + String locationOfCheck = TreeUtils.isClassTree(tree) ? "this constructor" : "the return type"; + String message = + isMustCallAliasAnnotationOnParameter + ? String.format( + "there is no @MustCallAlias annotation on %s, even though the parameter %s is annotated with @MustCallAlias", + locationOfCheck, paramWithMustCallAliasAnno) + : "no parameter has a @MustCallAlias annotation, even though the return type is annotated with @MustCallAlias"; + checker.reportWarning(tree, "mustcallalias.method.return.and.param", message); + } + } + + /** + * Given a method and its parameter list, look through each of the parameters and see if any are + * annotated with the {@code @MustCallAlias} annotation. + * + *

          Return the first parameter that is annotated with {@code @MustCallAlias}, otherwise return + * null. + * + * @param tree the method declaration + * @return the first parameter that is annotated with {@code @MustCallAlias}, otherwise return + * null + */ + private @Nullable Element getParameterWithMustCallAliasAnno(MethodTree tree) { + VariableTree receiverParameter = tree.getReceiverParameter(); + if (receiverParameter != null && rlTypeFactory.hasMustCallAlias(receiverParameter)) { + return TreeUtils.elementFromDeclaration(receiverParameter); + } + for (VariableTree param : tree.getParameters()) { + if (rlTypeFactory.hasMustCallAlias(param)) { + return TreeUtils.elementFromDeclaration(param); + } + } + return null; + } + /* NO-AFU @Override protected boolean shouldPerformContractInference() { diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/messages.properties b/checker/src/main/java/org/checkerframework/checker/resourceleak/messages.properties index 16a51267fc2..c5776fbf9a5 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/messages.properties +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/messages.properties @@ -6,6 +6,7 @@ creates.mustcall.for.override.invalid=Method %s cannot override method %s, which creates.mustcall.for.invalid.target=Cannot create a must-call obligation for "%s" since its type %s has no must-call methods. destructor.exceptional.postcondition=Method %s must resolve the must-call obligations of the owning field %s along all paths, including exceptional paths. On an exceptional path, the @EnsuresCalledMethods annotation was not satisfied.\nFound: %s\nRequired: %s mustcallalias.out.of.scope=This @MustCallAlias parameter might go out of scope without being assigned into an owning field of this object (if this is a constructor) or returned.\nReason for going out of scope: %s +mustcallalias.method.return.and.param=@MustCallAlias annotations must appear in pairs (one on a return type and one on a parameter type).\nBut %s owning.override.param=Incompatible ownership for parameter %s.\nfound : no ownership annotation or @NotOwning\nrequired: @Owning\nConsequence: method %s in %s cannot override method %s in %s owning.override.return=Incompatible ownership for return.\nfound : no ownership annotation or @Owning\nrequired: @NotOwning\nConsequence: method %s in %s cannot override method %s in %s required.method.not.known=The checker cannot determine the must call methods of %s or any of its aliases, so it could not determine if they were called. Typically, this error indicates that you need to write an @MustCall annotation (often on an unconstrained generic type).\nThe type of object is: %s.\nReason for going out of scope: %s diff --git a/checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java b/checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java new file mode 100644 index 00000000000..1c2570e6b4d --- /dev/null +++ b/checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java @@ -0,0 +1,24 @@ +// Test case for https://github.com/typetools/checker-framework/issues/6376 + +import java.io.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; + +@InheritableMustCall("close") +public class MustCallAliasNoAnnoOnConstructor { + + final @Owning InputStream is; + + // :: warning: (mustcallalias.method.return.and.param) + @MustCallAlias MustCallAliasNoAnnoOnConstructor(InputStream p, boolean b) throws Exception { + if (b) { + throw new Exception("an exception!"); + } + this.is = p; + } + + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } +} diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java index fedd16c7228..9d1bafe4dd6 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java @@ -6,6 +6,7 @@ class MustCallAliasPassthroughWrong3 { + // :: warning: (mustcallalias.method.return.and.param) static InputStream missingMCA(@MustCallAlias InputStream is) { // :: error: (return.type.incompatible) return is; diff --git a/checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java b/checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java new file mode 100644 index 00000000000..df7329b5910 --- /dev/null +++ b/checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java @@ -0,0 +1,59 @@ +// Test case for https://github.com/typetools/checker-framework/issues/6376 + +import java.io.*; +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; + +@InheritableMustCall("close") +public class MustCallAliasReturnAndParamSimple { + + final @Owning InputStream is; + + // Should have no error here. + @MustCallAlias MustCallAliasReturnAndParamSimple someFluentMethod( + @MustCallAlias MustCallAliasReturnAndParamSimple this) { + return this; + } + + // :: warning: (mustcallalias.method.return.and.param) + @MustCallAlias MustCallAliasReturnAndParamSimple someIncorrectlyAnnotatedFluentMethod( + MustCallAliasReturnAndParamSimple this) { + return this; + } + + @MustCallAlias MustCallAliasReturnAndParamSimple(@MustCallAlias InputStream p, boolean b) throws Exception { + if (b) { + throw new Exception("an exception!"); + } + this.is = p; + } + + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } + + // No errors or warnings here, properly annotated. + public static @MustCallAlias MustCallAliasReturnAndParamSimple mcaneFactory( + @MustCallAlias InputStream is) throws Exception { + return new MustCallAliasReturnAndParamSimple(is, false); + } + + // :: warning: (mustcallalias.method.return.and.param) + public static MustCallAliasReturnAndParamSimple mcaneFactory2(@MustCallAlias InputStream is) + throws Exception { + // :: error: (return) + return new MustCallAliasReturnAndParamSimple(is, false); + } + + // :: warning: (mustcallalias.method.return.and.param) + public static @MustCallAlias MustCallAliasReturnAndParamSimple mcaneFactory3(InputStream is) + throws Exception { + return new MustCallAliasReturnAndParamSimple(is, false); + } + + // No warning here; neither the return type or parameter is annotated with @MustCallAlias + public static MustCallAliasReturnAndParamSimple mcaneFactory4(InputStream is) throws Exception { + return new MustCallAliasReturnAndParamSimple(is, false); + } +} From 817d09e1228f6aa598cba6d399407c5bf6760a71 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Wed, 29 Jan 2025 18:56:02 -0500 Subject: [PATCH 008/173] Change gradle files to typetools formatting --- checker-qual-android/build.gradle | 96 +- checker-qual/build.gradle | 58 +- checker-util/build.gradle | 42 +- checker/build.gradle | 1810 ++++++++++++------------- dataflow/build.gradle | 316 ++--- docs/build.gradle | 18 +- docs/examples/errorprone/build.gradle | 72 +- docs/examples/lombok/build.gradle | 26 +- framework-test/build.gradle | 56 +- framework/build.gradle | 412 +++--- gradle-mvn-push.gradle | 42 +- javacutil/build.gradle | 78 +- 12 files changed, 1513 insertions(+), 1513 deletions(-) diff --git a/checker-qual-android/build.gradle b/checker-qual-android/build.gradle index 135d2ecb0f3..573966e969e 100644 --- a/checker-qual-android/build.gradle +++ b/checker-qual-android/build.gradle @@ -1,73 +1,73 @@ evaluationDependsOn(':checker-qual') task copySources(type: Copy) { - description = 'Copy checker-qual source to checker-qual-android' - includeEmptyDirs = false - doFirst { - // Delete the directory in case a previously copied file should no longer be in checker-qual - delete file(layout.buildDirectory.dir("generated/sources/main/java")) - } - from files(project(':checker-qual').sourceSets.main.java) - include '**/*.java' - exclude '**/SignednessUtilExtra.java' + description = 'Copy checker-qual source to checker-qual-android' + includeEmptyDirs = false + doFirst { + // Delete the directory in case a previously copied file should no longer be in checker-qual + delete file(layout.buildDirectory.dir("generated/sources/main/java")) + } + from files(project(':checker-qual').sourceSets.main.java) + include '**/*.java' + exclude '**/SignednessUtilExtra.java' - // Types annotated with runtime annotations are always kept in the main dex by the default Android Gradle plugin. - // Using the standard Checker Framework annotations can lead to main dex overflows; - // users of the Checker Framework may find themselves unable to build their Android apps. - // By contrast, class-retention annotations are stripped out before packaging by all build systems as a convention. - filter { line -> line.replaceAll('RetentionPolicy.RUNTIME', 'RetentionPolicy.CLASS') } + // Types annotated with runtime annotations are always kept in the main dex by the default Android Gradle plugin. + // Using the standard Checker Framework annotations can lead to main dex overflows; + // users of the Checker Framework may find themselves unable to build their Android apps. + // By contrast, class-retention annotations are stripped out before packaging by all build systems as a convention. + filter { line -> line.replaceAll('RetentionPolicy.RUNTIME', 'RetentionPolicy.CLASS') } - into file(layout.buildDirectory.dir("generated/sources/main/java")) + into file(layout.buildDirectory.dir("generated/sources/main/java")) - filePermissions { - user.read = true - group.read = true - other.read = true - } + filePermissions { + user.read = true + group.read = true + other.read = true + } } sourceSets { - main { - java { - srcDir(copySources) - } + main { + java { + srcDir(copySources) } + } } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerQualAndroidPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Qual Android' - description = 'checker-qual-android contains annotations (type qualifiers) that a programmer\n' + - 'writes to specify Java code for type-checking by the Checker Framework.\n' + - '\n' + - 'The checker-qual-android artifact is identical to the checker-qual\n' + - 'artifact, except that in checker-qual-android annotations have classfile\n' + - 'retention. The default Android Gradle plugin retains types annotated with\n' + - 'runtime annotations in the main dex, but strips out class-retention\n' + - 'annotations.\n' - licenses { - license { - name = 'The MIT License' - url = 'http://opensource.org/licenses/MIT' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Qual Android' + description = 'checker-qual-android contains annotations (type qualifiers) that a programmer\n' + + 'writes to specify Java code for type-checking by the Checker Framework.\n' + + '\n' + + 'The checker-qual-android artifact is identical to the checker-qual\n' + + 'artifact, except that in checker-qual-android annotations have classfile\n' + + 'retention. The default Android Gradle plugin retains types annotated with\n' + + 'runtime annotations in the main dex, but strips out class-retention\n' + + 'annotations.\n' + licenses { + license { + name = 'The MIT License' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } } + } } publishing { - publications { - checkerQualAndroid(MavenPublication) { - checkerQualAndroidPom it - } + publications { + checkerQualAndroid(MavenPublication) { + checkerQualAndroidPom it } + } } signing { - sign publishing.publications.checkerQualAndroid + sign publishing.publications.checkerQualAndroid } diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle index e369fb21b2b..d77d0348788 100644 --- a/checker-qual/build.gradle +++ b/checker-qual/build.gradle @@ -1,55 +1,55 @@ buildscript { - dependencies { - if (JavaVersion.current() >= JavaVersion.VERSION_17) { - // bnd builder depends on Java 17. - // As EISOP releases are made on Java 21, this shouldn't impact releases. - classpath('biz.aQute.bnd:biz.aQute.bnd.gradle:7.1.0') - } + dependencies { + if (JavaVersion.current() >= JavaVersion.VERSION_17) { + // bnd builder depends on Java 17. + // As EISOP releases are made on Java 21, this shouldn't impact releases. + classpath('biz.aQute.bnd:biz.aQute.bnd.gradle:7.1.0') } + } } plugins { - id 'java-library' + id 'java-library' } if (JavaVersion.current() >= JavaVersion.VERSION_17) { - apply plugin: 'biz.aQute.bnd.builder' + apply plugin: 'biz.aQute.bnd.builder' } jar { - manifest { - attributes('Export-Package': '*') - } + manifest { + attributes('Export-Package': '*') + } } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerQualPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Qual' - description = 'checker-qual contains annotations (type qualifiers) that a programmer\n' + - 'writes to specify Java code for type-checking by the Checker Framework.\n' - licenses { - license { - name = 'The MIT License' - url = 'http://opensource.org/licenses/MIT' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Qual' + description = 'checker-qual contains annotations (type qualifiers) that a programmer\n' + + 'writes to specify Java code for type-checking by the Checker Framework.\n' + licenses { + license { + name = 'The MIT License' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } } + } } publishing { - publications { - checkerQual(MavenPublication) { - checkerQualPom it - } + publications { + checkerQual(MavenPublication) { + checkerQualPom it } + } } signing { - sign publishing.publications.checkerQual + sign publishing.publications.checkerQual } diff --git a/checker-util/build.gradle b/checker-util/build.gradle index 693edfd979f..1d6daf11309 100644 --- a/checker-util/build.gradle +++ b/checker-util/build.gradle @@ -1,41 +1,41 @@ plugins { - id 'java-library' + id 'java-library' } dependencies { - api project(':checker-qual') - // Don't add implementation dependencies; checker-util.jar should have no dependencies. + api project(':checker-qual') + // Don't add implementation dependencies; checker-util.jar should have no dependencies. - testImplementation "junit:junit:${versions.junit}" + testImplementation "junit:junit:${versions.junit}" } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerUtilPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Util' - description = 'checker-util contains utility classes for programmers to use at run time.' - licenses { - license { - name = 'The MIT License' - url = 'http://opensource.org/licenses/MIT' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Util' + description = 'checker-util contains utility classes for programmers to use at run time.' + licenses { + license { + name = 'The MIT License' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } } + } } publishing { - publications { - checkerUtil(MavenPublication) { - checkerUtilPom it - } + publications { + checkerUtil(MavenPublication) { + checkerUtilPom it } + } } signing { - sign publishing.publications.checkerUtil + sign publishing.publications.checkerUtil } diff --git a/checker/build.gradle b/checker/build.gradle index caedf1412ce..abaae83b1a5 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -1,114 +1,114 @@ plugins { - id 'java-library' - id 'base' + id 'java-library' + id 'base' - // https://github.com/n0mer/gradle-git-properties - // Generates file build/resources/main/git.properties when the `classes` task runs. - id 'com.gorylenko.gradle-git-properties' version '2.4.2' + // https://github.com/n0mer/gradle-git-properties + // Generates file build/resources/main/git.properties when the `classes` task runs. + id 'com.gorylenko.gradle-git-properties' version '2.4.2' } sourceSets { - main { - java { - // NO-AFU - exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' - } - resources { - // Stub files, message.properties, etc. - srcDirs += ['src/main/java'] + main { + java { + // NO-AFU + exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' + } + resources { + // Stub files, message.properties, etc. + srcDirs += ['src/main/java'] - // NO-AFU - exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' - } + // NO-AFU + exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' } - testannotations { - java { - srcDirs = ['src/testannotations/java'] - } + } + testannotations { + java { + srcDirs = ['src/testannotations/java'] } + } } sourcesJar { - // The resources duplicate content from the src directory. - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + // The resources duplicate content from the src directory. + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } configurations { - implementation.extendsFrom(annotatedGuava) - fatJar { - canBeConsumed = true - canBeResolved = false - } + implementation.extendsFrom(annotatedGuava) + fatJar { + canBeConsumed = true + canBeResolved = false + } } dependencies { - // Use "api" instead of "implementation" to re-export sub-projects, making - // sure "minimize()" does not remove those classes. - api project(':framework') - - /* NO-AFU - // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. - // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite - implementation('org.checkerframework:annotation-file-utilities:*') { - exclude group: 'com.google.errorprone', module: 'javac' - } - */ - - api project(':checker-qual') - api project(':checker-util') - - // External dependencies: - // If you add an external dependency, you must shadow its packages. - // See the comment in ../build.gradle in the shadowJar block. - - // As of 2019-12-16, the version of reflection-util in the Annotation - // File Utilities takes priority over this version, in the fat jar - // file. :-( So update it and re-build it locally when updating this. - implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" - implementation "org.plumelib:plume-util:${versions.plumeUtil}" - - // Dependencies added to "shadow" appear as dependencies in Maven Central. - shadow project(':checker-qual') - shadow project(':checker-util') - - // Called Methods Checker AutoValue + Lombok support - testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" - testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" - testImplementation 'com.ryanharter.auto.value:auto-value-parcel:0.2.9' - testImplementation "org.projectlombok:lombok:${versions.lombok}" - // Called Methods Checker support for detecting misuses of AWS APIs - testImplementation 'com.amazonaws:aws-java-sdk-ec2' - testImplementation 'com.amazonaws:aws-java-sdk-kms' - // The AWS SDK is used for testing the Called Methods Checker. - testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.780') - // For the Resource Leak Checker's support for JavaEE. - testImplementation 'javax.servlet:javax.servlet-api:4.0.1' - // For the Resource Leak Checker's support for IOUtils. - testImplementation 'commons-io:commons-io:2.18.0' - // For the Nullness Checker test of junit-assertions.astub in JUnitNull.java - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' - testImplementation 'org.apiguardian:apiguardian-api:1.1.2' - // For tests that use JSpecify annotations - testImplementation 'org.jspecify:jspecify:1.0.0' - - // Required for checker/tests/index-initializedfields/RequireJavadoc.java - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - testImplementation 'org.plumelib:options:1.0.6' - } else { - testImplementation 'org.plumelib:options:2.0.3' - } - - testImplementation "junit:junit:${versions.junit}" - testImplementation project(':framework-test') - testImplementation sourceSets.testannotations.output - - testannotationsImplementation project(':checker-qual') + // Use "api" instead of "implementation" to re-export sub-projects, making + // sure "minimize()" does not remove those classes. + api project(':framework') + + /* NO-AFU + // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. + // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite + implementation('org.checkerframework:annotation-file-utilities:*') { + exclude group: 'com.google.errorprone', module: 'javac' + } + */ + + api project(':checker-qual') + api project(':checker-util') + + // External dependencies: + // If you add an external dependency, you must shadow its packages. + // See the comment in ../build.gradle in the shadowJar block. + + // As of 2019-12-16, the version of reflection-util in the Annotation + // File Utilities takes priority over this version, in the fat jar + // file. :-( So update it and re-build it locally when updating this. + implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" + implementation "org.plumelib:plume-util:${versions.plumeUtil}" + + // Dependencies added to "shadow" appear as dependencies in Maven Central. + shadow project(':checker-qual') + shadow project(':checker-util') + + // Called Methods Checker AutoValue + Lombok support + testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" + testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" + testImplementation 'com.ryanharter.auto.value:auto-value-parcel:0.2.9' + testImplementation "org.projectlombok:lombok:${versions.lombok}" + // Called Methods Checker support for detecting misuses of AWS APIs + testImplementation 'com.amazonaws:aws-java-sdk-ec2' + testImplementation 'com.amazonaws:aws-java-sdk-kms' + // The AWS SDK is used for testing the Called Methods Checker. + testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.780') + // For the Resource Leak Checker's support for JavaEE. + testImplementation 'javax.servlet:javax.servlet-api:4.0.1' + // For the Resource Leak Checker's support for IOUtils. + testImplementation 'commons-io:commons-io:2.18.0' + // For the Nullness Checker test of junit-assertions.astub in JUnitNull.java + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' + testImplementation 'org.apiguardian:apiguardian-api:1.1.2' + // For tests that use JSpecify annotations + testImplementation 'org.jspecify:jspecify:1.0.0' + + // Required for checker/tests/index-initializedfields/RequireJavadoc.java + if (JavaVersion.current() == JavaVersion.VERSION_1_8) { + testImplementation 'org.plumelib:options:1.0.6' + } else { + testImplementation 'org.plumelib:options:2.0.3' + } + + testImplementation "junit:junit:${versions.junit}" + testImplementation project(':framework-test') + testImplementation sourceSets.testannotations.output + + testannotationsImplementation project(':checker-qual') } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } // It's not clear why this dependencies exists, but Gradle issues the following warning: @@ -122,366 +122,366 @@ interface InjectedExecOps { generateGitProperties.dependsOn(':installGitHooks') jar { - manifest { - attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') - } + manifest { + attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') + } } // This task differs from the `assemble` task in that it does not build Javadoc. // It is useful for those who only want to run `javac`. // checker.jar is copied to checker/dist/ when it is built by the shadowJar task. task assembleForJavac(dependsOn: shadowJar, group: 'Build') { - description = 'Builds or downloads jars required by CheckerMain and puts them in checker/dist/.' - dependsOn project(':checker-qual').tasks.jar - def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) - def checkerUtilJarFile = file(project(':checker-util').tasks.getByName('jar').archiveFile) - - doLast { - if (!checkerQualJarFile.exists()) { - throw new GradleException('File not found: ' + checkerQualJarFile) - } - copy { - from checkerQualJarFile - into "${projectDir}/dist" - rename { String fileName -> - // remove version number on checker-qual.jar - fileName.replace(fileName, 'checker-qual.jar') - } - } + description = 'Builds or downloads jars required by CheckerMain and puts them in checker/dist/.' + dependsOn project(':checker-qual').tasks.jar + def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) + def checkerUtilJarFile = file(project(':checker-util').tasks.getByName('jar').archiveFile) + + doLast { + if (!checkerQualJarFile.exists()) { + throw new GradleException('File not found: ' + checkerQualJarFile) + } + copy { + from checkerQualJarFile + into "${projectDir}/dist" + rename { String fileName -> + // remove version number on checker-qual.jar + fileName.replace(fileName, 'checker-qual.jar') + } + } - if (!checkerUtilJarFile.exists()) { - throw new GradleException('File not found: ' + checkerUtilJarFile) - } - copy { - from checkerUtilJarFile - into "${projectDir}/dist" - rename { String fileName -> - // remove version number on checker-util.jar - fileName.replace(fileName, 'checker-util.jar') - } - } + if (!checkerUtilJarFile.exists()) { + throw new GradleException('File not found: ' + checkerUtilJarFile) + } + copy { + from checkerUtilJarFile + into "${projectDir}/dist" + rename { String fileName -> + // remove version number on checker-util.jar + fileName.replace(fileName, 'checker-util.jar') + } + } - copy { - // This is required to *run* the Checker Framework on JDK 8. - from configurations.javacJar - into "${projectDir}/dist" - rename { String fileName -> - fileName.replace(fileName, 'javac.jar') - } - } + copy { + // This is required to *run* the Checker Framework on JDK 8. + from configurations.javacJar + into "${projectDir}/dist" + rename { String fileName -> + fileName.replace(fileName, 'javac.jar') + } } + } } assemble.dependsOn assembleForJavac task allSourcesJar(type: Jar, group: 'Build') { - description = 'Creates a sources jar that includes sources for all Checker Framework classes in checker.jar' - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'checker-source.jar' - archiveClassifier = 'sources' - from (sourceSets.main.java, project(':framework').sourceSets.main.allJava, - project(':dataflow').sourceSets.main.allJava, project(':javacutil').sourceSets.main.allJava, - project(':checker-qual').sourceSets.main.allJava, project(':checker-util').sourceSets.main.allJava) + description = 'Creates a sources jar that includes sources for all Checker Framework classes in checker.jar' + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'checker-source.jar' + archiveClassifier = 'sources' + from (sourceSets.main.java, project(':framework').sourceSets.main.allJava, + project(':dataflow').sourceSets.main.allJava, project(':javacutil').sourceSets.main.allJava, + project(':checker-qual').sourceSets.main.allJava, project(':checker-util').sourceSets.main.allJava) } task allJavadocJar(type: Jar, group: 'Build') { - description = 'Creates javadoc jar including Javadoc for all of the Checker Framework' - dependsOn rootProject.tasks.allJavadoc - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'checker-javadoc.jar' - archiveClassifier = 'javadoc' - from rootProject.tasks.allJavadoc.destinationDir + description = 'Creates javadoc jar including Javadoc for all of the Checker Framework' + dependsOn rootProject.tasks.allJavadoc + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'checker-javadoc.jar' + archiveClassifier = 'javadoc' + from rootProject.tasks.allJavadoc.destinationDir } // Shadowing Test Sources and Dependencies import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar task checkerJar(type: ShadowJar, dependsOn: compileJava, group: 'Build') { - description = "Builds checker-${project.version}.jar with all dependencies except checker-qual and checker-util." - includeEmptyDirs = false - - from shadowJar.source - configurations = shadowJar.configurations - // To see what files are incorporated into the shadow jar file: - // doLast { println sourceSets.main.runtimeClasspath.asPath } - manifest { - attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') - } - exclude 'org/checkerframework/**/qual/*' - exclude 'org/checkerframework/checker/**/util/*' - relocators = shadowJar.getRelocators() + description = "Builds checker-${project.version}.jar with all dependencies except checker-qual and checker-util." + includeEmptyDirs = false + + from shadowJar.source + configurations = shadowJar.configurations + // To see what files are incorporated into the shadow jar file: + // doLast { println sourceSets.main.runtimeClasspath.asPath } + manifest { + attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') + } + exclude 'org/checkerframework/**/qual/*' + exclude 'org/checkerframework/checker/**/util/*' + relocators = shadowJar.getRelocators() } jar { - dependsOn(checkerJar) - // Never build the skinny jar. - onlyIf {false} - archiveClassifier = 'skinny' + dependsOn(checkerJar) + // Never build the skinny jar. + onlyIf {false} + archiveClassifier = 'skinny' } shadowJar { - description = 'Creates checker-VERSION-all.jar and copies it to dist/checker.jar.' - // To see what files are incorporated into the shadow jar file: - // doFirst { println sourceSets.main.runtimeClasspath.asPath } - archiveClassifier = 'all' - doLast{ - copy { - from archiveFile.get() - into file("${projectDir}/dist") - rename 'checker.*', 'checker.jar' - } + description = 'Creates checker-VERSION-all.jar and copies it to dist/checker.jar.' + // To see what files are incorporated into the shadow jar file: + // doFirst { println sourceSets.main.runtimeClasspath.asPath } + archiveClassifier = 'all' + doLast{ + copy { + from archiveFile.get() + into file("${projectDir}/dist") + rename 'checker.*', 'checker.jar' } + } - minimize() + minimize() } artifacts { - // Don't add this here or else the Javadoc and the sources jar is built during the assemble task. - // archives allJavadocJar - // archives allSourcesJar - archives shadowJar - archives checkerJar + // Don't add this here or else the Javadoc and the sources jar is built during the assemble task. + // archives allJavadocJar + // archives allSourcesJar + archives shadowJar + archives checkerJar - fatJar(shadowJar) + fatJar(shadowJar) } clean { - def injected = project.objects.newInstance(InjectedExecOps) - - delete("${projectDir}/dist") - delete('tests/calledmethods-delomboked') - delete('tests/ainfer-testchecker/annotated') - delete('tests/ainfer-testchecker/inference-output') - delete('tests/ainfer-nullness/annotated') - delete('tests/ainfer-nullness/inference-output') - delete('tests/ainfer-index/annotated') - delete('tests/ainfer-index/inference-output') - delete('tests/ainfer-resourceleak/annotated') - delete('tests/ainfer-resourceleak/inference-output') - delete('tests/build') - doLast { - injected.execOps.exec { - workingDir = file('tests/command-line') - commandLine 'make', 'clean' - } - injected.execOps.exec { - workingDir = file('tests/nullness-extra') - commandLine 'make', 'clean' - } + def injected = project.objects.newInstance(InjectedExecOps) + + delete("${projectDir}/dist") + delete('tests/calledmethods-delomboked') + delete('tests/ainfer-testchecker/annotated') + delete('tests/ainfer-testchecker/inference-output') + delete('tests/ainfer-nullness/annotated') + delete('tests/ainfer-nullness/inference-output') + delete('tests/ainfer-index/annotated') + delete('tests/ainfer-index/inference-output') + delete('tests/ainfer-resourceleak/annotated') + delete('tests/ainfer-resourceleak/inference-output') + delete('tests/build') + doLast { + injected.execOps.exec { + workingDir = file('tests/command-line') + commandLine 'make', 'clean' + } + injected.execOps.exec { + workingDir = file('tests/nullness-extra') + commandLine 'make', 'clean' } + } } clean.doLast { - while (buildDir.exists()) { - sleep(10000) // wait 10 seconds - buildDir.deleteDir() - } + while (buildDir.exists()) { + sleep(10000) // wait 10 seconds + buildDir.deleteDir() + } } // Add non-junit tests createCheckTypeTask(project.name, 'CompilerMessages', - 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') + 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') checkCompilerMessages { - doFirst { - options.compilerArgs += [ - '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + File.pathSeparator - + project(':framework').sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath - ] - } + doFirst { + options.compilerArgs += [ + '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + File.pathSeparator + + project(':framework').sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + ] + } } task nullnessExtraTests(type: Exec, dependsOn: assembleForJavac, group: 'Verification') { - description = 'Run extra tests for the Nullness Checker.' - executable 'make' - environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck", JAVAP: 'javap' - args = ['-C', 'tests/nullness-extra/'] + description = 'Run extra tests for the Nullness Checker.' + executable 'make' + environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck", JAVAP: 'javap' + args = ['-C', 'tests/nullness-extra/'] } task commandLineTests(type: Exec, dependsOn: assembleForJavac, group: 'Verification') { - description = 'Run tests that need a special command line.' - executable 'make' - environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" - args = ['-C', 'tests/command-line/'] + description = 'Run tests that need a special command line.' + executable 'make' + environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" + args = ['-C', 'tests/command-line/'] } task tutorialTests(dependsOn: assembleForJavac, group: 'Verification') { - description = 'Test that the tutorial is working as expected.' - doLast { - ant.ant(dir: "${rootDir}/docs/tutorial/tests", useNativeBasedir: 'true', inheritAll: 'false') { - target(name: 'check-tutorial') - } + description = 'Test that the tutorial is working as expected.' + doLast { + ant.ant(dir: "${rootDir}/docs/tutorial/tests", useNativeBasedir: 'true', inheritAll: 'false') { + target(name: 'check-tutorial') } + } } task exampleTests(type: Exec, dependsOn: assembleForJavac, group: 'Verification') { - description = 'Run tests for the example programs.' - executable 'make' - environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" - args = ['-C', '../docs/examples'] + description = 'Run tests for the example programs.' + executable 'make' + environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" + args = ['-C', '../docs/examples'] } task demosTests(dependsOn: assembleForJavac, group: 'Verification') { - description = 'Test that the demos are working as expected.' - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - File demosDir = new File(projectDir, '../../checker-framework.demos'); - if (!demosDir.exists()) { - injected.execOps.exec { - workingDir file(demosDir.toString() + '/../') - executable 'git' - args = [ - 'clone', - '--depth', - '1', - 'https://github.com/eisop/checker-framework.demos.git' - ] - } - } else { - injected.execOps.exec { - workingDir demosDir - executable 'git' - args = [ - 'pull', - 'https://github.com/eisop/checker-framework.demos.git' - ] - ignoreExitValue = true - } - } - ant.properties.put('checker.lib', file("${projectDir}/dist/checker.jar").absolutePath) - ant.ant(dir: demosDir.toString()) + description = 'Test that the demos are working as expected.' + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + File demosDir = new File(projectDir, '../../checker-framework.demos'); + if (!demosDir.exists()) { + injected.execOps.exec { + workingDir file(demosDir.toString() + '/../') + executable 'git' + args = [ + 'clone', + '--depth', + '1', + 'https://github.com/eisop/checker-framework.demos.git' + ] + } + } else { + injected.execOps.exec { + workingDir demosDir + executable 'git' + args = [ + 'pull', + 'https://github.com/eisop/checker-framework.demos.git' + ] + ignoreExitValue = true + } } + ant.properties.put('checker.lib', file("${projectDir}/dist/checker.jar").absolutePath) + ant.ant(dir: demosDir.toString()) + } } task templateTests(dependsOn: assembleForJavac, group: 'Verification') { - description = 'Test that the templatefora-checker is working as expected.' - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - File templateforCheckerDir = new File(projectDir, '../../templatefora-checker'); - if (!templateforCheckerDir.exists()) { - injected.execOps.exec { - workingDir file(templateforCheckerDir.toString() + '/../') - executable 'git' - args = [ - 'clone', - '--depth', - '1', - 'https://github.com/eisop/templatefora-checker.git' - ] - } - } else { - injected.execOps.exec { - workingDir templateforCheckerDir - executable 'git' - args = [ - 'pull', - 'https://github.com/eisop/templatefora-checker.git' - ] - ignoreExitValue = true - } - } - println "Running Gradle build in $templateforCheckerDir" - injected.execOps.exec { - workingDir = templateforCheckerDir - commandLine "./gradlew", "build" - } + description = 'Test that the templatefora-checker is working as expected.' + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + File templateforCheckerDir = new File(projectDir, '../../templatefora-checker'); + if (!templateforCheckerDir.exists()) { + injected.execOps.exec { + workingDir file(templateforCheckerDir.toString() + '/../') + executable 'git' + args = [ + 'clone', + '--depth', + '1', + 'https://github.com/eisop/templatefora-checker.git' + ] + } + } else { + injected.execOps.exec { + workingDir templateforCheckerDir + executable 'git' + args = [ + 'pull', + 'https://github.com/eisop/templatefora-checker.git' + ] + ignoreExitValue = true + } + } + println "Running Gradle build in $templateforCheckerDir" + injected.execOps.exec { + workingDir = templateforCheckerDir + commandLine "./gradlew", "build" } + } } task allNullnessTests(type: Test, group: 'Verification') { - description = 'Run all JUnit tests for the Nullness Checker.' - include '**/Nullness*.class' + description = 'Run all JUnit tests for the Nullness Checker.' + include '**/Nullness*.class' } task allCalledMethodsTests(type: Test, group: 'Verification') { - description = 'Run all JUnit tests for the Called Methods Checker.' - include '**/CalledMethods*.class' - if (!skipDelombok) { - dependsOn 'delombok' - } + description = 'Run all JUnit tests for the Called Methods Checker.' + include '**/CalledMethods*.class' + if (!skipDelombok) { + dependsOn 'delombok' + } } task allResourceLeakTests(type: Test, group: 'Verification') { - description = 'Run all JUnit tests for the Resource Leak Checker.' - include '**/ResourceLeak*.class' - include '**/MustCall*.class' + description = 'Run all JUnit tests for the Resource Leak Checker.' + include '**/ResourceLeak*.class' + include '**/MustCall*.class' } // These are tests that should only be run with JDK 11+. task jtregJdk11Tests(dependsOn: ':downloadJtreg', group: 'Verification') { - description = 'Run the jtreg tests made for JDK 11+.' - dependsOn('compileJava') - dependsOn('compileTestJava') - dependsOn('shadowJar') - - def injected = project.objects.newInstance(InjectedExecOps) - - String jtregOutput = "${buildDir}/jtregJdk11" - String name = 'all' - doLast { - if (isJava8) { - println 'This test is only run with JDK 11+.' - return; - } - injected.execOps.exec { - executable "${jtregHome}/bin/jtreg" - args = [ - "-dir:${projectDir}/jtregJdk11", - "-workDir:${jtregOutput}/${name}/work", - "-reportDir:${jtregOutput}/${name}/report", - '-verbose:summary', - '-javacoptions:-g', - '-keywords:!ignore', - '-samevm', - "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", - // Location of jtreg tests - '.' - ] - } + description = 'Run the jtreg tests made for JDK 11+.' + dependsOn('compileJava') + dependsOn('compileTestJava') + dependsOn('shadowJar') + + def injected = project.objects.newInstance(InjectedExecOps) + + String jtregOutput = "${buildDir}/jtregJdk11" + String name = 'all' + doLast { + if (isJava8) { + println 'This test is only run with JDK 11+.' + return; } + injected.execOps.exec { + executable "${jtregHome}/bin/jtreg" + args = [ + "-dir:${projectDir}/jtregJdk11", + "-workDir:${jtregOutput}/${name}/work", + "-reportDir:${jtregOutput}/${name}/report", + '-verbose:summary', + '-javacoptions:-g', + '-keywords:!ignore', + '-samevm', + "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", + // Location of jtreg tests + '.' + ] + } + } } task delombok { - description = 'Delomboks the source code tree in tests/calledmethods-lombok' + description = 'Delomboks the source code tree in tests/calledmethods-lombok' - def srcDelomboked = 'tests/calledmethods-delomboked' - def srcJava = 'tests/calledmethods-lombok' + def srcDelomboked = 'tests/calledmethods-delomboked' + def srcJava = 'tests/calledmethods-lombok' - inputs.files file(srcJava) - outputs.dir file(srcDelomboked) + inputs.files file(srcJava) + outputs.dir file(srcDelomboked) - // Because there are Checker Framework annotations in the test source. - dependsOn project(':checker-qual').tasks.jar + // Because there are Checker Framework annotations in the test source. + dependsOn project(':checker-qual').tasks.jar - doLast { - if(!skipDelombok) { - def collection = files(configurations.testCompileClasspath) - ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', - classpath: collection.asPath) - ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) - } + doLast { + if(!skipDelombok) { + def collection = files(configurations.testCompileClasspath) + ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', + classpath: collection.asPath) + ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) } + } } if (skipDelombok) { - delombok.enabled = false + delombok.enabled = false } else { - tasks.test.dependsOn('delombok') + tasks.test.dependsOn('delombok') } /// @@ -489,134 +489,134 @@ if (skipDelombok) { /// test { - useJUnit { - // These are run in task ainferTest. - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerJaifsGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerStubsGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerAjavaGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessJaifsGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessAjavaGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferIndexAjavaGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferResourceLeakAjavaGenerationTest' - } + useJUnit { + // These are run in task ainferTest. + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerJaifsGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerStubsGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerAjavaGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessJaifsGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessAjavaGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferIndexAjavaGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferResourceLeakAjavaGenerationTest' + } } task ainferTestCheckerGenerateStubs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker files with -Ainfer=stubs to generate stub files.' + description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker files with -Ainfer=stubs to generate stub files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-testchecker/annotated') - delete("${buildDir}/ainfer-testchecker/") - } + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-testchecker/annotated') + delete("${buildDir}/ainfer-testchecker/") + } + outputs.upToDateWhen { false } + include '**/AinferTestCheckerStubsGenerationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferTestCheckerStubsGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') - // The stub file format doesn't support annotations on anonymous inner classes, so - // this test also expects errors on these tests that expect annotations to be inferred - // inside anonymous classes. - delete('tests/ainfer-testchecker/annotated/UsesAnonymous.java') - delete('tests/ainfer-testchecker/annotated/AnonymousClassWithField.java') - - // This test outputs a warning about records. - delete('tests/ainfer-testchecker/annotated/all-systems/java17/Issue6069.java') - - // This test causes an error when its corresponding stub file is read, because the test - // contains an annotation definition. The stub file parser does not support reading - // files that define annotations; this test can be reinstated if the stub parser - // is extended to support annotation definitions. - delete('tests/ainfer-testchecker/annotated/all-systems/Issue4083.java') - - copy { - from file('tests/ainfer-testchecker/non-annotated/UsesAnonymous.java') - into file('tests/ainfer-testchecker/annotated') - } + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } + + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') + // The stub file format doesn't support annotations on anonymous inner classes, so + // this test also expects errors on these tests that expect annotations to be inferred + // inside anonymous classes. + delete('tests/ainfer-testchecker/annotated/UsesAnonymous.java') + delete('tests/ainfer-testchecker/annotated/AnonymousClassWithField.java') + + // This test outputs a warning about records. + delete('tests/ainfer-testchecker/annotated/all-systems/java17/Issue6069.java') + + // This test causes an error when its corresponding stub file is read, because the test + // contains an annotation definition. The stub file parser does not support reading + // files that define annotations; this test can be reinstated if the stub parser + // is extended to support annotation definitions. + delete('tests/ainfer-testchecker/annotated/all-systems/Issue4083.java') + + copy { + from file('tests/ainfer-testchecker/non-annotated/UsesAnonymous.java') + into file('tests/ainfer-testchecker/annotated') } + } } task ainferTestCheckerValidateStubs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker tests using the stub files generated by ainferTestCheckerGenerateStubs.' + description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker tests using the stub files generated by ainferTestCheckerGenerateStubs.' - dependsOn(ainferTestCheckerGenerateStubs) + dependsOn(ainferTestCheckerGenerateStubs) + outputs.upToDateWhen { false } + include '**/AinferTestCheckerStubsValidationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferTestCheckerStubsValidationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferTestCheckerGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This type-checks the ainfer-testchecker files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This type-checks the ainfer-testchecker files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-testchecker/annotated') - delete("${buildDir}/ainfer-testchecker/") - } + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-testchecker/annotated') + delete("${buildDir}/ainfer-testchecker/") + } + outputs.upToDateWhen { false } + include '**/AinferTestCheckerAjavaGenerationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferTestCheckerAjavaGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') - - // AinferTestCheckerAjavaValidationTest fails with "warning: (purity.methodref)", whenever - // there is a user-defined generic interface, and a variable of that type is assigned a - // method reference. - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Issue946.java') - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Receivers.java') - - // This test must be deleted, because otherwise an error about a missing type in an - // ajava file is issued. The test itself shouldn't be run as an all-systems test while testing - // WPI; see the copy in the non-annotated WPI tests for an explanation. - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Purity.java') - - // There is some kind of bad interaction between the purity checker's inference mode - // and method references to constructors: every one of them in this test causes a - // purity.methodref warning during validation. This problem only occurs for ajava-based - // inference because the relevant purity annotations that seem to trigger it are on - // inner classes, which stubs cannot annotate. - // TODO: investigate the cause of this error in the Purity checker, fix it, and then reinstate this test. - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/MemberReferences.java') - } + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } + + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') + + // AinferTestCheckerAjavaValidationTest fails with "warning: (purity.methodref)", whenever + // there is a user-defined generic interface, and a variable of that type is assigned a + // method reference. + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Issue946.java') + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Receivers.java') + + // This test must be deleted, because otherwise an error about a missing type in an + // ajava file is issued. The test itself shouldn't be run as an all-systems test while testing + // WPI; see the copy in the non-annotated WPI tests for an explanation. + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Purity.java') + + // There is some kind of bad interaction between the purity checker's inference mode + // and method references to constructors: every one of them in this test causes a + // purity.methodref warning during validation. This problem only occurs for ajava-based + // inference because the relevant purity annotations that seem to trigger it are on + // inner classes, which stubs cannot annotate. + // TODO: investigate the cause of this error in the Purity checker, fix it, and then reinstate this test. + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/MemberReferences.java') + } } task ainferTestCheckerValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This re-type-checks the ainfer-testchecker files using the ajava files generated by ainferTestCheckerGenerateAjava' + description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This re-type-checks the ainfer-testchecker files using the ajava files generated by ainferTestCheckerGenerateAjava' - dependsOn(ainferTestCheckerGenerateAjava) + dependsOn(ainferTestCheckerGenerateAjava) + outputs.upToDateWhen { false } + include '**/AinferTestCheckerAjavaValidationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferTestCheckerAjavaValidationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } // Copies directories as needed by WPI tests. @@ -625,381 +625,381 @@ task ainferTestCheckerValidateAjava(type: Test) { // 1. Copies whole-program inference test source code from the non-annotated/ to the annotated/ directory. // 2. Copies WPI output, such as .jaif or .stub files, to the inferference-output/ directory. void copyNonannotatedToAnnotatedDirectory(String testdir) { - // Copying all test files to another directory, removing all expected errors that should not - // occur after inserting inferred annotations from .jaif files. - copy { - from files("tests/${testdir}/non-annotated") - into file("tests/${testdir}/annotated") - filter { String line -> - (line.contains('// :: error:') - // Don't remove unchecked cast warnings, because they're genuinely expected in some all-systems - // tests, such as GenericsCasts.java. - || (line.contains('// :: warning:') && !line.contains('// :: warning: [unchecked]'))) - ? null : line - } - } - // The only file for which expected errors are maintained is ExpectedErrors.java, so we copy it over. - delete("tests/${testdir}/annotated/ExpectedErrors.java") - copy { - from file("tests/${testdir}/non-annotated/ExpectedErrors.java") - into file("tests/${testdir}/annotated") + // Copying all test files to another directory, removing all expected errors that should not + // occur after inserting inferred annotations from .jaif files. + copy { + from files("tests/${testdir}/non-annotated") + into file("tests/${testdir}/annotated") + filter { String line -> + (line.contains('// :: error:') + // Don't remove unchecked cast warnings, because they're genuinely expected in some all-systems + // tests, such as GenericsCasts.java. + || (line.contains('// :: warning:') && !line.contains('// :: warning: [unchecked]'))) + ? null : line } - - delete("tests/${testdir}/inference-output") - file('build/whole-program-inference').renameTo(file("tests/${testdir}/inference-output")) + } + // The only file for which expected errors are maintained is ExpectedErrors.java, so we copy it over. + delete("tests/${testdir}/annotated/ExpectedErrors.java") + copy { + from file("tests/${testdir}/non-annotated/ExpectedErrors.java") + into file("tests/${testdir}/annotated") + } + + delete("tests/${testdir}/inference-output") + file('build/whole-program-inference').renameTo(file("tests/${testdir}/inference-output")) } // This task is similar to the ainferTestCheckerJaifTest task below, but it doesn't // run the insert-annotations-to-source tool. Instead, it tests the -Ainfer=stubs feature // and the -AmergeStubsWithSource feature to do WPI using stub files. task ainferTestCheckerStubTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using stub files' - dependsOn(ainferTestCheckerValidateStubs) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using stub files' + dependsOn(ainferTestCheckerValidateStubs) + outputs.upToDateWhen { false } } // Like ainferTestCheckerStubTest, but with ajava files instead task ainferTestCheckerAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files' - dependsOn(ainferTestCheckerValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files' + dependsOn(ainferTestCheckerValidateAjava) + outputs.upToDateWhen { false } } task ainferTestCheckerGenerateJaifs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files with -Ainfer=jaifs to generate .jaif files' + description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files with -Ainfer=jaifs to generate .jaif files' - dependsOn(compileTestJava) - dependsOn(':checker-qual:jar') // For the Value Checker annotations. - doFirst { - delete('tests/ainfer-testchecker/annotated') - } + dependsOn(compileTestJava) + dependsOn(':checker-qual:jar') // For the Value Checker annotations. + doFirst { + delete('tests/ainfer-testchecker/annotated') + } + outputs.upToDateWhen { false } + include '**/AinferTestCheckerJaifsGenerationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferTestCheckerJaifsGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') - - // JAIF-based WPI fails these tests, which were added for stub-based WPI. - // See issue here: https://github.com/typetools/checker-framework/issues/3009 - delete('tests/ainfer-testchecker/annotated/ConflictingAnnotationsTest.java') - delete('tests/ainfer-testchecker/annotated/MultiDimensionalArrays.java') - - // JAIF-based WPI also fails this test. It used to pass, but the test was changed - // in a way that exposed a bug in the Annotation File Utilities: the AFU - // places annotations incorrectly on qualified types. In this test, a failure occurs because - // the AFU prints "@Annotation Outer.Inner this", rather than "Outer.@Annotation Inner this" - // (see an explanation of the syntax here: - // https://eisop.github.io/cf/manual/#common-problems-non-typechecking). - // TODO: fix this bug in the AFU, then reinstate this test. - delete('tests/ainfer-testchecker/annotated/OverriddenMethodsTest.java') - - // JAIF-based WPI fails this test, too, because the insertion of a declaration annotation - // onto a field with a multi-part type (e.g. Outer.Inner) doesn't appear to be supported by the AFU. - delete('tests/ainfer-testchecker/annotated/InnerClassFieldDeclAnno.java') - - // Inserting annotations from .jaif files in-place. - String jaifsDir = 'tests/ainfer-testchecker/inference-output'; - List jaifs = fileTree(jaifsDir).matching { - include '*.jaif' - }.asList() - if (jaifs.isEmpty()) { - throw new GradleException("no .jaif files found in ${jaifsDir}") - } - String javasDir = 'tests/ainfer-testchecker/annotated/'; - List javas = fileTree(javasDir).matching { - include '*.java' - }.asList() - if (javas.isEmpty()) { - throw new GradleException("no .java files found in ${javasDir}") - } + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') + + // JAIF-based WPI fails these tests, which were added for stub-based WPI. + // See issue here: https://github.com/typetools/checker-framework/issues/3009 + delete('tests/ainfer-testchecker/annotated/ConflictingAnnotationsTest.java') + delete('tests/ainfer-testchecker/annotated/MultiDimensionalArrays.java') + + // JAIF-based WPI also fails this test. It used to pass, but the test was changed + // in a way that exposed a bug in the Annotation File Utilities: the AFU + // places annotations incorrectly on qualified types. In this test, a failure occurs because + // the AFU prints "@Annotation Outer.Inner this", rather than "Outer.@Annotation Inner this" + // (see an explanation of the syntax here: + // https://eisop.github.io/cf/manual/#common-problems-non-typechecking). + // TODO: fix this bug in the AFU, then reinstate this test. + delete('tests/ainfer-testchecker/annotated/OverriddenMethodsTest.java') + + // JAIF-based WPI fails this test, too, because the insertion of a declaration annotation + // onto a field with a multi-part type (e.g. Outer.Inner) doesn't appear to be supported by the AFU. + delete('tests/ainfer-testchecker/annotated/InnerClassFieldDeclAnno.java') + + // Inserting annotations from .jaif files in-place. + String jaifsDir = 'tests/ainfer-testchecker/inference-output'; + List jaifs = fileTree(jaifsDir).matching { + include '*.jaif' + }.asList() + if (jaifs.isEmpty()) { + throw new GradleException("no .jaif files found in ${jaifsDir}") + } + String javasDir = 'tests/ainfer-testchecker/annotated/'; + List javas = fileTree(javasDir).matching { + include '*.java' + }.asList() + if (javas.isEmpty()) { + throw new GradleException("no .java files found in ${javasDir}") + } - def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) - injected.execOps.exec { - executable "${afu}/scripts/insert-annotations-to-source" - // Script argument -cp must precede Java program argument -i. - // checker-qual is needed for Constant Value Checker annotations. - // Note that "/" works on Windows as well as on Linux. - args = [ - '-cp', - "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') - ] - args += ['-i'] - for (File jaif : jaifs) { - args += [jaif.toString()] - } - for (File javaFile : javas) { - args += [javaFile.toString()] - } - } + def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) + injected.execOps.exec { + executable "${afu}/scripts/insert-annotations-to-source" + // Script argument -cp must precede Java program argument -i. + // checker-qual is needed for Constant Value Checker annotations. + // Note that "/" works on Windows as well as on Linux. + args = [ + '-cp', + "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') + ] + args += ['-i'] + for (File jaif : jaifs) { + args += [jaif.toString()] + } + for (File javaFile : javas) { + args += [javaFile.toString()] + } } + } } task ainferTestCheckerValidateJaifs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files using the .jaif files generated by ainferTestCheckerGenerateJaifs' + description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files using the .jaif files generated by ainferTestCheckerGenerateJaifs' - dependsOn(ainferTestCheckerGenerateJaifs) + dependsOn(ainferTestCheckerGenerateJaifs) + outputs.upToDateWhen { false } + include '**/AinferTestCheckerJaifsValidationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferTestCheckerJaifsValidationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferTestCheckerJaifTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using .jaif files' - dependsOn(ainferTestCheckerValidateJaifs) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using .jaif files' + dependsOn(ainferTestCheckerValidateJaifs) + outputs.upToDateWhen { false } } task ainferIndexGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferIndexAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferIndexAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-index/annotated') - delete("${buildDir}/ainfer-index/") - } + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-index/annotated') + delete("${buildDir}/ainfer-index/") + } + outputs.upToDateWhen { false } + include '**/AinferIndexAjavaGenerationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferIndexAjavaGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-index') - } + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } + + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-index') + } } task ainferIndexValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferIndexAjavaTest instead. This re-type-checks the ainfer-index files using the ajava files generated by ainferIndexGenerateAjava' + description = 'Internal task. Users should run ainferIndexAjavaTest instead. This re-type-checks the ainfer-index files using the ajava files generated by ainferIndexGenerateAjava' - dependsOn(ainferIndexGenerateAjava) + dependsOn(ainferIndexGenerateAjava) + outputs.upToDateWhen { false } + include '**/AinferIndexAjavaValidationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferIndexAjavaValidationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferIndexAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files and the Index Checker' - dependsOn(ainferIndexValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files and the Index Checker' + dependsOn(ainferIndexValidateAjava) + outputs.upToDateWhen { false } } task ainferNullnessGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This type-checks the ainfer-nullness files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This type-checks the ainfer-nullness files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-nullness/annotated') - delete("${buildDir}/ainfer-nullness/") - } + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-nullness/annotated') + delete("${buildDir}/ainfer-nullness/") + } + outputs.upToDateWhen { false } + include '**/AinferNullnessAjavaGenerationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferNullnessAjavaGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-nullness') - } + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } + + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-nullness') + } } task ainferNullnessValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This re-type-checks the ainfer-nullness files using the ajava files generated by ainferNullnessGenerateAjava' + description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This re-type-checks the ainfer-nullness files using the ajava files generated by ainferNullnessGenerateAjava' - dependsOn(ainferNullnessGenerateAjava) + dependsOn(ainferNullnessGenerateAjava) + outputs.upToDateWhen { false } + include '**/AinferNullnessAjavaValidationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferNullnessAjavaValidationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferNullnessAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files and the Nullness Checker' - dependsOn(ainferNullnessValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files and the Nullness Checker' + dependsOn(ainferNullnessValidateAjava) + outputs.upToDateWhen { false } } task ainferResourceLeakGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-resourceleak/annotated') - delete("${buildDir}/ainfer-resourceleak/") - } + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-resourceleak/annotated') + delete("${buildDir}/ainfer-resourceleak/") + } + outputs.upToDateWhen { false } + include '**/AinferResourceLeakAjavaGenerationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferResourceLeakAjavaGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-resourceleak') - } + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } + + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-resourceleak') + } } task ainferResourceLeakValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This re-type-checks the ainfer-resourceleak files using the ajava files generated by ainferResourceLeakGenerateAjava' + description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This re-type-checks the ainfer-resourceleak files using the ajava files generated by ainferResourceLeakGenerateAjava' - dependsOn(ainferResourceLeakGenerateAjava) + dependsOn(ainferResourceLeakGenerateAjava) + outputs.upToDateWhen { false } + include '**/AinferResourceLeakAjavaValidationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferResourceLeakAjavaValidationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferResourceLeakAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files and the Resource Leak Checker' - dependsOn(ainferResourceLeakValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files and the Resource Leak Checker' + dependsOn(ainferResourceLeakValidateAjava) + outputs.upToDateWhen { false } } task ainferNullnessGenerateJaifs(type: Test) { - description = 'Internal task. Users should run ainferNullnessJaifTest instead. This type-checks the ainfer-nullness files with -Ainfer=jaifs to generate .jaif files' + description = 'Internal task. Users should run ainferNullnessJaifTest instead. This type-checks the ainfer-nullness files with -Ainfer=jaifs to generate .jaif files' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-nullness/annotated') - } + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-nullness/annotated') + } + outputs.upToDateWhen { false } + include '**/AinferNullnessJaifsGenerationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferNullnessJaifsGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - def injected = project.objects.newInstance(InjectedExecOps) + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-nullness') + def injected = project.objects.newInstance(InjectedExecOps) - // JAIF-based WPI does not infer annotations on uses of type variables correctly. - delete('tests/ainfer-nullness/annotated/TwoCtorGenericAbstract.java') - delete('tests/ainfer-nullness/annotated/TypeVarReturnAnnotated.java') + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-nullness') - // Inserting annotations from .jaif files in-place. - String jaifsDir = 'tests/ainfer-nullness/inference-output'; - List jaifs = fileTree(jaifsDir).matching { - include '*.jaif' - }.asList() - if (jaifs.isEmpty()) { - throw new GradleException("no .jaif files found in ${jaifsDir}") - } - String javasDir = 'tests/ainfer-nullness/annotated/'; - List javas = fileTree(javasDir).matching { - include '*.java' - }.asList() - if (javas.isEmpty()) { - throw new GradleException("no .java files found in ${javasDir}") - } - def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) - injected.execOps.exec { - executable "${afu}/scripts/insert-annotations-to-source" - // Script argument -cp must precede Java program argument -i. - // Note that "/" works on Windows as well as on Linux. - args = [ - '-cp', - "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') - ] - args += ['-i'] - for (File jaif : jaifs) { - args += [jaif.toString()] - } - for (File javaFile : javas) { - args += [javaFile.toString()] - } - } + // JAIF-based WPI does not infer annotations on uses of type variables correctly. + delete('tests/ainfer-nullness/annotated/TwoCtorGenericAbstract.java') + delete('tests/ainfer-nullness/annotated/TypeVarReturnAnnotated.java') + + // Inserting annotations from .jaif files in-place. + String jaifsDir = 'tests/ainfer-nullness/inference-output'; + List jaifs = fileTree(jaifsDir).matching { + include '*.jaif' + }.asList() + if (jaifs.isEmpty()) { + throw new GradleException("no .jaif files found in ${jaifsDir}") } + String javasDir = 'tests/ainfer-nullness/annotated/'; + List javas = fileTree(javasDir).matching { + include '*.java' + }.asList() + if (javas.isEmpty()) { + throw new GradleException("no .java files found in ${javasDir}") + } + def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) + injected.execOps.exec { + executable "${afu}/scripts/insert-annotations-to-source" + // Script argument -cp must precede Java program argument -i. + // Note that "/" works on Windows as well as on Linux. + args = [ + '-cp', + "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') + ] + args += ['-i'] + for (File jaif : jaifs) { + args += [jaif.toString()] + } + for (File javaFile : javas) { + args += [javaFile.toString()] + } + } + } } task ainferNullnessValidateJaifs(type: Test) { - description = 'Internal task. Users should run ainferNullnessJaifTest instead. This re-type-checks the ainfer-nullness files using the .jaif files generated by ainferNullnessGenerateJaifs' + description = 'Internal task. Users should run ainferNullnessJaifTest instead. This re-type-checks the ainfer-nullness files using the .jaif files generated by ainferNullnessGenerateJaifs' - dependsOn(ainferNullnessGenerateJaifs) + dependsOn(ainferNullnessGenerateJaifs) + outputs.upToDateWhen { false } + include '**/AinferNullnessJaifsValidationTest.class' + testLogging { + // Always run the tests outputs.upToDateWhen { false } - include '**/AinferNullnessJaifsValidationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferNullnessJaifTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using .jaif files' - dependsOn(ainferNullnessValidateJaifs) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using .jaif files' + dependsOn(ainferNullnessValidateJaifs) + outputs.upToDateWhen { false } } // Empty task that just runs both the jaif and stub WPI tests. // It is run as part of the inferenceTests task. task ainferTest(group: 'Verification') { - description = 'Run tests for all whole program inference modes.' - dependsOn('ainferTestCheckerJaifTest') - dependsOn('ainferTestCheckerStubTest') - dependsOn('ainferTestCheckerAjavaTest') - dependsOn('ainferNullnessJaifTest') - dependsOn('ainferNullnessAjavaTest') - dependsOn('ainferIndexAjavaTest') - dependsOn('ainferResourceLeakAjavaTest') + description = 'Run tests for all whole program inference modes.' + dependsOn('ainferTestCheckerJaifTest') + dependsOn('ainferTestCheckerStubTest') + dependsOn('ainferTestCheckerAjavaTest') + dependsOn('ainferNullnessJaifTest') + dependsOn('ainferNullnessAjavaTest') + dependsOn('ainferIndexAjavaTest') + dependsOn('ainferResourceLeakAjavaTest') } /// @@ -1008,196 +1008,196 @@ task ainferTest(group: 'Verification') { // This is run as part of the inferenceTests task. task wpiManyTest(group: 'Verification') { - description = 'Tests the wpi-many.sh script (and indirectly the wpi.sh script). Requires an Internet connection.' - dependsOn(assembleForJavac) - dependsOn(':getDoLikeJavac') - // This test must always be re-run when requested. - outputs.upToDateWhen { false } - - doFirst { - delete("${project.projectDir}/build/wpi-many-tests-results/") - // wpi-many.sh is run in skip mode so that logs are preserved, but - // we don't actually want to skip previously-failing tests when we - // re-run the tests locally. - delete fileTree("${project.projectDir}/build/wpi-many-tests") { - include '**/.cannot-run-wpi' + description = 'Tests the wpi-many.sh script (and indirectly the wpi.sh script). Requires an Internet connection.' + dependsOn(assembleForJavac) + dependsOn(':getDoLikeJavac') + // This test must always be re-run when requested. + outputs.upToDateWhen { false } + + doFirst { + delete("${project.projectDir}/build/wpi-many-tests-results/") + // wpi-many.sh is run in skip mode so that logs are preserved, but + // we don't actually want to skip previously-failing tests when we + // re-run the tests locally. + delete fileTree("${project.projectDir}/build/wpi-many-tests") { + include '**/.cannot-run-wpi' + } + } + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + // Run wpi-many.sh + def typecheckFilesDir = "${project.projectDir}/build/wpi-many-tests-results/" + try { + injected.execOps.exec { + environment CHECKERFRAMEWORK: "${projectDir}/.." + commandLine 'bin/wpi-many.sh', + '-i', "${project.projectDir}/tests/wpi-many/testin.txt", + '-o', "${project.projectDir}/build/wpi-many-tests", + '-s', + '--', + '--checker', 'nullness,interning,lock,regex,signature,calledmethods,resourceleak', + '--extraJavacArgs=-AenableWpiForRlc' + } + } catch (Exception e) { + println('Failure: Running wpi-many.sh failed with a non-zero exit code.') + File wpiOut = new File("${typecheckFilesDir}/wpi-out") + if (wpiOut.exists()) { + println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + injected.execOps.exec { + commandLine 'cat', "${typecheckFilesDir}/wpi-out" } + println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + } else { + println("========= File ${typecheckFilesDir}/wpi-out does not exist. ========") + } + throw e } - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - // Run wpi-many.sh - def typecheckFilesDir = "${project.projectDir}/build/wpi-many-tests-results/" - try { - injected.execOps.exec { - environment CHECKERFRAMEWORK: "${projectDir}/.." - commandLine 'bin/wpi-many.sh', - '-i', "${project.projectDir}/tests/wpi-many/testin.txt", - '-o', "${project.projectDir}/build/wpi-many-tests", - '-s', - '--', - '--checker', 'nullness,interning,lock,regex,signature,calledmethods,resourceleak', - '--extraJavacArgs=-AenableWpiForRlc' - } - } catch (Exception e) { - println('Failure: Running wpi-many.sh failed with a non-zero exit code.') - File wpiOut = new File("${typecheckFilesDir}/wpi-out") - if (wpiOut.exists()) { - println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - injected.execOps.exec { - commandLine 'cat', "${typecheckFilesDir}/wpi-out" - } - println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - } else { - println("========= File ${typecheckFilesDir}/wpi-out does not exist. ========") - } - throw e - } + // collect the logs from running WPI + def typecheckFiles = fileTree(typecheckFilesDir).matching { + include '**/*-typecheck.out' + } + def testinLines = file("${project.projectDir}/tests/wpi-many/testin.txt").text.readLines() + testinLines.removeIf { it.startsWith('#') } + def expectedTypecheckFileCount = testinLines.size() + def actualTypecheckFileCount = typecheckFiles.size() + if (actualTypecheckFileCount != expectedTypecheckFileCount) { + println("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + + "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") + println("========= Found in ${typecheckFilesDir} ========") + injected.execOps.exec { + commandLine 'ls', '-al', "${typecheckFilesDir}" + } + println("========= Expected in ${typecheckFilesDir} ========") + injected.execOps.exec { + commandLine 'cat', "${project.projectDir}/tests/wpi-many/testin.txt" + } + println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + injected.execOps.exec { + commandLine 'cat', "${typecheckFilesDir}/wpi-out" + } + println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + def logFiles = fileTree(typecheckFilesDir).matching { + include '**/*.log' + } + logFiles.visit { FileVisitDetails details -> + def filename = "${typecheckFilesDir}" + details.getName() + println("======== start of contents of ${filename} ========") + details.getFile().eachLine { line -> println(line) } + println("======== end of contents of ${filename} ========") + } + // If any of these files are present, their contents should be an error + // message that might indicate what went wrong. Even their presenence, + // however, is intereseting (even if they are empty). + def cannotRunWpiFiles = fileTree(typecheckFilesDir).matching { + include '**/.cannot-run-wpi' + } + cannotRunWpiFiles.visit { FileVisitDetails details -> + def filename = "${typecheckFilesDir}" + details.getName() + println("======== start of contents of ${filename} ========") + details.getFile().eachLine { line -> println(line) } + println("======== end of contents of ${filename} ========") + } + throw new GradleException("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + + "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") + } - // collect the logs from running WPI - def typecheckFiles = fileTree(typecheckFilesDir).matching { - include '**/*-typecheck.out' + // check that WPI causes the expected builds to succeed + typecheckFiles.visit { FileVisitDetails details -> + def filename = "${project.projectDir}/build/wpi-many-tests-results/" + details.getName() + def file = details.getFile() + if (file.length() == 0) { + throw new GradleException('Failure: WPI produced empty typecheck file ' + filename) + } + file.eachLine { line -> + if ( + // Ignore the line that WPI echoes with the javac command being run. + line.startsWith('Running ') + // Warnings about bad path elements aren't related to WPI and are ignored. + || line.startsWith('warning: [path]') + // Ignore bootstrap classpath warning: + || line.startsWith('warning: [options] bootstrap') + // Ignore the warnings about --add-opens arguments to the JVM + || line.contains('warning: [options] --add-opens has no effect at compile time') + // Ignore the summary line that reports the total number of warnings (which can be single or plural). + || line.endsWith(' warning') + || line.endsWith(' warnings') + || line.startsWith('warning: No processor claimed any of these annotations: ')) { + return; } - def testinLines = file("${project.projectDir}/tests/wpi-many/testin.txt").text.readLines() - testinLines.removeIf { it.startsWith('#') } - def expectedTypecheckFileCount = testinLines.size() - def actualTypecheckFileCount = typecheckFiles.size() - if (actualTypecheckFileCount != expectedTypecheckFileCount) { - println("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + - "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") - println("========= Found in ${typecheckFilesDir} ========") - injected.execOps.exec { - commandLine 'ls', '-al', "${typecheckFilesDir}" - } - println("========= Expected in ${typecheckFilesDir} ========") - injected.execOps.exec { - commandLine 'cat', "${project.projectDir}/tests/wpi-many/testin.txt" - } - println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - injected.execOps.exec { - commandLine 'cat', "${typecheckFilesDir}/wpi-out" - } - println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - def logFiles = fileTree(typecheckFilesDir).matching { - include '**/*.log' - } - logFiles.visit { FileVisitDetails details -> - def filename = "${typecheckFilesDir}" + details.getName() - println("======== start of contents of ${filename} ========") - details.getFile().eachLine { line -> println(line) } - println("======== end of contents of ${filename} ========") - } - // If any of these files are present, their contents should be an error - // message that might indicate what went wrong. Even their presenence, - // however, is intereseting (even if they are empty). - def cannotRunWpiFiles = fileTree(typecheckFilesDir).matching { - include '**/.cannot-run-wpi' - } - cannotRunWpiFiles.visit { FileVisitDetails details -> - def filename = "${typecheckFilesDir}" + details.getName() - println("======== start of contents of ${filename} ========") - details.getFile().eachLine { line -> println(line) } - println("======== end of contents of ${filename} ========") - } - throw new GradleException("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + - "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") - } - - // check that WPI causes the expected builds to succeed - typecheckFiles.visit { FileVisitDetails details -> - def filename = "${project.projectDir}/build/wpi-many-tests-results/" + details.getName() - def file = details.getFile() - if (file.length() == 0) { - throw new GradleException('Failure: WPI produced empty typecheck file ' + filename) - } - file.eachLine { line -> - if ( - // Ignore the line that WPI echoes with the javac command being run. - line.startsWith('Running ') - // Warnings about bad path elements aren't related to WPI and are ignored. - || line.startsWith('warning: [path]') - // Ignore bootstrap classpath warning: - || line.startsWith('warning: [options] bootstrap') - // Ignore the warnings about --add-opens arguments to the JVM - || line.contains('warning: [options] --add-opens has no effect at compile time') - // Ignore the summary line that reports the total number of warnings (which can be single or plural). - || line.endsWith(' warning') - || line.endsWith(' warnings') - || line.startsWith('warning: No processor claimed any of these annotations: ')) { - return; - } - if (!line.trim().equals('')) { - println("======== start of contents of ${filename} ========") - details.getFile().eachLine { l -> println(l) } - println("======== end of contents of ${filename} ========") - throw new GradleException('Failure: WPI scripts produced an unexpected output in ' + filename + '. ' + - 'Failing line is the following: ' + line) - } - } + if (!line.trim().equals('')) { + println("======== start of contents of ${filename} ========") + details.getFile().eachLine { l -> println(l) } + println("======== end of contents of ${filename} ========") + throw new GradleException('Failure: WPI scripts produced an unexpected output in ' + filename + '. ' + + 'Failing line is the following: ' + line) } + } } + } } // This is run as part of the inferenceTests task. task wpiPlumeLibTest(group: 'Verification') { - description = 'Tests whole-program inference on the plume-lib projects. Requires an Internet connection.' - dependsOn(assembleForJavac) - dependsOn(':getDoLikeJavac') + description = 'Tests whole-program inference on the plume-lib projects. Requires an Internet connection.' + dependsOn(assembleForJavac) + dependsOn(':getDoLikeJavac') - // This test must always be re-run when requested. - outputs.upToDateWhen { false } + // This test must always be re-run when requested. + outputs.upToDateWhen { false } - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - commandLine 'bin-devel/wpi-plumelib/test-wpi-plumelib.sh' - ignoreExitValue = false - } + doLast { + injected.execOps.exec { + commandLine 'bin-devel/wpi-plumelib/test-wpi-plumelib.sh' + ignoreExitValue = false } + } } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerPom(publication) { - sharedPublicationConfiguration(publication) - // Don't use publication.from components.java which would publish the skinny jar as checker.jar. - publication.pom { - name = 'Checker Framework' - description = 'The Checker Framework enhances Java\'s type system to\n' + - 'make it more powerful and useful. This lets software developers\n' + - 'detect and prevent errors in their Java programs.\n' + - 'The Checker Framework includes compiler plug-ins ("checkers")\n' + - 'that find bugs or verify their absence. It also permits you to\n' + - 'write your own compiler plug-ins.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + // Don't use publication.from components.java which would publish the skinny jar as checker.jar. + publication.pom { + name = 'Checker Framework' + description = 'The Checker Framework enhances Java\'s type system to\n' + + 'make it more powerful and useful. This lets software developers\n' + + 'detect and prevent errors in their Java programs.\n' + + 'The Checker Framework includes compiler plug-ins ("checkers")\n' + + 'that find bugs or verify their absence. It also permits you to\n' + + 'write your own compiler plug-ins.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } } + } } publishing { - publications { - checker(MavenPublication) { - project.shadow.component it - // reset the artifacts because of project.shadow.component changes - // the classifier from 'all' to '' because of - // https://github.com/johnrengelman/shadow/issues/860 - artifacts = [shadowJar] - checkerPom it - artifact checkerJar - artifact allSourcesJar - artifact allJavadocJar - } + publications { + checker(MavenPublication) { + project.shadow.component it + // reset the artifacts because of project.shadow.component changes + // the classifier from 'all' to '' because of + // https://github.com/johnrengelman/shadow/issues/860 + artifacts = [shadowJar] + checkerPom it + artifact checkerJar + artifact allSourcesJar + artifact allJavadocJar } + } } signing { - sign publishing.publications.checker + sign publishing.publications.checker } diff --git a/dataflow/build.gradle b/dataflow/build.gradle index 3179ba9e2db..847eef0fb70 100644 --- a/dataflow/build.gradle +++ b/dataflow/build.gradle @@ -1,30 +1,30 @@ plugins { - id 'java-library' - id 'base' + id 'java-library' + id 'base' } dependencies { - api project(':javacutil') - api project(':checker-qual') - - // Node implements org.plumelib.util.UniqueId, so this dependency must be "api". - api "org.plumelib:plume-util:${versions.plumeUtil}" - // plume-util has an `implementation` dependency on hashmap-util. - // That follows Gradle's rules, but Gradle's rules are not entirely correct: - // https://github.com/gradle/gradle/issues/30054 - // To build with JDK 22+, we need to redeclare that dependency here. - implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" - - // External dependencies: - // If you add an external dependency, you must shadow its packages both in the dataflow-shaded - // artifact (see shadowJar block below) and also in checker.jar (see the comment in - // ../build.gradle in the createDataflowShaded task). + api project(':javacutil') + api project(':checker-qual') + + // Node implements org.plumelib.util.UniqueId, so this dependency must be "api". + api "org.plumelib:plume-util:${versions.plumeUtil}" + // plume-util has an `implementation` dependency on hashmap-util. + // That follows Gradle's rules, but Gradle's rules are not entirely correct: + // https://github.com/gradle/gradle/issues/30054 + // To build with JDK 22+, we need to redeclare that dependency here. + implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" + + // External dependencies: + // If you add an external dependency, you must shadow its packages both in the dataflow-shaded + // artifact (see shadowJar block below) and also in checker.jar (see the comment in + // ../build.gradle in the createDataflowShaded task). } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } // Shadowing Test Sources and Dependencies @@ -37,28 +37,28 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar * @return */ def createDataflowShaded(shadedPkgName) { - tasks.create(name: "dataflow${shadedPkgName}Jar", type: ShadowJar, dependsOn: compileJava, group: 'Build') { - description = "Builds dataflow-${shadedPkgName}.jar." - includeEmptyDirs = false - archiveBaseName.set("dataflow-${shadedPkgName}") - // Without this line, the Maven artifact will have the classifier "all". - archiveClassifier.set('') + tasks.create(name: "dataflow${shadedPkgName}Jar", type: ShadowJar, dependsOn: compileJava, group: 'Build') { + description = "Builds dataflow-${shadedPkgName}.jar." + includeEmptyDirs = false + archiveBaseName.set("dataflow-${shadedPkgName}") + // Without this line, the Maven artifact will have the classifier "all". + archiveClassifier.set('') - from shadowJar.source - configurations = shadowJar.configurations + from shadowJar.source + configurations = shadowJar.configurations - destinationDirectory = file("${buildDir}/shadow/dataflow${shadedPkgName}") + destinationDirectory = file("${buildDir}/shadow/dataflow${shadedPkgName}") - relocate('org.checkerframework', "org.checkerframework.${shadedPkgName}") { - // Shade all Checker Framework packages, except for the dataflow qualifiers. - exclude 'org.checkerframework.dataflow.qual.*' - } - - // Relocate external dependencies - relocate 'org.plumelib', "org.checkerframework.${shadedPkgName}.org.plumelib" - relocate 'com.google', "org.checkerframework.${shadedPkgName}.com.google" + relocate('org.checkerframework', "org.checkerframework.${shadedPkgName}") { + // Shade all Checker Framework packages, except for the dataflow qualifiers. + exclude 'org.checkerframework.dataflow.qual.*' } + + // Relocate external dependencies + relocate 'org.plumelib', "org.checkerframework.${shadedPkgName}.org.plumelib" + relocate 'com.google', "org.checkerframework.${shadedPkgName}.com.google" + } } // Creates a new shaded dataflow artifact. To add a new one, add a new method call below, and add @@ -68,28 +68,28 @@ createDataflowShaded('nullaway') createDataflowShaded('errorprone') task manual(group: 'Documentation') { - description = 'Build the manual' + description = 'Build the manual' - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - workingDir = file('manual') - commandLine 'make' - } + doLast { + injected.execOps.exec { + workingDir = file('manual') + commandLine 'make' } + } } tasks.withType(Test) { - // Disable the gradle generated test task as the dataflow framework does not use JUnit for testing. If it were kept enabled (which is the default), gradlew would produce a deprecation warning. - // The `allDataflowTests` task dependency is still enabled. - enabled = false - dependsOn('allDataflowTests') + // Disable the gradle generated test task as the dataflow framework does not use JUnit for testing. If it were kept enabled (which is the default), gradlew would produce a deprecation warning. + // The `allDataflowTests` task dependency is still enabled. + enabled = false + dependsOn('allDataflowTests') } tasks.create(name: 'allDataflowTests', group: 'Verification') { - description = 'Run all dataflow analysis tests' - // 'allDataflowTests' is automatically populated by testDataflowAnalysis(). + description = 'Run all dataflow analysis tests' + // 'allDataflowTests' is automatically populated by testDataflowAnalysis(). } /** @@ -107,65 +107,65 @@ tasks.create(name: 'allDataflowTests', group: 'Verification') { * @return */ def testDataflowAnalysis(taskName, dirName, className, diff) { - tasks.create(name: taskName, dependsOn: [assemble, compileTestJava], group: 'Verification') { - description = "Run the ${dirName} dataflow framework test." + tasks.create(name: taskName, dependsOn: [assemble, compileTestJava], group: 'Verification') { + description = "Run the ${dirName} dataflow framework test." + + def injected = project.objects.newInstance(InjectedExecOps) - def injected = project.objects.newInstance(InjectedExecOps) + if (diff) { + inputs.file("tests/${dirName}/Expected.txt") + } + inputs.file("tests/${dirName}/Test.java") + + outputs.file("tests/${dirName}/Out.txt") + outputs.file("tests/${dirName}/Test.class") - if (diff) { - inputs.file("tests/${dirName}/Expected.txt") + delete("tests/${dirName}/Out.txt") + delete("tests/${dirName}/Test.class") + doLast { + injected.execOps.javaexec { + workingDir = file("tests/${dirName}") + if (!JavaVersion.current().java9Compatible) { + jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() } - inputs.file("tests/${dirName}/Test.java") - - outputs.file("tests/${dirName}/Out.txt") - outputs.file("tests/${dirName}/Test.class") - - delete("tests/${dirName}/Out.txt") - delete("tests/${dirName}/Test.class") - doLast { - injected.execOps.javaexec { - workingDir = file("tests/${dirName}") - if (!JavaVersion.current().java9Compatible) { - jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() - } - else if (JavaVersion.current() > JavaVersion.VERSION_11) { - jvmArgs += [ - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - ] - } - - classpath = sourceSets.test.runtimeClasspath - classpath += sourceSets.test.output - mainClass = "${className}" - } - if (diff) { - injected.execOps.exec { - workingDir = file("tests/${dirName}") - executable 'diff' - args = [ - '-u', - 'Expected.txt', - 'Out.txt' - ] - } - } + else if (JavaVersion.current() > JavaVersion.VERSION_11) { + jvmArgs += [ + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + ] } + + classpath = sourceSets.test.runtimeClasspath + classpath += sourceSets.test.output + mainClass = "${className}" + } + if (diff) { + injected.execOps.exec { + workingDir = file("tests/${dirName}") + executable 'diff' + args = [ + '-u', + 'Expected.txt', + 'Out.txt' + ] + } + } } + } - allDataflowTests.dependsOn << taskName + allDataflowTests.dependsOn << taskName } // When adding a new test case, don't forget to add the temporary files to `../.gitignore`. @@ -180,20 +180,20 @@ apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading the dataflow artifacts to Maven repositories. */ final dataflowPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - // Information that is in all pom files is configured in checker-framework/gradle-mvn-push.gradle. - publication.pom { - name = 'Dataflow' - description = 'Dataflow is a dataflow framework based on the javac compiler.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + publication.from components.java + // Information that is in all pom files is configured in checker-framework/gradle-mvn-push.gradle. + publication.pom { + name = 'Dataflow' + description = 'Dataflow is a dataflow framework based on the javac compiler.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } } + } } /** @@ -201,63 +201,63 @@ final dataflowPom(publication) { * @param shadedPkgName the name of the shaded package to use; also used as part of the artifact name: "dataflow-${shadePkgName}" */ final dataflowShadedPom(MavenPublication publication, String shadedPkgName) { - sharedPublicationConfiguration(publication) - - publication.artifactId = "dataflow-${shadedPkgName}" - publication.pom { - name = "Dataflow (${shadedPkgName})" - description = "dataflow-${shadedPkgName} is a dataflow framework based on the javac compiler.\n" + - '\n' + - 'It differs from the org.checkerframework:dataflow artifact in two ways.\n' + - "First, the packages in this artifact have been renamed to org.checkerframework.${shadedPkgName}.*.\n" + - 'Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + + publication.artifactId = "dataflow-${shadedPkgName}" + publication.pom { + name = "Dataflow (${shadedPkgName})" + description = "dataflow-${shadedPkgName} is a dataflow framework based on the javac compiler.\n" + + '\n' + + 'It differs from the org.checkerframework:dataflow artifact in two ways.\n' + + "First, the packages in this artifact have been renamed to org.checkerframework.${shadedPkgName}.*.\n" + + 'Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } } + } } publishing { - publications { - dataflow(MavenPublication) { - dataflowPom it - } + publications { + dataflow(MavenPublication) { + dataflowPom it + } - dataflowShaded(MavenPublication) { - dataflowShadedPom(it, 'shaded') - artifact project.tasks.getByName('dataflowshadedJar').archiveFile - artifact sourcesJar - artifact javadocJar - } + dataflowShaded(MavenPublication) { + dataflowShadedPom(it, 'shaded') + artifact project.tasks.getByName('dataflowshadedJar').archiveFile + artifact sourcesJar + artifact javadocJar + } - dataflowShadednullaway(MavenPublication) { - dataflowShadedPom(it, 'nullaway') + dataflowShadednullaway(MavenPublication) { + dataflowShadedPom(it, 'nullaway') - artifact project.tasks.getByName('dataflownullawayJar').archiveFile - artifact sourcesJar - artifact javadocJar - } + artifact project.tasks.getByName('dataflownullawayJar').archiveFile + artifact sourcesJar + artifact javadocJar + } - dataflowShadederrorprone(MavenPublication) { - dataflowShadedPom(it, 'errorprone') + dataflowShadederrorprone(MavenPublication) { + dataflowShadedPom(it, 'errorprone') - artifact project.tasks.getByName('dataflowerrorproneJar').archiveFile - artifact sourcesJar - artifact javadocJar - } + artifact project.tasks.getByName('dataflowerrorproneJar').archiveFile + artifact sourcesJar + artifact javadocJar } + } } signing { - sign publishing.publications.dataflow - sign publishing.publications.dataflowShaded - sign publishing.publications.dataflowShadednullaway - sign publishing.publications.dataflowShadederrorprone + sign publishing.publications.dataflow + sign publishing.publications.dataflowShaded + sign publishing.publications.dataflowShadednullaway + sign publishing.publications.dataflowShadederrorprone } publishDataflowPublicationToMavenRepository.dependsOn(signDataflowShadedPublication) diff --git a/docs/build.gradle b/docs/build.gradle index ecb64aac695..0dbb6292d3f 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -1,17 +1,17 @@ // Enable execOperations.javaexec interface InjectedExecOps { - @Inject //@javax.inject.Inject - ExecOperations getExecOps() + @Inject //@javax.inject.Inject + ExecOperations getExecOps() } clean { - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - delete('api/') - doLast { - injected.execOps.exec { - workingDir = file('examples') - commandLine 'make', 'clean' - } + delete('api/') + doLast { + injected.execOps.exec { + workingDir = file('examples') + commandLine 'make', 'clean' } + } } diff --git a/docs/examples/errorprone/build.gradle b/docs/examples/errorprone/build.gradle index e1916676847..d342cd80cf8 100644 --- a/docs/examples/errorprone/build.gradle +++ b/docs/examples/errorprone/build.gradle @@ -3,60 +3,60 @@ /// plugins { - id 'java' - id 'net.ltgt.errorprone' version '4.1.0' - // Checker Framework pluggable type-checking - id 'org.checkerframework' version '0.6.48' apply false + id 'java' + id 'net.ltgt.errorprone' version '4.1.0' + // Checker Framework pluggable type-checking + id 'org.checkerframework' version '0.6.48' apply false } ext { - versions = [ - eisopVersion: '3.42.0-eisop5', - ] + versions = [ + eisopVersion: '3.42.0-eisop5', + ] } apply plugin: 'org.checkerframework' if (false) { - def cfHome = "${projectDir}/../../.." - dependencies { - compileOnly files(cfHome + '/checker/dist/checker-qual.jar') - testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') - checkerFramework files(cfHome + '/checker/dist/checker.jar') - } + def cfHome = "${projectDir}/../../.." + dependencies { + compileOnly files(cfHome + '/checker/dist/checker-qual.jar') + testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') + checkerFramework files(cfHome + '/checker/dist/checker.jar') + } } else { - dependencies { - compileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" - testCompileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" - checkerFramework "io.github.eisop:checker-qual:${versions.eisopVersion}" - checkerFramework "io.github.eisop:checker:${versions.eisopVersion}" - } + dependencies { + compileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" + testCompileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" + checkerFramework "io.github.eisop:checker-qual:${versions.eisopVersion}" + checkerFramework "io.github.eisop:checker:${versions.eisopVersion}" + } } dependencies { - // Must use at least version 2.4.0 of Error Prone. - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - errorprone 'com.google.errorprone:error_prone_core:2.10.0' - } else if (JavaVersion.current() < JavaVersion.VERSION_17) { - errorprone 'com.google.errorprone:error_prone_core:2.31.0' - } else { - errorprone 'com.google.errorprone:error_prone_core:2.36.0' - } + // Must use at least version 2.4.0 of Error Prone. + if (JavaVersion.current() == JavaVersion.VERSION_1_8) { + errorprone 'com.google.errorprone:error_prone_core:2.10.0' + } else if (JavaVersion.current() < JavaVersion.VERSION_17) { + errorprone 'com.google.errorprone:error_prone_core:2.31.0' + } else { + errorprone 'com.google.errorprone:error_prone_core:2.36.0' + } } repositories { - mavenCentral() + mavenCentral() } checkerFramework { - checkers = [ - 'org.checkerframework.checker.nullness.NullnessChecker', - ] - extraJavacArgs = ['-Aversion'] + checkers = [ + 'org.checkerframework.checker.nullness.NullnessChecker', + ] + extraJavacArgs = ['-Aversion'] } compileJava { - // A checker will only run if Error Prone does not issue any warnings. So - // convert the expected error to a warning to test that both Error Prone - // and the Nullness Checker run. - options.errorprone.warn('CollectionIncompatibleType') + // A checker will only run if Error Prone does not issue any warnings. So + // convert the expected error to a warning to test that both Error Prone + // and the Nullness Checker run. + options.errorprone.warn('CollectionIncompatibleType') } diff --git a/docs/examples/lombok/build.gradle b/docs/examples/lombok/build.gradle index 410b0c88be5..a439fd08b64 100644 --- a/docs/examples/lombok/build.gradle +++ b/docs/examples/lombok/build.gradle @@ -3,32 +3,32 @@ /// plugins { - id 'java' - id 'io.freefair.lombok' version '8.12' - // Checker Framework pluggable type-checking - id 'org.checkerframework' version '0.6.48' + id 'java' + id 'io.freefair.lombok' version '8.12' + // Checker Framework pluggable type-checking + id 'org.checkerframework' version '0.6.48' } lombok { - version = "1.18.30" + version = "1.18.30" } apply plugin: 'org.checkerframework' def cfHome = "${projectDir}/../../.." dependencies { - compileOnly files(cfHome + '/checker/dist/checker-qual.jar') - testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') - checkerFramework files(cfHome + '/checker/dist/checker.jar') + compileOnly files(cfHome + '/checker/dist/checker-qual.jar') + testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') + checkerFramework files(cfHome + '/checker/dist/checker.jar') } repositories { - mavenCentral() + mavenCentral() } checkerFramework { - checkers = [ - 'org.checkerframework.checker.nullness.NullnessChecker', - ] - extraJavacArgs = ['-Aversion'] + checkers = [ + 'org.checkerframework.checker.nullness.NullnessChecker', + ] + extraJavacArgs = ['-Aversion'] } diff --git a/framework-test/build.gradle b/framework-test/build.gradle index 888b92a2b46..840b91759ac 100644 --- a/framework-test/build.gradle +++ b/framework-test/build.gradle @@ -1,22 +1,22 @@ import org.gradle.internal.jvm.Jvm sourceSets { - taglet - tagletJdk8 + taglet + tagletJdk8 } dependencies { - implementation "junit:junit:${versions.junit}" - implementation project(':javacutil') - implementation project(':checker-qual') + implementation "junit:junit:${versions.junit}" + implementation project(':javacutil') + implementation project(':checker-qual') - implementation "org.plumelib:plume-util:${versions.plumeUtil}" + implementation "org.plumelib:plume-util:${versions.plumeUtil}" - if (Jvm.current().toolsJar) { - tagletJdk8Implementation files(Jvm.current().toolsJar) - } + if (Jvm.current().toolsJar) { + tagletJdk8Implementation files(Jvm.current().toolsJar) + } - testImplementation project(':framework') + testImplementation project(':framework') } jar.archiveBaseName = 'framework-test' @@ -25,30 +25,30 @@ apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final frameworkTest(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Framework Testing Library' - description = 'framework-test contains utility classes for testing type-checkers\n' + - 'that are built on the Checker Framework.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Framework Testing Library' + description = 'framework-test contains utility classes for testing type-checkers\n' + + 'that are built on the Checker Framework.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } } + } } publishing { - publications { - frameworkTest(MavenPublication) { - frameworkTest it - } + publications { + frameworkTest(MavenPublication) { + frameworkTest it } + } } signing { - sign publishing.publications.frameworkTest + sign publishing.publications.frameworkTest } diff --git a/framework/build.gradle b/framework/build.gradle index c6b4ed07177..c0d3e747f55 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -1,173 +1,173 @@ plugins { - id 'java-library' + id 'java-library' } ext { - annotatedJdkHome = '../../jdk' + annotatedJdkHome = '../../jdk' } sourceSets { - main { - java { - // NO-AFU - exclude '**/org/checkerframework/common/wholeprograminference/**' - exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' - exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' - } + main { + java { + // NO-AFU + exclude '**/org/checkerframework/common/wholeprograminference/**' + exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' + exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' + } - resources { - // Stub files, message.properties, etc. - srcDirs += [ - 'src/main/java', - "${buildDir}/generated/resources" - ] - - // NO-AFU - exclude '**/org/checkerframework/common/wholeprograminference/**' - exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' - exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' - } + resources { + // Stub files, message.properties, etc. + srcDirs += [ + 'src/main/java', + "${buildDir}/generated/resources" + ] + + // NO-AFU + exclude '**/org/checkerframework/common/wholeprograminference/**' + exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' + exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' } - testannotations { - java { - srcDirs = ['src/testannotations/java'] - } + } + testannotations { + java { + srcDirs = ['src/testannotations/java'] } + } } sourcesJar { - // The resources duplicate content from the src directory. - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + // The resources duplicate content from the src directory. + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } jar { - // The resources duplicate content from the src directory. - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + // The resources duplicate content from the src directory. + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } configurations { - implementation.extendsFrom(annotatedGuava) + implementation.extendsFrom(annotatedGuava) } dependencies { - api project(':javacutil') - api project(':dataflow') - // At the moement, there are no differences between eisop and typetools stubparsers. - api 'org.checkerframework:stubparser:3.25.10' - - // NO-AFU - /* - // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. - // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite - api('org.checkerframework:annotation-file-utilities:*') { - exclude group: 'com.google.errorprone', module: 'javac' - } - */ - - api project(':checker-qual') - - // External dependencies: - // If you add an external dependency, you must shadow its packages. - // See the comment in ../build.gradle in the shadowJar block. - implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" - implementation "org.plumelib:plume-util:${versions.plumeUtil}" - implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" - // Add this dependency if needed to debug classpath issues. - // implementation 'io.github.classgraph:classgraph:4.8.165' - - testImplementation "junit:junit:${versions.junit}" - testImplementation project(':framework-test') - testImplementation sourceSets.testannotations.output - - // AutoValue support in Returns Receiver Checker - testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" - testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" - - // Lombok support in Returns Receiver Checker - testImplementation "org.projectlombok:lombok:${versions.lombok}" + api project(':javacutil') + api project(':dataflow') + // At the moement, there are no differences between eisop and typetools stubparsers. + api 'org.checkerframework:stubparser:3.25.10' + + // NO-AFU + /* + // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. + // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite + api('org.checkerframework:annotation-file-utilities:*') { + exclude group: 'com.google.errorprone', module: 'javac' + } + */ + + api project(':checker-qual') + + // External dependencies: + // If you add an external dependency, you must shadow its packages. + // See the comment in ../build.gradle in the shadowJar block. + implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" + implementation "org.plumelib:plume-util:${versions.plumeUtil}" + implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" + // Add this dependency if needed to debug classpath issues. + // implementation 'io.github.classgraph:classgraph:4.8.165' + + testImplementation "junit:junit:${versions.junit}" + testImplementation project(':framework-test') + testImplementation sourceSets.testannotations.output + + // AutoValue support in Returns Receiver Checker + testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" + testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" + + // Lombok support in Returns Receiver Checker + testImplementation "org.projectlombok:lombok:${versions.lombok}" } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } task cloneAnnotatedJdk() { - description = 'Obtain or update the annotated JDK.' - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - if (file(annotatedJdkHome).exists()) { - injected.execOps.exec { - workingDir file(annotatedJdkHome) - executable 'git' - args = ['pull', '-q'] - ignoreExitValue = true - } - } else { - println "Cloning annotated JDK repository in ${annotatedJdkHome}/../" - injected.execOps.exec { - workingDir file("${annotatedJdkHome}/../") - executable 'git' - args = [ - 'clone', - '-q', - '--filter=blob:none', - 'https://github.com/eisop/jdk.git', - 'jdk' - ] - } - } + description = 'Obtain or update the annotated JDK.' + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + if (file(annotatedJdkHome).exists()) { + injected.execOps.exec { + workingDir file(annotatedJdkHome) + executable 'git' + args = ['pull', '-q'] + ignoreExitValue = true + } + } else { + println "Cloning annotated JDK repository in ${annotatedJdkHome}/../" + injected.execOps.exec { + workingDir file("${annotatedJdkHome}/../") + executable 'git' + args = [ + 'clone', + '-q', + '--filter=blob:none', + 'https://github.com/eisop/jdk.git', + 'jdk' + ] + } } + } } task copyAndMinimizeAnnotatedJdkFiles(dependsOn: cloneAnnotatedJdk, group: 'Build') { - dependsOn ':framework:compileJava', project(':javacutil').tasks.jar - def inputDir = "${annotatedJdkHome}/src" - def outputDir = "${buildDir}/generated/resources/annotated-jdk/" - - description = "Copy annotated JDK files to ${outputDir}. Removes private and package-private methods, method bodies, comments, etc. from the annotated JDK" - - inputs.dir file(inputDir) - outputs.dir file(outputDir) - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - FileTree tree = fileTree(dir: inputDir) - NavigableSet annotatedForFiles = new TreeSet<>(); - tree.visit { FileVisitDetails fvd -> - if (!fvd.file.isDirectory() && fvd.file.name.matches('.*\\.java') - && !fvd.file.path.contains('org/checkerframework')) { - fvd.getFile().readLines().any { line -> - if (line.contains('@AnnotatedFor') || line.contains('org.checkerframework') || line.contains('org.jspecify')) { - annotatedForFiles.add(fvd.file.absolutePath) - return true; - } - } - } - } - String absolutejdkHome = file(annotatedJdkHome).absolutePath - int jdkDirStringSize = absolutejdkHome.size() - copy { - from(annotatedJdkHome) - into(outputDir) - for (String filename : annotatedForFiles) { - include filename.substring(jdkDirStringSize) - } + dependsOn ':framework:compileJava', project(':javacutil').tasks.jar + def inputDir = "${annotatedJdkHome}/src" + def outputDir = "${buildDir}/generated/resources/annotated-jdk/" + + description = "Copy annotated JDK files to ${outputDir}. Removes private and package-private methods, method bodies, comments, etc. from the annotated JDK" + + inputs.dir file(inputDir) + outputs.dir file(outputDir) + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + FileTree tree = fileTree(dir: inputDir) + NavigableSet annotatedForFiles = new TreeSet<>(); + tree.visit { FileVisitDetails fvd -> + if (!fvd.file.isDirectory() && fvd.file.name.matches('.*\\.java') + && !fvd.file.path.contains('org/checkerframework')) { + fvd.getFile().readLines().any { line -> + if (line.contains('@AnnotatedFor') || line.contains('org.checkerframework') || line.contains('org.jspecify')) { + annotatedForFiles.add(fvd.file.absolutePath) + return true; + } } - injected.execOps.javaexec { - classpath = sourceSets.main.runtimeClasspath - standardOutput = System.out - errorOutput = System.err + } + } + String absolutejdkHome = file(annotatedJdkHome).absolutePath + int jdkDirStringSize = absolutejdkHome.size() + copy { + from(annotatedJdkHome) + into(outputDir) + for (String filename : annotatedForFiles) { + include filename.substring(jdkDirStringSize) + } + } + injected.execOps.javaexec { + classpath = sourceSets.main.runtimeClasspath + standardOutput = System.out + errorOutput = System.err - mainClass = 'org.checkerframework.framework.stub.JavaStubifier' - args outputDir - } + mainClass = 'org.checkerframework.framework.stub.JavaStubifier' + args outputDir } + } } sourcesJar.dependsOn(copyAndMinimizeAnnotatedJdkFiles) @@ -175,108 +175,108 @@ sourcesJar.dependsOn(copyAndMinimizeAnnotatedJdkFiles) processResources.dependsOn(copyAndMinimizeAnnotatedJdkFiles) task allSourcesJar(type: Jar, group: 'Build') { - description = 'Creates a sources jar that includes sources for all Checker Framework classes in framework.jar' - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'framework-source.jar' - from (project(':framework').sourceSets.main.java, - project(':dataflow').sourceSets.main.allJava, - project(':javacutil').sourceSets.main.allJava) + description = 'Creates a sources jar that includes sources for all Checker Framework classes in framework.jar' + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'framework-source.jar' + from (project(':framework').sourceSets.main.java, + project(':dataflow').sourceSets.main.allJava, + project(':javacutil').sourceSets.main.allJava) } task allJavadocJar(type: Jar, group: 'Build') { - description = 'Creates javadoc jar include Javadoc for all of the framework' - dependsOn (project(':framework').tasks.javadoc, - project(':dataflow').tasks.javadoc, - project(':javacutil').tasks.javadoc) - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'framework-javadoc.jar' - from (project(':framework').tasks.javadoc.destinationDir, - project(':dataflow').tasks.javadoc.destinationDir, - project(':javacutil').tasks.javadoc.destinationDir) + description = 'Creates javadoc jar include Javadoc for all of the framework' + dependsOn (project(':framework').tasks.javadoc, + project(':dataflow').tasks.javadoc, + project(':javacutil').tasks.javadoc) + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'framework-javadoc.jar' + from (project(':framework').tasks.javadoc.destinationDir, + project(':dataflow').tasks.javadoc.destinationDir, + project(':javacutil').tasks.javadoc.destinationDir) } shadowJar { - description = 'Creates the "fat" framework.jar in dist' - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'framework.jar' - manifest { - attributes('Automatic-Module-Name': 'org.checkerframework.framework') - } - - minimize() + description = 'Creates the "fat" framework.jar in dist' + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'framework.jar' + manifest { + attributes('Automatic-Module-Name': 'org.checkerframework.framework') + } + + minimize() } createCheckTypeTask(project.name, 'CompilerMessages', - 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') + 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') checkCompilerMessages { - options.compilerArgs += [ - '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath - ] + options.compilerArgs += [ + '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + ] } task loaderTests(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for the annotation class loader' - dependsOn(compileTestJava) - - def injected = project.objects.newInstance(InjectedExecOps) - - // TODO: this dependency on checker is a bit ugly. - dependsOn project(':checker-qual').tasks.jar - dependsOn project(':checker').tasks.assemble - doLast { - injected.execOps.exec { - workingDir = file('tests/annotationclassloader') - commandLine 'make', 'all' - } + description = 'Run tests for the annotation class loader' + dependsOn(compileTestJava) + + def injected = project.objects.newInstance(InjectedExecOps) + + // TODO: this dependency on checker is a bit ugly. + dependsOn project(':checker-qual').tasks.jar + dependsOn project(':checker').tasks.assemble + doLast { + injected.execOps.exec { + workingDir = file('tests/annotationclassloader') + commandLine 'make', 'all' } + } } clean { - def injected = project.objects.newInstance(InjectedExecOps) - - delete('tests/returnsreceiverdelomboked') - delete('dist') - delete('tests/build') - doLast { - injected.execOps.exec { - workingDir = file('tests/annotationclassloader') - commandLine 'make', 'clean' - } + def injected = project.objects.newInstance(InjectedExecOps) + + delete('tests/returnsreceiverdelomboked') + delete('dist') + delete('tests/build') + doLast { + injected.execOps.exec { + workingDir = file('tests/annotationclassloader') + commandLine 'make', 'clean' } + } } task delombok { - description = 'Delomboks the source code tree in tests/returnsreceiverlombok' + description = 'Delomboks the source code tree in tests/returnsreceiverlombok' - def srcDelomboked = 'tests/returnsreceiverdelomboked' - def srcJava = 'tests/returnsreceiverlombok' + def srcDelomboked = 'tests/returnsreceiverdelomboked' + def srcJava = 'tests/returnsreceiverlombok' - inputs.files file(srcJava) - outputs.dir file(srcDelomboked) + inputs.files file(srcJava) + outputs.dir file(srcDelomboked) - // This dependency is required to ensure the checker-qual jar exists, - // to prevent lombok from emitting "cannot find symbol" errors for @This - // annotations in the test input code. - dependsOn project(':checker-qual').tasks.jar + // This dependency is required to ensure the checker-qual jar exists, + // to prevent lombok from emitting "cannot find symbol" errors for @This + // annotations in the test input code. + dependsOn project(':checker-qual').tasks.jar - doLast { - if(!skipDelombok) { - def collection = files(configurations.testCompileClasspath) - ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', - classpath: collection.asPath) - ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) - } + doLast { + if(!skipDelombok) { + def collection = files(configurations.testCompileClasspath) + ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', + classpath: collection.asPath) + ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) } + } } if (skipDelombok) { - delombok.enabled = false - test { - exclude '**/ReturnsReceiverLombokTest.java' - } + delombok.enabled = false + test { + exclude '**/ReturnsReceiverLombokTest.java' + } } else { - tasks.test.dependsOn('delombok') + tasks.test.dependsOn('delombok') } diff --git a/gradle-mvn-push.gradle b/gradle-mvn-push.gradle index 1e550d35202..af33054759e 100644 --- a/gradle-mvn-push.gradle +++ b/gradle-mvn-push.gradle @@ -4,37 +4,37 @@ apply plugin: 'signing' final isSnapshot = version.contains('SNAPSHOT') // https://github.com/johnrengelman/shadow/issues/586#issuecomment-708375599 components.java.withVariantsFromConfiguration(configurations.shadowRuntimeElements) { - skip() + skip() } publishing { - repositories { - maven { - url = (isSnapshot - ? project.properties.getOrDefault('SNAPSHOT_REPOSITORY_URL', 'https://oss.sonatype.org/content/repositories/snapshots/') - : project.properties.getOrDefault('RELEASE_REPOSITORY_URL', 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') - ) - credentials { - username = project.properties.get('SONATYPE_NEXUS_USERNAME') - password = project.properties.get('SONATYPE_NEXUS_PASSWORD') - } - } + repositories { + maven { + url = (isSnapshot + ? project.properties.getOrDefault('SNAPSHOT_REPOSITORY_URL', 'https://oss.sonatype.org/content/repositories/snapshots/') + : project.properties.getOrDefault('RELEASE_REPOSITORY_URL', 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') + ) + credentials { + username = project.properties.get('SONATYPE_NEXUS_USERNAME') + password = project.properties.get('SONATYPE_NEXUS_PASSWORD') + } } + } } signing { - // Use external gpg cmd. This makes it easy to use gpg-agent, - // to avoid being prompted for a password once per artifact. - useGpgCmd() + // Use external gpg cmd. This makes it easy to use gpg-agent, + // to avoid being prompted for a password once per artifact. + useGpgCmd() - // If anything about signing is misconfigured, fail loudly rather than quietly continuing with - // unsigned artifacts. - required = true + // If anything about signing is misconfigured, fail loudly rather than quietly continuing with + // unsigned artifacts. + required = true } // Only sign releases; snapshots are unsigned. tasks.withType(Sign).configureEach { - onlyIf { - release - } + onlyIf { + release + } } diff --git a/javacutil/build.gradle b/javacutil/build.gradle index f4a17396271..3612f3ffc1f 100644 --- a/javacutil/build.gradle +++ b/javacutil/build.gradle @@ -1,63 +1,63 @@ plugins { - id 'java-library' + id 'java-library' } repositories { - mavenCentral() + mavenCentral() } configurations { - implementation.extendsFrom(annotatedGuava) + implementation.extendsFrom(annotatedGuava) } dependencies { - implementation project(':checker-qual') - - // This is used by org.checkerframework.javacutil.TypesUtils.isImmutableTypeInJdk. - // https://mvnrepository.com/artifact/org.plumelib/plume-util - implementation "org.plumelib:plume-util:${versions.plumeUtil}" - implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" - // plume-util has an `implementation` dependency on hashmap-util. - // That follows Gradle's rules, but Gradle's rules are not entirely correct: - // https://github.com/gradle/gradle/issues/30054 - // To build with JDK 22+, we need to redeclare that dependency here. - implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" - - // External dependencies: - // If you add an external dependency, you must shadow its packages both in checker.jar and - // and dataflow-shaded.jar. - // Update relocations in these locations: - // * in ../build.gradle in the shadowJar block. - // * in ../dataflow/build.gradle in the createDataflowShaded task. + implementation project(':checker-qual') + + // This is used by org.checkerframework.javacutil.TypesUtils.isImmutableTypeInJdk. + // https://mvnrepository.com/artifact/org.plumelib/plume-util + implementation "org.plumelib:plume-util:${versions.plumeUtil}" + implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" + // plume-util has an `implementation` dependency on hashmap-util. + // That follows Gradle's rules, but Gradle's rules are not entirely correct: + // https://github.com/gradle/gradle/issues/30054 + // To build with JDK 22+, we need to redeclare that dependency here. + implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" + + // External dependencies: + // If you add an external dependency, you must shadow its packages both in checker.jar and + // and dataflow-shaded.jar. + // Update relocations in these locations: + // * in ../build.gradle in the shadowJar block. + // * in ../dataflow/build.gradle in the createDataflowShaded task. } apply from: rootProject.file('gradle-mvn-push.gradle') final javacUtilPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - - publication.pom { - name = 'Javacutil' - description = 'javacutil contains utility classes for the javac compiler.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } - } + sharedPublicationConfiguration(publication) + publication.from components.java + + publication.pom { + name = 'Javacutil' + description = 'javacutil contains utility classes for the javac compiler.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } } + } } publishing { - publications { - javacUtil(MavenPublication) { - javacUtilPom it - } + publications { + javacUtil(MavenPublication) { + javacUtilPom it } + } } signing { - sign publishing.publications.javacUtil + sign publishing.publications.javacUtil } From e7f3731c9fffe215fb626815c31ce31b98882f12 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:06:03 -0800 Subject: [PATCH 009/173] Add `-AskipDirs` command-line argument (#6385) --- .../test/junit/NullnessSkipDirsTest.java | 23 +++++++ .../nullness-skipdirs/skip/SkipDirs1.java | 18 +++++ .../skip/this/SkipDirs2.java | 14 ++++ docs/manual/introduction.tex | 3 + docs/manual/warnings.tex | 23 +++++++ .../common/basetype/BaseTypeVisitor.java | 2 +- .../framework/source/SourceChecker.java | 67 ++++++++++++++++--- 7 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java create mode 100644 checker/tests/nullness-skipdirs/skip/SkipDirs1.java create mode 100644 checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java new file mode 100644 index 00000000000..2be0a4b0a2b --- /dev/null +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java @@ -0,0 +1,23 @@ +package org.checkerframework.checker.test.junit; + +import java.io.File; +import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + +/** JUnit tests for the Nullness Checker -- testing {@code -AskipDirs} command-line argument. */ +public class NullnessSkipDirsTest extends CheckerFrameworkPerDirectoryTest { + + public NullnessSkipDirsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AskipDirs=/skip/this/.*"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-skipdirs"}; + } +} diff --git a/checker/tests/nullness-skipdirs/skip/SkipDirs1.java b/checker/tests/nullness-skipdirs/skip/SkipDirs1.java new file mode 100644 index 00000000000..dd3b88be43c --- /dev/null +++ b/checker/tests/nullness-skipdirs/skip/SkipDirs1.java @@ -0,0 +1,18 @@ +import org.checkerframework.checker.nullness.qual.*; + +public class SkipDirs1 { + + static class DontSkipMe { + static Object foo() { + // :: error: (return) + return null; + } + } + + static class DontSkip { + static Object foo() { + // :: error: (return) + return null; + } + } +} diff --git a/checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java b/checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java new file mode 100644 index 00000000000..b68e744d10f --- /dev/null +++ b/checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java @@ -0,0 +1,14 @@ +import org.checkerframework.checker.nullness.qual.*; + +public class SkipDirs2 { + static class SkipMe { + + Object f; + + // If this test is NOT skipped, it should issue an "unexpected error" since + // There is a type error between f2 (Nullable) and f (NonNull). + void foo(@Nullable Object f2) { + f = f2; + } + } +} diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index 75731671c18..ec907729a3d 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -578,6 +578,9 @@ Suppress all errors and warnings within the definition of a given class --- or everywhere except within the definition of a given class. See Section~\ref{askipdefs}. +\item \<-AskipDirs> + Suppress all errors and warnings within a given directory/folder. See + Section~\ref{askipdirs}. \item \<-AassumeSideEffectFree>, \<-AassumeDeterministic>, \<-AassumePure>, \<-AassumePureGetters> Unsoundly assume that every method is side-effect-free, deterministic, or both; or that every getter method is pure. diff --git a/docs/manual/warnings.tex b/docs/manual/warnings.tex index 87501561c0a..a2d1d81abdd 100644 --- a/docs/manual/warnings.tex +++ b/docs/manual/warnings.tex @@ -50,6 +50,8 @@ the \code{-AskipUses} and \code{-AonlyUses} command-line options (Section~\ref{askipuses}), \item the \code{-AskipDefs} and \code{-AonlyDefs} command-line options (Section~\ref{askipdefs}), +\item + the \code{-AskipDirs} command-line option (Section~\ref{askipdirs}), \item the \code{-AuseConservativeDefaultsForUncheckedCode=source} command-line option (Section~\ref{compiling-libraries}), @@ -653,6 +655,27 @@ most important parts, you can incrementally check more classes until you are type-checking the whole thing. +\sectionAndLabel{\code{-AskipDirs} command-line option}{askipdirs} + +You can suppresss all errors and warnings originating from classes that are +located in a given directory/folder. +Errors and warnings may also be suppressed on a more granular level. +For example, the \code{-AonlyDefs} or the \code{-AskipDefs} command-line +options enable you to include or exclude classes from checking. + +Set the \code{-AskipDirs} command-line option to a +regular expression that matches the path to directories/folders containing +classes for which errors and warnings should be suppressed. + +For example, if you use +``{\codesize\verb|-AskipDirs=/build/generated|}'' on the command line +(with appropriate quoting) when invoking +\code{javac}, then the definitions of classes located in the +\codesize\verb|/build/generated| directory will not be checked. + +A common scenario for using this argument is when you want to exclude certain +directories/folders (such as those containing generated or legacy code) from +type-checking. \sectionAndLabel{\code{-Alint} command-line option\label{lint-options}}{alint} diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 67a61d8543d..ec0d51161d0 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -569,7 +569,7 @@ private String oneLine(Object arg) { */ @Override public final Void visitClass(ClassTree classTree, Void p) { - if (checker.shouldSkipDefs(classTree)) { + if (checker.shouldSkipDefs(classTree) || checker.shouldSkipDirs(classTree)) { // Not "return super.visitClass(classTree, p);" because that would recursively call // visitors on subtrees; we want to skip the class entirely. return null; diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index c73ff578399..e323c0c4a0b 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -125,6 +125,9 @@ "skipDefs", "onlyDefs", + // Set inclusion/exclusion of files based on directory + "skipDirs", + // Unsoundly assume all methods have no side effects, are deterministic, or both. "assumeSideEffectFree", "assumeDeterministic", @@ -550,6 +553,14 @@ public abstract class SourceChecker extends AbstractTypeProcessor implements Opt */ private @MonotonicNonNull Pattern onlyDefsPattern; + /** + * Regular expression pattern to specify directories that should not be checked. + * + *

          It contains the pattern specified by the user, through the option {@code checkers.skipDirs}; + * otherwise it contains a pattern that can match no directory. + */ + private @MonotonicNonNull Pattern skipDirsPattern; + /** The supported lint options. */ private @MonotonicNonNull Set supportedLints; @@ -893,14 +904,6 @@ private Pattern getPattern( } } - if (pattern.indexOf("/") != -1) { - throw new UserError( - "The " - + patternName - + " property contains \"/\", which will never match a class name: " - + pattern); - } - if (pattern.isEmpty()) { pattern = defaultPattern; } @@ -929,6 +932,17 @@ private Pattern getOnlyDefsPattern(Map options) { return getOnlyPattern("onlyDefs", options); } + /** + * Extract the value of the {@code skipDirs} option given the value of the options passed to the + * checker. + * + * @param options the map of options and their values passed to the checker + * @return the value of the {@code skipDirs} option + */ + private Pattern getSkipDirsPattern(Map options) { + return getSkipPattern("skipDirs", options); + } + /////////////////////////////////////////////////////////////////////////// /// Type-checking /// @@ -2687,6 +2701,43 @@ public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { return shouldSkipDefs(cls); } + /////////////////////////////////////////////////////////////////////////// + /// Skipping dirs + /// + + /** + * Tests whether the enclosing file path of the passed tree matches the pattern specified in the + * {@code checker.skipDirs} property. + * + * @param tree a tree + * @return true iff the enclosing directory of the tree should be skipped + */ + public final boolean shouldSkipDirs(ClassTree tree) { + if (tree == null) { + return false; + } + TypeElement typeElement = TreeUtils.elementFromDeclaration(tree); + if (typeElement == null) { + throw new BugInCF("elementFromDeclaration(%s [%s]) => null%n", tree, tree.getClass()); + } + String sourceFilePathForElement = ElementUtils.getSourceFilePath(typeElement); + return shouldSkipDirs(sourceFilePathForElement); + } + + /** + * Tests whether the file at the file path should be not be checked because it matches the {@code + * checker.skipDirs} property. + * + * @param path the path to the file to potentially skip + * @return true iff the checker should not check the file at {@code path} + */ + private boolean shouldSkipDirs(String path) { + if (skipDirsPattern == null) { + skipDirsPattern = getSkipDirsPattern(getOptions()); + } + return skipDirsPattern.matcher(path).find(); + } + /////////////////////////////////////////////////////////////////////////// /// Errors other than type-checking errors /// From ce88b3f381b31f55f090b9608c7580d75fbf7cf6 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 9 Jan 2024 15:57:26 -0800 Subject: [PATCH 010/173] Update Error Prone version to 2.24.1 --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 180249ef6c1..0258d57d748 100644 --- a/build.gradle +++ b/build.gradle @@ -563,6 +563,8 @@ allprojects { currentProj -> '-Xep:ExtendsObject:OFF', // For Visitors it is convenient to just pass a Void parameter. '-Xep:VoidUsed:OFF', + // The Checker Framework is the only nullness tool we care about. + '-Xep:NullableWildcard:OFF', // -Werror halts the build if Error Prone issues a warning, which ensures that // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) // stops as soon as it issues one warning, rather than outputting them all. From 13ea2adbf5d97a7e2c1c8abbec2062dce0fd33e6 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 10 Jan 2024 08:01:31 -0800 Subject: [PATCH 011/173] The latest version of Google Java Format formats Java 21 features. (#6386) --- build.gradle | 11 +++-------- checker/tests/nullness/java21/FlowSwitch.java | 4 ++-- .../tests/nullness/java21/NullableSwitchSelector.java | 6 +++--- checker/tests/nullness/java21/SimpleCaseGuard.java | 2 +- framework/tests/all-systems/java21/JEP440.java | 2 +- framework/tests/all-systems/java21/JEP441.java | 2 +- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 0258d57d748..ebd2037622d 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,7 @@ ext { versions = [ autoValue : '1.11.0', errorprone : '2.36.0', + googleJavaFormat : '1.19.2', hashmapUtil : '0.0.1', junit : '4.13.2', lombok : '1.18.36', @@ -145,7 +146,7 @@ if (currentRuntimeJavaVersion >= 11) { // > Could not create task ':spotlessJava'. // > Add a step with [com.google.googlejavaformat:google-java-format:1.15.0] into the `spotlessPredeclare` block in the root project. java { - googleJavaFormat() + googleJavaFormat(versions.googleJavaFormat) } groovyGradle { greclipse() @@ -277,12 +278,6 @@ allprojects { currentProj -> 'tests/*record*/' ] } - // As of 2023-09-24, google-java-format cannot parse Java 21 language features. - // See https://github.com/google/google-java-format/releases. - if (true) { - doNotFormat += ['tests/**/java21/'] - } - if (currentRuntimeJavaVersion < 21) { doNotFormat += ['tests/**/java21/'] } @@ -319,7 +314,7 @@ allprojects { currentProj -> } targetExclude doNotFormat - googleJavaFormat()// .aosp() + googleJavaFormat(versions.googleJavaFormat)// .aosp() // importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') formatAnnotations().addTypeAnnotation("PolyInitialized").addTypeAnnotation("PolyVP").addTypeAnnotation("ReceiverDependentQual") } diff --git a/checker/tests/nullness/java21/FlowSwitch.java b/checker/tests/nullness/java21/FlowSwitch.java index 65519df251e..6655d44b7bd 100644 --- a/checker/tests/nullness/java21/FlowSwitch.java +++ b/checker/tests/nullness/java21/FlowSwitch.java @@ -1,6 +1,6 @@ // @below-java21-jdk-skip-test -// None of the WPI formats support the new Java 21 languages features, so skip inference until they +// None of the WPI formats supports the new Java 21 languages features, so skip inference until they // do. // @infer-jaifs-skip-test // @infer-ajava-skip-test @@ -11,7 +11,7 @@ public class FlowSwitch { void test0(Number n) { String s = null; switch (n) { - default: + case null, default: { // TODO: this should issue a dereference of nullable error. n.toString(); diff --git a/checker/tests/nullness/java21/NullableSwitchSelector.java b/checker/tests/nullness/java21/NullableSwitchSelector.java index 9c4e8bfb2d2..bb613258f8a 100644 --- a/checker/tests/nullness/java21/NullableSwitchSelector.java +++ b/checker/tests/nullness/java21/NullableSwitchSelector.java @@ -2,7 +2,7 @@ // @below-java21-jdk-skip-test -// None of the WPI formats support the new Java 21 languages features, so skip inference until they +// None of the WPI formats supports the new Java 21 languages features, so skip inference until they // do. // @infer-jaifs-skip-test // @infer-ajava-skip-test @@ -13,7 +13,7 @@ static String formatterPatternSwitch1(@Nullable Object obj) { return switch (obj) { case Integer i -> obj.toString(); case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) + // :: error: (dereference.of.nullable) case null -> obj.toString(); default -> obj.toString(); }; @@ -35,7 +35,7 @@ static String formatterPatternSwitch3(@Nullable Object obj) { return switch (obj) { case Integer i -> obj.toString(); case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) + // :: error: (dereference.of.nullable) case null, default -> obj.toString(); }; } diff --git a/checker/tests/nullness/java21/SimpleCaseGuard.java b/checker/tests/nullness/java21/SimpleCaseGuard.java index 7d28129e944..ba7d8402aba 100644 --- a/checker/tests/nullness/java21/SimpleCaseGuard.java +++ b/checker/tests/nullness/java21/SimpleCaseGuard.java @@ -1,6 +1,6 @@ // @below-java21-jdk-skip-test -// None of the WPI formats support the new Java 21 languages features, so skip inference until they +// None of the WPI formats supports the new Java 21 languages features, so skip inference until they // do. // @infer-jaifs-skip-test // @infer-ajava-skip-test diff --git a/framework/tests/all-systems/java21/JEP440.java b/framework/tests/all-systems/java21/JEP440.java index 707bb176fe1..2c33031f0cd 100644 --- a/framework/tests/all-systems/java21/JEP440.java +++ b/framework/tests/all-systems/java21/JEP440.java @@ -1,6 +1,6 @@ // @below-java21-jdk-skip-test -// None of the WPI formats support the new Java 21 languages features, so skip inference until they +// None of the WPI formats supports the new Java 21 languages features, so skip inference until they // do. // @infer-jaifs-skip-test // @infer-ajava-skip-test diff --git a/framework/tests/all-systems/java21/JEP441.java b/framework/tests/all-systems/java21/JEP441.java index 9711b7f4e40..32116ad33e8 100644 --- a/framework/tests/all-systems/java21/JEP441.java +++ b/framework/tests/all-systems/java21/JEP441.java @@ -1,6 +1,6 @@ // @below-java21-jdk-skip-test -// None of the WPI formats support the new Java 21 languages features, so skip inference until they +// None of the WPI formats supports the new Java 21 languages features, so skip inference until they // do. // @infer-jaifs-skip-test // @infer-ajava-skip-test From f69173b362f293609bcf87ada82de6d3c2039d6e Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Wed, 29 Jan 2025 19:47:01 -0500 Subject: [PATCH 012/173] Switch back to newest version of GJF --- build.gradle | 2 +- checker/tests/nullness/java21/FlowSwitch.java | 3 ++- checker/tests/nullness/java21/NullableSwitchSelector.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index ebd2037622d..746c1313965 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ ext { versions = [ autoValue : '1.11.0', errorprone : '2.36.0', - googleJavaFormat : '1.19.2', + googleJavaFormat : '1.25.2', hashmapUtil : '0.0.1', junit : '4.13.2', lombok : '1.18.36', diff --git a/checker/tests/nullness/java21/FlowSwitch.java b/checker/tests/nullness/java21/FlowSwitch.java index 6655d44b7bd..89f48d91a5e 100644 --- a/checker/tests/nullness/java21/FlowSwitch.java +++ b/checker/tests/nullness/java21/FlowSwitch.java @@ -27,7 +27,8 @@ void test1(Integer i) { case -1, 1: msg = "-1 or 1"; break; - case Integer j when j > 0: + case Integer j + when j > 0: msg = "pos"; break; case Integer j: diff --git a/checker/tests/nullness/java21/NullableSwitchSelector.java b/checker/tests/nullness/java21/NullableSwitchSelector.java index bb613258f8a..8439bd05c54 100644 --- a/checker/tests/nullness/java21/NullableSwitchSelector.java +++ b/checker/tests/nullness/java21/NullableSwitchSelector.java @@ -13,7 +13,7 @@ static String formatterPatternSwitch1(@Nullable Object obj) { return switch (obj) { case Integer i -> obj.toString(); case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) + // :: error: (dereference.of.nullable) case null -> obj.toString(); default -> obj.toString(); }; @@ -35,7 +35,7 @@ static String formatterPatternSwitch3(@Nullable Object obj) { return switch (obj) { case Integer i -> obj.toString(); case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) + // :: error: (dereference.of.nullable) case null, default -> obj.toString(); }; } From c46c1278247aabea143c352a97a53b60d98cb40e Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 10 Jan 2024 13:26:55 -0800 Subject: [PATCH 013/173] Tweak exception message. (#6391) --- .../org/checkerframework/framework/util/AnnotatedTypes.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index b8af68e5cf3..a29fdca2e68 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -256,9 +256,7 @@ private static AnnotatedTypeMirror asOuterSuper( enclosingType = enclosingType.getEnclosingType(); } if (enclosingType == null) { - // TODO: work around a failure in guava that happens without this hack. - // throw new BugInCF("Enclosing type not found %s %s", dt, superType); - return superType; + throw new BugInCF("Enclosing type not found: type: %s supertype: %s", dt, superType); } return asSuper(atypeFactory, dt, superType); } From 5311ed331390456790975c3ccb275c3a763fed08 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:22:25 -0800 Subject: [PATCH 014/173] Update manual entries for the Nullness Checker and Optional Checker (#6390) --- docs/manual/nullness-checker.tex | 4 ++++ docs/manual/optional-checker.tex | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/docs/manual/nullness-checker.tex b/docs/manual/nullness-checker.tex index e866cfdb4ca..20fea3ddd7c 100644 --- a/docs/manual/nullness-checker.tex +++ b/docs/manual/nullness-checker.tex @@ -320,6 +320,10 @@ field to be non-null. \item[\refqualclass{checker/nullness/qual}{EnsuresNonNull}] + indicates a method postcondition. + With \, the successful return (i.e., a non-exceptional + return) of the annotated method results in the given expressions being + non-null. See the Javadoc for examples of its use. \item[\refqualclass{checker/nullness/qual}{EnsuresNonNullIf}] indicates a method postcondition. With \<@EnsuresNonNull>, the given expressions are non-null after the method returns; this is useful for a diff --git a/docs/manual/optional-checker.tex b/docs/manual/optional-checker.tex index 12c41c4d20e..320151afc32 100644 --- a/docs/manual/optional-checker.tex +++ b/docs/manual/optional-checker.tex @@ -98,6 +98,33 @@ \label{fig-optional-hierarchy} \end{figure} +\subsectionAndLabel{Optional method annotations}{optional-method-annotations} + +The Optional Checker supports several annotations that specify method +behavior. These are declaration annotations, not type annotations: they +apply to the method itself rather than to some particular type. + +\begin{description} + +\item[\refqualclass{checker/optional/qual}{RequiresPresent}] + indicates a method precondition: The annotated method expects the + specified expressions to be a present Optional when this + method is invoked. \<@RequiresPresent> is a useful annotation for a method + that requires a \<@MaybePresent> field to be \<@Present>. +\item[\refqualclass{checker/optional/qual}{EnsuresPresent}] + indicates a method postcondition. + The successful return (i.e., a non-exceptional return) of the annotated + method results in the given Optional expression being present. See the Javadoc + for examples of its use. +\item[\refqualclass{checker/optional/qual}{EnsuresPresentIf}] + indicates a method postcondition. With \<@EnsuresPresent>, the given + Optional expression is present after the method returns. With + \<@EnsuresPresentIf>, if the annotated + method returns the given boolean value (true or false), then the given + Optional expression is present. See the Javadoc for examples of their use. + +\end{description} + \sectionAndLabel{What the Optional Checker guarantees}{optional-guarantees} From 3408d88426eb21dde8a2a8f7abe3e14ce7d08941 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 10 Jan 2024 21:37:03 -0800 Subject: [PATCH 015/173] Fix problem with the order in which inference variables are resolved --- .../typeinference8/InvocationTypeInference.java | 12 ++++++++++++ .../typeinference8/types/VariableBounds.java | 16 ++++++++++++++++ .../java8inference/MapEntryGetFails.java | 11 +++++++++++ 3 files changed, 39 insertions(+) create mode 100644 framework/tests/all-systems/java8inference/MapEntryGetFails.java diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java index a7da602c1b4..bb045931048 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java @@ -8,6 +8,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; @@ -599,6 +600,17 @@ private BoundSet getB4(BoundSet b3, ConstraintSet c) { ConstraintSet subset = c.getClosedSubset(b3.getDependencies(newVariables)); Set alphas = subset.getAllInputVariables(); if (!alphas.isEmpty()) { + // First resolve only the variables with proper bounds. + for (Variable alpha : new ArrayList<>(alphas)) { + if (alpha.getBounds().onlyProperBounds()) { + Resolution.resolve(alpha, b3, context); + alphas.remove(alpha); + } + } + c.applyInstantiations(); + } + if (!alphas.isEmpty()) { + // Resolve any remaining variables that have bounds that are variable or inference types. Resolution.resolve(alphas, b3, context); c.applyInstantiations(); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java index 7e7e244cbfe..c36dd4494a3 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java @@ -359,6 +359,22 @@ private List getConstraintsFromParameterized(AbstractType s, AbstractTyp return constraints; } + /** + * Returns whether this variable only has bounds against proper types. + * + * @return whether this variable only has bounds against proper types. + */ + public boolean onlyProperBounds() { + for (BoundKind k : BoundKind.values()) { + for (AbstractType bound : bounds.get(k)) { + if (!bound.isProper()) { + return false; + } + } + } + return true; + } + /** * Return all lower bounds that are proper types. * diff --git a/framework/tests/all-systems/java8inference/MapEntryGetFails.java b/framework/tests/all-systems/java8inference/MapEntryGetFails.java new file mode 100644 index 00000000000..d28294f1fb4 --- /dev/null +++ b/framework/tests/all-systems/java8inference/MapEntryGetFails.java @@ -0,0 +1,11 @@ +import java.util.List; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class MapEntryGetFails { + void test(Stream> listStream) { + listStream.collect(Collectors.groupingByConcurrent(l -> l.get(1))).entrySet().stream() + .sorted(Entry.comparingByKey()); + } +} From 262abd9d13aebba30740502990f306f11697dbbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 08:28:03 -0800 Subject: [PATCH 016/173] Update dependency com.amazonaws:aws-java-sdk-bom to v1.12.634 (#6384) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- checker/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checker/build.gradle b/checker/build.gradle index abaae83b1a5..8a2aa670175 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -80,7 +80,7 @@ dependencies { testImplementation 'com.amazonaws:aws-java-sdk-ec2' testImplementation 'com.amazonaws:aws-java-sdk-kms' // The AWS SDK is used for testing the Called Methods Checker. - testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.780') + testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.634') // For the Resource Leak Checker's support for JavaEE. testImplementation 'javax.servlet:javax.servlet-api:4.0.1' // For the Resource Leak Checker's support for IOUtils. From ae56c5bcdeb81e6ccc1a6602f668f13288c1e3b7 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Sat, 13 Jan 2024 11:17:15 -0800 Subject: [PATCH 017/173] Disambiguate legal use of `this` in qualifier expressions (#6395) --- docs/manual/advanced-features.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 306bb8ce006..2a44aab819f 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1260,7 +1260,7 @@ \, the receiver object. You can write \ to annotate any variable or declaration where you could write \ in code. Notably, it cannot be used in annotations on declarations of - static fields or methods. For a field, \ is the field's + static fields or static methods. For a field, \ is the field's receiver (sometimes called its container). For a local variable, it is the method's receiver. From b3b3bcef49d2c96ddf693c089f25cbd2e526b466 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 10:08:39 -0800 Subject: [PATCH 018/173] Update plugin com.diffplug.spotless to v6.24.0 (#6398) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From d8c4da43aa8fcdaeae843e1f5eb2ddd51334a78c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 13 Jan 2024 15:22:30 -0800 Subject: [PATCH 019/173] Fix typos --- .../common/util/count/AnnotationStatistics.java | 2 +- .../org/checkerframework/common/util/report/ReportChecker.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java index 67833460d1e..a392df97d0d 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java @@ -54,7 +54,7 @@ * signature or in a body *

        1. {@code -Anolocations}: suppresses location output; only makes sense in conjunction with * {@code -Aannotations} - *
        2. {@code -Aannotationsummaryonly}: with both of the obove, only outputs a summary + *
        3. {@code -Aannotationsummaryonly}: with both of the above, only outputs a summary *
        4. Documenting refactoring ideas
        5. Version numbers for annotated libraries
        6. @@ -432,17 +432,27 @@

          How to change project visibility settings

        7. Click "Public".
        8. -

          What to do if a Continuous Integration build fails

          +

          Reproducing Continuous Integration build failures

          -Sometimes, CI tests for your pull request may fail even though your local build passed. -This is usually because the CI service performed more tests than you ran locally. +If a CI job fails, examine the CI logs.

          -First, examine the CI service's logs, which contain diagnostic output from the -failing command. You can determine which command was run from the logs, or -from the CI configuration file (such as azure-pipelines.yml). +You can also run the same test locally. Each CI job runs a different command. +You can see the commands in +file azure-pipelines.yml, +usually on lines starting with bash:. The scripts that are run +generally just invoke gradle to run particular tasks. +

          + +

          +Sometimes, CI tests for your pull request may fail even though the same command +passed locally. First, ensure you are using the same JDK. Second, all CI tests +are run in a Docker container, and you can use that container if necessary. The +container names are in top-level +file azure-pipelines.yml, +on lines starting with container:.

          From 79d6dc957eef6b8e7a7db1311f001d4dbf417fb3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 23:18:37 +0000 Subject: [PATCH 140/173] Update dependency io.github.classgraph:classgraph to v4.8.172 (#6537) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- framework/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/build.gradle b/framework/build.gradle index e37bbd08a6c..c1128b2b3ca 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation "org.plumelib:plume-util:${versions.plumeUtil}" implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" // Add this dependency if needed to debug classpath issues. - // implementation 'io.github.classgraph:classgraph:4.8.170' + // implementation 'io.github.classgraph:classgraph:4.8.172' testImplementation "junit:junit:${versions.junit}" testImplementation project(':framework-test') From 6072e7159941e793f4262bacde2b5c457580654e Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Sat, 20 Apr 2024 00:22:55 -0700 Subject: [PATCH 141/173] Add tips to fix inconsistent Gradle build state (#6536) Co-authored-by: Manu Sridharan --- docs/developer/developer-manual.html | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/developer/developer-manual.html b/docs/developer/developer-manual.html index 1f1acc34bab..89161cd5b89 100644 --- a/docs/developer/developer-manual.html +++ b/docs/developer/developer-manual.html @@ -161,6 +161,26 @@

          Build tasks

          main directory or any subproject runs the allTests task only in the framework project.

          +

          +An iterative build-test-debug cycle where you assemble the Checker Framework +and execute its tests can sometimes lead to a state where Gradle is not able to +compile and build the system for no obvious reason. +This is especially common when you kill tests before they fully finish, or kill +compilation before it is fully complete, leading to a bad state or an +inconsistent build cache. +

          + +

          +Below are some steps you might try to resolve this issue (in order): +

          +
            +
          • ./gradlew --no-build-cache assemble: attempts to rebuild the Checker Framework without the build cache. +
          • Kill any Gradle daemons; run ./gradlew --status to get a list of pids for Gradle. +
          • git clean -fdx: removes all files that are not under version control. Add or stash files that you would like to + keep before running this command. +
          • Delete the Gradle build cache, usually located at ~/.gradle/caches. Note: this will delete the entire build + cache, including the cache for other Gradle projects on your machine; the build step for the Checker Framework will need to re-download all required dependencies. +

          Testing the Checker Framework

          From 6432e1970f3205589933d61b49f36216923f080c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 22 Apr 2024 16:36:48 -0700 Subject: [PATCH 142/173] Disable guava_jdk11 CI job From 3e9e69b3f3486b125c8f2f4ac80feecdfd7dafe7 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 22 Apr 2024 16:37:13 -0700 Subject: [PATCH 143/173] Use Gradle 8.7 From ea9c0f843aef9485c3af929b308b9206d4cd1dab Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 22 Apr 2024 16:39:44 -0700 Subject: [PATCH 144/173] Use plume-util version 1.9.3 --- build.gradle | 2 +- .../MustCallConsistencyAnalyzer.java | 18 ++------------- .../ajava/InsertAjavaAnnotations.java | 23 +++++++++---------- .../stub/RemoveAnnotationsForInference.java | 6 ++--- 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/build.gradle b/build.gradle index 399b583abff..f4c0c164843 100644 --- a/build.gradle +++ b/build.gradle @@ -79,7 +79,7 @@ ext { junit : '4.13.2', lombok : '1.18.36', // plume-util includes a version of reflection-util. When updating ensure the versions are consistent. - plumeUtil : '1.9.0', + plumeUtil : '1.9.3', reflectionUtil : '1.1.3', ] } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java index 0ba4ce537a8..e28c11c9c2a 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java @@ -27,7 +27,6 @@ import java.util.Objects; import java.util.Set; import java.util.StringJoiner; -import java.util.stream.Collectors; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -84,6 +83,7 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; /** @@ -2586,7 +2586,7 @@ public static String collectionToString(Collection bwos) { for (BlockWithObligations bwo : bwos) { blocksWithDuplicates.add(bwo.block); } - List duplicateBlocks = duplicates(blocksWithDuplicates); + Collection duplicateBlocks = CollectionsPlume.duplicates(blocksWithDuplicates); StringJoiner result = new StringJoiner(", ", "BWOs[", "]"); for (BlockWithObligations bwo : bwos) { ImmutableSet obligations = bwo.obligations; @@ -2605,18 +2605,4 @@ public static String collectionToString(Collection bwos) { return result.toString(); } } - - // TODO: Use from plume-lib's CollectionsPlume once version 1.9.0 is released. - /** - * Returns the elements (once each) that appear more than once in the given collection. - * - * @param the type of elements - * @param c a collection - * @return the elements (once each) that appear more than once in the given collection - */ - public static List duplicates(Collection c) { - // Inefficient (because of streams) but simple implementation. - Set withoutDuplicates = new HashSet<>(); - return c.stream().filter(n -> !withoutDuplicates.add(n)).collect(Collectors.toList()); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java index 08272f6c5b7..62e3b57ff80 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java @@ -525,27 +525,26 @@ private static int compareInsertions(Insertion insertion1, Insertion insertion2) } /** - * Inserts all annotations from the ajava file at {@code annotationFilePath} into {@code - * javaFilePath}. + * Inserts all annotations from an ajava file into a Java file. * - * @param annotationFilePath path to an ajava file - * @param javaFilePath path to a Java file to insert annotation into + * @param annotationFileName an ajava file + * @param javaFileName a Java file to insert annotation into */ - public void insertAnnotations(String annotationFilePath, String javaFilePath) { + public void insertAnnotations(String annotationFileName, String javaFileName) { try { - File javaFile = new File(javaFilePath); - String fileContents = FilesPlume.readFile(javaFile); - String lineSeparator = FilesPlume.inferLineSeparator(annotationFilePath); - try (FileInputStream annotationInputStream = new FileInputStream(annotationFilePath)) { + File javaFile = new File(javaFileName); + String fileContents = FilesPlume.readString(Path.of(javaFileName)); + String lineSeparator = FilesPlume.inferLineSeparator(annotationFileName); + try (FileInputStream annotationInputStream = new FileInputStream(annotationFileName)) { String result = insertAnnotations(annotationInputStream, fileContents, lineSeparator); - FilesPlume.writeFile(javaFile, result); + FilesPlume.writeString(javaFile, result); } } catch (IOException e) { System.err.println( "Failed to insert annotations from file " - + annotationFilePath + + annotationFileName + " into file " - + javaFilePath); + + javaFileName); System.exit(1); } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java index 85f3ef7c34b..eeec1d2c2a3 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java @@ -102,9 +102,9 @@ private RemoveAnnotationsForInference() { * @param args command-line arguments: directories to process */ public static void main(String[] args) { - // TODO: using plume-lib's options here would be better, but would add a dependency - // to the whole Checker Framework, which is undesirable. Move this program elsewhere - // (e.g., to a plume-lib project)? + // TODO: using plume-lib's "Options" project here would be better, but would add a dependency to + // the whole Checker Framework, which is undesirable. Move this program elsewhere (e.g., to a + // plume-lib project)? if (args[0].contentEquals("-keepFile")) { if (args.length < 2) { System.err.println( From 89fe1fd4554a12db3a1b52c7c2adf450924cdf26 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 22 Apr 2024 21:30:11 -0700 Subject: [PATCH 145/173] Fix problems with type variables in type argument inference --- .../framework/util/AnnotatedTypes.java | 15 -------- .../util/typeinference8/types/Variable.java | 2 +- framework/tests/all-systems/LambdaParam.java | 35 +++++++++++++++++++ framework/tests/all-systems/PCGen.java | 16 +++++++++ 4 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 framework/tests/all-systems/LambdaParam.java create mode 100644 framework/tests/all-systems/PCGen.java diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index e24c6b992fa..5ed0cc950c4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -522,21 +522,6 @@ private static AnnotatedTypeMirror substituteTypeVariables( memberType = atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); } - if (receiverType.getKind() == TypeKind.DECLARED && member.getKind() == ElementKind.METHOD) { - AnnotatedDeclaredType capturedReceiver = - ((AnnotatedExecutableType) memberType).getReceiverType(); - TypeMirror s = types.asMemberOf(capturedReceiver.getUnderlyingType(), member); - AnnotatedExecutableType t = - (AnnotatedExecutableType) - AnnotatedTypeMirror.createType(s, atypeFactory, memberType.isDeclaration()); - t.setReceiverType(capturedReceiver.deepCopy()); - t.setElement((ExecutableElement) member); - - atypeFactory.initializeAtm(t); - atypeFactory.replaceAnnotations(memberType, t); - return t; - } - return memberType; } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java index 6a839ca172a..5c0fddf52c9 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java @@ -109,7 +109,7 @@ public VariableBounds getBounds() { * @param map used to determine if the bounds refer to another variable */ public void initialBounds(Theta map) { - TypeMirror upperBound = typeVariableJava.getUpperBound(); + TypeMirror upperBound = typeVariable.getUpperBound().getUnderlyingType(); // If Pl has no TypeBound, the bound {@literal al <: Object} appears in the set. Otherwise, // for each type T delimited by & in the TypeBound, the bound {@literal al <: T[P1:=a1,..., // Pp:=ap]} appears in the set; if this results in no proper upper bounds for al (only diff --git a/framework/tests/all-systems/LambdaParam.java b/framework/tests/all-systems/LambdaParam.java new file mode 100644 index 00000000000..37b234a39d2 --- /dev/null +++ b/framework/tests/all-systems/LambdaParam.java @@ -0,0 +1,35 @@ +import java.io.Serializable; + +@SuppressWarnings("all") // Just check for crashes. +public class LambdaParam> { + void method(Range restriction, Range> lowerBoundWindow) { + Cut> upperBoundOnLowerBounds = + Ordering.natural().min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); + } + + abstract static class Cut implements Comparable>, Serializable { + static Cut belowValue(C endpoint) { + throw new RuntimeException(); + } + } + + static class Ordering { + public static Ordering natural() { + throw new RuntimeException(); + } + + public E min(E a, E b) { + throw new RuntimeException(); + } + } + + public static final class Range { + final Cut upperBound = + new Cut() { + @Override + public int compareTo(Cut o) { + return 0; + } + }; + } +} diff --git a/framework/tests/all-systems/PCGen.java b/framework/tests/all-systems/PCGen.java new file mode 100644 index 00000000000..5b97f00e6ea --- /dev/null +++ b/framework/tests/all-systems/PCGen.java @@ -0,0 +1,16 @@ +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +public class PCGen { + private Map activeBonusMap = new HashMap<>(); + + void method(String prefix) { + final Set keys = + activeBonusMap.keySet().stream() + .filter(fullyQualifiedBonusType -> fullyQualifiedBonusType.startsWith(prefix)) + .collect(Collectors.toCollection(() -> new TreeSet<>())); + } +} From b88eb550292438ab59298d7f6e36113e8247150e Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 23 Apr 2024 11:59:16 -0700 Subject: [PATCH 146/173] Run shellcheck in CI --- checker/bin-devel/Makefile | 2 +- checker/bin-devel/test-cftests-all.sh | 1 - checker/bin-devel/test-cftests-inference-part1.sh | 1 - checker/bin-devel/test-cftests-inference-part2.sh | 1 - checker/bin-devel/test-cftests-inference.sh | 1 - checker/bin-devel/test-cftests-junit.sh | 1 - checker/bin-devel/test-cftests-nonjunit.sh | 1 - checker/bin-devel/test-daikon-part1.sh | 1 - checker/bin-devel/test-daikon-part2.sh | 1 - checker/bin-devel/test-daikon.sh | 1 - checker/bin-devel/test-downstream.sh | 1 - checker/bin-devel/test-guava-formatter.sh | 1 - checker/bin-devel/test-guava-index.sh | 1 - checker/bin-devel/test-guava-interning.sh | 1 - checker/bin-devel/test-guava-lock.sh | 1 - checker/bin-devel/test-guava-nullness.sh | 1 - checker/bin-devel/test-guava-regex.sh | 1 - checker/bin-devel/test-guava-signature.sh | 1 - checker/bin-devel/test-guava.sh | 1 - checker/bin-devel/test-misc.sh | 6 ++++-- checker/bin-devel/test-plume-lib.sh | 1 - checker/bin-devel/test-typecheck-part1.sh | 1 - checker/bin-devel/test-typecheck-part2.sh | 1 - checker/bin-devel/test-typecheck.sh | 1 - checker/bin/Makefile | 2 +- 25 files changed, 6 insertions(+), 26 deletions(-) diff --git a/checker/bin-devel/Makefile b/checker/bin-devel/Makefile index 40e3ecec6ac..f3ddb8108c7 100644 --- a/checker/bin-devel/Makefile +++ b/checker/bin-devel/Makefile @@ -2,7 +2,7 @@ SH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)sh' * | grep -v BASH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)bash' * | grep -v .git | grep -v "~") shell-script-style: - shellcheck --format=gcc ${SH_SCRIPTS} ${BASH_SCRIPTS} + shellcheck --format=gcc -P SCRIPTDIR ${SH_SCRIPTS} ${BASH_SCRIPTS} checkbashisms ${SH_SCRIPTS} showvars: diff --git a/checker/bin-devel/test-cftests-all.sh b/checker/bin-devel/test-cftests-all.sh index 96830e1e2a5..fe5417a2296 100755 --- a/checker/bin-devel/test-cftests-all.sh +++ b/checker/bin-devel/test-cftests-all.sh @@ -10,7 +10,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-cftests-inference-part1.sh b/checker/bin-devel/test-cftests-inference-part1.sh index 2064f33047e..3f516be3b6e 100755 --- a/checker/bin-devel/test-cftests-inference-part1.sh +++ b/checker/bin-devel/test-cftests-inference-part1.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-cftests-inference-part2.sh b/checker/bin-devel/test-cftests-inference-part2.sh index 1f35a502ded..ea5f1b33720 100755 --- a/checker/bin-devel/test-cftests-inference-part2.sh +++ b/checker/bin-devel/test-cftests-inference-part2.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-cftests-inference.sh b/checker/bin-devel/test-cftests-inference.sh index e9c27a3f8ea..5bef9a3d2bb 100755 --- a/checker/bin-devel/test-cftests-inference.sh +++ b/checker/bin-devel/test-cftests-inference.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-cftests-junit.sh b/checker/bin-devel/test-cftests-junit.sh index bd4d3884ec5..ff3caedf0e5 100755 --- a/checker/bin-devel/test-cftests-junit.sh +++ b/checker/bin-devel/test-cftests-junit.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh # Adding --max-workers=1 to avoid random failures in Github Actions. An alternative solution is to use --no-build-cache. diff --git a/checker/bin-devel/test-cftests-nonjunit.sh b/checker/bin-devel/test-cftests-nonjunit.sh index 55667f55a0c..90632b671ed 100755 --- a/checker/bin-devel/test-cftests-nonjunit.sh +++ b/checker/bin-devel/test-cftests-nonjunit.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh # Adding --max-workers=1 to avoid random failures in Github Actions. An alternative solution is to use --no-build-cache. diff --git a/checker/bin-devel/test-daikon-part1.sh b/checker/bin-devel/test-daikon-part1.sh index 627e663eb69..4969982136c 100755 --- a/checker/bin-devel/test-daikon-part1.sh +++ b/checker/bin-devel/test-daikon-part1.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090# In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh # Run assembleForJavac because it does not build the javadoc, so it is faster than assemble. diff --git a/checker/bin-devel/test-daikon-part2.sh b/checker/bin-devel/test-daikon-part2.sh index 1cb5e9d3117..2fba8fdcd7c 100755 --- a/checker/bin-devel/test-daikon-part2.sh +++ b/checker/bin-devel/test-daikon-part2.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh # Run assembleForJavac because it does not build the javadoc, so it is faster than assemble. diff --git a/checker/bin-devel/test-daikon.sh b/checker/bin-devel/test-daikon.sh index 7b3b153abfd..66cf85c9edf 100755 --- a/checker/bin-devel/test-daikon.sh +++ b/checker/bin-devel/test-daikon.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh # Run assembleForJavac because it does not build the javadoc, so it is faster than assemble. diff --git a/checker/bin-devel/test-downstream.sh b/checker/bin-devel/test-downstream.sh index 95cf1419347..c21e73e2d32 100755 --- a/checker/bin-devel/test-downstream.sh +++ b/checker/bin-devel/test-downstream.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava-formatter.sh b/checker/bin-devel/test-guava-formatter.sh index 63f50e1be7f..028d33fd885 100755 --- a/checker/bin-devel/test-guava-formatter.sh +++ b/checker/bin-devel/test-guava-formatter.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava-index.sh b/checker/bin-devel/test-guava-index.sh index fc1cca0679f..4e52a2725b5 100755 --- a/checker/bin-devel/test-guava-index.sh +++ b/checker/bin-devel/test-guava-index.sh @@ -8,7 +8,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava-interning.sh b/checker/bin-devel/test-guava-interning.sh index c44d6320aa6..7c1970e847d 100755 --- a/checker/bin-devel/test-guava-interning.sh +++ b/checker/bin-devel/test-guava-interning.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava-lock.sh b/checker/bin-devel/test-guava-lock.sh index 04200961692..f6226dc515b 100755 --- a/checker/bin-devel/test-guava-lock.sh +++ b/checker/bin-devel/test-guava-lock.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava-nullness.sh b/checker/bin-devel/test-guava-nullness.sh index 71b484d655a..1252f4a6402 100755 --- a/checker/bin-devel/test-guava-nullness.sh +++ b/checker/bin-devel/test-guava-nullness.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava-regex.sh b/checker/bin-devel/test-guava-regex.sh index 42dfcaf947e..3ab954d7cec 100755 --- a/checker/bin-devel/test-guava-regex.sh +++ b/checker/bin-devel/test-guava-regex.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava-signature.sh b/checker/bin-devel/test-guava-signature.sh index e7cd12dded8..95bca88a85a 100755 --- a/checker/bin-devel/test-guava-signature.sh +++ b/checker/bin-devel/test-guava-signature.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-guava.sh b/checker/bin-devel/test-guava.sh index 93589d4dd66..7f24646712f 100755 --- a/checker/bin-devel/test-guava.sh +++ b/checker/bin-devel/test-guava.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh ./gradlew assembleForJavac --console=plain -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.http.connectionTimeout=60000 diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index f2227a156e2..e6cd976a3f0 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh PLUME_SCRIPTS="$SCRIPTDIR/.plume-scripts" @@ -54,10 +53,13 @@ else fi if [ $status -ne 0 ]; then exit $status; fi +# Shell script style +make -C checker/bin shell-script-style +make -C checker/bin-devel shell-script-style ## User documentation ./gradlew manual -git diff --exit-code docs/manual/contributors.tex || \ +git diff --exit-code docs/manual/contributors.tex || \ (set +x && set +v && echo "docs/manual/contributors.tex is not up to date." && echo "If the above suggestion is appropriate, run: make -C docs/manual contributors.tex" && diff --git a/checker/bin-devel/test-plume-lib.sh b/checker/bin-devel/test-plume-lib.sh index 3f0f2ddbecf..4b992dcc542 100755 --- a/checker/bin-devel/test-plume-lib.sh +++ b/checker/bin-devel/test-plume-lib.sh @@ -37,7 +37,6 @@ echo "PACKAGES=" "${PACKAGES[@]}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh ./gradlew assembleForJavac --console=plain -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.http.connectionTimeout=60000 diff --git a/checker/bin-devel/test-typecheck-part1.sh b/checker/bin-devel/test-typecheck-part1.sh index 69dff413285..9ec4ef92f63 100755 --- a/checker/bin-devel/test-typecheck-part1.sh +++ b/checker/bin-devel/test-typecheck-part1.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-typecheck-part2.sh b/checker/bin-devel/test-typecheck-part2.sh index 87182e11038..ddda9026a99 100755 --- a/checker/bin-devel/test-typecheck-part2.sh +++ b/checker/bin-devel/test-typecheck-part2.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin-devel/test-typecheck.sh b/checker/bin-devel/test-typecheck.sh index ec4c0bcd1b1..4b1b86fcdab 100755 --- a/checker/bin-devel/test-typecheck.sh +++ b/checker/bin-devel/test-typecheck.sh @@ -7,7 +7,6 @@ export SHELLOPTS echo "SHELLOPTS=${SHELLOPTS}" SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) source "$SCRIPTDIR"/clone-related.sh diff --git a/checker/bin/Makefile b/checker/bin/Makefile index 40e3ecec6ac..f3ddb8108c7 100644 --- a/checker/bin/Makefile +++ b/checker/bin/Makefile @@ -2,7 +2,7 @@ SH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)sh' * | grep -v BASH_SCRIPTS = $(shell grep -r -l '^\#! \?\(/bin/\|/usr/bin/env \)bash' * | grep -v .git | grep -v "~") shell-script-style: - shellcheck --format=gcc ${SH_SCRIPTS} ${BASH_SCRIPTS} + shellcheck --format=gcc -P SCRIPTDIR ${SH_SCRIPTS} ${BASH_SCRIPTS} checkbashisms ${SH_SCRIPTS} showvars: From 045fa67d07352ec10aa051f5db790366b8f2740b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 24 Apr 2024 22:34:37 -0700 Subject: [PATCH 147/173] Avoid Azure timeouts by producing output during `:checker:test` task --- build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build.gradle b/build.gradle index f4c0c164843..ff0fc592f48 100644 --- a/build.gradle +++ b/build.gradle @@ -1188,7 +1188,18 @@ subprojects { // Run tests in parallel, except on CI where it seems to lead to flaky failures. // The TF_BUILD environment variable is set to 'True' for jobs running on Azure Pipelines. if (!System.getenv('TF_BUILD')?.equals('True')) { + // Not running under Azure Pipelines CI. maxParallelForks = Integer.MAX_VALUE + } else { + // Running under Azure Pipelines CI. + + // Azure seems to time out when a task doesn't produce periodic output. + if (project.name.is('checker')) { + testLogging { + events "started", "skipped", "failed" + displayGranularity 3 + } + } } if (project.name.is('checker')) { From 627564f62a7abc221b1c1527007d12076a8f727f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 26 Apr 2024 09:31:56 -0700 Subject: [PATCH 148/173] Set maxParallelForks as per Gradle's recommendation --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index ff0fc592f48..e596e1fe423 100644 --- a/build.gradle +++ b/build.gradle @@ -1190,6 +1190,10 @@ subprojects { if (!System.getenv('TF_BUILD')?.equals('True')) { // Not running under Azure Pipelines CI. maxParallelForks = Integer.MAX_VALUE + + // This uses Gradle's recommended value for `maxParallelForks`: + // https://docs.gradle.org/current/userguide/performance.html#optimize_java_projects + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 } else { // Running under Azure Pipelines CI. From 3f53692d62b7323d90fad06d5f9c930fd3be492b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 26 Apr 2024 14:07:45 -0700 Subject: [PATCH 149/173] Remove stray line --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index e596e1fe423..9f07781cfb5 100644 --- a/build.gradle +++ b/build.gradle @@ -1189,7 +1189,6 @@ subprojects { // The TF_BUILD environment variable is set to 'True' for jobs running on Azure Pipelines. if (!System.getenv('TF_BUILD')?.equals('True')) { // Not running under Azure Pipelines CI. - maxParallelForks = Integer.MAX_VALUE // This uses Gradle's recommended value for `maxParallelForks`: // https://docs.gradle.org/current/userguide/performance.html#optimize_java_projects From 4b474c69b66e281b518fe75dfa961aaf5392ec95 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:11:16 -0700 Subject: [PATCH 150/173] Update ubuntu Docker tag to v24 (#6552) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- checker/bin-devel/Dockerfile-ubuntu-jdk21 | 3 ++- checker/bin-devel/Dockerfile-ubuntu-jdk21-plus | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/checker/bin-devel/Dockerfile-ubuntu-jdk21 b/checker/bin-devel/Dockerfile-ubuntu-jdk21 index b2ba6dd9564..4c5e19ac45c 100644 --- a/checker/bin-devel/Dockerfile-ubuntu-jdk21 +++ b/checker/bin-devel/Dockerfile-ubuntu-jdk21 @@ -4,7 +4,8 @@ # Checker Framework and Annotation Tools repositories) for: (java|jdk).?21\b # "ubuntu" is the latest LTS release. "ubuntu:rolling" is the latest release. -FROM ubuntu:rolling +# As of 2023-11-15, "ubuntu:rolling" is still 23.04. +FROM ubuntu:24.04 MAINTAINER Werner Dietl # According to diff --git a/checker/bin-devel/Dockerfile-ubuntu-jdk21-plus b/checker/bin-devel/Dockerfile-ubuntu-jdk21-plus index 54f7a4a334d..a9f527aea02 100644 --- a/checker/bin-devel/Dockerfile-ubuntu-jdk21-plus +++ b/checker/bin-devel/Dockerfile-ubuntu-jdk21-plus @@ -4,7 +4,8 @@ # Checker Framework and Annotation Tools repositories) for: (java|jdk).?21\b # "ubuntu" is the latest LTS release. "ubuntu:rolling" is the latest release. -FROM ubuntu:rolling +# As of 2023-11-15, "ubuntu:rolling" is still 23.04. +FROM ubuntu:24.04 MAINTAINER Werner Dietl ## Keep this file in sync with ../../docs/manual/troubleshooting.tex From a0fbadccac98e78ac84360c93469669896f5f56e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 27 Apr 2024 09:08:06 +0000 Subject: [PATCH 151/173] Update plugin com.gorylenko.gradle-git-properties to v2.4.2 (#6554) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From d028fe2b067ce1921c37292efbc45c33db177adb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 27 Apr 2024 10:48:30 +0000 Subject: [PATCH 152/173] Update dependency org.checkerframework:stubparser to v3.25.10 (#6553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 8328b9cde4cd3fb7cd5bc5deb56a2bc99cf95e83 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 27 Apr 2024 22:18:19 -0700 Subject: [PATCH 153/173] Simplify calls to `List.toArray()` --- .../checker/formatter/qual/ConversionCategory.java | 2 +- .../checkerframework/checker/formatter/util/FormatUtil.java | 2 +- .../calledmethods/CalledMethodsAnnotatedTypeFactory.java | 2 +- .../checker/index/samelen/SameLenAnnotatedTypeFactory.java | 2 +- .../index/searchindex/SearchIndexAnnotatedTypeFactory.java | 3 +-- .../checker/mustcall/MustCallAnnotatedTypeFactory.java | 2 +- .../checker/resourceleak/MustCallConsistencyAnalyzer.java | 2 +- .../checker/resourceleak/MustCallInference.java | 3 +-- .../org/checkerframework/common/basetype/BaseTypeChecker.java | 3 +-- .../InitializedFieldsAnnotatedTypeFactory.java | 2 +- .../common/value/ValueAnnotatedTypeFactory.java | 4 ++-- .../checkerframework/framework/source/AggregateChecker.java | 3 +-- .../checkerframework/framework/type/AnnotatedTypeFactory.java | 2 +- .../java/org/checkerframework/framework/util/CheckerMain.java | 2 +- .../org/checkerframework/framework/test/junit/RangeTest.java | 2 +- 15 files changed, 16 insertions(+), 20 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java index 7da6fe4e6fa..71a0f376e77 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java @@ -144,7 +144,7 @@ public enum ConversionCategory { typesWithPrimitives.add(unwrapped); } } - this.types = typesWithPrimitives.toArray(new Class[typesWithPrimitives.size()]); + this.types = typesWithPrimitives.toArray(new Class[0]); } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java index ee35bfd159f..087b47ae0c2 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java @@ -257,7 +257,7 @@ private static Conversion[] parse(String format) { cs.add(new Conversion(c, indexFromFormat(m))); } } - return cs.toArray(new Conversion[cs.size()]); + return cs.toArray(new Conversion[0]); } public static class ExcessiveOrMissingFormatArgumentException diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java index a0c98b5c785..5aa98ce41fd 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java @@ -452,7 +452,7 @@ private AnnotationMirror ensuresCMAnno(String expression, List calledMet private AnnotationMirror ensuresCMAnno(String[] expressions, List calledMethods) { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresCalledMethods.class); builder.setValue("value", expressions); - builder.setValue("methods", calledMethods.toArray(new String[calledMethods.size()])); + builder.setValue("methods", calledMethods.toArray(new String[0])); AnnotationMirror am = builder.build(); return am; } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java index 65bfc497218..6f6e33a65f6 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java @@ -374,7 +374,7 @@ public List getSameLensFromString( */ public AnnotationMirror createSameLen(Collection exprs) { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class); - String[] exprArray = exprs.toArray(new String[exprs.size()]); + String[] exprArray = exprs.toArray(new String[0]); builder.setValue("value", exprArray); return builder.build(); } diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java index 868e1d96678..bdc81599f68 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java @@ -139,8 +139,7 @@ public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, Annota Set combinedSet = new HashSet<>(getValueElement(a1)); combinedSet.addAll(getValueElement(a2)); // The list is backed by the given array. - List combinedList = - Arrays.asList(combinedSet.toArray(new String[combinedSet.size()])); + List combinedList = Arrays.asList(combinedSet.toArray(new String[0])); // NegativeIndexFor <: SearchIndexFor. if (areSameByClass(a1, NegativeIndexFor.class) diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java index 343f7901354..80976b87a5a 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java @@ -433,7 +433,7 @@ public AnnotationMirror createMustCall(List val) { */ private AnnotationMirror createMustCallImpl(List methodList) { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MustCall.class); - String[] methodArray = methodList.toArray(new String[methodList.size()]); + String[] methodArray = methodList.toArray(new String[0]); Arrays.sort(methodArray); builder.setValue("value", methodArray); return builder.build(); diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java index e28c11c9c2a..745b17cda0a 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java @@ -2482,7 +2482,7 @@ private void incrementMustCallImpl(TypeMirror type) { // Create this annotation and use a subtype test because there's no guarantee that // cmAnno is actually an instance of CalledMethods: it could be CMBottom or CMPredicate. AnnotationMirror cmAnnoForMustCallMethods = - typeFactory.createCalledMethods(mustCallValues.toArray(new String[mustCallValues.size()])); + typeFactory.createCalledMethods(mustCallValues.toArray(new String[0])); return typeFactory .getQualifierHierarchy() .isSubtypeQualifiersOnly(cmAnno, cmAnnoForMustCallMethods); diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java index 5fba553a26f..b22bb3f86c4 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java @@ -564,8 +564,7 @@ private void addEnsuresCalledMethodsForDisposedFields() { for (String mustCallValue : methodToFields.keySet()) { Set fields = methodToFields.get(mustCallValue); AnnotationMirror am = - createEnsuresCalledMethods( - fields.toArray(new String[fields.size()]), new String[] {mustCallValue}); + createEnsuresCalledMethods(fields.toArray(new String[0]), new String[] {mustCallValue}); WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); wpi.addMethodDeclarationAnnotation(methodElt, am); } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index d11dc9b6fe4..ed2626a0b1e 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -816,8 +816,7 @@ public Set getSupportedOptions() { } options.addAll( - expandCFOptions( - Arrays.asList(this.getClass()), options.toArray(new String[options.size()]))); + expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0]))); supportedOptions = Collections.unmodifiableSet(options); } diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java index c3d25862b34..6edb24d99ea 100644 --- a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java @@ -201,7 +201,7 @@ private String[] fieldsToInitialize(TypeElement type) { } } - return result.toArray(new String[result.size()]); + return result.toArray(new String[0]); } /** diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index 5e166c28110..51e307efb82 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -1205,7 +1205,7 @@ public AnnotationMirror createMatchesRegexAnnotation(@Nullable List<@Regex Strin return BOTTOMVAL; } AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MatchesRegex.class); - builder.setValue("value", regexes.toArray(new String[regexes.size()])); + builder.setValue("value", regexes.toArray(new String[0])); return builder.build(); } @@ -1223,7 +1223,7 @@ public AnnotationMirror createDoesNotMatchRegexAnnotation(@Nullable List<@Regex return UNKNOWNVAL; } AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoesNotMatchRegex.class); - builder.setValue("value", regexes.toArray(new String[regexes.size()])); + builder.setValue("value", regexes.toArray(new String[0])); return builder.build(); } diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java index c90ce4fcdad..437a5554a0a 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java @@ -148,8 +148,7 @@ public final Set getSupportedOptions() { options.addAll(checker.getSupportedOptions()); } options.addAll( - expandCFOptions( - Arrays.asList(this.getClass()), options.toArray(new String[options.size()]))); + expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0]))); this.supportedOptions = options; } return this.supportedOptions; diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 710b591bf10..52ff2b74468 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -1073,7 +1073,7 @@ public void setRoot(@Nullable CompilationUnitTree root) { } if (candidateAjavaFiles.size() == 1) { currentFileAjavaTypes = new AnnotationFileElementTypes(this); - String ajavaPath = candidateAjavaFiles.toArray(new String[candidateAjavaFiles.size()])[0]; + String ajavaPath = candidateAjavaFiles.toArray(new String[0])[0]; try { currentFileAjavaTypes.parseAjavaFileWithTree(ajavaPath, root); } catch (Throwable e) { diff --git a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java index 9f21b3162d6..e76c4323a55 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java +++ b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java @@ -574,7 +574,7 @@ public int invokeCompiler() { } // Actually invoke the compiler - return ExecUtil.execute(args.toArray(new String[args.size()]), System.out, System.err); + return ExecUtil.execute(args.toArray(new String[0]), System.out, System.err); } private static void outputArgumentsToFile(String outputFilename, List args) { diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java index ef9b2f9c07e..0982ff1f6ed 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java @@ -105,7 +105,7 @@ public RangeTest() { } } } - ranges = rangesList.toArray(new Range[rangesList.size()]); + ranges = rangesList.toArray(new Range[0]); } /** The element is a member of the range. */ From 5dd5312b8d9f722180268e6408fe60f0c58d27e6 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 27 Apr 2024 23:37:04 -0700 Subject: [PATCH 154/173] Use Error Prone 2.27.0 --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 9f07781cfb5..84145dc7617 100644 --- a/build.gradle +++ b/build.gradle @@ -565,6 +565,8 @@ allprojects { currentProj -> '-Xep:VoidUsed:OFF', // The Checker Framework is the only nullness tool we care about. '-Xep:NullableWildcard:OFF', + // In the visitor pattern, it is natural to forward a Void reference. + '-Xep:VoidUsed:OFF', // -Werror halts the build if Error Prone issues a warning, which ensures that // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) // stops as soon as it issues one warning, rather than outputting them all. From 8d333e32ddf10ba8c82025ae6f78f0c29787f06a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 28 Apr 2024 00:42:19 -0700 Subject: [PATCH 155/173] Use Guava version 33.1.0 From b7148947e801361d470660bc2ecb1dce289993a9 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 30 Apr 2024 08:59:54 -0700 Subject: [PATCH 156/173] Update Stubparser to version 3.25.10(#6559) From 22f63ac6d3da9c420150f5355897110209439d27 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 1 May 2024 09:03:03 -0700 Subject: [PATCH 157/173] Prep for release. --- build.gradle | 2 +- docs/CHANGELOG.md | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 84145dc7617..a666b4fb5d0 100644 --- a/build.gradle +++ b/build.gradle @@ -158,7 +158,7 @@ allprojects { currentProj -> // * any new checkers have been added, or // * backward-incompatible changes have been made to APIs or elsewhere. // To make a snapshot release: ./gradlew publish - version '3.43.0-SNAPSHOT' + version '3.43.0' tasks.withType(JavaCompile).configureEach { options.fork = true diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cade775a96d..49fb10acc08 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,8 +1,12 @@ -Version 3.43.0 (?? ??, 2024) +Version 3.43.0 (May 1, 2024) ---------------------------- **User-visible changes:** +Method, constructor, lambda, and method reference type inference has been +greatly improved. The `-AconservativeUninferredTypeArguments` option is +no longer necessary and has been removed. + Renamed command-line arguments: * `-AskipDirs` has been renamed to `-AskipFiles`. `-AskipDirs` will continue to work for the time being. @@ -10,10 +14,6 @@ Renamed command-line arguments: New command-line arguments: * `-AonlyFiles` complements `-AskipFiles` -Method, constructor, lambda, and method reference type inference has been -greatly improved. The `-AconservativeUninferredTypeArguments` option is -no longer necessary and has been removed. - A specialized inference algorithm for the Resource Leak Checker runs automatically as part of whole-program inference. @@ -35,6 +35,11 @@ Renamed `BaseTypeVisitor.checkForPolymorphicQualifiers()` to **Closed issues:** +#979, #4559, #4593, #5058, #5734, #5781, #6071, #6093, #6239, #6297, #6317, +#6322, #6346, #6373, #6376, #6378, #6379, #6380, #6389, #6393, #6396, #6402, +#6406, #6407, #6417, #6421, #6430, #6433, #6438, #6442, #6473, #6480, #6507, +#6531, #6535. + Version 3.42.0-eisop6 (January ??, 2025) ---------------------------------------- From 6a3b244c057f2434805ae1e4f86d567c829390f2 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 1 May 2024 10:13:19 -0700 Subject: [PATCH 158/173] Only sign when publishing. --- gradle-mvn-push.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-mvn-push.gradle b/gradle-mvn-push.gradle index 79f5d394cb1..a40586a9cea 100644 --- a/gradle-mvn-push.gradle +++ b/gradle-mvn-push.gradle @@ -35,6 +35,6 @@ signing { // Only sign releases; snapshots are unsigned. tasks.withType(Sign).configureEach { onlyIf { - !isSnapshot + !isSnapshot && gradle.taskGraph.hasTask("publish") } } From 8ffc504632022e26bb5199cf71212a6243b1a482 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 1 May 2024 11:51:18 -0700 Subject: [PATCH 159/173] Use property instead. --- gradle-mvn-push.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-mvn-push.gradle b/gradle-mvn-push.gradle index a40586a9cea..ceba044ad3c 100644 --- a/gradle-mvn-push.gradle +++ b/gradle-mvn-push.gradle @@ -35,6 +35,6 @@ signing { // Only sign releases; snapshots are unsigned. tasks.withType(Sign).configureEach { onlyIf { - !isSnapshot && gradle.taskGraph.hasTask("publish") + !isSnapshot && project.hasProperty("release") } } From 70cd4f2672d4f3c1bdac6415e896c14da664abfc Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 1 May 2024 11:59:07 -0700 Subject: [PATCH 160/173] new release 3.43.0 From 0723d823d251169472a57ac1196e9f7feb1f17bc Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:07:32 -0500 Subject: [PATCH 161/173] Use correct package --- checker-qual/src/main/java/module-info.java | 2 +- .../framework/util/typeinference8/types/AbstractType.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/checker-qual/src/main/java/module-info.java b/checker-qual/src/main/java/module-info.java index 4c8fab8909d..cdc5b0e6d86 100644 --- a/checker-qual/src/main/java/module-info.java +++ b/checker-qual/src/main/java/module-info.java @@ -37,7 +37,7 @@ exports org.checkerframework.common.reflection.qual; exports org.checkerframework.common.returnsreceiver.qual; exports org.checkerframework.common.subtyping.qual; - exports org.checkerframework.common.util.count.report.qual; + exports org.checkerframework.common.util.report.qual; exports org.checkerframework.common.value.qual; exports org.checkerframework.dataflow.qual; exports org.checkerframework.framework.qual; diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java index 0da513b7018..a077e55b535 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java @@ -384,7 +384,7 @@ public AbstractType replaceTypeArgs(List args) { argTypes.add(arg.getAnnotatedType()); } newType.setTypeArguments(argTypes); - newType.replaceAnnotations(getAnnotatedType().getPrimaryAnnotations()); + newType.replaceAnnotations(getAnnotatedType().getAnnotations()); return create(newType, newTypeJava); } From 95a2e9905736ed9887de8e0d0e5b4949a49c3fc7 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:10:16 -0500 Subject: [PATCH 162/173] Use non-"Primary" method names --- .../ResourceLeakAnnotatedTypeFactory.java | 2 +- .../checker/resourceleak/ResourceLeakVisitor.java | 2 +- .../framework/ajava/JointJavacJavaParserVisitor.java | 4 ++-- .../util/typeinference8/types/InferenceType.java | 3 +-- .../util/typeinference8/types/InvocationType.java | 2 +- .../util/typeinference8/types/ProperType.java | 3 +-- .../util/typeinference8/types/UseOfVariable.java | 7 +++---- .../framework/util/typeinference8/types/Variable.java | 4 +--- .../util/typeinference8/util/Resolution.java | 11 +++++------ 9 files changed, 16 insertions(+), 22 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java index ba14af629d0..3a57f64195a 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java @@ -167,7 +167,7 @@ public AnnotationMirror getMustCallAnnotation(Object obj) { } else { throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getName()); } - return mustCallAnnotatedType.getPrimaryAnnotation(MustCall.class); + return mustCallAnnotatedType.getAnnotation(MustCall.class); } /** diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java index aff5596e7bc..f0de9088d76 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java @@ -233,7 +233,7 @@ private void checkMustCallAliasAnnotationForMethod( } AnnotatedTypeMirror returnType = mcAtf.getMethodReturnType(tree); - boolean isMustCallAliasAnnoOnReturnType = returnType.hasPrimaryAnnotation(PolyMustCall.class); + boolean isMustCallAliasAnnoOnReturnType = returnType.hasAnnotation(PolyMustCall.class); checkMustCallAliasAnnoMismatch( paramWithMustCallAliasAnno, isMustCallAliasAnnoOnReturnType, tree); } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java index c76d79831a9..3379d5b94a8 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java @@ -1127,9 +1127,9 @@ public Void visitNewArray(NewArrayTree javacTree, Node javaParserNode) { // TODO: Implement this. // // Some notes: - // - javacTree.getPrimaryAnnotations() seems to always return empty, any annotations on + // - javacTree.getAnnotations() seems to always return empty, any annotations on // the base type seem to go on the type itself in javacTree.getType(). The JavaParser - // version doesn't even have a corresponding getPrimaryAnnotations method. + // version doesn't even have a corresponding getAnnotations method. // - When there are no initializers, both systems use similar representations. The // dimensions line up. // - When there is an initializer, they differ greatly for multi-dimensional arrays. Javac diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java index 08d9405c53f..6450a922744 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java @@ -305,8 +305,7 @@ public String toString() { @Override public Set getQualifiers() { - return AbstractQualifier.create( - getAnnotatedType().getPrimaryAnnotations(), qualifierVars, context); + return AbstractQualifier.create(getAnnotatedType().getAnnotations(), qualifierVars, context); } /** diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java index 71751add6c6..bc194c0065a 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java @@ -74,7 +74,7 @@ public InvocationType( SimpleAnnotatedTypeScanner> s = new SimpleAnnotatedTypeScanner<>( (type, polys) -> { - for (AnnotationMirror a : type.getPrimaryAnnotations()) { + for (AnnotationMirror a : type.getAnnotations()) { if (typeFactory.getQualifierHierarchy().isPolymorphicQualifier(a)) { polys.add(a); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java index 2f9455c944d..7952418044a 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java @@ -265,8 +265,7 @@ public AbstractType applyInstantiations() { @Override public Set getQualifiers() { - return AbstractQualifier.create( - getAnnotatedType().getPrimaryAnnotations(), qualifierVars, context); + return AbstractQualifier.create(getAnnotatedType().getAnnotations(), qualifierVars, context); } @Override diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java index 5cb7e136f01..9cff553d79c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java @@ -57,11 +57,11 @@ public UseOfVariable( this.qualifierVars = qualifierVars; this.variable = variable; this.type = type.deepCopy(); - this.hasPrimaryAnno = !type.getPrimaryAnnotations().isEmpty(); + this.hasPrimaryAnno = !type.getAnnotations().isEmpty(); this.bots = new AnnotationMirrorSet(); this.tops = new AnnotationMirrorSet(); if (hasPrimaryAnno) { - for (AnnotationMirror anno : type.getPrimaryAnnotations()) { + for (AnnotationMirror anno : type.getAnnotations()) { bots.add(qh.getBottomAnnotation(anno)); tops.add(qh.getTopAnnotation(anno)); } @@ -187,8 +187,7 @@ public void addBound(BoundKind kind, AbstractType bound) { @Override public Set getQualifiers() { if (hasPrimaryAnno) { - return AbstractQualifier.create( - getAnnotatedType().getPrimaryAnnotations(), qualifierVars, context); + return AbstractQualifier.create(getAnnotatedType().getAnnotations(), qualifierVars, context); } else { return Collections.emptySet(); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java index 5c0fddf52c9..95aa1279d6a 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java @@ -132,9 +132,7 @@ public void initialBounds(Theta map) { Set quals = AbstractQualifier.create( - typeVariable.getLowerBound().getPrimaryAnnotations(), - AnnotationMirrorMap.emptyMap(), - context); + typeVariable.getLowerBound().getAnnotations(), AnnotationMirrorMap.emptyMap(), context); variableBounds.addQualifierBound(BoundKind.LOWER, quals); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java index c6b1a2e77d0..e09587a5512 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java @@ -286,14 +286,13 @@ private void resolveNoCapture(Variable ai) { if (lubProperType.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { Set newLubAnnos = qh.leastUpperBoundsQualifiersOnly( - lubAnnos, lubProperType.getAnnotatedType().getPrimaryAnnotations()); + lubAnnos, lubProperType.getAnnotatedType().getAnnotations()); lubProperType.getAnnotatedType().replaceAnnotations(newLubAnnos); } else { AnnotatedTypeVariable lubTV = (AnnotatedTypeVariable) lubProperType.getAnnotatedType(); Set newLubAnnos = - qh.leastUpperBoundsQualifiersOnly( - lubAnnos, lubTV.getLowerBound().getPrimaryAnnotations()); + qh.leastUpperBoundsQualifiersOnly(lubAnnos, lubTV.getLowerBound().getAnnotations()); lubTV.getLowerBound().replaceAnnotations(newLubAnnos); } } @@ -366,14 +365,14 @@ private static BoundSet resolveWithCapture( if (lowerBound.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { Set newLubAnnos = qh.leastUpperBoundsQualifiersOnly( - lowerBoundAnnos, lowerBound.getAnnotatedType().getPrimaryAnnotations()); + lowerBoundAnnos, lowerBound.getAnnotatedType().getAnnotations()); lowerBound.getAnnotatedType().replaceAnnotations(newLubAnnos); lowerBoundAnnos = newLubAnnos; } else { AnnotatedTypeVariable lubTV = (AnnotatedTypeVariable) lowerBound.getAnnotatedType(); Set newLubAnnos = qh.leastUpperBoundsQualifiersOnly( - lowerBoundAnnos, lubTV.getLowerBound().getPrimaryAnnotations()); + lowerBoundAnnos, lubTV.getLowerBound().getAnnotations()); lubTV.getLowerBound().replaceAnnotations(newLubAnnos); lowerBoundAnnos = newLubAnnos; } @@ -395,7 +394,7 @@ private static BoundSet resolveWithCapture( .typeFactory .getQualifierHierarchy() .greatestLowerBoundsQualifiersOnly( - upperBoundAnnos, upperBound.getAnnotatedType().getPrimaryAnnotations()); + upperBoundAnnos, upperBound.getAnnotatedType().getAnnotations()); upperBound.getAnnotatedType().replaceAnnotations(upperBoundAnnos); } } else { From 553a5f5ce437637c609de39a66d7e9b27cb913f5 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:12:37 -0500 Subject: [PATCH 163/173] Fix merge issues --- .../framework/source/SourceChecker.java | 3 -- .../framework/type/AnnotatedTypeFactory.java | 6 --- .../poly/AbstractQualifierPolymorphism.java | 2 +- .../framework/util/AnnotatedTypes.java | 3 +- .../util/defaults/QualifierDefaults.java | 37 +------------------ .../checkerframework/javacutil/TreeUtils.java | 33 +---------------- 6 files changed, 6 insertions(+), 78 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 004ed2825ac..8507ef0b442 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -618,9 +618,6 @@ public abstract class SourceChecker extends AbstractTypeProcessor implements Opt */ protected TreePathCacher treePathCacher = null; - /** Default constructor. */ - protected SourceChecker() {} - /** True if the -Afilenames command-line argument was passed. */ private boolean printFilenames; diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 52ff2b74468..f1dbd03de3b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -528,9 +528,6 @@ void checkRep(String aliasName) { /** Mapping from a Tree to its TreePath. Shared between all instances. */ private final TreePathCacher treePathCache; - /** Mapping from CFG-generated trees to their enclosing elements. */ - protected final Map artificialTreeToEnclosingElementMap; - /** Whether to ignore type arguments from raw types. */ public final boolean ignoreRawTypeArguments; @@ -1491,9 +1488,6 @@ public AnnotatedTypeMirror getAnnotatedType(Tree tree) { if (tree.getKind() == Kind.TYPE_CAST) { type = applyCaptureConversion(type); } - logGat( - "getAnnotatedType(%s): after addComputedTypeAnnotations, type=%s%n", - TreeUtils.toStringTruncated(tree, 60), type); if (shouldCache && (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD)) { // Don't cache VARIABLE diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java index 892e501a27b..93c63e2a85a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java @@ -215,7 +215,7 @@ public void resolve(NewClassTree tree, AnnotatedExecutableType type) { return; } List parameters = - AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments()); + AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments(), tree); List arguments = CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index 5ed0cc950c4..6346c04bd6a 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -995,7 +995,8 @@ private static AnnotatedTypeMirror glbSubtype( public static List adaptParameters( AnnotatedTypeFactory atypeFactory, AnnotatedExecutableType method, - List args) { + List args, + @Nullable NewClassTree tree) { List parameters = method.getParameterTypes(); // Handle anonymous constructors that extend a class with an enclosing type. // There is a mismatch between the number of parameters and arguments when diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index bcfd6319d4d..14ebde49909 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -680,42 +680,9 @@ private DefaultSet defaultsAt(Element elt) { return elementDefaults.get(elt); } - DefaultSet qualifiers = null; - - { - AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); - - if (dqAnno != null) { - qualifiers = new DefaultSet(); - Set p = fromDefaultQualifier(dqAnno); - - if (p != null) { - qualifiers.addAll(p); - } - } - } - - { - AnnotationMirror dqListAnno = - atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); - if (dqListAnno != null) { - if (qualifiers == null) { - qualifiers = new DefaultSet(); - } - - List values = - AnnotationUtils.getElementValueArray( - dqListAnno, defaultQualifierListValueElement, AnnotationMirror.class); - for (AnnotationMirror dqAnno : values) { - Set p = fromDefaultQualifier(dqAnno); - if (p != null) { - qualifiers.addAll(p); - } - } - } - } + DefaultSet qualifiers = defaultsAtDirect(elt); + DefaultSet parentDefaults; - Element parent; if (elt.getKind() == ElementKind.PACKAGE) { Element parent = ElementUtils.parentPackage((PackageElement) elt, elements); DefaultSet origParentDefaults = defaultsAt(parent); diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index d2ae940d758..6c83fc1e27e 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -2504,7 +2504,7 @@ public static boolean isSwitchStatement(Tree tree) { /** * Returns true if the given switch statement tree is an enhanced switch statement, as described - * in JSL + * in JLS * 14.11.2. * * @param switchTree the switch statement to check @@ -2553,37 +2553,6 @@ public static boolean isYield(Tree tree) { return tree.getKind().name().equals("YIELD"); } - /** - * Returns true if the given switch statement tree is an enhanced switch statement, as described - * in JSL - * 14.11.2. - * - * @param switchTree the switch statement to check - * @return true if the given tree is an enhanced switch statement - */ - public static boolean isEnhancedSwitchStatement(SwitchTree switchTree) { - TypeMirror exprType = typeOf(switchTree.getExpression()); - // TODO: this should be only char, byte, short, int, Character, Byte, Short, Integer. Is the - // over-approximation a problem? - Element exprElem = TypesUtils.getTypeElement(exprType); - boolean isNotEnum = exprElem == null || exprElem.getKind() != ElementKind.ENUM; - if (!TypesUtils.isPrimitiveOrBoxed(exprType) && !TypesUtils.isString(exprType) && isNotEnum) { - return true; - } - - for (CaseTree caseTree : switchTree.getCases()) { - for (Tree caseLabel : CaseUtils.getLabels(caseTree)) { - if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL - || TreeUtils.isBindingPatternTree(caseLabel) - || TreeUtils.isDeconstructionPatternTree(caseLabel)) { - return true; - } - } - } - - return false; - } - /** * Returns the value (expression) for {@code yieldTree}. * From 9e754c6f0cab9c8338a9e9fca5433899b58b4f0e Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:13:08 -0500 Subject: [PATCH 164/173] Move method to hopefully the right place --- .../InitializationAnnotatedTypeFactory.java | 19 ------------------- ...tializationParentAnnotatedTypeFactory.java | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java index eac335f6abd..2d2dbe2e9ce 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java @@ -1,12 +1,8 @@ package org.checkerframework.checker.initialization; import com.sun.source.tree.ClassTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.MemberReferenceTree; -import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; -import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; @@ -290,19 +286,4 @@ public static boolean isInitialized( return true; } - - @Override - protected ParameterizedExecutableType methodFromUse( - ExpressionTree tree, - ExecutableElement methodElt, - AnnotatedTypeMirror receiverType, - boolean inferTypeArgs) { - ParameterizedExecutableType x = - super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); - if (tree.getKind() == Kind.MEMBER_REFERENCE - && ((MemberReferenceTree) tree).getMode() == ReferenceMode.NEW) { - x.executableType.getReturnType().replaceAnnotation(INITIALIZED); - } - return x; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java index cee7a0c313f..51d9ea93f0b 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java @@ -4,6 +4,8 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewArrayTree; @@ -930,4 +932,20 @@ protected AnnotationMirror greatestLowerBoundWithElements( return createUnderInitializationAnnotation(typeFrame); } } + + // TODO: check where this method should go. + @Override + protected ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + ParameterizedExecutableType x = + super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); + if (tree.getKind() == Tree.Kind.MEMBER_REFERENCE + && ((MemberReferenceTree) tree).getMode() == ReferenceMode.NEW) { + x.executableType.getReturnType().replaceAnnotation(INITIALIZED); + } + return x; + } } From 3d90765eeb2e16c9c7ede56e0eadcd16c18b6f3a Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:14:00 -0500 Subject: [PATCH 165/173] Fix missing imports --- .../org/checkerframework/checker/nullness/NullnessChecker.java | 1 + .../framework/test/CheckerFrameworkPerDirectoryTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java index 8c0e2ba95b1..8e02ad65cae 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java @@ -4,6 +4,7 @@ import org.checkerframework.checker.initialization.InitializationChecker; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedLintOptions; /** diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java index 623dc9d25b6..c7726b42978 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java @@ -1,6 +1,7 @@ package org.checkerframework.framework.test; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; From 2ed69e3f7ad91a69ae26a16fb7f7e18de8c131cd Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:14:23 -0500 Subject: [PATCH 166/173] Suggested refactoring --- .../org/checkerframework/framework/test/TestUtilities.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java index 4240c73af3d..c7117a6e3da 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java @@ -3,7 +3,6 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; @@ -155,7 +154,7 @@ public static List> findJavaFilesPerDirectory(File parent, String... // Without this check Windows will treat the file as a meaningless one and skip it. if (dir.isFile()) { File p = dir; - try (BufferedReader br = new BufferedReader(new FileReader(dir))) { + try (BufferedReader br = Files.newBufferedReader(dir.toPath(), StandardCharsets.UTF_8)) { String allSystemPath = br.readLine(); if (allSystemPath == null) { throw new BugInCF("test directory does not exist: %s", dir); From 67e96012817fb46459ce40300edd7e70244d42a6 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:14:44 -0500 Subject: [PATCH 167/173] Keep original code --- .../checkerframework/common/aliasing/AliasingVisitor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java index 236ce2c337f..5a01d3d1b5e 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java @@ -269,9 +269,8 @@ protected void checkConstructorResult( // Don't issue warnings about @LeakedToResult or (implicit) @MaybeLeaked on constructor // results. if (!returnType.hasAnnotation(atypeFactory.NON_LEAKED)) { - constructorType = constructorType.shallowCopy(); - constructorType.shallowCopyReturnType(); - returnType = constructorType.getReturnType(); + // TODO: the visitor should not change qualifiers. + // Possible problem from aliasing of `returnType`, but all tests pass. returnType.replaceAnnotation(atypeFactory.NON_LEAKED); } From ce83dbb6203e2a352ec174ade7401d83b4e0d090 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:15:13 -0500 Subject: [PATCH 168/173] Remove inference parts NO-AFU --- .../common/basetype/BaseTypeVisitor.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 5141e7dd7f1..436b818b2d1 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1144,14 +1144,16 @@ protected void checkPurityAnnotations(MethodTree tree) { if (suggestPureMethods && !TreeUtils.isSynthetic(tree)) { // Issue a warning if the method is pure, but not annotated as such. EnumSet additionalKinds = r.getKinds().clone(); - if (!infer) { - // During WPI, propagate all purity kinds, even those that are already - // present (because they were inferred in a previous WPI round). - additionalKinds.removeAll(kinds); - } + /* NO-AFU + if (!infer) {*/ + // During WPI, propagate all purity kinds, even those that are already + // present (because they were inferred in a previous WPI round). + additionalKinds.removeAll(kinds); + // NO-AFU } if (TreeUtils.isConstructor(tree) || TreeUtils.isVoidReturn(tree)) { additionalKinds.remove(Pure.Kind.DETERMINISTIC); } + /* NO-AFU if (infer) { WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); @@ -1167,7 +1169,8 @@ protected void checkPurityAnnotations(MethodTree tree) { for (ExecutableElement overriddenElt : overriddenMethods) { inferPurityAnno(additionalKinds, wpi, overriddenElt); } - } else if (additionalKinds.isEmpty()) { + } else */ + if (additionalKinds.isEmpty()) { // No need to suggest @Impure, since it is equivalent to no annotation. } else if (additionalKinds.size() == 2) { checker.reportWarning(tree, "purity.more.pure", tree.getName()); @@ -1179,24 +1182,6 @@ protected void checkPurityAnnotations(MethodTree tree) { throw new BugInCF("Unexpected purity kind in " + additionalKinds); } } - /* NO-AFU - if (infer) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); - inferPurityAnno(additionalKinds, wpi, methodElt); - // The purity of overridden methods is impacted by the purity of this method. If a - // superclass method is pure, but an implementation in a subclass is not, WPI ought to treat - // **neither** as pure. The purity kind of the superclass method is the LUB of its own - // purity and the purity of all the methods that override it. Logically, this rule is the - // same as the WPI rule for overrides, but purity isn't a type system and therefore must be - // special-cased. - Set overriddenMethods = - ElementUtils.getOverriddenMethods(methodElt, types); - for (ExecutableElement overriddenElt : overriddenMethods) { - inferPurityAnno(additionalKinds, wpi, overriddenElt); - } - } - */ } // There will be code here that *may* use `body` (and may set `body` before using it). @@ -1992,7 +1977,7 @@ public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { methodName, invokedMethod.getTypeVariables()); List params = - AnnotatedTypes.adaptParameters(atypeFactory, invokedMethod, tree.getArguments()); + AnnotatedTypes.adaptParameters(atypeFactory, invokedMethod, tree.getArguments(), null); checkArguments(params, tree.getArguments(), methodName, method.getParameters()); checkVarargs(invokedMethod, tree); From 6355f519e153f2f71c1ab3f026605ec85968ff49 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:15:41 -0500 Subject: [PATCH 169/173] Don't use methods that only work with one top qualifier --- .../type/poly/DefaultQualifierPolymorphism.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java index 8d203c05d41..ef69e019e15 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java @@ -24,7 +24,7 @@ public class DefaultQualifierPolymorphism extends AbstractQualifierPolymorphism public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { super(env, factory); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + for (AnnotationMirror top : topQuals) { AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); if (poly != null) { polyQuals.put(poly, top); @@ -35,14 +35,16 @@ public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFact @Override protected void replace( AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { - if (replacements.isEmpty() && type.getEffectiveAnnotation() != null) { + if (replacements.isEmpty()) { // If the 'replacements' map is empty, it is likely a case where a method with // a varargs parameter was invoked with zero varargs actuals. // In this case, the polymorphic qualifiers should be replaced with the top type in // the qualifier hierarchy, since there is no further information to deduce. - AnnotationMirror effectiveAnno = type.getEffectiveAnnotation(); - if (qualHierarchy.isPolymorphicQualifier(effectiveAnno)) { - replacements.put(effectiveAnno, qualHierarchy.getTopAnnotation(effectiveAnno)); + for (AnnotationMirror top : topQuals) { + AnnotationMirror effectiveAnno = type.getEffectiveAnnotationInHierarchy(top); + if (effectiveAnno != null && qualHierarchy.isPolymorphicQualifier(effectiveAnno)) { + replacements.put(effectiveAnno, top); + } } } for (Map.Entry pqentry : replacements.entrySet()) { From e7bf7cdc4e88a81ab81ba2f95233b662afa01454 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:16:07 -0500 Subject: [PATCH 170/173] Adapt to method renamings --- .../org/checkerframework/framework/util/AtmLubVisitor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java index e191fd7985f..caed91dcc0c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java @@ -220,16 +220,16 @@ public Void visitDeclared_Declared( private void lubTypeArgument( AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { if ((type1.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type1).isUninferredTypeArgument()) + && ((AnnotatedWildcardType) type1).isTypeArgOfRawType()) || (type2.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type2).isUninferredTypeArgument())) { + && ((AnnotatedWildcardType) type2).isTypeArgOfRawType())) { // The asSuper calls below don't seem to retain if a type variable was uninferred. // There is a similar check in the wildcards branch below, not sure when that is // actually hit. // TODO: see whether anything else should be done. See typetools issue 6438 and eisop // issue 703. if (lub.getKind() == TypeKind.WILDCARD) { - ((AnnotatedWildcardType) lub).setUninferredTypeArgument(); + ((AnnotatedWildcardType) lub).setTypeArgOfRawType(); } return; } From 0d7bcc13ca3c9f819bcb3fc2857a8abe2f2af736 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:16:27 -0500 Subject: [PATCH 171/173] Adapt gradle syntax --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a666b4fb5d0..e775d28a6ce 100644 --- a/build.gradle +++ b/build.gradle @@ -158,7 +158,7 @@ allprojects { currentProj -> // * any new checkers have been added, or // * backward-incompatible changes have been made to APIs or elsewhere. // To make a snapshot release: ./gradlew publish - version '3.43.0' + version = '3.43.0' tasks.withType(JavaCompile).configureEach { options.fork = true From a99afcc7c08ddda10628ff2ac25f029e7473c826 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 16:56:46 -0500 Subject: [PATCH 172/173] Change back to EISOP formatting --- build.gradle | 2288 +-- checker-qual-android/build.gradle | 96 +- checker-qual/build.gradle | 86 +- checker-qual/src/main/java/module-info.java | 72 +- .../checker/builder/qual/CalledMethods.java | 12 +- .../builder/qual/NotCalledMethods.java | 12 +- .../calledmethods/qual/CalledMethods.java | 17 +- .../qual/CalledMethodsBottom.java | 7 +- .../qual/CalledMethodsPredicate.java | 23 +- .../qual/EnsuresCalledMethods.java | 71 +- .../qual/EnsuresCalledMethodsIf.java | 85 +- .../qual/EnsuresCalledMethodsOnException.java | 105 +- .../qual/EnsuresCalledMethodsVarArgs.java | 12 +- .../qual/RequiresCalledMethods.java | 65 +- .../compilermsgs/qual/CompilerMessageKey.java | 3 +- .../qual/CompilerMessageKeyBottom.java | 9 +- .../qual/UnknownCompilerMessageKey.java | 7 +- .../fenum/qual/AwtAlphaCompositingRule.java | 3 +- .../checker/fenum/qual/AwtColorSpace.java | 3 +- .../checker/fenum/qual/AwtCursorType.java | 3 +- .../checker/fenum/qual/AwtFlowLayout.java | 3 +- .../checker/fenum/qual/Fenum.java | 5 +- .../checker/fenum/qual/FenumBottom.java | 9 +- .../checker/fenum/qual/FenumTop.java | 17 +- .../checker/fenum/qual/FenumUnqualified.java | 9 +- .../checker/fenum/qual/PolyFenum.java | 3 +- .../fenum/qual/SwingBoxOrientation.java | 3 +- .../fenum/qual/SwingCompassDirection.java | 3 +- .../fenum/qual/SwingElementOrientation.java | 3 +- .../qual/SwingHorizontalOrientation.java | 3 +- .../fenum/qual/SwingSplitPaneOrientation.java | 3 +- .../fenum/qual/SwingTextOrientation.java | 3 +- .../fenum/qual/SwingTitleJustification.java | 3 +- .../fenum/qual/SwingTitlePosition.java | 3 +- .../fenum/qual/SwingVerticalOrientation.java | 3 +- .../formatter/qual/ConversionCategory.java | 610 +- .../checker/formatter/qual/Format.java | 19 +- .../checker/formatter/qual/FormatBottom.java | 9 +- .../checker/formatter/qual/InvalidFormat.java | 15 +- .../checker/formatter/qual/UnknownFormat.java | 11 +- .../checker/guieffect/qual/AlwaysSafe.java | 5 +- .../checker/guieffect/qual/PolyUI.java | 3 +- .../checker/guieffect/qual/UI.java | 3 +- .../checker/i18n/qual/LocalizableKey.java | 3 +- .../i18n/qual/LocalizableKeyBottom.java | 9 +- .../checker/i18n/qual/Localized.java | 21 +- .../i18n/qual/UnknownLocalizableKey.java | 7 +- .../checker/i18n/qual/UnknownLocalized.java | 7 +- .../qual/I18nConversionCategory.java | 356 +- .../i18nformatter/qual/I18nFormat.java | 15 +- .../i18nformatter/qual/I18nFormatBottom.java | 9 +- .../i18nformatter/qual/I18nFormatFor.java | 21 +- .../i18nformatter/qual/I18nInvalidFormat.java | 13 +- .../i18nformatter/qual/I18nUnknownFormat.java | 11 +- .../checker/index/qual/EnsuresLTLengthOf.java | 91 +- .../index/qual/EnsuresLTLengthOfIf.java | 105 +- .../checker/index/qual/GTENegativeOne.java | 3 +- .../checker/index/qual/HasSubsequence.java | 21 +- .../checker/index/qual/IndexFor.java | 4 +- .../checker/index/qual/IndexOrHigh.java | 6 +- .../checker/index/qual/IndexOrLow.java | 4 +- .../checker/index/qual/LTEqLengthOf.java | 11 +- .../checker/index/qual/LTLengthOf.java | 29 +- .../checker/index/qual/LTOMLengthOf.java | 15 +- .../checker/index/qual/LengthOf.java | 4 +- .../checker/index/qual/LessThan.java | 27 +- .../checker/index/qual/LessThanBottom.java | 3 +- .../checker/index/qual/LessThanUnknown.java | 7 +- .../checker/index/qual/LowerBoundBottom.java | 7 +- .../checker/index/qual/LowerBoundUnknown.java | 7 +- .../checker/index/qual/NegativeIndexFor.java | 17 +- .../checker/index/qual/NonNegative.java | 3 +- .../checker/index/qual/PolyIndex.java | 3 +- .../checker/index/qual/PolyLength.java | 3 +- .../checker/index/qual/PolyLowerBound.java | 3 +- .../checker/index/qual/PolySameLen.java | 3 +- .../checker/index/qual/PolyUpperBound.java | 3 +- .../checker/index/qual/Positive.java | 3 +- .../checker/index/qual/SameLen.java | 11 +- .../checker/index/qual/SameLenBottom.java | 7 +- .../checker/index/qual/SameLenUnknown.java | 7 +- .../checker/index/qual/SearchIndexBottom.java | 7 +- .../checker/index/qual/SearchIndexFor.java | 17 +- .../index/qual/SearchIndexUnknown.java | 7 +- .../index/qual/SubstringIndexBottom.java | 7 +- .../checker/index/qual/SubstringIndexFor.java | 41 +- .../index/qual/SubstringIndexUnknown.java | 7 +- .../checker/index/qual/UpperBoundBottom.java | 7 +- .../checker/index/qual/UpperBoundLiteral.java | 19 +- .../checker/index/qual/UpperBoundUnknown.java | 7 +- .../initialization/qual/FBCBottom.java | 11 +- .../qual/HoldsForDefaultValue.java | 3 +- .../initialization/qual/Initialized.java | 17 +- .../initialization/qual/PolyInitialized.java | 3 +- .../qual/UnderInitialization.java | 21 +- .../qual/UnknownInitialization.java | 25 +- .../interning/qual/CompareToMethod.java | 3 +- .../checker/interning/qual/EqualsMethod.java | 3 +- .../checker/interning/qual/InternMethod.java | 3 +- .../checker/interning/qual/Interned.java | 31 +- .../interning/qual/InternedDistinct.java | 7 +- .../checker/interning/qual/PolyInterned.java | 3 +- .../interning/qual/UnknownInterned.java | 7 +- .../checker/lock/qual/EnsuresLockHeld.java | 57 +- .../checker/lock/qual/EnsuresLockHeldIf.java | 75 +- .../checker/lock/qual/GuardSatisfied.java | 33 +- .../checker/lock/qual/GuardedBy.java | 73 +- .../checker/lock/qual/GuardedByBottom.java | 7 +- .../checker/lock/qual/GuardedByUnknown.java | 3 +- .../checker/lock/qual/Holding.java | 17 +- .../checker/lock/qual/LockHeld.java | 5 +- .../checker/lock/qual/LockPossiblyHeld.java | 9 +- .../checker/lock/qual/LockingFree.java | 7 +- .../checker/lock/qual/MayReleaseLocks.java | 3 +- .../checker/lock/qual/NewObject.java | 19 +- .../checker/lock/qual/ReleasesNoLocks.java | 7 +- .../mustcall/qual/CreatesMustCallFor.java | 69 +- .../mustcall/qual/InheritableMustCall.java | 12 +- .../checker/mustcall/qual/MustCall.java | 21 +- .../mustcall/qual/MustCallUnknown.java | 3 +- .../checker/mustcall/qual/PolyMustCall.java | 3 +- .../nullness/qual/AssertNonNullIfNonNull.java | 12 +- .../checker/nullness/qual/EnsuresKeyFor.java | 75 +- .../nullness/qual/EnsuresKeyForIf.java | 79 +- .../checker/nullness/qual/EnsuresNonNull.java | 51 +- .../nullness/qual/EnsuresNonNullIf.java | 63 +- .../checker/nullness/qual/KeyFor.java | 19 +- .../checker/nullness/qual/KeyForBottom.java | 9 +- .../nullness/qual/MonotonicNonNull.java | 5 +- .../checker/nullness/qual/NonNull.java | 33 +- .../checker/nullness/qual/Nullable.java | 9 +- .../checker/nullness/qual/PolyKeyFor.java | 3 +- .../checker/nullness/qual/PolyNull.java | 3 +- .../nullness/qual/RequiresNonNull.java | 51 +- .../checker/nullness/qual/UnknownKeyFor.java | 11 +- .../checker/optional/qual/EnsuresPresent.java | 17 +- .../optional/qual/EnsuresPresentIf.java | 63 +- .../checker/optional/qual/MaybePresent.java | 5 +- .../checker/optional/qual/OptionalBottom.java | 3 +- .../optional/qual/OptionalCreator.java | 3 +- .../optional/qual/OptionalEliminator.java | 3 +- .../optional/qual/OptionalPropagator.java | 3 +- .../checker/optional/qual/PolyPresent.java | 3 +- .../checker/optional/qual/Present.java | 3 +- .../optional/qual/RequiresPresent.java | 47 +- .../checker/propkey/qual/PropertyKey.java | 3 +- .../propkey/qual/PropertyKeyBottom.java | 9 +- .../propkey/qual/UnknownPropertyKey.java | 5 +- .../checker/regex/qual/PartialRegex.java | 15 +- .../checker/regex/qual/PolyRegex.java | 3 +- .../checker/regex/qual/Regex.java | 7 +- .../checker/regex/qual/RegexBottom.java | 11 +- .../checker/regex/qual/UnknownRegex.java | 11 +- .../signature/qual/ArrayWithoutPackage.java | 3 +- .../checker/signature/qual/BinaryName.java | 3 +- .../qual/BinaryNameOrPrimitiveType.java | 3 +- .../qual/BinaryNameWithoutPackage.java | 3 +- .../checker/signature/qual/CanonicalName.java | 9 +- .../qual/CanonicalNameAndBinaryName.java | 3 +- .../signature/qual/CanonicalNameOrEmpty.java | 3 +- .../qual/CanonicalNameOrPrimitiveType.java | 3 +- .../checker/signature/qual/ClassGetName.java | 3 +- .../signature/qual/ClassGetSimpleName.java | 3 +- .../qual/DotSeparatedIdentifiers.java | 3 +- ...otSeparatedIdentifiersOrPrimitiveType.java | 3 +- .../signature/qual/FieldDescriptor.java | 3 +- .../qual/FieldDescriptorForPrimitive.java | 3 +- .../qual/FieldDescriptorWithoutPackage.java | 3 +- .../checker/signature/qual/FqBinaryName.java | 3 +- .../signature/qual/FullyQualifiedName.java | 3 +- .../checker/signature/qual/Identifier.java | 9 +- .../qual/IdentifierOrPrimitiveType.java | 3 +- .../checker/signature/qual/InternalForm.java | 3 +- .../signature/qual/MethodDescriptor.java | 3 +- .../checker/signature/qual/PolySignature.java | 3 +- .../checker/signature/qual/PrimitiveType.java | 3 +- .../signature/qual/SignatureBottom.java | 17 +- .../signature/qual/SignatureUnknown.java | 5 +- .../checker/signedness/qual/PolySigned.java | 3 +- .../checker/signedness/qual/Signed.java | 49 +- .../signedness/qual/SignedPositive.java | 3 +- .../signedness/qual/SignednessBottom.java | 11 +- .../signedness/qual/SignednessGlb.java | 3 +- .../signedness/qual/UnknownSignedness.java | 3 +- .../checker/signedness/qual/Unsigned.java | 17 +- .../checker/tainting/qual/PolyTainted.java | 3 +- .../checker/tainting/qual/Tainted.java | 5 +- .../checker/tainting/qual/Untainted.java | 11 +- .../checker/units/qual/A.java | 5 +- .../checker/units/qual/Acceleration.java | 3 +- .../checker/units/qual/Angle.java | 3 +- .../checker/units/qual/Area.java | 3 +- .../checker/units/qual/C.java | 3 +- .../checker/units/qual/Current.java | 3 +- .../checker/units/qual/Force.java | 3 +- .../checker/units/qual/K.java | 5 +- .../checker/units/qual/Length.java | 3 +- .../checker/units/qual/Luminance.java | 3 +- .../checker/units/qual/Mass.java | 3 +- .../checker/units/qual/MixedUnits.java | 3 +- .../checker/units/qual/N.java | 5 +- .../checker/units/qual/PolyUnit.java | 3 +- .../checker/units/qual/Prefix.java | 84 +- .../checker/units/qual/Speed.java | 3 +- .../checker/units/qual/Substance.java | 3 +- .../checker/units/qual/Temperature.java | 3 +- .../checker/units/qual/Time.java | 3 +- .../checker/units/qual/UnitsBottom.java | 9 +- .../checker/units/qual/UnitsMultiple.java | 24 +- .../checker/units/qual/UnitsRelations.java | 22 +- .../checker/units/qual/UnknownUnits.java | 7 +- .../checker/units/qual/Volume.java | 3 +- .../checker/units/qual/cd.java | 5 +- .../checker/units/qual/degrees.java | 3 +- .../checker/units/qual/g.java | 5 +- .../checker/units/qual/h.java | 3 +- .../checker/units/qual/kN.java | 3 +- .../checker/units/qual/kg.java | 3 +- .../checker/units/qual/km.java | 3 +- .../checker/units/qual/km2.java | 3 +- .../checker/units/qual/km3.java | 3 +- .../checker/units/qual/kmPERh.java | 3 +- .../checker/units/qual/m.java | 5 +- .../checker/units/qual/m2.java | 7 +- .../checker/units/qual/m3.java | 3 +- .../checker/units/qual/mPERs.java | 5 +- .../checker/units/qual/mPERs2.java | 5 +- .../checker/units/qual/min.java | 3 +- .../checker/units/qual/mm.java | 3 +- .../checker/units/qual/mm2.java | 3 +- .../checker/units/qual/mm3.java | 3 +- .../checker/units/qual/mol.java | 5 +- .../checker/units/qual/radians.java | 5 +- .../checker/units/qual/s.java | 5 +- .../checker/units/qual/t.java | 3 +- .../common/aliasing/qual/LeakedToResult.java | 3 +- .../common/aliasing/qual/MaybeAliased.java | 13 +- .../common/aliasing/qual/MaybeLeaked.java | 7 +- .../common/aliasing/qual/NonLeaked.java | 3 +- .../common/aliasing/qual/Unique.java | 3 +- .../qual/EnsuresInitializedFields.java | 65 +- .../qual/InitializedFields.java | 17 +- .../qual/InitializedFieldsBottom.java | 7 +- .../qual/PolyInitializedFields.java | 3 +- .../common/reflection/qual/ClassBound.java | 13 +- .../common/reflection/qual/ClassVal.java | 21 +- .../reflection/qual/ClassValBottom.java | 9 +- .../common/reflection/qual/MethodVal.java | 26 +- .../reflection/qual/MethodValBottom.java | 9 +- .../common/reflection/qual/UnknownClass.java | 11 +- .../common/reflection/qual/UnknownMethod.java | 11 +- .../returnsreceiver/qual/BottomThis.java | 7 +- .../common/returnsreceiver/qual/This.java | 7 +- .../returnsreceiver/qual/UnknownThis.java | 9 +- .../common/subtyping/qual/Bottom.java | 7 +- .../common/subtyping/qual/Unqualified.java | 5 +- .../util/report/qual/ReportUnqualified.java | 7 +- .../common/value/qual/ArrayLen.java | 7 +- .../common/value/qual/ArrayLenRange.java | 27 +- .../common/value/qual/BoolVal.java | 7 +- .../common/value/qual/BottomVal.java | 33 +- .../common/value/qual/DoesNotMatchRegex.java | 15 +- .../common/value/qual/DoubleVal.java | 7 +- .../common/value/qual/EnsuresMinLenIf.java | 83 +- .../common/value/qual/EnumVal.java | 13 +- .../common/value/qual/IntRange.java | 27 +- .../qual/IntRangeFromGTENegativeOne.java | 3 +- .../value/qual/IntRangeFromNonNegative.java | 3 +- .../value/qual/IntRangeFromPositive.java | 3 +- .../common/value/qual/IntVal.java | 7 +- .../common/value/qual/MatchesRegex.java | 15 +- .../common/value/qual/MinLen.java | 4 +- .../value/qual/MinLenFieldInvariant.java | 23 +- .../common/value/qual/PolyValue.java | 3 +- .../common/value/qual/StringVal.java | 7 +- .../common/value/qual/UnknownVal.java | 7 +- .../dataflow/qual/AssertMethod.java | 52 +- .../checkerframework/dataflow/qual/Pure.java | 14 +- .../framework/qual/AnnotatedFor.java | 20 +- .../framework/qual/CFComment.java | 16 +- .../ConditionalPostconditionAnnotation.java | 12 +- .../framework/qual/Covariant.java | 4 +- .../framework/qual/DefaultFor.java | 108 +- .../framework/qual/DefaultQualifier.java | 98 +- .../qual/DefaultQualifierForUse.java | 4 +- .../framework/qual/EnsuresQualifier.java | 58 +- .../framework/qual/EnsuresQualifierIf.java | 76 +- .../framework/qual/FieldInvariant.java | 19 +- .../framework/qual/HasQualifierParameter.java | 12 +- .../framework/qual/LiteralKind.java | 86 +- .../framework/qual/MonotonicQualifier.java | 2 +- .../qual/NoDefaultQualifierForUse.java | 4 +- .../framework/qual/NoQualifierParameter.java | 12 +- .../ParametricTypeVariableUseQualifier.java | 22 +- .../framework/qual/PolymorphicQualifier.java | 22 +- .../qual/PostconditionAnnotation.java | 12 +- .../qual/PreconditionAnnotation.java | 4 +- .../framework/qual/QualifierArgument.java | 12 +- .../framework/qual/QualifierForLiterals.java | 32 +- .../framework/qual/RelevantJavaTypes.java | 26 +- .../framework/qual/RequiresQualifier.java | 54 +- .../framework/qual/StubFiles.java | 10 +- .../framework/qual/SubtypeOf.java | 4 +- .../framework/qual/TargetLocations.java | 12 +- .../framework/qual/TypeKind.java | 84 +- .../framework/qual/TypeUseLocation.java | 270 +- .../framework/qual/Unused.java | 10 +- .../framework/qual/UpperBoundFor.java | 28 +- checker-util/build.gradle | 42 +- .../checker/formatter/util/FormatUtil.java | 556 +- .../i18nformatter/util/I18nFormatUtil.java | 723 +- .../checker/nullness/util/NullnessUtil.java | 541 +- .../checker/nullness/util/Opt.java | 201 +- .../checker/optional/util/OptionalUtil.java | 95 +- .../checker/regex/util/RegexUtil.java | 811 +- .../signedness/util/SignednessUtil.java | 1033 +- .../signedness/util/SignednessUtilExtra.java | 101 +- .../checker/units/util/UnitsTools.java | 250 +- .../optional/util/OptionalUtilTest.java | 23 +- .../checker/regex/util/RegexUtilTest.java | 511 +- checker/build.gradle | 1822 +-- checker/jtreg/SymbolNotFoundErrors.java | 2 +- .../jtreg/index/UnneededSuppressionsTest.java | 64 +- checker/jtreg/index/valuestub/Test.java | 6 +- checker/jtreg/index/valuestub/UseTest.java | 6 +- checker/jtreg/issue1133/ClassA.java | 6 +- checker/jtreg/issue1133/ClassB.java | 6 +- .../advancedcrash/CrashyInterface.java | 2 +- .../issue469/advancedcrash/LetItCrash.java | 18 +- .../issue469/advancedcrash/SomeInterface.java | 2 +- .../issue469/simplecrash/CrashyInterface.java | 2 +- .../issue469/simplecrash/LetItCrash.java | 10 +- .../issue469/simplecrash/SomeRandomClass.java | 6 +- .../multiplecheckers/NullnessInterning.java | 8 +- checker/jtreg/multipleexecutions/Main.java | 72 +- checker/jtreg/multipleexecutions/Test.java | 8 +- .../nullness/AssignmentPerformanceTest.java | 904 +- .../jtreg/nullness/DefaultNonPublicClass.java | 14 +- checker/jtreg/nullness/Issue1438.java | 4012 +++--- checker/jtreg/nullness/Issue1438b.java | 4010 +++--- checker/jtreg/nullness/Issue1438c.java | 4010 +++--- checker/jtreg/nullness/Issue1809.java | 32 +- checker/jtreg/nullness/Issue2853.java | 150 +- checker/jtreg/nullness/Issue347.java | 30 +- checker/jtreg/nullness/Issue373.java | 8 +- checker/jtreg/nullness/Issue4948.java | 2401 ++-- checker/jtreg/nullness/Issue6373.java | 208 +- checker/jtreg/nullness/PersistUtil.java | 158 +- checker/jtreg/nullness/UncheckedWarning.java | 20 +- .../UnneededSuppressionsClassPrefixed.java | 8 +- ...uppressionsClassPrefixedRequirePrefix.java | 8 +- .../UnneededSuppressionsClassUnprefixed.java | 8 +- ...pressionsClassUnprefixedRequirePrefix.java | 8 +- .../nullness/UnneededSuppressionsTest.java | 40 +- ...UnneededSuppressionsTestRequirePrefix.java | 43 +- .../nullness/annotationsOnExtends/Other.java | 8 +- .../DefaultConstructor.java | 10 +- .../NonDefaultConstructor.java | 40 +- .../nullness/defaultsPersist/Classes.java | 448 +- .../defaultsPersist/Constructors.java | 372 +- .../nullness/defaultsPersist/Driver.java | 344 +- .../nullness/defaultsPersist/Fields.java | 438 +- .../nullness/defaultsPersist/Methods.java | 374 +- .../defaultsPersist/ReferenceInfoUtil.java | 450 +- checker/jtreg/nullness/eisop673/Lib.java | 2 +- checker/jtreg/nullness/eisop673/User.java | 8 +- .../inheritDeclAnnoPersist/AbstractClass.java | 8 +- .../inheritDeclAnnoPersist/Driver.java | 156 +- .../inheritDeclAnnoPersist/Extends.java | 54 +- .../inheritDeclAnnoPersist/Implements.java | 32 +- .../ReferenceInfoUtil.java | 197 +- .../inheritDeclAnnoPersist/Super.java | 28 +- .../nullness/issue12/BinaryDefaultTest.java | 10 +- .../issue12/BinaryDefaultTestBinary.java | 6 +- .../issue12/BinaryDefaultTestWithStub.java | 10 +- .../jtreg/nullness/issue141/CharStreams.java | 4 +- checker/jtreg/nullness/issue141/Files.java | 16 +- checker/jtreg/nullness/issue1582/foo/Foo.java | 24 +- .../JavaExpressionParseError.java | 8 +- .../jtreg/nullness/issue1929/Issue1929.java | 29 +- .../jtreg/nullness/issue1958/NPE2Test.java | 28 +- .../nullness/issue1958/SupplierDefs.java | 55 +- checker/jtreg/nullness/issue2173/View.java | 6 +- .../issue2318/RequireCheckerPrefix.java | 32 +- .../nullness/issue257/ClientBuilder.java | 16 +- checker/jtreg/nullness/issue257/Module.java | 14 +- checker/jtreg/nullness/issue257/Small.java | 12 +- checker/jtreg/nullness/issue3700/Client.java | 2 +- .../nullness/issue3700/TimeUnitRange.java | 12 +- checker/jtreg/nullness/issue380/DA.java | 4 +- checker/jtreg/nullness/issue380/DB.java | 2 +- checker/jtreg/nullness/issue380/Decl.java | 2 +- checker/jtreg/nullness/issue6053/Main.java | 2 +- checker/jtreg/nullness/issue6374/Lib.java | 16 +- checker/jtreg/nullness/issue6374/User.java | 20 +- checker/jtreg/nullness/issue767/Class1.java | 36 +- checker/jtreg/nullness/issue767/Class2.java | 28 +- checker/jtreg/nullness/issue790/Class1.java | 6 +- .../nullness/issue820/AnonymousClass.java | 20 +- checker/jtreg/nullness/issue820/Class1.java | 36 +- .../jtreg/nullness/issue820/Class1Min.java | 4 +- checker/jtreg/nullness/issue820/Class2.java | 20 +- .../jtreg/nullness/issue820/Class2Min.java | 10 +- checker/jtreg/nullness/issue820/Error.java | 2 +- checker/jtreg/nullness/issue824/Class2.java | 20 +- .../jtreg/nullness/issue824/NoStubFirst.java | 8 +- .../jtreg/nullness/issue824/NoStubSecond.java | 17 +- checker/jtreg/nullness/issue824/Second.java | 14 +- .../jtreg/nullness/issue824lib/Class1.java | 14 +- checker/jtreg/nullness/issue824lib/First.java | 6 +- .../nullness/preciseErrorMsg/Class1.java | 4 +- .../preciseErrorMsg/LocateArtificialTree.java | 11 +- .../nullness/stub-arg/EisopIssue608.java | 6 +- .../jtreg/nullness/stub-typeparams/Box.java | 10 +- .../nullness/stub-typeparams/NullableBox.java | 12 +- .../stub-typeparams/StubTypeParamsClass.java | 2 +- .../StubTypeParamsClassNbl.java | 2 +- .../stub-typeparams/StubTypeParamsMeth.java | 6 +- .../StubTypeParamsMethNbl.java | 6 +- .../stub-typeparams/StubWildcards.java | 6 +- .../stub-typeparams/StubWildcardsNbl.java | 6 +- .../stub-typeparams/StubWildcardsNon.java | 6 +- .../jtreg/nullness/stub-warnings/Binary.java | 27 +- checker/jtreg/rawtypes/RawTypeFail.java | 6 +- checker/jtreg/showPrefix/ShowPrefixTest.java | 6 +- checker/jtreg/sortwarnings/ErrorOrders.java | 84 +- .../jtreg/sortwarnings/OrderOfCheckers.java | 10 +- checker/jtreg/stubs/annotatedFor/UseTest.java | 13 +- checker/jtreg/stubs/annotatedForLib/Test.java | 10 +- .../default-on-class/List.java | 2 +- .../default-on-class/MutableList.java | 4 +- .../default-on-class/Test.java | 16 +- .../stubs/defaultqualinstub/pck/Defaults.java | 12 +- .../stubs/fakeoverrides/DefineClasses.java | 14 +- checker/jtreg/stubs/fakeoverrides/Use.java | 8 +- checker/jtreg/stubs/general/Driver.java | 8 +- checker/jtreg/stubs/general/Driver2.java | 2 +- .../stubs/issue1356/mypackage/MyClass.java | 14 +- .../stubs/issue1356/mypackage/UseMyClass.java | 6 +- checker/jtreg/stubs/issue1456/Main.java | 27 +- checker/jtreg/stubs/issue1456lib/Lib.java | 12 +- .../jtreg/stubs/issue1542/ExampleAnno.java | 154 +- .../jtreg/stubs/issue1542/NeedsIntRange.java | 12 +- checker/jtreg/stubs/issue1542/Stub.java | 52 +- .../jtreg/stubs/issue1542/UsesIntRange.java | 8 +- .../jtreg/stubs/issue1565/MyListGeneric.java | 6 +- .../jtreg/stubs/issue2147/EnumStubTest.java | 10 +- checker/jtreg/stubs/issue2147/SampleEnum.java | 4 +- checker/jtreg/stubs/sample/Sample.java | 8 +- .../jtreg/stubs/stub-over-jdk/Issue321.java | 5 +- checker/jtreg/stubs/wildcards/Wildcards.java | 6 +- checker/jtreg/tainting/NewClass.java | 14 +- checker/jtreg/tainting/classes/Issue919.java | 9 +- checker/jtreg/tainting/classes/Issue919B.java | 22 +- .../unboundedWildcards/issue1275/Crash.java | 8 +- .../unboundedWildcards/issue1275/Lib.java | 28 +- .../unboundedWildcards/issue1420/Crash8.java | 8 +- .../issue1420/Crash8Lib.java | 22 +- .../jtreg/unboundedWildcards/issue1427/B.java | 32 +- .../jtreg/unboundedWildcards/issue1427/T.java | 6 +- .../jtreg/unboundedWildcards/issue1428/B.java | 2 +- .../jtreg/unboundedWildcards/issue1428/T.java | 6 +- .../calledmethods/CalledMethodsAnalysis.java | 83 +- .../CalledMethodsAnnotatedTypeFactory.java | 970 +- .../calledmethods/CalledMethodsChecker.java | 168 +- .../calledmethods/CalledMethodsTransfer.java | 590 +- .../calledmethods/CalledMethodsVisitor.java | 244 +- ...nsuresCalledMethodOnExceptionContract.java | 87 +- .../builder/AutoValueSupport.java | 759 +- .../builder/BuilderFrameworkSupport.java | 103 +- .../builder/BuilderFrameworkSupportUtils.java | 50 +- .../calledmethods/builder/LombokSupport.java | 361 +- .../CompilerMessagesAnnotatedTypeFactory.java | 26 +- .../fenum/FenumAnnotatedTypeFactory.java | 262 +- .../checker/fenum/FenumChecker.java | 16 +- .../checker/fenum/FenumVisitor.java | 146 +- .../FormatterAnnotatedTypeFactory.java | 626 +- .../checker/formatter/FormatterTransfer.java | 57 +- .../checker/formatter/FormatterTreeUtil.java | 841 +- .../checker/formatter/FormatterVisitor.java | 494 +- .../checker/guieffect/Effect.java | 215 +- .../guieffect/GuiEffectTypeFactory.java | 1115 +- .../checker/guieffect/GuiEffectVisitor.java | 1073 +- .../i18n/I18nAnnotatedTypeFactory.java | 85 +- .../checker/i18n/I18nChecker.java | 19 +- .../LocalizableKeyAnnotatedTypeFactory.java | 48 +- .../checker/i18n/LocalizableKeyChecker.java | 4 +- .../I18nFormatterAnnotatedTypeFactory.java | 609 +- .../i18nformatter/I18nFormatterChecker.java | 3 +- .../i18nformatter/I18nFormatterTransfer.java | 119 +- .../i18nformatter/I18nFormatterTreeUtil.java | 1002 +- .../i18nformatter/I18nFormatterVisitor.java | 279 +- ...seAnnotatedTypeFactoryForIndexChecker.java | 110 +- .../checker/index/IndexAbstractTransfer.java | 149 +- .../checker/index/IndexChecker.java | 4 +- .../checker/index/IndexMethodIdentifier.java | 321 +- .../checker/index/IndexRefinementInfo.java | 141 +- .../checker/index/IndexUtil.java | 56 +- .../index/OffsetDependentTypesHelper.java | 45 +- .../checker/index/Subsequence.java | 280 +- .../LessThanAnnotatedTypeFactory.java | 588 +- .../index/inequality/LessThanChecker.java | 35 +- .../index/inequality/LessThanTransfer.java | 286 +- .../index/inequality/LessThanVisitor.java | 220 +- .../LowerBoundAnnotatedTypeFactory.java | 713 +- .../index/lowerbound/LowerBoundChecker.java | 83 +- .../index/lowerbound/LowerBoundTransfer.java | 1539 ++- .../index/lowerbound/LowerBoundVisitor.java | 127 +- .../samelen/SameLenAnnotatedTypeFactory.java | 645 +- .../checker/index/samelen/SameLenChecker.java | 4 +- .../index/samelen/SameLenTransfer.java | 504 +- .../checker/index/samelen/SameLenVisitor.java | 89 +- .../SearchIndexAnnotatedTypeFactory.java | 403 +- .../index/searchindex/SearchIndexChecker.java | 35 +- .../searchindex/SearchIndexTransfer.java | 147 +- .../SubstringIndexAnnotatedTypeFactory.java | 291 +- .../substringindex/SubstringIndexChecker.java | 4 +- .../index/upperbound/OffsetEquation.java | 876 +- .../checker/index/upperbound/UBQualifier.java | 2624 ++-- .../UpperBoundAnnotatedTypeFactory.java | 1657 +-- .../index/upperbound/UpperBoundChecker.java | 150 +- .../index/upperbound/UpperBoundTransfer.java | 1475 +- .../index/upperbound/UpperBoundVisitor.java | 932 +- .../InitializationAnalysis.java | 69 +- .../InitializationAnnotatedTypeFactory.java | 451 +- .../initialization/InitializationChecker.java | 158 +- ...zationFieldAccessAnnotatedTypeFactory.java | 101 +- .../InitializationFieldAccessSubchecker.java | 52 +- ...nitializationFieldAccessTreeAnnotator.java | 242 +- .../InitializationFieldAccessVisitor.java | 45 +- ...tializationParentAnnotatedTypeFactory.java | 1719 +-- .../initialization/InitializationStore.java | 369 +- .../InitializationTransfer.java | 233 +- .../initialization/InitializationVisitor.java | 675 +- .../InterningAnnotatedTypeFactory.java | 297 +- .../checker/interning/InterningChecker.java | 3 +- .../checker/interning/InterningVisitor.java | 1719 +-- .../checker/lock/LockAnalysis.java | 61 +- .../lock/LockAnnotatedTypeFactory.java | 1280 +- .../checker/lock/LockStore.java | 426 +- .../checker/lock/LockTransfer.java | 224 +- .../checker/lock/LockTreeAnnotator.java | 63 +- .../checker/lock/LockVisitor.java | 2284 +-- .../CreatesMustCallForElementSupplier.java | 27 +- .../CreatesMustCallForToJavaExpression.java | 347 +- .../MustCallAnnotatedTypeFactory.java | 1020 +- .../checker/mustcall/MustCallChecker.java | 44 +- .../MustCallNoCreatesMustCallForChecker.java | 14 +- .../checker/mustcall/MustCallTransfer.java | 537 +- .../mustcall/MustCallTypeAnnotator.java | 26 +- .../checker/mustcall/MustCallVisitor.java | 553 +- .../nullness/CollectionToArrayHeuristics.java | 380 +- .../checker/nullness/KeyForAnalysis.java | 59 +- .../nullness/KeyForAnnotatedTypeFactory.java | 404 +- .../KeyForPropagationTreeAnnotator.java | 104 +- .../checker/nullness/KeyForPropagator.java | 341 +- .../checker/nullness/KeyForStore.java | 14 +- .../checker/nullness/KeyForSubchecker.java | 3 +- .../checker/nullness/KeyForTransfer.java | 160 +- .../checker/nullness/KeyForValue.java | 191 +- .../checker/nullness/NullnessChecker.java | 109 +- .../nullness/NullnessNoInitAnalysis.java | 56 +- .../NullnessNoInitAnnotatedTypeFactory.java | 1869 +-- .../NullnessNoInitAnnotatedTypeFormatter.java | 79 +- .../checker/nullness/NullnessNoInitStore.java | 306 +- .../nullness/NullnessNoInitSubchecker.java | 80 +- .../nullness/NullnessNoInitTransfer.java | 849 +- .../checker/nullness/NullnessNoInitValue.java | 149 +- .../nullness/NullnessNoInitVisitor.java | 1820 +-- .../nullness/SystemGetPropertyHandler.java | 199 +- .../OptionalAnnotatedTypeFactory.java | 206 +- .../checker/optional/OptionalChecker.java | 7 +- .../checker/optional/OptionalTransfer.java | 136 +- .../checker/optional/OptionalVisitor.java | 1164 +- .../PropertyKeyAnnotatedTypeFactory.java | 375 +- .../checker/propkey/PropertyKeyChecker.java | 5 +- .../regex/RegexAnnotatedTypeFactory.java | 875 +- .../checker/regex/RegexChecker.java | 21 +- .../checker/regex/RegexTransfer.java | 376 +- .../checker/regex/RegexVisitor.java | 170 +- .../MustCallConsistencyAnalyzer.java | 4692 +++---- .../resourceleak/MustCallInference.java | 1749 +-- .../resourceleak/ResourceLeakAnalysis.java | 43 +- .../ResourceLeakAnnotatedTypeFactory.java | 937 +- .../resourceleak/ResourceLeakChecker.java | 498 +- .../resourceleak/ResourceLeakTransfer.java | 269 +- .../resourceleak/ResourceLeakVisitor.java | 1180 +- .../checker/resourceleak/SetOfTypes.java | 136 +- .../SignatureAnnotatedTypeFactory.java | 474 +- .../checker/signature/SignatureChecker.java | 11 +- .../checker/signature/SignatureTransfer.java | 81 +- .../SignednessAnnotatedTypeFactory.java | 719 +- .../checker/signedness/SignednessChecker.java | 35 +- .../checker/signedness/SignednessShifts.java | 518 +- .../checker/signedness/SignednessVisitor.java | 672 +- .../TaintingAnnotatedTypeFactory.java | 41 +- .../checker/tainting/TaintingVisitor.java | 33 +- .../units/UnitsAnnotatedTypeFactory.java | 1173 +- .../units/UnitsAnnotatedTypeFormatter.java | 130 +- .../units/UnitsAnnotationClassLoader.java | 76 +- .../checker/units/UnitsChecker.java | 16 +- .../checker/units/UnitsRelations.java | 55 +- .../checker/units/UnitsRelationsDefault.java | 437 +- .../checker/units/UnitsRelationsTools.java | 533 +- .../checker/units/UnitsVisitor.java | 42 +- .../test/junit/AnnotatedForNullnessTest.java | 51 +- .../junit/CalledMethodsAutoValueTest.java | 43 +- ...lledMethodsDisableReturnsReceiverTest.java | 31 +- .../CalledMethodsDisableframeworksTest.java | 51 +- .../test/junit/CalledMethodsLombokTest.java | 29 +- .../junit/CalledMethodsNoDelombokTest.java | 88 +- .../checker/test/junit/CalledMethodsTest.java | 35 +- .../CalledMethodsUseValueCheckerTest.java | 29 +- .../test/junit/CompilerMessagesTest.java | 37 +- .../checker/test/junit/CustomAliasTest.java | 43 +- .../checker/test/junit/DisbarUseTest.java | 47 +- .../checker/test/junit/FenumSwingTest.java | 43 +- .../checker/test/junit/FenumTest.java | 29 +- .../junit/FormatterLubGlbCheckerTest.java | 29 +- .../checker/test/junit/FormatterTest.java | 32 +- .../junit/FormatterUncheckedDefaultsTest.java | 37 +- .../checker/test/junit/FormatterUnitTest.java | 72 +- .../checker/test/junit/GuiEffectTest.java | 34 +- .../junit/I18nFormatterLubGlbCheckerTest.java | 29 +- .../checker/test/junit/I18nFormatterTest.java | 35 +- .../I18nFormatterUncheckedDefaultsTest.java | 37 +- .../test/junit/I18nFormatterUnitTest.java | 259 +- .../checker/test/junit/I18nTest.java | 39 +- .../test/junit/I18nUncheckedDefaultsTest.java | 37 +- .../junit/IndexInitializedFieldsTest.java | 47 +- .../checker/test/junit/IndexTest.java | 39 +- .../checker/test/junit/InterningTest.java | 32 +- ...InterningWarnRedundantAnnotationsTest.java | 37 +- .../test/junit/LockSafeDefaultsTest.java | 37 +- .../checker/test/junit/LockTest.java | 47 +- .../MustCallNoLightweightOwnershipTest.java | 31 +- .../checker/test/junit/MustCallTest.java | 29 +- .../junit/NestedAggregateCheckerTest.java | 29 +- .../test/junit/NullnessAssertsTest.java | 50 +- ...llnessAssumeAssertionsAreDisabledTest.java | 39 +- .../junit/NullnessAssumeInitializedTest.java | 52 +- .../test/junit/NullnessAssumeKeyForTest.java | 50 +- .../NullnessCheckCastElementTypeTest.java | 37 +- .../test/junit/NullnessConcurrentTest.java | 37 +- .../test/junit/NullnessEnclosingExprTest.java | 29 +- .../junit/NullnessGenericWildcardTest.java | 93 +- .../junit/NullnessInvariantArraysTest.java | 37 +- .../test/junit/NullnessJavacErrorsTest.java | 36 +- .../test/junit/NullnessJavadocTest.java | 61 +- .../test/junit/NullnessNoDelombokTest.java | 87 +- .../test/junit/NullnessNullMarkedTest.java | 51 +- .../NullnessPermitClearPropertyTest.java | 33 +- .../test/junit/NullnessRecordsTest.java | 49 +- .../test/junit/NullnessReflectionTest.java | 37 +- .../NullnessSafeDefaultsBytecodeTest.java | 37 +- .../NullnessSafeDefaultsSourceCodeTest.java | 99 +- .../test/junit/NullnessSkipDefsTest.java | 37 +- .../test/junit/NullnessSkipDirsTest.java | 27 +- .../test/junit/NullnessSkipUsesTest.java | 37 +- .../test/junit/NullnessStubfileTest.java | 43 +- .../checker/test/junit/NullnessTempTest.java | 38 +- .../checker/test/junit/NullnessTest.java | 46 +- .../NullnessWarnRedundantAnnotationsTest.java | 37 +- .../test/junit/OptionalPureGettersTest.java | 37 +- .../checker/test/junit/OptionalTest.java | 37 +- .../checker/test/junit/ParseAllJdkTest.java | 29 +- .../checker/test/junit/RegexTest.java | 29 +- ...sourceLeakCustomIgnoredExceptionsTest.java | 33 +- ...esourceLeakExtraIgnoredExceptionsTest.java | 33 +- .../ResourceLeakNoCreatesMustCallForTest.java | 33 +- ...esourceLeakNoLightweightOwnershipTest.java | 33 +- .../ResourceLeakNoResourceAliasesTest.java | 33 +- .../ResourceLeakPermitInitializationLeak.java | 33 +- .../junit/ResourceLeakPermitStaticOwning.java | 33 +- .../checker/test/junit/ResourceLeakTest.java | 31 +- .../checker/test/junit/SignatureTest.java | 32 +- .../SignednessInitializedFieldsTest.java | 43 +- .../checker/test/junit/SignednessTest.java | 39 +- .../SignednessUncheckedDefaultsTest.java | 37 +- .../test/junit/StubparserNullnessTest.java | 37 +- .../test/junit/StubparserRecordTest.java | 47 +- .../test/junit/StubparserTaintingTest.java | 39 +- .../checker/test/junit/TaintingTest.java | 29 +- .../checker/test/junit/UnitsTest.java | 29 +- .../test/junit/ValueIndexInteractionTest.java | 39 +- .../AinferIndexAjavaGenerationTest.java | 37 +- .../AinferIndexAjavaValidationTest.java | 39 +- .../AinferNullnessAjavaGenerationTest.java | 37 +- .../AinferNullnessAjavaValidationTest.java | 39 +- .../AinferNullnessJaifsGenerationTest.java | 37 +- .../AinferNullnessJaifsValidationTest.java | 43 +- ...AinferResourceLeakAjavaGenerationTest.java | 37 +- ...AinferResourceLeakAjavaValidationTest.java | 39 +- .../AinferTestCheckerAjavaGenerationTest.java | 37 +- .../AinferTestCheckerAjavaValidationTest.java | 41 +- .../AinferTestCheckerJaifsGenerationTest.java | 45 +- .../AinferTestCheckerJaifsValidationTest.java | 39 +- .../AinferTestCheckerStubsGenerationTest.java | 37 +- .../AinferTestCheckerStubsValidationTest.java | 45 +- .../testchecker/NestedAggregateChecker.java | 25 +- .../AinferTestAnnotatedTypeFactory.java | 417 +- .../testchecker/ainfer/AinferTestChecker.java | 23 +- .../testchecker/ainfer/AinferTestVisitor.java | 46 +- .../testchecker/ainfer/qual/AinferBottom.java | 5 +- .../ainfer/qual/AinferDefaultType.java | 5 +- .../ainfer/qual/AinferImplicitAnno.java | 5 +- .../testchecker/ainfer/qual/AinferParent.java | 3 +- .../ainfer/qual/AinferSibling1.java | 3 +- .../ainfer/qual/AinferSibling2.java | 3 +- .../ainfer/qual/AinferSiblingWithFields.java | 7 +- .../testchecker/ainfer/qual/AinferTop.java | 3 +- .../disbaruse/DisbarUseTypeFactory.java | 35 +- .../disbaruse/DisbarUseVisitor.java | 92 +- .../disbaruse/qual/DisbarUseBottom.java | 5 +- .../disbaruse/qual/DisbarUseTop.java | 5 +- .../lubglb/FormatterLubGlbChecker.java | 993 +- .../lubglb/I18nFormatterLubGlbChecker.java | 855 +- .../android/support/annotation/NonNull.java | 12 +- .../android/support/annotation/Nullable.java | 12 +- .../java/javax/annotation/Nonnull.java | 3 +- .../annotation/concurrent/GuardedBy.java | 19 +- .../java/javax/annotation/meta/When.java | 16 +- .../testannotations/java/lombok/NonNull.java | 10 +- .../java/net/jcip/annotations/GuardedBy.java | 19 +- .../org/jetbrains/annotations/NotNull.java | 12 +- .../org/jetbrains/annotations/Nullable.java | 12 +- checker/tests/aggregate/NullnessAndRegex.java | 22 +- .../non-annotated/Dataset6Crash.java | 84 +- .../non-annotated/Dataset9Crash.java | 148 +- .../non-annotated/DelocalizeAtCallsites.java | 98 +- ...DependentTypesViewpointAdaptationTest.java | 106 +- .../non-annotated/InternCrash.java | 20 +- .../non-annotated/LongMathTest.java | 6 +- .../non-annotated/RequireJavadocCrash.java | 21 +- .../non-annotated/TypeVarAssignment.java | 8 +- .../ainfer-nullness/non-annotated/Bug.java | 110 +- .../MonotonicNonNullInferenceTest.java | 108 +- .../non-annotated/NullTypeVarTest.java | 32 +- .../non-annotated/TwoCtorGenericAbstract.java | 16 +- .../non-annotated/TypeVarPlumeUtil.java | 22 +- .../non-annotated/TypeVarReturnAnnotated.java | 6 +- .../non-annotated/AddNotOwning.java | 156 +- .../ClassWithTwoOwningFieldsTest.java | 52 +- .../non-annotated/CrashForTempVar.java | 28 +- .../non-annotated/DatadirCleanupManager.java | 16 +- .../non-annotated/ECMInference.java | 98 +- .../EnsuresCalledMethodsTest.java | 40 +- .../EnsuresCalledMethodsVarArgsTest.java | 88 +- .../non-annotated/GenericClassFieldCrash.java | 14 +- .../non-annotated/HadoopCrash.java | 12 +- .../MustCallAliasOnReceiver.java | 51 +- .../MustCallAliasOnRegularExits.java | 75 +- .../non-annotated/MustCallAliasParams.java | 260 +- .../OwnershipTransferOnConstructor.java | 37 +- .../non-annotated/OwningField.java | 28 +- .../OwningFieldIndirectCall.java | 48 +- .../non-annotated/OwningParams.java | 108 +- .../non-annotated/PurgeTxnLog.java | 24 +- .../ReplaceMustCallAliasAnnotation.java | 58 +- .../non-annotated/UnwantedECMInference.java | 24 +- .../AddAnnosToFormalParameterTest.java | 34 +- .../non-annotated/AinferSibling1.java | 4 +- .../AnnotationWithFieldTest.java | 90 +- .../non-annotated/Anonymous.java | 42 +- .../non-annotated/AnonymousAndInnerClass.java | 58 +- .../non-annotated/AnonymousClassField.java | 2 +- .../AnonymousClassWithField.java | 36 +- .../non-annotated/AnonymousOverride.java | 24 +- .../non-annotated/ArrayAndTypevar.java | 16 +- .../non-annotated/CompoundTypeTest.java | 30 +- .../non-annotated/CompoundTypeTest2.java | 4 +- .../ConflictingAnnotationsTest.java | 36 +- .../non-annotated/ConstructorTest.java | 12 +- .../non-annotated/CrazyEnum.java | 26 +- .../non-annotated/DefaultsTest.java | 34 +- .../non-annotated/DeviceTypeTest.java | 14 +- .../non-annotated/DoubleGeneric.java | 2 +- .../EnsuresQualifierFieldDecl.java | 6 +- .../EnsuresQualifierParamsTest.java | 340 +- .../non-annotated/EnsuresQualifierTest.java | 116 +- .../non-annotated/EnumConstants.java | 18 +- .../non-annotated/EnumMapCrash.java | 28 +- .../non-annotated/EnumTest.java | 42 +- .../non-annotated/EnumWithInnerClass.java | 20 +- .../ExistingPurityAnnotations.java | 36 +- .../non-annotated/ExpectedErrors.java | 413 +- .../FieldInOtherCompilationUnit.java | 19 +- .../non-annotated/FromReceiver.java | 84 +- .../non-annotated/IShouldBeSibling1.java | 8 +- .../IgnoreMetaAnnotationTest1.java | 24 +- .../non-annotated/ImplicitAnnosTest.java | 8 +- .../non-annotated/InheritanceTest.java | 32 +- .../InnerClassFieldDeclAnno.java | 22 +- .../non-annotated/InnerTypeTest.java | 24 +- .../non-annotated/InnerTypeTest2.java | 14 +- .../non-annotated/InnerTypeTest3.java | 10 +- .../non-annotated/InterfaceTest.java | 18 +- .../non-annotated/LUBAssignmentTest.java | 86 +- .../non-annotated/LambdaParamCrash.java | 24 +- .../non-annotated/LambdaReturn.java | 20 +- .../non-annotated/LocalClassTest.java | 8 +- .../MethodDefinedInSupertype.java | 24 +- .../MethodOverrideInSubtype.java | 22 +- .../MethodOverrideInSubtype2.java | 14 +- .../MethodParameterInferenceTest.java | 12 +- .../non-annotated/MethodReturnTest.java | 70 +- .../non-annotated/MultiDimensionalArrays.java | 433 +- .../MultidimensionalAnnotatedArray.java | 8 +- .../NamedInnerClassInAnonymous.java | 24 +- .../non-annotated/OptionGroup.java | 4 +- .../non-annotated/OtherAnnotations.java | 52 +- .../OuterClassWithTypeParam.java | 6 +- .../non-annotated/OverloadedMethodsTest.java | 20 +- .../non-annotated/OverriddenMethodsTest.java | 91 +- ...hodsTestChildInAnotherCompilationUnit.java | 8 +- .../OverrideIncompatiblePurity.java | 56 +- .../non-annotated/ParameterInferenceTest.java | 28 +- .../non-annotated/Planet.java | 94 +- .../non-annotated/PublicFieldTest.java | 86 +- .../non-annotated/Purity.java | 28 +- .../non-annotated/RequiresQualifierTest.java | 90 +- .../StringConcatenationTest.java | 44 +- .../non-annotated/Tempvars.java | 8 +- .../TreatAsSibling1InferenceTest.java | 8 +- .../non-annotated/TreatAsSibling1Test.java | 6 +- .../non-annotated/TwoMethodsSameName.java | 28 +- .../non-annotated/TypeVariablesTest.java | 43 +- .../non-annotated/TypeVariablesTest2.java | 8 +- .../non-annotated/TypeVariablesTest3.java | 28 +- .../non-annotated/UsesAnnotationAsClass.java | 12 +- .../non-annotated/UsesAnonymous.java | 50 +- .../non-annotated/ValueCheck.java | 26 +- .../non-annotated/VarargsTest.java | 36 +- .../non-annotated/WildcardReturn.java | 16 +- .../tests/calledmethods-autovalue/Animal.java | 114 +- .../calledmethods-autovalue/AnimalNoSet.java | 85 +- .../BuilderGetter.java | 43 +- .../CallWithinBuilder.java | 42 +- .../FooParcelable.java | 41 +- .../calledmethods-autovalue/GetAndIs.java | 61 +- .../calledmethods-autovalue/GetAnimal.java | 87 +- .../GuavaImmutable.java | 33 +- .../GuavaImmutablePropBuilder.java | 27 +- .../calledmethods-autovalue/Inheritance.java | 63 +- .../calledmethods-autovalue/IsPreserved.java | 47 +- .../calledmethods-autovalue/NonAVBuilder.java | 17 +- .../calledmethods-autovalue/NonBuildName.java | 39 +- .../tests/calledmethods-autovalue/Parcel.java | 8 +- .../calledmethods-autovalue/Parcelable.java | 12 +- .../SetInsideBuild.java | 51 +- .../SetInsideBuildWithCM.java | 53 +- .../calledmethods-autovalue/Validation.java | 55 +- .../AnimalSimple.java | 66 +- .../LombokBuilderExample.java | 122 +- .../Parcel.java | 8 +- .../Parcelable.java | 12 +- .../SimpleFluentInference.java | 112 +- .../calledmethods-lombok/BuilderTest.java | 38 +- .../CheckerFrameworkBuilder.java | 348 +- .../calledmethods-lombok/DefaultedName.java | 20 +- .../LombokBuilderExample.java | 12 +- .../LombokBuilderSubclassExample.java | 43 +- .../LombokDefaultAssignments.java | 17 +- ...LombokNoSingularButClearMethodExample.java | 29 +- .../LombokSingularExample.java | 31 +- .../LombokToBuilderExample.java | 16 +- .../calledmethods-lombok/OldInherited.java | 70 +- .../UnsoundnessTest.java | 26 +- .../calledmethods-usevaluechecker/Cve.java | 168 +- .../calledmethods-usevaluechecker/Cve2.java | 49 +- .../GenerateDataKeyRequestExamples.java | 205 +- .../MorePreciseFilters.java | 103 +- .../OnlyOwnersFalsePositive.java | 11 +- .../RequestCreatedInCall.java | 14 +- .../SimpleFalsePositive.java | 23 +- .../SpecialNames.java | 78 +- .../WithOwnersFilter.java | 41 +- checker/tests/calledmethods/CmCfgCrash.java | 6 +- checker/tests/calledmethods/CmPredicate.java | 1116 +- .../EnsuresCalledMethodsIfRepeatable.java | 83 +- .../EnsuresCalledMethodsIfSubclass.java | 37 +- .../EnsuresCalledMethodsIfTest.java | 93 +- ...resCalledMethodsOnExceptionRepeatable.java | 67 +- ...suresCalledMethodsOnExceptionSubclass.java | 35 +- .../EnsuresCalledMethodsOnExceptionTest.java | 177 +- .../EnsuresCalledMethodsRepeatable.java | 87 +- .../EnsuresCalledMethodsSubclass.java | 23 +- .../EnsuresCalledMethodsThisLub.java | 64 +- .../EnsuresCalledMethodsVarArgsSimple.java | 51 +- .../tests/calledmethods/ExceptionalPath.java | 17 +- .../tests/calledmethods/ExceptionalPath2.java | 57 +- checker/tests/calledmethods/FinallyClose.java | 93 +- checker/tests/calledmethods/Generics.java | 81 +- checker/tests/calledmethods/Issue20.java | 44 +- checker/tests/calledmethods/Issue5402.java | 76 +- checker/tests/calledmethods/Not.java | 112 +- checker/tests/calledmethods/Parens.java | 6 +- .../tests/calledmethods/Postconditions.java | 244 +- .../RequiresCalledMethodsRepeatable.java | 46 +- .../RequiresCalledMethodsSubclass.java | 29 +- .../RequiresCalledMethodsTest.java | 24 +- .../calledmethods/SimpleFluentInference.java | 112 +- .../tests/calledmethods/SimpleInference.java | 24 +- .../calledmethods/SimpleInferenceMerge.java | 52 +- checker/tests/calledmethods/Subtyping.java | 138 +- .../calledmethods/UnparsablePredicate.java | 64 +- checker/tests/calledmethods/Xor.java | 98 +- .../command-line/issue618/TwoCheckers.java | 10 +- checker/tests/compilermsg/Basic.java | 8 +- checker/tests/compilermsg/Diamonds.java | 19 +- .../CustomAliasedAnnotations.java | 40 +- .../disbaruse-records/DisbarredClass.java | 72 +- .../disbaruse-records/DisbarredRecord.java | 28 +- .../DisbarredRecordByStubs.java | 28 +- .../DisbarredRecordByStubs2.java | 34 +- checker/tests/fenum/CastsFenum.java | 18 +- .../tests/fenum/CatchFenumUnqualified.java | 20 +- checker/tests/fenum/TestFlow.java | 48 +- checker/tests/fenum/TestInstance.java | 54 +- checker/tests/fenum/TestPrimitive.java | 58 +- checker/tests/fenum/TestStatic.java | 76 +- checker/tests/fenum/TestSwitch.java | 54 +- checker/tests/fenum/TypeVariable.java | 12 +- .../tests/fenum/UpperBoundsInByteCode.java | 32 +- checker/tests/fenumswing/FlowBreak.java | 60 +- .../fenumswing/IdentityArrayListTest.java | 51 +- checker/tests/fenumswing/PolyTest.java | 32 +- checker/tests/fenumswing/SwingTest.java | 496 +- checker/tests/fenumswing/TypeVariable.java | 12 +- .../TestUncheckedByteCode.java | 24 +- checker/tests/formatter/ConversionBasic.java | 123 +- checker/tests/formatter/ConversionNull.java | 38 +- checker/tests/formatter/ConversionNull2.java | 19 +- checker/tests/formatter/FlowFormatter.java | 131 +- checker/tests/formatter/FormatBasic.java | 54 +- checker/tests/formatter/FormatIndexing.java | 123 +- .../formatter/FormatMethodAndFormat.java | 20 +- .../formatter/FormatMethodAnnotation.java | 73 +- .../formatter/FormatMethodInvocation.java | 116 +- checker/tests/formatter/FormatNullArray.java | 14 +- .../tests/formatter/FormatNullCategory.java | 28 +- .../tests/formatter/InvalidFormatMethod.java | 30 +- checker/tests/formatter/Issue285.java | 6 +- .../formatter/ManualExampleFormatter.java | 50 +- .../formatter/TypeInferenceNonRelevant.java | 18 +- checker/tests/formatter/VarargsFormatter.java | 102 +- .../tests/guieffect/AnonInnerDefaults.java | 425 +- checker/tests/guieffect/AssignmentTests.java | 84 +- .../tests/guieffect/BadUIOverrideChild.java | 8 +- checker/tests/guieffect/FooConflict.java | 6 +- checker/tests/guieffect/GenericSubTask.java | 20 +- .../guieffect/GenericTaskSafeConsumer.java | 4 +- .../guieffect/GenericTaskUIConsumer.java | 4 +- checker/tests/guieffect/IAsyncUITask.java | 2 +- checker/tests/guieffect/IFooSafe.java | 4 +- checker/tests/guieffect/IFooUI.java | 4 +- checker/tests/guieffect/IGenericTask.java | 4 +- checker/tests/guieffect/Java8Lambdas.java | 288 +- checker/tests/guieffect/MouseTest.java | 13 +- .../tests/guieffect/NoAnnotationsTest.java | 2 +- checker/tests/guieffect/SafeParent.java | 4 +- checker/tests/guieffect/Specialization.java | 90 +- checker/tests/guieffect/TestProgram.java | 137 +- checker/tests/guieffect/ThrowCatchTest.java | 173 +- .../guieffect/TransitiveInheritance.java | 106 +- checker/tests/guieffect/UIChild.java | 52 +- checker/tests/guieffect/UIElement.java | 10 +- checker/tests/guieffect/UIParent.java | 16 +- checker/tests/guieffect/WeakeningChild.java | 12 +- .../guieffect/packagetests/SafeByDecl.java | 2 +- .../packagetests/UIByPackageDecl.java | 6 +- .../TestUncheckedByteCode.java | 24 +- .../ConversionCategoryTest.java | 96 +- checker/tests/i18n-formatter/HasFormat.java | 95 +- checker/tests/i18n-formatter/I18nFormat.java | 70 +- .../i18n-formatter/I18nFormatForTest.java | 185 +- checker/tests/i18n-formatter/IsFormat.java | 59 +- .../ManualExampleI18nFormatter.java | 52 +- checker/tests/i18n-formatter/Syntax.java | 182 +- .../TestUncheckedByteCode.java | 24 +- checker/tests/i18n/I18nCollectorsToList.java | 27 +- checker/tests/i18n/LocalizedMessage.java | 110 +- .../RequireJavadoc.java | 1707 +-- checker/tests/index/AndExample.java | 14 +- checker/tests/index/AnnotatedJDKTest.java | 8 +- checker/tests/index/ArrayAsList.java | 31 +- .../tests/index/ArrayAssignmentSameLen.java | 104 +- .../index/ArrayAssignmentSameLenComplex.java | 22 +- .../ArrayConstructionPositiveLength.java | 6 +- checker/tests/index/ArrayCopy.java | 10 +- checker/tests/index/ArrayCreationChecks.java | 86 +- checker/tests/index/ArrayCreationParam.java | 18 +- checker/tests/index/ArrayCreationTest.java | 8 +- checker/tests/index/ArrayIntro.java | 28 +- checker/tests/index/ArrayIntroWithCast.java | 18 +- checker/tests/index/ArrayLenTest.java | 16 +- checker/tests/index/ArrayLength.java | 8 +- checker/tests/index/ArrayLength2.java | 10 +- checker/tests/index/ArrayLength3.java | 14 +- checker/tests/index/ArrayLengthEquality.java | 24 +- checker/tests/index/ArrayLengthLBC.java | 14 +- checker/tests/index/ArrayNull.java | 2 +- checker/tests/index/ArrayWrapper.java | 74 +- checker/tests/index/ArraysSort.java | 15 +- checker/tests/index/BasicSubsequence.java | 86 +- checker/tests/index/BasicSubsequence2.java | 44 +- checker/tests/index/BasicSubsequence3.java | 32 +- checker/tests/index/BigBinaryExpr.java | 216 +- checker/tests/index/BinarySearchTest.java | 62 +- checker/tests/index/BinomialTest.java | 110 +- checker/tests/index/BitSetLowerBound.java | 21 +- checker/tests/index/Boilerplate.java | 8 +- checker/tests/index/BottomValTest.java | 18 +- checker/tests/index/CastArray.java | 6 +- .../tests/index/CharPrintedAsVariable.java | 16 +- checker/tests/index/CharSequenceTest.java | 125 +- checker/tests/index/CharToIntCast.java | 18 +- .../tests/index/CheckAgainstNegativeOne.java | 28 +- checker/tests/index/CheckNotNull1.java | 12 +- checker/tests/index/CheckNotNull2.java | 12 +- checker/tests/index/CombineFacts.java | 18 +- checker/tests/index/CompareBySubtraction.java | 30 +- .../tests/index/CompoundAssignmentCheck.java | 12 +- checker/tests/index/ComputeConst.java | 14 +- checker/tests/index/ConditionalIndex.java | 20 +- checker/tests/index/ConstantArrays.java | 46 +- checker/tests/index/ConstantOffsets.java | 28 +- checker/tests/index/ConstantsIndex.java | 12 +- .../tests/index/CustomContractWithArgs.java | 272 +- checker/tests/index/DaikonCrash.java | 19 +- checker/tests/index/DefaultingForEach.java | 20 +- checker/tests/index/Dimension.java | 26 +- checker/tests/index/DivisionTest.java | 6 +- checker/tests/index/EndsWith.java | 8 +- checker/tests/index/EndsWith2.java | 20 +- checker/tests/index/EnumValues.java | 32 +- checker/tests/index/EqualToIndex.java | 10 +- checker/tests/index/EqualToTransfer.java | 24 +- checker/tests/index/ErrorMessageCheck.java | 16 +- checker/tests/index/Errors.java | 30 +- checker/tests/index/ExampleUsage.java | 50 +- checker/tests/index/GenericAssignment.java | 35 +- .../index/GreaterThanOrEqualTransfer.java | 18 +- checker/tests/index/GreaterThanTransfer.java | 18 +- checker/tests/index/GuavaPrimitives.java | 229 +- checker/tests/index/HexEncode.java | 22 +- checker/tests/index/Index115.java | 32 +- checker/tests/index/Index118.java | 18 +- checker/tests/index/Index118NoLoop.java | 8 +- checker/tests/index/Index132.java | 12 +- checker/tests/index/Index166.java | 12 +- checker/tests/index/Index167.java | 32 +- checker/tests/index/Index176.java | 16 +- checker/tests/index/IndexByChar.java | 14 +- .../tests/index/IndexConditionalReport.java | 14 +- checker/tests/index/IndexForAverage.java | 16 +- checker/tests/index/IndexForTest.java | 116 +- checker/tests/index/IndexForTestLBC.java | 36 +- checker/tests/index/IndexForTwoArrays.java | 22 +- checker/tests/index/IndexForTwoArrays2.java | 22 +- checker/tests/index/IndexForVarargs.java | 40 +- .../tests/index/IndexIntValVsConstant.java | 28 +- checker/tests/index/IndexIssue6046.java | 67 +- checker/tests/index/IndexOf.java | 14 +- checker/tests/index/IndexOrLowTests.java | 38 +- checker/tests/index/IndexSameLen.java | 16 +- checker/tests/index/IntroAdd.java | 26 +- checker/tests/index/IntroAnd.java | 60 +- checker/tests/index/IntroRules.java | 46 +- checker/tests/index/IntroShift.java | 10 +- checker/tests/index/IntroSub.java | 30 +- checker/tests/index/InvalidSubsequence.java | 60 +- checker/tests/index/Issue1411.java | 10 +- checker/tests/index/Issue194.java | 54 +- checker/tests/index/Issue1984.java | 8 +- checker/tests/index/Issue20.java | 10 +- checker/tests/index/Issue2029.java | 43 +- checker/tests/index/Issue2030.java | 12 +- checker/tests/index/Issue21.java | 8 +- checker/tests/index/Issue2334.java | 48 +- checker/tests/index/Issue2420.java | 22 +- checker/tests/index/Issue2452.java | 47 +- checker/tests/index/Issue2493.java | 6 +- checker/tests/index/Issue2494.java | 30 +- checker/tests/index/Issue2505.java | 10 +- checker/tests/index/Issue2613.java | 26 +- checker/tests/index/Issue2629.java | 6 +- checker/tests/index/Issue3207.java | 20 +- checker/tests/index/Issue3224.java | 65 +- checker/tests/index/Issue5471.java | 26 +- checker/tests/index/Issue58Minimization.java | 80 +- checker/tests/index/Issue60.java | 22 +- checker/tests/index/IteratorVoid.java | 10 +- .../index/JavaxAnnotationNonnegative.java | 8 +- checker/tests/index/Kelloggm225.java | 14 +- checker/tests/index/Kelloggm228.java | 15 +- checker/tests/index/LBCSubtyping.java | 52 +- checker/tests/index/LTLDivide.java | 38 +- .../tests/index/LTLengthOfPostcondition.java | 67 +- .../tests/index/LengthOfArrayMinusOne.java | 12 +- checker/tests/index/LengthOfTest.java | 12 +- checker/tests/index/LengthTransfer.java | 28 +- checker/tests/index/LengthTransfer2.java | 22 +- .../tests/index/LengthTransferForMinLen.java | 28 +- checker/tests/index/LessThanBug.java | 14 +- .../tests/index/LessThanConstantAddition.java | 8 +- .../tests/index/LessThanCustomCollection.java | 106 +- checker/tests/index/LessThanDec.java | 16 +- checker/tests/index/LessThanFloat.java | 68 +- checker/tests/index/LessThanFloatLiteral.java | 14 +- checker/tests/index/LessThanLen.java | 92 +- checker/tests/index/LessThanLenBug.java | 28 +- .../tests/index/LessThanOrEqualTransfer.java | 18 +- checker/tests/index/LessThanTransferTest.java | 18 +- checker/tests/index/LessThanValue.java | 184 +- .../tests/index/LessThanZeroArrayLength.java | 8 +- checker/tests/index/ListAdd.java | 107 +- checker/tests/index/ListAddAll.java | 100 +- checker/tests/index/ListAddInfiniteLoop.java | 8 +- checker/tests/index/ListGet.java | 95 +- checker/tests/index/ListIterator.java | 98 +- checker/tests/index/ListLowerBound.java | 21 +- checker/tests/index/ListRemove.java | 112 +- checker/tests/index/ListSet.java | 97 +- checker/tests/index/ListSupport.java | 83 +- checker/tests/index/ListSupportLBC.java | 119 +- checker/tests/index/ListSupportML.java | 91 +- checker/tests/index/LiteralArray.java | 18 +- checker/tests/index/LiteralString.java | 18 +- .../index/LongAndIntegerBitsMethods.java | 18 +- checker/tests/index/Loops.java | 126 +- checker/tests/index/LubIndex.java | 66 +- checker/tests/index/MLEqualTo.java | 8 +- .../tests/index/MathPlumeClasscastCrash.java | 10 +- checker/tests/index/MethodOverrides.java | 14 +- checker/tests/index/MinLenFieldInvar.java | 82 +- .../tests/index/MinLenFourShenanigans.java | 32 +- checker/tests/index/MinLenFromPositive.java | 98 +- checker/tests/index/MinLenIndexFor.java | 58 +- checker/tests/index/MinLenOneAndLength.java | 10 +- .../tests/index/MinLenSameLenInteraction.java | 8 +- checker/tests/index/MinMax.java | 14 +- checker/tests/index/MinMaxIndex.java | 48 +- checker/tests/index/Modulo.java | 24 +- checker/tests/index/NegativeArray.java | 8 +- checker/tests/index/NegativeIndex.java | 8 +- checker/tests/index/NonNegArrayLength.java | 6 +- checker/tests/index/NonNegativeCharValue.java | 6 +- checker/tests/index/NonnegativeChar.java | 49 +- checker/tests/index/NotEnoughOffsets.java | 30 +- checker/tests/index/NotEqualTransfer.java | 34 +- checker/tests/index/ObjectClone.java | 37 +- checker/tests/index/Offset97.java | 14 +- checker/tests/index/OffsetAnnotations.java | 24 +- checker/tests/index/OffsetExample.java | 127 +- checker/tests/index/OffsetsAndConstants.java | 36 +- checker/tests/index/OneLTL.java | 14 +- checker/tests/index/OneOrTwo.java | 22 +- ...yCheckSubsequenceWhenAssigningToArray.java | 32 +- .../tests/index/OuterThisJavaExpression.java | 74 +- checker/tests/index/ParserOffsetTest.java | 130 +- checker/tests/index/ParsingBug.java | 14 +- checker/tests/index/Pilot2HalfLength.java | 12 +- checker/tests/index/Pilot3ArrayCreation.java | 10 +- checker/tests/index/Pilot4Subtraction.java | 18 +- checker/tests/index/PlumeFail.java | 21 +- checker/tests/index/PlumeFailMin.java | 24 +- checker/tests/index/PlusPlusBug.java | 18 +- checker/tests/index/PolyCrash.java | 6 +- checker/tests/index/PolyLengthTest.java | 24 +- checker/tests/index/Polymorphic.java | 100 +- checker/tests/index/Polymorphic2.java | 84 +- checker/tests/index/Polymorphic3.java | 90 +- checker/tests/index/Polymorphic4.java | 8 +- checker/tests/index/PreAndPostDec.java | 48 +- checker/tests/index/PredecrementTest.java | 52 +- checker/tests/index/PrimitiveWrappers.java | 14 +- checker/tests/index/PrivateSameLen.java | 16 +- checker/tests/index/RandomTest.java | 21 +- checker/tests/index/RandomTestLBC.java | 29 +- checker/tests/index/RangeIndex.java | 8 +- checker/tests/index/Reassignment.java | 12 +- checker/tests/index/RefineEq.java | 52 +- checker/tests/index/RefineGT.java | 90 +- checker/tests/index/RefineGTE.java | 90 +- checker/tests/index/RefineLT.java | 62 +- checker/tests/index/RefineLTE.java | 90 +- checker/tests/index/RefineLTE2.java | 22 +- checker/tests/index/RefineNeq.java | 58 +- checker/tests/index/RefineNeqLength.java | 116 +- checker/tests/index/RefineSubtrahend.java | 80 +- checker/tests/index/RefinementEq.java | 38 +- checker/tests/index/RefinementGT.java | 92 +- checker/tests/index/RefinementGTE.java | 76 +- checker/tests/index/RefinementLT.java | 88 +- checker/tests/index/RefinementLTE.java | 88 +- checker/tests/index/RefinementNEq.java | 42 +- checker/tests/index/ReflectArray.java | 33 +- checker/tests/index/RegexMatcher.java | 29 +- checker/tests/index/RepeatLTLengthOf.java | 170 +- .../index/RepeatLTLengthOfWithError.java | 178 +- checker/tests/index/Return.java | 6 +- checker/tests/index/SLSubtyping.java | 28 +- .../index/SameLenAssignmentTransfer.java | 10 +- .../tests/index/SameLenEqualsRefinement.java | 60 +- .../tests/index/SameLenFormalParameter2.java | 10 +- checker/tests/index/SameLenIrrelevant.java | 24 +- .../tests/index/SameLenLUBStrangeness.java | 14 +- checker/tests/index/SameLenManyArrays.java | 80 +- .../index/SameLenNewArrayWithSameLength.java | 12 +- .../tests/index/SameLenOnFormalParameter.java | 34 +- .../index/SameLenOnFormalParameterSimple.java | 8 +- checker/tests/index/SameLenSelf.java | 14 +- checker/tests/index/SameLenSimpleCase.java | 20 +- checker/tests/index/SameLenTripleThreat.java | 46 +- checker/tests/index/SameLenWithObjects.java | 22 +- checker/tests/index/SearchIndexTests.java | 83 +- checker/tests/index/ShiftRight.java | 32 +- checker/tests/index/ShiftRightAverage.java | 14 +- checker/tests/index/SimpleCollection.java | 26 +- checker/tests/index/SimpleTransferAdd.java | 20 +- checker/tests/index/SimpleTransferSub.java | 12 +- checker/tests/index/SizeVsLength.java | 12 +- checker/tests/index/SkipBufferedReader.java | 12 +- .../index/SpecialTransfersForEquality.java | 24 +- checker/tests/index/Split.java | 11 +- checker/tests/index/StartsEndsWith.java | 39 +- checker/tests/index/StaticInitializer.java | 2 +- checker/tests/index/Stopwatch.java | 23 +- checker/tests/index/StringBuilderOffset.java | 14 +- checker/tests/index/StringIndexOf.java | 62 +- checker/tests/index/StringLenRefinement.java | 60 +- checker/tests/index/StringLength.java | 111 +- checker/tests/index/StringMethods.java | 62 +- checker/tests/index/StringOffsetTest.java | 12 +- checker/tests/index/StringSameLen.java | 66 +- .../tests/index/StringTokenizerMinLen.java | 12 +- .../index/SubstringIndexForIrrelevant.java | 16 +- .../tests/index/SubtractingNonNegatives.java | 46 +- checker/tests/index/SubtractionIndex.java | 36 +- .../tests/index/SwitchDataflowRefinement.java | 32 +- checker/tests/index/SwitchTest.java | 24 +- checker/tests/index/TestAgainstLength.java | 18 +- checker/tests/index/ToArrayIndex.java | 9 +- checker/tests/index/TransferAdd.java | 102 +- checker/tests/index/TransferDivide.java | 106 +- checker/tests/index/TransferMod.java | 42 +- checker/tests/index/TransferSub.java | 138 +- checker/tests/index/TransferTimes.java | 38 +- .../index/TypeArrayLengthWithSameLen.java | 8 +- checker/tests/index/UBLiteralFlow.java | 330 +- checker/tests/index/UBPoly.java | 22 +- checker/tests/index/UBSubtyping.java | 38 +- .../UnaryOperationParsedIncorrectly.java | 14 +- checker/tests/index/UncheckedMinLen.java | 62 +- checker/tests/index/UpperBoundRefinement.java | 60 +- checker/tests/index/ValueCheckerProblem.java | 6 +- checker/tests/index/VarArgsIncompatible.java | 12 +- checker/tests/index/VarLteVar.java | 48 +- checker/tests/index/ViewpointAdaptTest.java | 14 +- checker/tests/index/VoidType.java | 2 +- checker/tests/index/ZeroMinLen.java | 18 +- .../tests/initialization/AnonymousInit.java | 30 +- checker/tests/initialization/CastInit.java | 10 +- .../initialization/ChainedInitialization.java | 12 +- checker/tests/initialization/Commitment.java | 136 +- checker/tests/initialization/Commitment2.java | 40 +- .../tests/initialization/CommitmentFlow.java | 27 +- checker/tests/initialization/FBCList.java | 80 +- .../initialization/FieldSuppressWarnings.java | 40 +- .../tests/initialization/FieldWithInit.java | 8 +- checker/tests/initialization/FlowFbc.java | 70 +- .../tests/initialization/GenericTest12b.java | 26 +- checker/tests/initialization/InstanceOf.java | 26 +- checker/tests/initialization/Issue1044.java | 96 +- checker/tests/initialization/Issue1120.java | 42 +- checker/tests/initialization/Issue1347.java | 32 +- checker/tests/initialization/Issue3407.java | 30 +- .../tests/initialization/Issue408Init.java | 34 +- checker/tests/initialization/Issue409.java | 39 +- checker/tests/initialization/Issue4567.java | 8 +- checker/tests/initialization/Issue556a.java | 12 +- checker/tests/initialization/Issue556b.java | 132 +- checker/tests/initialization/Issue574.java | 64 +- checker/tests/initialization/Issue779.java | 30 +- checker/tests/initialization/Issue813.java | 24 +- checker/tests/initialization/Issue904.java | 32 +- checker/tests/initialization/Issue905.java | 34 +- .../NotOnlyInitializedTest.java | 56 +- .../initialization/RawMethodInvocation.java | 84 +- .../tests/initialization/RawTypesInit.java | 454 +- .../ReceiverSuperInvocation.java | 12 +- checker/tests/initialization/SimpleFbc.java | 70 +- checker/tests/initialization/StaticInit.java | 8 +- checker/tests/initialization/Subtyping.java | 86 +- checker/tests/initialization/Suppression.java | 14 +- .../initialization/TestPolyInitialized.java | 80 +- checker/tests/initialization/TryFinally.java | 352 +- checker/tests/initialization/TryFinally2.java | 39 +- .../tests/initialization/TryFinallyBreak.java | 1034 +- .../initialization/TryFinallyContinue.java | 188 +- checker/tests/initialization/TypeFrames.java | 54 +- checker/tests/initialization/TypeFrames2.java | 42 +- checker/tests/initialization/TypeFrames3.java | 30 +- checker/tests/initialization/TypeFrames4.java | 28 +- checker/tests/initialization/TypeFrames5.java | 16 +- .../UnboxUninitalizedFieldTest.java | 10 +- checker/tests/initialization/Uninit.java | 4 +- checker/tests/initialization/Uninit10.java | 16 +- checker/tests/initialization/Uninit11.java | 23 +- checker/tests/initialization/Uninit12.java | 30 +- checker/tests/initialization/Uninit13.java | 12 +- checker/tests/initialization/Uninit14.java | 14 +- checker/tests/initialization/Uninit2.java | 8 +- checker/tests/initialization/Uninit3.java | 2 +- checker/tests/initialization/Uninit4.java | 48 +- checker/tests/initialization/Uninit5.java | 4 +- checker/tests/initialization/Uninit6.java | 10 +- checker/tests/initialization/Uninit7.java | 8 +- checker/tests/initialization/Uninit8.java | 18 +- checker/tests/initialization/Uninit9.java | 20 +- .../RedundantAnnotationOnField.java | 2 +- .../StaticFinalStringDefault.java | 4 +- .../tests/interning/ArrayInitializers.java | 6 +- checker/tests/interning/Arrays.java | 139 +- checker/tests/interning/ArraysMDETest.java | 12 +- checker/tests/interning/Autoboxing.java | 314 +- checker/tests/interning/BoxingInterning.java | 128 +- checker/tests/interning/Casts.java | 6 +- checker/tests/interning/ClassDefaults.java | 27 +- checker/tests/interning/Comparison.java | 62 +- .../tests/interning/CompileTimeConstants.java | 22 +- .../interning/CompileTimeConstants2.java | 14 +- .../tests/interning/ComplexComparison.java | 147 +- .../tests/interning/ConditionalInterning.java | 8 +- .../tests/interning/ConstantsInterning.java | 56 +- checker/tests/interning/Creation.java | 88 +- checker/tests/interning/Creation2.java | 14 +- checker/tests/interning/Distinct.java | 102 +- checker/tests/interning/DontCrash.java | 31 +- checker/tests/interning/Enumerations.java | 42 +- .../tests/interning/ExpressionsInterning.java | 78 +- checker/tests/interning/FieldsImplicits.java | 18 +- checker/tests/interning/FindDistinctTest.java | 40 +- checker/tests/interning/FlowInterning.java | 94 +- checker/tests/interning/FlowInterning1.java | 18 +- checker/tests/interning/Generics.java | 225 +- checker/tests/interning/HeuristicsTest.java | 439 +- checker/tests/interning/InternMethodTest.java | 35 +- checker/tests/interning/InternUnbox.java | 16 +- checker/tests/interning/InternedClass.java | 291 +- checker/tests/interning/InternedClass2.java | 121 +- .../tests/interning/InternedClassDecl.java | 6 +- checker/tests/interning/Issue2809.java | 34 +- checker/tests/interning/Issue3594.java | 12 +- checker/tests/interning/IterableGenerics.java | 14 +- checker/tests/interning/MapEntryLubError.java | 8 +- checker/tests/interning/MethodInvocation.java | 98 +- checker/tests/interning/NestedGenerics.java | 15 +- checker/tests/interning/Options.java | 143 +- checker/tests/interning/OverrideInterned.java | 86 +- checker/tests/interning/Polymorphism.java | 166 +- .../tests/interning/PrimitivesInterning.java | 209 +- checker/tests/interning/Raw3.java | 121 +- checker/tests/interning/RecursiveClass.java | 8 +- .../tests/interning/SequenceAndIndices.java | 74 +- .../tests/interning/StaticInternMethod.java | 39 +- checker/tests/interning/StringIntern.java | 99 +- checker/tests/interning/Subclass.java | 8 +- .../interning/SuppressWarningsClass.java | 6 +- .../tests/interning/SuppressWarningsVar.java | 8 +- checker/tests/interning/TVWCSuper.java | 4 +- checker/tests/interning/TestExtSup.java | 10 +- checker/tests/interning/TestInfer.java | 32 +- .../interning/ThreadUsesObjectEquals.java | 6 +- .../interning/TypeVarPrimitivesInterning.java | 25 +- checker/tests/interning/UnboxUninterned.java | 16 +- .../tests/interning/UsesObjectEqualsTest.java | 135 +- checker/tests/lock-records/LockRecord.java | 13 +- .../lock-safedefaults/BasicLockTest.java | 197 +- checker/tests/lock/ChapterExamples.java | 1051 +- checker/tests/lock/ClassLiterals.java | 40 +- checker/tests/lock/ConstructorReturnNPE.java | 4 +- checker/tests/lock/ConstructorsLock.java | 60 +- checker/tests/lock/Fields.java | 156 +- checker/tests/lock/FlowExpressionsTest.java | 52 +- checker/tests/lock/FullyQualified.java | 13 +- checker/tests/lock/GuardSatisfiedArray.java | 17 +- checker/tests/lock/GuardSatisfiedTest.java | 530 +- .../tests/lock/GuardedByLocalVariable.java | 54 +- checker/tests/lock/Issue152.java | 38 +- checker/tests/lock/Issue2163Lock.java | 10 +- checker/tests/lock/Issue523.java | 28 +- checker/tests/lock/Issue524.java | 43 +- checker/tests/lock/Issue753.java | 235 +- checker/tests/lock/Issue804.java | 27 +- checker/tests/lock/Issue805.java | 14 +- checker/tests/lock/ItselfExpressionCases.java | 230 +- checker/tests/lock/JCIPAnnotations.java | 209 +- checker/tests/lock/LockEffectAnnotations.java | 232 +- checker/tests/lock/LockExpressionIsFinal.java | 547 +- checker/tests/lock/LockInterfaceTest.java | 93 +- checker/tests/lock/Methods.java | 94 +- .../tests/lock/NestedSynchronizedBlocks.java | 60 +- checker/tests/lock/Overriding.java | 290 +- checker/tests/lock/PrimitivesLocking.java | 302 +- checker/tests/lock/ReturnsNewObjectTest.java | 20 +- checker/tests/lock/SimpleLockTest.java | 57 +- checker/tests/lock/Strings.java | 106 +- checker/tests/lock/TestAnon.java | 16 +- .../tests/lock/TestConcurrentSemantics1.java | 89 +- .../tests/lock/TestConcurrentSemantics2.java | 40 +- checker/tests/lock/TestTreeKinds.java | 903 +- checker/tests/lock/ThisPostCondition.java | 86 +- checker/tests/lock/ThisSuper.java | 143 +- checker/tests/lock/TypeVarNull.java | 2 +- checker/tests/lock/Update.java | 14 +- checker/tests/lock/ViewpointAdaptation.java | 64 +- checker/tests/lock/ViewpointAdaptation2.java | 52 +- checker/tests/lock/ViewpointAdaptation3.java | 184 +- .../BorrowOnReturn.java | 86 +- .../NonOwningPolyInteraction.java | 69 +- .../OwningParams.java | 8 +- .../tests/mustcall/BinaryInputArchive.java | 14 +- checker/tests/mustcall/BorrowOnReturn.java | 84 +- checker/tests/mustcall/ClassForNameInit.java | 48 +- checker/tests/mustcall/CommandResponse.java | 14 +- .../mustcall/CreatesMustCallForSimple.java | 98 +- .../tests/mustcall/EditLogInputStream.java | 6 +- .../FieldInitializationWithGeneric.java | 4 +- checker/tests/mustcall/FileDescriptors.java | 23 +- checker/tests/mustcall/InferTypeArgs.java | 20 +- .../mustcall/InheritableMustCallEmpty.java | 31 +- checker/tests/mustcall/ListOfMustCall.java | 51 +- checker/tests/mustcall/LogTheSocket.java | 81 +- checker/tests/mustcall/MapWrap.java | 25 +- checker/tests/mustcall/MustCallAliasImpl.java | 19 +- .../tests/mustcall/MustCallSubtypingTest.java | 244 +- checker/tests/mustcall/MyDataInputStream.java | 26 +- .../mustcall/NonOwningPolyInteraction.java | 73 +- .../NotInheritableMustCallOnClassError.java | 2 +- ...nheritableMustCallOnFinalClassNoError.java | 2 +- .../tests/mustcall/NullOutputStreamTest.java | 9 +- checker/tests/mustcall/NullableTransfer.java | 19 +- .../tests/mustcall/OwningMustCallNothing.java | 49 +- checker/tests/mustcall/OwningParams.java | 12 +- .../PlumeUtilRequiredAnnotations.java | 77 +- .../mustcall/PolyMustCallDifferentNames.java | 108 +- checker/tests/mustcall/PolyTests.java | 70 +- checker/tests/mustcall/SimpleException.java | 18 +- checker/tests/mustcall/SimpleSocketField.java | 27 +- .../tests/mustcall/SimpleStreamExample.java | 6 +- .../tests/mustcall/SocketBufferedReader.java | 27 +- checker/tests/mustcall/StreamBool.java | 8 +- checker/tests/mustcall/StringSort.java | 8 +- checker/tests/mustcall/Subtype0.java | 80 +- checker/tests/mustcall/Subtyping.java | 100 +- checker/tests/mustcall/SystemInOut.java | 15 +- checker/tests/mustcall/ToStringOnSocket.java | 18 +- .../tests/mustcall/TryWithResourcesCrash.java | 32 +- .../mustcall/TryWithResourcesSimple.java | 69 +- checker/tests/mustcall/TypeArgs.java | 171 +- .../AnnotatedForNullness.java | 124 +- .../annotated/Test.java | 8 +- .../notannotated/Test.java | 8 +- .../nullness-asserts/NonNullMapValue.java | 399 +- .../TestAssumeAssertionsAreEnabled.java | 16 +- .../TestAssumeAssertionsAreDisabled.java | 14 +- .../AssumeInitTest.java | 34 +- .../AssumeKeyForTest.java | 71 +- .../Issue1315.java | 48 +- .../Issue350.java | 54 +- .../NullnessEnclosingExprTest.java | 56 +- checker/tests/nullness-extra/Bug109_A.java | 10 +- checker/tests/nullness-extra/Bug109_B.java | 14 +- .../nullness-extra/compat/CompatTest.java | 7 +- .../tests/nullness-extra/compat/lib/Lib.java | 6 +- .../tests/nullness-extra/issue265/Delta.java | 8 +- .../issue265/ImmutableList.java | 6 +- .../nullness-extra/issue309/Issue309.java | 6 +- .../nullness-extra/issue309/lib/Lib.java | 4 +- .../nullness-extra/issue348/Issue348.java | 8 +- .../nullness-extra/issue348/lib/Lib.java | 4 +- .../nullness-extra/issue348/lib/LibSuper.java | 4 +- .../issue3597/testpkg/Issue3597A.java | 6 +- .../issue3597/testpkg/Issue3597B.java | 6 +- .../nullness-extra/issue502/Issue502.java | 8 +- .../nullness-extra/issue5174/Issue5174.java | 96 +- .../nullness-extra/issue559/Issue559.java | 12 +- .../nullness-extra/issue594/Issue594.java | 12 +- .../nullness-extra/issue607/Issue607.java | 8 +- .../issue607/Issue607Interface.java | 2 +- .../issue607/Issue607SuperClass.java | 4 +- .../nullness-extra/multiple-errors/C1.java | 2 +- .../nullness-extra/multiple-errors/C2.java | 2 +- .../nullness-extra/multiple-errors/C3.java | 12 +- .../nullness-extra/multiple-errors/C4.java | 6 +- .../test/PackageAnnotationTest.java | 4 +- .../shorthand/NullnessRegexWithErrors.java | 10 +- .../GenericWildcardInheritance.java | 4 +- .../nullness-genericwildcard/Issue511.java | 34 +- .../GwiParent.java | 2 +- .../AssignmentDuringInitialization.java | 62 +- .../EisopIssue635.java | 32 +- .../EnumFieldUninit.java | 28 +- .../nullness-initialization/FieldInit.java | 16 +- .../nullness-initialization/FinalClass.java | 36 +- .../FinalClassLambda.java | 140 +- .../FlowConstructor.java | 54 +- .../FlowConstructor2.java | 10 +- .../FlowInitialization.java | 68 +- .../nullness-initialization/Initializer.java | 148 +- .../nullness-initialization/Issue1096.java | 102 +- .../nullness-initialization/Issue1590.java | 20 +- .../nullness-initialization/Issue1590a.java | 18 +- .../nullness-initialization/Issue261.java | 22 +- .../nullness-initialization/Issue345.java | 22 +- .../nullness-initialization/Issue354.java | 28 +- .../nullness-initialization/Issue400.java | 24 +- .../nullness-initialization/Issue408.java | 34 +- .../KeyForValidation.java | 193 +- .../nullness-initialization/Listener.java | 34 +- .../MethodInvocation.java | 44 +- .../MultiConstructorInit.java | 32 +- .../ObjectArrayParam.java | 12 +- .../ObjectListParam.java | 17 +- .../PrivateMethodUnknownInit.java | 20 +- .../tests/nullness-initialization/Raw2.java | 42 +- .../nullness-initialization/RawField.java | 58 +- .../RawMethodInvocation.java | 52 +- .../RawTypesBounded.java | 348 +- .../nullness-initialization/Simple2.java | 34 +- .../StaticInitialization.java | 8 +- .../StaticInitializer.java | 62 +- .../SuperConstructorInit.java | 22 +- .../nullness-initialization/Suppression.java | 34 +- .../nullness-initialization/ThisNodeTest.java | 28 +- .../nullness-initialization/Throwing.java | 26 +- .../nullness-initialization/TryCatch.java | 55 +- .../TwoStaticInitBlocks.java | 70 +- .../nullness-initialization/ValidType.java | 12 +- .../nullness-initialization/VarInfoName.java | 16 +- .../nullness-initialization/Wellformed.java | 86 +- .../generics/AnnotatedGenerics.java | 122 +- .../generics/AnnotatedGenerics2.java | 256 +- .../generics/GenericBoundsExplicit.java | 69 +- .../generics/Issue314.java | 33 +- .../generics/Issue783c.java | 16 +- .../generics/NullableLUB.java | 44 +- .../generics/WellformedBounds.java | 44 +- .../java8/lambda/LambdaInit.java | 346 +- .../java8/lambda/ReceiversLambda.java | 34 +- .../TwoDimensionalArray.java | 12 +- .../JavadocJdkAnnotations.java | 23 +- .../nullness-nodelombok/UnsoundnessTest.java | 20 +- .../NotNullMarkedBecauseChildPackage.java | 2 +- .../NullMarkedBecausePackageIs.java | 4 +- .../PermitClearProperty.java | 235 +- .../tests/nullness-records/BasicRecord.java | 14 +- .../nullness-records/BasicRecordCanon.java | 16 +- .../nullness-records/BasicRecordNullable.java | 40 +- .../nullness-records/DefaultQualRecord.java | 60 +- .../tests/nullness-records/GenericPair.java | 10 +- checker/tests/nullness-records/Issue5200.java | 30 +- .../tests/nullness-records/LocalRecords.java | 14 +- .../nullness-records/NestedRecordTest.java | 160 +- .../nullness-records/NormalizingRecord.java | 62 +- .../tests/nullness-records/RecordPurity.java | 172 +- .../nullness-records/RecordPurityGeneric.java | 84 +- .../RecordPurityOverride.java | 56 +- .../NullnessReflectionExampleTest.java | 47 +- .../NullnessReflectionResolutionTest.java | 75 +- .../tests/nullness-reflection/VoidTest.java | 6 +- .../AnnotatedJdkTest.java | 15 +- .../ArraysMDE.java | 40 +- .../BytecodeDefaultsTest.java | 24 +- .../BasicSafeDefaultsTest.java | 70 +- .../Issue3449.java | 12 +- .../PrimitiveClassLiteral.java | 52 +- .../Lib.java | 36 +- .../tests/nullness-skipdefs/SkipDefs1.java | 18 +- .../tests/nullness-skipdefs/SkipDefs2.java | 20 +- .../nullness-skipdirs/skip/SkipDirs1.java | 20 +- .../skip/this/SkipDirs2.java | 14 +- .../tests/nullness-skipuses/SkipUses1.java | 32 +- .../tests/nullness-skipuses/SkipUses2.java | 38 +- .../tests/nullness-stubfile/Issue4598.java | 15 +- .../NullnessStubfileMerge.java | 32 +- .../AnnoOnTypeVariableCrashCase.java | 6 +- .../RedundantAnnoWithDefaultQualifier.java | 16 +- .../RedundantAnnotation.java | 101 +- .../RedundantAnnotationOptions.java | 27 +- .../tests/nullness/AliasedAnnotations.java | 136 +- checker/tests/nullness/Aliasing.java | 28 +- .../nullness/AnnotatedJdkEqualsTest.java | 20 +- checker/tests/nullness/AnnotatedJdkTest.java | 19 +- .../tests/nullness/AnnotatedSupertype.java | 23 +- checker/tests/nullness/AnonymousSkipDefs.java | 30 +- checker/tests/nullness/ArrayArgs.java | 44 +- .../tests/nullness/ArrayAssignmentFlow.java | 20 +- checker/tests/nullness/ArrayCreation.java | 14 +- .../tests/nullness/ArrayCreationNullable.java | 324 +- .../tests/nullness/ArrayCreationSubArray.java | 20 +- checker/tests/nullness/ArrayIndex.java | 22 +- checker/tests/nullness/ArrayInitBug.java | 8 +- checker/tests/nullness/ArrayLazyNN.java | 20 +- checker/tests/nullness/ArrayNew.java | 27 +- checker/tests/nullness/ArrayRefs.java | 61 +- checker/tests/nullness/AssertAfter.java | 98 +- checker/tests/nullness/AssertAfter2.java | 193 +- .../tests/nullness/AssertAfterChecked.java | 288 +- checker/tests/nullness/AssertIfChecked.java | 311 +- checker/tests/nullness/AssertIfClient.java | 98 +- checker/tests/nullness/AssertIfFalseTest.java | 98 +- .../tests/nullness/AssertIfFalseTest2.java | 38 +- .../tests/nullness/AssertIfNonNullTest.java | 18 +- checker/tests/nullness/AssertInStatic.java | 14 +- checker/tests/nullness/AssertMethodTest.java | 126 +- .../nullness/AssertNonNullIfNonNullTest.java | 72 +- checker/tests/nullness/AssertNonNullTest.java | 26 +- checker/tests/nullness/AssertNullable.java | 38 +- .../nullness/AssertParameterNullness.java | 44 +- checker/tests/nullness/AssertTwice.java | 48 +- checker/tests/nullness/AssertWithStatic.java | 98 +- checker/tests/nullness/Asserts.java | 120 +- checker/tests/nullness/BinaryOp.java | 6 +- checker/tests/nullness/BinarySearch.java | 14 +- checker/tests/nullness/BoxingNullness.java | 282 +- checker/tests/nullness/Bug102.java | 26 +- checker/tests/nullness/Bug103.java | 134 +- checker/tests/nullness/CallSuper.java | 17 +- checker/tests/nullness/CastTypeVariable.java | 16 +- checker/tests/nullness/CastsNullness.java | 166 +- checker/tests/nullness/ChainAssignment.java | 80 +- checker/tests/nullness/ChicoryPremain.java | 16 +- .../tests/nullness/ClassGetCanonicalName.java | 2 +- checker/tests/nullness/CompoundAssign.java | 20 +- .../tests/nullness/ConditionalNullness.java | 156 +- checker/tests/nullness/ConditionalOr.java | 8 +- .../tests/nullness/ConditionalPolyNull.java | 58 +- checker/tests/nullness/Conditions.java | 50 +- .../nullness/ConstructorPostcondition.java | 28 +- checker/tests/nullness/ControlFlow.java | 20 +- checker/tests/nullness/CopyOfArray.java | 17 +- checker/tests/nullness/DaikonEnhancedFor.java | 39 +- .../nullness/DaikonEnhancedForNoThis.java | 39 +- checker/tests/nullness/DaikonTests.java | 214 +- checker/tests/nullness/DefaultAnnotation.java | 209 +- checker/tests/nullness/DefaultFlow.java | 24 +- checker/tests/nullness/DefaultInterface.java | 8 +- checker/tests/nullness/DefaultLoops.java | 57 +- checker/tests/nullness/DefaultingForEach.java | 59 +- checker/tests/nullness/DefaultsNullness.java | 20 +- .../nullness/DependentTypeTypeInference.java | 11 +- checker/tests/nullness/DotClass.java | 19 +- checker/tests/nullness/EisopIssue308.java | 14 +- checker/tests/nullness/EmptyConstructor.java | 12 +- .../nullness/EnsuresKeyForOverriding.java | 31 +- .../EnsuresNonNullIfInheritedTest.java | 52 +- .../tests/nullness/EnsuresNonNullIfTest.java | 72 +- .../tests/nullness/EnsuresNonNullIfTest2.java | 98 +- .../tests/nullness/EnsuresNonNullIfTest4.java | 36 +- .../nullness/EnsuresNonNullIfTestSimple.java | 68 +- checker/tests/nullness/EnumStaticBlock.java | 18 +- checker/tests/nullness/EnumsNullness.java | 56 +- checker/tests/nullness/EqualToNullness.java | 66 +- checker/tests/nullness/ExceptionParam.java | 34 +- checker/tests/nullness/Exceptions.java | 72 +- .../tests/nullness/ExplictTypeVarAnnos.java | 84 +- .../tests/nullness/ExpressionsNullness.java | 85 +- checker/tests/nullness/ExtendsArrayList.java | 10 +- checker/tests/nullness/FenumExplicit.java | 24 +- .../nullness/FieldWithAnnotatedLambda.java | 21 +- checker/tests/nullness/FinalFields.java | 66 +- checker/tests/nullness/FinalVar.java | 28 +- checker/tests/nullness/FinalVar2.java | 68 +- checker/tests/nullness/FinalVar3.java | 16 +- checker/tests/nullness/FindBugs.java | 40 +- checker/tests/nullness/FlowAssignment.java | 10 +- checker/tests/nullness/FlowCompound.java | 104 +- .../nullness/FlowCompoundConcatenation.java | 26 +- checker/tests/nullness/FlowConditions.java | 55 +- .../nullness/FlowExpressionParsingBug.java | 173 +- checker/tests/nullness/FlowField.java | 106 +- checker/tests/nullness/FlowLoop.java | 298 +- checker/tests/nullness/FlowNegation.java | 158 +- checker/tests/nullness/FlowNonThis.java | 73 +- checker/tests/nullness/FlowNullness.java | 518 +- checker/tests/nullness/FlowSelf.java | 16 +- checker/tests/nullness/ForEachMin.java | 19 +- .../nullness/FullyQualifiedAnnotation.java | 40 +- checker/tests/nullness/GeneralATFStore.java | 10 +- checker/tests/nullness/GenericCast.java | 14 +- checker/tests/nullness/GetConstantStr.java | 8 +- .../tests/nullness/GetInterfacesPurity.java | 12 +- checker/tests/nullness/GetPackage1.java | 8 +- checker/tests/nullness/GetProperty.java | 33 +- checker/tests/nullness/GetRefArg.java | 13 +- checker/tests/nullness/HasInnerClass.java | 8 +- checker/tests/nullness/HierarchicalInit.java | 20 +- .../tests/nullness/ImplementInterface.java | 10 +- checker/tests/nullness/Imports1.java | 6 +- checker/tests/nullness/Imports2.java | 6 +- checker/tests/nullness/InferListParam.java | 8 +- checker/tests/nullness/InferNullType.java | 52 +- .../InferTypeArgsConditionalExpression.java | 12 +- .../nullness/InfiniteLoopIsSameType.java | 32 +- .../tests/nullness/InitSuppressWarnings.java | 8 +- checker/tests/nullness/InitThrows.java | 14 +- .../InitializationAssertionFailure.java | 4 +- checker/tests/nullness/InitializedField.java | 27 +- checker/tests/nullness/InnerCrash.java | 16 +- checker/tests/nullness/InvariantTypes.java | 42 +- checker/tests/nullness/IsEmptyPoll.java | 31 +- checker/tests/nullness/Issue1027.java | 45 +- checker/tests/nullness/Issue1046Java7.java | 34 +- checker/tests/nullness/Issue1059.java | 22 +- checker/tests/nullness/Issue1102.java | 23 +- checker/tests/nullness/Issue1147.java | 12 +- checker/tests/nullness/Issue1307.java | 8 +- checker/tests/nullness/Issue1406.java | 55 +- checker/tests/nullness/Issue1522.java | 91 +- checker/tests/nullness/Issue1555.java | 8 +- checker/tests/nullness/Issue160.java | 90 +- checker/tests/nullness/Issue1628.java | 16 +- checker/tests/nullness/Issue1712.java | 30 +- checker/tests/nullness/Issue1797.java | 402 +- checker/tests/nullness/Issue1847.java | 42 +- checker/tests/nullness/Issue1847B.java | 27 +- checker/tests/nullness/Issue1922.java | 45 +- checker/tests/nullness/Issue1949.java | 23 +- checker/tests/nullness/Issue1981.java | 22 +- checker/tests/nullness/Issue1983.java | 51 +- checker/tests/nullness/Issue2013.java | 182 +- checker/tests/nullness/Issue2031.java | 63 +- checker/tests/nullness/Issue2048.java | 32 +- checker/tests/nullness/Issue2052.java | 20 +- checker/tests/nullness/Issue2171.java | 58 +- checker/tests/nullness/Issue2247.java | 51 +- checker/tests/nullness/Issue2407.java | 26 +- checker/tests/nullness/Issue2432.java | 205 +- checker/tests/nullness/Issue2432b.java | 63 +- checker/tests/nullness/Issue2470.java | 98 +- checker/tests/nullness/Issue2564.java | 25 +- checker/tests/nullness/Issue2565.java | 8 +- checker/tests/nullness/Issue2587.java | 47 +- checker/tests/nullness/Issue2619.java | 63 +- checker/tests/nullness/Issue2619b.java | 75 +- checker/tests/nullness/Issue266.java | 26 +- checker/tests/nullness/Issue266a.java | 26 +- checker/tests/nullness/Issue2721.java | 12 +- checker/tests/nullness/Issue273.java | 33 +- checker/tests/nullness/Issue2865.java | 32 +- checker/tests/nullness/Issue2888.java | 48 +- checker/tests/nullness/Issue289.java | 47 +- checker/tests/nullness/Issue293.java | 90 +- checker/tests/nullness/Issue295.java | 62 +- checker/tests/nullness/Issue296.java | 37 +- checker/tests/nullness/Issue3013.java | 10 +- checker/tests/nullness/Issue3015.java | 24 +- checker/tests/nullness/Issue3020.java | 24 +- checker/tests/nullness/Issue3022.java | 12 +- checker/tests/nullness/Issue3033.java | 30 +- checker/tests/nullness/Issue306.java | 66 +- checker/tests/nullness/Issue308.java | 25 +- checker/tests/nullness/Issue3150.java | 40 +- checker/tests/nullness/Issue328.java | 25 +- checker/tests/nullness/Issue331.java | 8 +- checker/tests/nullness/Issue3349.java | 11 +- checker/tests/nullness/Issue338.java | 8 +- checker/tests/nullness/Issue3443.java | 20 +- checker/tests/nullness/Issue355.java | 71 +- checker/tests/nullness/Issue3614.java | 216 +- checker/tests/nullness/Issue3622.java | 177 +- checker/tests/nullness/Issue3631.java | 16 +- checker/tests/nullness/Issue3681.java | 84 +- checker/tests/nullness/Issue369.java | 6 +- checker/tests/nullness/Issue370.java | 6 +- checker/tests/nullness/Issue372.java | 13 +- checker/tests/nullness/Issue3754.java | 24 +- checker/tests/nullness/Issue376.java | 8 +- checker/tests/nullness/Issue3764.java | 35 +- checker/tests/nullness/Issue3792.java | 16 +- checker/tests/nullness/Issue3845.java | 32 +- checker/tests/nullness/Issue3850.java | 18 +- checker/tests/nullness/Issue388.java | 18 +- checker/tests/nullness/Issue3884.java | 28 +- checker/tests/nullness/Issue3888.java | 18 +- checker/tests/nullness/Issue391.java | 70 +- checker/tests/nullness/Issue3929.java | 33 +- checker/tests/nullness/Issue3935.java | 10 +- checker/tests/nullness/Issue3970.java | 20 +- checker/tests/nullness/Issue4007.java | 51 +- checker/tests/nullness/Issue411.java | 32 +- checker/tests/nullness/Issue414.java | 61 +- checker/tests/nullness/Issue415.java | 65 +- checker/tests/nullness/Issue419.java | 20 +- checker/tests/nullness/Issue425.java | 39 +- checker/tests/nullness/Issue427.java | 23 +- checker/tests/nullness/Issue4372.java | 24 +- checker/tests/nullness/Issue4381.java | 20 +- checker/tests/nullness/Issue4412.java | 423 +- checker/tests/nullness/Issue4523.java | 16 +- checker/tests/nullness/Issue4579.java | 45 +- checker/tests/nullness/Issue4593.java | 19 +- checker/tests/nullness/Issue4614.java | 32 +- checker/tests/nullness/Issue471.java | 8 +- checker/tests/nullness/Issue4853Nullness.java | 22 +- checker/tests/nullness/Issue4889.java | 17 +- checker/tests/nullness/Issue4923.java | 30 +- checker/tests/nullness/Issue4924.java | 20 +- checker/tests/nullness/Issue500.java | 32 +- checker/tests/nullness/Issue5042.java | 101 +- checker/tests/nullness/Issue5075NPE.java | 64 +- checker/tests/nullness/Issue5075a.java | 40 +- checker/tests/nullness/Issue5075b.java | 42 +- checker/tests/nullness/Issue5189a.java | 4 +- checker/tests/nullness/Issue5189b.java | 12 +- checker/tests/nullness/Issue520.java | 42 +- checker/tests/nullness/Issue5245.java | 4 +- checker/tests/nullness/Issue531.java | 24 +- checker/tests/nullness/Issue554.java | 88 +- checker/tests/nullness/Issue563.java | 10 +- checker/tests/nullness/Issue577.java | 84 +- checker/tests/nullness/Issue578.java | 10 +- checker/tests/nullness/Issue579Error.java | 12 +- checker/tests/nullness/Issue580.java | 10 +- checker/tests/nullness/Issue602.java | 26 +- checker/tests/nullness/Issue6260.java | 28 +- checker/tests/nullness/Issue6393.java | 33 +- checker/tests/nullness/Issue653.java | 48 +- checker/tests/nullness/Issue67.java | 22 +- checker/tests/nullness/Issue672.java | 26 +- checker/tests/nullness/Issue679.java | 10 +- checker/tests/nullness/Issue738.java | 50 +- checker/tests/nullness/Issue741.java | 26 +- checker/tests/nullness/Issue752.java | 96 +- checker/tests/nullness/Issue759.java | 54 +- checker/tests/nullness/Issue764.java | 24 +- checker/tests/nullness/Issue765.java | 18 +- checker/tests/nullness/Issue811.java | 36 +- checker/tests/nullness/Issue829.java | 14 +- checker/tests/nullness/Issue868.java | 102 +- checker/tests/nullness/Issue906.java | 14 +- checker/tests/nullness/Issue961.java | 67 +- checker/tests/nullness/Issue986.java | 50 +- checker/tests/nullness/Issue989.java | 21 +- checker/tests/nullness/Iterate.java | 8 +- checker/tests/nullness/IteratorEarlyExit.java | 55 +- checker/tests/nullness/JUnitNull.java | 6 +- checker/tests/nullness/JavaCopExplosion.java | 213 +- checker/tests/nullness/JavaCopFlow.java | 312 +- .../tests/nullness/JavaCopRandomTests.java | 38 +- checker/tests/nullness/JavaExprContext.java | 278 +- checker/tests/nullness/KeyForAutoboxing.java | 74 +- checker/tests/nullness/KeyForChecked.java | 251 +- checker/tests/nullness/KeyForDiamond.java | 21 +- checker/tests/nullness/KeyForFlow.java | 277 +- checker/tests/nullness/KeyForIssue328.java | 25 +- .../tests/nullness/KeyForLocalSideEffect.java | 29 +- .../tests/nullness/KeyForLocalVariable.java | 41 +- checker/tests/nullness/KeyForLub.java | 67 +- checker/tests/nullness/KeyForMultiple.java | 45 +- .../tests/nullness/KeyForPolymorphism.java | 21 +- .../tests/nullness/KeyForPostcondition.java | 65 +- checker/tests/nullness/KeyForPropagation.java | 29 +- checker/tests/nullness/KeyForShadowing.java | 109 +- checker/tests/nullness/KeyForStaticField.java | 37 +- checker/tests/nullness/KeyForSubst.java | 79 +- checker/tests/nullness/KeyForSubtyping.java | 251 +- checker/tests/nullness/KeyForTypeVar.java | 11 +- .../nullness/KeyFor_DirectionsFinder.java | 51 +- checker/tests/nullness/KeyFors.java | 187 +- checker/tests/nullness/Lazy.java | 102 +- .../tests/nullness/LazyInitialization.java | 160 +- checker/tests/nullness/LogRecordTest.java | 20 +- checker/tests/nullness/LogicOperations.java | 102 +- checker/tests/nullness/LubTest.java | 32 +- checker/tests/nullness/MapGetNullable.java | 211 +- checker/tests/nullness/MapMerge.java | 34 +- checker/tests/nullness/Marino.java | 100 +- .../MethodOverloadingContractsKeyFor.java | 55 +- checker/tests/nullness/MethodTypeVars4.java | 39 +- .../nullness/MissingBoundAnnotations.java | 25 +- checker/tests/nullness/MisuseProperties.java | 145 +- .../nullness/MonotonicNonNullFieldTest.java | 28 +- .../tests/nullness/MonotonicNonNullTest.java | 14 +- checker/tests/nullness/MultiAnnotations.java | 10 +- checker/tests/nullness/MultipleErrors.java | 12 +- checker/tests/nullness/MyException.java | 30 +- checker/tests/nullness/NNOEMoreTests.java | 68 +- checker/tests/nullness/NNOEStaticFields.java | 199 +- .../nullness/NegatingConditionalNullness.java | 91 +- checker/tests/nullness/NewNullable.java | 18 +- checker/tests/nullness/NewObjectNonNull.java | 26 +- .../tests/nullness/NonEmptyCollection.java | 71 +- .../tests/nullness/NonNullInitialization.java | 10 +- .../tests/nullness/NonNullIteratorNext.java | 18 +- checker/tests/nullness/NullableArrays.java | 8 +- .../tests/nullness/NullableConstructor.java | 4 +- checker/tests/nullness/NullableObject.java | 14 +- .../tests/nullness/NullnessFieldInvar.java | 292 +- checker/tests/nullness/NullnessIssue4996.java | 28 +- .../tests/nullness/ObjectsRequireNonNull.java | 19 +- .../nullness/ObjectsRequireNonNullElse.java | 14 +- checker/tests/nullness/OptTest.java | 14 +- checker/tests/nullness/OverrideANNA.java | 36 +- checker/tests/nullness/OverrideANNA2.java | 52 +- checker/tests/nullness/OverrideANNA3.java | 44 +- checker/tests/nullness/OverrideGenerics.java | 6 +- checker/tests/nullness/OverrideNNOE.java | 30 +- checker/tests/nullness/OverrideNNOE2.java | 36 +- .../tests/nullness/ParameterExpression.java | 333 +- checker/tests/nullness/PolyTest.java | 18 +- checker/tests/nullness/Polymorphism.java | 58 +- .../tests/nullness/PolymorphismArrays.java | 104 +- checker/tests/nullness/PostconditionBug.java | 10 +- .../nullness/PreconditionFieldNotInStore.java | 26 +- .../tests/nullness/PreventClearProperty.java | 239 +- .../tests/nullness/PrimitivesNullness.java | 6 +- checker/tests/nullness/PureTest.java | 196 +- checker/tests/nullness/RawAndPrimitive.java | 22 +- checker/tests/nullness/RawInt.java | 12 +- checker/tests/nullness/RawInt2.java | 20 +- checker/tests/nullness/RawParameter.java | 12 +- checker/tests/nullness/RawSuper.java | 134 +- .../tests/nullness/RawTypesAssignment.java | 37 +- checker/tests/nullness/RawTypesNullness.java | 108 +- checker/tests/nullness/RawTypesUses.java | 90 +- checker/tests/nullness/ReadyReadLine.java | 38 +- .../tests/nullness/ReceiverAnnotation.java | 10 +- .../tests/nullness/ReferencesDefaults.java | 26 +- checker/tests/nullness/RefineArray.java | 40 +- checker/tests/nullness/RefineOverride.java | 274 +- .../tests/nullness/RepeatEnsuresKeyFor.java | 171 +- .../RepeatEnsuresKeyForWithError.java | 167 +- .../tests/nullness/RepeatEnsuresNonNull.java | 148 +- .../RepeatEnsuresNonNullWithError.java | 156 +- .../nullness/RepeatedRequiresNonNull.java | 106 +- .../tests/nullness/RequiresNonNullTest.java | 250 +- .../tests/nullness/RequiresPrivateField.java | 10 +- checker/tests/nullness/SAMLineParser.java | 8 +- checker/tests/nullness/SamFileValidator.java | 8 +- checker/tests/nullness/ScopingConstruct.java | 728 +- checker/tests/nullness/SelfAssignment.java | 18 +- checker/tests/nullness/SelfDependentType.java | 171 +- .../tests/nullness/SequenceAndIndices.java | 8 +- checker/tests/nullness/SetIteratorTest.java | 72 +- checker/tests/nullness/SortingCollection.java | 12 +- checker/tests/nullness/StaticInLoop.java | 14 +- .../tests/nullness/StaticInitializer2.java | 10 +- checker/tests/nullness/Stats.java | 15 +- .../tests/nullness/StringTernaryConcat.java | 6 +- checker/tests/nullness/SuperCall.java | 16 +- .../tests/nullness/SuppressDeprecation.java | 24 +- .../nullness/SuppressWarningsPartialKeys.java | 222 +- .../tests/nullness/SuppressWarningsTest.java | 10 +- checker/tests/nullness/SwitchTest.java | 52 +- checker/tests/nullness/Synchronization.java | 50 +- checker/tests/nullness/TernaryNested.java | 19 +- checker/tests/nullness/TernaryNullness.java | 8 +- checker/tests/nullness/TestAssignment.java | 16 +- .../nullness/TestFromPullRequest880.java | 15 +- checker/tests/nullness/TestInfer.java | 25 +- checker/tests/nullness/TestPolyNull.java | 78 +- checker/tests/nullness/TestValOf.java | 14 +- checker/tests/nullness/ThisIsNN.java | 38 +- checker/tests/nullness/ThisQualified.java | 10 +- checker/tests/nullness/ThisTest.java | 22 +- checker/tests/nullness/ThreadLocalTest.java | 32 +- checker/tests/nullness/ThreadLocalTest2.java | 88 +- .../tests/nullness/ToArrayDiagnostics.java | 42 +- checker/tests/nullness/ToArrayNullness.java | 179 +- checker/tests/nullness/ToArrayTest.java | 27 +- checker/tests/nullness/TryWithResources.java | 67 +- .../tests/nullness/TryWithResourcesAnno.java | 28 +- .../nullness/TypeVarPrimitivesNullness.java | 44 +- checker/tests/nullness/UnannoPrimitives.java | 78 +- .../nullness/UnannoPrimitivesDefaults.java | 24 +- checker/tests/nullness/UnboxConditions.java | 46 +- checker/tests/nullness/Unboxing.java | 64 +- checker/tests/nullness/UnexpectedRaw.java | 92 +- checker/tests/nullness/UnusedNullness.java | 81 +- checker/tests/nullness/UnusedOnClass.java | 15 +- checker/tests/nullness/UtilArrays.java | 79 +- checker/tests/nullness/VarargsNullness.java | 96 +- checker/tests/nullness/VarargsNullness2.java | 22 +- checker/tests/nullness/VoidUse.java | 70 +- .../tests/nullness/WeakHasherMapNonNull.java | 27 +- .../tests/nullness/WeakHasherMapNullable.java | 25 +- checker/tests/nullness/WeakIdentityPair.java | 17 +- checker/tests/nullness/WeakRef.java | 9 +- checker/tests/nullness/WhileTest.java | 100 +- checker/tests/nullness/Widening.java | 10 +- checker/tests/nullness/WildcardGLB.java | 105 +- checker/tests/nullness/WildcardSubtype.java | 94 +- checker/tests/nullness/WildcardSubtype2.java | 87 +- checker/tests/nullness/Wildcards.java | 47 +- checker/tests/nullness/ZeroVarargs.java | 4 +- .../tests/nullness/flow/EisopIssue300.java | 30 +- .../tests/nullness/flow/EisopIssue300B.java | 26 +- .../tests/nullness/flow/EisopIssue300C.java | 36 +- .../tests/nullness/flow/EisopIssue553.java | 32 +- checker/tests/nullness/flow/Issue1214.java | 204 +- checker/tests/nullness/flow/Issue1345.java | 25 +- checker/tests/nullness/flow/Issue1727.java | 36 +- checker/tests/nullness/flow/Issue3249.java | 92 +- checker/tests/nullness/flow/Issue3267.java | 50 +- checker/tests/nullness/flow/Issue3275.java | 276 +- checker/tests/nullness/flow/Issue341.java | 20 +- checker/tests/nullness/flow/Issue818.java | 94 +- checker/tests/nullness/flow/MapGet.java | 31 +- checker/tests/nullness/flow/PathJoins.java | 22 +- checker/tests/nullness/flow/PureAndFlow.java | 86 +- checker/tests/nullness/flow/PurityError.java | 16 +- .../tests/nullness/flow/TestNullnessUtil.java | 148 +- checker/tests/nullness/flow/TestOpt.java | 106 +- .../nullness/generics/AnnotatedGenerics3.java | 64 +- .../generics/AnnotatedTypeParams.java | 24 +- .../generics/AnnotatedTypeParams2.java | 24 +- .../generics/AnnotatedTypeParams4.java | 112 +- .../nullness/generics/AnonymousClass.java | 20 +- .../generics/BoundedWildcardTest.java | 53 +- .../nullness/generics/BoxingGenerics.java | 22 +- .../nullness/generics/CapturedWildcards.java | 23 +- .../generics/CollectionsAnnotations.java | 82 +- .../generics/CollectionsAnnotationsMin.java | 82 +- .../tests/nullness/generics/GenericArgs.java | 95 +- .../tests/nullness/generics/GenericArgs2.java | 75 +- .../tests/nullness/generics/GenericArgs3.java | 113 +- .../nullness/generics/GenericReturnField.java | 12 +- .../nullness/generics/GenericTest11.java | 24 +- .../nullness/generics/GenericsBounds1.java | 26 +- .../nullness/generics/GenericsBounds2.java | 38 +- .../nullness/generics/GenericsBounds3.java | 20 +- .../nullness/generics/GenericsBounds4.java | 38 +- .../nullness/generics/GenericsBounds5.java | 82 +- .../generics/GenericsConstructor.java | 20 +- .../nullness/generics/GenericsExample.java | 324 +- .../nullness/generics/GenericsExampleMin.java | 140 +- .../tests/nullness/generics/InferMethod.java | 46 +- .../nullness/generics/InferredPrimitive.java | 6 +- checker/tests/nullness/generics/Issue134.java | 28 +- .../tests/nullness/generics/Issue1838.java | 37 +- .../tests/nullness/generics/Issue1838Min.java | 9 +- checker/tests/nullness/generics/Issue269.java | 42 +- checker/tests/nullness/generics/Issue270.java | 12 +- .../tests/nullness/generics/Issue2722.java | 18 +- checker/tests/nullness/generics/Issue282.java | 25 +- .../tests/nullness/generics/Issue282Min.java | 17 +- .../tests/nullness/generics/Issue2995.java | 10 +- .../tests/nullness/generics/Issue3025.java | 10 +- .../tests/nullness/generics/Issue3027.java | 16 +- checker/tests/nullness/generics/Issue312.java | 24 +- checker/tests/nullness/generics/Issue313.java | 8 +- checker/tests/nullness/generics/Issue319.java | 70 +- checker/tests/nullness/generics/Issue326.java | 11 +- checker/tests/nullness/generics/Issue329.java | 42 +- checker/tests/nullness/generics/Issue335.java | 18 +- checker/tests/nullness/generics/Issue337.java | 42 +- checker/tests/nullness/generics/Issue339.java | 16 +- checker/tests/nullness/generics/Issue421.java | 22 +- checker/tests/nullness/generics/Issue422.java | 8 +- checker/tests/nullness/generics/Issue428.java | 6 +- checker/tests/nullness/generics/Issue459.java | 24 +- .../tests/nullness/generics/Issue5006.java | 24 +- .../tests/nullness/generics/Issue6374.java | 44 +- .../tests/nullness/generics/Issue783a.java | 14 +- .../tests/nullness/generics/Issue783b.java | 14 +- checker/tests/nullness/generics/Issue849.java | 10 +- .../nullness/generics/KeyForPolyKeyFor.java | 29 +- checker/tests/nullness/generics/MapLoop.java | 23 +- .../nullness/generics/MethodTypeVars.java | 40 +- .../nullness/generics/MethodTypeVars2.java | 40 +- .../nullness/generics/MethodTypeVars3.java | 65 +- .../nullness/generics/MethodTypeVars5.java | 166 +- .../nullness/generics/MethodTypeVars6.java | 78 +- .../nullness/generics/MethodTypeVars7.java | 136 +- .../nullness/generics/MixTypeAndDeclAnno.java | 56 +- checker/tests/nullness/generics/MyMap.java | 23 +- .../nullness/generics/NullableGeneric.java | 52 +- .../nullness/generics/NullnessBound.java | 30 +- .../tests/nullness/generics/OptionsTest.java | 36 +- .../nullness/generics/RawTypesGenerics.java | 34 +- .../tests/nullness/generics/SourceVsJdk.java | 6 +- .../tests/nullness/generics/SuperRawness.java | 12 +- .../nullness/generics/TernaryGenerics.java | 72 +- .../tests/nullness/generics/VarArgsTest.java | 14 +- .../nullness/generics/WildcardAnnos.java | 69 +- .../generics/WildcardBoundDefault.java | 14 +- .../nullness/generics/WildcardBounds.java | 218 +- .../nullness/generics/WildcardOverride.java | 25 +- .../generics/WildcardOverrideMore.java | 62 +- .../nullness/generics/WildcardSubtyping.java | 75 +- .../nullness/generics/WildcardSubtyping2.java | 36 +- .../generics/WildcardSubtypingTypeArray.java | 11 +- .../nullness/generics/WildcardSuper.java | 50 +- .../tests/nullness/java-unsound/Figure1.java | 28 +- .../tests/nullness/java-unsound/Figure3.java | 60 +- .../nullness/java-unsound/Figure3NC.java | 50 +- .../tests/nullness/java-unsound/Figure4.java | 24 +- .../tests/nullness/java-unsound/Figure6.java | 38 +- .../nullness/java-unsound/Figure6NC.java | 36 +- .../tests/nullness/java-unsound/Figure7.java | 40 +- checker/tests/nullness/java17/Greeting.java | 35 +- .../nullness/java17/InferSwitchExpr.java | 59 +- .../java17/InstanceOfPatternVariable.java | 13 +- checker/tests/nullness/java17/Issue5047.java | 23 +- checker/tests/nullness/java17/Issue5967.java | 33 +- .../nullness/java17/NullnessInstanceOf.java | 70 +- .../nullness/java17/NullnessSwitchArrows.java | 128 +- .../NullnessSwitchExpressionLambda.java | 27 +- .../java17/NullnessSwitchExpressions.java | 108 +- .../java17/NullnessSwitchStatementRules.java | 66 +- .../java17/SwitchExpressionInvariant.java | 39 +- .../SwitchExpressionTypeArgInference.java | 58 +- .../nullness/java17/SwitchTestIssue5412.java | 214 +- checker/tests/nullness/java21/FlowSwitch.java | 160 +- checker/tests/nullness/java21/Issue6290.java | 10 +- .../tests/nullness/java21/NullRedundant.java | 174 +- .../java21/NullableSwitchSelector.java | 56 +- .../nullness/java21/SimpleCaseGuard.java | 30 +- .../tests/nullness/java8/DefaultMethods.java | 20 +- checker/tests/nullness/java8/Issue1000.java | 19 +- .../tests/nullness/java8/Issue1046Java8.java | 43 +- checker/tests/nullness/java8/Issue1098.java | 14 +- .../tests/nullness/java8/Issue1098NoJdk.java | 14 +- checker/tests/nullness/java8/Issue1633.java | 223 +- checker/tests/nullness/java8/Issue363.java | 14 +- checker/tests/nullness/java8/Issue366.java | 15 +- checker/tests/nullness/java8/Issue448.java | 8 +- checker/tests/nullness/java8/Issue448Ext.java | 20 +- checker/tests/nullness/java8/Issue496.java | 20 +- checker/tests/nullness/java8/Issue529.java | 24 +- checker/tests/nullness/java8/Issue557.java | 101 +- checker/tests/nullness/java8/Issue579.java | 28 +- checker/tests/nullness/java8/Issue596.java | 21 +- checker/tests/nullness/java8/Issue704.java | 2 +- checker/tests/nullness/java8/Issue720.java | 8 +- .../tests/nullness/java8/UnionTypeBug.java | 35 +- .../tests/nullness/java8/lambda/Dataflow.java | 22 +- .../java8/lambda/FinalLocalVariables.java | 160 +- .../nullness/java8/lambda/Issue1864.java | 14 +- .../nullness/java8/lambda/Issue1864b.java | 14 +- .../nullness/java8/lambda/Issue1897.java | 10 +- .../nullness/java8/lambda/Issue3217.java | 25 +- .../tests/nullness/java8/lambda/Issue367.java | 6 +- .../tests/nullness/java8/lambda/Issue403.java | 10 +- .../tests/nullness/java8/lambda/Issue436.java | 24 +- .../tests/nullness/java8/lambda/Issue572.java | 17 +- .../tests/nullness/java8/lambda/Issue870.java | 18 +- .../java8/lambda/Issue953bLambda.java | 29 +- .../nullness/java8/lambda/LambdaNullness.java | 180 +- .../nullness/java8/lambda/Parameters.java | 92 +- .../java8/lambda/ParametersInBody.java | 72 +- .../lambda/ParametersInBodyGenerics.java | 67 +- .../java8/lambda/RefinedLocalInLambda.java | 43 +- .../tests/nullness/java8/lambda/Returns.java | 48 +- .../tests/nullness/java8/lambda/Shadowed.java | 28 +- .../nullness/java8/lambda/TypeVarAssign.java | 10 +- .../methodref/AssignmentContextTest.java | 60 +- .../methodref/ClassTypeArgInference.java | 54 +- .../java8/methodref/FromByteCode.java | 10 +- .../java8/methodref/GroundTargetTypeLub.java | 22 +- .../java8/methodref/MemberReferences.java | 64 +- .../java8/methodref/PolyNullness.java | 34 +- .../java8/methodref/Postconditions.java | 32 +- .../java8/methodref/ReceiversMethodref.java | 122 +- .../nullness/java8/methodref/TestGenFunc.java | 74 +- .../java8inference/FalsePositives.java | 73 +- .../nullness/java8inference/InLambda.java | 42 +- .../java8inference/InLambdaAnnotated.java | 48 +- .../nullness/java8inference/Inference.java | 34 +- .../java8inference/InferenceSimpler.java | 18 +- .../nullness/java8inference/Issue1032.java | 37 +- .../nullness/java8inference/Issue1084.java | 22 +- .../nullness/java8inference/Issue1366.java | 12 +- .../nullness/java8inference/Issue1464.java | 22 +- .../nullness/java8inference/Issue1630.java | 41 +- .../nullness/java8inference/Issue1818.java | 11 +- .../nullness/java8inference/Issue1954.java | 33 +- .../nullness/java8inference/Issue2235.java | 34 +- .../nullness/java8inference/Issue2719.java | 23 +- .../nullness/java8inference/Issue402.java | 37 +- .../nullness/java8inference/Issue4048.java | 19 +- .../nullness/java8inference/Issue887.java | 21 +- .../java8inference/Issue953bInference.java | 31 +- .../nullness/java8inference/Issue980.java | 21 +- .../java8inference/NullnessBeamCrash3.java | 37 +- .../tests/nullness/java8inference/OneOf.java | 22 +- .../nullness/java8inference/SimpleLambda.java | 19 +- .../jdkannotations/EisopIssue270.java | 12 +- .../jdkannotations/HashtableTest.java | 27 +- .../nullness/jdkannotations/Issue1142.java | 15 +- .../jdkannotations/Issue1402EnumName.java | 12 +- .../nullness/jdkannotations/TreeSetTest.java | 15 +- .../optional-pure-getters/PureGetterTest.java | 132 +- checker/tests/optional/Base.java | 24 +- .../tests/optional/EnsuresPresentIfTest.java | 133 +- .../optional/FilterIspresentMapGetTest.java | 6 +- checker/tests/optional/FlowSensitivity.java | 30 +- .../tests/optional/IfPresentRefinement.java | 20 +- checker/tests/optional/JdkCheck.java | 139 +- checker/tests/optional/JdkCheck11.java | 37 +- checker/tests/optional/MapNoNewNull.java | 13 +- checker/tests/optional/Marks1Partial.java | 110 +- checker/tests/optional/Marks2.java | 24 +- checker/tests/optional/Marks3a.java | 44 +- checker/tests/optional/Marks3aJdk11.java | 24 +- checker/tests/optional/Marks3b.java | 34 +- checker/tests/optional/Marks3bJdk11.java | 32 +- checker/tests/optional/Marks4.java | 98 +- checker/tests/optional/Marks5.java | 75 +- checker/tests/optional/Marks6.java | 32 +- checker/tests/optional/Marks7.java | 12 +- .../tests/optional/NestedOptionalTest.java | 38 +- checker/tests/optional/OptionalBoxed.java | 30 +- .../optional/OptionalMapMethodReference.java | 47 +- .../tests/optional/OptionalParameterTest.java | 12 +- .../tests/optional/RequiresPresentTest.java | 135 +- checker/tests/optional/SubtypeCheck.java | 39 +- .../tests/optional/java17/OptionalSwitch.java | 14 +- checker/tests/regex/AllowedTypes.java | 80 +- checker/tests/regex/AnnotatedTypeParams3.java | 77 +- checker/tests/regex/Annotation.java | 20 +- checker/tests/regex/Constructors.java | 34 +- checker/tests/regex/Continue.java | 83 +- checker/tests/regex/ForEach.java | 10 +- checker/tests/regex/GenericsBoundsRange.java | 43 +- checker/tests/regex/GenericsEnclosing.java | 28 +- checker/tests/regex/GroupCounts.java | 235 +- checker/tests/regex/IntCast.java | 6 +- checker/tests/regex/InvariantTypes.java | 203 +- checker/tests/regex/InvariantTypesAtm.java | 15 +- checker/tests/regex/Issue3267.java | 19 +- checker/tests/regex/Issue3281.java | 109 +- checker/tests/regex/Issue809.java | 10 +- checker/tests/regex/LubRegex.java | 96 +- checker/tests/regex/MatcherGroupCount.java | 78 +- checker/tests/regex/MyMatchResult.java | 82 +- checker/tests/regex/Nested.java | 12 +- checker/tests/regex/PartialRegex.java | 30 +- checker/tests/regex/RawTypeTest.java | 167 +- checker/tests/regex/RegexUtilClient.java | 128 +- checker/tests/regex/SimpleRegex.java | 273 +- checker/tests/regex/TestIsRegex.java | 213 +- checker/tests/regex/TestRegex.java | 20 +- checker/tests/regex/TypeParamSubtype.java | 35 +- checker/tests/regex/TypeVarMemberSelect.java | 18 +- checker/tests/regex/WildcardInvoke.java | 14 +- checker/tests/regex_poly/PolyRegexTests.java | 148 +- .../StringBuilderToStringPolyRegex.java | 6 +- .../BasicTest.java | 97 +- .../BasicTest.java | 49 +- .../ConnectingServerSockets.java | 77 +- .../ConnectingSockets.java | 99 +- .../CreatesMustCallForSimpler.java | 40 +- .../DifferentSWKeys.java | 30 +- .../SocketContainer.java | 43 +- .../ACOwning.java | 98 +- .../MustCallAliasExamples.java | 81 +- .../MustCallAliasPassthroughLocal.java | 29 +- .../InstanceInitializer.java | 77 +- .../SocketContainer.java | 33 +- .../StaticOwningField.java | 79 +- .../StaticOwningFieldOtherClass.java | 17 +- .../ACExceptionalExitPointTest.java | 58 +- .../resourceleak/ACMethodInvocationTest.java | 174 +- checker/tests/resourceleak/ACOwning.java | 120 +- .../resourceleak/ACRegularExitPointTest.java | 597 +- checker/tests/resourceleak/ACSocketTest.java | 845 +- .../AccumulationValueFieldTest.java | 56 +- .../resourceleak/AccumulationValueTest.java | 146 +- checker/tests/resourceleak/BindChannel.java | 54 +- .../tests/resourceleak/COAnonymousClass.java | 106 +- checker/tests/resourceleak/COInSubtype.java | 34 +- checker/tests/resourceleak/CheckFields.java | 286 +- checker/tests/resourceleak/CloseSuper.java | 51 +- .../tests/resourceleak/CloseableAndMore.java | 33 +- .../tests/resourceleak/CommonModuleCrash.java | 10 +- .../resourceleak/ConnectingServerSockets.java | 77 +- .../tests/resourceleak/ConnectingSockets.java | 101 +- .../resourceleak/ConnectingSockets2.java | 14 +- .../resourceleak/ConstructorAddsMustCall.java | 24 +- .../CreatesMustCallForIndirect.java | 104 +- .../CreatesMustCallForInnerClass.java | 44 +- .../CreatesMustCallForOverride.java | 42 +- .../CreatesMustCallForOverride2.java | 334 +- .../CreatesMustCallForRepeat.java | 110 +- .../CreatesMustCallForSimple.java | 112 +- .../CreatesMustCallForSimpler.java | 34 +- .../CreatesMustCallForTargets.java | 121 +- .../CreatesMustCallForTwoAliases.java | 86 +- .../tests/resourceleak/DifferentSWKeys.java | 32 +- checker/tests/resourceleak/DoubleIf.java | 77 +- .../tests/resourceleak/DuplicateError.java | 25 +- checker/tests/resourceleak/Enclosing.java | 28 +- checker/tests/resourceleak/EnhancedFor.java | 97 +- .../resourceleak/FileDescriptorTest.java | 91 +- checker/tests/resourceleak/FilesTest.java | 16 +- .../tests/resourceleak/GetChannelOnLocks.java | 57 +- checker/tests/resourceleak/HBaseReport1.java | 40 +- checker/tests/resourceleak/HDFSReport.java | 34 +- checker/tests/resourceleak/HDFSReport2.java | 24 +- checker/tests/resourceleak/HdfsReport3.java | 26 +- checker/tests/resourceleak/IOUtilsTest.java | 21 +- .../resourceleak/IgnoredExceptionECM.java | 18 +- checker/tests/resourceleak/IndexMode.java | 125 +- .../tests/resourceleak/InheritanceStream.java | 24 +- .../InitializationAfterSuperTest.java | 25 +- .../resourceleak/InputOutputStreams.java | 329 +- .../resourceleak/InstanceInitializer.java | 83 +- checker/tests/resourceleak/IsClosed.java | 31 +- checker/tests/resourceleak/Issue4815.java | 19 +- checker/tests/resourceleak/Issue6030.java | 35 +- checker/tests/resourceleak/JavaEETest.java | 6 +- checker/tests/resourceleak/LemmaStack.java | 49 +- checker/tests/resourceleak/LhsArrayCast.java | 10 +- .../resourceleak/LineNumberReaderTest.java | 47 +- .../tests/resourceleak/MCANotOwningField.java | 14 +- .../tests/resourceleak/MCAOwningField.java | 25 +- checker/tests/resourceleak/MCAWithThis.java | 18 +- .../ManualMustCallEmptyOnConstructor.java | 27 +- .../MultipleIdenticalReturns.java | 46 +- ...MultipleMethodParamsMustCallAliasTest.java | 127 +- .../resourceleak/MultipleOwnedResources.java | 35 +- ...ultipleOwnedResourcesOfDifferentTypes.java | 64 +- .../resourceleak/MultipleReturnStmts.java | 25 +- .../MustCallAliasDifferentMethodNames.java | 58 +- .../resourceleak/MustCallAliasExamples.java | 81 +- .../tests/resourceleak/MustCallAliasImpl.java | 25 +- .../resourceleak/MustCallAliasImplWrong1.java | 27 +- .../resourceleak/MustCallAliasImplWrong2.java | 25 +- ...allAliasInitializeWithOwningParameter.java | 35 +- .../MustCallAliasLayeredStreams.java | 29 +- .../resourceleak/MustCallAliasLocal.java | 31 +- .../MustCallAliasNoAnnoOnConstructor.java | 25 +- .../resourceleak/MustCallAliasNormalExit.java | 123 +- .../resourceleak/MustCallAliasNotThis.java | 83 +- .../MustCallAliasNullConstructor.java | 31 +- .../MustCallAliasOwningField.java | 33 +- .../MustCallAliasPassthrough.java | 9 +- .../MustCallAliasPassthroughChain.java | 87 +- .../MustCallAliasPassthroughLocal.java | 27 +- .../MustCallAliasPassthroughThis.java | 15 +- .../MustCallAliasPassthroughWrong1.java | 11 +- .../MustCallAliasPassthroughWrong2.java | 27 +- .../MustCallAliasPassthroughWrong3.java | 37 +- .../MustCallAliasPassthroughWrong4.java | 13 +- .../MustCallAliasReturnAndParamSimple.java | 101 +- .../MustCallAliasSocketException.java | 36 +- .../MustCallAliasSubstitution.java | 29 +- .../tests/resourceleak/MustCallNullStore.java | 22 +- .../resourceleak/MustCloseIntoObject.java | 8 +- .../NonFinalFieldOnlyOverwrittenIfNull.java | 119 +- .../NonFinalFieldOnlyOverwrittenIfNull2.java | 89 +- .../tests/resourceleak/OptionalSocket.java | 23 +- .../OwnershipTransferAtReassignment.java | 40 +- .../resourceleak/OwnershipWithExceptions.java | 437 +- ...ingAndEnsuresCalledMethodsOnException.java | 105 +- .../OwningEnsuresCalledMethods.java | 27 +- .../OwningFieldStringComparison.java | 37 +- checker/tests/resourceleak/OwningMCU.java | 10 +- .../tests/resourceleak/OwningOverride.java | 37 +- .../tests/resourceleak/OwningOverride2.java | 21 +- checker/tests/resourceleak/PaperExample.java | 22 +- checker/tests/resourceleak/PrimitiveCast.java | 49 +- .../resourceleak/ReassignmentWithMCA.java | 75 +- .../resourceleak/ReplicaInputStreams.java | 35 +- .../resourceleak/ReplicaInputStreams2.java | 37 +- .../RequiresCalledMethodsTest.java | 87 +- .../resourceleak/ReturnOwningObject.java | 26 +- checker/tests/resourceleak/RlcThisTest.java | 86 +- .../resourceleak/SSLSocketFactoryTest.java | 20 +- checker/tests/resourceleak/SelfAssign.java | 49 +- .../resourceleak/SimpleSocketExample.java | 16 +- .../tests/resourceleak/SneakyDestructor.java | 23 +- checker/tests/resourceleak/SneakyDrop.java | 86 +- .../tests/resourceleak/SocketContainer.java | 39 +- .../tests/resourceleak/SocketContainer2.java | 25 +- .../tests/resourceleak/SocketContainer3.java | 21 +- checker/tests/resourceleak/SocketField.java | 101 +- .../tests/resourceleak/SocketIntoList.java | 65 +- .../resourceleak/SocketNullOverwrite.java | 14 +- .../resourceleak/SparkSessionCFGCrash.java | 6 +- .../tests/resourceleak/StaticOwningField.java | 89 +- .../StaticOwningFieldOtherClass.java | 23 +- .../resourceleak/StringConcatenation.java | 24 +- .../tests/resourceleak/StringFromObject.java | 20 +- .../resourceleak/TernaryExpressions.java | 188 +- .../TryWithResourcesDeclaration.java | 52 +- .../resourceleak/TryWithResourcesFP.java | 34 +- .../TryWithResourcesMultiResources.java | 51 +- .../TryWithResourcesVariable.java | 177 +- .../TwoConstructorsCloseable.java | 18 +- .../tests/resourceleak/TwoOwningMCATest.java | 58 +- .../tests/resourceleak/TwoResourcesECM.java | 53 +- .../resourceleak/TwoSocketContainer.java | 51 +- .../resourceleak/TwoSocketContainerSafe.java | 59 +- .../tests/resourceleak/TypeProcessError.java | 41 +- .../tests/resourceleak/TypevarDefault.java | 42 +- checker/tests/resourceleak/TypevarSimple.java | 8 +- .../resourceleak/UnconnectedSocketAlias.java | 14 +- checker/tests/resourceleak/WrapperStream.java | 6 +- .../tests/resourceleak/WrapperStreamPoly.java | 19 +- .../ZookeeperByteBufferInputStream.java | 83 +- .../tests/resourceleak/ZookeeperReport1.java | 103 +- .../tests/resourceleak/ZookeeperReport1a.java | 103 +- .../tests/resourceleak/ZookeeperReport3.java | 105 +- .../ZookeeperReport3WithOptional.java | 71 +- .../tests/resourceleak/ZookeeperReport6.java | 16 +- .../resourceleak/ZookeeperTernaryCrash.java | 110 +- .../java17/SwitchExpressions.java | 320 +- checker/tests/signature/ArraysAsList.java | 9 +- .../signature/CanonicalNameNonEmptyTest.java | 38 +- .../signature/ClassGetNameBinaryName.java | 140 +- checker/tests/signature/Conversion.java | 196 +- checker/tests/signature/DiamondTest.java | 9 +- checker/tests/signature/FakeOverridePoly.java | 9 +- .../tests/signature/PolySignatureTest.java | 14 +- .../tests/signature/PolySignatureTest2.java | 17 +- .../tests/signature/RefinedReturnTest.java | 24 +- .../signature/SignatureConcatenation.java | 8 +- .../tests/signature/SignatureLiteralTest.java | 4 +- .../signature/SignatureTypeFactoryTest.java | 1750 +-- checker/tests/signature/StubLibraryTest.java | 14 +- .../SignednessFields.java | 10 +- .../TestUncheckedByteCode.java | 30 +- .../tests/signedness/AdditionWithChar.java | 8 +- .../tests/signedness/AnnoBeforeModifier.java | 138 +- checker/tests/signedness/Arrays.java | 6 +- .../tests/signedness/BinaryOperations.java | 212 +- checker/tests/signedness/BooleansTest.java | 30 +- checker/tests/signedness/BoxedPrimitives.java | 137 +- checker/tests/signedness/Cast.java | 26 +- checker/tests/signedness/CastedShifts.java | 740 +- checker/tests/signedness/CharCast.java | 40 +- checker/tests/signedness/CharCastedToInt.java | 10 +- checker/tests/signedness/CharComparisons.java | 48 +- .../tests/signedness/CharSignedObject.java | 6 +- checker/tests/signedness/CharToFloat.java | 20 +- .../tests/signedness/CombinationIterator.java | 24 +- checker/tests/signedness/CompareChars.java | 22 +- checker/tests/signedness/Comparisons.java | 94 +- .../CompoundAssignmentsSignedness.java | 222 +- .../CompoundAssignmentsSignedness2.java | 90 +- checker/tests/signedness/ConstantTests.java | 32 +- .../tests/signedness/DefaultsSignedness.java | 242 +- checker/tests/signedness/Desugar.java | 43 +- .../signedness/IrrelevantAnnotationsTest.java | 12 +- checker/tests/signedness/Issue2482.java | 104 +- checker/tests/signedness/Issue2483.java | 8 +- checker/tests/signedness/Issue2534.java | 32 +- checker/tests/signedness/Issue2543.java | 73 +- checker/tests/signedness/Issue3710.java | 28 +- checker/tests/signedness/Issue5256.java | 24 +- .../tests/signedness/JdkConstantsTest.java | 16 +- checker/tests/signedness/LiteralCast.java | 117 +- .../tests/signedness/LocalVarDefaults.java | 36 +- checker/tests/signedness/LowerUpperBound.java | 46 +- checker/tests/signedness/MaskedShifts.java | 490 +- checker/tests/signedness/ObjectCasts.java | 198 +- checker/tests/signedness/Operations.java | 96 +- .../signedness/PolymorphicReturnType.java | 8 +- checker/tests/signedness/PrimitiveCasts.java | 70 +- .../signedness/RestrictedPolymorphism.java | 22 +- checker/tests/signedness/ShiftAndMask.java | 8 +- .../tests/signedness/ShiftPropogation.java | 40 +- .../signedness/SignednessAnnotationError.java | 11 +- .../signedness/SignednessAssignments.java | 258 +- checker/tests/signedness/SignednessCast.java | 54 +- .../tests/signedness/SignednessEquals.java | 181 +- .../signedness/SignednessManualExample.java | 62 +- .../signedness/SignednessNumberCasts.java | 22 +- .../tests/signedness/SignednessRangeTest.java | 10 +- checker/tests/signedness/StringConcat.java | 8 +- checker/tests/signedness/TestPrintln.java | 22 +- checker/tests/signedness/ToHexString.java | 18 +- checker/tests/signedness/UnsignedConcat.java | 90 +- checker/tests/signedness/UnsignedConcat2.java | 27 +- .../signedness/UnsignedRightShiftTest.java | 84 +- checker/tests/signedness/Utils.java | 226 +- checker/tests/signedness/UtilsJava8.java | 174 +- .../tests/signedness/ValueIntegration.java | 974 +- .../tests/signedness/WideningConversion.java | 164 +- checker/tests/signedness/WideningFloat.java | 26 +- .../signedness/WideningInitialization.java | 48 +- .../tests/signedness/java17/Issue6100.java | 17 +- .../MultidimentionalArrayAnnotationTest.java | 386 +- .../NoExplicitAnnotations.java | 80 +- ...argConstructorParameterAnnotationTest.java | 28 +- .../tests/stubparser-records/PairRecord.java | 6 +- .../stubparser-records/RecordStubbed.java | 6 +- .../tests/stubparser-records/RecordUsage.java | 38 +- .../FakeOverrideRSuper.java | 36 +- .../FakeOverrideReturn.java | 104 +- .../TypeParamWithInner.java | 8 +- checker/tests/tainting/AnonymousProblem.java | 2 +- checker/tests/tainting/Buffer.java | 123 +- checker/tests/tainting/CaptureSubtype.java | 15 +- checker/tests/tainting/CaptureSubtype2.java | 33 +- checker/tests/tainting/Casts.java | 110 +- .../tests/tainting/ClassQPTypeVarTest.java | 50 +- checker/tests/tainting/EnumTypeArgs.java | 14 +- checker/tests/tainting/ExtendHasQual.java | 46 +- .../tests/tainting/ExtendsAndAnnotation.java | 18 +- checker/tests/tainting/GenericsEnclosing.java | 24 +- .../tests/tainting/HasQualParamDefaults.java | 299 +- .../HasQualifierParameterIsNonTop.java | 2 +- .../tainting/InheritQualifierParameter.java | 14 +- .../tests/tainting/InitializerDataflow.java | 24 +- .../tainting/InnerHasQualifierParameter.java | 20 +- checker/tests/tainting/Issue1111.java | 29 +- checker/tests/tainting/Issue1705.java | 29 +- checker/tests/tainting/Issue1942.java | 23 +- checker/tests/tainting/Issue2107.java | 14 +- checker/tests/tainting/Issue2156.java | 16 +- checker/tests/tainting/Issue2159.java | 40 +- checker/tests/tainting/Issue2243.java | 10 +- checker/tests/tainting/Issue2330.java | 20 +- checker/tests/tainting/Issue3033.java | 22 +- checker/tests/tainting/Issue3036Tainting.java | 65 +- checker/tests/tainting/Issue352.java | 8 +- checker/tests/tainting/Issue3561.java | 18 +- checker/tests/tainting/Issue3562.java | 6 +- checker/tests/tainting/Issue3776.java | 68 +- checker/tests/tainting/Issue4170.java | 103 +- checker/tests/tainting/Issue5435.java | 8 +- checker/tests/tainting/Issue6110.java | 29 +- checker/tests/tainting/Issue6113.java | 8 +- checker/tests/tainting/Issue6116.java | 14 +- .../tainting/LambdaParameterDefaulting.java | 55 +- .../tainting/MemberReferenceInference.java | 51 +- checker/tests/tainting/NestedAnonymous.java | 14 +- .../tests/tainting/NestedTypeConstructor.java | 6 +- checker/tests/tainting/ObjectCreation.java | 66 +- checker/tests/tainting/PolyClassDecl.java | 43 +- checker/tests/tainting/PolyConstructor.java | 28 +- checker/tests/tainting/PolyReceivers.java | 56 +- checker/tests/tainting/PolyReturn.java | 28 +- checker/tests/tainting/PolyVarArgs.java | 92 +- checker/tests/tainting/Refine.java | 42 +- checker/tests/tainting/SameTypeBounds.java | 62 +- checker/tests/tainting/SimplePrims.java | 90 +- checker/tests/tainting/SimpleTainting.java | 84 +- checker/tests/tainting/SubClassHasQP.java | 80 +- .../tests/tainting/SubtypingConstraint.java | 23 +- .../tests/tainting/TaintedIntersections.java | 80 +- .../tainting/TaintingDiamondInference.java | 17 +- checker/tests/tainting/TaintingIssue6025.java | 22 +- checker/tests/tainting/TaintingIssue6060.java | 33 +- .../tests/tainting/TaintingPolyFields.java | 75 +- .../tainting/TaintingPrimitiveTarget.java | 23 +- .../tests/tainting/TestFieldPolymorphism.java | 100 +- .../TestNoQualifierParameterConflicting.java | 10 +- checker/tests/tainting/TypeInvalid.java | 76 +- .../tests/tainting/WildcardArrayBound.java | 55 +- .../tainting/WildcardMethodArgument.java | 13 +- .../java17/TaintingBindingVariable.java | 42 +- .../tainting/withdefault/NoQualifierTest.java | 4 +- .../tainting/withdefault/WithDefault.java | 2 +- checker/tests/units/Addition.java | 774 +- checker/tests/units/BasicUnits.java | 188 +- checker/tests/units/Consistency.java | 46 +- checker/tests/units/Division.java | 246 +- checker/tests/units/Issue4549.java | 10 +- checker/tests/units/Manual.java | 10 +- checker/tests/units/Multiples.java | 366 +- checker/tests/units/PolyUnitTest.java | 22 +- checker/tests/units/SubtractionUnits.java | 830 +- checker/tests/units/TypeVarsArrays.java | 10 +- checker/tests/units/Units.java | 12 +- checker/tests/units/UnqualTest.java | 10 +- .../MethodOverrides.java | 12 +- .../MethodOverrides3.java | 15 +- .../MinLenFromPositive.java | 96 +- .../OverrideIntVal.java | 84 +- dataflow/build.gradle | 320 +- .../dataflow/analysis/AbstractAnalysis.java | 954 +- .../dataflow/analysis/AbstractValue.java | 36 +- .../dataflow/analysis/Analysis.java | 272 +- .../dataflow/analysis/AnalysisResult.java | 976 +- .../dataflow/analysis/BackwardAnalysis.java | 21 +- .../analysis/BackwardAnalysisImpl.java | 713 +- .../analysis/BackwardTransferFunction.java | 43 +- .../analysis/ConditionalTransferResult.java | 275 +- .../dataflow/analysis/ForwardAnalysis.java | 25 +- .../analysis/ForwardAnalysisImpl.java | 1005 +- .../analysis/ForwardTransferFunction.java | 21 +- .../analysis/RegularTransferResult.java | 283 +- .../dataflow/analysis/Store.java | 179 +- .../dataflow/analysis/TransferFunction.java | 2 +- .../dataflow/analysis/TransferInput.java | 526 +- .../dataflow/analysis/TransferResult.java | 247 +- .../analysis/UnusedAbstractValue.java | 16 +- .../dataflow/busyexpr/BusyExprStore.java | 245 +- .../dataflow/busyexpr/BusyExprTransfer.java | 125 +- .../dataflow/busyexpr/BusyExprValue.java | 58 +- .../dataflow/cfg/CFGProcessor.java | 308 +- .../dataflow/cfg/ControlFlowGraph.java | 849 +- .../dataflow/cfg/UnderlyingAST.java | 417 +- .../dataflow/cfg/block/Block.java | 101 +- .../dataflow/cfg/block/BlockImpl.java | 99 +- .../dataflow/cfg/block/ConditionalBlock.java | 82 +- .../cfg/block/ConditionalBlockImpl.java | 205 +- .../dataflow/cfg/block/ExceptionBlock.java | 34 +- .../cfg/block/ExceptionBlockImpl.java | 134 +- .../dataflow/cfg/block/RegularBlock.java | 20 +- .../dataflow/cfg/block/RegularBlockImpl.java | 95 +- .../cfg/block/SingleSuccessorBlock.java | 44 +- .../cfg/block/SingleSuccessorBlockImpl.java | 99 +- .../dataflow/cfg/block/SpecialBlock.java | 30 +- .../dataflow/cfg/block/SpecialBlockImpl.java | 69 +- .../dataflow/cfg/builder/CFGBuilder.java | 256 +- .../cfg/builder/CFGTranslationPhaseOne.java | 8164 +++++------ .../cfg/builder/CFGTranslationPhaseThree.java | 618 +- .../cfg/builder/CFGTranslationPhaseTwo.java | 417 +- .../dataflow/cfg/builder/ConditionalJump.java | 146 +- .../dataflow/cfg/builder/ExtendedNode.java | 158 +- .../dataflow/cfg/builder/Label.java | 42 +- .../dataflow/cfg/builder/LabelCell.java | 72 +- .../dataflow/cfg/builder/MissingEdge.java | 125 +- .../dataflow/cfg/builder/NodeHolder.java | 46 +- .../cfg/builder/NodeWithExceptionsHolder.java | 102 +- .../dataflow/cfg/builder/PhaseOneResult.java | 361 +- .../dataflow/cfg/builder/TreeInfo.java | 48 +- .../dataflow/cfg/builder/TryCatchFrame.java | 181 +- .../dataflow/cfg/builder/TryFinallyFrame.java | 39 +- .../cfg/builder/TryFinallyScopeMap.java | 72 +- .../dataflow/cfg/builder/TryFrame.java | 13 +- .../dataflow/cfg/builder/TryStack.java | 108 +- .../cfg/builder/UnconditionalJump.java | 100 +- .../cfg/node/AbstractNodeVisitor.java | 750 +- .../dataflow/cfg/node/ArrayAccessNode.java | 210 +- .../dataflow/cfg/node/ArrayCreationNode.java | 181 +- .../dataflow/cfg/node/ArrayTypeNode.java | 81 +- .../dataflow/cfg/node/AssertionErrorNode.java | 173 +- .../dataflow/cfg/node/AssignmentNode.java | 237 +- .../cfg/node/BinaryOperationNode.java | 64 +- .../dataflow/cfg/node/BitwiseAndNode.java | 74 +- .../cfg/node/BitwiseComplementNode.java | 70 +- .../dataflow/cfg/node/BitwiseOrNode.java | 74 +- .../dataflow/cfg/node/BitwiseXorNode.java | 74 +- .../dataflow/cfg/node/BooleanLiteralNode.java | 51 +- .../dataflow/cfg/node/CaseNode.java | 223 +- .../dataflow/cfg/node/CatchMarkerNode.java | 97 +- .../cfg/node/CharacterLiteralNode.java | 51 +- .../cfg/node/ClassDeclarationNode.java | 82 +- .../dataflow/cfg/node/ClassNameNode.java | 203 +- .../dataflow/cfg/node/ConditionalAndNode.java | 74 +- .../dataflow/cfg/node/ConditionalNotNode.java | 70 +- .../dataflow/cfg/node/ConditionalOrNode.java | 74 +- .../cfg/node/DeconstructorPatternNode.java | 148 +- .../dataflow/cfg/node/DoubleLiteralNode.java | 51 +- .../dataflow/cfg/node/EqualToNode.java | 74 +- .../dataflow/cfg/node/ExplicitThisNode.java | 37 +- .../cfg/node/ExpressionStatementNode.java | 76 +- .../dataflow/cfg/node/FieldAccessNode.java | 210 +- .../dataflow/cfg/node/FloatLiteralNode.java | 51 +- .../cfg/node/FloatingDivisionNode.java | 74 +- .../cfg/node/FloatingRemainderNode.java | 74 +- .../cfg/node/FunctionalInterfaceNode.java | 102 +- .../dataflow/cfg/node/GreaterThanNode.java | 74 +- .../cfg/node/GreaterThanOrEqualNode.java | 74 +- .../dataflow/cfg/node/ImplicitThisNode.java | 46 +- .../dataflow/cfg/node/InstanceOfNode.java | 309 +- .../cfg/node/IntegerDivisionNode.java | 74 +- .../dataflow/cfg/node/IntegerLiteralNode.java | 51 +- .../cfg/node/IntegerRemainderNode.java | 74 +- .../cfg/node/LambdaResultExpressionNode.java | 134 +- .../dataflow/cfg/node/LeftShiftNode.java | 74 +- .../dataflow/cfg/node/LessThanNode.java | 74 +- .../cfg/node/LessThanOrEqualNode.java | 74 +- .../dataflow/cfg/node/LocalVariableNode.java | 189 +- .../dataflow/cfg/node/LongLiteralNode.java | 51 +- .../dataflow/cfg/node/MarkerNode.java | 92 +- .../dataflow/cfg/node/MethodAccessNode.java | 181 +- .../cfg/node/MethodInvocationNode.java | 256 +- .../cfg/node/NarrowingConversionNode.java | 89 +- .../dataflow/cfg/node/Node.java | 359 +- .../dataflow/cfg/node/NodeVisitor.java | 210 +- .../dataflow/cfg/node/NotEqualNode.java | 74 +- .../dataflow/cfg/node/NullChkNode.java | 102 +- .../dataflow/cfg/node/NullLiteralNode.java | 55 +- .../cfg/node/NumericalAdditionNode.java | 74 +- .../dataflow/cfg/node/NumericalMinusNode.java | 70 +- .../cfg/node/NumericalMultiplicationNode.java | 74 +- .../dataflow/cfg/node/NumericalPlusNode.java | 70 +- .../cfg/node/NumericalSubtractionNode.java | 74 +- .../dataflow/cfg/node/ObjectCreationNode.java | 353 +- .../dataflow/cfg/node/PackageNameNode.java | 167 +- .../cfg/node/ParameterizedTypeNode.java | 74 +- .../dataflow/cfg/node/PrimitiveTypeNode.java | 81 +- .../dataflow/cfg/node/ReturnNode.java | 121 +- .../dataflow/cfg/node/ShortLiteralNode.java | 51 +- .../cfg/node/SignedRightShiftNode.java | 74 +- .../cfg/node/StringConcatenateNode.java | 74 +- .../cfg/node/StringConversionNode.java | 91 +- .../dataflow/cfg/node/StringLiteralNode.java | 67 +- .../dataflow/cfg/node/SuperNode.java | 82 +- .../cfg/node/SwitchExpressionNode.java | 175 +- .../dataflow/cfg/node/SynchronizedNode.java | 107 +- .../cfg/node/TernaryExpressionNode.java | 244 +- .../dataflow/cfg/node/ThisNode.java | 44 +- .../dataflow/cfg/node/ThrowNode.java | 83 +- .../dataflow/cfg/node/TypeCastNode.java | 90 +- .../dataflow/cfg/node/UnaryOperationNode.java | 52 +- .../cfg/node/UnsignedRightShiftNode.java | 74 +- .../dataflow/cfg/node/ValueLiteralNode.java | 92 +- .../cfg/node/VariableDeclarationNode.java | 86 +- .../cfg/node/WideningConversionNode.java | 89 +- .../playground/BusyExpressionPlayground.java | 36 +- .../ConstantPropagationPlayground.java | 36 +- .../playground/LiveVariablePlayground.java | 36 +- .../ReachingDefinitionPlayground.java | 36 +- .../cfg/visualize/AbstractCFGVisualizer.java | 835 +- .../cfg/visualize/CFGVisualizeLauncher.java | 610 +- .../cfg/visualize/CFGVisualizeOptions.java | 464 +- .../dataflow/cfg/visualize/CFGVisualizer.java | 387 +- .../cfg/visualize/DOTCFGVisualizer.java | 668 +- .../cfg/visualize/StringCFGVisualizer.java | 347 +- .../constantpropagation/Constant.java | 213 +- .../ConstantPropagationStore.java | 267 +- .../ConstantPropagationTransfer.java | 119 +- .../dataflow/expression/ArrayAccess.java | 214 +- .../dataflow/expression/ArrayCreation.java | 289 +- .../dataflow/expression/BinaryOperation.java | 437 +- .../dataflow/expression/ClassName.java | 162 +- .../dataflow/expression/FieldAccess.java | 319 +- .../dataflow/expression/FormalParameter.java | 219 +- .../dataflow/expression/JavaExpression.java | 1453 +- .../expression/JavaExpressionConverter.java | 202 +- .../expression/JavaExpressionScanner.java | 187 +- .../expression/JavaExpressionVisitor.java | 212 +- .../dataflow/expression/LocalVariable.java | 231 +- .../dataflow/expression/MethodCall.java | 324 +- .../dataflow/expression/ThisReference.java | 107 +- .../dataflow/expression/UnaryOperation.java | 261 +- .../dataflow/expression/Unknown.java | 198 +- .../dataflow/expression/ValueLiteral.java | 320 +- .../ViewpointAdaptJavaExpression.java | 161 +- .../dataflow/livevariable/LiveVarNode.java | 62 +- .../dataflow/livevariable/LiveVarStore.java | 243 +- .../livevariable/LiveVarTransfer.java | 150 +- .../reachingdef/ReachingDefinitionNode.java | 58 +- .../reachingdef/ReachingDefinitionStore.java | 201 +- .../ReachingDefinitionTransfer.java | 83 +- .../dataflow/util/NodeUtils.java | 150 +- .../dataflow/util/PurityChecker.java | 696 +- .../dataflow/util/PurityUtils.java | 240 +- .../test/java/busyexpr/BusyExpression.java | 31 +- .../java/cfgconstruction/CFGConstruction.java | 14 +- .../ConstantPropagation.java | 30 +- .../src/test/java/livevar/LiveVariable.java | 31 +- .../java/reachingdef/ReachingDefinition.java | 30 +- dataflow/tests/busyexpr/Test.java | 40 +- dataflow/tests/cfgconstruction/Test.java | 32 +- dataflow/tests/constant-propagation/Test.java | 16 +- dataflow/tests/issue3447/Test.java | 12 +- dataflow/tests/live-variable/Test.java | 30 +- dataflow/tests/reachingdef/Test.java | 24 +- docs/build.gradle | 18 +- docs/examples/BazelExample/BazelExample.java | 23 +- docs/examples/InterningExample.java | 18 +- .../InterningExampleWithWarnings.java | 18 +- docs/examples/LockExample.java | 90 +- .../example/MavenExample.java | 14 +- .../example/MavenExample.java | 25 +- docs/examples/NullnessExample.java | 39 +- .../examples/NullnessExampleWithWarnings.java | 39 +- docs/examples/NullnessReleaseTests.java | 39 +- docs/examples/errorprone/build.gradle | 72 +- .../src/main/java/com/example/Demo.java | 8 +- docs/examples/fenum-extension/FenumDemo.java | 113 +- .../fenum-extension/qual/MyFenum.java | 7 +- docs/examples/lombok/build.gradle | 26 +- .../lombok/src/main/java/lib/Foo.java | 13 +- .../lombok/src/main/java/use/User.java | 12 +- docs/examples/subtyping-extension/Demo.java | 49 +- .../subtyping-extension/qual/Encrypted.java | 7 +- .../qual/PossiblyUnencrypted.java | 5 +- .../units-extension/UnitsExtensionDemo.java | 93 +- .../units-extension/qual/Frequency.java | 7 +- .../qual/FrequencyRelations.java | 85 +- docs/examples/units-extension/qual/Hz.java | 9 +- docs/examples/units-extension/qual/kHz.java | 9 +- docs/tutorial/src/NullnessExample.java | 12 +- docs/tutorial/src/RegexExample.java | 20 +- .../src/encrypted/EncryptionDemo.java | 44 +- docs/tutorial/src/myqual/Encrypted.java | 5 +- docs/tutorial/src/myqual/PolyEncrypted.java | 3 +- .../src/myqual/PossiblyUnencrypted.java | 5 +- .../service/PersonalBlogService.java | 302 +- .../struts/action/ReadAction.java | 138 +- framework-test/build.gradle | 56 +- .../test/AinferGeneratePerDirectoryTest.java | 51 +- .../test/AinferValidatePerDirectoryTest.java | 288 +- .../CheckerFrameworkPerDirectoryTest.java | 271 +- .../test/CheckerFrameworkPerFileTest.java | 129 +- .../test/CheckerFrameworkRootedTest.java | 44 +- .../CheckerFrameworkWPIPerDirectoryTest.java | 178 +- .../framework/test/CompilationResult.java | 103 +- .../test/ImmutableTestConfiguration.java | 195 +- .../framework/test/PerDirectorySuite.java | 280 +- .../framework/test/PerFileSuite.java | 348 +- .../framework/test/RootedSuite.java | 50 +- .../framework/test/SimpleOptionMap.java | 249 +- .../framework/test/TestConfiguration.java | 141 +- .../test/TestConfigurationBuilder.java | 1184 +- .../framework/test/TestRootDirectory.java | 12 +- .../framework/test/TestUtilities.java | 1048 +- .../framework/test/TypecheckExecutor.java | 231 +- .../framework/test/TypecheckResult.java | 274 +- .../diagnostics/DetailedTestDiagnostic.java | 228 +- .../test/diagnostics/DiagnosticKind.java | 57 +- .../diagnostics/JavaDiagnosticReader.java | 441 +- .../test/diagnostics/TestDiagnostic.java | 429 +- .../test/diagnostics/TestDiagnosticLine.java | 67 +- .../test/diagnostics/TestDiagnosticUtils.java | 805 +- .../checkerframework/taglet/ManualTaglet.java | 178 +- .../checkerframework/taglet/ManualTaglet.java | 192 +- .../junit/AlternateTestRootPerDirTest.java | 95 +- .../AlternateTestRootPerFileWithDirsTest.java | 93 +- ...AlternateTestRootPerFileWithFilesTest.java | 81 +- .../tests-alt/alt-dir-a/Issue6125A.java | 4 +- .../tests-alt/alt-dir-b/Issue6125B.java | 4 +- framework/build.gradle | 420 +- .../jtreg/DOTCFGVisualizerForVarargsTest.java | 16 +- .../AnnotationFileParserEnumTest.java | 69 +- .../checker/qual/NotInPackageTop.java | 5 +- .../jtreg/variablenamedefault/lib/Test.java | 2 +- .../variablenamedefault/use/UseTest.java | 7 +- .../accumulation/AccumulationAnalysis.java | 53 +- .../AccumulationAnnotatedTypeFactory.java | 1230 +- .../accumulation/AccumulationChecker.java | 89 +- .../accumulation/AccumulationStore.java | 40 +- .../accumulation/AccumulationTransfer.java | 214 +- .../accumulation/AccumulationValue.java | 240 +- .../accumulation/AccumulationVisitor.java | 42 +- .../AliasingAnnotatedTypeFactory.java | 185 +- .../common/aliasing/AliasingTransfer.java | 261 +- .../common/aliasing/AliasingVisitor.java | 557 +- .../basetype/BaseAnnotatedTypeFactory.java | 28 +- .../common/basetype/BaseTypeChecker.java | 1652 +-- .../common/basetype/BaseTypeValidator.java | 1329 +- .../common/basetype/BaseTypeVisitor.java | 9307 +++++++------ .../common/basetype/TypeValidator.java | 19 +- ...InitializedFieldsAnnotatedTypeFactory.java | 413 +- .../InitializedFieldsTransfer.java | 38 +- .../ClassValAnnotatedTypeFactory.java | 627 +- .../common/reflection/ClassValChecker.java | 45 +- .../common/reflection/ClassValVisitor.java | 90 +- .../reflection/DefaultReflectionResolver.java | 1166 +- .../MethodValAnnotatedTypeFactory.java | 852 +- .../common/reflection/MethodValChecker.java | 35 +- .../common/reflection/MethodValVisitor.java | 119 +- .../common/reflection/ReflectionResolver.java | 56 +- .../returnsreceiver/FluentAPIGenerator.java | 216 +- .../ReturnsReceiverAnnotatedTypeFactory.java | 124 +- .../ReturnsReceiverVisitor.java | 80 +- .../SubtypingAnnotatedTypeFactory.java | 191 +- .../SubtypingAnnotationClassLoader.java | 24 +- .../common/subtyping/SubtypingChecker.java | 74 +- .../common/util/TypeVisualizer.java | 1030 +- .../util/count/AnnotationStatistics.java | 435 +- .../common/util/count/JavaCodeStatistics.java | 277 +- .../common/util/debug/DoNothingProcessor.java | 21 +- .../common/util/debug/SignaturePrinter.java | 519 +- .../common/util/debug/TreeDebug.java | 140 +- .../common/util/debug/TreePrinter.java | 41 +- .../util/debug/TypeOutputtingChecker.java | 438 +- .../common/util/report/ReportChecker.java | 7 +- .../common/util/report/ReportVisitor.java | 485 +- .../common/value/JavaExpressionOptimizer.java | 108 +- .../common/value/RangeOrListOfValues.java | 210 +- .../common/value/ReflectiveEvaluator.java | 675 +- .../value/ValueAnnotatedTypeFactory.java | 3303 ++--- .../common/value/ValueChecker.java | 75 +- .../common/value/ValueCheckerUtils.java | 741 +- .../common/value/ValueMethodIdentifier.java | 276 +- .../common/value/ValueQualifierHierarchy.java | 1093 +- .../common/value/ValueTransfer.java | 3205 ++--- .../common/value/ValueTreeAnnotator.java | 1212 +- .../common/value/ValueTypeAnnotator.java | 315 +- .../common/value/ValueVisitor.java | 964 +- .../common/value/util/ByteMath.java | 686 +- .../common/value/util/DoubleMath.java | 542 +- .../common/value/util/FloatMath.java | 542 +- .../common/value/util/IntegerMath.java | 686 +- .../common/value/util/LongMath.java | 686 +- .../common/value/util/NumberMath.java | 118 +- .../common/value/util/NumberUtils.java | 264 +- .../common/value/util/Range.java | 2307 ++-- .../common/value/util/ShortMath.java | 686 +- .../AnnotationConverter.java | 347 +- .../SceneToStubWriter.java | 1618 +-- .../WholeProgramInference.java | 458 +- .../WholeProgramInferenceImplementation.java | 1996 +-- ...holeProgramInferenceJavaParserStorage.java | 3444 ++--- .../WholeProgramInferenceScenesStorage.java | 1923 +-- .../WholeProgramInferenceStorage.java | 448 +- .../scenelib/ASceneWrapper.java | 407 +- .../ajava/AnnotationEqualityVisitor.java | 131 +- .../framework/ajava/AnnotationFileStore.java | 102 +- ...ationMirrorToAnnotationExprConversion.java | 396 +- .../ajava/AnnotationTransferVisitor.java | 103 +- .../framework/ajava/DefaultJointVisitor.java | 364 +- .../ajava/DoubleJavaParserVisitor.java | 1598 +-- .../framework/ajava/ExpectedTreesVisitor.java | 626 +- .../ajava/InsertAjavaAnnotations.java | 1032 +- .../ajava/JointJavacJavaParserVisitor.java | 4410 +++--- .../ajava/JointVisitorWithDefaultAction.java | 886 +- .../ajava/TreeScannerWithDefaults.java | 843 +- .../framework/ajava/TypeAnnotationMover.java | 355 +- .../framework/flow/CFAbstractAnalysis.java | 417 +- .../framework/flow/CFAbstractStore.java | 2461 ++-- .../framework/flow/CFAbstractTransfer.java | 2488 ++-- .../framework/flow/CFAbstractValue.java | 1527 +- .../framework/flow/CFAnalysis.java | 49 +- .../framework/flow/CFCFGBuilder.java | 364 +- .../framework/flow/CFStore.java | 22 +- .../framework/flow/CFTransfer.java | 6 +- .../framework/flow/CFTreeBuilder.java | 336 +- .../framework/flow/CFValue.java | 29 +- .../framework/source/AggregateChecker.java | 298 +- .../framework/source/DiagMessage.java | 221 +- .../framework/source/SourceChecker.java | 6232 ++++----- .../framework/source/SourceVisitor.java | 205 +- .../source/SupportedLintOptions.java | 2 +- .../framework/source/SupportedOptions.java | 2 +- .../source/SuppressWarningsPrefix.java | 16 +- .../framework/stub/AddAnnotatedFor.java | 446 +- .../stub/AnnotationFileElementTypes.java | 1906 +-- .../framework/stub/AnnotationFileParser.java | 6396 ++++----- .../stub/AnnotationFileResource.java | 16 +- .../framework/stub/AnnotationFileUtil.java | 837 +- .../stub/FileAnnotationFileResource.java | 38 +- .../stub/JarEntryAnnotationFileResource.java | 45 +- .../framework/stub/JavaStubifier.java | 395 +- .../stub/RemoveAnnotationsForInference.java | 999 +- .../framework/stub/StubGenerator.java | 864 +- .../framework/stub/ToIndexFileConverter.java | 1197 +- .../type/AbstractViewpointAdapter.java | 915 +- .../framework/type/AnnotatedTypeCopier.java | 614 +- .../AnnotatedTypeCopierWithReplacement.java | 131 +- .../framework/type/AnnotatedTypeFactory.java | 11498 ++++++++-------- .../type/AnnotatedTypeFormatter.java | 40 +- .../framework/type/AnnotatedTypeMirror.java | 4593 +++--- .../type/AnnotatedTypeParameterBounds.java | 91 +- .../framework/type/AnnotatedTypeReplacer.java | 192 +- .../framework/type/AnnotationClassLoader.java | 1499 +- .../framework/type/AsSuperVisitor.java | 1657 +-- .../framework/type/BoundsInitializer.java | 465 +- .../type/DeclarationsIntoElements.java | 88 +- .../type/DefaultAnnotatedTypeFormatter.java | 861 +- .../type/DefaultInferredTypesApplier.java | 253 +- .../framework/type/DefaultTypeHierarchy.java | 2488 ++-- .../type/ElementAnnotationApplier.java | 352 +- .../type/ElementQualifierHierarchy.java | 423 +- .../framework/type/EqualityAtmComparer.java | 99 +- .../type/GenericAnnotatedTypeFactory.java | 6048 ++++---- .../framework/type/HashcodeAtmVisitor.java | 39 +- .../MostlyNoElementQualifierHierarchy.java | 226 +- .../type/NoElementQualifierHierarchy.java | 399 +- .../framework/type/QualifierHierarchy.java | 1538 ++- .../framework/type/QualifierUpperBounds.java | 272 +- .../type/StructuralEqualityComparer.java | 643 +- .../type/StructuralEqualityVisitHistory.java | 145 +- .../SubtypeIsSubsetQualifierHierarchy.java | 200 +- .../SubtypeIsSupersetQualifierHierarchy.java | 200 +- .../framework/type/SubtypeVisitHistory.java | 136 +- .../framework/type/SupertypeFinder.java | 742 +- .../framework/type/SyntheticArrays.java | 66 +- .../framework/type/TypeFromClassVisitor.java | 18 +- .../type/TypeFromExpressionVisitor.java | 683 +- .../framework/type/TypeFromMemberVisitor.java | 315 +- .../framework/type/TypeFromTree.java | 219 +- .../framework/type/TypeFromTreeVisitor.java | 23 +- .../type/TypeFromTypeTreeVisitor.java | 625 +- .../framework/type/TypeHierarchy.java | 312 +- .../type/TypeVariableSubstitutor.java | 287 +- .../framework/type/TypesIntoElements.java | 868 +- .../framework/type/ViewpointAdapter.java | 107 +- .../poly/AbstractQualifierPolymorphism.java | 1017 +- .../poly/DefaultQualifierPolymorphism.java | 118 +- .../type/poly/QualifierPolymorphism.java | 86 +- .../treeannotator/DebugListTreeAnnotator.java | 80 +- .../type/treeannotator/ListTreeAnnotator.java | 74 +- .../treeannotator/LiteralTreeAnnotator.java | 446 +- .../PropagationTreeAnnotator.java | 637 +- .../type/treeannotator/TreeAnnotator.java | 109 +- .../DefaultForTypeAnnotator.java | 587 +- .../DefaultQualifierForUseTypeAnnotator.java | 310 +- .../IrrelevantTypeAnnotator.java | 122 +- .../type/typeannotator/ListTypeAnnotator.java | 83 +- .../PropagationTypeAnnotator.java | 363 +- .../type/typeannotator/TypeAnnotator.java | 49 +- .../type/visitor/AbstractAtmComboVisitor.java | 1203 +- .../type/visitor/AnnotatedTypeCombiner.java | 113 +- .../type/visitor/AnnotatedTypeScanner.java | 475 +- .../type/visitor/AnnotatedTypeVisitor.java | 206 +- .../type/visitor/AtmComboVisitor.java | 478 +- .../visitor/DoubleAnnotatedTypeScanner.java | 342 +- .../visitor/EquivalentAtmComboScanner.java | 368 +- .../visitor/SimpleAnnotatedTypeScanner.java | 359 +- .../visitor/SimpleAnnotatedTypeVisitor.java | 182 +- .../framework/util/AnnotatedTypes.java | 3105 +++-- .../framework/util/AnnotationFormatter.java | 42 +- .../framework/util/AtmCombo.java | 1286 +- .../framework/util/AtmLubVisitor.java | 780 +- .../framework/util/CheckerMain.java | 1849 +-- .../framework/util/Contract.java | 519 +- .../framework/util/ContractsFromMethod.java | 70 +- .../util/DefaultAnnotationFormatter.java | 106 +- .../util/DefaultContractsFromMethod.java | 545 +- .../util/DefaultQualifierKindHierarchy.java | 1501 +- .../framework/util/ExecUtil.java | 128 +- .../framework/util/FieldInvariants.java | 238 +- .../framework/util/Heuristics.java | 380 +- .../util/JavaExpressionParseUtil.java | 2147 +-- .../framework/util/JavaParserUtil.java | 705 +- .../framework/util/NoContractsFromMethod.java | 95 +- .../framework/util/OptionConfiguration.java | 211 +- .../util/PurityAnnotatedTypeFactory.java | 23 +- .../framework/util/PurityChecker.java | 6 +- .../framework/util/QualifierKind.java | 195 +- .../util/QualifierKindHierarchy.java | 130 +- .../util/StringToJavaExpression.java | 595 +- .../framework/util/TreePathCacher.java | 190 +- .../framework/util/TypeArgumentMapper.java | 519 +- .../util/VoidVisitorWithDefaultAction.java | 1200 +- .../framework/util/defaults/Default.java | 120 +- .../framework/util/defaults/DefaultSet.java | 21 +- .../util/defaults/QualifierDefaults.java | 2480 ++-- .../dependenttypes/DependentTypesError.java | 220 +- .../dependenttypes/DependentTypesHelper.java | 2344 ++-- .../DependentTypesTreeAnnotator.java | 100 +- .../util/element/ClassTypeParamApplier.java | 212 +- .../util/element/ElementAnnotationUtil.java | 1096 +- .../IndexedElementAnnotationApplier.java | 91 +- .../framework/util/element/MethodApplier.java | 449 +- .../util/element/MethodTypeParamApplier.java | 266 +- .../framework/util/element/ParamApplier.java | 536 +- .../util/element/SuperTypeApplier.java | 265 +- .../TargetedElementAnnotationApplier.java | 357 +- .../util/element/TypeDeclarationApplier.java | 267 +- .../TypeParamElementAnnotationApplier.java | 407 +- .../util/element/TypeVarUseApplier.java | 592 +- .../util/element/VariableApplier.java | 232 +- .../DefaultTypeArgumentInference.java | 457 +- .../util/typeinference8/InferenceResult.java | 294 +- .../InvocationTypeInference.java | 1096 +- .../typeinference8/TypeArgumentInference.java | 29 +- .../util/typeinference8/bound/BoundSet.java | 677 +- .../typeinference8/bound/CaptureBound.java | 390 +- .../constraint/AdditionalArgument.java | 81 +- .../CheckedExceptionConstraint.java | 136 +- .../typeinference8/constraint/Constraint.java | 102 +- .../constraint/ConstraintSet.java | 597 +- .../typeinference8/constraint/Expression.java | 820 +- .../constraint/QualifierTyping.java | 220 +- .../constraint/ReductionResult.java | 82 +- .../constraint/TypeConstraint.java | 325 +- .../typeinference8/constraint/Typing.java | 847 +- .../types/AbstractQualifier.java | 271 +- .../typeinference8/types/AbstractType.java | 1233 +- .../AnnotatedContainsInferenceVariable.java | 245 +- .../typeinference8/types/CaptureVariable.java | 86 +- .../types/ContainsInferenceVariable.java | 297 +- .../typeinference8/types/Dependencies.java | 138 +- .../types/InferenceFactory.java | 2150 +-- .../typeinference8/types/InferenceType.java | 602 +- .../typeinference8/types/InvocationType.java | 442 +- .../util/typeinference8/types/ProperType.java | 496 +- .../util/typeinference8/types/Qualifier.java | 67 +- .../typeinference8/types/QualifierVar.java | 330 +- .../typeinference8/types/UseOfVariable.java | 451 +- .../util/typeinference8/types/Variable.java | 388 +- .../typeinference8/types/VariableBounds.java | 1252 +- .../util/CheckedExceptionsUtil.java | 511 +- .../util/FalseBoundException.java | 22 +- .../util/Java8InferenceContext.java | 276 +- .../util/typeinference8/util/Resolution.java | 744 +- .../util/typeinference8/util/Theta.java | 98 +- .../AbstractTypeInformationPresenter.java | 566 +- .../LspTypeInformationPresenter.java | 370 +- .../visualize/TypeInformationPresenter.java | 14 +- .../util/visualize/TypeOccurrenceKind.java | 34 +- .../util/visualize/TypeOccurrenceRange.java | 76 +- .../AccumulationNoReturnsReceiverTest.java | 35 +- .../test/junit/AccumulationTest.java | 39 +- .../framework/test/junit/AggregateTest.java | 25 +- .../framework/test/junit/AliasingTest.java | 25 +- .../test/junit/AnnotatedForTest.java | 45 +- .../test/junit/AnnotationBuilderTest.java | 607 +- .../framework/test/junit/ClassValTest.java | 25 +- .../test/junit/CompoundCheckerTest.java | 25 +- .../test/junit/DefaultingLowerBoundTest.java | 25 +- .../test/junit/DefaultingUpperBoundTest.java | 25 +- .../test/junit/FlowExpressionCheckerTest.java | 25 +- .../framework/test/junit/FlowTest.java | 25 +- .../test/junit/FrameworkJavacErrorsTest.java | 19 +- .../framework/test/junit/FrameworkTest.java | 19 +- .../framework/test/junit/H1H2CheckerTest.java | 35 +- .../test/junit/InitializedFieldsTest.java | 29 +- .../junit/InitializedFieldsValueTest.java | 43 +- .../framework/test/junit/LubGlbTest.java | 25 +- .../framework/test/junit/MethodValTest.java | 28 +- .../test/junit/NonTopDefaultTest.java | 25 +- .../test/junit/PuritySuggestionsTest.java | 35 +- .../framework/test/junit/RangeTest.java | 1349 +- .../framework/test/junit/ReflectionTest.java | 59 +- .../test/junit/ReportModifiersTest.java | 33 +- .../framework/test/junit/ReportTest.java | 33 +- .../test/junit/ReportTreeKindsTest.java | 33 +- .../junit/ReturnsReceiverAutoValueTest.java | 44 +- .../test/junit/ReturnsReceiverLombokTest.java | 43 +- .../test/junit/ReturnsReceiverTest.java | 33 +- .../test/junit/SubtypingEncryptedTest.java | 33 +- .../SubtypingStringPatternsFullTest.java | 33 +- .../test/junit/SupportedQualsTest.java | 25 +- .../framework/test/junit/TreeParserTest.java | 159 +- .../test/junit/TypeDeclBoundsTest.java | 19 +- .../test/junit/TypeDeclDefaultTest.java | 33 +- .../junit/ValueIgnoreRangeOverflowTest.java | 39 +- .../ValueNonNullStringsConcatenationTest.java | 39 +- .../framework/test/junit/ValueTest.java | 40 +- .../junit/ValueUncheckedDefaultsTest.java | 43 +- .../test/junit/VariableNameDefaultTest.java | 25 +- .../test/junit/ViewpointTestCheckerTest.java | 25 +- .../aggregate/AggregateOfCompoundChecker.java | 13 +- .../aggregate/TestAggregateChecker.java | 13 +- .../compound/AnotherCompoundChecker.java | 47 +- ...erCompoundCheckerAnnotatedTypeFactory.java | 79 +- .../testchecker/compound/CompoundChecker.java | 39 +- .../CompoundCheckerAnnotatedTypeFactory.java | 75 +- .../testchecker/compound/qual/ACCBottom.java | 5 +- .../testchecker/compound/qual/ACCTop.java | 5 +- .../testchecker/compound/qual/CCBottom.java | 5 +- .../testchecker/compound/qual/CCTop.java | 5 +- ...aultingLowerBoundAnnotatedTypeFactory.java | 27 +- ...aultingUpperBoundAnnotatedTypeFactory.java | 31 +- .../defaulting/LowerBoundQual.java | 55 +- .../defaulting/UpperBoundQual.java | 57 +- .../FlowExpressionAnnotatedTypeFactory.java | 186 +- .../flowexpression/qual/FEBottom.java | 3 +- .../flowexpression/qual/FETop.java | 5 +- .../flowexpression/qual/FlowExp.java | 9 +- .../h1h2checker/H1H2AnnotatedTypeFactory.java | 77 +- .../testchecker/h1h2checker/H1H2Visitor.java | 58 +- .../testchecker/h1h2checker/quals/H1Bot.java | 7 +- .../h1h2checker/quals/H1Invalid.java | 3 +- .../testchecker/h1h2checker/quals/H1Poly.java | 3 +- .../testchecker/h1h2checker/quals/H1S1.java | 3 +- .../testchecker/h1h2checker/quals/H1S2.java | 3 +- .../testchecker/h1h2checker/quals/H1Top.java | 5 +- .../testchecker/h1h2checker/quals/H2Bot.java | 7 +- .../h1h2checker/quals/H2OnlyOnLB.java | 7 +- .../testchecker/h1h2checker/quals/H2Poly.java | 3 +- .../testchecker/h1h2checker/quals/H2S1.java | 17 +- .../testchecker/h1h2checker/quals/H2S2.java | 3 +- .../testchecker/h1h2checker/quals/H2Top.java | 5 +- .../testchecker/lib/Issue3105Fields.java | 10 +- .../testchecker/lib/UncheckedByteCode.java | 54 +- .../testchecker/lib/VarArgMethods.java | 42 +- .../lubglb/LubGlbAnnotatedTypeFactory.java | 41 +- .../testchecker/lubglb/LubGlbChecker.java | 117 +- .../testchecker/lubglb/quals/LubglbA.java | 5 +- .../testchecker/lubglb/quals/LubglbB.java | 3 +- .../testchecker/lubglb/quals/LubglbC.java | 3 +- .../testchecker/lubglb/quals/LubglbD.java | 3 +- .../testchecker/lubglb/quals/LubglbE.java | 3 +- .../testchecker/lubglb/quals/LubglbF.java | 7 +- .../testchecker/lubglb/quals/PolyLubglb.java | 3 +- .../NTDAnnotatedTypeFactory.java | 25 +- .../testchecker/nontopdefault/NTDVisitor.java | 35 +- .../nontopdefault/qual/NTDBottom.java | 9 +- .../nontopdefault/qual/NTDMiddle.java | 5 +- .../nontopdefault/qual/NTDSide.java | 3 +- .../nontopdefault/qual/NTDTop.java | 13 +- .../ReflectionTestAnnotatedTypeFactory.java | 27 +- .../reflection/ReflectionTestChecker.java | 8 +- .../reflection/ReflectionTestVisitor.java | 16 +- .../reflection/qual/PolyTestReflect.java | 3 +- .../reflection/qual/TestReflectBottom.java | 5 +- .../reflection/qual/TestReflectSibling1.java | 3 +- .../reflection/qual/TestReflectSibling2.java | 3 +- .../reflection/qual/TestReflectTop.java | 5 +- .../supportedquals/SupportedQualsChecker.java | 47 +- .../supportedquals/qual/BottomQualifier.java | 5 +- .../supportedquals/qual/Qualifier.java | 5 +- .../TestAccumulationAnnotatedTypeFactory.java | 114 +- ...NoReturnsReceiverAnnotatedTypeFactory.java | 18 +- ...tAccumulationNoReturnsReceiverChecker.java | 21 +- ...AccumulationNoReturnsReceiverTransfer.java | 16 +- .../TestAccumulationTransfer.java | 38 +- .../qual/PolyTestAccumulation.java | 3 +- .../qual/TestAccumulation.java | 17 +- .../qual/TestAccumulationBottom.java | 7 +- .../qual/TestAccumulationPredicate.java | 17 +- .../TypeDeclBoundsAnnotatedTypeFactory.java | 25 +- .../typedeclbounds/TypeDeclBoundsVisitor.java | 14 +- .../typedeclbounds/quals/Bottom.java | 7 +- .../testchecker/typedeclbounds/quals/S1.java | 7 +- .../testchecker/typedeclbounds/quals/S2.java | 9 +- .../testchecker/typedeclbounds/quals/Top.java | 5 +- .../TypeDeclDefaultAnnotatedTypeFactory.java | 35 +- .../quals/PolyTypeDeclDefault.java | 3 +- .../quals/TypeDeclDefaultBottom.java | 9 +- .../quals/TypeDeclDefaultMiddle.java | 3 +- .../quals/TypeDeclDefaultTop.java | 7 +- .../testchecker/util/AnnoWithStringArg.java | 7 +- .../framework/testchecker/util/Critical.java | 5 +- .../framework/testchecker/util/Encrypted.java | 5 +- .../testchecker/util/EnsuresOdd.java | 7 +- .../testchecker/util/EnsuresOddIf.java | 9 +- .../framework/testchecker/util/Even.java | 5 +- .../testchecker/util/EvenOddChecker.java | 111 +- .../testchecker/util/FactoryTestChecker.java | 379 +- .../util/FlowTestAnnotatedTypeFactory.java | 198 +- .../testchecker/util/MonotonicOdd.java | 7 +- .../framework/testchecker/util/Odd.java | 3 +- .../framework/testchecker/util/PatternA.java | 5 +- .../framework/testchecker/util/PatternAB.java | 5 +- .../framework/testchecker/util/PatternAC.java | 5 +- .../framework/testchecker/util/PatternB.java | 5 +- .../framework/testchecker/util/PatternBC.java | 5 +- .../testchecker/util/PatternBottomFull.java | 5 +- .../framework/testchecker/util/PatternC.java | 5 +- .../testchecker/util/PatternUnknown.java | 5 +- .../testchecker/util/PolyEncrypted.java | 3 +- .../testchecker/util/RequiresOdd.java | 5 +- .../framework/testchecker/util/SubQual.java | 3 +- .../framework/testchecker/util/SuperQual.java | 5 +- .../testchecker/util/ValueTypeAnno.java | 7 +- ...riableNameDefaultAnnotatedTypeFactory.java | 35 +- .../quals/PolyVariableNameDefault.java | 3 +- .../quals/VariableNameDefaultBottom.java | 5 +- .../quals/VariableNameDefaultMiddle.java | 5 +- .../quals/VariableNameDefaultTop.java | 5 +- .../ViewpointTestAnnotatedTypeFactory.java | 84 +- .../ViewpointTestQualifierHierarchy.java | 51 +- .../ViewpointTestViewpointAdapter.java | 71 +- .../viewpointtest/ViewpointTestVisitor.java | 31 +- .../src/test/java/viewpointtest/quals/A.java | 3 +- .../src/test/java/viewpointtest/quals/B.java | 3 +- .../test/java/viewpointtest/quals/Bottom.java | 7 +- .../test/java/viewpointtest/quals/Lost.java | 3 +- .../test/java/viewpointtest/quals/PolyVP.java | 3 +- .../quals/ReceiverDependentQual.java | 3 +- .../test/java/viewpointtest/quals/Top.java | 5 +- .../android/support/annotation/IntRange.java | 4 +- framework/tests/Issue6060.java | 12 +- .../tests/accumulation-norr/SimpleFluent.java | 223 +- framework/tests/accumulation/Generics.java | 63 +- framework/tests/accumulation/Not.java | 112 +- .../ObjectConstructionIssue20.java | 44 +- framework/tests/accumulation/Parens.java | 6 +- framework/tests/accumulation/Predicates.java | 1116 +- .../tests/accumulation/SimpleFluent.java | 197 +- .../tests/accumulation/SimpleInference.java | 66 +- .../accumulation/SimpleInferenceMerge.java | 52 +- .../tests/accumulation/SimplePolymorphic.java | 22 +- .../tests/accumulation/SmallPredicate.java | 18 +- framework/tests/accumulation/Subtyping.java | 122 +- .../accumulation/UnparsablePredicate.java | 62 +- .../tests/accumulation/UseSimpleFluent.java | 8 +- framework/tests/accumulation/Xor.java | 96 +- .../tests/aggregate/AggregateBasicTest.java | 18 +- framework/tests/aggregate/JavaErrorTest.java | 20 +- framework/tests/aggregate/MultiError.java | 24 +- .../aliasing/AliasingConstructorTest.java | 30 +- .../tests/aliasing/ArrayInitializerTest.java | 20 +- framework/tests/aliasing/CatchTest.java | 20 +- framework/tests/aliasing/EnumTest.java | 4 +- .../aliasing/ExplicitAnnotationTest.java | 16 +- .../tests/aliasing/ForbiddenUniqueTest.java | 27 +- .../tests/aliasing/ReceiverParameterTest.java | 110 +- framework/tests/aliasing/SuperTest.java | 24 +- framework/tests/aliasing/ThrowTest.java | 16 +- framework/tests/aliasing/TypeRefinement.java | 106 +- framework/tests/aliasing/UniqueAnnoTest.java | 70 +- .../tests/aliasing/UniqueConstructorTest.java | 24 +- framework/tests/all-systems/Annotations.java | 18 +- .../all-systems/AnonymousAndInnerClass.java | 92 +- .../tests/all-systems/AnonymousClasses.java | 38 +- .../all-systems/AnonymousFieldAccess.java | 16 +- .../tests/all-systems/ArrayComparator.java | 8 +- framework/tests/all-systems/Arrays.java | 38 +- .../tests/all-systems/AsSuperCrashes.java | 139 +- .../all-systems/AssertWithSideEffect.java | 8 +- .../tests/all-systems/AssignmentContext.java | 36 +- .../tests/all-systems/BeamCrash2Full.java | 70 +- .../tests/all-systems/BigBinaryTrees.java | 523 +- framework/tests/all-systems/BigString.java | 504 +- .../all-systems/CaptureConversionCrash.java | 40 +- .../all-systems/CaptureMethodTypeArgs.java | 16 +- framework/tests/all-systems/Catch.java | 20 +- .../all-systems/ComplexPatternEscape.java | 10 +- .../all-systems/CompoundAssignments.java | 32 +- .../all-systems/ConditionalExpressions.java | 69 +- framework/tests/all-systems/CrazyEnum.java | 26 +- framework/tests/all-systems/DeepEquals.java | 18 +- .../tests/all-systems/DiamondLambda.java | 6 +- .../tests/all-systems/EisopIssue612.java | 14 +- .../tests/all-systems/EisopIssue612Min.java | 12 +- framework/tests/all-systems/EnumSwitch.java | 28 +- framework/tests/all-systems/Enums.java | 58 +- .../tests/all-systems/EqualityTests.java | 38 +- .../tests/all-systems/FieldAccessTest.java | 54 +- .../tests/all-systems/FieldWithInit.java | 8 +- framework/tests/all-systems/ForEach.java | 60 +- .../tests/all-systems/GenericCrazyBounds.java | 90 +- .../all-systems/GenericExtendsTypeVars.java | 30 +- framework/tests/all-systems/GenericNull.java | 24 +- .../tests/all-systems/GenericTest11full.java | 31 +- .../tests/all-systems/GenericTest12.java | 10 +- .../tests/all-systems/GenericTest12b.java | 30 +- .../tests/all-systems/GenericTest13.java | 18 +- .../tests/all-systems/GenericsBounds.java | 8 +- .../tests/all-systems/GenericsBounds2.java | 6 +- .../tests/all-systems/GenericsCasts.java | 76 +- .../tests/all-systems/GenericsEnclosing.java | 22 +- framework/tests/all-systems/GetClassTest.java | 42 +- framework/tests/all-systems/HiveCrash2.java | 16 +- .../all-systems/InferAndIntersection.java | 8 +- .../tests/all-systems/InferAndWildcards.java | 12 +- .../tests/all-systems/InferNullType.java | 52 +- .../tests/all-systems/InferTypeArgs.java | 28 +- .../tests/all-systems/InferTypeArgs2.java | 20 +- .../tests/all-systems/InferTypeArgs3.java | 24 +- .../InferTypeArgsConditionalExpression.java | 10 +- .../all-systems/InitializationVisitor.java | 26 +- framework/tests/all-systems/InstanceOf.java | 34 +- .../tests/all-systems/IntersectionTypes.java | 10 +- framework/tests/all-systems/IsSubarrayEq.java | 19 +- framework/tests/all-systems/Issue1003.java | 24 +- framework/tests/all-systems/Issue1006.java | 16 +- framework/tests/all-systems/Issue1039.java | 6 +- framework/tests/all-systems/Issue1043.java | 16 +- framework/tests/all-systems/Issue1049.java | 14 +- framework/tests/all-systems/Issue1102.java | 15 +- framework/tests/all-systems/Issue1111.java | 10 +- .../tests/all-systems/Issue116Graph.java | 2 +- framework/tests/all-systems/Issue1274.java | 22 +- framework/tests/all-systems/Issue1431.java | 14 +- framework/tests/all-systems/Issue1442.java | 44 +- framework/tests/all-systems/Issue1506.java | 14 +- framework/tests/all-systems/Issue1520.java | 44 +- framework/tests/all-systems/Issue1526.java | 20 +- framework/tests/all-systems/Issue1543.java | 14 +- framework/tests/all-systems/Issue1546.java | 18 +- framework/tests/all-systems/Issue1586.java | 26 +- framework/tests/all-systems/Issue1587.java | 32 +- framework/tests/all-systems/Issue1587b.java | 38 +- framework/tests/all-systems/Issue1690.java | 10 +- framework/tests/all-systems/Issue1696.java | 4 +- framework/tests/all-systems/Issue1697.java | 38 +- framework/tests/all-systems/Issue1698.java | 12 +- framework/tests/all-systems/Issue1708.java | 42 +- framework/tests/all-systems/Issue1709.java | 6 +- framework/tests/all-systems/Issue1738.java | 38 +- framework/tests/all-systems/Issue1749.java | 14 +- framework/tests/all-systems/Issue1809.java | 36 +- framework/tests/all-systems/Issue1865.java | 22 +- framework/tests/all-systems/Issue1867.java | 18 +- framework/tests/all-systems/Issue1920.java | 32 +- framework/tests/all-systems/Issue1948.java | 265 +- framework/tests/all-systems/Issue1991.java | 12 +- .../tests/all-systems/Issue1991Full.java | 22 +- framework/tests/all-systems/Issue1992.java | 28 +- framework/tests/all-systems/Issue2048.java | 12 +- framework/tests/all-systems/Issue2082.java | 4 +- framework/tests/all-systems/Issue2088.java | 28 +- framework/tests/all-systems/Issue2190.java | 28 +- framework/tests/all-systems/Issue2195.java | 18 +- framework/tests/all-systems/Issue2196.java | 18 +- framework/tests/all-systems/Issue2198.java | 18 +- framework/tests/all-systems/Issue2199.java | 16 +- framework/tests/all-systems/Issue2234.java | 8 +- framework/tests/all-systems/Issue2302.java | 32 +- framework/tests/all-systems/Issue2370.java | 34 +- framework/tests/all-systems/Issue2371.java | 8 +- framework/tests/all-systems/Issue2446.java | 14 +- framework/tests/all-systems/Issue2480.java | 8 +- framework/tests/all-systems/Issue263.java | 32 +- framework/tests/all-systems/Issue2678.java | 8 +- framework/tests/all-systems/Issue2717.java | 30 +- framework/tests/all-systems/Issue2739.java | 12 +- framework/tests/all-systems/Issue2779.java | 34 +- framework/tests/all-systems/Issue2781.java | 24 +- framework/tests/all-systems/Issue301.java | 2916 ++-- framework/tests/all-systems/Issue3021.java | 8 +- framework/tests/all-systems/Issue3055.java | 18 +- framework/tests/all-systems/Issue3120.java | 28 +- framework/tests/all-systems/Issue3128.java | 10 +- framework/tests/all-systems/Issue3232.java | 10 +- framework/tests/all-systems/Issue3277.java | 18 +- framework/tests/all-systems/Issue3295.java | 18 +- framework/tests/all-systems/Issue3302.java | 8 +- framework/tests/all-systems/Issue3377.java | 16 +- framework/tests/all-systems/Issue3569.java | 10 +- framework/tests/all-systems/Issue3570.java | 85 +- framework/tests/all-systems/Issue3598.java | 30 +- framework/tests/all-systems/Issue3785.java | 38 +- framework/tests/all-systems/Issue3791.java | 24 +- framework/tests/all-systems/Issue3826.java | 36 +- framework/tests/all-systems/Issue392.java | 6 +- framework/tests/all-systems/Issue3929.java | 12 +- framework/tests/all-systems/Issue393.java | 8 +- framework/tests/all-systems/Issue395.java | 6 +- framework/tests/all-systems/Issue396.java | 10 +- framework/tests/all-systems/Issue3994.java | 6 +- framework/tests/all-systems/Issue4083.java | 20 +- framework/tests/all-systems/Issue4115.java | 12 +- framework/tests/all-systems/Issue4170.java | 33 +- framework/tests/all-systems/Issue437.java | 30 +- framework/tests/all-systems/Issue438.java | 18 +- framework/tests/all-systems/Issue4384.java | 24 +- framework/tests/all-systems/Issue457.java | 16 +- framework/tests/all-systems/Issue478.java | 6 +- framework/tests/all-systems/Issue4829.java | 19 +- framework/tests/all-systems/Issue4849.java | 14 +- framework/tests/all-systems/Issue4852.java | 12 +- framework/tests/all-systems/Issue4853.java | 20 +- framework/tests/all-systems/Issue4876.java | 16 +- framework/tests/all-systems/Issue4877.java | 14 +- framework/tests/all-systems/Issue4878.java | 8 +- .../tests/all-systems/Issue4878Orig.java | 23 +- framework/tests/all-systems/Issue4879.java | 24 +- framework/tests/all-systems/Issue4890.java | 23 +- .../all-systems/Issue4890Interfaces.java | 42 +- framework/tests/all-systems/Issue4924.java | 18 +- framework/tests/all-systems/Issue4996.java | 24 +- framework/tests/all-systems/Issue5008.java | 12 +- framework/tests/all-systems/Issue5042.java | 101 +- framework/tests/all-systems/Issue5436.java | 28 +- framework/tests/all-systems/Issue577.java | 86 +- framework/tests/all-systems/Issue6019.java | 6 +- framework/tests/all-systems/Issue6025.java | 18 +- framework/tests/all-systems/Issue6076.java | 110 +- framework/tests/all-systems/Issue6078.java | 16 +- framework/tests/all-systems/Issue6098.java | 12 +- framework/tests/all-systems/Issue6104.java | 12 +- framework/tests/all-systems/Issue6259.java | 50 +- framework/tests/all-systems/Issue6282.java | 22 +- framework/tests/all-systems/Issue6319.java | 10 +- framework/tests/all-systems/Issue6373.java | 150 +- framework/tests/all-systems/Issue6421.java | 64 +- framework/tests/all-systems/Issue6421C.java | 14 +- framework/tests/all-systems/Issue6421D.java | 64 +- framework/tests/all-systems/Issue6433.java | 20 +- framework/tests/all-systems/Issue6438.java | 12 +- framework/tests/all-systems/Issue6442.java | 37 +- framework/tests/all-systems/Issue671.java | 14 +- framework/tests/all-systems/Issue689.java | 26 +- framework/tests/all-systems/Issue691.java | 2 +- framework/tests/all-systems/Issue692.java | 8 +- framework/tests/all-systems/Issue696.java | 2 +- framework/tests/all-systems/Issue703.java | 15 +- framework/tests/all-systems/Issue717.java | 22 +- framework/tests/all-systems/Issue738.java | 14 +- framework/tests/all-systems/Issue759.java | 38 +- framework/tests/all-systems/Issue807.java | 22 +- framework/tests/all-systems/Issue808.java | 24 +- framework/tests/all-systems/Issue810.java | 4 +- framework/tests/all-systems/Issue887.java | 12 +- framework/tests/all-systems/Issue888.java | 12 +- framework/tests/all-systems/Issue913.java | 16 +- framework/tests/all-systems/Issue953.java | 26 +- framework/tests/all-systems/Issue953b.java | 26 +- framework/tests/all-systems/Issue988.java | 10 +- framework/tests/all-systems/LambdaParam.java | 49 +- framework/tests/all-systems/LambdaParams.java | 26 +- .../tests/all-systems/LightWeightCache.java | 82 +- framework/tests/all-systems/LongDouble.java | 32 +- framework/tests/all-systems/LubRawTypes.java | 22 +- framework/tests/all-systems/MapCrashTest.java | 14 +- .../tests/all-systems/MethodTypeVars.java | 26 +- .../all-systems/MissingBoundAnnotations.java | 12 +- .../tests/all-systems/MultipleUnions.java | 54 +- framework/tests/all-systems/MyMapCrash.java | 14 +- framework/tests/all-systems/Options.java | 8 +- framework/tests/all-systems/PCGen.java | 16 +- framework/tests/all-systems/PQueue.java | 14 +- .../tests/all-systems/PlaceholderCrash1.java | 85 +- .../all-systems/PolyCollectorTypeVars.java | 36 +- framework/tests/all-systems/PrintArray.java | 22 +- framework/tests/all-systems/Quarkus1.java | 52 +- framework/tests/all-systems/Quarkus2.java | 36 +- framework/tests/all-systems/Quarkus3.java | 24 +- framework/tests/all-systems/RawSuper.java | 18 +- .../tests/all-systems/RawTypeAssignment.java | 20 +- framework/tests/all-systems/RawTypes.java | 10 +- .../tests/all-systems/ResourceVariables.java | 8 +- .../tests/all-systems/SameBoundsCrazy.java | 18 +- framework/tests/all-systems/SelfRef.java | 8 +- framework/tests/all-systems/SetOfSet.java | 12 +- framework/tests/all-systems/SimpleLog.java | 12 +- framework/tests/all-systems/StateMatch.java | 40 +- framework/tests/all-systems/SuperThis.java | 18 +- framework/tests/all-systems/Ternary.java | 108 +- framework/tests/all-systems/Throw.java | 20 +- .../TypeVarAndArrayRefinement.java | 32 +- .../TypeVarAndArrayRefinementSmall.java | 10 +- .../tests/all-systems/TypeVarInstanceOf.java | 6 +- .../tests/all-systems/TypeVarPrimitives.java | 12 +- .../tests/all-systems/TypeVarVarargs.java | 10 +- framework/tests/all-systems/TypeVars.java | 28 +- .../tests/all-systems/UncheckedCrash.java | 17 +- framework/tests/all-systems/UnionCrash.java | 34 +- framework/tests/all-systems/UnionTypes.java | 14 +- framework/tests/all-systems/Unions.java | 62 +- framework/tests/all-systems/VarKeyword.java | 18 +- framework/tests/all-systems/Viz.java | 18 +- .../tests/all-systems/WildCardCrash.java | 40 +- .../tests/all-systems/WildcardBounds.java | 16 +- .../all-systems/WildcardCharPrimitive.java | 36 +- framework/tests/all-systems/WildcardCon.java | 14 +- .../tests/all-systems/WildcardForEach.java | 12 +- .../tests/all-systems/WildcardInReturn.java | 16 +- .../tests/all-systems/WildcardIterable.java | 22 +- .../tests/all-systems/WildcardSuper.java | 18 +- .../tests/all-systems/WildcardSuper2.java | 14 +- .../all-systems/java17/BindingVariable.java | 12 +- .../tests/all-systems/java17/Issue5749.java | 12 +- .../tests/all-systems/java17/Issue5930.java | 26 +- .../tests/all-systems/java17/Issue6069.java | 24 +- .../tests/all-systems/java17/Issue6100.java | 11 +- .../all-systems/java17/SimpleRecord.java | 8 +- .../tests/all-systems/java17/SwitchTests.java | 288 +- .../tests/all-systems/java21/Issue6173.java | 30 +- .../tests/all-systems/java21/JEP440.java | 82 +- .../tests/all-systems/java21/JEP441.java | 472 +- .../all-systems/java8/DefaultMethods.java | 18 +- .../all-systems/java8/lambda/Issue1681.java | 22 +- .../all-systems/java8/lambda/Issue1817.java | 8 +- .../all-systems/java8/lambda/Issue436All.java | 24 +- .../all-systems/java8/lambda/Issue450.java | 48 +- .../all-systems/java8/lambda/Issue573.java | 10 +- .../all-systems/java8/lambda/ManyLambda.java | 138 +- .../memberref/AssignmentContextFunction.java | 26 +- .../java8/memberref/FromByteCode.java | 4 +- .../all-systems/java8/memberref/Issue871.java | 8 +- .../all-systems/java8/memberref/Issue946.java | 46 +- .../java8/memberref/MemberReferences.java | 176 +- .../all-systems/java8/memberref/Purity.java | 28 +- .../java8/memberref/Receivers.java | 56 +- .../all-systems/java8/memberref/VarArgs.java | 30 +- .../java8inference/ArrayInits.java | 6 +- .../java8inference/BeamCrash1.java | 35 +- .../java8inference/BeamCrash3.java | 29 +- .../java8inference/BeamCrash4.java | 76 +- .../all-systems/java8inference/Bug1.java | 16 +- .../all-systems/java8inference/Bug10.java | 62 +- .../all-systems/java8inference/Bug11.java | 20 +- .../all-systems/java8inference/Bug12.java | 17 +- .../all-systems/java8inference/Bug13.java | 40 +- .../all-systems/java8inference/Bug14.java | 74 +- .../all-systems/java8inference/Bug15.java | 12 +- .../all-systems/java8inference/Bug16.java | 22 +- .../all-systems/java8inference/Bug17.java | 120 +- .../all-systems/java8inference/Bug2.java | 26 +- .../all-systems/java8inference/Bug3.java | 28 +- .../all-systems/java8inference/Bug4.java | 12 +- .../all-systems/java8inference/Bug5.java | 22 +- .../all-systems/java8inference/Bug6.java | 38 +- .../all-systems/java8inference/Bug7.java | 45 +- .../all-systems/java8inference/Bug8.java | 104 +- .../all-systems/java8inference/Bug9.java | 29 +- .../java8inference/CollectorCollect.java | 153 +- .../java8inference/CollectorsToList.java | 41 +- .../CrashWithSuperWildcard.java | 74 +- .../java8inference/GuavaCrash.java | 232 +- .../all-systems/java8inference/Issue1308.java | 28 +- .../all-systems/java8inference/Issue1312.java | 10 +- .../all-systems/java8inference/Issue1313.java | 10 +- .../all-systems/java8inference/Issue1331.java | 10 +- .../all-systems/java8inference/Issue1332.java | 30 +- .../all-systems/java8inference/Issue1334.java | 6 +- .../all-systems/java8inference/Issue1377.java | 18 +- .../all-systems/java8inference/Issue1379.java | 14 +- .../all-systems/java8inference/Issue1397.java | 18 +- .../all-systems/java8inference/Issue1398.java | 34 +- .../all-systems/java8inference/Issue1399.java | 22 +- .../all-systems/java8inference/Issue1407.java | 12 +- .../all-systems/java8inference/Issue1408.java | 14 +- .../all-systems/java8inference/Issue1415.java | 30 +- .../all-systems/java8inference/Issue1416.java | 8 +- .../all-systems/java8inference/Issue1417.java | 20 +- .../all-systems/java8inference/Issue1419.java | 14 +- .../all-systems/java8inference/Issue1424.java | 26 +- .../all-systems/java8inference/Issue1715.java | 32 +- .../all-systems/java8inference/Issue1775.java | 18 +- .../all-systems/java8inference/Issue1815.java | 16 +- .../all-systems/java8inference/Issue2975.java | 20 +- .../all-systems/java8inference/Issue3032.java | 59 +- .../all-systems/java8inference/Issue3036.java | 62 +- .../all-systems/java8inference/Issue404.java | 6 +- .../all-systems/java8inference/Issue6046.java | 34 +- .../all-systems/java8inference/Issue6346.java | 116 +- .../java8inference/Issue953Inference.java | 16 +- .../java8inference/IteratorInference.java | 16 +- .../java8inference/MapEntryGetFails.java | 8 +- .../java8inference/MemRefInfere.java | 24 +- .../all-systems/java8inference/Misc.java | 26 +- .../java8inference/PrimitiveTarget.java | 6 +- .../java8inference/TooManyConstraints.java | 30 +- .../UncheckedConversionInference.java | 86 +- .../annotationclassloader/LoaderTest.java | 10 +- framework/tests/classval/ClassNameTest.java | 34 +- .../tests/classval/ClassValInferenceTest.java | 103 +- .../tests/classval/ClassValSubtypingTest.java | 198 +- framework/tests/classval/GLBTest.java | 38 +- .../compound-checker/CompoundBasicTest.java | 18 +- .../tests/compound-checker/MultiError.java | 12 +- .../javac-error/JavaErrorTest.java | 20 +- .../annotatedfor/AnnotatedForTest.java | 426 +- .../lowerbound/LowerBoundDefaulting.java | 78 +- .../upperbound/UpperBoundDefaulting.java | 61 +- framework/tests/flow/AnnotationAliasing.java | 76 +- framework/tests/flow/ArrayFlow.java | 30 +- framework/tests/flow/Basic.java | 70 +- framework/tests/flow/Basic2.java | 435 +- framework/tests/flow/ContractsOverriding.java | 340 +- .../flow/ContractsOverridingSubtyping.java | 108 +- .../tests/flow/CustomContractWithArgs.java | 122 +- framework/tests/flow/Equal.java | 64 +- framework/tests/flow/FieldShadowing.java | 66 +- framework/tests/flow/Fields.java | 24 +- framework/tests/flow/Issue4449.java | 48 +- framework/tests/flow/Issue951.java | 220 +- framework/tests/flow/MetaPostcondition.java | 330 +- framework/tests/flow/MetaPrecondition.java | 130 +- framework/tests/flow/MethodCallFlowExpr.java | 380 +- framework/tests/flow/Monotonic.java | 72 +- framework/tests/flow/MoreFields.java | 8 +- framework/tests/flow/NonMethodCode.java | 48 +- framework/tests/flow/ParamFlowExpr.java | 40 +- framework/tests/flow/Postcondition.java | 582 +- framework/tests/flow/Precondition.java | 302 +- framework/tests/flow/Purity.java | 480 +- framework/tests/flow/StorePure.java | 260 +- framework/tests/flow/Termination.java | 20 +- framework/tests/flow/Values.java | 103 +- .../flow/flowexpression-scope/Class1.java | 2 +- .../flow/flowexpression-scope/Class2.java | 71 +- .../flow/flowexpression-scope/Issue862.java | 16 +- .../flowexpression/ArrayCreationParsing.java | 36 +- .../flowexpression/BinaryOperations.java | 6 +- .../flowexpression/Canonicalization.java | 54 +- .../flowexpression/CharAndDoubleParsing.java | 12 +- .../tests/flowexpression/ClassLiterals.java | 40 +- framework/tests/flowexpression/Complex.java | 45 +- .../tests/flowexpression/Constructor.java | 36 +- framework/tests/flowexpression/Fields.java | 23 +- .../tests/flowexpression/InnerClasses.java | 46 +- framework/tests/flowexpression/Issue1609.java | 404 +- .../tests/flowexpression/LambdaParameter.java | 117 +- framework/tests/flowexpression/Private.java | 11 +- framework/tests/flowexpression/SimpleVPA.java | 22 +- .../tests/flowexpression/Standardize.java | 174 +- .../tests/flowexpression/TestParsing.java | 10 +- .../flowexpression/ThisStaticContext.java | 63 +- framework/tests/flowexpression/ThisSuper.java | 52 +- .../tests/flowexpression/UnaryOperations.java | 8 +- .../tests/flowexpression/Unparsable.java | 16 +- .../flowexpression/UnsupportJavaCode.java | 12 +- .../tests/flowexpression/UsePrivate.java | 12 +- .../tests/flowexpression/ValueLiterals.java | 10 +- .../flowexpression/ViewPointAdaptMethods.java | 56 +- .../flowexpression/ViewpointAdaptation.java | 50 +- .../flowexpression/ViewpointAdaptation2.java | 60 +- .../framework-javac-errors/Issue346.java | 4 +- .../MissingSymbolCrash.java | 8 +- .../framework-javac-errors/ResolveError.java | 8 +- .../UnimportedExtends2.java | 4 +- .../tests/framework/AnnotatedAnnotation.java | 75 +- .../tests/framework/AnnotatedGenerics.java | 246 +- .../framework/AnnotationWithComponents.java | 10 +- .../tests/framework/AnonymousClasses.java | 26 +- framework/tests/framework/ArraySubtyping.java | 40 +- framework/tests/framework/Arrays.java | 207 +- framework/tests/framework/Assignments.java | 96 +- .../tests/framework/AssignmentsGeneric.java | 73 +- framework/tests/framework/BridgeMethods.java | 32 +- .../tests/framework/ClassAnnotations.java | 8 +- framework/tests/framework/Compound.java | 16 +- framework/tests/framework/Constructors.java | 80 +- framework/tests/framework/DeepOverride.java | 22 +- .../tests/framework/DeepOverrideAbstract.java | 30 +- .../tests/framework/DeepOverrideBug.java | 36 +- .../framework/DeepOverrideInterface.java | 30 +- framework/tests/framework/ExtendsDefault.java | 28 +- framework/tests/framework/GenericAlias.java | 28 +- .../tests/framework/GenericAliasInvalid.java | 14 +- .../framework/GenericAliasInvalidCall.java | 22 +- framework/tests/framework/GenericEnum.java | 6 +- framework/tests/framework/GenericTest1.java | 22 +- framework/tests/framework/GenericTest10.java | 30 +- framework/tests/framework/GenericTest11.java | 24 +- framework/tests/framework/GenericTest12.java | 10 +- framework/tests/framework/GenericTest2.java | 14 +- framework/tests/framework/GenericTest3.java | 14 +- framework/tests/framework/GenericTest4.java | 95 +- framework/tests/framework/GenericTest5.java | 30 +- framework/tests/framework/GenericTest6.java | 38 +- framework/tests/framework/GenericTest7.java | 92 +- framework/tests/framework/GenericTest8.java | 18 +- framework/tests/framework/GenericTest9.java | 134 +- .../tests/framework/GetReceiverLoop.java | 30 +- framework/tests/framework/InnerGenerics.java | 46 +- framework/tests/framework/MatrixBug.java | 2 +- .../framework/MethodOverrideBadParam.java | 10 +- .../framework/MethodOverrideBadReceiver.java | 12 +- .../framework/MethodOverrideBadReturn.java | 12 +- .../tests/framework/MethodOverrides.java | 122 +- framework/tests/framework/MoreVarargs.java | 22 +- .../tests/framework/MultiBoundTypeVar.java | 16 +- framework/tests/framework/OverrideCrash.java | 8 +- .../tests/framework/PrimitiveDotClass.java | 10 +- framework/tests/framework/RandomTests.java | 16 +- framework/tests/framework/RecursiveDef.java | 8 +- framework/tests/framework/Supertypes.java | 137 +- framework/tests/framework/SymbolError.java | 6 +- framework/tests/framework/TypeInference.java | 49 +- framework/tests/framework/Unboxing.java | 20 +- framework/tests/framework/Varargs.java | 62 +- framework/tests/framework/WildcardSuper.java | 11 +- framework/tests/framework/Wildcards.java | 13 +- .../tests/h1h2checker/AnonymousClasses.java | 21 +- framework/tests/h1h2checker/Catch.java | 64 +- .../h1h2checker/CompoundStringAssignment.java | 78 +- framework/tests/h1h2checker/Constructors.java | 104 +- framework/tests/h1h2checker/Defaulting.java | 286 +- .../h1h2checker/EnforceTargetLocation.java | 39 +- framework/tests/h1h2checker/ForEach.java | 136 +- framework/tests/h1h2checker/Generics.java | 50 +- .../tests/h1h2checker/GetClassStubTest.java | 22 +- .../tests/h1h2checker/IncompatibleBounds.java | 68 +- .../h1h2checker/InferTypeArgsPolyChecker.java | 339 +- framework/tests/h1h2checker/Inheritance.java | 16 +- .../tests/h1h2checker/Issue2163Final.java | 52 +- framework/tests/h1h2checker/Issue2186.java | 29 +- framework/tests/h1h2checker/Issue2264.java | 20 +- framework/tests/h1h2checker/Issue282.java | 166 +- framework/tests/h1h2checker/Issue681.java | 16 +- framework/tests/h1h2checker/Issue798.java | 22 +- framework/tests/h1h2checker/Issue849.java | 10 +- framework/tests/h1h2checker/Primitive.java | 24 +- .../tests/h1h2checker/TypeRefinement.java | 24 +- .../tests/h1h2checker/WildcardBounds.java | 209 +- .../h1h2checker/pkg1/PackageDefaulting.java | 24 +- .../tests/h1h2checker/pkg1/package-info.java | 12 +- .../pkg1/pkg2/PackageDefaulting.java | 22 +- .../ClassInitializer.java | 26 +- .../ClassInitializer2.java | 20 +- .../ClassInitializer2a.java | 16 +- .../ClassInitializer3.java | 26 +- .../DeclaredType.java | 5 +- .../ManualConstructor.java | 42 +- .../PrimitiveField.java | 96 +- .../ConstructorPostcondition.java | 26 +- .../EnsuresInitializedFieldsTest.java | 126 +- .../HelperMethodInitializesFields.java | 356 +- .../initialized-fields/SimpleConstructor.java | 36 +- framework/tests/lubglb/Dummy.java | 4 +- framework/tests/lubglb/IntersectionTypes.java | 42 +- .../tests/lubglb/Issue2432Constructor.java | 167 +- framework/tests/methodval/MethodNameTest.java | 56 +- .../methodval/MethodValInferenceTest.java | 191 +- .../tests/methodval/MethodValLUBTest.java | 165 +- .../methodval/MethodValSubtypingTest.java | 106 +- framework/tests/nontopdefault/NTDTest.java | 82 +- .../tests/nontopdefault/TestCasting.java | 26 +- .../PuritySuggestionsClass.java | 314 +- .../tests/reflection/AnonymousClassTest.java | 213 +- framework/tests/reflection/MethodTest.java | 817 +- .../reflection/ReflectionConstructorTest.java | 105 +- framework/tests/report/Accesses.java | 106 +- framework/tests/report/CallOverrides.java | 42 +- framework/tests/report/Creation.java | 48 +- framework/tests/report/Inherit.java | 14 +- framework/tests/report/Interface.java | 68 +- framework/tests/report/Overrides.java | 36 +- framework/tests/report/Package.java | 68 +- framework/tests/report/TestStub.java | 12 +- .../tests/reportmodifiers/TestModifiers.java | 10 +- .../tests/reporttreekinds/TestTreeKinds.java | 10 +- .../tests/returnsreceiver/GenericReturn.java | 42 +- .../tests/returnsreceiver/MethodRef.java | 32 +- .../returnsreceiver/NullsAndGenerics.java | 42 +- .../tests/returnsreceiver/OverrideTest.java | 54 +- .../tests/returnsreceiver/SimpleTest.java | 96 +- .../tests/returnsreceiver/SubtypingTest.java | 14 +- .../returnsreceiverautovalue/Animal.java | 99 +- .../BuilderMethodRef.java | 32 +- .../returnsreceiverlombok/BuilderTest.java | 49 +- .../StringPatternsUsage.java | 140 +- .../tests/subtyping/InvariantArrays.java | 87 +- framework/tests/subtyping/Poly.java | 159 +- framework/tests/subtyping/Simple.java | 45 +- framework/tests/subtyping/ThisType.java | 22 +- framework/tests/subtyping/ThrowCatch.java | 48 +- framework/tests/subtyping/UnusedTypes.java | 23 +- .../typedeclbounds/BooleanExpression.java | 94 +- .../StringConcatConversion.java | 33 +- .../typedecldefault/BoundsAndDefaults.java | 39 +- .../TestDefaultForTypeDecl.java | 24 +- .../value-ignore-range-overflow/Index117.java | 12 +- .../RefinementEq.java | 38 +- .../RefinementGT.java | 106 +- .../RefinementGTE.java | 86 +- .../RefinementLT.java | 100 +- .../RefinementLTE.java | 102 +- .../RefinementNEq.java | 42 +- .../TransferAdd.java | 102 +- .../TransferDivide.java | 106 +- .../TransferMod.java | 42 +- .../TransferSub.java | 96 +- .../TransferTimes.java | 38 +- .../ValueNoOverflow.java | 180 +- .../value-ignore-range-overflow/Widen.java | 30 +- .../Binaries.java | 639 +- .../CompoundAssignment.java | 90 +- .../StringLenConcats.java | 217 +- framework/tests/value/Alias.java | 12 +- framework/tests/value/AnnotationUse.java | 12 +- framework/tests/value/ArrayInit.java | 360 +- framework/tests/value/ArrayIntro.java | 28 +- framework/tests/value/Basics.java | 476 +- framework/tests/value/BigIntegerTest.java | 51 +- framework/tests/value/Binaries.java | 639 +- .../tests/value/BitsMethodsIntRange.java | 16 +- framework/tests/value/BitwiseAnd.java | 60 +- framework/tests/value/Boxing.java | 26 +- .../CharArrayWithNonLiteralConstants.java | 10 +- framework/tests/value/CharacterToString.java | 6 +- framework/tests/value/ClassNotFound.java | 20 +- framework/tests/value/CompoundAssignment.java | 168 +- framework/tests/value/DivideByZero.java | 120 +- framework/tests/value/DoubleRounding.java | 8 +- .../tests/value/EmptyAnnotationArgument.java | 20 +- framework/tests/value/EnclosingClass.java | 56 +- framework/tests/value/EnumConstants.java | 317 +- framework/tests/value/EnumValue.java | 114 +- framework/tests/value/ExceptionTest.java | 12 +- framework/tests/value/Fields.java | 77 +- framework/tests/value/GTETransferBug.java | 12 +- framework/tests/value/Issue1214.java | 110 +- framework/tests/value/Issue1218.java | 261 +- framework/tests/value/Issue1229.java | 18 +- framework/tests/value/Issue1423.java | 12 +- framework/tests/value/Issue1579.java | 10 +- framework/tests/value/Issue1580.java | 10 +- framework/tests/value/Issue1655.java | 10 +- framework/tests/value/Issue2353.java | 6 +- framework/tests/value/Issue2367.java | 26 +- framework/tests/value/Issue3001.java | 8 +- framework/tests/value/Issue3105.java | 32 +- .../value/Issue3105FieldInSameClass.java | 14 +- .../tests/value/Issue3105StaticImport.java | 6 +- framework/tests/value/Issue3307.java | 14 +- framework/tests/value/Issue867.java | 104 +- .../tests/value/LengthTransferForMinLen.java | 28 +- framework/tests/value/LiteralArray.java | 12 +- framework/tests/value/LongMax.java | 8 +- framework/tests/value/Loop.java | 51 +- framework/tests/value/LubToRange.java | 14 +- framework/tests/value/MLEqualTo.java | 8 +- framework/tests/value/MathMinMax.java | 50 +- framework/tests/value/Methods.java | 156 +- framework/tests/value/MinLenConstants.java | 6 +- framework/tests/value/MinLenEqTransfer.java | 44 +- framework/tests/value/MinLenFieldInvar.java | 82 +- framework/tests/value/MinLenFieldInvar2.java | 32 +- framework/tests/value/MinLenGTETransfer.java | 18 +- framework/tests/value/MinLenGTTransfer.java | 18 +- framework/tests/value/MinLenLTETransfer.java | 18 +- framework/tests/value/MinLenLTTransfer.java | 18 +- framework/tests/value/MinLenLUB.java | 66 +- framework/tests/value/MinLenNEqTransfer.java | 34 +- .../tests/value/MinLenPostcondition.java | 8 +- framework/tests/value/MinLenVarargs.java | 26 +- .../value/MultipleBinaryExpressions.java | 124 +- framework/tests/value/MyTree.java | 62 +- framework/tests/value/MyTree2.java | 35 +- framework/tests/value/NegativeArrayLen.java | 28 +- .../value/NestedArrayLengthInference.java | 14 +- framework/tests/value/Overflows.java | 78 +- framework/tests/value/Polymorphic.java | 44 +- framework/tests/value/Polymorphic2.java | 66 +- framework/tests/value/RefineBoolean.java | 64 +- .../tests/value/RefineUnknownToIntRange.java | 68 +- framework/tests/value/Refinement.java | 551 +- framework/tests/value/Refinement2.java | 222 +- framework/tests/value/RegexMatching.java | 256 +- framework/tests/value/RegexNonMatching.java | 484 +- .../value/RegexPatternSyntaxException.java | 8 +- framework/tests/value/RepeatMinLenIf.java | 94 +- .../tests/value/RepeatMinLenIfWithError.java | 98 +- framework/tests/value/Repo.java | 19 +- framework/tests/value/SplitAssignments.java | 24 +- framework/tests/value/StartsEndsWith.java | 80 +- framework/tests/value/StaticExTest.java | 70 +- .../value/StaticallyExecutableWarnings.java | 68 +- framework/tests/value/StringConcats.java | 74 +- framework/tests/value/StringLen.java | 206 +- framework/tests/value/StringLenConcats.java | 217 +- framework/tests/value/StringLenMethods.java | 86 +- framework/tests/value/StringLenWidening.java | 22 +- framework/tests/value/StringPolyValue.java | 16 +- framework/tests/value/StringSplit.java | 20 +- framework/tests/value/StringValCrash.java | 8 +- framework/tests/value/StringValNull.java | 119 +- .../value/StringValNullConcatLength.java | 24 +- framework/tests/value/StringValOfArrays.java | 10 +- framework/tests/value/Switch.java | 344 +- framework/tests/value/TooWideRange.java | 46 +- framework/tests/value/TypeCast.java | 102 +- framework/tests/value/TypeVars.java | 68 +- framework/tests/value/Unaries.java | 110 +- framework/tests/value/UncheckedMinLen.java | 20 +- framework/tests/value/Underflows.java | 66 +- framework/tests/value/ValueCast.java | 106 +- framework/tests/value/ValueCast2.java | 30 +- framework/tests/value/ValueOpt.java | 18 +- framework/tests/value/ValueWrapperCast.java | 106 +- framework/tests/value/VarArgRe.java | 36 +- framework/tests/value/WildcardIn.java | 8 +- .../tests/value/java17/MultiCaseConst.java | 68 +- .../value/java17/SwitchExpressionTyping.java | 174 +- .../java17/ValueSwitchExprNeedsDataflow.java | 118 +- .../java17/ValueSwitchStatementRules.java | 42 +- framework/tests/value/loops/DoWhile.java | 54 +- framework/tests/value/loops/NestedLoops.java | 88 +- .../tests/value/loops/OscillatingLoops.java | 102 +- .../tests/value/loops/WidenedUpperBound.java | 351 +- .../TestVariableNameDefault.java | 400 +- .../tests/viewpointtest/LostNonReflexive.java | 64 +- .../tests/viewpointtest/PolyConstructor.java | 92 +- .../tests/viewpointtest/PolyWithVPA.java | 26 +- .../viewpointtest/SuperConstructorCalls.java | 74 +- .../viewpointtest/TestGetAnnotatedLhs.java | 68 +- .../viewpointtest/ThisConstructorCalls.java | 38 +- .../tests/viewpointtest/VPAExamples.java | 56 +- .../viewpointtest/VarargsConstructor.java | 114 +- gradle-mvn-push.gradle | 42 +- javacutil/build.gradle | 78 +- .../javacutil/AbstractTypeProcessor.java | 267 +- .../javacutil/AnnotationBuilder.java | 1585 +-- .../javacutil/AnnotationMirrorMap.java | 376 +- .../javacutil/AnnotationMirrorSet.java | 686 +- .../javacutil/AnnotationProvider.java | 94 +- .../javacutil/AnnotationUtils.java | 3176 ++--- .../javacutil/BasicAnnotationProvider.java | 129 +- .../javacutil/BasicTypeProcessor.java | 54 +- .../checkerframework/javacutil/BugInCF.java | 110 +- .../javacutil/CollectionUtils.java | 407 +- .../javacutil/DeepCopyable.java | 47 +- .../javacutil/ElementUtils.java | 2098 +-- .../javacutil/InternalUtils.java | 86 +- .../org/checkerframework/javacutil/Pair.java | 234 +- .../checkerframework/javacutil/Resolver.java | 1027 +- .../javacutil/SwitchExpressionScanner.java | 287 +- .../javacutil/SystemUtil.java | 229 +- .../javacutil/TreePathUtil.java | 890 +- .../checkerframework/javacutil/TreeUtils.java | 5651 ++++---- .../javacutil/TreeUtilsAfterJava11.java | 986 +- .../javacutil/TypeAnnotationUtils.java | 1184 +- .../javacutil/TypeKindUtils.java | 515 +- .../javacutil/TypeSystemError.java | 40 +- .../javacutil/TypesUtils.java | 2764 ++-- .../checkerframework/javacutil/UserError.java | 40 +- .../javacutil/trees/DetachedVarSymbol.java | 29 +- .../javacutil/trees/TreeBuilder.java | 1307 +- .../javacutil/trees/TreeParser.java | 237 +- 3977 files changed, 267289 insertions(+), 261032 deletions(-) diff --git a/build.gradle b/build.gradle index e775d28a6ce..d51f81974ba 100644 --- a/build.gradle +++ b/build.gradle @@ -1,605 +1,609 @@ import de.undercouch.gradle.tasks.download.Download buildscript { - dependencies { - if (JavaVersion.current() >= JavaVersion.VERSION_11) { - // Code formatting; defines targets "spotlessApply" and "spotlessCheck". - // https://github.com/diffplug/spotless/tags ; see tags starting "gradle/" - // Only works on JDK 11+. - classpath 'com.diffplug.spotless:spotless-plugin-gradle:7.0.2' + dependencies { + if (JavaVersion.current() >= JavaVersion.VERSION_11) { + // Code formatting; defines targets "spotlessApply" and "spotlessCheck". + // https://github.com/diffplug/spotless/tags ; see tags starting "gradle/" + // Only works on JDK 11+. + classpath 'com.diffplug.spotless:spotless-plugin-gradle:7.0.2' + } } - } } plugins { - // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow - id 'com.github.johnrengelman.shadow' version '8.1.1' - // https://plugins.gradle.org/plugin/de.undercouch.download - id 'de.undercouch.download' version '5.6.0' - id 'java' - // https://github.com/tbroyer/gradle-errorprone-plugin - id 'net.ltgt.errorprone' version '4.1.0' - // https://docs.gradle.org/current/userguide/eclipse_plugin.html - id 'eclipse' - // To show task list as a tree, run: ./gradlew taskTree - id 'com.dorongold.task-tree' version '4.0.0' + // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow + id 'com.github.johnrengelman.shadow' version '8.1.1' + // https://plugins.gradle.org/plugin/de.undercouch.download + id 'de.undercouch.download' version '5.6.0' + id 'java' + // https://github.com/tbroyer/gradle-errorprone-plugin + id 'net.ltgt.errorprone' version '4.1.0' + // https://docs.gradle.org/current/userguide/eclipse_plugin.html + id 'eclipse' + // To show task list as a tree, run: ./gradlew taskTree + id 'com.dorongold.task-tree' version '4.0.0' } apply plugin: 'de.undercouch.download' // There is another `repositories { ... }` block below; if you change this one, change that one as well. repositories { - maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} - mavenCentral() + maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} + mavenCentral() } def majorVersionToInt(majorVersionString) { - if (majorVersionString.endsWith("-ea")) { - majorVersionString = majorVersionString.substring(0, majorVersionString.length() - 3) - } - return Integer.valueOf(majorVersionString) + if (majorVersionString.endsWith("-ea")) { + majorVersionString = majorVersionString.substring(0, majorVersionString.length() - 3) + } + return Integer.valueOf(majorVersionString) } ext { - // On a Java 8 JVM, use error-prone javac and source/target 8. - // On a Java 9+ JVM, use the host javac, default source/target, and required module flags. - isJava8 = JavaVersion.current() == JavaVersion.VERSION_1_8 - - // The int corresponding to the major version of the current JVM. - currentRuntimeJavaVersion = majorVersionToInt(JavaVersion.current().getMajorVersion()) - - // As of 2024-12-24, delombok doesn't yet support JDK 24; see https://projectlombok.org/changelog . - // Keep in sync with check in docs/examples/lombok/Makefile - skipDelombok = currentRuntimeJavaVersion >= 24 - - parentDir = file("${rootDir}/../").absolutePath - - // NO-AFU - // annotationTools = "${parentDir}/annotation-tools" - // afu = "${annotationTools}/annotation-file-utilities" - - jtregHome = "${parentDir}/jtreg" - gitScriptsHome = "${project(':checker').projectDir}/bin-devel/.git-scripts" - plumeScriptsHome = "${project(':checker').projectDir}/bin-devel/.plume-scripts" - htmlToolsHome = "${project(':checker').projectDir}/bin-devel/.html-tools" - doLikeJavacHome = "${project(':checker').projectDir}/bin/.do-like-javac" - - javadocMemberLevel = JavadocMemberLevel.PROTECTED - - // The local git repository, typically in the .git directory, but not for worktrees. - // This value is always overwritten, but Gradle needs the variable to be initialized. - localRepo = '.git' - - versions = [ - autoValue : '1.11.0', - errorprone : '2.36.0', - googleJavaFormat : '1.25.2', - hashmapUtil : '0.0.1', - junit : '4.13.2', - lombok : '1.18.36', - // plume-util includes a version of reflection-util. When updating ensure the versions are consistent. - plumeUtil : '1.9.3', - reflectionUtil : '1.1.3', - ] + // Whether to use EISOP formatting. + // Can also be achieved by having a project property with the same name. + eisopFormatting = true + + // On a Java 8 JVM, use error-prone javac and source/target 8. + // On a Java 9+ JVM, use the host javac, default source/target, and required module flags. + isJava8 = JavaVersion.current() == JavaVersion.VERSION_1_8 + + // The int corresponding to the major version of the current JVM. + currentRuntimeJavaVersion = majorVersionToInt(JavaVersion.current().getMajorVersion()) + + // As of 2024-12-24, delombok doesn't yet support JDK 24; see https://projectlombok.org/changelog . + // Keep in sync with check in docs/examples/lombok/Makefile + skipDelombok = currentRuntimeJavaVersion >= 24 + + parentDir = file("${rootDir}/../").absolutePath + + // NO-AFU + // annotationTools = "${parentDir}/annotation-tools" + // afu = "${annotationTools}/annotation-file-utilities" + + jtregHome = "${parentDir}/jtreg" + gitScriptsHome = "${project(':checker').projectDir}/bin-devel/.git-scripts" + plumeScriptsHome = "${project(':checker').projectDir}/bin-devel/.plume-scripts" + htmlToolsHome = "${project(':checker').projectDir}/bin-devel/.html-tools" + doLikeJavacHome = "${project(':checker').projectDir}/bin/.do-like-javac" + + javadocMemberLevel = JavadocMemberLevel.PROTECTED + + // The local git repository, typically in the .git directory, but not for worktrees. + // This value is always overwritten, but Gradle needs the variable to be initialized. + localRepo = '.git' + + versions = [ + autoValue : '1.11.0', + errorprone : '2.36.0', + googleJavaFormat : '1.25.2', + hashmapUtil : '0.0.1', + junit : '4.13.2', + lombok : '1.18.36', + // plume-util includes a version of reflection-util. When updating ensure the versions are consistent. + plumeUtil : '1.9.3', + reflectionUtil : '1.1.3', + ] } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } // Keep in sync with check in // framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java . switch (JavaVersion.current()) { - case JavaVersion.VERSION_1_8: - case JavaVersion.VERSION_11: - case JavaVersion.VERSION_17: - case JavaVersion.VERSION_21: - break; // Supported versions - default: - logger.info('The Checker Framework has only been tested with JDK 8, 11, 17, and 21.' + - ' You are using JDK ' + JavaVersion.current().majorVersion + '.'); - break; + case JavaVersion.VERSION_1_8: + case JavaVersion.VERSION_11: + case JavaVersion.VERSION_17: + case JavaVersion.VERSION_21: + break; // Supported versions + default: + logger.info('The Checker Framework has only been tested with JDK 8, 11, 17, and 21.' + + ' You are using JDK ' + JavaVersion.current().majorVersion + '.'); + break; } task setLocalRepo(type:Exec) { - commandLine 'git', 'worktree', 'list' - standardOutput = new ByteArrayOutputStream() - doLast { - String worktreeList = standardOutput.toString() - localRepo = worktreeList.substring(0, worktreeList.indexOf(' ')) + '/.git' - } + commandLine 'git', 'worktree', 'list' + standardOutput = new ByteArrayOutputStream() + doLast { + String worktreeList = standardOutput.toString() + localRepo = worktreeList.substring(0, worktreeList.indexOf(' ')) + '/.git' + } } // No group so it does not show up in the output of `gradlew tasks` task installGitHooks(type: Copy, dependsOn: 'setLocalRepo') { - description = 'Copies git hooks to .git directory' - from files('checker/bin-devel/git.post-merge', 'checker/bin-devel/git.pre-commit') - rename('git\\.(.*)', '$1') - into localRepo + '/hooks' + description = 'Copies git hooks to .git directory' + from files('checker/bin-devel/git.post-merge', 'checker/bin-devel/git.pre-commit') + rename('git\\.(.*)', '$1') + into localRepo + '/hooks' } if (currentRuntimeJavaVersion >= 11) { - apply plugin: 'com.diffplug.spotless' - spotless { - // Resolve the Spotless plugin dependencies from the buildscript repositories rather than the - // project repositories. That way the spotless plugin does not use the locally built version of - // checker-qual as a dependency. Without this, errors like the follow are issued when running - // a spotless task without a locally-built version of checker-qual.jar: - // Could not determine the dependencies of task ':checker-qual:spotlessCheck'. - // > Could not create task ':checker-qual:spotlessJavaCheck'. - // > Could not create task ':checker-qual:spotlessJava'. - // > File signature can only be created for existing regular files, given: - // .../checker-framework/checker-qual/build/libs/checker-qual-3.25.1-SNAPSHOT.jar - predeclareDepsFromBuildscript() - } - - spotlessPredeclare { - // Put all the formatters that have dependencies here. Without this, errors like the following - // will happen: - // Could not determine the dependencies of task ':spotlessCheck'. - // > Could not create task ':spotlessJavaCheck'. - // > Could not create task ':spotlessJava'. - // > Add a step with [com.google.googlejavaformat:google-java-format:1.15.0] into the `spotlessPredeclare` block in the root project. - java { - googleJavaFormat(versions.googleJavaFormat) + apply plugin: 'com.diffplug.spotless' + spotless { + // Resolve the Spotless plugin dependencies from the buildscript repositories rather than the + // project repositories. That way the spotless plugin does not use the locally built version of + // checker-qual as a dependency. Without this, errors like the follow are issued when running + // a spotless task without a locally-built version of checker-qual.jar: + // Could not determine the dependencies of task ':checker-qual:spotlessCheck'. + // > Could not create task ':checker-qual:spotlessJavaCheck'. + // > Could not create task ':checker-qual:spotlessJava'. + // > File signature can only be created for existing regular files, given: + // .../checker-framework/checker-qual/build/libs/checker-qual-3.25.1-SNAPSHOT.jar + predeclareDepsFromBuildscript() } - groovyGradle { - greclipse() + + spotlessPredeclare { + // Put all the formatters that have dependencies here. Without this, errors like the following + // will happen: + // Could not determine the dependencies of task ':spotlessCheck'. + // > Could not create task ':spotlessJavaCheck'. + // > Could not create task ':spotlessJava'. + // > Add a step with [com.google.googlejavaformat:google-java-format:1.15.0] into the `spotlessPredeclare` block in the root project. + java { + googleJavaFormat(versions.googleJavaFormat) + } + groovyGradle { + greclipse() + } } - } } allprojects { currentProj -> - // Increment the minor version (second number) rather than just the patch - // level (third number) if: - // * any new checkers have been added, or - // * backward-incompatible changes have been made to APIs or elsewhere. - // To make a snapshot release: ./gradlew publish - version = '3.43.0' - - tasks.withType(JavaCompile).configureEach { - options.fork = true - } - - apply plugin: 'java' - apply plugin: 'eclipse' - apply plugin: 'com.github.johnrengelman.shadow' - apply plugin: 'de.undercouch.download' - apply plugin: 'net.ltgt.errorprone' - - group = 'io.github.eisop' - - // Keep in sync with "repositories { ... }" block above. - repositories { - maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} - mavenCentral() - } - - configurations { - // This is required to run the Checker Framework on JDK 8. - javacJar + // Increment the minor version (second number) rather than just the patch + // level (third number) if: + // * any new checkers have been added, or + // * backward-incompatible changes have been made to APIs or elsewhere. + // To make a snapshot release: ./gradlew publish + version = '3.43.0' + + tasks.withType(JavaCompile).configureEach { + options.fork = true + } - // Holds the combined classpath of all subprojects including the subprojects themselves. - allProjects + apply plugin: 'java' + apply plugin: 'eclipse' + apply plugin: 'com.github.johnrengelman.shadow' + apply plugin: 'de.undercouch.download' + apply plugin: 'net.ltgt.errorprone' - // Exclude checker-qual dependency added by Error Prone to avoid a circular dependency. - annotationProcessor.exclude group:'org.checkerframework', module:'checker-qual' - } + group = 'io.github.eisop' - dependencies { - javacJar group: 'com.google.errorprone', name: 'javac', version: "9+181-r4173-1" + // Keep in sync with "repositories { ... }" block above. + repositories { + maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/'} + mavenCentral() + } - errorproneJavac("com.google.errorprone:javac:9+181-r4173-1") + configurations { + // This is required to run the Checker Framework on JDK 8. + javacJar - allProjects subprojects - } + // Holds the combined classpath of all subprojects including the subprojects themselves. + allProjects - eclipse.classpath { - defaultOutputDir = file("build/default") - file.whenMerged { cp -> - cp.entries.forEach { cpe -> - if (cpe instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder) { - cpe.output = cpe.output.replace "bin/", "build/classes/java/" - } - if (cpe instanceof org.gradle.plugins.ide.eclipse.model.Output) { - cpe.path = cpe.path.replace "bin/", "build/" - } - } + // Exclude checker-qual dependency added by Error Prone to avoid a circular dependency. + annotationProcessor.exclude group:'org.checkerframework', module:'checker-qual' } - } - - ext { - // A list of add-export and add-open arguments to be used when running the Checker Framework. - // Keep this list in sync with the lists in CheckerMain#getExecArguments, - // the sections with labels "javac-jdk11-non-modularized", "maven", and "sbt" in the manual - // and in the checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject - compilerArgsForRunningCF = [ - // These are required in Java 16+ because the --illegal-access option is set to deny - // by default. None of these packages are accessed via reflection, so the module - // only needs to be exported, but not opened. - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - // Required because the Checker Framework reflectively accesses private members in com.sun.tools.javac.comp. - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - ] - } - if (currentRuntimeJavaVersion >= 11) { - apply plugin: 'com.diffplug.spotless' - // patterns of files to skip for individual sub-projects - def perProjectDoNotFormat = [ - 'checker': [ - 'bin-devel/.git-scripts/**', - 'bin-devel/.plume-scripts/**', - 'dist/**', - 'tests/ainfer-*/annotated/*', - 'tests/build/**', - 'tests/calledmethods-delomboked/*', - 'tests/nullness-javac-errors/*', - ], - 'dataflow': ['manual/examples/'], - 'framework': [ - 'tests/build/**', - 'tests/returnsreceiverdelomboked/*', - ], - ] - spotless { - // If you add any formatters to this block that require dependencies, then you must also - // add them to spotlessPredeclare block. - - // Always skip formatting of files under build directory - def doNotFormat = ['build/**'] - if (currentProj != project.rootProject) { - if (perProjectDoNotFormat.containsKey(currentProj.name)) { - doNotFormat += perProjectDoNotFormat[currentProj.name] - } - if (currentRuntimeJavaVersion < 16) { - doNotFormat += [ - 'tests/**/java17/', - 'tests/*record*/' - ] - } - if (currentRuntimeJavaVersion < 21) { - doNotFormat += ['tests/**/java21/'] - } - } - - format 'misc', { - // define the files to apply `misc` to - target '*.md', '*.tex', '.gitignore', 'Makefile' - targetExclude doNotFormat - // define the steps to apply to those files - leadingTabsToSpaces(2) - trimTrailingWhitespace() - // endWithNewline() // Don't want to end empty files with a newline - } - - java { - if (currentProj == currentProj.rootProject) { - // format .java files outside of Gradle sub-projects - def targets = [ - 'docs/examples', - 'docs/tutorial', - ] - targets = targets.collectMany { - [ - // must call toString() to convert GString to String - "${it}/**/*.java".toString(), - // Not .ajava files because formatting would remove import statements. - ] - } - target targets - } else { - // not the root project; format all .java files in the sub-project - target '**/*.java' - } - targetExclude doNotFormat + dependencies { + javacJar group: 'com.google.errorprone', name: 'javac', version: "9+181-r4173-1" - if (project.hasProperty('eisopFormatting')) { - googleJavaFormat(versions.googleJavaFormat).aosp() - importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') - } else { - googleJavaFormat(versions.googleJavaFormat) // the formatter to apply to Java files - } - formatAnnotations().addTypeAnnotation("PolyInitialized").addTypeAnnotation("PolyVP").addTypeAnnotation("ReceiverDependentQual") - } + errorproneJavac("com.google.errorprone:javac:9+181-r4173-1") - // Only define a groovyGradle task on the root project, for simplicity in setting the target pattern - if (currentProj == currentProj.rootProject) { - groovyGradle { - target '**/*.gradle' - targetExclude doNotFormat - greclipse() // which formatter Spotless should use to format .gradle files. - if (project.hasProperty('eisopFormatting')) { - indentWithSpaces(4) - } else { - indentWithSpaces(2) - } - trimTrailingWhitespace() - // endWithNewline() // Don't want to end empty files with a newline - } - } - - // a useful task for debugging; prints exactly which files are getting formatted by spotless - tasks.register('printSpotlessTaskInputs') { - doLast { - project.tasks.forEach { task -> - if (task.name.contains('spotless')) { - println "Inputs for task '${task.name}':" + allProjects subprojects + } - task.inputs.files.each { inputFile -> - println " Input: $inputFile" - } + eclipse.classpath { + defaultOutputDir = file("build/default") + file.whenMerged { cp -> + cp.entries.forEach { cpe -> + if (cpe instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder) { + cpe.output = cpe.output.replace "bin/", "build/classes/java/" + } + if (cpe instanceof org.gradle.plugins.ide.eclipse.model.Output) { + cpe.path = cpe.path.replace "bin/", "build/" + } } - } } - } } - } - - test { - minHeapSize = "256m" // initial heap size - maxHeapSize = "4g" // maximum heap size - } - // configurations.errorprone is no longer resolvable, work around this. - def errorproneProcessorCustom = configurations.resolvable("errorproneProcessorCustom") { - extendsFrom(configurations.errorprone) - } - - // After all the tasks have been created, modify some of them. - afterEvaluate { - configurations { - checkerFatJar { - canBeConsumed = false - canBeResolved = true - } + ext { + // A list of add-export and add-open arguments to be used when running the Checker Framework. + // Keep this list in sync with the lists in CheckerMain#getExecArguments, + // the sections with labels "javac-jdk11-non-modularized", "maven", and "sbt" in the manual + // and in the checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject + compilerArgsForRunningCF = [ + // These are required in Java 16+ because the --illegal-access option is set to deny + // by default. None of these packages are accessed via reflection, so the module + // only needs to be exported, but not opened. + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + // Required because the Checker Framework reflectively accesses private members in com.sun.tools.javac.comp. + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + ] } - dependencies { - checkerFatJar(project(path: ':checker', configuration: 'fatJar')) - } + if (currentRuntimeJavaVersion >= 11) { + apply plugin: 'com.diffplug.spotless' + // patterns of files to skip for individual sub-projects + def perProjectDoNotFormat = [ + 'checker': [ + 'bin-devel/.git-scripts/**', + 'bin-devel/.plume-scripts/**', + 'dist/**', + 'tests/ainfer-*/annotated/*', + 'tests/build/**', + 'tests/calledmethods-delomboked/*', + 'tests/nullness-javac-errors/*', + ], + 'dataflow': ['manual/examples/'], + 'framework': [ + 'tests/build/**', + 'tests/returnsreceiverdelomboked/*', + ], + ] + spotless { + // If you add any formatters to this block that require dependencies, then you must also + // add them to spotlessPredeclare block. + + // Always skip formatting of files under build directory + def doNotFormat = ['build/**'] + if (currentProj != project.rootProject) { + if (perProjectDoNotFormat.containsKey(currentProj.name)) { + doNotFormat += perProjectDoNotFormat[currentProj.name] + } + if (currentRuntimeJavaVersion < 16) { + doNotFormat += [ + 'tests/**/java17/', + 'tests/*record*/' + ] + } + if (currentRuntimeJavaVersion < 21) { + doNotFormat += ['tests/**/java21/'] + } + } - // Add the fat checker.jar to the classpath of every Javadoc task. This allows Javadoc in - // any module to reference classes in any other module. - // Also, build and use ManualTaglet as a taglet. - tasks.withType(Javadoc) { - // Similar test in framework-test/build.gradle - def tagletVersion = isJava8 ? 'tagletJdk8' : 'taglet' + format 'misc', { + // define the files to apply `misc` to + target '*.md', '*.tex', '.gitignore', 'Makefile' + targetExclude doNotFormat + // define the steps to apply to those files + leadingTabsToSpaces(2) + trimTrailingWhitespace() + // endWithNewline() // Don't want to end empty files with a newline + } - dependsOn(':checker:shadowJar') - dependsOn(":framework-test:${tagletVersion}Classes") + java { + if (currentProj == currentProj.rootProject) { + // format .java files outside of Gradle sub-projects + def targets = [ + 'docs/examples', + 'docs/tutorial', + ] + targets = targets.collectMany { + [ + // must call toString() to convert GString to String + "${it}/**/*.java".toString(), + // Not .ajava files because formatting would remove import statements. + ] + } + target targets + } else { + // not the root project; format all .java files in the sub-project + target '**/*.java' + } + targetExclude doNotFormat + + if (eisopFormatting || project.hasProperty('eisopFormatting')) { + googleJavaFormat(versions.googleJavaFormat).aosp() + importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') + } else { + googleJavaFormat(versions.googleJavaFormat) // the formatter to apply to Java files + } + formatAnnotations().addTypeAnnotation("PolyInitialized").addTypeAnnotation("PolyVP").addTypeAnnotation("ReceiverDependentQual") + } - doFirst { - options.encoding = 'UTF-8' - if (!name.equals('javadocDoclintAll')) { - options.memberLevel = javadocMemberLevel - } - classpath += configurations.getByName('checkerFatJar').asFileTree - if (isJava8) { - classpath += configurations.javacJar - } - options.taglets 'org.checkerframework.taglet.ManualTaglet' - options.tagletPath(project(':framework-test').sourceSets."${tagletVersion}".output.classesDirs.getFiles() as File[]) - - // This file is looked for by Javadoc. - file("${destinationDir}/resources/fonts/").mkdirs() - ant.touch(file: "${destinationDir}/resources/fonts/dejavu.css") - - if (!isJava8) { - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', true) - options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', true) + // Only define a groovyGradle task on the root project, for simplicity in setting the target pattern + if (currentProj == currentProj.rootProject) { + groovyGradle { + target '**/*.gradle' + targetExclude doNotFormat + greclipse() // which formatter Spotless should use to format .gradle files. + if (eisopFormatting || project.hasProperty('eisopFormatting')) { + indentWithSpaces(4) + } else { + indentWithSpaces(2) + } + trimTrailingWhitespace() + // endWithNewline() // Don't want to end empty files with a newline + } + } + + // a useful task for debugging; prints exactly which files are getting formatted by spotless + tasks.register('printSpotlessTaskInputs') { + doLast { + project.tasks.forEach { task -> + if (task.name.contains('spotless')) { + println "Inputs for task '${task.name}':" + + task.inputs.files.each { inputFile -> + println " Input: $inputFile" + } + } + } + } + } } + } - // "-Xwerror" requires Javadoc everywhere. Currently, CI jobs require Javadoc only - // on changed lines. Enable -Xwerror in the future when all Javadoc exists. - // options.addBooleanOption('Xwerror', true) - options.addStringOption('Xmaxwarns', '99999') - } + test { + minHeapSize = "256m" // initial heap size + maxHeapSize = "4g" // maximum heap size } - // Add standard javac options - tasks.withType(JavaCompile) { compilationTask -> - dependsOn(':installGitHooks') - String useJdkCompilerProp = project.getProperties().get('useJdkCompiler') - int useJdkCompiler - if (useJdkCompilerProp == null) { - // If the property is not given, use the same version as the runtime. - useJdkCompiler = currentRuntimeJavaVersion - } else { - useJdkCompiler = majorVersionToInt(useJdkCompilerProp) - boolean useToolchains = (currentRuntimeJavaVersion != useJdkCompiler) - if (!isJava8 && useToolchains) { - // This uses the requested Java compiler to compile all code. - // CI test test-cftests-junit-jdk21 runs the JUnit tests on the different JDK versions, - // to ensure there is no version mismatch between compiled-against javac APIs and runtime APIs. - // https://docs.gradle.org/current/userguide/toolchains.html - // This property is final on Java 8, so don't set it then. - javaCompiler = javaToolchains.compilerFor { - languageVersion = JavaLanguageVersion.of(useJdkCompiler) - } + // configurations.errorprone is no longer resolvable, work around this. + def errorproneProcessorCustom = configurations.resolvable("errorproneProcessorCustom") { + extendsFrom(configurations.errorprone) + } + + // After all the tasks have been created, modify some of them. + afterEvaluate { + configurations { + checkerFatJar { + canBeConsumed = false + canBeResolved = true + } } - } - - // Sorting is commented out because it disables incremental compilation. - // Uncomment when needed. - // // Put source files in deterministic order, for debugging. - // compilationTask.source = compilationTask.source.sort() - - // This test is for whether the Checker Framework supports (runs under) Java 8. - // Currently, the Checker Framework does support Java 8. - if (true) { - // Using `options.release.set(8)` here leads to compilation - // errors such as "package com.sun.source.tree does not exist". - sourceCompatibility = 8 - targetCompatibility = 8 - // Because the target is 8, all of the public compiler classes are accessible, so - // --add-exports are not required (nor are they allowed with target 8). See - // https://openjdk.org/jeps/247 for details on compiling for older versions. - } else { - // This makes the class files Java 11, and then the Checker Framework would not run under Java 8. - options.release.set(11) - options.compilerArgs += [ - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - ] - // This is equivalent to writing "exports jdk.compiler/... to ALL-UNNAMED" in the - // module-info.java of jdk.compiler, so corresponding --add-opens are only required for - // reflective access to private members. - // - // From https://openjdk.org/jeps/261, Section titled: "Breaking encapsulation" - // "The effect of each instance [of --add-exports] is to add a qualified export of the - // named package from the source module to the target module. This is, essentially, a - // command-line form of an exports clause in a module declaration[...]. - // [...] - // The --add-exports option enables access to the public types of a specified package. - // It is sometimes necessary to go further and enable access to all non-public elements - // via the setAccessible method of the core reflection API. The --add-opens option can - // be used, at run time, to do this." - } - - options.failOnError = true - options.deprecation = true - // -options: To not get a warning about missing bootstrap classpath (when using Java 9 and `-source 8`). - // -fallthrough: Don't check fallthroughs. Instead, use Error Prone. Its - // warnings are suppressible with a "// fall through" comment. - // -classfile: classgraph jar file and https://bugs.openjdk.org/browse/JDK-8190452 - String lint = '-Xlint:-options,-fallthrough,-classfile' - // Java 8 uses the Error Prone javac, not what is requested with useJdkCompiler. - // So there is no need to set additional lint options. - if (!isJava8) { - if (useJdkCompiler >= 21) { - // TODO: Ignore this-escape for now, we may want to review and suppress each one later. - lint +=',-this-escape' + + dependencies { + checkerFatJar(project(path: ':checker', configuration: 'fatJar')) } - if (useJdkCompiler >= 23) { - // TODO: Ignore dangling-doc-comments for now, we may want to fix them later. - lint +=',-dangling-doc-comments' + + // Add the fat checker.jar to the classpath of every Javadoc task. This allows Javadoc in + // any module to reference classes in any other module. + // Also, build and use ManualTaglet as a taglet. + tasks.withType(Javadoc) { + // Similar test in framework-test/build.gradle + def tagletVersion = isJava8 ? 'tagletJdk8' : 'taglet' + + dependsOn(':checker:shadowJar') + dependsOn(":framework-test:${tagletVersion}Classes") + + doFirst { + options.encoding = 'UTF-8' + if (!name.equals('javadocDoclintAll')) { + options.memberLevel = javadocMemberLevel + } + classpath += configurations.getByName('checkerFatJar').asFileTree + if (isJava8) { + classpath += configurations.javacJar + } + options.taglets 'org.checkerframework.taglet.ManualTaglet' + options.tagletPath(project(':framework-test').sourceSets."${tagletVersion}".output.classesDirs.getFiles() as File[]) + + // This file is looked for by Javadoc. + file("${destinationDir}/resources/fonts/").mkdirs() + ant.touch(file: "${destinationDir}/resources/fonts/dejavu.css") + + if (!isJava8) { + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', true) + options.addBooleanOption('-add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', true) + } + + // "-Xwerror" requires Javadoc everywhere. Currently, CI jobs require Javadoc only + // on changed lines. Enable -Xwerror in the future when all Javadoc exists. + // options.addBooleanOption('Xwerror', true) + options.addStringOption('Xmaxwarns', '99999') + } } - } - - options.compilerArgs += [ - '-g', - '-Werror', - lint, - '-Xlint', - ] - - options.encoding = 'UTF-8' - options.fork = true - if (isJava8) { - options.forkOptions.jvmArgs += [ - "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() - ] - } - - // Error Prone depends on checker-qual.jar, so don't run it on that project to avoid a circular dependency. - if ((compilationTask.name.equals('compileJava') || compilationTask.name.equals('compileTestJava')) && !project.name.startsWith('checker-qual')) { - // Error Prone must be available in the annotation processor path - options.annotationProcessorPath = errorproneProcessorCustom.get() - // Enable Error Prone - options.errorprone.enabled = (useJdkCompiler >= 17) && (useJdkCompiler <= 23) - options.errorprone.disableWarningsInGeneratedCode = true - options.errorprone.errorproneArgs = [ - // Many compiler classes are interned. - '-Xep:ReferenceEquality:OFF', - // Not useful to suggest Splitter; maybe clean up. - '-Xep:StringSplitter:OFF', - // Too broad, rejects seemingly-correct code. - '-Xep:EqualsGetClass:OFF', - // Not a real problem - '-Xep:MixedMutabilityReturnType:OFF', - // Don't want to add a dependency to ErrorProne. - '-Xep:AnnotateFormatMethod:OFF', - // Warns for every use of "@checker_framework.manual" - '-Xep:InvalidBlockTag:OFF', - // Recommends writing @InlineMe which is an Error-Prone-specific annotation - '-Xep:InlineMeSuggester:OFF', - // Recommends writing @CanIgnoreReturnValue which is an Error-Prone-specific annotation. - // It would be great if Error Prone recognized the @This annotation. - '-Xep:CanIgnoreReturnValueSuggester:OFF', - // Should be turned off when using the Checker Framework. - '-Xep:ExtendsObject:OFF', - // For Visitors it is convenient to just pass a Void parameter. - '-Xep:VoidUsed:OFF', - // The Checker Framework is the only nullness tool we care about. - '-Xep:NullableWildcard:OFF', - // In the visitor pattern, it is natural to forward a Void reference. - '-Xep:VoidUsed:OFF', - // -Werror halts the build if Error Prone issues a warning, which ensures that - // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) - // stops as soon as it issues one warning, rather than outputting them all. - // https://github.com/google/error-prone/issues/436 - '-Werror', - ] - if (!isJava8) { - // Options needed for Error Prone on Java 16+, but don't hurt on Java 9+ - options.forkOptions.jvmArgs += [ - '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - '--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - ] + + // Add standard javac options + tasks.withType(JavaCompile) { compilationTask -> + dependsOn(':installGitHooks') + String useJdkCompilerProp = project.getProperties().get('useJdkCompiler') + int useJdkCompiler + if (useJdkCompilerProp == null) { + // If the property is not given, use the same version as the runtime. + useJdkCompiler = currentRuntimeJavaVersion + } else { + useJdkCompiler = majorVersionToInt(useJdkCompilerProp) + boolean useToolchains = (currentRuntimeJavaVersion != useJdkCompiler) + if (!isJava8 && useToolchains) { + // This uses the requested Java compiler to compile all code. + // CI test test-cftests-junit-jdk21 runs the JUnit tests on the different JDK versions, + // to ensure there is no version mismatch between compiled-against javac APIs and runtime APIs. + // https://docs.gradle.org/current/userguide/toolchains.html + // This property is final on Java 8, so don't set it then. + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(useJdkCompiler) + } + } + } + + // Sorting is commented out because it disables incremental compilation. + // Uncomment when needed. + // // Put source files in deterministic order, for debugging. + // compilationTask.source = compilationTask.source.sort() + + // This test is for whether the Checker Framework supports (runs under) Java 8. + // Currently, the Checker Framework does support Java 8. + if (true) { + // Using `options.release.set(8)` here leads to compilation + // errors such as "package com.sun.source.tree does not exist". + sourceCompatibility = 8 + targetCompatibility = 8 + // Because the target is 8, all of the public compiler classes are accessible, so + // --add-exports are not required (nor are they allowed with target 8). See + // https://openjdk.org/jeps/247 for details on compiling for older versions. + } else { + // This makes the class files Java 11, and then the Checker Framework would not run under Java 8. + options.release.set(11) + options.compilerArgs += [ + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports', + 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + ] + // This is equivalent to writing "exports jdk.compiler/... to ALL-UNNAMED" in the + // module-info.java of jdk.compiler, so corresponding --add-opens are only required for + // reflective access to private members. + // + // From https://openjdk.org/jeps/261, Section titled: "Breaking encapsulation" + // "The effect of each instance [of --add-exports] is to add a qualified export of the + // named package from the source module to the target module. This is, essentially, a + // command-line form of an exports clause in a module declaration[...]. + // [...] + // The --add-exports option enables access to the public types of a specified package. + // It is sometimes necessary to go further and enable access to all non-public elements + // via the setAccessible method of the core reflection API. The --add-opens option can + // be used, at run time, to do this." + } + + options.failOnError = true + options.deprecation = true + // -options: To not get a warning about missing bootstrap classpath (when using Java 9 and `-source 8`). + // -fallthrough: Don't check fallthroughs. Instead, use Error Prone. Its + // warnings are suppressible with a "// fall through" comment. + // -classfile: classgraph jar file and https://bugs.openjdk.org/browse/JDK-8190452 + String lint = '-Xlint:-options,-fallthrough,-classfile' + // Java 8 uses the Error Prone javac, not what is requested with useJdkCompiler. + // So there is no need to set additional lint options. + if (!isJava8) { + if (useJdkCompiler >= 21) { + // TODO: Ignore this-escape for now, we may want to review and suppress each one later. + lint +=',-this-escape' + } + if (useJdkCompiler >= 23) { + // TODO: Ignore dangling-doc-comments for now, we may want to fix them later. + lint +=',-dangling-doc-comments' + } + } + + options.compilerArgs += [ + '-g', + '-Werror', + lint, + '-Xlint', + ] + + options.encoding = 'UTF-8' + options.fork = true + if (isJava8) { + options.forkOptions.jvmArgs += [ + "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() + ] + } + + // Error Prone depends on checker-qual.jar, so don't run it on that project to avoid a circular dependency. + if ((compilationTask.name.equals('compileJava') || compilationTask.name.equals('compileTestJava')) && !project.name.startsWith('checker-qual')) { + // Error Prone must be available in the annotation processor path + options.annotationProcessorPath = errorproneProcessorCustom.get() + // Enable Error Prone + options.errorprone.enabled = (useJdkCompiler >= 17) && (useJdkCompiler <= 23) + options.errorprone.disableWarningsInGeneratedCode = true + options.errorprone.errorproneArgs = [ + // Many compiler classes are interned. + '-Xep:ReferenceEquality:OFF', + // Not useful to suggest Splitter; maybe clean up. + '-Xep:StringSplitter:OFF', + // Too broad, rejects seemingly-correct code. + '-Xep:EqualsGetClass:OFF', + // Not a real problem + '-Xep:MixedMutabilityReturnType:OFF', + // Don't want to add a dependency to ErrorProne. + '-Xep:AnnotateFormatMethod:OFF', + // Warns for every use of "@checker_framework.manual" + '-Xep:InvalidBlockTag:OFF', + // Recommends writing @InlineMe which is an Error-Prone-specific annotation + '-Xep:InlineMeSuggester:OFF', + // Recommends writing @CanIgnoreReturnValue which is an Error-Prone-specific annotation. + // It would be great if Error Prone recognized the @This annotation. + '-Xep:CanIgnoreReturnValueSuggester:OFF', + // Should be turned off when using the Checker Framework. + '-Xep:ExtendsObject:OFF', + // For Visitors it is convenient to just pass a Void parameter. + '-Xep:VoidUsed:OFF', + // The Checker Framework is the only nullness tool we care about. + '-Xep:NullableWildcard:OFF', + // In the visitor pattern, it is natural to forward a Void reference. + '-Xep:VoidUsed:OFF', + // -Werror halts the build if Error Prone issues a warning, which ensures that + // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) + // stops as soon as it issues one warning, rather than outputting them all. + // https://github.com/google/error-prone/issues/436 + '-Werror', + ] + if (!isJava8) { + // Options needed for Error Prone on Java 16+, but don't hurt on Java 9+ + options.forkOptions.jvmArgs += [ + '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + '--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + ] + } + } else { + options.errorprone.enabled = false + } } - } else { - options.errorprone.enabled = false - } - } - } // end afterEvaluate + } // end afterEvaluate } // end allProjects task version(group: 'Documentation') { - description = 'Print Checker Framework version' - doLast { - println version - } + description = 'Print Checker Framework version' + doLast { + println version + } } /** @@ -612,83 +616,83 @@ task version(group: 'Documentation') { * @param args list of arguments to pass to the checker */ def createCheckTypeTask(projectName, taskName, checker, args = []) { - project("${projectName}").tasks.create(name: "check${taskName}", type: JavaCompile, dependsOn: ':checker:shadowJar') { - description = "Run the ${taskName} Checker on the main sources." - group = 'Verification' - // Always run the task. - outputs.upToDateWhen { false } - source = project("${projectName}").sourceSets.main.java - classpath = files(project("${projectName}").compileJava.classpath,project(':checker-qual').sourceSets.main.output) - destinationDirectory = file("${buildDir}") - - options.annotationProcessorPath = files(project(':checker').tasks.shadowJar.archiveFile) - options.compilerArgs += [ - '-processor', - "${checker}", - '-proc:only', - '-Xlint:-processing', - '-Xmaxerrs', - '10000', - '-Xmaxwarns', - '10000', - '-ArequirePrefixInWarningSuppressions', - '-AwarnUnneededSuppressions', - '-AwarnRedundantAnnotations', - '-AnoJreVersionCheck', - ] - options.compilerArgs += args - options.forkOptions.jvmArgs += ['-Xmx2g'] + project("${projectName}").tasks.create(name: "check${taskName}", type: JavaCompile, dependsOn: ':checker:shadowJar') { + description = "Run the ${taskName} Checker on the main sources." + group = 'Verification' + // Always run the task. + outputs.upToDateWhen { false } + source = project("${projectName}").sourceSets.main.java + classpath = files(project("${projectName}").compileJava.classpath,project(':checker-qual').sourceSets.main.output) + destinationDirectory = file("${buildDir}") - if (isJava8) { - options.compilerArgs += [ - '-source', - '8', - '-target', - '8' - ] - } else { - options.fork = true - options.forkOptions.jvmArgs += compilerArgsForRunningCF + options.annotationProcessorPath = files(project(':checker').tasks.shadowJar.archiveFile) + options.compilerArgs += [ + '-processor', + "${checker}", + '-proc:only', + '-Xlint:-processing', + '-Xmaxerrs', + '10000', + '-Xmaxwarns', + '10000', + '-ArequirePrefixInWarningSuppressions', + '-AwarnUnneededSuppressions', + '-AwarnRedundantAnnotations', + '-AnoJreVersionCheck', + ] + options.compilerArgs += args + options.forkOptions.jvmArgs += ['-Xmx2g'] + + if (isJava8) { + options.compilerArgs += [ + '-source', + '8', + '-target', + '8' + ] + } else { + options.fork = true + options.forkOptions.jvmArgs += compilerArgsForRunningCF + } } - } } task htmlValidate(type: Exec, group: 'Format') { - description = 'Validate that HTML files are well-formed' - executable 'html5validator' - args = [ - '--ignore', - '/api/', - '/build/', - '/docs/manual/manual.html', - '/docs/manual/plume-bib/docs/index.html', - '/checker/jdk/nullness/src/java/lang/ref/package.html' - ] + description = 'Validate that HTML files are well-formed' + executable 'html5validator' + args = [ + '--ignore', + '/api/', + '/build/', + '/docs/manual/manual.html', + '/docs/manual/plume-bib/docs/index.html', + '/checker/jdk/nullness/src/java/lang/ref/package.html' + ] } def javadocDirs = [ - project(':checker').sourceSets.main.allJava, - project(':checker').sourceSets.test.allJava, - project(':checker-qual').sourceSets.main.allJava, - project(':checker-util').sourceSets.main.allJava, - project(':checker-util').sourceSets.test.allJava, - project(':dataflow').sourceSets.main.allJava, - project(':dataflow').sourceSets.test.allJava, - project(':framework').sourceSets.main.allJava, - project(':framework').sourceSets.test.allJava, - project(':framework-test').sourceSets.main.allJava, - project(':framework-test').sourceSets.test.allJava, - project(':javacutil').sourceSets.main.allJava + project(':checker').sourceSets.main.allJava, + project(':checker').sourceSets.test.allJava, + project(':checker-qual').sourceSets.main.allJava, + project(':checker-util').sourceSets.main.allJava, + project(':checker-util').sourceSets.test.allJava, + project(':dataflow').sourceSets.main.allJava, + project(':dataflow').sourceSets.test.allJava, + project(':framework').sourceSets.main.allJava, + project(':framework').sourceSets.test.allJava, + project(':framework-test').sourceSets.main.allJava, + project(':framework-test').sourceSets.test.allJava, + project(':javacutil').sourceSets.main.allJava ] def requireJavadocDirs = javadocDirs project(':checker').afterEvaluate { - requireJavadocDirs += project(':checker').sourceSets.testannotations.allJava + requireJavadocDirs += project(':checker').sourceSets.testannotations.allJava } project(':framework').afterEvaluate { - requireJavadocDirs += project(':framework').sourceSets.testannotations.allJava + requireJavadocDirs += project(':framework').sourceSets.testannotations.allJava } // `gradle allJavadoc` builds the Javadoc for all modules in `docs/api`. @@ -698,62 +702,62 @@ project(':framework').afterEvaluate { // To make javadoc for only one subproject, run `./gradlew javadoc` // in the subproject or `./gradlew :checker:javadoc` at the top level. task allJavadoc(type: Javadoc, group: 'Documentation') { - description = 'Generates API documentation that includes all the modules.' - dependsOn(':checker:shadowJar', 'getPlumeScripts', 'getHtmlTools') - destinationDir = file("${rootDir}/docs/api") - source javadocDirs + description = 'Generates API documentation that includes all the modules.' + dependsOn(':checker:shadowJar', 'getPlumeScripts', 'getHtmlTools') + destinationDir = file("${rootDir}/docs/api") + source javadocDirs - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doFirst { - source( - project(':framework-test').sourceSets."${isJava8 ? 'tagletJdk8' : 'taglet'}".allJava - ) - } + doFirst { + source( + project(':framework-test').sourceSets."${isJava8 ? 'tagletJdk8' : 'taglet'}".allJava + ) + } - classpath = configurations.allProjects + classpath = configurations.allProjects - if (isJava8) { - classpath += configurations.javacJar - } + if (isJava8) { + classpath += configurations.javacJar + } - // disable interpreting module-info.java files until all sub-modules support them - modularity.inferModulePath = false + // disable interpreting module-info.java files until all sub-modules support them + modularity.inferModulePath = false - doLast { - copy { - from 'docs/logo/Checkmark/CFCheckmark_favicon.png' - rename('CFCheckmark_favicon.png', 'favicon-checkerframework.png') - into "${rootDir}/docs/api" - } - injected.execOps.exec { - workingDir file("${rootDir}/docs/api") - executable "${htmlToolsHome}/html-add-favicon" - args += [ - '.', - 'favicon-checkerframework.png' - ] + doLast { + copy { + from 'docs/logo/Checkmark/CFCheckmark_favicon.png' + rename('CFCheckmark_favicon.png', 'favicon-checkerframework.png') + into "${rootDir}/docs/api" + } + injected.execOps.exec { + workingDir file("${rootDir}/docs/api") + executable "${htmlToolsHome}/html-add-favicon" + args += [ + '.', + 'favicon-checkerframework.png' + ] + } } - } } // See documentation for allJavadoc task. javadoc.dependsOn(allJavadoc) configurations { - requireJavadoc + requireJavadoc } dependencies { - requireJavadoc 'org.plumelib:require-javadoc:1.0.9' + requireJavadoc 'org.plumelib:require-javadoc:1.0.9' } task requireJavadoc(type: JavaExec, group: 'Documentation') { - description = 'Ensures that Javadoc documentation exists in source code.' - mainClass = 'org.plumelib.javadoc.RequireJavadoc' - classpath = configurations.requireJavadoc - // Convert each srcDir to its absolute path and flatten the list - args requireJavadocDirs.collect { it.srcDirs*.absolutePath }.flatten() + description = 'Ensures that Javadoc documentation exists in source code.' + mainClass = 'org.plumelib.javadoc.RequireJavadoc' + classpath = configurations.requireJavadoc + // Convert each srcDir to its absolute path and flatten the list + args requireJavadocDirs.collect { it.srcDirs*.absolutePath }.flatten() } @@ -766,68 +770,68 @@ task requireJavadoc(type: JavaExec, group: 'Documentation') { * @return the new task */ def createJavadocTask(taskName, taskDescription, memberLevel) { - tasks.create(name: taskName, type: Javadoc) { - description = taskDescription - destinationDir = file("${rootDir}/docs/tmpapi") - destinationDir.mkdirs() - subprojects.forEach { - if (!it.name.startsWith('checker-qual-android')) { - source += it.sourceSets.main.allJava - } - } + tasks.create(name: taskName, type: Javadoc) { + description = taskDescription + destinationDir = file("${rootDir}/docs/tmpapi") + destinationDir.mkdirs() + subprojects.forEach { + if (!it.name.startsWith('checker-qual-android')) { + source += it.sourceSets.main.allJava + } + } - classpath = configurations.allProjects - // disable interpreting module-info.java files until all sub-modules support them - modularity.inferModulePath = false + classpath = configurations.allProjects + // disable interpreting module-info.java files until all sub-modules support them + modularity.inferModulePath = false - destinationDir.deleteDir() - options.memberLevel = memberLevel - options.addBooleanOption('Xdoclint:all', true) - options.addStringOption('Xmaxwarns', '99999') + destinationDir.deleteDir() + options.memberLevel = memberLevel + options.addBooleanOption('Xdoclint:all', true) + options.addStringOption('Xmaxwarns', '99999') - // options.addStringOption('skip', 'ClassNotToCheck|OtherClass') - } + // options.addStringOption('skip', 'ClassNotToCheck|OtherClass') + } } createJavadocTask('javadocDoclintAll', 'Runs javadoc with -Xdoclint:all option.', JavadocMemberLevel.PRIVATE) task manual(group: 'Documentation') { - description = 'Build the manual' - def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - workingDir = file('docs/manual') - commandLine 'make', 'all' + description = 'Build the manual' + def injected = project.objects.newInstance(InjectedExecOps) + doLast { + injected.execOps.exec { + workingDir = file('docs/manual') + commandLine 'make', 'all' + } } - } } // No group so it does not show up in the output of `gradlew tasks` task downloadJtreg(type: Download) { - description = 'Downloads and unpacks jtreg.' - onlyIf { !(new File("${jtregHome}/lib/jtreg.jar").exists()) } - // src 'https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz' - // If ci.adoptopenjdk.net is down, use this copy. - // src 'https://checkerframework.org/jtreg-4.2.0-tip.tar.gz' - // dest new File(buildDir, 'jtreg-4.2.0-tip.tar.gz') - // src 'https://builds.shipilev.net/jtreg/jtreg4.2-b16.zip' - src 'https://builds.shipilev.net/jtreg/jtreg-7.5+1.zip' - dest new File(buildDir, 'jtreg.zip') - overwrite true - retries 3 - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - copy { - // Use 'tarTree' when downloading a .tar.gz file - from zipTree(dest) - into "${jtregHome}/.." - } - injected.execOps.exec { - commandLine('chmod', '+x', "${jtregHome}/bin/jtdiff", "${jtregHome}/bin/jtreg") + description = 'Downloads and unpacks jtreg.' + onlyIf { !(new File("${jtregHome}/lib/jtreg.jar").exists()) } + // src 'https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz' + // If ci.adoptopenjdk.net is down, use this copy. + // src 'https://checkerframework.org/jtreg-4.2.0-tip.tar.gz' + // dest new File(buildDir, 'jtreg-4.2.0-tip.tar.gz') + // src 'https://builds.shipilev.net/jtreg/jtreg4.2-b16.zip' + src 'https://builds.shipilev.net/jtreg/jtreg-7.5+1.zip' + dest new File(buildDir, 'jtreg.zip') + overwrite true + retries 3 + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + copy { + // Use 'tarTree' when downloading a .tar.gz file + from zipTree(dest) + into "${jtregHome}/.." + } + injected.execOps.exec { + commandLine('chmod', '+x', "${jtregHome}/bin/jtdiff", "${jtregHome}/bin/jtreg") + } } - } } /** @@ -839,22 +843,22 @@ task downloadJtreg(type: Download) { * @param extraArgs any extra arguments to pass to git */ void clone(url, directory, ignoreError, extraArgs = []){ - def injected = project.objects.newInstance(InjectedExecOps) - injected.execOps.exec { - workingDir file("${directory}/../") - executable 'git' - args = [ - 'clone', - '-q', - '--filter=blob:none', - url, - file(directory).toPath().last() - ] - args += extraArgs - ignoreExitValue = ignoreError - } - // TODO: not sure this does what it is supposed to. - // timeout = Duration.ofSeconds(60) + def injected = project.objects.newInstance(InjectedExecOps) + injected.execOps.exec { + workingDir file("${directory}/../") + executable 'git' + args = [ + 'clone', + '-q', + '--filter=blob:none', + url, + file(directory).toPath().last() + ] + args += extraArgs + ignoreExitValue = ignoreError + } + // TODO: not sure this does what it is supposed to. + // timeout = Duration.ofSeconds(60) } /** @@ -868,39 +872,39 @@ void clone(url, directory, ignoreError, extraArgs = []){ * @param extraArgs arguments to pass to the git command */ def createCloneTask(taskName, url, directory, extraArgs = []) { - tasks.create(name: taskName) { - description = "Obtain or update ${url}" + tasks.create(name: taskName) { + description = "Obtain or update ${url}" - // Always run. - outputs.upToDateWhen { false } + // Always run. + outputs.upToDateWhen { false } - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - if (file(directory).exists()) { - injected.execOps.exec { - workingDir file(directory) - executable 'git' - args = ['pull', '-q'] - ignoreExitValue = true - } - // TODO: not sure this does what it is supposed to. - // timeout = Duration.ofSeconds(60) - } else { - try { - clone(url, directory, true, extraArgs) - } catch (Throwable t) { - println "Exception while cloning ${url}" - t.printStackTrace() - } - if (!file(directory).exists()) { - println "Cloning failed, will try again in 1 minute: clone(${url}, ${directory}, true, ${extraArgs})" - sleep(60000) // wait 1 minute, then try again - clone(url, directory, false, extraArgs) + doLast { + if (file(directory).exists()) { + injected.execOps.exec { + workingDir file(directory) + executable 'git' + args = ['pull', '-q'] + ignoreExitValue = true + } + // TODO: not sure this does what it is supposed to. + // timeout = Duration.ofSeconds(60) + } else { + try { + clone(url, directory, true, extraArgs) + } catch (Throwable t) { + println "Exception while cloning ${url}" + t.printStackTrace() + } + if (!file(directory).exists()) { + println "Cloning failed, will try again in 1 minute: clone(${url}, ${directory}, true, ${extraArgs})" + sleep(60000) // wait 1 minute, then try again + clone(url, directory, false, extraArgs) + } + } } - } } - } } @@ -912,419 +916,419 @@ createCloneTask('getDoLikeJavac', 'https://github.com/opprop/do-like-javac.git', // No group so it does not show up in the output of `gradlew tasks` task pythonIsInstalled(type: Exec) { - description = 'Check that the python3 executable is installed.' - executable = 'python3' - args '--version' + description = 'Check that the python3 executable is installed.' + executable = 'python3' + args '--version' } task tags { - group = 'Emacs' - description = 'Create Emacs TAGS table' + group = 'Emacs' + description = 'Create Emacs TAGS table' - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - commandLine 'etags', '-i', 'checker/TAGS', '-i', 'checker-qual/TAGS', '-i', 'checker-util/TAGS', '-i', 'dataflow/TAGS', '-i', 'framework/TAGS', '-i', 'framework-test/TAGS', '-i', 'javacutil/TAGS', '-i', 'docs/manual/TAGS' - } - injected.execOps.exec { - commandLine 'make', '-C', 'docs/manual', 'tags' + doLast { + injected.execOps.exec { + commandLine 'etags', '-i', 'checker/TAGS', '-i', 'checker-qual/TAGS', '-i', 'checker-util/TAGS', '-i', 'dataflow/TAGS', '-i', 'framework/TAGS', '-i', 'framework-test/TAGS', '-i', 'javacutil/TAGS', '-i', 'docs/manual/TAGS' + } + injected.execOps.exec { + commandLine 'make', '-C', 'docs/manual', 'tags' + } } - } } subprojects { - configurations { - errorprone - annotatedGuava - } - - dependencies { - // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core - // If you update this: - // * Temporarily comment out "-Werror" elsewhere in this file - // * Repeatedly run `./gradlew clean compileJava` and fix all errors - // * Uncomment "-Werror" - if (currentRuntimeJavaVersion >= 17) { - errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: versions.errorprone - } else { - // EP 2.31.0 is the last release that works on Java < 17. - errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: '2.31.0' + configurations { + errorprone + annotatedGuava } - // TODO: it's a bug that annotatedlib:guava requires the error_prone_annotations dependency. - annotatedGuava "com.google.errorprone:error_prone_annotations:${versions.errorprone}" - annotatedGuava ('org.checkerframework.annotatedlib:guava:33.1.0.2-jre') { - // So long as Guava only uses annotations from checker-qual, excluding it should not cause problems. - exclude group: 'org.checkerframework' - } - } - - shadowJar { - // If you add an external dependency, then do the following: - // * On the master branch and on the modified branch, run: - // ./gradlew assembleForJavac && jar tf checker/dist/checker.jar | grep -v '^annotated-jdk/' | sort > checker-jar-contents.txt - // * Compare the files, and add relocate lines below. - // * Repeat until no new classes appear (all are under org/checkerframework/). - - // Note that string literals are also relocated. Therefore, when the original - // names should be used, e.g. to load the original classes, one needs to work - // around the relocation. When adding a new external dependency, make - // sure no existing string literals are accidentally relocated. - // For an example work-around see NullnessAnnotatedTypeFactory#NONNULL_ALIASES. - - // Relocate packages that might conflict with user's classpath. - relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser' - relocate 'org.apache', 'org.checkerframework.org.apache' - relocate 'org.relaxng', 'org.checkerframework.org.relaxng' - relocate 'org.plumelib', 'org.checkerframework.org.plumelib' - relocate 'org.codehaus', 'org.checkerframework.org.codehaus' - relocate 'org.objectweb.asm', 'org.checkerframework.org.objectweb.asm' - // Add the classgraph relocations if it is included in releases. - // relocate 'io.github.classgraph', 'org.checkerframework.io.github.classgraph' - // relocate 'nonapi.io.github.classgraph', 'org.checkerframework.nonapi.io.github.classgraph' - // relocate 'sun', 'org.checkerframework.sun' - relocate 'com.google', 'org.checkerframework.com.google' - - exclude '**/module-info.class' - - minimize() - } - - if (!project.name.startsWith('checker-qual-android')) { - task tags(type: Exec) { - description = 'Create Emacs TAGS table' - commandLine 'bash', '-c', "find . \\( -name build -o -name jtreg -o -name tests \\) -prune -o -name '*.java' -print | sort-directory-order | xargs ctags -e -f TAGS" - } - } - - java { - withJavadocJar() - withSourcesJar() - } - - // Things in this block reference definitions in the subproject that do not exist, - // until the project is evaluated. - afterEvaluate { - // Adds manifest to all Jar files - tasks.withType(Jar) { - includeEmptyDirs = false - if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { - metaInf { - from './LICENSE.txt' - } - } else { - metaInf { - from "${rootDir}/LICENSE.txt" - } - } - manifest { - attributes('Implementation-Version': "${project.version}") - attributes('Implementation-URL': 'https://eisop.github.io/') - if (! archiveFileName.get().endsWith('source.jar') && ! archiveFileName.get().startsWith('checker-qual')) { - attributes('Automatic-Module-Name': 'org.checkerframework.' + project.name.replaceAll('-', '.')) - } - if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { - attributes('Bundle-License': 'MIT') + dependencies { + // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core + // If you update this: + // * Temporarily comment out "-Werror" elsewhere in this file + // * Repeatedly run `./gradlew clean compileJava` and fix all errors + // * Uncomment "-Werror" + if (currentRuntimeJavaVersion >= 17) { + errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: versions.errorprone } else { - attributes('Bundle-License': '(GPL-2.0-only WITH Classpath-exception-2.0)') + // EP 2.31.0 is the last release that works on Java < 17. + errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: '2.31.0' + } + + // TODO: it's a bug that annotatedlib:guava requires the error_prone_annotations dependency. + annotatedGuava "com.google.errorprone:error_prone_annotations:${versions.errorprone}" + annotatedGuava ('org.checkerframework.annotatedlib:guava:33.1.0.2-jre') { + // So long as Guava only uses annotations from checker-qual, excluding it should not cause problems. + exclude group: 'org.checkerframework' } - } } - // Tasks such as `checkResourceLeak` to run various checkers on all the main source sets. - // These pass and are run by the `typecheck` task. - // When you add one here, also update a dependsOn item for the 'typecheck' task. - createCheckTypeTask(project.name, 'Formatter', - 'org.checkerframework.checker.formatter.FormatterChecker') - createCheckTypeTask(project.name, 'Interning', - 'org.checkerframework.checker.interning.InterningChecker', - [ - '-Astubs=javax-lang-model-element-name.astub' - ]) - createCheckTypeTask(project.name, 'Optional', - 'org.checkerframework.checker.optional.OptionalChecker', - [ - // to avoid having to annotate JavaParser - '-AassumePureGetters', - '-AassumeAssertionsAreEnabled', - ]) - createCheckTypeTask(project.name, 'Purity', - 'org.checkerframework.framework.util.PurityChecker') - createCheckTypeTask(project.name, 'ResourceLeak', - 'org.checkerframework.checker.resourceleak.ResourceLeakChecker') - createCheckTypeTask(project.name, 'Signature', - 'org.checkerframework.checker.signature.SignatureChecker') - - // The checkNullness task runs on all code, but it only *checks* the following code: - // * All files outside the 'framework' and 'checker' subprojects. - // * In the 'framework' and 'checker' subprojects, files with `@AnnotatedFor("nullness")`. - if (project.name.is('framework') || project.name.is('checker')) { - createCheckTypeTask(project.name, 'Nullness', - 'org.checkerframework.checker.nullness.NullnessChecker', - [ - '-AskipUses=com\\.sun\\.*', - // If a file does not contain @AnnotatedFor("nullness"), all its routines are assumed to return @Nullable. - '-AuseConservativeDefaultsForUncheckedCode=source', - '-AconservativeArgumentNullnessAfterInvocation=true', - ]) - } else { - createCheckTypeTask(project.name, 'Nullness', - 'org.checkerframework.checker.nullness.NullnessChecker', - [ - '-AskipUses=com\\.sun\\.*', - '-AconservativeArgumentNullnessAfterInvocation=true' - ]) + shadowJar { + // If you add an external dependency, then do the following: + // * On the master branch and on the modified branch, run: + // ./gradlew assembleForJavac && jar tf checker/dist/checker.jar | grep -v '^annotated-jdk/' | sort > checker-jar-contents.txt + // * Compare the files, and add relocate lines below. + // * Repeat until no new classes appear (all are under org/checkerframework/). + + // Note that string literals are also relocated. Therefore, when the original + // names should be used, e.g. to load the original classes, one needs to work + // around the relocation. When adding a new external dependency, make + // sure no existing string literals are accidentally relocated. + // For an example work-around see NullnessAnnotatedTypeFactory#NONNULL_ALIASES. + + // Relocate packages that might conflict with user's classpath. + relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser' + relocate 'org.apache', 'org.checkerframework.org.apache' + relocate 'org.relaxng', 'org.checkerframework.org.relaxng' + relocate 'org.plumelib', 'org.checkerframework.org.plumelib' + relocate 'org.codehaus', 'org.checkerframework.org.codehaus' + relocate 'org.objectweb.asm', 'org.checkerframework.org.objectweb.asm' + // Add the classgraph relocations if it is included in releases. + // relocate 'io.github.classgraph', 'org.checkerframework.io.github.classgraph' + // relocate 'nonapi.io.github.classgraph', 'org.checkerframework.nonapi.io.github.classgraph' + // relocate 'sun', 'org.checkerframework.sun' + relocate 'com.google', 'org.checkerframework.com.google' + + exclude '**/module-info.class' + + minimize() } + if (!project.name.startsWith('checker-qual-android')) { + task tags(type: Exec) { + description = 'Create Emacs TAGS table' + commandLine 'bash', '-c', "find . \\( -name build -o -name jtreg -o -name tests \\) -prune -o -name '*.java' -print | sort-directory-order | xargs ctags -e -f TAGS" + } + } - // Add jtregTests to framework and checker modules - if (project.name.is('framework') || project.name.is('checker')) { - tasks.create(name: 'jtregTests', group: 'Verification') { - description = 'Run the jtreg tests.' + java { + withJavadocJar() + withSourcesJar() + } - if (currentRuntimeJavaVersion < 11) { - // jtreg only works on JDK 11+ - return + // Things in this block reference definitions in the subproject that do not exist, + // until the project is evaluated. + afterEvaluate { + // Adds manifest to all Jar files + tasks.withType(Jar) { + includeEmptyDirs = false + if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { + metaInf { + from './LICENSE.txt' + } + } else { + metaInf { + from "${rootDir}/LICENSE.txt" + } + } + manifest { + attributes('Implementation-Version': "${project.version}") + attributes('Implementation-URL': 'https://eisop.github.io/') + if (! archiveFileName.get().endsWith('source.jar') && ! archiveFileName.get().startsWith('checker-qual')) { + attributes('Automatic-Module-Name': 'org.checkerframework.' + project.name.replaceAll('-', '.')) + } + if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { + attributes('Bundle-License': 'MIT') + } else { + attributes('Bundle-License': '(GPL-2.0-only WITH Classpath-exception-2.0)') + } + } } - dependsOn(':downloadJtreg') - dependsOn('compileJava') - dependsOn('compileTestJava') - dependsOn('shadowJar') + // Tasks such as `checkResourceLeak` to run various checkers on all the main source sets. + // These pass and are run by the `typecheck` task. + // When you add one here, also update a dependsOn item for the 'typecheck' task. + createCheckTypeTask(project.name, 'Formatter', + 'org.checkerframework.checker.formatter.FormatterChecker') + createCheckTypeTask(project.name, 'Interning', + 'org.checkerframework.checker.interning.InterningChecker', + [ + '-Astubs=javax-lang-model-element-name.astub' + ]) + createCheckTypeTask(project.name, 'Optional', + 'org.checkerframework.checker.optional.OptionalChecker', + [ + // to avoid having to annotate JavaParser + '-AassumePureGetters', + '-AassumeAssertionsAreEnabled', + ]) + createCheckTypeTask(project.name, 'Purity', + 'org.checkerframework.framework.util.PurityChecker') + createCheckTypeTask(project.name, 'ResourceLeak', + 'org.checkerframework.checker.resourceleak.ResourceLeakChecker') + createCheckTypeTask(project.name, 'Signature', + 'org.checkerframework.checker.signature.SignatureChecker') + + // The checkNullness task runs on all code, but it only *checks* the following code: + // * All files outside the 'framework' and 'checker' subprojects. + // * In the 'framework' and 'checker' subprojects, files with `@AnnotatedFor("nullness")`. + if (project.name.is('framework') || project.name.is('checker')) { + createCheckTypeTask(project.name, 'Nullness', + 'org.checkerframework.checker.nullness.NullnessChecker', + [ + '-AskipUses=com\\.sun\\.*', + // If a file does not contain @AnnotatedFor("nullness"), all its routines are assumed to return @Nullable. + '-AuseConservativeDefaultsForUncheckedCode=source', + '-AconservativeArgumentNullnessAfterInvocation=true', + ]) + } else { + createCheckTypeTask(project.name, 'Nullness', + 'org.checkerframework.checker.nullness.NullnessChecker', + [ + '-AskipUses=com\\.sun\\.*', + '-AconservativeArgumentNullnessAfterInvocation=true' + ]) + } - def injected = project.objects.newInstance(InjectedExecOps) - def isFramework = project.name.is('framework') - def isChecker = project.name.is('checker') + // Add jtregTests to framework and checker modules + if (project.name.is('framework') || project.name.is('checker')) { + tasks.create(name: 'jtregTests', group: 'Verification') { + description = 'Run the jtreg tests.' + + if (currentRuntimeJavaVersion < 11) { + // jtreg only works on JDK 11+ + return + } + + dependsOn(':downloadJtreg') + dependsOn('compileJava') + dependsOn('compileTestJava') + dependsOn('shadowJar') + + def injected = project.objects.newInstance(InjectedExecOps) + + def isFramework = project.name.is('framework') + def isChecker = project.name.is('checker') + + String jtregOutput = "${buildDir}/jtreg" + String name = 'all' + String tests = '.' + + doLast { + try { + injected.execOps.exec { + executable "${jtregHome}/bin/jtreg" + args = [ + "-dir:${projectDir}/jtreg", + "-workDir:${jtregOutput}/${name}/work", + "-reportDir:${jtregOutput}/${name}/report", + '-verbose:error,fail,nopass', + // Don't add debugging information + // '-javacoptions:-g', + '-keywords:!ignore', + '-samevm', + "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + // Required for checker/jtreg/nullness/PersistUtil.java and other tests + "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + ] + args += [ + // checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java + // uses the jdk.jdeps module. + '-javacoptions:--add-modules jdk.jdeps', + '-javacoptions:--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + ] + if (isFramework) { + // Do not check for the annotated JDK + args += [ + '-javacoptions:-ApermitMissingJdk' + ] + } else if (isChecker) { + args += [ + "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", + ] + } + + // Allow running on any JRE version. + args += [ + "-javacoptions:-AnoJreVersionCheck" + ] + + // Location of jtreg tests + args += "${tests}" + } + } catch (Exception ex) { + if (ex.getCause() != null && ex.getCause().getCause()!= null) { + String msg = ex.getCause().getLocalizedMessage() + ':\n' + msg += ex.getCause().getCause().getLocalizedMessage() + '\n' + msg += 'Have you installed jtreg?' + println msg + } + throw ex + } + } + } + } - String jtregOutput = "${buildDir}/jtreg" - String name = 'all' - String tests = '.' + // Create a task for each JUnit test class whose name is the same as the JUnit class name. + // Regex [\\\\/] matches Unix and Windows directory separators. + sourceSets.test.allJava.filter {it.path.matches('.*test[\\\\/]junit.*')}.forEach { file -> + String junitClassName = file.name.replaceAll('.java', '') + tasks.create(name: "${junitClassName}", type: Test) { + description = "Run ${junitClassName} tests." + include "**/${name}.class" + testClassesDirs = testing.suites.test.sources.output.classesDirs + classpath = testing.suites.test.sources.runtimeClasspath + } + } - doLast { - try { - injected.execOps.exec { - executable "${jtregHome}/bin/jtreg" - args = [ - "-dir:${projectDir}/jtreg", - "-workDir:${jtregOutput}/${name}/work", - "-reportDir:${jtregOutput}/${name}/report", - '-verbose:error,fail,nopass', - // Don't add debugging information - // '-javacoptions:-g', - '-keywords:!ignore', - '-samevm', - "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - // Required for checker/jtreg/nullness/PersistUtil.java and other tests - "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - ] - args += [ - // checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java - // uses the jdk.jdeps module. - '-javacoptions:--add-modules jdk.jdeps', - '-javacoptions:--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - ] - if (isFramework) { - // Do not check for the annotated JDK - args += [ - '-javacoptions:-ApermitMissingJdk' - ] - } else if (isChecker) { - args += [ - "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", - ] - } + // Configure JUnit tests + tasks.withType(Test) { + if (isJava8) { + jvmArgs "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() + } else { + jvmArgs += compilerArgsForRunningCF + } - // Allow running on any JRE version. - args += [ - "-javacoptions:-AnoJreVersionCheck" - ] + // Run tests in parallel, except on CI where it seems to lead to flaky failures. + // The TF_BUILD environment variable is set to 'True' for jobs running on Azure Pipelines. + if (!System.getenv('TF_BUILD')?.equals('True')) { + // Not running under Azure Pipelines CI. + + // This uses Gradle's recommended value for `maxParallelForks`: + // https://docs.gradle.org/current/userguide/performance.html#optimize_java_projects + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + } else { + // Running under Azure Pipelines CI. + + // Azure seems to time out when a task doesn't produce periodic output. + if (project.name.is('checker')) { + testLogging { + events "started", "skipped", "failed" + displayGranularity 3 + } + } + } - // Location of jtreg tests - args += "${tests}" + if (project.name.is('checker')) { + dependsOn('assembleForJavac') } - } catch (Exception ex) { - if (ex.getCause() != null && ex.getCause().getCause()!= null) { - String msg = ex.getCause().getLocalizedMessage() + ':\n' - msg += ex.getCause().getCause().getLocalizedMessage() + '\n' - msg += 'Have you installed jtreg?' - println msg + + if (project.hasProperty('emit.test.debug')) { + systemProperties += ['emit.test.debug': 'true'] } - throw ex - } - } - } - } - // Create a task for each JUnit test class whose name is the same as the JUnit class name. - // Regex [\\\\/] matches Unix and Windows directory separators. - sourceSets.test.allJava.filter {it.path.matches('.*test[\\\\/]junit.*')}.forEach { file -> - String junitClassName = file.name.replaceAll('.java', '') - tasks.create(name: "${junitClassName}", type: Test) { - description = "Run ${junitClassName} tests." - include "**/${name}.class" - testClassesDirs = testing.suites.test.sources.output.classesDirs - classpath = testing.suites.test.sources.runtimeClasspath - } - } + testLogging { + showStandardStreams = true + // Always run the tests + outputs.upToDateWhen { false } - // Configure JUnit tests - tasks.withType(Test) { - if (isJava8) { - jvmArgs "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() - } else { - jvmArgs += compilerArgsForRunningCF - } - - // Run tests in parallel, except on CI where it seems to lead to flaky failures. - // The TF_BUILD environment variable is set to 'True' for jobs running on Azure Pipelines. - if (!System.getenv('TF_BUILD')?.equals('True')) { - // Not running under Azure Pipelines CI. - - // This uses Gradle's recommended value for `maxParallelForks`: - // https://docs.gradle.org/current/userguide/performance.html#optimize_java_projects - maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 - } else { - // Running under Azure Pipelines CI. - - // Azure seems to time out when a task doesn't produce periodic output. - if (project.name.is('checker')) { - testLogging { - events "started", "skipped", "failed" - displayGranularity 3 - } - } - } + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'failed' - if (project.name.is('checker')) { - dependsOn('assembleForJavac') - } + // Don't show the uninteresting stack traces from the exceptions. + showStackTraces = false + } - if (project.hasProperty('emit.test.debug')) { - systemProperties += ['emit.test.debug': 'true'] - } + // After each test, print a summary. + afterSuite { desc, result -> + if (desc.getClassName() != null) { + long mils = result.getEndTime() - result.getStartTime() + double seconds = mils / 1000.0 + + println "Testsuite: ${desc.getClassName()}\n" + + "Tests run: ${result.testCount}, " + + "Failures: ${result.failedTestCount}, " + + "Skipped: ${result.skippedTestCount}, " + + "Time elapsed: ${seconds} sec\n" + } + } + } - testLogging { - showStandardStreams = true - // Always run the tests - outputs.upToDateWhen { false } + // Create a nonJunitTests task per project + tasks.create(name: 'nonJunitTests', group: 'Verification') { + description = 'Run all Checker Framework tests except for the JUnit tests and inference tests.' + if (project.name.is('framework') || project.name.is('checker')) { + dependsOn('jtregTests') + } + if (project.name.is('framework')) { + dependsOn('loaderTests') + } - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'failed' - - // Don't show the uninteresting stack traces from the exceptions. - showStackTraces = false - } - - // After each test, print a summary. - afterSuite { desc, result -> - if (desc.getClassName() != null) { - long mils = result.getEndTime() - result.getStartTime() - double seconds = mils / 1000.0 - - println "Testsuite: ${desc.getClassName()}\n" + - "Tests run: ${result.testCount}, " + - "Failures: ${result.failedTestCount}, " + - "Skipped: ${result.skippedTestCount}, " + - "Time elapsed: ${seconds} sec\n" - } - } - } + if (project.name.is('checker')) { + if (!isJava8) { + dependsOn('jtregJdk11Tests') + } + dependsOn('nullnessExtraTests', 'commandLineTests', 'tutorialTests') + } - // Create a nonJunitTests task per project - tasks.create(name: 'nonJunitTests', group: 'Verification') { - description = 'Run all Checker Framework tests except for the JUnit tests and inference tests.' - if (project.name.is('framework') || project.name.is('checker')) { - dependsOn('jtregTests') - } - if (project.name.is('framework')) { - dependsOn('loaderTests') - } - - if (project.name.is('checker')) { - if (!isJava8) { - dependsOn('jtregJdk11Tests') + if (project.name.is('dataflow')) { + dependsOn('allDataflowTests') + } } - dependsOn('nullnessExtraTests', 'commandLineTests', 'tutorialTests') - } - if (project.name.is('dataflow')) { - dependsOn('allDataflowTests') - } - } - - // Create an inferenceTests task per project - tasks.create(name: 'inferenceTests', group: 'Verification') { - description = 'Run inference tests.' - if (project.name.is('checker')) { - // NO-AFU: Needs to depend on future AFU version of CF - // dependsOn('inferenceTests-part1', 'inferenceTests-part1') - } - } - tasks.create(name: 'inferenceTests-part1', group: 'Verification') { - description = 'Run inference tests (part 1).' - if (project.name.is('checker')) { - // NO-AFU: Needs to depend on future AFU version of CF - // dependsOn('ainferTest', 'wpiManyTest') - } - } - tasks.create(name: 'inferenceTests-part2', group: 'Verification') { - description = 'Run inference tests (part 2).' - if (project.name.is('checker')) { - // NO-AFU: Needs to depend on future AFU version of CF - // dependsOn('wpiPlumeLibTest') - } - } + // Create an inferenceTests task per project + tasks.create(name: 'inferenceTests', group: 'Verification') { + description = 'Run inference tests.' + if (project.name.is('checker')) { + // NO-AFU: Needs to depend on future AFU version of CF + // dependsOn('inferenceTests-part1', 'inferenceTests-part1') + } + } + tasks.create(name: 'inferenceTests-part1', group: 'Verification') { + description = 'Run inference tests (part 1).' + if (project.name.is('checker')) { + // NO-AFU: Needs to depend on future AFU version of CF + // dependsOn('ainferTest', 'wpiManyTest') + } + } + tasks.create(name: 'inferenceTests-part2', group: 'Verification') { + description = 'Run inference tests (part 2).' + if (project.name.is('checker')) { + // NO-AFU: Needs to depend on future AFU version of CF + // dependsOn('wpiPlumeLibTest') + } + } - // Create a typecheck task per project (dogfooding the Checker Framework on itself). - // This isn't a test of the Checker Framework as the test and nonJunitTests tasks are. - // Tasks such as 'checkInterning' are constructed by createCheckTypeTask. - tasks.create(name: 'typecheck', group: 'Verification') { - description = 'Run the Checker Framework on itself' - dependsOn('typecheck-part1', 'typecheck-part2') - } - tasks.create(name: 'typecheck-part1', group: 'Verification') { - description = 'Run the Checker Framework on itself (part 1)' - dependsOn('checkFormatter', 'checkInterning', 'checkOptional', 'checkPurity') - } - tasks.create(name: 'typecheck-part2', group: 'Verification') { - description = 'Run the Checker Framework on itself (part 2)' - dependsOn('checkResourceLeak', 'checkSignature') - if (project.name.is('framework') || project.name.is('checker')) { - dependsOn('checkCompilerMessages') - } - dependsOn('checkNullness') - } + // Create a typecheck task per project (dogfooding the Checker Framework on itself). + // This isn't a test of the Checker Framework as the test and nonJunitTests tasks are. + // Tasks such as 'checkInterning' are constructed by createCheckTypeTask. + tasks.create(name: 'typecheck', group: 'Verification') { + description = 'Run the Checker Framework on itself' + dependsOn('typecheck-part1', 'typecheck-part2') + } + tasks.create(name: 'typecheck-part1', group: 'Verification') { + description = 'Run the Checker Framework on itself (part 1)' + dependsOn('checkFormatter', 'checkInterning', 'checkOptional', 'checkPurity') + } + tasks.create(name: 'typecheck-part2', group: 'Verification') { + description = 'Run the Checker Framework on itself (part 2)' + dependsOn('checkResourceLeak', 'checkSignature') + if (project.name.is('framework') || project.name.is('checker')) { + dependsOn('checkCompilerMessages') + } + dependsOn('checkNullness') + } - // Create an allTests task per project. - // allTests = test + nonJunitTests + inferenceTests + typecheck - tasks.create(name: 'allTests', group: 'Verification') { - description = 'Run all Checker Framework tests' - // The 'test' target is just the JUnit tests. - dependsOn('test', 'nonJunitTests', 'inferenceTests', 'typecheck') - } + // Create an allTests task per project. + // allTests = test + nonJunitTests + inferenceTests + typecheck + tasks.create(name: 'allTests', group: 'Verification') { + description = 'Run all Checker Framework tests' + // The 'test' target is just the JUnit tests. + dependsOn('test', 'nonJunitTests', 'inferenceTests', 'typecheck') + } - task javadocPrivate(dependsOn: javadoc) { - doFirst { - javadocMemberLevel = JavadocMemberLevel.PRIVATE - } - doLast { - javadocMemberLevel = JavadocMemberLevel.PROTECTED - } + task javadocPrivate(dependsOn: javadoc) { + doFirst { + javadocMemberLevel = JavadocMemberLevel.PRIVATE + } + doLast { + javadocMemberLevel = JavadocMemberLevel.PROTECTED + } + } } - } } // The `assembleForJavac` task is faster than the `assemble` task, if you only @@ -1337,30 +1341,30 @@ assemble.dependsOn(':checker:assembleForJavac') assemble.mustRunAfter(clean) task buildAll(group: 'Build') { - description = 'Build all jar files, including source and javadoc jars' - dependsOn(allJavadoc) - subprojects { Project subproject -> - dependsOn("${subproject.name}:assemble") - dependsOn("${subproject.name}:javadocJar") - dependsOn("${subproject.name}:sourcesJar") - } - dependsOn('framework:allJavadocJar', 'framework:allSourcesJar', 'checker:allJavadocJar', 'checker:allSourcesJar', 'checker-qual:jar', 'checker-util:jar') - dependsOn('checker:assembleForJavac') + description = 'Build all jar files, including source and javadoc jars' + dependsOn(allJavadoc) + subprojects { Project subproject -> + dependsOn("${subproject.name}:assemble") + dependsOn("${subproject.name}:javadocJar") + dependsOn("${subproject.name}:sourcesJar") + } + dependsOn('framework:allJavadocJar', 'framework:allSourcesJar', 'checker:allJavadocJar', 'checker:allSourcesJar', 'checker-qual:jar', 'checker-util:jar') + dependsOn('checker:assembleForJavac') } task releaseBuild(group: 'Build') { - description = 'Cleans, then builds everything required for a release' - dependsOn(clean) - dependsOn(buildAll) + description = 'Cleans, then builds everything required for a release' + dependsOn(clean) + dependsOn(buildAll) } // No group so it does not show up in the output of `gradlew tasks` task releaseAndTest { - description = 'Build everything required for a release and run allTests' - dependsOn(releaseBuild) - subprojects { Project subproject -> - dependsOn("${subproject.name}:allTests") - } + description = 'Build everything required for a release and run allTests' + dependsOn(releaseBuild) + subprojects { Project subproject -> + dependsOn("${subproject.name}:allTests") + } } // Don't create an empty checker-framework-VERSION.jar @@ -1371,43 +1375,43 @@ jar.onlyIf {false} * @param publication the MavenPublication */ final sharedPublicationConfiguration(publication) { - publication.pom { - url = 'https://eisop.github.io/' - developers { - // These are the lead developers/maintainers, not all the developers or contributors. - - // For eisop, previously also typetools - developer { - id = 'wmdietl' - name = 'Werner M. Dietl' - email = 'wdietl@gmail.com' - url = 'https://ece.uwaterloo.ca/~wdietl/' - organization = 'University of Waterloo' - organizationUrl = 'https://uwaterloo.ca/' - } - - // For typetools - developer { - id = 'mernst' - name = 'Michael Ernst' - email = 'mernst@cs.washington.edu' - url = 'https://homes.cs.washington.edu/~mernst/' - organization = 'University of Washington' - organizationUrl = 'https://www.cs.washington.edu/' - } - developer { - id = 'smillst' - name = 'Suzanne Millstein' - email = 'smillst@cs.washington.edu' - organization = 'University of Washington' - organizationUrl = 'https://www.cs.washington.edu/' - } - } + publication.pom { + url = 'https://eisop.github.io/' + developers { + // These are the lead developers/maintainers, not all the developers or contributors. + + // For eisop, previously also typetools + developer { + id = 'wmdietl' + name = 'Werner M. Dietl' + email = 'wdietl@gmail.com' + url = 'https://ece.uwaterloo.ca/~wdietl/' + organization = 'University of Waterloo' + organizationUrl = 'https://uwaterloo.ca/' + } - scm { - url = 'https://github.com/eisop/checker-framework.git' - connection = 'scm:git:https://github.com/eisop/checker-framework.git' - developerConnection = 'scm:git:ssh://git@github.com/eisop/checker-framework.git' + // For typetools + developer { + id = 'mernst' + name = 'Michael Ernst' + email = 'mernst@cs.washington.edu' + url = 'https://homes.cs.washington.edu/~mernst/' + organization = 'University of Washington' + organizationUrl = 'https://www.cs.washington.edu/' + } + developer { + id = 'smillst' + name = 'Suzanne Millstein' + email = 'smillst@cs.washington.edu' + organization = 'University of Washington' + organizationUrl = 'https://www.cs.washington.edu/' + } + } + + scm { + url = 'https://github.com/eisop/checker-framework.git' + connection = 'scm:git:https://github.com/eisop/checker-framework.git' + developerConnection = 'scm:git:ssh://git@github.com/eisop/checker-framework.git' + } } - } } diff --git a/checker-qual-android/build.gradle b/checker-qual-android/build.gradle index 573966e969e..135d2ecb0f3 100644 --- a/checker-qual-android/build.gradle +++ b/checker-qual-android/build.gradle @@ -1,73 +1,73 @@ evaluationDependsOn(':checker-qual') task copySources(type: Copy) { - description = 'Copy checker-qual source to checker-qual-android' - includeEmptyDirs = false - doFirst { - // Delete the directory in case a previously copied file should no longer be in checker-qual - delete file(layout.buildDirectory.dir("generated/sources/main/java")) - } - from files(project(':checker-qual').sourceSets.main.java) - include '**/*.java' - exclude '**/SignednessUtilExtra.java' + description = 'Copy checker-qual source to checker-qual-android' + includeEmptyDirs = false + doFirst { + // Delete the directory in case a previously copied file should no longer be in checker-qual + delete file(layout.buildDirectory.dir("generated/sources/main/java")) + } + from files(project(':checker-qual').sourceSets.main.java) + include '**/*.java' + exclude '**/SignednessUtilExtra.java' - // Types annotated with runtime annotations are always kept in the main dex by the default Android Gradle plugin. - // Using the standard Checker Framework annotations can lead to main dex overflows; - // users of the Checker Framework may find themselves unable to build their Android apps. - // By contrast, class-retention annotations are stripped out before packaging by all build systems as a convention. - filter { line -> line.replaceAll('RetentionPolicy.RUNTIME', 'RetentionPolicy.CLASS') } + // Types annotated with runtime annotations are always kept in the main dex by the default Android Gradle plugin. + // Using the standard Checker Framework annotations can lead to main dex overflows; + // users of the Checker Framework may find themselves unable to build their Android apps. + // By contrast, class-retention annotations are stripped out before packaging by all build systems as a convention. + filter { line -> line.replaceAll('RetentionPolicy.RUNTIME', 'RetentionPolicy.CLASS') } - into file(layout.buildDirectory.dir("generated/sources/main/java")) + into file(layout.buildDirectory.dir("generated/sources/main/java")) - filePermissions { - user.read = true - group.read = true - other.read = true - } + filePermissions { + user.read = true + group.read = true + other.read = true + } } sourceSets { - main { - java { - srcDir(copySources) + main { + java { + srcDir(copySources) + } } - } } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerQualAndroidPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Qual Android' - description = 'checker-qual-android contains annotations (type qualifiers) that a programmer\n' + - 'writes to specify Java code for type-checking by the Checker Framework.\n' + - '\n' + - 'The checker-qual-android artifact is identical to the checker-qual\n' + - 'artifact, except that in checker-qual-android annotations have classfile\n' + - 'retention. The default Android Gradle plugin retains types annotated with\n' + - 'runtime annotations in the main dex, but strips out class-retention\n' + - 'annotations.\n' - licenses { - license { - name = 'The MIT License' - url = 'http://opensource.org/licenses/MIT' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Qual Android' + description = 'checker-qual-android contains annotations (type qualifiers) that a programmer\n' + + 'writes to specify Java code for type-checking by the Checker Framework.\n' + + '\n' + + 'The checker-qual-android artifact is identical to the checker-qual\n' + + 'artifact, except that in checker-qual-android annotations have classfile\n' + + 'retention. The default Android Gradle plugin retains types annotated with\n' + + 'runtime annotations in the main dex, but strips out class-retention\n' + + 'annotations.\n' + licenses { + license { + name = 'The MIT License' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } + } } - } } publishing { - publications { - checkerQualAndroid(MavenPublication) { - checkerQualAndroidPom it + publications { + checkerQualAndroid(MavenPublication) { + checkerQualAndroidPom it + } } - } } signing { - sign publishing.publications.checkerQualAndroid + sign publishing.publications.checkerQualAndroid } diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle index 28af96cd7c4..f87580fae12 100644 --- a/checker-qual/build.gradle +++ b/checker-qual/build.gradle @@ -1,83 +1,83 @@ buildscript { - dependencies { - if (JavaVersion.current() >= JavaVersion.VERSION_17) { - // bnd builder depends on Java 17. - // As EISOP releases are made on Java 21, this shouldn't impact releases. - classpath('biz.aQute.bnd:biz.aQute.bnd.gradle:7.1.0') + dependencies { + if (JavaVersion.current() >= JavaVersion.VERSION_17) { + // bnd builder depends on Java 17. + // As EISOP releases are made on Java 21, this shouldn't impact releases. + classpath('biz.aQute.bnd:biz.aQute.bnd.gradle:7.1.0') + } } - } } plugins { - id 'java-library' + id 'java-library' } if (JavaVersion.current() >= JavaVersion.VERSION_17) { - apply plugin: 'biz.aQute.bnd.builder' + apply plugin: 'biz.aQute.bnd.builder' } sourceSets { - main { - java { - exclude 'module-info.java' + main { + java { + exclude 'module-info.java' + } } - } - module_info { - java { - srcDirs ('src/main') + module_info { + java { + srcDirs ('src/main') + } } - } } task compileJava9(type: JavaCompile) { - source = sourceSets.module_info.java - destinationDirectory = sourceSets.main.output.classesDirs[0] - classpath = configurations.allProjects - options.release = 9 + source = sourceSets.module_info.java + destinationDirectory = sourceSets.main.output.classesDirs[0] + classpath = configurations.allProjects + options.release = 9 } compileJava { - dependsOn compileJava9 + dependsOn compileJava9 } javadoc { - modularity.inferModulePath = false + modularity.inferModulePath = false } jar { - manifest { - attributes('Export-Package': '*') - } + manifest { + attributes('Export-Package': '*') + } } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerQualPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Qual' - description = 'checker-qual contains annotations (type qualifiers) that a programmer\n' + - 'writes to specify Java code for type-checking by the Checker Framework.\n' - licenses { - license { - name = 'The MIT License' - url = 'http://opensource.org/licenses/MIT' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Qual' + description = 'checker-qual contains annotations (type qualifiers) that a programmer\n' + + 'writes to specify Java code for type-checking by the Checker Framework.\n' + licenses { + license { + name = 'The MIT License' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } + } } - } } publishing { - publications { - checkerQual(MavenPublication) { - checkerQualPom it + publications { + checkerQual(MavenPublication) { + checkerQualPom it + } } - } } signing { - sign publishing.publications.checkerQual + sign publishing.publications.checkerQual } diff --git a/checker-qual/src/main/java/module-info.java b/checker-qual/src/main/java/module-info.java index cdc5b0e6d86..de227a471d6 100644 --- a/checker-qual/src/main/java/module-info.java +++ b/checker-qual/src/main/java/module-info.java @@ -3,42 +3,42 @@ * for type-checking by the Checker Framework. */ module org.checkerframework.checker.qual { - // javadoc-only dependencies - requires static java.compiler; - requires static java.desktop; - requires static jdk.compiler; + // javadoc-only dependencies + requires static java.compiler; + requires static java.desktop; + requires static jdk.compiler; - // the .jar file (for the Automatic-Module-Name) is not ready during javadoc - // requires static org.checkerframework.checker; + // the .jar file (for the Automatic-Module-Name) is not ready during javadoc + // requires static org.checkerframework.checker; - exports org.checkerframework.checker.builder.qual; - exports org.checkerframework.checker.calledmethods.qual; - exports org.checkerframework.checker.compilermsgs.qual; - exports org.checkerframework.checker.fenum.qual; - exports org.checkerframework.checker.formatter.qual; - exports org.checkerframework.checker.guieffect.qual; - exports org.checkerframework.checker.i18n.qual; - exports org.checkerframework.checker.i18nformatter.qual; - exports org.checkerframework.checker.index.qual; - exports org.checkerframework.checker.initialization.qual; - exports org.checkerframework.checker.interning.qual; - exports org.checkerframework.checker.lock.qual; - exports org.checkerframework.checker.mustcall.qual; - exports org.checkerframework.checker.nullness.qual; - exports org.checkerframework.checker.optional.qual; - exports org.checkerframework.checker.propkey.qual; - exports org.checkerframework.checker.regex.qual; - exports org.checkerframework.checker.signature.qual; - exports org.checkerframework.checker.signedness.qual; - exports org.checkerframework.checker.tainting.qual; - exports org.checkerframework.checker.units.qual; - exports org.checkerframework.common.aliasing.qual; - exports org.checkerframework.common.initializedfields.qual; - exports org.checkerframework.common.reflection.qual; - exports org.checkerframework.common.returnsreceiver.qual; - exports org.checkerframework.common.subtyping.qual; - exports org.checkerframework.common.util.report.qual; - exports org.checkerframework.common.value.qual; - exports org.checkerframework.dataflow.qual; - exports org.checkerframework.framework.qual; + exports org.checkerframework.checker.builder.qual; + exports org.checkerframework.checker.calledmethods.qual; + exports org.checkerframework.checker.compilermsgs.qual; + exports org.checkerframework.checker.fenum.qual; + exports org.checkerframework.checker.formatter.qual; + exports org.checkerframework.checker.guieffect.qual; + exports org.checkerframework.checker.i18n.qual; + exports org.checkerframework.checker.i18nformatter.qual; + exports org.checkerframework.checker.index.qual; + exports org.checkerframework.checker.initialization.qual; + exports org.checkerframework.checker.interning.qual; + exports org.checkerframework.checker.lock.qual; + exports org.checkerframework.checker.mustcall.qual; + exports org.checkerframework.checker.nullness.qual; + exports org.checkerframework.checker.optional.qual; + exports org.checkerframework.checker.propkey.qual; + exports org.checkerframework.checker.regex.qual; + exports org.checkerframework.checker.signature.qual; + exports org.checkerframework.checker.signedness.qual; + exports org.checkerframework.checker.tainting.qual; + exports org.checkerframework.checker.units.qual; + exports org.checkerframework.common.aliasing.qual; + exports org.checkerframework.common.initializedfields.qual; + exports org.checkerframework.common.reflection.qual; + exports org.checkerframework.common.returnsreceiver.qual; + exports org.checkerframework.common.subtyping.qual; + exports org.checkerframework.common.util.report.qual; + exports org.checkerframework.common.value.qual; + exports org.checkerframework.dataflow.qual; + exports org.checkerframework.framework.qual; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java index bc457c6a797..1944baff64e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/CalledMethods.java @@ -18,10 +18,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface CalledMethods { - /** - * The names of methods that have definitely been called. - * - * @return the names of methods that have definitely been called - */ - String[] value(); + /** + * The names of methods that have definitely been called. + * + * @return the names of methods that have definitely been called + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java index 8d831ce73c3..0e9bb18d7f6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/builder/qual/NotCalledMethods.java @@ -22,10 +22,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface NotCalledMethods { - /** - * The names of the methods that have NOT been called. - * - * @return the names of the methods that have NOT been called - */ - String[] value(); + /** + * The names of the methods that have NOT been called. + * + * @return the names of the methods that have NOT been called + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java index cbacc8628b4..95377aa8894 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethods.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.calledmethods.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** * If an expression has type {@code @CalledMethods({"m1", "m2"})}, then methods {@code m1} and @@ -24,10 +25,10 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy public @interface CalledMethods { - /** - * Methods that have definitely been called on the expression whose type is annotated. - * - * @return methods that have definitely been called - */ - public String[] value() default {}; + /** + * Methods that have definitely been called on the expression whose type is annotated. + * + * @return methods that have definitely been called + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java index 307be07adfa..ecc5d2546f2 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsBottom.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.calledmethods.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type for the Called Methods type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java index 38aebe00572..c0f5e8bf222 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/CalledMethodsPredicate.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.calledmethods.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation represents a predicate on {@code @}{@link CalledMethods} annotations. If method @@ -18,14 +19,14 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({CalledMethods.class}) public @interface CalledMethodsPredicate { - /** - * A boolean expression constructed from the following grammar: - * - *

          S → method name | S && S | S || S | !S | (S) - * - *

          The expression uses standard Java operator precedence: "!" then "&&" then "||". - * - * @return the boolean expression - */ - String value(); + /** + * A boolean expression constructed from the following grammar: + * + *

          S → method name | S && S | S || S | !S | (S) + * + *

          The expression uses standard Java operator precedence: "!" then "&&" then "||". + * + * @return the boolean expression + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java index da6ce0f6441..d3728957fd0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethods.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.calledmethods.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the method, if it terminates successfully, always invokes the given methods on the @@ -47,40 +48,40 @@ @Repeatable(EnsuresCalledMethods.List.class) @InheritedAnnotation public @interface EnsuresCalledMethods { - /** - * The Java expressions that will have methods called on them. - * - * @return the Java expressions that will have methods called on them - * @see org.checkerframework.framework.qual.EnsuresQualifier - */ - // Postconditions must use "value" as the name (conditional postconditions use "expression"). - String[] value(); - - /** - * The methods guaranteed to be invoked on the expressions. - * - * @return the methods guaranteed to be invoked on the expressions - */ - @QualifierArgument("value") - String[] methods(); + /** + * The Java expressions that will have methods called on them. + * + * @return the Java expressions that will have methods called on them + * @see org.checkerframework.framework.qual.EnsuresQualifier + */ + // Postconditions must use "value" as the name (conditional postconditions use "expression"). + String[] value(); - /** - * A wrapper annotation that makes the {@link EnsuresCalledMethods} annotation repeatable. This - * annotation is an implementation detail: programmers generally do not need to write this. It is - * created automatically by Java when a programmer writes more than one {@link - * EnsuresCalledMethods} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @InheritedAnnotation - @PostconditionAnnotation(qualifier = CalledMethods.class) - public static @interface List { /** - * Return the repeatable annotations. + * The methods guaranteed to be invoked on the expressions. * - * @return the repeatable annotations + * @return the methods guaranteed to be invoked on the expressions + */ + @QualifierArgument("value") + String[] methods(); + + /** + * A wrapper annotation that makes the {@link EnsuresCalledMethods} annotation repeatable. This + * annotation is an implementation detail: programmers generally do not need to write this. It + * is created automatically by Java when a programmer writes more than one {@link + * EnsuresCalledMethods} annotation at the same location. */ - EnsuresCalledMethods[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @InheritedAnnotation + @PostconditionAnnotation(qualifier = CalledMethods.class) + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresCalledMethods[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java index 001cf1a8a76..62177670599 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsIf.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.calledmethods.qual; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the method, if it terminates with the given result, invokes the given methods on @@ -25,49 +26,49 @@ @InheritedAnnotation @Repeatable(EnsuresCalledMethodsIf.List.class) public @interface EnsuresCalledMethodsIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns Java expressions that have had the given methods called on them after the method - * returns {@link #result}. - * - * @return an array of Java expressions - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns Java expressions that have had the given methods called on them after the method + * returns {@link #result}. + * + * @return an array of Java expressions + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * The methods guaranteed to be invoked on the expressions if the result of the method is {@link - * #result}. - * - * @return the methods guaranteed to be invoked on the expressions if the result of the method is - * {@link #result} - */ - @QualifierArgument("value") - String[] methods(); + /** + * The methods guaranteed to be invoked on the expressions if the result of the method is {@link + * #result}. + * + * @return the methods guaranteed to be invoked on the expressions if the result of the method + * is {@link #result} + */ + @QualifierArgument("value") + String[] methods(); - /** - * A wrapper annotation that makes the {@link EnsuresCalledMethodsIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresCalledMethodsIf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = CalledMethods.class) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresCalledMethodsIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresCalledMethodsIf} annotation at the same location. */ - EnsuresCalledMethodsIf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = CalledMethods.class) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresCalledMethodsIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java index f0f0cb43261..5d2d7e6f26c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsOnException.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.calledmethods.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Indicates that the method, if it terminates by throwing an {@link Exception}, always invokes the @@ -38,60 +39,60 @@ @InheritedAnnotation public @interface EnsuresCalledMethodsOnException { - /** - * Returns Java expressions that have had the given methods called on them after the method throws - * an exception. - * - * @return an array of Java expressions - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); - - // NOTE 2023/10/6: There seems to be a fundamental limitation in the dataflow framework that - // prevent us from supporting a custom set of exceptions. Specifically, in the following code: - // - // try { - // m1(); - // } finally { - // m2(); - // } - // - // all exceptional edges out of the `m1()` call will flow to the same place: the start of the - // `m2()` call in the finally block. Any information about what `m1()` promised on specific - // exception types will be lost. - // - // /** - // * Returns the exception types under which the postcondition holds. - // * - // * @return the exception types under which the postcondition holds. - // */ - // Class[] exceptions(); + /** + * Returns Java expressions that have had the given methods called on them after the method + * throws an exception. + * + * @return an array of Java expressions + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); - /** - * The methods guaranteed to be invoked on the expressions if the result of the method throws an - * exception. - * - * @return the methods guaranteed to be invoked on the expressions if the method throws an - * exception - */ - String[] methods(); + // NOTE 2023/10/6: There seems to be a fundamental limitation in the dataflow framework that + // prevent us from supporting a custom set of exceptions. Specifically, in the following code: + // + // try { + // m1(); + // } finally { + // m2(); + // } + // + // all exceptional edges out of the `m1()` call will flow to the same place: the start of the + // `m2()` call in the finally block. Any information about what `m1()` promised on specific + // exception types will be lost. + // + // /** + // * Returns the exception types under which the postcondition holds. + // * + // * @return the exception types under which the postcondition holds. + // */ + // Class[] exceptions(); - /** - * A wrapper annotation that makes the {@link EnsuresCalledMethodsOnException} annotation - * repeatable. This annotation is an implementation detail: programmers generally do not need to - * write this. It is created automatically by Java when a programmer writes more than one {@link - * EnsuresCalledMethodsOnException} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * The methods guaranteed to be invoked on the expressions if the result of the method throws an + * exception. * - * @return the repeatable annotations + * @return the methods guaranteed to be invoked on the expressions if the method throws an + * exception + */ + String[] methods(); + + /** + * A wrapper annotation that makes the {@link EnsuresCalledMethodsOnException} annotation + * repeatable. This annotation is an implementation detail: programmers generally do not need to + * write this. It is created automatically by Java when a programmer writes more than one {@link + * EnsuresCalledMethodsOnException} annotation at the same location. */ - EnsuresCalledMethodsOnException[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresCalledMethodsOnException[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java index 5437acc8f7e..553ebc5ebd4 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/EnsuresCalledMethodsVarArgs.java @@ -22,10 +22,10 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface EnsuresCalledMethodsVarArgs { - /** - * Returns the methods guaranteed to be invoked on the varargs parameters. - * - * @return the methods guaranteed to be invoked on the varargs parameters - */ - String[] value(); + /** + * Returns the methods guaranteed to be invoked on the varargs parameters. + * + * @return the methods guaranteed to be invoked on the varargs parameters + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java index ac5ae5e185c..3b645366bf6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/calledmethods/qual/RequiresCalledMethods.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.calledmethods.qual; +import org.checkerframework.framework.qual.PreconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PreconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates a method precondition: when the method is invoked, the specified expressions must have @@ -25,39 +26,39 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Repeatable(RequiresCalledMethods.List.class) public @interface RequiresCalledMethods { - /** - * The Java expressions that must have had methods called on them. - * - * @return the Java expressions that must have had methods called on them - * @see org.checkerframework.framework.qual.EnsuresQualifier - */ - // Preconditions must use "value" as the name (conditional preconditions use "expression"). - String[] value(); + /** + * The Java expressions that must have had methods called on them. + * + * @return the Java expressions that must have had methods called on them + * @see org.checkerframework.framework.qual.EnsuresQualifier + */ + // Preconditions must use "value" as the name (conditional preconditions use "expression"). + String[] value(); - /** - * The methods guaranteed to be invoked on the expressions. - * - * @return the methods guaranteed to be invoked on the expressions - */ - @QualifierArgument("value") - String[] methods(); + /** + * The methods guaranteed to be invoked on the expressions. + * + * @return the methods guaranteed to be invoked on the expressions + */ + @QualifierArgument("value") + String[] methods(); - /** - * A wrapper annotation that makes the {@link RequiresCalledMethods} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresCalledMethods} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @PreconditionAnnotation(qualifier = CalledMethods.class) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link RequiresCalledMethods} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresCalledMethods} annotation at the same location. */ - RequiresCalledMethods[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @PreconditionAnnotation(qualifier = CalledMethods.class) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + RequiresCalledMethods[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java index cd9f177bf83..c842d05f1f6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKey.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.compilermsgs.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * A string that is definitely a compiler message key. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java index 9413b649bf2..f730e685a02 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/CompilerMessageKeyBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.compilermsgs.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Compiler Message Key type system. Programmers should rarely write this diff --git a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java index 1e8916ee106..8de3589889b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/compilermsgs/qual/UnknownCompilerMessageKey.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.compilermsgs.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * A {@code String} that might or might not be a compiler message key. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java index c6f28499e24..273f3678414 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtAlphaCompositingRule.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Basic alpha compositing rules for combining source and destination colors to achieve blending and diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java index 0dbe2718055..5488f870892 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtColorSpace.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Color space tags to identify the specific color space of a Color object or, via a ColorModel diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java index 7e78009279e..7cfeb7681f5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtCursorType.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * AwtCursorType. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java index a5bc81f1a5d..838cf66f6fc 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/AwtFlowLayout.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Line alignments in a flow layout (see {@link java.awt.FlowLayout} for more details). diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java index f17e9d4c932..25fc822b757 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/Fenum.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * A generic fake enumeration qualifier that is parameterized by a name. It is written in source @@ -19,5 +20,5 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(FenumTop.class) public @interface Fenum { - String value(); + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java index 43154759f49..b48b127a622 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Fenum type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java index 9a50d7356c3..8f0c43b5bff 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumTop.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The top of the fake enumeration type hierarchy. @@ -19,10 +20,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({ - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.RESOURCE_VARIABLE + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.RESOURCE_VARIABLE }) @SubtypeOf({}) @DefaultFor({TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE}) diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java index 4eeaf969214..d516e69de09 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/FenumUnqualified.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.fenum.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * An unqualified type. Such a type is incomparable to (that is, neither a subtype nor a supertype * of) any fake enum type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java index d203d9a861a..47757d9b486 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/PolyFenum.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the fake enum type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java index 1c079d5d7fa..03bbf87590a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingBoxOrientation.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * SwingBoxOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java index f89cd2dbd29..1e4dc12e68a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingCompassDirection.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * SwingCompassDirection. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java index 8935db1e0d2..e76c992d386 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingElementOrientation.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * SwingElementOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java index 569f8604fc2..1b50e42af0f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingHorizontalOrientation.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * SwingHorizontalOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java index d555bf175e3..a35095755c3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingSplitPaneOrientation.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * SwingSplitPaneOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java index 84833e6103d..eac953203d6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTextOrientation.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * SwingTextOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java index ac51e16cc98..cf1cad13892 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitleJustification.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Vertical orientations for the title text of a {@link javax.swing.border.TitledBorder}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java index e3ca36f90b1..bfed072887e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingTitlePosition.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Justifications for the title text of a {@link javax.swing.border.TitledBorder}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java index 7a70857ddc8..f1318741fcb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/fenum/qual/SwingVerticalOrientation.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.fenum.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * SwingVerticalOrientation. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java index 71a0f376e77..a1c1e71c116 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java @@ -1,5 +1,9 @@ package org.checkerframework.checker.formatter.qual; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.framework.qual.AnnotatedFor; + import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; @@ -10,9 +14,6 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.framework.qual.AnnotatedFor; /** * Elements of this enumeration are used in a {@link Format Format} annotation to indicate the valid @@ -36,339 +37,340 @@ @SuppressWarnings("unchecked") // ".class" expressions in varargs position @AnnotatedFor("nullness") public enum ConversionCategory { - /** Use if the parameter can be of any type. Applicable for conversions b, B, h, H, s, S. */ - GENERAL("bBhHsS", (Class[]) null /* everything */), + /** Use if the parameter can be of any type. Applicable for conversions b, B, h, H, s, S. */ + GENERAL("bBhHsS", (Class[]) null /* everything */), - /** - * Use if the parameter is of a basic types which represent Unicode characters: char, Character, - * byte, Byte, short, and Short. This conversion may also be applied to the types int and Integer - * when Character.isValidCodePoint(int) returns true. Applicable for conversions c, C. - */ - CHAR("cC", Character.class, Byte.class, Short.class, Integer.class), + /** + * Use if the parameter is of a basic types which represent Unicode characters: char, Character, + * byte, Byte, short, and Short. This conversion may also be applied to the types int and + * Integer when Character.isValidCodePoint(int) returns true. Applicable for conversions c, C. + */ + CHAR("cC", Character.class, Byte.class, Short.class, Integer.class), - /** - * Use if the parameter is an integral type: byte, Byte, short, Short, int and Integer, long, - * Long, and BigInteger. Applicable for conversions d, o, x, X. - */ - INT("doxX", Byte.class, Short.class, Integer.class, Long.class, BigInteger.class), + /** + * Use if the parameter is an integral type: byte, Byte, short, Short, int and Integer, long, + * Long, and BigInteger. Applicable for conversions d, o, x, X. + */ + INT("doxX", Byte.class, Short.class, Integer.class, Long.class, BigInteger.class), - /** - * Use if the parameter is a floating-point type: float, Float, double, Double, and BigDecimal. - * Applicable for conversions e, E, f, g, G, a, A. - */ - FLOAT("eEfgGaA", Float.class, Double.class, BigDecimal.class), + /** + * Use if the parameter is a floating-point type: float, Float, double, Double, and BigDecimal. + * Applicable for conversions e, E, f, g, G, a, A. + */ + FLOAT("eEfgGaA", Float.class, Double.class, BigDecimal.class), - /** - * Use if the parameter is a type which is capable of encoding a date or time: long, Long, - * Calendar, and Date. Applicable for conversions t, T. - */ - @SuppressWarnings("JdkObsolete") - TIME("tT", Long.class, Calendar.class, Date.class), + /** + * Use if the parameter is a type which is capable of encoding a date or time: long, Long, + * Calendar, and Date. Applicable for conversions t, T. + */ + @SuppressWarnings("JdkObsolete") + TIME("tT", Long.class, Calendar.class, Date.class), - /** - * Use if the parameter is both a char and an int. - * - *

          In a format string, multiple conversions may be applied to the same parameter. This is - * seldom needed, but the following is an example of such use: - * - *

          -   *   format("Test %1$c %1$d", (int)42);
          -   * 
          - * - * In this example, the first parameter is interpreted as both a character and an int, therefore - * the parameter must be compatible with both conversion, and can therefore neither be char nor - * long. This intersection of conversions is called CHAR_AND_INT. - * - *

          One other conversion intersection is interesting, namely the intersection of INT and TIME, - * resulting in INT_AND_TIME. - * - *

          All other intersection either lead to an already existing type, or NULL, in which case it is - * illegal to pass object's of any type as parameter. - */ - CHAR_AND_INT(null, Byte.class, Short.class, Integer.class), + /** + * Use if the parameter is both a char and an int. + * + *

          In a format string, multiple conversions may be applied to the same parameter. This is + * seldom needed, but the following is an example of such use: + * + *

          +     *   format("Test %1$c %1$d", (int)42);
          +     * 
          + * + * In this example, the first parameter is interpreted as both a character and an int, therefore + * the parameter must be compatible with both conversion, and can therefore neither be char nor + * long. This intersection of conversions is called CHAR_AND_INT. + * + *

          One other conversion intersection is interesting, namely the intersection of INT and TIME, + * resulting in INT_AND_TIME. + * + *

          All other intersection either lead to an already existing type, or NULL, in which case it + * is illegal to pass object's of any type as parameter. + */ + CHAR_AND_INT(null, Byte.class, Short.class, Integer.class), - /** - * Use if the parameter is both an int and a time. - * - * @see #CHAR_AND_INT - */ - INT_AND_TIME(null, Long.class), + /** + * Use if the parameter is both an int and a time. + * + * @see #CHAR_AND_INT + */ + INT_AND_TIME(null, Long.class), - /** - * Use if no object of any type can be passed as parameter. In this case, the only legal value is - * null. This is seldomly needed, and indicates an error in most cases. For example: - * - *

          -   *   format("Test %1$f %1$d", null);
          -   * 
          - * - * Only null can be legally passed, passing a value such as 4 or 4.2 would lead to an exception. - */ - NULL(null), + /** + * Use if no object of any type can be passed as parameter. In this case, the only legal value + * is null. This is seldomly needed, and indicates an error in most cases. For example: + * + *
          +     *   format("Test %1$f %1$d", null);
          +     * 
          + * + * Only null can be legally passed, passing a value such as 4 or 4.2 would lead to an exception. + */ + NULL(null), - /** - * Use if a parameter is not used by the formatter. This is seldomly needed, and indicates an - * error in most cases. For example: - * - *
          -   *   format("Test %1$s %3$s", "a","unused","b");
          -   * 
          - * - * Only the first "a" and third "b" parameters are used, the second "unused" parameter is ignored. - */ - UNUSED(null, (Class[]) null /* everything */); + /** + * Use if a parameter is not used by the formatter. This is seldomly needed, and indicates an + * error in most cases. For example: + * + *
          +     *   format("Test %1$s %3$s", "a","unused","b");
          +     * 
          + * + * Only the first "a" and third "b" parameters are used, the second "unused" parameter is + * ignored. + */ + UNUSED(null, (Class[]) null /* everything */); - /** The argument types. Null means every type. */ - @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final Class @Nullable [] types; + /** The argument types. Null means every type. */ + @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! + public final Class @Nullable [] types; - /** The format specifier characters. Null means users cannot specify it directly. */ - public final @Nullable String chars; + /** The format specifier characters. Null means users cannot specify it directly. */ + public final @Nullable String chars; - /** - * Create a new conversion category. - * - * @param chars the format specifier characters. Null means users cannot specify it directly. - * @param types the argument types. Null means every type. - */ - ConversionCategory(@Nullable String chars, Class @Nullable ... types) { - this.chars = chars; - if (types == null) { - this.types = types; - } else { - List> typesWithPrimitives = new ArrayList<>(types.length); - for (Class type : types) { - typesWithPrimitives.add(type); - Class unwrapped = unwrapPrimitive(type); - if (unwrapped != null) { - typesWithPrimitives.add(unwrapped); + /** + * Create a new conversion category. + * + * @param chars the format specifier characters. Null means users cannot specify it directly. + * @param types the argument types. Null means every type. + */ + ConversionCategory(@Nullable String chars, Class @Nullable ... types) { + this.chars = chars; + if (types == null) { + this.types = types; + } else { + List> typesWithPrimitives = new ArrayList<>(types.length); + for (Class type : types) { + typesWithPrimitives.add(type); + Class unwrapped = unwrapPrimitive(type); + if (unwrapped != null) { + typesWithPrimitives.add(unwrapped); + } + } + this.types = typesWithPrimitives.toArray(new Class[0]); } - } - this.types = typesWithPrimitives.toArray(new Class[0]); } - } - /** - * If the given class is a primitive wrapper, return the corresponding primitive class. Otherwise - * return null. - * - * @param c a class - * @return the unwrapped primitive, or null - */ - private static @Nullable Class unwrapPrimitive(Class c) { - if (c == Byte.class) { - return byte.class; - } - if (c == Character.class) { - return char.class; - } - if (c == Short.class) { - return short.class; - } - if (c == Integer.class) { - return int.class; - } - if (c == Long.class) { - return long.class; - } - if (c == Float.class) { - return float.class; - } - if (c == Double.class) { - return double.class; - } - if (c == Boolean.class) { - return boolean.class; + /** + * If the given class is a primitive wrapper, return the corresponding primitive class. + * Otherwise return null. + * + * @param c a class + * @return the unwrapped primitive, or null + */ + private static @Nullable Class unwrapPrimitive(Class c) { + if (c == Byte.class) { + return byte.class; + } + if (c == Character.class) { + return char.class; + } + if (c == Short.class) { + return short.class; + } + if (c == Integer.class) { + return int.class; + } + if (c == Long.class) { + return long.class; + } + if (c == Float.class) { + return float.class; + } + if (c == Double.class) { + return double.class; + } + if (c == Boolean.class) { + return boolean.class; + } + return null; } - return null; - } - /** - * The conversion categories that have a corresponding conversion character. This lacks UNUSED, - * TIME_AND_INT, etc. - */ - private static final ConversionCategory[] conversionCategoriesWithChar = - new ConversionCategory[] {GENERAL, CHAR, INT, FLOAT, TIME}; + /** + * The conversion categories that have a corresponding conversion character. This lacks UNUSED, + * TIME_AND_INT, etc. + */ + private static final ConversionCategory[] conversionCategoriesWithChar = + new ConversionCategory[] {GENERAL, CHAR, INT, FLOAT, TIME}; - /** - * Converts a conversion character to a category. For example: - * - *
          {@code
          -   * ConversionCategory.fromConversionChar('d') == ConversionCategory.INT
          -   * }
          - * - * @param c a conversion character - * @return the category for the given conversion character - */ - @SuppressWarnings("nullness:dereference.of.nullable") // `chars` field is non-null for these - public static ConversionCategory fromConversionChar(char c) { - for (ConversionCategory v : conversionCategoriesWithChar) { - if (v.chars.contains(String.valueOf(c))) { - return v; - } + /** + * Converts a conversion character to a category. For example: + * + *
          {@code
          +     * ConversionCategory.fromConversionChar('d') == ConversionCategory.INT
          +     * }
          + * + * @param c a conversion character + * @return the category for the given conversion character + */ + @SuppressWarnings("nullness:dereference.of.nullable") // `chars` field is non-null for these + public static ConversionCategory fromConversionChar(char c) { + for (ConversionCategory v : conversionCategoriesWithChar) { + if (v.chars.contains(String.valueOf(c))) { + return v; + } + } + throw new IllegalArgumentException("Bad conversion character " + c); } - throw new IllegalArgumentException("Bad conversion character " + c); - } - - private static Set arrayToSet(E[] a) { - return new HashSet<>(Arrays.asList(a)); - } - public static boolean isSubsetOf(ConversionCategory a, ConversionCategory b) { - return intersect(a, b) == a; - } - - /** Conversion categories that need to be considered by {@link #intersect}. */ - private static final ConversionCategory[] conversionCategoriesForIntersect = - new ConversionCategory[] {CHAR, INT, FLOAT, TIME, CHAR_AND_INT, INT_AND_TIME, NULL}; - - /** - * Returns the intersection of two categories. This is seldomly needed. - * - *
          - * - *
          -   * ConversionCategory.intersect(INT, TIME) == INT_AND_TIME;
          -   * 
          - * - *
          - * - * @param a a category - * @param b a category - * @return the intersection of the two categories (their greatest lower bound) - */ - public static ConversionCategory intersect(ConversionCategory a, ConversionCategory b) { - if (a == UNUSED) { - return b; - } - if (b == UNUSED) { - return a; - } - if (a == GENERAL) { - return b; - } - if (b == GENERAL) { - return a; + private static Set arrayToSet(E[] a) { + return new HashSet<>(Arrays.asList(a)); } - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> as = arrayToSet(a.types); - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> bs = arrayToSet(b.types); - as.retainAll(bs); // intersection - for (ConversionCategory v : conversionCategoriesForIntersect) { - @SuppressWarnings( - "nullness:argument.type.incompatible" // `types` field is null only for UNUSED - // and GENERAL - ) - Set> vs = arrayToSet(v.types); - if (vs.equals(as)) { - return v; - } + public static boolean isSubsetOf(ConversionCategory a, ConversionCategory b) { + return intersect(a, b) == a; } - throw new RuntimeException(); - } - /** Conversion categories that need to be considered by {@link #union}. */ - private static final ConversionCategory[] conversionCategoriesForUnion = - new ConversionCategory[] {NULL, CHAR_AND_INT, INT_AND_TIME, CHAR, INT, FLOAT, TIME}; + /** Conversion categories that need to be considered by {@link #intersect}. */ + private static final ConversionCategory[] conversionCategoriesForIntersect = + new ConversionCategory[] {CHAR, INT, FLOAT, TIME, CHAR_AND_INT, INT_AND_TIME, NULL}; - /** - * Returns the union of two categories. This is seldomly needed. - * - *
          - * - *
          -   * ConversionCategory.union(INT, TIME) == GENERAL;
          -   * 
          - * - *
          - * - * @param a a category - * @param b a category - * @return the union of the two categories (their least upper bound) - */ - public static ConversionCategory union(ConversionCategory a, ConversionCategory b) { - if (a == UNUSED || b == UNUSED) { - return UNUSED; - } - if (a == GENERAL || b == GENERAL) { - return GENERAL; - } - if ((a == CHAR_AND_INT && b == INT_AND_TIME) || (a == INT_AND_TIME && b == CHAR_AND_INT)) { - // This is special-cased because the union of a.types and b.types - // does not include BigInteger.class, whereas the types for INT does. - // Returning INT here to prevent returning GENERAL below. - return INT; - } + /** + * Returns the intersection of two categories. This is seldomly needed. + * + *
          + * + *
          +     * ConversionCategory.intersect(INT, TIME) == INT_AND_TIME;
          +     * 
          + * + *
          + * + * @param a a category + * @param b a category + * @return the intersection of the two categories (their greatest lower bound) + */ + public static ConversionCategory intersect(ConversionCategory a, ConversionCategory b) { + if (a == UNUSED) { + return b; + } + if (b == UNUSED) { + return a; + } + if (a == GENERAL) { + return b; + } + if (b == GENERAL) { + return a; + } - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> as = arrayToSet(a.types); - @SuppressWarnings( - "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and - // GENERAL - Set> bs = arrayToSet(b.types); - as.addAll(bs); // union - for (ConversionCategory v : conversionCategoriesForUnion) { - @SuppressWarnings( - "nullness:argument.type.incompatible" // `types` field is null only for UNUSED - // and GENERAL - ) - Set> vs = arrayToSet(v.types); - if (vs.equals(as)) { - return v; - } + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); + as.retainAll(bs); // intersection + for (ConversionCategory v : conversionCategoriesForIntersect) { + @SuppressWarnings( + "nullness:argument.type.incompatible" // `types` field is null only for UNUSED + // and GENERAL + ) + Set> vs = arrayToSet(v.types); + if (vs.equals(as)) { + return v; + } + } + throw new RuntimeException(); } - return GENERAL; - } + /** Conversion categories that need to be considered by {@link #union}. */ + private static final ConversionCategory[] conversionCategoriesForUnion = + new ConversionCategory[] {NULL, CHAR_AND_INT, INT_AND_TIME, CHAR, INT, FLOAT, TIME}; - /** - * Returns true if {@code argType} can be an argument used by this format specifier. - * - * @param argType an argument type - * @return true if {@code argType} can be an argument used by this format specifier - */ - public boolean isAssignableFrom(Class argType) { - if (types == null) { - return true; - } - if (argType == void.class) { - return true; - } - for (Class c : types) { - if (c.isAssignableFrom(argType)) { - return true; - } - } - return false; - } + /** + * Returns the union of two categories. This is seldomly needed. + * + *
          + * + *
          +     * ConversionCategory.union(INT, TIME) == GENERAL;
          +     * 
          + * + *
          + * + * @param a a category + * @param b a category + * @return the union of the two categories (their least upper bound) + */ + public static ConversionCategory union(ConversionCategory a, ConversionCategory b) { + if (a == UNUSED || b == UNUSED) { + return UNUSED; + } + if (a == GENERAL || b == GENERAL) { + return GENERAL; + } + if ((a == CHAR_AND_INT && b == INT_AND_TIME) || (a == INT_AND_TIME && b == CHAR_AND_INT)) { + // This is special-cased because the union of a.types and b.types + // does not include BigInteger.class, whereas the types for INT does. + // Returning INT here to prevent returning GENERAL below. + return INT; + } - /** Returns a pretty printed {@link ConversionCategory}. */ - @Pure - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(name()); - sb.append(" conversion category"); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); + as.addAll(bs); // union + for (ConversionCategory v : conversionCategoriesForUnion) { + @SuppressWarnings( + "nullness:argument.type.incompatible" // `types` field is null only for UNUSED + // and GENERAL + ) + Set> vs = arrayToSet(v.types); + if (vs.equals(as)) { + return v; + } + } - if (types == null || types.length == 0) { - return sb.toString(); + return GENERAL; } - StringJoiner sj = new StringJoiner(", ", "(one of: ", ")"); - for (Class cls : types) { - sj.add(cls.getSimpleName()); + /** + * Returns true if {@code argType} can be an argument used by this format specifier. + * + * @param argType an argument type + * @return true if {@code argType} can be an argument used by this format specifier + */ + public boolean isAssignableFrom(Class argType) { + if (types == null) { + return true; + } + if (argType == void.class) { + return true; + } + for (Class c : types) { + if (c.isAssignableFrom(argType)) { + return true; + } + } + return false; } - sb.append(" "); - sb.append(sj); - return sb.toString(); - } + /** Returns a pretty printed {@link ConversionCategory}. */ + @Pure + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(name()); + sb.append(" conversion category"); + + if (types == null || types.length == 0) { + return sb.toString(); + } + + StringJoiner sj = new StringJoiner(", ", "(one of: ", ")"); + for (Class cls : types) { + sj.add(cls.getSimpleName()); + } + sb.append(" "); + sb.append(sj); + + return sb.toString(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java index 878fce1ee22..d8f1d9a2b8a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/Format.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.formatter.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a String type, indicates that the String may be passed to {@link @@ -37,12 +38,12 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownFormat.class) public @interface Format { - /** - * An array of {@link ConversionCategory}, indicating the types of legal remaining arguments when - * a value of the annotated type is used as the first argument to {@link - * java.util.Formatter#format(String, Object...) Formatter.format} and similar methods. - * - * @return types that can be used as values when a value of this type is the format string - */ - ConversionCategory[] value(); + /** + * An array of {@link ConversionCategory}, indicating the types of legal remaining arguments + * when a value of the annotated type is used as the first argument to {@link + * java.util.Formatter#format(String, Object...) Formatter.format} and similar methods. + * + * @return types that can be used as values when a value of this type is the format string + */ + ConversionCategory[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java index e6cfffcccff..9438cf76820 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/FormatBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.formatter.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Format String type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java index d6597eec381..22c2ace3992 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/InvalidFormat.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.formatter.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a {@link java.lang.String String} type, indicates that the string is @@ -20,10 +21,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownFormat.class) public @interface InvalidFormat { - /** - * Using a value of the annotated type as the first argument to {@link - * java.util.Formatter#format(String, Object...) Formatter.format} or similar methods will lead to - * this exception message. - */ - String value(); + /** + * Using a value of the annotated type as the first argument to {@link + * java.util.Formatter#format(String, Object...) Formatter.format} or similar methods will lead + * to this exception message. + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java index ccddb774a9b..e0512950429 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/qual/UnknownFormat.java @@ -1,16 +1,17 @@ package org.checkerframework.checker.formatter.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * The top qualifier. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java index 07308112f71..d768a5bf921 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/AlwaysSafe.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.guieffect.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** * Annotation to override the UI effect on a class, and make a field or method safe for non-UI code diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java index 7586919ef90..9ad0b5878c7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/PolyUI.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.guieffect.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Annotation for the polymorphic-UI effect. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java index 615b4daa42e..fcf5682c66a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/guieffect/qual/UI.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.guieffect.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Annotation for the UI effect. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java index ce74615098a..da2d8c06b48 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKey.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.i18n.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} is a key into a property file or resource bundle containing diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java index ce55bf59ea4..f0727f5b9eb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/LocalizableKeyBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.i18n.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Internationalization type system. Programmers should rarely write this diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java index 663cb07dfbc..6d24a80559f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/Localized.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.i18n.qual; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has been localized and formatted for the target output @@ -20,12 +21,12 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownLocalized.class) @QualifierForLiterals({ - // All literals except chars and strings, which may need to be localized. - // (null is bottom by default.) - LiteralKind.INT, - LiteralKind.LONG, - LiteralKind.FLOAT, - LiteralKind.DOUBLE, - LiteralKind.BOOLEAN + // All literals except chars and strings, which may need to be localized. + // (null is bottom by default.) + LiteralKind.INT, + LiteralKind.LONG, + LiteralKind.FLOAT, + LiteralKind.DOUBLE, + LiteralKind.BOOLEAN }) public @interface Localized {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java index 71a8448dccf..1188f3aeac2 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalizableKey.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.i18n.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has an unknown localizable key property. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java index 2bb09803885..daccba594d7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18n/qual/UnknownLocalized.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.i18n.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has unknown localization properties. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java index 09e17e4b264..afeae5e1280 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.i18nformatter.qual; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; + import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.StringJoiner; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; /** * Elements of this enumeration are used in a {@link I18nFormat} annotation to indicate the valid @@ -24,189 +25,190 @@ @AnnotatedFor("nullness") public enum I18nConversionCategory { - /** - * Use if a parameter is not used by the formatter. For example, in - * - *
          -   * MessageFormat.format("{1}", a, b);
          -   * 
          - * - * only the second argument ("b") is used. The first argument ("a") is ignored. - */ - UNUSED(null /* everything */, null), - - /** Use if the parameter can be of any type. */ - GENERAL(null /* everything */, null), - - /** Use if the parameter can be of date, time, or number types. */ - DATE(new Class[] {Date.class, Number.class}, new String[] {"date", "time"}), - - /** - * Use if the parameter can be of number or choice types. An example of choice: - * - *
          {@code
          -   * format("{0, choice, 0#zero|1#one|1<{0, number} is more than 1}", 2)
          -   * }
          - * - * This will print "2 is more than 1". - */ - NUMBER(new Class[] {Number.class}, new String[] {"number", "choice"}); - - @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final Class @Nullable [] types; - - @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final String @Nullable [] strings; - - I18nConversionCategory(Class @Nullable [] types, String @Nullable [] strings) { - this.types = types; - this.strings = strings; - } - - /** Used by {@link #stringToI18nConversionCategory}. */ - private static final I18nConversionCategory[] namedCategories = - new I18nConversionCategory[] {DATE, NUMBER}; - - /** - * Creates a conversion cagetogry from a string name. - * - *
          -   * I18nConversionCategory.stringToI18nConversionCategory("number") == I18nConversionCategory.NUMBER;
          -   * 
          - * - * @return the I18nConversionCategory associated with the given string - */ - @SuppressWarnings( - "nullness:iterating.over.nullable") // in namedCategories, `strings` field is non-null - public static I18nConversionCategory stringToI18nConversionCategory(String string) { - string = string.toLowerCase(); - for (I18nConversionCategory v : namedCategories) { - for (String s : v.strings) { - if (s.equals(string)) { - return v; - } - } - } - throw new IllegalArgumentException("Invalid format type " + string); - } - - private static Set arrayToSet(E[] a) { - return new HashSet<>(Arrays.asList(a)); - } - - /** - * Return true if a is a subset of b. - * - * @return true if a is a subset of b - */ - public static boolean isSubsetOf(I18nConversionCategory a, I18nConversionCategory b) { - return intersect(a, b) == a; - } - - /** Conversion categories that need to be considered by {@link #intersect}. */ - private static final I18nConversionCategory[] conversionCategoriesForIntersect = - new I18nConversionCategory[] {DATE, NUMBER}; - - /** - * Returns the intersection of the two given I18nConversionCategories. - * - *
          - * - *
          -   * I18nConversionCategory.intersect(DATE, NUMBER) == NUMBER;
          -   * 
          - * - *
          - */ - public static I18nConversionCategory intersect( - I18nConversionCategory a, I18nConversionCategory b) { - if (a == UNUSED) { - return b; - } - if (b == UNUSED) { - return a; - } - if (a == GENERAL) { - return b; - } - if (b == GENERAL) { - return a; + /** + * Use if a parameter is not used by the formatter. For example, in + * + *
          +     * MessageFormat.format("{1}", a, b);
          +     * 
          + * + * only the second argument ("b") is used. The first argument ("a") is ignored. + */ + UNUSED(null /* everything */, null), + + /** Use if the parameter can be of any type. */ + GENERAL(null /* everything */, null), + + /** Use if the parameter can be of date, time, or number types. */ + DATE(new Class[] {Date.class, Number.class}, new String[] {"date", "time"}), + + /** + * Use if the parameter can be of number or choice types. An example of choice: + * + *
          {@code
          +     * format("{0, choice, 0#zero|1#one|1<{0, number} is more than 1}", 2)
          +     * }
          + * + * This will print "2 is more than 1". + */ + NUMBER(new Class[] {Number.class}, new String[] {"number", "choice"}); + + @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! + public final Class @Nullable [] types; + + @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! + public final String @Nullable [] strings; + + I18nConversionCategory(Class @Nullable [] types, String @Nullable [] strings) { + this.types = types; + this.strings = strings; } + /** Used by {@link #stringToI18nConversionCategory}. */ + private static final I18nConversionCategory[] namedCategories = + new I18nConversionCategory[] {DATE, NUMBER}; + + /** + * Creates a conversion cagetogry from a string name. + * + *
          +     * I18nConversionCategory.stringToI18nConversionCategory("number") == I18nConversionCategory.NUMBER;
          +     * 
          + * + * @return the I18nConversionCategory associated with the given string + */ @SuppressWarnings( - "nullness:argument.type.incompatible") // types field is only null in UNUSED and - // GENERAL - Set> as = arrayToSet(a.types); - @SuppressWarnings( - "nullness:argument.type.incompatible") // types field is only null in UNUSED and - // GENERAL - Set> bs = arrayToSet(b.types); - as.retainAll(bs); // intersection - for (I18nConversionCategory v : conversionCategoriesForIntersect) { - @SuppressWarnings("nullness:argument.type.incompatible") // in those values, `types` field is - // non-null - Set> vs = arrayToSet(v.types); - if (vs.equals(as)) { - return v; - } - } - throw new RuntimeException(); - } - - /** - * Returns the union of the two given I18nConversionCategories. - * - *
          -   * I18nConversionCategory.intersect(DATE, NUMBER) == DATE;
          -   * 
          - */ - public static I18nConversionCategory union(I18nConversionCategory a, I18nConversionCategory b) { - if (a == UNUSED || b == UNUSED) { - return UNUSED; + "nullness:iterating.over.nullable") // in namedCategories, `strings` field is non-null + public static I18nConversionCategory stringToI18nConversionCategory(String string) { + string = string.toLowerCase(); + for (I18nConversionCategory v : namedCategories) { + for (String s : v.strings) { + if (s.equals(string)) { + return v; + } + } + } + throw new IllegalArgumentException("Invalid format type " + string); } - if (a == GENERAL || b == GENERAL) { - return GENERAL; + + private static Set arrayToSet(E[] a) { + return new HashSet<>(Arrays.asList(a)); } - if (a == DATE || b == DATE) { - return DATE; + + /** + * Return true if a is a subset of b. + * + * @return true if a is a subset of b + */ + public static boolean isSubsetOf(I18nConversionCategory a, I18nConversionCategory b) { + return intersect(a, b) == a; } - return NUMBER; - } - - /** - * Returns true if {@code argType} can be an argument used by this format specifier. - * - * @param argType an argument type - * @return true if {@code argType} can be an argument used by this format specifier - */ - public boolean isAssignableFrom(Class argType) { - if (types == null) { - return true; + + /** Conversion categories that need to be considered by {@link #intersect}. */ + private static final I18nConversionCategory[] conversionCategoriesForIntersect = + new I18nConversionCategory[] {DATE, NUMBER}; + + /** + * Returns the intersection of the two given I18nConversionCategories. + * + *
          + * + *
          +     * I18nConversionCategory.intersect(DATE, NUMBER) == NUMBER;
          +     * 
          + * + *
          + */ + public static I18nConversionCategory intersect( + I18nConversionCategory a, I18nConversionCategory b) { + if (a == UNUSED) { + return b; + } + if (b == UNUSED) { + return a; + } + if (a == GENERAL) { + return b; + } + if (b == GENERAL) { + return a; + } + + @SuppressWarnings( + "nullness:argument.type.incompatible") // types field is only null in UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // types field is only null in UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); + as.retainAll(bs); // intersection + for (I18nConversionCategory v : conversionCategoriesForIntersect) { + @SuppressWarnings( + "nullness:argument.type.incompatible") // in those values, `types` field is + // non-null + Set> vs = arrayToSet(v.types); + if (vs.equals(as)) { + return v; + } + } + throw new RuntimeException(); } - if (argType == void.class) { - return true; + + /** + * Returns the union of the two given I18nConversionCategories. + * + *
          +     * I18nConversionCategory.intersect(DATE, NUMBER) == DATE;
          +     * 
          + */ + public static I18nConversionCategory union(I18nConversionCategory a, I18nConversionCategory b) { + if (a == UNUSED || b == UNUSED) { + return UNUSED; + } + if (a == GENERAL || b == GENERAL) { + return GENERAL; + } + if (a == DATE || b == DATE) { + return DATE; + } + return NUMBER; } - for (Class c : types) { - if (c.isAssignableFrom(argType)) { - return true; - } + + /** + * Returns true if {@code argType} can be an argument used by this format specifier. + * + * @param argType an argument type + * @return true if {@code argType} can be an argument used by this format specifier + */ + public boolean isAssignableFrom(Class argType) { + if (types == null) { + return true; + } + if (argType == void.class) { + return true; + } + for (Class c : types) { + if (c.isAssignableFrom(argType)) { + return true; + } + } + return false; } - return false; - } - - /** Returns a pretty printed {@link I18nConversionCategory}. */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder(this.name()); - if (this.types == null) { - sb.append(" conversion category (all types)"); - } else { - StringJoiner sj = new StringJoiner(", ", " conversion category (one of: ", ")"); - for (Class cls : this.types) { - sj.add(cls.getCanonicalName()); - } - sb.append(sj); + + /** Returns a pretty printed {@link I18nConversionCategory}. */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(this.name()); + if (this.types == null) { + sb.append(" conversion category (all types)"); + } else { + StringJoiner sj = new StringJoiner(", ", " conversion category (one of: ", ")"); + for (Class cls : this.types) { + sj.add(cls.getCanonicalName()); + } + sb.append(sj); + } + return sb.toString(); } - return sb.toString(); - } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java index 5e29edcc2d9..4ac4df7b653 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormat.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.i18nformatter.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a String type, indicates that the String may be passed to {@link @@ -33,10 +34,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(I18nUnknownFormat.class) public @interface I18nFormat { - /** - * An array of {@link I18nConversionCategory}, indicating the types of legal remaining arguments - * when a value of the annotated type is used as the first argument to {@link - * java.text.MessageFormat#format(String, Object...) Message.format}. - */ - I18nConversionCategory[] value(); + /** + * An array of {@link I18nConversionCategory}, indicating the types of legal remaining arguments + * when a value of the annotated type is used as the first argument to {@link + * java.text.MessageFormat#format(String, Object...) Message.format}. + */ + I18nConversionCategory[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java index 175d8502743..ff45b277970 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.i18nformatter.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Internationalization Format String type system. Programmers should rarely diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java index a0e30d6a74b..38d4aa8d05c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nFormatFor.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.i18nformatter.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation indicates that when a string of the annotated type is passed as the first @@ -31,13 +32,13 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(I18nUnknownFormat.class) public @interface I18nFormatFor { - /** - * Indicates which formal parameter is the arguments to the format method. The value should be - * {@code #} followed by the 1-based index of the formal parameter that is the arguments to the - * format method, e.g., {@code "#2"}. - * - * @return {@code #} followed by the 1-based index of the formal parameter that is the arguments - * to the format method - */ - String value(); + /** + * Indicates which formal parameter is the arguments to the format method. The value should be + * {@code #} followed by the 1-based index of the formal parameter that is the arguments to the + * format method, e.g., {@code "#2"}. + * + * @return {@code #} followed by the 1-based index of the formal parameter that is the arguments + * to the format method + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java index 379082d48f8..4515d25e127 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nInvalidFormat.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.i18nformatter.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation, attached to a {@link java.lang.String String} type, indicates that if the String @@ -18,9 +19,9 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(I18nUnknownFormat.class) public @interface I18nInvalidFormat { - /** - * Using a value of the annotated type as the first argument to {@link - * java.text.MessageFormat#format(String, Object...)} will lead to this exception message. - */ - String value(); + /** + * Using a value of the annotated type as the first argument to {@link + * java.text.MessageFormat#format(String, Object...)} will lead to this exception message. + */ + String value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java index 85b339c07f6..66b587b2653 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nUnknownFormat.java @@ -1,16 +1,17 @@ package org.checkerframework.checker.i18nformatter.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * The top qualifier. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java index 1a34f1d1a34..abc186b8321 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOf.java @@ -1,15 +1,16 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the value expressions evaluate to an integer whose value is less than the lengths @@ -56,51 +57,51 @@ @InheritedAnnotation @Repeatable(EnsuresLTLengthOf.List.class) public @interface EnsuresLTLengthOf { - /** - * The Java expressions that are less than the length of the given sequences on successful method - * termination. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - String[] value(); + /** + * The Java expressions that are less than the length of the given sequences on successful + * method termination. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + String[] value(); - /** - * Sequences, each of which is longer than the each of the expressions' value on successful method - * termination. - */ - @JavaExpression - @QualifierArgument("value") - String[] targetValue(); + /** + * Sequences, each of which is longer than the each of the expressions' value on successful + * method termination. + */ + @JavaExpression + @QualifierArgument("value") + String[] targetValue(); - /** - * This expression plus each of the value expressions is less than the length of the sequence on - * successful method termination. The {@code offset} element must ether be empty or the same - * length as {@code targetValue}. - * - * @return the offset expressions - */ - @JavaExpression - @QualifierArgument("offset") - String[] offset() default {}; + /** + * This expression plus each of the value expressions is less than the length of the sequence on + * successful method termination. The {@code offset} element must ether be empty or the same + * length as {@code targetValue}. + * + * @return the offset expressions + */ + @JavaExpression + @QualifierArgument("offset") + String[] offset() default {}; - /** - * A wrapper annotation that makes the {@link EnsuresLTLengthOf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLTLengthOf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = LTLengthOf.class) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresLTLengthOf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLTLengthOf} annotation at the same location. */ - EnsuresLTLengthOf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = LTLengthOf.class) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresLTLengthOf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java index 26872a6dc95..b0630a0e2f3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/EnsuresLTLengthOfIf.java @@ -1,15 +1,16 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the given expressions evaluate to an integer whose value is less than the lengths @@ -58,59 +59,59 @@ @InheritedAnnotation @Repeatable(EnsuresLTLengthOfIf.List.class) public @interface EnsuresLTLengthOfIf { - /** - * The return value of the method that needs to hold for the postcondition to hold. - * - * @return the return value of the method that needs to hold for the postcondition to hold - */ - boolean result(); + /** + * The return value of the method that needs to hold for the postcondition to hold. + * + * @return the return value of the method that needs to hold for the postcondition to hold + */ + boolean result(); - /** - * Java expression(s) that are less than the length of the given sequences after the method - * returns the given result. - * - * @return Java expression(s) that are less than the length of the given sequences after the - * method returns the given result - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Java expression(s) that are less than the length of the given sequences after the method + * returns the given result. + * + * @return Java expression(s) that are less than the length of the given sequences after the + * method returns the given result + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Sequences, each of which is longer than each of the expressions' value after the method returns - * the given result. - */ - @JavaExpression - @QualifierArgument("value") - String[] targetValue(); + /** + * Sequences, each of which is longer than each of the expressions' value after the method + * returns the given result. + */ + @JavaExpression + @QualifierArgument("value") + String[] targetValue(); - /** - * This expression plus each of the expressions is less than the length of the sequence after the - * method returns the given result. The {@code offset} element must ether be empty or the same - * length as {@code targetValue}. - * - * @return the offset expressions - */ - @JavaExpression - @QualifierArgument("offset") - String[] offset() default {}; + /** + * This expression plus each of the expressions is less than the length of the sequence after + * the method returns the given result. The {@code offset} element must ether be empty or the + * same length as {@code targetValue}. + * + * @return the offset expressions + */ + @JavaExpression + @QualifierArgument("offset") + String[] offset() default {}; - /** - * A wrapper annotation that makes the {@link EnsuresLTLengthOfIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLTLengthOfIf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresLTLengthOfIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLTLengthOfIf} annotation at the same location. */ - EnsuresLTLengthOfIf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresLTLengthOfIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java index 034dc325d92..1d7ead64650 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/GTENegativeOne.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer greater than or equal to -1. ("GTE" stands for diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java index 4ef51a04538..85e265a1940 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/HasSubsequence.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; /** * The annotated sequence contains a subsequence that is equal to the value of some other @@ -60,15 +61,15 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface HasSubsequence { - /** An expression that evaluates to the subsequence. */ - @JavaExpression - String subsequence(); + /** An expression that evaluates to the subsequence. */ + @JavaExpression + String subsequence(); - /** The index into this where the subsequence starts. */ - @JavaExpression - String from(); + /** The index into this where the subsequence starts. */ + @JavaExpression + String from(); - /** The index into this, immediately past where the subsequence ends. */ - @JavaExpression - String to(); + /** The index into this, immediately past where the subsequence ends. */ + @JavaExpression + String to(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java index 4bfc213db19..e08773c837e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexFor.java @@ -36,6 +36,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface IndexFor { - /** Sequences that the annotated expression is a valid index for. */ - String[] value(); + /** Sequences that the annotated expression is a valid index for. */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java index c1f20a6dc81..c91497e94aa 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrHigh.java @@ -33,6 +33,8 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface IndexOrHigh { - /** Sequences that the annotated expression is a valid index for or is equal to the lengeth of. */ - String[] value(); + /** + * Sequences that the annotated expression is a valid index for or is equal to the lengeth of. + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java index 04a956d9b5a..e4cd74d2b6d 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/IndexOrLow.java @@ -32,6 +32,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface IndexOrLow { - /** Sequences that the annotated expression is a valid index for (or it's -1). */ - String[] value(); + /** Sequences that the annotated expression is a valid index for (or it's -1). */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java index 1035cee7257..80c0ce3f45e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTEqLengthOf.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose value is less than or equal to the lengths @@ -27,7 +28,7 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UpperBoundUnknown.class) public @interface LTEqLengthOf { - /** Sequences, each of which is at least as long as the annotated expression's value. */ - @JavaExpression - public String[] value(); + /** Sequences, each of which is at least as long as the annotated expression's value. */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java index 770e19a788e..d022ccd4938 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTLengthOf.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose value is less than the lengths of all the @@ -36,17 +37,17 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(LTEqLengthOf.class) public @interface LTLengthOf { - /** Sequences, each of which is longer than the annotated expression's value. */ - @JavaExpression - public String[] value(); + /** Sequences, each of which is longer than the annotated expression's value. */ + @JavaExpression + public String[] value(); - /** - * This expression plus the annotated expression is less than the length of the sequence. The - * {@code offset} element must ether be empty or the same length as {@code value}. - * - *

          The expressions in {@code offset} may be addition/subtraction of any number of Java - * expressions. For example, {@code @LessThanLengthOf(value = "a", offset = "x + y + 2"}}. - */ - @JavaExpression - String[] offset() default {}; + /** + * This expression plus the annotated expression is less than the length of the sequence. The + * {@code offset} element must ether be empty or the same length as {@code value}. + * + *

          The expressions in {@code offset} may be addition/subtraction of any number of Java + * expressions. For example, {@code @LessThanLengthOf(value = "a", offset = "x + y + 2"}}. + */ + @JavaExpression + String[] offset() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java index 43a545d2ec6..2d361ad1694 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LTOMLengthOf.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose value is at least 2 less than the lengths @@ -30,9 +31,9 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(LTLengthOf.class) public @interface LTOMLengthOf { - /** - * Sequences, each of whose lengths is at least 1 larger than the annotated expression's value. - */ - @JavaExpression - public String[] value(); + /** + * Sequences, each of whose lengths is at least 1 larger than the annotated expression's value. + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java index 53adb1af1d4..643ca431281 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LengthOf.java @@ -29,6 +29,6 @@ // read it. @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER, ElementType.METHOD}) public @interface LengthOf { - /** Sequences that the annotated expression is equal to the length of. */ - String[] value(); + /** Sequences that the annotated expression is equal to the length of. */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java index dd608315c51..46b87853015 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThan.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the relationship between values with a byte, short, char, int, or long @@ -32,15 +33,15 @@ // false positives, the bigger value is final or effectively final, so it can appear in a dependent // annotation without causing soundness issues. public @interface LessThan { - /** - * The annotated expression's value is less than this expression. - * - *

          The expressions in {@code value} may be addition/subtraction of any number of Java - * expressions. For example, {@code @LessThan(value = "x + y + 2"}}. - * - *

          The expression in {@code value} must be final or constant or the addition/subtract of final - * or constant expressions. - */ - @JavaExpression - String[] value(); + /** + * The annotated expression's value is less than this expression. + * + *

          The expressions in {@code value} may be addition/subtraction of any number of Java + * expressions. For example, {@code @LessThan(value = "x + y + 2"}}. + * + *

          The expression in {@code value} must be final or constant or the addition/subtract of + * final or constant expressions. + */ + @JavaExpression + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java index 166ba63551a..3eda6aea692 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanBottom.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The bottom type in the LessThan type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java index 75c847b83e9..ca5b2f1a98f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LessThanUnknown.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * The top qualifier for the LessThan type hierarchy. It indicates that no other expression is known diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java index 37ec6f0e062..4bff20599c3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundBottom.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type of the lower bound type system. A variable annotated with this value cannot take diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java index 9025c8efa06..4d61ceab2dd 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/LowerBoundUnknown.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to value that might be -2 or lower. This is the top type for diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java index 2fd92db77b1..254c3281f33 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NegativeIndexFor.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression is between {@code -1} and {@code -a.length - 1}, inclusive, for each @@ -34,10 +35,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SearchIndexFor.class) public @interface NegativeIndexFor { - /** - * Sequences for which this value is a "negative index"; that is, the expression is in the range - * {@code -1} to {@code -a.length - 1}, inclusive, for each sequence {@code a} given here. - */ - @JavaExpression - public String[] value(); + /** + * Sequences for which this value is a "negative index"; that is, the expression is in the range + * {@code -1} to {@code -a.length - 1}, inclusive, for each sequence {@code a} given here. + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java index 40806f19a32..95d4af7435a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/NonNegative.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer greater than or equal to 0. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java index fa5c811ee6c..fc121728825 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyIndex.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Lower Bound and Upper Bound type systems. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java index 4d5123723a7..6f266072203 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLength.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Syntactic sugar for both @PolyValue and @PolySameLen. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java index 824609a8f5e..4548013e210 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyLowerBound.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Lower Bound type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java index 89e7fc431af..43c2518779c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolySameLen.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the SameLen type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java index c8eaa92a5d2..54d55085599 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/PolyUpperBound.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Upper Bound type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java index 7321a024340..14b40101de4 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/Positive.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer greater than or equal to 1. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java index db65d592afb..a4ab7d9bb73 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLen.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * An expression whose type has this annotation evaluates to a value that is a sequence, and that @@ -20,7 +21,7 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SameLenUnknown.class) public @interface SameLen { - /** A list of other sequences with the same length. */ - @JavaExpression - String[] value(); + /** A list of other sequences with the same length. */ + @JavaExpression + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java index a46fb49a31d..5f4360c00d5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenBottom.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the SameLen type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java index 08b1616a466..181811977e6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SameLenUnknown.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * This type represents any variable that isn't known to have the same length as another sequence. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java index 92fc4921645..5b36dc568ba 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexBottom.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Search Index type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java index 832adae5c7e..978318f07f8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexFor.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to an integer whose length is between {@code -a.length - 1} @@ -22,10 +23,10 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SearchIndexUnknown.class) public @interface SearchIndexFor { - /** - * Sequences for which the annotated expression has the type of the result of a call to {@link - * java.util.Arrays#binarySearch(Object[],Object) Arrays.binarySearch}. - */ - @JavaExpression - public String[] value(); + /** + * Sequences for which the annotated expression has the type of the result of a call to {@link + * java.util.Arrays#binarySearch(Object[],Object) Arrays.binarySearch}. + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java index 065f800d56f..87b4913b2d0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SearchIndexUnknown.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * The top type for the SearchIndex type system. This indicates that the Index checker does not know diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java index 158bd6e9354..2fe7a440d4c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexBottom.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Substring Index type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java index 235d0fad565..2ce25597311 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexFor.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * The annotated expression evaluates to either -1 or a non-negative integer less than the lengths @@ -36,23 +37,23 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(SubstringIndexUnknown.class) public @interface SubstringIndexFor { - /** - * Sequences, each of which is longer than the annotated expression plus the corresponding {@code - * offset}. (Exception: the annotated expression is also allowed to have the value -1.) - */ - @JavaExpression - public String[] value(); + /** + * Sequences, each of which is longer than the annotated expression plus the corresponding + * {@code offset}. (Exception: the annotated expression is also allowed to have the value -1.) + */ + @JavaExpression + public String[] value(); - /** - * This expression plus the annotated expression is less than the length of the corresponding - * sequence in the {@code value} array. (Exception: the annotated expression is also allowed to - * have the value -1.) - * - *

          The {@code offset} array must either be empty or the same length as {@code value}. - * - *

          The expressions in {@code offset} may be addition/subtraction of any number of Java - * expressions. For example, {@code @SubstringIndexFor(value = "a", offset = "b.length() - 1")}. - */ - @JavaExpression - public String[] offset(); + /** + * This expression plus the annotated expression is less than the length of the corresponding + * sequence in the {@code value} array. (Exception: the annotated expression is also allowed to + * have the value -1.) + * + *

          The {@code offset} array must either be empty or the same length as {@code value}. + * + *

          The expressions in {@code offset} may be addition/subtraction of any number of Java + * expressions. For example, {@code @SubstringIndexFor(value = "a", offset = "b.length() - 1")}. + */ + @JavaExpression + public String[] offset(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java index 6cc1921eefc..81410980978 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/SubstringIndexUnknown.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * The top type for the Substring Index type system. This indicates that the Index Checker does not diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java index df7f6b5a450..29b373fc5bb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundBottom.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Upper Bound type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java index 60fe6e7cbb1..2f29d0d337c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundLiteral.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * A literal value. Programmers should rarely write this type. @@ -21,10 +22,10 @@ @SubtypeOf(LTEqLengthOf.class) public @interface UpperBoundLiteral { - /** - * Returns the value of the literal. - * - * @return the value of the literal - */ - int value(); + /** + * Returns the value of the literal. + * + * @return the value of the literal + */ + int value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java index 87dff96d2b1..e47fd024970 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/index/qual/UpperBoundUnknown.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.index.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * A variable not known to have a relation to any sequence length. This is the top type of the Upper diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java index 9ae15aeb7cd..0f54368bbc6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/FBCBottom.java @@ -1,16 +1,17 @@ package org.checkerframework.checker.initialization.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.LiteralKind; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * The bottom type in the initialization type system. Programmers should rarely write this type. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java index 8c2834c485e..b3b3112b6b8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/HoldsForDefaultValue.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.initialization.qual; +import org.checkerframework.framework.qual.RelevantJavaTypes; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.RelevantJavaTypes; /** * A meta-annotation that indicates that the qualifier can be applied to the default value of every diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java index cac3fc896f1..f13abf0f8c5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/Initialized.java @@ -1,16 +1,17 @@ package org.checkerframework.checker.initialization.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * This type qualifier belongs to the freedom-before-commitment initialization tracking type-system. * This type-system is not used on its own, but in conjunction with some other type-system that @@ -29,8 +30,8 @@ @SubtypeOf(UnknownInitialization.class) @DefaultQualifierInHierarchy @DefaultFor({ - TypeUseLocation.IMPLICIT_UPPER_BOUND, - TypeUseLocation.IMPLICIT_LOWER_BOUND, - TypeUseLocation.EXCEPTION_PARAMETER + TypeUseLocation.IMPLICIT_UPPER_BOUND, + TypeUseLocation.IMPLICIT_LOWER_BOUND, + TypeUseLocation.EXCEPTION_PARAMETER }) public @interface Initialized {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java index d1ded65f7cb..529f322d8ad 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/PolyInitialized.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.initialization.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the freedom-before-commitment initialization tracking type-system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java index 5f3c43e0068..cc9b92c5625 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnderInitialization.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.initialization.qual; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.SubtypeOf; /** * This type qualifier indicates that an object is (definitely) in the process of being @@ -41,12 +42,12 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownInitialization.class) public @interface UnderInitialization { - /** - * The type-frame down to which the expression (of this type) has been initialized at least - * (inclusive). That is, an expression of type {@code @UnderInitialization(T.class)} has all - * type-frames initialized starting at {@code Object} down to (and including) {@code T}. - * - * @return the type whose fields are fully initialized - */ - Class value() default Object.class; + /** + * The type-frame down to which the expression (of this type) has been initialized at least + * (inclusive). That is, an expression of type {@code @UnderInitialization(T.class)} has all + * type-frames initialized starting at {@code Object} down to (and including) {@code T}. + * + * @return the type whose fields are fully initialized + */ + Class value() default Object.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java index 0924bcec7c7..73d66e2c4ca 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/initialization/qual/UnknownInitialization.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.initialization.qual; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** * This type qualifier indicates how much of an object has been fully initialized. An object is @@ -50,12 +51,12 @@ @SubtypeOf({}) @DefaultFor({TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE}) public @interface UnknownInitialization { - /** - * The type-frame down to which the expression (of this type) has been initialized at least - * (inclusive). That is, an expression of type {@code @UnknownInitialization(T.class)} has all - * type-frames initialized starting at {@code Object} down to (and including) {@code T}. - * - * @return the type whose fields are fully initialized - */ - Class value() default Object.class; + /** + * The type-frame down to which the expression (of this type) has been initialized at least + * (inclusive). That is, an expression of type {@code @UnknownInitialization(T.class)} has all + * type-frames initialized starting at {@code Object} down to (and including) {@code T}. + * + * @return the type whose fields are fully initialized + */ + Class value() default Object.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java index 8fc5212de46..968c3e406bc 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.interning.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method declaration annotation that indicates a method has a specification like {@code diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java index 727024e2759..921bf22d2f8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.interning.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method declaration annotation that indicates a method has a specification like {@code equals()}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java index 9157a728860..a5d618a7b0a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.interning.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method declaration annotation used to indicate that this method may be invoked on an uninterned diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java index 876bd794e2a..c75877f3f12 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/Interned.java @@ -1,16 +1,17 @@ package org.checkerframework.checker.interning.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.LiteralKind; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeKind; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Indicates that a variable has been interned, i.e., that the variable refers to the canonical * representation of an object. @@ -33,14 +34,14 @@ @SubtypeOf(UnknownInterned.class) @QualifierForLiterals({LiteralKind.PRIMITIVE, LiteralKind.STRING}) // everything but NULL @DefaultFor( - typeKinds = { - TypeKind.BOOLEAN, - TypeKind.BYTE, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.INT, - TypeKind.LONG, - TypeKind.SHORT - }) + typeKinds = { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.SHORT + }) public @interface Interned {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java index c1e7fb54a8c..ad2dd9516db 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/InternedDistinct.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.interning.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** * Indicates that no other value is {@code equals()} to the given value. Therefore, it is correct to diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java index fb657e40706..aaa1be3c2dd 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/PolyInterned.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.interning.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Interning type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java index c1a8b79cf09..f0801cd68e1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/interning/qual/UnknownInterned.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.interning.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * The top qualifier for the Interning Checker. It indicates lack of knowledge about whether values diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java index bd2d06e2dd9..8b76dfd8562 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeld.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; /** * Indicates that the given expressions are held if the method terminates successfully. @@ -23,34 +24,34 @@ @InheritedAnnotation @Repeatable(EnsuresLockHeld.List.class) public @interface EnsuresLockHeld { - /** - * Returns Java expressions whose values are locks that are held after successful method - * termination. - * - * @return Java expressions whose values are locks that are held after successful method - * termination - * @see Syntax of Java - * expressions - */ - String[] value(); + /** + * Returns Java expressions whose values are locks that are held after successful method + * termination. + * + * @return Java expressions whose values are locks that are held after successful method + * termination + * @see Syntax of + * Java expressions + */ + String[] value(); - /** - * A wrapper annotation that makes the {@link EnsuresLockHeld} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLockHeld} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = LockHeld.class) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresLockHeld} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLockHeld} annotation at the same location. */ - EnsuresLockHeld[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = LockHeld.class) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresLockHeld[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java index d8e42a64a82..2e4ca1c556a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/EnsuresLockHeldIf.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Indicates that the given expressions are held if the method terminates successfully and returns @@ -24,44 +25,44 @@ @InheritedAnnotation @Repeatable(EnsuresLockHeldIf.List.class) public @interface EnsuresLockHeldIf { - /** - * Returns the return value of the method under which the postconditions hold. - * - * @return the return value of the method under which the postconditions hold - */ - boolean result(); + /** + * Returns the return value of the method under which the postconditions hold. + * + * @return the return value of the method under which the postconditions hold + */ + boolean result(); - /** - * Returns Java expressions whose values are locks that are held after the method returns the - * given result. - * - * @return Java expressions whose values are locks that are held after the method returns the - * given result - * @see Syntax of Java - * expressions - */ - // It would be clearer for users if this field were named "lock". - // However, method ContractsFromMethod.getConditionalPostconditions in the CF implementation - // assumes that conditional postconditions have a field named "expression". - String[] expression(); + /** + * Returns Java expressions whose values are locks that are held after the method returns the + * given result. + * + * @return Java expressions whose values are locks that are held after the method returns the + * given result + * @see Syntax of + * Java expressions + */ + // It would be clearer for users if this field were named "lock". + // However, method ContractsFromMethod.getConditionalPostconditions in the CF implementation + // assumes that conditional postconditions have a field named "expression". + String[] expression(); - /** - * A wrapper annotation that makes the {@link EnsuresLockHeldIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresLockHeldIf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = LockHeld.class) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresLockHeldIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresLockHeldIf} annotation at the same location. */ - EnsuresLockHeldIf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = LockHeld.class) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresLockHeldIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java index 3914db10e5c..248b373eb12 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardSatisfied.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * If a variable {@code x} has type {@code @GuardSatisfied}, then all lock expressions for {@code @@ -33,20 +34,20 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) @TargetLocations({ - TypeUseLocation.RECEIVER, - TypeUseLocation.PARAMETER, - TypeUseLocation.RETURN, - TypeUseLocation.FIELD, - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.CONSTRUCTOR_RESULT + TypeUseLocation.RECEIVER, + TypeUseLocation.PARAMETER, + TypeUseLocation.RETURN, + TypeUseLocation.FIELD, + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.CONSTRUCTOR_RESULT }) @SubtypeOf(GuardedByUnknown.class) // TODO: Should @GuardSatisfied be in its own hierarchy? public @interface GuardSatisfied { - /** - * The index on the GuardSatisfied polymorphic qualifier, if any. Defaults to -1 so that, if the - * user writes 0, that is different than writing no index. Writing no index is the usual case. - * - * @return the index on the GuardSatisfied polymorphic qualifier, or -1 if none - */ - int value() default -1; + /** + * The index on the GuardSatisfied polymorphic qualifier, if any. Defaults to -1 so that, if the + * user writes 0, that is different than writing no index. Writing no index is the usual case. + * + * @return the index on the GuardSatisfied polymorphic qualifier, or -1 if none + */ + int value() default -1; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java index 6cf02546997..9735d961fba 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.lock.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.JavaExpression; @@ -13,6 +8,12 @@ import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.qual.UpperBoundFor; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Indicates that a thread may dereference the value referred to by the annotated variable only if * the thread holds all the given lock expressions. @@ -42,37 +43,37 @@ // These are required because the default for local variables is @GuardedByUnknown, but if the local // variable is one of these type kinds, the default should be @GuardedByUnknown. @DefaultFor( - value = {TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.UPPER_BOUND}, - typeKinds = { - TypeKind.BOOLEAN, - TypeKind.BYTE, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.INT, - TypeKind.LONG, - TypeKind.SHORT - }, - types = {String.class, Void.class}) + value = {TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.UPPER_BOUND}, + typeKinds = { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.SHORT + }, + types = {String.class, Void.class}) @UpperBoundFor( - typeKinds = { - TypeKind.BOOLEAN, - TypeKind.BYTE, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.INT, - TypeKind.LONG, - TypeKind.SHORT - }, - types = String.class) + typeKinds = { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.SHORT + }, + types = String.class) public @interface GuardedBy { - /** - * The Java value expressions that need to be held. - * - * @see Syntax of Java - * expressions - */ - @JavaExpression - String[] value() default {}; + /** + * The Java value expressions that need to be held. + * + * @see Syntax of + * Java expressions + */ + @JavaExpression + String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java index 5c493fcb9a8..793eb8069c9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByBottom.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the GuardedBy type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java index 08def367e70..e516921d9f6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/GuardedByUnknown.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * It is unknown what locks guard the value referred to by the annotated variable. Therefore, the diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java index 0bbf64324fb..090fe8be23b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/Holding.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.PreconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PreconditionAnnotation; /** * Indicates a method precondition: the specified expressions must be held when the annotated method @@ -25,11 +26,11 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @PreconditionAnnotation(qualifier = LockHeld.class) public @interface Holding { - /** - * The Java expressions that need to be held. - * - * @see Syntax of Java - * expressions - */ - String[] value(); + /** + * The Java expressions that need to be held. + * + * @see Syntax of + * Java expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java index d77d8c0842f..4d8af9898f8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockHeld.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that an expression is used as a lock and the lock is known to be held on the current diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java index c2bb14d8b2b..0cc89dae203 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockPossiblyHeld.java @@ -1,9 +1,5 @@ package org.checkerframework.checker.lock.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; @@ -13,6 +9,11 @@ import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Indicates that an expression is not known to be {@link LockHeld}. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java index e9737ea034e..5c8043426de 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/LockingFree.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * The method neither acquires nor releases locks, nor do any of the methods that it calls. More diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java index 80742faa252..be2247b9aff 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/MayReleaseLocks.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * The method, or one of the methods it calls, might release locks that were held prior to the diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java index a9932ca8ef9..2fe65e1fac0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/NewObject.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.lock.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.LiteralKind; import org.checkerframework.framework.qual.QualifierForLiterals; @@ -12,6 +7,12 @@ import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * A type that represents a newly-constructed object. It can be treated as having any * {@code @}{@link GuardedBy} type. Typically, it is only written on factory method return types. @@ -22,10 +23,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({ - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.CONSTRUCTOR_RESULT, - TypeUseLocation.RETURN + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.CONSTRUCTOR_RESULT, + TypeUseLocation.RETURN }) @SubtypeOf({GuardedBy.class, GuardSatisfied.class}) @DefaultFor(TypeUseLocation.CONSTRUCTOR_RESULT) diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java index dc43239f906..f595ff969b6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/ReleasesNoLocks.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.lock.qual; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * The method maintains a strictly nondecreasing lock held count on the current thread for any locks diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java index be464575c30..b1c71880a61 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/CreatesMustCallFor.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.mustcall.qual; +import org.checkerframework.checker.calledmethods.qual.CalledMethods; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.calledmethods.qual.CalledMethods; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; /** * Indicates that the method resets the expression's must-call type to its declared type. This @@ -74,39 +75,39 @@ @Repeatable(CreatesMustCallFor.List.class) public @interface CreatesMustCallFor { - /** - * Returns the expression whose must-call type is reset after a call to a method with this - * annotation. The expression must be visible in the scope immediately before each call site, so - * it can only refer to fields, the method's parameters (which should be referenced via the "#X" - * syntax, where "#1" is the first argument, #2 is the second, etc.), or {@code "this"}. The - * default is {@code "this"}. At call-sites, the viewpoint-adapted referent of expression must be - * owning (an owning field, a local variable tracked in a resource alias set, etc.) or a {@code - * reset.not.owning} error is issued. - * - * @return the expression to which must-call obligations are added when the annotated method is - * invoked - */ - @JavaExpression - String value() default "this"; + /** + * Returns the expression whose must-call type is reset after a call to a method with this + * annotation. The expression must be visible in the scope immediately before each call site, so + * it can only refer to fields, the method's parameters (which should be referenced via the "#X" + * syntax, where "#1" is the first argument, #2 is the second, etc.), or {@code "this"}. The + * default is {@code "this"}. At call-sites, the viewpoint-adapted referent of expression must + * be owning (an owning field, a local variable tracked in a resource alias set, etc.) or a + * {@code reset.not.owning} error is issued. + * + * @return the expression to which must-call obligations are added when the annotated method is + * invoked + */ + @JavaExpression + String value() default "this"; - /** - * A wrapper annotation that makes the {@link CreatesMustCallFor} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link CreatesMustCallFor} annotation at the same location. - * - * @checker_framework.manual #must-call-checker Must Call Checker - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD}) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link CreatesMustCallFor} annotation repeatable. + * + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link CreatesMustCallFor} annotation at the same location. * - * @return the repeatable annotations + * @checker_framework.manual #must-call-checker Must Call Checker */ - CreatesMustCallFor[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + CreatesMustCallFor[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java index 08839790d5f..396205c7a4a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/InheritableMustCall.java @@ -17,10 +17,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface InheritableMustCall { - /** - * Methods that might need to be called on the expression whose type is annotated. - * - * @return methods that might need to be called - */ - public String[] value() default {}; + /** + * Methods that might need to be called on the expression whose type is annotated. + * + * @return methods that might need to be called + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java index 803d1bed320..c9357960e9f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCall.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.mustcall.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * An expression of type {@code @MustCall({"m1", "m2"})} may be obligated to call {@code m1()} * and/or {@code m2()} before it is deallocated, but it is not obligated to call any other methods. @@ -29,10 +30,10 @@ @DefaultQualifierInHierarchy @DefaultFor({TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.UPPER_BOUND}) public @interface MustCall { - /** - * Methods that might need to be called on the expression whose type is annotated. - * - * @return methods that might need to be called - */ - public String[] value() default {}; + /** + * Methods that might need to be called on the expression whose type is annotated. + * + * @return methods that might need to be called + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java index 727a839403b..7f234fc1fac 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/MustCallUnknown.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.mustcall.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The top qualifier in the Must Call type hierarchy. It represents a type that might have an diff --git a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java index ff223c3f085..3e76f1dfb7a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/mustcall/qual/PolyMustCall.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.mustcall.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * The polymorphic qualifier for the Must Call type system. The semantics of this qualifier differ diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java index 68408f19bc0..93150087869 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/AssertNonNullIfNonNull.java @@ -40,10 +40,10 @@ @Target(ElementType.METHOD) public @interface AssertNonNullIfNonNull { - /** - * Java expression(s) that are non-null after the method returns a non-null value. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * Java expression(s) that are non-null after the method returns a non-null value. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java index 71af9a7a691..84b0200fba1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyFor.java @@ -1,15 +1,16 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the value expressions evaluate to a value that is a key in all the given maps, if @@ -36,42 +37,42 @@ @InheritedAnnotation @Repeatable(EnsuresKeyFor.List.class) public @interface EnsuresKeyFor { - /** - * Java expressions that are keys in the given maps on successful method termination. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * Java expressions that are keys in the given maps on successful method termination. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); - /** - * Returns Java expressions whose values are maps, each of which contains each expression value as - * a key (after successful method termination). - * - * @return Java expressions whose values are maps, each of which contains each expression value as - * a key (after successful method termination) - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - @QualifierArgument("value") - String[] map(); + /** + * Returns Java expressions whose values are maps, each of which contains each expression value + * as a key (after successful method termination). + * + * @return Java expressions whose values are maps, each of which contains each expression value + * as a key (after successful method termination) + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + @QualifierArgument("value") + String[] map(); - /** - * A wrapper annotation that makes the {@link EnsuresKeyFor} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresKeyFor} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = KeyFor.class) - @InheritedAnnotation - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresKeyFor} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresKeyFor} annotation at the same location. */ - EnsuresKeyFor[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = KeyFor.class) + @InheritedAnnotation + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresKeyFor[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java index c98351ecc76..017b10c554f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresKeyForIf.java @@ -1,15 +1,16 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the given expressions evaluate to a value that is a key in all the given maps, if @@ -36,45 +37,45 @@ @InheritedAnnotation @Repeatable(EnsuresKeyForIf.List.class) public @interface EnsuresKeyForIf { - /** The value the method must return, in order for the postcondition to hold. */ - boolean result(); + /** The value the method must return, in order for the postcondition to hold. */ + boolean result(); - /** - * Java expressions that are keys in the given maps after the method returns the given result. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Java expressions that are keys in the given maps after the method returns the given result. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns Java expressions whose values are maps, each of which contains each expression value as - * a key (after the method returns the given result). - * - * @return Java expressions whose values are maps, each of which contains each expression value as - * a key (after the method returns the given result) - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - @QualifierArgument("value") - String[] map(); + /** + * Returns Java expressions whose values are maps, each of which contains each expression value + * as a key (after the method returns the given result). + * + * @return Java expressions whose values are maps, each of which contains each expression value + * as a key (after the method returns the given result) + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + @QualifierArgument("value") + String[] map(); - /** - * A wrapper annotation that makes the {@link EnsuresKeyForIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresKeyForIf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = KeyFor.class) - @InheritedAnnotation - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresKeyForIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresKeyForIf} annotation at the same location. */ - EnsuresKeyForIf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = KeyFor.class) + @InheritedAnnotation + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresKeyForIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java index 95f194b0550..849c8d340cf 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNull.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; // TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text: Every prefix expression is // also non-null; for example, {@code @EnsuresNonNull(expression="a.b.c")} implies that both {@code @@ -46,31 +47,31 @@ @InheritedAnnotation @Repeatable(EnsuresNonNull.List.class) public @interface EnsuresNonNull { - /** - * Returns Java expressions that are {@link NonNull} after successful method termination. - * - * @return Java expressions that are {@link NonNull} after successful method termination - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * Returns Java expressions that are {@link NonNull} after successful method termination. + * + * @return Java expressions that are {@link NonNull} after successful method termination + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); - /** - * A wrapper annotation that makes the {@link EnsuresNonNull} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresNonNull} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = NonNull.class) - @InheritedAnnotation - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresNonNull} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresNonNull} annotation at the same location. */ - EnsuresNonNull[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = NonNull.class) + @InheritedAnnotation + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresNonNull[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java index d74189c154c..464c6f570ed 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/EnsuresNonNullIf.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; // TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text: Every prefix expression is // also non-null; for example, {@code @EnsuresNonNullIf(expression="a.b.c", results=true)} implies @@ -74,38 +75,38 @@ @InheritedAnnotation @Repeatable(EnsuresNonNullIf.List.class) public @interface EnsuresNonNullIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns Java expression(s) that are non-null after the method returns the given result. - * - * @return Java expression(s) that are non-null after the method returns the given result - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns Java expression(s) that are non-null after the method returns the given result. + * + * @return Java expression(s) that are non-null after the method returns the given result + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * * A wrapper annotation that makes the {@link EnsuresNonNullIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresNonNullIf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = NonNull.class) - @InheritedAnnotation - public static @interface List { /** - * Returns the repeatable annotations. + * * A wrapper annotation that makes the {@link EnsuresNonNullIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresNonNullIf} annotation at the same location. */ - EnsuresNonNullIf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = NonNull.class) + @InheritedAnnotation + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresNonNullIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java index 396f7160a43..cf0013289f9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyFor.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the value assigned to the annotated variable is a key for at least the given @@ -41,11 +42,11 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownKeyFor.class) public @interface KeyFor { - /** - * Java expression(s) that evaluate to a map for which the annotated type is a key. - * - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - public String[] value(); + /** + * Java expression(s) that evaluate to a map for which the annotated type is a key. + * + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + public String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java index e4120e21048..5ed2010f703 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/KeyForBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Map Key type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java index 8608c15fc15..7fae2732ed3 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/MonotonicNonNull.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.MonotonicQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.MonotonicQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that once the field (or variable) becomes non-null, it never becomes null again. There diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java index b522d663d0d..1eb704f0257 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/NonNull.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.nullness.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.LiteralKind; @@ -14,6 +9,12 @@ import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.qual.UpperBoundFor; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * If an expression's type is qualified by {@code @NonNull}, then the expression never evaluates to * {@code null}. (Unless the program has a bug; annotations specify intended behavior.) @@ -42,15 +43,15 @@ @QualifierForLiterals(LiteralKind.STRING) @DefaultFor(TypeUseLocation.EXCEPTION_PARAMETER) @UpperBoundFor( - typeKinds = { - TypeKind.PACKAGE, - TypeKind.INT, - TypeKind.BOOLEAN, - TypeKind.CHAR, - TypeKind.DOUBLE, - TypeKind.FLOAT, - TypeKind.LONG, - TypeKind.SHORT, - TypeKind.BYTE - }) + typeKinds = { + TypeKind.PACKAGE, + TypeKind.INT, + TypeKind.BOOLEAN, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.LONG, + TypeKind.SHORT, + TypeKind.BYTE + }) public @interface NonNull {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java index 2333f2cb8ae..74a3bf01236 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/Nullable.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; /** * {@link Nullable} is a type annotation that makes no commitments about whether the value is {@code diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java index 6a89836561b..c5dfc57550d 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyKeyFor.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Map Key (@KeyFor) type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java index 35b10a0b18a..bf42619c94b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/PolyNull.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the non-null type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java index 9dddbd1f365..29c5dc2b7f5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/RequiresNonNull.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.nullness.qual; +import org.checkerframework.framework.qual.PreconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PreconditionAnnotation; // TODO: In a fix for https://tinyurl.com/cfissue/1917, add the text: Every prefix expression must // also be non-null; for example, {@code @RequiresNonNull(expression="a.b.c")} implies that both @@ -65,32 +66,32 @@ @Repeatable(RequiresNonNull.List.class) @PreconditionAnnotation(qualifier = NonNull.class) public @interface RequiresNonNull { - /** - * The Java expressions that need to be {@link - * org.checkerframework.checker.nullness.qual.NonNull}. - * - * @return the Java expressions that need to be {@link - * org.checkerframework.checker.nullness.qual.NonNull} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * The Java expressions that need to be {@link + * org.checkerframework.checker.nullness.qual.NonNull}. + * + * @return the Java expressions that need to be {@link + * org.checkerframework.checker.nullness.qual.NonNull} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); - /** - * A wrapper annotation that makes the {@link RequiresNonNull} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresNonNull} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PreconditionAnnotation(qualifier = NonNull.class) - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link RequiresNonNull} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresNonNull} annotation at the same location. */ - RequiresNonNull[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PreconditionAnnotation(qualifier = NonNull.class) + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + RequiresNonNull[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java index 19e423ca910..72fbdf4f484 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.nullness.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; @@ -13,6 +8,12 @@ import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Used internally by the type system; should never be written by a programmer. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java index 841d4135e0e..a0037970bf8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresent.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; /** * Indicates that the expression evaluates to a non-empty Optional, if the method terminates @@ -40,10 +41,10 @@ @PostconditionAnnotation(qualifier = Present.class) @InheritedAnnotation public @interface EnsuresPresent { - /** - * The expression (of Optional type) that is present, if the method returns normally. - * - * @return the expression (of Optional type) that is present, if the method returns normally - */ - String[] value(); + /** + * The expression (of Optional type) that is present, if the method returns normally. + * + * @return the expression (of Optional type) that is present, if the method returns normally + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java index 1d094ae66c4..0e4fa63f846 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/EnsuresPresentIf.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Indicates that the given expressions of type Optional<T> are present, if the method returns @@ -53,38 +54,38 @@ @ConditionalPostconditionAnnotation(qualifier = Present.class) @InheritedAnnotation public @interface EnsuresPresentIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns the Java expressions of type {@code Optional} that are present after the method - * returns the given result. - * - * @return the Java expressions of type {@code Optional} that are present after the method - * returns the given result - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions of type {@code Optional} that are present after the method + * returns the given result. + * + * @return the Java expressions of type {@code Optional} that are present after the method + * returns the given result + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * A wrapper annotation that makes the {@link EnsuresPresentIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresPresentIf} annotation at the same location. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = Present.class) - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresPresentIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresPresentIf} annotation at the same location. */ - EnsuresPresentIf[] value(); - } + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = Present.class) + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresPresentIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java index b37ac127ffd..49455dbadf0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/MaybePresent.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** * The {@link java.util.Optional Optional} container may or may not contain a value. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java index 755f071ec45..3205701f76b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalBottom.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The bottom type qualifier for the Optional Checker. The only value of this type is {@code null}. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java index 1f7431d6ef5..327773dc1bd 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** Method annotation for methods that create an {@link java.util.Optional}. */ @Documented diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java index c030c88af02..1e2fc09ad7f 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method annotation for methods whose receiver is an {@link java.util.Optional} and return a diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java index 621c8eb32d7..9a023868629 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method annotation for methods whose receiver is an {@link java.util.Optional} and return an diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java index e462ed7732a..d159dd3a6bb 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/PolyPresent.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Optional type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java index 90c59e2c849..90261fe72e6 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/Present.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The {@link java.util.Optional Optional} container definitely contains a (non-null) value. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java index d8754d3beac..d6173ca97a7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/RequiresPresent.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.qual; +import org.checkerframework.framework.qual.PreconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PreconditionAnnotation; /** * Indicates a method precondition: the specified expressions of type Optional must be present @@ -62,30 +63,30 @@ @PreconditionAnnotation(qualifier = Present.class) public @interface RequiresPresent { - /** - * The Java expressions that that need to be {@link Present}. - * - * @return the Java expressions that need to be {@link Present} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] value(); + /** + * The Java expressions that that need to be {@link Present}. + * + * @return the Java expressions that need to be {@link Present} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] value(); - /** - * A wrapper annotation that makes the {@link RequiresPresent} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresPresent} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PreconditionAnnotation(qualifier = Present.class) - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link RequiresPresent} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresPresent} annotation at the same location. */ - RequiresPresent[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PreconditionAnnotation(qualifier = Present.class) + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + RequiresPresent[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java index 712fc8f4b27..ecb47f99bcd 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKey.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.propkey.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type can be used as key in a property file or resource bundle. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java index fc6d6d71167..e45d61a8ab8 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/PropertyKeyBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.propkey.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the PropertyKeyChecker (and associated checkers) qualifier hierarchy. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java index 89a55a0fb36..65f9516faaf 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/propkey/qual/UnknownPropertyKey.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.propkey.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates that the {@code String} type has an unknown property key property. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java index 2519b6749a9..edfd6b591e2 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PartialRegex.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.regex.qual; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates a String that is not a syntactically valid regular expression. The String itself can be @@ -24,9 +25,9 @@ @SubtypeOf(org.checkerframework.checker.regex.qual.UnknownRegex.class) public @interface PartialRegex { - /** - * The String qualified by this annotation. Used to verify concatenation of partial regular - * expressions. Defaults to the empty String. - */ - String value() default ""; + /** + * The String qualified by this annotation. Used to verify concatenation of partial regular + * expressions. Defaults to the empty String. + */ + String value() default ""; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java index 631675cecea..2ed68f54b64 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/PolyRegex.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.regex.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Regex type system. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java index 9f7e67446d6..d7d253e0c1a 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/Regex.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.regex.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * If a type is annotated as {@code @Regex(n)}, then the run-time value is a regular expression with @@ -23,6 +24,6 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf(UnknownRegex.class) public @interface Regex { - /** The number of groups in the regular expression. Defaults to 0. */ - int value() default 0; + /** The number of groups in the regular expression. Defaults to 0. */ + int value() default 0; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java index 2ebb27b3bd1..301b6b74fc4 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/RegexBottom.java @@ -1,16 +1,17 @@ package org.checkerframework.checker.regex.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * The bottom type in the Regex type system. Programmers should rarely write this type. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java index 63410c3da84..398b04b7398 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/qual/UnknownRegex.java @@ -1,16 +1,17 @@ package org.checkerframework.checker.regex.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Represents the top of the Regex qualifier hierarchy. * diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java index 880dcc449ee..3ec7a61428c 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/ArrayWithoutPackage.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.signature.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An identifier or primitive type, followed by any number of array square brackets. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java index c178d9371ff..d3d00e20f73 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/signature/qual/BinaryName.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.signature.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Represents a binary name as defined in the quantity(); + /** + * Returns the base unit to use. + * + * @return the base unit to use + */ + Class quantity(); - /** - * Returns the scaling prefix. - * - * @return the scaling prefix - */ - Prefix prefix() default Prefix.one; + /** + * Returns the scaling prefix. + * + * @return the scaling prefix + */ + Prefix prefix() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java index c8c0756f109..8b4e787f8b9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnitsRelations.java @@ -15,15 +15,15 @@ @Documented @Retention(RetentionPolicy.RUNTIME) public @interface UnitsRelations { - /** - * Returns the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use. - * - * @return the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use - */ - // The more precise type is Class, - // but org.checkerframework.checker.units.UnitsRelations is not in checker-qual.jar, nor can - // it be since it uses AnnotatedTypeMirrors. So this declaration uses a less precise type, and - // UnitsAnnotatedTypeFactory checks that the argument implements - // org.checkerframework.checker.units.UnitsRelations. - Class value(); + /** + * Returns the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use. + * + * @return the subclass of {@link org.checkerframework.checker.units.UnitsRelations} to use + */ + // The more precise type is Class, + // but org.checkerframework.checker.units.UnitsRelations is not in checker-qual.jar, nor can + // it be since it uses AnnotatedTypeMirrors. So this declaration uses a less precise type, and + // UnitsAnnotatedTypeFactory checks that the argument implements + // org.checkerframework.checker.units.UnitsRelations. + Class value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java index 70d98c07bc9..968115b2d30 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/UnknownUnits.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * UnknownUnits is the top type of the type hierarchy. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java index a387cc24d64..5d9769a3a25 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/Volume.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Units of volume. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java index 3952193bebb..91bc988a4cc 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/cd.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Candela (unit of luminance). @@ -18,5 +19,5 @@ @SubtypeOf(Luminance.class) @SuppressWarnings("checkstyle:typename") public @interface cd { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java index a4d7ae62b67..55bccafee67 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/degrees.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Degrees. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java index 54c0da10e4f..711f7439497 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/g.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Gram. @@ -18,5 +19,5 @@ @SubtypeOf(Mass.class) @SuppressWarnings("checkstyle:typename") public @interface g { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java index 6a568a9fd5a..9260815e0cf 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/h.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Hour. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java index fc8e99b1791..2294dd067b5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kN.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Kilonewton. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java index 714cdfb2972..ab2160812ee 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kg.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Kilogram. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java index c2cb32d87dd..5f200d1f553 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Kilometer. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java index 590d0fe2584..2e026b26ce0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km2.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Square kilometer. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java index 8dc4f86e8a7..a4f36d27aa9 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/km3.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Cubic kilometer. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java index 28abf4bd1c0..4fd6e24ec2e 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/kmPERh.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Kilometer per hour. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java index 0b9da5c0e65..688f365dfd0 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Meter. @@ -23,5 +24,5 @@ // @UnitsMultiple(quantity=m.class, prefix=Prefix.one) @SuppressWarnings("checkstyle:typename") public @interface m { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java index dc4473d006d..eb33c261ce7 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m2.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Square meter. @@ -18,6 +19,6 @@ @SubtypeOf(Area.class) @SuppressWarnings("checkstyle:typename") public @interface m2 { - // does this make sense? Is it multiple of (m^2)? Or (multiple of m)^2? - Prefix value() default Prefix.one; + // does this make sense? Is it multiple of (m^2)? Or (multiple of m)^2? + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java index 48de6bb09ac..7c88127862b 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/m3.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Cubic meter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java index b626b345699..9c2c18dded5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Meter per second. @@ -18,5 +19,5 @@ @SubtypeOf(Speed.class) @SuppressWarnings("checkstyle:typename") public @interface mPERs { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java index 71f24403327..42cf3bba135 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mPERs2.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Meter per second squared. @@ -18,5 +19,5 @@ @SubtypeOf(Acceleration.class) @SuppressWarnings("checkstyle:typename") public @interface mPERs2 { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java index b729aa02c23..f1fa0c3e229 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/min.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Minute. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java index dff99618b66..86f55d88976 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Millimeter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java index 2e5569887cb..f51200b7459 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm2.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Square millimeter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java index dc84f012cb8..f670cf1da81 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mm3.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Cubic millimeter. diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java index a1ea94e78e3..06870b3b7d5 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/mol.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Mole (unit of {@link Substance}). @@ -18,5 +19,5 @@ @SubtypeOf(Substance.class) @SuppressWarnings("checkstyle:typename") public @interface mol { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java index 1493949865f..4af13288755 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/radians.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Radians. @@ -18,5 +19,5 @@ @SubtypeOf(Angle.class) @SuppressWarnings("checkstyle:typename") public @interface radians { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java index 89b93fafab5..bda43dda3ba 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/s.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * A second (1/60 of a minute). @@ -18,5 +19,5 @@ @SubtypeOf(Time.class) @SuppressWarnings("checkstyle:typename") public @interface s { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java index ca254a060a1..845dae7bba1 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/qual/t.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.units.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Metric ton. diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java index b93160bde9c..2fa75945acb 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/LeakedToResult.java @@ -1,11 +1,12 @@ package org.checkerframework.common.aliasing.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation is used on a formal parameter to indicate that the parameter may be returned, but diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java index 241fdcbd9d4..409382e15e3 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeAliased.java @@ -1,14 +1,15 @@ package org.checkerframework.common.aliasing.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** * An expression with this type might have an alias. In other words, some other expression, @@ -23,6 +24,6 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy @DefaultFor( - value = {TypeUseLocation.UPPER_BOUND, TypeUseLocation.LOWER_BOUND}, - types = Void.class) + value = {TypeUseLocation.UPPER_BOUND, TypeUseLocation.LOWER_BOUND}, + types = Void.class) public @interface MaybeAliased {} diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java index 9038b6b75b0..0d421fc3242 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/MaybeLeaked.java @@ -1,12 +1,13 @@ package org.checkerframework.common.aliasing.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * Temporary type qualifier: diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java index ad0b07366d5..2417f2e4275 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/NonLeaked.java @@ -1,11 +1,12 @@ package org.checkerframework.common.aliasing.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This annotation is used on a formal parameter to indicate that the parameter is not leaked diff --git a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java index 61945114567..af66458ffa7 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java +++ b/checker-qual/src/main/java/org/checkerframework/common/aliasing/qual/Unique.java @@ -1,11 +1,12 @@ package org.checkerframework.common.aliasing.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type has no aliases. In other words, no other expression, evaluated at diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java index 11e768f2294..264e931be79 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/EnsuresInitializedFields.java @@ -1,14 +1,15 @@ package org.checkerframework.common.initializedfields.qual; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; /** * A method postcondition annotation indicates which fields the method definitely initializes. @@ -22,38 +23,38 @@ @InheritedAnnotation @Repeatable(EnsuresInitializedFields.List.class) public @interface EnsuresInitializedFields { - /** - * The object whose fields this method initializes. - * - * @return object whose fields are initialized - */ - public String[] value() default {"this"}; + /** + * The object whose fields this method initializes. + * + * @return object whose fields are initialized + */ + public String[] value() default {"this"}; - /** - * Fields that this method initializes. - * - * @return fields that this method initializes - */ - @QualifierArgument("value") - public String[] fields(); + /** + * Fields that this method initializes. + * + * @return fields that this method initializes + */ + @QualifierArgument("value") + public String[] fields(); - /** - * A wrapper annotation that makes the {@link EnsuresInitializedFields} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresInitializedFields} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @PostconditionAnnotation(qualifier = InitializedFields.class) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresInitializedFields} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresInitializedFields} annotation at the same location. */ - EnsuresInitializedFields[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @PostconditionAnnotation(qualifier = InitializedFields.class) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresInitializedFields[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java index d83042cc0a0..18ea2e2693a 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFields.java @@ -1,11 +1,12 @@ package org.checkerframework.common.initializedfields.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** * Indicates which fields have definitely been initialized. @@ -17,10 +18,10 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy public @interface InitializedFields { - /** - * Fields that have been initialized. - * - * @return the initialized fields - */ - public String[] value() default {}; + /** + * Fields that have been initialized. + * + * @return the initialized fields + */ + public String[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java index 9eae97f3209..628c199eca5 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/InitializedFieldsBottom.java @@ -1,12 +1,13 @@ package org.checkerframework.common.initializedfields.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type qualifier for the Initialized Fields type system. It is the type of {@code null}. diff --git a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java index 1f25542d80a..6fc0b75789e 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java +++ b/checker-qual/src/main/java/org/checkerframework/common/initializedfields/qual/PolyInitializedFields.java @@ -1,8 +1,9 @@ package org.checkerframework.common.initializedfields.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Polymorphic qualifier for the Initialized Fields type system. diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java index 0cae3ff79e3..8aa0aee983f 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassBound.java @@ -1,11 +1,12 @@ package org.checkerframework.common.reflection.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This represents a {@code Class} object whose run-time value is equal to or a subtype of one of @@ -18,9 +19,9 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({UnknownClass.class}) public @interface ClassBound { - /** - * The binary - * name of the class or classes that upper-bound the values of this Class object. - */ - String[] value(); + /** + * The binary + * name of the class or classes that upper-bound the values of this Class object. + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java index 424efe40bb8..3fb622077f0 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassVal.java @@ -1,11 +1,12 @@ package org.checkerframework.common.reflection.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This represents a {@link java.lang.Class Class<T>} object where the set of possible values @@ -19,13 +20,13 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({UnknownClass.class}) public @interface ClassVal { - /** - * The name of the type that this Class object represents. The name is a "fully-qualified binary - * name" ({@link org.checkerframework.checker.signature.qual.FqBinaryName}): a primitive or binary name, - * possibly followed by some number of array brackets. - * - * @return the name of the type that this Class object represents - */ - String[] value(); + /** + * The name of the type that this Class object represents. The name is a "fully-qualified binary + * name" ({@link org.checkerframework.checker.signature.qual.FqBinaryName}): a primitive or binary + * name, possibly followed by some number of array brackets. + * + * @return the name of the type that this Class object represents + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java index 7654cc078a5..e68c8281686 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/ClassValBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.common.reflection.qual; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the ClassVal type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java index 3c7612321b0..1a053e6a1a4 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodVal.java @@ -1,11 +1,12 @@ package org.checkerframework.common.reflection.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * This represents a set of {@link java.lang.reflect.Method Method} or {@link @@ -23,17 +24,18 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({UnknownMethod.class}) public @interface MethodVal { - /** - * The binary - * name of the class that declares this method. - */ - String[] className(); + /** + * The binary + * name of the class that declares this method. + */ + String[] className(); - /** - * The name of the method that this Method object represents. Use {@code } for constructors. - */ - String[] methodName(); + /** + * The name of the method that this Method object represents. Use {@code } for + * constructors. + */ + String[] methodName(); - /** The number of parameters to the method. */ - int[] params(); + /** The number of parameters to the method. */ + int[] params(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java index d224db54345..3e39e6b04d2 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/MethodValBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.common.reflection.qual; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the MethodVal type system. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java index beb5e1da192..ccea4c45929 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownClass.java @@ -1,16 +1,17 @@ package org.checkerframework.common.reflection.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Represents a Class object whose run-time value is not known at compile time. Also represents * non-Class values. diff --git a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java index 95dc28348a0..bf556909632 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/common/reflection/qual/UnknownMethod.java @@ -1,16 +1,17 @@ package org.checkerframework.common.reflection.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * Represents a {@link java.lang.reflect.Method Method} or {@link java.lang.reflect.Constructor * Constructor} expression whose run-time value is not known at compile time. Also represents diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java index 1099c089933..a3e0f92d8bf 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java +++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/BottomThis.java @@ -1,12 +1,13 @@ package org.checkerframework.common.returnsreceiver.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type for the Returns Receiver Checker's type system. Programmers should rarely write diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java index 4d30a59c72a..646aee27a90 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java +++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/This.java @@ -1,12 +1,13 @@ package org.checkerframework.common.returnsreceiver.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * Write {@code @This} on the return type of a method that always returns its receiver ({@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java index 7ada21419f1..eafa025cdfd 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java +++ b/checker-qual/src/main/java/org/checkerframework/common/returnsreceiver/qual/UnknownThis.java @@ -1,9 +1,5 @@ package org.checkerframework.common.returnsreceiver.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.InvisibleQualifier; @@ -12,6 +8,11 @@ import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * The top type for the Returns Receiver Checker's type system. Values of the annotated type might * be the receiver ({@code this}) or might not. Programmers should rarely write this type. diff --git a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java index 2283dbd7464..93c521d8964 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java +++ b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Bottom.java @@ -1,12 +1,13 @@ package org.checkerframework.common.subtyping.qual; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** * A special annotation intended solely for representing the bottom type in the qualifier hierarchy. * It should not be used! Instead, each type system should define its own dedicated bottom type. diff --git a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java index df08edb7b9b..ab339ca71ea 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java +++ b/checker-qual/src/main/java/org/checkerframework/common/subtyping/qual/Unqualified.java @@ -1,11 +1,12 @@ package org.checkerframework.common.subtyping.qual; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * A special annotation intended solely for representing an unqualified type in the qualifier diff --git a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java index d0bd11cf21b..8345f9bfa2c 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java +++ b/checker-qual/src/main/java/org/checkerframework/common/util/report/qual/ReportUnqualified.java @@ -1,13 +1,14 @@ package org.checkerframework.common.util.report.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation intended solely for representing an unqualified type in the qualifier hierarchy for diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java index 20b19a15bd6..85c2f5b4d6b 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLen.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the length of an array or a string. If an expression's type has this @@ -23,6 +24,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface ArrayLen { - /** The possible lengths of the array. */ - int[] value(); + /** The possible lengths of the array. */ + int[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java index 4a1d8baccaa..1dfd0d97e74 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/ArrayLenRange.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type evaluates to an array or a string whose length is in the given @@ -19,17 +20,17 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf(UnknownVal.class) public @interface ArrayLenRange { - /** - * Smallest value in the range, inclusive. - * - * @return the smallest value in the range, inclusive - */ - int from() default 0; + /** + * Smallest value in the range, inclusive. + * + * @return the smallest value in the range, inclusive + */ + int from() default 0; - /** - * Largest value in the range, inclusive. - * - * @return the largest value in the range, inclusive - */ - int to() default Integer.MAX_VALUE; + /** + * Largest value in the range, inclusive. + * + * @return the largest value in the range, inclusive + */ + int to() default Integer.MAX_VALUE; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java index 66f5a7fcbeb..adbb4d2d06f 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BoolVal.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a bool type. If an expression's type has this @@ -18,6 +19,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface BoolVal { - /** The values that the expression might evaluate to. */ - boolean[] value(); + /** The values that the expression might evaluate to. */ + boolean[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java index 83da15fb1da..26b00d63827 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/BottomVal.java @@ -1,14 +1,15 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** * The bottom type in the Constant Value type system. Programmers should rarely write this type. @@ -21,18 +22,18 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.UPPER_BOUND, TypeUseLocation.LOWER_BOUND}) @SubtypeOf({ - ArrayLen.class, - BoolVal.class, - DoubleVal.class, - IntVal.class, - StringVal.class, - MatchesRegex.class, - DoesNotMatchRegex.class, - ArrayLenRange.class, - IntRange.class, - IntRangeFromPositive.class, - IntRangeFromGTENegativeOne.class, - IntRangeFromNonNegative.class + ArrayLen.class, + BoolVal.class, + DoubleVal.class, + IntVal.class, + StringVal.class, + MatchesRegex.class, + DoesNotMatchRegex.class, + ArrayLenRange.class, + IntRange.class, + IntRangeFromPositive.class, + IntRangeFromGTENegativeOne.class, + IntRangeFromNonNegative.class }) @InvisibleQualifier public @interface BottomVal {} diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java index 8fde439d8e7..8fb1c2667e1 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoesNotMatchRegex.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a String type. The annotation's arguments are @@ -23,10 +24,10 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface DoesNotMatchRegex { - /** - * A set of Java regular expressions. - * - * @return the regular expressions - */ - String[] value(); + /** + * A set of Java regular expressions. + * + * @return the regular expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java index 1bfab7a21d7..612ec4da291 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/DoubleVal.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a double or float type. If an expression's type @@ -21,6 +22,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface DoubleVal { - /** The values that the expression might evaluate to. */ - double[] value(); + /** The values that the expression might evaluate to. */ + double[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java index afbcd1f1e4b..298b1ba3edd 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnsuresMinLenIf.java @@ -1,14 +1,15 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.QualifierArgument; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.QualifierArgument; /** * Indicates that the value of the given expression is a sequence containing at least the given @@ -27,48 +28,48 @@ @InheritedAnnotation @Repeatable(EnsuresMinLenIf.List.class) public @interface EnsuresMinLenIf { - /** - * Returns the return value of the method under which the postcondition holds. - * - * @return the return value of the method under which the postcondition holds - */ - boolean result(); + /** + * Returns the return value of the method under which the postcondition holds. + * + * @return the return value of the method under which the postcondition holds + */ + boolean result(); - /** - * Returns Java expression(s) that are a sequence with the given minimum length after the method - * returns {@link #result}. - * - * @return an array of Java expression(s), each of which is a sequence with the given minimum - * length after the method returns {@link #result} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns Java expression(s) that are a sequence with the given minimum length after the method + * returns {@link #result}. + * + * @return an array of Java expression(s), each of which is a sequence with the given minimum + * length after the method returns {@link #result} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the minimum number of elements in the sequence. - * - * @return the minimum number of elements in the sequence - */ - @QualifierArgument("value") - int targetValue() default 0; + /** + * Returns the minimum number of elements in the sequence. + * + * @return the minimum number of elements in the sequence + */ + @QualifierArgument("value") + int targetValue() default 0; - /** - * A wrapper annotation that makes the {@link EnsuresMinLenIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresMinLenIf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @ConditionalPostconditionAnnotation(qualifier = MinLen.class) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresMinLenIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresMinLenIf} annotation at the same location. */ - EnsuresMinLenIf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @ConditionalPostconditionAnnotation(qualifier = MinLen.class) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresMinLenIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java index c07b66938ca..cfd60863271 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/EnumVal.java @@ -20,10 +20,11 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface EnumVal { - /** - * The simple names of the possible enum values for an expression with the annotated type. - * - * @return the simple names of the possible enum values for an expression with the annotated type - */ - String[] value(); + /** + * The simple names of the possible enum values for an expression with the annotated type. + * + * @return the simple names of the possible enum values for an expression with the annotated + * type + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java index 42fd4b6c2e8..db16428bb02 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRange.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type evaluates to an integral value (byte, short, char, int, or long) in @@ -26,17 +27,17 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf(UnknownVal.class) public @interface IntRange { - /** - * Smallest value in the range, inclusive. - * - * @return the smallest value in the range, inclusive - */ - long from() default Long.MIN_VALUE; + /** + * Smallest value in the range, inclusive. + * + * @return the smallest value in the range, inclusive + */ + long from() default Long.MIN_VALUE; - /** - * Largest value in the range, inclusive. - * - * @return the largest value in the range, inclusive - */ - long to() default Long.MAX_VALUE; + /** + * Largest value in the range, inclusive. + * + * @return the largest value in the range, inclusive + */ + long to() default Long.MAX_VALUE; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java index fc65c4a9f20..231f9116087 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromGTENegativeOne.java @@ -1,10 +1,11 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java index 434ff259de3..0d39d1bf601 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromNonNegative.java @@ -1,10 +1,11 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java index 4941505975a..38fc9f88a18 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntRangeFromPositive.java @@ -1,10 +1,11 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An expression with this type is exactly the same as an {@link IntRange} annotation whose {@code diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java index 3ab290f1e7d..6ee20087e74 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/IntVal.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a byte, short, char, int, or long type. If an @@ -19,6 +20,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface IntVal { - /** The values that the expression might evaluate to. */ - long[] value(); + /** The values that the expression might evaluate to. */ + long[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java index 5c10b127c35..49fa1377a88 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MatchesRegex.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a String type. The annotation's arguments are @@ -23,10 +24,10 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface MatchesRegex { - /** - * A set of Java regular expressions. - * - * @return the regular expressions - */ - String[] value(); + /** + * A set of Java regular expressions. + * + * @return the regular expressions + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java index a05c8431bbf..63a5a04367b 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLen.java @@ -19,6 +19,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface MinLen { - /** The minimum number of elements in this sequence. */ - int value() default 0; + /** The minimum number of elements in this sequence. */ + int value() default 0; } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java index bf120018cd3..ffe7d404fd0 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/MinLenFieldInvariant.java @@ -1,12 +1,13 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.FieldInvariant; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.FieldInvariant; /** * A specialization of {@link FieldInvariant} for specifying the minimum length of an array. A class @@ -20,15 +21,15 @@ @Inherited public @interface MinLenFieldInvariant { - /** - * Min length of the array. Must be greater than the min length of the array as declared in the - * superclass. - */ - int[] minLen(); + /** + * Min length of the array. Must be greater than the min length of the array as declared in the + * superclass. + */ + int[] minLen(); - /** - * The field that has an array length qualifier in the class on which the field invariant is - * written. The field must be final and declared in a superclass. - */ - String[] field(); + /** + * The field that has an array length qualifier in the class on which the field invariant is + * written. The field must be final and declared in a superclass. + */ + String[] field(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java index 8ae998b9b7f..99e6ddec353 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/PolyValue.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the Constant Value Checker. diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java index 6f0ccb4dd77..95472e9746f 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/StringVal.java @@ -1,11 +1,12 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * An annotation indicating the possible values for a String type. If an expression's type has this @@ -18,6 +19,6 @@ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @SubtypeOf({UnknownVal.class}) public @interface StringVal { - /** The values that the expression might evaluate to. */ - String[] value(); + /** The values that the expression might evaluate to. */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java b/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java index 1ae28c0f75b..fdbd1ff373c 100644 --- a/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java +++ b/checker-qual/src/main/java/org/checkerframework/common/value/qual/UnknownVal.java @@ -1,13 +1,14 @@ package org.checkerframework.common.value.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; /** * UnknownVal is a type annotation indicating that the expression's value is not known at compile diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java index edc3948313a..5622d6de0f2 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/AssertMethod.java @@ -38,32 +38,32 @@ @Target(ElementType.METHOD) public @interface AssertMethod { - /** - * The class of the exception thrown by this method. The default is {@link AssertionError}. - * - * @return class of the exception thrown by this method - */ - Class value() default AssertionError.class; + /** + * The class of the exception thrown by this method. The default is {@link AssertionError}. + * + * @return class of the exception thrown by this method + */ + Class value() default AssertionError.class; - /** - * The one-based index of the boolean parameter that is tested. - * - * @return the one-based index of the boolean parameter that is tested - */ - int parameter() default 1; + /** + * The one-based index of the boolean parameter that is tested. + * + * @return the one-based index of the boolean parameter that is tested + */ + int parameter() default 1; - /** - * Returns whether this method asserts that the boolean expression is false. - * - *

          For example, JUnit's Assertions.assertFalse(...) - * throws an exception if the first argument is false. So it is annotated as follows: - * - *

          @AssertMethod(value = AssertionFailedError.class, isAssertFalse = true)
          -   * public static void assertFalse(boolean condition);
          -   * 
          - * - * @return the value for {@link #parameter} on which the method throws an exception - */ - boolean isAssertFalse() default false; + /** + * Returns whether this method asserts that the boolean expression is false. + * + *

          For example, JUnit's Assertions.assertFalse(...) + * throws an exception if the first argument is false. So it is annotated as follows: + * + *

          @AssertMethod(value = AssertionFailedError.class, isAssertFalse = true)
          +     * public static void assertFalse(boolean condition);
          +     * 
          + * + * @return the value for {@link #parameter} on which the method throws an exception + */ + boolean isAssertFalse() default false; } diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java index 9f891090870..1b57e655fd9 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/Pure.java @@ -26,12 +26,12 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface Pure { - /** The type of purity. */ - enum Kind { - /** The method has no visible side effects. */ - SIDE_EFFECT_FREE, + /** The type of purity. */ + enum Kind { + /** The method has no visible side effects. */ + SIDE_EFFECT_FREE, - /** The method returns exactly the same value when called in the same environment. */ - DETERMINISTIC - } + /** The method returns exactly the same value when called in the same environment. */ + DETERMINISTIC + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java index 8c2844ee992..48c4f7ab47f 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/AnnotatedFor.java @@ -29,14 +29,14 @@ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PACKAGE}) public @interface AnnotatedFor { - /** - * Returns the type systems for which the class has been annotated. Legal arguments are any string - * that may be passed to the {@code -processor} command-line argument: the fully-qualified class - * name for the checker, or a shorthand for built-in checkers. Using the annotation with no - * arguments, as in {@code @AnnotatedFor({})}, has no effect. - * - * @return the type systems for which the class has been annotated - * @checker_framework.manual #shorthand-for-checkers Short names for built-in checkers - */ - String[] value(); + /** + * Returns the type systems for which the class has been annotated. Legal arguments are any + * string that may be passed to the {@code -processor} command-line argument: the + * fully-qualified class name for the checker, or a shorthand for built-in checkers. Using the + * annotation with no arguments, as in {@code @AnnotatedFor({})}, has no effect. + * + * @return the type systems for which the class has been annotated + * @checker_framework.manual #shorthand-for-checkers Short names for built-in checkers + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java index f9ef370b591..c6ed6140cf9 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/CFComment.java @@ -30,12 +30,12 @@ @Documented @Retention(RetentionPolicy.SOURCE) public @interface CFComment { - /** - * Comments about Checker Framework annotations. The text is not interpreted by the Checker - * Framework. - * - *

          If you prefix each comment by the name of the type system, the comments are easier to - * understand and search for. - */ - String[] value(); + /** + * Comments about Checker Framework annotations. The text is not interpreted by the Checker + * Framework. + * + *

          If you prefix each comment by the name of the type system, the comments are easier to + * understand and search for. + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java index 9b7feb69237..ac65025991e 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/ConditionalPostconditionAnnotation.java @@ -65,10 +65,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface ConditionalPostconditionAnnotation { - /** - * The qualifier that will be established as a postcondition. - * - *

          This element is analogous to {@link EnsuresQualifierIf#qualifier()}. - */ - Class qualifier(); + /** + * The qualifier that will be established as a postcondition. + * + *

          This element is analogous to {@link EnsuresQualifierIf#qualifier()}. + */ + Class qualifier(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java index 1599464f6ac..ac79c2e99db 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/Covariant.java @@ -32,6 +32,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface Covariant { - /** The zero-based indices of the type parameters that should be treated covariantly. */ - int[] value(); + /** The zero-based indices of the type parameters that should be treated covariantly. */ + int[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java index 71276eab670..2b75ecf37a9 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultFor.java @@ -33,62 +33,62 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface DefaultFor { - /** - * Returns the locations to which the annotation should be applied. - * - * @return the locations to which the annotation should be applied - */ - TypeUseLocation[] value() default {}; + /** + * Returns the locations to which the annotation should be applied. + * + * @return the locations to which the annotation should be applied + */ + TypeUseLocation[] value() default {}; - /** - * Returns {@link TypeKind}s of types for which an annotation should be implicitly added. - * - * @return {@link TypeKind}s of types for which an annotation should be implicitly added - */ - TypeKind[] typeKinds() default {}; + /** + * Returns {@link TypeKind}s of types for which an annotation should be implicitly added. + * + * @return {@link TypeKind}s of types for which an annotation should be implicitly added + */ + TypeKind[] typeKinds() default {}; - /** - * Returns {@link Class}es for which an annotation should be applied. For example, if - * {@code @MyAnno} is meta-annotated with {@code @DefaultFor(classes=String.class)}, then every - * occurrence of {@code String} is actually {@code @MyAnno String}. - * - *

          Only the given types, not their subtypes, receive the default. For instance, if the {@code - * types} element contains only {@code Iterable}, then the default does not apply to a variable or - * expression of type {@code Collection} which is a subtype of {@code Iterable}. - * - * @return {@link Class}es for which an annotation should be applied - */ - Class[] types() default {}; + /** + * Returns {@link Class}es for which an annotation should be applied. For example, if + * {@code @MyAnno} is meta-annotated with {@code @DefaultFor(classes=String.class)}, then every + * occurrence of {@code String} is actually {@code @MyAnno String}. + * + *

          Only the given types, not their subtypes, receive the default. For instance, if the {@code + * types} element contains only {@code Iterable}, then the default does not apply to a variable + * or expression of type {@code Collection} which is a subtype of {@code Iterable}. + * + * @return {@link Class}es for which an annotation should be applied + */ + Class[] types() default {}; - /** - * Returns regular expressions matching names of variables, to whose types the annotation should - * be applied as a default. If a regular expression matches the name of a method, the annotation - * is applied as a default to the return type. - * - *

          The regular expression must match the entire variable or method name. For example, to match - * any name that contains "foo", use ".*foo.*". - * - *

          The default does not apply if the name matches any of the regexes in {@link - * #namesExceptions}. - * - *

          This affects formal parameter types only if one of the following is true: - * - *

            - *
          • The method's source code is available; that is, the method is type-checked along with - * client calls. - *
          • When the method was compiled in a previous run of javac, either the processor was run or - * the {@code -g} command-line option was provided. - *
          - * - * @return regular expressions matching variables to whose type an annotation should be applied - */ - String[] names() default {}; + /** + * Returns regular expressions matching names of variables, to whose types the annotation should + * be applied as a default. If a regular expression matches the name of a method, the annotation + * is applied as a default to the return type. + * + *

          The regular expression must match the entire variable or method name. For example, to + * match any name that contains "foo", use ".*foo.*". + * + *

          The default does not apply if the name matches any of the regexes in {@link + * #namesExceptions}. + * + *

          This affects formal parameter types only if one of the following is true: + * + *

            + *
          • The method's source code is available; that is, the method is type-checked along with + * client calls. + *
          • When the method was compiled in a previous run of javac, either the processor was run + * or the {@code -g} command-line option was provided. + *
          + * + * @return regular expressions matching variables to whose type an annotation should be applied + */ + String[] names() default {}; - /** - * Returns exceptions to regular expression rules. - * - * @return exceptions to regular expression rules - * @see #names - */ - String[] namesExceptions() default {}; + /** + * Returns exceptions to regular expression rules. + * + * @return exceptions to regular expression rules + * @see #names + */ + String[] namesExceptions() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java index e08c27d7b65..1d993322abc 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifier.java @@ -39,49 +39,6 @@ @Documented @Retention(RetentionPolicy.SOURCE) @Target({ - ElementType.PACKAGE, - ElementType.TYPE, - ElementType.CONSTRUCTOR, - ElementType.METHOD, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.PARAMETER -}) -@Repeatable(DefaultQualifier.List.class) -public @interface DefaultQualifier { - - /** - * The Class for the default annotation. - * - *

          To prevent affecting other type systems, always specify an annotation in your own type - * hierarchy. (For example, do not set {@link - * org.checkerframework.common.subtyping.qual.Unqualified} as the default.) - */ - Class value(); - - /** - * Returns the locations to which the annotation should be applied. - * - * @return the locations to which the annotation should be applied - */ - TypeUseLocation[] locations() default {TypeUseLocation.ALL}; - - /** - * When used on a package, whether the defaults should also apply to subpackages. - * - * @return whether the default should be inherited by subpackages - */ - boolean applyToSubpackages() default true; - - /** - * A wrapper annotation that makes the {@link DefaultQualifier} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link DefaultQualifier} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.PACKAGE, ElementType.TYPE, ElementType.CONSTRUCTOR, @@ -89,13 +46,56 @@ ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.PARAMETER - }) - public static @interface List { +}) +@Repeatable(DefaultQualifier.List.class) +public @interface DefaultQualifier { + + /** + * The Class for the default annotation. + * + *

          To prevent affecting other type systems, always specify an annotation in your own type + * hierarchy. (For example, do not set {@link + * org.checkerframework.common.subtyping.qual.Unqualified} as the default.) + */ + Class value(); + + /** + * Returns the locations to which the annotation should be applied. + * + * @return the locations to which the annotation should be applied + */ + TypeUseLocation[] locations() default {TypeUseLocation.ALL}; + + /** + * When used on a package, whether the defaults should also apply to subpackages. + * + * @return whether the default should be inherited by subpackages + */ + boolean applyToSubpackages() default true; + /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link DefaultQualifier} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link DefaultQualifier} annotation at the same location. */ - DefaultQualifier[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ + ElementType.PACKAGE, + ElementType.TYPE, + ElementType.CONSTRUCTOR, + ElementType.METHOD, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.PARAMETER + }) + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + DefaultQualifier[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java index c88065e8d1a..961ed0421f9 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/DefaultQualifierForUse.java @@ -15,6 +15,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DefaultQualifierForUse { - /** Qualifier to add to all unannotated uses of the type with this declaration annotation. */ - Class[] value() default {}; + /** Qualifier to add to all unannotated uses of the type with this declaration annotation. */ + Class[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java index 3c897376109..f6d4232f977 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifier.java @@ -36,37 +36,39 @@ @InheritedAnnotation @Repeatable(EnsuresQualifier.List.class) public @interface EnsuresQualifier { - /** - * Returns the Java expressions for which the qualifier holds after successful method termination. - * - * @return the Java expressions for which the qualifier holds after successful method termination - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions for which the qualifier holds after successful method + * termination. + * + * @return the Java expressions for which the qualifier holds after successful method + * termination + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the qualifier that is guaranteed to hold on successful termination of the method. - * - * @return the qualifier that is guaranteed to hold on successful termination of the method - */ - Class qualifier(); + /** + * Returns the qualifier that is guaranteed to hold on successful termination of the method. + * + * @return the qualifier that is guaranteed to hold on successful termination of the method + */ + Class qualifier(); - /** - * A wrapper annotation that makes the {@link EnsuresQualifier} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresQualifier} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresQualifier} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresQualifier} annotation at the same location. */ - EnsuresQualifier[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresQualifier[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java index e5e6734255d..4d502b6d38d 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/EnsuresQualifierIf.java @@ -39,48 +39,48 @@ @InheritedAnnotation @Repeatable(EnsuresQualifierIf.List.class) public @interface EnsuresQualifierIf { - /** - * Returns the return value of the method that needs to hold for the postcondition to hold. - * - * @return the return value of the method that needs to hold for the postcondition to hold - */ - boolean result(); + /** + * Returns the return value of the method that needs to hold for the postcondition to hold. + * + * @return the return value of the method that needs to hold for the postcondition to hold + */ + boolean result(); - /** - * Returns the Java expressions for which the qualifier holds if the method terminates with return - * value {@link #result()}. - * - * @return the Java expressions for which the qualifier holds if the method terminates with return - * value {@link #result()} - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions for which the qualifier holds if the method terminates with + * return value {@link #result()}. + * + * @return the Java expressions for which the qualifier holds if the method terminates with + * return value {@link #result()} + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the qualifier that is guaranteed to hold if the method terminates with return value - * {@link #result()}. - * - * @return the qualifier that is guaranteed to hold if the method terminates with return value - * {@link #result()} - */ - Class qualifier(); + /** + * Returns the qualifier that is guaranteed to hold if the method terminates with return value + * {@link #result()}. + * + * @return the qualifier that is guaranteed to hold if the method terminates with return value + * {@link #result()} + */ + Class qualifier(); - /** - * A wrapper annotation that makes the {@link EnsuresQualifierIf} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link EnsuresQualifierIf} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD}) - @InheritedAnnotation - public static @interface List { /** - * Return the repeatable annotations. + * A wrapper annotation that makes the {@link EnsuresQualifierIf} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link EnsuresQualifierIf} annotation at the same location. */ - EnsuresQualifierIf[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @InheritedAnnotation + public static @interface List { + /** + * Return the repeatable annotations. + * + * @return the repeatable annotations + */ + EnsuresQualifierIf[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java index 037312637f6..2f5d65e8cf5 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/FieldInvariant.java @@ -27,14 +27,15 @@ @Inherited public @interface FieldInvariant { - /** - * The qualifier on the field. Must be a subtype of the qualifier on the declaration of the field. - */ - Class[] qualifier(); + /** + * The qualifier on the field. Must be a subtype of the qualifier on the declaration of the + * field. + */ + Class[] qualifier(); - /** - * The field that has a more precise type, in the class on which the {@code FieldInvariant} - * annotation is written. The field must be declared in a superclass and must be {@code final}. - */ - String[] field(); + /** + * The field that has a more precise type, in the class on which the {@code FieldInvariant} + * annotation is written. The field must be declared in a superclass and must be {@code final}. + */ + String[] field(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java index 9cc75018286..b04026166b8 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/HasQualifierParameter.java @@ -70,10 +70,10 @@ @Target({ElementType.TYPE, ElementType.PACKAGE}) public @interface HasQualifierParameter { - /** - * Class of the top qualifier for the hierarchy for which this class has a qualifier parameter. - * - * @return the value - */ - Class[] value(); + /** + * Class of the top qualifier for the hierarchy for which this class has a qualifier parameter. + * + * @return the value + */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java index 7b3b291007b..e027ae6153f 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/LiteralKind.java @@ -13,48 +13,50 @@ */ // https://docs.oracle.com/javase/8/docs/technotes/tools/findingclasses.html#bootclass public enum LiteralKind { - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#NULL_LITERAL} trees. */ - NULL, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#INT_LITERAL} trees. */ - INT, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#LONG_LITERAL} trees. */ - LONG, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#FLOAT_LITERAL} trees. */ - FLOAT, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#DOUBLE_LITERAL} trees. */ - DOUBLE, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#BOOLEAN_LITERAL} trees. */ - BOOLEAN, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#CHAR_LITERAL} trees. */ - CHAR, - /** Corresponds to {@link com.sun.source.tree.Tree.Kind#STRING_LITERAL} trees. */ - STRING, - /** Shorthand for all other LiteralKind constants, other than PRIMITIVE. */ - ALL, - /** Shorthand for all primitive LiteralKind constants: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. */ - PRIMITIVE; + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#NULL_LITERAL} trees. */ + NULL, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#INT_LITERAL} trees. */ + INT, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#LONG_LITERAL} trees. */ + LONG, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#FLOAT_LITERAL} trees. */ + FLOAT, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#DOUBLE_LITERAL} trees. */ + DOUBLE, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#BOOLEAN_LITERAL} trees. */ + BOOLEAN, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#CHAR_LITERAL} trees. */ + CHAR, + /** Corresponds to {@link com.sun.source.tree.Tree.Kind#STRING_LITERAL} trees. */ + STRING, + /** Shorthand for all other LiteralKind constants, other than PRIMITIVE. */ + ALL, + /** + * Shorthand for all primitive LiteralKind constants: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. + */ + PRIMITIVE; - /** - * Returns all LiteralKinds except for ALL and PRIMITIVE (which are shorthands for groups of other - * LiteralKinds). - * - * @return list of LiteralKinds except for ALL and PRIMITIVE - */ - public static List allLiteralKinds() { - List list = new ArrayList<>(Arrays.asList(values())); - list.remove(ALL); - list.remove(PRIMITIVE); - return list; - } + /** + * Returns all LiteralKinds except for ALL and PRIMITIVE (which are shorthands for groups of + * other LiteralKinds). + * + * @return list of LiteralKinds except for ALL and PRIMITIVE + */ + public static List allLiteralKinds() { + List list = new ArrayList<>(Arrays.asList(values())); + list.remove(ALL); + list.remove(PRIMITIVE); + return list; + } - /** - * Returns the primitive {@code LiteralKind}s: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. This is - * all LiteralKinds except for NULL, STRING, and ones that are shorthands for groups of other - * LiteralKinds. - * - * @return list of LiteralKinds except for NULL and STRING - */ - public static List primitiveLiteralKinds() { - return Arrays.asList(INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR); - } + /** + * Returns the primitive {@code LiteralKind}s: INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR. This is + * all LiteralKinds except for NULL, STRING, and ones that are shorthands for groups of other + * LiteralKinds. + * + * @return list of LiteralKinds except for NULL and STRING + */ + public static List primitiveLiteralKinds() { + return Arrays.asList(INT, LONG, FLOAT, DOUBLE, BOOLEAN, CHAR); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java index 73b2fde809a..25232ebf100 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/MonotonicQualifier.java @@ -39,5 +39,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface MonotonicQualifier { - Class value(); + Class value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java index d1d1c76658d..c82472cd159 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoDefaultQualifierForUse.java @@ -18,6 +18,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface NoDefaultQualifierForUse { - /** Top qualifier in hierarchies for which no default annotation for use should be applied. */ - Class[] value() default {}; + /** Top qualifier in hierarchies for which no default annotation for use should be applied. */ + Class[] value() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java index 4dd970b9c73..b61b15aa44e 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/NoQualifierParameter.java @@ -32,10 +32,10 @@ @Inherited public @interface NoQualifierParameter { - /** - * Class of the top qualifier for the hierarchy for which this class has no qualifier parameter. - * - * @return the value - */ - Class[] value(); + /** + * Class of the top qualifier for the hierarchy for which this class has no qualifier parameter. + * + * @return the value + */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java index fc2d1278f02..c5829652b90 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/ParametricTypeVariableUseQualifier.java @@ -26,15 +26,15 @@ @Target({ElementType.ANNOTATION_TYPE}) @AnnotatedFor("nullness") public @interface ParametricTypeVariableUseQualifier { - /** - * Indicates which type system this annotation refers to (optional, and usually unnecessary). When - * multiple type hierarchies are supported by a single type system, then each parametric qualifier - * needs to indicate which sub-hierarchy it belongs to. Do so by passing the top qualifier from - * the given hierarchy. - * - * @return the top qualifier in the hierarchy of this qualifier - */ - // We use the meaningless Annotation.class as default value and - // then ensure there is a single top qualifier to use. - Class value() default Annotation.class; + /** + * Indicates which type system this annotation refers to (optional, and usually unnecessary). + * When multiple type hierarchies are supported by a single type system, then each parametric + * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top + * qualifier from the given hierarchy. + * + * @return the top qualifier in the hierarchy of this qualifier + */ + // We use the meaningless Annotation.class as default value and + // then ensure there is a single top qualifier to use. + Class value() default Annotation.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java index adcc8650019..c47a25f15d4 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java @@ -21,15 +21,15 @@ @Target({ElementType.ANNOTATION_TYPE}) @AnnotatedFor("nullness") public @interface PolymorphicQualifier { - /** - * Indicates which type system this annotation refers to (optional, and usually unnecessary). When - * multiple type hierarchies are supported by a single type system, then each polymorphic - * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top - * qualifier from the given hierarchy. - * - * @return the top qualifier in the hierarchy of this qualifier - */ - // We use the meaningless Annotation.class as default value and - // then ensure there is a single top qualifier to use. - Class value() default Annotation.class; + /** + * Indicates which type system this annotation refers to (optional, and usually unnecessary). + * When multiple type hierarchies are supported by a single type system, then each polymorphic + * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top + * qualifier from the given hierarchy. + * + * @return the top qualifier in the hierarchy of this qualifier + */ + // We use the meaningless Annotation.class as default value and + // then ensure there is a single top qualifier to use. + Class value() default Annotation.class; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java index 67bb434e848..1678614c67c 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PostconditionAnnotation.java @@ -60,10 +60,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface PostconditionAnnotation { - /** - * The qualifier that will be established as a postcondition. - * - *

          This element is analogous to {@link EnsuresQualifier#qualifier()}. - */ - Class qualifier(); + /** + * The qualifier that will be established as a postcondition. + * + *

          This element is analogous to {@link EnsuresQualifier#qualifier()}. + */ + Class qualifier(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java index 95af23ec376..542b1fa13b4 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/PreconditionAnnotation.java @@ -56,6 +56,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface PreconditionAnnotation { - /** The qualifier that must be established as a precondition. */ - Class qualifier(); + /** The qualifier that must be established as a precondition. */ + Class qualifier(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java index e56a2800ccc..0a6810569aa 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierArgument.java @@ -37,10 +37,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface QualifierArgument { - /** - * Specifies the name of the argument of the qualifier, that is passed the values held in the - * annotated element. If the value is omitted or is empty, then the name of the annotated element - * is used as the argument name. - */ - String value() default ""; + /** + * Specifies the name of the argument of the qualifier, that is passed the values held in the + * annotated element. If the value is omitted or is empty, then the name of the annotated + * element is used as the argument name. + */ + String value() default ""; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java index 72cf42edf45..6c75761f602 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/QualifierForLiterals.java @@ -15,21 +15,21 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface QualifierForLiterals { - /** - * The kinds of literals whose types have this qualifier. For example, if {@code @MyAnno} is - * meta-annotated with {@code @QualifierForLiterals(LiteralKind.STRING)}, then a literal {@code - * String} constant such as {@code "hello world"} has type {@code @MyAnno String}, but occurrences - * of {@code String} in the source code are not affected. - * - *

          For String literals, also see the {@link #stringPatterns} annotation element/field. - */ - LiteralKind[] value() default {}; + /** + * The kinds of literals whose types have this qualifier. For example, if {@code @MyAnno} is + * meta-annotated with {@code @QualifierForLiterals(LiteralKind.STRING)}, then a literal {@code + * String} constant such as {@code "hello world"} has type {@code @MyAnno String}, but + * occurrences of {@code String} in the source code are not affected. + * + *

          For String literals, also see the {@link #stringPatterns} annotation element/field. + */ + LiteralKind[] value() default {}; - /** - * A string literal that matches any of these patterns has this qualifier. - * - *

          If patterns for multiple qualifiers match, then the string literal is given the greatest - * lower bound of all the matches. - */ - String[] stringPatterns() default {}; + /** + * A string literal that matches any of these patterns has this qualifier. + * + *

          If patterns for multiple qualifiers match, then the string literal is given the greatest + * lower bound of all the matches. + */ + String[] stringPatterns() default {}; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java index 667f408627e..ce53d197aa5 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/RelevantJavaTypes.java @@ -27,17 +27,17 @@ @Target(ElementType.TYPE) @Inherited public @interface RelevantJavaTypes { - /** - * Classes where a type annotation supported by this checker may be written. - * - *

          {@code Object[].class} means that the checker processes all array types. No distinction - * among array types is currently made, and no other array class should be supplied to - * {@code @RelevantJavaTypes}. - * - *

          If a checker processes both primitive and boxed types, both must be specified separately, - * for example as {@code int.class} and {@code Integer.class}. - * - * @return classes where a type annotation supported by this checker may be written - */ - Class[] value(); + /** + * Classes where a type annotation supported by this checker may be written. + * + *

          {@code Object[].class} means that the checker processes all array types. No distinction + * among array types is currently made, and no other array class should be supplied to + * {@code @RelevantJavaTypes}. + * + *

          If a checker processes both primitive and boxed types, both must be specified separately, + * for example as {@code int.class} and {@code Integer.class}. + * + * @return classes where a type annotation supported by this checker may be written + */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java index 5b6bbab7662..2092c396e1a 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/RequiresQualifier.java @@ -21,36 +21,36 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Repeatable(RequiresQualifier.List.class) public @interface RequiresQualifier { - /** - * Returns the Java expressions for which the annotation need to be present. - * - * @return the Java expressions for which the annotation need to be present - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); + /** + * Returns the Java expressions for which the annotation need to be present. + * + * @return the Java expressions for which the annotation need to be present + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); - /** - * Returns the qualifier that is required. - * - * @return the qualifier that is required - */ - Class qualifier(); + /** + * Returns the qualifier that is required. + * + * @return the qualifier that is required + */ + Class qualifier(); - /** - * A wrapper annotation that makes the {@link RequiresQualifier} annotation repeatable. - * - *

          Programmers generally do not need to write this. It is created by Java when a programmer - * writes more than one {@link RequiresQualifier} annotation at the same location. - */ - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - public static @interface List { /** - * Returns the repeatable annotations. + * A wrapper annotation that makes the {@link RequiresQualifier} annotation repeatable. * - * @return the repeatable annotations + *

          Programmers generally do not need to write this. It is created by Java when a programmer + * writes more than one {@link RequiresQualifier} annotation at the same location. */ - RequiresQualifier[] value(); - } + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + public static @interface List { + /** + * Returns the repeatable annotations. + * + * @return the repeatable annotations + */ + RequiresQualifier[] value(); + } } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java index 25d74cbda4a..64dbeff0700 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/StubFiles.java @@ -21,9 +21,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface StubFiles { - /** - * Stub file names. These are basenames: they include the extension (usually ".astub"), but no - * directory component. - */ - String[] value(); + /** + * Stub file names. These are basenames: they include the extension (usually ".astub"), but no + * directory component. + */ + String[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java index 1f6b9543ab6..240c0a4eb73 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java @@ -40,6 +40,6 @@ @Target(ElementType.ANNOTATION_TYPE) @AnnotatedFor("nullness") public @interface SubtypeOf { - /** An array of the supertype qualifiers of the annotated qualifier. */ - Class[] value(); + /** An array of the supertype qualifiers of the annotated qualifier. */ + Class[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java index 6f2055c9a99..d1f544e7017 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TargetLocations.java @@ -34,10 +34,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface TargetLocations { - /** - * Type uses at which the qualifier is permitted to be applied in source code. - * - * @return type-use locations declared in this meta-annotation - */ - TypeUseLocation[] value(); + /** + * Type uses at which the qualifier is permitted to be applied in source code. + * + * @return type-use locations declared in this meta-annotation + */ + TypeUseLocation[] value(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java index 9adef287c7c..e80c6fd573b 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeKind.java @@ -10,66 +10,66 @@ * annotations */ public enum TypeKind { - /** Corresponds to {@link javax.lang.model.type.TypeKind#BOOLEAN} types. */ - BOOLEAN, + /** Corresponds to {@link javax.lang.model.type.TypeKind#BOOLEAN} types. */ + BOOLEAN, - /** Corresponds to {@link javax.lang.model.type.TypeKind#BYTE} types. */ - BYTE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#BYTE} types. */ + BYTE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#SHORT} types. */ - SHORT, + /** Corresponds to {@link javax.lang.model.type.TypeKind#SHORT} types. */ + SHORT, - /** Corresponds to {@link javax.lang.model.type.TypeKind#INT} types. */ - INT, + /** Corresponds to {@link javax.lang.model.type.TypeKind#INT} types. */ + INT, - /** Corresponds to {@link javax.lang.model.type.TypeKind#LONG} types. */ - LONG, + /** Corresponds to {@link javax.lang.model.type.TypeKind#LONG} types. */ + LONG, - /** Corresponds to {@link javax.lang.model.type.TypeKind#CHAR} types. */ - CHAR, + /** Corresponds to {@link javax.lang.model.type.TypeKind#CHAR} types. */ + CHAR, - /** Corresponds to {@link javax.lang.model.type.TypeKind#FLOAT} types. */ - FLOAT, + /** Corresponds to {@link javax.lang.model.type.TypeKind#FLOAT} types. */ + FLOAT, - /** Corresponds to {@link javax.lang.model.type.TypeKind#DOUBLE} types. */ - DOUBLE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#DOUBLE} types. */ + DOUBLE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#VOID} types. */ - VOID, + /** Corresponds to {@link javax.lang.model.type.TypeKind#VOID} types. */ + VOID, - /** Corresponds to {@link javax.lang.model.type.TypeKind#NONE} types. */ - NONE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#NONE} types. */ + NONE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#NULL} types. */ - NULL, + /** Corresponds to {@link javax.lang.model.type.TypeKind#NULL} types. */ + NULL, - /** Corresponds to {@link javax.lang.model.type.TypeKind#ARRAY} types. */ - ARRAY, + /** Corresponds to {@link javax.lang.model.type.TypeKind#ARRAY} types. */ + ARRAY, - /** Corresponds to {@link javax.lang.model.type.TypeKind#DECLARED} types. */ - DECLARED, + /** Corresponds to {@link javax.lang.model.type.TypeKind#DECLARED} types. */ + DECLARED, - /** Corresponds to {@link javax.lang.model.type.TypeKind#ERROR} types. */ - ERROR, + /** Corresponds to {@link javax.lang.model.type.TypeKind#ERROR} types. */ + ERROR, - /** Corresponds to {@link javax.lang.model.type.TypeKind#TYPEVAR} types. */ - TYPEVAR, + /** Corresponds to {@link javax.lang.model.type.TypeKind#TYPEVAR} types. */ + TYPEVAR, - /** Corresponds to {@link javax.lang.model.type.TypeKind#WILDCARD} types. */ - WILDCARD, + /** Corresponds to {@link javax.lang.model.type.TypeKind#WILDCARD} types. */ + WILDCARD, - /** Corresponds to {@link javax.lang.model.type.TypeKind#PACKAGE} types. */ - PACKAGE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#PACKAGE} types. */ + PACKAGE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#EXECUTABLE} types. */ - EXECUTABLE, + /** Corresponds to {@link javax.lang.model.type.TypeKind#EXECUTABLE} types. */ + EXECUTABLE, - /** Corresponds to {@link javax.lang.model.type.TypeKind#OTHER} types. */ - OTHER, + /** Corresponds to {@link javax.lang.model.type.TypeKind#OTHER} types. */ + OTHER, - /** Corresponds to {@link javax.lang.model.type.TypeKind#UNION} types. */ - UNION, + /** Corresponds to {@link javax.lang.model.type.TypeKind#UNION} types. */ + UNION, - /** Corresponds to {@link javax.lang.model.type.TypeKind#INTERSECTION} types. */ - INTERSECTION; + /** Corresponds to {@link javax.lang.model.type.TypeKind#INTERSECTION} types. */ + INTERSECTION; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java index 96ebe745d2e..22809919f57 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java @@ -18,139 +18,139 @@ */ public enum TypeUseLocation { - /** Apply default annotations to unannotated top-level types of fields. */ - FIELD, - - /** - * Apply default annotations to unannotated top-level types of local variables, casts, and - * instanceof. - */ - LOCAL_VARIABLE, - - /** Apply default annotations to unannotated top-level types of resource variables. */ - RESOURCE_VARIABLE, - - /** Apply default annotations to unannotated top-level types of exception parameters. */ - EXCEPTION_PARAMETER, - - /** Apply default annotations to unannotated top-level types of receiver types. */ - RECEIVER, - - /** - * Apply default annotations to unannotated top-level types of formal parameter types, excluding - * the receiver. - */ - PARAMETER, - - /** Apply default annotations to unannotated top-level types of return types. */ - RETURN, - - /** Apply default annotations to unannotated top-level types of constructor result types. */ - CONSTRUCTOR_RESULT, - - /** - * Apply default annotations to unannotated top-level lower bounds of type parameters and - * wildcards, both explicit ones in {@code super} clauses, and implicit lower bounds when no - * explicit {@code extends} or {@code super} clause is present. - */ - LOWER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit lower bounds of wildcards: {@code - * }. Type parameters have no syntax for explicit lower bound types. - */ - EXPLICIT_LOWER_BOUND, - - /** - * Apply default annotations to unannotated implicit lower bounds of type parameters and - * wildcards: {@code } and {@code }, possibly with explicit upper bounds. - */ - // Note: no distinction between implicit lower bound when upper bound is explicit or not, in - // contrast to what we do for upper bounds. We can add that if a type system needs it. - IMPLICIT_LOWER_BOUND, - - /** - * Apply default annotations to unannotated top-level upper bounds of type parameters and - * wildcards: both explicit ones in {@code extends} clauses, and implicit upper bounds when no - * explicit {@code extends} or {@code super} clause is present. - * - *

          Especially useful for parametrized classes that provide a lot of static methods with the - * same generic parameters as the class. - */ - UPPER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit type parameter and wildcard upper - * bounds: {@code } and {@code }. - */ - EXPLICIT_UPPER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit type parameter upper bounds: {@code - * }. - */ - EXPLICIT_TYPE_PARAMETER_UPPER_BOUND, - - /** - * Apply default annotations to unannotated top-level explicit wildcard upper bounds: {@code }. - */ - EXPLICIT_WILDCARD_UPPER_BOUND, - - /** - * Apply default annotations to unannotated upper bounds of type parameters and wildcards without - * explicit upper bounds: {@code }, {@code }, and {@code }. - */ - IMPLICIT_UPPER_BOUND, - - /** - * Apply default annotations to unannotated upper bounds of type parameters without explicit upper - * bounds: {@code }. - */ - IMPLICIT_TYPE_PARAMETER_UPPER_BOUND, - - /** - * Apply default annotations to unannotated upper bounds of wildcards without a super bound: - * {@code }. - */ - IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER, - - /** - * Apply default annotations to unannotated upper bounds of wildcards with a super bound: {@code - * }. - */ - IMPLICIT_WILDCARD_UPPER_BOUND_SUPER, - - /** - * Apply default annotations to unannotated upper bounds of wildcards with or without a super - * bound: {@code } or {@code }. - */ - IMPLICIT_WILDCARD_UPPER_BOUND, - - /** - * Apply default annotations to unannotated type variable uses that are not top-level local - * variables: {@code T field} or {@code List local}. - * - *

          Such uses of type variables are not flow-sensitively refined and are therefore usually - * parametric. - * - *

          To get parametric polymorphism: add a qualifier that is meta-annotated with {@link - * ParametricTypeVariableUseQualifier} to your type system and use it as default for {@code - * TYPE_VARIABLE_USE}, which is treated like no annotation on the type variable use. - * - *

          We could name this constant {@code TYPE_VARIABLE_USE_NOT_TOP_LEVEL_LOCAL_VARIABLE} and - * introduce a separate constant {@code TYPE_VARIABLE_USE_TOP_LEVEL_LOCAL_VARIABLE}. At the moment - * we use the {@code LOCAL_VARIABLE} default for unannotated top-level type variable uses of local - * variables: {@code T local}. - */ - TYPE_VARIABLE_USE, - - /** Apply if nothing more concrete is provided. TODO: clarify relation to ALL. */ - OTHERWISE, - - /** - * Apply default annotations to all type uses other than uses of type parameters. Does not allow - * any of the other constants. Usually you want OTHERWISE. - */ - ALL; + /** Apply default annotations to unannotated top-level types of fields. */ + FIELD, + + /** + * Apply default annotations to unannotated top-level types of local variables, casts, and + * instanceof. + */ + LOCAL_VARIABLE, + + /** Apply default annotations to unannotated top-level types of resource variables. */ + RESOURCE_VARIABLE, + + /** Apply default annotations to unannotated top-level types of exception parameters. */ + EXCEPTION_PARAMETER, + + /** Apply default annotations to unannotated top-level types of receiver types. */ + RECEIVER, + + /** + * Apply default annotations to unannotated top-level types of formal parameter types, excluding + * the receiver. + */ + PARAMETER, + + /** Apply default annotations to unannotated top-level types of return types. */ + RETURN, + + /** Apply default annotations to unannotated top-level types of constructor result types. */ + CONSTRUCTOR_RESULT, + + /** + * Apply default annotations to unannotated top-level lower bounds of type parameters and + * wildcards, both explicit ones in {@code super} clauses, and implicit lower bounds when no + * explicit {@code extends} or {@code super} clause is present. + */ + LOWER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit lower bounds of wildcards: {@code + * }. Type parameters have no syntax for explicit lower bound types. + */ + EXPLICIT_LOWER_BOUND, + + /** + * Apply default annotations to unannotated implicit lower bounds of type parameters and + * wildcards: {@code } and {@code }, possibly with explicit upper bounds. + */ + // Note: no distinction between implicit lower bound when upper bound is explicit or not, in + // contrast to what we do for upper bounds. We can add that if a type system needs it. + IMPLICIT_LOWER_BOUND, + + /** + * Apply default annotations to unannotated top-level upper bounds of type parameters and + * wildcards: both explicit ones in {@code extends} clauses, and implicit upper bounds when no + * explicit {@code extends} or {@code super} clause is present. + * + *

          Especially useful for parametrized classes that provide a lot of static methods with the + * same generic parameters as the class. + */ + UPPER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit type parameter and wildcard upper + * bounds: {@code } and {@code }. + */ + EXPLICIT_UPPER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit type parameter upper bounds: + * {@code }. + */ + EXPLICIT_TYPE_PARAMETER_UPPER_BOUND, + + /** + * Apply default annotations to unannotated top-level explicit wildcard upper bounds: {@code }. + */ + EXPLICIT_WILDCARD_UPPER_BOUND, + + /** + * Apply default annotations to unannotated upper bounds of type parameters and wildcards + * without explicit upper bounds: {@code }, {@code }, and {@code }. + */ + IMPLICIT_UPPER_BOUND, + + /** + * Apply default annotations to unannotated upper bounds of type parameters without explicit + * upper bounds: {@code }. + */ + IMPLICIT_TYPE_PARAMETER_UPPER_BOUND, + + /** + * Apply default annotations to unannotated upper bounds of wildcards without a super bound: + * {@code }. + */ + IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER, + + /** + * Apply default annotations to unannotated upper bounds of wildcards with a super bound: {@code + * }. + */ + IMPLICIT_WILDCARD_UPPER_BOUND_SUPER, + + /** + * Apply default annotations to unannotated upper bounds of wildcards with or without a super + * bound: {@code } or {@code }. + */ + IMPLICIT_WILDCARD_UPPER_BOUND, + + /** + * Apply default annotations to unannotated type variable uses that are not top-level local + * variables: {@code T field} or {@code List local}. + * + *

          Such uses of type variables are not flow-sensitively refined and are therefore usually + * parametric. + * + *

          To get parametric polymorphism: add a qualifier that is meta-annotated with {@link + * ParametricTypeVariableUseQualifier} to your type system and use it as default for {@code + * TYPE_VARIABLE_USE}, which is treated like no annotation on the type variable use. + * + *

          We could name this constant {@code TYPE_VARIABLE_USE_NOT_TOP_LEVEL_LOCAL_VARIABLE} and + * introduce a separate constant {@code TYPE_VARIABLE_USE_TOP_LEVEL_LOCAL_VARIABLE}. At the + * moment we use the {@code LOCAL_VARIABLE} default for unannotated top-level type variable uses + * of local variables: {@code T local}. + */ + TYPE_VARIABLE_USE, + + /** Apply if nothing more concrete is provided. TODO: clarify relation to ALL. */ + OTHERWISE, + + /** + * Apply default annotations to all type uses other than uses of type parameters. Does not allow + * any of the other constants. Usually you want OTHERWISE. + */ + ALL; } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java index e431e76dc32..f0d00d15217 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/Unused.java @@ -39,9 +39,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Unused { - /** - * The field that is annotated with @Unused may not be accessed via a receiver that is annotated - * with the "when" annotation. - */ - Class when(); + /** + * The field that is annotated with @Unused may not be accessed via a receiver that is annotated + * with the "when" annotation. + */ + Class when(); } diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java index 22dbca461dd..75d24027f27 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java @@ -31,19 +31,19 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface UpperBoundFor { - /** - * Returns {@link TypeKind}s of types that get an upper bound. The meta-annotated annotation is - * the upper bound. - * - * @return {@link TypeKind}s of types that get an upper bound - */ - TypeKind[] typeKinds() default {}; + /** + * Returns {@link TypeKind}s of types that get an upper bound. The meta-annotated annotation is + * the upper bound. + * + * @return {@link TypeKind}s of types that get an upper bound + */ + TypeKind[] typeKinds() default {}; - /** - * Returns {@link Class}es that should get an upper bound. The meta-annotated annotation is the - * upper bound. - * - * @return {@link Class}es that get an upper bound - */ - Class[] types() default {}; + /** + * Returns {@link Class}es that should get an upper bound. The meta-annotated annotation is the + * upper bound. + * + * @return {@link Class}es that get an upper bound + */ + Class[] types() default {}; } diff --git a/checker-util/build.gradle b/checker-util/build.gradle index 1d6daf11309..693edfd979f 100644 --- a/checker-util/build.gradle +++ b/checker-util/build.gradle @@ -1,41 +1,41 @@ plugins { - id 'java-library' + id 'java-library' } dependencies { - api project(':checker-qual') - // Don't add implementation dependencies; checker-util.jar should have no dependencies. + api project(':checker-qual') + // Don't add implementation dependencies; checker-util.jar should have no dependencies. - testImplementation "junit:junit:${versions.junit}" + testImplementation "junit:junit:${versions.junit}" } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerUtilPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Util' - description = 'checker-util contains utility classes for programmers to use at run time.' - licenses { - license { - name = 'The MIT License' - url = 'http://opensource.org/licenses/MIT' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Util' + description = 'checker-util contains utility classes for programmers to use at run time.' + licenses { + license { + name = 'The MIT License' + url = 'http://opensource.org/licenses/MIT' + distribution = 'repo' + } + } } - } } publishing { - publications { - checkerUtil(MavenPublication) { - checkerUtilPom it + publications { + checkerUtil(MavenPublication) { + checkerUtilPom it + } } - } } signing { - sign publishing.publications.checkerUtil + sign publishing.publications.checkerUtil } diff --git a/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java index 087b47ae0c2..0cc17af2279 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/formatter/util/FormatUtil.java @@ -1,5 +1,10 @@ package org.checkerframework.checker.formatter.util; +import org.checkerframework.checker.formatter.qual.ConversionCategory; +import org.checkerframework.checker.formatter.qual.ReturnsFormat; +import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.framework.qual.AnnotatedFor; + import java.util.ArrayList; import java.util.HashMap; import java.util.IllegalFormatConversionException; @@ -8,316 +13,319 @@ import java.util.MissingFormatArgumentException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.checkerframework.checker.formatter.qual.ConversionCategory; -import org.checkerframework.checker.formatter.qual.ReturnsFormat; -import org.checkerframework.checker.regex.qual.Regex; -import org.checkerframework.framework.qual.AnnotatedFor; /** This class provides a collection of utilities to ease working with format strings. */ @AnnotatedFor("nullness") public class FormatUtil { - /** - * A representation of a format specifier, which is represented by "%..." in the format string. - * Indicates how to convert a value into a string. - */ - private static class Conversion { - /** The index in the argument list. */ - private final int index; - - /** The conversion category. */ - private final ConversionCategory cath; - /** - * Construct a new Conversion. - * - * @param index the index in the argument list - * @param c the conversion character + * A representation of a format specifier, which is represented by "%..." in the format string. + * Indicates how to convert a value into a string. */ - public Conversion(char c, int index) { - this.index = index; - this.cath = ConversionCategory.fromConversionChar(c); + private static class Conversion { + /** The index in the argument list. */ + private final int index; + + /** The conversion category. */ + private final ConversionCategory cath; + + /** + * Construct a new Conversion. + * + * @param index the index in the argument list + * @param c the conversion character + */ + public Conversion(char c, int index) { + this.index = index; + this.cath = ConversionCategory.fromConversionChar(c); + } + + /** + * Returns the index in the argument list. + * + * @return the index in the argument list + */ + int index() { + return index; + } + + /** + * Returns the conversion category. + * + * @return the conversion category + */ + ConversionCategory category() { + return cath; + } } /** - * Returns the index in the argument list. + * Returns the first argument if the format string is satisfiable, and if the format's + * parameters match the passed {@link ConversionCategory}s. Otherwise throws an exception. * - * @return the index in the argument list + * @param format a format string + * @param cc an array of conversion categories + * @return the {@code format} argument + * @throws IllegalFormatException if the format string is incompatible with the conversion + * categories */ - int index() { - return index; + // TODO introduce more such functions, see RegexUtil for examples + @ReturnsFormat + public static String asFormat(String format, ConversionCategory... cc) + throws IllegalFormatException { + ConversionCategory[] fcc = formatParameterCategories(format); + if (fcc.length != cc.length) { + throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length); + } + + for (int i = 0; i < cc.length; i++) { + if (cc[i] != fcc[i]) { + throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]); + } + } + + return format; } /** - * Returns the conversion category. + * Throws an exception if the format is not syntactically valid. * - * @return the conversion category + * @param format a format string + * @throws IllegalFormatException if the format string is invalid */ - ConversionCategory category() { - return cath; - } - } - - /** - * Returns the first argument if the format string is satisfiable, and if the format's parameters - * match the passed {@link ConversionCategory}s. Otherwise throws an exception. - * - * @param format a format string - * @param cc an array of conversion categories - * @return the {@code format} argument - * @throws IllegalFormatException if the format string is incompatible with the conversion - * categories - */ - // TODO introduce more such functions, see RegexUtil for examples - @ReturnsFormat - public static String asFormat(String format, ConversionCategory... cc) - throws IllegalFormatException { - ConversionCategory[] fcc = formatParameterCategories(format); - if (fcc.length != cc.length) { - throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length); - } - - for (int i = 0; i < cc.length; i++) { - if (cc[i] != fcc[i]) { - throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]); - } + public static void tryFormatSatisfiability(String format) throws IllegalFormatException { + @SuppressWarnings({ + "unused", // called for side effect, to see if it throws an exception + "nullness:argument.type.incompatible", // it's not documented, but String.format permits + // a null array, which it treats as matching any format string (null is supplied to each + // format specifier). + "formatter:format.string.invalid", // this is a test of format string validity + }) + String unused = String.format(format, (Object[]) null); } - return format; - } - - /** - * Throws an exception if the format is not syntactically valid. - * - * @param format a format string - * @throws IllegalFormatException if the format string is invalid - */ - public static void tryFormatSatisfiability(String format) throws IllegalFormatException { - @SuppressWarnings({ - "unused", // called for side effect, to see if it throws an exception - "nullness:argument.type.incompatible", // it's not documented, but String.format permits - // a null array, which it treats as matching any format string (null is supplied to each - // format specifier). - "formatter:format.string.invalid", // this is a test of format string validity - }) - String unused = String.format(format, (Object[]) null); - } - - /** - * Returns a {@link ConversionCategory} for every conversion found in the format string. - * - *

          Throws an exception if the format is not syntactically valid. - */ - public static ConversionCategory[] formatParameterCategories(String format) - throws IllegalFormatException { - tryFormatSatisfiability(format); - - int last = -1; // index of last argument referenced - int lasto = -1; // last ordinary index - int maxindex = -1; - - Conversion[] cs = parse(format); - Map conv = new HashMap<>(cs.length); - - for (Conversion c : cs) { - int index = c.index(); - switch (index) { - case -1: // relative index - break; - case 0: // ordinary index - lasto++; - last = lasto; - break; - default: // explicit index - last = index - 1; - break; - } - maxindex = Math.max(maxindex, last); - Integer lastKey = last; - conv.put( - last, - ConversionCategory.intersect( - conv.containsKey(lastKey) ? conv.get(lastKey) : ConversionCategory.UNUSED, - c.category())); + /** + * Returns a {@link ConversionCategory} for every conversion found in the format string. + * + *

          Throws an exception if the format is not syntactically valid. + */ + public static ConversionCategory[] formatParameterCategories(String format) + throws IllegalFormatException { + tryFormatSatisfiability(format); + + int last = -1; // index of last argument referenced + int lasto = -1; // last ordinary index + int maxindex = -1; + + Conversion[] cs = parse(format); + Map conv = new HashMap<>(cs.length); + + for (Conversion c : cs) { + int index = c.index(); + switch (index) { + case -1: // relative index + break; + case 0: // ordinary index + lasto++; + last = lasto; + break; + default: // explicit index + last = index - 1; + break; + } + maxindex = Math.max(maxindex, last); + Integer lastKey = last; + conv.put( + last, + ConversionCategory.intersect( + conv.containsKey(lastKey) + ? conv.get(lastKey) + : ConversionCategory.UNUSED, + c.category())); + } + + ConversionCategory[] res = new ConversionCategory[maxindex + 1]; + for (int i = 0; i <= maxindex; ++i) { + Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null + res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED; + } + return res; } - ConversionCategory[] res = new ConversionCategory[maxindex + 1]; - for (int i = 0; i <= maxindex; ++i) { - Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null - res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED; - } - return res; - } - - /** - * A regex that matches a format specifier. Its syntax is specified in the See {@code - * Formatter} documentation. - * - *

          -   * %[argument_index$][flags][width][.precision][t]conversion
          -   * group 1            2      3      4           5 6
          -   * 
          - * - * For dates and times, the [t] is required and precision must not be provided. For types other - * than dates and times, the [t] must not be provided. - */ - private static final @Regex(6) String formatSpecifier = - "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; - - /** The capturing group for the optional {@code t} character. */ - private static final int formatSpecifierT = 5; - - /** - * The capturing group for the last character in a format specifier, which is the conversion - * character unless the {@code t} character was given. - */ - private static final int formatSpecifierConversion = 6; - - /** - * A Pattern that matches a format specifier. - * - * @see #formatSpecifier - */ - private static @Regex(6) Pattern fsPattern = Pattern.compile(formatSpecifier); - - /** - * Return the index, in the argument list, of the value that will be formatted by the matched - * format specifier. - * - * @param m a matcher that matches a format specifier - * @return the index of the argument to format - */ - private static int indexFromFormat(Matcher m) { - int index; - String s = m.group(1); - if (s != null) { // explicit index - index = Integer.parseInt(s.substring(0, s.length() - 1)); - } else { - String group2 = m.group(2); // not @Deterministic, so extract into local var - if (group2 != null && group2.contains(String.valueOf('<'))) { - index = -1; // relative index - } else { - index = 0; // ordinary index - } - } - return index; - } - - /** - * Returns the conversion character from a format specifier.. - * - * @param m a matcher that matches a format specifier - * @return the conversion character from the format specifier - */ - @SuppressWarnings( - "nullness:dereference.of.nullable") // group formatSpecifierConversion always exists - private static char conversionCharFromFormat(@Regex(6) Matcher m) { - String tGroup = m.group(formatSpecifierT); - if (tGroup != null) { - return tGroup.charAt(0); // This is the letter "t" or "T". - } else { - return m.group(formatSpecifierConversion).charAt(0); - } - } - - /** - * Return the conversion character that is in the given format specifier. - * - * @param formatSpecifier a format - * specifier - * @return the conversion character that is in the given format specifier - * @deprecated This method is public only for testing. Use private method {@code - * #conversionCharFromFormat(Matcher)}. - */ - @Deprecated // used only for testing. Use conversionCharFromFormat(Matcher). - public static char conversionCharFromFormat(String formatSpecifier) { - Matcher m = fsPattern.matcher(formatSpecifier); - assert m.find(); - return conversionCharFromFormat(m); - } - - /** - * Parse the given format string, return information about its format specifiers. - * - * @param format a format string - * @return the list of Conversions from the format specifiers in the format string - */ - private static Conversion[] parse(String format) { - ArrayList cs = new ArrayList<>(); - @Regex(7) Matcher m = fsPattern.matcher(format); - while (m.find()) { - char c = conversionCharFromFormat(m); - switch (c) { - case '%': - case 'n': - break; - default: - cs.add(new Conversion(c, indexFromFormat(m))); - } - } - return cs.toArray(new Conversion[0]); - } + /** + * A regex that matches a format specifier. Its syntax is specified in the See {@code + * Formatter} documentation. + * + *
          +     * %[argument_index$][flags][width][.precision][t]conversion
          +     * group 1            2      3      4           5 6
          +     * 
          + * + * For dates and times, the [t] is required and precision must not be provided. For types other + * than dates and times, the [t] must not be provided. + */ + private static final @Regex(6) String formatSpecifier = + "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; - public static class ExcessiveOrMissingFormatArgumentException - extends MissingFormatArgumentException { - private static final long serialVersionUID = 17000126L; + /** The capturing group for the optional {@code t} character. */ + private static final int formatSpecifierT = 5; - private final int expected; - private final int found; + /** + * The capturing group for the last character in a format specifier, which is the conversion + * character unless the {@code t} character was given. + */ + private static final int formatSpecifierConversion = 6; /** - * Constructs an instance of this class with the actual argument length and the expected one. + * A Pattern that matches a format specifier. + * + * @see #formatSpecifier */ - public ExcessiveOrMissingFormatArgumentException(int expected, int found) { - super("-"); - this.expected = expected; - this.found = found; - } + private static @Regex(6) Pattern fsPattern = Pattern.compile(formatSpecifier); - public int getExpected() { - return expected; + /** + * Return the index, in the argument list, of the value that will be formatted by the matched + * format specifier. + * + * @param m a matcher that matches a format specifier + * @return the index of the argument to format + */ + private static int indexFromFormat(Matcher m) { + int index; + String s = m.group(1); + if (s != null) { // explicit index + index = Integer.parseInt(s.substring(0, s.length() - 1)); + } else { + String group2 = m.group(2); // not @Deterministic, so extract into local var + if (group2 != null && group2.contains(String.valueOf('<'))) { + index = -1; // relative index + } else { + index = 0; // ordinary index + } + } + return index; } - public int getFound() { - return found; + /** + * Returns the conversion character from a format specifier.. + * + * @param m a matcher that matches a format specifier + * @return the conversion character from the format specifier + */ + @SuppressWarnings( + "nullness:dereference.of.nullable") // group formatSpecifierConversion always exists + private static char conversionCharFromFormat(@Regex(6) Matcher m) { + String tGroup = m.group(formatSpecifierT); + if (tGroup != null) { + return tGroup.charAt(0); // This is the letter "t" or "T". + } else { + return m.group(formatSpecifierConversion).charAt(0); + } } - @Override - public String getMessage() { - return String.format("Expected %d arguments but found %d.", expected, found); - } - } - - public static class IllegalFormatConversionCategoryException - extends IllegalFormatConversionException { - private static final long serialVersionUID = 17000126L; - - private final ConversionCategory expected; - private final ConversionCategory found; - - /** Constructs an instance of this class with the mismatched conversion and the expected one. */ - public IllegalFormatConversionCategoryException( - ConversionCategory expected, ConversionCategory found) { - super( - expected.chars == null || expected.chars.length() == 0 ? '-' : expected.chars.charAt(0), - found.types == null ? Object.class : found.types[0]); - this.expected = expected; - this.found = found; + /** + * Return the conversion character that is in the given format specifier. + * + * @param formatSpecifier a format + * specifier + * @return the conversion character that is in the given format specifier + * @deprecated This method is public only for testing. Use private method {@code + * #conversionCharFromFormat(Matcher)}. + */ + @Deprecated // used only for testing. Use conversionCharFromFormat(Matcher). + public static char conversionCharFromFormat(String formatSpecifier) { + Matcher m = fsPattern.matcher(formatSpecifier); + assert m.find(); + return conversionCharFromFormat(m); } - public ConversionCategory getExpected() { - return expected; + /** + * Parse the given format string, return information about its format specifiers. + * + * @param format a format string + * @return the list of Conversions from the format specifiers in the format string + */ + private static Conversion[] parse(String format) { + ArrayList cs = new ArrayList<>(); + @Regex(7) Matcher m = fsPattern.matcher(format); + while (m.find()) { + char c = conversionCharFromFormat(m); + switch (c) { + case '%': + case 'n': + break; + default: + cs.add(new Conversion(c, indexFromFormat(m))); + } + } + return cs.toArray(new Conversion[0]); } - public ConversionCategory getFound() { - return found; + public static class ExcessiveOrMissingFormatArgumentException + extends MissingFormatArgumentException { + private static final long serialVersionUID = 17000126L; + + private final int expected; + private final int found; + + /** + * Constructs an instance of this class with the actual argument length and the expected + * one. + */ + public ExcessiveOrMissingFormatArgumentException(int expected, int found) { + super("-"); + this.expected = expected; + this.found = found; + } + + public int getExpected() { + return expected; + } + + public int getFound() { + return found; + } + + @Override + public String getMessage() { + return String.format("Expected %d arguments but found %d.", expected, found); + } } - @Override - public String getMessage() { - return String.format("Expected category %s but found %s.", expected, found); + public static class IllegalFormatConversionCategoryException + extends IllegalFormatConversionException { + private static final long serialVersionUID = 17000126L; + + private final ConversionCategory expected; + private final ConversionCategory found; + + /** + * Constructs an instance of this class with the mismatched conversion and the expected one. + */ + public IllegalFormatConversionCategoryException( + ConversionCategory expected, ConversionCategory found) { + super( + expected.chars == null || expected.chars.length() == 0 + ? '-' + : expected.chars.charAt(0), + found.types == null ? Object.class : found.types[0]); + this.expected = expected; + this.found = found; + } + + public ConversionCategory getExpected() { + return expected; + } + + public ConversionCategory getFound() { + return found; + } + + @Override + public String getMessage() { + return String.format("Expected category %s but found %s.", expected, found); + } } - } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java b/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java index 02302770fc5..251579e6968 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/i18nformatter/util/I18nFormatUtil.java @@ -1,5 +1,15 @@ package org.checkerframework.checker.i18nformatter.util; +import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat; +import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; +import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.framework.qual.AnnotatedFor; + import java.text.ChoiceFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -11,15 +21,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat; -import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; -import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat; -import org.checkerframework.checker.interning.qual.InternedDistinct; -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.checkerframework.framework.qual.AnnotatedFor; /** * This class provides a collection of utilities to ease working with i18n format strings. @@ -29,380 +30,390 @@ @AnnotatedFor("nullness") public class I18nFormatUtil { - /** - * Throws an exception if the format is not syntactically valid. - * - * @param format the format string to parse - */ - @SuppressWarnings( - "nullness:argument.type.incompatible") // It's not documented, but passing null as the - // argument array is supported. - public static void tryFormatSatisfiability(String format) throws IllegalFormatException { - MessageFormat.format(format, (Object[]) null); - } - - /** - * Returns a {@link I18nConversionCategory} for every conversion found in the format string. - * - * @param format the format string to parse - * @throws IllegalFormatException if the format is not syntactically valid - */ - public static I18nConversionCategory[] formatParameterCategories(String format) - throws IllegalFormatException { - tryFormatSatisfiability(format); - I18nConversion[] cs = MessageFormatParser.parse(format); - - int maxIndex = -1; - Map conv = new HashMap<>(cs.length); - - for (I18nConversion c : cs) { - int index = c.index; - Integer indexKey = index; - conv.put( - indexKey, - I18nConversionCategory.intersect( - c.category, - conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED)); - maxIndex = Math.max(maxIndex, index); + /** + * Throws an exception if the format is not syntactically valid. + * + * @param format the format string to parse + */ + @SuppressWarnings( + "nullness:argument.type.incompatible") // It's not documented, but passing null as the + // argument array is supported. + public static void tryFormatSatisfiability(String format) throws IllegalFormatException { + MessageFormat.format(format, (Object[]) null); } - I18nConversionCategory[] res = new I18nConversionCategory[maxIndex + 1]; - for (int i = 0; i <= maxIndex; i++) { - Integer indexKey = i; - res[i] = conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED; - } - return res; - } - - /** - * Returns true if the format string is satisfiable, and if the format's parameters match the - * passed {@link I18nConversionCategory}s. Otherwise an error is thrown. - * - * @param format a format string - * @param cc a list of expected categories for the string's format specifiers - * @return true if the format string's specifiers are the given categories, in order - */ - // TODO introduce more such functions, see RegexUtil for examples - @I18nChecksFormat - public static boolean hasFormat(String format, I18nConversionCategory... cc) { - I18nConversionCategory[] fcc = formatParameterCategories(format); - if (fcc.length != cc.length) { - return false; - } + /** + * Returns a {@link I18nConversionCategory} for every conversion found in the format string. + * + * @param format the format string to parse + * @throws IllegalFormatException if the format is not syntactically valid + */ + public static I18nConversionCategory[] formatParameterCategories(String format) + throws IllegalFormatException { + tryFormatSatisfiability(format); + I18nConversion[] cs = MessageFormatParser.parse(format); + + int maxIndex = -1; + Map conv = new HashMap<>(cs.length); + + for (I18nConversion c : cs) { + int index = c.index; + Integer indexKey = index; + conv.put( + indexKey, + I18nConversionCategory.intersect( + c.category, + conv.containsKey(indexKey) + ? conv.get(indexKey) + : I18nConversionCategory.UNUSED)); + maxIndex = Math.max(maxIndex, index); + } - for (int i = 0; i < cc.length; i++) { - if (!I18nConversionCategory.isSubsetOf(cc[i], fcc[i])) { - return false; - } - } - return true; - } - - @I18nValidFormat - public static boolean isFormat(String format) { - try { - formatParameterCategories(format); - } catch (Exception e) { - return false; + I18nConversionCategory[] res = new I18nConversionCategory[maxIndex + 1]; + for (int i = 0; i <= maxIndex; i++) { + Integer indexKey = i; + res[i] = + conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED; + } + return res; } - return true; - } - - /** An I18n conversion directive. */ - private static class I18nConversion { - /** The index into the string. */ - public final int index; - - /** The conversion category. */ - public final I18nConversionCategory category; /** - * Creates a new I18nConversion. + * Returns true if the format string is satisfiable, and if the format's parameters match the + * passed {@link I18nConversionCategory}s. Otherwise an error is thrown. * - * @param index the index into the string - * @param category the conversion category + * @param format a format string + * @param cc a list of expected categories for the string's format specifiers + * @return true if the format string's specifiers are the given categories, in order */ - public I18nConversion(int index, I18nConversionCategory category) { - this.index = index; - this.category = category; - } + // TODO introduce more such functions, see RegexUtil for examples + @I18nChecksFormat + public static boolean hasFormat(String format, I18nConversionCategory... cc) { + I18nConversionCategory[] fcc = formatParameterCategories(format); + if (fcc.length != cc.length) { + return false; + } - @Override - public String toString() { - return category.toString() + "(index: " + index + ")"; + for (int i = 0; i < cc.length; i++) { + if (!I18nConversionCategory.isSubsetOf(cc[i], fcc[i])) { + return false; + } + } + return true; } - } - private static class MessageFormatParser { - - public static int maxOffset; - - /** The locale to use for formatting numbers and dates. Is set in {@link #parse}. */ - private static @MonotonicNonNull Locale locale; + @I18nValidFormat + public static boolean isFormat(String format) { + try { + formatParameterCategories(format); + } catch (Exception e) { + return false; + } + return true; + } - /** An array of formatters, which are used to format the arguments. Is set in {@link #parse}. */ - private static @MonotonicNonNull List categories; + /** An I18n conversion directive. */ + private static class I18nConversion { + /** The index into the string. */ + public final int index; + + /** The conversion category. */ + public final I18nConversionCategory category; + + /** + * Creates a new I18nConversion. + * + * @param index the index into the string + * @param category the conversion category + */ + public I18nConversion(int index, I18nConversionCategory category) { + this.index = index; + this.category = category; + } - /** - * The argument numbers corresponding to each formatter. (The formatters are stored in the order - * they occur in the pattern, not in the order in which the arguments are specified.) Is set in - * {@link #parse}. - */ - private static @MonotonicNonNull List argumentIndices; - - // I think this means the number of format specifiers in the format string. - /** The number of subformats. */ - private static int numFormat; - - // Indices for segments - private static final int SEG_RAW = 0; - private static final int SEG_INDEX = 1; - private static final int SEG_TYPE = 2; - private static final int SEG_MODIFIER = 3; // modifier or subformat - - // Indices for type keywords - private static final int TYPE_NULL = 0; - private static final int TYPE_NUMBER = 1; - private static final int TYPE_DATE = 2; - private static final int TYPE_TIME = 3; - private static final int TYPE_CHOICE = 4; - - private static final String[] TYPE_KEYWORDS = {"", "number", "date", "time", "choice"}; - - // Indices for number modifiers - private static final int MODIFIER_DEFAULT = 0; // common in number and date-time - private static final int MODIFIER_CURRENCY = 1; - private static final int MODIFIER_PERCENT = 2; - private static final int MODIFIER_INTEGER = 3; - - private static final String[] NUMBER_MODIFIER_KEYWORDS = {"", "currency", "percent", "integer"}; - - private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { - "", "short", "medium", "long", "full" - }; - - @EnsuresNonNull({"categories", "argumentIndices", "locale"}) - public static I18nConversion[] parse(String pattern) { - MessageFormatParser.categories = new ArrayList<>(); - MessageFormatParser.argumentIndices = new ArrayList<>(); - MessageFormatParser.locale = Locale.getDefault(Locale.Category.FORMAT); - applyPattern(pattern); - - I18nConversion[] ret = new I18nConversion[MessageFormatParser.numFormat]; - for (int i = 0; i < MessageFormatParser.numFormat; i++) { - ret[i] = new I18nConversion(argumentIndices.get(i), categories.get(i)); - } - return ret; + @Override + public String toString() { + return category.toString() + "(index: " + index + ")"; + } } - @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i] - @RequiresNonNull({"argumentIndices", "categories", "locale"}) - private static void applyPattern(String pattern) { - @Nullable StringBuilder[] segments = new StringBuilder[4]; - // Allocate only segments[SEG_RAW] here. The rest are - // allocated on demand. - segments[SEG_RAW] = new StringBuilder(); - - int part = SEG_RAW; - MessageFormatParser.numFormat = 0; - boolean inQuote = false; - int braceStack = 0; - maxOffset = -1; - for (int i = 0; i < pattern.length(); ++i) { - char ch = pattern.charAt(i); - if (part == SEG_RAW) { - if (ch == '\'') { - if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') { - segments[part].append(ch); // handle doubles - ++i; - } else { - inQuote = !inQuote; + private static class MessageFormatParser { + + public static int maxOffset; + + /** The locale to use for formatting numbers and dates. Is set in {@link #parse}. */ + private static @MonotonicNonNull Locale locale; + + /** + * An array of formatters, which are used to format the arguments. Is set in {@link #parse}. + */ + private static @MonotonicNonNull List categories; + + /** + * The argument numbers corresponding to each formatter. (The formatters are stored in the + * order they occur in the pattern, not in the order in which the arguments are specified.) + * Is set in {@link #parse}. + */ + private static @MonotonicNonNull List argumentIndices; + + // I think this means the number of format specifiers in the format string. + /** The number of subformats. */ + private static int numFormat; + + // Indices for segments + private static final int SEG_RAW = 0; + private static final int SEG_INDEX = 1; + private static final int SEG_TYPE = 2; + private static final int SEG_MODIFIER = 3; // modifier or subformat + + // Indices for type keywords + private static final int TYPE_NULL = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_DATE = 2; + private static final int TYPE_TIME = 3; + private static final int TYPE_CHOICE = 4; + + private static final String[] TYPE_KEYWORDS = {"", "number", "date", "time", "choice"}; + + // Indices for number modifiers + private static final int MODIFIER_DEFAULT = 0; // common in number and date-time + private static final int MODIFIER_CURRENCY = 1; + private static final int MODIFIER_PERCENT = 2; + private static final int MODIFIER_INTEGER = 3; + + private static final String[] NUMBER_MODIFIER_KEYWORDS = { + "", "currency", "percent", "integer" + }; + + private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { + "", "short", "medium", "long", "full" + }; + + @EnsuresNonNull({"categories", "argumentIndices", "locale"}) + public static I18nConversion[] parse(String pattern) { + MessageFormatParser.categories = new ArrayList<>(); + MessageFormatParser.argumentIndices = new ArrayList<>(); + MessageFormatParser.locale = Locale.getDefault(Locale.Category.FORMAT); + applyPattern(pattern); + + I18nConversion[] ret = new I18nConversion[MessageFormatParser.numFormat]; + for (int i = 0; i < MessageFormatParser.numFormat; i++) { + ret[i] = new I18nConversion(argumentIndices.get(i), categories.get(i)); } - } else if (ch == '{' && !inQuote) { - part = SEG_INDEX; - if (segments[SEG_INDEX] == null) { - segments[SEG_INDEX] = new StringBuilder(); - } - } else { - segments[part].append(ch); - } - } else { - if (inQuote) { // just copy quotes in parts - segments[part].append(ch); - if (ch == '\'') { - inQuote = false; - } - } else { - switch (ch) { - case ',': - if (part < SEG_MODIFIER) { - if (segments[++part] == null) { - segments[part] = new StringBuilder(); - } - } else { - segments[part].append(ch); - } - break; - case '{': - ++braceStack; - segments[part].append(ch); - break; - case '}': - if (braceStack == 0) { - part = SEG_RAW; - makeFormat(numFormat, segments); - numFormat++; - // throw away other segments - segments[SEG_INDEX] = null; - segments[SEG_TYPE] = null; - segments[SEG_MODIFIER] = null; + return ret; + } + + @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i] + @RequiresNonNull({"argumentIndices", "categories", "locale"}) + private static void applyPattern(String pattern) { + @Nullable StringBuilder[] segments = new StringBuilder[4]; + // Allocate only segments[SEG_RAW] here. The rest are + // allocated on demand. + segments[SEG_RAW] = new StringBuilder(); + + int part = SEG_RAW; + MessageFormatParser.numFormat = 0; + boolean inQuote = false; + int braceStack = 0; + maxOffset = -1; + for (int i = 0; i < pattern.length(); ++i) { + char ch = pattern.charAt(i); + if (part == SEG_RAW) { + if (ch == '\'') { + if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') { + segments[part].append(ch); // handle doubles + ++i; + } else { + inQuote = !inQuote; + } + } else if (ch == '{' && !inQuote) { + part = SEG_INDEX; + if (segments[SEG_INDEX] == null) { + segments[SEG_INDEX] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } } else { - --braceStack; - segments[part].append(ch); - } - break; - case ' ': - // Skip any leading space chars for SEG_TYPE. - if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { - segments[part].append(ch); + if (inQuote) { // just copy quotes in parts + segments[part].append(ch); + if (ch == '\'') { + inQuote = false; + } + } else { + switch (ch) { + case ',': + if (part < SEG_MODIFIER) { + if (segments[++part] == null) { + segments[part] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + break; + case '{': + ++braceStack; + segments[part].append(ch); + break; + case '}': + if (braceStack == 0) { + part = SEG_RAW; + makeFormat(numFormat, segments); + numFormat++; + // throw away other segments + segments[SEG_INDEX] = null; + segments[SEG_TYPE] = null; + segments[SEG_MODIFIER] = null; + } else { + --braceStack; + segments[part].append(ch); + } + break; + case ' ': + // Skip any leading space chars for SEG_TYPE. + if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { + segments[part].append(ch); + } + break; + case '\'': + inQuote = true; + segments[part].append(ch); + break; + default: + segments[part].append(ch); + break; + } + } } - break; - case '\'': - inQuote = true; - segments[part].append(ch); - break; - default: - segments[part].append(ch); - break; } - } + if (braceStack == 0 && part != 0) { + maxOffset = -1; + throw new IllegalArgumentException("Unmatched braces in the pattern"); + } } - } - if (braceStack == 0 && part != 0) { - maxOffset = -1; - throw new IllegalArgumentException("Unmatched braces in the pattern"); - } - } - /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */ - @RequiresNonNull({"argumentIndices", "categories", "locale"}) - private static void makeFormat(int offsetNumber, @Nullable StringBuilder[] textSegments) { - String[] segments = new String[textSegments.length]; - for (int i = 0; i < textSegments.length; i++) { - StringBuilder oneseg = textSegments[i]; - segments[i] = (oneseg != null) ? oneseg.toString() : ""; - } - - // get the argument number - int argumentNumber; - try { - argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always - // unlocalized! - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "can't parse argument number: " + segments[SEG_INDEX], e); - } - if (argumentNumber < 0) { - throw new IllegalArgumentException("negative argument number: " + argumentNumber); - } - - int oldMaxOffset = maxOffset; - maxOffset = offsetNumber; - argumentIndices.add(argumentNumber); - - // now get the format - final I18nConversionCategory category; - if (segments[SEG_TYPE].length() != 0) { - int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); - switch (type) { - case TYPE_NULL: - category = I18nConversionCategory.GENERAL; - break; - case TYPE_NUMBER: - switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { - case MODIFIER_DEFAULT: - case MODIFIER_CURRENCY: - case MODIFIER_PERCENT: - case MODIFIER_INTEGER: - break; - default: // DecimalFormat pattern - try { - new DecimalFormat( - segments[SEG_MODIFIER], DecimalFormatSymbols.getInstance(locale)); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - // invalid decimal subformat pattern - throw e; - } - break; + /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */ + @RequiresNonNull({"argumentIndices", "categories", "locale"}) + private static void makeFormat(int offsetNumber, @Nullable StringBuilder[] textSegments) { + String[] segments = new String[textSegments.length]; + for (int i = 0; i < textSegments.length; i++) { + StringBuilder oneseg = textSegments[i]; + segments[i] = (oneseg != null) ? oneseg.toString() : ""; } - category = I18nConversionCategory.NUMBER; - break; - case TYPE_DATE: - case TYPE_TIME: - int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); - if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { - // nothing to do - } else { - // SimpleDateFormat pattern - try { - new SimpleDateFormat(segments[SEG_MODIFIER], locale); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - // invalid date subformat pattern - throw e; - } + + // get the argument number + int argumentNumber; + try { + argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always + // unlocalized! + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "can't parse argument number: " + segments[SEG_INDEX], e); } - category = I18nConversionCategory.DATE; - break; - case TYPE_CHOICE: - if (segments[SEG_MODIFIER].length() == 0) { - throw new IllegalArgumentException( - "Choice Pattern requires Subformat Pattern: " + segments[SEG_MODIFIER]); + if (argumentNumber < 0) { + throw new IllegalArgumentException("negative argument number: " + argumentNumber); } - try { - // ChoiceFormat pattern - new ChoiceFormat(segments[SEG_MODIFIER]); - } catch (Exception e) { - maxOffset = oldMaxOffset; - // invalid choice subformat pattern - throw new IllegalArgumentException( - "Choice Pattern incorrect: " + segments[SEG_MODIFIER], e); + + int oldMaxOffset = maxOffset; + maxOffset = offsetNumber; + argumentIndices.add(argumentNumber); + + // now get the format + final I18nConversionCategory category; + if (segments[SEG_TYPE].length() != 0) { + int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); + switch (type) { + case TYPE_NULL: + category = I18nConversionCategory.GENERAL; + break; + case TYPE_NUMBER: + switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { + case MODIFIER_DEFAULT: + case MODIFIER_CURRENCY: + case MODIFIER_PERCENT: + case MODIFIER_INTEGER: + break; + default: // DecimalFormat pattern + try { + new DecimalFormat( + segments[SEG_MODIFIER], + DecimalFormatSymbols.getInstance(locale)); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + // invalid decimal subformat pattern + throw e; + } + break; + } + category = I18nConversionCategory.NUMBER; + break; + case TYPE_DATE: + case TYPE_TIME: + int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); + if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { + // nothing to do + } else { + // SimpleDateFormat pattern + try { + new SimpleDateFormat(segments[SEG_MODIFIER], locale); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + // invalid date subformat pattern + throw e; + } + } + category = I18nConversionCategory.DATE; + break; + case TYPE_CHOICE: + if (segments[SEG_MODIFIER].length() == 0) { + throw new IllegalArgumentException( + "Choice Pattern requires Subformat Pattern: " + + segments[SEG_MODIFIER]); + } + try { + // ChoiceFormat pattern + new ChoiceFormat(segments[SEG_MODIFIER]); + } catch (Exception e) { + maxOffset = oldMaxOffset; + // invalid choice subformat pattern + throw new IllegalArgumentException( + "Choice Pattern incorrect: " + segments[SEG_MODIFIER], e); + } + category = I18nConversionCategory.NUMBER; + break; + default: + maxOffset = oldMaxOffset; + throw new IllegalArgumentException( + "unknown format type: " + segments[SEG_TYPE]); + } + } else { + category = I18nConversionCategory.GENERAL; } - category = I18nConversionCategory.NUMBER; - break; - default: - maxOffset = oldMaxOffset; - throw new IllegalArgumentException("unknown format type: " + segments[SEG_TYPE]); + categories.add(category); } - } else { - category = I18nConversionCategory.GENERAL; - } - categories.add(category); - } - /** - * Return the index of s in list. If not found, return the index of - * s.trim().toLowerCase(Locale.ROOT) in list. If still not found, return -1. - */ - private static int findKeyword(String s, String[] list) { - for (int i = 0; i < list.length; ++i) { - if (s.equals(list[i])) { - return i; - } - } - - // Try trimmed lowercase. - @SuppressWarnings("interning:assignment.type.incompatible") // test if value changed - @InternedDistinct String ls = s.trim().toLowerCase(Locale.ROOT); - if (ls != s) { // Don't loop if the string trim().toLowerCase returned the same object. - for (int i = 0; i < list.length; ++i) { - if (ls.equals(list[i])) { - return i; - } + /** + * Return the index of s in list. If not found, return the index of + * s.trim().toLowerCase(Locale.ROOT) in list. If still not found, return -1. + */ + private static int findKeyword(String s, String[] list) { + for (int i = 0; i < list.length; ++i) { + if (s.equals(list[i])) { + return i; + } + } + + // Try trimmed lowercase. + @SuppressWarnings("interning:assignment.type.incompatible") // test if value changed + @InternedDistinct String ls = s.trim().toLowerCase(Locale.ROOT); + if (ls != s) { // Don't loop if the string trim().toLowerCase returned the same object. + for (int i = 0; i < list.length; ++i) { + if (ls.equals(list[i])) { + return i; + } + } + } + return -1; } - } - return -1; } - } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java index c7f897f601f..0312586d458 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/NullnessUtil.java @@ -20,294 +20,295 @@ * checker-qual.jar}, along with your binaries. Or, you can copy this class into your own project. */ @SuppressWarnings({ - "nullness", // Nullness utilities are trusted regarding nullness. - "cast" // Casts look redundant if Nullness Checker is not run. + "nullness", // Nullness utilities are trusted regarding nullness. + "cast" // Casts look redundant if Nullness Checker is not run. }) @AnnotatedFor("nullness") public final class NullnessUtil { - private NullnessUtil() { - throw new AssertionError("shouldn't be instantiated"); - } + private NullnessUtil() { + throw new AssertionError("shouldn't be instantiated"); + } - /** - * A method that suppresses warnings from the Nullness Checker. - * - *

          The method takes a possibly-null reference, unsafely casts it to have the @NonNull type - * qualifier, and returns it. The Nullness Checker considers both the return value, and also the - * argument, to be non-null after the method call. Therefore, the {@code castNonNull} method can - * be used either as a cast expression or as a statement. The Nullness Checker issues no warnings - * in any of the following code: - * - *

          
          -   *   // one way to use as a cast:
          -   *  {@literal @}NonNull String s = castNonNull(possiblyNull1);
          -   *
          -   *   // another way to use as a cast:
          -   *   castNonNull(possiblyNull2).toString();
          -   *
          -   *   // one way to use as a statement:
          -   *   castNonNull(possiblyNull3);
          -   *   possiblyNull3.toString();
          -   * 
          - * - * The {@code castNonNull} method is intended to be used in situations where the programmer - * definitively knows that a given reference is not null, but the type system is unable to make - * this deduction. It is not intended for defensive programming, in which a programmer cannot - * prove that the value is not null but wishes to have an earlier indication if it is. See the - * Checker Framework Manual for further discussion. - * - *

          The method throws {@link AssertionError} if Java assertions are enabled and the argument is - * {@code null}. If the exception is ever thrown, then that indicates that the programmer misused - * the method by using it in a circumstance where its argument can be null. - * - * @param the type of the reference - * @param ref a reference of @Nullable type, that is non-null at run time - * @return the argument, casted to have the type qualifier @NonNull - */ - @EnsuresNonNull("#1") - public static @NonNull T castNonNull(@Nullable T ref) { - assert ref != null : "Misuse of castNonNull: called with a null argument"; - return (@NonNull T) ref; - } + /** + * A method that suppresses warnings from the Nullness Checker. + * + *

          The method takes a possibly-null reference, unsafely casts it to have the @NonNull type + * qualifier, and returns it. The Nullness Checker considers both the return value, and also the + * argument, to be non-null after the method call. Therefore, the {@code castNonNull} method can + * be used either as a cast expression or as a statement. The Nullness Checker issues no + * warnings in any of the following code: + * + *

          
          +     *   // one way to use as a cast:
          +     *  {@literal @}NonNull String s = castNonNull(possiblyNull1);
          +     *
          +     *   // another way to use as a cast:
          +     *   castNonNull(possiblyNull2).toString();
          +     *
          +     *   // one way to use as a statement:
          +     *   castNonNull(possiblyNull3);
          +     *   possiblyNull3.toString();
          +     * 
          + * + * The {@code castNonNull} method is intended to be used in situations where the programmer + * definitively knows that a given reference is not null, but the type system is unable to make + * this deduction. It is not intended for defensive programming, in which a programmer cannot + * prove that the value is not null but wishes to have an earlier indication if it is. See the + * Checker Framework Manual for further discussion. + * + *

          The method throws {@link AssertionError} if Java assertions are enabled and the argument + * is {@code null}. If the exception is ever thrown, then that indicates that the programmer + * misused the method by using it in a circumstance where its argument can be null. + * + * @param the type of the reference + * @param ref a reference of @Nullable type, that is non-null at run time + * @return the argument, casted to have the type qualifier @NonNull + */ + @EnsuresNonNull("#1") + public static @NonNull T castNonNull(@Nullable T ref) { + assert ref != null : "Misuse of castNonNull: called with a null argument"; + return (@NonNull T) ref; + } - /** - * Suppress warnings from the Nullness Checker, with a custom error message. See {@link - * #castNonNull(Object)} for documentation. - * - * @see #castNonNull(Object) - * @param the type of the reference - * @param ref a reference of @Nullable type, that is non-null at run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull - */ - @EnsuresNonNull("#1") - public static @NonNull T castNonNull( - @Nullable T ref, String message) { - assert ref != null : "Misuse of castNonNull: called with a null argument: " + message; - return (@NonNull T) ref; - } + /** + * Suppress warnings from the Nullness Checker, with a custom error message. See {@link + * #castNonNull(Object)} for documentation. + * + * @see #castNonNull(Object) + * @param the type of the reference + * @param ref a reference of @Nullable type, that is non-null at run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull + */ + @EnsuresNonNull("#1") + public static @NonNull T castNonNull( + @Nullable T ref, String message) { + assert ref != null : "Misuse of castNonNull: called with a null argument: " + message; + return (@NonNull T) ref; + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [] castNonNullDeep( - T @Nullable [] arr) { - return (@NonNull T[]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [] castNonNullDeep( + T @Nullable [] arr) { + return (@NonNull T[]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [] castNonNullDeep( - T @Nullable [] arr, String message) { - return (@NonNull T[]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [] castNonNullDeep( + T @Nullable [] arr, String message) { + return (@NonNull T[]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type of the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][] castNonNullDeep( - T @Nullable [] @Nullable [] arr) { - return (@NonNull T[][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][] castNonNullDeep( + T @Nullable [] @Nullable [] arr) { + return (@NonNull T[][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type of the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][] castNonNullDeep( - T @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][] castNonNullDeep( + T @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type (three levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (three levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type (three levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (three levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][][]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type (four levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][][][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (four levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][][][]) castNonNullArray(arr, message); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type (four levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][][][]) castNonNullArray(arr, null); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (four levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][][][]) castNonNullArray(arr, null); + } - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that all - * elements at every array level are non-null. - * - * @param the component type (five levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - @EnsuresNonNull("#1") - public static @NonNull T @NonNull [][][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][][][][]) castNonNullArray(arr, message); - } + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (five levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + @EnsuresNonNull("#1") + public static @NonNull T @NonNull [][][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, + String message) { + return (@NonNull T[][][][][]) castNonNullArray(arr, message); + } - /** - * The implementation of castNonNullDeep. - * - * @param the component type (five levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at run - * time - * @param message text to include if there is a non-null value, or null to use uncustomized - * message - * @return the argument, casted to have the type qualifier @NonNull at all levels - */ - private static @NonNull T @NonNull [] castNonNullArray( - T @Nullable [] arr, @Nullable String message) { - assert arr != null - : "Misuse of castNonNullArray: called with a null array argument" - + ((message == null) ? "" : (": " + message)); - for (int i = 0; i < arr.length; ++i) { - assert arr[i] != null - : "Misuse of castNonNull: called with a null array element" - + ((message == null) ? "" : (": " + message)); - checkIfArray(arr[i], message); + /** + * The implementation of castNonNullDeep. + * + * @param the component type (five levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if there is a non-null value, or null to use uncustomized + * message + * @return the argument, casted to have the type qualifier @NonNull at all levels + */ + private static @NonNull T @NonNull [] castNonNullArray( + T @Nullable [] arr, @Nullable String message) { + assert arr != null + : "Misuse of castNonNullArray: called with a null array argument" + + ((message == null) ? "" : (": " + message)); + for (int i = 0; i < arr.length; ++i) { + assert arr[i] != null + : "Misuse of castNonNull: called with a null array element" + + ((message == null) ? "" : (": " + message)); + checkIfArray(arr[i], message); + } + return (@NonNull T[]) arr; } - return (@NonNull T[]) arr; - } - /** - * If the argument is an array, requires it to be non-null at all levels. - * - * @param ref a value; if an array, all of its elements, and their elements recursively, are - * non-null at run time - * @param message text to include if there is a non-null value, or null to use uncustomized - * message - */ - private static void checkIfArray(@NonNull Object ref, @Nullable String message) { - assert ref != null - : "Misuse of checkIfArray: called with a null argument" - + ((message == null) ? "" : (": " + message)); - Class comp = ref.getClass().getComponentType(); - if (comp != null) { - // comp is non-null for arrays, otherwise null. - if (comp.isPrimitive()) { - // Nothing to do for arrays of primitive type: primitives are - // never null. - } else { - castNonNullArray((Object[]) ref, message); - } + /** + * If the argument is an array, requires it to be non-null at all levels. + * + * @param ref a value; if an array, all of its elements, and their elements recursively, are + * non-null at run time + * @param message text to include if there is a non-null value, or null to use uncustomized + * message + */ + private static void checkIfArray(@NonNull Object ref, @Nullable String message) { + assert ref != null + : "Misuse of checkIfArray: called with a null argument" + + ((message == null) ? "" : (": " + message)); + Class comp = ref.getClass().getComponentType(); + if (comp != null) { + // comp is non-null for arrays, otherwise null. + if (comp.isPrimitive()) { + // Nothing to do for arrays of primitive type: primitives are + // never null. + } else { + castNonNullArray((Object[]) ref, message); + } + } } - } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java index 43d8a078f44..06631c75689 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java +++ b/checker-util/src/main/java/org/checkerframework/checker/nullness/util/Opt.java @@ -1,14 +1,15 @@ package org.checkerframework.checker.nullness.util; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; + import java.util.NoSuchElementException; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; /** * Utility class providing every method in {@link java.util.Optional}, but written for possibly-null @@ -31,115 +32,115 @@ @SuppressWarnings("NullableWildcard") // Set upper and lower bound of wildcards public final class Opt { - /** The Opt class cannot be instantiated. */ - private Opt() { - throw new AssertionError("shouldn't be instantiated"); - } + /** The Opt class cannot be instantiated. */ + private Opt() { + throw new AssertionError("shouldn't be instantiated"); + } - /** - * If primary is non-null, returns it, otherwise throws NoSuchElementException. - * - * @param the type of the argument - * @param primary a non-null value to return - * @return {@code primary} if it is non-null - * @throws NoSuchElementException if primary is null - * @see java.util.Optional#get() - */ - // `primary` is @NonNull; otherwise, the method could throw an exception. - public static T get(T primary) { - if (primary == null) { - throw new NoSuchElementException("No value present"); + /** + * If primary is non-null, returns it, otherwise throws NoSuchElementException. + * + * @param the type of the argument + * @param primary a non-null value to return + * @return {@code primary} if it is non-null + * @throws NoSuchElementException if primary is null + * @see java.util.Optional#get() + */ + // `primary` is @NonNull; otherwise, the method could throw an exception. + public static T get(T primary) { + if (primary == null) { + throw new NoSuchElementException("No value present"); + } + return primary; } - return primary; - } - /** - * Returns true if primary is non-null, false if primary is null. - * - * @see java.util.Optional#isPresent() - */ - @EnsuresNonNullIf(expression = "#1", result = true) - public static boolean isPresent(@Nullable Object primary) { - return primary != null; - } + /** + * Returns true if primary is non-null, false if primary is null. + * + * @see java.util.Optional#isPresent() + */ + @EnsuresNonNullIf(expression = "#1", result = true) + public static boolean isPresent(@Nullable Object primary) { + return primary != null; + } - /** - * If primary is non-null, invoke the specified consumer with the value, otherwise do nothing. - * - * @see java.util.Optional#ifPresent(Consumer) - */ - public static void ifPresent(T primary, Consumer<@NonNull ? super @NonNull T> consumer) { - if (primary != null) { - consumer.accept(primary); + /** + * If primary is non-null, invoke the specified consumer with the value, otherwise do nothing. + * + * @see java.util.Optional#ifPresent(Consumer) + */ + public static void ifPresent(T primary, Consumer<@NonNull ? super @NonNull T> consumer) { + if (primary != null) { + consumer.accept(primary); + } } - } - // TODO: Add ifPresentOrElse. + // TODO: Add ifPresentOrElse. - /** - * If primary is non-null, and its value matches the given predicate, return the value. If primary - * is null or its non-null value does not match the predicate, return null. - * - * @see java.util.Optional#filter(Predicate) - */ - public static @Nullable T filter( - T primary, Predicate<@NonNull ? super @NonNull T> predicate) { - if (primary == null) { - return null; - } else { - return predicate.test(primary) ? primary : null; + /** + * If primary is non-null, and its value matches the given predicate, return the value. If + * primary is null or its non-null value does not match the predicate, return null. + * + * @see java.util.Optional#filter(Predicate) + */ + public static @Nullable T filter( + T primary, Predicate<@NonNull ? super @NonNull T> predicate) { + if (primary == null) { + return null; + } else { + return predicate.test(primary) ? primary : null; + } } - } - /** - * If primary is non-null, apply the provided mapping function to it and return the result. If - * primary is null, return null. - * - * @see java.util.Optional#map(Function) - */ - public static @Nullable U map( - T primary, Function<@NonNull ? super @NonNull T, ? extends U> mapper) { - if (primary == null) { - return null; - } else { - return mapper.apply(primary); + /** + * If primary is non-null, apply the provided mapping function to it and return the result. If + * primary is null, return null. + * + * @see java.util.Optional#map(Function) + */ + public static @Nullable U map( + T primary, Function<@NonNull ? super @NonNull T, ? extends U> mapper) { + if (primary == null) { + return null; + } else { + return mapper.apply(primary); + } } - } - // flatMap would have the same signature and implementation as map + // flatMap would have the same signature and implementation as map - /** - * Return primary if it is non-null. If primary is null, return other. - * - * @see java.util.Optional#orElse(Object) - */ - public static @NonNull T orElse(T primary, @NonNull T other) { - return primary != null ? primary : other; - } + /** + * Return primary if it is non-null. If primary is null, return other. + * + * @see java.util.Optional#orElse(Object) + */ + public static @NonNull T orElse(T primary, @NonNull T other) { + return primary != null ? primary : other; + } - /** - * Return {@code primary} if it is non-null. If {@code primary} is null, invoke {@code other} and - * return the result of that invocation. - * - * @see java.util.Optional#orElseGet(Supplier) - */ - public static @NonNull T orElseGet(T primary, Supplier other) { - return primary != null ? primary : other.get(); - } + /** + * Return {@code primary} if it is non-null. If {@code primary} is null, invoke {@code other} + * and return the result of that invocation. + * + * @see java.util.Optional#orElseGet(Supplier) + */ + public static @NonNull T orElseGet(T primary, Supplier other) { + return primary != null ? primary : other.get(); + } - /** - * Return primary if it is non-null. If primary is null, throw an exception to be created by the - * provided supplier. - * - * @see java.util.Optional#orElseThrow(Supplier) - */ - // `primary` is @NonNull; otherwise, the method could throw an exception. - public static T orElseThrow( - T primary, Supplier exceptionSupplier) throws X { - if (primary != null) { - return primary; - } else { - throw exceptionSupplier.get(); + /** + * Return primary if it is non-null. If primary is null, throw an exception to be created by the + * provided supplier. + * + * @see java.util.Optional#orElseThrow(Supplier) + */ + // `primary` is @NonNull; otherwise, the method could throw an exception. + public static T orElseThrow( + T primary, Supplier exceptionSupplier) throws X { + if (primary != null) { + return primary; + } else { + throw exceptionSupplier.get(); + } } - } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java b/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java index e6f078b9191..b8dcf7803b8 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/optional/util/OptionalUtil.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional.util; -import java.util.Optional; import org.checkerframework.checker.optional.qual.EnsuresPresent; import org.checkerframework.checker.optional.qual.MaybePresent; import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.framework.qual.AnnotatedFor; +import java.util.Optional; + /** * This is a utility class for the Optional Checker. * @@ -21,55 +22,55 @@ * checker-qual.jar}, along with your binaries. Or, you can copy this class into your own project. */ @SuppressWarnings({ - "optional", // Optional utilities are trusted regarding the Optional type. - "cast" // Casts look redundant if Optional Checker is not run. + "optional", // Optional utilities are trusted regarding the Optional type. + "cast" // Casts look redundant if Optional Checker is not run. }) @AnnotatedFor("optional") public final class OptionalUtil { - /** The OptionalUtil class should not be instantiated. */ - private OptionalUtil() { - throw new AssertionError("do not instantiate"); - } + /** The OptionalUtil class should not be instantiated. */ + private OptionalUtil() { + throw new AssertionError("do not instantiate"); + } - /** - * A method that suppresses warnings from the Optional Checker. - * - *

          The method takes a possibly-empty Optional reference, unsafely casts it to have the @Present - * type qualifier, and returns it. The Optional Checker considers both the return value, and also - * the argument, to be present after the method call. Therefore, the {@code castPresent} method - * can be used either as a cast expression or as a statement. - * - *

          
          -   *   // one way to use as a cast:
          -   *  {@literal @}Present String s = castPresent(possiblyEmpty1);
          -   *
          -   *   // another way to use as a cast:
          -   *   castPresent(possiblyEmpty2).toString();
          -   *
          -   *   // one way to use as a statement:
          -   *   castPresent(possiblyEmpty3);
          -   *   possiblyEmpty3.toString();
          -   * 
          - * - * The {@code castPresent} method is intended to be used in situations where the programmer - * definitively knows that a given Optional reference is present, but the type system is unable to - * make this deduction. It is not intended for defensive programming, in which a programmer cannot - * prove that the value is not empty but wishes to have an earlier indication if it is. See the - * Checker Framework Manual for further discussion. - * - *

          The method throws {@link AssertionError} if Java assertions are enabled and the argument is - * empty. If the exception is ever thrown, then that indicates that the programmer misused the - * method by using it in a circumstance where its argument can be empty. - * - * @param the type of content of the Optional - * @param ref an Optional reference of @MaybePresent type, that is present at run time - * @return the argument, casted to have the type qualifier @Present - */ - @EnsuresPresent("#1") - public static @Present Optional castPresent( - @MaybePresent Optional ref) { - assert ref.isPresent() : "Misuse of castPresent: called with an empty Optional"; - return (@Present Optional) ref; - } + /** + * A method that suppresses warnings from the Optional Checker. + * + *

          The method takes a possibly-empty Optional reference, unsafely casts it to have + * the @Present type qualifier, and returns it. The Optional Checker considers both the return + * value, and also the argument, to be present after the method call. Therefore, the {@code + * castPresent} method can be used either as a cast expression or as a statement. + * + *

          
          +     *   // one way to use as a cast:
          +     *  {@literal @}Present String s = castPresent(possiblyEmpty1);
          +     *
          +     *   // another way to use as a cast:
          +     *   castPresent(possiblyEmpty2).toString();
          +     *
          +     *   // one way to use as a statement:
          +     *   castPresent(possiblyEmpty3);
          +     *   possiblyEmpty3.toString();
          +     * 
          + * + * The {@code castPresent} method is intended to be used in situations where the programmer + * definitively knows that a given Optional reference is present, but the type system is unable + * to make this deduction. It is not intended for defensive programming, in which a programmer + * cannot prove that the value is not empty but wishes to have an earlier indication if it is. + * See the Checker Framework Manual for further discussion. + * + *

          The method throws {@link AssertionError} if Java assertions are enabled and the argument + * is empty. If the exception is ever thrown, then that indicates that the programmer misused + * the method by using it in a circumstance where its argument can be empty. + * + * @param the type of content of the Optional + * @param ref an Optional reference of @MaybePresent type, that is present at run time + * @return the argument, casted to have the type qualifier @Present + */ + @EnsuresPresent("#1") + public static @Present Optional castPresent( + @MaybePresent Optional ref) { + assert ref.isPresent() : "Misuse of castPresent: called with an empty Optional"; + return (@Present Optional) ref; + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java b/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java index 81ad1edca4d..1abadef0a2f 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/regex/util/RegexUtil.java @@ -3,13 +3,6 @@ package org.checkerframework.checker.regex.util; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.RandomAccess; -import java.util.function.Function; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; import org.checkerframework.checker.index.qual.GTENegativeOne; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.mustcall.qual.MustCallUnknown; @@ -22,6 +15,14 @@ import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.qual.EnsuresQualifierIf; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + /** * Utility methods for regular expressions, most notably for testing whether a string is a regular * expression. @@ -36,446 +37,448 @@ @AnnotatedFor("nullness") public final class RegexUtil { - /** This class is a collection of methods; it does not represent anything. */ - private RegexUtil() { - throw new Error("do not instantiate"); - } - - /** - * A checked version of {@link PatternSyntaxException}. - * - *

          This exception is useful when an illegal regex is detected but the contextual information to - * report a helpful error message is not available at the current depth in the call stack. By - * using a checked PatternSyntaxException the error must be handled up the call stack where a - * better error message can be reported. - * - *

          Typical usage is: - * - *

          -   * void myMethod(...) throws CheckedPatternSyntaxException {
          -   *   ...
          -   *   if (! isRegex(myString)) {
          -   *     throw new CheckedPatternSyntaxException(...);
          -   *   }
          -   *   ... Pattern.compile(myString) ...
          -   * 
          - * - * Simply calling {@code Pattern.compile} would have a similar effect, in that {@code - * PatternSyntaxException} would be thrown at run time if {@code myString} is not a regular - * expression. There are two problems with such an approach. First, a client of {@code myMethod} - * might forget to handle the exception, since {@code PatternSyntaxException} is not checked. - * Also, the Regex Checker would issue a warning about the call to {@code Pattern.compile} that - * might throw an exception. The above usage pattern avoids both problems. - * - * @see PatternSyntaxException - */ - public static class CheckedPatternSyntaxException extends Exception { - - /** Unique identifier for serialization. If you add or remove fields, change this number. */ - private static final long serialVersionUID = 6266881831979001480L; - - /** The PatternSyntaxException that this is a wrapper around. */ - private final PatternSyntaxException pse; + /** This class is a collection of methods; it does not represent anything. */ + private RegexUtil() { + throw new Error("do not instantiate"); + } /** - * Constructs a new CheckedPatternSyntaxException equivalent to the given {@link - * PatternSyntaxException}. + * A checked version of {@link PatternSyntaxException}. + * + *

          This exception is useful when an illegal regex is detected but the contextual information + * to report a helpful error message is not available at the current depth in the call stack. By + * using a checked PatternSyntaxException the error must be handled up the call stack where a + * better error message can be reported. + * + *

          Typical usage is: * - *

          Consider calling this constructor with the result of {@link RegexUtil#regexError}. + *

          +     * void myMethod(...) throws CheckedPatternSyntaxException {
          +     *   ...
          +     *   if (! isRegex(myString)) {
          +     *     throw new CheckedPatternSyntaxException(...);
          +     *   }
          +     *   ... Pattern.compile(myString) ...
          +     * 
          * - * @param pse the PatternSyntaxException to be wrapped + * Simply calling {@code Pattern.compile} would have a similar effect, in that {@code + * PatternSyntaxException} would be thrown at run time if {@code myString} is not a regular + * expression. There are two problems with such an approach. First, a client of {@code myMethod} + * might forget to handle the exception, since {@code PatternSyntaxException} is not checked. + * Also, the Regex Checker would issue a warning about the call to {@code Pattern.compile} that + * might throw an exception. The above usage pattern avoids both problems. + * + * @see PatternSyntaxException */ - public CheckedPatternSyntaxException(PatternSyntaxException pse) { - this.pse = pse; + public static class CheckedPatternSyntaxException extends Exception { + + /** Unique identifier for serialization. If you add or remove fields, change this number. */ + private static final long serialVersionUID = 6266881831979001480L; + + /** The PatternSyntaxException that this is a wrapper around. */ + private final PatternSyntaxException pse; + + /** + * Constructs a new CheckedPatternSyntaxException equivalent to the given {@link + * PatternSyntaxException}. + * + *

          Consider calling this constructor with the result of {@link RegexUtil#regexError}. + * + * @param pse the PatternSyntaxException to be wrapped + */ + public CheckedPatternSyntaxException(PatternSyntaxException pse) { + this.pse = pse; + } + + /** + * Constructs a new CheckedPatternSyntaxException. + * + * @param desc a description of the error + * @param regex the erroneous pattern + * @param index the approximate index in the pattern of the error, or {@code -1} if the + * index is not known + */ + public CheckedPatternSyntaxException(String desc, String regex, @GTENegativeOne int index) { + this(new PatternSyntaxException(desc, regex, index)); + } + + /** + * Retrieves the description of the error. + * + * @return the description of the error + */ + public String getDescription() { + return pse.getDescription(); + } + + /** + * Retrieves the error index. + * + * @return the approximate index in the pattern of the error, or {@code -1} if the index is + * not known + */ + public int getIndex() { + return pse.getIndex(); + } + + /** + * Returns a multi-line string containing the description of the syntax error and its index, + * the erroneous regular-expression pattern, and a visual indication of the error index + * within the pattern. + * + * @return the full detail message + */ + @Override + @Pure + public String getMessage(@GuardSatisfied CheckedPatternSyntaxException this) { + return pse.getMessage(); + } + + /** + * Retrieves the erroneous regular-expression pattern. + * + * @return the erroneous pattern + */ + public String getPattern() { + return pse.getPattern(); + } } /** - * Constructs a new CheckedPatternSyntaxException. + * Returns true if the argument is a syntactically valid regular expression. * - * @param desc a description of the error - * @param regex the erroneous pattern - * @param index the approximate index in the pattern of the error, or {@code -1} if the index is - * not known + * @param s string to check for being a regular expression + * @return true iff s is a regular expression */ - public CheckedPatternSyntaxException(String desc, String regex, @GTENegativeOne int index) { - this(new PatternSyntaxException(desc, regex, index)); + @Pure + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(String s) { + return isRegex(s, 0); } /** - * Retrieves the description of the error. + * Returns true if the argument is a syntactically valid regular expression with at least the + * given number of groups. * - * @return the description of the error + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return true iff s is a regular expression with {@code groups} groups */ - public String getDescription() { - return pse.getDescription(); + @SuppressWarnings("regex") // RegexUtil + @Pure + // @EnsuresQualifierIf annotation is extraneous because this method is special-cased + // in RegexTransfer. + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(String s, int groups) { + Pattern p; + try { + p = Pattern.compile(s); + } catch (PatternSyntaxException e) { + return false; + } + return getGroupCount(p) >= groups; } /** - * Retrieves the error index. + * Returns true if the argument is a syntactically valid regular expression. * - * @return the approximate index in the pattern of the error, or {@code -1} if the index is not - * known + * @param c char to check for being a regular expression + * @return true iff c is a regular expression */ - public int getIndex() { - return pse.getIndex(); + @SuppressWarnings({ + "regex", "lock" + }) // RegexUtil; temp value used in pure method is equal up to equals but not up to == + @Pure + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(char c) { + return isRegex(Character.toString(c)); } /** - * Returns a multi-line string containing the description of the syntax error and its index, the - * erroneous regular-expression pattern, and a visual indication of the error index within the - * pattern. + * Returns the argument as a {@code @Regex String} if it is a regex, otherwise throws an error. + * The purpose of this method is to suppress Regex Checker warnings. It should be very rarely + * needed. * - * @return the full detail message + * @param s string to check for being a regular expression + * @return its argument + * @throws Error if argument is not a regex */ - @Override - @Pure - public String getMessage(@GuardSatisfied CheckedPatternSyntaxException this) { - return pse.getMessage(); + @SideEffectFree + // The return type annotation is irrelevant; this method is special-cased by + // RegexAnnotatedTypeFactory. + public static @Regex String asRegex(String s) { + return asRegex(s, 0); } /** - * Retrieves the erroneous regular-expression pattern. + * Returns the argument as a {@code @Regex(groups) String} if it is a regex with at least the + * given number of groups, otherwise throws an error. The purpose of this method is to suppress + * Regex Checker warnings. It should be very rarely needed. * - * @return the erroneous pattern + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return its argument + * @throws Error if argument is not a regex */ - public String getPattern() { - return pse.getPattern(); - } - } - - /** - * Returns true if the argument is a syntactically valid regular expression. - * - * @param s string to check for being a regular expression - * @return true iff s is a regular expression - */ - @Pure - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(String s) { - return isRegex(s, 0); - } - - /** - * Returns true if the argument is a syntactically valid regular expression with at least the - * given number of groups. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return true iff s is a regular expression with {@code groups} groups - */ - @SuppressWarnings("regex") // RegexUtil - @Pure - // @EnsuresQualifierIf annotation is extraneous because this method is special-cased - // in RegexTransfer. - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(String s, int groups) { - Pattern p; - try { - p = Pattern.compile(s); - } catch (PatternSyntaxException e) { - return false; + @SuppressWarnings("regex") // RegexUtil + @SideEffectFree + // The return type annotation is irrelevant; this method is special-cased by + // RegexAnnotatedTypeFactory. + public static @Regex String asRegex(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + throw new Error(regexErrorMessage(s, groups, actualGroups)); + } + return s; + } catch (PatternSyntaxException e) { + throw new Error(e); + } } - return getGroupCount(p) >= groups; - } - - /** - * Returns true if the argument is a syntactically valid regular expression. - * - * @param c char to check for being a regular expression - * @return true iff c is a regular expression - */ - @SuppressWarnings({ - "regex", "lock" - }) // RegexUtil; temp value used in pure method is equal up to equals but not up to == - @Pure - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(char c) { - return isRegex(Character.toString(c)); - } - - /** - * Returns the argument as a {@code @Regex String} if it is a regex, otherwise throws an error. - * The purpose of this method is to suppress Regex Checker warnings. It should be very rarely - * needed. - * - * @param s string to check for being a regular expression - * @return its argument - * @throws Error if argument is not a regex - */ - @SideEffectFree - // The return type annotation is irrelevant; this method is special-cased by - // RegexAnnotatedTypeFactory. - public static @Regex String asRegex(String s) { - return asRegex(s, 0); - } - - /** - * Returns the argument as a {@code @Regex(groups) String} if it is a regex with at least the - * given number of groups, otherwise throws an error. The purpose of this method is to suppress - * Regex Checker warnings. It should be very rarely needed. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return its argument - * @throws Error if argument is not a regex - */ - @SuppressWarnings("regex") // RegexUtil - @SideEffectFree - // The return type annotation is irrelevant; this method is special-cased by - // RegexAnnotatedTypeFactory. - public static @Regex String asRegex(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - throw new Error(regexErrorMessage(s, groups, actualGroups)); - } - return s; - } catch (PatternSyntaxException e) { - throw new Error(e); + + /** + * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a + * string describing why the argument is not a regex. + * + * @param s string to check for being a regular expression + * @return null, or a string describing why the argument is not a regex + */ + @SideEffectFree + public static @Nullable String regexError(String s) { + return regexError(s, 0); } - } - - /** - * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a - * string describing why the argument is not a regex. - * - * @param s string to check for being a regular expression - * @return null, or a string describing why the argument is not a regex - */ - @SideEffectFree - public static @Nullable String regexError(String s) { - return regexError(s, 0); - } - - /** - * Returns null if the argument is a syntactically valid regular expression with at least the - * given number of groups. Otherwise returns a string describing why the argument is not a regex. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return null, or a string describing why the argument is not a regex - */ - @SuppressWarnings({"regex", "not.sef"}) // RegexUtil; - @SideEffectFree - public static @Nullable String regexError(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - return regexErrorMessage(s, groups, actualGroups); - } - } catch (PatternSyntaxException e) { - return e.getMessage(); + + /** + * Returns null if the argument is a syntactically valid regular expression with at least the + * given number of groups. Otherwise returns a string describing why the argument is not a + * regex. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return null, or a string describing why the argument is not a regex + */ + @SuppressWarnings({"regex", "not.sef"}) // RegexUtil; + @SideEffectFree + public static @Nullable String regexError(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + return regexErrorMessage(s, groups, actualGroups); + } + } catch (PatternSyntaxException e) { + return e.getMessage(); + } + return null; } - return null; - } - - /** - * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a - * PatternSyntaxException describing why the argument is not a regex. - * - * @param s string to check for being a regular expression - * @return null, or a PatternSyntaxException describing why the argument is not a regex - */ - @SideEffectFree - public static @Nullable PatternSyntaxException regexException(String s) { - return regexException(s, 0); - } - - /** - * Returns null if the argument is a syntactically valid regular expression with at least the - * given number of groups. Otherwise returns a PatternSyntaxException describing why the argument - * is not a regex. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return null, or a PatternSyntaxException describing why the argument is not a regex - */ - @SuppressWarnings("regex") // RegexUtil - @SideEffectFree - public static @Nullable PatternSyntaxException regexException(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - return new PatternSyntaxException(regexErrorMessage(s, groups, actualGroups), s, -1); - } - } catch (PatternSyntaxException pse) { - return pse; + + /** + * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a + * PatternSyntaxException describing why the argument is not a regex. + * + * @param s string to check for being a regular expression + * @return null, or a PatternSyntaxException describing why the argument is not a regex + */ + @SideEffectFree + public static @Nullable PatternSyntaxException regexException(String s) { + return regexException(s, 0); } - return null; - } - - /** - * Generates an error message for s when expectedGroups are needed, but s only has actualGroups. - * - * @param s string to check for being a regular expression - * @param expectedGroups the number of needed capturing groups - * @param actualGroups the number of groups that {@code s} has - * @return an error message for s when expectedGroups groups are needed, but s only has - * actualGroups groups - */ - @SideEffectFree - private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) { - return "regex \"" - + s - + "\" has " - + actualGroups - + " groups, but " - + expectedGroups - + " groups are needed."; - } - - /** - * Returns the count of groups in the argument. - * - * @param p pattern whose groups to count - * @return the count of groups in the argument - */ - @SuppressWarnings("lock") // does not depend on object identity - @Pure - private static int getGroupCount(Pattern p) { - return p.matcher("").groupCount(); - } - - /** - * Return the strings such that any one of the regexes matches it. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return the strings such that any one of the regexes matches it - */ - public static List matchesSomeRegex( - Collection strings, Collection<@Regex String> regexes) { - List patterns = mapList(Pattern::compile, regexes); - List result = new ArrayList(strings.size()); - for (String s : strings) { - for (Pattern p : patterns) { - if (p.matcher(s).matches()) { - result.add(s); - break; + + /** + * Returns null if the argument is a syntactically valid regular expression with at least the + * given number of groups. Otherwise returns a PatternSyntaxException describing why the + * argument is not a regex. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return null, or a PatternSyntaxException describing why the argument is not a regex + */ + @SuppressWarnings("regex") // RegexUtil + @SideEffectFree + public static @Nullable PatternSyntaxException regexException(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + return new PatternSyntaxException( + regexErrorMessage(s, groups, actualGroups), s, -1); + } + } catch (PatternSyntaxException pse) { + return pse; } - } + return null; } - return result; - } - - /** - * Return true if every string is matched by at least one regex. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return true if every string is matched by at least one regex - */ - public static boolean everyStringMatchesSomeRegex( - Collection strings, Collection<@Regex String> regexes) { - List patterns = mapList(Pattern::compile, regexes); - outer: - for (String s : strings) { - for (Pattern p : patterns) { - if (p.matcher(s).matches()) { - continue outer; - } - } - return false; + + /** + * Generates an error message for s when expectedGroups are needed, but s only has actualGroups. + * + * @param s string to check for being a regular expression + * @param expectedGroups the number of needed capturing groups + * @param actualGroups the number of groups that {@code s} has + * @return an error message for s when expectedGroups groups are needed, but s only has + * actualGroups groups + */ + @SideEffectFree + private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) { + return "regex \"" + + s + + "\" has " + + actualGroups + + " groups, but " + + expectedGroups + + " groups are needed."; + } + + /** + * Returns the count of groups in the argument. + * + * @param p pattern whose groups to count + * @return the count of groups in the argument + */ + @SuppressWarnings("lock") // does not depend on object identity + @Pure + private static int getGroupCount(Pattern p) { + return p.matcher("").groupCount(); } - return true; - } - - /** - * Return the strings that are matched by no regex. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return the strings such that none of the regexes matches it - */ - public static List matchesNoRegex( - Collection strings, Collection<@Regex String> regexes) { - List patterns = mapList(Pattern::compile, regexes); - List result = new ArrayList(strings.size()); - outer: - for (String s : strings) { - for (Pattern p : patterns) { - if (p.matcher(s).matches()) { - continue outer; + + /** + * Return the strings such that any one of the regexes matches it. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return the strings such that any one of the regexes matches it + */ + public static List matchesSomeRegex( + Collection strings, Collection<@Regex String> regexes) { + List patterns = mapList(Pattern::compile, regexes); + List result = new ArrayList(strings.size()); + for (String s : strings) { + for (Pattern p : patterns) { + if (p.matcher(s).matches()) { + result.add(s); + break; + } + } } - } - result.add(s); + return result; } - return result; - } - - /** - * Return true if no string is matched by any regex. - * - * @param strings a collection of strings - * @param regexes a collection of regular expressions - * @return true if no string is matched by any regex - */ - public static boolean noStringMatchesAnyRegex( - Collection strings, Collection<@Regex String> regexes) { - for (String regex : regexes) { - Pattern p = Pattern.compile(regex); - for (String s : strings) { - if (p.matcher(s).matches()) { - return false; + + /** + * Return true if every string is matched by at least one regex. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return true if every string is matched by at least one regex + */ + public static boolean everyStringMatchesSomeRegex( + Collection strings, Collection<@Regex String> regexes) { + List patterns = mapList(Pattern::compile, regexes); + outer: + for (String s : strings) { + for (Pattern p : patterns) { + if (p.matcher(s).matches()) { + continue outer; + } + } + return false; } - } + return true; } - return true; - } - - /// - /// Utilities - /// - - // This is from CollectionsPlume, but is here to make the file self-contained. - - /** - * Applies the function to each element of the given iterable, producing a list of the results. - * - *

          The point of this method is to make mapping operations more concise. Import it with - * - *

          import static org.plumelib.util.CollectionsPlume.mapList;
          - * - * This method is just like {@code transform}, but with the arguments in the other order. - * - *

          To perform replacement in place, see {@code List.replaceAll}. - * - * @param the type of elements of the given iterable - * @param the type of elements of the result list - * @param f a function - * @param iterable an iterable - * @return a list of the results of applying {@code f} to the elements of {@code iterable} - */ - public static < - @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, - @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> - List mapList( - @MustCallUnknown Function<@MustCallUnknown ? super FROM, ? extends TO> f, - Iterable iterable) { - List result; - - if (iterable instanceof RandomAccess) { - // Per the Javadoc of RandomAccess, an indexed for loop is faster than a foreach loop. - List list = (List) iterable; - int size = list.size(); - result = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - result.add(f.apply(list.get(i))); - } - return result; + + /** + * Return the strings that are matched by no regex. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return the strings such that none of the regexes matches it + */ + public static List matchesNoRegex( + Collection strings, Collection<@Regex String> regexes) { + List patterns = mapList(Pattern::compile, regexes); + List result = new ArrayList(strings.size()); + outer: + for (String s : strings) { + for (Pattern p : patterns) { + if (p.matcher(s).matches()) { + continue outer; + } + } + result.add(s); + } + return result; } - if (iterable instanceof Collection) { - result = new ArrayList<>(((Collection) iterable).size()); - } else { - result = new ArrayList<>(); // no information about size is available + /** + * Return true if no string is matched by any regex. + * + * @param strings a collection of strings + * @param regexes a collection of regular expressions + * @return true if no string is matched by any regex + */ + public static boolean noStringMatchesAnyRegex( + Collection strings, Collection<@Regex String> regexes) { + for (String regex : regexes) { + Pattern p = Pattern.compile(regex); + for (String s : strings) { + if (p.matcher(s).matches()) { + return false; + } + } + } + return true; } - for (FROM elt : iterable) { - result.add(f.apply(elt)); + + /// + /// Utilities + /// + + // This is from CollectionsPlume, but is here to make the file self-contained. + + /** + * Applies the function to each element of the given iterable, producing a list of the results. + * + *

          The point of this method is to make mapping operations more concise. Import it with + * + *

          import static org.plumelib.util.CollectionsPlume.mapList;
          + * + * This method is just like {@code transform}, but with the arguments in the other order. + * + *

          To perform replacement in place, see {@code List.replaceAll}. + * + * @param the type of elements of the given iterable + * @param the type of elements of the result list + * @param f a function + * @param iterable an iterable + * @return a list of the results of applying {@code f} to the elements of {@code iterable} + */ + public static < + @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, + @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> + List mapList( + @MustCallUnknown Function<@MustCallUnknown ? super FROM, ? extends TO> f, + Iterable iterable) { + List result; + + if (iterable instanceof RandomAccess) { + // Per the Javadoc of RandomAccess, an indexed for loop is faster than a foreach loop. + List list = (List) iterable; + int size = list.size(); + result = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + result.add(f.apply(list.get(i))); + } + return result; + } + + if (iterable instanceof Collection) { + result = new ArrayList<>(((Collection) iterable).size()); + } else { + result = new ArrayList<>(); // no information about size is available + } + for (FROM elt : iterable) { + result.add(f.apply(elt)); + } + return result; } - return result; - } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java index 21c9e75f389..8f68a610b7d 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java +++ b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtil.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.signedness.util; +import org.checkerframework.checker.signedness.qual.Unsigned; +import org.checkerframework.framework.qual.AnnotatedFor; + import java.io.IOException; import java.io.RandomAccessFile; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.IntBuffer; -import org.checkerframework.checker.signedness.qual.Unsigned; -import org.checkerframework.framework.qual.AnnotatedFor; /** * Provides static utility methods for unsigned values, beyond what is available in the JDK's @@ -23,517 +24,519 @@ @AnnotatedFor("nullness") public final class SignednessUtil { - private SignednessUtil() { - throw new Error("Do not instantiate"); - } - - /** - * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link - * java.nio.ByteBuffer#wrap(byte[]) wrap(byte[])}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer wrapUnsigned(@Unsigned byte[] array) { - return ByteBuffer.wrap(array); - } - - /** - * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link - * java.nio.ByteBuffer#wrap(byte[], int, int) wrap(byte[], int, int)}, but assumes that the input - * should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer wrapUnsigned(@Unsigned byte[] array, int offset, int length) { - return ByteBuffer.wrap(array, offset, length); - } - - /** - * Gets an unsigned int from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#getInt() getInt()}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int getUnsignedInt(ByteBuffer b) { - return b.getInt(); - } - - /** - * Gets an unsigned short from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#getShort() getShort()}, but assumes that the result should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned short getUnsignedShort(ByteBuffer b) { - return b.getShort(); - } - - /** - * Gets an unsigned byte from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#get() get()}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned byte getUnsigned(ByteBuffer b) { - return b.get(); - } - - /** - * Gets an unsigned byte from the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#get(int) get(int)}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned byte getUnsigned(ByteBuffer b, int i) { - return b.get(i); - } - - /** - * Populates an unsigned byte array from the ByteBuffer b at i with l bytes. This method is a - * wrapper around {@link java.nio.ByteBuffer#get(byte[] bs, int, int) get(byte[], int, int)}, but - * assumes that the bytes should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer getUnsigned(ByteBuffer b, byte[] bs, int i, int l) { - return b.get(bs, i, l); - } - - /** - * Places an unsigned byte into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#put(byte) put(byte)}, but assumes that the input should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsigned(ByteBuffer b, @Unsigned byte ubyte) { - return b.put(ubyte); - } - - /** - * Places an unsigned byte into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#put(int, byte) put(int, byte)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsigned(ByteBuffer b, int i, @Unsigned byte ubyte) { - return b.put(i, ubyte); - } - - /** - * Places an unsigned int into the IntBuffer b. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int) put(int)}, but assumes that the input should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int uint) { - return b.put(uint); - } - - /** - * Places an unsigned int into the IntBuffer b at i. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int, int) put(int, int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, int i, @Unsigned int uint) { - return b.put(i, uint); - } - - /** - * Places an unsigned int array into the IntBuffer b. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int[]) put(int[])}, but assumes that the input should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints) { - return b.put(uints); - } - - /** - * Places an unsigned int array into the IntBuffer b at i with length l. This method is a wrapper - * around {@link java.nio.IntBuffer#put(int[], int, int) put(int[], int, int)}, but assumes that - * the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints, int i, int l) { - return b.put(uints, i, l); - } - - /** - * Gets an unsigned int from the IntBuffer b at i. This method is a wrapper around {@link - * java.nio.IntBuffer#get(int) get(int)}, but assumes that the output should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int getUnsigned(IntBuffer b, int i) { - return b.get(i); - } - - /** - * Places an unsigned short into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#putShort(short) putShort(short)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedShort(ByteBuffer b, @Unsigned short ushort) { - return b.putShort(ushort); - } - - /** - * Places an unsigned short into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putShort(int, short) putShort(int, short)}, but assumes that the input - * should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedShort(ByteBuffer b, int i, @Unsigned short ushort) { - return b.putShort(i, ushort); - } - - /** - * Places an unsigned int into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#putInt(int) putInt(int)}, but assumes that the input should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedInt(ByteBuffer b, @Unsigned int uint) { - return b.putInt(uint); - } - - /** - * Places an unsigned int into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putInt(int, int) putInt(int, int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedInt(ByteBuffer b, int i, @Unsigned int uint) { - return b.putInt(i, uint); - } - - /** - * Places an unsigned long into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putLong(int, long) putLong(int, long)}, but assumes that the input should - * be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedLong(ByteBuffer b, int i, @Unsigned long ulong) { - return b.putLong(i, ulong); - } - - /** - * Reads an unsigned char from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readChar() readChar()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned char readUnsignedChar(RandomAccessFile f) throws IOException { - return f.readChar(); - } - - /** - * Reads an unsigned int from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readInt() readInt()}, but assumes the output should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int readUnsignedInt(RandomAccessFile f) throws IOException { - return f.readInt(); - } - - /** - * Reads an unsigned long from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readLong() readLong()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned long readUnsignedLong(RandomAccessFile f) throws IOException { - return f.readLong(); - } - - /** - * Reads up to {@code len} bytes of data from this file into an unsigned array of bytes. This - * method is a wrapper around {@link java.io.RandomAccessFile#read(byte[], int, int) read(byte[], - * int, int)}, but assumes the output should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static int readUnsigned(RandomAccessFile f, @Unsigned byte b[], int off, int len) - throws IOException { - return f.read(b, off, len); - } - - /** - * Reads a file fully into an unsigned byte array. This method is a wrapper around {@link - * java.io.RandomAccessFile#readFully(byte[]) readFully(byte[])}, but assumes the output should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void readFullyUnsigned(RandomAccessFile f, @Unsigned byte b[]) throws IOException { - f.readFully(b); - } - - /** - * Writes len unsigned bytes to the RandomAccessFile f at offset off. This method is a wrapper - * around {@link java.io.RandomAccessFile#write(byte[], int, int) write(byte[], int, int)}, but - * assumes the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsigned(RandomAccessFile f, @Unsigned byte[] bs, int off, int len) - throws IOException { - f.write(bs, off, len); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeByte(int) writeByte(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedByte(RandomAccessFile f, @Unsigned byte b) throws IOException { - f.writeByte(Byte.toUnsignedInt(b)); - } - - /** - * Writes an unsigned char to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeChar(int) writeChar(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedChar(RandomAccessFile f, @Unsigned char c) throws IOException { - f.writeChar(toUnsignedInt(c)); - } - - /** - * Writes an unsigned short to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeShort(int) writeShort(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedShort(RandomAccessFile f, @Unsigned short s) throws IOException { - f.writeShort(Short.toUnsignedInt(s)); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeInt(int) writeInt(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedInt(RandomAccessFile f, @Unsigned int i) throws IOException { - f.writeInt(i); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeLong(long) writeLong(long)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedLong(RandomAccessFile f, @Unsigned long l) throws IOException { - f.writeLong(l); - } - - /** - * Gets an array of unsigned bytes from the ByteBuffer b and stores them in the array bs. This - * method is a wrapper around {@link java.nio.ByteBuffer#get(byte[]) get(byte[])}, but assumes - * that the array of bytes should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void getUnsigned(ByteBuffer b, @Unsigned byte[] bs) { - b.get(bs); - } - - /** - * Compares two unsigned shorts x and y. - * - *

          In Java 11 or later, use Short.compareUnsigned. - * - * @param x the first value to compare - * @param y the second value to compare - * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and - * zero iff x == y. - */ - // TODO: deprecate when we require Java 9, which defines Short.compareUnsigned() - // * @deprecated use Short.compareUnsigned - // @Deprecated // use Short.compareUnsigned - @SuppressWarnings("signedness") - public static int compareUnsigned(@Unsigned short x, @Unsigned short y) { - return Integer.compareUnsigned(Short.toUnsignedInt(x), Short.toUnsignedInt(y)); - } - - /** - * Compares two unsigned bytes x and y. - * - *

          In Java 11 or later, use Byte.compareUnsigned. - * - * @param x the first value to compare - * @param y the second value to compare - * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and - * zero iff x == y. - */ - // TODO: deprecate when we require Java 9, which defines Byte.compareUnsigned() - // * @deprecated use Byte.compareUnsigned - // @Deprecated // use Byte.compareUnsigned - @SuppressWarnings("signedness") - public static int compareUnsigned(@Unsigned byte x, @Unsigned byte y) { - return Integer.compareUnsigned(Byte.toUnsignedInt(x), Byte.toUnsignedInt(y)); - } - - /** Produces a string representation of the unsigned short s. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned short s) { - return Long.toString(Short.toUnsignedLong(s)); - } - - /** Produces a string representation of the unsigned short s in base radix. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned short s, int radix) { - return Integer.toUnsignedString(Short.toUnsignedInt(s), radix); - } - - /** Produces a string representation of the unsigned byte b. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned byte b) { - return Integer.toUnsignedString(Byte.toUnsignedInt(b)); - } - - /** Produces a string representation of the unsigned byte b in base radix. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned byte b, int radix) { - return Integer.toUnsignedString(Byte.toUnsignedInt(b), radix); - } - - /* - * Creates a BigInteger representing the same value as unsigned long. - * - * This is a reimplementation of Java 8's - * {@link Long.toUnsignedBigInteger(long)}. - */ - @SuppressWarnings("signedness") - private static @Unsigned BigInteger toUnsignedBigInteger(@Unsigned long l) { - // Java 8 version: return Long.toUnsignedBigInteger(l); - if (l >= 0L) { - return BigInteger.valueOf(l); - } else { - int upper = (int) (l >>> 32); - int lower = (int) l; - - // return (upper << 32) + lower - return BigInteger.valueOf(Integer.toUnsignedLong(upper)) - .shiftLeft(32) - .add(BigInteger.valueOf(Integer.toUnsignedLong(lower))); - } - } - - /** Returns an unsigned short representing the same value as an unsigned byte. */ - public static @Unsigned short toUnsignedShort(@Unsigned byte b) { - return (short) (((int) b) & 0xff); - } - - /** Returns an unsigned long representing the same value as an unsigned char. */ - public static @Unsigned long toUnsignedLong(@Unsigned char c) { - return ((long) c) & 0xffL; - } - - /** Returns an unsigned int representing the same value as an unsigned char. */ - public static @Unsigned int toUnsignedInt(@Unsigned char c) { - return ((int) c) & 0xff; - } - - /** Returns an unsigned short representing the same value as an unsigned char. */ - public static @Unsigned short toUnsignedShort(@Unsigned char c) { - return (short) (((int) c) & 0xff); - } - - /** Returns a float representing the same value as the unsigned byte. */ - public static float toFloat(@Unsigned byte b) { - return toUnsignedBigInteger(Byte.toUnsignedLong(b)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned short. */ - public static float toFloat(@Unsigned short s) { - return toUnsignedBigInteger(Short.toUnsignedLong(s)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned int. */ - public static float toFloat(@Unsigned int i) { - return toUnsignedBigInteger(Integer.toUnsignedLong(i)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned long. */ - public static float toFloat(@Unsigned long l) { - return toUnsignedBigInteger(l).floatValue(); - } - - /** Returns a double representing the same value as the unsigned byte. */ - public static double toDouble(@Unsigned byte b) { - return toUnsignedBigInteger(Byte.toUnsignedLong(b)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned short. */ - public static double toDouble(@Unsigned short s) { - return toUnsignedBigInteger(Short.toUnsignedLong(s)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned int. */ - public static double toDouble(@Unsigned int i) { - return toUnsignedBigInteger(Integer.toUnsignedLong(i)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned long. */ - public static double toDouble(@Unsigned long l) { - return toUnsignedBigInteger(l).doubleValue(); - } - - /** Returns an unsigned byte representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned byte byteFromFloat(float f) { - assert f >= 0; - return (byte) f; - } - - /** Returns an unsigned short representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned short shortFromFloat(float f) { - assert f >= 0; - return (short) f; - } - - /** Returns an unsigned int representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned int intFromFloat(float f) { - assert f >= 0; - return (int) f; - } - - /** Returns an unsigned long representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned long longFromFloat(float f) { - assert f >= 0; - return (long) f; - } - - /** Returns an unsigned byte representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned byte byteFromDouble(double d) { - assert d >= 0; - return (byte) d; - } - - /** Returns an unsigned short representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned short shortFromDouble(double d) { - assert d >= 0; - return (short) d; - } - - /** Returns an unsigned int representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned int intFromDouble(double d) { - assert d >= 0; - return (int) d; - } - - /** Returns an unsigned long representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned long longFromDouble(double d) { - assert d >= 0; - return (long) d; - } + private SignednessUtil() { + throw new Error("Do not instantiate"); + } + + /** + * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link + * java.nio.ByteBuffer#wrap(byte[]) wrap(byte[])}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer wrapUnsigned(@Unsigned byte[] array) { + return ByteBuffer.wrap(array); + } + + /** + * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link + * java.nio.ByteBuffer#wrap(byte[], int, int) wrap(byte[], int, int)}, but assumes that the + * input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer wrapUnsigned(@Unsigned byte[] array, int offset, int length) { + return ByteBuffer.wrap(array, offset, length); + } + + /** + * Gets an unsigned int from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#getInt() getInt()}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int getUnsignedInt(ByteBuffer b) { + return b.getInt(); + } + + /** + * Gets an unsigned short from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#getShort() getShort()}, but assumes that the result should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned short getUnsignedShort(ByteBuffer b) { + return b.getShort(); + } + + /** + * Gets an unsigned byte from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#get() get()}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned byte getUnsigned(ByteBuffer b) { + return b.get(); + } + + /** + * Gets an unsigned byte from the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#get(int) get(int)}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned byte getUnsigned(ByteBuffer b, int i) { + return b.get(i); + } + + /** + * Populates an unsigned byte array from the ByteBuffer b at i with l bytes. This method is a + * wrapper around {@link java.nio.ByteBuffer#get(byte[] bs, int, int) get(byte[], int, int)}, + * but assumes that the bytes should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer getUnsigned(ByteBuffer b, byte[] bs, int i, int l) { + return b.get(bs, i, l); + } + + /** + * Places an unsigned byte into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#put(byte) put(byte)}, but assumes that the input should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsigned(ByteBuffer b, @Unsigned byte ubyte) { + return b.put(ubyte); + } + + /** + * Places an unsigned byte into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#put(int, byte) put(int, byte)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsigned(ByteBuffer b, int i, @Unsigned byte ubyte) { + return b.put(i, ubyte); + } + + /** + * Places an unsigned int into the IntBuffer b. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int) put(int)}, but assumes that the input should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int uint) { + return b.put(uint); + } + + /** + * Places an unsigned int into the IntBuffer b at i. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int, int) put(int, int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, int i, @Unsigned int uint) { + return b.put(i, uint); + } + + /** + * Places an unsigned int array into the IntBuffer b. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int[]) put(int[])}, but assumes that the input should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints) { + return b.put(uints); + } + + /** + * Places an unsigned int array into the IntBuffer b at i with length l. This method is a + * wrapper around {@link java.nio.IntBuffer#put(int[], int, int) put(int[], int, int)}, but + * assumes that the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints, int i, int l) { + return b.put(uints, i, l); + } + + /** + * Gets an unsigned int from the IntBuffer b at i. This method is a wrapper around {@link + * java.nio.IntBuffer#get(int) get(int)}, but assumes that the output should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int getUnsigned(IntBuffer b, int i) { + return b.get(i); + } + + /** + * Places an unsigned short into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#putShort(short) putShort(short)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedShort(ByteBuffer b, @Unsigned short ushort) { + return b.putShort(ushort); + } + + /** + * Places an unsigned short into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putShort(int, short) putShort(int, short)}, but assumes that the input + * should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedShort(ByteBuffer b, int i, @Unsigned short ushort) { + return b.putShort(i, ushort); + } + + /** + * Places an unsigned int into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#putInt(int) putInt(int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedInt(ByteBuffer b, @Unsigned int uint) { + return b.putInt(uint); + } + + /** + * Places an unsigned int into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putInt(int, int) putInt(int, int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedInt(ByteBuffer b, int i, @Unsigned int uint) { + return b.putInt(i, uint); + } + + /** + * Places an unsigned long into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putLong(int, long) putLong(int, long)}, but assumes that the input should + * be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedLong(ByteBuffer b, int i, @Unsigned long ulong) { + return b.putLong(i, ulong); + } + + /** + * Reads an unsigned char from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readChar() readChar()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned char readUnsignedChar(RandomAccessFile f) throws IOException { + return f.readChar(); + } + + /** + * Reads an unsigned int from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readInt() readInt()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int readUnsignedInt(RandomAccessFile f) throws IOException { + return f.readInt(); + } + + /** + * Reads an unsigned long from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readLong() readLong()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned long readUnsignedLong(RandomAccessFile f) throws IOException { + return f.readLong(); + } + + /** + * Reads up to {@code len} bytes of data from this file into an unsigned array of bytes. This + * method is a wrapper around {@link java.io.RandomAccessFile#read(byte[], int, int) + * read(byte[], int, int)}, but assumes the output should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static int readUnsigned(RandomAccessFile f, @Unsigned byte b[], int off, int len) + throws IOException { + return f.read(b, off, len); + } + + /** + * Reads a file fully into an unsigned byte array. This method is a wrapper around {@link + * java.io.RandomAccessFile#readFully(byte[]) readFully(byte[])}, but assumes the output should + * be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void readFullyUnsigned(RandomAccessFile f, @Unsigned byte b[]) + throws IOException { + f.readFully(b); + } + + /** + * Writes len unsigned bytes to the RandomAccessFile f at offset off. This method is a wrapper + * around {@link java.io.RandomAccessFile#write(byte[], int, int) write(byte[], int, int)}, but + * assumes the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsigned(RandomAccessFile f, @Unsigned byte[] bs, int off, int len) + throws IOException { + f.write(bs, off, len); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeByte(int) writeByte(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedByte(RandomAccessFile f, @Unsigned byte b) throws IOException { + f.writeByte(Byte.toUnsignedInt(b)); + } + + /** + * Writes an unsigned char to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeChar(int) writeChar(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedChar(RandomAccessFile f, @Unsigned char c) throws IOException { + f.writeChar(toUnsignedInt(c)); + } + + /** + * Writes an unsigned short to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeShort(int) writeShort(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedShort(RandomAccessFile f, @Unsigned short s) + throws IOException { + f.writeShort(Short.toUnsignedInt(s)); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeInt(int) writeInt(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedInt(RandomAccessFile f, @Unsigned int i) throws IOException { + f.writeInt(i); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeLong(long) writeLong(long)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedLong(RandomAccessFile f, @Unsigned long l) throws IOException { + f.writeLong(l); + } + + /** + * Gets an array of unsigned bytes from the ByteBuffer b and stores them in the array bs. This + * method is a wrapper around {@link java.nio.ByteBuffer#get(byte[]) get(byte[])}, but assumes + * that the array of bytes should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void getUnsigned(ByteBuffer b, @Unsigned byte[] bs) { + b.get(bs); + } + + /** + * Compares two unsigned shorts x and y. + * + *

          In Java 11 or later, use Short.compareUnsigned. + * + * @param x the first value to compare + * @param y the second value to compare + * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and + * zero iff x == y. + */ + // TODO: deprecate when we require Java 9, which defines Short.compareUnsigned() + // * @deprecated use Short.compareUnsigned + // @Deprecated // use Short.compareUnsigned + @SuppressWarnings("signedness") + public static int compareUnsigned(@Unsigned short x, @Unsigned short y) { + return Integer.compareUnsigned(Short.toUnsignedInt(x), Short.toUnsignedInt(y)); + } + + /** + * Compares two unsigned bytes x and y. + * + *

          In Java 11 or later, use Byte.compareUnsigned. + * + * @param x the first value to compare + * @param y the second value to compare + * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and + * zero iff x == y. + */ + // TODO: deprecate when we require Java 9, which defines Byte.compareUnsigned() + // * @deprecated use Byte.compareUnsigned + // @Deprecated // use Byte.compareUnsigned + @SuppressWarnings("signedness") + public static int compareUnsigned(@Unsigned byte x, @Unsigned byte y) { + return Integer.compareUnsigned(Byte.toUnsignedInt(x), Byte.toUnsignedInt(y)); + } + + /** Produces a string representation of the unsigned short s. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned short s) { + return Long.toString(Short.toUnsignedLong(s)); + } + + /** Produces a string representation of the unsigned short s in base radix. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned short s, int radix) { + return Integer.toUnsignedString(Short.toUnsignedInt(s), radix); + } + + /** Produces a string representation of the unsigned byte b. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned byte b) { + return Integer.toUnsignedString(Byte.toUnsignedInt(b)); + } + + /** Produces a string representation of the unsigned byte b in base radix. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned byte b, int radix) { + return Integer.toUnsignedString(Byte.toUnsignedInt(b), radix); + } + + /* + * Creates a BigInteger representing the same value as unsigned long. + * + * This is a reimplementation of Java 8's + * {@link Long.toUnsignedBigInteger(long)}. + */ + @SuppressWarnings("signedness") + private static @Unsigned BigInteger toUnsignedBigInteger(@Unsigned long l) { + // Java 8 version: return Long.toUnsignedBigInteger(l); + if (l >= 0L) { + return BigInteger.valueOf(l); + } else { + int upper = (int) (l >>> 32); + int lower = (int) l; + + // return (upper << 32) + lower + return BigInteger.valueOf(Integer.toUnsignedLong(upper)) + .shiftLeft(32) + .add(BigInteger.valueOf(Integer.toUnsignedLong(lower))); + } + } + + /** Returns an unsigned short representing the same value as an unsigned byte. */ + public static @Unsigned short toUnsignedShort(@Unsigned byte b) { + return (short) (((int) b) & 0xff); + } + + /** Returns an unsigned long representing the same value as an unsigned char. */ + public static @Unsigned long toUnsignedLong(@Unsigned char c) { + return ((long) c) & 0xffL; + } + + /** Returns an unsigned int representing the same value as an unsigned char. */ + public static @Unsigned int toUnsignedInt(@Unsigned char c) { + return ((int) c) & 0xff; + } + + /** Returns an unsigned short representing the same value as an unsigned char. */ + public static @Unsigned short toUnsignedShort(@Unsigned char c) { + return (short) (((int) c) & 0xff); + } + + /** Returns a float representing the same value as the unsigned byte. */ + public static float toFloat(@Unsigned byte b) { + return toUnsignedBigInteger(Byte.toUnsignedLong(b)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned short. */ + public static float toFloat(@Unsigned short s) { + return toUnsignedBigInteger(Short.toUnsignedLong(s)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned int. */ + public static float toFloat(@Unsigned int i) { + return toUnsignedBigInteger(Integer.toUnsignedLong(i)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned long. */ + public static float toFloat(@Unsigned long l) { + return toUnsignedBigInteger(l).floatValue(); + } + + /** Returns a double representing the same value as the unsigned byte. */ + public static double toDouble(@Unsigned byte b) { + return toUnsignedBigInteger(Byte.toUnsignedLong(b)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned short. */ + public static double toDouble(@Unsigned short s) { + return toUnsignedBigInteger(Short.toUnsignedLong(s)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned int. */ + public static double toDouble(@Unsigned int i) { + return toUnsignedBigInteger(Integer.toUnsignedLong(i)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned long. */ + public static double toDouble(@Unsigned long l) { + return toUnsignedBigInteger(l).doubleValue(); + } + + /** Returns an unsigned byte representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned byte byteFromFloat(float f) { + assert f >= 0; + return (byte) f; + } + + /** Returns an unsigned short representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned short shortFromFloat(float f) { + assert f >= 0; + return (short) f; + } + + /** Returns an unsigned int representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned int intFromFloat(float f) { + assert f >= 0; + return (int) f; + } + + /** Returns an unsigned long representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned long longFromFloat(float f) { + assert f >= 0; + return (long) f; + } + + /** Returns an unsigned byte representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned byte byteFromDouble(double d) { + assert d >= 0; + return (byte) d; + } + + /** Returns an unsigned short representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned short shortFromDouble(double d) { + assert d >= 0; + return (short) d; + } + + /** Returns an unsigned int representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned int intFromDouble(double d) { + assert d >= 0; + return (int) d; + } + + /** Returns an unsigned long representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned long longFromDouble(double d) { + assert d >= 0; + return (long) d; + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java index 719f5d05306..215cd1351fe 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java +++ b/checker-util/src/main/java/org/checkerframework/checker/signedness/util/SignednessUtilExtra.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.signedness.util; -import java.awt.Dimension; -import java.awt.image.BufferedImage; import org.checkerframework.checker.signedness.qual.Unsigned; import org.checkerframework.framework.qual.AnnotatedFor; +import java.awt.Dimension; +import java.awt.image.BufferedImage; + /** * Provides more static utility methods for unsigned values. These methods use Java packages not * included in Android. @@ -16,56 +17,56 @@ */ @AnnotatedFor("nullness") public class SignednessUtilExtra { - /** Do not instantiate this class. */ - private SignednessUtilExtra() { - throw new Error("Do not instantiate"); - } + /** Do not instantiate this class. */ + private SignednessUtilExtra() { + throw new Error("Do not instantiate"); + } - /** Gets the unsigned width of a {@code Dimension}. */ - @SuppressWarnings("signedness") - public static @Unsigned int dimensionUnsignedWidth(Dimension dim) { - return dim.width; - } + /** Gets the unsigned width of a {@code Dimension}. */ + @SuppressWarnings("signedness") + public static @Unsigned int dimensionUnsignedWidth(Dimension dim) { + return dim.width; + } - /** Gets the unsigned height of a {@code Dimension}. */ - @SuppressWarnings("signedness") - public static @Unsigned int dimensionUnsignedHeight(Dimension dim) { - return dim.height; - } + /** Gets the unsigned height of a {@code Dimension}. */ + @SuppressWarnings("signedness") + public static @Unsigned int dimensionUnsignedHeight(Dimension dim) { + return dim.height; + } - /** - * Sets rgb of BufferedImage b given unsigned ints. This method is a wrapper around {@link - * java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB(int, int, int, - * int, int[], int, int)}, but assumes that the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void setUnsignedRGB( - BufferedImage b, - int startX, - int startY, - int w, - int h, - @Unsigned int[] rgbArray, - int offset, - int scansize) { - b.setRGB(startX, startY, w, h, rgbArray, offset, scansize); - } + /** + * Sets rgb of BufferedImage b given unsigned ints. This method is a wrapper around {@link + * java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB(int, int, + * int, int, int[], int, int)}, but assumes that the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void setUnsignedRGB( + BufferedImage b, + int startX, + int startY, + int w, + int h, + @Unsigned int[] rgbArray, + int offset, + int scansize) { + b.setRGB(startX, startY, w, h, rgbArray, offset, scansize); + } - /** - * Gets rgb of BufferedImage b as unsigned ints. This method is a wrapper around {@link - * java.awt.image.BufferedImage#getRGB(int, int, int, int, int[], int, int) getRGB(int, int, int, - * int, int[], int, int)}, but assumes that the output should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int[] getUnsignedRGB( - BufferedImage b, - int startX, - int startY, - int w, - int h, - @Unsigned int[] rgbArray, - int offset, - int scansize) { - return b.getRGB(startX, startY, w, h, rgbArray, offset, scansize); - } + /** + * Gets rgb of BufferedImage b as unsigned ints. This method is a wrapper around {@link + * java.awt.image.BufferedImage#getRGB(int, int, int, int, int[], int, int) getRGB(int, int, + * int, int, int[], int, int)}, but assumes that the output should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int[] getUnsignedRGB( + BufferedImage b, + int startX, + int startY, + int w, + int h, + @Unsigned int[] rgbArray, + int offset, + int scansize) { + return b.getRGB(startX, startY, w, h, rgbArray, offset, scansize); + } } diff --git a/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java b/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java index 7b92e252246..9069300a169 100644 --- a/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java +++ b/checker-util/src/main/java/org/checkerframework/checker/units/util/UnitsTools.java @@ -35,136 +35,136 @@ @SuppressWarnings({"units", "checkstyle:constantname"}) @AnnotatedFor("nullness") public class UnitsTools { - // Acceleration - public static final @mPERs2 int mPERs2 = 1; + // Acceleration + public static final @mPERs2 int mPERs2 = 1; - // Angle - public static final @radians double rad = 1; - public static final @degrees double deg = 1; + // Angle + public static final @radians double rad = 1; + public static final @degrees double deg = 1; - public static @radians double toRadians(@degrees double angdeg) { - return Math.toRadians(angdeg); - } + public static @radians double toRadians(@degrees double angdeg) { + return Math.toRadians(angdeg); + } - public static @degrees double toDegrees(@radians double angrad) { - return Math.toDegrees(angrad); - } + public static @degrees double toDegrees(@radians double angrad) { + return Math.toDegrees(angrad); + } - // Area - public static final @mm2 int mm2 = 1; - public static final @m2 int m2 = 1; - public static final @km2 int km2 = 1; - - // Volume - public static final @mm3 int mm3 = 1; - public static final @m3 int m3 = 1; - public static final @km3 int km3 = 1; + // Area + public static final @mm2 int mm2 = 1; + public static final @m2 int m2 = 1; + public static final @km2 int km2 = 1; + + // Volume + public static final @mm3 int mm3 = 1; + public static final @m3 int m3 = 1; + public static final @km3 int km3 = 1; - // Current - public static final @A int A = 1; - - // Luminance - public static final @cd int cd = 1; + // Current + public static final @A int A = 1; + + // Luminance + public static final @cd int cd = 1; - // Lengths - public static final @mm int mm = 1; - public static final @m int m = 1; - public static final @km int km = 1; - - public static @m int fromMilliMeterToMeter(@mm int mm) { - return mm / 1000; - } - - public static @mm int fromMeterToMilliMeter(@m int m) { - return m * 1000; - } - - public static @km int fromMeterToKiloMeter(@m int m) { - return m / 1000; - } - - public static @m int fromKiloMeterToMeter(@km int km) { - return km * 1000; - } - - // Mass - public static final @g int g = 1; - public static final @kg int kg = 1; - public static final @t int t = 1; - - public static @kg int fromGramToKiloGram(@g int g) { - return g / 1000; - } - - public static @g int fromKiloGramToGram(@kg int kg) { - return kg * 1000; - } - - public static @t int fromKiloGramToMetricTon(@kg int kg) { - return kg / 1000; - } - - public static @kg int fromMetricTonToKiloGram(@t int t) { - return t * 1000; - } - - // Force - public static final @N int N = 1; - public static final @kN int kN = 1; - - public static @kN int fromNewtonToKiloNewton(@N int N) { - return N / 1000; - } - - public static @N int fromKiloNewtonToNewton(@kN int kN) { - return kN * 1000; - } - - // Speed - public static final @mPERs int mPERs = 1; - public static final @kmPERh int kmPERh = 1; - - public static @kmPERh double fromMeterPerSecondToKiloMeterPerHour(@mPERs double mps) { - return mps * 3.6d; - } - - public static @mPERs double fromKiloMeterPerHourToMeterPerSecond(@kmPERh double kmph) { - return kmph / 3.6d; - } - - // Substance - public static final @mol int mol = 1; - - // Temperature - public static final @K int K = 1; - public static final @C int C = 1; - - public static @C int fromKelvinToCelsius(@K int k) { - return k - (int) 273.15; - } - - public static @K int fromCelsiusToKelvin(@C int c) { - return c + (int) 273.15; - } - - // Time - public static final @s int s = 1; - public static final @min int min = 1; - public static final @h int h = 1; - - public static @min int fromSecondToMinute(@s int s) { - return s / 60; - } - - public static @s int fromMinuteToSecond(@min int min) { - return min * 60; - } + // Lengths + public static final @mm int mm = 1; + public static final @m int m = 1; + public static final @km int km = 1; + + public static @m int fromMilliMeterToMeter(@mm int mm) { + return mm / 1000; + } + + public static @mm int fromMeterToMilliMeter(@m int m) { + return m * 1000; + } + + public static @km int fromMeterToKiloMeter(@m int m) { + return m / 1000; + } + + public static @m int fromKiloMeterToMeter(@km int km) { + return km * 1000; + } + + // Mass + public static final @g int g = 1; + public static final @kg int kg = 1; + public static final @t int t = 1; + + public static @kg int fromGramToKiloGram(@g int g) { + return g / 1000; + } + + public static @g int fromKiloGramToGram(@kg int kg) { + return kg * 1000; + } + + public static @t int fromKiloGramToMetricTon(@kg int kg) { + return kg / 1000; + } + + public static @kg int fromMetricTonToKiloGram(@t int t) { + return t * 1000; + } + + // Force + public static final @N int N = 1; + public static final @kN int kN = 1; + + public static @kN int fromNewtonToKiloNewton(@N int N) { + return N / 1000; + } + + public static @N int fromKiloNewtonToNewton(@kN int kN) { + return kN * 1000; + } + + // Speed + public static final @mPERs int mPERs = 1; + public static final @kmPERh int kmPERh = 1; + + public static @kmPERh double fromMeterPerSecondToKiloMeterPerHour(@mPERs double mps) { + return mps * 3.6d; + } + + public static @mPERs double fromKiloMeterPerHourToMeterPerSecond(@kmPERh double kmph) { + return kmph / 3.6d; + } + + // Substance + public static final @mol int mol = 1; + + // Temperature + public static final @K int K = 1; + public static final @C int C = 1; + + public static @C int fromKelvinToCelsius(@K int k) { + return k - (int) 273.15; + } + + public static @K int fromCelsiusToKelvin(@C int c) { + return c + (int) 273.15; + } + + // Time + public static final @s int s = 1; + public static final @min int min = 1; + public static final @h int h = 1; + + public static @min int fromSecondToMinute(@s int s) { + return s / 60; + } + + public static @s int fromMinuteToSecond(@min int min) { + return min * 60; + } - public static @h int fromMinuteToHour(@min int min) { - return min / 60; - } - - public static @min int fromHourToMinute(@h int h) { - return h * 60; - } + public static @h int fromMinuteToHour(@min int min) { + return min / 60; + } + + public static @min int fromHourToMinute(@h int h) { + return h * 60; + } } diff --git a/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java b/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java index c2d2d132ff8..285e44fc2b6 100644 --- a/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java +++ b/checker-util/src/test/java/org/checkerframework/checker/optional/util/OptionalUtilTest.java @@ -1,23 +1,24 @@ package org.checkerframework.checker.optional.util; -import java.util.Optional; import org.checkerframework.checker.optional.qual.Present; import org.junit.Assert; import org.junit.Test; +import java.util.Optional; + public final class OptionalUtilTest { - @Test - public void test_castPresent() { + @Test + public void test_castPresent() { - Optional nonEmptyOpt = Optional.of("non-empty"); - Optional emptyOpt = Optional.empty(); + Optional nonEmptyOpt = Optional.of("non-empty"); + Optional emptyOpt = Optional.empty(); - Assert.assertTrue(nonEmptyOpt.isPresent()); - @Present Optional foo = OptionalUtil.castPresent(nonEmptyOpt); - Assert.assertEquals(foo.get(), "non-empty"); + Assert.assertTrue(nonEmptyOpt.isPresent()); + @Present Optional foo = OptionalUtil.castPresent(nonEmptyOpt); + Assert.assertEquals(foo.get(), "non-empty"); - Assert.assertFalse(emptyOpt.isPresent()); - Assert.assertThrows(Error.class, () -> OptionalUtil.castPresent(emptyOpt)); - } + Assert.assertFalse(emptyOpt.isPresent()); + Assert.assertThrows(Error.class, () -> OptionalUtil.castPresent(emptyOpt)); + } } diff --git a/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java b/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java index 97bb896f024..12a10f70fed 100644 --- a/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java +++ b/checker-util/src/test/java/org/checkerframework/checker/regex/util/RegexUtilTest.java @@ -2,265 +2,266 @@ package org.checkerframework.checker.regex.util; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import org.checkerframework.checker.regex.qual.Regex; import org.junit.Assert; import org.junit.Test; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + public final class RegexUtilTest { - @Test - public void test_isRegex_and_asRegex() { - - String s1 = "colo(u?)r"; - String s2 = "(brown|beige)"; - String s3 = "colou?r"; - String s4 = "1) first point"; - - Assert.assertTrue(RegexUtil.isRegex(s1)); - RegexUtil.asRegex(s1); - Assert.assertTrue(RegexUtil.isRegex(s1, 0)); - RegexUtil.asRegex(s1, 0); - Assert.assertTrue(RegexUtil.isRegex(s1, 1)); - RegexUtil.asRegex(s1, 1); - Assert.assertFalse(RegexUtil.isRegex(s1, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s1, 2)); - - Assert.assertTrue(RegexUtil.isRegex(s2)); - RegexUtil.asRegex(s2); - Assert.assertTrue(RegexUtil.isRegex(s2, 0)); - RegexUtil.asRegex(s2, 0); - Assert.assertTrue(RegexUtil.isRegex(s2, 1)); - RegexUtil.asRegex(s2, 1); - Assert.assertFalse(RegexUtil.isRegex(s2, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s2, 2)); - - Assert.assertTrue(RegexUtil.isRegex(s3)); - RegexUtil.asRegex(s3); - Assert.assertTrue(RegexUtil.isRegex(s3, 0)); - RegexUtil.asRegex(s3, 0); - Assert.assertFalse(RegexUtil.isRegex(s3, 1)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 1)); - Assert.assertFalse(RegexUtil.isRegex(s3, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 2)); - - Assert.assertFalse(RegexUtil.isRegex(s4)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4)); - Assert.assertFalse(RegexUtil.isRegex(s4, 0)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 0)); - Assert.assertFalse(RegexUtil.isRegex(s4, 1)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 1)); - Assert.assertFalse(RegexUtil.isRegex(s4, 2)); - Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 2)); - } - - List s1 = Arrays.asList(new String[] {"a", "b", "c"}); - List s2 = Arrays.asList(new String[] {"a", "b", "c", "d"}); - List s3 = Arrays.asList(new String[] {"aa", "bb", "cc"}); - List s4 = Arrays.asList(new String[] {"a", "aa", "b", "bb", "c"}); - List s5 = Arrays.asList(new String[] {"d", "ee", "fff"}); - List s6 = Arrays.asList(new String[] {"a", "d", "ee", "fff"}); - - List<@Regex String> r1 = Arrays.asList(new @Regex String[] {}); - List<@Regex String> r2 = Arrays.asList(new @Regex String[] {"a", "b", "c"}); - List<@Regex String> r3 = Arrays.asList(new @Regex String[] {"a+", "b+", "c"}); - List<@Regex String> r4 = Arrays.asList(new @Regex String[] {"a+", "b+", "c+"}); - List<@Regex String> r5 = Arrays.asList(new @Regex String[] {".*"}); - - List<@Regex String> r6 = Arrays.asList(new @Regex String[] {"a?b", "a*"}); - List<@Regex String> r7 = Arrays.asList(new @Regex String[] {"a?b+", "a*"}); - - List empty = Collections.emptyList(); - List onlyA = Arrays.asList(new String[] {"a"}); - List onlyAA = Arrays.asList(new String[] {"aa"}); - List onlyC = Arrays.asList(new String[] {"c"}); - List onlyCC = Arrays.asList(new String[] {"cc"}); - List onlyD = Arrays.asList(new String[] {"d"}); - List aaab = Arrays.asList(new String[] {"a", "aa", "b"}); - List ab = Arrays.asList(new String[] {"a", "b"}); - List aabb = Arrays.asList(new String[] {"aa", "bb"}); - List aacc = Arrays.asList(new String[] {"aa", "cc"}); - List bbc = Arrays.asList(new String[] {"bb", "c"}); - List bbcc = Arrays.asList(new String[] {"bb", "cc"}); - List cc = Arrays.asList(new String[] {"cc"}); - List cd = Arrays.asList(new String[] {"c", "d"}); - List eefff = Arrays.asList(new String[] {"ee", "fff"}); - - @Test - public void test_matchesSomeRegex() { - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r1), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r1), empty); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r2), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r2), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r2), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r2), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r2), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r2), onlyA); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r3), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r3), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r3), aabb); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r3), s4); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r3), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r3), onlyA); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r4), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r4), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r4), s3); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r4), s4); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r4), empty); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r4), onlyA); - - Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r5), s1); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r5), s2); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r5), s3); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r5), s4); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r5), s5); - Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r5), s6); - - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r1)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r1)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r2)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r2)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r3)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r3)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r3)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r4)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r4)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r4)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r4)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r4)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r4)); - - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s2, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s5, r5)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s6, r5)); - } - - @Test - public void test_matchesNoRegex() { - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r1), s1); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r1), s2); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r1), s3); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r1), s4); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r1), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r1), s6); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r2), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r2), onlyD); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r2), s3); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r2), aabb); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r2), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r2), s5); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r3), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r3), onlyD); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r3), cc); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r3), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r3), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r3), s5); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r4), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r4), onlyD); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r4), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r4), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r4), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r4), s5); - - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r5), empty); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r5), empty); - - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s1, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s2, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s4, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r1)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s6, r1)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r2)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r2)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r2)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r2)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r2)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r2)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r3)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r3)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r3)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r4)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r4)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r4)); - - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s5, r5)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r5)); - } - - @Test - public void test_r6() { - Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s1, r6)); - Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s2, r6)); - Assert.assertEquals(onlyAA, RegexUtil.matchesSomeRegex(s3, r6)); - Assert.assertEquals(aaab, RegexUtil.matchesSomeRegex(s4, r6)); - Assert.assertEquals(empty, RegexUtil.matchesSomeRegex(s5, r6)); - Assert.assertEquals(onlyA, RegexUtil.matchesSomeRegex(s6, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r6)); - Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r6)); - Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(onlyA, r7)); - Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r6), onlyC); - Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r6), cd); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r6), bbcc); - Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r7), onlyCC); - Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r6), bbc); - Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r6), s5); - Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r6), s5); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r6)); - Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r6)); - Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r6)); - } + @Test + public void test_isRegex_and_asRegex() { + + String s1 = "colo(u?)r"; + String s2 = "(brown|beige)"; + String s3 = "colou?r"; + String s4 = "1) first point"; + + Assert.assertTrue(RegexUtil.isRegex(s1)); + RegexUtil.asRegex(s1); + Assert.assertTrue(RegexUtil.isRegex(s1, 0)); + RegexUtil.asRegex(s1, 0); + Assert.assertTrue(RegexUtil.isRegex(s1, 1)); + RegexUtil.asRegex(s1, 1); + Assert.assertFalse(RegexUtil.isRegex(s1, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s1, 2)); + + Assert.assertTrue(RegexUtil.isRegex(s2)); + RegexUtil.asRegex(s2); + Assert.assertTrue(RegexUtil.isRegex(s2, 0)); + RegexUtil.asRegex(s2, 0); + Assert.assertTrue(RegexUtil.isRegex(s2, 1)); + RegexUtil.asRegex(s2, 1); + Assert.assertFalse(RegexUtil.isRegex(s2, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s2, 2)); + + Assert.assertTrue(RegexUtil.isRegex(s3)); + RegexUtil.asRegex(s3); + Assert.assertTrue(RegexUtil.isRegex(s3, 0)); + RegexUtil.asRegex(s3, 0); + Assert.assertFalse(RegexUtil.isRegex(s3, 1)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 1)); + Assert.assertFalse(RegexUtil.isRegex(s3, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s3, 2)); + + Assert.assertFalse(RegexUtil.isRegex(s4)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4)); + Assert.assertFalse(RegexUtil.isRegex(s4, 0)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 0)); + Assert.assertFalse(RegexUtil.isRegex(s4, 1)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 1)); + Assert.assertFalse(RegexUtil.isRegex(s4, 2)); + Assert.assertThrows(Error.class, () -> RegexUtil.asRegex(s4, 2)); + } + + List s1 = Arrays.asList(new String[] {"a", "b", "c"}); + List s2 = Arrays.asList(new String[] {"a", "b", "c", "d"}); + List s3 = Arrays.asList(new String[] {"aa", "bb", "cc"}); + List s4 = Arrays.asList(new String[] {"a", "aa", "b", "bb", "c"}); + List s5 = Arrays.asList(new String[] {"d", "ee", "fff"}); + List s6 = Arrays.asList(new String[] {"a", "d", "ee", "fff"}); + + List<@Regex String> r1 = Arrays.asList(new @Regex String[] {}); + List<@Regex String> r2 = Arrays.asList(new @Regex String[] {"a", "b", "c"}); + List<@Regex String> r3 = Arrays.asList(new @Regex String[] {"a+", "b+", "c"}); + List<@Regex String> r4 = Arrays.asList(new @Regex String[] {"a+", "b+", "c+"}); + List<@Regex String> r5 = Arrays.asList(new @Regex String[] {".*"}); + + List<@Regex String> r6 = Arrays.asList(new @Regex String[] {"a?b", "a*"}); + List<@Regex String> r7 = Arrays.asList(new @Regex String[] {"a?b+", "a*"}); + + List empty = Collections.emptyList(); + List onlyA = Arrays.asList(new String[] {"a"}); + List onlyAA = Arrays.asList(new String[] {"aa"}); + List onlyC = Arrays.asList(new String[] {"c"}); + List onlyCC = Arrays.asList(new String[] {"cc"}); + List onlyD = Arrays.asList(new String[] {"d"}); + List aaab = Arrays.asList(new String[] {"a", "aa", "b"}); + List ab = Arrays.asList(new String[] {"a", "b"}); + List aabb = Arrays.asList(new String[] {"aa", "bb"}); + List aacc = Arrays.asList(new String[] {"aa", "cc"}); + List bbc = Arrays.asList(new String[] {"bb", "c"}); + List bbcc = Arrays.asList(new String[] {"bb", "cc"}); + List cc = Arrays.asList(new String[] {"cc"}); + List cd = Arrays.asList(new String[] {"c", "d"}); + List eefff = Arrays.asList(new String[] {"ee", "fff"}); + + @Test + public void test_matchesSomeRegex() { + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r1), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r1), empty); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r2), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r2), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r2), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r2), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r2), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r2), onlyA); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r3), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r3), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r3), aabb); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r3), s4); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r3), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r3), onlyA); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r4), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r4), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r4), s3); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r4), s4); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r4), empty); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r4), onlyA); + + Assert.assertEquals(RegexUtil.matchesSomeRegex(s1, r5), s1); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s2, r5), s2); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s3, r5), s3); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s4, r5), s4); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s5, r5), s5); + Assert.assertEquals(RegexUtil.matchesSomeRegex(s6, r5), s6); + + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r1)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r1)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r2)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r2)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r3)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r3)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r3)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r4)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r4)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r4)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r4)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r4)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r4)); + + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s1, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s2, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s3, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s4, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s5, r5)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(s6, r5)); + } + + @Test + public void test_matchesNoRegex() { + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r1), s1); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r1), s2); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r1), s3); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r1), s4); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r1), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r1), s6); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r2), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r2), onlyD); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r2), s3); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r2), aabb); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r2), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r2), s5); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r3), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r3), onlyD); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r3), cc); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r3), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r3), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r3), s5); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r4), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r4), onlyD); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r4), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r4), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r4), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r4), s5); + + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r5), empty); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r5), empty); + + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s1, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s2, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s4, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r1)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s6, r1)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r2)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r2)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s3, r2)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r2)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r2)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r2)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r3)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r3)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r3)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r4)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r4)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r4)); + + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s5, r5)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r5)); + } + + @Test + public void test_r6() { + Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s1, r6)); + Assert.assertEquals(ab, RegexUtil.matchesSomeRegex(s2, r6)); + Assert.assertEquals(onlyAA, RegexUtil.matchesSomeRegex(s3, r6)); + Assert.assertEquals(aaab, RegexUtil.matchesSomeRegex(s4, r6)); + Assert.assertEquals(empty, RegexUtil.matchesSomeRegex(s5, r6)); + Assert.assertEquals(onlyA, RegexUtil.matchesSomeRegex(s6, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s1, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s2, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s3, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s4, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s5, r6)); + Assert.assertFalse(RegexUtil.everyStringMatchesSomeRegex(s6, r6)); + Assert.assertTrue(RegexUtil.everyStringMatchesSomeRegex(onlyA, r7)); + Assert.assertEquals(RegexUtil.matchesNoRegex(s1, r6), onlyC); + Assert.assertEquals(RegexUtil.matchesNoRegex(s2, r6), cd); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r6), bbcc); + Assert.assertEquals(RegexUtil.matchesNoRegex(s3, r7), onlyCC); + Assert.assertEquals(RegexUtil.matchesNoRegex(s4, r6), bbc); + Assert.assertEquals(RegexUtil.matchesNoRegex(s5, r6), s5); + Assert.assertEquals(RegexUtil.matchesNoRegex(s6, r6), s5); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s1, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s2, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s3, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s4, r6)); + Assert.assertTrue(RegexUtil.noStringMatchesAnyRegex(s5, r6)); + Assert.assertFalse(RegexUtil.noStringMatchesAnyRegex(s6, r6)); + } } diff --git a/checker/build.gradle b/checker/build.gradle index a1c277507fe..02fb8d4272b 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -1,116 +1,116 @@ plugins { - id 'java-library' - id 'base' - // https://github.com/n0mer/gradle-git-properties - // Generates file build/resources/main/git.properties when the `classes` task runs. - id 'com.gorylenko.gradle-git-properties' version '2.4.2' + id 'java-library' + id 'base' + // https://github.com/n0mer/gradle-git-properties + // Generates file build/resources/main/git.properties when the `classes` task runs. + id 'com.gorylenko.gradle-git-properties' version '2.4.2' } sourceSets { - main { - java { - // NO-AFU - exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' - } - resources { - // Stub files, message.properties, etc. - srcDirs += ['src/main/java'] + main { + java { + // NO-AFU + exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' + } + resources { + // Stub files, message.properties, etc. + srcDirs += ['src/main/java'] - // NO-AFU - exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' + // NO-AFU + exclude '**/org/checkerframework/checker/resourceleak/MustCallInference.java' + } } - } - testannotations { - java { - srcDirs = ['src/testannotations/java'] + testannotations { + java { + srcDirs = ['src/testannotations/java'] + } } - } } sourcesJar { - // The resources duplicate content from the src directory. - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + // The resources duplicate content from the src directory. + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } configurations { - implementation.extendsFrom(annotatedGuava) - fatJar { - canBeConsumed = true - canBeResolved = false - } + implementation.extendsFrom(annotatedGuava) + fatJar { + canBeConsumed = true + canBeResolved = false + } } dependencies { - // Use "api" instead of "implementation" to re-export sub-projects, making - // sure "minimize()" does not remove those classes. - api project(':framework') - - /* NO-AFU - // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. - // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite - implementation('org.checkerframework:annotation-file-utilities:*') { - exclude group: 'com.google.errorprone', module: 'javac' - } - */ - - api project(':checker-qual') - api project(':checker-util') - - // External dependencies: - // If you add an external dependency, you must shadow its packages. - // See the comment in ../build.gradle in the shadowJar block. - - // As of 2019-12-16, the version of reflection-util in the Annotation - // File Utilities takes priority over this version, in the fat jar - // file. :-( So update it and re-build it locally when updating this. - implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" - implementation "org.plumelib:plume-util:${versions.plumeUtil}" - - // Dependencies added to "shadow" appear as dependencies in Maven Central. - shadow project(':checker-qual') - shadow project(':checker-util') - - // Called Methods Checker AutoValue + Lombok support - testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" - testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" - testImplementation 'com.ryanharter.auto.value:auto-value-parcel:0.2.9' - testImplementation "org.projectlombok:lombok:${versions.lombok}" - // Called Methods Checker support for detecting misuses of AWS APIs - testImplementation 'com.amazonaws:aws-java-sdk-ec2' - testImplementation 'com.amazonaws:aws-java-sdk-kms' - // The AWS SDK is used for testing the Called Methods Checker. - testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.670') - // For the Resource Leak Checker's support for JavaEE. - testImplementation 'javax.servlet:javax.servlet-api:4.0.1' - // For the Resource Leak Checker's support for IOUtils. - testImplementation 'commons-io:commons-io:2.18.0' - // For the Nullness Checker test of junit-assertions.astub in JUnitNull.java - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' - testImplementation 'org.apiguardian:apiguardian-api:1.1.2' - // For tests that use JSpecify annotations - testImplementation 'org.jspecify:jspecify:1.0.0' - // To test for an obscure crash in CFG construction for try-with-resources; - // see https://github.com/typetools/checker-framework/issues/6396 - testImplementation 'org.apache.spark:spark-sql_2.12:3.3.2' - - // Required for checker/tests/index-initializedfields/RequireJavadoc.java - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - testImplementation 'org.plumelib:options:1.0.6' - } else { - testImplementation 'org.plumelib:options:2.0.3' - } - - testImplementation "junit:junit:${versions.junit}" - testImplementation project(':framework-test') - testImplementation sourceSets.testannotations.output - - testannotationsImplementation project(':checker-qual') + // Use "api" instead of "implementation" to re-export sub-projects, making + // sure "minimize()" does not remove those classes. + api project(':framework') + + /* NO-AFU + // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. + // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite + implementation('org.checkerframework:annotation-file-utilities:*') { + exclude group: 'com.google.errorprone', module: 'javac' + } + */ + + api project(':checker-qual') + api project(':checker-util') + + // External dependencies: + // If you add an external dependency, you must shadow its packages. + // See the comment in ../build.gradle in the shadowJar block. + + // As of 2019-12-16, the version of reflection-util in the Annotation + // File Utilities takes priority over this version, in the fat jar + // file. :-( So update it and re-build it locally when updating this. + implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" + implementation "org.plumelib:plume-util:${versions.plumeUtil}" + + // Dependencies added to "shadow" appear as dependencies in Maven Central. + shadow project(':checker-qual') + shadow project(':checker-util') + + // Called Methods Checker AutoValue + Lombok support + testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" + testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" + testImplementation 'com.ryanharter.auto.value:auto-value-parcel:0.2.9' + testImplementation "org.projectlombok:lombok:${versions.lombok}" + // Called Methods Checker support for detecting misuses of AWS APIs + testImplementation 'com.amazonaws:aws-java-sdk-ec2' + testImplementation 'com.amazonaws:aws-java-sdk-kms' + // The AWS SDK is used for testing the Called Methods Checker. + testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.670') + // For the Resource Leak Checker's support for JavaEE. + testImplementation 'javax.servlet:javax.servlet-api:4.0.1' + // For the Resource Leak Checker's support for IOUtils. + testImplementation 'commons-io:commons-io:2.18.0' + // For the Nullness Checker test of junit-assertions.astub in JUnitNull.java + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4' + testImplementation 'org.apiguardian:apiguardian-api:1.1.2' + // For tests that use JSpecify annotations + testImplementation 'org.jspecify:jspecify:1.0.0' + // To test for an obscure crash in CFG construction for try-with-resources; + // see https://github.com/typetools/checker-framework/issues/6396 + testImplementation 'org.apache.spark:spark-sql_2.12:3.3.2' + + // Required for checker/tests/index-initializedfields/RequireJavadoc.java + if (JavaVersion.current() == JavaVersion.VERSION_1_8) { + testImplementation 'org.plumelib:options:1.0.6' + } else { + testImplementation 'org.plumelib:options:2.0.3' + } + + testImplementation "junit:junit:${versions.junit}" + testImplementation project(':framework-test') + testImplementation sourceSets.testannotations.output + + testannotationsImplementation project(':checker-qual') } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } // It's not clear why this dependencies exists, but Gradle issues the following warning: @@ -124,369 +124,369 @@ interface InjectedExecOps { generateGitProperties.dependsOn(':installGitHooks') jar { - manifest { - attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') - } + manifest { + attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') + } } // This task differs from the `assemble` task in that it does not build Javadoc. // It is useful for those who only want to run `javac`. // checker.jar is copied to checker/dist/ when it is built by the shadowJar task. task assembleForJavac(dependsOn: shadowJar, group: 'Build') { - description = 'Builds or downloads jars required by CheckerMain and puts them in checker/dist/.' - dependsOn project(':checker-qual').tasks.jar - def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) - def checkerUtilJarFile = file(project(':checker-util').tasks.getByName('jar').archiveFile) - - doLast { - if (!checkerQualJarFile.exists()) { - throw new GradleException('File not found: ' + checkerQualJarFile) - } - copy { - from checkerQualJarFile - into "${projectDir}/dist" - rename { String fileName -> - // remove version number on checker-qual.jar - fileName.replace(fileName, 'checker-qual.jar') - } - } + description = 'Builds or downloads jars required by CheckerMain and puts them in checker/dist/.' + dependsOn project(':checker-qual').tasks.jar + def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) + def checkerUtilJarFile = file(project(':checker-util').tasks.getByName('jar').archiveFile) - if (!checkerUtilJarFile.exists()) { - throw new GradleException('File not found: ' + checkerUtilJarFile) - } - copy { - from checkerUtilJarFile - into "${projectDir}/dist" - rename { String fileName -> - // remove version number on checker-util.jar - fileName.replace(fileName, 'checker-util.jar') - } - } + doLast { + if (!checkerQualJarFile.exists()) { + throw new GradleException('File not found: ' + checkerQualJarFile) + } + copy { + from checkerQualJarFile + into "${projectDir}/dist" + rename { String fileName -> + // remove version number on checker-qual.jar + fileName.replace(fileName, 'checker-qual.jar') + } + } - copy { - // This is required to *run* the Checker Framework on JDK 8. - from configurations.javacJar - into "${projectDir}/dist" - rename { String fileName -> - fileName.replace(fileName, 'javac.jar') - } + if (!checkerUtilJarFile.exists()) { + throw new GradleException('File not found: ' + checkerUtilJarFile) + } + copy { + from checkerUtilJarFile + into "${projectDir}/dist" + rename { String fileName -> + // remove version number on checker-util.jar + fileName.replace(fileName, 'checker-util.jar') + } + } + + copy { + // This is required to *run* the Checker Framework on JDK 8. + from configurations.javacJar + into "${projectDir}/dist" + rename { String fileName -> + fileName.replace(fileName, 'javac.jar') + } + } } - } } assemble.dependsOn assembleForJavac task allSourcesJar(type: Jar, group: 'Build') { - description = 'Creates a sources jar that includes sources for all Checker Framework classes in checker.jar' - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'checker-source.jar' - archiveClassifier = 'sources' - from (sourceSets.main.java, project(':framework').sourceSets.main.allJava, - project(':dataflow').sourceSets.main.allJava, project(':javacutil').sourceSets.main.allJava, - project(':checker-qual').sourceSets.main.allJava, project(':checker-util').sourceSets.main.allJava) + description = 'Creates a sources jar that includes sources for all Checker Framework classes in checker.jar' + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'checker-source.jar' + archiveClassifier = 'sources' + from (sourceSets.main.java, project(':framework').sourceSets.main.allJava, + project(':dataflow').sourceSets.main.allJava, project(':javacutil').sourceSets.main.allJava, + project(':checker-qual').sourceSets.main.allJava, project(':checker-util').sourceSets.main.allJava) } task allJavadocJar(type: Jar, group: 'Build') { - description = 'Creates javadoc jar including Javadoc for all of the Checker Framework' - dependsOn rootProject.tasks.allJavadoc - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'checker-javadoc.jar' - archiveClassifier = 'javadoc' - from rootProject.tasks.allJavadoc.destinationDir + description = 'Creates javadoc jar including Javadoc for all of the Checker Framework' + dependsOn rootProject.tasks.allJavadoc + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'checker-javadoc.jar' + archiveClassifier = 'javadoc' + from rootProject.tasks.allJavadoc.destinationDir } // Shadowing Test Sources and Dependencies import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar task checkerJar(type: ShadowJar, dependsOn: compileJava, group: 'Build') { - description = "Builds checker-${project.version}.jar with all dependencies except checker-qual and checker-util." - includeEmptyDirs = false - base { - archivesName = 'checker' - } - - from shadowJar.source - configurations = shadowJar.configurations - // To see what files are incorporated into the shadow jar file: - // doLast { println sourceSets.main.runtimeClasspath.asPath } - manifest { - attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') - } - exclude 'org/checkerframework/**/qual/*' - exclude 'org/checkerframework/checker/**/util/*' - relocators = shadowJar.getRelocators() + description = "Builds checker-${project.version}.jar with all dependencies except checker-qual and checker-util." + includeEmptyDirs = false + base { + archivesName = 'checker' + } + + from shadowJar.source + configurations = shadowJar.configurations + // To see what files are incorporated into the shadow jar file: + // doLast { println sourceSets.main.runtimeClasspath.asPath } + manifest { + attributes('Main-Class': 'org.checkerframework.framework.util.CheckerMain') + } + exclude 'org/checkerframework/**/qual/*' + exclude 'org/checkerframework/checker/**/util/*' + relocators = shadowJar.getRelocators() } jar { - dependsOn(checkerJar) - // Never build the skinny jar. - onlyIf {false} - archiveClassifier = 'skinny' + dependsOn(checkerJar) + // Never build the skinny jar. + onlyIf {false} + archiveClassifier = 'skinny' } shadowJar { - description = 'Creates checker-VERSION-all.jar and copies it to dist/checker.jar.' - // To see what files are incorporated into the shadow jar file: - // doFirst { println sourceSets.main.runtimeClasspath.asPath } - archiveClassifier = 'all' - doLast{ - copy { - from archiveFile.get() - into file("${projectDir}/dist") - rename 'checker.*', 'checker.jar' + description = 'Creates checker-VERSION-all.jar and copies it to dist/checker.jar.' + // To see what files are incorporated into the shadow jar file: + // doFirst { println sourceSets.main.runtimeClasspath.asPath } + archiveClassifier = 'all' + doLast{ + copy { + from archiveFile.get() + into file("${projectDir}/dist") + rename 'checker.*', 'checker.jar' + } } - } - minimize() + minimize() } artifacts { - // Don't add this here or else the Javadoc and the sources jar is built during the assemble task. - // archives allJavadocJar - // archives allSourcesJar - archives shadowJar - archives checkerJar + // Don't add this here or else the Javadoc and the sources jar is built during the assemble task. + // archives allJavadocJar + // archives allSourcesJar + archives shadowJar + archives checkerJar - fatJar(shadowJar) + fatJar(shadowJar) } clean { - def injected = project.objects.newInstance(InjectedExecOps) - - delete("${projectDir}/dist") - delete('tests/calledmethods-delomboked') - delete('tests/ainfer-testchecker/annotated') - delete('tests/ainfer-testchecker/inference-output') - delete('tests/ainfer-nullness/annotated') - delete('tests/ainfer-nullness/inference-output') - delete('tests/ainfer-index/annotated') - delete('tests/ainfer-index/inference-output') - delete('tests/ainfer-resourceleak/annotated') - delete('tests/ainfer-resourceleak/inference-output') - delete('tests/build') - doLast { - injected.execOps.exec { - workingDir = file('tests/command-line') - commandLine 'make', 'clean' - } - injected.execOps.exec { - workingDir = file('tests/nullness-extra') - commandLine 'make', 'clean' + def injected = project.objects.newInstance(InjectedExecOps) + + delete("${projectDir}/dist") + delete('tests/calledmethods-delomboked') + delete('tests/ainfer-testchecker/annotated') + delete('tests/ainfer-testchecker/inference-output') + delete('tests/ainfer-nullness/annotated') + delete('tests/ainfer-nullness/inference-output') + delete('tests/ainfer-index/annotated') + delete('tests/ainfer-index/inference-output') + delete('tests/ainfer-resourceleak/annotated') + delete('tests/ainfer-resourceleak/inference-output') + delete('tests/build') + doLast { + injected.execOps.exec { + workingDir = file('tests/command-line') + commandLine 'make', 'clean' + } + injected.execOps.exec { + workingDir = file('tests/nullness-extra') + commandLine 'make', 'clean' + } } - } } clean.doLast { - while (buildDir.exists()) { - sleep(10000) // wait 10 seconds - buildDir.deleteDir() - } + while (buildDir.exists()) { + sleep(10000) // wait 10 seconds + buildDir.deleteDir() + } } // Add non-junit tests createCheckTypeTask(project.name, 'CompilerMessages', - 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') + 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') checkCompilerMessages { - doFirst { - options.compilerArgs += [ - '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + File.pathSeparator - + project(':framework').sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath - ] - } + doFirst { + options.compilerArgs += [ + '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + File.pathSeparator + + project(':framework').sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + ] + } } task nullnessExtraTests(type: Exec, dependsOn: assembleForJavac, group: 'Verification') { - description = 'Run extra tests for the Nullness Checker.' - executable 'make' - environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck", JAVAP: 'javap' - args = ['-C', 'tests/nullness-extra/'] + description = 'Run extra tests for the Nullness Checker.' + executable 'make' + environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck", JAVAP: 'javap' + args = ['-C', 'tests/nullness-extra/'] } task commandLineTests(type: Exec, dependsOn: assembleForJavac, group: 'Verification') { - description = 'Run tests that need a special command line.' - executable 'make' - environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" - args = ['-C', 'tests/command-line/'] + description = 'Run tests that need a special command line.' + executable 'make' + environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" + args = ['-C', 'tests/command-line/'] } task tutorialTests(dependsOn: assembleForJavac, group: 'Verification') { - description = 'Test that the tutorial is working as expected.' - doLast { - ant.ant(dir: "${rootDir}/docs/tutorial/tests", useNativeBasedir: 'true', inheritAll: 'false') { - target(name: 'check-tutorial') + description = 'Test that the tutorial is working as expected.' + doLast { + ant.ant(dir: "${rootDir}/docs/tutorial/tests", useNativeBasedir: 'true', inheritAll: 'false') { + target(name: 'check-tutorial') + } } - } } task exampleTests(type: Exec, dependsOn: assembleForJavac, group: 'Verification') { - description = 'Run tests for the example programs.' - executable 'make' - environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" - args = ['-C', '../docs/examples'] + description = 'Run tests for the example programs.' + executable 'make' + environment JAVAC: "${projectDir}/bin/javac -AnoJreVersionCheck" + args = ['-C', '../docs/examples'] } task demosTests(dependsOn: assembleForJavac, group: 'Verification') { - description = 'Test that the demos are working as expected.' - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - File demosDir = new File(projectDir, '../../checker-framework.demos'); - if (!demosDir.exists()) { - injected.execOps.exec { - workingDir file(demosDir.toString() + '/../') - executable 'git' - args = [ - 'clone', - '--depth', - '1', - 'https://github.com/eisop/checker-framework.demos.git' - ] - } - } else { - injected.execOps.exec { - workingDir demosDir - executable 'git' - args = [ - 'pull', - 'https://github.com/eisop/checker-framework.demos.git' - ] - ignoreExitValue = true - } + description = 'Test that the demos are working as expected.' + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + File demosDir = new File(projectDir, '../../checker-framework.demos'); + if (!demosDir.exists()) { + injected.execOps.exec { + workingDir file(demosDir.toString() + '/../') + executable 'git' + args = [ + 'clone', + '--depth', + '1', + 'https://github.com/eisop/checker-framework.demos.git' + ] + } + } else { + injected.execOps.exec { + workingDir demosDir + executable 'git' + args = [ + 'pull', + 'https://github.com/eisop/checker-framework.demos.git' + ] + ignoreExitValue = true + } + } + ant.properties.put('checker.lib', file("${projectDir}/dist/checker.jar").absolutePath) + ant.ant(dir: demosDir.toString()) } - ant.properties.put('checker.lib', file("${projectDir}/dist/checker.jar").absolutePath) - ant.ant(dir: demosDir.toString()) - } } task templateTests(dependsOn: assembleForJavac, group: 'Verification') { - description = 'Test that the templatefora-checker is working as expected.' - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - File templateforCheckerDir = new File(projectDir, '../../templatefora-checker'); - if (!templateforCheckerDir.exists()) { - injected.execOps.exec { - workingDir file(templateforCheckerDir.toString() + '/../') - executable 'git' - args = [ - 'clone', - '--depth', - '1', - 'https://github.com/eisop/templatefora-checker.git' - ] - } - } else { - injected.execOps.exec { - workingDir templateforCheckerDir - executable 'git' - args = [ - 'pull', - 'https://github.com/eisop/templatefora-checker.git' - ] - ignoreExitValue = true - } - } - println "Running Gradle build in $templateforCheckerDir" - injected.execOps.exec { - workingDir = templateforCheckerDir - commandLine "./gradlew", "build" + description = 'Test that the templatefora-checker is working as expected.' + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + File templateforCheckerDir = new File(projectDir, '../../templatefora-checker'); + if (!templateforCheckerDir.exists()) { + injected.execOps.exec { + workingDir file(templateforCheckerDir.toString() + '/../') + executable 'git' + args = [ + 'clone', + '--depth', + '1', + 'https://github.com/eisop/templatefora-checker.git' + ] + } + } else { + injected.execOps.exec { + workingDir templateforCheckerDir + executable 'git' + args = [ + 'pull', + 'https://github.com/eisop/templatefora-checker.git' + ] + ignoreExitValue = true + } + } + println "Running Gradle build in $templateforCheckerDir" + injected.execOps.exec { + workingDir = templateforCheckerDir + commandLine "./gradlew", "build" + } } - } } task allNullnessTests(type: Test, group: 'Verification') { - description = 'Run all JUnit tests for the Nullness Checker.' - include '**/Nullness*.class' + description = 'Run all JUnit tests for the Nullness Checker.' + include '**/Nullness*.class' } task allCalledMethodsTests(type: Test, group: 'Verification') { - description = 'Run all JUnit tests for the Called Methods Checker.' - include '**/CalledMethods*.class' - if (!skipDelombok) { - dependsOn 'delombok' - } + description = 'Run all JUnit tests for the Called Methods Checker.' + include '**/CalledMethods*.class' + if (!skipDelombok) { + dependsOn 'delombok' + } } task allResourceLeakTests(type: Test, group: 'Verification') { - description = 'Run all JUnit tests for the Resource Leak Checker.' - include '**/ResourceLeak*.class' - include '**/MustCall*.class' + description = 'Run all JUnit tests for the Resource Leak Checker.' + include '**/ResourceLeak*.class' + include '**/MustCall*.class' } // These are tests that should only be run with JDK 11+. task jtregJdk11Tests(dependsOn: ':downloadJtreg', group: 'Verification') { - description = 'Run the jtreg tests made for JDK 11+.' - dependsOn('compileJava') - dependsOn('compileTestJava') - dependsOn('shadowJar') - - def injected = project.objects.newInstance(InjectedExecOps) - - String jtregOutput = "${buildDir}/jtregJdk11" - String name = 'all' - doLast { - if (isJava8) { - println 'This test is only run with JDK 11+.' - return; - } - injected.execOps.exec { - executable "${jtregHome}/bin/jtreg" - args = [ - "-dir:${projectDir}/jtregJdk11", - "-workDir:${jtregOutput}/${name}/work", - "-reportDir:${jtregOutput}/${name}/report", - '-verbose:summary', - '-javacoptions:-g', - '-keywords:!ignore', - '-samevm', - "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", - // Location of jtreg tests - '.' - ] + description = 'Run the jtreg tests made for JDK 11+.' + dependsOn('compileJava') + dependsOn('compileTestJava') + dependsOn('shadowJar') + + def injected = project.objects.newInstance(InjectedExecOps) + + String jtregOutput = "${buildDir}/jtregJdk11" + String name = 'all' + doLast { + if (isJava8) { + println 'This test is only run with JDK 11+.' + return; + } + injected.execOps.exec { + executable "${jtregHome}/bin/jtreg" + args = [ + "-dir:${projectDir}/jtregJdk11", + "-workDir:${jtregOutput}/${name}/work", + "-reportDir:${jtregOutput}/${name}/report", + '-verbose:summary', + '-javacoptions:-g', + '-keywords:!ignore', + '-samevm', + "-javacoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + "-vmoptions:-classpath ${tasks.shadowJar.archiveFile.get()}:${sourceSets.test.output.asPath}", + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '-vmoptions:--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + "-javacoptions:-classpath ${sourceSets.testannotations.output.asPath}", + // Location of jtreg tests + '.' + ] + } } - } } task delombok { - description = 'Delomboks the source code tree in tests/calledmethods-lombok' + description = 'Delomboks the source code tree in tests/calledmethods-lombok' - def srcDelomboked = 'tests/calledmethods-delomboked' - def srcJava = 'tests/calledmethods-lombok' + def srcDelomboked = 'tests/calledmethods-delomboked' + def srcJava = 'tests/calledmethods-lombok' - inputs.files file(srcJava) - outputs.dir file(srcDelomboked) + inputs.files file(srcJava) + outputs.dir file(srcDelomboked) - // Because there are Checker Framework annotations in the test source. - dependsOn project(':checker-qual').tasks.jar + // Because there are Checker Framework annotations in the test source. + dependsOn project(':checker-qual').tasks.jar - doLast { - if(!skipDelombok) { - def collection = files(configurations.testCompileClasspath) - ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', - classpath: collection.asPath) - ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) + doLast { + if(!skipDelombok) { + def collection = files(configurations.testCompileClasspath) + ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', + classpath: collection.asPath) + ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) + } } - } } if (skipDelombok) { - delombok.enabled = false + delombok.enabled = false } else { - tasks.test.dependsOn('delombok') + tasks.test.dependsOn('delombok') } /// @@ -494,134 +494,134 @@ if (skipDelombok) { /// test { - useJUnit { - // These are run in task ainferTest. - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerJaifsGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerStubsGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerAjavaGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessJaifsGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessAjavaGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferIndexAjavaGenerationTest' - excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferResourceLeakAjavaGenerationTest' - } + useJUnit { + // These are run in task ainferTest. + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerJaifsGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerStubsGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferTestCheckerAjavaGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessJaifsGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferNullnessAjavaGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferIndexAjavaGenerationTest' + excludeCategories 'org.checkerframework.checker.test.junit.ainferrunners.AinferResourceLeakAjavaGenerationTest' + } } task ainferTestCheckerGenerateStubs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker files with -Ainfer=stubs to generate stub files.' + description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker files with -Ainfer=stubs to generate stub files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-testchecker/annotated') - delete("${buildDir}/ainfer-testchecker/") - } - outputs.upToDateWhen { false } - include '**/AinferTestCheckerStubsGenerationTest.class' - testLogging { - // Always run the tests + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-testchecker/annotated') + delete("${buildDir}/ainfer-testchecker/") + } outputs.upToDateWhen { false } + include '**/AinferTestCheckerStubsGenerationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') - // The stub file format doesn't support annotations on anonymous inner classes, so - // this test also expects errors on these tests that expect annotations to be inferred - // inside anonymous classes. - delete('tests/ainfer-testchecker/annotated/UsesAnonymous.java') - delete('tests/ainfer-testchecker/annotated/AnonymousClassWithField.java') - - // This test outputs a warning about records. - delete('tests/ainfer-testchecker/annotated/all-systems/java17/Issue6069.java') - - // This test causes an error when its corresponding stub file is read, because the test - // contains an annotation definition. The stub file parser does not support reading - // files that define annotations; this test can be reinstated if the stub parser - // is extended to support annotation definitions. - delete('tests/ainfer-testchecker/annotated/all-systems/Issue4083.java') - - copy { - from file('tests/ainfer-testchecker/non-annotated/UsesAnonymous.java') - into file('tests/ainfer-testchecker/annotated') + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') + // The stub file format doesn't support annotations on anonymous inner classes, so + // this test also expects errors on these tests that expect annotations to be inferred + // inside anonymous classes. + delete('tests/ainfer-testchecker/annotated/UsesAnonymous.java') + delete('tests/ainfer-testchecker/annotated/AnonymousClassWithField.java') + + // This test outputs a warning about records. + delete('tests/ainfer-testchecker/annotated/all-systems/java17/Issue6069.java') + + // This test causes an error when its corresponding stub file is read, because the test + // contains an annotation definition. The stub file parser does not support reading + // files that define annotations; this test can be reinstated if the stub parser + // is extended to support annotation definitions. + delete('tests/ainfer-testchecker/annotated/all-systems/Issue4083.java') + + copy { + from file('tests/ainfer-testchecker/non-annotated/UsesAnonymous.java') + into file('tests/ainfer-testchecker/annotated') + } } - } } task ainferTestCheckerValidateStubs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker tests using the stub files generated by ainferTestCheckerGenerateStubs.' + description = 'Internal task. Users should run ainferTestCheckerStubTest instead. This type-checks the ainfer-testchecker tests using the stub files generated by ainferTestCheckerGenerateStubs.' - dependsOn(ainferTestCheckerGenerateStubs) - outputs.upToDateWhen { false } - include '**/AinferTestCheckerStubsValidationTest.class' - testLogging { - // Always run the tests + dependsOn(ainferTestCheckerGenerateStubs) outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + include '**/AinferTestCheckerStubsValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferTestCheckerGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This type-checks the ainfer-testchecker files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This type-checks the ainfer-testchecker files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-testchecker/annotated') - delete("${buildDir}/ainfer-testchecker/") - } - outputs.upToDateWhen { false } - include '**/AinferTestCheckerAjavaGenerationTest.class' - testLogging { - // Always run the tests + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-testchecker/annotated') + delete("${buildDir}/ainfer-testchecker/") + } outputs.upToDateWhen { false } + include '**/AinferTestCheckerAjavaGenerationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') - - // AinferTestCheckerAjavaValidationTest fails with "warning: (purity.methodref)", whenever - // there is a user-defined generic interface, and a variable of that type is assigned a - // method reference. - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Issue946.java') - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Receivers.java') - - // This test must be deleted, because otherwise an error about a missing type in an - // ajava file is issued. The test itself shouldn't be run as an all-systems test while testing - // WPI; see the copy in the non-annotated WPI tests for an explanation. - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Purity.java') - - // There is some kind of bad interaction between the purity checker's inference mode - // and method references to constructors: every one of them in this test causes a - // purity.methodref warning during validation. This problem only occurs for ajava-based - // inference because the relevant purity annotations that seem to trigger it are on - // inner classes, which stubs cannot annotate. - // TODO: investigate the cause of this error in the Purity checker, fix it, and then reinstate this test. - delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/MemberReferences.java') - } + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') + + // AinferTestCheckerAjavaValidationTest fails with "warning: (purity.methodref)", whenever + // there is a user-defined generic interface, and a variable of that type is assigned a + // method reference. + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Issue946.java') + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Receivers.java') + + // This test must be deleted, because otherwise an error about a missing type in an + // ajava file is issued. The test itself shouldn't be run as an all-systems test while testing + // WPI; see the copy in the non-annotated WPI tests for an explanation. + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/Purity.java') + + // There is some kind of bad interaction between the purity checker's inference mode + // and method references to constructors: every one of them in this test causes a + // purity.methodref warning during validation. This problem only occurs for ajava-based + // inference because the relevant purity annotations that seem to trigger it are on + // inner classes, which stubs cannot annotate. + // TODO: investigate the cause of this error in the Purity checker, fix it, and then reinstate this test. + delete('tests/ainfer-testchecker/annotated/all-systems/java8/memberref/MemberReferences.java') + } } task ainferTestCheckerValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This re-type-checks the ainfer-testchecker files using the ajava files generated by ainferTestCheckerGenerateAjava' + description = 'Internal task. Users should run ainferTestCheckerAjavaTest instead. This re-type-checks the ainfer-testchecker files using the ajava files generated by ainferTestCheckerGenerateAjava' - dependsOn(ainferTestCheckerGenerateAjava) - outputs.upToDateWhen { false } - include '**/AinferTestCheckerAjavaValidationTest.class' - testLogging { - // Always run the tests + dependsOn(ainferTestCheckerGenerateAjava) outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + include '**/AinferTestCheckerAjavaValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } // Copies directories as needed by WPI tests. @@ -630,381 +630,381 @@ task ainferTestCheckerValidateAjava(type: Test) { // 1. Copies whole-program inference test source code from the non-annotated/ to the annotated/ directory. // 2. Copies WPI output, such as .jaif or .stub files, to the inferference-output/ directory. void copyNonannotatedToAnnotatedDirectory(String testdir) { - // Copying all test files to another directory, removing all expected errors that should not - // occur after inserting inferred annotations from .jaif files. - copy { - from files("tests/${testdir}/non-annotated") - into file("tests/${testdir}/annotated") - filter { String line -> - (line.contains('// :: error:') - // Don't remove unchecked cast warnings, because they're genuinely expected in some all-systems - // tests, such as GenericsCasts.java. - || (line.contains('// :: warning:') && !line.contains('// :: warning: [unchecked]'))) - ? null : line + // Copying all test files to another directory, removing all expected errors that should not + // occur after inserting inferred annotations from .jaif files. + copy { + from files("tests/${testdir}/non-annotated") + into file("tests/${testdir}/annotated") + filter { String line -> + (line.contains('// :: error:') + // Don't remove unchecked cast warnings, because they're genuinely expected in some all-systems + // tests, such as GenericsCasts.java. + || (line.contains('// :: warning:') && !line.contains('// :: warning: [unchecked]'))) + ? null : line + } } - } - // The only file for which expected errors are maintained is ExpectedErrors.java, so we copy it over. - delete("tests/${testdir}/annotated/ExpectedErrors.java") - copy { - from file("tests/${testdir}/non-annotated/ExpectedErrors.java") - into file("tests/${testdir}/annotated") - } - - delete("tests/${testdir}/inference-output") - file('build/whole-program-inference').renameTo(file("tests/${testdir}/inference-output")) + // The only file for which expected errors are maintained is ExpectedErrors.java, so we copy it over. + delete("tests/${testdir}/annotated/ExpectedErrors.java") + copy { + from file("tests/${testdir}/non-annotated/ExpectedErrors.java") + into file("tests/${testdir}/annotated") + } + + delete("tests/${testdir}/inference-output") + file('build/whole-program-inference').renameTo(file("tests/${testdir}/inference-output")) } // This task is similar to the ainferTestCheckerJaifTest task below, but it doesn't // run the insert-annotations-to-source tool. Instead, it tests the -Ainfer=stubs feature // and the -AmergeStubsWithSource feature to do WPI using stub files. task ainferTestCheckerStubTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using stub files' - dependsOn(ainferTestCheckerValidateStubs) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using stub files' + dependsOn(ainferTestCheckerValidateStubs) + outputs.upToDateWhen { false } } // Like ainferTestCheckerStubTest, but with ajava files instead task ainferTestCheckerAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files' - dependsOn(ainferTestCheckerValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files' + dependsOn(ainferTestCheckerValidateAjava) + outputs.upToDateWhen { false } } task ainferTestCheckerGenerateJaifs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files with -Ainfer=jaifs to generate .jaif files' + description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files with -Ainfer=jaifs to generate .jaif files' - dependsOn(compileTestJava) - dependsOn(':checker-qual:jar') // For the Value Checker annotations. - doFirst { - delete('tests/ainfer-testchecker/annotated') - } - outputs.upToDateWhen { false } - include '**/AinferTestCheckerJaifsGenerationTest.class' - testLogging { - // Always run the tests - outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') - - // JAIF-based WPI fails these tests, which were added for stub-based WPI. - // See issue here: https://github.com/typetools/checker-framework/issues/3009 - delete('tests/ainfer-testchecker/annotated/ConflictingAnnotationsTest.java') - delete('tests/ainfer-testchecker/annotated/MultiDimensionalArrays.java') - - // JAIF-based WPI also fails this test. It used to pass, but the test was changed - // in a way that exposed a bug in the Annotation File Utilities: the AFU - // places annotations incorrectly on qualified types. In this test, a failure occurs because - // the AFU prints "@Annotation Outer.Inner this", rather than "Outer.@Annotation Inner this" - // (see an explanation of the syntax here: - // https://eisop.github.io/cf/manual/#common-problems-non-typechecking). - // TODO: fix this bug in the AFU, then reinstate this test. - delete('tests/ainfer-testchecker/annotated/OverriddenMethodsTest.java') - - // JAIF-based WPI fails this test, too, because the insertion of a declaration annotation - // onto a field with a multi-part type (e.g. Outer.Inner) doesn't appear to be supported by the AFU. - delete('tests/ainfer-testchecker/annotated/InnerClassFieldDeclAnno.java') - - // Inserting annotations from .jaif files in-place. - String jaifsDir = 'tests/ainfer-testchecker/inference-output'; - List jaifs = fileTree(jaifsDir).matching { - include '*.jaif' - }.asList() - if (jaifs.isEmpty()) { - throw new GradleException("no .jaif files found in ${jaifsDir}") + dependsOn(compileTestJava) + dependsOn(':checker-qual:jar') // For the Value Checker annotations. + doFirst { + delete('tests/ainfer-testchecker/annotated') } - String javasDir = 'tests/ainfer-testchecker/annotated/'; - List javas = fileTree(javasDir).matching { - include '*.java' - }.asList() - if (javas.isEmpty()) { - throw new GradleException("no .java files found in ${javasDir}") + outputs.upToDateWhen { false } + include '**/AinferTestCheckerJaifsGenerationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' } - def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) - injected.execOps.exec { - executable "${afu}/scripts/insert-annotations-to-source" - // Script argument -cp must precede Java program argument -i. - // checker-qual is needed for Constant Value Checker annotations. - // Note that "/" works on Windows as well as on Linux. - args = [ - '-cp', - "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') - ] - args += ['-i'] - for (File jaif : jaifs) { - args += [jaif.toString()] - } - for (File javaFile : javas) { - args += [javaFile.toString()] - } + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-testchecker') + + // JAIF-based WPI fails these tests, which were added for stub-based WPI. + // See issue here: https://github.com/typetools/checker-framework/issues/3009 + delete('tests/ainfer-testchecker/annotated/ConflictingAnnotationsTest.java') + delete('tests/ainfer-testchecker/annotated/MultiDimensionalArrays.java') + + // JAIF-based WPI also fails this test. It used to pass, but the test was changed + // in a way that exposed a bug in the Annotation File Utilities: the AFU + // places annotations incorrectly on qualified types. In this test, a failure occurs because + // the AFU prints "@Annotation Outer.Inner this", rather than "Outer.@Annotation Inner this" + // (see an explanation of the syntax here: + // https://eisop.github.io/cf/manual/#common-problems-non-typechecking). + // TODO: fix this bug in the AFU, then reinstate this test. + delete('tests/ainfer-testchecker/annotated/OverriddenMethodsTest.java') + + // JAIF-based WPI fails this test, too, because the insertion of a declaration annotation + // onto a field with a multi-part type (e.g. Outer.Inner) doesn't appear to be supported by the AFU. + delete('tests/ainfer-testchecker/annotated/InnerClassFieldDeclAnno.java') + + // Inserting annotations from .jaif files in-place. + String jaifsDir = 'tests/ainfer-testchecker/inference-output'; + List jaifs = fileTree(jaifsDir).matching { + include '*.jaif' + }.asList() + if (jaifs.isEmpty()) { + throw new GradleException("no .jaif files found in ${jaifsDir}") + } + String javasDir = 'tests/ainfer-testchecker/annotated/'; + List javas = fileTree(javasDir).matching { + include '*.java' + }.asList() + if (javas.isEmpty()) { + throw new GradleException("no .java files found in ${javasDir}") + } + + def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) + injected.execOps.exec { + executable "${afu}/scripts/insert-annotations-to-source" + // Script argument -cp must precede Java program argument -i. + // checker-qual is needed for Constant Value Checker annotations. + // Note that "/" works on Windows as well as on Linux. + args = [ + '-cp', + "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') + ] + args += ['-i'] + for (File jaif : jaifs) { + args += [jaif.toString()] + } + for (File javaFile : javas) { + args += [javaFile.toString()] + } + } } - } } task ainferTestCheckerValidateJaifs(type: Test) { - description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files using the .jaif files generated by ainferTestCheckerGenerateJaifs' + description = 'Internal task. Users should run ainferTestCheckerJaifTest instead. This type-checks the ainfer-testchecker files using the .jaif files generated by ainferTestCheckerGenerateJaifs' - dependsOn(ainferTestCheckerGenerateJaifs) - outputs.upToDateWhen { false } - include '**/AinferTestCheckerJaifsValidationTest.class' - testLogging { - // Always run the tests + dependsOn(ainferTestCheckerGenerateJaifs) outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + include '**/AinferTestCheckerJaifsValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferTestCheckerJaifTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using .jaif files' - dependsOn(ainferTestCheckerValidateJaifs) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using .jaif files' + dependsOn(ainferTestCheckerValidateJaifs) + outputs.upToDateWhen { false } } task ainferIndexGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferIndexAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferIndexAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-index/annotated') - delete("${buildDir}/ainfer-index/") - } - outputs.upToDateWhen { false } - include '**/AinferIndexAjavaGenerationTest.class' - testLogging { - // Always run the tests + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-index/annotated') + delete("${buildDir}/ainfer-index/") + } outputs.upToDateWhen { false } + include '**/AinferIndexAjavaGenerationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-index') - } + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-index') + } } task ainferIndexValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferIndexAjavaTest instead. This re-type-checks the ainfer-index files using the ajava files generated by ainferIndexGenerateAjava' + description = 'Internal task. Users should run ainferIndexAjavaTest instead. This re-type-checks the ainfer-index files using the ajava files generated by ainferIndexGenerateAjava' - dependsOn(ainferIndexGenerateAjava) - outputs.upToDateWhen { false } - include '**/AinferIndexAjavaValidationTest.class' - testLogging { - // Always run the tests + dependsOn(ainferIndexGenerateAjava) outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + include '**/AinferIndexAjavaValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferIndexAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files and the Index Checker' - dependsOn(ainferIndexValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files and the Index Checker' + dependsOn(ainferIndexValidateAjava) + outputs.upToDateWhen { false } } task ainferNullnessGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This type-checks the ainfer-nullness files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This type-checks the ainfer-nullness files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-nullness/annotated') - delete("${buildDir}/ainfer-nullness/") - } - outputs.upToDateWhen { false } - include '**/AinferNullnessAjavaGenerationTest.class' - testLogging { - // Always run the tests + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-nullness/annotated') + delete("${buildDir}/ainfer-nullness/") + } outputs.upToDateWhen { false } + include '**/AinferNullnessAjavaGenerationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-nullness') - } + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-nullness') + } } task ainferNullnessValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This re-type-checks the ainfer-nullness files using the ajava files generated by ainferNullnessGenerateAjava' + description = 'Internal task. Users should run ainferNullnessAjavaTest instead. This re-type-checks the ainfer-nullness files using the ajava files generated by ainferNullnessGenerateAjava' - dependsOn(ainferNullnessGenerateAjava) - outputs.upToDateWhen { false } - include '**/AinferNullnessAjavaValidationTest.class' - testLogging { - // Always run the tests + dependsOn(ainferNullnessGenerateAjava) outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + include '**/AinferNullnessAjavaValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferNullnessAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files and the Nullness Checker' - dependsOn(ainferNullnessValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files and the Nullness Checker' + dependsOn(ainferNullnessValidateAjava) + outputs.upToDateWhen { false } } task ainferResourceLeakGenerateAjava(type: Test) { - description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' + description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This type-checks the ainfer-index files with -Ainfer=ajava to generate ajava files.' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-resourceleak/annotated') - delete("${buildDir}/ainfer-resourceleak/") - } - outputs.upToDateWhen { false } - include '**/AinferResourceLeakAjavaGenerationTest.class' - testLogging { - // Always run the tests + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-resourceleak/annotated') + delete("${buildDir}/ainfer-resourceleak/") + } outputs.upToDateWhen { false } + include '**/AinferResourceLeakAjavaGenerationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-resourceleak') - } + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-resourceleak') + } } task ainferResourceLeakValidateAjava(type: Test) { - description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This re-type-checks the ainfer-resourceleak files using the ajava files generated by ainferResourceLeakGenerateAjava' + description = 'Internal task. Users should run ainferResourceLeakAjavaTest instead. This re-type-checks the ainfer-resourceleak files using the ajava files generated by ainferResourceLeakGenerateAjava' - dependsOn(ainferResourceLeakGenerateAjava) - outputs.upToDateWhen { false } - include '**/AinferResourceLeakAjavaValidationTest.class' - testLogging { - // Always run the tests + dependsOn(ainferResourceLeakGenerateAjava) outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and the expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + include '**/AinferResourceLeakAjavaValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and the expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferResourceLeakAjavaTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using ajava files and the Resource Leak Checker' - dependsOn(ainferResourceLeakValidateAjava) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using ajava files and the Resource Leak Checker' + dependsOn(ainferResourceLeakValidateAjava) + outputs.upToDateWhen { false } } task ainferNullnessGenerateJaifs(type: Test) { - description = 'Internal task. Users should run ainferNullnessJaifTest instead. This type-checks the ainfer-nullness files with -Ainfer=jaifs to generate .jaif files' + description = 'Internal task. Users should run ainferNullnessJaifTest instead. This type-checks the ainfer-nullness files with -Ainfer=jaifs to generate .jaif files' - dependsOn(compileTestJava) - doFirst { - delete('tests/ainfer-nullness/annotated') - } - outputs.upToDateWhen { false } - include '**/AinferNullnessJaifsGenerationTest.class' - testLogging { - // Always run the tests + dependsOn(compileTestJava) + doFirst { + delete('tests/ainfer-nullness/annotated') + } outputs.upToDateWhen { false } + include '**/AinferNullnessJaifsGenerationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } - - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - copyNonannotatedToAnnotatedDirectory('ainfer-nullness') + doLast { + copyNonannotatedToAnnotatedDirectory('ainfer-nullness') - // JAIF-based WPI does not infer annotations on uses of type variables correctly. - delete('tests/ainfer-nullness/annotated/TwoCtorGenericAbstract.java') - delete('tests/ainfer-nullness/annotated/TypeVarReturnAnnotated.java') + // JAIF-based WPI does not infer annotations on uses of type variables correctly. + delete('tests/ainfer-nullness/annotated/TwoCtorGenericAbstract.java') + delete('tests/ainfer-nullness/annotated/TypeVarReturnAnnotated.java') - // Inserting annotations from .jaif files in-place. - String jaifsDir = 'tests/ainfer-nullness/inference-output'; - List jaifs = fileTree(jaifsDir).matching { - include '*.jaif' - }.asList() - if (jaifs.isEmpty()) { - throw new GradleException("no .jaif files found in ${jaifsDir}") - } - String javasDir = 'tests/ainfer-nullness/annotated/'; - List javas = fileTree(javasDir).matching { - include '*.java' - }.asList() - if (javas.isEmpty()) { - throw new GradleException("no .java files found in ${javasDir}") - } - def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) - injected.execOps.exec { - executable "${afu}/scripts/insert-annotations-to-source" - // Script argument -cp must precede Java program argument -i. - // Note that "/" works on Windows as well as on Linux. - args = [ - '-cp', - "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') - ] - args += ['-i'] - for (File jaif : jaifs) { - args += [jaif.toString()] - } - for (File javaFile : javas) { - args += [javaFile.toString()] - } + // Inserting annotations from .jaif files in-place. + String jaifsDir = 'tests/ainfer-nullness/inference-output'; + List jaifs = fileTree(jaifsDir).matching { + include '*.jaif' + }.asList() + if (jaifs.isEmpty()) { + throw new GradleException("no .jaif files found in ${jaifsDir}") + } + String javasDir = 'tests/ainfer-nullness/annotated/'; + List javas = fileTree(javasDir).matching { + include '*.java' + }.asList() + if (javas.isEmpty()) { + throw new GradleException("no .java files found in ${javasDir}") + } + def checkerQualJarFile = file(project(':checker-qual').tasks.getByName('jar').archiveFile) + injected.execOps.exec { + executable "${afu}/scripts/insert-annotations-to-source" + // Script argument -cp must precede Java program argument -i. + // Note that "/" works on Windows as well as on Linux. + args = [ + '-cp', + "${sourceSets.test.output.asPath}:${checkerQualJarFile}:" + file('tests/build/testclasses') + ] + args += ['-i'] + for (File jaif : jaifs) { + args += [jaif.toString()] + } + for (File javaFile : javas) { + args += [javaFile.toString()] + } + } } - } } task ainferNullnessValidateJaifs(type: Test) { - description = 'Internal task. Users should run ainferNullnessJaifTest instead. This re-type-checks the ainfer-nullness files using the .jaif files generated by ainferNullnessGenerateJaifs' + description = 'Internal task. Users should run ainferNullnessJaifTest instead. This re-type-checks the ainfer-nullness files using the .jaif files generated by ainferNullnessGenerateJaifs' - dependsOn(ainferNullnessGenerateJaifs) - outputs.upToDateWhen { false } - include '**/AinferNullnessJaifsValidationTest.class' - testLogging { - // Always run the tests + dependsOn(ainferNullnessGenerateJaifs) outputs.upToDateWhen { false } - - // Show the found unexpected diagnostics and expected diagnostics not found. - exceptionFormat = 'full' - events 'passed', 'skipped', 'failed' - } + include '**/AinferNullnessJaifsValidationTest.class' + testLogging { + // Always run the tests + outputs.upToDateWhen { false } + + // Show the found unexpected diagnostics and expected diagnostics not found. + exceptionFormat = 'full' + events 'passed', 'skipped', 'failed' + } } task ainferNullnessJaifTest(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for whole-program inference using .jaif files' - dependsOn(ainferNullnessValidateJaifs) - outputs.upToDateWhen { false } + description = 'Run tests for whole-program inference using .jaif files' + dependsOn(ainferNullnessValidateJaifs) + outputs.upToDateWhen { false } } // Empty task that just runs both the jaif and stub WPI tests. // It is run as part of the inferenceTests task. task ainferTest(group: 'Verification') { - description = 'Run tests for all whole program inference modes.' - dependsOn('ainferTestCheckerJaifTest') - dependsOn('ainferTestCheckerStubTest') - dependsOn('ainferTestCheckerAjavaTest') - dependsOn('ainferNullnessJaifTest') - dependsOn('ainferNullnessAjavaTest') - dependsOn('ainferIndexAjavaTest') - dependsOn('ainferResourceLeakAjavaTest') + description = 'Run tests for all whole program inference modes.' + dependsOn('ainferTestCheckerJaifTest') + dependsOn('ainferTestCheckerStubTest') + dependsOn('ainferTestCheckerAjavaTest') + dependsOn('ainferNullnessJaifTest') + dependsOn('ainferNullnessAjavaTest') + dependsOn('ainferIndexAjavaTest') + dependsOn('ainferResourceLeakAjavaTest') } /// @@ -1013,197 +1013,197 @@ task ainferTest(group: 'Verification') { // This is run as part of the inferenceTests task. task wpiManyTest(group: 'Verification') { - description = 'Tests the wpi-many.sh script (and indirectly the wpi.sh script). Requires an Internet connection.' - dependsOn(assembleForJavac) - dependsOn(':getDoLikeJavac') - // This test must always be re-run when requested. - outputs.upToDateWhen { false } - - doFirst { - delete("${project.projectDir}/build/wpi-many-tests-results/") - // wpi-many.sh is run in skip mode so that logs are preserved, but - // we don't actually want to skip previously-failing tests when we - // re-run the tests locally. - delete fileTree("${project.projectDir}/build/wpi-many-tests") { - include '**/.cannot-run-wpi' - } - } - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - // Run wpi-many.sh - def typecheckFilesDir = "${project.projectDir}/build/wpi-many-tests-results/" - try { - injected.execOps.exec { - environment CHECKERFRAMEWORK: "${projectDir}/.." - commandLine 'bin/wpi-many.sh', - '-i', "${project.projectDir}/tests/wpi-many/testin.txt", - '-o', "${project.projectDir}/build/wpi-many-tests", - '-s', - '--', - '--checker', 'nullness,interning,lock,regex,signature,calledmethods,resourceleak', - '--extraJavacArgs=-AenableWpiForRlc' - } - } catch (Exception e) { - println('Failure: Running wpi-many.sh failed with a non-zero exit code.') - File wpiOut = new File("${typecheckFilesDir}/wpi-out") - if (wpiOut.exists()) { - println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - injected.execOps.exec { - commandLine 'cat', "${typecheckFilesDir}/wpi-out" + description = 'Tests the wpi-many.sh script (and indirectly the wpi.sh script). Requires an Internet connection.' + dependsOn(assembleForJavac) + dependsOn(':getDoLikeJavac') + // This test must always be re-run when requested. + outputs.upToDateWhen { false } + + doFirst { + delete("${project.projectDir}/build/wpi-many-tests-results/") + // wpi-many.sh is run in skip mode so that logs are preserved, but + // we don't actually want to skip previously-failing tests when we + // re-run the tests locally. + delete fileTree("${project.projectDir}/build/wpi-many-tests") { + include '**/.cannot-run-wpi' } - println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - } else { - println("========= File ${typecheckFilesDir}/wpi-out does not exist. ========") - } - throw e } - // collect the logs from running WPI - def typecheckFiles = fileTree(typecheckFilesDir).matching { - include '**/*-typecheck.out' - } - def testinLines = file("${project.projectDir}/tests/wpi-many/testin.txt").text.readLines() - testinLines.removeIf { it.startsWith('#') } - def expectedTypecheckFileCount = testinLines.size() - def actualTypecheckFileCount = typecheckFiles.size() - if (actualTypecheckFileCount != expectedTypecheckFileCount) { - println("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + - "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") - println("========= Found in ${typecheckFilesDir} ========") - injected.execOps.exec { - commandLine 'ls', '-al', "${typecheckFilesDir}" - } - println("========= Expected in ${typecheckFilesDir} ========") - injected.execOps.exec { - commandLine 'cat', "${project.projectDir}/tests/wpi-many/testin.txt" - } - println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - injected.execOps.exec { - commandLine 'cat', "${typecheckFilesDir}/wpi-out" - } - println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") - def logFiles = fileTree(typecheckFilesDir).matching { - include '**/*.log' - } - logFiles.visit { FileVisitDetails details -> - def filename = "${typecheckFilesDir}" + details.getName() - println("======== start of contents of ${filename} ========") - details.getFile().eachLine { line -> println(line) } - println("======== end of contents of ${filename} ========") - } - // If any of these files are present, their contents should be an error - // message that might indicate what went wrong. Even their presenence, - // however, is intereseting (even if they are empty). - def cannotRunWpiFiles = fileTree(typecheckFilesDir).matching { - include '**/.cannot-run-wpi' - } - cannotRunWpiFiles.visit { FileVisitDetails details -> - def filename = "${typecheckFilesDir}" + details.getName() - println("======== start of contents of ${filename} ========") - details.getFile().eachLine { line -> println(line) } - println("======== end of contents of ${filename} ========") - } - throw new GradleException("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + - "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") - } + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + // Run wpi-many.sh + def typecheckFilesDir = "${project.projectDir}/build/wpi-many-tests-results/" + try { + injected.execOps.exec { + environment CHECKERFRAMEWORK: "${projectDir}/.." + commandLine 'bin/wpi-many.sh', + '-i', "${project.projectDir}/tests/wpi-many/testin.txt", + '-o', "${project.projectDir}/build/wpi-many-tests", + '-s', + '--', + '--checker', 'nullness,interning,lock,regex,signature,calledmethods,resourceleak', + '--extraJavacArgs=-AenableWpiForRlc' + } + } catch (Exception e) { + println('Failure: Running wpi-many.sh failed with a non-zero exit code.') + File wpiOut = new File("${typecheckFilesDir}/wpi-out") + if (wpiOut.exists()) { + println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + injected.execOps.exec { + commandLine 'cat', "${typecheckFilesDir}/wpi-out" + } + println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + } else { + println("========= File ${typecheckFilesDir}/wpi-out does not exist. ========") + } + throw e + } - // check that WPI causes the expected builds to succeed - typecheckFiles.visit { FileVisitDetails details -> - def filename = "${project.projectDir}/build/wpi-many-tests-results/" + details.getName() - def file = details.getFile() - if (file.length() == 0) { - throw new GradleException('Failure: WPI produced empty typecheck file ' + filename) - } - file.eachLine { line -> - if ( - // Ignore the line that WPI echoes with the javac command being run. - line.startsWith('Running ') - // Warnings about bad path elements aren't related to WPI and are ignored. - || line.startsWith('warning: [path]') - // Ignore bootstrap classpath warning: - || line.startsWith('warning: [options] bootstrap') - // Ignore the warnings about --add-opens arguments to the JVM - || line.contains('warning: [options] --add-opens has no effect at compile time') - // Ignore the summary line that reports the total number of warnings (which can be single or plural). - || line.endsWith(' warning') - || line.endsWith(' warnings') - || line.startsWith('warning: No processor claimed any of these annotations: ')) { - return; + // collect the logs from running WPI + def typecheckFiles = fileTree(typecheckFilesDir).matching { + include '**/*-typecheck.out' } - if (!line.trim().equals('')) { - println("======== start of contents of ${filename} ========") - details.getFile().eachLine { l -> println(l) } - println("======== end of contents of ${filename} ========") - throw new GradleException('Failure: WPI scripts produced an unexpected output in ' + filename + '. ' + - 'Failing line is the following: ' + line) + def testinLines = file("${project.projectDir}/tests/wpi-many/testin.txt").text.readLines() + testinLines.removeIf { it.startsWith('#') } + def expectedTypecheckFileCount = testinLines.size() + def actualTypecheckFileCount = typecheckFiles.size() + if (actualTypecheckFileCount != expectedTypecheckFileCount) { + println("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + + "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") + println("========= Found in ${typecheckFilesDir} ========") + injected.execOps.exec { + commandLine 'ls', '-al', "${typecheckFilesDir}" + } + println("========= Expected in ${typecheckFilesDir} ========") + injected.execOps.exec { + commandLine 'cat', "${project.projectDir}/tests/wpi-many/testin.txt" + } + println("========= Start of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + injected.execOps.exec { + commandLine 'cat', "${typecheckFilesDir}/wpi-out" + } + println("========= End of output from last run of wpi.sh (${typecheckFilesDir}/wpi-out): ========") + def logFiles = fileTree(typecheckFilesDir).matching { + include '**/*.log' + } + logFiles.visit { FileVisitDetails details -> + def filename = "${typecheckFilesDir}" + details.getName() + println("======== start of contents of ${filename} ========") + details.getFile().eachLine { line -> println(line) } + println("======== end of contents of ${filename} ========") + } + // If any of these files are present, their contents should be an error + // message that might indicate what went wrong. Even their presenence, + // however, is intereseting (even if they are empty). + def cannotRunWpiFiles = fileTree(typecheckFilesDir).matching { + include '**/.cannot-run-wpi' + } + cannotRunWpiFiles.visit { FileVisitDetails details -> + def filename = "${typecheckFilesDir}" + details.getName() + println("======== start of contents of ${filename} ========") + details.getFile().eachLine { line -> println(line) } + println("======== end of contents of ${filename} ========") + } + throw new GradleException("Failure: Too few *-typecheck.out files in ${typecheckFilesDir}: " + + "found ${actualTypecheckFileCount} but expected ${expectedTypecheckFileCount}.") + } + + // check that WPI causes the expected builds to succeed + typecheckFiles.visit { FileVisitDetails details -> + def filename = "${project.projectDir}/build/wpi-many-tests-results/" + details.getName() + def file = details.getFile() + if (file.length() == 0) { + throw new GradleException('Failure: WPI produced empty typecheck file ' + filename) + } + file.eachLine { line -> + if ( + // Ignore the line that WPI echoes with the javac command being run. + line.startsWith('Running ') + // Warnings about bad path elements aren't related to WPI and are ignored. + || line.startsWith('warning: [path]') + // Ignore bootstrap classpath warning: + || line.startsWith('warning: [options] bootstrap') + // Ignore the warnings about --add-opens arguments to the JVM + || line.contains('warning: [options] --add-opens has no effect at compile time') + // Ignore the summary line that reports the total number of warnings (which can be single or plural). + || line.endsWith(' warning') + || line.endsWith(' warnings') + || line.startsWith('warning: No processor claimed any of these annotations: ')) { + return; + } + if (!line.trim().equals('')) { + println("======== start of contents of ${filename} ========") + details.getFile().eachLine { l -> println(l) } + println("======== end of contents of ${filename} ========") + throw new GradleException('Failure: WPI scripts produced an unexpected output in ' + filename + '. ' + + 'Failing line is the following: ' + line) + } + } } - } } - } } // This is run as part of the inferenceTests task. task wpiPlumeLibTest(group: 'Verification') { - description = 'Tests whole-program inference on the plume-lib projects. Requires an Internet connection.' - dependsOn(assembleForJavac) - dependsOn(':getDoLikeJavac') + description = 'Tests whole-program inference on the plume-lib projects. Requires an Internet connection.' + dependsOn(assembleForJavac) + dependsOn(':getDoLikeJavac') - // This test must always be re-run when requested. - outputs.upToDateWhen { false } + // This test must always be re-run when requested. + outputs.upToDateWhen { false } - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - commandLine 'bin-devel/wpi-plumelib/test-wpi-plumelib.sh' - ignoreExitValue = false + doLast { + injected.execOps.exec { + commandLine 'bin-devel/wpi-plumelib/test-wpi-plumelib.sh' + ignoreExitValue = false + } } - } } apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final checkerPom(publication) { - sharedPublicationConfiguration(publication) - // Don't use publication.from components.java which would publish the skinny jar as checker.jar. - publication.pom { - name = 'Checker Framework' - description = 'The Checker Framework enhances Java\'s type system to\n' + - 'make it more powerful and useful. This lets software developers\n' + - 'detect and prevent errors in their Java programs.\n' + - 'The Checker Framework includes compiler plug-ins ("checkers")\n' + - 'that find bugs or verify their absence. It also permits you to\n' + - 'write your own compiler plug-ins.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + // Don't use publication.from components.java which would publish the skinny jar as checker.jar. + publication.pom { + name = 'Checker Framework' + description = 'The Checker Framework enhances Java\'s type system to\n' + + 'make it more powerful and useful. This lets software developers\n' + + 'detect and prevent errors in their Java programs.\n' + + 'The Checker Framework includes compiler plug-ins ("checkers")\n' + + 'that find bugs or verify their absence. It also permits you to\n' + + 'write your own compiler plug-ins.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } + } } - } } publishing { - publications { - checker(MavenPublication) { - project.shadow.component it - // reset the artifacts because of project.shadow.component changes - // the classifier from 'all' to '' because of - // https://github.com/johnrengelman/shadow/issues/860 - artifacts = [shadowJar] - checkerPom it - artifact checkerJar - artifact allSourcesJar - artifact allJavadocJar + publications { + checker(MavenPublication) { + project.shadow.component it + // reset the artifacts because of project.shadow.component changes + // the classifier from 'all' to '' because of + // https://github.com/johnrengelman/shadow/issues/860 + artifacts = [shadowJar] + checkerPom it + artifact checkerJar + artifact allSourcesJar + artifact allJavadocJar + } } - } } signing { - sign publishing.publications.checker + sign publishing.publications.checker } diff --git a/checker/jtreg/SymbolNotFoundErrors.java b/checker/jtreg/SymbolNotFoundErrors.java index 6a93e7ed40a..8d869c33cbc 100644 --- a/checker/jtreg/SymbolNotFoundErrors.java +++ b/checker/jtreg/SymbolNotFoundErrors.java @@ -5,5 +5,5 @@ * @compile/fail/ref=SymbolNotFoundErrors2.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker SymbolNotFoundErrors.java */ public class SymbolNotFoundErrors { - CCC f; + CCC f; } diff --git a/checker/jtreg/index/UnneededSuppressionsTest.java b/checker/jtreg/index/UnneededSuppressionsTest.java index 5d4e6f7e568..fe71afccaaf 100644 --- a/checker/jtreg/index/UnneededSuppressionsTest.java +++ b/checker/jtreg/index/UnneededSuppressionsTest.java @@ -9,36 +9,36 @@ public class UnneededSuppressionsTest { - void method(@NonNegative int i) { - @SuppressWarnings("index") - @NonNegative int x = i - 1; - } - - void method2() { - @SuppressWarnings("fallthrough") - int x = 0; - } - - @SuppressWarnings({"tainting", "lowerbound"}) - void method3() { - @SuppressWarnings("upperbound:assignment.type.incompatible") - int z = 0; - } - - void method4() { - @SuppressWarnings("lowerbound:assignment.type.incompatible") - @NonNegative int x = -1; - } - - @SuppressWarnings("purity.not.deterministic.call") - void method5() {} - - @SuppressWarnings("purity") - void method6() {} - - @SuppressWarnings("index:foo.bar.baz") - void method7() {} - - @SuppressWarnings("allcheckers:purity.not.deterministic.call") - void method8() {} + void method(@NonNegative int i) { + @SuppressWarnings("index") + @NonNegative int x = i - 1; + } + + void method2() { + @SuppressWarnings("fallthrough") + int x = 0; + } + + @SuppressWarnings({"tainting", "lowerbound"}) + void method3() { + @SuppressWarnings("upperbound:assignment.type.incompatible") + int z = 0; + } + + void method4() { + @SuppressWarnings("lowerbound:assignment.type.incompatible") + @NonNegative int x = -1; + } + + @SuppressWarnings("purity.not.deterministic.call") + void method5() {} + + @SuppressWarnings("purity") + void method6() {} + + @SuppressWarnings("index:foo.bar.baz") + void method7() {} + + @SuppressWarnings("allcheckers:purity.not.deterministic.call") + void method8() {} } diff --git a/checker/jtreg/index/valuestub/Test.java b/checker/jtreg/index/valuestub/Test.java index 6c35a2d30bc..c06676fa9f8 100644 --- a/checker/jtreg/index/valuestub/Test.java +++ b/checker/jtreg/index/valuestub/Test.java @@ -4,7 +4,7 @@ @SuppressWarnings("index") public class Test { - public @LengthOf("this") int length() { - return 1; - } + public @LengthOf("this") int length() { + return 1; + } } diff --git a/checker/jtreg/index/valuestub/UseTest.java b/checker/jtreg/index/valuestub/UseTest.java index 4be77b34b5f..a117721fdd6 100644 --- a/checker/jtreg/index/valuestub/UseTest.java +++ b/checker/jtreg/index/valuestub/UseTest.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class UseTest { - void test(Test t) { - @IndexOrHigh("t") int x = t.length(); - } + void test(Test t) { + @IndexOrHigh("t") int x = t.length(); + } } diff --git a/checker/jtreg/issue1133/ClassA.java b/checker/jtreg/issue1133/ClassA.java index 1c3e27f646c..88d5b48d904 100644 --- a/checker/jtreg/issue1133/ClassA.java +++ b/checker/jtreg/issue1133/ClassA.java @@ -1,5 +1,5 @@ public class ClassA { - void test(ClassB classB) { - classB.test(); - } + void test(ClassB classB) { + classB.test(); + } } diff --git a/checker/jtreg/issue1133/ClassB.java b/checker/jtreg/issue1133/ClassB.java index e8a1d1060d3..c545d790662 100644 --- a/checker/jtreg/issue1133/ClassB.java +++ b/checker/jtreg/issue1133/ClassB.java @@ -1,5 +1,5 @@ public class ClassB { - public void test() { - Object @Nullable [] o; - } + public void test() { + Object @Nullable [] o; + } } diff --git a/checker/jtreg/issue469/advancedcrash/CrashyInterface.java b/checker/jtreg/issue469/advancedcrash/CrashyInterface.java index e687821a1f1..65e072885de 100644 --- a/checker/jtreg/issue469/advancedcrash/CrashyInterface.java +++ b/checker/jtreg/issue469/advancedcrash/CrashyInterface.java @@ -1,5 +1,5 @@ package advancedcrash; public interface CrashyInterface { - void makeItLongerAndCrash(); + void makeItLongerAndCrash(); } diff --git a/checker/jtreg/issue469/advancedcrash/LetItCrash.java b/checker/jtreg/issue469/advancedcrash/LetItCrash.java index f95fdfb67c6..14cebb2e3e5 100644 --- a/checker/jtreg/issue469/advancedcrash/LetItCrash.java +++ b/checker/jtreg/issue469/advancedcrash/LetItCrash.java @@ -2,15 +2,15 @@ public class LetItCrash implements SomeInterface { - Integer longer = 0; + Integer longer = 0; - @Override - public void doSomethingFancy() { - System.out.print("Yay"); - } + @Override + public void doSomethingFancy() { + System.out.print("Yay"); + } - @Override - public void makeItLongerAndCrash() { - this.longer += 0; - } + @Override + public void makeItLongerAndCrash() { + this.longer += 0; + } } diff --git a/checker/jtreg/issue469/advancedcrash/SomeInterface.java b/checker/jtreg/issue469/advancedcrash/SomeInterface.java index e83a4dc9747..100621f38a8 100644 --- a/checker/jtreg/issue469/advancedcrash/SomeInterface.java +++ b/checker/jtreg/issue469/advancedcrash/SomeInterface.java @@ -1,5 +1,5 @@ package advancedcrash; public interface SomeInterface extends CrashyInterface { - void doSomethingFancy(); + void doSomethingFancy(); } diff --git a/checker/jtreg/issue469/simplecrash/CrashyInterface.java b/checker/jtreg/issue469/simplecrash/CrashyInterface.java index 7016050f86e..49d75c6c85f 100644 --- a/checker/jtreg/issue469/simplecrash/CrashyInterface.java +++ b/checker/jtreg/issue469/simplecrash/CrashyInterface.java @@ -1,5 +1,5 @@ package simplecrash; public interface CrashyInterface { - void makeItLongerAndCrash(); + void makeItLongerAndCrash(); } diff --git a/checker/jtreg/issue469/simplecrash/LetItCrash.java b/checker/jtreg/issue469/simplecrash/LetItCrash.java index 519742f513c..6ef150ad5d7 100644 --- a/checker/jtreg/issue469/simplecrash/LetItCrash.java +++ b/checker/jtreg/issue469/simplecrash/LetItCrash.java @@ -1,10 +1,10 @@ package simplecrash; public class LetItCrash implements CrashyInterface { - private Long longer = 0L; + private Long longer = 0L; - @Override - public void makeItLongerAndCrash() { - this.longer += 0; - } + @Override + public void makeItLongerAndCrash() { + this.longer += 0; + } } diff --git a/checker/jtreg/issue469/simplecrash/SomeRandomClass.java b/checker/jtreg/issue469/simplecrash/SomeRandomClass.java index f4e89aaa4d5..a28dbf5bbd9 100644 --- a/checker/jtreg/issue469/simplecrash/SomeRandomClass.java +++ b/checker/jtreg/issue469/simplecrash/SomeRandomClass.java @@ -1,7 +1,7 @@ package simplecrash; public class SomeRandomClass { - void randomStuff(LetItCrash letItCrash) { - letItCrash.makeItLongerAndCrash(); - } + void randomStuff(LetItCrash letItCrash) { + letItCrash.makeItLongerAndCrash(); + } } diff --git a/checker/jtreg/multiplecheckers/NullnessInterning.java b/checker/jtreg/multiplecheckers/NullnessInterning.java index 89b3f95b131..faf6529c90a 100644 --- a/checker/jtreg/multiplecheckers/NullnessInterning.java +++ b/checker/jtreg/multiplecheckers/NullnessInterning.java @@ -16,9 +16,9 @@ */ public class NullnessInterning { - Object f = null; + Object f = null; - void m(Object p) { - if (f == p) {} - } + void m(Object p) { + if (f == p) {} + } } diff --git a/checker/jtreg/multipleexecutions/Main.java b/checker/jtreg/multipleexecutions/Main.java index ecf00de1e1a..a4aa9dac6f5 100644 --- a/checker/jtreg/multipleexecutions/Main.java +++ b/checker/jtreg/multipleexecutions/Main.java @@ -11,50 +11,52 @@ * https://groups.google.com/d/msg/checker-framework-dev/FvWmCxB8OpE/Cgp1DsPwnWwJ */ +import org.checkerframework.checker.regex.RegexChecker; + import java.io.File; import java.util.Arrays; + import javax.tools.JavaCompiler; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; -import org.checkerframework.checker.regex.RegexChecker; public class Main { - public static void main(String[] args) { - final JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); - final StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null); - if (!doStuff(javac, fileManager)) { - return; + public static void main(String[] args) { + final JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); + final StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, null); + if (!doStuff(javac, fileManager)) { + return; + } + if (!doStuff(javac, fileManager)) { + return; + } + if (!doStuff(javac, fileManager)) { + return; + } } - if (!doStuff(javac, fileManager)) { - return; - } - if (!doStuff(javac, fileManager)) { - return; - } - } - public static boolean doStuff(JavaCompiler javac, StandardJavaFileManager fileManager) { - File testfile = new File(System.getProperty("test.src", "."), "Test.java"); + public static boolean doStuff(JavaCompiler javac, StandardJavaFileManager fileManager) { + File testfile = new File(System.getProperty("test.src", "."), "Test.java"); - JavaCompiler.CompilationTask task = - javac.getTask( - null, - null, - null, - Arrays.asList( - "-classpath", - "../../dist/checker.jar", - "-proc:only", - "-AprintAllQualifiers", - "-source", - "8", - "-target", - "8", - "-Xlint:-options"), - null, - fileManager.getJavaFileObjects(testfile)); - task.setProcessors(Arrays.asList(new RegexChecker())); - return task.call(); - } + JavaCompiler.CompilationTask task = + javac.getTask( + null, + null, + null, + Arrays.asList( + "-classpath", + "../../dist/checker.jar", + "-proc:only", + "-AprintAllQualifiers", + "-source", + "8", + "-target", + "8", + "-Xlint:-options"), + null, + fileManager.getJavaFileObjects(testfile)); + task.setProcessors(Arrays.asList(new RegexChecker())); + return task.call(); + } } diff --git a/checker/jtreg/multipleexecutions/Test.java b/checker/jtreg/multipleexecutions/Test.java index 4813553b092..5322099b53d 100644 --- a/checker/jtreg/multipleexecutions/Test.java +++ b/checker/jtreg/multipleexecutions/Test.java @@ -2,9 +2,9 @@ import org.checkerframework.checker.regex.util.RegexUtil; public class Test { - void foo(String simple) { - if (RegexUtil.isRegex(simple)) { - @Regex String in = simple; + void foo(String simple) { + if (RegexUtil.isRegex(simple)) { + @Regex String in = simple; + } } - } } diff --git a/checker/jtreg/nullness/AssignmentPerformanceTest.java b/checker/jtreg/nullness/AssignmentPerformanceTest.java index e71e69d5a36..884c900456f 100644 --- a/checker/jtreg/nullness/AssignmentPerformanceTest.java +++ b/checker/jtreg/nullness/AssignmentPerformanceTest.java @@ -5,457 +5,457 @@ * @compile/timeout=60 -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint AssignmentPerformanceTest.java */ public class AssignmentPerformanceTest { - private String s1; - private String s2; - private String s3; - private String s4; - private String s5; - private String s6; - private String s7; - private String s8; - private String s9; - private String s10; - private String s11; - private String s12; - private String s13; - private String s14; - private String s15; - private String s16; - private String s17; - private String s18; - private String s19; - private String s20; - private String s21; - private String s22; - private String s23; - private String s24; - private String s25; - private String s26; - private String s27; - private String s28; - private String s29; - private String s30; - private String s31; - private String s32; - private String s33; - private String s34; - private String s35; - private String s36; - private String s37; - private String s38; - private String s39; - private String s40; - private String s41; - private String s42; - private String s43; - private String s44; - private String s45; - private String s46; - private String s47; - private String s48; - private String s49; - private String s50; - private String s51; - private String s52; - private String s53; - private String s54; - private String s55; - private String s56; - private String s57; - private String s58; - private String s59; - private String s60; - private String s61; - private String s62; - private String s63; - private String s64; - private String s65; - private String s66; - private String s67; - private String s68; - private String s69; - private String s70; - private String s71; - private String s72; - private String s73; - private String s74; - private String s75; - private String s76; - private String s77; - private String s78; - private String s79; - private String s80; - private String s81; - private String s82; - private String s83; - private String s84; - private String s85; - private String s86; - private String s87; - private String s88; - private String s89; - private String s90; - private String s91; - private String s92; - private String s93; - private String s94; - private String s95; - private String s96; - private String s97; - private String s98; - private String s99; - private String s100; - private String s101; - private String s102; - private String s103; - private String s104; - private String s105; - private String s106; - private String s107; - private String s108; - private String s109; - private String s110; - private String s111; - private String s112; - private String s113; - private String s114; - private String s115; - private String s116; - private String s117; - private String s118; - private String s119; - private String s120; - private String s121; - private String s122; - private String s123; - private String s124; - private String s125; - private String s126; - private String s127; - private String s128; - private String s129; - private String s130; - private String s131; - private String s132; - private String s133; - private String s134; - private String s135; - private String s136; - private String s137; - private String s138; - private String s139; - private String s140; - private String s141; - private String s142; - private String s143; - private String s144; - private String s145; - private String s146; - private String s147; - private String s148; - private String s149; - private String s150; + private String s1; + private String s2; + private String s3; + private String s4; + private String s5; + private String s6; + private String s7; + private String s8; + private String s9; + private String s10; + private String s11; + private String s12; + private String s13; + private String s14; + private String s15; + private String s16; + private String s17; + private String s18; + private String s19; + private String s20; + private String s21; + private String s22; + private String s23; + private String s24; + private String s25; + private String s26; + private String s27; + private String s28; + private String s29; + private String s30; + private String s31; + private String s32; + private String s33; + private String s34; + private String s35; + private String s36; + private String s37; + private String s38; + private String s39; + private String s40; + private String s41; + private String s42; + private String s43; + private String s44; + private String s45; + private String s46; + private String s47; + private String s48; + private String s49; + private String s50; + private String s51; + private String s52; + private String s53; + private String s54; + private String s55; + private String s56; + private String s57; + private String s58; + private String s59; + private String s60; + private String s61; + private String s62; + private String s63; + private String s64; + private String s65; + private String s66; + private String s67; + private String s68; + private String s69; + private String s70; + private String s71; + private String s72; + private String s73; + private String s74; + private String s75; + private String s76; + private String s77; + private String s78; + private String s79; + private String s80; + private String s81; + private String s82; + private String s83; + private String s84; + private String s85; + private String s86; + private String s87; + private String s88; + private String s89; + private String s90; + private String s91; + private String s92; + private String s93; + private String s94; + private String s95; + private String s96; + private String s97; + private String s98; + private String s99; + private String s100; + private String s101; + private String s102; + private String s103; + private String s104; + private String s105; + private String s106; + private String s107; + private String s108; + private String s109; + private String s110; + private String s111; + private String s112; + private String s113; + private String s114; + private String s115; + private String s116; + private String s117; + private String s118; + private String s119; + private String s120; + private String s121; + private String s122; + private String s123; + private String s124; + private String s125; + private String s126; + private String s127; + private String s128; + private String s129; + private String s130; + private String s131; + private String s132; + private String s133; + private String s134; + private String s135; + private String s136; + private String s137; + private String s138; + private String s139; + private String s140; + private String s141; + private String s142; + private String s143; + private String s144; + private String s145; + private String s146; + private String s147; + private String s148; + private String s149; + private String s150; - public AssignmentPerformanceTest( - String s1, - String s2, - String s3, - String s4, - String s5, - String s6, - String s7, - String s8, - String s9, - String s10, - String s11, - String s12, - String s13, - String s14, - String s15, - String s16, - String s17, - String s18, - String s19, - String s20, - String s21, - String s22, - String s23, - String s24, - String s25, - String s26, - String s27, - String s28, - String s29, - String s30, - String s31, - String s32, - String s33, - String s34, - String s35, - String s36, - String s37, - String s38, - String s39, - String s40, - String s41, - String s42, - String s43, - String s44, - String s45, - String s46, - String s47, - String s48, - String s49, - String s50, - String s51, - String s52, - String s53, - String s54, - String s55, - String s56, - String s57, - String s58, - String s59, - String s60, - String s61, - String s62, - String s63, - String s64, - String s65, - String s66, - String s67, - String s68, - String s69, - String s70, - String s71, - String s72, - String s73, - String s74, - String s75, - String s76, - String s77, - String s78, - String s79, - String s80, - String s81, - String s82, - String s83, - String s84, - String s85, - String s86, - String s87, - String s88, - String s89, - String s90, - String s91, - String s92, - String s93, - String s94, - String s95, - String s96, - String s97, - String s98, - String s99, - String s100, - String s101, - String s102, - String s103, - String s104, - String s105, - String s106, - String s107, - String s108, - String s109, - String s110, - String s111, - String s112, - String s113, - String s114, - String s115, - String s116, - String s117, - String s118, - String s119, - String s120, - String s121, - String s122, - String s123, - String s124, - String s125, - String s126, - String s127, - String s128, - String s129, - String s130, - String s131, - String s132, - String s133, - String s134, - String s135, - String s136, - String s137, - String s138, - String s139, - String s140, - String s141, - String s142, - String s143, - String s144, - String s145, - String s146, - String s147, - String s148, - String s149, - String s150) { - this.s1 = s1; - this.s2 = s2; - this.s3 = s3; - this.s4 = s4; - this.s5 = s5; - this.s6 = s6; - this.s7 = s7; - this.s8 = s8; - this.s9 = s9; - this.s10 = s10; - this.s11 = s11; - this.s12 = s12; - this.s13 = s13; - this.s14 = s14; - this.s15 = s15; - this.s16 = s16; - this.s17 = s17; - this.s18 = s18; - this.s19 = s19; - this.s20 = s20; - this.s21 = s21; - this.s22 = s22; - this.s23 = s23; - this.s24 = s24; - this.s25 = s25; - this.s26 = s26; - this.s27 = s27; - this.s28 = s28; - this.s29 = s29; - this.s30 = s30; - this.s31 = s31; - this.s32 = s32; - this.s33 = s33; - this.s34 = s34; - this.s35 = s35; - this.s36 = s36; - this.s37 = s37; - this.s38 = s38; - this.s39 = s39; - this.s40 = s40; - this.s41 = s41; - this.s42 = s42; - this.s43 = s43; - this.s44 = s44; - this.s45 = s45; - this.s46 = s46; - this.s47 = s47; - this.s48 = s48; - this.s49 = s49; - this.s50 = s50; - this.s51 = s51; - this.s52 = s52; - this.s53 = s53; - this.s54 = s54; - this.s55 = s55; - this.s56 = s56; - this.s57 = s57; - this.s58 = s58; - this.s59 = s59; - this.s60 = s60; - this.s61 = s61; - this.s62 = s62; - this.s63 = s63; - this.s64 = s64; - this.s65 = s65; - this.s66 = s66; - this.s67 = s67; - this.s68 = s68; - this.s69 = s69; - this.s70 = s70; - this.s71 = s71; - this.s72 = s72; - this.s73 = s73; - this.s74 = s74; - this.s75 = s75; - this.s76 = s76; - this.s77 = s77; - this.s78 = s78; - this.s79 = s79; - this.s80 = s80; - this.s81 = s81; - this.s82 = s82; - this.s83 = s83; - this.s84 = s84; - this.s85 = s85; - this.s86 = s86; - this.s87 = s87; - this.s88 = s88; - this.s89 = s89; - this.s90 = s90; - this.s91 = s91; - this.s92 = s92; - this.s93 = s93; - this.s94 = s94; - this.s95 = s95; - this.s96 = s96; - this.s97 = s97; - this.s98 = s98; - this.s99 = s99; - this.s100 = s100; - this.s101 = s101; - this.s102 = s102; - this.s103 = s103; - this.s104 = s104; - this.s105 = s105; - this.s106 = s106; - this.s107 = s107; - this.s108 = s108; - this.s109 = s109; - this.s110 = s110; - this.s111 = s111; - this.s112 = s112; - this.s113 = s113; - this.s114 = s114; - this.s115 = s115; - this.s116 = s116; - this.s117 = s117; - this.s118 = s118; - this.s119 = s119; - this.s120 = s120; - this.s121 = s121; - this.s122 = s122; - this.s123 = s123; - this.s124 = s124; - this.s125 = s125; - this.s126 = s126; - this.s127 = s127; - this.s128 = s128; - this.s129 = s129; - this.s130 = s130; - this.s131 = s131; - this.s132 = s132; - this.s133 = s133; - this.s134 = s134; - this.s135 = s135; - this.s136 = s136; - this.s137 = s137; - this.s138 = s138; - this.s139 = s139; - this.s140 = s140; - this.s141 = s141; - this.s142 = s142; - this.s143 = s143; - this.s144 = s144; - this.s145 = s145; - this.s146 = s146; - this.s147 = s147; - this.s148 = s148; - this.s149 = s149; - this.s150 = s150; - } + public AssignmentPerformanceTest( + String s1, + String s2, + String s3, + String s4, + String s5, + String s6, + String s7, + String s8, + String s9, + String s10, + String s11, + String s12, + String s13, + String s14, + String s15, + String s16, + String s17, + String s18, + String s19, + String s20, + String s21, + String s22, + String s23, + String s24, + String s25, + String s26, + String s27, + String s28, + String s29, + String s30, + String s31, + String s32, + String s33, + String s34, + String s35, + String s36, + String s37, + String s38, + String s39, + String s40, + String s41, + String s42, + String s43, + String s44, + String s45, + String s46, + String s47, + String s48, + String s49, + String s50, + String s51, + String s52, + String s53, + String s54, + String s55, + String s56, + String s57, + String s58, + String s59, + String s60, + String s61, + String s62, + String s63, + String s64, + String s65, + String s66, + String s67, + String s68, + String s69, + String s70, + String s71, + String s72, + String s73, + String s74, + String s75, + String s76, + String s77, + String s78, + String s79, + String s80, + String s81, + String s82, + String s83, + String s84, + String s85, + String s86, + String s87, + String s88, + String s89, + String s90, + String s91, + String s92, + String s93, + String s94, + String s95, + String s96, + String s97, + String s98, + String s99, + String s100, + String s101, + String s102, + String s103, + String s104, + String s105, + String s106, + String s107, + String s108, + String s109, + String s110, + String s111, + String s112, + String s113, + String s114, + String s115, + String s116, + String s117, + String s118, + String s119, + String s120, + String s121, + String s122, + String s123, + String s124, + String s125, + String s126, + String s127, + String s128, + String s129, + String s130, + String s131, + String s132, + String s133, + String s134, + String s135, + String s136, + String s137, + String s138, + String s139, + String s140, + String s141, + String s142, + String s143, + String s144, + String s145, + String s146, + String s147, + String s148, + String s149, + String s150) { + this.s1 = s1; + this.s2 = s2; + this.s3 = s3; + this.s4 = s4; + this.s5 = s5; + this.s6 = s6; + this.s7 = s7; + this.s8 = s8; + this.s9 = s9; + this.s10 = s10; + this.s11 = s11; + this.s12 = s12; + this.s13 = s13; + this.s14 = s14; + this.s15 = s15; + this.s16 = s16; + this.s17 = s17; + this.s18 = s18; + this.s19 = s19; + this.s20 = s20; + this.s21 = s21; + this.s22 = s22; + this.s23 = s23; + this.s24 = s24; + this.s25 = s25; + this.s26 = s26; + this.s27 = s27; + this.s28 = s28; + this.s29 = s29; + this.s30 = s30; + this.s31 = s31; + this.s32 = s32; + this.s33 = s33; + this.s34 = s34; + this.s35 = s35; + this.s36 = s36; + this.s37 = s37; + this.s38 = s38; + this.s39 = s39; + this.s40 = s40; + this.s41 = s41; + this.s42 = s42; + this.s43 = s43; + this.s44 = s44; + this.s45 = s45; + this.s46 = s46; + this.s47 = s47; + this.s48 = s48; + this.s49 = s49; + this.s50 = s50; + this.s51 = s51; + this.s52 = s52; + this.s53 = s53; + this.s54 = s54; + this.s55 = s55; + this.s56 = s56; + this.s57 = s57; + this.s58 = s58; + this.s59 = s59; + this.s60 = s60; + this.s61 = s61; + this.s62 = s62; + this.s63 = s63; + this.s64 = s64; + this.s65 = s65; + this.s66 = s66; + this.s67 = s67; + this.s68 = s68; + this.s69 = s69; + this.s70 = s70; + this.s71 = s71; + this.s72 = s72; + this.s73 = s73; + this.s74 = s74; + this.s75 = s75; + this.s76 = s76; + this.s77 = s77; + this.s78 = s78; + this.s79 = s79; + this.s80 = s80; + this.s81 = s81; + this.s82 = s82; + this.s83 = s83; + this.s84 = s84; + this.s85 = s85; + this.s86 = s86; + this.s87 = s87; + this.s88 = s88; + this.s89 = s89; + this.s90 = s90; + this.s91 = s91; + this.s92 = s92; + this.s93 = s93; + this.s94 = s94; + this.s95 = s95; + this.s96 = s96; + this.s97 = s97; + this.s98 = s98; + this.s99 = s99; + this.s100 = s100; + this.s101 = s101; + this.s102 = s102; + this.s103 = s103; + this.s104 = s104; + this.s105 = s105; + this.s106 = s106; + this.s107 = s107; + this.s108 = s108; + this.s109 = s109; + this.s110 = s110; + this.s111 = s111; + this.s112 = s112; + this.s113 = s113; + this.s114 = s114; + this.s115 = s115; + this.s116 = s116; + this.s117 = s117; + this.s118 = s118; + this.s119 = s119; + this.s120 = s120; + this.s121 = s121; + this.s122 = s122; + this.s123 = s123; + this.s124 = s124; + this.s125 = s125; + this.s126 = s126; + this.s127 = s127; + this.s128 = s128; + this.s129 = s129; + this.s130 = s130; + this.s131 = s131; + this.s132 = s132; + this.s133 = s133; + this.s134 = s134; + this.s135 = s135; + this.s136 = s136; + this.s137 = s137; + this.s138 = s138; + this.s139 = s139; + this.s140 = s140; + this.s141 = s141; + this.s142 = s142; + this.s143 = s143; + this.s144 = s144; + this.s145 = s145; + this.s146 = s146; + this.s147 = s147; + this.s148 = s148; + this.s149 = s149; + this.s150 = s150; + } } diff --git a/checker/jtreg/nullness/DefaultNonPublicClass.java b/checker/jtreg/nullness/DefaultNonPublicClass.java index 6908779f315..81030cd57b3 100644 --- a/checker/jtreg/nullness/DefaultNonPublicClass.java +++ b/checker/jtreg/nullness/DefaultNonPublicClass.java @@ -10,14 +10,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; class Test { - Integer foo() { - return 123; - } + Integer foo() { + return 123; + } } public class DefaultNonPublicClass { - public static void main(String[] args) { - Test ti = new Test(); - @NonNull Integer ls = ti.foo(); - } + public static void main(String[] args) { + Test ti = new Test(); + @NonNull Integer ls = ti.foo(); + } } diff --git a/checker/jtreg/nullness/Issue1438.java b/checker/jtreg/nullness/Issue1438.java index 7f0039f77ec..f90e6af852b 100644 --- a/checker/jtreg/nullness/Issue1438.java +++ b/checker/jtreg/nullness/Issue1438.java @@ -9,2011 +9,2011 @@ import java.util.Map; public class Issue1438 { - // Do not initialize these variables. The Nullness Checker is supposed to issue an error about - // uninitialized fields. - static Integer v0; - static Integer v1; - static Integer v2; - static Integer v3; - static Integer v4; - static Integer v5; - static Integer v6; - static Integer v7; - static Integer v8; - static Integer v9; - static Integer v10; - static Integer v11; - static Integer v12; - static Integer v13; - static Integer v14; - static Integer v15; - static Integer v16; - static Integer v17; - static Integer v18; - static Integer v19; - static Integer v20; - static Integer v21; - static Integer v22; - static Integer v23; - static Integer v24; - static Integer v25; - static Integer v26; - static Integer v27; - static Integer v28; - static Integer v29; - static Integer v30; - static Integer v31; - static Integer v32; - static Integer v33; - static Integer v34; - static Integer v35; - static Integer v36; - static Integer v37; - static Integer v38; - static Integer v39; - static Integer v40; - static Integer v41; - static Integer v42; - static Integer v43; - static Integer v44; - static Integer v45; - static Integer v46; - static Integer v47; - static Integer v48; - static Integer v49; - static Integer v50; - static Integer v51; - static Integer v52; - static Integer v53; - static Integer v54; - static Integer v55; - static Integer v56; - static Integer v57; - static Integer v58; - static Integer v59; - static Integer v60; - static Integer v61; - static Integer v62; - static Integer v63; - static Integer v64; - static Integer v65; - static Integer v66; - static Integer v67; - static Integer v68; - static Integer v69; - static Integer v70; - static Integer v71; - static Integer v72; - static Integer v73; - static Integer v74; - static Integer v75; - static Integer v76; - static Integer v77; - static Integer v78; - static Integer v79; - static Integer v80; - static Integer v81; - static Integer v82; - static Integer v83; - static Integer v84; - static Integer v85; - static Integer v86; - static Integer v87; - static Integer v88; - static Integer v89; - static Integer v90; - static Integer v91; - static Integer v92; - static Integer v93; - static Integer v94; - static Integer v95; - static Integer v96; - static Integer v97; - static Integer v98; - static Integer v99; - static Integer v100; - static Integer v101; - static Integer v102; - static Integer v103; - static Integer v104; - static Integer v105; - static Integer v106; - static Integer v107; - static Integer v108; - static Integer v109; - static Integer v110; - static Integer v111; - static Integer v112; - static Integer v113; - static Integer v114; - static Integer v115; - static Integer v116; - static Integer v117; - static Integer v118; - static Integer v119; - static Integer v120; - static Integer v121; - static Integer v122; - static Integer v123; - static Integer v124; - static Integer v125; - static Integer v126; - static Integer v127; - static Integer v128; - static Integer v129; - static Integer v130; - static Integer v131; - static Integer v132; - static Integer v133; - static Integer v134; - static Integer v135; - static Integer v136; - static Integer v137; - static Integer v138; - static Integer v139; - static Integer v140; - static Integer v141; - static Integer v142; - static Integer v143; - static Integer v144; - static Integer v145; - static Integer v146; - static Integer v147; - static Integer v148; - static Integer v149; - static Integer v150; - static Integer v151; - static Integer v152; - static Integer v153; - static Integer v154; - static Integer v155; - static Integer v156; - static Integer v157; - static Integer v158; - static Integer v159; - static Integer v160; - static Integer v161; - static Integer v162; - static Integer v163; - static Integer v164; - static Integer v165; - static Integer v166; - static Integer v167; - static Integer v168; - static Integer v169; - static Integer v170; - static Integer v171; - static Integer v172; - static Integer v173; - static Integer v174; - static Integer v175; - static Integer v176; - static Integer v177; - static Integer v178; - static Integer v179; - static Integer v180; - static Integer v181; - static Integer v182; - static Integer v183; - static Integer v184; - static Integer v185; - static Integer v186; - static Integer v187; - static Integer v188; - static Integer v189; - static Integer v190; - static Integer v191; - static Integer v192; - static Integer v193; - static Integer v194; - static Integer v195; - static Integer v196; - static Integer v197; - static Integer v198; - static Integer v199; - static Integer v200; - static Integer v201; - static Integer v202; - static Integer v203; - static Integer v204; - static Integer v205; - static Integer v206; - static Integer v207; - static Integer v208; - static Integer v209; - static Integer v210; - static Integer v211; - static Integer v212; - static Integer v213; - static Integer v214; - static Integer v215; - static Integer v216; - static Integer v217; - static Integer v218; - static Integer v219; - static Integer v220; - static Integer v221; - static Integer v222; - static Integer v223; - static Integer v224; - static Integer v225; - static Integer v226; - static Integer v227; - static Integer v228; - static Integer v229; - static Integer v230; - static Integer v231; - static Integer v232; - static Integer v233; - static Integer v234; - static Integer v235; - static Integer v236; - static Integer v237; - static Integer v238; - static Integer v239; - static Integer v240; - static Integer v241; - static Integer v242; - static Integer v243; - static Integer v244; - static Integer v245; - static Integer v246; - static Integer v247; - static Integer v248; - static Integer v249; - static Integer v250; - static Integer v251; - static Integer v252; - static Integer v253; - static Integer v254; - static Integer v255; - static Integer v256; - static Integer v257; - static Integer v258; - static Integer v259; - static Integer v260; - static Integer v261; - static Integer v262; - static Integer v263; - static Integer v264; - static Integer v265; - static Integer v266; - static Integer v267; - static Integer v268; - static Integer v269; - static Integer v270; - static Integer v271; - static Integer v272; - static Integer v273; - static Integer v274; - static Integer v275; - static Integer v276; - static Integer v277; - static Integer v278; - static Integer v279; - static Integer v280; - static Integer v281; - static Integer v282; - static Integer v283; - static Integer v284; - static Integer v285; - static Integer v286; - static Integer v287; - static Integer v288; - static Integer v289; - static Integer v290; - static Integer v291; - static Integer v292; - static Integer v293; - static Integer v294; - static Integer v295; - static Integer v296; - static Integer v297; - static Integer v298; - static Integer v299; - static Integer v300; - static Integer v301; - static Integer v302; - static Integer v303; - static Integer v304; - static Integer v305; - static Integer v306; - static Integer v307; - static Integer v308; - static Integer v309; - static Integer v310; - static Integer v311; - static Integer v312; - static Integer v313; - static Integer v314; - static Integer v315; - static Integer v316; - static Integer v317; - static Integer v318; - static Integer v319; - static Integer v320; - static Integer v321; - static Integer v322; - static Integer v323; - static Integer v324; - static Integer v325; - static Integer v326; - static Integer v327; - static Integer v328; - static Integer v329; - static Integer v330; - static Integer v331; - static Integer v332; - static Integer v333; - static Integer v334; - static Integer v335; - static Integer v336; - static Integer v337; - static Integer v338; - static Integer v339; - static Integer v340; - static Integer v341; - static Integer v342; - static Integer v343; - static Integer v344; - static Integer v345; - static Integer v346; - static Integer v347; - static Integer v348; - static Integer v349; - static Integer v350; - static Integer v351; - static Integer v352; - static Integer v353; - static Integer v354; - static Integer v355; - static Integer v356; - static Integer v357; - static Integer v358; - static Integer v359; - static Integer v360; - static Integer v361; - static Integer v362; - static Integer v363; - static Integer v364; - static Integer v365; - static Integer v366; - static Integer v367; - static Integer v368; - static Integer v369; - static Integer v370; - static Integer v371; - static Integer v372; - static Integer v373; - static Integer v374; - static Integer v375; - static Integer v376; - static Integer v377; - static Integer v378; - static Integer v379; - static Integer v380; - static Integer v381; - static Integer v382; - static Integer v383; - static Integer v384; - static Integer v385; - static Integer v386; - static Integer v387; - static Integer v388; - static Integer v389; - static Integer v390; - static Integer v391; - static Integer v392; - static Integer v393; - static Integer v394; - static Integer v395; - static Integer v396; - static Integer v397; - static Integer v398; - static Integer v399; - static Integer v400; - static Integer v401; - static Integer v402; - static Integer v403; - static Integer v404; - static Integer v405; - static Integer v406; - static Integer v407; - static Integer v408; - static Integer v409; - static Integer v410; - static Integer v411; - static Integer v412; - static Integer v413; - static Integer v414; - static Integer v415; - static Integer v416; - static Integer v417; - static Integer v418; - static Integer v419; - static Integer v420; - static Integer v421; - static Integer v422; - static Integer v423; - static Integer v424; - static Integer v425; - static Integer v426; - static Integer v427; - static Integer v428; - static Integer v429; - static Integer v430; - static Integer v431; - static Integer v432; - static Integer v433; - static Integer v434; - static Integer v435; - static Integer v436; - static Integer v437; - static Integer v438; - static Integer v439; - static Integer v440; - static Integer v441; - static Integer v442; - static Integer v443; - static Integer v444; - static Integer v445; - static Integer v446; - static Integer v447; - static Integer v448; - static Integer v449; - static Integer v450; - static Integer v451; - static Integer v452; - static Integer v453; - static Integer v454; - static Integer v455; - static Integer v456; - static Integer v457; - static Integer v458; - static Integer v459; - static Integer v460; - static Integer v461; - static Integer v462; - static Integer v463; - static Integer v464; - static Integer v465; - static Integer v466; - static Integer v467; - static Integer v468; - static Integer v469; - static Integer v470; - static Integer v471; - static Integer v472; - static Integer v473; - static Integer v474; - static Integer v475; - static Integer v476; - static Integer v477; - static Integer v478; - static Integer v479; - static Integer v480; - static Integer v481; - static Integer v482; - static Integer v483; - static Integer v484; - static Integer v485; - static Integer v486; - static Integer v487; - static Integer v488; - static Integer v489; - static Integer v490; - static Integer v491; - static Integer v492; - static Integer v493; - static Integer v494; - static Integer v495; - static Integer v496; - static Integer v497; - static Integer v498; - static Integer v499; - static Integer v500; - static Integer v501; - static Integer v502; - static Integer v503; - static Integer v504; - static Integer v505; - static Integer v506; - static Integer v507; - static Integer v508; - static Integer v509; - static Integer v510; - static Integer v511; - static Integer v512; - static Integer v513; - static Integer v514; - static Integer v515; - static Integer v516; - static Integer v517; - static Integer v518; - static Integer v519; - static Integer v520; - static Integer v521; - static Integer v522; - static Integer v523; - static Integer v524; - static Integer v525; - static Integer v526; - static Integer v527; - static Integer v528; - static Integer v529; - static Integer v530; - static Integer v531; - static Integer v532; - static Integer v533; - static Integer v534; - static Integer v535; - static Integer v536; - static Integer v537; - static Integer v538; - static Integer v539; - static Integer v540; - static Integer v541; - static Integer v542; - static Integer v543; - static Integer v544; - static Integer v545; - static Integer v546; - static Integer v547; - static Integer v548; - static Integer v549; - static Integer v550; - static Integer v551; - static Integer v552; - static Integer v553; - static Integer v554; - static Integer v555; - static Integer v556; - static Integer v557; - static Integer v558; - static Integer v559; - static Integer v560; - static Integer v561; - static Integer v562; - static Integer v563; - static Integer v564; - static Integer v565; - static Integer v566; - static Integer v567; - static Integer v568; - static Integer v569; - static Integer v570; - static Integer v571; - static Integer v572; - static Integer v573; - static Integer v574; - static Integer v575; - static Integer v576; - static Integer v577; - static Integer v578; - static Integer v579; - static Integer v580; - static Integer v581; - static Integer v582; - static Integer v583; - static Integer v584; - static Integer v585; - static Integer v586; - static Integer v587; - static Integer v588; - static Integer v589; - static Integer v590; - static Integer v591; - static Integer v592; - static Integer v593; - static Integer v594; - static Integer v595; - static Integer v596; - static Integer v597; - static Integer v598; - static Integer v599; - static Integer v600; - static Integer v601; - static Integer v602; - static Integer v603; - static Integer v604; - static Integer v605; - static Integer v606; - static Integer v607; - static Integer v608; - static Integer v609; - static Integer v610; - static Integer v611; - static Integer v612; - static Integer v613; - static Integer v614; - static Integer v615; - static Integer v616; - static Integer v617; - static Integer v618; - static Integer v619; - static Integer v620; - static Integer v621; - static Integer v622; - static Integer v623; - static Integer v624; - static Integer v625; - static Integer v626; - static Integer v627; - static Integer v628; - static Integer v629; - static Integer v630; - static Integer v631; - static Integer v632; - static Integer v633; - static Integer v634; - static Integer v635; - static Integer v636; - static Integer v637; - static Integer v638; - static Integer v639; - static Integer v640; - static Integer v641; - static Integer v642; - static Integer v643; - static Integer v644; - static Integer v645; - static Integer v646; - static Integer v647; - static Integer v648; - static Integer v649; - static Integer v650; - static Integer v651; - static Integer v652; - static Integer v653; - static Integer v654; - static Integer v655; - static Integer v656; - static Integer v657; - static Integer v658; - static Integer v659; - static Integer v660; - static Integer v661; - static Integer v662; - static Integer v663; - static Integer v664; - static Integer v665; - static Integer v666; - static Integer v667; - static Integer v668; - static Integer v669; - static Integer v670; - static Integer v671; - static Integer v672; - static Integer v673; - static Integer v674; - static Integer v675; - static Integer v676; - static Integer v677; - static Integer v678; - static Integer v679; - static Integer v680; - static Integer v681; - static Integer v682; - static Integer v683; - static Integer v684; - static Integer v685; - static Integer v686; - static Integer v687; - static Integer v688; - static Integer v689; - static Integer v690; - static Integer v691; - static Integer v692; - static Integer v693; - static Integer v694; - static Integer v695; - static Integer v696; - static Integer v697; - static Integer v698; - static Integer v699; - static Integer v700; - static Integer v701; - static Integer v702; - static Integer v703; - static Integer v704; - static Integer v705; - static Integer v706; - static Integer v707; - static Integer v708; - static Integer v709; - static Integer v710; - static Integer v711; - static Integer v712; - static Integer v713; - static Integer v714; - static Integer v715; - static Integer v716; - static Integer v717; - static Integer v718; - static Integer v719; - static Integer v720; - static Integer v721; - static Integer v722; - static Integer v723; - static Integer v724; - static Integer v725; - static Integer v726; - static Integer v727; - static Integer v728; - static Integer v729; - static Integer v730; - static Integer v731; - static Integer v732; - static Integer v733; - static Integer v734; - static Integer v735; - static Integer v736; - static Integer v737; - static Integer v738; - static Integer v739; - static Integer v740; - static Integer v741; - static Integer v742; - static Integer v743; - static Integer v744; - static Integer v745; - static Integer v746; - static Integer v747; - static Integer v748; - static Integer v749; - static Integer v750; - static Integer v751; - static Integer v752; - static Integer v753; - static Integer v754; - static Integer v755; - static Integer v756; - static Integer v757; - static Integer v758; - static Integer v759; - static Integer v760; - static Integer v761; - static Integer v762; - static Integer v763; - static Integer v764; - static Integer v765; - static Integer v766; - static Integer v767; - static Integer v768; - static Integer v769; - static Integer v770; - static Integer v771; - static Integer v772; - static Integer v773; - static Integer v774; - static Integer v775; - static Integer v776; - static Integer v777; - static Integer v778; - static Integer v779; - static Integer v780; - static Integer v781; - static Integer v782; - static Integer v783; - static Integer v784; - static Integer v785; - static Integer v786; - static Integer v787; - static Integer v788; - static Integer v789; - static Integer v790; - static Integer v791; - static Integer v792; - static Integer v793; - static Integer v794; - static Integer v795; - static Integer v796; - static Integer v797; - static Integer v798; - static Integer v799; - static Integer v800; - static Integer v801; - static Integer v802; - static Integer v803; - static Integer v804; - static Integer v805; - static Integer v806; - static Integer v807; - static Integer v808; - static Integer v809; - static Integer v810; - static Integer v811; - static Integer v812; - static Integer v813; - static Integer v814; - static Integer v815; - static Integer v816; - static Integer v817; - static Integer v818; - static Integer v819; - static Integer v820; - static Integer v821; - static Integer v822; - static Integer v823; - static Integer v824; - static Integer v825; - static Integer v826; - static Integer v827; - static Integer v828; - static Integer v829; - static Integer v830; - static Integer v831; - static Integer v832; - static Integer v833; - static Integer v834; - static Integer v835; - static Integer v836; - static Integer v837; - static Integer v838; - static Integer v839; - static Integer v840; - static Integer v841; - static Integer v842; - static Integer v843; - static Integer v844; - static Integer v845; - static Integer v846; - static Integer v847; - static Integer v848; - static Integer v849; - static Integer v850; - static Integer v851; - static Integer v852; - static Integer v853; - static Integer v854; - static Integer v855; - static Integer v856; - static Integer v857; - static Integer v858; - static Integer v859; - static Integer v860; - static Integer v861; - static Integer v862; - static Integer v863; - static Integer v864; - static Integer v865; - static Integer v866; - static Integer v867; - static Integer v868; - static Integer v869; - static Integer v870; - static Integer v871; - static Integer v872; - static Integer v873; - static Integer v874; - static Integer v875; - static Integer v876; - static Integer v877; - static Integer v878; - static Integer v879; - static Integer v880; - static Integer v881; - static Integer v882; - static Integer v883; - static Integer v884; - static Integer v885; - static Integer v886; - static Integer v887; - static Integer v888; - static Integer v889; - static Integer v890; - static Integer v891; - static Integer v892; - static Integer v893; - static Integer v894; - static Integer v895; - static Integer v896; - static Integer v897; - static Integer v898; - static Integer v899; - static Integer v900; - static Integer v901; - static Integer v902; - static Integer v903; - static Integer v904; - static Integer v905; - static Integer v906; - static Integer v907; - static Integer v908; - static Integer v909; - static Integer v910; - static Integer v911; - static Integer v912; - static Integer v913; - static Integer v914; - static Integer v915; - static Integer v916; - static Integer v917; - static Integer v918; - static Integer v919; - static Integer v920; - static Integer v921; - static Integer v922; - static Integer v923; - static Integer v924; - static Integer v925; - static Integer v926; - static Integer v927; - static Integer v928; - static Integer v929; - static Integer v930; - static Integer v931; - static Integer v932; - static Integer v933; - static Integer v934; - static Integer v935; - static Integer v936; - static Integer v937; - static Integer v938; - static Integer v939; - static Integer v940; - static Integer v941; - static Integer v942; - static Integer v943; - static Integer v944; - static Integer v945; - static Integer v946; - static Integer v947; - static Integer v948; - static Integer v949; - static Integer v950; - static Integer v951; - static Integer v952; - static Integer v953; - static Integer v954; - static Integer v955; - static Integer v956; - static Integer v957; - static Integer v958; - static Integer v959; - static Integer v960; - static Integer v961; - static Integer v962; - static Integer v963; - static Integer v964; - static Integer v965; - static Integer v966; - static Integer v967; - static Integer v968; - static Integer v969; - static Integer v970; - static Integer v971; - static Integer v972; - static Integer v973; - static Integer v974; - static Integer v975; - static Integer v976; - static Integer v977; - static Integer v978; - static Integer v979; - static Integer v980; - static Integer v981; - static Integer v982; - static Integer v983; - static Integer v984; - static Integer v985; - static Integer v986; - static Integer v987; - static Integer v988; - static Integer v989; - static Integer v990; - static Integer v991; - static Integer v992; - static Integer v993; - static Integer v994; - static Integer v995; - static Integer v996; - static Integer v997; - static Integer v998; - static Integer v999; + // Do not initialize these variables. The Nullness Checker is supposed to issue an error about + // uninitialized fields. + static Integer v0; + static Integer v1; + static Integer v2; + static Integer v3; + static Integer v4; + static Integer v5; + static Integer v6; + static Integer v7; + static Integer v8; + static Integer v9; + static Integer v10; + static Integer v11; + static Integer v12; + static Integer v13; + static Integer v14; + static Integer v15; + static Integer v16; + static Integer v17; + static Integer v18; + static Integer v19; + static Integer v20; + static Integer v21; + static Integer v22; + static Integer v23; + static Integer v24; + static Integer v25; + static Integer v26; + static Integer v27; + static Integer v28; + static Integer v29; + static Integer v30; + static Integer v31; + static Integer v32; + static Integer v33; + static Integer v34; + static Integer v35; + static Integer v36; + static Integer v37; + static Integer v38; + static Integer v39; + static Integer v40; + static Integer v41; + static Integer v42; + static Integer v43; + static Integer v44; + static Integer v45; + static Integer v46; + static Integer v47; + static Integer v48; + static Integer v49; + static Integer v50; + static Integer v51; + static Integer v52; + static Integer v53; + static Integer v54; + static Integer v55; + static Integer v56; + static Integer v57; + static Integer v58; + static Integer v59; + static Integer v60; + static Integer v61; + static Integer v62; + static Integer v63; + static Integer v64; + static Integer v65; + static Integer v66; + static Integer v67; + static Integer v68; + static Integer v69; + static Integer v70; + static Integer v71; + static Integer v72; + static Integer v73; + static Integer v74; + static Integer v75; + static Integer v76; + static Integer v77; + static Integer v78; + static Integer v79; + static Integer v80; + static Integer v81; + static Integer v82; + static Integer v83; + static Integer v84; + static Integer v85; + static Integer v86; + static Integer v87; + static Integer v88; + static Integer v89; + static Integer v90; + static Integer v91; + static Integer v92; + static Integer v93; + static Integer v94; + static Integer v95; + static Integer v96; + static Integer v97; + static Integer v98; + static Integer v99; + static Integer v100; + static Integer v101; + static Integer v102; + static Integer v103; + static Integer v104; + static Integer v105; + static Integer v106; + static Integer v107; + static Integer v108; + static Integer v109; + static Integer v110; + static Integer v111; + static Integer v112; + static Integer v113; + static Integer v114; + static Integer v115; + static Integer v116; + static Integer v117; + static Integer v118; + static Integer v119; + static Integer v120; + static Integer v121; + static Integer v122; + static Integer v123; + static Integer v124; + static Integer v125; + static Integer v126; + static Integer v127; + static Integer v128; + static Integer v129; + static Integer v130; + static Integer v131; + static Integer v132; + static Integer v133; + static Integer v134; + static Integer v135; + static Integer v136; + static Integer v137; + static Integer v138; + static Integer v139; + static Integer v140; + static Integer v141; + static Integer v142; + static Integer v143; + static Integer v144; + static Integer v145; + static Integer v146; + static Integer v147; + static Integer v148; + static Integer v149; + static Integer v150; + static Integer v151; + static Integer v152; + static Integer v153; + static Integer v154; + static Integer v155; + static Integer v156; + static Integer v157; + static Integer v158; + static Integer v159; + static Integer v160; + static Integer v161; + static Integer v162; + static Integer v163; + static Integer v164; + static Integer v165; + static Integer v166; + static Integer v167; + static Integer v168; + static Integer v169; + static Integer v170; + static Integer v171; + static Integer v172; + static Integer v173; + static Integer v174; + static Integer v175; + static Integer v176; + static Integer v177; + static Integer v178; + static Integer v179; + static Integer v180; + static Integer v181; + static Integer v182; + static Integer v183; + static Integer v184; + static Integer v185; + static Integer v186; + static Integer v187; + static Integer v188; + static Integer v189; + static Integer v190; + static Integer v191; + static Integer v192; + static Integer v193; + static Integer v194; + static Integer v195; + static Integer v196; + static Integer v197; + static Integer v198; + static Integer v199; + static Integer v200; + static Integer v201; + static Integer v202; + static Integer v203; + static Integer v204; + static Integer v205; + static Integer v206; + static Integer v207; + static Integer v208; + static Integer v209; + static Integer v210; + static Integer v211; + static Integer v212; + static Integer v213; + static Integer v214; + static Integer v215; + static Integer v216; + static Integer v217; + static Integer v218; + static Integer v219; + static Integer v220; + static Integer v221; + static Integer v222; + static Integer v223; + static Integer v224; + static Integer v225; + static Integer v226; + static Integer v227; + static Integer v228; + static Integer v229; + static Integer v230; + static Integer v231; + static Integer v232; + static Integer v233; + static Integer v234; + static Integer v235; + static Integer v236; + static Integer v237; + static Integer v238; + static Integer v239; + static Integer v240; + static Integer v241; + static Integer v242; + static Integer v243; + static Integer v244; + static Integer v245; + static Integer v246; + static Integer v247; + static Integer v248; + static Integer v249; + static Integer v250; + static Integer v251; + static Integer v252; + static Integer v253; + static Integer v254; + static Integer v255; + static Integer v256; + static Integer v257; + static Integer v258; + static Integer v259; + static Integer v260; + static Integer v261; + static Integer v262; + static Integer v263; + static Integer v264; + static Integer v265; + static Integer v266; + static Integer v267; + static Integer v268; + static Integer v269; + static Integer v270; + static Integer v271; + static Integer v272; + static Integer v273; + static Integer v274; + static Integer v275; + static Integer v276; + static Integer v277; + static Integer v278; + static Integer v279; + static Integer v280; + static Integer v281; + static Integer v282; + static Integer v283; + static Integer v284; + static Integer v285; + static Integer v286; + static Integer v287; + static Integer v288; + static Integer v289; + static Integer v290; + static Integer v291; + static Integer v292; + static Integer v293; + static Integer v294; + static Integer v295; + static Integer v296; + static Integer v297; + static Integer v298; + static Integer v299; + static Integer v300; + static Integer v301; + static Integer v302; + static Integer v303; + static Integer v304; + static Integer v305; + static Integer v306; + static Integer v307; + static Integer v308; + static Integer v309; + static Integer v310; + static Integer v311; + static Integer v312; + static Integer v313; + static Integer v314; + static Integer v315; + static Integer v316; + static Integer v317; + static Integer v318; + static Integer v319; + static Integer v320; + static Integer v321; + static Integer v322; + static Integer v323; + static Integer v324; + static Integer v325; + static Integer v326; + static Integer v327; + static Integer v328; + static Integer v329; + static Integer v330; + static Integer v331; + static Integer v332; + static Integer v333; + static Integer v334; + static Integer v335; + static Integer v336; + static Integer v337; + static Integer v338; + static Integer v339; + static Integer v340; + static Integer v341; + static Integer v342; + static Integer v343; + static Integer v344; + static Integer v345; + static Integer v346; + static Integer v347; + static Integer v348; + static Integer v349; + static Integer v350; + static Integer v351; + static Integer v352; + static Integer v353; + static Integer v354; + static Integer v355; + static Integer v356; + static Integer v357; + static Integer v358; + static Integer v359; + static Integer v360; + static Integer v361; + static Integer v362; + static Integer v363; + static Integer v364; + static Integer v365; + static Integer v366; + static Integer v367; + static Integer v368; + static Integer v369; + static Integer v370; + static Integer v371; + static Integer v372; + static Integer v373; + static Integer v374; + static Integer v375; + static Integer v376; + static Integer v377; + static Integer v378; + static Integer v379; + static Integer v380; + static Integer v381; + static Integer v382; + static Integer v383; + static Integer v384; + static Integer v385; + static Integer v386; + static Integer v387; + static Integer v388; + static Integer v389; + static Integer v390; + static Integer v391; + static Integer v392; + static Integer v393; + static Integer v394; + static Integer v395; + static Integer v396; + static Integer v397; + static Integer v398; + static Integer v399; + static Integer v400; + static Integer v401; + static Integer v402; + static Integer v403; + static Integer v404; + static Integer v405; + static Integer v406; + static Integer v407; + static Integer v408; + static Integer v409; + static Integer v410; + static Integer v411; + static Integer v412; + static Integer v413; + static Integer v414; + static Integer v415; + static Integer v416; + static Integer v417; + static Integer v418; + static Integer v419; + static Integer v420; + static Integer v421; + static Integer v422; + static Integer v423; + static Integer v424; + static Integer v425; + static Integer v426; + static Integer v427; + static Integer v428; + static Integer v429; + static Integer v430; + static Integer v431; + static Integer v432; + static Integer v433; + static Integer v434; + static Integer v435; + static Integer v436; + static Integer v437; + static Integer v438; + static Integer v439; + static Integer v440; + static Integer v441; + static Integer v442; + static Integer v443; + static Integer v444; + static Integer v445; + static Integer v446; + static Integer v447; + static Integer v448; + static Integer v449; + static Integer v450; + static Integer v451; + static Integer v452; + static Integer v453; + static Integer v454; + static Integer v455; + static Integer v456; + static Integer v457; + static Integer v458; + static Integer v459; + static Integer v460; + static Integer v461; + static Integer v462; + static Integer v463; + static Integer v464; + static Integer v465; + static Integer v466; + static Integer v467; + static Integer v468; + static Integer v469; + static Integer v470; + static Integer v471; + static Integer v472; + static Integer v473; + static Integer v474; + static Integer v475; + static Integer v476; + static Integer v477; + static Integer v478; + static Integer v479; + static Integer v480; + static Integer v481; + static Integer v482; + static Integer v483; + static Integer v484; + static Integer v485; + static Integer v486; + static Integer v487; + static Integer v488; + static Integer v489; + static Integer v490; + static Integer v491; + static Integer v492; + static Integer v493; + static Integer v494; + static Integer v495; + static Integer v496; + static Integer v497; + static Integer v498; + static Integer v499; + static Integer v500; + static Integer v501; + static Integer v502; + static Integer v503; + static Integer v504; + static Integer v505; + static Integer v506; + static Integer v507; + static Integer v508; + static Integer v509; + static Integer v510; + static Integer v511; + static Integer v512; + static Integer v513; + static Integer v514; + static Integer v515; + static Integer v516; + static Integer v517; + static Integer v518; + static Integer v519; + static Integer v520; + static Integer v521; + static Integer v522; + static Integer v523; + static Integer v524; + static Integer v525; + static Integer v526; + static Integer v527; + static Integer v528; + static Integer v529; + static Integer v530; + static Integer v531; + static Integer v532; + static Integer v533; + static Integer v534; + static Integer v535; + static Integer v536; + static Integer v537; + static Integer v538; + static Integer v539; + static Integer v540; + static Integer v541; + static Integer v542; + static Integer v543; + static Integer v544; + static Integer v545; + static Integer v546; + static Integer v547; + static Integer v548; + static Integer v549; + static Integer v550; + static Integer v551; + static Integer v552; + static Integer v553; + static Integer v554; + static Integer v555; + static Integer v556; + static Integer v557; + static Integer v558; + static Integer v559; + static Integer v560; + static Integer v561; + static Integer v562; + static Integer v563; + static Integer v564; + static Integer v565; + static Integer v566; + static Integer v567; + static Integer v568; + static Integer v569; + static Integer v570; + static Integer v571; + static Integer v572; + static Integer v573; + static Integer v574; + static Integer v575; + static Integer v576; + static Integer v577; + static Integer v578; + static Integer v579; + static Integer v580; + static Integer v581; + static Integer v582; + static Integer v583; + static Integer v584; + static Integer v585; + static Integer v586; + static Integer v587; + static Integer v588; + static Integer v589; + static Integer v590; + static Integer v591; + static Integer v592; + static Integer v593; + static Integer v594; + static Integer v595; + static Integer v596; + static Integer v597; + static Integer v598; + static Integer v599; + static Integer v600; + static Integer v601; + static Integer v602; + static Integer v603; + static Integer v604; + static Integer v605; + static Integer v606; + static Integer v607; + static Integer v608; + static Integer v609; + static Integer v610; + static Integer v611; + static Integer v612; + static Integer v613; + static Integer v614; + static Integer v615; + static Integer v616; + static Integer v617; + static Integer v618; + static Integer v619; + static Integer v620; + static Integer v621; + static Integer v622; + static Integer v623; + static Integer v624; + static Integer v625; + static Integer v626; + static Integer v627; + static Integer v628; + static Integer v629; + static Integer v630; + static Integer v631; + static Integer v632; + static Integer v633; + static Integer v634; + static Integer v635; + static Integer v636; + static Integer v637; + static Integer v638; + static Integer v639; + static Integer v640; + static Integer v641; + static Integer v642; + static Integer v643; + static Integer v644; + static Integer v645; + static Integer v646; + static Integer v647; + static Integer v648; + static Integer v649; + static Integer v650; + static Integer v651; + static Integer v652; + static Integer v653; + static Integer v654; + static Integer v655; + static Integer v656; + static Integer v657; + static Integer v658; + static Integer v659; + static Integer v660; + static Integer v661; + static Integer v662; + static Integer v663; + static Integer v664; + static Integer v665; + static Integer v666; + static Integer v667; + static Integer v668; + static Integer v669; + static Integer v670; + static Integer v671; + static Integer v672; + static Integer v673; + static Integer v674; + static Integer v675; + static Integer v676; + static Integer v677; + static Integer v678; + static Integer v679; + static Integer v680; + static Integer v681; + static Integer v682; + static Integer v683; + static Integer v684; + static Integer v685; + static Integer v686; + static Integer v687; + static Integer v688; + static Integer v689; + static Integer v690; + static Integer v691; + static Integer v692; + static Integer v693; + static Integer v694; + static Integer v695; + static Integer v696; + static Integer v697; + static Integer v698; + static Integer v699; + static Integer v700; + static Integer v701; + static Integer v702; + static Integer v703; + static Integer v704; + static Integer v705; + static Integer v706; + static Integer v707; + static Integer v708; + static Integer v709; + static Integer v710; + static Integer v711; + static Integer v712; + static Integer v713; + static Integer v714; + static Integer v715; + static Integer v716; + static Integer v717; + static Integer v718; + static Integer v719; + static Integer v720; + static Integer v721; + static Integer v722; + static Integer v723; + static Integer v724; + static Integer v725; + static Integer v726; + static Integer v727; + static Integer v728; + static Integer v729; + static Integer v730; + static Integer v731; + static Integer v732; + static Integer v733; + static Integer v734; + static Integer v735; + static Integer v736; + static Integer v737; + static Integer v738; + static Integer v739; + static Integer v740; + static Integer v741; + static Integer v742; + static Integer v743; + static Integer v744; + static Integer v745; + static Integer v746; + static Integer v747; + static Integer v748; + static Integer v749; + static Integer v750; + static Integer v751; + static Integer v752; + static Integer v753; + static Integer v754; + static Integer v755; + static Integer v756; + static Integer v757; + static Integer v758; + static Integer v759; + static Integer v760; + static Integer v761; + static Integer v762; + static Integer v763; + static Integer v764; + static Integer v765; + static Integer v766; + static Integer v767; + static Integer v768; + static Integer v769; + static Integer v770; + static Integer v771; + static Integer v772; + static Integer v773; + static Integer v774; + static Integer v775; + static Integer v776; + static Integer v777; + static Integer v778; + static Integer v779; + static Integer v780; + static Integer v781; + static Integer v782; + static Integer v783; + static Integer v784; + static Integer v785; + static Integer v786; + static Integer v787; + static Integer v788; + static Integer v789; + static Integer v790; + static Integer v791; + static Integer v792; + static Integer v793; + static Integer v794; + static Integer v795; + static Integer v796; + static Integer v797; + static Integer v798; + static Integer v799; + static Integer v800; + static Integer v801; + static Integer v802; + static Integer v803; + static Integer v804; + static Integer v805; + static Integer v806; + static Integer v807; + static Integer v808; + static Integer v809; + static Integer v810; + static Integer v811; + static Integer v812; + static Integer v813; + static Integer v814; + static Integer v815; + static Integer v816; + static Integer v817; + static Integer v818; + static Integer v819; + static Integer v820; + static Integer v821; + static Integer v822; + static Integer v823; + static Integer v824; + static Integer v825; + static Integer v826; + static Integer v827; + static Integer v828; + static Integer v829; + static Integer v830; + static Integer v831; + static Integer v832; + static Integer v833; + static Integer v834; + static Integer v835; + static Integer v836; + static Integer v837; + static Integer v838; + static Integer v839; + static Integer v840; + static Integer v841; + static Integer v842; + static Integer v843; + static Integer v844; + static Integer v845; + static Integer v846; + static Integer v847; + static Integer v848; + static Integer v849; + static Integer v850; + static Integer v851; + static Integer v852; + static Integer v853; + static Integer v854; + static Integer v855; + static Integer v856; + static Integer v857; + static Integer v858; + static Integer v859; + static Integer v860; + static Integer v861; + static Integer v862; + static Integer v863; + static Integer v864; + static Integer v865; + static Integer v866; + static Integer v867; + static Integer v868; + static Integer v869; + static Integer v870; + static Integer v871; + static Integer v872; + static Integer v873; + static Integer v874; + static Integer v875; + static Integer v876; + static Integer v877; + static Integer v878; + static Integer v879; + static Integer v880; + static Integer v881; + static Integer v882; + static Integer v883; + static Integer v884; + static Integer v885; + static Integer v886; + static Integer v887; + static Integer v888; + static Integer v889; + static Integer v890; + static Integer v891; + static Integer v892; + static Integer v893; + static Integer v894; + static Integer v895; + static Integer v896; + static Integer v897; + static Integer v898; + static Integer v899; + static Integer v900; + static Integer v901; + static Integer v902; + static Integer v903; + static Integer v904; + static Integer v905; + static Integer v906; + static Integer v907; + static Integer v908; + static Integer v909; + static Integer v910; + static Integer v911; + static Integer v912; + static Integer v913; + static Integer v914; + static Integer v915; + static Integer v916; + static Integer v917; + static Integer v918; + static Integer v919; + static Integer v920; + static Integer v921; + static Integer v922; + static Integer v923; + static Integer v924; + static Integer v925; + static Integer v926; + static Integer v927; + static Integer v928; + static Integer v929; + static Integer v930; + static Integer v931; + static Integer v932; + static Integer v933; + static Integer v934; + static Integer v935; + static Integer v936; + static Integer v937; + static Integer v938; + static Integer v939; + static Integer v940; + static Integer v941; + static Integer v942; + static Integer v943; + static Integer v944; + static Integer v945; + static Integer v946; + static Integer v947; + static Integer v948; + static Integer v949; + static Integer v950; + static Integer v951; + static Integer v952; + static Integer v953; + static Integer v954; + static Integer v955; + static Integer v956; + static Integer v957; + static Integer v958; + static Integer v959; + static Integer v960; + static Integer v961; + static Integer v962; + static Integer v963; + static Integer v964; + static Integer v965; + static Integer v966; + static Integer v967; + static Integer v968; + static Integer v969; + static Integer v970; + static Integer v971; + static Integer v972; + static Integer v973; + static Integer v974; + static Integer v975; + static Integer v976; + static Integer v977; + static Integer v978; + static Integer v979; + static Integer v980; + static Integer v981; + static Integer v982; + static Integer v983; + static Integer v984; + static Integer v985; + static Integer v986; + static Integer v987; + static Integer v988; + static Integer v989; + static Integer v990; + static Integer v991; + static Integer v992; + static Integer v993; + static Integer v994; + static Integer v995; + static Integer v996; + static Integer v997; + static Integer v998; + static Integer v999; - static Map f() { - Map map = new HashMap<>(); - map.put("k0", v0); - map.put("k1", v1); - map.put("k2", v2); - map.put("k3", v3); - map.put("k4", v4); - map.put("k5", v5); - map.put("k6", v6); - map.put("k7", v7); - map.put("k8", v8); - map.put("k9", v9); - map.put("k10", v10); - map.put("k11", v11); - map.put("k12", v12); - map.put("k13", v13); - map.put("k14", v14); - map.put("k15", v15); - map.put("k16", v16); - map.put("k17", v17); - map.put("k18", v18); - map.put("k19", v19); - map.put("k20", v20); - map.put("k21", v21); - map.put("k22", v22); - map.put("k23", v23); - map.put("k24", v24); - map.put("k25", v25); - map.put("k26", v26); - map.put("k27", v27); - map.put("k28", v28); - map.put("k29", v29); - map.put("k30", v30); - map.put("k31", v31); - map.put("k32", v32); - map.put("k33", v33); - map.put("k34", v34); - map.put("k35", v35); - map.put("k36", v36); - map.put("k37", v37); - map.put("k38", v38); - map.put("k39", v39); - map.put("k40", v40); - map.put("k41", v41); - map.put("k42", v42); - map.put("k43", v43); - map.put("k44", v44); - map.put("k45", v45); - map.put("k46", v46); - map.put("k47", v47); - map.put("k48", v48); - map.put("k49", v49); - map.put("k50", v50); - map.put("k51", v51); - map.put("k52", v52); - map.put("k53", v53); - map.put("k54", v54); - map.put("k55", v55); - map.put("k56", v56); - map.put("k57", v57); - map.put("k58", v58); - map.put("k59", v59); - map.put("k60", v60); - map.put("k61", v61); - map.put("k62", v62); - map.put("k63", v63); - map.put("k64", v64); - map.put("k65", v65); - map.put("k66", v66); - map.put("k67", v67); - map.put("k68", v68); - map.put("k69", v69); - map.put("k70", v70); - map.put("k71", v71); - map.put("k72", v72); - map.put("k73", v73); - map.put("k74", v74); - map.put("k75", v75); - map.put("k76", v76); - map.put("k77", v77); - map.put("k78", v78); - map.put("k79", v79); - map.put("k80", v80); - map.put("k81", v81); - map.put("k82", v82); - map.put("k83", v83); - map.put("k84", v84); - map.put("k85", v85); - map.put("k86", v86); - map.put("k87", v87); - map.put("k88", v88); - map.put("k89", v89); - map.put("k90", v90); - map.put("k91", v91); - map.put("k92", v92); - map.put("k93", v93); - map.put("k94", v94); - map.put("k95", v95); - map.put("k96", v96); - map.put("k97", v97); - map.put("k98", v98); - map.put("k99", v99); - map.put("k100", v100); - map.put("k101", v101); - map.put("k102", v102); - map.put("k103", v103); - map.put("k104", v104); - map.put("k105", v105); - map.put("k106", v106); - map.put("k107", v107); - map.put("k108", v108); - map.put("k109", v109); - map.put("k110", v110); - map.put("k111", v111); - map.put("k112", v112); - map.put("k113", v113); - map.put("k114", v114); - map.put("k115", v115); - map.put("k116", v116); - map.put("k117", v117); - map.put("k118", v118); - map.put("k119", v119); - map.put("k120", v120); - map.put("k121", v121); - map.put("k122", v122); - map.put("k123", v123); - map.put("k124", v124); - map.put("k125", v125); - map.put("k126", v126); - map.put("k127", v127); - map.put("k128", v128); - map.put("k129", v129); - map.put("k130", v130); - map.put("k131", v131); - map.put("k132", v132); - map.put("k133", v133); - map.put("k134", v134); - map.put("k135", v135); - map.put("k136", v136); - map.put("k137", v137); - map.put("k138", v138); - map.put("k139", v139); - map.put("k140", v140); - map.put("k141", v141); - map.put("k142", v142); - map.put("k143", v143); - map.put("k144", v144); - map.put("k145", v145); - map.put("k146", v146); - map.put("k147", v147); - map.put("k148", v148); - map.put("k149", v149); - map.put("k150", v150); - map.put("k151", v151); - map.put("k152", v152); - map.put("k153", v153); - map.put("k154", v154); - map.put("k155", v155); - map.put("k156", v156); - map.put("k157", v157); - map.put("k158", v158); - map.put("k159", v159); - map.put("k160", v160); - map.put("k161", v161); - map.put("k162", v162); - map.put("k163", v163); - map.put("k164", v164); - map.put("k165", v165); - map.put("k166", v166); - map.put("k167", v167); - map.put("k168", v168); - map.put("k169", v169); - map.put("k170", v170); - map.put("k171", v171); - map.put("k172", v172); - map.put("k173", v173); - map.put("k174", v174); - map.put("k175", v175); - map.put("k176", v176); - map.put("k177", v177); - map.put("k178", v178); - map.put("k179", v179); - map.put("k180", v180); - map.put("k181", v181); - map.put("k182", v182); - map.put("k183", v183); - map.put("k184", v184); - map.put("k185", v185); - map.put("k186", v186); - map.put("k187", v187); - map.put("k188", v188); - map.put("k189", v189); - map.put("k190", v190); - map.put("k191", v191); - map.put("k192", v192); - map.put("k193", v193); - map.put("k194", v194); - map.put("k195", v195); - map.put("k196", v196); - map.put("k197", v197); - map.put("k198", v198); - map.put("k199", v199); - map.put("k200", v200); - map.put("k201", v201); - map.put("k202", v202); - map.put("k203", v203); - map.put("k204", v204); - map.put("k205", v205); - map.put("k206", v206); - map.put("k207", v207); - map.put("k208", v208); - map.put("k209", v209); - map.put("k210", v210); - map.put("k211", v211); - map.put("k212", v212); - map.put("k213", v213); - map.put("k214", v214); - map.put("k215", v215); - map.put("k216", v216); - map.put("k217", v217); - map.put("k218", v218); - map.put("k219", v219); - map.put("k220", v220); - map.put("k221", v221); - map.put("k222", v222); - map.put("k223", v223); - map.put("k224", v224); - map.put("k225", v225); - map.put("k226", v226); - map.put("k227", v227); - map.put("k228", v228); - map.put("k229", v229); - map.put("k230", v230); - map.put("k231", v231); - map.put("k232", v232); - map.put("k233", v233); - map.put("k234", v234); - map.put("k235", v235); - map.put("k236", v236); - map.put("k237", v237); - map.put("k238", v238); - map.put("k239", v239); - map.put("k240", v240); - map.put("k241", v241); - map.put("k242", v242); - map.put("k243", v243); - map.put("k244", v244); - map.put("k245", v245); - map.put("k246", v246); - map.put("k247", v247); - map.put("k248", v248); - map.put("k249", v249); - map.put("k250", v250); - map.put("k251", v251); - map.put("k252", v252); - map.put("k253", v253); - map.put("k254", v254); - map.put("k255", v255); - map.put("k256", v256); - map.put("k257", v257); - map.put("k258", v258); - map.put("k259", v259); - map.put("k260", v260); - map.put("k261", v261); - map.put("k262", v262); - map.put("k263", v263); - map.put("k264", v264); - map.put("k265", v265); - map.put("k266", v266); - map.put("k267", v267); - map.put("k268", v268); - map.put("k269", v269); - map.put("k270", v270); - map.put("k271", v271); - map.put("k272", v272); - map.put("k273", v273); - map.put("k274", v274); - map.put("k275", v275); - map.put("k276", v276); - map.put("k277", v277); - map.put("k278", v278); - map.put("k279", v279); - map.put("k280", v280); - map.put("k281", v281); - map.put("k282", v282); - map.put("k283", v283); - map.put("k284", v284); - map.put("k285", v285); - map.put("k286", v286); - map.put("k287", v287); - map.put("k288", v288); - map.put("k289", v289); - map.put("k290", v290); - map.put("k291", v291); - map.put("k292", v292); - map.put("k293", v293); - map.put("k294", v294); - map.put("k295", v295); - map.put("k296", v296); - map.put("k297", v297); - map.put("k298", v298); - map.put("k299", v299); - map.put("k300", v300); - map.put("k301", v301); - map.put("k302", v302); - map.put("k303", v303); - map.put("k304", v304); - map.put("k305", v305); - map.put("k306", v306); - map.put("k307", v307); - map.put("k308", v308); - map.put("k309", v309); - map.put("k310", v310); - map.put("k311", v311); - map.put("k312", v312); - map.put("k313", v313); - map.put("k314", v314); - map.put("k315", v315); - map.put("k316", v316); - map.put("k317", v317); - map.put("k318", v318); - map.put("k319", v319); - map.put("k320", v320); - map.put("k321", v321); - map.put("k322", v322); - map.put("k323", v323); - map.put("k324", v324); - map.put("k325", v325); - map.put("k326", v326); - map.put("k327", v327); - map.put("k328", v328); - map.put("k329", v329); - map.put("k330", v330); - map.put("k331", v331); - map.put("k332", v332); - map.put("k333", v333); - map.put("k334", v334); - map.put("k335", v335); - map.put("k336", v336); - map.put("k337", v337); - map.put("k338", v338); - map.put("k339", v339); - map.put("k340", v340); - map.put("k341", v341); - map.put("k342", v342); - map.put("k343", v343); - map.put("k344", v344); - map.put("k345", v345); - map.put("k346", v346); - map.put("k347", v347); - map.put("k348", v348); - map.put("k349", v349); - map.put("k350", v350); - map.put("k351", v351); - map.put("k352", v352); - map.put("k353", v353); - map.put("k354", v354); - map.put("k355", v355); - map.put("k356", v356); - map.put("k357", v357); - map.put("k358", v358); - map.put("k359", v359); - map.put("k360", v360); - map.put("k361", v361); - map.put("k362", v362); - map.put("k363", v363); - map.put("k364", v364); - map.put("k365", v365); - map.put("k366", v366); - map.put("k367", v367); - map.put("k368", v368); - map.put("k369", v369); - map.put("k370", v370); - map.put("k371", v371); - map.put("k372", v372); - map.put("k373", v373); - map.put("k374", v374); - map.put("k375", v375); - map.put("k376", v376); - map.put("k377", v377); - map.put("k378", v378); - map.put("k379", v379); - map.put("k380", v380); - map.put("k381", v381); - map.put("k382", v382); - map.put("k383", v383); - map.put("k384", v384); - map.put("k385", v385); - map.put("k386", v386); - map.put("k387", v387); - map.put("k388", v388); - map.put("k389", v389); - map.put("k390", v390); - map.put("k391", v391); - map.put("k392", v392); - map.put("k393", v393); - map.put("k394", v394); - map.put("k395", v395); - map.put("k396", v396); - map.put("k397", v397); - map.put("k398", v398); - map.put("k399", v399); - map.put("k400", v400); - map.put("k401", v401); - map.put("k402", v402); - map.put("k403", v403); - map.put("k404", v404); - map.put("k405", v405); - map.put("k406", v406); - map.put("k407", v407); - map.put("k408", v408); - map.put("k409", v409); - map.put("k410", v410); - map.put("k411", v411); - map.put("k412", v412); - map.put("k413", v413); - map.put("k414", v414); - map.put("k415", v415); - map.put("k416", v416); - map.put("k417", v417); - map.put("k418", v418); - map.put("k419", v419); - map.put("k420", v420); - map.put("k421", v421); - map.put("k422", v422); - map.put("k423", v423); - map.put("k424", v424); - map.put("k425", v425); - map.put("k426", v426); - map.put("k427", v427); - map.put("k428", v428); - map.put("k429", v429); - map.put("k430", v430); - map.put("k431", v431); - map.put("k432", v432); - map.put("k433", v433); - map.put("k434", v434); - map.put("k435", v435); - map.put("k436", v436); - map.put("k437", v437); - map.put("k438", v438); - map.put("k439", v439); - map.put("k440", v440); - map.put("k441", v441); - map.put("k442", v442); - map.put("k443", v443); - map.put("k444", v444); - map.put("k445", v445); - map.put("k446", v446); - map.put("k447", v447); - map.put("k448", v448); - map.put("k449", v449); - map.put("k450", v450); - map.put("k451", v451); - map.put("k452", v452); - map.put("k453", v453); - map.put("k454", v454); - map.put("k455", v455); - map.put("k456", v456); - map.put("k457", v457); - map.put("k458", v458); - map.put("k459", v459); - map.put("k460", v460); - map.put("k461", v461); - map.put("k462", v462); - map.put("k463", v463); - map.put("k464", v464); - map.put("k465", v465); - map.put("k466", v466); - map.put("k467", v467); - map.put("k468", v468); - map.put("k469", v469); - map.put("k470", v470); - map.put("k471", v471); - map.put("k472", v472); - map.put("k473", v473); - map.put("k474", v474); - map.put("k475", v475); - map.put("k476", v476); - map.put("k477", v477); - map.put("k478", v478); - map.put("k479", v479); - map.put("k480", v480); - map.put("k481", v481); - map.put("k482", v482); - map.put("k483", v483); - map.put("k484", v484); - map.put("k485", v485); - map.put("k486", v486); - map.put("k487", v487); - map.put("k488", v488); - map.put("k489", v489); - map.put("k490", v490); - map.put("k491", v491); - map.put("k492", v492); - map.put("k493", v493); - map.put("k494", v494); - map.put("k495", v495); - map.put("k496", v496); - map.put("k497", v497); - map.put("k498", v498); - map.put("k499", v499); - map.put("k500", v500); - map.put("k501", v501); - map.put("k502", v502); - map.put("k503", v503); - map.put("k504", v504); - map.put("k505", v505); - map.put("k506", v506); - map.put("k507", v507); - map.put("k508", v508); - map.put("k509", v509); - map.put("k510", v510); - map.put("k511", v511); - map.put("k512", v512); - map.put("k513", v513); - map.put("k514", v514); - map.put("k515", v515); - map.put("k516", v516); - map.put("k517", v517); - map.put("k518", v518); - map.put("k519", v519); - map.put("k520", v520); - map.put("k521", v521); - map.put("k522", v522); - map.put("k523", v523); - map.put("k524", v524); - map.put("k525", v525); - map.put("k526", v526); - map.put("k527", v527); - map.put("k528", v528); - map.put("k529", v529); - map.put("k530", v530); - map.put("k531", v531); - map.put("k532", v532); - map.put("k533", v533); - map.put("k534", v534); - map.put("k535", v535); - map.put("k536", v536); - map.put("k537", v537); - map.put("k538", v538); - map.put("k539", v539); - map.put("k540", v540); - map.put("k541", v541); - map.put("k542", v542); - map.put("k543", v543); - map.put("k544", v544); - map.put("k545", v545); - map.put("k546", v546); - map.put("k547", v547); - map.put("k548", v548); - map.put("k549", v549); - map.put("k550", v550); - map.put("k551", v551); - map.put("k552", v552); - map.put("k553", v553); - map.put("k554", v554); - map.put("k555", v555); - map.put("k556", v556); - map.put("k557", v557); - map.put("k558", v558); - map.put("k559", v559); - map.put("k560", v560); - map.put("k561", v561); - map.put("k562", v562); - map.put("k563", v563); - map.put("k564", v564); - map.put("k565", v565); - map.put("k566", v566); - map.put("k567", v567); - map.put("k568", v568); - map.put("k569", v569); - map.put("k570", v570); - map.put("k571", v571); - map.put("k572", v572); - map.put("k573", v573); - map.put("k574", v574); - map.put("k575", v575); - map.put("k576", v576); - map.put("k577", v577); - map.put("k578", v578); - map.put("k579", v579); - map.put("k580", v580); - map.put("k581", v581); - map.put("k582", v582); - map.put("k583", v583); - map.put("k584", v584); - map.put("k585", v585); - map.put("k586", v586); - map.put("k587", v587); - map.put("k588", v588); - map.put("k589", v589); - map.put("k590", v590); - map.put("k591", v591); - map.put("k592", v592); - map.put("k593", v593); - map.put("k594", v594); - map.put("k595", v595); - map.put("k596", v596); - map.put("k597", v597); - map.put("k598", v598); - map.put("k599", v599); - map.put("k600", v600); - map.put("k601", v601); - map.put("k602", v602); - map.put("k603", v603); - map.put("k604", v604); - map.put("k605", v605); - map.put("k606", v606); - map.put("k607", v607); - map.put("k608", v608); - map.put("k609", v609); - map.put("k610", v610); - map.put("k611", v611); - map.put("k612", v612); - map.put("k613", v613); - map.put("k614", v614); - map.put("k615", v615); - map.put("k616", v616); - map.put("k617", v617); - map.put("k618", v618); - map.put("k619", v619); - map.put("k620", v620); - map.put("k621", v621); - map.put("k622", v622); - map.put("k623", v623); - map.put("k624", v624); - map.put("k625", v625); - map.put("k626", v626); - map.put("k627", v627); - map.put("k628", v628); - map.put("k629", v629); - map.put("k630", v630); - map.put("k631", v631); - map.put("k632", v632); - map.put("k633", v633); - map.put("k634", v634); - map.put("k635", v635); - map.put("k636", v636); - map.put("k637", v637); - map.put("k638", v638); - map.put("k639", v639); - map.put("k640", v640); - map.put("k641", v641); - map.put("k642", v642); - map.put("k643", v643); - map.put("k644", v644); - map.put("k645", v645); - map.put("k646", v646); - map.put("k647", v647); - map.put("k648", v648); - map.put("k649", v649); - map.put("k650", v650); - map.put("k651", v651); - map.put("k652", v652); - map.put("k653", v653); - map.put("k654", v654); - map.put("k655", v655); - map.put("k656", v656); - map.put("k657", v657); - map.put("k658", v658); - map.put("k659", v659); - map.put("k660", v660); - map.put("k661", v661); - map.put("k662", v662); - map.put("k663", v663); - map.put("k664", v664); - map.put("k665", v665); - map.put("k666", v666); - map.put("k667", v667); - map.put("k668", v668); - map.put("k669", v669); - map.put("k670", v670); - map.put("k671", v671); - map.put("k672", v672); - map.put("k673", v673); - map.put("k674", v674); - map.put("k675", v675); - map.put("k676", v676); - map.put("k677", v677); - map.put("k678", v678); - map.put("k679", v679); - map.put("k680", v680); - map.put("k681", v681); - map.put("k682", v682); - map.put("k683", v683); - map.put("k684", v684); - map.put("k685", v685); - map.put("k686", v686); - map.put("k687", v687); - map.put("k688", v688); - map.put("k689", v689); - map.put("k690", v690); - map.put("k691", v691); - map.put("k692", v692); - map.put("k693", v693); - map.put("k694", v694); - map.put("k695", v695); - map.put("k696", v696); - map.put("k697", v697); - map.put("k698", v698); - map.put("k699", v699); - map.put("k700", v700); - map.put("k701", v701); - map.put("k702", v702); - map.put("k703", v703); - map.put("k704", v704); - map.put("k705", v705); - map.put("k706", v706); - map.put("k707", v707); - map.put("k708", v708); - map.put("k709", v709); - map.put("k710", v710); - map.put("k711", v711); - map.put("k712", v712); - map.put("k713", v713); - map.put("k714", v714); - map.put("k715", v715); - map.put("k716", v716); - map.put("k717", v717); - map.put("k718", v718); - map.put("k719", v719); - map.put("k720", v720); - map.put("k721", v721); - map.put("k722", v722); - map.put("k723", v723); - map.put("k724", v724); - map.put("k725", v725); - map.put("k726", v726); - map.put("k727", v727); - map.put("k728", v728); - map.put("k729", v729); - map.put("k730", v730); - map.put("k731", v731); - map.put("k732", v732); - map.put("k733", v733); - map.put("k734", v734); - map.put("k735", v735); - map.put("k736", v736); - map.put("k737", v737); - map.put("k738", v738); - map.put("k739", v739); - map.put("k740", v740); - map.put("k741", v741); - map.put("k742", v742); - map.put("k743", v743); - map.put("k744", v744); - map.put("k745", v745); - map.put("k746", v746); - map.put("k747", v747); - map.put("k748", v748); - map.put("k749", v749); - map.put("k750", v750); - map.put("k751", v751); - map.put("k752", v752); - map.put("k753", v753); - map.put("k754", v754); - map.put("k755", v755); - map.put("k756", v756); - map.put("k757", v757); - map.put("k758", v758); - map.put("k759", v759); - map.put("k760", v760); - map.put("k761", v761); - map.put("k762", v762); - map.put("k763", v763); - map.put("k764", v764); - map.put("k765", v765); - map.put("k766", v766); - map.put("k767", v767); - map.put("k768", v768); - map.put("k769", v769); - map.put("k770", v770); - map.put("k771", v771); - map.put("k772", v772); - map.put("k773", v773); - map.put("k774", v774); - map.put("k775", v775); - map.put("k776", v776); - map.put("k777", v777); - map.put("k778", v778); - map.put("k779", v779); - map.put("k780", v780); - map.put("k781", v781); - map.put("k782", v782); - map.put("k783", v783); - map.put("k784", v784); - map.put("k785", v785); - map.put("k786", v786); - map.put("k787", v787); - map.put("k788", v788); - map.put("k789", v789); - map.put("k790", v790); - map.put("k791", v791); - map.put("k792", v792); - map.put("k793", v793); - map.put("k794", v794); - map.put("k795", v795); - map.put("k796", v796); - map.put("k797", v797); - map.put("k798", v798); - map.put("k799", v799); - map.put("k800", v800); - map.put("k801", v801); - map.put("k802", v802); - map.put("k803", v803); - map.put("k804", v804); - map.put("k805", v805); - map.put("k806", v806); - map.put("k807", v807); - map.put("k808", v808); - map.put("k809", v809); - map.put("k810", v810); - map.put("k811", v811); - map.put("k812", v812); - map.put("k813", v813); - map.put("k814", v814); - map.put("k815", v815); - map.put("k816", v816); - map.put("k817", v817); - map.put("k818", v818); - map.put("k819", v819); - map.put("k820", v820); - map.put("k821", v821); - map.put("k822", v822); - map.put("k823", v823); - map.put("k824", v824); - map.put("k825", v825); - map.put("k826", v826); - map.put("k827", v827); - map.put("k828", v828); - map.put("k829", v829); - map.put("k830", v830); - map.put("k831", v831); - map.put("k832", v832); - map.put("k833", v833); - map.put("k834", v834); - map.put("k835", v835); - map.put("k836", v836); - map.put("k837", v837); - map.put("k838", v838); - map.put("k839", v839); - map.put("k840", v840); - map.put("k841", v841); - map.put("k842", v842); - map.put("k843", v843); - map.put("k844", v844); - map.put("k845", v845); - map.put("k846", v846); - map.put("k847", v847); - map.put("k848", v848); - map.put("k849", v849); - map.put("k850", v850); - map.put("k851", v851); - map.put("k852", v852); - map.put("k853", v853); - map.put("k854", v854); - map.put("k855", v855); - map.put("k856", v856); - map.put("k857", v857); - map.put("k858", v858); - map.put("k859", v859); - map.put("k860", v860); - map.put("k861", v861); - map.put("k862", v862); - map.put("k863", v863); - map.put("k864", v864); - map.put("k865", v865); - map.put("k866", v866); - map.put("k867", v867); - map.put("k868", v868); - map.put("k869", v869); - map.put("k870", v870); - map.put("k871", v871); - map.put("k872", v872); - map.put("k873", v873); - map.put("k874", v874); - map.put("k875", v875); - map.put("k876", v876); - map.put("k877", v877); - map.put("k878", v878); - map.put("k879", v879); - map.put("k880", v880); - map.put("k881", v881); - map.put("k882", v882); - map.put("k883", v883); - map.put("k884", v884); - map.put("k885", v885); - map.put("k886", v886); - map.put("k887", v887); - map.put("k888", v888); - map.put("k889", v889); - map.put("k890", v890); - map.put("k891", v891); - map.put("k892", v892); - map.put("k893", v893); - map.put("k894", v894); - map.put("k895", v895); - map.put("k896", v896); - map.put("k897", v897); - map.put("k898", v898); - map.put("k899", v899); - map.put("k900", v900); - map.put("k901", v901); - map.put("k902", v902); - map.put("k903", v903); - map.put("k904", v904); - map.put("k905", v905); - map.put("k906", v906); - map.put("k907", v907); - map.put("k908", v908); - map.put("k909", v909); - map.put("k910", v910); - map.put("k911", v911); - map.put("k912", v912); - map.put("k913", v913); - map.put("k914", v914); - map.put("k915", v915); - map.put("k916", v916); - map.put("k917", v917); - map.put("k918", v918); - map.put("k919", v919); - map.put("k920", v920); - map.put("k921", v921); - map.put("k922", v922); - map.put("k923", v923); - map.put("k924", v924); - map.put("k925", v925); - map.put("k926", v926); - map.put("k927", v927); - map.put("k928", v928); - map.put("k929", v929); - map.put("k930", v930); - map.put("k931", v931); - map.put("k932", v932); - map.put("k933", v933); - map.put("k934", v934); - map.put("k935", v935); - map.put("k936", v936); - map.put("k937", v937); - map.put("k938", v938); - map.put("k939", v939); - map.put("k940", v940); - map.put("k941", v941); - map.put("k942", v942); - map.put("k943", v943); - map.put("k944", v944); - map.put("k945", v945); - map.put("k946", v946); - map.put("k947", v947); - map.put("k948", v948); - map.put("k949", v949); - map.put("k950", v950); - map.put("k951", v951); - map.put("k952", v952); - map.put("k953", v953); - map.put("k954", v954); - map.put("k955", v955); - map.put("k956", v956); - map.put("k957", v957); - map.put("k958", v958); - map.put("k959", v959); - map.put("k960", v960); - map.put("k961", v961); - map.put("k962", v962); - map.put("k963", v963); - map.put("k964", v964); - map.put("k965", v965); - map.put("k966", v966); - map.put("k967", v967); - map.put("k968", v968); - map.put("k969", v969); - map.put("k970", v970); - map.put("k971", v971); - map.put("k972", v972); - map.put("k973", v973); - map.put("k974", v974); - map.put("k975", v975); - map.put("k976", v976); - map.put("k977", v977); - map.put("k978", v978); - map.put("k979", v979); - map.put("k980", v980); - map.put("k981", v981); - map.put("k982", v982); - map.put("k983", v983); - map.put("k984", v984); - map.put("k985", v985); - map.put("k986", v986); - map.put("k987", v987); - map.put("k988", v988); - map.put("k989", v989); - map.put("k990", v990); - map.put("k991", v991); - map.put("k992", v992); - map.put("k993", v993); - map.put("k994", v994); - map.put("k995", v995); - map.put("k996", v996); - map.put("k997", v997); - map.put("k998", v998); - map.put("k999", v999); - return map; - } + static Map f() { + Map map = new HashMap<>(); + map.put("k0", v0); + map.put("k1", v1); + map.put("k2", v2); + map.put("k3", v3); + map.put("k4", v4); + map.put("k5", v5); + map.put("k6", v6); + map.put("k7", v7); + map.put("k8", v8); + map.put("k9", v9); + map.put("k10", v10); + map.put("k11", v11); + map.put("k12", v12); + map.put("k13", v13); + map.put("k14", v14); + map.put("k15", v15); + map.put("k16", v16); + map.put("k17", v17); + map.put("k18", v18); + map.put("k19", v19); + map.put("k20", v20); + map.put("k21", v21); + map.put("k22", v22); + map.put("k23", v23); + map.put("k24", v24); + map.put("k25", v25); + map.put("k26", v26); + map.put("k27", v27); + map.put("k28", v28); + map.put("k29", v29); + map.put("k30", v30); + map.put("k31", v31); + map.put("k32", v32); + map.put("k33", v33); + map.put("k34", v34); + map.put("k35", v35); + map.put("k36", v36); + map.put("k37", v37); + map.put("k38", v38); + map.put("k39", v39); + map.put("k40", v40); + map.put("k41", v41); + map.put("k42", v42); + map.put("k43", v43); + map.put("k44", v44); + map.put("k45", v45); + map.put("k46", v46); + map.put("k47", v47); + map.put("k48", v48); + map.put("k49", v49); + map.put("k50", v50); + map.put("k51", v51); + map.put("k52", v52); + map.put("k53", v53); + map.put("k54", v54); + map.put("k55", v55); + map.put("k56", v56); + map.put("k57", v57); + map.put("k58", v58); + map.put("k59", v59); + map.put("k60", v60); + map.put("k61", v61); + map.put("k62", v62); + map.put("k63", v63); + map.put("k64", v64); + map.put("k65", v65); + map.put("k66", v66); + map.put("k67", v67); + map.put("k68", v68); + map.put("k69", v69); + map.put("k70", v70); + map.put("k71", v71); + map.put("k72", v72); + map.put("k73", v73); + map.put("k74", v74); + map.put("k75", v75); + map.put("k76", v76); + map.put("k77", v77); + map.put("k78", v78); + map.put("k79", v79); + map.put("k80", v80); + map.put("k81", v81); + map.put("k82", v82); + map.put("k83", v83); + map.put("k84", v84); + map.put("k85", v85); + map.put("k86", v86); + map.put("k87", v87); + map.put("k88", v88); + map.put("k89", v89); + map.put("k90", v90); + map.put("k91", v91); + map.put("k92", v92); + map.put("k93", v93); + map.put("k94", v94); + map.put("k95", v95); + map.put("k96", v96); + map.put("k97", v97); + map.put("k98", v98); + map.put("k99", v99); + map.put("k100", v100); + map.put("k101", v101); + map.put("k102", v102); + map.put("k103", v103); + map.put("k104", v104); + map.put("k105", v105); + map.put("k106", v106); + map.put("k107", v107); + map.put("k108", v108); + map.put("k109", v109); + map.put("k110", v110); + map.put("k111", v111); + map.put("k112", v112); + map.put("k113", v113); + map.put("k114", v114); + map.put("k115", v115); + map.put("k116", v116); + map.put("k117", v117); + map.put("k118", v118); + map.put("k119", v119); + map.put("k120", v120); + map.put("k121", v121); + map.put("k122", v122); + map.put("k123", v123); + map.put("k124", v124); + map.put("k125", v125); + map.put("k126", v126); + map.put("k127", v127); + map.put("k128", v128); + map.put("k129", v129); + map.put("k130", v130); + map.put("k131", v131); + map.put("k132", v132); + map.put("k133", v133); + map.put("k134", v134); + map.put("k135", v135); + map.put("k136", v136); + map.put("k137", v137); + map.put("k138", v138); + map.put("k139", v139); + map.put("k140", v140); + map.put("k141", v141); + map.put("k142", v142); + map.put("k143", v143); + map.put("k144", v144); + map.put("k145", v145); + map.put("k146", v146); + map.put("k147", v147); + map.put("k148", v148); + map.put("k149", v149); + map.put("k150", v150); + map.put("k151", v151); + map.put("k152", v152); + map.put("k153", v153); + map.put("k154", v154); + map.put("k155", v155); + map.put("k156", v156); + map.put("k157", v157); + map.put("k158", v158); + map.put("k159", v159); + map.put("k160", v160); + map.put("k161", v161); + map.put("k162", v162); + map.put("k163", v163); + map.put("k164", v164); + map.put("k165", v165); + map.put("k166", v166); + map.put("k167", v167); + map.put("k168", v168); + map.put("k169", v169); + map.put("k170", v170); + map.put("k171", v171); + map.put("k172", v172); + map.put("k173", v173); + map.put("k174", v174); + map.put("k175", v175); + map.put("k176", v176); + map.put("k177", v177); + map.put("k178", v178); + map.put("k179", v179); + map.put("k180", v180); + map.put("k181", v181); + map.put("k182", v182); + map.put("k183", v183); + map.put("k184", v184); + map.put("k185", v185); + map.put("k186", v186); + map.put("k187", v187); + map.put("k188", v188); + map.put("k189", v189); + map.put("k190", v190); + map.put("k191", v191); + map.put("k192", v192); + map.put("k193", v193); + map.put("k194", v194); + map.put("k195", v195); + map.put("k196", v196); + map.put("k197", v197); + map.put("k198", v198); + map.put("k199", v199); + map.put("k200", v200); + map.put("k201", v201); + map.put("k202", v202); + map.put("k203", v203); + map.put("k204", v204); + map.put("k205", v205); + map.put("k206", v206); + map.put("k207", v207); + map.put("k208", v208); + map.put("k209", v209); + map.put("k210", v210); + map.put("k211", v211); + map.put("k212", v212); + map.put("k213", v213); + map.put("k214", v214); + map.put("k215", v215); + map.put("k216", v216); + map.put("k217", v217); + map.put("k218", v218); + map.put("k219", v219); + map.put("k220", v220); + map.put("k221", v221); + map.put("k222", v222); + map.put("k223", v223); + map.put("k224", v224); + map.put("k225", v225); + map.put("k226", v226); + map.put("k227", v227); + map.put("k228", v228); + map.put("k229", v229); + map.put("k230", v230); + map.put("k231", v231); + map.put("k232", v232); + map.put("k233", v233); + map.put("k234", v234); + map.put("k235", v235); + map.put("k236", v236); + map.put("k237", v237); + map.put("k238", v238); + map.put("k239", v239); + map.put("k240", v240); + map.put("k241", v241); + map.put("k242", v242); + map.put("k243", v243); + map.put("k244", v244); + map.put("k245", v245); + map.put("k246", v246); + map.put("k247", v247); + map.put("k248", v248); + map.put("k249", v249); + map.put("k250", v250); + map.put("k251", v251); + map.put("k252", v252); + map.put("k253", v253); + map.put("k254", v254); + map.put("k255", v255); + map.put("k256", v256); + map.put("k257", v257); + map.put("k258", v258); + map.put("k259", v259); + map.put("k260", v260); + map.put("k261", v261); + map.put("k262", v262); + map.put("k263", v263); + map.put("k264", v264); + map.put("k265", v265); + map.put("k266", v266); + map.put("k267", v267); + map.put("k268", v268); + map.put("k269", v269); + map.put("k270", v270); + map.put("k271", v271); + map.put("k272", v272); + map.put("k273", v273); + map.put("k274", v274); + map.put("k275", v275); + map.put("k276", v276); + map.put("k277", v277); + map.put("k278", v278); + map.put("k279", v279); + map.put("k280", v280); + map.put("k281", v281); + map.put("k282", v282); + map.put("k283", v283); + map.put("k284", v284); + map.put("k285", v285); + map.put("k286", v286); + map.put("k287", v287); + map.put("k288", v288); + map.put("k289", v289); + map.put("k290", v290); + map.put("k291", v291); + map.put("k292", v292); + map.put("k293", v293); + map.put("k294", v294); + map.put("k295", v295); + map.put("k296", v296); + map.put("k297", v297); + map.put("k298", v298); + map.put("k299", v299); + map.put("k300", v300); + map.put("k301", v301); + map.put("k302", v302); + map.put("k303", v303); + map.put("k304", v304); + map.put("k305", v305); + map.put("k306", v306); + map.put("k307", v307); + map.put("k308", v308); + map.put("k309", v309); + map.put("k310", v310); + map.put("k311", v311); + map.put("k312", v312); + map.put("k313", v313); + map.put("k314", v314); + map.put("k315", v315); + map.put("k316", v316); + map.put("k317", v317); + map.put("k318", v318); + map.put("k319", v319); + map.put("k320", v320); + map.put("k321", v321); + map.put("k322", v322); + map.put("k323", v323); + map.put("k324", v324); + map.put("k325", v325); + map.put("k326", v326); + map.put("k327", v327); + map.put("k328", v328); + map.put("k329", v329); + map.put("k330", v330); + map.put("k331", v331); + map.put("k332", v332); + map.put("k333", v333); + map.put("k334", v334); + map.put("k335", v335); + map.put("k336", v336); + map.put("k337", v337); + map.put("k338", v338); + map.put("k339", v339); + map.put("k340", v340); + map.put("k341", v341); + map.put("k342", v342); + map.put("k343", v343); + map.put("k344", v344); + map.put("k345", v345); + map.put("k346", v346); + map.put("k347", v347); + map.put("k348", v348); + map.put("k349", v349); + map.put("k350", v350); + map.put("k351", v351); + map.put("k352", v352); + map.put("k353", v353); + map.put("k354", v354); + map.put("k355", v355); + map.put("k356", v356); + map.put("k357", v357); + map.put("k358", v358); + map.put("k359", v359); + map.put("k360", v360); + map.put("k361", v361); + map.put("k362", v362); + map.put("k363", v363); + map.put("k364", v364); + map.put("k365", v365); + map.put("k366", v366); + map.put("k367", v367); + map.put("k368", v368); + map.put("k369", v369); + map.put("k370", v370); + map.put("k371", v371); + map.put("k372", v372); + map.put("k373", v373); + map.put("k374", v374); + map.put("k375", v375); + map.put("k376", v376); + map.put("k377", v377); + map.put("k378", v378); + map.put("k379", v379); + map.put("k380", v380); + map.put("k381", v381); + map.put("k382", v382); + map.put("k383", v383); + map.put("k384", v384); + map.put("k385", v385); + map.put("k386", v386); + map.put("k387", v387); + map.put("k388", v388); + map.put("k389", v389); + map.put("k390", v390); + map.put("k391", v391); + map.put("k392", v392); + map.put("k393", v393); + map.put("k394", v394); + map.put("k395", v395); + map.put("k396", v396); + map.put("k397", v397); + map.put("k398", v398); + map.put("k399", v399); + map.put("k400", v400); + map.put("k401", v401); + map.put("k402", v402); + map.put("k403", v403); + map.put("k404", v404); + map.put("k405", v405); + map.put("k406", v406); + map.put("k407", v407); + map.put("k408", v408); + map.put("k409", v409); + map.put("k410", v410); + map.put("k411", v411); + map.put("k412", v412); + map.put("k413", v413); + map.put("k414", v414); + map.put("k415", v415); + map.put("k416", v416); + map.put("k417", v417); + map.put("k418", v418); + map.put("k419", v419); + map.put("k420", v420); + map.put("k421", v421); + map.put("k422", v422); + map.put("k423", v423); + map.put("k424", v424); + map.put("k425", v425); + map.put("k426", v426); + map.put("k427", v427); + map.put("k428", v428); + map.put("k429", v429); + map.put("k430", v430); + map.put("k431", v431); + map.put("k432", v432); + map.put("k433", v433); + map.put("k434", v434); + map.put("k435", v435); + map.put("k436", v436); + map.put("k437", v437); + map.put("k438", v438); + map.put("k439", v439); + map.put("k440", v440); + map.put("k441", v441); + map.put("k442", v442); + map.put("k443", v443); + map.put("k444", v444); + map.put("k445", v445); + map.put("k446", v446); + map.put("k447", v447); + map.put("k448", v448); + map.put("k449", v449); + map.put("k450", v450); + map.put("k451", v451); + map.put("k452", v452); + map.put("k453", v453); + map.put("k454", v454); + map.put("k455", v455); + map.put("k456", v456); + map.put("k457", v457); + map.put("k458", v458); + map.put("k459", v459); + map.put("k460", v460); + map.put("k461", v461); + map.put("k462", v462); + map.put("k463", v463); + map.put("k464", v464); + map.put("k465", v465); + map.put("k466", v466); + map.put("k467", v467); + map.put("k468", v468); + map.put("k469", v469); + map.put("k470", v470); + map.put("k471", v471); + map.put("k472", v472); + map.put("k473", v473); + map.put("k474", v474); + map.put("k475", v475); + map.put("k476", v476); + map.put("k477", v477); + map.put("k478", v478); + map.put("k479", v479); + map.put("k480", v480); + map.put("k481", v481); + map.put("k482", v482); + map.put("k483", v483); + map.put("k484", v484); + map.put("k485", v485); + map.put("k486", v486); + map.put("k487", v487); + map.put("k488", v488); + map.put("k489", v489); + map.put("k490", v490); + map.put("k491", v491); + map.put("k492", v492); + map.put("k493", v493); + map.put("k494", v494); + map.put("k495", v495); + map.put("k496", v496); + map.put("k497", v497); + map.put("k498", v498); + map.put("k499", v499); + map.put("k500", v500); + map.put("k501", v501); + map.put("k502", v502); + map.put("k503", v503); + map.put("k504", v504); + map.put("k505", v505); + map.put("k506", v506); + map.put("k507", v507); + map.put("k508", v508); + map.put("k509", v509); + map.put("k510", v510); + map.put("k511", v511); + map.put("k512", v512); + map.put("k513", v513); + map.put("k514", v514); + map.put("k515", v515); + map.put("k516", v516); + map.put("k517", v517); + map.put("k518", v518); + map.put("k519", v519); + map.put("k520", v520); + map.put("k521", v521); + map.put("k522", v522); + map.put("k523", v523); + map.put("k524", v524); + map.put("k525", v525); + map.put("k526", v526); + map.put("k527", v527); + map.put("k528", v528); + map.put("k529", v529); + map.put("k530", v530); + map.put("k531", v531); + map.put("k532", v532); + map.put("k533", v533); + map.put("k534", v534); + map.put("k535", v535); + map.put("k536", v536); + map.put("k537", v537); + map.put("k538", v538); + map.put("k539", v539); + map.put("k540", v540); + map.put("k541", v541); + map.put("k542", v542); + map.put("k543", v543); + map.put("k544", v544); + map.put("k545", v545); + map.put("k546", v546); + map.put("k547", v547); + map.put("k548", v548); + map.put("k549", v549); + map.put("k550", v550); + map.put("k551", v551); + map.put("k552", v552); + map.put("k553", v553); + map.put("k554", v554); + map.put("k555", v555); + map.put("k556", v556); + map.put("k557", v557); + map.put("k558", v558); + map.put("k559", v559); + map.put("k560", v560); + map.put("k561", v561); + map.put("k562", v562); + map.put("k563", v563); + map.put("k564", v564); + map.put("k565", v565); + map.put("k566", v566); + map.put("k567", v567); + map.put("k568", v568); + map.put("k569", v569); + map.put("k570", v570); + map.put("k571", v571); + map.put("k572", v572); + map.put("k573", v573); + map.put("k574", v574); + map.put("k575", v575); + map.put("k576", v576); + map.put("k577", v577); + map.put("k578", v578); + map.put("k579", v579); + map.put("k580", v580); + map.put("k581", v581); + map.put("k582", v582); + map.put("k583", v583); + map.put("k584", v584); + map.put("k585", v585); + map.put("k586", v586); + map.put("k587", v587); + map.put("k588", v588); + map.put("k589", v589); + map.put("k590", v590); + map.put("k591", v591); + map.put("k592", v592); + map.put("k593", v593); + map.put("k594", v594); + map.put("k595", v595); + map.put("k596", v596); + map.put("k597", v597); + map.put("k598", v598); + map.put("k599", v599); + map.put("k600", v600); + map.put("k601", v601); + map.put("k602", v602); + map.put("k603", v603); + map.put("k604", v604); + map.put("k605", v605); + map.put("k606", v606); + map.put("k607", v607); + map.put("k608", v608); + map.put("k609", v609); + map.put("k610", v610); + map.put("k611", v611); + map.put("k612", v612); + map.put("k613", v613); + map.put("k614", v614); + map.put("k615", v615); + map.put("k616", v616); + map.put("k617", v617); + map.put("k618", v618); + map.put("k619", v619); + map.put("k620", v620); + map.put("k621", v621); + map.put("k622", v622); + map.put("k623", v623); + map.put("k624", v624); + map.put("k625", v625); + map.put("k626", v626); + map.put("k627", v627); + map.put("k628", v628); + map.put("k629", v629); + map.put("k630", v630); + map.put("k631", v631); + map.put("k632", v632); + map.put("k633", v633); + map.put("k634", v634); + map.put("k635", v635); + map.put("k636", v636); + map.put("k637", v637); + map.put("k638", v638); + map.put("k639", v639); + map.put("k640", v640); + map.put("k641", v641); + map.put("k642", v642); + map.put("k643", v643); + map.put("k644", v644); + map.put("k645", v645); + map.put("k646", v646); + map.put("k647", v647); + map.put("k648", v648); + map.put("k649", v649); + map.put("k650", v650); + map.put("k651", v651); + map.put("k652", v652); + map.put("k653", v653); + map.put("k654", v654); + map.put("k655", v655); + map.put("k656", v656); + map.put("k657", v657); + map.put("k658", v658); + map.put("k659", v659); + map.put("k660", v660); + map.put("k661", v661); + map.put("k662", v662); + map.put("k663", v663); + map.put("k664", v664); + map.put("k665", v665); + map.put("k666", v666); + map.put("k667", v667); + map.put("k668", v668); + map.put("k669", v669); + map.put("k670", v670); + map.put("k671", v671); + map.put("k672", v672); + map.put("k673", v673); + map.put("k674", v674); + map.put("k675", v675); + map.put("k676", v676); + map.put("k677", v677); + map.put("k678", v678); + map.put("k679", v679); + map.put("k680", v680); + map.put("k681", v681); + map.put("k682", v682); + map.put("k683", v683); + map.put("k684", v684); + map.put("k685", v685); + map.put("k686", v686); + map.put("k687", v687); + map.put("k688", v688); + map.put("k689", v689); + map.put("k690", v690); + map.put("k691", v691); + map.put("k692", v692); + map.put("k693", v693); + map.put("k694", v694); + map.put("k695", v695); + map.put("k696", v696); + map.put("k697", v697); + map.put("k698", v698); + map.put("k699", v699); + map.put("k700", v700); + map.put("k701", v701); + map.put("k702", v702); + map.put("k703", v703); + map.put("k704", v704); + map.put("k705", v705); + map.put("k706", v706); + map.put("k707", v707); + map.put("k708", v708); + map.put("k709", v709); + map.put("k710", v710); + map.put("k711", v711); + map.put("k712", v712); + map.put("k713", v713); + map.put("k714", v714); + map.put("k715", v715); + map.put("k716", v716); + map.put("k717", v717); + map.put("k718", v718); + map.put("k719", v719); + map.put("k720", v720); + map.put("k721", v721); + map.put("k722", v722); + map.put("k723", v723); + map.put("k724", v724); + map.put("k725", v725); + map.put("k726", v726); + map.put("k727", v727); + map.put("k728", v728); + map.put("k729", v729); + map.put("k730", v730); + map.put("k731", v731); + map.put("k732", v732); + map.put("k733", v733); + map.put("k734", v734); + map.put("k735", v735); + map.put("k736", v736); + map.put("k737", v737); + map.put("k738", v738); + map.put("k739", v739); + map.put("k740", v740); + map.put("k741", v741); + map.put("k742", v742); + map.put("k743", v743); + map.put("k744", v744); + map.put("k745", v745); + map.put("k746", v746); + map.put("k747", v747); + map.put("k748", v748); + map.put("k749", v749); + map.put("k750", v750); + map.put("k751", v751); + map.put("k752", v752); + map.put("k753", v753); + map.put("k754", v754); + map.put("k755", v755); + map.put("k756", v756); + map.put("k757", v757); + map.put("k758", v758); + map.put("k759", v759); + map.put("k760", v760); + map.put("k761", v761); + map.put("k762", v762); + map.put("k763", v763); + map.put("k764", v764); + map.put("k765", v765); + map.put("k766", v766); + map.put("k767", v767); + map.put("k768", v768); + map.put("k769", v769); + map.put("k770", v770); + map.put("k771", v771); + map.put("k772", v772); + map.put("k773", v773); + map.put("k774", v774); + map.put("k775", v775); + map.put("k776", v776); + map.put("k777", v777); + map.put("k778", v778); + map.put("k779", v779); + map.put("k780", v780); + map.put("k781", v781); + map.put("k782", v782); + map.put("k783", v783); + map.put("k784", v784); + map.put("k785", v785); + map.put("k786", v786); + map.put("k787", v787); + map.put("k788", v788); + map.put("k789", v789); + map.put("k790", v790); + map.put("k791", v791); + map.put("k792", v792); + map.put("k793", v793); + map.put("k794", v794); + map.put("k795", v795); + map.put("k796", v796); + map.put("k797", v797); + map.put("k798", v798); + map.put("k799", v799); + map.put("k800", v800); + map.put("k801", v801); + map.put("k802", v802); + map.put("k803", v803); + map.put("k804", v804); + map.put("k805", v805); + map.put("k806", v806); + map.put("k807", v807); + map.put("k808", v808); + map.put("k809", v809); + map.put("k810", v810); + map.put("k811", v811); + map.put("k812", v812); + map.put("k813", v813); + map.put("k814", v814); + map.put("k815", v815); + map.put("k816", v816); + map.put("k817", v817); + map.put("k818", v818); + map.put("k819", v819); + map.put("k820", v820); + map.put("k821", v821); + map.put("k822", v822); + map.put("k823", v823); + map.put("k824", v824); + map.put("k825", v825); + map.put("k826", v826); + map.put("k827", v827); + map.put("k828", v828); + map.put("k829", v829); + map.put("k830", v830); + map.put("k831", v831); + map.put("k832", v832); + map.put("k833", v833); + map.put("k834", v834); + map.put("k835", v835); + map.put("k836", v836); + map.put("k837", v837); + map.put("k838", v838); + map.put("k839", v839); + map.put("k840", v840); + map.put("k841", v841); + map.put("k842", v842); + map.put("k843", v843); + map.put("k844", v844); + map.put("k845", v845); + map.put("k846", v846); + map.put("k847", v847); + map.put("k848", v848); + map.put("k849", v849); + map.put("k850", v850); + map.put("k851", v851); + map.put("k852", v852); + map.put("k853", v853); + map.put("k854", v854); + map.put("k855", v855); + map.put("k856", v856); + map.put("k857", v857); + map.put("k858", v858); + map.put("k859", v859); + map.put("k860", v860); + map.put("k861", v861); + map.put("k862", v862); + map.put("k863", v863); + map.put("k864", v864); + map.put("k865", v865); + map.put("k866", v866); + map.put("k867", v867); + map.put("k868", v868); + map.put("k869", v869); + map.put("k870", v870); + map.put("k871", v871); + map.put("k872", v872); + map.put("k873", v873); + map.put("k874", v874); + map.put("k875", v875); + map.put("k876", v876); + map.put("k877", v877); + map.put("k878", v878); + map.put("k879", v879); + map.put("k880", v880); + map.put("k881", v881); + map.put("k882", v882); + map.put("k883", v883); + map.put("k884", v884); + map.put("k885", v885); + map.put("k886", v886); + map.put("k887", v887); + map.put("k888", v888); + map.put("k889", v889); + map.put("k890", v890); + map.put("k891", v891); + map.put("k892", v892); + map.put("k893", v893); + map.put("k894", v894); + map.put("k895", v895); + map.put("k896", v896); + map.put("k897", v897); + map.put("k898", v898); + map.put("k899", v899); + map.put("k900", v900); + map.put("k901", v901); + map.put("k902", v902); + map.put("k903", v903); + map.put("k904", v904); + map.put("k905", v905); + map.put("k906", v906); + map.put("k907", v907); + map.put("k908", v908); + map.put("k909", v909); + map.put("k910", v910); + map.put("k911", v911); + map.put("k912", v912); + map.put("k913", v913); + map.put("k914", v914); + map.put("k915", v915); + map.put("k916", v916); + map.put("k917", v917); + map.put("k918", v918); + map.put("k919", v919); + map.put("k920", v920); + map.put("k921", v921); + map.put("k922", v922); + map.put("k923", v923); + map.put("k924", v924); + map.put("k925", v925); + map.put("k926", v926); + map.put("k927", v927); + map.put("k928", v928); + map.put("k929", v929); + map.put("k930", v930); + map.put("k931", v931); + map.put("k932", v932); + map.put("k933", v933); + map.put("k934", v934); + map.put("k935", v935); + map.put("k936", v936); + map.put("k937", v937); + map.put("k938", v938); + map.put("k939", v939); + map.put("k940", v940); + map.put("k941", v941); + map.put("k942", v942); + map.put("k943", v943); + map.put("k944", v944); + map.put("k945", v945); + map.put("k946", v946); + map.put("k947", v947); + map.put("k948", v948); + map.put("k949", v949); + map.put("k950", v950); + map.put("k951", v951); + map.put("k952", v952); + map.put("k953", v953); + map.put("k954", v954); + map.put("k955", v955); + map.put("k956", v956); + map.put("k957", v957); + map.put("k958", v958); + map.put("k959", v959); + map.put("k960", v960); + map.put("k961", v961); + map.put("k962", v962); + map.put("k963", v963); + map.put("k964", v964); + map.put("k965", v965); + map.put("k966", v966); + map.put("k967", v967); + map.put("k968", v968); + map.put("k969", v969); + map.put("k970", v970); + map.put("k971", v971); + map.put("k972", v972); + map.put("k973", v973); + map.put("k974", v974); + map.put("k975", v975); + map.put("k976", v976); + map.put("k977", v977); + map.put("k978", v978); + map.put("k979", v979); + map.put("k980", v980); + map.put("k981", v981); + map.put("k982", v982); + map.put("k983", v983); + map.put("k984", v984); + map.put("k985", v985); + map.put("k986", v986); + map.put("k987", v987); + map.put("k988", v988); + map.put("k989", v989); + map.put("k990", v990); + map.put("k991", v991); + map.put("k992", v992); + map.put("k993", v993); + map.put("k994", v994); + map.put("k995", v995); + map.put("k996", v996); + map.put("k997", v997); + map.put("k998", v998); + map.put("k999", v999); + return map; + } } diff --git a/checker/jtreg/nullness/Issue1438b.java b/checker/jtreg/nullness/Issue1438b.java index 5c8fa0ea84b..bd778cefe81 100644 --- a/checker/jtreg/nullness/Issue1438b.java +++ b/checker/jtreg/nullness/Issue1438b.java @@ -9,2010 +9,2010 @@ import java.util.Map; public class Issue1438b { - // Do not initialize these variables. The Nullness Checker is supposed to issue an error about - // uninitialized fields. - Integer v0; - Integer v1; - Integer v2; - Integer v3; - Integer v4; - Integer v5; - Integer v6; - Integer v7; - Integer v8; - Integer v9; - Integer v10; - Integer v11; - Integer v12; - Integer v13; - Integer v14; - Integer v15; - Integer v16; - Integer v17; - Integer v18; - Integer v19; - Integer v20; - Integer v21; - Integer v22; - Integer v23; - Integer v24; - Integer v25; - Integer v26; - Integer v27; - Integer v28; - Integer v29; - Integer v30; - Integer v31; - Integer v32; - Integer v33; - Integer v34; - Integer v35; - Integer v36; - Integer v37; - Integer v38; - Integer v39; - Integer v40; - Integer v41; - Integer v42; - Integer v43; - Integer v44; - Integer v45; - Integer v46; - Integer v47; - Integer v48; - Integer v49; - Integer v50; - Integer v51; - Integer v52; - Integer v53; - Integer v54; - Integer v55; - Integer v56; - Integer v57; - Integer v58; - Integer v59; - Integer v60; - Integer v61; - Integer v62; - Integer v63; - Integer v64; - Integer v65; - Integer v66; - Integer v67; - Integer v68; - Integer v69; - Integer v70; - Integer v71; - Integer v72; - Integer v73; - Integer v74; - Integer v75; - Integer v76; - Integer v77; - Integer v78; - Integer v79; - Integer v80; - Integer v81; - Integer v82; - Integer v83; - Integer v84; - Integer v85; - Integer v86; - Integer v87; - Integer v88; - Integer v89; - Integer v90; - Integer v91; - Integer v92; - Integer v93; - Integer v94; - Integer v95; - Integer v96; - Integer v97; - Integer v98; - Integer v99; - Integer v100; - Integer v101; - Integer v102; - Integer v103; - Integer v104; - Integer v105; - Integer v106; - Integer v107; - Integer v108; - Integer v109; - Integer v110; - Integer v111; - Integer v112; - Integer v113; - Integer v114; - Integer v115; - Integer v116; - Integer v117; - Integer v118; - Integer v119; - Integer v120; - Integer v121; - Integer v122; - Integer v123; - Integer v124; - Integer v125; - Integer v126; - Integer v127; - Integer v128; - Integer v129; - Integer v130; - Integer v131; - Integer v132; - Integer v133; - Integer v134; - Integer v135; - Integer v136; - Integer v137; - Integer v138; - Integer v139; - Integer v140; - Integer v141; - Integer v142; - Integer v143; - Integer v144; - Integer v145; - Integer v146; - Integer v147; - Integer v148; - Integer v149; - Integer v150; - Integer v151; - Integer v152; - Integer v153; - Integer v154; - Integer v155; - Integer v156; - Integer v157; - Integer v158; - Integer v159; - Integer v160; - Integer v161; - Integer v162; - Integer v163; - Integer v164; - Integer v165; - Integer v166; - Integer v167; - Integer v168; - Integer v169; - Integer v170; - Integer v171; - Integer v172; - Integer v173; - Integer v174; - Integer v175; - Integer v176; - Integer v177; - Integer v178; - Integer v179; - Integer v180; - Integer v181; - Integer v182; - Integer v183; - Integer v184; - Integer v185; - Integer v186; - Integer v187; - Integer v188; - Integer v189; - Integer v190; - Integer v191; - Integer v192; - Integer v193; - Integer v194; - Integer v195; - Integer v196; - Integer v197; - Integer v198; - Integer v199; - Integer v200; - Integer v201; - Integer v202; - Integer v203; - Integer v204; - Integer v205; - Integer v206; - Integer v207; - Integer v208; - Integer v209; - Integer v210; - Integer v211; - Integer v212; - Integer v213; - Integer v214; - Integer v215; - Integer v216; - Integer v217; - Integer v218; - Integer v219; - Integer v220; - Integer v221; - Integer v222; - Integer v223; - Integer v224; - Integer v225; - Integer v226; - Integer v227; - Integer v228; - Integer v229; - Integer v230; - Integer v231; - Integer v232; - Integer v233; - Integer v234; - Integer v235; - Integer v236; - Integer v237; - Integer v238; - Integer v239; - Integer v240; - Integer v241; - Integer v242; - Integer v243; - Integer v244; - Integer v245; - Integer v246; - Integer v247; - Integer v248; - Integer v249; - Integer v250; - Integer v251; - Integer v252; - Integer v253; - Integer v254; - Integer v255; - Integer v256; - Integer v257; - Integer v258; - Integer v259; - Integer v260; - Integer v261; - Integer v262; - Integer v263; - Integer v264; - Integer v265; - Integer v266; - Integer v267; - Integer v268; - Integer v269; - Integer v270; - Integer v271; - Integer v272; - Integer v273; - Integer v274; - Integer v275; - Integer v276; - Integer v277; - Integer v278; - Integer v279; - Integer v280; - Integer v281; - Integer v282; - Integer v283; - Integer v284; - Integer v285; - Integer v286; - Integer v287; - Integer v288; - Integer v289; - Integer v290; - Integer v291; - Integer v292; - Integer v293; - Integer v294; - Integer v295; - Integer v296; - Integer v297; - Integer v298; - Integer v299; - Integer v300; - Integer v301; - Integer v302; - Integer v303; - Integer v304; - Integer v305; - Integer v306; - Integer v307; - Integer v308; - Integer v309; - Integer v310; - Integer v311; - Integer v312; - Integer v313; - Integer v314; - Integer v315; - Integer v316; - Integer v317; - Integer v318; - Integer v319; - Integer v320; - Integer v321; - Integer v322; - Integer v323; - Integer v324; - Integer v325; - Integer v326; - Integer v327; - Integer v328; - Integer v329; - Integer v330; - Integer v331; - Integer v332; - Integer v333; - Integer v334; - Integer v335; - Integer v336; - Integer v337; - Integer v338; - Integer v339; - Integer v340; - Integer v341; - Integer v342; - Integer v343; - Integer v344; - Integer v345; - Integer v346; - Integer v347; - Integer v348; - Integer v349; - Integer v350; - Integer v351; - Integer v352; - Integer v353; - Integer v354; - Integer v355; - Integer v356; - Integer v357; - Integer v358; - Integer v359; - Integer v360; - Integer v361; - Integer v362; - Integer v363; - Integer v364; - Integer v365; - Integer v366; - Integer v367; - Integer v368; - Integer v369; - Integer v370; - Integer v371; - Integer v372; - Integer v373; - Integer v374; - Integer v375; - Integer v376; - Integer v377; - Integer v378; - Integer v379; - Integer v380; - Integer v381; - Integer v382; - Integer v383; - Integer v384; - Integer v385; - Integer v386; - Integer v387; - Integer v388; - Integer v389; - Integer v390; - Integer v391; - Integer v392; - Integer v393; - Integer v394; - Integer v395; - Integer v396; - Integer v397; - Integer v398; - Integer v399; - Integer v400; - Integer v401; - Integer v402; - Integer v403; - Integer v404; - Integer v405; - Integer v406; - Integer v407; - Integer v408; - Integer v409; - Integer v410; - Integer v411; - Integer v412; - Integer v413; - Integer v414; - Integer v415; - Integer v416; - Integer v417; - Integer v418; - Integer v419; - Integer v420; - Integer v421; - Integer v422; - Integer v423; - Integer v424; - Integer v425; - Integer v426; - Integer v427; - Integer v428; - Integer v429; - Integer v430; - Integer v431; - Integer v432; - Integer v433; - Integer v434; - Integer v435; - Integer v436; - Integer v437; - Integer v438; - Integer v439; - Integer v440; - Integer v441; - Integer v442; - Integer v443; - Integer v444; - Integer v445; - Integer v446; - Integer v447; - Integer v448; - Integer v449; - Integer v450; - Integer v451; - Integer v452; - Integer v453; - Integer v454; - Integer v455; - Integer v456; - Integer v457; - Integer v458; - Integer v459; - Integer v460; - Integer v461; - Integer v462; - Integer v463; - Integer v464; - Integer v465; - Integer v466; - Integer v467; - Integer v468; - Integer v469; - Integer v470; - Integer v471; - Integer v472; - Integer v473; - Integer v474; - Integer v475; - Integer v476; - Integer v477; - Integer v478; - Integer v479; - Integer v480; - Integer v481; - Integer v482; - Integer v483; - Integer v484; - Integer v485; - Integer v486; - Integer v487; - Integer v488; - Integer v489; - Integer v490; - Integer v491; - Integer v492; - Integer v493; - Integer v494; - Integer v495; - Integer v496; - Integer v497; - Integer v498; - Integer v499; - Integer v500; - Integer v501; - Integer v502; - Integer v503; - Integer v504; - Integer v505; - Integer v506; - Integer v507; - Integer v508; - Integer v509; - Integer v510; - Integer v511; - Integer v512; - Integer v513; - Integer v514; - Integer v515; - Integer v516; - Integer v517; - Integer v518; - Integer v519; - Integer v520; - Integer v521; - Integer v522; - Integer v523; - Integer v524; - Integer v525; - Integer v526; - Integer v527; - Integer v528; - Integer v529; - Integer v530; - Integer v531; - Integer v532; - Integer v533; - Integer v534; - Integer v535; - Integer v536; - Integer v537; - Integer v538; - Integer v539; - Integer v540; - Integer v541; - Integer v542; - Integer v543; - Integer v544; - Integer v545; - Integer v546; - Integer v547; - Integer v548; - Integer v549; - Integer v550; - Integer v551; - Integer v552; - Integer v553; - Integer v554; - Integer v555; - Integer v556; - Integer v557; - Integer v558; - Integer v559; - Integer v560; - Integer v561; - Integer v562; - Integer v563; - Integer v564; - Integer v565; - Integer v566; - Integer v567; - Integer v568; - Integer v569; - Integer v570; - Integer v571; - Integer v572; - Integer v573; - Integer v574; - Integer v575; - Integer v576; - Integer v577; - Integer v578; - Integer v579; - Integer v580; - Integer v581; - Integer v582; - Integer v583; - Integer v584; - Integer v585; - Integer v586; - Integer v587; - Integer v588; - Integer v589; - Integer v590; - Integer v591; - Integer v592; - Integer v593; - Integer v594; - Integer v595; - Integer v596; - Integer v597; - Integer v598; - Integer v599; - Integer v600; - Integer v601; - Integer v602; - Integer v603; - Integer v604; - Integer v605; - Integer v606; - Integer v607; - Integer v608; - Integer v609; - Integer v610; - Integer v611; - Integer v612; - Integer v613; - Integer v614; - Integer v615; - Integer v616; - Integer v617; - Integer v618; - Integer v619; - Integer v620; - Integer v621; - Integer v622; - Integer v623; - Integer v624; - Integer v625; - Integer v626; - Integer v627; - Integer v628; - Integer v629; - Integer v630; - Integer v631; - Integer v632; - Integer v633; - Integer v634; - Integer v635; - Integer v636; - Integer v637; - Integer v638; - Integer v639; - Integer v640; - Integer v641; - Integer v642; - Integer v643; - Integer v644; - Integer v645; - Integer v646; - Integer v647; - Integer v648; - Integer v649; - Integer v650; - Integer v651; - Integer v652; - Integer v653; - Integer v654; - Integer v655; - Integer v656; - Integer v657; - Integer v658; - Integer v659; - Integer v660; - Integer v661; - Integer v662; - Integer v663; - Integer v664; - Integer v665; - Integer v666; - Integer v667; - Integer v668; - Integer v669; - Integer v670; - Integer v671; - Integer v672; - Integer v673; - Integer v674; - Integer v675; - Integer v676; - Integer v677; - Integer v678; - Integer v679; - Integer v680; - Integer v681; - Integer v682; - Integer v683; - Integer v684; - Integer v685; - Integer v686; - Integer v687; - Integer v688; - Integer v689; - Integer v690; - Integer v691; - Integer v692; - Integer v693; - Integer v694; - Integer v695; - Integer v696; - Integer v697; - Integer v698; - Integer v699; - Integer v700; - Integer v701; - Integer v702; - Integer v703; - Integer v704; - Integer v705; - Integer v706; - Integer v707; - Integer v708; - Integer v709; - Integer v710; - Integer v711; - Integer v712; - Integer v713; - Integer v714; - Integer v715; - Integer v716; - Integer v717; - Integer v718; - Integer v719; - Integer v720; - Integer v721; - Integer v722; - Integer v723; - Integer v724; - Integer v725; - Integer v726; - Integer v727; - Integer v728; - Integer v729; - Integer v730; - Integer v731; - Integer v732; - Integer v733; - Integer v734; - Integer v735; - Integer v736; - Integer v737; - Integer v738; - Integer v739; - Integer v740; - Integer v741; - Integer v742; - Integer v743; - Integer v744; - Integer v745; - Integer v746; - Integer v747; - Integer v748; - Integer v749; - Integer v750; - Integer v751; - Integer v752; - Integer v753; - Integer v754; - Integer v755; - Integer v756; - Integer v757; - Integer v758; - Integer v759; - Integer v760; - Integer v761; - Integer v762; - Integer v763; - Integer v764; - Integer v765; - Integer v766; - Integer v767; - Integer v768; - Integer v769; - Integer v770; - Integer v771; - Integer v772; - Integer v773; - Integer v774; - Integer v775; - Integer v776; - Integer v777; - Integer v778; - Integer v779; - Integer v780; - Integer v781; - Integer v782; - Integer v783; - Integer v784; - Integer v785; - Integer v786; - Integer v787; - Integer v788; - Integer v789; - Integer v790; - Integer v791; - Integer v792; - Integer v793; - Integer v794; - Integer v795; - Integer v796; - Integer v797; - Integer v798; - Integer v799; - Integer v800; - Integer v801; - Integer v802; - Integer v803; - Integer v804; - Integer v805; - Integer v806; - Integer v807; - Integer v808; - Integer v809; - Integer v810; - Integer v811; - Integer v812; - Integer v813; - Integer v814; - Integer v815; - Integer v816; - Integer v817; - Integer v818; - Integer v819; - Integer v820; - Integer v821; - Integer v822; - Integer v823; - Integer v824; - Integer v825; - Integer v826; - Integer v827; - Integer v828; - Integer v829; - Integer v830; - Integer v831; - Integer v832; - Integer v833; - Integer v834; - Integer v835; - Integer v836; - Integer v837; - Integer v838; - Integer v839; - Integer v840; - Integer v841; - Integer v842; - Integer v843; - Integer v844; - Integer v845; - Integer v846; - Integer v847; - Integer v848; - Integer v849; - Integer v850; - Integer v851; - Integer v852; - Integer v853; - Integer v854; - Integer v855; - Integer v856; - Integer v857; - Integer v858; - Integer v859; - Integer v860; - Integer v861; - Integer v862; - Integer v863; - Integer v864; - Integer v865; - Integer v866; - Integer v867; - Integer v868; - Integer v869; - Integer v870; - Integer v871; - Integer v872; - Integer v873; - Integer v874; - Integer v875; - Integer v876; - Integer v877; - Integer v878; - Integer v879; - Integer v880; - Integer v881; - Integer v882; - Integer v883; - Integer v884; - Integer v885; - Integer v886; - Integer v887; - Integer v888; - Integer v889; - Integer v890; - Integer v891; - Integer v892; - Integer v893; - Integer v894; - Integer v895; - Integer v896; - Integer v897; - Integer v898; - Integer v899; - Integer v900; - Integer v901; - Integer v902; - Integer v903; - Integer v904; - Integer v905; - Integer v906; - Integer v907; - Integer v908; - Integer v909; - Integer v910; - Integer v911; - Integer v912; - Integer v913; - Integer v914; - Integer v915; - Integer v916; - Integer v917; - Integer v918; - Integer v919; - Integer v920; - Integer v921; - Integer v922; - Integer v923; - Integer v924; - Integer v925; - Integer v926; - Integer v927; - Integer v928; - Integer v929; - Integer v930; - Integer v931; - Integer v932; - Integer v933; - Integer v934; - Integer v935; - Integer v936; - Integer v937; - Integer v938; - Integer v939; - Integer v940; - Integer v941; - Integer v942; - Integer v943; - Integer v944; - Integer v945; - Integer v946; - Integer v947; - Integer v948; - Integer v949; - Integer v950; - Integer v951; - Integer v952; - Integer v953; - Integer v954; - Integer v955; - Integer v956; - Integer v957; - Integer v958; - Integer v959; - Integer v960; - Integer v961; - Integer v962; - Integer v963; - Integer v964; - Integer v965; - Integer v966; - Integer v967; - Integer v968; - Integer v969; - Integer v970; - Integer v971; - Integer v972; - Integer v973; - Integer v974; - Integer v975; - Integer v976; - Integer v977; - Integer v978; - Integer v979; - Integer v980; - Integer v981; - Integer v982; - Integer v983; - Integer v984; - Integer v985; - Integer v986; - Integer v987; - Integer v988; - Integer v989; - Integer v990; - Integer v991; - Integer v992; - Integer v993; - Integer v994; - Integer v995; - Integer v996; - Integer v997; - Integer v998; - Integer v999; + // Do not initialize these variables. The Nullness Checker is supposed to issue an error about + // uninitialized fields. + Integer v0; + Integer v1; + Integer v2; + Integer v3; + Integer v4; + Integer v5; + Integer v6; + Integer v7; + Integer v8; + Integer v9; + Integer v10; + Integer v11; + Integer v12; + Integer v13; + Integer v14; + Integer v15; + Integer v16; + Integer v17; + Integer v18; + Integer v19; + Integer v20; + Integer v21; + Integer v22; + Integer v23; + Integer v24; + Integer v25; + Integer v26; + Integer v27; + Integer v28; + Integer v29; + Integer v30; + Integer v31; + Integer v32; + Integer v33; + Integer v34; + Integer v35; + Integer v36; + Integer v37; + Integer v38; + Integer v39; + Integer v40; + Integer v41; + Integer v42; + Integer v43; + Integer v44; + Integer v45; + Integer v46; + Integer v47; + Integer v48; + Integer v49; + Integer v50; + Integer v51; + Integer v52; + Integer v53; + Integer v54; + Integer v55; + Integer v56; + Integer v57; + Integer v58; + Integer v59; + Integer v60; + Integer v61; + Integer v62; + Integer v63; + Integer v64; + Integer v65; + Integer v66; + Integer v67; + Integer v68; + Integer v69; + Integer v70; + Integer v71; + Integer v72; + Integer v73; + Integer v74; + Integer v75; + Integer v76; + Integer v77; + Integer v78; + Integer v79; + Integer v80; + Integer v81; + Integer v82; + Integer v83; + Integer v84; + Integer v85; + Integer v86; + Integer v87; + Integer v88; + Integer v89; + Integer v90; + Integer v91; + Integer v92; + Integer v93; + Integer v94; + Integer v95; + Integer v96; + Integer v97; + Integer v98; + Integer v99; + Integer v100; + Integer v101; + Integer v102; + Integer v103; + Integer v104; + Integer v105; + Integer v106; + Integer v107; + Integer v108; + Integer v109; + Integer v110; + Integer v111; + Integer v112; + Integer v113; + Integer v114; + Integer v115; + Integer v116; + Integer v117; + Integer v118; + Integer v119; + Integer v120; + Integer v121; + Integer v122; + Integer v123; + Integer v124; + Integer v125; + Integer v126; + Integer v127; + Integer v128; + Integer v129; + Integer v130; + Integer v131; + Integer v132; + Integer v133; + Integer v134; + Integer v135; + Integer v136; + Integer v137; + Integer v138; + Integer v139; + Integer v140; + Integer v141; + Integer v142; + Integer v143; + Integer v144; + Integer v145; + Integer v146; + Integer v147; + Integer v148; + Integer v149; + Integer v150; + Integer v151; + Integer v152; + Integer v153; + Integer v154; + Integer v155; + Integer v156; + Integer v157; + Integer v158; + Integer v159; + Integer v160; + Integer v161; + Integer v162; + Integer v163; + Integer v164; + Integer v165; + Integer v166; + Integer v167; + Integer v168; + Integer v169; + Integer v170; + Integer v171; + Integer v172; + Integer v173; + Integer v174; + Integer v175; + Integer v176; + Integer v177; + Integer v178; + Integer v179; + Integer v180; + Integer v181; + Integer v182; + Integer v183; + Integer v184; + Integer v185; + Integer v186; + Integer v187; + Integer v188; + Integer v189; + Integer v190; + Integer v191; + Integer v192; + Integer v193; + Integer v194; + Integer v195; + Integer v196; + Integer v197; + Integer v198; + Integer v199; + Integer v200; + Integer v201; + Integer v202; + Integer v203; + Integer v204; + Integer v205; + Integer v206; + Integer v207; + Integer v208; + Integer v209; + Integer v210; + Integer v211; + Integer v212; + Integer v213; + Integer v214; + Integer v215; + Integer v216; + Integer v217; + Integer v218; + Integer v219; + Integer v220; + Integer v221; + Integer v222; + Integer v223; + Integer v224; + Integer v225; + Integer v226; + Integer v227; + Integer v228; + Integer v229; + Integer v230; + Integer v231; + Integer v232; + Integer v233; + Integer v234; + Integer v235; + Integer v236; + Integer v237; + Integer v238; + Integer v239; + Integer v240; + Integer v241; + Integer v242; + Integer v243; + Integer v244; + Integer v245; + Integer v246; + Integer v247; + Integer v248; + Integer v249; + Integer v250; + Integer v251; + Integer v252; + Integer v253; + Integer v254; + Integer v255; + Integer v256; + Integer v257; + Integer v258; + Integer v259; + Integer v260; + Integer v261; + Integer v262; + Integer v263; + Integer v264; + Integer v265; + Integer v266; + Integer v267; + Integer v268; + Integer v269; + Integer v270; + Integer v271; + Integer v272; + Integer v273; + Integer v274; + Integer v275; + Integer v276; + Integer v277; + Integer v278; + Integer v279; + Integer v280; + Integer v281; + Integer v282; + Integer v283; + Integer v284; + Integer v285; + Integer v286; + Integer v287; + Integer v288; + Integer v289; + Integer v290; + Integer v291; + Integer v292; + Integer v293; + Integer v294; + Integer v295; + Integer v296; + Integer v297; + Integer v298; + Integer v299; + Integer v300; + Integer v301; + Integer v302; + Integer v303; + Integer v304; + Integer v305; + Integer v306; + Integer v307; + Integer v308; + Integer v309; + Integer v310; + Integer v311; + Integer v312; + Integer v313; + Integer v314; + Integer v315; + Integer v316; + Integer v317; + Integer v318; + Integer v319; + Integer v320; + Integer v321; + Integer v322; + Integer v323; + Integer v324; + Integer v325; + Integer v326; + Integer v327; + Integer v328; + Integer v329; + Integer v330; + Integer v331; + Integer v332; + Integer v333; + Integer v334; + Integer v335; + Integer v336; + Integer v337; + Integer v338; + Integer v339; + Integer v340; + Integer v341; + Integer v342; + Integer v343; + Integer v344; + Integer v345; + Integer v346; + Integer v347; + Integer v348; + Integer v349; + Integer v350; + Integer v351; + Integer v352; + Integer v353; + Integer v354; + Integer v355; + Integer v356; + Integer v357; + Integer v358; + Integer v359; + Integer v360; + Integer v361; + Integer v362; + Integer v363; + Integer v364; + Integer v365; + Integer v366; + Integer v367; + Integer v368; + Integer v369; + Integer v370; + Integer v371; + Integer v372; + Integer v373; + Integer v374; + Integer v375; + Integer v376; + Integer v377; + Integer v378; + Integer v379; + Integer v380; + Integer v381; + Integer v382; + Integer v383; + Integer v384; + Integer v385; + Integer v386; + Integer v387; + Integer v388; + Integer v389; + Integer v390; + Integer v391; + Integer v392; + Integer v393; + Integer v394; + Integer v395; + Integer v396; + Integer v397; + Integer v398; + Integer v399; + Integer v400; + Integer v401; + Integer v402; + Integer v403; + Integer v404; + Integer v405; + Integer v406; + Integer v407; + Integer v408; + Integer v409; + Integer v410; + Integer v411; + Integer v412; + Integer v413; + Integer v414; + Integer v415; + Integer v416; + Integer v417; + Integer v418; + Integer v419; + Integer v420; + Integer v421; + Integer v422; + Integer v423; + Integer v424; + Integer v425; + Integer v426; + Integer v427; + Integer v428; + Integer v429; + Integer v430; + Integer v431; + Integer v432; + Integer v433; + Integer v434; + Integer v435; + Integer v436; + Integer v437; + Integer v438; + Integer v439; + Integer v440; + Integer v441; + Integer v442; + Integer v443; + Integer v444; + Integer v445; + Integer v446; + Integer v447; + Integer v448; + Integer v449; + Integer v450; + Integer v451; + Integer v452; + Integer v453; + Integer v454; + Integer v455; + Integer v456; + Integer v457; + Integer v458; + Integer v459; + Integer v460; + Integer v461; + Integer v462; + Integer v463; + Integer v464; + Integer v465; + Integer v466; + Integer v467; + Integer v468; + Integer v469; + Integer v470; + Integer v471; + Integer v472; + Integer v473; + Integer v474; + Integer v475; + Integer v476; + Integer v477; + Integer v478; + Integer v479; + Integer v480; + Integer v481; + Integer v482; + Integer v483; + Integer v484; + Integer v485; + Integer v486; + Integer v487; + Integer v488; + Integer v489; + Integer v490; + Integer v491; + Integer v492; + Integer v493; + Integer v494; + Integer v495; + Integer v496; + Integer v497; + Integer v498; + Integer v499; + Integer v500; + Integer v501; + Integer v502; + Integer v503; + Integer v504; + Integer v505; + Integer v506; + Integer v507; + Integer v508; + Integer v509; + Integer v510; + Integer v511; + Integer v512; + Integer v513; + Integer v514; + Integer v515; + Integer v516; + Integer v517; + Integer v518; + Integer v519; + Integer v520; + Integer v521; + Integer v522; + Integer v523; + Integer v524; + Integer v525; + Integer v526; + Integer v527; + Integer v528; + Integer v529; + Integer v530; + Integer v531; + Integer v532; + Integer v533; + Integer v534; + Integer v535; + Integer v536; + Integer v537; + Integer v538; + Integer v539; + Integer v540; + Integer v541; + Integer v542; + Integer v543; + Integer v544; + Integer v545; + Integer v546; + Integer v547; + Integer v548; + Integer v549; + Integer v550; + Integer v551; + Integer v552; + Integer v553; + Integer v554; + Integer v555; + Integer v556; + Integer v557; + Integer v558; + Integer v559; + Integer v560; + Integer v561; + Integer v562; + Integer v563; + Integer v564; + Integer v565; + Integer v566; + Integer v567; + Integer v568; + Integer v569; + Integer v570; + Integer v571; + Integer v572; + Integer v573; + Integer v574; + Integer v575; + Integer v576; + Integer v577; + Integer v578; + Integer v579; + Integer v580; + Integer v581; + Integer v582; + Integer v583; + Integer v584; + Integer v585; + Integer v586; + Integer v587; + Integer v588; + Integer v589; + Integer v590; + Integer v591; + Integer v592; + Integer v593; + Integer v594; + Integer v595; + Integer v596; + Integer v597; + Integer v598; + Integer v599; + Integer v600; + Integer v601; + Integer v602; + Integer v603; + Integer v604; + Integer v605; + Integer v606; + Integer v607; + Integer v608; + Integer v609; + Integer v610; + Integer v611; + Integer v612; + Integer v613; + Integer v614; + Integer v615; + Integer v616; + Integer v617; + Integer v618; + Integer v619; + Integer v620; + Integer v621; + Integer v622; + Integer v623; + Integer v624; + Integer v625; + Integer v626; + Integer v627; + Integer v628; + Integer v629; + Integer v630; + Integer v631; + Integer v632; + Integer v633; + Integer v634; + Integer v635; + Integer v636; + Integer v637; + Integer v638; + Integer v639; + Integer v640; + Integer v641; + Integer v642; + Integer v643; + Integer v644; + Integer v645; + Integer v646; + Integer v647; + Integer v648; + Integer v649; + Integer v650; + Integer v651; + Integer v652; + Integer v653; + Integer v654; + Integer v655; + Integer v656; + Integer v657; + Integer v658; + Integer v659; + Integer v660; + Integer v661; + Integer v662; + Integer v663; + Integer v664; + Integer v665; + Integer v666; + Integer v667; + Integer v668; + Integer v669; + Integer v670; + Integer v671; + Integer v672; + Integer v673; + Integer v674; + Integer v675; + Integer v676; + Integer v677; + Integer v678; + Integer v679; + Integer v680; + Integer v681; + Integer v682; + Integer v683; + Integer v684; + Integer v685; + Integer v686; + Integer v687; + Integer v688; + Integer v689; + Integer v690; + Integer v691; + Integer v692; + Integer v693; + Integer v694; + Integer v695; + Integer v696; + Integer v697; + Integer v698; + Integer v699; + Integer v700; + Integer v701; + Integer v702; + Integer v703; + Integer v704; + Integer v705; + Integer v706; + Integer v707; + Integer v708; + Integer v709; + Integer v710; + Integer v711; + Integer v712; + Integer v713; + Integer v714; + Integer v715; + Integer v716; + Integer v717; + Integer v718; + Integer v719; + Integer v720; + Integer v721; + Integer v722; + Integer v723; + Integer v724; + Integer v725; + Integer v726; + Integer v727; + Integer v728; + Integer v729; + Integer v730; + Integer v731; + Integer v732; + Integer v733; + Integer v734; + Integer v735; + Integer v736; + Integer v737; + Integer v738; + Integer v739; + Integer v740; + Integer v741; + Integer v742; + Integer v743; + Integer v744; + Integer v745; + Integer v746; + Integer v747; + Integer v748; + Integer v749; + Integer v750; + Integer v751; + Integer v752; + Integer v753; + Integer v754; + Integer v755; + Integer v756; + Integer v757; + Integer v758; + Integer v759; + Integer v760; + Integer v761; + Integer v762; + Integer v763; + Integer v764; + Integer v765; + Integer v766; + Integer v767; + Integer v768; + Integer v769; + Integer v770; + Integer v771; + Integer v772; + Integer v773; + Integer v774; + Integer v775; + Integer v776; + Integer v777; + Integer v778; + Integer v779; + Integer v780; + Integer v781; + Integer v782; + Integer v783; + Integer v784; + Integer v785; + Integer v786; + Integer v787; + Integer v788; + Integer v789; + Integer v790; + Integer v791; + Integer v792; + Integer v793; + Integer v794; + Integer v795; + Integer v796; + Integer v797; + Integer v798; + Integer v799; + Integer v800; + Integer v801; + Integer v802; + Integer v803; + Integer v804; + Integer v805; + Integer v806; + Integer v807; + Integer v808; + Integer v809; + Integer v810; + Integer v811; + Integer v812; + Integer v813; + Integer v814; + Integer v815; + Integer v816; + Integer v817; + Integer v818; + Integer v819; + Integer v820; + Integer v821; + Integer v822; + Integer v823; + Integer v824; + Integer v825; + Integer v826; + Integer v827; + Integer v828; + Integer v829; + Integer v830; + Integer v831; + Integer v832; + Integer v833; + Integer v834; + Integer v835; + Integer v836; + Integer v837; + Integer v838; + Integer v839; + Integer v840; + Integer v841; + Integer v842; + Integer v843; + Integer v844; + Integer v845; + Integer v846; + Integer v847; + Integer v848; + Integer v849; + Integer v850; + Integer v851; + Integer v852; + Integer v853; + Integer v854; + Integer v855; + Integer v856; + Integer v857; + Integer v858; + Integer v859; + Integer v860; + Integer v861; + Integer v862; + Integer v863; + Integer v864; + Integer v865; + Integer v866; + Integer v867; + Integer v868; + Integer v869; + Integer v870; + Integer v871; + Integer v872; + Integer v873; + Integer v874; + Integer v875; + Integer v876; + Integer v877; + Integer v878; + Integer v879; + Integer v880; + Integer v881; + Integer v882; + Integer v883; + Integer v884; + Integer v885; + Integer v886; + Integer v887; + Integer v888; + Integer v889; + Integer v890; + Integer v891; + Integer v892; + Integer v893; + Integer v894; + Integer v895; + Integer v896; + Integer v897; + Integer v898; + Integer v899; + Integer v900; + Integer v901; + Integer v902; + Integer v903; + Integer v904; + Integer v905; + Integer v906; + Integer v907; + Integer v908; + Integer v909; + Integer v910; + Integer v911; + Integer v912; + Integer v913; + Integer v914; + Integer v915; + Integer v916; + Integer v917; + Integer v918; + Integer v919; + Integer v920; + Integer v921; + Integer v922; + Integer v923; + Integer v924; + Integer v925; + Integer v926; + Integer v927; + Integer v928; + Integer v929; + Integer v930; + Integer v931; + Integer v932; + Integer v933; + Integer v934; + Integer v935; + Integer v936; + Integer v937; + Integer v938; + Integer v939; + Integer v940; + Integer v941; + Integer v942; + Integer v943; + Integer v944; + Integer v945; + Integer v946; + Integer v947; + Integer v948; + Integer v949; + Integer v950; + Integer v951; + Integer v952; + Integer v953; + Integer v954; + Integer v955; + Integer v956; + Integer v957; + Integer v958; + Integer v959; + Integer v960; + Integer v961; + Integer v962; + Integer v963; + Integer v964; + Integer v965; + Integer v966; + Integer v967; + Integer v968; + Integer v969; + Integer v970; + Integer v971; + Integer v972; + Integer v973; + Integer v974; + Integer v975; + Integer v976; + Integer v977; + Integer v978; + Integer v979; + Integer v980; + Integer v981; + Integer v982; + Integer v983; + Integer v984; + Integer v985; + Integer v986; + Integer v987; + Integer v988; + Integer v989; + Integer v990; + Integer v991; + Integer v992; + Integer v993; + Integer v994; + Integer v995; + Integer v996; + Integer v997; + Integer v998; + Integer v999; - Issue1438b() { - Map map = new HashMap<>(); - map.put("k0", v0); - map.put("k1", v1); - map.put("k2", v2); - map.put("k3", v3); - map.put("k4", v4); - map.put("k5", v5); - map.put("k6", v6); - map.put("k7", v7); - map.put("k8", v8); - map.put("k9", v9); - map.put("k10", v10); - map.put("k11", v11); - map.put("k12", v12); - map.put("k13", v13); - map.put("k14", v14); - map.put("k15", v15); - map.put("k16", v16); - map.put("k17", v17); - map.put("k18", v18); - map.put("k19", v19); - map.put("k20", v20); - map.put("k21", v21); - map.put("k22", v22); - map.put("k23", v23); - map.put("k24", v24); - map.put("k25", v25); - map.put("k26", v26); - map.put("k27", v27); - map.put("k28", v28); - map.put("k29", v29); - map.put("k30", v30); - map.put("k31", v31); - map.put("k32", v32); - map.put("k33", v33); - map.put("k34", v34); - map.put("k35", v35); - map.put("k36", v36); - map.put("k37", v37); - map.put("k38", v38); - map.put("k39", v39); - map.put("k40", v40); - map.put("k41", v41); - map.put("k42", v42); - map.put("k43", v43); - map.put("k44", v44); - map.put("k45", v45); - map.put("k46", v46); - map.put("k47", v47); - map.put("k48", v48); - map.put("k49", v49); - map.put("k50", v50); - map.put("k51", v51); - map.put("k52", v52); - map.put("k53", v53); - map.put("k54", v54); - map.put("k55", v55); - map.put("k56", v56); - map.put("k57", v57); - map.put("k58", v58); - map.put("k59", v59); - map.put("k60", v60); - map.put("k61", v61); - map.put("k62", v62); - map.put("k63", v63); - map.put("k64", v64); - map.put("k65", v65); - map.put("k66", v66); - map.put("k67", v67); - map.put("k68", v68); - map.put("k69", v69); - map.put("k70", v70); - map.put("k71", v71); - map.put("k72", v72); - map.put("k73", v73); - map.put("k74", v74); - map.put("k75", v75); - map.put("k76", v76); - map.put("k77", v77); - map.put("k78", v78); - map.put("k79", v79); - map.put("k80", v80); - map.put("k81", v81); - map.put("k82", v82); - map.put("k83", v83); - map.put("k84", v84); - map.put("k85", v85); - map.put("k86", v86); - map.put("k87", v87); - map.put("k88", v88); - map.put("k89", v89); - map.put("k90", v90); - map.put("k91", v91); - map.put("k92", v92); - map.put("k93", v93); - map.put("k94", v94); - map.put("k95", v95); - map.put("k96", v96); - map.put("k97", v97); - map.put("k98", v98); - map.put("k99", v99); - map.put("k100", v100); - map.put("k101", v101); - map.put("k102", v102); - map.put("k103", v103); - map.put("k104", v104); - map.put("k105", v105); - map.put("k106", v106); - map.put("k107", v107); - map.put("k108", v108); - map.put("k109", v109); - map.put("k110", v110); - map.put("k111", v111); - map.put("k112", v112); - map.put("k113", v113); - map.put("k114", v114); - map.put("k115", v115); - map.put("k116", v116); - map.put("k117", v117); - map.put("k118", v118); - map.put("k119", v119); - map.put("k120", v120); - map.put("k121", v121); - map.put("k122", v122); - map.put("k123", v123); - map.put("k124", v124); - map.put("k125", v125); - map.put("k126", v126); - map.put("k127", v127); - map.put("k128", v128); - map.put("k129", v129); - map.put("k130", v130); - map.put("k131", v131); - map.put("k132", v132); - map.put("k133", v133); - map.put("k134", v134); - map.put("k135", v135); - map.put("k136", v136); - map.put("k137", v137); - map.put("k138", v138); - map.put("k139", v139); - map.put("k140", v140); - map.put("k141", v141); - map.put("k142", v142); - map.put("k143", v143); - map.put("k144", v144); - map.put("k145", v145); - map.put("k146", v146); - map.put("k147", v147); - map.put("k148", v148); - map.put("k149", v149); - map.put("k150", v150); - map.put("k151", v151); - map.put("k152", v152); - map.put("k153", v153); - map.put("k154", v154); - map.put("k155", v155); - map.put("k156", v156); - map.put("k157", v157); - map.put("k158", v158); - map.put("k159", v159); - map.put("k160", v160); - map.put("k161", v161); - map.put("k162", v162); - map.put("k163", v163); - map.put("k164", v164); - map.put("k165", v165); - map.put("k166", v166); - map.put("k167", v167); - map.put("k168", v168); - map.put("k169", v169); - map.put("k170", v170); - map.put("k171", v171); - map.put("k172", v172); - map.put("k173", v173); - map.put("k174", v174); - map.put("k175", v175); - map.put("k176", v176); - map.put("k177", v177); - map.put("k178", v178); - map.put("k179", v179); - map.put("k180", v180); - map.put("k181", v181); - map.put("k182", v182); - map.put("k183", v183); - map.put("k184", v184); - map.put("k185", v185); - map.put("k186", v186); - map.put("k187", v187); - map.put("k188", v188); - map.put("k189", v189); - map.put("k190", v190); - map.put("k191", v191); - map.put("k192", v192); - map.put("k193", v193); - map.put("k194", v194); - map.put("k195", v195); - map.put("k196", v196); - map.put("k197", v197); - map.put("k198", v198); - map.put("k199", v199); - map.put("k200", v200); - map.put("k201", v201); - map.put("k202", v202); - map.put("k203", v203); - map.put("k204", v204); - map.put("k205", v205); - map.put("k206", v206); - map.put("k207", v207); - map.put("k208", v208); - map.put("k209", v209); - map.put("k210", v210); - map.put("k211", v211); - map.put("k212", v212); - map.put("k213", v213); - map.put("k214", v214); - map.put("k215", v215); - map.put("k216", v216); - map.put("k217", v217); - map.put("k218", v218); - map.put("k219", v219); - map.put("k220", v220); - map.put("k221", v221); - map.put("k222", v222); - map.put("k223", v223); - map.put("k224", v224); - map.put("k225", v225); - map.put("k226", v226); - map.put("k227", v227); - map.put("k228", v228); - map.put("k229", v229); - map.put("k230", v230); - map.put("k231", v231); - map.put("k232", v232); - map.put("k233", v233); - map.put("k234", v234); - map.put("k235", v235); - map.put("k236", v236); - map.put("k237", v237); - map.put("k238", v238); - map.put("k239", v239); - map.put("k240", v240); - map.put("k241", v241); - map.put("k242", v242); - map.put("k243", v243); - map.put("k244", v244); - map.put("k245", v245); - map.put("k246", v246); - map.put("k247", v247); - map.put("k248", v248); - map.put("k249", v249); - map.put("k250", v250); - map.put("k251", v251); - map.put("k252", v252); - map.put("k253", v253); - map.put("k254", v254); - map.put("k255", v255); - map.put("k256", v256); - map.put("k257", v257); - map.put("k258", v258); - map.put("k259", v259); - map.put("k260", v260); - map.put("k261", v261); - map.put("k262", v262); - map.put("k263", v263); - map.put("k264", v264); - map.put("k265", v265); - map.put("k266", v266); - map.put("k267", v267); - map.put("k268", v268); - map.put("k269", v269); - map.put("k270", v270); - map.put("k271", v271); - map.put("k272", v272); - map.put("k273", v273); - map.put("k274", v274); - map.put("k275", v275); - map.put("k276", v276); - map.put("k277", v277); - map.put("k278", v278); - map.put("k279", v279); - map.put("k280", v280); - map.put("k281", v281); - map.put("k282", v282); - map.put("k283", v283); - map.put("k284", v284); - map.put("k285", v285); - map.put("k286", v286); - map.put("k287", v287); - map.put("k288", v288); - map.put("k289", v289); - map.put("k290", v290); - map.put("k291", v291); - map.put("k292", v292); - map.put("k293", v293); - map.put("k294", v294); - map.put("k295", v295); - map.put("k296", v296); - map.put("k297", v297); - map.put("k298", v298); - map.put("k299", v299); - map.put("k300", v300); - map.put("k301", v301); - map.put("k302", v302); - map.put("k303", v303); - map.put("k304", v304); - map.put("k305", v305); - map.put("k306", v306); - map.put("k307", v307); - map.put("k308", v308); - map.put("k309", v309); - map.put("k310", v310); - map.put("k311", v311); - map.put("k312", v312); - map.put("k313", v313); - map.put("k314", v314); - map.put("k315", v315); - map.put("k316", v316); - map.put("k317", v317); - map.put("k318", v318); - map.put("k319", v319); - map.put("k320", v320); - map.put("k321", v321); - map.put("k322", v322); - map.put("k323", v323); - map.put("k324", v324); - map.put("k325", v325); - map.put("k326", v326); - map.put("k327", v327); - map.put("k328", v328); - map.put("k329", v329); - map.put("k330", v330); - map.put("k331", v331); - map.put("k332", v332); - map.put("k333", v333); - map.put("k334", v334); - map.put("k335", v335); - map.put("k336", v336); - map.put("k337", v337); - map.put("k338", v338); - map.put("k339", v339); - map.put("k340", v340); - map.put("k341", v341); - map.put("k342", v342); - map.put("k343", v343); - map.put("k344", v344); - map.put("k345", v345); - map.put("k346", v346); - map.put("k347", v347); - map.put("k348", v348); - map.put("k349", v349); - map.put("k350", v350); - map.put("k351", v351); - map.put("k352", v352); - map.put("k353", v353); - map.put("k354", v354); - map.put("k355", v355); - map.put("k356", v356); - map.put("k357", v357); - map.put("k358", v358); - map.put("k359", v359); - map.put("k360", v360); - map.put("k361", v361); - map.put("k362", v362); - map.put("k363", v363); - map.put("k364", v364); - map.put("k365", v365); - map.put("k366", v366); - map.put("k367", v367); - map.put("k368", v368); - map.put("k369", v369); - map.put("k370", v370); - map.put("k371", v371); - map.put("k372", v372); - map.put("k373", v373); - map.put("k374", v374); - map.put("k375", v375); - map.put("k376", v376); - map.put("k377", v377); - map.put("k378", v378); - map.put("k379", v379); - map.put("k380", v380); - map.put("k381", v381); - map.put("k382", v382); - map.put("k383", v383); - map.put("k384", v384); - map.put("k385", v385); - map.put("k386", v386); - map.put("k387", v387); - map.put("k388", v388); - map.put("k389", v389); - map.put("k390", v390); - map.put("k391", v391); - map.put("k392", v392); - map.put("k393", v393); - map.put("k394", v394); - map.put("k395", v395); - map.put("k396", v396); - map.put("k397", v397); - map.put("k398", v398); - map.put("k399", v399); - map.put("k400", v400); - map.put("k401", v401); - map.put("k402", v402); - map.put("k403", v403); - map.put("k404", v404); - map.put("k405", v405); - map.put("k406", v406); - map.put("k407", v407); - map.put("k408", v408); - map.put("k409", v409); - map.put("k410", v410); - map.put("k411", v411); - map.put("k412", v412); - map.put("k413", v413); - map.put("k414", v414); - map.put("k415", v415); - map.put("k416", v416); - map.put("k417", v417); - map.put("k418", v418); - map.put("k419", v419); - map.put("k420", v420); - map.put("k421", v421); - map.put("k422", v422); - map.put("k423", v423); - map.put("k424", v424); - map.put("k425", v425); - map.put("k426", v426); - map.put("k427", v427); - map.put("k428", v428); - map.put("k429", v429); - map.put("k430", v430); - map.put("k431", v431); - map.put("k432", v432); - map.put("k433", v433); - map.put("k434", v434); - map.put("k435", v435); - map.put("k436", v436); - map.put("k437", v437); - map.put("k438", v438); - map.put("k439", v439); - map.put("k440", v440); - map.put("k441", v441); - map.put("k442", v442); - map.put("k443", v443); - map.put("k444", v444); - map.put("k445", v445); - map.put("k446", v446); - map.put("k447", v447); - map.put("k448", v448); - map.put("k449", v449); - map.put("k450", v450); - map.put("k451", v451); - map.put("k452", v452); - map.put("k453", v453); - map.put("k454", v454); - map.put("k455", v455); - map.put("k456", v456); - map.put("k457", v457); - map.put("k458", v458); - map.put("k459", v459); - map.put("k460", v460); - map.put("k461", v461); - map.put("k462", v462); - map.put("k463", v463); - map.put("k464", v464); - map.put("k465", v465); - map.put("k466", v466); - map.put("k467", v467); - map.put("k468", v468); - map.put("k469", v469); - map.put("k470", v470); - map.put("k471", v471); - map.put("k472", v472); - map.put("k473", v473); - map.put("k474", v474); - map.put("k475", v475); - map.put("k476", v476); - map.put("k477", v477); - map.put("k478", v478); - map.put("k479", v479); - map.put("k480", v480); - map.put("k481", v481); - map.put("k482", v482); - map.put("k483", v483); - map.put("k484", v484); - map.put("k485", v485); - map.put("k486", v486); - map.put("k487", v487); - map.put("k488", v488); - map.put("k489", v489); - map.put("k490", v490); - map.put("k491", v491); - map.put("k492", v492); - map.put("k493", v493); - map.put("k494", v494); - map.put("k495", v495); - map.put("k496", v496); - map.put("k497", v497); - map.put("k498", v498); - map.put("k499", v499); - map.put("k500", v500); - map.put("k501", v501); - map.put("k502", v502); - map.put("k503", v503); - map.put("k504", v504); - map.put("k505", v505); - map.put("k506", v506); - map.put("k507", v507); - map.put("k508", v508); - map.put("k509", v509); - map.put("k510", v510); - map.put("k511", v511); - map.put("k512", v512); - map.put("k513", v513); - map.put("k514", v514); - map.put("k515", v515); - map.put("k516", v516); - map.put("k517", v517); - map.put("k518", v518); - map.put("k519", v519); - map.put("k520", v520); - map.put("k521", v521); - map.put("k522", v522); - map.put("k523", v523); - map.put("k524", v524); - map.put("k525", v525); - map.put("k526", v526); - map.put("k527", v527); - map.put("k528", v528); - map.put("k529", v529); - map.put("k530", v530); - map.put("k531", v531); - map.put("k532", v532); - map.put("k533", v533); - map.put("k534", v534); - map.put("k535", v535); - map.put("k536", v536); - map.put("k537", v537); - map.put("k538", v538); - map.put("k539", v539); - map.put("k540", v540); - map.put("k541", v541); - map.put("k542", v542); - map.put("k543", v543); - map.put("k544", v544); - map.put("k545", v545); - map.put("k546", v546); - map.put("k547", v547); - map.put("k548", v548); - map.put("k549", v549); - map.put("k550", v550); - map.put("k551", v551); - map.put("k552", v552); - map.put("k553", v553); - map.put("k554", v554); - map.put("k555", v555); - map.put("k556", v556); - map.put("k557", v557); - map.put("k558", v558); - map.put("k559", v559); - map.put("k560", v560); - map.put("k561", v561); - map.put("k562", v562); - map.put("k563", v563); - map.put("k564", v564); - map.put("k565", v565); - map.put("k566", v566); - map.put("k567", v567); - map.put("k568", v568); - map.put("k569", v569); - map.put("k570", v570); - map.put("k571", v571); - map.put("k572", v572); - map.put("k573", v573); - map.put("k574", v574); - map.put("k575", v575); - map.put("k576", v576); - map.put("k577", v577); - map.put("k578", v578); - map.put("k579", v579); - map.put("k580", v580); - map.put("k581", v581); - map.put("k582", v582); - map.put("k583", v583); - map.put("k584", v584); - map.put("k585", v585); - map.put("k586", v586); - map.put("k587", v587); - map.put("k588", v588); - map.put("k589", v589); - map.put("k590", v590); - map.put("k591", v591); - map.put("k592", v592); - map.put("k593", v593); - map.put("k594", v594); - map.put("k595", v595); - map.put("k596", v596); - map.put("k597", v597); - map.put("k598", v598); - map.put("k599", v599); - map.put("k600", v600); - map.put("k601", v601); - map.put("k602", v602); - map.put("k603", v603); - map.put("k604", v604); - map.put("k605", v605); - map.put("k606", v606); - map.put("k607", v607); - map.put("k608", v608); - map.put("k609", v609); - map.put("k610", v610); - map.put("k611", v611); - map.put("k612", v612); - map.put("k613", v613); - map.put("k614", v614); - map.put("k615", v615); - map.put("k616", v616); - map.put("k617", v617); - map.put("k618", v618); - map.put("k619", v619); - map.put("k620", v620); - map.put("k621", v621); - map.put("k622", v622); - map.put("k623", v623); - map.put("k624", v624); - map.put("k625", v625); - map.put("k626", v626); - map.put("k627", v627); - map.put("k628", v628); - map.put("k629", v629); - map.put("k630", v630); - map.put("k631", v631); - map.put("k632", v632); - map.put("k633", v633); - map.put("k634", v634); - map.put("k635", v635); - map.put("k636", v636); - map.put("k637", v637); - map.put("k638", v638); - map.put("k639", v639); - map.put("k640", v640); - map.put("k641", v641); - map.put("k642", v642); - map.put("k643", v643); - map.put("k644", v644); - map.put("k645", v645); - map.put("k646", v646); - map.put("k647", v647); - map.put("k648", v648); - map.put("k649", v649); - map.put("k650", v650); - map.put("k651", v651); - map.put("k652", v652); - map.put("k653", v653); - map.put("k654", v654); - map.put("k655", v655); - map.put("k656", v656); - map.put("k657", v657); - map.put("k658", v658); - map.put("k659", v659); - map.put("k660", v660); - map.put("k661", v661); - map.put("k662", v662); - map.put("k663", v663); - map.put("k664", v664); - map.put("k665", v665); - map.put("k666", v666); - map.put("k667", v667); - map.put("k668", v668); - map.put("k669", v669); - map.put("k670", v670); - map.put("k671", v671); - map.put("k672", v672); - map.put("k673", v673); - map.put("k674", v674); - map.put("k675", v675); - map.put("k676", v676); - map.put("k677", v677); - map.put("k678", v678); - map.put("k679", v679); - map.put("k680", v680); - map.put("k681", v681); - map.put("k682", v682); - map.put("k683", v683); - map.put("k684", v684); - map.put("k685", v685); - map.put("k686", v686); - map.put("k687", v687); - map.put("k688", v688); - map.put("k689", v689); - map.put("k690", v690); - map.put("k691", v691); - map.put("k692", v692); - map.put("k693", v693); - map.put("k694", v694); - map.put("k695", v695); - map.put("k696", v696); - map.put("k697", v697); - map.put("k698", v698); - map.put("k699", v699); - map.put("k700", v700); - map.put("k701", v701); - map.put("k702", v702); - map.put("k703", v703); - map.put("k704", v704); - map.put("k705", v705); - map.put("k706", v706); - map.put("k707", v707); - map.put("k708", v708); - map.put("k709", v709); - map.put("k710", v710); - map.put("k711", v711); - map.put("k712", v712); - map.put("k713", v713); - map.put("k714", v714); - map.put("k715", v715); - map.put("k716", v716); - map.put("k717", v717); - map.put("k718", v718); - map.put("k719", v719); - map.put("k720", v720); - map.put("k721", v721); - map.put("k722", v722); - map.put("k723", v723); - map.put("k724", v724); - map.put("k725", v725); - map.put("k726", v726); - map.put("k727", v727); - map.put("k728", v728); - map.put("k729", v729); - map.put("k730", v730); - map.put("k731", v731); - map.put("k732", v732); - map.put("k733", v733); - map.put("k734", v734); - map.put("k735", v735); - map.put("k736", v736); - map.put("k737", v737); - map.put("k738", v738); - map.put("k739", v739); - map.put("k740", v740); - map.put("k741", v741); - map.put("k742", v742); - map.put("k743", v743); - map.put("k744", v744); - map.put("k745", v745); - map.put("k746", v746); - map.put("k747", v747); - map.put("k748", v748); - map.put("k749", v749); - map.put("k750", v750); - map.put("k751", v751); - map.put("k752", v752); - map.put("k753", v753); - map.put("k754", v754); - map.put("k755", v755); - map.put("k756", v756); - map.put("k757", v757); - map.put("k758", v758); - map.put("k759", v759); - map.put("k760", v760); - map.put("k761", v761); - map.put("k762", v762); - map.put("k763", v763); - map.put("k764", v764); - map.put("k765", v765); - map.put("k766", v766); - map.put("k767", v767); - map.put("k768", v768); - map.put("k769", v769); - map.put("k770", v770); - map.put("k771", v771); - map.put("k772", v772); - map.put("k773", v773); - map.put("k774", v774); - map.put("k775", v775); - map.put("k776", v776); - map.put("k777", v777); - map.put("k778", v778); - map.put("k779", v779); - map.put("k780", v780); - map.put("k781", v781); - map.put("k782", v782); - map.put("k783", v783); - map.put("k784", v784); - map.put("k785", v785); - map.put("k786", v786); - map.put("k787", v787); - map.put("k788", v788); - map.put("k789", v789); - map.put("k790", v790); - map.put("k791", v791); - map.put("k792", v792); - map.put("k793", v793); - map.put("k794", v794); - map.put("k795", v795); - map.put("k796", v796); - map.put("k797", v797); - map.put("k798", v798); - map.put("k799", v799); - map.put("k800", v800); - map.put("k801", v801); - map.put("k802", v802); - map.put("k803", v803); - map.put("k804", v804); - map.put("k805", v805); - map.put("k806", v806); - map.put("k807", v807); - map.put("k808", v808); - map.put("k809", v809); - map.put("k810", v810); - map.put("k811", v811); - map.put("k812", v812); - map.put("k813", v813); - map.put("k814", v814); - map.put("k815", v815); - map.put("k816", v816); - map.put("k817", v817); - map.put("k818", v818); - map.put("k819", v819); - map.put("k820", v820); - map.put("k821", v821); - map.put("k822", v822); - map.put("k823", v823); - map.put("k824", v824); - map.put("k825", v825); - map.put("k826", v826); - map.put("k827", v827); - map.put("k828", v828); - map.put("k829", v829); - map.put("k830", v830); - map.put("k831", v831); - map.put("k832", v832); - map.put("k833", v833); - map.put("k834", v834); - map.put("k835", v835); - map.put("k836", v836); - map.put("k837", v837); - map.put("k838", v838); - map.put("k839", v839); - map.put("k840", v840); - map.put("k841", v841); - map.put("k842", v842); - map.put("k843", v843); - map.put("k844", v844); - map.put("k845", v845); - map.put("k846", v846); - map.put("k847", v847); - map.put("k848", v848); - map.put("k849", v849); - map.put("k850", v850); - map.put("k851", v851); - map.put("k852", v852); - map.put("k853", v853); - map.put("k854", v854); - map.put("k855", v855); - map.put("k856", v856); - map.put("k857", v857); - map.put("k858", v858); - map.put("k859", v859); - map.put("k860", v860); - map.put("k861", v861); - map.put("k862", v862); - map.put("k863", v863); - map.put("k864", v864); - map.put("k865", v865); - map.put("k866", v866); - map.put("k867", v867); - map.put("k868", v868); - map.put("k869", v869); - map.put("k870", v870); - map.put("k871", v871); - map.put("k872", v872); - map.put("k873", v873); - map.put("k874", v874); - map.put("k875", v875); - map.put("k876", v876); - map.put("k877", v877); - map.put("k878", v878); - map.put("k879", v879); - map.put("k880", v880); - map.put("k881", v881); - map.put("k882", v882); - map.put("k883", v883); - map.put("k884", v884); - map.put("k885", v885); - map.put("k886", v886); - map.put("k887", v887); - map.put("k888", v888); - map.put("k889", v889); - map.put("k890", v890); - map.put("k891", v891); - map.put("k892", v892); - map.put("k893", v893); - map.put("k894", v894); - map.put("k895", v895); - map.put("k896", v896); - map.put("k897", v897); - map.put("k898", v898); - map.put("k899", v899); - map.put("k900", v900); - map.put("k901", v901); - map.put("k902", v902); - map.put("k903", v903); - map.put("k904", v904); - map.put("k905", v905); - map.put("k906", v906); - map.put("k907", v907); - map.put("k908", v908); - map.put("k909", v909); - map.put("k910", v910); - map.put("k911", v911); - map.put("k912", v912); - map.put("k913", v913); - map.put("k914", v914); - map.put("k915", v915); - map.put("k916", v916); - map.put("k917", v917); - map.put("k918", v918); - map.put("k919", v919); - map.put("k920", v920); - map.put("k921", v921); - map.put("k922", v922); - map.put("k923", v923); - map.put("k924", v924); - map.put("k925", v925); - map.put("k926", v926); - map.put("k927", v927); - map.put("k928", v928); - map.put("k929", v929); - map.put("k930", v930); - map.put("k931", v931); - map.put("k932", v932); - map.put("k933", v933); - map.put("k934", v934); - map.put("k935", v935); - map.put("k936", v936); - map.put("k937", v937); - map.put("k938", v938); - map.put("k939", v939); - map.put("k940", v940); - map.put("k941", v941); - map.put("k942", v942); - map.put("k943", v943); - map.put("k944", v944); - map.put("k945", v945); - map.put("k946", v946); - map.put("k947", v947); - map.put("k948", v948); - map.put("k949", v949); - map.put("k950", v950); - map.put("k951", v951); - map.put("k952", v952); - map.put("k953", v953); - map.put("k954", v954); - map.put("k955", v955); - map.put("k956", v956); - map.put("k957", v957); - map.put("k958", v958); - map.put("k959", v959); - map.put("k960", v960); - map.put("k961", v961); - map.put("k962", v962); - map.put("k963", v963); - map.put("k964", v964); - map.put("k965", v965); - map.put("k966", v966); - map.put("k967", v967); - map.put("k968", v968); - map.put("k969", v969); - map.put("k970", v970); - map.put("k971", v971); - map.put("k972", v972); - map.put("k973", v973); - map.put("k974", v974); - map.put("k975", v975); - map.put("k976", v976); - map.put("k977", v977); - map.put("k978", v978); - map.put("k979", v979); - map.put("k980", v980); - map.put("k981", v981); - map.put("k982", v982); - map.put("k983", v983); - map.put("k984", v984); - map.put("k985", v985); - map.put("k986", v986); - map.put("k987", v987); - map.put("k988", v988); - map.put("k989", v989); - map.put("k990", v990); - map.put("k991", v991); - map.put("k992", v992); - map.put("k993", v993); - map.put("k994", v994); - map.put("k995", v995); - map.put("k996", v996); - map.put("k997", v997); - map.put("k998", v998); - map.put("k999", v999); - } + Issue1438b() { + Map map = new HashMap<>(); + map.put("k0", v0); + map.put("k1", v1); + map.put("k2", v2); + map.put("k3", v3); + map.put("k4", v4); + map.put("k5", v5); + map.put("k6", v6); + map.put("k7", v7); + map.put("k8", v8); + map.put("k9", v9); + map.put("k10", v10); + map.put("k11", v11); + map.put("k12", v12); + map.put("k13", v13); + map.put("k14", v14); + map.put("k15", v15); + map.put("k16", v16); + map.put("k17", v17); + map.put("k18", v18); + map.put("k19", v19); + map.put("k20", v20); + map.put("k21", v21); + map.put("k22", v22); + map.put("k23", v23); + map.put("k24", v24); + map.put("k25", v25); + map.put("k26", v26); + map.put("k27", v27); + map.put("k28", v28); + map.put("k29", v29); + map.put("k30", v30); + map.put("k31", v31); + map.put("k32", v32); + map.put("k33", v33); + map.put("k34", v34); + map.put("k35", v35); + map.put("k36", v36); + map.put("k37", v37); + map.put("k38", v38); + map.put("k39", v39); + map.put("k40", v40); + map.put("k41", v41); + map.put("k42", v42); + map.put("k43", v43); + map.put("k44", v44); + map.put("k45", v45); + map.put("k46", v46); + map.put("k47", v47); + map.put("k48", v48); + map.put("k49", v49); + map.put("k50", v50); + map.put("k51", v51); + map.put("k52", v52); + map.put("k53", v53); + map.put("k54", v54); + map.put("k55", v55); + map.put("k56", v56); + map.put("k57", v57); + map.put("k58", v58); + map.put("k59", v59); + map.put("k60", v60); + map.put("k61", v61); + map.put("k62", v62); + map.put("k63", v63); + map.put("k64", v64); + map.put("k65", v65); + map.put("k66", v66); + map.put("k67", v67); + map.put("k68", v68); + map.put("k69", v69); + map.put("k70", v70); + map.put("k71", v71); + map.put("k72", v72); + map.put("k73", v73); + map.put("k74", v74); + map.put("k75", v75); + map.put("k76", v76); + map.put("k77", v77); + map.put("k78", v78); + map.put("k79", v79); + map.put("k80", v80); + map.put("k81", v81); + map.put("k82", v82); + map.put("k83", v83); + map.put("k84", v84); + map.put("k85", v85); + map.put("k86", v86); + map.put("k87", v87); + map.put("k88", v88); + map.put("k89", v89); + map.put("k90", v90); + map.put("k91", v91); + map.put("k92", v92); + map.put("k93", v93); + map.put("k94", v94); + map.put("k95", v95); + map.put("k96", v96); + map.put("k97", v97); + map.put("k98", v98); + map.put("k99", v99); + map.put("k100", v100); + map.put("k101", v101); + map.put("k102", v102); + map.put("k103", v103); + map.put("k104", v104); + map.put("k105", v105); + map.put("k106", v106); + map.put("k107", v107); + map.put("k108", v108); + map.put("k109", v109); + map.put("k110", v110); + map.put("k111", v111); + map.put("k112", v112); + map.put("k113", v113); + map.put("k114", v114); + map.put("k115", v115); + map.put("k116", v116); + map.put("k117", v117); + map.put("k118", v118); + map.put("k119", v119); + map.put("k120", v120); + map.put("k121", v121); + map.put("k122", v122); + map.put("k123", v123); + map.put("k124", v124); + map.put("k125", v125); + map.put("k126", v126); + map.put("k127", v127); + map.put("k128", v128); + map.put("k129", v129); + map.put("k130", v130); + map.put("k131", v131); + map.put("k132", v132); + map.put("k133", v133); + map.put("k134", v134); + map.put("k135", v135); + map.put("k136", v136); + map.put("k137", v137); + map.put("k138", v138); + map.put("k139", v139); + map.put("k140", v140); + map.put("k141", v141); + map.put("k142", v142); + map.put("k143", v143); + map.put("k144", v144); + map.put("k145", v145); + map.put("k146", v146); + map.put("k147", v147); + map.put("k148", v148); + map.put("k149", v149); + map.put("k150", v150); + map.put("k151", v151); + map.put("k152", v152); + map.put("k153", v153); + map.put("k154", v154); + map.put("k155", v155); + map.put("k156", v156); + map.put("k157", v157); + map.put("k158", v158); + map.put("k159", v159); + map.put("k160", v160); + map.put("k161", v161); + map.put("k162", v162); + map.put("k163", v163); + map.put("k164", v164); + map.put("k165", v165); + map.put("k166", v166); + map.put("k167", v167); + map.put("k168", v168); + map.put("k169", v169); + map.put("k170", v170); + map.put("k171", v171); + map.put("k172", v172); + map.put("k173", v173); + map.put("k174", v174); + map.put("k175", v175); + map.put("k176", v176); + map.put("k177", v177); + map.put("k178", v178); + map.put("k179", v179); + map.put("k180", v180); + map.put("k181", v181); + map.put("k182", v182); + map.put("k183", v183); + map.put("k184", v184); + map.put("k185", v185); + map.put("k186", v186); + map.put("k187", v187); + map.put("k188", v188); + map.put("k189", v189); + map.put("k190", v190); + map.put("k191", v191); + map.put("k192", v192); + map.put("k193", v193); + map.put("k194", v194); + map.put("k195", v195); + map.put("k196", v196); + map.put("k197", v197); + map.put("k198", v198); + map.put("k199", v199); + map.put("k200", v200); + map.put("k201", v201); + map.put("k202", v202); + map.put("k203", v203); + map.put("k204", v204); + map.put("k205", v205); + map.put("k206", v206); + map.put("k207", v207); + map.put("k208", v208); + map.put("k209", v209); + map.put("k210", v210); + map.put("k211", v211); + map.put("k212", v212); + map.put("k213", v213); + map.put("k214", v214); + map.put("k215", v215); + map.put("k216", v216); + map.put("k217", v217); + map.put("k218", v218); + map.put("k219", v219); + map.put("k220", v220); + map.put("k221", v221); + map.put("k222", v222); + map.put("k223", v223); + map.put("k224", v224); + map.put("k225", v225); + map.put("k226", v226); + map.put("k227", v227); + map.put("k228", v228); + map.put("k229", v229); + map.put("k230", v230); + map.put("k231", v231); + map.put("k232", v232); + map.put("k233", v233); + map.put("k234", v234); + map.put("k235", v235); + map.put("k236", v236); + map.put("k237", v237); + map.put("k238", v238); + map.put("k239", v239); + map.put("k240", v240); + map.put("k241", v241); + map.put("k242", v242); + map.put("k243", v243); + map.put("k244", v244); + map.put("k245", v245); + map.put("k246", v246); + map.put("k247", v247); + map.put("k248", v248); + map.put("k249", v249); + map.put("k250", v250); + map.put("k251", v251); + map.put("k252", v252); + map.put("k253", v253); + map.put("k254", v254); + map.put("k255", v255); + map.put("k256", v256); + map.put("k257", v257); + map.put("k258", v258); + map.put("k259", v259); + map.put("k260", v260); + map.put("k261", v261); + map.put("k262", v262); + map.put("k263", v263); + map.put("k264", v264); + map.put("k265", v265); + map.put("k266", v266); + map.put("k267", v267); + map.put("k268", v268); + map.put("k269", v269); + map.put("k270", v270); + map.put("k271", v271); + map.put("k272", v272); + map.put("k273", v273); + map.put("k274", v274); + map.put("k275", v275); + map.put("k276", v276); + map.put("k277", v277); + map.put("k278", v278); + map.put("k279", v279); + map.put("k280", v280); + map.put("k281", v281); + map.put("k282", v282); + map.put("k283", v283); + map.put("k284", v284); + map.put("k285", v285); + map.put("k286", v286); + map.put("k287", v287); + map.put("k288", v288); + map.put("k289", v289); + map.put("k290", v290); + map.put("k291", v291); + map.put("k292", v292); + map.put("k293", v293); + map.put("k294", v294); + map.put("k295", v295); + map.put("k296", v296); + map.put("k297", v297); + map.put("k298", v298); + map.put("k299", v299); + map.put("k300", v300); + map.put("k301", v301); + map.put("k302", v302); + map.put("k303", v303); + map.put("k304", v304); + map.put("k305", v305); + map.put("k306", v306); + map.put("k307", v307); + map.put("k308", v308); + map.put("k309", v309); + map.put("k310", v310); + map.put("k311", v311); + map.put("k312", v312); + map.put("k313", v313); + map.put("k314", v314); + map.put("k315", v315); + map.put("k316", v316); + map.put("k317", v317); + map.put("k318", v318); + map.put("k319", v319); + map.put("k320", v320); + map.put("k321", v321); + map.put("k322", v322); + map.put("k323", v323); + map.put("k324", v324); + map.put("k325", v325); + map.put("k326", v326); + map.put("k327", v327); + map.put("k328", v328); + map.put("k329", v329); + map.put("k330", v330); + map.put("k331", v331); + map.put("k332", v332); + map.put("k333", v333); + map.put("k334", v334); + map.put("k335", v335); + map.put("k336", v336); + map.put("k337", v337); + map.put("k338", v338); + map.put("k339", v339); + map.put("k340", v340); + map.put("k341", v341); + map.put("k342", v342); + map.put("k343", v343); + map.put("k344", v344); + map.put("k345", v345); + map.put("k346", v346); + map.put("k347", v347); + map.put("k348", v348); + map.put("k349", v349); + map.put("k350", v350); + map.put("k351", v351); + map.put("k352", v352); + map.put("k353", v353); + map.put("k354", v354); + map.put("k355", v355); + map.put("k356", v356); + map.put("k357", v357); + map.put("k358", v358); + map.put("k359", v359); + map.put("k360", v360); + map.put("k361", v361); + map.put("k362", v362); + map.put("k363", v363); + map.put("k364", v364); + map.put("k365", v365); + map.put("k366", v366); + map.put("k367", v367); + map.put("k368", v368); + map.put("k369", v369); + map.put("k370", v370); + map.put("k371", v371); + map.put("k372", v372); + map.put("k373", v373); + map.put("k374", v374); + map.put("k375", v375); + map.put("k376", v376); + map.put("k377", v377); + map.put("k378", v378); + map.put("k379", v379); + map.put("k380", v380); + map.put("k381", v381); + map.put("k382", v382); + map.put("k383", v383); + map.put("k384", v384); + map.put("k385", v385); + map.put("k386", v386); + map.put("k387", v387); + map.put("k388", v388); + map.put("k389", v389); + map.put("k390", v390); + map.put("k391", v391); + map.put("k392", v392); + map.put("k393", v393); + map.put("k394", v394); + map.put("k395", v395); + map.put("k396", v396); + map.put("k397", v397); + map.put("k398", v398); + map.put("k399", v399); + map.put("k400", v400); + map.put("k401", v401); + map.put("k402", v402); + map.put("k403", v403); + map.put("k404", v404); + map.put("k405", v405); + map.put("k406", v406); + map.put("k407", v407); + map.put("k408", v408); + map.put("k409", v409); + map.put("k410", v410); + map.put("k411", v411); + map.put("k412", v412); + map.put("k413", v413); + map.put("k414", v414); + map.put("k415", v415); + map.put("k416", v416); + map.put("k417", v417); + map.put("k418", v418); + map.put("k419", v419); + map.put("k420", v420); + map.put("k421", v421); + map.put("k422", v422); + map.put("k423", v423); + map.put("k424", v424); + map.put("k425", v425); + map.put("k426", v426); + map.put("k427", v427); + map.put("k428", v428); + map.put("k429", v429); + map.put("k430", v430); + map.put("k431", v431); + map.put("k432", v432); + map.put("k433", v433); + map.put("k434", v434); + map.put("k435", v435); + map.put("k436", v436); + map.put("k437", v437); + map.put("k438", v438); + map.put("k439", v439); + map.put("k440", v440); + map.put("k441", v441); + map.put("k442", v442); + map.put("k443", v443); + map.put("k444", v444); + map.put("k445", v445); + map.put("k446", v446); + map.put("k447", v447); + map.put("k448", v448); + map.put("k449", v449); + map.put("k450", v450); + map.put("k451", v451); + map.put("k452", v452); + map.put("k453", v453); + map.put("k454", v454); + map.put("k455", v455); + map.put("k456", v456); + map.put("k457", v457); + map.put("k458", v458); + map.put("k459", v459); + map.put("k460", v460); + map.put("k461", v461); + map.put("k462", v462); + map.put("k463", v463); + map.put("k464", v464); + map.put("k465", v465); + map.put("k466", v466); + map.put("k467", v467); + map.put("k468", v468); + map.put("k469", v469); + map.put("k470", v470); + map.put("k471", v471); + map.put("k472", v472); + map.put("k473", v473); + map.put("k474", v474); + map.put("k475", v475); + map.put("k476", v476); + map.put("k477", v477); + map.put("k478", v478); + map.put("k479", v479); + map.put("k480", v480); + map.put("k481", v481); + map.put("k482", v482); + map.put("k483", v483); + map.put("k484", v484); + map.put("k485", v485); + map.put("k486", v486); + map.put("k487", v487); + map.put("k488", v488); + map.put("k489", v489); + map.put("k490", v490); + map.put("k491", v491); + map.put("k492", v492); + map.put("k493", v493); + map.put("k494", v494); + map.put("k495", v495); + map.put("k496", v496); + map.put("k497", v497); + map.put("k498", v498); + map.put("k499", v499); + map.put("k500", v500); + map.put("k501", v501); + map.put("k502", v502); + map.put("k503", v503); + map.put("k504", v504); + map.put("k505", v505); + map.put("k506", v506); + map.put("k507", v507); + map.put("k508", v508); + map.put("k509", v509); + map.put("k510", v510); + map.put("k511", v511); + map.put("k512", v512); + map.put("k513", v513); + map.put("k514", v514); + map.put("k515", v515); + map.put("k516", v516); + map.put("k517", v517); + map.put("k518", v518); + map.put("k519", v519); + map.put("k520", v520); + map.put("k521", v521); + map.put("k522", v522); + map.put("k523", v523); + map.put("k524", v524); + map.put("k525", v525); + map.put("k526", v526); + map.put("k527", v527); + map.put("k528", v528); + map.put("k529", v529); + map.put("k530", v530); + map.put("k531", v531); + map.put("k532", v532); + map.put("k533", v533); + map.put("k534", v534); + map.put("k535", v535); + map.put("k536", v536); + map.put("k537", v537); + map.put("k538", v538); + map.put("k539", v539); + map.put("k540", v540); + map.put("k541", v541); + map.put("k542", v542); + map.put("k543", v543); + map.put("k544", v544); + map.put("k545", v545); + map.put("k546", v546); + map.put("k547", v547); + map.put("k548", v548); + map.put("k549", v549); + map.put("k550", v550); + map.put("k551", v551); + map.put("k552", v552); + map.put("k553", v553); + map.put("k554", v554); + map.put("k555", v555); + map.put("k556", v556); + map.put("k557", v557); + map.put("k558", v558); + map.put("k559", v559); + map.put("k560", v560); + map.put("k561", v561); + map.put("k562", v562); + map.put("k563", v563); + map.put("k564", v564); + map.put("k565", v565); + map.put("k566", v566); + map.put("k567", v567); + map.put("k568", v568); + map.put("k569", v569); + map.put("k570", v570); + map.put("k571", v571); + map.put("k572", v572); + map.put("k573", v573); + map.put("k574", v574); + map.put("k575", v575); + map.put("k576", v576); + map.put("k577", v577); + map.put("k578", v578); + map.put("k579", v579); + map.put("k580", v580); + map.put("k581", v581); + map.put("k582", v582); + map.put("k583", v583); + map.put("k584", v584); + map.put("k585", v585); + map.put("k586", v586); + map.put("k587", v587); + map.put("k588", v588); + map.put("k589", v589); + map.put("k590", v590); + map.put("k591", v591); + map.put("k592", v592); + map.put("k593", v593); + map.put("k594", v594); + map.put("k595", v595); + map.put("k596", v596); + map.put("k597", v597); + map.put("k598", v598); + map.put("k599", v599); + map.put("k600", v600); + map.put("k601", v601); + map.put("k602", v602); + map.put("k603", v603); + map.put("k604", v604); + map.put("k605", v605); + map.put("k606", v606); + map.put("k607", v607); + map.put("k608", v608); + map.put("k609", v609); + map.put("k610", v610); + map.put("k611", v611); + map.put("k612", v612); + map.put("k613", v613); + map.put("k614", v614); + map.put("k615", v615); + map.put("k616", v616); + map.put("k617", v617); + map.put("k618", v618); + map.put("k619", v619); + map.put("k620", v620); + map.put("k621", v621); + map.put("k622", v622); + map.put("k623", v623); + map.put("k624", v624); + map.put("k625", v625); + map.put("k626", v626); + map.put("k627", v627); + map.put("k628", v628); + map.put("k629", v629); + map.put("k630", v630); + map.put("k631", v631); + map.put("k632", v632); + map.put("k633", v633); + map.put("k634", v634); + map.put("k635", v635); + map.put("k636", v636); + map.put("k637", v637); + map.put("k638", v638); + map.put("k639", v639); + map.put("k640", v640); + map.put("k641", v641); + map.put("k642", v642); + map.put("k643", v643); + map.put("k644", v644); + map.put("k645", v645); + map.put("k646", v646); + map.put("k647", v647); + map.put("k648", v648); + map.put("k649", v649); + map.put("k650", v650); + map.put("k651", v651); + map.put("k652", v652); + map.put("k653", v653); + map.put("k654", v654); + map.put("k655", v655); + map.put("k656", v656); + map.put("k657", v657); + map.put("k658", v658); + map.put("k659", v659); + map.put("k660", v660); + map.put("k661", v661); + map.put("k662", v662); + map.put("k663", v663); + map.put("k664", v664); + map.put("k665", v665); + map.put("k666", v666); + map.put("k667", v667); + map.put("k668", v668); + map.put("k669", v669); + map.put("k670", v670); + map.put("k671", v671); + map.put("k672", v672); + map.put("k673", v673); + map.put("k674", v674); + map.put("k675", v675); + map.put("k676", v676); + map.put("k677", v677); + map.put("k678", v678); + map.put("k679", v679); + map.put("k680", v680); + map.put("k681", v681); + map.put("k682", v682); + map.put("k683", v683); + map.put("k684", v684); + map.put("k685", v685); + map.put("k686", v686); + map.put("k687", v687); + map.put("k688", v688); + map.put("k689", v689); + map.put("k690", v690); + map.put("k691", v691); + map.put("k692", v692); + map.put("k693", v693); + map.put("k694", v694); + map.put("k695", v695); + map.put("k696", v696); + map.put("k697", v697); + map.put("k698", v698); + map.put("k699", v699); + map.put("k700", v700); + map.put("k701", v701); + map.put("k702", v702); + map.put("k703", v703); + map.put("k704", v704); + map.put("k705", v705); + map.put("k706", v706); + map.put("k707", v707); + map.put("k708", v708); + map.put("k709", v709); + map.put("k710", v710); + map.put("k711", v711); + map.put("k712", v712); + map.put("k713", v713); + map.put("k714", v714); + map.put("k715", v715); + map.put("k716", v716); + map.put("k717", v717); + map.put("k718", v718); + map.put("k719", v719); + map.put("k720", v720); + map.put("k721", v721); + map.put("k722", v722); + map.put("k723", v723); + map.put("k724", v724); + map.put("k725", v725); + map.put("k726", v726); + map.put("k727", v727); + map.put("k728", v728); + map.put("k729", v729); + map.put("k730", v730); + map.put("k731", v731); + map.put("k732", v732); + map.put("k733", v733); + map.put("k734", v734); + map.put("k735", v735); + map.put("k736", v736); + map.put("k737", v737); + map.put("k738", v738); + map.put("k739", v739); + map.put("k740", v740); + map.put("k741", v741); + map.put("k742", v742); + map.put("k743", v743); + map.put("k744", v744); + map.put("k745", v745); + map.put("k746", v746); + map.put("k747", v747); + map.put("k748", v748); + map.put("k749", v749); + map.put("k750", v750); + map.put("k751", v751); + map.put("k752", v752); + map.put("k753", v753); + map.put("k754", v754); + map.put("k755", v755); + map.put("k756", v756); + map.put("k757", v757); + map.put("k758", v758); + map.put("k759", v759); + map.put("k760", v760); + map.put("k761", v761); + map.put("k762", v762); + map.put("k763", v763); + map.put("k764", v764); + map.put("k765", v765); + map.put("k766", v766); + map.put("k767", v767); + map.put("k768", v768); + map.put("k769", v769); + map.put("k770", v770); + map.put("k771", v771); + map.put("k772", v772); + map.put("k773", v773); + map.put("k774", v774); + map.put("k775", v775); + map.put("k776", v776); + map.put("k777", v777); + map.put("k778", v778); + map.put("k779", v779); + map.put("k780", v780); + map.put("k781", v781); + map.put("k782", v782); + map.put("k783", v783); + map.put("k784", v784); + map.put("k785", v785); + map.put("k786", v786); + map.put("k787", v787); + map.put("k788", v788); + map.put("k789", v789); + map.put("k790", v790); + map.put("k791", v791); + map.put("k792", v792); + map.put("k793", v793); + map.put("k794", v794); + map.put("k795", v795); + map.put("k796", v796); + map.put("k797", v797); + map.put("k798", v798); + map.put("k799", v799); + map.put("k800", v800); + map.put("k801", v801); + map.put("k802", v802); + map.put("k803", v803); + map.put("k804", v804); + map.put("k805", v805); + map.put("k806", v806); + map.put("k807", v807); + map.put("k808", v808); + map.put("k809", v809); + map.put("k810", v810); + map.put("k811", v811); + map.put("k812", v812); + map.put("k813", v813); + map.put("k814", v814); + map.put("k815", v815); + map.put("k816", v816); + map.put("k817", v817); + map.put("k818", v818); + map.put("k819", v819); + map.put("k820", v820); + map.put("k821", v821); + map.put("k822", v822); + map.put("k823", v823); + map.put("k824", v824); + map.put("k825", v825); + map.put("k826", v826); + map.put("k827", v827); + map.put("k828", v828); + map.put("k829", v829); + map.put("k830", v830); + map.put("k831", v831); + map.put("k832", v832); + map.put("k833", v833); + map.put("k834", v834); + map.put("k835", v835); + map.put("k836", v836); + map.put("k837", v837); + map.put("k838", v838); + map.put("k839", v839); + map.put("k840", v840); + map.put("k841", v841); + map.put("k842", v842); + map.put("k843", v843); + map.put("k844", v844); + map.put("k845", v845); + map.put("k846", v846); + map.put("k847", v847); + map.put("k848", v848); + map.put("k849", v849); + map.put("k850", v850); + map.put("k851", v851); + map.put("k852", v852); + map.put("k853", v853); + map.put("k854", v854); + map.put("k855", v855); + map.put("k856", v856); + map.put("k857", v857); + map.put("k858", v858); + map.put("k859", v859); + map.put("k860", v860); + map.put("k861", v861); + map.put("k862", v862); + map.put("k863", v863); + map.put("k864", v864); + map.put("k865", v865); + map.put("k866", v866); + map.put("k867", v867); + map.put("k868", v868); + map.put("k869", v869); + map.put("k870", v870); + map.put("k871", v871); + map.put("k872", v872); + map.put("k873", v873); + map.put("k874", v874); + map.put("k875", v875); + map.put("k876", v876); + map.put("k877", v877); + map.put("k878", v878); + map.put("k879", v879); + map.put("k880", v880); + map.put("k881", v881); + map.put("k882", v882); + map.put("k883", v883); + map.put("k884", v884); + map.put("k885", v885); + map.put("k886", v886); + map.put("k887", v887); + map.put("k888", v888); + map.put("k889", v889); + map.put("k890", v890); + map.put("k891", v891); + map.put("k892", v892); + map.put("k893", v893); + map.put("k894", v894); + map.put("k895", v895); + map.put("k896", v896); + map.put("k897", v897); + map.put("k898", v898); + map.put("k899", v899); + map.put("k900", v900); + map.put("k901", v901); + map.put("k902", v902); + map.put("k903", v903); + map.put("k904", v904); + map.put("k905", v905); + map.put("k906", v906); + map.put("k907", v907); + map.put("k908", v908); + map.put("k909", v909); + map.put("k910", v910); + map.put("k911", v911); + map.put("k912", v912); + map.put("k913", v913); + map.put("k914", v914); + map.put("k915", v915); + map.put("k916", v916); + map.put("k917", v917); + map.put("k918", v918); + map.put("k919", v919); + map.put("k920", v920); + map.put("k921", v921); + map.put("k922", v922); + map.put("k923", v923); + map.put("k924", v924); + map.put("k925", v925); + map.put("k926", v926); + map.put("k927", v927); + map.put("k928", v928); + map.put("k929", v929); + map.put("k930", v930); + map.put("k931", v931); + map.put("k932", v932); + map.put("k933", v933); + map.put("k934", v934); + map.put("k935", v935); + map.put("k936", v936); + map.put("k937", v937); + map.put("k938", v938); + map.put("k939", v939); + map.put("k940", v940); + map.put("k941", v941); + map.put("k942", v942); + map.put("k943", v943); + map.put("k944", v944); + map.put("k945", v945); + map.put("k946", v946); + map.put("k947", v947); + map.put("k948", v948); + map.put("k949", v949); + map.put("k950", v950); + map.put("k951", v951); + map.put("k952", v952); + map.put("k953", v953); + map.put("k954", v954); + map.put("k955", v955); + map.put("k956", v956); + map.put("k957", v957); + map.put("k958", v958); + map.put("k959", v959); + map.put("k960", v960); + map.put("k961", v961); + map.put("k962", v962); + map.put("k963", v963); + map.put("k964", v964); + map.put("k965", v965); + map.put("k966", v966); + map.put("k967", v967); + map.put("k968", v968); + map.put("k969", v969); + map.put("k970", v970); + map.put("k971", v971); + map.put("k972", v972); + map.put("k973", v973); + map.put("k974", v974); + map.put("k975", v975); + map.put("k976", v976); + map.put("k977", v977); + map.put("k978", v978); + map.put("k979", v979); + map.put("k980", v980); + map.put("k981", v981); + map.put("k982", v982); + map.put("k983", v983); + map.put("k984", v984); + map.put("k985", v985); + map.put("k986", v986); + map.put("k987", v987); + map.put("k988", v988); + map.put("k989", v989); + map.put("k990", v990); + map.put("k991", v991); + map.put("k992", v992); + map.put("k993", v993); + map.put("k994", v994); + map.put("k995", v995); + map.put("k996", v996); + map.put("k997", v997); + map.put("k998", v998); + map.put("k999", v999); + } } diff --git a/checker/jtreg/nullness/Issue1438c.java b/checker/jtreg/nullness/Issue1438c.java index 8ac5ad793fb..07b379d3198 100644 --- a/checker/jtreg/nullness/Issue1438c.java +++ b/checker/jtreg/nullness/Issue1438c.java @@ -9,2010 +9,2010 @@ import java.util.Map; public class Issue1438c { - // Do not initialize these variables. The Nullness Checker is supposed to issue an error about - // uninitialized fields. - Integer v0; - Integer v1; - Integer v2; - Integer v3; - Integer v4; - Integer v5; - Integer v6; - Integer v7; - Integer v8; - Integer v9; - Integer v10; - Integer v11; - Integer v12; - Integer v13; - Integer v14; - Integer v15; - Integer v16; - Integer v17; - Integer v18; - Integer v19; - Integer v20; - Integer v21; - Integer v22; - Integer v23; - Integer v24; - Integer v25; - Integer v26; - Integer v27; - Integer v28; - Integer v29; - Integer v30; - Integer v31; - Integer v32; - Integer v33; - Integer v34; - Integer v35; - Integer v36; - Integer v37; - Integer v38; - Integer v39; - Integer v40; - Integer v41; - Integer v42; - Integer v43; - Integer v44; - Integer v45; - Integer v46; - Integer v47; - Integer v48; - Integer v49; - Integer v50; - Integer v51; - Integer v52; - Integer v53; - Integer v54; - Integer v55; - Integer v56; - Integer v57; - Integer v58; - Integer v59; - Integer v60; - Integer v61; - Integer v62; - Integer v63; - Integer v64; - Integer v65; - Integer v66; - Integer v67; - Integer v68; - Integer v69; - Integer v70; - Integer v71; - Integer v72; - Integer v73; - Integer v74; - Integer v75; - Integer v76; - Integer v77; - Integer v78; - Integer v79; - Integer v80; - Integer v81; - Integer v82; - Integer v83; - Integer v84; - Integer v85; - Integer v86; - Integer v87; - Integer v88; - Integer v89; - Integer v90; - Integer v91; - Integer v92; - Integer v93; - Integer v94; - Integer v95; - Integer v96; - Integer v97; - Integer v98; - Integer v99; - Integer v100; - Integer v101; - Integer v102; - Integer v103; - Integer v104; - Integer v105; - Integer v106; - Integer v107; - Integer v108; - Integer v109; - Integer v110; - Integer v111; - Integer v112; - Integer v113; - Integer v114; - Integer v115; - Integer v116; - Integer v117; - Integer v118; - Integer v119; - Integer v120; - Integer v121; - Integer v122; - Integer v123; - Integer v124; - Integer v125; - Integer v126; - Integer v127; - Integer v128; - Integer v129; - Integer v130; - Integer v131; - Integer v132; - Integer v133; - Integer v134; - Integer v135; - Integer v136; - Integer v137; - Integer v138; - Integer v139; - Integer v140; - Integer v141; - Integer v142; - Integer v143; - Integer v144; - Integer v145; - Integer v146; - Integer v147; - Integer v148; - Integer v149; - Integer v150; - Integer v151; - Integer v152; - Integer v153; - Integer v154; - Integer v155; - Integer v156; - Integer v157; - Integer v158; - Integer v159; - Integer v160; - Integer v161; - Integer v162; - Integer v163; - Integer v164; - Integer v165; - Integer v166; - Integer v167; - Integer v168; - Integer v169; - Integer v170; - Integer v171; - Integer v172; - Integer v173; - Integer v174; - Integer v175; - Integer v176; - Integer v177; - Integer v178; - Integer v179; - Integer v180; - Integer v181; - Integer v182; - Integer v183; - Integer v184; - Integer v185; - Integer v186; - Integer v187; - Integer v188; - Integer v189; - Integer v190; - Integer v191; - Integer v192; - Integer v193; - Integer v194; - Integer v195; - Integer v196; - Integer v197; - Integer v198; - Integer v199; - Integer v200; - Integer v201; - Integer v202; - Integer v203; - Integer v204; - Integer v205; - Integer v206; - Integer v207; - Integer v208; - Integer v209; - Integer v210; - Integer v211; - Integer v212; - Integer v213; - Integer v214; - Integer v215; - Integer v216; - Integer v217; - Integer v218; - Integer v219; - Integer v220; - Integer v221; - Integer v222; - Integer v223; - Integer v224; - Integer v225; - Integer v226; - Integer v227; - Integer v228; - Integer v229; - Integer v230; - Integer v231; - Integer v232; - Integer v233; - Integer v234; - Integer v235; - Integer v236; - Integer v237; - Integer v238; - Integer v239; - Integer v240; - Integer v241; - Integer v242; - Integer v243; - Integer v244; - Integer v245; - Integer v246; - Integer v247; - Integer v248; - Integer v249; - Integer v250; - Integer v251; - Integer v252; - Integer v253; - Integer v254; - Integer v255; - Integer v256; - Integer v257; - Integer v258; - Integer v259; - Integer v260; - Integer v261; - Integer v262; - Integer v263; - Integer v264; - Integer v265; - Integer v266; - Integer v267; - Integer v268; - Integer v269; - Integer v270; - Integer v271; - Integer v272; - Integer v273; - Integer v274; - Integer v275; - Integer v276; - Integer v277; - Integer v278; - Integer v279; - Integer v280; - Integer v281; - Integer v282; - Integer v283; - Integer v284; - Integer v285; - Integer v286; - Integer v287; - Integer v288; - Integer v289; - Integer v290; - Integer v291; - Integer v292; - Integer v293; - Integer v294; - Integer v295; - Integer v296; - Integer v297; - Integer v298; - Integer v299; - Integer v300; - Integer v301; - Integer v302; - Integer v303; - Integer v304; - Integer v305; - Integer v306; - Integer v307; - Integer v308; - Integer v309; - Integer v310; - Integer v311; - Integer v312; - Integer v313; - Integer v314; - Integer v315; - Integer v316; - Integer v317; - Integer v318; - Integer v319; - Integer v320; - Integer v321; - Integer v322; - Integer v323; - Integer v324; - Integer v325; - Integer v326; - Integer v327; - Integer v328; - Integer v329; - Integer v330; - Integer v331; - Integer v332; - Integer v333; - Integer v334; - Integer v335; - Integer v336; - Integer v337; - Integer v338; - Integer v339; - Integer v340; - Integer v341; - Integer v342; - Integer v343; - Integer v344; - Integer v345; - Integer v346; - Integer v347; - Integer v348; - Integer v349; - Integer v350; - Integer v351; - Integer v352; - Integer v353; - Integer v354; - Integer v355; - Integer v356; - Integer v357; - Integer v358; - Integer v359; - Integer v360; - Integer v361; - Integer v362; - Integer v363; - Integer v364; - Integer v365; - Integer v366; - Integer v367; - Integer v368; - Integer v369; - Integer v370; - Integer v371; - Integer v372; - Integer v373; - Integer v374; - Integer v375; - Integer v376; - Integer v377; - Integer v378; - Integer v379; - Integer v380; - Integer v381; - Integer v382; - Integer v383; - Integer v384; - Integer v385; - Integer v386; - Integer v387; - Integer v388; - Integer v389; - Integer v390; - Integer v391; - Integer v392; - Integer v393; - Integer v394; - Integer v395; - Integer v396; - Integer v397; - Integer v398; - Integer v399; - Integer v400; - Integer v401; - Integer v402; - Integer v403; - Integer v404; - Integer v405; - Integer v406; - Integer v407; - Integer v408; - Integer v409; - Integer v410; - Integer v411; - Integer v412; - Integer v413; - Integer v414; - Integer v415; - Integer v416; - Integer v417; - Integer v418; - Integer v419; - Integer v420; - Integer v421; - Integer v422; - Integer v423; - Integer v424; - Integer v425; - Integer v426; - Integer v427; - Integer v428; - Integer v429; - Integer v430; - Integer v431; - Integer v432; - Integer v433; - Integer v434; - Integer v435; - Integer v436; - Integer v437; - Integer v438; - Integer v439; - Integer v440; - Integer v441; - Integer v442; - Integer v443; - Integer v444; - Integer v445; - Integer v446; - Integer v447; - Integer v448; - Integer v449; - Integer v450; - Integer v451; - Integer v452; - Integer v453; - Integer v454; - Integer v455; - Integer v456; - Integer v457; - Integer v458; - Integer v459; - Integer v460; - Integer v461; - Integer v462; - Integer v463; - Integer v464; - Integer v465; - Integer v466; - Integer v467; - Integer v468; - Integer v469; - Integer v470; - Integer v471; - Integer v472; - Integer v473; - Integer v474; - Integer v475; - Integer v476; - Integer v477; - Integer v478; - Integer v479; - Integer v480; - Integer v481; - Integer v482; - Integer v483; - Integer v484; - Integer v485; - Integer v486; - Integer v487; - Integer v488; - Integer v489; - Integer v490; - Integer v491; - Integer v492; - Integer v493; - Integer v494; - Integer v495; - Integer v496; - Integer v497; - Integer v498; - Integer v499; - Integer v500; - Integer v501; - Integer v502; - Integer v503; - Integer v504; - Integer v505; - Integer v506; - Integer v507; - Integer v508; - Integer v509; - Integer v510; - Integer v511; - Integer v512; - Integer v513; - Integer v514; - Integer v515; - Integer v516; - Integer v517; - Integer v518; - Integer v519; - Integer v520; - Integer v521; - Integer v522; - Integer v523; - Integer v524; - Integer v525; - Integer v526; - Integer v527; - Integer v528; - Integer v529; - Integer v530; - Integer v531; - Integer v532; - Integer v533; - Integer v534; - Integer v535; - Integer v536; - Integer v537; - Integer v538; - Integer v539; - Integer v540; - Integer v541; - Integer v542; - Integer v543; - Integer v544; - Integer v545; - Integer v546; - Integer v547; - Integer v548; - Integer v549; - Integer v550; - Integer v551; - Integer v552; - Integer v553; - Integer v554; - Integer v555; - Integer v556; - Integer v557; - Integer v558; - Integer v559; - Integer v560; - Integer v561; - Integer v562; - Integer v563; - Integer v564; - Integer v565; - Integer v566; - Integer v567; - Integer v568; - Integer v569; - Integer v570; - Integer v571; - Integer v572; - Integer v573; - Integer v574; - Integer v575; - Integer v576; - Integer v577; - Integer v578; - Integer v579; - Integer v580; - Integer v581; - Integer v582; - Integer v583; - Integer v584; - Integer v585; - Integer v586; - Integer v587; - Integer v588; - Integer v589; - Integer v590; - Integer v591; - Integer v592; - Integer v593; - Integer v594; - Integer v595; - Integer v596; - Integer v597; - Integer v598; - Integer v599; - Integer v600; - Integer v601; - Integer v602; - Integer v603; - Integer v604; - Integer v605; - Integer v606; - Integer v607; - Integer v608; - Integer v609; - Integer v610; - Integer v611; - Integer v612; - Integer v613; - Integer v614; - Integer v615; - Integer v616; - Integer v617; - Integer v618; - Integer v619; - Integer v620; - Integer v621; - Integer v622; - Integer v623; - Integer v624; - Integer v625; - Integer v626; - Integer v627; - Integer v628; - Integer v629; - Integer v630; - Integer v631; - Integer v632; - Integer v633; - Integer v634; - Integer v635; - Integer v636; - Integer v637; - Integer v638; - Integer v639; - Integer v640; - Integer v641; - Integer v642; - Integer v643; - Integer v644; - Integer v645; - Integer v646; - Integer v647; - Integer v648; - Integer v649; - Integer v650; - Integer v651; - Integer v652; - Integer v653; - Integer v654; - Integer v655; - Integer v656; - Integer v657; - Integer v658; - Integer v659; - Integer v660; - Integer v661; - Integer v662; - Integer v663; - Integer v664; - Integer v665; - Integer v666; - Integer v667; - Integer v668; - Integer v669; - Integer v670; - Integer v671; - Integer v672; - Integer v673; - Integer v674; - Integer v675; - Integer v676; - Integer v677; - Integer v678; - Integer v679; - Integer v680; - Integer v681; - Integer v682; - Integer v683; - Integer v684; - Integer v685; - Integer v686; - Integer v687; - Integer v688; - Integer v689; - Integer v690; - Integer v691; - Integer v692; - Integer v693; - Integer v694; - Integer v695; - Integer v696; - Integer v697; - Integer v698; - Integer v699; - Integer v700; - Integer v701; - Integer v702; - Integer v703; - Integer v704; - Integer v705; - Integer v706; - Integer v707; - Integer v708; - Integer v709; - Integer v710; - Integer v711; - Integer v712; - Integer v713; - Integer v714; - Integer v715; - Integer v716; - Integer v717; - Integer v718; - Integer v719; - Integer v720; - Integer v721; - Integer v722; - Integer v723; - Integer v724; - Integer v725; - Integer v726; - Integer v727; - Integer v728; - Integer v729; - Integer v730; - Integer v731; - Integer v732; - Integer v733; - Integer v734; - Integer v735; - Integer v736; - Integer v737; - Integer v738; - Integer v739; - Integer v740; - Integer v741; - Integer v742; - Integer v743; - Integer v744; - Integer v745; - Integer v746; - Integer v747; - Integer v748; - Integer v749; - Integer v750; - Integer v751; - Integer v752; - Integer v753; - Integer v754; - Integer v755; - Integer v756; - Integer v757; - Integer v758; - Integer v759; - Integer v760; - Integer v761; - Integer v762; - Integer v763; - Integer v764; - Integer v765; - Integer v766; - Integer v767; - Integer v768; - Integer v769; - Integer v770; - Integer v771; - Integer v772; - Integer v773; - Integer v774; - Integer v775; - Integer v776; - Integer v777; - Integer v778; - Integer v779; - Integer v780; - Integer v781; - Integer v782; - Integer v783; - Integer v784; - Integer v785; - Integer v786; - Integer v787; - Integer v788; - Integer v789; - Integer v790; - Integer v791; - Integer v792; - Integer v793; - Integer v794; - Integer v795; - Integer v796; - Integer v797; - Integer v798; - Integer v799; - Integer v800; - Integer v801; - Integer v802; - Integer v803; - Integer v804; - Integer v805; - Integer v806; - Integer v807; - Integer v808; - Integer v809; - Integer v810; - Integer v811; - Integer v812; - Integer v813; - Integer v814; - Integer v815; - Integer v816; - Integer v817; - Integer v818; - Integer v819; - Integer v820; - Integer v821; - Integer v822; - Integer v823; - Integer v824; - Integer v825; - Integer v826; - Integer v827; - Integer v828; - Integer v829; - Integer v830; - Integer v831; - Integer v832; - Integer v833; - Integer v834; - Integer v835; - Integer v836; - Integer v837; - Integer v838; - Integer v839; - Integer v840; - Integer v841; - Integer v842; - Integer v843; - Integer v844; - Integer v845; - Integer v846; - Integer v847; - Integer v848; - Integer v849; - Integer v850; - Integer v851; - Integer v852; - Integer v853; - Integer v854; - Integer v855; - Integer v856; - Integer v857; - Integer v858; - Integer v859; - Integer v860; - Integer v861; - Integer v862; - Integer v863; - Integer v864; - Integer v865; - Integer v866; - Integer v867; - Integer v868; - Integer v869; - Integer v870; - Integer v871; - Integer v872; - Integer v873; - Integer v874; - Integer v875; - Integer v876; - Integer v877; - Integer v878; - Integer v879; - Integer v880; - Integer v881; - Integer v882; - Integer v883; - Integer v884; - Integer v885; - Integer v886; - Integer v887; - Integer v888; - Integer v889; - Integer v890; - Integer v891; - Integer v892; - Integer v893; - Integer v894; - Integer v895; - Integer v896; - Integer v897; - Integer v898; - Integer v899; - Integer v900; - Integer v901; - Integer v902; - Integer v903; - Integer v904; - Integer v905; - Integer v906; - Integer v907; - Integer v908; - Integer v909; - Integer v910; - Integer v911; - Integer v912; - Integer v913; - Integer v914; - Integer v915; - Integer v916; - Integer v917; - Integer v918; - Integer v919; - Integer v920; - Integer v921; - Integer v922; - Integer v923; - Integer v924; - Integer v925; - Integer v926; - Integer v927; - Integer v928; - Integer v929; - Integer v930; - Integer v931; - Integer v932; - Integer v933; - Integer v934; - Integer v935; - Integer v936; - Integer v937; - Integer v938; - Integer v939; - Integer v940; - Integer v941; - Integer v942; - Integer v943; - Integer v944; - Integer v945; - Integer v946; - Integer v947; - Integer v948; - Integer v949; - Integer v950; - Integer v951; - Integer v952; - Integer v953; - Integer v954; - Integer v955; - Integer v956; - Integer v957; - Integer v958; - Integer v959; - Integer v960; - Integer v961; - Integer v962; - Integer v963; - Integer v964; - Integer v965; - Integer v966; - Integer v967; - Integer v968; - Integer v969; - Integer v970; - Integer v971; - Integer v972; - Integer v973; - Integer v974; - Integer v975; - Integer v976; - Integer v977; - Integer v978; - Integer v979; - Integer v980; - Integer v981; - Integer v982; - Integer v983; - Integer v984; - Integer v985; - Integer v986; - Integer v987; - Integer v988; - Integer v989; - Integer v990; - Integer v991; - Integer v992; - Integer v993; - Integer v994; - Integer v995; - Integer v996; - Integer v997; - Integer v998; - Integer v999; + // Do not initialize these variables. The Nullness Checker is supposed to issue an error about + // uninitialized fields. + Integer v0; + Integer v1; + Integer v2; + Integer v3; + Integer v4; + Integer v5; + Integer v6; + Integer v7; + Integer v8; + Integer v9; + Integer v10; + Integer v11; + Integer v12; + Integer v13; + Integer v14; + Integer v15; + Integer v16; + Integer v17; + Integer v18; + Integer v19; + Integer v20; + Integer v21; + Integer v22; + Integer v23; + Integer v24; + Integer v25; + Integer v26; + Integer v27; + Integer v28; + Integer v29; + Integer v30; + Integer v31; + Integer v32; + Integer v33; + Integer v34; + Integer v35; + Integer v36; + Integer v37; + Integer v38; + Integer v39; + Integer v40; + Integer v41; + Integer v42; + Integer v43; + Integer v44; + Integer v45; + Integer v46; + Integer v47; + Integer v48; + Integer v49; + Integer v50; + Integer v51; + Integer v52; + Integer v53; + Integer v54; + Integer v55; + Integer v56; + Integer v57; + Integer v58; + Integer v59; + Integer v60; + Integer v61; + Integer v62; + Integer v63; + Integer v64; + Integer v65; + Integer v66; + Integer v67; + Integer v68; + Integer v69; + Integer v70; + Integer v71; + Integer v72; + Integer v73; + Integer v74; + Integer v75; + Integer v76; + Integer v77; + Integer v78; + Integer v79; + Integer v80; + Integer v81; + Integer v82; + Integer v83; + Integer v84; + Integer v85; + Integer v86; + Integer v87; + Integer v88; + Integer v89; + Integer v90; + Integer v91; + Integer v92; + Integer v93; + Integer v94; + Integer v95; + Integer v96; + Integer v97; + Integer v98; + Integer v99; + Integer v100; + Integer v101; + Integer v102; + Integer v103; + Integer v104; + Integer v105; + Integer v106; + Integer v107; + Integer v108; + Integer v109; + Integer v110; + Integer v111; + Integer v112; + Integer v113; + Integer v114; + Integer v115; + Integer v116; + Integer v117; + Integer v118; + Integer v119; + Integer v120; + Integer v121; + Integer v122; + Integer v123; + Integer v124; + Integer v125; + Integer v126; + Integer v127; + Integer v128; + Integer v129; + Integer v130; + Integer v131; + Integer v132; + Integer v133; + Integer v134; + Integer v135; + Integer v136; + Integer v137; + Integer v138; + Integer v139; + Integer v140; + Integer v141; + Integer v142; + Integer v143; + Integer v144; + Integer v145; + Integer v146; + Integer v147; + Integer v148; + Integer v149; + Integer v150; + Integer v151; + Integer v152; + Integer v153; + Integer v154; + Integer v155; + Integer v156; + Integer v157; + Integer v158; + Integer v159; + Integer v160; + Integer v161; + Integer v162; + Integer v163; + Integer v164; + Integer v165; + Integer v166; + Integer v167; + Integer v168; + Integer v169; + Integer v170; + Integer v171; + Integer v172; + Integer v173; + Integer v174; + Integer v175; + Integer v176; + Integer v177; + Integer v178; + Integer v179; + Integer v180; + Integer v181; + Integer v182; + Integer v183; + Integer v184; + Integer v185; + Integer v186; + Integer v187; + Integer v188; + Integer v189; + Integer v190; + Integer v191; + Integer v192; + Integer v193; + Integer v194; + Integer v195; + Integer v196; + Integer v197; + Integer v198; + Integer v199; + Integer v200; + Integer v201; + Integer v202; + Integer v203; + Integer v204; + Integer v205; + Integer v206; + Integer v207; + Integer v208; + Integer v209; + Integer v210; + Integer v211; + Integer v212; + Integer v213; + Integer v214; + Integer v215; + Integer v216; + Integer v217; + Integer v218; + Integer v219; + Integer v220; + Integer v221; + Integer v222; + Integer v223; + Integer v224; + Integer v225; + Integer v226; + Integer v227; + Integer v228; + Integer v229; + Integer v230; + Integer v231; + Integer v232; + Integer v233; + Integer v234; + Integer v235; + Integer v236; + Integer v237; + Integer v238; + Integer v239; + Integer v240; + Integer v241; + Integer v242; + Integer v243; + Integer v244; + Integer v245; + Integer v246; + Integer v247; + Integer v248; + Integer v249; + Integer v250; + Integer v251; + Integer v252; + Integer v253; + Integer v254; + Integer v255; + Integer v256; + Integer v257; + Integer v258; + Integer v259; + Integer v260; + Integer v261; + Integer v262; + Integer v263; + Integer v264; + Integer v265; + Integer v266; + Integer v267; + Integer v268; + Integer v269; + Integer v270; + Integer v271; + Integer v272; + Integer v273; + Integer v274; + Integer v275; + Integer v276; + Integer v277; + Integer v278; + Integer v279; + Integer v280; + Integer v281; + Integer v282; + Integer v283; + Integer v284; + Integer v285; + Integer v286; + Integer v287; + Integer v288; + Integer v289; + Integer v290; + Integer v291; + Integer v292; + Integer v293; + Integer v294; + Integer v295; + Integer v296; + Integer v297; + Integer v298; + Integer v299; + Integer v300; + Integer v301; + Integer v302; + Integer v303; + Integer v304; + Integer v305; + Integer v306; + Integer v307; + Integer v308; + Integer v309; + Integer v310; + Integer v311; + Integer v312; + Integer v313; + Integer v314; + Integer v315; + Integer v316; + Integer v317; + Integer v318; + Integer v319; + Integer v320; + Integer v321; + Integer v322; + Integer v323; + Integer v324; + Integer v325; + Integer v326; + Integer v327; + Integer v328; + Integer v329; + Integer v330; + Integer v331; + Integer v332; + Integer v333; + Integer v334; + Integer v335; + Integer v336; + Integer v337; + Integer v338; + Integer v339; + Integer v340; + Integer v341; + Integer v342; + Integer v343; + Integer v344; + Integer v345; + Integer v346; + Integer v347; + Integer v348; + Integer v349; + Integer v350; + Integer v351; + Integer v352; + Integer v353; + Integer v354; + Integer v355; + Integer v356; + Integer v357; + Integer v358; + Integer v359; + Integer v360; + Integer v361; + Integer v362; + Integer v363; + Integer v364; + Integer v365; + Integer v366; + Integer v367; + Integer v368; + Integer v369; + Integer v370; + Integer v371; + Integer v372; + Integer v373; + Integer v374; + Integer v375; + Integer v376; + Integer v377; + Integer v378; + Integer v379; + Integer v380; + Integer v381; + Integer v382; + Integer v383; + Integer v384; + Integer v385; + Integer v386; + Integer v387; + Integer v388; + Integer v389; + Integer v390; + Integer v391; + Integer v392; + Integer v393; + Integer v394; + Integer v395; + Integer v396; + Integer v397; + Integer v398; + Integer v399; + Integer v400; + Integer v401; + Integer v402; + Integer v403; + Integer v404; + Integer v405; + Integer v406; + Integer v407; + Integer v408; + Integer v409; + Integer v410; + Integer v411; + Integer v412; + Integer v413; + Integer v414; + Integer v415; + Integer v416; + Integer v417; + Integer v418; + Integer v419; + Integer v420; + Integer v421; + Integer v422; + Integer v423; + Integer v424; + Integer v425; + Integer v426; + Integer v427; + Integer v428; + Integer v429; + Integer v430; + Integer v431; + Integer v432; + Integer v433; + Integer v434; + Integer v435; + Integer v436; + Integer v437; + Integer v438; + Integer v439; + Integer v440; + Integer v441; + Integer v442; + Integer v443; + Integer v444; + Integer v445; + Integer v446; + Integer v447; + Integer v448; + Integer v449; + Integer v450; + Integer v451; + Integer v452; + Integer v453; + Integer v454; + Integer v455; + Integer v456; + Integer v457; + Integer v458; + Integer v459; + Integer v460; + Integer v461; + Integer v462; + Integer v463; + Integer v464; + Integer v465; + Integer v466; + Integer v467; + Integer v468; + Integer v469; + Integer v470; + Integer v471; + Integer v472; + Integer v473; + Integer v474; + Integer v475; + Integer v476; + Integer v477; + Integer v478; + Integer v479; + Integer v480; + Integer v481; + Integer v482; + Integer v483; + Integer v484; + Integer v485; + Integer v486; + Integer v487; + Integer v488; + Integer v489; + Integer v490; + Integer v491; + Integer v492; + Integer v493; + Integer v494; + Integer v495; + Integer v496; + Integer v497; + Integer v498; + Integer v499; + Integer v500; + Integer v501; + Integer v502; + Integer v503; + Integer v504; + Integer v505; + Integer v506; + Integer v507; + Integer v508; + Integer v509; + Integer v510; + Integer v511; + Integer v512; + Integer v513; + Integer v514; + Integer v515; + Integer v516; + Integer v517; + Integer v518; + Integer v519; + Integer v520; + Integer v521; + Integer v522; + Integer v523; + Integer v524; + Integer v525; + Integer v526; + Integer v527; + Integer v528; + Integer v529; + Integer v530; + Integer v531; + Integer v532; + Integer v533; + Integer v534; + Integer v535; + Integer v536; + Integer v537; + Integer v538; + Integer v539; + Integer v540; + Integer v541; + Integer v542; + Integer v543; + Integer v544; + Integer v545; + Integer v546; + Integer v547; + Integer v548; + Integer v549; + Integer v550; + Integer v551; + Integer v552; + Integer v553; + Integer v554; + Integer v555; + Integer v556; + Integer v557; + Integer v558; + Integer v559; + Integer v560; + Integer v561; + Integer v562; + Integer v563; + Integer v564; + Integer v565; + Integer v566; + Integer v567; + Integer v568; + Integer v569; + Integer v570; + Integer v571; + Integer v572; + Integer v573; + Integer v574; + Integer v575; + Integer v576; + Integer v577; + Integer v578; + Integer v579; + Integer v580; + Integer v581; + Integer v582; + Integer v583; + Integer v584; + Integer v585; + Integer v586; + Integer v587; + Integer v588; + Integer v589; + Integer v590; + Integer v591; + Integer v592; + Integer v593; + Integer v594; + Integer v595; + Integer v596; + Integer v597; + Integer v598; + Integer v599; + Integer v600; + Integer v601; + Integer v602; + Integer v603; + Integer v604; + Integer v605; + Integer v606; + Integer v607; + Integer v608; + Integer v609; + Integer v610; + Integer v611; + Integer v612; + Integer v613; + Integer v614; + Integer v615; + Integer v616; + Integer v617; + Integer v618; + Integer v619; + Integer v620; + Integer v621; + Integer v622; + Integer v623; + Integer v624; + Integer v625; + Integer v626; + Integer v627; + Integer v628; + Integer v629; + Integer v630; + Integer v631; + Integer v632; + Integer v633; + Integer v634; + Integer v635; + Integer v636; + Integer v637; + Integer v638; + Integer v639; + Integer v640; + Integer v641; + Integer v642; + Integer v643; + Integer v644; + Integer v645; + Integer v646; + Integer v647; + Integer v648; + Integer v649; + Integer v650; + Integer v651; + Integer v652; + Integer v653; + Integer v654; + Integer v655; + Integer v656; + Integer v657; + Integer v658; + Integer v659; + Integer v660; + Integer v661; + Integer v662; + Integer v663; + Integer v664; + Integer v665; + Integer v666; + Integer v667; + Integer v668; + Integer v669; + Integer v670; + Integer v671; + Integer v672; + Integer v673; + Integer v674; + Integer v675; + Integer v676; + Integer v677; + Integer v678; + Integer v679; + Integer v680; + Integer v681; + Integer v682; + Integer v683; + Integer v684; + Integer v685; + Integer v686; + Integer v687; + Integer v688; + Integer v689; + Integer v690; + Integer v691; + Integer v692; + Integer v693; + Integer v694; + Integer v695; + Integer v696; + Integer v697; + Integer v698; + Integer v699; + Integer v700; + Integer v701; + Integer v702; + Integer v703; + Integer v704; + Integer v705; + Integer v706; + Integer v707; + Integer v708; + Integer v709; + Integer v710; + Integer v711; + Integer v712; + Integer v713; + Integer v714; + Integer v715; + Integer v716; + Integer v717; + Integer v718; + Integer v719; + Integer v720; + Integer v721; + Integer v722; + Integer v723; + Integer v724; + Integer v725; + Integer v726; + Integer v727; + Integer v728; + Integer v729; + Integer v730; + Integer v731; + Integer v732; + Integer v733; + Integer v734; + Integer v735; + Integer v736; + Integer v737; + Integer v738; + Integer v739; + Integer v740; + Integer v741; + Integer v742; + Integer v743; + Integer v744; + Integer v745; + Integer v746; + Integer v747; + Integer v748; + Integer v749; + Integer v750; + Integer v751; + Integer v752; + Integer v753; + Integer v754; + Integer v755; + Integer v756; + Integer v757; + Integer v758; + Integer v759; + Integer v760; + Integer v761; + Integer v762; + Integer v763; + Integer v764; + Integer v765; + Integer v766; + Integer v767; + Integer v768; + Integer v769; + Integer v770; + Integer v771; + Integer v772; + Integer v773; + Integer v774; + Integer v775; + Integer v776; + Integer v777; + Integer v778; + Integer v779; + Integer v780; + Integer v781; + Integer v782; + Integer v783; + Integer v784; + Integer v785; + Integer v786; + Integer v787; + Integer v788; + Integer v789; + Integer v790; + Integer v791; + Integer v792; + Integer v793; + Integer v794; + Integer v795; + Integer v796; + Integer v797; + Integer v798; + Integer v799; + Integer v800; + Integer v801; + Integer v802; + Integer v803; + Integer v804; + Integer v805; + Integer v806; + Integer v807; + Integer v808; + Integer v809; + Integer v810; + Integer v811; + Integer v812; + Integer v813; + Integer v814; + Integer v815; + Integer v816; + Integer v817; + Integer v818; + Integer v819; + Integer v820; + Integer v821; + Integer v822; + Integer v823; + Integer v824; + Integer v825; + Integer v826; + Integer v827; + Integer v828; + Integer v829; + Integer v830; + Integer v831; + Integer v832; + Integer v833; + Integer v834; + Integer v835; + Integer v836; + Integer v837; + Integer v838; + Integer v839; + Integer v840; + Integer v841; + Integer v842; + Integer v843; + Integer v844; + Integer v845; + Integer v846; + Integer v847; + Integer v848; + Integer v849; + Integer v850; + Integer v851; + Integer v852; + Integer v853; + Integer v854; + Integer v855; + Integer v856; + Integer v857; + Integer v858; + Integer v859; + Integer v860; + Integer v861; + Integer v862; + Integer v863; + Integer v864; + Integer v865; + Integer v866; + Integer v867; + Integer v868; + Integer v869; + Integer v870; + Integer v871; + Integer v872; + Integer v873; + Integer v874; + Integer v875; + Integer v876; + Integer v877; + Integer v878; + Integer v879; + Integer v880; + Integer v881; + Integer v882; + Integer v883; + Integer v884; + Integer v885; + Integer v886; + Integer v887; + Integer v888; + Integer v889; + Integer v890; + Integer v891; + Integer v892; + Integer v893; + Integer v894; + Integer v895; + Integer v896; + Integer v897; + Integer v898; + Integer v899; + Integer v900; + Integer v901; + Integer v902; + Integer v903; + Integer v904; + Integer v905; + Integer v906; + Integer v907; + Integer v908; + Integer v909; + Integer v910; + Integer v911; + Integer v912; + Integer v913; + Integer v914; + Integer v915; + Integer v916; + Integer v917; + Integer v918; + Integer v919; + Integer v920; + Integer v921; + Integer v922; + Integer v923; + Integer v924; + Integer v925; + Integer v926; + Integer v927; + Integer v928; + Integer v929; + Integer v930; + Integer v931; + Integer v932; + Integer v933; + Integer v934; + Integer v935; + Integer v936; + Integer v937; + Integer v938; + Integer v939; + Integer v940; + Integer v941; + Integer v942; + Integer v943; + Integer v944; + Integer v945; + Integer v946; + Integer v947; + Integer v948; + Integer v949; + Integer v950; + Integer v951; + Integer v952; + Integer v953; + Integer v954; + Integer v955; + Integer v956; + Integer v957; + Integer v958; + Integer v959; + Integer v960; + Integer v961; + Integer v962; + Integer v963; + Integer v964; + Integer v965; + Integer v966; + Integer v967; + Integer v968; + Integer v969; + Integer v970; + Integer v971; + Integer v972; + Integer v973; + Integer v974; + Integer v975; + Integer v976; + Integer v977; + Integer v978; + Integer v979; + Integer v980; + Integer v981; + Integer v982; + Integer v983; + Integer v984; + Integer v985; + Integer v986; + Integer v987; + Integer v988; + Integer v989; + Integer v990; + Integer v991; + Integer v992; + Integer v993; + Integer v994; + Integer v995; + Integer v996; + Integer v997; + Integer v998; + Integer v999; - void foo() { - Map map = new HashMap<>(); - map.put("k0", v0); - map.put("k1", v1); - map.put("k2", v2); - map.put("k3", v3); - map.put("k4", v4); - map.put("k5", v5); - map.put("k6", v6); - map.put("k7", v7); - map.put("k8", v8); - map.put("k9", v9); - map.put("k10", v10); - map.put("k11", v11); - map.put("k12", v12); - map.put("k13", v13); - map.put("k14", v14); - map.put("k15", v15); - map.put("k16", v16); - map.put("k17", v17); - map.put("k18", v18); - map.put("k19", v19); - map.put("k20", v20); - map.put("k21", v21); - map.put("k22", v22); - map.put("k23", v23); - map.put("k24", v24); - map.put("k25", v25); - map.put("k26", v26); - map.put("k27", v27); - map.put("k28", v28); - map.put("k29", v29); - map.put("k30", v30); - map.put("k31", v31); - map.put("k32", v32); - map.put("k33", v33); - map.put("k34", v34); - map.put("k35", v35); - map.put("k36", v36); - map.put("k37", v37); - map.put("k38", v38); - map.put("k39", v39); - map.put("k40", v40); - map.put("k41", v41); - map.put("k42", v42); - map.put("k43", v43); - map.put("k44", v44); - map.put("k45", v45); - map.put("k46", v46); - map.put("k47", v47); - map.put("k48", v48); - map.put("k49", v49); - map.put("k50", v50); - map.put("k51", v51); - map.put("k52", v52); - map.put("k53", v53); - map.put("k54", v54); - map.put("k55", v55); - map.put("k56", v56); - map.put("k57", v57); - map.put("k58", v58); - map.put("k59", v59); - map.put("k60", v60); - map.put("k61", v61); - map.put("k62", v62); - map.put("k63", v63); - map.put("k64", v64); - map.put("k65", v65); - map.put("k66", v66); - map.put("k67", v67); - map.put("k68", v68); - map.put("k69", v69); - map.put("k70", v70); - map.put("k71", v71); - map.put("k72", v72); - map.put("k73", v73); - map.put("k74", v74); - map.put("k75", v75); - map.put("k76", v76); - map.put("k77", v77); - map.put("k78", v78); - map.put("k79", v79); - map.put("k80", v80); - map.put("k81", v81); - map.put("k82", v82); - map.put("k83", v83); - map.put("k84", v84); - map.put("k85", v85); - map.put("k86", v86); - map.put("k87", v87); - map.put("k88", v88); - map.put("k89", v89); - map.put("k90", v90); - map.put("k91", v91); - map.put("k92", v92); - map.put("k93", v93); - map.put("k94", v94); - map.put("k95", v95); - map.put("k96", v96); - map.put("k97", v97); - map.put("k98", v98); - map.put("k99", v99); - map.put("k100", v100); - map.put("k101", v101); - map.put("k102", v102); - map.put("k103", v103); - map.put("k104", v104); - map.put("k105", v105); - map.put("k106", v106); - map.put("k107", v107); - map.put("k108", v108); - map.put("k109", v109); - map.put("k110", v110); - map.put("k111", v111); - map.put("k112", v112); - map.put("k113", v113); - map.put("k114", v114); - map.put("k115", v115); - map.put("k116", v116); - map.put("k117", v117); - map.put("k118", v118); - map.put("k119", v119); - map.put("k120", v120); - map.put("k121", v121); - map.put("k122", v122); - map.put("k123", v123); - map.put("k124", v124); - map.put("k125", v125); - map.put("k126", v126); - map.put("k127", v127); - map.put("k128", v128); - map.put("k129", v129); - map.put("k130", v130); - map.put("k131", v131); - map.put("k132", v132); - map.put("k133", v133); - map.put("k134", v134); - map.put("k135", v135); - map.put("k136", v136); - map.put("k137", v137); - map.put("k138", v138); - map.put("k139", v139); - map.put("k140", v140); - map.put("k141", v141); - map.put("k142", v142); - map.put("k143", v143); - map.put("k144", v144); - map.put("k145", v145); - map.put("k146", v146); - map.put("k147", v147); - map.put("k148", v148); - map.put("k149", v149); - map.put("k150", v150); - map.put("k151", v151); - map.put("k152", v152); - map.put("k153", v153); - map.put("k154", v154); - map.put("k155", v155); - map.put("k156", v156); - map.put("k157", v157); - map.put("k158", v158); - map.put("k159", v159); - map.put("k160", v160); - map.put("k161", v161); - map.put("k162", v162); - map.put("k163", v163); - map.put("k164", v164); - map.put("k165", v165); - map.put("k166", v166); - map.put("k167", v167); - map.put("k168", v168); - map.put("k169", v169); - map.put("k170", v170); - map.put("k171", v171); - map.put("k172", v172); - map.put("k173", v173); - map.put("k174", v174); - map.put("k175", v175); - map.put("k176", v176); - map.put("k177", v177); - map.put("k178", v178); - map.put("k179", v179); - map.put("k180", v180); - map.put("k181", v181); - map.put("k182", v182); - map.put("k183", v183); - map.put("k184", v184); - map.put("k185", v185); - map.put("k186", v186); - map.put("k187", v187); - map.put("k188", v188); - map.put("k189", v189); - map.put("k190", v190); - map.put("k191", v191); - map.put("k192", v192); - map.put("k193", v193); - map.put("k194", v194); - map.put("k195", v195); - map.put("k196", v196); - map.put("k197", v197); - map.put("k198", v198); - map.put("k199", v199); - map.put("k200", v200); - map.put("k201", v201); - map.put("k202", v202); - map.put("k203", v203); - map.put("k204", v204); - map.put("k205", v205); - map.put("k206", v206); - map.put("k207", v207); - map.put("k208", v208); - map.put("k209", v209); - map.put("k210", v210); - map.put("k211", v211); - map.put("k212", v212); - map.put("k213", v213); - map.put("k214", v214); - map.put("k215", v215); - map.put("k216", v216); - map.put("k217", v217); - map.put("k218", v218); - map.put("k219", v219); - map.put("k220", v220); - map.put("k221", v221); - map.put("k222", v222); - map.put("k223", v223); - map.put("k224", v224); - map.put("k225", v225); - map.put("k226", v226); - map.put("k227", v227); - map.put("k228", v228); - map.put("k229", v229); - map.put("k230", v230); - map.put("k231", v231); - map.put("k232", v232); - map.put("k233", v233); - map.put("k234", v234); - map.put("k235", v235); - map.put("k236", v236); - map.put("k237", v237); - map.put("k238", v238); - map.put("k239", v239); - map.put("k240", v240); - map.put("k241", v241); - map.put("k242", v242); - map.put("k243", v243); - map.put("k244", v244); - map.put("k245", v245); - map.put("k246", v246); - map.put("k247", v247); - map.put("k248", v248); - map.put("k249", v249); - map.put("k250", v250); - map.put("k251", v251); - map.put("k252", v252); - map.put("k253", v253); - map.put("k254", v254); - map.put("k255", v255); - map.put("k256", v256); - map.put("k257", v257); - map.put("k258", v258); - map.put("k259", v259); - map.put("k260", v260); - map.put("k261", v261); - map.put("k262", v262); - map.put("k263", v263); - map.put("k264", v264); - map.put("k265", v265); - map.put("k266", v266); - map.put("k267", v267); - map.put("k268", v268); - map.put("k269", v269); - map.put("k270", v270); - map.put("k271", v271); - map.put("k272", v272); - map.put("k273", v273); - map.put("k274", v274); - map.put("k275", v275); - map.put("k276", v276); - map.put("k277", v277); - map.put("k278", v278); - map.put("k279", v279); - map.put("k280", v280); - map.put("k281", v281); - map.put("k282", v282); - map.put("k283", v283); - map.put("k284", v284); - map.put("k285", v285); - map.put("k286", v286); - map.put("k287", v287); - map.put("k288", v288); - map.put("k289", v289); - map.put("k290", v290); - map.put("k291", v291); - map.put("k292", v292); - map.put("k293", v293); - map.put("k294", v294); - map.put("k295", v295); - map.put("k296", v296); - map.put("k297", v297); - map.put("k298", v298); - map.put("k299", v299); - map.put("k300", v300); - map.put("k301", v301); - map.put("k302", v302); - map.put("k303", v303); - map.put("k304", v304); - map.put("k305", v305); - map.put("k306", v306); - map.put("k307", v307); - map.put("k308", v308); - map.put("k309", v309); - map.put("k310", v310); - map.put("k311", v311); - map.put("k312", v312); - map.put("k313", v313); - map.put("k314", v314); - map.put("k315", v315); - map.put("k316", v316); - map.put("k317", v317); - map.put("k318", v318); - map.put("k319", v319); - map.put("k320", v320); - map.put("k321", v321); - map.put("k322", v322); - map.put("k323", v323); - map.put("k324", v324); - map.put("k325", v325); - map.put("k326", v326); - map.put("k327", v327); - map.put("k328", v328); - map.put("k329", v329); - map.put("k330", v330); - map.put("k331", v331); - map.put("k332", v332); - map.put("k333", v333); - map.put("k334", v334); - map.put("k335", v335); - map.put("k336", v336); - map.put("k337", v337); - map.put("k338", v338); - map.put("k339", v339); - map.put("k340", v340); - map.put("k341", v341); - map.put("k342", v342); - map.put("k343", v343); - map.put("k344", v344); - map.put("k345", v345); - map.put("k346", v346); - map.put("k347", v347); - map.put("k348", v348); - map.put("k349", v349); - map.put("k350", v350); - map.put("k351", v351); - map.put("k352", v352); - map.put("k353", v353); - map.put("k354", v354); - map.put("k355", v355); - map.put("k356", v356); - map.put("k357", v357); - map.put("k358", v358); - map.put("k359", v359); - map.put("k360", v360); - map.put("k361", v361); - map.put("k362", v362); - map.put("k363", v363); - map.put("k364", v364); - map.put("k365", v365); - map.put("k366", v366); - map.put("k367", v367); - map.put("k368", v368); - map.put("k369", v369); - map.put("k370", v370); - map.put("k371", v371); - map.put("k372", v372); - map.put("k373", v373); - map.put("k374", v374); - map.put("k375", v375); - map.put("k376", v376); - map.put("k377", v377); - map.put("k378", v378); - map.put("k379", v379); - map.put("k380", v380); - map.put("k381", v381); - map.put("k382", v382); - map.put("k383", v383); - map.put("k384", v384); - map.put("k385", v385); - map.put("k386", v386); - map.put("k387", v387); - map.put("k388", v388); - map.put("k389", v389); - map.put("k390", v390); - map.put("k391", v391); - map.put("k392", v392); - map.put("k393", v393); - map.put("k394", v394); - map.put("k395", v395); - map.put("k396", v396); - map.put("k397", v397); - map.put("k398", v398); - map.put("k399", v399); - map.put("k400", v400); - map.put("k401", v401); - map.put("k402", v402); - map.put("k403", v403); - map.put("k404", v404); - map.put("k405", v405); - map.put("k406", v406); - map.put("k407", v407); - map.put("k408", v408); - map.put("k409", v409); - map.put("k410", v410); - map.put("k411", v411); - map.put("k412", v412); - map.put("k413", v413); - map.put("k414", v414); - map.put("k415", v415); - map.put("k416", v416); - map.put("k417", v417); - map.put("k418", v418); - map.put("k419", v419); - map.put("k420", v420); - map.put("k421", v421); - map.put("k422", v422); - map.put("k423", v423); - map.put("k424", v424); - map.put("k425", v425); - map.put("k426", v426); - map.put("k427", v427); - map.put("k428", v428); - map.put("k429", v429); - map.put("k430", v430); - map.put("k431", v431); - map.put("k432", v432); - map.put("k433", v433); - map.put("k434", v434); - map.put("k435", v435); - map.put("k436", v436); - map.put("k437", v437); - map.put("k438", v438); - map.put("k439", v439); - map.put("k440", v440); - map.put("k441", v441); - map.put("k442", v442); - map.put("k443", v443); - map.put("k444", v444); - map.put("k445", v445); - map.put("k446", v446); - map.put("k447", v447); - map.put("k448", v448); - map.put("k449", v449); - map.put("k450", v450); - map.put("k451", v451); - map.put("k452", v452); - map.put("k453", v453); - map.put("k454", v454); - map.put("k455", v455); - map.put("k456", v456); - map.put("k457", v457); - map.put("k458", v458); - map.put("k459", v459); - map.put("k460", v460); - map.put("k461", v461); - map.put("k462", v462); - map.put("k463", v463); - map.put("k464", v464); - map.put("k465", v465); - map.put("k466", v466); - map.put("k467", v467); - map.put("k468", v468); - map.put("k469", v469); - map.put("k470", v470); - map.put("k471", v471); - map.put("k472", v472); - map.put("k473", v473); - map.put("k474", v474); - map.put("k475", v475); - map.put("k476", v476); - map.put("k477", v477); - map.put("k478", v478); - map.put("k479", v479); - map.put("k480", v480); - map.put("k481", v481); - map.put("k482", v482); - map.put("k483", v483); - map.put("k484", v484); - map.put("k485", v485); - map.put("k486", v486); - map.put("k487", v487); - map.put("k488", v488); - map.put("k489", v489); - map.put("k490", v490); - map.put("k491", v491); - map.put("k492", v492); - map.put("k493", v493); - map.put("k494", v494); - map.put("k495", v495); - map.put("k496", v496); - map.put("k497", v497); - map.put("k498", v498); - map.put("k499", v499); - map.put("k500", v500); - map.put("k501", v501); - map.put("k502", v502); - map.put("k503", v503); - map.put("k504", v504); - map.put("k505", v505); - map.put("k506", v506); - map.put("k507", v507); - map.put("k508", v508); - map.put("k509", v509); - map.put("k510", v510); - map.put("k511", v511); - map.put("k512", v512); - map.put("k513", v513); - map.put("k514", v514); - map.put("k515", v515); - map.put("k516", v516); - map.put("k517", v517); - map.put("k518", v518); - map.put("k519", v519); - map.put("k520", v520); - map.put("k521", v521); - map.put("k522", v522); - map.put("k523", v523); - map.put("k524", v524); - map.put("k525", v525); - map.put("k526", v526); - map.put("k527", v527); - map.put("k528", v528); - map.put("k529", v529); - map.put("k530", v530); - map.put("k531", v531); - map.put("k532", v532); - map.put("k533", v533); - map.put("k534", v534); - map.put("k535", v535); - map.put("k536", v536); - map.put("k537", v537); - map.put("k538", v538); - map.put("k539", v539); - map.put("k540", v540); - map.put("k541", v541); - map.put("k542", v542); - map.put("k543", v543); - map.put("k544", v544); - map.put("k545", v545); - map.put("k546", v546); - map.put("k547", v547); - map.put("k548", v548); - map.put("k549", v549); - map.put("k550", v550); - map.put("k551", v551); - map.put("k552", v552); - map.put("k553", v553); - map.put("k554", v554); - map.put("k555", v555); - map.put("k556", v556); - map.put("k557", v557); - map.put("k558", v558); - map.put("k559", v559); - map.put("k560", v560); - map.put("k561", v561); - map.put("k562", v562); - map.put("k563", v563); - map.put("k564", v564); - map.put("k565", v565); - map.put("k566", v566); - map.put("k567", v567); - map.put("k568", v568); - map.put("k569", v569); - map.put("k570", v570); - map.put("k571", v571); - map.put("k572", v572); - map.put("k573", v573); - map.put("k574", v574); - map.put("k575", v575); - map.put("k576", v576); - map.put("k577", v577); - map.put("k578", v578); - map.put("k579", v579); - map.put("k580", v580); - map.put("k581", v581); - map.put("k582", v582); - map.put("k583", v583); - map.put("k584", v584); - map.put("k585", v585); - map.put("k586", v586); - map.put("k587", v587); - map.put("k588", v588); - map.put("k589", v589); - map.put("k590", v590); - map.put("k591", v591); - map.put("k592", v592); - map.put("k593", v593); - map.put("k594", v594); - map.put("k595", v595); - map.put("k596", v596); - map.put("k597", v597); - map.put("k598", v598); - map.put("k599", v599); - map.put("k600", v600); - map.put("k601", v601); - map.put("k602", v602); - map.put("k603", v603); - map.put("k604", v604); - map.put("k605", v605); - map.put("k606", v606); - map.put("k607", v607); - map.put("k608", v608); - map.put("k609", v609); - map.put("k610", v610); - map.put("k611", v611); - map.put("k612", v612); - map.put("k613", v613); - map.put("k614", v614); - map.put("k615", v615); - map.put("k616", v616); - map.put("k617", v617); - map.put("k618", v618); - map.put("k619", v619); - map.put("k620", v620); - map.put("k621", v621); - map.put("k622", v622); - map.put("k623", v623); - map.put("k624", v624); - map.put("k625", v625); - map.put("k626", v626); - map.put("k627", v627); - map.put("k628", v628); - map.put("k629", v629); - map.put("k630", v630); - map.put("k631", v631); - map.put("k632", v632); - map.put("k633", v633); - map.put("k634", v634); - map.put("k635", v635); - map.put("k636", v636); - map.put("k637", v637); - map.put("k638", v638); - map.put("k639", v639); - map.put("k640", v640); - map.put("k641", v641); - map.put("k642", v642); - map.put("k643", v643); - map.put("k644", v644); - map.put("k645", v645); - map.put("k646", v646); - map.put("k647", v647); - map.put("k648", v648); - map.put("k649", v649); - map.put("k650", v650); - map.put("k651", v651); - map.put("k652", v652); - map.put("k653", v653); - map.put("k654", v654); - map.put("k655", v655); - map.put("k656", v656); - map.put("k657", v657); - map.put("k658", v658); - map.put("k659", v659); - map.put("k660", v660); - map.put("k661", v661); - map.put("k662", v662); - map.put("k663", v663); - map.put("k664", v664); - map.put("k665", v665); - map.put("k666", v666); - map.put("k667", v667); - map.put("k668", v668); - map.put("k669", v669); - map.put("k670", v670); - map.put("k671", v671); - map.put("k672", v672); - map.put("k673", v673); - map.put("k674", v674); - map.put("k675", v675); - map.put("k676", v676); - map.put("k677", v677); - map.put("k678", v678); - map.put("k679", v679); - map.put("k680", v680); - map.put("k681", v681); - map.put("k682", v682); - map.put("k683", v683); - map.put("k684", v684); - map.put("k685", v685); - map.put("k686", v686); - map.put("k687", v687); - map.put("k688", v688); - map.put("k689", v689); - map.put("k690", v690); - map.put("k691", v691); - map.put("k692", v692); - map.put("k693", v693); - map.put("k694", v694); - map.put("k695", v695); - map.put("k696", v696); - map.put("k697", v697); - map.put("k698", v698); - map.put("k699", v699); - map.put("k700", v700); - map.put("k701", v701); - map.put("k702", v702); - map.put("k703", v703); - map.put("k704", v704); - map.put("k705", v705); - map.put("k706", v706); - map.put("k707", v707); - map.put("k708", v708); - map.put("k709", v709); - map.put("k710", v710); - map.put("k711", v711); - map.put("k712", v712); - map.put("k713", v713); - map.put("k714", v714); - map.put("k715", v715); - map.put("k716", v716); - map.put("k717", v717); - map.put("k718", v718); - map.put("k719", v719); - map.put("k720", v720); - map.put("k721", v721); - map.put("k722", v722); - map.put("k723", v723); - map.put("k724", v724); - map.put("k725", v725); - map.put("k726", v726); - map.put("k727", v727); - map.put("k728", v728); - map.put("k729", v729); - map.put("k730", v730); - map.put("k731", v731); - map.put("k732", v732); - map.put("k733", v733); - map.put("k734", v734); - map.put("k735", v735); - map.put("k736", v736); - map.put("k737", v737); - map.put("k738", v738); - map.put("k739", v739); - map.put("k740", v740); - map.put("k741", v741); - map.put("k742", v742); - map.put("k743", v743); - map.put("k744", v744); - map.put("k745", v745); - map.put("k746", v746); - map.put("k747", v747); - map.put("k748", v748); - map.put("k749", v749); - map.put("k750", v750); - map.put("k751", v751); - map.put("k752", v752); - map.put("k753", v753); - map.put("k754", v754); - map.put("k755", v755); - map.put("k756", v756); - map.put("k757", v757); - map.put("k758", v758); - map.put("k759", v759); - map.put("k760", v760); - map.put("k761", v761); - map.put("k762", v762); - map.put("k763", v763); - map.put("k764", v764); - map.put("k765", v765); - map.put("k766", v766); - map.put("k767", v767); - map.put("k768", v768); - map.put("k769", v769); - map.put("k770", v770); - map.put("k771", v771); - map.put("k772", v772); - map.put("k773", v773); - map.put("k774", v774); - map.put("k775", v775); - map.put("k776", v776); - map.put("k777", v777); - map.put("k778", v778); - map.put("k779", v779); - map.put("k780", v780); - map.put("k781", v781); - map.put("k782", v782); - map.put("k783", v783); - map.put("k784", v784); - map.put("k785", v785); - map.put("k786", v786); - map.put("k787", v787); - map.put("k788", v788); - map.put("k789", v789); - map.put("k790", v790); - map.put("k791", v791); - map.put("k792", v792); - map.put("k793", v793); - map.put("k794", v794); - map.put("k795", v795); - map.put("k796", v796); - map.put("k797", v797); - map.put("k798", v798); - map.put("k799", v799); - map.put("k800", v800); - map.put("k801", v801); - map.put("k802", v802); - map.put("k803", v803); - map.put("k804", v804); - map.put("k805", v805); - map.put("k806", v806); - map.put("k807", v807); - map.put("k808", v808); - map.put("k809", v809); - map.put("k810", v810); - map.put("k811", v811); - map.put("k812", v812); - map.put("k813", v813); - map.put("k814", v814); - map.put("k815", v815); - map.put("k816", v816); - map.put("k817", v817); - map.put("k818", v818); - map.put("k819", v819); - map.put("k820", v820); - map.put("k821", v821); - map.put("k822", v822); - map.put("k823", v823); - map.put("k824", v824); - map.put("k825", v825); - map.put("k826", v826); - map.put("k827", v827); - map.put("k828", v828); - map.put("k829", v829); - map.put("k830", v830); - map.put("k831", v831); - map.put("k832", v832); - map.put("k833", v833); - map.put("k834", v834); - map.put("k835", v835); - map.put("k836", v836); - map.put("k837", v837); - map.put("k838", v838); - map.put("k839", v839); - map.put("k840", v840); - map.put("k841", v841); - map.put("k842", v842); - map.put("k843", v843); - map.put("k844", v844); - map.put("k845", v845); - map.put("k846", v846); - map.put("k847", v847); - map.put("k848", v848); - map.put("k849", v849); - map.put("k850", v850); - map.put("k851", v851); - map.put("k852", v852); - map.put("k853", v853); - map.put("k854", v854); - map.put("k855", v855); - map.put("k856", v856); - map.put("k857", v857); - map.put("k858", v858); - map.put("k859", v859); - map.put("k860", v860); - map.put("k861", v861); - map.put("k862", v862); - map.put("k863", v863); - map.put("k864", v864); - map.put("k865", v865); - map.put("k866", v866); - map.put("k867", v867); - map.put("k868", v868); - map.put("k869", v869); - map.put("k870", v870); - map.put("k871", v871); - map.put("k872", v872); - map.put("k873", v873); - map.put("k874", v874); - map.put("k875", v875); - map.put("k876", v876); - map.put("k877", v877); - map.put("k878", v878); - map.put("k879", v879); - map.put("k880", v880); - map.put("k881", v881); - map.put("k882", v882); - map.put("k883", v883); - map.put("k884", v884); - map.put("k885", v885); - map.put("k886", v886); - map.put("k887", v887); - map.put("k888", v888); - map.put("k889", v889); - map.put("k890", v890); - map.put("k891", v891); - map.put("k892", v892); - map.put("k893", v893); - map.put("k894", v894); - map.put("k895", v895); - map.put("k896", v896); - map.put("k897", v897); - map.put("k898", v898); - map.put("k899", v899); - map.put("k900", v900); - map.put("k901", v901); - map.put("k902", v902); - map.put("k903", v903); - map.put("k904", v904); - map.put("k905", v905); - map.put("k906", v906); - map.put("k907", v907); - map.put("k908", v908); - map.put("k909", v909); - map.put("k910", v910); - map.put("k911", v911); - map.put("k912", v912); - map.put("k913", v913); - map.put("k914", v914); - map.put("k915", v915); - map.put("k916", v916); - map.put("k917", v917); - map.put("k918", v918); - map.put("k919", v919); - map.put("k920", v920); - map.put("k921", v921); - map.put("k922", v922); - map.put("k923", v923); - map.put("k924", v924); - map.put("k925", v925); - map.put("k926", v926); - map.put("k927", v927); - map.put("k928", v928); - map.put("k929", v929); - map.put("k930", v930); - map.put("k931", v931); - map.put("k932", v932); - map.put("k933", v933); - map.put("k934", v934); - map.put("k935", v935); - map.put("k936", v936); - map.put("k937", v937); - map.put("k938", v938); - map.put("k939", v939); - map.put("k940", v940); - map.put("k941", v941); - map.put("k942", v942); - map.put("k943", v943); - map.put("k944", v944); - map.put("k945", v945); - map.put("k946", v946); - map.put("k947", v947); - map.put("k948", v948); - map.put("k949", v949); - map.put("k950", v950); - map.put("k951", v951); - map.put("k952", v952); - map.put("k953", v953); - map.put("k954", v954); - map.put("k955", v955); - map.put("k956", v956); - map.put("k957", v957); - map.put("k958", v958); - map.put("k959", v959); - map.put("k960", v960); - map.put("k961", v961); - map.put("k962", v962); - map.put("k963", v963); - map.put("k964", v964); - map.put("k965", v965); - map.put("k966", v966); - map.put("k967", v967); - map.put("k968", v968); - map.put("k969", v969); - map.put("k970", v970); - map.put("k971", v971); - map.put("k972", v972); - map.put("k973", v973); - map.put("k974", v974); - map.put("k975", v975); - map.put("k976", v976); - map.put("k977", v977); - map.put("k978", v978); - map.put("k979", v979); - map.put("k980", v980); - map.put("k981", v981); - map.put("k982", v982); - map.put("k983", v983); - map.put("k984", v984); - map.put("k985", v985); - map.put("k986", v986); - map.put("k987", v987); - map.put("k988", v988); - map.put("k989", v989); - map.put("k990", v990); - map.put("k991", v991); - map.put("k992", v992); - map.put("k993", v993); - map.put("k994", v994); - map.put("k995", v995); - map.put("k996", v996); - map.put("k997", v997); - map.put("k998", v998); - map.put("k999", v999); - } + void foo() { + Map map = new HashMap<>(); + map.put("k0", v0); + map.put("k1", v1); + map.put("k2", v2); + map.put("k3", v3); + map.put("k4", v4); + map.put("k5", v5); + map.put("k6", v6); + map.put("k7", v7); + map.put("k8", v8); + map.put("k9", v9); + map.put("k10", v10); + map.put("k11", v11); + map.put("k12", v12); + map.put("k13", v13); + map.put("k14", v14); + map.put("k15", v15); + map.put("k16", v16); + map.put("k17", v17); + map.put("k18", v18); + map.put("k19", v19); + map.put("k20", v20); + map.put("k21", v21); + map.put("k22", v22); + map.put("k23", v23); + map.put("k24", v24); + map.put("k25", v25); + map.put("k26", v26); + map.put("k27", v27); + map.put("k28", v28); + map.put("k29", v29); + map.put("k30", v30); + map.put("k31", v31); + map.put("k32", v32); + map.put("k33", v33); + map.put("k34", v34); + map.put("k35", v35); + map.put("k36", v36); + map.put("k37", v37); + map.put("k38", v38); + map.put("k39", v39); + map.put("k40", v40); + map.put("k41", v41); + map.put("k42", v42); + map.put("k43", v43); + map.put("k44", v44); + map.put("k45", v45); + map.put("k46", v46); + map.put("k47", v47); + map.put("k48", v48); + map.put("k49", v49); + map.put("k50", v50); + map.put("k51", v51); + map.put("k52", v52); + map.put("k53", v53); + map.put("k54", v54); + map.put("k55", v55); + map.put("k56", v56); + map.put("k57", v57); + map.put("k58", v58); + map.put("k59", v59); + map.put("k60", v60); + map.put("k61", v61); + map.put("k62", v62); + map.put("k63", v63); + map.put("k64", v64); + map.put("k65", v65); + map.put("k66", v66); + map.put("k67", v67); + map.put("k68", v68); + map.put("k69", v69); + map.put("k70", v70); + map.put("k71", v71); + map.put("k72", v72); + map.put("k73", v73); + map.put("k74", v74); + map.put("k75", v75); + map.put("k76", v76); + map.put("k77", v77); + map.put("k78", v78); + map.put("k79", v79); + map.put("k80", v80); + map.put("k81", v81); + map.put("k82", v82); + map.put("k83", v83); + map.put("k84", v84); + map.put("k85", v85); + map.put("k86", v86); + map.put("k87", v87); + map.put("k88", v88); + map.put("k89", v89); + map.put("k90", v90); + map.put("k91", v91); + map.put("k92", v92); + map.put("k93", v93); + map.put("k94", v94); + map.put("k95", v95); + map.put("k96", v96); + map.put("k97", v97); + map.put("k98", v98); + map.put("k99", v99); + map.put("k100", v100); + map.put("k101", v101); + map.put("k102", v102); + map.put("k103", v103); + map.put("k104", v104); + map.put("k105", v105); + map.put("k106", v106); + map.put("k107", v107); + map.put("k108", v108); + map.put("k109", v109); + map.put("k110", v110); + map.put("k111", v111); + map.put("k112", v112); + map.put("k113", v113); + map.put("k114", v114); + map.put("k115", v115); + map.put("k116", v116); + map.put("k117", v117); + map.put("k118", v118); + map.put("k119", v119); + map.put("k120", v120); + map.put("k121", v121); + map.put("k122", v122); + map.put("k123", v123); + map.put("k124", v124); + map.put("k125", v125); + map.put("k126", v126); + map.put("k127", v127); + map.put("k128", v128); + map.put("k129", v129); + map.put("k130", v130); + map.put("k131", v131); + map.put("k132", v132); + map.put("k133", v133); + map.put("k134", v134); + map.put("k135", v135); + map.put("k136", v136); + map.put("k137", v137); + map.put("k138", v138); + map.put("k139", v139); + map.put("k140", v140); + map.put("k141", v141); + map.put("k142", v142); + map.put("k143", v143); + map.put("k144", v144); + map.put("k145", v145); + map.put("k146", v146); + map.put("k147", v147); + map.put("k148", v148); + map.put("k149", v149); + map.put("k150", v150); + map.put("k151", v151); + map.put("k152", v152); + map.put("k153", v153); + map.put("k154", v154); + map.put("k155", v155); + map.put("k156", v156); + map.put("k157", v157); + map.put("k158", v158); + map.put("k159", v159); + map.put("k160", v160); + map.put("k161", v161); + map.put("k162", v162); + map.put("k163", v163); + map.put("k164", v164); + map.put("k165", v165); + map.put("k166", v166); + map.put("k167", v167); + map.put("k168", v168); + map.put("k169", v169); + map.put("k170", v170); + map.put("k171", v171); + map.put("k172", v172); + map.put("k173", v173); + map.put("k174", v174); + map.put("k175", v175); + map.put("k176", v176); + map.put("k177", v177); + map.put("k178", v178); + map.put("k179", v179); + map.put("k180", v180); + map.put("k181", v181); + map.put("k182", v182); + map.put("k183", v183); + map.put("k184", v184); + map.put("k185", v185); + map.put("k186", v186); + map.put("k187", v187); + map.put("k188", v188); + map.put("k189", v189); + map.put("k190", v190); + map.put("k191", v191); + map.put("k192", v192); + map.put("k193", v193); + map.put("k194", v194); + map.put("k195", v195); + map.put("k196", v196); + map.put("k197", v197); + map.put("k198", v198); + map.put("k199", v199); + map.put("k200", v200); + map.put("k201", v201); + map.put("k202", v202); + map.put("k203", v203); + map.put("k204", v204); + map.put("k205", v205); + map.put("k206", v206); + map.put("k207", v207); + map.put("k208", v208); + map.put("k209", v209); + map.put("k210", v210); + map.put("k211", v211); + map.put("k212", v212); + map.put("k213", v213); + map.put("k214", v214); + map.put("k215", v215); + map.put("k216", v216); + map.put("k217", v217); + map.put("k218", v218); + map.put("k219", v219); + map.put("k220", v220); + map.put("k221", v221); + map.put("k222", v222); + map.put("k223", v223); + map.put("k224", v224); + map.put("k225", v225); + map.put("k226", v226); + map.put("k227", v227); + map.put("k228", v228); + map.put("k229", v229); + map.put("k230", v230); + map.put("k231", v231); + map.put("k232", v232); + map.put("k233", v233); + map.put("k234", v234); + map.put("k235", v235); + map.put("k236", v236); + map.put("k237", v237); + map.put("k238", v238); + map.put("k239", v239); + map.put("k240", v240); + map.put("k241", v241); + map.put("k242", v242); + map.put("k243", v243); + map.put("k244", v244); + map.put("k245", v245); + map.put("k246", v246); + map.put("k247", v247); + map.put("k248", v248); + map.put("k249", v249); + map.put("k250", v250); + map.put("k251", v251); + map.put("k252", v252); + map.put("k253", v253); + map.put("k254", v254); + map.put("k255", v255); + map.put("k256", v256); + map.put("k257", v257); + map.put("k258", v258); + map.put("k259", v259); + map.put("k260", v260); + map.put("k261", v261); + map.put("k262", v262); + map.put("k263", v263); + map.put("k264", v264); + map.put("k265", v265); + map.put("k266", v266); + map.put("k267", v267); + map.put("k268", v268); + map.put("k269", v269); + map.put("k270", v270); + map.put("k271", v271); + map.put("k272", v272); + map.put("k273", v273); + map.put("k274", v274); + map.put("k275", v275); + map.put("k276", v276); + map.put("k277", v277); + map.put("k278", v278); + map.put("k279", v279); + map.put("k280", v280); + map.put("k281", v281); + map.put("k282", v282); + map.put("k283", v283); + map.put("k284", v284); + map.put("k285", v285); + map.put("k286", v286); + map.put("k287", v287); + map.put("k288", v288); + map.put("k289", v289); + map.put("k290", v290); + map.put("k291", v291); + map.put("k292", v292); + map.put("k293", v293); + map.put("k294", v294); + map.put("k295", v295); + map.put("k296", v296); + map.put("k297", v297); + map.put("k298", v298); + map.put("k299", v299); + map.put("k300", v300); + map.put("k301", v301); + map.put("k302", v302); + map.put("k303", v303); + map.put("k304", v304); + map.put("k305", v305); + map.put("k306", v306); + map.put("k307", v307); + map.put("k308", v308); + map.put("k309", v309); + map.put("k310", v310); + map.put("k311", v311); + map.put("k312", v312); + map.put("k313", v313); + map.put("k314", v314); + map.put("k315", v315); + map.put("k316", v316); + map.put("k317", v317); + map.put("k318", v318); + map.put("k319", v319); + map.put("k320", v320); + map.put("k321", v321); + map.put("k322", v322); + map.put("k323", v323); + map.put("k324", v324); + map.put("k325", v325); + map.put("k326", v326); + map.put("k327", v327); + map.put("k328", v328); + map.put("k329", v329); + map.put("k330", v330); + map.put("k331", v331); + map.put("k332", v332); + map.put("k333", v333); + map.put("k334", v334); + map.put("k335", v335); + map.put("k336", v336); + map.put("k337", v337); + map.put("k338", v338); + map.put("k339", v339); + map.put("k340", v340); + map.put("k341", v341); + map.put("k342", v342); + map.put("k343", v343); + map.put("k344", v344); + map.put("k345", v345); + map.put("k346", v346); + map.put("k347", v347); + map.put("k348", v348); + map.put("k349", v349); + map.put("k350", v350); + map.put("k351", v351); + map.put("k352", v352); + map.put("k353", v353); + map.put("k354", v354); + map.put("k355", v355); + map.put("k356", v356); + map.put("k357", v357); + map.put("k358", v358); + map.put("k359", v359); + map.put("k360", v360); + map.put("k361", v361); + map.put("k362", v362); + map.put("k363", v363); + map.put("k364", v364); + map.put("k365", v365); + map.put("k366", v366); + map.put("k367", v367); + map.put("k368", v368); + map.put("k369", v369); + map.put("k370", v370); + map.put("k371", v371); + map.put("k372", v372); + map.put("k373", v373); + map.put("k374", v374); + map.put("k375", v375); + map.put("k376", v376); + map.put("k377", v377); + map.put("k378", v378); + map.put("k379", v379); + map.put("k380", v380); + map.put("k381", v381); + map.put("k382", v382); + map.put("k383", v383); + map.put("k384", v384); + map.put("k385", v385); + map.put("k386", v386); + map.put("k387", v387); + map.put("k388", v388); + map.put("k389", v389); + map.put("k390", v390); + map.put("k391", v391); + map.put("k392", v392); + map.put("k393", v393); + map.put("k394", v394); + map.put("k395", v395); + map.put("k396", v396); + map.put("k397", v397); + map.put("k398", v398); + map.put("k399", v399); + map.put("k400", v400); + map.put("k401", v401); + map.put("k402", v402); + map.put("k403", v403); + map.put("k404", v404); + map.put("k405", v405); + map.put("k406", v406); + map.put("k407", v407); + map.put("k408", v408); + map.put("k409", v409); + map.put("k410", v410); + map.put("k411", v411); + map.put("k412", v412); + map.put("k413", v413); + map.put("k414", v414); + map.put("k415", v415); + map.put("k416", v416); + map.put("k417", v417); + map.put("k418", v418); + map.put("k419", v419); + map.put("k420", v420); + map.put("k421", v421); + map.put("k422", v422); + map.put("k423", v423); + map.put("k424", v424); + map.put("k425", v425); + map.put("k426", v426); + map.put("k427", v427); + map.put("k428", v428); + map.put("k429", v429); + map.put("k430", v430); + map.put("k431", v431); + map.put("k432", v432); + map.put("k433", v433); + map.put("k434", v434); + map.put("k435", v435); + map.put("k436", v436); + map.put("k437", v437); + map.put("k438", v438); + map.put("k439", v439); + map.put("k440", v440); + map.put("k441", v441); + map.put("k442", v442); + map.put("k443", v443); + map.put("k444", v444); + map.put("k445", v445); + map.put("k446", v446); + map.put("k447", v447); + map.put("k448", v448); + map.put("k449", v449); + map.put("k450", v450); + map.put("k451", v451); + map.put("k452", v452); + map.put("k453", v453); + map.put("k454", v454); + map.put("k455", v455); + map.put("k456", v456); + map.put("k457", v457); + map.put("k458", v458); + map.put("k459", v459); + map.put("k460", v460); + map.put("k461", v461); + map.put("k462", v462); + map.put("k463", v463); + map.put("k464", v464); + map.put("k465", v465); + map.put("k466", v466); + map.put("k467", v467); + map.put("k468", v468); + map.put("k469", v469); + map.put("k470", v470); + map.put("k471", v471); + map.put("k472", v472); + map.put("k473", v473); + map.put("k474", v474); + map.put("k475", v475); + map.put("k476", v476); + map.put("k477", v477); + map.put("k478", v478); + map.put("k479", v479); + map.put("k480", v480); + map.put("k481", v481); + map.put("k482", v482); + map.put("k483", v483); + map.put("k484", v484); + map.put("k485", v485); + map.put("k486", v486); + map.put("k487", v487); + map.put("k488", v488); + map.put("k489", v489); + map.put("k490", v490); + map.put("k491", v491); + map.put("k492", v492); + map.put("k493", v493); + map.put("k494", v494); + map.put("k495", v495); + map.put("k496", v496); + map.put("k497", v497); + map.put("k498", v498); + map.put("k499", v499); + map.put("k500", v500); + map.put("k501", v501); + map.put("k502", v502); + map.put("k503", v503); + map.put("k504", v504); + map.put("k505", v505); + map.put("k506", v506); + map.put("k507", v507); + map.put("k508", v508); + map.put("k509", v509); + map.put("k510", v510); + map.put("k511", v511); + map.put("k512", v512); + map.put("k513", v513); + map.put("k514", v514); + map.put("k515", v515); + map.put("k516", v516); + map.put("k517", v517); + map.put("k518", v518); + map.put("k519", v519); + map.put("k520", v520); + map.put("k521", v521); + map.put("k522", v522); + map.put("k523", v523); + map.put("k524", v524); + map.put("k525", v525); + map.put("k526", v526); + map.put("k527", v527); + map.put("k528", v528); + map.put("k529", v529); + map.put("k530", v530); + map.put("k531", v531); + map.put("k532", v532); + map.put("k533", v533); + map.put("k534", v534); + map.put("k535", v535); + map.put("k536", v536); + map.put("k537", v537); + map.put("k538", v538); + map.put("k539", v539); + map.put("k540", v540); + map.put("k541", v541); + map.put("k542", v542); + map.put("k543", v543); + map.put("k544", v544); + map.put("k545", v545); + map.put("k546", v546); + map.put("k547", v547); + map.put("k548", v548); + map.put("k549", v549); + map.put("k550", v550); + map.put("k551", v551); + map.put("k552", v552); + map.put("k553", v553); + map.put("k554", v554); + map.put("k555", v555); + map.put("k556", v556); + map.put("k557", v557); + map.put("k558", v558); + map.put("k559", v559); + map.put("k560", v560); + map.put("k561", v561); + map.put("k562", v562); + map.put("k563", v563); + map.put("k564", v564); + map.put("k565", v565); + map.put("k566", v566); + map.put("k567", v567); + map.put("k568", v568); + map.put("k569", v569); + map.put("k570", v570); + map.put("k571", v571); + map.put("k572", v572); + map.put("k573", v573); + map.put("k574", v574); + map.put("k575", v575); + map.put("k576", v576); + map.put("k577", v577); + map.put("k578", v578); + map.put("k579", v579); + map.put("k580", v580); + map.put("k581", v581); + map.put("k582", v582); + map.put("k583", v583); + map.put("k584", v584); + map.put("k585", v585); + map.put("k586", v586); + map.put("k587", v587); + map.put("k588", v588); + map.put("k589", v589); + map.put("k590", v590); + map.put("k591", v591); + map.put("k592", v592); + map.put("k593", v593); + map.put("k594", v594); + map.put("k595", v595); + map.put("k596", v596); + map.put("k597", v597); + map.put("k598", v598); + map.put("k599", v599); + map.put("k600", v600); + map.put("k601", v601); + map.put("k602", v602); + map.put("k603", v603); + map.put("k604", v604); + map.put("k605", v605); + map.put("k606", v606); + map.put("k607", v607); + map.put("k608", v608); + map.put("k609", v609); + map.put("k610", v610); + map.put("k611", v611); + map.put("k612", v612); + map.put("k613", v613); + map.put("k614", v614); + map.put("k615", v615); + map.put("k616", v616); + map.put("k617", v617); + map.put("k618", v618); + map.put("k619", v619); + map.put("k620", v620); + map.put("k621", v621); + map.put("k622", v622); + map.put("k623", v623); + map.put("k624", v624); + map.put("k625", v625); + map.put("k626", v626); + map.put("k627", v627); + map.put("k628", v628); + map.put("k629", v629); + map.put("k630", v630); + map.put("k631", v631); + map.put("k632", v632); + map.put("k633", v633); + map.put("k634", v634); + map.put("k635", v635); + map.put("k636", v636); + map.put("k637", v637); + map.put("k638", v638); + map.put("k639", v639); + map.put("k640", v640); + map.put("k641", v641); + map.put("k642", v642); + map.put("k643", v643); + map.put("k644", v644); + map.put("k645", v645); + map.put("k646", v646); + map.put("k647", v647); + map.put("k648", v648); + map.put("k649", v649); + map.put("k650", v650); + map.put("k651", v651); + map.put("k652", v652); + map.put("k653", v653); + map.put("k654", v654); + map.put("k655", v655); + map.put("k656", v656); + map.put("k657", v657); + map.put("k658", v658); + map.put("k659", v659); + map.put("k660", v660); + map.put("k661", v661); + map.put("k662", v662); + map.put("k663", v663); + map.put("k664", v664); + map.put("k665", v665); + map.put("k666", v666); + map.put("k667", v667); + map.put("k668", v668); + map.put("k669", v669); + map.put("k670", v670); + map.put("k671", v671); + map.put("k672", v672); + map.put("k673", v673); + map.put("k674", v674); + map.put("k675", v675); + map.put("k676", v676); + map.put("k677", v677); + map.put("k678", v678); + map.put("k679", v679); + map.put("k680", v680); + map.put("k681", v681); + map.put("k682", v682); + map.put("k683", v683); + map.put("k684", v684); + map.put("k685", v685); + map.put("k686", v686); + map.put("k687", v687); + map.put("k688", v688); + map.put("k689", v689); + map.put("k690", v690); + map.put("k691", v691); + map.put("k692", v692); + map.put("k693", v693); + map.put("k694", v694); + map.put("k695", v695); + map.put("k696", v696); + map.put("k697", v697); + map.put("k698", v698); + map.put("k699", v699); + map.put("k700", v700); + map.put("k701", v701); + map.put("k702", v702); + map.put("k703", v703); + map.put("k704", v704); + map.put("k705", v705); + map.put("k706", v706); + map.put("k707", v707); + map.put("k708", v708); + map.put("k709", v709); + map.put("k710", v710); + map.put("k711", v711); + map.put("k712", v712); + map.put("k713", v713); + map.put("k714", v714); + map.put("k715", v715); + map.put("k716", v716); + map.put("k717", v717); + map.put("k718", v718); + map.put("k719", v719); + map.put("k720", v720); + map.put("k721", v721); + map.put("k722", v722); + map.put("k723", v723); + map.put("k724", v724); + map.put("k725", v725); + map.put("k726", v726); + map.put("k727", v727); + map.put("k728", v728); + map.put("k729", v729); + map.put("k730", v730); + map.put("k731", v731); + map.put("k732", v732); + map.put("k733", v733); + map.put("k734", v734); + map.put("k735", v735); + map.put("k736", v736); + map.put("k737", v737); + map.put("k738", v738); + map.put("k739", v739); + map.put("k740", v740); + map.put("k741", v741); + map.put("k742", v742); + map.put("k743", v743); + map.put("k744", v744); + map.put("k745", v745); + map.put("k746", v746); + map.put("k747", v747); + map.put("k748", v748); + map.put("k749", v749); + map.put("k750", v750); + map.put("k751", v751); + map.put("k752", v752); + map.put("k753", v753); + map.put("k754", v754); + map.put("k755", v755); + map.put("k756", v756); + map.put("k757", v757); + map.put("k758", v758); + map.put("k759", v759); + map.put("k760", v760); + map.put("k761", v761); + map.put("k762", v762); + map.put("k763", v763); + map.put("k764", v764); + map.put("k765", v765); + map.put("k766", v766); + map.put("k767", v767); + map.put("k768", v768); + map.put("k769", v769); + map.put("k770", v770); + map.put("k771", v771); + map.put("k772", v772); + map.put("k773", v773); + map.put("k774", v774); + map.put("k775", v775); + map.put("k776", v776); + map.put("k777", v777); + map.put("k778", v778); + map.put("k779", v779); + map.put("k780", v780); + map.put("k781", v781); + map.put("k782", v782); + map.put("k783", v783); + map.put("k784", v784); + map.put("k785", v785); + map.put("k786", v786); + map.put("k787", v787); + map.put("k788", v788); + map.put("k789", v789); + map.put("k790", v790); + map.put("k791", v791); + map.put("k792", v792); + map.put("k793", v793); + map.put("k794", v794); + map.put("k795", v795); + map.put("k796", v796); + map.put("k797", v797); + map.put("k798", v798); + map.put("k799", v799); + map.put("k800", v800); + map.put("k801", v801); + map.put("k802", v802); + map.put("k803", v803); + map.put("k804", v804); + map.put("k805", v805); + map.put("k806", v806); + map.put("k807", v807); + map.put("k808", v808); + map.put("k809", v809); + map.put("k810", v810); + map.put("k811", v811); + map.put("k812", v812); + map.put("k813", v813); + map.put("k814", v814); + map.put("k815", v815); + map.put("k816", v816); + map.put("k817", v817); + map.put("k818", v818); + map.put("k819", v819); + map.put("k820", v820); + map.put("k821", v821); + map.put("k822", v822); + map.put("k823", v823); + map.put("k824", v824); + map.put("k825", v825); + map.put("k826", v826); + map.put("k827", v827); + map.put("k828", v828); + map.put("k829", v829); + map.put("k830", v830); + map.put("k831", v831); + map.put("k832", v832); + map.put("k833", v833); + map.put("k834", v834); + map.put("k835", v835); + map.put("k836", v836); + map.put("k837", v837); + map.put("k838", v838); + map.put("k839", v839); + map.put("k840", v840); + map.put("k841", v841); + map.put("k842", v842); + map.put("k843", v843); + map.put("k844", v844); + map.put("k845", v845); + map.put("k846", v846); + map.put("k847", v847); + map.put("k848", v848); + map.put("k849", v849); + map.put("k850", v850); + map.put("k851", v851); + map.put("k852", v852); + map.put("k853", v853); + map.put("k854", v854); + map.put("k855", v855); + map.put("k856", v856); + map.put("k857", v857); + map.put("k858", v858); + map.put("k859", v859); + map.put("k860", v860); + map.put("k861", v861); + map.put("k862", v862); + map.put("k863", v863); + map.put("k864", v864); + map.put("k865", v865); + map.put("k866", v866); + map.put("k867", v867); + map.put("k868", v868); + map.put("k869", v869); + map.put("k870", v870); + map.put("k871", v871); + map.put("k872", v872); + map.put("k873", v873); + map.put("k874", v874); + map.put("k875", v875); + map.put("k876", v876); + map.put("k877", v877); + map.put("k878", v878); + map.put("k879", v879); + map.put("k880", v880); + map.put("k881", v881); + map.put("k882", v882); + map.put("k883", v883); + map.put("k884", v884); + map.put("k885", v885); + map.put("k886", v886); + map.put("k887", v887); + map.put("k888", v888); + map.put("k889", v889); + map.put("k890", v890); + map.put("k891", v891); + map.put("k892", v892); + map.put("k893", v893); + map.put("k894", v894); + map.put("k895", v895); + map.put("k896", v896); + map.put("k897", v897); + map.put("k898", v898); + map.put("k899", v899); + map.put("k900", v900); + map.put("k901", v901); + map.put("k902", v902); + map.put("k903", v903); + map.put("k904", v904); + map.put("k905", v905); + map.put("k906", v906); + map.put("k907", v907); + map.put("k908", v908); + map.put("k909", v909); + map.put("k910", v910); + map.put("k911", v911); + map.put("k912", v912); + map.put("k913", v913); + map.put("k914", v914); + map.put("k915", v915); + map.put("k916", v916); + map.put("k917", v917); + map.put("k918", v918); + map.put("k919", v919); + map.put("k920", v920); + map.put("k921", v921); + map.put("k922", v922); + map.put("k923", v923); + map.put("k924", v924); + map.put("k925", v925); + map.put("k926", v926); + map.put("k927", v927); + map.put("k928", v928); + map.put("k929", v929); + map.put("k930", v930); + map.put("k931", v931); + map.put("k932", v932); + map.put("k933", v933); + map.put("k934", v934); + map.put("k935", v935); + map.put("k936", v936); + map.put("k937", v937); + map.put("k938", v938); + map.put("k939", v939); + map.put("k940", v940); + map.put("k941", v941); + map.put("k942", v942); + map.put("k943", v943); + map.put("k944", v944); + map.put("k945", v945); + map.put("k946", v946); + map.put("k947", v947); + map.put("k948", v948); + map.put("k949", v949); + map.put("k950", v950); + map.put("k951", v951); + map.put("k952", v952); + map.put("k953", v953); + map.put("k954", v954); + map.put("k955", v955); + map.put("k956", v956); + map.put("k957", v957); + map.put("k958", v958); + map.put("k959", v959); + map.put("k960", v960); + map.put("k961", v961); + map.put("k962", v962); + map.put("k963", v963); + map.put("k964", v964); + map.put("k965", v965); + map.put("k966", v966); + map.put("k967", v967); + map.put("k968", v968); + map.put("k969", v969); + map.put("k970", v970); + map.put("k971", v971); + map.put("k972", v972); + map.put("k973", v973); + map.put("k974", v974); + map.put("k975", v975); + map.put("k976", v976); + map.put("k977", v977); + map.put("k978", v978); + map.put("k979", v979); + map.put("k980", v980); + map.put("k981", v981); + map.put("k982", v982); + map.put("k983", v983); + map.put("k984", v984); + map.put("k985", v985); + map.put("k986", v986); + map.put("k987", v987); + map.put("k988", v988); + map.put("k989", v989); + map.put("k990", v990); + map.put("k991", v991); + map.put("k992", v992); + map.put("k993", v993); + map.put("k994", v994); + map.put("k995", v995); + map.put("k996", v996); + map.put("k997", v997); + map.put("k998", v998); + map.put("k999", v999); + } } diff --git a/checker/jtreg/nullness/Issue1809.java b/checker/jtreg/nullness/Issue1809.java index c93491dd063..56b809ae7f4 100644 --- a/checker/jtreg/nullness/Issue1809.java +++ b/checker/jtreg/nullness/Issue1809.java @@ -16,26 +16,26 @@ @SuppressWarnings("unchecked") abstract class Issue1809 { - abstract Stream concat(Stream... streams); + abstract Stream concat(Stream... streams); - abstract Optional f(); + abstract Optional f(); - private static class A {} + private static class A {} - interface B { - List g(); - } + interface B { + List g(); + } - interface C { - List h(); - } + interface C { + List h(); + } - interface S {} + interface S {} - @SuppressWarnings("nullness") - private Stream xrefsFor(B b) { - return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) - .filter(Optional::isPresent) - .map(Optional::get); - } + @SuppressWarnings("nullness") + private Stream xrefsFor(B b) { + return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) + .filter(Optional::isPresent) + .map(Optional::get); + } } diff --git a/checker/jtreg/nullness/Issue2853.java b/checker/jtreg/nullness/Issue2853.java index ba037ecc5ac..f80d9591a60 100644 --- a/checker/jtreg/nullness/Issue2853.java +++ b/checker/jtreg/nullness/Issue2853.java @@ -6,114 +6,114 @@ */ public class Issue2853 { - abstract static class A { + abstract static class A { - abstract B getB(); + abstract B getB(); - public abstract C getC(); + public abstract C getC(); - public abstract Object getD(); + public abstract Object getD(); - public abstract Object getE(); + public abstract Object getE(); - public abstract Object getF(); + public abstract Object getF(); - public abstract Object getG(); + public abstract Object getG(); - public abstract H getH(); - } - - abstract static class B {} - - abstract static class C { - - abstract Object getI(); - } + public abstract H getH(); + } - static class I { + abstract static class B {} - static class J {} - } + abstract static class C { - abstract static class H { + abstract Object getI(); + } - abstract Object getK(); + static class I { - abstract Object getL(); + static class J {} + } - abstract Object getM(); + abstract static class H { - abstract Object getN(); - } + abstract Object getK(); - abstract static class O {} + abstract Object getL(); - abstract static class J { + abstract Object getM(); - static O f(B b) { - throw new AssertionError(); + abstract Object getN(); } - } - abstract static class K { + abstract static class O {} - abstract Object getL(); - } + abstract static class J { - abstract static class M { - - abstract N getN(); - } - - abstract static class N { + static O f(B b) { + throw new AssertionError(); + } + } - abstract Object f(); - } + abstract static class K { - static class Test { + abstract Object getL(); + } - static final ImmutableSet

          C = - new ImmutableSet.Builder

          () - .add(R.c((A x) -> J.f(x.getB()))) - .add(R.c((A x) -> x.getC().getI())) - .add(R.c((M x) -> x.getN().f())) - .add(R.c((A x) -> x.getH().getK())) - .add(R.c((A x) -> x.getH().getM())) - .add(R.c((A x) -> x.getH().getL())) - .add(R.c((A x) -> x.getH().getN())) - .add(R.c((A x) -> x.getD())) - .add(R.c((A x) -> x.getE())) - .add(R.c((A x) -> x.getE())) - .add(R.c((A x) -> x.getG())) - .add(R.c((K x) -> x.getL())) - .build(); + abstract static class M { - interface P {} + abstract N getN(); + } - interface Q { + abstract static class N { - V get(U message); + abstract Object f(); } - private static class R implements P { - - static R c(Q x) { - throw new AssertionError(); - } + static class Test { + + static final ImmutableSet

          C = + new ImmutableSet.Builder

          () + .add(R.c((A x) -> J.f(x.getB()))) + .add(R.c((A x) -> x.getC().getI())) + .add(R.c((M x) -> x.getN().f())) + .add(R.c((A x) -> x.getH().getK())) + .add(R.c((A x) -> x.getH().getM())) + .add(R.c((A x) -> x.getH().getL())) + .add(R.c((A x) -> x.getH().getN())) + .add(R.c((A x) -> x.getD())) + .add(R.c((A x) -> x.getE())) + .add(R.c((A x) -> x.getE())) + .add(R.c((A x) -> x.getG())) + .add(R.c((K x) -> x.getL())) + .build(); + + interface P {} + + interface Q { + + V get(U message); + } + + private static class R implements P { + + static R c(Q x) { + throw new AssertionError(); + } + } } - } - abstract static class ImmutableSet { + abstract static class ImmutableSet { - static class Builder { + static class Builder { - public Builder add(E... elements) { - throw new AssertionError(); - } + public Builder add(E... elements) { + throw new AssertionError(); + } - public ImmutableSet build() { - throw new AssertionError(); - } + public ImmutableSet build() { + throw new AssertionError(); + } + } } - } } diff --git a/checker/jtreg/nullness/Issue347.java b/checker/jtreg/nullness/Issue347.java index 0ebc27dba7b..29692279f03 100644 --- a/checker/jtreg/nullness/Issue347.java +++ b/checker/jtreg/nullness/Issue347.java @@ -11,24 +11,24 @@ public class Issue347 { - @MonotonicNonNull String mono; + @MonotonicNonNull String mono; - @Nullable String nble; + @Nullable String nble; - void testMono() { - if (mono == null) { - return; + void testMono() { + if (mono == null) { + return; + } + // The object referenced by mono might change, but it can't become null again, even in + // concurrent semantics. + mono.toString(); } - // The object referenced by mono might change, but it can't become null again, even in - // concurrent semantics. - mono.toString(); - } - void testNble() { - if (nble == null) { - return; + void testNble() { + if (nble == null) { + return; + } + // error expected in concurrent semantics only + nble.toString(); } - // error expected in concurrent semantics only - nble.toString(); - } } diff --git a/checker/jtreg/nullness/Issue373.java b/checker/jtreg/nullness/Issue373.java index b0e19bf5598..a11a6697dea 100644 --- a/checker/jtreg/nullness/Issue373.java +++ b/checker/jtreg/nullness/Issue373.java @@ -12,8 +12,8 @@ import java.util.Set; public class Issue373 extends AbstractMap { - @Override - public Set> entrySet() { - return Collections.emptySet(); - } + @Override + public Set> entrySet() { + return Collections.emptySet(); + } } diff --git a/checker/jtreg/nullness/Issue4948.java b/checker/jtreg/nullness/Issue4948.java index 798918a9082..4c33e056843 100644 --- a/checker/jtreg/nullness/Issue4948.java +++ b/checker/jtreg/nullness/Issue4948.java @@ -4,1221 +4,1222 @@ * * @compile/timeout=30 -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Alint Issue4948.java */ -import java.util.stream.Stream; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; +import java.util.stream.Stream; + @SuppressWarnings("nullness") // Check for timeout. public class Issue4948 { - static class MyClass { + static class MyClass { - // No type argumment inference. - static MyClass of2(@Nullable Object[]... p) { - throw new RuntimeException(); - } + // No type argumment inference. + static MyClass of2(@Nullable Object[]... p) { + throw new RuntimeException(); + } - // Poly qualifiers - static MyClass<@PolyNull Object[]> of3(@PolyNull Object[]... p) { - throw new RuntimeException(); + // Poly qualifiers + static MyClass<@PolyNull Object[]> of3(@PolyNull Object[]... p) { + throw new RuntimeException(); + } } - } - static Stream parseTest() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[1], - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTest() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[1], + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTest2() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTest2() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTest4() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTest4() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static MyClass parseTest11() { - return MyClass.of2( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static MyClass parseTest11() { + return MyClass.of2( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static MyClass parseTestB4() { - return MyClass.of3( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}, - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static MyClass parseTestB4() { + return MyClass.of3( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}, + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTestB9() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTestB9() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTestB10() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTestB10() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } - static Stream parseTestB11() { - return Stream.of( - new Object[] {"12:15:24", 125}, - new Object[] {"3:27", 5456}, - new Object[] {"-1:15:4:00", 54665453}, - new Object[] {"124:7:56:00", 45}, - new Object[] {"10", null}, - new Object[] {"10:74:00:00", null}, - new Object[] {"10:00:60:00", null}, - new Object[] {"10:00:00:05", null}, - new Object[] {"12#15:24", null}, - new Object[] {"12:15#24", null}, - new Object[] {"12:15:24#10", null}, - new Object[] {"x12:15:24", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"12:15:24x", null}, - new Object[] {"#####", -2}, - new Object[] {"*****", -1}, - new Object[] {"<<<<<", Integer.MIN_VALUE}, - new Object[] {">>>>>", Integer.MAX_VALUE}); - } + static Stream parseTestB11() { + return Stream.of( + new Object[] {"12:15:24", 125}, + new Object[] {"3:27", 5456}, + new Object[] {"-1:15:4:00", 54665453}, + new Object[] {"124:7:56:00", 45}, + new Object[] {"10", null}, + new Object[] {"10:74:00:00", null}, + new Object[] {"10:00:60:00", null}, + new Object[] {"10:00:00:05", null}, + new Object[] {"12#15:24", null}, + new Object[] {"12:15#24", null}, + new Object[] {"12:15:24#10", null}, + new Object[] {"x12:15:24", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"12:15:24x", null}, + new Object[] {"#####", -2}, + new Object[] {"*****", -1}, + new Object[] {"<<<<<", Integer.MIN_VALUE}, + new Object[] {">>>>>", Integer.MAX_VALUE}); + } } diff --git a/checker/jtreg/nullness/Issue6373.java b/checker/jtreg/nullness/Issue6373.java index 2da6966fd6a..1c17141251a 100644 --- a/checker/jtreg/nullness/Issue6373.java +++ b/checker/jtreg/nullness/Issue6373.java @@ -7,133 +7,133 @@ */ public class Issue6373 { - abstract static class C1< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5> - extends C6 {} - - static class C6 {} - - abstract static class C2< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - RT extends C5> - implements C7 {} - - abstract static class C3< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - R extends C5> {} - - abstract static class C4< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - R extends C5> { - interface I {} - } - - abstract static class C5> implements C7 {} - - interface C7 {} - - abstract static class C8< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5, - RpT extends C5> { - - public static < + abstract static class C1< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5> + extends C6 {} + + static class C6 {} + + abstract static class C2< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + RT extends C5> + implements C7 {} + + abstract static class C3< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> {} + + abstract static class C4< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> { + interface I {} + } + + abstract static class C5> implements C7 {} + + interface C7 {} + + abstract static class C8< C extends C1, Q extends C2, B extends C3, D extends C4, CR extends C5, - RpT extends C5> - Builder n(Q q) { - throw new AssertionError(); + RpT extends C5> { + + public static < + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> + Builder n(Q q) { + throw new AssertionError(); + } + + abstract static class Builder< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> { + + public abstract Builder f(C9 p); + + public C8 b() { + throw new AssertionError(); + } + } } - abstract static class Builder< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5, - RpT extends C5> { + abstract static class C9> {} - public abstract Builder f(C9 p); + static final class C10 extends C9 {} - public C8 b() { - throw new AssertionError(); - } - } - } + abstract static class C11, B extends C11> {} - abstract static class C9> {} + static final class C12 extends C11 { - static final class C10 extends C9 {} + public C10 b() { + throw new AssertionError(); + } + } - abstract static class C11, B extends C11> {} + static class C13 { - static final class C12 extends C11 { + public static final C12 n() { + return new C12(); + } - public C10 b() { - throw new AssertionError(); - } - } + static final class C14 extends C1 {} - static class C13 { + static final class C15 extends C2 {} - public static final C12 n() { - return new C12(); - } + static final class C18 extends C5 {} - static final class C14 extends C1 {} + static class C17 extends C4 implements C4.I, C19 {} - static final class C15 extends C2 {} + static final class C16 extends C3 { - static final class C18 extends C5 {} + public C15 b() { + throw new AssertionError(); + } + } - static class C17 extends C4 implements C4.I, C19 {} + static final C16 q() { + throw new AssertionError(); + } + } - static final class C16 extends C3 { + interface C19 {} - public C15 b() { - throw new AssertionError(); - } + void f() { + var x = C8.n(C13.q().b()).f(C13.n().b()).b(); + var y = x; + x = y; + x = y; + x = y; } - static final C16 q() { - throw new AssertionError(); + void g() { + Object x = C8.n(C13.q().b()).f(C13.n().b()).b(); + Object y = x; + x = y; + x = y; + x = y; } - } - - interface C19 {} - - void f() { - var x = C8.n(C13.q().b()).f(C13.n().b()).b(); - var y = x; - x = y; - x = y; - x = y; - } - - void g() { - Object x = C8.n(C13.q().b()).f(C13.n().b()).b(); - Object y = x; - x = y; - x = y; - x = y; - } } diff --git a/checker/jtreg/nullness/PersistUtil.java b/checker/jtreg/nullness/PersistUtil.java index db88815ebf1..1ce20637f24 100644 --- a/checker/jtreg/nullness/PersistUtil.java +++ b/checker/jtreg/nullness/PersistUtil.java @@ -3,6 +3,7 @@ // TODO: add a @Processor method-annotation to parameterize import com.sun.tools.classfile.ClassFile; + import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -24,97 +25,98 @@ */ public class PersistUtil { - public static String testClassOf(Method m) { - TestClass tc = m.getAnnotation(TestClass.class); - if (tc != null) { - return tc.value(); - } else { - return "Test"; + public static String testClassOf(Method m) { + TestClass tc = m.getAnnotation(TestClass.class); + if (tc != null) { + return tc.value(); + } else { + return "Test"; + } } - } - - public static ClassFile compileAndReturn(String fullFile, String testClass) throws Exception { - File source = writeTestFile(fullFile); - File clazzFile = compileTestFile(source, testClass); - return ClassFile.read(clazzFile); - } - - public static File writeTestFile(String fullFile) throws IOException { - File f = new File("Test.java"); - try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)))) { - out.println(fullFile); + + public static ClassFile compileAndReturn(String fullFile, String testClass) throws Exception { + File source = writeTestFile(fullFile); + File clazzFile = compileTestFile(source, testClass); + return ClassFile.read(clazzFile); } - return f; - } - - public static File compileTestFile(File f, String testClass) { - int rc = - com.sun.tools.javac.Main.compile( - new String[] { - "-AnoJreVersionCheck", - "-g", - "-processor", - "org.checkerframework.checker.nullness.NullnessChecker", - f.getPath() - }); - if (rc != 0) { - throw new Error("compilation failed. rc=" + rc); + + public static File writeTestFile(String fullFile) throws IOException { + File f = new File("Test.java"); + try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)))) { + out.println(fullFile); + } + return f; } - File result = new File(f.getParent(), testClass + ".class"); - - // This diagnostic code preserves temporary files and prints the paths where they are - // preserved. - if (false) { - try { - File tempDir = new File(System.getProperty("java.io.tmpdir")); - File fCopy = File.createTempFile("FCopy", ".java", tempDir); - File resultCopy = File.createTempFile("FCopy", ".class", tempDir); - // REPLACE_EXISTING is essential in the `Files.copy()` calls because createTempFile - // actually creates a file in addition to returning its name. - Files.copy(f.toPath(), fCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); - Files.copy(result.toPath(), resultCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); - System.out.printf("comileTestFile: copied to %s %s%n", fCopy, resultCopy); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + public static File compileTestFile(File f, String testClass) { + int rc = + com.sun.tools.javac.Main.compile( + new String[] { + "-AnoJreVersionCheck", + "-g", + "-processor", + "org.checkerframework.checker.nullness.NullnessChecker", + f.getPath() + }); + if (rc != 0) { + throw new Error("compilation failed. rc=" + rc); + } + + File result = new File(f.getParent(), testClass + ".class"); + + // This diagnostic code preserves temporary files and prints the paths where they are + // preserved. + if (false) { + try { + File tempDir = new File(System.getProperty("java.io.tmpdir")); + File fCopy = File.createTempFile("FCopy", ".java", tempDir); + File resultCopy = File.createTempFile("FCopy", ".class", tempDir); + // REPLACE_EXISTING is essential in the `Files.copy()` calls because createTempFile + // actually creates a file in addition to returning its name. + Files.copy(f.toPath(), fCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy( + result.toPath(), resultCopy.toPath(), StandardCopyOption.REPLACE_EXISTING); + System.out.printf("comileTestFile: copied to %s %s%n", fCopy, resultCopy); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + return result; } - return result; - } + public static String wrap(String compact) { + StringJoiner sj = new StringJoiner(System.lineSeparator()); - public static String wrap(String compact) { - StringJoiner sj = new StringJoiner(System.lineSeparator()); + // Automatically import java.util + sj.add(""); + sj.add("import java.util.*;"); + sj.add("import java.lang.annotation.*;"); - // Automatically import java.util - sj.add(""); - sj.add("import java.util.*;"); - sj.add("import java.lang.annotation.*;"); + // And the Nullness qualifiers + sj.add("import org.checkerframework.framework.qual.DefaultQualifier;"); + sj.add("import org.checkerframework.checker.nullness.qual.*;"); + sj.add("import org.checkerframework.dataflow.qual.*;"); - // And the Nullness qualifiers - sj.add("import org.checkerframework.framework.qual.DefaultQualifier;"); - sj.add("import org.checkerframework.checker.nullness.qual.*;"); - sj.add("import org.checkerframework.dataflow.qual.*;"); + sj.add(""); + boolean isSnippet = + !(compact.startsWith("class") || compact.contains(" class")) + && !compact.contains("interface") + && !compact.contains("enum"); - sj.add(""); - boolean isSnippet = - !(compact.startsWith("class") || compact.contains(" class")) - && !compact.contains("interface") - && !compact.contains("enum"); + if (isSnippet) { + sj.add("class Test {"); + } - if (isSnippet) { - sj.add("class Test {"); - } + sj.add(compact); - sj.add(compact); + if (isSnippet) { + sj.add("}"); + sj.add(""); + } - if (isSnippet) { - sj.add("}"); - sj.add(""); + return sj.toString(); } - - return sj.toString(); - } } /** @@ -124,5 +126,5 @@ public static String wrap(String compact) { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TestClass { - String value() default "Test"; + String value() default "Test"; } diff --git a/checker/jtreg/nullness/UncheckedWarning.java b/checker/jtreg/nullness/UncheckedWarning.java index 92d5c98a55a..64d11743e19 100644 --- a/checker/jtreg/nullness/UncheckedWarning.java +++ b/checker/jtreg/nullness/UncheckedWarning.java @@ -10,17 +10,17 @@ import java.util.List; class Test { - List foo() { - List ret = new ArrayList<>(); - ret.add("Hi there!"); - return (List) ret; - } + List foo() { + List ret = new ArrayList<>(); + ret.add("Hi there!"); + return (List) ret; + } } public class UncheckedWarning { - public static void main(String[] args) { - Test ti = new Test<>(); - List ls = ti.foo(); - Integer i = ls.get(0); - } + public static void main(String[] args) { + Test ti = new Test<>(); + List ls = ti.foo(); + Integer i = ls.get(0); + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java index 786b89d0c15..bce171b1e3c 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixed.java @@ -8,8 +8,8 @@ @SuppressWarnings("nullness:unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java index 9b654510aa7..c3c23a99d98 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassPrefixedRequirePrefix.java @@ -8,8 +8,8 @@ @SuppressWarnings("nullness:unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java index 45549b05b9d..4aa9fc66512 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixed.java @@ -8,8 +8,8 @@ @SuppressWarnings("unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return.type.incompatible") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return.type.incompatible") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java index 465c6ec1e9e..9365586d354 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java +++ b/checker/jtreg/nullness/UnneededSuppressionsClassUnprefixedRequirePrefix.java @@ -8,8 +8,8 @@ @SuppressWarnings("unneeded.suppression") class UnneededSuppressionsClassAnnotated { - @SuppressWarnings("nullness:return.type.incompatible") - public String getClassAndUid0() { - return "hello"; - } + @SuppressWarnings("nullness:return.type.incompatible") + public String getClassAndUid0() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsTest.java b/checker/jtreg/nullness/UnneededSuppressionsTest.java index e2b5c602b61..81202e12796 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsTest.java +++ b/checker/jtreg/nullness/UnneededSuppressionsTest.java @@ -7,28 +7,28 @@ class UnneededSuppressionsTest { - @SuppressWarnings({"nullness:return.type.incompatible"}) - public String getClassAndUid1() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible"}) + public String getClassAndUid1() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) - public String getClassAndUid2() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) + public String getClassAndUid2() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) - public String getClassAndUid3() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) + public String getClassAndUid3() { + return "hello"; + } - @SuppressWarnings({"unneeded.suppression", "nullness:return.type.incompatible"}) - public String getClassAndUid5() { - return "hello"; - } + @SuppressWarnings({"unneeded.suppression", "nullness:return.type.incompatible"}) + public String getClassAndUid5() { + return "hello"; + } - @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) - public String getClassAndUid6() { - return "hello"; - } + @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) + public String getClassAndUid6() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java b/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java index f822b77ed9c..a526a029f25 100644 --- a/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java +++ b/checker/jtreg/nullness/UnneededSuppressionsTestRequirePrefix.java @@ -7,28 +7,31 @@ class UnneededSuppressionsTest { - @SuppressWarnings({"nullness:return.type.incompatible"}) - public String getClassAndUid1() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible"}) + public String getClassAndUid1() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) - public String getClassAndUid2() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "unneeded.suppression"}) + public String getClassAndUid2() { + return "hello"; + } - @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) - public String getClassAndUid3() { - return "hello"; - } + @SuppressWarnings({"nullness:return.type.incompatible", "nullness:unneeded.suppression"}) + public String getClassAndUid3() { + return "hello"; + } - @SuppressWarnings({"unneeded.suppression.type.incompatible", "nullness:return.type.incompatible"}) - public String getClassAndUid5() { - return "hello"; - } + @SuppressWarnings({ + "unneeded.suppression.type.incompatible", + "nullness:return.type.incompatible" + }) + public String getClassAndUid5() { + return "hello"; + } - @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) - public String getClassAndUid6() { - return "hello"; - } + @SuppressWarnings({"nullness:unneeded.suppression", "nullness:return.type.incompatible"}) + public String getClassAndUid6() { + return "hello"; + } } diff --git a/checker/jtreg/nullness/annotationsOnExtends/Other.java b/checker/jtreg/nullness/annotationsOnExtends/Other.java index 67a8736a7f8..f0cc1b66948 100644 --- a/checker/jtreg/nullness/annotationsOnExtends/Other.java +++ b/checker/jtreg/nullness/annotationsOnExtends/Other.java @@ -7,8 +7,8 @@ */ public class Other { - void foo() { - Test other = null; - Test2 other2 = null; - } + void foo() { + Test other = null; + Test2 other2 = null; + } } diff --git a/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java index ffb839371cc..431446ff681 100644 --- a/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java +++ b/checker/jtreg/nullness/constructor-initialization/DefaultConstructor.java @@ -7,10 +7,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public class DefaultConstructor { - Object nullObject; - @MonotonicNonNull Object lazyField; + Object nullObject; + @MonotonicNonNull Object lazyField; - public Object getNull() { - return nullObject; - } + public Object getNull() { + return nullObject; + } } diff --git a/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java index 01addfea9a6..43023cae6b3 100644 --- a/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java +++ b/checker/jtreg/nullness/constructor-initialization/NonDefaultConstructor.java @@ -7,29 +7,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public class NonDefaultConstructor { - Object nonNull = 4; - Object nullObject; - @MonotonicNonNull Object lazyField; + Object nonNull = 4; + Object nullObject; + @MonotonicNonNull Object lazyField; - // error doesn't initialize nullObject - public NonDefaultConstructor() {} + // error doesn't initialize nullObject + public NonDefaultConstructor() {} - // error doesn't initialize nullObject - public NonDefaultConstructor(int i) { - lazyField = "m"; - } + // error doesn't initialize nullObject + public NonDefaultConstructor(int i) { + lazyField = "m"; + } - // OK, lazyField is lazy! - public NonDefaultConstructor(double a) { - nullObject = "n"; - } + // OK, lazyField is lazy! + public NonDefaultConstructor(double a) { + nullObject = "n"; + } - public NonDefaultConstructor(String s) { - nullObject = "a"; - lazyField = "m"; - } + public NonDefaultConstructor(String s) { + nullObject = "a"; + lazyField = "m"; + } - public Object getNull() { - return nullObject; - } + public Object getNull() { + return nullObject; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Classes.java b/checker/jtreg/nullness/defaultsPersist/Classes.java index 49c0903ca6f..3064a45e285 100644 --- a/checker/jtreg/nullness/defaultsPersist/Classes.java +++ b/checker/jtreg/nullness/defaultsPersist/Classes.java @@ -11,234 +11,234 @@ public class Classes { - /* TODO: store extends/implements in TypesIntoElements. - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), - }) - public String extendsDefault1() { - return "class Test {}"; - } + /* TODO: store extends/implements in TypesIntoElements. + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), + }) + public String extendsDefault1() { + return "class Test {}"; + } - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), - }) - public String extendsDefault2() { - return "class Test extends Object {}"; - } + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), + }) + public String extendsDefault2() { + return "class Test extends Object {}"; + } - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=0), - @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=0), - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=0), - }) - public String extendsDefault3() { - return "class Test implements java.io.Serializable {}"; - } - */ + @TADescriptions({ + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=-1), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = CLASS_EXTENDS, typeIndex=0), + @TADescription(annotation = "org/checkerframework/checker/initialization/qual/Initialized", type = CLASS_EXTENDS, typeIndex=0), + @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", type = CLASS_EXTENDS, typeIndex=0), + }) + public String extendsDefault3() { + return "class Test implements java.io.Serializable {}"; + } + */ - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams1() { - return "class Test {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams1() { + return "class Test {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams2() { - return "class Test {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams2() { + return "class Test {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams3() { - return "class Test> {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams3() { + return "class Test> {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER, - paramIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER, - paramIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER, - paramIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 1, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 1, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = CLASS_TYPE_PARAMETER_BOUND, - paramIndex = 1, - boundIndex = 1), - // Annotations on the implicit constructor, which the test ignores. - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", - // type = METHOD_RETURN), - // @TADescription( - // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String typeParams4() { - return "class Test> {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER, + paramIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER, + paramIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER, + paramIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 1, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 1, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = CLASS_TYPE_PARAMETER_BOUND, + paramIndex = 1, + boundIndex = 1), + // Annotations on the implicit constructor, which the test ignores. + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/initialization/qual/UnderInitialization", + // type = METHOD_RETURN), + // @TADescription( + // annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String typeParams4() { + return "class Test> {}"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Constructors.java b/checker/jtreg/nullness/defaultsPersist/Constructors.java index a5389af6d59..fdc5c147ca6 100644 --- a/checker/jtreg/nullness/defaultsPersist/Constructors.java +++ b/checker/jtreg/nullness/defaultsPersist/Constructors.java @@ -14,197 +14,197 @@ public class Constructors { - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - }) - public String paramDefault1() { - return "Test(Object o) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + }) + public String paramDefault1() { + return "Test(Object o) {}"; + } - @TADescriptions({ - // Should there be defaults? - // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", - // type = METHOD_RETURN), - // @TADescription(annotation = - // "org/checkerframework/checker/initialization/qual/Initialized", type = METHOD_RETURN), - // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - // type = METHOD_RETURN), - }) - public String retDefault1() { - return "Test() {}"; - } + @TADescriptions({ + // Should there be defaults? + // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", + // type = METHOD_RETURN), + // @TADescription(annotation = + // "org/checkerframework/checker/initialization/qual/Initialized", type = METHOD_RETURN), + // @TADescription(annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + // type = METHOD_RETURN), + }) + public String retDefault1() { + return "Test() {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), - }) - public String throwsDefault1() { - return "Test() throws Throwable {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), + }) + public String throwsDefault1() { + return "Test() throws Throwable {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 1), - }) - public String throwsDefault2() { - return "Test() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 1), + }) + public String throwsDefault2() { + return "Test() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_RECEIVER), - }) - @TestClass("Outer$Inner") - public String recvDefault1() { - return "class Outer {" + " class Inner {" + " Inner(Outer Outer.this) {}" + " }" + "}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_RECEIVER), + }) + @TestClass("Outer$Inner") + public String recvDefault1() { + return "class Outer {" + " class Inner {" + " Inner(Outer Outer.this) {}" + " }" + "}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams1() { - return " Test(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams1() { + return " Test(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams2() { - return " Test(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams2() { + return " Test(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - }) - public String typeParams3() { - return "> Test(M2 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + }) + public String typeParams3() { + return "> Test(M2 p) {}"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Driver.java b/checker/jtreg/nullness/defaultsPersist/Driver.java index 18de0aeb16d..cd45901ec97 100644 --- a/checker/jtreg/nullness/defaultsPersist/Driver.java +++ b/checker/jtreg/nullness/defaultsPersist/Driver.java @@ -8,6 +8,7 @@ import com.sun.tools.classfile.ClassFile; import com.sun.tools.classfile.TypeAnnotation; import com.sun.tools.classfile.TypeAnnotation.TargetType; + import java.io.PrintStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -19,198 +20,201 @@ public class Driver { - private static final PrintStream out = System.out; - - // The argument is in the format expected by Class.forName(). - public static void main(String[] args) throws Exception { - if (args.length != 1) { - throw new IllegalArgumentException("Usage: java Driver "); - } - String name = args[0]; - Class clazz = Class.forName(name); - new Driver().runDriver(clazz.newInstance()); - } - - protected void runDriver(Object object) throws Exception { - int passed = 0, failed = 0; - Class clazz = object.getClass(); - out.println("Tests for " + clazz.getName()); - - // Find methods - for (Method method : clazz.getMethods()) { - List expected = expectedOf(method); - if (expected == null) { - continue; - } - if (method.getReturnType() != String.class) { - throw new IllegalArgumentException("Test method needs to return a string: " + method); - } - String testClass = PersistUtil.testClassOf(method); - - try { - String compact = (String) method.invoke(object); - String fullFile = PersistUtil.wrap(compact); - ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); - boolean ignoreConstructors = !clazz.getName().equals("Constructors"); - List actual = - ReferenceInfoUtil.extendedAnnotationsOf(cf, ignoreConstructors); - String diagnostic = - String.join( - "; ", - "Tests for " + clazz.getName(), - "compact=" + compact, - "fullFile=" + fullFile, - "testClass=" + testClass); - ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); - out.println("PASSED: " + method.getName()); - ++passed; - } catch (Throwable e) { - out.println("FAILED: " + method.getName()); - out.println(" " + e); - ++failed; - } - } - - out.println(); - int total = passed + failed; - out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); - - out.flush(); - - if (failed != 0) { - throw new RuntimeException(failed + " tests failed"); - } - } - - private List expectedOf(Method m) { - TADescription ta = m.getAnnotation(TADescription.class); - TADescriptions tas = m.getAnnotation(TADescriptions.class); - - if (ta == null && tas == null) { - return null; - } - - List result = new ArrayList<>(); - - if (ta != null) { - result.add(expectedOf(ta)); - } - - if (tas != null) { - for (TADescription a : tas.value()) { - result.add(expectedOf(a)); - } - } - - return result; - } - - private AnnoPosPair expectedOf(TADescription d) { - String annoName = d.annotation(); - - TypeAnnotation.Position p = new TypeAnnotation.Position(); - p.type = d.type(); - if (d.offset() != NOT_SET) { - p.offset = d.offset(); - } - if (d.lvarOffset().length != 0) { - p.lvarOffset = d.lvarOffset(); - } - if (d.lvarLength().length != 0) { - p.lvarLength = d.lvarLength(); - } - if (d.lvarIndex().length != 0) { - p.lvarIndex = d.lvarIndex(); - } - if (d.boundIndex() != NOT_SET) { - p.bound_index = d.boundIndex(); - } - if (d.paramIndex() != NOT_SET) { - p.parameter_index = d.paramIndex(); - } - if (d.typeIndex() != NOT_SET) { - p.type_index = d.typeIndex(); - } - if (d.exceptionIndex() != NOT_SET) { - p.exception_index = d.exceptionIndex(); - } - if (d.genericLocation().length != 0) { - p.location = TypeAnnotation.Position.getTypePathFromBinary(wrapIntArray(d.genericLocation())); - } - - return AnnoPosPair.of(annoName, p); - } - - private List wrapIntArray(int[] ints) { - List list = new ArrayList<>(ints.length); - for (int i : ints) { - list.add(i); - } - return list; - } - - public static final int NOT_SET = -888; + private static final PrintStream out = System.out; + + // The argument is in the format expected by Class.forName(). + public static void main(String[] args) throws Exception { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: java Driver "); + } + String name = args[0]; + Class clazz = Class.forName(name); + new Driver().runDriver(clazz.newInstance()); + } + + protected void runDriver(Object object) throws Exception { + int passed = 0, failed = 0; + Class clazz = object.getClass(); + out.println("Tests for " + clazz.getName()); + + // Find methods + for (Method method : clazz.getMethods()) { + List expected = expectedOf(method); + if (expected == null) { + continue; + } + if (method.getReturnType() != String.class) { + throw new IllegalArgumentException( + "Test method needs to return a string: " + method); + } + String testClass = PersistUtil.testClassOf(method); + + try { + String compact = (String) method.invoke(object); + String fullFile = PersistUtil.wrap(compact); + ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); + boolean ignoreConstructors = !clazz.getName().equals("Constructors"); + List actual = + ReferenceInfoUtil.extendedAnnotationsOf(cf, ignoreConstructors); + String diagnostic = + String.join( + "; ", + "Tests for " + clazz.getName(), + "compact=" + compact, + "fullFile=" + fullFile, + "testClass=" + testClass); + ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); + out.println("PASSED: " + method.getName()); + ++passed; + } catch (Throwable e) { + out.println("FAILED: " + method.getName()); + out.println(" " + e); + ++failed; + } + } + + out.println(); + int total = passed + failed; + out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); + + out.flush(); + + if (failed != 0) { + throw new RuntimeException(failed + " tests failed"); + } + } + + private List expectedOf(Method m) { + TADescription ta = m.getAnnotation(TADescription.class); + TADescriptions tas = m.getAnnotation(TADescriptions.class); + + if (ta == null && tas == null) { + return null; + } + + List result = new ArrayList<>(); + + if (ta != null) { + result.add(expectedOf(ta)); + } + + if (tas != null) { + for (TADescription a : tas.value()) { + result.add(expectedOf(a)); + } + } + + return result; + } + + private AnnoPosPair expectedOf(TADescription d) { + String annoName = d.annotation(); + + TypeAnnotation.Position p = new TypeAnnotation.Position(); + p.type = d.type(); + if (d.offset() != NOT_SET) { + p.offset = d.offset(); + } + if (d.lvarOffset().length != 0) { + p.lvarOffset = d.lvarOffset(); + } + if (d.lvarLength().length != 0) { + p.lvarLength = d.lvarLength(); + } + if (d.lvarIndex().length != 0) { + p.lvarIndex = d.lvarIndex(); + } + if (d.boundIndex() != NOT_SET) { + p.bound_index = d.boundIndex(); + } + if (d.paramIndex() != NOT_SET) { + p.parameter_index = d.paramIndex(); + } + if (d.typeIndex() != NOT_SET) { + p.type_index = d.typeIndex(); + } + if (d.exceptionIndex() != NOT_SET) { + p.exception_index = d.exceptionIndex(); + } + if (d.genericLocation().length != 0) { + p.location = + TypeAnnotation.Position.getTypePathFromBinary( + wrapIntArray(d.genericLocation())); + } + + return AnnoPosPair.of(annoName, p); + } + + private List wrapIntArray(int[] ints) { + List list = new ArrayList<>(ints.length); + for (int i : ints) { + list.add(i); + } + return list; + } + + public static final int NOT_SET = -888; } /** A pair of an annotation name and a position. */ class AnnoPosPair { - /** The first element of the pair. */ - public final String first; - - /** The second element of the pair. */ - public final TypeAnnotation.Position second; - - /** - * Creates a new immutable pair. Clients should use {@link #of}. - * - * @param first the first element of the pair - * @param second the second element of the pair - */ - private AnnoPosPair(String first, TypeAnnotation.Position second) { - this.first = first; - this.second = second; - } - - /** - * Creates a new immutable pair. - * - * @param first first argument - * @param second second argument - * @return a pair of the values (first, second) - */ - public static AnnoPosPair of(String first, TypeAnnotation.Position second) { - return new AnnoPosPair(first, second); - } + /** The first element of the pair. */ + public final String first; + + /** The second element of the pair. */ + public final TypeAnnotation.Position second; + + /** + * Creates a new immutable pair. Clients should use {@link #of}. + * + * @param first the first element of the pair + * @param second the second element of the pair + */ + private AnnoPosPair(String first, TypeAnnotation.Position second) { + this.first = first; + this.second = second; + } + + /** + * Creates a new immutable pair. + * + * @param first first argument + * @param second second argument + * @return a pair of the values (first, second) + */ + public static AnnoPosPair of(String first, TypeAnnotation.Position second) { + return new AnnoPosPair(first, second); + } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TADescription { - String annotation(); + String annotation(); - TargetType type(); + TargetType type(); - int offset() default Driver.NOT_SET; + int offset() default Driver.NOT_SET; - int[] lvarOffset() default {}; + int[] lvarOffset() default {}; - int[] lvarLength() default {}; + int[] lvarLength() default {}; - int[] lvarIndex() default {}; + int[] lvarIndex() default {}; - int boundIndex() default Driver.NOT_SET; + int boundIndex() default Driver.NOT_SET; - int paramIndex() default Driver.NOT_SET; + int paramIndex() default Driver.NOT_SET; - int typeIndex() default Driver.NOT_SET; + int typeIndex() default Driver.NOT_SET; - int exceptionIndex() default Driver.NOT_SET; + int exceptionIndex() default Driver.NOT_SET; - int[] genericLocation() default {}; + int[] genericLocation() default {}; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TADescriptions { - TADescription[] value() default {}; + TADescription[] value() default {}; } diff --git a/checker/jtreg/nullness/defaultsPersist/Fields.java b/checker/jtreg/nullness/defaultsPersist/Fields.java index ad4b02c9e79..95ecc80b88b 100644 --- a/checker/jtreg/nullness/defaultsPersist/Fields.java +++ b/checker/jtreg/nullness/defaultsPersist/Fields.java @@ -11,227 +11,239 @@ public class Fields { - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - }) - public String fieldDefault() { - return "Object f = new Object();"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + }) + public String fieldDefault() { + return "Object f = new Object();"; + } - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - }) - public String fieldDefaultOneExplicit() { - return "@NonNull Object f = new Object();"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + }) + public String fieldDefaultOneExplicit() { + return "@NonNull Object f = new Object();"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - }) - public String fieldWithDefaultQualifier() { - return "@DefaultQualifier(Nullable.class)" + System.lineSeparator() + " Object f;"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + }) + public String fieldWithDefaultQualifier() { + return "@DefaultQualifier(Nullable.class)" + System.lineSeparator() + " Object f;"; + } - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0}), - }) - public String fieldArray1() { - return "String[] sa = new String[1];"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0}), + }) + public String fieldArray1() { + return "String[] sa = new String[1];"; + } - @TADescriptions({ - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {0, 0, 0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {0, 0, 0, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {0, 0, 0, 0, 0, 0}), - }) - public String fieldArray2() { - return "String[] @Nullable [][] saaa = new String[1][][];"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {0, 0, 0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {0, 0, 0, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {0, 0, 0, 0, 0, 0}), + }) + public String fieldArray2() { + return "String[] @Nullable [][] saaa = new String[1][][];"; + } - @TADescriptions({ - // in front of the java.util.List - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), + @TADescriptions({ + // in front of the java.util.List + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), - // in front of Object //TODO: NEXT ANNO CHANGE TO NULLABLE WHEN WE GET JDK WORKING WITH THIS - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0}), + // in front of Object //TODO: NEXT ANNO CHANGE TO NULLABLE WHEN WE GET JDK WORKING WITH THIS + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0}), - // in front of the wildcard (?) - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0}), - }) - public String wildcards1() { - return "java.util.List f = new java.util.ArrayList<>();"; - } + // in front of the wildcard (?) + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0}), + }) + public String wildcards1() { + return "java.util.List f = new java.util.ArrayList<>();"; + } - @TADescriptions({ - // in front of the first java.util.List - @TADescription(annotation = "org/checkerframework/checker/nullness/qual/NonNull", type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD), + @TADescriptions({ + // in front of the first java.util.List + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD), - // in front of the wildcard (?) - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0}), + // in front of the wildcard (?) + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0}), - // in front of the second java.util.List - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = FIELD, - genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), - }) - public String wildcards2() { - return "java.util.List> f = new" - + " java.util.ArrayList<>();"; - } + // in front of the second java.util.List + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = FIELD, + genericLocation = {3, 0, 2, 0, 3, 0, 0, 0}), + }) + public String wildcards2() { + return "java.util.List> f = new" + + " java.util.ArrayList<>();"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/Methods.java b/checker/jtreg/nullness/defaultsPersist/Methods.java index 8b2bf2ffd1f..8aa50c2d270 100644 --- a/checker/jtreg/nullness/defaultsPersist/Methods.java +++ b/checker/jtreg/nullness/defaultsPersist/Methods.java @@ -15,198 +15,198 @@ public class Methods { - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_FORMAL_PARAMETER, - paramIndex = 0), - }) - public String paramDefault1() { - return "void pm1(Object o) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_FORMAL_PARAMETER, + paramIndex = 0), + }) + public String paramDefault1() { + return "void pm1(Object o) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_RETURN), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_RETURN), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_RETURN), - }) - public String retDefault1() { - return "Object rm1() { return new Object(); }"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_RETURN), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_RETURN), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_RETURN), + }) + public String retDefault1() { + return "Object rm1() { return new Object(); }"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), - }) - public String throwsDefault1() { - return "void tm1() throws Throwable {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), + }) + public String throwsDefault1() { + return "void tm1() throws Throwable {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 0), // from KeyFor - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = THROWS, - typeIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = THROWS, - typeIndex = 1), - }) - public String throwsDefault2() { - return "void tm2() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 0), // from KeyFor + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = THROWS, + typeIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = THROWS, + typeIndex = 1), + }) + public String throwsDefault2() { + return "void tm2() throws ArrayIndexOutOfBoundsException, NullPointerException {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_RECEIVER), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_RECEIVER), - }) - public String recvDefault1() { - return "void rd1(Test this) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_RECEIVER), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_RECEIVER), + }) + public String recvDefault1() { + return "void rd1(Test this) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/Nullable", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams1() { - return " void foo(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/Nullable", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams1() { + return " void foo(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 0), - }) - public String typeParams2() { - return " void foo(M1 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 0), + }) + public String typeParams2() { + return " void foo(M1 p) {}"; + } - @TADescriptions({ - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER, - paramIndex = 0), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/NonNull", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/initialization/qual/Initialized", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - @TADescription( - annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", - type = METHOD_TYPE_PARAMETER_BOUND, - paramIndex = 0, - boundIndex = 1), - }) - public String typeParams3() { - return "> void bar(M2 p) {}"; - } + @TADescriptions({ + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER, + paramIndex = 0), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/NonNull", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/initialization/qual/Initialized", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + @TADescription( + annotation = "org/checkerframework/checker/nullness/qual/UnknownKeyFor", + type = METHOD_TYPE_PARAMETER_BOUND, + paramIndex = 0, + boundIndex = 1), + }) + public String typeParams3() { + return "> void bar(M2 p) {}"; + } } diff --git a/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java index 4f03292b6fb..d7044639bb0 100644 --- a/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java +++ b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java @@ -11,254 +11,264 @@ import com.sun.tools.classfile.Method; import com.sun.tools.classfile.RuntimeTypeAnnotations_attribute; import com.sun.tools.classfile.TypeAnnotation; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ReferenceInfoUtil { - public static final int IGNORE_VALUE = -321; - - /** If true, don't collect annotations on constructors. */ - boolean ignoreConstructors; - - /** - * Creates a new ReferenceInfoUtil. - * - * @param ignoreConstructors if true, don't collect annotations on constructor - */ - public ReferenceInfoUtil(boolean ignoreConstructors) { - this.ignoreConstructors = ignoreConstructors; - } - - public static List extendedAnnotationsOf( - ClassFile cf, boolean ignoreConstructors) { - ReferenceInfoUtil riu = new ReferenceInfoUtil(ignoreConstructors); - List annos = new ArrayList<>(); - riu.findAnnotations(cf, annos); - return annos; - } - - /////////////////// Extract type annotations ////////////////// - private void findAnnotations(ClassFile cf, List annos) { - findAnnotations(cf, Attribute.RuntimeVisibleTypeAnnotations, annos); - findAnnotations(cf, Attribute.RuntimeInvisibleTypeAnnotations, annos); - - for (Field f : cf.fields) { - findAnnotations(cf, f, annos); - } - for (Method m : cf.methods) { - String methodName; - try { - methodName = m.getName(cf.constant_pool); - } catch (Exception e) { - throw new Error(e); - } - // This method, `findAnnotations`, aims to extract annotations from one method. - // In JDK 17, constructors are included in ClassFile.methods(); in JDK 11, they are not. - // Therefore, this if statement is required in JDK 17, and has no effect in JDK 11. - if (ignoreConstructors && methodName.equals("")) { - continue; - } - - findAnnotations(cf, m, annos); - } - } - - private static void findAnnotations(ClassFile cf, Method m, List annos) { - findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); - findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); - } - - private static void findAnnotations(ClassFile cf, Field m, List annos) { - findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); - findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); - } - - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's name. - */ - private static void findAnnotations(ClassFile cf, String name, List annos) { - int index = cf.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = cf.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); - } - } - - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's name. - */ - private static void findAnnotations( - ClassFile cf, Method m, String name, List annos) { - int index = m.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = m.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); + public static final int IGNORE_VALUE = -321; + + /** If true, don't collect annotations on constructors. */ + boolean ignoreConstructors; + + /** + * Creates a new ReferenceInfoUtil. + * + * @param ignoreConstructors if true, don't collect annotations on constructor + */ + public ReferenceInfoUtil(boolean ignoreConstructors) { + this.ignoreConstructors = ignoreConstructors; } - int cindex = m.attributes.getIndex(cf.constant_pool, Attribute.Code); - if (cindex != -1) { - Attribute cattr = m.attributes.get(cindex); - assert cattr instanceof Code_attribute; - Code_attribute cAttr = (Code_attribute) cattr; - index = cAttr.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = cAttr.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); - } + public static List extendedAnnotationsOf( + ClassFile cf, boolean ignoreConstructors) { + ReferenceInfoUtil riu = new ReferenceInfoUtil(ignoreConstructors); + List annos = new ArrayList<>(); + riu.findAnnotations(cf, annos); + return annos; } - } - - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's name. - */ - private static void findAnnotations( - ClassFile cf, Field m, String name, List annos) { - int index = m.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = m.attributes.get(index); - assert attr instanceof RuntimeTypeAnnotations_attribute; - RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; - annos.addAll(Arrays.asList(tAttr.annotations)); + + /////////////////// Extract type annotations ////////////////// + private void findAnnotations(ClassFile cf, List annos) { + findAnnotations(cf, Attribute.RuntimeVisibleTypeAnnotations, annos); + findAnnotations(cf, Attribute.RuntimeInvisibleTypeAnnotations, annos); + + for (Field f : cf.fields) { + findAnnotations(cf, f, annos); + } + for (Method m : cf.methods) { + String methodName; + try { + methodName = m.getName(cf.constant_pool); + } catch (Exception e) { + throw new Error(e); + } + // This method, `findAnnotations`, aims to extract annotations from one method. + // In JDK 17, constructors are included in ClassFile.methods(); in JDK 11, they are not. + // Therefore, this if statement is required in JDK 17, and has no effect in JDK 11. + if (ignoreConstructors && methodName.equals("")) { + continue; + } + + findAnnotations(cf, m, annos); + } } - } - /////////////////////// Equality testing ///////////////////// - private static boolean areEquals(int a, int b) { - return a == b || a == IGNORE_VALUE || b == IGNORE_VALUE; - } + private static void findAnnotations(ClassFile cf, Method m, List annos) { + findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); + findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); + } - private static boolean areEquals(int[] a, int[] a2) { - if (a == a2) { - return true; + private static void findAnnotations(ClassFile cf, Field m, List annos) { + findAnnotations(cf, m, Attribute.RuntimeVisibleTypeAnnotations, annos); + findAnnotations(cf, m, Attribute.RuntimeInvisibleTypeAnnotations, annos); } - if (a == null || a2 == null) { - return false; + + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's + * name. + */ + private static void findAnnotations(ClassFile cf, String name, List annos) { + int index = cf.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = cf.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); + } } - int length = a.length; - if (a2.length != length) { - return false; + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's + * name. + */ + private static void findAnnotations( + ClassFile cf, Method m, String name, List annos) { + int index = m.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = m.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); + } + + int cindex = m.attributes.getIndex(cf.constant_pool, Attribute.Code); + if (cindex != -1) { + Attribute cattr = m.attributes.get(cindex); + assert cattr instanceof Code_attribute; + Code_attribute cAttr = (Code_attribute) cattr; + index = cAttr.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = cAttr.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); + } + } } - for (int i = 0; i < length; i++) { - if (areEquals(a[i], a2[i])) { - return false; - } + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's + * name. + */ + private static void findAnnotations( + ClassFile cf, Field m, String name, List annos) { + int index = m.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = m.attributes.get(index); + assert attr instanceof RuntimeTypeAnnotations_attribute; + RuntimeTypeAnnotations_attribute tAttr = (RuntimeTypeAnnotations_attribute) attr; + annos.addAll(Arrays.asList(tAttr.annotations)); + } } - return true; - } + /////////////////////// Equality testing ///////////////////// + private static boolean areEquals(int a, int b) { + return a == b || a == IGNORE_VALUE || b == IGNORE_VALUE; + } - public static boolean areEquals(TypeAnnotation.Position p1, TypeAnnotation.Position p2) { - if (p1 == p2) { - return true; + private static boolean areEquals(int[] a, int[] a2) { + if (a == a2) { + return true; + } + if (a == null || a2 == null) { + return false; + } + + int length = a.length; + if (a2.length != length) { + return false; + } + + for (int i = 0; i < length; i++) { + if (areEquals(a[i], a2[i])) { + return false; + } + } + + return true; } - if (p1 == null || p2 == null) { - return false; + + public static boolean areEquals(TypeAnnotation.Position p1, TypeAnnotation.Position p2) { + if (p1 == p2) { + return true; + } + if (p1 == null || p2 == null) { + return false; + } + + boolean result = + ((p1.type == p2.type) + && (p1.location.equals(p2.location)) + && areEquals(p1.offset, p2.offset) + && areEquals(p1.lvarOffset, p2.lvarOffset) + && areEquals(p1.lvarLength, p2.lvarLength) + && areEquals(p1.lvarIndex, p2.lvarIndex) + && areEquals(p1.bound_index, p2.bound_index) + && areEquals(p1.parameter_index, p2.parameter_index) + && areEquals(p1.type_index, p2.type_index) + && areEquals(p1.exception_index, p2.exception_index)); + return result; } - boolean result = - ((p1.type == p2.type) - && (p1.location.equals(p2.location)) - && areEquals(p1.offset, p2.offset) - && areEquals(p1.lvarOffset, p2.lvarOffset) - && areEquals(p1.lvarLength, p2.lvarLength) - && areEquals(p1.lvarIndex, p2.lvarIndex) - && areEquals(p1.bound_index, p2.bound_index) - && areEquals(p1.parameter_index, p2.parameter_index) - && areEquals(p1.type_index, p2.type_index) - && areEquals(p1.exception_index, p2.exception_index)); - return result; - } - - public static String positionCompareStr(TypeAnnotation.Position p1, TypeAnnotation.Position p2) { - return String.join( - System.lineSeparator(), - "type = " + p1.type + ", " + p2.type, - "offset = " + p1.offset + ", " + p2.offset, - "lvarOffset = " + p1.lvarOffset + ", " + p2.lvarOffset, - "lvarLength = " + p1.lvarLength + ", " + p2.lvarLength, - "lvarIndex = " + p1.lvarIndex + ", " + p2.lvarIndex, - "bound_index = " + p1.bound_index + ", " + p2.bound_index, - "parameter_index = " + p1.parameter_index + ", " + p2.parameter_index, - "type_index = " + p1.type_index + ", " + p2.type_index, - "exception_index = " + p1.exception_index + ", " + p2.exception_index, - ""); - } - - private static TypeAnnotation findAnnotation( - String name, TypeAnnotation.Position expected, List annotations, ClassFile cf) - throws InvalidIndex, UnexpectedEntry { - String properName = "L" + name + ";"; - for (TypeAnnotation anno : annotations) { - String actualName = cf.constant_pool.getUTF8Value(anno.annotation.type_index); - - if (properName.equals(actualName)) { - System.out.println("For Anno: " + actualName); - } - - if (properName.equals(actualName) && areEquals(expected, anno.position)) { - return anno; - } + public static String positionCompareStr( + TypeAnnotation.Position p1, TypeAnnotation.Position p2) { + return String.join( + System.lineSeparator(), + "type = " + p1.type + ", " + p2.type, + "offset = " + p1.offset + ", " + p2.offset, + "lvarOffset = " + p1.lvarOffset + ", " + p2.lvarOffset, + "lvarLength = " + p1.lvarLength + ", " + p2.lvarLength, + "lvarIndex = " + p1.lvarIndex + ", " + p2.lvarIndex, + "bound_index = " + p1.bound_index + ", " + p2.bound_index, + "parameter_index = " + p1.parameter_index + ", " + p2.parameter_index, + "type_index = " + p1.type_index + ", " + p2.type_index, + "exception_index = " + p1.exception_index + ", " + p2.exception_index, + ""); } - return null; - } - - public static boolean compare( - List expectedAnnos, - List actualAnnos, - ClassFile cf, - String diagnostic) - throws InvalidIndex, UnexpectedEntry { - if (actualAnnos.size() != expectedAnnos.size()) { - throw new ComparisonException( - "Wrong number of annotations in " + cf + "; " + diagnostic, expectedAnnos, actualAnnos); + + private static TypeAnnotation findAnnotation( + String name, + TypeAnnotation.Position expected, + List annotations, + ClassFile cf) + throws InvalidIndex, UnexpectedEntry { + String properName = "L" + name + ";"; + for (TypeAnnotation anno : annotations) { + String actualName = cf.constant_pool.getUTF8Value(anno.annotation.type_index); + + if (properName.equals(actualName)) { + System.out.println("For Anno: " + actualName); + } + + if (properName.equals(actualName) && areEquals(expected, anno.position)) { + return anno; + } + } + return null; } - for (AnnoPosPair e : expectedAnnos) { - String aName = e.first; - TypeAnnotation.Position expected = e.second; - TypeAnnotation actual = findAnnotation(aName, expected, actualAnnos, cf); - if (actual == null) { - throw new ComparisonException( - "Expected annotation not found: " - + aName - + " position: " - + expected - + "; " - + diagnostic, - expectedAnnos, - actualAnnos); - } + public static boolean compare( + List expectedAnnos, + List actualAnnos, + ClassFile cf, + String diagnostic) + throws InvalidIndex, UnexpectedEntry { + if (actualAnnos.size() != expectedAnnos.size()) { + throw new ComparisonException( + "Wrong number of annotations in " + cf + "; " + diagnostic, + expectedAnnos, + actualAnnos); + } + + for (AnnoPosPair e : expectedAnnos) { + String aName = e.first; + TypeAnnotation.Position expected = e.second; + TypeAnnotation actual = findAnnotation(aName, expected, actualAnnos, cf); + if (actual == null) { + throw new ComparisonException( + "Expected annotation not found: " + + aName + + " position: " + + expected + + "; " + + diagnostic, + expectedAnnos, + actualAnnos); + } + } + return true; } - return true; - } } class ComparisonException extends RuntimeException { - private static final long serialVersionUID = -3930499712333815821L; - - public final List expected; - public final List found; - - public ComparisonException( - String message, List expected, List found) { - super(message); - this.expected = expected; - this.found = found; - } - - public String toString() { - return String.format( - "%s%n Expected (%d): %s%s Found (%d): %s", - super.toString(), expected.size(), expected, found.size(), found); - } + private static final long serialVersionUID = -3930499712333815821L; + + public final List expected; + public final List found; + + public ComparisonException( + String message, List expected, List found) { + super(message); + this.expected = expected; + this.found = found; + } + + public String toString() { + return String.format( + "%s%n Expected (%d): %s%s Found (%d): %s", + super.toString(), expected.size(), expected, found.size(), found); + } } diff --git a/checker/jtreg/nullness/eisop673/Lib.java b/checker/jtreg/nullness/eisop673/Lib.java index 07801379a9d..42810b179d8 100644 --- a/checker/jtreg/nullness/eisop673/Lib.java +++ b/checker/jtreg/nullness/eisop673/Lib.java @@ -1,5 +1,5 @@ import org.jetbrains.annotations.Nullable; interface Lib { - @Nullable String[] get(); + @Nullable String[] get(); } diff --git a/checker/jtreg/nullness/eisop673/User.java b/checker/jtreg/nullness/eisop673/User.java index f13fb42746c..20e4b80c61e 100644 --- a/checker/jtreg/nullness/eisop673/User.java +++ b/checker/jtreg/nullness/eisop673/User.java @@ -9,8 +9,8 @@ * @compile/fail/ref=User.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker User.java */ class User { - void go(Lib lib, Lib7 lib7) { - String[] b = lib.get(); - String[] b7 = lib7.get(); - } + void go(Lib lib, Lib7 lib7) { + String[] b = lib.get(); + String[] b7 = lib7.get(); + } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java index a9d0388709e..17f272214a7 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/AbstractClass.java @@ -2,10 +2,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; public abstract class AbstractClass { - @Nullable Object f; + @Nullable Object f; - @EnsuresNonNull("f") - public abstract void setf(); + @EnsuresNonNull("f") + public abstract void setf(); - public abstract void setg(); + public abstract void setg(); } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java index 28e60064bad..604a470ac7e 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Driver.java @@ -3,6 +3,7 @@ import com.sun.tools.classfile.Annotation; import com.sun.tools.classfile.ClassFile; + import java.io.PrintStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -14,103 +15,104 @@ public class Driver { - private static final PrintStream out = System.out; + private static final PrintStream out = System.out; - // The argument is in the format expected by Class.forName(). - public static void main(String[] args) throws Exception { - if (args.length != 1) { - throw new IllegalArgumentException("Usage: java Driver "); + // The argument is in the format expected by Class.forName(). + public static void main(String[] args) throws Exception { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: java Driver "); + } + String name = args[0]; + Class clazz = Class.forName(name); + new Driver().runDriver(clazz.newInstance()); } - String name = args[0]; - Class clazz = Class.forName(name); - new Driver().runDriver(clazz.newInstance()); - } - - protected void runDriver(Object object) throws Exception { - int passed = 0, failed = 0; - Class clazz = object.getClass(); - out.println("Tests for " + clazz.getName()); - - // Find methods - for (Method method : clazz.getMethods()) { - List expected = expectedOf(method); - if (expected == null) { - continue; - } - if (method.getReturnType() != String.class) { - throw new IllegalArgumentException("Test method needs to return a string: " + method); - } - String testClass = PersistUtil.testClassOf(method); - - try { - String compact = (String) method.invoke(object); - String fullFile = PersistUtil.wrap(compact); - ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); - List actual = ReferenceInfoUtil.extendedAnnotationsOf(cf); - String diagnostic = - String.join( - "; ", - "Tests for " + clazz.getName(), - "compact=" + compact, - "fullFile=" + fullFile, - "testClass=" + testClass); - ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); - out.println("PASSED: " + method.getName()); - ++passed; - } catch (Throwable e) { - out.println("FAILED: " + method.getName()); - out.println(" " + e); - ++failed; - } + + protected void runDriver(Object object) throws Exception { + int passed = 0, failed = 0; + Class clazz = object.getClass(); + out.println("Tests for " + clazz.getName()); + + // Find methods + for (Method method : clazz.getMethods()) { + List expected = expectedOf(method); + if (expected == null) { + continue; + } + if (method.getReturnType() != String.class) { + throw new IllegalArgumentException( + "Test method needs to return a string: " + method); + } + String testClass = PersistUtil.testClassOf(method); + + try { + String compact = (String) method.invoke(object); + String fullFile = PersistUtil.wrap(compact); + ClassFile cf = PersistUtil.compileAndReturn(fullFile, testClass); + List actual = ReferenceInfoUtil.extendedAnnotationsOf(cf); + String diagnostic = + String.join( + "; ", + "Tests for " + clazz.getName(), + "compact=" + compact, + "fullFile=" + fullFile, + "testClass=" + testClass); + ReferenceInfoUtil.compare(expected, actual, cf, diagnostic); + out.println("PASSED: " + method.getName()); + ++passed; + } catch (Throwable e) { + out.println("FAILED: " + method.getName()); + out.println(" " + e); + ++failed; + } + } + + out.println(); + int total = passed + failed; + out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); + + out.flush(); + + if (failed != 0) { + throw new RuntimeException(failed + " tests failed"); + } } - out.println(); - int total = passed + failed; - out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); + private List expectedOf(Method m) { + ADescription ta = m.getAnnotation(ADescription.class); + ADescriptions tas = m.getAnnotation(ADescriptions.class); - out.flush(); + if (ta == null && tas == null) { + return null; + } - if (failed != 0) { - throw new RuntimeException(failed + " tests failed"); - } - } + List result = new ArrayList<>(); - private List expectedOf(Method m) { - ADescription ta = m.getAnnotation(ADescription.class); - ADescriptions tas = m.getAnnotation(ADescriptions.class); + if (ta != null) { + result.add(expectedOf(ta)); + } - if (ta == null && tas == null) { - return null; - } + if (tas != null) { + for (ADescription a : tas.value()) { + result.add(expectedOf(a)); + } + } - List result = new ArrayList<>(); - - if (ta != null) { - result.add(expectedOf(ta)); + return result; } - if (tas != null) { - for (ADescription a : tas.value()) { - result.add(expectedOf(a)); - } + private String expectedOf(ADescription d) { + return d.annotation(); } - - return result; - } - - private String expectedOf(ADescription d) { - return d.annotation(); - } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ADescription { - String annotation(); + String annotation(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ADescriptions { - ADescription[] value() default {}; + ADescription[] value() default {}; } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java index 1cf830d1017..224feb2b133 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java @@ -8,36 +8,36 @@ public class Extends { - @ADescriptions({ - @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") - }) - public String m1() { - StringBuilder sb = new StringBuilder(); - return TestWrapper.wrap("@Override void setf() { f = new Object(); }"); - } + @ADescriptions({ + @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") + }) + public String m1() { + StringBuilder sb = new StringBuilder(); + return TestWrapper.wrap("@Override void setf() { f = new Object(); }"); + } - @ADescriptions({}) - public String m2() { - return TestWrapper.wrap("@Override void setg() {}"); - } + @ADescriptions({}) + public String m2() { + return TestWrapper.wrap("@Override void setg() {}"); + } - // Issue 342 - // We do not want that behavior with related annotations. @Pure should override @SideEffectFree. - @ADescriptions({ - @ADescription(annotation = "org/checkerframework/dataflow/qual/Pure"), - @ADescription(annotation = "org/checkerframework/dataflow/qual/SideEffectFree") - }) - public String m3() { - return TestWrapper.wrap("@Pure @Override void seth() {}"); - } + // Issue 342 + // We do not want that behavior with related annotations. @Pure should override @SideEffectFree. + @ADescriptions({ + @ADescription(annotation = "org/checkerframework/dataflow/qual/Pure"), + @ADescription(annotation = "org/checkerframework/dataflow/qual/SideEffectFree") + }) + public String m3() { + return TestWrapper.wrap("@Pure @Override void seth() {}"); + } } class TestWrapper { - public static String wrap(String... method) { - return "class Test extends Super {" - + System.lineSeparator() - + String.join(System.lineSeparator(), method) - + System.lineSeparator() - + "}"; - } + public static String wrap(String... method) { + return "class Test extends Super {" + + System.lineSeparator() + + String.join(System.lineSeparator(), method) + + System.lineSeparator() + + "}"; + } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java index 31f43b7388d..096f49b3223 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java @@ -8,23 +8,23 @@ public class Implements { - @ADescriptions({ - @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") - }) - public String m1() { - return TestWrapper.wrap( - "public Test() { f = new Object(); }", - "@Override public void setf() { f = new Object(); }", - "@Override public void setg() {}"); - } + @ADescriptions({ + @ADescription(annotation = "org/checkerframework/checker/nullness/qual/EnsuresNonNull") + }) + public String m1() { + return TestWrapper.wrap( + "public Test() { f = new Object(); }", + "@Override public void setf() { f = new Object(); }", + "@Override public void setg() {}"); + } } class TestWrapper { - public static String wrap(String... method) { - return String.join( - System.lineSeparator(), - "class Test extends AbstractClass {", - String.join(System.lineSeparator(), method), - "}"); - } + public static String wrap(String... method) { + return String.join( + System.lineSeparator(), + "class Test extends AbstractClass {", + String.join(System.lineSeparator(), method), + "}"); + } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java index 697d995bf15..4dac1fbbf44 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java @@ -9,125 +9,132 @@ import com.sun.tools.classfile.ConstantPool.UnexpectedEntry; import com.sun.tools.classfile.Method; import com.sun.tools.classfile.RuntimeAnnotations_attribute; + import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; public class ReferenceInfoUtil { - public static final int IGNORE_VALUE = -321; - - public static List extendedAnnotationsOf(ClassFile cf) { - List annos = new ArrayList<>(); - findAnnotations(cf, annos); - return annos; - } + public static final int IGNORE_VALUE = -321; - /////////////////// Extract annotations ////////////////// - private static void findAnnotations(ClassFile cf, List annos) { - for (Method m : cf.methods) { - findAnnotations(cf, m, Attribute.RuntimeVisibleAnnotations, annos); + public static List extendedAnnotationsOf(ClassFile cf) { + List annos = new ArrayList<>(); + findAnnotations(cf, annos); + return annos; } - } - /** - * Test the result of Attributes.getIndex according to expectations encoded in the method's name. - */ - private static void findAnnotations(ClassFile cf, Method m, String name, List annos) { - int index = m.attributes.getIndex(cf.constant_pool, name); - if (index != -1) { - Attribute attr = m.attributes.get(index); - assert attr instanceof RuntimeAnnotations_attribute; - RuntimeAnnotations_attribute tAttr = (RuntimeAnnotations_attribute) attr; - for (Annotation an : tAttr.annotations) { - if (!containsName(annos, an, cf)) { - annos.add(an); + /////////////////// Extract annotations ////////////////// + private static void findAnnotations(ClassFile cf, List annos) { + for (Method m : cf.methods) { + findAnnotations(cf, m, Attribute.RuntimeVisibleAnnotations, annos); } - } } - } - private static Annotation findAnnotation(String name, List annotations, ClassFile cf) - throws InvalidIndex, UnexpectedEntry { - String properName = "L" + name + ";"; - for (Annotation anno : annotations) { - String actualName = cf.constant_pool.getUTF8Value(anno.type_index); - if (properName.equals(actualName)) { - return anno; - } + /** + * Test the result of Attributes.getIndex according to expectations encoded in the method's + * name. + */ + private static void findAnnotations( + ClassFile cf, Method m, String name, List annos) { + int index = m.attributes.getIndex(cf.constant_pool, name); + if (index != -1) { + Attribute attr = m.attributes.get(index); + assert attr instanceof RuntimeAnnotations_attribute; + RuntimeAnnotations_attribute tAttr = (RuntimeAnnotations_attribute) attr; + for (Annotation an : tAttr.annotations) { + if (!containsName(annos, an, cf)) { + annos.add(an); + } + } + } } - return null; - } - public static boolean compare( - List expectedAnnos, List actualAnnos, ClassFile cf, String diagnostic) - throws InvalidIndex, UnexpectedEntry { - if (actualAnnos.size() != expectedAnnos.size()) { - throw new ComparisonException( - "Wrong number of annotations; " + diagnostic, expectedAnnos, actualAnnos, cf); + private static Annotation findAnnotation( + String name, List annotations, ClassFile cf) + throws InvalidIndex, UnexpectedEntry { + String properName = "L" + name + ";"; + for (Annotation anno : annotations) { + String actualName = cf.constant_pool.getUTF8Value(anno.type_index); + if (properName.equals(actualName)) { + return anno; + } + } + return null; } - for (String annoName : expectedAnnos) { - Annotation anno = findAnnotation(annoName, actualAnnos, cf); - if (anno == null) { - throw new ComparisonException( - "Expected annotation not found: " + annoName + "; " + diagnostic, - expectedAnnos, - actualAnnos, - cf); - } + + public static boolean compare( + List expectedAnnos, + List actualAnnos, + ClassFile cf, + String diagnostic) + throws InvalidIndex, UnexpectedEntry { + if (actualAnnos.size() != expectedAnnos.size()) { + throw new ComparisonException( + "Wrong number of annotations; " + diagnostic, expectedAnnos, actualAnnos, cf); + } + for (String annoName : expectedAnnos) { + Annotation anno = findAnnotation(annoName, actualAnnos, cf); + if (anno == null) { + throw new ComparisonException( + "Expected annotation not found: " + annoName + "; " + diagnostic, + expectedAnnos, + actualAnnos, + cf); + } + } + return true; } - return true; - } - private static boolean containsName(List annos, Annotation anno, ClassFile cf) { - try { - for (Annotation an : annos) { - if (cf.constant_pool - .getUTF8Value(an.type_index) - .equals(cf.constant_pool.getUTF8Value(anno.type_index))) { - return true; + private static boolean containsName(List annos, Annotation anno, ClassFile cf) { + try { + for (Annotation an : annos) { + if (cf.constant_pool + .getUTF8Value(an.type_index) + .equals(cf.constant_pool.getUTF8Value(anno.type_index))) { + return true; + } + } + } catch (Exception e) { + throw new RuntimeException(); } - } - } catch (Exception e) { - throw new RuntimeException(); + return false; } - return false; - } } class ComparisonException extends RuntimeException { - private static final long serialVersionUID = -3930499712333815821L; + private static final long serialVersionUID = -3930499712333815821L; - public final List expected; - public final List found; - public final ClassFile cf; + public final List expected; + public final List found; + public final ClassFile cf; - public ComparisonException( - String message, List expected, List found, ClassFile cf) { - super(message); - this.expected = expected; - this.found = found; - this.cf = cf; - } + public ComparisonException( + String message, List expected, List found, ClassFile cf) { + super(message); + this.expected = expected; + this.found = found; + this.cf = cf; + } - public String toString() { - StringJoiner foundString = new StringJoiner(","); - for (Annotation anno : found) { - try { - foundString.add(cf.constant_pool.getUTF8Value(anno.type_index)); - } catch (Exception e) { - throw new RuntimeException(e); - } + public String toString() { + StringJoiner foundString = new StringJoiner(","); + for (Annotation anno : found) { + try { + foundString.add(cf.constant_pool.getUTF8Value(anno.type_index)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return String.join( + System.lineSeparator(), + super.toString(), + "\tExpected: " + + expected.size() + + " annotations; but found: " + + found.size() + + " annotations", + " Expected: " + expected, + " Found: " + foundString); } - return String.join( - System.lineSeparator(), - super.toString(), - "\tExpected: " - + expected.size() - + " annotations; but found: " - + found.size() - + " annotations", - " Expected: " + expected, - " Found: " + foundString); - } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java index 085cf32341a..2ab7a324c59 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Super.java @@ -2,21 +2,21 @@ import org.checkerframework.dataflow.qual.SideEffectFree; public class Super { - Object f; - Object g; - Object h; + Object f; + Object g; + Object h; - @EnsuresNonNull("f") - void setf() { - f = new Object(); - } + @EnsuresNonNull("f") + void setf() { + f = new Object(); + } - void setg() { - g = null; - } + void setg() { + g = null; + } - @SideEffectFree - void seth() { - h = null; - } + @SideEffectFree + void seth() { + h = null; + } } diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTest.java b/checker/jtreg/nullness/issue12/BinaryDefaultTest.java index 8323dc2b858..42408204f7c 100644 --- a/checker/jtreg/nullness/issue12/BinaryDefaultTest.java +++ b/checker/jtreg/nullness/issue12/BinaryDefaultTest.java @@ -11,9 +11,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class BinaryDefaultTest { - void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { - @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); - @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); - @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); - } + void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { + @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); + @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); + @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); + } } diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java b/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java index 35b333aea3e..1101b6c46b5 100644 --- a/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java +++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestBinary.java @@ -1,5 +1,5 @@ public class BinaryDefaultTestBinary { - public static BinaryDefaultTestBinary foo(BinaryDefaultTestInterface foo) { - return new BinaryDefaultTestBinary(); - } + public static BinaryDefaultTestBinary foo(BinaryDefaultTestInterface foo) { + return new BinaryDefaultTestBinary(); + } } diff --git a/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java index 17e85a394d0..9bf213bc4d6 100644 --- a/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java +++ b/checker/jtreg/nullness/issue12/BinaryDefaultTestWithStub.java @@ -13,9 +13,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class BinaryDefaultTestWithStub { - void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { - @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); - @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); - @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); - } + void test1(@NonNull BinaryDefaultTestInterface bar, @Nullable BinaryDefaultTestInterface bar2) { + @Nullable BinaryDefaultTestBinary foo = BinaryDefaultTestBinary.foo(bar); + @Nullable BinaryDefaultTestBinary baz = BinaryDefaultTestBinary.foo(bar2); + @NonNull BinaryDefaultTestBinary biz = BinaryDefaultTestBinary.foo(bar); + } } diff --git a/checker/jtreg/nullness/issue141/CharStreams.java b/checker/jtreg/nullness/issue141/CharStreams.java index b40120e8f59..75a7c761efa 100644 --- a/checker/jtreg/nullness/issue141/CharStreams.java +++ b/checker/jtreg/nullness/issue141/CharStreams.java @@ -1,4 +1,4 @@ public class CharStreams { - static void copy( - InputSupplier from, OutputSupplier to) {} + static void copy( + InputSupplier from, OutputSupplier to) {} } diff --git a/checker/jtreg/nullness/issue141/Files.java b/checker/jtreg/nullness/issue141/Files.java index e3f6c9e349e..138fae1e7cc 100644 --- a/checker/jtreg/nullness/issue141/Files.java +++ b/checker/jtreg/nullness/issue141/Files.java @@ -1,13 +1,13 @@ abstract class Files { - public void copy(InputSupplier from) { - CharStreams.copy(from, newWriterSupplier()); - } + public void copy(InputSupplier from) { + CharStreams.copy(from, newWriterSupplier()); + } - public void copy(OutputSupplier to) { - CharStreams.copy(newReaderSupplier(), to); - } + public void copy(OutputSupplier to) { + CharStreams.copy(newReaderSupplier(), to); + } - abstract OutputSupplier newWriterSupplier(); + abstract OutputSupplier newWriterSupplier(); - abstract InputSupplier newReaderSupplier(); + abstract InputSupplier newReaderSupplier(); } diff --git a/checker/jtreg/nullness/issue1582/foo/Foo.java b/checker/jtreg/nullness/issue1582/foo/Foo.java index c7aa9ce2eb4..cfff2d56fbc 100644 --- a/checker/jtreg/nullness/issue1582/foo/Foo.java +++ b/checker/jtreg/nullness/issue1582/foo/Foo.java @@ -6,18 +6,18 @@ public class Foo { - Foo(@Nullable Object theObject) {} + Foo(@Nullable Object theObject) {} - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - @EnsuresNonNullIf( - expression = {"theObject", "getTheObject()"}, - result = true) - public boolean hasTheObject() { - return false; - } + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + @EnsuresNonNullIf( + expression = {"theObject", "getTheObject()"}, + result = true) + public boolean hasTheObject() { + return false; + } - @Pure - public @Nullable Object getTheObject() { - return null; - } + @Pure + public @Nullable Object getTheObject() { + return null; + } } diff --git a/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java b/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java index f8641bb8024..700386c0107 100644 --- a/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java +++ b/checker/jtreg/nullness/issue1582/mainrepropkg/JavaExpressionParseError.java @@ -4,9 +4,9 @@ public class JavaExpressionParseError { - public void printAThing(Foo foo) { - if (foo.hasTheObject()) { - System.out.println("Print false: " + foo.getTheObject().equals(new Object())); + public void printAThing(Foo foo) { + if (foo.hasTheObject()) { + System.out.println("Print false: " + foo.getTheObject().equals(new Object())); + } } - } } diff --git a/checker/jtreg/nullness/issue1929/Issue1929.java b/checker/jtreg/nullness/issue1929/Issue1929.java index 73e873daf6e..6482d71e7a5 100644 --- a/checker/jtreg/nullness/issue1929/Issue1929.java +++ b/checker/jtreg/nullness/issue1929/Issue1929.java @@ -6,26 +6,27 @@ * @compile/fail/ref=Issue1929-trust.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Alint=trustArrayLenZero Issue1929.java */ -import java.util.Collection; import org.checkerframework.common.value.qual.ArrayLen; +import java.util.Collection; + public class Issue1929 { - String[] works1(Collection c) { - return c.toArray(new String[0]); - } + String[] works1(Collection c) { + return c.toArray(new String[0]); + } - private static final String @ArrayLen(0) [] EMPTY_STRING_ARRAY_2 = new String[0]; + private static final String @ArrayLen(0) [] EMPTY_STRING_ARRAY_2 = new String[0]; - String[] fails2(Collection c) { - return c.toArray(EMPTY_STRING_ARRAY_2); - } + String[] fails2(Collection c) { + return c.toArray(EMPTY_STRING_ARRAY_2); + } - private static final String[] EMPTY_STRING_ARRAY_3 = new String[0]; + private static final String[] EMPTY_STRING_ARRAY_3 = new String[0]; - String[] fails3(Collection c) { - // We don't determine field types from initialization expressions. - // :: error: (return.type.incompatible) - return c.toArray(EMPTY_STRING_ARRAY_3); - } + String[] fails3(Collection c) { + // We don't determine field types from initialization expressions. + // :: error: (return.type.incompatible) + return c.toArray(EMPTY_STRING_ARRAY_3); + } } diff --git a/checker/jtreg/nullness/issue1958/NPE2Test.java b/checker/jtreg/nullness/issue1958/NPE2Test.java index 8d292792fa4..7d16d44386e 100644 --- a/checker/jtreg/nullness/issue1958/NPE2Test.java +++ b/checker/jtreg/nullness/issue1958/NPE2Test.java @@ -1,19 +1,19 @@ public class NPE2Test { - public void testNPE() { - SupplierDefs.Supplier s = new SupplierDefs.NullSupplier(); - boolean b = s.get().equals(""); - } + public void testNPE() { + SupplierDefs.Supplier s = new SupplierDefs.NullSupplier(); + boolean b = s.get().equals(""); + } - public void testNPE2() { - SupplierDefs.MyInterface s = new SupplierDefs.NullInterface(); - boolean b = s.getT().equals(""); - } + public void testNPE2() { + SupplierDefs.MyInterface s = new SupplierDefs.NullInterface(); + boolean b = s.getT().equals(""); + } - public void testNPE3() { - SupplierDefs.MyInterface s = new SupplierDefs.NullSupplierMyInterface(); - boolean b = s.getT().equals(""); + public void testNPE3() { + SupplierDefs.MyInterface s = new SupplierDefs.NullSupplierMyInterface(); + boolean b = s.getT().equals(""); - SupplierDefs.Supplier s2 = new SupplierDefs.NullSupplierMyInterface(); - boolean b2 = s2.get().equals(""); - } + SupplierDefs.Supplier s2 = new SupplierDefs.NullSupplierMyInterface(); + boolean b2 = s2.get().equals(""); + } } diff --git a/checker/jtreg/nullness/issue1958/SupplierDefs.java b/checker/jtreg/nullness/issue1958/SupplierDefs.java index 9a8f054251e..4a15a8e5421 100644 --- a/checker/jtreg/nullness/issue1958/SupplierDefs.java +++ b/checker/jtreg/nullness/issue1958/SupplierDefs.java @@ -6,42 +6,43 @@ * @compile/fail/ref=NPE2Test.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker NPE2Test.java -Anomsgtext */ -import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.*; +import java.util.function.Supplier; + public class SupplierDefs { - public abstract static class Supplier { - public abstract R get(); - } - - public static class NullSupplier extends Supplier<@Nullable String> { - @Override - public @Nullable String get() { - return null; + public abstract static class Supplier { + public abstract R get(); } - } - public static class NullInterface implements MyInterface<@Nullable String> { - @Override - public @Nullable String getT() { - return null; + public static class NullSupplier extends Supplier<@Nullable String> { + @Override + public @Nullable String get() { + return null; + } } - } - public static class NullSupplierMyInterface extends Supplier<@Nullable String> - implements MyInterface<@Nullable String> { - @Override - public @Nullable String get() { - return null; + public static class NullInterface implements MyInterface<@Nullable String> { + @Override + public @Nullable String getT() { + return null; + } } - @Override - public @Nullable String getT() { - return null; + public static class NullSupplierMyInterface extends Supplier<@Nullable String> + implements MyInterface<@Nullable String> { + @Override + public @Nullable String get() { + return null; + } + + @Override + public @Nullable String getT() { + return null; + } } - } - public interface MyInterface { - T getT(); - } + public interface MyInterface { + T getT(); + } } diff --git a/checker/jtreg/nullness/issue2173/View.java b/checker/jtreg/nullness/issue2173/View.java index f46ea99a6e5..2c97d94b6eb 100644 --- a/checker/jtreg/nullness/issue2173/View.java +++ b/checker/jtreg/nullness/issue2173/View.java @@ -8,7 +8,7 @@ */ public class View { - private static void createTable() { - ImporterManager.chooseAndImportFile(""); - } + private static void createTable() { + ImporterManager.chooseAndImportFile(""); + } } diff --git a/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java index f32a4c77ce7..3d8b6e6fe22 100644 --- a/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java +++ b/checker/jtreg/nullness/issue2318/RequireCheckerPrefix.java @@ -11,21 +11,21 @@ public class RequireCheckerPrefix { - void method(@Nullable Object o) { - @SuppressWarnings("nullness:assignment.type.incompatible") - @NonNull Object s = o; - // "all" is not a valid prefix, so the warning is never suppressed. - @SuppressWarnings("all:assignment.type.incompatible") - @NonNull Object t = o; - @SuppressWarnings("allcheckers:assignment.type.incompatible") - @NonNull Object u = o; + void method(@Nullable Object o) { + @SuppressWarnings("nullness:assignment.type.incompatible") + @NonNull Object s = o; + // "all" is not a valid prefix, so the warning is never suppressed. + @SuppressWarnings("all:assignment.type.incompatible") + @NonNull Object t = o; + @SuppressWarnings("allcheckers:assignment.type.incompatible") + @NonNull Object u = o; - @SuppressWarnings("assignment.type.incompatible") - @NonNull Object p = o; - // Suppresses the warning if -ArequirePrefixInWarningSuppressions isn't used. - @SuppressWarnings("all") - @NonNull Object q = o; - @SuppressWarnings("allcheckers") - @NonNull Object w = o; - } + @SuppressWarnings("assignment.type.incompatible") + @NonNull Object p = o; + // Suppresses the warning if -ArequirePrefixInWarningSuppressions isn't used. + @SuppressWarnings("all") + @NonNull Object q = o; + @SuppressWarnings("allcheckers") + @NonNull Object w = o; + } } diff --git a/checker/jtreg/nullness/issue257/ClientBuilder.java b/checker/jtreg/nullness/issue257/ClientBuilder.java index f42da3f7acf..829d324cebc 100644 --- a/checker/jtreg/nullness/issue257/ClientBuilder.java +++ b/checker/jtreg/nullness/issue257/ClientBuilder.java @@ -2,14 +2,14 @@ public class ClientBuilder> { - static @NonNull ClientBuilder newBuilder() { - return new BuilderImpl(); - } + static @NonNull ClientBuilder newBuilder() { + return new BuilderImpl(); + } - // Dummy class to get the recursive Builder typing right. - static class BuilderImpl extends ClientBuilder {} + // Dummy class to get the recursive Builder typing right. + static class BuilderImpl extends ClientBuilder {} - T setThing() { - return (T) this; - } + T setThing() { + return (T) this; + } } diff --git a/checker/jtreg/nullness/issue257/Module.java b/checker/jtreg/nullness/issue257/Module.java index 785dacc4c99..400c94390a5 100644 --- a/checker/jtreg/nullness/issue257/Module.java +++ b/checker/jtreg/nullness/issue257/Module.java @@ -9,12 +9,12 @@ * @compile -XDrawDiagnostics ClientBuilder.java */ public class Module { - void buildClient() { - ClientBuilder builder = ClientBuilder.newBuilder().setThing().setThing(); - } + void buildClient() { + ClientBuilder builder = ClientBuilder.newBuilder().setThing().setThing(); + } - void smaller() { - ClientBuilder>>> - builder = ClientBuilder.newBuilder(); - } + void smaller() { + ClientBuilder>>> + builder = ClientBuilder.newBuilder(); + } } diff --git a/checker/jtreg/nullness/issue257/Small.java b/checker/jtreg/nullness/issue257/Small.java index 6209a3e6da9..cd4b6bea487 100644 --- a/checker/jtreg/nullness/issue257/Small.java +++ b/checker/jtreg/nullness/issue257/Small.java @@ -2,13 +2,13 @@ class Gen> { - static @Nullable Gen newBuilder() { - return null; - } + static @Nullable Gen newBuilder() { + return null; + } } public class Small { - void buildGen() { - Gen> builder = Gen.newBuilder(); - } + void buildGen() { + Gen> builder = Gen.newBuilder(); + } } diff --git a/checker/jtreg/nullness/issue3700/Client.java b/checker/jtreg/nullness/issue3700/Client.java index e9657c70ee0..0dd05d60d5a 100644 --- a/checker/jtreg/nullness/issue3700/Client.java +++ b/checker/jtreg/nullness/issue3700/Client.java @@ -1,4 +1,4 @@ public class Client { - TimeUnitRange t = TimeUnitRange.YEAR; + TimeUnitRange t = TimeUnitRange.YEAR; } diff --git a/checker/jtreg/nullness/issue3700/TimeUnitRange.java b/checker/jtreg/nullness/issue3700/TimeUnitRange.java index c11ced477e2..cc4e5f0f9af 100644 --- a/checker/jtreg/nullness/issue3700/TimeUnitRange.java +++ b/checker/jtreg/nullness/issue3700/TimeUnitRange.java @@ -6,11 +6,11 @@ */ public enum TimeUnitRange { - YEAR, - YEAR_TO_MONTH, - MONTH; + YEAR, + YEAR_TO_MONTH, + MONTH; - public static TimeUnitRange of(Object endUnit) { - throw new Error("body is irrelevant"); - } + public static TimeUnitRange of(Object endUnit) { + throw new Error("body is irrelevant"); + } } diff --git a/checker/jtreg/nullness/issue380/DA.java b/checker/jtreg/nullness/issue380/DA.java index 553eb49262f..8676c10dbca 100644 --- a/checker/jtreg/nullness/issue380/DA.java +++ b/checker/jtreg/nullness/issue380/DA.java @@ -1,4 +1,4 @@ public class DA { - @Decl(flag = true) - void foo() {} + @Decl(flag = true) + void foo() {} } diff --git a/checker/jtreg/nullness/issue380/DB.java b/checker/jtreg/nullness/issue380/DB.java index b9981120237..98b982598b0 100644 --- a/checker/jtreg/nullness/issue380/DB.java +++ b/checker/jtreg/nullness/issue380/DB.java @@ -1,3 +1,3 @@ public class DB extends DA { - void foo() {} + void foo() {} } diff --git a/checker/jtreg/nullness/issue380/Decl.java b/checker/jtreg/nullness/issue380/Decl.java index d262028269f..150e0ab6e27 100644 --- a/checker/jtreg/nullness/issue380/Decl.java +++ b/checker/jtreg/nullness/issue380/Decl.java @@ -2,5 +2,5 @@ @InheritedAnnotation public @interface Decl { - boolean flag(); + boolean flag(); } diff --git a/checker/jtreg/nullness/issue6053/Main.java b/checker/jtreg/nullness/issue6053/Main.java index 6939c6d15bb..5ea27ddacb9 100644 --- a/checker/jtreg/nullness/issue6053/Main.java +++ b/checker/jtreg/nullness/issue6053/Main.java @@ -5,5 +5,5 @@ * @compile/ref=Main.out -XDrawDiagnostics -Anomsgtext -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=stubs.jar Main.java */ public class Main { - public static void main(String[] args) {} + public static void main(String[] args) {} } diff --git a/checker/jtreg/nullness/issue6374/Lib.java b/checker/jtreg/nullness/issue6374/Lib.java index 0997350896d..dea871dac3a 100644 --- a/checker/jtreg/nullness/issue6374/Lib.java +++ b/checker/jtreg/nullness/issue6374/Lib.java @@ -3,15 +3,15 @@ @SuppressWarnings("unchecked") // ignore heap pollution class Lib { - // element type inferred, array non-null - static void none(T... o) {} + // element type inferred, array non-null + static void none(T... o) {} - // element type inferred, array non-null - static void decl(@NonNull T... o) {} + // element type inferred, array non-null + static void decl(@NonNull T... o) {} - // element type nullable, array non-null - static void type(@Nullable T... o) {} + // element type nullable, array non-null + static void type(@Nullable T... o) {} - // element type nullable, array nullable - static void typenn(@Nullable T @Nullable ... o) {} + // element type nullable, array nullable + static void typenn(@Nullable T @Nullable ... o) {} } diff --git a/checker/jtreg/nullness/issue6374/User.java b/checker/jtreg/nullness/issue6374/User.java index bfe3171cdec..ca0de77a7c2 100644 --- a/checker/jtreg/nullness/issue6374/User.java +++ b/checker/jtreg/nullness/issue6374/User.java @@ -7,14 +7,14 @@ */ // Also see checker/tests/nullness/generics/Issue6374.java class User { - void go() { - Lib.decl("", null); - // :: error: (argument.type.incompatible) - Lib.decl((Object[]) null); - Lib.type("", null); - // :: error: (argument.type.incompatible) - Lib.type((Object[]) null); - Lib.typenn("", null); - Lib.typenn((Object[]) null); - } + void go() { + Lib.decl("", null); + // :: error: (argument.type.incompatible) + Lib.decl((Object[]) null); + Lib.type("", null); + // :: error: (argument.type.incompatible) + Lib.type((Object[]) null); + Lib.typenn("", null); + Lib.typenn((Object[]) null); + } } diff --git a/checker/jtreg/nullness/issue767/Class1.java b/checker/jtreg/nullness/issue767/Class1.java index fc2c20bf3e7..40aea26c250 100644 --- a/checker/jtreg/nullness/issue767/Class1.java +++ b/checker/jtreg/nullness/issue767/Class1.java @@ -11,26 +11,26 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class1 { - public static @Nullable Object field = null; - public @Nullable Object instanceField = null; + public static @Nullable Object field = null; + public @Nullable Object instanceField = null; - @EnsuresNonNull("instanceField") - public void instanceMethod() { - instanceField = new Object(); - } + @EnsuresNonNull("instanceField") + public void instanceMethod() { + instanceField = new Object(); + } - @EnsuresNonNull("Class1.field") - public static void method() { - field = new Object(); - } + @EnsuresNonNull("Class1.field") + public static void method() { + field = new Object(); + } - @EnsuresNonNull("Class2.field") - public static void method2() { - Class2.field = new Object(); - } + @EnsuresNonNull("Class2.field") + public static void method2() { + Class2.field = new Object(); + } - @EnsuresNonNull("#1.instanceField") - public static void method3(Class2 class2) { - class2.instanceField = new Object(); - } + @EnsuresNonNull("#1.instanceField") + public static void method3(Class2 class2) { + class2.instanceField = new Object(); + } } diff --git a/checker/jtreg/nullness/issue767/Class2.java b/checker/jtreg/nullness/issue767/Class2.java index 5d508a91bf6..a7e561cc440 100644 --- a/checker/jtreg/nullness/issue767/Class2.java +++ b/checker/jtreg/nullness/issue767/Class2.java @@ -2,21 +2,21 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class2 { - public static @Nullable Object field; - public @Nullable Object instanceField; + public static @Nullable Object field; + public @Nullable Object instanceField; - public static void method(Class1 class1) { - Class1.method(); - @NonNull Object o = Class1.field; - Class1.method2(); - @NonNull Object o2 = field; + public static void method(Class1 class1) { + Class1.method(); + @NonNull Object o = Class1.field; + Class1.method2(); + @NonNull Object o2 = field; - class1.instanceMethod(); - @NonNull Object o3 = class1.instanceField; - } + class1.instanceMethod(); + @NonNull Object o3 = class1.instanceField; + } - void test() { - Class1.method3(this); - @NonNull Object o = instanceField; - } + void test() { + Class1.method3(this); + @NonNull Object o = instanceField; + } } diff --git a/checker/jtreg/nullness/issue790/Class1.java b/checker/jtreg/nullness/issue790/Class1.java index 9ff0193d76b..10ac7ec761c 100644 --- a/checker/jtreg/nullness/issue790/Class1.java +++ b/checker/jtreg/nullness/issue790/Class1.java @@ -8,7 +8,7 @@ * */ public class Class1 { - Class1() { - super(this); - } + Class1() { + super(this); + } } diff --git a/checker/jtreg/nullness/issue820/AnonymousClass.java b/checker/jtreg/nullness/issue820/AnonymousClass.java index b20c622f96a..cafe4c2bb59 100644 --- a/checker/jtreg/nullness/issue820/AnonymousClass.java +++ b/checker/jtreg/nullness/issue820/AnonymousClass.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class AnonymousClass { - @NonNull Object error = null; + @NonNull Object error = null; - void method() { - Object effectivelyFinalLocal = new Object(); - Object a = - new Object() { - void foo() { - @NonNull Object nonNull = effectivelyFinalLocal; - } - }; - } + void method() { + Object effectivelyFinalLocal = new Object(); + Object a = + new Object() { + void foo() { + @NonNull Object nonNull = effectivelyFinalLocal; + } + }; + } } diff --git a/checker/jtreg/nullness/issue820/Class1.java b/checker/jtreg/nullness/issue820/Class1.java index 4634344b6ce..09fc29986be 100644 --- a/checker/jtreg/nullness/issue820/Class1.java +++ b/checker/jtreg/nullness/issue820/Class1.java @@ -12,27 +12,27 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Class1 { - public static @Nullable Object field = null; - public @Nullable Object instanceField = null; + public static @Nullable Object field = null; + public @Nullable Object instanceField = null; - @EnsuresNonNull("#1.instanceField") - public static void method3(Class2 class2) { - class2.instanceField = new Object(); - } + @EnsuresNonNull("#1.instanceField") + public static void method3(Class2 class2) { + class2.instanceField = new Object(); + } - @EnsuresNonNull("#1.instanceField") - public static void method4(Class2 class2) { - class2.instanceField = new Object(); - } + @EnsuresNonNull("#1.instanceField") + public static void method4(Class2 class2) { + class2.instanceField = new Object(); + } - @EnsuresNonNull("#1.instanceField") - public static void method5(Class2 class2) {} + @EnsuresNonNull("#1.instanceField") + public static void method5(Class2 class2) {} - @EnsuresNonNull("#1") - public static void method6(Class2 class2) {} + @EnsuresNonNull("#1") + public static void method6(Class2 class2) {} - @RequiresNonNull("#1.instanceField") - public static void method3R(Class2 class2) { - class2.instanceField.toString(); - } + @RequiresNonNull("#1.instanceField") + public static void method3R(Class2 class2) { + class2.instanceField.toString(); + } } diff --git a/checker/jtreg/nullness/issue820/Class1Min.java b/checker/jtreg/nullness/issue820/Class1Min.java index 4e899730e55..5bc809672bc 100644 --- a/checker/jtreg/nullness/issue820/Class1Min.java +++ b/checker/jtreg/nullness/issue820/Class1Min.java @@ -10,6 +10,6 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class Class1Min { - @EnsuresNonNull("#1") - public void methodInstance(Class2Min class2) {} + @EnsuresNonNull("#1") + public void methodInstance(Class2Min class2) {} } diff --git a/checker/jtreg/nullness/issue820/Class2.java b/checker/jtreg/nullness/issue820/Class2.java index a7b28dcff7e..494521a35ea 100644 --- a/checker/jtreg/nullness/issue820/Class2.java +++ b/checker/jtreg/nullness/issue820/Class2.java @@ -2,16 +2,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class2 { - public static @Nullable Object field; - public @Nullable Object instanceField; + public static @Nullable Object field; + public @Nullable Object instanceField; - void test() { - Class1.method3(this); - @NonNull Object o = instanceField; - } + void test() { + Class1.method3(this); + @NonNull Object o = instanceField; + } - void test2() { - // instanceField = new Object(); Can't reproduce with assignment - Class1.method3R(this); - } + void test2() { + // instanceField = new Object(); Can't reproduce with assignment + Class1.method3R(this); + } } diff --git a/checker/jtreg/nullness/issue820/Class2Min.java b/checker/jtreg/nullness/issue820/Class2Min.java index 086fc7c2fe7..32ffbc66d24 100644 --- a/checker/jtreg/nullness/issue820/Class2Min.java +++ b/checker/jtreg/nullness/issue820/Class2Min.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class Class2Min { - void test(Class1Min class1) { - // Any error must be issued, not suppressed, for this to reproduce - @NonNull Object o = null; - class1.methodInstance(this); - } + void test(Class1Min class1) { + // Any error must be issued, not suppressed, for this to reproduce + @NonNull Object o = null; + class1.methodInstance(this); + } } diff --git a/checker/jtreg/nullness/issue820/Error.java b/checker/jtreg/nullness/issue820/Error.java index c3c97aafe68..05c981269a1 100644 --- a/checker/jtreg/nullness/issue820/Error.java +++ b/checker/jtreg/nullness/issue820/Error.java @@ -10,5 +10,5 @@ */ public class Error { - @NonNull Object o = null; + @NonNull Object o = null; } diff --git a/checker/jtreg/nullness/issue824/Class2.java b/checker/jtreg/nullness/issue824/Class2.java index 7891cc4cd6b..f29ad11bf89 100644 --- a/checker/jtreg/nullness/issue824/Class2.java +++ b/checker/jtreg/nullness/issue824/Class2.java @@ -12,16 +12,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Class2 extends Class1 { - void call(Class1<@Nullable X> class1, Gen<@Nullable X> gen) { - class1.methodTypeParam(null); - class1.classTypeParam(null); + void call(Class1<@Nullable X> class1, Gen<@Nullable X> gen) { + class1.methodTypeParam(null); + class1.classTypeParam(null); - class1.wildcardExtends(gen); - class1.wildcardSuper(gen); - } + class1.wildcardExtends(gen); + class1.wildcardSuper(gen); + } - @Override - public T methodTypeParam(T t) { - return super.methodTypeParam(t); - } + @Override + public T methodTypeParam(T t) { + return super.methodTypeParam(t); + } } diff --git a/checker/jtreg/nullness/issue824/NoStubFirst.java b/checker/jtreg/nullness/issue824/NoStubFirst.java index 27e8a027469..847fb20f9da 100644 --- a/checker/jtreg/nullness/issue824/NoStubFirst.java +++ b/checker/jtreg/nullness/issue824/NoStubFirst.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class NoStubFirst { - public static void method( - Supplier supplier, Callable callable) {} + public static void method( + Supplier supplier, Callable callable) {} - public interface Supplier {} + public interface Supplier {} - public interface Callable {} + public interface Callable {} } diff --git a/checker/jtreg/nullness/issue824/NoStubSecond.java b/checker/jtreg/nullness/issue824/NoStubSecond.java index ab32bcd6af7..9e48421710c 100644 --- a/checker/jtreg/nullness/issue824/NoStubSecond.java +++ b/checker/jtreg/nullness/issue824/NoStubSecond.java @@ -1,13 +1,14 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class NoStubSecond { - public static void one( - NoStubFirst.Supplier supplier, NoStubFirst.Callable<@Nullable Object> callable) { - NoStubFirst.method(supplier, callable); - } + public static void one( + NoStubFirst.Supplier supplier, + NoStubFirst.Callable<@Nullable Object> callable) { + NoStubFirst.method(supplier, callable); + } - public static void two( - NoStubFirst.Supplier supplier, NoStubFirst.Callable callable) { - NoStubFirst.method(supplier, callable); - } + public static void two( + NoStubFirst.Supplier supplier, NoStubFirst.Callable callable) { + NoStubFirst.method(supplier, callable); + } } diff --git a/checker/jtreg/nullness/issue824/Second.java b/checker/jtreg/nullness/issue824/Second.java index 495e94383b8..566f3252035 100644 --- a/checker/jtreg/nullness/issue824/Second.java +++ b/checker/jtreg/nullness/issue824/Second.java @@ -9,12 +9,12 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Second { - public static void one( - First.Supplier supplier, First.Callable<@Nullable Object> callable) { - First.method(supplier, callable); - } + public static void one( + First.Supplier supplier, First.Callable<@Nullable Object> callable) { + First.method(supplier, callable); + } - public static void two(First.Supplier supplier, First.Callable callable) { - First.method(supplier, callable); - } + public static void two(First.Supplier supplier, First.Callable callable) { + First.method(supplier, callable); + } } diff --git a/checker/jtreg/nullness/issue824lib/Class1.java b/checker/jtreg/nullness/issue824lib/Class1.java index a358e8262bc..bd4ce089af7 100644 --- a/checker/jtreg/nullness/issue824lib/Class1.java +++ b/checker/jtreg/nullness/issue824lib/Class1.java @@ -1,13 +1,13 @@ public class Class1 { - class Gen {} + class Gen {} - public T methodTypeParam(T t) { - return t; - } + public T methodTypeParam(T t) { + return t; + } - public void classTypeParam(Q e) {} + public void classTypeParam(Q e) {} - public void wildcardExtends(Gen class1) {} + public void wildcardExtends(Gen class1) {} - public void wildcardSuper(Gen class1) {} + public void wildcardSuper(Gen class1) {} } diff --git a/checker/jtreg/nullness/issue824lib/First.java b/checker/jtreg/nullness/issue824lib/First.java index f8cda22c01d..c92b9a24133 100644 --- a/checker/jtreg/nullness/issue824lib/First.java +++ b/checker/jtreg/nullness/issue824lib/First.java @@ -1,7 +1,7 @@ public class First { - public interface Supplier {} + public interface Supplier {} - public interface Callable {} + public interface Callable {} - public static void method(Supplier supplier, Callable callable) {} + public static void method(Supplier supplier, Callable callable) {} } diff --git a/checker/jtreg/nullness/preciseErrorMsg/Class1.java b/checker/jtreg/nullness/preciseErrorMsg/Class1.java index cbfd5cce103..5210560daaf 100644 --- a/checker/jtreg/nullness/preciseErrorMsg/Class1.java +++ b/checker/jtreg/nullness/preciseErrorMsg/Class1.java @@ -11,6 +11,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Class1 { - @RequiresNonNull("instanceField") - public static void foo() {} + @RequiresNonNull("instanceField") + public static void foo() {} } diff --git a/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java b/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java index 42ca5de847c..5860c6dc2fe 100644 --- a/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java +++ b/checker/jtreg/nullness/preciseErrorMsg/LocateArtificialTree.java @@ -8,14 +8,15 @@ * */ +import org.checkerframework.checker.nullness.qual.*; + import java.util.List; import java.util.function.Consumer; -import org.checkerframework.checker.nullness.qual.*; public class LocateArtificialTree { - @NonNull class A {} + @NonNull class A {} - void foo() { - Consumer> c = a -> {}; - } + void foo() { + Consumer> c = a -> {}; + } } diff --git a/checker/jtreg/nullness/stub-arg/EisopIssue608.java b/checker/jtreg/nullness/stub-arg/EisopIssue608.java index 16421c1519d..9e26a807659 100644 --- a/checker/jtreg/nullness/stub-arg/EisopIssue608.java +++ b/checker/jtreg/nullness/stub-arg/EisopIssue608.java @@ -9,7 +9,7 @@ import java.util.List; public class EisopIssue608 { - boolean go(List l) { - return l.contains(null); - } + boolean go(List l) { + return l.contains(null); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/Box.java b/checker/jtreg/nullness/stub-typeparams/Box.java index 4c3f729e8d3..cd8ebb7cbdd 100644 --- a/checker/jtreg/nullness/stub-typeparams/Box.java +++ b/checker/jtreg/nullness/stub-typeparams/Box.java @@ -1,10 +1,10 @@ // This class is not compiled with the Nullness Checker, // so that no annotations are stored in bytecode. public class Box { - static Box of(S in) { - // Implementation doesn't matter. - return null; - } + static Box of(S in) { + // Implementation doesn't matter. + return null; + } - static void consume(Box producer) {} + static void consume(Box producer) {} } diff --git a/checker/jtreg/nullness/stub-typeparams/NullableBox.java b/checker/jtreg/nullness/stub-typeparams/NullableBox.java index 38647cd95bb..b2f8d3b06cf 100644 --- a/checker/jtreg/nullness/stub-typeparams/NullableBox.java +++ b/checker/jtreg/nullness/stub-typeparams/NullableBox.java @@ -4,12 +4,12 @@ // This class is not compiled with the Nullness Checker, // so that only explicit annotations are stored in bytecode. public class NullableBox { - static NullableBox of(S in) { - // Implementation doesn't matter. - return null; - } + static NullableBox of(S in) { + // Implementation doesn't matter. + return null; + } - static void consume(NullableBox producer) {} + static void consume(NullableBox producer) {} - static void nonnull(NullableBox producer) {} + static void nonnull(NullableBox producer) {} } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java index 4d3fec61055..13b8d046341 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClass.java @@ -11,5 +11,5 @@ * @compile/fail/ref=StubTypeParamsClass.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=box-nonnull.astub -Werror StubTypeParamsClass.java */ public class StubTypeParamsClass { - @Nullable Box<@Nullable Object> f; + @Nullable Box<@Nullable Object> f; } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java index 13e396d3103..8b0b88b1e55 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsClassNbl.java @@ -11,5 +11,5 @@ * @compile/fail/ref=StubTypeParamsClassNbl.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=nullablebox-nonnull.astub -Werror StubTypeParamsClassNbl.java */ public class StubTypeParamsClassNbl { - @Nullable NullableBox<@Nullable Object> f; + @Nullable NullableBox<@Nullable Object> f; } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java index ea50a93203a..0bd86b94d98 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMeth.java @@ -9,7 +9,7 @@ * @compile/fail/ref=StubTypeParamsMeth.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=box-nonnull.astub -Werror StubTypeParamsMeth.java */ public class StubTypeParamsMeth { - void use() { - Box.of(null); - } + void use() { + Box.of(null); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java index ef16f72620c..62605bc5e6d 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java +++ b/checker/jtreg/nullness/stub-typeparams/StubTypeParamsMethNbl.java @@ -9,7 +9,7 @@ * @compile/fail/ref=StubTypeParamsMethNbl.out -XDrawDiagnostics -Xlint:unchecked -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=nullablebox-nonnull.astub -Werror StubTypeParamsMethNbl.java */ public class StubTypeParamsMethNbl { - void use() { - NullableBox.of(null); - } + void use() { + NullableBox.of(null); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubWildcards.java b/checker/jtreg/nullness/stub-typeparams/StubWildcards.java index 4d968f6da07..591c18e9c96 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubWildcards.java +++ b/checker/jtreg/nullness/stub-typeparams/StubWildcards.java @@ -11,7 +11,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class StubWildcards { - void use(Box<@Nullable String> bs) { - Box.consume(bs); - } + void use(Box<@Nullable String> bs) { + Box.consume(bs); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java index ccac3821527..ea3810028a1 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java +++ b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNbl.java @@ -11,7 +11,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class StubWildcardsNbl { - void use(NullableBox<@Nullable String> bs) { - NullableBox.consume(bs); - } + void use(NullableBox<@Nullable String> bs) { + NullableBox.consume(bs); + } } diff --git a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java index 0cfb78076d2..304c3241dcb 100644 --- a/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java +++ b/checker/jtreg/nullness/stub-typeparams/StubWildcardsNon.java @@ -11,7 +11,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class StubWildcardsNon { - void use(NullableBox<@Nullable String> bs) { - NullableBox.nonnull(bs); - } + void use(NullableBox<@Nullable String> bs) { + NullableBox.nonnull(bs); + } } diff --git a/checker/jtreg/nullness/stub-warnings/Binary.java b/checker/jtreg/nullness/stub-warnings/Binary.java index 3b98cd365b1..3e5037c12ce 100644 --- a/checker/jtreg/nullness/stub-warnings/Binary.java +++ b/checker/jtreg/nullness/stub-warnings/Binary.java @@ -1,23 +1,24 @@ // This class is not compiled with the Nullness Checker, // so that only explicit annotations are stored in bytecode. -import javax.annotation.Nullable; import org.checkerframework.checker.nullness.qual.NonNull; +import javax.annotation.Nullable; + public class Binary { - @Nullable Object foo() { - return null; - } + @Nullable Object foo() { + return null; + } - Object bar(Object p) { - return null; - } + Object bar(Object p) { + return null; + } - int baz(Object @NonNull [] p) { - return 1; - } + int baz(Object @NonNull [] p) { + return 1; + } - int baz2(Object[] p) { - return 1; - } + int baz2(Object[] p) { + return 1; + } } diff --git a/checker/jtreg/rawtypes/RawTypeFail.java b/checker/jtreg/rawtypes/RawTypeFail.java index 402bf5f7cfd..ba9aecc53cd 100644 --- a/checker/jtreg/rawtypes/RawTypeFail.java +++ b/checker/jtreg/rawtypes/RawTypeFail.java @@ -11,7 +11,7 @@ import java.util.Map; public class RawTypeFail { - Map mr = new HashMap(); - Map mc = mr; - Map mc2 = new HashMap(); + Map mr = new HashMap(); + Map mc = mr; + Map mc2 = new HashMap(); } diff --git a/checker/jtreg/showPrefix/ShowPrefixTest.java b/checker/jtreg/showPrefix/ShowPrefixTest.java index 6774bba3e67..b7a2eeed968 100644 --- a/checker/jtreg/showPrefix/ShowPrefixTest.java +++ b/checker/jtreg/showPrefix/ShowPrefixTest.java @@ -8,7 +8,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ShowPrefixTest { - @NonNull Object foo(@Nullable Object nble) { - return nble; - } + @NonNull Object foo(@Nullable Object nble) { + return nble; + } } diff --git a/checker/jtreg/sortwarnings/ErrorOrders.java b/checker/jtreg/sortwarnings/ErrorOrders.java index 3368651b05d..f2f8dec12f0 100644 --- a/checker/jtreg/sortwarnings/ErrorOrders.java +++ b/checker/jtreg/sortwarnings/ErrorOrders.java @@ -9,59 +9,59 @@ /** This class tests that errors are issued in order of postion. */ public class ErrorOrders { - void test2(int i, int[] a) { - a[i] = 2; - } + void test2(int i, int[] a) { + a[i] = 2; + } - // Ignore the test suite's usage of qualifiers in illegal locations. - @SuppressWarnings("type.invalid.annotations.on.location") - int test4( - @GTENegativeOne @UpperBoundBottom int p1, - @UpperBoundBottom @GTENegativeOne int p2, - int @BottomVal [] p3, - int @SameLenBottom [] p4, - @BottomVal int p5) { + // Ignore the test suite's usage of qualifiers in illegal locations. + @SuppressWarnings("type.invalid.annotations.on.location") + int test4( + @GTENegativeOne @UpperBoundBottom int p1, + @UpperBoundBottom @GTENegativeOne int p2, + int @BottomVal [] p3, + int @SameLenBottom [] p4, + @BottomVal int p5) { - @IndexFor("p2") int z = 0; - @IndexFor("This isn't an expression") int x = z; - return x; - } + @IndexFor("p2") int z = 0; + @IndexFor("This isn't an expression") int x = z; + return x; + } - void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { - test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); - } + void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { + test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); + } - class InnerClass { - @IndexFor("This isn't an expression") int x = 0; + class InnerClass { + @IndexFor("This isn't an expression") int x = 0; - void test2(int i, int[] a) { - a[i] = 2; + void test2(int i, int[] a) { + a[i] = 2; + } } - } } class InSameCompilationUnit { - @IndexFor("This isn't an expression") int x = 0; + @IndexFor("This isn't an expression") int x = 0; - void test2(int i, int[] a) { - a[i] = 2; - } + void test2(int i, int[] a) { + a[i] = 2; + } - // Ignore the test suite's usage of qualifiers in illegal locations. - @SuppressWarnings("type.invalid.annotations.on.location") - int test4( - @GTENegativeOne @UpperBoundBottom int p1, - @UpperBoundBottom @GTENegativeOne int p2, - int @BottomVal [] p3, - int @SameLenBottom [] p4, - @BottomVal int p5) { + // Ignore the test suite's usage of qualifiers in illegal locations. + @SuppressWarnings("type.invalid.annotations.on.location") + int test4( + @GTENegativeOne @UpperBoundBottom int p1, + @UpperBoundBottom @GTENegativeOne int p2, + int @BottomVal [] p3, + int @SameLenBottom [] p4, + @BottomVal int p5) { - @IndexFor("p2") int z = 0; - @IndexFor("This isn't an expression") int x = z; - return x; - } + @IndexFor("p2") int z = 0; + @IndexFor("This isn't an expression") int x = z; + return x; + } - void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { - test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); - } + void useTest4(int p1, int p2, int[] p3, int[] p4, int p5) { + test4(p1, test4(p1, p2, p3, p4, p5), p3, p4, p5); + } } diff --git a/checker/jtreg/sortwarnings/OrderOfCheckers.java b/checker/jtreg/sortwarnings/OrderOfCheckers.java index 2b6a7f912bf..b1c2df2104d 100644 --- a/checker/jtreg/sortwarnings/OrderOfCheckers.java +++ b/checker/jtreg/sortwarnings/OrderOfCheckers.java @@ -8,9 +8,9 @@ /** This class tests that errors issued on the same tree are sorted by checker. */ public class OrderOfCheckers { - // Ignore the test suite's usage of qualifiers in illegal locations. - @SuppressWarnings("type.invalid.annotations.on.location") - void test(int[] y) { - @GTENegativeOne @UpperBoundBottom @SearchIndexBottom int @BottomVal @SameLenBottom [] x = y; - } + // Ignore the test suite's usage of qualifiers in illegal locations. + @SuppressWarnings("type.invalid.annotations.on.location") + void test(int[] y) { + @GTENegativeOne @UpperBoundBottom @SearchIndexBottom int @BottomVal @SameLenBottom [] x = y; + } } diff --git a/checker/jtreg/stubs/annotatedFor/UseTest.java b/checker/jtreg/stubs/annotatedFor/UseTest.java index ac1ed62f226..a01dba5b82c 100644 --- a/checker/jtreg/stubs/annotatedFor/UseTest.java +++ b/checker/jtreg/stubs/annotatedFor/UseTest.java @@ -10,13 +10,14 @@ package annotatedfor; -import annotatedforlib.Test; import org.checkerframework.checker.nullness.qual.NonNull; +import annotatedforlib.Test; + public class UseTest { - void test(Test test) { - test.method1(null); - test.method2(null); - @NonNull Object o = test.method3(); - } + void test(Test test) { + test.method1(null); + test.method2(null); + @NonNull Object o = test.method3(); + } } diff --git a/checker/jtreg/stubs/annotatedForLib/Test.java b/checker/jtreg/stubs/annotatedForLib/Test.java index 0e99c4991de..931ab715f48 100644 --- a/checker/jtreg/stubs/annotatedForLib/Test.java +++ b/checker/jtreg/stubs/annotatedForLib/Test.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Test { - public void method1(T t) {} + public void method1(T t) {} - public void method2(@Nullable T t) {} + public void method2(@Nullable T t) {} - public Object method3() { - return ""; - } + public Object method3() { + return ""; + } } diff --git a/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java b/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java index e6081d96b3f..56ccb10f9c4 100644 --- a/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java +++ b/checker/jtreg/stubs/defaultqualinstub/default-on-class/List.java @@ -1,3 +1,3 @@ public abstract class List { - abstract void retainAll(List other); + abstract void retainAll(List other); } diff --git a/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java b/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java index e9892d0b924..a4a6b8377bd 100644 --- a/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java +++ b/checker/jtreg/stubs/defaultqualinstub/default-on-class/MutableList.java @@ -1,4 +1,4 @@ public abstract class MutableList extends List { - @Override - abstract void retainAll(List other); + @Override + abstract void retainAll(List other); } diff --git a/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java b/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java index 42300b4dc2a..6b3689ef26f 100644 --- a/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java +++ b/checker/jtreg/stubs/defaultqualinstub/default-on-class/Test.java @@ -10,13 +10,13 @@ */ public class Test { - // l1 has type List - // mutableList has type MutableList - void foo(List l1, MutableList mutableList) { - // retainAll only accepts List with non-null elements - l1.retainAll(mutableList); + // l1 has type List + // mutableList has type MutableList + void foo(List l1, MutableList mutableList) { + // retainAll only accepts List with non-null elements + l1.retainAll(mutableList); - // l2 has type List - List l2 = mutableList; - } + // l2 has type List + List l2 = mutableList; + } } diff --git a/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java b/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java index ff9be0203a4..32e338c728a 100644 --- a/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java +++ b/checker/jtreg/stubs/defaultqualinstub/pck/Defaults.java @@ -1,11 +1,11 @@ package pck; public class Defaults { - Object o; + Object o; - void test() { - // The astub file changes o to @Nullable - // and therefore the assignment is allowed. - o = null; - } + void test() { + // The astub file changes o to @Nullable + // and therefore the assignment is allowed. + o = null; + } } diff --git a/checker/jtreg/stubs/fakeoverrides/DefineClasses.java b/checker/jtreg/stubs/fakeoverrides/DefineClasses.java index a77f8765269..8894a01278c 100644 --- a/checker/jtreg/stubs/fakeoverrides/DefineClasses.java +++ b/checker/jtreg/stubs/fakeoverrides/DefineClasses.java @@ -3,17 +3,17 @@ public class DefineClasses {} interface SuperInterface { - default int m() { - return 0; - } + default int m() { + return 0; + } } class SuperClass implements SuperInterface { - // fake override: - // @Untainted int m(); + // fake override: + // @Untainted int m(); } interface SubInterface extends SuperInterface { - // fake override: - // int m(); + // fake override: + // int m(); } diff --git a/checker/jtreg/stubs/fakeoverrides/Use.java b/checker/jtreg/stubs/fakeoverrides/Use.java index bb0e7461971..cf1f8bba478 100644 --- a/checker/jtreg/stubs/fakeoverrides/Use.java +++ b/checker/jtreg/stubs/fakeoverrides/Use.java @@ -12,8 +12,8 @@ // TODO: Issue error SuperClass and SubInterface have conflicting fake overrides // See https://github.com/typetools/checker-framework/issues/2724 public class Use extends SuperClass implements SubInterface { - void use(Use d) { - // Ok, because the fake override in SuperClasses is taken over the one in SubInterface. - @Untainted int i = d.m(); - } + void use(Use d) { + // Ok, because the fake override in SuperClasses is taken over the one in SubInterface. + @Untainted int i = d.m(); + } } diff --git a/checker/jtreg/stubs/general/Driver.java b/checker/jtreg/stubs/general/Driver.java index cf36598054f..44b018fdba8 100644 --- a/checker/jtreg/stubs/general/Driver.java +++ b/checker/jtreg/stubs/general/Driver.java @@ -6,8 +6,8 @@ */ public class Driver { - void test() { - Object o = null; - String v = String.valueOf(o); - } + void test() { + Object o = null; + String v = String.valueOf(o); + } } diff --git a/checker/jtreg/stubs/general/Driver2.java b/checker/jtreg/stubs/general/Driver2.java index 4aebab0e333..4ca1a6d98e0 100644 --- a/checker/jtreg/stubs/general/Driver2.java +++ b/checker/jtreg/stubs/general/Driver2.java @@ -5,5 +5,5 @@ */ public class Driver2 { - void test() {} + void test() {} } diff --git a/checker/jtreg/stubs/issue1356/mypackage/MyClass.java b/checker/jtreg/stubs/issue1356/mypackage/MyClass.java index 9cdb2e476f6..4167328861d 100644 --- a/checker/jtreg/stubs/issue1356/mypackage/MyClass.java +++ b/checker/jtreg/stubs/issue1356/mypackage/MyClass.java @@ -1,15 +1,15 @@ package mypackage; public class MyClass { - static String toString(char c) { - return c + ""; - } + static String toString(char c) { + return c + ""; + } - void method1(Object[] param) {} + void method1(Object[] param) {} - void method2(Object[][] param) {} + void method2(Object[][] param) {} - void method3(Object[][][] param) {} + void method3(Object[][][] param) {} - void method4(Object[][][][] param) {} + void method4(Object[][][][] param) {} } diff --git a/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java b/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java index ddf224eabf3..19855ea2deb 100644 --- a/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java +++ b/checker/jtreg/stubs/issue1356/mypackage/UseMyClass.java @@ -1,7 +1,7 @@ package mypackage; public class UseMyClass { - void test() { - String s = MyClass.toString('a'); - } + void test() { + String s = MyClass.toString('a'); + } } diff --git a/checker/jtreg/stubs/issue1456/Main.java b/checker/jtreg/stubs/issue1456/Main.java index 7cac3b2ab78..8712dbd13b3 100644 --- a/checker/jtreg/stubs/issue1456/Main.java +++ b/checker/jtreg/stubs/issue1456/Main.java @@ -7,22 +7,23 @@ */ package issue1456; -import issue1456lib.Lib; import org.checkerframework.checker.tainting.qual.Untainted; +import issue1456lib.Lib; + public class Main { - void test(Lib lib) { - @Untainted Object o = lib.object; - @Untainted byte @Untainted [] b = lib.byteArray; - @Untainted Object o1 = lib.object1; - @Untainted Object o2 = lib.object2; - @Untainted byte b2 = lib.byte1; - @Untainted byte @Untainted [] b3 = lib.byteArray2; - byte @Untainted [] @Untainted [] b4 = lib.byteArray3; - } + void test(Lib lib) { + @Untainted Object o = lib.object; + @Untainted byte @Untainted [] b = lib.byteArray; + @Untainted Object o1 = lib.object1; + @Untainted Object o2 = lib.object2; + @Untainted byte b2 = lib.byte1; + @Untainted byte @Untainted [] b3 = lib.byteArray2; + byte @Untainted [] @Untainted [] b4 = lib.byteArray3; + } - void test2(Lib l) { - Lib f = new Lib(l); - } + void test2(Lib l) { + Lib f = new Lib(l); + } } diff --git a/checker/jtreg/stubs/issue1456lib/Lib.java b/checker/jtreg/stubs/issue1456lib/Lib.java index 12817ffadcf..5e6cdd7cc87 100644 --- a/checker/jtreg/stubs/issue1456lib/Lib.java +++ b/checker/jtreg/stubs/issue1456lib/Lib.java @@ -1,11 +1,11 @@ package issue1456lib; public class Lib { - public Lib(T p) {} + public Lib(T p) {} - public Object object1, object2; - public Object object; - public byte[] byteArray; - public byte byte1, byteArray2[]; - public byte[][] byteArray3; + public Object object1, object2; + public Object object; + public byte[] byteArray; + public byte byte1, byteArray2[]; + public byte[][] byteArray3; } diff --git a/checker/jtreg/stubs/issue1542/ExampleAnno.java b/checker/jtreg/stubs/issue1542/ExampleAnno.java index ffa3740553a..a409caf077f 100644 --- a/checker/jtreg/stubs/issue1542/ExampleAnno.java +++ b/checker/jtreg/stubs/issue1542/ExampleAnno.java @@ -1,81 +1,81 @@ package issue1542; public class ExampleAnno { - public enum MyEnum { - A, - B, - C; - } - - @interface DoubleExample { - double value(); - } - - @interface FloatExample { - float value(); - } - - @interface ShortExample { - short value(); - } - - @interface IntExample { - int value(); - } - - @interface LongExample { - long value(); - } - - @interface CharExample { - char value(); - } - - @interface StringExample { - String value(); - } - - @interface ClassExample { - Class value(); - } - - @interface MyEnumExample { - MyEnum value(); - } - - @interface DoubleArrayExample { - double[] value(); - } - - @interface FloatArrayExample { - float[] value(); - } - - @interface ShortArrayExample { - short[] value(); - } - - @interface IntArrayExample { - int[] value(); - } - - @interface LongArrayExample { - long[] value(); - } - - @interface CharArrayExample { - char[] value(); - } - - @interface StringArrayExample { - String[] value(); - } - - @interface ClassArrayExample { - Class[] value(); - } - - @interface MyEnumArrayExample { - MyEnum[] value(); - } + public enum MyEnum { + A, + B, + C; + } + + @interface DoubleExample { + double value(); + } + + @interface FloatExample { + float value(); + } + + @interface ShortExample { + short value(); + } + + @interface IntExample { + int value(); + } + + @interface LongExample { + long value(); + } + + @interface CharExample { + char value(); + } + + @interface StringExample { + String value(); + } + + @interface ClassExample { + Class value(); + } + + @interface MyEnumExample { + MyEnum value(); + } + + @interface DoubleArrayExample { + double[] value(); + } + + @interface FloatArrayExample { + float[] value(); + } + + @interface ShortArrayExample { + short[] value(); + } + + @interface IntArrayExample { + int[] value(); + } + + @interface LongArrayExample { + long[] value(); + } + + @interface CharArrayExample { + char[] value(); + } + + @interface StringArrayExample { + String[] value(); + } + + @interface ClassArrayExample { + Class[] value(); + } + + @interface MyEnumArrayExample { + MyEnum[] value(); + } } diff --git a/checker/jtreg/stubs/issue1542/NeedsIntRange.java b/checker/jtreg/stubs/issue1542/NeedsIntRange.java index 9473085d26d..ff039acd6fc 100644 --- a/checker/jtreg/stubs/issue1542/NeedsIntRange.java +++ b/checker/jtreg/stubs/issue1542/NeedsIntRange.java @@ -1,11 +1,11 @@ package issue1542; public class NeedsIntRange { - public static int range(boolean big) { - if (big) { - return 20000; - } else { - return 3; + public static int range(boolean big) { + if (big) { + return 20000; + } else { + return 3; + } } - } } diff --git a/checker/jtreg/stubs/issue1542/Stub.java b/checker/jtreg/stubs/issue1542/Stub.java index f1da1ae9201..d6ffcde2884 100644 --- a/checker/jtreg/stubs/issue1542/Stub.java +++ b/checker/jtreg/stubs/issue1542/Stub.java @@ -1,34 +1,34 @@ package issue1542; public class Stub { - int x1; - int x2; - int x3; - int x4; - int x5; - int x6; - int x7; - int x8; - int x9; - int x10; - int x11; - int x12; - int x13; - int x14; - int x15; - int x16; - int x17; - int x18; - int x19; - int x20; - - public class InnerStub { + int x1; + int x2; + int x3; + int x4; + int x5; + int x6; + int x7; + int x8; + int x9; + int x10; + int x11; + int x12; + int x13; + int x14; + int x15; + int x16; + int x17; + int x18; + int x19; int x20; - class InnerInnerStub { - int x20; + public class InnerStub { + int x20; + + class InnerInnerStub { + int x20; + } } - } - class InnerStub2 {} + class InnerStub2 {} } diff --git a/checker/jtreg/stubs/issue1542/UsesIntRange.java b/checker/jtreg/stubs/issue1542/UsesIntRange.java index 0a0571805ec..d5bfaa01525 100644 --- a/checker/jtreg/stubs/issue1542/UsesIntRange.java +++ b/checker/jtreg/stubs/issue1542/UsesIntRange.java @@ -3,8 +3,8 @@ import org.checkerframework.common.value.qual.IntRange; public class UsesIntRange { - void do_things() { - @IntRange(from = 3, to = 20000) int x = NeedsIntRange.range(true); - @IntRange(from = 3L, to = 20000) int y = NeedsIntRange.range(true); - } + void do_things() { + @IntRange(from = 3, to = 20000) int x = NeedsIntRange.range(true); + @IntRange(from = 3L, to = 20000) int y = NeedsIntRange.range(true); + } } diff --git a/checker/jtreg/stubs/issue1565/MyListGeneric.java b/checker/jtreg/stubs/issue1565/MyListGeneric.java index 719f64c91ea..d4fe937c69a 100644 --- a/checker/jtreg/stubs/issue1565/MyListGeneric.java +++ b/checker/jtreg/stubs/issue1565/MyListGeneric.java @@ -8,7 +8,7 @@ import java.util.AbstractList; public abstract class MyListGeneric extends AbstractList { - public MyListGeneric() { - clear(); - } + public MyListGeneric() { + clear(); + } } diff --git a/checker/jtreg/stubs/issue2147/EnumStubTest.java b/checker/jtreg/stubs/issue2147/EnumStubTest.java index 05de3b58088..79c6974beb4 100644 --- a/checker/jtreg/stubs/issue2147/EnumStubTest.java +++ b/checker/jtreg/stubs/issue2147/EnumStubTest.java @@ -11,10 +11,10 @@ import org.checkerframework.checker.tainting.qual.*; public class EnumStubTest { - void test() { - requireEnum(SampleEnum.FIRST); - requireEnum(SampleEnum.SECOND); - } + void test() { + requireEnum(SampleEnum.FIRST); + requireEnum(SampleEnum.SECOND); + } - void requireEnum(@Untainted SampleEnum sEnum) {} + void requireEnum(@Untainted SampleEnum sEnum) {} } diff --git a/checker/jtreg/stubs/issue2147/SampleEnum.java b/checker/jtreg/stubs/issue2147/SampleEnum.java index ef1bb6a85f6..912149c2fc4 100644 --- a/checker/jtreg/stubs/issue2147/SampleEnum.java +++ b/checker/jtreg/stubs/issue2147/SampleEnum.java @@ -1,6 +1,6 @@ import org.checkerframework.checker.tainting.qual.*; public enum SampleEnum { - FIRST, - SECOND; + FIRST, + SECOND; } diff --git a/checker/jtreg/stubs/sample/Sample.java b/checker/jtreg/stubs/sample/Sample.java index d2c2501be6b..9b15e7eb156 100644 --- a/checker/jtreg/stubs/sample/Sample.java +++ b/checker/jtreg/stubs/sample/Sample.java @@ -6,8 +6,8 @@ */ public class Sample { - void test() { - Object o = null; - String v = String.valueOf(o); - } + void test() { + Object o = null; + String v = String.valueOf(o); + } } diff --git a/checker/jtreg/stubs/stub-over-jdk/Issue321.java b/checker/jtreg/stubs/stub-over-jdk/Issue321.java index d0e1ae09e8d..ad8fac0360f 100644 --- a/checker/jtreg/stubs/stub-over-jdk/Issue321.java +++ b/checker/jtreg/stubs/stub-over-jdk/Issue321.java @@ -5,9 +5,10 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Astubs=Issue321.astub Issue321.java */ -import java.util.Optional; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Optional; + interface Issue321 { - @Nullable Optional o(); + @Nullable Optional o(); } diff --git a/checker/jtreg/stubs/wildcards/Wildcards.java b/checker/jtreg/stubs/wildcards/Wildcards.java index ae6d6f15aab..79caa4414aa 100644 --- a/checker/jtreg/stubs/wildcards/Wildcards.java +++ b/checker/jtreg/stubs/wildcards/Wildcards.java @@ -7,9 +7,9 @@ */ public class Wildcards { - NonN f = new NonN(); + NonN f = new NonN(); - class LocalNonN {} + class LocalNonN {} - LocalNonN g = new LocalNonN(); + LocalNonN g = new LocalNonN(); } diff --git a/checker/jtreg/tainting/NewClass.java b/checker/jtreg/tainting/NewClass.java index 67c2662856a..810f09b3130 100644 --- a/checker/jtreg/tainting/NewClass.java +++ b/checker/jtreg/tainting/NewClass.java @@ -8,13 +8,13 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class NewClass { - public NewClass(Object param) {} + public NewClass(Object param) {} - Object get(@Untainted Object o) { - return o; - } + Object get(@Untainted Object o) { + return o; + } - void test() { - NewClass newClass = new NewClass(get(get(""))); - } + void test() { + NewClass newClass = new NewClass(get(get(""))); + } } diff --git a/checker/jtreg/tainting/classes/Issue919.java b/checker/jtreg/tainting/classes/Issue919.java index 900178464fe..84ffb923cfd 100644 --- a/checker/jtreg/tainting/classes/Issue919.java +++ b/checker/jtreg/tainting/classes/Issue919.java @@ -1,10 +1,11 @@ package classes; -import classes.Issue919B.InnerClass; import java.util.Set; +import classes.Issue919B.InnerClass; + public class Issue919 { - private static void method(Set innerClassSet2) throws Exception { - Issue919B.otherMethod(innerClassSet2); - } + private static void method(Set innerClassSet2) throws Exception { + Issue919B.otherMethod(innerClassSet2); + } } diff --git a/checker/jtreg/tainting/classes/Issue919B.java b/checker/jtreg/tainting/classes/Issue919B.java index 6c906a369de..558fefa9559 100644 --- a/checker/jtreg/tainting/classes/Issue919B.java +++ b/checker/jtreg/tainting/classes/Issue919B.java @@ -5,20 +5,20 @@ public class Issue919B { - @SuppressWarnings("nullness:return.type.incompatible") - public static Map otherMethod(Set innerClassSet) { - return null; - } + @SuppressWarnings("nullness:return.type.incompatible") + public static Map otherMethod(Set innerClassSet) { + return null; + } - public static class InnerClass { + public static class InnerClass { - InnerClass() {} + InnerClass() {} - InnerClass method(String a) { - return new InnerClass(); + InnerClass method(String a) { + return new InnerClass(); + } } - } - // This class is required in order to reproduce the error. - public static class AnotherInnerClass {} + // This class is required in order to reproduce the error. + public static class AnotherInnerClass {} } diff --git a/checker/jtreg/unboundedWildcards/issue1275/Crash.java b/checker/jtreg/unboundedWildcards/issue1275/Crash.java index 46ecb23c8d5..2f3fddf281f 100644 --- a/checker/jtreg/unboundedWildcards/issue1275/Crash.java +++ b/checker/jtreg/unboundedWildcards/issue1275/Crash.java @@ -7,8 +7,8 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext Crash.java */ public class Crash { - void crash(Sub o) { - Sub.SubInner x = o.a().b().b(); - o.a().b().b().c(); - } + void crash(Sub o) { + Sub.SubInner x = o.a().b().b(); + o.a().b().b().c(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1275/Lib.java b/checker/jtreg/unboundedWildcards/issue1275/Lib.java index 769ae602059..3701d382939 100644 --- a/checker/jtreg/unboundedWildcards/issue1275/Lib.java +++ b/checker/jtreg/unboundedWildcards/issue1275/Lib.java @@ -1,22 +1,22 @@ abstract class Super { - abstract SuperInner a(); + abstract SuperInner a(); - abstract static class SuperInner> { - abstract T b(); + abstract static class SuperInner> { + abstract T b(); - abstract Super c(); - } + abstract Super c(); + } } abstract class Sub extends Super { - // It is significant that this method specializes the - // return type. If this returns SuperInner, no crash happens. - @Override - abstract SubInner a(); - - abstract static class SubInner> extends Super.SuperInner { - // Crashes with this overridden method and passes without it. + // It is significant that this method specializes the + // return type. If this returns SuperInner, no crash happens. @Override - abstract Super c(); - } + abstract SubInner a(); + + abstract static class SubInner> extends Super.SuperInner { + // Crashes with this overridden method and passes without it. + @Override + abstract Super c(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1420/Crash8.java b/checker/jtreg/unboundedWildcards/issue1420/Crash8.java index 554a114671a..6eb55f8104f 100644 --- a/checker/jtreg/unboundedWildcards/issue1420/Crash8.java +++ b/checker/jtreg/unboundedWildcards/issue1420/Crash8.java @@ -7,8 +7,8 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker Crash8.java */ abstract class Crash8> { - void test(Crash8Lib.Main main, boolean b, Class cls) { - Crash8Lib.MyIterable x = main.foo(cls).get1().getIterable2(); - x = b ? x : main.foo(cls).get1().getIterable2(); - } + void test(Crash8Lib.Main main, boolean b, Class cls) { + Crash8Lib.MyIterable x = main.foo(cls).get1().getIterable2(); + x = b ? x : main.foo(cls).get1().getIterable2(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java b/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java index 386ea3b455d..4cdb6cf77c2 100644 --- a/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java +++ b/checker/jtreg/unboundedWildcards/issue1420/Crash8Lib.java @@ -1,19 +1,19 @@ interface Crash8Lib { - interface MyIterable extends Iterable {} + interface MyIterable extends Iterable {} - interface Box> {} + interface Box> {} - interface Root, D> { - C get1(); + interface Root, D> { + C get1(); - MyIterable getIterable2(); - } + MyIterable getIterable2(); + } - interface Sub, G, H extends Box> extends Root {} + interface Sub, G, H extends Box> extends Root {} - interface Leaf, J extends Box> extends Sub {} + interface Leaf, J extends Box> extends Sub {} - interface Main { - > Leaf foo(Class b); - } + interface Main { + > Leaf foo(Class b); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1427/B.java b/checker/jtreg/unboundedWildcards/issue1427/B.java index ec411f85a4a..2ac65df83c3 100644 --- a/checker/jtreg/unboundedWildcards/issue1427/B.java +++ b/checker/jtreg/unboundedWildcards/issue1427/B.java @@ -1,28 +1,28 @@ class One { - abstract static class B, C extends One> { - abstract C build(); + abstract static class B, C extends One> { + abstract C build(); - A f() { - throw new AssertionError(); + A f() { + throw new AssertionError(); + } } - } } class Two extends One { - static class B> extends One.B { - @Override - Two build() { - throw new AssertionError(); + static class B> extends One.B { + @Override + Two build() { + throw new AssertionError(); + } } - } } class Three> extends Two.B { - static Three c() { - throw new AssertionError(); - } + static Three c() { + throw new AssertionError(); + } - E g() { - throw new AssertionError(); - } + E g() { + throw new AssertionError(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1427/T.java b/checker/jtreg/unboundedWildcards/issue1427/T.java index 632bf45d238..5fcad3ecdc0 100644 --- a/checker/jtreg/unboundedWildcards/issue1427/T.java +++ b/checker/jtreg/unboundedWildcards/issue1427/T.java @@ -7,7 +7,7 @@ * @compile -XDrawDiagnostics -processor org.checkerframework.checker.tainting.TaintingChecker T.java */ public class T { - { - Three.c().g().f().build(); - } + { + Three.c().g().f().build(); + } } diff --git a/checker/jtreg/unboundedWildcards/issue1428/B.java b/checker/jtreg/unboundedWildcards/issue1428/B.java index 181d6da11d8..d2fd0035002 100644 --- a/checker/jtreg/unboundedWildcards/issue1428/B.java +++ b/checker/jtreg/unboundedWildcards/issue1428/B.java @@ -1,7 +1,7 @@ import java.util.List; interface B { - List> getItems(); + List> getItems(); } interface V {} diff --git a/checker/jtreg/unboundedWildcards/issue1428/T.java b/checker/jtreg/unboundedWildcards/issue1428/T.java index fe27125b5c9..e85b0e55fbd 100644 --- a/checker/jtreg/unboundedWildcards/issue1428/T.java +++ b/checker/jtreg/unboundedWildcards/issue1428/T.java @@ -11,7 +11,7 @@ import java.util.List; public class T { - public List> f(B b) { - return true ? Collections.>emptyList() : b.getItems(); - } + public List> f(B b) { + return true ? Collections.>emptyList() : b.getItems(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java index 6eed186bdce..1ba2168db45 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java @@ -2,54 +2,57 @@ import com.google.common.collect.ImmutableSet; import com.sun.tools.javac.code.Type; -import java.util.Set; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.common.accumulation.AccumulationAnalysis; import org.checkerframework.common.basetype.BaseTypeChecker; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + /** * The analysis for the Called Methods Checker. The analysis is specialized to ignore certain * exception types; see {@link #isIgnoredExceptionType(TypeMirror)}. */ public class CalledMethodsAnalysis extends AccumulationAnalysis { - /** - * The fully-qualified names of the exception types that are ignored by this checker when - * computing dataflow stores. - */ - protected static final Set<@CanonicalName String> ignoredExceptionTypes = - ImmutableSet.of( - // Use the Nullness Checker instead. - NullPointerException.class.getCanonicalName(), - // Ignore run-time errors, which cannot be predicted statically. Doing - // so is unsound in the sense that they could still occur -- e.g., the - // program could run out of memory -- but if they did, the checker's - // results would be useless anyway. - Error.class.getCanonicalName(), - RuntimeException.class.getCanonicalName()); - - /** - * Creates a new {@code CalledMethodsAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - protected CalledMethodsAnalysis( - BaseTypeChecker checker, CalledMethodsAnnotatedTypeFactory factory) { - super(checker, factory); - } - - /** - * Ignore exceptional control flow due to ignored exception types. - * - * @param exceptionType exception type - * @return {@code true} if {@code exceptionType} is a member of {@link #ignoredExceptionTypes}, - * {@code false} otherwise - */ - @Override - protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { - return ignoredExceptionTypes.contains( - ((Type) exceptionType).tsym.getQualifiedName().toString()); - } + /** + * The fully-qualified names of the exception types that are ignored by this checker when + * computing dataflow stores. + */ + protected static final Set<@CanonicalName String> ignoredExceptionTypes = + ImmutableSet.of( + // Use the Nullness Checker instead. + NullPointerException.class.getCanonicalName(), + // Ignore run-time errors, which cannot be predicted statically. Doing + // so is unsound in the sense that they could still occur -- e.g., the + // program could run out of memory -- but if they did, the checker's + // results would be useless anyway. + Error.class.getCanonicalName(), + RuntimeException.class.getCanonicalName()); + + /** + * Creates a new {@code CalledMethodsAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + protected CalledMethodsAnalysis( + BaseTypeChecker checker, CalledMethodsAnnotatedTypeFactory factory) { + super(checker, factory); + } + + /** + * Ignore exceptional control flow due to ignored exception types. + * + * @param exceptionType exception type + * @return {@code true} if {@code exceptionType} is a member of {@link #ignoredExceptionTypes}, + * {@code false} otherwise + */ + @Override + protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { + return ignoredExceptionTypes.contains( + ((Type) exceptionType).tsym.getQualifiedName().toString()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java index 5aa98ce41fd..6b9edbeef73 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnnotatedTypeFactory.java @@ -4,19 +4,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.calledmethods.builder.AutoValueSupport; import org.checkerframework.checker.calledmethods.builder.BuilderFrameworkSupport; import org.checkerframework.checker.calledmethods.builder.LombokSupport; @@ -47,509 +35,547 @@ import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.UserError; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** The annotated type factory for the Called Methods Checker. */ public class CalledMethodsAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory { - /** - * The builder frameworks (such as Lombok and AutoValue) supported by this instance of the Called - * Methods Checker. - */ - private final Collection builderFrameworkSupports; - - /** - * Whether to use the Value Checker as a subchecker to reduce false positives when analyzing calls - * to the AWS SDK. Defaults to false. Controlled by the command-line option {@code - * -AuseValueChecker}. - */ - private final boolean useValueChecker; - - /** - * The {@link java.util.Collections#singletonList} method. It is treated specially by {@link - * #adjustMethodNameUsingValueChecker}. - */ - private final ExecutableElement collectionsSingletonList = - TreeUtils.getMethod("java.util.Collections", "singletonList", 1, getProcessingEnv()); - - /** The {@link CalledMethods#value} element/argument. */ - /*package-private*/ final ExecutableElement calledMethodsValueElement = - TreeUtils.getMethod(CalledMethods.class, "value", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsVarArgs#value} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsVarArgsValueElement = - TreeUtils.getMethod(EnsuresCalledMethodsVarArgs.class, "value", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsOnException#value} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionValueElement = - TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "value", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsOnException#methods} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionMethodsElement = - TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "methods", 0, processingEnv); - - /** The {@link EnsuresCalledMethodsOnException.List#value} element/argument. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionListValueElement = - TreeUtils.getMethod(EnsuresCalledMethodsOnException.List.class, "value", 0, processingEnv); - - /** - * Create a new CalledMethodsAnnotatedTypeFactory. - * - * @param checker the checker - */ - public CalledMethodsAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class); - - this.builderFrameworkSupports = new ArrayList<>(2); - List disabledFrameworks = - checker.getStringsOption(CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, ','); - enableFrameworks(disabledFrameworks); - - this.useValueChecker = checker.hasOption(CalledMethodsChecker.USE_VALUE_CHECKER); - - // Lombok generates @CalledMethods annotations using an old package name, - // so we maintain it as an alias. - addAliasedTypeAnnotation( - "org.checkerframework.checker.builder.qual.CalledMethods", CalledMethods.class, true); - // Lombok also generates an @NotCalledMethods annotation, which we have no support for. We - // therefore treat it as top. - addAliasedTypeAnnotation( - "org.checkerframework.checker.builder.qual.NotCalledMethods", this.top); - - // Don't call postInit() for subclasses. - if (this.getClass() == CalledMethodsAnnotatedTypeFactory.class) { - this.postInit(); + /** + * The builder frameworks (such as Lombok and AutoValue) supported by this instance of the + * Called Methods Checker. + */ + private final Collection builderFrameworkSupports; + + /** + * Whether to use the Value Checker as a subchecker to reduce false positives when analyzing + * calls to the AWS SDK. Defaults to false. Controlled by the command-line option {@code + * -AuseValueChecker}. + */ + private final boolean useValueChecker; + + /** + * The {@link java.util.Collections#singletonList} method. It is treated specially by {@link + * #adjustMethodNameUsingValueChecker}. + */ + private final ExecutableElement collectionsSingletonList = + TreeUtils.getMethod("java.util.Collections", "singletonList", 1, getProcessingEnv()); + + /** The {@link CalledMethods#value} element/argument. */ + /*package-private*/ final ExecutableElement calledMethodsValueElement = + TreeUtils.getMethod(CalledMethods.class, "value", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsVarArgs#value} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsVarArgsValueElement = + TreeUtils.getMethod(EnsuresCalledMethodsVarArgs.class, "value", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsOnException#value} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionValueElement = + TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "value", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsOnException#methods} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionMethodsElement = + TreeUtils.getMethod(EnsuresCalledMethodsOnException.class, "methods", 0, processingEnv); + + /** The {@link EnsuresCalledMethodsOnException.List#value} element/argument. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsOnExceptionListValueElement = + TreeUtils.getMethod( + EnsuresCalledMethodsOnException.List.class, "value", 0, processingEnv); + + /** + * Create a new CalledMethodsAnnotatedTypeFactory. + * + * @param checker the checker + */ + public CalledMethodsAnnotatedTypeFactory(BaseTypeChecker checker) { + super( + checker, + CalledMethods.class, + CalledMethodsBottom.class, + CalledMethodsPredicate.class); + + this.builderFrameworkSupports = new ArrayList<>(2); + List disabledFrameworks = + checker.getStringsOption( + CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, ','); + enableFrameworks(disabledFrameworks); + + this.useValueChecker = checker.hasOption(CalledMethodsChecker.USE_VALUE_CHECKER); + + // Lombok generates @CalledMethods annotations using an old package name, + // so we maintain it as an alias. + addAliasedTypeAnnotation( + "org.checkerframework.checker.builder.qual.CalledMethods", + CalledMethods.class, + true); + // Lombok also generates an @NotCalledMethods annotation, which we have no support for. We + // therefore treat it as top. + addAliasedTypeAnnotation( + "org.checkerframework.checker.builder.qual.NotCalledMethods", this.top); + + // Don't call postInit() for subclasses. + if (this.getClass() == CalledMethodsAnnotatedTypeFactory.class) { + this.postInit(); + } } - } - - /** - * Enables support for the default builder-generation frameworks, except those listed in the - * disabled builder frameworks parsed from the -AdisableBuilderFrameworkSupport option's - * arguments. Throws a UserError if the user included an unsupported framework in the list of - * frameworks to be disabled. - * - * @param disabledFrameworks the disabled builder frameworks - */ - private void enableFrameworks(List disabledFrameworks) { - boolean enableAutoValueSupport = true; - boolean enableLombokSupport = true; - for (String framework : disabledFrameworks) { - switch (framework) { - case "autovalue": - enableAutoValueSupport = false; - break; - case "lombok": - enableLombokSupport = false; - break; - default: - throw new UserError( - "Unsupported builder framework in -AdisableBuilderFrameworkSupports: " + framework); - } + + /** + * Enables support for the default builder-generation frameworks, except those listed in the + * disabled builder frameworks parsed from the -AdisableBuilderFrameworkSupport option's + * arguments. Throws a UserError if the user included an unsupported framework in the list of + * frameworks to be disabled. + * + * @param disabledFrameworks the disabled builder frameworks + */ + private void enableFrameworks(List disabledFrameworks) { + boolean enableAutoValueSupport = true; + boolean enableLombokSupport = true; + for (String framework : disabledFrameworks) { + switch (framework) { + case "autovalue": + enableAutoValueSupport = false; + break; + case "lombok": + enableLombokSupport = false; + break; + default: + throw new UserError( + "Unsupported builder framework in -AdisableBuilderFrameworkSupports: " + + framework); + } + } + if (enableAutoValueSupport) { + builderFrameworkSupports.add(new AutoValueSupport(this)); + } + if (enableLombokSupport) { + builderFrameworkSupports.add(new LombokSupport(this)); + } } - if (enableAutoValueSupport) { - builderFrameworkSupports.add(new AutoValueSupport(this)); + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new CalledMethodsTreeAnnotator(this)); } - if (enableLombokSupport) { - builderFrameworkSupports.add(new LombokSupport(this)); + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator( + super.createTypeAnnotator(), new CalledMethodsTypeAnnotator(this)); } - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new CalledMethodsTreeAnnotator(this)); - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(super.createTypeAnnotator(), new CalledMethodsTypeAnnotator(this)); - } - - @Override - public boolean returnsThis(MethodInvocationTree tree) { - return super.returnsThis(tree) - // Continue to trust but not check the old {@link - // org.checkerframework.checker.builder.qual.ReturnsReceiver} annotation, for - // backwards compatibility. - || this.getDeclAnnotation( - TreeUtils.elementFromUse(tree), - org.checkerframework.checker.builder.qual.ReturnsReceiver.class) - != null; - } - - /** - * Given a tree, returns the name of the method that the tree should be considered as calling. - * Returns "withOwners" if the call sets an "owner", "owner-alias", or "owner-id" filter. Returns - * "withImageIds" if the call sets an "image-ids" filter. - * - *

          Package-private to permit calls from {@link CalledMethodsTransfer}. - * - * @param methodName the name of the method being explicitly called - * @param tree the invocation of the method - * @return "withOwners" or "withImageIds" if the tree is an equivalent filter addition. Otherwise, - * return the first argument. - */ - // This cannot return a Name because filterTreeToMethodName cannot. - public String adjustMethodNameUsingValueChecker(String methodName, MethodInvocationTree tree) { - if (!useValueChecker) { - return methodName; + + @Override + public boolean returnsThis(MethodInvocationTree tree) { + return super.returnsThis(tree) + // Continue to trust but not check the old {@link + // org.checkerframework.checker.builder.qual.ReturnsReceiver} annotation, for + // backwards compatibility. + || this.getDeclAnnotation( + TreeUtils.elementFromUse(tree), + org.checkerframework.checker.builder.qual.ReturnsReceiver.class) + != null; } - ExecutableElement invokedMethod = TreeUtils.elementFromUse(tree); - if (!ElementUtils.enclosingTypeElement(invokedMethod) - .getQualifiedName() - .contentEquals("com.amazonaws.services.ec2.model.DescribeImagesRequest")) { - return methodName; + /** + * Given a tree, returns the name of the method that the tree should be considered as calling. + * Returns "withOwners" if the call sets an "owner", "owner-alias", or "owner-id" filter. + * Returns "withImageIds" if the call sets an "image-ids" filter. + * + *

          Package-private to permit calls from {@link CalledMethodsTransfer}. + * + * @param methodName the name of the method being explicitly called + * @param tree the invocation of the method + * @return "withOwners" or "withImageIds" if the tree is an equivalent filter addition. + * Otherwise, return the first argument. + */ + // This cannot return a Name because filterTreeToMethodName cannot. + public String adjustMethodNameUsingValueChecker(String methodName, MethodInvocationTree tree) { + if (!useValueChecker) { + return methodName; + } + + ExecutableElement invokedMethod = TreeUtils.elementFromUse(tree); + if (!ElementUtils.enclosingTypeElement(invokedMethod) + .getQualifiedName() + .contentEquals("com.amazonaws.services.ec2.model.DescribeImagesRequest")) { + return methodName; + } + + if (methodName.equals("withFilters") || methodName.equals("setFilters")) { + ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); + for (Tree filterTree : tree.getArguments()) { + if (TreeUtils.isMethodInvocation( + filterTree, collectionsSingletonList, getProcessingEnv())) { + // Descend into a call to Collections.singletonList() + filterTree = ((MethodInvocationTree) filterTree).getArguments().get(0); + } + String adjustedMethodName = filterTreeToMethodName(filterTree, valueATF); + if (adjustedMethodName != null) { + return adjustedMethodName; + } + } + } + return methodName; } - if (methodName.equals("withFilters") || methodName.equals("setFilters")) { - ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); - for (Tree filterTree : tree.getArguments()) { - if (TreeUtils.isMethodInvocation( - filterTree, collectionsSingletonList, getProcessingEnv())) { - // Descend into a call to Collections.singletonList() - filterTree = ((MethodInvocationTree) filterTree).getArguments().get(0); + /** + * Determine the name of the method in DescribeImagesRequest that is equivalent to the Filter in + * the given tree. + * + *

          Returns null unless the argument is one of the following: + * + *

            + *
          • a constructor invocation of the Filter constructor whose first argument is the name, + * such as {@code new Filter("owner").*}, or + *
          • a call to the withName method, such as {@code new Filter().*.withName("owner").*}. + *
          + * + * In those cases, it returns either the argument to the constructor or the argument to the last + * invocation of withName ("owner" in both of the above examples). + * + * @param filterTree the tree that represents the filter (an argument to the withFilters or + * setFilters method) + * @param valueATF the type factory from the Value Checker + * @return the adjusted method name, or null if the method name should not be adjusted + */ + // This cannot return a Name because filterKindToMethodName cannot. + private @Nullable String filterTreeToMethodName( + Tree filterTree, ValueAnnotatedTypeFactory valueATF) { + while (filterTree != null && filterTree.getKind() == Tree.Kind.METHOD_INVOCATION) { + + MethodInvocationTree filterTreeAsMethodInvocation = (MethodInvocationTree) filterTree; + String filterMethodName = TreeUtils.methodName(filterTreeAsMethodInvocation).toString(); + if (filterMethodName.contentEquals("withName") + && filterTreeAsMethodInvocation.getArguments().size() >= 1) { + Tree withNameArgTree = filterTreeAsMethodInvocation.getArguments().get(0); + String withNameArg = + ValueCheckerUtils.getExactStringValue(withNameArgTree, valueATF); + return filterKindToMethodName(withNameArg); + } + // Proceed leftward (toward the receiver) in a fluent call sequence. + filterTree = TreeUtils.getReceiverTree(filterTreeAsMethodInvocation.getMethodSelect()); + } + // The loop has reached the beginning of a fluent sequence of method calls. If the ultimate + // receiver at the beginning of that fluent sequence is a call to the Filter() constructor, + // then use the first argument to the Filter constructor, which is the name of the filter. + if (filterTree == null) { + return null; } - String adjustedMethodName = filterTreeToMethodName(filterTree, valueATF); - if (adjustedMethodName != null) { - return adjustedMethodName; + if (filterTree.getKind() == Tree.Kind.NEW_CLASS) { + ExpressionTree constructorArg = ((NewClassTree) filterTree).getArguments().get(0); + String filterKindName = ValueCheckerUtils.getExactStringValue(constructorArg, valueATF); + if (filterKindName != null) { + return filterKindToMethodName(filterKindName); + } + } + return null; + } + + /** + * Converts from a kind of filter to the name of the corresponding method on a + * DescribeImagesRequest object. + * + * @param filterKind the kind of filter + * @return "withOwners" if filterKind is "owner", "owner-alias", or "owner-id"; "withImageIds" + * if filterKind is "image-id"; null otherwise + */ + private static @Nullable String filterKindToMethodName(String filterKind) { + switch (filterKind) { + case "owner": + case "owner-alias": + case "owner-id": + return "withOwners"; + case "image-id": + return "withImageIds"; + default: + return null; } - } } - return methodName; - } - - /** - * Determine the name of the method in DescribeImagesRequest that is equivalent to the Filter in - * the given tree. - * - *

          Returns null unless the argument is one of the following: - * - *

            - *
          • a constructor invocation of the Filter constructor whose first argument is the name, such - * as {@code new Filter("owner").*}, or - *
          • a call to the withName method, such as {@code new Filter().*.withName("owner").*}. - *
          - * - * In those cases, it returns either the argument to the constructor or the argument to the last - * invocation of withName ("owner" in both of the above examples). - * - * @param filterTree the tree that represents the filter (an argument to the withFilters or - * setFilters method) - * @param valueATF the type factory from the Value Checker - * @return the adjusted method name, or null if the method name should not be adjusted - */ - // This cannot return a Name because filterKindToMethodName cannot. - private @Nullable String filterTreeToMethodName( - Tree filterTree, ValueAnnotatedTypeFactory valueATF) { - while (filterTree != null && filterTree.getKind() == Tree.Kind.METHOD_INVOCATION) { - - MethodInvocationTree filterTreeAsMethodInvocation = (MethodInvocationTree) filterTree; - String filterMethodName = TreeUtils.methodName(filterTreeAsMethodInvocation).toString(); - if (filterMethodName.contentEquals("withName") - && filterTreeAsMethodInvocation.getArguments().size() >= 1) { - Tree withNameArgTree = filterTreeAsMethodInvocation.getArguments().get(0); - String withNameArg = ValueCheckerUtils.getExactStringValue(withNameArgTree, valueATF); - return filterKindToMethodName(withNameArg); - } - // Proceed leftward (toward the receiver) in a fluent call sequence. - filterTree = TreeUtils.getReceiverTree(filterTreeAsMethodInvocation.getMethodSelect()); + + /** + * At a fluent method call (which returns {@code this}), add the method to the type of the + * return value. + */ + private class CalledMethodsTreeAnnotator extends AccumulationTreeAnnotator { + /** + * Creates an instance of this tree annotator for the given type factory. + * + * @param factory the type factory + */ + public CalledMethodsTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { + super(factory); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + // Accumulate a method call, by adding the method being invoked to the return type. + if (returnsThis(tree)) { + TypeMirror typeMirror = type.getUnderlyingType(); + String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); + methodName = adjustMethodNameUsingValueChecker(methodName, tree); + AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); + AnnotationMirror newAnno = + qualHierarchy.greatestLowerBoundShallow( + oldAnno, + typeMirror, + createAccumulatorAnnotation(methodName), + typeMirror); + type.replaceAnnotation(newAnno); + } + + // Also do the standard accumulation analysis behavior: copy any accumulation + // annotations from the receiver to the return type. + return super.visitMethodInvocation(tree, type); + } + + @Override + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { + builderFrameworkSupport.handleConstructor(tree, type); + } + return super.visitNewClass(tree, type); + } } - // The loop has reached the beginning of a fluent sequence of method calls. If the ultimate - // receiver at the beginning of that fluent sequence is a call to the Filter() constructor, - // then use the first argument to the Filter constructor, which is the name of the filter. - if (filterTree == null) { - return null; + + /** + * Adds @CalledMethod annotations for build() methods of AutoValue and Lombok Builders to ensure + * required properties have been set. + */ + private class CalledMethodsTypeAnnotator extends TypeAnnotator { + + /** + * Constructor matching super. + * + * @param atypeFactory the type factory + */ + public CalledMethodsTypeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { + ExecutableElement element = t.getElement(); + + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + + for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { + if (builderFrameworkSupport.isToBuilderMethod(element)) { + builderFrameworkSupport.handleToBuilderMethod(t); + } + } + + Element nextEnclosingElement = enclosingElement.getEnclosingElement(); + if (nextEnclosingElement.getKind().isClass()) { + for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { + if (builderFrameworkSupport.isBuilderBuildMethod(element)) { + builderFrameworkSupport.handleBuilderBuildMethod(t); + } + } + } + + return super.visitExecutable(t, p); + } } - if (filterTree.getKind() == Tree.Kind.NEW_CLASS) { - ExpressionTree constructorArg = ((NewClassTree) filterTree).getArguments().get(0); - String filterKindName = ValueCheckerUtils.getExactStringValue(constructorArg, valueATF); - if (filterKindName != null) { - return filterKindToMethodName(filterKindName); - } + + @Override + protected CalledMethodsAnalysis createFlowAnalysis() { + return new CalledMethodsAnalysis(checker, this); } - return null; - } - - /** - * Converts from a kind of filter to the name of the corresponding method on a - * DescribeImagesRequest object. - * - * @param filterKind the kind of filter - * @return "withOwners" if filterKind is "owner", "owner-alias", or "owner-id"; "withImageIds" if - * filterKind is "image-id"; null otherwise - */ - private static @Nullable String filterKindToMethodName(String filterKind) { - switch (filterKind) { - case "owner": - case "owner-alias": - case "owner-id": - return "withOwners"; - case "image-id": - return "withImageIds"; - default: + + /** + * Returns the annotation type mirror for the type of {@code expressionTree} with default + * annotations applied. As types relevant to Called Methods checking are rarely used inside + * generics, this is typically the best choice for type inference. + */ + @Override + public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { + TypeMirror type = TreeUtils.typeOf(expressionTree); + if (type.getKind() != TypeKind.VOID) { + AnnotatedTypeMirror atm = type(expressionTree); + addDefaultAnnotations(atm); + return atm; + } return null; } - } - /** - * At a fluent method call (which returns {@code this}), add the method to the type of the return - * value. - */ - private class CalledMethodsTreeAnnotator extends AccumulationTreeAnnotator { /** - * Creates an instance of this tree annotator for the given type factory. + * Fetch the supported builder frameworks that are enabled. * - * @param factory the type factory + * @return a collection of builder frameworks that are enabled in this run of the checker */ - public CalledMethodsTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { - super(factory); + /*package-private*/ Collection getBuilderFrameworkSupports() { + return builderFrameworkSupports; } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - // Accumulate a method call, by adding the method being invoked to the return type. - if (returnsThis(tree)) { - TypeMirror typeMirror = type.getUnderlyingType(); - String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); - methodName = adjustMethodNameUsingValueChecker(methodName, tree); - AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); - AnnotationMirror newAnno = - qualHierarchy.greatestLowerBoundShallow( - oldAnno, typeMirror, createAccumulatorAnnotation(methodName), typeMirror); - type.replaceAnnotation(newAnno); - } - - // Also do the standard accumulation analysis behavior: copy any accumulation - // annotations from the receiver to the return type. - return super.visitMethodInvocation(tree, type); + /** + * Get the called methods specified by the given {@link CalledMethods} annotation. + * + * @param calledMethodsAnnotation the annotation + * @return the called methods + */ + protected List getCalledMethods(AnnotationMirror calledMethodsAnnotation) { + return AnnotationUtils.getElementValueArray( + calledMethodsAnnotation, + calledMethodsValueElement, + String.class, + Collections.emptyList()); } @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { - builderFrameworkSupport.handleConstructor(tree, type); - } - return super.visitNewClass(tree, type); + protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( + String expression, + AnnotationMirror qualifier, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + if (preOrPost == BeforeOrAfter.AFTER && isAccumulatorAnnotation(qualifier)) { + List calledMethods = getCalledMethods(qualifier); + if (!calledMethods.isEmpty()) { + return ensuresCMAnno(expression, calledMethods); + } + } + return super.createRequiresOrEnsuresQualifier( + expression, qualifier, declaredType, preOrPost, preconds); } - } - /** - * Adds @CalledMethod annotations for build() methods of AutoValue and Lombok Builders to ensure - * required properties have been set. - */ - private class CalledMethodsTypeAnnotator extends TypeAnnotator { + /** + * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expression. + * + * @param expression the expression to put in the value field of the EnsuresCalledMethods + * annotation + * @param calledMethods the methods that were definitely called on the expression + * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression + */ + private AnnotationMirror ensuresCMAnno(String expression, List calledMethods) { + return ensuresCMAnno(new String[] {expression}, calledMethods); + } /** - * Constructor matching super. + * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expressions. * - * @param atypeFactory the type factory + * @param expressions the expressions to put in the value field of the EnsuresCalledMethods + * annotation + * @param calledMethods the methods that were definitely called on the expression + * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression */ - public CalledMethodsTypeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + private AnnotationMirror ensuresCMAnno(String[] expressions, List calledMethods) { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, EnsuresCalledMethods.class); + builder.setValue("value", expressions); + builder.setValue("methods", calledMethods.toArray(new String[0])); + AnnotationMirror am = builder.build(); + return am; } + /** + * Returns true if the checker should ignore exceptional control flow due to the given exception + * type. + * + * @param exceptionType exception type + * @return {@code true} if {@code exceptionType} is a member of {@link + * CalledMethodsAnalysis#ignoredExceptionTypes}, {@code false} otherwise + */ @Override - public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { - ExecutableElement element = t.getElement(); + public boolean isIgnoredExceptionType(TypeMirror exceptionType) { + if (exceptionType.getKind() == TypeKind.DECLARED) { + return CalledMethodsAnalysis.ignoredExceptionTypes.contains( + TypesUtils.getQualifiedName((DeclaredType) exceptionType)); + } + return false; + } - TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + /** + * Get the exceptional postconditions for the given method from the {@link + * EnsuresCalledMethodsOnException} annotations on it. + * + * @param methodOrConstructor the method to examine + * @return the exceptional postconditions on the given method; the return value is + * newly-allocated and can be freely modified by callers + */ + public Set getExceptionalPostconditions( + ExecutableElement methodOrConstructor) { + Set result = new LinkedHashSet<>(); - for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { - if (builderFrameworkSupport.isToBuilderMethod(element)) { - builderFrameworkSupport.handleToBuilderMethod(t); - } - } - - Element nextEnclosingElement = enclosingElement.getEnclosingElement(); - if (nextEnclosingElement.getKind().isClass()) { - for (BuilderFrameworkSupport builderFrameworkSupport : builderFrameworkSupports) { - if (builderFrameworkSupport.isBuilderBuildMethod(element)) { - builderFrameworkSupport.handleBuilderBuildMethod(t); - } - } - } + parseEnsuresCalledMethodOnExceptionListAnnotation( + getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.List.class), + result); - return super.visitExecutable(t, p); - } - } - - @Override - protected CalledMethodsAnalysis createFlowAnalysis() { - return new CalledMethodsAnalysis(checker, this); - } - - /** - * Returns the annotation type mirror for the type of {@code expressionTree} with default - * annotations applied. As types relevant to Called Methods checking are rarely used inside - * generics, this is typically the best choice for type inference. - */ - @Override - public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { - TypeMirror type = TreeUtils.typeOf(expressionTree); - if (type.getKind() != TypeKind.VOID) { - AnnotatedTypeMirror atm = type(expressionTree); - addDefaultAnnotations(atm); - return atm; - } - return null; - } - - /** - * Fetch the supported builder frameworks that are enabled. - * - * @return a collection of builder frameworks that are enabled in this run of the checker - */ - /*package-private*/ Collection getBuilderFrameworkSupports() { - return builderFrameworkSupports; - } - - /** - * Get the called methods specified by the given {@link CalledMethods} annotation. - * - * @param calledMethodsAnnotation the annotation - * @return the called methods - */ - protected List getCalledMethods(AnnotationMirror calledMethodsAnnotation) { - return AnnotationUtils.getElementValueArray( - calledMethodsAnnotation, calledMethodsValueElement, String.class, Collections.emptyList()); - } - - @Override - protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( - String expression, - AnnotationMirror qualifier, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - if (preOrPost == BeforeOrAfter.AFTER && isAccumulatorAnnotation(qualifier)) { - List calledMethods = getCalledMethods(qualifier); - if (!calledMethods.isEmpty()) { - return ensuresCMAnno(expression, calledMethods); - } - } - return super.createRequiresOrEnsuresQualifier( - expression, qualifier, declaredType, preOrPost, preconds); - } - - /** - * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expression. - * - * @param expression the expression to put in the value field of the EnsuresCalledMethods - * annotation - * @param calledMethods the methods that were definitely called on the expression - * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression - */ - private AnnotationMirror ensuresCMAnno(String expression, List calledMethods) { - return ensuresCMAnno(new String[] {expression}, calledMethods); - } - - /** - * Returns a {@code @EnsuresCalledMethods("...")} annotation for the given expressions. - * - * @param expressions the expressions to put in the value field of the EnsuresCalledMethods - * annotation - * @param calledMethods the methods that were definitely called on the expression - * @return a {@code @EnsuresCalledMethods("...")} annotation for the given expression - */ - private AnnotationMirror ensuresCMAnno(String[] expressions, List calledMethods) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresCalledMethods.class); - builder.setValue("value", expressions); - builder.setValue("methods", calledMethods.toArray(new String[0])); - AnnotationMirror am = builder.build(); - return am; - } - - /** - * Returns true if the checker should ignore exceptional control flow due to the given exception - * type. - * - * @param exceptionType exception type - * @return {@code true} if {@code exceptionType} is a member of {@link - * CalledMethodsAnalysis#ignoredExceptionTypes}, {@code false} otherwise - */ - @Override - public boolean isIgnoredExceptionType(TypeMirror exceptionType) { - if (exceptionType.getKind() == TypeKind.DECLARED) { - return CalledMethodsAnalysis.ignoredExceptionTypes.contains( - TypesUtils.getQualifiedName((DeclaredType) exceptionType)); - } - return false; - } - - /** - * Get the exceptional postconditions for the given method from the {@link - * EnsuresCalledMethodsOnException} annotations on it. - * - * @param methodOrConstructor the method to examine - * @return the exceptional postconditions on the given method; the return value is newly-allocated - * and can be freely modified by callers - */ - public Set getExceptionalPostconditions( - ExecutableElement methodOrConstructor) { - Set result = new LinkedHashSet<>(); - - parseEnsuresCalledMethodOnExceptionListAnnotation( - getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.List.class), result); - - parseEnsuresCalledMethodOnExceptionAnnotation( - getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.class), result); - - return result; - } - - /** - * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link - * EnsuresCalledMethodsOnException.List} annotation and stores the results in out. - * - * @param annotation the annotation - * @param out the output collection - */ - private void parseEnsuresCalledMethodOnExceptionListAnnotation( - @Nullable AnnotationMirror annotation, Set out) { - if (annotation == null) { - return; + parseEnsuresCalledMethodOnExceptionAnnotation( + getDeclAnnotation(methodOrConstructor, EnsuresCalledMethodsOnException.class), + result); + + return result; } - List annotations = - AnnotationUtils.getElementValueArray( - annotation, - ensuresCalledMethodsOnExceptionListValueElement, - AnnotationMirror.class, - Collections.emptyList()); + /** + * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link + * EnsuresCalledMethodsOnException.List} annotation and stores the results in out. + * + * @param annotation the annotation + * @param out the output collection + */ + private void parseEnsuresCalledMethodOnExceptionListAnnotation( + @Nullable AnnotationMirror annotation, + Set out) { + if (annotation == null) { + return; + } + + List annotations = + AnnotationUtils.getElementValueArray( + annotation, + ensuresCalledMethodsOnExceptionListValueElement, + AnnotationMirror.class, + Collections.emptyList()); - for (AnnotationMirror a : annotations) { - parseEnsuresCalledMethodOnExceptionAnnotation(a, out); - } - } - - /** - * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link - * EnsuresCalledMethodsOnException} annotation and stores the results in out. - * - * @param annotation the annotation - * @param out the output collection - */ - private void parseEnsuresCalledMethodOnExceptionAnnotation( - @Nullable AnnotationMirror annotation, Set out) { - if (annotation == null) { - return; + for (AnnotationMirror a : annotations) { + parseEnsuresCalledMethodOnExceptionAnnotation(a, out); + } } - List expressions = - AnnotationUtils.getElementValueArray( - annotation, - ensuresCalledMethodsOnExceptionValueElement, - String.class, - Collections.emptyList()); - List methods = - AnnotationUtils.getElementValueArray( - annotation, - ensuresCalledMethodsOnExceptionMethodsElement, - String.class, - Collections.emptyList()); - - for (String expr : expressions) { - for (String method : methods) { - out.add(new EnsuresCalledMethodOnExceptionContract(expr, method)); - } + /** + * Helper for {@link #getExceptionalPostconditions(ExecutableElement)} that parses a {@link + * EnsuresCalledMethodsOnException} annotation and stores the results in out. + * + * @param annotation the annotation + * @param out the output collection + */ + private void parseEnsuresCalledMethodOnExceptionAnnotation( + @Nullable AnnotationMirror annotation, + Set out) { + if (annotation == null) { + return; + } + + List expressions = + AnnotationUtils.getElementValueArray( + annotation, + ensuresCalledMethodsOnExceptionValueElement, + String.class, + Collections.emptyList()); + List methods = + AnnotationUtils.getElementValueArray( + annotation, + ensuresCalledMethodsOnExceptionMethodsElement, + String.class, + Collections.emptyList()); + + for (String expr : expressions) { + for (String method : methods) { + out.add(new EnsuresCalledMethodOnExceptionContract(expr, method)); + } + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java index e70175c5b81..a91368f6f12 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsChecker.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.calledmethods; -import java.util.Set; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.common.accumulation.AccumulationChecker; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -10,111 +9,114 @@ import org.checkerframework.framework.source.SupportedOptions; import org.checkerframework.framework.source.SuppressWarningsPrefix; +import java.util.Set; + /** * The Called Methods Checker tracks the methods that have definitely been called on an object. One * common use case for the Called Methods Checker is to specify safe combinations of options to * builder or builder-like interfaces, preventing objects from being instantiated incompletely. */ @SuppressWarningsPrefix({ - // Preferred checkername. - "calledmethods", - // Deprecated checkernames, supported for backward compatibility. - "builder", - "object.construction", - "objectconstruction" + // Preferred checkername. + "calledmethods", + // Deprecated checkernames, supported for backward compatibility. + "builder", + "object.construction", + "objectconstruction" }) @SupportedOptions({ - CalledMethodsChecker.USE_VALUE_CHECKER, - CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS, - CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, - CalledMethodsChecker.DISABLE_RETURNS_RECEIVER + CalledMethodsChecker.USE_VALUE_CHECKER, + CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS, + CalledMethodsChecker.DISABLE_BUILDER_FRAMEWORK_SUPPORTS, + CalledMethodsChecker.DISABLE_RETURNS_RECEIVER }) @StubFiles({"DescribeImages.astub", "GenerateDataKey.astub"}) public class CalledMethodsChecker extends AccumulationChecker { - /** - * If this option is supplied, count the number of analyzed calls to build() in supported builder - * frameworks and print it when analysis is complete. Useful for collecting metrics. - */ - public static final String COUNT_FRAMEWORK_BUILD_CALLS = "countFrameworkBuildCalls"; + /** + * If this option is supplied, count the number of analyzed calls to build() in supported + * builder frameworks and print it when analysis is complete. Useful for collecting metrics. + */ + public static final String COUNT_FRAMEWORK_BUILD_CALLS = "countFrameworkBuildCalls"; - /** - * This option disables the support for (and therefore the automated checking of) code that uses - * the given builder frameworks. Useful when a user **only** wants to enforce specifications on - * custom builder objects (such as the AWS SDK examples). - */ - public static final String DISABLE_BUILDER_FRAMEWORK_SUPPORTS = "disableBuilderFrameworkSupports"; + /** + * This option disables the support for (and therefore the automated checking of) code that uses + * the given builder frameworks. Useful when a user **only** wants to enforce specifications on + * custom builder objects (such as the AWS SDK examples). + */ + public static final String DISABLE_BUILDER_FRAMEWORK_SUPPORTS = + "disableBuilderFrameworkSupports"; - /** - * If this option is supplied, use the Value Checker to reduce false positives when analyzing - * calls to the AWS SDK. - */ - public static final String USE_VALUE_CHECKER = "useValueChecker"; + /** + * If this option is supplied, use the Value Checker to reduce false positives when analyzing + * calls to the AWS SDK. + */ + public static final String USE_VALUE_CHECKER = "useValueChecker"; - /** - * Some use cases for the Called Methods Checker do not involve checking fluent APIs, and in those - * cases disabling the Returns Receiver Checker using this flag will make the Called Methods - * Checker run much faster. - */ - public static final String DISABLE_RETURNS_RECEIVER = "disableReturnsReceiver"; + /** + * Some use cases for the Called Methods Checker do not involve checking fluent APIs, and in + * those cases disabling the Returns Receiver Checker using this flag will make the Called + * Methods Checker run much faster. + */ + public static final String DISABLE_RETURNS_RECEIVER = "disableReturnsReceiver"; - /** - * The number of calls to build frameworks supported by this invocation. Incremented only if the - * {@link #COUNT_FRAMEWORK_BUILD_CALLS} command-line option was supplied. - */ - int numBuildCalls = 0; + /** + * The number of calls to build frameworks supported by this invocation. Incremented only if the + * {@link #COUNT_FRAMEWORK_BUILD_CALLS} command-line option was supplied. + */ + int numBuildCalls = 0; - /** Never access this boolean directly. Call {@link #isReturnsReceiverDisabled()} instead. */ - private @MonotonicNonNull Boolean returnsReceiverDisabled = null; + /** Never access this boolean directly. Call {@link #isReturnsReceiverDisabled()} instead. */ + private @MonotonicNonNull Boolean returnsReceiverDisabled = null; - /** - * Was the Returns Receiver Checker disabled on the command line? - * - * @return whether the -AdisableReturnsReceiver option was specified on the command line - */ - private boolean isReturnsReceiverDisabled() { - if (returnsReceiverDisabled == null) { - returnsReceiverDisabled = hasOptionNoSubcheckers(DISABLE_RETURNS_RECEIVER); + /** + * Was the Returns Receiver Checker disabled on the command line? + * + * @return whether the -AdisableReturnsReceiver option was specified on the command line + */ + private boolean isReturnsReceiverDisabled() { + if (returnsReceiverDisabled == null) { + returnsReceiverDisabled = hasOptionNoSubcheckers(DISABLE_RETURNS_RECEIVER); + } + return returnsReceiverDisabled; } - return returnsReceiverDisabled; - } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - if (!isReturnsReceiverDisabled()) { - checkers.add(ReturnsReceiverChecker.class); - } - // BaseTypeChecker#hasOption calls this method (so that all subcheckers' options are - // considered), so the processingEnvironment must be checked for options directly. - if (this.processingEnv.getOptions().containsKey(USE_VALUE_CHECKER) - || this.processingEnv - .getOptions() - .containsKey(this.getClass().getSimpleName() + "_" + USE_VALUE_CHECKER)) { - checkers.add(ValueChecker.class); + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + if (!isReturnsReceiverDisabled()) { + checkers.add(ReturnsReceiverChecker.class); + } + // BaseTypeChecker#hasOption calls this method (so that all subcheckers' options are + // considered), so the processingEnvironment must be checked for options directly. + if (this.processingEnv.getOptions().containsKey(USE_VALUE_CHECKER) + || this.processingEnv + .getOptions() + .containsKey(this.getClass().getSimpleName() + "_" + USE_VALUE_CHECKER)) { + checkers.add(ValueChecker.class); + } + return checkers; } - return checkers; - } - /** - * Check whether the given alias analysis is enabled by this particular accumulation checker. - * - * @param aliasAnalysis the analysis to check - * @return true iff the analysis is enabled - */ - @Override - public boolean isEnabled(AliasAnalysis aliasAnalysis) { - if (aliasAnalysis == AliasAnalysis.RETURNS_RECEIVER) { - return !isReturnsReceiverDisabled(); + /** + * Check whether the given alias analysis is enabled by this particular accumulation checker. + * + * @param aliasAnalysis the analysis to check + * @return true iff the analysis is enabled + */ + @Override + public boolean isEnabled(AliasAnalysis aliasAnalysis) { + if (aliasAnalysis == AliasAnalysis.RETURNS_RECEIVER) { + return !isReturnsReceiverDisabled(); + } + return super.isEnabled(aliasAnalysis); } - return super.isEnabled(aliasAnalysis); - } - @Override - public void typeProcessingOver() { - if (getBooleanOption(COUNT_FRAMEWORK_BUILD_CALLS)) { - System.out.printf("Found %d build() method calls.%n", numBuildCalls); + @Override + public void typeProcessingOver() { + if (getBooleanOption(COUNT_FRAMEWORK_BUILD_CALLS)) { + System.out.printf("Found %d build() method calls.%n", numBuildCalls); + } + super.typeProcessingOver(); } - super.typeProcessingOver(); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java index 1f60219fd64..0578327890f 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java @@ -1,16 +1,5 @@ package org.checkerframework.checker.calledmethods; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.accumulation.AccumulationStore; @@ -34,317 +23,342 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + /** A transfer function that accumulates the names of methods called. */ public class CalledMethodsTransfer extends AccumulationTransfer { - /** - * {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} requires a TransferInput, - * but the actual exceptional stores need to be modified in {@link #accumulate(Node, - * TransferResult, String...)}, which only has access to a TransferResult. So this field is set to - * non-null in {@link #visitMethodInvocation(MethodInvocationNode, TransferInput)} via a call to - * {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} (which reads the CFStores - * from the TransferInput) before the call to accumulate(); accumulate() can then use this field - * to read the CFStores; and then finally this field is then reset to null afterwards to prevent - * it from being used somewhere it shouldn't be. - */ - private @Nullable Map exceptionalStores; + /** + * {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} requires a TransferInput, + * but the actual exceptional stores need to be modified in {@link #accumulate(Node, + * TransferResult, String...)}, which only has access to a TransferResult. So this field is set + * to non-null in {@link #visitMethodInvocation(MethodInvocationNode, TransferInput)} via a call + * to {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} (which reads the + * CFStores from the TransferInput) before the call to accumulate(); accumulate() can then use + * this field to read the CFStores; and then finally this field is then reset to null afterwards + * to prevent it from being used somewhere it shouldn't be. + */ + private @Nullable Map exceptionalStores; - /** - * The element for the CalledMethods annotation's value element. Stored in a field in this class - * to prevent the need to cast to CalledMethods ATF every time it's used. - */ - private final ExecutableElement calledMethodsValueElement; + /** + * The element for the CalledMethods annotation's value element. Stored in a field in this class + * to prevent the need to cast to CalledMethods ATF every time it's used. + */ + private final ExecutableElement calledMethodsValueElement; - /** The type mirror for {@link Exception}. */ - protected final TypeMirror javaLangExceptionType; + /** The type mirror for {@link Exception}. */ + protected final TypeMirror javaLangExceptionType; - /* NO-AFU - * True if -AenableWpiForRlc was passed on the command line. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - private final boolean enableWpiForRlc; - */ + /* NO-AFU + * True if -AenableWpiForRlc was passed on the command line. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + private final boolean enableWpiForRlc; + */ - /** - * Create a new CalledMethodsTransfer. - * - * @param analysis the analysis - */ - public CalledMethodsTransfer(CalledMethodsAnalysis analysis) { - super(analysis); - calledMethodsValueElement = - ((CalledMethodsAnnotatedTypeFactory) atypeFactory).calledMethodsValueElement; - // enableWpiForRlc = - // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + /** + * Create a new CalledMethodsTransfer. + * + * @param analysis the analysis + */ + public CalledMethodsTransfer(CalledMethodsAnalysis analysis) { + super(analysis); + calledMethodsValueElement = + ((CalledMethodsAnnotatedTypeFactory) atypeFactory).calledMethodsValueElement; + // enableWpiForRlc = + // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); - ProcessingEnvironment env = atypeFactory.getProcessingEnv(); - javaLangExceptionType = - env.getTypeUtils().getDeclaredType(ElementUtils.getTypeElement(env, Exception.class)); - } + ProcessingEnvironment env = atypeFactory.getProcessingEnv(); + javaLangExceptionType = + env.getTypeUtils() + .getDeclaredType(ElementUtils.getTypeElement(env, Exception.class)); + } - /* NO-AFU - * @param tree a tree - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) is not passed - * as a command line argument, otherwise returns the result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree tree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; + /* NO-AFU + * @param tree a tree + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) is not passed + * as a command line argument, otherwise returns the result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree tree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; + } + return super.shouldPerformWholeProgramInference(tree); } - return super.shouldPerformWholeProgramInference(tree); - } - */ + */ - /* NO-AFU - * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @param expressionTree a tree - * @param lhsTree its element - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the - * result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; + /* NO-AFU + * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @param expressionTree a tree + * @param lhsTree its element + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the + * result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; + } + return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); } - return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); - } - */ + */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput input) { - exceptionalStores = makeExceptionalStores(node, input); - TransferResult superResult = - super.visitMethodInvocation(node, input); + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput input) { + exceptionalStores = makeExceptionalStores(node, input); + TransferResult superResult = + super.visitMethodInvocation(node, input); - ExecutableElement method = TreeUtils.elementFromUse(node.getTree()); - handleEnsuresCalledMethodsVarArgs(node, method, superResult); - handleEnsuresCalledMethodsOnException(node, method, exceptionalStores); + ExecutableElement method = TreeUtils.elementFromUse(node.getTree()); + handleEnsuresCalledMethodsVarArgs(node, method, superResult); + handleEnsuresCalledMethodsOnException(node, method, exceptionalStores); - Node receiver = node.getTarget().getReceiver(); - if (receiver != null) { - String methodName = node.getTarget().getMethod().getSimpleName().toString(); - methodName = - ((CalledMethodsAnnotatedTypeFactory) atypeFactory) - .adjustMethodNameUsingValueChecker(methodName, node.getTree()); - accumulate(receiver, superResult, methodName); + Node receiver = node.getTarget().getReceiver(); + if (receiver != null) { + String methodName = node.getTarget().getMethod().getSimpleName().toString(); + methodName = + ((CalledMethodsAnnotatedTypeFactory) atypeFactory) + .adjustMethodNameUsingValueChecker(methodName, node.getTree()); + accumulate(receiver, superResult, methodName); + } + TransferResult finalResult = + new ConditionalTransferResult<>( + superResult.getResultValue(), + superResult.getThenStore(), + superResult.getElseStore(), + exceptionalStores); + exceptionalStores = null; + return finalResult; } - TransferResult finalResult = - new ConditionalTransferResult<>( - superResult.getResultValue(), - superResult.getThenStore(), - superResult.getElseStore(), - exceptionalStores); - exceptionalStores = null; - return finalResult; - } - @Override - public void accumulate( - Node node, TransferResult result, String... values) { - super.accumulate(node, result, values); - if (exceptionalStores == null) { - return; - } + @Override + public void accumulate( + Node node, + TransferResult result, + String... values) { + super.accumulate(node, result, values); + if (exceptionalStores == null) { + return; + } - List valuesAsList = Arrays.asList(values); - JavaExpression target = JavaExpression.fromNode(node); - if (CFAbstractStore.canInsertJavaExpression(target)) { - AccumulationValue flowValue = result.getRegularStore().getValue(target); - if (flowValue != null) { - // Dataflow has already recorded information about the target. Integrate it into - // the list of values in the new annotation. - AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); - assert flowAnnos.size() <= 1; - for (AnnotationMirror anno : flowAnnos) { - if (atypeFactory.isAccumulatorAnnotation(anno)) { - List oldFlowValues = - AnnotationUtils.getElementValueArray(anno, calledMethodsValueElement, String.class); - // valuesAsList cannot have its length changed -- it is backed by an - // array. getElementValueArray returns a new, modifiable list. - oldFlowValues.addAll(valuesAsList); - valuesAsList = oldFlowValues; - } + List valuesAsList = Arrays.asList(values); + JavaExpression target = JavaExpression.fromNode(node); + if (CFAbstractStore.canInsertJavaExpression(target)) { + AccumulationValue flowValue = result.getRegularStore().getValue(target); + if (flowValue != null) { + // Dataflow has already recorded information about the target. Integrate it into + // the list of values in the new annotation. + AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); + assert flowAnnos.size() <= 1; + for (AnnotationMirror anno : flowAnnos) { + if (atypeFactory.isAccumulatorAnnotation(anno)) { + List oldFlowValues = + AnnotationUtils.getElementValueArray( + anno, calledMethodsValueElement, String.class); + // valuesAsList cannot have its length changed -- it is backed by an + // array. getElementValueArray returns a new, modifiable list. + oldFlowValues.addAll(valuesAsList); + valuesAsList = oldFlowValues; + } + } + } + AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); + exceptionalStores.forEach( + (tm, s) -> + s.replaceValue( + target, + analysis.createSingleAnnotationValue( + newAnno, target.getType()))); } - } - AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); - exceptionalStores.forEach( - (tm, s) -> - s.replaceValue( - target, analysis.createSingleAnnotationValue(newAnno, target.getType()))); } - } - /** - * Create a set of stores for the exceptional paths out of the block containing {@code node}. This - * allows propagation, along those paths, of the fact that the method being invoked in {@code - * node} was definitely called. - * - * @param node a method invocation - * @param input the transfer input associated with the method invocation - * @return a map from types to stores. The keys are the same keys used by {@link - * ExceptionBlock#getExceptionalSuccessors()}. The values are copies of the regular store from - * {@code input}. - */ - private Map makeExceptionalStores( - MethodInvocationNode node, TransferInput input) { - if (!(node.getBlock() instanceof ExceptionBlock)) { - // This can happen in some weird (buggy?) cases: - // see https://github.com/typetools/checker-framework/issues/3585 - return Collections.emptyMap(); + /** + * Create a set of stores for the exceptional paths out of the block containing {@code node}. + * This allows propagation, along those paths, of the fact that the method being invoked in + * {@code node} was definitely called. + * + * @param node a method invocation + * @param input the transfer input associated with the method invocation + * @return a map from types to stores. The keys are the same keys used by {@link + * ExceptionBlock#getExceptionalSuccessors()}. The values are copies of the regular store + * from {@code input}. + */ + private Map makeExceptionalStores( + MethodInvocationNode node, TransferInput input) { + if (!(node.getBlock() instanceof ExceptionBlock)) { + // This can happen in some weird (buggy?) cases: + // see https://github.com/typetools/checker-framework/issues/3585 + return Collections.emptyMap(); + } + ExceptionBlock block = (ExceptionBlock) node.getBlock(); + Map result = new LinkedHashMap<>(); + block.getExceptionalSuccessors() + .forEach((tm, b) -> result.put(tm, input.getRegularStore().copy())); + return result; } - ExceptionBlock block = (ExceptionBlock) node.getBlock(); - Map result = new LinkedHashMap<>(); - block - .getExceptionalSuccessors() - .forEach((tm, b) -> result.put(tm, input.getRegularStore().copy())); - return result; - } - /** - * Update the types of varargs parameters passed to a method with an {@link - * EnsuresCalledMethodsVarArgs} annotation. This method is a no-op if no such annotation is - * present. - * - * @param node the method invocation node - * @param elt the method being invoked - * @param result the current result - */ - private void handleEnsuresCalledMethodsVarArgs( - MethodInvocationNode node, - ExecutableElement elt, - TransferResult result) { - AnnotationMirror annot = atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); - if (annot == null) { - return; - } - List ensuredMethodNames = - AnnotationUtils.getElementValueArray( - annot, - ((CalledMethodsAnnotatedTypeFactory) atypeFactory) - .ensuresCalledMethodsVarArgsValueElement, - String.class); - List parameters = elt.getParameters(); - int varArgsPos = parameters.size() - 1; - Node varArgActual = node.getArguments().get(varArgsPos); - // In the CFG, explicit passing of multiple arguments in the varargs position is represented - // via an ArrayCreationNode. This is the only case we handle for now. - if (varArgActual instanceof ArrayCreationNode) { - ArrayCreationNode arrayCreationNode = (ArrayCreationNode) varArgActual; - // add in the called method to all the vararg arguments - AccumulationStore thenStore = result.getThenStore(); - AccumulationStore elseStore = result.getElseStore(); - for (Node arg : arrayCreationNode.getInitializers()) { - AnnotatedTypeMirror currentType = atypeFactory.getAnnotatedType(arg.getTree()); - AnnotationMirror newType = getUpdatedCalledMethodsType(currentType, ensuredMethodNames); - if (newType == null) { - continue; + /** + * Update the types of varargs parameters passed to a method with an {@link + * EnsuresCalledMethodsVarArgs} annotation. This method is a no-op if no such annotation is + * present. + * + * @param node the method invocation node + * @param elt the method being invoked + * @param result the current result + */ + private void handleEnsuresCalledMethodsVarArgs( + MethodInvocationNode node, + ExecutableElement elt, + TransferResult result) { + AnnotationMirror annot = + atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); + if (annot == null) { + return; } + List ensuredMethodNames = + AnnotationUtils.getElementValueArray( + annot, + ((CalledMethodsAnnotatedTypeFactory) atypeFactory) + .ensuresCalledMethodsVarArgsValueElement, + String.class); + List parameters = elt.getParameters(); + int varArgsPos = parameters.size() - 1; + Node varArgActual = node.getArguments().get(varArgsPos); + // In the CFG, explicit passing of multiple arguments in the varargs position is represented + // via an ArrayCreationNode. This is the only case we handle for now. + if (varArgActual instanceof ArrayCreationNode) { + ArrayCreationNode arrayCreationNode = (ArrayCreationNode) varArgActual; + // add in the called method to all the vararg arguments + AccumulationStore thenStore = result.getThenStore(); + AccumulationStore elseStore = result.getElseStore(); + for (Node arg : arrayCreationNode.getInitializers()) { + AnnotatedTypeMirror currentType = atypeFactory.getAnnotatedType(arg.getTree()); + AnnotationMirror newType = + getUpdatedCalledMethodsType(currentType, ensuredMethodNames); + if (newType == null) { + continue; + } - JavaExpression receiverReceiver = JavaExpression.fromNode(arg); - thenStore.insertValue(receiverReceiver, newType); - elseStore.insertValue(receiverReceiver, newType); - } + JavaExpression receiverReceiver = JavaExpression.fromNode(arg); + thenStore.insertValue(receiverReceiver, newType); + elseStore.insertValue(receiverReceiver, newType); + } + } } - } - /** - * Update the given exceptionalStores for the {@link - * org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} annotations - * written on the given method. - * - * @param node a method invocation - * @param method the method being invoked - * @param exceptionalStores the stores to update - */ - private void handleEnsuresCalledMethodsOnException( - MethodInvocationNode node, - ExecutableElement method, - Map exceptionalStores) { - Types types = atypeFactory.getProcessingEnv().getTypeUtils(); - for (EnsuresCalledMethodOnExceptionContract postcond : - ((CalledMethodsAnnotatedTypeFactory) atypeFactory).getExceptionalPostconditions(method)) { - JavaExpression e; - try { - e = - StringToJavaExpression.atMethodInvocation( - postcond.getExpression(), node.getTree(), atypeFactory.getChecker()); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - // This parse error will be reported later. For now, we'll skip this malformed - // postcondition and move on to the others. - continue; - } + /** + * Update the given exceptionalStores for the {@link + * org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} annotations + * written on the given method. + * + * @param node a method invocation + * @param method the method being invoked + * @param exceptionalStores the stores to update + */ + private void handleEnsuresCalledMethodsOnException( + MethodInvocationNode node, + ExecutableElement method, + Map exceptionalStores) { + Types types = atypeFactory.getProcessingEnv().getTypeUtils(); + for (EnsuresCalledMethodOnExceptionContract postcond : + ((CalledMethodsAnnotatedTypeFactory) atypeFactory) + .getExceptionalPostconditions(method)) { + JavaExpression e; + try { + e = + StringToJavaExpression.atMethodInvocation( + postcond.getExpression(), + node.getTree(), + atypeFactory.getChecker()); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + // This parse error will be reported later. For now, we'll skip this malformed + // postcondition and move on to the others. + continue; + } - // NOTE: this code is a little inefficient; it creates a single-method annotation and - // calls `insertOrRefine` in a loop. Even worse, this code appears within a loop. - // For now we aren't too worried about it, since the number of - // EnsuresCalledMethodsOnException annotations should be small. - AnnotationMirror calledMethod = - atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); - for (Map.Entry successor : exceptionalStores.entrySet()) { - TypeMirror caughtException = successor.getKey(); - if (types.isSubtype(caughtException, javaLangExceptionType)) { - AccumulationStore resultStore = successor.getValue(); - resultStore.insertOrRefine(e, calledMethod); + // NOTE: this code is a little inefficient; it creates a single-method annotation and + // calls `insertOrRefine` in a loop. Even worse, this code appears within a loop. + // For now we aren't too worried about it, since the number of + // EnsuresCalledMethodsOnException annotations should be small. + AnnotationMirror calledMethod = + atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); + for (Map.Entry successor : + exceptionalStores.entrySet()) { + TypeMirror caughtException = successor.getKey(); + if (types.isSubtype(caughtException, javaLangExceptionType)) { + AccumulationStore resultStore = successor.getValue(); + resultStore.insertOrRefine(e, calledMethod); + } + } } - } } - } - /** - * Extract the current called-methods type from {@code currentType}, and then add each element of - * {@code methodNames} to it, and return the result. This method is similar to GLB, but should be - * used when the new methods come from a source other than an {@code CalledMethods} annotation. - * - * @param currentType the current type in the called-methods hierarchy - * @param methodNames the names of the new methods to add to the type - * @return the new annotation to be added to the type, or null if the current type cannot be - * converted to an accumulator annotation - */ - private @Nullable AnnotationMirror getUpdatedCalledMethodsType( - AnnotatedTypeMirror currentType, List methodNames) { - AnnotationMirror type; - if (currentType == null || !currentType.hasAnnotationInHierarchy(atypeFactory.top)) { - type = atypeFactory.top; - } else { - type = currentType.getAnnotationInHierarchy(atypeFactory.top); - } + /** + * Extract the current called-methods type from {@code currentType}, and then add each element + * of {@code methodNames} to it, and return the result. This method is similar to GLB, but + * should be used when the new methods come from a source other than an {@code CalledMethods} + * annotation. + * + * @param currentType the current type in the called-methods hierarchy + * @param methodNames the names of the new methods to add to the type + * @return the new annotation to be added to the type, or null if the current type cannot be + * converted to an accumulator annotation + */ + private @Nullable AnnotationMirror getUpdatedCalledMethodsType( + AnnotatedTypeMirror currentType, List methodNames) { + AnnotationMirror type; + if (currentType == null || !currentType.hasAnnotationInHierarchy(atypeFactory.top)) { + type = atypeFactory.top; + } else { + type = currentType.getAnnotationInHierarchy(atypeFactory.top); + } - // Don't attempt to strengthen @CalledMethodsPredicate annotations, because that would - // require reasoning about the predicate itself. Instead, start over from top. - if (AnnotationUtils.areSameByName( - type, "org.checkerframework.checker.calledmethods.qual.CalledMethodsPredicate")) { - type = atypeFactory.top; - } + // Don't attempt to strengthen @CalledMethodsPredicate annotations, because that would + // require reasoning about the predicate itself. Instead, start over from top. + if (AnnotationUtils.areSameByName( + type, "org.checkerframework.checker.calledmethods.qual.CalledMethodsPredicate")) { + type = atypeFactory.top; + } - if (AnnotationUtils.areSame(type, atypeFactory.bottom)) { - return null; - } + if (AnnotationUtils.areSame(type, atypeFactory.bottom)) { + return null; + } - List currentMethods = - AnnotationUtils.getElementValueArray(type, calledMethodsValueElement, String.class); - List newList = CollectionsPlume.concatenate(currentMethods, methodNames); + List currentMethods = + AnnotationUtils.getElementValueArray(type, calledMethodsValueElement, String.class); + List newList = CollectionsPlume.concatenate(currentMethods, methodNames); - return atypeFactory.createAccumulatorAnnotation(newList); - } + return atypeFactory.createAccumulatorAnnotation(newList); + } - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; - } - */ + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; + } + */ } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java index 5efb711bc64..2a7b83ac74e 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsVisitor.java @@ -3,14 +3,7 @@ import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.tools.Diagnostic; + import org.checkerframework.checker.calledmethods.builder.BuilderFrameworkSupport; import org.checkerframework.checker.calledmethods.qual.CalledMethods; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs; @@ -27,131 +20,148 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.tools.Diagnostic; + /** * This visitor implements the custom error message "finalizer.invocation.invalid". It also supports * counting the number of framework build calls. */ public class CalledMethodsVisitor extends AccumulationVisitor { - /** - * Creates a new CalledMethodsVisitor. - * - * @param checker the type-checker associated with this visitor - */ - public CalledMethodsVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * Issue an error at every EnsuresCalledMethodsVarArgs annotation, because using it is unsound. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - if (AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs")) { - // We can't verify these yet. Emit an error (which will have to be suppressed) for now. - checker.report(tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.unverified")); + /** + * Creates a new CalledMethodsVisitor. + * + * @param checker the type-checker associated with this visitor + */ + public CalledMethodsVisitor(BaseTypeChecker checker) { + super(checker); } - return super.visitAnnotation(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); - AnnotationMirror ecmva = atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); - if (ecmva != null) { - if (!elt.isVarArgs()) { - checker.report(tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.invalid")); - } - } - for (EnsuresCalledMethodOnExceptionContract postcond : - ((CalledMethodsAnnotatedTypeFactory) atypeFactory).getExceptionalPostconditions(elt)) { - checkExceptionalPostcondition(postcond, tree); - } - return super.visitMethod(tree, p); - } - - /** - * Check if the given postcondition is really ensured by the body of the given method. - * - * @param postcond the postcondition to check - * @param tree the method - */ - protected void checkExceptionalPostcondition( - EnsuresCalledMethodOnExceptionContract postcond, MethodTree tree) { - CFAbstractStore exitStore = atypeFactory.getExceptionalExitStore(tree); - if (exitStore == null) { - // If there is no exceptional exitStore, then the method cannot throw exceptions and - // there is no need to check anything. - return; + + /** + * Issue an error at every EnsuresCalledMethodsVarArgs annotation, because using it is unsound. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + if (AnnotationUtils.areSameByName( + anno, + "org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsVarArgs")) { + // We can't verify these yet. Emit an error (which will have to be suppressed) for now. + checker.report( + tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.unverified")); + } + return super.visitAnnotation(tree, p); } - JavaExpression e; - try { - e = StringToJavaExpression.atMethodBody(postcond.getExpression(), tree, checker); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(tree, ex.getDiagMessage()); - return; + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); + AnnotationMirror ecmva = + atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethodsVarArgs.class); + if (ecmva != null) { + if (!elt.isVarArgs()) { + checker.report( + tree, new DiagMessage(Diagnostic.Kind.ERROR, "ensuresvarargs.invalid")); + } + } + for (EnsuresCalledMethodOnExceptionContract postcond : + ((CalledMethodsAnnotatedTypeFactory) atypeFactory) + .getExceptionalPostconditions(elt)) { + checkExceptionalPostcondition(postcond, tree); + } + return super.visitMethod(tree, p); } - AnnotationMirror requiredAnno = atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); + /** + * Check if the given postcondition is really ensured by the body of the given method. + * + * @param postcond the postcondition to check + * @param tree the method + */ + protected void checkExceptionalPostcondition( + EnsuresCalledMethodOnExceptionContract postcond, MethodTree tree) { + CFAbstractStore exitStore = atypeFactory.getExceptionalExitStore(tree); + if (exitStore == null) { + // If there is no exceptional exitStore, then the method cannot throw exceptions and + // there is no need to check anything. + return; + } - CFAbstractValue value = exitStore.getValue(e); - AnnotationMirror inferredAnno = null; - if (value != null) { - AnnotationMirrorSet annos = value.getAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, requiredAnno); - } + JavaExpression e; + try { + e = StringToJavaExpression.atMethodBody(postcond.getExpression(), tree, checker); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(tree, ex.getDiagMessage()); + return; + } - if (!checkContract(e, requiredAnno, inferredAnno, exitStore)) { - checker.reportError( - tree, - "contracts.exceptional.postcondition.not.satisfied", - tree.getName(), - contractExpressionAndType(postcond.getExpression(), inferredAnno), - contractExpressionAndType(postcond.getExpression(), requiredAnno)); - } - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (checker.getBooleanOption(CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS)) { - ExecutableElement element = TreeUtils.elementFromUse(tree); - for (BuilderFrameworkSupport builderFrameworkSupport : - ((CalledMethodsAnnotatedTypeFactory) getTypeFactory()).getBuilderFrameworkSupports()) { - if (builderFrameworkSupport.isBuilderBuildMethod(element)) { - ((CalledMethodsChecker) checker).numBuildCalls++; - break; + AnnotationMirror requiredAnno = + atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); + + CFAbstractValue value = exitStore.getValue(e); + AnnotationMirror inferredAnno = null; + if (value != null) { + AnnotationMirrorSet annos = value.getAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, requiredAnno); + } + + if (!checkContract(e, requiredAnno, inferredAnno, exitStore)) { + checker.reportError( + tree, + "contracts.exceptional.postcondition.not.satisfied", + tree.getName(), + contractExpressionAndType(postcond.getExpression(), inferredAnno), + contractExpressionAndType(postcond.getExpression(), requiredAnno)); } - } } - return super.visitMethodInvocation(tree, p); - } - - /** Turns some "method.invocation.invalid" errors into "finalizer.invocation.invalid" errors. */ - @Override - protected void reportMethodInvocabilityError( - MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { - - AnnotationMirror expectedCM = expected.getAnnotation(CalledMethods.class); - if (expectedCM != null) { - AnnotationMirror foundCM = found.getAnnotation(CalledMethods.class); - Set foundMethods = - foundCM == null - ? Collections.emptySet() - : new HashSet<>(atypeFactory.getAccumulatedValues(foundCM)); - List expectedMethods = atypeFactory.getAccumulatedValues(expectedCM); - StringJoiner missingMethods = new StringJoiner(" "); - for (String expectedMethod : expectedMethods) { - if (!foundMethods.contains(expectedMethod)) { - missingMethods.add(expectedMethod + "()"); + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (checker.getBooleanOption(CalledMethodsChecker.COUNT_FRAMEWORK_BUILD_CALLS)) { + ExecutableElement element = TreeUtils.elementFromUse(tree); + for (BuilderFrameworkSupport builderFrameworkSupport : + ((CalledMethodsAnnotatedTypeFactory) getTypeFactory()) + .getBuilderFrameworkSupports()) { + if (builderFrameworkSupport.isBuilderBuildMethod(element)) { + ((CalledMethodsChecker) checker).numBuildCalls++; + break; + } + } } - } + return super.visitMethodInvocation(tree, p); + } + + /** Turns some "method.invocation.invalid" errors into "finalizer.invocation.invalid" errors. */ + @Override + protected void reportMethodInvocabilityError( + MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { - checker.reportError(tree, "finalizer.invocation.invalid", missingMethods.toString()); - } else { - super.reportMethodInvocabilityError(tree, found, expected); + AnnotationMirror expectedCM = expected.getAnnotation(CalledMethods.class); + if (expectedCM != null) { + AnnotationMirror foundCM = found.getAnnotation(CalledMethods.class); + Set foundMethods = + foundCM == null + ? Collections.emptySet() + : new HashSet<>(atypeFactory.getAccumulatedValues(foundCM)); + List expectedMethods = atypeFactory.getAccumulatedValues(expectedCM); + StringJoiner missingMethods = new StringJoiner(" "); + for (String expectedMethod : expectedMethods) { + if (!foundMethods.contains(expectedMethod)) { + missingMethods.add(expectedMethod + "()"); + } + } + + checker.reportError(tree, "finalizer.invocation.invalid", missingMethods.toString()); + } else { + super.reportMethodInvocabilityError(tree, found, expected); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java index bdc6fd10601..ab3605ea631 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/EnsuresCalledMethodOnExceptionContract.java @@ -13,53 +13,54 @@ // TODO: In the future, this class should be a record. public class EnsuresCalledMethodOnExceptionContract { - /** The expression described by this postcondition. */ - private final String expression; + /** The expression described by this postcondition. */ + private final String expression; - /** The method this postcondition promises to call. */ - private final String method; + /** The method this postcondition promises to call. */ + private final String method; - /** - * Create a new EnsuredCalledMethodOnException. Usually this should be constructed - * from a {@link org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} - * appearing in the source code. - * - * @param expression the expression described by this postcondition - * @param method the method this postcondition promises to call - */ - public EnsuresCalledMethodOnExceptionContract(String expression, String method) { - this.expression = expression; - this.method = method; - } + /** + * Create a new EnsuredCalledMethodOnException. Usually this should be constructed + * from a {@link + * org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException} appearing in + * the source code. + * + * @param expression the expression described by this postcondition + * @param method the method this postcondition promises to call + */ + public EnsuresCalledMethodOnExceptionContract(String expression, String method) { + this.expression = expression; + this.method = method; + } - /** - * The expression described by this postcondition. - * - * @return the expression described by this postcondition - */ - public String getExpression() { - return expression; - } + /** + * The expression described by this postcondition. + * + * @return the expression described by this postcondition + */ + public String getExpression() { + return expression; + } - /** - * The method this postcondition promises to call. - * - * @return the method this postcondition promises to call - */ - public String getMethod() { - return method; - } + /** + * The method this postcondition promises to call. + * + * @return the method this postcondition promises to call + */ + public String getMethod() { + return method; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof EnsuresCalledMethodOnExceptionContract)) return false; - EnsuresCalledMethodOnExceptionContract that = (EnsuresCalledMethodOnExceptionContract) o; - return expression.equals(that.expression) && method.equals(that.method); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EnsuresCalledMethodOnExceptionContract)) return false; + EnsuresCalledMethodOnExceptionContract that = (EnsuresCalledMethodOnExceptionContract) o; + return expression.equals(that.expression) && method.equals(that.method); + } - @Override - public int hashCode() { - return Objects.hash(expression, method); - } + @Override + public int hashCode() { + return Objects.hash(expression, method); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java index cf5ebb7f4c0..f7c52f5f30d 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/AutoValueSupport.java @@ -1,6 +1,20 @@ package org.checkerframework.checker.calledmethods.builder; import com.sun.source.tree.NewClassTree; + +import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; +import org.checkerframework.checker.calledmethods.qual.CalledMethods; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.checkerframework.javacutil.UserError; +import org.plumelib.util.ArraysPlume; + import java.beans.Introspector; import java.util.ArrayList; import java.util.Arrays; @@ -10,6 +24,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -19,18 +34,6 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; -import org.checkerframework.checker.calledmethods.qual.CalledMethods; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.checkerframework.javacutil.UserError; -import org.plumelib.util.ArraysPlume; /** * AutoValue support for the Called Methods Checker. This class adds {@code @}{@link CalledMethods} @@ -38,399 +41,411 @@ */ public class AutoValueSupport implements BuilderFrameworkSupport { - /** The type factory. */ - private final CalledMethodsAnnotatedTypeFactory atypeFactory; + /** The type factory. */ + private final CalledMethodsAnnotatedTypeFactory atypeFactory; - /** - * Create a new AutoValueSupport. - * - * @param atypeFactory the typechecker's type factory - */ - public AutoValueSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** + * Create a new AutoValueSupport. + * + * @param atypeFactory the typechecker's type factory + */ + public AutoValueSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - /** - * This method modifies the type of a copy constructor generated by AutoValue to match the type of - * the AutoValue toBuilder method, and has no effect if {@code tree} is a call to any other - * constructor. - * - * @param tree an AST for a constructor call - * @param type type of the call expression - */ - @Override - public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { - ExecutableElement element = TreeUtils.elementFromUse(tree); - TypeMirror superclass = ((TypeElement) element.getEnclosingElement()).getSuperclass(); + /** + * This method modifies the type of a copy constructor generated by AutoValue to match the type + * of the AutoValue toBuilder method, and has no effect if {@code tree} is a call to any other + * constructor. + * + * @param tree an AST for a constructor call + * @param type type of the call expression + */ + @Override + public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { + ExecutableElement element = TreeUtils.elementFromUse(tree); + TypeMirror superclass = ((TypeElement) element.getEnclosingElement()).getSuperclass(); - if (superclass.getKind() != TypeKind.NONE - && ElementUtils.hasAnnotation( - TypesUtils.getTypeElement(superclass), getAutoValuePackageName() + ".AutoValue.Builder") - && element.getParameters().size() > 0) { - handleToBuilderType( - type, - superclass, - (TypeElement) TypesUtils.getTypeElement(superclass).getEnclosingElement()); + if (superclass.getKind() != TypeKind.NONE + && ElementUtils.hasAnnotation( + TypesUtils.getTypeElement(superclass), + getAutoValuePackageName() + ".AutoValue.Builder") + && element.getParameters().size() > 0) { + handleToBuilderType( + type, + superclass, + (TypeElement) TypesUtils.getTypeElement(superclass).getEnclosingElement()); + } } - } - @Override - public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { - TypeElement builderElement = (TypeElement) candidateBuildElement.getEnclosingElement(); - if (ElementUtils.hasAnnotation( - builderElement, getAutoValuePackageName() + ".AutoValue.Builder")) { - Element classContainingBuilderElement = builderElement.getEnclosingElement(); - if (!ElementUtils.hasAnnotation( - classContainingBuilderElement, getAutoValuePackageName() + ".AutoValue")) { - throw new UserError( - "class " - + classContainingBuilderElement.getSimpleName() - + " is missing @AutoValue annotation"); - } - // it is a build method if it returns the type with the @AutoValue annotation - if (TypesUtils.getTypeElement(candidateBuildElement.getReturnType()) - .equals(classContainingBuilderElement)) { - return true; - } + @Override + public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { + TypeElement builderElement = (TypeElement) candidateBuildElement.getEnclosingElement(); + if (ElementUtils.hasAnnotation( + builderElement, getAutoValuePackageName() + ".AutoValue.Builder")) { + Element classContainingBuilderElement = builderElement.getEnclosingElement(); + if (!ElementUtils.hasAnnotation( + classContainingBuilderElement, getAutoValuePackageName() + ".AutoValue")) { + throw new UserError( + "class " + + classContainingBuilderElement.getSimpleName() + + " is missing @AutoValue annotation"); + } + // it is a build method if it returns the type with the @AutoValue annotation + if (TypesUtils.getTypeElement(candidateBuildElement.getReturnType()) + .equals(classContainingBuilderElement)) { + return true; + } + } + return false; } - return false; - } - - @Override - public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { - ExecutableElement element = builderBuildType.getElement(); - TypeElement builderElement = (TypeElement) element.getEnclosingElement(); - TypeElement autoValueClassElement = (TypeElement) builderElement.getEnclosingElement(); - AnnotationMirror newCalledMethodsAnno = - createCalledMethodsForAutoValueClass(builderElement, autoValueClassElement); - // Only add the new @CalledMethods annotation if there is not already a @CalledMethods - // annotation present. - builderBuildType.getReceiverType().addMissingAnnotation(newCalledMethodsAnno); - } + @Override + public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { - @Override - public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { - if (!"toBuilder".equals(candidateToBuilderElement.getSimpleName().toString())) { - return false; + ExecutableElement element = builderBuildType.getElement(); + TypeElement builderElement = (TypeElement) element.getEnclosingElement(); + TypeElement autoValueClassElement = (TypeElement) builderElement.getEnclosingElement(); + AnnotationMirror newCalledMethodsAnno = + createCalledMethodsForAutoValueClass(builderElement, autoValueClassElement); + // Only add the new @CalledMethods annotation if there is not already a @CalledMethods + // annotation present. + builderBuildType.getReceiverType().addMissingAnnotation(newCalledMethodsAnno); } - TypeElement candidateClassContainingToBuilder = - (TypeElement) candidateToBuilderElement.getEnclosingElement(); - boolean isAbstractAV = - isAutoValueGenerated(candidateClassContainingToBuilder) - && candidateToBuilderElement.getModifiers().contains(Modifier.ABSTRACT); - TypeMirror superclassOfClassContainingToBuilder = - candidateClassContainingToBuilder.getSuperclass(); - boolean superIsAV = false; - if (superclassOfClassContainingToBuilder.getKind() != TypeKind.NONE) { - superIsAV = - isAutoValueGenerated(TypesUtils.getTypeElement(superclassOfClassContainingToBuilder)); - } - return superIsAV || isAbstractAV; - } + @Override + public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { + if (!"toBuilder".equals(candidateToBuilderElement.getSimpleName().toString())) { + return false; + } - @Override - public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { - AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); - ExecutableElement toBuilderElement = toBuilderType.getElement(); - TypeElement classContainingToBuilder = (TypeElement) toBuilderElement.getEnclosingElement(); - // Because of the way that the check in #isToBuilderMethod works, if the code reaches this - // point and this condition is false, the other condition MUST be true (otherwise, - // isToBuilderMethod would have returned false). - if (isAutoValueGenerated(classContainingToBuilder) - && toBuilderElement.getModifiers().contains(Modifier.ABSTRACT)) { - handleToBuilderType(returnType, returnType.getUnderlyingType(), classContainingToBuilder); - } else { - TypeElement superElement = - TypesUtils.getTypeElement(classContainingToBuilder.getSuperclass()); - handleToBuilderType(returnType, returnType.getUnderlyingType(), superElement); + TypeElement candidateClassContainingToBuilder = + (TypeElement) candidateToBuilderElement.getEnclosingElement(); + boolean isAbstractAV = + isAutoValueGenerated(candidateClassContainingToBuilder) + && candidateToBuilderElement.getModifiers().contains(Modifier.ABSTRACT); + TypeMirror superclassOfClassContainingToBuilder = + candidateClassContainingToBuilder.getSuperclass(); + boolean superIsAV = false; + if (superclassOfClassContainingToBuilder.getKind() != TypeKind.NONE) { + superIsAV = + isAutoValueGenerated( + TypesUtils.getTypeElement(superclassOfClassContainingToBuilder)); + } + return superIsAV || isAbstractAV; } - } - - /** - * Was the given element generated by AutoValue? - * - * @param element the element to check - * @return true if the element was generated by AutoValue - */ - private boolean isAutoValueGenerated(Element element) { - return ElementUtils.hasAnnotation(element, getAutoValuePackageName() + ".AutoValue"); - } - /** - * Add, to {@code type}, a CalledMethods annotation with all required methods called. The type can - * be the return type of toBuilder or of the corresponding generated "copy" constructor. - * - * @param type type to update - * @param builderType type of abstract @AutoValue.Builder class - * @param classElement an AutoValue class corresponding to {@code type} - */ - private void handleToBuilderType( - AnnotatedTypeMirror type, TypeMirror builderType, TypeElement classElement) { - TypeElement builderElement = TypesUtils.getTypeElement(builderType); - AnnotationMirror calledMethodsAnno = - createCalledMethodsForAutoValueClass(builderElement, classElement); - type.replaceAnnotation(calledMethodsAnno); - } - - /** - * Create an @CalledMethods annotation for the given AutoValue class and builder. The returned - * annotation contains all the required setters. - * - * @param builderElement the element for the Builder class - * @param classElement the element for the AutoValue class (i.e. the class that is built by the - * builder) - * @return an @CalledMethods annotation representing that all the required setters have been - * called - */ - private AnnotationMirror createCalledMethodsForAutoValueClass( - TypeElement builderElement, TypeElement classElement) { - Set avBuilderSetterNames = getAutoValueBuilderSetterMethodNames(builderElement); - List requiredProperties = - getAutoValueRequiredProperties(classElement, avBuilderSetterNames); - return createCalledMethodsForAutoValueProperties(requiredProperties, avBuilderSetterNames); - } + @Override + public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { + AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); + ExecutableElement toBuilderElement = toBuilderType.getElement(); + TypeElement classContainingToBuilder = (TypeElement) toBuilderElement.getEnclosingElement(); + // Because of the way that the check in #isToBuilderMethod works, if the code reaches this + // point and this condition is false, the other condition MUST be true (otherwise, + // isToBuilderMethod would have returned false). + if (isAutoValueGenerated(classContainingToBuilder) + && toBuilderElement.getModifiers().contains(Modifier.ABSTRACT)) { + handleToBuilderType( + returnType, returnType.getUnderlyingType(), classContainingToBuilder); + } else { + TypeElement superElement = + TypesUtils.getTypeElement(classContainingToBuilder.getSuperclass()); + handleToBuilderType(returnType, returnType.getUnderlyingType(), superElement); + } + } - /** - * Creates a @CalledMethods annotation for the given property names, converting the names to the - * corresponding setter method name in the Builder. - * - * @param propertyNames the property names - * @param avBuilderSetterNames names of all setters in the builder class - * @return a @CalledMethods annotation that indicates all the given properties have been set - */ - private AnnotationMirror createCalledMethodsForAutoValueProperties( - List propertyNames, Set avBuilderSetterNames) { - List calledMethodNames = - propertyNames.stream() - .map(prop -> autoValuePropToBuilderSetterName(prop, avBuilderSetterNames)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - return atypeFactory.createAccumulatorAnnotation(calledMethodNames); - } + /** + * Was the given element generated by AutoValue? + * + * @param element the element to check + * @return true if the element was generated by AutoValue + */ + private boolean isAutoValueGenerated(Element element) { + return ElementUtils.hasAnnotation(element, getAutoValuePackageName() + ".AutoValue"); + } - /** - * Converts the name of a property (i.e., a field) into the name of its setter. - * - * @param prop the property (i.e., field) name - * @param builderSetterNames names of all methods in the builder class - * @return the name of the setter for prop, or null if it cannot be found - */ - private static @Nullable String autoValuePropToBuilderSetterName( - String prop, Set builderSetterNames) { - String[] possiblePropNames; - if (prop.startsWith("get") && prop.length() > 3 && Character.isUpperCase(prop.charAt(3))) { - possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(3))}; - } else if (prop.startsWith("is") - && prop.length() > 2 - && Character.isUpperCase(prop.charAt(2))) { - possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(2))}; - } else { - possiblePropNames = new String[] {prop}; + /** + * Add, to {@code type}, a CalledMethods annotation with all required methods called. The type + * can be the return type of toBuilder or of the corresponding generated "copy" constructor. + * + * @param type type to update + * @param builderType type of abstract @AutoValue.Builder class + * @param classElement an AutoValue class corresponding to {@code type} + */ + private void handleToBuilderType( + AnnotatedTypeMirror type, TypeMirror builderType, TypeElement classElement) { + TypeElement builderElement = TypesUtils.getTypeElement(builderType); + AnnotationMirror calledMethodsAnno = + createCalledMethodsForAutoValueClass(builderElement, classElement); + type.replaceAnnotation(calledMethodsAnno); } - for (String propName : possiblePropNames) { - // The setter may be the property name itself, or prefixed by 'set'. - if (builderSetterNames.contains(propName)) { - return propName; - } - String setterName = "set" + BuilderFrameworkSupportUtils.capitalize(propName); - if (builderSetterNames.contains(setterName)) { - return setterName; - } + /** + * Create an @CalledMethods annotation for the given AutoValue class and builder. The returned + * annotation contains all the required setters. + * + * @param builderElement the element for the Builder class + * @param classElement the element for the AutoValue class (i.e. the class that is built by the + * builder) + * @return an @CalledMethods annotation representing that all the required setters have been + * called + */ + private AnnotationMirror createCalledMethodsForAutoValueClass( + TypeElement builderElement, TypeElement classElement) { + Set avBuilderSetterNames = getAutoValueBuilderSetterMethodNames(builderElement); + List requiredProperties = + getAutoValueRequiredProperties(classElement, avBuilderSetterNames); + return createCalledMethodsForAutoValueProperties(requiredProperties, avBuilderSetterNames); } - // Could not find a corresponding setter. This is likely because an AutoValue Extension is - // in use. See https://github.com/kelloggm/object-construction-checker/issues/110 . - // For now we return null, but once that bug is fixed, this should be changed to an - // assertion failure. - return null; - } + /** + * Creates a @CalledMethods annotation for the given property names, converting the names to the + * corresponding setter method name in the Builder. + * + * @param propertyNames the property names + * @param avBuilderSetterNames names of all setters in the builder class + * @return a @CalledMethods annotation that indicates all the given properties have been set + */ + private AnnotationMirror createCalledMethodsForAutoValueProperties( + List propertyNames, Set avBuilderSetterNames) { + List calledMethodNames = + propertyNames.stream() + .map(prop -> autoValuePropToBuilderSetterName(prop, avBuilderSetterNames)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return atypeFactory.createAccumulatorAnnotation(calledMethodNames); + } - /** - * Computes the required properties of an @AutoValue class. - * - * @param autoValueClassElement the @AutoValue class - * @param avBuilderSetterNames names of all setters in the corresponding AutoValue builder class - * @return a list of required property names - */ - private List getAutoValueRequiredProperties( - TypeElement autoValueClassElement, Set avBuilderSetterNames) { - return getAllAbstractMethods(autoValueClassElement).stream() - .filter(member -> isAutoValueRequiredProperty(member, avBuilderSetterNames)) - .map(e -> e.getSimpleName().toString()) - .collect(Collectors.toList()); - } + /** + * Converts the name of a property (i.e., a field) into the name of its setter. + * + * @param prop the property (i.e., field) name + * @param builderSetterNames names of all methods in the builder class + * @return the name of the setter for prop, or null if it cannot be found + */ + private static @Nullable String autoValuePropToBuilderSetterName( + String prop, Set builderSetterNames) { + String[] possiblePropNames; + if (prop.startsWith("get") && prop.length() > 3 && Character.isUpperCase(prop.charAt(3))) { + possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(3))}; + } else if (prop.startsWith("is") + && prop.length() > 2 + && Character.isUpperCase(prop.charAt(2))) { + possiblePropNames = new String[] {prop, Introspector.decapitalize(prop.substring(2))}; + } else { + possiblePropNames = new String[] {prop}; + } - /** Method names for {@link #isAutoValueRequiredProperty} to ignore. */ - private final Set isAutoValueRequiredPropertyIgnored = - new HashSet<>(Arrays.asList("equals", "hashCode", "toString", "", "toBuilder")); + for (String propName : possiblePropNames) { + // The setter may be the property name itself, or prefixed by 'set'. + if (builderSetterNames.contains(propName)) { + return propName; + } + String setterName = "set" + BuilderFrameworkSupportUtils.capitalize(propName); + if (builderSetterNames.contains(setterName)) { + return setterName; + } + } - /** - * Does member represent a required property of an AutoValue class? - * - * @param member a member of an AutoValue class or superclass - * @param avBuilderSetterNames names of all setters in corresponding AutoValue builder class - * @return true if {@code member} is required - */ - private boolean isAutoValueRequiredProperty( - ExecutableElement member, Set avBuilderSetterNames) { - String name = member.getSimpleName().toString(); - // Ignore java.lang.Object overrides, constructors, and toBuilder methods in AutoValue - // classes. - // Strictly speaking, this code should check return types, etc. to handle strange - // overloads and other corner cases. They seem unlikely enough that we are skipping for now. - if (isAutoValueRequiredPropertyIgnored.contains(name)) { - return false; - } - TypeMirror returnType = member.getReturnType(); - if (returnType.getKind() == TypeKind.VOID) { - return false; - } - // shouldn't have a nullable return - boolean hasNullable = - Stream.concat( - atypeFactory.getElementUtils().getAllAnnotationMirrors(member).stream(), - returnType.getAnnotationMirrors().stream()) - .anyMatch(anm -> AnnotationUtils.annotationName(anm).endsWith(".Nullable")); - if (hasNullable) { - return false; - } - // if return type of foo() is a Guava Immutable type, not required if there is a - // builder method fooBuilder() - if (BuilderFrameworkSupportUtils.isGuavaImmutableType(returnType) - && avBuilderSetterNames.contains(name + "Builder")) { - return false; + // Could not find a corresponding setter. This is likely because an AutoValue Extension is + // in use. See https://github.com/kelloggm/object-construction-checker/issues/110 . + // For now we return null, but once that bug is fixed, this should be changed to an + // assertion failure. + return null; } - // if it's an Optional, the Builder will automatically initialize it - if (isOptional(returnType)) { - return false; + + /** + * Computes the required properties of an @AutoValue class. + * + * @param autoValueClassElement the @AutoValue class + * @param avBuilderSetterNames names of all setters in the corresponding AutoValue builder class + * @return a list of required property names + */ + private List getAutoValueRequiredProperties( + TypeElement autoValueClassElement, Set avBuilderSetterNames) { + return getAllAbstractMethods(autoValueClassElement).stream() + .filter(member -> isAutoValueRequiredProperty(member, avBuilderSetterNames)) + .map(e -> e.getSimpleName().toString()) + .collect(Collectors.toList()); } - // it's required! - return true; - } - /** Classes that AutoValue considers "optional". This list comes from AutoValue's source code. */ - private static final String[] optionalClassNames = - new String[] { - // Use concatenation to avoid ShadowJar relocate - // "com.google.common.base.Optional", - "com.go".toString() + "ogle.common.base.Optional", - "java.util.Optional", - "java.util.OptionalDouble", - "java.util.OptionalInt", - "java.util.OptionalLong" - }; + /** Method names for {@link #isAutoValueRequiredProperty} to ignore. */ + private final Set isAutoValueRequiredPropertyIgnored = + new HashSet<>(Arrays.asList("equals", "hashCode", "toString", "", "toBuilder")); - /** - * Returns whether AutoValue considers a type to be "optional". Optional types do not need to be - * set before build is called on a builder. Adapted from AutoValue source code. - * - * @param type some type - * @return true if type is an Optional type - */ - private static boolean isOptional(TypeMirror type) { - if (type.getKind() != TypeKind.DECLARED) { - return false; + /** + * Does member represent a required property of an AutoValue class? + * + * @param member a member of an AutoValue class or superclass + * @param avBuilderSetterNames names of all setters in corresponding AutoValue builder class + * @return true if {@code member} is required + */ + private boolean isAutoValueRequiredProperty( + ExecutableElement member, Set avBuilderSetterNames) { + String name = member.getSimpleName().toString(); + // Ignore java.lang.Object overrides, constructors, and toBuilder methods in AutoValue + // classes. + // Strictly speaking, this code should check return types, etc. to handle strange + // overloads and other corner cases. They seem unlikely enough that we are skipping for now. + if (isAutoValueRequiredPropertyIgnored.contains(name)) { + return false; + } + TypeMirror returnType = member.getReturnType(); + if (returnType.getKind() == TypeKind.VOID) { + return false; + } + // shouldn't have a nullable return + boolean hasNullable = + Stream.concat( + atypeFactory + .getElementUtils() + .getAllAnnotationMirrors(member) + .stream(), + returnType.getAnnotationMirrors().stream()) + .anyMatch(anm -> AnnotationUtils.annotationName(anm).endsWith(".Nullable")); + if (hasNullable) { + return false; + } + // if return type of foo() is a Guava Immutable type, not required if there is a + // builder method fooBuilder() + if (BuilderFrameworkSupportUtils.isGuavaImmutableType(returnType) + && avBuilderSetterNames.contains(name + "Builder")) { + return false; + } + // if it's an Optional, the Builder will automatically initialize it + if (isOptional(returnType)) { + return false; + } + // it's required! + return true; } - DeclaredType declaredType = (DeclaredType) type; - TypeElement typeElement = (TypeElement) declaredType.asElement(); - return typeElement.getTypeParameters().size() == declaredType.getTypeArguments().size() - && ArraysPlume.indexOf(optionalClassNames, typeElement.getQualifiedName().toString()) != -1; - } - /** - * Returns names of all setter methods. - * - * @see #isAutoValueBuilderSetter - * @param builderElement the element representing an AutoValue builder - * @return the names of setter methods for the AutoValue builder - */ - private Set getAutoValueBuilderSetterMethodNames(TypeElement builderElement) { - return getAllAbstractMethods(builderElement).stream() - .filter(e -> isAutoValueBuilderSetter(e, builderElement)) - .map(e -> e.getSimpleName().toString()) - .collect(Collectors.toSet()); - } + /** + * Classes that AutoValue considers "optional". This list comes from AutoValue's source code. + */ + private static final String[] optionalClassNames = + new String[] { + // Use concatenation to avoid ShadowJar relocate + // "com.google.common.base.Optional", + "com.go".toString() + "ogle.common.base.Optional", + "java.util.Optional", + "java.util.OptionalDouble", + "java.util.OptionalInt", + "java.util.OptionalLong" + }; - /** - * Return true if the given method is a setter for an AutoValue builder; that is, its return type - * is the builder itself or a Guava Immutable type. - * - * @param method a method of a builder or one of its supertypes - * @param builderElement element for the AutoValue builder - * @return true if {@code method} is a setter for the builder - */ - private boolean isAutoValueBuilderSetter(ExecutableElement method, TypeElement builderElement) { - TypeMirror retType = method.getReturnType(); - if (retType.getKind() == TypeKind.TYPEVAR) { - // instantiate the type variable for the Builder class - retType = - AnnotatedTypes.asMemberOf( - atypeFactory.getChecker().getTypeUtils(), - atypeFactory, - atypeFactory.getAnnotatedType(builderElement), - method) - .getReturnType() - .getUnderlyingType(); + /** + * Returns whether AutoValue considers a type to be "optional". Optional types do not need to be + * set before build is called on a builder. Adapted from AutoValue source code. + * + * @param type some type + * @return true if type is an Optional type + */ + private static boolean isOptional(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + DeclaredType declaredType = (DeclaredType) type; + TypeElement typeElement = (TypeElement) declaredType.asElement(); + return typeElement.getTypeParameters().size() == declaredType.getTypeArguments().size() + && ArraysPlume.indexOf( + optionalClassNames, typeElement.getQualifiedName().toString()) + != -1; } - // Either the return type should be the builder itself, or it should be a Guava immutable - // type. - return BuilderFrameworkSupportUtils.isGuavaImmutableType(retType) - || builderElement.equals(TypesUtils.getTypeElement(retType)); - } - /** - * Get all the abstract methods for a class. This includes inherited abstract methods that are not - * overridden by the class or a superclass. There is no guarantee that this method will work as - * intended on code that implements an interface (which AutoValue classes are not supposed to do: - * https://github.com/google/auto/blob/master/value/userguide/howto.md#inherit). - * - * @param classElement the class - * @return list of all abstract methods - */ - public List getAllAbstractMethods(TypeElement classElement) { - List supertypes = - ElementUtils.getAllSupertypes(classElement, atypeFactory.getProcessingEnv()); - List abstractMethods = new ArrayList<>(); - Set overriddenMethods = new HashSet<>(); - for (Element t : supertypes) { - for (Element member : t.getEnclosedElements()) { - if (member.getKind() != ElementKind.METHOD) { - continue; - } - Set modifiers = member.getModifiers(); - if (modifiers.contains(Modifier.STATIC)) { - continue; + /** + * Returns names of all setter methods. + * + * @see #isAutoValueBuilderSetter + * @param builderElement the element representing an AutoValue builder + * @return the names of setter methods for the AutoValue builder + */ + private Set getAutoValueBuilderSetterMethodNames(TypeElement builderElement) { + return getAllAbstractMethods(builderElement).stream() + .filter(e -> isAutoValueBuilderSetter(e, builderElement)) + .map(e -> e.getSimpleName().toString()) + .collect(Collectors.toSet()); + } + + /** + * Return true if the given method is a setter for an AutoValue builder; that is, its return + * type is the builder itself or a Guava Immutable type. + * + * @param method a method of a builder or one of its supertypes + * @param builderElement element for the AutoValue builder + * @return true if {@code method} is a setter for the builder + */ + private boolean isAutoValueBuilderSetter(ExecutableElement method, TypeElement builderElement) { + TypeMirror retType = method.getReturnType(); + if (retType.getKind() == TypeKind.TYPEVAR) { + // instantiate the type variable for the Builder class + retType = + AnnotatedTypes.asMemberOf( + atypeFactory.getChecker().getTypeUtils(), + atypeFactory, + atypeFactory.getAnnotatedType(builderElement), + method) + .getReturnType() + .getUnderlyingType(); } - if (modifiers.contains(Modifier.ABSTRACT)) { - // Make sure it's not overridden. This only works because ElementUtils#closure - // returns results in a particular order. - if (!overriddenMethods.contains(member)) { - abstractMethods.add((ExecutableElement) member); - } - } else { - // Exclude any methods that this overrides. - overriddenMethods.addAll( - AnnotatedTypes.overriddenMethods( - atypeFactory.getElementUtils(), atypeFactory, (ExecutableElement) member) - .values()); + // Either the return type should be the builder itself, or it should be a Guava immutable + // type. + return BuilderFrameworkSupportUtils.isGuavaImmutableType(retType) + || builderElement.equals(TypesUtils.getTypeElement(retType)); + } + + /** + * Get all the abstract methods for a class. This includes inherited abstract methods that are + * not overridden by the class or a superclass. There is no guarantee that this method will work + * as intended on code that implements an interface (which AutoValue classes are not supposed to + * do: https://github.com/google/auto/blob/master/value/userguide/howto.md#inherit). + * + * @param classElement the class + * @return list of all abstract methods + */ + public List getAllAbstractMethods(TypeElement classElement) { + List supertypes = + ElementUtils.getAllSupertypes(classElement, atypeFactory.getProcessingEnv()); + List abstractMethods = new ArrayList<>(); + Set overriddenMethods = new HashSet<>(); + for (Element t : supertypes) { + for (Element member : t.getEnclosedElements()) { + if (member.getKind() != ElementKind.METHOD) { + continue; + } + Set modifiers = member.getModifiers(); + if (modifiers.contains(Modifier.STATIC)) { + continue; + } + if (modifiers.contains(Modifier.ABSTRACT)) { + // Make sure it's not overridden. This only works because ElementUtils#closure + // returns results in a particular order. + if (!overriddenMethods.contains(member)) { + abstractMethods.add((ExecutableElement) member); + } + } else { + // Exclude any methods that this overrides. + overriddenMethods.addAll( + AnnotatedTypes.overriddenMethods( + atypeFactory.getElementUtils(), + atypeFactory, + (ExecutableElement) member) + .values()); + } + } } - } + return abstractMethods; } - return abstractMethods; - } - /** - * Get the qualified name of the package containing AutoValue annotations. This method constructs - * the String dynamically, to ensure it does not get rewritten due to relocation of the {@code - * "com.google"} package during the build process. - * - * @return {@code "com.google.auto.value"} - */ - private String getAutoValuePackageName() { - String com = "com"; - return com + "." + "google.auto.value"; - } + /** + * Get the qualified name of the package containing AutoValue annotations. This method + * constructs the String dynamically, to ensure it does not get rewritten due to relocation of + * the {@code "com.google"} package during the build process. + * + * @return {@code "com.google.auto.value"} + */ + private String getAutoValuePackageName() { + String com = "com"; + return com + "." + "google.auto.value"; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java index cbbf9951ef0..40aa4a06202 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupport.java @@ -1,10 +1,12 @@ package org.checkerframework.checker.calledmethods.builder; import com.sun.source.tree.NewClassTree; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import javax.lang.model.element.ExecutableElement; + /** * Provides hooks to add CalledMethods annotations to code generated by a builder framework like * Lombok or AutoValue. If you are adding support to the Called Methods Checker for a new builder @@ -17,57 +19,58 @@ */ public interface BuilderFrameworkSupport { - /** - * Returns true if a method is a {@code toBuilder} method on a type generated by the builder - * framework. - * - * @param candidateToBuilderElement a method - * @return {@code true} if {@code candidateToBuilderElement} is a {@code toBuilder} method on a - * type generated by the builder framework - */ - boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement); + /** + * Returns true if a method is a {@code toBuilder} method on a type generated by the builder + * framework. + * + * @param candidateToBuilderElement a method + * @return {@code true} if {@code candidateToBuilderElement} is a {@code toBuilder} method on a + * type generated by the builder framework + */ + boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement); - /** - * Hook for supporting a builder framework's {@code toBuilder} routine. Typically, the returned - * Builder has had all of its required setters invoked. So, implementations of this method should - * add a {@link org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation - * capturing this fact. - * - * @param toBuilderType the type of a method that is the {@code toBuilder} method (as determined - * by {@link #isToBuilderMethod(ExecutableElement)}) for a type that has an associated builder - */ - void handleToBuilderMethod(AnnotatedExecutableType toBuilderType); + /** + * Hook for supporting a builder framework's {@code toBuilder} routine. Typically, the returned + * Builder has had all of its required setters invoked. So, implementations of this method + * should add a {@link org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation + * capturing this fact. + * + * @param toBuilderType the type of a method that is the {@code toBuilder} method (as determined + * by {@link #isToBuilderMethod(ExecutableElement)}) for a type that has an associated + * builder + */ + void handleToBuilderMethod(AnnotatedExecutableType toBuilderType); - /** - * Returns true if a method is a {@code build} method on a {@code Builder} type for the builder - * framework. - * - * @param candidateBuildElement a method - * @return {@code true} if {@code candidateBuildElement} is a {@code build} method on a {@code - * Builder} type for the builder framework - */ - boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement); + /** + * Returns true if a method is a {@code build} method on a {@code Builder} type for the builder + * framework. + * + * @param candidateBuildElement a method + * @return {@code true} if {@code candidateBuildElement} is a {@code build} method on a {@code + * Builder} type for the builder framework + */ + boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement); - /** - * Hook for adding annotations to a build() method (i.e. a finalizer) generated by a builder - * framework. - * - *

          For {@code build} methods on {@code Builder} types, implementations of this method should - * determine the required properties and add a corresponding {@link - * org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation to the type of the - * receiver parameter. - * - * @param builderBuildType the type of a method that is the {@code build} method (as determined by - * {@link #isBuilderBuildMethod(ExecutableElement)}) for a builder - */ - void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType); + /** + * Hook for adding annotations to a build() method (i.e. a finalizer) generated by a builder + * framework. + * + *

          For {@code build} methods on {@code Builder} types, implementations of this method should + * determine the required properties and add a corresponding {@link + * org.checkerframework.checker.calledmethods.qual.CalledMethods} annotation to the type of the + * receiver parameter. + * + * @param builderBuildType the type of a method that is the {@code build} method (as determined + * by {@link #isBuilderBuildMethod(ExecutableElement)}) for a builder + */ + void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType); - /** - * Hook for adding annotations (e.g., {@code @}{@link - * org.checkerframework.checker.calledmethods.qual.CalledMethods}) to a constructor call. - * - * @param tree a constructor call - * @param type type of the call expression - */ - void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type); + /** + * Hook for adding annotations (e.g., {@code @}{@link + * org.checkerframework.checker.calledmethods.qual.CalledMethods}) to a constructor call. + * + * @param tree a constructor call + * @param type type of the call expression + */ + void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type); } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java index 679c63cc5e6..3d98fa883b1 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/BuilderFrameworkSupportUtils.java @@ -4,31 +4,31 @@ /** A utility class of static methods used in supporting builder-generation frameworks. */ public class BuilderFrameworkSupportUtils { - /** This class is non-instantiable. */ - private BuilderFrameworkSupportUtils() { - throw new Error("Do not instantiate"); - } + /** This class is non-instantiable. */ + private BuilderFrameworkSupportUtils() { + throw new Error("Do not instantiate"); + } - /** - * Returns true if the given type is one of the immutable collections defined in - * com.google.common.collect. - * - * @param type a type - * @return true if the type is a Guava immutable collection - */ - public static boolean isGuavaImmutableType(TypeMirror type) { - // Use concatenation to avoid ShadowJar relocate - // "com.google.common.collect.Immutable" - return type.toString().startsWith("com.go".toString() + "ogle.common.collect.Immutable"); - } + /** + * Returns true if the given type is one of the immutable collections defined in + * com.google.common.collect. + * + * @param type a type + * @return true if the type is a Guava immutable collection + */ + public static boolean isGuavaImmutableType(TypeMirror type) { + // Use concatenation to avoid ShadowJar relocate + // "com.google.common.collect.Immutable" + return type.toString().startsWith("com.go".toString() + "ogle.common.collect.Immutable"); + } - /** - * Capitalizes the first letter of the given string. - * - * @param prop a String - * @return the same String, but with the first character capitalized - */ - public static String capitalize(String prop) { - return Character.toUpperCase(prop.charAt(0)) + prop.substring(1); - } + /** + * Capitalizes the first letter of the given string. + * + * @param prop a String + * @return the same String, but with the first character capitalized + */ + public static String capitalize(String prop) { + return Character.toUpperCase(prop.charAt(0)) + prop.substring(1); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java index 753a271ff81..9807af5357f 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/builder/LombokSupport.java @@ -2,6 +2,13 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.VariableTree; + +import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; + import java.beans.Introspector; import java.util.ArrayList; import java.util.Arrays; @@ -9,17 +16,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; -import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.ElementUtils; /** * Lombok support for the Called Methods Checker. This class adds CalledMethods annotations to the @@ -27,180 +30,186 @@ */ public class LombokSupport implements BuilderFrameworkSupport { - /** The type factory. */ - private final CalledMethodsAnnotatedTypeFactory atypeFactory; - - /** - * Create a new LombokSupport. - * - * @param atypeFactory the typechecker's type factory - */ - public LombokSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } - - // The list is copied from lombok.core.handlers.HandlerUtil. The list cannot be used from that - // class directly because Lombok does not provide class files for its own implementation, to - // prevent itself from being accidentally added to clients' compile classpaths. This design - // decision means that it is impossible to depend directly on Lombok internals. - /** The list of annotations that Lombok treats as non-null. */ - public static final List NONNULL_ANNOTATIONS = - Collections.unmodifiableList( - Arrays.asList( - "android.annotation.NonNull", - "android.support.annotation.NonNull", - "com.sun.istack.internal.NotNull", - "edu.umd.cs.findbugs.annotations.NonNull", - "javax.annotation.Nonnull", - // "javax.validation.constraints.NotNull", // The field might contain a - // null value until it is persisted. - "lombok.NonNull", - "org.checkerframework.checker.nullness.qual.NonNull", - "org.eclipse.jdt.annotation.NonNull", - "org.eclipse.jgit.annotations.NonNull", - "org.jetbrains.annotations.NotNull", - "org.jmlspecs.annotation.NonNull", - "org.netbeans.api.annotations.common.NonNull", - "org.springframework.lang.NonNull")); - - /** - * A map from elements that have a lombok.Builder.Default annotation to the simple property name - * that should be treated as defaulted. - * - *

          This cache is kept because the usual method for checking that an element has been defaulted - * (calling declarationFromElement and examining the resulting VariableTree) only works if a - * corresponding Tree is available (for code that is only available as bytecode, no such Tree is - * available and that method returns null). See the code in {@link - * #getLombokRequiredProperties(Element)} that handles fields. - */ - private final Map defaultedElements = new HashMap<>(2); - - @Override - public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { - TypeElement candidateGeneratedBuilderElement = - (TypeElement) candidateBuildElement.getEnclosingElement(); - - if ((ElementUtils.hasAnnotation(candidateGeneratedBuilderElement, "lombok.Generated") - || ElementUtils.hasAnnotation(candidateBuildElement, "lombok.Generated")) - && candidateGeneratedBuilderElement.getSimpleName().toString().endsWith("Builder")) { - return candidateBuildElement.getSimpleName().contentEquals("build"); + /** The type factory. */ + private final CalledMethodsAnnotatedTypeFactory atypeFactory; + + /** + * Create a new LombokSupport. + * + * @param atypeFactory the typechecker's type factory + */ + public LombokSupport(CalledMethodsAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; } - return false; - } - - @Override - public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { - ExecutableElement buildElement = builderBuildType.getElement(); - - TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); - // The class with the @lombok.Builder annotation... - Element annotatedWithBuilderElement = generatedBuilderElement.getEnclosingElement(); - - List requiredProperties = getLombokRequiredProperties(annotatedWithBuilderElement); - AnnotationMirror newCalledMethodsAnno = - atypeFactory.createAccumulatorAnnotation(requiredProperties); - builderBuildType.getReceiverType().addAnnotation(newCalledMethodsAnno); - } - - @Override - public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { - return candidateToBuilderElement.getSimpleName().contentEquals("toBuilder") - && (ElementUtils.hasAnnotation(candidateToBuilderElement, "lombok.Generated") - || ElementUtils.hasAnnotation( - candidateToBuilderElement.getEnclosingElement(), "lombok.Generated")); - } - - @Override - public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { - AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); - ExecutableElement buildElement = toBuilderType.getElement(); - TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); - handleToBuilderType(returnType, generatedBuilderElement); - } - - /** - * Add, to a type, a CalledMethods annotation that states that all required setters have been - * called. The type can be the return type of toBuilder or of the corresponding generated "copy" - * constructor. - * - * @param type type to update - * @param classElement corresponding AutoValue class - */ - private void handleToBuilderType(AnnotatedTypeMirror type, Element classElement) { - List requiredProperties = getLombokRequiredProperties(classElement); - AnnotationMirror calledMethodsAnno = - atypeFactory.createAccumulatorAnnotation(requiredProperties); - type.replaceAnnotation(calledMethodsAnno); - } - - /** - * Computes the required properties of a @lombok.Builder class, i.e., the names of the fields - * with @lombok.NonNull annotations. - * - * @param lombokClassElement the class with the @lombok.Builder annotation - * @return a list of required property names - */ - private List getLombokRequiredProperties(Element lombokClassElement) { - List requiredPropertyNames = new ArrayList<>(); - List defaultedPropertyNames = new ArrayList<>(); - for (Element member : lombokClassElement.getEnclosedElements()) { - if (member.getKind() == ElementKind.FIELD) { - // Lombok never generates non-null fields with initializers in builders, unless the - // field is annotated with @Default or @Singular, which are handled elsewhere. So, - // this code doesn't need to consider whether the field has or does not have - // initializers. - for (AnnotationMirror anm : - atypeFactory.getElementUtils().getAllAnnotationMirrors(member)) { - if (NONNULL_ANNOTATIONS.contains(AnnotationUtils.annotationName(anm))) { - requiredPropertyNames.add(member.getSimpleName().toString()); - } - } - } else if (member.getKind() == ElementKind.METHOD - && ElementUtils.hasAnnotation(member, "lombok.Generated")) { - String methodName = member.getSimpleName().toString(); - // If a field foo has an @Builder.Default annotation, Lombok always generates a - // method called $default$foo. - if (methodName.startsWith("$default$")) { - String propName = methodName.substring(9); // $default$ has 9 characters - defaultedPropertyNames.add(propName); + + // The list is copied from lombok.core.handlers.HandlerUtil. The list cannot be used from that + // class directly because Lombok does not provide class files for its own implementation, to + // prevent itself from being accidentally added to clients' compile classpaths. This design + // decision means that it is impossible to depend directly on Lombok internals. + /** The list of annotations that Lombok treats as non-null. */ + public static final List NONNULL_ANNOTATIONS = + Collections.unmodifiableList( + Arrays.asList( + "android.annotation.NonNull", + "android.support.annotation.NonNull", + "com.sun.istack.internal.NotNull", + "edu.umd.cs.findbugs.annotations.NonNull", + "javax.annotation.Nonnull", + // "javax.validation.constraints.NotNull", // The field might contain a + // null value until it is persisted. + "lombok.NonNull", + "org.checkerframework.checker.nullness.qual.NonNull", + "org.eclipse.jdt.annotation.NonNull", + "org.eclipse.jgit.annotations.NonNull", + "org.jetbrains.annotations.NotNull", + "org.jmlspecs.annotation.NonNull", + "org.netbeans.api.annotations.common.NonNull", + "org.springframework.lang.NonNull")); + + /** + * A map from elements that have a lombok.Builder.Default annotation to the simple property name + * that should be treated as defaulted. + * + *

          This cache is kept because the usual method for checking that an element has been + * defaulted (calling declarationFromElement and examining the resulting VariableTree) only + * works if a corresponding Tree is available (for code that is only available as bytecode, no + * such Tree is available and that method returns null). See the code in {@link + * #getLombokRequiredProperties(Element)} that handles fields. + */ + private final Map defaultedElements = new HashMap<>(2); + + @Override + public boolean isBuilderBuildMethod(ExecutableElement candidateBuildElement) { + TypeElement candidateGeneratedBuilderElement = + (TypeElement) candidateBuildElement.getEnclosingElement(); + + if ((ElementUtils.hasAnnotation(candidateGeneratedBuilderElement, "lombok.Generated") + || ElementUtils.hasAnnotation(candidateBuildElement, "lombok.Generated")) + && candidateGeneratedBuilderElement + .getSimpleName() + .toString() + .endsWith("Builder")) { + return candidateBuildElement.getSimpleName().contentEquals("build"); } - } else if (member.getKind().isClass() && member.toString().endsWith("Builder")) { - // Note that the test above will fail to catch builders generated by Lombok that - // have custom names using the builderClassName attribute. TODO: find a way to - // handle such builders too. - - // If a field bar has an @Singular annotation, Lombok always generates a method - // called clearBar in the builder class itself. Therefore, search the builder for - // such a method, and extract the appropriate property name to treat as defaulted. - for (Element builderMember : member.getEnclosedElements()) { - if (builderMember.getKind() == ElementKind.METHOD - && ElementUtils.hasAnnotation(builderMember, "lombok.Generated")) { - String methodName = builderMember.getSimpleName().toString(); - if (methodName.startsWith("clear")) { - String propName = - Introspector.decapitalize(methodName.substring(5)); // clear has 5 characters - defaultedPropertyNames.add(propName); - } - } else if (builderMember.getKind() == ElementKind.FIELD) { - VariableTree variableTree = - (VariableTree) atypeFactory.declarationFromElement(builderMember); - if (variableTree != null && variableTree.getInitializer() != null) { - Name propName = variableTree.getName(); - defaultedPropertyNames.add(propName.toString()); - defaultedElements.put(builderMember, propName); - } else if (defaultedElements.containsKey(builderMember)) { - defaultedPropertyNames.add(defaultedElements.get(builderMember).toString()); + return false; + } + + @Override + public void handleBuilderBuildMethod(AnnotatedExecutableType builderBuildType) { + ExecutableElement buildElement = builderBuildType.getElement(); + + TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); + // The class with the @lombok.Builder annotation... + Element annotatedWithBuilderElement = generatedBuilderElement.getEnclosingElement(); + + List requiredProperties = getLombokRequiredProperties(annotatedWithBuilderElement); + AnnotationMirror newCalledMethodsAnno = + atypeFactory.createAccumulatorAnnotation(requiredProperties); + builderBuildType.getReceiverType().addAnnotation(newCalledMethodsAnno); + } + + @Override + public boolean isToBuilderMethod(ExecutableElement candidateToBuilderElement) { + return candidateToBuilderElement.getSimpleName().contentEquals("toBuilder") + && (ElementUtils.hasAnnotation(candidateToBuilderElement, "lombok.Generated") + || ElementUtils.hasAnnotation( + candidateToBuilderElement.getEnclosingElement(), + "lombok.Generated")); + } + + @Override + public void handleToBuilderMethod(AnnotatedExecutableType toBuilderType) { + AnnotatedTypeMirror returnType = toBuilderType.getReturnType(); + ExecutableElement buildElement = toBuilderType.getElement(); + TypeElement generatedBuilderElement = (TypeElement) buildElement.getEnclosingElement(); + handleToBuilderType(returnType, generatedBuilderElement); + } + + /** + * Add, to a type, a CalledMethods annotation that states that all required setters have been + * called. The type can be the return type of toBuilder or of the corresponding generated "copy" + * constructor. + * + * @param type type to update + * @param classElement corresponding AutoValue class + */ + private void handleToBuilderType(AnnotatedTypeMirror type, Element classElement) { + List requiredProperties = getLombokRequiredProperties(classElement); + AnnotationMirror calledMethodsAnno = + atypeFactory.createAccumulatorAnnotation(requiredProperties); + type.replaceAnnotation(calledMethodsAnno); + } + + /** + * Computes the required properties of a @lombok.Builder class, i.e., the names of the fields + * with @lombok.NonNull annotations. + * + * @param lombokClassElement the class with the @lombok.Builder annotation + * @return a list of required property names + */ + private List getLombokRequiredProperties(Element lombokClassElement) { + List requiredPropertyNames = new ArrayList<>(); + List defaultedPropertyNames = new ArrayList<>(); + for (Element member : lombokClassElement.getEnclosedElements()) { + if (member.getKind() == ElementKind.FIELD) { + // Lombok never generates non-null fields with initializers in builders, unless the + // field is annotated with @Default or @Singular, which are handled elsewhere. So, + // this code doesn't need to consider whether the field has or does not have + // initializers. + for (AnnotationMirror anm : + atypeFactory.getElementUtils().getAllAnnotationMirrors(member)) { + if (NONNULL_ANNOTATIONS.contains(AnnotationUtils.annotationName(anm))) { + requiredPropertyNames.add(member.getSimpleName().toString()); + } + } + } else if (member.getKind() == ElementKind.METHOD + && ElementUtils.hasAnnotation(member, "lombok.Generated")) { + String methodName = member.getSimpleName().toString(); + // If a field foo has an @Builder.Default annotation, Lombok always generates a + // method called $default$foo. + if (methodName.startsWith("$default$")) { + String propName = methodName.substring(9); // $default$ has 9 characters + defaultedPropertyNames.add(propName); + } + } else if (member.getKind().isClass() && member.toString().endsWith("Builder")) { + // Note that the test above will fail to catch builders generated by Lombok that + // have custom names using the builderClassName attribute. TODO: find a way to + // handle such builders too. + + // If a field bar has an @Singular annotation, Lombok always generates a method + // called clearBar in the builder class itself. Therefore, search the builder for + // such a method, and extract the appropriate property name to treat as defaulted. + for (Element builderMember : member.getEnclosedElements()) { + if (builderMember.getKind() == ElementKind.METHOD + && ElementUtils.hasAnnotation(builderMember, "lombok.Generated")) { + String methodName = builderMember.getSimpleName().toString(); + if (methodName.startsWith("clear")) { + String propName = + Introspector.decapitalize( + methodName.substring(5)); // clear has 5 characters + defaultedPropertyNames.add(propName); + } + } else if (builderMember.getKind() == ElementKind.FIELD) { + VariableTree variableTree = + (VariableTree) atypeFactory.declarationFromElement(builderMember); + if (variableTree != null && variableTree.getInitializer() != null) { + Name propName = variableTree.getName(); + defaultedPropertyNames.add(propName.toString()); + defaultedElements.put(builderMember, propName); + } else if (defaultedElements.containsKey(builderMember)) { + defaultedPropertyNames.add( + defaultedElements.get(builderMember).toString()); + } + } + } } - } } - } + requiredPropertyNames.removeAll(defaultedPropertyNames); + return requiredPropertyNames; + } + + @Override + public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { + // do nothing } - requiredPropertyNames.removeAll(defaultedPropertyNames); - return requiredPropertyNames; - } - - @Override - public void handleConstructor(NewClassTree tree, AnnotatedTypeMirror type) { - // do nothing - } } diff --git a/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java index c324d3cd835..a3fb2cf67ee 100644 --- a/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/compilermsgs/CompilerMessagesAnnotatedTypeFactory.java @@ -9,18 +9,18 @@ /** A PropertyKeyATF that uses CompilerMessageKey to annotate the keys. */ public class CompilerMessagesAnnotatedTypeFactory extends PropertyKeyAnnotatedTypeFactory { - public CompilerMessagesAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Does not call postInit() because its superclass does. - // If we ever add code to this constructor, it needs to: - // * call a superclass constructor that does not call postInit(), and - // * call postInit() itself. - } + public CompilerMessagesAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Does not call postInit() because its superclass does. + // If we ever add code to this constructor, it needs to: + // * call a superclass constructor that does not call postInit(), and + // * call postInit() itself. + } - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createBasicTreeAnnotator(), - new KeyLookupTreeAnnotator(this, CompilerMessageKey.class)); - } + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createBasicTreeAnnotator(), + new KeyLookupTreeAnnotator(this, CompilerMessageKey.class)); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java index 13162b0581f..aa84913b0c2 100644 --- a/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumAnnotatedTypeFactory.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.fenum; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.fenum.qual.Fenum; import org.checkerframework.checker.fenum.qual.FenumBottom; import org.checkerframework.checker.fenum.qual.FenumTop; @@ -24,149 +19,158 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** The type factory for the Fenum Checker. */ public class FenumAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** AnnotationMirror for {@link FenumUnqualified}. */ - protected final AnnotationMirror FENUM_UNQUALIFIED; - - /** AnnotationMirror for {@link FenumBottom}. */ - protected final AnnotationMirror FENUM_BOTTOM; - - /** AnnotationMirror for {@link FenumTop}. */ - protected final AnnotationMirror FENUM_TOP; - - /** - * Create a FenumAnnotatedTypeFactory. - * - * @param checker checker - */ - public FenumAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - FENUM_BOTTOM = AnnotationBuilder.fromClass(elements, FenumBottom.class); - FENUM_UNQUALIFIED = AnnotationBuilder.fromClass(elements, FenumUnqualified.class); - FENUM_TOP = AnnotationBuilder.fromClass(elements, FenumTop.class); - - this.postInit(); - } - - /** - * Copied from SubtypingChecker. Instead of returning an empty set if no "quals" option is given, - * we return Fenum as the only qualifier. - * - * @return the supported type qualifiers - */ - @Override - protected Set> createSupportedTypeQualifiers() { - // Load everything in qual directory, and top, bottom, unqualified, and fake enum - Set> qualSet = - getBundledTypeQualifiers( - FenumTop.class, - Fenum.class, - FenumUnqualified.class, - FenumBottom.class, - PolyFenum.class); - - // Load externally defined quals given in the -Aquals and/or -AqualDirs options - - // load individually named qualifiers - for (String qualName : checker.getStringsOption("quals", ',')) { - if (!Signatures.isBinaryName(qualName)) { - throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); - } - Class annoClass = loader.loadExternalAnnotationClass(qualName); - if (annoClass == null) { - throw new UserError("Cannot load qualifier \"%s\" in -Aquals", qualName); - } - qualSet.add(annoClass); - } + /** AnnotationMirror for {@link FenumUnqualified}. */ + protected final AnnotationMirror FENUM_UNQUALIFIED; - // load directories of qualifiers - for (String dirName : checker.getStringsOption("qualDirs", ',')) { - qualSet.addAll(loader.loadExternalAnnotationClassesFromDirectory(dirName)); - } + /** AnnotationMirror for {@link FenumBottom}. */ + protected final AnnotationMirror FENUM_BOTTOM; - // TODO: warn if no qualifiers given? - // Just Fenum("..") is still valid, though... - return qualSet; - } + /** AnnotationMirror for {@link FenumTop}. */ + protected final AnnotationMirror FENUM_TOP; - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new FenumQualifierHierarchy(getSupportedTypeQualifiers(), elements); - } + /** + * Create a FenumAnnotatedTypeFactory. + * + * @param checker checker + */ + public FenumAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); - /** Fenum qualifier hierarchy. */ - protected class FenumQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + FENUM_BOTTOM = AnnotationBuilder.fromClass(elements, FenumBottom.class); + FENUM_UNQUALIFIED = AnnotationBuilder.fromClass(elements, FenumUnqualified.class); + FENUM_TOP = AnnotationBuilder.fromClass(elements, FenumTop.class); - /** QualifierKind for {@link Fenum} qualifier. */ - private final QualifierKind FENUM_KIND; + this.postInit(); + } /** - * Creates FenumQualifierHierarchy. + * Copied from SubtypingChecker. Instead of returning an empty set if no "quals" option is + * given, we return Fenum as the only qualifier. * - * @param qualifierClasses qualifier classes - * @param elements element utils + * @return the supported type qualifiers */ - public FenumQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, FenumAnnotatedTypeFactory.this); - this.FENUM_KIND = - this.qualifierKindHierarchy.getQualifierKind(Fenum.class.getCanonicalName()); - } - @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization FenumQualifierHierarchy this, - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses, FenumBottom.class); + protected Set> createSupportedTypeQualifiers() { + // Load everything in qual directory, and top, bottom, unqualified, and fake enum + Set> qualSet = + getBundledTypeQualifiers( + FenumTop.class, + Fenum.class, + FenumUnqualified.class, + FenumBottom.class, + PolyFenum.class); + + // Load externally defined quals given in the -Aquals and/or -AqualDirs options + + // load individually named qualifiers + for (String qualName : checker.getStringsOption("quals", ',')) { + if (!Signatures.isBinaryName(qualName)) { + throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); + } + Class annoClass = loader.loadExternalAnnotationClass(qualName); + if (annoClass == null) { + throw new UserError("Cannot load qualifier \"%s\" in -Aquals", qualName); + } + qualSet.add(annoClass); + } + + // load directories of qualifiers + for (String dirName : checker.getStringsOption("qualDirs", ',')) { + qualSet.addAll(loader.loadExternalAnnotationClassesFromDirectory(dirName)); + } + + // TODO: warn if no qualifiers given? + // Just Fenum("..") is still valid, though... + return qualSet; } @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - return AnnotationUtils.areSame(subAnno, superAnno); + protected QualifierHierarchy createQualifierHierarchy() { + return new FenumQualifierHierarchy(getSupportedTypeQualifiers(), elements); } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; + /** Fenum qualifier hierarchy. */ + protected class FenumQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + /** QualifierKind for {@link Fenum} qualifier. */ + private final QualifierKind FENUM_KIND; + + /** + * Creates FenumQualifierHierarchy. + * + * @param qualifierClasses qualifier classes + * @param elements element utils + */ + public FenumQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, FenumAnnotatedTypeFactory.this); + this.FENUM_KIND = + this.qualifierKindHierarchy.getQualifierKind(Fenum.class.getCanonicalName()); } - return FENUM_TOP; - } else if (qualifierKind1 == FENUM_KIND) { - return a1; - } else if (qualifierKind2 == FENUM_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); - } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { - return FENUM_BOTTOM; - } else if (qualifierKind1 == FENUM_KIND) { - return a2; - } else if (qualifierKind2 == FENUM_KIND) { - return a1; - } - throw new TypeSystemError("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); + @Override + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization FenumQualifierHierarchy this, + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses, FenumBottom.class); + } + + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + return AnnotationUtils.areSame(subAnno, superAnno); + } + + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } + return FENUM_TOP; + } else if (qualifierKind1 == FENUM_KIND) { + return a1; + } else if (qualifierKind2 == FENUM_KIND) { + return a2; + } + throw new TypeSystemError( + "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); + } + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == FENUM_KIND && qualifierKind2 == FENUM_KIND) { + return FENUM_BOTTOM; + } else if (qualifierKind1 == FENUM_KIND) { + return a2; + } else if (qualifierKind2 == FENUM_KIND) { + return a1; + } + throw new TypeSystemError( + "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java index fc38b9269c0..7835b788c73 100644 --- a/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumChecker.java @@ -1,11 +1,13 @@ package org.checkerframework.checker.fenum; -import java.util.NavigableSet; -import javax.annotation.processing.SupportedOptions; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.SubtypingChecker; import org.checkerframework.framework.qual.StubFiles; +import java.util.NavigableSet; + +import javax.annotation.processing.SupportedOptions; + /** * The main checker class for the Fake Enum Checker. * @@ -25,9 +27,9 @@ @SupportedOptions({"quals", "qualDirs"}) public class FenumChecker extends BaseTypeChecker { - @Override - public NavigableSet getSuppressWarningsPrefixes() { - return SubtypingChecker.getSuppressWarningsPrefixes( - this.visitor, super.getSuppressWarningsPrefixes()); - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + return SubtypingChecker.getSuppressWarningsPrefixes( + this.visitor, super.getSuppressWarningsPrefixes()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java b/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java index c8b51644dee..5b915fb934c 100644 --- a/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/fenum/FenumVisitor.java @@ -6,8 +6,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.SwitchTree; import com.sun.source.tree.Tree; -import java.util.List; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -17,78 +16,83 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; +import java.util.List; + +import javax.lang.model.element.ExecutableElement; + /** The visitor for Fenum Checker. */ public class FenumVisitor extends BaseTypeVisitor { - /** - * Creates a Fenum Visitor - * - * @param checker the checker - */ - public FenumVisitor(BaseTypeChecker checker) { - super(checker); - } - - @Override - public Void visitBinary(BinaryTree tree, Void p) { - if (!TreeUtils.isStringConcatenation(tree)) { - // The Fenum Checker is only concerned with primitive types, so just check that - // the primary annotations are equivalent. - AnnotatedTypeMirror lhs = atypeFactory.getAnnotatedType(tree.getLeftOperand()); - AnnotatedTypeMirror rhs = atypeFactory.getAnnotatedType(tree.getRightOperand()); - - if (!(typeHierarchy.isSubtypeShallowEffective(lhs, rhs) - || typeHierarchy.isSubtypeShallowEffective(rhs, lhs))) { - checker.reportError(tree, "binary.type.incompatible", lhs, rhs); - } + /** + * Creates a Fenum Visitor + * + * @param checker the checker + */ + public FenumVisitor(BaseTypeChecker checker) { + super(checker); + } + + @Override + public Void visitBinary(BinaryTree tree, Void p) { + if (!TreeUtils.isStringConcatenation(tree)) { + // The Fenum Checker is only concerned with primitive types, so just check that + // the primary annotations are equivalent. + AnnotatedTypeMirror lhs = atypeFactory.getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rhs = atypeFactory.getAnnotatedType(tree.getRightOperand()); + + if (!(typeHierarchy.isSubtypeShallowEffective(lhs, rhs) + || typeHierarchy.isSubtypeShallowEffective(rhs, lhs))) { + checker.reportError(tree, "binary.type.incompatible", lhs, rhs); + } + } + return super.visitBinary(tree, p); + } + + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + ExpressionTree expr = tree.getExpression(); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); + + for (CaseTree caseExpr : tree.getCases()) { + List realCaseExprs = CaseUtils.getExpressions(caseExpr); + // Check all the case options against the switch expression type: + for (ExpressionTree realCaseExpr : realCaseExprs) { + AnnotatedTypeMirror caseType = atypeFactory.getAnnotatedType(realCaseExpr); + + // There is currently no "switch.type.incompatible" message key, so it is treated + // identically to "type.incompatible". + this.commonAssignmentCheck( + exprType, caseType, caseExpr, "switch.type.incompatible"); + } + } + return super.visitSwitch(tree, p); } - return super.visitBinary(tree, p); - } - - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - ExpressionTree expr = tree.getExpression(); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); - - for (CaseTree caseExpr : tree.getCases()) { - List realCaseExprs = CaseUtils.getExpressions(caseExpr); - // Check all the case options against the switch expression type: - for (ExpressionTree realCaseExpr : realCaseExprs) { - AnnotatedTypeMirror caseType = atypeFactory.getAnnotatedType(realCaseExpr); - - // There is currently no "switch.type.incompatible" message key, so it is treated - // identically to "type.incompatible". - this.commonAssignmentCheck(exprType, caseType, caseExpr, "switch.type.incompatible"); - } + + @Override + protected void checkConstructorInvocation( + AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { + // Ignore the default annotation on the constructor + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Skip this check + } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(atypeFactory.FENUM_UNQUALIFIED); + } + + // TODO: should we require a match between switch expression and cases? + + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // The checker calls this method to compare the annotation used in a type to the modifier it + // adds to the class declaration. As our default modifier is FenumBottom, this results in an + // error when a non-subtype is used. Can we use FenumTop as default instead? + return true; } - return super.visitSwitch(tree, p); - } - - @Override - protected void checkConstructorInvocation( - AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { - // Ignore the default annotation on the constructor - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Skip this check - } - - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(atypeFactory.FENUM_UNQUALIFIED); - } - - // TODO: should we require a match between switch expression and cases? - - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // The checker calls this method to compare the annotation used in a type to the modifier it - // adds to the class declaration. As our default modifier is FenumBottom, this results in an - // error when a non-subtype is used. Can we use FenumTop as default instead? - return true; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java index 533dd146882..ee429458c92 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java @@ -2,9 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.IllegalFormatException; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; import org.checkerframework.checker.formatter.qual.FormatBottom; @@ -26,6 +24,11 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.IllegalFormatException; + +import javax.lang.model.element.AnnotationMirror; + /** * Adds {@link Format} to the type of tree, if it is a {@code String} or {@code char} literal that * represents a satisfiable format. The annotation's value is set to be a list of appropriate {@link @@ -35,337 +38,348 @@ */ public class FormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link UnknownFormat} annotation. */ - protected final AnnotationMirror UNKNOWNFORMAT = - AnnotationBuilder.fromClass(elements, UnknownFormat.class); - - /** The @{@link FormatBottom} annotation. */ - protected final AnnotationMirror FORMATBOTTOM = - AnnotationBuilder.fromClass(elements, FormatBottom.class); - - /** The @{@link FormatMethod} annotation. */ - protected final AnnotationMirror FORMATMETHOD = - AnnotationBuilder.fromClass(elements, FormatMethod.class); - - /** The fully-qualified name of the {@link Format} qualifier. */ - protected static final @CanonicalName String FORMAT_NAME = Format.class.getCanonicalName(); - - /** The fully-qualified name of the {@link InvalidFormat} qualifier. */ - protected static final @CanonicalName String INVALIDFORMAT_NAME = - InvalidFormat.class.getCanonicalName(); - - /** Syntax tree utilities. */ - protected final FormatterTreeUtil treeUtil = new FormatterTreeUtil(checker); - - /** Creates a FormatterAnnotatedTypeFactory. */ - public FormatterAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - try { - // Use concatenation to avoid ShadowJar relocate - // "com.google.errorprone.annotations.FormatMethod" - @SuppressWarnings({ - "unchecked", // Class must be an annotation type - "signature:argument.type.incompatible" // Class name intentionally obfuscated - }) - Class cgFormatMethod = - (Class) - Class.forName("com.go".toString() + "ogle.errorprone.annotations.FormatMethod"); - - addAliasedDeclAnnotation(cgFormatMethod, FormatMethod.class, FORMATMETHOD); - } catch (ClassNotFoundException cnfe) { - // Ignore if com.google.errorprone.annotations.FormatMethod cannot be found. + /** The @{@link UnknownFormat} annotation. */ + protected final AnnotationMirror UNKNOWNFORMAT = + AnnotationBuilder.fromClass(elements, UnknownFormat.class); + + /** The @{@link FormatBottom} annotation. */ + protected final AnnotationMirror FORMATBOTTOM = + AnnotationBuilder.fromClass(elements, FormatBottom.class); + + /** The @{@link FormatMethod} annotation. */ + protected final AnnotationMirror FORMATMETHOD = + AnnotationBuilder.fromClass(elements, FormatMethod.class); + + /** The fully-qualified name of the {@link Format} qualifier. */ + protected static final @CanonicalName String FORMAT_NAME = Format.class.getCanonicalName(); + + /** The fully-qualified name of the {@link InvalidFormat} qualifier. */ + protected static final @CanonicalName String INVALIDFORMAT_NAME = + InvalidFormat.class.getCanonicalName(); + + /** Syntax tree utilities. */ + protected final FormatterTreeUtil treeUtil = new FormatterTreeUtil(checker); + + /** Creates a FormatterAnnotatedTypeFactory. */ + public FormatterAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + try { + // Use concatenation to avoid ShadowJar relocate + // "com.google.errorprone.annotations.FormatMethod" + @SuppressWarnings({ + "unchecked", // Class must be an annotation type + "signature:argument.type.incompatible" // Class name intentionally obfuscated + }) + Class cgFormatMethod = + (Class) + Class.forName( + "com.go".toString() + + "ogle.errorprone.annotations.FormatMethod"); + + addAliasedDeclAnnotation(cgFormatMethod, FormatMethod.class, FORMATMETHOD); + } catch (ClassNotFoundException cnfe) { + // Ignore if com.google.errorprone.annotations.FormatMethod cannot be found. + } + + this.postInit(); } - this.postInit(); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new FormatterQualifierHierarchy(); - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new FormatterTreeAnnotator(this)); - } - - /* NO-AFU - * {@inheritDoc} - * - *

          If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation - * from its first argument. - */ - /* NO-AFU - @Override - public void wpiPrepareMethodForWriting(AMethod method) { - super.wpiPrepareMethodForWriting(method); - if (hasFormatMethodAnno(method)) { - AField param = method.parameters.get(0); - if (param != null) { - Set paramTypeAnnos = param.type.tlAnnotationsHere; - paramTypeAnnos.removeIf( - a -> a.def.name.equals("org.checkerframework.checker.formatter.qual.Format")); - } - } - */ - - /* NO-AFU - * {@inheritDoc} - * - *

          If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation - * from its first argument. - */ - /* NO-AFU - @Override - public void wpiPrepareMethodForWriting( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, - Collection inSupertypes, - Collection inSubtypes) { - super.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); - if (hasFormatMethodAnno(methodAnnos)) { - AnnotatedTypeMirror atm = methodAnnos.getParameterType(0); - atm.removeAnnotationByClass(org.checkerframework.checker.formatter.qual.Format.class); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new FormatterQualifierHierarchy(); } - } - */ - - /* NO-AFU - * Returns true if the method has a {@code @FormatMethod} annotation. - * - * @param methodAnnos method annotations - * @return true if the method has a {@code @FormatMethod} annotation - */ - /* NO-AFU - private boolean hasFormatMethodAnno(AMethod methodAnnos) { - for (Annotation anno : methodAnnos.tlAnnotationsHere) { - String annoName = anno.def.name; - if (annoName.equals("org.checkerframework.checker.formatter.qual.FormatMethod") - || anno.def.name.equals("com.google.errorprone.annotations.FormatMethod")) { - return true; - } - } - */ - - /* NO-AFU - * Returns true if the method has a {@code @FormatMethod} annotation. - * - * @param methodAnnos method annotations - * @return true if the method has a {@code @FormatMethod} annotation - */ - /* NO-AFU - private boolean hasFormatMethodAnno( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); - return AnnotationUtils.containsSameByClass( - declarationAnnos, org.checkerframework.checker.formatter.qual.FormatMethod.class) - || AnnotationUtils.containsSameByName( - declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); - } - */ - - /* NO-AFU - * Returns true if the method has a {@code @FormatMethod} annotation. - * - * @param methodAnnos method annotations - * @return true if the method has a {@code @FormatMethod} annotation - */ - /* NO-AFU - private boolean hasFormatMethodAnno( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); - return AnnotationUtils.containsSameByClass( - declarationAnnos, - org.checkerframework.checker.formatter.qual.FormatMethod.class) - || AnnotationUtils.containsSameByName( - // TODO: avoid com.google relocate - declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); - } - */ - - /** The tree annotator for the Format String Checker. */ - private class FormatterTreeAnnotator extends TreeAnnotator { - /** - * Create the tree annotator for the Format String Checker. + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new FormatterTreeAnnotator(this)); + } + + /* NO-AFU + * {@inheritDoc} * - * @param atypeFactory the Format String Checker type factory + *

          If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation + * from its first argument. */ - public FormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /* NO-AFU + @Override + public void wpiPrepareMethodForWriting(AMethod method) { + super.wpiPrepareMethodForWriting(method); + if (hasFormatMethodAnno(method)) { + AField param = method.parameters.get(0); + if (param != null) { + Set paramTypeAnnos = param.type.tlAnnotationsHere; + paramTypeAnnos.removeIf( + a -> a.def.name.equals("org.checkerframework.checker.formatter.qual.Format")); + } } + */ + /* NO-AFU + * {@inheritDoc} + * + *

          If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation + * from its first argument. + */ + /* NO-AFU @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(UNKNOWNFORMAT)) { - String format = null; - if (tree.getKind() == Tree.Kind.STRING_LITERAL) { - format = (String) tree.getValue(); - } - if (format != null) { - AnnotationMirror anno; - try { - ConversionCategory[] cs = FormatUtil.formatParameterCategories(format); - anno = FormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs); - } catch (IllegalFormatException e) { - anno = - FormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation(e); - } - type.addAnnotation(anno); - } + public void wpiPrepareMethodForWriting( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, + Collection inSupertypes, + Collection inSubtypes) { + super.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); + if (hasFormatMethodAnno(methodAnnos)) { + AnnotatedTypeMirror atm = methodAnnos.getParameterType(0); + atm.removeAnnotationByClass(org.checkerframework.checker.formatter.qual.Format.class); } - return super.visitLiteral(tree, type); } - } - - /** Qualifier hierarchy for the Formatter Checker. */ - class FormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + */ - /** Qualifier kind for the @{@link Format} annotation. */ - private final QualifierKind FORMAT_KIND; - - /** Qualifier kind for the @{@link InvalidFormat} annotation. */ - private final QualifierKind INVALIDFORMAT_KIND; + /* NO-AFU + * Returns true if the method has a {@code @FormatMethod} annotation. + * + * @param methodAnnos method annotations + * @return true if the method has a {@code @FormatMethod} annotation + */ + /* NO-AFU + private boolean hasFormatMethodAnno(AMethod methodAnnos) { + for (Annotation anno : methodAnnos.tlAnnotationsHere) { + String annoName = anno.def.name; + if (annoName.equals("org.checkerframework.checker.formatter.qual.FormatMethod") + || anno.def.name.equals("com.google.errorprone.annotations.FormatMethod")) { + return true; + } + } + */ - /** Creates a {@link FormatterQualifierHierarchy}. */ - public FormatterQualifierHierarchy() { - super( - FormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - FormatterAnnotatedTypeFactory.this); - FORMAT_KIND = getQualifierKind(FORMAT_NAME); - INVALIDFORMAT_KIND = getQualifierKind(INVALIDFORMAT_NAME); + /* NO-AFU + * Returns true if the method has a {@code @FormatMethod} annotation. + * + * @param methodAnnos method annotations + * @return true if the method has a {@code @FormatMethod} annotation + */ + /* NO-AFU + private boolean hasFormatMethodAnno( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); + return AnnotationUtils.containsSameByClass( + declarationAnnos, org.checkerframework.checker.formatter.qual.FormatMethod.class) + || AnnotationUtils.containsSameByName( + declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); } + */ - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == FORMAT_KIND && superKind == FORMAT_KIND) { - ConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno); - ConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno); - - if (rhsArgTypes.length > lhsArgTypes.length) { - return false; + /* NO-AFU + * Returns true if the method has a {@code @FormatMethod} annotation. + * + * @param methodAnnos method annotations + * @return true if the method has a {@code @FormatMethod} annotation + */ + /* NO-AFU + private boolean hasFormatMethodAnno( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + AnnotationMirrorSet declarationAnnos = methodAnnos.getDeclarationAnnotations(); + return AnnotationUtils.containsSameByClass( + declarationAnnos, + org.checkerframework.checker.formatter.qual.FormatMethod.class) + || AnnotationUtils.containsSameByName( + // TODO: avoid com.google relocate + declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); + } + */ + + /** The tree annotator for the Format String Checker. */ + private class FormatterTreeAnnotator extends TreeAnnotator { + /** + * Create the tree annotator for the Format String Checker. + * + * @param atypeFactory the Format String Checker type factory + */ + public FormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - for (int i = 0; i < rhsArgTypes.length; ++i) { - if (!ConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { - return false; - } + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(UNKNOWNFORMAT)) { + String format = null; + if (tree.getKind() == Tree.Kind.STRING_LITERAL) { + format = (String) tree.getValue(); + } + if (format != null) { + AnnotationMirror anno; + try { + ConversionCategory[] cs = FormatUtil.formatParameterCategories(format); + anno = + FormatterAnnotatedTypeFactory.this.treeUtil + .categoriesToFormatAnnotation(cs); + } catch (IllegalFormatException e) { + anno = + FormatterAnnotatedTypeFactory.this.treeUtil + .exceptionToInvalidFormatAnnotation(e); + } + type.addAnnotation(anno); + } + } + return super.visitLiteral(tree, type); } - return true; - } else if (subKind == INVALIDFORMAT_KIND && superKind == INVALIDFORMAT_KIND) { - return true; - } - throw new TypeSystemError("Unexpected kinds: %s %s", subKind, superKind); } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.isBottom()) { - return anno2; - } else if (qualifierKind2.isBottom()) { - return anno1; - } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { - ConversionCategory[] shorterArgTypesList = treeUtil.formatAnnotationToCategories(anno1); - ConversionCategory[] longerArgTypesList = treeUtil.formatAnnotationToCategories(anno2); - if (shorterArgTypesList.length > longerArgTypesList.length) { - ConversionCategory[] temp = longerArgTypesList; - longerArgTypesList = shorterArgTypesList; - shorterArgTypesList = temp; - } + /** Qualifier hierarchy for the Formatter Checker. */ + class FormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. + /** Qualifier kind for the @{@link Format} annotation. */ + private final QualifierKind FORMAT_KIND; - ConversionCategory[] resultArgTypes = new ConversionCategory[longerArgTypesList.length]; + /** Qualifier kind for the @{@link InvalidFormat} annotation. */ + private final QualifierKind INVALIDFORMAT_KIND; - for (int i = 0; i < shorterArgTypesList.length; ++i) { - resultArgTypes[i] = - ConversionCategory.intersect(shorterArgTypesList[i], longerArgTypesList[i]); + /** Creates a {@link FormatterQualifierHierarchy}. */ + public FormatterQualifierHierarchy() { + super( + FormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + FormatterAnnotatedTypeFactory.this); + FORMAT_KIND = getQualifierKind(FORMAT_NAME); + INVALIDFORMAT_KIND = getQualifierKind(INVALIDFORMAT_NAME); } - System.arraycopy( - longerArgTypesList, - shorterArgTypesList.length, - resultArgTypes, - shorterArgTypesList.length, - longerArgTypesList.length - shorterArgTypesList.length); - return treeUtil.categoriesToFormatAnnotation(resultArgTypes); - } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) { - - assert !anno1.getElementValues().isEmpty(); - assert !anno2.getElementValues().isEmpty(); - - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; - } - - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " or " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } - - return UNKNOWNFORMAT; - } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1.isTop()) { - return anno2; - } else if (qualifierKind2.isTop()) { - return anno1; - } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { - ConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1); - ConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2); - - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - int length = anno1ArgTypes.length; - if (anno2ArgTypes.length < length) { - length = anno2ArgTypes.length; + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == FORMAT_KIND && superKind == FORMAT_KIND) { + ConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno); + ConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno); + + if (rhsArgTypes.length > lhsArgTypes.length) { + return false; + } + + for (int i = 0; i < rhsArgTypes.length; ++i) { + if (!ConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { + return false; + } + } + return true; + } else if (subKind == INVALIDFORMAT_KIND && superKind == INVALIDFORMAT_KIND) { + return true; + } + throw new TypeSystemError("Unexpected kinds: %s %s", subKind, superKind); } - ConversionCategory[] anno3ArgTypes = new ConversionCategory[length]; - - for (int i = 0; i < length; ++i) { - anno3ArgTypes[i] = ConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.isBottom()) { + return anno2; + } else if (qualifierKind2.isBottom()) { + return anno1; + } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { + ConversionCategory[] shorterArgTypesList = + treeUtil.formatAnnotationToCategories(anno1); + ConversionCategory[] longerArgTypesList = + treeUtil.formatAnnotationToCategories(anno2); + if (shorterArgTypesList.length > longerArgTypesList.length) { + ConversionCategory[] temp = longerArgTypesList; + longerArgTypesList = shorterArgTypesList; + shorterArgTypesList = temp; + } + + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + + ConversionCategory[] resultArgTypes = + new ConversionCategory[longerArgTypesList.length]; + + for (int i = 0; i < shorterArgTypesList.length; ++i) { + resultArgTypes[i] = + ConversionCategory.intersect( + shorterArgTypesList[i], longerArgTypesList[i]); + } + System.arraycopy( + longerArgTypesList, + shorterArgTypesList.length, + resultArgTypes, + shorterArgTypesList.length, + longerArgTypesList.length - shorterArgTypesList.length); + return treeUtil.categoriesToFormatAnnotation(resultArgTypes); + } else if (qualifierKind1 == INVALIDFORMAT_KIND + && qualifierKind2 == INVALIDFORMAT_KIND) { + + assert !anno1.getElementValues().isEmpty(); + assert !anno2.getElementValues().isEmpty(); + + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; + } + + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " or " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } + + return UNKNOWNFORMAT; } - return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); - } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) { - - assert !anno1.getElementValues().isEmpty(); - assert !anno2.getElementValues().isEmpty(); - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1.isTop()) { + return anno2; + } else if (qualifierKind2.isTop()) { + return anno1; + } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { + ConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1); + ConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2); + + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + int length = anno1ArgTypes.length; + if (anno2ArgTypes.length < length) { + length = anno2ArgTypes.length; + } + + ConversionCategory[] anno3ArgTypes = new ConversionCategory[length]; + + for (int i = 0; i < length; ++i) { + anno3ArgTypes[i] = ConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); + } + return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); + } else if (qualifierKind1 == INVALIDFORMAT_KIND + && qualifierKind2 == INVALIDFORMAT_KIND) { + + assert !anno1.getElementValues().isEmpty(); + assert !anno2.getElementValues().isEmpty(); + + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; + } + + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " and " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } + + return FORMATBOTTOM; } - - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " and " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } - - return FORMATBOTTOM; } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java index 3b235f94f4a..e6b4c410c0a 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.formatter; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.util.FormatUtil; @@ -13,35 +12,39 @@ import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; +import javax.lang.model.element.AnnotationMirror; + public class FormatterTransfer extends CFTransfer { - public FormatterTransfer(CFAnalysis analysis) { - super(analysis); - } + public FormatterTransfer(CFAnalysis analysis) { + super(analysis); + } - /** - * Makes it so that the {@link FormatUtil#asFormat} method returns a correctly annotated String. - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput in) { - FormatterAnnotatedTypeFactory atypeFactory = - (FormatterAnnotatedTypeFactory) analysis.getTypeFactory(); - TransferResult result = super.visitMethodInvocation(node, in); - FormatterTreeUtil tu = atypeFactory.treeUtil; + /** + * Makes it so that the {@link FormatUtil#asFormat} method returns a correctly annotated String. + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput in) { + FormatterAnnotatedTypeFactory atypeFactory = + (FormatterAnnotatedTypeFactory) analysis.getTypeFactory(); + TransferResult result = super.visitMethodInvocation(node, in); + FormatterTreeUtil tu = atypeFactory.treeUtil; - if (tu.isAsFormatCall(node, atypeFactory)) { - Result cats = tu.asFormatCallCategories(node); - if (cats.value() == null) { - tu.failure(cats, "format.asformat.indirect.arguments"); - } else { - AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); - CFValue newResultValue = - analysis.createSingleAnnotationValue(anno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - } + if (tu.isAsFormatCall(node, atypeFactory)) { + Result cats = tu.asFormatCallCategories(node); + if (cats.value() == null) { + tu.failure(cats, "format.asformat.indirect.arguments"); + } else { + AnnotationMirror anno = + atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); + CFValue newResultValue = + analysis.createSingleAnnotationValue( + anno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } + } - return result; - } + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java index c8e721ee101..4a7cc5cd977 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java @@ -5,16 +5,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.SimpleTreeVisitor; -import java.util.IllegalFormatException; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.NullType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor8; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; @@ -34,469 +25,493 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.IllegalFormatException; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleTypeVisitor8; + /** * This class provides a collection of utilities to ease working with syntax trees that have * something to do with Formatters. */ public class FormatterTreeUtil { - /** The checker. */ - public final BaseTypeChecker checker; - - /** The processing environment. */ - public final ProcessingEnvironment processingEnv; - - /** The value() element/field of an @Format annotation. */ - protected final ExecutableElement formatValueElement; - - /** The value() element/field of an @InvalidFormat annotation. */ - protected final ExecutableElement invalidFormatValueElement; - - // private final ExecutableElement formatArgTypesElement; - - public FormatterTreeUtil(BaseTypeChecker checker) { - this.checker = checker; - this.processingEnv = checker.getProcessingEnvironment(); - formatValueElement = TreeUtils.getMethod(Format.class, "value", 0, processingEnv); - invalidFormatValueElement = TreeUtils.getMethod(InvalidFormat.class, "value", 0, processingEnv); - /* - this.formatArgTypesElement = - TreeUtils.getMethod( - Format.class, - "value", - 0, - processingEnv); - */ - } + /** The checker. */ + public final BaseTypeChecker checker; + + /** The processing environment. */ + public final ProcessingEnvironment processingEnv; + + /** The value() element/field of an @Format annotation. */ + protected final ExecutableElement formatValueElement; + + /** The value() element/field of an @InvalidFormat annotation. */ + protected final ExecutableElement invalidFormatValueElement; + + // private final ExecutableElement formatArgTypesElement; + + public FormatterTreeUtil(BaseTypeChecker checker) { + this.checker = checker; + this.processingEnv = checker.getProcessingEnvironment(); + formatValueElement = TreeUtils.getMethod(Format.class, "value", 0, processingEnv); + invalidFormatValueElement = + TreeUtils.getMethod(InvalidFormat.class, "value", 0, processingEnv); + /* + this.formatArgTypesElement = + TreeUtils.getMethod( + Format.class, + "value", + 0, + processingEnv); + */ + } + + /** Describes the ways a format method may be invoked. */ + public enum InvocationType { + /** + * The parameters are passed as varargs. For example: + * + *

          + * + *
          +         * String.format("%s %d", "Example", 7);
          +         * 
          + * + *
          + */ + VARARG, + + /** + * The parameters are passed as array. For example: + * + *
          + * + *
          +         * Object[] a = new Object[]{"Example",7};
          +         * String.format("%s %d", a);
          +         * 
          + * + *
          + */ + ARRAY, + + /** + * A null array is passed to the format method. This happens seldomly. + * + *
          + * + *
          +         * String.format("%s %d", (Object[])null);
          +         * 
          + * + *
          + */ + NULLARRAY; + } + + /** A wrapper around a value of type E, plus an ExpressionTree location. */ + public static class Result { + private final E value; + public final ExpressionTree location; + + public Result(E value, ExpressionTree location) { + this.value = value; + this.location = location; + } + + public E value() { + return value; + } + } - /** Describes the ways a format method may be invoked. */ - public enum InvocationType { /** - * The parameters are passed as varargs. For example: - * - *
          - * - *
          -     * String.format("%s %d", "Example", 7);
          -     * 
          - * - *
          + * Returns true if the call is to a method with the @ReturnsFormat annotation. An example of + * such a method is FormatUtil.asFormat. */ - VARARG, + public boolean isAsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, ReturnsFormat.class); + return anno != null; + } + + private ConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( + MethodInvocationNode node) { + Node vararg = node.getArgument(1); + if (!(vararg instanceof ArrayCreationNode)) { + return null; + } + List convs = ((ArrayCreationNode) vararg).getInitializers(); + ConversionCategory[] res = new ConversionCategory[convs.size()]; + for (int i = 0; i < convs.size(); ++i) { + Node conv = convs.get(i); + if (conv instanceof FieldAccessNode) { + Class clazz = + TypesUtils.getClassFromType(((FieldAccessNode) conv).getType()); + if (clazz == ConversionCategory.class) { + res[i] = ConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName()); + continue; /* avoid returning null */ + } + } + return null; + } + return res; + } + + public Result asFormatCallCategories(MethodInvocationNode node) { + // TODO make sure the method signature looks good + return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); + } /** - * The parameters are passed as array. For example: - * - *
          + * Returns true if {@code tree} is a call to a method annotated with {@code @FormatMethod}. * - *
          -     * Object[] a = new Object[]{"Example",7};
          -     * String.format("%s %d", a);
          -     * 
          - * - *
          + * @param tree a method call + * @param atypeFactory a type factory + * @return true if {@code tree} is a call to a method annotated with {@code @FormatMethod} */ - ARRAY, + public boolean isFormatMethodCall( + MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, FormatMethod.class); + return anno != null; + } /** - * A null array is passed to the format method. This happens seldomly. - * - *
          + * Creates a new FormatCall, or returns null. * - *
          -     * String.format("%s %d", (Object[])null);
          -     * 
          - * - *
          + * @param invocationTree a method invocation, where the method is annotated @FormatMethod + * @param atypeFactory the type factory + * @return a new FormatCall, or null if the invocation is of a method that is improperly + * annotated @FormatMethod */ - NULLARRAY; - } - - /** A wrapper around a value of type E, plus an ExpressionTree location. */ - public static class Result { - private final E value; - public final ExpressionTree location; + public @Nullable FormatCall create( + MethodInvocationTree invocationTree, AnnotatedTypeFactory atypeFactory) { + FormatterTreeUtil ftu = ((FormatterAnnotatedTypeFactory) atypeFactory).treeUtil; + if (!ftu.isFormatMethodCall(invocationTree, atypeFactory)) { + return null; + } - public Result(E value, ExpressionTree location) { - this.value = value; - this.location = location; + ExecutableElement methodElement = TreeUtils.elementFromUse(invocationTree); + int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); + if (formatStringIndex == -1) { + // Reporting the error is redundant if the method was declared in source code, because + // the visitor will have reported it; but it is necessary if the method was declared in + // byte code. + atypeFactory + .getChecker() + .reportError( + invocationTree, "format.method.invalid", methodElement.getSimpleName()); + return null; + } + ExpressionTree formatStringTree = invocationTree.getArguments().get(formatStringIndex); + AnnotatedTypeMirror formatStringType = atypeFactory.getAnnotatedType(formatStringTree); + List allArgs = invocationTree.getArguments(); + List args = + allArgs.subList(formatStringIndex + 1, allArgs.size()); + + return new FormatCall( + invocationTree, formatStringTree, formatStringType, args, atypeFactory); } - public E value() { - return value; - } - } - - /** - * Returns true if the call is to a method with the @ReturnsFormat annotation. An example of such - * a method is FormatUtil.asFormat. - */ - public boolean isAsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, ReturnsFormat.class); - return anno != null; - } - - private ConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( - MethodInvocationNode node) { - Node vararg = node.getArgument(1); - if (!(vararg instanceof ArrayCreationNode)) { - return null; - } - List convs = ((ArrayCreationNode) vararg).getInitializers(); - ConversionCategory[] res = new ConversionCategory[convs.size()]; - for (int i = 0; i < convs.size(); ++i) { - Node conv = convs.get(i); - if (conv instanceof FieldAccessNode) { - Class clazz = - TypesUtils.getClassFromType(((FieldAccessNode) conv).getType()); - if (clazz == ConversionCategory.class) { - res[i] = ConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName()); - continue; /* avoid returning null */ + /** Represents a format method invocation in the syntax tree. */ + public class FormatCall { + /** The call itself. */ + /*package-private*/ final MethodInvocationTree invocationTree; + + /** The format string argument. */ + private final ExpressionTree formatStringTree; + + /** The type of the format string argument. */ + private final AnnotatedTypeMirror formatStringType; + + /** The arguments that follow the format string argument. */ + private final List args; + + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * Create a new FormatCall object. + * + * @param invocationTree the call itself + * @param formatStringTree the format string argument + * @param formatStringType the type of the format string argument + * @param args the arguments that follow the format string argument + * @param atypeFactory the type factory + */ + private FormatCall( + MethodInvocationTree invocationTree, + ExpressionTree formatStringTree, + AnnotatedTypeMirror formatStringType, + List args, + AnnotatedTypeFactory atypeFactory) { + this.invocationTree = invocationTree; + this.formatStringTree = formatStringTree; + this.formatStringType = formatStringType; + this.args = args; + this.atypeFactory = atypeFactory; } - } - return null; - } - return res; - } - - public Result asFormatCallCategories(MethodInvocationNode node) { - // TODO make sure the method signature looks good - return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); - } - - /** - * Returns true if {@code tree} is a call to a method annotated with {@code @FormatMethod}. - * - * @param tree a method call - * @param atypeFactory a type factory - * @return true if {@code tree} is a call to a method annotated with {@code @FormatMethod} - */ - public boolean isFormatMethodCall(MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, FormatMethod.class); - return anno != null; - } - - /** - * Creates a new FormatCall, or returns null. - * - * @param invocationTree a method invocation, where the method is annotated @FormatMethod - * @param atypeFactory the type factory - * @return a new FormatCall, or null if the invocation is of a method that is improperly - * annotated @FormatMethod - */ - public @Nullable FormatCall create( - MethodInvocationTree invocationTree, AnnotatedTypeFactory atypeFactory) { - FormatterTreeUtil ftu = ((FormatterAnnotatedTypeFactory) atypeFactory).treeUtil; - if (!ftu.isFormatMethodCall(invocationTree, atypeFactory)) { - return null; - } - ExecutableElement methodElement = TreeUtils.elementFromUse(invocationTree); - int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); - if (formatStringIndex == -1) { - // Reporting the error is redundant if the method was declared in source code, because - // the visitor will have reported it; but it is necessary if the method was declared in - // byte code. - atypeFactory - .getChecker() - .reportError(invocationTree, "format.method.invalid", methodElement.getSimpleName()); - return null; - } - ExpressionTree formatStringTree = invocationTree.getArguments().get(formatStringIndex); - AnnotatedTypeMirror formatStringType = atypeFactory.getAnnotatedType(formatStringTree); - List allArgs = invocationTree.getArguments(); - List args = allArgs.subList(formatStringIndex + 1, allArgs.size()); + /** + * Returns an error description if the format-string argument's type is not + * annotated as {@code @Format}. Returns null if it is annotated. + * + * @return an error description if the format string is not annotated as {@code @Format}, or + * null if it is + */ + public final @Nullable Result errMissingFormatAnnotation() { + if (!formatStringType.hasAnnotation(Format.class)) { + String msg = "(is a @Format annotation missing?)"; + AnnotationMirror inv = formatStringType.getAnnotation(InvalidFormat.class); + if (inv != null) { + msg = invalidFormatAnnotationToErrorMessage(inv); + } + return new Result<>(msg, formatStringTree); + } + return null; + } - return new FormatCall(invocationTree, formatStringTree, formatStringType, args, atypeFactory); - } + /** + * Returns the type of method invocation. + * + * @see InvocationType + */ + public final Result getInvocationType() { + InvocationType type = InvocationType.VARARG; + + if (args.size() == 1) { + ExpressionTree first = args.get(0); + TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); + // figure out if argType is an array + type = + argType.accept( + new SimpleTypeVisitor8>() { + @Override + protected InvocationType defaultAction( + TypeMirror e, Class p) { + // not an array + return InvocationType.VARARG; + } + + @Override + public InvocationType visitArray(ArrayType t, Class p) { + // it's an array, now figure out if it's a (Object[])null + // array + return first.accept( + new SimpleTreeVisitor< + InvocationType, Class>() { + @Override + protected InvocationType defaultAction( + Tree tree, Class p) { + // just a normal array + return InvocationType.ARRAY; + } + + @Override + public InvocationType visitTypeCast( + TypeCastTree tree, Class p) { + // it's a (Object[])null + return atypeFactory + .getAnnotatedType( + tree + .getExpression()) + .getUnderlyingType() + .getKind() + == TypeKind.NULL + ? InvocationType.NULLARRAY + : InvocationType.ARRAY; + } + }, + p); + } + + @Override + public InvocationType visitNull(NullType t, Class p) { + return InvocationType.NULLARRAY; + } + }, + Void.TYPE); + } - /** Represents a format method invocation in the syntax tree. */ - public class FormatCall { - /** The call itself. */ - /*package-private*/ final MethodInvocationTree invocationTree; + ExpressionTree loc = invocationTree.getMethodSelect(); + if (type != InvocationType.VARARG && !args.isEmpty()) { + loc = args.get(0); + } + return new Result<>(type, loc); + } - /** The format string argument. */ - private final ExpressionTree formatStringTree; + /** + * Returns the conversion category for every parameter. + * + * @return the conversion categories of all the parameters + * @see ConversionCategory + */ + public final ConversionCategory[] getFormatCategories() { + AnnotationMirror anno = formatStringType.getAnnotation(Format.class); + return formatAnnotationToCategories(anno); + } - /** The type of the format string argument. */ - private final AnnotatedTypeMirror formatStringType; + /** + * Returns the types of the arguments to the call. Use {@link #isValidArgument} and {@link + * #isArgumentNull} to work with the result. + * + * @return the types of the arguments to the call + */ + public final Result[] getArgTypes() { + // One to suppress warning in javac, the other to suppress warning in Eclipse... + @SuppressWarnings({"rawtypes", "unchecked"}) + Result[] res = new Result[args.size()]; + for (int i = 0; i < res.length; ++i) { + ExpressionTree arg = args.get(i); + TypeMirror argType; + if (TreeUtils.isNullExpression(arg)) { + argType = atypeFactory.getProcessingEnv().getTypeUtils().getNullType(); + } else { + argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); + } + res[i] = new Result<>(argType, arg); + } + return res; + } - /** The arguments that follow the format string argument. */ - private final List args; + /** + * Checks if the type of an argument returned from {@link #getArgTypes()} is valid for the + * passed ConversionCategory. + * + * @param formatCat a format specifier + * @param argType an argument type + * @return true if the argument can be passed to the format specifier + */ + public final boolean isValidArgument(ConversionCategory formatCat, TypeMirror argType) { + if (argType.getKind() == TypeKind.NULL || isArgumentNull(argType)) { + return true; + } + Class type = TypesUtils.getClassFromType(argType); + return formatCat.isAssignableFrom(type); + } - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; + /** + * Checks if the argument returned from {@link #getArgTypes()} is a {@code null} expression. + * + * @param type a type + * @return true if the argument is a {@code null} expression + */ + public final boolean isArgumentNull(TypeMirror type) { + // TODO: Just check whether it is the VOID TypeMirror. + + // is it the null literal + return type.accept( + new SimpleTypeVisitor8>() { + @Override + protected Boolean defaultAction(TypeMirror e, Class p) { + // it's not the null literal + return false; + } + + @Override + public Boolean visitNull(NullType t, Class p) { + // it's the null literal + return true; + } + }, + Void.TYPE); + } + } + // The failure() method is required so that FormatterTransfer, which has no access to the + // FormatterChecker, can report errors. /** - * Create a new FormatCall object. + * Reports an error. * - * @param invocationTree the call itself - * @param formatStringTree the format string argument - * @param formatStringType the type of the format string argument - * @param args the arguments that follow the format string argument - * @param atypeFactory the type factory + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message */ - private FormatCall( - MethodInvocationTree invocationTree, - ExpressionTree formatStringTree, - AnnotatedTypeMirror formatStringType, - List args, - AnnotatedTypeFactory atypeFactory) { - this.invocationTree = invocationTree; - this.formatStringTree = formatStringTree; - this.formatStringType = formatStringType; - this.args = args; - this.atypeFactory = atypeFactory; + public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportError(res.location, msgKey, args); } /** - * Returns an error description if the format-string argument's type is not annotated - * as {@code @Format}. Returns null if it is annotated. + * Reports a warning. * - * @return an error description if the format string is not annotated as {@code @Format}, or - * null if it is + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message */ - public final @Nullable Result errMissingFormatAnnotation() { - if (!formatStringType.hasAnnotation(Format.class)) { - String msg = "(is a @Format annotation missing?)"; - AnnotationMirror inv = formatStringType.getAnnotation(InvalidFormat.class); - if (inv != null) { - msg = invalidFormatAnnotationToErrorMessage(inv); - } - return new Result<>(msg, formatStringTree); - } - return null; + public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportWarning(res.location, msgKey, args); } /** - * Returns the type of method invocation. - * - * @see InvocationType + * Takes an exception that describes an invalid formatter string and, returns a syntax trees + * element that represents a {@link InvalidFormat} annotation with the exception's error message + * as value. */ - public final Result getInvocationType() { - InvocationType type = InvocationType.VARARG; - - if (args.size() == 1) { - ExpressionTree first = args.get(0); - TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); - // figure out if argType is an array - type = - argType.accept( - new SimpleTypeVisitor8>() { - @Override - protected InvocationType defaultAction(TypeMirror e, Class p) { - // not an array - return InvocationType.VARARG; - } - - @Override - public InvocationType visitArray(ArrayType t, Class p) { - // it's an array, now figure out if it's a (Object[])null - // array - return first.accept( - new SimpleTreeVisitor>() { - @Override - protected InvocationType defaultAction(Tree tree, Class p) { - // just a normal array - return InvocationType.ARRAY; - } - - @Override - public InvocationType visitTypeCast(TypeCastTree tree, Class p) { - // it's a (Object[])null - return atypeFactory - .getAnnotatedType(tree.getExpression()) - .getUnderlyingType() - .getKind() - == TypeKind.NULL - ? InvocationType.NULLARRAY - : InvocationType.ARRAY; - } - }, - p); - } - - @Override - public InvocationType visitNull(NullType t, Class p) { - return InvocationType.NULLARRAY; - } - }, - Void.TYPE); - } - - ExpressionTree loc = invocationTree.getMethodSelect(); - if (type != InvocationType.VARARG && !args.isEmpty()) { - loc = args.get(0); - } - return new Result<>(type, loc); + public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalFormatException ex) { + return stringToInvalidFormatAnnotation(ex.getMessage()); } /** - * Returns the conversion category for every parameter. + * Creates an {@link InvalidFormat} annotation with the given string as its value. * - * @return the conversion categories of all the parameters - * @see ConversionCategory + * @param invalidFormatString an invalid formatter string + * @return an {@link InvalidFormat} annotation with the given string as its value */ - public final ConversionCategory[] getFormatCategories() { - AnnotationMirror anno = formatStringType.getAnnotation(Format.class); - return formatAnnotationToCategories(anno); + /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation( + String invalidFormatString) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", invalidFormatString); + return builder.build(); } /** - * Returns the types of the arguments to the call. Use {@link #isValidArgument} and {@link - * #isArgumentNull} to work with the result. + * Gets the value() element/field out of an InvalidFormat annotation. * - * @return the types of the arguments to the call + * @param anno an InvalidFormat annotation + * @return its value() element/field */ - public final Result[] getArgTypes() { - // One to suppress warning in javac, the other to suppress warning in Eclipse... - @SuppressWarnings({"rawtypes", "unchecked"}) - Result[] res = new Result[args.size()]; - for (int i = 0; i < res.length; ++i) { - ExpressionTree arg = args.get(i); - TypeMirror argType; - if (TreeUtils.isNullExpression(arg)) { - argType = atypeFactory.getProcessingEnv().getTypeUtils().getNullType(); - } else { - argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); - } - res[i] = new Result<>(argType, arg); - } - return res; + private String getInvalidFormatValue(AnnotationMirror anno) { + return (String) anno.getElementValues().get(invalidFormatValueElement).getValue(); } /** - * Checks if the type of an argument returned from {@link #getArgTypes()} is valid for the - * passed ConversionCategory. + * Takes a syntax tree element that represents a {@link InvalidFormat} annotation, and returns + * its value. * - * @param formatCat a format specifier - * @param argType an argument type - * @return true if the argument can be passed to the format specifier + * @param anno an InvalidFormat annotation + * @return its value() element/field */ - public final boolean isValidArgument(ConversionCategory formatCat, TypeMirror argType) { - if (argType.getKind() == TypeKind.NULL || isArgumentNull(argType)) { - return true; - } - Class type = TypesUtils.getClassFromType(argType); - return formatCat.isAssignableFrom(type); + public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { + return "\"" + getInvalidFormatValue(anno) + "\""; } /** - * Checks if the argument returned from {@link #getArgTypes()} is a {@code null} expression. + * Creates a {@code @}{@link Format} annotation with the given list as its value. * - * @param type a type - * @return true if the argument is a {@code null} expression + * @param args conversion categories for the {@code @Format} annotation + * @return a {@code @}{@link Format} annotation with the given list as its value */ - public final boolean isArgumentNull(TypeMirror type) { - // TODO: Just check whether it is the VOID TypeMirror. - - // is it the null literal - return type.accept( - new SimpleTypeVisitor8>() { - @Override - protected Boolean defaultAction(TypeMirror e, Class p) { - // it's not the null literal - return false; - } + public AnnotationMirror categoriesToFormatAnnotation(ConversionCategory[] args) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Format.class); + builder.setValue("value", args); + return builder.build(); + } - @Override - public Boolean visitNull(NullType t, Class p) { - // it's the null literal - return true; - } - }, - Void.TYPE); + /** + * Returns the value of a {@code @}{@link Format} annotation. + * + * @param anno a {@code @}{@link Format} annotation + * @return the annotation's {@code value} element + */ + @SuppressWarnings("GetClassOnEnum") + public ConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { + return AnnotationUtils.getElementValueEnumArray( + anno, formatValueElement, ConversionCategory.class); } - } - - // The failure() method is required so that FormatterTransfer, which has no access to the - // FormatterChecker, can report errors. - /** - * Reports an error. - * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message - */ - public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportError(res.location, msgKey, args); - } - - /** - * Reports a warning. - * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message - */ - public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportWarning(res.location, msgKey, args); - } - - /** - * Takes an exception that describes an invalid formatter string and, returns a syntax trees - * element that represents a {@link InvalidFormat} annotation with the exception's error message - * as value. - */ - public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalFormatException ex) { - return stringToInvalidFormatAnnotation(ex.getMessage()); - } - - /** - * Creates an {@link InvalidFormat} annotation with the given string as its value. - * - * @param invalidFormatString an invalid formatter string - * @return an {@link InvalidFormat} annotation with the given string as its value - */ - /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation(String invalidFormatString) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", invalidFormatString); - return builder.build(); - } - - /** - * Gets the value() element/field out of an InvalidFormat annotation. - * - * @param anno an InvalidFormat annotation - * @return its value() element/field - */ - private String getInvalidFormatValue(AnnotationMirror anno) { - return (String) anno.getElementValues().get(invalidFormatValueElement).getValue(); - } - - /** - * Takes a syntax tree element that represents a {@link InvalidFormat} annotation, and returns its - * value. - * - * @param anno an InvalidFormat annotation - * @return its value() element/field - */ - public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { - return "\"" + getInvalidFormatValue(anno) + "\""; - } - - /** - * Creates a {@code @}{@link Format} annotation with the given list as its value. - * - * @param args conversion categories for the {@code @Format} annotation - * @return a {@code @}{@link Format} annotation with the given list as its value - */ - public AnnotationMirror categoriesToFormatAnnotation(ConversionCategory[] args) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Format.class); - builder.setValue("value", args); - return builder.build(); - } - - /** - * Returns the value of a {@code @}{@link Format} annotation. - * - * @param anno a {@code @}{@link Format} annotation - * @return the annotation's {@code value} element - */ - @SuppressWarnings("GetClassOnEnum") - public ConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { - return AnnotationUtils.getElementValueEnumArray( - anno, formatValueElement, ConversionCategory.class); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java index 15ccc76c09a..241954912e4 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java @@ -6,12 +6,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.FormatterTreeUtil.FormatCall; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; @@ -29,6 +24,14 @@ import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.UserError; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /* NO-AFU import org.checkerframework.common.wholeprograminference.WholeProgramInference; */ @@ -40,263 +43,276 @@ * @checker_framework.manual #formatter-guarantees Format String Checker */ public class FormatterVisitor extends BaseTypeVisitor { - public FormatterVisitor(BaseTypeChecker checker) { - super(checker); - } + public FormatterVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); - if (atypeFactory.getDeclAnnotation(methodElement, FormatMethod.class) != null) { - int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); - if (formatStringIndex == -1) { - checker.reportError(tree, "format.method.invalid", methodElement.getSimpleName()); - } + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); + if (atypeFactory.getDeclAnnotation(methodElement, FormatMethod.class) != null) { + int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement); + if (formatStringIndex == -1) { + checker.reportError(tree, "format.method.invalid", methodElement.getSimpleName()); + } + } + return super.visitMethod(tree, p); } - return super.visitMethod(tree, p); - } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - FormatterTreeUtil ftu = atypeFactory.treeUtil; - FormatCall fc = ftu.create(tree, atypeFactory); - if (fc != null) { - MethodTree enclosingMethod = - TreePathUtil.enclosingMethod(atypeFactory.getPath(fc.invocationTree)); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + FormatterTreeUtil ftu = atypeFactory.treeUtil; + FormatCall fc = ftu.create(tree, atypeFactory); + if (fc != null) { + MethodTree enclosingMethod = + TreePathUtil.enclosingMethod(atypeFactory.getPath(fc.invocationTree)); - Result errMissingFormat = fc.errMissingFormatAnnotation(); - if (errMissingFormat != null) { - // The string's type has no @Format annotation. - if (isWrappedFormatCall(fc, enclosingMethod)) { - // Nothing to do, because call is legal. - } else { - // I.1 - ftu.failure(errMissingFormat, "format.string.invalid", errMissingFormat.value()); - } - } else { - // The string has a @Format annotation. - Result invc = fc.getInvocationType(); - ConversionCategory[] formatCats = fc.getFormatCategories(); - switch (invc.value()) { - case VARARG: - Result[] argTypes = fc.getArgTypes(); - int argl = argTypes.length; - int formatl = formatCats.length; - if (argl < formatl) { - // For assignments, format.missing.arguments is issued from - // commonAssignmentCheck(). - // II.1 - ftu.failure(invc, "format.missing.arguments", formatl, argl); + Result errMissingFormat = fc.errMissingFormatAnnotation(); + if (errMissingFormat != null) { + // The string's type has no @Format annotation. + if (isWrappedFormatCall(fc, enclosingMethod)) { + // Nothing to do, because call is legal. + } else { + // I.1 + ftu.failure( + errMissingFormat, "format.string.invalid", errMissingFormat.value()); + } } else { - if (argl > formatl) { - // II.2 - ftu.warning(invc, "format.excess.arguments", formatl, argl); - } - for (int i = 0; i < formatl; ++i) { - ConversionCategory formatCat = formatCats[i]; - Result arg = argTypes[i]; - TypeMirror argType = arg.value(); + // The string has a @Format annotation. + Result invc = fc.getInvocationType(); + ConversionCategory[] formatCats = fc.getFormatCategories(); + switch (invc.value()) { + case VARARG: + Result[] argTypes = fc.getArgTypes(); + int argl = argTypes.length; + int formatl = formatCats.length; + if (argl < formatl) { + // For assignments, format.missing.arguments is issued from + // commonAssignmentCheck(). + // II.1 + ftu.failure(invc, "format.missing.arguments", formatl, argl); + } else { + if (argl > formatl) { + // II.2 + ftu.warning(invc, "format.excess.arguments", formatl, argl); + } + for (int i = 0; i < formatl; ++i) { + ConversionCategory formatCat = formatCats[i]; + Result arg = argTypes[i]; + TypeMirror argType = arg.value(); - switch (formatCat) { - case UNUSED: - // I.2 - ftu.warning(arg, "format.argument.unused", " " + (1 + i)); - break; - case NULL: - // I.3 - if (argType.getKind() == TypeKind.NULL) { - ftu.warning(arg, "format.specifier.null", " " + (1 + i)); - } else { - ftu.failure(arg, "format.specifier.null", " " + (1 + i)); - } - break; - case GENERAL: - break; - default: - if (!fc.isValidArgument(formatCat, argType)) { - // II.3 - ExecutableElement method = TreeUtils.elementFromUse(tree); - CharSequence methodName = ElementUtils.getSimpleDescription(method); - ftu.failure( - arg, - "argument.type.incompatible", - "in varargs position", - methodName, - argType, - formatCat); - } - break; + switch (formatCat) { + case UNUSED: + // I.2 + ftu.warning(arg, "format.argument.unused", " " + (1 + i)); + break; + case NULL: + // I.3 + if (argType.getKind() == TypeKind.NULL) { + ftu.warning( + arg, "format.specifier.null", " " + (1 + i)); + } else { + ftu.failure( + arg, "format.specifier.null", " " + (1 + i)); + } + break; + case GENERAL: + break; + default: + if (!fc.isValidArgument(formatCat, argType)) { + // II.3 + ExecutableElement method = + TreeUtils.elementFromUse(tree); + CharSequence methodName = + ElementUtils.getSimpleDescription(method); + ftu.failure( + arg, + "argument.type.incompatible", + "in varargs position", + methodName, + argType, + formatCat); + } + break; + } + } + } + break; + case ARRAY: + // III + if (!isWrappedFormatCall(fc, enclosingMethod)) { + ftu.warning(invc, "format.indirect.arguments"); + } + // TODO: If it is explict array construction, such as "new Object[] { + // ... }", then we could treat it like the VARARGS case, analyzing each + // argument. "new array" is probably rare, in the varargs position. + // fall through + case NULLARRAY: + for (ConversionCategory cat : formatCats) { + if (cat == ConversionCategory.NULL) { + // I.3 + if (invc.value() == FormatterTreeUtil.InvocationType.NULLARRAY) { + ftu.warning(invc, "format.specifier.null", ""); + } else { + ftu.failure(invc, "format.specifier.null", ""); + } + } + if (cat == ConversionCategory.UNUSED) { + // I.2 + ftu.warning(invc, "format.argument.unused", ""); + } + } + break; } - } } - break; - case ARRAY: - // III - if (!isWrappedFormatCall(fc, enclosingMethod)) { - ftu.warning(invc, "format.indirect.arguments"); - } - // TODO: If it is explict array construction, such as "new Object[] { - // ... }", then we could treat it like the VARARGS case, analyzing each - // argument. "new array" is probably rare, in the varargs position. - // fall through - case NULLARRAY: - for (ConversionCategory cat : formatCats) { - if (cat == ConversionCategory.NULL) { - // I.3 - if (invc.value() == FormatterTreeUtil.InvocationType.NULLARRAY) { - ftu.warning(invc, "format.specifier.null", ""); - } else { - ftu.failure(invc, "format.specifier.null", ""); - } - } - if (cat == ConversionCategory.UNUSED) { - // I.2 - ftu.warning(invc, "format.argument.unused", ""); - } - } - break; - } - } - /* NO-AFU - // Support -Ainfer command-line argument. - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - if (wpi != null && forwardsArguments(tree, enclosingMethod)) { - wpi.addMethodDeclarationAnnotation( - TreeUtils.elementFromDeclaration(enclosingMethod), - atypeFactory.FORMATMETHOD); - } - */ + /* NO-AFU + // Support -Ainfer command-line argument. + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + if (wpi != null && forwardsArguments(tree, enclosingMethod)) { + wpi.addMethodDeclarationAnnotation( + TreeUtils.elementFromDeclaration(enclosingMethod), + atypeFactory.FORMATMETHOD); + } + */ + } + return super.visitMethodInvocation(tree, p); } - return super.visitMethodInvocation(tree, p); - } - /** - * Returns true if {@code fc} is within a method m annotated as {@code @FormatMethod}, and fc's - * arguments are m's formal parameters. In other words, fc forwards m's arguments to another - * format method. - * - * @param fc an invocation of a format method - * @param enclosingMethod the method that contains the call - * @return true if {@code fc} is a call to a format method that forwards its containing method's - * arguments - */ - private boolean isWrappedFormatCall(FormatCall fc, @Nullable MethodTree enclosingMethod) { - if (enclosingMethod == null) { - return false; + /** + * Returns true if {@code fc} is within a method m annotated as {@code @FormatMethod}, and fc's + * arguments are m's formal parameters. In other words, fc forwards m's arguments to another + * format method. + * + * @param fc an invocation of a format method + * @param enclosingMethod the method that contains the call + * @return true if {@code fc} is a call to a format method that forwards its containing method's + * arguments + */ + private boolean isWrappedFormatCall(FormatCall fc, @Nullable MethodTree enclosingMethod) { + if (enclosingMethod == null) { + return false; + } + ExecutableElement enclosingMethodElement = + TreeUtils.elementFromDeclaration(enclosingMethod); + boolean withinFormatMethod = + (atypeFactory.getDeclAnnotation(enclosingMethodElement, FormatMethod.class) + != null); + return withinFormatMethod && forwardsArguments(fc.invocationTree, enclosingMethod); } - ExecutableElement enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); - boolean withinFormatMethod = - (atypeFactory.getDeclAnnotation(enclosingMethodElement, FormatMethod.class) != null); - return withinFormatMethod && forwardsArguments(fc.invocationTree, enclosingMethod); - } - /** - * Returns true if {@code invocationTree}'s arguments are {@code enclosingMethod}'s formal - * parameters. In other words, {@code invocationTree} forwards {@code enclosingMethod}'s - * arguments. - * - *

          Only arguments from the first String formal parameter onward count. Returns false if there - * is no String formal parameter. - * - * @param invocationTree an invocation of a method - * @param enclosingMethod the method that contains the call - * @return true if {@code invocationTree} is a call to a method that forwards its containing - * method's arguments - */ - private boolean forwardsArguments( - MethodInvocationTree invocationTree, @Nullable MethodTree enclosingMethod) { + /** + * Returns true if {@code invocationTree}'s arguments are {@code enclosingMethod}'s formal + * parameters. In other words, {@code invocationTree} forwards {@code enclosingMethod}'s + * arguments. + * + *

          Only arguments from the first String formal parameter onward count. Returns false if there + * is no String formal parameter. + * + * @param invocationTree an invocation of a method + * @param enclosingMethod the method that contains the call + * @return true if {@code invocationTree} is a call to a method that forwards its containing + * method's arguments + */ + private boolean forwardsArguments( + MethodInvocationTree invocationTree, @Nullable MethodTree enclosingMethod) { - if (enclosingMethod == null) { - return false; - } + if (enclosingMethod == null) { + return false; + } - ExecutableElement enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); - int paramIndex = formatStringIndex(enclosingMethodElement); - if (paramIndex == -1) { - return false; - } + ExecutableElement enclosingMethodElement = + TreeUtils.elementFromDeclaration(enclosingMethod); + int paramIndex = formatStringIndex(enclosingMethodElement); + if (paramIndex == -1) { + return false; + } - ExecutableElement calledMethodElement = TreeUtils.elementFromUse(invocationTree); - int callIndex = formatStringIndex(calledMethodElement); - if (callIndex == -1) { - throw new UserError( - "Method " - + calledMethodElement - + " is annotated @FormatMethod but has no String formal parameter"); - } + ExecutableElement calledMethodElement = TreeUtils.elementFromUse(invocationTree); + int callIndex = formatStringIndex(calledMethodElement); + if (callIndex == -1) { + throw new UserError( + "Method " + + calledMethodElement + + " is annotated @FormatMethod but has no String formal parameter"); + } - List args = invocationTree.getArguments(); - List params = enclosingMethod.getParameters(); + List args = invocationTree.getArguments(); + List params = enclosingMethod.getParameters(); - if (params.size() - paramIndex != args.size() - callIndex) { - return false; - } - while (paramIndex < params.size()) { - ExpressionTree argTree = args.get(callIndex); - if (argTree.getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - VariableTree param = params.get(paramIndex); - if (param.getName() != ((IdentifierTree) argTree).getName()) { - return false; - } - paramIndex++; - callIndex++; - } + if (params.size() - paramIndex != args.size() - callIndex) { + return false; + } + while (paramIndex < params.size()) { + ExpressionTree argTree = args.get(callIndex); + if (argTree.getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + VariableTree param = params.get(paramIndex); + if (param.getName() != ((IdentifierTree) argTree).getName()) { + return false; + } + paramIndex++; + callIndex++; + } - return true; - } + return true; + } - // TODO: Should this be the last String argument? That would require that every method - // annotated with @FormatMethod uses varargs syntax. - /** - * Returns the index of the format string of a method: the first formal parameter with declared - * type String. - * - * @param m a method - * @return the index of the last String formal parameter, or -1 if none - */ - public static int formatStringIndex(ExecutableElement m) { - List params = m.getParameters(); - for (int i = 0; i < params.size(); i++) { - if (TypesUtils.isString(params.get(i).asType())) { - return i; - } + // TODO: Should this be the last String argument? That would require that every method + // annotated with @FormatMethod uses varargs syntax. + /** + * Returns the index of the format string of a method: the first formal parameter with declared + * type String. + * + * @param m a method + * @return the index of the last String formal parameter, or -1 if none + */ + public static int formatStringIndex(ExecutableElement m) { + List params = m.getParameters(); + for (int i = 0; i < params.size(); i++) { + if (TypesUtils.isString(params.get(i).asType())) { + return i; + } + } + return -1; } - return -1; - } - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); - AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); + AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); + AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); - // From the manual: "It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued." - // The format.missing.arguments warning is issued here for assignments. - // For method calls, it is issued in visitMethodInvocation. - if (rhs != null - && lhs != null - && AnnotationUtils.areSameByName(rhs, FormatterAnnotatedTypeFactory.FORMAT_NAME) - && AnnotationUtils.areSameByName(lhs, FormatterAnnotatedTypeFactory.FORMAT_NAME)) { - ConversionCategory[] rhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(rhs); - ConversionCategory[] lhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(lhs); + // From the manual: "It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued." + // The format.missing.arguments warning is issued here for assignments. + // For method calls, it is issued in visitMethodInvocation. + if (rhs != null + && lhs != null + && AnnotationUtils.areSameByName(rhs, FormatterAnnotatedTypeFactory.FORMAT_NAME) + && AnnotationUtils.areSameByName(lhs, FormatterAnnotatedTypeFactory.FORMAT_NAME)) { + ConversionCategory[] rhsArgTypes = + atypeFactory.treeUtil.formatAnnotationToCategories(rhs); + ConversionCategory[] lhsArgTypes = + atypeFactory.treeUtil.formatAnnotationToCategories(lhs); - if (rhsArgTypes.length < lhsArgTypes.length) { - checker.reportWarning( - valueTree, "format.missing.arguments", varType.toString(), valueType.toString()); - result = false; - } + if (rhsArgTypes.length < lhsArgTypes.length) { + checker.reportWarning( + valueTree, + "format.missing.arguments", + varType.toString(), + valueType.toString()); + result = false; + } + } + return result; } - return result; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java b/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java index 907f511a116..dd07607b67a 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/Effect.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.guieffect; -import java.lang.annotation.Annotation; import org.checkerframework.checker.guieffect.qual.PolyUIEffect; import org.checkerframework.checker.guieffect.qual.SafeEffect; import org.checkerframework.checker.guieffect.qual.UIEffect; @@ -8,118 +7,120 @@ import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; +import java.lang.annotation.Annotation; + /** An effect -- either UIEffect, PolyUIEffect, or SafeEffect. */ public final class Effect { - // Colin hates Java's comparable interface, so he's not using it - - private final Class annotClass; - - /** - * Create a new Effect object. - * - * @param cls one of UIEffect.class, PolyUIEffect.class, or SafeEffect.class - */ - public Effect(Class cls) { - assert cls == UIEffect.class || cls == PolyUIEffect.class || cls == SafeEffect.class; - annotClass = cls; - } - - /** - * Return true iff {@code left} is less than or equal to {@code right}. - * - * @param left the first effect to compare - * @param right the first effect to compare - * @return true iff {@code left} is less than or equal to {@code right} - */ - public static boolean lessThanOrEqualTo(Effect left, Effect right) { - assert (left != null && right != null); - boolean leftBottom = left.annotClass == SafeEffect.class; - boolean rightTop = right.annotClass == UIEffect.class; - return leftBottom || rightTop || left.annotClass == right.annotClass; - } - - public static Effect min(Effect l, Effect r) { - if (lessThanOrEqualTo(l, r)) { - return l; - } else { - return r; + // Colin hates Java's comparable interface, so he's not using it + + private final Class annotClass; + + /** + * Create a new Effect object. + * + * @param cls one of UIEffect.class, PolyUIEffect.class, or SafeEffect.class + */ + public Effect(Class cls) { + assert cls == UIEffect.class || cls == PolyUIEffect.class || cls == SafeEffect.class; + annotClass = cls; } - } - public static final class EffectRange { - public final Effect min; - public final Effect max; + /** + * Return true iff {@code left} is less than or equal to {@code right}. + * + * @param left the first effect to compare + * @param right the first effect to compare + * @return true iff {@code left} is less than or equal to {@code right} + */ + public static boolean lessThanOrEqualTo(Effect left, Effect right) { + assert (left != null && right != null); + boolean leftBottom = left.annotClass == SafeEffect.class; + boolean rightTop = right.annotClass == UIEffect.class; + return leftBottom || rightTop || left.annotClass == right.annotClass; + } - public EffectRange(@Nullable Effect min, @Nullable Effect max) { - assert (min != null || max != null); - // If one is null, fill in with the other - this.min = (min != null ? min : max); - this.max = (max != null ? max : min); + public static Effect min(Effect l, Effect r) { + if (lessThanOrEqualTo(l, r)) { + return l; + } else { + return r; + } } - } - - /** - * Return true if this is SafeEffect. - * - * @return true if this is SafeEffect - */ - public boolean isSafe() { - return annotClass == SafeEffect.class; - } - - /** - * Return true if this is UIEffect. - * - * @return true if this is UIEffect - */ - public boolean isUI() { - return annotClass == UIEffect.class; - } - - /** - * Return true if this is PolyUIEffect. - * - * @return true if this is PolyUIEffect - */ - @Pure - public boolean isPoly() { - return annotClass == PolyUIEffect.class; - } - - public Class getAnnot() { - return annotClass; - } - - @SideEffectFree - @Override - public String toString() { - return annotClass.getSimpleName(); - } - - /** - * Return true if this equals the given effect. - * - * @param e the effect to compare this to - * @return true if this equals the given effect - */ - @SuppressWarnings("NonOverridingEquals") // TODO: clean this up! - public boolean equals(Effect e) { - return annotClass == e.annotClass; - } - - @Override - @SuppressWarnings("interning:not.interned") // equality - public boolean equals(@Nullable Object o) { - if (o instanceof Effect) { - return this.equals((Effect) o); - } else { - return this == o; + + public static final class EffectRange { + public final Effect min; + public final Effect max; + + public EffectRange(@Nullable Effect min, @Nullable Effect max) { + assert (min != null || max != null); + // If one is null, fill in with the other + this.min = (min != null ? min : max); + this.max = (max != null ? max : min); + } } - } - @Pure - @Override - public int hashCode() { - return 31 + annotClass.hashCode(); - } + /** + * Return true if this is SafeEffect. + * + * @return true if this is SafeEffect + */ + public boolean isSafe() { + return annotClass == SafeEffect.class; + } + + /** + * Return true if this is UIEffect. + * + * @return true if this is UIEffect + */ + public boolean isUI() { + return annotClass == UIEffect.class; + } + + /** + * Return true if this is PolyUIEffect. + * + * @return true if this is PolyUIEffect + */ + @Pure + public boolean isPoly() { + return annotClass == PolyUIEffect.class; + } + + public Class getAnnot() { + return annotClass; + } + + @SideEffectFree + @Override + public String toString() { + return annotClass.getSimpleName(); + } + + /** + * Return true if this equals the given effect. + * + * @param e the effect to compare this to + * @return true if this equals the given effect + */ + @SuppressWarnings("NonOverridingEquals") // TODO: clean this up! + public boolean equals(Effect e) { + return annotClass == e.annotClass; + } + + @Override + @SuppressWarnings("interning:not.interned") // equality + public boolean equals(@Nullable Object o) { + if (o instanceof Effect) { + return this.equals((Effect) o); + } else { + return this == o; + } + } + + @Pure + @Override + public int hashCode() { + return 31 + annotClass.hashCode(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java index 6eb98ab88b3..0e21ef50b93 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java @@ -8,16 +8,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.ParenthesizedTree; import com.sun.source.tree.Tree; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.guieffect.Effect.EffectRange; import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.PolyUI; @@ -43,593 +34,611 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** Annotated type factory for the GUI Effect Checker. */ public class GuiEffectTypeFactory extends BaseAnnotatedTypeFactory { - protected final boolean debugSpew; - - /** - * Keeps track of all lambda expressions with inferred UIEffect. - * - *

          {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} adds lambda - * expressions to this set, and is called from GuiEffectVisitor whenever a lambda expression calls - * a @UIEffect method. Afterwards {@link - * #getInferedEffectForLambdaExpression(LambdaExpressionTree) getInferedEffectForLambdaExpression} - * uses this set and the type annotations of the functional interface of the lambda to figure out - * if it can affect the UI or not. - */ - protected final Set uiLambdas = new HashSet<>(); - - /** - * Keeps track of all anonymous inner classes with inferred UIEffect. - * - *

          {@link #constrainAnonymousClassToUI(TypeElement) constrainAnonymousClassToUI} adds anonymous - * inner classes to this set, and is called from GuiEffectVisitor whenever an anonymous inner - * class calls a @UIEffect method. Afterwards {@link #isUIType(TypeElement) isUIType} and {@link - * #getAnnotatedType(Tree) getAnnotatedType} will treat this inner class as if it had been - * annotated with @UI. - */ - protected final Set uiAnonClasses = new HashSet<>(); - - /** The @{@link AlwaysSafe} annotation. */ - protected final AnnotationMirror ALWAYSSAFE = - AnnotationBuilder.fromClass(elements, AlwaysSafe.class); - - /** The @{@link PolyUI} annotation. */ - protected final AnnotationMirror POLYUI = AnnotationBuilder.fromClass(elements, PolyUI.class); - - /** The @{@link UI} annotation. */ - protected final AnnotationMirror UI = AnnotationBuilder.fromClass(elements, UI.class); - - public GuiEffectTypeFactory(BaseTypeChecker checker, boolean spew) { - // use true to enable flow inference, false to disable it - super(checker, false); - - debugSpew = spew; - this.postInit(); - } - - /** - * Returns true if the given type is polymorphic. - * - * @param cls the type to test - * @return true if the given type is polymorphic - */ - public boolean isPolymorphicType(TypeElement cls) { - assert (cls != null); - return getDeclAnnotation(cls, PolyUIType.class) != null - || fromElement(cls).hasAnnotation(PolyUI.class); - } - - public boolean isUIType(TypeElement cls) { - if (debugSpew) { - System.err.println(" isUIType(" + cls + ")"); + protected final boolean debugSpew; + + /** + * Keeps track of all lambda expressions with inferred UIEffect. + * + *

          {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} adds lambda + * expressions to this set, and is called from GuiEffectVisitor whenever a lambda expression + * calls a @UIEffect method. Afterwards {@link + * #getInferedEffectForLambdaExpression(LambdaExpressionTree) + * getInferedEffectForLambdaExpression} uses this set and the type annotations of the functional + * interface of the lambda to figure out if it can affect the UI or not. + */ + protected final Set uiLambdas = new HashSet<>(); + + /** + * Keeps track of all anonymous inner classes with inferred UIEffect. + * + *

          {@link #constrainAnonymousClassToUI(TypeElement) constrainAnonymousClassToUI} adds + * anonymous inner classes to this set, and is called from GuiEffectVisitor whenever an + * anonymous inner class calls a @UIEffect method. Afterwards {@link #isUIType(TypeElement) + * isUIType} and {@link #getAnnotatedType(Tree) getAnnotatedType} will treat this inner class as + * if it had been annotated with @UI. + */ + protected final Set uiAnonClasses = new HashSet<>(); + + /** The @{@link AlwaysSafe} annotation. */ + protected final AnnotationMirror ALWAYSSAFE = + AnnotationBuilder.fromClass(elements, AlwaysSafe.class); + + /** The @{@link PolyUI} annotation. */ + protected final AnnotationMirror POLYUI = AnnotationBuilder.fromClass(elements, PolyUI.class); + + /** The @{@link UI} annotation. */ + protected final AnnotationMirror UI = AnnotationBuilder.fromClass(elements, UI.class); + + public GuiEffectTypeFactory(BaseTypeChecker checker, boolean spew) { + // use true to enable flow inference, false to disable it + super(checker, false); + + debugSpew = spew; + this.postInit(); } - boolean targetClassUIP = fromElement(cls).hasAnnotation(UI.class); - AnnotationMirror targetClassUITypeP = getDeclAnnotation(cls, UIType.class); - AnnotationMirror targetClassSafeTypeP = getDeclAnnotation(cls, SafeType.class); - if (targetClassSafeTypeP != null) { - return false; // explicitly marked not a UI type + /** + * Returns true if the given type is polymorphic. + * + * @param cls the type to test + * @return true if the given type is polymorphic + */ + public boolean isPolymorphicType(TypeElement cls) { + assert (cls != null); + return getDeclAnnotation(cls, PolyUIType.class) != null + || fromElement(cls).hasAnnotation(PolyUI.class); } - boolean hasUITypeDirectly = (targetClassUIP || targetClassUITypeP != null); + public boolean isUIType(TypeElement cls) { + if (debugSpew) { + System.err.println(" isUIType(" + cls + ")"); + } + boolean targetClassUIP = fromElement(cls).hasAnnotation(UI.class); + AnnotationMirror targetClassUITypeP = getDeclAnnotation(cls, UIType.class); + AnnotationMirror targetClassSafeTypeP = getDeclAnnotation(cls, SafeType.class); - if (hasUITypeDirectly) { - return true; - } + if (targetClassSafeTypeP != null) { + return false; // explicitly marked not a UI type + } - // Anon inner classes should not inherit the package annotation, since - // they're so often used for closures to run async on background threads. - if (isAnonymousType(cls)) { - // However, we need to look into Anonymous class effect inference - if (uiAnonClasses.contains(cls)) { - return true; - } - return false; - } + boolean hasUITypeDirectly = (targetClassUIP || targetClassUITypeP != null); - // We don't check polymorphic annos so we can make a couple methods of - // an @UIType polymorphic explicitly - // AnnotationMirror targetClassPolyP = getDeclAnnotation(cls, PolyUI.class); - // AnnotationMirror targetClassPolyTypeP = getDeclAnnotation(cls, PolyUIType.class); - boolean targetClassSafeP = fromElement(cls).hasAnnotation(AlwaysSafe.class); - if (targetClassSafeP) { - return false; // explicitly annotated otherwise - } + if (hasUITypeDirectly) { + return true; + } - // Look for the package - Element packageP = ElementUtils.enclosingPackage(cls); + // Anon inner classes should not inherit the package annotation, since + // they're so often used for closures to run async on background threads. + if (isAnonymousType(cls)) { + // However, we need to look into Anonymous class effect inference + if (uiAnonClasses.contains(cls)) { + return true; + } + return false; + } - if (packageP != null) { - if (debugSpew) { - System.err.println("Found package " + packageP); - } - if (getDeclAnnotation(packageP, UIPackage.class) != null) { - if (debugSpew) { - System.err.println("Package " + packageP + " is annotated @UIPackage"); + // We don't check polymorphic annos so we can make a couple methods of + // an @UIType polymorphic explicitly + // AnnotationMirror targetClassPolyP = getDeclAnnotation(cls, PolyUI.class); + // AnnotationMirror targetClassPolyTypeP = getDeclAnnotation(cls, PolyUIType.class); + boolean targetClassSafeP = fromElement(cls).hasAnnotation(AlwaysSafe.class); + if (targetClassSafeP) { + return false; // explicitly annotated otherwise } - return true; - } - } - return false; - } - - // TODO: is there a framework method for this? - private static boolean isAnonymousType(TypeElement elem) { - return elem.getSimpleName().length() == 0; - } - - /** - * Calling context annotations. - * - *

          To make anon-inner-classes work, I need to climb the inheritance DAG, until I: - * - *

            - *
          • find the class/interface that declares this calling method (an anon inner class is a - * separate class that implements an interface) - *
          • check whether *that* declaration specifies @UI on either the type or method - *
          - * - * A method has the UI effect when: - * - *
            - *
          1. A method is UI if annotated @UIEffect - *
          2. A method is UI if the enclosing class is annotated @UI or @UIType and the method is not - * annotated @AlwaysSafe - *
          3. A method is UI if the corresponding method in the super-class/interface is UI, and this - * method is not annotated @AlwaysSafe, and this method resides in an anonymous inner class - * (named classes still require a package/class/method annotation to make it UI, only anon - * inner classes have this inheritance-by-default) - *
              - *
            • A method must be *annotated* UI if the method it overrides is *annotated* UI - *
            • A method must be *annotated* UI if it overrides a UI method and the enclosing class - * is not UI - *
            - *
          4. It is an error if a method is UI but the same method in a super-type is not UI - *
          5. It is an error if two super-types specify the same method, where one type says it's UI - * and one says it's not (it's possible to simply enforce the weaker (safe) effect, but this - * seems more principled, it's easier --- backwards-compatible --- to change our minds about - * this later) - *
          - */ - public Effect getDeclaredEffect(ExecutableElement methodElt) { - if (debugSpew) { - System.err.println("begin mayHaveUIEffect(" + methodElt + ")"); - } - AnnotationMirror targetUIP = getDeclAnnotation(methodElt, UIEffect.class); - AnnotationMirror targetSafeP = getDeclAnnotation(methodElt, SafeEffect.class); - AnnotationMirror targetPolyP = getDeclAnnotation(methodElt, PolyUIEffect.class); - TypeElement targetClassElt = (TypeElement) methodElt.getEnclosingElement(); - boolean hasMainThreadAnnot = - getDeclAnnotations(methodElt).toString().contains("@android.support.annotation.MainThread"); - - if (debugSpew) { - System.err.println("targetClassElt found"); + // Look for the package + Element packageP = ElementUtils.enclosingPackage(cls); + + if (packageP != null) { + if (debugSpew) { + System.err.println("Found package " + packageP); + } + if (getDeclAnnotation(packageP, UIPackage.class) != null) { + if (debugSpew) { + System.err.println("Package " + packageP + " is annotated @UIPackage"); + } + return true; + } + } + + return false; } - // Short-circuit if the method is explicitly annotated - if (targetSafeP != null) { - if (debugSpew) { - System.err.println("Method marked @SafeEffect"); - } - return new Effect(SafeEffect.class); - } else if (targetUIP != null || hasMainThreadAnnot) { - if (debugSpew) { - System.err.println("Method marked @UIEffect"); - } - return new Effect(UIEffect.class); - } else if (targetPolyP != null) { - if (debugSpew) { - System.err.println("Method marked @PolyUIEffect"); - } - return new Effect(PolyUIEffect.class); + // TODO: is there a framework method for this? + private static boolean isAnonymousType(TypeElement elem) { + return elem.getSimpleName().length() == 0; } - // The method is not explicitly annotated, so check class and package annotations, - // and supertype effects if in an anonymous inner class + /** + * Calling context annotations. + * + *

          To make anon-inner-classes work, I need to climb the inheritance DAG, until I: + * + *

            + *
          • find the class/interface that declares this calling method (an anon inner class is a + * separate class that implements an interface) + *
          • check whether *that* declaration specifies @UI on either the type or method + *
          + * + * A method has the UI effect when: + * + *
            + *
          1. A method is UI if annotated @UIEffect + *
          2. A method is UI if the enclosing class is annotated @UI or @UIType and the method is not + * annotated @AlwaysSafe + *
          3. A method is UI if the corresponding method in the super-class/interface is UI, and this + * method is not annotated @AlwaysSafe, and this method resides in an anonymous inner + * class (named classes still require a package/class/method annotation to make it UI, + * only anon inner classes have this inheritance-by-default) + *
              + *
            • A method must be *annotated* UI if the method it overrides is *annotated* UI + *
            • A method must be *annotated* UI if it overrides a UI method and the enclosing + * class is not UI + *
            + *
          4. It is an error if a method is UI but the same method in a super-type is not UI + *
          5. It is an error if two super-types specify the same method, where one type says it's UI + * and one says it's not (it's possible to simply enforce the weaker (safe) effect, but + * this seems more principled, it's easier --- backwards-compatible --- to change our + * minds about this later) + *
          + */ + public Effect getDeclaredEffect(ExecutableElement methodElt) { + if (debugSpew) { + System.err.println("begin mayHaveUIEffect(" + methodElt + ")"); + } + AnnotationMirror targetUIP = getDeclAnnotation(methodElt, UIEffect.class); + AnnotationMirror targetSafeP = getDeclAnnotation(methodElt, SafeEffect.class); + AnnotationMirror targetPolyP = getDeclAnnotation(methodElt, PolyUIEffect.class); + TypeElement targetClassElt = (TypeElement) methodElt.getEnclosingElement(); + boolean hasMainThreadAnnot = + getDeclAnnotations(methodElt) + .toString() + .contains("@android.support.annotation.MainThread"); - if (isUIType(targetClassElt)) { - // Already checked, no explicit @SafeEffect annotation - return new Effect(UIEffect.class); - } + if (debugSpew) { + System.err.println("targetClassElt found"); + } - // Anonymous inner types should just get the effect of the parent by default, rather than - // annotating every instance. Unless it's implementing a polymorphic supertype, in which - // case we still want the developer to be explicit. - if (isAnonymousType(targetClassElt)) { - boolean canInheritParentEffects = true; // Refine this for polymorphic parents - DeclaredType directSuper = (DeclaredType) targetClassElt.getSuperclass(); - TypeElement superElt = (TypeElement) directSuper.asElement(); - // Anonymous subtypes of polymorphic classes other than object can't inherit - if (getDeclAnnotation(superElt, PolyUIType.class) != null - && !TypesUtils.isObject(directSuper)) { - canInheritParentEffects = false; - } else { - for (TypeMirror ifaceM : targetClassElt.getInterfaces()) { - DeclaredType iface = (DeclaredType) ifaceM; - TypeElement ifaceElt = (TypeElement) iface.asElement(); - if (getDeclAnnotation(ifaceElt, PolyUIType.class) != null) { - canInheritParentEffects = false; - } + // Short-circuit if the method is explicitly annotated + if (targetSafeP != null) { + if (debugSpew) { + System.err.println("Method marked @SafeEffect"); + } + return new Effect(SafeEffect.class); + } else if (targetUIP != null || hasMainThreadAnnot) { + if (debugSpew) { + System.err.println("Method marked @UIEffect"); + } + return new Effect(UIEffect.class); + } else if (targetPolyP != null) { + if (debugSpew) { + System.err.println("Method marked @PolyUIEffect"); + } + return new Effect(PolyUIEffect.class); } - } - if (canInheritParentEffects) { - EffectRange r = findInheritedEffectRange(targetClassElt, methodElt); - return (r != null ? Effect.min(r.min, r.max) : new Effect(SafeEffect.class)); - } - } + // The method is not explicitly annotated, so check class and package annotations, + // and supertype effects if in an anonymous inner class - return new Effect(SafeEffect.class); - } - - /** - * Get the effect of a method call at its callsite, acknowledging polymorphic instantiation using - * type use annotations. - * - * @param tree the method invocation as an AST node - * @param callerReceiver the type of the receiver object if available. Used to resolve direct - * calls like "super()" - * @param methodElt the element of the callee method - * @return the computed effect (SafeEffect or UIEffect) for the method call - */ - public Effect getComputedEffectAtCallsite( - MethodInvocationTree tree, - AnnotatedTypeMirror.AnnotatedDeclaredType callerReceiver, - ExecutableElement methodElt) { - Effect targetEffect = getDeclaredEffect(methodElt); - if (targetEffect.isPoly()) { - AnnotatedTypeMirror srcType = null; - ExpressionTree methodSelect = tree.getMethodSelect(); - if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { - ExpressionTree src = ((MemberSelectTree) methodSelect).getExpression(); - srcType = getAnnotatedType(src); - } else if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { - // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()" - if (callerReceiver == null) { - // Not enought information provided to instantiate this type-polymorphic effects - return targetEffect; + if (isUIType(targetClassElt)) { + // Already checked, no explicit @SafeEffect annotation + return new Effect(UIEffect.class); } - srcType = callerReceiver; - } else { - throw new TypeSystemError("Unexpected getMethodSelect() kind at callsite " + tree); - } - - // Instantiate type-polymorphic effects - if (srcType.hasAnnotation(AlwaysSafe.class)) { - targetEffect = new Effect(SafeEffect.class); - } else if (srcType.hasAnnotation(UI.class)) { - targetEffect = new Effect(UIEffect.class); - } - // Poly substitution would be a noop. - } - return targetEffect; - } - - /** - * Get the inferred effect of a lambda expression based on the type annotations of its functional - * interface and the effects of the calls in its body. - * - *

          This relies on GuiEffectVisitor to perform the actual inference step and mark lambdas - * with @PolyUIEffect functional interfaces as being explicitly UI-affecting using the {@link - * #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} method. - * - * @param lambdaTree a lambda expression's AST node - * @return the inferred effect of the lambda - */ - public Effect getInferedEffectForLambdaExpression(LambdaExpressionTree lambdaTree) { - // @UI type if annotated on the lambda expression explicitly - if (uiLambdas.contains(lambdaTree)) { - return new Effect(UIEffect.class); - } - ExecutableElement functionalInterfaceMethodElt = - TreeUtils.findFunction(lambdaTree, checker.getProcessingEnvironment()); - if (debugSpew) { - System.err.println("functionalInterfaceMethodElt found for lambda"); - } - return getDeclaredEffect(functionalInterfaceMethodElt); - } - - /** - * Test if this tree corresponds to a lambda expression or new class marked as UI affecting by - * either {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI}} or {@link - * #constrainAnonymousClassToUI(TypeElement)}. Only explicit markings due to inference are - * considered here, for the properly computed type of the expression, use {@link - * #getAnnotatedType(Tree)} instead. - * - * @param tree the tree to check - * @return whether it is a lambda expression or new class marked as UI by inference - */ - public boolean isDirectlyMarkedUIThroughInference(Tree tree) { - if (tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - return uiLambdas.contains((LambdaExpressionTree) tree); - } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { - AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); - if (typeMirror.getKind() == TypeKind.DECLARED) { - return uiAnonClasses.contains(((DeclaredType) typeMirror.getUnderlyingType()).asElement()); - } - } - return false; - } - - @Override - public AnnotatedTypeMirror getAnnotatedType(Tree tree) { - AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); - if (typeMirror.hasAnnotation(UI.class)) { - return typeMirror; + + // Anonymous inner types should just get the effect of the parent by default, rather than + // annotating every instance. Unless it's implementing a polymorphic supertype, in which + // case we still want the developer to be explicit. + if (isAnonymousType(targetClassElt)) { + boolean canInheritParentEffects = true; // Refine this for polymorphic parents + DeclaredType directSuper = (DeclaredType) targetClassElt.getSuperclass(); + TypeElement superElt = (TypeElement) directSuper.asElement(); + // Anonymous subtypes of polymorphic classes other than object can't inherit + if (getDeclAnnotation(superElt, PolyUIType.class) != null + && !TypesUtils.isObject(directSuper)) { + canInheritParentEffects = false; + } else { + for (TypeMirror ifaceM : targetClassElt.getInterfaces()) { + DeclaredType iface = (DeclaredType) ifaceM; + TypeElement ifaceElt = (TypeElement) iface.asElement(); + if (getDeclAnnotation(ifaceElt, PolyUIType.class) != null) { + canInheritParentEffects = false; + } + } + } + + if (canInheritParentEffects) { + EffectRange r = findInheritedEffectRange(targetClassElt, methodElt); + return (r != null ? Effect.min(r.min, r.max) : new Effect(SafeEffect.class)); + } + } + + return new Effect(SafeEffect.class); } - // Check if this an @UI anonymous class or lambda due to inference, or an expression - // containing such class/lambda - if (isDirectlyMarkedUIThroughInference(tree)) { - typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); - } else if (tree.getKind() == Tree.Kind.PARENTHESIZED) { - ParenthesizedTree parenthesizedTree = (ParenthesizedTree) tree; - return this.getAnnotatedType(parenthesizedTree.getExpression()); - } else if (tree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { - ConditionalExpressionTree cet = (ConditionalExpressionTree) tree; - boolean isTrueOperandUI = - (cet.getTrueExpression() != null - && this.getAnnotatedType(cet.getTrueExpression()).hasAnnotation(UI.class)); - boolean isFalseOperandUI = - (cet.getFalseExpression() != null - && this.getAnnotatedType(cet.getFalseExpression()).hasAnnotation(UI.class)); - if (isTrueOperandUI || isFalseOperandUI) { - typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); - } + + /** + * Get the effect of a method call at its callsite, acknowledging polymorphic instantiation + * using type use annotations. + * + * @param tree the method invocation as an AST node + * @param callerReceiver the type of the receiver object if available. Used to resolve direct + * calls like "super()" + * @param methodElt the element of the callee method + * @return the computed effect (SafeEffect or UIEffect) for the method call + */ + public Effect getComputedEffectAtCallsite( + MethodInvocationTree tree, + AnnotatedTypeMirror.AnnotatedDeclaredType callerReceiver, + ExecutableElement methodElt) { + Effect targetEffect = getDeclaredEffect(methodElt); + if (targetEffect.isPoly()) { + AnnotatedTypeMirror srcType = null; + ExpressionTree methodSelect = tree.getMethodSelect(); + if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { + ExpressionTree src = ((MemberSelectTree) methodSelect).getExpression(); + srcType = getAnnotatedType(src); + } else if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { + // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()" + if (callerReceiver == null) { + // Not enought information provided to instantiate this type-polymorphic effects + return targetEffect; + } + srcType = callerReceiver; + } else { + throw new TypeSystemError("Unexpected getMethodSelect() kind at callsite " + tree); + } + + // Instantiate type-polymorphic effects + if (srcType.hasAnnotation(AlwaysSafe.class)) { + targetEffect = new Effect(SafeEffect.class); + } else if (srcType.hasAnnotation(UI.class)) { + targetEffect = new Effect(UIEffect.class); + } + // Poly substitution would be a noop. + } + return targetEffect; } - // TODO: Do we need to support other expression here? - // (i.e. are there any other operators that take new or lambda expressions as operands) - return typeMirror; - } - - // Only the visitMethod call should pass true for warnings - public EffectRange findInheritedEffectRange( - TypeElement declaringType, ExecutableElement overridingMethod) { - return findInheritedEffectRange(declaringType, overridingMethod, false, null); - } - - /** - * Find the greatest and least effects of methods the specified definition overrides. This method - * is used for two reasons: - * - *

          1. {@link GuiEffectVisitor#visitMethod(MethodTree,Void) GuiEffectVisitor.visitMethod} calls - * this to perform an effect override check (that a method's effect is less than or equal to the - * effect of any method it overrides). This use passes {@code true} for the {@code - * issueConflictWarning} in order to trigger warning messages. - * - *

          2. {@link #getDeclaredEffect(ExecutableElement) getDeclaredEffect} in this class uses this - * to infer the default effect of methods in anonymous inner classes. This use passes {@code - * false} for {@code issueConflictWarning}, because it only needs the return value. - * - * @param declaringType the type declaring the override - * @param overridingMethod the method override itself - * @param issueConflictWarning whether or not to issue warnings - * @param errorTree the method declaration AST node; used for reporting errors - * @return the min and max inherited effects, or null if none were discovered - */ - public @Nullable EffectRange findInheritedEffectRange( - TypeElement declaringType, - ExecutableElement overridingMethod, - boolean issueConflictWarning, - Tree errorTree) { - assert (declaringType != null); - ExecutableElement uiOverridden = null; - ExecutableElement safeOverridden = null; - ExecutableElement polyOverridden = null; - - // We must account for explicit annotation, type declaration annotations, and package - // annotations. - boolean isUI = - (getDeclAnnotation(overridingMethod, UIEffect.class) != null || isUIType(declaringType)) - && getDeclAnnotation(overridingMethod, SafeEffect.class) == null; - boolean isPolyUI = getDeclAnnotation(overridingMethod, PolyUIEffect.class) != null; - - // Check for invalid overrides. - // AnnotatedTypes.overriddenMethods retrieves all transitive definitions overridden by this - // declaration. - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, this, overridingMethod); - - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); - AnnotatedTypeMirror.AnnotatedExecutableType overriddenMethod = - AnnotatedTypes.asMemberOf(types, this, overriddenType, pair.getValue()); - ExecutableElement overriddenMethodElt = pair.getValue(); - if (debugSpew) { - System.err.println( - "Found " - + declaringType - + "::" - + overridingMethod - + " overrides " - + overriddenType - + "::" - + overriddenMethod); - } - Effect eff = getDeclaredEffect(overriddenMethodElt); - if (eff.isSafe()) { - safeOverridden = overriddenMethodElt; - if (isUI) { - checker.reportError( - errorTree, - "override.effect.invalid", - declaringType, - overridingMethod, - overriddenType, - safeOverridden); - } else if (isPolyUI) { - checker.reportError( - errorTree, - "override.effect.invalid.polymorphic", - declaringType, - overridingMethod, - overriddenType, - safeOverridden); + + /** + * Get the inferred effect of a lambda expression based on the type annotations of its + * functional interface and the effects of the calls in its body. + * + *

          This relies on GuiEffectVisitor to perform the actual inference step and mark lambdas + * with @PolyUIEffect functional interfaces as being explicitly UI-affecting using the {@link + * #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI} method. + * + * @param lambdaTree a lambda expression's AST node + * @return the inferred effect of the lambda + */ + public Effect getInferedEffectForLambdaExpression(LambdaExpressionTree lambdaTree) { + // @UI type if annotated on the lambda expression explicitly + if (uiLambdas.contains(lambdaTree)) { + return new Effect(UIEffect.class); } - } else if (eff.isUI()) { - uiOverridden = overriddenMethodElt; - } else { - assert eff.isPoly(); - polyOverridden = overriddenMethodElt; - if (isUI) { - // Need to special case an anonymous class with @UI on the decl, because - // "new @UI Runnable {...}" - // parses as @UI on an anon class decl extending Runnable - boolean isAnonInstantiation = - isAnonymousType(declaringType) - && (fromElement(declaringType).hasAnnotation(UI.class) - || uiAnonClasses.contains(declaringType)); - if (!isAnonInstantiation && !overriddenType.hasAnnotation(UI.class)) { - checker.reportError( - errorTree, - "override.effect.invalid.nonui", - declaringType, - overridingMethod, - overriddenType, - polyOverridden); - } + ExecutableElement functionalInterfaceMethodElt = + TreeUtils.findFunction(lambdaTree, checker.getProcessingEnvironment()); + if (debugSpew) { + System.err.println("functionalInterfaceMethodElt found for lambda"); } - } + return getDeclaredEffect(functionalInterfaceMethodElt); } - // We don't need to issue warnings for overriding both poly and a concrete effect. - if (uiOverridden != null && safeOverridden != null && issueConflictWarning) { - // There may be more than two parent methods, but for now it's - // enough to know there are at least 2 in conflict. - checker.reportWarning( - errorTree, - "override.effect.warning.inheritance", - declaringType, - overridingMethod, - uiOverridden.getEnclosingElement().asType(), - uiOverridden, - safeOverridden.getEnclosingElement().asType(), - safeOverridden); + /** + * Test if this tree corresponds to a lambda expression or new class marked as UI affecting by + * either {@link #constrainLambdaToUI(LambdaExpressionTree) constrainLambdaToUI}} or {@link + * #constrainAnonymousClassToUI(TypeElement)}. Only explicit markings due to inference are + * considered here, for the properly computed type of the expression, use {@link + * #getAnnotatedType(Tree)} instead. + * + * @param tree the tree to check + * @return whether it is a lambda expression or new class marked as UI by inference + */ + public boolean isDirectlyMarkedUIThroughInference(Tree tree) { + if (tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + return uiLambdas.contains((LambdaExpressionTree) tree); + } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { + AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); + if (typeMirror.getKind() == TypeKind.DECLARED) { + return uiAnonClasses.contains( + ((DeclaredType) typeMirror.getUnderlyingType()).asElement()); + } + } + return false; } - Effect min = - (safeOverridden != null - ? new Effect(SafeEffect.class) - : (polyOverridden != null - ? new Effect(PolyUIEffect.class) - : (uiOverridden != null ? new Effect(UIEffect.class) : null))); - Effect max = - (uiOverridden != null - ? new Effect(UIEffect.class) - : (polyOverridden != null - ? new Effect(PolyUIEffect.class) - : (safeOverridden != null ? new Effect(SafeEffect.class) : null))); - if (debugSpew) { - System.err.println( - "Found " - + declaringType - + "." - + overridingMethod - + " to have inheritance pair (" - + min - + "," - + max - + ")"); + @Override + public AnnotatedTypeMirror getAnnotatedType(Tree tree) { + AnnotatedTypeMirror typeMirror = super.getAnnotatedType(tree); + if (typeMirror.hasAnnotation(UI.class)) { + return typeMirror; + } + // Check if this an @UI anonymous class or lambda due to inference, or an expression + // containing such class/lambda + if (isDirectlyMarkedUIThroughInference(tree)) { + typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); + } else if (tree.getKind() == Tree.Kind.PARENTHESIZED) { + ParenthesizedTree parenthesizedTree = (ParenthesizedTree) tree; + return this.getAnnotatedType(parenthesizedTree.getExpression()); + } else if (tree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { + ConditionalExpressionTree cet = (ConditionalExpressionTree) tree; + boolean isTrueOperandUI = + (cet.getTrueExpression() != null + && this.getAnnotatedType(cet.getTrueExpression()) + .hasAnnotation(UI.class)); + boolean isFalseOperandUI = + (cet.getFalseExpression() != null + && this.getAnnotatedType(cet.getFalseExpression()) + .hasAnnotation(UI.class)); + if (isTrueOperandUI || isFalseOperandUI) { + typeMirror.replaceAnnotation(AnnotationBuilder.fromClass(elements, UI.class)); + } + } + // TODO: Do we need to support other expression here? + // (i.e. are there any other operators that take new or lambda expressions as operands) + return typeMirror; } - if (min == null && max == null) { - return null; - } else { - return new EffectRange(min, max); + // Only the visitMethod call should pass true for warnings + public EffectRange findInheritedEffectRange( + TypeElement declaringType, ExecutableElement overridingMethod) { + return findInheritedEffectRange(declaringType, overridingMethod, false, null); } - } - - @Override - protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { - return qualHierarchy.getBottomAnnotations(); - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new GuiEffectTreeAnnotator()); - } - - /** - * Force the given lambda expression to have UIEffect. - * - *

          Used by GuiEffectVisitor to mark as UIEffect all lambdas that perform UIEffect calls inside - * their bodies. - * - * @param lambdaExpressionTree a lambda expression's AST node - */ - public void constrainLambdaToUI(LambdaExpressionTree lambdaExpressionTree) { - uiLambdas.add(lambdaExpressionTree); - } - - /** - * Force the given anonymous inner class to be an @UI instantiation of its base class. - * - *

          Used by GuiEffectVisitor to mark as @UI all anonymous inner classes which: inherit from a - * PolyUIType annotated superclass, override a PolyUIEffect method from said superclass, and - * perform UIEffect calls inside the body of this method. - * - * @param classElt the TypeElement corresponding to the anonymous inner class to mark as an @UI - * instantiation of an UI-polymorphic superclass. - */ - public void constrainAnonymousClassToUI(TypeElement classElt) { - assert TypesUtils.isAnonymous(classElt.asType()); - uiAnonClasses.add(classElt); - } - - /** A class for adding annotations based on tree. */ - private class GuiEffectTreeAnnotator extends TreeAnnotator { - - GuiEffectTreeAnnotator() { - super(GuiEffectTypeFactory.this); + + /** + * Find the greatest and least effects of methods the specified definition overrides. This + * method is used for two reasons: + * + *

          1. {@link GuiEffectVisitor#visitMethod(MethodTree,Void) GuiEffectVisitor.visitMethod} + * calls this to perform an effect override check (that a method's effect is less than or equal + * to the effect of any method it overrides). This use passes {@code true} for the {@code + * issueConflictWarning} in order to trigger warning messages. + * + *

          2. {@link #getDeclaredEffect(ExecutableElement) getDeclaredEffect} in this class uses this + * to infer the default effect of methods in anonymous inner classes. This use passes {@code + * false} for {@code issueConflictWarning}, because it only needs the return value. + * + * @param declaringType the type declaring the override + * @param overridingMethod the method override itself + * @param issueConflictWarning whether or not to issue warnings + * @param errorTree the method declaration AST node; used for reporting errors + * @return the min and max inherited effects, or null if none were discovered + */ + public @Nullable EffectRange findInheritedEffectRange( + TypeElement declaringType, + ExecutableElement overridingMethod, + boolean issueConflictWarning, + Tree errorTree) { + assert (declaringType != null); + ExecutableElement uiOverridden = null; + ExecutableElement safeOverridden = null; + ExecutableElement polyOverridden = null; + + // We must account for explicit annotation, type declaration annotations, and package + // annotations. + boolean isUI = + (getDeclAnnotation(overridingMethod, UIEffect.class) != null + || isUIType(declaringType)) + && getDeclAnnotation(overridingMethod, SafeEffect.class) == null; + boolean isPolyUI = getDeclAnnotation(overridingMethod, PolyUIEffect.class) != null; + + // Check for invalid overrides. + // AnnotatedTypes.overriddenMethods retrieves all transitive definitions overridden by this + // declaration. + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, this, overridingMethod); + + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); + AnnotatedTypeMirror.AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf(types, this, overriddenType, pair.getValue()); + ExecutableElement overriddenMethodElt = pair.getValue(); + if (debugSpew) { + System.err.println( + "Found " + + declaringType + + "::" + + overridingMethod + + " overrides " + + overriddenType + + "::" + + overriddenMethod); + } + Effect eff = getDeclaredEffect(overriddenMethodElt); + if (eff.isSafe()) { + safeOverridden = overriddenMethodElt; + if (isUI) { + checker.reportError( + errorTree, + "override.effect.invalid", + declaringType, + overridingMethod, + overriddenType, + safeOverridden); + } else if (isPolyUI) { + checker.reportError( + errorTree, + "override.effect.invalid.polymorphic", + declaringType, + overridingMethod, + overriddenType, + safeOverridden); + } + } else if (eff.isUI()) { + uiOverridden = overriddenMethodElt; + } else { + assert eff.isPoly(); + polyOverridden = overriddenMethodElt; + if (isUI) { + // Need to special case an anonymous class with @UI on the decl, because + // "new @UI Runnable {...}" + // parses as @UI on an anon class decl extending Runnable + boolean isAnonInstantiation = + isAnonymousType(declaringType) + && (fromElement(declaringType).hasAnnotation(UI.class) + || uiAnonClasses.contains(declaringType)); + if (!isAnonInstantiation && !overriddenType.hasAnnotation(UI.class)) { + checker.reportError( + errorTree, + "override.effect.invalid.nonui", + declaringType, + overridingMethod, + overriddenType, + polyOverridden); + } + } + } + } + + // We don't need to issue warnings for overriding both poly and a concrete effect. + if (uiOverridden != null && safeOverridden != null && issueConflictWarning) { + // There may be more than two parent methods, but for now it's + // enough to know there are at least 2 in conflict. + checker.reportWarning( + errorTree, + "override.effect.warning.inheritance", + declaringType, + overridingMethod, + uiOverridden.getEnclosingElement().asType(), + uiOverridden, + safeOverridden.getEnclosingElement().asType(), + safeOverridden); + } + + Effect min = + (safeOverridden != null + ? new Effect(SafeEffect.class) + : (polyOverridden != null + ? new Effect(PolyUIEffect.class) + : (uiOverridden != null ? new Effect(UIEffect.class) : null))); + Effect max = + (uiOverridden != null + ? new Effect(UIEffect.class) + : (polyOverridden != null + ? new Effect(PolyUIEffect.class) + : (safeOverridden != null ? new Effect(SafeEffect.class) : null))); + if (debugSpew) { + System.err.println( + "Found " + + declaringType + + "." + + overridingMethod + + " to have inheritance pair (" + + min + + "," + + max + + ")"); + } + + if (min == null && max == null) { + return null; + } else { + return new EffectRange(min, max); + } } - /* - public boolean hasExplicitUIEffect(ExecutableElement methElt) { - return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, UIEffect.class) != null; + @Override + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + return qualHierarchy.getBottomAnnotations(); } - public boolean hasExplicitSafeEffect(ExecutableElement methElt) { - return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, SafeEffect.class) != null; + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new GuiEffectTreeAnnotator()); } - public boolean hasExplicitPolyUIEffect(ExecutableElement methElt) { - return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, PolyUIEffect.class) != null; + /** + * Force the given lambda expression to have UIEffect. + * + *

          Used by GuiEffectVisitor to mark as UIEffect all lambdas that perform UIEffect calls + * inside their bodies. + * + * @param lambdaExpressionTree a lambda expression's AST node + */ + public void constrainLambdaToUI(LambdaExpressionTree lambdaExpressionTree) { + uiLambdas.add(lambdaExpressionTree); } - public boolean hasExplicitEffect(ExecutableElement methElt) { - return hasExplicitUIEffect(methElt) - || hasExplicitSafeEffect(methElt) - || hasExplicitPolyUIEffect(methElt); + /** + * Force the given anonymous inner class to be an @UI instantiation of its base class. + * + *

          Used by GuiEffectVisitor to mark as @UI all anonymous inner classes which: inherit from a + * PolyUIType annotated superclass, override a PolyUIEffect method from said superclass, and + * perform UIEffect calls inside the body of this method. + * + * @param classElt the TypeElement corresponding to the anonymous inner class to mark as an @UI + * instantiation of an UI-polymorphic superclass. + */ + public void constrainAnonymousClassToUI(TypeElement classElt) { + assert TypesUtils.isAnonymous(classElt.asType()); + uiAnonClasses.add(classElt); } - */ - @Override - public Void visitMethod(MethodTree tree, AnnotatedTypeMirror type) { - AnnotatedTypeMirror.AnnotatedExecutableType methType = - (AnnotatedTypeMirror.AnnotatedExecutableType) type; - // Effect e = getDeclaredEffect(methType.getElement()); - TypeElement cls = (TypeElement) methType.getElement().getEnclosingElement(); - - // STEP 1: Get the method effect annotation - // if (!hasExplicitEffect(methType.getElement())) { - // TODO: This line does nothing! - // AnnotatedTypeMirror.addAnnotation silently ignores non-qualifier annotations! - // We should be digging up the /declaration/ of the method, and annotating that. - // methType.addAnnotation(e.getAnnot()); - // } - - // STEP 2: Fix up the method receiver annotation - AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = methType.getReceiverType(); - if (receiverType != null && !receiverType.hasAnnotationInHierarchy(UI)) { - receiverType.addAnnotation( - isPolymorphicType(cls) - ? POLYUI - : fromElement(cls).hasAnnotation(UI.class) ? UI : ALWAYSSAFE); - } - return super.visitMethod(tree, type); + /** A class for adding annotations based on tree. */ + private class GuiEffectTreeAnnotator extends TreeAnnotator { + + GuiEffectTreeAnnotator() { + super(GuiEffectTypeFactory.this); + } + + /* + public boolean hasExplicitUIEffect(ExecutableElement methElt) { + return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, UIEffect.class) != null; + } + + public boolean hasExplicitSafeEffect(ExecutableElement methElt) { + return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, SafeEffect.class) != null; + } + + public boolean hasExplicitPolyUIEffect(ExecutableElement methElt) { + return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, PolyUIEffect.class) != null; + } + + public boolean hasExplicitEffect(ExecutableElement methElt) { + return hasExplicitUIEffect(methElt) + || hasExplicitSafeEffect(methElt) + || hasExplicitPolyUIEffect(methElt); + } + */ + + @Override + public Void visitMethod(MethodTree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror.AnnotatedExecutableType methType = + (AnnotatedTypeMirror.AnnotatedExecutableType) type; + // Effect e = getDeclaredEffect(methType.getElement()); + TypeElement cls = (TypeElement) methType.getElement().getEnclosingElement(); + + // STEP 1: Get the method effect annotation + // if (!hasExplicitEffect(methType.getElement())) { + // TODO: This line does nothing! + // AnnotatedTypeMirror.addAnnotation silently ignores non-qualifier annotations! + // We should be digging up the /declaration/ of the method, and annotating that. + // methType.addAnnotation(e.getAnnot()); + // } + + // STEP 2: Fix up the method receiver annotation + AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = methType.getReceiverType(); + if (receiverType != null && !receiverType.hasAnnotationInHierarchy(UI)) { + receiverType.addAnnotation( + isPolymorphicType(cls) + ? POLYUI + : fromElement(cls).hasAnnotation(UI.class) ? UI : ALWAYSSAFE); + } + return super.visitMethod(tree, type); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java index bcb690571c8..2c94264ad98 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java @@ -11,14 +11,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.ArrayDeque; -import java.util.List; -import java.util.Map; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; + import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.PolyUI; import org.checkerframework.checker.guieffect.qual.PolyUIEffect; @@ -41,561 +34,583 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; + /** Require that only UI code invokes code with the UI effect. */ public class GuiEffectVisitor extends BaseTypeVisitor { - /** The type of the class currently being visited. */ - private @Nullable AnnotatedDeclaredType classType = null; + /** The type of the class currently being visited. */ + private @Nullable AnnotatedDeclaredType classType = null; - /** The receiver type of the enclosing method tree. */ - private @Nullable AnnotatedDeclaredType receiverType = null; + /** The receiver type of the enclosing method tree. */ + private @Nullable AnnotatedDeclaredType receiverType = null; - /** Whether or not to display debugging information. */ - protected final boolean debugSpew; + /** Whether or not to display debugging information. */ + protected final boolean debugSpew; - // effStack and currentMethods should always be the same size. - protected final ArrayDeque effStack; - protected final ArrayDeque currentMethods; + // effStack and currentMethods should always be the same size. + protected final ArrayDeque effStack; + protected final ArrayDeque currentMethods; - public GuiEffectVisitor(BaseTypeChecker checker) { - super(checker); - debugSpew = checker.getLintOption("debugSpew", false); - if (debugSpew) { - System.err.println("Running GuiEffectVisitor"); + public GuiEffectVisitor(BaseTypeChecker checker) { + super(checker); + debugSpew = checker.getLintOption("debugSpew", false); + if (debugSpew) { + System.err.println("Running GuiEffectVisitor"); + } + effStack = new ArrayDeque<>(); + currentMethods = new ArrayDeque<>(); } - effStack = new ArrayDeque<>(); - currentMethods = new ArrayDeque<>(); - } - - @Override - protected GuiEffectTypeFactory createTypeFactory() { - return new GuiEffectTypeFactory(checker, debugSpew); - } - - // The issue is that the receiver implicitly receives an @AlwaysSafe anno, so calls on @UI - // references fail because the framework doesn't implicitly upcast the receiver (which in - // general wouldn't be sound). - // TODO: Fix method receiver defaults: method-polymorphic for any polymorphic method, UI - // for any UI instantiations, safe otherwise - @Override - protected void checkMethodInvocability( - AnnotatedExecutableType method, MethodInvocationTree tree) { - // The inherited version of this complains about invoking methods of @UI instantiations of - // classes, which by default are annotated @AlwaysSafe, which for data type qualifiers is - // reasonable, but it not what we want, since we want . - // TODO: Undo this hack! - } - - protected class GuiEffectOverrideChecker extends OverrideChecker { - /** - * Extend the receiver part of the method override check. We extend the standard check, to - * additionally permit narrowing the receiver's permission to {@code @AlwaysSafe} in a safe - * instantiation of a {@code @PolyUIType}. Returns true if the override is permitted. - */ + @Override - protected boolean checkReceiverOverride() { - // We cannot reuse the inherited method because it directly issues the failure, but we - // want a more permissive check. So this is copied down and modified from - // BaseTypeVisitor.OverrideChecker.checkReceiverOverride. - // isSubtype() requires its arguments to be actual subtypes with - // respect to JLS, but the overrider receiver is not a subtype of the - // overridden receiver. Hence copying the annotations. - // TODO: Does this need to be improved for generic receivers? I.e., do we need to - // add extra checking to reject the case of also changing qualifiers in type parameters? - // Such as overriding a {@code @PolyUI C<@UI T>} by {@code @AlwaysSafe C<@AlwaysSafe - // T>}? The change to the receiver permission is acceptable, while the change to the - // parameter should be rejected. - AnnotatedTypeMirror overriddenReceiver = - overrider.getReceiverType().getErased().shallowCopy(false); - overriddenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations()); - if (!atypeFactory - .getTypeHierarchy() - .isSubtype(overriddenReceiver, overrider.getReceiverType().getErased())) { - // This is the point at which the default check would issue an error. - // We additionally permit overrides to move from @PolyUI receivers to @AlwaysSafe - // receivers, if it's in a @AlwaysSafe specialization of a @PolyUIType - boolean safeParent = overriddenType.getAnnotation(AlwaysSafe.class) != null; - boolean polyParentDecl = - atypeFactory.getDeclAnnotation( - overriddenType.getUnderlyingType().asElement(), PolyUIType.class) - != null; - // TODO: How much validation do I need here? Do I need to check that the overridden - // receiver was really @PolyUI and the method is really an @PolyUIEffect? I don't - // think so - we know it's a polymorphic parent type, so all receivers would be - // @PolyUI. - // Java would already reject before running type annotation processors if the Java - // types were wrong. - // The *only* extra leeway we want to permit is overriding @PolyUI receiver to - // @AlwaysSafe. But with generics, the tentative check below is inadequate. - boolean safeReceiverOverride = - overrider.getReceiverType().getAnnotation(AlwaysSafe.class) != null; - if (safeParent && polyParentDecl && safeReceiverOverride) { - return true; + protected GuiEffectTypeFactory createTypeFactory() { + return new GuiEffectTypeFactory(checker, debugSpew); + } + + // The issue is that the receiver implicitly receives an @AlwaysSafe anno, so calls on @UI + // references fail because the framework doesn't implicitly upcast the receiver (which in + // general wouldn't be sound). + // TODO: Fix method receiver defaults: method-polymorphic for any polymorphic method, UI + // for any UI instantiations, safe otherwise + @Override + protected void checkMethodInvocability( + AnnotatedExecutableType method, MethodInvocationTree tree) { + // The inherited version of this complains about invoking methods of @UI instantiations of + // classes, which by default are annotated @AlwaysSafe, which for data type qualifiers is + // reasonable, but it not what we want, since we want . + // TODO: Undo this hack! + } + + protected class GuiEffectOverrideChecker extends OverrideChecker { + /** + * Extend the receiver part of the method override check. We extend the standard check, to + * additionally permit narrowing the receiver's permission to {@code @AlwaysSafe} in a safe + * instantiation of a {@code @PolyUIType}. Returns true if the override is permitted. + */ + @Override + protected boolean checkReceiverOverride() { + // We cannot reuse the inherited method because it directly issues the failure, but we + // want a more permissive check. So this is copied down and modified from + // BaseTypeVisitor.OverrideChecker.checkReceiverOverride. + // isSubtype() requires its arguments to be actual subtypes with + // respect to JLS, but the overrider receiver is not a subtype of the + // overridden receiver. Hence copying the annotations. + // TODO: Does this need to be improved for generic receivers? I.e., do we need to + // add extra checking to reject the case of also changing qualifiers in type parameters? + // Such as overriding a {@code @PolyUI C<@UI T>} by {@code @AlwaysSafe C<@AlwaysSafe + // T>}? The change to the receiver permission is acceptable, while the change to the + // parameter should be rejected. + AnnotatedTypeMirror overriddenReceiver = + overrider.getReceiverType().getErased().shallowCopy(false); + overriddenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations()); + if (!atypeFactory + .getTypeHierarchy() + .isSubtype(overriddenReceiver, overrider.getReceiverType().getErased())) { + // This is the point at which the default check would issue an error. + // We additionally permit overrides to move from @PolyUI receivers to @AlwaysSafe + // receivers, if it's in a @AlwaysSafe specialization of a @PolyUIType + boolean safeParent = overriddenType.getAnnotation(AlwaysSafe.class) != null; + boolean polyParentDecl = + atypeFactory.getDeclAnnotation( + overriddenType.getUnderlyingType().asElement(), + PolyUIType.class) + != null; + // TODO: How much validation do I need here? Do I need to check that the overridden + // receiver was really @PolyUI and the method is really an @PolyUIEffect? I don't + // think so - we know it's a polymorphic parent type, so all receivers would be + // @PolyUI. + // Java would already reject before running type annotation processors if the Java + // types were wrong. + // The *only* extra leeway we want to permit is overriding @PolyUI receiver to + // @AlwaysSafe. But with generics, the tentative check below is inadequate. + boolean safeReceiverOverride = + overrider.getReceiverType().getAnnotation(AlwaysSafe.class) != null; + if (safeParent && polyParentDecl && safeReceiverOverride) { + return true; + } + checker.reportError( + overriderTree, + "override.receiver.invalid", + overrider.getReceiverType(), + overridden.getReceiverType(), + overriderType, + overrider, + overriddenType, + overridden); + return false; + } + return true; + } + + /** + * Create a GuiEffectOverrideChecker. + * + * @param overriderTree the AST node of the overriding method or method reference + * @param overrider the type of the overriding method + * @param overridingType the type enclosing the overrider method, usually an + * AnnotatedDeclaredType; for Method References may be something else + * @param overridingReturnType the return type of the overriding method + * @param overridden the type of the overridden method + * @param overriddenType the declared type enclosing the overridden method + * @param overriddenReturnType the return type of the overridden method + */ + public GuiEffectOverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overrider, + AnnotatedTypeMirror overridingType, + AnnotatedTypeMirror overridingReturnType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + super( + overriderTree, + overrider, + overridingType, + overridingReturnType, + overridden, + overriddenType, + overriddenReturnType); } - checker.reportError( - overriderTree, - "override.receiver.invalid", - overrider.getReceiverType(), - overridden.getReceiverType(), - overriderType, - overrider, - overriddenType, - overridden); - return false; - } - return true; } - /** - * Create a GuiEffectOverrideChecker. - * - * @param overriderTree the AST node of the overriding method or method reference - * @param overrider the type of the overriding method - * @param overridingType the type enclosing the overrider method, usually an - * AnnotatedDeclaredType; for Method References may be something else - * @param overridingReturnType the return type of the overriding method - * @param overridden the type of the overridden method - * @param overriddenType the declared type enclosing the overridden method - * @param overriddenReturnType the return type of the overridden method - */ - public GuiEffectOverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overrider, - AnnotatedTypeMirror overridingType, - AnnotatedTypeMirror overridingReturnType, - AnnotatedExecutableType overridden, - AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - super( - overriderTree, - overrider, - overridingType, - overridingReturnType, - overridden, - overriddenType, - overriddenReturnType); + @Override + protected OverrideChecker createOverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overrider, + AnnotatedTypeMirror overridingType, + AnnotatedTypeMirror overridingReturnType, + AnnotatedExecutableType overridden, + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + return new GuiEffectOverrideChecker( + overriderTree, + overrider, + overridingType, + overridingReturnType, + overridden, + overriddenType, + overriddenReturnType); } - } - - @Override - protected OverrideChecker createOverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overrider, - AnnotatedTypeMirror overridingType, - AnnotatedTypeMirror overridingReturnType, - AnnotatedExecutableType overridden, - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - return new GuiEffectOverrideChecker( - overriderTree, - overrider, - overridingType, - overridingReturnType, - overridden, - overriddenType, - overriddenReturnType); - } - - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(AnnotationBuilder.fromClass(elements, AlwaysSafe.class)); - } - - @Override - public boolean isValidUse( - AnnotatedTypeMirror.AnnotatedDeclaredType declarationType, - AnnotatedTypeMirror.AnnotatedDeclaredType useType, - Tree tree) { - boolean ret = - useType.hasAnnotation(AlwaysSafe.class) - || useType.hasAnnotation(PolyUI.class) - || atypeFactory.isPolymorphicType( - (TypeElement) declarationType.getUnderlyingType().asElement()) - || (useType.hasAnnotation(UI.class) && declarationType.hasAnnotation(UI.class)); - if (debugSpew && !ret) { - System.err.println("use: " + useType); - System.err.println("use safe: " + useType.hasAnnotation(AlwaysSafe.class)); - System.err.println("use poly: " + useType.hasAnnotation(PolyUI.class)); - System.err.println("use ui: " + useType.hasAnnotation(UI.class)); - System.err.println("declaration safe: " + declarationType.hasAnnotation(AlwaysSafe.class)); - System.err.println( - "declaration poly: " - + atypeFactory.isPolymorphicType( - (TypeElement) declarationType.getUnderlyingType().asElement())); - System.err.println("declaration ui: " + declarationType.hasAnnotation(UI.class)); - System.err.println("declaration: " + declarationType); + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(AnnotationBuilder.fromClass(elements, AlwaysSafe.class)); } - return ret; - } - - @Override - @SuppressWarnings("interning:not.interned") // comparing AST nodes - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - Void v = super.visitLambdaExpression(tree, p); - // If this is a lambda inferred to be @UI, scan up the path and re-check any assignments - // involving it. - if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { - // Backtrack path to the lambda expression itself - TreePath path = getCurrentPath(); - while (path.getLeaf() != tree) { - assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; - path = path.getParentPath(); - } - scanUp(path); + + @Override + public boolean isValidUse( + AnnotatedTypeMirror.AnnotatedDeclaredType declarationType, + AnnotatedTypeMirror.AnnotatedDeclaredType useType, + Tree tree) { + boolean ret = + useType.hasAnnotation(AlwaysSafe.class) + || useType.hasAnnotation(PolyUI.class) + || atypeFactory.isPolymorphicType( + (TypeElement) declarationType.getUnderlyingType().asElement()) + || (useType.hasAnnotation(UI.class) + && declarationType.hasAnnotation(UI.class)); + if (debugSpew && !ret) { + System.err.println("use: " + useType); + System.err.println("use safe: " + useType.hasAnnotation(AlwaysSafe.class)); + System.err.println("use poly: " + useType.hasAnnotation(PolyUI.class)); + System.err.println("use ui: " + useType.hasAnnotation(UI.class)); + System.err.println( + "declaration safe: " + declarationType.hasAnnotation(AlwaysSafe.class)); + System.err.println( + "declaration poly: " + + atypeFactory.isPolymorphicType( + (TypeElement) declarationType.getUnderlyingType().asElement())); + System.err.println("declaration ui: " + declarationType.hasAnnotation(UI.class)); + System.err.println("declaration: " + declarationType); + } + return ret; } - return v; - } - - @Override - protected void checkExtendsAndImplements(ClassTree classTree) { - // Skip this check - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Skip this check. - } - - @Override - protected void warnInvalidPolymorphicQualifier(ClassTree classTree) { - // Polymorphic qualifiers are legal on classes, so skip this check. - } - - // Check that the invoked effect is <= permitted effect (effStack.peek()) - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (debugSpew) { - System.err.println("For invocation " + tree + " in " + currentMethods.peek().getName()); + + @Override + @SuppressWarnings("interning:not.interned") // comparing AST nodes + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + Void v = super.visitLambdaExpression(tree, p); + // If this is a lambda inferred to be @UI, scan up the path and re-check any assignments + // involving it. + if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { + // Backtrack path to the lambda expression itself + TreePath path = getCurrentPath(); + while (path.getLeaf() != tree) { + assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; + path = path.getParentPath(); + } + scanUp(path); + } + return v; } - // Target method annotations - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - if (debugSpew) { - System.err.println("methodElt found"); + @Override + protected void checkExtendsAndImplements(ClassTree classTree) { + // Skip this check } - Tree callerTree = TreePathUtil.enclosingMethodOrLambda(getCurrentPath()); - if (callerTree == null) { - // Static initializer; let's assume this is safe to have the UI effect - if (debugSpew) { - System.err.println("No enclosing method: likely static initializer"); - } - return super.visitMethodInvocation(tree, p); + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Skip this check. } - if (debugSpew) { - System.err.println("callerTree found: " + callerTree.getKind()); + + @Override + protected void warnInvalidPolymorphicQualifier(ClassTree classTree) { + // Polymorphic qualifiers are legal on classes, so skip this check. } - Effect targetEffect = atypeFactory.getComputedEffectAtCallsite(tree, receiverType, methodElt); - - Effect callerEffect = null; - if (callerTree.getKind() == Tree.Kind.METHOD) { - ExecutableElement callerElt = TreeUtils.elementFromDeclaration((MethodTree) callerTree); - if (debugSpew) { - System.err.println("callerElt found"); - } - - callerEffect = atypeFactory.getDeclaredEffect(callerElt); - DeclaredType callerReceiverType = classType.getUnderlyingType(); - assert callerReceiverType != null; - TypeElement callerReceiverElt = (TypeElement) callerReceiverType.asElement(); - // Note: All these checks should be fast in the common case, but happen for every method - // call inside the anonymous class. Consider a cache here if profiling surfaces this as - // taking too long. - if (TypesUtils.isAnonymous(callerReceiverType) - // Skip if already inferred @UI - && !effStack.peek().isUI() - // Ignore if explicitly annotated - && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(AlwaysSafe.class) - && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(UI.class)) { - boolean overridesPolymorphic = false; - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, callerElt); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); - AnnotatedExecutableType overriddenMethod = - AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, pair.getValue()); - if (atypeFactory.getDeclAnnotation(overriddenMethod.getElement(), PolyUIEffect.class) - != null - && atypeFactory.getDeclAnnotation( - overriddenType.getUnderlyingType().asElement(), PolyUIType.class) - != null) { - overridesPolymorphic = true; - break; - } + // Check that the invoked effect is <= permitted effect (effStack.peek()) + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (debugSpew) { + System.err.println("For invocation " + tree + " in " + currentMethods.peek().getName()); } - // Perform anonymous class polymorphic effect inference: - // method overrides @PolyUIEffect method of @PolyUIClass class, calls @UIEffect => - // @UI anon class - if (overridesPolymorphic && targetEffect.isUI()) { - // Mark the anonymous class as @UI - atypeFactory.constrainAnonymousClassToUI(callerReceiverElt); - // Then re-calculate this method's effect (it might still not be an - // @PolyUIEffect method). - callerEffect = atypeFactory.getDeclaredEffect(callerElt); - effStack.pop(); - effStack.push(callerEffect); + + // Target method annotations + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + if (debugSpew) { + System.err.println("methodElt found"); } - } - - // Field initializers inside anonymous inner classes show up with a null current-method - // --- the traversal goes straight from the class to the initializer. - assert (currentMethods.peek() == null || callerEffect.equals(effStack.peek())); - } else if (callerTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - callerEffect = - atypeFactory.getInferedEffectForLambdaExpression((LambdaExpressionTree) callerTree); - // Perform lambda polymorphic effect inference: @PolyUI lambda, calling @UIEffect => @UI - // lambda - if (targetEffect.isUI() && callerEffect.isPoly()) { - atypeFactory.constrainLambdaToUI((LambdaExpressionTree) callerTree); - callerEffect = new Effect(UIEffect.class); - } - } - assert callerEffect != null; - if (!Effect.lessThanOrEqualTo(targetEffect, callerEffect)) { - checker.reportError(tree, "call.invalid.ui", targetEffect, callerEffect); - if (debugSpew) { - System.err.println("Issuing error for tree: " + tree); - } - } - if (debugSpew) { - System.err.println("Successfully finished main non-recursive checkinv of invocation " + tree); - } - return super.visitMethodInvocation(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); - AnnotatedDeclaredType previousReceiverType = receiverType; - receiverType = methodType.getReceiverType(); - - // TODO: If the type we're in is a polymorphic (over effect qualifiers) type, the receiver - // must be @PolyUI. Otherwise a "non-polymorphic" method of a polymorphic type could be - // called on a UI instance, which then gets a Safe reference to itself (unsound!) that it - // can then pass off elsewhere (dangerous!). So all receivers in methods of a @PolyUIType - // must be @PolyUI. - - // TODO: What do we do then about classes that inherit from a concrete instantiation? If it - // subclasses a Safe instantiation, all is well. If it subclasses a UI instantiation, then - // the receivers should probably be @UI in both new and override methods, so calls to - // polymorphic methods of the parent class will work correctly. In which case for proving - // anything, the qualifier on sublasses of UI instantiations would always have to be @UI... - // Need to write down |- t for this system! And the judgments for method overrides and - // inheritance! Those are actually the hardest part of the system. - - ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); - if (debugSpew) { - System.err.println("Visiting method " + methElt + " of " + methElt.getEnclosingElement()); + Tree callerTree = TreePathUtil.enclosingMethodOrLambda(getCurrentPath()); + if (callerTree == null) { + // Static initializer; let's assume this is safe to have the UI effect + if (debugSpew) { + System.err.println("No enclosing method: likely static initializer"); + } + return super.visitMethodInvocation(tree, p); + } + if (debugSpew) { + System.err.println("callerTree found: " + callerTree.getKind()); + } + + Effect targetEffect = + atypeFactory.getComputedEffectAtCallsite(tree, receiverType, methodElt); + + Effect callerEffect = null; + if (callerTree.getKind() == Tree.Kind.METHOD) { + ExecutableElement callerElt = TreeUtils.elementFromDeclaration((MethodTree) callerTree); + if (debugSpew) { + System.err.println("callerElt found"); + } + + callerEffect = atypeFactory.getDeclaredEffect(callerElt); + DeclaredType callerReceiverType = classType.getUnderlyingType(); + assert callerReceiverType != null; + TypeElement callerReceiverElt = (TypeElement) callerReceiverType.asElement(); + // Note: All these checks should be fast in the common case, but happen for every method + // call inside the anonymous class. Consider a cache here if profiling surfaces this as + // taking too long. + if (TypesUtils.isAnonymous(callerReceiverType) + // Skip if already inferred @UI + && !effStack.peek().isUI() + // Ignore if explicitly annotated + && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(AlwaysSafe.class) + && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(UI.class)) { + boolean overridesPolymorphic = false; + Map + overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, callerElt); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); + AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf( + types, atypeFactory, overriddenType, pair.getValue()); + if (atypeFactory.getDeclAnnotation( + overriddenMethod.getElement(), PolyUIEffect.class) + != null + && atypeFactory.getDeclAnnotation( + overriddenType.getUnderlyingType().asElement(), + PolyUIType.class) + != null) { + overridesPolymorphic = true; + break; + } + } + // Perform anonymous class polymorphic effect inference: + // method overrides @PolyUIEffect method of @PolyUIClass class, calls @UIEffect => + // @UI anon class + if (overridesPolymorphic && targetEffect.isUI()) { + // Mark the anonymous class as @UI + atypeFactory.constrainAnonymousClassToUI(callerReceiverElt); + // Then re-calculate this method's effect (it might still not be an + // @PolyUIEffect method). + callerEffect = atypeFactory.getDeclaredEffect(callerElt); + effStack.pop(); + effStack.push(callerEffect); + } + } + + // Field initializers inside anonymous inner classes show up with a null current-method + // --- the traversal goes straight from the class to the initializer. + assert (currentMethods.peek() == null || callerEffect.equals(effStack.peek())); + } else if (callerTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + callerEffect = + atypeFactory.getInferedEffectForLambdaExpression( + (LambdaExpressionTree) callerTree); + // Perform lambda polymorphic effect inference: @PolyUI lambda, calling @UIEffect => @UI + // lambda + if (targetEffect.isUI() && callerEffect.isPoly()) { + atypeFactory.constrainLambdaToUI((LambdaExpressionTree) callerTree); + callerEffect = new Effect(UIEffect.class); + } + } + assert callerEffect != null; + + if (!Effect.lessThanOrEqualTo(targetEffect, callerEffect)) { + checker.reportError(tree, "call.invalid.ui", targetEffect, callerEffect); + if (debugSpew) { + System.err.println("Issuing error for tree: " + tree); + } + } + if (debugSpew) { + System.err.println( + "Successfully finished main non-recursive checkinv of invocation " + tree); + } + return super.visitMethodInvocation(tree, p); } - // Check for conflicting (multiple) annotations - // TypeMirror scratch = methElt.getReturnType(); - AnnotationMirror targetUIP = atypeFactory.getDeclAnnotation(methElt, UIEffect.class); - AnnotationMirror targetSafeP = atypeFactory.getDeclAnnotation(methElt, SafeEffect.class); - AnnotationMirror targetPolyP = atypeFactory.getDeclAnnotation(methElt, PolyUIEffect.class); - TypeElement targetClassElt = (TypeElement) methElt.getEnclosingElement(); + @Override + public Void visitMethod(MethodTree tree, Void p) { + AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); + AnnotatedDeclaredType previousReceiverType = receiverType; + receiverType = methodType.getReceiverType(); + + // TODO: If the type we're in is a polymorphic (over effect qualifiers) type, the receiver + // must be @PolyUI. Otherwise a "non-polymorphic" method of a polymorphic type could be + // called on a UI instance, which then gets a Safe reference to itself (unsound!) that it + // can then pass off elsewhere (dangerous!). So all receivers in methods of a @PolyUIType + // must be @PolyUI. + + // TODO: What do we do then about classes that inherit from a concrete instantiation? If it + // subclasses a Safe instantiation, all is well. If it subclasses a UI instantiation, then + // the receivers should probably be @UI in both new and override methods, so calls to + // polymorphic methods of the parent class will work correctly. In which case for proving + // anything, the qualifier on sublasses of UI instantiations would always have to be @UI... + // Need to write down |- t for this system! And the judgments for method overrides and + // inheritance! Those are actually the hardest part of the system. + + ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); + if (debugSpew) { + System.err.println( + "Visiting method " + methElt + " of " + methElt.getEnclosingElement()); + } + + // Check for conflicting (multiple) annotations + // TypeMirror scratch = methElt.getReturnType(); + AnnotationMirror targetUIP = atypeFactory.getDeclAnnotation(methElt, UIEffect.class); + AnnotationMirror targetSafeP = atypeFactory.getDeclAnnotation(methElt, SafeEffect.class); + AnnotationMirror targetPolyP = atypeFactory.getDeclAnnotation(methElt, PolyUIEffect.class); + TypeElement targetClassElt = (TypeElement) methElt.getEnclosingElement(); + + if ((targetUIP != null && (targetSafeP != null || targetPolyP != null)) + || (targetSafeP != null && targetPolyP != null)) { + checker.reportError(tree, "annotations.conflicts"); + } + if (targetPolyP != null && !atypeFactory.isPolymorphicType(targetClassElt)) { + checker.reportError(tree, "polymorphism.invalid"); + } + if (targetUIP != null && atypeFactory.isUIType(targetClassElt)) { + checker.reportWarning(tree, "effects.redundant.uitype"); + } + + // TODO: Report an error for polymorphic method bodies??? Until we fix the receiver + // defaults, it won't really be correct + @SuppressWarnings("unused") // call has side effects + Effect.EffectRange range = + atypeFactory.findInheritedEffectRange( + ((TypeElement) methElt.getEnclosingElement()), methElt, true, tree); + // if (targetUIP == null && targetSafeP == null && targetPolyP == null) { + // implicitly annotate this method with the LUB of the effects of the methods it overrides + // atypeFactory.fromElement(methElt).addAnnotation(range != null ? range.min.getAnnot() + // : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? UI.class : + // AlwaysSafe.class)); + // TODO: This line does nothing! AnnotatedTypeMirror.addAnnotation + // silently ignores non-qualifier annotations! + // System.err.println("ERROR: TREE ANNOTATOR SHOULD HAVE ADDED EXPLICIT ANNOTATION! (" + // +tree.getName()+")"); + // atypeFactory + // .fromElement(methElt) + // .addAnnotation(atypeFactory.getDeclaredEffect(methElt).getAnnot()); + // } + + // We hang onto the current method here for ease. We back up the old + // current method because this code is reentrant when we traverse methods of an inner class + currentMethods.addFirst(tree); + // effStack.push(targetSafeP != null ? new Effect(AlwaysSafe.class) : + // (targetPolyP != null ? new Effect(PolyUI.class) : + // (targetUIP != null ? new Effect(UI.class) : + // (range != null ? range.min : + // (isUIType(((TypeElement)methElt.getEnclosingElement())) ? new Effect(UI.class) : new + // Effect(AlwaysSafe.class)))))); + effStack.addFirst(atypeFactory.getDeclaredEffect(methElt)); + if (debugSpew) { + System.err.println( + "Pushing " + effStack.peek() + " onto the stack when checking " + methElt); + } - if ((targetUIP != null && (targetSafeP != null || targetPolyP != null)) - || (targetSafeP != null && targetPolyP != null)) { - checker.reportError(tree, "annotations.conflicts"); + Void ret = super.visitMethod(tree, p); + currentMethods.removeFirst(); + effStack.removeFirst(); + receiverType = previousReceiverType; + return ret; } - if (targetPolyP != null && !atypeFactory.isPolymorphicType(targetClassElt)) { - checker.reportError(tree, "polymorphism.invalid"); + + @Override + @SuppressWarnings("interning:not.interned") // comparing AST nodes + public Void visitNewClass(NewClassTree tree, Void p) { + Void v = super.visitNewClass(tree, p); + // If this is an anonymous inner class inferred to be @UI, scan up the path and re-check any + // assignments involving it. + if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { + // Backtrack path to the new class expression itself + TreePath path = getCurrentPath(); + while (path.getLeaf() != tree) { + assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; + path = path.getParentPath(); + } + scanUp(getCurrentPath().getParentPath()); + } + return v; } - if (targetUIP != null && atypeFactory.isUIType(targetClassElt)) { - checker.reportWarning(tree, "effects.redundant.uitype"); + + /** + * This method is called to traverse the path back up from any anonymous inner class or lambda + * which has been inferred to be UI affecting and re-run {@code commonAssignmentCheck()} as + * needed on places where the class declaration or lambda expression are being assigned to a + * variable, passed as a parameter or returned from a method. This is necessary because the + * normal visitor traversal only checks assignments on the way down the AST, before inference + * has had a chance to run. + * + * @param path the path to traverse up from a UI-affecting class + */ + private void scanUp(TreePath path) { + Tree tree = path.getLeaf(); + switch (tree.getKind()) { + case ASSIGNMENT: + AssignmentTree assignmentTree = (AssignmentTree) tree; + commonAssignmentCheck( + atypeFactory.getAnnotatedType(assignmentTree.getVariable()), + atypeFactory.getAnnotatedType(assignmentTree.getExpression()), + assignmentTree.getExpression(), + "assignment.type.incompatible"); + break; + case VARIABLE: + VariableTree variableTree = (VariableTree) tree; + commonAssignmentCheck( + atypeFactory.getAnnotatedType(variableTree), + atypeFactory.getAnnotatedType(variableTree.getInitializer()), + variableTree.getInitializer(), + "assignment.type.incompatible"); + break; + case METHOD_INVOCATION: + MethodInvocationTree invocationTree = (MethodInvocationTree) tree; + List args = invocationTree.getArguments(); + ParameterizedExecutableType mType = atypeFactory.methodFromUse(invocationTree); + AnnotatedExecutableType invokedMethod = mType.executableType; + ExecutableElement method = invokedMethod.getElement(); + CharSequence methodName = ElementUtils.getSimpleDescription(method); + List methodParams = method.getParameters(); + List paramTypes = invokedMethod.getParameterTypes(); + for (int i = 0; i < args.size(); ++i) { + if (args.get(i).getKind() == Tree.Kind.NEW_CLASS + || args.get(i).getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + commonAssignmentCheck( + paramTypes.get(i), + atypeFactory.getAnnotatedType(args.get(i)), + args.get(i), + "argument.type.incompatible", + methodParams.get(i), + methodName); + } + } + break; + case RETURN: + ReturnTree returnTree = (ReturnTree) tree; + if (returnTree.getExpression().getKind() == Tree.Kind.NEW_CLASS + || returnTree.getExpression().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + Tree enclosing = TreePathUtil.enclosingMethodOrLambda(path); + AnnotatedTypeMirror ret = null; + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = (MethodTree) enclosing; + boolean valid = validateTypeOf(enclosing); + if (valid) { + ret = atypeFactory.getMethodReturnType(enclosingMethod, returnTree); + } + } else { + ret = + atypeFactory + .getFunctionTypeFromTree((LambdaExpressionTree) enclosing) + .getReturnType(); + } + + if (ret != null) { + commonAssignmentCheck( + ret, + atypeFactory.getAnnotatedType(returnTree.getExpression()), + returnTree.getExpression(), + "return.type.incompatible"); + } + } + break; + case METHOD: + // Stop scanning at method boundaries, since the expression can't escape the method + // without either being assigned to a field or returned. + return; + case CLASS: + // Can't ever happen, because we stop scanning at either method or field initializer + // boundaries + assert false; + return; + default: + scanUp(path.getParentPath()); + } } - // TODO: Report an error for polymorphic method bodies??? Until we fix the receiver - // defaults, it won't really be correct - @SuppressWarnings("unused") // call has side effects - Effect.EffectRange range = - atypeFactory.findInheritedEffectRange( - ((TypeElement) methElt.getEnclosingElement()), methElt, true, tree); - // if (targetUIP == null && targetSafeP == null && targetPolyP == null) { - // implicitly annotate this method with the LUB of the effects of the methods it overrides - // atypeFactory.fromElement(methElt).addAnnotation(range != null ? range.min.getAnnot() - // : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? UI.class : - // AlwaysSafe.class)); - // TODO: This line does nothing! AnnotatedTypeMirror.addAnnotation - // silently ignores non-qualifier annotations! - // System.err.println("ERROR: TREE ANNOTATOR SHOULD HAVE ADDED EXPLICIT ANNOTATION! (" - // +tree.getName()+")"); - // atypeFactory - // .fromElement(methElt) - // .addAnnotation(atypeFactory.getDeclaredEffect(methElt).getAnnot()); + // @Override + // public Void visitMemberSelect(MemberSelectTree tree, Void p) { + // TODO: Same effect checks as for methods + // return super.visitMemberSelect(tree, p); // } - // We hang onto the current method here for ease. We back up the old - // current method because this code is reentrant when we traverse methods of an inner class - currentMethods.addFirst(tree); - // effStack.push(targetSafeP != null ? new Effect(AlwaysSafe.class) : - // (targetPolyP != null ? new Effect(PolyUI.class) : - // (targetUIP != null ? new Effect(UI.class) : - // (range != null ? range.min : - // (isUIType(((TypeElement)methElt.getEnclosingElement())) ? new Effect(UI.class) : new - // Effect(AlwaysSafe.class)))))); - effStack.addFirst(atypeFactory.getDeclaredEffect(methElt)); - if (debugSpew) { - System.err.println("Pushing " + effStack.peek() + " onto the stack when checking " + methElt); - } + // @Override + // public void processClassTree(ClassTree tree) { + // TODO: Check constraints on this class decl vs. parent class decl., and interfaces + // TODO: This has to wait for now: maybe this will be easier with the isValidUse on the + // TypeFactory. + // AnnotatedTypeMirror.AnnotatedDeclaredType atype = atypeFactory.fromClass(tree); + + // Push a null method and UI effect onto the stack for static field initialization + // TODO: Figure out if this is safe! For static data, almost certainly, + // but for statically initialized instance fields, I'm assuming those + // are implicitly moved into each constructor, which must then be @UI. + // currentMethods.addFirst(null); + // effStack.addFirst(new Effect(UIEffect.class)); + // super.processClassTree(tree); + // currentMethods.removeFirst(); + // effStack.removeFirst(); + // } - Void ret = super.visitMethod(tree, p); - currentMethods.removeFirst(); - effStack.removeFirst(); - receiverType = previousReceiverType; - return ret; - } - - @Override - @SuppressWarnings("interning:not.interned") // comparing AST nodes - public Void visitNewClass(NewClassTree tree, Void p) { - Void v = super.visitNewClass(tree, p); - // If this is an anonymous inner class inferred to be @UI, scan up the path and re-check any - // assignments involving it. - if (atypeFactory.isDirectlyMarkedUIThroughInference(tree)) { - // Backtrack path to the new class expression itself - TreePath path = getCurrentPath(); - while (path.getLeaf() != tree) { - assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; - path = path.getParentPath(); - } - scanUp(getCurrentPath().getParentPath()); - } - return v; - } - - /** - * This method is called to traverse the path back up from any anonymous inner class or lambda - * which has been inferred to be UI affecting and re-run {@code commonAssignmentCheck()} as needed - * on places where the class declaration or lambda expression are being assigned to a variable, - * passed as a parameter or returned from a method. This is necessary because the normal visitor - * traversal only checks assignments on the way down the AST, before inference has had a chance to - * run. - * - * @param path the path to traverse up from a UI-affecting class - */ - private void scanUp(TreePath path) { - Tree tree = path.getLeaf(); - switch (tree.getKind()) { - case ASSIGNMENT: - AssignmentTree assignmentTree = (AssignmentTree) tree; - commonAssignmentCheck( - atypeFactory.getAnnotatedType(assignmentTree.getVariable()), - atypeFactory.getAnnotatedType(assignmentTree.getExpression()), - assignmentTree.getExpression(), - "assignment.type.incompatible"); - break; - case VARIABLE: - VariableTree variableTree = (VariableTree) tree; - commonAssignmentCheck( - atypeFactory.getAnnotatedType(variableTree), - atypeFactory.getAnnotatedType(variableTree.getInitializer()), - variableTree.getInitializer(), - "assignment.type.incompatible"); - break; - case METHOD_INVOCATION: - MethodInvocationTree invocationTree = (MethodInvocationTree) tree; - List args = invocationTree.getArguments(); - ParameterizedExecutableType mType = atypeFactory.methodFromUse(invocationTree); - AnnotatedExecutableType invokedMethod = mType.executableType; - ExecutableElement method = invokedMethod.getElement(); - CharSequence methodName = ElementUtils.getSimpleDescription(method); - List methodParams = method.getParameters(); - List paramTypes = invokedMethod.getParameterTypes(); - for (int i = 0; i < args.size(); ++i) { - if (args.get(i).getKind() == Tree.Kind.NEW_CLASS - || args.get(i).getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - commonAssignmentCheck( - paramTypes.get(i), - atypeFactory.getAnnotatedType(args.get(i)), - args.get(i), - "argument.type.incompatible", - methodParams.get(i), - methodName); - } - } - break; - case RETURN: - ReturnTree returnTree = (ReturnTree) tree; - if (returnTree.getExpression().getKind() == Tree.Kind.NEW_CLASS - || returnTree.getExpression().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - Tree enclosing = TreePathUtil.enclosingMethodOrLambda(path); - AnnotatedTypeMirror ret = null; - if (enclosing.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = (MethodTree) enclosing; - boolean valid = validateTypeOf(enclosing); - if (valid) { - ret = atypeFactory.getMethodReturnType(enclosingMethod, returnTree); - } - } else { - ret = - atypeFactory - .getFunctionTypeFromTree((LambdaExpressionTree) enclosing) - .getReturnType(); - } - - if (ret != null) { - commonAssignmentCheck( - ret, - atypeFactory.getAnnotatedType(returnTree.getExpression()), - returnTree.getExpression(), - "return.type.incompatible"); - } + @Override + public void processClassTree(ClassTree classTree) { + AnnotatedDeclaredType previousClassType = classType; + AnnotatedDeclaredType previousReceiverType = receiverType; + receiverType = null; + classType = atypeFactory.getAnnotatedType(TreeUtils.elementFromDeclaration(classTree)); + try { + super.processClassTree(classTree); + } finally { + classType = previousClassType; + receiverType = previousReceiverType; } - break; - case METHOD: - // Stop scanning at method boundaries, since the expression can't escape the method - // without either being assigned to a field or returned. - return; - case CLASS: - // Can't ever happen, because we stop scanning at either method or field initializer - // boundaries - assert false; - return; - default: - scanUp(path.getParentPath()); - } - } - - // @Override - // public Void visitMemberSelect(MemberSelectTree tree, Void p) { - // TODO: Same effect checks as for methods - // return super.visitMemberSelect(tree, p); - // } - - // @Override - // public void processClassTree(ClassTree tree) { - // TODO: Check constraints on this class decl vs. parent class decl., and interfaces - // TODO: This has to wait for now: maybe this will be easier with the isValidUse on the - // TypeFactory. - // AnnotatedTypeMirror.AnnotatedDeclaredType atype = atypeFactory.fromClass(tree); - - // Push a null method and UI effect onto the stack for static field initialization - // TODO: Figure out if this is safe! For static data, almost certainly, - // but for statically initialized instance fields, I'm assuming those - // are implicitly moved into each constructor, which must then be @UI. - // currentMethods.addFirst(null); - // effStack.addFirst(new Effect(UIEffect.class)); - // super.processClassTree(tree); - // currentMethods.removeFirst(); - // effStack.removeFirst(); - // } - - @Override - public void processClassTree(ClassTree classTree) { - AnnotatedDeclaredType previousClassType = classType; - AnnotatedDeclaredType previousReceiverType = receiverType; - receiverType = null; - classType = atypeFactory.getAnnotatedType(TreeUtils.elementFromDeclaration(classTree)); - try { - super.processClassTree(classTree); - } finally { - classType = previousClassType; - receiverType = previousReceiverType; } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java index 6f18ec73303..5ff26f76fa5 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/I18nAnnotatedTypeFactory.java @@ -4,11 +4,7 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.checker.i18n.qual.Localized; import org.checkerframework.checker.i18n.qual.UnknownLocalized; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; @@ -19,53 +15,60 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationBuilder; -public class I18nAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - - public I18nAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>(Arrays.asList(Localized.class, UnknownLocalized.class)); - } +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nTreeAnnotator(this)); - } +import javax.lang.model.element.AnnotationMirror; - /** Do not propagate types through binary/compound operations. */ - private class I18nTreeAnnotator extends TreeAnnotator { - /** The @{@link Localized} annotation. */ - private final AnnotationMirror LOCALIZED = - AnnotationBuilder.fromClass(elements, Localized.class); +public class I18nAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public I18nTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + public I18nAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); } @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(LOCALIZED); - return null; + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>(Arrays.asList(Localized.class, UnknownLocalized.class)); } @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(LOCALIZED); - return null; + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nTreeAnnotator(this)); } - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(LOCALIZED)) { - if (tree.getKind() == Tree.Kind.STRING_LITERAL && tree.getValue().equals("")) { - type.addAnnotation(LOCALIZED); + /** Do not propagate types through binary/compound operations. */ + private class I18nTreeAnnotator extends TreeAnnotator { + /** The @{@link Localized} annotation. */ + private final AnnotationMirror LOCALIZED = + AnnotationBuilder.fromClass(elements, Localized.class); + + public I18nTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(LOCALIZED); + return null; + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(LOCALIZED); + return null; + } + + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(LOCALIZED)) { + if (tree.getKind() == Tree.Kind.STRING_LITERAL && tree.getValue().equals("")) { + type.addAnnotation(LOCALIZED); + } + } + return super.visitLiteral(tree, type); } - } - return super.visitLiteral(tree, type); } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java b/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java index d08f2b13cc2..47217ffc53a 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/I18nChecker.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.i18n; -import java.util.ArrayList; -import java.util.Collection; import org.checkerframework.framework.source.AggregateChecker; import org.checkerframework.framework.source.SourceChecker; +import java.util.ArrayList; +import java.util.Collection; + /** * A type-checker that enforces (and finds the violations of) two properties: * @@ -20,11 +21,11 @@ */ public class I18nChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - Collection> checkers = new ArrayList<>(2); - checkers.add(I18nSubchecker.class); - checkers.add(LocalizableKeyChecker.class); - return checkers; - } + @Override + protected Collection> getSupportedCheckers() { + Collection> checkers = new ArrayList<>(2); + checkers.add(I18nSubchecker.class); + checkers.add(LocalizableKeyChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java index 5141db26f48..1cf79a45706 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyAnnotatedTypeFactory.java @@ -1,9 +1,5 @@ package org.checkerframework.checker.i18n; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; import org.checkerframework.checker.i18n.qual.LocalizableKey; import org.checkerframework.checker.i18n.qual.LocalizableKeyBottom; import org.checkerframework.checker.i18n.qual.UnknownLocalizableKey; @@ -12,27 +8,35 @@ import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + /** A PropertyKeyATF that uses LocalizableKey to annotate the keys. */ public class LocalizableKeyAnnotatedTypeFactory extends PropertyKeyAnnotatedTypeFactory { - public LocalizableKeyAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Does not call postInit() because its superclass does. - // If we ever add code to this constructor, it needs to: - // * call a superclass constructor that does not call postInit(), and - // * call postInit() itself. - } + public LocalizableKeyAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Does not call postInit() because its superclass does. + // If we ever add code to this constructor, it needs to: + // * call a superclass constructor that does not call postInit(), and + // * call postInit() itself. + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - LocalizableKey.class, LocalizableKeyBottom.class, UnknownLocalizableKey.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + LocalizableKey.class, + LocalizableKeyBottom.class, + UnknownLocalizableKey.class)); + } - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createBasicTreeAnnotator(), new KeyLookupTreeAnnotator(this, LocalizableKey.class)); - } + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createBasicTreeAnnotator(), + new KeyLookupTreeAnnotator(this, LocalizableKey.class)); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java index 64fc642f6e9..9099c7a124d 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/i18n/LocalizableKeyChecker.java @@ -1,9 +1,11 @@ package org.checkerframework.checker.i18n; +import org.checkerframework.checker.propkey.PropertyKeyChecker; + import java.util.Locale; import java.util.ResourceBundle; + import javax.annotation.processing.SupportedOptions; -import org.checkerframework.checker.propkey.PropertyKeyChecker; /** * A type-checker that checks that only valid localizable keys are used when using localizing diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java index 175ea40d10e..72ff676c2d7 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java @@ -2,18 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; -import java.util.ResourceBundle; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.qual.I18nFormat; import org.checkerframework.checker.i18nformatter.qual.I18nFormatBottom; @@ -36,6 +25,20 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.reflection.Signatures; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.ResourceBundle; + +import javax.lang.model.element.AnnotationMirror; + /** * Adds {@link I18nFormat} to the type of tree, if it is a {@code String} or {@code char} literal * that represents a satisfiable format. The annotation's value is set to be a list of appropriate @@ -49,321 +52,337 @@ */ public class I18nFormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link I18nUnknownFormat} annotation. */ - protected final AnnotationMirror I18NUNKNOWNFORMAT = - AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); - - /** The @{@link I18nFormatBottom} annotation. */ - protected final AnnotationMirror I18NFORMATBOTTOM = - AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); - - /** The fully-qualified name of {@link I18nFormat}. */ - protected static final @CanonicalName String I18NFORMAT_NAME = - I18nFormat.class.getCanonicalName(); - - /** The fully-qualified name of {@link I18nInvalidFormat}. */ - protected static final @CanonicalName String I18NINVALIDFORMAT_NAME = - I18nInvalidFormat.class.getCanonicalName(); - - /** The fully-qualified name of {@link I18nFormatFor}. */ - protected static final @CanonicalName String I18NFORMATFOR_NAME = - I18nFormatFor.class.getCanonicalName(); - - /** Map from a translation file key to its value in the file. */ - public final Map translations = Collections.unmodifiableMap(buildLookup()); - - /** Syntax tree utilities. */ - protected final I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(checker); - - /** Create a new I18nFormatterAnnotatedTypeFactory. */ - public I18nFormatterAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - this.postInit(); - } - - /** - * Builds a map from a translation file key to its value in the file. Builds the map for all files - * in the "-Apropfiles" command-line argument. - * - *

          Called only once, during initialization. - * - * @return a map from a translation file key to its value in the file - */ - private Map buildLookup() { - Map result = new HashMap<>(); - - if (checker.hasOption("propfiles")) { - for (String propfile : checker.getStringsOption("propfiles", File.pathSeparator)) { - Properties prop = new Properties(); - ClassLoader cl = this.getClass().getClassLoader(); - if (cl == null) { - // The class loader is null if the system class loader was used. - cl = ClassLoader.getSystemClassLoader(); - } - try (InputStream in = cl.getResourceAsStream(propfile)) { - if (in != null) { - prop.load(in); - } else { - // If the classloader didn't manage to load the file, try whether a - // FileInputStream works. For absolute paths this might help. - try (InputStream fis = new FileInputStream(propfile)) { - prop.load(fis); - } catch (FileNotFoundException e) { - System.err.println("Couldn't find the properties file: " + propfile); - // report(null, "propertykeychecker.filenotfound", propfile); - // return Collections.emptySet(); - continue; - } - } - - for (String key : prop.stringPropertyNames()) { - result.put(key, prop.getProperty(key)); - } - } catch (Exception e) { - // TODO: is there a nicer way to report messages, that are not connected to - // an AST node? One cannot use `report`, because it needs a node. - System.err.println( - "Exception in PropertyKeyChecker.keysOfPropertyFile while processing " - + propfile - + ": " - + e); - e.printStackTrace(); - } - } - } + /** The @{@link I18nUnknownFormat} annotation. */ + protected final AnnotationMirror I18NUNKNOWNFORMAT = + AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); - if (checker.hasOption("bundlenames")) { - for (String bundleName : checker.getStringsOption("bundlenames", ':')) { - if (!Signatures.isBinaryName(bundleName)) { - System.err.println( - "Malformed resource bundle: <" + bundleName + "> should be a binary name."); - continue; - } - ResourceBundle bundle = ResourceBundle.getBundle(bundleName); - if (bundle == null) { - System.err.println( - "Couldn't find the resource bundle: <" - + bundleName - + "> for locale <" - + Locale.getDefault() - + ">."); - continue; - } + /** The @{@link I18nFormatBottom} annotation. */ + protected final AnnotationMirror I18NFORMATBOTTOM = + AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); - for (String key : bundle.keySet()) { - result.put(key, bundle.getString(key)); - } - } - } + /** The fully-qualified name of {@link I18nFormat}. */ + protected static final @CanonicalName String I18NFORMAT_NAME = + I18nFormat.class.getCanonicalName(); - return result; - } + /** The fully-qualified name of {@link I18nInvalidFormat}. */ + protected static final @CanonicalName String I18NINVALIDFORMAT_NAME = + I18nInvalidFormat.class.getCanonicalName(); - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new I18nFormatterQualifierHierarchy(); - } + /** The fully-qualified name of {@link I18nFormatFor}. */ + protected static final @CanonicalName String I18NFORMATFOR_NAME = + I18nFormatFor.class.getCanonicalName(); - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new I18nFormatterTreeAnnotator(this)); - } + /** Map from a translation file key to its value in the file. */ + public final Map translations = Collections.unmodifiableMap(buildLookup()); - private class I18nFormatterTreeAnnotator extends TreeAnnotator { - public I18nFormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + /** Syntax tree utilities. */ + protected final I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(checker); - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(I18NUNKNOWNFORMAT)) { - String format = null; - if (tree.getKind() == Tree.Kind.STRING_LITERAL) { - format = (String) tree.getValue(); - } - if (format != null) { - AnnotationMirror anno; - try { - I18nConversionCategory[] cs = I18nFormatUtil.formatParameterCategories(format); - anno = I18nFormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs); - } catch (IllegalArgumentException e) { - anno = - I18nFormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation( - e); - } - type.addAnnotation(anno); - } - } + /** Create a new I18nFormatterAnnotatedTypeFactory. */ + public I18nFormatterAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); - return super.visitLiteral(tree, type); + this.postInit(); } - } - /** I18nFormatterQualifierHierarchy. */ - class I18nFormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** Qualifier kind for the @{@link I18nFormat} annotation. */ - private final QualifierKind I18NFORMAT_KIND; + /** + * Builds a map from a translation file key to its value in the file. Builds the map for all + * files in the "-Apropfiles" command-line argument. + * + *

          Called only once, during initialization. + * + * @return a map from a translation file key to its value in the file + */ + private Map buildLookup() { + Map result = new HashMap<>(); + + if (checker.hasOption("propfiles")) { + for (String propfile : checker.getStringsOption("propfiles", File.pathSeparator)) { + Properties prop = new Properties(); + ClassLoader cl = this.getClass().getClassLoader(); + if (cl == null) { + // The class loader is null if the system class loader was used. + cl = ClassLoader.getSystemClassLoader(); + } + try (InputStream in = cl.getResourceAsStream(propfile)) { + if (in != null) { + prop.load(in); + } else { + // If the classloader didn't manage to load the file, try whether a + // FileInputStream works. For absolute paths this might help. + try (InputStream fis = new FileInputStream(propfile)) { + prop.load(fis); + } catch (FileNotFoundException e) { + System.err.println("Couldn't find the properties file: " + propfile); + // report(null, "propertykeychecker.filenotfound", propfile); + // return Collections.emptySet(); + continue; + } + } + + for (String key : prop.stringPropertyNames()) { + result.put(key, prop.getProperty(key)); + } + } catch (Exception e) { + // TODO: is there a nicer way to report messages, that are not connected to + // an AST node? One cannot use `report`, because it needs a node. + System.err.println( + "Exception in PropertyKeyChecker.keysOfPropertyFile while processing " + + propfile + + ": " + + e); + e.printStackTrace(); + } + } + } - /** Qualifier kind for the @{@link I18nFormatFor} annotation. */ - private final QualifierKind I18NFORMATFOR_KIND; + if (checker.hasOption("bundlenames")) { + for (String bundleName : checker.getStringsOption("bundlenames", ':')) { + if (!Signatures.isBinaryName(bundleName)) { + System.err.println( + "Malformed resource bundle: <" + + bundleName + + "> should be a binary name."); + continue; + } + ResourceBundle bundle = ResourceBundle.getBundle(bundleName); + if (bundle == null) { + System.err.println( + "Couldn't find the resource bundle: <" + + bundleName + + "> for locale <" + + Locale.getDefault() + + ">."); + continue; + } + + for (String key : bundle.keySet()) { + result.put(key, bundle.getString(key)); + } + } + } - /** Qualifier kind for the @{@link I18nInvalidFormat} annotation. */ - private final QualifierKind I18NINVALIDFORMAT_KIND; + return result; + } - /** Creates I18nFormatterQualifierHierarchy. */ - public I18nFormatterQualifierHierarchy() { - super( - I18nFormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - I18nFormatterAnnotatedTypeFactory.this); - this.I18NFORMAT_KIND = this.getQualifierKind(I18NFORMAT_NAME); - this.I18NFORMATFOR_KIND = this.getQualifierKind(I18NFORMATFOR_NAME); - this.I18NINVALIDFORMAT_KIND = this.getQualifierKind(I18NINVALIDFORMAT_NAME); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new I18nFormatterQualifierHierarchy(); } @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == I18NFORMAT_KIND && superKind == I18NFORMAT_KIND) { - - I18nConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno); - I18nConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno); - - if (rhsArgTypes.length > lhsArgTypes.length) { - return false; + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new I18nFormatterTreeAnnotator(this)); + } + + private class I18nFormatterTreeAnnotator extends TreeAnnotator { + public I18nFormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - for (int i = 0; i < rhsArgTypes.length; ++i) { - if (!I18nConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { - return false; - } + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(I18NUNKNOWNFORMAT)) { + String format = null; + if (tree.getKind() == Tree.Kind.STRING_LITERAL) { + format = (String) tree.getValue(); + } + if (format != null) { + AnnotationMirror anno; + try { + I18nConversionCategory[] cs = + I18nFormatUtil.formatParameterCategories(format); + anno = + I18nFormatterAnnotatedTypeFactory.this.treeUtil + .categoriesToFormatAnnotation(cs); + } catch (IllegalArgumentException e) { + anno = + I18nFormatterAnnotatedTypeFactory.this.treeUtil + .exceptionToInvalidFormatAnnotation(e); + } + type.addAnnotation(anno); + } + } + + return super.visitLiteral(tree, type); } - return true; - } else if ((subKind == I18NINVALIDFORMAT_KIND && superKind == I18NINVALIDFORMAT_KIND) - || (subKind == I18NFORMATFOR_KIND && superKind == I18NFORMATFOR_KIND)) { - return Objects.equals( - treeUtil.getI18nInvalidFormatValue(subAnno), - treeUtil.getI18nInvalidFormatValue(superAnno)); - } - throw new TypeSystemError("Unexpected QualifierKinds: %s %s", subKind, superKind); } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.isBottom()) { - return anno2; - } else if (qualifierKind2.isBottom()) { - return anno1; - } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { - I18nConversionCategory[] shorterArgTypesList = treeUtil.formatAnnotationToCategories(anno1); - I18nConversionCategory[] longerArgTypesList = treeUtil.formatAnnotationToCategories(anno2); - if (shorterArgTypesList.length > longerArgTypesList.length) { - I18nConversionCategory[] temp = longerArgTypesList; - longerArgTypesList = shorterArgTypesList; - shorterArgTypesList = temp; - } + /** I18nFormatterQualifierHierarchy. */ + class I18nFormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. + /** Qualifier kind for the @{@link I18nFormat} annotation. */ + private final QualifierKind I18NFORMAT_KIND; - I18nConversionCategory[] resultArgTypes = - new I18nConversionCategory[longerArgTypesList.length]; + /** Qualifier kind for the @{@link I18nFormatFor} annotation. */ + private final QualifierKind I18NFORMATFOR_KIND; - for (int i = 0; i < shorterArgTypesList.length; ++i) { - resultArgTypes[i] = - I18nConversionCategory.intersect(shorterArgTypesList[i], longerArgTypesList[i]); - } - System.arraycopy( - longerArgTypesList, - shorterArgTypesList.length, - resultArgTypes, - shorterArgTypesList.length, - longerArgTypesList.length - shorterArgTypesList.length); - return treeUtil.categoriesToFormatAnnotation(resultArgTypes); - } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND - && qualifierKind2 == I18NINVALIDFORMAT_KIND) { - assert !anno1.getElementValues().isEmpty(); - assert !anno1.getElementValues().isEmpty(); - - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; - } + /** Qualifier kind for the @{@link I18nInvalidFormat} annotation. */ + private final QualifierKind I18NINVALIDFORMAT_KIND; - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " or " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } else if (qualifierKind1 == I18NFORMATFOR_KIND && AnnotationUtils.areSame(anno1, anno2)) { - // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. - return anno1; - } - - return I18NUNKNOWNFORMAT; - } + /** Creates I18nFormatterQualifierHierarchy. */ + public I18nFormatterQualifierHierarchy() { + super( + I18nFormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + I18nFormatterAnnotatedTypeFactory.this); + this.I18NFORMAT_KIND = this.getQualifierKind(I18NFORMAT_NAME); + this.I18NFORMATFOR_KIND = this.getQualifierKind(I18NFORMATFOR_NAME); + this.I18NINVALIDFORMAT_KIND = this.getQualifierKind(I18NINVALIDFORMAT_NAME); + } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror anno1, - QualifierKind qualifierKind1, - AnnotationMirror anno2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1.isTop()) { - return anno2; - } else if (qualifierKind2.isTop()) { - return anno1; - } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { - I18nConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1); - I18nConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2); - - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - int length = anno1ArgTypes.length; - if (anno2ArgTypes.length < length) { - length = anno2ArgTypes.length; + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == I18NFORMAT_KIND && superKind == I18NFORMAT_KIND) { + + I18nConversionCategory[] rhsArgTypes = + treeUtil.formatAnnotationToCategories(subAnno); + I18nConversionCategory[] lhsArgTypes = + treeUtil.formatAnnotationToCategories(superAnno); + + if (rhsArgTypes.length > lhsArgTypes.length) { + return false; + } + + for (int i = 0; i < rhsArgTypes.length; ++i) { + if (!I18nConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { + return false; + } + } + return true; + } else if ((subKind == I18NINVALIDFORMAT_KIND && superKind == I18NINVALIDFORMAT_KIND) + || (subKind == I18NFORMATFOR_KIND && superKind == I18NFORMATFOR_KIND)) { + return Objects.equals( + treeUtil.getI18nInvalidFormatValue(subAnno), + treeUtil.getI18nInvalidFormatValue(superAnno)); + } + throw new TypeSystemError("Unexpected QualifierKinds: %s %s", subKind, superKind); } - I18nConversionCategory[] anno3ArgTypes = new I18nConversionCategory[length]; + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.isBottom()) { + return anno2; + } else if (qualifierKind2.isBottom()) { + return anno1; + } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { + I18nConversionCategory[] shorterArgTypesList = + treeUtil.formatAnnotationToCategories(anno1); + I18nConversionCategory[] longerArgTypesList = + treeUtil.formatAnnotationToCategories(anno2); + if (shorterArgTypesList.length > longerArgTypesList.length) { + I18nConversionCategory[] temp = longerArgTypesList; + longerArgTypesList = shorterArgTypesList; + shorterArgTypesList = temp; + } + + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + + I18nConversionCategory[] resultArgTypes = + new I18nConversionCategory[longerArgTypesList.length]; + + for (int i = 0; i < shorterArgTypesList.length; ++i) { + resultArgTypes[i] = + I18nConversionCategory.intersect( + shorterArgTypesList[i], longerArgTypesList[i]); + } + System.arraycopy( + longerArgTypesList, + shorterArgTypesList.length, + resultArgTypes, + shorterArgTypesList.length, + longerArgTypesList.length - shorterArgTypesList.length); + return treeUtil.categoriesToFormatAnnotation(resultArgTypes); + } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND + && qualifierKind2 == I18NINVALIDFORMAT_KIND) { + assert !anno1.getElementValues().isEmpty(); + assert !anno1.getElementValues().isEmpty(); + + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; + } + + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " or " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } else if (qualifierKind1 == I18NFORMATFOR_KIND + && AnnotationUtils.areSame(anno1, anno2)) { + // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. + return anno1; + } - for (int i = 0; i < length; ++i) { - anno3ArgTypes[i] = I18nConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); + return I18NUNKNOWNFORMAT; } - return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); - } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND - && qualifierKind2 == I18NINVALIDFORMAT_KIND) { - assert !anno2.getElementValues().isEmpty(); + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror anno1, + QualifierKind qualifierKind1, + AnnotationMirror anno2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1.isTop()) { + return anno2; + } else if (qualifierKind2.isTop()) { + return anno1; + } else if (qualifierKind1 == I18NFORMAT_KIND && qualifierKind2 == I18NFORMAT_KIND) { + I18nConversionCategory[] anno1ArgTypes = + treeUtil.formatAnnotationToCategories(anno1); + I18nConversionCategory[] anno2ArgTypes = + treeUtil.formatAnnotationToCategories(anno2); + + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + int length = anno1ArgTypes.length; + if (anno2ArgTypes.length < length) { + length = anno2ArgTypes.length; + } + + I18nConversionCategory[] anno3ArgTypes = new I18nConversionCategory[length]; + + for (int i = 0; i < length; ++i) { + anno3ArgTypes[i] = + I18nConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); + } + return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); + } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND + && qualifierKind2 == I18NINVALIDFORMAT_KIND) { + + assert !anno2.getElementValues().isEmpty(); + + if (AnnotationUtils.areSame(anno1, anno2)) { + return anno1; + } + + return treeUtil.stringToInvalidFormatAnnotation( + "(" + + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) + + " and " + + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) + + ")"); + } else if (qualifierKind1 == I18NFORMATFOR_KIND + && AnnotationUtils.areSame(anno1, anno2)) { + // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. + return anno1; + } - if (AnnotationUtils.areSame(anno1, anno2)) { - return anno1; + return I18NFORMATBOTTOM; } - - return treeUtil.stringToInvalidFormatAnnotation( - "(" - + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) - + " and " - + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) - + ")"); - } else if (qualifierKind1 == I18NFORMATFOR_KIND && AnnotationUtils.areSame(anno1, anno2)) { - // @I18nFormatFor annotations are unrelated by subtyping, unless they are identical. - return anno1; - } - - return I18NFORMATBOTTOM; } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java index af8910612f3..01749e614ec 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterChecker.java @@ -1,9 +1,10 @@ package org.checkerframework.checker.i18nformatter; -import javax.annotation.processing.SupportedOptions; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; +import javax.annotation.processing.SupportedOptions; + /** * A type-checker plug-in for the qualifier that finds syntactically invalid i18n-formatter calls * (MessageFormat.format()). diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java index a366029cd7c..5dd05f7058a 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.i18nformatter; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat; @@ -16,6 +15,8 @@ import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationBuilder; +import javax.lang.model.element.AnnotationMirror; + /** * The transfer function for the Internationalization Format String Checker. * @@ -23,65 +24,69 @@ */ public class I18nFormatterTransfer extends CFTransfer { - public I18nFormatterTransfer(CFAnalysis analysis) { - super(analysis); - } + public I18nFormatterTransfer(CFAnalysis analysis) { + super(analysis); + } + + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput in) { + I18nFormatterAnnotatedTypeFactory atypeFactory = + (I18nFormatterAnnotatedTypeFactory) analysis.getTypeFactory(); + TransferResult result = super.visitMethodInvocation(node, in); + I18nFormatterTreeUtil tu = atypeFactory.treeUtil; - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput in) { - I18nFormatterAnnotatedTypeFactory atypeFactory = - (I18nFormatterAnnotatedTypeFactory) analysis.getTypeFactory(); - TransferResult result = super.visitMethodInvocation(node, in); - I18nFormatterTreeUtil tu = atypeFactory.treeUtil; + // If hasFormat is called, make sure that the format string is annotated correctly + if (tu.isHasFormatCall(node, atypeFactory)) { + CFStore thenStore = result.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + Result cats = tu.getHasFormatCallCategories(node); + if (cats.value() == null) { + tu.failure(cats, "i18nformat.indirect.arguments"); + } else { + JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); + AnnotationMirror anno = + atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); + thenStore.insertValue(firstParam, anno); + } + return newResult; + } - // If hasFormat is called, make sure that the format string is annotated correctly - if (tu.isHasFormatCall(node, atypeFactory)) { - CFStore thenStore = result.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - Result cats = tu.getHasFormatCallCategories(node); - if (cats.value() == null) { - tu.failure(cats, "i18nformat.indirect.arguments"); - } else { - JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); - AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); - thenStore.insertValue(firstParam, anno); - } - return newResult; - } + // If isFormat is called, annotate the format string with I18nInvalidFormat + if (tu.isIsFormatCall(node, atypeFactory)) { + CFStore thenStore = result.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); + AnnotationBuilder builder = + new AnnotationBuilder(tu.processingEnv, I18nInvalidFormat.class); + // No need to set a value of @I18nInvalidFormat + builder.setValue("value", ""); + elseStore.insertValue(firstParam, builder.build()); + return newResult; + } - // If isFormat is called, annotate the format string with I18nInvalidFormat - if (tu.isIsFormatCall(node, atypeFactory)) { - CFStore thenStore = result.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - JavaExpression firstParam = JavaExpression.fromNode(node.getArgument(0)); - AnnotationBuilder builder = new AnnotationBuilder(tu.processingEnv, I18nInvalidFormat.class); - // No need to set a value of @I18nInvalidFormat - builder.setValue("value", ""); - elseStore.insertValue(firstParam, builder.build()); - return newResult; - } + // @I18nMakeFormat that will be used to annotate ResourceBundle.getString() so that when the + // getString() method is called, this will check if the given key exist in the translation + // file and annotate the result string with the correct format annotation according to the + // corresponding key's value. + if (tu.isMakeFormatCall(node, atypeFactory)) { + Result cats = tu.makeFormatCallCategories(node, atypeFactory); + if (cats.value() == null) { + tu.failure(cats, "i18nformat.key.not.found"); + } else { + AnnotationMirror anno = + atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); + CFValue newResultValue = + analysis.createSingleAnnotationValue( + anno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } + } - // @I18nMakeFormat that will be used to annotate ResourceBundle.getString() so that when the - // getString() method is called, this will check if the given key exist in the translation - // file and annotate the result string with the correct format annotation according to the - // corresponding key's value. - if (tu.isMakeFormatCall(node, atypeFactory)) { - Result cats = tu.makeFormatCallCategories(node, atypeFactory); - if (cats.value() == null) { - tu.failure(cats, "i18nformat.key.not.found"); - } else { - AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); - CFValue newResultValue = - analysis.createSingleAnnotationValue(anno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } + return result; } - - return result; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java index 1077510e08c..fb3f4f9f52a 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java @@ -5,21 +5,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.SimpleTreeVisitor; -import java.util.List; -import java.util.Map; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.NullType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor8; -import javax.lang.model.util.SimpleTypeVisitor8; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; @@ -48,6 +34,23 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import java.util.List; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; + /** * This class provides a collection of utilities to ease working with syntax trees that have * something to do with I18nFormatters. @@ -55,530 +58,545 @@ * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker */ public class I18nFormatterTreeUtil { - /** The checker. */ - public final BaseTypeChecker checker; - - /** The processing environment. */ - public final ProcessingEnvironment processingEnv; - - /** The value() element/field of an @I18nFormat annotation. */ - protected final ExecutableElement i18nFormatValueElement; - - /** The value() element/field of an @I18nFormatFor annotation. */ - protected final ExecutableElement i18nFormatForValueElement; - - /** The value() element/field of an @I18nInvalidFormat annotation. */ - protected final ExecutableElement i18nInvalidFormatValueElement; - - /** - * Creates a new I18nFormatterTreeUtil. - * - * @param checker the checker - */ - public I18nFormatterTreeUtil(BaseTypeChecker checker) { - this.checker = checker; - this.processingEnv = checker.getProcessingEnvironment(); - i18nFormatValueElement = TreeUtils.getMethod(I18nFormat.class, "value", 0, processingEnv); - i18nFormatForValueElement = TreeUtils.getMethod(I18nFormatFor.class, "value", 0, processingEnv); - i18nInvalidFormatValueElement = - TreeUtils.getMethod(I18nInvalidFormat.class, "value", 0, processingEnv); - } - - /** Describe the format annotation type. */ - public enum FormatType { - I18NINVALID, - I18NFORMAT, - I18NFORMATFOR - } - - /** - * Takes an exception that describes an invalid formatter string and returns a syntax trees - * element that represents a {@link I18nInvalidFormat} annotation with the exception's error - * message as value. - */ - public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalArgumentException ex) { - return stringToInvalidFormatAnnotation(ex.getMessage()); - } - - /** - * Creates an {@link I18nInvalidFormat} annotation with the given string as its value. - * - * @param invalidFormatString an invalid formatter string - * @return an {@link I18nInvalidFormat} annotation with the given string as its value - */ - /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation(String invalidFormatString) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", invalidFormatString); - return builder.build(); - } - - /** - * Gets the value() element/field out of an I18nInvalidFormat annotation. - * - * @param anno an I18nInvalidFormat annotation - * @return its value() element/field, or null if it does not have one - */ - /*package-private*/ @Nullable String getI18nInvalidFormatValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValue(anno, i18nInvalidFormatValueElement, String.class, null); - } - - /** - * Gets the value() element/field out of an I18NFormatFor annotation. - * - * @param anno an I18NFormatFor annotation - * @return its value() element/field - */ - /*package-private*/ String getI18nFormatForValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValue(anno, i18nFormatForValueElement, String.class); - } - - /** - * Takes a syntax tree element that represents a {@link I18nInvalidFormat} annotation, and returns - * its value. - * - * @param anno an I18nInvalidFormat annotation - * @return its value() element/field, within double-quotes - */ - public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { - return "\"" + getI18nInvalidFormatValue(anno) + "\""; - } - - /** - * Creates a {@code @}{@link I18nFormat} annotation with the given list as its value. - * - * @param args conversion categories for the {@code @Format} annotation - * @return a {@code @}{@link I18nFormat} annotation with the given list as its value - */ - public AnnotationMirror categoriesToFormatAnnotation(I18nConversionCategory[] args) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nFormat.class); - builder.setValue("value", args); - return builder.build(); - } - - /** - * Takes an {@code @}{@link I18nFormat} annotation, and returns its {@code value} element - * - * @param anno an {@code @}{@link I18nFormat} annotation - * @return the {@code @}{@link I18nFormat} annotation's {@code value} element - */ - public I18nConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { - return AnnotationUtils.getElementValueEnumArray( - anno, i18nFormatValueElement, I18nConversionCategory.class); - } - - /** - * Returns true if the call is to a method with the @I18nChecksFormat annotation. An example of - * such a method is I18nFormatUtil.hasFormat. - */ - public boolean isHasFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nChecksFormat.class); - return anno != null; - } - - /** - * Returns true if the call is to a method with the @I18nValidFormat annotation. An example of - * such a method is I18nFormatUtil.isFormat. - */ - public boolean isIsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nValidFormat.class); - return anno != null; - } - - /** - * Returns true if the call is to a method with the @I18nMakeFormat annotation. An example of such - * a method is ResourceBundle.getString. - */ - public boolean isMakeFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { - ExecutableElement method = node.getTarget().getMethod(); - AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nMakeFormat.class); - return anno != null; - } - - /** - * Reports an error. - * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message - */ - public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportError(res.location, msgKey, args); - } - - /** - * Reports a warning. - * - * @param res used for source location information - * @param msgKey the diagnostic message key - * @param args arguments to the diagnostic message - */ - public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { - checker.reportWarning(res.location, msgKey, args); - } - - private I18nConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( - MethodInvocationNode node) { - Node vararg = node.getArgument(1); - if (vararg instanceof ArrayCreationNode) { - List convs = ((ArrayCreationNode) vararg).getInitializers(); - I18nConversionCategory[] res = new I18nConversionCategory[convs.size()]; - for (int i = 0; i < convs.size(); i++) { - Node conv = convs.get(i); - if (conv instanceof FieldAccessNode) { - if (typeMirrorToClass(((FieldAccessNode) conv).getType()) - == I18nConversionCategory.class) { - res[i] = I18nConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName()); - continue; /* avoid returning null */ - } - } - return null; - } - return res; - } - return null; - } - - public Result getHasFormatCallCategories(MethodInvocationNode node) { - return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); - } - - public Result makeFormatCallCategories( - MethodInvocationNode node, I18nFormatterAnnotatedTypeFactory atypeFactory) { - Map translations = atypeFactory.translations; - Node firstParam = node.getArgument(0); - Result ret = new Result<>(null, node.getTree()); - - // Now only work with a literal string - if (firstParam instanceof StringLiteralNode) { - String s = ((StringLiteralNode) firstParam).getValue(); - if (translations.containsKey(s)) { - String value = translations.get(s); - ret = new Result<>(I18nFormatUtil.formatParameterCategories(value), node.getTree()); - } - } - return ret; - } - - /** - * Returns an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. - * Otherwise, returns null. - * - * @param tree method invocation tree - * @param atypeFactory type factory - * @return an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. - * Otherwise, returns null. - */ - public @Nullable I18nFormatCall createFormatForCall( - MethodInvocationTree tree, I18nFormatterAnnotatedTypeFactory atypeFactory) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); - for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { - // find @FormatFor - if (paramType.getAnnotation(I18nFormatFor.class) != null) { - return atypeFactory.treeUtil.new I18nFormatCall(tree, atypeFactory); - } - } - return null; - } + /** The checker. */ + public final BaseTypeChecker checker; - /** - * Represents a format method invocation in the syntax tree. - * - *

          An I18nFormatCall instance can only be instantiated by the createFormatForCall method. - */ - public class I18nFormatCall { + /** The processing environment. */ + public final ProcessingEnvironment processingEnv; - /** The AST node for the call. */ - private final MethodInvocationTree tree; + /** The value() element/field of an @I18nFormat annotation. */ + protected final ExecutableElement i18nFormatValueElement; - /** The format string argument. */ - private ExpressionTree formatArg; + /** The value() element/field of an @I18nFormatFor annotation. */ + protected final ExecutableElement i18nFormatForValueElement; - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; + /** The value() element/field of an @I18nInvalidFormat annotation. */ + protected final ExecutableElement i18nInvalidFormatValueElement; - /** The arguments to the format string. */ - private List args; + /** + * Creates a new I18nFormatterTreeUtil. + * + * @param checker the checker + */ + public I18nFormatterTreeUtil(BaseTypeChecker checker) { + this.checker = checker; + this.processingEnv = checker.getProcessingEnvironment(); + i18nFormatValueElement = TreeUtils.getMethod(I18nFormat.class, "value", 0, processingEnv); + i18nFormatForValueElement = + TreeUtils.getMethod(I18nFormatFor.class, "value", 0, processingEnv); + i18nInvalidFormatValueElement = + TreeUtils.getMethod(I18nInvalidFormat.class, "value", 0, processingEnv); + } - /** Extra description for error messages. */ - private String invalidMessage; + /** Describe the format annotation type. */ + public enum FormatType { + I18NINVALID, + I18NFORMAT, + I18NFORMATFOR + } - /** The type of the format string formal parameter. */ - private AnnotatedTypeMirror formatAnno; + /** + * Takes an exception that describes an invalid formatter string and returns a syntax trees + * element that represents a {@link I18nInvalidFormat} annotation with the exception's error + * message as value. + */ + public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalArgumentException ex) { + return stringToInvalidFormatAnnotation(ex.getMessage()); + } /** - * Creates an {@code I18nFormatCall} for the given method invocation tree. + * Creates an {@link I18nInvalidFormat} annotation with the given string as its value. * - * @param tree method invocation tree - * @param atypeFactory type factory + * @param invalidFormatString an invalid formatter string + * @return an {@link I18nInvalidFormat} annotation with the given string as its value */ - @SuppressWarnings("nullness:initialization.fields.uninitialized") - public I18nFormatCall(MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { - this.tree = tree; - this.atypeFactory = atypeFactory; - List theargs = tree.getArguments(); - this.args = null; - ExecutableElement method = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); - initialCheck(theargs, method, methodAnno); + /*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation( + String invalidFormatString) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", invalidFormatString); + return builder.build(); } /** - * Returns the AST node for the call. + * Gets the value() element/field out of an I18nInvalidFormat annotation. * - * @return the AST node for the call + * @param anno an I18nInvalidFormat annotation + * @return its value() element/field, or null if it does not have one */ - public MethodInvocationTree getTree() { - return tree; + /*package-private*/ @Nullable String getI18nInvalidFormatValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValue( + anno, i18nInvalidFormatValueElement, String.class, null); } - @Override - public String toString() { - return this.tree.toString(); + /** + * Gets the value() element/field out of an I18NFormatFor annotation. + * + * @param anno an I18NFormatFor annotation + * @return its value() element/field + */ + /*package-private*/ String getI18nFormatForValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValue(anno, i18nFormatForValueElement, String.class); } /** - * This method checks the validity of the FormatFor. If it is valid, this.args will be set to - * the correct parameter arguments. Otherwise, it will be still null. + * Takes a syntax tree element that represents a {@link I18nInvalidFormat} annotation, and + * returns its value. * - * @param theargs arguments to the format method call - * @param method the ExecutableElement of the format method - * @param methodAnno annotated type of {@code method} + * @param anno an I18nInvalidFormat annotation + * @return its value() element/field, within double-quotes */ - private void initialCheck( - List theargs, - ExecutableElement method, - AnnotatedExecutableType methodAnno) { - // paramIndex is a 0-based index - int paramIndex = -1; - int i = 0; - for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { - if (paramType.getAnnotation(I18nFormatFor.class) != null) { - this.formatArg = theargs.get(i); - this.formatAnno = atypeFactory.getAnnotatedType(formatArg); - - if (typeMirrorToClass(paramType.getUnderlyingType()) != String.class) { - // Invalid FormatFor invocation - return; - } - - String formatforArg = getI18nFormatForValue(paramType.getAnnotation(I18nFormatFor.class)); - - paramIndex = JavaExpressionParseUtil.parameterIndex(formatforArg); - if (paramIndex == -1) { - // report errors here - checker.reportError(tree, "i18nformat.invalid.formatfor"); - } else { - paramIndex--; - } - break; - } - i++; - } - - if (paramIndex != -1) { - VariableElement param = method.getParameters().get(paramIndex); - if (param.asType().getKind() == TypeKind.ARRAY) { - this.args = theargs.subList(paramIndex, theargs.size()); - } else { - this.args = theargs.subList(paramIndex, paramIndex + 1); - } - } + public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { + return "\"" + getI18nInvalidFormatValue(anno) + "\""; } - public Result getFormatType() { - FormatType type; - if (isValidFormatForInvocation()) { - if (formatAnno.hasAnnotation(I18nFormat.class)) { - type = FormatType.I18NFORMAT; - } else if (formatAnno.hasAnnotation(I18nFormatFor.class)) { - type = FormatType.I18NFORMATFOR; - } else { - type = FormatType.I18NINVALID; - AnnotationMirror inv = formatAnno.getAnnotation(I18nInvalidFormat.class); - if (inv != null) { - invalidMessage = getI18nInvalidFormatValue(inv); - } else { - invalidMessage = "(is a @I18nFormat annotation missing?)"; - } - } - } else { - // If the FormatFor is invalid, it's still I18nFormatFor type but invalid, - // and we can't do anything else - type = FormatType.I18NFORMATFOR; - } - return new Result<>(type, formatArg); + /** + * Creates a {@code @}{@link I18nFormat} annotation with the given list as its value. + * + * @param args conversion categories for the {@code @Format} annotation + * @return a {@code @}{@link I18nFormat} annotation with the given list as its value + */ + public AnnotationMirror categoriesToFormatAnnotation(I18nConversionCategory[] args) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nFormat.class); + builder.setValue("value", args); + return builder.build(); } - public final String getInvalidError() { - return invalidMessage; + /** + * Takes an {@code @}{@link I18nFormat} annotation, and returns its {@code value} element + * + * @param anno an {@code @}{@link I18nFormat} annotation + * @return the {@code @}{@link I18nFormat} annotation's {@code value} element + */ + public I18nConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { + return AnnotationUtils.getElementValueEnumArray( + anno, i18nFormatValueElement, I18nConversionCategory.class); } - public boolean isValidFormatForInvocation() { - return this.args != null; + /** + * Returns true if the call is to a method with the @I18nChecksFormat annotation. An example of + * such a method is I18nFormatUtil.hasFormat. + */ + public boolean isHasFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nChecksFormat.class); + return anno != null; } /** - * Returns the type of method invocation. - * - * @see InvocationType + * Returns true if the call is to a method with the @I18nValidFormat annotation. An example of + * such a method is I18nFormatUtil.isFormat. + */ + public boolean isIsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nValidFormat.class); + return anno != null; + } + + /** + * Returns true if the call is to a method with the @I18nMakeFormat annotation. An example of + * such a method is ResourceBundle.getString. */ - public final Result getInvocationType() { - InvocationType type = InvocationType.VARARG; - - if (args.size() == 1) { - ExpressionTree first = args.get(0); - TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); - // figure out if argType is an array - type = - argType.accept( - new SimpleTypeVisitor8>() { - @Override - protected InvocationType defaultAction(TypeMirror e, Class p) { - // not an array - return InvocationType.VARARG; - } - - @Override - public InvocationType visitArray(ArrayType t, Class p) { - // it's an array, now figure out if it's a - // (Object[])null array - return first.accept( - new SimpleTreeVisitor>() { - @Override - protected InvocationType defaultAction(Tree tree, Class p) { - // just a normal array - return InvocationType.ARRAY; - } - - @Override - public InvocationType visitTypeCast(TypeCastTree tree, Class p) { - // it's a (Object[])null - return atypeFactory - .getAnnotatedType(tree.getExpression()) - .getUnderlyingType() - .getKind() - == TypeKind.NULL - ? InvocationType.NULLARRAY - : InvocationType.ARRAY; - } - }, - p); - } - - @Override - public InvocationType visitNull(NullType t, Class p) { - return InvocationType.NULLARRAY; - } - }, - Void.TYPE); - } - - ExpressionTree loc; - loc = tree.getMethodSelect(); - if (type != InvocationType.VARARG && !args.isEmpty()) { - loc = args.get(0); - } - return new Result<>(type, loc); + public boolean isMakeFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { + ExecutableElement method = node.getTarget().getMethod(); + AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nMakeFormat.class); + return anno != null; } - public Result getInvalidInvocationType() { - return new Result<>(FormatType.I18NFORMATFOR, formatArg); + /** + * Reports an error. + * + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message + */ + public final void failure(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportError(res.location, msgKey, args); } /** - * Returns the conversion category for every parameter. + * Reports a warning. * - * @see I18nConversionCategory + * @param res used for source location information + * @param msgKey the diagnostic message key + * @param args arguments to the diagnostic message */ - public final I18nConversionCategory[] getFormatCategories() { - AnnotationMirror anno = formatAnno.getAnnotation(I18nFormat.class); - return formatAnnotationToCategories(anno); + public final void warning(Result res, @CompilerMessageKey String msgKey, Object... args) { + checker.reportWarning(res.location, msgKey, args); } - public final Result[] getParamTypes() { - // One to suppress warning in javac, the other to suppress warning in Eclipse... - @SuppressWarnings({"rawtypes", "unchecked"}) - Result[] res = new Result[args.size()]; - for (int i = 0; i < res.length; ++i) { - ExpressionTree arg = args.get(i); - TypeMirror argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); - res[i] = new Result<>(argType, arg); - } - return res; + private I18nConversionCategory @Nullable [] asFormatCallCategoriesLowLevel( + MethodInvocationNode node) { + Node vararg = node.getArgument(1); + if (vararg instanceof ArrayCreationNode) { + List convs = ((ArrayCreationNode) vararg).getInitializers(); + I18nConversionCategory[] res = new I18nConversionCategory[convs.size()]; + for (int i = 0; i < convs.size(); i++) { + Node conv = convs.get(i); + if (conv instanceof FieldAccessNode) { + if (typeMirrorToClass(((FieldAccessNode) conv).getType()) + == I18nConversionCategory.class) { + res[i] = + I18nConversionCategory.valueOf( + ((FieldAccessNode) conv).getFieldName()); + continue; /* avoid returning null */ + } + } + return null; + } + return res; + } + return null; } - public boolean isValidParameter(I18nConversionCategory formatCat, TypeMirror paramType) { - Class type = typeMirrorToClass(paramType); - if (type == null) { - // we did not recognize the parameter type - return false; - } - return formatCat.isAssignableFrom(type); + public Result getHasFormatCallCategories(MethodInvocationNode node) { + return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree()); } - } - - /** Converts a TypeMirror to a Class. */ - private static class TypeMirrorToClassVisitor - extends SimpleTypeVisitor8, Class> { - @Override - public Class visitPrimitive(PrimitiveType t, Class v) { - switch (t.getKind()) { - case BOOLEAN: - return Boolean.class; - case BYTE: - return Byte.class; - case CHAR: - return Character.class; - case SHORT: - return Short.class; - case INT: - return Integer.class; - case LONG: - return Long.class; - case FLOAT: - return Float.class; - case DOUBLE: - return Double.class; - default: - throw new BugInCF("unknown primitive type " + t); - } + + public Result makeFormatCallCategories( + MethodInvocationNode node, I18nFormatterAnnotatedTypeFactory atypeFactory) { + Map translations = atypeFactory.translations; + Node firstParam = node.getArgument(0); + Result ret = new Result<>(null, node.getTree()); + + // Now only work with a literal string + if (firstParam instanceof StringLiteralNode) { + String s = ((StringLiteralNode) firstParam).getValue(); + if (translations.containsKey(s)) { + String value = translations.get(s); + ret = new Result<>(I18nFormatUtil.formatParameterCategories(value), node.getTree()); + } + } + return ret; } - @Override - public Class visitDeclared(DeclaredType dt, Class v) { - return dt.asElement() - .accept( - new SimpleElementVisitor8, Class>() { - @Override - public Class visitType(TypeElement te, Class v) { - try { - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658: - // Name.toString should be @PolySignature - @BinaryName String cname = te.getQualifiedName().toString(); - return Class.forName(cname); - } catch (ClassNotFoundException e) { - // The lookup should work for all the classes we care about. - throw new Error(e); - } + /** + * Returns an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. + * Otherwise, returns null. + * + * @param tree method invocation tree + * @param atypeFactory type factory + * @return an I18nFormatCall instance, only if there is an {@code @I18nFormatFor} annotation. + * Otherwise, returns null. + */ + public @Nullable I18nFormatCall createFormatForCall( + MethodInvocationTree tree, I18nFormatterAnnotatedTypeFactory atypeFactory) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); + for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { + // find @FormatFor + if (paramType.getAnnotation(I18nFormatFor.class) != null) { + return atypeFactory.treeUtil.new I18nFormatCall(tree, atypeFactory); + } + } + return null; + } + + /** + * Represents a format method invocation in the syntax tree. + * + *

          An I18nFormatCall instance can only be instantiated by the createFormatForCall method. + */ + public class I18nFormatCall { + + /** The AST node for the call. */ + private final MethodInvocationTree tree; + + /** The format string argument. */ + private ExpressionTree formatArg; + + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** The arguments to the format string. */ + private List args; + + /** Extra description for error messages. */ + private String invalidMessage; + + /** The type of the format string formal parameter. */ + private AnnotatedTypeMirror formatAnno; + + /** + * Creates an {@code I18nFormatCall} for the given method invocation tree. + * + * @param tree method invocation tree + * @param atypeFactory type factory + */ + @SuppressWarnings("nullness:initialization.fields.uninitialized") + public I18nFormatCall(MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) { + this.tree = tree; + this.atypeFactory = atypeFactory; + List theargs = tree.getArguments(); + this.args = null; + ExecutableElement method = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method); + initialCheck(theargs, method, methodAnno); + } + + /** + * Returns the AST node for the call. + * + * @return the AST node for the call + */ + public MethodInvocationTree getTree() { + return tree; + } + + @Override + public String toString() { + return this.tree.toString(); + } + + /** + * This method checks the validity of the FormatFor. If it is valid, this.args will be set + * to the correct parameter arguments. Otherwise, it will be still null. + * + * @param theargs arguments to the format method call + * @param method the ExecutableElement of the format method + * @param methodAnno annotated type of {@code method} + */ + private void initialCheck( + List theargs, + ExecutableElement method, + AnnotatedExecutableType methodAnno) { + // paramIndex is a 0-based index + int paramIndex = -1; + int i = 0; + for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) { + if (paramType.getAnnotation(I18nFormatFor.class) != null) { + this.formatArg = theargs.get(i); + this.formatAnno = atypeFactory.getAnnotatedType(formatArg); + + if (typeMirrorToClass(paramType.getUnderlyingType()) != String.class) { + // Invalid FormatFor invocation + return; + } + + String formatforArg = + getI18nFormatForValue(paramType.getAnnotation(I18nFormatFor.class)); + + paramIndex = JavaExpressionParseUtil.parameterIndex(formatforArg); + if (paramIndex == -1) { + // report errors here + checker.reportError(tree, "i18nformat.invalid.formatfor"); + } else { + paramIndex--; + } + break; } - }, - Void.TYPE); + i++; + } + + if (paramIndex != -1) { + VariableElement param = method.getParameters().get(paramIndex); + if (param.asType().getKind() == TypeKind.ARRAY) { + this.args = theargs.subList(paramIndex, theargs.size()); + } else { + this.args = theargs.subList(paramIndex, paramIndex + 1); + } + } + } + + public Result getFormatType() { + FormatType type; + if (isValidFormatForInvocation()) { + if (formatAnno.hasAnnotation(I18nFormat.class)) { + type = FormatType.I18NFORMAT; + } else if (formatAnno.hasAnnotation(I18nFormatFor.class)) { + type = FormatType.I18NFORMATFOR; + } else { + type = FormatType.I18NINVALID; + AnnotationMirror inv = formatAnno.getAnnotation(I18nInvalidFormat.class); + if (inv != null) { + invalidMessage = getI18nInvalidFormatValue(inv); + } else { + invalidMessage = "(is a @I18nFormat annotation missing?)"; + } + } + } else { + // If the FormatFor is invalid, it's still I18nFormatFor type but invalid, + // and we can't do anything else + type = FormatType.I18NFORMATFOR; + } + return new Result<>(type, formatArg); + } + + public final String getInvalidError() { + return invalidMessage; + } + + public boolean isValidFormatForInvocation() { + return this.args != null; + } + + /** + * Returns the type of method invocation. + * + * @see InvocationType + */ + public final Result getInvocationType() { + InvocationType type = InvocationType.VARARG; + + if (args.size() == 1) { + ExpressionTree first = args.get(0); + TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); + // figure out if argType is an array + type = + argType.accept( + new SimpleTypeVisitor8>() { + @Override + protected InvocationType defaultAction( + TypeMirror e, Class p) { + // not an array + return InvocationType.VARARG; + } + + @Override + public InvocationType visitArray(ArrayType t, Class p) { + // it's an array, now figure out if it's a + // (Object[])null array + return first.accept( + new SimpleTreeVisitor< + InvocationType, Class>() { + @Override + protected InvocationType defaultAction( + Tree tree, Class p) { + // just a normal array + return InvocationType.ARRAY; + } + + @Override + public InvocationType visitTypeCast( + TypeCastTree tree, Class p) { + // it's a (Object[])null + return atypeFactory + .getAnnotatedType( + tree + .getExpression()) + .getUnderlyingType() + .getKind() + == TypeKind.NULL + ? InvocationType.NULLARRAY + : InvocationType.ARRAY; + } + }, + p); + } + + @Override + public InvocationType visitNull(NullType t, Class p) { + return InvocationType.NULLARRAY; + } + }, + Void.TYPE); + } + + ExpressionTree loc; + loc = tree.getMethodSelect(); + if (type != InvocationType.VARARG && !args.isEmpty()) { + loc = args.get(0); + } + return new Result<>(type, loc); + } + + public Result getInvalidInvocationType() { + return new Result<>(FormatType.I18NFORMATFOR, formatArg); + } + + /** + * Returns the conversion category for every parameter. + * + * @see I18nConversionCategory + */ + public final I18nConversionCategory[] getFormatCategories() { + AnnotationMirror anno = formatAnno.getAnnotation(I18nFormat.class); + return formatAnnotationToCategories(anno); + } + + public final Result[] getParamTypes() { + // One to suppress warning in javac, the other to suppress warning in Eclipse... + @SuppressWarnings({"rawtypes", "unchecked"}) + Result[] res = new Result[args.size()]; + for (int i = 0; i < res.length; ++i) { + ExpressionTree arg = args.get(i); + TypeMirror argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); + res[i] = new Result<>(argType, arg); + } + return res; + } + + public boolean isValidParameter(I18nConversionCategory formatCat, TypeMirror paramType) { + Class type = typeMirrorToClass(paramType); + if (type == null) { + // we did not recognize the parameter type + return false; + } + return formatCat.isAssignableFrom(type); + } + } + + /** Converts a TypeMirror to a Class. */ + private static class TypeMirrorToClassVisitor + extends SimpleTypeVisitor8, Class> { + @Override + public Class visitPrimitive(PrimitiveType t, Class v) { + switch (t.getKind()) { + case BOOLEAN: + return Boolean.class; + case BYTE: + return Byte.class; + case CHAR: + return Character.class; + case SHORT: + return Short.class; + case INT: + return Integer.class; + case LONG: + return Long.class; + case FLOAT: + return Float.class; + case DOUBLE: + return Double.class; + default: + throw new BugInCF("unknown primitive type " + t); + } + } + + @Override + public Class visitDeclared(DeclaredType dt, Class v) { + return dt.asElement() + .accept( + new SimpleElementVisitor8, Class>() { + @Override + public Class visitType( + TypeElement te, Class v) { + try { + @SuppressWarnings( + "signature") // https://tinyurl.com/cfissue/658: + // Name.toString should be @PolySignature + @BinaryName String cname = te.getQualifiedName().toString(); + return Class.forName(cname); + } catch (ClassNotFoundException e) { + // The lookup should work for all the classes we care about. + throw new Error(e); + } + } + }, + Void.TYPE); + } + } + + /** The singleton instance of TypeMirrorToClassVisitor. */ + private static TypeMirrorToClassVisitor typeMirrorToClassVisitor = + new TypeMirrorToClassVisitor(); + + /** + * Converts a TypeMirror to a Class. + * + * @param type a TypeMirror + * @return the class corresponding to the argument + */ + private static Class typeMirrorToClass(TypeMirror type) { + return type.accept(typeMirrorToClassVisitor, Void.TYPE); } - } - - /** The singleton instance of TypeMirrorToClassVisitor. */ - private static TypeMirrorToClassVisitor typeMirrorToClassVisitor = new TypeMirrorToClassVisitor(); - - /** - * Converts a TypeMirror to a Class. - * - * @param type a TypeMirror - * @return the class corresponding to the argument - */ - private static Class typeMirrorToClass(TypeMirror type) { - return type.accept(typeMirrorToClassVisitor, Void.TYPE); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java index f6556fafa58..3b1c3af7bd6 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java @@ -2,9 +2,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; @@ -19,6 +17,10 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + /** * Whenever a method with {@link I18nFormatFor} annotation is invoked, it will perform the format * string verification. @@ -27,146 +29,157 @@ */ public class I18nFormatterVisitor extends BaseTypeVisitor { - public I18nFormatterVisitor(BaseTypeChecker checker) { - super(checker); - } + public I18nFormatterVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - I18nFormatterTreeUtil tu = atypeFactory.treeUtil; - I18nFormatCall fc = tu.createFormatForCall(tree, atypeFactory); - if (fc != null) { - checkInvocationFormatFor(fc); - return p; - } else { - return super.visitMethodInvocation(tree, p); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + I18nFormatterTreeUtil tu = atypeFactory.treeUtil; + I18nFormatCall fc = tu.createFormatForCall(tree, atypeFactory); + if (fc != null) { + checkInvocationFormatFor(fc); + return p; + } else { + return super.visitMethodInvocation(tree, p); + } } - } - private void checkInvocationFormatFor(I18nFormatCall fc) { - I18nFormatterTreeUtil tu = atypeFactory.treeUtil; - Result type = fc.getFormatType(); + private void checkInvocationFormatFor(I18nFormatCall fc) { + I18nFormatterTreeUtil tu = atypeFactory.treeUtil; + Result type = fc.getFormatType(); - switch (type.value()) { - case I18NINVALID: - tu.failure(type, "i18nformat.string.invalid", fc.getInvalidError()); - break; - case I18NFORMATFOR: - if (!fc.isValidFormatForInvocation()) { - Result failureType = fc.getInvalidInvocationType(); - tu.failure(failureType, "i18nformat.invalid.formatfor"); - } - break; - case I18NFORMAT: - Result invc = fc.getInvocationType(); - I18nConversionCategory[] formatCats = fc.getFormatCategories(); - switch (invc.value()) { - case VARARG: - Result[] paramTypes = fc.getParamTypes(); - int paraml = paramTypes.length; - int formatl = formatCats.length; + switch (type.value()) { + case I18NINVALID: + tu.failure(type, "i18nformat.string.invalid", fc.getInvalidError()); + break; + case I18NFORMATFOR: + if (!fc.isValidFormatForInvocation()) { + Result failureType = fc.getInvalidInvocationType(); + tu.failure(failureType, "i18nformat.invalid.formatfor"); + } + break; + case I18NFORMAT: + Result invc = fc.getInvocationType(); + I18nConversionCategory[] formatCats = fc.getFormatCategories(); + switch (invc.value()) { + case VARARG: + Result[] paramTypes = fc.getParamTypes(); + int paraml = paramTypes.length; + int formatl = formatCats.length; - // For assignments, "i18nformat.missing.arguments" and - // "i18nformat.excess.arguments" are - // issued from commonAssignmentCheck(). - if (paraml < formatl) { - tu.warning(invc, "i18nformat.missing.arguments", formatl, paraml); - } - if (paraml > formatl) { - tu.warning(invc, "i18nformat.excess.arguments", formatl, paraml); - } - for (int i = 0; i < formatl && i < paraml; ++i) { - I18nConversionCategory formatCat = formatCats[i]; - Result param = paramTypes[i]; - TypeMirror paramType = param.value(); - switch (formatCat) { - case UNUSED: - tu.warning(param, "i18nformat.argument.unused", " " + (1 + i)); - break; - case GENERAL: - break; - default: - if (!fc.isValidParameter(formatCat, paramType)) { - ExecutableElement method = TreeUtils.elementFromUse(fc.getTree()); - CharSequence methodName = ElementUtils.getSimpleDescription(method); - tu.failure( - param, - "argument.type.incompatible", - "", // parameter name is not useful - methodName, - paramType, - formatCat); - } - } - } - break; - case NULLARRAY: - // fall-through - case ARRAY: - for (I18nConversionCategory cat : formatCats) { - if (cat == I18nConversionCategory.UNUSED) { - tu.warning(invc, "i18nformat.argument.unused", ""); - } - } - tu.warning(invc, "i18nformat.indirect.arguments"); - break; - default: - break; + // For assignments, "i18nformat.missing.arguments" and + // "i18nformat.excess.arguments" are + // issued from commonAssignmentCheck(). + if (paraml < formatl) { + tu.warning(invc, "i18nformat.missing.arguments", formatl, paraml); + } + if (paraml > formatl) { + tu.warning(invc, "i18nformat.excess.arguments", formatl, paraml); + } + for (int i = 0; i < formatl && i < paraml; ++i) { + I18nConversionCategory formatCat = formatCats[i]; + Result param = paramTypes[i]; + TypeMirror paramType = param.value(); + switch (formatCat) { + case UNUSED: + tu.warning(param, "i18nformat.argument.unused", " " + (1 + i)); + break; + case GENERAL: + break; + default: + if (!fc.isValidParameter(formatCat, paramType)) { + ExecutableElement method = + TreeUtils.elementFromUse(fc.getTree()); + CharSequence methodName = + ElementUtils.getSimpleDescription(method); + tu.failure( + param, + "argument.type.incompatible", + "", // parameter name is not useful + methodName, + paramType, + formatCat); + } + } + } + break; + case NULLARRAY: + // fall-through + case ARRAY: + for (I18nConversionCategory cat : formatCats) { + if (cat == I18nConversionCategory.UNUSED) { + tu.warning(invc, "i18nformat.argument.unused", ""); + } + } + tu.warning(invc, "i18nformat.indirect.arguments"); + break; + default: + break; + } + break; + default: + break; } - break; - default: - break; } - } - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = true; + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = true; - AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); - AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); + AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); + AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); - // "i18nformat.missing.arguments" and "i18nformat.excess.arguments" are issued here for - // assignments. - // For method calls, they are issued in checkInvocationFormatFor(). - if (rhs != null - && lhs != null - && AnnotationUtils.areSameByName(rhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME) - && AnnotationUtils.areSameByName(lhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME)) { - I18nConversionCategory[] rhsArgTypes = - atypeFactory.treeUtil.formatAnnotationToCategories(rhs); - I18nConversionCategory[] lhsArgTypes = - atypeFactory.treeUtil.formatAnnotationToCategories(lhs); + // "i18nformat.missing.arguments" and "i18nformat.excess.arguments" are issued here for + // assignments. + // For method calls, they are issued in checkInvocationFormatFor(). + if (rhs != null + && lhs != null + && AnnotationUtils.areSameByName( + rhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME) + && AnnotationUtils.areSameByName( + lhs, I18nFormatterAnnotatedTypeFactory.I18NFORMAT_NAME)) { + I18nConversionCategory[] rhsArgTypes = + atypeFactory.treeUtil.formatAnnotationToCategories(rhs); + I18nConversionCategory[] lhsArgTypes = + atypeFactory.treeUtil.formatAnnotationToCategories(lhs); - if (rhsArgTypes.length < lhsArgTypes.length) { - // From the manual: - // It is legal to use a format string with fewer format specifiers - // than required, but a warning is issued. - checker.reportWarning( - valueTree, "i18nformat.missing.arguments", varType.toString(), valueType.toString()); - } else if (rhsArgTypes.length > lhsArgTypes.length) { - // Since it is known that too many conversion categories were provided, issue a more - // specific error message to that effect than "assignment.type.incompatible". - checker.reportError( - valueTree, "i18nformat.excess.arguments", varType.toString(), valueType.toString()); - result = false; - } - } + if (rhsArgTypes.length < lhsArgTypes.length) { + // From the manual: + // It is legal to use a format string with fewer format specifiers + // than required, but a warning is issued. + checker.reportWarning( + valueTree, + "i18nformat.missing.arguments", + varType.toString(), + valueType.toString()); + } else if (rhsArgTypes.length > lhsArgTypes.length) { + // Since it is known that too many conversion categories were provided, issue a more + // specific error message to that effect than "assignment.type.incompatible". + checker.reportError( + valueTree, + "i18nformat.excess.arguments", + varType.toString(), + valueType.toString()); + result = false; + } + } - // TODO: What does "take precedence over" mean? Both are issued, but the - // "i18nformat.excess.arguments" appears first in the output. Is this meant to not call - // super.commonAssignmentCheck() if `result` is already false? - // By calling super.commonAssignmentCheck() last, any "i18nformat.excess.arguments" - // message issued for a given line of code will take precedence over the - // "assignment.type.incompatible" - // issued by super.commonAssignmentCheck(). - result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) && result; - return result; - } + // TODO: What does "take precedence over" mean? Both are issued, but the + // "i18nformat.excess.arguments" appears first in the output. Is this meant to not call + // super.commonAssignmentCheck() if `result` is already false? + // By calling super.commonAssignmentCheck() last, any "i18nformat.excess.arguments" + // message issued for a given line of code will take precedence over the + // "assignment.type.incompatible" + // issued by super.commonAssignmentCheck(). + result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) + && result; + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java index 2c9d016536c..ab390781238 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/BaseAnnotatedTypeFactoryForIndexChecker.java @@ -1,73 +1,77 @@ package org.checkerframework.checker.index; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.index.qual.HasSubsequence; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + /** * A class for functionality common to multiple type-checkers that are used by the Index Checker. */ public abstract class BaseAnnotatedTypeFactoryForIndexChecker extends BaseAnnotatedTypeFactory { - /** The from() element/field of a @HasSubsequence annotation. */ - protected final ExecutableElement hasSubsequenceFromElement = - TreeUtils.getMethod(HasSubsequence.class, "from", 0, processingEnv); + /** The from() element/field of a @HasSubsequence annotation. */ + protected final ExecutableElement hasSubsequenceFromElement = + TreeUtils.getMethod(HasSubsequence.class, "from", 0, processingEnv); - /** The to() element/field of a @HasSubsequence annotation. */ - protected final ExecutableElement hasSubsequenceToElement = - TreeUtils.getMethod(HasSubsequence.class, "to", 0, processingEnv); + /** The to() element/field of a @HasSubsequence annotation. */ + protected final ExecutableElement hasSubsequenceToElement = + TreeUtils.getMethod(HasSubsequence.class, "to", 0, processingEnv); - /** The subsequence() element/field of a @HasSubsequence annotation. */ - protected final ExecutableElement hasSubsequenceSubsequenceElement = - TreeUtils.getMethod(HasSubsequence.class, "subsequence", 0, processingEnv); + /** The subsequence() element/field of a @HasSubsequence annotation. */ + protected final ExecutableElement hasSubsequenceSubsequenceElement = + TreeUtils.getMethod(HasSubsequence.class, "subsequence", 0, processingEnv); - /** - * Creates a new BaseAnnotatedTypeFactoryForIndexChecker. - * - * @param checker the checker - */ - public BaseAnnotatedTypeFactoryForIndexChecker(BaseTypeChecker checker) { - super(checker); - } + /** + * Creates a new BaseAnnotatedTypeFactoryForIndexChecker. + * + * @param checker the checker + */ + public BaseAnnotatedTypeFactoryForIndexChecker(BaseTypeChecker checker) { + super(checker); + } - /** - * Gets the from() element/field out of a HasSubsequence annotation. - * - * @param anno a HasSubsequence annotation - * @return its from() element/field - */ - public String hasSubsequenceFromValue(AnnotationMirror anno) { - String result = AnnotationUtils.getElementValue(anno, hasSubsequenceFromElement, String.class); - assert result != null : "@AssumeAssertion(nullness)"; - return result; - } + /** + * Gets the from() element/field out of a HasSubsequence annotation. + * + * @param anno a HasSubsequence annotation + * @return its from() element/field + */ + public String hasSubsequenceFromValue(AnnotationMirror anno) { + String result = + AnnotationUtils.getElementValue(anno, hasSubsequenceFromElement, String.class); + assert result != null : "@AssumeAssertion(nullness)"; + return result; + } - /** - * Gets the to() element/field out of a HasSubsequence annotation. - * - * @param anno a HasSubsequence annotation - * @return its to() element/field - */ - public String hasSubsequenceToValue(AnnotationMirror anno) { - String result = AnnotationUtils.getElementValue(anno, hasSubsequenceToElement, String.class); - assert result != null : "@AssumeAssertion(nullness)"; - return result; - } + /** + * Gets the to() element/field out of a HasSubsequence annotation. + * + * @param anno a HasSubsequence annotation + * @return its to() element/field + */ + public String hasSubsequenceToValue(AnnotationMirror anno) { + String result = + AnnotationUtils.getElementValue(anno, hasSubsequenceToElement, String.class); + assert result != null : "@AssumeAssertion(nullness)"; + return result; + } - /** - * Gets the subsequence() element/field out of a HasSubsequence annotation. - * - * @param anno a HasSubsequence annotation - * @return its subsequence() element/field - */ - public String hasSubsequenceSubsequenceValue(AnnotationMirror anno) { - String result = - AnnotationUtils.getElementValue(anno, hasSubsequenceSubsequenceElement, String.class); - assert result != null : "@AssumeAssertion(nullness)"; - return result; - } + /** + * Gets the subsequence() element/field out of a HasSubsequence annotation. + * + * @param anno a HasSubsequence annotation + * @return its subsequence() element/field + */ + public String hasSubsequenceSubsequenceValue(AnnotationMirror anno) { + String result = + AnnotationUtils.getElementValue( + anno, hasSubsequenceSubsequenceElement, String.class); + assert result != null : "@AssumeAssertion(nullness)"; + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java index 5e1eda17297..2ce0f5a0403 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexAbstractTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.index; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.GreaterThanNode; @@ -13,6 +12,8 @@ import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; +import javax.lang.model.element.AnnotationMirror; + /** * This class provides methods shared by the Index Checker's internal checkers in their transfer * functions. In particular, it provides a common framework for visiting comparison operators. @@ -20,96 +21,96 @@ @SuppressWarnings("ArgumentSelectionDefectChecker") // TODO: apply suggested error-prone fixes public abstract class IndexAbstractTransfer extends CFTransfer { - protected IndexAbstractTransfer(CFAnalysis analysis) { - super(analysis); - } + protected IndexAbstractTransfer(CFAnalysis analysis) { + super(analysis); + } + + @Override + public TransferResult visitGreaterThan( + GreaterThanNode node, TransferInput in) { + TransferResult result = super.visitGreaterThan(node, in); + + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; + } + // Refine the then branch. + refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); - @Override - public TransferResult visitGreaterThan( - GreaterThanNode node, TransferInput in) { - TransferResult result = super.visitGreaterThan(node, in); + // Refine the else branch, which is the inverse of the then branch. + refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; + return rfi.newResult; } - // Refine the then branch. - refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); - // Refine the else branch, which is the inverse of the then branch. - refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); + @Override + public TransferResult visitGreaterThanOrEqual( + GreaterThanOrEqualNode node, TransferInput in) { + TransferResult result = super.visitGreaterThanOrEqual(node, in); - return rfi.newResult; - } + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; + } - @Override - public TransferResult visitGreaterThanOrEqual( - GreaterThanOrEqualNode node, TransferInput in) { - TransferResult result = super.visitGreaterThanOrEqual(node, in); + // Refine the then branch. + refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } + // Refine the else branch. + refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); - // Refine the then branch. - refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.thenStore, in); + return rfi.newResult; + } - // Refine the else branch. - refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.elseStore, in); + @Override + public TransferResult visitLessThanOrEqual( + LessThanOrEqualNode node, TransferInput in) { + TransferResult result = super.visitLessThanOrEqual(node, in); - return rfi.newResult; - } + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; + } - @Override - public TransferResult visitLessThanOrEqual( - LessThanOrEqualNode node, TransferInput in) { - TransferResult result = super.visitLessThanOrEqual(node, in); + // Refine the then branch. A <= is just a flipped >=. + refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; + // Refine the else branch. + refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); + return rfi.newResult; } - // Refine the then branch. A <= is just a flipped >=. - refineGTE(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); + @Override + public TransferResult visitLessThan( + LessThanNode node, TransferInput in) { + TransferResult result = super.visitLessThan(node, in); - // Refine the else branch. - refineGT(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); - return rfi.newResult; - } + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; + } - @Override - public TransferResult visitLessThan( - LessThanNode node, TransferInput in) { - TransferResult result = super.visitLessThan(node, in); + // Refine the then branch. A < is just a flipped >. + refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, node); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; + // Refine the else branch. + refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); + return rfi.newResult; } - // Refine the then branch. A < is just a flipped >. - refineGT(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, rfi.thenStore, in); - - // Refine the else branch. - refineGTE(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, rfi.elseStore, in); - return rfi.newResult; - } - - protected abstract void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in); - - protected abstract void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in); + protected abstract void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in); + + protected abstract void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in); } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java index c1152466fd7..5dd34ae41b5 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexChecker.java @@ -79,6 +79,6 @@ // @RelevantJavaTypes annotations appear on other checkers. public class IndexChecker extends UpperBoundChecker { - /** Creates the Index Checker. */ - public IndexChecker() {} + /** Creates the Index Checker. */ + public IndexChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java b/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java index 58844e54b7a..75d232b2db6 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexMethodIdentifier.java @@ -2,12 +2,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.checker.index.qual.LengthOf; import org.checkerframework.dataflow.cfg.node.MethodAccessNode; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; @@ -16,165 +11,179 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; + /** * Given a Tree or other construct, this class has methods to query whether it is a particular * method call. */ public class IndexMethodIdentifier { - /** The {@code java.lang.Math#random()} method. */ - private final ExecutableElement mathRandom; - - /** The {@code java.util.Random#nextDouble()} method. */ - private final ExecutableElement randomNextDouble; - - /** The {@code java.util.Random#nextInt()} method. */ - private final ExecutableElement randomNextInt; - - /** The {@code java.lang.String#length()} method. */ - private final ExecutableElement stringLength; - - /** The {@code java.lang.Math#min()} methods. */ - private final List mathMinMethods; - - /** The {@code java.lang.Math#max()} methods. */ - private final List mathMaxMethods; - - /** The LengthOf.value argument/element. */ - private final ExecutableElement lengthOfValueElement; - - /** - * The {@code java.lang.String#indexOf} and {@code #lastIndexOf} methods that take a string as a - * non-receiver parameter. - */ - private final List indexOfStringMethods; - - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - public IndexMethodIdentifier(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - mathRandom = TreeUtils.getMethod("java.lang.Math", "random", 0, processingEnv); - randomNextDouble = TreeUtils.getMethod("java.util.Random", "nextDouble", 0, processingEnv); - randomNextInt = TreeUtils.getMethod("java.util.Random", "nextInt", 1, processingEnv); - - stringLength = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); - - mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); - mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); - - indexOfStringMethods = new ArrayList<>(4); - indexOfStringMethods.add( - TreeUtils.getMethod("java.lang.String", "indexOf", processingEnv, "java.lang.String")); - indexOfStringMethods.add( - TreeUtils.getMethod( - "java.lang.String", "indexOf", processingEnv, "java.lang.String", "int")); - indexOfStringMethods.add( - TreeUtils.getMethod("java.lang.String", "lastIndexOf", processingEnv, "java.lang.String")); - indexOfStringMethods.add( - TreeUtils.getMethod( - "java.lang.String", "lastIndexOf", processingEnv, "java.lang.String", "int")); - - lengthOfValueElement = TreeUtils.getMethod(LengthOf.class, "value", 0, processingEnv); - } - - /** - * Returns true iff the argument is an invocation of String#indexOf or String#lastIndexOf that - * takes a string parameter. - * - * @param methodTree the method invocation tree to be tested - * @return true iff the argument is an invocation of one of String's indexOf or lastIndexOf - * methods that takes another string as a parameter - */ - public boolean isIndexOfString(Tree methodTree) { - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - return TreeUtils.isMethodInvocation(methodTree, indexOfStringMethods, processingEnv); - } - - /** - * Returns true iff the argument is an invocation of Math.min. - * - * @param methodTree the method invocation tree to be tested - * @return true iff the argument is an invocation of Math.min() - */ - public boolean isMathMin(Tree methodTree) { - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); - } - - /** Returns true iff the argument is an invocation of Math.max. */ - public boolean isMathMax(Tree methodTree) { - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); - } - - /** Returns true iff the argument is an invocation of Math.random(). */ - public boolean isMathRandom(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, mathRandom, processingEnv); - } - - /** Returns true iff the argument is an invocation of Random.nextDouble(). */ - public boolean isRandomNextDouble(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, randomNextDouble, processingEnv); - } - - /** Returns true iff the argument is an invocation of Random.nextInt(). */ - public boolean isRandomNextInt(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, randomNextInt, processingEnv); - } - - /** - * Returns true if {@code tree} is an invocation of a method that returns the length of "this" - * - * @param tree a tree - * @return true if {@code tree} is an invocation of a method that returns the length of {@code - * this} - */ - public boolean isLengthOfMethodInvocation(Tree tree) { - if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { - return false; + /** The {@code java.lang.Math#random()} method. */ + private final ExecutableElement mathRandom; + + /** The {@code java.util.Random#nextDouble()} method. */ + private final ExecutableElement randomNextDouble; + + /** The {@code java.util.Random#nextInt()} method. */ + private final ExecutableElement randomNextInt; + + /** The {@code java.lang.String#length()} method. */ + private final ExecutableElement stringLength; + + /** The {@code java.lang.Math#min()} methods. */ + private final List mathMinMethods; + + /** The {@code java.lang.Math#max()} methods. */ + private final List mathMaxMethods; + + /** The LengthOf.value argument/element. */ + private final ExecutableElement lengthOfValueElement; + + /** + * The {@code java.lang.String#indexOf} and {@code #lastIndexOf} methods that take a string as a + * non-receiver parameter. + */ + private final List indexOfStringMethods; + + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + public IndexMethodIdentifier(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + mathRandom = TreeUtils.getMethod("java.lang.Math", "random", 0, processingEnv); + randomNextDouble = TreeUtils.getMethod("java.util.Random", "nextDouble", 0, processingEnv); + randomNextInt = TreeUtils.getMethod("java.util.Random", "nextInt", 1, processingEnv); + + stringLength = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); + + mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); + mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); + + indexOfStringMethods = new ArrayList<>(4); + indexOfStringMethods.add( + TreeUtils.getMethod( + "java.lang.String", "indexOf", processingEnv, "java.lang.String")); + indexOfStringMethods.add( + TreeUtils.getMethod( + "java.lang.String", "indexOf", processingEnv, "java.lang.String", "int")); + indexOfStringMethods.add( + TreeUtils.getMethod( + "java.lang.String", "lastIndexOf", processingEnv, "java.lang.String")); + indexOfStringMethods.add( + TreeUtils.getMethod( + "java.lang.String", + "lastIndexOf", + processingEnv, + "java.lang.String", + "int")); + + lengthOfValueElement = TreeUtils.getMethod(LengthOf.class, "value", 0, processingEnv); } - return isLengthOfMethodInvocation(TreeUtils.elementFromUse((MethodInvocationTree) tree)); - } - - /** - * Returns true if {@code tree} evaluates to the length of "this". This might be a call to - * String,length, or a method annotated with @LengthOf. - * - * @return true if {@code tree} evaluates to the length of "this" - */ - public boolean isLengthOfMethodInvocation(ExecutableElement ele) { - if (stringLength.equals(ele)) { - // TODO: Why not just annotate String.length with @LengthOf and thus eliminate the - // special case in this method's implementation? - return true; + + /** + * Returns true iff the argument is an invocation of String#indexOf or String#lastIndexOf that + * takes a string parameter. + * + * @param methodTree the method invocation tree to be tested + * @return true iff the argument is an invocation of one of String's indexOf or lastIndexOf + * methods that takes another string as a parameter + */ + public boolean isIndexOfString(Tree methodTree) { + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + return TreeUtils.isMethodInvocation(methodTree, indexOfStringMethods, processingEnv); + } + + /** + * Returns true iff the argument is an invocation of Math.min. + * + * @param methodTree the method invocation tree to be tested + * @return true iff the argument is an invocation of Math.min() + */ + public boolean isMathMin(Tree methodTree) { + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); + } + + /** Returns true iff the argument is an invocation of Math.max. */ + public boolean isMathMax(Tree methodTree) { + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); + } + + /** Returns true iff the argument is an invocation of Math.random(). */ + public boolean isMathRandom(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, mathRandom, processingEnv); } - AnnotationMirror lengthOfAnno = atypeFactory.getDeclAnnotation(ele, LengthOf.class); - if (lengthOfAnno == null) { - return false; + /** Returns true iff the argument is an invocation of Random.nextDouble(). */ + public boolean isRandomNextDouble(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, randomNextDouble, processingEnv); } - AnnotationValue lengthOfValue = lengthOfAnno.getElementValues().get(lengthOfValueElement); - return AnnotationUtils.annotationValueContains(lengthOfValue, "this"); - } - - /** - * Returns true if {@code node} is an invocation of a method that returns the length of {@code - * this} - * - * @param node a node - * @return true if {@code node} is an invocation of a method that returns the length of {@code - * this} - */ - public boolean isLengthOfMethodInvocation(Node node) { - if (node instanceof MethodInvocationNode) { - MethodInvocationNode methodInvocationNode = (MethodInvocationNode) node; - MethodAccessNode methodAccessNode = methodInvocationNode.getTarget(); - ExecutableElement ele = methodAccessNode.getMethod(); - - return isLengthOfMethodInvocation(ele); + + /** Returns true iff the argument is an invocation of Random.nextInt(). */ + public boolean isRandomNextInt(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, randomNextInt, processingEnv); + } + + /** + * Returns true if {@code tree} is an invocation of a method that returns the length of "this" + * + * @param tree a tree + * @return true if {@code tree} is an invocation of a method that returns the length of {@code + * this} + */ + public boolean isLengthOfMethodInvocation(Tree tree) { + if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { + return false; + } + return isLengthOfMethodInvocation(TreeUtils.elementFromUse((MethodInvocationTree) tree)); + } + + /** + * Returns true if {@code tree} evaluates to the length of "this". This might be a call to + * String,length, or a method annotated with @LengthOf. + * + * @return true if {@code tree} evaluates to the length of "this" + */ + public boolean isLengthOfMethodInvocation(ExecutableElement ele) { + if (stringLength.equals(ele)) { + // TODO: Why not just annotate String.length with @LengthOf and thus eliminate the + // special case in this method's implementation? + return true; + } + + AnnotationMirror lengthOfAnno = atypeFactory.getDeclAnnotation(ele, LengthOf.class); + if (lengthOfAnno == null) { + return false; + } + AnnotationValue lengthOfValue = lengthOfAnno.getElementValues().get(lengthOfValueElement); + return AnnotationUtils.annotationValueContains(lengthOfValue, "this"); + } + + /** + * Returns true if {@code node} is an invocation of a method that returns the length of {@code + * this} + * + * @param node a node + * @return true if {@code node} is an invocation of a method that returns the length of {@code + * this} + */ + public boolean isLengthOfMethodInvocation(Node node) { + if (node instanceof MethodInvocationNode) { + MethodInvocationNode methodInvocationNode = (MethodInvocationNode) node; + MethodAccessNode methodAccessNode = methodInvocationNode.getTarget(); + ExecutableElement ele = methodAccessNode.getMethod(); + + return isLengthOfMethodInvocation(ele); + } + return false; } - return false; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java b/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java index 233773cf79d..31e4c5d2854 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexRefinementInfo.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.index; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.TransferResult; @@ -13,6 +12,8 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.TypeSystemError; +import javax.lang.model.element.AnnotationMirror; + /** * This struct contains all of the information that the refinement functions need. It's called by * each node function (i.e. greater than node, less than node, etc.) and then the results are passed @@ -21,85 +22,87 @@ */ public class IndexRefinementInfo { - /** The left operand. */ - public final Node left; + /** The left operand. */ + public final Node left; - /** The right operand. */ - public final Node right; + /** The right operand. */ + public final Node right; - /** - * Annotation for left expressions. Might be null if dataflow doesn't have a value for the - * expression. - */ - public final @Nullable AnnotationMirror leftAnno; + /** + * Annotation for left expressions. Might be null if dataflow doesn't have a value for the + * expression. + */ + public final @Nullable AnnotationMirror leftAnno; - /** - * Annotation for right expressions. Might be null if dataflow doesn't have a value for the - * expression. - */ - public final @Nullable AnnotationMirror rightAnno; + /** + * Annotation for right expressions. Might be null if dataflow doesn't have a value for the + * expression. + */ + public final @Nullable AnnotationMirror rightAnno; - /** The then store. */ - public final CFStore thenStore; + /** The then store. */ + public final CFStore thenStore; - /** The else store. */ - public final CFStore elseStore; + /** The else store. */ + public final CFStore elseStore; - /** The new result, after refinement. */ - public final ConditionalTransferResult newResult; + /** The new result, after refinement. */ + public final ConditionalTransferResult newResult; - /** - * Creates a new IndexRefinementInfo. - * - * @param left the left operand - * @param right the right operand - * @param result the new result, after refinement - * @param analysis the CFAbstractAnalysis - */ - public IndexRefinementInfo( - TransferResult result, - CFAbstractAnalysis analysis, - Node right, - Node left) { - this.right = right; - this.left = left; + /** + * Creates a new IndexRefinementInfo. + * + * @param left the left operand + * @param right the right operand + * @param result the new result, after refinement + * @param analysis the CFAbstractAnalysis + */ + public IndexRefinementInfo( + TransferResult result, + CFAbstractAnalysis analysis, + Node right, + Node left) { + this.right = right; + this.left = left; - thenStore = result.getThenStore(); - elseStore = result.getElseStore(); + thenStore = result.getThenStore(); + elseStore = result.getElseStore(); - if (analysis.getValue(right) == null || analysis.getValue(left) == null) { - leftAnno = null; - rightAnno = null; - newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - } else { - QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - rightAnno = getAnno(analysis.getValue(right).getAnnotations(), hierarchy); - leftAnno = getAnno(analysis.getValue(left).getAnnotations(), hierarchy); - newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + if (analysis.getValue(right) == null || analysis.getValue(left) == null) { + leftAnno = null; + rightAnno = null; + newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + } else { + QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + rightAnno = getAnno(analysis.getValue(right).getAnnotations(), hierarchy); + leftAnno = getAnno(analysis.getValue(left).getAnnotations(), hierarchy); + newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + } } - } - public IndexRefinementInfo( - TransferResult result, - CFAbstractAnalysis analysis, - BinaryOperationNode node) { - this(result, analysis, node.getRightOperand(), node.getLeftOperand()); - } + public IndexRefinementInfo( + TransferResult result, + CFAbstractAnalysis analysis, + BinaryOperationNode node) { + this(result, analysis, node.getRightOperand(), node.getLeftOperand()); + } - /** - * Returns the annotation (from the given set) in the given hierarchy. - * - * @param set a set of annotations - * @param hierarchy a qualifier hierarchy - * @return the annotation (from {@code set}) in the given hierarchy - */ - private static AnnotationMirror getAnno(AnnotationMirrorSet set, QualifierHierarchy hierarchy) { - AnnotationMirrorSet tops = hierarchy.getTopAnnotations(); - if (tops.size() != 1) { - throw new TypeSystemError( - "%s: Found %d tops, but expected one.%nFound: %s", - IndexRefinementInfo.class, tops.size(), tops); + /** + * Returns the annotation (from the given set) in the given hierarchy. + * + * @param set a set of annotations + * @param hierarchy a qualifier hierarchy + * @return the annotation (from {@code set}) in the given hierarchy + */ + private static AnnotationMirror getAnno(AnnotationMirrorSet set, QualifierHierarchy hierarchy) { + AnnotationMirrorSet tops = hierarchy.getTopAnnotations(); + if (tops.size() != 1) { + throw new TypeSystemError( + "%s: Found %d tops, but expected one.%nFound: %s", + IndexRefinementInfo.class, tops.size(), tops); + } + return hierarchy.findAnnotationInHierarchy(set, tops.iterator().next()); } - return hierarchy.findAnnotationInHierarchy(set, tops.iterator().next()); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java b/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java index 5cc17ff13ca..9f86692a4e4 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/index/IndexUtil.java @@ -4,40 +4,42 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** A collection of utility functions used by several Index Checker subcheckers. */ public class IndexUtil { - /** Do not instantiate IndexUtil. */ - private IndexUtil() { - throw new Error("Do not instantiate IndexUtil."); - } - - /** - * Returns true if the type is a sequence supported by this checker. - * - * @param type a type - * @return true if the type is a sequence supported by this checker - */ - public static boolean isSequenceType(TypeMirror type) { - return type.getKind() == TypeKind.ARRAY || TypesUtils.isString(type); - } - - /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ - public static @Nullable ExpressionTree getLengthSequenceTree( - Tree lengthTree, IndexMethodIdentifier imf, ProcessingEnvironment processingEnv) { - if (TreeUtils.isArrayLengthAccess(lengthTree)) { - return ((MemberSelectTree) lengthTree).getExpression(); - } else if (imf.isLengthOfMethodInvocation(lengthTree)) { - return TreeUtils.getReceiverTree((MethodInvocationTree) lengthTree); + /** Do not instantiate IndexUtil. */ + private IndexUtil() { + throw new Error("Do not instantiate IndexUtil."); + } + + /** + * Returns true if the type is a sequence supported by this checker. + * + * @param type a type + * @return true if the type is a sequence supported by this checker + */ + public static boolean isSequenceType(TypeMirror type) { + return type.getKind() == TypeKind.ARRAY || TypesUtils.isString(type); } - return null; - } + /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ + public static @Nullable ExpressionTree getLengthSequenceTree( + Tree lengthTree, IndexMethodIdentifier imf, ProcessingEnvironment processingEnv) { + if (TreeUtils.isArrayLengthAccess(lengthTree)) { + return ((MemberSelectTree) lengthTree).getExpression(); + } else if (imf.isLengthOfMethodInvocation(lengthTree)) { + return TreeUtils.getReceiverTree((MethodInvocationTree) lengthTree); + } + + return null; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java b/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java index 202f82913fe..316ac4f43f2 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java +++ b/checker/src/main/java/org/checkerframework/checker/index/OffsetDependentTypesHelper.java @@ -1,6 +1,7 @@ package org.checkerframework.checker.index; import com.sun.source.tree.MemberSelectTree; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.ValueCheckerUtils; import org.checkerframework.dataflow.expression.JavaExpression; @@ -16,28 +17,28 @@ * addition or subtraction of several Java expressions. For example, {@code array.length - 1}. */ public class OffsetDependentTypesHelper extends DependentTypesHelper { - public OffsetDependentTypesHelper(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + public OffsetDependentTypesHelper(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - @Override - protected @Nullable JavaExpression transform(JavaExpression javaExpr) { - return ValueCheckerUtils.optimize(javaExpr, atypeFactory); - } + @Override + protected @Nullable JavaExpression transform(JavaExpression javaExpr) { + return ValueCheckerUtils.optimize(javaExpr, atypeFactory); + } - @Override - public TreeAnnotator createDependentTypesTreeAnnotator() { - return new DependentTypesTreeAnnotator(atypeFactory, this) { - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - // UpperBoundTreeAnnotator changes the type of array.length to @LTEL("array"). - // If the DependentTypesTreeAnnotator tries to viewpoint-adapt it based on the - // declaration of length, it will fail. - if (TreeUtils.isArrayLengthAccess(tree)) { - return null; - } - return super.visitMemberSelect(tree, type); - } - }; - } + @Override + public TreeAnnotator createDependentTypesTreeAnnotator() { + return new DependentTypesTreeAnnotator(atypeFactory, this) { + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + // UpperBoundTreeAnnotator changes the type of array.length to @LTEL("array"). + // If the DependentTypesTreeAnnotator tries to viewpoint-adapt it based on the + // declaration of length, it will fail. + if (TreeUtils.isArrayLengthAccess(tree)) { + return null; + } + return super.visitMemberSelect(tree, type); + } + }; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java b/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java index 126eac408e9..fca1e8aa2ce 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java +++ b/checker/src/main/java/org/checkerframework/checker/index/Subsequence.java @@ -1,9 +1,7 @@ package org.checkerframework.checker.index; import com.sun.source.tree.Tree; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.index.qual.HasSubsequence; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.expression.FieldAccess; @@ -14,154 +12,158 @@ import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; + /** Holds information from {@link HasSubsequence} annotations. */ public class Subsequence { - /** Name of the Subsequence. */ - public final String array; - - /** First index of the subsequence in the backing sequence. */ - public final String from; - - /** Last index of the subsequence in the backing sequence. */ - public final String to; - - private Subsequence(String array, String from, String to) { - this.array = array; - this.from = from; - this.to = to; - } - - /** - * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration of - * {@code varTree} or null if there is not such annotation. - * - *

          Note that this method does not standardize or viewpoint adapt the arguments to the - * annotation, unlike getSubsequenceFromReceiver. - * - * @param varTree some tree - * @param factory an AnnotatedTypeFactory - * @return null or a new Subsequence from the declaration of {@code varTree} - */ - public static @Nullable Subsequence getSubsequenceFromTree( - Tree varTree, BaseAnnotatedTypeFactoryForIndexChecker factory) { - - if (!(varTree.getKind() == Tree.Kind.IDENTIFIER - || varTree.getKind() == Tree.Kind.MEMBER_SELECT - || varTree.getKind() == Tree.Kind.VARIABLE)) { - return null; - } + /** Name of the Subsequence. */ + public final String array; + + /** First index of the subsequence in the backing sequence. */ + public final String from; + + /** Last index of the subsequence in the backing sequence. */ + public final String to; - Element element = TreeUtils.elementFromTree(varTree); - AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); - return createSubsequence(hasSub, factory); - } - - /** - * Factory method to create a representation of a subsequence. - * - * @param hasSub {@link HasSubsequence} annotation or null - * @param factory the type factory - * @return a new Subsequence object representing {@code hasSub} or null - */ - private static @Nullable Subsequence createSubsequence( - AnnotationMirror hasSub, BaseAnnotatedTypeFactoryForIndexChecker factory) { - if (hasSub == null) { - return null; + private Subsequence(String array, String from, String to) { + this.array = array; + this.from = from; + this.to = to; } - String from = factory.hasSubsequenceFromValue(hasSub); - String to = factory.hasSubsequenceToValue(hasSub); - String array = factory.hasSubsequenceSubsequenceValue(hasSub); - - return new Subsequence(array, from, to); - } - - /** - * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration of - * {@code rec} or null if there is not such annotation. - * - * @param expr some tree - * @param factory an AnnotatedTypeFactory - * @return null or a new Subsequence from the declaration of {@code varTree} - */ - public static @Nullable Subsequence getSubsequenceFromReceiver( - JavaExpression expr, BaseAnnotatedTypeFactoryForIndexChecker factory) { - if (!(expr instanceof FieldAccess)) { - return null; + + /** + * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration + * of {@code varTree} or null if there is not such annotation. + * + *

          Note that this method does not standardize or viewpoint adapt the arguments to the + * annotation, unlike getSubsequenceFromReceiver. + * + * @param varTree some tree + * @param factory an AnnotatedTypeFactory + * @return null or a new Subsequence from the declaration of {@code varTree} + */ + public static @Nullable Subsequence getSubsequenceFromTree( + Tree varTree, BaseAnnotatedTypeFactoryForIndexChecker factory) { + + if (!(varTree.getKind() == Tree.Kind.IDENTIFIER + || varTree.getKind() == Tree.Kind.MEMBER_SELECT + || varTree.getKind() == Tree.Kind.VARIABLE)) { + return null; + } + + Element element = TreeUtils.elementFromTree(varTree); + AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); + return createSubsequence(hasSub, factory); } - FieldAccess fa = (FieldAccess) expr; - VariableElement element = fa.getField(); - AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); - if (hasSub == null) { - return null; + /** + * Factory method to create a representation of a subsequence. + * + * @param hasSub {@link HasSubsequence} annotation or null + * @param factory the type factory + * @return a new Subsequence object representing {@code hasSub} or null + */ + private static @Nullable Subsequence createSubsequence( + AnnotationMirror hasSub, BaseAnnotatedTypeFactoryForIndexChecker factory) { + if (hasSub == null) { + return null; + } + String from = factory.hasSubsequenceFromValue(hasSub); + String to = factory.hasSubsequenceToValue(hasSub); + String array = factory.hasSubsequenceSubsequenceValue(hasSub); + + return new Subsequence(array, from, to); } - String array = - standardizeAndViewpointAdapt( - factory.hasSubsequenceSubsequenceValue(hasSub), fa, factory.getChecker()); - String from = - standardizeAndViewpointAdapt( - factory.hasSubsequenceFromValue(hasSub), fa, factory.getChecker()); - String to = - standardizeAndViewpointAdapt( - factory.hasSubsequenceToValue(hasSub), fa, factory.getChecker()); - - return new Subsequence(array, from, to); - } - - /** - * Helper function to standardize and viewpoint-adapt a string at a given field access. Wraps - * {@link JavaExpressionParseUtil#parse}. If a parse exception is encountered, this returns its - * argument. - * - * @param s a Java expression string - * @param fieldAccess the field access - * @param checker used to parse the expression - * @return the argument, standardized and viewpoint-adapted - */ - private static String standardizeAndViewpointAdapt( - String s, FieldAccess fieldAccess, SourceChecker checker) { - JavaExpression parseResult; - try { - parseResult = StringToJavaExpression.atFieldDecl(s, fieldAccess.getField(), checker); - } catch (JavaExpressionParseException e) { - return s; + /** + * Returns a Subsequence representing the {@link HasSubsequence} annotation on the declaration + * of {@code rec} or null if there is not such annotation. + * + * @param expr some tree + * @param factory an AnnotatedTypeFactory + * @return null or a new Subsequence from the declaration of {@code varTree} + */ + public static @Nullable Subsequence getSubsequenceFromReceiver( + JavaExpression expr, BaseAnnotatedTypeFactoryForIndexChecker factory) { + if (!(expr instanceof FieldAccess)) { + return null; + } + + FieldAccess fa = (FieldAccess) expr; + VariableElement element = fa.getField(); + AnnotationMirror hasSub = factory.getDeclAnnotation(element, HasSubsequence.class); + if (hasSub == null) { + return null; + } + + String array = + standardizeAndViewpointAdapt( + factory.hasSubsequenceSubsequenceValue(hasSub), fa, factory.getChecker()); + String from = + standardizeAndViewpointAdapt( + factory.hasSubsequenceFromValue(hasSub), fa, factory.getChecker()); + String to = + standardizeAndViewpointAdapt( + factory.hasSubsequenceToValue(hasSub), fa, factory.getChecker()); + + return new Subsequence(array, from, to); } - return parseResult.atFieldAccess(fieldAccess.getReceiver()).toString(); - } - - /** - * Returns the additive inverse of the given String. That is, if the result of this method is some - * String s', then s + s' == 0 will evaluate to true. Note that this relies on the fact that the - * JavaExpression parser cannot parse multiplication, so it naively just changes '-' to '+' and - * vice-versa. - * - * @param s a Java expression string - * @return the negated string - */ - public static String negateString(String s) { - String original = s; - String result = ""; - if (!original.startsWith("-")) { - result += '-'; + /** + * Helper function to standardize and viewpoint-adapt a string at a given field access. Wraps + * {@link JavaExpressionParseUtil#parse}. If a parse exception is encountered, this returns its + * argument. + * + * @param s a Java expression string + * @param fieldAccess the field access + * @param checker used to parse the expression + * @return the argument, standardized and viewpoint-adapted + */ + private static String standardizeAndViewpointAdapt( + String s, FieldAccess fieldAccess, SourceChecker checker) { + JavaExpression parseResult; + try { + parseResult = StringToJavaExpression.atFieldDecl(s, fieldAccess.getField(), checker); + } catch (JavaExpressionParseException e) { + return s; + } + + return parseResult.atFieldAccess(fieldAccess.getReceiver()).toString(); } - for (int i = 0; i < original.length(); i++) { - char c = original.charAt(i); - if (c == '-') { - result += '+'; - } else if (c == '+') { - result += '-'; - } else { - result += c; - } + + /** + * Returns the additive inverse of the given String. That is, if the result of this method is + * some String s', then s + s' == 0 will evaluate to true. Note that this relies on the fact + * that the JavaExpression parser cannot parse multiplication, so it naively just changes '-' to + * '+' and vice-versa. + * + * @param s a Java expression string + * @return the negated string + */ + public static String negateString(String s) { + String original = s; + String result = ""; + if (!original.startsWith("-")) { + result += '-'; + } + for (int i = 0; i < original.length(); i++) { + char c = original.charAt(i); + if (c == '-') { + result += '+'; + } else if (c == '+') { + result += '-'; + } else { + result += c; + } + } + return result; } - return result; - } - @Override - public String toString() { - return "Subsequence(array=" + this.array + ", from=" + this.from + ", to=" + this.to + ")"; - } + @Override + public String toString() { + return "Subsequence(array=" + this.array + ", from=" + this.from + ", to=" + this.to + ")"; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java index 0ca964eedec..641527d5231 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanAnnotatedTypeFactory.java @@ -3,16 +3,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker; import org.checkerframework.checker.index.OffsetDependentTypesHelper; import org.checkerframework.checker.index.qual.LessThan; @@ -40,316 +31,335 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; + /** The type factory for the Less Than Checker. */ public class LessThanAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker { - /** The @LessThanBottom annotation. */ - private final AnnotationMirror LESS_THAN_BOTTOM = - AnnotationBuilder.fromClass(elements, LessThanBottom.class); - - /** The @LessThanUnknown annotation. */ - public final AnnotationMirror LESS_THAN_UNKNOWN = - AnnotationBuilder.fromClass(elements, LessThanUnknown.class); - - /** The LessThan.value argument/element. */ - private final ExecutableElement lessThanValueElement = - TreeUtils.getMethod(LessThan.class, "value", 0, processingEnv); - - /** - * Creates a new LessThanAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this type factory - */ - public LessThanAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - /** - * Returns the Value Checker's annotated type factory. - * - * @return the Value Checker's annotated type factory - */ - public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList(LessThan.class, LessThanUnknown.class, LessThanBottom.class)); - } - - @Override - protected DependentTypesHelper createDependentTypesHelper() { - // Allows + or - in a @LessThan. - return new OffsetDependentTypesHelper(this); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new LessThanQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** LessThanQualifierHierarchy. */ - class LessThanQualifierHierarchy extends ElementQualifierHierarchy { + /** The @LessThanBottom annotation. */ + private final AnnotationMirror LESS_THAN_BOTTOM = + AnnotationBuilder.fromClass(elements, LessThanBottom.class); + + /** The @LessThanUnknown annotation. */ + public final AnnotationMirror LESS_THAN_UNKNOWN = + AnnotationBuilder.fromClass(elements, LessThanUnknown.class); + + /** The LessThan.value argument/element. */ + private final ExecutableElement lessThanValueElement = + TreeUtils.getMethod(LessThan.class, "value", 0, processingEnv); /** - * Creates a LessThanQualifierHierarchy from the given classes. + * Creates a new LessThanAnnotatedTypeFactory. * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils + * @param checker the type-checker associated with this type factory */ - public LessThanQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, LessThanAnnotatedTypeFactory.this); + public LessThanAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); } - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - List subList = getLessThanExpressions(subAnno); - if (subList == null) { - return true; - } - List superList = getLessThanExpressions(superAnno); - if (superList == null) { - return false; - } + /** + * Returns the Value Checker's annotated type factory. + * + * @return the Value Checker's annotated type factory + */ + public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); + } - return subList.containsAll(superList); + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList(LessThan.class, LessThanUnknown.class, LessThanBottom.class)); } @Override - public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } - - List a1List = getLessThanExpressions(a1); - List a2List = getLessThanExpressions(a2); - a1List.retainAll(a2List); // intersection - return createLessThanQualifier(a1List); + protected DependentTypesHelper createDependentTypesHelper() { + // Allows + or - in a @LessThan. + return new OffsetDependentTypesHelper(this); } @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } - - List a1List = getLessThanExpressions(a1); - List a2List = getLessThanExpressions(a2); - CollectionsPlume.adjoinAll(a1List, a2List); // union - return createLessThanQualifier(a1List); + protected QualifierHierarchy createQualifierHierarchy() { + return new LessThanQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } - } - - /** - * Returns true if {@code left} is less than {@code right}. - * - * @param left the first tree to compare - * @param right the second tree to compare - * @return is left less than right? - */ - public boolean isLessThan(Tree left, String right) { - AnnotatedTypeMirror leftATM = getAnnotatedType(left); - return isLessThan(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); - } - - /** - * Returns true if {@code left} is less than {@code right}. - * - * @param left the first value to compare (an annotation) - * @param right the second value to compare (an expression) - * @return is left less than right? - */ - public boolean isLessThan(AnnotationMirror left, String right) { - List expressions = getLessThanExpressions(left); - if (expressions == null) { - // `left` is @LessThanBottom - return true; + + /** LessThanQualifierHierarchy. */ + class LessThanQualifierHierarchy extends ElementQualifierHierarchy { + + /** + * Creates a LessThanQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + public LessThanQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, LessThanAnnotatedTypeFactory.this); + } + + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + List subList = getLessThanExpressions(subAnno); + if (subList == null) { + return true; + } + List superList = getLessThanExpressions(superAnno); + if (superList == null) { + return false; + } + + return subList.containsAll(superList); + } + + @Override + public AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } + + List a1List = getLessThanExpressions(a1); + List a2List = getLessThanExpressions(a2); + a1List.retainAll(a2List); // intersection + return createLessThanQualifier(a1List); + } + + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } + + List a1List = getLessThanExpressions(a1); + List a2List = getLessThanExpressions(a2); + CollectionsPlume.adjoinAll(a1List, a2List); // union + return createLessThanQualifier(a1List); + } } - return expressions.contains(right); - } - - /** - * Returns true if {@code smaller < bigger}. - * - * @param smaller the first value to compare - * @param bigger the second value to compare - * @param path used to parse expressions strings - * @return {@code smaller < bigger}, using information from the Value Checker - */ - public boolean isLessThanByValue(Tree smaller, String bigger, TreePath path) { - Long smallerValue = ValueCheckerUtils.getMaxValue(smaller, getValueAnnotatedTypeFactory()); - if (smallerValue == null) { - return false; + + /** + * Returns true if {@code left} is less than {@code right}. + * + * @param left the first tree to compare + * @param right the second tree to compare + * @return is left less than right? + */ + public boolean isLessThan(Tree left, String right) { + AnnotatedTypeMirror leftATM = getAnnotatedType(left); + return isLessThan(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); } - OffsetEquation offsetEquation = OffsetEquation.createOffsetFromJavaExpression(bigger); - if (offsetEquation.isInt()) { - // bigger is an int literal - return smallerValue < offsetEquation.getInt(); + /** + * Returns true if {@code left} is less than {@code right}. + * + * @param left the first value to compare (an annotation) + * @param right the second value to compare (an expression) + * @return is left less than right? + */ + public boolean isLessThan(AnnotationMirror left, String right) { + List expressions = getLessThanExpressions(left); + if (expressions == null) { + // `left` is @LessThanBottom + return true; + } + return expressions.contains(right); } - // If bigger is "expression + literal", then smaller < expression + literal - // can be reduced to smaller - literal < expression + literal - literal - smallerValue = smallerValue - offsetEquation.getInt(); - offsetEquation = - offsetEquation.copyAdd('-', OffsetEquation.createOffsetForInt(offsetEquation.getInt())); - - long minValueOfBigger = getMinValueFromString(offsetEquation.toString(), smaller, path); - return smallerValue < minValueOfBigger; - } - - /** - * Returns the minimum value of {@code expression} at {@code tree}. - * - * @param expression the expression whose minimum value to retrieve - * @param tree where to determine the value - * @param path the path to {@code tree} - * @return the minimum value of {@code expression} at {@code tree} - */ - private long getMinValueFromString(String expression, Tree tree, TreePath path) { - ValueAnnotatedTypeFactory valueAtypeFactory = getValueAnnotatedTypeFactory(); - JavaExpression expressionJe; - try { - expressionJe = valueAtypeFactory.parseJavaExpressionString(expression, path); - } catch (JavaExpressionParseException e) { - return Long.MIN_VALUE; + + /** + * Returns true if {@code smaller < bigger}. + * + * @param smaller the first value to compare + * @param bigger the second value to compare + * @param path used to parse expressions strings + * @return {@code smaller < bigger}, using information from the Value Checker + */ + public boolean isLessThanByValue(Tree smaller, String bigger, TreePath path) { + Long smallerValue = ValueCheckerUtils.getMaxValue(smaller, getValueAnnotatedTypeFactory()); + if (smallerValue == null) { + return false; + } + + OffsetEquation offsetEquation = OffsetEquation.createOffsetFromJavaExpression(bigger); + if (offsetEquation.isInt()) { + // bigger is an int literal + return smallerValue < offsetEquation.getInt(); + } + // If bigger is "expression + literal", then smaller < expression + literal + // can be reduced to smaller - literal < expression + literal - literal + smallerValue = smallerValue - offsetEquation.getInt(); + offsetEquation = + offsetEquation.copyAdd( + '-', OffsetEquation.createOffsetForInt(offsetEquation.getInt())); + + long minValueOfBigger = getMinValueFromString(offsetEquation.toString(), smaller, path); + return smallerValue < minValueOfBigger; } - AnnotationMirror intRange = - valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntRange.class); - if (intRange != null) { - return valueAtypeFactory.getRange(intRange).from; + /** + * Returns the minimum value of {@code expression} at {@code tree}. + * + * @param expression the expression whose minimum value to retrieve + * @param tree where to determine the value + * @param path the path to {@code tree} + * @return the minimum value of {@code expression} at {@code tree} + */ + private long getMinValueFromString(String expression, Tree tree, TreePath path) { + ValueAnnotatedTypeFactory valueAtypeFactory = getValueAnnotatedTypeFactory(); + JavaExpression expressionJe; + try { + expressionJe = valueAtypeFactory.parseJavaExpressionString(expression, path); + } catch (JavaExpressionParseException e) { + return Long.MIN_VALUE; + } + + AnnotationMirror intRange = + valueAtypeFactory.getAnnotationFromJavaExpression( + expressionJe, tree, IntRange.class); + if (intRange != null) { + return valueAtypeFactory.getRange(intRange).from; + } + AnnotationMirror intValue = + valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntVal.class); + if (intValue != null) { + List possibleValues = valueAtypeFactory.getIntValues(intValue); + return Collections.min(possibleValues); + } + + if (expressionJe instanceof FieldAccess) { + FieldAccess fieldAccess = ((FieldAccess) expressionJe); + if (fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY) { + // array.length might not be in the store, so check for the length of the array. + AnnotationMirror arrayRange = + valueAtypeFactory.getAnnotationFromJavaExpression( + fieldAccess.getReceiver(), tree, ArrayLenRange.class); + if (arrayRange != null) { + return valueAtypeFactory.getRange(arrayRange).from; + } + AnnotationMirror arrayLen = + valueAtypeFactory.getAnnotationFromJavaExpression( + expressionJe, tree, ArrayLen.class); + if (arrayLen != null) { + List possibleValues = valueAtypeFactory.getArrayLength(arrayLen); + return Collections.min(possibleValues); + } + // Even arrays that we know nothing about must have at least zero length. + return 0; + } + } + + return Long.MIN_VALUE; } - AnnotationMirror intValue = - valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, IntVal.class); - if (intValue != null) { - List possibleValues = valueAtypeFactory.getIntValues(intValue); - return Collections.min(possibleValues); + + /** + * Returns true if left is less than or equal to right. + * + * @param left the first value to compare + * @param right the second value to compare + * @return is left less than or equal to right? + */ + public boolean isLessThanOrEqual(Tree left, String right) { + AnnotatedTypeMirror leftATM = getAnnotatedType(left); + return isLessThanOrEqual(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); } - if (expressionJe instanceof FieldAccess) { - FieldAccess fieldAccess = ((FieldAccess) expressionJe); - if (fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY) { - // array.length might not be in the store, so check for the length of the array. - AnnotationMirror arrayRange = - valueAtypeFactory.getAnnotationFromJavaExpression( - fieldAccess.getReceiver(), tree, ArrayLenRange.class); - if (arrayRange != null) { - return valueAtypeFactory.getRange(arrayRange).from; + /** + * Returns true if left is less than or equal to right. + * + * @param left the first value to compare + * @param right the second value to compare + * @return is left less than or equal to right? + */ + public boolean isLessThanOrEqual(AnnotationMirror left, String right) { + List expressions = getLessThanExpressions(left); + if (expressions == null) { + // left is bottom so it is always less than right. + return true; + } + if (expressions.contains(right)) { + return true; } - AnnotationMirror arrayLen = - valueAtypeFactory.getAnnotationFromJavaExpression(expressionJe, tree, ArrayLen.class); - if (arrayLen != null) { - List possibleValues = valueAtypeFactory.getArrayLength(arrayLen); - return Collections.min(possibleValues); + for (String expression : expressions) { + if (expression.endsWith(" + 1") + && expression.substring(0, expression.length() - 4).equals(right)) { + return true; + } } - // Even arrays that we know nothing about must have at least zero length. - return 0; - } + return false; } - return Long.MIN_VALUE; - } - - /** - * Returns true if left is less than or equal to right. - * - * @param left the first value to compare - * @param right the second value to compare - * @return is left less than or equal to right? - */ - public boolean isLessThanOrEqual(Tree left, String right) { - AnnotatedTypeMirror leftATM = getAnnotatedType(left); - return isLessThanOrEqual(leftATM.getAnnotationInHierarchy(LESS_THAN_UNKNOWN), right); - } - - /** - * Returns true if left is less than or equal to right. - * - * @param left the first value to compare - * @param right the second value to compare - * @return is left less than or equal to right? - */ - public boolean isLessThanOrEqual(AnnotationMirror left, String right) { - List expressions = getLessThanExpressions(left); - if (expressions == null) { - // left is bottom so it is always less than right. - return true; - } - if (expressions.contains(right)) { - return true; + /** + * Returns a sorted, modifiable list of expressions that {@code expression} is less than. If the + * {@code expression} is annotated with {@link LessThanBottom}, null is returned. + * + * @param expression an expression + * @return expressions that {@code expression} is less than + */ + public @Nullable List getLessThanExpressions(ExpressionTree expression) { + AnnotatedTypeMirror annotatedTypeMirror = getAnnotatedType(expression); + return getLessThanExpressions( + annotatedTypeMirror.getAnnotationInHierarchy(LESS_THAN_UNKNOWN)); } - for (String expression : expressions) { - if (expression.endsWith(" + 1") - && expression.substring(0, expression.length() - 4).equals(right)) { - return true; - } + + /** + * Creates a less than qualifier given the expressions. + * + *

          If expressions is null, {@link LessThanBottom} is returned. If expressions is empty, + * {@link LessThanUnknown} is returned. Otherwise, {@code @LessThan(expressions)} is returned. + * + * @param expressions a list of expressions + * @return a @LessThan qualifier with the given arguments + */ + public AnnotationMirror createLessThanQualifier(@Nullable List expressions) { + if (expressions == null) { + return LESS_THAN_BOTTOM; + } else if (expressions.isEmpty()) { + return LESS_THAN_UNKNOWN; + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, LessThan.class); + builder.setValue("value", expressions); + return builder.build(); + } } - return false; - } - - /** - * Returns a sorted, modifiable list of expressions that {@code expression} is less than. If the - * {@code expression} is annotated with {@link LessThanBottom}, null is returned. - * - * @param expression an expression - * @return expressions that {@code expression} is less than - */ - public @Nullable List getLessThanExpressions(ExpressionTree expression) { - AnnotatedTypeMirror annotatedTypeMirror = getAnnotatedType(expression); - return getLessThanExpressions(annotatedTypeMirror.getAnnotationInHierarchy(LESS_THAN_UNKNOWN)); - } - - /** - * Creates a less than qualifier given the expressions. - * - *

          If expressions is null, {@link LessThanBottom} is returned. If expressions is empty, {@link - * LessThanUnknown} is returned. Otherwise, {@code @LessThan(expressions)} is returned. - * - * @param expressions a list of expressions - * @return a @LessThan qualifier with the given arguments - */ - public AnnotationMirror createLessThanQualifier(@Nullable List expressions) { - if (expressions == null) { - return LESS_THAN_BOTTOM; - } else if (expressions.isEmpty()) { - return LESS_THAN_UNKNOWN; - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, LessThan.class); - builder.setValue("value", expressions); - return builder.build(); + + /** Returns {@code @LessThan(expression)}. */ + public AnnotationMirror createLessThanQualifier(String expression) { + return createLessThanQualifier(Collections.singletonList(expression)); } - } - - /** Returns {@code @LessThan(expression)}. */ - public AnnotationMirror createLessThanQualifier(String expression) { - return createLessThanQualifier(Collections.singletonList(expression)); - } - - /** - * If the annotation is LessThan, returns a list of expressions in the annotation. If the - * annotation is {@link LessThanBottom}, returns null. If the annotation is {@link - * LessThanUnknown}, returns the empty list. - * - * @param annotation an annotation from the same hierarchy as LessThan - * @return the list of expressions in the annotation - */ - public @Nullable List getLessThanExpressions(AnnotationMirror annotation) { - if (AnnotationUtils.areSameByName( - annotation, "org.checkerframework.checker.index.qual.LessThanBottom")) { - return null; - } else if (AnnotationUtils.areSameByName( - annotation, "org.checkerframework.checker.index.qual.LessThanUnknown")) { - return Collections.emptyList(); - } else { - // The annotation is @LessThan. - return AnnotationUtils.getElementValueArray(annotation, lessThanValueElement, String.class); + + /** + * If the annotation is LessThan, returns a list of expressions in the annotation. If the + * annotation is {@link LessThanBottom}, returns null. If the annotation is {@link + * LessThanUnknown}, returns the empty list. + * + * @param annotation an annotation from the same hierarchy as LessThan + * @return the list of expressions in the annotation + */ + public @Nullable List getLessThanExpressions(AnnotationMirror annotation) { + if (AnnotationUtils.areSameByName( + annotation, "org.checkerframework.checker.index.qual.LessThanBottom")) { + return null; + } else if (AnnotationUtils.areSameByName( + annotation, "org.checkerframework.checker.index.qual.LessThanUnknown")) { + return Collections.emptyList(); + } else { + // The annotation is @LessThan. + return AnnotationUtils.getElementValueArray( + annotation, lessThanValueElement, String.class); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java index 2b601b0e131..43cafb61bf3 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanChecker.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.inequality; -import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SuppressWarningsPrefix; +import java.util.Set; + /** * An internal checker that estimates which expression's values are less than other expressions' * values. @@ -14,22 +15,22 @@ */ @SuppressWarningsPrefix({"index", "lessthan"}) @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) public class LessThanChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java index bc297041ad9..b3552e7b31c 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanTransfer.java @@ -1,9 +1,5 @@ package org.checkerframework.checker.index.inequality; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.common.value.ValueAnnotatedTypeFactory; import org.checkerframework.common.value.ValueCheckerUtils; @@ -21,6 +17,12 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.plumelib.util.CollectionsPlume; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; + /** * Implements 3 refinement rules: * @@ -34,156 +36,158 @@ */ public class LessThanTransfer extends IndexAbstractTransfer { - public LessThanTransfer(CFAnalysis analysis) { - super(analysis); - } + public LessThanTransfer(CFAnalysis analysis) { + super(analysis); + } - /** Case 1. */ - @Override - protected void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - // left > right so right < left - // Refine right to @LessThan("left") - JavaExpression leftJe = JavaExpression.fromNode(left); - if (leftJe != null && leftJe.isUnassignableByOtherCode()) { - if (isDoubleOrFloatLiteral(leftJe)) { - return; - } - LessThanAnnotatedTypeFactory factory = - (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - List lessThanExpressions = factory.getLessThanExpressions(rightAnno); - if (lessThanExpressions == null) { - // right is already bottom, nothing to refine. - return; - } - String leftString = leftJe.toString(); - if (!lessThanExpressions.contains(leftString)) { - lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftString); - JavaExpression rightJe = JavaExpression.fromNode(right); - store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); - } + /** Case 1. */ + @Override + protected void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + // left > right so right < left + // Refine right to @LessThan("left") + JavaExpression leftJe = JavaExpression.fromNode(left); + if (leftJe != null && leftJe.isUnassignableByOtherCode()) { + if (isDoubleOrFloatLiteral(leftJe)) { + return; + } + LessThanAnnotatedTypeFactory factory = + (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + List lessThanExpressions = factory.getLessThanExpressions(rightAnno); + if (lessThanExpressions == null) { + // right is already bottom, nothing to refine. + return; + } + String leftString = leftJe.toString(); + if (!lessThanExpressions.contains(leftString)) { + lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftString); + JavaExpression rightJe = JavaExpression.fromNode(right); + store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); + } + } } - } - /** Case 2. */ - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - // left >= right so right is less than left - // Refine right to @LessThan("left + 1") + /** Case 2. */ + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + // left >= right so right is less than left + // Refine right to @LessThan("left + 1") - // left > right so right is less than left - // Refine right to @LessThan("left") - JavaExpression leftJe = JavaExpression.fromNode(left); - if (leftJe != null && leftJe.isUnassignableByOtherCode()) { - if (isDoubleOrFloatLiteral(leftJe)) { - return; - } - LessThanAnnotatedTypeFactory factory = - (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - List lessThanExpressions = factory.getLessThanExpressions(rightAnno); - if (lessThanExpressions == null) { - // right is already bottom, nothing to refine. - return; - } - String leftIncremented = incrementedExpression(leftJe); - if (!lessThanExpressions.contains(leftIncremented)) { - lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftIncremented); - JavaExpression rightJe = JavaExpression.fromNode(right); - store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); - } + // left > right so right is less than left + // Refine right to @LessThan("left") + JavaExpression leftJe = JavaExpression.fromNode(left); + if (leftJe != null && leftJe.isUnassignableByOtherCode()) { + if (isDoubleOrFloatLiteral(leftJe)) { + return; + } + LessThanAnnotatedTypeFactory factory = + (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + List lessThanExpressions = factory.getLessThanExpressions(rightAnno); + if (lessThanExpressions == null) { + // right is already bottom, nothing to refine. + return; + } + String leftIncremented = incrementedExpression(leftJe); + if (!lessThanExpressions.contains(leftIncremented)) { + lessThanExpressions = CollectionsPlume.append(lessThanExpressions, leftIncremented); + JavaExpression rightJe = JavaExpression.fromNode(right); + store.insertValue(rightJe, factory.createLessThanQualifier(lessThanExpressions)); + } + } } - } - /** Case 3. */ - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput in) { - LessThanAnnotatedTypeFactory factory = (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - JavaExpression leftJe = JavaExpression.fromNode(n.getLeftOperand()); - if (leftJe != null && leftJe.isUnassignableByOtherCode()) { - ValueAnnotatedTypeFactory valueFactory = factory.getValueAnnotatedTypeFactory(); - Long right = ValueCheckerUtils.getMinValue(n.getRightOperand().getTree(), valueFactory); - if (right != null && 0 < right) { - // left - right < left iff 0 < right - List expressions = getLessThanExpressions(n.getLeftOperand()); - if (!isDoubleOrFloatLiteral(leftJe)) { - if (expressions == null) { - expressions = Collections.singletonList(leftJe.toString()); - } else { - expressions = CollectionsPlume.append(expressions, leftJe.toString()); - } + /** Case 3. */ + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput in) { + LessThanAnnotatedTypeFactory factory = + (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + JavaExpression leftJe = JavaExpression.fromNode(n.getLeftOperand()); + if (leftJe != null && leftJe.isUnassignableByOtherCode()) { + ValueAnnotatedTypeFactory valueFactory = factory.getValueAnnotatedTypeFactory(); + Long right = ValueCheckerUtils.getMinValue(n.getRightOperand().getTree(), valueFactory); + if (right != null && 0 < right) { + // left - right < left iff 0 < right + List expressions = getLessThanExpressions(n.getLeftOperand()); + if (!isDoubleOrFloatLiteral(leftJe)) { + if (expressions == null) { + expressions = Collections.singletonList(leftJe.toString()); + } else { + expressions = CollectionsPlume.append(expressions, leftJe.toString()); + } + } + AnnotationMirror refine = factory.createLessThanQualifier(expressions); + CFValue value = analysis.createSingleAnnotationValue(refine, n.getType()); + CFStore info = in.getRegularStore(); + return new RegularTransferResult<>(finishValue(value, info), info); + } } - AnnotationMirror refine = factory.createLessThanQualifier(expressions); - CFValue value = analysis.createSingleAnnotationValue(refine, n.getType()); - CFStore info = in.getRegularStore(); - return new RegularTransferResult<>(finishValue(value, info), info); - } + return super.visitNumericalSubtraction(n, in); } - return super.visitNumericalSubtraction(n, in); - } - /** - * Return the expressions that {@code node} is less than. - * - * @param node a CFG node - * @return the expressions that {@code node} is less than - */ - private List getLessThanExpressions(Node node) { - AnnotationMirrorSet s = analysis.getValue(node).getAnnotations(); - if (s != null && !s.isEmpty()) { - LessThanAnnotatedTypeFactory factory = - (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); - return factory.getLessThanExpressions( - factory.getQualifierHierarchy().findAnnotationInHierarchy(s, factory.LESS_THAN_UNKNOWN)); - } else { - return Collections.emptyList(); + /** + * Return the expressions that {@code node} is less than. + * + * @param node a CFG node + * @return the expressions that {@code node} is less than + */ + private List getLessThanExpressions(Node node) { + AnnotationMirrorSet s = analysis.getValue(node).getAnnotations(); + if (s != null && !s.isEmpty()) { + LessThanAnnotatedTypeFactory factory = + (LessThanAnnotatedTypeFactory) analysis.getTypeFactory(); + return factory.getLessThanExpressions( + factory.getQualifierHierarchy() + .findAnnotationInHierarchy(s, factory.LESS_THAN_UNKNOWN)); + } else { + return Collections.emptyList(); + } } - } - /** - * Return true if {@code expr} is a double or float literal, which can't be parsed by {@link - * JavaExpressionParseUtil}. - */ - private boolean isDoubleOrFloatLiteral(JavaExpression expr) { - if (expr instanceof ValueLiteral) { - return expr.getType().getKind() == TypeKind.DOUBLE - || expr.getType().getKind() == TypeKind.FLOAT; - } else { - return false; + /** + * Return true if {@code expr} is a double or float literal, which can't be parsed by {@link + * JavaExpressionParseUtil}. + */ + private boolean isDoubleOrFloatLiteral(JavaExpression expr) { + if (expr instanceof ValueLiteral) { + return expr.getType().getKind() == TypeKind.DOUBLE + || expr.getType().getKind() == TypeKind.FLOAT; + } else { + return false; + } } - } - /** - * Return the string representation of {@code expr + 1}. - * - * @param expr a JavaExpression - * @return the string representation of {@code expr + 1} - */ - private String incrementedExpression(JavaExpression expr) { - expr = ValueCheckerUtils.optimize(expr, analysis.getTypeFactory()); - if (expr instanceof ValueLiteral) { - ValueLiteral literal = (ValueLiteral) expr; - if (literal.getValue() instanceof Number) { - long longLiteral = ((Number) literal.getValue()).longValue(); - if (longLiteral != Long.MAX_VALUE) { - return (longLiteral + 1) + "L"; + /** + * Return the string representation of {@code expr + 1}. + * + * @param expr a JavaExpression + * @return the string representation of {@code expr + 1} + */ + private String incrementedExpression(JavaExpression expr) { + expr = ValueCheckerUtils.optimize(expr, analysis.getTypeFactory()); + if (expr instanceof ValueLiteral) { + ValueLiteral literal = (ValueLiteral) expr; + if (literal.getValue() instanceof Number) { + long longLiteral = ((Number) literal.getValue()).longValue(); + if (longLiteral != Long.MAX_VALUE) { + return (longLiteral + 1) + "L"; + } + } } - } - } - // Could do more optimization to merge with a literal at end of `exprString`. Is that - // needed? - return expr + " + 1"; - } + // Could do more optimization to merge with a literal at end of `exprString`. Is that + // needed? + return expr + " + 1"; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java index 0f2b145ef59..85a5f6fafed 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java @@ -3,8 +3,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.Tree; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.Subsequence; import org.checkerframework.checker.index.upperbound.OffsetEquation; @@ -14,123 +13,132 @@ import org.checkerframework.framework.util.JavaExpressionParseUtil; import org.plumelib.util.CollectionsPlume; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + /** The visitor for the Less Than Checker. */ public class LessThanVisitor extends BaseTypeVisitor { - private static final @CompilerMessageKey String FROM_GT_TO = "from.gt.to"; - - public LessThanVisitor(BaseTypeChecker checker) { - super(checker); - } - - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - boolean result = true; - - // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) - // occurs, from <= to. - - Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); - if (subSeq != null) { - AnnotationMirror anm; - try { - anm = - atypeFactory.getAnnotationMirrorFromJavaExpressionString( - subSeq.from, varTree, getCurrentPath()); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - anm = null; - } - - LessThanAnnotatedTypeFactory factory = getTypeFactory(); - - if (anm == null || !factory.isLessThanOrEqual(anm, subSeq.to)) { - // issue an error - checker.reportError( - valueTree, - FROM_GT_TO, - subSeq.from, - subSeq.to, - anm == null ? "@LessThanUnknown" : anm, - subSeq.to, - subSeq.to); - result = false; - } + private static final @CompilerMessageKey String FROM_GT_TO = "from.gt.to"; + + public LessThanVisitor(BaseTypeChecker checker) { + super(checker); } - result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; - return result; - } - - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // If value is less than all expressions in the annotation in varType, - // using the Value Checker, then skip the common assignment check. - // Also skip the check if the only expression is "a + 1" and the valueTree is "a". - List expressions = - getTypeFactory() - .getLessThanExpressions( - varType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN)); - if (expressions != null) { - boolean isLessThan = true; - for (String expression : expressions) { - if (!atypeFactory.isLessThanByValue(valueTree, expression, getCurrentPath())) { - isLessThan = false; + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + boolean result = true; + + // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) + // occurs, from <= to. + + Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); + if (subSeq != null) { + AnnotationMirror anm; + try { + anm = + atypeFactory.getAnnotationMirrorFromJavaExpressionString( + subSeq.from, varTree, getCurrentPath()); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + anm = null; + } + + LessThanAnnotatedTypeFactory factory = getTypeFactory(); + + if (anm == null || !factory.isLessThanOrEqual(anm, subSeq.to)) { + // issue an error + checker.reportError( + valueTree, + FROM_GT_TO, + subSeq.from, + subSeq.to, + anm == null ? "@LessThanUnknown" : anm, + subSeq.to, + subSeq.to); + result = false; + } } - } - if (!isLessThan && expressions.size() == 1) { - String expression = expressions.get(0); - if (expression.endsWith(" + 1")) { - String value = expression.substring(0, expression.length() - 4); - if (valueTree.getKind() == Tree.Kind.IDENTIFIER) { - String id = ((IdentifierTree) valueTree).getName().toString(); - if (id.equals(value)) { - isLessThan = true; + + result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; + return result; + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // If value is less than all expressions in the annotation in varType, + // using the Value Checker, then skip the common assignment check. + // Also skip the check if the only expression is "a + 1" and the valueTree is "a". + List expressions = + getTypeFactory() + .getLessThanExpressions( + varType.getEffectiveAnnotationInHierarchy( + atypeFactory.LESS_THAN_UNKNOWN)); + if (expressions != null) { + boolean isLessThan = true; + for (String expression : expressions) { + if (!atypeFactory.isLessThanByValue(valueTree, expression, getCurrentPath())) { + isLessThan = false; + } + } + if (!isLessThan && expressions.size() == 1) { + String expression = expressions.get(0); + if (expression.endsWith(" + 1")) { + String value = expression.substring(0, expression.length() - 4); + if (valueTree.getKind() == Tree.Kind.IDENTIFIER) { + String id = ((IdentifierTree) valueTree).getName().toString(); + if (id.equals(value)) { + isLessThan = true; + } + } + } + } + + if (isLessThan) { + // Print the messages because super isn't called. + commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); + commonAssignmentCheckEndDiagnostic( + true, "isLessThan", varType, valueType, valueTree); + // skip call to super, everything is OK. + return true; } - } } - } - - if (isLessThan) { - // Print the messages because super isn't called. - commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); - commonAssignmentCheckEndDiagnostic(true, "isLessThan", varType, valueType, valueTree); - // skip call to super, everything is OK. - return true; - } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - } - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - AnnotationMirror exprLTAnno = - exprType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN); + AnnotationMirror exprLTAnno = + exprType.getEffectiveAnnotationInHierarchy(atypeFactory.LESS_THAN_UNKNOWN); - if (exprLTAnno != null) { - LessThanAnnotatedTypeFactory factory = getTypeFactory(); - List initialAnnotations = factory.getLessThanExpressions(exprLTAnno); + if (exprLTAnno != null) { + LessThanAnnotatedTypeFactory factory = getTypeFactory(); + List initialAnnotations = factory.getLessThanExpressions(exprLTAnno); - if (initialAnnotations != null) { - List updatedAnnotations = - CollectionsPlume.mapList( - annotation -> OffsetEquation.createOffsetFromJavaExpression(annotation).toString(), - initialAnnotations); + if (initialAnnotations != null) { + List updatedAnnotations = + CollectionsPlume.mapList( + annotation -> + OffsetEquation.createOffsetFromJavaExpression(annotation) + .toString(), + initialAnnotations); - exprType.replaceAnnotation(atypeFactory.createLessThanQualifier(updatedAnnotations)); - } - } + exprType.replaceAnnotation( + atypeFactory.createLessThanQualifier(updatedAnnotations)); + } + } - return super.isTypeCastSafe(castType, exprType); - } + return super.isTypeCastSafe(castType, exprType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java index cbf9e24a6e2..521911c6ecd 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java @@ -7,12 +7,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; + import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker; import org.checkerframework.checker.index.IndexMethodIdentifier; import org.checkerframework.checker.index.inequality.LessThanAnnotatedTypeFactory; @@ -49,6 +44,14 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.TreeUtils; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; + /** * Implements the introduction rules for the Lower Bound Checker. * @@ -90,379 +93,389 @@ */ public class LowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker { - /** The canonical @{@link GTENegativeOne} annotation. */ - public final AnnotationMirror GTEN1 = AnnotationBuilder.fromClass(elements, GTENegativeOne.class); - - /** The canonical @{@link NonNegative} annotation. */ - public final AnnotationMirror NN = AnnotationBuilder.fromClass(elements, NonNegative.class); - - /** The canonical @{@link Positive} annotation. */ - public final AnnotationMirror POS = AnnotationBuilder.fromClass(elements, Positive.class); - - /** The bottom annotation. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, LowerBoundBottom.class); - - /** The canonical @{@link LowerBoundUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, LowerBoundUnknown.class); - - /** The canonical @{@link PolyLowerBound} annotation. */ - public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyLowerBound.class); - - /** Predicates about method calls. */ - private final IndexMethodIdentifier imf; - - /** - * Create a new LowerBoundAnnotatedTypeFactory. - * - * @param checker the type-checker - */ - public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Any annotations that are aliased to @NonNegative, @Positive, or @GTENegativeOne must also - // be aliased in the constructor of ValueAnnotatedTypeFactory to the appropriate - // @IntRangeFrom* annotation. - addAliasedTypeAnnotation("javax.annotation.Nonnegative", NN); - addAliasedTypeAnnotation(IndexFor.class, NN); - addAliasedTypeAnnotation(IndexOrLow.class, GTEN1); - addAliasedTypeAnnotation(IndexOrHigh.class, NN); - addAliasedTypeAnnotation(LengthOf.class, NN); - addAliasedTypeAnnotation(PolyIndex.class, POLY); - addAliasedTypeAnnotation(SubstringIndexFor.class, GTEN1); - - addAliasedTypeAnnotation(SignedPositive.class, NN); - addAliasedTypeAnnotation(SignednessGlb.class, NN); - - imf = new IndexMethodIdentifier(this); - - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - Positive.class, - NonNegative.class, - GTENegativeOne.class, - LowerBoundUnknown.class, - PolyLowerBound.class, - LowerBoundBottom.class)); - } - - /** - * Takes a value type (only interesting if it's an IntVal), and converts it to a lower bound type. - * If the new lower bound type is more specific than type, convert type to that type. - * - * @param valueType the Value Checker type - * @param type the current lower bound type of the expression being evaluated - */ - private void addLowerBoundTypeFromValueType( - AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { - AnnotationMirror anm = getLowerBoundAnnotationFromValueType(valueType); - if (!type.hasAnnotationInHierarchy(UNKNOWN)) { - if (!areSameByClass(anm, LowerBoundUnknown.class)) { - type.addAnnotation(anm); - } - return; - } - if (typeHierarchy.isSubtypeShallowEffective(anm, type)) { - type.replaceAnnotation(anm); - } - } - - /** Handles cases 1, 2, and 3. */ - @Override - public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(element, type); - if (element != null) { - AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element); - addLowerBoundTypeFromValueType(valueType, type); - } - } - - /** Handles cases 1, 2, and 3. */ - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(tree, type); - // If dataflow shouldn't be used to compute this type, then do not use the result from - // the Value Checker, because dataflow is used to compute that type. (Without this, - // "int i = 1; --i;" fails.) - if (tree != null - // Necessary to check that an ajava file isn't being parsed, because the call - // to the Value Checker's getAnnotatedType() method can fail during parsing: - // the check in GenericAnnotatedTypeFactory#addComputedTypeAnnotations only - // checks if the **current** type factory is parsing, not whether the parent - // checker's type factory is parsing. - && !ajavaTypes.isParsing() - && TreeUtils.isExpressionTree(tree) - && (getUseFlow() || tree instanceof LiteralTree)) { - AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); - addLowerBoundTypeFromValueType(valueType, type); - } - } - - /** Returns the Value Checker's annotated type factory. */ - public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); - } - - /** Returns the SearchIndexFor Checker's annotated type factory. */ - public SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SearchIndexChecker.class); - } - - /** Returns the LessThan Checker's annotated type factory. */ - public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(LessThanChecker.class); - } - - /** Returns the type in the lower bound hierarchy that a Value Checker type corresponds to. */ - private AnnotationMirror getLowerBoundAnnotationFromValueType(AnnotatedTypeMirror valueType) { - Range possibleValues = - ValueCheckerUtils.getPossibleValues(valueType, getValueAnnotatedTypeFactory()); - // possibleValues is null if the Value Checker does not have any estimate. - if (possibleValues == null) { - // possibleValues is null if there is no IntVal annotation on the type - such as - // when there is a BottomVal annotation. In that case, give this the LBC's bottom type. - if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { - return BOTTOM; - } - return UNKNOWN; - } - // The annotation of the whole list is the min of the list. - long lvalMin = possibleValues.from; - // Turn it into an integer. - int valMin = (int) Math.max(Math.min(Integer.MAX_VALUE, lvalMin), Integer.MIN_VALUE); - return anmFromVal(valMin); - } - - /** Determine the annotation that should be associated with a literal. */ - /*package-private*/ AnnotationMirror anmFromVal(long val) { - if (val >= 1) { - return POS; - } else if (val >= 0) { - return NN; - } else if (val >= -1) { - return GTEN1; - } else { - return UNKNOWN; - } - } + /** The canonical @{@link GTENegativeOne} annotation. */ + public final AnnotationMirror GTEN1 = + AnnotationBuilder.fromClass(elements, GTENegativeOne.class); - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new LowerBoundTreeAnnotator(this), super.createTreeAnnotator()); - } + /** The canonical @{@link NonNegative} annotation. */ + public final AnnotationMirror NN = AnnotationBuilder.fromClass(elements, NonNegative.class); - private class LowerBoundTreeAnnotator extends TreeAnnotator { - public LowerBoundTreeAnnotator(AnnotatedTypeFactory annotatedTypeFactory) { - super(annotatedTypeFactory); - } + /** The canonical @{@link Positive} annotation. */ + public final AnnotationMirror POS = AnnotationBuilder.fromClass(elements, Positive.class); + + /** The bottom annotation. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, LowerBoundBottom.class); + + /** The canonical @{@link LowerBoundUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, LowerBoundUnknown.class); + + /** The canonical @{@link PolyLowerBound} annotation. */ + public final AnnotationMirror POLY = + AnnotationBuilder.fromClass(elements, PolyLowerBound.class); + + /** Predicates about method calls. */ + private final IndexMethodIdentifier imf; /** - * Sets typeDst to the immediate supertype of typeSrc, unless typeSrc is already Positive. - * Implements the following transitions: + * Create a new LowerBoundAnnotatedTypeFactory. * - *

          -     *      pos → pos
          -     *      nn → pos
          -     *      gte-1 → nn
          -     *      lbu → lbu
          -     *  
          + * @param checker the type-checker */ - private void promoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { - if (typeSrc.hasAnnotation(POS)) { - typeDst.replaceAnnotation(POS); - } else if (typeSrc.hasAnnotation(NN)) { - typeDst.replaceAnnotation(POS); - } else if (typeSrc.hasAnnotation(GTEN1)) { - typeDst.replaceAnnotation(NN); - } else { // Only unknown is left. - typeDst.replaceAnnotation(UNKNOWN); - } + public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Any annotations that are aliased to @NonNegative, @Positive, or @GTENegativeOne must also + // be aliased in the constructor of ValueAnnotatedTypeFactory to the appropriate + // @IntRangeFrom* annotation. + addAliasedTypeAnnotation("javax.annotation.Nonnegative", NN); + addAliasedTypeAnnotation(IndexFor.class, NN); + addAliasedTypeAnnotation(IndexOrLow.class, GTEN1); + addAliasedTypeAnnotation(IndexOrHigh.class, NN); + addAliasedTypeAnnotation(LengthOf.class, NN); + addAliasedTypeAnnotation(PolyIndex.class, POLY); + addAliasedTypeAnnotation(SubstringIndexFor.class, GTEN1); + + addAliasedTypeAnnotation(SignedPositive.class, NN); + addAliasedTypeAnnotation(SignednessGlb.class, NN); + + imf = new IndexMethodIdentifier(this); + + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList( + Positive.class, + NonNegative.class, + GTENegativeOne.class, + LowerBoundUnknown.class, + PolyLowerBound.class, + LowerBoundBottom.class)); } /** - * Sets typeDst to the immediate subtype of typeSrc, unless typeSrc is already - * LowerBoundUnknown. Implements the following transitions: + * Takes a value type (only interesting if it's an IntVal), and converts it to a lower bound + * type. If the new lower bound type is more specific than type, convert type to that type. * - *
          -     *       pos → nn
          -     *       nn → gte-1
          -     *       gte-1, lbu → lbu
          -     *  
          + * @param valueType the Value Checker type + * @param type the current lower bound type of the expression being evaluated */ - private void demoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { - if (typeSrc.hasAnnotation(POS)) { - typeDst.replaceAnnotation(NN); - } else if (typeSrc.hasAnnotation(NN)) { - typeDst.replaceAnnotation(GTEN1); - } else { // GTEN1 and UNKNOWN both become UNKNOWN. - typeDst.replaceAnnotation(UNKNOWN); - } + private void addLowerBoundTypeFromValueType( + AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { + AnnotationMirror anm = getLowerBoundAnnotationFromValueType(valueType); + if (!type.hasAnnotationInHierarchy(UNKNOWN)) { + if (!areSameByClass(anm, LowerBoundUnknown.class)) { + type.addAnnotation(anm); + } + return; + } + if (typeHierarchy.isSubtypeShallowEffective(anm, type)) { + type.replaceAnnotation(anm); + } } - /** Call increment and decrement helper functions. Handles cases 4, 5 and 6. */ + /** Handles cases 1, 2, and 3. */ @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror typeDst) { - AnnotatedTypeMirror typeSrc = getAnnotatedType(tree.getExpression()); - switch (tree.getKind()) { - case PREFIX_INCREMENT: - promoteType(typeSrc, typeDst); - break; - case PREFIX_DECREMENT: - demoteType(typeSrc, typeDst); - break; - case POSTFIX_INCREMENT: - case POSTFIX_DECREMENT: - // Do nothing. The CF should take care of these itself. - break; - case BITWISE_COMPLEMENT: - handleBitWiseComplement( - getSearchIndexAnnotatedTypeFactory().getAnnotatedType(tree.getExpression()), typeDst); - break; - default: - break; - } - return super.visitUnary(tree, typeDst); + public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(element, type); + if (element != null) { + AnnotatedTypeMirror valueType = + getValueAnnotatedTypeFactory().getAnnotatedType(element); + addLowerBoundTypeFromValueType(valueType, type); + } } - /** - * Bitwise complement converts between {@code @NegativeIndexFor} and {@code @IndexOrHigh}. This - * handles the lowerbound part of that type, so the result is converted to {@code @NonNegative}. - * - * @param searchIndexType the type of an expression in a bitwise complement. For instance, in - * {@code ~x}, this is the type of {@code x}. - * @param typeDst the type of the entire bitwise complement expression. It is modified by this - * method. - */ - private void handleBitWiseComplement( - AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { - if (containsSameByClass(searchIndexType.getAnnotations(), NegativeIndexFor.class)) { - typeDst.addAnnotation(NN); - } + /** Handles cases 1, 2, and 3. */ + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); + // If dataflow shouldn't be used to compute this type, then do not use the result from + // the Value Checker, because dataflow is used to compute that type. (Without this, + // "int i = 1; --i;" fails.) + if (tree != null + // Necessary to check that an ajava file isn't being parsed, because the call + // to the Value Checker's getAnnotatedType() method can fail during parsing: + // the check in GenericAnnotatedTypeFactory#addComputedTypeAnnotations only + // checks if the **current** type factory is parsing, not whether the parent + // checker's type factory is parsing. + && !ajavaTypes.isParsing() + && TreeUtils.isExpressionTree(tree) + && (getUseFlow() || tree instanceof LiteralTree)) { + AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); + addLowerBoundTypeFromValueType(valueType, type); + } + } + + /** Returns the Value Checker's annotated type factory. */ + public ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); + } + + /** Returns the SearchIndexFor Checker's annotated type factory. */ + public SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SearchIndexChecker.class); + } + + /** Returns the LessThan Checker's annotated type factory. */ + public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(LessThanChecker.class); + } + + /** Returns the type in the lower bound hierarchy that a Value Checker type corresponds to. */ + private AnnotationMirror getLowerBoundAnnotationFromValueType(AnnotatedTypeMirror valueType) { + Range possibleValues = + ValueCheckerUtils.getPossibleValues(valueType, getValueAnnotatedTypeFactory()); + // possibleValues is null if the Value Checker does not have any estimate. + if (possibleValues == null) { + // possibleValues is null if there is no IntVal annotation on the type - such as + // when there is a BottomVal annotation. In that case, give this the LBC's bottom type. + if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { + return BOTTOM; + } + return UNKNOWN; + } + // The annotation of the whole list is the min of the list. + long lvalMin = possibleValues.from; + // Turn it into an integer. + int valMin = (int) Math.max(Math.min(Integer.MAX_VALUE, lvalMin), Integer.MIN_VALUE); + return anmFromVal(valMin); + } + + /** Determine the annotation that should be associated with a literal. */ + /*package-private*/ AnnotationMirror anmFromVal(long val) { + if (val >= 1) { + return POS; + } else if (val >= 0) { + return NN; + } else if (val >= -1) { + return GTEN1; + } else { + return UNKNOWN; + } } - /** Special handling for Math.max. The return is the GLB of the arguments. Case 7. */ @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (imf.isMathMax(tree)) { - ExpressionTree left = tree.getArguments().get(0); - ExpressionTree right = tree.getArguments().get(1); - AnnotatedTypeMirror leftType = getAnnotatedType(left); - AnnotatedTypeMirror rightType = getAnnotatedType(right); - type.replaceAnnotation( - qualHierarchy.greatestLowerBoundShallow( - leftType.getAnnotationInHierarchy(POS), leftType.getUnderlyingType(), - rightType.getAnnotationInHierarchy(POS), rightType.getUnderlyingType())); - } - return super.visitMethodInvocation(tree, type); + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + new LowerBoundTreeAnnotator(this), super.createTreeAnnotator()); + } + + private class LowerBoundTreeAnnotator extends TreeAnnotator { + public LowerBoundTreeAnnotator(AnnotatedTypeFactory annotatedTypeFactory) { + super(annotatedTypeFactory); + } + + /** + * Sets typeDst to the immediate supertype of typeSrc, unless typeSrc is already Positive. + * Implements the following transitions: + * + *
          +         *      pos → pos
          +         *      nn → pos
          +         *      gte-1 → nn
          +         *      lbu → lbu
          +         *  
          + */ + private void promoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { + if (typeSrc.hasAnnotation(POS)) { + typeDst.replaceAnnotation(POS); + } else if (typeSrc.hasAnnotation(NN)) { + typeDst.replaceAnnotation(POS); + } else if (typeSrc.hasAnnotation(GTEN1)) { + typeDst.replaceAnnotation(NN); + } else { // Only unknown is left. + typeDst.replaceAnnotation(UNKNOWN); + } + } + + /** + * Sets typeDst to the immediate subtype of typeSrc, unless typeSrc is already + * LowerBoundUnknown. Implements the following transitions: + * + *
          +         *       pos → nn
          +         *       nn → gte-1
          +         *       gte-1, lbu → lbu
          +         *  
          + */ + private void demoteType(AnnotatedTypeMirror typeSrc, AnnotatedTypeMirror typeDst) { + if (typeSrc.hasAnnotation(POS)) { + typeDst.replaceAnnotation(NN); + } else if (typeSrc.hasAnnotation(NN)) { + typeDst.replaceAnnotation(GTEN1); + } else { // GTEN1 and UNKNOWN both become UNKNOWN. + typeDst.replaceAnnotation(UNKNOWN); + } + } + + /** Call increment and decrement helper functions. Handles cases 4, 5 and 6. */ + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror typeDst) { + AnnotatedTypeMirror typeSrc = getAnnotatedType(tree.getExpression()); + switch (tree.getKind()) { + case PREFIX_INCREMENT: + promoteType(typeSrc, typeDst); + break; + case PREFIX_DECREMENT: + demoteType(typeSrc, typeDst); + break; + case POSTFIX_INCREMENT: + case POSTFIX_DECREMENT: + // Do nothing. The CF should take care of these itself. + break; + case BITWISE_COMPLEMENT: + handleBitWiseComplement( + getSearchIndexAnnotatedTypeFactory() + .getAnnotatedType(tree.getExpression()), + typeDst); + break; + default: + break; + } + return super.visitUnary(tree, typeDst); + } + + /** + * Bitwise complement converts between {@code @NegativeIndexFor} and {@code @IndexOrHigh}. + * This handles the lowerbound part of that type, so the result is converted to + * {@code @NonNegative}. + * + * @param searchIndexType the type of an expression in a bitwise complement. For instance, + * in {@code ~x}, this is the type of {@code x}. + * @param typeDst the type of the entire bitwise complement expression. It is modified by + * this method. + */ + private void handleBitWiseComplement( + AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { + if (containsSameByClass(searchIndexType.getAnnotations(), NegativeIndexFor.class)) { + typeDst.addAnnotation(NN); + } + } + + /** Special handling for Math.max. The return is the GLB of the arguments. Case 7. */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (imf.isMathMax(tree)) { + ExpressionTree left = tree.getArguments().get(0); + ExpressionTree right = tree.getArguments().get(1); + AnnotatedTypeMirror leftType = getAnnotatedType(left); + AnnotatedTypeMirror rightType = getAnnotatedType(right); + type.replaceAnnotation( + qualHierarchy.greatestLowerBoundShallow( + leftType.getAnnotationInHierarchy(POS), + leftType.getUnderlyingType(), + rightType.getAnnotationInHierarchy(POS), + rightType.getUnderlyingType())); + } + return super.visitMethodInvocation(tree, type); + } + + /** + * For dealing with array length expressions. Looks for array length accesses specifically, + * then dispatches to the MinLen checker to determine the length of the relevant array. If + * it's found, use it to give the expression a type. Case 8. + */ + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + Integer minLen = getMinLenFromMemberSelectTree(tree); + if (minLen != null) { + type.replaceAnnotation(anmFromVal(minLen)); + } + return super.visitMemberSelect(tree, type); + } + + /** + * Does not dispatch to binary operator helper methods. The Lower Bound Checker handles + * binary operations via its transfer function. + */ + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.addAnnotation(UNKNOWN); + return super.visitBinary(tree, type); + } } /** - * For dealing with array length expressions. Looks for array length accesses specifically, then - * dispatches to the MinLen checker to determine the length of the relevant array. If it's - * found, use it to give the expression a type. Case 8. + * Looks up the minlen of a member select tree. Returns null if the tree doesn't represent an + * array's length field. */ - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - Integer minLen = getMinLenFromMemberSelectTree(tree); - if (minLen != null) { - type.replaceAnnotation(anmFromVal(minLen)); - } - return super.visitMemberSelect(tree, type); + /*package-private*/ @Nullable Integer getMinLenFromMemberSelectTree(MemberSelectTree tree) { + if (TreeUtils.isArrayLengthAccess(tree)) { + return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); + } + return null; } /** - * Does not dispatch to binary operator helper methods. The Lower Bound Checker handles binary - * operations via its transfer function. + * Looks up the minlen of a method invocation tree. Returns null if the tree doesn't represent + * an string length method. */ - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.addAnnotation(UNKNOWN); - return super.visitBinary(tree, type); - } - } - - /** - * Looks up the minlen of a member select tree. Returns null if the tree doesn't represent an - * array's length field. - */ - /*package-private*/ @Nullable Integer getMinLenFromMemberSelectTree(MemberSelectTree tree) { - if (TreeUtils.isArrayLengthAccess(tree)) { - return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); + /*package-private*/ @Nullable Integer getMinLenFromMethodInvocationTree( + MethodInvocationTree tree) { + if (imf.isLengthOfMethodInvocation(tree)) { + return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); + } + return null; } - return null; - } - - /** - * Looks up the minlen of a method invocation tree. Returns null if the tree doesn't represent an - * string length method. - */ - /*package-private*/ @Nullable Integer getMinLenFromMethodInvocationTree( - MethodInvocationTree tree) { - if (imf.isLengthOfMethodInvocation(tree)) { - return ValueCheckerUtils.getMinLenFromTree(tree, getValueAnnotatedTypeFactory()); - } - return null; - } - - /** - * Given a multiplication, return its type if the LBC special-cases it, or null otherwise. - * - *

          The LBC special-cases {@code Math.random() * array.length} and {@code Random.nextDouble() * - * array.length}. - * - * @param node a multiplication node that may need special casing - * @return an AnnotationMirror representing the result if the special case is valid, or null if - * not - */ - /*package-private*/ @Nullable AnnotationMirror checkForMathRandomSpecialCase( - NumericalMultiplicationNode node) { - AnnotationMirror forwardRes = - checkForMathRandomSpecialCase( - node.getLeftOperand().getTree(), node.getRightOperand().getTree()); - if (forwardRes != null) { - return forwardRes; + + /** + * Given a multiplication, return its type if the LBC special-cases it, or null otherwise. + * + *

          The LBC special-cases {@code Math.random() * array.length} and {@code Random.nextDouble() + * * array.length}. + * + * @param node a multiplication node that may need special casing + * @return an AnnotationMirror representing the result if the special case is valid, or null if + * not + */ + /*package-private*/ @Nullable AnnotationMirror checkForMathRandomSpecialCase( + NumericalMultiplicationNode node) { + AnnotationMirror forwardRes = + checkForMathRandomSpecialCase( + node.getLeftOperand().getTree(), node.getRightOperand().getTree()); + if (forwardRes != null) { + return forwardRes; + } + AnnotationMirror backwardsRes = + checkForMathRandomSpecialCase( + node.getRightOperand().getTree(), node.getLeftOperand().getTree()); + if (backwardsRes != null) { + return backwardsRes; + } + return null; } - AnnotationMirror backwardsRes = - checkForMathRandomSpecialCase( - node.getRightOperand().getTree(), node.getLeftOperand().getTree()); - if (backwardsRes != null) { - return backwardsRes; + + /** + * Return a non-null value if randTree is a call to Math.random() or Random.nextDouble(), and + * arrLenTree is someArray.length. + */ + private @Nullable AnnotationMirror checkForMathRandomSpecialCase( + Tree randTree, Tree arrLenTree) { + if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION + && TreeUtils.isArrayLengthAccess(arrLenTree)) { + MethodInvocationTree miTree = (MethodInvocationTree) randTree; + + if (imf.isMathRandom(miTree, processingEnv)) { + // This is Math.random() * array.length, which must be NonNegative + return NN; + } + + if (imf.isRandomNextDouble(miTree, processingEnv)) { + // This is Random.nextDouble() * array.length, which must be NonNegative + return NN; + } + } + return null; } - return null; - } - - /** - * Return a non-null value if randTree is a call to Math.random() or Random.nextDouble(), and - * arrLenTree is someArray.length. - */ - private @Nullable AnnotationMirror checkForMathRandomSpecialCase(Tree randTree, Tree arrLenTree) { - if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION - && TreeUtils.isArrayLengthAccess(arrLenTree)) { - MethodInvocationTree miTree = (MethodInvocationTree) randTree; - - if (imf.isMathRandom(miTree, processingEnv)) { - // This is Math.random() * array.length, which must be NonNegative - return NN; - } - - if (imf.isRandomNextDouble(miTree, processingEnv)) { - // This is Random.nextDouble() * array.length, which must be NonNegative - return NN; - } + + /** Checks if the expression is non-negative, i.e. it has Positive on NonNegative annotation. */ + public boolean isNonNegative(Tree tree) { + // TODO: consolidate with the isNonNegative method in LowerBoundTransfer + AnnotatedTypeMirror treeType = getAnnotatedType(tree); + return treeType.hasAnnotation(NonNegative.class) || treeType.hasAnnotation(Positive.class); } - return null; - } - - /** Checks if the expression is non-negative, i.e. it has Positive on NonNegative annotation. */ - public boolean isNonNegative(Tree tree) { - // TODO: consolidate with the isNonNegative method in LowerBoundTransfer - AnnotatedTypeMirror treeType = getAnnotatedType(tree); - return treeType.hasAnnotation(NonNegative.class) || treeType.hasAnnotation(Positive.class); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java index 13370f9d74f..adaa12abd55 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundChecker.java @@ -1,7 +1,5 @@ package org.checkerframework.checker.index.lowerbound; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.checker.index.inequality.LessThanChecker; import org.checkerframework.checker.index.searchindex.SearchIndexChecker; import org.checkerframework.checker.signature.qual.FullyQualifiedName; @@ -10,6 +8,9 @@ import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SuppressWarningsPrefix; +import java.util.HashSet; +import java.util.Set; + /** * A type-checker for preventing fixed-length sequences such as arrays or strings from being * accessed with values that are too low. Normally bundled as part of the Index Checker. @@ -18,51 +19,51 @@ */ @SuppressWarningsPrefix({"index", "lowerbound"}) @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) public class LowerBoundChecker extends BaseTypeChecker { - /** - * These collection classes have some subtypes whose length can change and some subtypes whose - * length cannot change. Lower bound checker warnings are skipped at uses of them. - */ - private final HashSet collectionBaseTypeNames; + /** + * These collection classes have some subtypes whose length can change and some subtypes whose + * length cannot change. Lower bound checker warnings are skipped at uses of them. + */ + private final HashSet collectionBaseTypeNames; - /** - * A type-checker for preventing fixed-length sequences such as arrays or strings from being - * accessed with values that are too low. Normally bundled as part of the Index Checker. - */ - public LowerBoundChecker() { - Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; - collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); - for (Class collectionBaseClass : collectionBaseClasses) { - collectionBaseTypeNames.add(collectionBaseClass.getName()); + /** + * A type-checker for preventing fixed-length sequences such as arrays or strings from being + * accessed with values that are too low. Normally bundled as part of the Index Checker. + */ + public LowerBoundChecker() { + Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; + collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); + for (Class collectionBaseClass : collectionBaseClasses) { + collectionBaseTypeNames.add(collectionBaseClass.getName()); + } } - } - @Override - public boolean shouldSkipUses(@FullyQualifiedName String typeName) { - if (collectionBaseTypeNames.contains(typeName)) { - return true; + @Override + public boolean shouldSkipUses(@FullyQualifiedName String typeName) { + if (collectionBaseTypeNames.contains(typeName)) { + return true; + } + return super.shouldSkipUses(typeName); } - return super.shouldSkipUses(typeName); - } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - checkers.add(LessThanChecker.class); - checkers.add(SearchIndexChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + checkers.add(LessThanChecker.class); + checkers.add(SearchIndexChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java index c4e626939b7..fdd566a4816 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundTransfer.java @@ -5,10 +5,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.checker.index.IndexRefinementInfo; import org.checkerframework.checker.index.qual.GTENegativeOne; @@ -40,6 +37,12 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; + /** * Implements dataflow refinement rules based on tests: <, >, ==, and their derivatives. * @@ -160,765 +163,777 @@ */ public class LowerBoundTransfer extends IndexAbstractTransfer { - /** The canonical {@link GTENegativeOne} annotation. */ - public final AnnotationMirror GTEN1; - - /** The canonical {@link NonNegative} annotation. */ - public final AnnotationMirror NN; - - /** The canonical {@link Positive} annotation. */ - public final AnnotationMirror POS; - - /** The canonical {@link LowerBoundUnknown} annotation. */ - public final AnnotationMirror UNKNOWN; - - /** The annotated type factory. */ - private final LowerBoundAnnotatedTypeFactory atypeFactory; - - /** - * Create a new LowerBoundTransfer. - * - * @param analysis the CFAnalysis - */ - public LowerBoundTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (LowerBoundAnnotatedTypeFactory) analysis.getTypeFactory(); - // Initialize qualifiers. - GTEN1 = atypeFactory.GTEN1; - NN = atypeFactory.NN; - POS = atypeFactory.POS; - UNKNOWN = atypeFactory.UNKNOWN; - } - - /** - * Refines GTEN1 to NN if it is not equal to -1, and NN to Pos if it is not equal to 0. Implements - * case 7. - * - * @param mLiteral a potential literal - * @param otherNode the node on the other side of the ==/!= - * @param otherAnno the annotation of the other side of the ==/!= - */ - private void notEqualToValue( - Node mLiteral, Node otherNode, AnnotationMirror otherAnno, CFStore store) { - - Long integerLiteral = - ValueCheckerUtils.getExactValue( - mLiteral.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - - if (integerLiteral == null) { - return; - } - long intLiteral = integerLiteral.longValue(); - - if (intLiteral == 0) { - if (atypeFactory.areSameByClass(otherAnno, NonNegative.class)) { - List internals = splitAssignments(otherNode); - for (Node internal : internals) { - JavaExpression je = JavaExpression.fromNode(internal); - store.insertValue(je, POS); - } - } - } else if (intLiteral == -1) { - if (atypeFactory.areSameByClass(otherAnno, GTENegativeOne.class)) { - List internals = splitAssignments(otherNode); - for (Node internal : internals) { - JavaExpression je = JavaExpression.fromNode(internal); - store.insertValue(je, NN); - } - } - } - } - - /** - * Implements the transfer rules for both equal nodes and not-equals nodes (i.e. cases 5, 6, 32). - */ - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult result, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - result = - super.strengthenAnnotationOfEqualTo( - result, firstNode, secondNode, firstValue, secondValue, notEqualTo); - - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, secondNode, firstNode); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; - } - - // There is also special processing to look for literals on one side of the equals and a - // GTEN1 or NN on the other, so that those types can be promoted in the branch where their - // values are not equal to certain literals. - CFStore notEqualsStore = notEqualTo ? rfi.thenStore : rfi.elseStore; - notEqualToValue(rfi.left, rfi.right, rfi.rightAnno, notEqualsStore); - notEqualToValue(rfi.right, rfi.left, rfi.leftAnno, notEqualsStore); - - notEqualsLessThan(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, notEqualsStore); - notEqualsLessThan(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, notEqualsStore); - - return rfi.newResult; - } - - /** Implements case 32. */ - private void notEqualsLessThan( - Node leftNode, - AnnotationMirror leftAnno, - Node otherNode, - AnnotationMirror otherAnno, - CFStore store) { - if (!isNonNegative(leftAnno) || !isNonNegative(otherAnno)) { - return; - } - JavaExpression otherJe = JavaExpression.fromNode(otherNode); - if (atypeFactory - .getLessThanAnnotatedTypeFactory() - .isLessThanOrEqual(leftNode.getTree(), otherJe.toString())) { - store.insertValue(otherJe, POS); - } - } - - /** - * The implementation of the algorithm for refining a > test. Changes the type of left (the - * greater one) to one closer to bottom than the type of right. Can't call the promote function - * from the ATF directly because a new expression isn't introduced here - the modifications have - * to be made to an existing one. - * - *

          This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described in - * the Javadoc of this class. - */ - @Override - protected void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - - if (rightAnno == null || leftAnno == null) { - return; - } - - JavaExpression leftJe = JavaExpression.fromNode(left); - - if (AnnotationUtils.areSame(rightAnno, GTEN1)) { - store.insertValue(leftJe, NN); - return; - } - if (AnnotationUtils.areSame(rightAnno, NN)) { - store.insertValue(leftJe, POS); - return; - } - if (AnnotationUtils.areSame(rightAnno, POS)) { - store.insertValue(leftJe, POS); - return; - } - } - - /** - * Refines left to exactly the level of right, since in the worst case they're equal. Modifies an - * existing type in the store, but has to be careful not to overwrite a more precise existing - * type. - * - *

          This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described in - * this class's Javadoc. - */ - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - - if (rightAnno == null || leftAnno == null) { - return; - } - - JavaExpression leftJe = JavaExpression.fromNode(left); - - AnnotationMirror newLBType = - atypeFactory - .getQualifierHierarchy() - .greatestLowerBoundShallow(rightAnno, right.getType(), leftAnno, left.getType()); - - store.insertValue(leftJe, newLBType); - } - - /** - * Returns an annotation mirror representing the result of subtracting one from {@code oldAnm}. - */ - private AnnotationMirror anmAfterSubtractingOne(AnnotationMirror oldAnm) { - if (isPositive(oldAnm)) { - return NN; - } else if (isNonNegative(oldAnm)) { - return GTEN1; - } else { - return UNKNOWN; - } - } - - /** Returns an annotation mirror representing the result of adding one to {@code oldAnm}. */ - private AnnotationMirror anmAfterAddingOne(AnnotationMirror oldAnm) { - if (isNonNegative(oldAnm)) { - return POS; - } else if (isGTEN1(oldAnm)) { - return NN; - } else { - return UNKNOWN; - } - } - - /** - * Helper method for getAnnotationForPlus. Handles addition of constants (cases 8 and 9). - * - * @param val the integer value of the constant - * @param nonLiteralType the type of the side of the expression that isn't a constant - */ - private AnnotationMirror getAnnotationForLiteralPlus(int val, AnnotationMirror nonLiteralType) { - if (val == -2) { - if (isPositive(nonLiteralType)) { - return GTEN1; - } - } else if (val == -1) { - return anmAfterSubtractingOne(nonLiteralType); - } else if (val == 0) { - return nonLiteralType; - } else if (val == 1) { - return anmAfterAddingOne(nonLiteralType); - } else if (val >= 2) { - if (isGTEN1(nonLiteralType)) { - // 2 + a positive, or a non-negative, or a non-negative-1 is a positive - return POS; - } - } - return UNKNOWN; - } - - /** - * getAnnotationForPlus handles the following cases (cases 10-12 above): - * - *

          -   *      8. lit -2 + pos → gte-1
          -   *      lit -1 + * → call demote
          -   *      lit 0 + * → *
          -   *      lit 1 + * → call promote
          -   *      9. lit ≥ 2 + {gte-1, nn, or pos} → pos
          -   *      let all other lits, including sets, fall through:
          -   *      10. pos + pos → pos
          -   *      11. nn + * → *
          -   *      12. pos + gte-1 → nn
          -   *      * + * → lbu
          -   *  
          - */ - private AnnotationMirror getAnnotationForPlus( - BinaryOperationNode binaryOpNode, TransferInput p) { - - Node leftExprNode = binaryOpNode.getLeftOperand(); - Node rightExprNode = binaryOpNode.getRightOperand(); - - AnnotationMirror leftAnno = getLowerBoundAnnotation(leftExprNode, p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - rightExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return getAnnotationForLiteralPlus(valRight.intValue(), leftAnno); - } - - AnnotationMirror rightAnno = getLowerBoundAnnotation(rightExprNode, p); - - // Check if the left side's value is known at compile time. - Long valLeft = - ValueCheckerUtils.getExactValue( - leftExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valLeft != null) { - return getAnnotationForLiteralPlus(valLeft.intValue(), rightAnno); - } - - /* This section is handling the generic cases: - * pos + pos -> pos - * nn + * -> * - * pos + gte-1 -> nn + /** The canonical {@link GTENegativeOne} annotation. */ + public final AnnotationMirror GTEN1; + + /** The canonical {@link NonNegative} annotation. */ + public final AnnotationMirror NN; + + /** The canonical {@link Positive} annotation. */ + public final AnnotationMirror POS; + + /** The canonical {@link LowerBoundUnknown} annotation. */ + public final AnnotationMirror UNKNOWN; + + /** The annotated type factory. */ + private final LowerBoundAnnotatedTypeFactory atypeFactory; + + /** + * Create a new LowerBoundTransfer. + * + * @param analysis the CFAnalysis + */ + public LowerBoundTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (LowerBoundAnnotatedTypeFactory) analysis.getTypeFactory(); + // Initialize qualifiers. + GTEN1 = atypeFactory.GTEN1; + NN = atypeFactory.NN; + POS = atypeFactory.POS; + UNKNOWN = atypeFactory.UNKNOWN; + } + + /** + * Refines GTEN1 to NN if it is not equal to -1, and NN to Pos if it is not equal to 0. + * Implements case 7. + * + * @param mLiteral a potential literal + * @param otherNode the node on the other side of the ==/!= + * @param otherAnno the annotation of the other side of the ==/!= + */ + private void notEqualToValue( + Node mLiteral, Node otherNode, AnnotationMirror otherAnno, CFStore store) { + + Long integerLiteral = + ValueCheckerUtils.getExactValue( + mLiteral.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + + if (integerLiteral == null) { + return; + } + long intLiteral = integerLiteral.longValue(); + + if (intLiteral == 0) { + if (atypeFactory.areSameByClass(otherAnno, NonNegative.class)) { + List internals = splitAssignments(otherNode); + for (Node internal : internals) { + JavaExpression je = JavaExpression.fromNode(internal); + store.insertValue(je, POS); + } + } + } else if (intLiteral == -1) { + if (atypeFactory.areSameByClass(otherAnno, GTENegativeOne.class)) { + List internals = splitAssignments(otherNode); + for (Node internal : internals) { + JavaExpression je = JavaExpression.fromNode(internal); + store.insertValue(je, NN); + } + } + } + } + + /** + * Implements the transfer rules for both equal nodes and not-equals nodes (i.e. cases 5, 6, + * 32). + */ + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult result, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + result = + super.strengthenAnnotationOfEqualTo( + result, firstNode, secondNode, firstValue, secondValue, notEqualTo); + + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, secondNode, firstNode); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; + } + + // There is also special processing to look for literals on one side of the equals and a + // GTEN1 or NN on the other, so that those types can be promoted in the branch where their + // values are not equal to certain literals. + CFStore notEqualsStore = notEqualTo ? rfi.thenStore : rfi.elseStore; + notEqualToValue(rfi.left, rfi.right, rfi.rightAnno, notEqualsStore); + notEqualToValue(rfi.right, rfi.left, rfi.leftAnno, notEqualsStore); + + notEqualsLessThan(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, notEqualsStore); + notEqualsLessThan(rfi.right, rfi.rightAnno, rfi.left, rfi.leftAnno, notEqualsStore); + + return rfi.newResult; + } + + /** Implements case 32. */ + private void notEqualsLessThan( + Node leftNode, + AnnotationMirror leftAnno, + Node otherNode, + AnnotationMirror otherAnno, + CFStore store) { + if (!isNonNegative(leftAnno) || !isNonNegative(otherAnno)) { + return; + } + JavaExpression otherJe = JavaExpression.fromNode(otherNode); + if (atypeFactory + .getLessThanAnnotatedTypeFactory() + .isLessThanOrEqual(leftNode.getTree(), otherJe.toString())) { + store.insertValue(otherJe, POS); + } + } + + /** + * The implementation of the algorithm for refining a > test. Changes the type of left (the + * greater one) to one closer to bottom than the type of right. Can't call the promote function + * from the ATF directly because a new expression isn't introduced here - the modifications have + * to be made to an existing one. + * + *

          This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described + * in the Javadoc of this class. + */ + @Override + protected void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + + if (rightAnno == null || leftAnno == null) { + return; + } + + JavaExpression leftJe = JavaExpression.fromNode(left); + + if (AnnotationUtils.areSame(rightAnno, GTEN1)) { + store.insertValue(leftJe, NN); + return; + } + if (AnnotationUtils.areSame(rightAnno, NN)) { + store.insertValue(leftJe, POS); + return; + } + if (AnnotationUtils.areSame(rightAnno, POS)) { + store.insertValue(leftJe, POS); + return; + } + } + + /** + * Refines left to exactly the level of right, since in the worst case they're equal. Modifies + * an existing type in the store, but has to be careful not to overwrite a more precise existing + * type. + * + *

          This implements parts of cases 1, 2, 3, and 4 using the decomposition strategy described + * in this class's Javadoc. + */ + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + + if (rightAnno == null || leftAnno == null) { + return; + } + + JavaExpression leftJe = JavaExpression.fromNode(left); + + AnnotationMirror newLBType = + atypeFactory + .getQualifierHierarchy() + .greatestLowerBoundShallow( + rightAnno, right.getType(), leftAnno, left.getType()); + + store.insertValue(leftJe, newLBType); + } + + /** + * Returns an annotation mirror representing the result of subtracting one from {@code oldAnm}. */ - if (atypeFactory.areSameByClass(leftAnno, Positive.class) - && atypeFactory.areSameByClass(rightAnno, Positive.class)) { - return POS; - } - - if (atypeFactory.areSameByClass(leftAnno, NonNegative.class)) { - return rightAnno; - } - - if (atypeFactory.areSameByClass(rightAnno, NonNegative.class)) { - return leftAnno; - } - - if ((isPositive(leftAnno) && isGTEN1(rightAnno)) - || (isGTEN1(leftAnno) && isPositive(rightAnno))) { - return NN; - } - return UNKNOWN; - } - - /** - * getAnnotationForMinus handles the following cases: - * - *

          -   *      * - lit → call plus(*, -1 * the value of the lit)
          -   *      * - * → lbu
          -   *      13. if the LessThan type checker can establish that the left side of the expression is > the right side,
          -   *      returns POS.
          -   *      14. if the LessThan type checker can establish that the left side of the expression is ≥ the right side,
          -   *      returns NN.
          -   *      15. special handling for when the left side is the length of an array or String that's stored as a field,
          -   *      and the right side is a compile time constant. Do we need this?
          -   *  
          - */ - private AnnotationMirror getAnnotationForMinus( - BinaryOperationNode minusNode, TransferInput p) { - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - minusNode.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - AnnotationMirror leftAnno = getLowerBoundAnnotation(minusNode.getLeftOperand(), p); - // Instead of a separate method for subtraction, add the negative of a constant. - AnnotationMirror result = getAnnotationForLiteralPlus(-1 * valRight.intValue(), leftAnno); - - Tree leftExpr = minusNode.getLeftOperand().getTree(); - Integer minLen = null; - // Check if the left side is a field access of an array's length, or invocation of - // String.length. If so, try to look up the MinLen of the array, and potentially keep - // this either NN or POS instead of GTEN1 or LBU. - if (leftExpr.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree mstree = (MemberSelectTree) leftExpr; - minLen = atypeFactory.getMinLenFromMemberSelectTree(mstree); - } else if (leftExpr.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree mitree = (MethodInvocationTree) leftExpr; - minLen = atypeFactory.getMinLenFromMethodInvocationTree(mitree); - } - - if (minLen != null) { - result = atypeFactory.anmFromVal(minLen - valRight); - } - return result; - } - - OffsetEquation leftExpression = - OffsetEquation.createOffsetFromNode(minusNode.getLeftOperand(), atypeFactory, '+'); - if (leftExpression != null) { - if (atypeFactory - .getLessThanAnnotatedTypeFactory() - .isLessThan(minusNode.getRightOperand().getTree(), leftExpression.toString())) { - return POS; - } - - if (atypeFactory - .getLessThanAnnotatedTypeFactory() - .isLessThanOrEqual(minusNode.getRightOperand().getTree(), leftExpression.toString())) { - return NN; - } - } - - // The checker can't reason about arbitrary (i.e. non-literal) - // things that are being subtracted, so it gives up. - return UNKNOWN; - } - - /** - * Helper function for getAnnotationForMultiply. Handles compile-time known constants. - * - * @param val the integer value of the constant - * @param nonLiteralType the type of the side of the expression that isn't a constant - */ - private AnnotationMirror getAnnotationForLiteralMultiply( - int val, AnnotationMirror nonLiteralType) { - if (val == 0) { - return NN; - } else if (val == 1) { - return nonLiteralType; - } else if (val > 1) { - if (isNonNegative(nonLiteralType)) { - return nonLiteralType; - } - } - return UNKNOWN; - } - - /** - * getAnnotationForMultiply handles the following cases: - * - *
          -   *        * * lit 0 → nn (=0)
          -   *        16. * * lit 1 → *
          -   *        17. pos * pos → pos
          -   *        18. pos * nn → nn
          -   *        19. nn * nn → nn
          -   *        * * * → lbu
          -   *  
          - * - * Also handles a special case involving Math.random (case 20). - */ - private AnnotationMirror getAnnotationForMultiply( - NumericalMultiplicationNode node, TransferInput p) { - - // Special handling for multiplying an array length by a Math.random(). - AnnotationMirror randomSpecialCaseResult = atypeFactory.checkForMathRandomSpecialCase(node); - if (randomSpecialCaseResult != null) { - return randomSpecialCaseResult; - } - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - node.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return getAnnotationForLiteralMultiply(valRight.intValue(), leftAnno); - } - - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - // Check if the left side's value is known at compile time. - Long valLeft = - ValueCheckerUtils.getExactValue( - node.getLeftOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valLeft != null) { - return getAnnotationForLiteralMultiply(valLeft.intValue(), rightAnno); - } - - /* This section handles generic annotations: - * pos * pos -> pos - * nn * pos -> nn (elided, since positives are also non-negative) - * nn * nn -> nn + private AnnotationMirror anmAfterSubtractingOne(AnnotationMirror oldAnm) { + if (isPositive(oldAnm)) { + return NN; + } else if (isNonNegative(oldAnm)) { + return GTEN1; + } else { + return UNKNOWN; + } + } + + /** Returns an annotation mirror representing the result of adding one to {@code oldAnm}. */ + private AnnotationMirror anmAfterAddingOne(AnnotationMirror oldAnm) { + if (isNonNegative(oldAnm)) { + return POS; + } else if (isGTEN1(oldAnm)) { + return NN; + } else { + return UNKNOWN; + } + } + + /** + * Helper method for getAnnotationForPlus. Handles addition of constants (cases 8 and 9). + * + * @param val the integer value of the constant + * @param nonLiteralType the type of the side of the expression that isn't a constant */ - if (isPositive(leftAnno) && isPositive(rightAnno)) { - return POS; - } - if (isNonNegative(leftAnno) && isNonNegative(rightAnno)) { - return NN; - } - return UNKNOWN; - } - - /** When the value on the left is known at compile time. */ - private AnnotationMirror addAnnotationForLiteralDivideLeft(int val, AnnotationMirror rightAnno) { - if (val == 0) { - return NN; - } else if (val == 1) { - if (isNonNegative(rightAnno)) { - return NN; - } else { - // (1 / x) can't be outside the range [-1, 1] when x is an integer. - return GTEN1; - } - } - return UNKNOWN; - } - - /** When the value on the right is known at compile time. */ - private AnnotationMirror addAnnotationForLiteralDivideRight(int val, AnnotationMirror leftAnno) { - if (val == 0) { - // Reaching this indicates a divide by zero error. If the value is zero, then this is - // division by zero. Division by zero is treated as bottom so that users aren't warned - // about dead code that's dividing by zero. This code assumes that non-dead code won't - // include literal divide by zeros... - return atypeFactory.BOTTOM; - } else if (val == 1) { - return leftAnno; - } else if (val >= 2) { - if (isNonNegative(leftAnno)) { - return NN; - } - } - return UNKNOWN; - } - - /** - * getAnnotationForDivide handles the following cases (21-26). - * - *
          -   *      lit 0 / * → nn (=0)
          -   *      * / lit 0 → pos
          -   *      lit 1 / {pos, nn} → nn
          -   *      lit 1 / * → gten1
          -   *      * / lit 1 → *
          -   *      {pos, nn} / lit >1 → nn
          -   *      pos / {pos, nn} → nn (can round to zero)
          -   *      * / {pos, nn} → *
          -   *      * / * → lbu
          -   *  
          - */ - private AnnotationMirror getAnnotationForDivide( - IntegerDivisionNode node, TransferInput p) { - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - node.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return addAnnotationForLiteralDivideRight(valRight.intValue(), leftAnno); - } - - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - - // Check if the left side's value is known at compile time. - Long valLeft = - ValueCheckerUtils.getExactValue( - node.getLeftOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valLeft != null) { - return addAnnotationForLiteralDivideLeft(valLeft.intValue(), leftAnno); - } - - /* This section handles generic annotations: - * pos / {pos, nn} -> nn (can round to zero) - * * / {pos, nn} -> * + private AnnotationMirror getAnnotationForLiteralPlus(int val, AnnotationMirror nonLiteralType) { + if (val == -2) { + if (isPositive(nonLiteralType)) { + return GTEN1; + } + } else if (val == -1) { + return anmAfterSubtractingOne(nonLiteralType); + } else if (val == 0) { + return nonLiteralType; + } else if (val == 1) { + return anmAfterAddingOne(nonLiteralType); + } else if (val >= 2) { + if (isGTEN1(nonLiteralType)) { + // 2 + a positive, or a non-negative, or a non-negative-1 is a positive + return POS; + } + } + return UNKNOWN; + } + + /** + * getAnnotationForPlus handles the following cases (cases 10-12 above): + * + *
          +     *      8. lit -2 + pos → gte-1
          +     *      lit -1 + * → call demote
          +     *      lit 0 + * → *
          +     *      lit 1 + * → call promote
          +     *      9. lit ≥ 2 + {gte-1, nn, or pos} → pos
          +     *      let all other lits, including sets, fall through:
          +     *      10. pos + pos → pos
          +     *      11. nn + * → *
          +     *      12. pos + gte-1 → nn
          +     *      * + * → lbu
          +     *  
          */ - if (isPositive(leftAnno) && isNonNegative(rightAnno)) { - return NN; - } - if (isNonNegative(rightAnno)) { - return leftAnno; - } - // Everything else is unknown. - return UNKNOWN; - } - - /** A remainder with 1 or -1 as the divisor always results in zero. */ - private AnnotationMirror addAnnotationForLiteralRemainder(int val) { - if (val == 1 || val == -1) { - return NN; - } - return UNKNOWN; - } - - /** Adds a default NonNegative annotation to every character. Implements case 33. */ - @Override - protected void addInformationFromPreconditions( - CFStore info, - AnnotatedTypeFactory factory, - UnderlyingAST.CFGMethod method, - MethodTree methodTree, - ExecutableElement methodElement) { - super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); - - List paramTrees = methodTree.getParameters(); - - for (VariableTree variableTree : paramTrees) { - if (TreeUtils.typeOf(variableTree).getKind() == TypeKind.CHAR) { - JavaExpression je = JavaExpression.fromVariableTree(variableTree); - info.insertValuePermitNondeterministic(je, atypeFactory.NN); - } - } - } - - /** - * getAnnotationForRemainder handles these cases: - * - *
          -   *      27. * % 1/-1 → nn
          -   *      28. pos/nn % * → nn
          -   *      29. gten1 % * → gten1
          -   *      * % * → lbu
          -   * 
          - */ - public AnnotationMirror getAnnotationForRemainder( - IntegerRemainderNode node, TransferInput p) { - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - - // Check if the right side's value is known at compile time. - Long valRight = - ValueCheckerUtils.getExactValue( - node.getRightOperand().getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (valRight != null) { - return addAnnotationForLiteralRemainder(valRight.intValue()); - } - - /* This section handles generic annotations: - pos/nn % * -> nn - gten1 % * -> gten1 - */ - if (isNonNegative(leftAnno)) { - return NN; - } - if (isGTEN1(leftAnno)) { - return GTEN1; - } - - // Everything else is unknown. - return UNKNOWN; - } - - /** Handles shifts (case 30). * >> NonNegative → NonNegative */ - private AnnotationMirror getAnnotationForRightShift( - BinaryOperationNode node, TransferInput p) { - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - - if (isNonNegative(leftAnno)) { - if (isNonNegative(rightAnno)) { - return NN; - } - } - return UNKNOWN; - } - - /** - * Handles masking (case 31). Particularly, handles the following cases: * & NonNegative - * → NonNegative - */ - private AnnotationMirror getAnnotationForAnd( - BitwiseAndNode node, TransferInput p) { - - AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); - if (isNonNegative(rightAnno)) { - return NN; - } - - AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); - if (isNonNegative(leftAnno)) { - return NN; - } - return UNKNOWN; - } - - /** - * Returns true if the argument is the @Positive type annotation. - * - * @param anm the annotation to test - * @return true if the argument is the @Positive type annotation - */ - private boolean isPositive(AnnotationMirror anm) { - return atypeFactory.areSameByClass(anm, Positive.class); - } - - /** - * Returns true if the argument is the @NonNegative type annotation (or a stronger one). - * - * @param anm the annotation to test - * @return true if the argument is the @NonNegative type annotation - */ - private boolean isNonNegative(AnnotationMirror anm) { - return atypeFactory.areSameByClass(anm, NonNegative.class) || isPositive(anm); - } - - /** - * Returns true if the argument is the @GTENegativeOne type annotation (or a stronger one). - * - * @param anm the annotation to test - * @return true if the argument is the @GTENegativeOne type annotation - */ - private boolean isGTEN1(AnnotationMirror anm) { - return atypeFactory.areSameByClass(anm, GTENegativeOne.class) || isNonNegative(anm); - } - - private AnnotationMirror getLowerBoundAnnotation( - Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - if (value == null) { - throw new BugInCF("value==null for getLowerBoundAnnotation(%s, %s)%n", subNode, p); - } - return getLowerBoundAnnotation(value); - } - - /** - * Returns the lower bound annotation for the given value. - * - * @param cfValue a value - * @return the lower bound annotation for the given value - */ - private AnnotationMirror getLowerBoundAnnotation(CFValue cfValue) { - return atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(cfValue.getAnnotations(), atypeFactory.UNKNOWN); - } - - @Override - public TransferResult visitNumericalAddition( - NumericalAdditionNode n, TransferInput p) { - TransferResult result = super.visitNumericalAddition(n, p); - AnnotationMirror newAnno = getAnnotationForPlus(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput p) { - TransferResult result = super.visitNumericalSubtraction(n, p); - AnnotationMirror newAnno = getAnnotationForMinus(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitNumericalMultiplication( - NumericalMultiplicationNode n, TransferInput p) { - TransferResult result = super.visitNumericalMultiplication(n, p); - AnnotationMirror newAnno = getAnnotationForMultiply(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitIntegerDivision( - IntegerDivisionNode n, TransferInput p) { - TransferResult result = super.visitIntegerDivision(n, p); - AnnotationMirror newAnno = getAnnotationForDivide(n, p); - return createNewResult(result, newAnno); - } - - @Override - public TransferResult visitIntegerRemainder( - IntegerRemainderNode n, TransferInput p) { - TransferResult transferResult = super.visitIntegerRemainder(n, p); - AnnotationMirror resultAnno = getAnnotationForRemainder(n, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitSignedRightShift( - SignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitSignedRightShift(n, p); - AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitUnsignedRightShift( - UnsignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitUnsignedRightShift(n, p); - AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseAnd( - BitwiseAndNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseAnd(n, p); - AnnotationMirror resultAnno = getAnnotationForAnd(n, p); - return createNewResult(transferResult, resultAnno); - } - - /** - * Create a new transfer result based on the original result and the new annotation. - * - * @param result the original result - * @param resultAnno the new annotation - * @return the new transfer result - */ - private TransferResult createNewResult( - TransferResult result, AnnotationMirror resultAnno) { - CFValue newResultValue = - analysis.createSingleAnnotationValue( - resultAnno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } + private AnnotationMirror getAnnotationForPlus( + BinaryOperationNode binaryOpNode, TransferInput p) { + + Node leftExprNode = binaryOpNode.getLeftOperand(); + Node rightExprNode = binaryOpNode.getRightOperand(); + + AnnotationMirror leftAnno = getLowerBoundAnnotation(leftExprNode, p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + rightExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return getAnnotationForLiteralPlus(valRight.intValue(), leftAnno); + } + + AnnotationMirror rightAnno = getLowerBoundAnnotation(rightExprNode, p); + + // Check if the left side's value is known at compile time. + Long valLeft = + ValueCheckerUtils.getExactValue( + leftExprNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (valLeft != null) { + return getAnnotationForLiteralPlus(valLeft.intValue(), rightAnno); + } + + /* This section is handling the generic cases: + * pos + pos -> pos + * nn + * -> * + * pos + gte-1 -> nn + */ + if (atypeFactory.areSameByClass(leftAnno, Positive.class) + && atypeFactory.areSameByClass(rightAnno, Positive.class)) { + return POS; + } + + if (atypeFactory.areSameByClass(leftAnno, NonNegative.class)) { + return rightAnno; + } + + if (atypeFactory.areSameByClass(rightAnno, NonNegative.class)) { + return leftAnno; + } + + if ((isPositive(leftAnno) && isGTEN1(rightAnno)) + || (isGTEN1(leftAnno) && isPositive(rightAnno))) { + return NN; + } + return UNKNOWN; + } + + /** + * getAnnotationForMinus handles the following cases: + * + *
          +     *      * - lit → call plus(*, -1 * the value of the lit)
          +     *      * - * → lbu
          +     *      13. if the LessThan type checker can establish that the left side of the expression is > the right side,
          +     *      returns POS.
          +     *      14. if the LessThan type checker can establish that the left side of the expression is ≥ the right side,
          +     *      returns NN.
          +     *      15. special handling for when the left side is the length of an array or String that's stored as a field,
          +     *      and the right side is a compile time constant. Do we need this?
          +     *  
          + */ + private AnnotationMirror getAnnotationForMinus( + BinaryOperationNode minusNode, TransferInput p) { + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + minusNode.getRightOperand().getTree(), + atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + AnnotationMirror leftAnno = getLowerBoundAnnotation(minusNode.getLeftOperand(), p); + // Instead of a separate method for subtraction, add the negative of a constant. + AnnotationMirror result = + getAnnotationForLiteralPlus(-1 * valRight.intValue(), leftAnno); + + Tree leftExpr = minusNode.getLeftOperand().getTree(); + Integer minLen = null; + // Check if the left side is a field access of an array's length, or invocation of + // String.length. If so, try to look up the MinLen of the array, and potentially keep + // this either NN or POS instead of GTEN1 or LBU. + if (leftExpr.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree mstree = (MemberSelectTree) leftExpr; + minLen = atypeFactory.getMinLenFromMemberSelectTree(mstree); + } else if (leftExpr.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree mitree = (MethodInvocationTree) leftExpr; + minLen = atypeFactory.getMinLenFromMethodInvocationTree(mitree); + } + + if (minLen != null) { + result = atypeFactory.anmFromVal(minLen - valRight); + } + return result; + } + + OffsetEquation leftExpression = + OffsetEquation.createOffsetFromNode(minusNode.getLeftOperand(), atypeFactory, '+'); + if (leftExpression != null) { + if (atypeFactory + .getLessThanAnnotatedTypeFactory() + .isLessThan(minusNode.getRightOperand().getTree(), leftExpression.toString())) { + return POS; + } + + if (atypeFactory + .getLessThanAnnotatedTypeFactory() + .isLessThanOrEqual( + minusNode.getRightOperand().getTree(), leftExpression.toString())) { + return NN; + } + } + + // The checker can't reason about arbitrary (i.e. non-literal) + // things that are being subtracted, so it gives up. + return UNKNOWN; + } + + /** + * Helper function for getAnnotationForMultiply. Handles compile-time known constants. + * + * @param val the integer value of the constant + * @param nonLiteralType the type of the side of the expression that isn't a constant + */ + private AnnotationMirror getAnnotationForLiteralMultiply( + int val, AnnotationMirror nonLiteralType) { + if (val == 0) { + return NN; + } else if (val == 1) { + return nonLiteralType; + } else if (val > 1) { + if (isNonNegative(nonLiteralType)) { + return nonLiteralType; + } + } + return UNKNOWN; + } + + /** + * getAnnotationForMultiply handles the following cases: + * + *
          +     *        * * lit 0 → nn (=0)
          +     *        16. * * lit 1 → *
          +     *        17. pos * pos → pos
          +     *        18. pos * nn → nn
          +     *        19. nn * nn → nn
          +     *        * * * → lbu
          +     *  
          + * + * Also handles a special case involving Math.random (case 20). + */ + private AnnotationMirror getAnnotationForMultiply( + NumericalMultiplicationNode node, TransferInput p) { + + // Special handling for multiplying an array length by a Math.random(). + AnnotationMirror randomSpecialCaseResult = atypeFactory.checkForMathRandomSpecialCase(node); + if (randomSpecialCaseResult != null) { + return randomSpecialCaseResult; + } + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + node.getRightOperand().getTree(), + atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return getAnnotationForLiteralMultiply(valRight.intValue(), leftAnno); + } + + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + // Check if the left side's value is known at compile time. + Long valLeft = + ValueCheckerUtils.getExactValue( + node.getLeftOperand().getTree(), + atypeFactory.getValueAnnotatedTypeFactory()); + if (valLeft != null) { + return getAnnotationForLiteralMultiply(valLeft.intValue(), rightAnno); + } + + /* This section handles generic annotations: + * pos * pos -> pos + * nn * pos -> nn (elided, since positives are also non-negative) + * nn * nn -> nn + */ + if (isPositive(leftAnno) && isPositive(rightAnno)) { + return POS; + } + if (isNonNegative(leftAnno) && isNonNegative(rightAnno)) { + return NN; + } + return UNKNOWN; + } + + /** When the value on the left is known at compile time. */ + private AnnotationMirror addAnnotationForLiteralDivideLeft( + int val, AnnotationMirror rightAnno) { + if (val == 0) { + return NN; + } else if (val == 1) { + if (isNonNegative(rightAnno)) { + return NN; + } else { + // (1 / x) can't be outside the range [-1, 1] when x is an integer. + return GTEN1; + } + } + return UNKNOWN; + } + + /** When the value on the right is known at compile time. */ + private AnnotationMirror addAnnotationForLiteralDivideRight( + int val, AnnotationMirror leftAnno) { + if (val == 0) { + // Reaching this indicates a divide by zero error. If the value is zero, then this is + // division by zero. Division by zero is treated as bottom so that users aren't warned + // about dead code that's dividing by zero. This code assumes that non-dead code won't + // include literal divide by zeros... + return atypeFactory.BOTTOM; + } else if (val == 1) { + return leftAnno; + } else if (val >= 2) { + if (isNonNegative(leftAnno)) { + return NN; + } + } + return UNKNOWN; + } + + /** + * getAnnotationForDivide handles the following cases (21-26). + * + *
          +     *      lit 0 / * → nn (=0)
          +     *      * / lit 0 → pos
          +     *      lit 1 / {pos, nn} → nn
          +     *      lit 1 / * → gten1
          +     *      * / lit 1 → *
          +     *      {pos, nn} / lit >1 → nn
          +     *      pos / {pos, nn} → nn (can round to zero)
          +     *      * / {pos, nn} → *
          +     *      * / * → lbu
          +     *  
          + */ + private AnnotationMirror getAnnotationForDivide( + IntegerDivisionNode node, TransferInput p) { + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + node.getRightOperand().getTree(), + atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return addAnnotationForLiteralDivideRight(valRight.intValue(), leftAnno); + } + + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + + // Check if the left side's value is known at compile time. + Long valLeft = + ValueCheckerUtils.getExactValue( + node.getLeftOperand().getTree(), + atypeFactory.getValueAnnotatedTypeFactory()); + if (valLeft != null) { + return addAnnotationForLiteralDivideLeft(valLeft.intValue(), leftAnno); + } + + /* This section handles generic annotations: + * pos / {pos, nn} -> nn (can round to zero) + * * / {pos, nn} -> * + */ + if (isPositive(leftAnno) && isNonNegative(rightAnno)) { + return NN; + } + if (isNonNegative(rightAnno)) { + return leftAnno; + } + // Everything else is unknown. + return UNKNOWN; + } + + /** A remainder with 1 or -1 as the divisor always results in zero. */ + private AnnotationMirror addAnnotationForLiteralRemainder(int val) { + if (val == 1 || val == -1) { + return NN; + } + return UNKNOWN; + } + + /** Adds a default NonNegative annotation to every character. Implements case 33. */ + @Override + protected void addInformationFromPreconditions( + CFStore info, + AnnotatedTypeFactory factory, + UnderlyingAST.CFGMethod method, + MethodTree methodTree, + ExecutableElement methodElement) { + super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); + + List paramTrees = methodTree.getParameters(); + + for (VariableTree variableTree : paramTrees) { + if (TreeUtils.typeOf(variableTree).getKind() == TypeKind.CHAR) { + JavaExpression je = JavaExpression.fromVariableTree(variableTree); + info.insertValuePermitNondeterministic(je, atypeFactory.NN); + } + } + } + + /** + * getAnnotationForRemainder handles these cases: + * + *
          +     *      27. * % 1/-1 → nn
          +     *      28. pos/nn % * → nn
          +     *      29. gten1 % * → gten1
          +     *      * % * → lbu
          +     * 
          + */ + public AnnotationMirror getAnnotationForRemainder( + IntegerRemainderNode node, TransferInput p) { + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + + // Check if the right side's value is known at compile time. + Long valRight = + ValueCheckerUtils.getExactValue( + node.getRightOperand().getTree(), + atypeFactory.getValueAnnotatedTypeFactory()); + if (valRight != null) { + return addAnnotationForLiteralRemainder(valRight.intValue()); + } + + /* This section handles generic annotations: + pos/nn % * -> nn + gten1 % * -> gten1 + */ + if (isNonNegative(leftAnno)) { + return NN; + } + if (isGTEN1(leftAnno)) { + return GTEN1; + } + + // Everything else is unknown. + return UNKNOWN; + } + + /** Handles shifts (case 30). * >> NonNegative → NonNegative */ + private AnnotationMirror getAnnotationForRightShift( + BinaryOperationNode node, TransferInput p) { + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + + if (isNonNegative(leftAnno)) { + if (isNonNegative(rightAnno)) { + return NN; + } + } + return UNKNOWN; + } + + /** + * Handles masking (case 31). Particularly, handles the following cases: * & NonNegative + * → NonNegative + */ + private AnnotationMirror getAnnotationForAnd( + BitwiseAndNode node, TransferInput p) { + + AnnotationMirror rightAnno = getLowerBoundAnnotation(node.getRightOperand(), p); + if (isNonNegative(rightAnno)) { + return NN; + } + + AnnotationMirror leftAnno = getLowerBoundAnnotation(node.getLeftOperand(), p); + if (isNonNegative(leftAnno)) { + return NN; + } + return UNKNOWN; + } + + /** + * Returns true if the argument is the @Positive type annotation. + * + * @param anm the annotation to test + * @return true if the argument is the @Positive type annotation + */ + private boolean isPositive(AnnotationMirror anm) { + return atypeFactory.areSameByClass(anm, Positive.class); + } + + /** + * Returns true if the argument is the @NonNegative type annotation (or a stronger one). + * + * @param anm the annotation to test + * @return true if the argument is the @NonNegative type annotation + */ + private boolean isNonNegative(AnnotationMirror anm) { + return atypeFactory.areSameByClass(anm, NonNegative.class) || isPositive(anm); + } + + /** + * Returns true if the argument is the @GTENegativeOne type annotation (or a stronger one). + * + * @param anm the annotation to test + * @return true if the argument is the @GTENegativeOne type annotation + */ + private boolean isGTEN1(AnnotationMirror anm) { + return atypeFactory.areSameByClass(anm, GTENegativeOne.class) || isNonNegative(anm); + } + + private AnnotationMirror getLowerBoundAnnotation( + Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + if (value == null) { + throw new BugInCF("value==null for getLowerBoundAnnotation(%s, %s)%n", subNode, p); + } + return getLowerBoundAnnotation(value); + } + + /** + * Returns the lower bound annotation for the given value. + * + * @param cfValue a value + * @return the lower bound annotation for the given value + */ + private AnnotationMirror getLowerBoundAnnotation(CFValue cfValue) { + return atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(cfValue.getAnnotations(), atypeFactory.UNKNOWN); + } + + @Override + public TransferResult visitNumericalAddition( + NumericalAdditionNode n, TransferInput p) { + TransferResult result = super.visitNumericalAddition(n, p); + AnnotationMirror newAnno = getAnnotationForPlus(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput p) { + TransferResult result = super.visitNumericalSubtraction(n, p); + AnnotationMirror newAnno = getAnnotationForMinus(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitNumericalMultiplication( + NumericalMultiplicationNode n, TransferInput p) { + TransferResult result = super.visitNumericalMultiplication(n, p); + AnnotationMirror newAnno = getAnnotationForMultiply(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitIntegerDivision( + IntegerDivisionNode n, TransferInput p) { + TransferResult result = super.visitIntegerDivision(n, p); + AnnotationMirror newAnno = getAnnotationForDivide(n, p); + return createNewResult(result, newAnno); + } + + @Override + public TransferResult visitIntegerRemainder( + IntegerRemainderNode n, TransferInput p) { + TransferResult transferResult = super.visitIntegerRemainder(n, p); + AnnotationMirror resultAnno = getAnnotationForRemainder(n, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitSignedRightShift( + SignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitSignedRightShift(n, p); + AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitUnsignedRightShift( + UnsignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitUnsignedRightShift(n, p); + AnnotationMirror resultAnno = getAnnotationForRightShift(n, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseAnd( + BitwiseAndNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseAnd(n, p); + AnnotationMirror resultAnno = getAnnotationForAnd(n, p); + return createNewResult(transferResult, resultAnno); + } + + /** + * Create a new transfer result based on the original result and the new annotation. + * + * @param result the original result + * @param resultAnno the new annotation + * @return the new transfer result + */ + private TransferResult createNewResult( + TransferResult result, AnnotationMirror resultAnno) { + CFValue newResultValue = + analysis.createSingleAnnotationValue( + resultAnno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java index ee67e1087e4..dfeef36f9b0 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java @@ -4,7 +4,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.Subsequence; import org.checkerframework.checker.index.qual.NonNegative; @@ -14,6 +14,8 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.util.JavaExpressionParseUtil; +import javax.lang.model.element.AnnotationMirror; + /** * Implements the actual checks to make sure that array accesses aren't too low. Will issue a * warning if a variable that can't be proved to be either "NonNegative" (i.e. ≥ 0) or "Positive" @@ -21,75 +23,80 @@ */ public class LowerBoundVisitor extends BaseTypeVisitor { - /* This is a key into the messages.properties file in the same - * directory, which includes the actual text of the warning. - */ - private static final @CompilerMessageKey String LOWER_BOUND = "array.access.unsafe.low"; - private static final @CompilerMessageKey String NEGATIVE_ARRAY = "array.length.negative"; - private static final @CompilerMessageKey String FROM_NOT_NN = "from.not.nonnegative"; - - public LowerBoundVisitor(BaseTypeChecker checker) { - super(checker); - } + /* This is a key into the messages.properties file in the same + * directory, which includes the actual text of the warning. + */ + private static final @CompilerMessageKey String LOWER_BOUND = "array.access.unsafe.low"; + private static final @CompilerMessageKey String NEGATIVE_ARRAY = "array.length.negative"; + private static final @CompilerMessageKey String FROM_NOT_NN = "from.not.nonnegative"; - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void type) { - ExpressionTree index = tree.getIndex(); - String arrName = tree.getExpression().toString(); - AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(index); - if (!(indexType.hasAnnotation(NonNegative.class) || indexType.hasAnnotation(Positive.class))) { - checker.reportError(index, LOWER_BOUND, indexType.toString(), arrName); + public LowerBoundVisitor(BaseTypeChecker checker) { + super(checker); } - return super.visitArrayAccess(tree, type); - } + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void type) { + ExpressionTree index = tree.getIndex(); + String arrName = tree.getExpression().toString(); + AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(index); + if (!(indexType.hasAnnotation(NonNegative.class) + || indexType.hasAnnotation(Positive.class))) { + checker.reportError(index, LOWER_BOUND, indexType.toString(), arrName); + } - @Override - public Void visitNewArray(NewArrayTree tree, Void type) { - if (!tree.getDimensions().isEmpty()) { - for (ExpressionTree dim : tree.getDimensions()) { - AnnotatedTypeMirror dimType = atypeFactory.getAnnotatedType(dim); - if (!(dimType.hasAnnotation(NonNegative.class) || dimType.hasAnnotation(Positive.class))) { - checker.reportError(dim, NEGATIVE_ARRAY, dimType.toString()); + return super.visitArrayAccess(tree, type); + } + + @Override + public Void visitNewArray(NewArrayTree tree, Void type) { + if (!tree.getDimensions().isEmpty()) { + for (ExpressionTree dim : tree.getDimensions()) { + AnnotatedTypeMirror dimType = atypeFactory.getAnnotatedType(dim); + if (!(dimType.hasAnnotation(NonNegative.class) + || dimType.hasAnnotation(Positive.class))) { + checker.reportError(dim, NEGATIVE_ARRAY, dimType.toString()); + } + } } - } + + return super.visitNewArray(tree, type); } - return super.visitNewArray(tree, type); - } + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { + // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) + // occurs, from is non-negative. - // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) - // occurs, from is non-negative. + boolean result = true; - boolean result = true; + Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); + if (subSeq != null) { + AnnotationMirror anm; + try { + anm = + atypeFactory.getAnnotationMirrorFromJavaExpressionString( + subSeq.from, varTree, getCurrentPath()); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + anm = null; + } + if (anm == null + || !(atypeFactory.areSameByClass(anm, NonNegative.class) + || atypeFactory.areSameByClass(anm, Positive.class))) { + checker.reportError( + valueTree, + FROM_NOT_NN, + subSeq.from, + anm == null ? "@LowerBoundUnknown" : anm); + result = false; + } + } - Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); - if (subSeq != null) { - AnnotationMirror anm; - try { - anm = - atypeFactory.getAnnotationMirrorFromJavaExpressionString( - subSeq.from, varTree, getCurrentPath()); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - anm = null; - } - if (anm == null - || !(atypeFactory.areSameByClass(anm, NonNegative.class) - || atypeFactory.areSameByClass(anm, Positive.class))) { - checker.reportError( - valueTree, FROM_NOT_NN, subSeq.from, anm == null ? "@LowerBoundUnknown" : anm); - result = false; - } + result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; + return result; } - - result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; - return result; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java index 6f6e33a65f6..5f89ffc57d7 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenAnnotatedTypeFactory.java @@ -5,17 +5,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.index.IndexMethodIdentifier; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.PolyLength; @@ -40,6 +30,19 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** * The SameLen Checker is used to determine whether there are multiple fixed-length sequences (such * as arrays or strings) in a program that share the same length. It is part of the Index Checker, @@ -70,339 +73,351 @@ */ public class SameLenAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link SameLenUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, SameLenUnknown.class); - - /** The @{@link SameLenBottom} annotation. */ - private final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, SameLenBottom.class); - - /** The @{@link PolySameLen} annotation. */ - private final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolySameLen.class); - - /** The SameLen.value field/element. */ - final ExecutableElement sameLenValueElement = - TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); - - /** Predicates about method calls. */ - private final IndexMethodIdentifier imf = new IndexMethodIdentifier(this); - - /** Create a new SameLenAnnotatedTypeFactory. */ - public SameLenAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - addAliasedTypeAnnotation(PolyLength.class, POLY); - - this.postInit(); - } - - /** Gets a helper object that holds references to methods with special handling. */ - IndexMethodIdentifier getMethodIdentifier() { - return imf; - } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList(SameLen.class, SameLenBottom.class, SameLenUnknown.class, PolySameLen.class)); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SameLenQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - // Handles case "user-written SameLen" - @Override - public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree tree) { - AnnotatedTypeMirror atm = super.getAnnotatedTypeLhs(tree); - - if (tree.getKind() == Tree.Kind.VARIABLE) { - AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); - if (sameLenAnno != null) { - JavaExpression je = JavaExpression.fromVariableTree((VariableTree) tree); - String varName = je.toString(); - - List exprs = - AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class); - exprs.remove(varName); - if (exprs.isEmpty()) { - atm.replaceAnnotation(UNKNOWN); - } else { - atm.replaceAnnotation(createSameLen(exprs)); - } - } + /** The @{@link SameLenUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, SameLenUnknown.class); + + /** The @{@link SameLenBottom} annotation. */ + private final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, SameLenBottom.class); + + /** The @{@link PolySameLen} annotation. */ + private final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolySameLen.class); + + /** The SameLen.value field/element. */ + final ExecutableElement sameLenValueElement = + TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); + + /** Predicates about method calls. */ + private final IndexMethodIdentifier imf = new IndexMethodIdentifier(this); + + /** Create a new SameLenAnnotatedTypeFactory. */ + public SameLenAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + addAliasedTypeAnnotation(PolyLength.class, POLY); + + this.postInit(); } - return atm; - } - - /** Returns true if the given expression may appear in a @SameLen annotation. */ - public static boolean mayAppearInSameLen(JavaExpression expr) { - return !expr.containsUnknown() - && !(expr instanceof ArrayCreation) - && !(expr instanceof ClassName) - // avoid SameLen expressions with e.g. literal String constants - && !(expr instanceof ValueLiteral) - // Big expressions cause a stack overflow in JavaExpressionParseUtil. - // So limit them to an arbitrary length of 999. - && expr.toString().length() < 1000; - } - - /** - * The qualifier hierarchy for the SameLen type system. Most types are distinct and at the same - * level: for instance @SameLen("a") and @SameLen("b) have nothing in common. However, if one type - * includes even one overlapping name, then the types have to be the same: - * so @SameLen({"a","b","c"} and @SameLen({"c","f","g"} are actually the same type -- both should - * usually be replaced by a SameLen with the union of the lists of names. - */ - private final class SameLenQualifierHierarchy extends ElementQualifierHierarchy { + /** Gets a helper object that holds references to methods with special handling. */ + IndexMethodIdentifier getMethodIdentifier() { + return imf; + } - /** - * Creates a SameLenQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - public SameLenQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, SameLenAnnotatedTypeFactory.this); + @Override + protected Set> createSupportedTypeQualifiers() { + // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList( + SameLen.class, + SameLenBottom.class, + SameLenUnknown.class, + PolySameLen.class)); } @Override - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - return UNKNOWN; + protected QualifierHierarchy createQualifierHierarchy() { + return new SameLenQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + // Handles case "user-written SameLen" + @Override + public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree tree) { + AnnotatedTypeMirror atm = super.getAnnotatedTypeLhs(tree); + + if (tree.getKind() == Tree.Kind.VARIABLE) { + AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); + if (sameLenAnno != null) { + JavaExpression je = JavaExpression.fromVariableTree((VariableTree) tree); + String varName = je.toString(); + + List exprs = + AnnotationUtils.getElementValueArray( + sameLenAnno, sameLenValueElement, String.class); + exprs.remove(varName); + if (exprs.isEmpty()) { + atm.replaceAnnotation(UNKNOWN); + } else { + atm.replaceAnnotation(createSameLen(exprs)); + } + } + } + + return atm; + } + + /** Returns true if the given expression may appear in a @SameLen annotation. */ + public static boolean mayAppearInSameLen(JavaExpression expr) { + return !expr.containsUnknown() + && !(expr instanceof ArrayCreation) + && !(expr instanceof ClassName) + // avoid SameLen expressions with e.g. literal String constants + && !(expr instanceof ValueLiteral) + // Big expressions cause a stack overflow in JavaExpressionParseUtil. + // So limit them to an arbitrary length of 999. + && expr.toString().length() < 1000; } /** - * If the collections are both non-empty and disjoint, returns null. Otherwise, returns their - * union. The collections must not contain duplicates. - * - * @param c1 a collection of Strings (intended to be the value argument of a SameLen annotation) - * @param c2 another collection of Strings - * @return if the two inputs are disjoint (i.e., have no elements in common) and both are - * non-empty, returns null. Otherwise, returns the union of the two collections (which, if - * one collection is empty, is just the other collection). The result is sorted. + * The qualifier hierarchy for the SameLen type system. Most types are distinct and at the same + * level: for instance @SameLen("a") and @SameLen("b) have nothing in common. However, if one + * type includes even one overlapping name, then the types have to be the same: + * so @SameLen({"a","b","c"} and @SameLen({"c","f","g"} are actually the same type -- both + * should usually be replaced by a SameLen with the union of the lists of names. */ - private @Nullable Collection unionIfNotDisjoint( - Collection c1, Collection c2) { - if (c1.isEmpty()) { - return c2; - } else if (c2.isEmpty()) { - return c1; - } - Set result = new TreeSet<>(c1); - boolean disjoint = true; - for (String s : c2) { - if (!result.add(s)) { - disjoint = false; + private final class SameLenQualifierHierarchy extends ElementQualifierHierarchy { + + /** + * Creates a SameLenQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + public SameLenQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, SameLenAnnotatedTypeFactory.this); } - } - if (!disjoint) { - return result; - } else { - return null; - } - } - // The GLB of two SameLen annotations is the union of the two sets of arrays, or is bottom - // if the sets do not intersect. - @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { - List a1Val = - AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); - List a2Val = - AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); - - Collection exprs = unionIfNotDisjoint(a1Val, a2Val); - if (exprs == null) { - return BOTTOM; - } else { - return createSameLen(exprs); + @Override + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + return UNKNOWN; } - } else { - // If one of the annotations is top, the glb is the other annotation; otherwise - // bottom. - if (areSameByClass(a1, SameLenUnknown.class)) { - return a2; - } else if (areSameByClass(a2, SameLenUnknown.class)) { - return a1; - } else { - return BOTTOM; + + /** + * If the collections are both non-empty and disjoint, returns null. Otherwise, returns + * their union. The collections must not contain duplicates. + * + * @param c1 a collection of Strings (intended to be the value argument of a SameLen + * annotation) + * @param c2 another collection of Strings + * @return if the two inputs are disjoint (i.e., have no elements in common) and both are + * non-empty, returns null. Otherwise, returns the union of the two collections (which, + * if one collection is empty, is just the other collection). The result is sorted. + */ + private @Nullable Collection unionIfNotDisjoint( + Collection c1, Collection c2) { + if (c1.isEmpty()) { + return c2; + } else if (c2.isEmpty()) { + return c1; + } + Set result = new TreeSet<>(c1); + boolean disjoint = true; + for (String s : c2) { + if (!result.add(s)) { + disjoint = false; + } + } + if (!disjoint) { + return result; + } else { + return null; + } } - } - } - // The LUB of two SameLen annotations is the intersection of the two sets of arrays, or is - // top if they do not intersect. - @Override - public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { - List a1Val = - AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); - List a2Val = - AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); - - if (!Collections.disjoint(a1Val, a2Val)) { - a1Val.retainAll(a2Val); - return createSameLen(a1Val); - } else { - return UNKNOWN; + // The GLB of two SameLen annotations is the union of the two sets of arrays, or is bottom + // if the sets do not intersect. + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { + List a1Val = + AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); + List a2Val = + AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); + + Collection exprs = unionIfNotDisjoint(a1Val, a2Val); + if (exprs == null) { + return BOTTOM; + } else { + return createSameLen(exprs); + } + } else { + // If one of the annotations is top, the glb is the other annotation; otherwise + // bottom. + if (areSameByClass(a1, SameLenUnknown.class)) { + return a2; + } else if (areSameByClass(a2, SameLenUnknown.class)) { + return a1; + } else { + return BOTTOM; + } + } } - } else { - // the lub is either one of the annotations (if the other is bottom), or top. - if (areSameByClass(a1, SameLenBottom.class)) { - return a2; - } else if (areSameByClass(a2, SameLenBottom.class)) { - return a1; - } else if (areSameByClass(a1, PolySameLen.class) && areSameByClass(a2, PolySameLen.class)) { - return a1; - } else { - return UNKNOWN; + // The LUB of two SameLen annotations is the intersection of the two sets of arrays, or is + // top if they do not intersect. + @Override + public AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (areSameByClass(a1, SameLen.class) && areSameByClass(a2, SameLen.class)) { + List a1Val = + AnnotationUtils.getElementValueArray(a1, sameLenValueElement, String.class); + List a2Val = + AnnotationUtils.getElementValueArray(a2, sameLenValueElement, String.class); + + if (!Collections.disjoint(a1Val, a2Val)) { + a1Val.retainAll(a2Val); + return createSameLen(a1Val); + } else { + return UNKNOWN; + } + + } else { + // the lub is either one of the annotations (if the other is bottom), or top. + if (areSameByClass(a1, SameLenBottom.class)) { + return a2; + } else if (areSameByClass(a2, SameLenBottom.class)) { + return a1; + } else if (areSameByClass(a1, PolySameLen.class) + && areSameByClass(a2, PolySameLen.class)) { + return a1; + } else { + return UNKNOWN; + } + } } - } - } - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (areSameByClass(subAnno, SameLenBottom.class)) { - return true; - } else if (areSameByClass(superAnno, SameLenUnknown.class)) { - return true; - } else if (areSameByClass(subAnno, PolySameLen.class)) { - return areSameByClass(superAnno, PolySameLen.class); - } else if (areSameByClass(subAnno, SameLen.class) - && areSameByClass(superAnno, SameLen.class)) { - List subArrays = - AnnotationUtils.getElementValueArray(subAnno, sameLenValueElement, String.class); - List superArrays = - AnnotationUtils.getElementValueArray(superAnno, sameLenValueElement, String.class); - - if (subArrays.containsAll(superArrays)) { - return true; + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (areSameByClass(subAnno, SameLenBottom.class)) { + return true; + } else if (areSameByClass(superAnno, SameLenUnknown.class)) { + return true; + } else if (areSameByClass(subAnno, PolySameLen.class)) { + return areSameByClass(superAnno, PolySameLen.class); + } else if (areSameByClass(subAnno, SameLen.class) + && areSameByClass(superAnno, SameLen.class)) { + List subArrays = + AnnotationUtils.getElementValueArray( + subAnno, sameLenValueElement, String.class); + List superArrays = + AnnotationUtils.getElementValueArray( + superAnno, sameLenValueElement, String.class); + + if (subArrays.containsAll(superArrays)) { + return true; + } + } + return false; } - } - return false; } - } - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new SameLenTreeAnnotator(this)); - } + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new SameLenTreeAnnotator(this)); + } - /** - * SameLen needs a tree annotator in order to properly type the right side of assignments of new - * arrays that are initialized with the length of another array. - */ - protected class SameLenTreeAnnotator extends TreeAnnotator { + /** + * SameLen needs a tree annotator in order to properly type the right side of assignments of new + * arrays that are initialized with the length of another array. + */ + protected class SameLenTreeAnnotator extends TreeAnnotator { - public SameLenTreeAnnotator(SameLenAnnotatedTypeFactory factory) { - super(factory); - } + public SameLenTreeAnnotator(SameLenAnnotatedTypeFactory factory) { + super(factory); + } - // Case "new array" for "new T[a.length]" - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - if (tree.getDimensions().size() == 1) { - Tree dimensionTree = tree.getDimensions().get(0); - ExpressionTree sequenceTree = - IndexUtil.getLengthSequenceTree(dimensionTree, imf, processingEnv); - if (sequenceTree != null) { - AnnotationMirror sequenceAnno = - getAnnotatedType(sequenceTree).getAnnotationInHierarchy(UNKNOWN); - - JavaExpression sequenceExpr = JavaExpression.fromTree(sequenceTree); - if (mayAppearInSameLen(sequenceExpr)) { - String recString = sequenceExpr.toString(); - if (areSameByClass(sequenceAnno, SameLenUnknown.class)) { - sequenceAnno = createSameLen(Collections.singletonList(recString)); - } else if (areSameByClass(sequenceAnno, SameLen.class)) { - // Add the sequence whose length is being used to the annotation. - List exprs = - AnnotationUtils.getElementValueArray( - sequenceAnno, sameLenValueElement, String.class); - int index = Collections.binarySearch(exprs, recString); - if (index < 0) { - exprs.add(-index - 1, recString); - sequenceAnno = createSameLen(exprs); - } + // Case "new array" for "new T[a.length]" + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + if (tree.getDimensions().size() == 1) { + Tree dimensionTree = tree.getDimensions().get(0); + ExpressionTree sequenceTree = + IndexUtil.getLengthSequenceTree(dimensionTree, imf, processingEnv); + if (sequenceTree != null) { + AnnotationMirror sequenceAnno = + getAnnotatedType(sequenceTree).getAnnotationInHierarchy(UNKNOWN); + + JavaExpression sequenceExpr = JavaExpression.fromTree(sequenceTree); + if (mayAppearInSameLen(sequenceExpr)) { + String recString = sequenceExpr.toString(); + if (areSameByClass(sequenceAnno, SameLenUnknown.class)) { + sequenceAnno = createSameLen(Collections.singletonList(recString)); + } else if (areSameByClass(sequenceAnno, SameLen.class)) { + // Add the sequence whose length is being used to the annotation. + List exprs = + AnnotationUtils.getElementValueArray( + sequenceAnno, sameLenValueElement, String.class); + int index = Collections.binarySearch(exprs, recString); + if (index < 0) { + exprs.add(-index - 1, recString); + sequenceAnno = createSameLen(exprs); + } + } + } + type.addAnnotation(sequenceAnno); + } } - } - type.addAnnotation(sequenceAnno); + return null; } - } - return null; - } - } - - /** - * Find all the sequences that are members of the SameLen annotation associated with the sequence - * named in sequenceExpression along the current path. - */ - public List getSameLensFromString( - String sequenceExpression, Tree tree, TreePath currentPath) { - AnnotationMirror sameLenAnno; - try { - sameLenAnno = - getAnnotationFromJavaExpressionString( - sequenceExpression, tree, currentPath, SameLen.class); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - // ignore parse errors - return Collections.emptyList(); } - if (sameLenAnno == null) { - return Collections.emptyList(); + + /** + * Find all the sequences that are members of the SameLen annotation associated with the + * sequence named in sequenceExpression along the current path. + */ + public List getSameLensFromString( + String sequenceExpression, Tree tree, TreePath currentPath) { + AnnotationMirror sameLenAnno; + try { + sameLenAnno = + getAnnotationFromJavaExpressionString( + sequenceExpression, tree, currentPath, SameLen.class); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + // ignore parse errors + return Collections.emptyList(); + } + if (sameLenAnno == null) { + return Collections.emptyList(); + } + return AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class); } - return AnnotationUtils.getElementValueArray(sameLenAnno, sameLenValueElement, String.class); - } - - /// - /// Creating @SameLen annotations - /// - - /** - * Creates a @SameLen annotation whose values are the given strings. - * - * @param exprs the values for the @SameLen annotation. This must be an ordered - * collection such as a list or TreeSet in which the strings are in alphabetical order. - * @return a @SameLen annotation whose values are the given strings - */ - public AnnotationMirror createSameLen(Collection exprs) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class); - String[] exprArray = exprs.toArray(new String[0]); - builder.setValue("value", exprArray); - return builder.build(); - } - - /** - * Generates a SameLen that includes each expression, as well as everything in the annotations2, - * if they are SameLen annotations. - * - * @param exprs a list of expressions representing arrays to be included in the combined - * annotation - * @param annos a list of annotations - * @return a combined SameLen annotation - */ - public AnnotationMirror createCombinedSameLen( - List exprs, List annos) { - - Set strings = new TreeSet<>(); - for (JavaExpression expr : exprs) { - if (mayAppearInSameLen(expr)) { - strings.add(expr.toString()); - } + + /// + /// Creating @SameLen annotations + /// + + /** + * Creates a @SameLen annotation whose values are the given strings. + * + * @param exprs the values for the @SameLen annotation. This must be an ordered + * collection such as a list or TreeSet in which the strings are in alphabetical order. + * @return a @SameLen annotation whose values are the given strings + */ + public AnnotationMirror createSameLen(Collection exprs) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SameLen.class); + String[] exprArray = exprs.toArray(new String[0]); + builder.setValue("value", exprArray); + return builder.build(); } - for (AnnotationMirror anno : annos) { - if (areSameByClass(anno, SameLen.class)) { - strings.addAll( - AnnotationUtils.getElementValueArray(anno, sameLenValueElement, String.class)); - } + + /** + * Generates a SameLen that includes each expression, as well as everything in the annotations2, + * if they are SameLen annotations. + * + * @param exprs a list of expressions representing arrays to be included in the combined + * annotation + * @param annos a list of annotations + * @return a combined SameLen annotation + */ + public AnnotationMirror createCombinedSameLen( + List exprs, List annos) { + + Set strings = new TreeSet<>(); + for (JavaExpression expr : exprs) { + if (mayAppearInSameLen(expr)) { + strings.add(expr.toString()); + } + } + for (AnnotationMirror anno : annos) { + if (areSameByClass(anno, SameLen.class)) { + strings.addAll( + AnnotationUtils.getElementValueArray( + anno, sameLenValueElement, String.class)); + } + } + return createSameLen(strings); } - return createSameLen(strings); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java index 97394dd83e5..1b33b608da3 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenChecker.java @@ -14,6 +14,6 @@ // @RelevantJavaTypes({CharSequence.class, Object[].class, Object.class}) @SuppressWarningsPrefix({"index", "samelen"}) public class SameLenChecker extends BaseTypeChecker { - /** Create a new SameLenChecker. */ - public SameLenChecker() {} + /** Create a new SameLenChecker. */ + public SameLenChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java index 17606d22572..69db0830c8b 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenTransfer.java @@ -3,13 +3,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.SameLen; import org.checkerframework.checker.nullness.qual.Nullable; @@ -32,6 +26,15 @@ import org.checkerframework.framework.util.JavaExpressionParseUtil; import org.checkerframework.javacutil.AnnotationUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; + /** * The transfer function for the SameLen checker. Contains three cases: * @@ -44,270 +47,273 @@ */ public class SameLenTransfer extends CFTransfer { - /** The annotated type factory. */ - private final SameLenAnnotatedTypeFactory atypeFactory; - - /** Shorthand for atypeFactory.UNKNOWN. */ - private final AnnotationMirror UNKNOWN; - - /** - * Create a new SameLenTransfer. - * - * @param analysis the CFAnalysis - */ - public SameLenTransfer(CFAnalysis analysis) { - super(analysis); - this.atypeFactory = (SameLenAnnotatedTypeFactory) analysis.getTypeFactory(); - this.UNKNOWN = atypeFactory.UNKNOWN; - } - - /** - * Gets the receiver sequence of a length access node, or null if {@code lengthNode} is not a - * length access. - */ - private @Nullable Node getLengthReceiver(Node lengthNode) { - if (isArrayLengthAccess(lengthNode)) { - // lengthNode is a.length - FieldAccessNode lengthFieldAccessNode = (FieldAccessNode) lengthNode; - return lengthFieldAccessNode.getReceiver(); - } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthNode)) { - // lengthNode is s.length() - MethodInvocationNode lengthMethodInvocationNode = (MethodInvocationNode) lengthNode; - return lengthMethodInvocationNode.getTarget().getReceiver(); - } else { - return null; + /** The annotated type factory. */ + private final SameLenAnnotatedTypeFactory atypeFactory; + + /** Shorthand for atypeFactory.UNKNOWN. */ + private final AnnotationMirror UNKNOWN; + + /** + * Create a new SameLenTransfer. + * + * @param analysis the CFAnalysis + */ + public SameLenTransfer(CFAnalysis analysis) { + super(analysis); + this.atypeFactory = (SameLenAnnotatedTypeFactory) analysis.getTypeFactory(); + this.UNKNOWN = atypeFactory.UNKNOWN; } - } - - @Override - public TransferResult visitAssignment( - AssignmentNode node, TransferInput in) { - TransferResult result = super.visitAssignment(node, in); - - // Handle b = new T[a.length] - if (node.getExpression() instanceof ArrayCreationNode) { - ArrayCreationNode acNode = (ArrayCreationNode) node.getExpression(); - if (acNode.getDimensions().size() == 1) { - - Node lengthNode = acNode.getDimension(0); - Node lengthNodeReceiver = getLengthReceiver(lengthNode); - - if (lengthNodeReceiver != null) { - // "new T[a.length]" or "new T[s.length()]" is the right hand side of the - // assignment. - // lengthNode is known to be "lengthNodeReceiver.length" or - // "lengthNodeReceiver.length()" - - // targetRec is the receiver for the left hand side of the assignment. - JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); - JavaExpression otherRec = JavaExpression.fromNode(lengthNodeReceiver); - - AnnotationMirror lengthNodeAnnotation = - atypeFactory - .getAnnotatedType(lengthNodeReceiver.getTree()) - .getAnnotationInHierarchy(UNKNOWN); - - AnnotationMirror combinedSameLen = - atypeFactory.createCombinedSameLen( - Arrays.asList(targetRec, otherRec), Arrays.asList(UNKNOWN, lengthNodeAnnotation)); - - propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); - return result; + + /** + * Gets the receiver sequence of a length access node, or null if {@code lengthNode} is not a + * length access. + */ + private @Nullable Node getLengthReceiver(Node lengthNode) { + if (isArrayLengthAccess(lengthNode)) { + // lengthNode is a.length + FieldAccessNode lengthFieldAccessNode = (FieldAccessNode) lengthNode; + return lengthFieldAccessNode.getReceiver(); + } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthNode)) { + // lengthNode is s.length() + MethodInvocationNode lengthMethodInvocationNode = (MethodInvocationNode) lengthNode; + return lengthMethodInvocationNode.getTarget().getReceiver(); + } else { + return null; } - } } - AnnotationMirror rightAnno = - atypeFactory - .getAnnotatedType(node.getExpression().getTree()) - .getAnnotationInHierarchy(UNKNOWN); + @Override + public TransferResult visitAssignment( + AssignmentNode node, TransferInput in) { + TransferResult result = super.visitAssignment(node, in); + + // Handle b = new T[a.length] + if (node.getExpression() instanceof ArrayCreationNode) { + ArrayCreationNode acNode = (ArrayCreationNode) node.getExpression(); + if (acNode.getDimensions().size() == 1) { + + Node lengthNode = acNode.getDimension(0); + Node lengthNodeReceiver = getLengthReceiver(lengthNode); + + if (lengthNodeReceiver != null) { + // "new T[a.length]" or "new T[s.length()]" is the right hand side of the + // assignment. + // lengthNode is known to be "lengthNodeReceiver.length" or + // "lengthNodeReceiver.length()" + + // targetRec is the receiver for the left hand side of the assignment. + JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); + JavaExpression otherRec = JavaExpression.fromNode(lengthNodeReceiver); + + AnnotationMirror lengthNodeAnnotation = + atypeFactory + .getAnnotatedType(lengthNodeReceiver.getTree()) + .getAnnotationInHierarchy(UNKNOWN); + + AnnotationMirror combinedSameLen = + atypeFactory.createCombinedSameLen( + Arrays.asList(targetRec, otherRec), + Arrays.asList(UNKNOWN, lengthNodeAnnotation)); + + propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); + return result; + } + } + } + + AnnotationMirror rightAnno = + atypeFactory + .getAnnotatedType(node.getExpression().getTree()) + .getAnnotationInHierarchy(UNKNOWN); - // If the left side of the assignment is an array or a string, then have both the right and - // left side be SameLen of each other. + // If the left side of the assignment is an array or a string, then have both the right and + // left side be SameLen of each other. - JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); + JavaExpression targetRec = JavaExpression.fromNode(node.getTarget()); - JavaExpression exprRec = JavaExpression.fromNode(node.getExpression()); + JavaExpression exprRec = JavaExpression.fromNode(node.getExpression()); - if (IndexUtil.isSequenceType(node.getTarget().getType()) - || (rightAnno != null && atypeFactory.areSameByClass(rightAnno, SameLen.class))) { + if (IndexUtil.isSequenceType(node.getTarget().getType()) + || (rightAnno != null && atypeFactory.areSameByClass(rightAnno, SameLen.class))) { - AnnotationMirror rightAnnoOrUnknown = rightAnno == null ? UNKNOWN : rightAnno; + AnnotationMirror rightAnnoOrUnknown = rightAnno == null ? UNKNOWN : rightAnno; - AnnotationMirror combinedSameLen = - atypeFactory.createCombinedSameLen( - Arrays.asList(targetRec, exprRec), Arrays.asList(UNKNOWN, rightAnnoOrUnknown)); + AnnotationMirror combinedSameLen = + atypeFactory.createCombinedSameLen( + Arrays.asList(targetRec, exprRec), + Arrays.asList(UNKNOWN, rightAnnoOrUnknown)); - propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); - } + propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); + } - return result; - } - - /** - * Insert a @SameLen annotation into the store as the SameLen type of each array listed in it. - * - * @param sameLenAnno a {@code @SameLen} annotation. Not just an annotation in the SameLen - * hierarchy; this annotation must be {@code @SameLen(...)}. - * @param node the node in the tree where the combination is happening. Used for context. - * @param store the store to modify - */ - private void propagateCombinedSameLen(AnnotationMirror sameLenAnno, Node node, CFStore store) { - TreePath currentPath = atypeFactory.getPath(node.getTree()); - if (currentPath == null) { - return; - } - for (String exprString : - AnnotationUtils.getElementValueArray( - sameLenAnno, atypeFactory.sameLenValueElement, String.class)) { - JavaExpression je; - try { - je = atypeFactory.parseJavaExpressionString(exprString, currentPath); - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - continue; - } - store.clearValue(je); - store.insertValue(je, sameLenAnno); - } - } - - /** Returns true if node is of the form "someArray.length". */ - private boolean isArrayLengthAccess(Node node) { - return (node instanceof FieldAccessNode - && ((FieldAccessNode) node).getFieldName().equals("length") - && ((FieldAccessNode) node).getReceiver().getType().getKind() == TypeKind.ARRAY); - } - - /** - * Handles refinement of equality comparisons. Assumes "a == b" or "a.length == b.length" - * evaluates to true. The method gives a and b SameLen of each other in the store. - * - * @param left the first argument to the equality operator - * @param right the second argument to the equality operator - * @param store the store in which to perform refinement - */ - private void refineEq(Node left, Node right, CFStore store) { - List exprs = new ArrayList<>(2); - List annos = new ArrayList<>(2); - for (Node internal : splitAssignments(left)) { - exprs.add(JavaExpression.fromNode(internal)); - annos.add(getAnno(internal)); + return result; } - for (Node internal : splitAssignments(right)) { - exprs.add(JavaExpression.fromNode(internal)); - annos.add(getAnno(internal)); + + /** + * Insert a @SameLen annotation into the store as the SameLen type of each array listed in it. + * + * @param sameLenAnno a {@code @SameLen} annotation. Not just an annotation in the SameLen + * hierarchy; this annotation must be {@code @SameLen(...)}. + * @param node the node in the tree where the combination is happening. Used for context. + * @param store the store to modify + */ + private void propagateCombinedSameLen(AnnotationMirror sameLenAnno, Node node, CFStore store) { + TreePath currentPath = atypeFactory.getPath(node.getTree()); + if (currentPath == null) { + return; + } + for (String exprString : + AnnotationUtils.getElementValueArray( + sameLenAnno, atypeFactory.sameLenValueElement, String.class)) { + JavaExpression je; + try { + je = atypeFactory.parseJavaExpressionString(exprString, currentPath); + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + continue; + } + store.clearValue(je); + store.insertValue(je, sameLenAnno); + } } - AnnotationMirror combinedSameLen = atypeFactory.createCombinedSameLen(exprs, annos); - - propagateCombinedSameLen(combinedSameLen, left, store); - } - - /** - * Returns {@code n}'s annotation from the SameLen hierarchy. - * - *

          analysis.getValue fails if called on an lvalue. However, this method needs to always - * succeed, even when n is an lvalue. Consider this code: - * - *

          {@code if ((a = b) == c) {...}}
          - * - * where a, b, and c are all arrays, and a has type {@code @SameLen("d")}. Afterwards, all three - * should have the type {@code @SameLen({"a", "b", "c", "d"})}, but in order to accomplish this, - * this method must return the type of a, which is an lvalue. - * - * @param n a node whose SameLen annotation to return - * @return {@code n}'s annotation from the SameLen hierarchy - */ - AnnotationMirror getAnno(Node n) { - if (n.isLValue()) { - return atypeFactory.getAnnotatedType(n.getTree()).getAnnotationInHierarchy(UNKNOWN); + /** Returns true if node is of the form "someArray.length". */ + private boolean isArrayLengthAccess(Node node) { + return (node instanceof FieldAccessNode + && ((FieldAccessNode) node).getFieldName().equals("length") + && ((FieldAccessNode) node).getReceiver().getType().getKind() == TypeKind.ARRAY); } - CFValue cfValue = analysis.getValue(n); - if (cfValue == null) { - return UNKNOWN; + + /** + * Handles refinement of equality comparisons. Assumes "a == b" or "a.length == b.length" + * evaluates to true. The method gives a and b SameLen of each other in the store. + * + * @param left the first argument to the equality operator + * @param right the second argument to the equality operator + * @param store the store in which to perform refinement + */ + private void refineEq(Node left, Node right, CFStore store) { + List exprs = new ArrayList<>(2); + List annos = new ArrayList<>(2); + for (Node internal : splitAssignments(left)) { + exprs.add(JavaExpression.fromNode(internal)); + annos.add(getAnno(internal)); + } + for (Node internal : splitAssignments(right)) { + exprs.add(JavaExpression.fromNode(internal)); + annos.add(getAnno(internal)); + } + + AnnotationMirror combinedSameLen = atypeFactory.createCombinedSameLen(exprs, annos); + + propagateCombinedSameLen(combinedSameLen, left, store); } - return atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(cfValue.getAnnotations(), UNKNOWN); - } - - /** Implements the transfer rules for both equal nodes and not-equals nodes. */ - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult result, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - // If result is a Regular transfer, then the elseStore is a copy of the then store, that is - // created when getElseStore is called. So do that before refining any values. - CFStore elseStore = result.getElseStore(); - CFStore thenStore = result.getThenStore(); - CFStore equalStore = notEqualTo ? elseStore : thenStore; - - Node firstLengthReceiver = getLengthReceiver(firstNode); - Node secondLengthReceiver = getLengthReceiver(secondNode); - - if (firstLengthReceiver != null && secondLengthReceiver != null) { - // Refinement in the else store if this is a.length == b.length (or length() in case of - // strings). - refineEq(firstLengthReceiver, secondLengthReceiver, equalStore); - } else if (IndexUtil.isSequenceType(firstNode.getType()) - || IndexUtil.isSequenceType(secondNode.getType())) { - refineEq(firstNode, secondNode, equalStore); + + /** + * Returns {@code n}'s annotation from the SameLen hierarchy. + * + *

          analysis.getValue fails if called on an lvalue. However, this method needs to always + * succeed, even when n is an lvalue. Consider this code: + * + *

          {@code if ((a = b) == c) {...}}
          + * + * where a, b, and c are all arrays, and a has type {@code @SameLen("d")}. Afterwards, all three + * should have the type {@code @SameLen({"a", "b", "c", "d"})}, but in order to accomplish this, + * this method must return the type of a, which is an lvalue. + * + * @param n a node whose SameLen annotation to return + * @return {@code n}'s annotation from the SameLen hierarchy + */ + AnnotationMirror getAnno(Node n) { + if (n.isLValue()) { + return atypeFactory.getAnnotatedType(n.getTree()).getAnnotationInHierarchy(UNKNOWN); + } + CFValue cfValue = analysis.getValue(n); + if (cfValue == null) { + return UNKNOWN; + } + return atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(cfValue.getAnnotations(), UNKNOWN); } - return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - } - - /** Overridden to ensure that SameLen annotations on method parameters are symmetric. */ - @Override - protected void addInformationFromPreconditions( - CFStore info, - AnnotatedTypeFactory factory, - UnderlyingAST.CFGMethod method, - MethodTree methodTree, - ExecutableElement methodElement) { - super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); - - List paramTrees = methodTree.getParameters(); - int numParams = paramTrees.size(); - List paramNames = new ArrayList<>(numParams); - List params = new ArrayList<>(numParams); - - for (VariableTree tree : paramTrees) { - paramNames.add(tree.getName().toString()); - params.add(atypeFactory.getAnnotatedType(tree)); + /** Implements the transfer rules for both equal nodes and not-equals nodes. */ + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult result, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + // If result is a Regular transfer, then the elseStore is a copy of the then store, that is + // created when getElseStore is called. So do that before refining any values. + CFStore elseStore = result.getElseStore(); + CFStore thenStore = result.getThenStore(); + CFStore equalStore = notEqualTo ? elseStore : thenStore; + + Node firstLengthReceiver = getLengthReceiver(firstNode); + Node secondLengthReceiver = getLengthReceiver(secondNode); + + if (firstLengthReceiver != null && secondLengthReceiver != null) { + // Refinement in the else store if this is a.length == b.length (or length() in case of + // strings). + refineEq(firstLengthReceiver, secondLengthReceiver, equalStore); + } else if (IndexUtil.isSequenceType(firstNode.getType()) + || IndexUtil.isSequenceType(secondNode.getType())) { + refineEq(firstNode, secondNode, equalStore); + } + + return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); } - for (int index = 0; index < numParams; index++) { - - // If the parameter has a samelen annotation, then look for other parameters in that - // annotation and propagate default the other annotation so that it is symmetric. - AnnotatedTypeMirror atm = params.get(index); - AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); - if (sameLenAnno == null) { - continue; - } - - List values = - AnnotationUtils.getElementValueArray( - sameLenAnno, atypeFactory.sameLenValueElement, String.class); - for (String value : values) { - int otherParamIndex = paramNames.indexOf(value); - if (otherParamIndex == -1) { - continue; + /** Overridden to ensure that SameLen annotations on method parameters are symmetric. */ + @Override + protected void addInformationFromPreconditions( + CFStore info, + AnnotatedTypeFactory factory, + UnderlyingAST.CFGMethod method, + MethodTree methodTree, + ExecutableElement methodElement) { + super.addInformationFromPreconditions(info, factory, method, methodTree, methodElement); + + List paramTrees = methodTree.getParameters(); + int numParams = paramTrees.size(); + List paramNames = new ArrayList<>(numParams); + List params = new ArrayList<>(numParams); + + for (VariableTree tree : paramTrees) { + paramNames.add(tree.getName().toString()); + params.add(atypeFactory.getAnnotatedType(tree)); } - // the SameLen value is in the list of params, so modify the type of - // that param in the store - AnnotationMirror newSameLen = - atypeFactory.createSameLen(Collections.singletonList(paramNames.get(index))); - JavaExpression otherParamRec = - JavaExpression.fromVariableTree(paramTrees.get(otherParamIndex)); - info.insertValuePermitNondeterministic(otherParamRec, newSameLen); - } + for (int index = 0; index < numParams; index++) { + + // If the parameter has a samelen annotation, then look for other parameters in that + // annotation and propagate default the other annotation so that it is symmetric. + AnnotatedTypeMirror atm = params.get(index); + AnnotationMirror sameLenAnno = atm.getAnnotation(SameLen.class); + if (sameLenAnno == null) { + continue; + } + + List values = + AnnotationUtils.getElementValueArray( + sameLenAnno, atypeFactory.sameLenValueElement, String.class); + for (String value : values) { + int otherParamIndex = paramNames.indexOf(value); + if (otherParamIndex == -1) { + continue; + } + + // the SameLen value is in the list of params, so modify the type of + // that param in the store + AnnotationMirror newSameLen = + atypeFactory.createSameLen( + Collections.singletonList(paramNames.get(index))); + JavaExpression otherParamRec = + JavaExpression.fromVariableTree(paramTrees.get(otherParamIndex)); + info.insertValuePermitNondeterministic(otherParamRec, newSameLen); + } + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java index a28a076d347..4244f77ad0c 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java @@ -2,10 +2,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.TreeSet; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.PolySameLen; @@ -17,47 +14,55 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.TreeSet; + +import javax.lang.model.element.AnnotationMirror; + public class SameLenVisitor extends BaseTypeVisitor { - public SameLenVisitor(BaseTypeChecker checker) { - super(checker); - } + public SameLenVisitor(BaseTypeChecker checker) { + super(checker); + } - /** - * Merges SameLen annotations, then calls super. - * - *

          {@inheritDoc} - */ - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - if (IndexUtil.isSequenceType(valueType.getUnderlyingType()) - && TreeUtils.isExpressionTree(valueTree) - // if both annotations are @PolySameLen, there is nothing to do - && !(valueType.hasAnnotation(PolySameLen.class) - && varType.hasAnnotation(PolySameLen.class))) { + /** + * Merges SameLen annotations, then calls super. + * + *

          {@inheritDoc} + */ + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + if (IndexUtil.isSequenceType(valueType.getUnderlyingType()) + && TreeUtils.isExpressionTree(valueTree) + // if both annotations are @PolySameLen, there is nothing to do + && !(valueType.hasAnnotation(PolySameLen.class) + && varType.hasAnnotation(PolySameLen.class))) { - JavaExpression rhs = JavaExpression.fromTree((ExpressionTree) valueTree); - if (rhs != null && SameLenAnnotatedTypeFactory.mayAppearInSameLen(rhs)) { - String rhsExpr = rhs.toString(); - AnnotationMirror sameLenAnno = valueType.getAnnotation(SameLen.class); - Collection exprs; - if (sameLenAnno == null) { - exprs = Collections.singletonList(rhsExpr); - } else { - exprs = - new TreeSet<>( - AnnotationUtils.getElementValueArray( - sameLenAnno, atypeFactory.sameLenValueElement, String.class)); - exprs.add(rhsExpr); + JavaExpression rhs = JavaExpression.fromTree((ExpressionTree) valueTree); + if (rhs != null && SameLenAnnotatedTypeFactory.mayAppearInSameLen(rhs)) { + String rhsExpr = rhs.toString(); + AnnotationMirror sameLenAnno = valueType.getAnnotation(SameLen.class); + Collection exprs; + if (sameLenAnno == null) { + exprs = Collections.singletonList(rhsExpr); + } else { + exprs = + new TreeSet<>( + AnnotationUtils.getElementValueArray( + sameLenAnno, + atypeFactory.sameLenValueElement, + String.class)); + exprs.add(rhsExpr); + } + AnnotationMirror newSameLen = atypeFactory.createSameLen(exprs); + valueType.replaceAnnotation(newSameLen); + } } - AnnotationMirror newSameLen = atypeFactory.createSameLen(exprs); - valueType.replaceAnnotation(newSameLen); - } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java index bdc81599f68..e1284d978ac 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexAnnotatedTypeFactory.java @@ -1,16 +1,5 @@ package org.checkerframework.checker.index.searchindex; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; import org.checkerframework.checker.index.qual.NegativeIndexFor; import org.checkerframework.checker.index.qual.SearchIndexBottom; import org.checkerframework.checker.index.qual.SearchIndexFor; @@ -26,219 +15,237 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** * The Search Index Checker is used to help type the results of calls to the JDK's binary search * methods. It is part of the Index Checker. */ public class SearchIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link SearchIndexUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, SearchIndexUnknown.class); - - /** The @{@link SearchIndexBottom} annotation. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, SearchIndexBottom.class); - - /** The NegativeIndexFor.value field/element. */ - protected final ExecutableElement negativeIndexForValueElement = - TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); - - /** The SearchIndexFor.value field/element. */ - protected final ExecutableElement searchIndexForValueElement = - TreeUtils.getMethod(SearchIndexFor.class, "value", 0, processingEnv); - - /** - * Create a new SearchIndexAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this - */ - public SearchIndexAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - this.postInit(); - } - - /** - * Provides a way to query the Constant Value Checker, which computes the values of expressions - * known at compile time (constant propagation and folding). - */ - ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - SearchIndexFor.class, - SearchIndexBottom.class, - SearchIndexUnknown.class, - NegativeIndexFor.class)); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SearchIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** - * Returns the {@code value} field/element of the given annotation. - * - * @param am a @NegativeIndexFor or @SearchIndexFor annotation - * @return the {@code value} field/element of the given annotation - */ - private List getValueElement(AnnotationMirror am) { - if (areSameByClass(am, NegativeIndexFor.class)) { - return AnnotationUtils.getElementValueArray(am, negativeIndexForValueElement, String.class); - } else if (areSameByClass(am, SearchIndexFor.class)) { - return AnnotationUtils.getElementValueArray(am, searchIndexForValueElement, String.class); - } else { - throw new TypeSystemError("indexForValue(%s)", am); - } - } + /** The @{@link SearchIndexUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, SearchIndexUnknown.class); + + /** The @{@link SearchIndexBottom} annotation. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, SearchIndexBottom.class); + + /** The NegativeIndexFor.value field/element. */ + protected final ExecutableElement negativeIndexForValueElement = + TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); - /** SearchIndexQualifierHierarchy. */ - private final class SearchIndexQualifierHierarchy extends ElementQualifierHierarchy { + /** The SearchIndexFor.value field/element. */ + protected final ExecutableElement searchIndexForValueElement = + TreeUtils.getMethod(SearchIndexFor.class, "value", 0, processingEnv); /** - * Creates a SearchIndexQualifierHierarchy from the given classes. + * Create a new SearchIndexAnnotatedTypeFactory. * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils + * @param checker the type-checker associated with this */ - public SearchIndexQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, SearchIndexAnnotatedTypeFactory.this); + public SearchIndexAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); } - @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a1; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a2; - } - if (isSubtypeQualifiers(a1, a2)) { - return a1; - } - if (isSubtypeQualifiers(a2, a1)) { - return a2; - } - // If neither is a subtype of the other, then create an - // annotation that combines their values. - - // Each annotation is either NegativeIndexFor or SearchIndexFor. - Set combinedSet = new HashSet<>(getValueElement(a1)); - combinedSet.addAll(getValueElement(a2)); - // The list is backed by the given array. - List combinedList = Arrays.asList(combinedSet.toArray(new String[0])); - - // NegativeIndexFor <: SearchIndexFor. - if (areSameByClass(a1, NegativeIndexFor.class) - || areSameByClass(a2, NegativeIndexFor.class)) { - return createNegativeIndexFor(combinedList); - } else { - return createSearchIndexFor(combinedList); - } + /** + * Provides a way to query the Constant Value Checker, which computes the values of expressions + * known at compile time (constant propagation and folding). + */ + ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); } @Override - public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a2; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a1; - } - if (isSubtypeQualifiers(a1, a2)) { - return a2; - } - if (isSubtypeQualifiers(a2, a1)) { - return a1; - } - // If neither is a subtype of the other, then create an - // annotation that includes only their overlapping values. - - // Each annotation is either NegativeIndexFor or SearchIndexFor. - List arrayIntersection = getValueElement(a1); - arrayIntersection.retainAll(getValueElement(a2)); // intersection - - if (arrayIntersection.isEmpty()) { - return UNKNOWN; - } - - if (areSameByClass(a1, SearchIndexFor.class) || areSameByClass(a2, SearchIndexFor.class)) { - return createSearchIndexFor(arrayIntersection); - } else { - return createNegativeIndexFor(arrayIntersection); - } + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + SearchIndexFor.class, + SearchIndexBottom.class, + SearchIndexUnknown.class, + NegativeIndexFor.class)); } @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (areSameByClass(superAnno, SearchIndexUnknown.class)) { - return true; - } - if (areSameByClass(subAnno, SearchIndexBottom.class)) { - return true; - } - if (areSameByClass(subAnno, SearchIndexUnknown.class)) { - return false; - } - if (areSameByClass(superAnno, SearchIndexBottom.class)) { - return false; - } - - // Each annotation is either NegativeIndexFor or SearchIndexFor. - List superArrays = getValueElement(superAnno); - List subArrays = getValueElement(subAnno); - - // Subtyping requires: - // * subtype is NegativeIndexFor or supertype is SearchIndexFor - // * subtype's arrays are a superset of supertype's arrays - return ((areSameByClass(subAnno, NegativeIndexFor.class) - || areSameByClass(superAnno, SearchIndexFor.class)) - && subArrays.containsAll(superArrays)); + protected QualifierHierarchy createQualifierHierarchy() { + return new SearchIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } - } - /** Create a new {@code @NegativeIndexFor} annotation with the given arrays as its arguments. */ - AnnotationMirror createNegativeIndexFor(List arrays) { - if (arrays.isEmpty()) { - return UNKNOWN; + /** + * Returns the {@code value} field/element of the given annotation. + * + * @param am a @NegativeIndexFor or @SearchIndexFor annotation + * @return the {@code value} field/element of the given annotation + */ + private List getValueElement(AnnotationMirror am) { + if (areSameByClass(am, NegativeIndexFor.class)) { + return AnnotationUtils.getElementValueArray( + am, negativeIndexForValueElement, String.class); + } else if (areSameByClass(am, SearchIndexFor.class)) { + return AnnotationUtils.getElementValueArray( + am, searchIndexForValueElement, String.class); + } else { + throw new TypeSystemError("indexForValue(%s)", am); + } } - arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort + /** SearchIndexQualifierHierarchy. */ + private final class SearchIndexQualifierHierarchy extends ElementQualifierHierarchy { + + /** + * Creates a SearchIndexQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + public SearchIndexQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, SearchIndexAnnotatedTypeFactory.this); + } + + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a1; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a2; + } + if (isSubtypeQualifiers(a1, a2)) { + return a1; + } + if (isSubtypeQualifiers(a2, a1)) { + return a2; + } + // If neither is a subtype of the other, then create an + // annotation that combines their values. + + // Each annotation is either NegativeIndexFor or SearchIndexFor. + Set combinedSet = new HashSet<>(getValueElement(a1)); + combinedSet.addAll(getValueElement(a2)); + // The list is backed by the given array. + List combinedList = Arrays.asList(combinedSet.toArray(new String[0])); + + // NegativeIndexFor <: SearchIndexFor. + if (areSameByClass(a1, NegativeIndexFor.class) + || areSameByClass(a2, NegativeIndexFor.class)) { + return createNegativeIndexFor(combinedList); + } else { + return createSearchIndexFor(combinedList); + } + } + + @Override + public AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a2; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a1; + } + if (isSubtypeQualifiers(a1, a2)) { + return a2; + } + if (isSubtypeQualifiers(a2, a1)) { + return a1; + } + // If neither is a subtype of the other, then create an + // annotation that includes only their overlapping values. + + // Each annotation is either NegativeIndexFor or SearchIndexFor. + List arrayIntersection = getValueElement(a1); + arrayIntersection.retainAll(getValueElement(a2)); // intersection + + if (arrayIntersection.isEmpty()) { + return UNKNOWN; + } + + if (areSameByClass(a1, SearchIndexFor.class) + || areSameByClass(a2, SearchIndexFor.class)) { + return createSearchIndexFor(arrayIntersection); + } else { + return createNegativeIndexFor(arrayIntersection); + } + } + + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (areSameByClass(superAnno, SearchIndexUnknown.class)) { + return true; + } + if (areSameByClass(subAnno, SearchIndexBottom.class)) { + return true; + } + if (areSameByClass(subAnno, SearchIndexUnknown.class)) { + return false; + } + if (areSameByClass(superAnno, SearchIndexBottom.class)) { + return false; + } + + // Each annotation is either NegativeIndexFor or SearchIndexFor. + List superArrays = getValueElement(superAnno); + List subArrays = getValueElement(subAnno); + + // Subtyping requires: + // * subtype is NegativeIndexFor or supertype is SearchIndexFor + // * subtype's arrays are a superset of supertype's arrays + return ((areSameByClass(subAnno, NegativeIndexFor.class) + || areSameByClass(superAnno, SearchIndexFor.class)) + && subArrays.containsAll(superArrays)); + } + } + + /** Create a new {@code @NegativeIndexFor} annotation with the given arrays as its arguments. */ + AnnotationMirror createNegativeIndexFor(List arrays) { + if (arrays.isEmpty()) { + return UNKNOWN; + } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, NegativeIndexFor.class); - builder.setValue("value", arrays); - return builder.build(); - } + arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort - /** Create a new {@code @SearchIndexFor} annotation with the given arrays as its arguments. */ - AnnotationMirror createSearchIndexFor(List arrays) { - if (arrays.isEmpty()) { - return UNKNOWN; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, NegativeIndexFor.class); + builder.setValue("value", arrays); + return builder.build(); } - arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort + /** Create a new {@code @SearchIndexFor} annotation with the given arrays as its arguments. */ + AnnotationMirror createSearchIndexFor(List arrays) { + if (arrays.isEmpty()) { + return UNKNOWN; + } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SearchIndexFor.class); - builder.setValue("value", arrays); - return builder.build(); - } + arrays = new ArrayList<>(new TreeSet<>(arrays)); // remove duplicates and sort + + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, SearchIndexFor.class); + builder.setValue("value", arrays); + return builder.build(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java index 7f929a949e1..fad7e9e235c 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexChecker.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.index.searchindex; -import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SuppressWarningsPrefix; +import java.util.Set; + /** * An internal checker that assists the Index Checker in typing the results of calls to the JDK's * {@link java.util.Arrays#binarySearch(Object[],Object) Arrays.binarySearch} routine. @@ -14,23 +15,23 @@ */ @SuppressWarningsPrefix({"index", "searchindex"}) @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) public class SearchIndexChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java index b4a2b6be3c5..060428fa680 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/searchindex/SearchIndexTransfer.java @@ -1,7 +1,5 @@ package org.checkerframework.checker.index.searchindex; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.checker.index.qual.NegativeIndexFor; import org.checkerframework.checker.index.qual.SearchIndexFor; @@ -14,6 +12,10 @@ import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationUtils; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + /** * The transfer function for the SearchIndexFor checker. Allows {@link SearchIndexFor} to be refined * to {@link NegativeIndexFor}. @@ -22,79 +24,80 @@ */ public class SearchIndexTransfer extends IndexAbstractTransfer { - /** The annotated type factory. */ - private final SearchIndexAnnotatedTypeFactory atypeFactory; + /** The annotated type factory. */ + private final SearchIndexAnnotatedTypeFactory atypeFactory; - /** - * Create a new SearchIndexTransfer. - * - * @param analysis the CFAnalysis - */ - public SearchIndexTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (SearchIndexAnnotatedTypeFactory) analysis.getTypeFactory(); - } + /** + * Create a new SearchIndexTransfer. + * + * @param analysis the CFAnalysis + */ + public SearchIndexTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (SearchIndexAnnotatedTypeFactory) analysis.getTypeFactory(); + } - /** - * If a {@link SearchIndexFor} value is negative, then refine it to {@link NegativeIndexFor}. This - * method is called by refinement rules for binary comparison operators. - * - *

          If the left value (in a greater-than or greater-than-or-equal binary comparison) is exactly - * the value of {@code valueToCompareTo}, and the right side has type {@link SearchIndexFor}, then - * the right side's new value in the store should become {@link NegativeIndexFor}. - * - *

          For example, this allows the following code to typecheck: - * - *

          -   * 
          -   * {@literal @}SearchIndexFor("a") int index = Arrays.binarySearch(a, y);
          -   *  if (index < 0) {
          -   *    {@literal @}NegativeIndexFor("a") int negInsertionPoint = index;
          -   *  }
          -   * 
          -   * 
          - * - * @param valueToCompareTo this value must be 0 (for greater than or less than) or -1 (for greater - * than or equal or less than or equal) - */ - private void refineSearchIndexToNegativeIndexFor( - Node left, Node right, CFStore store, int valueToCompareTo) { - assert valueToCompareTo == 0 || valueToCompareTo == -1; - Long leftValue = - ValueCheckerUtils.getExactValue( - left.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (leftValue != null && leftValue == valueToCompareTo) { - AnnotationMirror rightSIF = - atypeFactory.getAnnotationMirror(right.getTree(), SearchIndexFor.class); - if (rightSIF != null) { - List arrays = - AnnotationUtils.getElementValueArray( - rightSIF, atypeFactory.searchIndexForValueElement, String.class); - AnnotationMirror nif = atypeFactory.createNegativeIndexFor(arrays); - store.insertValue(JavaExpression.fromNode(right), nif); - } + /** + * If a {@link SearchIndexFor} value is negative, then refine it to {@link NegativeIndexFor}. + * This method is called by refinement rules for binary comparison operators. + * + *

          If the left value (in a greater-than or greater-than-or-equal binary comparison) is + * exactly the value of {@code valueToCompareTo}, and the right side has type {@link + * SearchIndexFor}, then the right side's new value in the store should become {@link + * NegativeIndexFor}. + * + *

          For example, this allows the following code to typecheck: + * + *

          +     * 
          +     * {@literal @}SearchIndexFor("a") int index = Arrays.binarySearch(a, y);
          +     *  if (index < 0) {
          +     *    {@literal @}NegativeIndexFor("a") int negInsertionPoint = index;
          +     *  }
          +     * 
          +     * 
          + * + * @param valueToCompareTo this value must be 0 (for greater than or less than) or -1 (for + * greater than or equal or less than or equal) + */ + private void refineSearchIndexToNegativeIndexFor( + Node left, Node right, CFStore store, int valueToCompareTo) { + assert valueToCompareTo == 0 || valueToCompareTo == -1; + Long leftValue = + ValueCheckerUtils.getExactValue( + left.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (leftValue != null && leftValue == valueToCompareTo) { + AnnotationMirror rightSIF = + atypeFactory.getAnnotationMirror(right.getTree(), SearchIndexFor.class); + if (rightSIF != null) { + List arrays = + AnnotationUtils.getElementValueArray( + rightSIF, atypeFactory.searchIndexForValueElement, String.class); + AnnotationMirror nif = atypeFactory.createNegativeIndexFor(arrays); + store.insertValue(JavaExpression.fromNode(right), nif); + } + } } - } - @Override - protected void refineGT( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - refineSearchIndexToNegativeIndexFor(left, right, store, 0); - } + @Override + protected void refineGT( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + refineSearchIndexToNegativeIndexFor(left, right, store, 0); + } - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - refineSearchIndexToNegativeIndexFor(left, right, store, -1); - } + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + refineSearchIndexToNegativeIndexFor(left, right, store, -1); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java index 8d6711c911e..55f91d5e1c8 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java @@ -1,11 +1,5 @@ package org.checkerframework.checker.index.substringindex; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.checker.index.OffsetDependentTypesHelper; import org.checkerframework.checker.index.qual.SubstringIndexBottom; @@ -21,159 +15,178 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** * Builds types with annotations from the Substring Index checker hierarchy, which contains * the @{@link SubstringIndexFor} annotation. */ public class SubstringIndexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The top qualifier of the Substring Index hierarchy. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, SubstringIndexUnknown.class); - - /** The bottom qualifier of the Substring Index hierarchy. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, SubstringIndexBottom.class); - - /** - * Create a new SubstringIndexAnnotatedTypeFactory. - * - * @param checker the associated checker - */ - public SubstringIndexAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - this.postInit(); - } - - /** - * Returns a mutable set of annotation classes that are supported by the Substring Index Checker. - * - * @return mutable set containing annotation classes from the Substring Index qualifier hierarchy - */ - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - SubstringIndexUnknown.class, SubstringIndexFor.class, SubstringIndexBottom.class)); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SubstringIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** - * Creates an {@link DependentTypesHelper} that allows use of addition and subtraction in the - * Substring Index Checker annotations. - */ - @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new OffsetDependentTypesHelper(this); - } - - /** - * The Substring Index qualifier hierarchy. The hierarchy consists of a top element {@link - * #UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link #BOTTOM} of type {@link - * SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the subtyping - * relation of {@link UBQualifier}. - */ - private final class SubstringIndexQualifierHierarchy extends ElementQualifierHierarchy { + /** The top qualifier of the Substring Index hierarchy. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, SubstringIndexUnknown.class); + + /** The bottom qualifier of the Substring Index hierarchy. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, SubstringIndexBottom.class); /** - * Creates a SubstringIndexQualifierHierarchy from the given classes. + * Create a new SubstringIndexAnnotatedTypeFactory. * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils + * @param checker the associated checker */ - public SubstringIndexQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, SubstringIndexAnnotatedTypeFactory.this); + public SubstringIndexAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); } + /** + * Returns a mutable set of annotation classes that are supported by the Substring Index + * Checker. + * + * @return mutable set containing annotation classes from the Substring Index qualifier + * hierarchy + */ @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a1; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a2; - } - UBQualifier ubq1 = - UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier ubq2 = - UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier glb = ubq1.glb(ubq2); - return convertUBQualifierToAnnotation(glb); + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + SubstringIndexUnknown.class, + SubstringIndexFor.class, + SubstringIndexBottom.class)); } @Override - public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, UNKNOWN)) { - return a1; - } - if (AnnotationUtils.areSame(a2, UNKNOWN)) { - return a2; - } - if (AnnotationUtils.areSame(a1, BOTTOM)) { - return a2; - } - if (AnnotationUtils.areSame(a2, BOTTOM)) { - return a1; - } - UBQualifier ubq1 = - UBQualifier.createUBQualifier(a1, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier ubq2 = - UBQualifier.createUBQualifier(a2, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier lub = ubq1.lub(ubq2); - return convertUBQualifierToAnnotation(lub); + protected QualifierHierarchy createQualifierHierarchy() { + return new SubstringIndexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } + /** + * Creates an {@link DependentTypesHelper} that allows use of addition and subtraction in the + * Substring Index Checker annotations. + */ @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (areSameByClass(superAnno, SubstringIndexUnknown.class)) { - return true; - } - if (areSameByClass(subAnno, SubstringIndexBottom.class)) { - return true; - } - if (areSameByClass(subAnno, SubstringIndexUnknown.class)) { - return false; - } - if (areSameByClass(superAnno, SubstringIndexBottom.class)) { - return false; - } - - UBQualifier subtype = - UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker.getUltimateParentChecker()); - UBQualifier supertype = - UBQualifier.createUBQualifier( - superAnno, (IndexChecker) checker.getUltimateParentChecker()); - return subtype.isSubtype(supertype); + protected DependentTypesHelper createDependentTypesHelper() { + return new OffsetDependentTypesHelper(this); } - } - - /** - * Converts an instance of {@link UBQualifier} to an annotation from the Substring Index - * hierarchy. - * - * @param qualifier the {@link UBQualifier} to be converted - * @return an annotation from the Substring Index hierarchy, representing {@code qualifier} - */ - public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { - if (qualifier.isUnknown()) { - return UNKNOWN; - } else if (qualifier.isBottom()) { - return BOTTOM; + + /** + * The Substring Index qualifier hierarchy. The hierarchy consists of a top element {@link + * #UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link #BOTTOM} of type + * {@link SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the + * subtyping relation of {@link UBQualifier}. + */ + private final class SubstringIndexQualifierHierarchy extends ElementQualifierHierarchy { + + /** + * Creates a SubstringIndexQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + public SubstringIndexQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, SubstringIndexAnnotatedTypeFactory.this); + } + + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a1; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a2; + } + UBQualifier ubq1 = + UBQualifier.createUBQualifier( + a1, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier ubq2 = + UBQualifier.createUBQualifier( + a2, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier glb = ubq1.glb(ubq2); + return convertUBQualifierToAnnotation(glb); + } + + @Override + public AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, UNKNOWN)) { + return a1; + } + if (AnnotationUtils.areSame(a2, UNKNOWN)) { + return a2; + } + if (AnnotationUtils.areSame(a1, BOTTOM)) { + return a2; + } + if (AnnotationUtils.areSame(a2, BOTTOM)) { + return a1; + } + UBQualifier ubq1 = + UBQualifier.createUBQualifier( + a1, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier ubq2 = + UBQualifier.createUBQualifier( + a2, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier lub = ubq1.lub(ubq2); + return convertUBQualifierToAnnotation(lub); + } + + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (areSameByClass(superAnno, SubstringIndexUnknown.class)) { + return true; + } + if (areSameByClass(subAnno, SubstringIndexBottom.class)) { + return true; + } + if (areSameByClass(subAnno, SubstringIndexUnknown.class)) { + return false; + } + if (areSameByClass(superAnno, SubstringIndexBottom.class)) { + return false; + } + + UBQualifier subtype = + UBQualifier.createUBQualifier( + subAnno, (IndexChecker) checker.getUltimateParentChecker()); + UBQualifier supertype = + UBQualifier.createUBQualifier( + superAnno, (IndexChecker) checker.getUltimateParentChecker()); + return subtype.isSubtype(supertype); + } } - LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; - return ltlQualifier.convertToSubstringIndexAnnotation(processingEnv); - } + /** + * Converts an instance of {@link UBQualifier} to an annotation from the Substring Index + * hierarchy. + * + * @param qualifier the {@link UBQualifier} to be converted + * @return an annotation from the Substring Index hierarchy, representing {@code qualifier} + */ + public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { + if (qualifier.isUnknown()) { + return UNKNOWN; + } else if (qualifier.isBottom()) { + return BOTTOM; + } + + LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; + return ltlQualifier.convertToSubstringIndexAnnotation(processingEnv); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java index 160dc0592cb..20e44ef50e4 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexChecker.java @@ -15,6 +15,6 @@ // int.class is for @SubstringIndexFor @RelevantJavaTypes({CharSequence.class, Object[].class, int.class}) public class SubstringIndexChecker extends BaseTypeChecker { - /** Creates a SubstringIndexChecker. */ - public SubstringIndexChecker() {} + /** Creates a SubstringIndexChecker. */ + public SubstringIndexChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java index 577f7ce38cd..3a323570523 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/OffsetEquation.java @@ -1,11 +1,5 @@ package org.checkerframework.checker.index.upperbound; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.ValueAnnotatedTypeFactory; import org.checkerframework.common.value.ValueCheckerUtils; @@ -18,6 +12,13 @@ import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; + /** * An offset equation is 2 sets of Java expression strings, one set of added terms and one set of * subtracted terms, and a single integer constant. The Java expression strings have been @@ -26,469 +27,472 @@ *

          An OffsetEquation is mutable. */ public class OffsetEquation { - /** The equation for 0 (zero). */ - public static final OffsetEquation ZERO = createOffsetForInt(0); - - /** The equation for -1. */ - public static final OffsetEquation NEG_1 = createOffsetForInt(-1); - - /** The equation for 1. */ - public static final OffsetEquation ONE = createOffsetForInt(1); - - /** Mutable list of terms that have been added to this. */ - private final List addedTerms; - - /** Mutable list of terms that have been subtracted from this. */ - private final List subtractedTerms; - - /** The integer offset. */ - private int intValue; - - /** Non-null if an error has occurred. */ - private @Nullable String error; - - /** Create a new OffsetEquation. */ - private OffsetEquation() { - addedTerms = new ArrayList<>(1); - subtractedTerms = new ArrayList<>(1); - this.intValue = 0; - this.error = null; - } - - /** - * Create a new OffsetEquation that is a copy of the given one. - * - * @param other the OffsetEquation to copy - */ - protected OffsetEquation(OffsetEquation other) { - this.addedTerms = new ArrayList<>(other.addedTerms); - this.subtractedTerms = new ArrayList<>(other.subtractedTerms); - this.intValue = other.intValue; - this.error = other.error; - } - - public boolean hasError() { - return error != null; - } - - public @Nullable String getError() { - return error; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; + /** The equation for 0 (zero). */ + public static final OffsetEquation ZERO = createOffsetForInt(0); + + /** The equation for -1. */ + public static final OffsetEquation NEG_1 = createOffsetForInt(-1); + + /** The equation for 1. */ + public static final OffsetEquation ONE = createOffsetForInt(1); + + /** Mutable list of terms that have been added to this. */ + private final List addedTerms; + + /** Mutable list of terms that have been subtracted from this. */ + private final List subtractedTerms; + + /** The integer offset. */ + private int intValue; + + /** Non-null if an error has occurred. */ + private @Nullable String error; + + /** Create a new OffsetEquation. */ + private OffsetEquation() { + addedTerms = new ArrayList<>(1); + subtractedTerms = new ArrayList<>(1); + this.intValue = 0; + this.error = null; } - if (o == null || getClass() != o.getClass()) { - return false; + + /** + * Create a new OffsetEquation that is a copy of the given one. + * + * @param other the OffsetEquation to copy + */ + protected OffsetEquation(OffsetEquation other) { + this.addedTerms = new ArrayList<>(other.addedTerms); + this.subtractedTerms = new ArrayList<>(other.subtractedTerms); + this.intValue = other.intValue; + this.error = other.error; } - OffsetEquation that = (OffsetEquation) o; + public boolean hasError() { + return error != null; + } - if (intValue != that.intValue) { - return false; + public @Nullable String getError() { + return error; } - if (addedTerms.size() != that.addedTerms.size() - || !addedTerms.containsAll(that.addedTerms) - || !that.addedTerms.containsAll(addedTerms)) { - return false; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OffsetEquation that = (OffsetEquation) o; + + if (intValue != that.intValue) { + return false; + } + if (addedTerms.size() != that.addedTerms.size() + || !addedTerms.containsAll(that.addedTerms) + || !that.addedTerms.containsAll(addedTerms)) { + return false; + } + if (subtractedTerms.size() != that.subtractedTerms.size() + || !subtractedTerms.containsAll(that.subtractedTerms) + || !that.subtractedTerms.containsAll(subtractedTerms)) { + return false; + } + return error != null ? error.equals(that.error) : that.error == null; } - if (subtractedTerms.size() != that.subtractedTerms.size() - || !subtractedTerms.containsAll(that.subtractedTerms) - || !that.subtractedTerms.containsAll(subtractedTerms)) { - return false; + + @Override + public int hashCode() { + return Objects.hash(addedTerms, subtractedTerms, intValue, error); } - return error != null ? error.equals(that.error) : that.error == null; - } - - @Override - public int hashCode() { - return Objects.hash(addedTerms, subtractedTerms, intValue, error); - } - - @Override - public String toString() { - if (addedTerms.isEmpty() && subtractedTerms.isEmpty()) { - return String.valueOf(intValue); + + @Override + public String toString() { + if (addedTerms.isEmpty() && subtractedTerms.isEmpty()) { + return String.valueOf(intValue); + } + List sortedAdds = new ArrayList<>(addedTerms); + Collections.sort(sortedAdds); + + List sortedSubs = new ArrayList<>(subtractedTerms); + Collections.sort(sortedSubs); + + String adds = String.join(" + ", sortedAdds); + String minus = String.join(" - ", sortedSubs); + if (sortedSubs.size() == 1 && sortedAdds.isEmpty()) { + minus = "-" + minus; + } else if (!sortedSubs.isEmpty()) { + minus = " - " + minus; + } + String terms = (adds + minus).trim(); + if (intValue != 0) { + char op = intValue > 0 ? '+' : '-'; + if (terms.isEmpty()) { + terms += intValue; + } else { + terms += " " + op + " " + Math.abs(intValue); + } + } + return terms; } - List sortedAdds = new ArrayList<>(addedTerms); - Collections.sort(sortedAdds); - - List sortedSubs = new ArrayList<>(subtractedTerms); - Collections.sort(sortedSubs); - - String adds = String.join(" + ", sortedAdds); - String minus = String.join(" - ", sortedSubs); - if (sortedSubs.size() == 1 && sortedAdds.isEmpty()) { - minus = "-" + minus; - } else if (!sortedSubs.isEmpty()) { - minus = " - " + minus; + + /** + * Makes a copy of this offset and removes any added terms that are accesses to the length of + * the listed sequences. If any terms were removed, then the copy is returned. Otherwise, null + * is returned. + * + * @param sequences list of sequences (arrays or strings) + * @return a copy of this equation with array.length and string.length() removed or null if no + * array.lengths or string.length() could be removed + */ + public @Nullable OffsetEquation removeSequenceLengths(List sequences) { + OffsetEquation copy = new OffsetEquation(this); + boolean simplified = false; + for (String sequence : sequences) { + String arrayLen = sequence + ".length"; + if (addedTerms.contains(arrayLen)) { + copy.addedTerms.remove(arrayLen); + simplified = true; + } + String stringLen = sequence + ".length()"; + if (addedTerms.contains(stringLen)) { + copy.addedTerms.remove(stringLen); + simplified = true; + } + } + return simplified ? copy : null; } - String terms = (adds + minus).trim(); - if (intValue != 0) { - char op = intValue > 0 ? '+' : '-'; - if (terms.isEmpty()) { - terms += intValue; - } else { - terms += " " + op + " " + Math.abs(intValue); - } + + /** + * Adds or subtracts the other equation to a copy of this one. + * + *

          If subtraction is specified, then every term in other is subtracted. + * + * @param op '-' for subtraction or '+' for addition + * @param other equation to add or subtract + * @return a copy of this equation +/- other + */ + public OffsetEquation copyAdd(char op, OffsetEquation other) { + assert op == '-' || op == '+'; + OffsetEquation copy = new OffsetEquation(this); + if (op == '+') { + copy.plus(other); + } else { + copy.minus(other); + } + return copy; } - return terms; - } - - /** - * Makes a copy of this offset and removes any added terms that are accesses to the length of the - * listed sequences. If any terms were removed, then the copy is returned. Otherwise, null is - * returned. - * - * @param sequences list of sequences (arrays or strings) - * @return a copy of this equation with array.length and string.length() removed or null if no - * array.lengths or string.length() could be removed - */ - public @Nullable OffsetEquation removeSequenceLengths(List sequences) { - OffsetEquation copy = new OffsetEquation(this); - boolean simplified = false; - for (String sequence : sequences) { - String arrayLen = sequence + ".length"; - if (addedTerms.contains(arrayLen)) { - copy.addedTerms.remove(arrayLen); - simplified = true; - } - String stringLen = sequence + ".length()"; - if (addedTerms.contains(stringLen)) { - copy.addedTerms.remove(stringLen); - simplified = true; - } + + private void plus(OffsetEquation eq) { + addInt(eq.intValue); + for (String term : eq.addedTerms) { + addTerm('+', term); + } + for (String term : eq.subtractedTerms) { + addTerm('-', term); + } } - return simplified ? copy : null; - } - - /** - * Adds or subtracts the other equation to a copy of this one. - * - *

          If subtraction is specified, then every term in other is subtracted. - * - * @param op '-' for subtraction or '+' for addition - * @param other equation to add or subtract - * @return a copy of this equation +/- other - */ - public OffsetEquation copyAdd(char op, OffsetEquation other) { - assert op == '-' || op == '+'; - OffsetEquation copy = new OffsetEquation(this); - if (op == '+') { - copy.plus(other); - } else { - copy.minus(other); + + private void minus(OffsetEquation eq) { + addInt(-1 * eq.intValue); + for (String term : eq.addedTerms) { + addTerm('-', term); + } + for (String term : eq.subtractedTerms) { + addTerm('+', term); + } } - return copy; - } - private void plus(OffsetEquation eq) { - addInt(eq.intValue); - for (String term : eq.addedTerms) { - addTerm('+', term); + /** + * Returns whether or not this equation is known to be less than or equal to the other equation. + * + * @param other equation + * @return whether or not this equation is known to be less than or equal to the other equation + */ + public boolean lessThanOrEqual(OffsetEquation other) { + return (isInt() && other.isInt() && intValue <= other.getInt()) || this.equals(other); } - for (String term : eq.subtractedTerms) { - addTerm('-', term); + + /** + * Returns true if this equation is a single int value. + * + * @return true if this equation is a single int value + */ + public boolean isInt() { + return addedTerms.isEmpty() && subtractedTerms.isEmpty(); } - } - private void minus(OffsetEquation eq) { - addInt(-1 * eq.intValue); - for (String term : eq.addedTerms) { - addTerm('-', term); + /** + * Returns the int value associated with this equation. + * + *

          The equation may or may not have other terms. Use {@link #isInt()} to determine if the + * equation is only this int value. + * + * @return the int value associated with this equation + */ + public int getInt() { + return intValue; } - for (String term : eq.subtractedTerms) { - addTerm('+', term); + + /** + * Returns true if this equation is exactly -1. + * + * @return true if this equation is exactly -1 + */ + public boolean isNegOne() { + return isInt() && getInt() == -1; } - } - - /** - * Returns whether or not this equation is known to be less than or equal to the other equation. - * - * @param other equation - * @return whether or not this equation is known to be less than or equal to the other equation - */ - public boolean lessThanOrEqual(OffsetEquation other) { - return (isInt() && other.isInt() && intValue <= other.getInt()) || this.equals(other); - } - - /** - * Returns true if this equation is a single int value. - * - * @return true if this equation is a single int value - */ - public boolean isInt() { - return addedTerms.isEmpty() && subtractedTerms.isEmpty(); - } - - /** - * Returns the int value associated with this equation. - * - *

          The equation may or may not have other terms. Use {@link #isInt()} to determine if the - * equation is only this int value. - * - * @return the int value associated with this equation - */ - public int getInt() { - return intValue; - } - - /** - * Returns true if this equation is exactly -1. - * - * @return true if this equation is exactly -1 - */ - public boolean isNegOne() { - return isInt() && getInt() == -1; - } - - /** - * Returns true if this equation non-negative. - * - * @return true if this equation non-negative - */ - public boolean isNonNegative() { - return isInt() && getInt() >= 0; - } - - /** - * Returns true if this equation is negative or zero. - * - * @return true if this equation is negative or zero - */ - public boolean isNegativeOrZero() { - return isInt() && getInt() <= 0; - } - - /** - * Adds the term to this equation. If string is an integer, then it is added or subtracted, - * depending on operator, from the int value of this equation. Otherwise, the term is placed in - * the added or subtracted terms set, depending on operator. - * - * @param operator '+' or '-' - * @param term an int value or Java expression to add to this equation - */ - private void addTerm(char operator, String term) { - term = term.trim(); - if (operator == '-' && term.equals("2147483648")) { - addInt(-2147483648); - return; + + /** + * Returns true if this equation non-negative. + * + * @return true if this equation non-negative + */ + public boolean isNonNegative() { + return isInt() && getInt() >= 0; } - if (isInt(term)) { - int literal = parseInt(term); - addInt(operator == '-' ? -1 * literal : literal); - return; + + /** + * Returns true if this equation is negative or zero. + * + * @return true if this equation is negative or zero + */ + public boolean isNegativeOrZero() { + return isInt() && getInt() <= 0; } - if (operator == '-') { - if (addedTerms.contains(term)) { - addedTerms.remove(term); - } else { - subtractedTerms.add(term); - } - } else if (operator == '+') { - if (subtractedTerms.contains(term)) { - subtractedTerms.remove(term); - } else { - addedTerms.add(term); - } - } else { - assert false; + + /** + * Adds the term to this equation. If string is an integer, then it is added or subtracted, + * depending on operator, from the int value of this equation. Otherwise, the term is placed in + * the added or subtracted terms set, depending on operator. + * + * @param operator '+' or '-' + * @param term an int value or Java expression to add to this equation + */ + private void addTerm(char operator, String term) { + term = term.trim(); + if (operator == '-' && term.equals("2147483648")) { + addInt(-2147483648); + return; + } + if (isInt(term)) { + int literal = parseInt(term); + addInt(operator == '-' ? -1 * literal : literal); + return; + } + if (operator == '-') { + if (addedTerms.contains(term)) { + addedTerms.remove(term); + } else { + subtractedTerms.add(term); + } + } else if (operator == '+') { + if (subtractedTerms.contains(term)) { + subtractedTerms.remove(term); + } else { + addedTerms.add(term); + } + } else { + assert false; + } } - } - - private void addInt(int value) { - intValue += value; - } - - /** - * Returns the offset equation that is an int value or null if there isn't one. - * - * @param equationSet a set of offset equations - * @return the offset equation that is an int value or null if there isn't one - */ - public static @Nullable OffsetEquation getIntOffsetEquation(Set equationSet) { - for (OffsetEquation eq : equationSet) { - if (eq.isInt()) { - return eq; - } + + private void addInt(int value) { + intValue += value; } - return null; - } - - /** - * Creates an offset equation that is only the int value specified. - * - * @param value int value of the equation - * @return an offset equation that is only the int value specified - */ - public static OffsetEquation createOffsetForInt(int value) { - OffsetEquation equation = new OffsetEquation(); - equation.intValue = value; - return equation; - } - - /** - * Creates an offset equation from the expressionEquation. The expressionEquation may be several - * Java expressions added or subtracted from each other. The expressionEquation may also start - * with + or -. If the expressionEquation is the empty string, then the offset equation returned - * is zero. - * - * @param expressionEquation a Java expression made up of sums and differences - * @return an offset equation created from expressionEquation - */ - public static OffsetEquation createOffsetFromJavaExpression(String expressionEquation) { - expressionEquation = expressionEquation.trim(); - OffsetEquation equation = new OffsetEquation(); - if (expressionEquation.isEmpty()) { - equation.addTerm('+', "0"); - return equation; + + /** + * Returns the offset equation that is an int value or null if there isn't one. + * + * @param equationSet a set of offset equations + * @return the offset equation that is an int value or null if there isn't one + */ + public static @Nullable OffsetEquation getIntOffsetEquation(Set equationSet) { + for (OffsetEquation eq : equationSet) { + if (eq.isInt()) { + return eq; + } + } + return null; } - if (DependentTypesError.isExpressionError(expressionEquation)) { - equation.error = expressionEquation; - return equation; + /** + * Creates an offset equation that is only the int value specified. + * + * @param value int value of the equation + * @return an offset equation that is only the int value specified + */ + public static OffsetEquation createOffsetForInt(int value) { + OffsetEquation equation = new OffsetEquation(); + equation.intValue = value; + return equation; } - if (indexOf(expressionEquation, '-', '+', 0) == -1) { - equation.addTerm('+', expressionEquation); - return equation; + + /** + * Creates an offset equation from the expressionEquation. The expressionEquation may be several + * Java expressions added or subtracted from each other. The expressionEquation may also start + * with + or -. If the expressionEquation is the empty string, then the offset equation returned + * is zero. + * + * @param expressionEquation a Java expression made up of sums and differences + * @return an offset equation created from expressionEquation + */ + public static OffsetEquation createOffsetFromJavaExpression(String expressionEquation) { + expressionEquation = expressionEquation.trim(); + OffsetEquation equation = new OffsetEquation(); + if (expressionEquation.isEmpty()) { + equation.addTerm('+', "0"); + return equation; + } + + if (DependentTypesError.isExpressionError(expressionEquation)) { + equation.error = expressionEquation; + return equation; + } + if (indexOf(expressionEquation, '-', '+', 0) == -1) { + equation.addTerm('+', expressionEquation); + return equation; + } + + int index = 0; + while (index < expressionEquation.length()) { + char operator = expressionEquation.charAt(index); + if (operator == '-' || operator == '+') { + index++; + } else { + operator = '+'; + } + + int endIndex = indexOf(expressionEquation, '-', '+', index); + String subexpression; + if (endIndex == -1) { + endIndex = expressionEquation.length(); + subexpression = expressionEquation.substring(index); + } else { + subexpression = expressionEquation.substring(index, endIndex); + } + + equation.addTerm(operator, subexpression); + index = endIndex; + } + return equation; } - int index = 0; - while (index < expressionEquation.length()) { - char operator = expressionEquation.charAt(index); - if (operator == '-' || operator == '+') { - index++; - } else { - operator = '+'; - } - - int endIndex = indexOf(expressionEquation, '-', '+', index); - String subexpression; - if (endIndex == -1) { - endIndex = expressionEquation.length(); - subexpression = expressionEquation.substring(index); - } else { - subexpression = expressionEquation.substring(index, endIndex); - } - - equation.addTerm(operator, subexpression); - index = endIndex; + /** A regular expression that matches an integer literal. */ + private static Pattern intPattern = Pattern.compile("[-+]?[0-9]+"); + + /** + * Returns true if the given string is an integer literal + * + * @param string a string + * @return true if the given string is an integer literal + */ + private static boolean isInt(String string) { + return intPattern.matcher(string).matches(); } - return equation; - } - - /** A regular expression that matches an integer literal. */ - private static Pattern intPattern = Pattern.compile("[-+]?[0-9]+"); - - /** - * Returns true if the given string is an integer literal - * - * @param string a string - * @return true if the given string is an integer literal - */ - private static boolean isInt(String string) { - return intPattern.matcher(string).matches(); - } - - private static int parseInt(String intLiteral) { - if (intLiteral.isEmpty()) { - return 0; + + private static int parseInt(String intLiteral) { + if (intLiteral.isEmpty()) { + return 0; + } + return Integer.valueOf(intLiteral); } - return Integer.valueOf(intLiteral); - } - - /** Returns the first index of a or b in string, or -1 if neither char is in string. */ - private static int indexOf(String string, char a, char b, int index) { - int aIndex = string.indexOf(a, index); - int bIndex = string.indexOf(b, index); - if (aIndex == -1) { - return bIndex; - } else if (bIndex == -1) { - return aIndex; - } else { - return Math.min(aIndex, bIndex); + + /** Returns the first index of a or b in string, or -1 if neither char is in string. */ + private static int indexOf(String string, char a, char b, int index) { + int aIndex = string.indexOf(a, index); + int bIndex = string.indexOf(b, index); + if (aIndex == -1) { + return bIndex; + } else if (bIndex == -1) { + return aIndex; + } else { + return Math.min(aIndex, bIndex); + } } - } - - /** - * If node is an int value known at compile time, then the returned equation is just the int value - * or if op is '-', the return equation is the negation of the int value. - * - *

          Otherwise, null is returned. - * - * @param node the Node from which to create an offset equation - * @param factory an AnnotationTypeFactory - * @param op '+' or '-' - * @return an offset equation from value of known or null if the value isn't known - */ - public static @Nullable OffsetEquation createOffsetFromNodesValue( - Node node, ValueAnnotatedTypeFactory factory, char op) { - assert op == '+' || op == '-'; - if (node.getTree() != null && TreeUtils.isExpressionTree(node.getTree())) { - Long i = ValueCheckerUtils.getExactValue(node.getTree(), factory); - if (i != null) { - if (op == '-') { - i = -i; + + /** + * If node is an int value known at compile time, then the returned equation is just the int + * value or if op is '-', the return equation is the negation of the int value. + * + *

          Otherwise, null is returned. + * + * @param node the Node from which to create an offset equation + * @param factory an AnnotationTypeFactory + * @param op '+' or '-' + * @return an offset equation from value of known or null if the value isn't known + */ + public static @Nullable OffsetEquation createOffsetFromNodesValue( + Node node, ValueAnnotatedTypeFactory factory, char op) { + assert op == '+' || op == '-'; + if (node.getTree() != null && TreeUtils.isExpressionTree(node.getTree())) { + Long i = ValueCheckerUtils.getExactValue(node.getTree(), factory); + if (i != null) { + if (op == '-') { + i = -i; + } + OffsetEquation eq = new OffsetEquation(); + eq.addInt(i.intValue()); + return eq; + } } + return null; + } + + /** + * Creates an offset equation from the Node. + * + *

          If node is an addition or subtracted node, then this method is called recursively on the + * left and right hand nodes and the two equations are added/subtracted to each other depending + * on the value of op. + * + *

          Otherwise the return equation is created by converting the node to a {@link + * org.checkerframework.dataflow.expression.JavaExpression} and then added as a term to the + * returned equation. If op is '-' then it is a subtracted term. + * + * @param node the Node from which to create an offset equation + * @param factory an AnnotationTypeFactory + * @param op '+' or '-' + * @return an offset equation from the Node + */ + public static OffsetEquation createOffsetFromNode( + Node node, AnnotationProvider factory, char op) { + assert op == '+' || op == '-'; OffsetEquation eq = new OffsetEquation(); - eq.addInt(i.intValue()); + createOffsetFromNode(node, factory, eq, op); return eq; - } } - return null; - } - - /** - * Creates an offset equation from the Node. - * - *

          If node is an addition or subtracted node, then this method is called recursively on the - * left and right hand nodes and the two equations are added/subtracted to each other depending on - * the value of op. - * - *

          Otherwise the return equation is created by converting the node to a {@link - * org.checkerframework.dataflow.expression.JavaExpression} and then added as a term to the - * returned equation. If op is '-' then it is a subtracted term. - * - * @param node the Node from which to create an offset equation - * @param factory an AnnotationTypeFactory - * @param op '+' or '-' - * @return an offset equation from the Node - */ - public static OffsetEquation createOffsetFromNode( - Node node, AnnotationProvider factory, char op) { - assert op == '+' || op == '-'; - OffsetEquation eq = new OffsetEquation(); - createOffsetFromNode(node, factory, eq, op); - return eq; - } - - /** - * Updates an offset equation from a Node. - * - * @param node the Node from which to create an offset equation - * @param factory an AnnotationTypeFactory - * @param eq an OffsetEquation to update - * @param op '+' or '-' - */ - private static void createOffsetFromNode( - Node node, AnnotationProvider factory, OffsetEquation eq, char op) { - JavaExpression je = JavaExpression.fromNode(node); - if (je instanceof Unknown || je == null) { - if (node instanceof NumericalAdditionNode) { - createOffsetFromNode(((NumericalAdditionNode) node).getLeftOperand(), factory, eq, op); - createOffsetFromNode(((NumericalAdditionNode) node).getRightOperand(), factory, eq, op); - } else if (node instanceof NumericalSubtractionNode) { - createOffsetFromNode(((NumericalSubtractionNode) node).getLeftOperand(), factory, eq, op); - char other = op == '+' ? '-' : '+'; - createOffsetFromNode( - ((NumericalSubtractionNode) node).getRightOperand(), factory, eq, other); - } else { - eq.error = node.toString(); - } - } else { - eq.addTerm(op, je.toString()); + + /** + * Updates an offset equation from a Node. + * + * @param node the Node from which to create an offset equation + * @param factory an AnnotationTypeFactory + * @param eq an OffsetEquation to update + * @param op '+' or '-' + */ + private static void createOffsetFromNode( + Node node, AnnotationProvider factory, OffsetEquation eq, char op) { + JavaExpression je = JavaExpression.fromNode(node); + if (je instanceof Unknown || je == null) { + if (node instanceof NumericalAdditionNode) { + createOffsetFromNode( + ((NumericalAdditionNode) node).getLeftOperand(), factory, eq, op); + createOffsetFromNode( + ((NumericalAdditionNode) node).getRightOperand(), factory, eq, op); + } else if (node instanceof NumericalSubtractionNode) { + createOffsetFromNode( + ((NumericalSubtractionNode) node).getLeftOperand(), factory, eq, op); + char other = op == '+' ? '-' : '+'; + createOffsetFromNode( + ((NumericalSubtractionNode) node).getRightOperand(), factory, eq, other); + } else { + eq.error = node.toString(); + } + } else { + eq.addTerm(op, je.toString()); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java index b7dc890e584..2e99a826fc2 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UBQualifier.java @@ -1,15 +1,5 @@ package org.checkerframework.checker.index.upperbound; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.index.qual.LTEqLengthOf; import org.checkerframework.checker.index.qual.LTLengthOf; import org.checkerframework.checker.index.qual.LTOMLengthOf; @@ -25,6 +15,18 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; + /** * Abstraction for Upper Bound annotations. This abstract class has 4 subclasses, each of which is a * nested class: {@link LessThanLengthOf}, {@link UpperBoundUnknownQualifier}, {@code @@ -36,1463 +38,1483 @@ */ public abstract class UBQualifier { - /** - * Create a UBQualifier from the given annotation. - * - * @param am the annotation to turn into a UBQualifier - * @param ubChecker used to obtain the fields of {@code am} - * @return a UBQualifier that represents the same information as the given annotation - */ - public static UBQualifier createUBQualifier(AnnotationMirror am, UpperBoundChecker ubChecker) { - return createUBQualifier(am, null, ubChecker); - } - - /** - * Create a UBQualifier from the given annotation, with an extra offset. - * - * @param am the annotation to turn into a UBQualifier - * @param offset the extra offset; may be null - * @param ubChecker used to obtain the fields of {@code am} - * @return a UBQualifier that represents the same information as the given annotation (plus an - * optional offset) - */ - public static UBQualifier createUBQualifier( - AnnotationMirror am, @Nullable String offset, UpperBoundChecker ubChecker) { - switch (AnnotationUtils.annotationName(am)) { - case "org.checkerframework.checker.index.qual.UpperBoundUnknown": - return UpperBoundUnknownQualifier.UNKNOWN; - case "org.checkerframework.checker.index.qual.UpperBoundBottom": - return UpperBoundBottomQualifier.BOTTOM; - case "org.checkerframework.checker.index.qual.UpperBoundLiteral": - int intValue = - AnnotationUtils.getElementValueInt(am, ubChecker.upperBoundLiteralValueElement); - return UpperBoundLiteralQualifier.create(intValue); - case "org.checkerframework.checker.index.qual.LTLengthOf": - return parseLTLengthOf(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.SubstringIndexFor": - return parseSubstringIndexFor(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.LTEqLengthOf": - return parseLTEqLengthOf(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.LTOMLengthOf": - return parseLTOMLengthOf(am, offset, ubChecker); - case "org.checkerframework.checker.index.qual.PolyUpperBound": - // TODO: Ignores offset. Should we check that offset is not set? - return PolyQualifier.POLY; - default: - throw new TypeSystemError("createUBQualifier(%s, %s, ...)", am, offset); - } - } - - /** A cache for the {@link #nCopiesEmptyStringCache} method. */ - private static final List> nCopiesEmptyStringCache = new ArrayList<>(10); - - static { - nCopiesEmptyStringCache.add(Collections.emptyList()); - nCopiesEmptyStringCache.add(Collections.singletonList("")); - nCopiesEmptyStringCache.add(Collections.nCopies(2, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(3, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(4, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(5, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(6, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(7, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(8, "")); - nCopiesEmptyStringCache.add(Collections.nCopies(9, "")); - } - - /** - * Equivalent to {@code Collections.nCopies(n, "")}. - * - * @param n the length of the list - * @return an immutable list of {@code n} copies of {@code ""} - */ - private static List nCopiesEmptyString(int n) { - if (n < 10) { - return nCopiesEmptyStringCache.get(n); - } else { - return Collections.nCopies(n, ""); - } - } - - /** - * Create a UBQualifier from a @LTLengthOf annotation. - * - * @param ltLengthOfAnno a @LTLengthOf annotation - * @param extraOffset the extra offset - * @param ubChecker used to obtain the fields of {@code am} - * @return a UBQualifier created from the @LTLengthOf annotation - */ - private static UBQualifier parseLTLengthOf( - AnnotationMirror ltLengthOfAnno, String extraOffset, UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray( - ltLengthOfAnno, ubChecker.ltLengthOfValueElement, String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of an LTLengthOf annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; - } - List offsets = - AnnotationUtils.getElementValueArray( - ltLengthOfAnno, - ubChecker.ltLengthOfOffsetElement, - String.class, - nCopiesEmptyString(sequences.size())); - return createUBQualifier(sequences, offsets, extraOffset); - } - - /** - * Create a UBQualifier from a @SubstringIndexFor annotation. - * - * @param substringIndexForAnno a @SubstringIndexFor annotation - * @param extraOffset the extra offset - * @param ubChecker used for obtaining arguments/elements from {@code substringIndexForAnno} - * @return a UBQualifier created from the @SubstringIndexFor annotation - */ - private static UBQualifier parseSubstringIndexFor( - AnnotationMirror substringIndexForAnno, String extraOffset, UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray( - substringIndexForAnno, ubChecker.substringIndexForValueElement, String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of a SubstringIndexFor annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; - } - List offsets = - AnnotationUtils.getElementValueArray( - substringIndexForAnno, ubChecker.substringIndexForOffsetElement, String.class); - if (offsets.isEmpty()) { - offsets = nCopiesEmptyString(sequences.size()); - } - return createUBQualifier(sequences, offsets, extraOffset); - } - - /** - * Create a UBQualifier from a @LTEqLengthOf annotation. - * - * @param am a @LTEqLengthOf annotation - * @param extraOffset the extra offset - * @param ubChecker used for obtaining fields from {@code am} - * @return a UBQualifier created from the @LTEqLengthOf annotation - */ - private static UBQualifier parseLTEqLengthOf( - AnnotationMirror am, String extraOffset, UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray(am, ubChecker.ltEqLengthOfValueElement, String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of an LTEqLengthOf annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; - } - List offset = Collections.nCopies(sequences.size(), "-1"); - return createUBQualifier(sequences, offset, extraOffset); - } - - /** - * Create a UBQualifier from a @LTOMLengthOf annotation. - * - * @param am a @LTOMLengthOf annotation - * @param extraOffset offset to add to each element of offsets; may be null - * @param ubChecker used for obtaining fields from {@code am} - * @return a UBQualifier created from the @LTOMLengthOf annotation - */ - private static UBQualifier parseLTOMLengthOf( - AnnotationMirror am, @Nullable String extraOffset, UpperBoundChecker ubChecker) { - List sequences = - AnnotationUtils.getElementValueArray(am, ubChecker.ltOMLengthOfValueElement, String.class); - if (sequences.isEmpty()) { - // These annotations can be created by delocalization of an LTOMLengthOf annotation - // that only contains local variables at a call site. - return UpperBoundUnknownQualifier.UNKNOWN; - } - List offset = Collections.nCopies(sequences.size(), "1"); - return createUBQualifier(sequences, offset, extraOffset); - } - - public static UBQualifier createUBQualifier(String sequence, String offset) { - return createUBQualifier( - Collections.singletonList(sequence), Collections.singletonList(offset)); - } - - /** - * Create an upper bound qualifier. - * - * @param type the type from which to obtain an annotation - * @param top the top annotation in a hierarchy; the annotation in this hierarchy will be used - * @param ubChecker used to obtain the fields of {@code am} - * @return a new upper bound qualifier - */ - public static UBQualifier createUBQualifier( - AnnotatedTypeMirror type, AnnotationMirror top, UpperBoundChecker ubChecker) { - return createUBQualifier(type.getEffectiveAnnotationInHierarchy(top), ubChecker); - } - - /** - * Creates an {@link UBQualifier} from the given sequences and offsets. The list of sequences may - * not be empty. If the offsets list is empty, then an offset of 0 is used for each sequence. If - * the offsets list is not empty, then it must be the same size as sequence. - * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @return an {@link UBQualifier} for the sequences with the given offsets - */ - public static UBQualifier createUBQualifier(List sequences, List offsets) { - return createUBQualifier(sequences, offsets, null); - } - - /** - * Creates an {@link UBQualifier} from the given sequences and offsets, with the given additional - * offset. The list of sequences may not be empty. If the offsets list is empty, then an offset of - * 0 is used for each sequence. If the offsets list is not empty, then it must be the same size as - * sequence. - * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @param extraOffset offset to add to each element of offsets; may be null - * @return an {@link UBQualifier} for the sequences with the given offsets - */ - public static UBQualifier createUBQualifier( - List sequences, List offsets, @Nullable String extraOffset) { - assert !sequences.isEmpty(); - - OffsetEquation extraEq; - if (extraOffset == null) { - extraEq = OffsetEquation.ZERO; - } else { - extraEq = OffsetEquation.createOffsetFromJavaExpression(extraOffset); - if (extraEq.hasError()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - } - - return new LessThanLengthOf(sequences, offsets, extraEq); - } - - /** - * Add the node as an offset to a copy of this qualifier. If this qualifier is UNKNOWN or BOTTOM, - * then UNKNOWN is returned. Otherwise, see {@link LessThanLengthOf#plusOffset(int)} for an - * explanation of how node is added as an offset. - * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @return a copy of this qualifier with node added as an offset - */ - public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - - public UBQualifier plusOffset(int value) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - - public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - - public UBQualifier minusOffset(int value) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - - public boolean isLessThanLengthQualifier() { - return false; - } - - /** - * Returns true if this UBQualifier represents a literal integer. - * - * @return true if this UBQualifier represents a literal integer - */ - public boolean isLiteral() { - return false; - } - - /** - * Returns true if this UBQualifier is the top type. - * - * @return true if this UBQualifier is the top type - */ - public boolean isUnknown() { - return false; - } - - /** - * Returns true if this UBQualifier is the bottom type. - * - * @return true if this UBQualifier is the bottom type - */ - public boolean isBottom() { - return false; - } - - /** - * Return true if this is UBQualifier.PolyQualifier. - * - * @return true if this is UBQualifier.PolyQualifier - */ - @Pure - public boolean isPoly() { - return false; - } - - public abstract boolean isSubtype(UBQualifier superType); - - public abstract UBQualifier lub(UBQualifier other); - - public UBQualifier widenUpperBound(UBQualifier obj) { - return lub(obj); - } - - public abstract UBQualifier glb(UBQualifier other); - - /** - * Is the value with this qualifier less than the length of sequence? - * - * @param sequence a String sequence - * @return whether or not the value with this qualifier is less than the length of sequence - */ - public boolean isLessThanLengthOf(String sequence) { - return false; - } - - /** - * Is the value with this qualifier less than the length of any of the sequences? - * - * @param sequences list of sequences - * @return whether or not the value with this qualifier is less than the length of any of the - * sequences - */ - public boolean isLessThanLengthOfAny(List sequences) { - return false; - } - - /** - * Returns whether or not this qualifier has sequence with the specified offset. - * - * @param sequence sequence expression - * @param offset the offset being looked for - * @return whether or not this qualifier has sequence with the specified offset - */ - public boolean hasSequenceWithOffset(String sequence, int offset) { - return false; - } - - /** - * Returns whether or not this qualifier has sequence with the specified offset. - * - * @param sequence sequence expression - * @param offset the offset being looked for - * @return whether or not this qualifier has sequence with the specified offset - */ - public boolean hasSequenceWithOffset(String sequence, String offset) { - return false; - } - - /** - * Is the value with this qualifier less than or equal to the length of sequence? - * - * @param sequence a String sequence - * @return whether or not the value with this qualifier is less than or equal to the length of - * sequence - */ - public boolean isLessThanOrEqualTo(String sequence) { - return false; - } - - /** The less-than-length-of qualifier (@LTLengthOf). */ - public static class LessThanLengthOf extends UBQualifier { - - // There are two representations for sequences and offsets. - // In source code, they are represented by two parallel arrays, as in - // @LTLengthOf(value = {"a", "b", "a", "c"}, offset = {"-1", "x", "y", "0"}). - // In this implementation, they are represented by a single map; the above would be - // { "a" : {"-1", "y"}, "b" : {"x"}, "c" : {"0"} } - // Code in this class transforms from one representation to the other. - - /** Maps from sequence name to offset. */ - private final Map> map; - /** - * Returns a copy of the map. + * Create a UBQualifier from the given annotation. * - * @return a copy of the map + * @param am the annotation to turn into a UBQualifier + * @param ubChecker used to obtain the fields of {@code am} + * @return a UBQualifier that represents the same information as the given annotation */ - private Map> copyMap() { - Map> result = new HashMap<>(CollectionsPlume.mapCapacity(map)); - for (String sequenceName : map.keySet()) { - Set oldEquations = map.get(sequenceName); - Set newEquations = - new HashSet<>(CollectionsPlume.mapCapacity(oldEquations)); - for (OffsetEquation offsetEquation : oldEquations) { - newEquations.add(new OffsetEquation(offsetEquation)); - } - result.put(sequenceName, newEquations); - } - return result; + public static UBQualifier createUBQualifier(AnnotationMirror am, UpperBoundChecker ubChecker) { + return createUBQualifier(am, null, ubChecker); } /** - * Returns true if the given integer literal is a subtype of this. The literal is a subtype of - * this if, for every offset expression, {@code literal + offset <= -1}. + * Create a UBQualifier from the given annotation, with an extra offset. * - * @param i an integer - * @return true if the given integer literal is a subtype of this + * @param am the annotation to turn into a UBQualifier + * @param offset the extra offset; may be null + * @param ubChecker used to obtain the fields of {@code am} + * @return a UBQualifier that represents the same information as the given annotation (plus an + * optional offset) */ - /*package-private*/ boolean literalIsSubtype(int i) { - for (Map.Entry> entry : map.entrySet()) { - for (OffsetEquation equation : entry.getValue()) { - if (!equation.isInt()) { - return false; - } - int offset = equation.getInt(); - if (i + offset > -1) { - return false; - } + public static UBQualifier createUBQualifier( + AnnotationMirror am, @Nullable String offset, UpperBoundChecker ubChecker) { + switch (AnnotationUtils.annotationName(am)) { + case "org.checkerframework.checker.index.qual.UpperBoundUnknown": + return UpperBoundUnknownQualifier.UNKNOWN; + case "org.checkerframework.checker.index.qual.UpperBoundBottom": + return UpperBoundBottomQualifier.BOTTOM; + case "org.checkerframework.checker.index.qual.UpperBoundLiteral": + int intValue = + AnnotationUtils.getElementValueInt( + am, ubChecker.upperBoundLiteralValueElement); + return UpperBoundLiteralQualifier.create(intValue); + case "org.checkerframework.checker.index.qual.LTLengthOf": + return parseLTLengthOf(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.SubstringIndexFor": + return parseSubstringIndexFor(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.LTEqLengthOf": + return parseLTEqLengthOf(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.LTOMLengthOf": + return parseLTOMLengthOf(am, offset, ubChecker); + case "org.checkerframework.checker.index.qual.PolyUpperBound": + // TODO: Ignores offset. Should we check that offset is not set? + return PolyQualifier.POLY; + default: + throw new TypeSystemError("createUBQualifier(%s, %s, ...)", am, offset); } - } - return true; } - /** - * Convert the parallel array representation to the map representation. - * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @param extraEq offset to add to each element of offsets; may be null - * @return the map representation of a {@link UBQualifier}, or null if there is an error - */ - private static @Nullable Map> sequencesAndOffsetsToMap( - List sequences, List offsets, @Nullable OffsetEquation extraEq) { - - Map> map = new HashMap<>(CollectionsPlume.mapCapacity(sequences)); - if (offsets.isEmpty()) { - for (String sequence : sequences) { - // Not `Collections.singleton(extraEq)` because the values get modified - Set thisSet = new HashSet<>(1); - thisSet.add(extraEq); - map.put(sequence, thisSet); - } - } else { - assert sequences.size() == offsets.size(); - for (int i = 0; i < sequences.size(); i++) { - String sequence = sequences.get(i); - String offset = offsets.get(i); - Set set = map.computeIfAbsent(sequence, __ -> new HashSet<>()); - OffsetEquation eq = OffsetEquation.createOffsetFromJavaExpression(offset); - if (eq.hasError()) { - return null; - } - eq = eq.copyAdd('+', extraEq); - set.add(eq); - } - } - return map; - } + /** A cache for the {@link #nCopiesEmptyStringCache} method. */ + private static final List> nCopiesEmptyStringCache = new ArrayList<>(10); - /** A triple that is the return type of {@link #mapToSequencesAndOffsets}. */ - private static class SequencesOffsetsAndClass { - /** List of sequences. */ - public final List sequences; - - /** List of offsets. */ - public final List offsets; - - /** The class of the annotation to be built. */ - public final Class annoClass; - - /** - * Creates a new SequencesOffsetsAndClass. - * - * @param sequences list of sequences - * @param offsets list of offsets - * @param annoClass the class of the annotation to be built - */ - public SequencesOffsetsAndClass( - List sequences, List offsets, Class annoClass) { - - this.sequences = sequences; - this.offsets = offsets; - this.annoClass = annoClass; - } + static { + nCopiesEmptyStringCache.add(Collections.emptyList()); + nCopiesEmptyStringCache.add(Collections.singletonList("")); + nCopiesEmptyStringCache.add(Collections.nCopies(2, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(3, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(4, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(5, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(6, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(7, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(8, "")); + nCopiesEmptyStringCache.add(Collections.nCopies(9, "")); } /** - * Given the map representation, returns parallel-arrays representation. + * Equivalent to {@code Collections.nCopies(n, "")}. * - * @param map the internal representation of LessThanLengthOf - * @param buildSubstringIndexAnnotation if true, the annoClass in the result is - * ubstringIndexFor.class - * @return the external representation + * @param n the length of the list + * @return an immutable list of {@code n} copies of {@code ""} */ - private static SequencesOffsetsAndClass mapToSequencesAndOffsets( - Map> map, boolean buildSubstringIndexAnnotation) { - List<@KeyFor("map") String> sortedSequences = new ArrayList<>(map.keySet()); - Collections.sort(sortedSequences); - List sequences = new ArrayList<>(); - List offsets = new ArrayList<>(); - boolean isLTEq = true; - boolean isLTOM = true; - for (String sequence : sortedSequences) { - // The offsets for this sequence. - List thisOffsets = new ArrayList<>(); - for (OffsetEquation eq : map.get(sequence)) { - isLTEq = isLTEq && eq.equals(OffsetEquation.NEG_1); - isLTOM = isLTOM && eq.equals(OffsetEquation.ONE); - thisOffsets.add(eq.toString()); - } - Collections.sort(thisOffsets); - for (String offset : thisOffsets) { - sequences.add(sequence); - offsets.add(offset); - } - } - Class annoClass; - if (buildSubstringIndexAnnotation) { - annoClass = SubstringIndexFor.class; - } else if (isLTEq) { - annoClass = LTEqLengthOf.class; - } else if (isLTOM) { - annoClass = LTOMLengthOf.class; - } else { - annoClass = LTLengthOf.class; - } - return new SequencesOffsetsAndClass(sequences, offsets, annoClass); + private static List nCopiesEmptyString(int n) { + if (n < 10) { + return nCopiesEmptyStringCache.get(n); + } else { + return Collections.nCopies(n, ""); + } } - // End of code for manipulating the representation - /** - * Create a new LessThanLengthOf, from the internal representation. + * Create a UBQualifier from a @LTLengthOf annotation. * - * @param map a map from sequence name to offse + * @param ltLengthOfAnno a @LTLengthOf annotation + * @param extraOffset the extra offset + * @param ubChecker used to obtain the fields of {@code am} + * @return a UBQualifier created from the @LTLengthOf annotation */ - private LessThanLengthOf(Map> map) { - assert !map.isEmpty(); - this.map = map; + private static UBQualifier parseLTLengthOf( + AnnotationMirror ltLengthOfAnno, String extraOffset, UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray( + ltLengthOfAnno, ubChecker.ltLengthOfValueElement, String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of an LTLengthOf annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; + } + List offsets = + AnnotationUtils.getElementValueArray( + ltLengthOfAnno, + ubChecker.ltLengthOfOffsetElement, + String.class, + nCopiesEmptyString(sequences.size())); + return createUBQualifier(sequences, offsets, extraOffset); } /** - * Create a new LessThanLengthOf from the parallel array representation. + * Create a UBQualifier from a @SubstringIndexFor annotation. * - * @param sequences non-empty list of sequences - * @param offsets list of offset, if empty, an offset of 0 is used - * @param extraEq offset to add to each element of offsets; may be null + * @param substringIndexForAnno a @SubstringIndexFor annotation + * @param extraOffset the extra offset + * @param ubChecker used for obtaining arguments/elements from {@code substringIndexForAnno} + * @return a UBQualifier created from the @SubstringIndexFor annotation */ - private LessThanLengthOf( - List sequences, List offsets, @Nullable OffsetEquation extraEq) { - this(sequencesAndOffsetsToMap(sequences, offsets, extraEq)); - } - - @Override - public boolean hasSequenceWithOffset(String sequence, int offset) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - return offsets.contains(OffsetEquation.createOffsetForInt(offset)); - } - - @Override - public boolean hasSequenceWithOffset(String sequence, String offset) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - OffsetEquation target = OffsetEquation.createOffsetFromJavaExpression(offset); - return offsets.contains(target); + private static UBQualifier parseSubstringIndexFor( + AnnotationMirror substringIndexForAnno, + String extraOffset, + UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray( + substringIndexForAnno, + ubChecker.substringIndexForValueElement, + String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of a SubstringIndexFor annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; + } + List offsets = + AnnotationUtils.getElementValueArray( + substringIndexForAnno, + ubChecker.substringIndexForOffsetElement, + String.class); + if (offsets.isEmpty()) { + offsets = nCopiesEmptyString(sequences.size()); + } + return createUBQualifier(sequences, offsets, extraOffset); } /** - * Is a value with this type less than or equal to the length of sequence? + * Create a UBQualifier from a @LTEqLengthOf annotation. * - * @param sequence a String sequence - * @return true if a value with this type is less than or equal to the length of sequence + * @param am a @LTEqLengthOf annotation + * @param extraOffset the extra offset + * @param ubChecker used for obtaining fields from {@code am} + * @return a UBQualifier created from the @LTEqLengthOf annotation */ - @Override - public boolean isLessThanOrEqualTo(String sequence) { - return isLessThanLengthOf(sequence) || hasSequenceWithOffset(sequence, -1); + private static UBQualifier parseLTEqLengthOf( + AnnotationMirror am, String extraOffset, UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray( + am, ubChecker.ltEqLengthOfValueElement, String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of an LTEqLengthOf annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; + } + List offset = Collections.nCopies(sequences.size(), "-1"); + return createUBQualifier(sequences, offset, extraOffset); } /** - * Is a value with this type less than the length of any of the sequences? + * Create a UBQualifier from a @LTOMLengthOf annotation. * - * @param sequences list of sequences - * @return true if a value with this type is less than the length of any of the sequences + * @param am a @LTOMLengthOf annotation + * @param extraOffset offset to add to each element of offsets; may be null + * @param ubChecker used for obtaining fields from {@code am} + * @return a UBQualifier created from the @LTOMLengthOf annotation */ - @Override - public boolean isLessThanLengthOfAny(List sequences) { - for (String sequence : sequences) { - if (isLessThanLengthOf(sequence)) { - return true; + private static UBQualifier parseLTOMLengthOf( + AnnotationMirror am, @Nullable String extraOffset, UpperBoundChecker ubChecker) { + List sequences = + AnnotationUtils.getElementValueArray( + am, ubChecker.ltOMLengthOfValueElement, String.class); + if (sequences.isEmpty()) { + // These annotations can be created by delocalization of an LTOMLengthOf annotation + // that only contains local variables at a call site. + return UpperBoundUnknownQualifier.UNKNOWN; } - } - return false; + List offset = Collections.nCopies(sequences.size(), "1"); + return createUBQualifier(sequences, offset, extraOffset); + } + + public static UBQualifier createUBQualifier(String sequence, String offset) { + return createUBQualifier( + Collections.singletonList(sequence), Collections.singletonList(offset)); } /** - * Is a value with this type less than the length of the sequence? + * Create an upper bound qualifier. * - * @param sequence a String sequence - * @return true if a value with this type is less than the length of the sequence + * @param type the type from which to obtain an annotation + * @param top the top annotation in a hierarchy; the annotation in this hierarchy will be used + * @param ubChecker used to obtain the fields of {@code am} + * @return a new upper bound qualifier */ - @Override - public boolean isLessThanLengthOf(String sequence) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - if (offsets.isEmpty()) { - return true; - } - for (OffsetEquation offset : offsets) { - if (offset.isNonNegative()) { - return true; - } - } - return false; + public static UBQualifier createUBQualifier( + AnnotatedTypeMirror type, AnnotationMirror top, UpperBoundChecker ubChecker) { + return createUBQualifier(type.getEffectiveAnnotationInHierarchy(top), ubChecker); } /** - * Returns the AnnotationMirror that represents this qualifier. If possible, AnnotationMirrors - * using @{@link LTEqLengthOf} or @{@link LTOMLengthOf} are returned. Otherwise, @{@link - * LTLengthOf} is used. - * - *

          The returned annotation is canonicalized by sorting its arguments by sequence and then - * offset. This is so that {@link AnnotationUtils#areSame(AnnotationMirror, AnnotationMirror)} - * returns true for equivalent annotations. + * Creates an {@link UBQualifier} from the given sequences and offsets. The list of sequences + * may not be empty. If the offsets list is empty, then an offset of 0 is used for each + * sequence. If the offsets list is not empty, then it must be the same size as sequence. * - * @param env a processing environment used to build the returned annotation - * @return the AnnotationMirror that represents this qualifier + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @return an {@link UBQualifier} for the sequences with the given offsets */ - public AnnotationMirror convertToAnnotation(ProcessingEnvironment env) { - return convertToAnnotation(env, false); + public static UBQualifier createUBQualifier(List sequences, List offsets) { + return createUBQualifier(sequences, offsets, null); } /** - * Returns the @{@link SubstringIndexFor} AnnotationMirror from the Substring Index hierarchy - * that imposes the same upper bounds on the annotated expression as this qualifier. However, - * the upper bounds represented by this qualifier do not apply to the value -1 which is always - * allowed by the returned annotation. + * Creates an {@link UBQualifier} from the given sequences and offsets, with the given + * additional offset. The list of sequences may not be empty. If the offsets list is empty, then + * an offset of 0 is used for each sequence. If the offsets list is not empty, then it must be + * the same size as sequence. * - * @param env a processing environment used to build the returned annotation - * @return the AnnotationMirror from the Substring Index hierarchy that represents the same - * upper bounds as this qualifier + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @param extraOffset offset to add to each element of offsets; may be null + * @return an {@link UBQualifier} for the sequences with the given offsets */ - public AnnotationMirror convertToSubstringIndexAnnotation(ProcessingEnvironment env) { - return convertToAnnotation(env, true); + public static UBQualifier createUBQualifier( + List sequences, List offsets, @Nullable String extraOffset) { + assert !sequences.isEmpty(); + + OffsetEquation extraEq; + if (extraOffset == null) { + extraEq = OffsetEquation.ZERO; + } else { + extraEq = OffsetEquation.createOffsetFromJavaExpression(extraOffset); + if (extraEq.hasError()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + } + + return new LessThanLengthOf(sequences, offsets, extraEq); } /** - * Helper method called by {@link #convertToAnnotation} and {@link - * convertToSubstringIndexAnnotation} that does the real work. + * Add the node as an offset to a copy of this qualifier. If this qualifier is UNKNOWN or + * BOTTOM, then UNKNOWN is returned. Otherwise, see {@link LessThanLengthOf#plusOffset(int)} for + * an explanation of how node is added as an offset. * - * @param env a processing environment used to build the returned annotation - * @param buildSubstringIndexAnnotation if true, act like {@link - * #convertToSubstringIndexAnnotation} and return a @{@link SubstringIndexFor} annotation; - * if false, act like {@link #convertToAnnotation} - * @return the AnnotationMirror that represents the same upper bounds as this qualifier + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @return a copy of this qualifier with node added as an offset */ - private AnnotationMirror convertToAnnotation( - ProcessingEnvironment env, boolean buildSubstringIndexAnnotation) { - SequencesOffsetsAndClass soc = mapToSequencesAndOffsets(map, buildSubstringIndexAnnotation); - List sequences = soc.sequences; - List offsets = soc.offsets; - Class annoClass = soc.annoClass; - - AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); - if (annoClass == SubstringIndexFor.class) { - builder.setValue("value", sequences); - builder.setValue("offset", offsets); - } else if (annoClass == LTEqLengthOf.class) { - builder.setValue("value", sequences); - } else if (annoClass == LTOMLengthOf.class) { - builder.setValue("value", sequences); - } else if (annoClass == LTLengthOf.class) { - builder.setValue("value", sequences); - builder.setValue("offset", offsets); - } else { - throw new TypeSystemError("What annoClass? " + annoClass); - } - return builder.build(); + public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return UpperBoundUnknownQualifier.UNKNOWN; } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - LessThanLengthOf qualifier = (LessThanLengthOf) o; - if (containsSame(map.keySet(), qualifier.map.keySet())) { - for (Map.Entry> entry : map.entrySet()) { - Set otherOffset = qualifier.map.get(entry.getKey()); - Set thisOffset = entry.getValue(); - if (!containsSame(otherOffset, thisOffset)) { - return false; - } - } - return true; - } - return false; + public UBQualifier plusOffset(int value) { + return UpperBoundUnknownQualifier.UNKNOWN; } - private static boolean containsSame(Set set1, Set set2) { - return set1.containsAll(set2) && set2.containsAll(set1); + public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return UpperBoundUnknownQualifier.UNKNOWN; } - @Override - public int hashCode() { - return map.hashCode(); + public UBQualifier minusOffset(int value) { + return UpperBoundUnknownQualifier.UNKNOWN; } - @Override public boolean isLessThanLengthQualifier() { - return true; + return false; } /** - * If superType is Unknown, return true. If superType is Bottom, return false. - * - *

          Otherwise, return true if this qualifier contains all the sequences in superType, AND for - * each of the offsets for each sequence in superType, there is an offset in this qualifier for - * the sequence that is greater than or equal to the super offset. + * Returns true if this UBQualifier represents a literal integer. * - * @param superType other qualifier - * @return whether this qualifier is a subtype of superType + * @return true if this UBQualifier represents a literal integer */ - @Override - public boolean isSubtype(UBQualifier superType) { - if (superType.isUnknown()) { - return true; - } else if (superType.isBottom()) { - return false; - } else if (superType.isLiteral()) { - return false; - } - - LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; - - if (!map.keySet().containsAll(superTypeLTL.map.keySet())) { + public boolean isLiteral() { return false; - } - for (Map.Entry> entry : superTypeLTL.map.entrySet()) { - String sequence = entry.getKey(); - Set superOffsets = entry.getValue(); - Set subOffsets = map.get(sequence); - - if (!isSubtypeOffset(subOffsets, superOffsets)) { - return false; - } - } - - return true; } /** - * One set of offsets is a subtype of another if for every superOffsets, at least one suboffset - * is greater than or equal to the superOffset. + * Returns true if this UBQualifier is the top type. + * + * @return true if this UBQualifier is the top type */ - private boolean isSubtypeOffset( - Set subOffsets, Set superOffsets) { - for (OffsetEquation superOffset : superOffsets) { - boolean oneIsSubtype = false; - for (OffsetEquation subOffset : subOffsets) { - if (superOffset.lessThanOrEqual(subOffset)) { - oneIsSubtype = true; - break; - } - } - if (!oneIsSubtype) { - return false; - } - } - return true; + public boolean isUnknown() { + return false; } /** - * If other is Unknown, return Unknown. If other is Bottom, return this. - * - *

          Otherwise lub is computed as follows: - * - *

          1. Create the intersection of the sets of arrays for this and other. - * - *

          2. For each sequence in the intersection, get the offsets for this and other. If any - * offset in this is a less than or equal to an offset in other, then that offset is an offset - * for the sequence in lub. If any offset in other is a less than or equal to an offset in this, - * then that offset is an offset for the sequence in lub. + * Returns true if this UBQualifier is the bottom type. * - * @param other to lub with this - * @return the lub + * @return true if this UBQualifier is the bottom type */ - @Override - public UBQualifier lub(UBQualifier other) { - if (other.isUnknown()) { - return other; - } else if (other.isBottom()) { - return this; - } else if (other.isLiteral()) { - return other.lub(this); - } - LessThanLengthOf otherLtl = (LessThanLengthOf) other; - - Set sequences = new HashSet<>(map.keySet()); - sequences.retainAll(otherLtl.map.keySet()); - - Map> lubMap = - new HashMap<>(CollectionsPlume.mapCapacity(sequences)); - for (String sequence : sequences) { - Set offsets1 = map.get(sequence); - Set offsets2 = otherLtl.map.get(sequence); - Set lub = new HashSet<>(offsets1.size() + offsets2.size()); - for (OffsetEquation offset1 : offsets1) { - for (OffsetEquation offset2 : offsets2) { - if (offset2.lessThanOrEqual(offset1)) { - lub.add(offset2); - } else if (offset1.lessThanOrEqual(offset2)) { - lub.add(offset1); - } - } - } - if (!lub.isEmpty()) { - lubMap.put(sequence, lub); - } - } - if (lubMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - return new LessThanLengthOf(lubMap); - } - - @Override - public UBQualifier widenUpperBound(UBQualifier obj) { - UBQualifier lub = lub(obj); - if (!lub.isLessThanLengthQualifier() || !obj.isLessThanLengthQualifier()) { - return lub; - } - Map> lubMap = ((LessThanLengthOf) lub).map; - widenLub((LessThanLengthOf) obj, lubMap); - if (lubMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - return new LessThanLengthOf(lubMap); + public boolean isBottom() { + return false; } /** + * Return true if this is UBQualifier.PolyQualifier. * - * - *

          @LTLengthOf("a") int i = ...;
          -     * while (expr) {
          -     *   i++;
          -     * }
          - * - *

          Dataflow never stops analyzing the above loop, because the type of i always changes after - * each analysis of the loop: - * - *

          1. @LTLengthOf(value="a', offset="-1") - * - *

          2. @LTLengthOf(value="a', offset="-2") - * - *

          3. @LTLengthOf(value="a', offset="-3") - * - *

          In order to prevent this, if both types passed to lub include all the same sequences with - * the same non-constant value offsets and if the constant value offsets are different then - * remove that sequence-offset pair from lub. - * - *

          For example: - * - *

          LUB @LTLengthOf(value={"a", "b"}, offset={"0", "0") and @LTLengthOf(value={"a", "b"}, - * offset={"-20", "0") is @LTLengthOf("b") - * - *

          This widened lub should only be used in order to break dataflow analysis loops. + * @return true if this is UBQualifier.PolyQualifier */ - private void widenLub(LessThanLengthOf other, Map> lubMap) { - if (!containsSame(this.map.keySet(), lubMap.keySet()) - || !containsSame(other.map.keySet(), lubMap.keySet())) { - return; - } - List> remove = new ArrayList<>(); - for (Map.Entry> entry : lubMap.entrySet()) { - String sequence = entry.getKey(); - Set lubOffsets = entry.getValue(); - Set thisOffsets = this.map.get(sequence); - Set otherOffsets = other.map.get(sequence); - if (lubOffsets.size() != thisOffsets.size() || lubOffsets.size() != otherOffsets.size()) { - return; - } - for (OffsetEquation lubEq : lubOffsets) { - if (lubEq.isInt()) { - int thisInt = OffsetEquation.getIntOffsetEquation(thisOffsets).getInt(); - int otherInt = OffsetEquation.getIntOffsetEquation(otherOffsets).getInt(); - if (thisInt != otherInt) { - remove.add(IPair.of(sequence, lubEq)); - } - } else if (thisOffsets.contains(lubEq) && otherOffsets.contains(lubEq)) { - // continue; - } else { - return; - } - } - } - for (IPair pair : remove) { - String sequence = pair.first; - Set offsets = lubMap.get(sequence); - offsets.remove(pair.second); - if (offsets.isEmpty()) { - lubMap.remove(sequence); - } - } + @Pure + public boolean isPoly() { + return false; } - @Override - public UBQualifier glb(UBQualifier other) { - if (other.isUnknown()) { - return this; - } else if (other.isBottom()) { - return other; - } else if (other.isLiteral()) { - return other.glb(this); - } - LessThanLengthOf otherLtl = (LessThanLengthOf) other; - - Set sequences = new HashSet<>(map.keySet()); - sequences.addAll(otherLtl.map.keySet()); - - Map> glbMap = new HashMap<>(sequences.size()); - for (String sequence : sequences) { - Set glb = map.get(sequence); - Set otherglb = otherLtl.map.get(sequence); - if (glb == null) { - glb = otherglb; - } else if (otherglb != null) { - glb.addAll(otherglb); - } - glbMap.put(sequence, removeSmallerInts(glb)); - } - return new LessThanLengthOf(glbMap); - } + public abstract boolean isSubtype(UBQualifier superType); - /** - * Returns a copy of the argument, but it contains just one offset equation that is an int value - * -- the largest one in the argument. Any non-int offset equations appear in the result. Does - * not side effect its argument. - * - * @param offsets a set of offset equations - * @return a copy of the argument with just one int value (the largest in the input) and - * arbitrarily many non-ints - */ - private Set removeSmallerInts(Set offsets) { - Set newOff = new HashSet<>(offsets.size()); - OffsetEquation literal = null; - for (OffsetEquation eq : offsets) { - if (eq.isInt()) { - if (literal == null) { - literal = eq; - } else { - literal = literal.lessThanOrEqual(eq) ? eq : literal; - } - } else { - newOff.add(eq); - } - } - if (literal != null) { - newOff.add(literal); - } - return newOff; - } + public abstract UBQualifier lub(UBQualifier other); - /** - * Adds node as an offset to a copy of this qualifier. This is done by creating an offset - * equation for node and then adding that equation to every offset equation in a copy of this - * object. - * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @return a copy of this qualifier with node add as an offset - */ - @Override - public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return plusOrMinusOffset(node, factory, '+'); + public UBQualifier widenUpperBound(UBQualifier obj) { + return lub(obj); } + public abstract UBQualifier glb(UBQualifier other); + /** - * Adds node as a negative offset to a copy of this qualifier. This is done by creating a - * negative offset equation for node and then adding that equation to every offset equation in a - * copy of this object. + * Is the value with this qualifier less than the length of sequence? * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @return a copy of this qualifier with node add as an offset + * @param sequence a String sequence + * @return whether or not the value with this qualifier is less than the length of sequence */ - @Override - public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { - return plusOrMinusOffset(node, factory, '-'); + public boolean isLessThanLengthOf(String sequence) { + return false; } /** - * Adds node as a positive or negative offset to a copy of this qualifier. This is done by - * creating an offset equation for node and then adding or subtracting that equation to every - * offset equation in a copy of this object. + * Is the value with this qualifier less than the length of any of the sequences? * - * @param node a Node - * @param factory an AnnotatedTypeFactory - * @param op either '-' or '+' - * @return a copy of this qualifier with node add as an offset + * @param sequences list of sequences + * @return whether or not the value with this qualifier is less than the length of any of the + * sequences */ - private UBQualifier plusOrMinusOffset( - Node node, UpperBoundAnnotatedTypeFactory factory, char op) { - assert op == '-' || op == '+'; - - // Try treating the offset as both an OffsetEquation and as a value. - // Use whichever is not null, or glb the two. - - OffsetEquation newOffset = OffsetEquation.createOffsetFromNode(node, factory, op); - LessThanLengthOf nodeOffsetQualifier = null; - if (!newOffset.hasError()) { - UBQualifier nodeOffsetQualifierMaybe = addOffset(newOffset); - if (!(nodeOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { - nodeOffsetQualifier = (LessThanLengthOf) nodeOffsetQualifierMaybe; - } - } - - OffsetEquation valueOffset = - OffsetEquation.createOffsetFromNodesValue( - node, factory.getValueAnnotatedTypeFactory(), op); - LessThanLengthOf valueOffsetQualifier = null; - if (valueOffset != null && !valueOffset.hasError()) { - UBQualifier valueOffsetQualifierMaybe = addOffset(valueOffset); - if (!(valueOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { - valueOffsetQualifier = (LessThanLengthOf) valueOffsetQualifierMaybe; - } - } - - if (valueOffsetQualifier == null) { - if (nodeOffsetQualifier == null) { - return UpperBoundUnknownQualifier.UNKNOWN; - } else { - return nodeOffsetQualifier; - } - } else { - if (nodeOffsetQualifier == null) { - return valueOffsetQualifier; - } else { - return nodeOffsetQualifier.glb(valueOffsetQualifier); - } - } + public boolean isLessThanLengthOfAny(List sequences) { + return false; } /** - * Adds value as an offset to a copy of this qualifier. This is done by adding value to every - * offset equation in a copy of this object. + * Returns whether or not this qualifier has sequence with the specified offset. * - * @param value int value to add - * @return a copy of this qualifier with value add as an offset + * @param sequence sequence expression + * @param offset the offset being looked for + * @return whether or not this qualifier has sequence with the specified offset */ - @Override - public UBQualifier plusOffset(int value) { - OffsetEquation newOffset = OffsetEquation.createOffsetForInt(value); - return addOffset(newOffset); + public boolean hasSequenceWithOffset(String sequence, int offset) { + return false; } /** - * Adds the negation of value as an offset to a copy of this qualifier. This is done by adding - * the negation of {@code value} to every offset equation in a copy of this object. + * Returns whether or not this qualifier has sequence with the specified offset. * - * @param value int value to add - * @return a copy of this qualifier with value add as an offset + * @param sequence sequence expression + * @param offset the offset being looked for + * @return whether or not this qualifier has sequence with the specified offset */ - @Override - public UBQualifier minusOffset(int value) { - OffsetEquation newOffset = OffsetEquation.createOffsetForInt(-value); - return addOffset(newOffset); + public boolean hasSequenceWithOffset(String sequence, String offset) { + return false; } /** - * Returns a copy of this qualifier with sequence-offset pairs where in the original the offset - * contains an access of an sequence length in {@code sequences}. The sequence length access has - * been removed from the offset. If the original qualifier has no sequence length offsets, then - * UNKNOWN is returned. + * Is the value with this qualifier less than or equal to the length of sequence? * - * @param sequences access of the length of these sequences are removed - * @return a copy of this qualifier with some offsets removed + * @param sequence a String sequence + * @return whether or not the value with this qualifier is less than or equal to the length of + * sequence */ - public UBQualifier removeSequenceLengthAccess(List sequences) { - if (sequences.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - OffsetEquationFunction removeSequenceLengthsFunc = eq -> eq.removeSequenceLengths(sequences); - return computeNewOffsets(removeSequenceLengthsFunc); + public boolean isLessThanOrEqualTo(String sequence) { + return false; } - /** - * Returns a copy of this qualifier with sequence-offset pairs where in the original the offset - * contains an access of an sequence length in {@code sequences}. The sequence length access has - * been removed from the offset. If the offset also has -1 then -1 is also removed. - * - * @param sequences access of the length of these sequences are removed - * @return a copy of this qualifier with some offsets removed - */ - public UBQualifier removeSequenceLengthAccessAndNeg1(List sequences) { - if (sequences.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - OffsetEquationFunction removeSequenceLenFunc = - eq -> { - OffsetEquation newEq = eq.removeSequenceLengths(sequences); - if (newEq == null) { - return null; + /** The less-than-length-of qualifier (@LTLengthOf). */ + public static class LessThanLengthOf extends UBQualifier { + + // There are two representations for sequences and offsets. + // In source code, they are represented by two parallel arrays, as in + // @LTLengthOf(value = {"a", "b", "a", "c"}, offset = {"-1", "x", "y", "0"}). + // In this implementation, they are represented by a single map; the above would be + // { "a" : {"-1", "y"}, "b" : {"x"}, "c" : {"0"} } + // Code in this class transforms from one representation to the other. + + /** Maps from sequence name to offset. */ + private final Map> map; + + /** + * Returns a copy of the map. + * + * @return a copy of the map + */ + private Map> copyMap() { + Map> result = + new HashMap<>(CollectionsPlume.mapCapacity(map)); + for (String sequenceName : map.keySet()) { + Set oldEquations = map.get(sequenceName); + Set newEquations = + new HashSet<>(CollectionsPlume.mapCapacity(oldEquations)); + for (OffsetEquation offsetEquation : oldEquations) { + newEquations.add(new OffsetEquation(offsetEquation)); + } + result.put(sequenceName, newEquations); } - if (newEq.getInt() == -1) { - return newEq.copyAdd('+', OffsetEquation.ONE); + return result; + } + + /** + * Returns true if the given integer literal is a subtype of this. The literal is a subtype + * of this if, for every offset expression, {@code literal + offset <= -1}. + * + * @param i an integer + * @return true if the given integer literal is a subtype of this + */ + /*package-private*/ boolean literalIsSubtype(int i) { + for (Map.Entry> entry : map.entrySet()) { + for (OffsetEquation equation : entry.getValue()) { + if (!equation.isInt()) { + return false; + } + int offset = equation.getInt(); + if (i + offset > -1) { + return false; + } + } } - return newEq; - }; - return computeNewOffsets(removeSequenceLenFunc); - } + return true; + } - /** - * Returns a new qualifier, which is this qualifier plus the given offset. - * - * @param newOffset the offset to add to this - * @return a new qualifier, which is this qualifier plus the given offset - */ - private UBQualifier addOffset(OffsetEquation newOffset) { - OffsetEquationFunction addOffsetFunc = eq -> eq.copyAdd('+', newOffset); - return computeNewOffsets(addOffsetFunc); - } + /** + * Convert the parallel array representation to the map representation. + * + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @param extraEq offset to add to each element of offsets; may be null + * @return the map representation of a {@link UBQualifier}, or null if there is an error + */ + private static @Nullable Map> sequencesAndOffsetsToMap( + List sequences, List offsets, @Nullable OffsetEquation extraEq) { + + Map> map = + new HashMap<>(CollectionsPlume.mapCapacity(sequences)); + if (offsets.isEmpty()) { + for (String sequence : sequences) { + // Not `Collections.singleton(extraEq)` because the values get modified + Set thisSet = new HashSet<>(1); + thisSet.add(extraEq); + map.put(sequence, thisSet); + } + } else { + assert sequences.size() == offsets.size(); + for (int i = 0; i < sequences.size(); i++) { + String sequence = sequences.get(i); + String offset = offsets.get(i); + Set set = map.computeIfAbsent(sequence, __ -> new HashSet<>()); + OffsetEquation eq = OffsetEquation.createOffsetFromJavaExpression(offset); + if (eq.hasError()) { + return null; + } + eq = eq.copyAdd('+', extraEq); + set.add(eq); + } + } + return map; + } - /** - * If divisor == 1, return this object. - * - *

          If divisor greater than 1, then return a copy of this object keeping only sequences and - * offsets where the offset is less than or equal to zero. - * - *

          Otherwise, return UNKNOWN. - * - * @param divisor number to divide by - * @return the result of dividing a value with this qualifier by divisor - */ - public UBQualifier divide(int divisor) { - if (divisor == 1) { - return this; - } else if (divisor > 1) { - OffsetEquationFunction divideFunc = eq -> (eq.isNegativeOrZero() ? eq : null); - return computeNewOffsets(divideFunc); - } - return UpperBoundUnknownQualifier.UNKNOWN; - } + /** A triple that is the return type of {@link #mapToSequencesAndOffsets}. */ + private static class SequencesOffsetsAndClass { + /** List of sequences. */ + public final List sequences; + + /** List of offsets. */ + public final List offsets; + + /** The class of the annotation to be built. */ + public final Class annoClass; + + /** + * Creates a new SequencesOffsetsAndClass. + * + * @param sequences list of sequences + * @param offsets list of offsets + * @param annoClass the class of the annotation to be built + */ + public SequencesOffsetsAndClass( + List sequences, + List offsets, + Class annoClass) { + + this.sequences = sequences; + this.offsets = offsets; + this.annoClass = annoClass; + } + } - public boolean isValuePlusOffsetLessThanMinLen(String sequence, long value, int minlen) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - for (OffsetEquation offset : offsets) { - if (offset.isInt()) { - // This expression must not overflow - return (long) minlen - offset.getInt() > value; - } - } - return false; - } + /** + * Given the map representation, returns parallel-arrays representation. + * + * @param map the internal representation of LessThanLengthOf + * @param buildSubstringIndexAnnotation if true, the annoClass in the result is + * ubstringIndexFor.class + * @return the external representation + */ + private static SequencesOffsetsAndClass mapToSequencesAndOffsets( + Map> map, boolean buildSubstringIndexAnnotation) { + List<@KeyFor("map") String> sortedSequences = new ArrayList<>(map.keySet()); + Collections.sort(sortedSequences); + List sequences = new ArrayList<>(); + List offsets = new ArrayList<>(); + boolean isLTEq = true; + boolean isLTOM = true; + for (String sequence : sortedSequences) { + // The offsets for this sequence. + List thisOffsets = new ArrayList<>(); + for (OffsetEquation eq : map.get(sequence)) { + isLTEq = isLTEq && eq.equals(OffsetEquation.NEG_1); + isLTOM = isLTOM && eq.equals(OffsetEquation.ONE); + thisOffsets.add(eq.toString()); + } + Collections.sort(thisOffsets); + for (String offset : thisOffsets) { + sequences.add(sequence); + offsets.add(offset); + } + } + Class annoClass; + if (buildSubstringIndexAnnotation) { + annoClass = SubstringIndexFor.class; + } else if (isLTEq) { + annoClass = LTEqLengthOf.class; + } else if (isLTOM) { + annoClass = LTOMLengthOf.class; + } else { + annoClass = LTLengthOf.class; + } + return new SequencesOffsetsAndClass(sequences, offsets, annoClass); + } - /** - * Checks whether replacing sequence with replacementSequence in this qualifier creates - * replacementSequence entry in other. - */ - public boolean isValidReplacement( - String sequence, String replacementSequence, LessThanLengthOf other) { - Set offsets = map.get(sequence); - if (offsets == null) { - return false; - } - Set otherOffsets = other.map.get(replacementSequence); - if (otherOffsets == null) { - return false; - } - return containsSame(offsets, otherOffsets); - } + // End of code for manipulating the representation - @Override - public String toString() { - return "LessThanLengthOf{" + "map=" + map + '}'; - } + /** + * Create a new LessThanLengthOf, from the internal representation. + * + * @param map a map from sequence name to offse + */ + private LessThanLengthOf(Map> map) { + assert !map.isEmpty(); + this.map = map; + } - public Iterable getSequences() { - return map.keySet(); - } + /** + * Create a new LessThanLengthOf from the parallel array representation. + * + * @param sequences non-empty list of sequences + * @param offsets list of offset, if empty, an offset of 0 is used + * @param extraEq offset to add to each element of offsets; may be null + */ + private LessThanLengthOf( + List sequences, List offsets, @Nullable OffsetEquation extraEq) { + this(sequencesAndOffsetsToMap(sequences, offsets, extraEq)); + } - /** - * Generates a new UBQualifer without the given (sequence, offset) pair. Other occurrences of - * the sequence and the offset may remain in the result, but not together. - * - * @param sequence a Java expression representing a string - * @param offset an integral offset - * @return a new UBQualifer without the given sequence and offset - */ - public UBQualifier removeOffset(String sequence, int offset) { - OffsetEquation offsetEq = OffsetEquation.createOffsetForInt(offset); - Map> newMap = copyMap(); - Set equations = newMap.get(sequence); - if (equations != null) { - equations.remove(offsetEq); - if (equations.isEmpty()) { - newMap.remove(sequence); - } - } - - if (newMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } else { - return new LessThanLengthOf(newMap); - } - } + @Override + public boolean hasSequenceWithOffset(String sequence, int offset) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + return offsets.contains(OffsetEquation.createOffsetForInt(offset)); + } - /** Functional interface that operates on {@link OffsetEquation}s. */ - private interface OffsetEquationFunction { - /** - * Returns the result of the computation or null if the passed equation should be removed. - * - * @param eq current offset equation - * @return the result of the computation or null if the passed equation should be removed - */ - @Nullable OffsetEquation compute(OffsetEquation eq); - } + @Override + public boolean hasSequenceWithOffset(String sequence, String offset) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + OffsetEquation target = OffsetEquation.createOffsetFromJavaExpression(offset); + return offsets.contains(target); + } - /** - * Returns a new qualifier that is a copy of this qualifier with the OffsetEquationFunction - * applied to each offset. - * - *

          If the {@link OffsetEquationFunction} returns null, it's not added as an offset. If after - * all functions have been applied, an sequence has no offsets, then that sequence is not added - * to the returned qualifier. If no sequences are added to the returned qualifier, then UNKNOWN - * is returned. - * - * @param f function to apply - * @return a new qualifier that is a copy of this qualifier with the OffsetEquationFunction - * applied to each offset - */ - private UBQualifier computeNewOffsets(OffsetEquationFunction f) { - Map> newMap = new HashMap<>(map.size()); - for (Map.Entry> entry : map.entrySet()) { - Set offsets = new HashSet<>(entry.getValue().size()); - for (OffsetEquation eq : entry.getValue()) { - OffsetEquation newEq = f.compute(eq); - if (newEq != null) { - offsets.add(newEq); - } - } - if (!offsets.isEmpty()) { - newMap.put(entry.getKey(), offsets); - } - } - if (newMap.isEmpty()) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - return new LessThanLengthOf(newMap); - } - } + /** + * Is a value with this type less than or equal to the length of sequence? + * + * @param sequence a String sequence + * @return true if a value with this type is less than or equal to the length of sequence + */ + @Override + public boolean isLessThanOrEqualTo(String sequence) { + return isLessThanLengthOf(sequence) || hasSequenceWithOffset(sequence, -1); + } - /** Represents an integer value that is known at compile time. */ - public static class UpperBoundLiteralQualifier extends UBQualifier { + /** + * Is a value with this type less than the length of any of the sequences? + * + * @param sequences list of sequences + * @return true if a value with this type is less than the length of any of the sequences + */ + @Override + public boolean isLessThanLengthOfAny(List sequences) { + for (String sequence : sequences) { + if (isLessThanLengthOf(sequence)) { + return true; + } + } + return false; + } - /** Represents the value -1. */ - public static final UpperBoundLiteralQualifier NEGATIVEONE = new UpperBoundLiteralQualifier(-1); + /** + * Is a value with this type less than the length of the sequence? + * + * @param sequence a String sequence + * @return true if a value with this type is less than the length of the sequence + */ + @Override + public boolean isLessThanLengthOf(String sequence) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + if (offsets.isEmpty()) { + return true; + } + for (OffsetEquation offset : offsets) { + if (offset.isNonNegative()) { + return true; + } + } + return false; + } - /** Represents the value 0. */ - public static final UpperBoundLiteralQualifier ZERO = new UpperBoundLiteralQualifier(0); + /** + * Returns the AnnotationMirror that represents this qualifier. If possible, + * AnnotationMirrors using @{@link LTEqLengthOf} or @{@link LTOMLengthOf} are returned. + * Otherwise, @{@link LTLengthOf} is used. + * + *

          The returned annotation is canonicalized by sorting its arguments by sequence and then + * offset. This is so that {@link AnnotationUtils#areSame(AnnotationMirror, + * AnnotationMirror)} returns true for equivalent annotations. + * + * @param env a processing environment used to build the returned annotation + * @return the AnnotationMirror that represents this qualifier + */ + public AnnotationMirror convertToAnnotation(ProcessingEnvironment env) { + return convertToAnnotation(env, false); + } - /** Represents the value 1. */ - public static final UpperBoundLiteralQualifier ONE = new UpperBoundLiteralQualifier(1); + /** + * Returns the @{@link SubstringIndexFor} AnnotationMirror from the Substring Index + * hierarchy that imposes the same upper bounds on the annotated expression as this + * qualifier. However, the upper bounds represented by this qualifier do not apply to the + * value -1 which is always allowed by the returned annotation. + * + * @param env a processing environment used to build the returned annotation + * @return the AnnotationMirror from the Substring Index hierarchy that represents the same + * upper bounds as this qualifier + */ + public AnnotationMirror convertToSubstringIndexAnnotation(ProcessingEnvironment env) { + return convertToAnnotation(env, true); + } - /** - * Creates a new UpperBoundLiteralQualifier, without using cached values. - * - * @param value the integer value - */ - private UpperBoundLiteralQualifier(int value) { - this.value = value; - } + /** + * Helper method called by {@link #convertToAnnotation} and {@link + * convertToSubstringIndexAnnotation} that does the real work. + * + * @param env a processing environment used to build the returned annotation + * @param buildSubstringIndexAnnotation if true, act like {@link + * #convertToSubstringIndexAnnotation} and return a @{@link SubstringIndexFor} + * annotation; if false, act like {@link #convertToAnnotation} + * @return the AnnotationMirror that represents the same upper bounds as this qualifier + */ + private AnnotationMirror convertToAnnotation( + ProcessingEnvironment env, boolean buildSubstringIndexAnnotation) { + SequencesOffsetsAndClass soc = + mapToSequencesAndOffsets(map, buildSubstringIndexAnnotation); + List sequences = soc.sequences; + List offsets = soc.offsets; + Class annoClass = soc.annoClass; + + AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); + if (annoClass == SubstringIndexFor.class) { + builder.setValue("value", sequences); + builder.setValue("offset", offsets); + } else if (annoClass == LTEqLengthOf.class) { + builder.setValue("value", sequences); + } else if (annoClass == LTOMLengthOf.class) { + builder.setValue("value", sequences); + } else if (annoClass == LTLengthOf.class) { + builder.setValue("value", sequences); + builder.setValue("offset", offsets); + } else { + throw new TypeSystemError("What annoClass? " + annoClass); + } + return builder.build(); + } - /** - * Creates an UpperBoundLiteralQualifier. - * - * @param value the integer value - * @return an UpperBoundLiteralQualifier - */ - public static UpperBoundLiteralQualifier create(int value) { - switch (value) { - case -1: - return NEGATIVEONE; - case 0: - return ZERO; - case 1: - return ONE; - default: - return new UpperBoundLiteralQualifier(value); - } - } + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - /** The integer value. */ - private final int value; + LessThanLengthOf qualifier = (LessThanLengthOf) o; + if (containsSame(map.keySet(), qualifier.map.keySet())) { + for (Map.Entry> entry : map.entrySet()) { + Set otherOffset = qualifier.map.get(entry.getKey()); + Set thisOffset = entry.getValue(); + if (!containsSame(otherOffset, thisOffset)) { + return false; + } + } + return true; + } + return false; + } - /** - * Returns the integer value. - * - * @return the integer value - */ - public int getValue() { - return value; - } + private static boolean containsSame(Set set1, Set set2) { + return set1.containsAll(set2) && set2.containsAll(set1); + } - @Override - public boolean isLiteral() { - return true; - } + @Override + public int hashCode() { + return map.hashCode(); + } - @Override - public boolean isSubtype(UBQualifier superType) { - if (superType.isUnknown()) { - return true; - } else if (superType.isBottom()) { - return false; - } else if (superType.isPoly()) { - return false; - } else if (superType.isLiteral()) { - int otherValue = ((UpperBoundLiteralQualifier) superType).value; - return value == otherValue; - } + @Override + public boolean isLessThanLengthQualifier() { + return true; + } - LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; - return superTypeLTL.literalIsSubtype(value); - } + /** + * If superType is Unknown, return true. If superType is Bottom, return false. + * + *

          Otherwise, return true if this qualifier contains all the sequences in superType, AND + * for each of the offsets for each sequence in superType, there is an offset in this + * qualifier for the sequence that is greater than or equal to the super offset. + * + * @param superType other qualifier + * @return whether this qualifier is a subtype of superType + */ + @Override + public boolean isSubtype(UBQualifier superType) { + if (superType.isUnknown()) { + return true; + } else if (superType.isBottom()) { + return false; + } else if (superType.isLiteral()) { + return false; + } - @Override - public UBQualifier lub(UBQualifier other) { - if (isSubtype(other)) { - return other; - } else { - return UpperBoundUnknownQualifier.UNKNOWN; - } - } + LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; - @Override - public UBQualifier glb(UBQualifier other) { - if (isSubtype(other)) { - return this; - } else { - return UpperBoundBottomQualifier.BOTTOM; - } - } + if (!map.keySet().containsAll(superTypeLTL.map.keySet())) { + return false; + } + for (Map.Entry> entry : superTypeLTL.map.entrySet()) { + String sequence = entry.getKey(); + Set superOffsets = entry.getValue(); + Set subOffsets = map.get(sequence); + + if (!isSubtypeOffset(subOffsets, superOffsets)) { + return false; + } + } - @Override - public String toString() { - return "Literal(" + value + ")"; - } - } + return true; + } - /** The top type qualifier. */ - public static class UpperBoundUnknownQualifier extends UBQualifier { - /** The canonical representative. */ - public static final UBQualifier UNKNOWN = new UpperBoundUnknownQualifier(); + /** + * One set of offsets is a subtype of another if for every superOffsets, at least one + * suboffset is greater than or equal to the superOffset. + */ + private boolean isSubtypeOffset( + Set subOffsets, Set superOffsets) { + for (OffsetEquation superOffset : superOffsets) { + boolean oneIsSubtype = false; + for (OffsetEquation subOffset : subOffsets) { + if (superOffset.lessThanOrEqual(subOffset)) { + oneIsSubtype = true; + break; + } + } + if (!oneIsSubtype) { + return false; + } + } + return true; + } - /** This class is a singleton. */ - private UpperBoundUnknownQualifier() {} + /** + * If other is Unknown, return Unknown. If other is Bottom, return this. + * + *

          Otherwise lub is computed as follows: + * + *

          1. Create the intersection of the sets of arrays for this and other. + * + *

          2. For each sequence in the intersection, get the offsets for this and other. If any + * offset in this is a less than or equal to an offset in other, then that offset is an + * offset for the sequence in lub. If any offset in other is a less than or equal to an + * offset in this, then that offset is an offset for the sequence in lub. + * + * @param other to lub with this + * @return the lub + */ + @Override + public UBQualifier lub(UBQualifier other) { + if (other.isUnknown()) { + return other; + } else if (other.isBottom()) { + return this; + } else if (other.isLiteral()) { + return other.lub(this); + } + LessThanLengthOf otherLtl = (LessThanLengthOf) other; + + Set sequences = new HashSet<>(map.keySet()); + sequences.retainAll(otherLtl.map.keySet()); + + Map> lubMap = + new HashMap<>(CollectionsPlume.mapCapacity(sequences)); + for (String sequence : sequences) { + Set offsets1 = map.get(sequence); + Set offsets2 = otherLtl.map.get(sequence); + Set lub = new HashSet<>(offsets1.size() + offsets2.size()); + for (OffsetEquation offset1 : offsets1) { + for (OffsetEquation offset2 : offsets2) { + if (offset2.lessThanOrEqual(offset1)) { + lub.add(offset2); + } else if (offset1.lessThanOrEqual(offset2)) { + lub.add(offset1); + } + } + } + if (!lub.isEmpty()) { + lubMap.put(sequence, lub); + } + } + if (lubMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return new LessThanLengthOf(lubMap); + } - @Override - public boolean isSubtype(UBQualifier superType) { - return superType.isUnknown(); - } + @Override + public UBQualifier widenUpperBound(UBQualifier obj) { + UBQualifier lub = lub(obj); + if (!lub.isLessThanLengthQualifier() || !obj.isLessThanLengthQualifier()) { + return lub; + } + Map> lubMap = ((LessThanLengthOf) lub).map; + widenLub((LessThanLengthOf) obj, lubMap); + if (lubMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return new LessThanLengthOf(lubMap); + } - @Override - public boolean isUnknown() { - return true; - } + /** + * + * + *

          @LTLengthOf("a") int i = ...;
          +         * while (expr) {
          +         *   i++;
          +         * }
          + * + *

          Dataflow never stops analyzing the above loop, because the type of i always changes + * after each analysis of the loop: + * + *

          1. @LTLengthOf(value="a', offset="-1") + * + *

          2. @LTLengthOf(value="a', offset="-2") + * + *

          3. @LTLengthOf(value="a', offset="-3") + * + *

          In order to prevent this, if both types passed to lub include all the same sequences + * with the same non-constant value offsets and if the constant value offsets are different + * then remove that sequence-offset pair from lub. + * + *

          For example: + * + *

          LUB @LTLengthOf(value={"a", "b"}, offset={"0", "0") and @LTLengthOf(value={"a", "b"}, + * offset={"-20", "0") is @LTLengthOf("b") + * + *

          This widened lub should only be used in order to break dataflow analysis loops. + */ + private void widenLub(LessThanLengthOf other, Map> lubMap) { + if (!containsSame(this.map.keySet(), lubMap.keySet()) + || !containsSame(other.map.keySet(), lubMap.keySet())) { + return; + } + List> remove = new ArrayList<>(); + for (Map.Entry> entry : lubMap.entrySet()) { + String sequence = entry.getKey(); + Set lubOffsets = entry.getValue(); + Set thisOffsets = this.map.get(sequence); + Set otherOffsets = other.map.get(sequence); + if (lubOffsets.size() != thisOffsets.size() + || lubOffsets.size() != otherOffsets.size()) { + return; + } + for (OffsetEquation lubEq : lubOffsets) { + if (lubEq.isInt()) { + int thisInt = OffsetEquation.getIntOffsetEquation(thisOffsets).getInt(); + int otherInt = OffsetEquation.getIntOffsetEquation(otherOffsets).getInt(); + if (thisInt != otherInt) { + remove.add(IPair.of(sequence, lubEq)); + } + } else if (thisOffsets.contains(lubEq) && otherOffsets.contains(lubEq)) { + // continue; + } else { + return; + } + } + } + for (IPair pair : remove) { + String sequence = pair.first; + Set offsets = lubMap.get(sequence); + offsets.remove(pair.second); + if (offsets.isEmpty()) { + lubMap.remove(sequence); + } + } + } - @Override - public UBQualifier lub(UBQualifier other) { - return this; - } + @Override + public UBQualifier glb(UBQualifier other) { + if (other.isUnknown()) { + return this; + } else if (other.isBottom()) { + return other; + } else if (other.isLiteral()) { + return other.glb(this); + } + LessThanLengthOf otherLtl = (LessThanLengthOf) other; + + Set sequences = new HashSet<>(map.keySet()); + sequences.addAll(otherLtl.map.keySet()); + + Map> glbMap = new HashMap<>(sequences.size()); + for (String sequence : sequences) { + Set glb = map.get(sequence); + Set otherglb = otherLtl.map.get(sequence); + if (glb == null) { + glb = otherglb; + } else if (otherglb != null) { + glb.addAll(otherglb); + } + glbMap.put(sequence, removeSmallerInts(glb)); + } + return new LessThanLengthOf(glbMap); + } - @Override - public UBQualifier glb(UBQualifier other) { - return other; - } + /** + * Returns a copy of the argument, but it contains just one offset equation that is an int + * value -- the largest one in the argument. Any non-int offset equations appear in the + * result. Does not side effect its argument. + * + * @param offsets a set of offset equations + * @return a copy of the argument with just one int value (the largest in the input) and + * arbitrarily many non-ints + */ + private Set removeSmallerInts(Set offsets) { + Set newOff = new HashSet<>(offsets.size()); + OffsetEquation literal = null; + for (OffsetEquation eq : offsets) { + if (eq.isInt()) { + if (literal == null) { + literal = eq; + } else { + literal = literal.lessThanOrEqual(eq) ? eq : literal; + } + } else { + newOff.add(eq); + } + } + if (literal != null) { + newOff.add(literal); + } + return newOff; + } - @Override - public String toString() { - return "UNKNOWN"; - } - } + /** + * Adds node as an offset to a copy of this qualifier. This is done by creating an offset + * equation for node and then adding that equation to every offset equation in a copy of + * this object. + * + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @return a copy of this qualifier with node add as an offset + */ + @Override + public UBQualifier plusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return plusOrMinusOffset(node, factory, '+'); + } - /** The bottom qualifier for the upperbound type system. */ - private static class UpperBoundBottomQualifier extends UBQualifier { - /** The canonical bottom qualifier for the upperbound type system. */ - public static final UBQualifier BOTTOM = new UpperBoundBottomQualifier(); + /** + * Adds node as a negative offset to a copy of this qualifier. This is done by creating a + * negative offset equation for node and then adding that equation to every offset equation + * in a copy of this object. + * + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @return a copy of this qualifier with node add as an offset + */ + @Override + public UBQualifier minusOffset(Node node, UpperBoundAnnotatedTypeFactory factory) { + return plusOrMinusOffset(node, factory, '-'); + } - /** This class is a singleton. */ - private UpperBoundBottomQualifier() {} + /** + * Adds node as a positive or negative offset to a copy of this qualifier. This is done by + * creating an offset equation for node and then adding or subtracting that equation to + * every offset equation in a copy of this object. + * + * @param node a Node + * @param factory an AnnotatedTypeFactory + * @param op either '-' or '+' + * @return a copy of this qualifier with node add as an offset + */ + private UBQualifier plusOrMinusOffset( + Node node, UpperBoundAnnotatedTypeFactory factory, char op) { + assert op == '-' || op == '+'; + + // Try treating the offset as both an OffsetEquation and as a value. + // Use whichever is not null, or glb the two. + + OffsetEquation newOffset = OffsetEquation.createOffsetFromNode(node, factory, op); + LessThanLengthOf nodeOffsetQualifier = null; + if (!newOffset.hasError()) { + UBQualifier nodeOffsetQualifierMaybe = addOffset(newOffset); + if (!(nodeOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { + nodeOffsetQualifier = (LessThanLengthOf) nodeOffsetQualifierMaybe; + } + } - @Override - public boolean isBottom() { - return true; - } + OffsetEquation valueOffset = + OffsetEquation.createOffsetFromNodesValue( + node, factory.getValueAnnotatedTypeFactory(), op); + LessThanLengthOf valueOffsetQualifier = null; + if (valueOffset != null && !valueOffset.hasError()) { + UBQualifier valueOffsetQualifierMaybe = addOffset(valueOffset); + if (!(valueOffsetQualifierMaybe instanceof UpperBoundUnknownQualifier)) { + valueOffsetQualifier = (LessThanLengthOf) valueOffsetQualifierMaybe; + } + } - @Override - public boolean isSubtype(UBQualifier superType) { - return true; - } + if (valueOffsetQualifier == null) { + if (nodeOffsetQualifier == null) { + return UpperBoundUnknownQualifier.UNKNOWN; + } else { + return nodeOffsetQualifier; + } + } else { + if (nodeOffsetQualifier == null) { + return valueOffsetQualifier; + } else { + return nodeOffsetQualifier.glb(valueOffsetQualifier); + } + } + } - @Override - public UBQualifier lub(UBQualifier other) { - return other; - } + /** + * Adds value as an offset to a copy of this qualifier. This is done by adding value to + * every offset equation in a copy of this object. + * + * @param value int value to add + * @return a copy of this qualifier with value add as an offset + */ + @Override + public UBQualifier plusOffset(int value) { + OffsetEquation newOffset = OffsetEquation.createOffsetForInt(value); + return addOffset(newOffset); + } - @Override - public UBQualifier glb(UBQualifier other) { - return this; - } + /** + * Adds the negation of value as an offset to a copy of this qualifier. This is done by + * adding the negation of {@code value} to every offset equation in a copy of this object. + * + * @param value int value to add + * @return a copy of this qualifier with value add as an offset + */ + @Override + public UBQualifier minusOffset(int value) { + OffsetEquation newOffset = OffsetEquation.createOffsetForInt(-value); + return addOffset(newOffset); + } + + /** + * Returns a copy of this qualifier with sequence-offset pairs where in the original the + * offset contains an access of an sequence length in {@code sequences}. The sequence length + * access has been removed from the offset. If the original qualifier has no sequence length + * offsets, then UNKNOWN is returned. + * + * @param sequences access of the length of these sequences are removed + * @return a copy of this qualifier with some offsets removed + */ + public UBQualifier removeSequenceLengthAccess(List sequences) { + if (sequences.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + OffsetEquationFunction removeSequenceLengthsFunc = + eq -> eq.removeSequenceLengths(sequences); + return computeNewOffsets(removeSequenceLengthsFunc); + } + + /** + * Returns a copy of this qualifier with sequence-offset pairs where in the original the + * offset contains an access of an sequence length in {@code sequences}. The sequence length + * access has been removed from the offset. If the offset also has -1 then -1 is also + * removed. + * + * @param sequences access of the length of these sequences are removed + * @return a copy of this qualifier with some offsets removed + */ + public UBQualifier removeSequenceLengthAccessAndNeg1(List sequences) { + if (sequences.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + OffsetEquationFunction removeSequenceLenFunc = + eq -> { + OffsetEquation newEq = eq.removeSequenceLengths(sequences); + if (newEq == null) { + return null; + } + if (newEq.getInt() == -1) { + return newEq.copyAdd('+', OffsetEquation.ONE); + } + return newEq; + }; + return computeNewOffsets(removeSequenceLenFunc); + } + + /** + * Returns a new qualifier, which is this qualifier plus the given offset. + * + * @param newOffset the offset to add to this + * @return a new qualifier, which is this qualifier plus the given offset + */ + private UBQualifier addOffset(OffsetEquation newOffset) { + OffsetEquationFunction addOffsetFunc = eq -> eq.copyAdd('+', newOffset); + return computeNewOffsets(addOffsetFunc); + } + + /** + * If divisor == 1, return this object. + * + *

          If divisor greater than 1, then return a copy of this object keeping only sequences + * and offsets where the offset is less than or equal to zero. + * + *

          Otherwise, return UNKNOWN. + * + * @param divisor number to divide by + * @return the result of dividing a value with this qualifier by divisor + */ + public UBQualifier divide(int divisor) { + if (divisor == 1) { + return this; + } else if (divisor > 1) { + OffsetEquationFunction divideFunc = eq -> (eq.isNegativeOrZero() ? eq : null); + return computeNewOffsets(divideFunc); + } + return UpperBoundUnknownQualifier.UNKNOWN; + } + + public boolean isValuePlusOffsetLessThanMinLen(String sequence, long value, int minlen) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + for (OffsetEquation offset : offsets) { + if (offset.isInt()) { + // This expression must not overflow + return (long) minlen - offset.getInt() > value; + } + } + return false; + } + + /** + * Checks whether replacing sequence with replacementSequence in this qualifier creates + * replacementSequence entry in other. + */ + public boolean isValidReplacement( + String sequence, String replacementSequence, LessThanLengthOf other) { + Set offsets = map.get(sequence); + if (offsets == null) { + return false; + } + Set otherOffsets = other.map.get(replacementSequence); + if (otherOffsets == null) { + return false; + } + return containsSame(offsets, otherOffsets); + } + + @Override + public String toString() { + return "LessThanLengthOf{" + "map=" + map + '}'; + } + + public Iterable getSequences() { + return map.keySet(); + } - @Override - public String toString() { - return "BOTTOM"; + /** + * Generates a new UBQualifer without the given (sequence, offset) pair. Other occurrences + * of the sequence and the offset may remain in the result, but not together. + * + * @param sequence a Java expression representing a string + * @param offset an integral offset + * @return a new UBQualifer without the given sequence and offset + */ + public UBQualifier removeOffset(String sequence, int offset) { + OffsetEquation offsetEq = OffsetEquation.createOffsetForInt(offset); + Map> newMap = copyMap(); + Set equations = newMap.get(sequence); + if (equations != null) { + equations.remove(offsetEq); + if (equations.isEmpty()) { + newMap.remove(sequence); + } + } + + if (newMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } else { + return new LessThanLengthOf(newMap); + } + } + + /** Functional interface that operates on {@link OffsetEquation}s. */ + private interface OffsetEquationFunction { + /** + * Returns the result of the computation or null if the passed equation should be + * removed. + * + * @param eq current offset equation + * @return the result of the computation or null if the passed equation should be + * removed + */ + @Nullable OffsetEquation compute(OffsetEquation eq); + } + + /** + * Returns a new qualifier that is a copy of this qualifier with the OffsetEquationFunction + * applied to each offset. + * + *

          If the {@link OffsetEquationFunction} returns null, it's not added as an offset. If + * after all functions have been applied, an sequence has no offsets, then that sequence is + * not added to the returned qualifier. If no sequences are added to the returned qualifier, + * then UNKNOWN is returned. + * + * @param f function to apply + * @return a new qualifier that is a copy of this qualifier with the OffsetEquationFunction + * applied to each offset + */ + private UBQualifier computeNewOffsets(OffsetEquationFunction f) { + Map> newMap = new HashMap<>(map.size()); + for (Map.Entry> entry : map.entrySet()) { + Set offsets = new HashSet<>(entry.getValue().size()); + for (OffsetEquation eq : entry.getValue()) { + OffsetEquation newEq = f.compute(eq); + if (newEq != null) { + offsets.add(newEq); + } + } + if (!offsets.isEmpty()) { + newMap.put(entry.getKey(), offsets); + } + } + if (newMap.isEmpty()) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return new LessThanLengthOf(newMap); + } } - } - /** The polymorphic qualifier. */ - private static class PolyQualifier extends UBQualifier { - /** The canonical representative. */ - public static final UBQualifier POLY = new PolyQualifier(); + /** Represents an integer value that is known at compile time. */ + public static class UpperBoundLiteralQualifier extends UBQualifier { - /** This class is a singleton. */ - private PolyQualifier() {} + /** Represents the value -1. */ + public static final UpperBoundLiteralQualifier NEGATIVEONE = + new UpperBoundLiteralQualifier(-1); - @Override - @Pure - public boolean isPoly() { - return true; + /** Represents the value 0. */ + public static final UpperBoundLiteralQualifier ZERO = new UpperBoundLiteralQualifier(0); + + /** Represents the value 1. */ + public static final UpperBoundLiteralQualifier ONE = new UpperBoundLiteralQualifier(1); + + /** + * Creates a new UpperBoundLiteralQualifier, without using cached values. + * + * @param value the integer value + */ + private UpperBoundLiteralQualifier(int value) { + this.value = value; + } + + /** + * Creates an UpperBoundLiteralQualifier. + * + * @param value the integer value + * @return an UpperBoundLiteralQualifier + */ + public static UpperBoundLiteralQualifier create(int value) { + switch (value) { + case -1: + return NEGATIVEONE; + case 0: + return ZERO; + case 1: + return ONE; + default: + return new UpperBoundLiteralQualifier(value); + } + } + + /** The integer value. */ + private final int value; + + /** + * Returns the integer value. + * + * @return the integer value + */ + public int getValue() { + return value; + } + + @Override + public boolean isLiteral() { + return true; + } + + @Override + public boolean isSubtype(UBQualifier superType) { + if (superType.isUnknown()) { + return true; + } else if (superType.isBottom()) { + return false; + } else if (superType.isPoly()) { + return false; + } else if (superType.isLiteral()) { + int otherValue = ((UpperBoundLiteralQualifier) superType).value; + return value == otherValue; + } + + LessThanLengthOf superTypeLTL = (LessThanLengthOf) superType; + return superTypeLTL.literalIsSubtype(value); + } + + @Override + public UBQualifier lub(UBQualifier other) { + if (isSubtype(other)) { + return other; + } else { + return UpperBoundUnknownQualifier.UNKNOWN; + } + } + + @Override + public UBQualifier glb(UBQualifier other) { + if (isSubtype(other)) { + return this; + } else { + return UpperBoundBottomQualifier.BOTTOM; + } + } + + @Override + public String toString() { + return "Literal(" + value + ")"; + } } - @Override - public boolean isSubtype(UBQualifier superType) { - return superType.isUnknown() || superType.isPoly(); + /** The top type qualifier. */ + public static class UpperBoundUnknownQualifier extends UBQualifier { + /** The canonical representative. */ + public static final UBQualifier UNKNOWN = new UpperBoundUnknownQualifier(); + + /** This class is a singleton. */ + private UpperBoundUnknownQualifier() {} + + @Override + public boolean isSubtype(UBQualifier superType) { + return superType.isUnknown(); + } + + @Override + public boolean isUnknown() { + return true; + } + + @Override + public UBQualifier lub(UBQualifier other) { + return this; + } + + @Override + public UBQualifier glb(UBQualifier other) { + return other; + } + + @Override + public String toString() { + return "UNKNOWN"; + } } - @Override - public UBQualifier lub(UBQualifier other) { - if (other.isPoly() || other.isBottom()) { - return this; - } - return UpperBoundUnknownQualifier.UNKNOWN; + /** The bottom qualifier for the upperbound type system. */ + private static class UpperBoundBottomQualifier extends UBQualifier { + /** The canonical bottom qualifier for the upperbound type system. */ + public static final UBQualifier BOTTOM = new UpperBoundBottomQualifier(); + + /** This class is a singleton. */ + private UpperBoundBottomQualifier() {} + + @Override + public boolean isBottom() { + return true; + } + + @Override + public boolean isSubtype(UBQualifier superType) { + return true; + } + + @Override + public UBQualifier lub(UBQualifier other) { + return other; + } + + @Override + public UBQualifier glb(UBQualifier other) { + return this; + } + + @Override + public String toString() { + return "BOTTOM"; + } } - @Override - public UBQualifier glb(UBQualifier other) { - if (other.isPoly() || other.isUnknown()) { - return this; - } - return UpperBoundBottomQualifier.BOTTOM; + /** The polymorphic qualifier. */ + private static class PolyQualifier extends UBQualifier { + /** The canonical representative. */ + public static final UBQualifier POLY = new PolyQualifier(); + + /** This class is a singleton. */ + private PolyQualifier() {} + + @Override + @Pure + public boolean isPoly() { + return true; + } + + @Override + public boolean isSubtype(UBQualifier superType) { + return superType.isUnknown() || superType.isPoly(); + } + + @Override + public UBQualifier lub(UBQualifier other) { + if (other.isPoly() || other.isBottom()) { + return this; + } + return UpperBoundUnknownQualifier.UNKNOWN; + } + + @Override + public UBQualifier glb(UBQualifier other) { + if (other.isPoly() || other.isUnknown()) { + return this; + } + return UpperBoundBottomQualifier.BOTTOM; + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java index 65415bb9b28..5eaba43cee8 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java @@ -8,18 +8,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; import com.sun.source.util.TreePath; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.index.BaseAnnotatedTypeFactoryForIndexChecker; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.checker.index.IndexMethodIdentifier; @@ -82,6 +71,20 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.IPair; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** * Implements the introduction rules for the Upper Bound Checker. * @@ -109,889 +112,925 @@ */ public class UpperBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactoryForIndexChecker { - /** The @{@link UpperBoundUnknown} annotation. */ - public final AnnotationMirror UNKNOWN = - AnnotationBuilder.fromClass(elements, UpperBoundUnknown.class); - - /** The @{@link UpperBoundBottom} annotation. */ - public final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, UpperBoundBottom.class); - - /** The @{@link PolyUpperBound} annotation. */ - public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyUpperBound.class); - - /** The @{@link UpperBoundLiteral}(-1) annotation. */ - public final AnnotationMirror NEGATIVEONE = - new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", -1) - .build(); - - /** The @{@link UpperBoundLiteral}(0) annotation. */ - public final AnnotationMirror ZERO = - new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", 0) - .build(); - - /** The @{@link UpperBoundLiteral}(1) annotation. */ - public final AnnotationMirror ONE = - new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", 1) - .build(); - - /** The NegativeIndexFor.value element/field. */ - public final ExecutableElement negativeIndexForValueElement = - TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); - - /** The SameLen.value element/field. */ - public final ExecutableElement sameLenValueElement = - TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); - - /** The LTLengthOf.value element/field. */ - public final ExecutableElement ltLengthOfValueElement = - TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); - - /** The LTLengthOf.offset element/field. */ - public final ExecutableElement ltLengthOfOffsetElement = - TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); - - /** Predicates about what method an invocation is calling. */ - private final IndexMethodIdentifier imf; - - /** Create a new UpperBoundAnnotatedTypeFactory. */ - public UpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - addAliasedTypeAnnotation(IndexFor.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(IndexOrLow.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(IndexOrHigh.class, LTEqLengthOf.class, true); - addAliasedTypeAnnotation(SearchIndexFor.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(NegativeIndexFor.class, LTLengthOf.class, true); - addAliasedTypeAnnotation(LengthOf.class, LTEqLengthOf.class, true); - addAliasedTypeAnnotation(PolyIndex.class, POLY); - - imf = new IndexMethodIdentifier(this); - - this.postInit(); - } - - /** Gets a helper object that holds references to methods with special handling. */ - IndexMethodIdentifier getMethodIdentifier() { - return imf; - } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - UpperBoundUnknown.class, - LTEqLengthOf.class, - LTLengthOf.class, - LTOMLengthOf.class, - UpperBoundLiteral.class, - UpperBoundBottom.class, - PolyUpperBound.class)); - } - - /** - * Provides a way to query the Constant Value Checker, which computes the values of expressions - * known at compile time (constant propagation and folding). - */ - ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(ValueChecker.class); - } - - /** - * Provides a way to query the Search Index Checker, which helps the Index Checker type the - * results of calling the JDK's binary search methods correctly. - */ - private SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SearchIndexChecker.class); - } - - /** - * Gets the annotated type factory of the Substring Index Checker running along with the Upper - * Bound checker, allowing it to refine the upper bounds of expressions annotated by Substring - * Index Checker annotations. - */ - SubstringIndexAnnotatedTypeFactory getSubstringIndexAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SubstringIndexChecker.class); - } - - /** - * Provides a way to query the SameLen (same length) Checker, which determines the relationships - * among the lengths of arrays. - */ - SameLenAnnotatedTypeFactory getSameLenAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(SameLenChecker.class); - } - - /** - * Provides a way to query the Lower Bound Checker, which determines whether each integer in the - * program is non-negative or not, and checks that no possibly negative integers are used to - * access arrays. - */ - LowerBoundAnnotatedTypeFactory getLowerBoundAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(LowerBoundChecker.class); - } - - /** Returns the LessThan Checker's annotated type factory. */ - public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { - return getTypeFactoryOfSubchecker(LessThanChecker.class); - } - - @Override - public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(element, type); - if (element != null && !ajavaTypes.isParsing()) { - AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(element); - addUpperBoundTypeFromValueType(valueType, type); - } - } - - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(tree, type); - // If dataflow shouldn't be used to compute this type, then do not use the result from - // the Value Checker, because dataflow is used to compute that type. (Without this, - // "int i = 1; --i;" fails.) - if (getUseFlow() - && tree != null - && !ajavaTypes.isParsing() - && TreeUtils.isExpressionTree(tree)) { - AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); - addUpperBoundTypeFromValueType(valueType, type); + /** The @{@link UpperBoundUnknown} annotation. */ + public final AnnotationMirror UNKNOWN = + AnnotationBuilder.fromClass(elements, UpperBoundUnknown.class); + + /** The @{@link UpperBoundBottom} annotation. */ + public final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, UpperBoundBottom.class); + + /** The @{@link PolyUpperBound} annotation. */ + public final AnnotationMirror POLY = + AnnotationBuilder.fromClass(elements, PolyUpperBound.class); + + /** The @{@link UpperBoundLiteral}(-1) annotation. */ + public final AnnotationMirror NEGATIVEONE = + new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", -1) + .build(); + + /** The @{@link UpperBoundLiteral}(0) annotation. */ + public final AnnotationMirror ZERO = + new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", 0) + .build(); + + /** The @{@link UpperBoundLiteral}(1) annotation. */ + public final AnnotationMirror ONE = + new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", 1) + .build(); + + /** The NegativeIndexFor.value element/field. */ + public final ExecutableElement negativeIndexForValueElement = + TreeUtils.getMethod(NegativeIndexFor.class, "value", 0, processingEnv); + + /** The SameLen.value element/field. */ + public final ExecutableElement sameLenValueElement = + TreeUtils.getMethod(SameLen.class, "value", 0, processingEnv); + + /** The LTLengthOf.value element/field. */ + public final ExecutableElement ltLengthOfValueElement = + TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); + + /** The LTLengthOf.offset element/field. */ + public final ExecutableElement ltLengthOfOffsetElement = + TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); + + /** Predicates about what method an invocation is calling. */ + private final IndexMethodIdentifier imf; + + /** Create a new UpperBoundAnnotatedTypeFactory. */ + public UpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + addAliasedTypeAnnotation(IndexFor.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(IndexOrLow.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(IndexOrHigh.class, LTEqLengthOf.class, true); + addAliasedTypeAnnotation(SearchIndexFor.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(NegativeIndexFor.class, LTLengthOf.class, true); + addAliasedTypeAnnotation(LengthOf.class, LTEqLengthOf.class, true); + addAliasedTypeAnnotation(PolyIndex.class, POLY); + + imf = new IndexMethodIdentifier(this); + + this.postInit(); } - } - - /** - * Checks if valueType contains a {@link org.checkerframework.common.value.qual.BottomVal} - * annotation. If so, adds an {@link UpperBoundBottom} annotation to type. - * - * @param valueType annotated type from the {@link ValueAnnotatedTypeFactory} - * @param type annotated type from this factory that is side effected - */ - private void addUpperBoundTypeFromValueType( - AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { - if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { - type.replaceAnnotation(BOTTOM); - } - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(new UpperBoundTypeAnnotator(this), super.createTypeAnnotator()); - } - - /** - * Performs pre-processing on annotations written by users, replacing illegal annotations by legal - * ones. - */ - private class UpperBoundTypeAnnotator extends TypeAnnotator { - private UpperBoundTypeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** Gets a helper object that holds references to methods with special handling. */ + IndexMethodIdentifier getMethodIdentifier() { + return imf; } @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - // If there is an LTLengthOf annotation whose argument lengths don't match, replace it - // with bottom. - AnnotationMirror anm = type.getAnnotation(LTLengthOf.class); - if (anm != null) { - List sequences = - AnnotationUtils.getElementValueArray(anm, ltLengthOfValueElement, String.class); - List offsets = - AnnotationUtils.getElementValueArray( - anm, ltLengthOfOffsetElement, String.class, Collections.emptyList()); - if (sequences != null - && offsets != null - && sequences.size() != offsets.size() - && !offsets.isEmpty()) { - // Cannot use type.replaceAnnotation because it will call isSubtype, which will - // try to process the annotation and throw an error. - type.clearAnnotations(); - type.addAnnotation(BOTTOM); - } - } - return super.scan(type, aVoid); - } - } - - @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new OffsetDependentTypesHelper(this); - } - - /** - * Queries the SameLen Checker to return the type that the SameLen Checker associates with the - * given tree. - */ - public @Nullable AnnotationMirror sameLenAnnotationFromTree(Tree tree) { - AnnotatedTypeMirror sameLenType = getSameLenAnnotatedTypeFactory().getAnnotatedType(tree); - return sameLenType.getAnnotation(SameLen.class); - } - - // Wrapper methods for accessing the IndexMethodIdentifier. - - public boolean isMathMin(Tree methodTree) { - return imf.isMathMin(methodTree); - } - - /** - * Returns true if the tree is for {@code Random.nextInt(int)}. - * - * @param methodTree a tree to check - * @return true iff the tree is for {@code Random.nextInt(int)} - */ - public boolean isRandomNextInt(Tree methodTree) { - return imf.isRandomNextInt(methodTree, processingEnv); - } - - /** - * Creates a new @LTLengthOf annotation. - * - * @param names the arguments to @LTLengthOf - * @return a new @LTLengthOf annotation with the given arguments - */ - AnnotationMirror createLTLengthOfAnnotation(String... names) { - if (names == null || names.length == 0) { - throw new TypeSystemError( - "createLTLengthOfAnnotation: bad argument %s", Arrays.toString(names)); - } - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTLengthOf.class); - builder.setValue("value", names); - return builder.build(); - } - - /** - * Creates a new @LTEqLengthOf annotation. - * - * @param names the arguments to @LTEqLengthOf - * @return a new @LTEqLengthOf annotation with the given arguments - */ - AnnotationMirror createLTEqLengthOfAnnotation(String... names) { - if (names == null || names.length == 0) { - throw new TypeSystemError( - "createLTEqLengthOfAnnotation: bad argument %s", Arrays.toString(names)); + protected Set> createSupportedTypeQualifiers() { + // Because the Index Checker is a subclass, the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList( + UpperBoundUnknown.class, + LTEqLengthOf.class, + LTLengthOf.class, + LTOMLengthOf.class, + UpperBoundLiteral.class, + UpperBoundBottom.class, + PolyUpperBound.class)); } - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTEqLengthOf.class); - builder.setValue("value", names); - return builder.build(); - } - - /** - * Returns true iff the given node has the passed Lower Bound qualifier according to the LBC. The - * last argument should be Positive.class, NonNegative.class, or GTENegativeOne.class. - * - * @param node the given node - * @param classOfType one of Positive.class, NonNegative.class, or GTENegativeOne.class - * @return true iff the given node has the passed Lower Bound qualifier according to the LBC - */ - public boolean hasLowerBoundTypeByClass(Node node, Class classOfType) { - return areSameByClass( - getLowerBoundAnnotatedTypeFactory() - .getAnnotatedType(node.getTree()) - .getAnnotationInHierarchy(getLowerBoundAnnotatedTypeFactory().UNKNOWN), - classOfType); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new UpperBoundQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** The qualifier hierarchy for the upperbound type system. */ - protected final class UpperBoundQualifierHierarchy extends ElementQualifierHierarchy { + /** - * Creates an UpperBoundQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils + * Provides a way to query the Constant Value Checker, which computes the values of expressions + * known at compile time (constant propagation and folding). */ - UpperBoundQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, UpperBoundAnnotatedTypeFactory.this); - } - - @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); - UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); - UBQualifier glb = a1Obj.glb(a2Obj); - return convertUBQualifierToAnnotation(glb); + ValueAnnotatedTypeFactory getValueAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(ValueChecker.class); } /** - * Determines the least upper bound of a1 and a2. If a1 and a2 are both the same type of Value - * annotation, then the LUB is the result of taking the intersection of values from both a1 and - * a2. - * - * @return the least upper bound of a1 and a2 + * Provides a way to query the Search Index Checker, which helps the Index Checker type the + * results of calling the JDK's binary search methods correctly. */ - @Override - public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); - UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); - UBQualifier lub = a1Obj.lub(a2Obj); - return convertUBQualifierToAnnotation(lub); + private SearchIndexAnnotatedTypeFactory getSearchIndexAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SearchIndexChecker.class); } - @Override - public AnnotationMirror widenedUpperBound( - AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { - UBQualifier a1Obj = UBQualifier.createUBQualifier(newQualifier, (IndexChecker) checker); - UBQualifier a2Obj = UBQualifier.createUBQualifier(previousQualifier, (IndexChecker) checker); - UBQualifier lub = a1Obj.widenUpperBound(a2Obj); - return convertUBQualifierToAnnotation(lub); + /** + * Gets the annotated type factory of the Substring Index Checker running along with the Upper + * Bound checker, allowing it to refine the upper bounds of expressions annotated by Substring + * Index Checker annotations. + */ + SubstringIndexAnnotatedTypeFactory getSubstringIndexAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SubstringIndexChecker.class); } - @Override - public int numberOfIterationsBeforeWidening() { - return 10; + /** + * Provides a way to query the SameLen (same length) Checker, which determines the relationships + * among the lengths of arrays. + */ + SameLenAnnotatedTypeFactory getSameLenAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(SameLenChecker.class); } /** - * {@inheritDoc} - * - *

          Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both - * annotations have the same class. In this case, rhs is a subtype of lhs iff rhs contains every - * element of lhs. + * Provides a way to query the Lower Bound Checker, which determines whether each integer in the + * program is non-negative or not, and checks that no possibly negative integers are used to + * access arrays. */ - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - UBQualifier subtypeQual = UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker); - UBQualifier supertypeQual = UBQualifier.createUBQualifier(superAnno, (IndexChecker) checker); - return subtypeQual.isSubtype(supertypeQual); + LowerBoundAnnotatedTypeFactory getLowerBoundAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(LowerBoundChecker.class); } - } - - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new UpperBoundTreeAnnotator(this), super.createTreeAnnotator()); - } - protected class UpperBoundTreeAnnotator extends TreeAnnotator { + /** Returns the LessThan Checker's annotated type factory. */ + public LessThanAnnotatedTypeFactory getLessThanAnnotatedTypeFactory() { + return getTypeFactoryOfSubchecker(LessThanChecker.class); + } - public UpperBoundTreeAnnotator(UpperBoundAnnotatedTypeFactory factory) { - super(factory); + @Override + public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(element, type); + if (element != null && !ajavaTypes.isParsing()) { + AnnotatedTypeMirror valueType = + getValueAnnotatedTypeFactory().getAnnotatedType(element); + addUpperBoundTypeFromValueType(valueType, type); + } } - /** - * This exists for Math.min and Random.nextInt, which must be special-cased. - * - *

            - *
          • Math.min has unusual semantics that combines annotations for the UBC. - *
          • The return type of Random.nextInt depends on the argument, but is not equal to it, so a - * polymorphic qualifier is insufficient. - *
          - * - * Other methods should not be special-cased here unless there is a compelling reason to do so. - */ @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (isMathMin(tree)) { - AnnotatedTypeMirror leftType = getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror rightType = getAnnotatedType(tree.getArguments().get(1)); - - type.replaceAnnotation( - qualHierarchy.greatestLowerBoundShallow( - leftType.getAnnotationInHierarchy(UNKNOWN), - leftType.getUnderlyingType(), - rightType.getAnnotationInHierarchy(UNKNOWN), - rightType.getUnderlyingType())); - } - if (isRandomNextInt(tree)) { - AnnotatedTypeMirror argType = getAnnotatedType(tree.getArguments().get(0)); - AnnotationMirror anno = argType.getAnnotationInHierarchy(UNKNOWN); - UBQualifier qualifier = UBQualifier.createUBQualifier(anno, (IndexChecker) checker); - qualifier = qualifier.plusOffset(1); - type.replaceAnnotation(convertUBQualifierToAnnotation(qualifier)); - } - if (imf.isIndexOfString(tree)) { - // String#indexOf(String) and its variants that also take a String technically - // return (and are annotated as) @LTEqLengthOf the receiver. However, the result is - // always @LTLengthOf the receiver unless both the receiver and the target string - // are the empty string: "".indexOf("") returns 0, which isn't an index into "". So, - // this special case modifies the return type of these methods if either the - // parameter or the receiver is known (by the Value Checker) to not be the empty - // string. There are three ways the Value Checker might have that information: - // either string could have a @StringVal annotation whose value doesn't include "", - // either could have an @ArrayLen annotation whose value doesn't contain zero, or - // either could have an @ArrayLenRange annotation whose from value is any positive - // integer. - ValueAnnotatedTypeFactory vatf = - ((UpperBoundAnnotatedTypeFactory) this.atypeFactory).getValueAnnotatedTypeFactory(); - AnnotatedTypeMirror argType = vatf.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror receiverType = vatf.getReceiverType(tree); - if (definitelyIsNotTheEmptyString(argType, vatf) - || definitelyIsNotTheEmptyString(receiverType, vatf)) { - String receiverName = JavaExpression.getReceiver(tree).toString(); - UBQualifier ltLengthOfReceiver = UBQualifier.createUBQualifier(receiverName, "0"); - AnnotationMirror currentReturnAnno = type.getAnnotationInHierarchy(UNKNOWN); - UBQualifier currentUBQualifier = - UBQualifier.createUBQualifier(currentReturnAnno, (IndexChecker) checker); - UBQualifier result = currentUBQualifier.glb(ltLengthOfReceiver); - type.replaceAnnotation(convertUBQualifierToAnnotation(result)); - } - } - return super.visitMethodInvocation(tree, type); + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); + // If dataflow shouldn't be used to compute this type, then do not use the result from + // the Value Checker, because dataflow is used to compute that type. (Without this, + // "int i = 1; --i;" fails.) + if (getUseFlow() + && tree != null + && !ajavaTypes.isParsing() + && TreeUtils.isExpressionTree(tree)) { + AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); + addUpperBoundTypeFromValueType(valueType, type); + } } /** - * Returns true if the given Value Checker annotations guarantee that the annotated element is - * not the empty string. + * Checks if valueType contains a {@link org.checkerframework.common.value.qual.BottomVal} + * annotation. If so, adds an {@link UpperBoundBottom} annotation to type. * - * @param atm an annotated type from the Value Checker - * @param vatf the Value Annotated Type Factory - * @return true iff atm contains a {@code StringVal} annotation whose value doesn't contain "", - * an {@code ArrayLen} annotation whose value doesn't contain 0, or an {@code ArrayLenRange} - * annotation whose from value is greater than 0 + * @param valueType annotated type from the {@link ValueAnnotatedTypeFactory} + * @param type annotated type from this factory that is side effected */ - private boolean definitelyIsNotTheEmptyString( - AnnotatedTypeMirror atm, ValueAnnotatedTypeFactory vatf) { - AnnotationMirrorSet annos = atm.getAnnotations(); - for (AnnotationMirror anno : annos) { - switch (AnnotationUtils.annotationName(anno)) { - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - List strings = vatf.getStringValues(anno); - if (strings != null && !strings.contains("")) { - return true; - } - break; - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - List lengths = vatf.getArrayLength(anno); - if (lengths != null && !lengths.contains(0)) { - return true; - } - break; - default: - Range range = vatf.getRange(anno); - if (range != null && range.from > 0) { - return true; - } - break; + private void addUpperBoundTypeFromValueType( + AnnotatedTypeMirror valueType, AnnotatedTypeMirror type) { + if (containsSameByClass(valueType.getAnnotations(), BottomVal.class)) { + type.replaceAnnotation(BOTTOM); } - } - return false; } @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - // Could also handle long literals, but array indexes are always ints. - if (tree.getKind() == Tree.Kind.INT_LITERAL) { - type.addAnnotation(createLiteral(((Integer) tree.getValue()).intValue())); - } - return super.visitLiteral(tree, type); - } - - /* Handles case 3. */ - @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - // Dataflow refines this type if possible - if (tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT) { - addAnnotationForBitwiseComplement( - getSearchIndexAnnotatedTypeFactory().getAnnotatedType(tree.getExpression()), type); - } else { - type.addAnnotation(UNKNOWN); - } - return super.visitUnary(tree, type); + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator( + new UpperBoundTypeAnnotator(this), super.createTypeAnnotator()); } /** - * If a type returned by an {@link SearchIndexAnnotatedTypeFactory} has a {@link - * NegativeIndexFor} annotation, then refine the result to be {@link LTEqLengthOf}. This handles - * this case: - * - *
          {@code
          -     * int i = Arrays.binarySearch(a, x);
          -     * if (i >= 0) {
          -     *     // do something
          -     * } else {
          -     *     i = ~i;
          -     *     // i is now @LTEqLengthOf("a"), because the bitwise complement of a NegativeIndexFor is an LTL.
          -     *     for (int j = 0; j < i; j++) {
          -     *          // j is now a valid index for "a"
          -     *     }
          -     * }
          -     * }
          - * - * @param searchIndexType the type of an expression in a bitwise complement. For instance, in - * {@code ~x}, this is the type of {@code x}. - * @param typeDst the type of the entire bitwise complement expression. It is modified by this - * method. + * Performs pre-processing on annotations written by users, replacing illegal annotations by + * legal ones. */ - private void addAnnotationForBitwiseComplement( - AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { - AnnotationMirror nif = searchIndexType.getAnnotation(NegativeIndexFor.class); - if (nif != null) { - List arrays = - AnnotationUtils.getElementValueArray(nif, negativeIndexForValueElement, String.class); - List negativeOnes = Collections.nCopies(arrays.size(), "-1"); - UBQualifier qual = UBQualifier.createUBQualifier(arrays, negativeOnes); - typeDst.addAnnotation(convertUBQualifierToAnnotation(qual)); - } else { - typeDst.addAnnotation(UNKNOWN); - } - } + private class UpperBoundTypeAnnotator extends TypeAnnotator { - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - // Dataflow refines this type if possible - type.addAnnotation(UNKNOWN); - return super.visitCompoundAssignment(tree, type); + private UpperBoundTypeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + // If there is an LTLengthOf annotation whose argument lengths don't match, replace it + // with bottom. + AnnotationMirror anm = type.getAnnotation(LTLengthOf.class); + if (anm != null) { + List sequences = + AnnotationUtils.getElementValueArray( + anm, ltLengthOfValueElement, String.class); + List offsets = + AnnotationUtils.getElementValueArray( + anm, + ltLengthOfOffsetElement, + String.class, + Collections.emptyList()); + if (sequences != null + && offsets != null + && sequences.size() != offsets.size() + && !offsets.isEmpty()) { + // Cannot use type.replaceAnnotation because it will call isSubtype, which will + // try to process the annotation and throw an error. + type.clearAnnotations(); + type.addAnnotation(BOTTOM); + } + } + return super.scan(type, aVoid); + } } @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - // A few small rules for addition/subtraction by 0/1, etc. - if (TreeUtils.isStringConcatenation(tree)) { - type.addAnnotation(UNKNOWN); - return super.visitBinary(tree, type); - } - - ExpressionTree left = tree.getLeftOperand(); - ExpressionTree right = tree.getRightOperand(); - switch (tree.getKind()) { - case PLUS: - case MINUS: - // Dataflow refines this type if possible - type.addAnnotation(UNKNOWN); - break; - case MULTIPLY: - addAnnotationForMultiply(left, right, type); - break; - case DIVIDE: - addAnnotationForDivide(left, right, type); - break; - case REMAINDER: - addAnnotationForRemainder(left, right, type); - break; - case AND: - addAnnotationForAnd(left, right, type); - break; - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - addAnnotationForRightShift(left, right, type); - break; - default: - break; - } - return super.visitBinary(tree, type); + protected DependentTypesHelper createDependentTypesHelper() { + return new OffsetDependentTypesHelper(this); } /** - * Infers upper-bound annotation for {@code left >> right} and {@code left >>> right} (case 4). + * Queries the SameLen Checker to return the type that the SameLen Checker associates with the + * given tree. */ - private void addAnnotationForRightShift( - ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { - LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); - if (lowerBoundATF.isNonNegative(left)) { - AnnotationMirror annotation = getAnnotatedType(left).getAnnotationInHierarchy(UNKNOWN); - // For non-negative numbers, right shift is equivalent to division by a power of - // two. - // The range of the shift amount is limited to 0..30 to avoid overflows and int/long - // differences. - Long shiftAmount = ValueCheckerUtils.getExactValue(right, getValueAnnotatedTypeFactory()); - if (shiftAmount != null && shiftAmount >= 0 && shiftAmount < Integer.SIZE - 1) { - int divisor = 1 << shiftAmount; - // Support average by shift just like for division - UBQualifier plusDivQualifier = plusTreeDivideByVal(divisor, left); - if (!plusDivQualifier.isUnknown()) { - UBQualifier qualifier = - UBQualifier.createUBQualifier(annotation, (IndexChecker) checker); - qualifier = qualifier.glb(plusDivQualifier); - annotation = convertUBQualifierToAnnotation(qualifier); - } - } - type.addAnnotation(annotation); - } + public @Nullable AnnotationMirror sameLenAnnotationFromTree(Tree tree) { + AnnotatedTypeMirror sameLenType = getSameLenAnnotatedTypeFactory().getAnnotatedType(tree); + return sameLenType.getAnnotation(SameLen.class); } - /** - * If either argument is non-negative, the and expression retains that argument's upperbound - * type. If both are non-negative, the result of the expression is the GLB of the two (case 5). - */ - private void addAnnotationForAnd( - ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { - LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); - AnnotatedTypeMirror leftType = getAnnotatedType(left); - AnnotationMirror leftResultAnno = UNKNOWN; - if (lowerBoundATF.isNonNegative(left)) { - leftResultAnno = leftType.getAnnotationInHierarchy(UNKNOWN); - } - - AnnotatedTypeMirror rightType = getAnnotatedType(right); - AnnotationMirror rightResultAnno = UNKNOWN; - if (lowerBoundATF.isNonNegative(right)) { - rightResultAnno = rightType.getAnnotationInHierarchy(UNKNOWN); - } - - type.addAnnotation( - qualHierarchy.greatestLowerBoundShallow( - leftResultAnno, - leftType.getUnderlyingType(), - rightResultAnno, - rightType.getUnderlyingType())); - } + // Wrapper methods for accessing the IndexMethodIdentifier. - /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ - private @Nullable ExpressionTree getLengthSequenceTree(ExpressionTree lengthTree) { - return IndexUtil.getLengthSequenceTree(lengthTree, imf, processingEnv); + public boolean isMathMin(Tree methodTree) { + return imf.isMathMin(methodTree); } /** - * Infers upper-bound annotation for {@code numerator % divisor}. There are two cases where an - * upperbound type is inferred: + * Returns true if the tree is for {@code Random.nextInt(int)}. * - *
            - *
          • 6. if numerator ≥ 0, then numerator % divisor ≤ numerator - *
          • 7. if divisor ≥ 0, then numerator % divisor < divisor - *
          + * @param methodTree a tree to check + * @return true iff the tree is for {@code Random.nextInt(int)} */ - private void addAnnotationForRemainder( - ExpressionTree numeratorTree, ExpressionTree divisorTree, AnnotatedTypeMirror resultType) { - LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); - UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; - // if numerator >= 0, then numerator%divisor <= numerator - if (lowerBoundATF.isNonNegative(numeratorTree)) { - result = - UBQualifier.createUBQualifier( - getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); - } - // if divisor >= 0, then numerator%divisor < divisor - if (lowerBoundATF.isNonNegative(divisorTree)) { - UBQualifier divisor = - UBQualifier.createUBQualifier( - getAnnotatedType(divisorTree), UNKNOWN, (IndexChecker) checker); - result = result.glb(divisor.plusOffset(1)); - } - resultType.addAnnotation(convertUBQualifierToAnnotation(result)); + public boolean isRandomNextInt(Tree methodTree) { + return imf.isRandomNextInt(methodTree, processingEnv); } /** - * Implements two cases (8 and 9): + * Creates a new @LTLengthOf annotation. * - *
            - *
          • 8. If the numerator is an array length access of an array with non-zero length, and the - * divisor is greater than one, glb the result with an LTL of the array. - *
          • 9. if numeratorTree is a + b and divisor greater than 1, and a and b are less than the - * length of some sequence, then (a + b) / divisor is less than the length of that - * sequence. - *
          + * @param names the arguments to @LTLengthOf + * @return a new @LTLengthOf annotation with the given arguments */ - private void addAnnotationForDivide( - ExpressionTree numeratorTree, ExpressionTree divisorTree, AnnotatedTypeMirror resultType) { - - Long divisor = ValueCheckerUtils.getExactValue(divisorTree, getValueAnnotatedTypeFactory()); - if (divisor == null) { - resultType.addAnnotation(UNKNOWN); - return; - } - - UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; - UBQualifier numerator = - UBQualifier.createUBQualifier( - getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); - if (numerator.isLessThanLengthQualifier()) { - result = ((LessThanLengthOf) numerator).divide(divisor.intValue()); - } - result = result.glb(plusTreeDivideByVal(divisor.intValue(), numeratorTree)); - - ExpressionTree numeratorSequenceTree = getLengthSequenceTree(numeratorTree); - // If the numerator is an array length access of an array with non-zero length, and the - // divisor is greater than one, glb the result with an LTL of the array. - if (numeratorSequenceTree != null && divisor > 1) { - String arrayName = numeratorSequenceTree.toString(); - int minlen = - getValueAnnotatedTypeFactory() - .getMinLenFromString(arrayName, numeratorTree, getPath(numeratorTree)); - if (minlen > 0) { - result = result.glb(UBQualifier.createUBQualifier(arrayName, "0")); - } - } - - resultType.addAnnotation(convertUBQualifierToAnnotation(result)); + AnnotationMirror createLTLengthOfAnnotation(String... names) { + if (names == null || names.length == 0) { + throw new TypeSystemError( + "createLTLengthOfAnnotation: bad argument %s", Arrays.toString(names)); + } + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTLengthOf.class); + builder.setValue("value", names); + return builder.build(); } /** - * If {@code numeratorTree} is "a + b" and {@code divisor} is greater than 1, and a and b are - * less than the length of some sequence, then "(a + b) / divisor" is less than the length of - * that sequence. + * Creates a new @LTEqLengthOf annotation. * - * @param divisor the divisor - * @param numeratorTree an addition tree that is divided by {@code divisor} - * @return a qualifier for the division + * @param names the arguments to @LTEqLengthOf + * @return a new @LTEqLengthOf annotation with the given arguments */ - private UBQualifier plusTreeDivideByVal(int divisor, ExpressionTree numeratorTree) { - numeratorTree = TreeUtils.withoutParens(numeratorTree); - if (divisor < 2 || numeratorTree.getKind() != Tree.Kind.PLUS) { - return UpperBoundUnknownQualifier.UNKNOWN; - } - BinaryTree plusTree = (BinaryTree) numeratorTree; - UBQualifier left = - UBQualifier.createUBQualifier( - getAnnotatedType(plusTree.getLeftOperand()), UNKNOWN, (IndexChecker) checker); - UBQualifier right = - UBQualifier.createUBQualifier( - getAnnotatedType(plusTree.getRightOperand()), UNKNOWN, (IndexChecker) checker); - if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { - LessThanLengthOf leftLTL = (LessThanLengthOf) left; - LessThanLengthOf rightLTL = (LessThanLengthOf) right; - List sequences = new ArrayList<>(); - for (String sequence : leftLTL.getSequences()) { - if (rightLTL.isLessThanLengthOf(sequence) && leftLTL.isLessThanLengthOf(sequence)) { - sequences.add(sequence); - } - } - if (!sequences.isEmpty()) { - return UBQualifier.createUBQualifier(sequences, Collections.emptyList()); - } - } - - return UpperBoundUnknownQualifier.UNKNOWN; + AnnotationMirror createLTEqLengthOfAnnotation(String... names) { + if (names == null || names.length == 0) { + throw new TypeSystemError( + "createLTEqLengthOfAnnotation: bad argument %s", Arrays.toString(names)); + } + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), LTEqLengthOf.class); + builder.setValue("value", names); + return builder.build(); } /** - * Special handling for Math.random: Math.random() * array.length is LTL array. Same for - * Random.nextDouble. Case 10. + * Returns true iff the given node has the passed Lower Bound qualifier according to the LBC. + * The last argument should be Positive.class, NonNegative.class, or GTENegativeOne.class. + * + * @param node the given node + * @param classOfType one of Positive.class, NonNegative.class, or GTENegativeOne.class + * @return true iff the given node has the passed Lower Bound qualifier according to the LBC */ - private boolean checkForMathRandomSpecialCase( - ExpressionTree randTree, ExpressionTree seqLenTree, AnnotatedTypeMirror type) { + public boolean hasLowerBoundTypeByClass(Node node, Class classOfType) { + return areSameByClass( + getLowerBoundAnnotatedTypeFactory() + .getAnnotatedType(node.getTree()) + .getAnnotationInHierarchy(getLowerBoundAnnotatedTypeFactory().UNKNOWN), + classOfType); + } - ExpressionTree seqTree = getLengthSequenceTree(seqLenTree); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new UpperBoundQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** The qualifier hierarchy for the upperbound type system. */ + protected final class UpperBoundQualifierHierarchy extends ElementQualifierHierarchy { + /** + * Creates an UpperBoundQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + UpperBoundQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, UpperBoundAnnotatedTypeFactory.this); + } - if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION && seqTree != null) { + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); + UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); + UBQualifier glb = a1Obj.glb(a2Obj); + return convertUBQualifierToAnnotation(glb); + } - MethodInvocationTree mitree = (MethodInvocationTree) randTree; + /** + * Determines the least upper bound of a1 and a2. If a1 and a2 are both the same type of + * Value annotation, then the LUB is the result of taking the intersection of values from + * both a1 and a2. + * + * @return the least upper bound of a1 and a2 + */ + @Override + public AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + UBQualifier a1Obj = UBQualifier.createUBQualifier(a1, (IndexChecker) checker); + UBQualifier a2Obj = UBQualifier.createUBQualifier(a2, (IndexChecker) checker); + UBQualifier lub = a1Obj.lub(a2Obj); + return convertUBQualifierToAnnotation(lub); + } - if (imf.isMathRandom(mitree, processingEnv)) { - // Okay, so this is Math.random() * array.length, which must be NonNegative - type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); - return true; + @Override + public AnnotationMirror widenedUpperBound( + AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { + UBQualifier a1Obj = UBQualifier.createUBQualifier(newQualifier, (IndexChecker) checker); + UBQualifier a2Obj = + UBQualifier.createUBQualifier(previousQualifier, (IndexChecker) checker); + UBQualifier lub = a1Obj.widenUpperBound(a2Obj); + return convertUBQualifierToAnnotation(lub); } - if (imf.isRandomNextDouble(mitree, processingEnv)) { - // Okay, so this is Random.nextDouble() * array.length, which must be - // NonNegative - type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); - return true; + @Override + public int numberOfIterationsBeforeWidening() { + return 10; } - } - return false; + /** + * {@inheritDoc} + * + *

          Computes subtyping as per the subtyping in the qualifier hierarchy structure unless + * both annotations have the same class. In this case, rhs is a subtype of lhs iff rhs + * contains every element of lhs. + */ + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + UBQualifier subtypeQual = + UBQualifier.createUBQualifier(subAnno, (IndexChecker) checker); + UBQualifier supertypeQual = + UBQualifier.createUBQualifier(superAnno, (IndexChecker) checker); + return subtypeQual.isSubtype(supertypeQual); + } } - private void addAnnotationForMultiply( - ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { - // Special handling for multiplying an array length by a random variable. - if (checkForMathRandomSpecialCase(rightExpr, leftExpr, type) - || checkForMathRandomSpecialCase(leftExpr, rightExpr, type)) { - return; - } - type.addAnnotation(UNKNOWN); + @Override + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + new UpperBoundTreeAnnotator(this), super.createTreeAnnotator()); } - } - - /** - * Creates a @{@link UpperBoundLiteral} annotation. - * - * @param i the integer - * @return a @{@link UpperBoundLiteral} annotation - */ - public AnnotationMirror createLiteral(int i) { - switch (i) { - case -1: - return NEGATIVEONE; - case 0: - return ZERO; - case 1: - return ONE; - default: - return new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) - .setValue("value", i) - .build(); + + protected class UpperBoundTreeAnnotator extends TreeAnnotator { + + public UpperBoundTreeAnnotator(UpperBoundAnnotatedTypeFactory factory) { + super(factory); + } + + /** + * This exists for Math.min and Random.nextInt, which must be special-cased. + * + *

            + *
          • Math.min has unusual semantics that combines annotations for the UBC. + *
          • The return type of Random.nextInt depends on the argument, but is not equal to it, + * so a polymorphic qualifier is insufficient. + *
          + * + * Other methods should not be special-cased here unless there is a compelling reason to do + * so. + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (isMathMin(tree)) { + AnnotatedTypeMirror leftType = getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror rightType = getAnnotatedType(tree.getArguments().get(1)); + + type.replaceAnnotation( + qualHierarchy.greatestLowerBoundShallow( + leftType.getAnnotationInHierarchy(UNKNOWN), + leftType.getUnderlyingType(), + rightType.getAnnotationInHierarchy(UNKNOWN), + rightType.getUnderlyingType())); + } + if (isRandomNextInt(tree)) { + AnnotatedTypeMirror argType = getAnnotatedType(tree.getArguments().get(0)); + AnnotationMirror anno = argType.getAnnotationInHierarchy(UNKNOWN); + UBQualifier qualifier = UBQualifier.createUBQualifier(anno, (IndexChecker) checker); + qualifier = qualifier.plusOffset(1); + type.replaceAnnotation(convertUBQualifierToAnnotation(qualifier)); + } + if (imf.isIndexOfString(tree)) { + // String#indexOf(String) and its variants that also take a String technically + // return (and are annotated as) @LTEqLengthOf the receiver. However, the result is + // always @LTLengthOf the receiver unless both the receiver and the target string + // are the empty string: "".indexOf("") returns 0, which isn't an index into "". So, + // this special case modifies the return type of these methods if either the + // parameter or the receiver is known (by the Value Checker) to not be the empty + // string. There are three ways the Value Checker might have that information: + // either string could have a @StringVal annotation whose value doesn't include "", + // either could have an @ArrayLen annotation whose value doesn't contain zero, or + // either could have an @ArrayLenRange annotation whose from value is any positive + // integer. + ValueAnnotatedTypeFactory vatf = + ((UpperBoundAnnotatedTypeFactory) this.atypeFactory) + .getValueAnnotatedTypeFactory(); + AnnotatedTypeMirror argType = vatf.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror receiverType = vatf.getReceiverType(tree); + if (definitelyIsNotTheEmptyString(argType, vatf) + || definitelyIsNotTheEmptyString(receiverType, vatf)) { + String receiverName = JavaExpression.getReceiver(tree).toString(); + UBQualifier ltLengthOfReceiver = + UBQualifier.createUBQualifier(receiverName, "0"); + AnnotationMirror currentReturnAnno = type.getAnnotationInHierarchy(UNKNOWN); + UBQualifier currentUBQualifier = + UBQualifier.createUBQualifier( + currentReturnAnno, (IndexChecker) checker); + UBQualifier result = currentUBQualifier.glb(ltLengthOfReceiver); + type.replaceAnnotation(convertUBQualifierToAnnotation(result)); + } + } + return super.visitMethodInvocation(tree, type); + } + + /** + * Returns true if the given Value Checker annotations guarantee that the annotated element + * is not the empty string. + * + * @param atm an annotated type from the Value Checker + * @param vatf the Value Annotated Type Factory + * @return true iff atm contains a {@code StringVal} annotation whose value doesn't contain + * "", an {@code ArrayLen} annotation whose value doesn't contain 0, or an {@code + * ArrayLenRange} annotation whose from value is greater than 0 + */ + private boolean definitelyIsNotTheEmptyString( + AnnotatedTypeMirror atm, ValueAnnotatedTypeFactory vatf) { + AnnotationMirrorSet annos = atm.getAnnotations(); + for (AnnotationMirror anno : annos) { + switch (AnnotationUtils.annotationName(anno)) { + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + List strings = vatf.getStringValues(anno); + if (strings != null && !strings.contains("")) { + return true; + } + break; + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + List lengths = vatf.getArrayLength(anno); + if (lengths != null && !lengths.contains(0)) { + return true; + } + break; + default: + Range range = vatf.getRange(anno); + if (range != null && range.from > 0) { + return true; + } + break; + } + } + return false; + } + + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + // Could also handle long literals, but array indexes are always ints. + if (tree.getKind() == Tree.Kind.INT_LITERAL) { + type.addAnnotation(createLiteral(((Integer) tree.getValue()).intValue())); + } + return super.visitLiteral(tree, type); + } + + /* Handles case 3. */ + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + // Dataflow refines this type if possible + if (tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT) { + addAnnotationForBitwiseComplement( + getSearchIndexAnnotatedTypeFactory().getAnnotatedType(tree.getExpression()), + type); + } else { + type.addAnnotation(UNKNOWN); + } + return super.visitUnary(tree, type); + } + + /** + * If a type returned by an {@link SearchIndexAnnotatedTypeFactory} has a {@link + * NegativeIndexFor} annotation, then refine the result to be {@link LTEqLengthOf}. This + * handles this case: + * + *
          {@code
          +         * int i = Arrays.binarySearch(a, x);
          +         * if (i >= 0) {
          +         *     // do something
          +         * } else {
          +         *     i = ~i;
          +         *     // i is now @LTEqLengthOf("a"), because the bitwise complement of a NegativeIndexFor is an LTL.
          +         *     for (int j = 0; j < i; j++) {
          +         *          // j is now a valid index for "a"
          +         *     }
          +         * }
          +         * }
          + * + * @param searchIndexType the type of an expression in a bitwise complement. For instance, + * in {@code ~x}, this is the type of {@code x}. + * @param typeDst the type of the entire bitwise complement expression. It is modified by + * this method. + */ + private void addAnnotationForBitwiseComplement( + AnnotatedTypeMirror searchIndexType, AnnotatedTypeMirror typeDst) { + AnnotationMirror nif = searchIndexType.getAnnotation(NegativeIndexFor.class); + if (nif != null) { + List arrays = + AnnotationUtils.getElementValueArray( + nif, negativeIndexForValueElement, String.class); + List negativeOnes = Collections.nCopies(arrays.size(), "-1"); + UBQualifier qual = UBQualifier.createUBQualifier(arrays, negativeOnes); + typeDst.addAnnotation(convertUBQualifierToAnnotation(qual)); + } else { + typeDst.addAnnotation(UNKNOWN); + } + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + // Dataflow refines this type if possible + type.addAnnotation(UNKNOWN); + return super.visitCompoundAssignment(tree, type); + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + // A few small rules for addition/subtraction by 0/1, etc. + if (TreeUtils.isStringConcatenation(tree)) { + type.addAnnotation(UNKNOWN); + return super.visitBinary(tree, type); + } + + ExpressionTree left = tree.getLeftOperand(); + ExpressionTree right = tree.getRightOperand(); + switch (tree.getKind()) { + case PLUS: + case MINUS: + // Dataflow refines this type if possible + type.addAnnotation(UNKNOWN); + break; + case MULTIPLY: + addAnnotationForMultiply(left, right, type); + break; + case DIVIDE: + addAnnotationForDivide(left, right, type); + break; + case REMAINDER: + addAnnotationForRemainder(left, right, type); + break; + case AND: + addAnnotationForAnd(left, right, type); + break; + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + addAnnotationForRightShift(left, right, type); + break; + default: + break; + } + return super.visitBinary(tree, type); + } + + /** + * Infers upper-bound annotation for {@code left >> right} and {@code left >>> right} (case + * 4). + */ + private void addAnnotationForRightShift( + ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { + LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); + if (lowerBoundATF.isNonNegative(left)) { + AnnotationMirror annotation = + getAnnotatedType(left).getAnnotationInHierarchy(UNKNOWN); + // For non-negative numbers, right shift is equivalent to division by a power of + // two. + // The range of the shift amount is limited to 0..30 to avoid overflows and int/long + // differences. + Long shiftAmount = + ValueCheckerUtils.getExactValue(right, getValueAnnotatedTypeFactory()); + if (shiftAmount != null && shiftAmount >= 0 && shiftAmount < Integer.SIZE - 1) { + int divisor = 1 << shiftAmount; + // Support average by shift just like for division + UBQualifier plusDivQualifier = plusTreeDivideByVal(divisor, left); + if (!plusDivQualifier.isUnknown()) { + UBQualifier qualifier = + UBQualifier.createUBQualifier(annotation, (IndexChecker) checker); + qualifier = qualifier.glb(plusDivQualifier); + annotation = convertUBQualifierToAnnotation(qualifier); + } + } + type.addAnnotation(annotation); + } + } + + /** + * If either argument is non-negative, the and expression retains that argument's upperbound + * type. If both are non-negative, the result of the expression is the GLB of the two (case + * 5). + */ + private void addAnnotationForAnd( + ExpressionTree left, ExpressionTree right, AnnotatedTypeMirror type) { + LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); + AnnotatedTypeMirror leftType = getAnnotatedType(left); + AnnotationMirror leftResultAnno = UNKNOWN; + if (lowerBoundATF.isNonNegative(left)) { + leftResultAnno = leftType.getAnnotationInHierarchy(UNKNOWN); + } + + AnnotatedTypeMirror rightType = getAnnotatedType(right); + AnnotationMirror rightResultAnno = UNKNOWN; + if (lowerBoundATF.isNonNegative(right)) { + rightResultAnno = rightType.getAnnotationInHierarchy(UNKNOWN); + } + + type.addAnnotation( + qualHierarchy.greatestLowerBoundShallow( + leftResultAnno, + leftType.getUnderlyingType(), + rightResultAnno, + rightType.getUnderlyingType())); + } + + /** Gets a sequence tree for a length access tree, or null if it is not a length access. */ + private @Nullable ExpressionTree getLengthSequenceTree(ExpressionTree lengthTree) { + return IndexUtil.getLengthSequenceTree(lengthTree, imf, processingEnv); + } + + /** + * Infers upper-bound annotation for {@code numerator % divisor}. There are two cases where + * an upperbound type is inferred: + * + *
            + *
          • 6. if numerator ≥ 0, then numerator % divisor ≤ numerator + *
          • 7. if divisor ≥ 0, then numerator % divisor < divisor + *
          + */ + private void addAnnotationForRemainder( + ExpressionTree numeratorTree, + ExpressionTree divisorTree, + AnnotatedTypeMirror resultType) { + LowerBoundAnnotatedTypeFactory lowerBoundATF = getLowerBoundAnnotatedTypeFactory(); + UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; + // if numerator >= 0, then numerator%divisor <= numerator + if (lowerBoundATF.isNonNegative(numeratorTree)) { + result = + UBQualifier.createUBQualifier( + getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); + } + // if divisor >= 0, then numerator%divisor < divisor + if (lowerBoundATF.isNonNegative(divisorTree)) { + UBQualifier divisor = + UBQualifier.createUBQualifier( + getAnnotatedType(divisorTree), UNKNOWN, (IndexChecker) checker); + result = result.glb(divisor.plusOffset(1)); + } + resultType.addAnnotation(convertUBQualifierToAnnotation(result)); + } + + /** + * Implements two cases (8 and 9): + * + *
            + *
          • 8. If the numerator is an array length access of an array with non-zero length, and + * the divisor is greater than one, glb the result with an LTL of the array. + *
          • 9. if numeratorTree is a + b and divisor greater than 1, and a and b are less than + * the length of some sequence, then (a + b) / divisor is less than the length of that + * sequence. + *
          + */ + private void addAnnotationForDivide( + ExpressionTree numeratorTree, + ExpressionTree divisorTree, + AnnotatedTypeMirror resultType) { + + Long divisor = + ValueCheckerUtils.getExactValue(divisorTree, getValueAnnotatedTypeFactory()); + if (divisor == null) { + resultType.addAnnotation(UNKNOWN); + return; + } + + UBQualifier result = UpperBoundUnknownQualifier.UNKNOWN; + UBQualifier numerator = + UBQualifier.createUBQualifier( + getAnnotatedType(numeratorTree), UNKNOWN, (IndexChecker) checker); + if (numerator.isLessThanLengthQualifier()) { + result = ((LessThanLengthOf) numerator).divide(divisor.intValue()); + } + result = result.glb(plusTreeDivideByVal(divisor.intValue(), numeratorTree)); + + ExpressionTree numeratorSequenceTree = getLengthSequenceTree(numeratorTree); + // If the numerator is an array length access of an array with non-zero length, and the + // divisor is greater than one, glb the result with an LTL of the array. + if (numeratorSequenceTree != null && divisor > 1) { + String arrayName = numeratorSequenceTree.toString(); + int minlen = + getValueAnnotatedTypeFactory() + .getMinLenFromString( + arrayName, numeratorTree, getPath(numeratorTree)); + if (minlen > 0) { + result = result.glb(UBQualifier.createUBQualifier(arrayName, "0")); + } + } + + resultType.addAnnotation(convertUBQualifierToAnnotation(result)); + } + + /** + * If {@code numeratorTree} is "a + b" and {@code divisor} is greater than 1, and a and b + * are less than the length of some sequence, then "(a + b) / divisor" is less than the + * length of that sequence. + * + * @param divisor the divisor + * @param numeratorTree an addition tree that is divided by {@code divisor} + * @return a qualifier for the division + */ + private UBQualifier plusTreeDivideByVal(int divisor, ExpressionTree numeratorTree) { + numeratorTree = TreeUtils.withoutParens(numeratorTree); + if (divisor < 2 || numeratorTree.getKind() != Tree.Kind.PLUS) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + BinaryTree plusTree = (BinaryTree) numeratorTree; + UBQualifier left = + UBQualifier.createUBQualifier( + getAnnotatedType(plusTree.getLeftOperand()), + UNKNOWN, + (IndexChecker) checker); + UBQualifier right = + UBQualifier.createUBQualifier( + getAnnotatedType(plusTree.getRightOperand()), + UNKNOWN, + (IndexChecker) checker); + if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { + LessThanLengthOf leftLTL = (LessThanLengthOf) left; + LessThanLengthOf rightLTL = (LessThanLengthOf) right; + List sequences = new ArrayList<>(); + for (String sequence : leftLTL.getSequences()) { + if (rightLTL.isLessThanLengthOf(sequence) + && leftLTL.isLessThanLengthOf(sequence)) { + sequences.add(sequence); + } + } + if (!sequences.isEmpty()) { + return UBQualifier.createUBQualifier(sequences, Collections.emptyList()); + } + } + + return UpperBoundUnknownQualifier.UNKNOWN; + } + + /** + * Special handling for Math.random: Math.random() * array.length is LTL array. Same for + * Random.nextDouble. Case 10. + */ + private boolean checkForMathRandomSpecialCase( + ExpressionTree randTree, ExpressionTree seqLenTree, AnnotatedTypeMirror type) { + + ExpressionTree seqTree = getLengthSequenceTree(seqLenTree); + + if (randTree.getKind() == Tree.Kind.METHOD_INVOCATION && seqTree != null) { + + MethodInvocationTree mitree = (MethodInvocationTree) randTree; + + if (imf.isMathRandom(mitree, processingEnv)) { + // Okay, so this is Math.random() * array.length, which must be NonNegative + type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); + return true; + } + + if (imf.isRandomNextDouble(mitree, processingEnv)) { + // Okay, so this is Random.nextDouble() * array.length, which must be + // NonNegative + type.addAnnotation(createLTLengthOfAnnotation(seqTree.toString())); + return true; + } + } + + return false; + } + + private void addAnnotationForMultiply( + ExpressionTree leftExpr, ExpressionTree rightExpr, AnnotatedTypeMirror type) { + // Special handling for multiplying an array length by a random variable. + if (checkForMathRandomSpecialCase(rightExpr, leftExpr, type) + || checkForMathRandomSpecialCase(leftExpr, rightExpr, type)) { + return; + } + type.addAnnotation(UNKNOWN); + } } - } - - /** - * Convert the internal representation to an annotation. - * - * @param qualifier a UBQualifier - * @return an annotation corresponding to the given qualifier - */ - public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { - if (qualifier.isUnknown()) { - return UNKNOWN; - } else if (qualifier.isBottom()) { - return BOTTOM; - } else if (qualifier.isPoly()) { - return POLY; - } else if (qualifier.isLiteral()) { - return createLiteral(((UpperBoundLiteralQualifier) qualifier).getValue()); + + /** + * Creates a @{@link UpperBoundLiteral} annotation. + * + * @param i the integer + * @return a @{@link UpperBoundLiteral} annotation + */ + public AnnotationMirror createLiteral(int i) { + switch (i) { + case -1: + return NEGATIVEONE; + case 0: + return ZERO; + case 1: + return ONE; + default: + return new AnnotationBuilder(getProcessingEnv(), UpperBoundLiteral.class) + .setValue("value", i) + .build(); + } } - LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; - return ltlQualifier.convertToAnnotation(processingEnv); - } + /** + * Convert the internal representation to an annotation. + * + * @param qualifier a UBQualifier + * @return an annotation corresponding to the given qualifier + */ + public AnnotationMirror convertUBQualifierToAnnotation(UBQualifier qualifier) { + if (qualifier.isUnknown()) { + return UNKNOWN; + } else if (qualifier.isBottom()) { + return BOTTOM; + } else if (qualifier.isPoly()) { + return POLY; + } else if (qualifier.isLiteral()) { + return createLiteral(((UpperBoundLiteralQualifier) qualifier).getValue()); + } - @Nullable UBQualifier fromLessThan(ExpressionTree tree, TreePath treePath) { - List lessThanExpressions = - getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); - if (lessThanExpressions == null) { - return null; + LessThanLengthOf ltlQualifier = (LessThanLengthOf) qualifier; + return ltlQualifier.convertToAnnotation(processingEnv); } - UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); - if (ubQualifier != null) { - return ubQualifier.plusOffset(1); + + @Nullable UBQualifier fromLessThan(ExpressionTree tree, TreePath treePath) { + List lessThanExpressions = + getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); + if (lessThanExpressions == null) { + return null; + } + UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); + if (ubQualifier != null) { + return ubQualifier.plusOffset(1); + } + return null; } - return null; - } - - @Nullable UBQualifier fromLessThanOrEqual(ExpressionTree tree, TreePath treePath) { - List lessThanExpressions = - getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); - if (lessThanExpressions == null) { - return null; + + @Nullable UBQualifier fromLessThanOrEqual(ExpressionTree tree, TreePath treePath) { + List lessThanExpressions = + getLessThanAnnotatedTypeFactory().getLessThanExpressions(tree); + if (lessThanExpressions == null) { + return null; + } + UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); + return ubQualifier; } - UBQualifier ubQualifier = fromLessThanOrEqual(tree, treePath, lessThanExpressions); - return ubQualifier; - } - - private @Nullable UBQualifier fromLessThanOrEqual( - Tree tree, TreePath treePath, List lessThanExpressions) { - UBQualifier ubQualifier = null; - for (String expression : lessThanExpressions) { - IPair exprAndOffset; - try { - exprAndOffset = getExpressionAndOffsetFromJavaExpressionString(expression, treePath); - } catch (JavaExpressionParseException e) { - exprAndOffset = null; - } - if (exprAndOffset == null) { - continue; - } - JavaExpression je = exprAndOffset.first; - String offset = exprAndOffset.second; - - if (!CFAbstractStore.canInsertJavaExpression(je)) { - continue; - } - CFStore store = getStoreBefore(tree); - CFValue value = store.getValue(je); - if (value != null && value.getAnnotations().size() == 1) { - UBQualifier newUBQ = - UBQualifier.createUBQualifier( - qualHierarchy.findAnnotationInHierarchy(value.getAnnotations(), UNKNOWN), - AnnotatedTypeFactory.negateConstant(offset), - (IndexChecker) checker); - if (ubQualifier == null) { - ubQualifier = newUBQ; - } else { - ubQualifier = ubQualifier.glb(newUBQ); - } - } + + private @Nullable UBQualifier fromLessThanOrEqual( + Tree tree, TreePath treePath, List lessThanExpressions) { + UBQualifier ubQualifier = null; + for (String expression : lessThanExpressions) { + IPair exprAndOffset; + try { + exprAndOffset = + getExpressionAndOffsetFromJavaExpressionString(expression, treePath); + } catch (JavaExpressionParseException e) { + exprAndOffset = null; + } + if (exprAndOffset == null) { + continue; + } + JavaExpression je = exprAndOffset.first; + String offset = exprAndOffset.second; + + if (!CFAbstractStore.canInsertJavaExpression(je)) { + continue; + } + CFStore store = getStoreBefore(tree); + CFValue value = store.getValue(je); + if (value != null && value.getAnnotations().size() == 1) { + UBQualifier newUBQ = + UBQualifier.createUBQualifier( + qualHierarchy.findAnnotationInHierarchy( + value.getAnnotations(), UNKNOWN), + AnnotatedTypeFactory.negateConstant(offset), + (IndexChecker) checker); + if (ubQualifier == null) { + ubQualifier = newUBQ; + } else { + ubQualifier = ubQualifier.glb(newUBQ); + } + } + } + return ubQualifier; } - return ubQualifier; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java index 515c9b6fb4b..2e4fbf840d3 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundChecker.java @@ -1,8 +1,5 @@ package org.checkerframework.checker.index.upperbound; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.index.inequality.LessThanChecker; import org.checkerframework.checker.index.lowerbound.LowerBoundChecker; import org.checkerframework.checker.index.qual.LTEqLengthOf; @@ -21,96 +18,103 @@ import org.checkerframework.framework.source.SuppressWarningsPrefix; import org.checkerframework.javacutil.TreeUtils; +import java.util.HashSet; +import java.util.Set; + +import javax.lang.model.element.ExecutableElement; + /** * A type-checker for preventing arrays from being accessed with values that are too high. * * @checker_framework.manual #index-checker Index Checker */ @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - Character.class, - byte.class, - short.class, - int.class, - long.class, - char.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Character.class, + byte.class, + short.class, + int.class, + long.class, + char.class, }) @SuppressWarningsPrefix({"index", "upperbound"}) public class UpperBoundChecker extends BaseTypeChecker { - /** The SubstringIndexFor.value argument/element. */ - public @MonotonicNonNull ExecutableElement substringIndexForValueElement; + /** The SubstringIndexFor.value argument/element. */ + public @MonotonicNonNull ExecutableElement substringIndexForValueElement; - /** The SubstringIndexFor.offset argument/element. */ - public @MonotonicNonNull ExecutableElement substringIndexForOffsetElement; + /** The SubstringIndexFor.offset argument/element. */ + public @MonotonicNonNull ExecutableElement substringIndexForOffsetElement; - /** The LTLengthOf.value argument/element. */ - public @MonotonicNonNull ExecutableElement ltLengthOfValueElement; + /** The LTLengthOf.value argument/element. */ + public @MonotonicNonNull ExecutableElement ltLengthOfValueElement; - /** The LTLengthOf.offset argument/element. */ - public @MonotonicNonNull ExecutableElement ltLengthOfOffsetElement; + /** The LTLengthOf.offset argument/element. */ + public @MonotonicNonNull ExecutableElement ltLengthOfOffsetElement; - /** The LTEqLengthOf.value argument/element. */ - public @MonotonicNonNull ExecutableElement ltEqLengthOfValueElement; + /** The LTEqLengthOf.value argument/element. */ + public @MonotonicNonNull ExecutableElement ltEqLengthOfValueElement; - /** The LTOMLengthOf.value argument/element. */ - public @MonotonicNonNull ExecutableElement ltOMLengthOfValueElement; + /** The LTOMLengthOf.value argument/element. */ + public @MonotonicNonNull ExecutableElement ltOMLengthOfValueElement; - /** The UpperBoundLiteral.value element/field. */ - public @MonotonicNonNull ExecutableElement upperBoundLiteralValueElement; + /** The UpperBoundLiteral.value element/field. */ + public @MonotonicNonNull ExecutableElement upperBoundLiteralValueElement; - /** - * These collection classes have some subtypes whose length can change and some subtypes whose - * length cannot change. Warnings are skipped at uses of them. - */ - private final HashSet collectionBaseTypeNames; + /** + * These collection classes have some subtypes whose length can change and some subtypes whose + * length cannot change. Warnings are skipped at uses of them. + */ + private final HashSet collectionBaseTypeNames; - /** Create a new UpperBoundChecker. */ - public UpperBoundChecker() { - // These classes are bases for both mutable and immutable sequence collections, which - // contain methods that change the length. - // Upper bound checker warnings are skipped at uses of them. - Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; - collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); - for (Class collectionBaseClass : collectionBaseClasses) { - collectionBaseTypeNames.add(collectionBaseClass.getName()); + /** Create a new UpperBoundChecker. */ + public UpperBoundChecker() { + // These classes are bases for both mutable and immutable sequence collections, which + // contain methods that change the length. + // Upper bound checker warnings are skipped at uses of them. + Class[] collectionBaseClasses = {java.util.List.class, java.util.AbstractList.class}; + collectionBaseTypeNames = new HashSet<>(collectionBaseClasses.length); + for (Class collectionBaseClass : collectionBaseClasses) { + collectionBaseTypeNames.add(collectionBaseClass.getName()); + } } - } - @Override - public void initChecker() { - super.initChecker(); - substringIndexForValueElement = - TreeUtils.getMethod(SubstringIndexFor.class, "value", 0, processingEnv); - substringIndexForOffsetElement = - TreeUtils.getMethod(SubstringIndexFor.class, "offset", 0, processingEnv); - ltLengthOfValueElement = TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); - ltLengthOfOffsetElement = TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); - ltEqLengthOfValueElement = TreeUtils.getMethod(LTEqLengthOf.class, "value", 0, processingEnv); - ltOMLengthOfValueElement = TreeUtils.getMethod(LTOMLengthOf.class, "value", 0, processingEnv); - upperBoundLiteralValueElement = - TreeUtils.getMethod(UpperBoundLiteral.class, "value", 0, processingEnv); - } + @Override + public void initChecker() { + super.initChecker(); + substringIndexForValueElement = + TreeUtils.getMethod(SubstringIndexFor.class, "value", 0, processingEnv); + substringIndexForOffsetElement = + TreeUtils.getMethod(SubstringIndexFor.class, "offset", 0, processingEnv); + ltLengthOfValueElement = TreeUtils.getMethod(LTLengthOf.class, "value", 0, processingEnv); + ltLengthOfOffsetElement = TreeUtils.getMethod(LTLengthOf.class, "offset", 0, processingEnv); + ltEqLengthOfValueElement = + TreeUtils.getMethod(LTEqLengthOf.class, "value", 0, processingEnv); + ltOMLengthOfValueElement = + TreeUtils.getMethod(LTOMLengthOf.class, "value", 0, processingEnv); + upperBoundLiteralValueElement = + TreeUtils.getMethod(UpperBoundLiteral.class, "value", 0, processingEnv); + } - @Override - public boolean shouldSkipUses(@FullyQualifiedName String typeName) { - if (collectionBaseTypeNames.contains(typeName)) { - return true; + @Override + public boolean shouldSkipUses(@FullyQualifiedName String typeName) { + if (collectionBaseTypeNames.contains(typeName)) { + return true; + } + return super.shouldSkipUses(typeName); } - return super.shouldSkipUses(typeName); - } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(SubstringIndexChecker.class); - checkers.add(SearchIndexChecker.class); - checkers.add(SameLenChecker.class); - checkers.add(LowerBoundChecker.class); - checkers.add(ValueChecker.class); - checkers.add(LessThanChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(SubstringIndexChecker.class); + checkers.add(SearchIndexChecker.class); + checkers.add(SameLenChecker.class); + checkers.add(LowerBoundChecker.class); + checkers.add(ValueChecker.class); + checkers.add(LessThanChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java index 2919c1d43a5..37fe1b85bab 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundTransfer.java @@ -2,12 +2,7 @@ import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.index.IndexAbstractTransfer; import org.checkerframework.checker.index.IndexRefinementInfo; import org.checkerframework.checker.index.Subsequence; @@ -47,6 +42,14 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * Contains the transfer functions for the upper bound type system, a part of the Index Checker. * This class implements the following refinement rules: @@ -107,759 +110,789 @@ */ public class UpperBoundTransfer extends IndexAbstractTransfer { - /** The type factory associated with this transfer function. */ - private final UpperBoundAnnotatedTypeFactory atypeFactory; - - /** The int TypeMirror. */ - private final TypeMirror intTM; - - /** - * Creates a new UpperBoundTransfer. - * - * @param analysis the analysis for this transfer function - */ - public UpperBoundTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (UpperBoundAnnotatedTypeFactory) analysis.getTypeFactory(); - intTM = atypeFactory.types.getPrimitiveType(TypeKind.INT); - } - - /** - * Case 1: Refine the type of expressions used as an array dimension to be less than length of the - * array to which the new array is assigned. For example, in "int[] array = new int[expr];", the - * type of expr is @LTEqLength("array"). - */ - @Override - public TransferResult visitAssignment( - AssignmentNode node, TransferInput in) { - TransferResult result = super.visitAssignment(node, in); - - Node expNode = node.getExpression(); - - // strip off typecast if any - Node expNodeSansCast = - (expNode instanceof TypeCastNode) ? ((TypeCastNode) expNode).getOperand() : expNode; - // null if right-hand-side is not an array creation expression - ArrayCreationNode acNode = - (expNodeSansCast instanceof ArrayCreationNode) - ? acNode = (ArrayCreationNode) expNodeSansCast - : null; - - if (acNode != null) { - // Right-hand side of assignment is an array creation expression - List nodeList = acNode.getDimensions(); - if (nodeList.size() < 1) { - return result; - } - Node dim = acNode.getDimension(0); - - UBQualifier previousQualifier = getUBQualifier(dim, in); - JavaExpression arrayExpr = JavaExpression.fromNode(node.getTarget()); - String arrayString = arrayExpr.toString(); - LessThanLengthOf newInfo = - (LessThanLengthOf) UBQualifier.createUBQualifier(arrayString, "-1"); - UBQualifier combined = previousQualifier.glb(newInfo); - AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(combined); - - JavaExpression dimExpr = JavaExpression.fromNode(dim); - result.getRegularStore().insertValue(dimExpr, newAnno); - propagateToOperands(newInfo, dim, in, result.getRegularStore()); - } - return result; - } - - /** - * {@code node} is known to be {@code typeOfNode}. If the node is a plus or a minus then the types - * of the left and right operands can be refined to include offsets. If the node is a - * multiplication, its operands can also be refined. See {@link - * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, CFStore)}, - * {@link #propagateToSubtractionOperands(UBQualifier.LessThanLengthOf, NumericalSubtractionNode, - * TransferInput, CFStore)}, and {@link - * #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, - * CFStore)} for details. - * - * @param typeOfNode type of node - * @param node the node - * @param in the TransferInput before propagate to this operand - * @param store location to store the refined type - */ - private void propagateToOperands( - LessThanLengthOf typeOfNode, Node node, TransferInput in, CFStore store) { - if (node instanceof NumericalAdditionNode) { - Node right = ((NumericalAdditionNode) node).getRightOperand(); - Node left = ((NumericalAdditionNode) node).getLeftOperand(); - propagateToAdditionOperand(typeOfNode, left, right, in, store); - propagateToAdditionOperand(typeOfNode, right, left, in, store); - } else if (node instanceof NumericalSubtractionNode) { - propagateToSubtractionOperands(typeOfNode, (NumericalSubtractionNode) node, in, store); - } else if (node instanceof NumericalMultiplicationNode) { - if (atypeFactory.hasLowerBoundTypeByClass(node, Positive.class)) { - Node right = ((NumericalMultiplicationNode) node).getRightOperand(); - Node left = ((NumericalMultiplicationNode) node).getLeftOperand(); - propagateToMultiplicationOperand(typeOfNode, left, right, in, store); - propagateToMultiplicationOperand(typeOfNode, right, left, in, store); - } - } - } - - /** - * {@code other} times {@code node} is known to be {@code typeOfMultiplication}. - * - *

          This implies that if {@code other} is positive, then {@code node} is {@code - * typeOfMultiplication}. If {@code other} is greater than 1, then {@code node} is {@code - * typeOfMultiplication} plus 1. These are cases 2 and 3, respectively. - */ - private void propagateToMultiplicationOperand( - LessThanLengthOf typeOfMultiplication, - Node node, - Node other, - TransferInput in, - CFStore store) { - if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { - Long minValue = - ValueCheckerUtils.getMinValue( - other.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (minValue != null && minValue > 1) { - typeOfMultiplication = (LessThanLengthOf) typeOfMultiplication.plusOffset(1); - } - UBQualifier qual = getUBQualifier(node, in); - UBQualifier newQual = qual.glb(typeOfMultiplication); - JavaExpression je = JavaExpression.fromNode(node); - store.insertValue(je, atypeFactory.convertUBQualifierToAnnotation(newQual)); - } - } - - /** - * The subtraction node, {@code node}, is known to be {@code typeOfSubtraction}. - * - *

          This means that the left node is less than or equal to the length of the array when the - * right node is subtracted from the left node. Note that unlike {@link - * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, CFStore)} - * and {@link #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, Node, - * TransferInput, CFStore)}, this method takes the NumericalSubtractionNode instead of the two - * operand nodes. This implements case 4. - * - * @param typeOfSubtraction type of node - * @param node subtraction node that has typeOfSubtraction - * @param in a TransferInput - * @param store location to store the refined type - */ - private void propagateToSubtractionOperands( - LessThanLengthOf typeOfSubtraction, - NumericalSubtractionNode node, - TransferInput in, - CFStore store) { - UBQualifier left = getUBQualifier(node.getLeftOperand(), in); - UBQualifier newInfo = typeOfSubtraction.minusOffset(node.getRightOperand(), atypeFactory); - - UBQualifier newLeft = left.glb(newInfo); - JavaExpression leftJe = JavaExpression.fromNode(node.getLeftOperand()); - store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(newLeft)); - } - - /** - * Refines the type of {@code operand} to {@code typeOfAddition} plus {@code other}. If {@code - * other} is non-negative, then {@code operand} also less than the length of the arrays in {@code - * typeOfAddition}. If {@code other} is positive, then {@code operand} also less than the length - * of the arrays in {@code typeOfAddition} plus 1. These are cases 5, 6, and 7. - * - * @param typeOfAddition type of {@code operand + other} - * @param operand the Node to refine - * @param other the Node added to {@code operand} - * @param in a TransferInput - * @param store location to store the refined types - */ - private void propagateToAdditionOperand( - LessThanLengthOf typeOfAddition, - Node operand, - Node other, - TransferInput in, - CFStore store) { - UBQualifier operandQual = getUBQualifier(operand, in); - UBQualifier newQual = operandQual.glb(typeOfAddition.plusOffset(other, atypeFactory)); - - // If the node is NonNegative, add an LTEL to the qual. If Positive, add an LTL. - if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { - newQual = newQual.glb(typeOfAddition.plusOffset(1)); - } else if (atypeFactory.hasLowerBoundTypeByClass(other, NonNegative.class)) { - newQual = newQual.glb(typeOfAddition); - } - JavaExpression operandJe = JavaExpression.fromNode(operand); - store.insertValue(operandJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); - } - - /** - * Case 8: if x < y, and y has a type that is related to the length of an array, then x has the - * same type, with an offset that is one less. - */ - @Override - protected void refineGT( - Node larger, - AnnotationMirror largerAnno, - Node smaller, - AnnotationMirror smallerAnno, - CFStore store, - TransferInput in) { - // larger > smaller - UBQualifier largerQual = - UBQualifier.createUBQualifier(largerAnno, (UpperBoundChecker) atypeFactory.getChecker()); - // larger + 1 >= smaller - UBQualifier largerQualPlus1 = largerQual.plusOffset(1); - UBQualifier rightQualifier = - UBQualifier.createUBQualifier(smallerAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier refinedRight = rightQualifier.glb(largerQualPlus1); - - if (largerQualPlus1.isLessThanLengthQualifier()) { - propagateToOperands((LessThanLengthOf) largerQualPlus1, smaller, in, store); + /** The type factory associated with this transfer function. */ + private final UpperBoundAnnotatedTypeFactory atypeFactory; + + /** The int TypeMirror. */ + private final TypeMirror intTM; + + /** + * Creates a new UpperBoundTransfer. + * + * @param analysis the analysis for this transfer function + */ + public UpperBoundTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (UpperBoundAnnotatedTypeFactory) analysis.getTypeFactory(); + intTM = atypeFactory.types.getPrimitiveType(TypeKind.INT); } - refineSubtrahendWithOffset(larger, smaller, true, in, store); - - JavaExpression rightJe = JavaExpression.fromNode(smaller); - store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); - } - - /** - * Case 9: if x ≤ y, and y has a type that is related to the length of an array, then x has the - * same type. - */ - @Override - protected void refineGTE( - Node left, - AnnotationMirror leftAnno, - Node right, - AnnotationMirror rightAnno, - CFStore store, - TransferInput in) { - UBQualifier leftQualifier = - UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier rightQualifier = - UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier refinedRight = rightQualifier.glb(leftQualifier); - - if (leftQualifier.isLessThanLengthQualifier()) { - propagateToOperands((LessThanLengthOf) leftQualifier, right, in, store); + /** + * Case 1: Refine the type of expressions used as an array dimension to be less than length of + * the array to which the new array is assigned. For example, in "int[] array = new int[expr];", + * the type of expr is @LTEqLength("array"). + */ + @Override + public TransferResult visitAssignment( + AssignmentNode node, TransferInput in) { + TransferResult result = super.visitAssignment(node, in); + + Node expNode = node.getExpression(); + + // strip off typecast if any + Node expNodeSansCast = + (expNode instanceof TypeCastNode) ? ((TypeCastNode) expNode).getOperand() : expNode; + // null if right-hand-side is not an array creation expression + ArrayCreationNode acNode = + (expNodeSansCast instanceof ArrayCreationNode) + ? acNode = (ArrayCreationNode) expNodeSansCast + : null; + + if (acNode != null) { + // Right-hand side of assignment is an array creation expression + List nodeList = acNode.getDimensions(); + if (nodeList.size() < 1) { + return result; + } + Node dim = acNode.getDimension(0); + + UBQualifier previousQualifier = getUBQualifier(dim, in); + JavaExpression arrayExpr = JavaExpression.fromNode(node.getTarget()); + String arrayString = arrayExpr.toString(); + LessThanLengthOf newInfo = + (LessThanLengthOf) UBQualifier.createUBQualifier(arrayString, "-1"); + UBQualifier combined = previousQualifier.glb(newInfo); + AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(combined); + + JavaExpression dimExpr = JavaExpression.fromNode(dim); + result.getRegularStore().insertValue(dimExpr, newAnno); + propagateToOperands(newInfo, dim, in, result.getRegularStore()); + } + return result; } - refineSubtrahendWithOffset(left, right, false, in, store); - - JavaExpression rightJe = JavaExpression.fromNode(right); - store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); - } - - /** - * Refines the subtrahend in a subtraction which is greater than or equal to a certain offset. The - * type of the subtrahend is refined to the type of the minuend with the offset added. This is - * case 10. - * - *

          This is based on the fact that if {@code (minuend - subtrahend) >= offset}, and {@code - * minuend + o < l}, then {@code subtrahend + o + offset < l}. - * - *

          If {@code gtNode} is not a {@link NumericalSubtractionNode}, the method does nothing. - * - * @param gtNode the node that is greater or equal to the offset - * @param offsetNode a node part of the offset - * @param offsetAddOne whether to add one to the offset - * @param in input of the transfer function - * @param store location to store the refined types - */ - private void refineSubtrahendWithOffset( - Node gtNode, - Node offsetNode, - boolean offsetAddOne, - TransferInput in, - CFStore store) { - if (gtNode instanceof NumericalSubtractionNode) { - NumericalSubtractionNode subtractionNode = (NumericalSubtractionNode) gtNode; - - Node minuend = subtractionNode.getLeftOperand(); - UBQualifier minuendQual = getUBQualifier(minuend, in); - Node subtrahend = subtractionNode.getRightOperand(); - UBQualifier subtrahendQual = getUBQualifier(subtrahend, in); - - UBQualifier newQual = - subtrahendQual.glb( - minuendQual.plusOffset(offsetNode, atypeFactory).plusOffset(offsetAddOne ? 1 : 0)); - JavaExpression subtrahendJe = JavaExpression.fromNode(subtrahend); - store.insertValue(subtrahendJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); - } - } - - /** Implements case 11. */ - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult res, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - TransferResult result = - super.strengthenAnnotationOfEqualTo( - res, firstNode, secondNode, firstValue, secondValue, notEqualTo); - IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, firstNode, secondNode); - if (rfi.leftAnno == null || rfi.rightAnno == null) { - return result; + /** + * {@code node} is known to be {@code typeOfNode}. If the node is a plus or a minus then the + * types of the left and right operands can be refined to include offsets. If the node is a + * multiplication, its operands can also be refined. See {@link + * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, + * CFStore)}, {@link #propagateToSubtractionOperands(UBQualifier.LessThanLengthOf, + * NumericalSubtractionNode, TransferInput, CFStore)}, and {@link + * #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, + * CFStore)} for details. + * + * @param typeOfNode type of node + * @param node the node + * @param in the TransferInput before propagate to this operand + * @param store location to store the refined type + */ + private void propagateToOperands( + LessThanLengthOf typeOfNode, + Node node, + TransferInput in, + CFStore store) { + if (node instanceof NumericalAdditionNode) { + Node right = ((NumericalAdditionNode) node).getRightOperand(); + Node left = ((NumericalAdditionNode) node).getLeftOperand(); + propagateToAdditionOperand(typeOfNode, left, right, in, store); + propagateToAdditionOperand(typeOfNode, right, left, in, store); + } else if (node instanceof NumericalSubtractionNode) { + propagateToSubtractionOperands(typeOfNode, (NumericalSubtractionNode) node, in, store); + } else if (node instanceof NumericalMultiplicationNode) { + if (atypeFactory.hasLowerBoundTypeByClass(node, Positive.class)) { + Node right = ((NumericalMultiplicationNode) node).getRightOperand(); + Node left = ((NumericalMultiplicationNode) node).getLeftOperand(); + propagateToMultiplicationOperand(typeOfNode, left, right, in, store); + propagateToMultiplicationOperand(typeOfNode, right, left, in, store); + } + } } - CFStore equalsStore = notEqualTo ? rfi.elseStore : rfi.thenStore; - CFStore notEqualStore = notEqualTo ? rfi.thenStore : rfi.elseStore; - - refineEq(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, equalsStore); - - refineNeqSequenceLength(rfi.left, rfi.right, rfi.rightAnno, notEqualStore); - refineNeqSequenceLength(rfi.right, rfi.left, rfi.leftAnno, notEqualStore); - return rfi.newResult; - } - - /** Refines the type of the left and right node to glb of the left and right annotation. */ - private void refineEq( - Node left, AnnotationMirror leftAnno, Node right, AnnotationMirror rightAnno, CFStore store) { - UBQualifier leftQualifier = - UBQualifier.createUBQualifier(leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier rightQualifier = - UBQualifier.createUBQualifier(rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); - UBQualifier glb = rightQualifier.glb(leftQualifier); - AnnotationMirror glbAnno = atypeFactory.convertUBQualifierToAnnotation(glb); - - List internalsRight = splitAssignments(right); - for (Node internal : internalsRight) { - JavaExpression rightJe = JavaExpression.fromNode(internal); - store.insertValue(rightJe, glbAnno); + /** + * {@code other} times {@code node} is known to be {@code typeOfMultiplication}. + * + *

          This implies that if {@code other} is positive, then {@code node} is {@code + * typeOfMultiplication}. If {@code other} is greater than 1, then {@code node} is {@code + * typeOfMultiplication} plus 1. These are cases 2 and 3, respectively. + */ + private void propagateToMultiplicationOperand( + LessThanLengthOf typeOfMultiplication, + Node node, + Node other, + TransferInput in, + CFStore store) { + if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { + Long minValue = + ValueCheckerUtils.getMinValue( + other.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (minValue != null && minValue > 1) { + typeOfMultiplication = (LessThanLengthOf) typeOfMultiplication.plusOffset(1); + } + UBQualifier qual = getUBQualifier(node, in); + UBQualifier newQual = qual.glb(typeOfMultiplication); + JavaExpression je = JavaExpression.fromNode(node); + store.insertValue(je, atypeFactory.convertUBQualifierToAnnotation(newQual)); + } } - List internalsLeft = splitAssignments(left); - for (Node internal : internalsLeft) { - JavaExpression leftJe = JavaExpression.fromNode(internal); - store.insertValue(leftJe, glbAnno); + /** + * The subtraction node, {@code node}, is known to be {@code typeOfSubtraction}. + * + *

          This means that the left node is less than or equal to the length of the array when the + * right node is subtracted from the left node. Note that unlike {@link + * #propagateToAdditionOperand(UBQualifier.LessThanLengthOf, Node, Node, TransferInput, + * CFStore)} and {@link #propagateToMultiplicationOperand(UBQualifier.LessThanLengthOf, Node, + * Node, TransferInput, CFStore)}, this method takes the NumericalSubtractionNode instead of the + * two operand nodes. This implements case 4. + * + * @param typeOfSubtraction type of node + * @param node subtraction node that has typeOfSubtraction + * @param in a TransferInput + * @param store location to store the refined type + */ + private void propagateToSubtractionOperands( + LessThanLengthOf typeOfSubtraction, + NumericalSubtractionNode node, + TransferInput in, + CFStore store) { + UBQualifier left = getUBQualifier(node.getLeftOperand(), in); + UBQualifier newInfo = typeOfSubtraction.minusOffset(node.getRightOperand(), atypeFactory); + + UBQualifier newLeft = left.glb(newInfo); + JavaExpression leftJe = JavaExpression.fromNode(node.getLeftOperand()); + store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(newLeft)); } - } - - /** - * If lengthAccess node is an sequence length field or method access (optionally with a constant - * offset subtracted) and the other node is less than or equal to that sequence length (minus the - * offset), then refine the other node's type to less than the sequence length (minus the offset). - * This is case 12. - */ - private void refineNeqSequenceLength( - Node lengthAccess, Node otherNode, AnnotationMirror otherNodeAnno, CFStore store) { - - // If lengthAccess is "receiver.length - c" where c is an integer constant, - // then lengthOffset is "c". - int lengthOffset = 0; - if (lengthAccess instanceof NumericalSubtractionNode) { - NumericalSubtractionNode subtraction = (NumericalSubtractionNode) lengthAccess; - Node offsetNode = subtraction.getRightOperand(); - Long offsetValue = - ValueCheckerUtils.getExactValue( - offsetNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); - if (offsetValue != null - && offsetValue > Integer.MIN_VALUE - && offsetValue <= Integer.MAX_VALUE) { - lengthOffset = offsetValue.intValue(); - lengthAccess = subtraction.getLeftOperand(); - } else { - // Subtraction of non-constant expressions is not supported - return; - } + + /** + * Refines the type of {@code operand} to {@code typeOfAddition} plus {@code other}. If {@code + * other} is non-negative, then {@code operand} also less than the length of the arrays in + * {@code typeOfAddition}. If {@code other} is positive, then {@code operand} also less than the + * length of the arrays in {@code typeOfAddition} plus 1. These are cases 5, 6, and 7. + * + * @param typeOfAddition type of {@code operand + other} + * @param operand the Node to refine + * @param other the Node added to {@code operand} + * @param in a TransferInput + * @param store location to store the refined types + */ + private void propagateToAdditionOperand( + LessThanLengthOf typeOfAddition, + Node operand, + Node other, + TransferInput in, + CFStore store) { + UBQualifier operandQual = getUBQualifier(operand, in); + UBQualifier newQual = operandQual.glb(typeOfAddition.plusOffset(other, atypeFactory)); + + // If the node is NonNegative, add an LTEL to the qual. If Positive, add an LTL. + if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) { + newQual = newQual.glb(typeOfAddition.plusOffset(1)); + } else if (atypeFactory.hasLowerBoundTypeByClass(other, NonNegative.class)) { + newQual = newQual.glb(typeOfAddition); + } + JavaExpression operandJe = JavaExpression.fromNode(operand); + store.insertValue(operandJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); } - JavaExpression receiver = null; - if (NodeUtils.isArrayLengthFieldAccess(lengthAccess)) { - FieldAccess fa = - (FieldAccess) JavaExpression.fromNodeFieldAccess((FieldAccessNode) lengthAccess); - receiver = fa.getReceiver(); - - } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthAccess)) { - JavaExpression ma = JavaExpression.fromNode(lengthAccess); - if (ma instanceof MethodCall) { - receiver = ((MethodCall) ma).getReceiver(); - } + /** + * Case 8: if x < y, and y has a type that is related to the length of an array, then x has + * the same type, with an offset that is one less. + */ + @Override + protected void refineGT( + Node larger, + AnnotationMirror largerAnno, + Node smaller, + AnnotationMirror smallerAnno, + CFStore store, + TransferInput in) { + // larger > smaller + UBQualifier largerQual = + UBQualifier.createUBQualifier( + largerAnno, (UpperBoundChecker) atypeFactory.getChecker()); + // larger + 1 >= smaller + UBQualifier largerQualPlus1 = largerQual.plusOffset(1); + UBQualifier rightQualifier = + UBQualifier.createUBQualifier( + smallerAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier refinedRight = rightQualifier.glb(largerQualPlus1); + + if (largerQualPlus1.isLessThanLengthQualifier()) { + propagateToOperands((LessThanLengthOf) largerQualPlus1, smaller, in, store); + } + + refineSubtrahendWithOffset(larger, smaller, true, in, store); + + JavaExpression rightJe = JavaExpression.fromNode(smaller); + store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); } - if (receiver != null && !receiver.containsUnknown()) { - UBQualifier otherQualifier = - UBQualifier.createUBQualifier( - otherNodeAnno, (UpperBoundChecker) atypeFactory.getChecker()); - String sequence = receiver.toString(); - // Check if otherNode + c - 1 < receiver.length - if (otherQualifier.hasSequenceWithOffset(sequence, lengthOffset - 1)) { - // Add otherNode + c < receiver.length - UBQualifier newQualifier = - UBQualifier.createUBQualifier(sequence, Integer.toString(lengthOffset)); - otherQualifier = otherQualifier.glb(newQualifier); - for (Node internal : splitAssignments(otherNode)) { - JavaExpression leftJe = JavaExpression.fromNode(internal); - store.insertValue(leftJe, atypeFactory.convertUBQualifierToAnnotation(otherQualifier)); + /** + * Case 9: if x ≤ y, and y has a type that is related to the length of an array, then x has + * the same type. + */ + @Override + protected void refineGTE( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store, + TransferInput in) { + UBQualifier leftQualifier = + UBQualifier.createUBQualifier( + leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier rightQualifier = + UBQualifier.createUBQualifier( + rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier refinedRight = rightQualifier.glb(leftQualifier); + + if (leftQualifier.isLessThanLengthQualifier()) { + propagateToOperands((LessThanLengthOf) leftQualifier, right, in, store); } - } + + refineSubtrahendWithOffset(left, right, false, in, store); + + JavaExpression rightJe = JavaExpression.fromNode(right); + store.insertValue(rightJe, atypeFactory.convertUBQualifierToAnnotation(refinedRight)); } - } - - /** - * If some Node a is known to be less than the length of some array, x, then, the type of a + b, - * is @LTLengthOf(value="x", offset="-b"). If b is known to be less than the length of some other - * array, y, then the type of a + b is @LTLengthOf(value={"x", "y"}, offset={"-b", "-a"}). - * - *

          Alternatively, if a is known to be less than the length of x when some offset, o, is added - * to a (the type of a is @LTLengthOf(value="x", offset="o")), then the type of a + b - * is @LTLengthOf(value="x",offset="o - b"). (Note, if "o - b" can be computed, then it is and the - * result is used in the annotation.) - * - *

          In addition, If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int - * and expression j is less than or equal to the length of f1, then the type of i + j is - * .@LTLengthOf("f2"). - * - *

          These three cases correspond to cases 13-15. - */ - @Override - public TransferResult visitNumericalAddition( - NumericalAdditionNode n, TransferInput in) { - // type of leftNode + rightNode is glb(t, s) where - // t = minusOffset(type(leftNode), rightNode) and - // s = minusOffset(type(rightNode), leftNode) - - UBQualifier left = getUBQualifierForAddition(n.getLeftOperand(), in); - UBQualifier t = left.minusOffset(n.getRightOperand(), atypeFactory); - - UBQualifier right = getUBQualifierForAddition(n.getRightOperand(), in); - UBQualifier s = right.minusOffset(n.getLeftOperand(), atypeFactory); - - UBQualifier glb = t.glb(s); - if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { - // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and - // expression j is less than or equal to the length of f1, then the type of i + j is - // @LTLengthOf("f2"). - UBQualifier r = removeSequenceLengths((LessThanLengthOf) left, (LessThanLengthOf) right); - glb = glb.glb(r); - UBQualifier l = removeSequenceLengths((LessThanLengthOf) right, (LessThanLengthOf) left); - glb = glb.glb(l); + + /** + * Refines the subtrahend in a subtraction which is greater than or equal to a certain offset. + * The type of the subtrahend is refined to the type of the minuend with the offset added. This + * is case 10. + * + *

          This is based on the fact that if {@code (minuend - subtrahend) >= offset}, and {@code + * minuend + o < l}, then {@code subtrahend + o + offset < l}. + * + *

          If {@code gtNode} is not a {@link NumericalSubtractionNode}, the method does nothing. + * + * @param gtNode the node that is greater or equal to the offset + * @param offsetNode a node part of the offset + * @param offsetAddOne whether to add one to the offset + * @param in input of the transfer function + * @param store location to store the refined types + */ + private void refineSubtrahendWithOffset( + Node gtNode, + Node offsetNode, + boolean offsetAddOne, + TransferInput in, + CFStore store) { + if (gtNode instanceof NumericalSubtractionNode) { + NumericalSubtractionNode subtractionNode = (NumericalSubtractionNode) gtNode; + + Node minuend = subtractionNode.getLeftOperand(); + UBQualifier minuendQual = getUBQualifier(minuend, in); + Node subtrahend = subtractionNode.getRightOperand(); + UBQualifier subtrahendQual = getUBQualifier(subtrahend, in); + + UBQualifier newQual = + subtrahendQual.glb( + minuendQual + .plusOffset(offsetNode, atypeFactory) + .plusOffset(offsetAddOne ? 1 : 0)); + JavaExpression subtrahendJe = JavaExpression.fromNode(subtrahend); + store.insertValue(subtrahendJe, atypeFactory.convertUBQualifierToAnnotation(newQual)); + } } - return createTransferResult(n, in, glb); - } - - /** - * Return the result of adding i to j. - * - *

          When expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length") int} and - * expression j is less than or equal to the length of f1, then the type of i + j - * is @LTLengthOf("f2"). - * - *

          When expression i has type {@code @LTLengthOf (value = "f2", offset = "f1.length - 1") int} - * and expression j is less than the length of f1, then the type of i + j is @LTLengthOf("f2"). - * - * @param i the type of the expression added to j - * @param j the type of the expression added to i - * @return the type of i + j - */ - private UBQualifier removeSequenceLengths(LessThanLengthOf i, LessThanLengthOf j) { - List lessThan = new ArrayList<>(); - List lessThanOrEqual = new ArrayList<>(); - for (String sequence : i.getSequences()) { - if (i.isLessThanLengthOf(sequence)) { - lessThan.add(sequence); - } else if (i.hasSequenceWithOffset(sequence, -1)) { - lessThanOrEqual.add(sequence); - } + /** Implements case 11. */ + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult res, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + TransferResult result = + super.strengthenAnnotationOfEqualTo( + res, firstNode, secondNode, firstValue, secondValue, notEqualTo); + IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, firstNode, secondNode); + if (rfi.leftAnno == null || rfi.rightAnno == null) { + return result; + } + + CFStore equalsStore = notEqualTo ? rfi.elseStore : rfi.thenStore; + CFStore notEqualStore = notEqualTo ? rfi.thenStore : rfi.elseStore; + + refineEq(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, equalsStore); + + refineNeqSequenceLength(rfi.left, rfi.right, rfi.rightAnno, notEqualStore); + refineNeqSequenceLength(rfi.right, rfi.left, rfi.leftAnno, notEqualStore); + return rfi.newResult; } - // Creates a qualifier that is the same a j with the array.length offsets removed. If - // an offset doesn't have an array.length, then the offset/array pair is removed. If - // there are no such pairs, Unknown is returned. - UBQualifier lessThanEqQ = j.removeSequenceLengthAccess(lessThanOrEqual); - // Creates a qualifier that is the same a j with the array.length - 1 offsets removed. If - // an offset doesn't have an array.length, then the offset/array pair is removed. If - // there are no such pairs, Unknown is returned. - UBQualifier lessThanQ = j.removeSequenceLengthAccessAndNeg1(lessThan); - - return lessThanEqQ.glb(lessThanQ); - } - - /** - * If some Node a is known to be less than the length of some sequence x, then the type of a - b - * is @LTLengthOf(value="x", offset="b"). If b is known to be less than the length of some other - * sequence, this doesn't add any information about the type of a - b. But, if b is non-negative - * or positive, then a - b should keep the types of a. This corresponds to cases 16 and 17. - */ - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput in) { - UBQualifier left = getUBQualifier(n.getLeftOperand(), in); - UBQualifier leftWithOffset = left.plusOffset(n.getRightOperand(), atypeFactory); - if (atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), NonNegative.class) - || atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), Positive.class)) { - // If the right side of the expression is NN or POS, then all the left side's - // annotations should be kept. - if (left.isLessThanLengthQualifier()) { - leftWithOffset = left.glb(leftWithOffset); - } + + /** Refines the type of the left and right node to glb of the left and right annotation. */ + private void refineEq( + Node left, + AnnotationMirror leftAnno, + Node right, + AnnotationMirror rightAnno, + CFStore store) { + UBQualifier leftQualifier = + UBQualifier.createUBQualifier( + leftAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier rightQualifier = + UBQualifier.createUBQualifier( + rightAnno, (UpperBoundChecker) atypeFactory.getChecker()); + UBQualifier glb = rightQualifier.glb(leftQualifier); + AnnotationMirror glbAnno = atypeFactory.convertUBQualifierToAnnotation(glb); + + List internalsRight = splitAssignments(right); + for (Node internal : internalsRight) { + JavaExpression rightJe = JavaExpression.fromNode(internal); + store.insertValue(rightJe, glbAnno); + } + + List internalsLeft = splitAssignments(left); + for (Node internal : internalsLeft) { + JavaExpression leftJe = JavaExpression.fromNode(internal); + store.insertValue(leftJe, glbAnno); + } } - // If the result of a numerical subtraction would be LTEL(b) or LTL(b), and b is HSS(a, - // from, to), and the subtraction node itself is i - from where i is LTEL(b), then the - // result is LTEL(a). If i is LTL(b) instead, the result is LTL(a). - - if (leftWithOffset.isLessThanLengthQualifier()) { - - LessThanLengthOf subtractionResult = (LessThanLengthOf) leftWithOffset; - - for (String b : subtractionResult.getSequences()) { - if (subtractionResult.hasSequenceWithOffset(b, -1) - || subtractionResult.hasSequenceWithOffset(b, 0)) { - - TreePath currentPath = this.atypeFactory.getPath(n.getTree()); - JavaExpression je; - try { - je = UpperBoundVisitor.parseJavaExpressionString(b, atypeFactory, currentPath); - } catch (NullPointerException npe) { - // I have no idea why this seems to happen only on a few JDK classes. It - // appears to only happen during the preprocessing step - the NPE is thrown - // while trying to find the enclosing class of a class tree, which is null. - // I can't find a reproducible test case that's smaller than the size of - // DualPivotQuicksort. Since this refinement is optional, but useful - // elsewhere, catching this NPE here and returning is always safe. - return createTransferResult(n, in, leftWithOffset); - } - - Subsequence subsequence = Subsequence.getSubsequenceFromReceiver(je, atypeFactory); - - if (subsequence != null) { - String from = subsequence.from; - String to = subsequence.to; - String a = subsequence.array; - - JavaExpression leftOp = JavaExpression.fromNode(n.getLeftOperand()); - JavaExpression rightOp = JavaExpression.fromNode(n.getRightOperand()); - - if (rightOp.toString().equals(from)) { - LessThanAnnotatedTypeFactory lessThanAtypeFactory = - atypeFactory.getLessThanAnnotatedTypeFactory(); - AnnotationMirror lessThanType = - lessThanAtypeFactory - .getAnnotatedType(n.getLeftOperand().getTree()) - .getAnnotation(LessThan.class); - - if (lessThanType != null && lessThanAtypeFactory.isLessThan(lessThanType, to)) { - UBQualifier ltlA = UBQualifier.createUBQualifier(a, "0"); - leftWithOffset = leftWithOffset.glb(ltlA); - } else if (leftOp.toString().equals(to) - || (lessThanType != null - && lessThanAtypeFactory.isLessThanOrEqual(lessThanType, to))) { - // It's necessary to check if leftOp == to because LessThan doesn't - // infer that things are less than or equal to themselves. - UBQualifier ltelA = UBQualifier.createUBQualifier(a, "-1"); - leftWithOffset = leftWithOffset.glb(ltelA); - } + /** + * If lengthAccess node is an sequence length field or method access (optionally with a constant + * offset subtracted) and the other node is less than or equal to that sequence length (minus + * the offset), then refine the other node's type to less than the sequence length (minus the + * offset). This is case 12. + */ + private void refineNeqSequenceLength( + Node lengthAccess, Node otherNode, AnnotationMirror otherNodeAnno, CFStore store) { + + // If lengthAccess is "receiver.length - c" where c is an integer constant, + // then lengthOffset is "c". + int lengthOffset = 0; + if (lengthAccess instanceof NumericalSubtractionNode) { + NumericalSubtractionNode subtraction = (NumericalSubtractionNode) lengthAccess; + Node offsetNode = subtraction.getRightOperand(); + Long offsetValue = + ValueCheckerUtils.getExactValue( + offsetNode.getTree(), atypeFactory.getValueAnnotatedTypeFactory()); + if (offsetValue != null + && offsetValue > Integer.MIN_VALUE + && offsetValue <= Integer.MAX_VALUE) { + lengthOffset = offsetValue.intValue(); + lengthAccess = subtraction.getLeftOperand(); + } else { + // Subtraction of non-constant expressions is not supported + return; + } + } + + JavaExpression receiver = null; + if (NodeUtils.isArrayLengthFieldAccess(lengthAccess)) { + FieldAccess fa = + (FieldAccess) + JavaExpression.fromNodeFieldAccess((FieldAccessNode) lengthAccess); + receiver = fa.getReceiver(); + + } else if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(lengthAccess)) { + JavaExpression ma = JavaExpression.fromNode(lengthAccess); + if (ma instanceof MethodCall) { + receiver = ((MethodCall) ma).getReceiver(); + } + } + + if (receiver != null && !receiver.containsUnknown()) { + UBQualifier otherQualifier = + UBQualifier.createUBQualifier( + otherNodeAnno, (UpperBoundChecker) atypeFactory.getChecker()); + String sequence = receiver.toString(); + // Check if otherNode + c - 1 < receiver.length + if (otherQualifier.hasSequenceWithOffset(sequence, lengthOffset - 1)) { + // Add otherNode + c < receiver.length + UBQualifier newQualifier = + UBQualifier.createUBQualifier(sequence, Integer.toString(lengthOffset)); + otherQualifier = otherQualifier.glb(newQualifier); + for (Node internal : splitAssignments(otherNode)) { + JavaExpression leftJe = JavaExpression.fromNode(internal); + store.insertValue( + leftJe, atypeFactory.convertUBQualifierToAnnotation(otherQualifier)); + } } - } } - } - } - return createTransferResult(n, in, leftWithOffset); - } - - /** - * Computes a type of a sequence length access. This is case 18. - * - * @param n sequence length access node - */ - private @Nullable TransferResult visitLengthAccess( - Node n, TransferInput in, JavaExpression sequenceJe, Tree sequenceTree) { - if (sequenceTree == null) { - return null; - } - // Look up the SameLen type of the sequence. - AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(sequenceTree); - List sameLenSequences; - if (sameLenAnno == null) { - sameLenSequences = Collections.singletonList(sequenceJe.toString()); - } else { - sameLenSequences = - AnnotationUtils.getElementValueArray( - sameLenAnno, atypeFactory.sameLenValueElement, String.class); - String sequenceString = sequenceJe.toString(); - if (!sameLenSequences.contains(sequenceString)) { - sameLenSequences.add(sequenceString); - } } - List offsets = Collections.nCopies(sameLenSequences.size(), "-1"); + /** + * If some Node a is known to be less than the length of some array, x, then, the type of a + b, + * is @LTLengthOf(value="x", offset="-b"). If b is known to be less than the length of some + * other array, y, then the type of a + b is @LTLengthOf(value={"x", "y"}, offset={"-b", "-a"}). + * + *

          Alternatively, if a is known to be less than the length of x when some offset, o, is added + * to a (the type of a is @LTLengthOf(value="x", offset="o")), then the type of a + b + * is @LTLengthOf(value="x",offset="o - b"). (Note, if "o - b" can be computed, then it is and + * the result is used in the annotation.) + * + *

          In addition, If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int + * and expression j is less than or equal to the length of f1, then the type of i + j is + * .@LTLengthOf("f2"). + * + *

          These three cases correspond to cases 13-15. + */ + @Override + public TransferResult visitNumericalAddition( + NumericalAdditionNode n, TransferInput in) { + // type of leftNode + rightNode is glb(t, s) where + // t = minusOffset(type(leftNode), rightNode) and + // s = minusOffset(type(rightNode), leftNode) + + UBQualifier left = getUBQualifierForAddition(n.getLeftOperand(), in); + UBQualifier t = left.minusOffset(n.getRightOperand(), atypeFactory); + + UBQualifier right = getUBQualifierForAddition(n.getRightOperand(), in); + UBQualifier s = right.minusOffset(n.getLeftOperand(), atypeFactory); + + UBQualifier glb = t.glb(s); + if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) { + // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and + // expression j is less than or equal to the length of f1, then the type of i + j is + // @LTLengthOf("f2"). + UBQualifier r = + removeSequenceLengths((LessThanLengthOf) left, (LessThanLengthOf) right); + glb = glb.glb(r); + UBQualifier l = + removeSequenceLengths((LessThanLengthOf) right, (LessThanLengthOf) left); + glb = glb.glb(l); + } - if (CFAbstractStore.canInsertJavaExpression(sequenceJe)) { - UBQualifier qualifier = UBQualifier.createUBQualifier(sameLenSequences, offsets); - UBQualifier previous = getUBQualifier(n, in); - return createTransferResult(n, in, qualifier.glb(previous)); + return createTransferResult(n, in, glb); } - return null; - } - - /** - * If n is an array length field access, then the type of a.length is the glb - * of @LTEqLengthOf("a") and the value of a.length in the store. This is case 19. - */ - @Override - public TransferResult visitFieldAccess( - FieldAccessNode n, TransferInput in) { - if (NodeUtils.isArrayLengthFieldAccess(n)) { - FieldAccess arrayLength = (FieldAccess) JavaExpression.fromNodeFieldAccess(n); - JavaExpression arrayJe = arrayLength.getReceiver(); - Tree arrayTree = n.getReceiver().getTree(); - TransferResult result = visitLengthAccess(n, in, arrayJe, arrayTree); - if (result != null) { - return result; - } + /** + * Return the result of adding i to j. + * + *

          When expression i has type {@code @LTLengthOf(value = "f2", offset = "f1.length") int} and + * expression j is less than or equal to the length of f1, then the type of i + j + * is @LTLengthOf("f2"). + * + *

          When expression i has type {@code @LTLengthOf (value = "f2", offset = "f1.length - 1") + * int} and expression j is less than the length of f1, then the type of i + j + * is @LTLengthOf("f2"). + * + * @param i the type of the expression added to j + * @param j the type of the expression added to i + * @return the type of i + j + */ + private UBQualifier removeSequenceLengths(LessThanLengthOf i, LessThanLengthOf j) { + List lessThan = new ArrayList<>(); + List lessThanOrEqual = new ArrayList<>(); + for (String sequence : i.getSequences()) { + if (i.isLessThanLengthOf(sequence)) { + lessThan.add(sequence); + } else if (i.hasSequenceWithOffset(sequence, -1)) { + lessThanOrEqual.add(sequence); + } + } + // Creates a qualifier that is the same a j with the array.length offsets removed. If + // an offset doesn't have an array.length, then the offset/array pair is removed. If + // there are no such pairs, Unknown is returned. + UBQualifier lessThanEqQ = j.removeSequenceLengthAccess(lessThanOrEqual); + // Creates a qualifier that is the same a j with the array.length - 1 offsets removed. If + // an offset doesn't have an array.length, then the offset/array pair is removed. If + // there are no such pairs, Unknown is returned. + UBQualifier lessThanQ = j.removeSequenceLengthAccessAndNeg1(lessThan); + + return lessThanEqQ.glb(lessThanQ); } - return super.visitFieldAccess(n, in); - } - - /** - * If n is a String.length() method invocation, then the type of s.length() is the glb - * of @LTEqLengthOf("s") and the value of s.length() in the store. This is case 20. - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - - if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(n)) { - JavaExpression stringLength = JavaExpression.fromNode(n); - if (stringLength instanceof MethodCall) { - JavaExpression receiverJe = ((MethodCall) stringLength).getReceiver(); - Tree receiverTree = n.getTarget().getReceiver().getTree(); - // receiverTree is null when the receiver is implicit "this". - if (receiverTree != null) { - TransferResult result = - visitLengthAccess(n, in, receiverJe, receiverTree); - if (result != null) { - return result; - } + + /** + * If some Node a is known to be less than the length of some sequence x, then the type of a - b + * is @LTLengthOf(value="x", offset="b"). If b is known to be less than the length of some other + * sequence, this doesn't add any information about the type of a - b. But, if b is non-negative + * or positive, then a - b should keep the types of a. This corresponds to cases 16 and 17. + */ + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput in) { + UBQualifier left = getUBQualifier(n.getLeftOperand(), in); + UBQualifier leftWithOffset = left.plusOffset(n.getRightOperand(), atypeFactory); + if (atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), NonNegative.class) + || atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), Positive.class)) { + // If the right side of the expression is NN or POS, then all the left side's + // annotations should be kept. + if (left.isLessThanLengthQualifier()) { + leftWithOffset = left.glb(leftWithOffset); + } + } + + // If the result of a numerical subtraction would be LTEL(b) or LTL(b), and b is HSS(a, + // from, to), and the subtraction node itself is i - from where i is LTEL(b), then the + // result is LTEL(a). If i is LTL(b) instead, the result is LTL(a). + + if (leftWithOffset.isLessThanLengthQualifier()) { + + LessThanLengthOf subtractionResult = (LessThanLengthOf) leftWithOffset; + + for (String b : subtractionResult.getSequences()) { + if (subtractionResult.hasSequenceWithOffset(b, -1) + || subtractionResult.hasSequenceWithOffset(b, 0)) { + + TreePath currentPath = this.atypeFactory.getPath(n.getTree()); + JavaExpression je; + try { + je = + UpperBoundVisitor.parseJavaExpressionString( + b, atypeFactory, currentPath); + } catch (NullPointerException npe) { + // I have no idea why this seems to happen only on a few JDK classes. It + // appears to only happen during the preprocessing step - the NPE is thrown + // while trying to find the enclosing class of a class tree, which is null. + // I can't find a reproducible test case that's smaller than the size of + // DualPivotQuicksort. Since this refinement is optional, but useful + // elsewhere, catching this NPE here and returning is always safe. + return createTransferResult(n, in, leftWithOffset); + } + + Subsequence subsequence = + Subsequence.getSubsequenceFromReceiver(je, atypeFactory); + + if (subsequence != null) { + String from = subsequence.from; + String to = subsequence.to; + String a = subsequence.array; + + JavaExpression leftOp = JavaExpression.fromNode(n.getLeftOperand()); + JavaExpression rightOp = JavaExpression.fromNode(n.getRightOperand()); + + if (rightOp.toString().equals(from)) { + LessThanAnnotatedTypeFactory lessThanAtypeFactory = + atypeFactory.getLessThanAnnotatedTypeFactory(); + AnnotationMirror lessThanType = + lessThanAtypeFactory + .getAnnotatedType(n.getLeftOperand().getTree()) + .getAnnotation(LessThan.class); + + if (lessThanType != null + && lessThanAtypeFactory.isLessThan(lessThanType, to)) { + UBQualifier ltlA = UBQualifier.createUBQualifier(a, "0"); + leftWithOffset = leftWithOffset.glb(ltlA); + } else if (leftOp.toString().equals(to) + || (lessThanType != null + && lessThanAtypeFactory.isLessThanOrEqual( + lessThanType, to))) { + // It's necessary to check if leftOp == to because LessThan doesn't + // infer that things are less than or equal to themselves. + UBQualifier ltelA = UBQualifier.createUBQualifier(a, "-1"); + leftWithOffset = leftWithOffset.glb(ltelA); + } + } + } + } + } } - } + return createTransferResult(n, in, leftWithOffset); } - return super.visitMethodInvocation(n, in); - } - - /** - * Returns the UBQualifier for a node, with additional refinement useful specifically for integer - * addition, based on the information from subcheckers of the Index Checker. - * - * @param n the node - * @param in dataflow analysis transfer input - * @return the UBQualifier for {@code node} - */ - private UBQualifier getUBQualifierForAddition(Node n, TransferInput in) { - - // The method takes the greatest lower bound of the qualifier returned by - // getUBQualifier and a qualifier created from a SubstringIndexFor annotation, if such - // annotation is present and the index is known to be non-negative. - - UBQualifier ubQualifier = getUBQualifier(n, in); - Tree nodeTree = n.getTree(); - // Annotation from the Substring Index hierarchy - AnnotatedTypeMirror substringIndexType = - atypeFactory.getSubstringIndexAnnotatedTypeFactory().getAnnotatedType(nodeTree); - AnnotationMirror substringIndexAnno = substringIndexType.getAnnotation(SubstringIndexFor.class); - // Annotation from the Lower bound hierarchy - AnnotatedTypeMirror lowerBoundType = - atypeFactory.getLowerBoundAnnotatedTypeFactory().getAnnotatedType(nodeTree); - // If the index has an SubstringIndexFor annotation and at the same time is non-negative, - // convert the SubstringIndexFor annotation to a upper bound qualifier. - if (substringIndexAnno != null - && (lowerBoundType.hasAnnotation(NonNegative.class) - || lowerBoundType.hasAnnotation(Positive.class))) { - UBQualifier substringIndexQualifier = - UBQualifier.createUBQualifier( - substringIndexAnno, (UpperBoundChecker) atypeFactory.getChecker()); - ubQualifier = ubQualifier.glb(substringIndexQualifier); + + /** + * Computes a type of a sequence length access. This is case 18. + * + * @param n sequence length access node + */ + private @Nullable TransferResult visitLengthAccess( + Node n, + TransferInput in, + JavaExpression sequenceJe, + Tree sequenceTree) { + if (sequenceTree == null) { + return null; + } + // Look up the SameLen type of the sequence. + AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(sequenceTree); + List sameLenSequences; + if (sameLenAnno == null) { + sameLenSequences = Collections.singletonList(sequenceJe.toString()); + } else { + sameLenSequences = + AnnotationUtils.getElementValueArray( + sameLenAnno, atypeFactory.sameLenValueElement, String.class); + String sequenceString = sequenceJe.toString(); + if (!sameLenSequences.contains(sequenceString)) { + sameLenSequences.add(sequenceString); + } + } + + List offsets = Collections.nCopies(sameLenSequences.size(), "-1"); + + if (CFAbstractStore.canInsertJavaExpression(sequenceJe)) { + UBQualifier qualifier = UBQualifier.createUBQualifier(sameLenSequences, offsets); + UBQualifier previous = getUBQualifier(n, in); + return createTransferResult(n, in, qualifier.glb(previous)); + } + + return null; } - return ubQualifier; - } - - /** - * Returns the UBQualifier for node. It does this by finding a {@link CFValue} for node. First it - * checks the store in the transfer input. If one isn't there, the analysis is checked. If the - * UNKNOWN qualifier is returned, then the AnnotatedTypeMirror from the type factory is used. - * - * @param n node - * @param in transfer input - * @return the UBQualifier for node - */ - private UBQualifier getUBQualifier(Node n, TransferInput in) { - QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - JavaExpression je = JavaExpression.fromNode(n); - CFValue value = null; - if (CFAbstractStore.canInsertJavaExpression(je)) { - value = in.getRegularStore().getValue(je); + + /** + * If n is an array length field access, then the type of a.length is the glb + * of @LTEqLengthOf("a") and the value of a.length in the store. This is case 19. + */ + @Override + public TransferResult visitFieldAccess( + FieldAccessNode n, TransferInput in) { + if (NodeUtils.isArrayLengthFieldAccess(n)) { + FieldAccess arrayLength = (FieldAccess) JavaExpression.fromNodeFieldAccess(n); + JavaExpression arrayJe = arrayLength.getReceiver(); + Tree arrayTree = n.getReceiver().getTree(); + TransferResult result = visitLengthAccess(n, in, arrayJe, arrayTree); + if (result != null) { + return result; + } + } + return super.visitFieldAccess(n, in); } - if (value == null) { - value = analysis.getValue(n); + + /** + * If n is a String.length() method invocation, then the type of s.length() is the glb + * of @LTEqLengthOf("s") and the value of s.length() in the store. This is case 20. + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + + if (atypeFactory.getMethodIdentifier().isLengthOfMethodInvocation(n)) { + JavaExpression stringLength = JavaExpression.fromNode(n); + if (stringLength instanceof MethodCall) { + JavaExpression receiverJe = ((MethodCall) stringLength).getReceiver(); + Tree receiverTree = n.getTarget().getReceiver().getTree(); + // receiverTree is null when the receiver is implicit "this". + if (receiverTree != null) { + TransferResult result = + visitLengthAccess(n, in, receiverJe, receiverTree); + if (result != null) { + return result; + } + } + } + } + return super.visitMethodInvocation(n, in); } - UBQualifier qualifier = getUBQualifier(hierarchy, value); - if (qualifier.isUnknown()) { - // The qualifier from the store or analysis might be UNKNOWN if there was some error. - // For example, - // @LTLength("a") int i = 4; // error - // The type of i in the store is @UpperBoundUnknown, but the type of i as computed by - // the type factory is @LTLength("a"), so use that type. - CFValue valueFromFactory = getValueFromFactory(n.getTree(), n); - return getUBQualifier(hierarchy, valueFromFactory); + + /** + * Returns the UBQualifier for a node, with additional refinement useful specifically for + * integer addition, based on the information from subcheckers of the Index Checker. + * + * @param n the node + * @param in dataflow analysis transfer input + * @return the UBQualifier for {@code node} + */ + private UBQualifier getUBQualifierForAddition(Node n, TransferInput in) { + + // The method takes the greatest lower bound of the qualifier returned by + // getUBQualifier and a qualifier created from a SubstringIndexFor annotation, if such + // annotation is present and the index is known to be non-negative. + + UBQualifier ubQualifier = getUBQualifier(n, in); + Tree nodeTree = n.getTree(); + // Annotation from the Substring Index hierarchy + AnnotatedTypeMirror substringIndexType = + atypeFactory.getSubstringIndexAnnotatedTypeFactory().getAnnotatedType(nodeTree); + AnnotationMirror substringIndexAnno = + substringIndexType.getAnnotation(SubstringIndexFor.class); + // Annotation from the Lower bound hierarchy + AnnotatedTypeMirror lowerBoundType = + atypeFactory.getLowerBoundAnnotatedTypeFactory().getAnnotatedType(nodeTree); + // If the index has an SubstringIndexFor annotation and at the same time is non-negative, + // convert the SubstringIndexFor annotation to a upper bound qualifier. + if (substringIndexAnno != null + && (lowerBoundType.hasAnnotation(NonNegative.class) + || lowerBoundType.hasAnnotation(Positive.class))) { + UBQualifier substringIndexQualifier = + UBQualifier.createUBQualifier( + substringIndexAnno, (UpperBoundChecker) atypeFactory.getChecker()); + ubQualifier = ubQualifier.glb(substringIndexQualifier); + } + return ubQualifier; } - return qualifier; - } - private UBQualifier getUBQualifier(QualifierHierarchy hierarchy, CFValue value) { - if (value == null) { - return UpperBoundUnknownQualifier.UNKNOWN; + /** + * Returns the UBQualifier for node. It does this by finding a {@link CFValue} for node. First + * it checks the store in the transfer input. If one isn't there, the analysis is checked. If + * the UNKNOWN qualifier is returned, then the AnnotatedTypeMirror from the type factory is + * used. + * + * @param n node + * @param in transfer input + * @return the UBQualifier for node + */ + private UBQualifier getUBQualifier(Node n, TransferInput in) { + QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + JavaExpression je = JavaExpression.fromNode(n); + CFValue value = null; + if (CFAbstractStore.canInsertJavaExpression(je)) { + value = in.getRegularStore().getValue(je); + } + if (value == null) { + value = analysis.getValue(n); + } + UBQualifier qualifier = getUBQualifier(hierarchy, value); + if (qualifier.isUnknown()) { + // The qualifier from the store or analysis might be UNKNOWN if there was some error. + // For example, + // @LTLength("a") int i = 4; // error + // The type of i in the store is @UpperBoundUnknown, but the type of i as computed by + // the type factory is @LTLength("a"), so use that type. + CFValue valueFromFactory = getValueFromFactory(n.getTree(), n); + return getUBQualifier(hierarchy, valueFromFactory); + } + return qualifier; } - AnnotationMirrorSet set = value.getAnnotations(); - AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(set, atypeFactory.UNKNOWN); - if (anno == null) { - return UpperBoundUnknownQualifier.UNKNOWN; + + private UBQualifier getUBQualifier(QualifierHierarchy hierarchy, CFValue value) { + if (value == null) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + AnnotationMirrorSet set = value.getAnnotations(); + AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(set, atypeFactory.UNKNOWN); + if (anno == null) { + return UpperBoundUnknownQualifier.UNKNOWN; + } + return UBQualifier.createUBQualifier(anno, (UpperBoundChecker) atypeFactory.getChecker()); } - return UBQualifier.createUBQualifier(anno, (UpperBoundChecker) atypeFactory.getChecker()); - } - - private TransferResult createTransferResult( - Node n, TransferInput in, UBQualifier qualifier) { - AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(qualifier); - CFValue value = analysis.createSingleAnnotationValue(newAnno, n.getType()); - return createTransferResult(value, in); - } - - @Override - public TransferResult visitCase( - CaseNode n, TransferInput in) { - TransferResult result = super.visitCase(n, in); - // Refines subtrahend in the switch expression - // TODO: This cannot be done in strengthenAnnotationOfEqualTo, because that does not provide - // transfer input. - List caseNodes = n.getCaseOperands(); - AssignmentNode assign = n.getSwitchOperand(); - Node switchNode = assign.getExpression(); - for (Node caseNode : caseNodes) { - refineSubtrahendWithOffset(switchNode, caseNode, false, in, result.getThenStore()); + + private TransferResult createTransferResult( + Node n, TransferInput in, UBQualifier qualifier) { + AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(qualifier); + CFValue value = analysis.createSingleAnnotationValue(newAnno, n.getType()); + return createTransferResult(value, in); } - return result; - } - - @Override - public TransferResult visitIntegerLiteral( - IntegerLiteralNode n, TransferInput pi) { - TransferResult result = super.visitIntegerLiteral(n, pi); - - int intValue = n.getValue(); - AnnotationMirror newAnno; - switch (intValue) { - case 0: - newAnno = atypeFactory.ZERO; - break; - case -1: - newAnno = atypeFactory.NEGATIVEONE; - break; - default: + + @Override + public TransferResult visitCase( + CaseNode n, TransferInput in) { + TransferResult result = super.visitCase(n, in); + // Refines subtrahend in the switch expression + // TODO: This cannot be done in strengthenAnnotationOfEqualTo, because that does not provide + // transfer input. + List caseNodes = n.getCaseOperands(); + AssignmentNode assign = n.getSwitchOperand(); + Node switchNode = assign.getExpression(); + for (Node caseNode : caseNodes) { + refineSubtrahendWithOffset(switchNode, caseNode, false, in, result.getThenStore()); + } return result; } - CFValue c = new CFValue(analysis, AnnotationMirrorSet.singleton(newAnno), intTM); - return new RegularTransferResult<>(c, result.getRegularStore()); - } + + @Override + public TransferResult visitIntegerLiteral( + IntegerLiteralNode n, TransferInput pi) { + TransferResult result = super.visitIntegerLiteral(n, pi); + + int intValue = n.getValue(); + AnnotationMirror newAnno; + switch (intValue) { + case 0: + newAnno = atypeFactory.ZERO; + break; + case -1: + newAnno = atypeFactory.NEGATIVEONE; + break; + default: + return result; + } + CFValue c = new CFValue(analysis, AnnotationMirrorSet.singleton(newAnno), intTM); + return new RegularTransferResult<>(c, result.getRegularStore()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java index 98a7aaf4b84..c04714bbc46 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java @@ -7,11 +7,7 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.index.Subsequence; import org.checkerframework.checker.index.qual.HasSubsequence; @@ -39,485 +35,515 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; + /** Warns about array accesses that could be too high. */ public class UpperBoundVisitor extends BaseTypeVisitor { - private static final @CompilerMessageKey String UPPER_BOUND = "array.access.unsafe.high"; - private static final @CompilerMessageKey String UPPER_BOUND_CONST = - "array.access.unsafe.high.constant"; - private static final @CompilerMessageKey String UPPER_BOUND_RANGE = - "array.access.unsafe.high.range"; - private static final @CompilerMessageKey String TO_NOT_LTEL = "to.not.ltel"; - private static final @CompilerMessageKey String NOT_FINAL = "not.final"; - private static final @CompilerMessageKey String HSS = "which.subsequence"; - - public UpperBoundVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * When the visitor reaches an array access, it needs to check a couple of things. First, it - * checks if the index has been assigned a reasonable UpperBound type: only an index with type - * LTLengthOf(arr) is safe to access arr. If that fails, it checks if the access is still safe. To - * do so, it checks if the Value Checker knows the minimum length of arr by querying the Value - * Annotated Type Factory. If the minimum length of the array is known, the visitor can check if - * the index is less than that minimum length. If so, then the access is still safe. Otherwise, - * report a potential unsafe access. - */ - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void type) { - ExpressionTree indexTree = tree.getIndex(); - ExpressionTree arrTree = tree.getExpression(); - visitAccess(indexTree, arrTree); - return super.visitArrayAccess(tree, type); - } - - /** Warns about LTLengthOf annotations with arguments whose lengths do not match. */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - if (atypeFactory.areSameByClass(anno, LTLengthOf.class)) { - List args = tree.getArguments(); - if (args.size() == 2) { - // If offsets are provided, there must be the same number of them as there are - // arrays. - List sequences = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.ltLengthOfValueElement, String.class); - List offsets = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.ltLengthOfOffsetElement, String.class, Collections.emptyList()); - if (sequences.size() != offsets.size() && !offsets.isEmpty()) { - checker.reportError( - tree, "different.length.sequences.offsets", sequences.size(), offsets.size()); - return null; - } - } - } else if (atypeFactory.areSameByClass(anno, HasSubsequence.class)) { - // Check that the arguments to a HasSubsequence annotation are valid JavaExpressions, - // and issue an error if one of them is not. - - String seq = atypeFactory.hasSubsequenceSubsequenceValue(anno); - String from = atypeFactory.hasSubsequenceFromValue(anno); - String to = atypeFactory.hasSubsequenceToValue(anno); - - // check that each expression is parsable at the declaration of this class - ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); - checkEffectivelyFinalAndParsable(seq, enclosingClass, tree); - checkEffectivelyFinalAndParsable(from, enclosingClass, tree); - checkEffectivelyFinalAndParsable(to, enclosingClass, tree); - } - return super.visitAnnotation(tree, p); - } - - /** - * Reports an error if the Java expression named by s is not effectively final when parsed at the - * declaration of the given class. - * - * @param s a Java expression - * @param classTree the expression is parsed with respect to this class - * @param whereToReportError the tree at which to possibly report an error - */ - private void checkEffectivelyFinalAndParsable( - String s, ClassTree classTree, Tree whereToReportError) { - JavaExpression je; - try { - je = - StringToJavaExpression.atTypeDecl( - s, TreeUtils.elementFromDeclaration(classTree), checker); - } catch (JavaExpressionParseException e) { - checker.report(whereToReportError, e.getDiagMessage()); - return; - } - Element element = null; - if (je instanceof LocalVariable) { - element = ((LocalVariable) je).getElement(); - } else if (je instanceof FieldAccess) { - element = ((FieldAccess) je).getField(); - } else if (je instanceof ThisReference || je instanceof ValueLiteral) { - return; - } - if (element == null || !ElementUtils.isEffectivelyFinal(element)) { - checker.reportError(whereToReportError, NOT_FINAL, je); - } - } - - /** - * Checks if this array access is legal. Uses the common assignment check and a simple MinLen - * check of its own. The MinLen check is needed because the common assignment check always returns - * false when the upper bound qualifier is @UpperBoundUnknown. - * - * @param indexTree the array index - * @param arrTree the array - */ - private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) { - - String arrName = JavaExpression.fromTree(arrTree).toString(); - LessThanLengthOf lhsQual = (LessThanLengthOf) UBQualifier.createUBQualifier(arrName, "0"); - if (relaxedCommonAssignmentCheck(lhsQual, indexTree) || checkMinLen(indexTree, arrTree)) { - return; - } // else issue errors. - - // We can issue three different errors: - // 1. If the index is a compile-time constant, issue an error that describes the array type. - // 2. If the index is a compile-time range and has no upperbound qualifier, - // issue an error that names the upperbound of the range and the array's type. - // 3. If neither of the above, issue an error that names the upper bound type. - - AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(indexTree); - UBQualifier qualifier = - UBQualifier.createUBQualifier(indexType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - ValueAnnotatedTypeFactory valueFactory = atypeFactory.getValueAnnotatedTypeFactory(); - Long valMax = ValueCheckerUtils.getMaxValue(indexTree, valueFactory); - - if (ValueCheckerUtils.getExactValue(indexTree, valueFactory) != null) { - // Note that valMax is equal to the exact value in this case. - checker.reportError( - indexTree, - UPPER_BOUND_CONST, - valMax, - valueFactory.getAnnotatedType(arrTree).toString(), - valMax + 1, - valMax + 1); - } else if (valMax != null && qualifier.isUnknown() && valMax != Integer.MAX_VALUE) { - - checker.reportError( - indexTree, - UPPER_BOUND_RANGE, - valueFactory.getAnnotatedType(indexTree).toString(), - valueFactory.getAnnotatedType(arrTree).toString(), - arrName, - arrName, - valMax + 1); - } else { - checker.reportError(indexTree, UPPER_BOUND, indexType.toString(), arrName, arrName, arrName); - } - } - - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - boolean result = true; - - // check that when an assignment to a variable b declared as @HasSubsequence(a, from, to) - // occurs, to <= a.length, i.e. to is @LTEqLengthOf(a). - - Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); - if (subSeq != null) { - AnnotationMirror anm; - try { - anm = - atypeFactory.getAnnotationMirrorFromJavaExpressionString( - subSeq.to, varTree, getCurrentPath()); - } catch (JavaExpressionParseException e) { - anm = null; - } - - boolean ltelCheckFailed = true; - if (anm != null) { - UBQualifier qual = UBQualifier.createUBQualifier(anm, (UpperBoundChecker) checker); - ltelCheckFailed = !qual.isLessThanOrEqualTo(subSeq.array); - } - - if (ltelCheckFailed) { - // issue an error - checker.reportError( - valueTree, - TO_NOT_LTEL, - subSeq.to, - subSeq.array, - anm == null ? "@UpperBoundUnknown" : anm, - subSeq.array, - subSeq.array, - subSeq.array); - result = false; - } else { - checker.reportWarning( - valueTree, - HSS, - subSeq.array, - subSeq.from, - subSeq.from, - subSeq.to, - subSeq.to, - subSeq.array, - subSeq.array); - } + private static final @CompilerMessageKey String UPPER_BOUND = "array.access.unsafe.high"; + private static final @CompilerMessageKey String UPPER_BOUND_CONST = + "array.access.unsafe.high.constant"; + private static final @CompilerMessageKey String UPPER_BOUND_RANGE = + "array.access.unsafe.high.range"; + private static final @CompilerMessageKey String TO_NOT_LTEL = "to.not.ltel"; + private static final @CompilerMessageKey String NOT_FINAL = "not.final"; + private static final @CompilerMessageKey String HSS = "which.subsequence"; + + public UpperBoundVisitor(BaseTypeChecker checker) { + super(checker); } - result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; - return result; - } - - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree); - commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); - boolean result = true; - String diagnosticMessage = ""; - if (!relaxedCommonAssignment(varType, valueTree)) { - commonAssignmentCheckEndDiagnostic( - "relaxedCommonAssignment did not succeed, now must call super", - varType, - valueType, - valueTree); - result = super.commonAssignmentCheck(varType, valueTree, errorKey, extraArgs); - if (!result && showchecks) { - diagnosticMessage = "relaxedCommonAssignment()=>false and super()=>false"; - } + /** + * When the visitor reaches an array access, it needs to check a couple of things. First, it + * checks if the index has been assigned a reasonable UpperBound type: only an index with type + * LTLengthOf(arr) is safe to access arr. If that fails, it checks if the access is still safe. + * To do so, it checks if the Value Checker knows the minimum length of arr by querying the + * Value Annotated Type Factory. If the minimum length of the array is known, the visitor can + * check if the index is less than that minimum length. If so, then the access is still safe. + * Otherwise, report a potential unsafe access. + */ + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void type) { + ExpressionTree indexTree = tree.getIndex(); + ExpressionTree arrTree = tree.getExpression(); + visitAccess(indexTree, arrTree); + return super.visitArrayAccess(tree, type); } - commonAssignmentCheckEndDiagnostic(result, diagnosticMessage, varType, valueType, valueTree); - return result; - } - - /** - * Returns whether the assignment is legal based on the relaxed assignment rules. - * - *

          The relaxed assignment rules are the following: Assuming the varType (left-hand side) is - * less than the length of some array given some offset - * - *

          1. If both the offset and the value expression (rhs) are ints known at compile time, and if - * the min length of the array is greater than offset + value, then the assignment is legal. (This - * method returns true.) - * - *

          2. If the value expression (rhs) is less than the length of an array that is the same length - * as the array in the varType, and if the offsets are equal, then the assignment is legal. (This - * method returns true.) - * - *

          3. Otherwise the assignment is only legal if the usual assignment rules are true, so this - * method returns false. - * - *

          If the varType is less than the length of multiple arrays, then this method only returns - * true if the relaxed rules above apply for each array. - * - *

          If the varType is an array type and the value expression is an array initializer, then the - * above rules are applied for expression in the initializer where the varType is the component - * type of the array. - * - * @param varType the type of the left-hand side (the variable in the assignment) - * @param valueExp the right-hand side (the expression in the assignment) - * @return true if the assignment is legal based on special Upper Bound rules - */ - private boolean relaxedCommonAssignment(AnnotatedTypeMirror varType, ExpressionTree valueExp) { - if (valueExp.getKind() == Tree.Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) { - List expressions = ((NewArrayTree) valueExp).getInitializers(); - if (expressions == null || expressions.isEmpty()) { - return false; - } - // The qualifier we need for an array is in the component type, not varType. - AnnotatedTypeMirror componentType = ((AnnotatedArrayType) varType).getComponentType(); - UBQualifier qualifier = - UBQualifier.createUBQualifier( - componentType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - if (!qualifier.isLessThanLengthQualifier()) { - return false; - } - for (ExpressionTree expressionTree : expressions) { - if (!relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, expressionTree)) { - return false; + + /** Warns about LTLengthOf annotations with arguments whose lengths do not match. */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + if (atypeFactory.areSameByClass(anno, LTLengthOf.class)) { + List args = tree.getArguments(); + if (args.size() == 2) { + // If offsets are provided, there must be the same number of them as there are + // arrays. + List sequences = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.ltLengthOfValueElement, String.class); + List offsets = + AnnotationUtils.getElementValueArray( + anno, + atypeFactory.ltLengthOfOffsetElement, + String.class, + Collections.emptyList()); + if (sequences.size() != offsets.size() && !offsets.isEmpty()) { + checker.reportError( + tree, + "different.length.sequences.offsets", + sequences.size(), + offsets.size()); + return null; + } + } + } else if (atypeFactory.areSameByClass(anno, HasSubsequence.class)) { + // Check that the arguments to a HasSubsequence annotation are valid JavaExpressions, + // and issue an error if one of them is not. + + String seq = atypeFactory.hasSubsequenceSubsequenceValue(anno); + String from = atypeFactory.hasSubsequenceFromValue(anno); + String to = atypeFactory.hasSubsequenceToValue(anno); + + // check that each expression is parsable at the declaration of this class + ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); + checkEffectivelyFinalAndParsable(seq, enclosingClass, tree); + checkEffectivelyFinalAndParsable(from, enclosingClass, tree); + checkEffectivelyFinalAndParsable(to, enclosingClass, tree); } - } - return true; + return super.visitAnnotation(tree, p); } - UBQualifier qualifier = - UBQualifier.createUBQualifier(varType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - return qualifier.isLessThanLengthQualifier() - && relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, valueExp); - } - - /** - * Fetches a receiver and an offset from a String using the passed type factory. Returns null if - * there is a parse exception. This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. - * - *

          This is useful for expressions like "n+1", for which {@link #parseJavaExpressionString} - * returns null because the whole expression is not a receiver. - */ - static @Nullable IPair getExpressionAndOffsetFromJavaExpressionString( - String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { - - IPair p = AnnotatedTypeFactory.getExpressionAndOffset(s); - - JavaExpression je = parseJavaExpressionString(p.first, atypeFactory, currentPath); - if (je == null) { - return null; - } - return IPair.of(je, p.second); - } - - /** - * Fetches a receiver from a String using the passed type factory. Returns null if there is a - * parse exception -- that is, if the string does not represent an expression for a - * JavaExpression. For example, the expression "n+1" does not represent a JavaExpression. - * - *

          This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. - */ - static @Nullable JavaExpression parseJavaExpressionString( - String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { - JavaExpression result; - try { - result = atypeFactory.parseJavaExpressionString(s, currentPath); - } catch (JavaExpressionParseException e) { - result = null; + /** + * Reports an error if the Java expression named by s is not effectively final when parsed at + * the declaration of the given class. + * + * @param s a Java expression + * @param classTree the expression is parsed with respect to this class + * @param whereToReportError the tree at which to possibly report an error + */ + private void checkEffectivelyFinalAndParsable( + String s, ClassTree classTree, Tree whereToReportError) { + JavaExpression je; + try { + je = + StringToJavaExpression.atTypeDecl( + s, TreeUtils.elementFromDeclaration(classTree), checker); + } catch (JavaExpressionParseException e) { + checker.report(whereToReportError, e.getDiagMessage()); + return; + } + Element element = null; + if (je instanceof LocalVariable) { + element = ((LocalVariable) je).getElement(); + } else if (je instanceof FieldAccess) { + element = ((FieldAccess) je).getField(); + } else if (je instanceof ThisReference || je instanceof ValueLiteral) { + return; + } + if (element == null || !ElementUtils.isEffectivelyFinal(element)) { + checker.reportError(whereToReportError, NOT_FINAL, je); + } } - return result; - } - - /* - * Queries the Value Checker to determine if the maximum possible value of indexTree - * is less than the minimum possible length of arrTree, and returns true if so. - */ - private boolean checkMinLen(ExpressionTree indexTree, ExpressionTree arrTree) { - int minLen = ValueCheckerUtils.getMinLen(arrTree, atypeFactory.getValueAnnotatedTypeFactory()); - Long valMax = - ValueCheckerUtils.getMaxValue(indexTree, atypeFactory.getValueAnnotatedTypeFactory()); - return valMax != null && valMax < minLen; - } - - /** - * Implements the actual check for the relaxed common assignment check. For what is permitted, see - * {@link #relaxedCommonAssignment}. - * - * @param varLtlQual the variable qualifier (the left-hand side of the assignment) - * @param valueExp the expression (the right-hand side of the assignment) - * @return true if the assignment is legal: varLtlQual is a supertype of the type of valueExp - */ - private boolean relaxedCommonAssignmentCheck( - LessThanLengthOf varLtlQual, ExpressionTree valueExp) { - - AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(valueExp); - UBQualifier expQual = - UBQualifier.createUBQualifier(expType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); - - UBQualifier lessThanQual = atypeFactory.fromLessThan(valueExp, getCurrentPath()); - if (lessThanQual != null) { - expQual = expQual.glb(lessThanQual); + + /** + * Checks if this array access is legal. Uses the common assignment check and a simple MinLen + * check of its own. The MinLen check is needed because the common assignment check always + * returns false when the upper bound qualifier is @UpperBoundUnknown. + * + * @param indexTree the array index + * @param arrTree the array + */ + private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) { + + String arrName = JavaExpression.fromTree(arrTree).toString(); + LessThanLengthOf lhsQual = (LessThanLengthOf) UBQualifier.createUBQualifier(arrName, "0"); + if (relaxedCommonAssignmentCheck(lhsQual, indexTree) || checkMinLen(indexTree, arrTree)) { + return; + } // else issue errors. + + // We can issue three different errors: + // 1. If the index is a compile-time constant, issue an error that describes the array type. + // 2. If the index is a compile-time range and has no upperbound qualifier, + // issue an error that names the upperbound of the range and the array's type. + // 3. If neither of the above, issue an error that names the upper bound type. + + AnnotatedTypeMirror indexType = atypeFactory.getAnnotatedType(indexTree); + UBQualifier qualifier = + UBQualifier.createUBQualifier( + indexType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + ValueAnnotatedTypeFactory valueFactory = atypeFactory.getValueAnnotatedTypeFactory(); + Long valMax = ValueCheckerUtils.getMaxValue(indexTree, valueFactory); + + if (ValueCheckerUtils.getExactValue(indexTree, valueFactory) != null) { + // Note that valMax is equal to the exact value in this case. + checker.reportError( + indexTree, + UPPER_BOUND_CONST, + valMax, + valueFactory.getAnnotatedType(arrTree).toString(), + valMax + 1, + valMax + 1); + } else if (valMax != null && qualifier.isUnknown() && valMax != Integer.MAX_VALUE) { + + checker.reportError( + indexTree, + UPPER_BOUND_RANGE, + valueFactory.getAnnotatedType(indexTree).toString(), + valueFactory.getAnnotatedType(arrTree).toString(), + arrName, + arrName, + valMax + 1); + } else { + checker.reportError( + indexTree, UPPER_BOUND, indexType.toString(), arrName, arrName, arrName); + } } - UBQualifier lessThanOrEqualQual = atypeFactory.fromLessThanOrEqual(valueExp, getCurrentPath()); - if (lessThanOrEqualQual != null) { - expQual = expQual.glb(lessThanOrEqualQual); + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + boolean result = true; + + // check that when an assignment to a variable b declared as @HasSubsequence(a, from, to) + // occurs, to <= a.length, i.e. to is @LTEqLengthOf(a). + + Subsequence subSeq = Subsequence.getSubsequenceFromTree(varTree, atypeFactory); + if (subSeq != null) { + AnnotationMirror anm; + try { + anm = + atypeFactory.getAnnotationMirrorFromJavaExpressionString( + subSeq.to, varTree, getCurrentPath()); + } catch (JavaExpressionParseException e) { + anm = null; + } + + boolean ltelCheckFailed = true; + if (anm != null) { + UBQualifier qual = UBQualifier.createUBQualifier(anm, (UpperBoundChecker) checker); + ltelCheckFailed = !qual.isLessThanOrEqualTo(subSeq.array); + } + + if (ltelCheckFailed) { + // issue an error + checker.reportError( + valueTree, + TO_NOT_LTEL, + subSeq.to, + subSeq.array, + anm == null ? "@UpperBoundUnknown" : anm, + subSeq.array, + subSeq.array, + subSeq.array); + result = false; + } else { + checker.reportWarning( + valueTree, + HSS, + subSeq.array, + subSeq.from, + subSeq.from, + subSeq.to, + subSeq.to, + subSeq.array, + subSeq.array); + } + } + + result = super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs) && result; + return result; } - if (expQual.isSubtype(varLtlQual)) { - return true; + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree); + commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); + boolean result = true; + String diagnosticMessage = ""; + if (!relaxedCommonAssignment(varType, valueTree)) { + commonAssignmentCheckEndDiagnostic( + "relaxedCommonAssignment did not succeed, now must call super", + varType, + valueType, + valueTree); + result = super.commonAssignmentCheck(varType, valueTree, errorKey, extraArgs); + if (!result && showchecks) { + diagnosticMessage = "relaxedCommonAssignment()=>false and super()=>false"; + } + } + commonAssignmentCheckEndDiagnostic( + result, diagnosticMessage, varType, valueType, valueTree); + return result; } - // Take advantage of information available on a HasSubsequence(a, from, to) annotation - // on the lhs qualifier (varLtlQual): - // this allows us to show that iff varLtlQual includes LTL(b), b has HSS, and expQual - // includes LTL(a, -from), then the LTL(b) can be removed from varLtlQual. + /** + * Returns whether the assignment is legal based on the relaxed assignment rules. + * + *

          The relaxed assignment rules are the following: Assuming the varType (left-hand side) is + * less than the length of some array given some offset + * + *

          1. If both the offset and the value expression (rhs) are ints known at compile time, and + * if the min length of the array is greater than offset + value, then the assignment is legal. + * (This method returns true.) + * + *

          2. If the value expression (rhs) is less than the length of an array that is the same + * length as the array in the varType, and if the offsets are equal, then the assignment is + * legal. (This method returns true.) + * + *

          3. Otherwise the assignment is only legal if the usual assignment rules are true, so this + * method returns false. + * + *

          If the varType is less than the length of multiple arrays, then this method only returns + * true if the relaxed rules above apply for each array. + * + *

          If the varType is an array type and the value expression is an array initializer, then the + * above rules are applied for expression in the initializer where the varType is the component + * type of the array. + * + * @param varType the type of the left-hand side (the variable in the assignment) + * @param valueExp the right-hand side (the expression in the assignment) + * @return true if the assignment is legal based on special Upper Bound rules + */ + private boolean relaxedCommonAssignment(AnnotatedTypeMirror varType, ExpressionTree valueExp) { + if (valueExp.getKind() == Tree.Kind.NEW_ARRAY && varType.getKind() == TypeKind.ARRAY) { + List expressions = + ((NewArrayTree) valueExp).getInitializers(); + if (expressions == null || expressions.isEmpty()) { + return false; + } + // The qualifier we need for an array is in the component type, not varType. + AnnotatedTypeMirror componentType = ((AnnotatedArrayType) varType).getComponentType(); + UBQualifier qualifier = + UBQualifier.createUBQualifier( + componentType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + if (!qualifier.isLessThanLengthQualifier()) { + return false; + } + for (ExpressionTree expressionTree : expressions) { + if (!relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, expressionTree)) { + return false; + } + } + return true; + } - UBQualifier newLHS = processSubsequenceForLHS(varLtlQual, expQual); - if (newLHS.isUnknown()) { - return true; - } else { - varLtlQual = (LessThanLengthOf) newLHS; + UBQualifier qualifier = + UBQualifier.createUBQualifier( + varType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + return qualifier.isLessThanLengthQualifier() + && relaxedCommonAssignmentCheck((LessThanLengthOf) qualifier, valueExp); } - Long value = - ValueCheckerUtils.getMaxValue(valueExp, atypeFactory.getValueAnnotatedTypeFactory()); - - if (value == null && !expQual.isLessThanLengthQualifier()) { - return false; + /** + * Fetches a receiver and an offset from a String using the passed type factory. Returns null if + * there is a parse exception. This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. + * + *

          This is useful for expressions like "n+1", for which {@link #parseJavaExpressionString} + * returns null because the whole expression is not a receiver. + */ + static @Nullable IPair getExpressionAndOffsetFromJavaExpressionString( + String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { + + IPair p = AnnotatedTypeFactory.getExpressionAndOffset(s); + + JavaExpression je = parseJavaExpressionString(p.first, atypeFactory, currentPath); + if (je == null) { + return null; + } + return IPair.of(je, p.second); } - SameLenAnnotatedTypeFactory sameLenFactory = atypeFactory.getSameLenAnnotatedTypeFactory(); - ValueAnnotatedTypeFactory valueAnnotatedTypeFactory = - atypeFactory.getValueAnnotatedTypeFactory(); - checkloop: - for (String sequenceName : varLtlQual.getSequences()) { - - List sameLenSequences = - sameLenFactory.getSameLensFromString(sequenceName, valueExp, getCurrentPath()); - if (testSameLen(expQual, varLtlQual, sameLenSequences, sequenceName)) { - continue; - } - - int minlen = - valueAnnotatedTypeFactory.getMinLenFromString(sequenceName, valueExp, getCurrentPath()); - if (testMinLen(value, minlen, sequenceName, varLtlQual)) { - continue; - } - for (String sequence : sameLenSequences) { - int minlenSL = - valueAnnotatedTypeFactory.getMinLenFromString(sequence, valueExp, getCurrentPath()); - if (testMinLen(value, minlenSL, sequenceName, varLtlQual)) { - continue checkloop; + /** + * Fetches a receiver from a String using the passed type factory. Returns null if there is a + * parse exception -- that is, if the string does not represent an expression for a + * JavaExpression. For example, the expression "n+1" does not represent a JavaExpression. + * + *

          This wraps GenericAnnotatedTypeFactory#parseJavaExpressionString. + */ + static @Nullable JavaExpression parseJavaExpressionString( + String s, UpperBoundAnnotatedTypeFactory atypeFactory, TreePath currentPath) { + JavaExpression result; + try { + result = atypeFactory.parseJavaExpressionString(s, currentPath); + } catch (JavaExpressionParseException e) { + result = null; } - } + return result; + } - return false; + /* + * Queries the Value Checker to determine if the maximum possible value of indexTree + * is less than the minimum possible length of arrTree, and returns true if so. + */ + private boolean checkMinLen(ExpressionTree indexTree, ExpressionTree arrTree) { + int minLen = + ValueCheckerUtils.getMinLen(arrTree, atypeFactory.getValueAnnotatedTypeFactory()); + Long valMax = + ValueCheckerUtils.getMaxValue( + indexTree, atypeFactory.getValueAnnotatedTypeFactory()); + return valMax != null && valMax < minLen; } - return true; - } + /** + * Implements the actual check for the relaxed common assignment check. For what is permitted, + * see {@link #relaxedCommonAssignment}. + * + * @param varLtlQual the variable qualifier (the left-hand side of the assignment) + * @param valueExp the expression (the right-hand side of the assignment) + * @return true if the assignment is legal: varLtlQual is a supertype of the type of valueExp + */ + private boolean relaxedCommonAssignmentCheck( + LessThanLengthOf varLtlQual, ExpressionTree valueExp) { + + AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(valueExp); + UBQualifier expQual = + UBQualifier.createUBQualifier( + expType, atypeFactory.UNKNOWN, (UpperBoundChecker) checker); + + UBQualifier lessThanQual = atypeFactory.fromLessThan(valueExp, getCurrentPath()); + if (lessThanQual != null) { + expQual = expQual.glb(lessThanQual); + } - /* Returns the new value of the left hand side after processing the arrays named in the lhs. - * Iff varLtlQual includes LTL(lhsSeq), - * lhsSeq has HSS, and expQual includes LTL(a, -from), then the LTL(lhsSeq) will be removed from varLtlQual - */ - private UBQualifier processSubsequenceForLHS(LessThanLengthOf varLtlQual, UBQualifier expQual) { - UBQualifier newLHS = varLtlQual; - for (String lhsSeq : varLtlQual.getSequences()) { - // check is lhsSeq is an actual LTL - if (varLtlQual.hasSequenceWithOffset(lhsSeq, 0)) { + UBQualifier lessThanOrEqualQual = + atypeFactory.fromLessThanOrEqual(valueExp, getCurrentPath()); + if (lessThanOrEqualQual != null) { + expQual = expQual.glb(lessThanOrEqualQual); + } + if (expQual.isSubtype(varLtlQual)) { + return true; + } - JavaExpression lhsSeqExpr = - parseJavaExpressionString(lhsSeq, atypeFactory, getCurrentPath()); - Subsequence subSeq = Subsequence.getSubsequenceFromReceiver(lhsSeqExpr, atypeFactory); + // Take advantage of information available on a HasSubsequence(a, from, to) annotation + // on the lhs qualifier (varLtlQual): + // this allows us to show that iff varLtlQual includes LTL(b), b has HSS, and expQual + // includes LTL(a, -from), then the LTL(b) can be removed from varLtlQual. - if (subSeq != null) { - String from = subSeq.from; - String a = subSeq.array; - if (expQual.hasSequenceWithOffset(a, Subsequence.negateString(from))) { - // This cast is safe because LTLs cannot contain duplicates. - // Note that this updates newLHS on each iteration from its old value, - // so even if there are multiple HSS arrays the result will be correct. - newLHS = ((LessThanLengthOf) newLHS).removeOffset(lhsSeq, 0); - } + UBQualifier newLHS = processSubsequenceForLHS(varLtlQual, expQual); + if (newLHS.isUnknown()) { + return true; + } else { + varLtlQual = (LessThanLengthOf) newLHS; + } + + Long value = + ValueCheckerUtils.getMaxValue( + valueExp, atypeFactory.getValueAnnotatedTypeFactory()); + + if (value == null && !expQual.isLessThanLengthQualifier()) { + return false; + } + + SameLenAnnotatedTypeFactory sameLenFactory = atypeFactory.getSameLenAnnotatedTypeFactory(); + ValueAnnotatedTypeFactory valueAnnotatedTypeFactory = + atypeFactory.getValueAnnotatedTypeFactory(); + checkloop: + for (String sequenceName : varLtlQual.getSequences()) { + + List sameLenSequences = + sameLenFactory.getSameLensFromString(sequenceName, valueExp, getCurrentPath()); + if (testSameLen(expQual, varLtlQual, sameLenSequences, sequenceName)) { + continue; + } + + int minlen = + valueAnnotatedTypeFactory.getMinLenFromString( + sequenceName, valueExp, getCurrentPath()); + if (testMinLen(value, minlen, sequenceName, varLtlQual)) { + continue; + } + for (String sequence : sameLenSequences) { + int minlenSL = + valueAnnotatedTypeFactory.getMinLenFromString( + sequence, valueExp, getCurrentPath()); + if (testMinLen(value, minlenSL, sequenceName, varLtlQual)) { + continue checkloop; + } + } + + return false; } - } + + return true; } - return newLHS; - } - - /** - * Tests whether replacing any of the arrays in sameLenArrays with arrayName makes expQual - * equivalent to varQual. - */ - private boolean testSameLen( - UBQualifier expQual, LessThanLengthOf varQual, List sameLenArrays, String arrayName) { - - if (!expQual.isLessThanLengthQualifier()) { - return false; + + /* Returns the new value of the left hand side after processing the arrays named in the lhs. + * Iff varLtlQual includes LTL(lhsSeq), + * lhsSeq has HSS, and expQual includes LTL(a, -from), then the LTL(lhsSeq) will be removed from varLtlQual + */ + private UBQualifier processSubsequenceForLHS(LessThanLengthOf varLtlQual, UBQualifier expQual) { + UBQualifier newLHS = varLtlQual; + for (String lhsSeq : varLtlQual.getSequences()) { + // check is lhsSeq is an actual LTL + if (varLtlQual.hasSequenceWithOffset(lhsSeq, 0)) { + + JavaExpression lhsSeqExpr = + parseJavaExpressionString(lhsSeq, atypeFactory, getCurrentPath()); + Subsequence subSeq = + Subsequence.getSubsequenceFromReceiver(lhsSeqExpr, atypeFactory); + + if (subSeq != null) { + String from = subSeq.from; + String a = subSeq.array; + if (expQual.hasSequenceWithOffset(a, Subsequence.negateString(from))) { + // This cast is safe because LTLs cannot contain duplicates. + // Note that this updates newLHS on each iteration from its old value, + // so even if there are multiple HSS arrays the result will be correct. + newLHS = ((LessThanLengthOf) newLHS).removeOffset(lhsSeq, 0); + } + } + } + } + return newLHS; } - for (String sameLenArrayName : sameLenArrays) { - // Check whether replacing the value for any of the current type's offset results - // in the type we're trying to match. - if (varQual.isValidReplacement(arrayName, sameLenArrayName, (LessThanLengthOf) expQual)) { - return true; - } + /** + * Tests whether replacing any of the arrays in sameLenArrays with arrayName makes expQual + * equivalent to varQual. + */ + private boolean testSameLen( + UBQualifier expQual, + LessThanLengthOf varQual, + List sameLenArrays, + String arrayName) { + + if (!expQual.isLessThanLengthQualifier()) { + return false; + } + + for (String sameLenArrayName : sameLenArrays) { + // Check whether replacing the value for any of the current type's offset results + // in the type we're trying to match. + if (varQual.isValidReplacement( + arrayName, sameLenArrayName, (LessThanLengthOf) expQual)) { + return true; + } + } + return false; } - return false; - } - - /** - * Tests a constant value (value) against the minlen (minlens) of an array (arrayName) with a - * qualifier (varQual). - */ - private boolean testMinLen(Long value, int minLen, String arrayName, LessThanLengthOf varQual) { - if (value == null) { - return false; + + /** + * Tests a constant value (value) against the minlen (minlens) of an array (arrayName) with a + * qualifier (varQual). + */ + private boolean testMinLen(Long value, int minLen, String arrayName, LessThanLengthOf varQual) { + if (value == null) { + return false; + } + return varQual.isValuePlusOffsetLessThanMinLen(arrayName, value, minLen); } - return varQual.isValuePlusOffsetLessThanMinLen(arrayName, value, minLen); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java index de54b499895..1c87a27f488 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnalysis.java @@ -1,48 +1,49 @@ package org.checkerframework.checker.initialization; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.type.TypeMirror; + /** * The analysis class for the initialization type system (serves as factory for the transfer * function, stores, and abstract values. */ public class InitializationAnalysis - extends CFAbstractAnalysis { - - /** - * Creates a new {@code InitializationAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - protected InitializationAnalysis( - BaseTypeChecker checker, InitializationParentAnnotatedTypeFactory factory) { - super(checker, factory); - } - - @Override - public InitializationStore createEmptyStore(boolean sequentialSemantics) { - return new InitializationStore(this, sequentialSemantics); - } - - @Override - public InitializationStore createCopiedStore(InitializationStore s) { - return new InitializationStore(s); - } - - @Override - public @Nullable CFValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - return defaultCreateAbstractValue(this, annotations, underlyingType); - } - - @Override - public InitializationParentAnnotatedTypeFactory getTypeFactory() { - return (InitializationParentAnnotatedTypeFactory) super.getTypeFactory(); - } + extends CFAbstractAnalysis { + + /** + * Creates a new {@code InitializationAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + protected InitializationAnalysis( + BaseTypeChecker checker, InitializationParentAnnotatedTypeFactory factory) { + super(checker, factory); + } + + @Override + public InitializationStore createEmptyStore(boolean sequentialSemantics) { + return new InitializationStore(this, sequentialSemantics); + } + + @Override + public InitializationStore createCopiedStore(InitializationStore s) { + return new InitializationStore(s); + } + + @Override + public @Nullable CFValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + return defaultCreateAbstractValue(this, annotations, underlyingType); + } + + @Override + public InitializationParentAnnotatedTypeFactory getTypeFactory() { + return (InitializationParentAnnotatedTypeFactory) super.getTypeFactory(); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java index 2d2dbe2e9ce..13b1c96ee37 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java @@ -7,13 +7,7 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -39,6 +33,15 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.VariableElement; + /** * The annotated type factory for the freedom-before-commitment type system. When using the * freedom-before-commitment type system as a subchecker, you must ensure that the parent checker @@ -46,244 +49,250 @@ */ public class InitializationAnnotatedTypeFactory extends InitializationParentAnnotatedTypeFactory { - /** - * Create a new InitializationAnnotatedTypeFactory. - * - * @param checker the checker to which the new type factory belongs - */ - public InitializationAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + /** + * Create a new InitializationAnnotatedTypeFactory. + * + * @param checker the checker to which the new type factory belongs + */ + public InitializationAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - public InitializationChecker getChecker() { - return (InitializationChecker) super.getChecker(); - } + @Override + public InitializationChecker getChecker() { + return (InitializationChecker) super.getChecker(); + } - /** - * Gets the factory of the {@link InitializationFieldAccessSubchecker}, whose flow-analysis - * results we reuse to avoid performing the same flow analysis twice. - * - *

          If type checking has not yet started, the subcheckers are uninitialized, and this returns - * {@code null}. More concretely, this method only returns a non-null value after {@link - * SourceChecker#initChecker()} has been called on all subcheckers. Since the flow analysis is - * initialized in {@link AnnotatedTypeFactory#postInit()}, and the type factory is created after - * all subcheckers have been initialized, this method will always return a non-null value unless a - * subclass attempts to use it for some purpose other than accessing the flow analysis. - * - * @return the factory of the {@link InitializationFieldAccessSubchecker}, or {@code null} if not - * yet initialized - * @see #createFlowAnalysis() - * @see #performFlowAnalysis(ClassTree) - * @see #getRegularExitStore(Tree) - * @see #getExceptionalExitStore(Tree) - * @see #getReturnStatementStores(MethodTree) - */ - protected @Nullable InitializationFieldAccessAnnotatedTypeFactory getFieldAccessFactory() { - InitializationChecker checker = getChecker(); - BaseTypeChecker targetChecker = checker.getSubchecker(checker.getTargetCheckerClass()); - return targetChecker.getTypeFactoryOfSubcheckerOrNull( - InitializationFieldAccessSubchecker.class); - } + /** + * Gets the factory of the {@link InitializationFieldAccessSubchecker}, whose flow-analysis + * results we reuse to avoid performing the same flow analysis twice. + * + *

          If type checking has not yet started, the subcheckers are uninitialized, and this returns + * {@code null}. More concretely, this method only returns a non-null value after {@link + * SourceChecker#initChecker()} has been called on all subcheckers. Since the flow analysis is + * initialized in {@link AnnotatedTypeFactory#postInit()}, and the type factory is created after + * all subcheckers have been initialized, this method will always return a non-null value unless + * a subclass attempts to use it for some purpose other than accessing the flow analysis. + * + * @return the factory of the {@link InitializationFieldAccessSubchecker}, or {@code null} if + * not yet initialized + * @see #createFlowAnalysis() + * @see #performFlowAnalysis(ClassTree) + * @see #getRegularExitStore(Tree) + * @see #getExceptionalExitStore(Tree) + * @see #getReturnStatementStores(MethodTree) + */ + protected @Nullable InitializationFieldAccessAnnotatedTypeFactory getFieldAccessFactory() { + InitializationChecker checker = getChecker(); + BaseTypeChecker targetChecker = checker.getSubchecker(checker.getTargetCheckerClass()); + return targetChecker.getTypeFactoryOfSubcheckerOrNull( + InitializationFieldAccessSubchecker.class); + } - @Override - protected InitializationAnalysis createFlowAnalysis() { - return getFieldAccessFactory().getAnalysis(); - } + @Override + protected InitializationAnalysis createFlowAnalysis() { + return getFieldAccessFactory().getAnalysis(); + } - @Override - protected void performFlowAnalysis(ClassTree classTree) { - flowResult = getFieldAccessFactory().getFlowResult(); - } + @Override + protected void performFlowAnalysis(ClassTree classTree) { + flowResult = getFieldAccessFactory().getFlowResult(); + } - @Override - public InitializationStore getRegularExitStore(Tree tree) { - return getFieldAccessFactory().getRegularExitStore(tree); - } + @Override + public InitializationStore getRegularExitStore(Tree tree) { + return getFieldAccessFactory().getRegularExitStore(tree); + } - @Override - public InitializationStore getExceptionalExitStore(Tree tree) { - return getFieldAccessFactory().getExceptionalExitStore(tree); - } + @Override + public InitializationStore getExceptionalExitStore(Tree tree) { + return getFieldAccessFactory().getExceptionalExitStore(tree); + } - @Override - public List>> - getReturnStatementStores(MethodTree methodTree) { - return getFieldAccessFactory().getReturnStatementStores(methodTree); - } + @Override + public List>> + getReturnStatementStores(MethodTree methodTree) { + return getFieldAccessFactory().getReturnStatementStores(methodTree); + } - /** - * {@inheritDoc} - * - *

          This implementaiton also takes the target checker into account. - * - * @see #getUninitializedFields(InitializationStore, CFAbstractStore, TreePath, boolean, - * Collection) - */ - @Override - protected void setSelfTypeInInitializationCode( - Tree tree, AnnotatedTypeMirror.AnnotatedDeclaredType selfType, TreePath path) { - ClassTree enclosingClass = TreePathUtil.enclosingClass(path); - Type classType = ((JCTree) enclosingClass).type; - AnnotationMirror annotation; + /** + * {@inheritDoc} + * + *

          This implementaiton also takes the target checker into account. + * + * @see #getUninitializedFields(InitializationStore, CFAbstractStore, TreePath, boolean, + * Collection) + */ + @Override + protected void setSelfTypeInInitializationCode( + Tree tree, AnnotatedTypeMirror.AnnotatedDeclaredType selfType, TreePath path) { + ClassTree enclosingClass = TreePathUtil.enclosingClass(path); + Type classType = ((JCTree) enclosingClass).type; + AnnotationMirror annotation; - // If all fields are initialized-only, and they are all initialized, - // then: - // - if the class is final, this is @Initialized - // - otherwise, this is @UnderInitialization(CurrentClass) as - // there might still be subclasses that need initialization. - if (areAllFieldsInitializedOnly(enclosingClass)) { - GenericAnnotatedTypeFactory targetFactory = - checker.getTypeFactoryOfSubcheckerOrNull( - ((InitializationChecker) checker).getTargetCheckerClass()); - InitializationStore initStore = getStoreBefore(tree); - CFAbstractStore targetStore = targetFactory.getStoreBefore(tree); - if (initStore != null - && targetStore != null - && getUninitializedFields(initStore, targetStore, path, false, Collections.emptyList()) - .isEmpty()) { - if (classType.isFinal()) { - annotation = INITIALIZED; + // If all fields are initialized-only, and they are all initialized, + // then: + // - if the class is final, this is @Initialized + // - otherwise, this is @UnderInitialization(CurrentClass) as + // there might still be subclasses that need initialization. + if (areAllFieldsInitializedOnly(enclosingClass)) { + GenericAnnotatedTypeFactory targetFactory = + checker.getTypeFactoryOfSubcheckerOrNull( + ((InitializationChecker) checker).getTargetCheckerClass()); + InitializationStore initStore = getStoreBefore(tree); + CFAbstractStore targetStore = targetFactory.getStoreBefore(tree); + if (initStore != null + && targetStore != null + && getUninitializedFields( + initStore, targetStore, path, false, Collections.emptyList()) + .isEmpty()) { + if (classType.isFinal()) { + annotation = INITIALIZED; + } else { + annotation = createUnderInitializationAnnotation(classType); + } + } else if (initStore != null + && getUninitializedFields(initStore, path, false, Collections.emptyList()) + .isEmpty()) { + if (classType.isFinal()) { + annotation = INITIALIZED; + } else { + annotation = createUnderInitializationAnnotation(classType); + } + } else { + annotation = null; + } } else { - annotation = createUnderInitializationAnnotation(classType); + annotation = null; } - } else if (initStore != null - && getUninitializedFields(initStore, path, false, Collections.emptyList()).isEmpty()) { - if (classType.isFinal()) { - annotation = INITIALIZED; - } else { - annotation = createUnderInitializationAnnotation(classType); + + if (annotation == null) { + annotation = getUnderInitializationAnnotationOfSuperType(classType); } - } else { - annotation = null; - } - } else { - annotation = null; + selfType.replaceAnnotation(annotation); } - if (annotation == null) { - annotation = getUnderInitializationAnnotationOfSuperType(classType); - } - selfType.replaceAnnotation(annotation); - } + /** + * Returns the fields that are not yet initialized in a given store, taking into account the + * target checker. + * + *

          A field f is initialized if + * + *

            + *
          • f is initialized in the initialization store, i.e., it has been assigned; + *
          • the value of f in the target store has a non-top qualifier that does not have the + * meta-annotation {@link HoldsForDefaultValue}; or + *
          • the declared qualifier of f in the target hierarchy either has the meta-annotation + * {@link HoldsForDefaultValue} or is a top qualifier. + *
          + * + *

          See {@link #getUninitializedFields(InitializationStore, TreePath, boolean, Collection)} + * for a method that does not require the target checker. + * + * @param initStore a store for the initialization checker + * @param targetStore a store for the target checker corresponding to initStore + * @param path the current path, used to determine the current class + * @param isStatic whether to report static fields or instance fields + * @param receiverAnnotations the annotations on the receiver + * @return the fields that are not yet initialized in a given store + */ + public List getUninitializedFields( + InitializationStore initStore, + CFAbstractStore targetStore, + TreePath path, + boolean isStatic, + Collection receiverAnnotations) { + List uninitializedFields = + super.getUninitializedFields(initStore, path, isStatic, receiverAnnotations); - /** - * Returns the fields that are not yet initialized in a given store, taking into account the - * target checker. - * - *

          A field f is initialized if - * - *

            - *
          • f is initialized in the initialization store, i.e., it has been assigned; - *
          • the value of f in the target store has a non-top qualifier that does not have the - * meta-annotation {@link HoldsForDefaultValue}; or - *
          • the declared qualifier of f in the target hierarchy either has the meta-annotation {@link - * HoldsForDefaultValue} or is a top qualifier. - *
          - * - *

          See {@link #getUninitializedFields(InitializationStore, TreePath, boolean, Collection)} for - * a method that does not require the target checker. - * - * @param initStore a store for the initialization checker - * @param targetStore a store for the target checker corresponding to initStore - * @param path the current path, used to determine the current class - * @param isStatic whether to report static fields or instance fields - * @param receiverAnnotations the annotations on the receiver - * @return the fields that are not yet initialized in a given store - */ - public List getUninitializedFields( - InitializationStore initStore, - CFAbstractStore targetStore, - TreePath path, - boolean isStatic, - Collection receiverAnnotations) { - List uninitializedFields = - super.getUninitializedFields(initStore, path, isStatic, receiverAnnotations); + GenericAnnotatedTypeFactory factory = + checker.getTypeFactoryOfSubcheckerOrNull( + ((InitializationChecker) checker).getTargetCheckerClass()); - GenericAnnotatedTypeFactory factory = - checker.getTypeFactoryOfSubcheckerOrNull( - ((InitializationChecker) checker).getTargetCheckerClass()); + if (factory == null) { + throw new BugInCF( + "Did not find target type factory for checker " + + ((InitializationChecker) checker).getTargetCheckerClass()); + } - if (factory == null) { - throw new BugInCF( - "Did not find target type factory for checker " - + ((InitializationChecker) checker).getTargetCheckerClass()); - } + // Remove primitives + if (!((InitializationChecker) checker).checkPrimitives()) { + uninitializedFields.removeIf(var -> getAnnotatedType(var).getKind().isPrimitive()); + } - // Remove primitives - if (!((InitializationChecker) checker).checkPrimitives()) { - uninitializedFields.removeIf(var -> getAnnotatedType(var).getKind().isPrimitive()); - } + // Filter out fields which are initialized according to subchecker + uninitializedFields.removeIf( + var -> { + ClassTree enclosingClass = TreePathUtil.enclosingClass(getPath(var)); + Node receiver; + if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration(var))) { + receiver = new ClassNameNode(enclosingClass); + } else { + receiver = + new ImplicitThisNode( + TreeUtils.elementFromDeclaration(enclosingClass).asType()); + } + VariableElement varElement = TreeUtils.elementFromDeclaration(var); + FieldAccessNode fa = new FieldAccessNode(var, varElement, receiver); + CFAbstractValue value = targetStore.getValue(fa); + return isInitialized(factory, value, varElement); + }); - // Filter out fields which are initialized according to subchecker - uninitializedFields.removeIf( - var -> { - ClassTree enclosingClass = TreePathUtil.enclosingClass(getPath(var)); - Node receiver; - if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration(var))) { - receiver = new ClassNameNode(enclosingClass); - } else { - receiver = - new ImplicitThisNode(TreeUtils.elementFromDeclaration(enclosingClass).asType()); - } - VariableElement varElement = TreeUtils.elementFromDeclaration(var); - FieldAccessNode fa = new FieldAccessNode(var, varElement, receiver); - CFAbstractValue value = targetStore.getValue(fa); - return isInitialized(factory, value, varElement); - }); + return uninitializedFields; + } - return uninitializedFields; - } + /** + * Determines whether the specified variable's current value is initialized. + * + *

          Returns {@code true} iff the variable's current value is initialized. This holds for + * variables whose value has a non-top qualifier that does not have the meta-annotation {@link + * HoldsForDefaultValue} (e.g., variables with a {@code NonNull} value), as well as variables + * whose declaration has a qualifier that has the meta-annotation {@link HoldsForDefaultValue} + * (e.g., variables whose declared type is {@code Nullable}). + * + * @param factory the parent checker's factory + * @param value the variable's current value + * @param var the variable to check + * @return whether the specified variable is yet to be initialized + */ + public static boolean isInitialized( + GenericAnnotatedTypeFactory factory, + CFAbstractValue value, + VariableElement var) { + AnnotatedTypeMirror declType = factory.getAnnotatedType(var); - /** - * Determines whether the specified variable's current value is initialized. - * - *

          Returns {@code true} iff the variable's current value is initialized. This holds for - * variables whose value has a non-top qualifier that does not have the meta-annotation {@link - * HoldsForDefaultValue} (e.g., variables with a {@code NonNull} value), as well as variables - * whose declaration has a qualifier that has the meta-annotation {@link HoldsForDefaultValue} - * (e.g., variables whose declared type is {@code Nullable}). - * - * @param factory the parent checker's factory - * @param value the variable's current value - * @param var the variable to check - * @return whether the specified variable is yet to be initialized - */ - public static boolean isInitialized( - GenericAnnotatedTypeFactory factory, - CFAbstractValue value, - VariableElement var) { - AnnotatedTypeMirror declType = factory.getAnnotatedType(var); + Set topAnnotations = + factory.getQualifierHierarchy().getTopAnnotations(); - Set topAnnotations = - factory.getQualifierHierarchy().getTopAnnotations(); + for (Class invariant : factory.getSupportedTypeQualifiers()) { + // Skip default-value, monotonic, polymorphic, and top qualifiers + if (invariant.getAnnotation(HoldsForDefaultValue.class) != null + || invariant.getAnnotation(MonotonicQualifier.class) != null + || invariant.getAnnotation(PolymorphicQualifier.class) != null + || topAnnotations.stream() + .anyMatch( + annotation -> factory.areSameByClass(annotation, invariant))) { + continue; + } - for (Class invariant : factory.getSupportedTypeQualifiers()) { - // Skip default-value, monotonic, polymorphic, and top qualifiers - if (invariant.getAnnotation(HoldsForDefaultValue.class) != null - || invariant.getAnnotation(MonotonicQualifier.class) != null - || invariant.getAnnotation(PolymorphicQualifier.class) != null - || topAnnotations.stream() - .anyMatch(annotation -> factory.areSameByClass(annotation, invariant))) { - continue; - } + boolean hasInvariantInStore = + value != null + && value.getAnnotations().stream() + .anyMatch( + annotation -> + factory.areSameByClass(annotation, invariant)); + boolean hasInvariantAtDeclaration = + AnnotatedTypes.findEffectiveLowerBoundAnnotations( + factory.getQualifierHierarchy(), declType) + .stream() + .anyMatch(annotation -> factory.areSameByClass(annotation, invariant)); - boolean hasInvariantInStore = - value != null - && value.getAnnotations().stream() - .anyMatch(annotation -> factory.areSameByClass(annotation, invariant)); - boolean hasInvariantAtDeclaration = - AnnotatedTypes.findEffectiveLowerBoundAnnotations( - factory.getQualifierHierarchy(), declType) - .stream() - .anyMatch(annotation -> factory.areSameByClass(annotation, invariant)); + if (hasInvariantAtDeclaration && !hasInvariantInStore) { + return false; + } + } - if (hasInvariantAtDeclaration && !hasInvariantInStore) { - return false; - } + return true; } - - return true; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java index c0ec95770a8..8aa8cdd3db3 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationChecker.java @@ -3,10 +3,7 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import java.util.ArrayList; -import java.util.List; -import java.util.NavigableSet; -import java.util.Set; + import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; import org.checkerframework.checker.initialization.qual.Initialized; import org.checkerframework.checker.nullness.NullnessChecker; @@ -18,6 +15,11 @@ import org.checkerframework.checker.signature.qual.FullyQualifiedName; import org.checkerframework.common.basetype.BaseTypeChecker; +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; +import java.util.Set; + /** * Tracks whether a value is initialized (all its fields are set), and checks that values are * initialized before being used. Implements the freedom-before-commitment scheme for @@ -69,89 +71,89 @@ */ public abstract class InitializationChecker extends BaseTypeChecker { - /** Default constructor for InitializationChecker. */ - public InitializationChecker() {} + /** Default constructor for InitializationChecker. */ + public InitializationChecker() {} - /** - * Whether to check primitives for initialization. - * - * @return whether to check primitives for initialization - */ - public abstract boolean checkPrimitives(); + /** + * Whether to check primitives for initialization. + * + * @return whether to check primitives for initialization + */ + public abstract boolean checkPrimitives(); - /** - * The checker for the target type system for which to check initialization. - * - * @return the checker for the target type system. - */ - public abstract Class getTargetCheckerClass(); + /** + * The checker for the target type system for which to check initialization. + * + * @return the checker for the target type system. + */ + public abstract Class getTargetCheckerClass(); - /** - * Also handle {@code AnnotatedFor} annotations for this checker. See {@link - * InitializationFieldAccessSubchecker#getUpstreamCheckerNames()} and the two implementations - * should be kept in sync. - */ - @Override - public List<@FullyQualifiedName String> getUpstreamCheckerNames() { - if (upstreamCheckerNames == null) { - super.getUpstreamCheckerNames(); - upstreamCheckerNames.add(InitializationChecker.class.getName()); + /** + * Also handle {@code AnnotatedFor} annotations for this checker. See {@link + * InitializationFieldAccessSubchecker#getUpstreamCheckerNames()} and the two implementations + * should be kept in sync. + */ + @Override + public List<@FullyQualifiedName String> getUpstreamCheckerNames() { + if (upstreamCheckerNames == null) { + super.getUpstreamCheckerNames(); + upstreamCheckerNames.add(InitializationChecker.class.getName()); + } + return upstreamCheckerNames; } - return upstreamCheckerNames; - } - @Override - public NavigableSet getSuppressWarningsPrefixes() { - NavigableSet result = super.getSuppressWarningsPrefixes(); - // "fbc" is for backward compatibility only; you should use - // "initialization" instead. - result.add("fbc"); - // The default prefix "initialization" must be added manually because this checker class - // is abstract and its subclasses are not named "InitializationChecker". - result.add("initialization"); - return result; - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + NavigableSet result = super.getSuppressWarningsPrefixes(); + // "fbc" is for backward compatibility only; you should use + // "initialization" instead. + result.add("fbc"); + // The default prefix "initialization" must be added manually because this checker class + // is abstract and its subclasses are not named "InitializationChecker". + result.add("initialization"); + return result; + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(getTargetCheckerClass()); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(getTargetCheckerClass()); + return checkers; + } - /** - * Returns a list of all fields of the given class. - * - * @param clazz the class - * @return a list of all fields of {@code clazz} - */ - public static List getAllFields(ClassTree clazz) { - List fields = new ArrayList<>(); - for (Tree t : clazz.getMembers()) { - if (t.getKind() == Tree.Kind.VARIABLE) { - VariableTree vt = (VariableTree) t; - fields.add(vt); - } + /** + * Returns a list of all fields of the given class. + * + * @param clazz the class + * @return a list of all fields of {@code clazz} + */ + public static List getAllFields(ClassTree clazz) { + List fields = new ArrayList<>(); + for (Tree t : clazz.getMembers()) { + if (t.getKind() == Tree.Kind.VARIABLE) { + VariableTree vt = (VariableTree) t; + fields.add(vt); + } + } + return fields; } - return fields; - } - @Override - public InitializationAnnotatedTypeFactory getTypeFactory() { - return (InitializationAnnotatedTypeFactory) super.getTypeFactory(); - } + @Override + public InitializationAnnotatedTypeFactory getTypeFactory() { + return (InitializationAnnotatedTypeFactory) super.getTypeFactory(); + } - @Override - protected InitializationVisitor createSourceVisitor() { - return new InitializationVisitor(this); - } + @Override + protected InitializationVisitor createSourceVisitor() { + return new InitializationVisitor(this); + } - @Override - protected boolean messageKeyMatches( - String messageKey, String messageKeyInSuppressWarningsString) { - // Also support the shorter keys used by typetools - return super.messageKeyMatches(messageKey, messageKeyInSuppressWarningsString) - || super.messageKeyMatches( - messageKey.replace(".invalid", ""), messageKeyInSuppressWarningsString); - } + @Override + protected boolean messageKeyMatches( + String messageKey, String messageKeyInSuppressWarningsString) { + // Also support the shorter keys used by typetools + return super.messageKeyMatches(messageKey, messageKeyInSuppressWarningsString) + || super.messageKeyMatches( + messageKey.replace(".invalid", ""), messageKeyInSuppressWarningsString); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java index 926d61d12b3..4f04ab3340b 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessAnnotatedTypeFactory.java @@ -1,61 +1,62 @@ package org.checkerframework.checker.initialization; import com.sun.source.tree.ClassTree; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.analysis.AnalysisResult; import org.checkerframework.framework.flow.CFValue; /** The type factory for the {@link InitializationFieldAccessSubchecker}. */ public class InitializationFieldAccessAnnotatedTypeFactory - extends InitializationParentAnnotatedTypeFactory { - - /** - * Create a new InitializationFieldAccessAnnotatedTypeFactory. - * - * @param checker the checker to which the new type factory belongs - */ - public InitializationFieldAccessAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - @Override - protected InitializationAnalysis createFlowAnalysis() { - return new InitializationAnalysis(checker, this); - } - - @Override - protected void performFlowAnalysis(ClassTree classTree) { - // Only perform the analysis if initialization checking is turned on. - if (!assumeInitialized) { - super.performFlowAnalysis(classTree); + extends InitializationParentAnnotatedTypeFactory { + + /** + * Create a new InitializationFieldAccessAnnotatedTypeFactory. + * + * @param checker the checker to which the new type factory belongs + */ + public InitializationFieldAccessAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } + + @Override + protected InitializationAnalysis createFlowAnalysis() { + return new InitializationAnalysis(checker, this); + } + + @Override + protected void performFlowAnalysis(ClassTree classTree) { + // Only perform the analysis if initialization checking is turned on. + if (!assumeInitialized) { + super.performFlowAnalysis(classTree); + } + } + + /** + * Returns the flow analysis. + * + * @return the flow analysis + * @see #getFlowResult() + */ + /*package-private*/ InitializationAnalysis getAnalysis() { + return analysis; + } + + /** + * Returns the result of the flow analysis. Invariant: + * + *

          +     *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
          +     * 
          + * + * Note that flowResult contains analysis results for Trees from multiple classes which are + * produced by multiple calls to performFlowAnalysis. + * + * @return the result of the flow analysis + * @see #getAnalysis() + */ + /*package-private*/ AnalysisResult getFlowResult() { + return flowResult; } - } - - /** - * Returns the flow analysis. - * - * @return the flow analysis - * @see #getFlowResult() - */ - /*package-private*/ InitializationAnalysis getAnalysis() { - return analysis; - } - - /** - * Returns the result of the flow analysis. Invariant: - * - *
          -   *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
          -   * 
          - * - * Note that flowResult contains analysis results for Trees from multiple classes which are - * produced by multiple calls to performFlowAnalysis. - * - * @return the result of the flow analysis - * @see #getAnalysis() - */ - /*package-private*/ AnalysisResult getFlowResult() { - return flowResult; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java index 0e041af00dc..0d3f5799020 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessSubchecker.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.initialization; -import java.util.List; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.signature.qual.FullyQualifiedName; import org.checkerframework.common.basetype.BaseTypeChecker; +import java.util.List; + /** * Part of the freedom-before-commitment type system. * @@ -21,32 +22,33 @@ */ public class InitializationFieldAccessSubchecker extends BaseTypeChecker { - /** Default constructor for InitializationFieldAccessSubchecker. */ - public InitializationFieldAccessSubchecker() {} - - /** - * Also handle {@code AnnotatedFor} annotations for the {@link InitializationChecker}. See {@link - * InitializationChecker#getUpstreamCheckerNames()} and the two implementations should be kept in - * sync. - */ - @Override - public List<@FullyQualifiedName String> getUpstreamCheckerNames() { - if (upstreamCheckerNames == null) { - super.getUpstreamCheckerNames(); - upstreamCheckerNames.add(InitializationChecker.class.getName()); + /** Default constructor for InitializationFieldAccessSubchecker. */ + public InitializationFieldAccessSubchecker() {} + + /** + * Also handle {@code AnnotatedFor} annotations for the {@link InitializationChecker}. See + * {@link InitializationChecker#getUpstreamCheckerNames()} and the two implementations should be + * kept in sync. + */ + @Override + public List<@FullyQualifiedName String> getUpstreamCheckerNames() { + if (upstreamCheckerNames == null) { + super.getUpstreamCheckerNames(); + upstreamCheckerNames.add(InitializationChecker.class.getName()); + } + return upstreamCheckerNames; } - return upstreamCheckerNames; - } - // Suppress all errors and warnings, since they are also reported by the InitializationChecker + // Suppress all errors and warnings, since they are also reported by the InitializationChecker - @Override - public void reportError(Object source, @CompilerMessageKey String messageKey, Object... args) { - // do nothing - } + @Override + public void reportError(Object source, @CompilerMessageKey String messageKey, Object... args) { + // do nothing + } - @Override - public void reportWarning(Object source, @CompilerMessageKey String messageKey, Object... args) { - // do nothing - } + @Override + public void reportWarning( + Object source, @CompilerMessageKey String messageKey, Object... args) { + // do nothing + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java index 9b6de183bfa..e2ddd5f094c 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessTreeAnnotator.java @@ -4,8 +4,7 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; @@ -13,6 +12,9 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; + /** * Part of the freedom-before-commitment type system. * @@ -24,128 +26,130 @@ */ public class InitializationFieldAccessTreeAnnotator extends TreeAnnotator { - /** The value of the assumeInitialized option. */ - protected final boolean assumeInitialized; - - /** - * Creates a new CommitmentFieldAccessTreeAnnotator. - * - * @param atypeFactory the type factory belonging to the init checker's parent - */ - public InitializationFieldAccessTreeAnnotator( - GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - assumeInitialized = atypeFactory.getChecker().hasOption("assumeInitialized"); - } - - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror p) { - super.visitIdentifier(tree, p); - computeFieldAccessType(tree, p); - return null; - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror p) { - super.visitMemberSelect(tree, p); - computeFieldAccessType(tree, p); - return null; - } - - /** - * Adapts the type in the target checker hierarchy of a field access depending on the field's - * declared type and the receiver's initialization type. - * - * @param tree the field access - * @param type the field access's unadapted type - */ - private void computeFieldAccessType(ExpressionTree tree, AnnotatedTypeMirror type) { - GenericAnnotatedTypeFactory factory = - (GenericAnnotatedTypeFactory) atypeFactory; - - // Don't adapt anything if initialization checking is turned off. - if (assumeInitialized) { - return; - } - - // Don't adapt anything if "tree" is not actually a field access. - - // Don't adapt uses of the identifiers "this" or "super" that are not field accesses - // (e.g., constructor calls or uses of an outer this). - if (tree instanceof IdentifierTree) { - IdentifierTree identTree = (IdentifierTree) tree; - if (identTree.getName().contentEquals("this") || identTree.getName().contentEquals("super")) { - return; - } - } - - // Don't adapt method accesses. - if (type instanceof AnnotatedTypeMirror.AnnotatedExecutableType) { - return; - } - - // Don't adapt trees that do not have a (explicit or implicit) receiver (e.g., local - // variables). - InitializationFieldAccessAnnotatedTypeFactory initFactory = - atypeFactory - .getChecker() - .getTypeFactoryOfSubcheckerOrNull(InitializationFieldAccessSubchecker.class); - if (initFactory == null) { - throw new BugInCF("Did not find InitializationFieldAccessSubchecker!"); - } - AnnotatedTypeMirror receiver = initFactory.getReceiverType(tree); - if (receiver == null) { - return; + /** The value of the assumeInitialized option. */ + protected final boolean assumeInitialized; + + /** + * Creates a new CommitmentFieldAccessTreeAnnotator. + * + * @param atypeFactory the type factory belonging to the init checker's parent + */ + public InitializationFieldAccessTreeAnnotator( + GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + assumeInitialized = atypeFactory.getChecker().hasOption("assumeInitialized"); } - // Don't adapt trees whose receiver is initialized. - if (!initFactory.isUnknownInitialization(receiver) - && !initFactory.isUnderInitialization(receiver)) { - return; + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror p) { + super.visitIdentifier(tree, p); + computeFieldAccessType(tree, p); + return null; } - // Don't adapt trees with an explicit UnknownInitialization annotation on the field - Element element = TreeUtils.elementFromUse(tree); - AnnotatedTypeMirror fieldAnnotations = factory.getAnnotatedType(element); - if (AnnotationUtils.containsSameByName( - fieldAnnotations.getAnnotations(), initFactory.UNKNOWN_INITIALIZATION)) { - return; + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror p) { + super.visitMemberSelect(tree, p); + computeFieldAccessType(tree, p); + return null; } - TypeMirror fieldOwnerType = element.getEnclosingElement().asType(); - boolean isReceiverInitToOwner = initFactory.isInitializedForFrame(receiver, fieldOwnerType); - - // If the field has been initialized, don't clear annotations. - // This is ok even if the field was initialized with a non-invariant - // value because in that case, there must have been an error before. - // E.g.: - // { f1 = f2; - // f2 = f1; } - // Here, we will get an error for the first assignment, but we won't get another - // error for the second assignment. - // See the AssignmentDuringInitialization test case. - Tree fieldDeclarationTree = initFactory.declarationFromElement(element); - InitializationStore store = initFactory.getStoreBefore(tree); - // If the field declaration is null (because the field is declared in bytecode), - // or the store is null (because flow-sensitive refinement is turned off), - // the field is considered uninitialized. - // Fields of objects other than this are not tracked and thus also considered uninitialized. - // Otherwise, check if the field is initialized in the given store. - boolean isFieldInitialized = - fieldDeclarationTree != null - && store != null - && TreeUtils.isSelfAccess(tree) - && initFactory - .getInitializedFields(store, initFactory.getPath(tree)) - .contains(fieldDeclarationTree); - if (!isReceiverInitToOwner - && !isFieldInitialized - && !factory.isComputingAnnotatedTypeMirrorOfLhs()) { - // The receiver is not initialized for this frame and the type being computed is - // not a LHS. - // Replace all annotations with the top annotation for that hierarchy. - type.clearAnnotations(); - type.addAnnotations(factory.getQualifierHierarchy().getTopAnnotations()); + /** + * Adapts the type in the target checker hierarchy of a field access depending on the field's + * declared type and the receiver's initialization type. + * + * @param tree the field access + * @param type the field access's unadapted type + */ + private void computeFieldAccessType(ExpressionTree tree, AnnotatedTypeMirror type) { + GenericAnnotatedTypeFactory factory = + (GenericAnnotatedTypeFactory) atypeFactory; + + // Don't adapt anything if initialization checking is turned off. + if (assumeInitialized) { + return; + } + + // Don't adapt anything if "tree" is not actually a field access. + + // Don't adapt uses of the identifiers "this" or "super" that are not field accesses + // (e.g., constructor calls or uses of an outer this). + if (tree instanceof IdentifierTree) { + IdentifierTree identTree = (IdentifierTree) tree; + if (identTree.getName().contentEquals("this") + || identTree.getName().contentEquals("super")) { + return; + } + } + + // Don't adapt method accesses. + if (type instanceof AnnotatedTypeMirror.AnnotatedExecutableType) { + return; + } + + // Don't adapt trees that do not have a (explicit or implicit) receiver (e.g., local + // variables). + InitializationFieldAccessAnnotatedTypeFactory initFactory = + atypeFactory + .getChecker() + .getTypeFactoryOfSubcheckerOrNull( + InitializationFieldAccessSubchecker.class); + if (initFactory == null) { + throw new BugInCF("Did not find InitializationFieldAccessSubchecker!"); + } + AnnotatedTypeMirror receiver = initFactory.getReceiverType(tree); + if (receiver == null) { + return; + } + + // Don't adapt trees whose receiver is initialized. + if (!initFactory.isUnknownInitialization(receiver) + && !initFactory.isUnderInitialization(receiver)) { + return; + } + + // Don't adapt trees with an explicit UnknownInitialization annotation on the field + Element element = TreeUtils.elementFromUse(tree); + AnnotatedTypeMirror fieldAnnotations = factory.getAnnotatedType(element); + if (AnnotationUtils.containsSameByName( + fieldAnnotations.getAnnotations(), initFactory.UNKNOWN_INITIALIZATION)) { + return; + } + + TypeMirror fieldOwnerType = element.getEnclosingElement().asType(); + boolean isReceiverInitToOwner = initFactory.isInitializedForFrame(receiver, fieldOwnerType); + + // If the field has been initialized, don't clear annotations. + // This is ok even if the field was initialized with a non-invariant + // value because in that case, there must have been an error before. + // E.g.: + // { f1 = f2; + // f2 = f1; } + // Here, we will get an error for the first assignment, but we won't get another + // error for the second assignment. + // See the AssignmentDuringInitialization test case. + Tree fieldDeclarationTree = initFactory.declarationFromElement(element); + InitializationStore store = initFactory.getStoreBefore(tree); + // If the field declaration is null (because the field is declared in bytecode), + // or the store is null (because flow-sensitive refinement is turned off), + // the field is considered uninitialized. + // Fields of objects other than this are not tracked and thus also considered uninitialized. + // Otherwise, check if the field is initialized in the given store. + boolean isFieldInitialized = + fieldDeclarationTree != null + && store != null + && TreeUtils.isSelfAccess(tree) + && initFactory + .getInitializedFields(store, initFactory.getPath(tree)) + .contains(fieldDeclarationTree); + if (!isReceiverInitToOwner + && !isFieldInitialized + && !factory.isComputingAnnotatedTypeMirrorOfLhs()) { + // The receiver is not initialized for this frame and the type being computed is + // not a LHS. + // Replace all annotations with the top annotation for that hierarchy. + type.clearAnnotations(); + type.addAnnotations(factory.getQualifierHierarchy().getTopAnnotations()); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java index 9223cc0051d..65b6ba51ab8 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationFieldAccessVisitor.java @@ -1,35 +1,36 @@ package org.checkerframework.checker.initialization; import com.sun.source.tree.ClassTree; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; /** The visitor for the {@link InitializationFieldAccessSubchecker}. */ public class InitializationFieldAccessVisitor - extends BaseTypeVisitor { + extends BaseTypeVisitor { - /** The value of the assumeInitialized option. */ - private final boolean assumeInitialized; + /** The value of the assumeInitialized option. */ + private final boolean assumeInitialized; - /** - * Create an InitializationFieldAccessVisitor. - * - * @param checker the initialization field-access checker - */ - public InitializationFieldAccessVisitor(BaseTypeChecker checker) { - super(checker); - assumeInitialized = checker.hasOption("assumeInitialized"); - } + /** + * Create an InitializationFieldAccessVisitor. + * + * @param checker the initialization field-access checker + */ + public InitializationFieldAccessVisitor(BaseTypeChecker checker) { + super(checker); + assumeInitialized = checker.hasOption("assumeInitialized"); + } - @Override - public void processClassTree(ClassTree classTree) { - // As stated in the documentation for the InitializationFieldAccessChecker - // and InitializationChecker, this checker performs the flow analysis - // (which is handled in the BaseTypeVisitor), but does not perform - // any type checking. - // Thus, this method does nothing but scan through the members. - if (!assumeInitialized) { - scan(classTree.getMembers(), null); + @Override + public void processClassTree(ClassTree classTree) { + // As stated in the documentation for the InitializationFieldAccessChecker + // and InitializationChecker, this checker performs the flow analysis + // (which is handled in the BaseTypeVisitor), but does not perform + // any type checking. + // Thus, this method does nothing but scan through the members. + if (!assumeInitialized) { + scan(classTree.getMembers(), null); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java index 51d9ea93f0b..648a5b3db46 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java @@ -16,25 +16,7 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; + import org.checkerframework.checker.initialization.qual.FBCBottom; import org.checkerframework.checker.initialization.qual.Initialized; import org.checkerframework.checker.initialization.qual.NotOnlyInitialized; @@ -68,884 +50,935 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + /** * Superclass for {@link InitializationFieldAccessAnnotatedTypeFactory} and {@link * InitializationAnnotatedTypeFactory} to contain common functionality. */ public abstract class InitializationParentAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory< - CFValue, InitializationStore, InitializationTransfer, InitializationAnalysis> { - - /** {@link UnknownInitialization}. */ - protected final AnnotationMirror UNKNOWN_INITIALIZATION; - - /** {@link Initialized}. */ - protected final AnnotationMirror INITIALIZED; - - /** {@link UnderInitialization} or null. */ - protected final AnnotationMirror UNDER_INITALIZATION; - - /** {@link NotOnlyInitialized} or null. */ - protected final AnnotationMirror NOT_ONLY_INITIALIZED; - - /** {@link PolyInitialized}. */ - protected final AnnotationMirror POLY_INITIALIZED; - - /** {@link FBCBottom}. */ - protected final AnnotationMirror FBCBOTTOM; - - /** The java.lang.Object type. */ - protected final TypeMirror objectTypeMirror; - - /** The Unused.when field/element. */ - protected final ExecutableElement unusedWhenElement; - - /** The UnderInitialization.value field/element. */ - protected final ExecutableElement underInitializationValueElement; - - /** The UnknownInitialization.value field/element. */ - protected final ExecutableElement unknownInitializationValueElement; - - /** The value of the assumeInitialized option. */ - protected final boolean assumeInitialized; - - /** - * Create a new InitializationParentAnnotatedTypeFactory. - * - *

          Don't forget to call {@link #postInit()} in the concrete subclass. - * - * @param checker the checker to which the new type factory belongs - */ - public InitializationParentAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - - UNKNOWN_INITIALIZATION = AnnotationBuilder.fromClass(elements, UnknownInitialization.class); - INITIALIZED = AnnotationBuilder.fromClass(elements, Initialized.class); - UNDER_INITALIZATION = AnnotationBuilder.fromClass(elements, UnderInitialization.class); - NOT_ONLY_INITIALIZED = AnnotationBuilder.fromClass(elements, NotOnlyInitialized.class); - POLY_INITIALIZED = AnnotationBuilder.fromClass(elements, PolyInitialized.class); - FBCBOTTOM = AnnotationBuilder.fromClass(elements, FBCBottom.class); - - objectTypeMirror = processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType(); - unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, processingEnv); - underInitializationValueElement = - TreeUtils.getMethod(UnderInitialization.class, "value", 0, processingEnv); - unknownInitializationValueElement = - TreeUtils.getMethod(UnknownInitialization.class, "value", 0, processingEnv); - - assumeInitialized = checker.hasOption("assumeInitialized"); - } - - @Override - public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { - super.postAsMemberOf(type, owner, element); - - if (element.getKind().isField()) { - Collection declaredFieldAnnotations = getDeclAnnotations(element); - AnnotatedTypeMirror fieldAnnotations = getAnnotatedType(element); - computeFieldAccessInitializationType(type, declaredFieldAnnotations, owner, fieldAnnotations); - } - } - - /** - * Adapts the initialization type of a field access (implicit or explicit) based on the receiver - * type and the declared annotations for the field. - * - *

          To adapt the type in the target checker's hierarchy, see the {@link - * InitializationFieldAccessTreeAnnotator} instead. - * - * @param type type of the field access expression - * @param declaredFieldAnnotations declared annotations on the field - * @param receiverType inferred annotations of the receiver - * @param fieldType inferred annotations of the field - */ - private void computeFieldAccessInitializationType( - AnnotatedTypeMirror type, - Collection declaredFieldAnnotations, - AnnotatedTypeMirror receiverType, - AnnotatedTypeMirror fieldType) { - // Primitive values have no fields and are thus always @Initialized. - if (TypesUtils.isPrimitive(type.getUnderlyingType())) { - return; - } - // not necessary if there is an explicit UnknownInitialization - // annotation on the field - if (AnnotationUtils.containsSameByName(fieldType.getAnnotations(), UNKNOWN_INITIALIZATION)) { - return; - } - - if (isUnknownInitialization(receiverType) || isUnderInitialization(receiverType)) { - if (AnnotationUtils.containsSame(declaredFieldAnnotations, NOT_ONLY_INITIALIZED)) { - // A field declared @NotOnlyInitialized with an uninitialized receiver has - // @UnknownInitialization - type.replaceAnnotation(UNKNOWN_INITIALIZATION); - } else { - // A field declared @NotOnlyInitialized with an initialized receiver is - // @Initialized - type.replaceAnnotation(INITIALIZED); - } - } - } - - @Override - protected Set> createSupportedTypeQualifiers() { - Set> result = new HashSet<>(); - result.add(UnknownInitialization.class); - result.add(UnderInitialization.class); - result.add(Initialized.class); - result.add(FBCBottom.class); - result.add(PolyInitialized.class); - return result; - } - - @Override - public InitializationTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new InitializationTransfer((InitializationAnalysis) analysis); - } - - /** - * Returns {@code true}. Initialization cannot be undone, i.e., an @Initialized object always - * stays @Initialized, an @UnderInitialization(A) object always stays @UnderInitialization(A) - * (though it may additionally become @Initialized), etc. - */ - @Override - public boolean isImmutable(TypeMirror type) { - return true; - } - - /** - * Creates a {@link UnderInitialization} annotation with the given type as its type frame - * argument. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnderInitialization} annotation with the given argument - */ - public AnnotationMirror createUnderInitializationAnnotation(TypeMirror typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); - } - - @Override - public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { - AnnotatedDeclaredType selfType = super.getSelfType(tree); - - if (assumeInitialized) { - return selfType; - } - - TreePath path = getPath(tree); - AnnotatedDeclaredType enclosing = selfType; - while (path != null && enclosing != null) { - TreePath topLevelMemberPath = findTopLevelClassMemberForTree(path); - if (topLevelMemberPath != null && topLevelMemberPath.getLeaf() != null) { - Tree topLevelMember = topLevelMemberPath.getLeaf(); - if (topLevelMember.getKind() != Tree.Kind.METHOD - || TreeUtils.isConstructor((MethodTree) topLevelMember)) { - setSelfTypeInInitializationCode(tree, enclosing, topLevelMemberPath); - } - path = topLevelMemberPath.getParentPath(); - enclosing = enclosing.getEnclosingType(); - } else { - break; - } - } - - return selfType; - } - - /** - * In the first enclosing class, find the path to the top-level member that contains {@code path}. - * - * @param path the path whose leaf is the target - * @return path to a top-level member containing the leaf of {@code path} - */ - @SuppressWarnings("interning:not.interned") // AST node comparison - private @Nullable TreePath findTopLevelClassMemberForTree(TreePath path) { - if (TreeUtils.isClassTree(path.getLeaf())) { - path = path.getParentPath(); - if (path == null) { - return null; - } - } - ClassTree enclosingClass = TreePathUtil.enclosingClass(path); - if (enclosingClass != null) { - List classMembers = enclosingClass.getMembers(); - TreePath searchPath = path; - while (searchPath.getParentPath() != null - && searchPath.getParentPath().getLeaf() != enclosingClass) { - searchPath = searchPath.getParentPath(); - if (classMembers.contains(searchPath.getLeaf())) { - return searchPath; - } - } - } - return null; - } - - /** - * Side-effects argument {@code selfType} to make it @Initialized or @UnderInitialization, - * depending on whether all fields have been set. - * - * @param tree a tree - * @param selfType the type to side-effect - * @param path a path - */ - protected void setSelfTypeInInitializationCode( - Tree tree, AnnotatedDeclaredType selfType, TreePath path) { - ClassTree enclosingClass = TreePathUtil.enclosingClass(path); - Type classType = ((JCTree) enclosingClass).type; - AnnotationMirror annotation = null; - - // If all fields are initialized-only, and they are all initialized, - // then: - // - if the class is final, this is @Initialized - // - otherwise, this is @UnderInitialization(CurrentClass) as - // there might still be subclasses that need initialization. - if (areAllFieldsInitializedOnly(enclosingClass)) { - InitializationStore store = getStoreBefore(tree); - if (store != null - && getUninitializedFields(store, path, false, Collections.emptyList()).isEmpty()) { - if (classType.isFinal()) { - annotation = INITIALIZED; - } else { - annotation = createUnderInitializationAnnotation(classType); - } - } - } - - if (annotation == null) { - annotation = getUnderInitializationAnnotationOfSuperType(classType); - } - selfType.replaceAnnotation(annotation); - } - - /** - * Returns an {@link UnderInitialization} annotation that has the superclass of {@code type} as - * type frame. - * - * @param type a type - * @return true an {@link UnderInitialization} for the supertype of {@code type} - */ - protected AnnotationMirror getUnderInitializationAnnotationOfSuperType(TypeMirror type) { - // Find supertype if possible. - AnnotationMirror annotation; - List superTypes = types.directSupertypes(type); - TypeMirror superClass = null; - for (TypeMirror superType : superTypes) { - ElementKind kind = types.asElement(superType).getKind(); - if (kind == ElementKind.CLASS) { - superClass = superType; - break; - } - } - // Create annotation. - if (superClass != null) { - annotation = createUnderInitializationAnnotation(superClass); - } else { - // Use Object as a valid super-class. - annotation = createUnderInitializationAnnotation(Object.class); - } - return annotation; - } - - /** - * Returns whether the specified field is unused, given the specified annotations on the receiver. - * - * @param field the field to check - * @param receiverAnnos the annotations on the receiver - * @return whether {@code field} is unused given {@code receiverAnnos} - */ - protected boolean isUnused( - VariableTree field, Collection receiverAnnos) { - if (receiverAnnos.isEmpty()) { - return false; - } - - AnnotationMirror unused = - getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class); - if (unused == null) { - return false; - } - - Name when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement); - for (AnnotationMirror anno : receiverAnnos) { - Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName(); - if (annoName.contentEquals(when)) { - return true; - } - } - - return false; - } - - /** - * Creates a {@link UnderInitialization} annotation with the given type frame. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnderInitialization} annotation with the given argument - */ - public AnnotationMirror createUnderInitializationAnnotation(Class typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); - } - - /** - * Are all fields initialized-only? - * - * @param classTree the class to query - * @return true if all fields are initialized-only - */ - protected boolean areAllFieldsInitializedOnly(ClassTree classTree) { - for (Tree member : classTree.getMembers()) { - if (member.getKind() != Tree.Kind.VARIABLE) { - continue; - } - VariableTree var = (VariableTree) member; - VariableElement varElt = TreeUtils.elementFromDeclaration(var); - // var is not initialized-only - if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) { - // var is not static -- need a check of initializer blocks, - // not of constructor which is where this is used - if (!varElt.getModifiers().contains(Modifier.STATIC)) { - return false; - } - } - } - return true; - } - - /** - * Returns the fields that are possibly uninitialized in a given store, without taking into - * account the target checker. - * - *

          I.e., this method returns all fields that have not been assigned, without considering fields - * that may be considered initialized by the target checker even though they have not been - * explicitly assigned. See {@link InitializationAnnotatedTypeFactory#getUninitializedFields( - * InitializationStore, CFAbstractStore, TreePath, boolean, Collection)} for a method that does - * take the target checker into account. - * - * @param store a store - * @param path the current path, used to determine the current class - * @param isStatic whether to report static fields or instance fields - * @param receiverAnnotations the annotations on the receiver - * @return the fields that are not yet initialized in a given store - */ - public List getUninitializedFields( - InitializationStore store, - TreePath path, - boolean isStatic, - Collection receiverAnnotations) { - ClassTree currentClass = TreePathUtil.enclosingClass(path); - List fields = InitializationChecker.getAllFields(currentClass); - List uninit = new ArrayList<>(); - for (VariableTree field : fields) { - if (isUnused(field, receiverAnnotations)) { - continue; // don't consider unused fields - } - VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); - if (ElementUtils.isStatic(fieldElem) == isStatic) { - if (!store.isFieldInitialized(fieldElem)) { - uninit.add(field); - } - } - } - return uninit; - } - - /** - * Returns the fields that are initialized in the given store. - * - * @param store a store - * @param path the current path; used to compute the current class - * @return the fields that are initialized in the given store - */ - public List getInitializedFields(InitializationStore store, TreePath path) { - // TODO: Instead of passing the TreePath around, can we use - // getCurrentClassTree? - ClassTree currentClass = TreePathUtil.enclosingClass(path); - List fields = InitializationChecker.getAllFields(currentClass); - List initializedFields = new ArrayList<>(); - for (VariableTree field : fields) { - VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); - if (!ElementUtils.isStatic(fieldElem)) { - if (store.isFieldInitialized(fieldElem)) { - initializedFields.add(field); - } - } - } - return initializedFields; - } - - @Override - public boolean isNotFullyInitializedReceiver(MethodTree methodTree) { - if (super.isNotFullyInitializedReceiver(methodTree)) { - return true; - } - AnnotatedDeclaredType receiverType = getAnnotatedType(methodTree).getReceiverType(); - if (receiverType != null) { - return isUnknownInitialization(receiverType) || isUnderInitialization(receiverType); - } else { - // There is no receiver e.g. in static methods. - return false; - } - } - - /** - * Creates a {@link UnknownInitialization} annotation with a given type frame. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnknownInitialization} annotation with the given argument - */ - public AnnotationMirror createUnknownInitializationAnnotation(Class typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); - } - - /** - * Creates an {@link UnknownInitialization} annotation with a given type frame. - * - * @param typeFrame the type down to which some value has been initialized - * @return an {@link UnknownInitialization} annotation with the given argument - */ - public AnnotationMirror createUnknownInitializationAnnotation(TypeMirror typeFrame) { - assert typeFrame != null; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class); - builder.setValue("value", typeFrame); - return builder.build(); - } - - /** - * Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link UnderInitialization} - */ - public boolean isUnderInitialization(AnnotationMirror anno) { - return areSameByClass(anno, UnderInitialization.class); - } - - /** - * Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link UnknownInitialization} - */ - public boolean isUnknownInitialization(AnnotationMirror anno) { - return areSameByClass(anno, UnknownInitialization.class); - } - - /** - * Is {@code anno} the bottom annotation? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link FBCBottom} - */ - public boolean isFbcBottom(AnnotationMirror anno) { - return AnnotationUtils.areSame(anno, FBCBOTTOM); - } - - /** - * Is {@code anno} the {@link Initialized} annotation? - * - * @param anno the annotation to check - * @return true if {@code anno} is {@link Initialized} - */ - public boolean isInitialized(AnnotationMirror anno) { - return AnnotationUtils.areSame(anno, INITIALIZED); - } - - /** - * Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link UnderInitialization} - */ - public boolean isUnderInitialization(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(UnderInitialization.class); - } - - /** - * Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link UnknownInitialization} - */ - public boolean isUnknownInitialization(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(UnknownInitialization.class); - } - - /** - * Does {@code anno} have the bottom annotation? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link FBCBottom} - */ - public boolean isFbcBottom(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(FBCBottom.class); - } - - /** - * Does {@code anno} have the annotation {@link Initialized}? - * - * @param anno the annotation to check - * @return true if {@code anno} has {@link Initialized} - */ - public boolean isInitialized(AnnotatedTypeMirror anno) { - return anno.hasEffectiveAnnotation(Initialized.class); - } - - /** - * Return true if the type is initialized with respect to the given frame -- that is, all of the - * fields of the frame are initialized. - * - * @param type the type whose initialization type qualifiers to check - * @param frame a class in {@code type}'s class hierarchy - * @return true if the type is initialized for the given frame - */ - public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) { - if (isInitialized(type)) { - return true; - } - - AnnotationMirror initializationAnno = - type.getEffectiveAnnotationInHierarchy(UNKNOWN_INITIALIZATION); - if (initializationAnno == null) { - initializationAnno = type.getEffectiveAnnotationInHierarchy(UNDER_INITALIZATION); - } - - TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno); - Types types = processingEnv.getTypeUtils(); - return types.isSubtype(typeFrame, types.erasure(frame)); - } - - /** - * Returns the type frame (that is, the argument) of a given initialization annotation. - * - * @param annotation a {@link UnderInitialization} or {@link UnknownInitialization} annotation - * @return the annotation's argument - */ - public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) { - if (AnnotationUtils.areSameByName( - annotation, "org.checkerframework.checker.initialization.qual.UnderInitialization")) { - return AnnotationUtils.getElementValue( - annotation, underInitializationValueElement, TypeMirror.class, objectTypeMirror); - } else { - return AnnotationUtils.getElementValue( - annotation, unknownInitializationValueElement, TypeMirror.class, objectTypeMirror); - } - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new InitializationQualifierHierarchy(); - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(super.createTypeAnnotator(), new CommitmentTypeAnnotator(this)); - } - - /** - * Returns {@code false}. Redundancy in only the initialization hierarchy is ok and may even be - * caused by implicit default annotations. The parent checker should determine whether to warn - * about redundancy. - */ - @Override - public boolean shouldWarnIfStubRedundantWithBytecode() { - return false; - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because we want our CommitmentTreeAnnotator - // instead of the default PropagationTreeAnnotator - List treeAnnotators = new ArrayList<>(2); - treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); - if (dependentTypesHelper.hasDependentAnnotations()) { - treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); - } - treeAnnotators.add(new CommitmentTreeAnnotator(this)); - return new ListTreeAnnotator(treeAnnotators); - } - - /** This type annotator adds the correct UnderInitialization annotation to super constructors. */ - protected class CommitmentTypeAnnotator extends TypeAnnotator { + extends GenericAnnotatedTypeFactory< + CFValue, InitializationStore, InitializationTransfer, InitializationAnalysis> { + + /** {@link UnknownInitialization}. */ + protected final AnnotationMirror UNKNOWN_INITIALIZATION; + + /** {@link Initialized}. */ + protected final AnnotationMirror INITIALIZED; + + /** {@link UnderInitialization} or null. */ + protected final AnnotationMirror UNDER_INITALIZATION; + + /** {@link NotOnlyInitialized} or null. */ + protected final AnnotationMirror NOT_ONLY_INITIALIZED; + + /** {@link PolyInitialized}. */ + protected final AnnotationMirror POLY_INITIALIZED; + + /** {@link FBCBottom}. */ + protected final AnnotationMirror FBCBOTTOM; + + /** The java.lang.Object type. */ + protected final TypeMirror objectTypeMirror; + + /** The Unused.when field/element. */ + protected final ExecutableElement unusedWhenElement; + + /** The UnderInitialization.value field/element. */ + protected final ExecutableElement underInitializationValueElement; + + /** The UnknownInitialization.value field/element. */ + protected final ExecutableElement unknownInitializationValueElement; + + /** The value of the assumeInitialized option. */ + protected final boolean assumeInitialized; /** - * Creates a new CommitmentTypeAnnotator. + * Create a new InitializationParentAnnotatedTypeFactory. + * + *

          Don't forget to call {@link #postInit()} in the concrete subclass. * - * @param atypeFactory this factory + * @param checker the checker to which the new type factory belongs */ - public CommitmentTypeAnnotator(InitializationParentAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + public InitializationParentAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + + UNKNOWN_INITIALIZATION = AnnotationBuilder.fromClass(elements, UnknownInitialization.class); + INITIALIZED = AnnotationBuilder.fromClass(elements, Initialized.class); + UNDER_INITALIZATION = AnnotationBuilder.fromClass(elements, UnderInitialization.class); + NOT_ONLY_INITIALIZED = AnnotationBuilder.fromClass(elements, NotOnlyInitialized.class); + POLY_INITIALIZED = AnnotationBuilder.fromClass(elements, PolyInitialized.class); + FBCBOTTOM = AnnotationBuilder.fromClass(elements, FBCBottom.class); + + objectTypeMirror = + processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType(); + unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, processingEnv); + underInitializationValueElement = + TreeUtils.getMethod(UnderInitialization.class, "value", 0, processingEnv); + unknownInitializationValueElement = + TreeUtils.getMethod(UnknownInitialization.class, "value", 0, processingEnv); + + assumeInitialized = checker.hasOption("assumeInitialized"); } @Override - public Void visitExecutable(AnnotatedExecutableType t, Void p) { - Void result = super.visitExecutable(t, p); - Element elem = t.getElement(); - if (elem.getKind() == ElementKind.CONSTRUCTOR) { - AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); - DeclaredType underlyingType = returnType.getUnderlyingType(); - // TODO: Make sure we check explicit annotations somewhere. - returnType.replaceAnnotation(getUnderInitializationAnnotationOfSuperType(underlyingType)); - } - return result; - } - } - - /** - * This tree annotator modifies the propagation tree annotator to add propagation rules for the - * freedom-before-commitment system. - */ - protected class CommitmentTreeAnnotator extends PropagationTreeAnnotator { + public void postAsMemberOf( + AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { + super.postAsMemberOf(type, owner, element); + + if (element.getKind().isField()) { + Collection declaredFieldAnnotations = + getDeclAnnotations(element); + AnnotatedTypeMirror fieldAnnotations = getAnnotatedType(element); + computeFieldAccessInitializationType( + type, declaredFieldAnnotations, owner, fieldAnnotations); + } + } /** - * Creates a new CommitmentTreeAnnotator. + * Adapts the initialization type of a field access (implicit or explicit) based on the receiver + * type and the declared annotations for the field. + * + *

          To adapt the type in the target checker's hierarchy, see the {@link + * InitializationFieldAccessTreeAnnotator} instead. * - * @param initializationAnnotatedTypeFactory this factory + * @param type type of the field access expression + * @param declaredFieldAnnotations declared annotations on the field + * @param receiverType inferred annotations of the receiver + * @param fieldType inferred annotations of the field */ - public CommitmentTreeAnnotator( - InitializationParentAnnotatedTypeFactory initializationAnnotatedTypeFactory) { - super(initializationAnnotatedTypeFactory); + private void computeFieldAccessInitializationType( + AnnotatedTypeMirror type, + Collection declaredFieldAnnotations, + AnnotatedTypeMirror receiverType, + AnnotatedTypeMirror fieldType) { + // Primitive values have no fields and are thus always @Initialized. + if (TypesUtils.isPrimitive(type.getUnderlyingType())) { + return; + } + // not necessary if there is an explicit UnknownInitialization + // annotation on the field + if (AnnotationUtils.containsSameByName( + fieldType.getAnnotations(), UNKNOWN_INITIALIZATION)) { + return; + } + + if (isUnknownInitialization(receiverType) || isUnderInitialization(receiverType)) { + if (AnnotationUtils.containsSame(declaredFieldAnnotations, NOT_ONLY_INITIALIZED)) { + // A field declared @NotOnlyInitialized with an uninitialized receiver has + // @UnknownInitialization + type.replaceAnnotation(UNKNOWN_INITIALIZATION); + } else { + // A field declared @NotOnlyInitialized with an initialized receiver is + // @Initialized + type.replaceAnnotation(INITIALIZED); + } + } } @Override - public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { - Void result = super.visitMethod(tree, p); - if (TreeUtils.isConstructor(tree)) { - assert p instanceof AnnotatedExecutableType; - AnnotatedExecutableType exeType = (AnnotatedExecutableType) p; - DeclaredType underlyingType = (DeclaredType) exeType.getReturnType().getUnderlyingType(); - AnnotationMirror a = getUnderInitializationAnnotationOfSuperType(underlyingType); - exeType.getReturnType().replaceAnnotation(a); - } - return result; + protected Set> createSupportedTypeQualifiers() { + Set> result = new HashSet<>(); + result.add(UnknownInitialization.class); + result.add(UnderInitialization.class); + result.add(Initialized.class); + result.add(FBCBottom.class); + result.add(PolyInitialized.class); + return result; } @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror p) { - super.visitNewClass(tree, p); - boolean allInitialized = true; - Type type = ((JCTree) tree).type; - for (ExpressionTree a : tree.getArguments()) { - if (!TreeUtils.isStandaloneExpression(a)) { - continue; - } - boolean oldShouldCache = shouldCache; - shouldCache = false; - AnnotatedTypeMirror t = getAnnotatedType(a); - shouldCache = oldShouldCache; - allInitialized &= (isInitialized(t) || isFbcBottom(t)); - } - if (!allInitialized) { - p.replaceAnnotation(createUnderInitializationAnnotation(type)); - return null; - } - p.replaceAnnotation(INITIALIZED); - return null; + public InitializationTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new InitializationTransfer((InitializationAnalysis) analysis); } + /** + * Returns {@code true}. Initialization cannot be undone, i.e., an @Initialized object always + * stays @Initialized, an @UnderInitialization(A) object always stays @UnderInitialization(A) + * (though it may additionally become @Initialized), etc. + */ @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (tree.getKind() != Tree.Kind.NULL_LITERAL) { - type.addAnnotation(INITIALIZED); - } - return super.visitLiteral(tree, type); + public boolean isImmutable(TypeMirror type) { + return true; } - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - // The most precise element type for `new Object[] {null}` is @FBCBottom, but - // the most useful element type is @Initialized (which is also accurate). - AnnotatedArrayType arrayType = (AnnotatedArrayType) type; - AnnotatedTypeMirror componentType = arrayType.getComponentType(); - if (componentType.hasEffectiveAnnotation(FBCBOTTOM)) { - componentType.replaceAnnotation(INITIALIZED); - } - return null; + /** + * Creates a {@link UnderInitialization} annotation with the given type as its type frame + * argument. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnderInitialization} annotation with the given argument + */ + public AnnotationMirror createUnderInitializationAnnotation(TypeMirror typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); } @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - if (TreeUtils.isArrayLengthAccess(tree)) { - annotatedTypeMirror.replaceAnnotation(INITIALIZED); - } - return super.visitMemberSelect(tree, annotatedTypeMirror); + public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { + AnnotatedDeclaredType selfType = super.getSelfType(tree); + + if (assumeInitialized) { + return selfType; + } + + TreePath path = getPath(tree); + AnnotatedDeclaredType enclosing = selfType; + while (path != null && enclosing != null) { + TreePath topLevelMemberPath = findTopLevelClassMemberForTree(path); + if (topLevelMemberPath != null && topLevelMemberPath.getLeaf() != null) { + Tree topLevelMember = topLevelMemberPath.getLeaf(); + if (topLevelMember.getKind() != Tree.Kind.METHOD + || TreeUtils.isConstructor((MethodTree) topLevelMember)) { + setSelfTypeInInitializationCode(tree, enclosing, topLevelMemberPath); + } + path = topLevelMemberPath.getParentPath(); + enclosing = enclosing.getEnclosingType(); + } else { + break; + } + } + + return selfType; } - /* The result of a binary or unary operator is either primitive or a String. - * Primitives have no fields and are thus always @Initialized. - * Since all String constructors return @Initialized strings, Strings - * are also always @Initialized. */ + /** + * In the first enclosing class, find the path to the top-level member that contains {@code + * path}. + * + * @param path the path whose leaf is the target + * @return path to a top-level member containing the leaf of {@code path} + */ + @SuppressWarnings("interning:not.interned") // AST node comparison + private @Nullable TreePath findTopLevelClassMemberForTree(TreePath path) { + if (TreeUtils.isClassTree(path.getLeaf())) { + path = path.getParentPath(); + if (path == null) { + return null; + } + } + ClassTree enclosingClass = TreePathUtil.enclosingClass(path); + if (enclosingClass != null) { + List classMembers = enclosingClass.getMembers(); + TreePath searchPath = path; + while (searchPath.getParentPath() != null + && searchPath.getParentPath().getLeaf() != enclosingClass) { + searchPath = searchPath.getParentPath(); + if (classMembers.contains(searchPath.getLeaf())) { + return searchPath; + } + } + } + return null; + } - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - return null; + /** + * Side-effects argument {@code selfType} to make it @Initialized or @UnderInitialization, + * depending on whether all fields have been set. + * + * @param tree a tree + * @param selfType the type to side-effect + * @param path a path + */ + protected void setSelfTypeInInitializationCode( + Tree tree, AnnotatedDeclaredType selfType, TreePath path) { + ClassTree enclosingClass = TreePathUtil.enclosingClass(path); + Type classType = ((JCTree) enclosingClass).type; + AnnotationMirror annotation = null; + + // If all fields are initialized-only, and they are all initialized, + // then: + // - if the class is final, this is @Initialized + // - otherwise, this is @UnderInitialization(CurrentClass) as + // there might still be subclasses that need initialization. + if (areAllFieldsInitializedOnly(enclosingClass)) { + InitializationStore store = getStoreBefore(tree); + if (store != null + && getUninitializedFields(store, path, false, Collections.emptyList()) + .isEmpty()) { + if (classType.isFinal()) { + annotation = INITIALIZED; + } else { + annotation = createUnderInitializationAnnotation(classType); + } + } + } + + if (annotation == null) { + annotation = getUnderInitializationAnnotationOfSuperType(classType); + } + selfType.replaceAnnotation(annotation); + } + + /** + * Returns an {@link UnderInitialization} annotation that has the superclass of {@code type} as + * type frame. + * + * @param type a type + * @return true an {@link UnderInitialization} for the supertype of {@code type} + */ + protected AnnotationMirror getUnderInitializationAnnotationOfSuperType(TypeMirror type) { + // Find supertype if possible. + AnnotationMirror annotation; + List superTypes = types.directSupertypes(type); + TypeMirror superClass = null; + for (TypeMirror superType : superTypes) { + ElementKind kind = types.asElement(superType).getKind(); + if (kind == ElementKind.CLASS) { + superClass = superType; + break; + } + } + // Create annotation. + if (superClass != null) { + annotation = createUnderInitializationAnnotation(superClass); + } else { + // Use Object as a valid super-class. + annotation = createUnderInitializationAnnotation(Object.class); + } + return annotation; + } + + /** + * Returns whether the specified field is unused, given the specified annotations on the + * receiver. + * + * @param field the field to check + * @param receiverAnnos the annotations on the receiver + * @return whether {@code field} is unused given {@code receiverAnnos} + */ + protected boolean isUnused( + VariableTree field, Collection receiverAnnos) { + if (receiverAnnos.isEmpty()) { + return false; + } + + AnnotationMirror unused = + getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class); + if (unused == null) { + return false; + } + + Name when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement); + for (AnnotationMirror anno : receiverAnnos) { + Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName(); + if (annoName.contentEquals(when)) { + return true; + } + } + + return false; + } + + /** + * Creates a {@link UnderInitialization} annotation with the given type frame. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnderInitialization} annotation with the given argument + */ + public AnnotationMirror createUnderInitializationAnnotation(Class typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); + } + + /** + * Are all fields initialized-only? + * + * @param classTree the class to query + * @return true if all fields are initialized-only + */ + protected boolean areAllFieldsInitializedOnly(ClassTree classTree) { + for (Tree member : classTree.getMembers()) { + if (member.getKind() != Tree.Kind.VARIABLE) { + continue; + } + VariableTree var = (VariableTree) member; + VariableElement varElt = TreeUtils.elementFromDeclaration(var); + // var is not initialized-only + if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) { + // var is not static -- need a check of initializer blocks, + // not of constructor which is where this is used + if (!varElt.getModifiers().contains(Modifier.STATIC)) { + return false; + } + } + } + return true; + } + + /** + * Returns the fields that are possibly uninitialized in a given store, without taking into + * account the target checker. + * + *

          I.e., this method returns all fields that have not been assigned, without considering + * fields that may be considered initialized by the target checker even though they have not + * been explicitly assigned. See {@link + * InitializationAnnotatedTypeFactory#getUninitializedFields( InitializationStore, + * CFAbstractStore, TreePath, boolean, Collection)} for a method that does take the target + * checker into account. + * + * @param store a store + * @param path the current path, used to determine the current class + * @param isStatic whether to report static fields or instance fields + * @param receiverAnnotations the annotations on the receiver + * @return the fields that are not yet initialized in a given store + */ + public List getUninitializedFields( + InitializationStore store, + TreePath path, + boolean isStatic, + Collection receiverAnnotations) { + ClassTree currentClass = TreePathUtil.enclosingClass(path); + List fields = InitializationChecker.getAllFields(currentClass); + List uninit = new ArrayList<>(); + for (VariableTree field : fields) { + if (isUnused(field, receiverAnnotations)) { + continue; // don't consider unused fields + } + VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); + if (ElementUtils.isStatic(fieldElem) == isStatic) { + if (!store.isFieldInitialized(fieldElem)) { + uninit.add(field); + } + } + } + return uninit; + } + + /** + * Returns the fields that are initialized in the given store. + * + * @param store a store + * @param path the current path; used to compute the current class + * @return the fields that are initialized in the given store + */ + public List getInitializedFields(InitializationStore store, TreePath path) { + // TODO: Instead of passing the TreePath around, can we use + // getCurrentClassTree? + ClassTree currentClass = TreePathUtil.enclosingClass(path); + List fields = InitializationChecker.getAllFields(currentClass); + List initializedFields = new ArrayList<>(); + for (VariableTree field : fields) { + VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); + if (!ElementUtils.isStatic(fieldElem)) { + if (store.isFieldInitialized(fieldElem)) { + initializedFields.add(field); + } + } + } + return initializedFields; } @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - return null; + public boolean isNotFullyInitializedReceiver(MethodTree methodTree) { + if (super.isNotFullyInitializedReceiver(methodTree)) { + return true; + } + AnnotatedDeclaredType receiverType = getAnnotatedType(methodTree).getReceiverType(); + if (receiverType != null) { + return isUnknownInitialization(receiverType) || isUnderInitialization(receiverType); + } else { + // There is no receiver e.g. in static methods. + return false; + } } - } - /** The {@link QualifierHierarchy} for the initialization type system. */ - protected class InitializationQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + /** + * Creates a {@link UnknownInitialization} annotation with a given type frame. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnknownInitialization} annotation with the given argument + */ + public AnnotationMirror createUnknownInitializationAnnotation(Class typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, UnknownInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); + } - /** Qualifier kind for the @{@link UnknownInitialization} annotation. */ - private final QualifierKind UNKNOWN_INIT; + /** + * Creates an {@link UnknownInitialization} annotation with a given type frame. + * + * @param typeFrame the type down to which some value has been initialized + * @return an {@link UnknownInitialization} annotation with the given argument + */ + public AnnotationMirror createUnknownInitializationAnnotation(TypeMirror typeFrame) { + assert typeFrame != null; + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, UnknownInitialization.class); + builder.setValue("value", typeFrame); + return builder.build(); + } - /** Qualifier kind for the @{@link UnderInitialization} annotation. */ - private final QualifierKind UNDER_INIT; + /** + * Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link UnderInitialization} + */ + public boolean isUnderInitialization(AnnotationMirror anno) { + return areSameByClass(anno, UnderInitialization.class); + } - /** Create an InitializationQualifierHierarchy. */ - protected InitializationQualifierHierarchy() { - super( - InitializationParentAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - InitializationParentAnnotatedTypeFactory.this); - UNKNOWN_INIT = getQualifierKind(UNKNOWN_INITIALIZATION); - UNDER_INIT = getQualifierKind(UNDER_INITALIZATION); + /** + * Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link UnknownInitialization} + */ + public boolean isUnknownInitialization(AnnotationMirror anno) { + return areSameByClass(anno, UnknownInitialization.class); + } + + /** + * Is {@code anno} the bottom annotation? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link FBCBottom} + */ + public boolean isFbcBottom(AnnotationMirror anno) { + return AnnotationUtils.areSame(anno, FBCBOTTOM); + } + + /** + * Is {@code anno} the {@link Initialized} annotation? + * + * @param anno the annotation to check + * @return true if {@code anno} is {@link Initialized} + */ + public boolean isInitialized(AnnotationMirror anno) { + return AnnotationUtils.areSame(anno, INITIALIZED); + } + + /** + * Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link UnderInitialization} + */ + public boolean isUnderInitialization(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(UnderInitialization.class); + } + + /** + * Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link UnknownInitialization} + */ + public boolean isUnknownInitialization(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(UnknownInitialization.class); + } + + /** + * Does {@code anno} have the bottom annotation? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link FBCBottom} + */ + public boolean isFbcBottom(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(FBCBottom.class); + } + + /** + * Does {@code anno} have the annotation {@link Initialized}? + * + * @param anno the annotation to check + * @return true if {@code anno} has {@link Initialized} + */ + public boolean isInitialized(AnnotatedTypeMirror anno) { + return anno.hasEffectiveAnnotation(Initialized.class); + } + + /** + * Return true if the type is initialized with respect to the given frame -- that is, all of the + * fields of the frame are initialized. + * + * @param type the type whose initialization type qualifiers to check + * @param frame a class in {@code type}'s class hierarchy + * @return true if the type is initialized for the given frame + */ + public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) { + if (isInitialized(type)) { + return true; + } + + AnnotationMirror initializationAnno = + type.getEffectiveAnnotationInHierarchy(UNKNOWN_INITIALIZATION); + if (initializationAnno == null) { + initializationAnno = type.getEffectiveAnnotationInHierarchy(UNDER_INITALIZATION); + } + + TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno); + Types types = processingEnv.getTypeUtils(); + return types.isSubtype(typeFrame, types.erasure(frame)); + } + + /** + * Returns the type frame (that is, the argument) of a given initialization annotation. + * + * @param annotation a {@link UnderInitialization} or {@link UnknownInitialization} annotation + * @return the annotation's argument + */ + public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) { + if (AnnotationUtils.areSameByName( + annotation, + "org.checkerframework.checker.initialization.qual.UnderInitialization")) { + return AnnotationUtils.getElementValue( + annotation, + underInitializationValueElement, + TypeMirror.class, + objectTypeMirror); + } else { + return AnnotationUtils.getElementValue( + annotation, + unknownInitializationValueElement, + TypeMirror.class, + objectTypeMirror); + } + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new InitializationQualifierHierarchy(); } @Override - public boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (!subKind.isSubtypeOf(superKind)) { + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator( + super.createTypeAnnotator(), new CommitmentTypeAnnotator(this)); + } + + /** + * Returns {@code false}. Redundancy in only the initialization hierarchy is ok and may even be + * caused by implicit default annotations. The parent checker should determine whether to warn + * about redundancy. + */ + @Override + public boolean shouldWarnIfStubRedundantWithBytecode() { return false; - } else if ((subKind == UNDER_INIT && superKind == UNDER_INIT) - || (subKind == UNDER_INIT && superKind == UNKNOWN_INIT) - || (subKind == UNKNOWN_INIT && superKind == UNKNOWN_INIT)) { - // Thus, we only need to look at the type frame. - TypeMirror frame1 = getTypeFrameFromAnnotation(subAnno); - TypeMirror frame2 = getTypeFrameFromAnnotation(superAnno); - return types.isSubtype(frame1, frame2); - } else { - return true; - } } @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror anno1, - QualifierKind qual1, - AnnotationMirror anno2, - QualifierKind qual2, - QualifierKind lubKind) { - // Handle the case where one is a subtype of the other. - if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { - return anno2; - } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { - return anno1; - } - boolean unknowninit1 = isUnknownInitialization(anno1); - boolean unknowninit2 = isUnknownInitialization(anno2); - boolean underinit1 = isUnderInitialization(anno1); - boolean underinit2 = isUnderInitialization(anno2); - - // Handle @Initialized. - if (isInitialized(anno1)) { - assert underinit2; - return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno2)); - } else if (isInitialized(anno2)) { - assert underinit1; - return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno1)); - } - - if (underinit1 && underinit2) { - return createUnderInitializationAnnotation( - lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2))); - } - - assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); - return createUnknownInitializationAnnotation( - lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2))); + protected TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because we want our CommitmentTreeAnnotator + // instead of the default PropagationTreeAnnotator + List treeAnnotators = new ArrayList<>(2); + treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); + if (dependentTypesHelper.hasDependentAnnotations()) { + treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); + } + treeAnnotators.add(new CommitmentTreeAnnotator(this)); + return new ListTreeAnnotator(treeAnnotators); } /** - * Returns the least upper bound of two Java basetypes (without annotations). - * - * @param a the first argument - * @param b the second argument - * @return the lub of the two arguments + * This type annotator adds the correct UnderInitialization annotation to super constructors. + */ + protected class CommitmentTypeAnnotator extends TypeAnnotator { + + /** + * Creates a new CommitmentTypeAnnotator. + * + * @param atypeFactory this factory + */ + public CommitmentTypeAnnotator(InitializationParentAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + Void result = super.visitExecutable(t, p); + Element elem = t.getElement(); + if (elem.getKind() == ElementKind.CONSTRUCTOR) { + AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); + DeclaredType underlyingType = returnType.getUnderlyingType(); + // TODO: Make sure we check explicit annotations somewhere. + returnType.replaceAnnotation( + getUnderInitializationAnnotationOfSuperType(underlyingType)); + } + return result; + } + } + + /** + * This tree annotator modifies the propagation tree annotator to add propagation rules for the + * freedom-before-commitment system. */ - protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) { - if (types.isSubtype(a, b)) { - return b; - } else if (types.isSubtype(b, a)) { - return a; - } + protected class CommitmentTreeAnnotator extends PropagationTreeAnnotator { + + /** + * Creates a new CommitmentTreeAnnotator. + * + * @param initializationAnnotatedTypeFactory this factory + */ + public CommitmentTreeAnnotator( + InitializationParentAnnotatedTypeFactory initializationAnnotatedTypeFactory) { + super(initializationAnnotatedTypeFactory); + } + + @Override + public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { + Void result = super.visitMethod(tree, p); + if (TreeUtils.isConstructor(tree)) { + assert p instanceof AnnotatedExecutableType; + AnnotatedExecutableType exeType = (AnnotatedExecutableType) p; + DeclaredType underlyingType = + (DeclaredType) exeType.getReturnType().getUnderlyingType(); + AnnotationMirror a = getUnderInitializationAnnotationOfSuperType(underlyingType); + exeType.getReturnType().replaceAnnotation(a); + } + return result; + } + + @Override + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror p) { + super.visitNewClass(tree, p); + boolean allInitialized = true; + Type type = ((JCTree) tree).type; + for (ExpressionTree a : tree.getArguments()) { + if (!TreeUtils.isStandaloneExpression(a)) { + continue; + } + boolean oldShouldCache = shouldCache; + shouldCache = false; + AnnotatedTypeMirror t = getAnnotatedType(a); + shouldCache = oldShouldCache; + allInitialized &= (isInitialized(t) || isFbcBottom(t)); + } + if (!allInitialized) { + p.replaceAnnotation(createUnderInitializationAnnotation(type)); + return null; + } + p.replaceAnnotation(INITIALIZED); + return null; + } + + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (tree.getKind() != Tree.Kind.NULL_LITERAL) { + type.addAnnotation(INITIALIZED); + } + return super.visitLiteral(tree, type); + } + + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + // The most precise element type for `new Object[] {null}` is @FBCBottom, but + // the most useful element type is @Initialized (which is also accurate). + AnnotatedArrayType arrayType = (AnnotatedArrayType) type; + AnnotatedTypeMirror componentType = arrayType.getComponentType(); + if (componentType.hasEffectiveAnnotation(FBCBOTTOM)) { + componentType.replaceAnnotation(INITIALIZED); + } + return null; + } + + @Override + public Void visitMemberSelect( + MemberSelectTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + if (TreeUtils.isArrayLengthAccess(tree)) { + annotatedTypeMirror.replaceAnnotation(INITIALIZED); + } + return super.visitMemberSelect(tree, annotatedTypeMirror); + } + + /* The result of a binary or unary operator is either primitive or a String. + * Primitives have no fields and are thus always @Initialized. + * Since all String constructors return @Initialized strings, Strings + * are also always @Initialized. */ - return TypesUtils.leastUpperBound(a, b, processingEnv); + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + return null; + } + + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + return null; + } } + /** The {@link QualifierHierarchy} for the initialization type system. */ + protected class InitializationQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + /** Qualifier kind for the @{@link UnknownInitialization} annotation. */ + private final QualifierKind UNKNOWN_INIT; + + /** Qualifier kind for the @{@link UnderInitialization} annotation. */ + private final QualifierKind UNDER_INIT; + + /** Create an InitializationQualifierHierarchy. */ + protected InitializationQualifierHierarchy() { + super( + InitializationParentAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + InitializationParentAnnotatedTypeFactory.this); + UNKNOWN_INIT = getQualifierKind(UNKNOWN_INITIALIZATION); + UNDER_INIT = getQualifierKind(UNDER_INITALIZATION); + } + + @Override + public boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (!subKind.isSubtypeOf(superKind)) { + return false; + } else if ((subKind == UNDER_INIT && superKind == UNDER_INIT) + || (subKind == UNDER_INIT && superKind == UNKNOWN_INIT) + || (subKind == UNKNOWN_INIT && superKind == UNKNOWN_INIT)) { + // Thus, we only need to look at the type frame. + TypeMirror frame1 = getTypeFrameFromAnnotation(subAnno); + TypeMirror frame2 = getTypeFrameFromAnnotation(superAnno); + return types.isSubtype(frame1, frame2); + } else { + return true; + } + } + + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror anno1, + QualifierKind qual1, + AnnotationMirror anno2, + QualifierKind qual2, + QualifierKind lubKind) { + // Handle the case where one is a subtype of the other. + if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { + return anno2; + } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { + return anno1; + } + boolean unknowninit1 = isUnknownInitialization(anno1); + boolean unknowninit2 = isUnknownInitialization(anno2); + boolean underinit1 = isUnderInitialization(anno1); + boolean underinit2 = isUnderInitialization(anno2); + + // Handle @Initialized. + if (isInitialized(anno1)) { + assert underinit2; + return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno2)); + } else if (isInitialized(anno2)) { + assert underinit1; + return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno1)); + } + + if (underinit1 && underinit2) { + return createUnderInitializationAnnotation( + lubTypeFrame( + getTypeFrameFromAnnotation(anno1), + getTypeFrameFromAnnotation(anno2))); + } + + assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); + return createUnknownInitializationAnnotation( + lubTypeFrame( + getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2))); + } + + /** + * Returns the least upper bound of two Java basetypes (without annotations). + * + * @param a the first argument + * @param b the second argument + * @return the lub of the two arguments + */ + protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) { + if (types.isSubtype(a, b)) { + return b; + } else if (types.isSubtype(b, a)) { + return a; + } + + return TypesUtils.leastUpperBound(a, b, processingEnv); + } + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror anno1, + QualifierKind qual1, + AnnotationMirror anno2, + QualifierKind qual2, + QualifierKind glbKind) { + // Handle the case where one is a subtype of the other. + if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { + return anno1; + } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { + return anno2; + } + boolean unknowninit1 = isUnknownInitialization(anno1); + boolean unknowninit2 = isUnknownInitialization(anno2); + boolean underinit1 = isUnderInitialization(anno1); + boolean underinit2 = isUnderInitialization(anno2); + + // Handle @Initialized. + if (isInitialized(anno1)) { + assert underinit2; + return FBCBOTTOM; + } else if (isInitialized(anno2)) { + assert underinit1; + return FBCBOTTOM; + } + + TypeMirror typeFrame = + TypesUtils.greatestLowerBound( + getTypeFrameFromAnnotation(anno1), + getTypeFrameFromAnnotation(anno2), + processingEnv); + if (typeFrame.getKind() == TypeKind.ERROR + || typeFrame.getKind() == TypeKind.INTERSECTION) { + return FBCBOTTOM; + } + + if (underinit1 && underinit2) { + return createUnderInitializationAnnotation(typeFrame); + } + + assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); + return createUnderInitializationAnnotation(typeFrame); + } + } + + // TODO: check where this method should go. @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror anno1, - QualifierKind qual1, - AnnotationMirror anno2, - QualifierKind qual2, - QualifierKind glbKind) { - // Handle the case where one is a subtype of the other. - if (isSubtypeWithElements(anno1, qual1, anno2, qual2)) { - return anno1; - } else if (isSubtypeWithElements(anno2, qual2, anno1, qual1)) { - return anno2; - } - boolean unknowninit1 = isUnknownInitialization(anno1); - boolean unknowninit2 = isUnknownInitialization(anno2); - boolean underinit1 = isUnderInitialization(anno1); - boolean underinit2 = isUnderInitialization(anno2); - - // Handle @Initialized. - if (isInitialized(anno1)) { - assert underinit2; - return FBCBOTTOM; - } else if (isInitialized(anno2)) { - assert underinit1; - return FBCBOTTOM; - } - - TypeMirror typeFrame = - TypesUtils.greatestLowerBound( - getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2), processingEnv); - if (typeFrame.getKind() == TypeKind.ERROR || typeFrame.getKind() == TypeKind.INTERSECTION) { - return FBCBOTTOM; - } - - if (underinit1 && underinit2) { - return createUnderInitializationAnnotation(typeFrame); - } - - assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2); - return createUnderInitializationAnnotation(typeFrame); - } - } - - // TODO: check where this method should go. - @Override - protected ParameterizedExecutableType methodFromUse( - ExpressionTree tree, - ExecutableElement methodElt, - AnnotatedTypeMirror receiverType, - boolean inferTypeArgs) { - ParameterizedExecutableType x = - super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); - if (tree.getKind() == Tree.Kind.MEMBER_REFERENCE - && ((MemberReferenceTree) tree).getMode() == ReferenceMode.NEW) { - x.executableType.getReturnType().replaceAnnotation(INITIALIZED); - } - return x; - } + protected ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + ParameterizedExecutableType x = + super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); + if (tree.getKind() == Tree.Kind.MEMBER_REFERENCE + && ((MemberReferenceTree) tree).getMode() == ReferenceMode.NEW) { + x.executableType.getReturnType().replaceAnnotation(INITIALIZED); + } + return x; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java index 4efdb783383..09a38400b19 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationStore.java @@ -1,9 +1,5 @@ package org.checkerframework.checker.initialization; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer; import org.checkerframework.dataflow.expression.ClassName; import org.checkerframework.dataflow.expression.FieldAccess; @@ -13,6 +9,12 @@ import org.checkerframework.framework.flow.CFValue; import org.plumelib.util.ToStringComparator; +import java.util.HashSet; +import java.util.Set; + +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; + /** * A store that extends {@code CFAbstractStore} and additionally tracks which fields of the 'self' * reference have been initialized. @@ -21,194 +23,195 @@ */ public class InitializationStore extends CFAbstractStore { - /** The set of fields that are initialized. */ - protected final Set initializedFields; - - /** - * Creates a new InitializationStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics? - */ - public InitializationStore(InitializationAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - // The initialCapacity for the two maps is set to 4, an arbitrary, small value. - initializedFields = new HashSet<>(4); - } - - /** - * {@inheritDoc} - * - *

          If the receiver is a field, and has an invariant annotation, then it can be considered - * initialized. - */ - @Override - public void insertValue(JavaExpression je, CFValue value, boolean permitNondeterministic) { - if (!shouldInsert(je, value, permitNondeterministic)) { - return; + /** The set of fields that are initialized. */ + protected final Set initializedFields; + + /** + * Creates a new InitializationStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics? + */ + public InitializationStore(InitializationAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + // The initialCapacity for the two maps is set to 4, an arbitrary, small value. + initializedFields = new HashSet<>(4); + } + + /** + * {@inheritDoc} + * + *

          If the receiver is a field, and has an invariant annotation, then it can be considered + * initialized. + */ + @Override + public void insertValue(JavaExpression je, CFValue value, boolean permitNondeterministic) { + if (!shouldInsert(je, value, permitNondeterministic)) { + return; + } + + super.insertValue(je, value, permitNondeterministic); + + if (je instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) je; + if (fa.getReceiver() instanceof ThisReference + || fa.getReceiver() instanceof ClassName) { + addInitializedField(fa.getField()); + } + } + } + + /** + * A copy constructor. + * + * @param other the store to copy + */ + public InitializationStore(InitializationStore other) { + super(other); + initializedFields = new HashSet<>(other.initializedFields); } - super.insertValue(je, value, permitNondeterministic); + /** + * Mark the field identified by the element {@code field} as initialized if it belongs to the + * current class, or is static (in which case there is no aliasing issue and we can just add all + * static fields). + * + * @param field a field that is initialized + */ + public void addInitializedField(FieldAccess field) { + boolean fieldOnThisReference = field.getReceiver() instanceof ThisReference; + boolean staticField = field.isStatic(); + if (fieldOnThisReference || staticField) { + initializedFields.add(field.getField()); + } + } - if (je instanceof FieldAccess) { - FieldAccess fa = (FieldAccess) je; - if (fa.getReceiver() instanceof ThisReference || fa.getReceiver() instanceof ClassName) { - addInitializedField(fa.getField()); - } + /** + * Mark the field identified by the element {@code f} as initialized (the caller needs to ensure + * that the field belongs to the current class, or is a static field). + * + * @param f a field that is initialized + */ + public void addInitializedField(VariableElement f) { + initializedFields.add(f); } - } - - /** - * A copy constructor. - * - * @param other the store to copy - */ - public InitializationStore(InitializationStore other) { - super(other); - initializedFields = new HashSet<>(other.initializedFields); - } - - /** - * Mark the field identified by the element {@code field} as initialized if it belongs to the - * current class, or is static (in which case there is no aliasing issue and we can just add all - * static fields). - * - * @param field a field that is initialized - */ - public void addInitializedField(FieldAccess field) { - boolean fieldOnThisReference = field.getReceiver() instanceof ThisReference; - boolean staticField = field.isStatic(); - if (fieldOnThisReference || staticField) { - initializedFields.add(field.getField()); + + /** Is the field identified by the element {@code f} initialized? */ + public boolean isFieldInitialized(Element f) { + return initializedFields.contains(f); } - } - - /** - * Mark the field identified by the element {@code f} as initialized (the caller needs to ensure - * that the field belongs to the current class, or is a static field). - * - * @param f a field that is initialized - */ - public void addInitializedField(VariableElement f) { - initializedFields.add(f); - } - - /** Is the field identified by the element {@code f} initialized? */ - public boolean isFieldInitialized(Element f) { - return initializedFields.contains(f); - } - - @Override - protected boolean supersetOf(CFAbstractStore o) { - if (!(o instanceof InitializationStore)) { - return false; + + @Override + protected boolean supersetOf(CFAbstractStore o) { + if (!(o instanceof InitializationStore)) { + return false; + } + InitializationStore other = (InitializationStore) o; + + for (Element field : other.initializedFields) { + if (!initializedFields.contains(field)) { + return false; + } + } + + return super.supersetOf(other); + } + + @Override + public InitializationStore leastUpperBound(InitializationStore other) { + InitializationStore result = super.leastUpperBound(other); + + result.initializedFields.addAll(other.initializedFields); + result.initializedFields.retainAll(initializedFields); + + return result; } - InitializationStore other = (InitializationStore) o; - for (Element field : other.initializedFields) { - if (!initializedFields.contains(field)) { + /* + * TODO: implement a meaningful `isDeclaredInitialized`. + * + @Override + protected CFValue newFieldValueAfterMethodCall( + FieldAccess fieldAccess, + GenericAnnotatedTypeFactory atypeFactory, + CFValue value) { + if (isDeclaredInitialized(fieldAccess)) { + return value; + } + + return super.newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + } + */ + + /* + * Determine whether the given field is declared as {@link Initialized} (taking into account + * viewpoint adaption for {@link NotOnlyInitialized}). + * + * @param fieldAccess the field to check + * @return whether the given field is declared as {@link Initialized} (taking into account + * viewpoint adaption for {@link NotOnlyInitialized}) + * + protected boolean isDeclaredInitialized(FieldAccess fieldAccess) { + // Returning false is the conservative answer, but not much faster than asking the ATF. return false; - } + /* + InitializationParentAnnotatedTypeFactory atypeFactory = + (InitializationParentAnnotatedTypeFactory) analysis.getTypeFactory(); + AnnotatedTypeMirror declField = atypeFactory.getAnnotatedType(fieldAccess.getField()); + if (!declField.hasAnnotation(atypeFactory.INITIALIZED)) { + return false; + } + + AnnotatedTypeMirror receiverType; + if (thisValue != null + && thisValue.getUnderlyingType().getKind() != TypeKind.ERROR + && thisValue.getUnderlyingType().getKind() != TypeKind.NULL) { + receiverType = + AnnotatedTypeMirror.createType( + thisValue.getUnderlyingType(), atypeFactory, false); + for (AnnotationMirror anno : thisValue.getAnnotations()) { + receiverType.replaceAnnotation(anno); + } + } else if (!fieldAccess.isStatic()) { + receiverType = + AnnotatedTypeMirror.createType( + fieldAccess.getReceiver().getType(), atypeFactory, false) + .getErased(); + receiverType.addAnnotations(atypeFactory.getQualifierHierarchy().getTopAnnotations()); + } else { + receiverType = null; + } + + if (receiverType != null) { + return receiverType.hasAnnotation(atypeFactory.INITIALIZED); + } else { + // The field is static and INITIALIZED, so there is nothing else to check. + return true; + } + } + */ + + @Override + protected String internalVisualize(CFGVisualizer viz) { + String superVisualize = super.internalVisualize(viz); + + String initializedVisualize = + viz.visualizeStoreKeyVal( + "initialized fields", ToStringComparator.sorted(initializedFields)); + + if (superVisualize.isEmpty()) { + return String.join(viz.getSeparator(), initializedVisualize); + } else { + return String.join(viz.getSeparator(), superVisualize, initializedVisualize); + } } - return super.supersetOf(other); - } - - @Override - public InitializationStore leastUpperBound(InitializationStore other) { - InitializationStore result = super.leastUpperBound(other); - - result.initializedFields.addAll(other.initializedFields); - result.initializedFields.retainAll(initializedFields); - - return result; - } - - /* - * TODO: implement a meaningful `isDeclaredInitialized`. - * - @Override - protected CFValue newFieldValueAfterMethodCall( - FieldAccess fieldAccess, - GenericAnnotatedTypeFactory atypeFactory, - CFValue value) { - if (isDeclaredInitialized(fieldAccess)) { - return value; - } - - return super.newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); - } - */ - - /* - * Determine whether the given field is declared as {@link Initialized} (taking into account - * viewpoint adaption for {@link NotOnlyInitialized}). - * - * @param fieldAccess the field to check - * @return whether the given field is declared as {@link Initialized} (taking into account - * viewpoint adaption for {@link NotOnlyInitialized}) - * - protected boolean isDeclaredInitialized(FieldAccess fieldAccess) { - // Returning false is the conservative answer, but not much faster than asking the ATF. - return false; - /* - InitializationParentAnnotatedTypeFactory atypeFactory = - (InitializationParentAnnotatedTypeFactory) analysis.getTypeFactory(); - AnnotatedTypeMirror declField = atypeFactory.getAnnotatedType(fieldAccess.getField()); - if (!declField.hasAnnotation(atypeFactory.INITIALIZED)) { - return false; - } - - AnnotatedTypeMirror receiverType; - if (thisValue != null - && thisValue.getUnderlyingType().getKind() != TypeKind.ERROR - && thisValue.getUnderlyingType().getKind() != TypeKind.NULL) { - receiverType = - AnnotatedTypeMirror.createType( - thisValue.getUnderlyingType(), atypeFactory, false); - for (AnnotationMirror anno : thisValue.getAnnotations()) { - receiverType.replaceAnnotation(anno); - } - } else if (!fieldAccess.isStatic()) { - receiverType = - AnnotatedTypeMirror.createType( - fieldAccess.getReceiver().getType(), atypeFactory, false) - .getErased(); - receiverType.addAnnotations(atypeFactory.getQualifierHierarchy().getTopAnnotations()); - } else { - receiverType = null; - } - - if (receiverType != null) { - return receiverType.hasAnnotation(atypeFactory.INITIALIZED); - } else { - // The field is static and INITIALIZED, so there is nothing else to check. - return true; - } - } - */ - - @Override - protected String internalVisualize(CFGVisualizer viz) { - String superVisualize = super.internalVisualize(viz); - - String initializedVisualize = - viz.visualizeStoreKeyVal( - "initialized fields", ToStringComparator.sorted(initializedFields)); - - if (superVisualize.isEmpty()) { - return String.join(viz.getSeparator(), initializedVisualize); - } else { - return String.join(viz.getSeparator(), superVisualize, initializedVisualize); + /** + * Returns the analysis associated with this store. + * + * @return the analysis associated with this store + */ + public InitializationAnalysis getAnalysis() { + return (InitializationAnalysis) analysis; } - } - - /** - * Returns the analysis associated with this store. - * - * @return the analysis associated with this store - */ - public InitializationAnalysis getAnalysis() { - return (InitializationAnalysis) analysis; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java index 2ca38944d7c..f6c0cbff8c8 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java @@ -3,14 +3,7 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.code.Symbol; -import java.util.ArrayList; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; + import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; @@ -25,6 +18,16 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.List; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; + /** * A transfer function that extends {@link CFAbstractTransfer} and tracks {@link * InitializationStore}s. In addition to the features of {@link CFAbstractTransfer}, this transfer @@ -40,118 +43,118 @@ * */ public class InitializationTransfer - extends CFAbstractTransfer { - - /** The initialization type factory */ - protected final InitializationParentAnnotatedTypeFactory atypeFactory; - - /** - * Create a new InitializationTransfer for the given analysis. - * - * @param analysis init analysi.s - */ - public InitializationTransfer(InitializationAnalysis analysis) { - super(analysis); - this.atypeFactory = analysis.getTypeFactory(); - } - - /** - * Returns the fields that can safely be considered initialized after the method call {@code - * node}. - * - * @param node a method call - * @return the fields that are initialized after the method call - */ - protected List initializedFieldsAfterCall(MethodInvocationNode node) { - List result = new ArrayList<>(); - MethodInvocationTree tree = node.getTree(); - ExecutableElement method = TreeUtils.elementFromUse(tree); - boolean isConstructor = method.getSimpleName().contentEquals(""); - Node receiver = node.getTarget().getReceiver(); - String methodString = tree.getMethodSelect().toString(); - - // Case 1: After a call to the constructor of the same class, all - // fields are guaranteed to be initialized. - if (isConstructor && receiver instanceof ThisNode && methodString.equals("this")) { - ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); - TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); - markFieldsAsInitialized(result, clazzElem); + extends CFAbstractTransfer { + + /** The initialization type factory */ + protected final InitializationParentAnnotatedTypeFactory atypeFactory; + + /** + * Create a new InitializationTransfer for the given analysis. + * + * @param analysis init analysi.s + */ + public InitializationTransfer(InitializationAnalysis analysis) { + super(analysis); + this.atypeFactory = analysis.getTypeFactory(); } - // Case 4: After a call to the constructor of the super class, all - // fields of any super class are guaranteed to be initialized. - if (isConstructor && receiver instanceof ThisNode && methodString.equals("super")) { - ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); - TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); - TypeMirror superClass = clazzElem.getSuperclass(); - - while (superClass != null && superClass.getKind() != TypeKind.NONE) { - clazzElem = (TypeElement) analysis.getTypes().asElement(superClass); - superClass = clazzElem.getSuperclass(); - markFieldsAsInitialized(result, clazzElem); - } + /** + * Returns the fields that can safely be considered initialized after the method call {@code + * node}. + * + * @param node a method call + * @return the fields that are initialized after the method call + */ + protected List initializedFieldsAfterCall(MethodInvocationNode node) { + List result = new ArrayList<>(); + MethodInvocationTree tree = node.getTree(); + ExecutableElement method = TreeUtils.elementFromUse(tree); + boolean isConstructor = method.getSimpleName().contentEquals(""); + Node receiver = node.getTarget().getReceiver(); + String methodString = tree.getMethodSelect().toString(); + + // Case 1: After a call to the constructor of the same class, all + // fields are guaranteed to be initialized. + if (isConstructor && receiver instanceof ThisNode && methodString.equals("this")) { + ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); + TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); + markFieldsAsInitialized(result, clazzElem); + } + + // Case 4: After a call to the constructor of the super class, all + // fields of any super class are guaranteed to be initialized. + if (isConstructor && receiver instanceof ThisNode && methodString.equals("super")) { + ClassTree clazz = TreePathUtil.enclosingClass(analysis.getTypeFactory().getPath(tree)); + TypeElement clazzElem = TreeUtils.elementFromDeclaration(clazz); + TypeMirror superClass = clazzElem.getSuperclass(); + + while (superClass != null && superClass.getKind() != TypeKind.NONE) { + clazzElem = (TypeElement) analysis.getTypes().asElement(superClass); + superClass = clazzElem.getSuperclass(); + markFieldsAsInitialized(result, clazzElem); + } + } + + return result; } - return result; - } - - /** - * Adds all the fields of the class {@code clazzElem} to the list of initialized fields {@code - * result}. - * - * @param result the list of initialized fields - * @param clazzElem the class whose fields to add - */ - protected void markFieldsAsInitialized(List result, TypeElement clazzElem) { - List fields = ElementFilter.fieldsIn(clazzElem.getEnclosedElements()); - for (VariableElement field : fields) { - if (((Symbol) field).type.tsym.completer != Symbol.Completer.NULL_COMPLETER - || ((Symbol) field).type.getKind() == TypeKind.ERROR) { - // If the type is not completed yet, we might run into trouble. Skip the field. - // TODO: is there a nicer solution? - // This was raised by Issue #244. - continue; - } - result.add(field); + /** + * Adds all the fields of the class {@code clazzElem} to the list of initialized fields {@code + * result}. + * + * @param result the list of initialized fields + * @param clazzElem the class whose fields to add + */ + protected void markFieldsAsInitialized(List result, TypeElement clazzElem) { + List fields = ElementFilter.fieldsIn(clazzElem.getEnclosedElements()); + for (VariableElement field : fields) { + if (((Symbol) field).type.tsym.completer != Symbol.Completer.NULL_COMPLETER + || ((Symbol) field).type.getKind() == TypeKind.ERROR) { + // If the type is not completed yet, we might run into trouble. Skip the field. + // TODO: is there a nicer solution? + // This was raised by Issue #244. + continue; + } + result.add(field); + } } - } - - @Override - public TransferResult visitAssignment( - AssignmentNode n, TransferInput in) { - TransferResult result = super.visitAssignment(n, in); - JavaExpression lhs = JavaExpression.fromNode(n.getTarget()); - - // If this is an assignment to a field of 'this', then mark the field as initialized. - if (!lhs.containsUnknown() && lhs instanceof FieldAccess) { - FieldAccess fa = (FieldAccess) lhs; - // Only a ternary expression may cause a conditional transfer result, e.g. - // condExpr#num0 = (obj instanceof List) - // In such cases, the LHS is never a FieldAccess, so we can assert that result - // is a regular transfer result. This is important because otherwise the - // addInitializedField would be called on a temporary, merged store. - assert result instanceof RegularTransferResult; - result.getRegularStore().addInitializedField(fa); + + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput in) { + TransferResult result = super.visitAssignment(n, in); + JavaExpression lhs = JavaExpression.fromNode(n.getTarget()); + + // If this is an assignment to a field of 'this', then mark the field as initialized. + if (!lhs.containsUnknown() && lhs instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) lhs; + // Only a ternary expression may cause a conditional transfer result, e.g. + // condExpr#num0 = (obj instanceof List) + // In such cases, the LHS is never a FieldAccess, so we can assert that result + // is a regular transfer result. This is important because otherwise the + // addInitializedField would be called on a temporary, merged store. + assert result instanceof RegularTransferResult; + result.getRegularStore().addInitializedField(fa); + } + return result; } - return result; - } - - /** - * If an invariant field is initialized and has the invariant annotation, then it has at least the - * invariant annotation. Note that only fields of the 'this' receiver are tracked for - * initialization. - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = super.visitMethodInvocation(n, in); - List newlyInitializedFields = initializedFieldsAfterCall(n); - if (!newlyInitializedFields.isEmpty()) { - for (VariableElement f : newlyInitializedFields) { - result.getThenStore().addInitializedField(f); - result.getElseStore().addInitializedField(f); - } + + /** + * If an invariant field is initialized and has the invariant annotation, then it has at least + * the invariant annotation. Note that only fields of the 'this' receiver are tracked for + * initialization. + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = super.visitMethodInvocation(n, in); + List newlyInitializedFields = initializedFieldsAfterCall(n); + if (!newlyInitializedFields.isEmpty()) { + for (VariableElement f : newlyInitializedFields) { + result.getThenStore().addInitializedField(f); + result.getElseStore().addInitializedField(f); + } + } + return result; } - return result; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java index c1c691ad972..9cb5828fe09 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java @@ -12,15 +12,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.StringJoiner; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -37,6 +29,17 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; + /* NO-AFU import org.checkerframework.common.wholeprograminference.WholeProgramInference; */ @@ -48,351 +51,357 @@ */ public class InitializationVisitor extends BaseTypeVisitor { - // Error message keys - private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_TYPE = - "initialization.invalid.field.type"; - private static final @CompilerMessageKey String COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE = - "initialization.invalid.constructor.return.type"; - private static final @CompilerMessageKey String - COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION = - "initialization.invalid.field.write.unknown"; - private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED = - "initialization.invalid.field.write.initialized"; - - /** List of fields in the current compilation unit that have been initialized. */ - protected final List initializedFields; - - /** The value of the assumeInitialized option. */ - protected final boolean assumeInitialized; - - /** - * Create an InitializationVisitor. - * - * @param checker the initialization checker - */ - public InitializationVisitor(BaseTypeChecker checker) { - super(checker); - initializedFields = new ArrayList<>(); - assumeInitialized = checker.hasOption("assumeInitialized"); - } - - @Override - protected InitializationAnnotatedTypeFactory createTypeFactory() { - return new InitializationAnnotatedTypeFactory(checker); - } - - @Override - public void visit(TreePath path) { - // This visitor does nothing if init checking is turned off. - if (!assumeInitialized) { - super.visit(path); + // Error message keys + private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_TYPE = + "initialization.invalid.field.type"; + private static final @CompilerMessageKey String COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE = + "initialization.invalid.constructor.return.type"; + private static final @CompilerMessageKey String + COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION = + "initialization.invalid.field.write.unknown"; + private static final @CompilerMessageKey String COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED = + "initialization.invalid.field.write.initialized"; + + /** List of fields in the current compilation unit that have been initialized. */ + protected final List initializedFields; + + /** The value of the assumeInitialized option. */ + protected final boolean assumeInitialized; + + /** + * Create an InitializationVisitor. + * + * @param checker the initialization checker + */ + public InitializationVisitor(BaseTypeChecker checker) { + super(checker); + initializedFields = new ArrayList<>(); + assumeInitialized = checker.hasOption("assumeInitialized"); } - } - - @Override - public void setRoot(CompilationUnitTree root) { - // Clean up the cache of initialized fields once per compilation unit. - // Alternatively, but harder to determine, this could be done once per top-level class. - initializedFields.clear(); - super.setRoot(root); - } - - @Override - protected void checkConstructorInvocation( - AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { - // Receiver annotations for constructors are forbidden, therefore no check is necessary. - // TODO: nested constructors can have receivers! - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Nothing to check - } - - @Override - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { - // Nothing to check - } - - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // field write of the form x.f = y - if (TreeUtils.isFieldAccess(varTree)) { - // cast is safe: a field access can only be an IdentifierTree or MemberSelectTree - ExpressionTree lhs = (ExpressionTree) varTree; - VariableElement el = TreeUtils.variableElementFromUse(lhs); - AnnotatedTypeMirror lhsReceiverType = atypeFactory.getReceiverType(lhs); - AnnotatedTypeMirror valueExpType = atypeFactory.getAnnotatedType(valueExpTree); - // the special FBC rules do not apply if there is an explicit - // UnknownInitialization annotation - AnnotationMirrorSet fieldAnnotations = atypeFactory.getAnnotatedType(el).getAnnotations(); - if (!AnnotationUtils.containsSameByName( - fieldAnnotations, atypeFactory.UNKNOWN_INITIALIZATION)) { - if (!ElementUtils.isStatic(el) - && !(atypeFactory.isInitialized(valueExpType) - || atypeFactory.isUnderInitialization(lhsReceiverType) - || atypeFactory.isFbcBottom(valueExpType))) { - @CompilerMessageKey String err; - if (atypeFactory.isInitialized(lhsReceiverType)) { - err = COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED; - } else { - err = COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION; - } - checker.reportError(varTree, err, varTree); - return false; // prevent issuing another error about subtyping - } - } + + @Override + protected InitializationAnnotatedTypeFactory createTypeFactory() { + return new InitializationAnnotatedTypeFactory(checker); } - return super.commonAssignmentCheck(varTree, valueExpTree, errorKey, extraArgs); - } - - @Override - protected void checkExceptionParameter(CatchTree node) { - // TODO Issue 363 - // https://github.com/eisop/checker-framework/issues/363 - } - - @Override - public void processClassTree(ClassTree tree) { - // go through all members and look for initializers. - // save all fields that are initialized and do not report errors about - // them later when checking constructors. - for (Tree member : tree.getMembers()) { - if (member.getKind() == Tree.Kind.BLOCK && !((BlockTree) member).isStatic()) { - BlockTree block = (BlockTree) member; - InitializationStore store = atypeFactory.getRegularExitStore(block); - - // Add field values for fields with an initializer. - for (FieldInitialValue fieldInitialValue : - store.getAnalysis().getFieldInitialValues()) { - if (fieldInitialValue.initializer != null) { - store.addInitializedField(fieldInitialValue.fieldDecl.getField()); - } + + @Override + public void visit(TreePath path) { + // This visitor does nothing if init checking is turned off. + if (!assumeInitialized) { + super.visit(path); } - List init = atypeFactory.getInitializedFields(store, getCurrentPath()); - initializedFields.addAll(init); - } } - super.processClassTree(tree); - - // Warn about uninitialized static fields. - Tree.Kind nodeKind = tree.getKind(); - // Skip interfaces (and annotations, which are interfaces). In an interface, every static - // field must be initialized. Java forbids uninitialized variables and static initializer - // blocks. - if (nodeKind != Tree.Kind.INTERFACE && nodeKind != Tree.Kind.ANNOTATION_TYPE) { - // See GenericAnnotatedTypeFactory.performFlowAnalysis for why we use - // the regular exit store of the class here. - InitializationStore store = atypeFactory.getRegularExitStore(tree); - if (store != null) { - // Add field values for fields with an initializer. - for (FieldInitialValue fieldInitialValue : - store.getAnalysis().getFieldInitialValues()) { - if (fieldInitialValue.initializer != null) { - store.addInitializedField(fieldInitialValue.fieldDecl.getField()); - } + @Override + public void setRoot(CompilationUnitTree root) { + // Clean up the cache of initialized fields once per compilation unit. + // Alternatively, but harder to determine, this could be done once per top-level class. + initializedFields.clear(); + super.setRoot(root); + } + + @Override + protected void checkConstructorInvocation( + AnnotatedDeclaredType dt, AnnotatedExecutableType constructor, NewClassTree src) { + // Receiver annotations for constructors are forbidden, therefore no check is necessary. + // TODO: nested constructors can have receivers! + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Nothing to check + } + + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + // Nothing to check + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // field write of the form x.f = y + if (TreeUtils.isFieldAccess(varTree)) { + // cast is safe: a field access can only be an IdentifierTree or MemberSelectTree + ExpressionTree lhs = (ExpressionTree) varTree; + VariableElement el = TreeUtils.variableElementFromUse(lhs); + AnnotatedTypeMirror lhsReceiverType = atypeFactory.getReceiverType(lhs); + AnnotatedTypeMirror valueExpType = atypeFactory.getAnnotatedType(valueExpTree); + // the special FBC rules do not apply if there is an explicit + // UnknownInitialization annotation + AnnotationMirrorSet fieldAnnotations = + atypeFactory.getAnnotatedType(el).getAnnotations(); + if (!AnnotationUtils.containsSameByName( + fieldAnnotations, atypeFactory.UNKNOWN_INITIALIZATION)) { + if (!ElementUtils.isStatic(el) + && !(atypeFactory.isInitialized(valueExpType) + || atypeFactory.isUnderInitialization(lhsReceiverType) + || atypeFactory.isFbcBottom(valueExpType))) { + @CompilerMessageKey String err; + if (atypeFactory.isInitialized(lhsReceiverType)) { + err = COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED; + } else { + err = COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION; + } + checker.reportError(varTree, err, varTree); + return false; // prevent issuing another error about subtyping + } + } } - } + return super.commonAssignmentCheck(varTree, valueExpTree, errorKey, extraArgs); + } - List receiverAnnotations = Collections.emptyList(); - checkFieldsInitialized(tree, true, store, receiverAnnotations); + @Override + protected void checkExceptionParameter(CatchTree node) { + // TODO Issue 363 + // https://github.com/eisop/checker-framework/issues/363 } - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - if (TreeUtils.isConstructor(tree)) { - Collection returnTypeAnnotations = - AnnotationUtils.getExplicitAnnotationsOnConstructorResult(tree); - // check for invalid constructor return type - for (Class c : atypeFactory.getSupportedTypeQualifiers()) { - for (AnnotationMirror a : returnTypeAnnotations) { - if (atypeFactory.areSameByClass(a, c)) { - checker.reportError(tree, COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE, tree); - break; - } + + @Override + public void processClassTree(ClassTree tree) { + // go through all members and look for initializers. + // save all fields that are initialized and do not report errors about + // them later when checking constructors. + for (Tree member : tree.getMembers()) { + if (member.getKind() == Tree.Kind.BLOCK && !((BlockTree) member).isStatic()) { + BlockTree block = (BlockTree) member; + InitializationStore store = atypeFactory.getRegularExitStore(block); + + // Add field values for fields with an initializer. + for (FieldInitialValue fieldInitialValue : + store.getAnalysis().getFieldInitialValues()) { + if (fieldInitialValue.initializer != null) { + store.addInitializedField(fieldInitialValue.fieldDecl.getField()); + } + } + List init = + atypeFactory.getInitializedFields(store, getCurrentPath()); + initializedFields.addAll(init); + } } - } - // Check that all fields have been initialized at the end of the constructor. - boolean isStatic = false; + super.processClassTree(tree); + + // Warn about uninitialized static fields. + Tree.Kind nodeKind = tree.getKind(); + // Skip interfaces (and annotations, which are interfaces). In an interface, every static + // field must be initialized. Java forbids uninitialized variables and static initializer + // blocks. + if (nodeKind != Tree.Kind.INTERFACE && nodeKind != Tree.Kind.ANNOTATION_TYPE) { + // See GenericAnnotatedTypeFactory.performFlowAnalysis for why we use + // the regular exit store of the class here. + InitializationStore store = atypeFactory.getRegularExitStore(tree); + if (store != null) { + // Add field values for fields with an initializer. + for (FieldInitialValue fieldInitialValue : + store.getAnalysis().getFieldInitialValues()) { + if (fieldInitialValue.initializer != null) { + store.addInitializedField(fieldInitialValue.fieldDecl.getField()); + } + } + } + + List receiverAnnotations = Collections.emptyList(); + checkFieldsInitialized(tree, true, store, receiverAnnotations); + } + } - InitializationStore store = atypeFactory.getRegularExitStore(tree); - List receiverAnnotations = getAllReceiverAnnotations(tree); - checkFieldsInitialized(tree, isStatic, store, receiverAnnotations); + @Override + public Void visitMethod(MethodTree tree, Void p) { + if (TreeUtils.isConstructor(tree)) { + Collection returnTypeAnnotations = + AnnotationUtils.getExplicitAnnotationsOnConstructorResult(tree); + // check for invalid constructor return type + for (Class c : atypeFactory.getSupportedTypeQualifiers()) { + for (AnnotationMirror a : returnTypeAnnotations) { + if (atypeFactory.areSameByClass(a, c)) { + checker.reportError(tree, COMMITMENT_INVALID_CONSTRUCTOR_RETURN_TYPE, tree); + break; + } + } + } + + // Check that all fields have been initialized at the end of the constructor. + boolean isStatic = false; + + InitializationStore store = atypeFactory.getRegularExitStore(tree); + List receiverAnnotations = getAllReceiverAnnotations(tree); + checkFieldsInitialized(tree, isStatic, store, receiverAnnotations); + } + return super.visitMethod(tree, p); } - return super.visitMethod(tree, p); - } - - /** - * The assignment/variable/method invocation tree currently being checked. - * - *

          In the case that the right-hand side is an object, this is used by {@link - * #reportCommonAssignmentError(AnnotatedTypeMirror, AnnotatedTypeMirror, Tree, String, - * Object...)} to get the correct store value for the right-hand side's fields and check whether - * they are initialized according to the target checker. - */ - protected Tree commonAssignmentTree; - - @Override - public Void visitVariable(VariableTree tree, Void p) { - Tree oldCommonAssignmentTree = commonAssignmentTree; - commonAssignmentTree = tree; - // is this a field (and not a local variable)? - if (TreeUtils.elementFromDeclaration(tree).getKind().isField()) { - AnnotationMirrorSet annotationMirrors = - atypeFactory.getAnnotatedType(tree).getExplicitAnnotations(); - // Fields cannot have commitment annotations. - for (Class c : atypeFactory.getSupportedTypeQualifiers()) { - for (AnnotationMirror a : annotationMirrors) { - if (atypeFactory.isUnknownInitialization(a)) { - continue; // unknown initialization is allowed - } - if (atypeFactory.areSameByClass(a, c)) { - checker.reportError(tree, COMMITMENT_INVALID_FIELD_TYPE, tree); - break; - } + + /** + * The assignment/variable/method invocation tree currently being checked. + * + *

          In the case that the right-hand side is an object, this is used by {@link + * #reportCommonAssignmentError(AnnotatedTypeMirror, AnnotatedTypeMirror, Tree, String, + * Object...)} to get the correct store value for the right-hand side's fields and check whether + * they are initialized according to the target checker. + */ + protected Tree commonAssignmentTree; + + @Override + public Void visitVariable(VariableTree tree, Void p) { + Tree oldCommonAssignmentTree = commonAssignmentTree; + commonAssignmentTree = tree; + // is this a field (and not a local variable)? + if (TreeUtils.elementFromDeclaration(tree).getKind().isField()) { + AnnotationMirrorSet annotationMirrors = + atypeFactory.getAnnotatedType(tree).getExplicitAnnotations(); + // Fields cannot have commitment annotations. + for (Class c : atypeFactory.getSupportedTypeQualifiers()) { + for (AnnotationMirror a : annotationMirrors) { + if (atypeFactory.isUnknownInitialization(a)) { + continue; // unknown initialization is allowed + } + if (atypeFactory.areSameByClass(a, c)) { + checker.reportError(tree, COMMITMENT_INVALID_FIELD_TYPE, tree); + break; + } + } + } } - } + super.visitVariable(tree, p); + commonAssignmentTree = oldCommonAssignmentTree; + return null; } - super.visitVariable(tree, p); - commonAssignmentTree = oldCommonAssignmentTree; - return null; - } - - @Override - public Void visitAssignment(AssignmentTree node, Void p) { - Tree oldCommonAssignmentTree = commonAssignmentTree; - commonAssignmentTree = node; - super.visitAssignment(node, p); - commonAssignmentTree = oldCommonAssignmentTree; - return null; - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree node, Void p) { - Tree oldCommonAssignmentTree = commonAssignmentTree; - commonAssignmentTree = node; - super.visitMethodInvocation(node, p); - commonAssignmentTree = oldCommonAssignmentTree; - return null; - } - - /** - * Returns the full list of annotations on the receiver. - * - * @param tree a method declaration - * @return all the annotations on the method's receiver - */ - private List getAllReceiverAnnotations(MethodTree tree) { - // TODO: get access to a Types instance and use it to get receiver type - // Or, extend ExecutableElement with such a method. - // Note that we cannot use the receiver type from AnnotatedExecutableType, because that - // would only have the nullness annotations; here we want to see all annotations on the - // receiver. - if (TreeUtils.isConstructor(tree)) { - com.sun.tools.javac.code.Symbol meth = - (com.sun.tools.javac.code.Symbol) TreeUtils.elementFromDeclaration(tree); - return meth.getRawTypeAttributes(); + + @Override + public Void visitAssignment(AssignmentTree node, Void p) { + Tree oldCommonAssignmentTree = commonAssignmentTree; + commonAssignmentTree = node; + super.visitAssignment(node, p); + commonAssignmentTree = oldCommonAssignmentTree; + return null; } - return Collections.emptyList(); - } - - /** - * Checks that all fields (all static fields if {@code staticFields} is true) are initialized at - * the end of a given constructor or static class initializer. - * - * @param tree a {@link ClassTree} if {@code staticFields} is true; a {@link MethodTree} for a - * constructor if {@code staticFields} is false. This is where errors are reported, if they - * are not reported at the fields themselves - * @param staticFields whether to check static fields or instance fields - * @param initExitStore the initialization exit store for the constructor or static initializer - * @param receiverAnnotations the annotations on the receiver - */ - protected void checkFieldsInitialized( - Tree tree, - boolean staticFields, - InitializationStore initExitStore, - List receiverAnnotations) { - // If the store is null, then the constructor cannot terminate successfully - if (initExitStore == null) { - return; + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void p) { + Tree oldCommonAssignmentTree = commonAssignmentTree; + commonAssignmentTree = node; + super.visitMethodInvocation(node, p); + commonAssignmentTree = oldCommonAssignmentTree; + return null; } - // Compact canonical record constructors do not generate visible assignments in the source, - // but by definition they assign to all the record's fields so we don't need to - // check for uninitialized fields in them: - if (tree.getKind() == Tree.Kind.METHOD - && TreeUtils.isCompactCanonicalRecordConstructor((MethodTree) tree)) { - return; + /** + * Returns the full list of annotations on the receiver. + * + * @param tree a method declaration + * @return all the annotations on the method's receiver + */ + private List getAllReceiverAnnotations(MethodTree tree) { + // TODO: get access to a Types instance and use it to get receiver type + // Or, extend ExecutableElement with such a method. + // Note that we cannot use the receiver type from AnnotatedExecutableType, because that + // would only have the nullness annotations; here we want to see all annotations on the + // receiver. + if (TreeUtils.isConstructor(tree)) { + com.sun.tools.javac.code.Symbol meth = + (com.sun.tools.javac.code.Symbol) TreeUtils.elementFromDeclaration(tree); + return meth.getRawTypeAttributes(); + } + return Collections.emptyList(); } - GenericAnnotatedTypeFactory targetFactory = - checker.getTypeFactoryOfSubcheckerOrNull( - ((InitializationChecker) checker).getTargetCheckerClass()); - // The target checker's store corresponding to initExitStore - CFAbstractStore targetExitStore = targetFactory.getRegularExitStore(tree); - List uninitializedFields = - atypeFactory.getUninitializedFields( - initExitStore, targetExitStore, getCurrentPath(), staticFields, receiverAnnotations); - uninitializedFields.removeAll(initializedFields); - - // If we are checking initialization of a class's static fields or of a default constructor, - // we issue an error for every uninitialized field at the respective field declaration. - // If we are checking a non-default constructor, we issue a single error at the constructor - // declaration. - boolean errorAtField = staticFields || TreeUtils.isSynthetic((MethodTree) tree); - - String errorMsg = - (staticFields - ? "initialization.static.field.uninitialized" - : errorAtField - ? "initialization.field.uninitialized" - : "initialization.fields.uninitialized"); - - // Remove fields with a relevant @SuppressWarnings annotation - uninitializedFields.removeIf( - f -> checker.shouldSuppressWarnings(TreeUtils.elementFromDeclaration(f), errorMsg)); - - if (!uninitializedFields.isEmpty()) { - if (errorAtField) { - // Issue each error at the relevant field - for (VariableTree f : uninitializedFields) { - checker.reportError(f, errorMsg, f.getName()); + /** + * Checks that all fields (all static fields if {@code staticFields} is true) are initialized at + * the end of a given constructor or static class initializer. + * + * @param tree a {@link ClassTree} if {@code staticFields} is true; a {@link MethodTree} for a + * constructor if {@code staticFields} is false. This is where errors are reported, if they + * are not reported at the fields themselves + * @param staticFields whether to check static fields or instance fields + * @param initExitStore the initialization exit store for the constructor or static initializer + * @param receiverAnnotations the annotations on the receiver + */ + protected void checkFieldsInitialized( + Tree tree, + boolean staticFields, + InitializationStore initExitStore, + List receiverAnnotations) { + // If the store is null, then the constructor cannot terminate successfully + if (initExitStore == null) { + return; } - } else { - // Issue all the errors at the relevant constructor - StringJoiner fieldsString = new StringJoiner(", "); - for (VariableTree f : uninitializedFields) { - fieldsString.add(f.getName()); + + // Compact canonical record constructors do not generate visible assignments in the source, + // but by definition they assign to all the record's fields so we don't need to + // check for uninitialized fields in them: + if (tree.getKind() == Tree.Kind.METHOD + && TreeUtils.isCompactCanonicalRecordConstructor((MethodTree) tree)) { + return; + } + + GenericAnnotatedTypeFactory targetFactory = + checker.getTypeFactoryOfSubcheckerOrNull( + ((InitializationChecker) checker).getTargetCheckerClass()); + // The target checker's store corresponding to initExitStore + CFAbstractStore targetExitStore = targetFactory.getRegularExitStore(tree); + List uninitializedFields = + atypeFactory.getUninitializedFields( + initExitStore, + targetExitStore, + getCurrentPath(), + staticFields, + receiverAnnotations); + uninitializedFields.removeAll(initializedFields); + + // If we are checking initialization of a class's static fields or of a default constructor, + // we issue an error for every uninitialized field at the respective field declaration. + // If we are checking a non-default constructor, we issue a single error at the constructor + // declaration. + boolean errorAtField = staticFields || TreeUtils.isSynthetic((MethodTree) tree); + + String errorMsg = + (staticFields + ? "initialization.static.field.uninitialized" + : errorAtField + ? "initialization.field.uninitialized" + : "initialization.fields.uninitialized"); + + // Remove fields with a relevant @SuppressWarnings annotation + uninitializedFields.removeIf( + f -> checker.shouldSuppressWarnings(TreeUtils.elementFromDeclaration(f), errorMsg)); + + if (!uninitializedFields.isEmpty()) { + if (errorAtField) { + // Issue each error at the relevant field + for (VariableTree f : uninitializedFields) { + checker.reportError(f, errorMsg, f.getName()); + } + } else { + // Issue all the errors at the relevant constructor + StringJoiner fieldsString = new StringJoiner(", "); + for (VariableTree f : uninitializedFields) { + fieldsString.add(f.getName()); + } + checker.reportError(tree, errorMsg, fieldsString); + } } - checker.reportError(tree, errorMsg, fieldsString); - } - } - /* NO-AFU - // Support -Ainfer command-line argument. - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - if (wpi != null) { - // For each uninitialized field, treat it as if the default value is assigned to it. - List uninitFields = new ArrayList<>(violatingFields); - uninitFields.addAll(nonviolatingFields); - for (VariableTree fieldTree : uninitFields) { - Element elt = TreeUtils.elementFromDeclaration(fieldTree); - wpi.updateFieldFromType( - fieldTree, - elt, - fieldTree.getName().toString(), - atypeFactory.getDefaultValueAnnotatedType(elt.asType())); - } + /* NO-AFU + // Support -Ainfer command-line argument. + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + if (wpi != null) { + // For each uninitialized field, treat it as if the default value is assigned to it. + List uninitFields = new ArrayList<>(violatingFields); + uninitFields.addAll(nonviolatingFields); + for (VariableTree fieldTree : uninitFields) { + Element elt = TreeUtils.elementFromDeclaration(fieldTree); + wpi.updateFieldFromType( + fieldTree, + elt, + fieldTree.getName().toString(), + atypeFactory.getDefaultValueAnnotatedType(elt.asType())); + } + } + */ } - */ - } } diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java index 015ccee3765..06da92f8c2e 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java @@ -6,12 +6,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.tools.javac.code.Symbol.MethodSymbol; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternMethod; import org.checkerframework.checker.interning.qual.Interned; @@ -36,6 +31,13 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * An {@link AnnotatedTypeFactory} that accounts for the properties of the Interned type system. * This type factory will add the {@link Interned} annotation to a type if the input: @@ -64,175 +66,176 @@ */ public class InterningAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@link UnknownInterned} annotation. */ - final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownInterned.class); - - /** The {@link Interned} annotation. */ - final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); - - /** The {@link InternedDistinct} annotation. */ - final AnnotationMirror INTERNED_DISTINCT = - AnnotationBuilder.fromClass(elements, InternedDistinct.class); - - /** A set containing just {@link #INTERNED}. */ - final AnnotationMirrorSet INTERNED_SET = AnnotationMirrorSet.singleton(INTERNED); + /** The {@link UnknownInterned} annotation. */ + final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownInterned.class); - /** - * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST. - * - * @param checker the checker to use - */ - public InterningAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + /** The {@link Interned} annotation. */ + final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); - // If you update the following, also update ../../../../../docs/manual/interning-checker.tex - addAliasedTypeAnnotation("com.sun.istack.internal.Interned", INTERNED); + /** The {@link InternedDistinct} annotation. */ + final AnnotationMirror INTERNED_DISTINCT = + AnnotationBuilder.fromClass(elements, InternedDistinct.class); - this.postInit(); - } + /** A set containing just {@link #INTERNED}. */ + final AnnotationMirrorSet INTERNED_SET = AnnotationMirrorSet.singleton(INTERNED); - @Override - protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { - return new InterningDefaultQualifierForUseTypeAnnotator(this); - } + /** + * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST. + * + * @param checker the checker to use + */ + public InterningAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); - /** - * Does not add defaults for type uses on constructor results. Constructor results should be - * {@code @UnknownInterned} by default. - */ - static class InterningDefaultQualifierForUseTypeAnnotator - extends DefaultQualifierForUseTypeAnnotator { + // If you update the following, also update ../../../../../docs/manual/interning-checker.tex + addAliasedTypeAnnotation("com.sun.istack.internal.Interned", INTERNED); - public InterningDefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); + this.postInit(); } @Override - public Void visitExecutable(AnnotatedExecutableType type, Void p) { - MethodSymbol methodElt = (MethodSymbol) type.getElement(); - - if (methodElt == null || methodElt.getKind() != ElementKind.CONSTRUCTOR) { - // Annotate method returns, not constructors. - scan(type.getReturnType(), p); - } - AnnotatedTypeMirror receiverType = type.getReceiverType(); - if (receiverType != null - // Intern method may be called on UnknownInterned object, so its receiver should - // not be annotated as @Interned. - && atypeFactory.getDeclAnnotation(methodElt, InternMethod.class) == null) { - scanAndReduce(receiverType, p, null); - } - scanAndReduce(type.getParameterTypes(), p, null); - scanAndReduce(type.getThrownTypes(), p, null); - scanAndReduce(type.getTypeVariables(), p, null); - return null; + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new InterningDefaultQualifierForUseTypeAnnotator(this); } - } - @Override - public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror typeMirror) { - if (typeMirror.getKind() == TypeKind.DECLARED - && ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.ENUM) { - return INTERNED_SET; - } - return super.getTypeDeclarationBounds(typeMirror); - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new InterningTreeAnnotator(this)); - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(new InterningTypeAnnotator(this), super.createTypeAnnotator()); - } - - @Override - public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(INTERNED) && ElementUtils.isCompileTimeConstant(element)) { - type.addAnnotation(INTERNED); - } - super.addComputedTypeAnnotations(element, type); - } - - /** A class for adding annotations based on tree. */ - private class InterningTreeAnnotator extends TreeAnnotator { - - InterningTreeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** + * Does not add defaults for type uses on constructor results. Constructor results should be + * {@code @UnknownInterned} by default. + */ + static class InterningDefaultQualifierForUseTypeAnnotator + extends DefaultQualifierForUseTypeAnnotator { + + public InterningDefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void p) { + MethodSymbol methodElt = (MethodSymbol) type.getElement(); + + if (methodElt == null || methodElt.getKind() != ElementKind.CONSTRUCTOR) { + // Annotate method returns, not constructors. + scan(type.getReturnType(), p); + } + AnnotatedTypeMirror receiverType = type.getReceiverType(); + if (receiverType != null + // Intern method may be called on UnknownInterned object, so its receiver should + // not be annotated as @Interned. + && atypeFactory.getDeclAnnotation(methodElt, InternMethod.class) == null) { + scanAndReduce(receiverType, p, null); + } + scanAndReduce(type.getParameterTypes(), p, null); + scanAndReduce(type.getThrownTypes(), p, null); + scanAndReduce(type.getTypeVariables(), p, null); + return null; + } } @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isCompileTimeString(tree)) { - type.replaceAnnotation(INTERNED); - } else if (TreeUtils.isStringConcatenation(tree)) { - type.replaceAnnotation(TOP); - } else if (type.getKind().isPrimitive() - || tree.getKind() == Tree.Kind.EQUAL_TO - || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { - type.replaceAnnotation(INTERNED); - } else { - type.replaceAnnotation(TOP); - } - return super.visitBinary(tree, type); + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror typeMirror) { + if (typeMirror.getKind() == TypeKind.DECLARED + && ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.ENUM) { + return INTERNED_SET; + } + return super.getTypeDeclarationBounds(typeMirror); } - /* Compound assignments never result in an interned result. - */ @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(TOP); - return super.visitCompoundAssignment(tree, type); + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new InterningTreeAnnotator(this)); } @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.typeOf(tree.getType()).getKind().isPrimitive()) { - type.replaceAnnotation(INTERNED); - } - return super.visitTypeCast(tree, type); + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(new InterningTypeAnnotator(this), super.createTypeAnnotator()); } @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - Element e = TreeUtils.elementFromUse(tree); - if (atypeFactory.getDeclAnnotation(e, FindDistinct.class) != null) { - // TODO: See note above about this being a poor implementation. - type.replaceAnnotation(INTERNED_DISTINCT); - } - return super.visitIdentifier(tree, type); + public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(INTERNED) + && ElementUtils.isCompileTimeConstant(element)) { + type.addAnnotation(INTERNED); + } + super.addComputedTypeAnnotations(element, type); } - } - /** Adds @Interned to enum types. */ - private class InterningTypeAnnotator extends TypeAnnotator { + /** A class for adding annotations based on tree. */ + private class InterningTreeAnnotator extends TreeAnnotator { + + InterningTreeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isCompileTimeString(tree)) { + type.replaceAnnotation(INTERNED); + } else if (TreeUtils.isStringConcatenation(tree)) { + type.replaceAnnotation(TOP); + } else if (type.getKind().isPrimitive() + || tree.getKind() == Tree.Kind.EQUAL_TO + || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { + type.replaceAnnotation(INTERNED); + } else { + type.replaceAnnotation(TOP); + } + return super.visitBinary(tree, type); + } + + /* Compound assignments never result in an interned result. + */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(TOP); + return super.visitCompoundAssignment(tree, type); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.typeOf(tree.getType()).getKind().isPrimitive()) { + type.replaceAnnotation(INTERNED); + } + return super.visitTypeCast(tree, type); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + Element e = TreeUtils.elementFromUse(tree); + if (atypeFactory.getDeclAnnotation(e, FindDistinct.class) != null) { + // TODO: See note above about this being a poor implementation. + type.replaceAnnotation(INTERNED_DISTINCT); + } + return super.visitIdentifier(tree, type); + } + } - InterningTypeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** Adds @Interned to enum types. */ + private class InterningTypeAnnotator extends TypeAnnotator { + + InterningTypeAnnotator(InterningAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType t, Void p) { + // case 3: Enum types, and the Enum class itself, are interned + Element elt = t.getUnderlyingType().asElement(); + assert elt != null; + if (elt.getKind() == ElementKind.ENUM) { + t.replaceAnnotation(INTERNED); + } + return super.visitDeclared(t, p); + } } + /** + * Unbox type and replace any interning type annotations with @Interned since all primitives can + * safely use ==. See case 4 in the class comments. + */ @Override - public Void visitDeclared(AnnotatedDeclaredType t, Void p) { - // case 3: Enum types, and the Enum class itself, are interned - Element elt = t.getUnderlyingType().asElement(); - assert elt != null; - if (elt.getKind() == ElementKind.ENUM) { - t.replaceAnnotation(INTERNED); - } - return super.visitDeclared(t, p); + public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) { + AnnotatedPrimitiveType primitive = super.getUnboxedType(type); + primitive.replaceAnnotation(INTERNED); + return primitive; } - } - - /** - * Unbox type and replace any interning type annotations with @Interned since all primitives can - * safely use ==. See case 4 in the class comments. - */ - @Override - public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) { - AnnotatedPrimitiveType primitive = super.getUnboxedType(type); - primitive.replaceAnnotation(INTERNED); - return primitive; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java index 063200c6a5f..428fed8e423 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningChecker.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.interning; -import javax.annotation.processing.SupportedOptions; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedLintOptions; +import javax.annotation.processing.SupportedOptions; + /** * A type-checker plug-in for the {@link Interned} qualifier that finds (and verifies the absence * of) equality-testing and interning errors. diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java index a28327326cf..eff70f42a86 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java @@ -17,19 +17,7 @@ import com.sun.source.tree.StatementTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import java.util.Comparator; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; -import javax.tools.Diagnostic; + import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.interning.qual.CompareToMethod; import org.checkerframework.checker.interning.qual.EqualsMethod; @@ -51,6 +39,21 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.Comparator; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.tools.Diagnostic; + /** * Typechecks source code for interning violations. A type is considered interned if its primary * annotation is {@link Interned} or {@link InternedDistinct}. This visitor reports errors or @@ -68,901 +71,923 @@ */ public final class InterningVisitor extends BaseTypeVisitor { - /** The @Interned annotation. */ - private final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); - - /** The @InternedDistinct annotation. */ - private final AnnotationMirror INTERNED_DISTINCT = - AnnotationBuilder.fromClass(elements, InternedDistinct.class); - - /** - * The declared type of which the equality tests should be tested, if the user explicitly passed - * one. The user can pass the class name via the {@code -Acheckclass=...} option. Null if no class - * is specified, or the class specified isn't in the classpath. - */ - private final @Nullable DeclaredType typeToCheck = typeToCheck(); - - /** The Comparable.compareTo method. */ - private final ExecutableElement comparableCompareTo = - TreeUtils.getMethod( - "java.lang.Comparable", "compareTo", 1, checker.getProcessingEnvironment()); - - /** Create an InterningVisitor. */ - public InterningVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * Returns true if interning should be verified for the input expression. By default, all classes - * are checked for interning unless {@code -Acheckclass} is specified. - * - * @return true if interning should be verified for the input expression - * @see What the Interning Checker - * checks - */ - private boolean shouldCheckExpression(ExpressionTree tree) { - if (typeToCheck == null) { - return true; + /** The @Interned annotation. */ + private final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); + + /** The @InternedDistinct annotation. */ + private final AnnotationMirror INTERNED_DISTINCT = + AnnotationBuilder.fromClass(elements, InternedDistinct.class); + + /** + * The declared type of which the equality tests should be tested, if the user explicitly passed + * one. The user can pass the class name via the {@code -Acheckclass=...} option. Null if no + * class is specified, or the class specified isn't in the classpath. + */ + private final @Nullable DeclaredType typeToCheck = typeToCheck(); + + /** The Comparable.compareTo method. */ + private final ExecutableElement comparableCompareTo = + TreeUtils.getMethod( + "java.lang.Comparable", "compareTo", 1, checker.getProcessingEnvironment()); + + /** Create an InterningVisitor. */ + public InterningVisitor(BaseTypeChecker checker) { + super(checker); + } + + /** + * Returns true if interning should be verified for the input expression. By default, all + * classes are checked for interning unless {@code -Acheckclass} is specified. + * + * @return true if interning should be verified for the input expression + * @see What the Interning + * Checker checks + */ + private boolean shouldCheckExpression(ExpressionTree tree) { + if (typeToCheck == null) { + return true; + } + + TypeMirror type = TreeUtils.typeOf(tree); + return types.isSubtype(type, typeToCheck) || types.isSubtype(typeToCheck, type); } - TypeMirror type = TreeUtils.typeOf(tree); - return types.isSubtype(type, typeToCheck) || types.isSubtype(typeToCheck, type); - } + /** Checks comparison operators, == and !=, for INTERNING violations. */ + @Override + public Void visitBinary(BinaryTree tree, Void p) { - /** Checks comparison operators, == and !=, for INTERNING violations. */ - @Override - public Void visitBinary(BinaryTree tree, Void p) { + // No checking unless the operator is "==" or "!=". + if (!(tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { + return super.visitBinary(tree, p); + } - // No checking unless the operator is "==" or "!=". - if (!(tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { - return super.visitBinary(tree, p); - } + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); + // Check passes if either arg is null. + if (leftOp.getKind() == Tree.Kind.NULL_LITERAL + || rightOp.getKind() == Tree.Kind.NULL_LITERAL) { + return super.visitBinary(tree, p); + } - // Check passes if either arg is null. - if (leftOp.getKind() == Tree.Kind.NULL_LITERAL || rightOp.getKind() == Tree.Kind.NULL_LITERAL) { - return super.visitBinary(tree, p); - } + AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); + AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); - AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); - AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); + // If either argument is a primitive, check passes due to auto-unboxing + if (left.getKind().isPrimitive() || right.getKind().isPrimitive()) { + return super.visitBinary(tree, p); + } - // If either argument is a primitive, check passes due to auto-unboxing - if (left.getKind().isPrimitive() || right.getKind().isPrimitive()) { - return super.visitBinary(tree, p); - } + if (left.hasEffectiveAnnotation(INTERNED_DISTINCT) + || right.hasEffectiveAnnotation(INTERNED_DISTINCT)) { + return super.visitBinary(tree, p); + } - if (left.hasEffectiveAnnotation(INTERNED_DISTINCT) - || right.hasEffectiveAnnotation(INTERNED_DISTINCT)) { - return super.visitBinary(tree, p); - } + // If shouldCheckExpression returns true for either the LHS or RHS, + // this method proceeds with the interning check. + + // Justification: Consider the following scenario: + + // interface I { ... } + // class A { ... } + // class B extends A implements I { ... } + // ... + // I i; + // A a; + // ... + // if (a == i) { ... } + + // The Java compiler does not issue a compilation error for the (a == i) comparison because, + // even though A does not implement I, 'a' could be assigned an instance of B, and B does + // implement I (note that the compiler does not need to know about the existence of B + // in order to assume this). + + // Now suppose the user passes -AcheckClass=A on the command-line. + // I is not a subtype or supertype of A, so shouldCheckExpression will not return true for + // I. + // But the interning check must be performed, given the argument above. Therefore if + // shouldCheckExpression returns true for either the LHS or the RHS, this method proceeds + // with the interning check. + + if (!shouldCheckExpression(leftOp) && !shouldCheckExpression(rightOp)) { + return super.visitBinary(tree, p); + } - // If shouldCheckExpression returns true for either the LHS or RHS, - // this method proceeds with the interning check. - - // Justification: Consider the following scenario: - - // interface I { ... } - // class A { ... } - // class B extends A implements I { ... } - // ... - // I i; - // A a; - // ... - // if (a == i) { ... } - - // The Java compiler does not issue a compilation error for the (a == i) comparison because, - // even though A does not implement I, 'a' could be assigned an instance of B, and B does - // implement I (note that the compiler does not need to know about the existence of B - // in order to assume this). - - // Now suppose the user passes -AcheckClass=A on the command-line. - // I is not a subtype or supertype of A, so shouldCheckExpression will not return true for - // I. - // But the interning check must be performed, given the argument above. Therefore if - // shouldCheckExpression returns true for either the LHS or the RHS, this method proceeds - // with the interning check. - - if (!shouldCheckExpression(leftOp) && !shouldCheckExpression(rightOp)) { - return super.visitBinary(tree, p); - } + // Syntactic checks for legal uses of == + if (suppressInsideComparison(tree)) { + return super.visitBinary(tree, p); + } + if (suppressEarlyEquals(tree)) { + return super.visitBinary(tree, p); + } + if (suppressEarlyCompareTo(tree)) { + return super.visitBinary(tree, p); + } - // Syntactic checks for legal uses of == - if (suppressInsideComparison(tree)) { - return super.visitBinary(tree, p); - } - if (suppressEarlyEquals(tree)) { - return super.visitBinary(tree, p); - } - if (suppressEarlyCompareTo(tree)) { - return super.visitBinary(tree, p); - } + if (suppressEqualsIfClassIsAnnotated(left, right)) { + return super.visitBinary(tree, p); + } - if (suppressEqualsIfClassIsAnnotated(left, right)) { - return super.visitBinary(tree, p); - } + Element leftElt = TypesUtils.getTypeElement(left.getUnderlyingType()); + // If neither @Interned or @UsesObjectEquals, report error. + if (!(left.hasEffectiveAnnotation(INTERNED) + || (leftElt != null + && atypeFactory.getDeclAnnotation(leftElt, UsesObjectEquals.class) + != null))) { + checker.reportError(leftOp, "not.interned"); + } - Element leftElt = TypesUtils.getTypeElement(left.getUnderlyingType()); - // If neither @Interned or @UsesObjectEquals, report error. - if (!(left.hasEffectiveAnnotation(INTERNED) - || (leftElt != null - && atypeFactory.getDeclAnnotation(leftElt, UsesObjectEquals.class) != null))) { - checker.reportError(leftOp, "not.interned"); - } + Element rightElt = TypesUtils.getTypeElement(right.getUnderlyingType()); + if (!(right.hasEffectiveAnnotation(INTERNED) + || (rightElt != null + && atypeFactory.getDeclAnnotation(rightElt, UsesObjectEquals.class) + != null))) { + checker.reportError(rightOp, "not.interned"); + } + return super.visitBinary(tree, p); + } + + /** + * If lint option "dotequals" is specified, warn if the .equals method is used where reference + * equality is safe. + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (isInvocationOfEquals(tree)) { + AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); + assert receiverType != null : "@AssumeAssertion(nullness)"; + AnnotatedTypeMirror comp = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + + if (this.checker.getLintOption("dotequals", true) + && receiverType.hasEffectiveAnnotation(INTERNED) + && comp.hasEffectiveAnnotation(INTERNED)) { + checker.reportWarning(tree, "unnecessary.equals"); + } + } - Element rightElt = TypesUtils.getTypeElement(right.getUnderlyingType()); - if (!(right.hasEffectiveAnnotation(INTERNED) - || (rightElt != null - && atypeFactory.getDeclAnnotation(rightElt, UsesObjectEquals.class) != null))) { - checker.reportError(rightOp, "not.interned"); - } - return super.visitBinary(tree, p); - } - - /** - * If lint option "dotequals" is specified, warn if the .equals method is used where reference - * equality is safe. - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (isInvocationOfEquals(tree)) { - AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); - assert receiverType != null : "@AssumeAssertion(nullness)"; - AnnotatedTypeMirror comp = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - - if (this.checker.getLintOption("dotequals", true) - && receiverType.hasEffectiveAnnotation(INTERNED) - && comp.hasEffectiveAnnotation(INTERNED)) { - checker.reportWarning(tree, "unnecessary.equals"); - } - } + return super.visitMethodInvocation(tree, p); + } + + // Ensure that method annotations are not written on methods they don't apply to. + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); + boolean hasCompareToMethodAnno = + atypeFactory.getDeclAnnotation(methElt, CompareToMethod.class) != null; + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; + boolean hasInternMethodAnno = + atypeFactory.getDeclAnnotation(methElt, InternMethod.class) != null; + int params = methElt.getParameters().size(); + if (hasCompareToMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + tree, + "invalid.method.annotation", + "@CompareToMethod", + "1 or 2", + methElt, + params); + } else if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); + } else if (hasInternMethodAnno && !(params == 0)) { + checker.reportError( + tree, "invalid.method.annotation", "@InternMethod", "0", methElt, params); + } - return super.visitMethodInvocation(tree, p); - } - - // Ensure that method annotations are not written on methods they don't apply to. - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); - boolean hasCompareToMethodAnno = - atypeFactory.getDeclAnnotation(methElt, CompareToMethod.class) != null; - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; - boolean hasInternMethodAnno = - atypeFactory.getDeclAnnotation(methElt, InternMethod.class) != null; - int params = methElt.getParameters().size(); - if (hasCompareToMethodAnno && !(params == 1 || params == 2)) { - checker.reportError( - tree, "invalid.method.annotation", "@CompareToMethod", "1 or 2", methElt, params); - } else if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { - checker.reportError( - tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); - } else if (hasInternMethodAnno && !(params == 0)) { - checker.reportError(tree, "invalid.method.annotation", "@InternMethod", "0", methElt, params); + return super.visitMethod(tree, p); + } + + /** + * Method to implement the @UsesObjectEquals functionality. If a class is annotated + * with @UsesObjectEquals, it must: + * + *

            + *
          • not override .equals(Object) and be a subclass of a class annotated + * with @UsesObjectEquals, or + *
          • override equals(Object) with body "this == arg" + *
          + * + * If a class is not annotated with @UsesObjectEquals, it must: + * + *
            + *
          • not have a superclass annotated with @UsesObjectEquals + *
          + * + * @see + * org.checkerframework.common.basetype.BaseTypeVisitor#visitClass(com.sun.source.tree.ClassTree, + * java.lang.Object) + */ + @Override + public void processClassTree(ClassTree classTree) { + TypeElement elt = TreeUtils.elementFromDeclaration(classTree); + AnnotationMirror annotation = atypeFactory.getDeclAnnotation(elt, UsesObjectEquals.class); + + // If @UsesObjectEquals is present, check to make sure the class does not override equals + // and its supertype is Object or is annotated with @UsesObjectEquals. + if (annotation != null) { + MethodTree equalsMethod = equalsImplementation(classTree); + if (equalsMethod != null) { + if (!isReferenceEqualityImplementation(equalsMethod)) { + checker.reportError(classTree, "overrides.equals"); + } + } else { + // Does not override equals() + TypeMirror superClass = elt.getSuperclass(); + if (superClass != null + // The super class of an interface is "none" rather than null. + && superClass.getKind() == TypeKind.DECLARED) { + TypeElement superClassElement = TypesUtils.getTypeElement(superClass); + if (superClassElement != null + && !ElementUtils.isObject(superClassElement) + && atypeFactory.getDeclAnnotation( + superClassElement, UsesObjectEquals.class) + == null) { + checker.reportError(classTree, "superclass.notannotated"); + } + } + } + } + + super.processClassTree(classTree); + } + + /** + * Returns true if the given equals() method implements reference equality. + * + * @param equalsMethod an overriding implementation of Object.equals() + * @return true if the given equals() method implements reference equality + */ + private boolean isReferenceEqualityImplementation(MethodTree equalsMethod) { + BlockTree body = equalsMethod.getBody(); + List bodyStatements = body.getStatements(); + if (bodyStatements.size() == 1) { + StatementTree bodyStatement = bodyStatements.get(0); + if (bodyStatement.getKind() == Tree.Kind.RETURN) { + ExpressionTree returnExpr = + TreeUtils.withoutParens(((ReturnTree) bodyStatement).getExpression()); + if (returnExpr.getKind() == Tree.Kind.EQUAL_TO) { + BinaryTree bt = (BinaryTree) returnExpr; + ExpressionTree lhsTree = bt.getLeftOperand(); + ExpressionTree rhsTree = bt.getRightOperand(); + if (lhsTree.getKind() == Tree.Kind.IDENTIFIER + && rhsTree.getKind() == Tree.Kind.IDENTIFIER) { + Name leftName = ((IdentifierTree) lhsTree).getName(); + Name rightName = ((IdentifierTree) rhsTree).getName(); + Name paramName = equalsMethod.getParameters().get(0).getName(); + if ((leftName.contentEquals("this") && rightName == paramName) + || (leftName == paramName && rightName.contentEquals("this"))) { + return true; + } + } + } + } + } + return false; } - return super.visitMethod(tree, p); - } - - /** - * Method to implement the @UsesObjectEquals functionality. If a class is annotated - * with @UsesObjectEquals, it must: - * - *
            - *
          • not override .equals(Object) and be a subclass of a class annotated - * with @UsesObjectEquals, or - *
          • override equals(Object) with body "this == arg" - *
          - * - * If a class is not annotated with @UsesObjectEquals, it must: - * - *
            - *
          • not have a superclass annotated with @UsesObjectEquals - *
          - * - * @see - * org.checkerframework.common.basetype.BaseTypeVisitor#visitClass(com.sun.source.tree.ClassTree, - * java.lang.Object) - */ - @Override - public void processClassTree(ClassTree classTree) { - TypeElement elt = TreeUtils.elementFromDeclaration(classTree); - AnnotationMirror annotation = atypeFactory.getDeclAnnotation(elt, UsesObjectEquals.class); - - // If @UsesObjectEquals is present, check to make sure the class does not override equals - // and its supertype is Object or is annotated with @UsesObjectEquals. - if (annotation != null) { - MethodTree equalsMethod = equalsImplementation(classTree); - if (equalsMethod != null) { - if (!isReferenceEqualityImplementation(equalsMethod)) { - checker.reportError(classTree, "overrides.equals"); - } - } else { - // Does not override equals() - TypeMirror superClass = elt.getSuperclass(); - if (superClass != null - // The super class of an interface is "none" rather than null. - && superClass.getKind() == TypeKind.DECLARED) { - TypeElement superClassElement = TypesUtils.getTypeElement(superClass); - if (superClassElement != null - && !ElementUtils.isObject(superClassElement) - && atypeFactory.getDeclAnnotation(superClassElement, UsesObjectEquals.class) - == null) { - checker.reportError(classTree, "superclass.notannotated"); - } - } - } + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + if (constructorElement.getEnclosingElement().getKind() == ElementKind.ENUM) { + // Enums constructor are only called once per enum constant. + return; + } + super.checkConstructorResult(constructorType, constructorElement); } - super.processClassTree(classTree); - } - - /** - * Returns true if the given equals() method implements reference equality. - * - * @param equalsMethod an overriding implementation of Object.equals() - * @return true if the given equals() method implements reference equality - */ - private boolean isReferenceEqualityImplementation(MethodTree equalsMethod) { - BlockTree body = equalsMethod.getBody(); - List bodyStatements = body.getStatements(); - if (bodyStatements.size() == 1) { - StatementTree bodyStatement = bodyStatements.get(0); - if (bodyStatement.getKind() == Tree.Kind.RETURN) { - ExpressionTree returnExpr = - TreeUtils.withoutParens(((ReturnTree) bodyStatement).getExpression()); - if (returnExpr.getKind() == Tree.Kind.EQUAL_TO) { - BinaryTree bt = (BinaryTree) returnExpr; - ExpressionTree lhsTree = bt.getLeftOperand(); - ExpressionTree rhsTree = bt.getRightOperand(); - if (lhsTree.getKind() == Tree.Kind.IDENTIFIER - && rhsTree.getKind() == Tree.Kind.IDENTIFIER) { - Name leftName = ((IdentifierTree) lhsTree).getName(); - Name rightName = ((IdentifierTree) rhsTree).getName(); - Name paramName = equalsMethod.getParameters().get(0).getName(); - if ((leftName.contentEquals("this") && rightName == paramName) - || (leftName == paramName && rightName.contentEquals("this"))) { - return true; + @Override + public boolean validateTypeOf(Tree tree) { + // Don't check the result type of a constructor, because it must be @UnknownInterned, even + // if the type on the class declaration is @Interned. + if (tree.getKind() == Tree.Kind.METHOD && TreeUtils.isConstructor((MethodTree) tree)) { + return true; + } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { + NewClassTree newClassTree = (NewClassTree) tree; + TypeMirror typeMirror = TreeUtils.typeOf(newClassTree); + AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(typeMirror); + // Don't issue an invalid type warning for creations of objects of interned classes; + // instead, issue an interned.object.creation if required. + if (atypeFactory.containsSameByClass(bounds, Interned.class)) { + ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(newClassTree); + AnnotatedExecutableType constructor = fromUse.executableType; + if (!checkCreationOfInternedObject(newClassTree, constructor)) { + return false; + } } - } } - } - } - return false; - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - if (constructorElement.getEnclosingElement().getKind() == ElementKind.ENUM) { - // Enums constructor are only called once per enum constant. - return; - } - super.checkConstructorResult(constructorType, constructorElement); - } - - @Override - public boolean validateTypeOf(Tree tree) { - // Don't check the result type of a constructor, because it must be @UnknownInterned, even - // if the type on the class declaration is @Interned. - if (tree.getKind() == Tree.Kind.METHOD && TreeUtils.isConstructor((MethodTree) tree)) { - return true; - } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { - NewClassTree newClassTree = (NewClassTree) tree; - TypeMirror typeMirror = TreeUtils.typeOf(newClassTree); - AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(typeMirror); - // Don't issue an invalid type warning for creations of objects of interned classes; - // instead, issue an interned.object.creation if required. - if (atypeFactory.containsSameByClass(bounds, Interned.class)) { - ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(newClassTree); - AnnotatedExecutableType constructor = fromUse.executableType; - if (!checkCreationOfInternedObject(newClassTree, constructor)) { - return false; - } - } - } - return super.validateTypeOf(tree); - } - - /** - * Issue an error if {@code newInternedObject} is not immediately interned. - * - * @param newInternedObject call to a constructor of an interned class - * @param constructor declared type of the constructor - * @return false unless {@code newInternedObject} is immediately interned - */ - private boolean checkCreationOfInternedObject( - NewClassTree newInternedObject, AnnotatedExecutableType constructor) { - if (constructor.getReturnType().hasAnnotation(Interned.class)) { - return true; - } - TreePath path = getCurrentPath(); - if (path != null) { - TreePath parentPath = path.getParentPath(); - while (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.PARENTHESIZED) { - parentPath = parentPath.getParentPath(); - } - if (parentPath != null && parentPath.getParentPath() != null) { - Tree parent = parentPath.getParentPath().getLeaf(); - if (parent.getKind() == Tree.Kind.METHOD_INVOCATION) { - // Allow new MyInternType().intern(), where "intern" is any method marked - // @InternMethod. - ExecutableElement elt = TreeUtils.elementFromUse((MethodInvocationTree) parent); - if (atypeFactory.getDeclAnnotation(elt, InternMethod.class) != null) { + return super.validateTypeOf(tree); + } + + /** + * Issue an error if {@code newInternedObject} is not immediately interned. + * + * @param newInternedObject call to a constructor of an interned class + * @param constructor declared type of the constructor + * @return false unless {@code newInternedObject} is immediately interned + */ + private boolean checkCreationOfInternedObject( + NewClassTree newInternedObject, AnnotatedExecutableType constructor) { + if (constructor.getReturnType().hasAnnotation(Interned.class)) { return true; - } } - } - } - - checker.reportError(newInternedObject, "interned.object.creation"); - return false; - } - - // ********************************************************************** - // Helper methods - // ********************************************************************** - - /** - * Returns the method that overrides Object.equals, or null. - * - * @param tree a class - * @return the class's implementation of equals, or null - */ - private @Nullable MethodTree equalsImplementation(ClassTree tree) { - List members = tree.getMembers(); - for (Tree member : members) { - if (member instanceof MethodTree) { - MethodTree mTree = (MethodTree) member; - ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree); - if (overrides(enclosing, Object.class, "equals")) { - return mTree; - } - } - } - return null; - } - - /** - * Tests whether a method invocation is an invocation of {@link #equals} with one argument. - * - *

          Returns true even if a method overloads {@link Object#equals(Object)}, because of the common - * idiom of writing an equals method with a non-Object parameter, in addition to the equals method - * that overrides {@link Object#equals(Object)}. - * - * @param tree a method invocation tree - * @return true iff {@code tree} is a invocation of {@code equals()} - */ - public static boolean isInvocationOfEquals(MethodInvocationTree tree) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - return (method.getParameters().size() == 1 - && method.getReturnType().getKind() == TypeKind.BOOLEAN - // method symbols only have simple names - && method.getSimpleName().contentEquals("equals")); - } - - /** - * Pattern matches particular comparisons to avoid common false positives in the {@link - * Comparable#compareTo(Object)} and {@link Object#equals(Object)}. - * - *

          Specifically, this method tests if: the comparison is a == comparison, it is the test of an - * if statement that's the first statement in the method, and one of the following is true: - * - *

            - *
          1. the method overrides {@link Comparator#compare}, the "then" branch of the if statement - * returns zero, and the comparison tests equality of the method's two parameters - *
          2. the method overrides {@link Object#equals(Object)} and the comparison tests "this" - * against the method's parameter - *
          3. the method overrides {@link Comparable#compareTo(Object)}, the "then" branch of the if - * statement returns zero, and the comparison tests "this" against the method's parameter - *
          - * - * @param binaryTree the comparison to check - * @return true if one of the supported heuristics is matched, false otherwise - */ - // TODO: handle != comparisons too! - // TODO: handle more methods, such as early return from addAll when this == arg - private boolean suppressInsideComparison(BinaryTree binaryTree) { - // Only handle == binary trees - if (binaryTree.getKind() != Tree.Kind.EQUAL_TO) { - return false; - } - - ExpressionTree left = binaryTree.getLeftOperand(); - ExpressionTree right = binaryTree.getRightOperand(); - - // Only valid if we're comparing identifiers. - if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { - return false; - } - - TreePath path = getCurrentPath(); - TreePath parentPath = path.getParentPath(); - Tree parent = parentPath.getLeaf(); + TreePath path = getCurrentPath(); + if (path != null) { + TreePath parentPath = path.getParentPath(); + while (parentPath != null + && parentPath.getLeaf().getKind() == Tree.Kind.PARENTHESIZED) { + parentPath = parentPath.getParentPath(); + } + if (parentPath != null && parentPath.getParentPath() != null) { + Tree parent = parentPath.getParentPath().getLeaf(); + if (parent.getKind() == Tree.Kind.METHOD_INVOCATION) { + // Allow new MyInternType().intern(), where "intern" is any method marked + // @InternMethod. + ExecutableElement elt = TreeUtils.elementFromUse((MethodInvocationTree) parent); + if (atypeFactory.getDeclAnnotation(elt, InternMethod.class) != null) { + return true; + } + } + } + } - // Ensure the == is in a return or in an if, and that enclosing statement is the first - // statement in the method. - if (parent.getKind() == Tree.Kind.RETURN) { - // ensure the return statement is the first statement in the method - if (parentPath.getParentPath().getParentPath().getLeaf().getKind() != Tree.Kind.METHOD) { + checker.reportError(newInternedObject, "interned.object.creation"); return false; - } - - // maybe set some variables?? - } else if (Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) { - // Ensure the if statement is the first statement in the method - - // Retrieve the enclosing if statement tree and method tree - Tree ifStatementTree = null; - MethodTree methodTree = null; - // Set ifStatementTree and methodTree - { - TreePath ppath = parentPath; - Tree candidateTree; - while ((candidateTree = ppath.getLeaf()) != null) { - if (candidateTree.getKind() == Tree.Kind.IF) { - ifStatementTree = candidateTree; - } else if (candidateTree.getKind() == Tree.Kind.METHOD) { - methodTree = (MethodTree) candidateTree; - break; - } - ppath = ppath.getParentPath(); - } - } - assert ifStatementTree != null; - assert methodTree != null; - StatementTree firstStmnt = methodTree.getBody().getStatements().get(0); - assert firstStmnt != null; - @SuppressWarnings("interning:not.interned") // comparing AST nodes - boolean notSameNode = firstStmnt != ifStatementTree; - if (notSameNode) { - return false; // The if statement is not the first statement in the method. - } - } else { - return false; } - ExecutableElement enclosingMethod = TreeUtils.elementFromDeclaration(methodTree); - - Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); - Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); - - // Matcher to check for if statement that returns zero - Heuristics.Matcher matcherIfReturnsZero = - new Heuristics.Matcher() { - - @Override - public Boolean visitIf(IfTree tree, Void p) { - return visit(tree.getThenStatement(), p); - } - - @Override - public Boolean visitBlock(BlockTree tree, Void p) { - if (tree.getStatements().isEmpty()) { - return false; + // ********************************************************************** + // Helper methods + // ********************************************************************** + + /** + * Returns the method that overrides Object.equals, or null. + * + * @param tree a class + * @return the class's implementation of equals, or null + */ + private @Nullable MethodTree equalsImplementation(ClassTree tree) { + List members = tree.getMembers(); + for (Tree member : members) { + if (member instanceof MethodTree) { + MethodTree mTree = (MethodTree) member; + ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree); + if (overrides(enclosing, Object.class, "equals")) { + return mTree; + } } - return visit(tree.getStatements().get(0), p); - } - - @Override - public Boolean visitReturn(ReturnTree tree, Void p) { - ExpressionTree expr = tree.getExpression(); - return (expr != null - && expr.getKind() == Tree.Kind.INT_LITERAL - && ((LiteralTree) expr).getValue().equals(0)); - } - }; - - boolean hasCompareToMethodAnno = - atypeFactory.getDeclAnnotation(enclosingMethod, CompareToMethod.class) != null; - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(enclosingMethod, EqualsMethod.class) != null; - int params = enclosingMethod.getParameters().size(); - - // Determine whether or not the "then" statement of the if has a single - // "return 0" statement (for the Comparator.compare heuristic). - if (overrides(enclosingMethod, Comparator.class, "compare") - || (hasCompareToMethodAnno && params == 2)) { - boolean returnsZero = - new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) - .match(getCurrentPath()); - - if (!returnsZero) { - return false; - } - - assert params == 2; - Element p1 = enclosingMethod.getParameters().get(0); - Element p2 = enclosingMethod.getParameters().get(1); - return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); - - } else if (overrides(enclosingMethod, Object.class, "equals") - || (hasEqualsMethodAnno && params == 1)) { - assert params == 1; - Element param = enclosingMethod.getParameters().get(0); - Element thisElt = getThis(trees.getScope(getCurrentPath())); - assert thisElt != null; - return (thisElt.equals(lhs) && param.equals(rhs)) - || (thisElt.equals(rhs) && param.equals(lhs)); - - } else if (hasEqualsMethodAnno && params == 2) { - Element p1 = enclosingMethod.getParameters().get(0); - Element p2 = enclosingMethod.getParameters().get(1); - return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); - - } else if (overrides(enclosingMethod, Comparable.class, "compareTo") - || (hasCompareToMethodAnno && params == 1)) { - - boolean returnsZero = - new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) - .match(getCurrentPath()); - - if (!returnsZero) { - return false; - } - - assert params == 1; - Element param = enclosingMethod.getParameters().get(0); - Element thisElt = getThis(trees.getScope(getCurrentPath())); - assert thisElt != null; - return (thisElt.equals(lhs) && param.equals(rhs)) - || (thisElt.equals(rhs) && param.equals(lhs)); - } + } + return null; + } + + /** + * Tests whether a method invocation is an invocation of {@link #equals} with one argument. + * + *

          Returns true even if a method overloads {@link Object#equals(Object)}, because of the + * common idiom of writing an equals method with a non-Object parameter, in addition to the + * equals method that overrides {@link Object#equals(Object)}. + * + * @param tree a method invocation tree + * @return true iff {@code tree} is a invocation of {@code equals()} + */ + public static boolean isInvocationOfEquals(MethodInvocationTree tree) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + return (method.getParameters().size() == 1 + && method.getReturnType().getKind() == TypeKind.BOOLEAN + // method symbols only have simple names + && method.getSimpleName().contentEquals("equals")); + } + + /** + * Pattern matches particular comparisons to avoid common false positives in the {@link + * Comparable#compareTo(Object)} and {@link Object#equals(Object)}. + * + *

          Specifically, this method tests if: the comparison is a == comparison, it is the test of + * an if statement that's the first statement in the method, and one of the following is true: + * + *

            + *
          1. the method overrides {@link Comparator#compare}, the "then" branch of the if statement + * returns zero, and the comparison tests equality of the method's two parameters + *
          2. the method overrides {@link Object#equals(Object)} and the comparison tests "this" + * against the method's parameter + *
          3. the method overrides {@link Comparable#compareTo(Object)}, the "then" branch of the if + * statement returns zero, and the comparison tests "this" against the method's parameter + *
          + * + * @param binaryTree the comparison to check + * @return true if one of the supported heuristics is matched, false otherwise + */ + // TODO: handle != comparisons too! + // TODO: handle more methods, such as early return from addAll when this == arg + private boolean suppressInsideComparison(BinaryTree binaryTree) { + // Only handle == binary trees + if (binaryTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } - return false; - } - - /** - * Pattern matches to prevent false positives of the forms: - * - *
          {@code
          -   * (a == b) || a.equals(b)
          -   * (a == b) || (a != null ? a.equals(b) : false)
          -   * (a == b) || (a != null && a.equals(b))
          -   * }
          - * - * Returns true iff the given tree fits this pattern. - * - * @param topBinaryTree the binary operation to check - * @return true iff the tree fits a pattern such as (a == b || a.equals(b)) - */ - private boolean suppressEarlyEquals(BinaryTree topBinaryTree) { - // Only handle == binary trees - if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { - return false; - } + ExpressionTree left = binaryTree.getLeftOperand(); + ExpressionTree right = binaryTree.getRightOperand(); - // should strip parens - ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); - ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); + // Only valid if we're comparing identifiers. + if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { + return false; + } - // looking for ((a == b || a.equals(b)) - Heuristics.Matcher matcherEqOrEquals = - new Heuristics.Matcher() { + TreePath path = getCurrentPath(); + TreePath parentPath = path.getParentPath(); + Tree parent = parentPath.getLeaf(); - /** Returns true if e is either "e1 != null" or "e2 != null". */ - private boolean isNeqNull(ExpressionTree e, ExpressionTree e1, ExpressionTree e2) { - e = TreeUtils.withoutParens(e); - if (e.getKind() != Tree.Kind.NOT_EQUAL_TO) { - return false; - } - ExpressionTree neqLeft = ((BinaryTree) e).getLeftOperand(); - ExpressionTree neqRight = ((BinaryTree) e).getRightOperand(); - return (((TreeUtils.sameTree(neqLeft, e1) || TreeUtils.sameTree(neqLeft, e2)) - && neqRight.getKind() == Tree.Kind.NULL_LITERAL) - // also check for "null != e1" and "null != e2" - || ((TreeUtils.sameTree(neqRight, e1) || TreeUtils.sameTree(neqRight, e2)) - && neqLeft.getKind() == Tree.Kind.NULL_LITERAL)); - } - - @Override - public Boolean visitBinary(BinaryTree tree, Void p) { - ExpressionTree leftTree = tree.getLeftOperand(); - ExpressionTree rightTree = tree.getRightOperand(); - - if (tree.getKind() == Tree.Kind.CONDITIONAL_OR) { - if (TreeUtils.sameTree(leftTree, topBinaryTree)) { - // left is "a==b" - // check right, which should be a.equals(b) or b.equals(a) or - // similar - return visit(rightTree, p); - } else { + // Ensure the == is in a return or in an if, and that enclosing statement is the first + // statement in the method. + if (parent.getKind() == Tree.Kind.RETURN) { + // ensure the return statement is the first statement in the method + if (parentPath.getParentPath().getParentPath().getLeaf().getKind() + != Tree.Kind.METHOD) { return false; - } } - if (tree.getKind() == Tree.Kind.CONDITIONAL_AND) { - // looking for: (a != null && a.equals(b))) - if (isNeqNull(leftTree, left, right)) { - return visit(rightTree, p); - } - return false; + // maybe set some variables?? + } else if (Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) { + // Ensure the if statement is the first statement in the method + + // Retrieve the enclosing if statement tree and method tree + Tree ifStatementTree = null; + MethodTree methodTree = null; + // Set ifStatementTree and methodTree + { + TreePath ppath = parentPath; + Tree candidateTree; + while ((candidateTree = ppath.getLeaf()) != null) { + if (candidateTree.getKind() == Tree.Kind.IF) { + ifStatementTree = candidateTree; + } else if (candidateTree.getKind() == Tree.Kind.METHOD) { + methodTree = (MethodTree) candidateTree; + break; + } + ppath = ppath.getParentPath(); + } } - - return false; - } - - @Override - public Boolean visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - // looking for: (a != null ? a.equals(b) : false) - ExpressionTree cond = tree.getCondition(); - ExpressionTree trueExp = tree.getTrueExpression(); - ExpressionTree falseExp = tree.getFalseExpression(); - if (isNeqNull(cond, left, right) - && (falseExp.getKind() == Tree.Kind.BOOLEAN_LITERAL) - && ((LiteralTree) falseExp).getValue().equals(false)) { - return visit(trueExp, p); + assert ifStatementTree != null; + assert methodTree != null; + StatementTree firstStmnt = methodTree.getBody().getStatements().get(0); + assert firstStmnt != null; + @SuppressWarnings("interning:not.interned") // comparing AST nodes + boolean notSameNode = firstStmnt != ifStatementTree; + if (notSameNode) { + return false; // The if statement is not the first statement in the method. } + } else { return false; - } - - @Override - public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (!isInvocationOfEquals(tree)) { - return false; - } + } - List args = tree.getArguments(); - if (args.size() != 1) { - return false; - } - ExpressionTree arg = args.get(0); - // if (arg.getKind() != Tree.Kind.IDENTIFIER) { - // return false; - // } - // Element argElt = TreeUtils.elementFromUse((IdentifierTree) arg); - - ExpressionTree exp = tree.getMethodSelect(); - if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; + ExecutableElement enclosingMethod = TreeUtils.elementFromDeclaration(methodTree); + + Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); + Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); + + // Matcher to check for if statement that returns zero + Heuristics.Matcher matcherIfReturnsZero = + new Heuristics.Matcher() { + + @Override + public Boolean visitIf(IfTree tree, Void p) { + return visit(tree.getThenStatement(), p); + } + + @Override + public Boolean visitBlock(BlockTree tree, Void p) { + if (tree.getStatements().isEmpty()) { + return false; + } + return visit(tree.getStatements().get(0), p); + } + + @Override + public Boolean visitReturn(ReturnTree tree, Void p) { + ExpressionTree expr = tree.getExpression(); + return (expr != null + && expr.getKind() == Tree.Kind.INT_LITERAL + && ((LiteralTree) expr).getValue().equals(0)); + } + }; + + boolean hasCompareToMethodAnno = + atypeFactory.getDeclAnnotation(enclosingMethod, CompareToMethod.class) != null; + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(enclosingMethod, EqualsMethod.class) != null; + int params = enclosingMethod.getParameters().size(); + + // Determine whether or not the "then" statement of the if has a single + // "return 0" statement (for the Comparator.compare heuristic). + if (overrides(enclosingMethod, Comparator.class, "compare") + || (hasCompareToMethodAnno && params == 2)) { + boolean returnsZero = + new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) + .match(getCurrentPath()); + + if (!returnsZero) { + return false; } - MemberSelectTree member = (MemberSelectTree) exp; - ExpressionTree receiver = member.getExpression(); - // Element refElt = TreeUtils.elementFromUse(receiver); - - // if (!((refElt.equals(lhs) && argElt.equals(rhs)) || - // ((refElt.equals(rhs) && argElt.equals(lhs))))) { - // return false; - // } - if (TreeUtils.sameTree(receiver, left) && TreeUtils.sameTree(arg, right)) { - return true; - } - if (TreeUtils.sameTree(receiver, right) && TreeUtils.sameTree(arg, left)) { - return true; + assert params == 2; + Element p1 = enclosingMethod.getParameters().get(0); + Element p2 = enclosingMethod.getParameters().get(1); + return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); + + } else if (overrides(enclosingMethod, Object.class, "equals") + || (hasEqualsMethodAnno && params == 1)) { + assert params == 1; + Element param = enclosingMethod.getParameters().get(0); + Element thisElt = getThis(trees.getScope(getCurrentPath())); + assert thisElt != null; + return (thisElt.equals(lhs) && param.equals(rhs)) + || (thisElt.equals(rhs) && param.equals(lhs)); + + } else if (hasEqualsMethodAnno && params == 2) { + Element p1 = enclosingMethod.getParameters().get(0); + Element p2 = enclosingMethod.getParameters().get(1); + return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); + + } else if (overrides(enclosingMethod, Comparable.class, "compareTo") + || (hasCompareToMethodAnno && params == 1)) { + + boolean returnsZero = + new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) + .match(getCurrentPath()); + + if (!returnsZero) { + return false; } - return false; - } - }; - - boolean okay = - new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrEquals)) - .match(getCurrentPath()); - return okay; - } - - /** - * Pattern matches to prevent false positives of the form {@code (a == b || a.compareTo(b) == 0)}. - * Returns true iff the given tree fits this pattern. - * - * @param topBinaryTree the binary operation to check - * @return true iff the tree fits the pattern (a == b || a.compareTo(b) == 0) - */ - private boolean suppressEarlyCompareTo(BinaryTree topBinaryTree) { - // Only handle == binary trees - if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { - return false; - } - - ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); - ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); + assert params == 1; + Element param = enclosingMethod.getParameters().get(0); + Element thisElt = getThis(trees.getScope(getCurrentPath())); + assert thisElt != null; + return (thisElt.equals(lhs) && param.equals(rhs)) + || (thisElt.equals(rhs) && param.equals(lhs)); + } - // Only valid if we're comparing identifiers. - if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { - return false; + return false; } - Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); - Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); + /** + * Pattern matches to prevent false positives of the forms: + * + *
          {@code
          +     * (a == b) || a.equals(b)
          +     * (a == b) || (a != null ? a.equals(b) : false)
          +     * (a == b) || (a != null && a.equals(b))
          +     * }
          + * + * Returns true iff the given tree fits this pattern. + * + * @param topBinaryTree the binary operation to check + * @return true iff the tree fits a pattern such as (a == b || a.equals(b)) + */ + private boolean suppressEarlyEquals(BinaryTree topBinaryTree) { + // Only handle == binary trees + if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } - // looking for ((a == b || a.compareTo(b) == 0) - Heuristics.Matcher matcherEqOrCompareTo = - new Heuristics.Matcher() { + // should strip parens + ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); + ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); + + // looking for ((a == b || a.equals(b)) + Heuristics.Matcher matcherEqOrEquals = + new Heuristics.Matcher() { + + /** Returns true if e is either "e1 != null" or "e2 != null". */ + private boolean isNeqNull( + ExpressionTree e, ExpressionTree e1, ExpressionTree e2) { + e = TreeUtils.withoutParens(e); + if (e.getKind() != Tree.Kind.NOT_EQUAL_TO) { + return false; + } + ExpressionTree neqLeft = ((BinaryTree) e).getLeftOperand(); + ExpressionTree neqRight = ((BinaryTree) e).getRightOperand(); + return (((TreeUtils.sameTree(neqLeft, e1) + || TreeUtils.sameTree(neqLeft, e2)) + && neqRight.getKind() == Tree.Kind.NULL_LITERAL) + // also check for "null != e1" and "null != e2" + || ((TreeUtils.sameTree(neqRight, e1) + || TreeUtils.sameTree(neqRight, e2)) + && neqLeft.getKind() == Tree.Kind.NULL_LITERAL)); + } + + @Override + public Boolean visitBinary(BinaryTree tree, Void p) { + ExpressionTree leftTree = tree.getLeftOperand(); + ExpressionTree rightTree = tree.getRightOperand(); + + if (tree.getKind() == Tree.Kind.CONDITIONAL_OR) { + if (TreeUtils.sameTree(leftTree, topBinaryTree)) { + // left is "a==b" + // check right, which should be a.equals(b) or b.equals(a) or + // similar + return visit(rightTree, p); + } else { + return false; + } + } + + if (tree.getKind() == Tree.Kind.CONDITIONAL_AND) { + // looking for: (a != null && a.equals(b))) + if (isNeqNull(leftTree, left, right)) { + return visit(rightTree, p); + } + return false; + } + + return false; + } + + @Override + public Boolean visitConditionalExpression( + ConditionalExpressionTree tree, Void p) { + // looking for: (a != null ? a.equals(b) : false) + ExpressionTree cond = tree.getCondition(); + ExpressionTree trueExp = tree.getTrueExpression(); + ExpressionTree falseExp = tree.getFalseExpression(); + if (isNeqNull(cond, left, right) + && (falseExp.getKind() == Tree.Kind.BOOLEAN_LITERAL) + && ((LiteralTree) falseExp).getValue().equals(false)) { + return visit(trueExp, p); + } + return false; + } + + @Override + public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (!isInvocationOfEquals(tree)) { + return false; + } + + List args = tree.getArguments(); + if (args.size() != 1) { + return false; + } + ExpressionTree arg = args.get(0); + // if (arg.getKind() != Tree.Kind.IDENTIFIER) { + // return false; + // } + // Element argElt = TreeUtils.elementFromUse((IdentifierTree) arg); + + ExpressionTree exp = tree.getMethodSelect(); + if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + MemberSelectTree member = (MemberSelectTree) exp; + ExpressionTree receiver = member.getExpression(); + // Element refElt = TreeUtils.elementFromUse(receiver); + + // if (!((refElt.equals(lhs) && argElt.equals(rhs)) || + // ((refElt.equals(rhs) && argElt.equals(lhs))))) { + // return false; + // } + + if (TreeUtils.sameTree(receiver, left) && TreeUtils.sameTree(arg, right)) { + return true; + } + if (TreeUtils.sameTree(receiver, right) && TreeUtils.sameTree(arg, left)) { + return true; + } + + return false; + } + }; + + boolean okay = + new Heuristics.Within( + new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrEquals)) + .match(getCurrentPath()); + return okay; + } + + /** + * Pattern matches to prevent false positives of the form {@code (a == b || a.compareTo(b) == + * 0)}. Returns true iff the given tree fits this pattern. + * + * @param topBinaryTree the binary operation to check + * @return true iff the tree fits the pattern (a == b || a.compareTo(b) == 0) + */ + private boolean suppressEarlyCompareTo(BinaryTree topBinaryTree) { + // Only handle == binary trees + if (topBinaryTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } - @Override - public Boolean visitBinary(BinaryTree tree, Void p) { - if (tree.getKind() == Tree.Kind.EQUAL_TO) { // a.compareTo(b) == 0 - ExpressionTree leftTree = tree.getLeftOperand(); // looking for a.compareTo(b) or - // b.compareTo(a) - ExpressionTree rightTree = tree.getRightOperand(); // looking for 0 + ExpressionTree left = TreeUtils.withoutParens(topBinaryTree.getLeftOperand()); + ExpressionTree right = TreeUtils.withoutParens(topBinaryTree.getRightOperand()); - if (rightTree.getKind() != Tree.Kind.INT_LITERAL) { - return false; - } - LiteralTree rightLiteral = (LiteralTree) rightTree; - if (!rightLiteral.getValue().equals(0)) { - return false; - } + // Only valid if we're comparing identifiers. + if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { + return false; + } - return visit(leftTree, p); - } else { - // a == b || a.compareTo(b) == 0 - @SuppressWarnings("interning:assignment.type.incompatible" // AST node comparisons - ) - @InternedDistinct ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b - ExpressionTree rightTree = tree.getRightOperand(); // looking for a.compareTo(b) == 0 - // or b.compareTo(a) == 0 - if (leftTree != topBinaryTree) { - return false; - } - if (rightTree.getKind() != Tree.Kind.EQUAL_TO) { - return false; - } - return visit(rightTree, p); - } - } + Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); + Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); + + // looking for ((a == b || a.compareTo(b) == 0) + Heuristics.Matcher matcherEqOrCompareTo = + new Heuristics.Matcher() { + + @Override + public Boolean visitBinary(BinaryTree tree, Void p) { + if (tree.getKind() == Tree.Kind.EQUAL_TO) { // a.compareTo(b) == 0 + ExpressionTree leftTree = + tree.getLeftOperand(); // looking for a.compareTo(b) or + // b.compareTo(a) + ExpressionTree rightTree = tree.getRightOperand(); // looking for 0 + + if (rightTree.getKind() != Tree.Kind.INT_LITERAL) { + return false; + } + LiteralTree rightLiteral = (LiteralTree) rightTree; + if (!rightLiteral.getValue().equals(0)) { + return false; + } + + return visit(leftTree, p); + } else { + // a == b || a.compareTo(b) == 0 + @SuppressWarnings( + "interning:assignment.type.incompatible" // AST node comparisons + ) + @InternedDistinct ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b + ExpressionTree rightTree = + tree.getRightOperand(); // looking for a.compareTo(b) == 0 + // or b.compareTo(a) == 0 + if (leftTree != topBinaryTree) { + return false; + } + if (rightTree.getKind() != Tree.Kind.EQUAL_TO) { + return false; + } + return visit(rightTree, p); + } + } + + @Override + public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (!TreeUtils.isMethodInvocation( + tree, comparableCompareTo, checker.getProcessingEnvironment())) { + return false; + } + + List args = tree.getArguments(); + if (args.size() != 1) { + return false; + } + ExpressionTree arg = args.get(0); + if (arg.getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + Element argElt = TreeUtils.elementFromUse(arg); + + ExpressionTree exp = tree.getMethodSelect(); + if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + MemberSelectTree member = (MemberSelectTree) exp; + if (member.getExpression().getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + + Element refElt = TreeUtils.elementFromUse(member.getExpression()); + + if (!((refElt.equals(lhs) && argElt.equals(rhs)) + || (refElt.equals(rhs) && argElt.equals(lhs)))) { + return false; + } + return true; + } + }; + + boolean okay = + new Heuristics.Within( + new Heuristics.OfKind( + Tree.Kind.CONDITIONAL_OR, matcherEqOrCompareTo)) + .match(getCurrentPath()); + return okay; + } + + /** + * Given {@code a == b}, where a has type A and b has type B, don't issue a warning when either + * the declaration of A or that of B is annotated with @Interned because {@code a == b} will be + * true only if a's run-time type is B (or lower), in which case a is actually interned. + */ + private boolean suppressEqualsIfClassIsAnnotated( + AnnotatedTypeMirror left, AnnotatedTypeMirror right) { + // It would be better to just test their greatest lower bound. + // That could permit some comparisons that this forbids. + return classIsAnnotated(left) || classIsAnnotated(right); + } + + /** Returns true if the type's declaration has an @Interned annotation. */ + private boolean classIsAnnotated(AnnotatedTypeMirror type) { + + TypeMirror tm = type.getUnderlyingType(); + if (tm == null) { + // Maybe a type variable or wildcard had no upper bound + return false; + } - @Override - public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (!TreeUtils.isMethodInvocation( - tree, comparableCompareTo, checker.getProcessingEnvironment())) { - return false; - } + tm = TypesUtils.findConcreteUpperBound(tm); + if (tm == null || tm.getKind() == TypeKind.ARRAY) { + // Bound of a wildcard might be null + return false; + } - List args = tree.getArguments(); - if (args.size() != 1) { - return false; - } - ExpressionTree arg = args.get(0); - if (arg.getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - Element argElt = TreeUtils.elementFromUse(arg); + if (tm.getKind() != TypeKind.DECLARED) { + checker.message( + Diagnostic.Kind.WARNING, + "InterningVisitor.classIsAnnotated: tm = %s (%s)", + tm, + tm.getClass()); + } + Element classElt = ((DeclaredType) tm).asElement(); + if (classElt == null) { + checker.message( + Diagnostic.Kind.WARNING, + "InterningVisitor.classIsAnnotated: classElt = null for tm = %s (%s)", + tm, + tm.getClass()); + } + if (classElt != null) { + AnnotationMirrorSet bound = atypeFactory.getTypeDeclarationBounds(tm); + return atypeFactory.containsSameByClass(bound, Interned.class); + } + return false; + } - ExpressionTree exp = tree.getMethodSelect(); - if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; + /** + * Determines the element corresponding to "this" inside a scope. Returns null within static + * methods. + * + * @param scope the scope to search for the element corresponding to "this" in + * @return the element corresponding to "this" in the given scope, or null if not found + */ + private @Nullable Element getThis(Scope scope) { + for (Element e : scope.getLocalElements()) { + if (e.getSimpleName().contentEquals("this")) { + return e; } - MemberSelectTree member = (MemberSelectTree) exp; - if (member.getExpression().getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - - Element refElt = TreeUtils.elementFromUse(member.getExpression()); - - if (!((refElt.equals(lhs) && argElt.equals(rhs)) - || (refElt.equals(rhs) && argElt.equals(lhs)))) { - return false; + } + return null; + } + + /** + * Returns true if the given element overrides the named method in the named class. + * + * @param e an element for a method + * @param clazz the class + * @param method the name of a method + * @return true if the method given by {@code e} overrides the named method in the named class; + * false otherwise + */ + private boolean overrides(ExecutableElement e, Class clazz, String method) { + + // Get the element named by "clazz". + TypeElement clazzElt = elements.getTypeElement(clazz.getCanonicalName()); + assert clazzElt != null; + + // Check all of the methods in the class for name matches and overriding. + for (ExecutableElement elt : ElementFilter.methodsIn(clazzElt.getEnclosedElements())) { + if (elt.getSimpleName().contentEquals(method) && elements.overrides(e, elt, clazzElt)) { + return true; } - return true; - } - }; - - boolean okay = - new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrCompareTo)) - .match(getCurrentPath()); - return okay; - } - - /** - * Given {@code a == b}, where a has type A and b has type B, don't issue a warning when either - * the declaration of A or that of B is annotated with @Interned because {@code a == b} will be - * true only if a's run-time type is B (or lower), in which case a is actually interned. - */ - private boolean suppressEqualsIfClassIsAnnotated( - AnnotatedTypeMirror left, AnnotatedTypeMirror right) { - // It would be better to just test their greatest lower bound. - // That could permit some comparisons that this forbids. - return classIsAnnotated(left) || classIsAnnotated(right); - } - - /** Returns true if the type's declaration has an @Interned annotation. */ - private boolean classIsAnnotated(AnnotatedTypeMirror type) { - - TypeMirror tm = type.getUnderlyingType(); - if (tm == null) { - // Maybe a type variable or wildcard had no upper bound - return false; - } + } - tm = TypesUtils.findConcreteUpperBound(tm); - if (tm == null || tm.getKind() == TypeKind.ARRAY) { - // Bound of a wildcard might be null - return false; + return false; } - if (tm.getKind() != TypeKind.DECLARED) { - checker.message( - Diagnostic.Kind.WARNING, - "InterningVisitor.classIsAnnotated: tm = %s (%s)", - tm, - tm.getClass()); - } - Element classElt = ((DeclaredType) tm).asElement(); - if (classElt == null) { - checker.message( - Diagnostic.Kind.WARNING, - "InterningVisitor.classIsAnnotated: classElt = null for tm = %s (%s)", - tm, - tm.getClass()); - } - if (classElt != null) { - AnnotationMirrorSet bound = atypeFactory.getTypeDeclarationBounds(tm); - return atypeFactory.containsSameByClass(bound, Interned.class); - } - return false; - } - - /** - * Determines the element corresponding to "this" inside a scope. Returns null within static - * methods. - * - * @param scope the scope to search for the element corresponding to "this" in - * @return the element corresponding to "this" in the given scope, or null if not found - */ - private @Nullable Element getThis(Scope scope) { - for (Element e : scope.getLocalElements()) { - if (e.getSimpleName().contentEquals("this")) { - return e; - } - } - return null; - } - - /** - * Returns true if the given element overrides the named method in the named class. - * - * @param e an element for a method - * @param clazz the class - * @param method the name of a method - * @return true if the method given by {@code e} overrides the named method in the named class; - * false otherwise - */ - private boolean overrides(ExecutableElement e, Class clazz, String method) { - - // Get the element named by "clazz". - TypeElement clazzElt = elements.getTypeElement(clazz.getCanonicalName()); - assert clazzElt != null; - - // Check all of the methods in the class for name matches and overriding. - for (ExecutableElement elt : ElementFilter.methodsIn(clazzElt.getEnclosedElements())) { - if (elt.getSimpleName().contentEquals(method) && elements.overrides(e, elt, clazzElt)) { - return true; - } - } + /** + * Returns the type to check. + * + * @return the type to check + */ + private @Nullable DeclaredType typeToCheck( + @UnknownInitialization(BaseTypeVisitor.class) InterningVisitor this) { + @SuppressWarnings("signature:assignment.type.incompatible") // user input + @CanonicalName String className = checker.getOption("checkclass"); + if (className == null) { + return null; + } - return false; - } - - /** - * Returns the type to check. - * - * @return the type to check - */ - private @Nullable DeclaredType typeToCheck( - @UnknownInitialization(BaseTypeVisitor.class) InterningVisitor this) { - @SuppressWarnings("signature:assignment.type.incompatible") // user input - @CanonicalName String className = checker.getOption("checkclass"); - if (className == null) { - return null; - } + TypeElement classElt = elements.getTypeElement(className); + if (classElt == null) { + return null; + } - TypeElement classElt = elements.getTypeElement(className); - if (classElt == null) { - return null; + return types.getDeclaredType(classElt); } - return types.getDeclaredType(classElt); - } - - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - if (castType.getKind().isPrimitive()) { - return true; + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + if (castType.getKind().isPrimitive()) { + return true; + } + return super.isTypeCastSafe(castType, exprType); } - return super.isTypeCastSafe(castType, exprType); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java index 76256f1b706..91bc9ac9cfc 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnalysis.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.lock; -import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFStore; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.type.TypeMirror; + /** * The analysis class for the lock type system. * @@ -15,33 +16,33 @@ */ public class LockAnalysis extends CFAbstractAnalysis { - /** - * Creates a new {@link LockAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public LockAnalysis(BaseTypeChecker checker, LockAnnotatedTypeFactory factory) { - super(checker, factory); - } - - @Override - public LockTransfer createTransferFunction() { - return new LockTransfer(this, (LockChecker) checker); - } - - @Override - public LockStore createEmptyStore(boolean sequentialSemantics) { - return new LockStore(this, sequentialSemantics); - } - - @Override - public LockStore createCopiedStore(LockStore s) { - return new LockStore(this, s); - } - - @Override - public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { - return defaultCreateAbstractValue(this, annotations, underlyingType); - } + /** + * Creates a new {@link LockAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public LockAnalysis(BaseTypeChecker checker, LockAnnotatedTypeFactory factory) { + super(checker, factory); + } + + @Override + public LockTransfer createTransferFunction() { + return new LockTransfer(this, (LockChecker) checker); + } + + @Override + public LockStore createEmptyStore(boolean sequentialSemantics) { + return new LockStore(this, sequentialSemantics); + } + + @Override + public LockStore createCopiedStore(LockStore s) { + return new LockStore(this, s); + } + + @Override + public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { + return defaultCreateAbstractValue(this, annotations, underlyingType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java index 2de0553dc1a..7fdd97ba7be 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java @@ -4,22 +4,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.lock.qual.EnsuresLockHeld; import org.checkerframework.checker.lock.qual.EnsuresLockHeldIf; import org.checkerframework.checker.lock.qual.GuardSatisfied; @@ -64,6 +49,24 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.CollectionsPlume; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** * LockAnnotatedTypeFactory builds types with @LockHeld and @LockPossiblyHeld annotations. LockHeld * identifies that an object is being used as a lock and is being held when a given tree is @@ -77,706 +80,719 @@ * @checker_framework.manual #lock-checker Lock Checker */ public class LockAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory { - - /** dependent type annotation error message for when the expression is not effectively final. */ - public static final String NOT_EFFECTIVELY_FINAL = "lock expression is not effectively final"; + extends GenericAnnotatedTypeFactory { - /** The @{@link LockHeld} annotation. */ - protected final AnnotationMirror LOCKHELD = AnnotationBuilder.fromClass(elements, LockHeld.class); + /** dependent type annotation error message for when the expression is not effectively final. */ + public static final String NOT_EFFECTIVELY_FINAL = "lock expression is not effectively final"; - /** The @{@link LockPossiblyHeld} annotation. */ - protected final AnnotationMirror LOCKPOSSIBLYHELD = - AnnotationBuilder.fromClass(elements, LockPossiblyHeld.class); + /** The @{@link LockHeld} annotation. */ + protected final AnnotationMirror LOCKHELD = + AnnotationBuilder.fromClass(elements, LockHeld.class); - /** The @{@link SideEffectFree} annotation. */ - protected final AnnotationMirror SIDEEFFECTFREE = - AnnotationBuilder.fromClass(elements, SideEffectFree.class); + /** The @{@link LockPossiblyHeld} annotation. */ + protected final AnnotationMirror LOCKPOSSIBLYHELD = + AnnotationBuilder.fromClass(elements, LockPossiblyHeld.class); - /** The @{@link GuardedByUnknown} annotation. */ - protected final AnnotationMirror GUARDEDBYUNKNOWN = - AnnotationBuilder.fromClass(elements, GuardedByUnknown.class); + /** The @{@link SideEffectFree} annotation. */ + protected final AnnotationMirror SIDEEFFECTFREE = + AnnotationBuilder.fromClass(elements, SideEffectFree.class); - /** The @{@link GuardedBy} annotation. */ - protected final AnnotationMirror GUARDEDBY = - createGuardedByAnnotationMirror(new ArrayList()); + /** The @{@link GuardedByUnknown} annotation. */ + protected final AnnotationMirror GUARDEDBYUNKNOWN = + AnnotationBuilder.fromClass(elements, GuardedByUnknown.class); - /** The @{@link NewObject} annotation. */ - protected final AnnotationMirror NEWOBJECT = - AnnotationBuilder.fromClass(elements, NewObject.class); + /** The @{@link GuardedBy} annotation. */ + protected final AnnotationMirror GUARDEDBY = + createGuardedByAnnotationMirror(new ArrayList()); - /** The @{@link GuardedByBottom} annotation. */ - protected final AnnotationMirror GUARDEDBYBOTTOM = - AnnotationBuilder.fromClass(elements, GuardedByBottom.class); + /** The @{@link NewObject} annotation. */ + protected final AnnotationMirror NEWOBJECT = + AnnotationBuilder.fromClass(elements, NewObject.class); - /** The @{@link GuardSatisfied} annotation. */ - protected final AnnotationMirror GUARDSATISFIED = - AnnotationBuilder.fromClass(elements, GuardSatisfied.class); + /** The @{@link GuardedByBottom} annotation. */ + protected final AnnotationMirror GUARDEDBYBOTTOM = + AnnotationBuilder.fromClass(elements, GuardedByBottom.class); - /** The value() element/field of a @GuardedBy annotation. */ - protected final ExecutableElement guardedByValueElement = - TreeUtils.getMethod(GuardedBy.class, "value", 0, processingEnv); + /** The @{@link GuardSatisfied} annotation. */ + protected final AnnotationMirror GUARDSATISFIED = + AnnotationBuilder.fromClass(elements, GuardSatisfied.class); - /** The value() element/field of a @GuardSatisfied annotation. */ - protected final ExecutableElement guardSatisfiedValueElement = - TreeUtils.getMethod(GuardSatisfied.class, "value", 0, processingEnv); + /** The value() element/field of a @GuardedBy annotation. */ + protected final ExecutableElement guardedByValueElement = + TreeUtils.getMethod(GuardedBy.class, "value", 0, processingEnv); - /** The EnsuresLockHeld.value element/field. */ - protected final ExecutableElement ensuresLockHeldValueElement = - TreeUtils.getMethod(EnsuresLockHeld.class, "value", 0, processingEnv); + /** The value() element/field of a @GuardSatisfied annotation. */ + protected final ExecutableElement guardSatisfiedValueElement = + TreeUtils.getMethod(GuardSatisfied.class, "value", 0, processingEnv); - /** The EnsuresLockHeldIf.expression element/field. */ - protected final ExecutableElement ensuresLockHeldIfExpressionElement = - TreeUtils.getMethod(EnsuresLockHeldIf.class, "expression", 0, processingEnv); + /** The EnsuresLockHeld.value element/field. */ + protected final ExecutableElement ensuresLockHeldValueElement = + TreeUtils.getMethod(EnsuresLockHeld.class, "value", 0, processingEnv); - /** The net.jcip.annotations.GuardedBy annotation, or null if not on the classpath. */ - protected final @Nullable Class jcipGuardedBy; + /** The EnsuresLockHeldIf.expression element/field. */ + protected final ExecutableElement ensuresLockHeldIfExpressionElement = + TreeUtils.getMethod(EnsuresLockHeldIf.class, "expression", 0, processingEnv); - /** The javax.annotation.concurrent.GuardedBy annotation, or null if not on the classpath. */ - protected final @Nullable Class javaxGuardedBy; + /** The net.jcip.annotations.GuardedBy annotation, or null if not on the classpath. */ + protected final @Nullable Class jcipGuardedBy; - /** Create a new LockAnnotatedTypeFactory. */ - public LockAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); + /** The javax.annotation.concurrent.GuardedBy annotation, or null if not on the classpath. */ + protected final @Nullable Class javaxGuardedBy; - jcipGuardedBy = classForNameOrNull("net.jcip.annotations.GuardedBy"); + /** Create a new LockAnnotatedTypeFactory. */ + public LockAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); - javaxGuardedBy = classForNameOrNull("javax.annotation.concurrent.GuardedBy"); + jcipGuardedBy = classForNameOrNull("net.jcip.annotations.GuardedBy"); - postInit(); - } + javaxGuardedBy = classForNameOrNull("javax.annotation.concurrent.GuardedBy"); - /** - * Returns the value of Class.forName, or null if Class.forName would throw an exception. - * - * @param annotationClassName an annotation's name, in ClassGetName format - * @return an annotation class or null - */ - @SuppressWarnings("unchecked") // cast to generic type - private @Nullable Class classForNameOrNull( - @ClassGetName String annotationClassName) { - try { - return (Class) Class.forName(annotationClassName); - } catch (Exception e) { - return null; + postInit(); } - } - - @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new DependentTypesHelper(this) { - @Override - protected void reportErrors(Tree errorTree, List errors) { - // If the error message is NOT_EFFECTIVELY_FINAL, then report - // "lock.expression.not.final" instead of "expression.unparsable.type.invalid". - List superErrors = new ArrayList<>(errors.size()); - for (DependentTypesError error : errors) { - if (error.error.equals(NOT_EFFECTIVELY_FINAL)) { - checker.reportError(errorTree, "lock.expression.not.final", error.expression); - } else { - superErrors.add(error); - } - } - super.reportErrors(errorTree, superErrors); - } - - @Override - protected boolean shouldPassThroughExpression(String expression) { - // There is no expression to use to replace here, so just pass the expression - // along. - return super.shouldPassThroughExpression(expression) - || LockVisitor.SELF_RECEIVER_PATTERN.matcher(expression).matches(); - } - - @Override - protected @Nullable JavaExpression transform(JavaExpression javaExpr) { - if (javaExpr instanceof Unknown || isExpressionEffectivelyFinal(javaExpr)) { - return javaExpr; - } - // If the expression isn't effectively final, then return the NOT_EFFECTIVELY_FINAL - // error string. - return createError(javaExpr.toString(), NOT_EFFECTIVELY_FINAL); - } - }; - } - - /** - * Returns whether or not the expression is effectively final. - * - *

          This method returns true in the following cases when expr is: - * - *

          1. a field access and the field is final and the field access expression is effectively - * final as specified by this method. - * - *

          2. an effectively final local variable. - * - *

          3. a deterministic method call whose arguments and receiver expression are effectively final - * as specified by this method. - * - *

          4. a this reference or a class literal - * - * @param expr expression - * @return whether or not the expression is effectively final - */ - boolean isExpressionEffectivelyFinal(JavaExpression expr) { - if (expr instanceof FieldAccess) { - FieldAccess fieldAccess = (FieldAccess) expr; - JavaExpression receiver = fieldAccess.getReceiver(); - // Don't call fieldAccess - return fieldAccess.isFinal() && isExpressionEffectivelyFinal(receiver); - } else if (expr instanceof LocalVariable) { - return ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement()); - } else if (expr instanceof MethodCall) { - MethodCall methodCall = (MethodCall) expr; - for (JavaExpression arg : methodCall.getArguments()) { - if (!isExpressionEffectivelyFinal(arg)) { - return false; + /** + * Returns the value of Class.forName, or null if Class.forName would throw an exception. + * + * @param annotationClassName an annotation's name, in ClassGetName format + * @return an annotation class or null + */ + @SuppressWarnings("unchecked") // cast to generic type + private @Nullable Class classForNameOrNull( + @ClassGetName String annotationClassName) { + try { + return (Class) Class.forName(annotationClassName); + } catch (Exception e) { + return null; } - } - return PurityUtils.isDeterministic(this, methodCall.getElement()) - && isExpressionEffectivelyFinal(methodCall.getReceiver()); - } else if (expr instanceof ThisReference || expr instanceof ClassName) { - // this is always final. "ClassName" is actually a class literal (String.class), it's - // final too. - return true; - } else { // type of 'expr' is not supported in @GuardedBy(...) lock expressions - return false; } - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList( - LockHeld.class, - LockPossiblyHeld.class, - GuardedBy.class, - GuardedByUnknown.class, - GuardSatisfied.class, - NewObject.class, - GuardedByBottom.class)); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new LockQualifierHierarchy(getSupportedTypeQualifiers(), elements); - } - - @Override - protected LockAnalysis createFlowAnalysis() { - return new LockAnalysis(checker, this); - } - - @Override - public LockTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new LockTransfer((LockAnalysis) analysis, (LockChecker) this.checker); - } - - /** LockQualifierHierarchy. */ - class LockQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - - /** Qualifier kind for the @{@link GuardedByUnknown} annotation. */ - private final QualifierKind GUARDEDBYUNKNOWN_KIND; - - /** Qualifier kind for the @{@link GuardedBy} annotation. */ - private final QualifierKind GUARDEDBY_KIND; - - /** Qualifier kind for the @{@link GuardSatisfied} annotation. */ - private final QualifierKind GUARDSATISFIED_KIND; - - /** Qualifier kind for the @{@link NewObject} annotation. */ - private final QualifierKind NEWOBJECT_KIND; - - /** Qualifier kind for the @{@link GuardedByBottom} annotation. */ - private final QualifierKind GUARDEDBYBOTTOM_KIND; + + @Override + protected DependentTypesHelper createDependentTypesHelper() { + return new DependentTypesHelper(this) { + @Override + protected void reportErrors(Tree errorTree, List errors) { + // If the error message is NOT_EFFECTIVELY_FINAL, then report + // "lock.expression.not.final" instead of "expression.unparsable.type.invalid". + List superErrors = new ArrayList<>(errors.size()); + for (DependentTypesError error : errors) { + if (error.error.equals(NOT_EFFECTIVELY_FINAL)) { + checker.reportError( + errorTree, "lock.expression.not.final", error.expression); + } else { + superErrors.add(error); + } + } + super.reportErrors(errorTree, superErrors); + } + + @Override + protected boolean shouldPassThroughExpression(String expression) { + // There is no expression to use to replace here, so just pass the expression + // along. + return super.shouldPassThroughExpression(expression) + || LockVisitor.SELF_RECEIVER_PATTERN.matcher(expression).matches(); + } + + @Override + protected @Nullable JavaExpression transform(JavaExpression javaExpr) { + if (javaExpr instanceof Unknown || isExpressionEffectivelyFinal(javaExpr)) { + return javaExpr; + } + + // If the expression isn't effectively final, then return the NOT_EFFECTIVELY_FINAL + // error string. + return createError(javaExpr.toString(), NOT_EFFECTIVELY_FINAL); + } + }; + } /** - * Creates a LockQualifierHierarchy. + * Returns whether or not the expression is effectively final. + * + *

          This method returns true in the following cases when expr is: * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils + *

          1. a field access and the field is final and the field access expression is effectively + * final as specified by this method. + * + *

          2. an effectively final local variable. + * + *

          3. a deterministic method call whose arguments and receiver expression are effectively + * final as specified by this method. + * + *

          4. a this reference or a class literal + * + * @param expr expression + * @return whether or not the expression is effectively final */ - public LockQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, LockAnnotatedTypeFactory.this); - GUARDEDBYUNKNOWN_KIND = getQualifierKind(GUARDEDBYUNKNOWN); - GUARDEDBY_KIND = getQualifierKind(GUARDEDBY); - GUARDSATISFIED_KIND = getQualifierKind(GUARDSATISFIED); - NEWOBJECT_KIND = getQualifierKind(NEWOBJECT); - GUARDEDBYBOTTOM_KIND = getQualifierKind(GUARDEDBYBOTTOM); + boolean isExpressionEffectivelyFinal(JavaExpression expr) { + if (expr instanceof FieldAccess) { + FieldAccess fieldAccess = (FieldAccess) expr; + JavaExpression receiver = fieldAccess.getReceiver(); + // Don't call fieldAccess + return fieldAccess.isFinal() && isExpressionEffectivelyFinal(receiver); + } else if (expr instanceof LocalVariable) { + return ElementUtils.isEffectivelyFinal(((LocalVariable) expr).getElement()); + } else if (expr instanceof MethodCall) { + MethodCall methodCall = (MethodCall) expr; + for (JavaExpression arg : methodCall.getArguments()) { + if (!isExpressionEffectivelyFinal(arg)) { + return false; + } + } + return PurityUtils.isDeterministic(this, methodCall.getElement()) + && isExpressionEffectivelyFinal(methodCall.getReceiver()); + } else if (expr instanceof ThisReference || expr instanceof ClassName) { + // this is always final. "ClassName" is actually a class literal (String.class), it's + // final too. + return true; + } else { // type of 'expr' is not supported in @GuardedBy(...) lock expressions + return false; + } } @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == GUARDEDBY_KIND && superKind == GUARDEDBY_KIND) { - List subLocks = - AnnotationUtils.getElementValueArray( - superAnno, guardedByValueElement, String.class, Collections.emptyList()); - List superLocks = - AnnotationUtils.getElementValueArray( - subAnno, guardedByValueElement, String.class, Collections.emptyList()); - return subLocks.containsAll(superLocks) && superLocks.containsAll(subLocks); - } else if (subKind == GUARDSATISFIED_KIND && superKind == GUARDSATISFIED_KIND) { - return AnnotationUtils.areSame(superAnno, subAnno); - } - throw new RuntimeException("Unexpected"); + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + LockHeld.class, + LockPossiblyHeld.class, + GuardedBy.class, + GuardedByUnknown.class, + GuardSatisfied.class, + NewObject.class, + GuardedByBottom.class)); } @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { - List locks1 = - AnnotationUtils.getElementValueArray( - a1, guardedByValueElement, String.class, Collections.emptyList()); - List locks2 = - AnnotationUtils.getElementValueArray( - a2, guardedByValueElement, String.class, Collections.emptyList()); - if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { - return a1; - } else { - return GUARDEDBYUNKNOWN; - } - } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return GUARDEDBYUNKNOWN; - } - } else if (qualifierKind1 == GUARDEDBYBOTTOM_KIND) { - return a2; - } else if (qualifierKind2 == GUARDEDBYBOTTOM_KIND) { - return a1; - } else if (qualifierKind1 == NEWOBJECT_KIND) { - return a2; - } else if (qualifierKind2 == NEWOBJECT_KIND) { - return a1; - } - throw new TypeSystemError( - "leastUpperBoundWithElements(%s, %s, %s, %s, %s)", - a1, qualifierKind1, a2, qualifierKind2, lubKind); + protected QualifierHierarchy createQualifierHierarchy() { + return new LockQualifierHierarchy(getSupportedTypeQualifiers(), elements); } - // GLB never returns @NewObject unless one of the argumetns is @NewObject; it returns - // @GuardedByBottom instead, to prevent showing users the unexpected @NewObject type. @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { - List locks1 = - AnnotationUtils.getElementValueArray( - a1, guardedByValueElement, String.class, Collections.emptyList()); - List locks2 = - AnnotationUtils.getElementValueArray( - a2, guardedByValueElement, String.class, Collections.emptyList()); - if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { - return a1; - } else { - return GUARDEDBYBOTTOM; - } - } else if (qualifierKind1 == GUARDSATISFIED_KIND && qualifierKind2 == GUARDSATISFIED_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return GUARDEDBYBOTTOM; - } - } else if (qualifierKind1 == GUARDEDBYUNKNOWN_KIND) { - return a2; - } else if (qualifierKind2 == GUARDEDBYUNKNOWN_KIND) { - return a1; - } - throw new TypeSystemError( - "greatestLowerBoundWithElements(%s, %s, %s, %s, %s)", - a1, qualifierKind1, a2, qualifierKind2, glbKind); + protected LockAnalysis createFlowAnalysis() { + return new LockAnalysis(checker, this); } - } - - // The side effect annotations processed by the Lock Checker. - enum SideEffectAnnotation { - MAYRELEASELOCKS("@MayReleaseLocks", MayReleaseLocks.class), - RELEASESNOLOCKS("@ReleasesNoLocks", ReleasesNoLocks.class), - LOCKINGFREE("@LockingFree", LockingFree.class), - SIDEEFFECTFREE("@SideEffectFree", SideEffectFree.class), - PURE("@Pure", Pure.class); - final String annotation; - final Class annotationClass; - - SideEffectAnnotation(String annotation, Class annotationClass) { - this.annotation = annotation; - this.annotationClass = annotationClass; + + @Override + public LockTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new LockTransfer((LockAnalysis) analysis, (LockChecker) this.checker); } - public String getNameOfSideEffectAnnotation() { - return annotation; + /** LockQualifierHierarchy. */ + class LockQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + /** Qualifier kind for the @{@link GuardedByUnknown} annotation. */ + private final QualifierKind GUARDEDBYUNKNOWN_KIND; + + /** Qualifier kind for the @{@link GuardedBy} annotation. */ + private final QualifierKind GUARDEDBY_KIND; + + /** Qualifier kind for the @{@link GuardSatisfied} annotation. */ + private final QualifierKind GUARDSATISFIED_KIND; + + /** Qualifier kind for the @{@link NewObject} annotation. */ + private final QualifierKind NEWOBJECT_KIND; + + /** Qualifier kind for the @{@link GuardedByBottom} annotation. */ + private final QualifierKind GUARDEDBYBOTTOM_KIND; + + /** + * Creates a LockQualifierHierarchy. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + public LockQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, LockAnnotatedTypeFactory.this); + GUARDEDBYUNKNOWN_KIND = getQualifierKind(GUARDEDBYUNKNOWN); + GUARDEDBY_KIND = getQualifierKind(GUARDEDBY); + GUARDSATISFIED_KIND = getQualifierKind(GUARDSATISFIED); + NEWOBJECT_KIND = getQualifierKind(NEWOBJECT); + GUARDEDBYBOTTOM_KIND = getQualifierKind(GUARDEDBYBOTTOM); + } + + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == GUARDEDBY_KIND && superKind == GUARDEDBY_KIND) { + List subLocks = + AnnotationUtils.getElementValueArray( + superAnno, + guardedByValueElement, + String.class, + Collections.emptyList()); + List superLocks = + AnnotationUtils.getElementValueArray( + subAnno, + guardedByValueElement, + String.class, + Collections.emptyList()); + return subLocks.containsAll(superLocks) && superLocks.containsAll(subLocks); + } else if (subKind == GUARDSATISFIED_KIND && superKind == GUARDSATISFIED_KIND) { + return AnnotationUtils.areSame(superAnno, subAnno); + } + throw new RuntimeException("Unexpected"); + } + + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { + List locks1 = + AnnotationUtils.getElementValueArray( + a1, guardedByValueElement, String.class, Collections.emptyList()); + List locks2 = + AnnotationUtils.getElementValueArray( + a2, guardedByValueElement, String.class, Collections.emptyList()); + if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { + return a1; + } else { + return GUARDEDBYUNKNOWN; + } + } else if (qualifierKind1 == GUARDSATISFIED_KIND + && qualifierKind2 == GUARDSATISFIED_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return GUARDEDBYUNKNOWN; + } + } else if (qualifierKind1 == GUARDEDBYBOTTOM_KIND) { + return a2; + } else if (qualifierKind2 == GUARDEDBYBOTTOM_KIND) { + return a1; + } else if (qualifierKind1 == NEWOBJECT_KIND) { + return a2; + } else if (qualifierKind2 == NEWOBJECT_KIND) { + return a1; + } + throw new TypeSystemError( + "leastUpperBoundWithElements(%s, %s, %s, %s, %s)", + a1, qualifierKind1, a2, qualifierKind2, lubKind); + } + + // GLB never returns @NewObject unless one of the argumetns is @NewObject; it returns + // @GuardedByBottom instead, to prevent showing users the unexpected @NewObject type. + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == GUARDEDBY_KIND && qualifierKind2 == GUARDEDBY_KIND) { + List locks1 = + AnnotationUtils.getElementValueArray( + a1, guardedByValueElement, String.class, Collections.emptyList()); + List locks2 = + AnnotationUtils.getElementValueArray( + a2, guardedByValueElement, String.class, Collections.emptyList()); + if (locks1.containsAll(locks2) && locks2.containsAll(locks1)) { + return a1; + } else { + return GUARDEDBYBOTTOM; + } + } else if (qualifierKind1 == GUARDSATISFIED_KIND + && qualifierKind2 == GUARDSATISFIED_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return GUARDEDBYBOTTOM; + } + } else if (qualifierKind1 == GUARDEDBYUNKNOWN_KIND) { + return a2; + } else if (qualifierKind2 == GUARDEDBYUNKNOWN_KIND) { + return a1; + } + throw new TypeSystemError( + "greatestLowerBoundWithElements(%s, %s, %s, %s, %s)", + a1, qualifierKind1, a2, qualifierKind2, glbKind); + } } - public Class getAnnotationClass() { - return annotationClass; + // The side effect annotations processed by the Lock Checker. + enum SideEffectAnnotation { + MAYRELEASELOCKS("@MayReleaseLocks", MayReleaseLocks.class), + RELEASESNOLOCKS("@ReleasesNoLocks", ReleasesNoLocks.class), + LOCKINGFREE("@LockingFree", LockingFree.class), + SIDEEFFECTFREE("@SideEffectFree", SideEffectFree.class), + PURE("@Pure", Pure.class); + final String annotation; + final Class annotationClass; + + SideEffectAnnotation(String annotation, Class annotationClass) { + this.annotation = annotation; + this.annotationClass = annotationClass; + } + + public String getNameOfSideEffectAnnotation() { + return annotation; + } + + public Class getAnnotationClass() { + return annotationClass; + } + + /** + * Returns true if the receiver side effect annotation is weaker than side effect annotation + * 'other'. + */ + boolean isWeakerThan(SideEffectAnnotation other) { + boolean weaker = false; + + switch (other) { + case MAYRELEASELOCKS: + break; + case RELEASESNOLOCKS: + if (this == SideEffectAnnotation.MAYRELEASELOCKS) { + weaker = true; + } + break; + case LOCKINGFREE: + switch (this) { + case MAYRELEASELOCKS: + case RELEASESNOLOCKS: + weaker = true; + break; + default: + } + break; + case SIDEEFFECTFREE: + switch (this) { + case MAYRELEASELOCKS: + case RELEASESNOLOCKS: + case LOCKINGFREE: + weaker = true; + break; + default: + } + break; + case PURE: + switch (this) { + case MAYRELEASELOCKS: + case RELEASESNOLOCKS: + case LOCKINGFREE: + case SIDEEFFECTFREE: + weaker = true; + break; + default: + } + break; + } + + return weaker; + } + + static SideEffectAnnotation weakest = null; + + public static SideEffectAnnotation weakest() { + if (weakest == null) { + for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { + if (weakest == null) { + weakest = sea; + } + if (sea.isWeakerThan(weakest)) { + weakest = sea; + } + } + } + return weakest; + } } /** - * Returns true if the receiver side effect annotation is weaker than side effect annotation - * 'other'. + * Indicates which side effect annotation is present on the given method. If more than one + * annotation is present, this method issues an error (if issueErrorIfMoreThanOnePresent is + * true) and returns the annotation providing the weakest guarantee. Only call with + * issueErrorIfMoreThanOnePresent == true when visiting a method definition. This prevents + * multiple errors being issued for the same method (as would occur if + * issueErrorIfMoreThanOnePresent were set to true when visiting method invocations). If no + * annotation is present, return RELEASESNOLOCKS as the default, and MAYRELEASELOCKS as the + * conservative default. + * + * @param methodElement the method element + * @param issueErrorIfMoreThanOnePresent whether to issue an error if more than one side effect + * annotation is present on the method + * @return the side effect annotation that is present on the given method */ - boolean isWeakerThan(SideEffectAnnotation other) { - boolean weaker = false; - - switch (other) { - case MAYRELEASELOCKS: - break; - case RELEASESNOLOCKS: - if (this == SideEffectAnnotation.MAYRELEASELOCKS) { - weaker = true; - } - break; - case LOCKINGFREE: - switch (this) { - case MAYRELEASELOCKS: - case RELEASESNOLOCKS: - weaker = true; - break; - default: - } - break; - case SIDEEFFECTFREE: - switch (this) { - case MAYRELEASELOCKS: - case RELEASESNOLOCKS: - case LOCKINGFREE: - weaker = true; - break; - default: - } - break; - case PURE: - switch (this) { - case MAYRELEASELOCKS: - case RELEASESNOLOCKS: - case LOCKINGFREE: - case SIDEEFFECTFREE: - weaker = true; - break; - default: - } - break; - } - - return weaker; - } - - static SideEffectAnnotation weakest = null; + /*package-private*/ @Nullable SideEffectAnnotation methodSideEffectAnnotation( + ExecutableElement methodElement, boolean issueErrorIfMoreThanOnePresent) { + if (methodElement == null) { + // When there is not enough information to determine the correct side effect annotation, + // return the weakest one. + return SideEffectAnnotation.weakest(); + } - public static SideEffectAnnotation weakest() { - if (weakest == null) { + Set sideEffectAnnotationPresent = + EnumSet.noneOf(SideEffectAnnotation.class); for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { - if (weakest == null) { - weakest = sea; - } - if (sea.isWeakerThan(weakest)) { - weakest = sea; - } + if (getDeclAnnotationNoAliases(methodElement, sea.getAnnotationClass()) != null) { + sideEffectAnnotationPresent.add(sea); + } } - } - return weakest; - } - } - - /** - * Indicates which side effect annotation is present on the given method. If more than one - * annotation is present, this method issues an error (if issueErrorIfMoreThanOnePresent is true) - * and returns the annotation providing the weakest guarantee. Only call with - * issueErrorIfMoreThanOnePresent == true when visiting a method definition. This prevents - * multiple errors being issued for the same method (as would occur if - * issueErrorIfMoreThanOnePresent were set to true when visiting method invocations). If no - * annotation is present, return RELEASESNOLOCKS as the default, and MAYRELEASELOCKS as the - * conservative default. - * - * @param methodElement the method element - * @param issueErrorIfMoreThanOnePresent whether to issue an error if more than one side effect - * annotation is present on the method - * @return the side effect annotation that is present on the given method - */ - /*package-private*/ @Nullable SideEffectAnnotation methodSideEffectAnnotation( - ExecutableElement methodElement, boolean issueErrorIfMoreThanOnePresent) { - if (methodElement == null) { - // When there is not enough information to determine the correct side effect annotation, - // return the weakest one. - return SideEffectAnnotation.weakest(); - } - Set sideEffectAnnotationPresent = - EnumSet.noneOf(SideEffectAnnotation.class); - for (SideEffectAnnotation sea : SideEffectAnnotation.values()) { - if (getDeclAnnotationNoAliases(methodElement, sea.getAnnotationClass()) != null) { - sideEffectAnnotationPresent.add(sea); - } - } + int count = sideEffectAnnotationPresent.size(); - int count = sideEffectAnnotationPresent.size(); + if (count == 0) { + return defaults.applyConservativeDefaults(methodElement) + ? SideEffectAnnotation.MAYRELEASELOCKS + : SideEffectAnnotation.RELEASESNOLOCKS; + } - if (count == 0) { - return defaults.applyConservativeDefaults(methodElement) - ? SideEffectAnnotation.MAYRELEASELOCKS - : SideEffectAnnotation.RELEASESNOLOCKS; - } + if (count > 1 && issueErrorIfMoreThanOnePresent) { + // TODO: Turn on after figuring out how this interacts with inherited annotations. + // checker.reportError(methodElement, "multiple.sideeffect.annotations"); + } - if (count > 1 && issueErrorIfMoreThanOnePresent) { - // TODO: Turn on after figuring out how this interacts with inherited annotations. - // checker.reportError(methodElement, "multiple.sideeffect.annotations"); + SideEffectAnnotation weakest = null; + // At least one side effect annotation was found. Return the weakest. + for (SideEffectAnnotation sea : sideEffectAnnotationPresent) { + if (weakest == null || sea.isWeakerThan(weakest)) { + weakest = sea; + } + } + return weakest; } - SideEffectAnnotation weakest = null; - // At least one side effect annotation was found. Return the weakest. - for (SideEffectAnnotation sea : sideEffectAnnotationPresent) { - if (weakest == null || sea.isWeakerThan(weakest)) { - weakest = sea; - } + /** + * Returns the index (that is, the {@code value} element) on the {@code @}{@link GuardSatisfied} + * annotation in the given AnnotatedTypeMirror. + * + * @param atm an AnnotatedTypeMirror containing a {@link GuardSatisfied} annotation + * @return the index on the {@link GuardSatisfied} annotation + */ + /*package-private*/ int getGuardSatisfiedIndex(AnnotatedTypeMirror atm) { + return getGuardSatisfiedIndex(atm.getAnnotation(GuardSatisfied.class)); } - return weakest; - } - - /** - * Returns the index (that is, the {@code value} element) on the {@code @}{@link GuardSatisfied} - * annotation in the given AnnotatedTypeMirror. - * - * @param atm an AnnotatedTypeMirror containing a {@link GuardSatisfied} annotation - * @return the index on the {@link GuardSatisfied} annotation - */ - /*package-private*/ int getGuardSatisfiedIndex(AnnotatedTypeMirror atm) { - return getGuardSatisfiedIndex(atm.getAnnotation(GuardSatisfied.class)); - } - - /** - * Returns the index (that is, the {@code value} element) on the given {@code @}{@link - * GuardSatisfied} annotation. - * - * @param am an AnnotationMirror for a {@link GuardSatisfied} annotation - * @return the index on the {@link GuardSatisfied} annotation - */ - /*package-private*/ int getGuardSatisfiedIndex(AnnotationMirror am) { - return AnnotationUtils.getElementValueInt(am, guardSatisfiedValueElement, -1); - } - - @Override - public ParameterizedExecutableType methodFromUse( - ExpressionTree tree, - ExecutableElement methodElt, - AnnotatedTypeMirror receiverType, - boolean inferTypeArgs) { - ParameterizedExecutableType mType = - super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); - - if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { - return mType; + + /** + * Returns the index (that is, the {@code value} element) on the given {@code @}{@link + * GuardSatisfied} annotation. + * + * @param am an AnnotationMirror for a {@link GuardSatisfied} annotation + * @return the index on the {@link GuardSatisfied} annotation + */ + /*package-private*/ int getGuardSatisfiedIndex(AnnotationMirror am) { + return AnnotationUtils.getElementValueInt(am, guardSatisfiedValueElement, -1); } - // If a method's formal return type is annotated with @GuardSatisfied(index), look for the - // first instance of @GuardSatisfied(index) in the method definition's receiver type or - // formal parameters, retrieve the corresponding type of the actual parameter / receiver at - // the call site (e.g. @GuardedBy("someLock") and replace the return type at the call site - // with this type. + @Override + public ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + ParameterizedExecutableType mType = + super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); + + if (tree.getKind() != Tree.Kind.METHOD_INVOCATION) { + return mType; + } - AnnotatedExecutableType invokedMethod = mType.executableType; + // If a method's formal return type is annotated with @GuardSatisfied(index), look for the + // first instance of @GuardSatisfied(index) in the method definition's receiver type or + // formal parameters, retrieve the corresponding type of the actual parameter / receiver at + // the call site (e.g. @GuardedBy("someLock") and replace the return type at the call site + // with this type. - if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) { - return mType; - } + AnnotatedExecutableType invokedMethod = mType.executableType; - AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType(); + if (invokedMethod.getElement().getKind() == ElementKind.CONSTRUCTOR) { + return mType; + } - if (methodDefinitionReturn == null - || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) { - return mType; - } + AnnotatedTypeMirror methodDefinitionReturn = invokedMethod.getReturnType(); - int returnGuardSatisfiedIndex = getGuardSatisfiedIndex(methodDefinitionReturn); + if (methodDefinitionReturn == null + || !methodDefinitionReturn.hasAnnotation(GuardSatisfied.class)) { + return mType; + } - // @GuardSatisfied with no index defaults to index -1. Ignore instances of @GuardSatisfied - // with no index. If a method is defined with a return type of @GuardSatisfied with no - // index, an error is reported by LockVisitor.visitMethod. + int returnGuardSatisfiedIndex = getGuardSatisfiedIndex(methodDefinitionReturn); - if (returnGuardSatisfiedIndex == -1) { - return mType; - } + // @GuardSatisfied with no index defaults to index -1. Ignore instances of @GuardSatisfied + // with no index. If a method is defined with a return type of @GuardSatisfied with no + // index, an error is reported by LockVisitor.visitMethod. - // Find the receiver or first parameter whose @GS index matches that of the return type. - // Ensuring that the type annotations on distinct @GS parameters with the same index - // match at the call site is handled in LockVisitor.visitMethodInvocation - - if (!ElementUtils.isStatic(invokedMethod.getElement()) - && replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( - methodDefinitionReturn, - invokedMethod.getReceiverType() /* the method definition receiver*/, - returnGuardSatisfiedIndex, - receiverType.getAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { - return mType; - } + if (returnGuardSatisfiedIndex == -1) { + return mType; + } - List methodInvocationTreeArguments = - ((MethodInvocationTree) tree).getArguments(); - List paramTypes = invokedMethod.getParameterTypes(); - - for (int i = 0; i < paramTypes.size(); i++) { - if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( - methodDefinitionReturn, - paramTypes.get(i), - returnGuardSatisfiedIndex, - getAnnotatedType(methodInvocationTreeArguments.get(i)) - .getEffectiveAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { - return mType; - } - } + // Find the receiver or first parameter whose @GS index matches that of the return type. + // Ensuring that the type annotations on distinct @GS parameters with the same index + // match at the call site is handled in LockVisitor.visitMethodInvocation + + if (!ElementUtils.isStatic(invokedMethod.getElement()) + && replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( + methodDefinitionReturn, + invokedMethod.getReceiverType() /* the method definition receiver*/, + returnGuardSatisfiedIndex, + receiverType.getAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { + return mType; + } + + List methodInvocationTreeArguments = + ((MethodInvocationTree) tree).getArguments(); + List paramTypes = invokedMethod.getParameterTypes(); + + for (int i = 0; i < paramTypes.size(); i++) { + if (replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( + methodDefinitionReturn, + paramTypes.get(i), + returnGuardSatisfiedIndex, + getAnnotatedType(methodInvocationTreeArguments.get(i)) + .getEffectiveAnnotationInHierarchy(GUARDEDBYUNKNOWN))) { + return mType; + } + } - return mType; - } - - /** - * If {@code atm} is not null and contains a {@code @GuardSatisfied} annotation, and if the index - * of this {@code @GuardSatisfied} annotation matches {@code matchingGuardSatisfiedIndex}, then - * {@code methodReturnAtm} will have its annotation in the {@code @GuardedBy} hierarchy replaced - * with that in {@code annotationInGuardedByHierarchy}. - * - * @param methodReturnAtm the AnnotatedTypeMirror for the return type of a method that will - * potentially have its annotation in the {@code @GuardedBy} hierarchy replaced. - * @param atm an AnnotatedTypeMirror that may contain a {@code @GuardSatisfied} annotation. May be - * null. - * @param matchingGuardSatisfiedIndex the {code @GuardSatisfied} index that the - * {@code @GuardSatisfied} annotation in {@code atm} must have in order for the replacement to - * occur. - * @param annotationInGuardedByHierarchy if the replacement occurs, the annotation in the - * {@code @GuardedBy} hierarchy in this parameter will be used for the replacement. - * @return true if the replacement occurred, false otherwise - */ - private boolean replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( - AnnotatedTypeMirror methodReturnAtm, - @Nullable AnnotatedTypeMirror atm, - int matchingGuardSatisfiedIndex, - AnnotationMirror annotationInGuardedByHierarchy) { - if (atm == null - || !atm.hasAnnotation(GuardSatisfied.class) - || getGuardSatisfiedIndex(atm) != matchingGuardSatisfiedIndex) { - return false; + return mType; } - methodReturnAtm.replaceAnnotation(annotationInGuardedByHierarchy); + /** + * If {@code atm} is not null and contains a {@code @GuardSatisfied} annotation, and if the + * index of this {@code @GuardSatisfied} annotation matches {@code matchingGuardSatisfiedIndex}, + * then {@code methodReturnAtm} will have its annotation in the {@code @GuardedBy} hierarchy + * replaced with that in {@code annotationInGuardedByHierarchy}. + * + * @param methodReturnAtm the AnnotatedTypeMirror for the return type of a method that will + * potentially have its annotation in the {@code @GuardedBy} hierarchy replaced. + * @param atm an AnnotatedTypeMirror that may contain a {@code @GuardSatisfied} annotation. May + * be null. + * @param matchingGuardSatisfiedIndex the {code @GuardSatisfied} index that the + * {@code @GuardSatisfied} annotation in {@code atm} must have in order for the replacement + * to occur. + * @param annotationInGuardedByHierarchy if the replacement occurs, the annotation in the + * {@code @GuardedBy} hierarchy in this parameter will be used for the replacement. + * @return true if the replacement occurred, false otherwise + */ + private boolean replaceAnnotationInGuardedByHierarchyIfGuardSatisfiedIndexMatches( + AnnotatedTypeMirror methodReturnAtm, + @Nullable AnnotatedTypeMirror atm, + int matchingGuardSatisfiedIndex, + AnnotationMirror annotationInGuardedByHierarchy) { + if (atm == null + || !atm.hasAnnotation(GuardSatisfied.class) + || getGuardSatisfiedIndex(atm) != matchingGuardSatisfiedIndex) { + return false; + } - return true; - } + methodReturnAtm.replaceAnnotation(annotationInGuardedByHierarchy); - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new LockTreeAnnotator(this), super.createTreeAnnotator()); - } + return true; + } - @Override - public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - translateJcipAndJavaxAnnotations(elt, type); + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new LockTreeAnnotator(this), super.createTreeAnnotator()); + } - super.addComputedTypeAnnotations(elt, type); - } + @Override + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + translateJcipAndJavaxAnnotations(elt, type); - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - if (tree.getKind() == Tree.Kind.VARIABLE) { - translateJcipAndJavaxAnnotations(TreeUtils.elementFromDeclaration((VariableTree) tree), type); + super.addComputedTypeAnnotations(elt, type); } - super.addComputedTypeAnnotations(tree, type); - } - - /** - * Given a field declaration with a {@code @net.jcip.annotations.GuardedBy} or {@code - * javax.annotation.concurrent.GuardedBy} annotation and an AnnotatedTypeMirror for that field, - * inserts the corresponding {@code @org.checkerframework.checker.lock.qual.GuardedBy} type - * qualifier into that AnnotatedTypeMirror. - * - * @param element any Element (this method does nothing if the Element is not for a field - * declaration) - * @param atm the AnnotatedTypeMirror for element - the {@code @GuardedBy} type qualifier will be - * inserted here - */ - private void translateJcipAndJavaxAnnotations(Element element, AnnotatedTypeMirror atm) { - if (!element.getKind().isField()) { - return; + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + if (tree.getKind() == Tree.Kind.VARIABLE) { + translateJcipAndJavaxAnnotations( + TreeUtils.elementFromDeclaration((VariableTree) tree), type); + } + + super.addComputedTypeAnnotations(tree, type); } - AnnotationMirror anno = null; + /** + * Given a field declaration with a {@code @net.jcip.annotations.GuardedBy} or {@code + * javax.annotation.concurrent.GuardedBy} annotation and an AnnotatedTypeMirror for that field, + * inserts the corresponding {@code @org.checkerframework.checker.lock.qual.GuardedBy} type + * qualifier into that AnnotatedTypeMirror. + * + * @param element any Element (this method does nothing if the Element is not for a field + * declaration) + * @param atm the AnnotatedTypeMirror for element - the {@code @GuardedBy} type qualifier will + * be inserted here + */ + private void translateJcipAndJavaxAnnotations(Element element, AnnotatedTypeMirror atm) { + if (!element.getKind().isField()) { + return; + } - if (jcipGuardedBy != null) { - anno = getDeclAnnotation(element, jcipGuardedBy); - } + AnnotationMirror anno = null; - if (anno == null && javaxGuardedBy != null) { - anno = getDeclAnnotation(element, javaxGuardedBy); - } + if (jcipGuardedBy != null) { + anno = getDeclAnnotation(element, jcipGuardedBy); + } - if (anno == null) { - return; - } + if (anno == null && javaxGuardedBy != null) { + anno = getDeclAnnotation(element, javaxGuardedBy); + } + + if (anno == null) { + return; + } + + // The version of javax.annotation.concurrent.GuardedBy included with the Checker Framework + // declares the type of value as an array of Strings, whereas the one defined in JCIP and + // included with FindBugs declares it as a String. So, the code below figures out which type + // should be used. + Map valmap = + anno.getElementValues(); + Object value = null; + for (ExecutableElement elem : valmap.keySet()) { + if (elem.getSimpleName().contentEquals("value")) { + value = valmap.get(elem).getValue(); + break; + } + } + List lockExpressions; + if (value instanceof List) { + @SuppressWarnings("unchecked") + List la = (List) value; + lockExpressions = + CollectionsPlume.mapList((AnnotationValue a) -> (String) a.getValue(), la); + } else if (value instanceof String) { + lockExpressions = Collections.singletonList((String) value); + } else { + return; + } - // The version of javax.annotation.concurrent.GuardedBy included with the Checker Framework - // declares the type of value as an array of Strings, whereas the one defined in JCIP and - // included with FindBugs declares it as a String. So, the code below figures out which type - // should be used. - Map valmap = anno.getElementValues(); - Object value = null; - for (ExecutableElement elem : valmap.keySet()) { - if (elem.getSimpleName().contentEquals("value")) { - value = valmap.get(elem).getValue(); - break; - } + if (lockExpressions.isEmpty()) { + atm.addAnnotation(GUARDEDBY); + } else { + atm.addAnnotation(createGuardedByAnnotationMirror(lockExpressions)); + } } - List lockExpressions; - if (value instanceof List) { - @SuppressWarnings("unchecked") - List la = (List) value; - lockExpressions = CollectionsPlume.mapList((AnnotationValue a) -> (String) a.getValue(), la); - } else if (value instanceof String) { - lockExpressions = Collections.singletonList((String) value); - } else { - return; + + /** + * Returns an AnnotationMirror corresponding to @GuardedBy(values). + * + * @param values a list of lock expressions + * @return an AnnotationMirror corresponding to @GuardedBy(values) + */ + private AnnotationMirror createGuardedByAnnotationMirror(List values) { + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), GuardedBy.class); + builder.setValue("value", values.toArray()); + + // Return the resulting AnnotationMirror + return builder.build(); } - if (lockExpressions.isEmpty()) { - atm.addAnnotation(GUARDEDBY); - } else { - atm.addAnnotation(createGuardedByAnnotationMirror(lockExpressions)); + @Override + public boolean isSideEffectFree(ExecutableElement method) { + SideEffectAnnotation seAnno = methodSideEffectAnnotation(method, false); + return seAnno == SideEffectAnnotation.RELEASESNOLOCKS + || seAnno == SideEffectAnnotation.LOCKINGFREE + || super.isSideEffectFree(method); } - } - - /** - * Returns an AnnotationMirror corresponding to @GuardedBy(values). - * - * @param values a list of lock expressions - * @return an AnnotationMirror corresponding to @GuardedBy(values) - */ - private AnnotationMirror createGuardedByAnnotationMirror(List values) { - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), GuardedBy.class); - builder.setValue("value", values.toArray()); - - // Return the resulting AnnotationMirror - return builder.build(); - } - - @Override - public boolean isSideEffectFree(ExecutableElement method) { - SideEffectAnnotation seAnno = methodSideEffectAnnotation(method, false); - return seAnno == SideEffectAnnotation.RELEASESNOLOCKS - || seAnno == SideEffectAnnotation.LOCKINGFREE - || super.isSideEffectFree(method); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java index fe902901a75..cbf58ce5c44 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java @@ -1,8 +1,5 @@ package org.checkerframework.checker.lock; -import java.util.ArrayList; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.lock.qual.LockHeld; import org.checkerframework.checker.lock.qual.LockPossiblyHeld; import org.checkerframework.checker.nullness.qual.Nullable; @@ -22,6 +19,11 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; +import java.util.ArrayList; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + /** * The Lock Store behaves like CFAbstractStore but requires the ability to insert exact annotations. * This is because we want to be able to insert @LockPossiblyHeld to replace @LockHeld, which @@ -29,242 +31,242 @@ */ public class LockStore extends CFAbstractStore { - /** - * If true, indicates that the store refers to a point in the code inside a constructor or - * initializer. This is useful because constructors and initializers are special with regard to - * the set of locks that is considered to be held. For example, 'this' is considered to be held - * inside a constructor. - */ - protected boolean inConstructorOrInitializer = false; + /** + * If true, indicates that the store refers to a point in the code inside a constructor or + * initializer. This is useful because constructors and initializers are special with regard to + * the set of locks that is considered to be held. For example, 'this' is considered to be held + * inside a constructor. + */ + protected boolean inConstructorOrInitializer = false; - /** The type factory to use. */ - private final LockAnnotatedTypeFactory atypeFactory; + /** The type factory to use. */ + private final LockAnnotatedTypeFactory atypeFactory; - /** - * Create a LockStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume that - * only one thread is running at all times)? - */ - public LockStore(LockAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); - } - - /** Copy constructor. */ - public LockStore(LockAnalysis analysis, CFAbstractStore other) { - super(other); - this.inConstructorOrInitializer = ((LockStore) other).inConstructorOrInitializer; - this.atypeFactory = ((LockStore) other).atypeFactory; - } + /** + * Create a LockStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume + * that only one thread is running at all times)? + */ + public LockStore(LockAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); + } - @Override - public LockStore leastUpperBound(LockStore other) { - LockStore newStore = super.leastUpperBound(other); + /** Copy constructor. */ + public LockStore(LockAnalysis analysis, CFAbstractStore other) { + super(other); + this.inConstructorOrInitializer = ((LockStore) other).inConstructorOrInitializer; + this.atypeFactory = ((LockStore) other).atypeFactory; + } - // Least upper bound of a boolean - newStore.inConstructorOrInitializer = - this.inConstructorOrInitializer && other.inConstructorOrInitializer; + @Override + public LockStore leastUpperBound(LockStore other) { + LockStore newStore = super.leastUpperBound(other); - return newStore; - } + // Least upper bound of a boolean + newStore.inConstructorOrInitializer = + this.inConstructorOrInitializer && other.inConstructorOrInitializer; - /* - * Insert an annotation exactly, without regard to whether an annotation was already present. - * This is only done for @LockPossiblyHeld. This is not sound for other type qualifiers. - */ - public void insertLockPossiblyHeld(JavaExpression je) { - if (je.containsUnknown()) { - // Expressions containing unknown expressions are not stored. - return; - } - if (je instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) je; - CFValue current = localVariableValues.get(localVar); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - localVariableValues.put(localVar, value); - } - } else if (je instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) je; - CFValue current = fieldValues.get(fieldAcc); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - fieldValues.put(fieldAcc, value); - } - } else if (je instanceof MethodCall) { - MethodCall method = (MethodCall) je; - CFValue current = methodValues.get(method); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - methodValues.put(method, value); - } - } else if (je instanceof ArrayAccess) { - ArrayAccess arrayAccess = (ArrayAccess) je; - CFValue current = arrayValues.get(arrayAccess); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - arrayValues.put(arrayAccess, value); - } - } else if (je instanceof ThisReference) { - thisValue = changeLockAnnoToTop(je, thisValue); - } else if (je instanceof ClassName) { - ClassName className = (ClassName) je; - CFValue current = classValues.get(className); - CFValue value = changeLockAnnoToTop(je, current); - if (value != null) { - classValues.put(className, value); - } - } else { - // No other types of expressions need to be stored. + return newStore; } - } - /** - * Makes a new CFValue with the same annotations as currentValue except that the annotation in the - * LockPossiblyHeld hierarchy is set to LockPossiblyHeld. If currentValue is null, then a new - * value is created where the annotation set is LockPossiblyHeld and GuardedByUnknown - */ - private CFValue changeLockAnnoToTop(JavaExpression je, @Nullable CFValue currentValue) { - if (currentValue == null) { - AnnotationMirrorSet set = new AnnotationMirrorSet(); - set.add(atypeFactory.GUARDEDBYUNKNOWN); - set.add(atypeFactory.LOCKPOSSIBLYHELD); - return analysis.createAbstractValue(set, je.getType()); + /* + * Insert an annotation exactly, without regard to whether an annotation was already present. + * This is only done for @LockPossiblyHeld. This is not sound for other type qualifiers. + */ + public void insertLockPossiblyHeld(JavaExpression je) { + if (je.containsUnknown()) { + // Expressions containing unknown expressions are not stored. + return; + } + if (je instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) je; + CFValue current = localVariableValues.get(localVar); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + localVariableValues.put(localVar, value); + } + } else if (je instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) je; + CFValue current = fieldValues.get(fieldAcc); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + fieldValues.put(fieldAcc, value); + } + } else if (je instanceof MethodCall) { + MethodCall method = (MethodCall) je; + CFValue current = methodValues.get(method); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + methodValues.put(method, value); + } + } else if (je instanceof ArrayAccess) { + ArrayAccess arrayAccess = (ArrayAccess) je; + CFValue current = arrayValues.get(arrayAccess); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + arrayValues.put(arrayAccess, value); + } + } else if (je instanceof ThisReference) { + thisValue = changeLockAnnoToTop(je, thisValue); + } else if (je instanceof ClassName) { + ClassName className = (ClassName) je; + CFValue current = classValues.get(className); + CFValue value = changeLockAnnoToTop(je, current); + if (value != null) { + classValues.put(className, value); + } + } else { + // No other types of expressions need to be stored. + } } - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - AnnotationMirrorSet currentSet = currentValue.getAnnotations(); - AnnotationMirror gb = - qualHierarchy.findAnnotationInHierarchy(currentSet, atypeFactory.GUARDEDBYUNKNOWN); - AnnotationMirrorSet newSet = new AnnotationMirrorSet(); - newSet.add(atypeFactory.LOCKPOSSIBLYHELD); - if (gb != null) { - newSet.add(gb); + /** + * Makes a new CFValue with the same annotations as currentValue except that the annotation in + * the LockPossiblyHeld hierarchy is set to LockPossiblyHeld. If currentValue is null, then a + * new value is created where the annotation set is LockPossiblyHeld and GuardedByUnknown + */ + private CFValue changeLockAnnoToTop(JavaExpression je, @Nullable CFValue currentValue) { + if (currentValue == null) { + AnnotationMirrorSet set = new AnnotationMirrorSet(); + set.add(atypeFactory.GUARDEDBYUNKNOWN); + set.add(atypeFactory.LOCKPOSSIBLYHELD); + return analysis.createAbstractValue(set, je.getType()); + } + + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + AnnotationMirrorSet currentSet = currentValue.getAnnotations(); + AnnotationMirror gb = + qualHierarchy.findAnnotationInHierarchy(currentSet, atypeFactory.GUARDEDBYUNKNOWN); + AnnotationMirrorSet newSet = new AnnotationMirrorSet(); + newSet.add(atypeFactory.LOCKPOSSIBLYHELD); + if (gb != null) { + newSet.add(gb); + } + return analysis.createAbstractValue(newSet, currentValue.getUnderlyingType()); } - return analysis.createAbstractValue(newSet, currentValue.getUnderlyingType()); - } - public void setInConstructorOrInitializer() { - inConstructorOrInitializer = true; - } + public void setInConstructorOrInitializer() { + inConstructorOrInitializer = true; + } - @Override - public @Nullable CFValue getValue(JavaExpression expr) { + @Override + public @Nullable CFValue getValue(JavaExpression expr) { - if (inConstructorOrInitializer) { - // 'this' is automatically considered as being held in a constructor or initializer. - // The class name, however, is not. - if (expr instanceof ThisReference) { - initializeThisValue(atypeFactory.LOCKHELD, expr.getType()); - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - if (!fieldAcc.isStatic() && fieldAcc.getReceiver() instanceof ThisReference) { - insertValue(fieldAcc.getReceiver(), atypeFactory.LOCKHELD); + if (inConstructorOrInitializer) { + // 'this' is automatically considered as being held in a constructor or initializer. + // The class name, however, is not. + if (expr instanceof ThisReference) { + initializeThisValue(atypeFactory.LOCKHELD, expr.getType()); + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + if (!fieldAcc.isStatic() && fieldAcc.getReceiver() instanceof ThisReference) { + insertValue(fieldAcc.getReceiver(), atypeFactory.LOCKHELD); + } + } } - } + + return super.getValue(expr); } - return super.getValue(expr); - } + @Override + protected String internalVisualize(CFGVisualizer viz) { + return viz.visualizeStoreKeyVal("inConstructorOrInitializer", inConstructorOrInitializer) + + viz.getSeparator() + + super.internalVisualize(viz); + } - @Override - protected String internalVisualize(CFGVisualizer viz) { - return viz.visualizeStoreKeyVal("inConstructorOrInitializer", inConstructorOrInitializer) - + viz.getSeparator() - + super.internalVisualize(viz); - } + @Override + public void updateForMethodCall( + MethodInvocationNode n, + GenericAnnotatedTypeFactory atypeFactory, + CFValue val) { + super.updateForMethodCall(n, atypeFactory, val); + ExecutableElement method = n.getTarget().getMethod(); + // The following behavior is similar to setting the sideEffectsUnrefineAliases field of + // Lockannotatedtypefactory, but it affects only the LockPosssiblyHeld type hierarchy (not + // the @GuardedBy hierarchy), so it cannot use that logic. + if (!atypeFactory.isSideEffectFree(method)) { + // After the call to super.updateForMethodCall, only final fields are left in + // fieldValues (if the method called is side-effecting). For the LockPossiblyHeld + // hierarchy, even a final field might be locked or unlocked by a side-effecting method. + // So, final fields must be set to @LockPossiblyHeld, but the annotation in the + // GuardedBy hierarchy should not be changed. + for (FieldAccess field : new ArrayList<>(fieldValues.keySet())) { + CFValue newValue = changeLockAnnoToTop(field, fieldValues.get(field)); + if (newValue != null) { + fieldValues.put(field, newValue); + } else { + fieldValues.remove(field); + } + } - @Override - public void updateForMethodCall( - MethodInvocationNode n, - GenericAnnotatedTypeFactory atypeFactory, - CFValue val) { - super.updateForMethodCall(n, atypeFactory, val); - ExecutableElement method = n.getTarget().getMethod(); - // The following behavior is similar to setting the sideEffectsUnrefineAliases field of - // Lockannotatedtypefactory, but it affects only the LockPosssiblyHeld type hierarchy (not - // the @GuardedBy hierarchy), so it cannot use that logic. - if (!atypeFactory.isSideEffectFree(method)) { - // After the call to super.updateForMethodCall, only final fields are left in - // fieldValues (if the method called is side-effecting). For the LockPossiblyHeld - // hierarchy, even a final field might be locked or unlocked by a side-effecting method. - // So, final fields must be set to @LockPossiblyHeld, but the annotation in the - // GuardedBy hierarchy should not be changed. - for (FieldAccess field : new ArrayList<>(fieldValues.keySet())) { - CFValue newValue = changeLockAnnoToTop(field, fieldValues.get(field)); - if (newValue != null) { - fieldValues.put(field, newValue); - } else { - fieldValues.remove(field); - } - } + // Local variables could also be unlocked via an alias + for (LocalVariable var : new ArrayList<>(localVariableValues.keySet())) { + CFValue newValue = changeLockAnnoToTop(var, localVariableValues.get(var)); + if (newValue != null) { + localVariableValues.put(var, newValue); + } + } - // Local variables could also be unlocked via an alias - for (LocalVariable var : new ArrayList<>(localVariableValues.keySet())) { - CFValue newValue = changeLockAnnoToTop(var, localVariableValues.get(var)); - if (newValue != null) { - localVariableValues.put(var, newValue); + if (thisValue != null) { + thisValue = changeLockAnnoToTop(null, thisValue); + } } - } - - if (thisValue != null) { - thisValue = changeLockAnnoToTop(null, thisValue); - } } - } - /** - * Whether the specified value has the {@link LockHeld} annotation. - * - * @param value the value to check. - * @return whether the {@code value} has the {@link LockHeld} annotation - */ - boolean hasLockHeld(CFValue value) { - return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKHELD); - } - - /** - * Whether the specified value has the {@link LockPossiblyHeld} annotation. - * - * @param value the value to check. - * @return whether the {@code value} has the {@link LockPossiblyHeld} annotation - */ - boolean hasLockPossiblyHeld(CFValue value) { - return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKPOSSIBLYHELD); - } + /** + * Whether the specified value has the {@link LockHeld} annotation. + * + * @param value the value to check. + * @return whether the {@code value} has the {@link LockHeld} annotation + */ + boolean hasLockHeld(CFValue value) { + return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKHELD); + } - @Override - public void insertValue( - JavaExpression je, @Nullable CFValue value, boolean permitNondeterministic) { - if (!shouldInsert(je, value, permitNondeterministic)) { - return; + /** + * Whether the specified value has the {@link LockPossiblyHeld} annotation. + * + * @param value the value to check. + * @return whether the {@code value} has the {@link LockPossiblyHeld} annotation + */ + boolean hasLockPossiblyHeld(CFValue value) { + return AnnotationUtils.containsSame(value.getAnnotations(), atypeFactory.LOCKPOSSIBLYHELD); } - // Even with concurrent semantics enabled, a @LockHeld value must always be - // stored for fields and @Pure method calls. This is sound because: - // * Another thread can never release the lock on the current thread, and - // * Locks are assumed to be effectively final, hence another thread will not - // side effect the lock expression that has value @LockHeld. - if (hasLockHeld(value)) { - if (je instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) je; - CFValue oldValue = fieldValues.get(fieldAcc); - CFValue newValue = value.mostSpecific(oldValue, null); - if (newValue != null) { - fieldValues.put(fieldAcc, newValue); + @Override + public void insertValue( + JavaExpression je, @Nullable CFValue value, boolean permitNondeterministic) { + if (!shouldInsert(je, value, permitNondeterministic)) { + return; } - } else if (je instanceof MethodCall) { - MethodCall method = (MethodCall) je; - CFValue oldValue = methodValues.get(method); - CFValue newValue = value.mostSpecific(oldValue, null); - if (newValue != null) { - methodValues.put(method, newValue); + + // Even with concurrent semantics enabled, a @LockHeld value must always be + // stored for fields and @Pure method calls. This is sound because: + // * Another thread can never release the lock on the current thread, and + // * Locks are assumed to be effectively final, hence another thread will not + // side effect the lock expression that has value @LockHeld. + if (hasLockHeld(value)) { + if (je instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) je; + CFValue oldValue = fieldValues.get(fieldAcc); + CFValue newValue = value.mostSpecific(oldValue, null); + if (newValue != null) { + fieldValues.put(fieldAcc, newValue); + } + } else if (je instanceof MethodCall) { + MethodCall method = (MethodCall) je; + CFValue oldValue = methodValues.get(method); + CFValue newValue = value.mostSpecific(oldValue, null); + if (newValue != null) { + methodValues.put(method, newValue); + } + } } - } - } - super.insertValue(je, value, permitNondeterministic); - } + super.insertValue(je, value, permitNondeterministic); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java b/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java index 2745c36871b..740c0ba594c 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockTransfer.java @@ -2,11 +2,7 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; -import java.util.List; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.UnderlyingAST; @@ -21,134 +17,142 @@ import org.checkerframework.framework.flow.CFValue; import org.checkerframework.javacutil.TreeUtils; +import java.util.List; + +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; + /** * LockTransfer handles constructors, initializers, synchronized methods, and synchronized blocks. */ public class LockTransfer extends CFAbstractTransfer { - /** The type factory associated with this transfer function. */ - private final LockAnnotatedTypeFactory atypeFactory; - - /** - * Create a transfer function for the Lock Checker. - * - * @param analysis the analysis this transfer function belongs to - * @param checker the type-checker this transfer function belongs to - */ - public LockTransfer(LockAnalysis analysis, LockChecker checker) { - // Always run the Lock Checker with -AconcurrentSemantics turned on. - super(analysis, /* useConcurrentSemantics= */ true); - this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); - } - - /** - * Sets a given {@link Node} to @LockHeld in the given {@code store}. - * - * @param store the store to update - * @param node the node that should be @LockHeld - */ - protected void makeLockHeld(LockStore store, Node node) { - JavaExpression internalRepr = JavaExpression.fromNode(node); - store.insertValue(internalRepr, atypeFactory.LOCKHELD); - } - - /** - * Sets a given {@link Node} to @LockPossiblyHeld in the given {@code store}. - * - * @param store the store to update - * @param node the node that should be @LockPossiblyHeld - */ - protected void makeLockPossiblyHeld(LockStore store, Node node) { - JavaExpression internalRepr = JavaExpression.fromNode(node); - - // insertValue cannot change an annotation to a less specific type (e.g. LockHeld to - // LockPossiblyHeld), so insertLockPossiblyHeld is called. - store.insertLockPossiblyHeld(internalRepr); - } - - /** Sets a given {@link Node} {@code node} to LockHeld in the given {@link TransferResult}. */ - protected void makeLockHeld(TransferResult result, Node node) { - if (result.containsTwoStores()) { - makeLockHeld(result.getThenStore(), node); - makeLockHeld(result.getElseStore(), node); - } else { - makeLockHeld(result.getRegularStore(), node); + /** The type factory associated with this transfer function. */ + private final LockAnnotatedTypeFactory atypeFactory; + + /** + * Create a transfer function for the Lock Checker. + * + * @param analysis the analysis this transfer function belongs to + * @param checker the type-checker this transfer function belongs to + */ + public LockTransfer(LockAnalysis analysis, LockChecker checker) { + // Always run the Lock Checker with -AconcurrentSemantics turned on. + super(analysis, /* useConcurrentSemantics= */ true); + this.atypeFactory = (LockAnnotatedTypeFactory) analysis.getTypeFactory(); } - } - - /** - * Sets a given {@link Node} {@code node} to LockPossiblyHeld in the given {@link TransferResult}. - */ - protected void makeLockPossiblyHeld(TransferResult result, Node node) { - if (result.containsTwoStores()) { - makeLockPossiblyHeld(result.getThenStore(), node); - makeLockPossiblyHeld(result.getElseStore(), node); - } else { - makeLockPossiblyHeld(result.getRegularStore(), node); + + /** + * Sets a given {@link Node} to @LockHeld in the given {@code store}. + * + * @param store the store to update + * @param node the node that should be @LockHeld + */ + protected void makeLockHeld(LockStore store, Node node) { + JavaExpression internalRepr = JavaExpression.fromNode(node); + store.insertValue(internalRepr, atypeFactory.LOCKHELD); } - } - @Override - public LockStore initialStore(UnderlyingAST underlyingAST, List parameters) { + /** + * Sets a given {@link Node} to @LockPossiblyHeld in the given {@code store}. + * + * @param store the store to update + * @param node the node that should be @LockPossiblyHeld + */ + protected void makeLockPossiblyHeld(LockStore store, Node node) { + JavaExpression internalRepr = JavaExpression.fromNode(node); + + // insertValue cannot change an annotation to a less specific type (e.g. LockHeld to + // LockPossiblyHeld), so insertLockPossiblyHeld is called. + store.insertLockPossiblyHeld(internalRepr); + } - LockStore store = super.initialStore(underlyingAST, parameters); + /** Sets a given {@link Node} {@code node} to LockHeld in the given {@link TransferResult}. */ + protected void makeLockHeld(TransferResult result, Node node) { + if (result.containsTwoStores()) { + makeLockHeld(result.getThenStore(), node); + makeLockHeld(result.getElseStore(), node); + } else { + makeLockHeld(result.getRegularStore(), node); + } + } - Kind astKind = underlyingAST.getKind(); + /** + * Sets a given {@link Node} {@code node} to LockPossiblyHeld in the given {@link + * TransferResult}. + */ + protected void makeLockPossiblyHeld(TransferResult result, Node node) { + if (result.containsTwoStores()) { + makeLockPossiblyHeld(result.getThenStore(), node); + makeLockPossiblyHeld(result.getElseStore(), node); + } else { + makeLockPossiblyHeld(result.getRegularStore(), node); + } + } - // Methods with the 'synchronized' modifier are holding the 'this' lock. + @Override + public LockStore initialStore(UnderlyingAST underlyingAST, List parameters) { - // There is a subtle difference between synchronized methods and constructors/initializers. - // A synchronized method is only taking the intrinsic lock of the current object. It says - // nothing about any fields of the current object. + LockStore store = super.initialStore(underlyingAST, parameters); - // Furthermore, since the current object already exists, other objects may be guarded by the - // current object. So a synchronized method can affect the locking behavior of other - // objects. + Kind astKind = underlyingAST.getKind(); - // A constructor/initializer behaves as if the current object and all its non-static fields - // were held as locks. But in reality no locks are held. + // Methods with the 'synchronized' modifier are holding the 'this' lock. - // Furthermore, since the current object is being constructed, no other object can be - // guarded by it or any of its non-static fields. + // There is a subtle difference between synchronized methods and constructors/initializers. + // A synchronized method is only taking the intrinsic lock of the current object. It says + // nothing about any fields of the current object. - // Handle synchronized methods and constructors. - if (astKind == UnderlyingAST.Kind.METHOD) { - CFGMethod method = (CFGMethod) underlyingAST; - MethodTree methodTree = method.getMethod(); + // Furthermore, since the current object already exists, other objects may be guarded by the + // current object. So a synchronized method can affect the locking behavior of other + // objects. - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); + // A constructor/initializer behaves as if the current object and all its non-static fields + // were held as locks. But in reality no locks are held. - if (methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { - ClassTree classTree = method.getClassTree(); - TypeMirror classType = TreeUtils.typeOf(classTree); + // Furthermore, since the current object is being constructed, no other object can be + // guarded by it or any of its non-static fields. - if (methodElement.getModifiers().contains(Modifier.STATIC)) { - store.insertValue(new ClassName(classType), atypeFactory.LOCKHELD); - } else { - store.insertThisValue(atypeFactory.LOCKHELD, classType); + // Handle synchronized methods and constructors. + if (astKind == UnderlyingAST.Kind.METHOD) { + CFGMethod method = (CFGMethod) underlyingAST; + MethodTree methodTree = method.getMethod(); + + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); + + if (methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { + ClassTree classTree = method.getClassTree(); + TypeMirror classType = TreeUtils.typeOf(classTree); + + if (methodElement.getModifiers().contains(Modifier.STATIC)) { + store.insertValue(new ClassName(classType), atypeFactory.LOCKHELD); + } else { + store.insertThisValue(atypeFactory.LOCKHELD, classType); + } + } else if (methodElement.getKind() == ElementKind.CONSTRUCTOR) { + store.setInConstructorOrInitializer(); + } + } else if (astKind == Kind.ARBITRARY_CODE) { // Handle initializers + store.setInConstructorOrInitializer(); } - } else if (methodElement.getKind() == ElementKind.CONSTRUCTOR) { - store.setInConstructorOrInitializer(); - } - } else if (astKind == Kind.ARBITRARY_CODE) { // Handle initializers - store.setInConstructorOrInitializer(); + + return store; } - return store; - } + @Override + public TransferResult visitSynchronized( + SynchronizedNode n, TransferInput p) { - @Override - public TransferResult visitSynchronized( - SynchronizedNode n, TransferInput p) { + TransferResult result = super.visitSynchronized(n, p); - TransferResult result = super.visitSynchronized(n, p); + // Handle the entering and leaving of the synchronized block + if (n.getIsStartOfBlock()) { + makeLockHeld(result, n.getExpression()); + } else { + makeLockPossiblyHeld(result, n.getExpression()); + } - // Handle the entering and leaving of the synchronized block - if (n.getIsStartOfBlock()) { - makeLockHeld(result, n.getExpression()); - } else { - makeLockPossiblyHeld(result, n.getExpression()); + return result; } - - return result; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java index 5030b5f20e1..03ab5dbc12c 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockTreeAnnotator.java @@ -3,6 +3,7 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.NewArrayTree; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; @@ -15,45 +16,45 @@ */ public class LockTreeAnnotator extends TreeAnnotator { - public LockTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + public LockTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + // For any binary operation whose LHS or RHS can be a non-boolean type, and whose resulting + // type is necessarily boolean, the resulting annotation on the boolean type must be + // @GuardedBy({}). - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - // For any binary operation whose LHS or RHS can be a non-boolean type, and whose resulting - // type is necessarily boolean, the resulting annotation on the boolean type must be - // @GuardedBy({}). + // There is no need to enforce that the annotation on the result of &&, ||, etc. is + // @GuardedBy({}) since for such operators, both operands are of type @GuardedBy({}) boolean + // to begin with. - // There is no need to enforce that the annotation on the result of &&, ||, etc. is - // @GuardedBy({}) since for such operators, both operands are of type @GuardedBy({}) boolean - // to begin with. + if (TreeUtils.isBinaryComparison(tree) || TypesUtils.isString(type.getUnderlyingType())) { + // A boolean or String is always @GuardedBy({}). LockVisitor determines whether + // the LHS and RHS of this operation can be legally dereferenced. + type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); - if (TreeUtils.isBinaryComparison(tree) || TypesUtils.isString(type.getUnderlyingType())) { - // A boolean or String is always @GuardedBy({}). LockVisitor determines whether - // the LHS and RHS of this operation can be legally dereferenced. - type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); + return null; + } - return null; + return super.visitBinary(tree, type); } - return super.visitBinary(tree, type); - } + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TypesUtils.isString(type.getUnderlyingType())) { + type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); + } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TypesUtils.isString(type.getUnderlyingType())) { - type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY); + return super.visitCompoundAssignment(tree, type); } - return super.visitCompoundAssignment(tree, type); - } - - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT)) { - type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT); + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT)) { + type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).NEWOBJECT); + } + return super.visitNewArray(tree, type); } - return super.visitNewArray(tree, type); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java index a046bc81b14..6c8f71dcf9c 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java @@ -14,21 +14,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.lock.LockAnnotatedTypeFactory.SideEffectAnnotation; import org.checkerframework.checker.lock.qual.EnsuresLockHeld; @@ -63,6 +49,23 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * The LockVisitor enforces the special type-checking rules described in the Lock Checker manual * chapter. @@ -70,1219 +73,1284 @@ * @checker_framework.manual #lock-checker Lock Checker */ public class LockVisitor extends BaseTypeVisitor { - /** The class of GuardedBy */ - private static final Class checkerGuardedByClass = GuardedBy.class; - - /** The class of GuardSatisfied */ - private static final Class checkerGuardSatisfiedClass = - GuardSatisfied.class; - - /** A pattern for spotting self receiver */ - protected static final Pattern SELF_RECEIVER_PATTERN = Pattern.compile("^(\\.(.*))?$"); - - /** - * Constructs a {@link LockVisitor}. - * - * @param checker the type checker to use - */ - public LockVisitor(BaseTypeChecker checker) { - super(checker); - } - - @Override - public Void visitVariable(VariableTree tree, Void p) { // visit a variable declaration - // A user may not annotate a primitive type, a boxed primitive type or a String - // with any qualifier from the @GuardedBy hierarchy. - // They are immutable, so there is no need to guard them. - - TypeMirror tm = TreeUtils.typeOf(tree); - - if (TypesUtils.isBoxedPrimitive(tm) || TypesUtils.isPrimitive(tm) || TypesUtils.isString(tm)) { - AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(tree); - if (atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDSATISFIED) - || atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDEDBY) - || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYUNKNOWN) - || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYBOTTOM)) { - checker.reportError(tree, "immutable.type.guardedby"); - } + /** The class of GuardedBy */ + private static final Class checkerGuardedByClass = GuardedBy.class; + + /** The class of GuardSatisfied */ + private static final Class checkerGuardSatisfiedClass = + GuardSatisfied.class; + + /** A pattern for spotting self receiver */ + protected static final Pattern SELF_RECEIVER_PATTERN = Pattern.compile("^(\\.(.*))?$"); + + /** + * Constructs a {@link LockVisitor}. + * + * @param checker the type checker to use + */ + public LockVisitor(BaseTypeChecker checker) { + super(checker); } - issueErrorIfMoreThanOneGuardedByAnnotationPresent(tree); - - return super.visitVariable(tree, p); - } - - /** - * Issues an error if two or more of the following annotations are present on a variable - * declaration. - * - *

            - *
          • {@code @org.checkerframework.checker.lock.qual.GuardedBy} - *
          • {@code @net.jcip.annotations.GuardedBy} - *
          • {@code @javax.annotation.concurrent.GuardedBy} - *
          - * - * @param variableTree the VariableTree for the variable declaration used to determine if - * multiple @GuardedBy annotations are present and to report the error - */ - private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) { - int guardedByAnnotationCount = 0; - - List annos = - TreeUtils.annotationsFromTypeAnnotationTrees(variableTree.getModifiers().getAnnotations()); - for (AnnotationMirror anno : annos) { - if (atypeFactory.areSameByClass(anno, GuardedBy.class) - || AnnotationUtils.areSameByName(anno, "net.jcip.annotations.GuardedBy") - || AnnotationUtils.areSameByName(anno, "javax.annotation.concurrent.GuardedBy")) { - guardedByAnnotationCount++; - if (guardedByAnnotationCount > 1) { - checker.reportError(variableTree, "multiple.guardedby.annotations"); - return; + @Override + public Void visitVariable(VariableTree tree, Void p) { // visit a variable declaration + // A user may not annotate a primitive type, a boxed primitive type or a String + // with any qualifier from the @GuardedBy hierarchy. + // They are immutable, so there is no need to guard them. + + TypeMirror tm = TreeUtils.typeOf(tree); + + if (TypesUtils.isBoxedPrimitive(tm) + || TypesUtils.isPrimitive(tm) + || TypesUtils.isString(tm)) { + AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(tree); + if (atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDSATISFIED) + || atm.hasExplicitAnnotationRelaxed(atypeFactory.GUARDEDBY) + || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYUNKNOWN) + || atm.hasExplicitAnnotation(atypeFactory.GUARDEDBYBOTTOM)) { + checker.reportError(tree, "immutable.type.guardedby"); + } } - } + + issueErrorIfMoreThanOneGuardedByAnnotationPresent(tree); + + return super.visitVariable(tree, p); } - } - - @Override - public LockAnnotatedTypeFactory createTypeFactory() { - return new LockAnnotatedTypeFactory(checker); - } - - /** - * Issues an error if a method (explicitly or implicitly) annotated with @MayReleaseLocks has a - * formal parameter or receiver (explicitly or implicitly) annotated with @GuardSatisfied. Also - * issues an error if a synchronized method has a @LockingFree, @SideEffectFree, or @Pure - * annotation. - * - * @param tree the MethodTree of the method definition to visit - */ - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); - - issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, tree); - - SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true); - - if (sea == SideEffectAnnotation.MAYRELEASELOCKS) { - boolean issueGSwithMRLWarning = false; - - VariableTree receiver = tree.getReceiverParameter(); - if (receiver != null) { - if (atypeFactory.getAnnotatedType(receiver).hasAnnotation(checkerGuardSatisfiedClass)) { - issueGSwithMRLWarning = true; - } - } - - if (!issueGSwithMRLWarning) { // Skip loop if we already decided to issue the warning. - for (VariableTree vt : tree.getParameters()) { - if (atypeFactory.getAnnotatedType(vt).hasAnnotation(checkerGuardSatisfiedClass)) { - issueGSwithMRLWarning = true; - break; - } + + /** + * Issues an error if two or more of the following annotations are present on a variable + * declaration. + * + *
            + *
          • {@code @org.checkerframework.checker.lock.qual.GuardedBy} + *
          • {@code @net.jcip.annotations.GuardedBy} + *
          • {@code @javax.annotation.concurrent.GuardedBy} + *
          + * + * @param variableTree the VariableTree for the variable declaration used to determine if + * multiple @GuardedBy annotations are present and to report the error + */ + private void issueErrorIfMoreThanOneGuardedByAnnotationPresent(VariableTree variableTree) { + int guardedByAnnotationCount = 0; + + List annos = + TreeUtils.annotationsFromTypeAnnotationTrees( + variableTree.getModifiers().getAnnotations()); + for (AnnotationMirror anno : annos) { + if (atypeFactory.areSameByClass(anno, GuardedBy.class) + || AnnotationUtils.areSameByName(anno, "net.jcip.annotations.GuardedBy") + || AnnotationUtils.areSameByName( + anno, "javax.annotation.concurrent.GuardedBy")) { + guardedByAnnotationCount++; + if (guardedByAnnotationCount > 1) { + checker.reportError(variableTree, "multiple.guardedby.annotations"); + return; + } + } } - } + } - if (issueGSwithMRLWarning) { - checker.reportError(tree, "guardsatisfied.with.mayreleaselocks"); - } + @Override + public LockAnnotatedTypeFactory createTypeFactory() { + return new LockAnnotatedTypeFactory(checker); } - // Issue an error if a non-constructor method definition has a return type of - // @GuardSatisfied without an index. - if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) { - AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(tree).getReturnType(); + /** + * Issues an error if a method (explicitly or implicitly) annotated with @MayReleaseLocks has a + * formal parameter or receiver (explicitly or implicitly) annotated with @GuardSatisfied. Also + * issues an error if a synchronized method has a @LockingFree, @SideEffectFree, or @Pure + * annotation. + * + * @param tree the MethodTree of the method definition to visit + */ + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); + + issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent(methodElement, tree); + + SideEffectAnnotation sea = atypeFactory.methodSideEffectAnnotation(methodElement, true); + + if (sea == SideEffectAnnotation.MAYRELEASELOCKS) { + boolean issueGSwithMRLWarning = false; + + VariableTree receiver = tree.getReceiverParameter(); + if (receiver != null) { + if (atypeFactory + .getAnnotatedType(receiver) + .hasAnnotation(checkerGuardSatisfiedClass)) { + issueGSwithMRLWarning = true; + } + } - if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) { - int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM); + if (!issueGSwithMRLWarning) { // Skip loop if we already decided to issue the warning. + for (VariableTree vt : tree.getParameters()) { + if (atypeFactory + .getAnnotatedType(vt) + .hasAnnotation(checkerGuardSatisfiedClass)) { + issueGSwithMRLWarning = true; + break; + } + } + } - if (returnGuardSatisfiedIndex == -1) { - checker.reportError(tree, "guardsatisfied.return.must.have.index"); + if (issueGSwithMRLWarning) { + checker.reportError(tree, "guardsatisfied.with.mayreleaselocks"); + } } - } - } - if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE) - && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { - checker.reportError(tree, "lockingfree.synchronized.method", sea); - } + // Issue an error if a non-constructor method definition has a return type of + // @GuardSatisfied without an index. + if (methodElement != null && methodElement.getKind() != ElementKind.CONSTRUCTOR) { + AnnotatedTypeMirror returnTypeATM = atypeFactory.getAnnotatedType(tree).getReturnType(); + + if (returnTypeATM != null && returnTypeATM.hasAnnotation(GuardSatisfied.class)) { + int returnGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(returnTypeATM); + + if (returnGuardSatisfiedIndex == -1) { + checker.reportError(tree, "guardsatisfied.return.must.have.index"); + } + } + } + + if (!sea.isWeakerThan(SideEffectAnnotation.LOCKINGFREE) + && methodElement.getModifiers().contains(Modifier.SYNCHRONIZED)) { + checker.reportError(tree, "lockingfree.synchronized.method", sea); + } - return super.visitMethod(tree, p); - } - - /** - * Issues an error if two or more of the following annotations are present on a method. - * - *
            - *
          • {@code @Holding} - *
          • {@code @net.jcip.annotations.GuardedBy} - *
          • {@code @javax.annotation.concurrent.GuardedBy} - *
          - * - * @param methodElement the ExecutableElement for the method call referred to by {@code tree} - * @param treeForErrorReporting the MethodTree used to report the error - */ - private void issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent( - ExecutableElement methodElement, MethodTree treeForErrorReporting) { - int lockPreconditionAnnotationCount = 0; - - if (atypeFactory.getDeclAnnotation(methodElement, Holding.class) != null) { - lockPreconditionAnnotationCount++; + return super.visitMethod(tree, p); } - try { - if (atypeFactory.jcipGuardedBy != null - && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.jcipGuardedBy) != null) { - lockPreconditionAnnotationCount++; - } - - if (lockPreconditionAnnotationCount < 2 - && atypeFactory.javaxGuardedBy != null - && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.javaxGuardedBy) != null) { - lockPreconditionAnnotationCount++; - } - } catch (Exception e) { - // Ignore exceptions from Class.forName + /** + * Issues an error if two or more of the following annotations are present on a method. + * + *
            + *
          • {@code @Holding} + *
          • {@code @net.jcip.annotations.GuardedBy} + *
          • {@code @javax.annotation.concurrent.GuardedBy} + *
          + * + * @param methodElement the ExecutableElement for the method call referred to by {@code tree} + * @param treeForErrorReporting the MethodTree used to report the error + */ + private void issueErrorIfMoreThanOneLockPreconditionMethodAnnotationPresent( + ExecutableElement methodElement, MethodTree treeForErrorReporting) { + int lockPreconditionAnnotationCount = 0; + + if (atypeFactory.getDeclAnnotation(methodElement, Holding.class) != null) { + lockPreconditionAnnotationCount++; + } + + try { + if (atypeFactory.jcipGuardedBy != null + && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.jcipGuardedBy) + != null) { + lockPreconditionAnnotationCount++; + } + + if (lockPreconditionAnnotationCount < 2 + && atypeFactory.javaxGuardedBy != null + && atypeFactory.getDeclAnnotation(methodElement, atypeFactory.javaxGuardedBy) + != null) { + lockPreconditionAnnotationCount++; + } + } catch (Exception e) { + // Ignore exceptions from Class.forName + } + + if (lockPreconditionAnnotationCount > 1) { + checker.reportError(treeForErrorReporting, "multiple.lock.precondition.annotations"); + } } - if (lockPreconditionAnnotationCount > 1) { - checker.reportError(treeForErrorReporting, "multiple.lock.precondition.annotations"); + /** + * When visiting a method call, if the receiver formal parameter has type @GuardSatisfied and + * the receiver actual parameter has type @GuardedBy(...), this method verifies that the guard + * is satisfied, and it returns true, indicating that the receiver subtype check should be + * skipped. If the receiver actual parameter has type @GuardSatisfied, this method simply + * returns true without performing any other actions. The method returns false otherwise. + * + * @param methodInvocationTree the MethodInvocationTree of the method being called + * @param methodDefinitionReceiver the ATM of the formal receiver parameter of the method being + * called + * @param methodCallReceiver the ATM of the receiver argument of the method call + * @return whether the caller can skip the receiver subtype check + */ + @Override + protected boolean skipReceiverSubtypeCheck( + MethodInvocationTree methodInvocationTree, + AnnotatedTypeMirror methodDefinitionReceiver, + AnnotatedTypeMirror methodCallReceiver) { + + AnnotationMirror primaryGb = + methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + AnnotationMirror effectiveGb = + methodCallReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + + // If the receiver actual parameter has type @GuardSatisfied, skip the subtype check. + // Consider only a @GuardSatisfied primary annotation - hence use primaryGb instead of + // effectiveGb. + if (primaryGb != null + && atypeFactory.areSameByClass(primaryGb, checkerGuardSatisfiedClass)) { + AnnotationMirror primaryGbOnMethodDefinition = + methodDefinitionReceiver.getAnnotationInHierarchy( + atypeFactory.GUARDEDBYUNKNOWN); + if (primaryGbOnMethodDefinition != null + && atypeFactory.areSameByClass( + primaryGbOnMethodDefinition, checkerGuardSatisfiedClass)) { + return true; + } + } + + if (atypeFactory.areSameByClass(effectiveGb, checkerGuardedByClass)) { + AnnotationMirrorSet annos = methodDefinitionReceiver.getAnnotations(); + AnnotationMirror guardSatisfied = + atypeFactory.getAnnotationByClass(annos, checkerGuardSatisfiedClass); + if (guardSatisfied != null) { + ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); + if (receiverTree == null) { + checkLockOfImplicitThis(methodInvocationTree, effectiveGb); + } else { + checkLock(receiverTree, effectiveGb); + } + return true; + } + } + + return false; } - } - - /** - * When visiting a method call, if the receiver formal parameter has type @GuardSatisfied and the - * receiver actual parameter has type @GuardedBy(...), this method verifies that the guard is - * satisfied, and it returns true, indicating that the receiver subtype check should be skipped. - * If the receiver actual parameter has type @GuardSatisfied, this method simply returns true - * without performing any other actions. The method returns false otherwise. - * - * @param methodInvocationTree the MethodInvocationTree of the method being called - * @param methodDefinitionReceiver the ATM of the formal receiver parameter of the method being - * called - * @param methodCallReceiver the ATM of the receiver argument of the method call - * @return whether the caller can skip the receiver subtype check - */ - @Override - protected boolean skipReceiverSubtypeCheck( - MethodInvocationTree methodInvocationTree, - AnnotatedTypeMirror methodDefinitionReceiver, - AnnotatedTypeMirror methodCallReceiver) { - - AnnotationMirror primaryGb = - methodCallReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - AnnotationMirror effectiveGb = - methodCallReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - - // If the receiver actual parameter has type @GuardSatisfied, skip the subtype check. - // Consider only a @GuardSatisfied primary annotation - hence use primaryGb instead of - // effectiveGb. - if (primaryGb != null && atypeFactory.areSameByClass(primaryGb, checkerGuardSatisfiedClass)) { - AnnotationMirror primaryGbOnMethodDefinition = - methodDefinitionReceiver.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - if (primaryGbOnMethodDefinition != null - && atypeFactory.areSameByClass(primaryGbOnMethodDefinition, checkerGuardSatisfiedClass)) { - return true; - } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); + for (AnnotationMirror anno : tops) { + if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN)) { + annotationSet.add(atypeFactory.GUARDEDBY); + } else { + annotationSet.add(anno); + } + } + return annotationSet; } - if (atypeFactory.areSameByClass(effectiveGb, checkerGuardedByClass)) { - AnnotationMirrorSet annos = methodDefinitionReceiver.getAnnotations(); - AnnotationMirror guardSatisfied = - atypeFactory.getAnnotationByClass(annos, checkerGuardSatisfiedClass); - if (guardSatisfied != null) { - ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); - if (receiverTree == null) { - checkLockOfImplicitThis(methodInvocationTree, effectiveGb); - } else { - checkLock(receiverTree, effectiveGb); + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Newly created objects are guarded by nothing, so allow @GuardedBy({}) on constructor + // results. + AnnotationMirror anno = + constructorType + .getReturnType() + .getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN) + || AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYBOTTOM)) { + checker.reportWarning(constructorElement, "inconsistent.constructor.type", anno, null); } - return true; - } } - return false; - } - - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); - for (AnnotationMirror anno : tops) { - if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN)) { - annotationSet.add(atypeFactory.GUARDEDBY); - } else { - annotationSet.add(anno); - } + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + // In cases where assigning a value with a @GuardedBy annotation to a variable with a + // @GuardSatisfied annotation is legal, this is our last chance to check that the + // appropriate locks are held before the information in the @GuardedBy annotation is lost in + // the assignment to the variable annotated with @GuardSatisfied. See the discussion of + // @GuardSatisfied in the "Type-checking rules" section of the Lock Checker manual chapter + // for more details. + + boolean result = true; + if (varType.hasAnnotation(GuardSatisfied.class)) { + if (valueType.hasAnnotation(GuardedBy.class)) { + return checkLock(valueTree, valueType.getAnnotation(GuardedBy.class)); + } else if (valueType.hasAnnotation(GuardSatisfied.class)) { + // TODO: Find a cleaner, non-abstraction-breaking way to know whether method actual + // parameters are being assigned to formal parameters. + + if (!errorKey.equals("argument.type.incompatible")) { + // If both @GuardSatisfied have no index, the assignment is not allowed because + // the LHS and RHS expressions may be guarded by different lock expressions. + // The assignment is allowed when matching a formal parameter to an actual + // parameter (see the if block above). + + int varTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(varType); + int valueTypeGuardSatisfiedIndex = + atypeFactory.getGuardSatisfiedIndex(valueType); + + if (varTypeGuardSatisfiedIndex == -1 && valueTypeGuardSatisfiedIndex == -1) { + checker.reportError( + valueTree, + "guardsatisfied.assignment.disallowed", + varType, + valueType); + result = false; + } + } else { + // The RHS can be @GuardSatisfied with a different index when matching method + // formal parameters to actual parameters. + // The actual matching is done in LockVisitor.visitMethodInvocation and a + // guardsatisfied.parameters.must.match error + // is issued if the parameters do not match exactly. + // Do nothing here, since there is no precondition to be checked on a + // @GuardSatisfied parameter. + // Note: this matching of a @GS(index) to a @GS(differentIndex) is *only* + // allowed when matching method formal parameters to actual parameters. + + return true; + } + } else if (!atypeFactory.getTypeHierarchy().isSubtype(valueType, varType)) { + // Special case: replace the @GuardSatisfied primary annotation on the LHS with + // @GuardedBy({}) and see if it type checks. + + AnnotatedTypeMirror varType2 = + varType.deepCopy(); // TODO: Would shallowCopy be sufficient? + varType2.replaceAnnotation(atypeFactory.GUARDEDBY); + if (atypeFactory.getTypeHierarchy().isSubtype(valueType, varType2)) { + return true; + } + } + } + + result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) + && result; + return result; } - return annotationSet; - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Newly created objects are guarded by nothing, so allow @GuardedBy({}) on constructor - // results. - AnnotationMirror anno = - constructorType.getReturnType().getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - if (AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYUNKNOWN) - || AnnotationUtils.areSame(anno, atypeFactory.GUARDEDBYBOTTOM)) { - checker.reportWarning(constructorElement, "inconsistent.constructor.type", anno, null); + + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + if (TreeUtils.isFieldAccess(tree)) { + AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); + // The atmOfReceiver for "void.class" is TypeKind.VOID, which isn't annotated so avoid + // it. + if (atmOfReceiver.getKind() != TypeKind.VOID) { + AnnotationMirror gb = + atmOfReceiver.getEffectiveAnnotationInHierarchy( + atypeFactory.GUARDEDBYUNKNOWN); + checkLock(tree.getExpression(), gb); + } + } + + return super.visitMemberSelect(tree, p); } - } - - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - // In cases where assigning a value with a @GuardedBy annotation to a variable with a - // @GuardSatisfied annotation is legal, this is our last chance to check that the - // appropriate locks are held before the information in the @GuardedBy annotation is lost in - // the assignment to the variable annotated with @GuardSatisfied. See the discussion of - // @GuardSatisfied in the "Type-checking rules" section of the Lock Checker manual chapter - // for more details. - - boolean result = true; - if (varType.hasAnnotation(GuardSatisfied.class)) { - if (valueType.hasAnnotation(GuardedBy.class)) { - return checkLock(valueTree, valueType.getAnnotation(GuardedBy.class)); - } else if (valueType.hasAnnotation(GuardSatisfied.class)) { - // TODO: Find a cleaner, non-abstraction-breaking way to know whether method actual - // parameters are being assigned to formal parameters. - - if (!errorKey.equals("argument.type.incompatible")) { - // If both @GuardSatisfied have no index, the assignment is not allowed because - // the LHS and RHS expressions may be guarded by different lock expressions. - // The assignment is allowed when matching a formal parameter to an actual - // parameter (see the if block above). - - int varTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(varType); - int valueTypeGuardSatisfiedIndex = atypeFactory.getGuardSatisfiedIndex(valueType); - - if (varTypeGuardSatisfiedIndex == -1 && valueTypeGuardSatisfiedIndex == -1) { + + private void reportFailure( + @CompilerMessageKey String messageKey, + MethodTree overriderTree, + AnnotatedDeclaredType enclosingType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType, + List overriderLocks, + List overriddenLocks) { + // Get the type of the overriding method. + AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree); + + if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) { + overridden = overridden.getErased(); + } + String overriderMeth = overrider.toString(); + String overriderTyp = enclosingType.getUnderlyingType().asElement().toString(); + String overriddenMeth = overridden.toString(); + String overriddenTyp = overriddenType.getUnderlyingType().asElement().toString(); + + if (overriderLocks == null || overriddenLocks == null) { checker.reportError( - valueTree, "guardsatisfied.assignment.disallowed", varType, valueType); - result = false; - } + overriderTree, + messageKey, + overriderTyp, + overriderMeth, + overriddenTyp, + overriddenMeth); } else { - // The RHS can be @GuardSatisfied with a different index when matching method - // formal parameters to actual parameters. - // The actual matching is done in LockVisitor.visitMethodInvocation and a - // guardsatisfied.parameters.must.match error - // is issued if the parameters do not match exactly. - // Do nothing here, since there is no precondition to be checked on a - // @GuardSatisfied parameter. - // Note: this matching of a @GS(index) to a @GS(differentIndex) is *only* - // allowed when matching method formal parameters to actual parameters. - - return true; + checker.reportError( + overriderTree, + messageKey, + overriderTyp, + overriderMeth, + overriddenTyp, + overriddenMeth, + overriderLocks, + overriddenLocks); } - } else if (!atypeFactory.getTypeHierarchy().isSubtype(valueType, varType)) { - // Special case: replace the @GuardSatisfied primary annotation on the LHS with - // @GuardedBy({}) and see if it type checks. - - AnnotatedTypeMirror varType2 = varType.deepCopy(); // TODO: Would shallowCopy be sufficient? - varType2.replaceAnnotation(atypeFactory.GUARDEDBY); - if (atypeFactory.getTypeHierarchy().isSubtype(valueType, varType2)) { - return true; + } + + /** + * Ensures that subclass methods are annotated with a stronger or equally strong side effect + * annotation than the parent class method. + */ + @Override + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedDeclaredType enclosingType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType) { + + boolean isValid = true; + + SideEffectAnnotation seaOfOverriderMethod = + atypeFactory.methodSideEffectAnnotation( + TreeUtils.elementFromDeclaration(overriderTree), false); + SideEffectAnnotation seaOfOverriddenMethod = + atypeFactory.methodSideEffectAnnotation(overriddenMethodType.getElement(), false); + + if (seaOfOverriderMethod.isWeakerThan(seaOfOverriddenMethod)) { + isValid = false; + reportFailure( + "override.sideeffect.invalid", + overriderTree, + enclosingType, + overriddenMethodType, + overriddenType, + null, + null); } - } + + return super.checkOverride( + overriderTree, enclosingType, overriddenMethodType, overriddenType) + && isValid; } - result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs) && result; - return result; - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - if (TreeUtils.isFieldAccess(tree)) { - AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); - // The atmOfReceiver for "void.class" is TypeKind.VOID, which isn't annotated so avoid - // it. - if (atmOfReceiver.getKind() != TypeKind.VOID) { + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); AnnotationMirror gb = - atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); + atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); checkLock(tree.getExpression(), gb); - } + return super.visitArrayAccess(tree, p); } - return super.visitMemberSelect(tree, p); - } - - private void reportFailure( - @CompilerMessageKey String messageKey, - MethodTree overriderTree, - AnnotatedDeclaredType enclosingType, - AnnotatedExecutableType overridden, - AnnotatedDeclaredType overriddenType, - List overriderLocks, - List overriddenLocks) { - // Get the type of the overriding method. - AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree); - - if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) { - overridden = overridden.getErased(); - } - String overriderMeth = overrider.toString(); - String overriderTyp = enclosingType.getUnderlyingType().asElement().toString(); - String overriddenMeth = overridden.toString(); - String overriddenTyp = overriddenType.getUnderlyingType().asElement().toString(); - - if (overriderLocks == null || overriddenLocks == null) { - checker.reportError( - overriderTree, messageKey, overriderTyp, overriderMeth, overriddenTyp, overriddenMeth); - } else { - checker.reportError( - overriderTree, - messageKey, - overriderTyp, - overriderMeth, - overriddenTyp, - overriddenMeth, - overriderLocks, - overriddenLocks); - } - } - - /** - * Ensures that subclass methods are annotated with a stronger or equally strong side effect - * annotation than the parent class method. - */ - @Override - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedDeclaredType enclosingType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType) { - - boolean isValid = true; - - SideEffectAnnotation seaOfOverriderMethod = - atypeFactory.methodSideEffectAnnotation( - TreeUtils.elementFromDeclaration(overriderTree), false); - SideEffectAnnotation seaOfOverriddenMethod = - atypeFactory.methodSideEffectAnnotation(overriddenMethodType.getElement(), false); - - if (seaOfOverriderMethod.isWeakerThan(seaOfOverriddenMethod)) { - isValid = false; - reportFailure( - "override.sideeffect.invalid", - overriderTree, - enclosingType, - overriddenMethodType, - overriddenType, - null, - null); + /** + * Skips the call to super and returns true. + * + *

          {@code GuardedBy({})} is the default type on class declarations, which is a subtype of the + * top annotation {@code @GuardedByUnknown}. However, it is valid to declare an instance of a + * class with any annotation from the {@code @GuardedBy} hierarchy. Hence, this method returns + * true for annotations in the {@code @GuardedBy} hierarchy. + * + *

          Also returns true for annotations in the {@code @LockPossiblyHeld} hierarchy since the + * default for that hierarchy is the top type and annotations from that hierarchy cannot be + * explicitly written in code. + */ + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + return true; } - return super.checkOverride(overriderTree, enclosingType, overriddenMethodType, overriddenType) - && isValid; - } - - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - AnnotatedTypeMirror atmOfReceiver = atypeFactory.getAnnotatedType(tree.getExpression()); - AnnotationMirror gb = - atmOfReceiver.getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN); - checkLock(tree.getExpression(), gb); - return super.visitArrayAccess(tree, p); - } - - /** - * Skips the call to super and returns true. - * - *

          {@code GuardedBy({})} is the default type on class declarations, which is a subtype of the - * top annotation {@code @GuardedByUnknown}. However, it is valid to declare an instance of a - * class with any annotation from the {@code @GuardedBy} hierarchy. Hence, this method returns - * true for annotations in the {@code @GuardedBy} hierarchy. - * - *

          Also returns true for annotations in the {@code @LockPossiblyHeld} hierarchy since the - * default for that hierarchy is the top type and annotations from that hierarchy cannot be - * explicitly written in code. - */ - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - return true; - } - - /** - * When visiting a method invocation, issue an error if the side effect annotation on the called - * method causes the side effect guarantee of the enclosing method to be violated. For example, a - * method annotated with @ReleasesNoLocks may not call a method annotated with @MayReleaseLocks. - * Also check that matching @GuardSatisfied(index) on a method's formal receiver/parameters - * matches those in corresponding locations on the method call site. - * - * @param methodInvocationTree the MethodInvocationTree of the method call being visited - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree methodInvocationTree, Void p) { - ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); - - SideEffectAnnotation seaOfInvokedMethod = - atypeFactory.methodSideEffectAnnotation(methodElement, false); - - MethodTree enclosingMethod = - TreePathUtil.enclosingMethod(atypeFactory.getPath(methodInvocationTree)); - - ExecutableElement enclosingMethodElement = null; - if (enclosingMethod != null) { - enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); - } + /** + * When visiting a method invocation, issue an error if the side effect annotation on the called + * method causes the side effect guarantee of the enclosing method to be violated. For example, + * a method annotated with @ReleasesNoLocks may not call a method annotated + * with @MayReleaseLocks. Also check that matching @GuardSatisfied(index) on a method's formal + * receiver/parameters matches those in corresponding locations on the method call site. + * + * @param methodInvocationTree the MethodInvocationTree of the method call being visited + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree methodInvocationTree, Void p) { + ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); + + SideEffectAnnotation seaOfInvokedMethod = + atypeFactory.methodSideEffectAnnotation(methodElement, false); + + MethodTree enclosingMethod = + TreePathUtil.enclosingMethod(atypeFactory.getPath(methodInvocationTree)); + + ExecutableElement enclosingMethodElement = null; + if (enclosingMethod != null) { + enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); + } - if (enclosingMethodElement != null) { - SideEffectAnnotation seaOfContainingMethod = - atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false); - - if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) { - checker.reportError( - methodInvocationTree, - "method.guarantee.violated", - seaOfContainingMethod.getNameOfSideEffectAnnotation(), - enclosingMethodElement.getSimpleName(), - methodElement.getSimpleName(), - seaOfInvokedMethod.getNameOfSideEffectAnnotation()); - } - } + if (enclosingMethodElement != null) { + SideEffectAnnotation seaOfContainingMethod = + atypeFactory.methodSideEffectAnnotation(enclosingMethodElement, false); - if (methodElement != null) { - // Handle releasing of explicit locks. Verify that the lock expression is effectively - // final. - ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); - - ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(methodElement, receiverTree); - - // Handle acquiring of explicit locks. Verify that the lock expression is effectively - // final. - - // If the method causes expression "this" or "#1" to be locked, verify that those - // expressions are effectively final. TODO: generalize to any expression. This is - // currently designed only to support methods in ReentrantLock and - // ReentrantReadWriteLock (which use the "this" expression), as well as Thread.holdsLock - // (which uses the "#1" expression). - - AnnotationMirror ensuresLockHeldAnno = - atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class); - - List expressions = new ArrayList<>(); - if (ensuresLockHeldAnno != null) { - expressions.addAll( - AnnotationUtils.getElementValueArray( - ensuresLockHeldAnno, atypeFactory.ensuresLockHeldValueElement, String.class)); - } - - AnnotationMirror ensuresLockHeldIfAnno = - atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class); - - if (ensuresLockHeldIfAnno != null) { - expressions.addAll( - AnnotationUtils.getElementValueArray( - ensuresLockHeldIfAnno, - atypeFactory.ensuresLockHeldIfExpressionElement, - String.class)); - } - - for (String expr : expressions) { - if (expr.equals("this")) { - // receiverTree will be null for implicit this, or class name receivers. But - // they are also final. So nothing to be checked for them. - if (receiverTree != null) { - ensureExpressionIsEffectivelyFinal(receiverTree); - } - } else if (expr.equals("#1")) { - ExpressionTree firstParameter = methodInvocationTree.getArguments().get(0); - if (firstParameter != null) { - ensureExpressionIsEffectivelyFinal(firstParameter); - } + if (seaOfInvokedMethod.isWeakerThan(seaOfContainingMethod)) { + checker.reportError( + methodInvocationTree, + "method.guarantee.violated", + seaOfContainingMethod.getNameOfSideEffectAnnotation(), + enclosingMethodElement.getSimpleName(), + methodElement.getSimpleName(), + seaOfInvokedMethod.getNameOfSideEffectAnnotation()); + } } - } - } - // Check that matching @GuardSatisfied(index) on a method's formal receiver/parameters - // matches those in corresponding locations on the method call site. + if (methodElement != null) { + // Handle releasing of explicit locks. Verify that the lock expression is effectively + // final. + ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvocationTree); - ParameterizedExecutableType mType = atypeFactory.methodFromUse(methodInvocationTree); - AnnotatedExecutableType invokedMethod = mType.executableType; + ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal(methodElement, receiverTree); - List paramTypes = invokedMethod.getParameterTypes(); + // Handle acquiring of explicit locks. Verify that the lock expression is effectively + // final. - // Index on @GuardSatisfied at each location. -1 when no @GuardSatisfied annotation was - // present. - // Note that @GuardSatisfied with no index is normally represented as having index -1. - // We would like to ignore a @GuardSatisfied with no index for these purposes, so if it is - // encountered we leave its index as -1. - // The first element of the array is reserved for the receiver. - int guardSatisfiedIndex[] = - new int[paramTypes.size() + 1]; // + 1 for the receiver parameter type + // If the method causes expression "this" or "#1" to be locked, verify that those + // expressions are effectively final. TODO: generalize to any expression. This is + // currently designed only to support methods in ReentrantLock and + // ReentrantReadWriteLock (which use the "this" expression), as well as Thread.holdsLock + // (which uses the "#1" expression). - // Retrieve receiver types from method definition and method call + AnnotationMirror ensuresLockHeldAnno = + atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeld.class); - guardSatisfiedIndex[0] = -1; + List expressions = new ArrayList<>(); + if (ensuresLockHeldAnno != null) { + expressions.addAll( + AnnotationUtils.getElementValueArray( + ensuresLockHeldAnno, + atypeFactory.ensuresLockHeldValueElement, + String.class)); + } - AnnotatedTypeMirror methodDefinitionReceiver = null; - AnnotatedTypeMirror methodCallReceiver = null; + AnnotationMirror ensuresLockHeldIfAnno = + atypeFactory.getDeclAnnotation(methodElement, EnsuresLockHeldIf.class); - ExecutableElement invokedMethodElement = invokedMethod.getElement(); - if (!ElementUtils.isStatic(invokedMethodElement) - && invokedMethod.getElement().getKind() != ElementKind.CONSTRUCTOR) { - methodDefinitionReceiver = invokedMethod.getReceiverType(); - if (methodDefinitionReceiver != null - && methodDefinitionReceiver.hasAnnotation(checkerGuardSatisfiedClass)) { - guardSatisfiedIndex[0] = atypeFactory.getGuardSatisfiedIndex(methodDefinitionReceiver); - methodCallReceiver = atypeFactory.getReceiverType(methodInvocationTree); - } - } + if (ensuresLockHeldIfAnno != null) { + expressions.addAll( + AnnotationUtils.getElementValueArray( + ensuresLockHeldIfAnno, + atypeFactory.ensuresLockHeldIfExpressionElement, + String.class)); + } - // Retrieve formal parameter types from the method definition. + for (String expr : expressions) { + if (expr.equals("this")) { + // receiverTree will be null for implicit this, or class name receivers. But + // they are also final. So nothing to be checked for them. + if (receiverTree != null) { + ensureExpressionIsEffectivelyFinal(receiverTree); + } + } else if (expr.equals("#1")) { + ExpressionTree firstParameter = methodInvocationTree.getArguments().get(0); + if (firstParameter != null) { + ensureExpressionIsEffectivelyFinal(firstParameter); + } + } + } + } - for (int i = 0; i < paramTypes.size(); i++) { - guardSatisfiedIndex[i + 1] = -1; + // Check that matching @GuardSatisfied(index) on a method's formal receiver/parameters + // matches those in corresponding locations on the method call site. - AnnotatedTypeMirror paramType = paramTypes.get(i); + ParameterizedExecutableType mType = atypeFactory.methodFromUse(methodInvocationTree); + AnnotatedExecutableType invokedMethod = mType.executableType; - if (paramType.hasAnnotation(checkerGuardSatisfiedClass)) { - guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(paramType); - } - } + List paramTypes = invokedMethod.getParameterTypes(); - // Combine all of the actual parameters into one list of AnnotationMirrors. + // Index on @GuardSatisfied at each location. -1 when no @GuardSatisfied annotation was + // present. + // Note that @GuardSatisfied with no index is normally represented as having index -1. + // We would like to ignore a @GuardSatisfied with no index for these purposes, so if it is + // encountered we leave its index as -1. + // The first element of the array is reserved for the receiver. + int guardSatisfiedIndex[] = + new int[paramTypes.size() + 1]; // + 1 for the receiver parameter type - ArrayList passedArgTypes = new ArrayList<>(guardSatisfiedIndex.length); - passedArgTypes.add(methodCallReceiver); - for (ExpressionTree argTree : methodInvocationTree.getArguments()) { - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argTree); - passedArgTypes.add(argType); - } - ArrayList passedArgAnnotations = new ArrayList<>(guardSatisfiedIndex.length); - for (AnnotatedTypeMirror atm : passedArgTypes) { - if (atm != null) { - passedArgAnnotations.add(atm.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN)); - } else { - passedArgAnnotations.add(null); - } - } + // Retrieve receiver types from method definition and method call - // Perform the validity check and issue an error if not valid. - - for (int i = 0; i < guardSatisfiedIndex.length; i++) { - if (guardSatisfiedIndex[i] != -1) { - for (int j = i + 1; j < guardSatisfiedIndex.length; j++) { - if (guardSatisfiedIndex[i] == guardSatisfiedIndex[j]) { - // The @GuardedBy/@GuardSatisfied/@GuardedByUnknown/@GuardedByBottom - // annotations must be identical on the corresponding actual parameters. - AnnotationMirror arg1Anno = passedArgAnnotations.get(i); - AnnotationMirror arg2Anno = passedArgAnnotations.get(j); - if (arg1Anno != null && arg2Anno != null) { - boolean bothAreGSwithNoIndex = false; - - if (atypeFactory.areSameByClass(arg1Anno, checkerGuardSatisfiedClass) - && atypeFactory.areSameByClass(arg2Anno, checkerGuardSatisfiedClass)) { - if (atypeFactory.getGuardSatisfiedIndex(arg1Anno) == -1 - && atypeFactory.getGuardSatisfiedIndex(arg2Anno) == -1) { - // Generally speaking, two @GuardSatisfied annotations with no - // index are incomparable. - // TODO: If they come from the same variable, they are - // comparable. Fix and add a test case. - bothAreGSwithNoIndex = true; - } - } + guardSatisfiedIndex[0] = -1; + + AnnotatedTypeMirror methodDefinitionReceiver = null; + AnnotatedTypeMirror methodCallReceiver = null; - TypeMirror arg1TM = passedArgTypes.get(i).getUnderlyingType(); - TypeMirror arg2TM = passedArgTypes.get(j).getUnderlyingType(); + ExecutableElement invokedMethodElement = invokedMethod.getElement(); + if (!ElementUtils.isStatic(invokedMethodElement) + && invokedMethod.getElement().getKind() != ElementKind.CONSTRUCTOR) { + methodDefinitionReceiver = invokedMethod.getReceiverType(); + if (methodDefinitionReceiver != null + && methodDefinitionReceiver.hasAnnotation(checkerGuardSatisfiedClass)) { + guardSatisfiedIndex[0] = + atypeFactory.getGuardSatisfiedIndex(methodDefinitionReceiver); + methodCallReceiver = atypeFactory.getReceiverType(methodInvocationTree); + } + } - if (bothAreGSwithNoIndex - || !(qualHierarchy.isSubtypeShallow(arg1Anno, arg1TM, arg2Anno, arg2TM) - || qualHierarchy.isSubtypeShallow(arg2Anno, arg2TM, arg1Anno, arg1TM))) { + // Retrieve formal parameter types from the method definition. - String formalParam1; - if (i == 0) { - formalParam1 = "The receiver type"; - } else { - formalParam1 = "Parameter #" + i; // i, not i-1, so the index is 1-based - } + for (int i = 0; i < paramTypes.size(); i++) { + guardSatisfiedIndex[i + 1] = -1; - String formalParam2 = "parameter #" + j; // j, not j-1, so the index is 1-based + AnnotatedTypeMirror paramType = paramTypes.get(i); - checker.reportError( - methodInvocationTree, - "guardsatisfied.parameters.must.match", - formalParam1, - formalParam2, - invokedMethod.toString(), - guardSatisfiedIndex[i], - arg1Anno, - arg2Anno); - } + if (paramType.hasAnnotation(checkerGuardSatisfiedClass)) { + guardSatisfiedIndex[i + 1] = atypeFactory.getGuardSatisfiedIndex(paramType); } - } } - } - } - return super.visitMethodInvocation(methodInvocationTree, p); - } - - /** - * Issues an error if the receiver of an unlock() call is not effectively final. - * - * @param methodElement the ExecutableElement for a method call to unlock() - * @param lockExpression the receiver tree for the method call to unlock(). Can be null. - */ - private void ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal( - ExecutableElement methodElement, @Nullable ExpressionTree lockExpression) { - if (lockExpression == null) { - // Implicit this, or class name receivers, are null. But they are also final. So nothing - // to be checked for them. - return; - } + // Combine all of the actual parameters into one list of AnnotationMirrors. - if (!methodElement.getSimpleName().contentEquals("unlock")) { - return; - } + ArrayList passedArgTypes = new ArrayList<>(guardSatisfiedIndex.length); + passedArgTypes.add(methodCallReceiver); + for (ExpressionTree argTree : methodInvocationTree.getArguments()) { + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argTree); + passedArgTypes.add(argType); + } + ArrayList passedArgAnnotations = + new ArrayList<>(guardSatisfiedIndex.length); + for (AnnotatedTypeMirror atm : passedArgTypes) { + if (atm != null) { + passedArgAnnotations.add( + atm.getAnnotationInHierarchy(atypeFactory.GUARDEDBYUNKNOWN)); + } else { + passedArgAnnotations.add(null); + } + } - TypeMirror lockExpressionType = TreeUtils.typeOf(lockExpression); + // Perform the validity check and issue an error if not valid. + + for (int i = 0; i < guardSatisfiedIndex.length; i++) { + if (guardSatisfiedIndex[i] != -1) { + for (int j = i + 1; j < guardSatisfiedIndex.length; j++) { + if (guardSatisfiedIndex[i] == guardSatisfiedIndex[j]) { + // The @GuardedBy/@GuardSatisfied/@GuardedByUnknown/@GuardedByBottom + // annotations must be identical on the corresponding actual parameters. + AnnotationMirror arg1Anno = passedArgAnnotations.get(i); + AnnotationMirror arg2Anno = passedArgAnnotations.get(j); + if (arg1Anno != null && arg2Anno != null) { + boolean bothAreGSwithNoIndex = false; + + if (atypeFactory.areSameByClass(arg1Anno, checkerGuardSatisfiedClass) + && atypeFactory.areSameByClass( + arg2Anno, checkerGuardSatisfiedClass)) { + if (atypeFactory.getGuardSatisfiedIndex(arg1Anno) == -1 + && atypeFactory.getGuardSatisfiedIndex(arg2Anno) == -1) { + // Generally speaking, two @GuardSatisfied annotations with no + // index are incomparable. + // TODO: If they come from the same variable, they are + // comparable. Fix and add a test case. + bothAreGSwithNoIndex = true; + } + } + + TypeMirror arg1TM = passedArgTypes.get(i).getUnderlyingType(); + TypeMirror arg2TM = passedArgTypes.get(j).getUnderlyingType(); + + if (bothAreGSwithNoIndex + || !(qualHierarchy.isSubtypeShallow( + arg1Anno, arg1TM, arg2Anno, arg2TM) + || qualHierarchy.isSubtypeShallow( + arg2Anno, arg2TM, arg1Anno, arg1TM))) { + + String formalParam1; + if (i == 0) { + formalParam1 = "The receiver type"; + } else { + formalParam1 = + "Parameter #" + + i; // i, not i-1, so the index is 1-based + } + + String formalParam2 = + "parameter #" + j; // j, not j-1, so the index is 1-based + + checker.reportError( + methodInvocationTree, + "guardsatisfied.parameters.must.match", + formalParam1, + formalParam2, + invokedMethod.toString(), + guardSatisfiedIndex[i], + arg1Anno, + arg2Anno); + } + } + } + } + } + } - ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); + return super.visitMethodInvocation(methodInvocationTree, p); + } - javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); + /** + * Issues an error if the receiver of an unlock() call is not effectively final. + * + * @param methodElement the ExecutableElement for a method call to unlock() + * @param lockExpression the receiver tree for the method call to unlock(). Can be null. + */ + private void ensureReceiverOfExplicitUnlockCallIsEffectivelyFinal( + ExecutableElement methodElement, @Nullable ExpressionTree lockExpression) { + if (lockExpression == null) { + // Implicit this, or class name receivers, are null. But they are also final. So nothing + // to be checked for them. + return; + } - // TODO: make a type declaration annotation for this rather than looking for the - // Lock.unlock() method explicitly. - TypeMirror lockInterfaceTypeMirror = - TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils()); + if (!methodElement.getSimpleName().contentEquals("unlock")) { + return; + } - if (types.isSubtype(types.erasure(lockExpressionType), lockInterfaceTypeMirror)) { - ensureExpressionIsEffectivelyFinal(lockExpression); - } - } - - /** - * When visiting a synchronized block, issue an error if the expression has a type that implements - * the java.util.concurrent.locks.Lock interface. This prevents explicit locks from being - * accidentally used as built-in (monitor) locks. This is important because the Lock Checker does - * not have a mechanism to separately keep track of the explicit lock and the monitor lock of an - * expression that implements the Lock interface (i.e. there is a @LockHeld annotation used in - * dataflow, but there are not distinct @MonitorLockHeld and @ExplicitLockHeld annotations). It is - * assumed that both kinds of locks will never be held for any expression that implements Lock. - * - *

          Additionally, a synchronized block may not be present in a method that has a @LockingFree - * guarantee or stronger. An error is issued in this case. - * - * @param tree the SynchronizedTree for the synchronized block being visited - */ - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); - - javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); - - // TODO: make a type declaration annotation for this rather than looking for Lock.class - // explicitly. - TypeMirror lockInterfaceTypeMirror = - TypesUtils.typeFromClass(Lock.class, types, processingEnvironment.getElementUtils()); - - ExpressionTree synchronizedExpression = tree.getExpression(); - - ensureExpressionIsEffectivelyFinal(synchronizedExpression); - - TypeMirror expressionType = - types.erasure(atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType()); - - if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) { - checker.reportError(tree, "explicit.lock.synchronized"); - } + TypeMirror lockExpressionType = TreeUtils.typeOf(lockExpression); - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(tree)); + ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); - ExecutableElement methodElement = null; - if (enclosingMethod != null) { - methodElement = TreeUtils.elementFromDeclaration(enclosingMethod); + javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); - SideEffectAnnotation seaOfContainingMethod = - atypeFactory.methodSideEffectAnnotation(methodElement, false); + // TODO: make a type declaration annotation for this rather than looking for the + // Lock.unlock() method explicitly. + TypeMirror lockInterfaceTypeMirror = + TypesUtils.typeFromClass( + Lock.class, types, processingEnvironment.getElementUtils()); - if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) { - checker.reportError( - tree, "synchronized.block.in.lockingfree.method", seaOfContainingMethod); - } + if (types.isSubtype(types.erasure(lockExpressionType), lockInterfaceTypeMirror)) { + ensureExpressionIsEffectivelyFinal(lockExpression); + } } - return super.visitSynchronized(tree, p); - } - - /** - * Ensures that each variable accessed in an expression is final or effectively final and that - * each called method in the expression is @Deterministic. Issues an error otherwise. Recursively - * performs this check on method arguments. Only intended to be used on the expression of a - * synchronized block. - * - *

          Example: given the expression var1.field1.method1(var2.method2()).field2, var1, var2, field1 - * and field2 are enforced to be final or effectively final, and method1 and method2 are enforced - * to be @Deterministic. - * - * @param lockExpressionTree the expression tree of a synchronized block - * @return true if the check succeeds, false if an error message was issued - */ - private boolean ensureExpressionIsEffectivelyFinal(ExpressionTree lockExpressionTree) { - // This functionality could be implemented using a visitor instead, however with this - // design, it is easier to be certain that an error will always be issued if a tree kind is - // not recognized. - // Only the most common tree kinds for synchronized expressions are supported. - - // Traverse the expression using 'tree', as 'lockExpressionTree' is used for error - // reporting. - ExpressionTree tree = lockExpressionTree; - - boolean result = true; - while (true) { - tree = TreeUtils.withoutParens(tree); - - switch (tree.getKind()) { - case MEMBER_SELECT: - if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { - checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); - return false; - } - tree = ((MemberSelectTree) tree).getExpression(); - break; - case IDENTIFIER: - if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { - checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); - return false; - } - return result; - case METHOD_INVOCATION: - Element elem = TreeUtils.elementFromUse(tree); - if (atypeFactory.getDeclAnnotationNoAliases(elem, Deterministic.class) == null - && atypeFactory.getDeclAnnotationNoAliases(elem, Pure.class) == null) { - checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); - return false; - } + /** + * When visiting a synchronized block, issue an error if the expression has a type that + * implements the java.util.concurrent.locks.Lock interface. This prevents explicit locks from + * being accidentally used as built-in (monitor) locks. This is important because the Lock + * Checker does not have a mechanism to separately keep track of the explicit lock and the + * monitor lock of an expression that implements the Lock interface (i.e. there is a @LockHeld + * annotation used in dataflow, but there are not distinct @MonitorLockHeld + * and @ExplicitLockHeld annotations). It is assumed that both kinds of locks will never be held + * for any expression that implements Lock. + * + *

          Additionally, a synchronized block may not be present in a method that has a @LockingFree + * guarantee or stronger. An error is issued in this case. + * + * @param tree the SynchronizedTree for the synchronized block being visited + */ + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + ProcessingEnvironment processingEnvironment = checker.getProcessingEnvironment(); + + javax.lang.model.util.Types types = processingEnvironment.getTypeUtils(); + + // TODO: make a type declaration annotation for this rather than looking for Lock.class + // explicitly. + TypeMirror lockInterfaceTypeMirror = + TypesUtils.typeFromClass( + Lock.class, types, processingEnvironment.getElementUtils()); + + ExpressionTree synchronizedExpression = tree.getExpression(); + + ensureExpressionIsEffectivelyFinal(synchronizedExpression); + + TypeMirror expressionType = + types.erasure( + atypeFactory.getAnnotatedType(synchronizedExpression).getUnderlyingType()); + + if (types.isSubtype(expressionType, lockInterfaceTypeMirror)) { + checker.reportError(tree, "explicit.lock.synchronized"); + } + + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(atypeFactory.getPath(tree)); - MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; + ExecutableElement methodElement = null; + if (enclosingMethod != null) { + methodElement = TreeUtils.elementFromDeclaration(enclosingMethod); - for (ExpressionTree argTree : methodInvocationTree.getArguments()) { - result = ensureExpressionIsEffectivelyFinal(argTree) && result; - } + SideEffectAnnotation seaOfContainingMethod = + atypeFactory.methodSideEffectAnnotation(methodElement, false); - tree = methodInvocationTree.getMethodSelect(); - break; - default: - checker.reportError(tree, "lock.expression.possibly.not.final", lockExpressionTree); - return false; - } + if (!seaOfContainingMethod.isWeakerThan(SideEffectAnnotation.LOCKINGFREE)) { + checker.reportError( + tree, "synchronized.block.in.lockingfree.method", seaOfContainingMethod); + } + } + + return super.visitSynchronized(tree, p); } - } - - /** - * Issues an error if the given expression is not effectively final. Returns true if the - * expression is effectively final, false if an error was issued. - * - * @param lockExpr an expression that might be effectively final - * @param expressionForErrorReporting how to print the expression in an error message - * @param treeForErrorReporting where to report the error - * @return true if the expression is effectively final, false if an error was issued - */ - private boolean ensureExpressionIsEffectivelyFinal( - JavaExpression lockExpr, String expressionForErrorReporting, Tree treeForErrorReporting) { - boolean result = atypeFactory.isExpressionEffectivelyFinal(lockExpr); - if (!result) { - checker.reportError( - treeForErrorReporting, "lock.expression.not.final", expressionForErrorReporting); + + /** + * Ensures that each variable accessed in an expression is final or effectively final and that + * each called method in the expression is @Deterministic. Issues an error otherwise. + * Recursively performs this check on method arguments. Only intended to be used on the + * expression of a synchronized block. + * + *

          Example: given the expression var1.field1.method1(var2.method2()).field2, var1, var2, + * field1 and field2 are enforced to be final or effectively final, and method1 and method2 are + * enforced to be @Deterministic. + * + * @param lockExpressionTree the expression tree of a synchronized block + * @return true if the check succeeds, false if an error message was issued + */ + private boolean ensureExpressionIsEffectivelyFinal(ExpressionTree lockExpressionTree) { + // This functionality could be implemented using a visitor instead, however with this + // design, it is easier to be certain that an error will always be issued if a tree kind is + // not recognized. + // Only the most common tree kinds for synchronized expressions are supported. + + // Traverse the expression using 'tree', as 'lockExpressionTree' is used for error + // reporting. + ExpressionTree tree = lockExpressionTree; + + boolean result = true; + while (true) { + tree = TreeUtils.withoutParens(tree); + + switch (tree.getKind()) { + case MEMBER_SELECT: + if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { + checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); + return false; + } + tree = ((MemberSelectTree) tree).getExpression(); + break; + case IDENTIFIER: + if (!isTreeSymbolEffectivelyFinalOrUnmodifiable(tree)) { + checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); + return false; + } + return result; + case METHOD_INVOCATION: + Element elem = TreeUtils.elementFromUse(tree); + if (atypeFactory.getDeclAnnotationNoAliases(elem, Deterministic.class) == null + && atypeFactory.getDeclAnnotationNoAliases(elem, Pure.class) == null) { + checker.reportError(tree, "lock.expression.not.final", lockExpressionTree); + return false; + } + + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; + + for (ExpressionTree argTree : methodInvocationTree.getArguments()) { + result = ensureExpressionIsEffectivelyFinal(argTree) && result; + } + + tree = methodInvocationTree.getMethodSelect(); + break; + default: + checker.reportError( + tree, "lock.expression.possibly.not.final", lockExpressionTree); + return false; + } + } } - return result; - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - ArrayList annotationTreeList = new ArrayList<>(1); - annotationTreeList.add(tree); - List amList = - TreeUtils.annotationsFromTypeAnnotationTrees(annotationTreeList); - - if (amList != null) { - for (AnnotationMirror annotationMirror : amList) { - if (atypeFactory.areSameByClass(annotationMirror, checkerGuardSatisfiedClass)) { - issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(tree); + + /** + * Issues an error if the given expression is not effectively final. Returns true if the + * expression is effectively final, false if an error was issued. + * + * @param lockExpr an expression that might be effectively final + * @param expressionForErrorReporting how to print the expression in an error message + * @param treeForErrorReporting where to report the error + * @return true if the expression is effectively final, false if an error was issued + */ + private boolean ensureExpressionIsEffectivelyFinal( + JavaExpression lockExpr, + String expressionForErrorReporting, + Tree treeForErrorReporting) { + boolean result = atypeFactory.isExpressionEffectivelyFinal(lockExpr); + if (!result) { + checker.reportError( + treeForErrorReporting, + "lock.expression.not.final", + expressionForErrorReporting); } - } + return result; } - return super.visitAnnotation(tree, p); - } - - /** - * Issues an error if a GuardSatisfied annotation is found in a location other than a method - * return type or parameter (including the receiver). - * - * @param annotationTree an AnnotationTree used for error reporting and to help determine that an - * array parameter has no GuardSatisfied annotations except on the array type - */ - // TODO: Remove this method once @TargetLocations are enforced (i.e. once - // issue https://github.com/typetools/checker-framework/issues/1919 is closed). - private void issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation( - AnnotationTree annotationTree) { - TreePath currentPath = getCurrentPath(); - TreePath path = getPathForLocalVariableRetrieval(currentPath); - if (path != null) { - Tree tree = path.getLeaf(); - Tree.Kind kind = tree.getKind(); - - if (kind == Tree.Kind.METHOD) { - // The @GuardSatisfied annotation is on the return type. - return; - } else if (kind == Tree.Kind.VARIABLE) { - VariableTree varTree = (VariableTree) tree; - Tree varTypeTree = varTree.getType(); - if (varTypeTree != null) { - TreePath parentPath = path.getParentPath(); - if (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) { - Tree.Kind varTypeTreeKind = varTypeTree.getKind(); - if (varTypeTreeKind == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) varTypeTree; - - if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE - || annotatedTypeTree.getAnnotations().contains(annotationTree)) { - // Method parameter - return; - } - } else if (varTypeTreeKind != Tree.Kind.ARRAY_TYPE) { - // Method parameter or receiver - return; + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + ArrayList annotationTreeList = new ArrayList<>(1); + annotationTreeList.add(tree); + List amList = + TreeUtils.annotationsFromTypeAnnotationTrees(annotationTreeList); + + if (amList != null) { + for (AnnotationMirror annotationMirror : amList) { + if (atypeFactory.areSameByClass(annotationMirror, checkerGuardSatisfiedClass)) { + issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation(tree); + } } - } } - } + + return super.visitAnnotation(tree, p); } - checker.reportError(annotationTree, "guardsatisfied.location.disallowed"); - } - - /** - * The JavaExpression parser requires a path for retrieving the scope that will be used to resolve - * local variables. One would expect that simply providing the path to an AnnotationTree would - * work, since the compiler (as called by the org.checkerframework.javacutil.Resolver class) could - * walk up the path from the AnnotationTree to determine the scope. Unfortunately this is not how - * the compiler works. One must provide the path at the right level (not so deep that it results - * in a symbol not being found, but not so high up that it is out of the scope at hand). This is a - * problem when trying to retrieve local variables, since one could silently miss a local variable - * in scope and accidentally retrieve a field with the same name. This method returns the correct - * path for this purpose, given a path to an AnnotationTree. - * - *

          Note: this is definitely necessary for local variable retrieval. It has not been tested - * whether this is strictly necessary for fields or other identifiers. - * - *

          Only call this method from visitAnnotation. - * - * @param path the TreePath whose leaf is an AnnotationTree - * @return a TreePath that can be passed to methods in the Resolver class to locate local - * variables - */ - private @Nullable TreePath getPathForLocalVariableRetrieval(TreePath path) { - assert path.getLeaf() instanceof AnnotationTree; - - // TODO: handle annotations in trees of kind NEW_CLASS (and add test coverage for this - // scenario). - // Currently an annotation in such a tree, such as "new @GuardedBy("foo") Object()", - // results in a "constructor.invocation.invalid" error. This must be fixed first. - - path = path.getParentPath(); - - if (path == null) { - return null; + /** + * Issues an error if a GuardSatisfied annotation is found in a location other than a method + * return type or parameter (including the receiver). + * + * @param annotationTree an AnnotationTree used for error reporting and to help determine that + * an array parameter has no GuardSatisfied annotations except on the array type + */ + // TODO: Remove this method once @TargetLocations are enforced (i.e. once + // issue https://github.com/typetools/checker-framework/issues/1919 is closed). + private void issueErrorIfGuardSatisfiedAnnotationInUnsupportedLocation( + AnnotationTree annotationTree) { + TreePath currentPath = getCurrentPath(); + TreePath path = getPathForLocalVariableRetrieval(currentPath); + if (path != null) { + Tree tree = path.getLeaf(); + Tree.Kind kind = tree.getKind(); + + if (kind == Tree.Kind.METHOD) { + // The @GuardSatisfied annotation is on the return type. + return; + } else if (kind == Tree.Kind.VARIABLE) { + VariableTree varTree = (VariableTree) tree; + Tree varTypeTree = varTree.getType(); + if (varTypeTree != null) { + TreePath parentPath = path.getParentPath(); + if (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) { + Tree.Kind varTypeTreeKind = varTypeTree.getKind(); + if (varTypeTreeKind == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) varTypeTree; + + if (annotatedTypeTree.getUnderlyingType().getKind() + != Tree.Kind.ARRAY_TYPE + || annotatedTypeTree + .getAnnotations() + .contains(annotationTree)) { + // Method parameter + return; + } + } else if (varTypeTreeKind != Tree.Kind.ARRAY_TYPE) { + // Method parameter or receiver + return; + } + } + } + } + } + + checker.reportError(annotationTree, "guardsatisfied.location.disallowed"); } - // A MODIFIERS tree for a VARIABLE or METHOD parent tree would be available at this level, - // but it is not directly handled. Instead, its parent tree (one level higher) is handled. - // Other tree kinds are also handled one level higher. + /** + * The JavaExpression parser requires a path for retrieving the scope that will be used to + * resolve local variables. One would expect that simply providing the path to an AnnotationTree + * would work, since the compiler (as called by the org.checkerframework.javacutil.Resolver + * class) could walk up the path from the AnnotationTree to determine the scope. Unfortunately + * this is not how the compiler works. One must provide the path at the right level (not so deep + * that it results in a symbol not being found, but not so high up that it is out of the scope + * at hand). This is a problem when trying to retrieve local variables, since one could silently + * miss a local variable in scope and accidentally retrieve a field with the same name. This + * method returns the correct path for this purpose, given a path to an AnnotationTree. + * + *

          Note: this is definitely necessary for local variable retrieval. It has not been tested + * whether this is strictly necessary for fields or other identifiers. + * + *

          Only call this method from visitAnnotation. + * + * @param path the TreePath whose leaf is an AnnotationTree + * @return a TreePath that can be passed to methods in the Resolver class to locate local + * variables + */ + private @Nullable TreePath getPathForLocalVariableRetrieval(TreePath path) { + assert path.getLeaf() instanceof AnnotationTree; + + // TODO: handle annotations in trees of kind NEW_CLASS (and add test coverage for this + // scenario). + // Currently an annotation in such a tree, such as "new @GuardedBy("foo") Object()", + // results in a "constructor.invocation.invalid" error. This must be fixed first. + + path = path.getParentPath(); + + if (path == null) { + return null; + } - path = path.getParentPath(); + // A MODIFIERS tree for a VARIABLE or METHOD parent tree would be available at this level, + // but it is not directly handled. Instead, its parent tree (one level higher) is handled. + // Other tree kinds are also handled one level higher. - if (path == null) { - return null; - } + path = path.getParentPath(); - Tree tree = path.getLeaf(); - Tree.Kind kind = tree.getKind(); - - switch (kind) { - case ARRAY_TYPE: - case VARIABLE: - case TYPE_CAST: - case INSTANCE_OF: - case METHOD: - case NEW_ARRAY: - case TYPE_PARAMETER: - // TODO: visitAnnotation does not currently visit annotations on wildcard bounds. - // Address this for the Lock Checker somehow and enable these, as well as the - // corresponding test cases in ChapterExamples.java - // case EXTENDS_WILDCARD: - // case SUPER_WILDCARD: - return path; - default: - return null; + if (path == null) { + return null; + } + + Tree tree = path.getLeaf(); + Tree.Kind kind = tree.getKind(); + + switch (kind) { + case ARRAY_TYPE: + case VARIABLE: + case TYPE_CAST: + case INSTANCE_OF: + case METHOD: + case NEW_ARRAY: + case TYPE_PARAMETER: + // TODO: visitAnnotation does not currently visit annotations on wildcard bounds. + // Address this for the Lock Checker somehow and enable these, as well as the + // corresponding test cases in ChapterExamples.java + // case EXTENDS_WILDCARD: + // case SUPER_WILDCARD: + return path; + default: + return null; + } } - } - - /** - * Returns true if the symbol for the given tree is final or effectively final. Package, class and - * method symbols are unmodifiable and therefore considered final. - * - * @param tree the tree to test - * @return true if the symbol for the given tree is final or effectively final - */ - private boolean isTreeSymbolEffectivelyFinalOrUnmodifiable(Tree tree) { - Element elem = TreeUtils.elementFromTree(tree); - ElementKind ek = elem.getKind(); - return ek == ElementKind.PACKAGE - || ek == ElementKind.CLASS - || ek == ElementKind.METHOD - || ElementUtils.isEffectivelyFinal(elem); - } - - @Override - @SuppressWarnings("interning:not.interned") // AST node comparison - public Void visitIdentifier(IdentifierTree tree, Void p) { - // If the identifier is a field accessed via an implicit this, then check the lock of this. - // (All other field accesses are checked in visitMemberSelect.) - if (TreeUtils.isFieldAccess(tree)) { - Tree parent = getCurrentPath().getParentPath().getLeaf(); - // If the parent is not a member select, or if it is and the field is the expression, - // then the field is accessed via an implicit this. - if ((parent.getKind() != Tree.Kind.MEMBER_SELECT - || ((MemberSelectTree) parent).getExpression() == tree) - && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { - AnnotationMirror guardedBy = - atypeFactory.getSelfType(tree).getAnnotationInHierarchy(atypeFactory.GUARDEDBY); - checkLockOfImplicitThis(tree, guardedBy); - } + + /** + * Returns true if the symbol for the given tree is final or effectively final. Package, class + * and method symbols are unmodifiable and therefore considered final. + * + * @param tree the tree to test + * @return true if the symbol for the given tree is final or effectively final + */ + private boolean isTreeSymbolEffectivelyFinalOrUnmodifiable(Tree tree) { + Element elem = TreeUtils.elementFromTree(tree); + ElementKind ek = elem.getKind(); + return ek == ElementKind.PACKAGE + || ek == ElementKind.CLASS + || ek == ElementKind.METHOD + || ElementUtils.isEffectivelyFinal(elem); } - return super.visitIdentifier(tree, p); - } - - @Override - public Void visitBinary(BinaryTree binaryTree, Void p) { - if (TreeUtils.isStringConcatenation(binaryTree)) { - ExpressionTree leftTree = binaryTree.getLeftOperand(); - ExpressionTree rightTree = binaryTree.getRightOperand(); - - boolean lhsIsString = TypesUtils.isString(TreeUtils.typeOf(leftTree)); - boolean rhsIsString = TypesUtils.isString(TreeUtils.typeOf(rightTree)); - if (!lhsIsString) { - checkPreconditionsForImplicitToStringCall(leftTree); - } else if (!rhsIsString) { - checkPreconditionsForImplicitToStringCall(rightTree); - } + + @Override + @SuppressWarnings("interning:not.interned") // AST node comparison + public Void visitIdentifier(IdentifierTree tree, Void p) { + // If the identifier is a field accessed via an implicit this, then check the lock of this. + // (All other field accesses are checked in visitMemberSelect.) + if (TreeUtils.isFieldAccess(tree)) { + Tree parent = getCurrentPath().getParentPath().getLeaf(); + // If the parent is not a member select, or if it is and the field is the expression, + // then the field is accessed via an implicit this. + if ((parent.getKind() != Tree.Kind.MEMBER_SELECT + || ((MemberSelectTree) parent).getExpression() == tree) + && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { + AnnotationMirror guardedBy = + atypeFactory + .getSelfType(tree) + .getAnnotationInHierarchy(atypeFactory.GUARDEDBY); + checkLockOfImplicitThis(tree, guardedBy); + } + } + return super.visitIdentifier(tree, p); } - return super.visitBinary(binaryTree, p); - } + @Override + public Void visitBinary(BinaryTree binaryTree, Void p) { + if (TreeUtils.isStringConcatenation(binaryTree)) { + ExpressionTree leftTree = binaryTree.getLeftOperand(); + ExpressionTree rightTree = binaryTree.getRightOperand(); + + boolean lhsIsString = TypesUtils.isString(TreeUtils.typeOf(leftTree)); + boolean rhsIsString = TypesUtils.isString(TreeUtils.typeOf(rightTree)); + if (!lhsIsString) { + checkPreconditionsForImplicitToStringCall(leftTree); + } else if (!rhsIsString) { + checkPreconditionsForImplicitToStringCall(rightTree); + } + } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - ExpressionTree rightTree = tree.getExpression(); - if (!TypesUtils.isString(TreeUtils.typeOf(rightTree))) { - checkPreconditionsForImplicitToStringCall(rightTree); - } + return super.visitBinary(binaryTree, p); } - return super.visitCompoundAssignment(tree, p); - } - - /** - * Checks precondition for {@code tree} that is known to be the receiver of an implicit toString() - * call. The receiver of toString() is defined in the annotated JDK to be @GuardSatisfied. - * Therefore if the expression is guarded by a set of locks, the locks must be held prior to this - * implicit call to toString(). - * - *

          Only call this method from visitBinary and visitCompoundAssignment. - * - * @param tree the Tree corresponding to the expression that is known to be the receiver of an - * implicit toString() call - */ - // TODO: If and when the de-sugared .toString() tree is accessible from BaseTypeVisitor, - // the toString() method call should be visited instead of doing this. This would result - // in "contracts.precondition.not.satisfied" errors being issued instead of - // "contracts.precondition.not.satisfied.field", so it would be clear that - // the error refers to an implicit method call, not a dereference (field access). - private void checkPreconditionsForImplicitToStringCall(ExpressionTree tree) { - AnnotationMirror gbAnno = - atypeFactory - .getAnnotatedType(tree) - .getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBY); - checkLock(tree, gbAnno); - } - - private void checkLockOfImplicitThis(Tree tree, AnnotationMirror gbAnno) { - checkLockOfThisOrTree(tree, true, gbAnno); - } - - /** - * Checks the lock of the given tree. - * - * @param tree a tree whose lock to check - * @param gbAnno a {@code @GuardedBy} annotation - * @return true if the check succeeds, false if an error message was issued - */ - private boolean checkLock(Tree tree, AnnotationMirror gbAnno) { - return checkLockOfThisOrTree(tree, false, gbAnno); - } - - /** - * Helper method that checks the lock of either the implicit {@code this} or the given tree. - * - * @param tree a tree whose lock to check - * @param implicitThis true if checking the lock of the implicit {@code this} - * @param gbAnno a {@code @GuardedBy} annotation - * @return true if the check succeeds, false if an error message was issued - */ - private boolean checkLockOfThisOrTree(Tree tree, boolean implicitThis, AnnotationMirror gbAnno) { - if (gbAnno == null) { - throw new TypeSystemError("LockVisitor.checkLock: gbAnno cannot be null"); + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + ExpressionTree rightTree = tree.getExpression(); + if (!TypesUtils.isString(TreeUtils.typeOf(rightTree))) { + checkPreconditionsForImplicitToStringCall(rightTree); + } + } + + return super.visitCompoundAssignment(tree, p); } - if (atypeFactory.areSameByClass(gbAnno, GuardedByUnknown.class) - || atypeFactory.areSameByClass(gbAnno, GuardedByBottom.class)) { - checker.reportError(tree, "lock.not.held", "unknown lock " + gbAnno); - return false; - } else if (atypeFactory.areSameByClass(gbAnno, GuardSatisfied.class)) { - return true; + + /** + * Checks precondition for {@code tree} that is known to be the receiver of an implicit + * toString() call. The receiver of toString() is defined in the annotated JDK to + * be @GuardSatisfied. Therefore if the expression is guarded by a set of locks, the locks must + * be held prior to this implicit call to toString(). + * + *

          Only call this method from visitBinary and visitCompoundAssignment. + * + * @param tree the Tree corresponding to the expression that is known to be the receiver of an + * implicit toString() call + */ + // TODO: If and when the de-sugared .toString() tree is accessible from BaseTypeVisitor, + // the toString() method call should be visited instead of doing this. This would result + // in "contracts.precondition.not.satisfied" errors being issued instead of + // "contracts.precondition.not.satisfied.field", so it would be clear that + // the error refers to an implicit method call, not a dereference (field access). + private void checkPreconditionsForImplicitToStringCall(ExpressionTree tree) { + AnnotationMirror gbAnno = + atypeFactory + .getAnnotatedType(tree) + .getEffectiveAnnotationInHierarchy(atypeFactory.GUARDEDBY); + checkLock(tree, gbAnno); } - List expressions = getLockExpressions(implicitThis, gbAnno, tree); - if (expressions.isEmpty()) { - return true; + private void checkLockOfImplicitThis(Tree tree, AnnotationMirror gbAnno) { + checkLockOfThisOrTree(tree, true, gbAnno); } - boolean result = true; - LockStore store = atypeFactory.getStoreBefore(tree); - for (LockExpression expression : expressions) { - if (expression.error != null) { - checker.reportError( - tree, "expression.unparsable.type.invalid", expression.error.toString()); - result = false; - } else if (expression.lockExpression == null) { - checker.reportError( - tree, "expression.unparsable.type.invalid", expression.expressionString); - result = false; - } else if (!isLockHeld(expression.lockExpression, store)) { - checker.reportError(tree, "lock.not.held", expression.lockExpression.toString()); - result = false; - } - - if (expression.error != null && expression.lockExpression != null) { - result = - ensureExpressionIsEffectivelyFinal( - expression.lockExpression, expression.expressionString, tree) - && result; - } + /** + * Checks the lock of the given tree. + * + * @param tree a tree whose lock to check + * @param gbAnno a {@code @GuardedBy} annotation + * @return true if the check succeeds, false if an error message was issued + */ + private boolean checkLock(Tree tree, AnnotationMirror gbAnno) { + return checkLockOfThisOrTree(tree, false, gbAnno); } - return result; - } - private boolean isLockHeld(JavaExpression lockExpr, LockStore store) { - if (store == null) { - return false; + /** + * Helper method that checks the lock of either the implicit {@code this} or the given tree. + * + * @param tree a tree whose lock to check + * @param implicitThis true if checking the lock of the implicit {@code this} + * @param gbAnno a {@code @GuardedBy} annotation + * @return true if the check succeeds, false if an error message was issued + */ + private boolean checkLockOfThisOrTree( + Tree tree, boolean implicitThis, AnnotationMirror gbAnno) { + if (gbAnno == null) { + throw new TypeSystemError("LockVisitor.checkLock: gbAnno cannot be null"); + } + if (atypeFactory.areSameByClass(gbAnno, GuardedByUnknown.class) + || atypeFactory.areSameByClass(gbAnno, GuardedByBottom.class)) { + checker.reportError(tree, "lock.not.held", "unknown lock " + gbAnno); + return false; + } else if (atypeFactory.areSameByClass(gbAnno, GuardSatisfied.class)) { + return true; + } + + List expressions = getLockExpressions(implicitThis, gbAnno, tree); + if (expressions.isEmpty()) { + return true; + } + + boolean result = true; + LockStore store = atypeFactory.getStoreBefore(tree); + for (LockExpression expression : expressions) { + if (expression.error != null) { + checker.reportError( + tree, "expression.unparsable.type.invalid", expression.error.toString()); + result = false; + } else if (expression.lockExpression == null) { + checker.reportError( + tree, "expression.unparsable.type.invalid", expression.expressionString); + result = false; + } else if (!isLockHeld(expression.lockExpression, store)) { + checker.reportError(tree, "lock.not.held", expression.lockExpression.toString()); + result = false; + } + + if (expression.error != null && expression.lockExpression != null) { + result = + ensureExpressionIsEffectivelyFinal( + expression.lockExpression, + expression.expressionString, + tree) + && result; + } + } + return result; } - CFAbstractValue value = store.getValue(lockExpr); - if (value == null) { - return false; + + private boolean isLockHeld(JavaExpression lockExpr, LockStore store) { + if (store == null) { + return false; + } + CFAbstractValue value = store.getValue(lockExpr); + if (value == null) { + return false; + } + AnnotationMirrorSet annos = value.getAnnotations(); + AnnotationMirror lockAnno = + qualHierarchy.findAnnotationInSameHierarchy(annos, atypeFactory.LOCKHELD); + return lockAnno != null && atypeFactory.areSameByClass(lockAnno, LockHeld.class); } - AnnotationMirrorSet annos = value.getAnnotations(); - AnnotationMirror lockAnno = - qualHierarchy.findAnnotationInSameHierarchy(annos, atypeFactory.LOCKHELD); - return lockAnno != null && atypeFactory.areSameByClass(lockAnno, LockHeld.class); - } - private List getLockExpressions( - boolean implicitThis, AnnotationMirror gbAnno, Tree tree) { + private List getLockExpressions( + boolean implicitThis, AnnotationMirror gbAnno, Tree tree) { - List expressions = - AnnotationUtils.getElementValueArray( - gbAnno, atypeFactory.guardedByValueElement, String.class, Collections.emptyList()); + List expressions = + AnnotationUtils.getElementValueArray( + gbAnno, + atypeFactory.guardedByValueElement, + String.class, + Collections.emptyList()); - if (expressions.isEmpty()) { - return Collections.emptyList(); - } + if (expressions.isEmpty()) { + return Collections.emptyList(); + } - TreePath currentPath = getCurrentPath(); + TreePath currentPath = getCurrentPath(); - TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(currentPath)); - JavaExpression pseudoReceiver = JavaExpression.getPseudoReceiver(currentPath, enclosingType); + TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(currentPath)); + JavaExpression pseudoReceiver = + JavaExpression.getPseudoReceiver(currentPath, enclosingType); - JavaExpression self; - if (implicitThis) { - self = pseudoReceiver; - } else if (TreeUtils.isExpressionTree(tree)) { - self = JavaExpression.fromTree((ExpressionTree) tree); - } else { - self = new Unknown(tree); - } + JavaExpression self; + if (implicitThis) { + self = pseudoReceiver; + } else if (TreeUtils.isExpressionTree(tree)) { + self = JavaExpression.fromTree((ExpressionTree) tree); + } else { + self = new Unknown(tree); + } - return CollectionsPlume.mapList( - expression -> parseExpressionString(expression, currentPath, self), expressions); - } - - /** - * Parse a Java expression. - * - * @param expression the Java expression - * @param path the path to the expression - * @param itself the self expression - * @return the parsed expression - */ - private LockExpression parseExpressionString( - String expression, TreePath path, JavaExpression itself) { - - LockExpression lockExpression = new LockExpression(expression); - if (DependentTypesError.isExpressionError(expression)) { - lockExpression.error = DependentTypesError.unparse(expression); - return lockExpression; + return CollectionsPlume.mapList( + expression -> parseExpressionString(expression, currentPath, self), expressions); } - Matcher selfReceiverMatcher = SELF_RECEIVER_PATTERN.matcher(expression); - try { - if (selfReceiverMatcher.matches()) { - String remainingExpression = selfReceiverMatcher.group(2); - if (remainingExpression == null || remainingExpression.isEmpty()) { - lockExpression.lockExpression = itself; - if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { - checker.reportError( - path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression); - } - return lockExpression; - } else { - lockExpression.lockExpression = - StringToJavaExpression.atPath( - itself.toString() + "." + remainingExpression, path, checker); - if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { - checker.reportError( - path.getLeaf(), "lock.expression.not.final", lockExpression.lockExpression); - } - return lockExpression; + /** + * Parse a Java expression. + * + * @param expression the Java expression + * @param path the path to the expression + * @param itself the self expression + * @return the parsed expression + */ + private LockExpression parseExpressionString( + String expression, TreePath path, JavaExpression itself) { + + LockExpression lockExpression = new LockExpression(expression); + if (DependentTypesError.isExpressionError(expression)) { + lockExpression.error = DependentTypesError.unparse(expression); + return lockExpression; + } + + Matcher selfReceiverMatcher = SELF_RECEIVER_PATTERN.matcher(expression); + try { + if (selfReceiverMatcher.matches()) { + String remainingExpression = selfReceiverMatcher.group(2); + if (remainingExpression == null || remainingExpression.isEmpty()) { + lockExpression.lockExpression = itself; + if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { + checker.reportError( + path.getLeaf(), + "lock.expression.not.final", + lockExpression.lockExpression); + } + return lockExpression; + } else { + lockExpression.lockExpression = + StringToJavaExpression.atPath( + itself.toString() + "." + remainingExpression, path, checker); + if (!atypeFactory.isExpressionEffectivelyFinal(lockExpression.lockExpression)) { + checker.reportError( + path.getLeaf(), + "lock.expression.not.final", + lockExpression.lockExpression); + } + return lockExpression; + } + } else { + lockExpression.lockExpression = + StringToJavaExpression.atPath(expression, path, checker); + return lockExpression; + } + } catch (JavaExpressionParseException ex) { + lockExpression.error = new DependentTypesError(expression, ex); + return lockExpression; } - } else { - lockExpression.lockExpression = StringToJavaExpression.atPath(expression, path, checker); - return lockExpression; - } - } catch (JavaExpressionParseException ex) { - lockExpression.error = new DependentTypesError(expression, ex); - return lockExpression; } - } - private static class LockExpression { - final String expressionString; - JavaExpression lockExpression = null; - DependentTypesError error = null; + private static class LockExpression { + final String expressionString; + JavaExpression lockExpression = null; + DependentTypesError error = null; - LockExpression(String expression) { - this.expressionString = expression; + LockExpression(String expression) { + this.expressionString = expression; + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java index 7c949e9b8b8..a055574c6c7 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForElementSupplier.java @@ -1,8 +1,9 @@ package org.checkerframework.checker.mustcall; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; +import javax.lang.model.element.ExecutableElement; + /** * This interface should be implemented by all type factories that can provide an {@link * ExecutableElement} for {@link CreatesMustCallFor} and {@link CreatesMustCallFor.List}. This @@ -13,17 +14,17 @@ */ public interface CreatesMustCallForElementSupplier { - /** - * Returns the CreatesMustCallFor.value field/element. - * - * @return the CreatesMustCallFor.value field/element - */ - ExecutableElement getCreatesMustCallForValueElement(); + /** + * Returns the CreatesMustCallFor.value field/element. + * + * @return the CreatesMustCallFor.value field/element + */ + ExecutableElement getCreatesMustCallForValueElement(); - /** - * Returns the CreatesMustCallFor.List.value field/element. - * - * @return the CreatesMustCallFor.List.value field/element - */ - ExecutableElement getCreatesMustCallForListValueElement(); + /** + * Returns the CreatesMustCallFor.List.value field/element. + * + * @return the CreatesMustCallFor.List.value field/element + */ + ExecutableElement getCreatesMustCallForListValueElement(); } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java index 746fe2ef310..66eaeaf0d26 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/CreatesMustCallForToJavaExpression.java @@ -2,11 +2,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; -import java.util.ArrayList; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; + import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; @@ -18,182 +14,197 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; + /** * Utility methods to convert targets of {@code @CreatesMustCallFor} annotations to {@link * org.checkerframework.dataflow.expression.JavaExpression}s. */ public class CreatesMustCallForToJavaExpression { - /** static utility methods only; don't create instances */ - private CreatesMustCallForToJavaExpression() {} + /** static utility methods only; don't create instances */ + private CreatesMustCallForToJavaExpression() {} - /** - * Returns the elements of the @CreatesMustCallFor annotations on the invoked method, as - * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor - * annotation. - * - *

          If any expression is unparseable, this method reports an error and returns the empty set. - * - * @param n a method invocation - * @param atypeFactory the type factory to report errors and parse the expression string - * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor - * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory - * again. The arguments are different so that the given type factory's adherence to both - * protocols are checked by the type system. - * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list - */ - public static List getCreatesMustCallForExpressionsAtInvocation( - MethodInvocationNode n, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier) { - List results = new ArrayList<>(1); - List createsMustCallForAnnos = - getCreatesMustCallForAnnos(n.getTarget().getMethod(), atypeFactory, supplier); - for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { - JavaExpression expr = - getCreatesMustCallForExpression( - createsMustCallFor, - n.getTree(), - n.getTarget().getMethod().getSimpleName(), - atypeFactory, - supplier, - (s) -> StringToJavaExpression.atMethodInvocation(s, n, atypeFactory.getChecker())); - if (expr != null && !results.contains(expr)) { - results.add(expr); - } + /** + * Returns the elements of the @CreatesMustCallFor annotations on the invoked method, as + * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor + * annotation. + * + *

          If any expression is unparseable, this method reports an error and returns the empty set. + * + * @param n a method invocation + * @param atypeFactory the type factory to report errors and parse the expression string + * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor + * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory + * again. The arguments are different so that the given type factory's adherence to both + * protocols are checked by the type system. + * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list + */ + public static List getCreatesMustCallForExpressionsAtInvocation( + MethodInvocationNode n, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier) { + List results = new ArrayList<>(1); + List createsMustCallForAnnos = + getCreatesMustCallForAnnos(n.getTarget().getMethod(), atypeFactory, supplier); + for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { + JavaExpression expr = + getCreatesMustCallForExpression( + createsMustCallFor, + n.getTree(), + n.getTarget().getMethod().getSimpleName(), + atypeFactory, + supplier, + (s) -> + StringToJavaExpression.atMethodInvocation( + s, n, atypeFactory.getChecker())); + if (expr != null && !results.contains(expr)) { + results.add(expr); + } + } + return results; } - return results; - } - /** - * Returns the elements of the @CreatesMustCallFor annotations on the method declaration, as - * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor - * annotation. - * - *

          If any expression is unparseable, this method reports an error and returns the empty set. - * - * @param tree a method declaration - * @param atypeFactory the type factory to report errors and parse the expression string - * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor - * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory - * again. The arguments are different so that the given type factory's adherence to both - * protocols are checked by the type system. - * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list - */ - public static List getCreatesMustCallForExpressionsAtMethodDeclaration( - MethodTree tree, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier) { - List results = new ArrayList<>(1); - ExecutableElement method = TreeUtils.elementFromDeclaration(tree); - List createsMustCallForAnnos = - getCreatesMustCallForAnnos(method, atypeFactory, supplier); - for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { - JavaExpression expr = - getCreatesMustCallForExpression( - createsMustCallFor, - tree, - method.getSimpleName(), - atypeFactory, - supplier, - (s) -> StringToJavaExpression.atMethodBody(s, tree, atypeFactory.getChecker())); - if (expr != null && !results.contains(expr)) { - results.add(expr); - } + /** + * Returns the elements of the @CreatesMustCallFor annotations on the method declaration, as + * JavaExpressions. Returns the empty set if the given method has no @CreatesMustCallFor + * annotation. + * + *

          If any expression is unparseable, this method reports an error and returns the empty set. + * + * @param tree a method declaration + * @param atypeFactory the type factory to report errors and parse the expression string + * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor + * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory + * again. The arguments are different so that the given type factory's adherence to both + * protocols are checked by the type system. + * @return the arguments of the method's @CreatesMustCallFor annotation, or an empty list + */ + public static List getCreatesMustCallForExpressionsAtMethodDeclaration( + MethodTree tree, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier) { + List results = new ArrayList<>(1); + ExecutableElement method = TreeUtils.elementFromDeclaration(tree); + List createsMustCallForAnnos = + getCreatesMustCallForAnnos(method, atypeFactory, supplier); + for (AnnotationMirror createsMustCallFor : createsMustCallForAnnos) { + JavaExpression expr = + getCreatesMustCallForExpression( + createsMustCallFor, + tree, + method.getSimpleName(), + atypeFactory, + supplier, + (s) -> + StringToJavaExpression.atMethodBody( + s, tree, atypeFactory.getChecker())); + if (expr != null && !results.contains(expr)) { + results.add(expr); + } + } + return results; } - return results; - } - /** - * Returns the {@code CreatesMustCallFor} annotations on a method - * - * @param method the method - * @param atypeFactory the type factory to use for looking up annotations - * @param supplier supplier to use to get elements - * @return the {@code CreatesMustCallFor} annotations - */ - private static List getCreatesMustCallForAnnos( - ExecutableElement method, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier) { - AnnotationMirror createsMustCallForList = - atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.List.class); - List result = new ArrayList<>(); - if (createsMustCallForList != null) { - // Handle a set of CreatesMustCallFor annotations. - result.addAll( - AnnotationUtils.getElementValueArray( - createsMustCallForList, - supplier.getCreatesMustCallForListValueElement(), - AnnotationMirror.class)); - } - AnnotationMirror createsMustCallFor = - atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.class); - if (createsMustCallFor != null) { - result.add(createsMustCallFor); + /** + * Returns the {@code CreatesMustCallFor} annotations on a method + * + * @param method the method + * @param atypeFactory the type factory to use for looking up annotations + * @param supplier supplier to use to get elements + * @return the {@code CreatesMustCallFor} annotations + */ + private static List getCreatesMustCallForAnnos( + ExecutableElement method, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier) { + AnnotationMirror createsMustCallForList = + atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.List.class); + List result = new ArrayList<>(); + if (createsMustCallForList != null) { + // Handle a set of CreatesMustCallFor annotations. + result.addAll( + AnnotationUtils.getElementValueArray( + createsMustCallForList, + supplier.getCreatesMustCallForListValueElement(), + AnnotationMirror.class)); + } + AnnotationMirror createsMustCallFor = + atypeFactory.getDeclAnnotation(method, CreatesMustCallFor.class); + if (createsMustCallFor != null) { + result.add(createsMustCallFor); + } + return result; } - return result; - } - /** - * Parses a single CreatesMustCallFor annotation. If the target expression cannot be parsed, - * issues a checker error. - * - * @param createsMustCallFor a @CreatesMustCallFor annotation - * @param tree the tree on which to report an error if annotation cannot be parsed - * @param methodName name to use in error message if annotation cannot be parsed - * @param atypeFactory the type factory - * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor - * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory - * again. The arguments are different so that the given type factory's adherence to both - * protocols are checked by the type system. - * @param converter function to be used to create JavaExpression - * @return the Java expression representing the target, or null if the target is unparseable - */ - private static @Nullable JavaExpression getCreatesMustCallForExpression( - AnnotationMirror createsMustCallFor, - Tree tree, - Name methodName, - GenericAnnotatedTypeFactory atypeFactory, - CreatesMustCallForElementSupplier supplier, - StringToJavaExpression converter) { - // Unfortunately, there is no way to avoid passing the default string "this" here. The - // default must be hard-coded into the client, such as here. That is the price for the - // efficiency of not having to query the annotation definition (such queries are expensive). - String targetStrWithoutAdaptation = - AnnotationUtils.getElementValue( - createsMustCallFor, supplier.getCreatesMustCallForValueElement(), String.class, "this"); - // TODO: find a way to also check if the target is a known tempvar, and if so return that. - // That should improve the quality of the error messages we give. - JavaExpression targetExpr; - try { - targetExpr = converter.toJavaExpression(targetStrWithoutAdaptation); - if (targetExpr instanceof Unknown) { - issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); - return null; - } - } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { - issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); - return null; + /** + * Parses a single CreatesMustCallFor annotation. If the target expression cannot be parsed, + * issues a checker error. + * + * @param createsMustCallFor a @CreatesMustCallFor annotation + * @param tree the tree on which to report an error if annotation cannot be parsed + * @param methodName name to use in error message if annotation cannot be parsed + * @param atypeFactory the type factory + * @param supplier a type factory that can supply the executable elements for CreatesMustCallFor + * and CreatesMustCallFor.List's value elements. Usually, you should just pass atypeFactory + * again. The arguments are different so that the given type factory's adherence to both + * protocols are checked by the type system. + * @param converter function to be used to create JavaExpression + * @return the Java expression representing the target, or null if the target is unparseable + */ + private static @Nullable JavaExpression getCreatesMustCallForExpression( + AnnotationMirror createsMustCallFor, + Tree tree, + Name methodName, + GenericAnnotatedTypeFactory atypeFactory, + CreatesMustCallForElementSupplier supplier, + StringToJavaExpression converter) { + // Unfortunately, there is no way to avoid passing the default string "this" here. The + // default must be hard-coded into the client, such as here. That is the price for the + // efficiency of not having to query the annotation definition (such queries are expensive). + String targetStrWithoutAdaptation = + AnnotationUtils.getElementValue( + createsMustCallFor, + supplier.getCreatesMustCallForValueElement(), + String.class, + "this"); + // TODO: find a way to also check if the target is a known tempvar, and if so return that. + // That should improve the quality of the error messages we give. + JavaExpression targetExpr; + try { + targetExpr = converter.toJavaExpression(targetStrWithoutAdaptation); + if (targetExpr instanceof Unknown) { + issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); + return null; + } + } catch (JavaExpressionParseUtil.JavaExpressionParseException e) { + issueUnparseableError(tree, methodName, atypeFactory, targetStrWithoutAdaptation); + return null; + } + return targetExpr; } - return targetExpr; - } - /** - * Issues a createsmustcallfor.target.unparseable error. - * - * @param tree the tree on which to report the error - * @param methodName method name to use in error message - * @param atypeFactory the type factory to use to issue the error - * @param unparseable the unparseable string - */ - private static void issueUnparseableError( - Tree tree, - Name methodName, - GenericAnnotatedTypeFactory atypeFactory, - String unparseable) { - atypeFactory - .getChecker() - .reportError(tree, "createsmustcallfor.target.unparseable", methodName, unparseable); - } + /** + * Issues a createsmustcallfor.target.unparseable error. + * + * @param tree the tree on which to report the error + * @param methodName method name to use in error message + * @param atypeFactory the type factory to use to issue the error + * @param unparseable the unparseable string + */ + private static void issueUnparseableError( + Tree tree, + Name methodName, + GenericAnnotatedTypeFactory atypeFactory, + String unparseable) { + atypeFactory + .getChecker() + .reportError( + tree, "createsmustcallfor.target.unparseable", methodName, unparseable); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java index 80976b87a5a..a95722d94f3 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java @@ -7,23 +7,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; @@ -62,542 +46,574 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + /** * The annotated type factory for the Must Call Checker. Primarily responsible for the subtyping * rules between @MustCall annotations. */ public class MustCallAnnotatedTypeFactory extends BaseAnnotatedTypeFactory - implements CreatesMustCallForElementSupplier { - - /** The {@code @}{@link MustCallUnknown} annotation. */ - public final AnnotationMirror TOP; - - /** The {@code @}{@link MustCall}{@code ()} annotation. It is the default in unannotated code. */ - public final AnnotationMirror BOTTOM; - - /** The {@code @}{@link PolyMustCall} annotation. */ - public final AnnotationMirror POLY; - - /** - * Map from trees representing expressions to the temporary variables that represent them in the - * store. - * - *

          Consider the following code, adapted from Apache Zookeeper: - * - *

          -   *   sock = SocketChannel.open();
          -   *   sock.socket().setSoLinger(false, -1);
          -   * 
          - * - * This code is safe from resource leaks: sock is an unconnected socket and therefore has no - * must-call obligation. The expression sock.socket() similarly has no must-call obligation - * because it is a resource alias, but without a temporary variable that represents that - * expression in the store, the resource leak checker wouldn't be able to determine that. - * - *

          These temporary variables are only created once---here---but are used by all three parts of - * the resource leak checker by calling {@link #getTempVar(Node)}. The temporary variables are - * shared in the same way that subcheckers share CFG structure; see {@link - * #getSharedCFGForTree(Tree)}. - */ - /*package-private*/ final IdentityHashMap tempVars = - new IdentityHashMap<>(100); - - /** The MustCall.value field/element. */ - private final ExecutableElement mustCallValueElement = - TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); - - /** The InheritableMustCall.value field/element. */ - /*package-private*/ final ExecutableElement inheritableMustCallValueElement = - TreeUtils.getMethod(InheritableMustCall.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.List.value field/element. */ - private final ExecutableElement createsMustCallForListValueElement = - TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.value field/element. */ - private final ExecutableElement createsMustCallForValueElement = - TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); - - /** True if -AnoLightweightOwnership was passed on the command line. */ - private final boolean noLightweightOwnership; - - /* NO-AFU - * True if -AenableWpiForRlc (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) was passed on - * the command line. - * - private final boolean enableWpiForRlc; - */ - - /** - * Creates a MustCallAnnotatedTypeFactory. - * - * @param checker the checker associated with this type factory - */ - public MustCallAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - TOP = AnnotationBuilder.fromClass(elements, MustCallUnknown.class); - BOTTOM = createMustCall(Collections.emptyList()); - POLY = AnnotationBuilder.fromClass(elements, PolyMustCall.class); - addAliasedTypeAnnotation(InheritableMustCall.class, MustCall.class, true); - if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) { - // In NO_RESOURCE_ALIASES mode, all @MustCallAlias annotations are ignored. - addAliasedTypeAnnotation(MustCallAlias.class, POLY); + implements CreatesMustCallForElementSupplier { + + /** The {@code @}{@link MustCallUnknown} annotation. */ + public final AnnotationMirror TOP; + + /** + * The {@code @}{@link MustCall}{@code ()} annotation. It is the default in unannotated code. + */ + public final AnnotationMirror BOTTOM; + + /** The {@code @}{@link PolyMustCall} annotation. */ + public final AnnotationMirror POLY; + + /** + * Map from trees representing expressions to the temporary variables that represent them in the + * store. + * + *

          Consider the following code, adapted from Apache Zookeeper: + * + *

          +     *   sock = SocketChannel.open();
          +     *   sock.socket().setSoLinger(false, -1);
          +     * 
          + * + * This code is safe from resource leaks: sock is an unconnected socket and therefore has no + * must-call obligation. The expression sock.socket() similarly has no must-call obligation + * because it is a resource alias, but without a temporary variable that represents that + * expression in the store, the resource leak checker wouldn't be able to determine that. + * + *

          These temporary variables are only created once---here---but are used by all three parts + * of the resource leak checker by calling {@link #getTempVar(Node)}. The temporary variables + * are shared in the same way that subcheckers share CFG structure; see {@link + * #getSharedCFGForTree(Tree)}. + */ + /*package-private*/ final IdentityHashMap tempVars = + new IdentityHashMap<>(100); + + /** The MustCall.value field/element. */ + private final ExecutableElement mustCallValueElement = + TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); + + /** The InheritableMustCall.value field/element. */ + /*package-private*/ final ExecutableElement inheritableMustCallValueElement = + TreeUtils.getMethod(InheritableMustCall.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.List.value field/element. */ + private final ExecutableElement createsMustCallForListValueElement = + TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.value field/element. */ + private final ExecutableElement createsMustCallForValueElement = + TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); + + /** True if -AnoLightweightOwnership was passed on the command line. */ + private final boolean noLightweightOwnership; + + /* NO-AFU + * True if -AenableWpiForRlc (see {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}) was passed on + * the command line. + * + private final boolean enableWpiForRlc; + */ + + /** + * Creates a MustCallAnnotatedTypeFactory. + * + * @param checker the checker associated with this type factory + */ + public MustCallAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + TOP = AnnotationBuilder.fromClass(elements, MustCallUnknown.class); + BOTTOM = createMustCall(Collections.emptyList()); + POLY = AnnotationBuilder.fromClass(elements, PolyMustCall.class); + addAliasedTypeAnnotation(InheritableMustCall.class, MustCall.class, true); + if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) { + // In NO_RESOURCE_ALIASES mode, all @MustCallAlias annotations are ignored. + addAliasedTypeAnnotation(MustCallAlias.class, POLY); + } + noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); + // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + this.postInit(); } - noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); - // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); - this.postInit(); - } - - @Override - public void setRoot(@Nullable CompilationUnitTree root) { - super.setRoot(root); - // TODO: This should probably be guarded by isSafeToClearSharedCFG from - // GenericAnnotatedTypeFactory, but this works here because we know the Must Call Checker is - // always the first subchecker that's sharing tempvars. - tempVars.clear(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Explicitly name the qualifiers, in order to exclude @MustCallAlias. - return new LinkedHashSet<>( - Arrays.asList(MustCall.class, MustCallUnknown.class, PolyMustCall.class)); - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new MustCallTreeAnnotator(this)); - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(super.createTypeAnnotator(), new MustCallTypeAnnotator(this)); - } - - /** - * Returns a {@literal @}MustCall annotation that is like the input, but it does not have "close". - * Returns the argument annotation mirror (not a new one) if the argument doesn't have "close" as - * one of its elements. - * - *

          If the argument is null, returns bottom. - * - * @param anno a MustCall annotation - * @return a MustCall annotation that does not have "close" as one of its values, but is otherwise - * identical to anno - */ - public AnnotationMirror withoutClose(@Nullable AnnotationMirror anno) { - if (anno == null || AnnotationUtils.areSame(anno, BOTTOM)) { - return BOTTOM; - } else if (!AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.mustcall.qual.MustCall")) { - return anno; + + @Override + public void setRoot(@Nullable CompilationUnitTree root) { + super.setRoot(root); + // TODO: This should probably be guarded by isSafeToClearSharedCFG from + // GenericAnnotatedTypeFactory, but this works here because we know the Must Call Checker is + // always the first subchecker that's sharing tempvars. + tempVars.clear(); } - List values = - AnnotationUtils.getElementValueArray(anno, mustCallValueElement, String.class); - // Use `removeAll` because `remove` only removes the first occurrence. - if (values.removeAll(Collections.singletonList("close"))) { - return createMustCall(values); - } else { - return anno; + + @Override + protected Set> createSupportedTypeQualifiers() { + // Explicitly name the qualifiers, in order to exclude @MustCallAlias. + return new LinkedHashSet<>( + Arrays.asList(MustCall.class, MustCallUnknown.class, PolyMustCall.class)); } - } - - /** Treat non-owning method parameters as @MustCallUnknown (top) when the method is called. */ - @Override - public void methodFromUsePreSubstitution( - ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { - ExecutableElement declaration; - if (tree instanceof MethodInvocationTree) { - declaration = TreeUtils.elementFromUse((MethodInvocationTree) tree); - } else if (tree instanceof MemberReferenceTree) { - declaration = (ExecutableElement) TreeUtils.elementFromUse(tree); - } else { - throw new TypeSystemError("unexpected type of method tree: " + tree.getKind()); + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(super.createTreeAnnotator(), new MustCallTreeAnnotator(this)); } - changeNonOwningParameterTypesToTop(declaration, type); - super.methodFromUsePreSubstitution(tree, type, resolvePolyQuals); - } - - @Override - protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { - ExecutableElement declaration = TreeUtils.elementFromUse(tree); - changeNonOwningParameterTypesToTop(declaration, type); - super.constructorFromUsePreSubstitution(tree, type, resolvePolyQuals); - } - - /** - * Class to implement the customized semantics of {@link MustCallAlias} (and {@link PolyMustCall}) - * annotations; see the {@link MustCallAlias} documentation for details. - */ - private class MustCallQualifierPolymorphism extends DefaultQualifierPolymorphism { + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(super.createTypeAnnotator(), new MustCallTypeAnnotator(this)); + } + /** - * Creates a {@link MustCallQualifierPolymorphism}. + * Returns a {@literal @}MustCall annotation that is like the input, but it does not have + * "close". Returns the argument annotation mirror (not a new one) if the argument doesn't have + * "close" as one of its elements. + * + *

          If the argument is null, returns bottom. * - * @param env the processing environment - * @param factory the factory for the current checker + * @param anno a MustCall annotation + * @return a MustCall annotation that does not have "close" as one of its values, but is + * otherwise identical to anno */ - public MustCallQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { - super(env, factory); + public AnnotationMirror withoutClose(@Nullable AnnotationMirror anno) { + if (anno == null || AnnotationUtils.areSame(anno, BOTTOM)) { + return BOTTOM; + } else if (!AnnotationUtils.areSameByName( + anno, "org.checkerframework.checker.mustcall.qual.MustCall")) { + return anno; + } + List values = + AnnotationUtils.getElementValueArray(anno, mustCallValueElement, String.class); + // Use `removeAll` because `remove` only removes the first occurrence. + if (values.removeAll(Collections.singletonList("close"))) { + return createMustCall(values); + } else { + return anno; + } } + /** Treat non-owning method parameters as @MustCallUnknown (top) when the method is called. */ @Override - protected void replace( - AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { - AnnotationMirrorMap realReplacements = replacements; - AnnotationMirror extantPolyAnnoReplacement = null; - TypeElement typeElement = TypesUtils.getTypeElement(type.getUnderlyingType()); - // only customize replacement for type elements - if (typeElement != null) { - assert replacements.size() == 1 && replacements.containsKey(POLY); - extantPolyAnnoReplacement = replacements.get(POLY); - if (AnnotationUtils.areSameByName( - extantPolyAnnoReplacement, MustCall.class.getCanonicalName())) { - List extentReplacementVals = - AnnotationUtils.getElementValueArray( - extantPolyAnnoReplacement, - getMustCallValueElement(), - String.class, - Collections.emptyList()); - // Replacement is only customized when the parameter type has a non-empty - // must-call obligation. - if (!extentReplacementVals.isEmpty()) { - AnnotationMirror inheritableMustCall = - getDeclAnnotation(typeElement, InheritableMustCall.class); - if (inheritableMustCall != null) { - List inheritableMustCallVals = - AnnotationUtils.getElementValueArray( - inheritableMustCall, - inheritableMustCallValueElement, - String.class, - Collections.emptyList()); - if (!inheritableMustCallVals.equals(extentReplacementVals)) { - // Use the must call values from the @InheritableMustCall annotation - // instead. This allows for wrapper types to have a must-call method - // with a different name than the must-call method for the wrapped - // type. - AnnotationMirror mustCall = createMustCall(inheritableMustCallVals); - realReplacements = new AnnotationMirrorMap<>(); - realReplacements.put(POLY, mustCall); - } + public void methodFromUsePreSubstitution( + ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { + ExecutableElement declaration; + if (tree instanceof MethodInvocationTree) { + declaration = TreeUtils.elementFromUse((MethodInvocationTree) tree); + } else if (tree instanceof MemberReferenceTree) { + declaration = (ExecutableElement) TreeUtils.elementFromUse(tree); + } else { + throw new TypeSystemError("unexpected type of method tree: " + tree.getKind()); + } + changeNonOwningParameterTypesToTop(declaration, type); + super.methodFromUsePreSubstitution(tree, type, resolvePolyQuals); + } + + @Override + protected void constructorFromUsePreSubstitution( + NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { + ExecutableElement declaration = TreeUtils.elementFromUse(tree); + changeNonOwningParameterTypesToTop(declaration, type); + super.constructorFromUsePreSubstitution(tree, type, resolvePolyQuals); + } + + /** + * Class to implement the customized semantics of {@link MustCallAlias} (and {@link + * PolyMustCall}) annotations; see the {@link MustCallAlias} documentation for details. + */ + private class MustCallQualifierPolymorphism extends DefaultQualifierPolymorphism { + /** + * Creates a {@link MustCallQualifierPolymorphism}. + * + * @param env the processing environment + * @param factory the factory for the current checker + */ + public MustCallQualifierPolymorphism( + ProcessingEnvironment env, AnnotatedTypeFactory factory) { + super(env, factory); + } + + @Override + protected void replace( + AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { + AnnotationMirrorMap realReplacements = replacements; + AnnotationMirror extantPolyAnnoReplacement = null; + TypeElement typeElement = TypesUtils.getTypeElement(type.getUnderlyingType()); + // only customize replacement for type elements + if (typeElement != null) { + assert replacements.size() == 1 && replacements.containsKey(POLY); + extantPolyAnnoReplacement = replacements.get(POLY); + if (AnnotationUtils.areSameByName( + extantPolyAnnoReplacement, MustCall.class.getCanonicalName())) { + List extentReplacementVals = + AnnotationUtils.getElementValueArray( + extantPolyAnnoReplacement, + getMustCallValueElement(), + String.class, + Collections.emptyList()); + // Replacement is only customized when the parameter type has a non-empty + // must-call obligation. + if (!extentReplacementVals.isEmpty()) { + AnnotationMirror inheritableMustCall = + getDeclAnnotation(typeElement, InheritableMustCall.class); + if (inheritableMustCall != null) { + List inheritableMustCallVals = + AnnotationUtils.getElementValueArray( + inheritableMustCall, + inheritableMustCallValueElement, + String.class, + Collections.emptyList()); + if (!inheritableMustCallVals.equals(extentReplacementVals)) { + // Use the must call values from the @InheritableMustCall annotation + // instead. This allows for wrapper types to have a must-call method + // with a different name than the must-call method for the wrapped + // type. + AnnotationMirror mustCall = createMustCall(inheritableMustCallVals); + realReplacements = new AnnotationMirrorMap<>(); + realReplacements.put(POLY, mustCall); + } + } + } + } } - } + super.replace(type, realReplacements); } - } - super.replace(type, realReplacements); } - } - - @Override - protected QualifierPolymorphism createQualifierPolymorphism() { - return new MustCallQualifierPolymorphism(processingEnv, this); - } - - /** - * Changes the type of each parameter not annotated as @Owning to @MustCallUnknown (top). Also - * replaces the component type of the varargs array, if applicable. - * - *

          This method is not responsible for handling receivers, which can never be owning. - * - * @param declaration a method or constructor declaration - * @param type the method or constructor's type - */ - private void changeNonOwningParameterTypesToTop( - ExecutableElement declaration, AnnotatedExecutableType type) { - // Formal parameters without a declared owning annotation are disregarded by the RLC - // _analysis_, as their @MustCall obligation is set to Top in this method. However, - // this computation is not desirable for RLC _inference_ in unannotated programs, - // where a goal is to infer and add @Owning annotations to formal parameters. - /* NO-AFU - if (getWholeProgramInference() != null && !isWpiEnabledForRLC()) { - return; + + @Override + protected QualifierPolymorphism createQualifierPolymorphism() { + return new MustCallQualifierPolymorphism(processingEnv, this); } - */ - List parameterTypes = type.getParameterTypes(); - for (int i = 0; i < parameterTypes.size(); i++) { - Element paramDecl = declaration.getParameters().get(i); - if (noLightweightOwnership || getDeclAnnotation(paramDecl, Owning.class) == null) { - AnnotatedTypeMirror paramType = parameterTypes.get(i); - if (!paramType.hasAnnotation(POLY)) { - paramType.replaceAnnotation(TOP); + + /** + * Changes the type of each parameter not annotated as @Owning to @MustCallUnknown (top). Also + * replaces the component type of the varargs array, if applicable. + * + *

          This method is not responsible for handling receivers, which can never be owning. + * + * @param declaration a method or constructor declaration + * @param type the method or constructor's type + */ + private void changeNonOwningParameterTypesToTop( + ExecutableElement declaration, AnnotatedExecutableType type) { + // Formal parameters without a declared owning annotation are disregarded by the RLC + // _analysis_, as their @MustCall obligation is set to Top in this method. However, + // this computation is not desirable for RLC _inference_ in unannotated programs, + // where a goal is to infer and add @Owning annotations to formal parameters. + /* NO-AFU + if (getWholeProgramInference() != null && !isWpiEnabledForRLC()) { + return; + } + */ + List parameterTypes = type.getParameterTypes(); + for (int i = 0; i < parameterTypes.size(); i++) { + Element paramDecl = declaration.getParameters().get(i); + if (noLightweightOwnership || getDeclAnnotation(paramDecl, Owning.class) == null) { + AnnotatedTypeMirror paramType = parameterTypes.get(i); + if (!paramType.hasAnnotation(POLY)) { + paramType.replaceAnnotation(TOP); + } + } + } + if (declaration.isVarArgs()) { + // also modify the component type of a varargs array + AnnotatedTypeMirror varargsType = + ((AnnotatedArrayType) parameterTypes.get(parameterTypes.size() - 1)) + .getComponentType(); + if (!varargsType.hasAnnotation(POLY)) { + varargsType.replaceAnnotation(TOP); + } } - } } - if (declaration.isVarArgs()) { - // also modify the component type of a varargs array - AnnotatedTypeMirror varargsType = - ((AnnotatedArrayType) parameterTypes.get(parameterTypes.size() - 1)).getComponentType(); - if (!varargsType.hasAnnotation(POLY)) { - varargsType.replaceAnnotation(TOP); - } + + @Override + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new MustCallDefaultQualifierForUseTypeAnnotator(); + } + + /** + * Returns the {@link MustCall#value} element. For use with {@link + * AnnotationUtils#getElementValueArray}. + * + * @return the {@link MustCall#value} element + */ + public ExecutableElement getMustCallValueElement() { + return mustCallValueElement; } - } - - @Override - protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { - return new MustCallDefaultQualifierForUseTypeAnnotator(); - } - - /** - * Returns the {@link MustCall#value} element. For use with {@link - * AnnotationUtils#getElementValueArray}. - * - * @return the {@link MustCall#value} element - */ - public ExecutableElement getMustCallValueElement() { - return mustCallValueElement; - } - - /** Support @InheritableMustCall meaning @MustCall on all subtype elements. */ - private class MustCallDefaultQualifierForUseTypeAnnotator - extends DefaultQualifierForUseTypeAnnotator { - - /** Creates a {@code MustCallDefaultQualifierForUseTypeAnnotator}. */ - public MustCallDefaultQualifierForUseTypeAnnotator() { - super(MustCallAnnotatedTypeFactory.this); + + /** Support @InheritableMustCall meaning @MustCall on all subtype elements. */ + private class MustCallDefaultQualifierForUseTypeAnnotator + extends DefaultQualifierForUseTypeAnnotator { + + /** Creates a {@code MustCallDefaultQualifierForUseTypeAnnotator}. */ + public MustCallDefaultQualifierForUseTypeAnnotator() { + super(MustCallAnnotatedTypeFactory.this); + } + + @Override + protected AnnotationMirrorSet getExplicitAnnos(Element element) { + AnnotationMirrorSet explict = super.getExplicitAnnos(element); + if (explict.isEmpty() && ElementUtils.isTypeElement(element)) { + AnnotationMirror inheritableMustCall = + getDeclAnnotation(element, InheritableMustCall.class); + if (inheritableMustCall != null) { + List mustCallVal = + AnnotationUtils.getElementValueArray( + inheritableMustCall, + inheritableMustCallValueElement, + String.class); + return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); + } + } + return explict; + } } @Override - protected AnnotationMirrorSet getExplicitAnnos(Element element) { - AnnotationMirrorSet explict = super.getExplicitAnnos(element); - if (explict.isEmpty() && ElementUtils.isTypeElement(element)) { - AnnotationMirror inheritableMustCall = - getDeclAnnotation(element, InheritableMustCall.class); - if (inheritableMustCall != null) { - List mustCallVal = - AnnotationUtils.getElementValueArray( - inheritableMustCall, inheritableMustCallValueElement, String.class); - return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); + protected QualifierUpperBounds createQualifierUpperBounds() { + return new MustCallQualifierUpperBounds(); + } + + /** Support @InheritableMustCall meaning @MustCall on all subtypes. */ + private class MustCallQualifierUpperBounds extends QualifierUpperBounds { + + /** + * Creates a {@link QualifierUpperBounds} from the MustCall Checker the annotations that are + * in the type hierarchy. + */ + public MustCallQualifierUpperBounds() { + super(MustCallAnnotatedTypeFactory.this); + } + + @Override + protected AnnotationMirrorSet getAnnotationFromElement(Element element) { + AnnotationMirrorSet explict = super.getAnnotationFromElement(element); + if (!explict.isEmpty()) { + return explict; + } + AnnotationMirror inheritableMustCall = + getDeclAnnotation(element, InheritableMustCall.class); + if (inheritableMustCall != null) { + List mustCallVal = + AnnotationUtils.getElementValueArray( + inheritableMustCall, inheritableMustCallValueElement, String.class); + return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); + } + return AnnotationMirrorSet.emptySet(); } - } - return explict; } - } - @Override - protected QualifierUpperBounds createQualifierUpperBounds() { - return new MustCallQualifierUpperBounds(); - } + /** + * Cache of the MustCall annotations that have actually been created. Most programs require few + * distinct MustCall annotations (e.g. MustCall() and MustCall("close")). + */ + private final Map, AnnotationMirror> mustCallAnnotations = new HashMap<>(10); - /** Support @InheritableMustCall meaning @MustCall on all subtypes. */ - private class MustCallQualifierUpperBounds extends QualifierUpperBounds { + /** + * Creates a {@link MustCall} annotation whose values are the given strings. + * + * @param val the methods that should be called + * @return an annotation indicating that the given methods should be called + */ + public AnnotationMirror createMustCall(List val) { + return mustCallAnnotations.computeIfAbsent(val, this::createMustCallImpl); + } /** - * Creates a {@link QualifierUpperBounds} from the MustCall Checker the annotations that are in - * the type hierarchy. + * Creates a {@link MustCall} annotation whose values are the given strings. + * + *

          This internal version bypasses the cache, and is only used for new annotations. + * + * @param methodList the methods that should be called + * @return an annotation indicating that the given methods should be called */ - public MustCallQualifierUpperBounds() { - super(MustCallAnnotatedTypeFactory.this); + private AnnotationMirror createMustCallImpl(List methodList) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MustCall.class); + String[] methodArray = methodList.toArray(new String[0]); + Arrays.sort(methodArray); + builder.setValue("value", methodArray); + return builder.build(); } @Override - protected AnnotationMirrorSet getAnnotationFromElement(Element element) { - AnnotationMirrorSet explict = super.getAnnotationFromElement(element); - if (!explict.isEmpty()) { - return explict; - } - AnnotationMirror inheritableMustCall = getDeclAnnotation(element, InheritableMustCall.class); - if (inheritableMustCall != null) { - List mustCallVal = - AnnotationUtils.getElementValueArray( - inheritableMustCall, inheritableMustCallValueElement, String.class); - return AnnotationMirrorSet.singleton(createMustCall(mustCallVal)); - } - return AnnotationMirrorSet.emptySet(); + protected QualifierHierarchy createQualifierHierarchy() { + return new MustCallQualifierHierarchy( + this.getSupportedTypeQualifiers(), this.getProcessingEnv(), this); } - } - - /** - * Cache of the MustCall annotations that have actually been created. Most programs require few - * distinct MustCall annotations (e.g. MustCall() and MustCall("close")). - */ - private final Map, AnnotationMirror> mustCallAnnotations = new HashMap<>(10); - - /** - * Creates a {@link MustCall} annotation whose values are the given strings. - * - * @param val the methods that should be called - * @return an annotation indicating that the given methods should be called - */ - public AnnotationMirror createMustCall(List val) { - return mustCallAnnotations.computeIfAbsent(val, this::createMustCallImpl); - } - - /** - * Creates a {@link MustCall} annotation whose values are the given strings. - * - *

          This internal version bypasses the cache, and is only used for new annotations. - * - * @param methodList the methods that should be called - * @return an annotation indicating that the given methods should be called - */ - private AnnotationMirror createMustCallImpl(List methodList) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MustCall.class); - String[] methodArray = methodList.toArray(new String[0]); - Arrays.sort(methodArray); - builder.setValue("value", methodArray); - return builder.build(); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new MustCallQualifierHierarchy( - this.getSupportedTypeQualifiers(), this.getProcessingEnv(), this); - } - - /** - * Fetches the store from the results of dataflow for {@code first}. If {@code afterFirstStore} is - * true, then the store after {@code first} is returned; if {@code afterFirstStore} is false, the - * store before {@code succ} is returned. - * - * @param afterFirstStore whether to use the store after the first block or the store before its - * successor, succ - * @param first a block - * @param succ first's successor - * @return the appropriate CFStore, populated with MustCall annotations, from the results of - * running dataflow - */ - public CFStore getStoreForBlock(boolean afterFirstStore, Block first, Block succ) { - return afterFirstStore ? flowResult.getStoreAfter(first) : flowResult.getStoreBefore(succ); - } - - /** - * Returns the CreatesMustCallFor.value field/element. - * - * @return the CreatesMustCallFor.value field/element - */ - @Override - public ExecutableElement getCreatesMustCallForValueElement() { - return createsMustCallForValueElement; - } - - /** - * Returns the CreatesMustCallFor.List.value field/element. - * - * @return the CreatesMustCallFor.List.value field/element - */ - @Override - public ExecutableElement getCreatesMustCallForListValueElement() { - return createsMustCallForListValueElement; - } - - /** - * The TreeAnnotator for the MustCall type system. - * - *

          This tree annotator treats non-owning method parameters as bottom, regardless of their - * declared type, when they appear in the body of the method. Doing so is safe because being - * non-owning means, by definition, that their must-call obligations are only relevant in the - * callee. (This behavior is disabled if the {@code -AnoLightweightOwnership} option is passed to - * the checker.) - * - *

          The tree annotator also changes the type of resource variables to remove "close" from their - * must-call types, because the try-with-resources statement guarantees that close() is called on - * all such variables. - */ - private class MustCallTreeAnnotator extends TreeAnnotator { + /** - * Create a MustCallTreeAnnotator. + * Fetches the store from the results of dataflow for {@code first}. If {@code afterFirstStore} + * is true, then the store after {@code first} is returned; if {@code afterFirstStore} is false, + * the store before {@code succ} is returned. * - * @param mustCallAnnotatedTypeFactory the type factory + * @param afterFirstStore whether to use the store after the first block or the store before its + * successor, succ + * @param first a block + * @param succ first's successor + * @return the appropriate CFStore, populated with MustCall annotations, from the results of + * running dataflow */ - public MustCallTreeAnnotator(MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory) { - super(mustCallAnnotatedTypeFactory); + public CFStore getStoreForBlock(boolean afterFirstStore, Block first, Block succ) { + return afterFirstStore ? flowResult.getStoreAfter(first) : flowResult.getStoreBefore(succ); } + /** + * Returns the CreatesMustCallFor.value field/element. + * + * @return the CreatesMustCallFor.value field/element + */ @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - Element elt = TreeUtils.elementFromUse(tree); - // The following changes are not desired for RLC _inference_ in unannotated programs, - // where a goal is to infer and add @Owning annotations to formal parameters. - // Therefore, if WPI is enabled, they should not be executed. - if ( // NO-AFU getWholeProgramInference() == null && - elt.getKind() == ElementKind.PARAMETER - && (noLightweightOwnership || getDeclAnnotation(elt, Owning.class) == null)) { - if (!type.hasAnnotation(POLY)) { - // Parameters that are not annotated with @Owning should be treated as bottom - // (to suppress warnings about them). An exception is polymorphic parameters, - // which might be @MustCallAlias (and so wouldn't be annotated with @Owning): - // these are not modified, to support verification of @MustCallAlias - // annotations. - type.replaceAnnotation(BOTTOM); + public ExecutableElement getCreatesMustCallForValueElement() { + return createsMustCallForValueElement; + } + + /** + * Returns the CreatesMustCallFor.List.value field/element. + * + * @return the CreatesMustCallFor.List.value field/element + */ + @Override + public ExecutableElement getCreatesMustCallForListValueElement() { + return createsMustCallForListValueElement; + } + + /** + * The TreeAnnotator for the MustCall type system. + * + *

          This tree annotator treats non-owning method parameters as bottom, regardless of their + * declared type, when they appear in the body of the method. Doing so is safe because being + * non-owning means, by definition, that their must-call obligations are only relevant in the + * callee. (This behavior is disabled if the {@code -AnoLightweightOwnership} option is passed + * to the checker.) + * + *

          The tree annotator also changes the type of resource variables to remove "close" from + * their must-call types, because the try-with-resources statement guarantees that close() is + * called on all such variables. + */ + private class MustCallTreeAnnotator extends TreeAnnotator { + /** + * Create a MustCallTreeAnnotator. + * + * @param mustCallAnnotatedTypeFactory the type factory + */ + public MustCallTreeAnnotator(MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory) { + super(mustCallAnnotatedTypeFactory); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + Element elt = TreeUtils.elementFromUse(tree); + // The following changes are not desired for RLC _inference_ in unannotated programs, + // where a goal is to infer and add @Owning annotations to formal parameters. + // Therefore, if WPI is enabled, they should not be executed. + if ( // NO-AFU getWholeProgramInference() == null && + elt.getKind() == ElementKind.PARAMETER + && (noLightweightOwnership || getDeclAnnotation(elt, Owning.class) == null)) { + if (!type.hasAnnotation(POLY)) { + // Parameters that are not annotated with @Owning should be treated as bottom + // (to suppress warnings about them). An exception is polymorphic parameters, + // which might be @MustCallAlias (and so wouldn't be annotated with @Owning): + // these are not modified, to support verification of @MustCallAlias + // annotations. + type.replaceAnnotation(BOTTOM); + } + } + return super.visitIdentifier(tree, type); } - } - return super.visitIdentifier(tree, type); } - } - - /** - * Return the temporary variable for node, if it exists. See {@code #tempVars}. - * - * @param node a CFG node - * @return the corresponding temporary variable, or null if there is not one - */ - public @Nullable LocalVariableNode getTempVar(Node node) { - return tempVars.get(node.getTree()); - } - - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; - } - */ - - /** - * Returns true if the given type should never have a must-call obligation. - * - * @param type the type to check - * @return true if the given type should never have a must-call obligation - */ - public boolean shouldHaveNoMustCallObligation(TypeMirror type) { - return type.getKind().isPrimitive() || TypesUtils.isClass(type) || TypesUtils.isString(type); - } - - /** Qualifier hierarchy for the Must Call Checker. */ - private class MustCallQualifierHierarchy extends SubtypeIsSubsetQualifierHierarchy { /** - * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. + * Return the temporary variable for node, if it exists. See {@code #tempVars}. * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param processingEnv processing environment - * @param atypeFactory the associated type factory + * @param node a CFG node + * @return the corresponding temporary variable, or null if there is not one */ - public MustCallQualifierHierarchy( - Collection> qualifierClasses, - ProcessingEnvironment processingEnv, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, processingEnv, atypeFactory); + public @Nullable LocalVariableNode getTempVar(Node node) { + return tempVars.get(node.getTree()); } - @Override - public boolean isSubtypeShallow( - AnnotationMirror subQualifier, - TypeMirror subType, - AnnotationMirror superQualifier, - TypeMirror superType) { - if (shouldHaveNoMustCallObligation(subType) || shouldHaveNoMustCallObligation(superType)) { - return true; - } - return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; } + */ - @Override - public @Nullable AnnotationMirror leastUpperBoundShallow( - AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { - boolean tm1NoMustCall = shouldHaveNoMustCallObligation(tm1); - boolean tm2NoMustCall = shouldHaveNoMustCallObligation(tm2); - if (tm1NoMustCall == tm2NoMustCall) { - return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); - } else if (tm1NoMustCall) { - return qualifier1; - } else { // if (tm2NoMustCall) { - return qualifier2; - } + /** + * Returns true if the given type should never have a must-call obligation. + * + * @param type the type to check + * @return true if the given type should never have a must-call obligation + */ + public boolean shouldHaveNoMustCallObligation(TypeMirror type) { + return type.getKind().isPrimitive() + || TypesUtils.isClass(type) + || TypesUtils.isString(type); + } + + /** Qualifier hierarchy for the Must Call Checker. */ + private class MustCallQualifierHierarchy extends SubtypeIsSubsetQualifierHierarchy { + + /** + * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param processingEnv processing environment + * @param atypeFactory the associated type factory + */ + public MustCallQualifierHierarchy( + Collection> qualifierClasses, + ProcessingEnvironment processingEnv, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, processingEnv, atypeFactory); + } + + @Override + public boolean isSubtypeShallow( + AnnotationMirror subQualifier, + TypeMirror subType, + AnnotationMirror superQualifier, + TypeMirror superType) { + if (shouldHaveNoMustCallObligation(subType) + || shouldHaveNoMustCallObligation(superType)) { + return true; + } + return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); + } + + @Override + public @Nullable AnnotationMirror leastUpperBoundShallow( + AnnotationMirror qualifier1, + TypeMirror tm1, + AnnotationMirror qualifier2, + TypeMirror tm2) { + boolean tm1NoMustCall = shouldHaveNoMustCallObligation(tm1); + boolean tm2NoMustCall = shouldHaveNoMustCallObligation(tm2); + if (tm1NoMustCall == tm2NoMustCall) { + return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); + } else if (tm1NoMustCall) { + return qualifier1; + } else { // if (tm2NoMustCall) { + return qualifier2; + } + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java index 617fe571db6..85265b53d55 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallChecker.java @@ -10,33 +10,33 @@ * another. The Resource Leak Checker verifies that the given methods are actually called. */ @StubFiles({ - "IOUtils.astub", - "JavaEE.astub", - "JdkCompiler.astub", - "Reflection.astub", - "SocketCreatesMustCallFor.astub", + "IOUtils.astub", + "JavaEE.astub", + "JdkCompiler.astub", + "Reflection.astub", + "SocketCreatesMustCallFor.astub", }) @SupportedOptions({ - MustCallChecker.NO_CREATES_MUSTCALLFOR, - MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, - MustCallChecker.NO_RESOURCE_ALIASES + MustCallChecker.NO_CREATES_MUSTCALLFOR, + MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, + MustCallChecker.NO_RESOURCE_ALIASES }) public class MustCallChecker extends BaseTypeChecker { - /** - * Disables @CreatesMustCallFor support. Not of interest to most users. Not documented in the - * manual. - */ - public static final String NO_CREATES_MUSTCALLFOR = "noCreatesMustCallFor"; + /** + * Disables @CreatesMustCallFor support. Not of interest to most users. Not documented in the + * manual. + */ + public static final String NO_CREATES_MUSTCALLFOR = "noCreatesMustCallFor"; - /** - * Disables @Owning/@NotOwning support. Not of interest to most users. Not documented in the - * manual. - */ - public static final String NO_LIGHTWEIGHT_OWNERSHIP = "noLightweightOwnership"; + /** + * Disables @Owning/@NotOwning support. Not of interest to most users. Not documented in the + * manual. + */ + public static final String NO_LIGHTWEIGHT_OWNERSHIP = "noLightweightOwnership"; - /** - * Disables @MustCallAlias support. Not of interest to most users. Not documented in the manual. - */ - public static final String NO_RESOURCE_ALIASES = "noResourceAliases"; + /** + * Disables @MustCallAlias support. Not of interest to most users. Not documented in the manual. + */ + public static final String NO_RESOURCE_ALIASES = "noResourceAliases"; } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java index 6affabc60f1..53b04aade45 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallNoCreatesMustCallForChecker.java @@ -11,15 +11,15 @@ *

          The only difference is the contents of the @StubFiles annotation. */ @StubFiles({ - "JavaEE.astub", - "Reflection.astub", + "JavaEE.astub", + "Reflection.astub", }) @SuppressWarningsPrefix({ - // Preferred checkername, so that warnings are suppressed regardless of the option passed. - "mustcall", - // Also supported, but will only suppress warnings from this checker (and not from the regular - // Must Call Checker). - "mustcallnocreatesmustcallfor" + // Preferred checkername, so that warnings are suppressed regardless of the option passed. + "mustcall", + // Also supported, but will only suppress warnings from this checker (and not from the regular + // Must Call Checker). + "mustcallnocreatesmustcallfor" }) @SupportedOptions({MustCallChecker.NO_CREATES_MUSTCALLFOR}) public class MustCallNoCreatesMustCallForChecker extends MustCallChecker {} diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java index ed606c0cfdc..e5b56c6ee98 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java @@ -4,12 +4,7 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.TransferInput; @@ -31,6 +26,14 @@ import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.trees.TreeBuilder; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; + /** * Transfer function for the must-call type system. Its primary purposes are (1) to create temporary * variables for expressions (which allow those expressions to have refined information in the @@ -39,298 +42,300 @@ */ public class MustCallTransfer extends CFTransfer { - /** For building new AST nodes. */ - private final TreeBuilder treeBuilder; + /** For building new AST nodes. */ + private final TreeBuilder treeBuilder; - /** The type factory. */ - private final MustCallAnnotatedTypeFactory atypeFactory; + /** The type factory. */ + private final MustCallAnnotatedTypeFactory atypeFactory; - /** - * A cache for the default type for java.lang.String, to avoid needing to look it up for every - * implicit string conversion. See {@link #getDefaultStringType(StringConversionNode)}. - */ - private @MonotonicNonNull AnnotationMirror defaultStringType; + /** + * A cache for the default type for java.lang.String, to avoid needing to look it up for every + * implicit string conversion. See {@link #getDefaultStringType(StringConversionNode)}. + */ + private @MonotonicNonNull AnnotationMirror defaultStringType; - /** True if -AnoCreatesMustCallFor was passed on the command line. */ - private final boolean noCreatesMustCallFor; + /** True if -AnoCreatesMustCallFor was passed on the command line. */ + private final boolean noCreatesMustCallFor; - /* NO-AFU - * True if -AenableWpiForRlc was passed on the command line. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - private final boolean enableWpiForRlc; - */ + /* NO-AFU + * True if -AenableWpiForRlc was passed on the command line. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + private final boolean enableWpiForRlc; + */ - /** - * Create a MustCallTransfer. - * - * @param analysis the analysis - */ - public MustCallTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (MustCallAnnotatedTypeFactory) analysis.getTypeFactory(); - noCreatesMustCallFor = - atypeFactory.getChecker().hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); - // enableWpiForRlc = - // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); - ProcessingEnvironment env = atypeFactory.getChecker().getProcessingEnvironment(); - treeBuilder = new TreeBuilder(env); - } + /** + * Create a MustCallTransfer. + * + * @param analysis the analysis + */ + public MustCallTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (MustCallAnnotatedTypeFactory) analysis.getTypeFactory(); + noCreatesMustCallFor = + atypeFactory.getChecker().hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); + // enableWpiForRlc = + // atypeFactory.getChecker().hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); + ProcessingEnvironment env = atypeFactory.getChecker().getProcessingEnvironment(); + treeBuilder = new TreeBuilder(env); + } - @Override - public TransferResult visitStringConversion( - StringConversionNode n, TransferInput p) { - // Implicit String conversions should assume that the String's type is - // whatever the default for String is, not that the conversion is polymorphic. - TransferResult result = super.visitStringConversion(n, p); - LocalVariableNode temp = getOrCreateTempVar(n); - if (temp != null) { - AnnotationMirror defaultStringType = getDefaultStringType(n); - JavaExpression localExp = JavaExpression.fromNode(temp); - insertIntoStores(result, localExp, defaultStringType); + @Override + public TransferResult visitStringConversion( + StringConversionNode n, TransferInput p) { + // Implicit String conversions should assume that the String's type is + // whatever the default for String is, not that the conversion is polymorphic. + TransferResult result = super.visitStringConversion(n, p); + LocalVariableNode temp = getOrCreateTempVar(n); + if (temp != null) { + AnnotationMirror defaultStringType = getDefaultStringType(n); + JavaExpression localExp = JavaExpression.fromNode(temp); + insertIntoStores(result, localExp, defaultStringType); + } + return result; } - return result; - } - /** - * Returns the default type for java.lang.String, which is cached in this class to avoid - * recomputing it. If the cache is currently unset, this method sets it. - * - * @param n a string conversion node, from which the type is computed if it is required - * @return the type of java.lang.String - */ - private AnnotationMirror getDefaultStringType(StringConversionNode n) { - if (this.defaultStringType == null) { - this.defaultStringType = - atypeFactory - .getAnnotatedType(TypesUtils.getTypeElement(n.getType())) - .getAnnotationInHierarchy(atypeFactory.TOP); - assert this.defaultStringType != null : "@AssumeAssertion(nullness): same hierarchy"; + /** + * Returns the default type for java.lang.String, which is cached in this class to avoid + * recomputing it. If the cache is currently unset, this method sets it. + * + * @param n a string conversion node, from which the type is computed if it is required + * @return the type of java.lang.String + */ + private AnnotationMirror getDefaultStringType(StringConversionNode n) { + if (this.defaultStringType == null) { + this.defaultStringType = + atypeFactory + .getAnnotatedType(TypesUtils.getTypeElement(n.getType())) + .getAnnotationInHierarchy(atypeFactory.TOP); + assert this.defaultStringType != null : "@AssumeAssertion(nullness): same hierarchy"; + } + return this.defaultStringType; } - return this.defaultStringType; - } - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = super.visitMethodInvocation(n, in); + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = super.visitMethodInvocation(n, in); - updateStoreWithTempVar(result, n); - if (!noCreatesMustCallFor) { - List targetExprs = - CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( - n, atypeFactory, atypeFactory); - for (JavaExpression targetExpr : targetExprs) { - AnnotationMirror defaultType = - atypeFactory - .getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) - .getAnnotationInHierarchy(atypeFactory.TOP); + updateStoreWithTempVar(result, n); + if (!noCreatesMustCallFor) { + List targetExprs = + CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( + n, atypeFactory, atypeFactory); + for (JavaExpression targetExpr : targetExprs) { + AnnotationMirror defaultType = + atypeFactory + .getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) + .getAnnotationInHierarchy(atypeFactory.TOP); - if (result.containsTwoStores()) { - CFStore thenStore = result.getThenStore(); - lubWithStoreValue(thenStore, targetExpr, defaultType); + if (result.containsTwoStores()) { + CFStore thenStore = result.getThenStore(); + lubWithStoreValue(thenStore, targetExpr, defaultType); - CFStore elseStore = result.getElseStore(); - lubWithStoreValue(elseStore, targetExpr, defaultType); - } else { - CFStore store = result.getRegularStore(); - lubWithStoreValue(store, targetExpr, defaultType); + CFStore elseStore = result.getElseStore(); + lubWithStoreValue(elseStore, targetExpr, defaultType); + } else { + CFStore store = result.getRegularStore(); + lubWithStoreValue(store, targetExpr, defaultType); + } + } } - } + return result; } - return result; - } - - /** - * Computes the LUB of the current value in the store for expr, if it exists, and defaultType. - * Inserts that LUB into the store as the new value for expr. - * - * @param store a CFStore - * @param expr an expression that might be in the store - * @param defaultType the default type of the expression's static type - */ - private void lubWithStoreValue(CFStore store, JavaExpression expr, AnnotationMirror defaultType) { - CFValue value = store.getValue(expr); - CFValue defaultTypeAsCFValue = - analysis.createSingleAnnotationValue(defaultType, expr.getType()); - CFValue newValue = defaultTypeAsCFValue.leastUpperBound(value); - store.replaceValue(expr, newValue); - } - /* NO-AFU - * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @param tree a tree - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the - * result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree tree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; + /** + * Computes the LUB of the current value in the store for expr, if it exists, and defaultType. + * Inserts that LUB into the store as the new value for expr. + * + * @param store a CFStore + * @param expr an expression that might be in the store + * @param defaultType the default type of the expression's static type + */ + private void lubWithStoreValue( + CFStore store, JavaExpression expr, AnnotationMirror defaultType) { + CFValue value = store.getValue(expr); + CFValue defaultTypeAsCFValue = + analysis.createSingleAnnotationValue(defaultType, expr.getType()); + CFValue newValue = defaultTypeAsCFValue.leastUpperBound(value); + store.replaceValue(expr, newValue); } - return super.shouldPerformWholeProgramInference(tree); - } - /* - /* NO-AFU - * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @param expressionTree a tree - * @param lhsTree its element - * @return false if Resource Leak Checker is running as one of the upstream checkers and the - * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the - * result of the super call - */ - /* NO-AFU - @Override - protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { - if (!isWpiEnabledForRLC() - && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { - return false; + /* NO-AFU + * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @param tree a tree + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the + * result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree tree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; + } + return super.shouldPerformWholeProgramInference(tree); } - return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); - } - */ + /* - @Override - public TransferResult visitObjectCreation( - ObjectCreationNode node, TransferInput input) { - TransferResult result = super.visitObjectCreation(node, input); - updateStoreWithTempVar(result, node); - return result; - } + /* NO-AFU + * See {@link ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @param expressionTree a tree + * @param lhsTree its element + * @return false if Resource Leak Checker is running as one of the upstream checkers and the + * -AenableWpiForRlc flag is not passed as a command line argument, otherwise returns the + * result of the super call + */ + /* NO-AFU + @Override + protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { + if (!isWpiEnabledForRLC() + && atypeFactory.getCheckerNames().contains(ResourceLeakChecker.class.getCanonicalName())) { + return false; + } + return super.shouldPerformWholeProgramInference(expressionTree, lhsTree); + } + */ - @Override - public TransferResult visitTernaryExpression( - TernaryExpressionNode node, TransferInput input) { - TransferResult result = super.visitTernaryExpression(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - atypeFactory.tempVars.put(node.getTree(), node.getTernaryExpressionVar()); + @Override + public TransferResult visitObjectCreation( + ObjectCreationNode node, TransferInput input) { + TransferResult result = super.visitObjectCreation(node, input); + updateStoreWithTempVar(result, node); + return result; } - return result; - } - @Override - public TransferResult visitSwitchExpressionNode( - SwitchExpressionNode node, TransferInput input) { - TransferResult result = super.visitSwitchExpressionNode(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - atypeFactory.tempVars.put(node.getTree(), node.getSwitchExpressionVar()); + @Override + public TransferResult visitTernaryExpression( + TernaryExpressionNode node, TransferInput input) { + TransferResult result = super.visitTernaryExpression(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + atypeFactory.tempVars.put(node.getTree(), node.getTernaryExpressionVar()); + } + return result; } - return result; - } - /** - * This method either creates or looks up the temp var t for node, and then updates the store to - * give t the same type as {@code node}. - * - * @param node the node to be assigned to a temporary variable - * @param result the transfer result containing the store to be modified - */ - public void updateStoreWithTempVar(TransferResult result, Node node) { - // Must-call obligations on primitives are not supported. - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - LocalVariableNode temp = getOrCreateTempVar(node); - if (temp != null) { - JavaExpression localExp = JavaExpression.fromNode(temp); - AnnotationMirror anm = - atypeFactory - .getAnnotatedType(node.getTree()) - .getAnnotationInHierarchy(atypeFactory.TOP); - insertIntoStores(result, localExp, anm == null ? atypeFactory.TOP : anm); - } + @Override + public TransferResult visitSwitchExpressionNode( + SwitchExpressionNode node, TransferInput input) { + TransferResult result = super.visitSwitchExpressionNode(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + atypeFactory.tempVars.put(node.getTree(), node.getSwitchExpressionVar()); + } + return result; } - } - /** - * Either returns the temporary variable associated with node, or creates one if one does not - * exist. - * - * @param node a node, which must be an expression (not a statement) - * @return a temporary variable node representing {@code node} that can be placed into a store - */ - private @Nullable LocalVariableNode getOrCreateTempVar(Node node) { - LocalVariableNode localVariableNode = atypeFactory.tempVars.get(node.getTree()); - if (localVariableNode == null) { - VariableTree temp = createTemporaryVar(node); - if (temp != null) { - IdentifierTree identifierTree = treeBuilder.buildVariableUse(temp); - localVariableNode = new LocalVariableNode(identifierTree); - localVariableNode.setInSource(true); - atypeFactory.tempVars.put(node.getTree(), localVariableNode); - } + /** + * This method either creates or looks up the temp var t for node, and then updates the store to + * give t the same type as {@code node}. + * + * @param node the node to be assigned to a temporary variable + * @param result the transfer result containing the store to be modified + */ + public void updateStoreWithTempVar(TransferResult result, Node node) { + // Must-call obligations on primitives are not supported. + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + LocalVariableNode temp = getOrCreateTempVar(node); + if (temp != null) { + JavaExpression localExp = JavaExpression.fromNode(temp); + AnnotationMirror anm = + atypeFactory + .getAnnotatedType(node.getTree()) + .getAnnotationInHierarchy(atypeFactory.TOP); + insertIntoStores(result, localExp, anm == null ? atypeFactory.TOP : anm); + } + } } - return localVariableNode; - } - /** - * Creates a variable declaration for the given expression node, if possible. - * - *

          Note that error reporting code assumes that the names of temporary variables are not legal - * Java identifiers (see JLS 3.8). The - * temporary variable names generated here include an {@code '-'} character to make the names - * invalid. - * - * @param node an expression node - * @return a variable tree for the node, or null if an appropriate containing element cannot be - * located - */ - protected @Nullable VariableTree createTemporaryVar(Node node) { - ExpressionTree tree = (ExpressionTree) node.getTree(); - TypeMirror treeType = TreeUtils.typeOf(tree); - Element enclosingElement; - TreePath path = atypeFactory.getPath(tree); - if (path == null) { - enclosingElement = TreeUtils.elementFromUse(tree).getEnclosingElement(); - } else { - // Issue 6473 - // Adjusts handling of nearest enclosing element for temporary variables. - // This approach ensures the correct enclosing element (method or class) is determined. - enclosingElement = TreePathUtil.findNearestEnclosingElement(path); + /** + * Either returns the temporary variable associated with node, or creates one if one does not + * exist. + * + * @param node a node, which must be an expression (not a statement) + * @return a temporary variable node representing {@code node} that can be placed into a store + */ + private @Nullable LocalVariableNode getOrCreateTempVar(Node node) { + LocalVariableNode localVariableNode = atypeFactory.tempVars.get(node.getTree()); + if (localVariableNode == null) { + VariableTree temp = createTemporaryVar(node); + if (temp != null) { + IdentifierTree identifierTree = treeBuilder.buildVariableUse(temp); + localVariableNode = new LocalVariableNode(identifierTree); + localVariableNode.setInSource(true); + atypeFactory.tempVars.put(node.getTree(), localVariableNode); + } + } + return localVariableNode; } - if (enclosingElement == null) { - return null; + + /** + * Creates a variable declaration for the given expression node, if possible. + * + *

          Note that error reporting code assumes that the names of temporary variables are not legal + * Java identifiers (see JLS 3.8). + * The temporary variable names generated here include an {@code '-'} character to make the + * names invalid. + * + * @param node an expression node + * @return a variable tree for the node, or null if an appropriate containing element cannot be + * located + */ + protected @Nullable VariableTree createTemporaryVar(Node node) { + ExpressionTree tree = (ExpressionTree) node.getTree(); + TypeMirror treeType = TreeUtils.typeOf(tree); + Element enclosingElement; + TreePath path = atypeFactory.getPath(tree); + if (path == null) { + enclosingElement = TreeUtils.elementFromUse(tree).getEnclosingElement(); + } else { + // Issue 6473 + // Adjusts handling of nearest enclosing element for temporary variables. + // This approach ensures the correct enclosing element (method or class) is determined. + enclosingElement = TreePathUtil.findNearestEnclosingElement(path); + } + if (enclosingElement == null) { + return null; + } + // Declare and initialize a new, unique variable + VariableTree tmpVarTree = + treeBuilder.buildVariableDecl( + treeType, uniqueName("temp-var"), enclosingElement, tree); + return tmpVarTree; } - // Declare and initialize a new, unique variable - VariableTree tmpVarTree = - treeBuilder.buildVariableDecl(treeType, uniqueName("temp-var"), enclosingElement, tree); - return tmpVarTree; - } - /** A unique identifier counter for node names. */ - private static AtomicLong uid = new AtomicLong(); + /** A unique identifier counter for node names. */ + private static AtomicLong uid = new AtomicLong(); - /** - * Creates a unique, arbitrary string that can be used as a name for a temporary variable, using - * the given prefix. Can be used up to Long.MAX_VALUE times. - * - *

          Note that the correctness of the Resource Leak Checker depends on these names actually being - * unique, because {@code LocalVariableNode}s derived from them are used as keys in a map. - * - * @param prefix the prefix for the name - * @return a unique name that starts with the prefix - */ - protected String uniqueName(String prefix) { - return prefix + "-" + uid.getAndIncrement(); - } + /** + * Creates a unique, arbitrary string that can be used as a name for a temporary variable, using + * the given prefix. Can be used up to Long.MAX_VALUE times. + * + *

          Note that the correctness of the Resource Leak Checker depends on these names actually + * being unique, because {@code LocalVariableNode}s derived from them are used as keys in a map. + * + * @param prefix the prefix for the name + * @return a unique name that starts with the prefix + */ + protected String uniqueName(String prefix) { + return prefix + "-" + uid.getAndIncrement(); + } - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; - } - */ + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; + } + */ } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java index fb5cc66ec10..a60ac7db446 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTypeAnnotator.java @@ -6,18 +6,18 @@ /** Primitive types always have no must-call obligations. */ public class MustCallTypeAnnotator extends TypeAnnotator { - /** - * Create a MustCallTypeAnnotator. - * - * @param typeFactory the type factory - */ - protected MustCallTypeAnnotator(MustCallAnnotatedTypeFactory typeFactory) { - super(typeFactory); - } + /** + * Create a MustCallTypeAnnotator. + * + * @param typeFactory the type factory + */ + protected MustCallTypeAnnotator(MustCallAnnotatedTypeFactory typeFactory) { + super(typeFactory); + } - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - type.replaceAnnotation(((MustCallAnnotatedTypeFactory) atypeFactory).BOTTOM); - return super.visitPrimitive(type, aVoid); - } + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { + type.replaceAnnotation(((MustCallAnnotatedTypeFactory) atypeFactory).BOTTOM); + return super.visitPrimitive(type, aVoid); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java index 95a06e28630..c0f96db13ca 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallVisitor.java @@ -8,15 +8,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.ReturnTree; import com.sun.source.tree.Tree; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.MustCallAlias; @@ -34,6 +26,17 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + /** * The visitor for the Must Call Checker. This visitor is similar to BaseTypeVisitor, but overrides * methods that don't work well with the MustCall type hierarchy because it doesn't use the top type @@ -41,289 +44,309 @@ */ public class MustCallVisitor extends BaseTypeVisitor { - /** True if -AnoLightweightOwnership was passed on the command line. */ - private final boolean noLightweightOwnership; + /** True if -AnoLightweightOwnership was passed on the command line. */ + private final boolean noLightweightOwnership; - /** - * Creates a new MustCallVisitor. - * - * @param checker the type-checker associated with this visitor - */ - public MustCallVisitor(BaseTypeChecker checker) { - super(checker); - noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); - } + /** + * Creates a new MustCallVisitor. + * + * @param checker the type-checker associated with this visitor + */ + public MustCallVisitor(BaseTypeChecker checker) { + super(checker); + noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); + } - @Override - public Void visitReturn(ReturnTree tree, Void p) { - // Only check return types if ownership is being transferred. - if (!noLightweightOwnership) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(this.getCurrentPath()); - // enclosingMethod is null if this return site is inside a lambda. TODO: handle lambdas - // more precisely? - if (enclosingMethod != null) { - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(enclosingMethod); - AnnotationMirror notOwningAnno = atypeFactory.getDeclAnnotation(methodElt, NotOwning.class); - if (notOwningAnno != null) { - // Skip return type subtyping check, because not-owning pointer means Object - // Construction Checker won't check anyway. - return null; + @Override + public Void visitReturn(ReturnTree tree, Void p) { + // Only check return types if ownership is being transferred. + if (!noLightweightOwnership) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(this.getCurrentPath()); + // enclosingMethod is null if this return site is inside a lambda. TODO: handle lambdas + // more precisely? + if (enclosingMethod != null) { + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(enclosingMethod); + AnnotationMirror notOwningAnno = + atypeFactory.getDeclAnnotation(methodElt, NotOwning.class); + if (notOwningAnno != null) { + // Skip return type subtyping check, because not-owning pointer means Object + // Construction Checker won't check anyway. + return null; + } + } } - } + return super.visitReturn(tree, p); } - return super.visitReturn(tree, p); - } - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - // This code implements the following rule: - // * It is always safe to assign a MustCallAlias parameter of a constructor - // to an owning field of the containing class. - // It is necessary to special case this because MustCallAlias is translated - // into @PolyMustCall, so the common assignment check will fail when assigning - // an @MustCallAlias parameter to an owning field: the parameter is polymorphic, - // but the field is not. - ExpressionTree lhs = tree.getVariable(); - ExpressionTree rhs = tree.getExpression(); - Element lhsElt = TreeUtils.elementFromTree(lhs); - Element rhsElt = TreeUtils.elementFromTree(rhs); - if (lhsElt != null && rhsElt != null) { - // Note that it is not necessary to check that the assignment is to a field of this, - // because that is implied by the other conditions: - // * if the field is final, then the only place it can be assigned to is in the - // constructor of the proper object (enforced by javac). - // * if the field is not final, then it cannot be assigned to in a constructor at all: - // the @CreatesMustCallFor annotation cannot be written on a constructor (it has - // @Target({ElementType.METHOD})), so this code relies on the standard rules for - // non-final owning field reassignment, which prevent it without an - // @CreatesMustCallFor annotation except in the constructor of the object containing - // the field. - boolean lhsIsOwningField = - lhs.getKind() == Tree.Kind.MEMBER_SELECT - && atypeFactory.getDeclAnnotation(lhsElt, Owning.class) != null; - boolean rhsIsMCA = - AnnotationUtils.containsSameByClass(rhsElt.getAnnotationMirrors(), MustCallAlias.class); - boolean rhsIsConstructorParam = - rhsElt.getKind() == ElementKind.PARAMETER - && rhsElt.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR; - if (lhsIsOwningField && rhsIsMCA && rhsIsConstructorParam) { - // Do not execute common assignment check. - return null; - } + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + // This code implements the following rule: + // * It is always safe to assign a MustCallAlias parameter of a constructor + // to an owning field of the containing class. + // It is necessary to special case this because MustCallAlias is translated + // into @PolyMustCall, so the common assignment check will fail when assigning + // an @MustCallAlias parameter to an owning field: the parameter is polymorphic, + // but the field is not. + ExpressionTree lhs = tree.getVariable(); + ExpressionTree rhs = tree.getExpression(); + Element lhsElt = TreeUtils.elementFromTree(lhs); + Element rhsElt = TreeUtils.elementFromTree(rhs); + if (lhsElt != null && rhsElt != null) { + // Note that it is not necessary to check that the assignment is to a field of this, + // because that is implied by the other conditions: + // * if the field is final, then the only place it can be assigned to is in the + // constructor of the proper object (enforced by javac). + // * if the field is not final, then it cannot be assigned to in a constructor at all: + // the @CreatesMustCallFor annotation cannot be written on a constructor (it has + // @Target({ElementType.METHOD})), so this code relies on the standard rules for + // non-final owning field reassignment, which prevent it without an + // @CreatesMustCallFor annotation except in the constructor of the object containing + // the field. + boolean lhsIsOwningField = + lhs.getKind() == Tree.Kind.MEMBER_SELECT + && atypeFactory.getDeclAnnotation(lhsElt, Owning.class) != null; + boolean rhsIsMCA = + AnnotationUtils.containsSameByClass( + rhsElt.getAnnotationMirrors(), MustCallAlias.class); + boolean rhsIsConstructorParam = + rhsElt.getKind() == ElementKind.PARAMETER + && rhsElt.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR; + if (lhsIsOwningField && rhsIsMCA && rhsIsConstructorParam) { + // Do not execute common assignment check. + return null; + } + } + + return super.visitAssignment(tree, p); } - return super.visitAssignment(tree, p); - } + /** An empty string list. */ + private static final List emptyStringList = Collections.emptyList(); - /** An empty string list. */ - private static final List emptyStringList = Collections.emptyList(); + @Override + protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isClassTree(tree)) { + TypeElement classEle = TreeUtils.elementFromDeclaration((ClassTree) tree); + // If no @InheritableMustCall annotation is written here, `getDeclAnnotation()` gets one + // from stub files and supertypes. + AnnotationMirror anyInheritableMustCall = + atypeFactory.getDeclAnnotation(classEle, InheritableMustCall.class); + // An @InheritableMustCall annotation that is directly present. + AnnotationMirror directInheritableMustCall = + AnnotationUtils.getAnnotationByClass( + classEle.getAnnotationMirrors(), InheritableMustCall.class); + if (anyInheritableMustCall == null) { + if (!ElementUtils.isFinal(classEle)) { + // There is no @InheritableMustCall annotation on this or any superclass and + // this is a non-final class. + // If an explicit @MustCall annotation is present, issue a warning suggesting + // that @InheritableMustCall is probably what the programmer means, for + // usability. + if (atypeFactory.getDeclAnnotation(classEle, MustCall.class) != null) { + checker.reportWarning( + tree, + "mustcall.not.inheritable", + ElementUtils.getQualifiedName(classEle)); + } + } + } else { + // There is an @InheritableMustCall annotation on this, on a superclass, or in an + // annotation file. + // There are two possible problems: + // 1. There is an inconsistent @MustCall on this. + // 2. There is an explicit @InheritableMustCall here, and it is inconsistent with + // an @InheritableMustCall annotation on a supertype. - @Override - protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isClassTree(tree)) { - TypeElement classEle = TreeUtils.elementFromDeclaration((ClassTree) tree); - // If no @InheritableMustCall annotation is written here, `getDeclAnnotation()` gets one - // from stub files and supertypes. - AnnotationMirror anyInheritableMustCall = - atypeFactory.getDeclAnnotation(classEle, InheritableMustCall.class); - // An @InheritableMustCall annotation that is directly present. - AnnotationMirror directInheritableMustCall = - AnnotationUtils.getAnnotationByClass( - classEle.getAnnotationMirrors(), InheritableMustCall.class); - if (anyInheritableMustCall == null) { - if (!ElementUtils.isFinal(classEle)) { - // There is no @InheritableMustCall annotation on this or any superclass and - // this is a non-final class. - // If an explicit @MustCall annotation is present, issue a warning suggesting - // that @InheritableMustCall is probably what the programmer means, for - // usability. - if (atypeFactory.getDeclAnnotation(classEle, MustCall.class) != null) { - checker.reportWarning( - tree, "mustcall.not.inheritable", ElementUtils.getQualifiedName(classEle)); - } - } - } else { - // There is an @InheritableMustCall annotation on this, on a superclass, or in an - // annotation file. - // There are two possible problems: - // 1. There is an inconsistent @MustCall on this. - // 2. There is an explicit @InheritableMustCall here, and it is inconsistent with - // an @InheritableMustCall annotation on a supertype. + // Check for problem 1. + AnnotationMirror explicitMustCall = + atypeFactory + .fromElement(classEle) + .getAnnotationInHierarchy(atypeFactory.TOP); + if (explicitMustCall != null) { + // There is a @MustCall annotation here. - // Check for problem 1. - AnnotationMirror explicitMustCall = - atypeFactory.fromElement(classEle).getAnnotationInHierarchy(atypeFactory.TOP); - if (explicitMustCall != null) { - // There is a @MustCall annotation here. + List inheritableMustCallVal = + AnnotationUtils.getElementValueArray( + anyInheritableMustCall, + atypeFactory.inheritableMustCallValueElement, + String.class, + emptyStringList); + AnnotationMirror inheritedMCAnno = + atypeFactory.createMustCall(inheritableMustCallVal); - List inheritableMustCallVal = - AnnotationUtils.getElementValueArray( - anyInheritableMustCall, - atypeFactory.inheritableMustCallValueElement, - String.class, - emptyStringList); - AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(inheritableMustCallVal); + // Issue an error if there is an inconsistent, user-written @MustCall annotation + // here. + AnnotationMirror effectiveMCAnno = + type.getAnnotationInHierarchy(atypeFactory.TOP); + TypeMirror tm = type.getUnderlyingType(); + if (effectiveMCAnno != null + && !qualHierarchy.isSubtypeShallow( + inheritedMCAnno, effectiveMCAnno, tm)) { - // Issue an error if there is an inconsistent, user-written @MustCall annotation - // here. - AnnotationMirror effectiveMCAnno = type.getAnnotationInHierarchy(atypeFactory.TOP); - TypeMirror tm = type.getUnderlyingType(); - if (effectiveMCAnno != null - && !qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) { + checker.reportError( + tree, + "inconsistent.mustcall.subtype", + ElementUtils.getQualifiedName(classEle), + effectiveMCAnno, + anyInheritableMustCall); + return false; + } + } - checker.reportError( - tree, - "inconsistent.mustcall.subtype", - ElementUtils.getQualifiedName(classEle), - effectiveMCAnno, - anyInheritableMustCall); - return false; - } - } + // Check for problem 2. + if (directInheritableMustCall != null) { - // Check for problem 2. - if (directInheritableMustCall != null) { + // `inheritedImcs` is inherited @InheritableMustCall annotations. + List inheritedImcs = new ArrayList<>(); + for (TypeElement elt : + ElementUtils.getDirectSuperTypeElements(classEle, elements)) { + AnnotationMirror imc = + atypeFactory.getDeclAnnotation(elt, InheritableMustCall.class); + if (imc != null) { + inheritedImcs.add(imc); + } + } + if (!inheritedImcs.isEmpty()) { + // There is an inherited @InheritableMustCall annotation, in addition to the + // one written explicitly here. + List inheritedMustCallVal = new ArrayList<>(); + for (AnnotationMirror inheritedImc : inheritedImcs) { + inheritedMustCallVal.addAll( + AnnotationUtils.getElementValueArray( + inheritedImc, + atypeFactory.inheritableMustCallValueElement, + String.class)); + } + AnnotationMirror inheritedMCAnno = + atypeFactory.createMustCall(inheritedMustCallVal); - // `inheritedImcs` is inherited @InheritableMustCall annotations. - List inheritedImcs = new ArrayList<>(); - for (TypeElement elt : ElementUtils.getDirectSuperTypeElements(classEle, elements)) { - AnnotationMirror imc = atypeFactory.getDeclAnnotation(elt, InheritableMustCall.class); - if (imc != null) { - inheritedImcs.add(imc); - } - } - if (!inheritedImcs.isEmpty()) { - // There is an inherited @InheritableMustCall annotation, in addition to the - // one written explicitly here. - List inheritedMustCallVal = new ArrayList<>(); - for (AnnotationMirror inheritedImc : inheritedImcs) { - inheritedMustCallVal.addAll( - AnnotationUtils.getElementValueArray( - inheritedImc, atypeFactory.inheritableMustCallValueElement, String.class)); - } - AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(inheritedMustCallVal); + AnnotationMirror effectiveMCAnno = + type.getAnnotationInHierarchy(atypeFactory.TOP); - AnnotationMirror effectiveMCAnno = type.getAnnotationInHierarchy(atypeFactory.TOP); + TypeMirror tm = type.getUnderlyingType(); - TypeMirror tm = type.getUnderlyingType(); + if (!qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) { - if (!qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) { + checker.reportError( + tree, + "inconsistent.mustcall.subtype", + ElementUtils.getQualifiedName(classEle), + effectiveMCAnno, + inheritedMCAnno); + return false; + } + } + } + } + } + return super.validateType(tree, type); + } - checker.reportError( - tree, - "inconsistent.mustcall.subtype", - ElementUtils.getQualifiedName(classEle), - effectiveMCAnno, - inheritedMCAnno); - return false; + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // MustCallAlias annotations are always permitted on type uses, despite not technically + // being a part of the type hierarchy. It's necessary to get the annotation from the element + // because MustCallAlias is aliased to PolyMustCall, which is what useType would contain. + // Note that isValidUse does not need to consider component types, on which it should be + // called separately. + Element elt = TreeUtils.elementFromTree(tree); + if (elt != null) { + if (AnnotationUtils.containsSameByClass( + elt.getAnnotationMirrors(), MustCallAlias.class)) { + return true; + } + // Need to check the type mirror for ajava-derived annotations and the element itself + // for human-written annotations from the source code. Getting to the ajava file + // directly at this point is impossible, so we approximate "the ajava file has an + // @MustCallAlias annotation" with "there is an @PolyMustCall annotation on the use + // type, but not in the source code". This only works because none of our inference + // techniques infer @PolyMustCall, so if @PolyMustCall is present but wasn't in the + // source, it must have been derived from an @MustCallAlias annotation (which we do + // infer). + boolean ajavaFileHasMustCallAlias = + useType.hasAnnotation(PolyMustCall.class) + && !AnnotationUtils.containsSameByClass( + elt.getAnnotationMirrors(), PolyMustCall.class); + if (ajavaFileHasMustCallAlias) { + return true; } - } } - } + return super.isValidUse(declarationType, useType, tree); } - return super.validateType(tree, type); - } - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // MustCallAlias annotations are always permitted on type uses, despite not technically - // being a part of the type hierarchy. It's necessary to get the annotation from the element - // because MustCallAlias is aliased to PolyMustCall, which is what useType would contain. - // Note that isValidUse does not need to consider component types, on which it should be - // called separately. - Element elt = TreeUtils.elementFromTree(tree); - if (elt != null) { - if (AnnotationUtils.containsSameByClass(elt.getAnnotationMirrors(), MustCallAlias.class)) { + @Override + protected boolean skipReceiverSubtypeCheck( + MethodInvocationTree tree, + AnnotatedTypeMirror methodDefinitionReceiver, + AnnotatedTypeMirror methodCallReceiver) { + // It does not make sense for receivers to have must-call obligations. If the receiver of a + // method were to have a non-empty must-call obligation, then actually this method should + // be part of the must-call annotation on the class declaration! So skipping this check is + // always sound. return true; - } - // Need to check the type mirror for ajava-derived annotations and the element itself - // for human-written annotations from the source code. Getting to the ajava file - // directly at this point is impossible, so we approximate "the ajava file has an - // @MustCallAlias annotation" with "there is an @PolyMustCall annotation on the use - // type, but not in the source code". This only works because none of our inference - // techniques infer @PolyMustCall, so if @PolyMustCall is present but wasn't in the - // source, it must have been derived from an @MustCallAlias annotation (which we do - // infer). - boolean ajavaFileHasMustCallAlias = - useType.hasAnnotation(PolyMustCall.class) - && !AnnotationUtils.containsSameByClass( - elt.getAnnotationMirrors(), PolyMustCall.class); - if (ajavaFileHasMustCallAlias) { - return true; - } } - return super.isValidUse(declarationType, useType, tree); - } - - @Override - protected boolean skipReceiverSubtypeCheck( - MethodInvocationTree tree, - AnnotatedTypeMirror methodDefinitionReceiver, - AnnotatedTypeMirror methodCallReceiver) { - // It does not make sense for receivers to have must-call obligations. If the receiver of a - // method were to have a non-empty must-call obligation, then actually this method should - // be part of the must-call annotation on the class declaration! So skipping this check is - // always sound. - return true; - } - /** - * This method typically issues a warning if the result type of the constructor is not top, - * because in top-default type systems that indicates a potential problem. The Must Call Checker - * does not need this warning, because it expects the type of all constructors to be {@code - * MustCall({})} (by default) or some other {@code MustCall} type, not the top type. - * - *

          Instead, this method checks that the result type of a constructor is a supertype of the - * declared type on the class, if one exists. - * - * @param constructorType an AnnotatedExecutableType for the constructor - * @param constructorElement element that declares the constructor - */ - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - AnnotatedTypeMirror defaultType = - atypeFactory.getAnnotatedType(ElementUtils.enclosingTypeElement(constructorElement)); - AnnotationMirror defaultAnno = defaultType.getAnnotationInHierarchy(atypeFactory.TOP); - AnnotatedTypeMirror resultType = constructorType.getReturnType(); - AnnotationMirror resultAnno = resultType.getAnnotationInHierarchy(atypeFactory.TOP); - if (!qualHierarchy.isSubtypeShallow( - defaultAnno, defaultType.getUnderlyingType(), resultAnno, resultType.getUnderlyingType())) { - checker.reportError( - constructorElement, "inconsistent.constructor.type", resultAnno, defaultAnno); + /** + * This method typically issues a warning if the result type of the constructor is not top, + * because in top-default type systems that indicates a potential problem. The Must Call Checker + * does not need this warning, because it expects the type of all constructors to be {@code + * MustCall({})} (by default) or some other {@code MustCall} type, not the top type. + * + *

          Instead, this method checks that the result type of a constructor is a supertype of the + * declared type on the class, if one exists. + * + * @param constructorType an AnnotatedExecutableType for the constructor + * @param constructorElement element that declares the constructor + */ + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + AnnotatedTypeMirror defaultType = + atypeFactory.getAnnotatedType( + ElementUtils.enclosingTypeElement(constructorElement)); + AnnotationMirror defaultAnno = defaultType.getAnnotationInHierarchy(atypeFactory.TOP); + AnnotatedTypeMirror resultType = constructorType.getReturnType(); + AnnotationMirror resultAnno = resultType.getAnnotationInHierarchy(atypeFactory.TOP); + if (!qualHierarchy.isSubtypeShallow( + defaultAnno, + defaultType.getUnderlyingType(), + resultAnno, + resultType.getUnderlyingType())) { + checker.reportError( + constructorElement, "inconsistent.constructor.type", resultAnno, defaultAnno); + } } - } - /** - * Change the default for exception parameter lower bounds to bottom (the default), to prevent - * false positives. This is unsound; see the discussion on - * https://github.com/typetools/checker-framework/issues/3839. - * - *

          TODO: change checking of throws clauses to require that the thrown exception - * is @MustCall({}). This would probably eliminate most of the same false positives, without - * adding undue false positives. - * - * @return a set containing only the @MustCall({}) annotation - */ - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(atypeFactory.BOTTOM); - } + /** + * Change the default for exception parameter lower bounds to bottom (the default), to prevent + * false positives. This is unsound; see the discussion on + * https://github.com/typetools/checker-framework/issues/3839. + * + *

          TODO: change checking of throws clauses to require that the thrown exception + * is @MustCall({}). This would probably eliminate most of the same false positives, without + * adding undue false positives. + * + * @return a set containing only the @MustCall({}) annotation + */ + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(atypeFactory.BOTTOM); + } - /** - * Does not issue any warnings. - * - *

          This implementation prevents recursing into annotation arguments. Annotation arguments are - * literals, which don't have must-call obligations. - * - *

          Annotation arguments are treated as return locations for the purposes of defaulting, rather - * than parameter locations. This causes them to default incorrectly when the annotation is - * defined in bytecode. See https://github.com/typetools/checker-framework/issues/3178 for an - * explanation of why this is necessary to avoid false positives. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - return null; - } + /** + * Does not issue any warnings. + * + *

          This implementation prevents recursing into annotation arguments. Annotation arguments are + * literals, which don't have must-call obligations. + * + *

          Annotation arguments are treated as return locations for the purposes of defaulting, + * rather than parameter locations. This causes them to default incorrectly when the annotation + * is defined in bytecode. See https://github.com/typetools/checker-framework/issues/3178 for an + * explanation of why this is necessary to avoid false positives. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + return null; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java b/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java index 7872adc79a1..a7108d5eec0 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/CollectionToArrayHeuristics.java @@ -5,14 +5,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -24,6 +17,16 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * Determines the nullness type of calls to {@link java.util.Collection#toArray()}. * @@ -33,195 +36,202 @@ */ public class CollectionToArrayHeuristics { - /** The processing environment. */ - private final ProcessingEnvironment processingEnv; - - /** The checker, used for issuing diagnostic messages. */ - private final BaseTypeChecker checker; - - /** The type factory. */ - private final NullnessNoInitAnnotatedTypeFactory atypeFactory; - - /** Whether to trust {@code @ArrayLen(0)} annotations. */ - private final boolean trustArrayLenZero; - - /** The Collection type. */ - private final AnnotatedDeclaredType collectionType; - - /** The Collection.toArray(T[]) method. */ - private final ExecutableElement collectionToArrayE; - - /** The Collection.size() method. */ - private final ExecutableElement size; - - /** The ArrayLen.value field/element. */ - private final ExecutableElement arrayLenValueElement; - - /** - * Create a CollectionToArrayHeuristics. - * - * @param checker the checker, used for issuing diagnostic messages - * @param factory the type factory - */ - public CollectionToArrayHeuristics( - BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { - this.processingEnv = checker.getProcessingEnvironment(); - this.checker = checker; - this.atypeFactory = factory; - - this.collectionType = - factory.fromElement(ElementUtils.getTypeElement(processingEnv, Collection.class)); - this.collectionToArrayE = - TreeUtils.getMethod("java.util.Collection", "toArray", processingEnv, "T[]"); - this.size = TreeUtils.getMethod("java.util.Collection", "size", 0, processingEnv); - this.arrayLenValueElement = TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); - - this.trustArrayLenZero = - checker.getLintOption( - NullnessChecker.LINT_TRUSTARRAYLENZERO, NullnessChecker.LINT_DEFAULT_TRUSTARRAYLENZERO); - } - - /** - * If the method invocation is a call to {@code toArray}, then it manipulates the returned type of - * {@code method} arg to contain the appropriate nullness. Otherwise, it does nothing. - * - * @param tree method invocation tree - * @param method invoked method type - */ - public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { - if (TreeUtils.isMethodInvocation(tree, collectionToArrayE, processingEnv)) { - assert !tree.getArguments().isEmpty() : tree; - ExpressionTree argument = tree.getArguments().get(0); - boolean receiverIsNonNull = receiverIsCollectionOfNonNullElements(tree); - boolean argIsHandled = - isHandledArrayCreation(argument, receiverName(tree.getMethodSelect())) - || (trustArrayLenZero && isArrayLenZeroFieldAccess(argument)); - setComponentNullness(receiverIsNonNull && argIsHandled, method.getReturnType()); - - // TODO: We need a mechanism to prevent nullable collections - // from inserting null elements into a nonnull arrays. - if (!receiverIsNonNull) { - setComponentNullness(false, method.getParameterTypes().get(0)); - } - - if (receiverIsNonNull && !argIsHandled) { - if (argument.getKind() != Tree.Kind.NEW_ARRAY) { - checker.reportWarning(tree, "toarray.nullable.elements.not.newarray"); - } else { - checker.reportWarning(tree, "toarray.nullable.elements.mismatched.size"); - } - } + /** The processing environment. */ + private final ProcessingEnvironment processingEnv; + + /** The checker, used for issuing diagnostic messages. */ + private final BaseTypeChecker checker; + + /** The type factory. */ + private final NullnessNoInitAnnotatedTypeFactory atypeFactory; + + /** Whether to trust {@code @ArrayLen(0)} annotations. */ + private final boolean trustArrayLenZero; + + /** The Collection type. */ + private final AnnotatedDeclaredType collectionType; + + /** The Collection.toArray(T[]) method. */ + private final ExecutableElement collectionToArrayE; + + /** The Collection.size() method. */ + private final ExecutableElement size; + + /** The ArrayLen.value field/element. */ + private final ExecutableElement arrayLenValueElement; + + /** + * Create a CollectionToArrayHeuristics. + * + * @param checker the checker, used for issuing diagnostic messages + * @param factory the type factory + */ + public CollectionToArrayHeuristics( + BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { + this.processingEnv = checker.getProcessingEnvironment(); + this.checker = checker; + this.atypeFactory = factory; + + this.collectionType = + factory.fromElement(ElementUtils.getTypeElement(processingEnv, Collection.class)); + this.collectionToArrayE = + TreeUtils.getMethod("java.util.Collection", "toArray", processingEnv, "T[]"); + this.size = TreeUtils.getMethod("java.util.Collection", "size", 0, processingEnv); + this.arrayLenValueElement = TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); + + this.trustArrayLenZero = + checker.getLintOption( + NullnessChecker.LINT_TRUSTARRAYLENZERO, + NullnessChecker.LINT_DEFAULT_TRUSTARRAYLENZERO); } - } - - /** - * Sets the nullness of the component of the array type. - * - * @param isNonNull indicates which annotation ({@code NonNull} or {@code Nullable}) should be - * inserted - * @param type the array type - */ - private void setComponentNullness(boolean isNonNull, AnnotatedTypeMirror type) { - assert type.getKind() == TypeKind.ARRAY; - AnnotatedTypeMirror compType = ((AnnotatedArrayType) type).getComponentType(); - compType.replaceAnnotation(isNonNull ? atypeFactory.NONNULL : atypeFactory.NULLABLE); - } - - /** - * Returns true if {@code argument} is one of the array creation trees that the heuristic handles. - * - * @param argument the tree passed to {@link Collection#toArray(Object[]) Collection.toArray(T[])} - * @param receiver the expression for the receiver collection - * @return true if the argument is handled and assume to return nonnull elements - */ - private boolean isHandledArrayCreation(Tree argument, String receiver) { - if (argument.getKind() != Tree.Kind.NEW_ARRAY) { - return false; + + /** + * If the method invocation is a call to {@code toArray}, then it manipulates the returned type + * of {@code method} arg to contain the appropriate nullness. Otherwise, it does nothing. + * + * @param tree method invocation tree + * @param method invoked method type + */ + public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { + if (TreeUtils.isMethodInvocation(tree, collectionToArrayE, processingEnv)) { + assert !tree.getArguments().isEmpty() : tree; + ExpressionTree argument = tree.getArguments().get(0); + boolean receiverIsNonNull = receiverIsCollectionOfNonNullElements(tree); + boolean argIsHandled = + isHandledArrayCreation(argument, receiverName(tree.getMethodSelect())) + || (trustArrayLenZero && isArrayLenZeroFieldAccess(argument)); + setComponentNullness(receiverIsNonNull && argIsHandled, method.getReturnType()); + + // TODO: We need a mechanism to prevent nullable collections + // from inserting null elements into a nonnull arrays. + if (!receiverIsNonNull) { + setComponentNullness(false, method.getParameterTypes().get(0)); + } + + if (receiverIsNonNull && !argIsHandled) { + if (argument.getKind() != Tree.Kind.NEW_ARRAY) { + checker.reportWarning(tree, "toarray.nullable.elements.not.newarray"); + } else { + checker.reportWarning(tree, "toarray.nullable.elements.mismatched.size"); + } + } + } } - NewArrayTree newArr = (NewArrayTree) argument; - // empty array initializer - if (newArr.getInitializers() != null) { - return newArr.getInitializers().isEmpty(); + /** + * Sets the nullness of the component of the array type. + * + * @param isNonNull indicates which annotation ({@code NonNull} or {@code Nullable}) should be + * inserted + * @param type the array type + */ + private void setComponentNullness(boolean isNonNull, AnnotatedTypeMirror type) { + assert type.getKind() == TypeKind.ARRAY; + AnnotatedTypeMirror compType = ((AnnotatedArrayType) type).getComponentType(); + compType.replaceAnnotation(isNonNull ? atypeFactory.NONNULL : atypeFactory.NULLABLE); } - assert !newArr.getDimensions().isEmpty(); - Tree dimension = newArr.getDimensions().get(newArr.getDimensions().size() - 1); + /** + * Returns true if {@code argument} is one of the array creation trees that the heuristic + * handles. + * + * @param argument the tree passed to {@link Collection#toArray(Object[]) + * Collection.toArray(T[])} + * @param receiver the expression for the receiver collection + * @return true if the argument is handled and assume to return nonnull elements + */ + private boolean isHandledArrayCreation(Tree argument, String receiver) { + if (argument.getKind() != Tree.Kind.NEW_ARRAY) { + return false; + } + NewArrayTree newArr = (NewArrayTree) argument; - // 0-length array creation - if (dimension.toString().equals("0")) { - return true; - } + // empty array initializer + if (newArr.getInitializers() != null) { + return newArr.getInitializers().isEmpty(); + } - // size()-length array creation - if (TreeUtils.isMethodInvocation(dimension, size, processingEnv)) { - MethodInvocationTree invok = (MethodInvocationTree) dimension; - String invokReceiver = receiverName(invok.getMethodSelect()); - return invokReceiver.equals(receiver); + assert !newArr.getDimensions().isEmpty(); + Tree dimension = newArr.getDimensions().get(newArr.getDimensions().size() - 1); + + // 0-length array creation + if (dimension.toString().equals("0")) { + return true; + } + + // size()-length array creation + if (TreeUtils.isMethodInvocation(dimension, size, processingEnv)) { + MethodInvocationTree invok = (MethodInvocationTree) dimension; + String invokReceiver = receiverName(invok.getMethodSelect()); + return invokReceiver.equals(receiver); + } + + return false; } - return false; - } - - /** - * Returns true if the argument is a field access expression, where the field has declared type - * {@code @ArrayLen(0)}. - * - * @param argument an expression tree - * @return true if the argument is a field access expression, where the field has declared type - * {@code @ArrayLen(0)} - */ - private boolean isArrayLenZeroFieldAccess(ExpressionTree argument) { - Element el = TreeUtils.elementFromUse(argument); - if (el != null && el.getKind().isField()) { - TypeMirror t = ElementUtils.getType(el); - if (t.getKind() == TypeKind.ARRAY) { - List ams = t.getAnnotationMirrors(); - for (AnnotationMirror am : ams) { - if (atypeFactory.areSameByClass(am, ArrayLen.class)) { - List lens = - AnnotationUtils.getElementValueArray(am, arrayLenValueElement, Integer.class); - if (lens.size() == 1 && lens.get(0) == 0) { - return true; + /** + * Returns true if the argument is a field access expression, where the field has declared type + * {@code @ArrayLen(0)}. + * + * @param argument an expression tree + * @return true if the argument is a field access expression, where the field has declared type + * {@code @ArrayLen(0)} + */ + private boolean isArrayLenZeroFieldAccess(ExpressionTree argument) { + Element el = TreeUtils.elementFromUse(argument); + if (el != null && el.getKind().isField()) { + TypeMirror t = ElementUtils.getType(el); + if (t.getKind() == TypeKind.ARRAY) { + List ams = t.getAnnotationMirrors(); + for (AnnotationMirror am : ams) { + if (atypeFactory.areSameByClass(am, ArrayLen.class)) { + List lens = + AnnotationUtils.getElementValueArray( + am, arrayLenValueElement, Integer.class); + if (lens.size() == 1 && lens.get(0) == 0) { + return true; + } + } + } } - } } - } + return false; } - return false; - } - - /** - * Returns {@code true} if the method invocation tree receiver is collection that contains - * non-null elements (i.e. its type argument is {@code @NonNull}. - * - * @param tree a method invocation - * @return true if the receiver is a collection of non-null elements - */ - private boolean receiverIsCollectionOfNonNullElements(MethodInvocationTree tree) { - // check receiver - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - AnnotatedDeclaredType collection = - AnnotatedTypes.asSuper(atypeFactory, receiver, collectionType); - - if (collection.getTypeArguments().isEmpty() // raw type - || !collection.getTypeArguments().get(0).hasEffectiveAnnotation(atypeFactory.NONNULL)) { - return false; + + /** + * Returns {@code true} if the method invocation tree receiver is collection that contains + * non-null elements (i.e. its type argument is {@code @NonNull}. + * + * @param tree a method invocation + * @return true if the receiver is a collection of non-null elements + */ + private boolean receiverIsCollectionOfNonNullElements(MethodInvocationTree tree) { + // check receiver + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + AnnotatedDeclaredType collection = + AnnotatedTypes.asSuper(atypeFactory, receiver, collectionType); + + if (collection.getTypeArguments().isEmpty() // raw type + || !collection + .getTypeArguments() + .get(0) + .hasEffectiveAnnotation(atypeFactory.NONNULL)) { + return false; + } + return true; } - return true; - } - - /** - * The name of the receiver object of the tree. - * - * @param tree either an identifier tree or a member select tree - */ - // This method is quite sloppy, but works most of the time - private String receiverName(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - return ((MemberSelectTree) tree).getExpression().toString(); - } else { - return "this"; + + /** + * The name of the receiver object of the tree. + * + * @param tree either an identifier tree or a member select tree + */ + // This method is quite sloppy, but works most of the time + private String receiverName(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + return ((MemberSelectTree) tree).getExpression().toString(); + } else { + return "this"; + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java index 301bd382cc0..a6f18ffccb2 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnalysis.java @@ -1,42 +1,43 @@ package org.checkerframework.checker.nullness; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.type.TypeMirror; + /** Boilerplate code to glue together all the parts the KeyFor dataflow classes. */ public class KeyForAnalysis extends CFAbstractAnalysis { - /** - * Creates a new {@code KeyForAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public KeyForAnalysis(BaseTypeChecker checker, KeyForAnnotatedTypeFactory factory) { - super(checker, factory); - } - - @Override - public KeyForStore createEmptyStore(boolean sequentialSemantics) { - return new KeyForStore(this, sequentialSemantics); - } - - @Override - public KeyForStore createCopiedStore(KeyForStore store) { - return new KeyForStore(store); - } - - @Override - public @Nullable KeyForValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; + /** + * Creates a new {@code KeyForAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public KeyForAnalysis(BaseTypeChecker checker, KeyForAnnotatedTypeFactory factory) { + super(checker, factory); + } + + @Override + public KeyForStore createEmptyStore(boolean sequentialSemantics) { + return new KeyForStore(this, sequentialSemantics); + } + + @Override + public KeyForStore createCopiedStore(KeyForStore store) { + return new KeyForStore(store); + } + + @Override + public @Nullable KeyForValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; + } + return new KeyForValue(this, annotations, underlyingType); } - return new KeyForValue(this, annotations, underlyingType); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java index 690526ca9f1..dbdaae6af24 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForAnnotatedTypeFactory.java @@ -3,14 +3,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.PolyKeyFor; @@ -30,195 +23,210 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + public class KeyForAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory { - - /** The @{@link UnknownKeyFor} annotation. */ - protected final AnnotationMirror UNKNOWNKEYFOR = - AnnotationBuilder.fromClass(elements, UnknownKeyFor.class); - - /** The @{@link KeyForBottom} annotation. */ - protected final AnnotationMirror KEYFORBOTTOM = - AnnotationBuilder.fromClass(elements, KeyForBottom.class); - - /** The canonical name of the KeyFor class. */ - protected static final @CanonicalName String KEYFOR_NAME = KeyFor.class.getCanonicalName(); - - /** The Map.containsKey method. */ - private final ExecutableElement mapContainsKey = - TreeUtils.getMethod("java.util.Map", "containsKey", 1, processingEnv); - - /** The Map.get method. */ - private final ExecutableElement mapGet = - TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); - - /** The Map.put method. */ - private final ExecutableElement mapPut = - TreeUtils.getMethod("java.util.Map", "put", 2, processingEnv); - - /** The KeyFor.value field/element. */ - protected final ExecutableElement keyForValueElement = - TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); - - /** Moves annotations from one side of a pseudo-assignment to the other. */ - private final KeyForPropagator keyForPropagator = new KeyForPropagator(UNKNOWNKEYFOR); - - /** - * If true, assume the argument to Map.get is always a key for the receiver map. This is set by - * the `-AassumeKeyFor` command-line argument. However, if the Nullness Checker is being run, then - * `-AassumeKeyFor` disables the Map Key Checker. - */ - private final boolean assumeKeyFor; - - /** - * Creates a new KeyForAnnotatedTypeFactory. - * - * @param checker the associated checker - */ - public KeyForAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - - assumeKeyFor = checker.hasOption("assumeKeyFor"); - - // Add compatibility annotations: - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.KeyForDecl", KeyFor.class, true); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.KeyForType", KeyFor.class, true); - - // While strictly required for soundness, this leads to too many false positives. Printing - // a key or putting it in a map erases all knowledge of what maps it was a key for. - // TODO: Revisit when side effect annotations are more precise. - // sideEffectsUnrefineAliases = true; - - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList(KeyFor.class, UnknownKeyFor.class, KeyForBottom.class, PolyKeyFor.class)); - } - - @Override - protected ParameterizedExecutableType constructorFromUse( - NewClassTree tree, boolean inferTypeArgs) { - ParameterizedExecutableType result = super.constructorFromUse(tree, inferTypeArgs); - keyForPropagator.propagateNewClassTree(tree, result.executableType.getReturnType(), this); - return result; - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new KeyForPropagationTreeAnnotator(this, keyForPropagator)); - } - - @Override - protected KeyForAnalysis createFlowAnalysis() { - // Explicitly call the constructor instead of using reflection. - return new KeyForAnalysis(checker, this); - } - - @Override - public KeyForTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - // Explicitly call the constructor instead of using reflection. - return new KeyForTransfer((KeyForAnalysis) analysis); - } - - /** - * Given a string array 'values', returns an AnnotationMirror corresponding to @KeyFor(values) - * - * @param values the values for the {@code @KeyFor} annotation - * @return a {@code @KeyFor} annotation with the given values - */ - public AnnotationMirror createKeyForAnnotationMirrorWithValue(Set values) { - // Create an AnnotationBuilder with the ArrayList - AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), KeyFor.class); - builder.setValue("value", values.toArray()); - - // Return the resulting AnnotationMirror - return builder.build(); - } - - /** - * Given a string 'value', returns an AnnotationMirror corresponding to @KeyFor(value) - * - * @param value the argument to {@code @KeyFor} - * @return a {@code @KeyFor} annotation with the given value - */ - public AnnotationMirror createKeyForAnnotationMirrorWithValue(String value) { - return createKeyForAnnotationMirrorWithValue(Collections.singleton(value)); - } - - /** - * Returns true if the expression tree is a key for the map. - * - * @param mapExpression expression that has type Map - * @param tree expression that might be a key for the map - * @return whether or not the expression is a key for the map - */ - public boolean isKeyForMap(String mapExpression, ExpressionTree tree) { - // This test only has an effect if the Map Key Checker is being run on its own. If the - // Nullness Checker is being run, then -AassumeKeyFor disables the Map Key Checker. - if (assumeKeyFor) { - return true; - } - Collection maps = null; - AnnotatedTypeMirror type = getAnnotatedType(tree); - AnnotationMirror keyForAnno = type.getEffectiveAnnotation(KeyFor.class); - if (keyForAnno != null) { - maps = AnnotationUtils.getElementValueArray(keyForAnno, keyForValueElement, String.class); - } else { - KeyForValue value = getInferredValueFor(tree); - if (value != null) { - maps = value.getKeyForMaps(); - } - } - - return maps != null && maps.contains(mapExpression); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new SubtypeIsSupersetQualifierHierarchy( - getSupportedTypeQualifiers(), processingEnv, KeyForAnnotatedTypeFactory.this); - } - - /** Returns true if the node is an invocation of Map.containsKey. */ - boolean isMapContainsKey(Tree tree) { - return TreeUtils.isMethodInvocation(tree, mapContainsKey, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.get. */ - boolean isMapGet(Tree tree) { - return TreeUtils.isMethodInvocation(tree, mapGet, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.put. */ - boolean isMapPut(Tree tree) { - return TreeUtils.isMethodInvocation(tree, mapPut, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.containsKey. */ - boolean isMapContainsKey(Node node) { - return NodeUtils.isMethodInvocation(node, mapContainsKey, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.get. */ - boolean isMapGet(Node node) { - return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); - } - - /** Returns true if the node is an invocation of Map.put. */ - boolean isMapPut(Node node) { - return NodeUtils.isMethodInvocation(node, mapPut, getProcessingEnv()); - } - - /** Returns false. Redundancy in the KeyFor hierarchy is not worth warning about. */ - @Override - public boolean shouldWarnIfStubRedundantWithBytecode() { - return false; - } + extends GenericAnnotatedTypeFactory< + KeyForValue, KeyForStore, KeyForTransfer, KeyForAnalysis> { + + /** The @{@link UnknownKeyFor} annotation. */ + protected final AnnotationMirror UNKNOWNKEYFOR = + AnnotationBuilder.fromClass(elements, UnknownKeyFor.class); + + /** The @{@link KeyForBottom} annotation. */ + protected final AnnotationMirror KEYFORBOTTOM = + AnnotationBuilder.fromClass(elements, KeyForBottom.class); + + /** The canonical name of the KeyFor class. */ + protected static final @CanonicalName String KEYFOR_NAME = KeyFor.class.getCanonicalName(); + + /** The Map.containsKey method. */ + private final ExecutableElement mapContainsKey = + TreeUtils.getMethod("java.util.Map", "containsKey", 1, processingEnv); + + /** The Map.get method. */ + private final ExecutableElement mapGet = + TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); + + /** The Map.put method. */ + private final ExecutableElement mapPut = + TreeUtils.getMethod("java.util.Map", "put", 2, processingEnv); + + /** The KeyFor.value field/element. */ + protected final ExecutableElement keyForValueElement = + TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); + + /** Moves annotations from one side of a pseudo-assignment to the other. */ + private final KeyForPropagator keyForPropagator = new KeyForPropagator(UNKNOWNKEYFOR); + + /** + * If true, assume the argument to Map.get is always a key for the receiver map. This is set by + * the `-AassumeKeyFor` command-line argument. However, if the Nullness Checker is being run, + * then `-AassumeKeyFor` disables the Map Key Checker. + */ + private final boolean assumeKeyFor; + + /** + * Creates a new KeyForAnnotatedTypeFactory. + * + * @param checker the associated checker + */ + public KeyForAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + + assumeKeyFor = checker.hasOption("assumeKeyFor"); + + // Add compatibility annotations: + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.KeyForDecl", KeyFor.class, true); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.KeyForType", KeyFor.class, true); + + // While strictly required for soundness, this leads to too many false positives. Printing + // a key or putting it in a map erases all knowledge of what maps it was a key for. + // TODO: Revisit when side effect annotations are more precise. + // sideEffectsUnrefineAliases = true; + + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + KeyFor.class, UnknownKeyFor.class, KeyForBottom.class, PolyKeyFor.class)); + } + + @Override + protected ParameterizedExecutableType constructorFromUse( + NewClassTree tree, boolean inferTypeArgs) { + ParameterizedExecutableType result = super.constructorFromUse(tree, inferTypeArgs); + keyForPropagator.propagateNewClassTree(tree, result.executableType.getReturnType(), this); + return result; + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), + new KeyForPropagationTreeAnnotator(this, keyForPropagator)); + } + + @Override + protected KeyForAnalysis createFlowAnalysis() { + // Explicitly call the constructor instead of using reflection. + return new KeyForAnalysis(checker, this); + } + + @Override + public KeyForTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + // Explicitly call the constructor instead of using reflection. + return new KeyForTransfer((KeyForAnalysis) analysis); + } + + /** + * Given a string array 'values', returns an AnnotationMirror corresponding to @KeyFor(values) + * + * @param values the values for the {@code @KeyFor} annotation + * @return a {@code @KeyFor} annotation with the given values + */ + public AnnotationMirror createKeyForAnnotationMirrorWithValue(Set values) { + // Create an AnnotationBuilder with the ArrayList + AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), KeyFor.class); + builder.setValue("value", values.toArray()); + + // Return the resulting AnnotationMirror + return builder.build(); + } + + /** + * Given a string 'value', returns an AnnotationMirror corresponding to @KeyFor(value) + * + * @param value the argument to {@code @KeyFor} + * @return a {@code @KeyFor} annotation with the given value + */ + public AnnotationMirror createKeyForAnnotationMirrorWithValue(String value) { + return createKeyForAnnotationMirrorWithValue(Collections.singleton(value)); + } + + /** + * Returns true if the expression tree is a key for the map. + * + * @param mapExpression expression that has type Map + * @param tree expression that might be a key for the map + * @return whether or not the expression is a key for the map + */ + public boolean isKeyForMap(String mapExpression, ExpressionTree tree) { + // This test only has an effect if the Map Key Checker is being run on its own. If the + // Nullness Checker is being run, then -AassumeKeyFor disables the Map Key Checker. + if (assumeKeyFor) { + return true; + } + Collection maps = null; + AnnotatedTypeMirror type = getAnnotatedType(tree); + AnnotationMirror keyForAnno = type.getEffectiveAnnotation(KeyFor.class); + if (keyForAnno != null) { + maps = + AnnotationUtils.getElementValueArray( + keyForAnno, keyForValueElement, String.class); + } else { + KeyForValue value = getInferredValueFor(tree); + if (value != null) { + maps = value.getKeyForMaps(); + } + } + + return maps != null && maps.contains(mapExpression); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new SubtypeIsSupersetQualifierHierarchy( + getSupportedTypeQualifiers(), processingEnv, KeyForAnnotatedTypeFactory.this); + } + + /** Returns true if the node is an invocation of Map.containsKey. */ + boolean isMapContainsKey(Tree tree) { + return TreeUtils.isMethodInvocation(tree, mapContainsKey, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.get. */ + boolean isMapGet(Tree tree) { + return TreeUtils.isMethodInvocation(tree, mapGet, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.put. */ + boolean isMapPut(Tree tree) { + return TreeUtils.isMethodInvocation(tree, mapPut, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.containsKey. */ + boolean isMapContainsKey(Node node) { + return NodeUtils.isMethodInvocation(node, mapContainsKey, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.get. */ + boolean isMapGet(Node node) { + return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); + } + + /** Returns true if the node is an invocation of Map.put. */ + boolean isMapPut(Node node) { + return NodeUtils.isMethodInvocation(node, mapPut, getProcessingEnv()); + } + + /** Returns false. Redundancy in the KeyFor hierarchy is not worth warning about. */ + @Override + public boolean shouldWarnIfStubRedundantWithBytecode() { + return false; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java index a51bf035eb6..abacdbcbbfe 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagationTreeAnnotator.java @@ -3,8 +3,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.VariableTree; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.nullness.KeyForPropagator.PropagationDirection; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -12,6 +11,9 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; + /** * For the following initializations we wish to propagate the annotations from the left-hand side to * the right-hand side or vice versa: @@ -45,61 +47,63 @@ * AnnotatedDeclaredType then this class does nothing. */ public class KeyForPropagationTreeAnnotator extends TreeAnnotator { - private final KeyForPropagator keyForPropagator; - private final ExecutableElement keySetMethod; + private final KeyForPropagator keyForPropagator; + private final ExecutableElement keySetMethod; - public KeyForPropagationTreeAnnotator( - AnnotatedTypeFactory atypeFactory, KeyForPropagator propagationTreeAnnotator) { - super(atypeFactory); - this.keyForPropagator = propagationTreeAnnotator; - keySetMethod = - TreeUtils.getMethod("java.util.Map", "keySet", 0, atypeFactory.getProcessingEnv()); - } + public KeyForPropagationTreeAnnotator( + AnnotatedTypeFactory atypeFactory, KeyForPropagator propagationTreeAnnotator) { + super(atypeFactory); + this.keyForPropagator = propagationTreeAnnotator; + keySetMethod = + TreeUtils.getMethod("java.util.Map", "keySet", 0, atypeFactory.getProcessingEnv()); + } - /** - * Returns true iff expression is a call to java.util.Map.KeySet. - * - * @return true iff expression is a call to java.util.Map.KeySet - */ - public boolean isCallToKeyset(ExpressionTree expression) { - return TreeUtils.isMethodInvocation(expression, keySetMethod, atypeFactory.getProcessingEnv()); - } + /** + * Returns true iff expression is a call to java.util.Map.KeySet. + * + * @return true iff expression is a call to java.util.Map.KeySet + */ + public boolean isCallToKeyset(ExpressionTree expression) { + return TreeUtils.isMethodInvocation( + expression, keySetMethod, atypeFactory.getProcessingEnv()); + } - /** - * Transfers annotations on type arguments from the initializer to the variableTree, if the - * initializer is a call to java.util.Map.keySet. - */ - @Override - public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { - super.visitVariable(variableTree, type); + /** + * Transfers annotations on type arguments from the initializer to the variableTree, if the + * initializer is a call to java.util.Map.keySet. + */ + @Override + public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { + super.visitVariable(variableTree, type); - // This should only happen on Map.keySet(); - if (type.getKind() == TypeKind.DECLARED) { - ExpressionTree initializer = variableTree.getInitializer(); + // This should only happen on Map.keySet(); + if (type.getKind() == TypeKind.DECLARED) { + ExpressionTree initializer = variableTree.getInitializer(); - if (isCallToKeyset(initializer)) { - AnnotatedDeclaredType variableType = (AnnotatedDeclaredType) type; - AnnotatedTypeMirror initializerType = atypeFactory.getAnnotatedType(initializer); + if (isCallToKeyset(initializer)) { + AnnotatedDeclaredType variableType = (AnnotatedDeclaredType) type; + AnnotatedTypeMirror initializerType = atypeFactory.getAnnotatedType(initializer); - // Propagate just for declared (class) types, not for array types, boxed primitives, - // etc. - if (variableType.getKind() == TypeKind.DECLARED) { - keyForPropagator.propagate( - (AnnotatedDeclaredType) initializerType, - variableType, - PropagationDirection.TO_SUPERTYPE, - atypeFactory); + // Propagate just for declared (class) types, not for array types, boxed primitives, + // etc. + if (variableType.getKind() == TypeKind.DECLARED) { + keyForPropagator.propagate( + (AnnotatedDeclaredType) initializerType, + variableType, + PropagationDirection.TO_SUPERTYPE, + atypeFactory); + } + } } - } - } - return null; - } + return null; + } - /** Transfers annotations to type if the left hand side is a variable declaration. */ - @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - keyForPropagator.propagateNewClassTree(tree, type, (KeyForAnnotatedTypeFactory) atypeFactory); - return super.visitNewClass(tree, type); - } + /** Transfers annotations to type if the left hand side is a variable declaration. */ + @Override + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + keyForPropagator.propagateNewClassTree( + tree, type, (KeyForAnnotatedTypeFactory) atypeFactory); + return super.visitNewClass(tree, type); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java index efd5827ba3c..93649fdcaaf 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForPropagator.java @@ -4,12 +4,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Types; + import org.checkerframework.checker.nullness.qual.UnknownKeyFor; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -20,6 +15,14 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; + /** * KeyForPropagator is used to move nested KeyFor annotations in type arguments from one side of a * pseudo-assignment to the other. The KeyForPropagationTreeAnnotator details the locations in which @@ -28,180 +31,180 @@ * @see org.checkerframework.checker.nullness.KeyForPropagationTreeAnnotator */ public class KeyForPropagator { - public enum PropagationDirection { - // transfer FROM the super type to the subtype - TO_SUBTYPE, - - // transfer FROM the subtype to the supertype - TO_SUPERTYPE, - - // first execute TO_SUBTYPE then TO_SUPERTYPE, if TO_SUBTYPE actually transfers - // an annotation for a particular type T then T will not be affected by the - // TO_SUPERTYPE transfer because it will already have a KeyFor annotation - BOTH - } - - /** - * The top type of the KeyFor hierarchy. - * - *

          This class will replace @UnknownKeyFor annotations. It will also add annotations when they - * are missing for types that require primary annotation (i.e. not TypeVars, Wildcards, - * Intersections, or Unions). - */ - private final AnnotationMirror UNKNOWN_KEYFOR; - - /** Instance of {@link KeyForPropagationReplacer}. */ - private final KeyForPropagationReplacer replacer = new KeyForPropagationReplacer(); - - /** - * Creates a KeyForPropagator - * - * @param unknownKeyfor an {@link UnknownKeyFor} annotation - */ - public KeyForPropagator(AnnotationMirror unknownKeyfor) { - this.UNKNOWN_KEYFOR = unknownKeyfor; - } - - /** - * Propagate annotations from the type arguments of one type to another. Which type is the source - * and destination of the annotations depends on the direction parameter. Only @KeyFor annotations - * are propagated and only if the type to which it would be propagated contains an @UnknownKeyFor - * or contains no key for annotations of any kind. If any of the type arguments are wildcards than - * they are ignored. - * - *

          Note the primary annotations of subtype/supertype are not used. - * - *

          Simple Example: - * - *

          {@code
          -   * typeOf(subtype) = ArrayList<@KeyFor("a") String>
          -   * typeOf(supertype) = List<@UnknownKeyFor String>
          -   * direction = TO_SUPERTYPE
          -   * }
          - * - * The type of supertype after propagate would be: {@code List<@KeyFor("a") String>} - * - *

          A more complex example would be: - * - *

          {@code
          -   * typeOf(subtype) = HashMap<@UnknownKeyFor String, @KeyFor("b") List<@KeyFor("c") String>>
          -   * typeOf(supertype) = Map<@KeyFor("a") String, @KeyFor("b") List<@KeyFor("c") String>>
          -   * direction = TO_SUBTYPE
          -   * }
          - * - * The type of subtype after propagate would be: {@code HashMap<@KeyFor("a") String, @KeyFor("b") - * List<@KeyFor("c") String>>} - */ - public void propagate( - AnnotatedDeclaredType subtype, - AnnotatedDeclaredType supertype, - PropagationDirection direction, - AnnotatedTypeFactory typeFactory) { - TypeElement subtypeElement = (TypeElement) subtype.getUnderlyingType().asElement(); - TypeElement supertypeElement = (TypeElement) supertype.getUnderlyingType().asElement(); - Types types = typeFactory.getProcessingEnv().getTypeUtils(); - - // Note: The right hand side of this or expression will cover raw types - if (subtype.getTypeArguments().isEmpty()) { - return; - } // else - - // this can happen for two reasons: - // 1) the subclass introduced NEW type arguments when the superclass had none - // 2) the supertype was RAW. - // In either case, there is no reason to propagate - if (supertype.getTypeArguments().isEmpty()) { - return; + public enum PropagationDirection { + // transfer FROM the super type to the subtype + TO_SUBTYPE, + + // transfer FROM the subtype to the supertype + TO_SUPERTYPE, + + // first execute TO_SUBTYPE then TO_SUPERTYPE, if TO_SUBTYPE actually transfers + // an annotation for a particular type T then T will not be affected by the + // TO_SUPERTYPE transfer because it will already have a KeyFor annotation + BOTH } - Set> typeParamMappings = - TypeArgumentMapper.mapTypeArgumentIndices(subtypeElement, supertypeElement, types); + /** + * The top type of the KeyFor hierarchy. + * + *

          This class will replace @UnknownKeyFor annotations. It will also add annotations when they + * are missing for types that require primary annotation (i.e. not TypeVars, Wildcards, + * Intersections, or Unions). + */ + private final AnnotationMirror UNKNOWN_KEYFOR; + + /** Instance of {@link KeyForPropagationReplacer}. */ + private final KeyForPropagationReplacer replacer = new KeyForPropagationReplacer(); + + /** + * Creates a KeyForPropagator + * + * @param unknownKeyfor an {@link UnknownKeyFor} annotation + */ + public KeyForPropagator(AnnotationMirror unknownKeyfor) { + this.UNKNOWN_KEYFOR = unknownKeyfor; + } - List subtypeArgs = subtype.getTypeArguments(); - List supertypeArgs = supertype.getTypeArguments(); + /** + * Propagate annotations from the type arguments of one type to another. Which type is the + * source and destination of the annotations depends on the direction parameter. Only @KeyFor + * annotations are propagated and only if the type to which it would be propagated contains + * an @UnknownKeyFor or contains no key for annotations of any kind. If any of the type + * arguments are wildcards than they are ignored. + * + *

          Note the primary annotations of subtype/supertype are not used. + * + *

          Simple Example: + * + *

          {@code
          +     * typeOf(subtype) = ArrayList<@KeyFor("a") String>
          +     * typeOf(supertype) = List<@UnknownKeyFor String>
          +     * direction = TO_SUPERTYPE
          +     * }
          + * + * The type of supertype after propagate would be: {@code List<@KeyFor("a") String>} + * + *

          A more complex example would be: + * + *

          {@code
          +     * typeOf(subtype) = HashMap<@UnknownKeyFor String, @KeyFor("b") List<@KeyFor("c") String>>
          +     * typeOf(supertype) = Map<@KeyFor("a") String, @KeyFor("b") List<@KeyFor("c") String>>
          +     * direction = TO_SUBTYPE
          +     * }
          + * + * The type of subtype after propagate would be: {@code HashMap<@KeyFor("a") + * String, @KeyFor("b") List<@KeyFor("c") String>>} + */ + public void propagate( + AnnotatedDeclaredType subtype, + AnnotatedDeclaredType supertype, + PropagationDirection direction, + AnnotatedTypeFactory typeFactory) { + TypeElement subtypeElement = (TypeElement) subtype.getUnderlyingType().asElement(); + TypeElement supertypeElement = (TypeElement) supertype.getUnderlyingType().asElement(); + Types types = typeFactory.getProcessingEnv().getTypeUtils(); + + // Note: The right hand side of this or expression will cover raw types + if (subtype.getTypeArguments().isEmpty()) { + return; + } // else + + // this can happen for two reasons: + // 1) the subclass introduced NEW type arguments when the superclass had none + // 2) the supertype was RAW. + // In either case, there is no reason to propagate + if (supertype.getTypeArguments().isEmpty()) { + return; + } - for (IPair path : typeParamMappings) { - AnnotatedTypeMirror subtypeArg = subtypeArgs.get(path.first); - AnnotatedTypeMirror supertypeArg = supertypeArgs.get(path.second); + Set> typeParamMappings = + TypeArgumentMapper.mapTypeArgumentIndices(subtypeElement, supertypeElement, types); - if (subtypeArg.getKind() == TypeKind.WILDCARD - || supertypeArg.getKind() == TypeKind.WILDCARD) { - continue; - } + List subtypeArgs = subtype.getTypeArguments(); + List supertypeArgs = supertype.getTypeArguments(); - switch (direction) { - case TO_SUBTYPE: - replacer.visit(supertypeArg, subtypeArg); - break; + for (IPair path : typeParamMappings) { + AnnotatedTypeMirror subtypeArg = subtypeArgs.get(path.first); + AnnotatedTypeMirror supertypeArg = supertypeArgs.get(path.second); - case TO_SUPERTYPE: - replacer.visit(subtypeArg, supertypeArg); - break; + if (subtypeArg.getKind() == TypeKind.WILDCARD + || supertypeArg.getKind() == TypeKind.WILDCARD) { + continue; + } - case BOTH: - // note if they both have an annotation nothing will happen - replacer.visit(subtypeArg, supertypeArg); - replacer.visit(supertypeArg, subtypeArg); - break; - } - } - } - - /** - * Propagate annotations from the type arguments of {@code type} to the assignment context of - * {@code newClassTree} if one exists. - * - * @param newClassTree new class tree - * @param type annotated type of {@code newClassTree} - * @param atypeFactory factory - */ - public void propagateNewClassTree( - NewClassTree newClassTree, - AnnotatedTypeMirror type, - KeyForAnnotatedTypeFactory atypeFactory) { - if (type.getKind() != TypeKind.DECLARED || TreeUtils.isDiamondTree(newClassTree)) { - return; - } - TreePath path = atypeFactory.getPath(newClassTree); - if (path == null) { - return; - } - Tree assignmentContext = TreePathUtil.getContextForPolyExpression(path); - AnnotatedTypeMirror assignedTo; - if (assignmentContext instanceof VariableTree) { - if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) assignmentContext)) { - return; - } - assignedTo = atypeFactory.getAnnotatedTypeLhs(assignmentContext); - } else { - return; + switch (direction) { + case TO_SUBTYPE: + replacer.visit(supertypeArg, subtypeArg); + break; + + case TO_SUPERTYPE: + replacer.visit(subtypeArg, supertypeArg); + break; + + case BOTH: + // note if they both have an annotation nothing will happen + replacer.visit(subtypeArg, supertypeArg); + replacer.visit(supertypeArg, subtypeArg); + break; + } + } } - // array types and boxed primitives etc don't require propagation - if (assignedTo.getKind() == TypeKind.DECLARED) { - propagate( - (AnnotatedDeclaredType) type, - (AnnotatedDeclaredType) assignedTo, - PropagationDirection.TO_SUBTYPE, - atypeFactory); + /** + * Propagate annotations from the type arguments of {@code type} to the assignment context of + * {@code newClassTree} if one exists. + * + * @param newClassTree new class tree + * @param type annotated type of {@code newClassTree} + * @param atypeFactory factory + */ + public void propagateNewClassTree( + NewClassTree newClassTree, + AnnotatedTypeMirror type, + KeyForAnnotatedTypeFactory atypeFactory) { + if (type.getKind() != TypeKind.DECLARED || TreeUtils.isDiamondTree(newClassTree)) { + return; + } + TreePath path = atypeFactory.getPath(newClassTree); + if (path == null) { + return; + } + Tree assignmentContext = TreePathUtil.getContextForPolyExpression(path); + AnnotatedTypeMirror assignedTo; + if (assignmentContext instanceof VariableTree) { + if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) assignmentContext)) { + return; + } + assignedTo = atypeFactory.getAnnotatedTypeLhs(assignmentContext); + } else { + return; + } + + // array types and boxed primitives etc don't require propagation + if (assignedTo.getKind() == TypeKind.DECLARED) { + propagate( + (AnnotatedDeclaredType) type, + (AnnotatedDeclaredType) assignedTo, + PropagationDirection.TO_SUBTYPE, + atypeFactory); + } } - } - - /** - * An {@link AnnotatedTypeReplacer} that copies the annotation in KeyFor hierarchy from the first - * types to the second type, if the second type is annotated with @UnknownKeyFor or has no - * annotation in the KeyFor hierarchy. - */ - private class KeyForPropagationReplacer extends AnnotatedTypeReplacer { - @Override - protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - AnnotationMirror fromKeyFor = from.getAnnotationInHierarchy(UNKNOWN_KEYFOR); - if (fromKeyFor != null) { - if (to.hasAnnotation(UNKNOWN_KEYFOR) - || to.getAnnotationInHierarchy(UNKNOWN_KEYFOR) == null) { - to.replaceAnnotation(fromKeyFor); + + /** + * An {@link AnnotatedTypeReplacer} that copies the annotation in KeyFor hierarchy from the + * first types to the second type, if the second type is annotated with @UnknownKeyFor or has no + * annotation in the KeyFor hierarchy. + */ + private class KeyForPropagationReplacer extends AnnotatedTypeReplacer { + @Override + protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + AnnotationMirror fromKeyFor = from.getAnnotationInHierarchy(UNKNOWN_KEYFOR); + if (fromKeyFor != null) { + if (to.hasAnnotation(UNKNOWN_KEYFOR) + || to.getAnnotationInHierarchy(UNKNOWN_KEYFOR) == null) { + to.replaceAnnotation(fromKeyFor); + } + } } - } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java index f59c9ef4614..75f09cb88db 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForStore.java @@ -4,12 +4,12 @@ import org.checkerframework.framework.flow.CFAbstractStore; public class KeyForStore extends CFAbstractStore { - public KeyForStore( - CFAbstractAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - } + public KeyForStore( + CFAbstractAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } - protected KeyForStore(CFAbstractStore other) { - super(other); - } + protected KeyForStore(CFAbstractStore other) { + super(other); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java index bce1270fce6..1aa71bcf0c2 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForSubchecker.java @@ -1,8 +1,9 @@ package org.checkerframework.checker.nullness; -import javax.annotation.processing.SupportedOptions; import org.checkerframework.common.basetype.BaseTypeChecker; +import javax.annotation.processing.SupportedOptions; + /** * A type-checker for determining which values are keys for which maps. Typically used as part of * the compound checker for the nullness type system. diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java index 4898845185b..3061400b306 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForTransfer.java @@ -1,11 +1,5 @@ package org.checkerframework.checker.nullness; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; @@ -16,87 +10,95 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + /** * KeyForTransfer ensures that java.util.Map.put and containsKey cause the appropriate @KeyFor * annotation to be added to the key. */ public class KeyForTransfer extends CFAbstractTransfer { - /** The KeyFor.value element/field. */ - protected final ExecutableElement keyForValueElement; - - /** - * Creates a new KeyForTransfer. - * - * @param analysis the analysis - */ - public KeyForTransfer(KeyForAnalysis analysis) { - super(analysis); - - ProcessingEnvironment processingEnv = - ((KeyForAnnotatedTypeFactory) analysis.getTypeFactory()).getProcessingEnv(); - keyForValueElement = TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); - } - - /* - * Provided that m is of a type that implements interface java.util.Map: - *
            - *
          • Given a call m.containsKey(k), ensures that k is @KeyFor("m") in the thenStore of the transfer result. - *
          • Given a call m.put(k, ...), ensures that k is @KeyFor("m") in the thenStore and elseStore of the transfer result. - *
          - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput in) { - - TransferResult result = super.visitMethodInvocation(node, in); - KeyForAnnotatedTypeFactory factory = (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); - if (factory.isMapContainsKey(node) || factory.isMapPut(node)) { - - Node receiver = node.getTarget().getReceiver(); - JavaExpression receiverJe = JavaExpression.fromNode(receiver); - String mapName = receiverJe.toString(); - JavaExpression keyExpr = JavaExpression.fromNode(node.getArgument(0)); - - LinkedHashSet keyForMaps = new LinkedHashSet<>(); - keyForMaps.add(mapName); - - KeyForValue previousKeyValue = in.getValueOfSubNode(node.getArgument(0)); - if (previousKeyValue != null) { - for (AnnotationMirror prevAm : previousKeyValue.getAnnotations()) { - if (prevAm != null && factory.areSameByClass(prevAm, KeyFor.class)) { - keyForMaps.addAll(getKeys(prevAm)); - } - } - } - - AnnotationMirror am = factory.createKeyForAnnotationMirrorWithValue(keyForMaps); - - if (factory.isMapContainsKey(node)) { - // method is Map.containsKey - result.getThenStore().insertValue(keyExpr, am); - } else { - // method is Map.put - result.getThenStore().insertValue(keyExpr, am); - result.getElseStore().insertValue(keyExpr, am); - } + /** The KeyFor.value element/field. */ + protected final ExecutableElement keyForValueElement; + + /** + * Creates a new KeyForTransfer. + * + * @param analysis the analysis + */ + public KeyForTransfer(KeyForAnalysis analysis) { + super(analysis); + + ProcessingEnvironment processingEnv = + ((KeyForAnnotatedTypeFactory) analysis.getTypeFactory()).getProcessingEnv(); + keyForValueElement = TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv); } - return result; - } - - /** - * Returns the elements/arguments of a {@code @KeyFor} annotation. - * - * @param keyFor a {@code @KeyFor} annotation - * @return the elements/arguments of a {@code @KeyFor} annotation - */ - private Set getKeys(AnnotationMirror keyFor) { - if (keyFor.getElementValues().isEmpty()) { - return Collections.emptySet(); + /* + * Provided that m is of a type that implements interface java.util.Map: + *
            + *
          • Given a call m.containsKey(k), ensures that k is @KeyFor("m") in the thenStore of the transfer result. + *
          • Given a call m.put(k, ...), ensures that k is @KeyFor("m") in the thenStore and elseStore of the transfer result. + *
          + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput in) { + + TransferResult result = super.visitMethodInvocation(node, in); + KeyForAnnotatedTypeFactory factory = (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); + if (factory.isMapContainsKey(node) || factory.isMapPut(node)) { + + Node receiver = node.getTarget().getReceiver(); + JavaExpression receiverJe = JavaExpression.fromNode(receiver); + String mapName = receiverJe.toString(); + JavaExpression keyExpr = JavaExpression.fromNode(node.getArgument(0)); + + LinkedHashSet keyForMaps = new LinkedHashSet<>(); + keyForMaps.add(mapName); + + KeyForValue previousKeyValue = in.getValueOfSubNode(node.getArgument(0)); + if (previousKeyValue != null) { + for (AnnotationMirror prevAm : previousKeyValue.getAnnotations()) { + if (prevAm != null && factory.areSameByClass(prevAm, KeyFor.class)) { + keyForMaps.addAll(getKeys(prevAm)); + } + } + } + + AnnotationMirror am = factory.createKeyForAnnotationMirrorWithValue(keyForMaps); + + if (factory.isMapContainsKey(node)) { + // method is Map.containsKey + result.getThenStore().insertValue(keyExpr, am); + } else { + // method is Map.put + result.getThenStore().insertValue(keyExpr, am); + result.getElseStore().insertValue(keyExpr, am); + } + } + + return result; } - return new LinkedHashSet<>( - AnnotationUtils.getElementValueArray(keyFor, keyForValueElement, String.class)); - } + /** + * Returns the elements/arguments of a {@code @KeyFor} annotation. + * + * @param keyFor a {@code @KeyFor} annotation + * @return the elements/arguments of a {@code @KeyFor} annotation + */ + private Set getKeys(AnnotationMirror keyFor) { + if (keyFor.getElementValues().isEmpty()) { + return Collections.emptySet(); + } + + return new LinkedHashSet<>( + AnnotationUtils.getElementValueArray(keyFor, keyForValueElement, String.class)); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java index 9c04223489d..3f6c231f503 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/KeyForValue.java @@ -1,12 +1,7 @@ package org.checkerframework.checker.nullness; import com.sun.source.tree.ExpressionTree; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.flow.CFAbstractAnalysis; @@ -15,6 +10,14 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.plumelib.util.CollectionsPlume; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * KeyForValue holds additional information about which maps this value is a key for. This extra * information is required when adding the @KeyFor qualifier to the type is not a refinement of the @@ -40,103 +43,103 @@ * is used in {@link KeyForAnnotatedTypeFactory#isKeyForMap(String, ExpressionTree)}. */ public class KeyForValue extends CFAbstractValue { - /** - * If the underlying type is a type variable or a wildcard, then this is a set of maps for which - * this value is a key. Otherwise, it's null. - */ - // Cannot be final because lub re-assigns; add a new constructor to do this cleanly? - private @Nullable Set keyForMaps; + /** + * If the underlying type is a type variable or a wildcard, then this is a set of maps for which + * this value is a key. Otherwise, it's null. + */ + // Cannot be final because lub re-assigns; add a new constructor to do this cleanly? + private @Nullable Set keyForMaps; - /** - * Create a KeyForValue. - * - * @param analysis the analysis - * @param annotations the annotations - * @param underlyingType the underlying type - */ - public KeyForValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - KeyForAnnotatedTypeFactory atypeFactory = - (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); - AnnotationMirror keyfor = atypeFactory.getAnnotationByClass(annotations, KeyFor.class); - if (keyfor != null - && (underlyingType.getKind() == TypeKind.TYPEVAR - || underlyingType.getKind() == TypeKind.WILDCARD)) { - List list = - AnnotationUtils.getElementValueArray( - keyfor, atypeFactory.keyForValueElement, String.class); - keyForMaps = new LinkedHashSet<>(list.size()); - keyForMaps.addAll(list); - } else { - keyForMaps = null; + /** + * Create a KeyForValue. + * + * @param analysis the analysis + * @param annotations the annotations + * @param underlyingType the underlying type + */ + public KeyForValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + KeyForAnnotatedTypeFactory atypeFactory = + (KeyForAnnotatedTypeFactory) analysis.getTypeFactory(); + AnnotationMirror keyfor = atypeFactory.getAnnotationByClass(annotations, KeyFor.class); + if (keyfor != null + && (underlyingType.getKind() == TypeKind.TYPEVAR + || underlyingType.getKind() == TypeKind.WILDCARD)) { + List list = + AnnotationUtils.getElementValueArray( + keyfor, atypeFactory.keyForValueElement, String.class); + keyForMaps = new LinkedHashSet<>(list.size()); + keyForMaps.addAll(list); + } else { + keyForMaps = null; + } } - } - /** - * If the underlying type is a type variable or a wildcard, then this is a set of maps for which - * this value is a key. Otherwise, it's null. - */ - public @Nullable Set getKeyForMaps() { - return keyForMaps; - } + /** + * If the underlying type is a type variable or a wildcard, then this is a set of maps for which + * this value is a key. Otherwise, it's null. + */ + public @Nullable Set getKeyForMaps() { + return keyForMaps; + } - @Override - protected KeyForValue upperBound( - @Nullable KeyForValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { - KeyForValue upperBound = super.upperBound(other, upperBoundTypeMirror, shouldWiden); + @Override + protected KeyForValue upperBound( + @Nullable KeyForValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { + KeyForValue upperBound = super.upperBound(other, upperBoundTypeMirror, shouldWiden); - if (other == null || other.keyForMaps == null || this.keyForMaps == null) { - return upperBound; - } - // Lub the keyForMaps by intersecting the sets. - upperBound.keyForMaps = new LinkedHashSet<>(this.keyForMaps.size()); - upperBound.keyForMaps.addAll(this.keyForMaps); - upperBound.keyForMaps.retainAll(other.keyForMaps); - if (upperBound.keyForMaps.isEmpty()) { - upperBound.keyForMaps = null; + if (other == null || other.keyForMaps == null || this.keyForMaps == null) { + return upperBound; + } + // Lub the keyForMaps by intersecting the sets. + upperBound.keyForMaps = new LinkedHashSet<>(this.keyForMaps.size()); + upperBound.keyForMaps.addAll(this.keyForMaps); + upperBound.keyForMaps.retainAll(other.keyForMaps); + if (upperBound.keyForMaps.isEmpty()) { + upperBound.keyForMaps = null; + } + return upperBound; } - return upperBound; - } - @Override - public @Nullable KeyForValue mostSpecific( - @Nullable KeyForValue other, @Nullable KeyForValue backup) { - KeyForValue mostSpecific = super.mostSpecific(other, backup); - if (mostSpecific == null) { - if (other == null) { - return this; - } - // mostSpecific is null if the two types are not comparable. This is normally - // because one of this or other is a type variable and annotations is empty, but the - // other annotations are not empty. In this case, copy the keyForMaps and to the - // value with the no annotations and return it as most specific. - if (other.getAnnotations().isEmpty()) { - other.addKeyFor(this.keyForMaps); - return other; - } else if (this.getAnnotations().isEmpty()) { - this.addKeyFor(other.keyForMaps); - return this; - } - return null; - } + @Override + public @Nullable KeyForValue mostSpecific( + @Nullable KeyForValue other, @Nullable KeyForValue backup) { + KeyForValue mostSpecific = super.mostSpecific(other, backup); + if (mostSpecific == null) { + if (other == null) { + return this; + } + // mostSpecific is null if the two types are not comparable. This is normally + // because one of this or other is a type variable and annotations is empty, but the + // other annotations are not empty. In this case, copy the keyForMaps and to the + // value with the no annotations and return it as most specific. + if (other.getAnnotations().isEmpty()) { + other.addKeyFor(this.keyForMaps); + return other; + } else if (this.getAnnotations().isEmpty()) { + this.addKeyFor(other.keyForMaps); + return this; + } + return null; + } - mostSpecific.addKeyFor(this.keyForMaps); - if (other != null) { - mostSpecific.addKeyFor(other.keyForMaps); + mostSpecific.addKeyFor(this.keyForMaps); + if (other != null) { + mostSpecific.addKeyFor(other.keyForMaps); + } + return mostSpecific; } - return mostSpecific; - } - private void addKeyFor(Set newKeyForMaps) { - if (newKeyForMaps == null || newKeyForMaps.isEmpty()) { - return; - } - if (keyForMaps == null) { - keyForMaps = new LinkedHashSet<>(CollectionsPlume.mapCapacity(newKeyForMaps.size())); + private void addKeyFor(Set newKeyForMaps) { + if (newKeyForMaps == null || newKeyForMaps.isEmpty()) { + return; + } + if (keyForMaps == null) { + keyForMaps = new LinkedHashSet<>(CollectionsPlume.mapCapacity(newKeyForMaps.size())); + } + keyForMaps.addAll(newKeyForMaps); } - keyForMaps.addAll(newKeyForMaps); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java index 8e02ad65cae..c2657410411 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.nullness; -import javax.annotation.processing.SupportedOptions; import org.checkerframework.checker.initialization.InitializationChecker; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedLintOptions; +import javax.annotation.processing.SupportedOptions; + /** * An implementation of the nullness type-system, parameterized by an initialization type-system for * safe initialization. It uses freedom-before-commitment, augmented by type frames (which are @@ -32,73 +33,73 @@ * @checker_framework.manual #nullness-checker Nullness Checker */ @SupportedLintOptions({ - NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, - NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, - // Temporary option to forbid non-null array component types, which is allowed by default. - // Forbidding is sound and will eventually be the default. - // Allowing is unsound, as described in Section 3.3.4, "Nullness and arrays": - // https://eisop.github.io/cf/manual/#nullness-arrays - // It is the default temporarily, until we improve the analysis to reduce false positives or we - // learn what advice to give programmers about avoid false positive warnings. - // See issue #986: https://github.com/typetools/checker-framework/issues/986 - "soundArrayCreationNullness", - // Old name for soundArrayCreationNullness, for backward compatibility; remove in January 2021. - "forbidnonnullarraycomponents", - NullnessChecker.LINT_TRUSTARRAYLENZERO, - NullnessChecker.LINT_PERMITCLEARPROPERTY, + NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, + // Temporary option to forbid non-null array component types, which is allowed by default. + // Forbidding is sound and will eventually be the default. + // Allowing is unsound, as described in Section 3.3.4, "Nullness and arrays": + // https://eisop.github.io/cf/manual/#nullness-arrays + // It is the default temporarily, until we improve the analysis to reduce false positives or we + // learn what advice to give programmers about avoid false positive warnings. + // See issue #986: https://github.com/typetools/checker-framework/issues/986 + "soundArrayCreationNullness", + // Old name for soundArrayCreationNullness, for backward compatibility; remove in January 2021. + "forbidnonnullarraycomponents", + NullnessChecker.LINT_TRUSTARRAYLENZERO, + NullnessChecker.LINT_PERMITCLEARPROPERTY, }) @SupportedOptions({ - "assumeKeyFor", - "assumeInitialized", - "jspecifyNullMarkedAlias", - "conservativeArgumentNullnessAfterInvocation" + "assumeKeyFor", + "assumeInitialized", + "jspecifyNullMarkedAlias", + "conservativeArgumentNullnessAfterInvocation" }) @StubFiles({"junit-assertions.astub", "log4j.astub"}) public class NullnessChecker extends InitializationChecker { - /** Should we be strict about initialization of {@link MonotonicNonNull} variables? */ - public static final String LINT_NOINITFORMONOTONICNONNULL = "noInitForMonotonicNonNull"; + /** Should we be strict about initialization of {@link MonotonicNonNull} variables? */ + public static final String LINT_NOINITFORMONOTONICNONNULL = "noInitForMonotonicNonNull"; - /** Default for {@link #LINT_NOINITFORMONOTONICNONNULL}. */ - public static final boolean LINT_DEFAULT_NOINITFORMONOTONICNONNULL = false; + /** Default for {@link #LINT_NOINITFORMONOTONICNONNULL}. */ + public static final boolean LINT_DEFAULT_NOINITFORMONOTONICNONNULL = false; - /** - * Warn about redundant comparisons of an expression with {@code null}, if the expression is known - * to be non-null. - */ - public static final String LINT_REDUNDANTNULLCOMPARISON = "redundantNullComparison"; + /** + * Warn about redundant comparisons of an expression with {@code null}, if the expression is + * known to be non-null. + */ + public static final String LINT_REDUNDANTNULLCOMPARISON = "redundantNullComparison"; - /** Default for {@link #LINT_REDUNDANTNULLCOMPARISON}. */ - public static final boolean LINT_DEFAULT_REDUNDANTNULLCOMPARISON = false; + /** Default for {@link #LINT_REDUNDANTNULLCOMPARISON}. */ + public static final boolean LINT_DEFAULT_REDUNDANTNULLCOMPARISON = false; - /** - * Should the Nullness Checker unsoundly trust {@code @ArrayLen(0)} annotations to improve - * handling of {@link java.util.Collection#toArray()} by {@link CollectionToArrayHeuristics}? - */ - public static final String LINT_TRUSTARRAYLENZERO = "trustArrayLenZero"; + /** + * Should the Nullness Checker unsoundly trust {@code @ArrayLen(0)} annotations to improve + * handling of {@link java.util.Collection#toArray()} by {@link CollectionToArrayHeuristics}? + */ + public static final String LINT_TRUSTARRAYLENZERO = "trustArrayLenZero"; - /** Default for {@link #LINT_TRUSTARRAYLENZERO}. */ - public static final boolean LINT_DEFAULT_TRUSTARRAYLENZERO = false; + /** Default for {@link #LINT_TRUSTARRAYLENZERO}. */ + public static final boolean LINT_DEFAULT_TRUSTARRAYLENZERO = false; - /** - * If true, client code may clear system properties. If false (the default), some calls to {@code - * System.getProperty} are refined to return @NonNull. - */ - public static final String LINT_PERMITCLEARPROPERTY = "permitClearProperty"; + /** + * If true, client code may clear system properties. If false (the default), some calls to + * {@code System.getProperty} are refined to return @NonNull. + */ + public static final String LINT_PERMITCLEARPROPERTY = "permitClearProperty"; - /** Default for {@link #LINT_PERMITCLEARPROPERTY}. */ - public static final boolean LINT_DEFAULT_PERMITCLEARPROPERTY = false; + /** Default for {@link #LINT_PERMITCLEARPROPERTY}. */ + public static final boolean LINT_DEFAULT_PERMITCLEARPROPERTY = false; - /** Default constructor for NullnessChecker. */ - public NullnessChecker() {} + /** Default constructor for NullnessChecker. */ + public NullnessChecker() {} - @Override - public boolean checkPrimitives() { - return false; - } + @Override + public boolean checkPrimitives() { + return false; + } - @Override - public Class getTargetCheckerClass() { - return NullnessNoInitSubchecker.class; - } + @Override + public Class getTargetCheckerClass() { + return NullnessNoInitSubchecker.class; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java index 9ef53ddadb6..f1ff4ca3c0a 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnalysis.java @@ -1,46 +1,48 @@ package org.checkerframework.checker.nullness; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.type.TypeMirror; + /** * The analysis class for the non-null type system (serves as factory for the transfer function, * stores and abstract values. */ public class NullnessNoInitAnalysis - extends CFAbstractAnalysis { + extends CFAbstractAnalysis< + NullnessNoInitValue, NullnessNoInitStore, NullnessNoInitTransfer> { - /** - * Creates a new {@code NullnessAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public NullnessNoInitAnalysis( - BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { - super(checker, factory); - } + /** + * Creates a new {@code NullnessAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public NullnessNoInitAnalysis( + BaseTypeChecker checker, NullnessNoInitAnnotatedTypeFactory factory) { + super(checker, factory); + } - @Override - public NullnessNoInitStore createEmptyStore(boolean sequentialSemantics) { - return new NullnessNoInitStore(this, sequentialSemantics); - } + @Override + public NullnessNoInitStore createEmptyStore(boolean sequentialSemantics) { + return new NullnessNoInitStore(this, sequentialSemantics); + } - @Override - public NullnessNoInitStore createCopiedStore(NullnessNoInitStore s) { - return new NullnessNoInitStore(s); - } + @Override + public NullnessNoInitStore createCopiedStore(NullnessNoInitStore s) { + return new NullnessNoInitStore(s); + } - @Override - public @Nullable NullnessNoInitValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; + @Override + public @Nullable NullnessNoInitValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; + } + return new NullnessNoInitValue(this, annotations, underlyingType); } - return new NullnessNoInitValue(this, annotations, underlyingType); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java index b5b74cd97c8..91d9363fed1 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java @@ -15,18 +15,7 @@ import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.initialization.InitializationFieldAccessAnnotatedTypeFactory; import org.checkerframework.checker.initialization.InitializationFieldAccessSubchecker; import org.checkerframework.checker.initialization.InitializationFieldAccessTreeAnnotator; @@ -69,993 +58,1027 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + /** The annotated type factory for the nullness type-system. */ public class NullnessNoInitAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory< - NullnessNoInitValue, NullnessNoInitStore, NullnessNoInitTransfer, NullnessNoInitAnalysis> { - - /** The @{@link NonNull} annotation. */ - protected final AnnotationMirror NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); - - /** The @{@link Nullable} annotation. */ - protected final AnnotationMirror NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class); - - /** The @{@link PolyNull} annotation. */ - protected final AnnotationMirror POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class); - - /** The @{@link MonotonicNonNull} annotation. */ - protected final AnnotationMirror MONOTONIC_NONNULL = - AnnotationBuilder.fromClass(elements, MonotonicNonNull.class); - - /** Handles invocations of {@link java.lang.System#getProperty(String)}. */ - protected final SystemGetPropertyHandler systemGetPropertyHandler; - - /** Determines the nullness type of calls to {@link java.util.Collection#toArray()}. */ - protected final CollectionToArrayHeuristics collectionToArrayHeuristics; - - /** The Class.getCanonicalName() method. */ - protected final ExecutableElement classGetCanonicalName; - - /** The Arrays.copyOf() methods that operate on arrays of references. */ - private final List copyOfMethods; - - /** Cache for the nullness annotations. */ - protected final Set> nullnessAnnos; - - /** The Map.get method. */ - private final ExecutableElement mapGet = - TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); - - // List is in alphabetical order. If you update it, also update - // ../../../../../../../../docs/manual/nullness-checker.tex - // and make a pull request for variables NONNULL_ANNOTATIONS and BASE_COPYABLE_ANNOTATIONS in - // https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/core/handlers/HandlerUtil.java . - // Avoid changes to the string constants by ShadowJar relocate by using "start".toString() + - // "rest". - // Keep the original string constant in a comment to allow searching for it. - /** Aliases for {@code @Nonnull}. */ - @SuppressWarnings({ - "signature:argument.type.incompatible", // Class names intentionally obfuscated - "signature:assignment.type.incompatible" // Class names intentionally obfuscated - }) - private static final List<@FullyQualifiedName String> NONNULL_ALIASES = - Arrays.<@FullyQualifiedName String>asList( - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java - // https://developer.android.com/reference/androidx/annotation/NonNull - "android.annotation.NonNull", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/NonNull.java - // https://developer.android.com/reference/android/support/annotation/NonNull - "android.support.annotation.NonNull", - // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java - "android.support.annotation.RecentlyNonNull", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/NonNull.java - "androidx.annotation.NonNull", - // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java - "androidx.annotation.RecentlyNonNull", - // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/NonNull.java - "com.android.annotations.NonNull", - // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/NotNull.java - // "com.google.firebase.database.annotations.NotNull", - "com.go".toString() + "ogle.firebase.database.annotations.NotNull", - // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/NonNull.java - // "com.google.firebase.internal.NonNull", - "com.go".toString() + "ogle.firebase.internal.NonNull", - // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/NonNull.java - "com.mongodb.lang.NonNull", - // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/NotNull.java - "com.sun.istack.NotNull", - // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/NotNull.java - "com.sun.istack.internal.NotNull", - // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/NotNull.java - "com.unboundid.util.NotNull", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/NonNull.html - "edu.umd.cs.findbugs.annotations.NonNull", - // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/NonNull.java - "io.micrometer.core.lang.NonNull", - // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/NonNull.java - "io.micronaut.core.annotation.NonNull", - // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/NonNull.java - "io.reactivex.annotations.NonNull", - // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/NonNull.java - "io.reactivex.rxjava3.annotations.NonNull", - // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nonnull.java - "jakarta.annotation.Nonnull", - // https://jcp.org/en/jsr/detail?id=305; no documentation at - // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nonnull.html - "javax.annotation.Nonnull", - // https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/NotNull.html - "javax.validation.constraints.NotNull", - // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/NonNull.java - "libcore.util.NonNull", - // https://github.com/projectlombok/lombok/blob/master/src/core/lombok/NonNull.java - "lombok.NonNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java - "net.bytebuddy.agent.utility.nullability.NeverNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/NeverNull.java - "net.bytebuddy.utility.nullability.NeverNull", - // Removed in ANTLR 4.6. - // https://github.com/antlr/antlr4/blob/master/runtime/Java/src/org/antlr/v4/runtime/misc/NotNull.java - "org.antlr.v4.runtime.misc.NotNull", - // https://search.maven.org/artifact/org.checkerframework/checker-compat-qual/2.5.5/jar - "org.checkerframework.checker.nullness.compatqual.NonNullDecl", - "org.checkerframework.checker.nullness.compatqual.NonNullType", - // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/NotNull.html - "org.codehaus.commons.nullanalysis.NotNull", - // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/NonNull.html - // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/NonNull.java - "org.eclipse.jdt.annotation.NonNull", - // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java - "org.eclipse.jgit.annotations.NonNull", - // https://github.com/eclipse/lsp4j/blob/main/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java - "org.eclipse.lsp4j.jsonrpc.validation.NonNull", - // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/NotNull.java - // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html - "org.jetbrains.annotations.NotNull", - // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/NonNull.java - "org.jmlspecs.annotation.NonNull", - // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/NonNull.java - "org.jspecify.annotations.NonNull", - // 2022-11-17: Deprecated old package location, remove after some grace period - // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness - "org.jspecify.nullness.NonNull", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NonNull.html - "org.netbeans.api.annotations.common.NonNull", - // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/NonNull.java - "org.springframework.lang.NonNull", - // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/NonNull.java - "reactor.util.annotation.NonNull"); - - // List is in alphabetical order. If you update it, also update - // ../../../../../../../../docs/manual/nullness-checker.tex . - // See more comments with NONNULL_ALIASES above. - /** Aliases for {@code @Nullable}. */ - @SuppressWarnings({ - "signature:argument.type.incompatible", // Class names intentionally obfuscated - "signature:assignment.type.incompatible" // Class names intentionally obfuscated - }) - private static final List<@FullyQualifiedName String> NULLABLE_ALIASES = - Arrays.<@FullyQualifiedName String>asList( - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java - // https://developer.android.com/reference/androidx/annotation/Nullable - "android.annotation.Nullable", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/Nullable.java - // https://developer.android.com/reference/android/support/annotation/Nullable - "android.support.annotation.Nullable", - // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java - "android.support.annotation.RecentlyNullable", - // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/Nullable.java - "androidx.annotation.Nullable", - // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java - "androidx.annotation.RecentlyNullable", - // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/Nullable.java - "com.android.annotations.Nullable", - // https://github.com/lpantano/java_seqbuster/blob/master/AdRec/src/adrec/com/beust/jcommander/internal/Nullable.java - "com.beust.jcommander.internal.Nullable", - // https://github.com/cloudendpoints/endpoints-java/blob/master/endpoints-framework/src/main/java/com/google/api/server/spi/config/Nullable.java - // "com.google.api.server.spi.config.Nullable", - "com.go".toString() + "ogle.api.server.spi.config.Nullable", - // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/Nullable.java - // "com.google.firebase.database.annotations.Nullable", - "com.go".toString() + "ogle.firebase.database.annotations.Nullable", - // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/Nullable.java - // "com.google.firebase.internal.Nullable", - "com.go".toString() + "ogle.firebase.internal.Nullable", - // https://gerrit.googlesource.com/gerrit/+/refs/heads/master/java/com/google/gerrit/common/Nullable.java - // "com.google.gerrit.common.Nullable", - "com.go".toString() + "ogle.gerrit.common.Nullable", - // - // "com.google.protobuf.Internal.ProtoMethodAcceptsNullParameter", - "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodAcceptsNullParameter", - // - // "com.google.protobuf.Internal.ProtoMethodMayReturnNull", - "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodMayReturnNull", - // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/Nullable.java - "com.mongodb.lang.Nullable", - // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/Nullable.java - "com.sun.istack.Nullable", - // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/Nullable.java - "com.sun.istack.internal.Nullable", - // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/Nullable.java - "com.unboundid.util.Nullable", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/CheckForNull.html - "edu.umd.cs.findbugs.annotations.CheckForNull", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html - "edu.umd.cs.findbugs.annotations.Nullable", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/PossiblyNull.html - "edu.umd.cs.findbugs.annotations.PossiblyNull", - // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/UnknownNullness.html - "edu.umd.cs.findbugs.annotations.UnknownNullness", - // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/Nullable.java - "io.micrometer.core.lang.Nullable", - // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/Nullable.java - "io.micronaut.core.annotation.Nullable", - // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Nullable.java - "io.reactivex.annotations.Nullable", - // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/Nullable.java - "io.reactivex.rxjava3.annotations.Nullable", - // https://github.com/eclipse-vertx/vertx-codegen/blob/master/src/main/java/io/vertx/codegen/annotations/Nullable.java - "io.vertx.codegen.annotations.Nullable", - // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nullable.java - "jakarta.annotation.Nullable", - // https://jcp.org/en/jsr/detail?id=305; no documentation at - // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nullable.html - "javax.annotation.CheckForNull", - "javax.annotation.Nullable", - // https://github.com/Pragmatists/JUnitParams/blob/master/src/main/java/junitparams/converters/Nullable.java - "junitparams.converters.Nullable", - // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/Nullable.java - "libcore.util.Nullable", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java - "net.bytebuddy.agent.utility.nullability.AlwaysNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java - "net.bytebuddy.agent.utility.nullability.MaybeNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java - "net.bytebuddy.agent.utility.nullability.UnknownNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/AlwaysNull.java - "net.bytebuddy.utility.nullability.AlwaysNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/MaybeNull.java - "net.bytebuddy.utility.nullability.MaybeNull", - // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/UnknownNull.java - "net.bytebuddy.utility.nullability.UnknownNull", - // https://github.com/apache/avro/blob/master/lang/java/avro/src/main/java/org/apache/avro/reflect/Nullable.java - // "org.apache.avro.reflect.Nullable", - "org.apa".toString() + "che.avro.reflect.Nullable", - // https://github.com/apache/cxf/blob/master/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/Nullable.java - // "org.apache.cxf.jaxrs.ext.Nullable", - "org.apa".toString() + "che.cxf.jaxrs.ext.Nullable", - // https://github.com/gatein/gatein-shindig/blob/master/java/common/src/main/java/org/apache/shindig/common/Nullable.java - // "org.apache.shindig.common.Nullable", - "org.apa".toString() + "che.shindig.common.Nullable", - // https://search.maven.org/search?q=a:checker-compat-qual - "org.checkerframework.checker.nullness.compatqual.NullableDecl", - "org.checkerframework.checker.nullness.compatqual.NullableType", - // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/Nullable.html - "org.codehaus.commons.nullanalysis.Nullable", - // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/Nullable.html - // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Nullable.java - "org.eclipse.jdt.annotation.Nullable", - // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java - "org.eclipse.jgit.annotations.Nullable", - // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/Nullable.java - // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html - "org.jetbrains.annotations.Nullable", - // https://github.com/JetBrains/java-annotations/blob/master/java8/src/main/java/org/jetbrains/annotations/UnknownNullability.java - "org.jetbrains.annotations.UnknownNullability", - // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/Nullable.java - "org.jmlspecs.annotation.Nullable", - // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/Nullable.java - "org.jspecify.annotations.Nullable", - // 2022-11-17: Deprecated old package location, remove after some grace period - // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness - "org.jspecify.nullness.Nullable", - "org.jspecify.nullness.NullnessUnspecified", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/CheckForNull.html - "org.netbeans.api.annotations.common.CheckForNull", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullAllowed.html - "org.netbeans.api.annotations.common.NullAllowed", - // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullUnknown.html - "org.netbeans.api.annotations.common.NullUnknown", - // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/Nullable.java - "org.springframework.lang.Nullable", - // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/Nullable.java - "reactor.util.annotation.Nullable"); - - // List is in alphabetical order. If you update it, also update - // ../../../../../../../../docs/manual/nullness-checker.tex . - // See more comments with NONNULL_ALIASES above. - /** Aliases for {@code @PolyNull}. */ - @SuppressWarnings({ - "signature:argument.type.incompatible", // Class names intentionally obfuscated - "signature:assignment.type.incompatible" // Class names intentionally obfuscated - }) - private static final List<@FullyQualifiedName String> POLYNULL_ALIASES = - Arrays.<@FullyQualifiedName String>asList( - // "com.google.protobuf.Internal.ProtoPassThroughNullness", - "com.go".toString() + "ogle.protobuf.Internal.ProtoPassThroughNullness"); - - /** - * Creates a NullnessAnnotatedTypeFactory. - * - * @param checker the associated {@link NullnessNoInitSubchecker} - */ - public NullnessNoInitAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - Set> tempNullnessAnnos = new LinkedHashSet<>(4); - tempNullnessAnnos.add(NonNull.class); - tempNullnessAnnos.add(MonotonicNonNull.class); - tempNullnessAnnos.add(Nullable.class); - tempNullnessAnnos.add(PolyNull.class); - nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos); - - NONNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NONNULL)); - NULLABLE_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NULLABLE)); - POLYNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, POLYNULL)); - - // Add compatibility annotations: - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.PolyNullDecl", POLYNULL); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl", MONOTONIC_NONNULL); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.PolyNullType", POLYNULL); - addAliasedTypeAnnotation( - "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType", MONOTONIC_NONNULL); - - if (checker.getUltimateParentChecker().getBooleanOption("jspecifyNullMarkedAlias", true)) { - AnnotationMirror nullMarkedDefaultQual = - new AnnotationBuilder(processingEnv, DefaultQualifier.class) - .setValue("value", NonNull.class) - .setValue("locations", new TypeUseLocation[] {TypeUseLocation.UPPER_BOUND}) - .setValue("applyToSubpackages", false) - .build(); - addAliasedDeclAnnotation( - "org.jspecify.annotations.NullMarked", - DefaultQualifier.class.getCanonicalName(), - nullMarkedDefaultQual); - - // 2022-11-17: Deprecated old package location, remove after some grace period - addAliasedDeclAnnotation( - "org.jspecify.nullness.NullMarked", - DefaultQualifier.class.getCanonicalName(), - nullMarkedDefaultQual); - } + extends GenericAnnotatedTypeFactory< + NullnessNoInitValue, + NullnessNoInitStore, + NullnessNoInitTransfer, + NullnessNoInitAnalysis> { + + /** The @{@link NonNull} annotation. */ + protected final AnnotationMirror NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); + + /** The @{@link Nullable} annotation. */ + protected final AnnotationMirror NULLABLE = + AnnotationBuilder.fromClass(elements, Nullable.class); + + /** The @{@link PolyNull} annotation. */ + protected final AnnotationMirror POLYNULL = + AnnotationBuilder.fromClass(elements, PolyNull.class); + + /** The @{@link MonotonicNonNull} annotation. */ + protected final AnnotationMirror MONOTONIC_NONNULL = + AnnotationBuilder.fromClass(elements, MonotonicNonNull.class); + + /** Handles invocations of {@link java.lang.System#getProperty(String)}. */ + protected final SystemGetPropertyHandler systemGetPropertyHandler; + + /** Determines the nullness type of calls to {@link java.util.Collection#toArray()}. */ + protected final CollectionToArrayHeuristics collectionToArrayHeuristics; + + /** The Class.getCanonicalName() method. */ + protected final ExecutableElement classGetCanonicalName; + + /** The Arrays.copyOf() methods that operate on arrays of references. */ + private final List copyOfMethods; + + /** Cache for the nullness annotations. */ + protected final Set> nullnessAnnos; + + /** The Map.get method. */ + private final ExecutableElement mapGet = + TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv); + + // List is in alphabetical order. If you update it, also update + // ../../../../../../../../docs/manual/nullness-checker.tex + // and make a pull request for variables NONNULL_ANNOTATIONS and BASE_COPYABLE_ANNOTATIONS in + // https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/core/handlers/HandlerUtil.java . + // Avoid changes to the string constants by ShadowJar relocate by using "start".toString() + + // "rest". + // Keep the original string constant in a comment to allow searching for it. + /** Aliases for {@code @Nonnull}. */ + @SuppressWarnings({ + "signature:argument.type.incompatible", // Class names intentionally obfuscated + "signature:assignment.type.incompatible" // Class names intentionally obfuscated + }) + private static final List<@FullyQualifiedName String> NONNULL_ALIASES = + Arrays.<@FullyQualifiedName String>asList( + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java + // https://developer.android.com/reference/androidx/annotation/NonNull + "android.annotation.NonNull", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/NonNull.java + // https://developer.android.com/reference/android/support/annotation/NonNull + "android.support.annotation.NonNull", + // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java + "android.support.annotation.RecentlyNonNull", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/NonNull.java + "androidx.annotation.NonNull", + // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java + "androidx.annotation.RecentlyNonNull", + // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/NonNull.java + "com.android.annotations.NonNull", + // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/NotNull.java + // "com.google.firebase.database.annotations.NotNull", + "com.go".toString() + "ogle.firebase.database.annotations.NotNull", + // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/NonNull.java + // "com.google.firebase.internal.NonNull", + "com.go".toString() + "ogle.firebase.internal.NonNull", + // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/NonNull.java + "com.mongodb.lang.NonNull", + // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/NotNull.java + "com.sun.istack.NotNull", + // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/NotNull.java + "com.sun.istack.internal.NotNull", + // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/NotNull.java + "com.unboundid.util.NotNull", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/NonNull.html + "edu.umd.cs.findbugs.annotations.NonNull", + // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/NonNull.java + "io.micrometer.core.lang.NonNull", + // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/NonNull.java + "io.micronaut.core.annotation.NonNull", + // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/NonNull.java + "io.reactivex.annotations.NonNull", + // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/NonNull.java + "io.reactivex.rxjava3.annotations.NonNull", + // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nonnull.java + "jakarta.annotation.Nonnull", + // https://jcp.org/en/jsr/detail?id=305; no documentation at + // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nonnull.html + "javax.annotation.Nonnull", + // https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/NotNull.html + "javax.validation.constraints.NotNull", + // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/NonNull.java + "libcore.util.NonNull", + // https://github.com/projectlombok/lombok/blob/master/src/core/lombok/NonNull.java + "lombok.NonNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/NeverNull.java + "net.bytebuddy.agent.utility.nullability.NeverNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/NeverNull.java + "net.bytebuddy.utility.nullability.NeverNull", + // Removed in ANTLR 4.6. + // https://github.com/antlr/antlr4/blob/master/runtime/Java/src/org/antlr/v4/runtime/misc/NotNull.java + "org.antlr.v4.runtime.misc.NotNull", + // https://search.maven.org/artifact/org.checkerframework/checker-compat-qual/2.5.5/jar + "org.checkerframework.checker.nullness.compatqual.NonNullDecl", + "org.checkerframework.checker.nullness.compatqual.NonNullType", + // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/NotNull.html + "org.codehaus.commons.nullanalysis.NotNull", + // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/NonNull.html + // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/NonNull.java + "org.eclipse.jdt.annotation.NonNull", + // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java + "org.eclipse.jgit.annotations.NonNull", + // https://github.com/eclipse/lsp4j/blob/main/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java + "org.eclipse.lsp4j.jsonrpc.validation.NonNull", + // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/NotNull.java + // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html + "org.jetbrains.annotations.NotNull", + // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/NonNull.java + "org.jmlspecs.annotation.NonNull", + // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/NonNull.java + "org.jspecify.annotations.NonNull", + // 2022-11-17: Deprecated old package location, remove after some grace period + // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness + "org.jspecify.nullness.NonNull", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NonNull.html + "org.netbeans.api.annotations.common.NonNull", + // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/NonNull.java + "org.springframework.lang.NonNull", + // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/NonNull.java + "reactor.util.annotation.NonNull"); + + // List is in alphabetical order. If you update it, also update + // ../../../../../../../../docs/manual/nullness-checker.tex . + // See more comments with NONNULL_ALIASES above. + /** Aliases for {@code @Nullable}. */ + @SuppressWarnings({ + "signature:argument.type.incompatible", // Class names intentionally obfuscated + "signature:assignment.type.incompatible" // Class names intentionally obfuscated + }) + private static final List<@FullyQualifiedName String> NULLABLE_ALIASES = + Arrays.<@FullyQualifiedName String>asList( + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java + // https://developer.android.com/reference/androidx/annotation/Nullable + "android.annotation.Nullable", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/android/support/annotation/Nullable.java + // https://developer.android.com/reference/android/support/annotation/Nullable + "android.support.annotation.Nullable", + // https://android.googlesource.com/platform/tools/metalava/+/9ad32fadc5a22e1357c82b447e33ec7fecdcd8c1/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java + "android.support.annotation.RecentlyNullable", + // https://android.googlesource.com/platform/frameworks/support/+/master/annotations/src/main/java/androidx/annotation/Nullable.java + "androidx.annotation.Nullable", + // https://android.googlesource.com/platform/tools/metalava/+/master/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java + "androidx.annotation.RecentlyNullable", + // https://android.googlesource.com/platform/sdk/+/66fcecc/common/src/com/android/annotations/Nullable.java + "com.android.annotations.Nullable", + // https://github.com/lpantano/java_seqbuster/blob/master/AdRec/src/adrec/com/beust/jcommander/internal/Nullable.java + "com.beust.jcommander.internal.Nullable", + // https://github.com/cloudendpoints/endpoints-java/blob/master/endpoints-framework/src/main/java/com/google/api/server/spi/config/Nullable.java + // "com.google.api.server.spi.config.Nullable", + "com.go".toString() + "ogle.api.server.spi.config.Nullable", + // https://github.com/firebase/firebase-android-sdk/blob/master/firebase-database/src/main/java/com/google/firebase/database/annotations/Nullable.java + // "com.google.firebase.database.annotations.Nullable", + "com.go".toString() + "ogle.firebase.database.annotations.Nullable", + // https://github.com/firebase/firebase-admin-java/blob/master/src/main/java/com/google/firebase/internal/Nullable.java + // "com.google.firebase.internal.Nullable", + "com.go".toString() + "ogle.firebase.internal.Nullable", + // https://gerrit.googlesource.com/gerrit/+/refs/heads/master/java/com/google/gerrit/common/Nullable.java + // "com.google.gerrit.common.Nullable", + "com.go".toString() + "ogle.gerrit.common.Nullable", + // + // "com.google.protobuf.Internal.ProtoMethodAcceptsNullParameter", + "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodAcceptsNullParameter", + // + // "com.google.protobuf.Internal.ProtoMethodMayReturnNull", + "com.go".toString() + "ogle.protobuf.Internal.ProtoMethodMayReturnNull", + // https://github.com/mongodb/mongo-java-driver/blob/master/driver-core/src/main/com/mongodb/lang/Nullable.java + "com.mongodb.lang.Nullable", + // https://github.com/eclipse-ee4j/jaxb-istack-commons/blob/master/istack-commons/runtime/src/main/java/com/sun/istack/Nullable.java + "com.sun.istack.Nullable", + // https://github.com/openjdk/jdk8/blob/master/jaxws/src/share/jaxws_classes/com/sun/istack/internal/Nullable.java + "com.sun.istack.internal.Nullable", + // https://github.com/pingidentity/ldapsdk/blob/master/src/com/unboundid/util/Nullable.java + "com.unboundid.util.Nullable", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/CheckForNull.html + "edu.umd.cs.findbugs.annotations.CheckForNull", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/Nullable.html + "edu.umd.cs.findbugs.annotations.Nullable", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/PossiblyNull.html + "edu.umd.cs.findbugs.annotations.PossiblyNull", + // https://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/UnknownNullness.html + "edu.umd.cs.findbugs.annotations.UnknownNullness", + // https://github.com/micrometer-metrics/micrometer/blob/main/micrometer-core/src/main/java/io/micrometer/core/lang/Nullable.java + "io.micrometer.core.lang.Nullable", + // https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/annotation/Nullable.java + "io.micronaut.core.annotation.Nullable", + // https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Nullable.java + "io.reactivex.annotations.Nullable", + // https://github.com/ReactiveX/RxJava/blob/3.x/src/main/java/io/reactivex/rxjava3/annotations/Nullable.java + "io.reactivex.rxjava3.annotations.Nullable", + // https://github.com/eclipse-vertx/vertx-codegen/blob/master/src/main/java/io/vertx/codegen/annotations/Nullable.java + "io.vertx.codegen.annotations.Nullable", + // https://github.com/jakartaee/common-annotations-api/blob/master/api/src/main/java/jakarta/annotation/Nullable.java + "jakarta.annotation.Nullable", + // https://jcp.org/en/jsr/detail?id=305; no documentation at + // https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.1/javax/annotation/Nullable.html + "javax.annotation.CheckForNull", + "javax.annotation.Nullable", + // https://github.com/Pragmatists/JUnitParams/blob/master/src/main/java/junitparams/converters/Nullable.java + "junitparams.converters.Nullable", + // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/Nullable.java + "libcore.util.Nullable", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/AlwaysNull.java + "net.bytebuddy.agent.utility.nullability.AlwaysNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/MaybeNull.java + "net.bytebuddy.agent.utility.nullability.MaybeNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-agent/src/main/java/net/bytebuddy/agent/utility/nullability/UnknownNull.java + "net.bytebuddy.agent.utility.nullability.UnknownNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/AlwaysNull.java + "net.bytebuddy.utility.nullability.AlwaysNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/MaybeNull.java + "net.bytebuddy.utility.nullability.MaybeNull", + // https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/utility/nullability/UnknownNull.java + "net.bytebuddy.utility.nullability.UnknownNull", + // https://github.com/apache/avro/blob/master/lang/java/avro/src/main/java/org/apache/avro/reflect/Nullable.java + // "org.apache.avro.reflect.Nullable", + "org.apa".toString() + "che.avro.reflect.Nullable", + // https://github.com/apache/cxf/blob/master/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/Nullable.java + // "org.apache.cxf.jaxrs.ext.Nullable", + "org.apa".toString() + "che.cxf.jaxrs.ext.Nullable", + // https://github.com/gatein/gatein-shindig/blob/master/java/common/src/main/java/org/apache/shindig/common/Nullable.java + // "org.apache.shindig.common.Nullable", + "org.apa".toString() + "che.shindig.common.Nullable", + // https://search.maven.org/search?q=a:checker-compat-qual + "org.checkerframework.checker.nullness.compatqual.NullableDecl", + "org.checkerframework.checker.nullness.compatqual.NullableType", + // https://janino-compiler.github.io/janino/apidocs/org/codehaus/commons/nullanalysis/Nullable.html + "org.codehaus.commons.nullanalysis.Nullable", + // https://help.eclipse.org/neon/index.jsp?topic=/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/annotation/Nullable.html + // https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tree/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Nullable.java + "org.eclipse.jdt.annotation.Nullable", + // https://github.com/eclipse/jgit/blob/master/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java + "org.eclipse.jgit.annotations.Nullable", + // https://github.com/JetBrains/intellij-community/blob/master/platform/annotations/java8/src/org/jetbrains/annotations/Nullable.java + // https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html + "org.jetbrains.annotations.Nullable", + // https://github.com/JetBrains/java-annotations/blob/master/java8/src/main/java/org/jetbrains/annotations/UnknownNullability.java + "org.jetbrains.annotations.UnknownNullability", + // http://svn.code.sf.net/p/jmlspecs/code/JMLAnnotations/trunk/src/org/jmlspecs/annotation/Nullable.java + "org.jmlspecs.annotation.Nullable", + // https://github.com/jspecify/jspecify/blob/main/src/main/java/org/jspecify/annotations/Nullable.java + "org.jspecify.annotations.Nullable", + // 2022-11-17: Deprecated old package location, remove after some grace period + // https://github.com/jspecify/jspecify/tree/main/src/main/java/org/jspecify/nullness + "org.jspecify.nullness.Nullable", + "org.jspecify.nullness.NullnessUnspecified", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/CheckForNull.html + "org.netbeans.api.annotations.common.CheckForNull", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullAllowed.html + "org.netbeans.api.annotations.common.NullAllowed", + // http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/NullUnknown.html + "org.netbeans.api.annotations.common.NullUnknown", + // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/lang/Nullable.java + "org.springframework.lang.Nullable", + // https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/util/annotation/Nullable.java + "reactor.util.annotation.Nullable"); + + // List is in alphabetical order. If you update it, also update + // ../../../../../../../../docs/manual/nullness-checker.tex . + // See more comments with NONNULL_ALIASES above. + /** Aliases for {@code @PolyNull}. */ + @SuppressWarnings({ + "signature:argument.type.incompatible", // Class names intentionally obfuscated + "signature:assignment.type.incompatible" // Class names intentionally obfuscated + }) + private static final List<@FullyQualifiedName String> POLYNULL_ALIASES = + Arrays.<@FullyQualifiedName String>asList( + // "com.google.protobuf.Internal.ProtoPassThroughNullness", + "com.go".toString() + "ogle.protobuf.Internal.ProtoPassThroughNullness"); - boolean permitClearProperty = - checker.getLintOption( - NullnessChecker.LINT_PERMITCLEARPROPERTY, - NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); - systemGetPropertyHandler = - new SystemGetPropertyHandler(processingEnv, this, permitClearProperty); - - classGetCanonicalName = - TreeUtils.getMethod("java.lang.Class", "getCanonicalName", 0, processingEnv); - copyOfMethods = - Arrays.asList( - TreeUtils.getMethod("java.util.Arrays", "copyOf", processingEnv, "T[]", "int"), - TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); - - postInit(); - - // do this last, as it might use the factory again. - this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(checker, this); - } - - @Override - public NullnessNoInitSubchecker getChecker() { - return (NullnessNoInitSubchecker) checker; - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>( - Arrays.asList(Nullable.class, MonotonicNonNull.class, NonNull.class, PolyNull.class)); - } - - /** - * For types of left-hand side of an assignment, this method replaces {@link PolyNull} with {@link - * Nullable} (or with {@link NonNull} if the org.checkerframework.dataflow analysis has determined - * that this is allowed soundly. For example: - * - *
           @PolyNull String foo(@PolyNull String param) {
          -   *    if (param == null) {
          -   *        //  @PolyNull is really @Nullable, so change
          -   *        // the type of param to @Nullable.
          -   *        param = null;
          -   *    }
          -   *    return param;
          -   * }
          -   * 
          - * - * @param lhsType type to replace whose polymorphic qualifier will be replaced - * @param context tree used to get dataflow value - */ - protected void replacePolyQualifier(AnnotatedTypeMirror lhsType, Tree context) { - if (lhsType.hasAnnotation(PolyNull.class)) { - NullnessNoInitValue inferred = getInferredValueFor(context); - if (inferred != null) { - if (inferred.isPolyNullNonNull) { - lhsType.replaceAnnotation(NONNULL); - } else if (inferred.isPolyNullNull) { - lhsType.replaceAnnotation(NULLABLE); + /** + * Creates a NullnessAnnotatedTypeFactory. + * + * @param checker the associated {@link NullnessNoInitSubchecker} + */ + public NullnessNoInitAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + Set> tempNullnessAnnos = new LinkedHashSet<>(4); + tempNullnessAnnos.add(NonNull.class); + tempNullnessAnnos.add(MonotonicNonNull.class); + tempNullnessAnnos.add(Nullable.class); + tempNullnessAnnos.add(PolyNull.class); + nullnessAnnos = Collections.unmodifiableSet(tempNullnessAnnos); + + NONNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NONNULL)); + NULLABLE_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, NULLABLE)); + POLYNULL_ALIASES.forEach(annotation -> addAliasedTypeAnnotation(annotation, POLYNULL)); + + // Add compatibility annotations: + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.PolyNullDecl", POLYNULL); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl", + MONOTONIC_NONNULL); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.PolyNullType", POLYNULL); + addAliasedTypeAnnotation( + "org.checkerframework.checker.nullness.compatqual.MonotonicNonNullType", + MONOTONIC_NONNULL); + + if (checker.getUltimateParentChecker().getBooleanOption("jspecifyNullMarkedAlias", true)) { + AnnotationMirror nullMarkedDefaultQual = + new AnnotationBuilder(processingEnv, DefaultQualifier.class) + .setValue("value", NonNull.class) + .setValue( + "locations", + new TypeUseLocation[] {TypeUseLocation.UPPER_BOUND}) + .setValue("applyToSubpackages", false) + .build(); + addAliasedDeclAnnotation( + "org.jspecify.annotations.NullMarked", + DefaultQualifier.class.getCanonicalName(), + nullMarkedDefaultQual); + + // 2022-11-17: Deprecated old package location, remove after some grace period + addAliasedDeclAnnotation( + "org.jspecify.nullness.NullMarked", + DefaultQualifier.class.getCanonicalName(), + nullMarkedDefaultQual); } - } - } - } - - @Override - protected NullnessNoInitAnalysis createFlowAnalysis() { - return new NullnessNoInitAnalysis(checker, this); - } - - @Override - public NullnessNoInitTransfer createFlowTransferFunction( - CFAbstractAnalysis - analysis) { - return new NullnessNoInitTransfer((NullnessNoInitAnalysis) analysis); - } - - /** - * Returns an AnnotatedTypeFormatter that does not print the qualifiers on null literals. - * - * @return an AnnotatedTypeFormatter that does not print the qualifiers on null literals - */ - @Override - protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { - boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); - return new NullnessNoInitAnnotatedTypeFormatter( - printVerboseGenerics, - // -AprintVerboseGenerics implies -AprintAllQualifiers - printVerboseGenerics || checker.hasOption("printAllQualifiers")); - } - - @Override - protected ParameterizedExecutableType methodFromUse( - MethodInvocationTree tree, boolean inferTypeArgs) { - ParameterizedExecutableType mType = super.methodFromUse(tree, inferTypeArgs); - AnnotatedExecutableType method = mType.executableType; - - // Special cases for method invocations with specific arguments. - systemGetPropertyHandler.handle(tree, method); - collectionToArrayHeuristics.handle(tree, method); - // `MyClass.class.getCanonicalName()` is non-null. - if (TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv)) { - ExpressionTree receiver = ((MemberSelectTree) tree.getMethodSelect()).getExpression(); - if (TreeUtils.isClassLiteral(receiver)) { - AnnotatedTypeMirror type = method.getReturnType(); - type.replaceAnnotation(NONNULL); - } - } - return mType; - } - - @Override - public void adaptGetClassReturnTypeToReceiver( - AnnotatedExecutableType getClassType, AnnotatedTypeMirror receiverType, ExpressionTree tree) { - - super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); - - // Make the captured wildcard always @NonNull, regardless of the declared type. - - AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); - List typeArgs = returnAdt.getTypeArguments(); - AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); - classWildcardArg.getUpperBound().replaceAnnotation(NONNULL); - } - - @Override - public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { - AnnotatedTypeMirror result = super.getMethodReturnType(m, r); - replacePolyQualifier(result, r); - return result; - } - - @Override - public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { - InitializationFieldAccessAnnotatedTypeFactory initFactory = - getChecker().getTypeFactoryOfSubcheckerOrNull(InitializationFieldAccessSubchecker.class); - if (initFactory == null) { - // init checker is deactivated. - return super.isNotFullyInitializedReceiver(methodDeclTree); - } - return initFactory.isNotFullyInitializedReceiver(methodDeclTree); - } - - @Override - public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { - InitializationFieldAccessAnnotatedTypeFactory initFactory = - getChecker().getTypeFactoryOfSubcheckerOrNull(InitializationFieldAccessSubchecker.class); - if (initFactory == null) { - // init checker is deactivated. - return super.getAnnotatedTypeBefore(expr, tree); + boolean permitClearProperty = + checker.getLintOption( + NullnessChecker.LINT_PERMITCLEARPROPERTY, + NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); + systemGetPropertyHandler = + new SystemGetPropertyHandler(processingEnv, this, permitClearProperty); + + classGetCanonicalName = + TreeUtils.getMethod("java.lang.Class", "getCanonicalName", 0, processingEnv); + copyOfMethods = + Arrays.asList( + TreeUtils.getMethod( + "java.util.Arrays", "copyOf", processingEnv, "T[]", "int"), + TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); + + postInit(); + + // do this last, as it might use the factory again. + this.collectionToArrayHeuristics = new CollectionToArrayHeuristics(checker, this); } - if (expr instanceof FieldAccess) { - FieldAccess fa = (FieldAccess) expr; - JavaExpression receiver = fa.getReceiver(); - TypeMirror declaringClass = fa.getField().getEnclosingElement().asType(); - AnnotatedTypeMirror receiverType; - - if (receiver instanceof LocalVariable) { - Element receiverElem = ((LocalVariable) receiver).getElement(); - receiverType = initFactory.getAnnotatedType(receiverElem); - } else if (receiver instanceof ThisReference) { - receiverType = initFactory.getSelfType(tree); - } else { - return super.getAnnotatedTypeBefore(expr, tree); - } - if (initFactory.isInitializedForFrame(receiverType, declaringClass)) { - AnnotatedTypeMirror declared = getAnnotatedType(fa.getField()); - AnnotatedTypeMirror refined = super.getAnnotatedTypeBefore(expr, tree); - AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(fa.getType(), this, false); - // If the expression is initialized, then by definition, it has at least its - // declared annotation. - // Assuming the correctness of the Nullness Checker's type refinement, - // it also has its refined annotation. - // We thus use the GLB of those two annotations. - res.addAnnotations( - qualHierarchy.greatestLowerBoundsShallow( - declared.getAnnotations(), - declared.getUnderlyingType(), - refined.getAnnotations(), - refined.getUnderlyingType())); - return res; - } + @Override + public NullnessNoInitSubchecker getChecker() { + return (NullnessNoInitSubchecker) checker; } - // Is there anything better we could do? - // Ideally, we would turn the expression string into a Tree or Element - // instead of a JavaExpression, so we could use - // atypeFactory.getAnnotatedType on the whole expression, - // but that doesn't seem possible. - return super.getAnnotatedTypeBefore(expr, tree); - } - - @Override - protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { - DefaultForTypeAnnotator defaultForTypeAnnotator = new DefaultForTypeAnnotator(this); - defaultForTypeAnnotator.addAtmClass(AnnotatedNoType.class, NONNULL); - defaultForTypeAnnotator.addAtmClass(AnnotatedPrimitiveType.class, NONNULL); - return defaultForTypeAnnotator; - } - - @Override - protected void addAnnotationsFromDefaultForType( - @Nullable Element element, AnnotatedTypeMirror type) { - if (element != null - && element.getKind() == ElementKind.LOCAL_VARIABLE - && type.getKind().isPrimitive()) { - // Always apply the DefaultQualifierForUse for primitives. - super.addAnnotationsFromDefaultForType(null, type); - } else { - super.addAnnotationsFromDefaultForType(element, type); + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + Nullable.class, MonotonicNonNull.class, NonNull.class, PolyNull.class)); } - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because the default tree annotators are incorrect - // for the Nullness Checker. - List annotators = new ArrayList<>(3); - // annotators.add(new DebugListTreeAnnotator(new Tree.Kind[] - // {Tree.Kind.CONDITIONAL_EXPRESSION})); - annotators.add(new InitializationFieldAccessTreeAnnotator(this)); - annotators.add(new NullnessPropagationTreeAnnotator(this)); - annotators.add(new LiteralTreeAnnotator(this)); - return new ListTreeAnnotator(annotators); - } - - /** Adds nullness-specific propagation rules */ - protected class NullnessPropagationTreeAnnotator extends PropagationTreeAnnotator { /** - * Creates a NullnessPropagationTreeAnnotator. + * For types of left-hand side of an assignment, this method replaces {@link PolyNull} with + * {@link Nullable} (or with {@link NonNull} if the org.checkerframework.dataflow analysis has + * determined that this is allowed soundly. For example: * - * @param atypeFactory this factory + *
           @PolyNull String foo(@PolyNull String param) {
          +     *    if (param == null) {
          +     *        //  @PolyNull is really @Nullable, so change
          +     *        // the type of param to @Nullable.
          +     *        param = null;
          +     *    }
          +     *    return param;
          +     * }
          +     * 
          + * + * @param lhsType type to replace whose polymorphic qualifier will be replaced + * @param context tree used to get dataflow value */ - public NullnessPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + protected void replacePolyQualifier(AnnotatedTypeMirror lhsType, Tree context) { + if (lhsType.hasAnnotation(PolyNull.class)) { + NullnessNoInitValue inferred = getInferredValueFor(context); + if (inferred != null) { + if (inferred.isPolyNullNonNull) { + lhsType.replaceAnnotation(NONNULL); + } else if (inferred.isPolyNullNull) { + lhsType.replaceAnnotation(NULLABLE); + } + } + } } @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - if (type.getKind().isPrimitive()) { - AnnotationMirror NONNULL = ((NullnessNoInitAnnotatedTypeFactory) atypeFactory).NONNULL; - // If a @Nullable expression is cast to a primitive, then an unboxing.of.nullable - // error is issued. Treat the cast as if it were annotated as @NonNull to avoid an - // "type.invalid.annotations.on.use" error. - type.addMissingAnnotation(NONNULL); - } - return super.visitTypeCast(tree, type); + protected NullnessNoInitAnalysis createFlowAnalysis() { + return new NullnessNoInitAnalysis(checker, this); } @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - Element elt = TreeUtils.elementFromUse(tree); - assert elt != null; - - // Make primitive variable @NonNull in case the Initialization Checker - // considers it uninitialized. - if (TypesUtils.isPrimitive(type.getUnderlyingType())) { - type.replaceAnnotation(NONNULL); - } - - return null; + public NullnessNoInitTransfer createFlowTransferFunction( + CFAbstractAnalysis + analysis) { + return new NullnessNoInitTransfer((NullnessNoInitAnalysis) analysis); } + /** + * Returns an AnnotatedTypeFormatter that does not print the qualifiers on null literals. + * + * @return an AnnotatedTypeFormatter that does not print the qualifiers on null literals + */ @Override - public Void visitVariable(VariableTree tree, AnnotatedTypeMirror type) { - Element elt = TreeUtils.elementFromDeclaration(tree); - if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { - // case 9. exception parameter - type.addMissingAnnotation(NONNULL); - } - return null; + protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { + boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); + return new NullnessNoInitAnnotatedTypeFormatter( + printVerboseGenerics, + // -AprintVerboseGenerics implies -AprintAllQualifiers + printVerboseGenerics || checker.hasOption("printAllQualifiers")); } @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + protected ParameterizedExecutableType methodFromUse( + MethodInvocationTree tree, boolean inferTypeArgs) { + ParameterizedExecutableType mType = super.methodFromUse(tree, inferTypeArgs); + AnnotatedExecutableType method = mType.executableType; + + // Special cases for method invocations with specific arguments. + systemGetPropertyHandler.handle(tree, method); + collectionToArrayHeuristics.handle(tree, method); + // `MyClass.class.getCanonicalName()` is non-null. + if (TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv)) { + ExpressionTree receiver = ((MemberSelectTree) tree.getMethodSelect()).getExpression(); + if (TreeUtils.isClassLiteral(receiver)) { + AnnotatedTypeMirror type = method.getReturnType(); + type.replaceAnnotation(NONNULL); + } + } - Element elt = TreeUtils.elementFromUse(tree); - assert elt != null; + return mType; + } - if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { - // TODO: It's surprising that we have to do this in both visitVariable and - // visitIdentifier. This should already be handled by applying the defaults anyway. - // case 9. exception parameter - type.replaceAnnotation(NONNULL); - } + @Override + public void adaptGetClassReturnTypeToReceiver( + AnnotatedExecutableType getClassType, + AnnotatedTypeMirror receiverType, + ExpressionTree tree) { - // Make primitive variable @NonNull in case the Initialization Checker - // considers it uninitialized. - if (TypesUtils.isPrimitive(type.getUnderlyingType())) { - type.replaceAnnotation(NONNULL); - } + super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); + + // Make the captured wildcard always @NonNull, regardless of the declared type. - return null; + AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); + List typeArgs = returnAdt.getTypeArguments(); + AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); + classWildcardArg.getUpperBound().replaceAnnotation(NONNULL); } - // The result of a binary operation is always non-null. @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(NONNULL); - return null; + public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { + AnnotatedTypeMirror result = super.getMethodReturnType(m, r); + replacePolyQualifier(result, r); + return result; } - // The result of a compound operation is always non-null. @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - super.visitCompoundAssignment(tree, type); - type.replaceAnnotation(NONNULL); - return null; + public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { + InitializationFieldAccessAnnotatedTypeFactory initFactory = + getChecker() + .getTypeFactoryOfSubcheckerOrNull( + InitializationFieldAccessSubchecker.class); + if (initFactory == null) { + // init checker is deactivated. + return super.isNotFullyInitializedReceiver(methodDeclTree); + } + return initFactory.isNotFullyInitializedReceiver(methodDeclTree); } - // The result of a unary operation is always non-null. @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(NONNULL); - return null; + public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { + InitializationFieldAccessAnnotatedTypeFactory initFactory = + getChecker() + .getTypeFactoryOfSubcheckerOrNull( + InitializationFieldAccessSubchecker.class); + if (initFactory == null) { + // init checker is deactivated. + return super.getAnnotatedTypeBefore(expr, tree); + } + if (expr instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) expr; + JavaExpression receiver = fa.getReceiver(); + TypeMirror declaringClass = fa.getField().getEnclosingElement().asType(); + AnnotatedTypeMirror receiverType; + + if (receiver instanceof LocalVariable) { + Element receiverElem = ((LocalVariable) receiver).getElement(); + receiverType = initFactory.getAnnotatedType(receiverElem); + } else if (receiver instanceof ThisReference) { + receiverType = initFactory.getSelfType(tree); + } else { + return super.getAnnotatedTypeBefore(expr, tree); + } + + if (initFactory.isInitializedForFrame(receiverType, declaringClass)) { + AnnotatedTypeMirror declared = getAnnotatedType(fa.getField()); + AnnotatedTypeMirror refined = super.getAnnotatedTypeBefore(expr, tree); + AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(fa.getType(), this, false); + // If the expression is initialized, then by definition, it has at least its + // declared annotation. + // Assuming the correctness of the Nullness Checker's type refinement, + // it also has its refined annotation. + // We thus use the GLB of those two annotations. + res.addAnnotations( + qualHierarchy.greatestLowerBoundsShallow( + declared.getAnnotations(), + declared.getUnderlyingType(), + refined.getAnnotations(), + refined.getUnderlyingType())); + return res; + } + } + + // Is there anything better we could do? + // Ideally, we would turn the expression string into a Tree or Element + // instead of a JavaExpression, so we could use + // atypeFactory.getAnnotatedType on the whole expression, + // but that doesn't seem possible. + return super.getAnnotatedTypeBefore(expr, tree); } - // The result of newly allocated structures is always non-null, - // explicit nullable annotations are left intact for the visitor to inspect. @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - // The constructor return type should already be NONNULL, so in most cases this will do - // nothing. - type.addMissingAnnotation(NONNULL); - return null; + protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { + DefaultForTypeAnnotator defaultForTypeAnnotator = new DefaultForTypeAnnotator(this); + defaultForTypeAnnotator.addAtmClass(AnnotatedNoType.class, NONNULL); + defaultForTypeAnnotator.addAtmClass(AnnotatedPrimitiveType.class, NONNULL); + return defaultForTypeAnnotator; } - // The result of newly allocated structures is always non-null, - // explicit nullable annotations are left intact for the visitor to inspect. @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - super.visitNewArray(tree, type); - type.addMissingAnnotation(NONNULL); - return null; + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + if (element != null + && element.getKind() == ElementKind.LOCAL_VARIABLE + && type.getKind().isPrimitive()) { + // Always apply the DefaultQualifierForUse for primitives. + super.addAnnotationsFromDefaultForType(null, type); + } else { + super.addAnnotationsFromDefaultForType(element, type); + } } @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv)) { - List args = tree.getArguments(); - ExpressionTree lengthArg = args.get(1); - if (TreeUtils.isArrayLengthAccess(lengthArg)) { - // TODO: This syntactic test may not be not correct if the array expression has - // a side effect that affects the array length. This code could require that - // the expression has no method calls, assignments, etc. - ExpressionTree arrayArg = args.get(0); - if (TreeUtils.sameTree(arrayArg, ((MemberSelectTree) lengthArg).getExpression())) { - AnnotatedArrayType arrayArgType = (AnnotatedArrayType) getAnnotatedType(arrayArg); - AnnotatedTypeMirror arrayArgComponentType = arrayArgType.getComponentType(); - // Maybe this call is only necessary if argNullness is @NonNull. - ((AnnotatedArrayType) type) - .getComponentType() - .replaceAnnotations(arrayArgComponentType.getAnnotations()); - } + protected TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because the default tree annotators are incorrect + // for the Nullness Checker. + List annotators = new ArrayList<>(3); + // annotators.add(new DebugListTreeAnnotator(new Tree.Kind[] + // {Tree.Kind.CONDITIONAL_EXPRESSION})); + annotators.add(new InitializationFieldAccessTreeAnnotator(this)); + annotators.add(new NullnessPropagationTreeAnnotator(this)); + annotators.add(new LiteralTreeAnnotator(this)); + return new ListTreeAnnotator(annotators); + } + + /** Adds nullness-specific propagation rules */ + protected class NullnessPropagationTreeAnnotator extends PropagationTreeAnnotator { + + /** + * Creates a NullnessPropagationTreeAnnotator. + * + * @param atypeFactory this factory + */ + public NullnessPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + if (type.getKind().isPrimitive()) { + AnnotationMirror NONNULL = + ((NullnessNoInitAnnotatedTypeFactory) atypeFactory).NONNULL; + // If a @Nullable expression is cast to a primitive, then an unboxing.of.nullable + // error is issued. Treat the cast as if it were annotated as @NonNull to avoid an + // "type.invalid.annotations.on.use" error. + type.addMissingAnnotation(NONNULL); + } + return super.visitTypeCast(tree, type); + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + Element elt = TreeUtils.elementFromUse(tree); + assert elt != null; + + // Make primitive variable @NonNull in case the Initialization Checker + // considers it uninitialized. + if (TypesUtils.isPrimitive(type.getUnderlyingType())) { + type.replaceAnnotation(NONNULL); + } + + return null; + } + + @Override + public Void visitVariable(VariableTree tree, AnnotatedTypeMirror type) { + Element elt = TreeUtils.elementFromDeclaration(tree); + if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { + // case 9. exception parameter + type.addMissingAnnotation(NONNULL); + } + return null; + } + + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + + Element elt = TreeUtils.elementFromUse(tree); + assert elt != null; + + if (elt.getKind() == ElementKind.EXCEPTION_PARAMETER) { + // TODO: It's surprising that we have to do this in both visitVariable and + // visitIdentifier. This should already be handled by applying the defaults anyway. + // case 9. exception parameter + type.replaceAnnotation(NONNULL); + } + + // Make primitive variable @NonNull in case the Initialization Checker + // considers it uninitialized. + if (TypesUtils.isPrimitive(type.getUnderlyingType())) { + type.replaceAnnotation(NONNULL); + } + + return null; + } + + // The result of a binary operation is always non-null. + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(NONNULL); + return null; + } + + // The result of a compound operation is always non-null. + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + super.visitCompoundAssignment(tree, type); + type.replaceAnnotation(NONNULL); + return null; + } + + // The result of a unary operation is always non-null. + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(NONNULL); + return null; + } + + // The result of newly allocated structures is always non-null, + // explicit nullable annotations are left intact for the visitor to inspect. + @Override + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + // The constructor return type should already be NONNULL, so in most cases this will do + // nothing. + type.addMissingAnnotation(NONNULL); + return null; + } + + // The result of newly allocated structures is always non-null, + // explicit nullable annotations are left intact for the visitor to inspect. + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + super.visitNewArray(tree, type); + type.addMissingAnnotation(NONNULL); + return null; + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv)) { + List args = tree.getArguments(); + ExpressionTree lengthArg = args.get(1); + if (TreeUtils.isArrayLengthAccess(lengthArg)) { + // TODO: This syntactic test may not be not correct if the array expression has + // a side effect that affects the array length. This code could require that + // the expression has no method calls, assignments, etc. + ExpressionTree arrayArg = args.get(0); + if (TreeUtils.sameTree( + arrayArg, ((MemberSelectTree) lengthArg).getExpression())) { + AnnotatedArrayType arrayArgType = + (AnnotatedArrayType) getAnnotatedType(arrayArg); + AnnotatedTypeMirror arrayArgComponentType = arrayArgType.getComponentType(); + // Maybe this call is only necessary if argNullness is @NonNull. + ((AnnotatedArrayType) type) + .getComponentType() + .replaceAnnotations(arrayArgComponentType.getAnnotations()); + } + } + } + return super.visitMethodInvocation(tree, type); } - } - return super.visitMethodInvocation(tree, type); } - } - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(super.createTypeAnnotator(), new NullnessTypeAnnotator(this)); - } + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(super.createTypeAnnotator(), new NullnessTypeAnnotator(this)); + } + + /** + * This type annotator ensures that constructor return types are NONNULL, unless there is an + * explicit different annotation. + */ + protected class NullnessTypeAnnotator extends TypeAnnotator { + + /** + * Creates a new NullnessTypeAnnotator. + * + * @param atypeFactory this factory + */ + public NullnessTypeAnnotator(NullnessNoInitAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - /** - * This type annotator ensures that constructor return types are NONNULL, unless there is an - * explicit different annotation. - */ - protected class NullnessTypeAnnotator extends TypeAnnotator { + @Override + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + Void result = super.visitExecutable(t, p); + Element elem = t.getElement(); + if (elem.getKind() == ElementKind.CONSTRUCTOR) { + AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); + returnType.addMissingAnnotation(NONNULL); + } + return result; + } + } /** - * Creates a new NullnessTypeAnnotator. + * Returns the list of annotations of the non-null type system. * - * @param atypeFactory this factory + * @return the list of annotations of the non-null type system */ - public NullnessTypeAnnotator(NullnessNoInitAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + public Set> getNullnessAnnotations() { + return nullnessAnnos; } @Override - public Void visitExecutable(AnnotatedExecutableType t, Void p) { - Void result = super.visitExecutable(t, p); - Element elem = t.getElement(); - if (elem.getKind() == ElementKind.CONSTRUCTOR) { - AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType(); - returnType.addMissingAnnotation(NONNULL); - } - return result; + protected QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this); } - } - - /** - * Returns the list of annotations of the non-null type system. - * - * @return the list of annotations of the non-null type system - */ - public Set> getNullnessAnnotations() { - return nullnessAnnos; - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this); - } - - /** - * Returns true if some annotation on the given type, or in the given list, is a nullness - * annotation such as @NonNull, @Nullable, @MonotonicNonNull, etc. - * - *

          This method ignores aliases of nullness annotations that are declaration annotations, - * because they may apply to inner types. - * - * @param annoTrees a list of annotations that the Java parser attached to the variable/method - * declaration; null if this type is not from such a location. This is a list of extra - * annotations to check, in addition to those on the type. - * @param typeTree the type whose annotations to test - * @return true if some annotation is a nullness annotation - */ - protected boolean containsNullnessAnnotation( - @Nullable List annoTrees, Tree typeTree) { - List annos = - TreeUtils.getExplicitAnnotationTrees(annoTrees, typeTree); - return containsNullnessAnnotation(annos); - } - - /** - * Returns true if some annotation in the given list is a nullness annotation such - * as @NonNull, @Nullable, @MonotonicNonNull, etc. - * - *

          This method ignores aliases of nullness annotations that are declaration annotations, - * because they may apply to inner types. - * - *

          Clients that are processing a field or variable definition, or a method return type, should - * call {@link #containsNullnessAnnotation(List, Tree)} instead. - * - * @param annoTrees a list of annotations to check - * @return true if some annotation is a nullness annotation - * @see #containsNullnessAnnotation(List, Tree) - */ - protected boolean containsNullnessAnnotation(List annoTrees) { - for (AnnotationTree annoTree : annoTrees) { - AnnotationMirror am = TreeUtils.annotationFromAnnotationTree(annoTree); - if (isNullnessAnnotation(am) && AnnotationUtils.isTypeUseAnnotation(am)) { - return true; - } + + /** + * Returns true if some annotation on the given type, or in the given list, is a nullness + * annotation such as @NonNull, @Nullable, @MonotonicNonNull, etc. + * + *

          This method ignores aliases of nullness annotations that are declaration annotations, + * because they may apply to inner types. + * + * @param annoTrees a list of annotations that the Java parser attached to the variable/method + * declaration; null if this type is not from such a location. This is a list of extra + * annotations to check, in addition to those on the type. + * @param typeTree the type whose annotations to test + * @return true if some annotation is a nullness annotation + */ + protected boolean containsNullnessAnnotation( + @Nullable List annoTrees, Tree typeTree) { + List annos = + TreeUtils.getExplicitAnnotationTrees(annoTrees, typeTree); + return containsNullnessAnnotation(annos); } - return false; - } - - /** - * Returns true if the given annotation is a nullness annotation such as {@code @NonNull}, - * {@code @Nullable}, {@code @MonotonicNonNull}, {@code @PolyNull}, or an alias thereof. - * - * @param am an annotation - * @return true if the given annotation is a nullness annotation - */ - protected boolean isNullnessAnnotation(AnnotationMirror am) { - return isNonNullOrAlias(am) - || isNullableOrAlias(am) - || AnnotationUtils.areSameByName(am, MONOTONIC_NONNULL) - || isPolyNullOrAlias(am); - } - - /** - * Returns true if the given annotation is {@code @NonNull} or an alias for it. - * - * @param am an annotation - * @return true if the given annotation is {@code @NonNull} or an alias for it - */ - protected boolean isNonNullOrAlias(AnnotationMirror am) { - AnnotationMirror canonical = canonicalAnnotation(am); - if (canonical != null) { - am = canonical; + + /** + * Returns true if some annotation in the given list is a nullness annotation such + * as @NonNull, @Nullable, @MonotonicNonNull, etc. + * + *

          This method ignores aliases of nullness annotations that are declaration annotations, + * because they may apply to inner types. + * + *

          Clients that are processing a field or variable definition, or a method return type, + * should call {@link #containsNullnessAnnotation(List, Tree)} instead. + * + * @param annoTrees a list of annotations to check + * @return true if some annotation is a nullness annotation + * @see #containsNullnessAnnotation(List, Tree) + */ + protected boolean containsNullnessAnnotation(List annoTrees) { + for (AnnotationTree annoTree : annoTrees) { + AnnotationMirror am = TreeUtils.annotationFromAnnotationTree(annoTree); + if (isNullnessAnnotation(am) && AnnotationUtils.isTypeUseAnnotation(am)) { + return true; + } + } + return false; } - return AnnotationUtils.areSameByName(am, NONNULL); - } - - /** - * Returns true if the given annotation is {@code @Nullable} or an alias for it. - * - * @param am an annotation - * @return true if the given annotation is {@code @Nullable} or an alias for it - */ - protected boolean isNullableOrAlias(AnnotationMirror am) { - AnnotationMirror canonical = canonicalAnnotation(am); - if (canonical != null) { - am = canonical; + + /** + * Returns true if the given annotation is a nullness annotation such as {@code @NonNull}, + * {@code @Nullable}, {@code @MonotonicNonNull}, {@code @PolyNull}, or an alias thereof. + * + * @param am an annotation + * @return true if the given annotation is a nullness annotation + */ + protected boolean isNullnessAnnotation(AnnotationMirror am) { + return isNonNullOrAlias(am) + || isNullableOrAlias(am) + || AnnotationUtils.areSameByName(am, MONOTONIC_NONNULL) + || isPolyNullOrAlias(am); } - return AnnotationUtils.areSameByName(am, NULLABLE); - } - - /** - * Returns true if the given annotation is {@code @PolyNull} or an alias for it. - * - * @param am an annotation - * @return true if the given annotation is {@code @PolyNull} or an alias for it - */ - protected boolean isPolyNullOrAlias(AnnotationMirror am) { - AnnotationMirror canonical = canonicalAnnotation(am); - if (canonical != null) { - am = canonical; + + /** + * Returns true if the given annotation is {@code @NonNull} or an alias for it. + * + * @param am an annotation + * @return true if the given annotation is {@code @NonNull} or an alias for it + */ + protected boolean isNonNullOrAlias(AnnotationMirror am) { + AnnotationMirror canonical = canonicalAnnotation(am); + if (canonical != null) { + am = canonical; + } + return AnnotationUtils.areSameByName(am, NONNULL); } - return AnnotationUtils.areSameByName(am, POLYNULL); - } - - // If a reference field has no initializer, then its default value is null. Treat that as - // @MonotonicNonNull rather than as @Nullable. - @Override - public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { - AnnotatedTypeMirror result = super.getDefaultValueAnnotatedType(typeMirror); - if (getAnnotationByClass(result.getAnnotations(), Nullable.class) != null) { - result.replaceAnnotation(MONOTONIC_NONNULL); + + /** + * Returns true if the given annotation is {@code @Nullable} or an alias for it. + * + * @param am an annotation + * @return true if the given annotation is {@code @Nullable} or an alias for it + */ + protected boolean isNullableOrAlias(AnnotationMirror am) { + AnnotationMirror canonical = canonicalAnnotation(am); + if (canonical != null) { + am = canonical; + } + return AnnotationUtils.areSameByName(am, NULLABLE); } - return result; - } - - /** A non-null reference to an object stays non-null under mutation. */ - @Override - public boolean isImmutable(TypeMirror type) { - return true; - } - - /* NO-AFU - // If - // 1. rhs is @Nullable - // 2. lhs is a field of this - // 3. in a constructor, initializer block, or field initializer - // then change rhs to @MonotonicNonNull. - @Override - public void wpiAdjustForUpdateField( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { - // Synthetic variable names contain "#". Ignore them. - if (!rhsATM.hasAnnotation(Nullable.class) || fieldName.contains("#")) { - return; + + /** + * Returns true if the given annotation is {@code @PolyNull} or an alias for it. + * + * @param am an annotation + * @return true if the given annotation is {@code @PolyNull} or an alias for it + */ + protected boolean isPolyNullOrAlias(AnnotationMirror am) { + AnnotationMirror canonical = canonicalAnnotation(am); + if (canonical != null) { + am = canonical; + } + return AnnotationUtils.areSameByName(am, POLYNULL); } - TreePath lhsPath = getPath(lhsTree); - TypeElement enclosingClassOfLhs = - TreeUtils.elementFromDeclaration(TreePathUtil.enclosingClass(lhsPath)); - ClassSymbol enclosingClassOfField = ((VarSymbol) element).enclClass(); - if (enclosingClassOfLhs.equals(enclosingClassOfField) && TreePathUtil.inConstructor(lhsPath)) { - rhsATM.replaceAnnotation(MONOTONIC_NONNULL); + + // If a reference field has no initializer, then its default value is null. Treat that as + // @MonotonicNonNull rather than as @Nullable. + @Override + public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { + AnnotatedTypeMirror result = super.getDefaultValueAnnotatedType(typeMirror); + if (getAnnotationByClass(result.getAnnotations(), Nullable.class) != null) { + result.replaceAnnotation(MONOTONIC_NONNULL); + } + return result; } - } - - // If - // 1. rhs is @MonotonicNonNull - // then change rhs to @Nullable - @Override - public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) { - if (rhsATM.hasAnnotation(MonotonicNonNull.class)) { - rhsATM.replaceAnnotation(NULLABLE); + + /** A non-null reference to an object stays non-null under mutation. */ + @Override + public boolean isImmutable(TypeMirror type) { + return true; + } + + /* NO-AFU + // If + // 1. rhs is @Nullable + // 2. lhs is a field of this + // 3. in a constructor, initializer block, or field initializer + // then change rhs to @MonotonicNonNull. + @Override + public void wpiAdjustForUpdateField( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { + // Synthetic variable names contain "#". Ignore them. + if (!rhsATM.hasAnnotation(Nullable.class) || fieldName.contains("#")) { + return; + } + TreePath lhsPath = getPath(lhsTree); + TypeElement enclosingClassOfLhs = + TreeUtils.elementFromDeclaration(TreePathUtil.enclosingClass(lhsPath)); + ClassSymbol enclosingClassOfField = ((VarSymbol) element).enclClass(); + if (enclosingClassOfLhs.equals(enclosingClassOfField) && TreePathUtil.inConstructor(lhsPath)) { + rhsATM.replaceAnnotation(MONOTONIC_NONNULL); + } } - } - - @Override - public boolean wpiShouldInferTypesForReceivers() { - // All receivers must be non-null, or the dereference involved in - // the method call would fail (and cause an NPE). So, WPI should not - // infer non-null or nullable annotations on method receiver parameters. - return false; - } - - // This implementation overrides the superclass implementation to: - // * check for @MonotonicNonNull - // * output @RequiresNonNull rather than @RequiresQualifier. - @Override - protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( - String expression, - AnnotationMirror qualifier, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - // TODO: This does not handle the possibility that the user set a different default - // annotation. - if (!(declaredType.hasAnnotation(NULLABLE) - || declaredType.hasAnnotation(POLYNULL) - || declaredType.hasAnnotation(MONOTONIC_NONNULL))) { - return null; + + // If + // 1. rhs is @MonotonicNonNull + // then change rhs to @Nullable + @Override + public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) { + if (rhsATM.hasAnnotation(MonotonicNonNull.class)) { + rhsATM.replaceAnnotation(NULLABLE); + } } - if (preOrPost == BeforeOrAfter.AFTER - && declaredType.hasAnnotation(MONOTONIC_NONNULL) - && preconds.contains(requiresNonNullAnno(expression))) { - // The postcondition is implied by the precondition and the field being - // @MonotonicNonNull. - return null; + @Override + public boolean wpiShouldInferTypesForReceivers() { + // All receivers must be non-null, or the dereference involved in + // the method call would fail (and cause an NPE). So, WPI should not + // infer non-null or nullable annotations on method receiver parameters. + return false; } - if (AnnotationUtils.areSameByName( - qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { - if (preOrPost == BeforeOrAfter.BEFORE) { - return requiresNonNullAnno(expression); - } else { - return ensuresNonNullAnno(expression); + // This implementation overrides the superclass implementation to: + // * check for @MonotonicNonNull + // * output @RequiresNonNull rather than @RequiresQualifier. + @Override + protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( + String expression, + AnnotationMirror qualifier, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + // TODO: This does not handle the possibility that the user set a different default + // annotation. + if (!(declaredType.hasAnnotation(NULLABLE) + || declaredType.hasAnnotation(POLYNULL) + || declaredType.hasAnnotation(MONOTONIC_NONNULL))) { + return null; } if (preOrPost == BeforeOrAfter.AFTER - && declaredType.hasAnnotation(MONOTONIC_NONNULL) - && preconds.contains(requiresNonNullAnno(expression))) { - // The postcondition is implied by the precondition and the field being - // @MonotonicNonNull. - return null; + && declaredType.hasAnnotation(MONOTONIC_NONNULL) + && preconds.contains(requiresNonNullAnno(expression))) { + // The postcondition is implied by the precondition and the field being + // @MonotonicNonNull. + return null; } if (AnnotationUtils.areSameByName( - qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { - if (preOrPost == BeforeOrAfter.BEFORE) { - return requiresNonNullAnno(expression); - } else { - return ensuresNonNullAnno(expression); - } - } - return super.createRequiresOrEnsuresQualifier( - expression, qualifier, declaredType, preOrPost, preconds); - } - */ - - /* NO-AFU - * Returns a {@code RequiresNonNull("...")} annotation for the given expression. - * - * @param expression an expression - * @return a {@code RequiresNonNull("...")} annotation for the given expression - */ - /* NO-AFU - private AnnotationMirror requiresNonNullAnno(String expression) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, RequiresNonNull.class); - builder.setValue("value", new String[] {expression}); - AnnotationMirror am = builder.build(); - return am; - } - */ - - /* NO-AFU - * Returns a {@code EnsuresNonNull("...")} annotation for the given expression. - * - * @param expression an expression - * @return a {@code EnsuresNonNull("...")} annotation for the given expression - */ - /* NO-AFU - private AnnotationMirror ensuresNonNullAnno(String expression) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresNonNull.class); - builder.setValue("value", new String[] {expression}); - AnnotationMirror am = builder.build(); - return am; - } - */ - - /** - * Returns true if {@code node} is an invocation of Map.get. - * - * @param node a CFG node - * @return true if {@code node} is an invocation of Map.get - */ - public boolean isMapGet(Node node) { - return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); - } + qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { + if (preOrPost == BeforeOrAfter.BEFORE) { + return requiresNonNullAnno(expression); + } else { + return ensuresNonNullAnno(expression); + } + + if (preOrPost == BeforeOrAfter.AFTER + && declaredType.hasAnnotation(MONOTONIC_NONNULL) + && preconds.contains(requiresNonNullAnno(expression))) { + // The postcondition is implied by the precondition and the field being + // @MonotonicNonNull. + return null; + } + + if (AnnotationUtils.areSameByName( + qualifier, "org.checkerframework.checker.nullness.qual.NonNull")) { + if (preOrPost == BeforeOrAfter.BEFORE) { + return requiresNonNullAnno(expression); + } else { + return ensuresNonNullAnno(expression); + } + } + return super.createRequiresOrEnsuresQualifier( + expression, qualifier, declaredType, preOrPost, preconds); + } + */ + + /* NO-AFU + * Returns a {@code RequiresNonNull("...")} annotation for the given expression. + * + * @param expression an expression + * @return a {@code RequiresNonNull("...")} annotation for the given expression + */ + /* NO-AFU + private AnnotationMirror requiresNonNullAnno(String expression) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, RequiresNonNull.class); + builder.setValue("value", new String[] {expression}); + AnnotationMirror am = builder.build(); + return am; + } + */ + + /* NO-AFU + * Returns a {@code EnsuresNonNull("...")} annotation for the given expression. + * + * @param expression an expression + * @return a {@code EnsuresNonNull("...")} annotation for the given expression + */ + /* NO-AFU + private AnnotationMirror ensuresNonNullAnno(String expression) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, EnsuresNonNull.class); + builder.setValue("value", new String[] {expression}); + AnnotationMirror am = builder.build(); + return am; + } + */ + + /** + * Returns true if {@code node} is an invocation of Map.get. + * + * @param node a CFG node + * @return true if {@code node} is an invocation of Map.get + */ + public boolean isMapGet(Node node) { + return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java index b68697f8dd5..ee85f8ada9e 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFormatter.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.nullness; -import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; @@ -8,54 +7,58 @@ import org.checkerframework.framework.util.AnnotationFormatter; import org.checkerframework.framework.util.DefaultAnnotationFormatter; +import java.util.Set; + /** A DefaultAnnotatedTypeFormatter that prints null literals without their annotations. */ public class NullnessNoInitAnnotatedTypeFormatter extends DefaultAnnotatedTypeFormatter { - /** - * Create a new NullnessNoInitAnnotatedTypeFormatter - * - * @param printVerboseGenerics whether to print type variables in a less ambiguous manner using - * {@code []} to delimit bounds - * @param printInvisibleQualifiers whether or not to print invisible qualifiers - */ - public NullnessNoInitAnnotatedTypeFormatter( - boolean printVerboseGenerics, boolean printInvisibleQualifiers) { - super( - new NullnessFormattingVisitor( - new DefaultAnnotationFormatter(), printVerboseGenerics, printInvisibleQualifiers)); - } - - /** The visitor used by the {@code NullnessNoInitAnnotatedTypeFormatter}. */ - protected static class NullnessFormattingVisitor extends FormattingVisitor { - /** - * Create a new NullnessFormattingVisitor. + * Create a new NullnessNoInitAnnotatedTypeFormatter * - * @param annoFormatter the formatter to use * @param printVerboseGenerics whether to print type variables in a less ambiguous manner using * {@code []} to delimit bounds - * @param defaultInvisiblesSetting whether or not to print invisible qualifiers + * @param printInvisibleQualifiers whether or not to print invisible qualifiers */ - public NullnessFormattingVisitor( - AnnotationFormatter annoFormatter, - boolean printVerboseGenerics, - boolean defaultInvisiblesSetting) { - super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); + public NullnessNoInitAnnotatedTypeFormatter( + boolean printVerboseGenerics, boolean printInvisibleQualifiers) { + super( + new NullnessFormattingVisitor( + new DefaultAnnotationFormatter(), + printVerboseGenerics, + printInvisibleQualifiers)); } - @Override - public String visitNull(AnnotatedNullType type, Set visiting) { - if (type.getAnnotation(Nullable.class) != null) { - // The null type will be understood as nullable by readers (I hope), therefore omit - // the annotations if they are @Nullable. - // Note: The visitTypeVariable will still print lower bounds with Null kind as - // "Void" - if (!currentPrintInvisibleSetting) { - return "null (NullType)"; + /** The visitor used by the {@code NullnessNoInitAnnotatedTypeFormatter}. */ + protected static class NullnessFormattingVisitor extends FormattingVisitor { + + /** + * Create a new NullnessFormattingVisitor. + * + * @param annoFormatter the formatter to use + * @param printVerboseGenerics whether to print type variables in a less ambiguous manner + * using {@code []} to delimit bounds + * @param defaultInvisiblesSetting whether or not to print invisible qualifiers + */ + public NullnessFormattingVisitor( + AnnotationFormatter annoFormatter, + boolean printVerboseGenerics, + boolean defaultInvisiblesSetting) { + super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); } - } - return super.visitNull(type, visiting); + @Override + public String visitNull(AnnotatedNullType type, Set visiting) { + if (type.getAnnotation(Nullable.class) != null) { + // The null type will be understood as nullable by readers (I hope), therefore omit + // the annotations if they are @Nullable. + // Note: The visitTypeVariable will still print lower bounds with Null kind as + // "Void" + if (!currentPrintInvisibleSetting) { + return "null (NullType)"; + } + } + + return super.visitNull(type, visiting); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java index 78edc3ebcbb..78942759eaf 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitStore.java @@ -1,7 +1,5 @@ package org.checkerframework.checker.nullness; -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -13,168 +11,174 @@ import org.checkerframework.framework.qual.MonotonicQualifier; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import java.util.HashMap; +import java.util.Map; + /** * In addition to the base class behavior, tracks whether {@link PolyNull} is known to be {@link * NonNull} or {@link Nullable} (or not known to be either). */ public class NullnessNoInitStore extends CFAbstractStore { - /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ - protected boolean isPolyNullNonNull; - - /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ - protected boolean isPolyNullNull; - - /** - * Initialized fields and their values. - * - *

          This is used by {@link #newFieldValueAfterMethodCall(FieldAccess, - * GenericAnnotatedTypeFactory, NullnessNoInitValue)} as cache to avoid performance issue in - * #1438. - * - * @see - * InitializationAnnotatedTypeFactory#isInitialized(org.checkerframework.framework.type.GenericAnnotatedTypeFactory, - * org.checkerframework.framework.flow.CFAbstractValue, - * javax.lang.model.element.VariableElement) - */ - protected Map initializedFields; - - /** - * Create a NullnessStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume that - * only one thread is running at all times)? - */ - public NullnessNoInitStore( - CFAbstractAnalysis analysis, - boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - isPolyNullNonNull = false; - isPolyNullNull = false; - } - - /** - * Create a NullnessStore (copy constructor). - * - * @param s a store to copy - */ - public NullnessNoInitStore(NullnessNoInitStore s) { - super(s); - isPolyNullNonNull = s.isPolyNullNonNull; - isPolyNullNull = s.isPolyNullNull; - if (s.initializedFields != null) { - initializedFields = s.initializedFields; + /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ + protected boolean isPolyNullNonNull; + + /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ + protected boolean isPolyNullNull; + + /** + * Initialized fields and their values. + * + *

          This is used by {@link #newFieldValueAfterMethodCall(FieldAccess, + * GenericAnnotatedTypeFactory, NullnessNoInitValue)} as cache to avoid performance issue in + * #1438. + * + * @see + * InitializationAnnotatedTypeFactory#isInitialized(org.checkerframework.framework.type.GenericAnnotatedTypeFactory, + * org.checkerframework.framework.flow.CFAbstractValue, + * javax.lang.model.element.VariableElement) + */ + protected Map initializedFields; + + /** + * Create a NullnessStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics (i.e., assume + * that only one thread is running at all times)? + */ + public NullnessNoInitStore( + CFAbstractAnalysis analysis, + boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + isPolyNullNonNull = false; + isPolyNullNull = false; + } + + /** + * Create a NullnessStore (copy constructor). + * + * @param s a store to copy + */ + public NullnessNoInitStore(NullnessNoInitStore s) { + super(s); + isPolyNullNonNull = s.isPolyNullNonNull; + isPolyNullNull = s.isPolyNullNull; + if (s.initializedFields != null) { + initializedFields = s.initializedFields; + } } - } - - @Override - protected NullnessNoInitValue newFieldValueAfterMethodCall( - FieldAccess fieldAccess, - GenericAnnotatedTypeFactory atypeFactory, - NullnessNoInitValue value) { - // If the field is unassignable, it cannot change; thus we keep - // its current value. - // Unassignable fields must be handled before initialized fields - // because in the case of a field that is both unassignable and - // initialized, the initializedFields cache may contain an older, - // less refined value. - if (fieldAccess.isUnassignableByOtherCode()) { - return value; + + @Override + protected NullnessNoInitValue newFieldValueAfterMethodCall( + FieldAccess fieldAccess, + GenericAnnotatedTypeFactory + atypeFactory, + NullnessNoInitValue value) { + // If the field is unassignable, it cannot change; thus we keep + // its current value. + // Unassignable fields must be handled before initialized fields + // because in the case of a field that is both unassignable and + // initialized, the initializedFields cache may contain an older, + // less refined value. + if (fieldAccess.isUnassignableByOtherCode()) { + return value; + } + + if (initializedFields == null) { + initializedFields = new HashMap<>(4); + } + + // If the field is initialized, it can change, but cannot be uninitialized. + // We thus keep a new value based on its declared type. + if (initializedFields.containsKey(fieldAccess)) { + return initializedFields.get(fieldAccess); + } else if (InitializationAnnotatedTypeFactory.isInitialized( + atypeFactory, value, fieldAccess.getField()) + && atypeFactory + .getAnnotationWithMetaAnnotation( + fieldAccess.getField(), MonotonicQualifier.class) + .isEmpty()) { + + NullnessNoInitValue newValue = + analysis.createAbstractValue( + atypeFactory.getAnnotatedType(fieldAccess.getField()).getAnnotations(), + value.getUnderlyingType()); + initializedFields.put(fieldAccess, newValue); + return newValue; + } + + // If the field has a monotonic annotation, we use the superclass's + // handling of monotonic annotations. + return super.newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); } - if (initializedFields == null) { - initializedFields = new HashMap<>(4); + @Override + public NullnessNoInitStore leastUpperBound(NullnessNoInitStore other) { + NullnessNoInitStore lub = super.leastUpperBound(other); + lub.isPolyNullNonNull = isPolyNullNonNull && other.isPolyNullNonNull; + lub.isPolyNullNull = isPolyNullNull && other.isPolyNullNull; + return lub; } - // If the field is initialized, it can change, but cannot be uninitialized. - // We thus keep a new value based on its declared type. - if (initializedFields.containsKey(fieldAccess)) { - return initializedFields.get(fieldAccess); - } else if (InitializationAnnotatedTypeFactory.isInitialized( - atypeFactory, value, fieldAccess.getField()) - && atypeFactory - .getAnnotationWithMetaAnnotation(fieldAccess.getField(), MonotonicQualifier.class) - .isEmpty()) { - - NullnessNoInitValue newValue = - analysis.createAbstractValue( - atypeFactory.getAnnotatedType(fieldAccess.getField()).getAnnotations(), - value.getUnderlyingType()); - initializedFields.put(fieldAccess, newValue); - return newValue; + @Override + protected boolean supersetOf(CFAbstractStore o) { + if (!(o instanceof NullnessNoInitStore)) { + return false; + } + NullnessNoInitStore other = (NullnessNoInitStore) o; + if ((other.isPolyNullNonNull != isPolyNullNonNull) + || (other.isPolyNullNull != isPolyNullNull)) { + return false; + } + return super.supersetOf(other); } - // If the field has a monotonic annotation, we use the superclass's - // handling of monotonic annotations. - return super.newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); - } - - @Override - public NullnessNoInitStore leastUpperBound(NullnessNoInitStore other) { - NullnessNoInitStore lub = super.leastUpperBound(other); - lub.isPolyNullNonNull = isPolyNullNonNull && other.isPolyNullNonNull; - lub.isPolyNullNull = isPolyNullNull && other.isPolyNullNull; - return lub; - } - - @Override - protected boolean supersetOf(CFAbstractStore o) { - if (!(o instanceof NullnessNoInitStore)) { - return false; + @Override + protected String internalVisualize( + CFGVisualizer viz) { + return super.internalVisualize(viz) + + viz.getSeparator() + + viz.visualizeStoreKeyVal("isPolyNullNonNull", isPolyNullNonNull) + + viz.getSeparator() + + viz.visualizeStoreKeyVal("isPolyNullNull", isPolyNullNull); } - NullnessNoInitStore other = (NullnessNoInitStore) o; - if ((other.isPolyNullNonNull != isPolyNullNonNull) - || (other.isPolyNullNull != isPolyNullNull)) { - return false; + + /** + * Returns true if, at this point, {@link PolyNull} is known to be {@link NonNull}. + * + * @return true if, at this point, {@link PolyNull} is known to be {@link NonNull} + */ + public boolean isPolyNullNonNull() { + return isPolyNullNonNull; + } + + /** + * Set the value of whether, at this point, {@link PolyNull} is known to be {@link NonNull}. + * + * @param isPolyNullNonNull whether, at this point, {@link PolyNull} is known to be {@link + * NonNull} + */ + public void setPolyNullNonNull(boolean isPolyNullNonNull) { + this.isPolyNullNonNull = isPolyNullNonNull; + } + + /** + * Returns true if, at this point, {@link PolyNull} is known to be {@link Nullable}. + * + * @return true if, at this point, {@link PolyNull} is known to be {@link Nullable} + */ + public boolean isPolyNullNull() { + return isPolyNullNull; + } + + /** + * Set the value of whether, at this point, {@link PolyNull} is known to be {@link Nullable}. + * + * @param isPolyNullNull whether, at this point, {@link PolyNull} is known to be {@link + * Nullable} + */ + public void setPolyNullNull(boolean isPolyNullNull) { + this.isPolyNullNull = isPolyNullNull; } - return super.supersetOf(other); - } - - @Override - protected String internalVisualize( - CFGVisualizer viz) { - return super.internalVisualize(viz) - + viz.getSeparator() - + viz.visualizeStoreKeyVal("isPolyNullNonNull", isPolyNullNonNull) - + viz.getSeparator() - + viz.visualizeStoreKeyVal("isPolyNullNull", isPolyNullNull); - } - - /** - * Returns true if, at this point, {@link PolyNull} is known to be {@link NonNull}. - * - * @return true if, at this point, {@link PolyNull} is known to be {@link NonNull} - */ - public boolean isPolyNullNonNull() { - return isPolyNullNonNull; - } - - /** - * Set the value of whether, at this point, {@link PolyNull} is known to be {@link NonNull}. - * - * @param isPolyNullNonNull whether, at this point, {@link PolyNull} is known to be {@link - * NonNull} - */ - public void setPolyNullNonNull(boolean isPolyNullNonNull) { - this.isPolyNullNonNull = isPolyNullNonNull; - } - - /** - * Returns true if, at this point, {@link PolyNull} is known to be {@link Nullable}. - * - * @return true if, at this point, {@link PolyNull} is known to be {@link Nullable} - */ - public boolean isPolyNullNull() { - return isPolyNullNull; - } - - /** - * Set the value of whether, at this point, {@link PolyNull} is known to be {@link Nullable}. - * - * @param isPolyNullNull whether, at this point, {@link PolyNull} is known to be {@link Nullable} - */ - public void setPolyNullNull(boolean isPolyNullNull) { - this.isPolyNullNull = isPolyNullNull; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java index 28968b51033..0536a3ccb7f 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitSubchecker.java @@ -2,8 +2,7 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; -import java.util.NavigableSet; -import java.util.Set; + import org.checkerframework.checker.initialization.InitializationChecker; import org.checkerframework.checker.initialization.InitializationFieldAccessSubchecker; import org.checkerframework.checker.nullness.qual.NonNull; @@ -11,6 +10,9 @@ import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.qual.StubFiles; +import java.util.NavigableSet; +import java.util.Set; + /** * The subchecker of the {@link NullnessChecker} which actually checks {@link NonNull} and related * qualifiers. @@ -21,50 +23,50 @@ @StubFiles({"junit-assertions.astub"}) public class NullnessNoInitSubchecker extends BaseTypeChecker { - /** Default constructor for NonNullChecker. */ - public NullnessNoInitSubchecker() {} + /** Default constructor for NonNullChecker. */ + public NullnessNoInitSubchecker() {} - @Override - public NullnessNoInitAnnotatedTypeFactory getTypeFactory() { - return (NullnessNoInitAnnotatedTypeFactory) super.getTypeFactory(); - } + @Override + public NullnessNoInitAnnotatedTypeFactory getTypeFactory() { + return (NullnessNoInitAnnotatedTypeFactory) super.getTypeFactory(); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - if (!hasOption("assumeKeyFor")) { - checkers.add(KeyForSubchecker.class); + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + if (!hasOption("assumeKeyFor")) { + checkers.add(KeyForSubchecker.class); + } + checkers.add(InitializationFieldAccessSubchecker.class); + return checkers; } - checkers.add(InitializationFieldAccessSubchecker.class); - return checkers; - } - @Override - public NavigableSet getSuppressWarningsPrefixes() { - NavigableSet result = super.getSuppressWarningsPrefixes(); - result.add("nullness"); - return result; - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + NavigableSet result = super.getSuppressWarningsPrefixes(); + result.add("nullness"); + return result; + } - @Override - protected String getWarningMessagePrefix() { - return "nullness"; - } + @Override + protected String getWarningMessagePrefix() { + return "nullness"; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new NullnessNoInitVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new NullnessNoInitVisitor(this); + } - // The NullnessNoInitChecker should also skip defs skipped by the NullnessChecker + // The NullnessNoInitChecker should also skip defs skipped by the NullnessChecker - @Override - public boolean shouldSkipDefs(ClassTree tree) { - return super.shouldSkipDefs(tree) || parentChecker.shouldSkipDefs(tree); - } + @Override + public boolean shouldSkipDefs(ClassTree tree) { + return super.shouldSkipDefs(tree) || parentChecker.shouldSkipDefs(tree); + } - @Override - public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { - return super.shouldSkipDefs(cls, meth) || parentChecker.shouldSkipDefs(cls, meth); - } + @Override + public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { + return super.shouldSkipDefs(cls, meth) || parentChecker.shouldSkipDefs(cls, meth); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java index 34e4f3064ee..7c951135d8c 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitTransfer.java @@ -3,14 +3,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; -import java.util.List; -import java.util.Map; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; @@ -44,6 +37,16 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; + /** * Transfer function for the non-null type system. Performs the following refinements: * @@ -58,443 +61,453 @@ * */ public class NullnessNoInitTransfer - extends CFAbstractTransfer { - - /** The @{@link NonNull} annotation. */ - protected final AnnotationMirror NONNULL; - - /** The @{@link Nullable} annotation. */ - protected final AnnotationMirror NULLABLE; - - /** The @{@link PolyNull} annotation. */ - protected final AnnotationMirror POLYNULL; - - /** - * Java's Map interface. - * - *

          The qualifiers in this type don't matter -- it is not used as a fully-annotated - * AnnotatedDeclaredType, but just passed to asSuper(). - */ - protected final AnnotatedDeclaredType MAP_TYPE; - - /** The type factory for the nullness analysis that was passed to the constructor. */ - protected final NullnessNoInitAnnotatedTypeFactory nullnessTypeFactory; - - /** - * The type factory for the map key analysis, or null if the Map Key Checker should not be run. - */ - protected final @Nullable KeyForAnnotatedTypeFactory keyForTypeFactory; - - /** True if -AassumeKeyFor was provided on the command line. */ - private final boolean assumeKeyFor; - - /** - * True if conservativeArgumentNullnessAfterInvocation flag is turned off, meaning that after a - * method call or constructor invocation, arguments of the invocation (including the receiver) are - * assumed to be non-null. - */ - private final boolean nonNullAssumptionAfterInvocation; - - /** - * Create a new NullnessTransfer for the given analysis. - * - * @param analysis nullness analysis - */ - public NullnessNoInitTransfer(NullnessNoInitAnalysis analysis) { - super(analysis); - this.nullnessTypeFactory = (NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory(); - Elements elements = nullnessTypeFactory.getElementUtils(); - BaseTypeChecker checker = nullnessTypeFactory.getChecker(); - assumeKeyFor = checker.hasOption("assumeKeyFor"); - if (assumeKeyFor) { - this.keyForTypeFactory = null; - } else { - // It is error-prone to put a type factory in a field. It is OK here because - // keyForTypeFactory is used only to call methods isMapGet() and isKeyForMap(). - this.keyForTypeFactory = - nullnessTypeFactory.getTypeFactoryOfSubchecker(KeyForSubchecker.class); + extends CFAbstractTransfer< + NullnessNoInitValue, NullnessNoInitStore, NullnessNoInitTransfer> { + + /** The @{@link NonNull} annotation. */ + protected final AnnotationMirror NONNULL; + + /** The @{@link Nullable} annotation. */ + protected final AnnotationMirror NULLABLE; + + /** The @{@link PolyNull} annotation. */ + protected final AnnotationMirror POLYNULL; + + /** + * Java's Map interface. + * + *

          The qualifiers in this type don't matter -- it is not used as a fully-annotated + * AnnotatedDeclaredType, but just passed to asSuper(). + */ + protected final AnnotatedDeclaredType MAP_TYPE; + + /** The type factory for the nullness analysis that was passed to the constructor. */ + protected final NullnessNoInitAnnotatedTypeFactory nullnessTypeFactory; + + /** + * The type factory for the map key analysis, or null if the Map Key Checker should not be run. + */ + protected final @Nullable KeyForAnnotatedTypeFactory keyForTypeFactory; + + /** True if -AassumeKeyFor was provided on the command line. */ + private final boolean assumeKeyFor; + + /** + * True if conservativeArgumentNullnessAfterInvocation flag is turned off, meaning that after a + * method call or constructor invocation, arguments of the invocation (including the receiver) + * are assumed to be non-null. + */ + private final boolean nonNullAssumptionAfterInvocation; + + /** + * Create a new NullnessTransfer for the given analysis. + * + * @param analysis nullness analysis + */ + public NullnessNoInitTransfer(NullnessNoInitAnalysis analysis) { + super(analysis); + this.nullnessTypeFactory = (NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory(); + Elements elements = nullnessTypeFactory.getElementUtils(); + BaseTypeChecker checker = nullnessTypeFactory.getChecker(); + assumeKeyFor = checker.hasOption("assumeKeyFor"); + if (assumeKeyFor) { + this.keyForTypeFactory = null; + } else { + // It is error-prone to put a type factory in a field. It is OK here because + // keyForTypeFactory is used only to call methods isMapGet() and isKeyForMap(). + this.keyForTypeFactory = + nullnessTypeFactory.getTypeFactoryOfSubchecker(KeyForSubchecker.class); + } + + NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); + NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class); + POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class); + + MAP_TYPE = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + TypesUtils.typeFromClass(Map.class, analysis.getTypes(), elements), + nullnessTypeFactory, + false); + + nonNullAssumptionAfterInvocation = + !analysis.getTypeFactory() + .getChecker() + .getUltimateParentChecker() + .getBooleanOption("conservativeArgumentNullnessAfterInvocation", false); + } + + /** + * Sets a given {@link Node} to non-null in the given {@code store}. Calls to this method + * implement case 2. + * + * @param store the store to update + * @param node the node that should be non-null + */ + protected void makeNonNull(NullnessNoInitStore store, Node node) { + JavaExpression internalRepr = JavaExpression.fromNode(node); + store.insertValue(internalRepr, NONNULL); } - NONNULL = AnnotationBuilder.fromClass(elements, NonNull.class); - NULLABLE = AnnotationBuilder.fromClass(elements, Nullable.class); - POLYNULL = AnnotationBuilder.fromClass(elements, PolyNull.class); - - MAP_TYPE = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType( - TypesUtils.typeFromClass(Map.class, analysis.getTypes(), elements), - nullnessTypeFactory, - false); - - nonNullAssumptionAfterInvocation = - !analysis - .getTypeFactory() - .getChecker() - .getUltimateParentChecker() - .getBooleanOption("conservativeArgumentNullnessAfterInvocation", false); - } - - /** - * Sets a given {@link Node} to non-null in the given {@code store}. Calls to this method - * implement case 2. - * - * @param store the store to update - * @param node the node that should be non-null - */ - protected void makeNonNull(NullnessNoInitStore store, Node node) { - JavaExpression internalRepr = JavaExpression.fromNode(node); - store.insertValue(internalRepr, NONNULL); - } - - /** - * Sets a given node to non-null in the given transfer result. - * - * @param result the transfer result - * @param node the node to make non-null - */ - protected void makeNonNull( - TransferResult result, Node node) { - if (result.containsTwoStores()) { - makeNonNull(result.getThenStore(), node); - makeNonNull(result.getElseStore(), node); - } else { - makeNonNull(result.getRegularStore(), node); + /** + * Sets a given node to non-null in the given transfer result. + * + * @param result the transfer result + * @param node the node to make non-null + */ + protected void makeNonNull( + TransferResult result, Node node) { + if (result.containsTwoStores()) { + makeNonNull(result.getThenStore(), node); + makeNonNull(result.getElseStore(), node); + } else { + makeNonNull(result.getRegularStore(), node); + } } - } - - /** - * Refine the given result to @NonNull. - * - * @param result the result to refine - */ - protected void refineToNonNull(TransferResult result) { - NullnessNoInitValue oldResultValue = result.getResultValue(); - NullnessNoInitValue refinedResultValue = - analysis.createSingleAnnotationValue(NONNULL, oldResultValue.getUnderlyingType()); - NullnessNoInitValue newResultValue = refinedResultValue.mostSpecific(oldResultValue, null); - result.setResultValue(newResultValue); - } - - @Override - protected @Nullable NullnessNoInitValue finishValue( - @Nullable NullnessNoInitValue value, NullnessNoInitStore store) { - value = super.finishValue(value, store); - if (value != null) { - value.isPolyNullNonNull = store.isPolyNullNonNull(); - value.isPolyNullNull = store.isPolyNullNull(); + + /** + * Refine the given result to @NonNull. + * + * @param result the result to refine + */ + protected void refineToNonNull( + TransferResult result) { + NullnessNoInitValue oldResultValue = result.getResultValue(); + NullnessNoInitValue refinedResultValue = + analysis.createSingleAnnotationValue(NONNULL, oldResultValue.getUnderlyingType()); + NullnessNoInitValue newResultValue = refinedResultValue.mostSpecific(oldResultValue, null); + result.setResultValue(newResultValue); } - return value; - } - - @Override - protected @Nullable NullnessNoInitValue finishValue( - @Nullable NullnessNoInitValue value, - NullnessNoInitStore thenStore, - NullnessNoInitStore elseStore) { - value = super.finishValue(value, thenStore, elseStore); - if (value != null) { - value.isPolyNullNonNull = thenStore.isPolyNullNonNull() && elseStore.isPolyNullNonNull(); - value.isPolyNullNull = thenStore.isPolyNullNull() && elseStore.isPolyNullNull(); + + @Override + protected @Nullable NullnessNoInitValue finishValue( + @Nullable NullnessNoInitValue value, NullnessNoInitStore store) { + value = super.finishValue(value, store); + if (value != null) { + value.isPolyNullNonNull = store.isPolyNullNonNull(); + value.isPolyNullNull = store.isPolyNullNull(); + } + return value; } - return value; - } - - /** - * {@inheritDoc} - * - *

          Furthermore, this method refines the type to {@code NonNull} for the appropriate branch if - * an expression is compared to the {@code null} literal (listed as case 1 in the class - * description). - */ - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult res, - Node firstNode, - Node secondNode, - NullnessNoInitValue firstValue, - NullnessNoInitValue secondValue, - boolean notEqualTo) { - res = - super.strengthenAnnotationOfEqualTo( - res, firstNode, secondNode, firstValue, secondValue, notEqualTo); - if (firstNode instanceof NullLiteralNode) { - NullnessNoInitStore thenStore = res.getThenStore(); - NullnessNoInitStore elseStore = res.getElseStore(); - - List secondParts = splitAssignments(secondNode); - for (Node secondPart : secondParts) { - JavaExpression secondInternal = JavaExpression.fromNode(secondPart); - if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { - thenStore = thenStore == null ? res.getThenStore() : thenStore; - elseStore = elseStore == null ? res.getElseStore() : elseStore; - if (notEqualTo) { - thenStore.insertValue(secondInternal, NONNULL); - } else { - elseStore.insertValue(secondInternal, NONNULL); - } + + @Override + protected @Nullable NullnessNoInitValue finishValue( + @Nullable NullnessNoInitValue value, + NullnessNoInitStore thenStore, + NullnessNoInitStore elseStore) { + value = super.finishValue(value, thenStore, elseStore); + if (value != null) { + value.isPolyNullNonNull = + thenStore.isPolyNullNonNull() && elseStore.isPolyNullNonNull(); + value.isPolyNullNull = thenStore.isPolyNullNull() && elseStore.isPolyNullNull(); } - } - - AnnotationMirrorSet secondAnnos = - secondValue != null ? secondValue.getAnnotations() : new AnnotationMirrorSet(); - if (nullnessTypeFactory.containsSameByClass(secondAnnos, PolyNull.class)) { - thenStore = thenStore == null ? res.getThenStore() : thenStore; - elseStore = elseStore == null ? res.getElseStore() : elseStore; - // TODO: methodTree is null for lambdas. Handle that case. See Issue3850.java. - MethodTree methodTree = analysis.getContainingMethod(secondNode.getTree()); - ExecutableElement methodElem = - methodTree == null ? null : TreeUtils.elementFromDeclaration(methodTree); - if (notEqualTo) { - elseStore.setPolyNullNull(true); - if (methodElem != null && polyNullIsNonNull(methodElem, thenStore)) { - thenStore.setPolyNullNonNull(true); - } - } else { - thenStore.setPolyNullNull(true); - if (methodElem != null && polyNullIsNonNull(methodElem, elseStore)) { - elseStore.setPolyNullNonNull(true); - } + return value; + } + + /** + * {@inheritDoc} + * + *

          Furthermore, this method refines the type to {@code NonNull} for the appropriate branch if + * an expression is compared to the {@code null} literal (listed as case 1 in the class + * description). + */ + @Override + protected TransferResult + strengthenAnnotationOfEqualTo( + TransferResult res, + Node firstNode, + Node secondNode, + NullnessNoInitValue firstValue, + NullnessNoInitValue secondValue, + boolean notEqualTo) { + res = + super.strengthenAnnotationOfEqualTo( + res, firstNode, secondNode, firstValue, secondValue, notEqualTo); + if (firstNode instanceof NullLiteralNode) { + NullnessNoInitStore thenStore = res.getThenStore(); + NullnessNoInitStore elseStore = res.getElseStore(); + + List secondParts = splitAssignments(secondNode); + for (Node secondPart : secondParts) { + JavaExpression secondInternal = JavaExpression.fromNode(secondPart); + if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { + thenStore = thenStore == null ? res.getThenStore() : thenStore; + elseStore = elseStore == null ? res.getElseStore() : elseStore; + if (notEqualTo) { + thenStore.insertValue(secondInternal, NONNULL); + } else { + elseStore.insertValue(secondInternal, NONNULL); + } + } + } + + AnnotationMirrorSet secondAnnos = + secondValue != null ? secondValue.getAnnotations() : new AnnotationMirrorSet(); + if (nullnessTypeFactory.containsSameByClass(secondAnnos, PolyNull.class)) { + thenStore = thenStore == null ? res.getThenStore() : thenStore; + elseStore = elseStore == null ? res.getElseStore() : elseStore; + // TODO: methodTree is null for lambdas. Handle that case. See Issue3850.java. + MethodTree methodTree = analysis.getContainingMethod(secondNode.getTree()); + ExecutableElement methodElem = + methodTree == null ? null : TreeUtils.elementFromDeclaration(methodTree); + if (notEqualTo) { + elseStore.setPolyNullNull(true); + if (methodElem != null && polyNullIsNonNull(methodElem, thenStore)) { + thenStore.setPolyNullNonNull(true); + } + } else { + thenStore.setPolyNullNull(true); + if (methodElem != null && polyNullIsNonNull(methodElem, elseStore)) { + elseStore.setPolyNullNonNull(true); + } + } + } + + if (thenStore != null) { + return new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); + } } - } + return res; + } - if (thenStore != null) { - return new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); - } + /** + * Returns true if every formal parameter that is declared as @PolyNull is currently known to be + * non-null. + * + * @param method a method + * @param s a store + * @return true if every formal parameter declared as @PolyNull is non-null + */ + private boolean polyNullIsNonNull(ExecutableElement method, NullnessNoInitStore s) { + // No need to check the receiver, which is always non-null. + for (VariableElement var : method.getParameters()) { + AnnotatedTypeMirror varType = nullnessTypeFactory.fromElement(var); + + if (containsPolyNullNotAtTopLevel(varType)) { + return false; + } + + if (varType.hasAnnotation(POLYNULL)) { + NullnessNoInitValue v = s.getValue(new LocalVariable(var)); + if (!AnnotationUtils.containsSameByName(v.getAnnotations(), NONNULL)) { + return false; + } + } + } + return true; } - return res; - } - - /** - * Returns true if every formal parameter that is declared as @PolyNull is currently known to be - * non-null. - * - * @param method a method - * @param s a store - * @return true if every formal parameter declared as @PolyNull is non-null - */ - private boolean polyNullIsNonNull(ExecutableElement method, NullnessNoInitStore s) { - // No need to check the receiver, which is always non-null. - for (VariableElement var : method.getParameters()) { - AnnotatedTypeMirror varType = nullnessTypeFactory.fromElement(var); - - if (containsPolyNullNotAtTopLevel(varType)) { - return false; - } - - if (varType.hasAnnotation(POLYNULL)) { - NullnessNoInitValue v = s.getValue(new LocalVariable(var)); - if (!AnnotationUtils.containsSameByName(v.getAnnotations(), NONNULL)) { - return false; + + /** + * A scanner that returns true if there is an occurrence of @PolyNull that is not at the top + * level. + */ + // Not static so it can access field POLYNULL. + private class ContainsPolyNullNotAtTopLevelScanner + extends SimpleAnnotatedTypeScanner { + /** + * True if the top-level type has not yet been processed (by the first call to + * defaultAction). + */ + private boolean isTopLevel = true; + + /** Create a ContainsPolyNullNotAtTopLevelScanner. */ + ContainsPolyNullNotAtTopLevelScanner() { + super(Boolean::logicalOr, false); + } + + @Override + protected Boolean defaultAction(AnnotatedTypeMirror type, Void p) { + if (isTopLevel) { + isTopLevel = false; + return false; + } else { + return type.hasAnnotation(POLYNULL); + } } - } } - return true; - } - - /** - * A scanner that returns true if there is an occurrence of @PolyNull that is not at the top - * level. - */ - // Not static so it can access field POLYNULL. - private class ContainsPolyNullNotAtTopLevelScanner - extends SimpleAnnotatedTypeScanner { + /** - * True if the top-level type has not yet been processed (by the first call to defaultAction). + * Returns true if there is an occurrence of @PolyNull that is not at the top level. + * + * @param t a type + * @return true if there is an occurrence of @PolyNull that is not at the top level */ - private boolean isTopLevel = true; + private boolean containsPolyNullNotAtTopLevel(AnnotatedTypeMirror t) { + return new ContainsPolyNullNotAtTopLevelScanner().visit(t); + } - /** Create a ContainsPolyNullNotAtTopLevelScanner. */ - ContainsPolyNullNotAtTopLevelScanner() { - super(Boolean::logicalOr, false); + @Override + public TransferResult visitArrayAccess( + ArrayAccessNode n, TransferInput p) { + TransferResult result = + super.visitArrayAccess(n, p); + makeNonNull(result, n.getArray()); + return result; } @Override - protected Boolean defaultAction(AnnotatedTypeMirror type, Void p) { - if (isTopLevel) { - isTopLevel = false; - return false; - } else { - return type.hasAnnotation(POLYNULL); - } + public TransferResult visitInstanceOf( + InstanceOfNode n, TransferInput p) { + TransferResult result = + super.visitInstanceOf(n, p); + NullnessNoInitStore thenStore = result.getThenStore(); + NullnessNoInitStore elseStore = result.getElseStore(); + makeNonNull(thenStore, n.getOperand()); + return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); } - } - - /** - * Returns true if there is an occurrence of @PolyNull that is not at the top level. - * - * @param t a type - * @return true if there is an occurrence of @PolyNull that is not at the top level - */ - private boolean containsPolyNullNotAtTopLevel(AnnotatedTypeMirror t) { - return new ContainsPolyNullNotAtTopLevelScanner().visit(t); - } - - @Override - public TransferResult visitArrayAccess( - ArrayAccessNode n, TransferInput p) { - TransferResult result = super.visitArrayAccess(n, p); - makeNonNull(result, n.getArray()); - return result; - } - - @Override - public TransferResult visitInstanceOf( - InstanceOfNode n, TransferInput p) { - TransferResult result = super.visitInstanceOf(n, p); - NullnessNoInitStore thenStore = result.getThenStore(); - NullnessNoInitStore elseStore = result.getElseStore(); - makeNonNull(thenStore, n.getOperand()); - return new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - } - - @Override - public TransferResult visitMethodAccess( - MethodAccessNode n, TransferInput p) { - TransferResult result = super.visitMethodAccess(n, p); - // The receiver of an instance method access is non-null. A static method access does not - // ensure that the receiver is non-null. - if (!n.isStatic()) { - makeNonNull(result, n.getReceiver()); + + @Override + public TransferResult visitMethodAccess( + MethodAccessNode n, TransferInput p) { + TransferResult result = + super.visitMethodAccess(n, p); + // The receiver of an instance method access is non-null. A static method access does not + // ensure that the receiver is non-null. + if (!n.isStatic()) { + makeNonNull(result, n.getReceiver()); + } + return result; } - return result; - } - - @Override - public TransferResult visitFieldAccess( - FieldAccessNode n, TransferInput p) { - TransferResult result = super.visitFieldAccess(n, p); - // The receiver of an instance field access is non-null. A static field access does not - // ensure that the receiver is non-null. - if (!n.isStatic()) { - makeNonNull(result, n.getReceiver()); + + @Override + public TransferResult visitFieldAccess( + FieldAccessNode n, TransferInput p) { + TransferResult result = + super.visitFieldAccess(n, p); + // The receiver of an instance field access is non-null. A static field access does not + // ensure that the receiver is non-null. + if (!n.isStatic()) { + makeNonNull(result, n.getReceiver()); + } + return result; } - return result; - } - - @Override - public TransferResult visitThrow( - ThrowNode n, TransferInput p) { - TransferResult result = super.visitThrow(n, p); - makeNonNull(result, n.getExpression()); - return result; - } - - /** - * {@inheritDoc} - * - *

          When the conservativeArgumentNullnessAfterInvocation flag is turned off, the receiver and - * arguments that are passed to non-null parameters in a method call or constructor invocation are - * unsoundly assumed to be non-null after the invocation. - * - *

          When the flag is turned on, the analysis is more conservative by checking the method is - * SideEffectFree or the receiver is unassignable. Only if either one of the two is true, is the - * receiver made non-null. Similar logic is applied to the arguments of the invocation. - * - *

          Provided that m is of a type that implements interface java.util.Map: - * - *

            - *
          • Given a call m.get(k), if k is @KeyFor("m") and m's value type is @NonNull, then the - * result is @NonNull in the thenStore and elseStore of the transfer result. - *
          - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = - super.visitMethodInvocation(n, in); - - MethodInvocationTree tree = n.getTree(); - ExecutableElement method = TreeUtils.elementFromUse(tree); - - boolean isMethodSideEffectFree = - nullnessTypeFactory.isSideEffectFree(method) - || PurityUtils.isSideEffectFree(nullnessTypeFactory, method); - Node receiver = n.getTarget().getReceiver(); - if (nonNullAssumptionAfterInvocation - || isMethodSideEffectFree - || JavaExpression.fromNode(receiver).isUnassignableByOtherCode()) { - // Make receiver non-null. - makeNonNull(result, receiver); + + @Override + public TransferResult visitThrow( + ThrowNode n, TransferInput p) { + TransferResult result = super.visitThrow(n, p); + makeNonNull(result, n.getExpression()); + return result; } - // For all formal parameters with a non-null annotation, make the actual argument non-null. - // The point of this is to prevent cascaded errors -- the Nullness Checker will issue a - // warning for the method invocation, but not for subsequent uses of the argument. See test - // case FlowNullness.java. - AnnotatedExecutableType methodType = nullnessTypeFactory.getAnnotatedType(method); - List methodParams = methodType.getParameterTypes(); - List methodArgs = tree.getArguments(); - for (int i = 0; i < methodParams.size() && i < methodArgs.size(); ++i) { - if (methodParams.get(i).hasAnnotation(NONNULL) - && (nonNullAssumptionAfterInvocation - || isMethodSideEffectFree - || JavaExpression.fromTree(methodArgs.get(i)).isUnassignableByOtherCode())) { - makeNonNull(result, n.getArgument(i)); - } + /** + * {@inheritDoc} + * + *

          When the conservativeArgumentNullnessAfterInvocation flag is turned off, the receiver and + * arguments that are passed to non-null parameters in a method call or constructor invocation + * are unsoundly assumed to be non-null after the invocation. + * + *

          When the flag is turned on, the analysis is more conservative by checking the method is + * SideEffectFree or the receiver is unassignable. Only if either one of the two is true, is the + * receiver made non-null. Similar logic is applied to the arguments of the invocation. + * + *

          Provided that m is of a type that implements interface java.util.Map: + * + *

            + *
          • Given a call m.get(k), if k is @KeyFor("m") and m's value type is @NonNull, then the + * result is @NonNull in the thenStore and elseStore of the transfer result. + *
          + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = + super.visitMethodInvocation(n, in); + + MethodInvocationTree tree = n.getTree(); + ExecutableElement method = TreeUtils.elementFromUse(tree); + + boolean isMethodSideEffectFree = + nullnessTypeFactory.isSideEffectFree(method) + || PurityUtils.isSideEffectFree(nullnessTypeFactory, method); + Node receiver = n.getTarget().getReceiver(); + if (nonNullAssumptionAfterInvocation + || isMethodSideEffectFree + || JavaExpression.fromNode(receiver).isUnassignableByOtherCode()) { + // Make receiver non-null. + makeNonNull(result, receiver); + } + + // For all formal parameters with a non-null annotation, make the actual argument non-null. + // The point of this is to prevent cascaded errors -- the Nullness Checker will issue a + // warning for the method invocation, but not for subsequent uses of the argument. See test + // case FlowNullness.java. + AnnotatedExecutableType methodType = nullnessTypeFactory.getAnnotatedType(method); + List methodParams = methodType.getParameterTypes(); + List methodArgs = tree.getArguments(); + for (int i = 0; i < methodParams.size() && i < methodArgs.size(); ++i) { + if (methodParams.get(i).hasAnnotation(NONNULL) + && (nonNullAssumptionAfterInvocation + || isMethodSideEffectFree + || JavaExpression.fromTree(methodArgs.get(i)) + .isUnassignableByOtherCode())) { + makeNonNull(result, n.getArgument(i)); + } + } + + // Refine result to @NonNull if n is an invocation of Map.get, the argument is a key for + // the map, and the map's value type is not @Nullable. + if (nullnessTypeFactory.isMapGet(n)) { + boolean isKeyFor; + if (keyForTypeFactory != null) { + String mapName = JavaExpression.fromNode(receiver).toString(); + isKeyFor = keyForTypeFactory.isKeyForMap(mapName, methodArgs.get(0)); + } else { + isKeyFor = assumeKeyFor; + } + if (isKeyFor) { + AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree()); + if (!hasNullableValueType(receiverType)) { + makeNonNull(result, n); + refineToNonNull(result); + } + } + } + + return result; } - // Refine result to @NonNull if n is an invocation of Map.get, the argument is a key for - // the map, and the map's value type is not @Nullable. - if (nullnessTypeFactory.isMapGet(n)) { - boolean isKeyFor; - if (keyForTypeFactory != null) { - String mapName = JavaExpression.fromNode(receiver).toString(); - isKeyFor = keyForTypeFactory.isKeyForMap(mapName, methodArgs.get(0)); - } else { - isKeyFor = assumeKeyFor; - } - if (isKeyFor) { - AnnotatedTypeMirror receiverType = nullnessTypeFactory.getReceiverType(n.getTree()); - if (!hasNullableValueType(receiverType)) { - makeNonNull(result, n); - refineToNonNull(result); + /** + * Returns true if mapType's value type (the V type argument to Map) is @Nullable. + * + * @param mapOrSubtype the Map type, or a subtype + * @return true if mapType's value type is @Nullable + */ + private boolean hasNullableValueType(AnnotatedTypeMirror mapOrSubtype) { + AnnotatedDeclaredType mapType = + AnnotatedTypes.asSuper(nullnessTypeFactory, mapOrSubtype, MAP_TYPE); + int numTypeArguments = mapType.getTypeArguments().size(); + if (numTypeArguments != 2) { + throw new TypeSystemError( + "Wrong number %d of type arguments: %s", numTypeArguments, mapType); } - } + AnnotatedTypeMirror valueType = mapType.getTypeArguments().get(1); + return valueType.hasAnnotation(NULLABLE); } - return result; - } - - /** - * Returns true if mapType's value type (the V type argument to Map) is @Nullable. - * - * @param mapOrSubtype the Map type, or a subtype - * @return true if mapType's value type is @Nullable - */ - private boolean hasNullableValueType(AnnotatedTypeMirror mapOrSubtype) { - AnnotatedDeclaredType mapType = - AnnotatedTypes.asSuper(nullnessTypeFactory, mapOrSubtype, MAP_TYPE); - int numTypeArguments = mapType.getTypeArguments().size(); - if (numTypeArguments != 2) { - throw new TypeSystemError("Wrong number %d of type arguments: %s", numTypeArguments, mapType); + @Override + public TransferResult visitReturn( + ReturnNode n, TransferInput in) { + TransferResult result = super.visitReturn(n, in); + + if (result.getResultValue() == null) { + // Make sure there is a value for return statements, to record (at this return + // statement) the values of isPolyNullNotNull and isPolyNullNull. + return recreateTransferResult(createDummyValue(), result); + } else { + return result; + } } - AnnotatedTypeMirror valueType = mapType.getTypeArguments().get(1); - return valueType.hasAnnotation(NULLABLE); - } - - @Override - public TransferResult visitReturn( - ReturnNode n, TransferInput in) { - TransferResult result = super.visitReturn(n, in); - - if (result.getResultValue() == null) { - // Make sure there is a value for return statements, to record (at this return - // statement) the values of isPolyNullNotNull and isPolyNullNull. - return recreateTransferResult(createDummyValue(), result); - } else { - return result; + + /** + * Creates a dummy abstract value (whose type is not supposed to be looked at). + * + * @return a dummy abstract value + */ + private NullnessNoInitValue createDummyValue() { + TypeMirror dummy = analysis.getEnv().getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN); + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + annos.addAll(nullnessTypeFactory.getQualifierHierarchy().getBottomAnnotations()); + return new NullnessNoInitValue(analysis, annos, dummy); } - } - - /** - * Creates a dummy abstract value (whose type is not supposed to be looked at). - * - * @return a dummy abstract value - */ - private NullnessNoInitValue createDummyValue() { - TypeMirror dummy = analysis.getEnv().getTypeUtils().getPrimitiveType(TypeKind.BOOLEAN); - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - annos.addAll(nullnessTypeFactory.getQualifierHierarchy().getBottomAnnotations()); - return new NullnessNoInitValue(analysis, annos, dummy); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java index 104ce05cd2d..8a3bb327d80 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitValue.java @@ -1,7 +1,5 @@ package org.checkerframework.checker.nullness; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; @@ -14,90 +12,95 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TypesUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; + /** * Behaves just like {@link CFValue}, but additionally tracks whether at this point {@link PolyNull} * is known to be {@link NonNull} or {@link Nullable} (or not known to be either) */ public class NullnessNoInitValue extends CFAbstractValue { - /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ - protected boolean isPolyNullNonNull; + /** True if, at this point, {@link PolyNull} is known to be {@link NonNull}. */ + protected boolean isPolyNullNonNull; - /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ - protected boolean isPolyNullNull; + /** True if, at this point, {@link PolyNull} is known to be {@link Nullable}. */ + protected boolean isPolyNullNull; - /** - * Creates a new NullnessValue. - * - * @param analysis the analysis - * @param annotations the annotations - * @param underlyingType the underlying type - */ - public NullnessNoInitValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - } + /** + * Creates a new NullnessValue. + * + * @param analysis the analysis + * @param annotations the annotations + * @param underlyingType the underlying type + */ + public NullnessNoInitValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + } - @Override - protected NullnessNoInitValue upperBound( - @Nullable NullnessNoInitValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { - NullnessNoInitValue result = super.upperBound(other, upperBoundTypeMirror, shouldWiden); + @Override + protected NullnessNoInitValue upperBound( + @Nullable NullnessNoInitValue other, + TypeMirror upperBoundTypeMirror, + boolean shouldWiden) { + NullnessNoInitValue result = super.upperBound(other, upperBoundTypeMirror, shouldWiden); - AnnotationMirror resultNullableAnno = - analysis.getTypeFactory().getAnnotationByClass(result.annotations, Nullable.class); + AnnotationMirror resultNullableAnno = + analysis.getTypeFactory().getAnnotationByClass(result.annotations, Nullable.class); - if (resultNullableAnno != null && other != null) { - if ((this.isPolyNullNonNull - && this.containsNonNullOrPolyNull() - && other.isPolyNullNull - && other.containsNullableOrPolyNull()) - || (other.isPolyNullNonNull - && other.containsNonNullOrPolyNull() - && this.isPolyNullNull - && this.containsNullableOrPolyNull())) { - result.annotations.remove(resultNullableAnno); - result.annotations.add( - ((NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory()).POLYNULL); - } + if (resultNullableAnno != null && other != null) { + if ((this.isPolyNullNonNull + && this.containsNonNullOrPolyNull() + && other.isPolyNullNull + && other.containsNullableOrPolyNull()) + || (other.isPolyNullNonNull + && other.containsNonNullOrPolyNull() + && this.isPolyNullNull + && this.containsNullableOrPolyNull())) { + result.annotations.remove(resultNullableAnno); + result.annotations.add( + ((NullnessNoInitAnnotatedTypeFactory) analysis.getTypeFactory()).POLYNULL); + } + } + return result; } - return result; - } - /** - * Returns true if this value contains {@code @NonNull} or {@code @PolyNull}. - * - * @return true if this value contains {@code @NonNull} or {@code @PolyNull} - */ - @Pure - private boolean containsNonNullOrPolyNull() { - return analysis.getTypeFactory().containsSameByClass(annotations, NonNull.class) - || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); - } + /** + * Returns true if this value contains {@code @NonNull} or {@code @PolyNull}. + * + * @return true if this value contains {@code @NonNull} or {@code @PolyNull} + */ + @Pure + private boolean containsNonNullOrPolyNull() { + return analysis.getTypeFactory().containsSameByClass(annotations, NonNull.class) + || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); + } - /** - * Returns true if this value contans {@code @Nullable} or {@code @PolyNull}. - * - * @return true if this value contans {@code @Nullable} or {@code @PolyNull} - */ - @Pure - private boolean containsNullableOrPolyNull() { - return analysis.getTypeFactory().containsSameByClass(annotations, Nullable.class) - || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); - } + /** + * Returns true if this value contans {@code @Nullable} or {@code @PolyNull}. + * + * @return true if this value contans {@code @Nullable} or {@code @PolyNull} + */ + @Pure + private boolean containsNullableOrPolyNull() { + return analysis.getTypeFactory().containsSameByClass(annotations, Nullable.class) + || analysis.getTypeFactory().containsSameByClass(annotations, PolyNull.class); + } - @SideEffectFree - @Override - public String toStringSimple() { - return "NV{" - + AnnotationUtils.toStringSimple(annotations) - + ", " - + TypesUtils.simpleTypeName(underlyingType) - + ", poly nn/n=" - + (isPolyNullNonNull ? 't' : 'f') - + '/' - + (isPolyNullNull ? 't' : 'f') - + '}'; - } + @SideEffectFree + @Override + public String toStringSimple() { + return "NV{" + + AnnotationUtils.toStringSimple(annotations) + + ", " + + TypesUtils.simpleTypeName(underlyingType) + + ", poly nn/n=" + + (isPolyNullNonNull ? 't' : 'f') + + '/' + + (isPolyNullNull ? 't' : 'f') + + '}'; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java index 208f5d9060f..2b85cf0efc9 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitVisitor.java @@ -34,13 +34,7 @@ import com.sun.source.tree.VariableTree; import com.sun.source.tree.WhileLoopTree; import com.sun.source.util.TreePath; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.nullness.qual.NonNull; @@ -65,918 +59,938 @@ import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + /** The visitor for the nullness type-system. */ public class NullnessNoInitVisitor extends BaseTypeVisitor { - // Error message keys - // private static final @CompilerMessageKey String ASSIGNMENT_TYPE_INCOMPATIBLE = - // "assignment.type.incompatible"; - /** Error message key. */ - private static final @CompilerMessageKey String UNBOXING_OF_NULLABLE = "unboxing.of.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String LOCKING_NULLABLE = "locking.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String THROWING_NULLABLE = "throwing.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String ACCESSING_NULLABLE = "accessing.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String CONDITION_NULLABLE = "condition.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String ITERATING_NULLABLE = "iterating.over.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String SWITCHING_NULLABLE = "switching.nullable"; - - /** Error message key. */ - private static final @CompilerMessageKey String DEREFERENCE_OF_NULLABLE = - "dereference.of.nullable"; - - /** Annotation mirrors for nullness annotations. */ - private final AnnotationMirror NONNULL, NULLABLE, MONOTONIC_NONNULL, POLYNULL; - - /** TypeMirror for java.lang.String. */ - private final TypeMirror stringType; - - /** The element for java.util.Collection.size(). */ - private final ExecutableElement collectionSize; - - /** The element for java.util.Collection.toArray(T). */ - private final ExecutableElement collectionToArray; - - /** The System.clearProperty(String) method. */ - private final ExecutableElement systemClearProperty; - - /** The System.setProperties(String) method. */ - private final ExecutableElement systemSetProperties; - - /** True if checked code may clear system properties. */ - private final boolean permitClearProperty; - - /** True if -AassumeAssertionsAreEnabled was passed on the command line. */ - private final boolean assumeAssertionsAreEnabled; - - /** True if -AassumeAssertionsAreDisabled was passed on the command line. */ - private final boolean assumeAssertionsAreDisabled; - - /** True if -Alint=redundantNullComparison was passed on the command line. */ - private final boolean redundantNullComparison; - - /** - * Create a new NullnessVisitor. - * - * @param checker the checker to which this visitor belongs - */ - public NullnessNoInitVisitor(BaseTypeChecker checker) { - super(checker); - - NONNULL = atypeFactory.NONNULL; - NULLABLE = atypeFactory.NULLABLE; - MONOTONIC_NONNULL = atypeFactory.MONOTONIC_NONNULL; - POLYNULL = atypeFactory.POLYNULL; - stringType = elements.getTypeElement(String.class.getCanonicalName()).asType(); - - ProcessingEnvironment env = checker.getProcessingEnvironment(); - this.collectionSize = TreeUtils.getMethod("java.util.Collection", "size", 0, env); - this.collectionToArray = TreeUtils.getMethod("java.util.Collection", "toArray", env, "T[]"); - systemClearProperty = TreeUtils.getMethod("java.lang.System", "clearProperty", 1, env); - systemSetProperties = TreeUtils.getMethod("java.lang.System", "setProperties", 1, env); - - this.permitClearProperty = - checker.getLintOption( - NullnessChecker.LINT_PERMITCLEARPROPERTY, - NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); - assumeAssertionsAreEnabled = checker.hasOption("assumeAssertionsAreEnabled"); - assumeAssertionsAreDisabled = checker.hasOption("assumeAssertionsAreDisabled"); - redundantNullComparison = - checker.getLintOption( - NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, - NullnessChecker.LINT_DEFAULT_REDUNDANTNULLCOMPARISON); - } - - @Override - public NullnessNoInitAnnotatedTypeFactory createTypeFactory() { - return new NullnessNoInitAnnotatedTypeFactory(checker); - } - - @Override - public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { - // The Nullness Checker issues a more comprehensible "nullness.on.primitive" error rather - // than the "type.invalid.annotations.on.use" error this method would issue. - return true; - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Constructor results are always @NonNull. Other annotations are forbidden by - // #visitMethod. - // Nothing to check. - } - - @Override - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { - // Constructor results are always @NonNull, so the result type of a this/super call is - // always equal to the result type of the current constructor. - // Nothing to check. - } - - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // Allow a MonotonicNonNull field to be initialized to null at its declaration, in a - // constructor, or in an initializer block. (The latter two are, strictly speaking, unsound - // because the constructor or initializer block might have previously set the field to a - // non-null value. Maybe add an option to disable that behavior.) - Element elem = initializedElement(varTree); - if (elem != null - && atypeFactory.fromElement(elem).hasEffectiveAnnotation(MONOTONIC_NONNULL) - && !checker.getLintOption( - NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, - NullnessChecker.LINT_DEFAULT_NOINITFORMONOTONICNONNULL)) { - return true; - } - return super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); - } - - /** - * Returns the variable element, if the argument is an initialization; otherwise returns null. - * - * @param varTree an assignment LHS - * @return the initialized element, or null - */ - @SuppressWarnings("UnusedMethod") - private @Nullable Element initializedElement(Tree varTree) { - switch (varTree.getKind()) { - case VARIABLE: - // It's a variable declaration. - return TreeUtils.elementFromDeclaration((VariableTree) varTree); - - case MEMBER_SELECT: - MemberSelectTree mst = (MemberSelectTree) varTree; - ExpressionTree receiver = mst.getExpression(); - // This recognizes "this.fieldname = ..." but not "MyClass.fieldname = ..." or - // "MyClass.this.fieldname = ...". The latter forms are probably rare in a - // constructor. - // Note that this method should return non-null only for fields of this class, not - // fields of any other class, including outer classes. - if (receiver.getKind() != Tree.Kind.IDENTIFIER - || !((IdentifierTree) receiver).getName().contentEquals("this")) { - return null; - } - // fallthrough - case IDENTIFIER: - TreePath path = getCurrentPath(); - if (TreePathUtil.inConstructor(path)) { - return TreeUtils.elementFromUse((ExpressionTree) varTree); + // Error message keys + // private static final @CompilerMessageKey String ASSIGNMENT_TYPE_INCOMPATIBLE = + // "assignment.type.incompatible"; + /** Error message key. */ + private static final @CompilerMessageKey String UNBOXING_OF_NULLABLE = "unboxing.of.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String LOCKING_NULLABLE = "locking.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String THROWING_NULLABLE = "throwing.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String ACCESSING_NULLABLE = "accessing.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String CONDITION_NULLABLE = "condition.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String ITERATING_NULLABLE = "iterating.over.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String SWITCHING_NULLABLE = "switching.nullable"; + + /** Error message key. */ + private static final @CompilerMessageKey String DEREFERENCE_OF_NULLABLE = + "dereference.of.nullable"; + + /** Annotation mirrors for nullness annotations. */ + private final AnnotationMirror NONNULL, NULLABLE, MONOTONIC_NONNULL, POLYNULL; + + /** TypeMirror for java.lang.String. */ + private final TypeMirror stringType; + + /** The element for java.util.Collection.size(). */ + private final ExecutableElement collectionSize; + + /** The element for java.util.Collection.toArray(T). */ + private final ExecutableElement collectionToArray; + + /** The System.clearProperty(String) method. */ + private final ExecutableElement systemClearProperty; + + /** The System.setProperties(String) method. */ + private final ExecutableElement systemSetProperties; + + /** True if checked code may clear system properties. */ + private final boolean permitClearProperty; + + /** True if -AassumeAssertionsAreEnabled was passed on the command line. */ + private final boolean assumeAssertionsAreEnabled; + + /** True if -AassumeAssertionsAreDisabled was passed on the command line. */ + private final boolean assumeAssertionsAreDisabled; + + /** True if -Alint=redundantNullComparison was passed on the command line. */ + private final boolean redundantNullComparison; + + /** + * Create a new NullnessVisitor. + * + * @param checker the checker to which this visitor belongs + */ + public NullnessNoInitVisitor(BaseTypeChecker checker) { + super(checker); + + NONNULL = atypeFactory.NONNULL; + NULLABLE = atypeFactory.NULLABLE; + MONOTONIC_NONNULL = atypeFactory.MONOTONIC_NONNULL; + POLYNULL = atypeFactory.POLYNULL; + stringType = elements.getTypeElement(String.class.getCanonicalName()).asType(); + + ProcessingEnvironment env = checker.getProcessingEnvironment(); + this.collectionSize = TreeUtils.getMethod("java.util.Collection", "size", 0, env); + this.collectionToArray = TreeUtils.getMethod("java.util.Collection", "toArray", env, "T[]"); + systemClearProperty = TreeUtils.getMethod("java.lang.System", "clearProperty", 1, env); + systemSetProperties = TreeUtils.getMethod("java.lang.System", "setProperties", 1, env); + + this.permitClearProperty = + checker.getLintOption( + NullnessChecker.LINT_PERMITCLEARPROPERTY, + NullnessChecker.LINT_DEFAULT_PERMITCLEARPROPERTY); + assumeAssertionsAreEnabled = checker.hasOption("assumeAssertionsAreEnabled"); + assumeAssertionsAreDisabled = checker.hasOption("assumeAssertionsAreDisabled"); + redundantNullComparison = + checker.getLintOption( + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON, + NullnessChecker.LINT_DEFAULT_REDUNDANTNULLCOMPARISON); + } + + @Override + public NullnessNoInitAnnotatedTypeFactory createTypeFactory() { + return new NullnessNoInitAnnotatedTypeFactory(checker); + } + + @Override + public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { + // The Nullness Checker issues a more comprehensible "nullness.on.primitive" error rather + // than the "type.invalid.annotations.on.use" error this method would issue. + return true; + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Constructor results are always @NonNull. Other annotations are forbidden by + // #visitMethod. + // Nothing to check. + } + + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + // Constructor results are always @NonNull, so the result type of a this/super call is + // always equal to the result type of the current constructor. + // Nothing to check. + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // Allow a MonotonicNonNull field to be initialized to null at its declaration, in a + // constructor, or in an initializer block. (The latter two are, strictly speaking, unsound + // because the constructor or initializer block might have previously set the field to a + // non-null value. Maybe add an option to disable that behavior.) + Element elem = initializedElement(varTree); + if (elem != null + && atypeFactory.fromElement(elem).hasEffectiveAnnotation(MONOTONIC_NONNULL) + && !checker.getLintOption( + NullnessChecker.LINT_NOINITFORMONOTONICNONNULL, + NullnessChecker.LINT_DEFAULT_NOINITFORMONOTONICNONNULL)) { + return true; + } + return super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); + } + + /** + * Returns the variable element, if the argument is an initialization; otherwise returns null. + * + * @param varTree an assignment LHS + * @return the initialized element, or null + */ + @SuppressWarnings("UnusedMethod") + private @Nullable Element initializedElement(Tree varTree) { + switch (varTree.getKind()) { + case VARIABLE: + // It's a variable declaration. + return TreeUtils.elementFromDeclaration((VariableTree) varTree); + + case MEMBER_SELECT: + MemberSelectTree mst = (MemberSelectTree) varTree; + ExpressionTree receiver = mst.getExpression(); + // This recognizes "this.fieldname = ..." but not "MyClass.fieldname = ..." or + // "MyClass.this.fieldname = ...". The latter forms are probably rare in a + // constructor. + // Note that this method should return non-null only for fields of this class, not + // fields of any other class, including outer classes. + if (receiver.getKind() != Tree.Kind.IDENTIFIER + || !((IdentifierTree) receiver).getName().contentEquals("this")) { + return null; + } + // fallthrough + case IDENTIFIER: + TreePath path = getCurrentPath(); + if (TreePathUtil.inConstructor(path)) { + return TreeUtils.elementFromUse((ExpressionTree) varTree); + } else { + return null; + } + + default: + return null; + } + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + // Use the valueExp as the context because data flow will have a value for that tree. It + // might not have a value for the var tree. This is sound because if data flow has + // determined @PolyNull is @Nullable at the RHS, then it is also @Nullable for the LHS. + // TODO: A visitor should not change types, so this call to replacePolyQualifier is a hack. + // To work around this hack here, NullnessVisitor#visitConditionalExpression needs a further + // hack. Both should be undone, by properly resolving polymorphic qualifiers in the + // ATF/transfer. + atypeFactory.replacePolyQualifier(varType, valueExp); + return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); + } + + @Override + @FormatMethod + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + if (TypesUtils.isPrimitive(varType.getUnderlyingType()) + && !TypesUtils.isPrimitive(valueType.getUnderlyingType())) { + boolean succeed = checkForNullability(valueType, valueTree, UNBOXING_OF_NULLABLE); + if (!succeed) { + // Only issue the unboxing of nullable error. + return false; + } + } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } + + /** Case 1: Check for null dereferencing. */ + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + // if (atypeFactory.isUnreachable(tree)) { + // return super.visitMemberSelect(tree, p); + // } + Element e = TreeUtils.elementFromUse(tree); + if (e.getKind() == ElementKind.CLASS) { + if (atypeFactory.containsNullnessAnnotation(null, tree.getExpression())) { + checker.reportError(tree, "nullness.on.outer"); + } + } else if (!(TreeUtils.isSelfAccess(tree) + || tree.getExpression().getKind() == Tree.Kind.PARAMETERIZED_TYPE + // case 8. static member access + || ElementUtils.isStatic(e))) { + checkForNullability(tree.getExpression(), DEREFERENCE_OF_NULLABLE); + } + + return super.visitMemberSelect(tree, p); + } + + /** Case 2: Check for implicit {@code .iterator} call. */ + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + checkForNullability(tree.getExpression(), ITERATING_NULLABLE); + return super.visitEnhancedForLoop(tree, p); + } + + /** Case 3: Check for array dereferencing. */ + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + checkForNullability(tree.getExpression(), ACCESSING_NULLABLE); + return super.visitArrayAccess(tree, p); + } + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + AnnotatedArrayType type = atypeFactory.getAnnotatedType(tree); + AnnotatedTypeMirror componentType = type.getComponentType(); + if (componentType.hasEffectiveAnnotation(NONNULL) + && !isNewArrayAllZeroDims(tree) + && !isNewArrayInToArray(tree) + && !TypesUtils.isPrimitive(componentType.getUnderlyingType()) + && (checker.getLintOption("soundArrayCreationNullness", false) + // temporary, for backward compatibility + || checker.getLintOption("forbidnonnullarraycomponents", false))) { + checker.reportError( + tree, + "new.array.type.invalid", + componentType.getAnnotations(), + type.toString()); + } + + if (type.hasEffectiveAnnotation(NULLABLE) + || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) + || type.hasEffectiveAnnotation(POLYNULL)) { + checker.reportError(tree, "nullness.on.new.array"); + } + + return super.visitNewArray(tree, p); + } + + /** + * Determine whether all dimensions given in a new array expression have zero as length. For + * example "new Object[0][0];". Also true for empty dimensions, as in "new Object[] {...}". + * + * @param tree the constructor invocation to check + * @return true if every array dimension has a size of zero + */ + private static boolean isNewArrayAllZeroDims(NewArrayTree tree) { + boolean isAllZeros = true; + for (ExpressionTree dim : tree.getDimensions()) { + if (dim instanceof LiteralTree) { + Object val = ((LiteralTree) dim).getValue(); + if (!(val instanceof Number) || !Integer.valueOf(0).equals(val)) { + isAllZeros = false; + break; + } + } else { + isAllZeros = false; + break; + } + } + return isAllZeros; + } + + /** + * Return true if the given tree is "new X[]", in the context "toArray(new X[])". + * + * @param tree a tree to test + * @return true if the tree is a new array within a call to toArray() + */ + private boolean isNewArrayInToArray(NewArrayTree tree) { + if (tree.getDimensions().size() != 1) { + return false; + } + + ExpressionTree dim = tree.getDimensions().get(0); + ProcessingEnvironment env = checker.getProcessingEnvironment(); + + if (!TreeUtils.isMethodInvocation(dim, collectionSize, env)) { + return false; + } + + ExpressionTree rcvsize = ((MethodInvocationTree) dim).getMethodSelect(); + if (!(rcvsize instanceof MemberSelectTree)) { + return false; + } + rcvsize = ((MemberSelectTree) rcvsize).getExpression(); + if (!(rcvsize instanceof IdentifierTree)) { + return false; + } + + Tree encl = getCurrentPath().getParentPath().getLeaf(); + + if (!TreeUtils.isMethodInvocation(encl, collectionToArray, env)) { + return false; + } + + ExpressionTree rcvtoarray = ((MethodInvocationTree) encl).getMethodSelect(); + if (!(rcvtoarray instanceof MemberSelectTree)) { + return false; + } + rcvtoarray = ((MemberSelectTree) rcvtoarray).getExpression(); + if (!(rcvtoarray instanceof IdentifierTree)) { + return false; + } + + return ((IdentifierTree) rcvsize).getName() == ((IdentifierTree) rcvtoarray).getName(); + } + + /** Case 4: Check for thrown exception nullness. */ + @Override + protected void checkThrownExpression(ThrowTree tree) { + checkForNullability(tree.getExpression(), THROWING_NULLABLE); + } + + /** Case 5: Check for synchronizing locks. */ + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + checkForNullability(tree.getExpression(), LOCKING_NULLABLE); + return super.visitSynchronized(tree, p); + } + + @Override + public Void visitAssert(AssertTree tree, Void p) { + // See also + // org.checkerframework.dataflow.cfg.builder.CFGBuilder.CFGTranslationPhaseOne.visitAssert + + // In cases where neither assumeAssertionsAreEnabled nor assumeAssertionsAreDisabled are + // turned on and @AssumeAssertions is not used, checkForNullability is still called since + // the CFGBuilder will have generated one branch for which asserts are assumed to be + // enabled. + + boolean doVisitAssert; + if (assumeAssertionsAreEnabled + || CFCFGBuilder.assumeAssertionsActivatedForAssertTree(checker, tree)) { + doVisitAssert = true; + } else if (assumeAssertionsAreDisabled) { + doVisitAssert = false; } else { - return null; + // no option given -> visit + doVisitAssert = true; + } + + if (doVisitAssert) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitAssert(tree, p); } - default: return null; } - } - - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - // Use the valueExp as the context because data flow will have a value for that tree. It - // might not have a value for the var tree. This is sound because if data flow has - // determined @PolyNull is @Nullable at the RHS, then it is also @Nullable for the LHS. - // TODO: A visitor should not change types, so this call to replacePolyQualifier is a hack. - // To work around this hack here, NullnessVisitor#visitConditionalExpression needs a further - // hack. Both should be undone, by properly resolving polymorphic qualifiers in the - // ATF/transfer. - atypeFactory.replacePolyQualifier(varType, valueExp); - return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); - } - - @Override - @FormatMethod - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - if (TypesUtils.isPrimitive(varType.getUnderlyingType()) - && !TypesUtils.isPrimitive(valueType.getUnderlyingType())) { - boolean succeed = checkForNullability(valueType, valueTree, UNBOXING_OF_NULLABLE); - if (!succeed) { - // Only issue the unboxing of nullable error. - return false; - } - } - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - } - - /** Case 1: Check for null dereferencing. */ - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - // if (atypeFactory.isUnreachable(tree)) { - // return super.visitMemberSelect(tree, p); - // } - Element e = TreeUtils.elementFromUse(tree); - if (e.getKind() == ElementKind.CLASS) { - if (atypeFactory.containsNullnessAnnotation(null, tree.getExpression())) { - checker.reportError(tree, "nullness.on.outer"); - } - } else if (!(TreeUtils.isSelfAccess(tree) - || tree.getExpression().getKind() == Tree.Kind.PARAMETERIZED_TYPE - // case 8. static member access - || ElementUtils.isStatic(e))) { - checkForNullability(tree.getExpression(), DEREFERENCE_OF_NULLABLE); - } - - return super.visitMemberSelect(tree, p); - } - - /** Case 2: Check for implicit {@code .iterator} call. */ - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - checkForNullability(tree.getExpression(), ITERATING_NULLABLE); - return super.visitEnhancedForLoop(tree, p); - } - - /** Case 3: Check for array dereferencing. */ - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - checkForNullability(tree.getExpression(), ACCESSING_NULLABLE); - return super.visitArrayAccess(tree, p); - } - - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - AnnotatedArrayType type = atypeFactory.getAnnotatedType(tree); - AnnotatedTypeMirror componentType = type.getComponentType(); - if (componentType.hasEffectiveAnnotation(NONNULL) - && !isNewArrayAllZeroDims(tree) - && !isNewArrayInToArray(tree) - && !TypesUtils.isPrimitive(componentType.getUnderlyingType()) - && (checker.getLintOption("soundArrayCreationNullness", false) - // temporary, for backward compatibility - || checker.getLintOption("forbidnonnullarraycomponents", false))) { - checker.reportError( - tree, "new.array.type.invalid", componentType.getAnnotations(), type.toString()); - } - - if (type.hasEffectiveAnnotation(NULLABLE) - || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) - || type.hasEffectiveAnnotation(POLYNULL)) { - checker.reportError(tree, "nullness.on.new.array"); - } - - return super.visitNewArray(tree, p); - } - - /** - * Determine whether all dimensions given in a new array expression have zero as length. For - * example "new Object[0][0];". Also true for empty dimensions, as in "new Object[] {...}". - * - * @param tree the constructor invocation to check - * @return true if every array dimension has a size of zero - */ - private static boolean isNewArrayAllZeroDims(NewArrayTree tree) { - boolean isAllZeros = true; - for (ExpressionTree dim : tree.getDimensions()) { - if (dim instanceof LiteralTree) { - Object val = ((LiteralTree) dim).getValue(); - if (!(val instanceof Number) || !Integer.valueOf(0).equals(val)) { - isAllZeros = false; - break; - } - } else { - isAllZeros = false; - break; - } - } - return isAllZeros; - } - - /** - * Return true if the given tree is "new X[]", in the context "toArray(new X[])". - * - * @param tree a tree to test - * @return true if the tree is a new array within a call to toArray() - */ - private boolean isNewArrayInToArray(NewArrayTree tree) { - if (tree.getDimensions().size() != 1) { - return false; - } - - ExpressionTree dim = tree.getDimensions().get(0); - ProcessingEnvironment env = checker.getProcessingEnvironment(); - - if (!TreeUtils.isMethodInvocation(dim, collectionSize, env)) { - return false; - } - - ExpressionTree rcvsize = ((MethodInvocationTree) dim).getMethodSelect(); - if (!(rcvsize instanceof MemberSelectTree)) { - return false; - } - rcvsize = ((MemberSelectTree) rcvsize).getExpression(); - if (!(rcvsize instanceof IdentifierTree)) { - return false; - } - - Tree encl = getCurrentPath().getParentPath().getLeaf(); - - if (!TreeUtils.isMethodInvocation(encl, collectionToArray, env)) { - return false; - } - - ExpressionTree rcvtoarray = ((MethodInvocationTree) encl).getMethodSelect(); - if (!(rcvtoarray instanceof MemberSelectTree)) { - return false; - } - rcvtoarray = ((MemberSelectTree) rcvtoarray).getExpression(); - if (!(rcvtoarray instanceof IdentifierTree)) { - return false; - } - - return ((IdentifierTree) rcvsize).getName() == ((IdentifierTree) rcvtoarray).getName(); - } - - /** Case 4: Check for thrown exception nullness. */ - @Override - protected void checkThrownExpression(ThrowTree tree) { - checkForNullability(tree.getExpression(), THROWING_NULLABLE); - } - - /** Case 5: Check for synchronizing locks. */ - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - checkForNullability(tree.getExpression(), LOCKING_NULLABLE); - return super.visitSynchronized(tree, p); - } - - @Override - public Void visitAssert(AssertTree tree, Void p) { - // See also - // org.checkerframework.dataflow.cfg.builder.CFGBuilder.CFGTranslationPhaseOne.visitAssert - - // In cases where neither assumeAssertionsAreEnabled nor assumeAssertionsAreDisabled are - // turned on and @AssumeAssertions is not used, checkForNullability is still called since - // the CFGBuilder will have generated one branch for which asserts are assumed to be - // enabled. - - boolean doVisitAssert; - if (assumeAssertionsAreEnabled - || CFCFGBuilder.assumeAssertionsActivatedForAssertTree(checker, tree)) { - doVisitAssert = true; - } else if (assumeAssertionsAreDisabled) { - doVisitAssert = false; - } else { - // no option given -> visit - doVisitAssert = true; - } - - if (doVisitAssert) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitAssert(tree, p); - } - - return null; - } - - @Override - public Void visitIf(IfTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitIf(tree, p); - } - - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - // The "reference type" is the type after "instanceof". - Tree refTypeTree = tree.getType(); - if (refTypeTree == null) { - // TODO: the type is null for deconstructor patterns. - // Handle them properly. - return null; - } - - List annotations = null; - if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - annotations = TreeUtils.annotationsFromTree((AnnotatedTypeTree) refTypeTree); - } else { - Tree patternTree = TreeUtilsAfterJava11.InstanceOfUtils.getPattern(tree); - if (patternTree != null && TreeUtils.isBindingPatternTree(patternTree)) { - VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); - if (variableTree.getModifiers() != null) { - List annotationTree = - variableTree.getModifiers().getAnnotations(); - annotations = TreeUtils.annotationsFromTypeAnnotationTrees(annotationTree); - } - } - } - - if (annotations != null) { - if (AnnotationUtils.containsSame(annotations, NULLABLE)) { - checker.reportError(tree, "instanceof.nullable"); - } - if (AnnotationUtils.containsSame(annotations, NONNULL)) { - checker.reportWarning(tree, "instanceof.nonnull.redundant"); - } - } - - // Don't call super because it will issue an incorrect instanceof.unsafe warning. - return null; - } - - /** - * Reports an error if a comparison of a @NonNull expression with the null literal is performed. - * Does nothing unless {@code -Alint=redundantNullComparison} is passed on the command line. - * - * @param tree a tree that might be a comparison of a @NonNull expression with the null literal - */ - protected void checkForRedundantTests(BinaryTree tree) { - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); - - // respect command-line option - if (!redundantNullComparison) { - return; - } - - // equality tests - if ((tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { - AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); - AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); - if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && right.hasEffectiveAnnotation(NONNULL)) { - checker.reportWarning(tree, "nulltest.redundant", rightOp.toString()); - } else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL - && left.hasEffectiveAnnotation(NONNULL)) { - checker.reportWarning(tree, "nulltest.redundant", leftOp.toString()); - } - } - } - - /** Case 6: Check for redundant nullness tests Case 7: unboxing case: primitive operations. */ - @Override - public Void visitBinary(BinaryTree tree, Void p) { - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); - - if (isUnboxingOperation(tree)) { - checkForNullability(leftOp, UNBOXING_OF_NULLABLE); - checkForNullability(rightOp, UNBOXING_OF_NULLABLE); - } - - checkForRedundantTests(tree); - - return super.visitBinary(tree, p); - } - - /** Case 7: unboxing case: primitive operation. */ - @Override - public Void visitUnary(UnaryTree tree, Void p) { - checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); - return super.visitUnary(tree, p); - } - - /** Case 7: unboxing case: primitive operation. */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - // ignore String concatenation - if (!isString(tree)) { - checkForNullability(tree.getVariable(), UNBOXING_OF_NULLABLE); - checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); - } - return super.visitCompoundAssignment(tree, p); - } - - /** Case 7: unboxing case: casting to a primitive. */ - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - if (isPrimitive(tree) && !isPrimitive(tree.getExpression())) { - if (!checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE)) { - // If unboxing of nullable is issued, don't issue any other errors. + + @Override + public Void visitIf(IfTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitIf(tree, p); + } + + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + // The "reference type" is the type after "instanceof". + Tree refTypeTree = tree.getType(); + if (refTypeTree == null) { + // TODO: the type is null for deconstructor patterns. + // Handle them properly. + return null; + } + + List annotations = null; + if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + annotations = TreeUtils.annotationsFromTree((AnnotatedTypeTree) refTypeTree); + } else { + Tree patternTree = TreeUtilsAfterJava11.InstanceOfUtils.getPattern(tree); + if (patternTree != null && TreeUtils.isBindingPatternTree(patternTree)) { + VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); + if (variableTree.getModifiers() != null) { + List annotationTree = + variableTree.getModifiers().getAnnotations(); + annotations = TreeUtils.annotationsFromTypeAnnotationTrees(annotationTree); + } + } + } + + if (annotations != null) { + if (AnnotationUtils.containsSame(annotations, NULLABLE)) { + checker.reportError(tree, "instanceof.nullable"); + } + if (AnnotationUtils.containsSame(annotations, NONNULL)) { + checker.reportWarning(tree, "instanceof.nonnull.redundant"); + } + } + + // Don't call super because it will issue an incorrect instanceof.unsafe warning. return null; - } - } - return super.visitTypeCast(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - VariableTree receiver = tree.getReceiverParameter(); - - if (TreeUtils.isConstructor(tree)) { - // Constructor results are always @NonNull. Any annotations are forbidden. - List annoTrees = tree.getModifiers().getAnnotations(); - if (atypeFactory.containsNullnessAnnotation(annoTrees)) { - checker.reportError(tree, "nullness.on.constructor"); - } - } - - if (receiver != null) { - List annoTrees = receiver.getModifiers().getAnnotations(); - Tree type = receiver.getType(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { - checker.reportError(tree, "nullness.on.receiver"); - } - } - - return super.visitMethod(tree, p); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (!permitClearProperty) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - if (TreeUtils.isMethodInvocation(tree, systemClearProperty, env)) { - String literal = literalFirstArgument(tree); - if (literal == null - || SystemGetPropertyHandler.predefinedSystemProperties.contains(literal)) { - checker.reportError(tree, "clear.system.property"); - } - } - if (TreeUtils.isMethodInvocation(tree, systemSetProperties, env)) { - checker.reportError(tree, "clear.system.property"); - } - } - return super.visitMethodInvocation(tree, p); - } - - /** - * If the first argument of a method call is a literal, return it; otherwise return null. - * - * @param tree a method invocation whose first formal parameter is of String type - * @return the first argument if it is a literal, otherwise null - */ - /*package-private*/ static @Nullable String literalFirstArgument(MethodInvocationTree tree) { - List args = tree.getArguments(); - assert args.size() > 0; - ExpressionTree arg = args.get(0); - if (arg.getKind() == Tree.Kind.STRING_LITERAL) { - String literal = (String) ((LiteralTree) arg).getValue(); - return literal; - } - return null; - } - - @Override - public void processClassTree(ClassTree classTree) { - Tree extendsClause = classTree.getExtendsClause(); - if (extendsClause != null) { - reportErrorIfSupertypeContainsNullnessAnnotation(extendsClause); - } - for (Tree implementsClause : classTree.getImplementsClause()) { - reportErrorIfSupertypeContainsNullnessAnnotation(implementsClause); - } - - if (classTree.getKind() == Tree.Kind.ENUM) { - for (Tree member : classTree.getMembers()) { - if (member.getKind() == Tree.Kind.VARIABLE - && TreeUtils.elementFromDeclaration((VariableTree) member).getKind() - == ElementKind.ENUM_CONSTANT) { - VariableTree varDecl = (VariableTree) member; - List annoTrees = varDecl.getModifiers().getAnnotations(); - Tree type = varDecl.getType(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { - checker.reportError(member, "nullness.on.enum"); - } - } - } - } - - super.processClassTree(classTree); - } - - /** - * Report "nullness.on.supertype" error if a supertype has a nullness annotation. - * - * @param typeTree a supertype tree, from an {@code extends} or {@code implements} clause - */ - private void reportErrorIfSupertypeContainsNullnessAnnotation(Tree typeTree) { - if (typeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - List annoTrees = ((AnnotatedTypeTree) typeTree).getAnnotations(); - if (atypeFactory.containsNullnessAnnotation(annoTrees)) { - checker.reportError(typeTree, "nullness.on.supertype"); - } - } - } - - // ///////////// Utility methods ////////////////////////////// - - /** - * Issues the error message if the type of the tree is not of a {@link NonNull} type. - * - * @param tree the tree where the error is to reported - * @param errMsg the error message (must be {@link CompilerMessageKey}) - * @return whether or not the check succeeded - */ - private boolean checkForNullability(ExpressionTree tree, @CompilerMessageKey String errMsg) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - return checkForNullability(type, tree, errMsg); - } - - /** - * Issues the error message if an expression with this type may be null. - * - * @param type annotated type - * @param tree the tree where the error is to reported - * @param errMsg the error message (must be {@link CompilerMessageKey}) - * @return whether or not the check succeeded - */ - private boolean checkForNullability( - AnnotatedTypeMirror type, Tree tree, @CompilerMessageKey String errMsg) { - if (!type.hasEffectiveAnnotation(NONNULL)) { - checker.reportError(tree, errMsg, tree); - return false; - } - return true; - } - - @Override - protected void checkMethodInvocability( - AnnotatedExecutableType method, MethodInvocationTree tree) { - if (method.getReceiverType() == null) { - // Static methods don't have a receiver to check. - return; - } - - if (!TreeUtils.isSelfAccess(tree) - && - // Static methods don't have a receiver - method.getReceiverType() != null) { - // TODO: should all or some constructors be excluded? - // method.getElement().getKind() != ElementKind.CONSTRUCTOR) { - AnnotationMirrorSet receiverAnnos = atypeFactory.getReceiverType(tree).getAnnotations(); - AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); - AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); - AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); - treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); - // If receiver is Nullable, then we don't want to issue a warning about method - // invocability (we'd rather have only the "dereference.of.nullable" message). - if (treeReceiver.hasAnnotation(NULLABLE) - || receiverAnnos.contains(MONOTONIC_NONNULL) - || treeReceiver.hasAnnotation(POLYNULL)) { - return; - } - } - super.checkMethodInvocability(method, tree); - } - - /** - * Returns true if the binary operation could cause an unboxing operation. - * - * @param tree a binary operation - * @return true if the binary operation could cause an unboxing operation - */ - private boolean isUnboxingOperation(BinaryTree tree) { - if (tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { - // it is valid to check equality between two reference types, even - // if one (or both) of them is null - return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand()); - } else { - // All BinaryTree's are of type String, a primitive type or the reference type - // equivalent of a primitive type. Furthermore, Strings don't have a primitive type, and - // therefore only BinaryTrees that aren't String can cause unboxing. - return !isString(tree); - } - } - - /** - * Returns true if the type of the tree is a super of String. - * - * @param tree a tree - * @return true if the type of the tree is a super of String - */ - private boolean isString(ExpressionTree tree) { - TypeMirror type = TreeUtils.typeOf(tree); - return types.isAssignable(stringType, type); - } - - /** - * Returns true if the type of the tree is a primitive. - * - * @param tree a tree - * @return true if the type of the tree is a primitive - */ - private static boolean isPrimitive(ExpressionTree tree) { - return TreeUtils.typeOf(tree).getKind().isPrimitive(); - } - - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - if (!TreeUtils.hasNullCaseLabel(tree)) { - checkForNullability(tree.getExpression(), SWITCHING_NULLABLE); - } - if (redundantNullComparison && TreeUtils.isEnhancedSwitchStatement(tree)) { - ExpressionTree expression = tree.getExpression(); - List cases = tree.getCases(); - checkSwitchNullRedundant(expression, cases); - } - return super.visitSwitch(tree, p); - } - - @Override - public void visitSwitchExpression17(Tree switchExprTree) { - if (!TreeUtils.hasNullCaseLabel(switchExprTree)) { - checkForNullability(SwitchExpressionUtils.getExpression(switchExprTree), SWITCHING_NULLABLE); - } - if (redundantNullComparison) { - ExpressionTree expression = SwitchExpressionUtils.getExpression(switchExprTree); - List cases = SwitchExpressionUtils.getCases(switchExprTree); - checkSwitchNullRedundant(expression, cases); - } - super.visitSwitchExpression17(switchExprTree); - } - - /** - * Reports a warning if the expression of the switch statement or expression is {@code @NonNull} - * and one of the case labels in the case trees is the null literal. - * - * @param expression the expression of the switch statement or expression - * @param cases the cases of the switch statement or expression - */ - private void checkSwitchNullRedundant(ExpressionTree expression, List cases) { - AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(expression); - if (!switchType.hasEffectiveAnnotation(NONNULL)) { - return; - } - outer: - for (CaseTree caseTree : cases) { - List caseLabels = TreeUtilsAfterJava11.CaseUtils.getLabels(caseTree); - for (Tree caseLabel : caseLabels) { - if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL) { - checker.reportWarning(caseLabel, "nulltest.redundant", expression.toString()); - break outer; - } - } - } - } - - @Override - public Void visitForLoop(ForLoopTree tree, Void p) { - if (tree.getCondition() != null) { - // Condition is null e.g. in "for (;;) {...}" - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - } - return super.visitForLoop(tree, p); - } - - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - ExpressionTree enclosingExpr = tree.getEnclosingExpression(); - if (enclosingExpr != null) { - checkForNullability(enclosingExpr, DEREFERENCE_OF_NULLABLE); - } - - AnnotatedTypeMirror.AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); - if (type.hasEffectiveAnnotation(NULLABLE) - || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) - || type.hasEffectiveAnnotation(POLYNULL)) { - checker.reportError(tree, "nullness.on.new.object"); - } - return super.visitNewClass(tree, p); - } - - @Override - public Void visitWhileLoop(WhileLoopTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitWhileLoop(tree, p); - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - return super.visitDoWhileLoop(tree, p); - } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - checkForNullability(tree.getCondition(), CONDITION_NULLABLE); - // Note: Because of the hack in NullnessVisitor#commonAssignmentCheck, this code needs to - // use a copy of the types for the two invocations of #commonAssignmentCheck. These hacks - // should be undone, by properly resolving polymorphic qualifiers in the ATF/transfer. As - // this code is mostly duplicated from the super method, this code needs to be kept in sync. - AnnotatedTypeMirror condThen = atypeFactory.getAnnotatedType(tree); - AnnotatedTypeMirror condElse = condThen.deepCopy(); - this.commonAssignmentCheck(condThen, tree.getTrueExpression(), "conditional.type.incompatible"); - this.commonAssignmentCheck( - condElse, tree.getFalseExpression(), "conditional.type.incompatible"); - // Avoid calling super, as the super method does not handle conditional branches correctly. - // Instead, manually implement the logic from TreeScanner#visitConditionalExpression to - // traverse the subtree. - Void r = scan(tree.getCondition(), p); - r = reduce(scan(tree.getTrueExpression(), p), r); - r = reduce(scan(tree.getFalseExpression(), p), r); - return r; - } - - @Override - protected void checkExceptionParameter(CatchTree tree) { - VariableTree param = tree.getParameter(); - List annoTrees = param.getModifiers().getAnnotations(); - Tree paramType = param.getType(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, paramType)) { - // This is a warning rather than an error because writing `@Nullable` could make sense - // if the catch block re-assigns the variable to null. (That would be bad style.) - checker.reportWarning(param, "nullness.on.exception.parameter"); - } - - // Don't call super. - // BasetypeVisitor forces annotations on exception parameters to be top, but because - // exceptions can never be null, the Nullness Checker does not require this check. - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - // All annotation arguments are non-null and initialized, so no need to check them. - return null; - } - - @Override - public void visitAnnotatedType( - @Nullable List annoTrees, Tree typeTree) { - // Look for a MEMBER_SELECT or PRIMITIVE within the type. - Tree t = typeTree; - while (t != null) { - switch (t.getKind()) { - case MEMBER_SELECT: - Tree expr = ((MemberSelectTree) t).getExpression(); - if (atypeFactory.containsNullnessAnnotation(annoTrees, expr)) { - checker.reportError(expr, "nullness.on.outer"); - } - t = null; - break; - case PRIMITIVE_TYPE: - if (atypeFactory.containsNullnessAnnotation(annoTrees, t)) { - checker.reportError(t, "nullness.on.primitive"); - } - t = null; - break; - case ANNOTATED_TYPE: - AnnotatedTypeTree at = ((AnnotatedTypeTree) t); - Tree underlying = at.getUnderlyingType(); - if (underlying.getKind() == Tree.Kind.PRIMITIVE_TYPE) { - if (atypeFactory.containsNullnessAnnotation(null, at)) { - checker.reportError(t, "nullness.on.primitive"); + } + + /** + * Reports an error if a comparison of a @NonNull expression with the null literal is performed. + * Does nothing unless {@code -Alint=redundantNullComparison} is passed on the command line. + * + * @param tree a tree that might be a comparison of a @NonNull expression with the null literal + */ + protected void checkForRedundantTests(BinaryTree tree) { + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); + + // respect command-line option + if (!redundantNullComparison) { + return; + } + + // equality tests + if ((tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO)) { + AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); + AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); + if (leftOp.getKind() == Tree.Kind.NULL_LITERAL + && right.hasEffectiveAnnotation(NONNULL)) { + checker.reportWarning(tree, "nulltest.redundant", rightOp.toString()); + } else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL + && left.hasEffectiveAnnotation(NONNULL)) { + checker.reportWarning(tree, "nulltest.redundant", leftOp.toString()); + } + } + } + + /** Case 6: Check for redundant nullness tests Case 7: unboxing case: primitive operations. */ + @Override + public Void visitBinary(BinaryTree tree, Void p) { + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); + + if (isUnboxingOperation(tree)) { + checkForNullability(leftOp, UNBOXING_OF_NULLABLE); + checkForNullability(rightOp, UNBOXING_OF_NULLABLE); + } + + checkForRedundantTests(tree); + + return super.visitBinary(tree, p); + } + + /** Case 7: unboxing case: primitive operation. */ + @Override + public Void visitUnary(UnaryTree tree, Void p) { + checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); + return super.visitUnary(tree, p); + } + + /** Case 7: unboxing case: primitive operation. */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + // ignore String concatenation + if (!isString(tree)) { + checkForNullability(tree.getVariable(), UNBOXING_OF_NULLABLE); + checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE); + } + return super.visitCompoundAssignment(tree, p); + } + + /** Case 7: unboxing case: casting to a primitive. */ + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + if (isPrimitive(tree) && !isPrimitive(tree.getExpression())) { + if (!checkForNullability(tree.getExpression(), UNBOXING_OF_NULLABLE)) { + // If unboxing of nullable is issued, don't issue any other errors. + return null; + } + } + return super.visitTypeCast(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + VariableTree receiver = tree.getReceiverParameter(); + + if (TreeUtils.isConstructor(tree)) { + // Constructor results are always @NonNull. Any annotations are forbidden. + List annoTrees = tree.getModifiers().getAnnotations(); + if (atypeFactory.containsNullnessAnnotation(annoTrees)) { + checker.reportError(tree, "nullness.on.constructor"); + } + } + + if (receiver != null) { + List annoTrees = receiver.getModifiers().getAnnotations(); + Tree type = receiver.getType(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { + checker.reportError(tree, "nullness.on.receiver"); } - t = null; - } else { - t = underlying; - } - break; - case ARRAY_TYPE: - t = ((ArrayTypeTree) t).getType(); - break; - case PARAMETERIZED_TYPE: - t = ((ParameterizedTypeTree) t).getType(); - break; - default: - t = null; - break; - } - } - - super.visitAnnotatedType(annoTrees, typeTree); - } - - @Override - protected TypeValidator createTypeValidator() { - return new NullnessValidator(checker, this, atypeFactory); - } - - /** - * Check that primitive types are annotated with {@code @NonNull} even if they are the type of a - * local variable. - */ - private static class NullnessValidator extends BaseTypeValidator { + } + + return super.visitMethod(tree, p); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (!permitClearProperty) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + if (TreeUtils.isMethodInvocation(tree, systemClearProperty, env)) { + String literal = literalFirstArgument(tree); + if (literal == null + || SystemGetPropertyHandler.predefinedSystemProperties.contains(literal)) { + checker.reportError(tree, "clear.system.property"); + } + } + if (TreeUtils.isMethodInvocation(tree, systemSetProperties, env)) { + checker.reportError(tree, "clear.system.property"); + } + } + return super.visitMethodInvocation(tree, p); + } /** - * Create NullnessValidator. + * If the first argument of a method call is a literal, return it; otherwise return null. * - * @param checker checker - * @param visitor visitor - * @param atypeFactory factory + * @param tree a method invocation whose first formal parameter is of String type + * @return the first argument if it is a literal, otherwise null */ - public NullnessValidator( - BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); + /*package-private*/ static @Nullable String literalFirstArgument(MethodInvocationTree tree) { + List args = tree.getArguments(); + assert args.size() > 0; + ExpressionTree arg = args.get(0); + if (arg.getKind() == Tree.Kind.STRING_LITERAL) { + String literal = (String) ((LiteralTree) arg).getValue(); + return literal; + } + return null; } @Override - protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( - AnnotatedTypeMirror type, Tree tree) { - if (type.getKind().isPrimitive()) { + public void processClassTree(ClassTree classTree) { + Tree extendsClause = classTree.getExtendsClause(); + if (extendsClause != null) { + reportErrorIfSupertypeContainsNullnessAnnotation(extendsClause); + } + for (Tree implementsClause : classTree.getImplementsClause()) { + reportErrorIfSupertypeContainsNullnessAnnotation(implementsClause); + } + + if (classTree.getKind() == Tree.Kind.ENUM) { + for (Tree member : classTree.getMembers()) { + if (member.getKind() == Tree.Kind.VARIABLE + && TreeUtils.elementFromDeclaration((VariableTree) member).getKind() + == ElementKind.ENUM_CONSTANT) { + VariableTree varDecl = (VariableTree) member; + List annoTrees = + varDecl.getModifiers().getAnnotations(); + Tree type = varDecl.getType(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, type)) { + checker.reportError(member, "nullness.on.enum"); + } + } + } + } + + super.processClassTree(classTree); + } + + /** + * Report "nullness.on.supertype" error if a supertype has a nullness annotation. + * + * @param typeTree a supertype tree, from an {@code extends} or {@code implements} clause + */ + private void reportErrorIfSupertypeContainsNullnessAnnotation(Tree typeTree) { + if (typeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + List annoTrees = + ((AnnotatedTypeTree) typeTree).getAnnotations(); + if (atypeFactory.containsNullnessAnnotation(annoTrees)) { + checker.reportError(typeTree, "nullness.on.supertype"); + } + } + } + + // ///////////// Utility methods ////////////////////////////// + + /** + * Issues the error message if the type of the tree is not of a {@link NonNull} type. + * + * @param tree the tree where the error is to reported + * @param errMsg the error message (must be {@link CompilerMessageKey}) + * @return whether or not the check succeeded + */ + private boolean checkForNullability(ExpressionTree tree, @CompilerMessageKey String errMsg) { + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + return checkForNullability(type, tree, errMsg); + } + + /** + * Issues the error message if an expression with this type may be null. + * + * @param type annotated type + * @param tree the tree where the error is to reported + * @param errMsg the error message (must be {@link CompilerMessageKey}) + * @return whether or not the check succeeded + */ + private boolean checkForNullability( + AnnotatedTypeMirror type, Tree tree, @CompilerMessageKey String errMsg) { + if (!type.hasEffectiveAnnotation(NONNULL)) { + checker.reportError(tree, errMsg, tree); + return false; + } return true; - } - return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); } - } + + @Override + protected void checkMethodInvocability( + AnnotatedExecutableType method, MethodInvocationTree tree) { + if (method.getReceiverType() == null) { + // Static methods don't have a receiver to check. + return; + } + + if (!TreeUtils.isSelfAccess(tree) + && + // Static methods don't have a receiver + method.getReceiverType() != null) { + // TODO: should all or some constructors be excluded? + // method.getElement().getKind() != ElementKind.CONSTRUCTOR) { + AnnotationMirrorSet receiverAnnos = atypeFactory.getReceiverType(tree).getAnnotations(); + AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); + AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); + AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); + treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); + // If receiver is Nullable, then we don't want to issue a warning about method + // invocability (we'd rather have only the "dereference.of.nullable" message). + if (treeReceiver.hasAnnotation(NULLABLE) + || receiverAnnos.contains(MONOTONIC_NONNULL) + || treeReceiver.hasAnnotation(POLYNULL)) { + return; + } + } + super.checkMethodInvocability(method, tree); + } + + /** + * Returns true if the binary operation could cause an unboxing operation. + * + * @param tree a binary operation + * @return true if the binary operation could cause an unboxing operation + */ + private boolean isUnboxingOperation(BinaryTree tree) { + if (tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { + // it is valid to check equality between two reference types, even + // if one (or both) of them is null + return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand()); + } else { + // All BinaryTree's are of type String, a primitive type or the reference type + // equivalent of a primitive type. Furthermore, Strings don't have a primitive type, and + // therefore only BinaryTrees that aren't String can cause unboxing. + return !isString(tree); + } + } + + /** + * Returns true if the type of the tree is a super of String. + * + * @param tree a tree + * @return true if the type of the tree is a super of String + */ + private boolean isString(ExpressionTree tree) { + TypeMirror type = TreeUtils.typeOf(tree); + return types.isAssignable(stringType, type); + } + + /** + * Returns true if the type of the tree is a primitive. + * + * @param tree a tree + * @return true if the type of the tree is a primitive + */ + private static boolean isPrimitive(ExpressionTree tree) { + return TreeUtils.typeOf(tree).getKind().isPrimitive(); + } + + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + if (!TreeUtils.hasNullCaseLabel(tree)) { + checkForNullability(tree.getExpression(), SWITCHING_NULLABLE); + } + if (redundantNullComparison && TreeUtils.isEnhancedSwitchStatement(tree)) { + ExpressionTree expression = tree.getExpression(); + List cases = tree.getCases(); + checkSwitchNullRedundant(expression, cases); + } + return super.visitSwitch(tree, p); + } + + @Override + public void visitSwitchExpression17(Tree switchExprTree) { + if (!TreeUtils.hasNullCaseLabel(switchExprTree)) { + checkForNullability( + SwitchExpressionUtils.getExpression(switchExprTree), SWITCHING_NULLABLE); + } + if (redundantNullComparison) { + ExpressionTree expression = SwitchExpressionUtils.getExpression(switchExprTree); + List cases = SwitchExpressionUtils.getCases(switchExprTree); + checkSwitchNullRedundant(expression, cases); + } + super.visitSwitchExpression17(switchExprTree); + } + + /** + * Reports a warning if the expression of the switch statement or expression is {@code @NonNull} + * and one of the case labels in the case trees is the null literal. + * + * @param expression the expression of the switch statement or expression + * @param cases the cases of the switch statement or expression + */ + private void checkSwitchNullRedundant( + ExpressionTree expression, List cases) { + AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(expression); + if (!switchType.hasEffectiveAnnotation(NONNULL)) { + return; + } + outer: + for (CaseTree caseTree : cases) { + List caseLabels = TreeUtilsAfterJava11.CaseUtils.getLabels(caseTree); + for (Tree caseLabel : caseLabels) { + if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL) { + checker.reportWarning(caseLabel, "nulltest.redundant", expression.toString()); + break outer; + } + } + } + } + + @Override + public Void visitForLoop(ForLoopTree tree, Void p) { + if (tree.getCondition() != null) { + // Condition is null e.g. in "for (;;) {...}" + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + } + return super.visitForLoop(tree, p); + } + + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + ExpressionTree enclosingExpr = tree.getEnclosingExpression(); + if (enclosingExpr != null) { + checkForNullability(enclosingExpr, DEREFERENCE_OF_NULLABLE); + } + + AnnotatedTypeMirror.AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); + if (type.hasEffectiveAnnotation(NULLABLE) + || type.hasEffectiveAnnotation(MONOTONIC_NONNULL) + || type.hasEffectiveAnnotation(POLYNULL)) { + checker.reportError(tree, "nullness.on.new.object"); + } + return super.visitNewClass(tree, p); + } + + @Override + public Void visitWhileLoop(WhileLoopTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitWhileLoop(tree, p); + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + return super.visitDoWhileLoop(tree, p); + } + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + checkForNullability(tree.getCondition(), CONDITION_NULLABLE); + // Note: Because of the hack in NullnessVisitor#commonAssignmentCheck, this code needs to + // use a copy of the types for the two invocations of #commonAssignmentCheck. These hacks + // should be undone, by properly resolving polymorphic qualifiers in the ATF/transfer. As + // this code is mostly duplicated from the super method, this code needs to be kept in sync. + AnnotatedTypeMirror condThen = atypeFactory.getAnnotatedType(tree); + AnnotatedTypeMirror condElse = condThen.deepCopy(); + this.commonAssignmentCheck( + condThen, tree.getTrueExpression(), "conditional.type.incompatible"); + this.commonAssignmentCheck( + condElse, tree.getFalseExpression(), "conditional.type.incompatible"); + // Avoid calling super, as the super method does not handle conditional branches correctly. + // Instead, manually implement the logic from TreeScanner#visitConditionalExpression to + // traverse the subtree. + Void r = scan(tree.getCondition(), p); + r = reduce(scan(tree.getTrueExpression(), p), r); + r = reduce(scan(tree.getFalseExpression(), p), r); + return r; + } + + @Override + protected void checkExceptionParameter(CatchTree tree) { + VariableTree param = tree.getParameter(); + List annoTrees = param.getModifiers().getAnnotations(); + Tree paramType = param.getType(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, paramType)) { + // This is a warning rather than an error because writing `@Nullable` could make sense + // if the catch block re-assigns the variable to null. (That would be bad style.) + checker.reportWarning(param, "nullness.on.exception.parameter"); + } + + // Don't call super. + // BasetypeVisitor forces annotations on exception parameters to be top, but because + // exceptions can never be null, the Nullness Checker does not require this check. + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + // All annotation arguments are non-null and initialized, so no need to check them. + return null; + } + + @Override + public void visitAnnotatedType( + @Nullable List annoTrees, Tree typeTree) { + // Look for a MEMBER_SELECT or PRIMITIVE within the type. + Tree t = typeTree; + while (t != null) { + switch (t.getKind()) { + case MEMBER_SELECT: + Tree expr = ((MemberSelectTree) t).getExpression(); + if (atypeFactory.containsNullnessAnnotation(annoTrees, expr)) { + checker.reportError(expr, "nullness.on.outer"); + } + t = null; + break; + case PRIMITIVE_TYPE: + if (atypeFactory.containsNullnessAnnotation(annoTrees, t)) { + checker.reportError(t, "nullness.on.primitive"); + } + t = null; + break; + case ANNOTATED_TYPE: + AnnotatedTypeTree at = ((AnnotatedTypeTree) t); + Tree underlying = at.getUnderlyingType(); + if (underlying.getKind() == Tree.Kind.PRIMITIVE_TYPE) { + if (atypeFactory.containsNullnessAnnotation(null, at)) { + checker.reportError(t, "nullness.on.primitive"); + } + t = null; + } else { + t = underlying; + } + break; + case ARRAY_TYPE: + t = ((ArrayTypeTree) t).getType(); + break; + case PARAMETERIZED_TYPE: + t = ((ParameterizedTypeTree) t).getType(); + break; + default: + t = null; + break; + } + } + + super.visitAnnotatedType(annoTrees, typeTree); + } + + @Override + protected TypeValidator createTypeValidator() { + return new NullnessValidator(checker, this, atypeFactory); + } + + /** + * Check that primitive types are annotated with {@code @NonNull} even if they are the type of a + * local variable. + */ + private static class NullnessValidator extends BaseTypeValidator { + + /** + * Create NullnessValidator. + * + * @param checker checker + * @param visitor visitor + * @param atypeFactory factory + */ + public NullnessValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } + + @Override + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( + AnnotatedTypeMirror type, Tree tree) { + if (type.getKind().isPrimitive()) { + return true; + } + return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); + } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java b/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java index bfa4276dd8e..346e008e896 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/SystemGetPropertyHandler.java @@ -1,14 +1,17 @@ package org.checkerframework.checker.nullness; import com.sun.source.tree.MethodInvocationTree; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.TreeUtils; + import java.util.Arrays; import java.util.Collection; import java.util.HashSet; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.TreeUtils; /** * Utility class for handling {@link java.lang.System#getProperty(String)} and related invocations. @@ -19,108 +22,108 @@ */ public class SystemGetPropertyHandler { - /** - * If true, client code may clear system properties, and this class (SystemGetPropertyHandler) has - * no effect. - */ - private final boolean permitClearProperty; + /** + * If true, client code may clear system properties, and this class (SystemGetPropertyHandler) + * has no effect. + */ + private final boolean permitClearProperty; - /** The processing environment. */ - private final ProcessingEnvironment env; + /** The processing environment. */ + private final ProcessingEnvironment env; - /** The factory for constructing and looking up types. */ - private final NullnessNoInitAnnotatedTypeFactory factory; + /** The factory for constructing and looking up types. */ + private final NullnessNoInitAnnotatedTypeFactory factory; - /** The System.getProperty(String) method. */ - private final ExecutableElement systemGetProperty; + /** The System.getProperty(String) method. */ + private final ExecutableElement systemGetProperty; - /** The System.setProperty(String) method. */ - private final ExecutableElement systemSetProperty; + /** The System.setProperty(String) method. */ + private final ExecutableElement systemSetProperty; - /** - * System properties that are defined at startup on every JVM. - * - *

          This list is from the Javadoc of System.getProperties, for Java 17. - */ - public static final Collection predefinedSystemProperties = - new HashSet<>( - Arrays.asList( - "java.version", - "java.version.date", - "java.vendor", - "java.vendor.url", - "java.vendor.version", - "java.home", - "java.vm.specification.version", - "java.vm.specification.vendor", - "java.vm.specification.name", - "java.vm.version", - "java.vm.vendor", - "java.vm.name", - "java.specification.version", - "java.specification.maintenance.version", - "java.specification.vendor", - "java.specification.name", - "java.class.version", - "java.class.path", - "java.library.path", - "java.io.tmpdir", - "java.compiler", - "os.name", - "os.arch", - "os.version", - "file.separator", - "path.separator", - "line.separator", - "user.name", - "user.home", - "user.dir", - "native.encoding", - "stdout.encoding", - "stderr.encoding", - "jdk.module.path", - "jdk.module.upgrade.path", - "jdk.module.main", - "jdk.module.main.class", - "file.encoding")); + /** + * System properties that are defined at startup on every JVM. + * + *

          This list is from the Javadoc of System.getProperties, for Java 17. + */ + public static final Collection predefinedSystemProperties = + new HashSet<>( + Arrays.asList( + "java.version", + "java.version.date", + "java.vendor", + "java.vendor.url", + "java.vendor.version", + "java.home", + "java.vm.specification.version", + "java.vm.specification.vendor", + "java.vm.specification.name", + "java.vm.version", + "java.vm.vendor", + "java.vm.name", + "java.specification.version", + "java.specification.maintenance.version", + "java.specification.vendor", + "java.specification.name", + "java.class.version", + "java.class.path", + "java.library.path", + "java.io.tmpdir", + "java.compiler", + "os.name", + "os.arch", + "os.version", + "file.separator", + "path.separator", + "line.separator", + "user.name", + "user.home", + "user.dir", + "native.encoding", + "stdout.encoding", + "stderr.encoding", + "jdk.module.path", + "jdk.module.upgrade.path", + "jdk.module.main", + "jdk.module.main.class", + "file.encoding")); - /** - * Creates a SystemGetPropertyHandler. - * - * @param env the processing environment - * @param factory the factory for constructing and looking up types - * @param permitClearProperty if true, client code may clear system properties, and this object - * does nothing - */ - public SystemGetPropertyHandler( - ProcessingEnvironment env, - NullnessNoInitAnnotatedTypeFactory factory, - boolean permitClearProperty) { - this.env = env; - this.factory = factory; - this.permitClearProperty = permitClearProperty; + /** + * Creates a SystemGetPropertyHandler. + * + * @param env the processing environment + * @param factory the factory for constructing and looking up types + * @param permitClearProperty if true, client code may clear system properties, and this object + * does nothing + */ + public SystemGetPropertyHandler( + ProcessingEnvironment env, + NullnessNoInitAnnotatedTypeFactory factory, + boolean permitClearProperty) { + this.env = env; + this.factory = factory; + this.permitClearProperty = permitClearProperty; - systemGetProperty = TreeUtils.getMethod("java.lang.System", "getProperty", 1, env); - systemSetProperty = TreeUtils.getMethod("java.lang.System", "setProperty", 2, env); - } - - /** - * Apply rules regarding System.getProperty and related methods. - * - * @param tree a method invocation - * @param method the method being invoked - */ - public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { - if (permitClearProperty) { - return; + systemGetProperty = TreeUtils.getMethod("java.lang.System", "getProperty", 1, env); + systemSetProperty = TreeUtils.getMethod("java.lang.System", "setProperty", 2, env); } - if (TreeUtils.isMethodInvocation(tree, systemGetProperty, env) - || TreeUtils.isMethodInvocation(tree, systemSetProperty, env)) { - String literal = NullnessNoInitVisitor.literalFirstArgument(tree); - if (literal != null && predefinedSystemProperties.contains(literal)) { - AnnotatedTypeMirror type = method.getReturnType(); - type.replaceAnnotation(factory.NONNULL); - } + + /** + * Apply rules regarding System.getProperty and related methods. + * + * @param tree a method invocation + * @param method the method being invoked + */ + public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { + if (permitClearProperty) { + return; + } + if (TreeUtils.isMethodInvocation(tree, systemGetProperty, env) + || TreeUtils.isMethodInvocation(tree, systemSetProperty, env)) { + String literal = NullnessNoInitVisitor.literalFirstArgument(tree); + if (literal != null && predefinedSystemProperties.contains(literal)) { + AnnotatedTypeMirror type = method.getReturnType(); + type.replaceAnnotation(factory.NONNULL); + } + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java index 951d4f4551a..52142d042dd 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalAnnotatedTypeFactory.java @@ -5,11 +5,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; -import java.util.Collection; -import java.util.function.Function; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -21,114 +17,122 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.TreeUtils; -/** OptionalAnnotatedTypeFactory for the Optional Checker. */ -public class OptionalAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { +import java.util.Collection; +import java.util.function.Function; - /** The element for java.util.Optional.map(). */ - private final ExecutableElement optionalMap; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; - /** The @{@link Present} annotation. */ - protected final AnnotationMirror PRESENT = AnnotationBuilder.fromClass(elements, Present.class); +/** OptionalAnnotatedTypeFactory for the Optional Checker. */ +public class OptionalAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** - * Creates an OptionalAnnotatedTypeFactory. - * - * @param checker the Optional Checker associated with this type factory - */ - public OptionalAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - optionalMap = TreeUtils.getMethodOrNull("java.util.Optional", "map", 1, getProcessingEnv()); - postInit(); - } + /** The element for java.util.Optional.map(). */ + private final ExecutableElement optionalMap; - @Override - public AnnotatedTypeMirror getAnnotatedType(Tree tree) { - AnnotatedTypeMirror result = super.getAnnotatedType(tree); - optionalMapNonNull(tree, result); - return result; - } + /** The @{@link Present} annotation. */ + protected final AnnotationMirror PRESENT = AnnotationBuilder.fromClass(elements, Present.class); - /** - * If {@code tree} is a call to {@link java.util.Optional#map(Function)} whose argument is a - * method reference, then this method adds {@code @Present} to {@code type} if the following is - * true: - * - *

            - *
          • The type of the receiver to {@link java.util.Optional#map(Function)} is {@code @Present}, - * and - *
          • {@link #returnHasNullable(MemberReferenceTree)} returns false. - *
          - * - * @param tree a tree - * @param type the type of the tree, which may be side-effected by this method - */ - private void optionalMapNonNull(Tree tree, AnnotatedTypeMirror type) { - if (!TreeUtils.isMethodInvocation(tree, optionalMap, processingEnv)) { - return; + /** + * Creates an OptionalAnnotatedTypeFactory. + * + * @param checker the Optional Checker associated with this type factory + */ + public OptionalAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + optionalMap = TreeUtils.getMethodOrNull("java.util.Optional", "map", 1, getProcessingEnv()); + postInit(); } - MethodInvocationTree mapTree = (MethodInvocationTree) tree; - ExpressionTree argTree = mapTree.getArguments().get(0); - if (argTree.getKind() == Kind.MEMBER_REFERENCE) { - MemberReferenceTree memberReferenceTree = (MemberReferenceTree) argTree; - AnnotatedTypeMirror optType = getReceiverType(mapTree); - if (optType == null || !optType.hasEffectiveAnnotation(Present.class)) { - return; - } - if (!returnHasNullable(memberReferenceTree)) { - // The method still could have a @PolyNull on the return and might return null. - // If @PolyNull is the primary annotation on the parameter and not on any type - // arguments or array elements, then it is still safe to mark the optional type as - // present. - // TODO: Add the check for poly null on arguments. - type.replaceAnnotation(PRESENT); - } - } - } - /** - * Returns true if the return type of the function type of {@code memberReferenceTree} is - * annotated with {@code @Nullable}. - * - * @param memberReferenceTree a member reference - * @return true if the return type of the function type of {@code memberReferenceTree} is - * annotated with {@code @Nullable} - */ - private boolean returnHasNullable(MemberReferenceTree memberReferenceTree) { - if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree) - .isConstructorReference()) { - return false; + @Override + public AnnotatedTypeMirror getAnnotatedType(Tree tree) { + AnnotatedTypeMirror result = super.getAnnotatedType(tree); + optionalMapNonNull(tree, result); + return result; } - ExecutableElement memberReferenceFuncType = TreeUtils.elementFromUse(memberReferenceTree); - if (memberReferenceFuncType.getEnclosingElement().getKind() == ElementKind.ANNOTATION_TYPE) { - // Annotation element accessor are always non-null; - return false; + + /** + * If {@code tree} is a call to {@link java.util.Optional#map(Function)} whose argument is a + * method reference, then this method adds {@code @Present} to {@code type} if the following is + * true: + * + *
            + *
          • The type of the receiver to {@link java.util.Optional#map(Function)} is + * {@code @Present}, and + *
          • {@link #returnHasNullable(MemberReferenceTree)} returns false. + *
          + * + * @param tree a tree + * @param type the type of the tree, which may be side-effected by this method + */ + private void optionalMapNonNull(Tree tree, AnnotatedTypeMirror type) { + if (!TreeUtils.isMethodInvocation(tree, optionalMap, processingEnv)) { + return; + } + MethodInvocationTree mapTree = (MethodInvocationTree) tree; + ExpressionTree argTree = mapTree.getArguments().get(0); + if (argTree.getKind() == Kind.MEMBER_REFERENCE) { + MemberReferenceTree memberReferenceTree = (MemberReferenceTree) argTree; + AnnotatedTypeMirror optType = getReceiverType(mapTree); + if (optType == null || !optType.hasEffectiveAnnotation(Present.class)) { + return; + } + if (!returnHasNullable(memberReferenceTree)) { + // The method still could have a @PolyNull on the return and might return null. + // If @PolyNull is the primary annotation on the parameter and not on any type + // arguments or array elements, then it is still safe to mark the optional type as + // present. + // TODO: Add the check for poly null on arguments. + type.replaceAnnotation(PRESENT); + } + } } - if (!checker.hasOption("optionalMapAssumeNonNull")) { - return true; + /** + * Returns true if the return type of the function type of {@code memberReferenceTree} is + * annotated with {@code @Nullable}. + * + * @param memberReferenceTree a member reference + * @return true if the return type of the function type of {@code memberReferenceTree} is + * annotated with {@code @Nullable} + */ + private boolean returnHasNullable(MemberReferenceTree memberReferenceTree) { + if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree) + .isConstructorReference()) { + return false; + } + ExecutableElement memberReferenceFuncType = TreeUtils.elementFromUse(memberReferenceTree); + if (memberReferenceFuncType.getEnclosingElement().getKind() + == ElementKind.ANNOTATION_TYPE) { + // Annotation element accessor are always non-null; + return false; + } + + if (!checker.hasOption("optionalMapAssumeNonNull")) { + return true; + } + return containsNullable(memberReferenceFuncType.getAnnotationMirrors()) + || containsNullable(memberReferenceFuncType.getReturnType().getAnnotationMirrors()); } - return containsNullable(memberReferenceFuncType.getAnnotationMirrors()) - || containsNullable(memberReferenceFuncType.getReturnType().getAnnotationMirrors()); - } - /** - * Returns true if {@code annos} contains a nullable annotation. - * - * @param annos a collection of annotations - * @return true if {@code annos} contains a nullable annotation - */ - private boolean containsNullable(Collection annos) { - for (AnnotationMirror anno : annos) { - if (anno.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) { - return true; - } + /** + * Returns true if {@code annos} contains a nullable annotation. + * + * @param annos a collection of annotations + * @return true if {@code annos} contains a nullable annotation + */ + private boolean containsNullable(Collection annos) { + for (AnnotationMirror anno : annos) { + if (anno.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) { + return true; + } + } + return false; } - return false; - } - @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new OptionalTransfer(analysis); - } + @Override + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new OptionalTransfer(analysis); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java index 6604299464d..f6b382f644a 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.optional; -import java.util.Optional; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedOptions; +import java.util.Optional; + /** * A type-checker that prevents misuse of the {@link java.util.Optional} class. * @@ -17,6 +18,6 @@ @StubFiles({"javaparser.astub"}) @SupportedOptions("optionalMapAssumeNonNull") public class OptionalChecker extends BaseTypeChecker { - /** Create an OptionalChecker. */ - public OptionalChecker() {} + /** Create an OptionalChecker. */ + public OptionalChecker() {} } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java index de521118adc..ceb8574c7ff 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalTransfer.java @@ -7,11 +7,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.dataflow.cfg.UnderlyingAST; @@ -26,77 +22,87 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.TreeUtils; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** The transfer function for the Optional Checker. */ public class OptionalTransfer extends CFTransfer { - /** The @{@link Present} annotation. */ - private final AnnotationMirror PRESENT; + /** The @{@link Present} annotation. */ + private final AnnotationMirror PRESENT; - /** The element for java.util.Optional.ifPresent(). */ - private final ExecutableElement optionalIfPresent; + /** The element for java.util.Optional.ifPresent(). */ + private final ExecutableElement optionalIfPresent; - /** The element for java.util.Optional.ifPresentOrElse(), or null. */ - private final @Nullable ExecutableElement optionalIfPresentOrElse; + /** The element for java.util.Optional.ifPresentOrElse(), or null. */ + private final @Nullable ExecutableElement optionalIfPresentOrElse; - /** The type factory associated with this transfer function. */ - private final AnnotatedTypeFactory atypeFactory; + /** The type factory associated with this transfer function. */ + private final AnnotatedTypeFactory atypeFactory; - /** - * Create an OptionalTransfer. - * - * @param analysis the Optional Checker instance - */ - public OptionalTransfer(CFAbstractAnalysis analysis) { - super(analysis); - atypeFactory = analysis.getTypeFactory(); - Elements elements = atypeFactory.getElementUtils(); - PRESENT = AnnotationBuilder.fromClass(elements, Present.class); - ProcessingEnvironment env = atypeFactory.getProcessingEnv(); - optionalIfPresent = TreeUtils.getMethod("java.util.Optional", "ifPresent", 1, env); - optionalIfPresentOrElse = - TreeUtils.getMethodOrNull("java.util.Optional", "ifPresentOrElse", 2, env); - } + /** + * Create an OptionalTransfer. + * + * @param analysis the Optional Checker instance + */ + public OptionalTransfer(CFAbstractAnalysis analysis) { + super(analysis); + atypeFactory = analysis.getTypeFactory(); + Elements elements = atypeFactory.getElementUtils(); + PRESENT = AnnotationBuilder.fromClass(elements, Present.class); + ProcessingEnvironment env = atypeFactory.getProcessingEnv(); + optionalIfPresent = TreeUtils.getMethod("java.util.Optional", "ifPresent", 1, env); + optionalIfPresentOrElse = + TreeUtils.getMethodOrNull("java.util.Optional", "ifPresentOrElse", 2, env); + } - @Override - public CFStore initialStore(UnderlyingAST underlyingAST, List parameters) { + @Override + public CFStore initialStore(UnderlyingAST underlyingAST, List parameters) { - CFStore result = super.initialStore(underlyingAST, parameters); + CFStore result = super.initialStore(underlyingAST, parameters); - if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { - // Check whether this lambda is an argument to `Optional.ifPresent()` or - // `Optional.ifPresentOrElse()`. If so, then within the lambda, the receiver of the - // `ifPresent*` method is @Present. - CFGLambda cfgLambda = (CFGLambda) underlyingAST; - LambdaExpressionTree lambdaTree = cfgLambda.getLambdaTree(); - List lambdaParams = lambdaTree.getParameters(); - if (lambdaParams.size() == 1) { - TreePath lambdaPath = atypeFactory.getPath(lambdaTree); - Tree lambdaParent = lambdaPath.getParentPath().getLeaf(); - if (lambdaParent.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree invok = (MethodInvocationTree) lambdaParent; - ExecutableElement methodElt = TreeUtils.elementFromUse(invok); - if (methodElt.equals(optionalIfPresent) || methodElt.equals(optionalIfPresentOrElse)) { - // `underlyingAST` is an invocation of `Optional.ifPresent()` or - // `Optional.ifPresentOrElse()`. In the lambda, the receiver is @Present. - ExpressionTree methodSelectTree = TreeUtils.withoutParens(invok.getMethodSelect()); - ExpressionTree receiverTree = ((MemberSelectTree) methodSelectTree).getExpression(); - JavaExpression receiverJe = JavaExpression.fromTree(receiverTree); - result.insertValue(receiverJe, PRESENT); - } + if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { + // Check whether this lambda is an argument to `Optional.ifPresent()` or + // `Optional.ifPresentOrElse()`. If so, then within the lambda, the receiver of the + // `ifPresent*` method is @Present. + CFGLambda cfgLambda = (CFGLambda) underlyingAST; + LambdaExpressionTree lambdaTree = cfgLambda.getLambdaTree(); + List lambdaParams = lambdaTree.getParameters(); + if (lambdaParams.size() == 1) { + TreePath lambdaPath = atypeFactory.getPath(lambdaTree); + Tree lambdaParent = lambdaPath.getParentPath().getLeaf(); + if (lambdaParent.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree invok = (MethodInvocationTree) lambdaParent; + ExecutableElement methodElt = TreeUtils.elementFromUse(invok); + if (methodElt.equals(optionalIfPresent) + || methodElt.equals(optionalIfPresentOrElse)) { + // `underlyingAST` is an invocation of `Optional.ifPresent()` or + // `Optional.ifPresentOrElse()`. In the lambda, the receiver is @Present. + ExpressionTree methodSelectTree = + TreeUtils.withoutParens(invok.getMethodSelect()); + ExpressionTree receiverTree = + ((MemberSelectTree) methodSelectTree).getExpression(); + JavaExpression receiverJe = JavaExpression.fromTree(receiverTree); + result.insertValue(receiverJe, PRESENT); + } + } + } } - } - } - // TODO: Similar logic to the above can be applied in the Nullness Checker. - // Some methods take a function as an argument, guaranteeing that, if the function is - // called: - // * the value passed to the function is non-null - // * some other argument to the method is non-null - // Examples: - // * Jodd's `StringUtil.ifNotNull()` - // * `Opt.ifPresent()` - // * `Opt.map()` + // TODO: Similar logic to the above can be applied in the Nullness Checker. + // Some methods take a function as an argument, guaranteeing that, if the function is + // called: + // * the value passed to the function is non-null + // * some other argument to the method is non-null + // Examples: + // * Jodd's `StringUtil.ifNotNull()` + // * `Opt.ifPresent()` + // * `Opt.map()` - return result; - } + return result; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java index 575375071ee..5a9a0827419 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java @@ -15,18 +15,7 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.optional.qual.OptionalCreator; @@ -44,6 +33,20 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.IPair; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * The OptionalVisitor enforces the Optional Checker rules. These rules are described in the Checker * Framework Manual. @@ -51,467 +54,446 @@ * @checker_framework.manual #optional-checker Optional Checker */ public class OptionalVisitor - extends BaseTypeVisitor { - - /** The Collection type. */ - private final TypeMirror collectionType; - - /** The element for java.util.Optional.get(). */ - private final ExecutableElement optionalGet; - - /** The element for java.util.Optional.isPresent(). */ - private final ExecutableElement optionalIsPresent; - - /** The element for java.util.Optional.isEmpty(), or null if running under JDK 8. */ - private final @Nullable ExecutableElement optionalIsEmpty; - - /** The element for java.util.stream.Stream.filter(). */ - private final ExecutableElement streamFilter; - - /** The element for java.util.stream.Stream.map(). */ - private final ExecutableElement streamMap; - - /** - * Create an OptionalVisitor. - * - * @param checker the associated OptionalChecker - */ - public OptionalVisitor(BaseTypeChecker checker) { - super(checker); - collectionType = types.erasure(TypesUtils.typeFromClass(Collection.class, types, elements)); - - ProcessingEnvironment env = checker.getProcessingEnvironment(); - optionalGet = TreeUtils.getMethod("java.util.Optional", "get", 0, env); - optionalIsPresent = TreeUtils.getMethod("java.util.Optional", "isPresent", 0, env); - optionalIsEmpty = TreeUtils.getMethodOrNull("java.util.Optional", "isEmpty", 0, env); - - streamFilter = TreeUtils.getMethod("java.util.stream.Stream", "filter", 1, env); - streamMap = TreeUtils.getMethod("java.util.stream.Stream", "map", 1, env); - } - - @Override - protected BaseTypeValidator createTypeValidator() { - return new OptionalTypeValidator(checker, this, atypeFactory); - } - - /** - * Returns true iff {@code expression} is a call to java.util.Optional.get. - * - * @param expression an expression - * @return true iff {@code expression} is a call to java.util.Optional.get - */ - private boolean isCallToGet(ExpressionTree expression) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - return TreeUtils.isMethodInvocation(expression, optionalGet, env); - } - - /** - * Is the expression a call to {@code isPresent} or {@code isEmpty}? If not, returns null. If so, - * returns a pair of (boolean, receiver expression). The boolean is true if the given expression - * is a call to {@code isPresent} and is false if the given expression is a call to {@code - * isEmpty}. - * - * @param expression an expression - * @return a pair of a boolean (indicating whether the expression is a call to {@code - * Optional.isPresent} or to {@code Optional.isEmpty}) and its receiver; or null if not a call - * to either of the methods - */ - private @Nullable IPair isCallToIsPresent( - ExpressionTree expression) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - boolean negate = false; - while (true) { - switch (expression.getKind()) { - case PARENTHESIZED: - expression = ((ParenthesizedTree) expression).getExpression(); - break; - case LOGICAL_COMPLEMENT: - expression = ((UnaryTree) expression).getExpression(); - negate = !negate; - break; - case METHOD_INVOCATION: - if (TreeUtils.isMethodInvocation(expression, optionalIsPresent, env)) { - return IPair.of(!negate, TreeUtils.getReceiverTree(expression)); - } else if (optionalIsEmpty != null - && TreeUtils.isMethodInvocation(expression, optionalIsEmpty, env)) { - return IPair.of(negate, TreeUtils.getReceiverTree(expression)); - } else { - return null; - } - default: - return null; - } - } - } - - /** - * Returns true iff the method being called is Optional creation: empty, of, ofNullable. - * - * @param methInvok a method invocation - * @return true iff the method being called is Optional creation: empty, of, ofNullable - */ - private boolean isOptionalCreation(MethodInvocationTree methInvok) { - ExecutableElement method = TreeUtils.elementFromUse(methInvok); - return atypeFactory.getDeclAnnotation(method, OptionalCreator.class) != null; - } - - /** - * Returns true iff the method being called is Optional propagation: filter, flatMap, map, or. - * - * @param methInvok a method invocation - * @return true true iff the method being called is Optional propagation: filter, flatMap, map, or - */ - private boolean isOptionalPropagation(MethodInvocationTree methInvok) { - ExecutableElement method = TreeUtils.elementFromUse(methInvok); - return atypeFactory.getDeclAnnotation(method, OptionalPropagator.class) != null; - } - - /** - * Returns true iff the method being called is Optional elimination: get, orElse, orElseGet, - * orElseThrow. - * - * @param methInvok a method invocation - * @return true iff the method being called is Optional elimination: get, orElse, orElseGet, - * orElseThrow - */ - private boolean isOptionalElimination(MethodInvocationTree methInvok) { - ExecutableElement method = TreeUtils.elementFromUse(methInvok); - return atypeFactory.getDeclAnnotation(method, OptionalEliminator.class) != null; - } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - handleTernaryIsPresentGet(tree); - return super.visitConditionalExpression(tree, p); - } - - /** - * Part of rule #3. - * - *

          Pattern match for: {@code VAR.isPresent() ? VAR.get().METHOD() : VALUE} - * - *

          Prefer: {@code VAR.map(METHOD).orElse(VALUE);} - * - * @param tree a conditional expression that can perhaps be simplified - */ - // TODO: Should handle this via a transfer function, instead of pattern-matching. - public void handleTernaryIsPresentGet(ConditionalExpressionTree tree) { - - ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); - IPair isPresentCall = isCallToIsPresent(condExpr); - if (isPresentCall == null) { - return; - } - ExpressionTree trueExpr = TreeUtils.withoutParens(tree.getTrueExpression()); - ExpressionTree falseExpr = TreeUtils.withoutParens(tree.getFalseExpression()); - if (!isPresentCall.first) { - ExpressionTree tmp = trueExpr; - trueExpr = falseExpr; - falseExpr = tmp; - } + extends BaseTypeVisitor { - if (trueExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { - return; - } - ExpressionTree trueReceiver = TreeUtils.getReceiverTree(trueExpr); - if (!isCallToGet(trueReceiver)) { - return; - } - ExpressionTree getReceiver = TreeUtils.getReceiverTree(trueReceiver); - - // What is a better way to do this than string comparison? - // Use transfer functions and Store entries. - ExpressionTree receiver = isPresentCall.second; - if (sameExpression(receiver, getReceiver)) { - ExecutableElement ele = TreeUtils.elementFromUse((MethodInvocationTree) trueExpr); - - checker.reportWarning( - tree, - "prefer.map.and.orelse", - receiver, - // The literal "CONTAININGCLASS::" is gross. - // TODO: add this to the error message. - // ElementUtils.getQualifiedClassName(ele); - ele.getSimpleName(), - falseExpr); - } - } - - /** - * Returns true if the two trees represent the same expression. - * - * @param tree1 the first tree - * @param tree2 the second tree - * @return true if the two trees represent the same expression - */ - private boolean sameExpression(ExpressionTree tree1, ExpressionTree tree2) { - JavaExpression r1 = JavaExpression.fromTree(tree1); - JavaExpression r2 = JavaExpression.fromTree(tree2); - if (r1 != null && !r1.containsUnknown() && r2 != null && !r2.containsUnknown()) { - return r1.equals(r2); - } else { - return tree1.toString().equals(tree2.toString()); - } - } - - @Override - public Void visitIf(IfTree tree, Void p) { - handleConditionalStatementIsPresentGet(tree); - return super.visitIf(tree, p); - } - - /** - * Part of rule #3. - * - *

          Pattern match for: {@code if (VAR.isPresent()) { METHOD(VAR.get()); }} - * - *

          Prefer: {@code VAR.ifPresent(METHOD);} - * - * @param tree an if statement that can perhaps be simplified - */ - public void handleConditionalStatementIsPresentGet(IfTree tree) { - - ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); - IPair isPresentCall = isCallToIsPresent(condExpr); - if (isPresentCall == null) { - return; + /** The Collection type. */ + private final TypeMirror collectionType; + + /** The element for java.util.Optional.get(). */ + private final ExecutableElement optionalGet; + + /** The element for java.util.Optional.isPresent(). */ + private final ExecutableElement optionalIsPresent; + + /** The element for java.util.Optional.isEmpty(), or null if running under JDK 8. */ + private final @Nullable ExecutableElement optionalIsEmpty; + + /** The element for java.util.stream.Stream.filter(). */ + private final ExecutableElement streamFilter; + + /** The element for java.util.stream.Stream.map(). */ + private final ExecutableElement streamMap; + + /** + * Create an OptionalVisitor. + * + * @param checker the associated OptionalChecker + */ + public OptionalVisitor(BaseTypeChecker checker) { + super(checker); + collectionType = types.erasure(TypesUtils.typeFromClass(Collection.class, types, elements)); + + ProcessingEnvironment env = checker.getProcessingEnvironment(); + optionalGet = TreeUtils.getMethod("java.util.Optional", "get", 0, env); + optionalIsPresent = TreeUtils.getMethod("java.util.Optional", "isPresent", 0, env); + optionalIsEmpty = TreeUtils.getMethodOrNull("java.util.Optional", "isEmpty", 0, env); + + streamFilter = TreeUtils.getMethod("java.util.stream.Stream", "filter", 1, env); + streamMap = TreeUtils.getMethod("java.util.stream.Stream", "map", 1, env); } - StatementTree thenStmt = skipBlocks(tree.getThenStatement()); - StatementTree elseStmt = skipBlocks(tree.getElseStatement()); - if (!isPresentCall.first) { - StatementTree tmp = thenStmt; - thenStmt = elseStmt; - elseStmt = tmp; + @Override + protected BaseTypeValidator createTypeValidator() { + return new OptionalTypeValidator(checker, this, atypeFactory); } - if (!(elseStmt == null - || (elseStmt.getKind() == Tree.Kind.BLOCK - && ((BlockTree) elseStmt).getStatements().isEmpty()))) { - // else block is missing or is an empty block: "{}" - return; + /** + * Returns true iff {@code expression} is a call to java.util.Optional.get. + * + * @param expression an expression + * @return true iff {@code expression} is a call to java.util.Optional.get + */ + private boolean isCallToGet(ExpressionTree expression) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + return TreeUtils.isMethodInvocation(expression, optionalGet, env); } - if (thenStmt.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { - return; + /** + * Is the expression a call to {@code isPresent} or {@code isEmpty}? If not, returns null. If + * so, returns a pair of (boolean, receiver expression). The boolean is true if the given + * expression is a call to {@code isPresent} and is false if the given expression is a call to + * {@code isEmpty}. + * + * @param expression an expression + * @return a pair of a boolean (indicating whether the expression is a call to {@code + * Optional.isPresent} or to {@code Optional.isEmpty}) and its receiver; or null if not a + * call to either of the methods + */ + private @Nullable IPair isCallToIsPresent( + ExpressionTree expression) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + boolean negate = false; + while (true) { + switch (expression.getKind()) { + case PARENTHESIZED: + expression = ((ParenthesizedTree) expression).getExpression(); + break; + case LOGICAL_COMPLEMENT: + expression = ((UnaryTree) expression).getExpression(); + negate = !negate; + break; + case METHOD_INVOCATION: + if (TreeUtils.isMethodInvocation(expression, optionalIsPresent, env)) { + return IPair.of(!negate, TreeUtils.getReceiverTree(expression)); + } else if (optionalIsEmpty != null + && TreeUtils.isMethodInvocation(expression, optionalIsEmpty, env)) { + return IPair.of(negate, TreeUtils.getReceiverTree(expression)); + } else { + return null; + } + default: + return null; + } + } } - ExpressionTree thenExpr = ((ExpressionStatementTree) thenStmt).getExpression(); - if (thenExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { - return; + + /** + * Returns true iff the method being called is Optional creation: empty, of, ofNullable. + * + * @param methInvok a method invocation + * @return true iff the method being called is Optional creation: empty, of, ofNullable + */ + private boolean isOptionalCreation(MethodInvocationTree methInvok) { + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalCreator.class) != null; } - MethodInvocationTree invok = (MethodInvocationTree) thenExpr; - List args = invok.getArguments(); - if (args.size() != 1) { - return; + + /** + * Returns true iff the method being called is Optional propagation: filter, flatMap, map, or. + * + * @param methInvok a method invocation + * @return true true iff the method being called is Optional propagation: filter, flatMap, map, + * or + */ + private boolean isOptionalPropagation(MethodInvocationTree methInvok) { + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalPropagator.class) != null; } - ExpressionTree arg = TreeUtils.withoutParens(args.get(0)); - if (!isCallToGet(arg)) { - return; + + /** + * Returns true iff the method being called is Optional elimination: get, orElse, orElseGet, + * orElseThrow. + * + * @param methInvok a method invocation + * @return true iff the method being called is Optional elimination: get, orElse, orElseGet, + * orElseThrow + */ + private boolean isOptionalElimination(MethodInvocationTree methInvok) { + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalEliminator.class) != null; } - ExpressionTree receiver = isPresentCall.second; - ExpressionTree getReceiver = TreeUtils.getReceiverTree(arg); - if (!receiver.toString().equals(getReceiver.toString())) { - return; + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + handleTernaryIsPresentGet(tree); + return super.visitConditionalExpression(tree, p); } - ExpressionTree method = invok.getMethodSelect(); - String methodString = method.toString(); - int dotPos = methodString.lastIndexOf("."); - if (dotPos != -1) { - methodString = methodString.substring(0, dotPos) + "::" + methodString.substring(dotPos + 1); + /** + * Part of rule #3. + * + *

          Pattern match for: {@code VAR.isPresent() ? VAR.get().METHOD() : VALUE} + * + *

          Prefer: {@code VAR.map(METHOD).orElse(VALUE);} + * + * @param tree a conditional expression that can perhaps be simplified + */ + // TODO: Should handle this via a transfer function, instead of pattern-matching. + public void handleTernaryIsPresentGet(ConditionalExpressionTree tree) { + + ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); + IPair isPresentCall = isCallToIsPresent(condExpr); + if (isPresentCall == null) { + return; + } + ExpressionTree trueExpr = TreeUtils.withoutParens(tree.getTrueExpression()); + ExpressionTree falseExpr = TreeUtils.withoutParens(tree.getFalseExpression()); + if (!isPresentCall.first) { + ExpressionTree tmp = trueExpr; + trueExpr = falseExpr; + falseExpr = tmp; + } + + if (trueExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { + return; + } + ExpressionTree trueReceiver = TreeUtils.getReceiverTree(trueExpr); + if (!isCallToGet(trueReceiver)) { + return; + } + ExpressionTree getReceiver = TreeUtils.getReceiverTree(trueReceiver); + + // What is a better way to do this than string comparison? + // Use transfer functions and Store entries. + ExpressionTree receiver = isPresentCall.second; + if (sameExpression(receiver, getReceiver)) { + ExecutableElement ele = TreeUtils.elementFromUse((MethodInvocationTree) trueExpr); + + checker.reportWarning( + tree, + "prefer.map.and.orelse", + receiver, + // The literal "CONTAININGCLASS::" is gross. + // TODO: add this to the error message. + // ElementUtils.getQualifiedClassName(ele); + ele.getSimpleName(), + falseExpr); + } } - checker.reportWarning(tree, "prefer.ifpresent", receiver, methodString); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - handleCreationElimination(tree); - handleNestedOptionalCreation(tree); - return super.visitMethodInvocation(tree, p); - } - - @Override - public Void visitBinary(BinaryTree tree, Void p) { - handleCompareToNull(tree); - return super.visitBinary(tree, p); - } - - /** - * Partially enforces Rule #1. - * - *

          If an Optional value is compared with the null literal, it indicates that the programmer - * expects it might have been assigned a null value (or no value at all) somewhere in the code. - * - * @param tree a binary tree representing a binary operation. - */ - private void handleCompareToNull(BinaryTree tree) { - if (!isEqualityOperation(tree)) { - return; + /** + * Returns true if the two trees represent the same expression. + * + * @param tree1 the first tree + * @param tree2 the second tree + * @return true if the two trees represent the same expression + */ + private boolean sameExpression(ExpressionTree tree1, ExpressionTree tree2) { + JavaExpression r1 = JavaExpression.fromTree(tree1); + JavaExpression r2 = JavaExpression.fromTree(tree2); + if (r1 != null && !r1.containsUnknown() && r2 != null && !r2.containsUnknown()) { + return r1.equals(r2); + } else { + return tree1.toString().equals(tree2.toString()); + } } - ExpressionTree leftOp = TreeUtils.withoutParens(tree.getLeftOperand()); - ExpressionTree rightOp = TreeUtils.withoutParens(tree.getRightOperand()); - TypeMirror leftOpType = TreeUtils.typeOf(leftOp); - TypeMirror rightOpType = TreeUtils.typeOf(rightOp); - if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(rightOpType)) { - checker.reportWarning(tree, "optional.null.comparison"); + @Override + public Void visitIf(IfTree tree, Void p) { + handleConditionalStatementIsPresentGet(tree); + return super.visitIf(tree, p); } - if (rightOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(leftOpType)) { - checker.reportWarning(tree, "optional.null.comparison"); + + /** + * Part of rule #3. + * + *

          Pattern match for: {@code if (VAR.isPresent()) { METHOD(VAR.get()); }} + * + *

          Prefer: {@code VAR.ifPresent(METHOD);} + * + * @param tree an if statement that can perhaps be simplified + */ + public void handleConditionalStatementIsPresentGet(IfTree tree) { + + ExpressionTree condExpr = TreeUtils.withoutParens(tree.getCondition()); + IPair isPresentCall = isCallToIsPresent(condExpr); + if (isPresentCall == null) { + return; + } + + StatementTree thenStmt = skipBlocks(tree.getThenStatement()); + StatementTree elseStmt = skipBlocks(tree.getElseStatement()); + if (!isPresentCall.first) { + StatementTree tmp = thenStmt; + thenStmt = elseStmt; + elseStmt = tmp; + } + + if (!(elseStmt == null + || (elseStmt.getKind() == Tree.Kind.BLOCK + && ((BlockTree) elseStmt).getStatements().isEmpty()))) { + // else block is missing or is an empty block: "{}" + return; + } + + if (thenStmt.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { + return; + } + ExpressionTree thenExpr = ((ExpressionStatementTree) thenStmt).getExpression(); + if (thenExpr.getKind() != Tree.Kind.METHOD_INVOCATION) { + return; + } + MethodInvocationTree invok = (MethodInvocationTree) thenExpr; + List args = invok.getArguments(); + if (args.size() != 1) { + return; + } + ExpressionTree arg = TreeUtils.withoutParens(args.get(0)); + if (!isCallToGet(arg)) { + return; + } + ExpressionTree receiver = isPresentCall.second; + ExpressionTree getReceiver = TreeUtils.getReceiverTree(arg); + if (!receiver.toString().equals(getReceiver.toString())) { + return; + } + ExpressionTree method = invok.getMethodSelect(); + + String methodString = method.toString(); + int dotPos = methodString.lastIndexOf("."); + if (dotPos != -1) { + methodString = + methodString.substring(0, dotPos) + "::" + methodString.substring(dotPos + 1); + } + + checker.reportWarning(tree, "prefer.ifpresent", receiver, methodString); } - } - - /** - * Returns true if the binary operation is {@code ==} or {@code !=}. - * - * @param tree a binary operation - * @return true if the binary operation is {@code ==} or {@code !=} - */ - private boolean isEqualityOperation(BinaryTree tree) { - return tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO; - } - - // Partially enforces Rule #1. (Only handles the literal `null`, not all nullable expressions.) - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = super.commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); - ExpressionTree valueWithoutParens = TreeUtils.withoutParens(valueExpTree); - if (valueWithoutParens.getKind() == Kind.NULL_LITERAL - && isOptionalType(varType.getUnderlyingType())) { - checker.reportWarning(valueWithoutParens, "optional.null.assignment"); - return false; + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + handleCreationElimination(tree); + handleNestedOptionalCreation(tree); + return super.visitMethodInvocation(tree, p); } - return result; - } - - /** - * Rule #4. - * - *

          Pattern match for: {@code CREATION().PROPAGATION()*.ELIMINATION()} - * - *

          Prefer: {@code VAR.ifPresent(METHOD);} - * - * @param tree a method invocation that can perhaps be simplified - */ - public void handleCreationElimination(MethodInvocationTree tree) { - if (!isOptionalElimination(tree)) { - return; + + @Override + public Void visitBinary(BinaryTree tree, Void p) { + handleCompareToNull(tree); + return super.visitBinary(tree, p); } - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - while (true) { - if (receiver == null) { - // The receiver can be null if the receiver is the implicit "this.". - return; - } - if (receiver.getKind() != Tree.Kind.METHOD_INVOCATION) { - return; - } - MethodInvocationTree methodCall = (MethodInvocationTree) receiver; - if (isOptionalPropagation(methodCall)) { - receiver = TreeUtils.getReceiverTree(methodCall); - // Continue with new receiver. - } else if (isOptionalCreation(methodCall)) { - checker.reportWarning(tree, "introduce.eliminate"); - return; - } else { - return; - } + + /** + * Partially enforces Rule #1. + * + *

          If an Optional value is compared with the null literal, it indicates that the programmer + * expects it might have been assigned a null value (or no value at all) somewhere in the code. + * + * @param tree a binary tree representing a binary operation. + */ + private void handleCompareToNull(BinaryTree tree) { + if (!isEqualityOperation(tree)) { + return; + } + ExpressionTree leftOp = TreeUtils.withoutParens(tree.getLeftOperand()); + ExpressionTree rightOp = TreeUtils.withoutParens(tree.getRightOperand()); + TypeMirror leftOpType = TreeUtils.typeOf(leftOp); + TypeMirror rightOpType = TreeUtils.typeOf(rightOp); + + if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(rightOpType)) { + checker.reportWarning(tree, "optional.null.comparison"); + } + if (rightOp.getKind() == Tree.Kind.NULL_LITERAL && isOptionalType(leftOpType)) { + checker.reportWarning(tree, "optional.null.comparison"); + } } - } - - /** - * Partial support for Rule #5 and Rule #7. - * - *

          Rule #5: Avoid nested Optional chains, or operations that have an intermediate Optional - * value. - * - *

          Rule #7: Don't use Optional to wrap any collection type. - * - *

          Certain types are illegal, such as {@code Optional}. The type validator may see a - * supertype of the most precise run-time type; for example, it may see the type as {@code - * Optional}, and it would not flag any problem with such a type. This method - * checks at {@code Optional} creation sites. - * - *

          TODO: This finds only some {@code Optional}: those that consist of {@code - * Optional.of(optionalExpr)} or {@code Optional.ofNullable(optionalExpr)}, where {@code - * optionalExpr} has type {@code Optional}. There are other ways that {@code Optional} - * can be created, such as {@code optionalExpr.map(Optional::of)}. - * - *

          TODO: Also check at collection creation sites, but there are so many of them, and there - * often are not values of the element type at the collection creation site. - * - * @param tree a method invocation that might create an Optional of an illegal type - */ - public void handleNestedOptionalCreation(MethodInvocationTree tree) { - if (!isOptionalCreation(tree)) { - return; + + /** + * Returns true if the binary operation is {@code ==} or {@code !=}. + * + * @param tree a binary operation + * @return true if the binary operation is {@code ==} or {@code !=} + */ + private boolean isEqualityOperation(BinaryTree tree) { + return tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO; } - if (tree.getArguments().isEmpty()) { - // This is a call to Optional.empty(), which takes no argument. - return; + + // Partially enforces Rule #1. (Only handles the literal `null`, not all nullable expressions.) + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = super.commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); + ExpressionTree valueWithoutParens = TreeUtils.withoutParens(valueExpTree); + if (valueWithoutParens.getKind() == Kind.NULL_LITERAL + && isOptionalType(varType.getUnderlyingType())) { + checker.reportWarning(valueWithoutParens, "optional.null.assignment"); + return false; + } + return result; } - ExpressionTree arg = tree.getArguments().get(0); - AnnotatedTypeMirror argAtm = atypeFactory.getAnnotatedType(arg); - TypeMirror argType = argAtm.getUnderlyingType(); - if (isOptionalType(argType)) { - checker.reportWarning(tree, "optional.nesting"); - } else if (isCollectionType(argType)) { - checker.reportWarning(tree, "optional.collection"); + + /** + * Rule #4. + * + *

          Pattern match for: {@code CREATION().PROPAGATION()*.ELIMINATION()} + * + *

          Prefer: {@code VAR.ifPresent(METHOD);} + * + * @param tree a method invocation that can perhaps be simplified + */ + public void handleCreationElimination(MethodInvocationTree tree) { + if (!isOptionalElimination(tree)) { + return; + } + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + while (true) { + if (receiver == null) { + // The receiver can be null if the receiver is the implicit "this.". + return; + } + if (receiver.getKind() != Tree.Kind.METHOD_INVOCATION) { + return; + } + MethodInvocationTree methodCall = (MethodInvocationTree) receiver; + if (isOptionalPropagation(methodCall)) { + receiver = TreeUtils.getReceiverTree(methodCall); + // Continue with new receiver. + } else if (isOptionalCreation(methodCall)) { + checker.reportWarning(tree, "introduce.eliminate"); + return; + } else { + return; + } + } } - } - - /** - * Rule #6 (partial). - * - *

          Don't use Optional in fields and method parameters. - */ - @Override - public Void visitVariable(VariableTree tree, Void p) { - VariableElement ve = TreeUtils.elementFromDeclaration(tree); - TypeMirror tm = ve.asType(); - if (isOptionalType(tm)) { - ElementKind ekind = TreeUtils.elementFromDeclaration(tree).getKind(); - if (ekind.isField()) { - checker.reportWarning(tree, "optional.field"); - } else if (ekind == ElementKind.PARAMETER) { - TreePath paramPath = getCurrentPath(); - Tree parent = paramPath.getParentPath().getLeaf(); - if (parent.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - // Exception to rule: lambda parameters can have type Optional. - } else { - checker.reportWarning(tree, "optional.parameter"); + + /** + * Partial support for Rule #5 and Rule #7. + * + *

          Rule #5: Avoid nested Optional chains, or operations that have an intermediate Optional + * value. + * + *

          Rule #7: Don't use Optional to wrap any collection type. + * + *

          Certain types are illegal, such as {@code Optional}. The type validator may see + * a supertype of the most precise run-time type; for example, it may see the type as {@code + * Optional}, and it would not flag any problem with such a type. This method + * checks at {@code Optional} creation sites. + * + *

          TODO: This finds only some {@code Optional}: those that consist of {@code + * Optional.of(optionalExpr)} or {@code Optional.ofNullable(optionalExpr)}, where {@code + * optionalExpr} has type {@code Optional}. There are other ways that {@code Optional} + * can be created, such as {@code optionalExpr.map(Optional::of)}. + * + *

          TODO: Also check at collection creation sites, but there are so many of them, and there + * often are not values of the element type at the collection creation site. + * + * @param tree a method invocation that might create an Optional of an illegal type + */ + public void handleNestedOptionalCreation(MethodInvocationTree tree) { + if (!isOptionalCreation(tree)) { + return; + } + if (tree.getArguments().isEmpty()) { + // This is a call to Optional.empty(), which takes no argument. + return; + } + ExpressionTree arg = tree.getArguments().get(0); + AnnotatedTypeMirror argAtm = atypeFactory.getAnnotatedType(arg); + TypeMirror argType = argAtm.getUnderlyingType(); + if (isOptionalType(argType)) { + checker.reportWarning(tree, "optional.nesting"); + } else if (isCollectionType(argType)) { + checker.reportWarning(tree, "optional.collection"); } - } } - return super.visitVariable(tree, p); - } - - /** - * Handles Rule #5, part of Rule #6, and also Rule #7. - * - *

          Rule #5: Avoid nested Optional chains, or operations that have an intermediate Optional - * value. - * - *

          Rule #6: Don't use Optional in fields, parameters, and collections. - * - *

          Rule #7: Don't use Optional to wrap any collection type. - * - *

          The validator is called on the type of every expression, such as on the right-hand side of - * {@code x = Optional.of(Optional.of("baz"));}. However, the type of the right-hand side is - * {@code Optional}, not {@code Optional>}. Therefore, to fully - * check for improper types, it is necessary to examine, in the type checker, the argument to - * construction of an Optional. Method {@link #handleNestedOptionalCreation} does so. - */ - private final class OptionalTypeValidator extends BaseTypeValidator { - - public OptionalTypeValidator( - BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); + + /** + * Rule #6 (partial). + * + *

          Don't use Optional in fields and method parameters. + */ + @Override + public Void visitVariable(VariableTree tree, Void p) { + VariableElement ve = TreeUtils.elementFromDeclaration(tree); + TypeMirror tm = ve.asType(); + if (isOptionalType(tm)) { + ElementKind ekind = TreeUtils.elementFromDeclaration(tree).getKind(); + if (ekind.isField()) { + checker.reportWarning(tree, "optional.field"); + } else if (ekind == ElementKind.PARAMETER) { + TreePath paramPath = getCurrentPath(); + Tree parent = paramPath.getParentPath().getLeaf(); + if (parent.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + // Exception to rule: lambda parameters can have type Optional. + } else { + checker.reportWarning(tree, "optional.parameter"); + } + } + } + return super.visitVariable(tree, p); } /** @@ -523,144 +505,172 @@ public OptionalTypeValidator( *

          Rule #6: Don't use Optional in fields, parameters, and collections. * *

          Rule #7: Don't use Optional to wrap any collection type. + * + *

          The validator is called on the type of every expression, such as on the right-hand side of + * {@code x = Optional.of(Optional.of("baz"));}. However, the type of the right-hand side is + * {@code Optional}, not {@code Optional>}. Therefore, to + * fully check for improper types, it is necessary to examine, in the type checker, the argument + * to construction of an Optional. Method {@link #handleNestedOptionalCreation} does so. */ - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - TypeMirror tm = type.getUnderlyingType(); - if (isCollectionType(tm)) { - List typeArgs = ((DeclaredType) tm).getTypeArguments(); - if (typeArgs.size() == 1) { - // TODO: handle collections that have more than one type parameter - TypeMirror typeArg = typeArgs.get(0); - if (isOptionalType(typeArg)) { - checker.reportWarning(tree, "optional.as.element.type"); - } - } - } else if (isOptionalType(tm)) { - List typeArgs = ((DeclaredType) tm).getTypeArguments(); - // If typeArgs.size()==0, then the user wrote a raw type `Optional`. - if (typeArgs.size() == 1) { - TypeMirror typeArg = typeArgs.get(0); - if (isCollectionType(typeArg)) { - checker.reportWarning(tree, "optional.collection"); - } - if (isOptionalType(typeArg)) { - checker.reportWarning(tree, "optional.nesting"); - } + private final class OptionalTypeValidator extends BaseTypeValidator { + + public OptionalTypeValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } + + /** + * Handles Rule #5, part of Rule #6, and also Rule #7. + * + *

          Rule #5: Avoid nested Optional chains, or operations that have an intermediate + * Optional value. + * + *

          Rule #6: Don't use Optional in fields, parameters, and collections. + * + *

          Rule #7: Don't use Optional to wrap any collection type. + */ + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + TypeMirror tm = type.getUnderlyingType(); + if (isCollectionType(tm)) { + List typeArgs = ((DeclaredType) tm).getTypeArguments(); + if (typeArgs.size() == 1) { + // TODO: handle collections that have more than one type parameter + TypeMirror typeArg = typeArgs.get(0); + if (isOptionalType(typeArg)) { + checker.reportWarning(tree, "optional.as.element.type"); + } + } + } else if (isOptionalType(tm)) { + List typeArgs = ((DeclaredType) tm).getTypeArguments(); + // If typeArgs.size()==0, then the user wrote a raw type `Optional`. + if (typeArgs.size() == 1) { + TypeMirror typeArg = typeArgs.get(0); + if (isCollectionType(typeArg)) { + checker.reportWarning(tree, "optional.collection"); + } + if (isOptionalType(typeArg)) { + checker.reportWarning(tree, "optional.nesting"); + } + } + } + return super.visitDeclared(type, tree); } - } - return super.visitDeclared(type, tree); } - } - - /** - * Return true if tm is a subtype of Collection (other than the Null type). - * - * @param tm a type - * @return true if the given type is a subtype of Collection - */ - private boolean isCollectionType(TypeMirror tm) { - return tm.getKind() == TypeKind.DECLARED && types.isSubtype(tm, collectionType); - } - - /** The fully-qualified names of the 4 optional classes in java.util. */ - private static final Set fqOptionalTypes = - new HashSet<>( - Arrays.asList( - "java.util.Optional", - "java.util.OptionalDouble", - "java.util.OptionalInt", - "java.util.OptionalLong")); - - /** - * Return true if tm is class Optional, OptionalDouble, OptionalInt, or OptionalLong in java.util. - * - * @param tm a type - * @return true if the given type is Optional, OptionalDouble, OptionalInt, or OptionalLong - */ - private boolean isOptionalType(TypeMirror tm) { - return TypesUtils.isDeclaredOfName(tm, fqOptionalTypes); - } - - /** - * If the given tree is a block tree with a single element, return the enclosed non-block - * statement. Otherwise, return the same tree. - * - * @param tree a statement tree - * @return the single enclosed statement, if it exists; otherwise, the same tree - */ - // TODO: The Optional Checker should work over the CFG, then it would not need this any longer. - public static StatementTree skipBlocks(StatementTree tree) { - if (tree == null) { - return tree; + + /** + * Return true if tm is a subtype of Collection (other than the Null type). + * + * @param tm a type + * @return true if the given type is a subtype of Collection + */ + private boolean isCollectionType(TypeMirror tm) { + return tm.getKind() == TypeKind.DECLARED && types.isSubtype(tm, collectionType); + } + + /** The fully-qualified names of the 4 optional classes in java.util. */ + private static final Set fqOptionalTypes = + new HashSet<>( + Arrays.asList( + "java.util.Optional", + "java.util.OptionalDouble", + "java.util.OptionalInt", + "java.util.OptionalLong")); + + /** + * Return true if tm is class Optional, OptionalDouble, OptionalInt, or OptionalLong in + * java.util. + * + * @param tm a type + * @return true if the given type is Optional, OptionalDouble, OptionalInt, or OptionalLong + */ + private boolean isOptionalType(TypeMirror tm) { + return TypesUtils.isDeclaredOfName(tm, fqOptionalTypes); } - StatementTree s = tree; - while (s.getKind() == Tree.Kind.BLOCK) { - List stmts = ((BlockTree) s).getStatements(); - if (stmts.size() == 1) { - s = stmts.get(0); - } else { + + /** + * If the given tree is a block tree with a single element, return the enclosed non-block + * statement. Otherwise, return the same tree. + * + * @param tree a statement tree + * @return the single enclosed statement, if it exists; otherwise, the same tree + */ + // TODO: The Optional Checker should work over the CFG, then it would not need this any longer. + public static StatementTree skipBlocks(StatementTree tree) { + if (tree == null) { + return tree; + } + StatementTree s = tree; + while (s.getKind() == Tree.Kind.BLOCK) { + List stmts = ((BlockTree) s).getStatements(); + if (stmts.size() == 1) { + s = stmts.get(0); + } else { + return s; + } + } return s; - } - } - return s; - } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void p) { - if (isFilterIsPresentMapGet(tree)) { - // TODO: This is a (sound) workaround until - // https://github.com/typetools/checker-framework/issues/1345 - // is fixed. - return null; } - return super.visitMemberReference(tree, p); - } - - /** - * Returns true if {@code memberRefTree} is the {@code Optional::get} in {@code - * Stream.filter(Optional::isPresent).map(Optional::get)}. - * - * @param memberRefTree a member reference tree - * @return true if {@code memberRefTree} the {@code Optional::get} in {@code - * Stream.filter(Optional::isPresent).map(Optional::get)} - */ - private boolean isFilterIsPresentMapGet(MemberReferenceTree memberRefTree) { - if (!TreeUtils.elementFromUse(memberRefTree).equals(optionalGet)) { - // The method reference is not Optional::get - return false; + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void p) { + if (isFilterIsPresentMapGet(tree)) { + // TODO: This is a (sound) workaround until + // https://github.com/typetools/checker-framework/issues/1345 + // is fixed. + return null; + } + return super.visitMemberReference(tree, p); } - // "getPath" means "the path to the node `Optional::get`". - TreePath getPath = getCurrentPath(); - TreePath getParentPath = getPath.getParentPath(); - // "getParent" means "the parent of the node `Optional::get`". - Tree getParent = getParentPath.getLeaf(); - if (getParent.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree hasGetAsArgumentTree = (MethodInvocationTree) getParent; - ExecutableElement hasGetAsArgumentElement = TreeUtils.elementFromUse(hasGetAsArgumentTree); - if (!hasGetAsArgumentElement.equals(streamMap)) { - // Optional::get is not an argument to stream#map + + /** + * Returns true if {@code memberRefTree} is the {@code Optional::get} in {@code + * Stream.filter(Optional::isPresent).map(Optional::get)}. + * + * @param memberRefTree a member reference tree + * @return true if {@code memberRefTree} the {@code Optional::get} in {@code + * Stream.filter(Optional::isPresent).map(Optional::get)} + */ + private boolean isFilterIsPresentMapGet(MemberReferenceTree memberRefTree) { + if (!TreeUtils.elementFromUse(memberRefTree).equals(optionalGet)) { + // The method reference is not Optional::get + return false; + } + // "getPath" means "the path to the node `Optional::get`". + TreePath getPath = getCurrentPath(); + TreePath getParentPath = getPath.getParentPath(); + // "getParent" means "the parent of the node `Optional::get`". + Tree getParent = getParentPath.getLeaf(); + if (getParent.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree hasGetAsArgumentTree = (MethodInvocationTree) getParent; + ExecutableElement hasGetAsArgumentElement = + TreeUtils.elementFromUse(hasGetAsArgumentTree); + if (!hasGetAsArgumentElement.equals(streamMap)) { + // Optional::get is not an argument to stream#map + return false; + } + // hasGetAsArgumentTree is an invocation of Stream#map(...). + Tree mapReceiverTree = TreeUtils.getReceiverTree(hasGetAsArgumentTree); + // Will check whether mapParent is the call `Stream.filter(Optional::isPresent)`. + if (mapReceiverTree != null + && mapReceiverTree.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree fluentToMapTree = (MethodInvocationTree) mapReceiverTree; + ExecutableElement fluentToMapElement = TreeUtils.elementFromUse(fluentToMapTree); + if (!fluentToMapElement.equals(streamFilter)) { + // The receiver of map(Optional::get) is not Stream#filter + return false; + } + MethodInvocationTree filterInvocationTree = fluentToMapTree; + ExpressionTree filterArgTree = filterInvocationTree.getArguments().get(0); + if (filterArgTree.getKind() == Tree.Kind.MEMBER_REFERENCE) { + ExecutableElement filterArgElement = + TreeUtils.elementFromUse((MemberReferenceTree) filterArgTree); + return filterArgElement.equals(optionalIsPresent); + } + } + } return false; - } - // hasGetAsArgumentTree is an invocation of Stream#map(...). - Tree mapReceiverTree = TreeUtils.getReceiverTree(hasGetAsArgumentTree); - // Will check whether mapParent is the call `Stream.filter(Optional::isPresent)`. - if (mapReceiverTree != null && mapReceiverTree.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree fluentToMapTree = (MethodInvocationTree) mapReceiverTree; - ExecutableElement fluentToMapElement = TreeUtils.elementFromUse(fluentToMapTree); - if (!fluentToMapElement.equals(streamFilter)) { - // The receiver of map(Optional::get) is not Stream#filter - return false; - } - MethodInvocationTree filterInvocationTree = fluentToMapTree; - ExpressionTree filterArgTree = filterInvocationTree.getArguments().get(0); - if (filterArgTree.getKind() == Tree.Kind.MEMBER_REFERENCE) { - ExecutableElement filterArgElement = - TreeUtils.elementFromUse((MemberReferenceTree) filterArgTree); - return filterArgElement.equals(optionalIsPresent); - } - } } - return false; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java index eead5ddda91..142f51f9829 100644 --- a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyAnnotatedTypeFactory.java @@ -4,6 +4,17 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.propkey.qual.PropertyKey; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.plumelib.reflection.Signatures; +import org.plumelib.util.CollectionsPlume; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -16,17 +27,9 @@ import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; + import javax.lang.model.element.AnnotationMirror; import javax.tools.Diagnostic; -import org.checkerframework.checker.propkey.qual.PropertyKey; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; -import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.plumelib.reflection.Signatures; -import org.plumelib.util.CollectionsPlume; /** * This AnnotatedTypeFactory adds PropertyKey annotations to String literals that contain values @@ -34,202 +37,206 @@ */ public class PropertyKeyAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private final Set lookupKeys; - - public PropertyKeyAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.lookupKeys = Collections.unmodifiableSet(buildLookupKeys()); - - this.postInit(); - } - - @Override - public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this, PropertyKey.class)); - } - - // To allow subclasses access to createTreeAnnotator from the BATF. - protected TreeAnnotator createBasicTreeAnnotator() { - return super.createTreeAnnotator(); - } - - /** - * This TreeAnnotator checks for every String literal whether it is included in the lookup keys. - * If it is, the given annotation is added to the literal; otherwise, nothing happens. Subclasses - * of this AnnotatedTypeFactory can directly reuse this class and use a different annotation as - * parameter. - */ - protected class KeyLookupTreeAnnotator extends TreeAnnotator { - AnnotationMirror theAnnot; - - public KeyLookupTreeAnnotator(BaseAnnotatedTypeFactory atf, Class annot) { - super(atf); - theAnnot = AnnotationBuilder.fromClass(elements, annot); - } + private final Set lookupKeys; - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(theAnnot) - && tree.getKind() == Tree.Kind.STRING_LITERAL - && strContains(lookupKeys, tree.getValue().toString())) { - type.addAnnotation(theAnnot); - } - // A possible extension is to record all the keys that have been used and - // in the end output a list of keys that were not used in the program, - // possibly pointing to the opposite problem, keys that were supposed to - // be used somewhere, but have not been, maybe because of copy-and-paste errors. - return super.visitLiteral(tree, type); + public PropertyKeyAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.lookupKeys = Collections.unmodifiableSet(buildLookupKeys()); + + this.postInit(); } - // Result of binary op might not be a property key. @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(theAnnot); - return null; // super.visitBinary(tree, type); + public TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this, PropertyKey.class)); } - // Result of unary op might not be a property key. - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - type.removeAnnotation(theAnnot); - return null; // super.visitCompoundAssignment(tree, type); + // To allow subclasses access to createTreeAnnotator from the BATF. + protected TreeAnnotator createBasicTreeAnnotator() { + return super.createTreeAnnotator(); } - } - - /** - * Instead of a precise comparison, we incrementally remove leading dot-separated strings until we - * find a match. For example if messages contains "y.z" and we look for "x.y.z" we find a match - * after removing the first "x.". - * - *

          Compare to SourceChecker.fullMessageOf. - */ - private static boolean strContains(Set messages, String messageKey) { - String key = messageKey; - - do { - if (messages.contains(key)) { - return true; - } - - int dot = key.indexOf('.'); - if (dot < 0) { - return false; - } - key = key.substring(dot + 1); - } while (true); - } - - /** - * Returns a set of the valid keys that can be used. - * - * @return the valid keys that can be used - */ - public Set getLookupKeys() { - return this.lookupKeys; - } - - private Set buildLookupKeys() { - Set result = new HashSet<>(); - - if (checker.hasOption("propfiles")) { - result.addAll(keysOfPropertyFiles(checker.getStringsOption("propfiles", File.pathSeparator))); + + /** + * This TreeAnnotator checks for every String literal whether it is included in the lookup keys. + * If it is, the given annotation is added to the literal; otherwise, nothing happens. + * Subclasses of this AnnotatedTypeFactory can directly reuse this class and use a different + * annotation as parameter. + */ + protected class KeyLookupTreeAnnotator extends TreeAnnotator { + AnnotationMirror theAnnot; + + public KeyLookupTreeAnnotator( + BaseAnnotatedTypeFactory atf, Class annot) { + super(atf); + theAnnot = AnnotationBuilder.fromClass(elements, annot); + } + + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(theAnnot) + && tree.getKind() == Tree.Kind.STRING_LITERAL + && strContains(lookupKeys, tree.getValue().toString())) { + type.addAnnotation(theAnnot); + } + // A possible extension is to record all the keys that have been used and + // in the end output a list of keys that were not used in the program, + // possibly pointing to the opposite problem, keys that were supposed to + // be used somewhere, but have not been, maybe because of copy-and-paste errors. + return super.visitLiteral(tree, type); + } + + // Result of binary op might not be a property key. + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(theAnnot); + return null; // super.visitBinary(tree, type); + } + + // Result of unary op might not be a property key. + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + type.removeAnnotation(theAnnot); + return null; // super.visitCompoundAssignment(tree, type); + } } - if (checker.hasOption("bundlenames")) { - result.addAll(keysOfResourceBundle(checker.getStringsOption("bundlenames", ':'))); + + /** + * Instead of a precise comparison, we incrementally remove leading dot-separated strings until + * we find a match. For example if messages contains "y.z" and we look for "x.y.z" we find a + * match after removing the first "x.". + * + *

          Compare to SourceChecker.fullMessageOf. + */ + private static boolean strContains(Set messages, String messageKey) { + String key = messageKey; + + do { + if (messages.contains(key)) { + return true; + } + + int dot = key.indexOf('.'); + if (dot < 0) { + return false; + } + key = key.substring(dot + 1); + } while (true); } - return result; - } - - /** - * Obtains the keys from all the property files. - * - * @param propfiles a list of property file names - * @return a set of all the keys found in all the property files - */ - private Set keysOfPropertyFiles(List propfiles) { - if (propfiles.isEmpty()) { - return Collections.emptySet(); + /** + * Returns a set of the valid keys that can be used. + * + * @return the valid keys that can be used + */ + public Set getLookupKeys() { + return this.lookupKeys; } - Set result = new HashSet<>(CollectionsPlume.mapCapacity(propfiles)); + private Set buildLookupKeys() { + Set result = new HashSet<>(); + + if (checker.hasOption("propfiles")) { + result.addAll( + keysOfPropertyFiles(checker.getStringsOption("propfiles", File.pathSeparator))); + } + if (checker.hasOption("bundlenames")) { + result.addAll(keysOfResourceBundle(checker.getStringsOption("bundlenames", ':'))); + } - for (String propfile : propfiles) { - try { - Properties prop = new Properties(); + return result; + } - ClassLoader cl = this.getClass().getClassLoader(); - if (cl == null) { - // The class loader is null if the system class loader was used. - cl = ClassLoader.getSystemClassLoader(); + /** + * Obtains the keys from all the property files. + * + * @param propfiles a list of property file names + * @return a set of all the keys found in all the property files + */ + private Set keysOfPropertyFiles(List propfiles) { + if (propfiles.isEmpty()) { + return Collections.emptySet(); } - try (InputStream in = cl.getResourceAsStream(propfile)) { - if (in != null) { - prop.load(in); - } else { - // If the classloader didn't manage to load the file, try whether a - // FileInputStream works. For absolute paths this might help. - try (InputStream fis = new FileInputStream(propfile)) { - prop.load(fis); - } catch (FileNotFoundException e) { - checker.message( - Diagnostic.Kind.WARNING, "Couldn't find the properties file: " + propfile); - // report(null, "propertykeychecker.filenotfound", propfile); - // return Collections.emptySet(); - continue; + Set result = new HashSet<>(CollectionsPlume.mapCapacity(propfiles)); + + for (String propfile : propfiles) { + try { + Properties prop = new Properties(); + + ClassLoader cl = this.getClass().getClassLoader(); + if (cl == null) { + // The class loader is null if the system class loader was used. + cl = ClassLoader.getSystemClassLoader(); + } + + try (InputStream in = cl.getResourceAsStream(propfile)) { + if (in != null) { + prop.load(in); + } else { + // If the classloader didn't manage to load the file, try whether a + // FileInputStream works. For absolute paths this might help. + try (InputStream fis = new FileInputStream(propfile)) { + prop.load(fis); + } catch (FileNotFoundException e) { + checker.message( + Diagnostic.Kind.WARNING, + "Couldn't find the properties file: " + propfile); + // report(null, "propertykeychecker.filenotfound", propfile); + // return Collections.emptySet(); + continue; + } + } + } + + result.addAll(prop.stringPropertyNames()); + } catch (Exception e) { + // TODO: is there a nicer way to report messages, that are not connected to an AST + // node? + // One cannot use `report`, because it needs a node. + checker.message( + Diagnostic.Kind.WARNING, + "Exception in PropertyKeyChecker.keysOfPropertyFile: " + e); + e.printStackTrace(); } - } } - result.addAll(prop.stringPropertyNames()); - } catch (Exception e) { - // TODO: is there a nicer way to report messages, that are not connected to an AST - // node? - // One cannot use `report`, because it needs a node. - checker.message( - Diagnostic.Kind.WARNING, "Exception in PropertyKeyChecker.keysOfPropertyFile: " + e); - e.printStackTrace(); - } + return result; } - return result; - } - - /** - * Returns the keys for the given resource bundles. - * - * @param bundleNames names of resource bundles - * @return the keys for the given resource bundles - */ - private Set keysOfResourceBundle(List bundleNames) { - if (bundleNames.isEmpty()) { - return Collections.emptySet(); - } + /** + * Returns the keys for the given resource bundles. + * + * @param bundleNames names of resource bundles + * @return the keys for the given resource bundles + */ + private Set keysOfResourceBundle(List bundleNames) { + if (bundleNames.isEmpty()) { + return Collections.emptySet(); + } + + Set result = new HashSet<>(CollectionsPlume.mapCapacity(bundleNames)); + + for (String bundleName : bundleNames) { + if (!Signatures.isBinaryName(bundleName)) { + System.err.println( + "Malformed resource bundle: <" + bundleName + "> should be a binary name."); + continue; + } + ResourceBundle bundle = ResourceBundle.getBundle(bundleName); + if (bundle == null) { + checker.message( + Diagnostic.Kind.WARNING, + "Couldn't find the resource bundle: <" + + bundleName + + "> for locale <" + + Locale.getDefault() + + ">"); + continue; + } - Set result = new HashSet<>(CollectionsPlume.mapCapacity(bundleNames)); - - for (String bundleName : bundleNames) { - if (!Signatures.isBinaryName(bundleName)) { - System.err.println( - "Malformed resource bundle: <" + bundleName + "> should be a binary name."); - continue; - } - ResourceBundle bundle = ResourceBundle.getBundle(bundleName); - if (bundle == null) { - checker.message( - Diagnostic.Kind.WARNING, - "Couldn't find the resource bundle: <" - + bundleName - + "> for locale <" - + Locale.getDefault() - + ">"); - continue; - } - - result.addAll(bundle.keySet()); + result.addAll(bundle.keySet()); + } + return result; } - return result; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java index 4a3f02c8321..0b251add1b0 100644 --- a/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/propkey/PropertyKeyChecker.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.propkey; -import java.util.Locale; -import java.util.ResourceBundle; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.source.SupportedOptions; +import java.util.Locale; +import java.util.ResourceBundle; + /** * A type-checker that checks that only valid keys are used to access property files and resource * bundles. Subclasses can specialize this class for the different uses of property files, for diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java index 96849822346..b500fc36e4d 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexAnnotatedTypeFactory.java @@ -6,14 +6,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Set; -import java.util.regex.Pattern; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.regex.qual.PartialRegex; import org.checkerframework.checker.regex.qual.PolyRegex; @@ -46,6 +39,16 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; + /** * Adds {@link Regex} to the type of tree, in the following cases: * @@ -77,482 +80,488 @@ */ public class RegexAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @{@link Regex} annotation. */ - protected final AnnotationMirror REGEX = AnnotationBuilder.fromClass(elements, Regex.class); - - /** The @{@link RegexBottom} annotation. */ - protected final AnnotationMirror REGEXBOTTOM = - AnnotationBuilder.fromClass(elements, RegexBottom.class); - - /** The @{@link PartialRegex} annotation. */ - protected final AnnotationMirror PARTIALREGEX = - AnnotationBuilder.fromClass(elements, PartialRegex.class); - - /** The @{@link PolyRegex} annotation. */ - protected final AnnotationMirror POLYREGEX = - AnnotationBuilder.fromClass(elements, PolyRegex.class); - - /** The @{@link UnknownRegex} annotation. */ - protected final AnnotationMirror UNKNOWNREGEX = - AnnotationBuilder.fromClass(elements, UnknownRegex.class); - - /** A set containing just {@link #UNKNOWNREGEX}. */ - protected final AnnotationMirrorSet UNKNOWNREGEX_SET = - AnnotationMirrorSet.singleton(UNKNOWNREGEX); - - /** The method that returns the value element of a {@code @Regex} annotation. */ - protected final ExecutableElement regexValueElement = - TreeUtils.getMethod( - "org.checkerframework.checker.regex.qual.Regex", "value", 0, processingEnv); - - /** - * The value method of the PartialRegex qualifier. - * - * @see org.checkerframework.checker.regex.qual.PartialRegex - */ - private final ExecutableElement partialRegexValueElement = - TreeUtils.getMethod(PartialRegex.class, "value", 0, processingEnv); - - /** - * The Pattern.compile method. - * - * @see java.util.regex.Pattern#compile(String) - */ - private final ExecutableElement patternCompile = - TreeUtils.getMethod("java.util.regex.Pattern", "compile", 1, processingEnv); - - /** - * The Pattern.compile method that takes two formal parameters (second one is flags). - * - * @see java.util.regex.Pattern#compile(String, int) - */ - private final ExecutableElement patternCompile2 = - TreeUtils.getMethod("java.util.regex.Pattern", "compile", 2, processingEnv); - - /** - * Create a new RegexAnnotatedTypeFactory. - * - * @param checker the checker - */ - public RegexAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - Regex.class, PartialRegex.class, - RegexBottom.class, UnknownRegex.class); - } - - @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new RegexTransfer((CFAnalysis) analysis); - } - - /** Returns a new Regex annotation with the given group count. */ - /*package-private*/ AnnotationMirror createRegexAnnotation(int groupCount) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Regex.class); - if (groupCount > 0) { - builder.setValue("value", groupCount); - } - return builder.build(); - } + /** The @{@link Regex} annotation. */ + protected final AnnotationMirror REGEX = AnnotationBuilder.fromClass(elements, Regex.class); - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new RegexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } + /** The @{@link RegexBottom} annotation. */ + protected final AnnotationMirror REGEXBOTTOM = + AnnotationBuilder.fromClass(elements, RegexBottom.class); - /** - * A custom qualifier hierarchy for the Regex Checker. This makes a regex annotation a subtype of - * all regex annotations with lower group count values. For example, {@code @Regex(3)} is a - * subtype of {@code @Regex(1)}. All regex annotations are subtypes of {@code @Regex}, which has a - * default value of 0. - */ - private final class RegexQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + /** The @{@link PartialRegex} annotation. */ + protected final AnnotationMirror PARTIALREGEX = + AnnotationBuilder.fromClass(elements, PartialRegex.class); + + /** The @{@link PolyRegex} annotation. */ + protected final AnnotationMirror POLYREGEX = + AnnotationBuilder.fromClass(elements, PolyRegex.class); + + /** The @{@link UnknownRegex} annotation. */ + protected final AnnotationMirror UNKNOWNREGEX = + AnnotationBuilder.fromClass(elements, UnknownRegex.class); + + /** A set containing just {@link #UNKNOWNREGEX}. */ + protected final AnnotationMirrorSet UNKNOWNREGEX_SET = + AnnotationMirrorSet.singleton(UNKNOWNREGEX); + + /** The method that returns the value element of a {@code @Regex} annotation. */ + protected final ExecutableElement regexValueElement = + TreeUtils.getMethod( + "org.checkerframework.checker.regex.qual.Regex", "value", 0, processingEnv); + + /** + * The value method of the PartialRegex qualifier. + * + * @see org.checkerframework.checker.regex.qual.PartialRegex + */ + private final ExecutableElement partialRegexValueElement = + TreeUtils.getMethod(PartialRegex.class, "value", 0, processingEnv); - /** Qualifier kind for the @{@link Regex} annotation. */ - private final QualifierKind REGEX_KIND; + /** + * The Pattern.compile method. + * + * @see java.util.regex.Pattern#compile(String) + */ + private final ExecutableElement patternCompile = + TreeUtils.getMethod("java.util.regex.Pattern", "compile", 1, processingEnv); - /** Qualifier kind for the @{@link PartialRegex} annotation. */ - private final QualifierKind PARTIALREGEX_KIND; + /** + * The Pattern.compile method that takes two formal parameters (second one is flags). + * + * @see java.util.regex.Pattern#compile(String, int) + */ + private final ExecutableElement patternCompile2 = + TreeUtils.getMethod("java.util.regex.Pattern", "compile", 2, processingEnv); /** - * Creates a RegexQualifierHierarchy from the given classes. + * Create a new RegexAnnotatedTypeFactory. * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils + * @param checker the checker */ - private RegexQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, RegexAnnotatedTypeFactory.this); - REGEX_KIND = getQualifierKind(REGEX); - PARTIALREGEX_KIND = getQualifierKind(PARTIALREGEX); + public RegexAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + this.postInit(); } @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == REGEX_KIND && superKind == REGEX_KIND) { - int rhsValue = getRegexValue(subAnno); - int lhsValue = getRegexValue(superAnno); - return lhsValue <= rhsValue; - } else if (subKind == PARTIALREGEX_KIND && superKind == PARTIALREGEX_KIND) { - return AnnotationUtils.areSame(subAnno, superAnno); - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + Regex.class, PartialRegex.class, + RegexBottom.class, UnknownRegex.class); } @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { - int value1 = getRegexValue(a1); - int value2 = getRegexValue(a2); - if (value1 < value2) { - return a1; - } else { - return a2; - } - } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return UNKNOWNREGEX; + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new RegexTransfer((CFAnalysis) analysis); + } + + /** Returns a new Regex annotation with the given group count. */ + /*package-private*/ AnnotationMirror createRegexAnnotation(int groupCount) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Regex.class); + if (groupCount > 0) { + builder.setValue("value", groupCount); } - } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { - return a1; - } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + return builder.build(); } @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { - int value1 = getRegexValue(a1); - int value2 = getRegexValue(a2); - if (value1 > value2) { - return a1; - } else { - return a2; + protected QualifierHierarchy createQualifierHierarchy() { + return new RegexQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** + * A custom qualifier hierarchy for the Regex Checker. This makes a regex annotation a subtype + * of all regex annotations with lower group count values. For example, {@code @Regex(3)} is a + * subtype of {@code @Regex(1)}. All regex annotations are subtypes of {@code @Regex}, which has + * a default value of 0. + */ + private final class RegexQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + /** Qualifier kind for the @{@link Regex} annotation. */ + private final QualifierKind REGEX_KIND; + + /** Qualifier kind for the @{@link PartialRegex} annotation. */ + private final QualifierKind PARTIALREGEX_KIND; + + /** + * Creates a RegexQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + private RegexQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, RegexAnnotatedTypeFactory.this); + REGEX_KIND = getQualifierKind(REGEX); + PARTIALREGEX_KIND = getQualifierKind(PARTIALREGEX); } - } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return REGEXBOTTOM; + + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == REGEX_KIND && superKind == REGEX_KIND) { + int rhsValue = getRegexValue(subAnno); + int lhsValue = getRegexValue(superAnno); + return lhsValue <= rhsValue; + } else if (subKind == PARTIALREGEX_KIND && superKind == PARTIALREGEX_KIND) { + return AnnotationUtils.areSame(subAnno, superAnno); + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); + } + + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { + int value1 = getRegexValue(a1); + int value2 = getRegexValue(a2); + if (value1 < value2) { + return a1; + } else { + return a2; + } + } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return UNKNOWNREGEX; + } + } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { + return a1; + } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + } + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == REGEX_KIND && qualifierKind2 == REGEX_KIND) { + int value1 = getRegexValue(a1); + int value2 = getRegexValue(a2); + if (value1 > value2) { + return a1; + } else { + return a2; + } + } else if (qualifierKind1 == PARTIALREGEX_KIND && qualifierKind2 == PARTIALREGEX_KIND) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return REGEXBOTTOM; + } + } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { + return a1; + } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + } + + /** + * Gets the value out of a regex annotation. + * + * @param anno a @Regex annotation + * @return the {@code value} element of the annotation + */ + private int getRegexValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); } - } else if (qualifierKind1 == PARTIALREGEX_KIND || qualifierKind1 == REGEX_KIND) { - return a1; - } else if (qualifierKind2 == PARTIALREGEX_KIND || qualifierKind2 == REGEX_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); } /** - * Gets the value out of a regex annotation. + * Returns the group count value of the given annotation or 0 if there's a problem getting the + * group count value. * * @param anno a @Regex annotation * @return the {@code value} element of the annotation */ - private int getRegexValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); + public int getGroupCount(AnnotationMirror anno) { + if (anno == null) { + return 0; + } + return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); } - } - - /** - * Returns the group count value of the given annotation or 0 if there's a problem getting the - * group count value. - * - * @param anno a @Regex annotation - * @return the {@code value} element of the annotation - */ - public int getGroupCount(AnnotationMirror anno) { - if (anno == null) { - return 0; + + /** Returns the number of groups in the given regex String. */ + public static int getGroupCount(@Regex String regexp) { + return Pattern.compile(regexp).matcher("").groupCount(); } - return AnnotationUtils.getElementValue(anno, regexValueElement, Integer.class, 0); - } - - /** Returns the number of groups in the given regex String. */ - public static int getGroupCount(@Regex String regexp) { - return Pattern.compile(regexp).matcher("").groupCount(); - } - - @Override - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - return UNKNOWNREGEX_SET; - } - - @Override - public TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because the PropagationTreeAnnotator types binary - // expressions as lub. - return new ListTreeAnnotator( - new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), - new RegexTreeAnnotator(this), - new RegexPropagationTreeAnnotator(this)); - } - - private static class RegexPropagationTreeAnnotator extends PropagationTreeAnnotator { - - public RegexPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + + @Override + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + return UNKNOWNREGEX_SET; } @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - // Don't call super method which will try to create a LUB - // Even when it is not yet valid: i.e. between a @PolyRegex and a @Regex - return null; + public TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because the PropagationTreeAnnotator types binary + // expressions as lub. + return new ListTreeAnnotator( + new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), + new RegexTreeAnnotator(this), + new RegexPropagationTreeAnnotator(this)); } - } - private class RegexTreeAnnotator extends TreeAnnotator { + private static class RegexPropagationTreeAnnotator extends PropagationTreeAnnotator { + + public RegexPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - public RegexTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + // Don't call super method which will try to create a LUB + // Even when it is not yet valid: i.e. between a @PolyRegex and a @Regex + return null; + } } - /** - * Case 1: valid regular expression String or char literal. Adds PartialRegex annotation to - * String literals that are not valid regular expressions. - */ - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(REGEX)) { - String regex = null; - if (tree.getKind() == Tree.Kind.STRING_LITERAL) { - regex = (String) tree.getValue(); - } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) { - regex = Character.toString((Character) tree.getValue()); + private class RegexTreeAnnotator extends TreeAnnotator { + + public RegexTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - if (regex != null) { - if (RegexUtil.isRegex(regex)) { - int groupCount = getGroupCount(regex); - type.addAnnotation(createRegexAnnotation(groupCount)); - } else { - type.addAnnotation(createPartialRegexAnnotation(regex)); - } + + /** + * Case 1: valid regular expression String or char literal. Adds PartialRegex annotation to + * String literals that are not valid regular expressions. + */ + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(REGEX)) { + String regex = null; + if (tree.getKind() == Tree.Kind.STRING_LITERAL) { + regex = (String) tree.getValue(); + } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) { + regex = Character.toString((Character) tree.getValue()); + } + if (regex != null) { + if (RegexUtil.isRegex(regex)) { + int groupCount = getGroupCount(regex); + type.addAnnotation(createRegexAnnotation(groupCount)); + } else { + type.addAnnotation(createPartialRegexAnnotation(regex)); + } + } + } + return super.visitLiteral(tree, type); } - } - return super.visitLiteral(tree, type); - } - /** - * Case 2: concatenation of Regex or PolyRegex String/char literals. Also handles concatenation - * of partial regular expressions. - */ - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (!type.hasAnnotationInHierarchy(REGEX) && TreeUtils.isStringConcatenation(tree)) { - AnnotatedTypeMirror lExpr = getAnnotatedType(tree.getLeftOperand()); - AnnotatedTypeMirror rExpr = getAnnotatedType(tree.getRightOperand()); - - Integer lGroupCount = getMinimumRegexCount(lExpr); - Integer rGroupCount = getMinimumRegexCount(rExpr); - boolean lExprRE = lGroupCount != null; - boolean rExprRE = rGroupCount != null; - boolean lExprPart = lExpr.hasAnnotation(PartialRegex.class); - boolean rExprPart = rExpr.hasAnnotation(PartialRegex.class); - boolean lExprPoly = lExpr.hasAnnotation(PolyRegex.class); - boolean rExprPoly = rExpr.hasAnnotation(PolyRegex.class); - - if (lExprRE && rExprRE) { - // Remove current @Regex annotation and add a new one with the correct group - // count value. - type.replaceAnnotation(createRegexAnnotation(lGroupCount + rGroupCount)); - } else if ((lExprPoly && rExprPoly) || (lExprPoly && rExprRE) || (lExprRE && rExprPoly)) { - type.addAnnotation(POLYREGEX); - } else if (lExprPart && rExprPart) { - String lRegex = getPartialRegexValue(lExpr); - String rRegex = getPartialRegexValue(rExpr); - String concat = lRegex + rRegex; - if (RegexUtil.isRegex(concat)) { - int groupCount = getGroupCount(concat); - type.addAnnotation(createRegexAnnotation(groupCount)); - } else { - type.addAnnotation(createPartialRegexAnnotation(concat)); - } - } else if (lExprRE && rExprPart) { - String rRegex = getPartialRegexValue(rExpr); - String concat = "e" + rRegex; - type.addAnnotation(createPartialRegexAnnotation(concat)); - } else if (lExprPart && rExprRE) { - String lRegex = getPartialRegexValue(lExpr); - String concat = lRegex + "e"; - type.addAnnotation(createPartialRegexAnnotation(concat)); + /** + * Case 2: concatenation of Regex or PolyRegex String/char literals. Also handles + * concatenation of partial regular expressions. + */ + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (!type.hasAnnotationInHierarchy(REGEX) && TreeUtils.isStringConcatenation(tree)) { + AnnotatedTypeMirror lExpr = getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rExpr = getAnnotatedType(tree.getRightOperand()); + + Integer lGroupCount = getMinimumRegexCount(lExpr); + Integer rGroupCount = getMinimumRegexCount(rExpr); + boolean lExprRE = lGroupCount != null; + boolean rExprRE = rGroupCount != null; + boolean lExprPart = lExpr.hasAnnotation(PartialRegex.class); + boolean rExprPart = rExpr.hasAnnotation(PartialRegex.class); + boolean lExprPoly = lExpr.hasAnnotation(PolyRegex.class); + boolean rExprPoly = rExpr.hasAnnotation(PolyRegex.class); + + if (lExprRE && rExprRE) { + // Remove current @Regex annotation and add a new one with the correct group + // count value. + type.replaceAnnotation(createRegexAnnotation(lGroupCount + rGroupCount)); + } else if ((lExprPoly && rExprPoly) + || (lExprPoly && rExprRE) + || (lExprRE && rExprPoly)) { + type.addAnnotation(POLYREGEX); + } else if (lExprPart && rExprPart) { + String lRegex = getPartialRegexValue(lExpr); + String rRegex = getPartialRegexValue(rExpr); + String concat = lRegex + rRegex; + if (RegexUtil.isRegex(concat)) { + int groupCount = getGroupCount(concat); + type.addAnnotation(createRegexAnnotation(groupCount)); + } else { + type.addAnnotation(createPartialRegexAnnotation(concat)); + } + } else if (lExprRE && rExprPart) { + String rRegex = getPartialRegexValue(rExpr); + String concat = "e" + rRegex; + type.addAnnotation(createPartialRegexAnnotation(concat)); + } else if (lExprPart && rExprRE) { + String lRegex = getPartialRegexValue(lExpr); + String concat = lRegex + "e"; + type.addAnnotation(createPartialRegexAnnotation(concat)); + } + } + return null; // super.visitBinary(tree, type); } - } - return null; // super.visitBinary(tree, type); - } - /** Case 2: Also handle compound String concatenation. */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - AnnotatedTypeMirror rhs = getAnnotatedType(tree.getExpression()); - AnnotatedTypeMirror lhs = getAnnotatedType(tree.getVariable()); - - Integer lhsRegexCount = getMinimumRegexCount(lhs); - Integer rhsRegexCount = getMinimumRegexCount(rhs); - - if (lhsRegexCount != null && rhsRegexCount != null) { - int lCount = getGroupCount(lhs.getAnnotation(Regex.class)); - int rCount = getGroupCount(rhs.getAnnotation(Regex.class)); - type.removeAnnotationInHierarchy(REGEX); - type.addAnnotation(createRegexAnnotation(lCount + rCount)); + /** Case 2: Also handle compound String concatenation. */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + AnnotatedTypeMirror rhs = getAnnotatedType(tree.getExpression()); + AnnotatedTypeMirror lhs = getAnnotatedType(tree.getVariable()); + + Integer lhsRegexCount = getMinimumRegexCount(lhs); + Integer rhsRegexCount = getMinimumRegexCount(rhs); + + if (lhsRegexCount != null && rhsRegexCount != null) { + int lCount = getGroupCount(lhs.getAnnotation(Regex.class)); + int rCount = getGroupCount(rhs.getAnnotation(Regex.class)); + type.removeAnnotationInHierarchy(REGEX); + type.addAnnotation(createRegexAnnotation(lCount + rCount)); + } + } + return null; // super.visitCompoundAssignment(tree, type); } - } - return null; // super.visitCompoundAssignment(tree, type); - } - /** - * Case 3: For a call to Pattern.compile, add an annotation to the return type that has the same - * group count value as the parameter. For calls to {@code asRegex(String, int)} change the - * return type to have the same group count as the value of the second argument. - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isMethodInvocation(tree, patternCompile, processingEnv) - || TreeUtils.isMethodInvocation(tree, patternCompile2, processingEnv)) { - ExpressionTree arg0 = tree.getArguments().get(0); - - AnnotatedTypeMirror argType = getAnnotatedType(arg0); - Integer regexCount = getMinimumRegexCount(argType); - AnnotationMirror bottomAnno = getAnnotatedType(arg0).getAnnotation(RegexBottom.class); - - if (regexCount != null) { - // Remove current @Regex annotation... - // ...and add a new one with the correct group count value. - type.replaceAnnotation(createRegexAnnotation(regexCount)); - } else if (bottomAnno != null) { - type.replaceAnnotation(AnnotationBuilder.fromClass(elements, RegexBottom.class)); + /** + * Case 3: For a call to Pattern.compile, add an annotation to the return type that has the + * same group count value as the parameter. For calls to {@code asRegex(String, int)} change + * the return type to have the same group count as the value of the second argument. + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isMethodInvocation(tree, patternCompile, processingEnv) + || TreeUtils.isMethodInvocation(tree, patternCompile2, processingEnv)) { + ExpressionTree arg0 = tree.getArguments().get(0); + + AnnotatedTypeMirror argType = getAnnotatedType(arg0); + Integer regexCount = getMinimumRegexCount(argType); + AnnotationMirror bottomAnno = + getAnnotatedType(arg0).getAnnotation(RegexBottom.class); + + if (regexCount != null) { + // Remove current @Regex annotation... + // ...and add a new one with the correct group count value. + type.replaceAnnotation(createRegexAnnotation(regexCount)); + } else if (bottomAnno != null) { + type.replaceAnnotation( + AnnotationBuilder.fromClass(elements, RegexBottom.class)); + } + } + return super.visitMethodInvocation(tree, type); } - } - return super.visitMethodInvocation(tree, type); - } - /** Returns a new PartialRegex annotation with the given partial regular expression. */ - private AnnotationMirror createPartialRegexAnnotation(String partial) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, PartialRegex.class); - builder.setValue("value", partial); - return builder.build(); - } + /** Returns a new PartialRegex annotation with the given partial regular expression. */ + private AnnotationMirror createPartialRegexAnnotation(String partial) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, PartialRegex.class); + builder.setValue("value", partial); + return builder.build(); + } - /** - * Returns the {@code value} element of a {@code @PartialRegex} annotation, if there is one in - * {@code type}. - * - * @param type a type - * @return the {@code value} element of a {@code @PartialRegex} annotation, or "" if none - */ - private String getPartialRegexValue(AnnotatedTypeMirror type) { - AnnotationMirror partialRegexAnno = type.getAnnotation(PartialRegex.class); - if (partialRegexAnno == null) { - return ""; - } - return AnnotationUtils.getElementValue( - partialRegexAnno, partialRegexValueElement, String.class, ""); - } + /** + * Returns the {@code value} element of a {@code @PartialRegex} annotation, if there is one + * in {@code type}. + * + * @param type a type + * @return the {@code value} element of a {@code @PartialRegex} annotation, or "" if none + */ + private String getPartialRegexValue(AnnotatedTypeMirror type) { + AnnotationMirror partialRegexAnno = type.getAnnotation(PartialRegex.class); + if (partialRegexAnno == null) { + return ""; + } + return AnnotationUtils.getElementValue( + partialRegexAnno, partialRegexValueElement, String.class, ""); + } - /** - * Returns the value of the Regex annotation on the given type or NULL if there is no Regex - * annotation. If type is a TYPEVAR, WILDCARD, or INTERSECTION type, visit first their primary - * annotation then visit their upper bounds to get the Regex annotation. The method gets - * "minimum" regex count because, depending on the bounds of a typevar or wildcard, the actual - * type may have more than the upper bound's count. - * - * @param type type that may carry a Regex annotation - * @return the Integer value of the Regex annotation (0 if no value exists) - */ - private @Nullable Integer getMinimumRegexCount(AnnotatedTypeMirror type) { - AnnotationMirror primaryRegexAnno = type.getAnnotation(Regex.class); - if (primaryRegexAnno == null) { - switch (type.getKind()) { - case TYPEVAR: - return getMinimumRegexCount(((AnnotatedTypeVariable) type).getUpperBound()); - - case WILDCARD: - return getMinimumRegexCount(((AnnotatedWildcardType) type).getExtendsBound()); - - case INTERSECTION: - Integer maxBound = null; - for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) type).getBounds()) { - Integer boundRegexNum = getMinimumRegexCount(bound); - if (boundRegexNum != null) { - if (maxBound == null || boundRegexNum > maxBound) { - maxBound = boundRegexNum; + /** + * Returns the value of the Regex annotation on the given type or NULL if there is no Regex + * annotation. If type is a TYPEVAR, WILDCARD, or INTERSECTION type, visit first their + * primary annotation then visit their upper bounds to get the Regex annotation. The method + * gets "minimum" regex count because, depending on the bounds of a typevar or wildcard, the + * actual type may have more than the upper bound's count. + * + * @param type type that may carry a Regex annotation + * @return the Integer value of the Regex annotation (0 if no value exists) + */ + private @Nullable Integer getMinimumRegexCount(AnnotatedTypeMirror type) { + AnnotationMirror primaryRegexAnno = type.getAnnotation(Regex.class); + if (primaryRegexAnno == null) { + switch (type.getKind()) { + case TYPEVAR: + return getMinimumRegexCount(((AnnotatedTypeVariable) type).getUpperBound()); + + case WILDCARD: + return getMinimumRegexCount( + ((AnnotatedWildcardType) type).getExtendsBound()); + + case INTERSECTION: + Integer maxBound = null; + for (AnnotatedTypeMirror bound : + ((AnnotatedIntersectionType) type).getBounds()) { + Integer boundRegexNum = getMinimumRegexCount(bound); + if (boundRegexNum != null) { + if (maxBound == null || boundRegexNum > maxBound) { + maxBound = boundRegexNum; + } + } + } + return maxBound; + default: + // Nothing to do for other cases. } - } + + return null; } - return maxBound; - default: - // Nothing to do for other cases. - } - return null; - } + return getGroupCount(primaryRegexAnno); + } - return getGroupCount(primaryRegexAnno); + // This won't work correctly until flow sensitivity is supported by the + // the Regex Checker. For example: + // + // char @Regex [] arr = {'r', 'e'}; + // arr[0] = '('; // type is still "char @Regex []", but this is no longer correct + // + // There are associated tests in tests/regex/Simple.java:testCharArrays + // that can be uncommented when this is uncommented. + // /** + // * Case 4: a char array that as a String is a valid regular expression. + // */ + // @Override + // public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + // boolean isCharArray = ((ArrayType) type.getUnderlyingType()) + // .getComponentType().getKind() == TypeKind.CHAR; + // if (isCharArray && tree.getInitializers() != null) { + // List initializers = tree.getInitializers(); + // StringBuilder charArray = new StringBuilder(); + // boolean allLiterals = true; + // for (int i = 0; allLiterals && i < initializers.size(); i++) { + // ExpressionTree e = initializers.get(i); + // if (e.getKind() == Tree.Kind.CHAR_LITERAL) { + // charArray.append(((LiteralTree) e).getValue()); + // } else if (getAnnotatedType(e).hasAnnotation(Regex.class)) { + // // if there's an @Regex char in the array then substitute + // // it with a . + // charArray.append('.'); + // } else { + // allLiterals = false; + // } + // } + // if (allLiterals && RegexUtil.isRegex(charArray.toString())) { + // type.addAnnotation(Regex.class); + // } + // } + // return super.visitNewArray(tree, type); + // } } - - // This won't work correctly until flow sensitivity is supported by the - // the Regex Checker. For example: - // - // char @Regex [] arr = {'r', 'e'}; - // arr[0] = '('; // type is still "char @Regex []", but this is no longer correct - // - // There are associated tests in tests/regex/Simple.java:testCharArrays - // that can be uncommented when this is uncommented. - // /** - // * Case 4: a char array that as a String is a valid regular expression. - // */ - // @Override - // public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - // boolean isCharArray = ((ArrayType) type.getUnderlyingType()) - // .getComponentType().getKind() == TypeKind.CHAR; - // if (isCharArray && tree.getInitializers() != null) { - // List initializers = tree.getInitializers(); - // StringBuilder charArray = new StringBuilder(); - // boolean allLiterals = true; - // for (int i = 0; allLiterals && i < initializers.size(); i++) { - // ExpressionTree e = initializers.get(i); - // if (e.getKind() == Tree.Kind.CHAR_LITERAL) { - // charArray.append(((LiteralTree) e).getValue()); - // } else if (getAnnotatedType(e).hasAnnotation(Regex.class)) { - // // if there's an @Regex char in the array then substitute - // // it with a . - // charArray.append('.'); - // } else { - // allLiterals = false; - // } - // } - // if (allLiterals && RegexUtil.isRegex(charArray.toString())) { - // type.addAnnotation(Regex.class); - // } - // } - // return super.visitNewArray(tree, type); - // } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java index 2b1f72579d8..822b687e5fe 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexChecker.java @@ -1,13 +1,14 @@ package org.checkerframework.checker.regex; -import java.util.regex.MatchResult; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.qual.StubFiles; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * A type-checker plug-in for the {@link Regex} qualifier that finds syntactically invalid regular * expressions. @@ -16,12 +17,12 @@ */ @StubFiles("apache-xerces.astub") @RelevantJavaTypes({ - CharSequence.class, - // javax.swing.text.Segment.class - char.class, - Character.class, - Pattern.class, - Matcher.class, - MatchResult.class + CharSequence.class, + // javax.swing.text.Segment.class + char.class, + Character.class, + Pattern.class, + Matcher.class, + MatchResult.class }) public class RegexChecker extends BaseTypeChecker {} diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java index ce9d1dd72e9..4cf6e3cd40e 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexTransfer.java @@ -1,7 +1,5 @@ package org.checkerframework.checker.regex; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -24,201 +22,209 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + /** The transfer function for the Regex Checker. */ public class RegexTransfer extends CFTransfer { - // isRegex and asRegex are tested as signatures (string name plus formal parameters), not - // ExecutableElement, because they exist in two packages: - // org.checkerframework.checker.regex.util.RegexUtil.isRegex(String,int) - // org.plumelib.util.RegexUtil.isRegex(String,int) - // and org.plumelib.util might not be on the classpath. - private static final String IS_REGEX_METHOD_NAME = "isRegex"; - private static final String AS_REGEX_METHOD_NAME = "asRegex"; - - /** The MatchResult.groupCount() method. */ - private final ExecutableElement matchResultgroupCount; - - /** Create the transfer function for the Regex Checker. */ - public RegexTransfer(CFAbstractAnalysis analysis) { - super(analysis); - this.matchResultgroupCount = - TreeUtils.getMethod( - "java.util.regex.MatchResult", - "groupCount", - 0, - analysis.getTypeFactory().getProcessingEnv()); - } - - // TODO: These are special cases for isRegex(String, int) and asRegex(String, int). They should - // be replaced by adding an @EnsuresQualifierIf annotation that supports specifying attributes. - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult result = super.visitMethodInvocation(n, in); - - // refine result for some helper methods - MethodAccessNode target = n.getTarget(); - ExecutableElement method = target.getMethod(); - Node receiver = target.getReceiver(); - if (receiver instanceof ClassNameNode) { - ClassNameNode cnn = (ClassNameNode) receiver; - String receiverName = cnn.getElement().toString(); - if (isRegexUtil(receiverName)) { - result = handleRegexUtil(n, method, result); - } + // isRegex and asRegex are tested as signatures (string name plus formal parameters), not + // ExecutableElement, because they exist in two packages: + // org.checkerframework.checker.regex.util.RegexUtil.isRegex(String,int) + // org.plumelib.util.RegexUtil.isRegex(String,int) + // and org.plumelib.util might not be on the classpath. + private static final String IS_REGEX_METHOD_NAME = "isRegex"; + private static final String AS_REGEX_METHOD_NAME = "asRegex"; + + /** The MatchResult.groupCount() method. */ + private final ExecutableElement matchResultgroupCount; + + /** Create the transfer function for the Regex Checker. */ + public RegexTransfer(CFAbstractAnalysis analysis) { + super(analysis); + this.matchResultgroupCount = + TreeUtils.getMethod( + "java.util.regex.MatchResult", + "groupCount", + 0, + analysis.getTypeFactory().getProcessingEnv()); } - return result; - } - - private TransferResult handleRegexUtil( - MethodInvocationNode n, ExecutableElement method, TransferResult result) { - RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); - if (ElementUtils.matchesElement(method, IS_REGEX_METHOD_NAME, String.class, int.class)) { - // RegexUtil.isRegex(s, groups) method - // (No special case is needed for isRegex(String) because of - // the annotation on that method's definition.) - - CFStore thenStore = result.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); - JavaExpression firstParam = JavaExpression.fromNode(n.getArgument(0)); - - // add annotation with correct group count (if possible, - // regex annotation without count otherwise) - Node count = n.getArgument(1); - int groupCount; - if (count instanceof IntegerLiteralNode) { - IntegerLiteralNode iln = (IntegerLiteralNode) count; - groupCount = iln.getValue(); - } else { - groupCount = 0; - } - AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); - thenStore.insertValue(firstParam, regexAnnotation); - return newResult; - } else if (ElementUtils.matchesElement(method, AS_REGEX_METHOD_NAME, String.class, int.class)) { - // RegexUtil.asRegex(s, groups) method - // (No special case is needed for asRegex(String) because of - // the annotation on that method's definition.) - - // add annotation with correct group count (if possible, - // regex annotation without count otherwise) - AnnotationMirror regexAnnotation; - Node count = n.getArgument(1); - int groupCount; - if (count instanceof IntegerLiteralNode) { - IntegerLiteralNode iln = (IntegerLiteralNode) count; - groupCount = iln.getValue(); - } else { - groupCount = 0; - } - regexAnnotation = factory.createRegexAnnotation(groupCount); - - CFValue newResultValue = - analysis.createSingleAnnotationValue( - regexAnnotation, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + + // TODO: These are special cases for isRegex(String, int) and asRegex(String, int). They should + // be replaced by adding an @EnsuresQualifierIf annotation that supports specifying attributes. + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult result = super.visitMethodInvocation(n, in); + + // refine result for some helper methods + MethodAccessNode target = n.getTarget(); + ExecutableElement method = target.getMethod(); + Node receiver = target.getReceiver(); + if (receiver instanceof ClassNameNode) { + ClassNameNode cnn = (ClassNameNode) receiver; + String receiverName = cnn.getElement().toString(); + if (isRegexUtil(receiverName)) { + result = handleRegexUtil(n, method, result); + } + } + return result; } - return result; - } - - @Override - public TransferResult visitLessThan( - LessThanNode n, TransferInput in) { - // Look for: constant < mat.groupCount() - // Make mat be @Regex(constant + 1) - TransferResult res = super.visitLessThan(n, in); - return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), false, res); - } - - @Override - public TransferResult visitLessThanOrEqual( - LessThanOrEqualNode n, TransferInput in) { - // Look for: constant <= mat.groupCount() - // Make mat be @Regex(constant) - TransferResult res = super.visitLessThanOrEqual(n, in); - return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), true, res); - } - - @Override - public TransferResult visitGreaterThan( - GreaterThanNode n, TransferInput in) { - - TransferResult res = super.visitGreaterThan(n, in); - return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), false, res); - } - - @Override - public TransferResult visitGreaterThanOrEqual( - GreaterThanOrEqualNode n, TransferInput in) { - // Look for: mat.groupCount() >= constant - // Make mat be @Regex(constant) - TransferResult res = super.visitGreaterThanOrEqual(n, in); - return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), true, res); - } - - /** - * See whether possibleMatcher is a call of groupCount on a Matcher and possibleConstant is a - * constant. If so, annotate the matcher as constant + 1 if !isAlsoEqual constant if isAlsoEqual - * - * @param possibleMatcher the Node that might be a call of Matcher.groupCount() - * @param possibleConstant the Node that might be a constant - * @param isAlsoEqual whether the comparison operation is strict or reflexive - * @param resultIn the TransferResult - * @return the possibly refined output TransferResult - */ - private TransferResult handleMatcherGroupCount( - Node possibleMatcher, - Node possibleConstant, - boolean isAlsoEqual, - TransferResult resultIn) { - if (!(possibleMatcher instanceof MethodInvocationNode)) { - return resultIn; + + private TransferResult handleRegexUtil( + MethodInvocationNode n, + ExecutableElement method, + TransferResult result) { + RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); + if (ElementUtils.matchesElement(method, IS_REGEX_METHOD_NAME, String.class, int.class)) { + // RegexUtil.isRegex(s, groups) method + // (No special case is needed for isRegex(String) because of + // the annotation on that method's definition.) + + CFStore thenStore = result.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); + JavaExpression firstParam = JavaExpression.fromNode(n.getArgument(0)); + + // add annotation with correct group count (if possible, + // regex annotation without count otherwise) + Node count = n.getArgument(1); + int groupCount; + if (count instanceof IntegerLiteralNode) { + IntegerLiteralNode iln = (IntegerLiteralNode) count; + groupCount = iln.getValue(); + } else { + groupCount = 0; + } + AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); + thenStore.insertValue(firstParam, regexAnnotation); + return newResult; + } else if (ElementUtils.matchesElement( + method, AS_REGEX_METHOD_NAME, String.class, int.class)) { + // RegexUtil.asRegex(s, groups) method + // (No special case is needed for asRegex(String) because of + // the annotation on that method's definition.) + + // add annotation with correct group count (if possible, + // regex annotation without count otherwise) + AnnotationMirror regexAnnotation; + Node count = n.getArgument(1); + int groupCount; + if (count instanceof IntegerLiteralNode) { + IntegerLiteralNode iln = (IntegerLiteralNode) count; + groupCount = iln.getValue(); + } else { + groupCount = 0; + } + regexAnnotation = factory.createRegexAnnotation(groupCount); + + CFValue newResultValue = + analysis.createSingleAnnotationValue( + regexAnnotation, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } + return result; } - if (!(possibleConstant instanceof IntegerLiteralNode)) { - return resultIn; + + @Override + public TransferResult visitLessThan( + LessThanNode n, TransferInput in) { + // Look for: constant < mat.groupCount() + // Make mat be @Regex(constant + 1) + TransferResult res = super.visitLessThan(n, in); + return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), false, res); } - if (!NodeUtils.isMethodInvocation( - possibleMatcher, matchResultgroupCount, analysis.getTypeFactory().getProcessingEnv())) { - return resultIn; + @Override + public TransferResult visitLessThanOrEqual( + LessThanOrEqualNode n, TransferInput in) { + // Look for: constant <= mat.groupCount() + // Make mat be @Regex(constant) + TransferResult res = super.visitLessThanOrEqual(n, in); + return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), true, res); } - MethodAccessNode methodAccessNode = ((MethodInvocationNode) possibleMatcher).getTarget(); - Node receiver = methodAccessNode.getReceiver(); + @Override + public TransferResult visitGreaterThan( + GreaterThanNode n, TransferInput in) { + + TransferResult res = super.visitGreaterThan(n, in); + return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), false, res); + } - JavaExpression matcherReceiver = JavaExpression.fromNode(receiver); + @Override + public TransferResult visitGreaterThanOrEqual( + GreaterThanOrEqualNode n, TransferInput in) { + // Look for: mat.groupCount() >= constant + // Make mat be @Regex(constant) + TransferResult res = super.visitGreaterThanOrEqual(n, in); + return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), true, res); + } - IntegerLiteralNode iln = (IntegerLiteralNode) possibleConstant; - int groupCount; - if (isAlsoEqual) { - groupCount = iln.getValue(); - } else { - groupCount = iln.getValue() + 1; + /** + * See whether possibleMatcher is a call of groupCount on a Matcher and possibleConstant is a + * constant. If so, annotate the matcher as constant + 1 if !isAlsoEqual constant if isAlsoEqual + * + * @param possibleMatcher the Node that might be a call of Matcher.groupCount() + * @param possibleConstant the Node that might be a constant + * @param isAlsoEqual whether the comparison operation is strict or reflexive + * @param resultIn the TransferResult + * @return the possibly refined output TransferResult + */ + private TransferResult handleMatcherGroupCount( + Node possibleMatcher, + Node possibleConstant, + boolean isAlsoEqual, + TransferResult resultIn) { + if (!(possibleMatcher instanceof MethodInvocationNode)) { + return resultIn; + } + if (!(possibleConstant instanceof IntegerLiteralNode)) { + return resultIn; + } + + if (!NodeUtils.isMethodInvocation( + possibleMatcher, + matchResultgroupCount, + analysis.getTypeFactory().getProcessingEnv())) { + return resultIn; + } + + MethodAccessNode methodAccessNode = ((MethodInvocationNode) possibleMatcher).getTarget(); + Node receiver = methodAccessNode.getReceiver(); + + JavaExpression matcherReceiver = JavaExpression.fromNode(receiver); + + IntegerLiteralNode iln = (IntegerLiteralNode) possibleConstant; + int groupCount; + if (isAlsoEqual) { + groupCount = iln.getValue(); + } else { + groupCount = iln.getValue() + 1; + } + + CFStore thenStore = resultIn.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult newResult = + new ConditionalTransferResult<>(resultIn.getResultValue(), thenStore, elseStore); + RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); + + AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); + thenStore.insertValue(matcherReceiver, regexAnnotation); + + return newResult; } - CFStore thenStore = resultIn.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult newResult = - new ConditionalTransferResult<>(resultIn.getResultValue(), thenStore, elseStore); - RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); - - AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); - thenStore.insertValue(matcherReceiver, regexAnnotation); - - return newResult; - } - - /** - * Returns true if the given receiver is a class named "RegexUtil". Examples of such classes are - * org.checkerframework.checker.regex.util.RegexUtil and org.plumelib.util.RegexUtil, and the user - * might copy one into their own project. - * - * @param receiver some string - * @return true if the given receiver is a class named "RegexUtil" - */ - private boolean isRegexUtil(String receiver) { - return receiver.equals("RegexUtil") || receiver.endsWith(".RegexUtil"); - } + /** + * Returns true if the given receiver is a class named "RegexUtil". Examples of such classes are + * org.checkerframework.checker.regex.util.RegexUtil and org.plumelib.util.RegexUtil, and the + * user might copy one into their own project. + * + * @param receiver some string + * @return true if the given receiver is a class named "RegexUtil" + */ + private boolean isRegexUtil(String receiver) { + return receiver.equals("RegexUtil") || receiver.endsWith(".RegexUtil"); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java index 23e9f55cda6..cbe60cddeca 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexVisitor.java @@ -5,15 +5,17 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.TreeUtils; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; + /** * A type-checking visitor for the Regex type system. * @@ -30,92 +32,96 @@ */ public class RegexVisitor extends BaseTypeVisitor { - /** The method java.util.regex.MatchResult.end(int). */ - private final ExecutableElement matchResultEndInt; + /** The method java.util.regex.MatchResult.end(int). */ + private final ExecutableElement matchResultEndInt; - /** The method java.util.regex.MatchResult.group(int). */ - private final ExecutableElement matchResultGroupInt; + /** The method java.util.regex.MatchResult.group(int). */ + private final ExecutableElement matchResultGroupInt; - /** The method java.util.regex.MatchResult.start(int). */ - private final ExecutableElement matchResultStartInt; + /** The method java.util.regex.MatchResult.start(int). */ + private final ExecutableElement matchResultStartInt; - /** The method java.util.regex.Pattern.compile. */ - private final ExecutableElement patternCompile; + /** The method java.util.regex.Pattern.compile. */ + private final ExecutableElement patternCompile; - /** The field java.util.regex.Pattern.LITERAL. */ - private final VariableElement patternLiteral; + /** The field java.util.regex.Pattern.LITERAL. */ + private final VariableElement patternLiteral; - /** - * Create a RegexVisitor. - * - * @param checker the associated RegexChecker - */ - public RegexVisitor(BaseTypeChecker checker) { - super(checker); - ProcessingEnvironment env = checker.getProcessingEnvironment(); - this.matchResultEndInt = TreeUtils.getMethod("java.util.regex.MatchResult", "end", env, "int"); - this.matchResultGroupInt = - TreeUtils.getMethod("java.util.regex.MatchResult", "group", env, "int"); - this.matchResultStartInt = - TreeUtils.getMethod("java.util.regex.MatchResult", "start", env, "int"); - this.patternCompile = - TreeUtils.getMethod("java.util.regex.Pattern", "compile", env, "java.lang.String", "int"); - this.patternLiteral = TreeUtils.getField("java.util.regex.Pattern", "LITERAL", env); - } + /** + * Create a RegexVisitor. + * + * @param checker the associated RegexChecker + */ + public RegexVisitor(BaseTypeChecker checker) { + super(checker); + ProcessingEnvironment env = checker.getProcessingEnvironment(); + this.matchResultEndInt = + TreeUtils.getMethod("java.util.regex.MatchResult", "end", env, "int"); + this.matchResultGroupInt = + TreeUtils.getMethod("java.util.regex.MatchResult", "group", env, "int"); + this.matchResultStartInt = + TreeUtils.getMethod("java.util.regex.MatchResult", "start", env, "int"); + this.patternCompile = + TreeUtils.getMethod( + "java.util.regex.Pattern", "compile", env, "java.lang.String", "int"); + this.patternLiteral = TreeUtils.getField("java.util.regex.Pattern", "LITERAL", env); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ProcessingEnvironment env = checker.getProcessingEnvironment(); - if (TreeUtils.isMethodInvocation(tree, patternCompile, env)) { - /* - * Case 1: Don't require a Regex annotation on the String argument to Pattern.compile if the - * Pattern.LITERAL flag is passed. - */ - ExpressionTree flagParam = tree.getArguments().get(1); - if (flagParam.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree memSelect = (MemberSelectTree) flagParam; - if (TreeUtils.isSpecificFieldAccess(memSelect, patternLiteral)) { - // This is a call to Pattern.compile with the Pattern.LITERAL flag so the first - // parameter doesn't need to be a @Regex String. Don't call the super method to - // skip checking if the first parameter is a @Regex String, but make sure to - // still recurse on all of the different parts of the method call. - Void r = scan(tree.getTypeArguments(), p); - r = reduce(scan(tree.getMethodSelect(), p), r); - r = reduce(scan(tree.getArguments(), p), r); - return r; - } - } - } else if (TreeUtils.isMethodInvocation(tree, matchResultEndInt, env) - || TreeUtils.isMethodInvocation(tree, matchResultGroupInt, env) - || TreeUtils.isMethodInvocation(tree, matchResultStartInt, env)) { - // Case 2: Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code - // MatchResult.group} to ensure that a valid group number is passed. - ExpressionTree group = tree.getArguments().get(0); - if (group.getKind() == Tree.Kind.INT_LITERAL) { - LiteralTree literal = (LiteralTree) group; - int paramGroups = (Integer) literal.getValue(); - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - if (receiver == null) { - // When checking implementations of java.util.regex.MatcherResult, calls to - // group (and other methods) don't have a receiver tree. So, just do the - // regular checking. - // Verifying an implementation of a subclass of MatcherResult is out of the - // scope of this checker. - return super.visitMethodInvocation(tree, p); - } - int annoGroups = 0; - AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(receiver); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ProcessingEnvironment env = checker.getProcessingEnvironment(); + if (TreeUtils.isMethodInvocation(tree, patternCompile, env)) { + /* + * Case 1: Don't require a Regex annotation on the String argument to Pattern.compile if the + * Pattern.LITERAL flag is passed. + */ + ExpressionTree flagParam = tree.getArguments().get(1); + if (flagParam.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree memSelect = (MemberSelectTree) flagParam; + if (TreeUtils.isSpecificFieldAccess(memSelect, patternLiteral)) { + // This is a call to Pattern.compile with the Pattern.LITERAL flag so the first + // parameter doesn't need to be a @Regex String. Don't call the super method to + // skip checking if the first parameter is a @Regex String, but make sure to + // still recurse on all of the different parts of the method call. + Void r = scan(tree.getTypeArguments(), p); + r = reduce(scan(tree.getMethodSelect(), p), r); + r = reduce(scan(tree.getArguments(), p), r); + return r; + } + } + } else if (TreeUtils.isMethodInvocation(tree, matchResultEndInt, env) + || TreeUtils.isMethodInvocation(tree, matchResultGroupInt, env) + || TreeUtils.isMethodInvocation(tree, matchResultStartInt, env)) { + // Case 2: Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code + // MatchResult.group} to ensure that a valid group number is passed. + ExpressionTree group = tree.getArguments().get(0); + if (group.getKind() == Tree.Kind.INT_LITERAL) { + LiteralTree literal = (LiteralTree) group; + int paramGroups = (Integer) literal.getValue(); + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + if (receiver == null) { + // When checking implementations of java.util.regex.MatcherResult, calls to + // group (and other methods) don't have a receiver tree. So, just do the + // regular checking. + // Verifying an implementation of a subclass of MatcherResult is out of the + // scope of this checker. + return super.visitMethodInvocation(tree, p); + } + int annoGroups = 0; + AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(receiver); - if (receiverType != null && receiverType.hasAnnotation(Regex.class)) { - annoGroups = atypeFactory.getGroupCount(receiverType.getAnnotation(Regex.class)); - } - if (paramGroups > annoGroups) { - checker.reportError(group, "group.count.invalid", paramGroups, annoGroups, receiver); + if (receiverType != null && receiverType.hasAnnotation(Regex.class)) { + annoGroups = + atypeFactory.getGroupCount(receiverType.getAnnotation(Regex.class)); + } + if (paramGroups > annoGroups) { + checker.reportError( + group, "group.count.invalid", paramGroups, annoGroups, receiver); + } + } else { + checker.reportWarning(group, "group.count.unknown"); + } } - } else { - checker.reportWarning(group, "group.count.unknown"); - } + return super.visitMethodInvocation(tree, p); } - return super.visitMethodInvocation(tree, p); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java index 745b17cda0a..646f66bf1d2 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java @@ -10,32 +10,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.StringJoiner; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.calledmethods.qual.CalledMethods; import org.checkerframework.checker.mustcall.CreatesMustCallForToJavaExpression; import org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory; @@ -86,6 +61,34 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; + +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * An analyzer that checks consistency of {@link MustCall} and {@link CalledMethods} types, thereby * detecting resource leaks. For any expression e the analyzer ensures that when e @@ -144,2465 +147,2530 @@ /*package-private*/ class MustCallConsistencyAnalyzer { - /** True if errors related to static owning fields should be suppressed. */ - private final boolean permitStaticOwning; - - /** True if errors related to field initialization should be suppressed. */ - private final boolean permitInitializationLeak; - - /** - * Aliases about which the checker has already reported about a resource leak, to avoid duplicate - * reports. - */ - private final Set reportedErrorAliases = new HashSet<>(); - - /** - * The type factory for the Resource Leak Checker, which is used to get called methods types and - * to access the Must Call Checker. - */ - private final ResourceLeakAnnotatedTypeFactory typeFactory; - - /** - * A cache for the result of calling {@code ResourceLeakAnnotatedTypeFactory.getStoreAfter()} on a - * node. The cache prevents repeatedly computing least upper bounds on stores - */ - private final IdentityHashMap cmStoreAfter = new IdentityHashMap<>(); - - /** - * A cache for the result of calling {@code MustCallAnnotatedTypeFactory.getStoreAfter()} on a - * node. The cache prevents repeatedly computing least upper bounds on stores - */ - private final IdentityHashMap mcStoreAfter = new IdentityHashMap<>(); - - /** The Resource Leak Checker, used to issue errors. */ - private final ResourceLeakChecker checker; - - /** The analysis from the Resource Leak Checker, used to get input stores based on CFG blocks. */ - private final ResourceLeakAnalysis analysis; - - /** True if -AnoLightweightOwnership was passed on the command line. */ - private final boolean noLightweightOwnership; - - /** True if -AcountMustCall was passed on the command line. */ - private final boolean countMustCall; - - /** A description for how a method might exit. */ - /*package-private*/ enum MethodExitKind { - - /** The method exits normally by returning. */ - NORMAL_RETURN, - - /** The method exits by throwing an exception. */ - EXCEPTIONAL_EXIT; - - /** An immutable set containing all possible ways for a method to exit. */ - public static final Set ALL = - ImmutableSet.copyOf(EnumSet.allOf(MethodExitKind.class)); - } - - /** - * An Obligation is a dataflow fact: a set of resource aliases and when those resources need to be - * cleaned up. Abstractly, each Obligation represents a resource for which the analyzed program - * might have a must-call obligation. Each Obligation is a pair of a set of resource aliases and - * their must-call obligation. Must-call obligations are tracked by the {@link MustCallChecker} - * and are accessed by looking up the type(s) in its type system of the resource aliases contained - * in each {@code Obligation} using {@link #getMustCallMethods(ResourceLeakAnnotatedTypeFactory, - * CFStore)}. - * - *

          An Obligation might not matter on all paths out of a method. For instance, after a - * constructor assigns a resource to an {@link Owning} field, the resource only needs to be closed - * if the constructor throws an exception. If the constructor exits normally then the obligation - * is satisfied because the field is now responsible for its must-call obligations. See {@link - * #whenToEnforce}, which defines when the Obligation needs to be enforced. - * - *

          There is no guarantee that a given Obligation represents a resource with a real must-call - * obligation. When the analysis can conclude that a given Obligation certainly does not represent - * a real resource with a real must-call obligation (such as if the only resource alias is - * certainly a null pointer, or if the must-call obligation is the empty set), the analysis can - * discard the Obligation. - */ - /*package-private*/ static class Obligation { + /** True if errors related to static owning fields should be suppressed. */ + private final boolean permitStaticOwning; + + /** True if errors related to field initialization should be suppressed. */ + private final boolean permitInitializationLeak; /** - * The set of resource aliases through which a must-call obligation can be satisfied. Calling - * the required method(s) in the must-call obligation through any of them satisfies the - * must-call obligation: that is, if the called-methods type of any alias contains the required - * method(s), then the must-call obligation is satisfied. See {@link #getMustCallMethods}. - * - *

          {@code Obligation} is deeply immutable. If some code were to accidentally mutate a {@code - * resourceAliases} set it could be really nasty to debug, so this set is always immutable. + * Aliases about which the checker has already reported about a resource leak, to avoid + * duplicate reports. */ - public final ImmutableSet resourceAliases; + private final Set reportedErrorAliases = new HashSet<>(); /** - * The ways a method can exit along which this Obligation has to be enforced. For example, this - * will usually be {@link MethodExitKind#ALL}, indicating that this Obligation has to be - * enforced no matter how the method exits. It may also be a smaller set indicating that the - * Obligation only has to be enforced on certain exit conditions. - * - *

          If this set is empty then the Obligation can be dropped as it never needs to be enforced. + * The type factory for the Resource Leak Checker, which is used to get called methods types and + * to access the Must Call Checker. */ - public final ImmutableSet whenToEnforce; + private final ResourceLeakAnnotatedTypeFactory typeFactory; /** - * Create an Obligation from a set of resource aliases. - * - * @param resourceAliases a set of resource aliases - * @param whenToEnforce when this Obligation should be enforced + * A cache for the result of calling {@code ResourceLeakAnnotatedTypeFactory.getStoreAfter()} on + * a node. The cache prevents repeatedly computing least upper bounds on stores + */ + private final IdentityHashMap cmStoreAfter = new IdentityHashMap<>(); + + /** + * A cache for the result of calling {@code MustCallAnnotatedTypeFactory.getStoreAfter()} on a + * node. The cache prevents repeatedly computing least upper bounds on stores + */ + private final IdentityHashMap mcStoreAfter = new IdentityHashMap<>(); + + /** The Resource Leak Checker, used to issue errors. */ + private final ResourceLeakChecker checker; + + /** + * The analysis from the Resource Leak Checker, used to get input stores based on CFG blocks. */ - public Obligation(Set resourceAliases, Set whenToEnforce) { - this.resourceAliases = ImmutableSet.copyOf(resourceAliases); - this.whenToEnforce = ImmutableSet.copyOf(whenToEnforce); + private final ResourceLeakAnalysis analysis; + + /** True if -AnoLightweightOwnership was passed on the command line. */ + private final boolean noLightweightOwnership; + + /** True if -AcountMustCall was passed on the command line. */ + private final boolean countMustCall; + + /** A description for how a method might exit. */ + /*package-private*/ enum MethodExitKind { + + /** The method exits normally by returning. */ + NORMAL_RETURN, + + /** The method exits by throwing an exception. */ + EXCEPTIONAL_EXIT; + + /** An immutable set containing all possible ways for a method to exit. */ + public static final Set ALL = + ImmutableSet.copyOf(EnumSet.allOf(MethodExitKind.class)); } /** - * Returns the resource alias in this Obligation's resource alias set corresponding to {@code - * localVariableNode} if one is present. Otherwise, returns null. + * An Obligation is a dataflow fact: a set of resource aliases and when those resources need to + * be cleaned up. Abstractly, each Obligation represents a resource for which the analyzed + * program might have a must-call obligation. Each Obligation is a pair of a set of resource + * aliases and their must-call obligation. Must-call obligations are tracked by the {@link + * MustCallChecker} and are accessed by looking up the type(s) in its type system of the + * resource aliases contained in each {@code Obligation} using {@link + * #getMustCallMethods(ResourceLeakAnnotatedTypeFactory, CFStore)}. * - * @param localVariableNode a local variable - * @return the resource alias corresponding to {@code localVariableNode} if one is present; - * otherwise, null + *

          An Obligation might not matter on all paths out of a method. For instance, after a + * constructor assigns a resource to an {@link Owning} field, the resource only needs to be + * closed if the constructor throws an exception. If the constructor exits normally then the + * obligation is satisfied because the field is now responsible for its must-call obligations. + * See {@link #whenToEnforce}, which defines when the Obligation needs to be enforced. + * + *

          There is no guarantee that a given Obligation represents a resource with a real must-call + * obligation. When the analysis can conclude that a given Obligation certainly does not + * represent a real resource with a real must-call obligation (such as if the only resource + * alias is certainly a null pointer, or if the must-call obligation is the empty set), the + * analysis can discard the Obligation. */ - private @Nullable ResourceAlias getResourceAlias(LocalVariableNode localVariableNode) { - Element element = localVariableNode.getElement(); - for (ResourceAlias alias : resourceAliases) { - if (alias.reference instanceof LocalVariable && alias.element.equals(element)) { - return alias; - } - } - return null; + /*package-private*/ static class Obligation { + + /** + * The set of resource aliases through which a must-call obligation can be satisfied. + * Calling the required method(s) in the must-call obligation through any of them satisfies + * the must-call obligation: that is, if the called-methods type of any alias contains the + * required method(s), then the must-call obligation is satisfied. See {@link + * #getMustCallMethods}. + * + *

          {@code Obligation} is deeply immutable. If some code were to accidentally mutate a + * {@code resourceAliases} set it could be really nasty to debug, so this set is always + * immutable. + */ + public final ImmutableSet resourceAliases; + + /** + * The ways a method can exit along which this Obligation has to be enforced. For example, + * this will usually be {@link MethodExitKind#ALL}, indicating that this Obligation has to + * be enforced no matter how the method exits. It may also be a smaller set indicating that + * the Obligation only has to be enforced on certain exit conditions. + * + *

          If this set is empty then the Obligation can be dropped as it never needs to be + * enforced. + */ + public final ImmutableSet whenToEnforce; + + /** + * Create an Obligation from a set of resource aliases. + * + * @param resourceAliases a set of resource aliases + * @param whenToEnforce when this Obligation should be enforced + */ + public Obligation(Set resourceAliases, Set whenToEnforce) { + this.resourceAliases = ImmutableSet.copyOf(resourceAliases); + this.whenToEnforce = ImmutableSet.copyOf(whenToEnforce); + } + + /** + * Returns the resource alias in this Obligation's resource alias set corresponding to + * {@code localVariableNode} if one is present. Otherwise, returns null. + * + * @param localVariableNode a local variable + * @return the resource alias corresponding to {@code localVariableNode} if one is present; + * otherwise, null + */ + private @Nullable ResourceAlias getResourceAlias(LocalVariableNode localVariableNode) { + Element element = localVariableNode.getElement(); + for (ResourceAlias alias : resourceAliases) { + if (alias.reference instanceof LocalVariable && alias.element.equals(element)) { + return alias; + } + } + return null; + } + + /** + * Returns the resource alias in this Obligation's resource alias set corresponding to + * {@code expression} if one is present. Otherwise, returns null. + * + * @param expression a Java expression + * @return the resource alias corresponding to {@code expression} if one is present; + * otherwise, null + */ + private @Nullable ResourceAlias getResourceAlias(JavaExpression expression) { + for (ResourceAlias alias : resourceAliases) { + if (alias.reference.equals(expression)) { + return alias; + } + } + return null; + } + + /** + * Returns true if this contains a resource alias corresponding to {@code + * localVariableNode}, meaning that calling the required methods on {@code + * localVariableNode} is sufficient to satisfy the must-call obligation this object + * represents. + * + * @param localVariableNode a local variable node + * @return true if a resource alias corresponding to {@code localVariableNode} is present + */ + private boolean canBeSatisfiedThrough(LocalVariableNode localVariableNode) { + return getResourceAlias(localVariableNode) != null; + } + + /** + * Does this Obligation contain any resource aliases that were derived from {@link + * MustCallAlias} parameters? + * + * @return the logical or of the {@link ResourceAlias#derivedFromMustCallAliasParam} fields + * of this Obligation's resource aliases + */ + public boolean derivedFromMustCallAlias() { + for (ResourceAlias ra : resourceAliases) { + if (ra.derivedFromMustCallAliasParam) { + return true; + } + } + return false; + } + + /** + * Gets the must-call methods (i.e. the list of methods that must be called to satisfy the + * must-call obligation) of each resource alias represented by this Obligation. + * + * @param rlAtf a Resource Leak Annotated Type Factory + * @param mcStore a CFStore produced by the MustCall checker's dataflow analysis. If this is + * null, then the default MustCall type of each variable's class will be used. + * @return a map from each resource alias of this to a list of its must-call method names, + * or null if the must-call obligations are unsatisfiable (i.e. the value of some + * tracked resource alias of this in the Must Call store is MustCallUnknown) + */ + public @Nullable Map> getMustCallMethods( + ResourceLeakAnnotatedTypeFactory rlAtf, @Nullable CFStore mcStore) { + Map> result = new HashMap<>(this.resourceAliases.size()); + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + rlAtf.getTypeFactoryOfSubchecker(MustCallChecker.class); + + for (ResourceAlias alias : this.resourceAliases) { + AnnotationMirror mcAnno = + getMustCallValue(alias, mcStore, mustCallAnnotatedTypeFactory); + if (!AnnotationUtils.areSameByName(mcAnno, MustCall.class.getCanonicalName())) { + // MustCallUnknown; cannot be satisfied + return null; + } + List annoVals = rlAtf.getMustCallValues(mcAnno); + // Really, annoVals should never be empty here; we should not have created the + // obligation in the first place. + // TODO: add an assertion that annoVals is non-empty and address any failures + result.put(alias, annoVals); + } + return result; + } + + /** + * Gets the must-call type associated with the given resource alias, falling on back on the + * declared type if there is no refined type for the alias in the store. + * + * @param alias a resource alias + * @param mcStore the must-call checker's store + * @param mcAtf the must-call checker's annotated type factory + * @return the annotation from the must-call type hierarchy associated with {@code alias} + */ + private static AnnotationMirror getMustCallValue( + ResourceAlias alias, + @Nullable CFStore mcStore, + MustCallAnnotatedTypeFactory mcAtf) { + JavaExpression reference = alias.reference; + CFValue value = mcStore == null ? null : mcStore.getValue(reference); + if (value != null) { + AnnotationMirror result = + AnnotationUtils.getAnnotationByClass( + value.getAnnotations(), MustCall.class); + if (result != null) { + return result; + } + } + + AnnotationMirror result = + mcAtf.getAnnotatedType(alias.element) + .getEffectiveAnnotationInHierarchy(mcAtf.TOP); + if (result != null && !AnnotationUtils.areSame(result, mcAtf.TOP)) { + return result; + } + // There wasn't an @MustCall annotation for it in the store and the type factory has no + // information, so fall back to the default must-call type for the class. + // TODO: we currently end up in this case when checking a call to the return type + // of a returns-receiver method on something with a MustCall type; for example, + // see tests/socket/ZookeeperReport6.java. We should instead use a poly type if we can. + TypeElement typeElt = TypesUtils.getTypeElement(reference.getType()); + if (typeElt == null) { + // typeElt is null if reference.getType() was not a class, interface, annotation + // type, or enum -- that is, was not an annotatable type. + // That happens rarely, such as when it is a wildcard type. In these cases, fall + // back on a safe default: top. + return mcAtf.TOP; + } + if (typeElt.asType().getKind() == TypeKind.VOID) { + // Void types can't have methods called on them, so returning bottom is safe. + return mcAtf.BOTTOM; + } + + return mcAtf.getAnnotatedType(typeElt).getAnnotationInHierarchy(mcAtf.TOP); + } + + @Override + public String toString() { + return "Obligation: resourceAliases=" + + Iterables.toString(resourceAliases) + + ", whenToEnforce=" + + whenToEnforce; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Obligation that = (Obligation) obj; + return this.resourceAliases.equals(that.resourceAliases) + && this.whenToEnforce.equals(that.whenToEnforce); + } + + @Override + public int hashCode() { + return Objects.hash(resourceAliases, whenToEnforce); + } } + // Is there a different Obligation on every line of the program, or is Obligation mutable? + // (Or maybe Obligation is abstractly mutable when you consider the @MustCall types that are not + // recorded in Obligation's representation.) Could you clarify? I found the first paragraph + // confusing, including "correspond to". /** - * Returns the resource alias in this Obligation's resource alias set corresponding to {@code - * expression} if one is present. Otherwise, returns null. + * A resource alias is a reference through which a must-call obligation can be satisfied. Any + * must-call obligation might be satisfiable through one or more resource aliases. An {@link + * Obligation} tracks one set of resource aliases that correspond to one must-call obligation in + * the program. * - * @param expression a Java expression - * @return the resource alias corresponding to {@code expression} if one is present; otherwise, - * null + *

          A resource alias is always owning; non-owning aliases are, by definition, not tracked. + * + *

          Internally, a resource alias is represented by a pair of a {@link JavaExpression} (the + * "reference" through which the must-call obligations for the alias set to which it belongs can + * be satisfied) and a tree that "assigns" the reference. */ - private @Nullable ResourceAlias getResourceAlias(JavaExpression expression) { - for (ResourceAlias alias : resourceAliases) { - if (alias.reference.equals(expression)) { - return alias; + /*package-private*/ static class ResourceAlias { + + /** An expression from the source code or a temporary variable for an expression. */ + public final JavaExpression reference; + + /** The element for {@link #reference}. */ + public final Element element; + + /** The tree at which {@code reference} was assigned, for the purpose of error reporting. */ + public final Tree tree; + + /** + * Was this ResourceAlias derived from a parameter to a method that was annotated as {@link + * MustCallAlias}? If so, the obligation containing this resource alias must be discharged + * only in one of the following ways: + * + *

            + *
          • it is passed to another method or constructor in an @MustCallAlias position, and + * then the containing method returns that method’s result, or the call is a super() + * constructor call annotated with {@link MustCallAlias}, or + *
          • it is stored in an owning field of the class under analysis + *
          + */ + public final boolean derivedFromMustCallAliasParam; + + /** + * Create a new resource alias. This constructor should only be used if the resource alias + * was not derived from a method parameter annotated as {@link MustCallAlias}. + * + * @param reference the local variable + * @param tree the tree + */ + public ResourceAlias(LocalVariable reference, Tree tree) { + this(reference, reference.getElement(), tree); + } + + /** + * Create a new resource alias. This constructor should only be used if the resource alias + * was not derived from a method parameter annotated as {@link MustCallAlias}. + * + * @param reference the reference + * @param element the element for the given reference + * @param tree the tree + */ + public ResourceAlias(JavaExpression reference, Element element, Tree tree) { + this(reference, element, tree, false); + } + + /** + * Create a new resource alias. + * + * @param reference the local variable + * @param element the element for the reference + * @param tree the tree + * @param derivedFromMustCallAliasParam true iff this resource alias was created because of + * an {@link MustCallAlias} parameter + */ + public ResourceAlias( + JavaExpression reference, + Element element, + Tree tree, + boolean derivedFromMustCallAliasParam) { + this.reference = reference; + this.element = element; + this.tree = tree; + this.derivedFromMustCallAliasParam = derivedFromMustCallAliasParam; + } + + @Override + public String toString() { + return "(ResourceAlias: reference: " + reference + " |||| tree: " + tree + ")"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ResourceAlias that = (ResourceAlias) o; + return reference.equals(that.reference) && tree.equals(that.tree); + } + + @Override + public int hashCode() { + return Objects.hash(reference, tree); + } + + /** + * Returns an appropriate String for representing this in an error message. In particular, + * if {@link #reference} is a temporary variable, we return the String representation of + * {@link #tree}, to avoid exposing the temporary name (which has no meaning for the user) + * in the error message + * + * @return an appropriate String for representing this in an error message + */ + public String stringForErrorMessage() { + String referenceStr = reference.toString(); + // We assume that any temporary variable name will not be a syntactically-valid + // identifier or keyword. + return !SourceVersion.isIdentifier(referenceStr) ? tree.toString() : referenceStr; } - } - return null; } /** - * Returns true if this contains a resource alias corresponding to {@code localVariableNode}, - * meaning that calling the required methods on {@code localVariableNode} is sufficient to - * satisfy the must-call obligation this object represents. + * Creates a consistency analyzer. Typically, the type factory's postAnalyze method would + * instantiate a new consistency analyzer using this constructor and then call {@link + * #analyze(ControlFlowGraph)}. * - * @param localVariableNode a local variable node - * @return true if a resource alias corresponding to {@code localVariableNode} is present + * @param typeFactory the type factory + * @param analysis the analysis from the type factory. Usually this would have protected access, + * so this constructor cannot get it directly. */ - private boolean canBeSatisfiedThrough(LocalVariableNode localVariableNode) { - return getResourceAlias(localVariableNode) != null; + /*package-private*/ MustCallConsistencyAnalyzer( + ResourceLeakAnnotatedTypeFactory typeFactory, ResourceLeakAnalysis analysis) { + this.typeFactory = typeFactory; + this.checker = (ResourceLeakChecker) typeFactory.getChecker(); + this.analysis = analysis; + this.permitStaticOwning = checker.hasOption("permitStaticOwning"); + this.permitInitializationLeak = checker.hasOption("permitInitializationLeak"); + this.noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); + this.countMustCall = checker.hasOption(ResourceLeakChecker.COUNT_MUST_CALL); } /** - * Does this Obligation contain any resource aliases that were derived from {@link - * MustCallAlias} parameters? + * The main function of the consistency dataflow analysis. The analysis tracks dataflow facts + * ("Obligations") of type {@link Obligation}, each representing a set of owning resource + * aliases for some value with a non-empty {@code @MustCall} obligation. The set of tracked + * Obligations is guaranteed to include at least one Obligation for each actual resource in the + * program, but might include other, spurious Obligations, too (that is, it is a conservative + * over-approximation of the true Obligation set). + * + *

          The analysis improves its precision by removing Obligations from tracking when it can + * prove that they do not represent real resources. For example, it is not necessary to track + * expressions with empty {@code @MustCall} obligations, because they are trivially fulfilled. + * Nor is tracking non-owning aliases necessary, because by definition they cannot be used to + * fulfill must-call obligations. * - * @return the logical or of the {@link ResourceAlias#derivedFromMustCallAliasParam} fields of - * this Obligation's resource aliases + * @param cfg the control flow graph of the method to check */ - public boolean derivedFromMustCallAlias() { - for (ResourceAlias ra : resourceAliases) { - if (ra.derivedFromMustCallAliasParam) { - return true; + // TODO: This analysis is currently implemented directly using a worklist; in the future, it + // should be rewritten to use the dataflow framework of the Checker Framework. + /*package-private*/ void analyze(ControlFlowGraph cfg) { + // The `visited` set contains everything that has been added to the worklist, even if it has + // not yet been removed and analyzed. + Set visited = new HashSet<>(); + Deque worklist = new ArrayDeque<>(); + + // Add any owning parameters to the initial set of variables to track. + BlockWithObligations entry = + new BlockWithObligations(cfg.getEntryBlock(), computeOwningParameters(cfg)); + worklist.add(entry); + visited.add(entry); + + while (!worklist.isEmpty()) { + BlockWithObligations current = worklist.remove(); + propagateObligationsToSuccessorBlocks( + cfg, current.obligations, current.block, visited, worklist); } - } - return false; } /** - * Gets the must-call methods (i.e. the list of methods that must be called to satisfy the - * must-call obligation) of each resource alias represented by this Obligation. - * - * @param rlAtf a Resource Leak Annotated Type Factory - * @param mcStore a CFStore produced by the MustCall checker's dataflow analysis. If this is - * null, then the default MustCall type of each variable's class will be used. - * @return a map from each resource alias of this to a list of its must-call method names, or - * null if the must-call obligations are unsatisfiable (i.e. the value of some tracked - * resource alias of this in the Must Call store is MustCallUnknown) + * Update a set of Obligations to account for a method or constructor invocation. + * + * @param obligations the Obligations to update + * @param node the method or constructor invocation + * @param exceptionType a description of the outgoing CFG edge from the node: null + * to indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given + * throwable class was thrown */ - public @Nullable Map> getMustCallMethods( - ResourceLeakAnnotatedTypeFactory rlAtf, @Nullable CFStore mcStore) { - Map> result = new HashMap<>(this.resourceAliases.size()); - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - rlAtf.getTypeFactoryOfSubchecker(MustCallChecker.class); - - for (ResourceAlias alias : this.resourceAliases) { - AnnotationMirror mcAnno = getMustCallValue(alias, mcStore, mustCallAnnotatedTypeFactory); - if (!AnnotationUtils.areSameByName(mcAnno, MustCall.class.getCanonicalName())) { - // MustCallUnknown; cannot be satisfied - return null; - } - List annoVals = rlAtf.getMustCallValues(mcAnno); - // Really, annoVals should never be empty here; we should not have created the - // obligation in the first place. - // TODO: add an assertion that annoVals is non-empty and address any failures - result.put(alias, annoVals); - } - return result; + private void updateObligationsForInvocation( + Set obligations, Node node, @Nullable TypeMirror exceptionType) { + removeObligationsAtOwnershipTransferToParameters(obligations, node, exceptionType); + if (node instanceof MethodInvocationNode + && typeFactory.canCreateObligations() + && typeFactory.hasCreatesMustCallFor((MethodInvocationNode) node)) { + checkCreatesMustCallForInvocation(obligations, (MethodInvocationNode) node); + // Count calls to @CreatesMustCallFor methods as creating new resources. Doing so could + // result in slightly over-counting, because @CreatesMustCallFor doesn't guarantee that + // a new resource is created: it just means that a new resource might have been created. + incrementNumMustCall(node); + } + + if (!shouldTrackInvocationResult(obligations, node)) { + return; + } + + if (typeFactory.declaredTypeHasMustCall(node.getTree())) { + // The incrementNumMustCall call above increments the count for the target of the + // @CreatesMustCallFor annotation. By contrast, this call increments the count for the + // return value of the method (which can't be the target of the annotation, because our + // syntax doesn't support that). + incrementNumMustCall(node); + } + updateObligationsWithInvocationResult(obligations, node); } /** - * Gets the must-call type associated with the given resource alias, falling on back on the - * declared type if there is no refined type for the alias in the store. + * Checks that an invocation of a CreatesMustCallFor method is valid. + * + *

          Such an invocation is valid if any of the conditions in {@link + * #isValidCreatesMustCallForExpression(Set, JavaExpression, TreePath)} is true for each + * expression in the argument to the CreatesMustCallFor annotation. As a special case, the + * invocation of a CreatesMustCallFor method with "this" as its expression is permitted in the + * constructor of the relevant class (invoking a constructor already creates an obligation). If + * none of these conditions are true for any of the expressions, this method issues a + * reset.not.owning error. + * + *

          For soundness, this method also guarantees that if any of the expressions in the + * CreatesMustCallFor annotation has a tracked Obligation, any tracked resource aliases of it + * will be removed (lest the analysis conclude that it is already closed because one of these + * aliases was closed before the method was invoked). Aliases created after the + * CreatesMustCallFor method is invoked are still permitted. * - * @param alias a resource alias - * @param mcStore the must-call checker's store - * @param mcAtf the must-call checker's annotated type factory - * @return the annotation from the must-call type hierarchy associated with {@code alias} + * @param obligations the currently-tracked Obligations; this value is side-effected if there is + * an Obligation in it which tracks any expression from the CreatesMustCallFor annotation as + * one of its resource aliases + * @param node a method invocation node, invoking a method with a CreatesMustCallFor annotation */ - private static AnnotationMirror getMustCallValue( - ResourceAlias alias, @Nullable CFStore mcStore, MustCallAnnotatedTypeFactory mcAtf) { - JavaExpression reference = alias.reference; - CFValue value = mcStore == null ? null : mcStore.getValue(reference); - if (value != null) { - AnnotationMirror result = - AnnotationUtils.getAnnotationByClass(value.getAnnotations(), MustCall.class); - if (result != null) { - return result; - } - } - - AnnotationMirror result = - mcAtf.getAnnotatedType(alias.element).getEffectiveAnnotationInHierarchy(mcAtf.TOP); - if (result != null && !AnnotationUtils.areSame(result, mcAtf.TOP)) { - return result; - } - // There wasn't an @MustCall annotation for it in the store and the type factory has no - // information, so fall back to the default must-call type for the class. - // TODO: we currently end up in this case when checking a call to the return type - // of a returns-receiver method on something with a MustCall type; for example, - // see tests/socket/ZookeeperReport6.java. We should instead use a poly type if we can. - TypeElement typeElt = TypesUtils.getTypeElement(reference.getType()); - if (typeElt == null) { - // typeElt is null if reference.getType() was not a class, interface, annotation - // type, or enum -- that is, was not an annotatable type. - // That happens rarely, such as when it is a wildcard type. In these cases, fall - // back on a safe default: top. - return mcAtf.TOP; - } - if (typeElt.asType().getKind() == TypeKind.VOID) { - // Void types can't have methods called on them, so returning bottom is safe. - return mcAtf.BOTTOM; - } - - return mcAtf.getAnnotatedType(typeElt).getAnnotationInHierarchy(mcAtf.TOP); - } + private void checkCreatesMustCallForInvocation( + Set obligations, MethodInvocationNode node) { + + TreePath currentPath = typeFactory.getPath(node.getTree()); + List cmcfExpressions = + CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( + node, typeFactory, typeFactory); + List missing = new ArrayList<>(0); + for (JavaExpression expression : cmcfExpressions) { + if (!isValidCreatesMustCallForExpression(obligations, expression, currentPath)) { + missing.add(expression); + } + } - @Override - public String toString() { - return "Obligation: resourceAliases=" - + Iterables.toString(resourceAliases) - + ", whenToEnforce=" - + whenToEnforce; - } + if (missing.isEmpty()) { + // All expressions matched one of the rules, so the invocation is valid. + return; + } - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - Obligation that = (Obligation) obj; - return this.resourceAliases.equals(that.resourceAliases) - && this.whenToEnforce.equals(that.whenToEnforce); - } + // Special case for invocations of CreatesMustCallFor("this") methods in the constructor. + if (missing.size() == 1) { + JavaExpression expression = missing.get(0); + if (expression instanceof ThisReference && TreePathUtil.inConstructor(currentPath)) { + return; + } + } - @Override - public int hashCode() { - return Objects.hash(resourceAliases, whenToEnforce); + StringJoiner missingStrs = new StringJoiner(","); + for (JavaExpression m : missing) { + String s = m.toString(); + missingStrs.add(s.equals("this") ? s + " of type " + m.getType() : s); + } + checker.reportError( + node.getTree(), + "reset.not.owning", + node.getTarget().getMethod().getSimpleName().toString(), + missingStrs.toString()); } - } - - // Is there a different Obligation on every line of the program, or is Obligation mutable? - // (Or maybe Obligation is abstractly mutable when you consider the @MustCall types that are not - // recorded in Obligation's representation.) Could you clarify? I found the first paragraph - // confusing, including "correspond to". - /** - * A resource alias is a reference through which a must-call obligation can be satisfied. Any - * must-call obligation might be satisfiable through one or more resource aliases. An {@link - * Obligation} tracks one set of resource aliases that correspond to one must-call obligation in - * the program. - * - *

          A resource alias is always owning; non-owning aliases are, by definition, not tracked. - * - *

          Internally, a resource alias is represented by a pair of a {@link JavaExpression} (the - * "reference" through which the must-call obligations for the alias set to which it belongs can - * be satisfied) and a tree that "assigns" the reference. - */ - /*package-private*/ static class ResourceAlias { - - /** An expression from the source code or a temporary variable for an expression. */ - public final JavaExpression reference; - - /** The element for {@link #reference}. */ - public final Element element; - - /** The tree at which {@code reference} was assigned, for the purpose of error reporting. */ - public final Tree tree; /** - * Was this ResourceAlias derived from a parameter to a method that was annotated as {@link - * MustCallAlias}? If so, the obligation containing this resource alias must be discharged only - * in one of the following ways: + * Checks the validity of the given expression from an invoked method's {@link + * org.checkerframework.checker.mustcall.qual.CreatesMustCallFor} annotation. Helper method for + * {@link #checkCreatesMustCallForInvocation(Set, MethodInvocationNode)}. + * + *

          An expression is valid if one of the following conditions is true: * *

            - *
          • it is passed to another method or constructor in an @MustCallAlias position, and then - * the containing method returns that method’s result, or the call is a super() - * constructor call annotated with {@link MustCallAlias}, or - *
          • it is stored in an owning field of the class under analysis + *
          • 1) the expression is an owning pointer, + *
          • 2) the expression already has a tracked Obligation (i.e. there is already a resource + * alias in some Obligation's resource alias set that refers to the expression), or + *
          • 3) the method in which the invocation occurs also has an @CreatesMustCallFor + * annotation, with the same expression. *
          + * + * @param obligations the currently-tracked Obligations; this value is side-effected if there is + * an Obligation in it which tracks {@code expression} as one of its resource aliases + * @param expression an element of a method's @CreatesMustCallFor annotation + * @param invocationPath the path to the invocation of the method from whose @CreateMustCallFor + * annotation {@code expression} came + * @return true iff the expression is valid, as defined above */ - public final boolean derivedFromMustCallAliasParam; + private boolean isValidCreatesMustCallForExpression( + Set obligations, JavaExpression expression, TreePath invocationPath) { + if (expression instanceof FieldAccess) { + Element elt = ((FieldAccess) expression).getField(); + if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { + // The expression is an Owning field. This satisfies case 1. + return true; + } + } else if (expression instanceof LocalVariable) { + Element elt = ((LocalVariable) expression).getElement(); + if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { + // The expression is an Owning formal parameter. Note that this cannot actually + // be a local variable (despite expressions's type being LocalVariable) because + // the @Owning annotation can only be written on methods, parameters, and fields; + // formal parameters are also represented by LocalVariable in the bodies of methods. + // This satisfies case 1. + return true; + } else { + Obligation toRemove = null; + Obligation toAdd = null; + for (Obligation obligation : obligations) { + ResourceAlias alias = obligation.getResourceAlias(expression); + if (alias != null) { + // This satisfies case 2 above. Remove all its aliases, then return below. + if (toRemove != null) { + throw new TypeSystemError( + "tried to remove multiple sets containing a reset expression at" + + " once"); + } + toRemove = obligation; + toAdd = new Obligation(ImmutableSet.of(alias), obligation.whenToEnforce); + } + } + + if (toRemove != null) { + obligations.remove(toRemove); + obligations.add(toAdd); + // This satisfies case 2. + return true; + } + } + } + + // TODO: Getting this every time is inefficient if a method has many @CreatesMustCallFor + // annotations, but that should be rare. + MethodTree callerMethodTree = TreePathUtil.enclosingMethod(invocationPath); + if (callerMethodTree == null) { + return false; + } + ExecutableElement callerMethodElt = TreeUtils.elementFromDeclaration(callerMethodTree); + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + List callerCmcfValues = + ResourceLeakVisitor.getCreatesMustCallForValues( + callerMethodElt, mcAtf, typeFactory); + if (callerCmcfValues.isEmpty()) { + return false; + } + for (String callerCmcfValue : callerCmcfValues) { + JavaExpression callerTarget; + try { + callerTarget = + StringToJavaExpression.atMethodBody( + callerCmcfValue, callerMethodTree, checker); + } catch (JavaExpressionParseException e) { + // Do not issue an error here, because it would be a duplicate. + // The error will be issued by the Transfer class of the checker, + // via the CreatesMustCallForElementSupplier interface. + callerTarget = null; + } + + if (areSame(expression, callerTarget)) { + // This satisfies case 3. + return true; + } + } + return false; + } /** - * Create a new resource alias. This constructor should only be used if the resource alias was - * not derived from a method parameter annotated as {@link MustCallAlias}. + * Checks whether the two JavaExpressions are the same. This is identical to calling equals() on + * one of them, with two exceptions: the second expression can be null, and {@code this} + * references are compared using their underlying type. (ThisReference#equals always returns + * true, which is probably a bug and isn't accurate in the case of nested classes.) * - * @param reference the local variable - * @param tree the tree + * @param target a JavaExpression + * @param enclosingTarget another, possibly null, JavaExpression + * @return true iff they represent the same program element */ - public ResourceAlias(LocalVariable reference, Tree tree) { - this(reference, reference.getElement(), tree); + private boolean areSame(JavaExpression target, @Nullable JavaExpression enclosingTarget) { + if (enclosingTarget == null) { + return false; + } + if (enclosingTarget instanceof ThisReference && target instanceof ThisReference) { + return enclosingTarget.getType().toString().equals(target.getType().toString()); + } else { + return enclosingTarget.equals(target); + } } /** - * Create a new resource alias. This constructor should only be used if the resource alias was - * not derived from a method parameter annotated as {@link MustCallAlias}. + * Given a node representing a method or constructor call, updates the set of Obligations to + * account for the result, which is treated as a new resource alias. Adds the new resource alias + * to the set of an Obligation in {@code obligations}: either an existing Obligation if the + * result is definitely resource-aliased with it, or a new Obligation if not. * - * @param reference the reference - * @param element the element for the given reference - * @param tree the tree + * @param obligations the currently-tracked Obligations. This is always side-effected: either a + * new resource alias is added to the resource alias set of an existing Obligation, or a new + * Obligation with a single-element resource alias set is created and added. + * @param node the invocation node whose result is to be tracked; must be {@link + * MethodInvocationNode} or {@link ObjectCreationNode} */ - public ResourceAlias(JavaExpression reference, Element element, Tree tree) { - this(reference, element, tree, false); + /*package-private*/ void updateObligationsWithInvocationResult( + Set obligations, Node node) { + Tree tree = node.getTree(); + // Only track the result of the call if there is a temporary variable for the call node + // (because if there is no temporary, then the invocation must produce an untrackable value, + // such as a primitive type). + LocalVariableNode tmpVar = typeFactory.getTempVarForNode(node); + if (tmpVar == null) { + return; + } + + // `mustCallAliases` is a (possibly-empty) list of arguments passed in a MustCallAlias + // position. + List mustCallAliases = getMustCallAliasArgumentNodes(node); + // If call returns @This, add the receiver to mustCallAliases. + if (node instanceof MethodInvocationNode + && typeFactory.returnsThis((MethodInvocationTree) tree)) { + mustCallAliases.add( + removeCastsAndGetTmpVarIfPresent( + ((MethodInvocationNode) node).getTarget().getReceiver())); + } + + if (mustCallAliases.isEmpty()) { + // If mustCallAliases is an empty List, add tmpVarAsResourceAlias to a new set. + ResourceAlias tmpVarAsResourceAlias = + new ResourceAlias(new LocalVariable(tmpVar), tree); + obligations.add( + new Obligation(ImmutableSet.of(tmpVarAsResourceAlias), MethodExitKind.ALL)); + } else { + for (Node mustCallAlias : mustCallAliases) { + if (mustCallAlias instanceof FieldAccessNode) { + // Do not track the call result if the MustCallAlias argument is a field. + // Handling of @Owning fields is a completely separate check, and there is never + // a need to track an alias of a non-@Owning field, as by definition such a + // field does not have must-call obligations! + } else if (mustCallAlias instanceof LocalVariableNode) { + // If mustCallAlias is a local variable already being tracked, add + // tmpVarAsResourceAlias to the set containing mustCallAlias. + Obligation obligationContainingMustCallAlias = + getObligationForVar(obligations, (LocalVariableNode) mustCallAlias); + if (obligationContainingMustCallAlias != null) { + ResourceAlias tmpVarAsResourceAlias = + new ResourceAlias( + new LocalVariable(tmpVar), + tmpVar.getElement(), + tree, + obligationContainingMustCallAlias + .derivedFromMustCallAlias()); + Set newResourceAliasSet = + FluentIterable.from( + obligationContainingMustCallAlias.resourceAliases) + .append(tmpVarAsResourceAlias) + .toSet(); + obligations.remove(obligationContainingMustCallAlias); + obligations.add( + new Obligation( + newResourceAliasSet, + obligationContainingMustCallAlias.whenToEnforce)); + // It is not an error if there is no Obligation containing the must-call + // alias. In that case, what has usually happened is that no Obligation was + // created in the first place. + // For example, when checking the invocation of a "wrapper stream" + // constructor, if the argument in the must-call alias position is some + // stream with no must-call obligations like a ByteArrayInputStream, then no + // Obligation object will have been created for it and therefore + // obligationContainingMustCallAlias will be null. + } + } + } + } } /** - * Create a new resource alias. + * Returns true if the result of the given method or constructor invocation node should be + * tracked in {@code obligations}. In some cases, there is no need to track the result because + * the must-call obligations are already satisfied in some other way or there cannot possibly be + * must-call obligations because of the structure of the code. + * + *

          Specifically, an invocation result does NOT need to be tracked if any of the following is + * true: + * + *

            + *
          • The invocation is a call to a {@code this()} or {@code super()} constructor. + *
          • The method's return type is annotated with MustCallAlias and the argument passed in + * this invocation in the corresponding position is an owning field. + *
          • The method's return type is non-owning, which can either be because the method has no + * return type or because the return type is annotated with {@link NotOwning}. + *
          + * + *

          This method can also side-effect {@code obligations}, if node is a super or this + * constructor call with MustCallAlias annotations, by removing that Obligation. * - * @param reference the local variable - * @param element the element for the reference - * @param tree the tree - * @param derivedFromMustCallAliasParam true iff this resource alias was created because of an - * {@link MustCallAlias} parameter + * @param obligations the current set of Obligations, which may be side-effected + * @param node the invocation node to check; must be {@link MethodInvocationNode} or {@link + * ObjectCreationNode} + * @return true iff the result of {@code node} should be tracked in {@code obligations} */ - public ResourceAlias( - JavaExpression reference, - Element element, - Tree tree, - boolean derivedFromMustCallAliasParam) { - this.reference = reference; - this.element = element; - this.tree = tree; - this.derivedFromMustCallAliasParam = derivedFromMustCallAliasParam; - } + private boolean shouldTrackInvocationResult(Set obligations, Node node) { + Tree callTree = node.getTree(); + if (callTree.getKind() == Tree.Kind.NEW_CLASS) { + // Constructor results from new expressions are tracked as long as the declared type has + // a non-empty @MustCall annotation. + NewClassTree newClassTree = (NewClassTree) callTree; + ExecutableElement executableElement = TreeUtils.elementFromUse(newClassTree); + TypeElement typeElt = + TypesUtils.getTypeElement(ElementUtils.getType(executableElement)); + return typeElt == null + || !typeFactory.hasEmptyMustCallValue(typeElt) + || !typeFactory.hasEmptyMustCallValue(newClassTree); + } - @Override - public String toString() { - return "(ResourceAlias: reference: " + reference + " |||| tree: " + tree + ")"; + // Now callTree.getKind() == Tree.Kind.METHOD_INVOCATION. + MethodInvocationTree methodInvokeTree = (MethodInvocationTree) callTree; + + if (TreeUtils.isSuperConstructorCall(methodInvokeTree) + || TreeUtils.isThisConstructorCall(methodInvokeTree)) { + List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); + // If there is a MustCallAlias argument that is also in the set of Obligations, then + // remove it; its must-call obligation has been fulfilled by being passed on to the + // MustCallAlias constructor (because a this/super constructor call can only occur in + // the body of another constructor). + for (Node mustCallAliasArgument : mustCallAliasArguments) { + if (mustCallAliasArgument instanceof LocalVariableNode) { + removeObligationsContainingVar( + obligations, (LocalVariableNode) mustCallAliasArgument); + } + } + return false; + } + return !returnTypeIsMustCallAliasWithUntrackable((MethodInvocationNode) node) + && shouldTrackReturnType((MethodInvocationNode) node); } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ResourceAlias that = (ResourceAlias) o; - return reference.equals(that.reference) && tree.equals(that.tree); + /** + * Returns true if this node represents a method invocation of a must-call-alias method, where + * the argument in the must-call-alias position is untrackable: an owning field or a pointer + * that is guaranteed to be non-owning, such as {@code "this"} or a non-owning field. Owning + * fields are handled by the rest of the checker, not by this algorithm, so they are + * "untrackable". Non-owning fields and this nodes are guaranteed to be non-owning, and are + * therefore also "untrackable". Because both owning and non-owning fields are untrackable (and + * there are no other kinds of fields), this method returns true for all field accesses. + * + * @param node a method invocation node + * @return true if this is the invocation of a method whose return type is MCA with an owning + * field or a definitely non-owning pointer + */ + private boolean returnTypeIsMustCallAliasWithUntrackable(MethodInvocationNode node) { + List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); + for (Node mustCallAliasArg : mustCallAliasArguments) { + if (!(mustCallAliasArg instanceof FieldAccessNode + || mustCallAliasArg instanceof ThisNode)) { + return false; + } + } + return !mustCallAliasArguments.isEmpty(); } - @Override - public int hashCode() { - return Objects.hash(reference, tree); + /** + * Checks if {@code node} is either directly enclosed by a {@link TypeCastNode}, by looking at + * the successor block in the CFG. In this case the enclosing operator is a "no-op" that + * evaluates to the same value as {@code node}. This method is only used within {@link + * #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, Deque)} to ensure + * Obligations are propagated to cast nodes properly. It relies on the assumption that a {@link + * TypeCastNode} will only appear in a CFG as the first node in a block. + * + * @param node the CFG node + * @return {@code true} if {@code node} is in a {@link SingleSuccessorBlock} {@code b}, the + * first {@link Node} in {@code b}'s successor block is a {@link TypeCastNode}, and {@code + * node} is an operand of the successor node; {@code false} otherwise + */ + private boolean inCast(Node node) { + if (!(node.getBlock() instanceof SingleSuccessorBlock)) { + return false; + } + Block successorBlock = ((SingleSuccessorBlock) node.getBlock()).getSuccessor(); + if (successorBlock != null) { + List succNodes = successorBlock.getNodes(); + if (succNodes.size() > 0) { + Node succNode = succNodes.get(0); + if (succNode instanceof TypeCastNode) { + return ((TypeCastNode) succNode).getOperand().equals(node); + } + } + } + return false; } /** - * Returns an appropriate String for representing this in an error message. In particular, if - * {@link #reference} is a temporary variable, we return the String representation of {@link - * #tree}, to avoid exposing the temporary name (which has no meaning for the user) in the error - * message + * Transfer ownership of any locals passed as arguments to {@code @Owning} parameters at a + * method or constructor call by removing the Obligations corresponding to those locals. * - * @return an appropriate String for representing this in an error message + * @param obligations the current set of Obligations, which is side-effected to remove + * Obligations for locals that are passed as owning parameters to the method or constructor + * @param node a method or constructor invocation node + * @param exceptionType a description of the outgoing CFG edge from the node: null + * to indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given + * throwable class was thrown */ - public String stringForErrorMessage() { - String referenceStr = reference.toString(); - // We assume that any temporary variable name will not be a syntactically-valid - // identifier or keyword. - return !SourceVersion.isIdentifier(referenceStr) ? tree.toString() : referenceStr; - } - } - - /** - * Creates a consistency analyzer. Typically, the type factory's postAnalyze method would - * instantiate a new consistency analyzer using this constructor and then call {@link - * #analyze(ControlFlowGraph)}. - * - * @param typeFactory the type factory - * @param analysis the analysis from the type factory. Usually this would have protected access, - * so this constructor cannot get it directly. - */ - /*package-private*/ MustCallConsistencyAnalyzer( - ResourceLeakAnnotatedTypeFactory typeFactory, ResourceLeakAnalysis analysis) { - this.typeFactory = typeFactory; - this.checker = (ResourceLeakChecker) typeFactory.getChecker(); - this.analysis = analysis; - this.permitStaticOwning = checker.hasOption("permitStaticOwning"); - this.permitInitializationLeak = checker.hasOption("permitInitializationLeak"); - this.noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP); - this.countMustCall = checker.hasOption(ResourceLeakChecker.COUNT_MUST_CALL); - } - - /** - * The main function of the consistency dataflow analysis. The analysis tracks dataflow facts - * ("Obligations") of type {@link Obligation}, each representing a set of owning resource aliases - * for some value with a non-empty {@code @MustCall} obligation. The set of tracked Obligations is - * guaranteed to include at least one Obligation for each actual resource in the program, but - * might include other, spurious Obligations, too (that is, it is a conservative - * over-approximation of the true Obligation set). - * - *

          The analysis improves its precision by removing Obligations from tracking when it can prove - * that they do not represent real resources. For example, it is not necessary to track - * expressions with empty {@code @MustCall} obligations, because they are trivially fulfilled. Nor - * is tracking non-owning aliases necessary, because by definition they cannot be used to fulfill - * must-call obligations. - * - * @param cfg the control flow graph of the method to check - */ - // TODO: This analysis is currently implemented directly using a worklist; in the future, it - // should be rewritten to use the dataflow framework of the Checker Framework. - /*package-private*/ void analyze(ControlFlowGraph cfg) { - // The `visited` set contains everything that has been added to the worklist, even if it has - // not yet been removed and analyzed. - Set visited = new HashSet<>(); - Deque worklist = new ArrayDeque<>(); - - // Add any owning parameters to the initial set of variables to track. - BlockWithObligations entry = - new BlockWithObligations(cfg.getEntryBlock(), computeOwningParameters(cfg)); - worklist.add(entry); - visited.add(entry); - - while (!worklist.isEmpty()) { - BlockWithObligations current = worklist.remove(); - propagateObligationsToSuccessorBlocks( - cfg, current.obligations, current.block, visited, worklist); - } - } - - /** - * Update a set of Obligations to account for a method or constructor invocation. - * - * @param obligations the Obligations to update - * @param node the method or constructor invocation - * @param exceptionType a description of the outgoing CFG edge from the node: null to - * indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given - * throwable class was thrown - */ - private void updateObligationsForInvocation( - Set obligations, Node node, @Nullable TypeMirror exceptionType) { - removeObligationsAtOwnershipTransferToParameters(obligations, node, exceptionType); - if (node instanceof MethodInvocationNode - && typeFactory.canCreateObligations() - && typeFactory.hasCreatesMustCallFor((MethodInvocationNode) node)) { - checkCreatesMustCallForInvocation(obligations, (MethodInvocationNode) node); - // Count calls to @CreatesMustCallFor methods as creating new resources. Doing so could - // result in slightly over-counting, because @CreatesMustCallFor doesn't guarantee that - // a new resource is created: it just means that a new resource might have been created. - incrementNumMustCall(node); - } + private void removeObligationsAtOwnershipTransferToParameters( + Set obligations, Node node, @Nullable TypeMirror exceptionType) { - if (!shouldTrackInvocationResult(obligations, node)) { - return; - } + if (exceptionType != null) { + // Do not transfer ownership if the called method throws an exception. + return; + } - if (typeFactory.declaredTypeHasMustCall(node.getTree())) { - // The incrementNumMustCall call above increments the count for the target of the - // @CreatesMustCallFor annotation. By contrast, this call increments the count for the - // return value of the method (which can't be the target of the annotation, because our - // syntax doesn't support that). - incrementNumMustCall(node); - } - updateObligationsWithInvocationResult(obligations, node); - } - - /** - * Checks that an invocation of a CreatesMustCallFor method is valid. - * - *

          Such an invocation is valid if any of the conditions in {@link - * #isValidCreatesMustCallForExpression(Set, JavaExpression, TreePath)} is true for each - * expression in the argument to the CreatesMustCallFor annotation. As a special case, the - * invocation of a CreatesMustCallFor method with "this" as its expression is permitted in the - * constructor of the relevant class (invoking a constructor already creates an obligation). If - * none of these conditions are true for any of the expressions, this method issues a - * reset.not.owning error. - * - *

          For soundness, this method also guarantees that if any of the expressions in the - * CreatesMustCallFor annotation has a tracked Obligation, any tracked resource aliases of it will - * be removed (lest the analysis conclude that it is already closed because one of these aliases - * was closed before the method was invoked). Aliases created after the CreatesMustCallFor method - * is invoked are still permitted. - * - * @param obligations the currently-tracked Obligations; this value is side-effected if there is - * an Obligation in it which tracks any expression from the CreatesMustCallFor annotation as - * one of its resource aliases - * @param node a method invocation node, invoking a method with a CreatesMustCallFor annotation - */ - private void checkCreatesMustCallForInvocation( - Set obligations, MethodInvocationNode node) { - - TreePath currentPath = typeFactory.getPath(node.getTree()); - List cmcfExpressions = - CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( - node, typeFactory, typeFactory); - List missing = new ArrayList<>(0); - for (JavaExpression expression : cmcfExpressions) { - if (!isValidCreatesMustCallForExpression(obligations, expression, currentPath)) { - missing.add(expression); - } - } + if (noLightweightOwnership) { + // Never transfer ownership to parameters, matching the default in the analysis built + // into Eclipse. + return; + } - if (missing.isEmpty()) { - // All expressions matched one of the rules, so the invocation is valid. - return; + List arguments = getArgumentsOfInvocation(node); + List parameters = getParametersOfInvocation(node); + + if (arguments.size() != parameters.size()) { + // This could happen, e.g., with varargs, or with strange cases like generated Enum + // constructors. In the varargs case (i.e. if the varargs parameter is owning), + // only the first of the varargs arguments will actually get transferred: the second + // and later varargs arguments will continue to be tracked at the call-site. + // For now, just skip this case - the worst that will happen is a false positive in + // cases like the varargs one described above. + // TODO allow for ownership transfer here if needed in future + return; + } + for (int i = 0; i < arguments.size(); i++) { + Node n = removeCastsAndGetTmpVarIfPresent(arguments.get(i)); + if (n instanceof LocalVariableNode) { + LocalVariableNode local = (LocalVariableNode) n; + if (varTrackedInObligations(obligations, local)) { + + // check if parameter has an @Owning annotation + VariableElement parameter = parameters.get(i); + if (typeFactory.hasOwning(parameter)) { + Obligation localObligation = getObligationForVar(obligations, local); + // Passing to an owning parameter is not sufficient to resolve the + // obligation created from a MustCallAlias parameter, because the + // containing method must actually return the value. + if (!localObligation.derivedFromMustCallAlias()) { + // Transfer ownership! + obligations.remove(localObligation); + } + } + } + } + } } - // Special case for invocations of CreatesMustCallFor("this") methods in the constructor. - if (missing.size() == 1) { - JavaExpression expression = missing.get(0); - if (expression instanceof ThisReference && TreePathUtil.inConstructor(currentPath)) { - return; - } + /** + * If the return type of the enclosing method is {@code @Owning}, treat the must-call + * obligations of the return expression as satisfied by removing all references to them from + * {@code obligations}. + * + * @param obligations the current set of tracked Obligations. If ownership is transferred, it is + * side-effected to remove any Obligations that are resource-aliased to the return node. + * @param cfg the CFG of the enclosing method + * @param node a return node + */ + private void updateObligationsForOwningReturn( + Set obligations, ControlFlowGraph cfg, ReturnNode node) { + if (isTransferOwnershipAtReturn(cfg)) { + Node returnExpr = node.getResult(); + returnExpr = getTempVarOrNode(returnExpr); + if (returnExpr instanceof LocalVariableNode) { + removeObligationsContainingVar(obligations, (LocalVariableNode) returnExpr); + } + } } - StringJoiner missingStrs = new StringJoiner(","); - for (JavaExpression m : missing) { - String s = m.toString(); - missingStrs.add(s.equals("this") ? s + " of type " + m.getType() : s); + /** + * Helper method that gets the temporary node corresponding to {@code node}, if one exists. If + * not, this method returns its input. + * + * @param node a node + * @return the temporary for node, or node if no temporary exists + */ + /*package-private*/ Node getTempVarOrNode(Node node) { + Node temp = typeFactory.getTempVarForNode(node); + if (temp != null) { + return temp; + } + return node; } - checker.reportError( - node.getTree(), - "reset.not.owning", - node.getTarget().getMethod().getSimpleName().toString(), - missingStrs.toString()); - } - - /** - * Checks the validity of the given expression from an invoked method's {@link - * org.checkerframework.checker.mustcall.qual.CreatesMustCallFor} annotation. Helper method for - * {@link #checkCreatesMustCallForInvocation(Set, MethodInvocationNode)}. - * - *

          An expression is valid if one of the following conditions is true: - * - *

            - *
          • 1) the expression is an owning pointer, - *
          • 2) the expression already has a tracked Obligation (i.e. there is already a resource - * alias in some Obligation's resource alias set that refers to the expression), or - *
          • 3) the method in which the invocation occurs also has an @CreatesMustCallFor annotation, - * with the same expression. - *
          - * - * @param obligations the currently-tracked Obligations; this value is side-effected if there is - * an Obligation in it which tracks {@code expression} as one of its resource aliases - * @param expression an element of a method's @CreatesMustCallFor annotation - * @param invocationPath the path to the invocation of the method from whose @CreateMustCallFor - * annotation {@code expression} came - * @return true iff the expression is valid, as defined above - */ - private boolean isValidCreatesMustCallForExpression( - Set obligations, JavaExpression expression, TreePath invocationPath) { - if (expression instanceof FieldAccess) { - Element elt = ((FieldAccess) expression).getField(); - if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { - // The expression is an Owning field. This satisfies case 1. - return true; - } - } else if (expression instanceof LocalVariable) { - Element elt = ((LocalVariable) expression).getElement(); - if (!noLightweightOwnership && typeFactory.hasOwning(elt)) { - // The expression is an Owning formal parameter. Note that this cannot actually - // be a local variable (despite expressions's type being LocalVariable) because - // the @Owning annotation can only be written on methods, parameters, and fields; - // formal parameters are also represented by LocalVariable in the bodies of methods. - // This satisfies case 1. - return true; - } else { - Obligation toRemove = null; - Obligation toAdd = null; - for (Obligation obligation : obligations) { - ResourceAlias alias = obligation.getResourceAlias(expression); - if (alias != null) { - // This satisfies case 2 above. Remove all its aliases, then return below. - if (toRemove != null) { - throw new TypeSystemError( - "tried to remove multiple sets containing a reset expression at" + " once"); - } - toRemove = obligation; - toAdd = new Obligation(ImmutableSet.of(alias), obligation.whenToEnforce); - } + + /** + * Should ownership be transferred to the return type of the method corresponding to a CFG? + * Returns true when there is no {@link NotOwning} annotation on the return type. + * + * @param cfg the CFG of the method + * @return true iff ownership should be transferred to the return type of the method + * corresponding to a CFG + */ + private boolean isTransferOwnershipAtReturn(ControlFlowGraph cfg) { + if (noLightweightOwnership) { + // If not using LO, default to always transfer at return, just like Eclipse does. + return true; } - if (toRemove != null) { - obligations.remove(toRemove); - obligations.add(toAdd); - // This satisfies case 2. - return true; + UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); + if (underlyingAST instanceof UnderlyingAST.CFGMethod) { + // TODO: lambdas? In that case false is returned below, which means that ownership will + // not be transferred. + MethodTree method = ((UnderlyingAST.CFGMethod) underlyingAST).getMethod(); + ExecutableElement executableElement = TreeUtils.elementFromDeclaration(method); + return !typeFactory.hasNotOwning(executableElement); } - } + return false; } - // TODO: Getting this every time is inefficient if a method has many @CreatesMustCallFor - // annotations, but that should be rare. - MethodTree callerMethodTree = TreePathUtil.enclosingMethod(invocationPath); - if (callerMethodTree == null) { - return false; - } - ExecutableElement callerMethodElt = TreeUtils.elementFromDeclaration(callerMethodTree); - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - List callerCmcfValues = - ResourceLeakVisitor.getCreatesMustCallForValues(callerMethodElt, mcAtf, typeFactory); - if (callerCmcfValues.isEmpty()) { - return false; + /** + * Updates a set of Obligations to account for an assignment. Assigning to an owning field might + * remove Obligations, assigning to a resource variable might remove obligations, assigning to a + * new local variable might modify an Obligation (by increasing the size of its resource alias + * set), etc. + * + * @param obligations the set of Obligations to update + * @param cfg the control flow graph that contains {@code assignmentNode} + * @param assignmentNode the assignment + */ + private void updateObligationsForAssignment( + Set obligations, ControlFlowGraph cfg, AssignmentNode assignmentNode) { + Node lhs = assignmentNode.getTarget(); + Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); + if (lhsElement == null) { + return; + } + // Use the temporary variable for the rhs if it exists. + Node rhs = NodeUtils.removeCasts(assignmentNode.getExpression()); + rhs = getTempVarOrNode(rhs); + + // Ownership transfer to @Owning field. + if (lhsElement.getKind() == ElementKind.FIELD) { + boolean isOwningField = !noLightweightOwnership && typeFactory.hasOwning(lhsElement); + // Check that the must-call obligations of the lhs have been satisfied, if the field is + // non-final and owning. + if (isOwningField + && typeFactory.canCreateObligations() + && !ElementUtils.isFinal(lhsElement)) { + checkReassignmentToField(obligations, assignmentNode); + } + + // Remove Obligations from local variables, now that the owning field is responsible. + // (When obligation creation is turned off, non-final fields cannot take ownership.) + if (isOwningField + && rhs instanceof LocalVariableNode + && (typeFactory.canCreateObligations() || ElementUtils.isFinal(lhsElement))) { + + LocalVariableNode rhsVar = (LocalVariableNode) rhs; + + MethodTree containingMethod = cfg.getContainingMethod(assignmentNode.getTree()); + boolean inConstructor = + containingMethod != null && TreeUtils.isConstructor(containingMethod); + + // Determine which obligations this field assignment can clear. In a constructor, + // assignments to `this.field` only clears obligations on normal return, since + // on exception `this` becomes inaccessible. + Set toClear; + if (inConstructor + && lhs instanceof FieldAccessNode + && ((FieldAccessNode) lhs).getReceiver() instanceof ThisNode) { + toClear = Collections.singleton(MethodExitKind.NORMAL_RETURN); + } else { + toClear = MethodExitKind.ALL; + } + + @Nullable Element enclosingElem = lhsElement.getEnclosingElement(); + @Nullable TypeElement enclosingType = + enclosingElem != null + ? ElementUtils.enclosingTypeElement(enclosingElem) + : null; + + // Assigning to an owning field is sufficient to clear a must-call alias obligation + // in a constructor, if the enclosing class has at most one @Owning field. If the + // class had multiple owning fields, then a soundness bug would occur: the must call + // alias relationship would allow the whole class' obligation to be fulfilled by + // closing only one of the parameters passed to the constructor (but the other + // owning fields might not actually have had their obligations fulfilled). See test + // case checker/tests/resourceleak/TwoOwningMCATest.java for an example. + if (hasAtMostOneOwningField(enclosingType)) { + removeObligationsContainingVar( + obligations, + rhsVar, + MustCallAliasHandling.NO_SPECIAL_HANDLING, + toClear); + } else { + removeObligationsContainingVar( + obligations, + rhsVar, + MustCallAliasHandling + .RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, + toClear); + } + + // Finally, if any obligations containing this var remain, then closing the field + // will satisfy them. Here we are overly cautious and only track final fields. In + // the future we could perhaps relax this guard with careful handling for field + // reassignments. + if (ElementUtils.isFinal(lhsElement)) { + addAliasToObligationsContainingVar( + obligations, + rhsVar, + new ResourceAlias( + JavaExpression.fromNode(lhs), lhsElement, lhs.getTree())); + } + } + } else if (lhs instanceof LocalVariableNode) { + LocalVariableNode lhsVar = (LocalVariableNode) lhs; + updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); + } } - for (String callerCmcfValue : callerCmcfValues) { - JavaExpression callerTarget; - try { - callerTarget = - StringToJavaExpression.atMethodBody(callerCmcfValue, callerMethodTree, checker); - } catch (JavaExpressionParseException e) { - // Do not issue an error here, because it would be a duplicate. - // The error will be issued by the Transfer class of the checker, - // via the CreatesMustCallForElementSupplier interface. - callerTarget = null; - } - - if (areSame(expression, callerTarget)) { - // This satisfies case 3. + + /** + * Returns true iff the given type element has 0 or 1 @Owning fields. + * + * @param element an element for a class + * @return true iff element has no more than 1 owning field + */ + private boolean hasAtMostOneOwningField(TypeElement element) { + List fields = + ElementUtils.getAllFieldsIn(element, typeFactory.getElementUtils()); + // Has an owning field already been encountered? + boolean hasOwningField = false; + for (VariableElement field : fields) { + if (typeFactory.hasOwning(field)) { + if (hasOwningField) { + return false; + } else { + hasOwningField = true; + } + } + } + // We haven't seen two owning fields, so there must be 1 or 0. return true; - } - } - return false; - } - - /** - * Checks whether the two JavaExpressions are the same. This is identical to calling equals() on - * one of them, with two exceptions: the second expression can be null, and {@code this} - * references are compared using their underlying type. (ThisReference#equals always returns true, - * which is probably a bug and isn't accurate in the case of nested classes.) - * - * @param target a JavaExpression - * @param enclosingTarget another, possibly null, JavaExpression - * @return true iff they represent the same program element - */ - private boolean areSame(JavaExpression target, @Nullable JavaExpression enclosingTarget) { - if (enclosingTarget == null) { - return false; - } - if (enclosingTarget instanceof ThisReference && target instanceof ThisReference) { - return enclosingTarget.getType().toString().equals(target.getType().toString()); - } else { - return enclosingTarget.equals(target); - } - } - - /** - * Given a node representing a method or constructor call, updates the set of Obligations to - * account for the result, which is treated as a new resource alias. Adds the new resource alias - * to the set of an Obligation in {@code obligations}: either an existing Obligation if the result - * is definitely resource-aliased with it, or a new Obligation if not. - * - * @param obligations the currently-tracked Obligations. This is always side-effected: either a - * new resource alias is added to the resource alias set of an existing Obligation, or a new - * Obligation with a single-element resource alias set is created and added. - * @param node the invocation node whose result is to be tracked; must be {@link - * MethodInvocationNode} or {@link ObjectCreationNode} - */ - /*package-private*/ void updateObligationsWithInvocationResult( - Set obligations, Node node) { - Tree tree = node.getTree(); - // Only track the result of the call if there is a temporary variable for the call node - // (because if there is no temporary, then the invocation must produce an untrackable value, - // such as a primitive type). - LocalVariableNode tmpVar = typeFactory.getTempVarForNode(node); - if (tmpVar == null) { - return; } - // `mustCallAliases` is a (possibly-empty) list of arguments passed in a MustCallAlias - // position. - List mustCallAliases = getMustCallAliasArgumentNodes(node); - // If call returns @This, add the receiver to mustCallAliases. - if (node instanceof MethodInvocationNode - && typeFactory.returnsThis((MethodInvocationTree) tree)) { - mustCallAliases.add( - removeCastsAndGetTmpVarIfPresent( - ((MethodInvocationNode) node).getTarget().getReceiver())); - } + /** + * Add a new alias to all Obligations that have {@code var} in their resource-alias set. This + * method should be used when {@code var} and {@code newAlias} definitively point to the same + * object in memory. + * + * @param obligations the set of Obligations to modify + * @param var a variable + * @param newAlias a new {@link ResourceAlias} to add + */ + private void addAliasToObligationsContainingVar( + Set obligations, LocalVariableNode var, ResourceAlias newAlias) { + Iterator it = obligations.iterator(); + List newObligations = new ArrayList<>(); - if (mustCallAliases.isEmpty()) { - // If mustCallAliases is an empty List, add tmpVarAsResourceAlias to a new set. - ResourceAlias tmpVarAsResourceAlias = new ResourceAlias(new LocalVariable(tmpVar), tree); - obligations.add(new Obligation(ImmutableSet.of(tmpVarAsResourceAlias), MethodExitKind.ALL)); - } else { - for (Node mustCallAlias : mustCallAliases) { - if (mustCallAlias instanceof FieldAccessNode) { - // Do not track the call result if the MustCallAlias argument is a field. - // Handling of @Owning fields is a completely separate check, and there is never - // a need to track an alias of a non-@Owning field, as by definition such a - // field does not have must-call obligations! - } else if (mustCallAlias instanceof LocalVariableNode) { - // If mustCallAlias is a local variable already being tracked, add - // tmpVarAsResourceAlias to the set containing mustCallAlias. - Obligation obligationContainingMustCallAlias = - getObligationForVar(obligations, (LocalVariableNode) mustCallAlias); - if (obligationContainingMustCallAlias != null) { - ResourceAlias tmpVarAsResourceAlias = - new ResourceAlias( - new LocalVariable(tmpVar), - tmpVar.getElement(), - tree, - obligationContainingMustCallAlias.derivedFromMustCallAlias()); - Set newResourceAliasSet = - FluentIterable.from(obligationContainingMustCallAlias.resourceAliases) - .append(tmpVarAsResourceAlias) - .toSet(); - obligations.remove(obligationContainingMustCallAlias); - obligations.add( - new Obligation( - newResourceAliasSet, obligationContainingMustCallAlias.whenToEnforce)); - // It is not an error if there is no Obligation containing the must-call - // alias. In that case, what has usually happened is that no Obligation was - // created in the first place. - // For example, when checking the invocation of a "wrapper stream" - // constructor, if the argument in the must-call alias position is some - // stream with no must-call obligations like a ByteArrayInputStream, then no - // Obligation object will have been created for it and therefore - // obligationContainingMustCallAlias will be null. - } - } - } - } - } - - /** - * Returns true if the result of the given method or constructor invocation node should be tracked - * in {@code obligations}. In some cases, there is no need to track the result because the - * must-call obligations are already satisfied in some other way or there cannot possibly be - * must-call obligations because of the structure of the code. - * - *

          Specifically, an invocation result does NOT need to be tracked if any of the following is - * true: - * - *

            - *
          • The invocation is a call to a {@code this()} or {@code super()} constructor. - *
          • The method's return type is annotated with MustCallAlias and the argument passed in this - * invocation in the corresponding position is an owning field. - *
          • The method's return type is non-owning, which can either be because the method has no - * return type or because the return type is annotated with {@link NotOwning}. - *
          - * - *

          This method can also side-effect {@code obligations}, if node is a super or this constructor - * call with MustCallAlias annotations, by removing that Obligation. - * - * @param obligations the current set of Obligations, which may be side-effected - * @param node the invocation node to check; must be {@link MethodInvocationNode} or {@link - * ObjectCreationNode} - * @return true iff the result of {@code node} should be tracked in {@code obligations} - */ - private boolean shouldTrackInvocationResult(Set obligations, Node node) { - Tree callTree = node.getTree(); - if (callTree.getKind() == Tree.Kind.NEW_CLASS) { - // Constructor results from new expressions are tracked as long as the declared type has - // a non-empty @MustCall annotation. - NewClassTree newClassTree = (NewClassTree) callTree; - ExecutableElement executableElement = TreeUtils.elementFromUse(newClassTree); - TypeElement typeElt = TypesUtils.getTypeElement(ElementUtils.getType(executableElement)); - return typeElt == null - || !typeFactory.hasEmptyMustCallValue(typeElt) - || !typeFactory.hasEmptyMustCallValue(newClassTree); + while (it.hasNext()) { + Obligation obligation = it.next(); + if (obligation.canBeSatisfiedThrough(var)) { + it.remove(); + + Set newAliases = new LinkedHashSet<>(obligation.resourceAliases); + newAliases.add(newAlias); + + newObligations.add(new Obligation(newAliases, obligation.whenToEnforce)); + } + } + + obligations.addAll(newObligations); } - // Now callTree.getKind() == Tree.Kind.METHOD_INVOCATION. - MethodInvocationTree methodInvokeTree = (MethodInvocationTree) callTree; - - if (TreeUtils.isSuperConstructorCall(methodInvokeTree) - || TreeUtils.isThisConstructorCall(methodInvokeTree)) { - List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); - // If there is a MustCallAlias argument that is also in the set of Obligations, then - // remove it; its must-call obligation has been fulfilled by being passed on to the - // MustCallAlias constructor (because a this/super constructor call can only occur in - // the body of another constructor). - for (Node mustCallAliasArgument : mustCallAliasArguments) { - if (mustCallAliasArgument instanceof LocalVariableNode) { - removeObligationsContainingVar(obligations, (LocalVariableNode) mustCallAliasArgument); - } - } - return false; - } - return !returnTypeIsMustCallAliasWithUntrackable((MethodInvocationNode) node) - && shouldTrackReturnType((MethodInvocationNode) node); - } - - /** - * Returns true if this node represents a method invocation of a must-call-alias method, where the - * argument in the must-call-alias position is untrackable: an owning field or a pointer that is - * guaranteed to be non-owning, such as {@code "this"} or a non-owning field. Owning fields are - * handled by the rest of the checker, not by this algorithm, so they are "untrackable". - * Non-owning fields and this nodes are guaranteed to be non-owning, and are therefore also - * "untrackable". Because both owning and non-owning fields are untrackable (and there are no - * other kinds of fields), this method returns true for all field accesses. - * - * @param node a method invocation node - * @return true if this is the invocation of a method whose return type is MCA with an owning - * field or a definitely non-owning pointer - */ - private boolean returnTypeIsMustCallAliasWithUntrackable(MethodInvocationNode node) { - List mustCallAliasArguments = getMustCallAliasArgumentNodes(node); - for (Node mustCallAliasArg : mustCallAliasArguments) { - if (!(mustCallAliasArg instanceof FieldAccessNode || mustCallAliasArg instanceof ThisNode)) { - return false; - } - } - return !mustCallAliasArguments.isEmpty(); - } - - /** - * Checks if {@code node} is either directly enclosed by a {@link TypeCastNode}, by looking at the - * successor block in the CFG. In this case the enclosing operator is a "no-op" that evaluates to - * the same value as {@code node}. This method is only used within {@link - * #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, Deque)} to ensure - * Obligations are propagated to cast nodes properly. It relies on the assumption that a {@link - * TypeCastNode} will only appear in a CFG as the first node in a block. - * - * @param node the CFG node - * @return {@code true} if {@code node} is in a {@link SingleSuccessorBlock} {@code b}, the first - * {@link Node} in {@code b}'s successor block is a {@link TypeCastNode}, and {@code node} is - * an operand of the successor node; {@code false} otherwise - */ - private boolean inCast(Node node) { - if (!(node.getBlock() instanceof SingleSuccessorBlock)) { - return false; - } - Block successorBlock = ((SingleSuccessorBlock) node.getBlock()).getSuccessor(); - if (successorBlock != null) { - List succNodes = successorBlock.getNodes(); - if (succNodes.size() > 0) { - Node succNode = succNodes.get(0); - if (succNode instanceof TypeCastNode) { - return ((TypeCastNode) succNode).getOperand().equals(node); - } - } - } - return false; - } - - /** - * Transfer ownership of any locals passed as arguments to {@code @Owning} parameters at a method - * or constructor call by removing the Obligations corresponding to those locals. - * - * @param obligations the current set of Obligations, which is side-effected to remove Obligations - * for locals that are passed as owning parameters to the method or constructor - * @param node a method or constructor invocation node - * @param exceptionType a description of the outgoing CFG edge from the node: null to - * indicate normal return, or a {@link TypeMirror} to indicate a subclass of the given - * throwable class was thrown - */ - private void removeObligationsAtOwnershipTransferToParameters( - Set obligations, Node node, @Nullable TypeMirror exceptionType) { - - if (exceptionType != null) { - // Do not transfer ownership if the called method throws an exception. - return; + /** + * Remove any Obligations that contain {@code var} in their resource-alias set. + * + * @param obligations the set of Obligations to modify + * @param var a variable + */ + /*package-private*/ void removeObligationsContainingVar( + Set obligations, LocalVariableNode var) { + removeObligationsContainingVar( + obligations, var, MustCallAliasHandling.NO_SPECIAL_HANDLING, MethodExitKind.ALL); } - if (noLightweightOwnership) { - // Never transfer ownership to parameters, matching the default in the analysis built - // into Eclipse. - return; - } + /** + * Helper type for {@link #removeObligationsContainingVar(Set, LocalVariableNode, + * MustCallAliasHandling, Set)} + */ + private enum MustCallAliasHandling { + /** + * Obligations derived from {@link MustCallAlias} parameters do not require special + * handling, and they should be removed like any other obligation. + */ + NO_SPECIAL_HANDLING, - List arguments = getArgumentsOfInvocation(node); - List parameters = getParametersOfInvocation(node); - - if (arguments.size() != parameters.size()) { - // This could happen, e.g., with varargs, or with strange cases like generated Enum - // constructors. In the varargs case (i.e. if the varargs parameter is owning), - // only the first of the varargs arguments will actually get transferred: the second - // and later varargs arguments will continue to be tracked at the call-site. - // For now, just skip this case - the worst that will happen is a false positive in - // cases like the varargs one described above. - // TODO allow for ownership transfer here if needed in future - return; + /** + * Obligations derived from {@link MustCallAlias} parameters are not satisfied and should be + * retained. + */ + RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, } - for (int i = 0; i < arguments.size(); i++) { - Node n = removeCastsAndGetTmpVarIfPresent(arguments.get(i)); - if (n instanceof LocalVariableNode) { - LocalVariableNode local = (LocalVariableNode) n; - if (varTrackedInObligations(obligations, local)) { - - // check if parameter has an @Owning annotation - VariableElement parameter = parameters.get(i); - if (typeFactory.hasOwning(parameter)) { - Obligation localObligation = getObligationForVar(obligations, local); - // Passing to an owning parameter is not sufficient to resolve the - // obligation created from a MustCallAlias parameter, because the - // containing method must actually return the value. - if (!localObligation.derivedFromMustCallAlias()) { - // Transfer ownership! - obligations.remove(localObligation); + + /** + * Remove Obligations that contain {@code var} in their resource-alias set. + * + *

          Some operations do not satisfy all Obligations. For instance, assigning to a field in a + * constructor only satisfies Obligations when the constructor exits normally (i.e. without + * throwing an exception). The last two arguments to this method can be used to retain some + * Obligations in special circumstances. + * + * @param obligations the set of Obligations to modify + * @param var a variable + * @param mustCallAliasHandling how to treat Obligations derived from {@link MustCallAlias} + * parameters + * @param whatToClear the kind of Obligations to remove + */ + private void removeObligationsContainingVar( + Set obligations, + LocalVariableNode var, + MustCallAliasHandling mustCallAliasHandling, + Set whatToClear) { + List newObligations = new ArrayList<>(); + + Iterator it = obligations.iterator(); + while (it.hasNext()) { + Obligation obligation = it.next(); + + if (obligation.canBeSatisfiedThrough(var) + && (mustCallAliasHandling == MustCallAliasHandling.NO_SPECIAL_HANDLING + || !obligation.derivedFromMustCallAlias())) { + it.remove(); + + Set whenToEnforce = new HashSet<>(obligation.whenToEnforce); + whenToEnforce.removeAll(whatToClear); + + if (!whenToEnforce.isEmpty()) { + newObligations.add(new Obligation(obligation.resourceAliases, whenToEnforce)); + } } - } } - } - } - } - - /** - * If the return type of the enclosing method is {@code @Owning}, treat the must-call obligations - * of the return expression as satisfied by removing all references to them from {@code - * obligations}. - * - * @param obligations the current set of tracked Obligations. If ownership is transferred, it is - * side-effected to remove any Obligations that are resource-aliased to the return node. - * @param cfg the CFG of the enclosing method - * @param node a return node - */ - private void updateObligationsForOwningReturn( - Set obligations, ControlFlowGraph cfg, ReturnNode node) { - if (isTransferOwnershipAtReturn(cfg)) { - Node returnExpr = node.getResult(); - returnExpr = getTempVarOrNode(returnExpr); - if (returnExpr instanceof LocalVariableNode) { - removeObligationsContainingVar(obligations, (LocalVariableNode) returnExpr); - } - } - } - - /** - * Helper method that gets the temporary node corresponding to {@code node}, if one exists. If - * not, this method returns its input. - * - * @param node a node - * @return the temporary for node, or node if no temporary exists - */ - /*package-private*/ Node getTempVarOrNode(Node node) { - Node temp = typeFactory.getTempVarForNode(node); - if (temp != null) { - return temp; - } - return node; - } - - /** - * Should ownership be transferred to the return type of the method corresponding to a CFG? - * Returns true when there is no {@link NotOwning} annotation on the return type. - * - * @param cfg the CFG of the method - * @return true iff ownership should be transferred to the return type of the method corresponding - * to a CFG - */ - private boolean isTransferOwnershipAtReturn(ControlFlowGraph cfg) { - if (noLightweightOwnership) { - // If not using LO, default to always transfer at return, just like Eclipse does. - return true; - } - UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); - if (underlyingAST instanceof UnderlyingAST.CFGMethod) { - // TODO: lambdas? In that case false is returned below, which means that ownership will - // not be transferred. - MethodTree method = ((UnderlyingAST.CFGMethod) underlyingAST).getMethod(); - ExecutableElement executableElement = TreeUtils.elementFromDeclaration(method); - return !typeFactory.hasNotOwning(executableElement); - } - return false; - } - - /** - * Updates a set of Obligations to account for an assignment. Assigning to an owning field might - * remove Obligations, assigning to a resource variable might remove obligations, assigning to a - * new local variable might modify an Obligation (by increasing the size of its resource alias - * set), etc. - * - * @param obligations the set of Obligations to update - * @param cfg the control flow graph that contains {@code assignmentNode} - * @param assignmentNode the assignment - */ - private void updateObligationsForAssignment( - Set obligations, ControlFlowGraph cfg, AssignmentNode assignmentNode) { - Node lhs = assignmentNode.getTarget(); - Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); - if (lhsElement == null) { - return; - } - // Use the temporary variable for the rhs if it exists. - Node rhs = NodeUtils.removeCasts(assignmentNode.getExpression()); - rhs = getTempVarOrNode(rhs); - - // Ownership transfer to @Owning field. - if (lhsElement.getKind() == ElementKind.FIELD) { - boolean isOwningField = !noLightweightOwnership && typeFactory.hasOwning(lhsElement); - // Check that the must-call obligations of the lhs have been satisfied, if the field is - // non-final and owning. - if (isOwningField - && typeFactory.canCreateObligations() - && !ElementUtils.isFinal(lhsElement)) { - checkReassignmentToField(obligations, assignmentNode); - } - - // Remove Obligations from local variables, now that the owning field is responsible. - // (When obligation creation is turned off, non-final fields cannot take ownership.) - if (isOwningField - && rhs instanceof LocalVariableNode - && (typeFactory.canCreateObligations() || ElementUtils.isFinal(lhsElement))) { - - LocalVariableNode rhsVar = (LocalVariableNode) rhs; - - MethodTree containingMethod = cfg.getContainingMethod(assignmentNode.getTree()); - boolean inConstructor = - containingMethod != null && TreeUtils.isConstructor(containingMethod); - - // Determine which obligations this field assignment can clear. In a constructor, - // assignments to `this.field` only clears obligations on normal return, since - // on exception `this` becomes inaccessible. - Set toClear; - if (inConstructor - && lhs instanceof FieldAccessNode - && ((FieldAccessNode) lhs).getReceiver() instanceof ThisNode) { - toClear = Collections.singleton(MethodExitKind.NORMAL_RETURN); - } else { - toClear = MethodExitKind.ALL; - } - - @Nullable Element enclosingElem = lhsElement.getEnclosingElement(); - @Nullable TypeElement enclosingType = - enclosingElem != null ? ElementUtils.enclosingTypeElement(enclosingElem) : null; - - // Assigning to an owning field is sufficient to clear a must-call alias obligation - // in a constructor, if the enclosing class has at most one @Owning field. If the - // class had multiple owning fields, then a soundness bug would occur: the must call - // alias relationship would allow the whole class' obligation to be fulfilled by - // closing only one of the parameters passed to the constructor (but the other - // owning fields might not actually have had their obligations fulfilled). See test - // case checker/tests/resourceleak/TwoOwningMCATest.java for an example. - if (hasAtMostOneOwningField(enclosingType)) { - removeObligationsContainingVar( - obligations, rhsVar, MustCallAliasHandling.NO_SPECIAL_HANDLING, toClear); - } else { - removeObligationsContainingVar( - obligations, - rhsVar, - MustCallAliasHandling.RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, - toClear); - } - - // Finally, if any obligations containing this var remain, then closing the field - // will satisfy them. Here we are overly cautious and only track final fields. In - // the future we could perhaps relax this guard with careful handling for field - // reassignments. - if (ElementUtils.isFinal(lhsElement)) { - addAliasToObligationsContainingVar( - obligations, - rhsVar, - new ResourceAlias(JavaExpression.fromNode(lhs), lhsElement, lhs.getTree())); - } - } - } else if (lhs instanceof LocalVariableNode) { - LocalVariableNode lhsVar = (LocalVariableNode) lhs; - updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); - } - } - - /** - * Returns true iff the given type element has 0 or 1 @Owning fields. - * - * @param element an element for a class - * @return true iff element has no more than 1 owning field - */ - private boolean hasAtMostOneOwningField(TypeElement element) { - List fields = - ElementUtils.getAllFieldsIn(element, typeFactory.getElementUtils()); - // Has an owning field already been encountered? - boolean hasOwningField = false; - for (VariableElement field : fields) { - if (typeFactory.hasOwning(field)) { - if (hasOwningField) { - return false; - } else { - hasOwningField = true; - } - } - } - // We haven't seen two owning fields, so there must be 1 or 0. - return true; - } - - /** - * Add a new alias to all Obligations that have {@code var} in their resource-alias set. This - * method should be used when {@code var} and {@code newAlias} definitively point to the same - * object in memory. - * - * @param obligations the set of Obligations to modify - * @param var a variable - * @param newAlias a new {@link ResourceAlias} to add - */ - private void addAliasToObligationsContainingVar( - Set obligations, LocalVariableNode var, ResourceAlias newAlias) { - Iterator it = obligations.iterator(); - List newObligations = new ArrayList<>(); - - while (it.hasNext()) { - Obligation obligation = it.next(); - if (obligation.canBeSatisfiedThrough(var)) { - it.remove(); - - Set newAliases = new LinkedHashSet<>(obligation.resourceAliases); - newAliases.add(newAlias); - - newObligations.add(new Obligation(newAliases, obligation.whenToEnforce)); - } + obligations.addAll(newObligations); } - obligations.addAll(newObligations); - } - - /** - * Remove any Obligations that contain {@code var} in their resource-alias set. - * - * @param obligations the set of Obligations to modify - * @param var a variable - */ - /*package-private*/ void removeObligationsContainingVar( - Set obligations, LocalVariableNode var) { - removeObligationsContainingVar( - obligations, var, MustCallAliasHandling.NO_SPECIAL_HANDLING, MethodExitKind.ALL); - } - - /** - * Helper type for {@link #removeObligationsContainingVar(Set, LocalVariableNode, - * MustCallAliasHandling, Set)} - */ - private enum MustCallAliasHandling { /** - * Obligations derived from {@link MustCallAlias} parameters do not require special handling, - * and they should be removed like any other obligation. + * Update a set of tracked Obligations to account for a (pseudo-)assignment to some variable, as + * in a gen-kill dataflow analysis problem. That is, add ("gen") and remove ("kill") resource + * aliases from Obligations in the {@code obligations} set as appropriate based on the + * (pseudo-)assignment performed by {@code node}. This method may also remove an Obligation + * entirely if the analysis concludes that its resource alias set is empty because the last + * tracked alias to it has been overwritten (including checking that the must-call obligations + * were satisfied before the assignment). + * + *

          Pseudo-assignments may include operations that "assign" to a temporary variable, exposing + * the possible value flow into the variable. E.g., for a ternary expression {@code b ? x : y} + * whose temporary variable is {@code t}, this method may process "assignments" {@code t = x} + * and {@code t = y}, thereby capturing the two possible values of {@code t}. + * + * @param obligations the tracked Obligations, which will be side-effected + * @param node the node performing the pseudo-assignment; it is not necessarily an assignment + * node + * @param lhsVar the left-hand side variable for the pseudo-assignment + * @param rhs the right-hand side for the pseudo-assignment, which must have been converted to a + * temporary variable (via a call to {@link + * ResourceLeakAnnotatedTypeFactory#getTempVarForNode}) */ - NO_SPECIAL_HANDLING, + /*package-private*/ void updateObligationsForPseudoAssignment( + Set obligations, Node node, LocalVariableNode lhsVar, Node rhs) { + // Replacements to eventually perform in Obligations. This map is kept to avoid a + // ConcurrentModificationException in the loop below. + Map replacements = new LinkedHashMap<>(); + // Cache to re-use on subsequent iterations. + ResourceAlias aliasForAssignment = null; + for (Obligation obligation : obligations) { + // This is a non-null value iff the resource alias set for obligation needs to + // change because of the pseudo-assignment. The value of this variable is the new + // alias set for `obligation` if it is non-null. + Set newResourceAliasesForObligation = null; + + // Always kill the lhs var if it is present in the resource alias set for this + // Obligation by removing it from the resource alias set. + ResourceAlias aliasForLhs = obligation.getResourceAlias(lhsVar); + if (aliasForLhs != null) { + newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases); + newResourceAliasesForObligation.remove(aliasForLhs); + } + // If rhs is a variable tracked in the Obligation's resource alias set, gen the lhs + // by adding it to the resource alias set. + if (rhs instanceof LocalVariableNode + && obligation.canBeSatisfiedThrough((LocalVariableNode) rhs)) { + LocalVariableNode rhsVar = (LocalVariableNode) rhs; + if (newResourceAliasesForObligation == null) { + newResourceAliasesForObligation = + new LinkedHashSet<>(obligation.resourceAliases); + } + if (aliasForAssignment == null) { + // It is possible to observe assignments to temporary variables, e.g., + // synthetic assignments to ternary expression variables in the CFG. For such + // cases, use the tree associated with the temp var for the resource alias, + // as that is the tree where errors should be reported. + Tree treeForAlias = + typeFactory.isTempVar(lhsVar) + ? typeFactory.getTreeForTempVar(lhsVar) + : node.getTree(); + aliasForAssignment = new ResourceAlias(new LocalVariable(lhsVar), treeForAlias); + } + newResourceAliasesForObligation.add(aliasForAssignment); + // Remove temp vars from tracking once they are assigned to another location. + if (typeFactory.isTempVar(rhsVar)) { + ResourceAlias aliasForRhs = obligation.getResourceAlias(rhsVar); + if (aliasForRhs != null) { + newResourceAliasesForObligation.remove(aliasForRhs); + } + } + } + + // If no changes were made to the resource alias set, there is no need to update the + // Obligation. + if (newResourceAliasesForObligation == null) { + continue; + } + + if (newResourceAliasesForObligation.isEmpty()) { + // Because the last reference to the resource has been overwritten, check the + // must-call obligation. + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + checkMustCall( + obligation, + typeFactory.getStoreBefore(node), + mcAtf.getStoreBefore(node), + "variable overwritten by assignment " + node.getTree()); + replacements.put(obligation, null); + } else { + replacements.put( + obligation, + new Obligation(newResourceAliasesForObligation, obligation.whenToEnforce)); + } + } + + // Finally, update the set of Obligations according to the replacements. + for (Map.Entry entry : replacements.entrySet()) { + obligations.remove(entry.getKey()); + if (entry.getValue() != null && !entry.getValue().resourceAliases.isEmpty()) { + obligations.add(entry.getValue()); + } + } + } /** - * Obligations derived from {@link MustCallAlias} parameters are not satisfied and should be - * retained. + * Issues an error if the given re-assignment to a non-final, owning field is not valid. A + * re-assignment is valid if the called methods type of the lhs before the assignment satisfies + * the must-call obligations of the field. + * + *

          Despite the name of this method, the argument {@code node} might be the first and only + * assignment to a field. + * + * @param obligations current tracked Obligations + * @param node an assignment to a non-final, owning field */ - RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER, - } - - /** - * Remove Obligations that contain {@code var} in their resource-alias set. - * - *

          Some operations do not satisfy all Obligations. For instance, assigning to a field in a - * constructor only satisfies Obligations when the constructor exits normally (i.e. without - * throwing an exception). The last two arguments to this method can be used to retain some - * Obligations in special circumstances. - * - * @param obligations the set of Obligations to modify - * @param var a variable - * @param mustCallAliasHandling how to treat Obligations derived from {@link MustCallAlias} - * parameters - * @param whatToClear the kind of Obligations to remove - */ - private void removeObligationsContainingVar( - Set obligations, - LocalVariableNode var, - MustCallAliasHandling mustCallAliasHandling, - Set whatToClear) { - List newObligations = new ArrayList<>(); - - Iterator it = obligations.iterator(); - while (it.hasNext()) { - Obligation obligation = it.next(); - - if (obligation.canBeSatisfiedThrough(var) - && (mustCallAliasHandling == MustCallAliasHandling.NO_SPECIAL_HANDLING - || !obligation.derivedFromMustCallAlias())) { - it.remove(); - - Set whenToEnforce = new HashSet<>(obligation.whenToEnforce); - whenToEnforce.removeAll(whatToClear); - - if (!whenToEnforce.isEmpty()) { - newObligations.add(new Obligation(obligation.resourceAliases, whenToEnforce)); - } - } - } + private void checkReassignmentToField(Set obligations, AssignmentNode node) { - obligations.addAll(newObligations); - } - - /** - * Update a set of tracked Obligations to account for a (pseudo-)assignment to some variable, as - * in a gen-kill dataflow analysis problem. That is, add ("gen") and remove ("kill") resource - * aliases from Obligations in the {@code obligations} set as appropriate based on the - * (pseudo-)assignment performed by {@code node}. This method may also remove an Obligation - * entirely if the analysis concludes that its resource alias set is empty because the last - * tracked alias to it has been overwritten (including checking that the must-call obligations - * were satisfied before the assignment). - * - *

          Pseudo-assignments may include operations that "assign" to a temporary variable, exposing - * the possible value flow into the variable. E.g., for a ternary expression {@code b ? x : y} - * whose temporary variable is {@code t}, this method may process "assignments" {@code t = x} and - * {@code t = y}, thereby capturing the two possible values of {@code t}. - * - * @param obligations the tracked Obligations, which will be side-effected - * @param node the node performing the pseudo-assignment; it is not necessarily an assignment node - * @param lhsVar the left-hand side variable for the pseudo-assignment - * @param rhs the right-hand side for the pseudo-assignment, which must have been converted to a - * temporary variable (via a call to {@link - * ResourceLeakAnnotatedTypeFactory#getTempVarForNode}) - */ - /*package-private*/ void updateObligationsForPseudoAssignment( - Set obligations, Node node, LocalVariableNode lhsVar, Node rhs) { - // Replacements to eventually perform in Obligations. This map is kept to avoid a - // ConcurrentModificationException in the loop below. - Map replacements = new LinkedHashMap<>(); - // Cache to re-use on subsequent iterations. - ResourceAlias aliasForAssignment = null; - for (Obligation obligation : obligations) { - // This is a non-null value iff the resource alias set for obligation needs to - // change because of the pseudo-assignment. The value of this variable is the new - // alias set for `obligation` if it is non-null. - Set newResourceAliasesForObligation = null; - - // Always kill the lhs var if it is present in the resource alias set for this - // Obligation by removing it from the resource alias set. - ResourceAlias aliasForLhs = obligation.getResourceAlias(lhsVar); - if (aliasForLhs != null) { - newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases); - newResourceAliasesForObligation.remove(aliasForLhs); - } - // If rhs is a variable tracked in the Obligation's resource alias set, gen the lhs - // by adding it to the resource alias set. - if (rhs instanceof LocalVariableNode - && obligation.canBeSatisfiedThrough((LocalVariableNode) rhs)) { - LocalVariableNode rhsVar = (LocalVariableNode) rhs; - if (newResourceAliasesForObligation == null) { - newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases); - } - if (aliasForAssignment == null) { - // It is possible to observe assignments to temporary variables, e.g., - // synthetic assignments to ternary expression variables in the CFG. For such - // cases, use the tree associated with the temp var for the resource alias, - // as that is the tree where errors should be reported. - Tree treeForAlias = - typeFactory.isTempVar(lhsVar) - ? typeFactory.getTreeForTempVar(lhsVar) - : node.getTree(); - aliasForAssignment = new ResourceAlias(new LocalVariable(lhsVar), treeForAlias); - } - newResourceAliasesForObligation.add(aliasForAssignment); - // Remove temp vars from tracking once they are assigned to another location. - if (typeFactory.isTempVar(rhsVar)) { - ResourceAlias aliasForRhs = obligation.getResourceAlias(rhsVar); - if (aliasForRhs != null) { - newResourceAliasesForObligation.remove(aliasForRhs); - } - } - } - - // If no changes were made to the resource alias set, there is no need to update the - // Obligation. - if (newResourceAliasesForObligation == null) { - continue; - } - - if (newResourceAliasesForObligation.isEmpty()) { - // Because the last reference to the resource has been overwritten, check the - // must-call obligation. - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - checkMustCall( - obligation, - typeFactory.getStoreBefore(node), - mcAtf.getStoreBefore(node), - "variable overwritten by assignment " + node.getTree()); - replacements.put(obligation, null); - } else { - replacements.put( - obligation, new Obligation(newResourceAliasesForObligation, obligation.whenToEnforce)); - } - } + Node lhsNode = node.getTarget(); - // Finally, update the set of Obligations according to the replacements. - for (Map.Entry entry : replacements.entrySet()) { - obligations.remove(entry.getKey()); - if (entry.getValue() != null && !entry.getValue().resourceAliases.isEmpty()) { - obligations.add(entry.getValue()); - } - } - } - - /** - * Issues an error if the given re-assignment to a non-final, owning field is not valid. A - * re-assignment is valid if the called methods type of the lhs before the assignment satisfies - * the must-call obligations of the field. - * - *

          Despite the name of this method, the argument {@code node} might be the first and only - * assignment to a field. - * - * @param obligations current tracked Obligations - * @param node an assignment to a non-final, owning field - */ - private void checkReassignmentToField(Set obligations, AssignmentNode node) { - - Node lhsNode = node.getTarget(); - - if (!(lhsNode instanceof FieldAccessNode)) { - throw new TypeSystemError( - "checkReassignmentToField: non-field node " + node + " of class " + node.getClass()); - } + if (!(lhsNode instanceof FieldAccessNode)) { + throw new TypeSystemError( + "checkReassignmentToField: non-field node " + + node + + " of class " + + node.getClass()); + } - FieldAccessNode lhs = (FieldAccessNode) lhsNode; - Node receiver = lhs.getReceiver(); + FieldAccessNode lhs = (FieldAccessNode) lhsNode; + Node receiver = lhs.getReceiver(); - if (permitStaticOwning && receiver instanceof ClassNameNode) { - return; - } + if (permitStaticOwning && receiver instanceof ClassNameNode) { + return; + } + + // TODO: it would be better to defer getting the path until after checking + // for a CreatesMustCallFor annotation, because getting the path can be expensive. + // It might be possible to exploit the CFG structure to find the containing + // method (rather than using the path, as below), because if a method is being + // analyzed then it should be the root of the CFG (I think). + TreePath currentPath = typeFactory.getPath(node.getTree()); + MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(currentPath); + + if (enclosingMethodTree == null) { + // The assignment is taking place outside of a method: in a variable declaration's + // initializer or in an initializer block. + // The Resource Leak Checker issues no error if the assignment is a field initializer. + if (node.getTree().getKind() == Tree.Kind.VARIABLE) { + // An assignment to a field that is also a declaration must be a field initializer + // (VARIABLE Trees are only used for declarations). Assignment in a field + // initializer is always permitted. + return; + } else if (permitInitializationLeak + && TreePathUtil.isTopLevelAssignmentInInitializerBlock(currentPath)) { + // This is likely not reassignment; if reassignment, the number of assignments that + // were not warned about is limited to other initializations (is not unbounded). + // This behavior is unsound; see InstanceInitializer.java test case. + return; + } else { + // Issue an error if the field has a non-empty must-call type. + MustCallAnnotatedTypeFactory mcTypeFactory = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotationMirror mcAnno = + mcTypeFactory + .getAnnotatedType(lhs.getElement()) + .getAnnotation(MustCall.class); + List mcValues = + AnnotationUtils.getElementValueArray( + mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); + if (mcValues.isEmpty()) { + return; + } + VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); + checker.reportError( + node.getTree(), + "required.method.not.called", + formatMissingMustCallMethods(mcValues), + "field " + lhsElement.getSimpleName().toString(), + lhsElement.asType().toString(), + "Field assignment outside method or declaration might overwrite field's" + + " current value"); + return; + } + } else if (permitInitializationLeak && TreeUtils.isConstructor(enclosingMethodTree)) { + Element enclosingClassElement = + TreeUtils.elementFromDeclaration(enclosingMethodTree).getEnclosingElement(); + if (ElementUtils.isTypeElement(enclosingClassElement)) { + Element receiverElement = TypesUtils.getTypeElement(receiver.getType()); + if (Objects.equals(enclosingClassElement, receiverElement)) { + return; + } + } + } + + // Check that there is a corresponding CreatesMustCallFor annotation, unless this is + // 1) an assignment to a field of a newly-declared local variable whose scope does not + // extend beyond the method's body (and which therefore could not be targeted by an + // annotation on the method declaration), or 2) the rhs is a null literal (so there's + // nothing to reset). + if (!(receiver instanceof LocalVariableNode + && varTrackedInObligations(obligations, (LocalVariableNode) receiver)) + && !(node.getExpression() instanceof NullLiteralNode)) { + checkEnclosingMethodIsCreatesMustCallFor(node, enclosingMethodTree); + } + + // The following code handles a special case where the field being assigned is itself + // getting passed in an owning position to another method on the RHS of the assignment. + // For example, if the field's type is a class whose constructor takes another instance + // of itself (such as a node in a linked list) in an owning position, re-assigning the + // field to a new instance that takes the field's value as an owning parameter is safe + // (the new value has taken responsibility for closing the old value). In such a case, + // it is not required that the must-call obligation of the field be satisfied via method + // calls before the assignment, since the invoked method will take ownership of the + // object previously referenced by the field and handle the obligation. This fixes the + // false positive in https://github.com/typetools/checker-framework/issues/5971. + Node rhs = node.getExpression(); + if (!noLightweightOwnership + && (rhs instanceof ObjectCreationNode || rhs instanceof MethodInvocationNode)) { + + List arguments = getArgumentsOfInvocation(rhs); + List parameters = getParametersOfInvocation(rhs); + + if (arguments.size() == parameters.size()) { + for (int i = 0; i < arguments.size(); i++) { + VariableElement param = parameters.get(i); + if (typeFactory.hasOwning(param)) { + Node argument = arguments.get(i); + if (argument.equals(lhs)) { + return; + } + } + } + } else { + // This could happen, e.g., with varargs, or with strange cases like generated Enum + // constructors. In the varargs case (i.e. if the varargs parameter is owning), + // only the first of the varargs arguments will actually get transferred: the second + // and later varargs arguments will continue to be tracked at the call-site. + // For now, just skip this case - the worst that will happen is a false positive in + // cases like the varargs one described above. + // TODO allow for ownership transfer here if needed in future, but for now do + // nothing + } + } - // TODO: it would be better to defer getting the path until after checking - // for a CreatesMustCallFor annotation, because getting the path can be expensive. - // It might be possible to exploit the CFG structure to find the containing - // method (rather than using the path, as below), because if a method is being - // analyzed then it should be the root of the CFG (I think). - TreePath currentPath = typeFactory.getPath(node.getTree()); - MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(currentPath); - - if (enclosingMethodTree == null) { - // The assignment is taking place outside of a method: in a variable declaration's - // initializer or in an initializer block. - // The Resource Leak Checker issues no error if the assignment is a field initializer. - if (node.getTree().getKind() == Tree.Kind.VARIABLE) { - // An assignment to a field that is also a declaration must be a field initializer - // (VARIABLE Trees are only used for declarations). Assignment in a field - // initializer is always permitted. - return; - } else if (permitInitializationLeak - && TreePathUtil.isTopLevelAssignmentInInitializerBlock(currentPath)) { - // This is likely not reassignment; if reassignment, the number of assignments that - // were not warned about is limited to other initializations (is not unbounded). - // This behavior is unsound; see InstanceInitializer.java test case. - return; - } else { - // Issue an error if the field has a non-empty must-call type. MustCallAnnotatedTypeFactory mcTypeFactory = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotationMirror mcAnno = - mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class); + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + + // Get the Must Call type for the field. If there's info about this field in the store, use + // that. Otherwise, use the declared type of the field + CFStore mcStore = mcTypeFactory.getStoreBefore(lhs); + CFValue mcValue = mcStore.getValue(lhs); + AnnotationMirror mcAnno = null; + if (mcValue != null) { + mcAnno = AnnotationUtils.getAnnotationByClass(mcValue.getAnnotations(), MustCall.class); + } + if (mcAnno == null) { + // No stored value (or the stored value is Poly/top), so use the declared type. + mcAnno = mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class); + } + // if mcAnno is still null, then the declared type must be something other than + // @MustCall (probably @MustCallUnknown). Do nothing in this case: a warning + // about the field will be issued elsewhere (it will be impossible to satisfy its + // obligations!). + if (mcAnno == null) { + return; + } List mcValues = - AnnotationUtils.getElementValueArray( - mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); + AnnotationUtils.getElementValueArray( + mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); + if (mcValues.isEmpty()) { - return; + return; } - VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); - checker.reportError( - node.getTree(), - "required.method.not.called", - formatMissingMustCallMethods(mcValues), - "field " + lhsElement.getSimpleName().toString(), - lhsElement.asType().toString(), - "Field assignment outside method or declaration might overwrite field's" - + " current value"); - return; - } - } else if (permitInitializationLeak && TreeUtils.isConstructor(enclosingMethodTree)) { - Element enclosingClassElement = - TreeUtils.elementFromDeclaration(enclosingMethodTree).getEnclosingElement(); - if (ElementUtils.isTypeElement(enclosingClassElement)) { - Element receiverElement = TypesUtils.getTypeElement(receiver.getType()); - if (Objects.equals(enclosingClassElement, receiverElement)) { - return; - } - } - } - - // Check that there is a corresponding CreatesMustCallFor annotation, unless this is - // 1) an assignment to a field of a newly-declared local variable whose scope does not - // extend beyond the method's body (and which therefore could not be targeted by an - // annotation on the method declaration), or 2) the rhs is a null literal (so there's - // nothing to reset). - if (!(receiver instanceof LocalVariableNode - && varTrackedInObligations(obligations, (LocalVariableNode) receiver)) - && !(node.getExpression() instanceof NullLiteralNode)) { - checkEnclosingMethodIsCreatesMustCallFor(node, enclosingMethodTree); - } - // The following code handles a special case where the field being assigned is itself - // getting passed in an owning position to another method on the RHS of the assignment. - // For example, if the field's type is a class whose constructor takes another instance - // of itself (such as a node in a linked list) in an owning position, re-assigning the - // field to a new instance that takes the field's value as an owning parameter is safe - // (the new value has taken responsibility for closing the old value). In such a case, - // it is not required that the must-call obligation of the field be satisfied via method - // calls before the assignment, since the invoked method will take ownership of the - // object previously referenced by the field and handle the obligation. This fixes the - // false positive in https://github.com/typetools/checker-framework/issues/5971. - Node rhs = node.getExpression(); - if (!noLightweightOwnership - && (rhs instanceof ObjectCreationNode || rhs instanceof MethodInvocationNode)) { - - List arguments = getArgumentsOfInvocation(rhs); - List parameters = getParametersOfInvocation(rhs); - - if (arguments.size() == parameters.size()) { - for (int i = 0; i < arguments.size(); i++) { - VariableElement param = parameters.get(i); - if (typeFactory.hasOwning(param)) { - Node argument = arguments.get(i); - if (argument.equals(lhs)) { - return; + // Get the store before the RHS rather than the assignment node, because the CFG always has + // the RHS first. If the RHS has side-effects, then the assignment node's store will have + // had its inferred types erased. + AccumulationStore cmStoreBefore = typeFactory.getStoreBefore(rhs); + AccumulationValue cmValue = cmStoreBefore == null ? null : cmStoreBefore.getValue(lhs); + AnnotationMirror cmAnno = null; + if (cmValue != null) { // When store contains the lhs + Set accumulatedValues = cmValue.getAccumulatedValues(); + if (accumulatedValues != null) { // type variable or wildcard type + cmAnno = typeFactory.createCalledMethods(accumulatedValues.toArray(new String[0])); + } else { + for (AnnotationMirror anno : cmValue.getAnnotations()) { + if (AnnotationUtils.areSameByName( + anno, + "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { + cmAnno = anno; + } + } } - } - } - } else { - // This could happen, e.g., with varargs, or with strange cases like generated Enum - // constructors. In the varargs case (i.e. if the varargs parameter is owning), - // only the first of the varargs arguments will actually get transferred: the second - // and later varargs arguments will continue to be tracked at the call-site. - // For now, just skip this case - the worst that will happen is a false positive in - // cases like the varargs one described above. - // TODO allow for ownership transfer here if needed in future, but for now do - // nothing - } + } + if (cmAnno == null) { + cmAnno = typeFactory.top; + } + if (!calledMethodsSatisfyMustCall(mcValues, cmAnno)) { + VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); + if (!checker.shouldSkipUses(lhsElement)) { + checker.reportError( + node.getTree(), + "required.method.not.called", + formatMissingMustCallMethods(mcValues), + "field " + lhsElement.getSimpleName().toString(), + lhsElement.asType().toString(), + " Non-final owning field might be overwritten"); + } + } } - MustCallAnnotatedTypeFactory mcTypeFactory = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + /** + * Checks that the method that encloses an assignment is marked with @CreatesMustCallFor + * annotation whose target is the object whose field is being re-assigned. + * + * @param node an assignment node whose lhs is a non-final, owning field + * @param enclosingMethod the MethodTree in which the re-assignment takes place + */ + private void checkEnclosingMethodIsCreatesMustCallFor( + AssignmentNode node, MethodTree enclosingMethod) { + Node lhs = node.getTarget(); + if (!(lhs instanceof FieldAccessNode)) { + return; + } + if (permitStaticOwning && ((FieldAccessNode) lhs).getReceiver() instanceof ClassNameNode) { + return; + } - // Get the Must Call type for the field. If there's info about this field in the store, use - // that. Otherwise, use the declared type of the field - CFStore mcStore = mcTypeFactory.getStoreBefore(lhs); - CFValue mcValue = mcStore.getValue(lhs); - AnnotationMirror mcAnno = null; - if (mcValue != null) { - mcAnno = AnnotationUtils.getAnnotationByClass(mcValue.getAnnotations(), MustCall.class); - } - if (mcAnno == null) { - // No stored value (or the stored value is Poly/top), so use the declared type. - mcAnno = mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class); - } - // if mcAnno is still null, then the declared type must be something other than - // @MustCall (probably @MustCallUnknown). Do nothing in this case: a warning - // about the field will be issued elsewhere (it will be impossible to satisfy its - // obligations!). - if (mcAnno == null) { - return; - } - List mcValues = - AnnotationUtils.getElementValueArray( - mcAnno, mcTypeFactory.getMustCallValueElement(), String.class); + String receiverString = receiverAsString((FieldAccessNode) lhs); + if ("this".equals(receiverString) && TreeUtils.isConstructor(enclosingMethod)) { + // Constructors always create must-call obligations, so there is no need for them to + // be annotated. + return; + } + ExecutableElement enclosingMethodElt = TreeUtils.elementFromDeclaration(enclosingMethod); + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - if (mcValues.isEmpty()) { - return; - } + List cmcfValues = + ResourceLeakVisitor.getCreatesMustCallForValues( + enclosingMethodElt, mcAtf, typeFactory); - // Get the store before the RHS rather than the assignment node, because the CFG always has - // the RHS first. If the RHS has side-effects, then the assignment node's store will have - // had its inferred types erased. - AccumulationStore cmStoreBefore = typeFactory.getStoreBefore(rhs); - AccumulationValue cmValue = cmStoreBefore == null ? null : cmStoreBefore.getValue(lhs); - AnnotationMirror cmAnno = null; - if (cmValue != null) { // When store contains the lhs - Set accumulatedValues = cmValue.getAccumulatedValues(); - if (accumulatedValues != null) { // type variable or wildcard type - cmAnno = typeFactory.createCalledMethods(accumulatedValues.toArray(new String[0])); - } else { - for (AnnotationMirror anno : cmValue.getAnnotations()) { - if (AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { - cmAnno = anno; - } - } - } - } - if (cmAnno == null) { - cmAnno = typeFactory.top; - } - if (!calledMethodsSatisfyMustCall(mcValues, cmAnno)) { - VariableElement lhsElement = TreeUtils.variableElementFromTree(lhs.getTree()); - if (!checker.shouldSkipUses(lhsElement)) { + if (cmcfValues.isEmpty()) { + checker.reportError( + enclosingMethod, + "missing.creates.mustcall.for", + enclosingMethodElt.getSimpleName().toString(), + receiverString, + ((FieldAccessNode) lhs).getFieldName()); + return; + } + + List checked = new ArrayList<>(); + for (String targetStrWithoutAdaptation : cmcfValues) { + String targetStr; + try { + targetStr = + StringToJavaExpression.atMethodBody( + targetStrWithoutAdaptation, enclosingMethod, checker) + .toString(); + } catch (JavaExpressionParseException e) { + targetStr = targetStrWithoutAdaptation; + } + if (targetStr.equals(receiverString)) { + // This @CreatesMustCallFor annotation matches. + return; + } + checked.add(targetStr); + } checker.reportError( - node.getTree(), - "required.method.not.called", - formatMissingMustCallMethods(mcValues), - "field " + lhsElement.getSimpleName().toString(), - lhsElement.asType().toString(), - " Non-final owning field might be overwritten"); - } - } - } - - /** - * Checks that the method that encloses an assignment is marked with @CreatesMustCallFor - * annotation whose target is the object whose field is being re-assigned. - * - * @param node an assignment node whose lhs is a non-final, owning field - * @param enclosingMethod the MethodTree in which the re-assignment takes place - */ - private void checkEnclosingMethodIsCreatesMustCallFor( - AssignmentNode node, MethodTree enclosingMethod) { - Node lhs = node.getTarget(); - if (!(lhs instanceof FieldAccessNode)) { - return; - } - if (permitStaticOwning && ((FieldAccessNode) lhs).getReceiver() instanceof ClassNameNode) { - return; + enclosingMethod, + "incompatible.creates.mustcall.for", + enclosingMethodElt.getSimpleName().toString(), + receiverString, + ((FieldAccessNode) lhs).getFieldName(), + String.join(", ", checked)); } - String receiverString = receiverAsString((FieldAccessNode) lhs); - if ("this".equals(receiverString) && TreeUtils.isConstructor(enclosingMethod)) { - // Constructors always create must-call obligations, so there is no need for them to - // be annotated. - return; - } - ExecutableElement enclosingMethodElt = TreeUtils.elementFromDeclaration(enclosingMethod); - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - - List cmcfValues = - ResourceLeakVisitor.getCreatesMustCallForValues(enclosingMethodElt, mcAtf, typeFactory); - - if (cmcfValues.isEmpty()) { - checker.reportError( - enclosingMethod, - "missing.creates.mustcall.for", - enclosingMethodElt.getSimpleName().toString(), - receiverString, - ((FieldAccessNode) lhs).getFieldName()); - return; + /** + * Gets a standardized name for an object whose field is being re-assigned. + * + * @param fieldAccessNode a field access node + * @return the name of the object whose field is being accessed (the receiver), as a string + */ + private String receiverAsString(FieldAccessNode fieldAccessNode) { + Node receiver = fieldAccessNode.getReceiver(); + if (receiver instanceof ThisNode) { + return "this"; + } + if (receiver instanceof LocalVariableNode) { + return ((LocalVariableNode) receiver).getName(); + } + if (receiver instanceof ClassNameNode) { + return ((ClassNameNode) receiver).getElement().toString(); + } + if (receiver instanceof SuperNode) { + return "super"; + } + throw new TypeSystemError( + "unexpected receiver of field assignment: " + + receiver + + " of type " + + receiver.getClass()); } - List checked = new ArrayList<>(); - for (String targetStrWithoutAdaptation : cmcfValues) { - String targetStr; - try { - targetStr = - StringToJavaExpression.atMethodBody( - targetStrWithoutAdaptation, enclosingMethod, checker) - .toString(); - } catch (JavaExpressionParseException e) { - targetStr = targetStrWithoutAdaptation; - } - if (targetStr.equals(receiverString)) { - // This @CreatesMustCallFor annotation matches. - return; - } - checked.add(targetStr); - } - checker.reportError( - enclosingMethod, - "incompatible.creates.mustcall.for", - enclosingMethodElt.getSimpleName().toString(), - receiverString, - ((FieldAccessNode) lhs).getFieldName(), - String.join(", ", checked)); - } - - /** - * Gets a standardized name for an object whose field is being re-assigned. - * - * @param fieldAccessNode a field access node - * @return the name of the object whose field is being accessed (the receiver), as a string - */ - private String receiverAsString(FieldAccessNode fieldAccessNode) { - Node receiver = fieldAccessNode.getReceiver(); - if (receiver instanceof ThisNode) { - return "this"; - } - if (receiver instanceof LocalVariableNode) { - return ((LocalVariableNode) receiver).getName(); - } - if (receiver instanceof ClassNameNode) { - return ((ClassNameNode) receiver).getElement().toString(); - } - if (receiver instanceof SuperNode) { - return "super"; - } - throw new TypeSystemError( - "unexpected receiver of field assignment: " + receiver + " of type " + receiver.getClass()); - } - - /** - * Finds the arguments passed in the {@code @MustCallAlias} positions for a call. - * - * @param callNode callNode representing the call; must be {@link MethodInvocationNode} or {@link - * ObjectCreationNode} - * @return if {@code callNode} invokes a method with a {@code @MustCallAlias} annotation on some - * formal parameter(s) (or the receiver), returns the result of calling {@link - * #removeCastsAndGetTmpVarIfPresent(Node)} on the argument(s) passed in corresponding - * position(s). Otherwise, returns an empty List. - */ - private List getMustCallAliasArgumentNodes(Node callNode) { - Preconditions.checkArgument( - callNode instanceof MethodInvocationNode || callNode instanceof ObjectCreationNode); - List result = new ArrayList<>(); - if (!typeFactory.hasMustCallAlias(callNode.getTree())) { - return result; - } + /** + * Finds the arguments passed in the {@code @MustCallAlias} positions for a call. + * + * @param callNode callNode representing the call; must be {@link MethodInvocationNode} or + * {@link ObjectCreationNode} + * @return if {@code callNode} invokes a method with a {@code @MustCallAlias} annotation on some + * formal parameter(s) (or the receiver), returns the result of calling {@link + * #removeCastsAndGetTmpVarIfPresent(Node)} on the argument(s) passed in corresponding + * position(s). Otherwise, returns an empty List. + */ + private List getMustCallAliasArgumentNodes(Node callNode) { + Preconditions.checkArgument( + callNode instanceof MethodInvocationNode || callNode instanceof ObjectCreationNode); + List result = new ArrayList<>(); + if (!typeFactory.hasMustCallAlias(callNode.getTree())) { + return result; + } - List args = getArgumentsOfInvocation(callNode); - List parameters = getParametersOfInvocation(callNode); - for (int i = 0; i < args.size(); i++) { - if (typeFactory.hasMustCallAlias(parameters.get(i))) { - result.add(removeCastsAndGetTmpVarIfPresent(args.get(i))); - } - } + List args = getArgumentsOfInvocation(callNode); + List parameters = getParametersOfInvocation(callNode); + for (int i = 0; i < args.size(); i++) { + if (typeFactory.hasMustCallAlias(parameters.get(i))) { + result.add(removeCastsAndGetTmpVarIfPresent(args.get(i))); + } + } - // If none of the parameters were @MustCallAlias, it must be the receiver - if (result.isEmpty() && callNode instanceof MethodInvocationNode) { - result.add( - removeCastsAndGetTmpVarIfPresent( - ((MethodInvocationNode) callNode).getTarget().getReceiver())); - } + // If none of the parameters were @MustCallAlias, it must be the receiver + if (result.isEmpty() && callNode instanceof MethodInvocationNode) { + result.add( + removeCastsAndGetTmpVarIfPresent( + ((MethodInvocationNode) callNode).getTarget().getReceiver())); + } - return result; - } - - /** - * If a temporary variable exists for node after typecasts have been removed, return it. - * Otherwise, return node. - * - * @param node a node - * @return either a tempvar for node's content sans typecasts, or node - */ - /*package-private*/ Node removeCastsAndGetTmpVarIfPresent(Node node) { - // TODO: Create temp vars for TypeCastNodes as well, so there is no need to explicitly - // remove casts here. - node = NodeUtils.removeCasts(node); - return getTempVarOrNode(node); - } - - /** - * Get the nodes representing the arguments of a method or constructor invocation from the - * invocation node. - * - * @param node a MethodInvocation or ObjectCreation node - * @return the arguments, in order - */ - /*package-private*/ List getArgumentsOfInvocation(Node node) { - if (node instanceof MethodInvocationNode) { - MethodInvocationNode invocationNode = (MethodInvocationNode) node; - return invocationNode.getArguments(); - } else if (node instanceof ObjectCreationNode) { - return ((ObjectCreationNode) node).getArguments(); - } else { - throw new TypeSystemError("unexpected node type " + node.getClass()); - } - } - - /** - * Get the elements representing the formal parameters of a method or constructor, from an - * invocation of that method or constructor. - * - * @param node a method invocation or object creation node - * @return a list of the declarations of the formal parameters of the method or constructor being - * invoked - */ - /*package-private*/ List getParametersOfInvocation(Node node) { - ExecutableElement executableElement; - if (node instanceof MethodInvocationNode) { - MethodInvocationNode invocationNode = (MethodInvocationNode) node; - executableElement = TreeUtils.elementFromUse(invocationNode.getTree()); - } else if (node instanceof ObjectCreationNode) { - executableElement = TreeUtils.elementFromUse(((ObjectCreationNode) node).getTree()); - } else { - throw new TypeSystemError("unexpected node type " + node.getClass()); + return result; } - return executableElement.getParameters(); - } - - /** - * Is the return type of the invoked method one that should be tracked? - * - * @param node a method invocation - * @return true iff the checker is in no-lightweight-ownership mode, or the method has a - * {@code @MustCallAlias} annotation, or (1) the method has a return type that needs to be - * tracked (i.e., it has a non-empty {@code @MustCall} obligation and (2) the method - * declaration does not have a {@code @NotOwning} annotation - */ - private boolean shouldTrackReturnType(MethodInvocationNode node) { - if (noLightweightOwnership) { - // Default to always transferring at return if not using LO, just like Eclipse does. - return true; - } - MethodInvocationTree methodInvocationTree = node.getTree(); - ExecutableElement executableElement = TreeUtils.elementFromUse(methodInvocationTree); - if (typeFactory.hasMustCallAlias(executableElement)) { - // assume tracking is required - return true; - } - TypeMirror type = ElementUtils.getType(executableElement); - // void or primitive-returning methods are "not owning" by construction - if (type.getKind() == TypeKind.VOID || type.getKind().isPrimitive()) { - return false; - } - TypeElement typeElt = TypesUtils.getTypeElement(type); - // no need to track if type has no possible @MustCall obligation - if (typeElt != null - && typeFactory.hasEmptyMustCallValue(typeElt) - && typeFactory.hasEmptyMustCallValue(methodInvocationTree)) { - return false; - } - // check for absence of @NotOwning annotation - return !typeFactory.hasNotOwning(executableElement); - } - - /** - * Get all successor blocks for some block, except for those corresponding to ignored exception - * types. See {@link ResourceLeakAnalysis#isIgnoredExceptionType(TypeMirror)}. Each exceptional - * successor is paired with the type of exception that leads to it, for use in error messages. - * - * @param block input block - * @return set of pairs (b, t), where b is a successor block, and t is the type of exception for - * the CFG edge from block to b, or {@code null} if b is a non-exceptional successor - */ - private Set> getSuccessorsExceptIgnoredExceptions( - Block block) { - if (block.getType() == Block.BlockType.EXCEPTION_BLOCK) { - ExceptionBlock excBlock = (ExceptionBlock) block; - Set> result = new LinkedHashSet<>(); - // regular successor - Block regularSucc = excBlock.getSuccessor(); - if (regularSucc != null) { - result.add(IPair.of(regularSucc, null)); - } - // non-ignored exception successors - Map> exceptionalSuccessors = excBlock.getExceptionalSuccessors(); - for (Map.Entry> entry : exceptionalSuccessors.entrySet()) { - TypeMirror exceptionType = entry.getKey(); - if (!analysis.isIgnoredExceptionType(exceptionType)) { - for (Block exSucc : entry.getValue()) { - result.add(IPair.of(exSucc, exceptionType)); - } - } - } - return result; - } else { - Set> result = new LinkedHashSet<>(); - for (Block b : block.getSuccessors()) { - result.add(IPair.of(b, null)); - } - return result; - } - } - - /** - * Propagates a set of Obligations to successors, and performs consistency checks when variables - * are going out of scope. - * - *

          The basic algorithm loops over the successor blocks of the current block. For each - * successor, two things happen: - * - *

          First, it constructs an updated set of Obligations using {@code incomingObligations}, the - * nodes in {@code currentBlock}, and the nature of the edge from {@code currentBlock} to the - * successor. The edge can either be normal control flow or an exception. See - * - *

            - *
          • {@link #updateObligationsForAssignment(Set, ControlFlowGraph, AssignmentNode)} - *
          • {@link #updateObligationsForOwningReturn(Set, ControlFlowGraph, ReturnNode)} - *
          • {@link #updateObligationsForInvocation(Set, Node, TypeMirror)} - *
          - * - *

          Second, it checks every Obligation in obligations. If the successor is an exit block or all - * of an Obligation's resource aliases might be going out of scope, then a consistency check - * occurs (with two exceptions, both related to temporary variables that don't actually get - * assigned; see code comments for details) and an error is issued if it fails. If the successor - * is any other kind of block and there is information about at least one of the Obligation's - * aliases in the successor store (i.e. the resource itself definitely does not go out of scope), - * then the Obligation is passed forward to the successor ("propagated") with any definitely - * out-of-scope aliases removed from its resource alias set. - * - * @param cfg the control flow graph - * @param incomingObligations the Obligations for the current block - * @param currentBlock the current block - * @param visited block-Obligations pairs already analyzed or already on the worklist - * @param worklist current worklist - */ - private void propagateObligationsToSuccessorBlocks( - ControlFlowGraph cfg, - Set incomingObligations, - Block currentBlock, - Set visited, - Deque worklist) { - // For each successor block that isn't caused by an ignored exception type, this loop - // computes the set of Obligations that should be propagated to it and then adds it to the - // worklist if any of its resource aliases are still in scope in the successor block. If - // none are, then the loop performs a consistency check for that Obligation. - for (IPair successorAndExceptionType : - getSuccessorsExceptIgnoredExceptions(currentBlock)) { - - // A *mutable* set that eventually holds the set of dataflow facts to be propagated to - // successor blocks. The set is initialized to the current dataflow facts and updated by - // the methods invoked in the for loop below. - Set obligations = new LinkedHashSet<>(incomingObligations); - - // PERFORMANCE NOTE: The computed changes to `obligations` are mostly the same for each - // successor block, but can vary slightly depending on the exception type. There might - // be some opportunities for optimization in this mostly-redundant work. - for (Node node : currentBlock.getNodes()) { - if (node instanceof AssignmentNode) { - updateObligationsForAssignment(obligations, cfg, (AssignmentNode) node); - } else if (node instanceof ReturnNode) { - updateObligationsForOwningReturn(obligations, cfg, (ReturnNode) node); - } else if (node instanceof MethodInvocationNode || node instanceof ObjectCreationNode) { - updateObligationsForInvocation(obligations, node, successorAndExceptionType.second); - } - // All other types of nodes are ignored. This is safe, because other kinds of - // nodes cannot create or modify the resource-alias sets that the algorithm is - // tracking. - } - - propagateObligationsToSuccessorBlock( - obligations, - currentBlock, - successorAndExceptionType.first, - successorAndExceptionType.second, - visited, - worklist); + /** + * If a temporary variable exists for node after typecasts have been removed, return it. + * Otherwise, return node. + * + * @param node a node + * @return either a tempvar for node's content sans typecasts, or node + */ + /*package-private*/ Node removeCastsAndGetTmpVarIfPresent(Node node) { + // TODO: Create temp vars for TypeCastNodes as well, so there is no need to explicitly + // remove casts here. + node = NodeUtils.removeCasts(node); + return getTempVarOrNode(node); } - } - - /** - * Helper for {@link #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, - * Deque)} that propagates obligations along a single edge. - * - * @param obligations the Obligations for the current block - * @param currentBlock the current block - * @param successor a successor of the current block - * @param exceptionType the type of edge from currentBlock to successor - * : null for normal control flow, or a throwable type for exceptional - * control flow - * @param visited block-Obligations pairs already analyzed or already on the worklist - * @param worklist current worklist - */ - private void propagateObligationsToSuccessorBlock( - Set obligations, - Block currentBlock, - Block successor, - @Nullable TypeMirror exceptionType, - Set visited, - Deque worklist) { - List currentBlockNodes = currentBlock.getNodes(); - // successorObligations eventually contains the Obligations to propagate to successor. - // The loop below mutates it. - Set successorObligations = new LinkedHashSet<>(); - // A detailed reason to give in the case that the last resource alias of an Obligation - // goes out of scope without a called-methods type that satisfies the corresponding - // must-call obligation along the current control-flow edge. Computed here for - // efficiency; used in the loop over the Obligations, below. - String exitReasonForErrorMessage = - exceptionType == null - ? - // Technically the variable may be going out of scope before the method - // exit, but that doesn't seem to provide additional helpful - // information. - "regular method exit" - : "possible exceptional exit due to " - + ((ExceptionBlock) currentBlock).getNode().getTree() - + " with exception type " - + exceptionType; - // Computed outside the Obligation loop for efficiency. - AccumulationStore regularStoreOfSuccessor = analysis.getInput(successor).getRegularStore(); - for (Obligation obligation : obligations) { - // This boolean is true if there is no evidence that the Obligation does not go out - // of scope - that is, if there is definitely a resource alias that is in scope in - // the successor. - boolean obligationGoesOutOfScopeBeforeSuccessor = true; - for (ResourceAlias resourceAlias : obligation.resourceAliases) { - if (aliasInScopeInSuccessor(regularStoreOfSuccessor, resourceAlias)) { - obligationGoesOutOfScopeBeforeSuccessor = false; - break; - } - } - // This check is to determine if this Obligation's resource aliases are definitely - // going out of scope: if this is an exit block or there is no information about any - // of them in the successor store, all aliases must be going out of scope and a - // consistency check should occur. - if (successor.getType() == BlockType.SPECIAL_BLOCK /* special blocks are exit blocks */ - || obligationGoesOutOfScopeBeforeSuccessor) { - MustCallAnnotatedTypeFactory mcAtf = - typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - - // If successor is an exceptional successor, and Obligation represents the - // temporary variable for currentBlock's node, do not propagate or do a - // consistency check, as in the exceptional case the "assignment" to the - // temporary variable does not succeed. - // - // Note that this test cannot be "successor.getType() == - // BlockType.EXCEPTIONAL_BLOCK", because not every exceptional successor is an - // exceptional block. For example, the successor might be a regular block - // (containing a catch clause, for example), or a special block indicating an - // exceptional exit. Nor can this test be "currentBlock.getType() == - // BlockType.EXCEPTIONAL_BLOCK", because some exception types are ignored. - // Whether exceptionType is null captures the logic of both of these cases. - if (exceptionType != null) { - Node exceptionalNode = NodeUtils.removeCasts(((ExceptionBlock) currentBlock).getNode()); - LocalVariableNode tmpVarForExcNode = typeFactory.getTempVarForNode(exceptionalNode); - if (tmpVarForExcNode != null - && obligation.resourceAliases.size() == 1 - && obligation.canBeSatisfiedThrough(tmpVarForExcNode)) { - continue; - } - } - - // Always propagate the Obligation to the successor if current block represents - // code nested in a cast. Without this logic, the analysis may report a false - // positive when the Obligation represents a temporary variable for a nested - // expression, as the temporary may not appear in the successor store and hence - // seems to be going out of scope. The temporary will be handled with special - // logic; casts are unwrapped at various points in the analysis. - if (currentBlockNodes.size() == 1 && inCast(currentBlockNodes.get(0))) { - successorObligations.add(obligation); - continue; - } - - // At this point, a consistency check will definitely occur, unless the - // obligation was derived from a MustCallAlias parameter. If it was, an error is - // immediately issued, because such a parameter should not go out of scope - // without its obligation being resolved some other way. - if (obligation.derivedFromMustCallAlias()) { - // MustCallAlias annotations only have meaning if the method returns - // normally, so issue an error if and only if this exit is happening on a - // normal exit path. - if (exceptionType == null - && obligation.whenToEnforce.contains(MethodExitKind.NORMAL_RETURN)) { - checker.reportError( - obligation.resourceAliases.asList().get(0).tree, - "mustcallalias.out.of.scope", - exitReasonForErrorMessage); - } - // Whether or not an error is issued, the check is now complete - there is - // no further checking to do on a must-call-alias-derived obligation along - // an exceptional path. - continue; - } - - // Which stores from the called-methods and must-call checkers are used in the - // consistency check varies depending on the context. Generally speaking, we would - // like to use the store propagated along the CFG edge from currentBlock to - // successor. But, there are special cases to consider. The rules are: - // 1. if the current block has no nodes, it is either a ConditionalBlock or a - // SpecialBlock. - // For the called-methods store, we obtain the exact CFG edge store that we need - // (see getStoreForEdgeFromEmptyBlock()). For the must-call store, due to API - // limitations, we use the following heuristics: - // 1a. if there is information about any alias in the resource alias set - // in the successor store, use the successor's MC store, which - // contains whatever information is true after this block finishes. - // 1b. if there is not any information about any alias in the resource alias - // set in the successor store, use the current block's MC store, - // which contain whatever information is true before this (empty) block. - // 2. if the current block has one or more nodes, always use the CM store after - // the last node. To decide which MC store to use: - // 2a. if the last node in the block is the invocation of an - // @CreatesMustCallFor method that might throw an exception, and the - // consistency check is for an exceptional path, use the MC store - // immediately before the method invocation, because the method threw an - // exception rather than finishing and therefore did not actually create - // any must-call obligation, so the MC store after might contain - // must-call obligations that do not need to be fulfilled along this - // path. - // 2b. in all other cases, use the MC store from after the last node in - // the block. - CFStore mcStore; - AccumulationStore cmStore; - if (currentBlockNodes.size() == 0 /* currentBlock is special or conditional */) { - cmStore = getStoreForEdgeFromEmptyBlock(currentBlock, successor); // 1. (CM) - // For the Must Call Checker, we currently apply a less precise handling and do - // not get the store for the specific CFG edge from currentBlock to successor. - // We do not believe this will impact precision except in convoluted and - // uncommon cases. If we find that we need more precision, we can revisit this, - // but it will require additional API support in the AnalysisResult type to get - // the information that we need. - mcStore = - mcAtf.getStoreForBlock( - obligationGoesOutOfScopeBeforeSuccessor, - currentBlock, // 1a. (MC) - successor); // 1b. (MC) + + /** + * Get the nodes representing the arguments of a method or constructor invocation from the + * invocation node. + * + * @param node a MethodInvocation or ObjectCreation node + * @return the arguments, in order + */ + /*package-private*/ List getArgumentsOfInvocation(Node node) { + if (node instanceof MethodInvocationNode) { + MethodInvocationNode invocationNode = (MethodInvocationNode) node; + return invocationNode.getArguments(); + } else if (node instanceof ObjectCreationNode) { + return ((ObjectCreationNode) node).getArguments(); } else { - // In this case, current block has at least one node. - // Use the called-methods store immediately after the last node in - // currentBlock. - Node last = currentBlockNodes.get(currentBlockNodes.size() - 1); // 2. (CM) - - if (cmStoreAfter.containsKey(last)) { - cmStore = cmStoreAfter.get(last); - } else { - cmStore = typeFactory.getStoreAfter(last); - cmStoreAfter.put(last, cmStore); - } - // If this is an exceptional block, check the MC store beforehand to avoid - // issuing an error about a call to a CreatesMustCallFor method that might - // throw an exception. Otherwise, use the store after. - if (exceptionType != null && isInvocationOfCreatesMustCallForMethod(last)) { - mcStore = mcAtf.getStoreBefore(last); // 2a. (MC) - } else { - if (mcStoreAfter.containsKey(last)) { - mcStore = mcStoreAfter.get(last); - } else { - mcStore = mcAtf.getStoreAfter(last); // 2b. (MC) - mcStoreAfter.put(last, mcStore); - } - } - } - - MethodExitKind exitKind = - exceptionType == null ? MethodExitKind.NORMAL_RETURN : MethodExitKind.EXCEPTIONAL_EXIT; - if (obligation.whenToEnforce.contains(exitKind)) { - checkMustCall(obligation, cmStore, mcStore, exitReasonForErrorMessage); - } - } else { - // In this case, there is info in the successor store about some alias in the - // Obligation. - // Handles the possibility that some resource in the Obligation may go out of - // scope. - Set copyOfResourceAliases = new LinkedHashSet<>(obligation.resourceAliases); - copyOfResourceAliases.removeIf( - alias -> !aliasInScopeInSuccessor(regularStoreOfSuccessor, alias)); - successorObligations.add(new Obligation(copyOfResourceAliases, obligation.whenToEnforce)); - } + throw new TypeSystemError("unexpected node type " + node.getClass()); + } } - propagate(new BlockWithObligations(successor, successorObligations), visited, worklist); - } - - /** - * Gets the store propagated by the {@link ResourceLeakAnalysis} (containing called methods - * information) along a particular CFG edge during local type inference. The source {@link Block} - * of the edge must contain no {@link Node}s. - * - * @param currentBlock source block of the CFG edge. Must contain no {@link Node}s. - * @param successor target block of the CFG edge. - * @return store propagated by the {@link ResourceLeakAnalysis} along the CFG edge. - */ - private AccumulationStore getStoreForEdgeFromEmptyBlock(Block currentBlock, Block successor) { - switch (currentBlock.getType()) { - case CONDITIONAL_BLOCK: - ConditionalBlock condBlock = (ConditionalBlock) currentBlock; - if (condBlock.getThenSuccessor().equals(successor)) { - return analysis.getInput(currentBlock).getThenStore(); - } else if (condBlock.getElseSuccessor().equals(successor)) { - return analysis.getInput(currentBlock).getElseStore(); + /** + * Get the elements representing the formal parameters of a method or constructor, from an + * invocation of that method or constructor. + * + * @param node a method invocation or object creation node + * @return a list of the declarations of the formal parameters of the method or constructor + * being invoked + */ + /*package-private*/ List getParametersOfInvocation(Node node) { + ExecutableElement executableElement; + if (node instanceof MethodInvocationNode) { + MethodInvocationNode invocationNode = (MethodInvocationNode) node; + executableElement = TreeUtils.elementFromUse(invocationNode.getTree()); + } else if (node instanceof ObjectCreationNode) { + executableElement = TreeUtils.elementFromUse(((ObjectCreationNode) node).getTree()); } else { - throw new BugInCF("successor not found"); + throw new TypeSystemError("unexpected node type " + node.getClass()); } - case SPECIAL_BLOCK: - return analysis.getInput(successor).getRegularStore(); - default: - throw new BugInCF("unexpected block type " + currentBlock.getType()); - } - } - - /** - * Returns true if {@code alias.reference} is definitely in-scope in the successor store: that is, - * there is a value for it in {@code successorStore}. - * - * @param successorStore the regular store of the successor block - * @param alias the resource alias to check - * @return true if the variable is definitely in scope for the purposes of the consistency - * checking algorithm in the successor block from which the store came - */ - private boolean aliasInScopeInSuccessor(AccumulationStore successorStore, ResourceAlias alias) { - return successorStore.getValue(alias.reference) != null; - } - - /** - * Returns true if node is a MethodInvocationNode of a method with a CreatesMustCallFor - * annotation. - * - * @param node a node - * @return true if node is a MethodInvocationNode of a method with a CreatesMustCallFor annotation - */ - private boolean isInvocationOfCreatesMustCallForMethod(Node node) { - if (!(node instanceof MethodInvocationNode)) { - return false; - } - MethodInvocationNode miNode = (MethodInvocationNode) node; - return typeFactory.hasCreatesMustCallFor(miNode); - } - - /** - * Finds {@link Owning} formal parameters for the method corresponding to a CFG. - * - * @param cfg the CFG - * @return the owning formal parameters of the method that corresponds to the given cfg, or an - * empty set if the given CFG doesn't correspond to a method body - */ - private Set computeOwningParameters(ControlFlowGraph cfg) { - // TODO what about lambdas? - if (cfg.getUnderlyingAST().getKind() == Kind.METHOD) { - MethodTree method = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); - Set result = new LinkedHashSet<>(1); - for (VariableTree param : method.getParameters()) { - VariableElement paramElement = TreeUtils.elementFromDeclaration(param); - boolean hasMustCallAlias = typeFactory.hasMustCallAlias(paramElement); - if (hasMustCallAlias - || (typeFactory.declaredTypeHasMustCall(param) - && !noLightweightOwnership - && paramElement.getAnnotation(Owning.class) != null)) { - result.add( - new Obligation( - ImmutableSet.of( - new ResourceAlias( - new LocalVariable(paramElement), paramElement, param, hasMustCallAlias)), - Collections.singleton(MethodExitKind.NORMAL_RETURN))); - // Increment numMustCall for each @Owning parameter tracked by the enclosing - // method. - incrementNumMustCall(paramElement); - } - } - return result; - } - return Collections.emptySet(); - } - - /** - * Checks whether there is some resource alias set R in {@code obligations} such that - * R contains a {@link ResourceAlias} whose local variable is {@code node}. - * - * @param obligations the set of Obligations to search - * @param var the local variable to look for - * @return true iff there is a resource alias set in {@code obligations} that contains node - */ - private static boolean varTrackedInObligations( - Set obligations, LocalVariableNode var) { - for (Obligation obligation : obligations) { - if (obligation.canBeSatisfiedThrough(var)) { - return true; - } + + return executableElement.getParameters(); } - return false; - } - - /** - * Gets the Obligation whose resource aliase set contains the given local variable, if one exists - * in {@code obligations}. - * - * @param obligations a set of Obligations - * @param node variable of interest - * @return the Obligation in {@code obligations} whose resource alias set contains {@code node}, - * or {@code null} if there is no such Obligation - */ - /*package-private*/ static @Nullable Obligation getObligationForVar( - Set obligations, LocalVariableNode node) { - for (Obligation obligation : obligations) { - if (obligation.canBeSatisfiedThrough(node)) { - return obligation; - } + + /** + * Is the return type of the invoked method one that should be tracked? + * + * @param node a method invocation + * @return true iff the checker is in no-lightweight-ownership mode, or the method has a + * {@code @MustCallAlias} annotation, or (1) the method has a return type that needs to be + * tracked (i.e., it has a non-empty {@code @MustCall} obligation and (2) the method + * declaration does not have a {@code @NotOwning} annotation + */ + private boolean shouldTrackReturnType(MethodInvocationNode node) { + if (noLightweightOwnership) { + // Default to always transferring at return if not using LO, just like Eclipse does. + return true; + } + MethodInvocationTree methodInvocationTree = node.getTree(); + ExecutableElement executableElement = TreeUtils.elementFromUse(methodInvocationTree); + if (typeFactory.hasMustCallAlias(executableElement)) { + // assume tracking is required + return true; + } + TypeMirror type = ElementUtils.getType(executableElement); + // void or primitive-returning methods are "not owning" by construction + if (type.getKind() == TypeKind.VOID || type.getKind().isPrimitive()) { + return false; + } + TypeElement typeElt = TypesUtils.getTypeElement(type); + // no need to track if type has no possible @MustCall obligation + if (typeElt != null + && typeFactory.hasEmptyMustCallValue(typeElt) + && typeFactory.hasEmptyMustCallValue(methodInvocationTree)) { + return false; + } + // check for absence of @NotOwning annotation + return !typeFactory.hasNotOwning(executableElement); } - return null; - } - - /** - * For the given Obligation, checks that at least one of its variables has its {@code @MustCall} - * obligation satisfied, based on {@code @CalledMethods} and {@code @MustCall} types in the given - * stores. - * - * @param obligation the Obligation - * @param cmStore the called-methods store - * @param mcStore the must-call store - * @param outOfScopeReason if the {@code @MustCall} obligation is not satisfied, a useful - * explanation to include in the error message - */ - private void checkMustCall( - Obligation obligation, AccumulationStore cmStore, CFStore mcStore, String outOfScopeReason) { - - Map> mustCallValues = - obligation.getMustCallMethods(typeFactory, mcStore); - - // Optimization: if mustCallValues is null, always issue a warning (there is no way to - // satisfy the check). A null mustCallValue occurs when the type is top - // (@MustCallUnknown). - if (mustCallValues == null) { - // Report the error at the first alias' definition. This choice is arbitrary but - // consistent. - ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); - if (!reportedErrorAliases.contains(firstAlias)) { - if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { - reportedErrorAliases.add(firstAlias); - checker.reportError( - firstAlias.tree, - "required.method.not.known", - firstAlias.stringForErrorMessage(), - firstAlias.reference.getType().toString(), - outOfScopeReason); - } - } - return; + + /** + * Get all successor blocks for some block, except for those corresponding to ignored exception + * types. See {@link ResourceLeakAnalysis#isIgnoredExceptionType(TypeMirror)}. Each exceptional + * successor is paired with the type of exception that leads to it, for use in error messages. + * + * @param block input block + * @return set of pairs (b, t), where b is a successor block, and t is the type of exception for + * the CFG edge from block to b, or {@code null} if b is a non-exceptional successor + */ + private Set> getSuccessorsExceptIgnoredExceptions( + Block block) { + if (block.getType() == Block.BlockType.EXCEPTION_BLOCK) { + ExceptionBlock excBlock = (ExceptionBlock) block; + Set> result = new LinkedHashSet<>(); + // regular successor + Block regularSucc = excBlock.getSuccessor(); + if (regularSucc != null) { + result.add(IPair.of(regularSucc, null)); + } + // non-ignored exception successors + Map> exceptionalSuccessors = excBlock.getExceptionalSuccessors(); + for (Map.Entry> entry : exceptionalSuccessors.entrySet()) { + TypeMirror exceptionType = entry.getKey(); + if (!analysis.isIgnoredExceptionType(exceptionType)) { + for (Block exSucc : entry.getValue()) { + result.add(IPair.of(exSucc, exceptionType)); + } + } + } + return result; + } else { + Set> result = new LinkedHashSet<>(); + for (Block b : block.getSuccessors()) { + result.add(IPair.of(b, null)); + } + return result; + } } - if (mustCallValues.isEmpty()) { - throw new TypeSystemError("unexpected empty must-call values for obligation " + obligation); + + /** + * Propagates a set of Obligations to successors, and performs consistency checks when variables + * are going out of scope. + * + *

          The basic algorithm loops over the successor blocks of the current block. For each + * successor, two things happen: + * + *

          First, it constructs an updated set of Obligations using {@code incomingObligations}, the + * nodes in {@code currentBlock}, and the nature of the edge from {@code currentBlock} to the + * successor. The edge can either be normal control flow or an exception. See + * + *

            + *
          • {@link #updateObligationsForAssignment(Set, ControlFlowGraph, AssignmentNode)} + *
          • {@link #updateObligationsForOwningReturn(Set, ControlFlowGraph, ReturnNode)} + *
          • {@link #updateObligationsForInvocation(Set, Node, TypeMirror)} + *
          + * + *

          Second, it checks every Obligation in obligations. If the successor is an exit block or + * all of an Obligation's resource aliases might be going out of scope, then a consistency check + * occurs (with two exceptions, both related to temporary variables that don't actually get + * assigned; see code comments for details) and an error is issued if it fails. If the successor + * is any other kind of block and there is information about at least one of the Obligation's + * aliases in the successor store (i.e. the resource itself definitely does not go out of + * scope), then the Obligation is passed forward to the successor ("propagated") with any + * definitely out-of-scope aliases removed from its resource alias set. + * + * @param cfg the control flow graph + * @param incomingObligations the Obligations for the current block + * @param currentBlock the current block + * @param visited block-Obligations pairs already analyzed or already on the worklist + * @param worklist current worklist + */ + private void propagateObligationsToSuccessorBlocks( + ControlFlowGraph cfg, + Set incomingObligations, + Block currentBlock, + Set visited, + Deque worklist) { + // For each successor block that isn't caused by an ignored exception type, this loop + // computes the set of Obligations that should be propagated to it and then adds it to the + // worklist if any of its resource aliases are still in scope in the successor block. If + // none are, then the loop performs a consistency check for that Obligation. + for (IPair successorAndExceptionType : + getSuccessorsExceptIgnoredExceptions(currentBlock)) { + + // A *mutable* set that eventually holds the set of dataflow facts to be propagated to + // successor blocks. The set is initialized to the current dataflow facts and updated by + // the methods invoked in the for loop below. + Set obligations = new LinkedHashSet<>(incomingObligations); + + // PERFORMANCE NOTE: The computed changes to `obligations` are mostly the same for each + // successor block, but can vary slightly depending on the exception type. There might + // be some opportunities for optimization in this mostly-redundant work. + for (Node node : currentBlock.getNodes()) { + if (node instanceof AssignmentNode) { + updateObligationsForAssignment(obligations, cfg, (AssignmentNode) node); + } else if (node instanceof ReturnNode) { + updateObligationsForOwningReturn(obligations, cfg, (ReturnNode) node); + } else if (node instanceof MethodInvocationNode + || node instanceof ObjectCreationNode) { + updateObligationsForInvocation( + obligations, node, successorAndExceptionType.second); + } + // All other types of nodes are ignored. This is safe, because other kinds of + // nodes cannot create or modify the resource-alias sets that the algorithm is + // tracking. + } + + propagateObligationsToSuccessorBlock( + obligations, + currentBlock, + successorAndExceptionType.first, + successorAndExceptionType.second, + visited, + worklist); + } } - boolean mustCallSatisfied = false; - for (ResourceAlias alias : obligation.resourceAliases) { - - List mustCallValuesForAlias = mustCallValues.get(alias); - // optimization when there are no methods to call - if (mustCallValuesForAlias.isEmpty()) { - mustCallSatisfied = true; - break; - } - - // sometimes the store is null! this looks like a bug in checker dataflow. - // TODO track down and report the root-cause bug - AccumulationValue cmValue = cmStore != null ? cmStore.getValue(alias.reference) : null; - AnnotationMirror cmAnno = null; - - if (cmValue != null) { // When store contains the lhs - Set accumulatedValues = cmValue.getAccumulatedValues(); - if (accumulatedValues != null) { // type variable or wildcard type - cmAnno = typeFactory.createCalledMethods(accumulatedValues.toArray(new String[0])); - } else { - for (AnnotationMirror anno : cmValue.getAnnotations()) { - if (AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods") - || AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.calledmethods.qual.CalledMethodsBottom")) { - cmAnno = anno; + /** + * Helper for {@link #propagateObligationsToSuccessorBlocks(ControlFlowGraph, Set, Block, Set, + * Deque)} that propagates obligations along a single edge. + * + * @param obligations the Obligations for the current block + * @param currentBlock the current block + * @param successor a successor of the current block + * @param exceptionType the type of edge from currentBlock to successor + * : null for normal control flow, or a throwable type for exceptional + * control flow + * @param visited block-Obligations pairs already analyzed or already on the worklist + * @param worklist current worklist + */ + private void propagateObligationsToSuccessorBlock( + Set obligations, + Block currentBlock, + Block successor, + @Nullable TypeMirror exceptionType, + Set visited, + Deque worklist) { + List currentBlockNodes = currentBlock.getNodes(); + // successorObligations eventually contains the Obligations to propagate to successor. + // The loop below mutates it. + Set successorObligations = new LinkedHashSet<>(); + // A detailed reason to give in the case that the last resource alias of an Obligation + // goes out of scope without a called-methods type that satisfies the corresponding + // must-call obligation along the current control-flow edge. Computed here for + // efficiency; used in the loop over the Obligations, below. + String exitReasonForErrorMessage = + exceptionType == null + ? + // Technically the variable may be going out of scope before the method + // exit, but that doesn't seem to provide additional helpful + // information. + "regular method exit" + : "possible exceptional exit due to " + + ((ExceptionBlock) currentBlock).getNode().getTree() + + " with exception type " + + exceptionType; + // Computed outside the Obligation loop for efficiency. + AccumulationStore regularStoreOfSuccessor = analysis.getInput(successor).getRegularStore(); + for (Obligation obligation : obligations) { + // This boolean is true if there is no evidence that the Obligation does not go out + // of scope - that is, if there is definitely a resource alias that is in scope in + // the successor. + boolean obligationGoesOutOfScopeBeforeSuccessor = true; + for (ResourceAlias resourceAlias : obligation.resourceAliases) { + if (aliasInScopeInSuccessor(regularStoreOfSuccessor, resourceAlias)) { + obligationGoesOutOfScopeBeforeSuccessor = false; + break; + } + } + // This check is to determine if this Obligation's resource aliases are definitely + // going out of scope: if this is an exit block or there is no information about any + // of them in the successor store, all aliases must be going out of scope and a + // consistency check should occur. + if (successor.getType() == BlockType.SPECIAL_BLOCK /* special blocks are exit blocks */ + || obligationGoesOutOfScopeBeforeSuccessor) { + MustCallAnnotatedTypeFactory mcAtf = + typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + + // If successor is an exceptional successor, and Obligation represents the + // temporary variable for currentBlock's node, do not propagate or do a + // consistency check, as in the exceptional case the "assignment" to the + // temporary variable does not succeed. + // + // Note that this test cannot be "successor.getType() == + // BlockType.EXCEPTIONAL_BLOCK", because not every exceptional successor is an + // exceptional block. For example, the successor might be a regular block + // (containing a catch clause, for example), or a special block indicating an + // exceptional exit. Nor can this test be "currentBlock.getType() == + // BlockType.EXCEPTIONAL_BLOCK", because some exception types are ignored. + // Whether exceptionType is null captures the logic of both of these cases. + if (exceptionType != null) { + Node exceptionalNode = + NodeUtils.removeCasts(((ExceptionBlock) currentBlock).getNode()); + LocalVariableNode tmpVarForExcNode = + typeFactory.getTempVarForNode(exceptionalNode); + if (tmpVarForExcNode != null + && obligation.resourceAliases.size() == 1 + && obligation.canBeSatisfiedThrough(tmpVarForExcNode)) { + continue; + } + } + + // Always propagate the Obligation to the successor if current block represents + // code nested in a cast. Without this logic, the analysis may report a false + // positive when the Obligation represents a temporary variable for a nested + // expression, as the temporary may not appear in the successor store and hence + // seems to be going out of scope. The temporary will be handled with special + // logic; casts are unwrapped at various points in the analysis. + if (currentBlockNodes.size() == 1 && inCast(currentBlockNodes.get(0))) { + successorObligations.add(obligation); + continue; + } + + // At this point, a consistency check will definitely occur, unless the + // obligation was derived from a MustCallAlias parameter. If it was, an error is + // immediately issued, because such a parameter should not go out of scope + // without its obligation being resolved some other way. + if (obligation.derivedFromMustCallAlias()) { + // MustCallAlias annotations only have meaning if the method returns + // normally, so issue an error if and only if this exit is happening on a + // normal exit path. + if (exceptionType == null + && obligation.whenToEnforce.contains(MethodExitKind.NORMAL_RETURN)) { + checker.reportError( + obligation.resourceAliases.asList().get(0).tree, + "mustcallalias.out.of.scope", + exitReasonForErrorMessage); + } + // Whether or not an error is issued, the check is now complete - there is + // no further checking to do on a must-call-alias-derived obligation along + // an exceptional path. + continue; + } + + // Which stores from the called-methods and must-call checkers are used in the + // consistency check varies depending on the context. Generally speaking, we would + // like to use the store propagated along the CFG edge from currentBlock to + // successor. But, there are special cases to consider. The rules are: + // 1. if the current block has no nodes, it is either a ConditionalBlock or a + // SpecialBlock. + // For the called-methods store, we obtain the exact CFG edge store that we need + // (see getStoreForEdgeFromEmptyBlock()). For the must-call store, due to API + // limitations, we use the following heuristics: + // 1a. if there is information about any alias in the resource alias set + // in the successor store, use the successor's MC store, which + // contains whatever information is true after this block finishes. + // 1b. if there is not any information about any alias in the resource alias + // set in the successor store, use the current block's MC store, + // which contain whatever information is true before this (empty) block. + // 2. if the current block has one or more nodes, always use the CM store after + // the last node. To decide which MC store to use: + // 2a. if the last node in the block is the invocation of an + // @CreatesMustCallFor method that might throw an exception, and the + // consistency check is for an exceptional path, use the MC store + // immediately before the method invocation, because the method threw an + // exception rather than finishing and therefore did not actually create + // any must-call obligation, so the MC store after might contain + // must-call obligations that do not need to be fulfilled along this + // path. + // 2b. in all other cases, use the MC store from after the last node in + // the block. + CFStore mcStore; + AccumulationStore cmStore; + if (currentBlockNodes.size() == 0 /* currentBlock is special or conditional */) { + cmStore = getStoreForEdgeFromEmptyBlock(currentBlock, successor); // 1. (CM) + // For the Must Call Checker, we currently apply a less precise handling and do + // not get the store for the specific CFG edge from currentBlock to successor. + // We do not believe this will impact precision except in convoluted and + // uncommon cases. If we find that we need more precision, we can revisit this, + // but it will require additional API support in the AnalysisResult type to get + // the information that we need. + mcStore = + mcAtf.getStoreForBlock( + obligationGoesOutOfScopeBeforeSuccessor, + currentBlock, // 1a. (MC) + successor); // 1b. (MC) + } else { + // In this case, current block has at least one node. + // Use the called-methods store immediately after the last node in + // currentBlock. + Node last = currentBlockNodes.get(currentBlockNodes.size() - 1); // 2. (CM) + + if (cmStoreAfter.containsKey(last)) { + cmStore = cmStoreAfter.get(last); + } else { + cmStore = typeFactory.getStoreAfter(last); + cmStoreAfter.put(last, cmStore); + } + // If this is an exceptional block, check the MC store beforehand to avoid + // issuing an error about a call to a CreatesMustCallFor method that might + // throw an exception. Otherwise, use the store after. + if (exceptionType != null && isInvocationOfCreatesMustCallForMethod(last)) { + mcStore = mcAtf.getStoreBefore(last); // 2a. (MC) + } else { + if (mcStoreAfter.containsKey(last)) { + mcStore = mcStoreAfter.get(last); + } else { + mcStore = mcAtf.getStoreAfter(last); // 2b. (MC) + mcStoreAfter.put(last, mcStore); + } + } + } + + MethodExitKind exitKind = + exceptionType == null + ? MethodExitKind.NORMAL_RETURN + : MethodExitKind.EXCEPTIONAL_EXIT; + if (obligation.whenToEnforce.contains(exitKind)) { + checkMustCall(obligation, cmStore, mcStore, exitReasonForErrorMessage); + } + } else { + // In this case, there is info in the successor store about some alias in the + // Obligation. + // Handles the possibility that some resource in the Obligation may go out of + // scope. + Set copyOfResourceAliases = + new LinkedHashSet<>(obligation.resourceAliases); + copyOfResourceAliases.removeIf( + alias -> !aliasInScopeInSuccessor(regularStoreOfSuccessor, alias)); + successorObligations.add( + new Obligation(copyOfResourceAliases, obligation.whenToEnforce)); } - } - } - } - if (cmAnno == null) { - cmAnno = - typeFactory - .getAnnotatedType(alias.element) - .getEffectiveAnnotationInHierarchy(typeFactory.top); - } - - if (calledMethodsSatisfyMustCall(mustCallValuesForAlias, cmAnno)) { - mustCallSatisfied = true; - break; - } + } + + propagate(new BlockWithObligations(successor, successorObligations), visited, worklist); } - if (!mustCallSatisfied) { - // Report the error at the first alias' definition. This choice is arbitrary but - // consistent. - ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); - if (!reportedErrorAliases.contains(firstAlias)) { - if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { - reportedErrorAliases.add(firstAlias); - checker.reportError( - firstAlias.tree, - "required.method.not.called", - formatMissingMustCallMethods(mustCallValues.get(firstAlias)), - firstAlias.stringForErrorMessage(), - firstAlias.reference.getType().toString(), - outOfScopeReason); - } - } + /** + * Gets the store propagated by the {@link ResourceLeakAnalysis} (containing called methods + * information) along a particular CFG edge during local type inference. The source {@link + * Block} of the edge must contain no {@link Node}s. + * + * @param currentBlock source block of the CFG edge. Must contain no {@link Node}s. + * @param successor target block of the CFG edge. + * @return store propagated by the {@link ResourceLeakAnalysis} along the CFG edge. + */ + private AccumulationStore getStoreForEdgeFromEmptyBlock(Block currentBlock, Block successor) { + switch (currentBlock.getType()) { + case CONDITIONAL_BLOCK: + ConditionalBlock condBlock = (ConditionalBlock) currentBlock; + if (condBlock.getThenSuccessor().equals(successor)) { + return analysis.getInput(currentBlock).getThenStore(); + } else if (condBlock.getElseSuccessor().equals(successor)) { + return analysis.getInput(currentBlock).getElseStore(); + } else { + throw new BugInCF("successor not found"); + } + case SPECIAL_BLOCK: + return analysis.getInput(successor).getRegularStore(); + default: + throw new BugInCF("unexpected block type " + currentBlock.getType()); + } } - } - - /** - * Increment the -AcountMustCall counter. - * - * @param node the node being counted, to extract the type - */ - private void incrementNumMustCall(Node node) { - if (countMustCall) { - TypeMirror type = node.getType(); - incrementMustCallImpl(type); + + /** + * Returns true if {@code alias.reference} is definitely in-scope in the successor store: that + * is, there is a value for it in {@code successorStore}. + * + * @param successorStore the regular store of the successor block + * @param alias the resource alias to check + * @return true if the variable is definitely in scope for the purposes of the consistency + * checking algorithm in the successor block from which the store came + */ + private boolean aliasInScopeInSuccessor(AccumulationStore successorStore, ResourceAlias alias) { + return successorStore.getValue(alias.reference) != null; } - } - - /** - * Increment the -AcountMustCall counter. - * - * @param elt the elt being counted, to extract the type - */ - private void incrementNumMustCall(Element elt) { - if (countMustCall) { - TypeMirror type = elt.asType(); - incrementMustCallImpl(type); + + /** + * Returns true if node is a MethodInvocationNode of a method with a CreatesMustCallFor + * annotation. + * + * @param node a node + * @return true if node is a MethodInvocationNode of a method with a CreatesMustCallFor + * annotation + */ + private boolean isInvocationOfCreatesMustCallForMethod(Node node) { + if (!(node instanceof MethodInvocationNode)) { + return false; + } + MethodInvocationNode miNode = (MethodInvocationNode) node; + return typeFactory.hasCreatesMustCallFor(miNode); } - } - - /** - * Shared implementation for the two version of countMustCall. Don't call this directly. - * - * @param type the type of the object that has a must call obligation - */ - private void incrementMustCallImpl(TypeMirror type) { - // only count uses of JDK classes, since that's what the paper reported - if (!isJdkClass(TypesUtils.getTypeElement(type).getQualifiedName().toString())) { - return; + + /** + * Finds {@link Owning} formal parameters for the method corresponding to a CFG. + * + * @param cfg the CFG + * @return the owning formal parameters of the method that corresponds to the given cfg, or an + * empty set if the given CFG doesn't correspond to a method body + */ + private Set computeOwningParameters(ControlFlowGraph cfg) { + // TODO what about lambdas? + if (cfg.getUnderlyingAST().getKind() == Kind.METHOD) { + MethodTree method = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); + Set result = new LinkedHashSet<>(1); + for (VariableTree param : method.getParameters()) { + VariableElement paramElement = TreeUtils.elementFromDeclaration(param); + boolean hasMustCallAlias = typeFactory.hasMustCallAlias(paramElement); + if (hasMustCallAlias + || (typeFactory.declaredTypeHasMustCall(param) + && !noLightweightOwnership + && paramElement.getAnnotation(Owning.class) != null)) { + result.add( + new Obligation( + ImmutableSet.of( + new ResourceAlias( + new LocalVariable(paramElement), + paramElement, + param, + hasMustCallAlias)), + Collections.singleton(MethodExitKind.NORMAL_RETURN))); + // Increment numMustCall for each @Owning parameter tracked by the enclosing + // method. + incrementNumMustCall(paramElement); + } + } + return result; + } + return Collections.emptySet(); } - checker.numMustCall++; - } - - /** - * Is the given class a java* class? This is a heuristic for whether the class was defined in the - * JDK. - * - * @param qualifiedName a fully qualified name of a class - * @return true iff the type's fully-qualified name starts with "java", indicating that it is from - * a java.* or javax.* package (probably) - */ - /*package-private*/ static boolean isJdkClass(String qualifiedName) { - return qualifiedName.startsWith("java"); - } - - /** - * Do the called methods represented by the {@link CalledMethods} type {@code cmAnno} include all - * the methods in {@code mustCallValues}? - * - * @param mustCallValues the strings representing the must-call obligations - * @param cmAnno an annotation from the called-methods type hierarchy - * @return true iff cmAnno is a subtype of a called-methods annotation with the same values as - * mustCallValues - */ - /*package-private*/ boolean calledMethodsSatisfyMustCall( - List mustCallValues, AnnotationMirror cmAnno) { - // Create this annotation and use a subtype test because there's no guarantee that - // cmAnno is actually an instance of CalledMethods: it could be CMBottom or CMPredicate. - AnnotationMirror cmAnnoForMustCallMethods = - typeFactory.createCalledMethods(mustCallValues.toArray(new String[0])); - return typeFactory - .getQualifierHierarchy() - .isSubtypeQualifiersOnly(cmAnno, cmAnnoForMustCallMethods); - } - - /** - * If the input {@code state} has not been visited yet, add it to {@code visited} and {@code - * worklist}. - * - * @param state the current state - * @param visited the states that have been analyzed or are already on the worklist - * @param worklist the states that will be analyzed - */ - private static void propagate( - BlockWithObligations state, - Set visited, - Deque worklist) { - - if (visited.add(state)) { - worklist.add(state); + + /** + * Checks whether there is some resource alias set R in {@code obligations} such that + * R contains a {@link ResourceAlias} whose local variable is {@code node}. + * + * @param obligations the set of Obligations to search + * @param var the local variable to look for + * @return true iff there is a resource alias set in {@code obligations} that contains node + */ + private static boolean varTrackedInObligations( + Set obligations, LocalVariableNode var) { + for (Obligation obligation : obligations) { + if (obligation.canBeSatisfiedThrough(var)) { + return true; + } + } + return false; } - } - - /** - * Formats a list of must-call method names to be printed in an error message. - * - * @param mustCallVal the list of must-call strings - * @return a formatted string - */ - /*package-private*/ static String formatMissingMustCallMethods(List mustCallVal) { - int size = mustCallVal.size(); - if (size == 0) { - throw new TypeSystemError("empty mustCallVal " + mustCallVal); - } else if (size == 1) { - return "method " + mustCallVal.get(0); - } else { - return "methods " + String.join(", ", mustCallVal); + + /** + * Gets the Obligation whose resource aliase set contains the given local variable, if one + * exists in {@code obligations}. + * + * @param obligations a set of Obligations + * @param node variable of interest + * @return the Obligation in {@code obligations} whose resource alias set contains {@code node}, + * or {@code null} if there is no such Obligation + */ + /*package-private*/ static @Nullable Obligation getObligationForVar( + Set obligations, LocalVariableNode node) { + for (Obligation obligation : obligations) { + if (obligation.canBeSatisfiedThrough(node)) { + return obligation; + } + } + return null; } - } - /** - * A pair of a {@link Block} and a set of dataflow facts on entry to the block. Each dataflow fact - * represents a set of resource aliases for some tracked resource. The analyzer's worklist - * consists of BlockWithObligations objects, each representing the need to handle the set of - * dataflow facts reaching the block during analysis. - */ - /*package-private*/ static class BlockWithObligations { + /** + * For the given Obligation, checks that at least one of its variables has its {@code @MustCall} + * obligation satisfied, based on {@code @CalledMethods} and {@code @MustCall} types in the + * given stores. + * + * @param obligation the Obligation + * @param cmStore the called-methods store + * @param mcStore the must-call store + * @param outOfScopeReason if the {@code @MustCall} obligation is not satisfied, a useful + * explanation to include in the error message + */ + private void checkMustCall( + Obligation obligation, + AccumulationStore cmStore, + CFStore mcStore, + String outOfScopeReason) { + + Map> mustCallValues = + obligation.getMustCallMethods(typeFactory, mcStore); + + // Optimization: if mustCallValues is null, always issue a warning (there is no way to + // satisfy the check). A null mustCallValue occurs when the type is top + // (@MustCallUnknown). + if (mustCallValues == null) { + // Report the error at the first alias' definition. This choice is arbitrary but + // consistent. + ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); + if (!reportedErrorAliases.contains(firstAlias)) { + if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { + reportedErrorAliases.add(firstAlias); + checker.reportError( + firstAlias.tree, + "required.method.not.known", + firstAlias.stringForErrorMessage(), + firstAlias.reference.getType().toString(), + outOfScopeReason); + } + } + return; + } + if (mustCallValues.isEmpty()) { + throw new TypeSystemError( + "unexpected empty must-call values for obligation " + obligation); + } - /** The block. */ - public final Block block; + boolean mustCallSatisfied = false; + for (ResourceAlias alias : obligation.resourceAliases) { - /** The dataflow facts. */ - public final ImmutableSet obligations; + List mustCallValuesForAlias = mustCallValues.get(alias); + // optimization when there are no methods to call + if (mustCallValuesForAlias.isEmpty()) { + mustCallSatisfied = true; + break; + } + + // sometimes the store is null! this looks like a bug in checker dataflow. + // TODO track down and report the root-cause bug + AccumulationValue cmValue = cmStore != null ? cmStore.getValue(alias.reference) : null; + AnnotationMirror cmAnno = null; + + if (cmValue != null) { // When store contains the lhs + Set accumulatedValues = cmValue.getAccumulatedValues(); + if (accumulatedValues != null) { // type variable or wildcard type + cmAnno = + typeFactory.createCalledMethods( + accumulatedValues.toArray(new String[0])); + } else { + for (AnnotationMirror anno : cmValue.getAnnotations()) { + if (AnnotationUtils.areSameByName( + anno, + "org.checkerframework.checker.calledmethods.qual.CalledMethods") + || AnnotationUtils.areSameByName( + anno, + "org.checkerframework.checker.calledmethods.qual.CalledMethodsBottom")) { + cmAnno = anno; + } + } + } + } + if (cmAnno == null) { + cmAnno = + typeFactory + .getAnnotatedType(alias.element) + .getEffectiveAnnotationInHierarchy(typeFactory.top); + } + + if (calledMethodsSatisfyMustCall(mustCallValuesForAlias, cmAnno)) { + mustCallSatisfied = true; + break; + } + } + + if (!mustCallSatisfied) { + // Report the error at the first alias' definition. This choice is arbitrary but + // consistent. + ResourceAlias firstAlias = obligation.resourceAliases.iterator().next(); + if (!reportedErrorAliases.contains(firstAlias)) { + if (!checker.shouldSkipUses(TreeUtils.elementFromTree(firstAlias.tree))) { + reportedErrorAliases.add(firstAlias); + checker.reportError( + firstAlias.tree, + "required.method.not.called", + formatMissingMustCallMethods(mustCallValues.get(firstAlias)), + firstAlias.stringForErrorMessage(), + firstAlias.reference.getType().toString(), + outOfScopeReason); + } + } + } + } /** - * Create a new BlockWithObligations from a block and a set of dataflow facts. + * Increment the -AcountMustCall counter. * - * @param b the block - * @param obligations the set of incoming Obligations at the start of the block (may be the - * empty set) + * @param node the node being counted, to extract the type */ - public BlockWithObligations(Block b, Set obligations) { - this.block = b; - this.obligations = ImmutableSet.copyOf(obligations); + private void incrementNumMustCall(Node node) { + if (countMustCall) { + TypeMirror type = node.getType(); + incrementMustCallImpl(type); + } } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - BlockWithObligations that = (BlockWithObligations) o; - return block.equals(that.block) && obligations.equals(that.obligations); + /** + * Increment the -AcountMustCall counter. + * + * @param elt the elt being counted, to extract the type + */ + private void incrementNumMustCall(Element elt) { + if (countMustCall) { + TypeMirror type = elt.asType(); + incrementMustCallImpl(type); + } + } + + /** + * Shared implementation for the two version of countMustCall. Don't call this directly. + * + * @param type the type of the object that has a must call obligation + */ + private void incrementMustCallImpl(TypeMirror type) { + // only count uses of JDK classes, since that's what the paper reported + if (!isJdkClass(TypesUtils.getTypeElement(type).getQualifiedName().toString())) { + return; + } + checker.numMustCall++; + } + + /** + * Is the given class a java* class? This is a heuristic for whether the class was defined in + * the JDK. + * + * @param qualifiedName a fully qualified name of a class + * @return true iff the type's fully-qualified name starts with "java", indicating that it is + * from a java.* or javax.* package (probably) + */ + /*package-private*/ static boolean isJdkClass(String qualifiedName) { + return qualifiedName.startsWith("java"); } - @Override - public int hashCode() { - return Objects.hash(block, obligations); + /** + * Do the called methods represented by the {@link CalledMethods} type {@code cmAnno} include + * all the methods in {@code mustCallValues}? + * + * @param mustCallValues the strings representing the must-call obligations + * @param cmAnno an annotation from the called-methods type hierarchy + * @return true iff cmAnno is a subtype of a called-methods annotation with the same values as + * mustCallValues + */ + /*package-private*/ boolean calledMethodsSatisfyMustCall( + List mustCallValues, AnnotationMirror cmAnno) { + // Create this annotation and use a subtype test because there's no guarantee that + // cmAnno is actually an instance of CalledMethods: it could be CMBottom or CMPredicate. + AnnotationMirror cmAnnoForMustCallMethods = + typeFactory.createCalledMethods(mustCallValues.toArray(new String[0])); + return typeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly(cmAnno, cmAnnoForMustCallMethods); } - @Override - public String toString() { - return String.format( - "BWO{%s %d, %d obligations %d}", - block.getType(), block.getUid(), obligations.size(), obligations.hashCode()); + /** + * If the input {@code state} has not been visited yet, add it to {@code visited} and {@code + * worklist}. + * + * @param state the current state + * @param visited the states that have been analyzed or are already on the worklist + * @param worklist the states that will be analyzed + */ + private static void propagate( + BlockWithObligations state, + Set visited, + Deque worklist) { + + if (visited.add(state)) { + worklist.add(state); + } } /** - * Returns a printed representation of a collection of BlockWithObligations. If a - * BlockWithObligations appears multiple times in the collection, it is printed more succinctly - * after the first time. + * Formats a list of must-call method names to be printed in an error message. * - * @param bwos a collection of BlockWithObligations, to format - * @return a printed representation of a collection of BlockWithObligations + * @param mustCallVal the list of must-call strings + * @return a formatted string */ - public static String collectionToString(Collection bwos) { - List blocksWithDuplicates = new ArrayList<>(); - for (BlockWithObligations bwo : bwos) { - blocksWithDuplicates.add(bwo.block); - } - Collection duplicateBlocks = CollectionsPlume.duplicates(blocksWithDuplicates); - StringJoiner result = new StringJoiner(", ", "BWOs[", "]"); - for (BlockWithObligations bwo : bwos) { - ImmutableSet obligations = bwo.obligations; - if (duplicateBlocks.contains(bwo.block)) { - result.add( - String.format( - "BWO{%s %d, %d obligations %s}", - bwo.block.getType(), bwo.block.getUid(), obligations.size(), obligations)); + /*package-private*/ static String formatMissingMustCallMethods(List mustCallVal) { + int size = mustCallVal.size(); + if (size == 0) { + throw new TypeSystemError("empty mustCallVal " + mustCallVal); + } else if (size == 1) { + return "method " + mustCallVal.get(0); } else { - result.add( - String.format( - "BWO{%s %d, %d obligations}", - bwo.block.getType(), bwo.block.getUid(), obligations.size())); + return "methods " + String.join(", ", mustCallVal); + } + } + + /** + * A pair of a {@link Block} and a set of dataflow facts on entry to the block. Each dataflow + * fact represents a set of resource aliases for some tracked resource. The analyzer's worklist + * consists of BlockWithObligations objects, each representing the need to handle the set of + * dataflow facts reaching the block during analysis. + */ + /*package-private*/ static class BlockWithObligations { + + /** The block. */ + public final Block block; + + /** The dataflow facts. */ + public final ImmutableSet obligations; + + /** + * Create a new BlockWithObligations from a block and a set of dataflow facts. + * + * @param b the block + * @param obligations the set of incoming Obligations at the start of the block (may be the + * empty set) + */ + public BlockWithObligations(Block b, Set obligations) { + this.block = b; + this.obligations = ImmutableSet.copyOf(obligations); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BlockWithObligations that = (BlockWithObligations) o; + return block.equals(that.block) && obligations.equals(that.obligations); + } + + @Override + public int hashCode() { + return Objects.hash(block, obligations); + } + + @Override + public String toString() { + return String.format( + "BWO{%s %d, %d obligations %d}", + block.getType(), block.getUid(), obligations.size(), obligations.hashCode()); + } + + /** + * Returns a printed representation of a collection of BlockWithObligations. If a + * BlockWithObligations appears multiple times in the collection, it is printed more + * succinctly after the first time. + * + * @param bwos a collection of BlockWithObligations, to format + * @return a printed representation of a collection of BlockWithObligations + */ + public static String collectionToString(Collection bwos) { + List blocksWithDuplicates = new ArrayList<>(); + for (BlockWithObligations bwo : bwos) { + blocksWithDuplicates.add(bwo.block); + } + Collection duplicateBlocks = CollectionsPlume.duplicates(blocksWithDuplicates); + StringJoiner result = new StringJoiner(", ", "BWOs[", "]"); + for (BlockWithObligations bwo : bwos) { + ImmutableSet obligations = bwo.obligations; + if (duplicateBlocks.contains(bwo.block)) { + result.add( + String.format( + "BWO{%s %d, %d obligations %s}", + bwo.block.getType(), + bwo.block.getUid(), + obligations.size(), + obligations)); + } else { + result.add( + String.format( + "BWO{%s %d, %d obligations}", + bwo.block.getType(), bwo.block.getUid(), obligations.size())); + } + } + return result.toString(); } - } - return result.toString(); } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java index b22bb3f86c4..dbe24bdb48d 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java @@ -4,25 +4,7 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCallAlias; @@ -60,6 +42,27 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + /** * This class implements the annotation inference algorithm for the Resource Leak Checker. It infers * annotations such as {@code @}{@link Owning} on owning fields and parameters, {@code @}{@link @@ -92,877 +95,905 @@ */ public class MustCallInference { - /** - * This map keeps the fields that have been inferred to be disposed within the current method. - * Keys represent inferred owning fields, and values contain the must-call method names (Note: - * currently this code assumes that the must-call set only contains one element). When inference - * finishes, all of the fields in this map will be given an @Owning annotation. Note that this map - * is not monotonically-increasing: fields may be added to this map and then removed during - * inference. For example, if a field's must-call method is called, it is added to this map. If, - * in a later statement in the same method, the same field is re-assigned, it will be removed from - * this map (since the field assignment invalidated the previously-inferred disposing of the - * obligation). - */ - private final Map disposedFields = new LinkedHashMap<>(); - - /** - * The owned fields. This includes: - * - *

            - *
          • fields with written {@code @Owning} annotations, and - *
          • the inferred owning fields in this analysis. - *
          - * - * This set is a superset of the key set in the {@code disposedFields} map. - */ - private final Set owningFields = new HashSet<>(); - - /** - * The type factory for the Resource Leak Checker, which is used to access the Must Call Checker. - */ - private final ResourceLeakAnnotatedTypeFactory resourceLeakAtf; - - /** The MustCallConsistencyAnalyzer. */ - private final MustCallConsistencyAnalyzer mcca; - - /** The {@link Owning} annotation. */ - protected final AnnotationMirror OWNING; - - /** The {@link NotOwning} annotation. */ - protected final AnnotationMirror NOTOWNING; - - /** The {@link MustCallAlias} annotation. */ - protected final AnnotationMirror MUSTCALLALIAS; - - /** - * The control flow graph of the current method. There is a separate MustCallInference for each - * method. - */ - private final ControlFlowGraph cfg; - - /** The MethodTree of the current method. */ - private final MethodTree methodTree; - - /** The element for the current method. */ - private final ExecutableElement methodElt; - - /** The tree for the enclosing class of the current method. */ - private final ClassTree classTree; - - /** - * The element for the enclosing class of the current method. It can be null for certain kinds of - * anonymous classes, such as PolyCollectorTypeVar.java in the all-systems test suite. - */ - private final @Nullable TypeElement classElt; - - /** - * This map is used to track must-alias relationships between the obligations that are - * resource-aliased to the return nodes and method parameters. The keys are the obligation of - * return nodes, and the values are the index of current method formal parameter (1-based) that is - * aliased with the return node. This map will be used to infer the {@link MustCallAlias} - * annotation for method parameters. - */ - private final Map returnObligationToParameter = new HashMap<>(); - - /** - * Creates a MustCallInference instance. - * - * @param resourceLeakAtf the type factory - * @param cfg the control flow graph of the method to check - * @param mcca the MustCallConsistencyAnalyzer - */ - /*package-private*/ MustCallInference( - ResourceLeakAnnotatedTypeFactory resourceLeakAtf, - ControlFlowGraph cfg, - MustCallConsistencyAnalyzer mcca) { - this.resourceLeakAtf = resourceLeakAtf; - this.mcca = mcca; - this.cfg = cfg; - this.OWNING = AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), Owning.class); - this.NOTOWNING = - AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), NotOwning.class); - this.MUSTCALLALIAS = - AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), MustCallAlias.class); - this.methodTree = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); - this.methodElt = TreeUtils.elementFromDeclaration(methodTree); - this.classTree = TreePathUtil.enclosingClass(resourceLeakAtf.getPath(methodTree)); - this.classElt = TreeUtils.elementFromDeclaration(classTree); - if (classElt != null) { - for (Element memberElt : classElt.getEnclosedElements()) { - if (memberElt.getKind().isField() && resourceLeakAtf.hasOwning(memberElt)) { - owningFields.add((VariableElement) memberElt); - } - } - } - } - - /** - * Creates a MustCallInference instance and runs the inference algorithm. This method is called by - * the {@link ResourceLeakAnnotatedTypeFactory#postAnalyze} method if Whole Program Inference is - * enabled. - * - * @param resourceLeakAtf the type factory - * @param cfg the control flow graph of the method to check - * @param mcca the MustCallConsistencyAnalyzer - */ - /*package-private*/ static void runMustCallInference( - ResourceLeakAnnotatedTypeFactory resourceLeakAtf, - ControlFlowGraph cfg, - MustCallConsistencyAnalyzer mcca) { - MustCallInference mustCallInferenceLogic = new MustCallInference(resourceLeakAtf, cfg, mcca); - mustCallInferenceLogic.runInference(); - } - - /** - * Runs the inference algorithm on the current method (the {@link #cfg} field). It discovers - * annotations such as {@code @}{@link Owning} on owning fields and parameters, {@code @}{@link - * NotOwning} on return types, {@code @}{@link EnsuresCalledMethods} on methods, {@code @}{@link - * MustCallAlias} on parameters and return types, and {@code @}{@link InheritableMustCall} on - * class declarations. - * - *

          Operationally, it checks method invocations for fields and parameters (with - * non-empty @MustCall obligations) along all paths to the regular exit point. - */ - private void runInference() { - - Set visited = new HashSet<>(); - Deque worklist = new ArrayDeque<>(); - - BlockWithObligations entry = - new BlockWithObligations(cfg.getEntryBlock(), getNonEmptyMCParams(cfg)); - worklist.add(entry); - visited.add(entry); - - while (!worklist.isEmpty()) { - BlockWithObligations current = worklist.remove(); - - // Use a LinkedHashSet for determinism. - Set obligations = new LinkedHashSet<>(current.obligations); - - for (Node node : current.block.getNodes()) { - // The obligation set calculated for RLC differs from the Inference process. In the - // Inference process, it exclusively tracks parameters with non-empty must-call - // types, whether they have the @Owning annotation or not. However, there are some - // shared computations, such as updateObligationsWithInvocationResult, which is used - // during inference and could potentially affect the RLC result if it were called - // before the checking phase. However, calling - // updateObligationsWithInvocationResult() will not have any side effects on the - // outcome of the Resource Leak Checker. This is because the inference occurs within - // the postAnalyze method of the ResourceLeakAnnotatedTypeFactory, once the - // consistency analyzer has completed its process. - if (node instanceof MethodInvocationNode || node instanceof ObjectCreationNode) { - mcca.updateObligationsWithInvocationResult(obligations, node); - inferOwningFromInvocation(obligations, node); - } else if (node instanceof AssignmentNode) { - analyzeAssignmentNode(obligations, (AssignmentNode) node); - } else if (node instanceof ReturnNode) { - analyzeReturnNode(obligations, (ReturnNode) node); - } - } - - addNonExceptionalSuccessorsToWorklist(obligations, current.block, visited, worklist); + /** + * This map keeps the fields that have been inferred to be disposed within the current method. + * Keys represent inferred owning fields, and values contain the must-call method names (Note: + * currently this code assumes that the must-call set only contains one element). When inference + * finishes, all of the fields in this map will be given an @Owning annotation. Note that this + * map is not monotonically-increasing: fields may be added to this map and then removed during + * inference. For example, if a field's must-call method is called, it is added to this map. If, + * in a later statement in the same method, the same field is re-assigned, it will be removed + * from this map (since the field assignment invalidated the previously-inferred disposing of + * the obligation). + */ + private final Map disposedFields = new LinkedHashMap<>(); + + /** + * The owned fields. This includes: + * + *

            + *
          • fields with written {@code @Owning} annotations, and + *
          • the inferred owning fields in this analysis. + *
          + * + * This set is a superset of the key set in the {@code disposedFields} map. + */ + private final Set owningFields = new HashSet<>(); + + /** + * The type factory for the Resource Leak Checker, which is used to access the Must Call + * Checker. + */ + private final ResourceLeakAnnotatedTypeFactory resourceLeakAtf; + + /** The MustCallConsistencyAnalyzer. */ + private final MustCallConsistencyAnalyzer mcca; + + /** The {@link Owning} annotation. */ + protected final AnnotationMirror OWNING; + + /** The {@link NotOwning} annotation. */ + protected final AnnotationMirror NOTOWNING; + + /** The {@link MustCallAlias} annotation. */ + protected final AnnotationMirror MUSTCALLALIAS; + + /** + * The control flow graph of the current method. There is a separate MustCallInference for each + * method. + */ + private final ControlFlowGraph cfg; + + /** The MethodTree of the current method. */ + private final MethodTree methodTree; + + /** The element for the current method. */ + private final ExecutableElement methodElt; + + /** The tree for the enclosing class of the current method. */ + private final ClassTree classTree; + + /** + * The element for the enclosing class of the current method. It can be null for certain kinds + * of anonymous classes, such as PolyCollectorTypeVar.java in the all-systems test suite. + */ + private final @Nullable TypeElement classElt; + + /** + * This map is used to track must-alias relationships between the obligations that are + * resource-aliased to the return nodes and method parameters. The keys are the obligation of + * return nodes, and the values are the index of current method formal parameter (1-based) that + * is aliased with the return node. This map will be used to infer the {@link MustCallAlias} + * annotation for method parameters. + */ + private final Map returnObligationToParameter = new HashMap<>(); + + /** + * Creates a MustCallInference instance. + * + * @param resourceLeakAtf the type factory + * @param cfg the control flow graph of the method to check + * @param mcca the MustCallConsistencyAnalyzer + */ + /*package-private*/ MustCallInference( + ResourceLeakAnnotatedTypeFactory resourceLeakAtf, + ControlFlowGraph cfg, + MustCallConsistencyAnalyzer mcca) { + this.resourceLeakAtf = resourceLeakAtf; + this.mcca = mcca; + this.cfg = cfg; + this.OWNING = + AnnotationBuilder.fromClass(this.resourceLeakAtf.getElementUtils(), Owning.class); + this.NOTOWNING = + AnnotationBuilder.fromClass( + this.resourceLeakAtf.getElementUtils(), NotOwning.class); + this.MUSTCALLALIAS = + AnnotationBuilder.fromClass( + this.resourceLeakAtf.getElementUtils(), MustCallAlias.class); + this.methodTree = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod(); + this.methodElt = TreeUtils.elementFromDeclaration(methodTree); + this.classTree = TreePathUtil.enclosingClass(resourceLeakAtf.getPath(methodTree)); + this.classElt = TreeUtils.elementFromDeclaration(classTree); + if (classElt != null) { + for (Element memberElt : classElt.getEnclosedElements()) { + if (memberElt.getKind().isField() && resourceLeakAtf.hasOwning(memberElt)) { + owningFields.add((VariableElement) memberElt); + } + } + } } - addMemberAndClassAnnotations(); - } - - /** - * Analyzes a return statement and performs two computations. Does nothing if the return - * expression's type has an empty must-call obligation. - * - *
            - *
          • If the returned expression is a field with non-empty must-call obligations, adds a {@link - * NotOwning} annotation to the return type of the current method. Note the implication: if - * a method returns a field of this class at any return site, the return type is - * inferred to be non-owning. - *
          • Compute the index of the parameter that is an alias of the return node and add it the - * {@link #returnObligationToParameter} map. - *
          - * - * @param obligations the current set of tracked Obligations - * @param node the return node - */ - private void analyzeReturnNode(Set obligations, ReturnNode node) { - Node returnNode = node.getResult(); - returnNode = mcca.removeCastsAndGetTmpVarIfPresent(returnNode); - if (resourceLeakAtf.hasEmptyMustCallValue(returnNode.getTree())) { - return; - } + /** + * Creates a MustCallInference instance and runs the inference algorithm. This method is called + * by the {@link ResourceLeakAnnotatedTypeFactory#postAnalyze} method if Whole Program Inference + * is enabled. + * + * @param resourceLeakAtf the type factory + * @param cfg the control flow graph of the method to check + * @param mcca the MustCallConsistencyAnalyzer + */ + /*package-private*/ static void runMustCallInference( + ResourceLeakAnnotatedTypeFactory resourceLeakAtf, + ControlFlowGraph cfg, + MustCallConsistencyAnalyzer mcca) { + MustCallInference mustCallInferenceLogic = + new MustCallInference(resourceLeakAtf, cfg, mcca); + mustCallInferenceLogic.runInference(); + } + + /** + * Runs the inference algorithm on the current method (the {@link #cfg} field). It discovers + * annotations such as {@code @}{@link Owning} on owning fields and parameters, {@code @}{@link + * NotOwning} on return types, {@code @}{@link EnsuresCalledMethods} on methods, {@code @}{@link + * MustCallAlias} on parameters and return types, and {@code @}{@link InheritableMustCall} on + * class declarations. + * + *

          Operationally, it checks method invocations for fields and parameters (with + * non-empty @MustCall obligations) along all paths to the regular exit point. + */ + private void runInference() { + + Set visited = new HashSet<>(); + Deque worklist = new ArrayDeque<>(); + + BlockWithObligations entry = + new BlockWithObligations(cfg.getEntryBlock(), getNonEmptyMCParams(cfg)); + worklist.add(entry); + visited.add(entry); + + while (!worklist.isEmpty()) { + BlockWithObligations current = worklist.remove(); + + // Use a LinkedHashSet for determinism. + Set obligations = new LinkedHashSet<>(current.obligations); + + for (Node node : current.block.getNodes()) { + // The obligation set calculated for RLC differs from the Inference process. In the + // Inference process, it exclusively tracks parameters with non-empty must-call + // types, whether they have the @Owning annotation or not. However, there are some + // shared computations, such as updateObligationsWithInvocationResult, which is used + // during inference and could potentially affect the RLC result if it were called + // before the checking phase. However, calling + // updateObligationsWithInvocationResult() will not have any side effects on the + // outcome of the Resource Leak Checker. This is because the inference occurs within + // the postAnalyze method of the ResourceLeakAnnotatedTypeFactory, once the + // consistency analyzer has completed its process. + if (node instanceof MethodInvocationNode || node instanceof ObjectCreationNode) { + mcca.updateObligationsWithInvocationResult(obligations, node); + inferOwningFromInvocation(obligations, node); + } else if (node instanceof AssignmentNode) { + analyzeAssignmentNode(obligations, (AssignmentNode) node); + } else if (node instanceof ReturnNode) { + analyzeReturnNode(obligations, (ReturnNode) node); + } + } + + addNonExceptionalSuccessorsToWorklist(obligations, current.block, visited, worklist); + } - if (returnNode instanceof FieldAccessNode) { - addNotOwningToMethodDecl(); - } else if (returnNode instanceof LocalVariableNode) { - Obligation returnNodeObligation = - MustCallConsistencyAnalyzer.getObligationForVar( - obligations, (LocalVariableNode) returnNode); - if (returnNodeObligation != null) { - returnObligationToParameter.put( - returnNodeObligation, getIndexOfParam(returnNodeObligation)); - } - } - } - - /** - * Adds inferred {@literal @Owning} annotations to fields, {@literal @EnsuresCalledMethods} - * annotations to the current method, {@literal @MustCallAlias} annotations to the parameter, and - * {@literal @InheritableMustCall} annotations to the enclosing class. - */ - private void addMemberAndClassAnnotations() { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - assert wpi != null : "MustCallInference is running without WPI."; - for (VariableElement fieldElt : getOwningFields()) { - wpi.addFieldDeclarationAnnotation(fieldElt, OWNING); - } - if (!disposedFields.isEmpty()) { - addEnsuresCalledMethodsForDisposedFields(); - } + addMemberAndClassAnnotations(); + } + + /** + * Analyzes a return statement and performs two computations. Does nothing if the return + * expression's type has an empty must-call obligation. + * + *

            + *
          • If the returned expression is a field with non-empty must-call obligations, adds a + * {@link NotOwning} annotation to the return type of the current method. Note the + * implication: if a method returns a field of this class at any return site, the + * return type is inferred to be non-owning. + *
          • Compute the index of the parameter that is an alias of the return node and add it the + * {@link #returnObligationToParameter} map. + *
          + * + * @param obligations the current set of tracked Obligations + * @param node the return node + */ + private void analyzeReturnNode(Set obligations, ReturnNode node) { + Node returnNode = node.getResult(); + returnNode = mcca.removeCastsAndGetTmpVarIfPresent(returnNode); + if (resourceLeakAtf.hasEmptyMustCallValue(returnNode.getTree())) { + return; + } - // If all return statements alias the same parameter index, then add the @MustCallAlias - // annotation to that parameter and the return type. - if (!returnObligationToParameter.isEmpty()) { - if (returnObligationToParameter.values().stream().distinct().count() == 1) { - int indexOfParam = returnObligationToParameter.values().iterator().next(); - if (indexOfParam > 0) { - addMustCallAliasToFormalParameter(indexOfParam); + if (returnNode instanceof FieldAccessNode) { + addNotOwningToMethodDecl(); + } else if (returnNode instanceof LocalVariableNode) { + Obligation returnNodeObligation = + MustCallConsistencyAnalyzer.getObligationForVar( + obligations, (LocalVariableNode) returnNode); + if (returnNodeObligation != null) { + returnObligationToParameter.put( + returnNodeObligation, getIndexOfParam(returnNodeObligation)); + } } - } } - addInheritableMustCallToClass(); - } - - /** - * Returns a set of obligations representing the formal parameters of the current method that have - * non-empty MustCall annotations. Returns an empty set if the given CFG doesn't correspond to a - * method body. - * - * @param cfg the control flow graph of the method to check - * @return a set of obligations representing the parameters with non-empty MustCall obligations - */ - private Set getNonEmptyMCParams(ControlFlowGraph cfg) { - // TODO what about lambdas? - if (cfg.getUnderlyingAST().getKind() != UnderlyingAST.Kind.METHOD) { - return Collections.emptySet(); - } - Set result = null; - for (VariableTree param : methodTree.getParameters()) { - if (resourceLeakAtf.declaredTypeHasMustCall(param)) { - VariableElement paramElement = TreeUtils.elementFromDeclaration(param); - if (result == null) { - result = new HashSet<>(2); - } - result.add( - new Obligation( - ImmutableSet.of( - new ResourceAlias(new LocalVariable(paramElement), paramElement, param)), - Collections.singleton(MethodExitKind.NORMAL_RETURN))); - } - } - return result != null ? result : Collections.emptySet(); - } - - /** - * Retrieves the owning fields, including fields inferred as owning from the current iteration. - * - * @return the owning fields - */ - private Set getOwningFields() { - return owningFields; - } - - /** - * Adds an owning annotation to the formal parameter at the given index. - * - * @param index the index of a formal parameter of the current method (1-based) - */ - private void addOwningToParam(int index) { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, OWNING); - } - - /** - * This method checks if a field is an owning candidate. A field is an owning candidate if it has - * a non-empty must-call obligation, unless it is {code @MustCallUnknown}. For a - * {code @MustCallUnknown} field, we don't want to infer anything. So, we conservatively treat it - * as a non-owning candidate. - * - * @param resourceLeakAtf the type factory - * @param field the field to check - * @return true if the field is an owning candidate, false otherwise - */ - private boolean isFieldOwningCandidate( - ResourceLeakAnnotatedTypeFactory resourceLeakAtf, Element field) { - AnnotationMirror mustCallAnnotation = resourceLeakAtf.getMustCallAnnotation(field); - if (mustCallAnnotation == null) { - // Indicates @MustCallUnknown. We want to conservatively avoid inferring an @Owning - // annotation for @MustCallUnknown. - return false; - } - // Otherwise, the field is an @Owning candidate if it has a non-empty @MustCall obligation - return !resourceLeakAtf.getMustCallValues(mustCallAnnotation).isEmpty(); - } - - /** - * Adds the node to the disposedFields map and the owningFields set if it is a field and its - * must-call obligation is satisfied by the given method call. If so, it will be given an @Owning - * annotation later. - * - * @param node possibly an owning field - * @param invocation method invoked on the possible owning field - */ - private void inferOwningField(Node node, MethodInvocationNode invocation) { - Element nodeElt = TreeUtils.elementFromTree(node.getTree()); - if (nodeElt == null || !nodeElt.getKind().isField()) { - return; - } - if (isFieldOwningCandidate(resourceLeakAtf, nodeElt)) { - node = NodeUtils.removeCasts(node); - JavaExpression nodeJe = JavaExpression.fromNode(node); - AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, nodeJe); - List mustCallValues = resourceLeakAtf.getMustCallValues(nodeElt); - if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { - assert !mustCallValues.isEmpty() - : "Must-call obligation of owning field " + nodeElt + " is empty."; - // TODO: generalize this to MustCall annotations with more than one element. - // Currently, this code assumes that the must-call set has only one element. - assert mustCallValues.size() == 1 - : "The must-call set of " + nodeElt + "should be a singleton: " + mustCallValues; - disposedFields.put((VariableElement) nodeElt, mustCallValues.get(0)); - owningFields.add((VariableElement) nodeElt); - } - } - } - - /** - * Analyzes an assignment statement and performs the following computations. Does nothing if the - * rhs is not a local variable or parameter, or if the rhs has no must-call obligation. - * - *
            - *
          • 1) If the current method under analysis is a constructor, the left-hand side of the - * assignment is the only owning field of the enclosing class, and the rhs is an alias of a - * formal parameter, it adds an {@code @MustCallAlias} annotation to the formal parameter - * and the result type of the constructor. - *
          • 2) If the left-hand side of the assignment is an owning field, and the rhs is an alias of - * a formal parameter, it adds an {@code @Owning} annotation to the formal parameter. - *
          • 3) Otherwise, updates the set of tracked obligations to account for the - * (pseudo-)assignment, as in a gen-kill dataflow analysis problem. - *
          - * - * @param obligations the set of obligations to update - * @param assignmentNode the assignment statement - */ - private void analyzeAssignmentNode(Set obligations, AssignmentNode assignmentNode) { - Node lhs = assignmentNode.getTarget(); - Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); - // Use the temporary variable for the rhs if it exists. - Node rhs = mcca.removeCastsAndGetTmpVarIfPresent(assignmentNode.getExpression()); - - if (!(rhs instanceof LocalVariableNode)) { - return; - } - Obligation rhsObligation = - MustCallConsistencyAnalyzer.getObligationForVar(obligations, (LocalVariableNode) rhs); - if (rhsObligation == null) { - return; - } + /** + * Adds inferred {@literal @Owning} annotations to fields, {@literal @EnsuresCalledMethods} + * annotations to the current method, {@literal @MustCallAlias} annotations to the parameter, + * and {@literal @InheritableMustCall} annotations to the enclosing class. + */ + private void addMemberAndClassAnnotations() { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + assert wpi != null : "MustCallInference is running without WPI."; + for (VariableElement fieldElt : getOwningFields()) { + wpi.addFieldDeclarationAnnotation(fieldElt, OWNING); + } + if (!disposedFields.isEmpty()) { + addEnsuresCalledMethodsForDisposedFields(); + } - if (lhsElement.getKind() == ElementKind.FIELD) { - if (!getOwningFields().contains(lhsElement)) { - return; - } - - // If the owning field is present in the disposedFields map and there is an assignment - // to the field, it must be removed from the set. This is essential since the - // disposedFields map is used for adding @EnsuresCalledMethods annotations to the - // current method later. Note that this removal doesn't affect the owning annotation - // we inferred for the field, as the owningField set is updated with the inferred - // owning field in the 'inferOwningField' method. - if (!TreeUtils.isConstructor(methodTree)) { - disposedFields.remove((VariableElement) lhsElement); - } - - int paramIndex = getIndexOfParam(rhsObligation); - if (paramIndex == -1) { - // We are only tracking formal parameter aliases. If the rhsObligation is not an - // alias of any of the formal parameters, it won't be present in the obligations - // set. Thus, skipping the rest of this method is fine. - return; - } - - if (TreeUtils.isConstructor(methodTree) && getOwningFields().size() == 1) { - // case 1 is satisfied. - addMustCallAliasToFormalParameter(paramIndex); - mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); - } else { - // case 2 is satisfied. - addOwningToParam(paramIndex); - mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); - } - - } else if (lhs instanceof LocalVariableNode) { - // Updates the set of tracked obligations. (case 4) - LocalVariableNode lhsVar = (LocalVariableNode) lhs; - mcca.updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); - } - } - - /** - * Return the (1-based) index of the method parameter that exist in the set of aliases of the - * given {@code obligation}, if one exists; otherwise, return -1. - * - * @param obligation the obligation - * @return the index of the current method parameter that exist in the set of aliases of the given - * obligation, if one exists; otherwise, return -1. - */ - private int getIndexOfParam(Obligation obligation) { - Set resourceAliases = obligation.resourceAliases; - List paramElts = - CollectionsPlume.mapList(TreeUtils::elementFromDeclaration, methodTree.getParameters()); - for (ResourceAlias resourceAlias : resourceAliases) { - int paramIndex = paramElts.indexOf(resourceAlias.element); - if (paramIndex != -1) { - return paramIndex + 1; - } - } + // If all return statements alias the same parameter index, then add the @MustCallAlias + // annotation to that parameter and the return type. + if (!returnObligationToParameter.isEmpty()) { + if (returnObligationToParameter.values().stream().distinct().count() == 1) { + int indexOfParam = returnObligationToParameter.values().iterator().next(); + if (indexOfParam > 0) { + addMustCallAliasToFormalParameter(indexOfParam); + } + } + } - return -1; - } - - /** Adds a {@link NotOwning} annotation to the current method. */ - private void addNotOwningToMethodDecl() { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addMethodDeclarationAnnotation(methodElt, NOTOWNING); - } - - /** - * Adds a {@link MustCallAlias} annotation to the formal parameter at the given index. - * - * @param index the index of a formal parameter of the current method (1-based) - */ - private void addMustCallAliasToFormalParameter(int index) { - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addMethodDeclarationAnnotation(methodElt, MUSTCALLALIAS); - wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, MUSTCALLALIAS); - } - - /** - * Adds an {@link EnsuresCalledMethods} annotation to the current method for any owning field - * whose must-call obligation is satisfied within the current method, i.e., the fields in {@link - * #disposedFields}. - */ - private void addEnsuresCalledMethodsForDisposedFields() { - // The keys are the must-call method names, and the values are the set of fields on which - // those methods are called. This map is used to create a @EnsuresCalledMethods annotation - // for each set of fields that share the same must-call obligation. - Map> methodToFields = new LinkedHashMap<>(); - for (VariableElement disposedField : disposedFields.keySet()) { - String mustCallValue = disposedFields.get(disposedField); - String fieldName = "this." + disposedField.getSimpleName().toString(); - methodToFields.computeIfAbsent(mustCallValue, k -> new LinkedHashSet<>()).add(fieldName); + addInheritableMustCallToClass(); + } + + /** + * Returns a set of obligations representing the formal parameters of the current method that + * have non-empty MustCall annotations. Returns an empty set if the given CFG doesn't correspond + * to a method body. + * + * @param cfg the control flow graph of the method to check + * @return a set of obligations representing the parameters with non-empty MustCall obligations + */ + private Set getNonEmptyMCParams(ControlFlowGraph cfg) { + // TODO what about lambdas? + if (cfg.getUnderlyingAST().getKind() != UnderlyingAST.Kind.METHOD) { + return Collections.emptySet(); + } + Set result = null; + for (VariableTree param : methodTree.getParameters()) { + if (resourceLeakAtf.declaredTypeHasMustCall(param)) { + VariableElement paramElement = TreeUtils.elementFromDeclaration(param); + if (result == null) { + result = new HashSet<>(2); + } + result.add( + new Obligation( + ImmutableSet.of( + new ResourceAlias( + new LocalVariable(paramElement), + paramElement, + param)), + Collections.singleton(MethodExitKind.NORMAL_RETURN))); + } + } + return result != null ? result : Collections.emptySet(); + } + + /** + * Retrieves the owning fields, including fields inferred as owning from the current iteration. + * + * @return the owning fields + */ + private Set getOwningFields() { + return owningFields; + } + + /** + * Adds an owning annotation to the formal parameter at the given index. + * + * @param index the index of a formal parameter of the current method (1-based) + */ + private void addOwningToParam(int index) { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, OWNING); + } + + /** + * This method checks if a field is an owning candidate. A field is an owning candidate if it + * has a non-empty must-call obligation, unless it is {code @MustCallUnknown}. For a + * {code @MustCallUnknown} field, we don't want to infer anything. So, we conservatively treat + * it as a non-owning candidate. + * + * @param resourceLeakAtf the type factory + * @param field the field to check + * @return true if the field is an owning candidate, false otherwise + */ + private boolean isFieldOwningCandidate( + ResourceLeakAnnotatedTypeFactory resourceLeakAtf, Element field) { + AnnotationMirror mustCallAnnotation = resourceLeakAtf.getMustCallAnnotation(field); + if (mustCallAnnotation == null) { + // Indicates @MustCallUnknown. We want to conservatively avoid inferring an @Owning + // annotation for @MustCallUnknown. + return false; + } + // Otherwise, the field is an @Owning candidate if it has a non-empty @MustCall obligation + return !resourceLeakAtf.getMustCallValues(mustCallAnnotation).isEmpty(); + } + + /** + * Adds the node to the disposedFields map and the owningFields set if it is a field and its + * must-call obligation is satisfied by the given method call. If so, it will be given + * an @Owning annotation later. + * + * @param node possibly an owning field + * @param invocation method invoked on the possible owning field + */ + private void inferOwningField(Node node, MethodInvocationNode invocation) { + Element nodeElt = TreeUtils.elementFromTree(node.getTree()); + if (nodeElt == null || !nodeElt.getKind().isField()) { + return; + } + if (isFieldOwningCandidate(resourceLeakAtf, nodeElt)) { + node = NodeUtils.removeCasts(node); + JavaExpression nodeJe = JavaExpression.fromNode(node); + AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, nodeJe); + List mustCallValues = resourceLeakAtf.getMustCallValues(nodeElt); + if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { + assert !mustCallValues.isEmpty() + : "Must-call obligation of owning field " + nodeElt + " is empty."; + // TODO: generalize this to MustCall annotations with more than one element. + // Currently, this code assumes that the must-call set has only one element. + assert mustCallValues.size() == 1 + : "The must-call set of " + + nodeElt + + "should be a singleton: " + + mustCallValues; + disposedFields.put((VariableElement) nodeElt, mustCallValues.get(0)); + owningFields.add((VariableElement) nodeElt); + } + } } - for (String mustCallValue : methodToFields.keySet()) { - Set fields = methodToFields.get(mustCallValue); - AnnotationMirror am = - createEnsuresCalledMethods(fields.toArray(new String[0]), new String[] {mustCallValue}); - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - wpi.addMethodDeclarationAnnotation(methodElt, am); - } - } - - /** - * Possibly adds an InheritableMustCall annotation on the enclosing class. - * - *

          Let the enclosing class be C. If C already has a non-empty MustCall type (that is written or - * inherited from one of its superclasses), this method preserves the exising must-call type to - * avoid infinite iteration. Otherwise, if the current method is not private and satisfies the - * must-call obligations of all the owning fields in C, it adds an InheritableMustCall annotation - * to C. - */ - private void addInheritableMustCallToClass() { - if (classElt == null) { - return; - } + /** + * Analyzes an assignment statement and performs the following computations. Does nothing if the + * rhs is not a local variable or parameter, or if the rhs has no must-call obligation. + * + *

            + *
          • 1) If the current method under analysis is a constructor, the left-hand side of the + * assignment is the only owning field of the enclosing class, and the rhs is an alias of + * a formal parameter, it adds an {@code @MustCallAlias} annotation to the formal + * parameter and the result type of the constructor. + *
          • 2) If the left-hand side of the assignment is an owning field, and the rhs is an alias + * of a formal parameter, it adds an {@code @Owning} annotation to the formal parameter. + *
          • 3) Otherwise, updates the set of tracked obligations to account for the + * (pseudo-)assignment, as in a gen-kill dataflow analysis problem. + *
          + * + * @param obligations the set of obligations to update + * @param assignmentNode the assignment statement + */ + private void analyzeAssignmentNode(Set obligations, AssignmentNode assignmentNode) { + Node lhs = assignmentNode.getTarget(); + Element lhsElement = TreeUtils.elementFromTree(lhs.getTree()); + // Use the temporary variable for the rhs if it exists. + Node rhs = mcca.removeCastsAndGetTmpVarIfPresent(assignmentNode.getExpression()); + + if (!(rhs instanceof LocalVariableNode)) { + return; + } + Obligation rhsObligation = + MustCallConsistencyAnalyzer.getObligationForVar( + obligations, (LocalVariableNode) rhs); + if (rhsObligation == null) { + return; + } - WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); - - List currentMustCallValues = resourceLeakAtf.getMustCallValues(classElt); - if (!currentMustCallValues.isEmpty()) { - // The class already has a MustCall annotation. - - // If it is inherited from a superclass, do nothing. - if (classElt.getSuperclass() != null) { - TypeMirror superType = classElt.getSuperclass(); - TypeElement superClassElt = TypesUtils.getTypeElement(superType); - if (superClassElt != null && !resourceLeakAtf.getMustCallValues(superClassElt).isEmpty()) { - return; - } - } - - // If the enclosing class already has a non-empty @MustCall type, either added by - // programmers or inferred in previous iterations (not-inherited), we do not change it - // in the current analysis round to prevent potential inconsistencies and guarantee - // the termination of the inference algorithm. This becomes particularly important - // when multiple methods could satisfy the must-call obligation of the enclosing - // class. To ensure the existing @MustCall annotation is included in the inference - // result for this iteration, we re-add it. - assert currentMustCallValues.size() == 1 : "TODO: Handle multiple must-call values"; - AnnotationMirror am = createInheritableMustCall(new String[] {currentMustCallValues.get(0)}); - wpi.addClassDeclarationAnnotation(classElt, am); - return; + if (lhsElement.getKind() == ElementKind.FIELD) { + if (!getOwningFields().contains(lhsElement)) { + return; + } + + // If the owning field is present in the disposedFields map and there is an assignment + // to the field, it must be removed from the set. This is essential since the + // disposedFields map is used for adding @EnsuresCalledMethods annotations to the + // current method later. Note that this removal doesn't affect the owning annotation + // we inferred for the field, as the owningField set is updated with the inferred + // owning field in the 'inferOwningField' method. + if (!TreeUtils.isConstructor(methodTree)) { + disposedFields.remove((VariableElement) lhsElement); + } + + int paramIndex = getIndexOfParam(rhsObligation); + if (paramIndex == -1) { + // We are only tracking formal parameter aliases. If the rhsObligation is not an + // alias of any of the formal parameters, it won't be present in the obligations + // set. Thus, skipping the rest of this method is fine. + return; + } + + if (TreeUtils.isConstructor(methodTree) && getOwningFields().size() == 1) { + // case 1 is satisfied. + addMustCallAliasToFormalParameter(paramIndex); + mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); + } else { + // case 2 is satisfied. + addOwningToParam(paramIndex); + mcca.removeObligationsContainingVar(obligations, (LocalVariableNode) rhs); + } + + } else if (lhs instanceof LocalVariableNode) { + // Updates the set of tracked obligations. (case 4) + LocalVariableNode lhsVar = (LocalVariableNode) lhs; + mcca.updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs); + } } - // If the current method is not private and satisfies the must-call obligation of all owning - // fields, then add (to the class) an InheritableMustCall annotation with the name of this - // method. - if (!methodTree.getModifiers().getFlags().contains(Modifier.PRIVATE)) { - // Since the result of getOwningFields() is a superset of the key set in - // disposedFields map, it is sufficient to check the equality of their sizes to - // determine if both sets are equal. - if (!disposedFields.isEmpty() && disposedFields.size() == getOwningFields().size()) { - AnnotationMirror am = - createInheritableMustCall(new String[] {methodTree.getName().toString()}); - wpi.addClassDeclarationAnnotation(classElt, am); - } - } - } - - /** - * Infers ownership transfer at the method call to infer @owning annotations for formal parameters - * of the current method, if the parameter is passed into the call and the corresponding formal - * parameter of the callee is @owning. - * - * @param obligations the current set of tracked Obligations - * @param invocation the method or constructor invocation - */ - private void inferOwningParamsViaOwnershipTransfer(Set obligations, Node invocation) { - List paramsOfCurrentMethod = methodTree.getParameters(); - if (paramsOfCurrentMethod.isEmpty()) { - return; - } - List calleeParams = mcca.getParametersOfInvocation(invocation); - if (calleeParams.isEmpty()) { - return; - } - List arguments = mcca.getArgumentsOfInvocation(invocation); + /** + * Return the (1-based) index of the method parameter that exist in the set of aliases of the + * given {@code obligation}, if one exists; otherwise, return -1. + * + * @param obligation the obligation + * @return the index of the current method parameter that exist in the set of aliases of the + * given obligation, if one exists; otherwise, return -1. + */ + private int getIndexOfParam(Obligation obligation) { + Set resourceAliases = obligation.resourceAliases; + List paramElts = + CollectionsPlume.mapList( + TreeUtils::elementFromDeclaration, methodTree.getParameters()); + for (ResourceAlias resourceAlias : resourceAliases) { + int paramIndex = paramElts.indexOf(resourceAlias.element); + if (paramIndex != -1) { + return paramIndex + 1; + } + } - for (int i = 0; i < arguments.size(); i++) { - if (!resourceLeakAtf.hasOwning(calleeParams.get(i))) { - continue; - } - for (int j = 0; j < paramsOfCurrentMethod.size(); j++) { - VariableTree paramOfCurrMethod = paramsOfCurrentMethod.get(j); - if (resourceLeakAtf.hasEmptyMustCallValue(paramOfCurrMethod)) { - continue; + return -1; + } + + /** Adds a {@link NotOwning} annotation to the current method. */ + private void addNotOwningToMethodDecl() { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addMethodDeclarationAnnotation(methodElt, NOTOWNING); + } + + /** + * Adds a {@link MustCallAlias} annotation to the formal parameter at the given index. + * + * @param index the index of a formal parameter of the current method (1-based) + */ + private void addMustCallAliasToFormalParameter(int index) { + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addMethodDeclarationAnnotation(methodElt, MUSTCALLALIAS); + wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, MUSTCALLALIAS); + } + + /** + * Adds an {@link EnsuresCalledMethods} annotation to the current method for any owning field + * whose must-call obligation is satisfied within the current method, i.e., the fields in {@link + * #disposedFields}. + */ + private void addEnsuresCalledMethodsForDisposedFields() { + // The keys are the must-call method names, and the values are the set of fields on which + // those methods are called. This map is used to create a @EnsuresCalledMethods annotation + // for each set of fields that share the same must-call obligation. + Map> methodToFields = new LinkedHashMap<>(); + for (VariableElement disposedField : disposedFields.keySet()) { + String mustCallValue = disposedFields.get(disposedField); + String fieldName = "this." + disposedField.getSimpleName().toString(); + methodToFields + .computeIfAbsent(mustCallValue, k -> new LinkedHashSet<>()) + .add(fieldName); } - Node arg = NodeUtils.removeCasts(arguments.get(i)); - VariableElement paramElt = TreeUtils.elementFromDeclaration(paramOfCurrMethod); - if (nodeAndElementResourceAliased(obligations, arg, paramElt)) { - addOwningToParam(j + 1); - break; + for (String mustCallValue : methodToFields.keySet()) { + Set fields = methodToFields.get(mustCallValue); + AnnotationMirror am = + createEnsuresCalledMethods( + fields.toArray(new String[0]), new String[] {mustCallValue}); + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + wpi.addMethodDeclarationAnnotation(methodElt, am); } - } - } - } - - /** - * Checks whether the given element is a resource alias of the given node in the provided set of - * obligations. - * - * @param obligations the current set of tracked Obligations - * @param node the node - * @param element the element - * @return true if {@code element} is a resource alias of {@code node} - */ - private boolean nodeAndElementResourceAliased( - Set obligations, Node node, VariableElement element) { - Set nodeAliases = getResourceAliasOfNode(obligations, node); - for (ResourceAlias nodeAlias : nodeAliases) { - Element nodeAliasElt = nodeAlias.element; - if (nodeAliasElt.equals(element)) { - return true; - } - } - return false; - } - - /** - * Infers @Owning annotations for fields or the parameters of the current method that are passed - * in the receiver or arguments position of a call if their must-call obligation is satisfied via - * the {@code invocation}. - * - * @param obligations the current set of tracked Obligations - * @param invocation a method invocation node to check - */ - private void inferOwningForRecieverOrFormalParamPassedToCall( - Set obligations, MethodInvocationNode invocation) { - Node receiver = invocation.getTarget().getReceiver(); - receiver = NodeUtils.removeCasts(receiver); - if (receiver.getTree() != null) { - inferOwningForParamOrField(obligations, invocation, receiver); } - for (Node argument : mcca.getArgumentsOfInvocation(invocation)) { - Node arg = mcca.removeCastsAndGetTmpVarIfPresent(argument); - // In the CFG, explicit passing of multiple arguments in the varargs position is - // represented via an ArrayCreationNode. In this case, it checks the called methods - // set of each argument passed in this position. - if (arg instanceof ArrayCreationNode) { - ArrayCreationNode varArgsNode = (ArrayCreationNode) arg; - for (Node varArgNode : varArgsNode.getInitializers()) { - inferOwningForParamOrField(obligations, invocation, varArgNode); - } - } else { - inferOwningForParamOrField(obligations, invocation, arg); - } - } - } - - /** - * Infers an @Owning annotation for the {@code arg} that can be a receiver or an argument passed - * into a method call if the must-call obligation of the {@code arg} is satisfied via the {@code - * invocation}. - * - * @param obligations the current set of tracked Obligations - * @param invocation the method invocation node to check - * @param arg a receiver or an argument passed to the method call - */ - private void inferOwningForParamOrField( - Set obligations, MethodInvocationNode invocation, Node arg) { - Element argElt = TreeUtils.elementFromTree(arg.getTree()); - // The must-call obligation of a field can be satisfied either through a call where it - // serves as a receiver or within the callee method when it is passed as an argument. - if (argElt != null && argElt.getKind().isField()) { - inferOwningField(arg, invocation); - return; - } + /** + * Possibly adds an InheritableMustCall annotation on the enclosing class. + * + *

          Let the enclosing class be C. If C already has a non-empty MustCall type (that is written + * or inherited from one of its superclasses), this method preserves the exising must-call type + * to avoid infinite iteration. Otherwise, if the current method is not private and satisfies + * the must-call obligations of all the owning fields in C, it adds an InheritableMustCall + * annotation to C. + */ + private void addInheritableMustCallToClass() { + if (classElt == null) { + return; + } - List paramsOfCurrentMethod = methodTree.getParameters(); - outerLoop: - for (int i = 0; i < paramsOfCurrentMethod.size(); i++) { - VariableTree currentMethodParamTree = paramsOfCurrentMethod.get(i); - if (resourceLeakAtf.hasEmptyMustCallValue(currentMethodParamTree)) { - continue; - } - - VariableElement paramElt = TreeUtils.elementFromDeclaration(currentMethodParamTree); - if (!nodeAndElementResourceAliased(obligations, arg, paramElt)) { - continue; - } - - List mustCallValues = resourceLeakAtf.getMustCallValues(paramElt); - assert mustCallValues.size() <= 1 : "TODO: Handle larger must-call values sets"; - Set nodeAliases = getResourceAliasOfNode(obligations, arg); - for (ResourceAlias resourceAlias : nodeAliases) { - AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, resourceAlias.reference); - if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { - addOwningToParam(i + 1); - break outerLoop; - } - } - } - } - - /** - * Returns the set of resource aliases associated with the given node, by looking up the - * corresponding obligation in the given set of obligations. - * - * @param obligations the set of obligations to search in - * @param node the node whose resource aliases are to be returned - * @return the resource aliases associated with the given node, or an empty set if the node has - * none - */ - private Set getResourceAliasOfNode(Set obligations, Node node) { - Node tempVar = mcca.getTempVarOrNode(node); - if (!(tempVar instanceof LocalVariableNode)) { - return Collections.emptySet(); - } + WholeProgramInference wpi = resourceLeakAtf.getWholeProgramInference(); + + List currentMustCallValues = resourceLeakAtf.getMustCallValues(classElt); + if (!currentMustCallValues.isEmpty()) { + // The class already has a MustCall annotation. + + // If it is inherited from a superclass, do nothing. + if (classElt.getSuperclass() != null) { + TypeMirror superType = classElt.getSuperclass(); + TypeElement superClassElt = TypesUtils.getTypeElement(superType); + if (superClassElt != null + && !resourceLeakAtf.getMustCallValues(superClassElt).isEmpty()) { + return; + } + } + + // If the enclosing class already has a non-empty @MustCall type, either added by + // programmers or inferred in previous iterations (not-inherited), we do not change it + // in the current analysis round to prevent potential inconsistencies and guarantee + // the termination of the inference algorithm. This becomes particularly important + // when multiple methods could satisfy the must-call obligation of the enclosing + // class. To ensure the existing @MustCall annotation is included in the inference + // result for this iteration, we re-add it. + assert currentMustCallValues.size() == 1 : "TODO: Handle multiple must-call values"; + AnnotationMirror am = + createInheritableMustCall(new String[] {currentMustCallValues.get(0)}); + wpi.addClassDeclarationAnnotation(classElt, am); + return; + } - Obligation argumentObligation = - MustCallConsistencyAnalyzer.getObligationForVar(obligations, (LocalVariableNode) tempVar); - if (argumentObligation == null) { - return Collections.emptySet(); - } - return argumentObligation.resourceAliases; - } - - /** - * Infers @Owning or @MustCallAlias annotations for formal parameters of the enclosing method - * and @Owning annotations for fields of the enclosing class, as follows: - * - *

            - *
          • If a formal parameter is passed as an owning parameter, add an @Owning annotation to that - * formal parameter (see {@link #inferOwningParamsViaOwnershipTransfer}). - *
          • It calls {@link #inferOwningForRecieverOrFormalParamPassedToCall} to infer @Owning - * annotations for the receiver or arguments of a call by analyzing the called-methods set - * after the call. - *
          • It calls {@link #inferMustCallAliasFromThisOrSuperCall} to infer @MustCallAlias - * annotation for formal parameters and the result of the constructor. - *
          - * - * @param obligations the set of obligations to search in - * @param invocation the method or constructor invocation - */ - private void inferOwningFromInvocation(Set obligations, Node invocation) { - if (invocation instanceof ObjectCreationNode) { - // If the invocation corresponds to an object creation node, only ownership transfer - // checking is required, as constructor parameters may have an @Owning annotation. We - // do not handle @EnsuresCalledMethods annotations on constructors as we have not - // observed them in practice. - inferOwningParamsViaOwnershipTransfer(obligations, invocation); - } else if (invocation instanceof MethodInvocationNode) { - inferMustCallAliasFromThisOrSuperCall(obligations, (MethodInvocationNode) invocation); - inferOwningParamsViaOwnershipTransfer(obligations, invocation); - inferOwningForRecieverOrFormalParamPassedToCall( - obligations, (MethodInvocationNode) invocation); - } - } - - /** - * Adds the @MustCallAlias annotation to a method parameter when it is passed in a @MustCallAlias - * position during a constructor call using {@literal this} or {@literal super}. - * - * @param obligations the current set of tracked Obligations - * @param node a method invocation node - */ - private void inferMustCallAliasFromThisOrSuperCall( - Set obligations, MethodInvocationNode node) { - if (!TreeUtils.isSuperConstructorCall(node.getTree()) - && !TreeUtils.isThisConstructorCall(node.getTree())) { - return; + // If the current method is not private and satisfies the must-call obligation of all owning + // fields, then add (to the class) an InheritableMustCall annotation with the name of this + // method. + if (!methodTree.getModifiers().getFlags().contains(Modifier.PRIVATE)) { + // Since the result of getOwningFields() is a superset of the key set in + // disposedFields map, it is sufficient to check the equality of their sizes to + // determine if both sets are equal. + if (!disposedFields.isEmpty() && disposedFields.size() == getOwningFields().size()) { + AnnotationMirror am = + createInheritableMustCall(new String[] {methodTree.getName().toString()}); + wpi.addClassDeclarationAnnotation(classElt, am); + } + } } - List calleeParams = mcca.getParametersOfInvocation(node); - List arguments = mcca.getArgumentsOfInvocation(node); - for (int i = 0; i < arguments.size(); i++) { - if (!resourceLeakAtf.hasMustCallAlias(calleeParams.get(i))) { - continue; - } - - Node arg = mcca.removeCastsAndGetTmpVarIfPresent(arguments.get(i)); - Obligation argObligation = - MustCallConsistencyAnalyzer.getObligationForVar(obligations, (LocalVariableNode) arg); - if (argObligation == null) { - return; - } - int index = getIndexOfParam(argObligation); - if (index != -1) { - addMustCallAliasToFormalParameter(index); - break; - } + + /** + * Infers ownership transfer at the method call to infer @owning annotations for formal + * parameters of the current method, if the parameter is passed into the call and the + * corresponding formal parameter of the callee is @owning. + * + * @param obligations the current set of tracked Obligations + * @param invocation the method or constructor invocation + */ + private void inferOwningParamsViaOwnershipTransfer( + Set obligations, Node invocation) { + List paramsOfCurrentMethod = methodTree.getParameters(); + if (paramsOfCurrentMethod.isEmpty()) { + return; + } + List calleeParams = mcca.getParametersOfInvocation(invocation); + if (calleeParams.isEmpty()) { + return; + } + List arguments = mcca.getArgumentsOfInvocation(invocation); + + for (int i = 0; i < arguments.size(); i++) { + if (!resourceLeakAtf.hasOwning(calleeParams.get(i))) { + continue; + } + for (int j = 0; j < paramsOfCurrentMethod.size(); j++) { + VariableTree paramOfCurrMethod = paramsOfCurrentMethod.get(j); + if (resourceLeakAtf.hasEmptyMustCallValue(paramOfCurrMethod)) { + continue; + } + + Node arg = NodeUtils.removeCasts(arguments.get(i)); + VariableElement paramElt = TreeUtils.elementFromDeclaration(paramOfCurrMethod); + if (nodeAndElementResourceAliased(obligations, arg, paramElt)) { + addOwningToParam(j + 1); + break; + } + } + } } - } - - /** - * Returns the called methods annotation for the given Java expression after the invocation node. - * - * @param invocation the MethodInvocationNode - * @param varJe a Java expression - * @return the called methods annotation for the {@code varJe} after the {@code invocation} node - */ - private AnnotationMirror getCalledMethodsAnno( - MethodInvocationNode invocation, JavaExpression varJe) { - AccumulationStore cmStoreAfter = resourceLeakAtf.getStoreAfter(invocation); - AccumulationValue cmValue = cmStoreAfter == null ? null : cmStoreAfter.getValue(varJe); - - AnnotationMirror cmAnno = null; - - if (cmValue != null) { - // The store contains the lhs. - Set accumulatedValues = cmValue.getAccumulatedValues(); - if (accumulatedValues != null) { // type variable or wildcard type - cmAnno = resourceLeakAtf.createCalledMethods(accumulatedValues.toArray(new String[0])); - } else { - for (AnnotationMirror anno : cmValue.getAnnotations()) { - if (AnnotationUtils.areSameByName( - anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { - cmAnno = anno; - } - } - } + + /** + * Checks whether the given element is a resource alias of the given node in the provided set of + * obligations. + * + * @param obligations the current set of tracked Obligations + * @param node the node + * @param element the element + * @return true if {@code element} is a resource alias of {@code node} + */ + private boolean nodeAndElementResourceAliased( + Set obligations, Node node, VariableElement element) { + Set nodeAliases = getResourceAliasOfNode(obligations, node); + for (ResourceAlias nodeAlias : nodeAliases) { + Element nodeAliasElt = nodeAlias.element; + if (nodeAliasElt.equals(element)) { + return true; + } + } + return false; + } + + /** + * Infers @Owning annotations for fields or the parameters of the current method that are passed + * in the receiver or arguments position of a call if their must-call obligation is satisfied + * via the {@code invocation}. + * + * @param obligations the current set of tracked Obligations + * @param invocation a method invocation node to check + */ + private void inferOwningForRecieverOrFormalParamPassedToCall( + Set obligations, MethodInvocationNode invocation) { + Node receiver = invocation.getTarget().getReceiver(); + receiver = NodeUtils.removeCasts(receiver); + if (receiver.getTree() != null) { + inferOwningForParamOrField(obligations, invocation, receiver); + } + + for (Node argument : mcca.getArgumentsOfInvocation(invocation)) { + Node arg = mcca.removeCastsAndGetTmpVarIfPresent(argument); + // In the CFG, explicit passing of multiple arguments in the varargs position is + // represented via an ArrayCreationNode. In this case, it checks the called methods + // set of each argument passed in this position. + if (arg instanceof ArrayCreationNode) { + ArrayCreationNode varArgsNode = (ArrayCreationNode) arg; + for (Node varArgNode : varArgsNode.getInitializers()) { + inferOwningForParamOrField(obligations, invocation, varArgNode); + } + } else { + inferOwningForParamOrField(obligations, invocation, arg); + } + } } - if (cmAnno == null) { - cmAnno = resourceLeakAtf.top; + /** + * Infers an @Owning annotation for the {@code arg} that can be a receiver or an argument passed + * into a method call if the must-call obligation of the {@code arg} is satisfied via the {@code + * invocation}. + * + * @param obligations the current set of tracked Obligations + * @param invocation the method invocation node to check + * @param arg a receiver or an argument passed to the method call + */ + private void inferOwningForParamOrField( + Set obligations, MethodInvocationNode invocation, Node arg) { + Element argElt = TreeUtils.elementFromTree(arg.getTree()); + // The must-call obligation of a field can be satisfied either through a call where it + // serves as a receiver or within the callee method when it is passed as an argument. + if (argElt != null && argElt.getKind().isField()) { + inferOwningField(arg, invocation); + return; + } + + List paramsOfCurrentMethod = methodTree.getParameters(); + outerLoop: + for (int i = 0; i < paramsOfCurrentMethod.size(); i++) { + VariableTree currentMethodParamTree = paramsOfCurrentMethod.get(i); + if (resourceLeakAtf.hasEmptyMustCallValue(currentMethodParamTree)) { + continue; + } + + VariableElement paramElt = TreeUtils.elementFromDeclaration(currentMethodParamTree); + if (!nodeAndElementResourceAliased(obligations, arg, paramElt)) { + continue; + } + + List mustCallValues = resourceLeakAtf.getMustCallValues(paramElt); + assert mustCallValues.size() <= 1 : "TODO: Handle larger must-call values sets"; + Set nodeAliases = getResourceAliasOfNode(obligations, arg); + for (ResourceAlias resourceAlias : nodeAliases) { + AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, resourceAlias.reference); + if (mcca.calledMethodsSatisfyMustCall(mustCallValues, cmAnno)) { + addOwningToParam(i + 1); + break outerLoop; + } + } + } } - return cmAnno; - } - - /** - * Adds all non-exceptional successors to {@code worklist}. - * - * @param obligations the current set of tracked Obligations - * @param curBlock the block whose successors to add to the worklist - * @param visited block-Obligations pairs already analyzed or already on the worklist - * @param worklist the worklist, which is side-effected by this method - */ - private void addNonExceptionalSuccessorsToWorklist( - Set obligations, - Block curBlock, - Set visited, - Deque worklist) { - - for (Block successor : getNonExceptionalSuccessors(curBlock)) { - // If successor is a special block, it must be the regular exit. - if (successor.getType() != Block.BlockType.SPECIAL_BLOCK) { - BlockWithObligations state = new BlockWithObligations(successor, obligations); - if (visited.add(state)) { - worklist.add(state); - } - } + /** + * Returns the set of resource aliases associated with the given node, by looking up the + * corresponding obligation in the given set of obligations. + * + * @param obligations the set of obligations to search in + * @param node the node whose resource aliases are to be returned + * @return the resource aliases associated with the given node, or an empty set if the node has + * none + */ + private Set getResourceAliasOfNode(Set obligations, Node node) { + Node tempVar = mcca.getTempVarOrNode(node); + if (!(tempVar instanceof LocalVariableNode)) { + return Collections.emptySet(); + } + + Obligation argumentObligation = + MustCallConsistencyAnalyzer.getObligationForVar( + obligations, (LocalVariableNode) tempVar); + if (argumentObligation == null) { + return Collections.emptySet(); + } + return argumentObligation.resourceAliases; + } + + /** + * Infers @Owning or @MustCallAlias annotations for formal parameters of the enclosing method + * and @Owning annotations for fields of the enclosing class, as follows: + * + *
            + *
          • If a formal parameter is passed as an owning parameter, add an @Owning annotation to + * that formal parameter (see {@link #inferOwningParamsViaOwnershipTransfer}). + *
          • It calls {@link #inferOwningForRecieverOrFormalParamPassedToCall} to infer @Owning + * annotations for the receiver or arguments of a call by analyzing the called-methods set + * after the call. + *
          • It calls {@link #inferMustCallAliasFromThisOrSuperCall} to infer @MustCallAlias + * annotation for formal parameters and the result of the constructor. + *
          + * + * @param obligations the set of obligations to search in + * @param invocation the method or constructor invocation + */ + private void inferOwningFromInvocation(Set obligations, Node invocation) { + if (invocation instanceof ObjectCreationNode) { + // If the invocation corresponds to an object creation node, only ownership transfer + // checking is required, as constructor parameters may have an @Owning annotation. We + // do not handle @EnsuresCalledMethods annotations on constructors as we have not + // observed them in practice. + inferOwningParamsViaOwnershipTransfer(obligations, invocation); + } else if (invocation instanceof MethodInvocationNode) { + inferMustCallAliasFromThisOrSuperCall(obligations, (MethodInvocationNode) invocation); + inferOwningParamsViaOwnershipTransfer(obligations, invocation); + inferOwningForRecieverOrFormalParamPassedToCall( + obligations, (MethodInvocationNode) invocation); + } } - } - - /** - * Returns the non-exceptional successors of a block. - * - * @param cur a block - * @return the successors of the given block - */ - private List getNonExceptionalSuccessors(Block cur) { - if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { - ConditionalBlock ccur = (ConditionalBlock) cur; - return Arrays.asList(ccur.getThenSuccessor(), ccur.getElseSuccessor()); + + /** + * Adds the @MustCallAlias annotation to a method parameter when it is passed in + * a @MustCallAlias position during a constructor call using {@literal this} or {@literal + * super}. + * + * @param obligations the current set of tracked Obligations + * @param node a method invocation node + */ + private void inferMustCallAliasFromThisOrSuperCall( + Set obligations, MethodInvocationNode node) { + if (!TreeUtils.isSuperConstructorCall(node.getTree()) + && !TreeUtils.isThisConstructorCall(node.getTree())) { + return; + } + List calleeParams = mcca.getParametersOfInvocation(node); + List arguments = mcca.getArgumentsOfInvocation(node); + for (int i = 0; i < arguments.size(); i++) { + if (!resourceLeakAtf.hasMustCallAlias(calleeParams.get(i))) { + continue; + } + + Node arg = mcca.removeCastsAndGetTmpVarIfPresent(arguments.get(i)); + Obligation argObligation = + MustCallConsistencyAnalyzer.getObligationForVar( + obligations, (LocalVariableNode) arg); + if (argObligation == null) { + return; + } + int index = getIndexOfParam(argObligation); + if (index != -1) { + addMustCallAliasToFormalParameter(index); + break; + } + } } - if (!(cur instanceof SingleSuccessorBlock)) { - throw new BugInCF("Not a conditional block nor a SingleSuccessorBlock: " + cur); + + /** + * Returns the called methods annotation for the given Java expression after the invocation + * node. + * + * @param invocation the MethodInvocationNode + * @param varJe a Java expression + * @return the called methods annotation for the {@code varJe} after the {@code invocation} node + */ + private AnnotationMirror getCalledMethodsAnno( + MethodInvocationNode invocation, JavaExpression varJe) { + AccumulationStore cmStoreAfter = resourceLeakAtf.getStoreAfter(invocation); + AccumulationValue cmValue = cmStoreAfter == null ? null : cmStoreAfter.getValue(varJe); + + AnnotationMirror cmAnno = null; + + if (cmValue != null) { + // The store contains the lhs. + Set accumulatedValues = cmValue.getAccumulatedValues(); + if (accumulatedValues != null) { // type variable or wildcard type + cmAnno = + resourceLeakAtf.createCalledMethods( + accumulatedValues.toArray(new String[0])); + } else { + for (AnnotationMirror anno : cmValue.getAnnotations()) { + if (AnnotationUtils.areSameByName( + anno, + "org.checkerframework.checker.calledmethods.qual.CalledMethods")) { + cmAnno = anno; + } + } + } + } + + if (cmAnno == null) { + cmAnno = resourceLeakAtf.top; + } + + return cmAnno; + } + + /** + * Adds all non-exceptional successors to {@code worklist}. + * + * @param obligations the current set of tracked Obligations + * @param curBlock the block whose successors to add to the worklist + * @param visited block-Obligations pairs already analyzed or already on the worklist + * @param worklist the worklist, which is side-effected by this method + */ + private void addNonExceptionalSuccessorsToWorklist( + Set obligations, + Block curBlock, + Set visited, + Deque worklist) { + + for (Block successor : getNonExceptionalSuccessors(curBlock)) { + // If successor is a special block, it must be the regular exit. + if (successor.getType() != Block.BlockType.SPECIAL_BLOCK) { + BlockWithObligations state = new BlockWithObligations(successor, obligations); + if (visited.add(state)) { + worklist.add(state); + } + } + } } - Block successor = ((SingleSuccessorBlock) cur).getSuccessor(); - if (successor != null) { - return Collections.singletonList(successor); + /** + * Returns the non-exceptional successors of a block. + * + * @param cur a block + * @return the successors of the given block + */ + private List getNonExceptionalSuccessors(Block cur) { + if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { + ConditionalBlock ccur = (ConditionalBlock) cur; + return Arrays.asList(ccur.getThenSuccessor(), ccur.getElseSuccessor()); + } + if (!(cur instanceof SingleSuccessorBlock)) { + throw new BugInCF("Not a conditional block nor a SingleSuccessorBlock: " + cur); + } + + Block successor = ((SingleSuccessorBlock) cur).getSuccessor(); + if (successor != null) { + return Collections.singletonList(successor); + } + return Collections.emptyList(); + } + + /** + * Creates an {@code @EnsuresCalledMethods} annotation with the given arguments. + * + * @param value the expressions that will have methods called on them + * @param methods the methods guaranteed to be invoked on the expressions + * @return an {@code @EnsuresCalledMethods} annotation with the given arguments + */ + private AnnotationMirror createEnsuresCalledMethods(String[] value, String[] methods) { + AnnotationBuilder builder = + new AnnotationBuilder( + resourceLeakAtf.getProcessingEnv(), EnsuresCalledMethods.class); + builder.setValue("value", value); + builder.setValue("methods", methods); + AnnotationMirror am = builder.build(); + return am; + } + + /** + * Creates an {@code @InheritableMustCall} annotation with the given arguments. + * + * @param methods methods that might need to be called on the expression whose type is annotated + * @return an {@code @InheritableMustCall} annotation with the given arguments + */ + private AnnotationMirror createInheritableMustCall(String[] methods) { + AnnotationBuilder builder = + new AnnotationBuilder( + resourceLeakAtf.getProcessingEnv(), InheritableMustCall.class); + Arrays.sort(methods); + builder.setValue("value", methods); + return builder.build(); } - return Collections.emptyList(); - } - - /** - * Creates an {@code @EnsuresCalledMethods} annotation with the given arguments. - * - * @param value the expressions that will have methods called on them - * @param methods the methods guaranteed to be invoked on the expressions - * @return an {@code @EnsuresCalledMethods} annotation with the given arguments - */ - private AnnotationMirror createEnsuresCalledMethods(String[] value, String[] methods) { - AnnotationBuilder builder = - new AnnotationBuilder(resourceLeakAtf.getProcessingEnv(), EnsuresCalledMethods.class); - builder.setValue("value", value); - builder.setValue("methods", methods); - AnnotationMirror am = builder.build(); - return am; - } - - /** - * Creates an {@code @InheritableMustCall} annotation with the given arguments. - * - * @param methods methods that might need to be called on the expression whose type is annotated - * @return an {@code @InheritableMustCall} annotation with the given arguments - */ - private AnnotationMirror createInheritableMustCall(String[] methods) { - AnnotationBuilder builder = - new AnnotationBuilder(resourceLeakAtf.getProcessingEnv(), InheritableMustCall.class); - Arrays.sort(methods); - builder.setValue("value", methods); - return builder.build(); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java index 1dab92a1da8..0b68646c1c0 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnalysis.java @@ -1,9 +1,10 @@ package org.checkerframework.checker.resourceleak; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.calledmethods.CalledMethodsAnalysis; import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; +import javax.lang.model.type.TypeMirror; + /** * This variant of CFAnalysis extends the set of ignored exception types. * @@ -11,26 +12,26 @@ */ public class ResourceLeakAnalysis extends CalledMethodsAnalysis { - /** - * The set of exceptions to ignore, cached from {@link - * ResourceLeakChecker#getIgnoredExceptions()}. - */ - private final SetOfTypes ignoredExceptions; + /** + * The set of exceptions to ignore, cached from {@link + * ResourceLeakChecker#getIgnoredExceptions()}. + */ + private final SetOfTypes ignoredExceptions; - /** - * Creates a new {@code CalledMethodsAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - protected ResourceLeakAnalysis( - ResourceLeakChecker checker, CalledMethodsAnnotatedTypeFactory factory) { - super(checker, factory); - this.ignoredExceptions = checker.getIgnoredExceptions(); - } + /** + * Creates a new {@code CalledMethodsAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + protected ResourceLeakAnalysis( + ResourceLeakChecker checker, CalledMethodsAnnotatedTypeFactory factory) { + super(checker, factory); + this.ignoredExceptions = checker.getIgnoredExceptions(); + } - @Override - public boolean isIgnoredExceptionType(TypeMirror exceptionType) { - return ignoredExceptions.contains(getTypes(), exceptionType); - } + @Override + public boolean isIgnoredExceptionType(TypeMirror exceptionType) { + return ignoredExceptions.contains(getTypes(), exceptionType); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java index 3a57f64195a..81a8b33f9a7 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java @@ -3,15 +3,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; + import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory; import org.checkerframework.checker.calledmethods.EnsuresCalledMethodOnExceptionContract; import org.checkerframework.checker.calledmethods.qual.CalledMethods; @@ -42,478 +34,493 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + /** * The type factory for the Resource Leak Checker. The main difference between this and the Called * Methods type factory from which it is derived is that this version's {@link * #postAnalyze(ControlFlowGraph)} method checks that must-call obligations are fulfilled. */ public class ResourceLeakAnnotatedTypeFactory extends CalledMethodsAnnotatedTypeFactory - implements CreatesMustCallForElementSupplier { - - /** The MustCall.value element/field. */ - private final ExecutableElement mustCallValueElement = - TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); - - /** The EnsuresCalledMethods.value element/field. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsValueElement = - TreeUtils.getMethod(EnsuresCalledMethods.class, "value", 0, processingEnv); - - /** The EnsuresCalledMethods.methods element/field. */ - /*package-private*/ final ExecutableElement ensuresCalledMethodsMethodsElement = - TreeUtils.getMethod(EnsuresCalledMethods.class, "methods", 0, processingEnv); - - /** The EnsuresCalledMethods.List.value element/field. */ - private final ExecutableElement ensuresCalledMethodsListValueElement = - TreeUtils.getMethod(EnsuresCalledMethods.List.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.List.value element/field. */ - private final ExecutableElement createsMustCallForListValueElement = - TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); - - /** The CreatesMustCallFor.value element/field. */ - private final ExecutableElement createsMustCallForValueElement = - TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); - - /** True if -AnoResourceAliases was passed on the command line. */ - private final boolean noResourceAliases; - - /** - * Bidirectional map to store temporary variables created for expressions with non-empty @MustCall - * obligations and the corresponding trees. Keys are the artificial local variable nodes created - * as temporary variables; values are the corresponding trees. - * - *

          Note that in an ideal world, this would be an {@code IdentityBiMap}: that is, a BiMap using - * {@link java.util.IdentityHashMap} as both of the backing maps. However, Guava doesn't have such - * a map AND their implementation is incompatible with IdentityHashMap as a backing map, because - * even their {@code AbstractBiMap} class uses {@code equals} calls in its implementation (and its - * documentation calls out that it and all its derived BiMaps are incompatible with - * IdentityHashMap as a backing map for this reason). Therefore, we use a regular BiMap. Doing so - * is safe iff 1) the LocalVariableNode keys all have different names, and 2) a standard Tree - * implementation that uses reference equality for equality (e.g., JCTree in javac) is used. - */ - private final BiMap tempVarToTree = HashBiMap.create(); - - /** - * Creates a new ResourceLeakAnnotatedTypeFactory. - * - * @param checker the checker associated with this type factory - */ - public ResourceLeakAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.noResourceAliases = checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES); - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class); - } - - /** - * Creates a @CalledMethods annotation whose values are the given strings. - * - * @param val the methods that have been called - * @return an annotation indicating that the given methods have been called - */ - public AnnotationMirror createCalledMethods(String... val) { - return createAccumulatorAnnotation(Arrays.asList(val)); - } - - @Override - public void postAnalyze(ControlFlowGraph cfg) { - MustCallConsistencyAnalyzer mustCallConsistencyAnalyzer = - new MustCallConsistencyAnalyzer(this, (ResourceLeakAnalysis) this.analysis); - mustCallConsistencyAnalyzer.analyze(cfg); - - // Inferring owning annotations for @Owning fields/parameters, @EnsuresCalledMethods for - // finalizer methods and @InheritableMustCall annotations for the class declarations. - /* NO-AFU - if (getWholeProgramInference() != null) { - if (cfg.getUnderlyingAST().getKind() == UnderlyingAST.Kind.METHOD) { - MustCallInference.runMustCallInference(this, cfg, mustCallConsistencyAnalyzer); - } + implements CreatesMustCallForElementSupplier { + + /** The MustCall.value element/field. */ + private final ExecutableElement mustCallValueElement = + TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv); + + /** The EnsuresCalledMethods.value element/field. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsValueElement = + TreeUtils.getMethod(EnsuresCalledMethods.class, "value", 0, processingEnv); + + /** The EnsuresCalledMethods.methods element/field. */ + /*package-private*/ final ExecutableElement ensuresCalledMethodsMethodsElement = + TreeUtils.getMethod(EnsuresCalledMethods.class, "methods", 0, processingEnv); + + /** The EnsuresCalledMethods.List.value element/field. */ + private final ExecutableElement ensuresCalledMethodsListValueElement = + TreeUtils.getMethod(EnsuresCalledMethods.List.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.List.value element/field. */ + private final ExecutableElement createsMustCallForListValueElement = + TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv); + + /** The CreatesMustCallFor.value element/field. */ + private final ExecutableElement createsMustCallForValueElement = + TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv); + + /** True if -AnoResourceAliases was passed on the command line. */ + private final boolean noResourceAliases; + + /** + * Bidirectional map to store temporary variables created for expressions with + * non-empty @MustCall obligations and the corresponding trees. Keys are the artificial local + * variable nodes created as temporary variables; values are the corresponding trees. + * + *

          Note that in an ideal world, this would be an {@code IdentityBiMap}: that is, a BiMap + * using {@link java.util.IdentityHashMap} as both of the backing maps. However, Guava doesn't + * have such a map AND their implementation is incompatible with IdentityHashMap as a backing + * map, because even their {@code AbstractBiMap} class uses {@code equals} calls in its + * implementation (and its documentation calls out that it and all its derived BiMaps are + * incompatible with IdentityHashMap as a backing map for this reason). Therefore, we use a + * regular BiMap. Doing so is safe iff 1) the LocalVariableNode keys all have different names, + * and 2) a standard Tree implementation that uses reference equality for equality (e.g., JCTree + * in javac) is used. + */ + private final BiMap tempVarToTree = HashBiMap.create(); + + /** + * Creates a new ResourceLeakAnnotatedTypeFactory. + * + * @param checker the checker associated with this type factory + */ + public ResourceLeakAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.noResourceAliases = checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES); + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class); + } + + /** + * Creates a @CalledMethods annotation whose values are the given strings. + * + * @param val the methods that have been called + * @return an annotation indicating that the given methods have been called + */ + public AnnotationMirror createCalledMethods(String... val) { + return createAccumulatorAnnotation(Arrays.asList(val)); + } + + @Override + public void postAnalyze(ControlFlowGraph cfg) { + MustCallConsistencyAnalyzer mustCallConsistencyAnalyzer = + new MustCallConsistencyAnalyzer(this, (ResourceLeakAnalysis) this.analysis); + mustCallConsistencyAnalyzer.analyze(cfg); + + // Inferring owning annotations for @Owning fields/parameters, @EnsuresCalledMethods for + // finalizer methods and @InheritableMustCall annotations for the class declarations. + /* NO-AFU + if (getWholeProgramInference() != null) { + if (cfg.getUnderlyingAST().getKind() == UnderlyingAST.Kind.METHOD) { + MustCallInference.runMustCallInference(this, cfg, mustCallConsistencyAnalyzer); + } + } + */ + + super.postAnalyze(cfg); + tempVarToTree.clear(); + } + + @Override + protected ResourceLeakAnalysis createFlowAnalysis() { + return new ResourceLeakAnalysis((ResourceLeakChecker) checker, this); + } + + /** + * Retrieves the {@code @MustCall} annotation for the given object, which can be either an + * {@link Element} or a {@link Tree}. This method delegates to the {@code + * MustCallAnnotatedTypeFactory} to get the annotated type of the input object and then extracts + * the primary {@code @MustCall} annotation from it. + * + * @param obj the object for which to retrieve the {@code @MustCall} annotation. Must be either + * an instance of {@link Element} or {@link Tree}. + * @return the {@code @MustCall} annotation if present, null otherwise + * @throws IllegalArgumentException if the input object type is not supported + */ + public AnnotationMirror getMustCallAnnotation(Object obj) { + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotatedTypeMirror mustCallAnnotatedType; + if (obj instanceof Element) { + mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType((Element) obj); + } else if (obj instanceof Tree) { + mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType((Tree) obj); + } else { + throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getName()); + } + return mustCallAnnotatedType.getAnnotation(MustCall.class); + } + + /** + * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on + * the type of {@code tree} is definitely empty. + * + *

          This method only considers the declared type: it does not consider flow-sensitive + * refinement. + * + * @param tree a tree + * @return true if the Must Call type is non-empty or top + */ + /*package-private*/ boolean hasEmptyMustCallValue(Tree tree) { + AnnotationMirror mustCallAnnotation = getMustCallAnnotation(tree); + if (mustCallAnnotation != null) { + return getMustCallValues(mustCallAnnotation).isEmpty(); + } else { + // Indicates @MustCallUnknown, which should be treated (conservatively) as if it + // contains some must call values. + return false; + } } - */ - - super.postAnalyze(cfg); - tempVarToTree.clear(); - } - - @Override - protected ResourceLeakAnalysis createFlowAnalysis() { - return new ResourceLeakAnalysis((ResourceLeakChecker) checker, this); - } - - /** - * Retrieves the {@code @MustCall} annotation for the given object, which can be either an {@link - * Element} or a {@link Tree}. This method delegates to the {@code MustCallAnnotatedTypeFactory} - * to get the annotated type of the input object and then extracts the primary {@code @MustCall} - * annotation from it. - * - * @param obj the object for which to retrieve the {@code @MustCall} annotation. Must be either an - * instance of {@link Element} or {@link Tree}. - * @return the {@code @MustCall} annotation if present, null otherwise - * @throws IllegalArgumentException if the input object type is not supported - */ - public AnnotationMirror getMustCallAnnotation(Object obj) { - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotatedTypeMirror mustCallAnnotatedType; - if (obj instanceof Element) { - mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType((Element) obj); - } else if (obj instanceof Tree) { - mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType((Tree) obj); - } else { - throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getName()); + + /** + * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on + * the type of {@code element} is definitely empty. + * + *

          This method only considers the declared type: it does not consider flow-sensitive + * refinement. + * + * @param element an element + * @return true if the Must Call type is non-empty or top + */ + /*package-private*/ boolean hasEmptyMustCallValue(Element element) { + AnnotationMirror mustCallAnnotation = getMustCallAnnotation(element); + if (mustCallAnnotation != null) { + return getMustCallValues(mustCallAnnotation).isEmpty(); + } else { + // Indicates @MustCallUnknown, which should be treated (conservatively) as if it + // contains some must call values. + return false; + } } - return mustCallAnnotatedType.getAnnotation(MustCall.class); - } - - /** - * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on the - * type of {@code tree} is definitely empty. - * - *

          This method only considers the declared type: it does not consider flow-sensitive - * refinement. - * - * @param tree a tree - * @return true if the Must Call type is non-empty or top - */ - /*package-private*/ boolean hasEmptyMustCallValue(Tree tree) { - AnnotationMirror mustCallAnnotation = getMustCallAnnotation(tree); - if (mustCallAnnotation != null) { - return getMustCallValues(mustCallAnnotation).isEmpty(); - } else { - // Indicates @MustCallUnknown, which should be treated (conservatively) as if it - // contains some must call values. - return false; + + /** + * Returns the {@link MustCall#value} element/argument of the @MustCall annotation on the class + * type of {@code element}. If there is no such annotation, returns the empty list. + * + *

          Do not use this method to get the MustCall values of an {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, + * use {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, + * CFStore)}. + * + *

          Do not call {@link List#isEmpty()} on the result of this method: prefer to call {@link + * #hasEmptyMustCallValue(Element)}, which correctly accounts for @MustCallUnknown, instead. + * + * @param element an element + * @return the strings in its must-call type + */ + /*package-private*/ List getMustCallValues(Element element) { + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotatedTypeMirror mustCallAnnotatedType = + mustCallAnnotatedTypeFactory.getAnnotatedType(element); + AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); + return getMustCallValues(mustCallAnnotation); } - } - - /** - * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on the - * type of {@code element} is definitely empty. - * - *

          This method only considers the declared type: it does not consider flow-sensitive - * refinement. - * - * @param element an element - * @return true if the Must Call type is non-empty or top - */ - /*package-private*/ boolean hasEmptyMustCallValue(Element element) { - AnnotationMirror mustCallAnnotation = getMustCallAnnotation(element); - if (mustCallAnnotation != null) { - return getMustCallValues(mustCallAnnotation).isEmpty(); - } else { - // Indicates @MustCallUnknown, which should be treated (conservatively) as if it - // contains some must call values. - return false; + + /** + * Helper method for getting the must-call values from a must-call annotation. + * + * @param mustCallAnnotation a {@link MustCall} annotation, or null + * @return the strings in mustCallAnnotation's value element, or the empty list if + * mustCallAnnotation is null + */ + /*package-private*/ List getMustCallValues( + @Nullable AnnotationMirror mustCallAnnotation) { + if (mustCallAnnotation == null) { + return Collections.emptyList(); + } + return AnnotationUtils.getElementValueArray( + mustCallAnnotation, mustCallValueElement, String.class, Collections.emptyList()); } - } - - /** - * Returns the {@link MustCall#value} element/argument of the @MustCall annotation on the class - * type of {@code element}. If there is no such annotation, returns the empty list. - * - *

          Do not use this method to get the MustCall values of an {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, use - * {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, - * CFStore)}. - * - *

          Do not call {@link List#isEmpty()} on the result of this method: prefer to call {@link - * #hasEmptyMustCallValue(Element)}, which correctly accounts for @MustCallUnknown, instead. - * - * @param element an element - * @return the strings in its must-call type - */ - /*package-private*/ List getMustCallValues(Element element) { - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotatedTypeMirror mustCallAnnotatedType = - mustCallAnnotatedTypeFactory.getAnnotatedType(element); - AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); - return getMustCallValues(mustCallAnnotation); - } - - /** - * Helper method for getting the must-call values from a must-call annotation. - * - * @param mustCallAnnotation a {@link MustCall} annotation, or null - * @return the strings in mustCallAnnotation's value element, or the empty list if - * mustCallAnnotation is null - */ - /*package-private*/ List getMustCallValues( - @Nullable AnnotationMirror mustCallAnnotation) { - if (mustCallAnnotation == null) { - return Collections.emptyList(); + + /** + * Helper method to get the temporary variable that represents the given node, if one exists. + * + * @param node a node + * @return the tempvar for node's expression, or null if one does not exist + */ + /*package-private*/ @Nullable LocalVariableNode getTempVarForNode(Node node) { + return tempVarToTree.inverse().get(node.getTree()); } - return AnnotationUtils.getElementValueArray( - mustCallAnnotation, mustCallValueElement, String.class, Collections.emptyList()); - } - - /** - * Helper method to get the temporary variable that represents the given node, if one exists. - * - * @param node a node - * @return the tempvar for node's expression, or null if one does not exist - */ - /*package-private*/ @Nullable LocalVariableNode getTempVarForNode(Node node) { - return tempVarToTree.inverse().get(node.getTree()); - } - - /** - * Is the given node a temporary variable? - * - * @param node a node - * @return true iff the given node is a temporary variable - */ - /*package-private*/ boolean isTempVar(Node node) { - return tempVarToTree.containsKey(node); - } - - /** - * Gets the tree for a temporary variable - * - * @param node a node for a temporary variable - * @return the tree for {@code node} - */ - /*package-private*/ Tree getTreeForTempVar(Node node) { - if (!tempVarToTree.containsKey(node)) { - throw new TypeSystemError(node + " must be a temporary variable"); + + /** + * Is the given node a temporary variable? + * + * @param node a node + * @return true iff the given node is a temporary variable + */ + /*package-private*/ boolean isTempVar(Node node) { + return tempVarToTree.containsKey(node); } - return tempVarToTree.get(node); - } - - /** - * Registers a temporary variable by adding it to this type factory's tempvar map. - * - * @param tmpVar a temporary variable - * @param tree the tree of the expression the tempvar represents - */ - /*package-private*/ void addTempVar(LocalVariableNode tmpVar, Tree tree) { - if (!tempVarToTree.containsValue(tree)) { - tempVarToTree.put(tmpVar, tree); + + /** + * Gets the tree for a temporary variable + * + * @param node a node for a temporary variable + * @return the tree for {@code node} + */ + /*package-private*/ Tree getTreeForTempVar(Node node) { + if (!tempVarToTree.containsKey(node)) { + throw new TypeSystemError(node + " must be a temporary variable"); + } + return tempVarToTree.get(node); } - } - - /** - * Returns true if the type of the tree includes a must-call annotation. Note that this method may - * not consider dataflow, and is only safe to use when you need the declared, rather than - * inferred, type of the tree. - * - *

          Do not use this method if you are trying to get the must-call obligations of the resource - * aliases of an {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, use - * {@link - * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, - * CFStore)}. - * - * @param tree a tree - * @return whether the tree has declared must-call obligations - */ - /*package-private*/ boolean declaredTypeHasMustCall(Tree tree) { - assert tree.getKind() == Tree.Kind.METHOD - || tree.getKind() == Tree.Kind.VARIABLE - || tree.getKind() == Tree.Kind.NEW_CLASS - || tree.getKind() == Tree.Kind.METHOD_INVOCATION - : "unexpected declaration tree kind: " + tree.getKind(); - return !hasEmptyMustCallValue(tree); - } - - /** - * Returns true if the given tree has an {@link MustCallAlias} annotation and resource-alias - * tracking is not disabled. - * - * @param tree a tree - * @return true if the given tree has an {@link MustCallAlias} annotation - */ - /*package-private*/ boolean hasMustCallAlias(Tree tree) { - Element elt = TreeUtils.elementFromTree(tree); - return hasMustCallAlias(elt); - } - - /** - * Returns true if the given element has an {@link MustCallAlias} annotation and resource-alias - * tracking is not disabled. - * - * @param elt an element - * @return true if the given element has an {@link MustCallAlias} annotation - */ - /*package-private*/ boolean hasMustCallAlias(Element elt) { - if (noResourceAliases) { - return false; + + /** + * Registers a temporary variable by adding it to this type factory's tempvar map. + * + * @param tmpVar a temporary variable + * @param tree the tree of the expression the tempvar represents + */ + /*package-private*/ void addTempVar(LocalVariableNode tmpVar, Tree tree) { + if (!tempVarToTree.containsValue(tree)) { + tempVarToTree.put(tmpVar, tree); + } } - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - return mustCallAnnotatedTypeFactory.getDeclAnnotationNoAliases(elt, MustCallAlias.class) - != null; - } - - /** - * Returns true if the declaration of the method being invoked has one or more {@link - * CreatesMustCallFor} annotations. - * - * @param node a method invocation node - * @return true iff there is one or more @CreatesMustCallFor annotations on the declaration of the - * invoked method - */ - public boolean hasCreatesMustCallFor(MethodInvocationNode node) { - ExecutableElement decl = TreeUtils.elementFromUse(node.getTree()); - return getDeclAnnotation(decl, CreatesMustCallFor.class) != null - || getDeclAnnotation(decl, CreatesMustCallFor.List.class) != null; - } - - /** - * Does this type factory support {@link CreatesMustCallFor}? - * - * @return true iff the -AnoCreatesMustCallFor command-line argument was not supplied to the - * checker - */ - public boolean canCreateObligations() { - // Precomputing this call to `hasOption` causes a NullPointerException, so leave it as is. - return !checker.hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); - } - - @Override - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public > - @Nullable T getTypeFactoryOfSubcheckerOrNull( - Class subCheckerClass) { - if (subCheckerClass == MustCallChecker.class) { - if (!canCreateObligations()) { - return super.getTypeFactoryOfSubcheckerOrNull(MustCallNoCreatesMustCallForChecker.class); - } + + /** + * Returns true if the type of the tree includes a must-call annotation. Note that this method + * may not consider dataflow, and is only safe to use when you need the declared, rather than + * inferred, type of the tree. + * + *

          Do not use this method if you are trying to get the must-call obligations of the resource + * aliases of an {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, + * use {@link + * org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory, + * CFStore)}. + * + * @param tree a tree + * @return whether the tree has declared must-call obligations + */ + /*package-private*/ boolean declaredTypeHasMustCall(Tree tree) { + assert tree.getKind() == Tree.Kind.METHOD + || tree.getKind() == Tree.Kind.VARIABLE + || tree.getKind() == Tree.Kind.NEW_CLASS + || tree.getKind() == Tree.Kind.METHOD_INVOCATION + : "unexpected declaration tree kind: " + tree.getKind(); + return !hasEmptyMustCallValue(tree); } - return super.getTypeFactoryOfSubcheckerOrNull(subCheckerClass); - } - - /** - * Returns the {@link EnsuresCalledMethods.List#value} element. - * - * @return the {@link EnsuresCalledMethods.List#value} element - */ - public ExecutableElement getEnsuresCalledMethodsListValueElement() { - return ensuresCalledMethodsListValueElement; - } - - /** - * Returns the {@link CreatesMustCallFor#value} element. - * - * @return the {@link CreatesMustCallFor#value} element - */ - @Override - public ExecutableElement getCreatesMustCallForValueElement() { - return createsMustCallForValueElement; - } - - /** - * Returns the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} - * element. - * - * @return the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} - * element - */ - @Override - public ExecutableElement getCreatesMustCallForListValueElement() { - return createsMustCallForListValueElement; - } - - /** - * Does the given element have an {@code @NotOwning} annotation (including in stub files)? - * - *

          Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type factory - * directly, which won't find this annotation in stub files (it only considers stub files loaded - * by this checker, not subcheckers). - * - * @param elt an element - * @return whether there is a NotOwning annotation on the given element - */ - public boolean hasNotOwning(Element elt) { - MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); - return mcatf.getDeclAnnotation(elt, NotOwning.class) != null; - } - - /** - * Does the given element have an {@code @Owning} annotation (including in stub files)? - * - *

          Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type factory - * directly, which won't find this annotation in stub files (it only considers stub files loaded - * by this checker, not subcheckers). - * - * @param elt an element - * @return whether there is an Owning annotation on the given element - */ - public boolean hasOwning(Element elt) { - MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); - return mcatf.getDeclAnnotation(elt, Owning.class) != null; - } - - @Override - public Set getExceptionalPostconditions( - ExecutableElement methodOrConstructor) { - Set result = - super.getExceptionalPostconditions(methodOrConstructor); - - // This override is a sneaky way to satisfy a few subtle design constraints: - // 1. The RLC requires destructors to close the class's @Owning fields even on exception - // (see ResourceLeakVisitor.checkOwningField). - // 2. In versions 3.39.0 and earlier, the RLC did not have the annotation - // @EnsuresCalledMethodsOnException, meaning that for destructors it had to treat - // a simple @EnsuresCalledMethods annotation as serving both purposes. - // - // As a result, there is a lot of code that is missing the "correct" - // @EnsuresCalledMethodsOnException annotations on its destructors. - // - // This override treats the @EnsuresCalledMethods annotations on destructors as if they - // were also @EnsuresCalledMethodsOnException for backwards compatibility. By overriding - // this method we get both directions of checking: destructor implementations have to - // satisfy these implicit contracts, and destructor callers get to benefit from them. - // - // It should be possible to remove this override entirely without sacrificing any soundness. - // However, that is undesirable at this point because it would be a breaking change. - // - // TODO: gradually remove this override. - // 1. When this override adds an implicit annotation, the Checker Framework should issue - // a warning along with a suggestion to add the right annotations. - // 2. After a few months we should remove this override and require proper annotations on - // all destructors. - - if (isMustCallMethod(methodOrConstructor)) { - Set normalPostconditions = - getContractsFromMethod().getPostconditions(methodOrConstructor); - for (Contract.Postcondition normalPostcondition : normalPostconditions) { - for (String method : getCalledMethods(normalPostcondition.annotation)) { - result.add( - new EnsuresCalledMethodOnExceptionContract( - normalPostcondition.expressionString, method)); + + /** + * Returns true if the given tree has an {@link MustCallAlias} annotation and resource-alias + * tracking is not disabled. + * + * @param tree a tree + * @return true if the given tree has an {@link MustCallAlias} annotation + */ + /*package-private*/ boolean hasMustCallAlias(Tree tree) { + Element elt = TreeUtils.elementFromTree(tree); + return hasMustCallAlias(elt); + } + + /** + * Returns true if the given element has an {@link MustCallAlias} annotation and resource-alias + * tracking is not disabled. + * + * @param elt an element + * @return true if the given element has an {@link MustCallAlias} annotation + */ + /*package-private*/ boolean hasMustCallAlias(Element elt) { + if (noResourceAliases) { + return false; } - } + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + return mustCallAnnotatedTypeFactory.getDeclAnnotationNoAliases(elt, MustCallAlias.class) + != null; + } + + /** + * Returns true if the declaration of the method being invoked has one or more {@link + * CreatesMustCallFor} annotations. + * + * @param node a method invocation node + * @return true iff there is one or more @CreatesMustCallFor annotations on the declaration of + * the invoked method + */ + public boolean hasCreatesMustCallFor(MethodInvocationNode node) { + ExecutableElement decl = TreeUtils.elementFromUse(node.getTree()); + return getDeclAnnotation(decl, CreatesMustCallFor.class) != null + || getDeclAnnotation(decl, CreatesMustCallFor.List.class) != null; + } + + /** + * Does this type factory support {@link CreatesMustCallFor}? + * + * @return true iff the -AnoCreatesMustCallFor command-line argument was not supplied to the + * checker + */ + public boolean canCreateObligations() { + // Precomputing this call to `hasOption` causes a NullPointerException, so leave it as is. + return !checker.hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR); } - return result; - } - - /** - * Returns true iff the {@code MustCall} annotation of the class that encloses the methodTree - * names this method. - * - * @param elt a method - * @return whether that method is one of the must-call methods for its enclosing class - */ - private boolean isMustCallMethod(ExecutableElement elt) { - TypeElement containingClass = ElementUtils.enclosingTypeElement(elt); - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotationMirror mcAnno = - mustCallAnnotatedTypeFactory - .getAnnotatedType(containingClass) - .getAnnotationInHierarchy(mustCallAnnotatedTypeFactory.TOP); - List mcValues = - AnnotationUtils.getElementValueArray( - mcAnno, mustCallAnnotatedTypeFactory.getMustCallValueElement(), String.class); - String methodName = elt.getSimpleName().toString(); - return mcValues.contains(methodName); - } + @Override + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public > + @Nullable T getTypeFactoryOfSubcheckerOrNull( + Class subCheckerClass) { + if (subCheckerClass == MustCallChecker.class) { + if (!canCreateObligations()) { + return super.getTypeFactoryOfSubcheckerOrNull( + MustCallNoCreatesMustCallForChecker.class); + } + } + return super.getTypeFactoryOfSubcheckerOrNull(subCheckerClass); + } + + /** + * Returns the {@link EnsuresCalledMethods.List#value} element. + * + * @return the {@link EnsuresCalledMethods.List#value} element + */ + public ExecutableElement getEnsuresCalledMethodsListValueElement() { + return ensuresCalledMethodsListValueElement; + } + + /** + * Returns the {@link CreatesMustCallFor#value} element. + * + * @return the {@link CreatesMustCallFor#value} element + */ + @Override + public ExecutableElement getCreatesMustCallForValueElement() { + return createsMustCallForValueElement; + } + + /** + * Returns the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} + * element. + * + * @return the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value} + * element + */ + @Override + public ExecutableElement getCreatesMustCallForListValueElement() { + return createsMustCallForListValueElement; + } + + /** + * Does the given element have an {@code @NotOwning} annotation (including in stub files)? + * + *

          Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type + * factory directly, which won't find this annotation in stub files (it only considers stub + * files loaded by this checker, not subcheckers). + * + * @param elt an element + * @return whether there is a NotOwning annotation on the given element + */ + public boolean hasNotOwning(Element elt) { + MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); + return mcatf.getDeclAnnotation(elt, NotOwning.class) != null; + } + + /** + * Does the given element have an {@code @Owning} annotation (including in stub files)? + * + *

          Prefer this method to calling {@link #getDeclAnnotation(Element, Class)} on the type + * factory directly, which won't find this annotation in stub files (it only considers stub + * files loaded by this checker, not subcheckers). + * + * @param elt an element + * @return whether there is an Owning annotation on the given element + */ + public boolean hasOwning(Element elt) { + MustCallAnnotatedTypeFactory mcatf = getTypeFactoryOfSubchecker(MustCallChecker.class); + return mcatf.getDeclAnnotation(elt, Owning.class) != null; + } + + @Override + public Set getExceptionalPostconditions( + ExecutableElement methodOrConstructor) { + Set result = + super.getExceptionalPostconditions(methodOrConstructor); + + // This override is a sneaky way to satisfy a few subtle design constraints: + // 1. The RLC requires destructors to close the class's @Owning fields even on exception + // (see ResourceLeakVisitor.checkOwningField). + // 2. In versions 3.39.0 and earlier, the RLC did not have the annotation + // @EnsuresCalledMethodsOnException, meaning that for destructors it had to treat + // a simple @EnsuresCalledMethods annotation as serving both purposes. + // + // As a result, there is a lot of code that is missing the "correct" + // @EnsuresCalledMethodsOnException annotations on its destructors. + // + // This override treats the @EnsuresCalledMethods annotations on destructors as if they + // were also @EnsuresCalledMethodsOnException for backwards compatibility. By overriding + // this method we get both directions of checking: destructor implementations have to + // satisfy these implicit contracts, and destructor callers get to benefit from them. + // + // It should be possible to remove this override entirely without sacrificing any soundness. + // However, that is undesirable at this point because it would be a breaking change. + // + // TODO: gradually remove this override. + // 1. When this override adds an implicit annotation, the Checker Framework should issue + // a warning along with a suggestion to add the right annotations. + // 2. After a few months we should remove this override and require proper annotations on + // all destructors. + + if (isMustCallMethod(methodOrConstructor)) { + Set normalPostconditions = + getContractsFromMethod().getPostconditions(methodOrConstructor); + for (Contract.Postcondition normalPostcondition : normalPostconditions) { + for (String method : getCalledMethods(normalPostcondition.annotation)) { + result.add( + new EnsuresCalledMethodOnExceptionContract( + normalPostcondition.expressionString, method)); + } + } + } + + return result; + } + + /** + * Returns true iff the {@code MustCall} annotation of the class that encloses the methodTree + * names this method. + * + * @param elt a method + * @return whether that method is one of the must-call methods for its enclosing class + */ + private boolean isMustCallMethod(ExecutableElement elt) { + TypeElement containingClass = ElementUtils.enclosingTypeElement(elt); + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotationMirror mcAnno = + mustCallAnnotatedTypeFactory + .getAnnotatedType(containingClass) + .getAnnotationInHierarchy(mustCallAnnotatedTypeFactory.TOP); + List mcValues = + AnnotationUtils.getElementValueArray( + mcAnno, + mustCallAnnotatedTypeFactory.getMustCallValueElement(), + String.class); + String methodName = elt.getSimpleName().toString(); + return mcValues.contains(methodName); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java index e3005ab940e..e2d24292b90 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java @@ -1,15 +1,7 @@ package org.checkerframework.checker.resourceleak; import com.google.common.collect.ImmutableSet; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic; + import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.mustcall.MustCallChecker; @@ -21,273 +13,295 @@ import org.checkerframework.framework.qual.StubFiles; import org.checkerframework.framework.source.SupportedOptions; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + /** * The entry point for the Resource Leak Checker. This checker is a modifed {@link * CalledMethodsChecker} that checks that the must-call obligations of each expression (as computed * via the {@link org.checkerframework.checker.mustcall.MustCallChecker} have been fulfilled. */ @SupportedOptions({ - "permitStaticOwning", - "permitInitializationLeak", - ResourceLeakChecker.COUNT_MUST_CALL, - ResourceLeakChecker.IGNORED_EXCEPTIONS, - MustCallChecker.NO_CREATES_MUSTCALLFOR, - MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, - MustCallChecker.NO_RESOURCE_ALIASES, - // NO-AFU ResourceLeakChecker.ENABLE_WPI_FOR_RLC, + "permitStaticOwning", + "permitInitializationLeak", + ResourceLeakChecker.COUNT_MUST_CALL, + ResourceLeakChecker.IGNORED_EXCEPTIONS, + MustCallChecker.NO_CREATES_MUSTCALLFOR, + MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP, + MustCallChecker.NO_RESOURCE_ALIASES, + // NO-AFU ResourceLeakChecker.ENABLE_WPI_FOR_RLC, }) @StubFiles("IOUtils.astub") public class ResourceLeakChecker extends CalledMethodsChecker { - /** Creates a ResourceLeakChecker. */ - public ResourceLeakChecker() {} + /** Creates a ResourceLeakChecker. */ + public ResourceLeakChecker() {} - /** - * Command-line option for counting how many must-call obligations were checked by the Resource - * Leak Checker, and emitting the number after processing all files. Used for generating tables - * for a research paper. Not of interest to most users. - */ - public static final String COUNT_MUST_CALL = "countMustCall"; + /** + * Command-line option for counting how many must-call obligations were checked by the Resource + * Leak Checker, and emitting the number after processing all files. Used for generating tables + * for a research paper. Not of interest to most users. + */ + public static final String COUNT_MUST_CALL = "countMustCall"; - /** - * The exception types in this set are ignored in the CFG when determining if a resource leaks - * along an exceptional path. These kinds of errors fall into a few categories: runtime errors, - * errors that the JVM can issue on any statement, and errors that can be prevented by running - * some other CF checker. - */ - private static final SetOfTypes DEFAULT_IGNORED_EXCEPTIONS = - SetOfTypes.anyOfTheseNames( - ImmutableSet.of( - // Any method call has a CFG edge for Throwable/RuntimeException/Error - // to represent run-time misbehavior. Ignore it. - Throwable.class.getCanonicalName(), - Error.class.getCanonicalName(), - RuntimeException.class.getCanonicalName(), - // Use the Nullness Checker to prove this won't happen. - NullPointerException.class.getCanonicalName(), - // These errors can't be predicted statically, so ignore them and assume - // they won't happen. - ClassCircularityError.class.getCanonicalName(), - ClassFormatError.class.getCanonicalName(), - NoClassDefFoundError.class.getCanonicalName(), - OutOfMemoryError.class.getCanonicalName(), - // It's not our problem if the Java type system is wrong. - ClassCastException.class.getCanonicalName(), - // It's not our problem if the code is going to divide by zero. - ArithmeticException.class.getCanonicalName(), - // Use the Index Checker to prevent these errors. - ArrayIndexOutOfBoundsException.class.getCanonicalName(), - NegativeArraySizeException.class.getCanonicalName(), - // Most of the time, this exception is infeasible, as the charset used - // is guaranteed to be present by the Java spec (e.g., "UTF-8"). - // Eventually, this exclusion could be refined by looking at the charset - // being requested. - UnsupportedEncodingException.class.getCanonicalName())); + /** + * The exception types in this set are ignored in the CFG when determining if a resource leaks + * along an exceptional path. These kinds of errors fall into a few categories: runtime errors, + * errors that the JVM can issue on any statement, and errors that can be prevented by running + * some other CF checker. + */ + private static final SetOfTypes DEFAULT_IGNORED_EXCEPTIONS = + SetOfTypes.anyOfTheseNames( + ImmutableSet.of( + // Any method call has a CFG edge for Throwable/RuntimeException/Error + // to represent run-time misbehavior. Ignore it. + Throwable.class.getCanonicalName(), + Error.class.getCanonicalName(), + RuntimeException.class.getCanonicalName(), + // Use the Nullness Checker to prove this won't happen. + NullPointerException.class.getCanonicalName(), + // These errors can't be predicted statically, so ignore them and assume + // they won't happen. + ClassCircularityError.class.getCanonicalName(), + ClassFormatError.class.getCanonicalName(), + NoClassDefFoundError.class.getCanonicalName(), + OutOfMemoryError.class.getCanonicalName(), + // It's not our problem if the Java type system is wrong. + ClassCastException.class.getCanonicalName(), + // It's not our problem if the code is going to divide by zero. + ArithmeticException.class.getCanonicalName(), + // Use the Index Checker to prevent these errors. + ArrayIndexOutOfBoundsException.class.getCanonicalName(), + NegativeArraySizeException.class.getCanonicalName(), + // Most of the time, this exception is infeasible, as the charset used + // is guaranteed to be present by the Java spec (e.g., "UTF-8"). + // Eventually, this exclusion could be refined by looking at the charset + // being requested. + UnsupportedEncodingException.class.getCanonicalName())); - /** - * Command-line option for controlling which exceptions are ignored. - * - * @see #DEFAULT_IGNORED_EXCEPTIONS - * @see #getIgnoredExceptions() - */ - public static final String IGNORED_EXCEPTIONS = "resourceLeakIgnoredExceptions"; + /** + * Command-line option for controlling which exceptions are ignored. + * + * @see #DEFAULT_IGNORED_EXCEPTIONS + * @see #getIgnoredExceptions() + */ + public static final String IGNORED_EXCEPTIONS = "resourceLeakIgnoredExceptions"; - /** - * A pattern that matches one or more consecutive commas, optionally preceded and followed by - * whitespace. - */ - private static final Pattern COMMAS = Pattern.compile("\\s*(?:" + Pattern.quote(",") + "\\s*)+"); + /** + * A pattern that matches one or more consecutive commas, optionally preceded and followed by + * whitespace. + */ + private static final Pattern COMMAS = + Pattern.compile("\\s*(?:" + Pattern.quote(",") + "\\s*)+"); - /** - * A pattern that matches an exception specifier for the {@link #IGNORED_EXCEPTIONS} option: an - * optional "=" followed by a qualified name. The whole thing can be padded with whitespace. - */ - private static final Pattern EXCEPTION_SPECIFIER = - Pattern.compile( - "^\\s*" + "(" + Pattern.quote("=") + "\\s*" + ")?" + "(\\w+(?:\\.\\w+)*)" + "\\s*$"); + /** + * A pattern that matches an exception specifier for the {@link #IGNORED_EXCEPTIONS} option: an + * optional "=" followed by a qualified name. The whole thing can be padded with whitespace. + */ + private static final Pattern EXCEPTION_SPECIFIER = + Pattern.compile( + "^\\s*" + + "(" + + Pattern.quote("=") + + "\\s*" + + ")?" + + "(\\w+(?:\\.\\w+)*)" + + "\\s*$"); - /* NO-AFU - * Ordinarily, when the -Ainfer flag is used, whole-program inference is run for every checker - * and sub-checker. However, the Resource Leak Checker is different. The -Ainfer flag enables - * the RLC's own (non-WPI) inference mechanism ({@link MustCallInference}). To use WPI in - * addition to this mechanism for its sub-checkers, use the -AenableWpiForRlc flag, which is - * intended only for testing and experiments. - * - public static final String ENABLE_WPI_FOR_RLC = "enableWpiForRlc"; - */ + /* NO-AFU + * Ordinarily, when the -Ainfer flag is used, whole-program inference is run for every checker + * and sub-checker. However, the Resource Leak Checker is different. The -Ainfer flag enables + * the RLC's own (non-WPI) inference mechanism ({@link MustCallInference}). To use WPI in + * addition to this mechanism for its sub-checkers, use the -AenableWpiForRlc flag, which is + * intended only for testing and experiments. + * + public static final String ENABLE_WPI_FOR_RLC = "enableWpiForRlc"; + */ - /** - * The number of expressions with must-call obligations that were checked. Incremented only if the - * {@link #COUNT_MUST_CALL} command-line option was supplied. - */ - /*package-private*/ int numMustCall = 0; + /** + * The number of expressions with must-call obligations that were checked. Incremented only if + * the {@link #COUNT_MUST_CALL} command-line option was supplied. + */ + /*package-private*/ int numMustCall = 0; - /** - * The number of must-call-related errors issued. The count of verified must-call expressions is - * the difference between this and {@link #numMustCall}. - */ - private int numMustCallFailed = 0; + /** + * The number of must-call-related errors issued. The count of verified must-call expressions is + * the difference between this and {@link #numMustCall}. + */ + private int numMustCallFailed = 0; - /** - * The cached set of ignored exceptions parsed from {@link #IGNORED_EXCEPTIONS}. Caching this - * field prevents the checker from issuing duplicate warnings about missing exception types. - * - * @see #getIgnoredExceptions() - */ - private @MonotonicNonNull SetOfTypes ignoredExceptions = null; + /** + * The cached set of ignored exceptions parsed from {@link #IGNORED_EXCEPTIONS}. Caching this + * field prevents the checker from issuing duplicate warnings about missing exception types. + * + * @see #getIgnoredExceptions() + */ + private @MonotonicNonNull SetOfTypes ignoredExceptions = null; - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); - if (this.processingEnv.getOptions().containsKey(MustCallChecker.NO_CREATES_MUSTCALLFOR)) { - checkers.add(MustCallNoCreatesMustCallForChecker.class); - } else { - checkers.add(MustCallChecker.class); - } + if (this.processingEnv.getOptions().containsKey(MustCallChecker.NO_CREATES_MUSTCALLFOR)) { + checkers.add(MustCallNoCreatesMustCallForChecker.class); + } else { + checkers.add(MustCallChecker.class); + } - return checkers; - } + return checkers; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ResourceLeakVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ResourceLeakVisitor(this); + } - @Override - public void reportError( - @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { - if (messageKey.equals("required.method.not.called")) { - // This is safe because of the message key. - String qualifiedTypeName = (String) args[1]; - // Only count classes in the JDK, not user-defined classes. - if (MustCallConsistencyAnalyzer.isJdkClass(qualifiedTypeName)) { - numMustCallFailed++; - } + @Override + public void reportError( + @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { + if (messageKey.equals("required.method.not.called")) { + // This is safe because of the message key. + String qualifiedTypeName = (String) args[1]; + // Only count classes in the JDK, not user-defined classes. + if (MustCallConsistencyAnalyzer.isJdkClass(qualifiedTypeName)) { + numMustCallFailed++; + } + } + super.reportError(source, messageKey, args); } - super.reportError(source, messageKey, args); - } - @Override - public void typeProcessingOver() { - if (hasOption(COUNT_MUST_CALL)) { - message(Diagnostic.Kind.WARNING, "Found %d must call obligation(s).%n", numMustCall); - message( - Diagnostic.Kind.WARNING, - "Successfully verified %d must call obligation(s).%n", - numMustCall - numMustCallFailed); + @Override + public void typeProcessingOver() { + if (hasOption(COUNT_MUST_CALL)) { + message(Diagnostic.Kind.WARNING, "Found %d must call obligation(s).%n", numMustCall); + message( + Diagnostic.Kind.WARNING, + "Successfully verified %d must call obligation(s).%n", + numMustCall - numMustCallFailed); + } + super.typeProcessingOver(); } - super.typeProcessingOver(); - } - /** - * Get the set of exceptions that should be ignored. This set comes from the {@link - * #IGNORED_EXCEPTIONS} option if it was provided, or {@link #DEFAULT_IGNORED_EXCEPTIONS} if not. - * - * @return the set of exceptions to ignore - */ - public SetOfTypes getIgnoredExceptions() { - SetOfTypes result = ignoredExceptions; - if (result == null) { - String ignoredExceptionsOptionValue = getOption(IGNORED_EXCEPTIONS); - result = - ignoredExceptionsOptionValue == null - ? DEFAULT_IGNORED_EXCEPTIONS - : parseIgnoredExceptions(ignoredExceptionsOptionValue); - ignoredExceptions = result; + /** + * Get the set of exceptions that should be ignored. This set comes from the {@link + * #IGNORED_EXCEPTIONS} option if it was provided, or {@link #DEFAULT_IGNORED_EXCEPTIONS} if + * not. + * + * @return the set of exceptions to ignore + */ + public SetOfTypes getIgnoredExceptions() { + SetOfTypes result = ignoredExceptions; + if (result == null) { + String ignoredExceptionsOptionValue = getOption(IGNORED_EXCEPTIONS); + result = + ignoredExceptionsOptionValue == null + ? DEFAULT_IGNORED_EXCEPTIONS + : parseIgnoredExceptions(ignoredExceptionsOptionValue); + ignoredExceptions = result; + } + return result; } - return result; - } - /** - * Parse the argument given for the {@link #IGNORED_EXCEPTIONS} option. Warnings will be issued - * for any problems in the argument, for instance if any of the named exceptions cannot be found. - * - * @param ignoredExceptionsOptionValue the value given for {@link #IGNORED_EXCEPTIONS} - * @return the set of ignored exceptions - */ - protected SetOfTypes parseIgnoredExceptions(String ignoredExceptionsOptionValue) { - String[] exceptions = COMMAS.split(ignoredExceptionsOptionValue); - List sets = new ArrayList<>(); - for (String e : exceptions) { - SetOfTypes set = parseExceptionSpecifier(e, ignoredExceptionsOptionValue); - if (set != null) { - sets.add(set); - } + /** + * Parse the argument given for the {@link #IGNORED_EXCEPTIONS} option. Warnings will be issued + * for any problems in the argument, for instance if any of the named exceptions cannot be + * found. + * + * @param ignoredExceptionsOptionValue the value given for {@link #IGNORED_EXCEPTIONS} + * @return the set of ignored exceptions + */ + protected SetOfTypes parseIgnoredExceptions(String ignoredExceptionsOptionValue) { + String[] exceptions = COMMAS.split(ignoredExceptionsOptionValue); + List sets = new ArrayList<>(); + for (String e : exceptions) { + SetOfTypes set = parseExceptionSpecifier(e, ignoredExceptionsOptionValue); + if (set != null) { + sets.add(set); + } + } + return SetOfTypes.union(sets.toArray(new SetOfTypes[0])); } - return SetOfTypes.union(sets.toArray(new SetOfTypes[0])); - } - /** - * Parse a single exception specifier from the {@link #IGNORED_EXCEPTIONS} option and issue - * warnings if it does not parse. See {@link #EXCEPTION_SPECIFIER} for a description of the - * syntax. - * - * @param exceptionSpecifier the exception specifier to parse - * @param ignoredExceptionsOptionValue the whole value of the {@link #IGNORED_EXCEPTIONS} option; - * only used for error reporting - * @return the parsed set of types, or null if the value does not parse - */ - @SuppressWarnings({ - // user input might not be a legal @CanonicalName, but it should be safe to pass to - // `SetOfTypes.anyOfTheseNames` - "signature:type.arguments.not.inferred", - }) - protected @Nullable SetOfTypes parseExceptionSpecifier( - String exceptionSpecifier, String ignoredExceptionsOptionValue) { - Matcher m = EXCEPTION_SPECIFIER.matcher(exceptionSpecifier); - if (m.matches()) { - @Nullable String equalsSign = m.group(1); - String qualifiedName = m.group(2); + /** + * Parse a single exception specifier from the {@link #IGNORED_EXCEPTIONS} option and issue + * warnings if it does not parse. See {@link #EXCEPTION_SPECIFIER} for a description of the + * syntax. + * + * @param exceptionSpecifier the exception specifier to parse + * @param ignoredExceptionsOptionValue the whole value of the {@link #IGNORED_EXCEPTIONS} + * option; only used for error reporting + * @return the parsed set of types, or null if the value does not parse + */ + @SuppressWarnings({ + // user input might not be a legal @CanonicalName, but it should be safe to pass to + // `SetOfTypes.anyOfTheseNames` + "signature:type.arguments.not.inferred", + }) + protected @Nullable SetOfTypes parseExceptionSpecifier( + String exceptionSpecifier, String ignoredExceptionsOptionValue) { + Matcher m = EXCEPTION_SPECIFIER.matcher(exceptionSpecifier); + if (m.matches()) { + @Nullable String equalsSign = m.group(1); + String qualifiedName = m.group(2); - if (qualifiedName.equalsIgnoreCase("default")) { - return DEFAULT_IGNORED_EXCEPTIONS; - } - TypeMirror type = checkCanonicalName(qualifiedName); - if (type == null) { - // There is a chance that the user named a real type, but the class is not - // accessible for some reason. We'll issue a warning (in case this was a typo) but - // add the type as ignored anyway (in case it's just an inaccessible type). - // - // Note that if the user asked to ignore subtypes of this exception, this code won't - // do it because we can't know what those subtypes are. We have to treat this as if - // it were "=qualifiedName" even if no equals sign was provided. - message( - Diagnostic.Kind.WARNING, - "The exception '%s' appears in the -A%s=%s option, but it does not seem to exist", - exceptionSpecifier, - IGNORED_EXCEPTIONS, - ignoredExceptionsOptionValue); - return SetOfTypes.anyOfTheseNames(ImmutableSet.of(qualifiedName)); - } else { - return equalsSign == null ? SetOfTypes.allSubtypes(type) : SetOfTypes.singleton(type); - } - } else if (!exceptionSpecifier.trim().isEmpty()) { - message( - Diagnostic.Kind.WARNING, - "The string '%s' appears in the -A%s=%s option," - + " but it is not a legal exception specifier", - exceptionSpecifier, - IGNORED_EXCEPTIONS, - ignoredExceptionsOptionValue); + if (qualifiedName.equalsIgnoreCase("default")) { + return DEFAULT_IGNORED_EXCEPTIONS; + } + TypeMirror type = checkCanonicalName(qualifiedName); + if (type == null) { + // There is a chance that the user named a real type, but the class is not + // accessible for some reason. We'll issue a warning (in case this was a typo) but + // add the type as ignored anyway (in case it's just an inaccessible type). + // + // Note that if the user asked to ignore subtypes of this exception, this code won't + // do it because we can't know what those subtypes are. We have to treat this as if + // it were "=qualifiedName" even if no equals sign was provided. + message( + Diagnostic.Kind.WARNING, + "The exception '%s' appears in the -A%s=%s option, but it does not seem to exist", + exceptionSpecifier, + IGNORED_EXCEPTIONS, + ignoredExceptionsOptionValue); + return SetOfTypes.anyOfTheseNames(ImmutableSet.of(qualifiedName)); + } else { + return equalsSign == null + ? SetOfTypes.allSubtypes(type) + : SetOfTypes.singleton(type); + } + } else if (!exceptionSpecifier.trim().isEmpty()) { + message( + Diagnostic.Kind.WARNING, + "The string '%s' appears in the -A%s=%s option," + + " but it is not a legal exception specifier", + exceptionSpecifier, + IGNORED_EXCEPTIONS, + ignoredExceptionsOptionValue); + } + return null; } - return null; - } - /** - * Check if the given String refers to an actual type. - * - * @param s any string - * @return the referenced type, or null if it does not exist - */ - @SuppressWarnings({ - "signature:argument", // `s` is not a qualified name, but we pass it to getTypeElement - }) - protected @Nullable TypeMirror checkCanonicalName(String s) { - TypeElement elem = getProcessingEnvironment().getElementUtils().getTypeElement(s); - if (elem == null) { - return null; + /** + * Check if the given String refers to an actual type. + * + * @param s any string + * @return the referenced type, or null if it does not exist + */ + @SuppressWarnings({ + "signature:argument", // `s` is not a qualified name, but we pass it to getTypeElement + }) + protected @Nullable TypeMirror checkCanonicalName(String s) { + TypeElement elem = getProcessingEnvironment().getElementUtils().getTypeElement(s); + if (elem == null) { + return null; + } + return types.getDeclaredType(elem); } - return types.getDeclaredType(elem); - } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java index 4817965da02..91672e523f4 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakTransfer.java @@ -1,7 +1,5 @@ package org.checkerframework.checker.resourceleak; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.calledmethods.CalledMethodsTransfer; import org.checkerframework.checker.mustcall.CreatesMustCallForToJavaExpression; import org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory; @@ -19,147 +17,152 @@ import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.javacutil.TypesUtils; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + /** The transfer function for the resource-leak extension to the called-methods type system. */ public class ResourceLeakTransfer extends CalledMethodsTransfer { - /** - * Shadowed because we must dispatch to the Resource Leak Checker's version of - * getTypefactoryOfSubchecker to get the correct MustCallAnnotatedTypeFactory. - */ - private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; - - /** - * Create a new resource leak transfer function. - * - * @param analysis the analysis. Its type factory must be a {@link - * ResourceLeakAnnotatedTypeFactory}. - */ - public ResourceLeakTransfer(ResourceLeakAnalysis analysis) { - super(analysis); - this.rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) analysis.getTypeFactory(); - } - - @Override - public TransferResult visitTernaryExpression( - TernaryExpressionNode node, TransferInput input) { - TransferResult result = - super.visitTernaryExpression(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - rlTypeFactory.addTempVar(node.getTernaryExpressionVar(), node.getTree()); - } - return result; - } - - @Override - public TransferResult visitSwitchExpressionNode( - SwitchExpressionNode node, TransferInput input) { - TransferResult result = - super.visitSwitchExpressionNode(node, input); - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - // Add the synthetic variable created during CFG construction to the temporary - // variable map (rather than creating a redundant temp var) - rlTypeFactory.addTempVar(node.getSwitchExpressionVar(), node.getTree()); + /** + * Shadowed because we must dispatch to the Resource Leak Checker's version of + * getTypefactoryOfSubchecker to get the correct MustCallAnnotatedTypeFactory. + */ + private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; + + /** + * Create a new resource leak transfer function. + * + * @param analysis the analysis. Its type factory must be a {@link + * ResourceLeakAnnotatedTypeFactory}. + */ + public ResourceLeakTransfer(ResourceLeakAnalysis analysis) { + super(analysis); + this.rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) analysis.getTypeFactory(); } - return result; - } - - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput input) { - - TransferResult result = - super.visitMethodInvocation(node, input); - - handleCreatesMustCallFor(node, result); - updateStoreWithTempVar(result, node); - - // If there is a temporary variable for the receiver, update its type. - Node receiver = node.getTarget().getReceiver(); - MustCallAnnotatedTypeFactory mcAtf = - rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - Node accumulationTarget = mcAtf.getTempVar(receiver); - if (accumulationTarget != null) { - String methodName = node.getTarget().getMethod().getSimpleName().toString(); - methodName = rlTypeFactory.adjustMethodNameUsingValueChecker(methodName, node.getTree()); - accumulate(accumulationTarget, result, methodName); + + @Override + public TransferResult visitTernaryExpression( + TernaryExpressionNode node, TransferInput input) { + TransferResult result = + super.visitTernaryExpression(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + rlTypeFactory.addTempVar(node.getTernaryExpressionVar(), node.getTree()); + } + return result; } - return result; - } - - /** - * Clears the called-methods store of all information about the target if an @CreatesMustCallFor - * method is invoked and the type factory can create obligations. Otherwise, does nothing. - * - * @param n a method invocation - * @param result the transfer result whose stores should be cleared of information - */ - private void handleCreatesMustCallFor( - MethodInvocationNode n, TransferResult result) { - if (!rlTypeFactory.canCreateObligations()) { - return; + @Override + public TransferResult visitSwitchExpressionNode( + SwitchExpressionNode node, TransferInput input) { + TransferResult result = + super.visitSwitchExpressionNode(node, input); + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + // Add the synthetic variable created during CFG construction to the temporary + // variable map (rather than creating a redundant temp var) + rlTypeFactory.addTempVar(node.getSwitchExpressionVar(), node.getTree()); + } + return result; } - List targetExprs = - CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( - n, rlTypeFactory, rlTypeFactory); - AnnotationMirror defaultType = rlTypeFactory.top; - for (JavaExpression targetExpr : targetExprs) { - AccumulationValue defaultTypeValue = - analysis.createSingleAnnotationValue(defaultType, targetExpr.getType()); - if (result.containsTwoStores()) { - result.getThenStore().replaceValue(targetExpr, defaultTypeValue); - result.getElseStore().replaceValue(targetExpr, defaultTypeValue); - } else { - result.getRegularStore().replaceValue(targetExpr, defaultTypeValue); - } + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput input) { + + TransferResult result = + super.visitMethodInvocation(node, input); + + handleCreatesMustCallFor(node, result); + updateStoreWithTempVar(result, node); + + // If there is a temporary variable for the receiver, update its type. + Node receiver = node.getTarget().getReceiver(); + MustCallAnnotatedTypeFactory mcAtf = + rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + Node accumulationTarget = mcAtf.getTempVar(receiver); + if (accumulationTarget != null) { + String methodName = node.getTarget().getMethod().getSimpleName().toString(); + methodName = + rlTypeFactory.adjustMethodNameUsingValueChecker(methodName, node.getTree()); + accumulate(accumulationTarget, result, methodName); + } + + return result; } - } - - @Override - public TransferResult visitObjectCreation( - ObjectCreationNode node, TransferInput input) { - TransferResult result = - super.visitObjectCreation(node, input); - updateStoreWithTempVar(result, node); - return result; - } - - /** - * This method either creates or looks up the temp var t for node, and then updates the store to - * give t the same type as node. Temporary variables are supported for expressions throughout this - * checker (and the Must Call Checker) to enable refinement of their types. See the documentation - * of {@link MustCallConsistencyAnalyzer} for more details. - * - * @param node the node to be assigned to a temporary variable - * @param result the transfer result containing the store to be modified - */ - public void updateStoreWithTempVar( - TransferResult result, Node node) { - // Must-call obligations on primitives are not supported. - if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { - MustCallAnnotatedTypeFactory mcAtf = - rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - LocalVariableNode temp = mcAtf.getTempVar(node); - if (temp != null) { - rlTypeFactory.addTempVar(temp, node.getTree()); - JavaExpression localExp = JavaExpression.fromNode(temp); - AnnotationMirror anm = - rlTypeFactory - .getAnnotatedType(node.getTree()) - .getAnnotationInHierarchy(rlTypeFactory.top); - if (anm == null) { - anm = rlTypeFactory.top; + + /** + * Clears the called-methods store of all information about the target if an @CreatesMustCallFor + * method is invoked and the type factory can create obligations. Otherwise, does nothing. + * + * @param n a method invocation + * @param result the transfer result whose stores should be cleared of information + */ + private void handleCreatesMustCallFor( + MethodInvocationNode n, TransferResult result) { + if (!rlTypeFactory.canCreateObligations()) { + return; } - if (result.containsTwoStores()) { - result.getThenStore().insertValue(localExp, anm); - result.getElseStore().insertValue(localExp, anm); - } else { - result.getRegularStore().insertValue(localExp, anm); + + List targetExprs = + CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtInvocation( + n, rlTypeFactory, rlTypeFactory); + AnnotationMirror defaultType = rlTypeFactory.top; + for (JavaExpression targetExpr : targetExprs) { + AccumulationValue defaultTypeValue = + analysis.createSingleAnnotationValue(defaultType, targetExpr.getType()); + if (result.containsTwoStores()) { + result.getThenStore().replaceValue(targetExpr, defaultTypeValue); + result.getElseStore().replaceValue(targetExpr, defaultTypeValue); + } else { + result.getRegularStore().replaceValue(targetExpr, defaultTypeValue); + } + } + } + + @Override + public TransferResult visitObjectCreation( + ObjectCreationNode node, TransferInput input) { + TransferResult result = + super.visitObjectCreation(node, input); + updateStoreWithTempVar(result, node); + return result; + } + + /** + * This method either creates or looks up the temp var t for node, and then updates the store to + * give t the same type as node. Temporary variables are supported for expressions throughout + * this checker (and the Must Call Checker) to enable refinement of their types. See the + * documentation of {@link MustCallConsistencyAnalyzer} for more details. + * + * @param node the node to be assigned to a temporary variable + * @param result the transfer result containing the store to be modified + */ + public void updateStoreWithTempVar( + TransferResult result, Node node) { + // Must-call obligations on primitives are not supported. + if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) { + MustCallAnnotatedTypeFactory mcAtf = + rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + LocalVariableNode temp = mcAtf.getTempVar(node); + if (temp != null) { + rlTypeFactory.addTempVar(temp, node.getTree()); + JavaExpression localExp = JavaExpression.fromNode(temp); + AnnotationMirror anm = + rlTypeFactory + .getAnnotatedType(node.getTree()) + .getAnnotationInHierarchy(rlTypeFactory.top); + if (anm == null) { + anm = rlTypeFactory.top; + } + if (result.containsTwoStores()) { + result.getThenStore().insertValue(localExp, anm); + result.getElseStore().insertValue(localExp, anm); + } else { + result.getRegularStore().insertValue(localExp, anm); + } + } } - } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java index f0de9088d76..899b556b3d1 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java @@ -2,18 +2,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.calledmethods.CalledMethodsVisitor; import org.checkerframework.checker.calledmethods.EnsuresCalledMethodOnExceptionContract; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; @@ -39,6 +28,20 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; + /** * The visitor for the Resource Leak Checker. Responsible for checking that the rules for {@link * Owning} fields are satisfied, and for checking that {@link CreatesMustCallFor} overrides are @@ -46,619 +49,632 @@ */ public class ResourceLeakVisitor extends CalledMethodsVisitor { - /** True if errors related to static owning fields should be suppressed. */ - private final boolean permitStaticOwning; - - /** - * Because CalledMethodsVisitor doesn't have a type parameter, we need a reference to the type - * factory that has this static type to access the features that ResourceLeakAnnotatedTypeFactory - * implements but CalledMethodsAnnotatedTypeFactory does not. - */ - private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; - - /** True if -AnoLightweightOwnership was supplied on the command line. */ - private final boolean noLightweightOwnership; - - /* NO-AFU - * True if -AenableWpiForRlc was passed on the command line. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - private final boolean enableWpiForRlc; - */ - - /** - * Create the visitor. - * - * @param checker the type-checker associated with this visitor - */ - public ResourceLeakVisitor(BaseTypeChecker checker) { - super(checker); - rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) atypeFactory; - permitStaticOwning = checker.hasOption("permitStaticOwning"); - noLightweightOwnership = checker.hasOption("noLightweightOwnership"); - // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); - } - - @Override - protected ResourceLeakAnnotatedTypeFactory createTypeFactory() { - return new ResourceLeakAnnotatedTypeFactory(checker); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); - MustCallAnnotatedTypeFactory mcAtf = - rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); - List cmcfValues = getCreatesMustCallForValues(elt, mcAtf, rlTypeFactory); - if (!cmcfValues.isEmpty()) { - checkCreatesMustCallForOverrides(tree, elt, mcAtf, cmcfValues); - checkCreatesMustCallForTargetsHaveNonEmptyMustCall(tree, mcAtf); - } - checkOwningOverrides(tree, elt, mcAtf); - if (TreeUtils.isConstructor(tree)) { - checkMustCallAliasAnnotationForConstructor(tree); - } else { - checkMustCallAliasAnnotationForMethod(tree, mcAtf); - } - return super.visitMethod(tree, p); - } - - /** - * checks that any created must-call obligation has a declared type with a non-empty - * {@code @MustCall} obligation - * - * @param tree the method - * @param mcAtf the type factory - */ - private void checkCreatesMustCallForTargetsHaveNonEmptyMustCall( - MethodTree tree, MustCallAnnotatedTypeFactory mcAtf) { - // Get all the JavaExpressions for all CreatesMustCallFor annotations - List createsMustCallExprs = - CreatesMustCallForToJavaExpression.getCreatesMustCallForExpressionsAtMethodDeclaration( - tree, mcAtf, mcAtf); - for (JavaExpression targetExpr : createsMustCallExprs) { - AnnotationMirror mustCallAnno = - mcAtf - .getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) - .getAnnotationInHierarchy(mcAtf.TOP); - if (rlTypeFactory.getMustCallValues(mustCallAnno).isEmpty()) { - checker.reportError( - tree, - "creates.mustcall.for.invalid.target", - targetExpr.toString(), - targetExpr.getType().toString()); - } + /** True if errors related to static owning fields should be suppressed. */ + private final boolean permitStaticOwning; + + /** + * Because CalledMethodsVisitor doesn't have a type parameter, we need a reference to the type + * factory that has this static type to access the features that + * ResourceLeakAnnotatedTypeFactory implements but CalledMethodsAnnotatedTypeFactory does not. + */ + private final ResourceLeakAnnotatedTypeFactory rlTypeFactory; + + /** True if -AnoLightweightOwnership was supplied on the command line. */ + private final boolean noLightweightOwnership; + + /* NO-AFU + * True if -AenableWpiForRlc was passed on the command line. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + private final boolean enableWpiForRlc; + */ + + /** + * Create the visitor. + * + * @param checker the type-checker associated with this visitor + */ + public ResourceLeakVisitor(BaseTypeChecker checker) { + super(checker); + rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) atypeFactory; + permitStaticOwning = checker.hasOption("permitStaticOwning"); + noLightweightOwnership = checker.hasOption("noLightweightOwnership"); + // enableWpiForRlc = checker.hasOption(ResourceLeakChecker.ENABLE_WPI_FOR_RLC); } - } - - /** - * Check that an overriding method does not reduce the number of created must-call obligations - * - * @param tree overriding method - * @param elt element for overriding method - * @param mcAtf the type factory - * @param cmcfValues must call values created by overriding method - */ - private void checkCreatesMustCallForOverrides( - MethodTree tree, - ExecutableElement elt, - MustCallAnnotatedTypeFactory mcAtf, - List cmcfValues) { - // If this method overrides another method, it must create at least as many - // obligations. Without this check, dynamic dispatch might allow e.g. a field to be - // overwritten by a CMCF method, but the CMCF effect wouldn't occur. - for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(elt, this.types)) { - List overriddenCmcfValues = - getCreatesMustCallForValues(overridden, mcAtf, rlTypeFactory); - if (!overriddenCmcfValues.containsAll(cmcfValues)) { - String foundCmcfValueString = String.join(", ", cmcfValues); - String neededCmcfValueString = String.join(", ", overriddenCmcfValues); - String actualClassname = ElementUtils.getEnclosingClassName(elt); - String overriddenClassname = ElementUtils.getEnclosingClassName(overridden); - checker.reportError( - tree, - "creates.mustcall.for.override.invalid", - actualClassname + "#" + elt, - overriddenClassname + "#" + overridden, - foundCmcfValueString, - neededCmcfValueString); - } + + @Override + protected ResourceLeakAnnotatedTypeFactory createTypeFactory() { + return new ResourceLeakAnnotatedTypeFactory(checker); } - } - - /** - * Checks that overrides respect behavioral subtyping for @Owning and @NotOwning annotations. In - * particular, checks that 1) if an overridden method has an @Owning parameter, then that - * parameter is @Owning in the overrider, and 2) if an overridden method has an @NotOwning return, - * then the overrider also has an @NotOwning return. - * - * @param tree overriding method, for error reporting - * @param overrider element for overriding method - * @param mcAtf the type factory - */ - private void checkOwningOverrides( - MethodTree tree, ExecutableElement overrider, MustCallAnnotatedTypeFactory mcAtf) { - for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(overrider, this.types)) { - // Check for @Owning parameters. Must use an explicitly-indexed for loop so that the - // same parameter index can be accessed in the overrider's parameter list, which is - // the same length. - for (int i = 0; i < overridden.getParameters().size(); i++) { - if (mcAtf.getDeclAnnotation(overridden.getParameters().get(i), Owning.class) != null) { - if (mcAtf.getDeclAnnotation(overrider.getParameters().get(i), Owning.class) == null) { - checker.reportError( - tree, - "owning.override.param", - overrider.getParameters().get(i).getSimpleName().toString(), - overrider.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overrider), - overridden.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overridden)); - } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); + MustCallAnnotatedTypeFactory mcAtf = + rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class); + List cmcfValues = getCreatesMustCallForValues(elt, mcAtf, rlTypeFactory); + if (!cmcfValues.isEmpty()) { + checkCreatesMustCallForOverrides(tree, elt, mcAtf, cmcfValues); + checkCreatesMustCallForTargetsHaveNonEmptyMustCall(tree, mcAtf); } - } - // Check for @NotOwning returns. - if (mcAtf.getDeclAnnotation(overridden, NotOwning.class) != null - && mcAtf.getDeclAnnotation(overrider, NotOwning.class) == null) { - checker.reportError( - tree, - "owning.override.return", - overrider.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overrider), - overridden.getSimpleName().toString(), - ElementUtils.getEnclosingClassName(overridden)); - } - } - } - - /** - * If a {@code @MustCallAlias} annotation appears in a method declaration, it must appear as an - * annotation on both the return type, and a parameter type. - * - *

          The return type is checked if it is annotated with {@code @PolyMustCall} because the Must - * Call Checker treats {@code @MustCallAlias} as an alias of {@code @PolyMustCall}. - * - * @param tree the method declaration - * @param mcAtf the MustCallAnnotatedTypeFactory - */ - private void checkMustCallAliasAnnotationForMethod( - MethodTree tree, MustCallAnnotatedTypeFactory mcAtf) { - - Element paramWithMustCallAliasAnno = getParameterWithMustCallAliasAnno(tree); - boolean isMustCallAliasAnnoOnParameter = paramWithMustCallAliasAnno != null; - - if (TreeUtils.isVoidReturn(tree) && isMustCallAliasAnnoOnParameter) { - checker.reportWarning( - tree, "mustcallalias.method.return.and.param", "this method has a void return"); - return; + checkOwningOverrides(tree, elt, mcAtf); + if (TreeUtils.isConstructor(tree)) { + checkMustCallAliasAnnotationForConstructor(tree); + } else { + checkMustCallAliasAnnotationForMethod(tree, mcAtf); + } + return super.visitMethod(tree, p); } - AnnotatedTypeMirror returnType = mcAtf.getMethodReturnType(tree); - boolean isMustCallAliasAnnoOnReturnType = returnType.hasAnnotation(PolyMustCall.class); - checkMustCallAliasAnnoMismatch( - paramWithMustCallAliasAnno, isMustCallAliasAnnoOnReturnType, tree); - } - - /** - * Given a constructor, a {@code @MustCallAlias} must appear in both the list of parameters and as - * an annotation on the constructor itself, if it is to appear at all. - * - *

          That is, a {@code @MustCallAlias} annotation must appear on both the constructor and its - * parameter list, or not at all. - * - * @param tree the constructor - */ - private void checkMustCallAliasAnnotationForConstructor(MethodTree tree) { - ExecutableElement constructorDecl = TreeUtils.elementFromDeclaration(tree); - boolean isMustCallAliasAnnoOnConstructor = - constructorDecl != null && rlTypeFactory.hasMustCallAlias(constructorDecl); - Element paramWithMustCallAliasAnno = getParameterWithMustCallAliasAnno(tree); - checkMustCallAliasAnnoMismatch( - paramWithMustCallAliasAnno, isMustCallAliasAnnoOnConstructor, tree); - } - - /** - * Construct the warning message for the case where a {@code @MustCallAlias} annotation does not - * appear in pairs in a method or constructor declaration. - * - *

          If a parameter of a method or a constructor is annotated with a {@code @MustCallAlias} - * annotation, the return type (for a method) should also be annotated with - * {@code @MustCallAlias}. In the case of a constructor, which has no return type, a - * {@code @MustCallAlias} annotation must appear on its declaration. - * - * @param paramWithMustCallAliasAnno a parameter with a {@code @MustCallAlias} annotation, null if - * there are none - * @param isMustCallAliasAnnoOnMethodOrConstructorDecl true if and only if a - * {@code @MustCallAlias} annotation appears on a method or constructor declaration - * @param tree the method or constructor declaration - */ - private void checkMustCallAliasAnnoMismatch( - @Nullable Element paramWithMustCallAliasAnno, - boolean isMustCallAliasAnnoOnMethodOrConstructorDecl, - MethodTree tree) { - boolean isMustCallAliasAnnotationOnParameter = paramWithMustCallAliasAnno != null; - if (isMustCallAliasAnnotationOnParameter != isMustCallAliasAnnoOnMethodOrConstructorDecl) { - String locationOfCheck = TreeUtils.isClassTree(tree) ? "this constructor" : "the return type"; - String message = - isMustCallAliasAnnotationOnParameter - ? String.format( - "there is no @MustCallAlias annotation on %s, even though the parameter %s is" - + " annotated with @MustCallAlias", - locationOfCheck, paramWithMustCallAliasAnno) - : "no parameter has a @MustCallAlias annotation, even though the return type is" - + " annotated with @MustCallAlias"; - checker.reportWarning(tree, "mustcallalias.method.return.and.param", message); - } - } - - /** - * Given a method and its parameter list, look through each of the parameters and see if any are - * annotated with the {@code @MustCallAlias} annotation. - * - *

          Return the first parameter that is annotated with {@code @MustCallAlias}, otherwise return - * null. - * - * @param tree the method declaration - * @return the first parameter that is annotated with {@code @MustCallAlias}, otherwise return - * null - */ - private @Nullable Element getParameterWithMustCallAliasAnno(MethodTree tree) { - VariableTree receiverParameter = tree.getReceiverParameter(); - if (receiverParameter != null && rlTypeFactory.hasMustCallAlias(receiverParameter)) { - return TreeUtils.elementFromDeclaration(receiverParameter); - } - for (VariableTree param : tree.getParameters()) { - if (rlTypeFactory.hasMustCallAlias(param)) { - return TreeUtils.elementFromDeclaration(param); - } - } - return null; - } - - /* NO-AFU - @Override - protected boolean shouldPerformContractInference() { - return atypeFactory.getWholeProgramInference() != null && isWpiEnabledForRLC(); - } - */ - - /** - * Returns the {@link CreatesMustCallFor#value} element/argument of the given @CreatesMustCallFor - * annotation, or "this" if there is none. - * - *

          Does not vipewpoint-adaptation. - * - * @param createsMustCallFor an @CreatesMustCallFor annotation - * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element - * @return the string value - */ - private static String getCreatesMustCallForValue( - AnnotationMirror createsMustCallFor, MustCallAnnotatedTypeFactory mcAtf) { - return AnnotationUtils.getElementValue( - createsMustCallFor, mcAtf.getCreatesMustCallForValueElement(), String.class, "this"); - } - - /** - * Returns all the {@link CreatesMustCallFor#value} elements/arguments of all @CreatesMustCallFor - * annotations on the given element. - * - *

          Does no viewpoint-adaptation, unlike {@link - * CreatesMustCallForToJavaExpression#getCreatesMustCallForExpressionsAtInvocation} which does. - * - * @param elt an executable element - * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element - * @param atypeFactory a ResourceLeakAnnotatedTypeFactory - * @return the literal strings present in the @CreatesMustCallFor annotation(s) of that element, - * substituting the default "this" for empty annotations. This method returns the empty list - * iff there are no @CreatesMustCallFor annotations on elt. The returned list is always - * modifiable if it is non-empty. - */ - /*package-private*/ static List getCreatesMustCallForValues( - ExecutableElement elt, - MustCallAnnotatedTypeFactory mcAtf, - ResourceLeakAnnotatedTypeFactory atypeFactory) { - AnnotationMirror createsMustCallForList = - atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.List.class); - List result = new ArrayList<>(4); - if (createsMustCallForList != null) { - List createsMustCallFors = - AnnotationUtils.getElementValueArray( - createsMustCallForList, - mcAtf.getCreatesMustCallForListValueElement(), - AnnotationMirror.class); - for (AnnotationMirror cmcf : createsMustCallFors) { - result.add(getCreatesMustCallForValue(cmcf, mcAtf)); - } - } - AnnotationMirror createsMustCallFor = - atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.class); - if (createsMustCallFor != null) { - result.add(getCreatesMustCallForValue(createsMustCallFor, mcAtf)); + /** + * checks that any created must-call obligation has a declared type with a non-empty + * {@code @MustCall} obligation + * + * @param tree the method + * @param mcAtf the type factory + */ + private void checkCreatesMustCallForTargetsHaveNonEmptyMustCall( + MethodTree tree, MustCallAnnotatedTypeFactory mcAtf) { + // Get all the JavaExpressions for all CreatesMustCallFor annotations + List createsMustCallExprs = + CreatesMustCallForToJavaExpression + .getCreatesMustCallForExpressionsAtMethodDeclaration(tree, mcAtf, mcAtf); + for (JavaExpression targetExpr : createsMustCallExprs) { + AnnotationMirror mustCallAnno = + mcAtf.getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType())) + .getAnnotationInHierarchy(mcAtf.TOP); + if (rlTypeFactory.getMustCallValues(mustCallAnno).isEmpty()) { + checker.reportError( + tree, + "creates.mustcall.for.invalid.target", + targetExpr.toString(), + targetExpr.getType().toString()); + } + } } - return result; - } - - /** - * Get all {@link EnsuresCalledMethods} annotations on an element. - * - * @param elt an executable element that might have {@link EnsuresCalledMethods} annotations - * @param atypeFactory a ResourceLeakAnnotatedTypeFactory - * @return a set of {@link EnsuresCalledMethods} annotations - */ - @Pure - private static AnnotationMirrorSet getEnsuresCalledMethodsAnnotations( - ExecutableElement elt, ResourceLeakAnnotatedTypeFactory atypeFactory) { - AnnotationMirror ensuresCalledMethodsAnnos = - atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.List.class); - AnnotationMirrorSet result = new AnnotationMirrorSet(); - if (ensuresCalledMethodsAnnos != null) { - result.addAll( - AnnotationUtils.getElementValueArray( - ensuresCalledMethodsAnnos, - atypeFactory.getEnsuresCalledMethodsListValueElement(), - AnnotationMirror.class)); + + /** + * Check that an overriding method does not reduce the number of created must-call obligations + * + * @param tree overriding method + * @param elt element for overriding method + * @param mcAtf the type factory + * @param cmcfValues must call values created by overriding method + */ + private void checkCreatesMustCallForOverrides( + MethodTree tree, + ExecutableElement elt, + MustCallAnnotatedTypeFactory mcAtf, + List cmcfValues) { + // If this method overrides another method, it must create at least as many + // obligations. Without this check, dynamic dispatch might allow e.g. a field to be + // overwritten by a CMCF method, but the CMCF effect wouldn't occur. + for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(elt, this.types)) { + List overriddenCmcfValues = + getCreatesMustCallForValues(overridden, mcAtf, rlTypeFactory); + if (!overriddenCmcfValues.containsAll(cmcfValues)) { + String foundCmcfValueString = String.join(", ", cmcfValues); + String neededCmcfValueString = String.join(", ", overriddenCmcfValues); + String actualClassname = ElementUtils.getEnclosingClassName(elt); + String overriddenClassname = ElementUtils.getEnclosingClassName(overridden); + checker.reportError( + tree, + "creates.mustcall.for.override.invalid", + actualClassname + "#" + elt, + overriddenClassname + "#" + overridden, + foundCmcfValueString, + neededCmcfValueString); + } + } } - AnnotationMirror ensuresCalledMethod = - atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.class); - if (ensuresCalledMethod != null) { - result.add(ensuresCalledMethod); + + /** + * Checks that overrides respect behavioral subtyping for @Owning and @NotOwning annotations. In + * particular, checks that 1) if an overridden method has an @Owning parameter, then that + * parameter is @Owning in the overrider, and 2) if an overridden method has an @NotOwning + * return, then the overrider also has an @NotOwning return. + * + * @param tree overriding method, for error reporting + * @param overrider element for overriding method + * @param mcAtf the type factory + */ + private void checkOwningOverrides( + MethodTree tree, ExecutableElement overrider, MustCallAnnotatedTypeFactory mcAtf) { + for (ExecutableElement overridden : + ElementUtils.getOverriddenMethods(overrider, this.types)) { + // Check for @Owning parameters. Must use an explicitly-indexed for loop so that the + // same parameter index can be accessed in the overrider's parameter list, which is + // the same length. + for (int i = 0; i < overridden.getParameters().size(); i++) { + if (mcAtf.getDeclAnnotation(overridden.getParameters().get(i), Owning.class) + != null) { + if (mcAtf.getDeclAnnotation(overrider.getParameters().get(i), Owning.class) + == null) { + checker.reportError( + tree, + "owning.override.param", + overrider.getParameters().get(i).getSimpleName().toString(), + overrider.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overrider), + overridden.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overridden)); + } + } + } + // Check for @NotOwning returns. + if (mcAtf.getDeclAnnotation(overridden, NotOwning.class) != null + && mcAtf.getDeclAnnotation(overrider, NotOwning.class) == null) { + checker.reportError( + tree, + "owning.override.return", + overrider.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overrider), + overridden.getSimpleName().toString(), + ElementUtils.getEnclosingClassName(overridden)); + } + } } - return result; - } - @Override - public Void visitVariable(VariableTree tree, Void p) { - VariableElement varElement = TreeUtils.elementFromDeclaration(tree); + /** + * If a {@code @MustCallAlias} annotation appears in a method declaration, it must appear as an + * annotation on both the return type, and a parameter type. + * + *

          The return type is checked if it is annotated with {@code @PolyMustCall} because the Must + * Call Checker treats {@code @MustCallAlias} as an alias of {@code @PolyMustCall}. + * + * @param tree the method declaration + * @param mcAtf the MustCallAnnotatedTypeFactory + */ + private void checkMustCallAliasAnnotationForMethod( + MethodTree tree, MustCallAnnotatedTypeFactory mcAtf) { + + Element paramWithMustCallAliasAnno = getParameterWithMustCallAliasAnno(tree); + boolean isMustCallAliasAnnoOnParameter = paramWithMustCallAliasAnno != null; - if (varElement.getKind().isField() - && !noLightweightOwnership - && rlTypeFactory.getDeclAnnotation(varElement, Owning.class) != null) { - checkOwningField(varElement); - } + if (TreeUtils.isVoidReturn(tree) && isMustCallAliasAnnoOnParameter) { + checker.reportWarning( + tree, "mustcallalias.method.return.and.param", "this method has a void return"); + return; + } - return super.visitVariable(tree, p); - } + AnnotatedTypeMirror returnType = mcAtf.getMethodReturnType(tree); + boolean isMustCallAliasAnnoOnReturnType = returnType.hasAnnotation(PolyMustCall.class); + checkMustCallAliasAnnoMismatch( + paramWithMustCallAliasAnno, isMustCallAliasAnnoOnReturnType, tree); + } - /** - * An obligation that must be satisfied by a destructor. Helper type for {@link - * #checkOwningField(VariableElement)}. - */ - // TODO: In the future, this class should be a record. - private static final class DestructorObligation { - /** The method that must be called on the field. */ - final String mustCallMethod; + /** + * Given a constructor, a {@code @MustCallAlias} must appear in both the list of parameters and + * as an annotation on the constructor itself, if it is to appear at all. + * + *

          That is, a {@code @MustCallAlias} annotation must appear on both the constructor and its + * parameter list, or not at all. + * + * @param tree the constructor + */ + private void checkMustCallAliasAnnotationForConstructor(MethodTree tree) { + ExecutableElement constructorDecl = TreeUtils.elementFromDeclaration(tree); + boolean isMustCallAliasAnnoOnConstructor = + constructorDecl != null && rlTypeFactory.hasMustCallAlias(constructorDecl); + Element paramWithMustCallAliasAnno = getParameterWithMustCallAliasAnno(tree); + checkMustCallAliasAnnoMismatch( + paramWithMustCallAliasAnno, isMustCallAliasAnnoOnConstructor, tree); + } - /** When the method must be called. */ - final MustCallConsistencyAnalyzer.MethodExitKind exitKind; + /** + * Construct the warning message for the case where a {@code @MustCallAlias} annotation does not + * appear in pairs in a method or constructor declaration. + * + *

          If a parameter of a method or a constructor is annotated with a {@code @MustCallAlias} + * annotation, the return type (for a method) should also be annotated with + * {@code @MustCallAlias}. In the case of a constructor, which has no return type, a + * {@code @MustCallAlias} annotation must appear on its declaration. + * + * @param paramWithMustCallAliasAnno a parameter with a {@code @MustCallAlias} annotation, null + * if there are none + * @param isMustCallAliasAnnoOnMethodOrConstructorDecl true if and only if a + * {@code @MustCallAlias} annotation appears on a method or constructor declaration + * @param tree the method or constructor declaration + */ + private void checkMustCallAliasAnnoMismatch( + @Nullable Element paramWithMustCallAliasAnno, + boolean isMustCallAliasAnnoOnMethodOrConstructorDecl, + MethodTree tree) { + boolean isMustCallAliasAnnotationOnParameter = paramWithMustCallAliasAnno != null; + if (isMustCallAliasAnnotationOnParameter != isMustCallAliasAnnoOnMethodOrConstructorDecl) { + String locationOfCheck = + TreeUtils.isClassTree(tree) ? "this constructor" : "the return type"; + String message = + isMustCallAliasAnnotationOnParameter + ? String.format( + "there is no @MustCallAlias annotation on %s, even though the parameter %s is" + + " annotated with @MustCallAlias", + locationOfCheck, paramWithMustCallAliasAnno) + : "no parameter has a @MustCallAlias annotation, even though the return type is" + + " annotated with @MustCallAlias"; + checker.reportWarning(tree, "mustcallalias.method.return.and.param", message); + } + } /** - * Create a new obligation. + * Given a method and its parameter list, look through each of the parameters and see if any are + * annotated with the {@code @MustCallAlias} annotation. + * + *

          Return the first parameter that is annotated with {@code @MustCallAlias}, otherwise return + * null. * - * @param mustCallMethod the method that must be called - * @param exitKind when the method must be called + * @param tree the method declaration + * @return the first parameter that is annotated with {@code @MustCallAlias}, otherwise return + * null */ - public DestructorObligation( - String mustCallMethod, MustCallConsistencyAnalyzer.MethodExitKind exitKind) { - this.mustCallMethod = mustCallMethod; - this.exitKind = exitKind; + private @Nullable Element getParameterWithMustCallAliasAnno(MethodTree tree) { + VariableTree receiverParameter = tree.getReceiverParameter(); + if (receiverParameter != null && rlTypeFactory.hasMustCallAlias(receiverParameter)) { + return TreeUtils.elementFromDeclaration(receiverParameter); + } + for (VariableTree param : tree.getParameters()) { + if (rlTypeFactory.hasMustCallAlias(param)) { + return TreeUtils.elementFromDeclaration(param); + } + } + return null; } + /* NO-AFU @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DestructorObligation that = (DestructorObligation) o; - return mustCallMethod.equals(that.mustCallMethod) && exitKind == that.exitKind; + protected boolean shouldPerformContractInference() { + return atypeFactory.getWholeProgramInference() != null && isWpiEnabledForRLC(); } + */ - @Override - public int hashCode() { - return Objects.hash(mustCallMethod, exitKind); + /** + * Returns the {@link CreatesMustCallFor#value} element/argument of the + * given @CreatesMustCallFor annotation, or "this" if there is none. + * + *

          Does not vipewpoint-adaptation. + * + * @param createsMustCallFor an @CreatesMustCallFor annotation + * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element + * @return the string value + */ + private static String getCreatesMustCallForValue( + AnnotationMirror createsMustCallFor, MustCallAnnotatedTypeFactory mcAtf) { + return AnnotationUtils.getElementValue( + createsMustCallFor, + mcAtf.getCreatesMustCallForValueElement(), + String.class, + "this"); } - } - - /** - * Checks validity of a field {@code field} with an {@code @}{@link Owning} annotation. Say the - * type of {@code field} is {@code @MustCall("m"}}. This method checks that the enclosing class of - * {@code field} has a type {@code @MustCall("m2")} for some method {@code m2}, and that {@code - * m2} has an annotation {@code @EnsuresCalledMethods(value = "this.field", methods = "m")}, - * guaranteeing that the {@code @MustCall} obligation of the field will be satisfied. - * - * @param field the declaration of the field to check - */ - private void checkOwningField(VariableElement field) { - - if (checker.shouldSkipUses(field)) { - return; + + /** + * Returns all the {@link CreatesMustCallFor#value} elements/arguments of + * all @CreatesMustCallFor annotations on the given element. + * + *

          Does no viewpoint-adaptation, unlike {@link + * CreatesMustCallForToJavaExpression#getCreatesMustCallForExpressionsAtInvocation} which does. + * + * @param elt an executable element + * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element + * @param atypeFactory a ResourceLeakAnnotatedTypeFactory + * @return the literal strings present in the @CreatesMustCallFor annotation(s) of that element, + * substituting the default "this" for empty annotations. This method returns the empty list + * iff there are no @CreatesMustCallFor annotations on elt. The returned list is always + * modifiable if it is non-empty. + */ + /*package-private*/ static List getCreatesMustCallForValues( + ExecutableElement elt, + MustCallAnnotatedTypeFactory mcAtf, + ResourceLeakAnnotatedTypeFactory atypeFactory) { + AnnotationMirror createsMustCallForList = + atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.List.class); + List result = new ArrayList<>(4); + if (createsMustCallForList != null) { + List createsMustCallFors = + AnnotationUtils.getElementValueArray( + createsMustCallForList, + mcAtf.getCreatesMustCallForListValueElement(), + AnnotationMirror.class); + for (AnnotationMirror cmcf : createsMustCallFors) { + result.add(getCreatesMustCallForValue(cmcf, mcAtf)); + } + } + AnnotationMirror createsMustCallFor = + atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.class); + if (createsMustCallFor != null) { + result.add(getCreatesMustCallForValue(createsMustCallFor, mcAtf)); + } + return result; } - Set modifiers = field.getModifiers(); - if (modifiers.contains(Modifier.STATIC)) { - if (permitStaticOwning) { - return; - } - if (modifiers.contains(Modifier.FINAL)) { - return; - } + /** + * Get all {@link EnsuresCalledMethods} annotations on an element. + * + * @param elt an executable element that might have {@link EnsuresCalledMethods} annotations + * @param atypeFactory a ResourceLeakAnnotatedTypeFactory + * @return a set of {@link EnsuresCalledMethods} annotations + */ + @Pure + private static AnnotationMirrorSet getEnsuresCalledMethodsAnnotations( + ExecutableElement elt, ResourceLeakAnnotatedTypeFactory atypeFactory) { + AnnotationMirror ensuresCalledMethodsAnnos = + atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.List.class); + AnnotationMirrorSet result = new AnnotationMirrorSet(); + if (ensuresCalledMethodsAnnos != null) { + result.addAll( + AnnotationUtils.getElementValueArray( + ensuresCalledMethodsAnnos, + atypeFactory.getEnsuresCalledMethodsListValueElement(), + AnnotationMirror.class)); + } + AnnotationMirror ensuresCalledMethod = + atypeFactory.getDeclAnnotation(elt, EnsuresCalledMethods.class); + if (ensuresCalledMethod != null) { + result.add(ensuresCalledMethod); + } + return result; } - List mustCallObligationsOfOwningField = rlTypeFactory.getMustCallValues(field); + @Override + public Void visitVariable(VariableTree tree, Void p) { + VariableElement varElement = TreeUtils.elementFromDeclaration(tree); + + if (varElement.getKind().isField() + && !noLightweightOwnership + && rlTypeFactory.getDeclAnnotation(varElement, Owning.class) != null) { + checkOwningField(varElement); + } - if (mustCallObligationsOfOwningField.isEmpty()) { - return; + return super.visitVariable(tree, p); } - // This value is side-effected. - Set unsatisfiedMustCallObligationsOfOwningField = new LinkedHashSet<>(); - for (String mustCallMethod : mustCallObligationsOfOwningField) { - for (MustCallConsistencyAnalyzer.MethodExitKind exitKind : - MustCallConsistencyAnalyzer.MethodExitKind.values()) { - unsatisfiedMustCallObligationsOfOwningField.add( - new DestructorObligation(mustCallMethod, exitKind)); - } + /** + * An obligation that must be satisfied by a destructor. Helper type for {@link + * #checkOwningField(VariableElement)}. + */ + // TODO: In the future, this class should be a record. + private static final class DestructorObligation { + /** The method that must be called on the field. */ + final String mustCallMethod; + + /** When the method must be called. */ + final MustCallConsistencyAnalyzer.MethodExitKind exitKind; + + /** + * Create a new obligation. + * + * @param mustCallMethod the method that must be called + * @param exitKind when the method must be called + */ + public DestructorObligation( + String mustCallMethod, MustCallConsistencyAnalyzer.MethodExitKind exitKind) { + this.mustCallMethod = mustCallMethod; + this.exitKind = exitKind; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DestructorObligation that = (DestructorObligation) o; + return mustCallMethod.equals(that.mustCallMethod) && exitKind == that.exitKind; + } + + @Override + public int hashCode() { + return Objects.hash(mustCallMethod, exitKind); + } } - String error; - Element enclosingElement = field.getEnclosingElement(); - List enclosingMustCallValues = rlTypeFactory.getMustCallValues(enclosingElement); - - if (enclosingMustCallValues == null) { - error = - " The enclosing element " - + ElementUtils.getQualifiedName(enclosingElement) - + " doesn't have a @MustCall annotation"; - } else if (enclosingMustCallValues.isEmpty()) { - error = - " The enclosing element " - + ElementUtils.getQualifiedName(enclosingElement) - + " has an empty @MustCall annotation"; - } else { - error = " [[checkOwningField() did not find a reason!]]"; // should be reassigned - List siblingsOfOwningField = enclosingElement.getEnclosedElements(); - for (Element siblingElement : siblingsOfOwningField) { - if (siblingElement.getKind() == ElementKind.METHOD - && enclosingMustCallValues.contains(siblingElement.getSimpleName().toString())) { - - ExecutableElement siblingMethod = (ExecutableElement) siblingElement; - - AnnotationMirrorSet allEnsuresCalledMethodsAnnos = - getEnsuresCalledMethodsAnnotations(siblingMethod, rlTypeFactory); - for (AnnotationMirror ensuresCalledMethodsAnno : allEnsuresCalledMethodsAnnos) { - List values = - AnnotationUtils.getElementValueArray( - ensuresCalledMethodsAnno, - rlTypeFactory.ensuresCalledMethodsValueElement, - String.class); - for (String value : values) { - if (expressionEqualsField(value, field)) { - List methods = - AnnotationUtils.getElementValueArray( - ensuresCalledMethodsAnno, - rlTypeFactory.ensuresCalledMethodsMethodsElement, - String.class); - for (String method : methods) { - unsatisfiedMustCallObligationsOfOwningField.remove( - new DestructorObligation( - method, MustCallConsistencyAnalyzer.MethodExitKind.NORMAL_RETURN)); - } - } - } + /** + * Checks validity of a field {@code field} with an {@code @}{@link Owning} annotation. Say the + * type of {@code field} is {@code @MustCall("m"}}. This method checks that the enclosing class + * of {@code field} has a type {@code @MustCall("m2")} for some method {@code m2}, and that + * {@code m2} has an annotation {@code @EnsuresCalledMethods(value = "this.field", methods = + * "m")}, guaranteeing that the {@code @MustCall} obligation of the field will be satisfied. + * + * @param field the declaration of the field to check + */ + private void checkOwningField(VariableElement field) { + + if (checker.shouldSkipUses(field)) { + return; + } - Set exceptionalPostconds = - rlTypeFactory.getExceptionalPostconditions(siblingMethod); - for (EnsuresCalledMethodOnExceptionContract postcond : exceptionalPostconds) { - if (expressionEqualsField(postcond.getExpression(), field)) { - unsatisfiedMustCallObligationsOfOwningField.remove( - new DestructorObligation( - postcond.getMethod(), - MustCallConsistencyAnalyzer.MethodExitKind.EXCEPTIONAL_EXIT)); - } + Set modifiers = field.getModifiers(); + if (modifiers.contains(Modifier.STATIC)) { + if (permitStaticOwning) { + return; } + if (modifiers.contains(Modifier.FINAL)) { + return; + } + } - // Optimization: stop early as soon as we've exhausted the list of - // obligations. - if (unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { - return; + List mustCallObligationsOfOwningField = rlTypeFactory.getMustCallValues(field); + + if (mustCallObligationsOfOwningField.isEmpty()) { + return; + } + + // This value is side-effected. + Set unsatisfiedMustCallObligationsOfOwningField = + new LinkedHashSet<>(); + for (String mustCallMethod : mustCallObligationsOfOwningField) { + for (MustCallConsistencyAnalyzer.MethodExitKind exitKind : + MustCallConsistencyAnalyzer.MethodExitKind.values()) { + unsatisfiedMustCallObligationsOfOwningField.add( + new DestructorObligation(mustCallMethod, exitKind)); } - } + } + + String error; + Element enclosingElement = field.getEnclosingElement(); + List enclosingMustCallValues = rlTypeFactory.getMustCallValues(enclosingElement); - if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { - // This variable could be set immediately before reporting the error, but - // IMO it is more clear to set it here. + if (enclosingMustCallValues == null) { error = - "Postconditions written on MustCall methods are missing: " - + formatMissingMustCallMethodPostconditions( - field, unsatisfiedMustCallObligationsOfOwningField); - } + " The enclosing element " + + ElementUtils.getQualifiedName(enclosingElement) + + " doesn't have a @MustCall annotation"; + } else if (enclosingMustCallValues.isEmpty()) { + error = + " The enclosing element " + + ElementUtils.getQualifiedName(enclosingElement) + + " has an empty @MustCall annotation"; + } else { + error = " [[checkOwningField() did not find a reason!]]"; // should be reassigned + List siblingsOfOwningField = enclosingElement.getEnclosedElements(); + for (Element siblingElement : siblingsOfOwningField) { + if (siblingElement.getKind() == ElementKind.METHOD + && enclosingMustCallValues.contains( + siblingElement.getSimpleName().toString())) { + + ExecutableElement siblingMethod = (ExecutableElement) siblingElement; + + AnnotationMirrorSet allEnsuresCalledMethodsAnnos = + getEnsuresCalledMethodsAnnotations(siblingMethod, rlTypeFactory); + for (AnnotationMirror ensuresCalledMethodsAnno : allEnsuresCalledMethodsAnnos) { + List values = + AnnotationUtils.getElementValueArray( + ensuresCalledMethodsAnno, + rlTypeFactory.ensuresCalledMethodsValueElement, + String.class); + for (String value : values) { + if (expressionEqualsField(value, field)) { + List methods = + AnnotationUtils.getElementValueArray( + ensuresCalledMethodsAnno, + rlTypeFactory.ensuresCalledMethodsMethodsElement, + String.class); + for (String method : methods) { + unsatisfiedMustCallObligationsOfOwningField.remove( + new DestructorObligation( + method, + MustCallConsistencyAnalyzer.MethodExitKind + .NORMAL_RETURN)); + } + } + } + + Set exceptionalPostconds = + rlTypeFactory.getExceptionalPostconditions(siblingMethod); + for (EnsuresCalledMethodOnExceptionContract postcond : + exceptionalPostconds) { + if (expressionEqualsField(postcond.getExpression(), field)) { + unsatisfiedMustCallObligationsOfOwningField.remove( + new DestructorObligation( + postcond.getMethod(), + MustCallConsistencyAnalyzer.MethodExitKind + .EXCEPTIONAL_EXIT)); + } + } + + // Optimization: stop early as soon as we've exhausted the list of + // obligations. + if (unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { + return; + } + } + + if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { + // This variable could be set immediately before reporting the error, but + // IMO it is more clear to set it here. + error = + "Postconditions written on MustCall methods are missing: " + + formatMissingMustCallMethodPostconditions( + field, unsatisfiedMustCallObligationsOfOwningField); + } + } + } + } + + if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { + Set missingMethods = new LinkedHashSet<>(); + for (DestructorObligation obligation : unsatisfiedMustCallObligationsOfOwningField) { + missingMethods.add(obligation.mustCallMethod); + } + + checker.reportError( + field, + "required.method.not.called", + MustCallConsistencyAnalyzer.formatMissingMustCallMethods( + new ArrayList<>(missingMethods)), + "field " + field.getSimpleName().toString(), + field.asType().toString(), + error); } - } } - if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { - Set missingMethods = new LinkedHashSet<>(); - for (DestructorObligation obligation : unsatisfiedMustCallObligationsOfOwningField) { - missingMethods.add(obligation.mustCallMethod); - } - - checker.reportError( - field, - "required.method.not.called", - MustCallConsistencyAnalyzer.formatMissingMustCallMethods(new ArrayList<>(missingMethods)), - "field " + field.getSimpleName().toString(), - field.asType().toString(), - error); + /** + * Determine if the given expression e refers to this.field. + * + * @param e the expression + * @param field the field + * @return true if e refers to this.field + */ + private boolean expressionEqualsField(String e, VariableElement field) { + try { + JavaExpression je = StringToJavaExpression.atFieldDecl(e, field, this.checker); + return je instanceof FieldAccess && ((FieldAccess) je).getField().equals(field); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + // The parsing error will be reported elsewhere, assuming e was derived from an + // annotation. + return false; + } } - } - - /** - * Determine if the given expression e refers to this.field. - * - * @param e the expression - * @param field the field - * @return true if e refers to this.field - */ - private boolean expressionEqualsField(String e, VariableElement field) { - try { - JavaExpression je = StringToJavaExpression.atFieldDecl(e, field, this.checker); - return je instanceof FieldAccess && ((FieldAccess) je).getField().equals(field); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - // The parsing error will be reported elsewhere, assuming e was derived from an - // annotation. - return false; + + /* NO-AFU + * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link + * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. + * + * @return returns true if WPI is enabled for the Resource Leak Checker + * + protected boolean isWpiEnabledForRLC() { + return enableWpiForRlc; } - } - - /* NO-AFU - * Checks if WPI is enabled for the Resource Leak Checker inference. See {@link - * ResourceLeakChecker#ENABLE_WPI_FOR_RLC}. - * - * @return returns true if WPI is enabled for the Resource Leak Checker - * - protected boolean isWpiEnabledForRLC() { - return enableWpiForRlc; - } - */ - - /** - * Formats a list of must-call method post-conditions to be printed in an error message. - * - * @param field the value whose methods must be called - * @param mustCallVal the list of must-call strings - * @return a formatted string - */ - /*package-private*/ static String formatMissingMustCallMethodPostconditions( - Element field, Set mustCallVal) { - int size = mustCallVal.size(); - if (size == 0) { - throw new TypeSystemError("empty mustCallVal " + mustCallVal); + */ + + /** + * Formats a list of must-call method post-conditions to be printed in an error message. + * + * @param field the value whose methods must be called + * @param mustCallVal the list of must-call strings + * @return a formatted string + */ + /*package-private*/ static String formatMissingMustCallMethodPostconditions( + Element field, Set mustCallVal) { + int size = mustCallVal.size(); + if (size == 0) { + throw new TypeSystemError("empty mustCallVal " + mustCallVal); + } + String fieldName = field.getSimpleName().toString(); + return mustCallVal.stream() + .map( + o -> + postconditionAnnotationFor(o.exitKind) + + "(value = \"" + + fieldName + + "\", methods = \"" + + o.mustCallMethod + + "\")") + .collect(Collectors.joining(", ")); } - String fieldName = field.getSimpleName().toString(); - return mustCallVal.stream() - .map( - o -> - postconditionAnnotationFor(o.exitKind) - + "(value = \"" - + fieldName - + "\", methods = \"" - + o.mustCallMethod - + "\")") - .collect(Collectors.joining(", ")); - } - - /** - * Format a must-call post-condition to be printed in an error message. - * - * @param exitKind the kind of method exit - * @return the name of the annotation - */ - private static String postconditionAnnotationFor( - MustCallConsistencyAnalyzer.MethodExitKind exitKind) { - switch (exitKind) { - case NORMAL_RETURN: - return "@EnsuresCalledMethods"; - case EXCEPTIONAL_EXIT: - return "@EnsuresCalledMethodsOnException"; - default: - throw new UnsupportedOperationException(exitKind.toString()); + + /** + * Format a must-call post-condition to be printed in an error message. + * + * @param exitKind the kind of method exit + * @return the name of the annotation + */ + private static String postconditionAnnotationFor( + MustCallConsistencyAnalyzer.MethodExitKind exitKind) { + switch (exitKind) { + case NORMAL_RETURN: + return "@EnsuresCalledMethods"; + case EXCEPTIONAL_EXIT: + return "@EnsuresCalledMethodsOnException"; + default: + throw new UnsupportedOperationException(exitKind.toString()); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java index 4187adfc506..185bbbf70e9 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/SetOfTypes.java @@ -2,11 +2,13 @@ import com.google.common.collect.ImmutableSet; import com.sun.tools.javac.code.Type; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; + import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.dataflow.qual.Pure; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + /** * A set of types. * @@ -23,75 +25,75 @@ */ public interface SetOfTypes { - /** - * Test whether this set contains the given type. - * - * @param typeUtils a {@code Types} object for computing the relationships between types - * @param type the type in question - * @return true if this set contains {@code type}, or false otherwise - */ - @Pure - boolean contains(Types typeUtils, TypeMirror type); + /** + * Test whether this set contains the given type. + * + * @param typeUtils a {@code Types} object for computing the relationships between types + * @param type the type in question + * @return true if this set contains {@code type}, or false otherwise + */ + @Pure + boolean contains(Types typeUtils, TypeMirror type); - /** An empty set of types. */ - SetOfTypes EMPTY = (typeUtils, type) -> false; + /** An empty set of types. */ + SetOfTypes EMPTY = (typeUtils, type) -> false; - /** - * Create a set containing exactly the given type, but not its subtypes. - * - * @param t the type - * @return a set containing only {@code t} - */ - @Pure - static SetOfTypes singleton(TypeMirror t) { - return (typeUtils, u) -> typeUtils.isSameType(t, u); - } + /** + * Create a set containing exactly the given type, but not its subtypes. + * + * @param t the type + * @return a set containing only {@code t} + */ + @Pure + static SetOfTypes singleton(TypeMirror t) { + return (typeUtils, u) -> typeUtils.isSameType(t, u); + } - /** - * Create a set containing the given type and all of its subtypes. - * - * @param t the type - * @return a set containing {@code t} and its subtypes - */ - @Pure - static SetOfTypes allSubtypes(TypeMirror t) { - return (typeUtils, u) -> typeUtils.isSubtype(u, t); - } + /** + * Create a set containing the given type and all of its subtypes. + * + * @param t the type + * @return a set containing {@code t} and its subtypes + */ + @Pure + static SetOfTypes allSubtypes(TypeMirror t) { + return (typeUtils, u) -> typeUtils.isSubtype(u, t); + } - /** - * Create a set containing exactly the types with the given names, but not their subtypes. - * - * @param names the type names - * @return a set containing only the named types - */ - @Pure - static SetOfTypes anyOfTheseNames(ImmutableSet<@CanonicalName String> names) { - return (typeUtils, u) -> - u instanceof Type && names.contains(((Type) u).tsym.getQualifiedName().toString()); - } + /** + * Create a set containing exactly the types with the given names, but not their subtypes. + * + * @param names the type names + * @return a set containing only the named types + */ + @Pure + static SetOfTypes anyOfTheseNames(ImmutableSet<@CanonicalName String> names) { + return (typeUtils, u) -> + u instanceof Type && names.contains(((Type) u).tsym.getQualifiedName().toString()); + } - /** - * Create a set representing the union of all the given sets. - * - * @param typeSets an array of sets - * @return the union of the given sets - */ - @Pure - static SetOfTypes union(SetOfTypes... typeSets) { - switch (typeSets.length) { - case 0: - return EMPTY; - case 1: - return typeSets[0]; - default: - return (typeUtils, type) -> { - for (SetOfTypes set : typeSets) { - if (set.contains(typeUtils, type)) { - return true; - } - } - return false; - }; + /** + * Create a set representing the union of all the given sets. + * + * @param typeSets an array of sets + * @return the union of the given sets + */ + @Pure + static SetOfTypes union(SetOfTypes... typeSets) { + switch (typeSets.length) { + case 0: + return EMPTY; + case 1: + return typeSets[0]; + default: + return (typeUtils, type) -> { + for (SetOfTypes set : typeSets) { + if (set.contains(typeUtils, type)) { + return true; + } + } + return false; + }; + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java index 6b31fd81869..be385c7881b 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java @@ -8,15 +8,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.PrimitiveTypeTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.signature.qual.ArrayWithoutPackage; import org.checkerframework.checker.signature.qual.BinaryName; import org.checkerframework.checker.signature.qual.BinaryNameOrPrimitiveType; @@ -50,263 +42,283 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.reflection.SignatureRegexes; +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + // TODO: Does not yet handle method signature annotations, such as // @MethodDescriptor. /** Accounts for the effects of certain calls to String.replace. */ public class SignatureAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@literal @}{@link SignatureUnknown} annotation. */ - protected final AnnotationMirror SIGNATURE_UNKNOWN = - AnnotationBuilder.fromClass(elements, SignatureUnknown.class); - - /** The {@literal @}{@link BinaryName} annotation. */ - protected final AnnotationMirror BINARY_NAME = - AnnotationBuilder.fromClass(elements, BinaryName.class); - - /** The {@literal @}{@link InternalForm} annotation. */ - protected final AnnotationMirror INTERNAL_FORM = - AnnotationBuilder.fromClass(elements, InternalForm.class); + /** The {@literal @}{@link SignatureUnknown} annotation. */ + protected final AnnotationMirror SIGNATURE_UNKNOWN = + AnnotationBuilder.fromClass(elements, SignatureUnknown.class); - /** The {@literal @}{@link DotSeparatedIdentifiers} annotation. */ - protected final AnnotationMirror DOT_SEPARATED_IDENTIFIERS = - AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class); + /** The {@literal @}{@link BinaryName} annotation. */ + protected final AnnotationMirror BINARY_NAME = + AnnotationBuilder.fromClass(elements, BinaryName.class); - /** The {@literal @}{@link CanonicalName} annotation. */ - protected final AnnotationMirror CANONICAL_NAME = - AnnotationBuilder.fromClass(elements, CanonicalName.class); + /** The {@literal @}{@link InternalForm} annotation. */ + protected final AnnotationMirror INTERNAL_FORM = + AnnotationBuilder.fromClass(elements, InternalForm.class); - /** The {@literal @}{@link CanonicalNameAndBinaryName} annotation. */ - protected final AnnotationMirror CANONICAL_NAME_AND_BINARY_NAME = - AnnotationBuilder.fromClass(elements, CanonicalNameAndBinaryName.class); + /** The {@literal @}{@link DotSeparatedIdentifiers} annotation. */ + protected final AnnotationMirror DOT_SEPARATED_IDENTIFIERS = + AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class); - /** The {@literal @}{@link PrimitiveType} annotation. */ - protected final AnnotationMirror PRIMITIVE_TYPE = - AnnotationBuilder.fromClass(elements, PrimitiveType.class); + /** The {@literal @}{@link CanonicalName} annotation. */ + protected final AnnotationMirror CANONICAL_NAME = + AnnotationBuilder.fromClass(elements, CanonicalName.class); - /** The {@link String#replace(char, char)} method. */ - private final ExecutableElement replaceCharChar = - TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char"); + /** The {@literal @}{@link CanonicalNameAndBinaryName} annotation. */ + protected final AnnotationMirror CANONICAL_NAME_AND_BINARY_NAME = + AnnotationBuilder.fromClass(elements, CanonicalNameAndBinaryName.class); - /** The {@link String#replace(CharSequence, CharSequence)} method. */ - private final ExecutableElement replaceCharSequenceCharSequence = - TreeUtils.getMethod( - "java.lang.String", - "replace", - processingEnv, - "java.lang.CharSequence", - "java.lang.CharSequence"); + /** The {@literal @}{@link PrimitiveType} annotation. */ + protected final AnnotationMirror PRIMITIVE_TYPE = + AnnotationBuilder.fromClass(elements, PrimitiveType.class); - /** The {@link Class#getName()} method. */ - private final ExecutableElement classGetName = - TreeUtils.getMethod("java.lang.Class", "getName", processingEnv); + /** The {@link String#replace(char, char)} method. */ + private final ExecutableElement replaceCharChar = + TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char"); - /** The {@link Class#getCanonicalName()} method. */ - private final ExecutableElement classGetCanonicalName = - TreeUtils.getMethod(java.lang.Class.class, "getCanonicalName", processingEnv); + /** The {@link String#replace(CharSequence, CharSequence)} method. */ + private final ExecutableElement replaceCharSequenceCharSequence = + TreeUtils.getMethod( + "java.lang.String", + "replace", + processingEnv, + "java.lang.CharSequence", + "java.lang.CharSequence"); - /** - * Creates a SignatureAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this type factory - */ - public SignatureAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + /** The {@link Class#getName()} method. */ + private final ExecutableElement classGetName = + TreeUtils.getMethod("java.lang.Class", "getName", processingEnv); - this.postInit(); - } + /** The {@link Class#getCanonicalName()} method. */ + private final ExecutableElement classGetCanonicalName = + TreeUtils.getMethod(java.lang.Class.class, "getCanonicalName", processingEnv); - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(SignatureUnknown.class, SignatureBottom.class); - } - - @Override - public TreeAnnotator createTreeAnnotator() { - // It is slightly inefficient that super also adds a LiteralTreeAnnotator, but it seems - // better than hard-coding the behavior of super here. - return new ListTreeAnnotator( - signatureLiteralTreeAnnotator(this), - new SignatureTreeAnnotator(this), - super.createTreeAnnotator()); - } - - /** - * Create a LiteralTreeAnnotator for the Signature Checker. - * - * @param atypeFactory the type factory - * @return a LiteralTreeAnnotator for the Signature Checker - */ - private LiteralTreeAnnotator signatureLiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - LiteralTreeAnnotator result = new LiteralTreeAnnotator(atypeFactory); - result.addStandardLiteralQualifiers(); - - // The below code achieves the same effect as writing a meta-annotation - // @QualifierForLiterals(stringPatterns = "...") - // on each type qualifier definition. Annotation elements cannot be computations (not even - // string concatenations of literal strings) and cannot be not references to compile-time - // constants such as effectively-final fields. So every `stringPatterns = "..."` would have - // to be a literal string, which would be verbose ard hard to maintain. - result.addStringPattern( - SignatureRegexes.ArrayWithoutPackageRegex, - AnnotationBuilder.fromClass(elements, ArrayWithoutPackage.class)); - result.addStringPattern( - SignatureRegexes.BinaryNameRegex, AnnotationBuilder.fromClass(elements, BinaryName.class)); - result.addStringPattern( - SignatureRegexes.BinaryNameOrPrimitiveTypeRegex, - AnnotationBuilder.fromClass(elements, BinaryNameOrPrimitiveType.class)); - result.addStringPattern( - SignatureRegexes.BinaryNameWithoutPackageRegex, - AnnotationBuilder.fromClass(elements, BinaryNameWithoutPackage.class)); - result.addStringPattern( - SignatureRegexes.ClassGetNameRegex, - AnnotationBuilder.fromClass(elements, ClassGetName.class)); - result.addStringPattern( - SignatureRegexes.ClassGetSimpleNameRegex, - AnnotationBuilder.fromClass(elements, ClassGetSimpleName.class)); - result.addStringPattern( - SignatureRegexes.DotSeparatedIdentifiersRegex, - AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class)); - result.addStringPattern( - SignatureRegexes.DotSeparatedIdentifiersOrPrimitiveTypeRegex, - AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiersOrPrimitiveType.class)); - result.addStringPattern( - SignatureRegexes.FieldDescriptorRegex, - AnnotationBuilder.fromClass(elements, FieldDescriptor.class)); - result.addStringPattern( - SignatureRegexes.FieldDescriptorForPrimitiveRegex, - AnnotationBuilder.fromClass(elements, FieldDescriptorForPrimitive.class)); - result.addStringPattern( - SignatureRegexes.FieldDescriptorWithoutPackageRegex, - AnnotationBuilder.fromClass(elements, FieldDescriptorWithoutPackage.class)); - result.addStringPattern( - SignatureRegexes.FqBinaryNameRegex, - AnnotationBuilder.fromClass(elements, FqBinaryName.class)); - result.addStringPattern( - SignatureRegexes.FullyQualifiedNameRegex, - AnnotationBuilder.fromClass(elements, FullyQualifiedName.class)); - result.addStringPattern( - SignatureRegexes.IdentifierRegex, AnnotationBuilder.fromClass(elements, Identifier.class)); - result.addStringPattern( - SignatureRegexes.IdentifierOrPrimitiveTypeRegex, - AnnotationBuilder.fromClass(elements, IdentifierOrPrimitiveType.class)); - result.addStringPattern( - SignatureRegexes.InternalFormRegex, - AnnotationBuilder.fromClass(elements, InternalForm.class)); - result.addStringPattern( - SignatureRegexes.PrimitiveTypeRegex, - AnnotationBuilder.fromClass(elements, PrimitiveType.class)); - return result; - } - - private class SignatureTreeAnnotator extends TreeAnnotator { + /** + * Creates a SignatureAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this type factory + */ + public SignatureAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); - public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + this.postInit(); } @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringConcatenation(tree)) { - // This could be made more precise. - type.replaceAnnotation(SIGNATURE_UNKNOWN); - } - return null; // super.visitBinary(tree, type); + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(SignatureUnknown.class, SignatureBottom.class); } @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - // This could be made more precise. - type.replaceAnnotation(SIGNATURE_UNKNOWN); - } - return null; // super.visitCompoundAssignment(tree, type); + public TreeAnnotator createTreeAnnotator() { + // It is slightly inefficient that super also adds a LiteralTreeAnnotator, but it seems + // better than hard-coding the behavior of super here. + return new ListTreeAnnotator( + signatureLiteralTreeAnnotator(this), + new SignatureTreeAnnotator(this), + super.createTreeAnnotator()); } /** - * String.replace, when called with specific constant arguments, converts between internal form - * and binary name: - * - *

          
          -     * {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
          -     * {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
          -     * 
          - * - * Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return a - * {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they - * return a {@link BinaryName}: + * Create a LiteralTreeAnnotator for the Signature Checker. * - *
          
          -     * {@literal @}BinaryName String binaryName = MyClass.class.getName();
          -     * 
          + * @param atypeFactory the type factory + * @return a LiteralTreeAnnotator for the Signature Checker */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv) - || TreeUtils.isMethodInvocation(tree, replaceCharSequenceCharSequence, processingEnv)) { - char oldChar = ' '; // initial dummy value - char newChar = ' '; // initial dummy value - if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)) { - ExpressionTree arg0 = tree.getArguments().get(0); - ExpressionTree arg1 = tree.getArguments().get(1); - if (arg0.getKind() == Tree.Kind.CHAR_LITERAL - && arg1.getKind() == Tree.Kind.CHAR_LITERAL) { - oldChar = (char) ((LiteralTree) arg0).getValue(); - newChar = (char) ((LiteralTree) arg1).getValue(); - } - } else { - ExpressionTree arg0 = tree.getArguments().get(0); - ExpressionTree arg1 = tree.getArguments().get(1); - if (arg0.getKind() == Tree.Kind.STRING_LITERAL - && arg1.getKind() == Tree.Kind.STRING_LITERAL) { - String const0 = (String) ((LiteralTree) arg0).getValue(); - String const1 = (String) ((LiteralTree) arg1).getValue(); - if (const0.length() == 1 && const1.length() == 1) { - oldChar = const0.charAt(0); - newChar = const1.charAt(0); + private LiteralTreeAnnotator signatureLiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + LiteralTreeAnnotator result = new LiteralTreeAnnotator(atypeFactory); + result.addStandardLiteralQualifiers(); + + // The below code achieves the same effect as writing a meta-annotation + // @QualifierForLiterals(stringPatterns = "...") + // on each type qualifier definition. Annotation elements cannot be computations (not even + // string concatenations of literal strings) and cannot be not references to compile-time + // constants such as effectively-final fields. So every `stringPatterns = "..."` would have + // to be a literal string, which would be verbose ard hard to maintain. + result.addStringPattern( + SignatureRegexes.ArrayWithoutPackageRegex, + AnnotationBuilder.fromClass(elements, ArrayWithoutPackage.class)); + result.addStringPattern( + SignatureRegexes.BinaryNameRegex, + AnnotationBuilder.fromClass(elements, BinaryName.class)); + result.addStringPattern( + SignatureRegexes.BinaryNameOrPrimitiveTypeRegex, + AnnotationBuilder.fromClass(elements, BinaryNameOrPrimitiveType.class)); + result.addStringPattern( + SignatureRegexes.BinaryNameWithoutPackageRegex, + AnnotationBuilder.fromClass(elements, BinaryNameWithoutPackage.class)); + result.addStringPattern( + SignatureRegexes.ClassGetNameRegex, + AnnotationBuilder.fromClass(elements, ClassGetName.class)); + result.addStringPattern( + SignatureRegexes.ClassGetSimpleNameRegex, + AnnotationBuilder.fromClass(elements, ClassGetSimpleName.class)); + result.addStringPattern( + SignatureRegexes.DotSeparatedIdentifiersRegex, + AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class)); + result.addStringPattern( + SignatureRegexes.DotSeparatedIdentifiersOrPrimitiveTypeRegex, + AnnotationBuilder.fromClass( + elements, DotSeparatedIdentifiersOrPrimitiveType.class)); + result.addStringPattern( + SignatureRegexes.FieldDescriptorRegex, + AnnotationBuilder.fromClass(elements, FieldDescriptor.class)); + result.addStringPattern( + SignatureRegexes.FieldDescriptorForPrimitiveRegex, + AnnotationBuilder.fromClass(elements, FieldDescriptorForPrimitive.class)); + result.addStringPattern( + SignatureRegexes.FieldDescriptorWithoutPackageRegex, + AnnotationBuilder.fromClass(elements, FieldDescriptorWithoutPackage.class)); + result.addStringPattern( + SignatureRegexes.FqBinaryNameRegex, + AnnotationBuilder.fromClass(elements, FqBinaryName.class)); + result.addStringPattern( + SignatureRegexes.FullyQualifiedNameRegex, + AnnotationBuilder.fromClass(elements, FullyQualifiedName.class)); + result.addStringPattern( + SignatureRegexes.IdentifierRegex, + AnnotationBuilder.fromClass(elements, Identifier.class)); + result.addStringPattern( + SignatureRegexes.IdentifierOrPrimitiveTypeRegex, + AnnotationBuilder.fromClass(elements, IdentifierOrPrimitiveType.class)); + result.addStringPattern( + SignatureRegexes.InternalFormRegex, + AnnotationBuilder.fromClass(elements, InternalForm.class)); + result.addStringPattern( + SignatureRegexes.PrimitiveTypeRegex, + AnnotationBuilder.fromClass(elements, PrimitiveType.class)); + return result; + } + + private class SignatureTreeAnnotator extends TreeAnnotator { + + public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringConcatenation(tree)) { + // This could be made more precise. + type.replaceAnnotation(SIGNATURE_UNKNOWN); } - } + return null; // super.visitBinary(tree, type); } - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - AnnotatedTypeMirror receiverType = getAnnotatedType(receiver); - if ((oldChar == '.' && newChar == '/') - && receiverType.getAnnotation(BinaryName.class) != null) { - type.replaceAnnotation(INTERNAL_FORM); - } else if ((oldChar == '/' && newChar == '.') - && receiverType.getAnnotation(InternalForm.class) != null) { - type.replaceAnnotation(BINARY_NAME); + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + // This could be made more precise. + type.replaceAnnotation(SIGNATURE_UNKNOWN); + } + return null; // super.visitCompoundAssignment(tree, type); } - } else { - boolean isClassGetName = TreeUtils.isMethodInvocation(tree, classGetName, processingEnv); - boolean isClassGetCanonicalName = - TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv); - if (isClassGetName || isClassGetCanonicalName) { - ExpressionTree receiver = TreeUtils.getReceiverTree(tree); - if (TreeUtils.isClassLiteral(receiver)) { - ExpressionTree classExpr = ((MemberSelectTree) receiver).getExpression(); - if (classExpr.getKind() == Tree.Kind.PRIMITIVE_TYPE) { - if (((PrimitiveTypeTree) classExpr).getPrimitiveTypeKind() == TypeKind.VOID) { - // do nothing - } else { - type.replaceAnnotation(PRIMITIVE_TYPE); - } + + /** + * String.replace, when called with specific constant arguments, converts between internal + * form and binary name: + * + *
          
          +         * {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
          +         * {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
          +         * 
          + * + * Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return + * a {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they + * return a {@link BinaryName}: + * + *
          
          +         * {@literal @}BinaryName String binaryName = MyClass.class.getName();
          +         * 
          + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv) + || TreeUtils.isMethodInvocation( + tree, replaceCharSequenceCharSequence, processingEnv)) { + char oldChar = ' '; // initial dummy value + char newChar = ' '; // initial dummy value + if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)) { + ExpressionTree arg0 = tree.getArguments().get(0); + ExpressionTree arg1 = tree.getArguments().get(1); + if (arg0.getKind() == Tree.Kind.CHAR_LITERAL + && arg1.getKind() == Tree.Kind.CHAR_LITERAL) { + oldChar = (char) ((LiteralTree) arg0).getValue(); + newChar = (char) ((LiteralTree) arg1).getValue(); + } + } else { + ExpressionTree arg0 = tree.getArguments().get(0); + ExpressionTree arg1 = tree.getArguments().get(1); + if (arg0.getKind() == Tree.Kind.STRING_LITERAL + && arg1.getKind() == Tree.Kind.STRING_LITERAL) { + String const0 = (String) ((LiteralTree) arg0).getValue(); + String const1 = (String) ((LiteralTree) arg1).getValue(); + if (const0.length() == 1 && const1.length() == 1) { + oldChar = const0.charAt(0); + newChar = const1.charAt(0); + } + } + } + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + AnnotatedTypeMirror receiverType = getAnnotatedType(receiver); + if ((oldChar == '.' && newChar == '/') + && receiverType.getAnnotation(BinaryName.class) != null) { + type.replaceAnnotation(INTERNAL_FORM); + } else if ((oldChar == '/' && newChar == '.') + && receiverType.getAnnotation(InternalForm.class) != null) { + type.replaceAnnotation(BINARY_NAME); + } } else { - // Binary name if non-array, non-primitive, non-nested. - TypeMirror literalType = TreeUtils.typeOf(classExpr); - if (literalType.getKind() == TypeKind.DECLARED) { - TypeElement typeElt = TypesUtils.getTypeElement(literalType); - Element enclosing = typeElt.getEnclosingElement(); - if (enclosing == null || enclosing.getKind() == ElementKind.PACKAGE) { - type.replaceAnnotation( - isClassGetName ? DOT_SEPARATED_IDENTIFIERS : CANONICAL_NAME_AND_BINARY_NAME); + boolean isClassGetName = + TreeUtils.isMethodInvocation(tree, classGetName, processingEnv); + boolean isClassGetCanonicalName = + TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv); + if (isClassGetName || isClassGetCanonicalName) { + ExpressionTree receiver = TreeUtils.getReceiverTree(tree); + if (TreeUtils.isClassLiteral(receiver)) { + ExpressionTree classExpr = ((MemberSelectTree) receiver).getExpression(); + if (classExpr.getKind() == Tree.Kind.PRIMITIVE_TYPE) { + if (((PrimitiveTypeTree) classExpr).getPrimitiveTypeKind() + == TypeKind.VOID) { + // do nothing + } else { + type.replaceAnnotation(PRIMITIVE_TYPE); + } + } else { + // Binary name if non-array, non-primitive, non-nested. + TypeMirror literalType = TreeUtils.typeOf(classExpr); + if (literalType.getKind() == TypeKind.DECLARED) { + TypeElement typeElt = TypesUtils.getTypeElement(literalType); + Element enclosing = typeElt.getEnclosingElement(); + if (enclosing == null + || enclosing.getKind() == ElementKind.PACKAGE) { + type.replaceAnnotation( + isClassGetName + ? DOT_SEPARATED_IDENTIFIERS + : CANONICAL_NAME_AND_BINARY_NAME); + } + } + } + } } - } } - } - } - } - return super.visitMethodInvocation(tree, type); + return super.visitMethodInvocation(tree, type); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java index 770db52ca15..6b3bcfa4dfc 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureChecker.java @@ -1,6 +1,7 @@ package org.checkerframework.checker.signature; import com.sun.source.tree.CompilationUnitTree; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.StubFiles; @@ -15,9 +16,9 @@ @StubFiles({"javac.astub", "javaparser.astub"}) public final class SignatureChecker extends BaseTypeChecker { - // This method is needed only under MacOS, perhaps as a result of the - // broken Apple Java distribution. - public SignatureAnnotatedTypeFactory createFactory(CompilationUnitTree root) { - return new SignatureAnnotatedTypeFactory(this); - } + // This method is needed only under MacOS, perhaps as a result of the + // broken Apple Java distribution. + public SignatureAnnotatedTypeFactory createFactory(CompilationUnitTree root) { + return new SignatureAnnotatedTypeFactory(this); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java index fcefc408603..d9caf2cac54 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.signature; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -17,46 +16,50 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; +import javax.lang.model.element.ExecutableElement; + /** The transfer function for the Signature Checker. */ public class SignatureTransfer extends CFTransfer { - /** The annotated type factory for this transfer function. */ - private final SignatureAnnotatedTypeFactory atypeFactory; - - /** - * Create a new SignatureTransfer. - * - * @param analysis the analysis - */ - public SignatureTransfer(CFAnalysis analysis) { - super(analysis); - atypeFactory = (SignatureAnnotatedTypeFactory) analysis.getTypeFactory(); - } - - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - TransferResult superResult = super.visitMethodInvocation(n, in); - - MethodAccessNode target = n.getTarget(); - ExecutableElement method = target.getMethod(); - Node receiver = target.getReceiver(); - if (TypesUtils.isString(receiver.getType()) && ElementUtils.matchesElement(method, "isEmpty")) { - - AnnotatedTypeMirror receiverAtm = atypeFactory.getAnnotatedType(receiver.getTree()); - if (receiverAtm.hasAnnotation(CanonicalNameOrEmpty.class)) { - - CFStore thenStore = superResult.getRegularStore(); - CFStore elseStore = thenStore.copy(); - ConditionalTransferResult result = - new ConditionalTransferResult<>(superResult.getResultValue(), thenStore, elseStore); - // The refined expression is the receiver of the method call. - JavaExpression refinedExpr = JavaExpression.fromNode(receiver); - - elseStore.insertValue(refinedExpr, atypeFactory.CANONICAL_NAME); - return result; - } + /** The annotated type factory for this transfer function. */ + private final SignatureAnnotatedTypeFactory atypeFactory; + + /** + * Create a new SignatureTransfer. + * + * @param analysis the analysis + */ + public SignatureTransfer(CFAnalysis analysis) { + super(analysis); + atypeFactory = (SignatureAnnotatedTypeFactory) analysis.getTypeFactory(); + } + + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + TransferResult superResult = super.visitMethodInvocation(n, in); + + MethodAccessNode target = n.getTarget(); + ExecutableElement method = target.getMethod(); + Node receiver = target.getReceiver(); + if (TypesUtils.isString(receiver.getType()) + && ElementUtils.matchesElement(method, "isEmpty")) { + + AnnotatedTypeMirror receiverAtm = atypeFactory.getAnnotatedType(receiver.getTree()); + if (receiverAtm.hasAnnotation(CanonicalNameOrEmpty.class)) { + + CFStore thenStore = superResult.getRegularStore(); + CFStore elseStore = thenStore.copy(); + ConditionalTransferResult result = + new ConditionalTransferResult<>( + superResult.getResultValue(), thenStore, elseStore); + // The refined expression is the receiver of the method call. + JavaExpression refinedExpr = JavaExpression.fromNode(receiver); + + elseStore.insertValue(refinedExpr, atypeFactory.CANONICAL_NAME); + return result; + } + } + return superResult; } - return superResult; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java index 6a0c99cf025..b3b189cc238 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java @@ -7,13 +7,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.TreePath; -import java.io.Serializable; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signedness.qual.PolySigned; import org.checkerframework.checker.signedness.qual.Signed; @@ -46,6 +40,15 @@ import org.checkerframework.javacutil.TypeKindUtils; import org.checkerframework.javacutil.TypesUtils; +import java.io.Serializable; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * The type factory for the Signedness Checker. * @@ -53,386 +56,392 @@ */ public class SignednessAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @Signed annotation. */ - protected final AnnotationMirror SIGNED = AnnotationBuilder.fromClass(elements, Signed.class); - - /** The @Unsigned annotation. */ - protected final AnnotationMirror UNSIGNED = AnnotationBuilder.fromClass(elements, Unsigned.class); - - /** The @SignednessGlb annotation. Do not use @SignedPositive; use this instead. */ - protected final AnnotationMirror SIGNEDNESS_GLB = - AnnotationBuilder.fromClass(elements, SignednessGlb.class); - - /** The @SignedPositive annotation. */ - protected final AnnotationMirror SIGNED_POSITIVE = - AnnotationBuilder.fromClass(elements, SignedPositive.class); - - /** The @SignednessBottom annotation. */ - protected final AnnotationMirror SIGNEDNESS_BOTTOM = - AnnotationBuilder.fromClass(elements, SignednessBottom.class); - - /** The @PolySigned annotation. */ - protected final AnnotationMirror POLY_SIGNED = - AnnotationBuilder.fromClass(elements, PolySigned.class); - - /** The @NonNegative annotation of the Index Checker, as represented by the Value Checker. */ - private final AnnotationMirror INT_RANGE_FROM_NON_NEGATIVE = - AnnotationBuilder.fromClass(elements, IntRangeFromNonNegative.class); - - /** The @Positive annotation of the Index Checker, as represented by the Value Checker. */ - private final AnnotationMirror INT_RANGE_FROM_POSITIVE = - AnnotationBuilder.fromClass(elements, IntRangeFromPositive.class); - - /** The Serializable type mirror. */ - private final TypeMirror serializableTM = - elements.getTypeElement(Serializable.class.getCanonicalName()).asType(); - - /** The Comparable type mirror. */ - private final TypeMirror comparableTM = - elements.getTypeElement(Comparable.class.getCanonicalName()).asType(); - - /** The Number type mirror. */ - private final TypeMirror numberTM = - elements.getTypeElement(Number.class.getCanonicalName()).asType(); - - /** A set containing just {@code @Signed}. */ - private final AnnotationMirrorSet SIGNED_SINGLETON = new AnnotationMirrorSet(SIGNED); - - /** A set containing just {@code @Unsigned}. */ - private final AnnotationMirrorSet UNSIGNED_SINGLETON = new AnnotationMirrorSet(UNSIGNED); - - /** - * Create a SignednessAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this type factory - */ - public SignednessAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - addAliasedTypeAnnotation("jdk.jfr.Unsigned", UNSIGNED); - - postInit(); - } - - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - Tree.Kind treeKind = tree.getKind(); - if (treeKind == Tree.Kind.INT_LITERAL) { - int literalValue = (int) ((LiteralTree) tree).getValue(); - if (literalValue >= 0) { - type.replaceAnnotation(SIGNED_POSITIVE); - } else { - type.replaceAnnotation(SIGNEDNESS_GLB); - } - } else if (treeKind == Tree.Kind.LONG_LITERAL) { - long literalValue = (long) ((LiteralTree) tree).getValue(); - if (literalValue >= 0) { - type.replaceAnnotation(SIGNED_POSITIVE); - } else { - type.replaceAnnotation(SIGNEDNESS_GLB); - } - } else if (!isComputingAnnotatedTypeMirrorOfLhs()) { - addSignedPositiveAnnotation(tree, type); - } - super.addComputedTypeAnnotations(tree, type); - } - - /** - * Refines an integer expression to @SignedPositive if its value is within the signed positive - * range (i.e. its MSB is zero). Does not refine the type of cast expressions. - * - * @param tree an AST node, whose type may be refined - * @param type the type of the tree - */ - private void addSignedPositiveAnnotation(Tree tree, AnnotatedTypeMirror type) { - if (tree.getKind() == Tree.Kind.TYPE_CAST) { - return; - } - TypeMirror javaType = type.getUnderlyingType(); - TypeKind javaTypeKind = javaType.getKind(); - if (tree.getKind() == Tree.Kind.VARIABLE) { - return; - } - if (!(javaTypeKind == TypeKind.BYTE - || javaTypeKind == TypeKind.CHAR - || javaTypeKind == TypeKind.SHORT - || javaTypeKind == TypeKind.INT - || javaTypeKind == TypeKind.LONG)) { - return; - } - ValueAnnotatedTypeFactory valueFactory = getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotatedTypeMirror valueATM = valueFactory.getAnnotatedType(tree); - // These annotations are trusted rather than checked. Maybe have an option to - // disable using them? - if ((valueATM.hasAnnotation(INT_RANGE_FROM_NON_NEGATIVE) - || valueATM.hasAnnotation(INT_RANGE_FROM_POSITIVE)) - && type.hasAnnotation(SIGNED)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } else { - Range treeRange = ValueCheckerUtils.getPossibleValues(valueATM, valueFactory); - - if (treeRange != null) { - switch (javaType.getKind()) { - case BYTE: - case CHAR: - if (treeRange.isWithin(0, Byte.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - case SHORT: - if (treeRange.isWithin(0, Short.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - case INT: - if (treeRange.isWithin(0, Integer.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - case LONG: - if (treeRange.isWithin(0, Long.MAX_VALUE)) { - type.replaceAnnotation(SIGNED_POSITIVE); - } - break; - default: - // Nothing - } - } - } - } + /** The @Signed annotation. */ + protected final AnnotationMirror SIGNED = AnnotationBuilder.fromClass(elements, Signed.class); - @Override - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - assert annos.size() == 1; + /** The @Unsigned annotation. */ + protected final AnnotationMirror UNSIGNED = + AnnotationBuilder.fromClass(elements, Unsigned.class); - AnnotationMirrorSet result = new AnnotationMirrorSet(); - if (TypeKindUtils.isFloatingPoint(widenedTypeKind)) { - result.add(SIGNED); - return result; - } - if (widenedTypeKind == TypeKind.CHAR) { - result.add(UNSIGNED); - return result; - } - if ((widenedTypeKind == TypeKind.INT || widenedTypeKind == TypeKind.LONG) - && typeKind == TypeKind.CHAR) { - result.add(SIGNED_POSITIVE); - return result; - } - return annos; - } + /** The @SignednessGlb annotation. Do not use @SignedPositive; use this instead. */ + protected final AnnotationMirror SIGNEDNESS_GLB = + AnnotationBuilder.fromClass(elements, SignednessGlb.class); - @Override - public AnnotationMirrorSet getNarrowedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { - assert annos.size() == 1; + /** The @SignedPositive annotation. */ + protected final AnnotationMirror SIGNED_POSITIVE = + AnnotationBuilder.fromClass(elements, SignedPositive.class); - AnnotationMirrorSet result = new AnnotationMirrorSet(); + /** The @SignednessBottom annotation. */ + protected final AnnotationMirror SIGNEDNESS_BOTTOM = + AnnotationBuilder.fromClass(elements, SignednessBottom.class); - if (narrowedTypeKind == TypeKind.CHAR) { - result.add(SIGNED); - return result; - } + /** The @PolySigned annotation. */ + protected final AnnotationMirror POLY_SIGNED = + AnnotationBuilder.fromClass(elements, PolySigned.class); - return annos; - } + /** The @NonNegative annotation of the Index Checker, as represented by the Value Checker. */ + private final AnnotationMirror INT_RANGE_FROM_NON_NEGATIVE = + AnnotationBuilder.fromClass(elements, IntRangeFromNonNegative.class); - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new SignednessTreeAnnotator(this), super.createTreeAnnotator()); - } + /** The @Positive annotation of the Index Checker, as represented by the Value Checker. */ + private final AnnotationMirror INT_RANGE_FROM_POSITIVE = + AnnotationBuilder.fromClass(elements, IntRangeFromPositive.class); - @Override - public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { - if (TypesUtils.isCharType(tm)) { - return UNSIGNED_SINGLETON; - } else { - return SIGNED_SINGLETON; - } - } - - /** - * This TreeAnnotator ensures that: - * - *
            - *
          • boolean expressions are not given Unsigned or Signed annotations by {@link - * PropagationTreeAnnotator}, - *
          • shift results take on the type of their left operand, - *
          • the types of identifiers are refined based on the results of the Value Checker. - *
          • casts take types related to widening - *
          - */ - private class SignednessTreeAnnotator extends TreeAnnotator { - - public SignednessTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** The Serializable type mirror. */ + private final TypeMirror serializableTM = + elements.getTypeElement(Serializable.class.getCanonicalName()).asType(); + + /** The Comparable type mirror. */ + private final TypeMirror comparableTM = + elements.getTypeElement(Comparable.class.getCanonicalName()).asType(); + + /** The Number type mirror. */ + private final TypeMirror numberTM = + elements.getTypeElement(Number.class.getCanonicalName()).asType(); + + /** A set containing just {@code @Signed}. */ + private final AnnotationMirrorSet SIGNED_SINGLETON = new AnnotationMirrorSet(SIGNED); + + /** A set containing just {@code @Unsigned}. */ + private final AnnotationMirrorSet UNSIGNED_SINGLETON = new AnnotationMirrorSet(UNSIGNED); + + /** + * Create a SignednessAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this type factory + */ + public SignednessAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + addAliasedTypeAnnotation("jdk.jfr.Unsigned", UNSIGNED); + + postInit(); } @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - switch (tree.getKind()) { - case LEFT_SHIFT: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - TreePath path = getPath(tree); - if (path != null - && (SignednessShifts.isMaskedShiftEitherSignedness(tree, path) - || SignednessShifts.isCastedShiftEitherSignedness(tree, path))) { + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + Tree.Kind treeKind = tree.getKind(); + if (treeKind == Tree.Kind.INT_LITERAL) { + int literalValue = (int) ((LiteralTree) tree).getValue(); + if (literalValue >= 0) { + type.replaceAnnotation(SIGNED_POSITIVE); + } else { + type.replaceAnnotation(SIGNEDNESS_GLB); + } + } else if (treeKind == Tree.Kind.LONG_LITERAL) { + long literalValue = (long) ((LiteralTree) tree).getValue(); + if (literalValue >= 0) { + type.replaceAnnotation(SIGNED_POSITIVE); + } else { + type.replaceAnnotation(SIGNEDNESS_GLB); + } + } else if (!isComputingAnnotatedTypeMirrorOfLhs()) { + addSignedPositiveAnnotation(tree, type); + } + super.addComputedTypeAnnotations(tree, type); + } + + /** + * Refines an integer expression to @SignedPositive if its value is within the signed positive + * range (i.e. its MSB is zero). Does not refine the type of cast expressions. + * + * @param tree an AST node, whose type may be refined + * @param type the type of the tree + */ + private void addSignedPositiveAnnotation(Tree tree, AnnotatedTypeMirror type) { + if (tree.getKind() == Tree.Kind.TYPE_CAST) { + return; + } + TypeMirror javaType = type.getUnderlyingType(); + TypeKind javaTypeKind = javaType.getKind(); + if (tree.getKind() == Tree.Kind.VARIABLE) { + return; + } + if (!(javaTypeKind == TypeKind.BYTE + || javaTypeKind == TypeKind.CHAR + || javaTypeKind == TypeKind.SHORT + || javaTypeKind == TypeKind.INT + || javaTypeKind == TypeKind.LONG)) { + return; + } + ValueAnnotatedTypeFactory valueFactory = getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotatedTypeMirror valueATM = valueFactory.getAnnotatedType(tree); + // These annotations are trusted rather than checked. Maybe have an option to + // disable using them? + if ((valueATM.hasAnnotation(INT_RANGE_FROM_NON_NEGATIVE) + || valueATM.hasAnnotation(INT_RANGE_FROM_POSITIVE)) + && type.hasAnnotation(SIGNED)) { type.replaceAnnotation(SIGNED_POSITIVE); - } else { - AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); - type.replaceAnnotations(lht.getAnnotations()); - } - break; - default: - // Do nothing - } - return null; + } else { + Range treeRange = ValueCheckerUtils.getPossibleValues(valueATM, valueFactory); + + if (treeRange != null) { + switch (javaType.getKind()) { + case BYTE: + case CHAR: + if (treeRange.isWithin(0, Byte.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + case SHORT: + if (treeRange.isWithin(0, Short.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + case INT: + if (treeRange.isWithin(0, Integer.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + case LONG: + if (treeRange.isWithin(0, Long.MAX_VALUE)) { + type.replaceAnnotation(SIGNED_POSITIVE); + } + break; + default: + // Nothing + } + } + } } @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isStringCompoundConcatenation(tree)) { - if (TypesUtils.isCharType(TreeUtils.typeOf(tree.getExpression()))) { - type.replaceAnnotation(SIGNED); + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + assert annos.size() == 1; + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + if (TypeKindUtils.isFloatingPoint(widenedTypeKind)) { + result.add(SIGNED); + return result; + } + if (widenedTypeKind == TypeKind.CHAR) { + result.add(UNSIGNED); + return result; } - } - return null; + if ((widenedTypeKind == TypeKind.INT || widenedTypeKind == TypeKind.LONG) + && typeKind == TypeKind.CHAR) { + result.add(SIGNED_POSITIVE); + return result; + } + return annos; } @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - // Don't change the annotation on a cast with an explicit annotation. - if (TypesUtils.isCharType(type.getUnderlyingType())) { - type.replaceAnnotation(UNSIGNED); - } else if (type.getAnnotations().isEmpty() && !maybeIntegral(type)) { - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); - if ((type.getKind() != TypeKind.TYPEVAR || exprType.getKind() != TypeKind.TYPEVAR) - && !AnnotationUtils.containsSame(exprType.getEffectiveAnnotations(), UNSIGNED)) { - type.addAnnotation(SIGNED); + public AnnotationMirrorSet getNarrowedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { + assert annos.size() == 1; + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + + if (narrowedTypeKind == TypeKind.CHAR) { + result.add(SIGNED); + return result; } - } - log("SATF.visitTypeCast(%s, ...) final: %s%n", tree, type); - log("SATF: treeAnnotator=%s%n", treeAnnotator); - return null; + + return annos; } - } - - /** - * Returns true if {@code type}'s underlying type might be integral: it is a number, char, or a - * supertype of them. - * - * @param type a type - * @return true if {@code type}'s underlying type might be integral - */ - public boolean maybeIntegral(AnnotatedTypeMirror type) { - - TypeKind kind = type.getKind(); - - switch (kind) { - case BOOLEAN: - return false; - case BYTE: - case SHORT: - case INT: - case LONG: - case CHAR: - return true; - case FLOAT: - case DOUBLE: - return false; - - case DECLARED: - case TYPEVAR: - case WILDCARD: - TypeMirror erasedType = types.erasure(type.getUnderlyingType()); - return (TypesUtils.isBoxedPrimitive(erasedType) - || TypesUtils.isObject(erasedType) - || TypesUtils.isErasedSubtype(numberTM, erasedType, types) - || TypesUtils.isErasedSubtype(serializableTM, erasedType, types) - || TypesUtils.isErasedSubtype(comparableTM, erasedType, types)); - - default: - return false; + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + new SignednessTreeAnnotator(this), super.createTreeAnnotator()); } - } - - @Override - protected void adaptGetClassReturnTypeToReceiver( - AnnotatedExecutableType getClassType, AnnotatedTypeMirror receiverType, ExpressionTree tree) { - super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); - // Make the captured wildcard always @Signed, regardless of the declared type. - AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); - List typeArgs = returnAdt.getTypeArguments(); - AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); - classWildcardArg.getUpperBound().replaceAnnotation(SIGNED); - } - - @Override - protected void addAnnotationsFromDefaultForType( - @Nullable Element element, AnnotatedTypeMirror type) { - TypeMirror underlying = type.getUnderlyingType(); - if (TypesUtils.isFloatingPrimitive(underlying) - || TypesUtils.isBoxedFloating(underlying) - || TypesUtils.isCharType(underlying)) { - // Floats are always signed and chars are always unsigned. - super.addAnnotationsFromDefaultForType(null, type); - } else { - super.addAnnotationsFromDefaultForType(element, type); + + @Override + public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { + if (TypesUtils.isCharType(tm)) { + return UNSIGNED_SINGLETON; + } else { + return SIGNED_SINGLETON; + } } - } - - /** - * Requires that, when two formal parameter types are annotated with {@code @PolySigned}, the two - * arguments must have the same signedness type annotation. - */ - // Not static because it references SIGNEDNESS_BOTTOM. - private class SignednessQualifierPolymorphism extends DefaultQualifierPolymorphism { + /** - * Creates a {@link SignednessQualifierPolymorphism}. + * This TreeAnnotator ensures that: * - * @param env the processing environment - * @param factory the factory for the current checker + *
            + *
          • boolean expressions are not given Unsigned or Signed annotations by {@link + * PropagationTreeAnnotator}, + *
          • shift results take on the type of their left operand, + *
          • the types of identifiers are refined based on the results of the Value Checker. + *
          • casts take types related to widening + *
          */ - public SignednessQualifierPolymorphism( - ProcessingEnvironment env, AnnotatedTypeFactory factory) { - super(env, factory); + private class SignednessTreeAnnotator extends TreeAnnotator { + + public SignednessTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + switch (tree.getKind()) { + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + TreePath path = getPath(tree); + if (path != null + && (SignednessShifts.isMaskedShiftEitherSignedness(tree, path) + || SignednessShifts.isCastedShiftEitherSignedness( + tree, path))) { + type.replaceAnnotation(SIGNED_POSITIVE); + } else { + AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); + type.replaceAnnotations(lht.getAnnotations()); + } + break; + default: + // Do nothing + } + return null; + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringCompoundConcatenation(tree)) { + if (TypesUtils.isCharType(TreeUtils.typeOf(tree.getExpression()))) { + type.replaceAnnotation(SIGNED); + } + } + return null; + } + + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + // Don't change the annotation on a cast with an explicit annotation. + if (TypesUtils.isCharType(type.getUnderlyingType())) { + type.replaceAnnotation(UNSIGNED); + } else if (type.getAnnotations().isEmpty() && !maybeIntegral(type)) { + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); + if ((type.getKind() != TypeKind.TYPEVAR || exprType.getKind() != TypeKind.TYPEVAR) + && !AnnotationUtils.containsSame( + exprType.getEffectiveAnnotations(), UNSIGNED)) { + type.addAnnotation(SIGNED); + } + } + log("SATF.visitTypeCast(%s, ...) final: %s%n", tree, type); + log("SATF: treeAnnotator=%s%n", treeAnnotator); + return null; + } } /** - * Combines the two annotations. If they are comparable, return the lub. If they are - * incomparable, return @SignednessBottom. + * Returns true if {@code type}'s underlying type might be integral: it is a number, char, or a + * supertype of them. * - * @param polyQual the polymorphic qualifier - * @param a1 the first annotation to compare - * @param a2 the second annotation to compare - * @return the lub, unless the annotations are incomparable + * @param type a type + * @return true if {@code type}'s underlying type might be integral */ + public boolean maybeIntegral(AnnotatedTypeMirror type) { + + TypeKind kind = type.getKind(); + + switch (kind) { + case BOOLEAN: + return false; + case BYTE: + case SHORT: + case INT: + case LONG: + case CHAR: + return true; + case FLOAT: + case DOUBLE: + return false; + + case DECLARED: + case TYPEVAR: + case WILDCARD: + TypeMirror erasedType = types.erasure(type.getUnderlyingType()); + return (TypesUtils.isBoxedPrimitive(erasedType) + || TypesUtils.isObject(erasedType) + || TypesUtils.isErasedSubtype(numberTM, erasedType, types) + || TypesUtils.isErasedSubtype(serializableTM, erasedType, types) + || TypesUtils.isErasedSubtype(comparableTM, erasedType, types)); + + default: + return false; + } + } + @Override - protected AnnotationMirror combine( - AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == null) { - return a2; - } else if (a2 == null) { - return a1; - } else if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else if (qualHierarchy.isSubtypeQualifiersOnly(a1, a2)) { - return a2; - } else if (qualHierarchy.isSubtypeQualifiersOnly(a2, a1)) { - return a1; - } else - // The two annotations are incomparable - // TODO: Issue a warning at the proper code location. - // TODO: Returning bottom leads to obscure error messages. It would probably be - // better to issue a warning in this method, then return lub as usual. - return SIGNEDNESS_BOTTOM; + protected void adaptGetClassReturnTypeToReceiver( + AnnotatedExecutableType getClassType, + AnnotatedTypeMirror receiverType, + ExpressionTree tree) { + super.adaptGetClassReturnTypeToReceiver(getClassType, receiverType, tree); + // Make the captured wildcard always @Signed, regardless of the declared type. + AnnotatedDeclaredType returnAdt = (AnnotatedDeclaredType) getClassType.getReturnType(); + List typeArgs = returnAdt.getTypeArguments(); + AnnotatedTypeVariable classWildcardArg = (AnnotatedTypeVariable) typeArgs.get(0); + classWildcardArg.getUpperBound().replaceAnnotation(SIGNED); } - } - @Override - protected QualifierPolymorphism createQualifierPolymorphism() { - return new SignednessQualifierPolymorphism(processingEnv, this); - } + @Override + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + TypeMirror underlying = type.getUnderlyingType(); + if (TypesUtils.isFloatingPrimitive(underlying) + || TypesUtils.isBoxedFloating(underlying) + || TypesUtils.isCharType(underlying)) { + // Floats are always signed and chars are always unsigned. + super.addAnnotationsFromDefaultForType(null, type); + } else { + super.addAnnotationsFromDefaultForType(element, type); + } + } + + /** + * Requires that, when two formal parameter types are annotated with {@code @PolySigned}, the + * two arguments must have the same signedness type annotation. + */ + // Not static because it references SIGNEDNESS_BOTTOM. + private class SignednessQualifierPolymorphism extends DefaultQualifierPolymorphism { + /** + * Creates a {@link SignednessQualifierPolymorphism}. + * + * @param env the processing environment + * @param factory the factory for the current checker + */ + public SignednessQualifierPolymorphism( + ProcessingEnvironment env, AnnotatedTypeFactory factory) { + super(env, factory); + } + + /** + * Combines the two annotations. If they are comparable, return the lub. If they are + * incomparable, return @SignednessBottom. + * + * @param polyQual the polymorphic qualifier + * @param a1 the first annotation to compare + * @param a2 the second annotation to compare + * @return the lub, unless the annotations are incomparable + */ + @Override + protected AnnotationMirror combine( + AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == null) { + return a2; + } else if (a2 == null) { + return a1; + } else if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else if (qualHierarchy.isSubtypeQualifiersOnly(a1, a2)) { + return a2; + } else if (qualHierarchy.isSubtypeQualifiersOnly(a2, a1)) { + return a1; + } else + // The two annotations are incomparable + // TODO: Issue a warning at the proper code location. + // TODO: Returning bottom leads to obscure error messages. It would probably be + // better to issue a warning in this method, then return lub as usual. + return SIGNEDNESS_BOTTOM; + } + } + + @Override + protected QualifierPolymorphism createQualifierPolymorphism() { + return new SignednessQualifierPolymorphism(processingEnv, this); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java index 8c6e21c6130..a7823bd4568 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessChecker.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.signedness; -import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.qual.StubFiles; +import java.util.Set; + /** * A type-checker that prevents mixing of unsigned and signed values, and prevents meaningless * operations on unsigned values. @@ -15,25 +16,25 @@ // Character and char are omitted here because they are always @Unsigned, and the user is not // allowed to write @Signed or @Unsigned on them. @RelevantJavaTypes({ - Byte.class, - Short.class, - Integer.class, - Long.class, - byte.class, - short.class, - int.class, - long.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + byte.class, + short.class, + int.class, + long.class, }) @StubFiles({"junit-assertions.astub"}) public class SignednessChecker extends BaseTypeChecker { - /** Creates a new SignednessChecker. */ - public SignednessChecker() {} + /** Creates a new SignednessChecker. */ + public SignednessChecker() {} - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java index b26663dbbd5..69758906971 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessShifts.java @@ -8,7 +8,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.TreePath; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.TreePathUtil; @@ -16,6 +16,8 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.IPair; +import javax.lang.model.type.TypeKind; + /** * This file contains code to special-case shifts whose result does not depend on the MSB of the * first argument, due to subsequent masking or casts. @@ -24,279 +26,279 @@ */ public class SignednessShifts { - /** Do not instantiate SignednessShifts. */ - private SignednessShifts() { - throw new Error("Do not instantiate SignednessShifts"); - } - - /** - * Returns true iff the given tree node is a mask operation (& or |). - * - * @param tree a tree to test - * @return true iff node is a mask operation (& or |) - */ - private static boolean isMask(Tree tree) { - Tree.Kind kind = tree.getKind(); - - return kind == Tree.Kind.AND || kind == Tree.Kind.OR; - } - - // TODO: Return a TypeKind rather than a PrimitiveTypeTree? - /** - * Returns the type of a primitive cast, or null if the argument is not a cast to a primitive. - * - * @param tree a tree that might be a cast to a primitive - * @return type of a primitive cast, or null if not a cast to a primitive - */ - private static @Nullable PrimitiveTypeTree primitiveTypeCast(Tree tree) { - if (tree.getKind() != Tree.Kind.TYPE_CAST) { - return null; + /** Do not instantiate SignednessShifts. */ + private SignednessShifts() { + throw new Error("Do not instantiate SignednessShifts"); } - TypeCastTree cast = (TypeCastTree) tree; - Tree castType = cast.getType(); + /** + * Returns true iff the given tree node is a mask operation (& or |). + * + * @param tree a tree to test + * @return true iff node is a mask operation (& or |) + */ + private static boolean isMask(Tree tree) { + Tree.Kind kind = tree.getKind(); - Tree underlyingType; - if (castType.getKind() == Tree.Kind.ANNOTATED_TYPE) { - underlyingType = ((AnnotatedTypeTree) castType).getUnderlyingType(); - } else { - underlyingType = castType; + return kind == Tree.Kind.AND || kind == Tree.Kind.OR; } - if (underlyingType.getKind() != Tree.Kind.PRIMITIVE_TYPE) { - return null; + // TODO: Return a TypeKind rather than a PrimitiveTypeTree? + /** + * Returns the type of a primitive cast, or null if the argument is not a cast to a primitive. + * + * @param tree a tree that might be a cast to a primitive + * @return type of a primitive cast, or null if not a cast to a primitive + */ + private static @Nullable PrimitiveTypeTree primitiveTypeCast(Tree tree) { + if (tree.getKind() != Tree.Kind.TYPE_CAST) { + return null; + } + + TypeCastTree cast = (TypeCastTree) tree; + Tree castType = cast.getType(); + + Tree underlyingType; + if (castType.getKind() == Tree.Kind.ANNOTATED_TYPE) { + underlyingType = ((AnnotatedTypeTree) castType).getUnderlyingType(); + } else { + underlyingType = castType; + } + + if (underlyingType.getKind() != Tree.Kind.PRIMITIVE_TYPE) { + return null; + } + + return (PrimitiveTypeTree) underlyingType; } - return (PrimitiveTypeTree) underlyingType; - } - - /** - * Returns true iff the given tree is a literal. - * - * @param expr a tree to test - * @return true iff expr is a literal - */ - private static boolean isLiteral(ExpressionTree expr) { - return expr instanceof LiteralTree; - } - - /** - * Returns the long value of an Integer or a Long - * - * @param obj either an Integer or a Long - * @return the long value of obj - */ - private static long getLong(Object obj) { - return ((Number) obj).longValue(); - } - - /** - * Given a masking operation of the form {@code expr & maskLit} or {@code expr | maskLit}, return - * true iff the masking operation results in the same output regardless of the value of the - * shiftAmount most significant bits of expr. This is if the shiftAmount most significant bits of - * mask are 0 for AND, and 1 for OR. For example, assuming that shiftAmount is 4, the following is - * true about AND and OR masks: - * - *

          {@code expr & 0x0[anything] == 0x0[something] ;} - * - *

          {@code expr | 0xF[anything] == 0xF[something] ;} - * - * @param maskKind the kind of mask (AND or OR) - * @param shiftAmountLit the LiteralTree whose value is shiftAmount - * @param maskLit the LiteralTree whose value is mask - * @param shiftedTypeKind the type of shift operation; int or long - * @return true iff the shiftAmount most significant bits of mask are 0 for AND, and 1 for OR - */ - private static boolean maskIgnoresMSB( - Tree.Kind maskKind, - LiteralTree shiftAmountLit, - LiteralTree maskLit, - TypeKind shiftedTypeKind) { - long shiftAmount = getLong(shiftAmountLit.getValue()); - - // Shift of zero is a nop - if (shiftAmount == 0) { - return true; + /** + * Returns true iff the given tree is a literal. + * + * @param expr a tree to test + * @return true iff expr is a literal + */ + private static boolean isLiteral(ExpressionTree expr) { + return expr instanceof LiteralTree; } - long mask = getLong(maskLit.getValue()); - // Shift the shiftAmount most significant bits to become the shiftAmount least significant - // bits, zeroing out the rest. - if (shiftedTypeKind == TypeKind.INT) { - mask <<= 32; - } - mask >>>= (64 - shiftAmount); - - if (maskKind == Tree.Kind.AND) { - // Check that the shiftAmount most significant bits of the mask were 0. - return mask == 0; - } else if (maskKind == Tree.Kind.OR) { - // Check that the shiftAmount most significant bits of the mask were 1. - return mask == (1 << shiftAmount) - 1; - } else { - throw new TypeSystemError("Invalid Masking Operation"); - } - } - - /** - * Given a casted right shift of the form {@code (type) (baseExpr >> shiftAmount)} or {@code - * (type) (baseExpr >>> shiftAmount)}, return true iff the expression's value is the same - * regardless of the type of right shift (signed or unsigned). This is true if the cast ignores - * the shiftAmount most significant bits of the shift result -- that is, if the cast ignores all - * the new bits that the right shift introduced on the left. - * - *

          For example, the function returns true for - * - *

          {@code (short) (myInt >> 16)}
          - * - * and for - * - *
          {@code (short) (myInt >>> 16)}
          - * - * because these two expressions are guaranteed to have the same result. - * - * @param shiftTypeKind the kind of the type of the shift literal (BYTE, CHAR, SHORT, INT, or - * LONG) - * @param castTypeKind the kind of the cast target type (BYTE, CHAR, SHORT, INT, or LONG) - * @param shiftAmountLit the LiteralTree whose value is shiftAmount - * @return true iff introduced bits are discarded - */ - private static boolean castIgnoresMSB( - TypeKind shiftTypeKind, TypeKind castTypeKind, LiteralTree shiftAmountLit) { - - // Determine number of bits in the shift type, note shifts upcast to int. - // Also determine the shift amount as it is dependent on the shift type. - long shiftBits; - long shiftAmount; - switch (shiftTypeKind) { - case INT: - shiftBits = 32; - // When the LHS of the shift is an int, the 5 lower order bits of the RHS are used. - shiftAmount = 0x1F & getLong(shiftAmountLit.getValue()); - break; - case LONG: - shiftBits = 64; - // When the LHS of the shift is a long, the 6 lower order bits of the RHS are used. - shiftAmount = 0x3F & getLong(shiftAmountLit.getValue()); - break; - default: - throw new TypeSystemError("Invalid shift type"); + /** + * Returns the long value of an Integer or a Long + * + * @param obj either an Integer or a Long + * @return the long value of obj + */ + private static long getLong(Object obj) { + return ((Number) obj).longValue(); } - // Determine number of bits in the cast type - long castBits; - switch (castTypeKind) { - case BYTE: - castBits = 8; - break; - case CHAR: - castBits = 8; - break; - case SHORT: - castBits = 16; - break; - case INT: - castBits = 32; - break; - case LONG: - castBits = 64; - break; - default: - throw new TypeSystemError("Invalid cast target"); + /** + * Given a masking operation of the form {@code expr & maskLit} or {@code expr | maskLit}, + * return true iff the masking operation results in the same output regardless of the value of + * the shiftAmount most significant bits of expr. This is if the shiftAmount most significant + * bits of mask are 0 for AND, and 1 for OR. For example, assuming that shiftAmount is 4, the + * following is true about AND and OR masks: + * + *

          {@code expr & 0x0[anything] == 0x0[something] ;} + * + *

          {@code expr | 0xF[anything] == 0xF[something] ;} + * + * @param maskKind the kind of mask (AND or OR) + * @param shiftAmountLit the LiteralTree whose value is shiftAmount + * @param maskLit the LiteralTree whose value is mask + * @param shiftedTypeKind the type of shift operation; int or long + * @return true iff the shiftAmount most significant bits of mask are 0 for AND, and 1 for OR + */ + private static boolean maskIgnoresMSB( + Tree.Kind maskKind, + LiteralTree shiftAmountLit, + LiteralTree maskLit, + TypeKind shiftedTypeKind) { + long shiftAmount = getLong(shiftAmountLit.getValue()); + + // Shift of zero is a nop + if (shiftAmount == 0) { + return true; + } + + long mask = getLong(maskLit.getValue()); + // Shift the shiftAmount most significant bits to become the shiftAmount least significant + // bits, zeroing out the rest. + if (shiftedTypeKind == TypeKind.INT) { + mask <<= 32; + } + mask >>>= (64 - shiftAmount); + + if (maskKind == Tree.Kind.AND) { + // Check that the shiftAmount most significant bits of the mask were 0. + return mask == 0; + } else if (maskKind == Tree.Kind.OR) { + // Check that the shiftAmount most significant bits of the mask were 1. + return mask == (1 << shiftAmount) - 1; + } else { + throw new TypeSystemError("Invalid Masking Operation"); + } } - long bitsDiscarded = shiftBits - castBits; - - return shiftAmount <= bitsDiscarded || shiftAmount == 0; - } - - /** - * Returns true if a right shift operation, {@code >>} or {@code >>>}, is masked with a masking - * operation of the form {@code shiftExpr & maskLit} or {@code shiftExpr | maskLit} such that the - * mask renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by destroying the bits - * duplicated into the shift result. For example, the following pairs of right shifts on {@code - * byte b} both produce the same results under any input, because of their masks: - * - *

          {@code (b >> 4) & 0x0F == (b >>> 4) & 0x0F;} - * - *

          {@code (b >> 4) | 0xF0 == (b >>> 4) | 0xF0;} - * - * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} - * @param path the path to {@code shiftExpr} - * @return true iff the right shift is masked such that a signed or unsigned right shift has the - * same effect - */ - /*package-private*/ static boolean isMaskedShiftEitherSignedness( - BinaryTree shiftExpr, TreePath path) { - IPair enclosingPair = TreePathUtil.enclosingNonParen(path); - // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr - Tree enclosing = enclosingPair.first; - // enclosingChild is a child of enclosing: shiftExpr or a parenthesized version of it. - @SuppressWarnings("interning:assignment.type.incompatible") // comparing AST nodes - @InternedDistinct Tree enclosingChild = enclosingPair.second; - - if (!isMask(enclosing)) { - return false; + /** + * Given a casted right shift of the form {@code (type) (baseExpr >> shiftAmount)} or {@code + * (type) (baseExpr >>> shiftAmount)}, return true iff the expression's value is the same + * regardless of the type of right shift (signed or unsigned). This is true if the cast ignores + * the shiftAmount most significant bits of the shift result -- that is, if the cast ignores all + * the new bits that the right shift introduced on the left. + * + *

          For example, the function returns true for + * + *

          {@code (short) (myInt >> 16)}
          + * + * and for + * + *
          {@code (short) (myInt >>> 16)}
          + * + * because these two expressions are guaranteed to have the same result. + * + * @param shiftTypeKind the kind of the type of the shift literal (BYTE, CHAR, SHORT, INT, or + * LONG) + * @param castTypeKind the kind of the cast target type (BYTE, CHAR, SHORT, INT, or LONG) + * @param shiftAmountLit the LiteralTree whose value is shiftAmount + * @return true iff introduced bits are discarded + */ + private static boolean castIgnoresMSB( + TypeKind shiftTypeKind, TypeKind castTypeKind, LiteralTree shiftAmountLit) { + + // Determine number of bits in the shift type, note shifts upcast to int. + // Also determine the shift amount as it is dependent on the shift type. + long shiftBits; + long shiftAmount; + switch (shiftTypeKind) { + case INT: + shiftBits = 32; + // When the LHS of the shift is an int, the 5 lower order bits of the RHS are used. + shiftAmount = 0x1F & getLong(shiftAmountLit.getValue()); + break; + case LONG: + shiftBits = 64; + // When the LHS of the shift is a long, the 6 lower order bits of the RHS are used. + shiftAmount = 0x3F & getLong(shiftAmountLit.getValue()); + break; + default: + throw new TypeSystemError("Invalid shift type"); + } + + // Determine number of bits in the cast type + long castBits; + switch (castTypeKind) { + case BYTE: + castBits = 8; + break; + case CHAR: + castBits = 8; + break; + case SHORT: + castBits = 16; + break; + case INT: + castBits = 32; + break; + case LONG: + castBits = 64; + break; + default: + throw new TypeSystemError("Invalid cast target"); + } + + long bitsDiscarded = shiftBits - castBits; + + return shiftAmount <= bitsDiscarded || shiftAmount == 0; } - BinaryTree maskExpr = (BinaryTree) enclosing; - ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); - - // Determine which child of maskExpr leads to shiftExpr. The other one is the mask. - ExpressionTree mask = - maskExpr.getRightOperand() == enclosingChild - ? maskExpr.getLeftOperand() - : maskExpr.getRightOperand(); - - // Strip away the parentheses from the mask if any exist - mask = TreeUtils.withoutParens(mask); - - if (!isLiteral(shiftAmountExpr) || !isLiteral(mask)) { - return false; - } - - LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; - LiteralTree maskLit = (LiteralTree) mask; - - return maskIgnoresMSB( - maskExpr.getKind(), shiftLit, maskLit, TreeUtils.typeOf(shiftExpr).getKind()); - } - - /** - * Returns true if a right shift operation, {@code >>} or {@code >>>}, is type casted such that - * the cast renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by discarding the - * bits duplicated into the shift result. For example, the following pair of right shifts on - * {@code short s} both produce the same results under any input, because of type casting: - * - *

          {@code (byte)(s >> 8) == (byte)(b >>> 8);} - * - * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} - * @param path the path to {@code shiftExpr} - * @return true iff the right shift is type casted such that a signed or unsigned right shift has - * the same effect - */ - /*package-private*/ static boolean isCastedShiftEitherSignedness( - BinaryTree shiftExpr, TreePath path) { - // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr - Tree enclosing = TreePathUtil.enclosingNonParen(path).first; - - PrimitiveTypeTree castPrimitiveType = primitiveTypeCast(enclosing); - if (castPrimitiveType == null) { - return false; + /** + * Returns true if a right shift operation, {@code >>} or {@code >>>}, is masked with a masking + * operation of the form {@code shiftExpr & maskLit} or {@code shiftExpr | maskLit} such that + * the mask renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by destroying + * the bits duplicated into the shift result. For example, the following pairs of right shifts + * on {@code byte b} both produce the same results under any input, because of their masks: + * + *

          {@code (b >> 4) & 0x0F == (b >>> 4) & 0x0F;} + * + *

          {@code (b >> 4) | 0xF0 == (b >>> 4) | 0xF0;} + * + * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} + * @param path the path to {@code shiftExpr} + * @return true iff the right shift is masked such that a signed or unsigned right shift has the + * same effect + */ + /*package-private*/ static boolean isMaskedShiftEitherSignedness( + BinaryTree shiftExpr, TreePath path) { + IPair enclosingPair = TreePathUtil.enclosingNonParen(path); + // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr + Tree enclosing = enclosingPair.first; + // enclosingChild is a child of enclosing: shiftExpr or a parenthesized version of it. + @SuppressWarnings("interning:assignment.type.incompatible") // comparing AST nodes + @InternedDistinct Tree enclosingChild = enclosingPair.second; + + if (!isMask(enclosing)) { + return false; + } + + BinaryTree maskExpr = (BinaryTree) enclosing; + ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); + + // Determine which child of maskExpr leads to shiftExpr. The other one is the mask. + ExpressionTree mask = + maskExpr.getRightOperand() == enclosingChild + ? maskExpr.getLeftOperand() + : maskExpr.getRightOperand(); + + // Strip away the parentheses from the mask if any exist + mask = TreeUtils.withoutParens(mask); + + if (!isLiteral(shiftAmountExpr) || !isLiteral(mask)) { + return false; + } + + LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; + LiteralTree maskLit = (LiteralTree) mask; + + return maskIgnoresMSB( + maskExpr.getKind(), shiftLit, maskLit, TreeUtils.typeOf(shiftExpr).getKind()); } - TypeKind castTypeKind = castPrimitiveType.getPrimitiveTypeKind(); - - // Determine the type of the shift result - TypeKind shiftTypeKind = TreeUtils.typeOf(shiftExpr).getKind(); - // Determine shift literal - ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); - if (!isLiteral(shiftAmountExpr)) { - return false; + /** + * Returns true if a right shift operation, {@code >>} or {@code >>>}, is type casted such that + * the cast renders the shift signedness ({@code >>} vs {@code >>>}) irrelevant by discarding + * the bits duplicated into the shift result. For example, the following pair of right shifts on + * {@code short s} both produce the same results under any input, because of type casting: + * + *

          {@code (byte)(s >> 8) == (byte)(b >>> 8);} + * + * @param shiftExpr a right shift expression: {@code expr1 >> expr2} or {@code expr1 >>> expr2} + * @param path the path to {@code shiftExpr} + * @return true iff the right shift is type casted such that a signed or unsigned right shift + * has the same effect + */ + /*package-private*/ static boolean isCastedShiftEitherSignedness( + BinaryTree shiftExpr, TreePath path) { + // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr + Tree enclosing = TreePathUtil.enclosingNonParen(path).first; + + PrimitiveTypeTree castPrimitiveType = primitiveTypeCast(enclosing); + if (castPrimitiveType == null) { + return false; + } + TypeKind castTypeKind = castPrimitiveType.getPrimitiveTypeKind(); + + // Determine the type of the shift result + TypeKind shiftTypeKind = TreeUtils.typeOf(shiftExpr).getKind(); + + // Determine shift literal + ExpressionTree shiftAmountExpr = shiftExpr.getRightOperand(); + if (!isLiteral(shiftAmountExpr)) { + return false; + } + LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; + + boolean result = castIgnoresMSB(shiftTypeKind, castTypeKind, shiftLit); + return result; } - LiteralTree shiftLit = (LiteralTree) shiftAmountExpr; - - boolean result = castIgnoresMSB(shiftTypeKind, castTypeKind, shiftLit); - return result; - } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java index 6d61a021e16..38ce6a96736 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java @@ -6,9 +6,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.interning.InterningVisitor; import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.checker.signedness.qual.PolySigned; @@ -24,6 +22,10 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.IPair; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + /** * The SignednessVisitor enforces the Signedness Checker rules. These rules are described in the * Checker Framework Manual. @@ -32,341 +34,365 @@ */ public class SignednessVisitor extends BaseTypeVisitor { - public SignednessVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * Returns true if an annotated type is annotated as {@link Unsigned} or {@link PolySigned} - * - * @param type the annotated type to be checked - * @return true if the annotated type is annotated as {@link Unsigned} or {@link PolySigned} - */ - private boolean hasUnsignedAnnotation(AnnotatedTypeMirror type) { - return type.hasAnnotation(Unsigned.class) || type.hasAnnotation(PolySigned.class); - } - - /** - * Returns true if an annotated type is annotated as {@link Signed} or {@link PolySigned} - * - * @param type the annotated type to be checked - * @return true if the annotated type is annotated as {@link Signed} or {@link PolySigned} - */ - private boolean hasSignedAnnotation(AnnotatedTypeMirror type) { - return type.hasAnnotation(Signed.class) || type.hasAnnotation(PolySigned.class); - } - - /** - * Enforces the following rules on binary operations involving Unsigned and Signed types: - * - *

            - *
          • Do not allow any Unsigned types or PolySigned types in {@literal {/, %}} operations. - *
          • Do not allow signed right shift {@literal {>>}} on an Unsigned type or a PolySigned type. - *
          • Do not allow unsigned right shift {@literal {>>>}} on a Signed type or a PolySigned type. - *
          • Allow any left shift {@literal {<<}}. - *
          • Do not allow non-equality comparisons {@literal {<, <=, >, >=}} on Unsigned types or - * PolySigned types. - *
          • Do not allow the mixing of Signed and Unsigned types. - *
          - */ - @Override - public Void visitBinary(BinaryTree tree, Void p) { - // Used in diagnostic messages. - ExpressionTree leftOp = tree.getLeftOperand(); - ExpressionTree rightOp = tree.getRightOperand(); - - IPair argTypes = - atypeFactory.binaryTreeArgTypes(tree); - AnnotatedTypeMirror leftOpType = argTypes.first; - AnnotatedTypeMirror rightOpType = argTypes.second; - - Tree.Kind kind = tree.getKind(); - - switch (kind) { - case DIVIDE: - case REMAINDER: - if (hasUnsignedAnnotation(leftOpType)) { - checker.reportError(leftOp, "operation.unsignedlhs", kind, leftOpType, rightOpType); - } else if (hasUnsignedAnnotation(rightOpType)) { - checker.reportError(rightOp, "operation.unsignedrhs", kind, leftOpType, rightOpType); - } - break; + public SignednessVisitor(BaseTypeChecker checker) { + super(checker); + } - case RIGHT_SHIFT: - if (hasUnsignedAnnotation(leftOpType) - && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) - && !SignednessShifts.isCastedShiftEitherSignedness(tree, getCurrentPath())) { - checker.reportError(leftOp, "shift.signed", kind, leftOpType, rightOpType); - } - break; + /** + * Returns true if an annotated type is annotated as {@link Unsigned} or {@link PolySigned} + * + * @param type the annotated type to be checked + * @return true if the annotated type is annotated as {@link Unsigned} or {@link PolySigned} + */ + private boolean hasUnsignedAnnotation(AnnotatedTypeMirror type) { + return type.hasAnnotation(Unsigned.class) || type.hasAnnotation(PolySigned.class); + } - case UNSIGNED_RIGHT_SHIFT: - if (hasSignedAnnotation(leftOpType) - && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) - && !SignednessShifts.isCastedShiftEitherSignedness(tree, getCurrentPath())) { - checker.reportError(leftOp, "shift.unsigned", kind, leftOpType, rightOpType); - } - break; - - case LEFT_SHIFT: - break; - - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case LESS_THAN: - case LESS_THAN_EQUAL: - if (hasUnsignedAnnotation(leftOpType)) { - checker.reportError(leftOp, "comparison.unsignedlhs", leftOpType, rightOpType); - } else if (hasUnsignedAnnotation(rightOpType)) { - checker.reportError(rightOp, "comparison.unsignedrhs", leftOpType, rightOpType); - } - break; + /** + * Returns true if an annotated type is annotated as {@link Signed} or {@link PolySigned} + * + * @param type the annotated type to be checked + * @return true if the annotated type is annotated as {@link Signed} or {@link PolySigned} + */ + private boolean hasSignedAnnotation(AnnotatedTypeMirror type) { + return type.hasAnnotation(Signed.class) || type.hasAnnotation(PolySigned.class); + } - case EQUAL_TO: - case NOT_EQUAL_TO: - if (!atypeFactory.maybeIntegral(leftOpType) || !atypeFactory.maybeIntegral(rightOpType)) { - break; - } - if (leftOpType.hasEffectiveAnnotation(Unsigned.class) - && rightOpType.hasEffectiveAnnotation(Signed.class)) { - checker.reportError(tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); - } else if (leftOpType.hasEffectiveAnnotation(Signed.class) - && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { - checker.reportError(tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); + /** + * Enforces the following rules on binary operations involving Unsigned and Signed types: + * + *
            + *
          • Do not allow any Unsigned types or PolySigned types in {@literal {/, %}} operations. + *
          • Do not allow signed right shift {@literal {>>}} on an Unsigned type or a PolySigned + * type. + *
          • Do not allow unsigned right shift {@literal {>>>}} on a Signed type or a PolySigned + * type. + *
          • Allow any left shift {@literal {<<}}. + *
          • Do not allow non-equality comparisons {@literal {<, <=, >, >=}} on Unsigned types or + * PolySigned types. + *
          • Do not allow the mixing of Signed and Unsigned types. + *
          + */ + @Override + public Void visitBinary(BinaryTree tree, Void p) { + // Used in diagnostic messages. + ExpressionTree leftOp = tree.getLeftOperand(); + ExpressionTree rightOp = tree.getRightOperand(); + + IPair argTypes = + atypeFactory.binaryTreeArgTypes(tree); + AnnotatedTypeMirror leftOpType = argTypes.first; + AnnotatedTypeMirror rightOpType = argTypes.second; + + Tree.Kind kind = tree.getKind(); + + switch (kind) { + case DIVIDE: + case REMAINDER: + if (hasUnsignedAnnotation(leftOpType)) { + checker.reportError( + leftOp, "operation.unsignedlhs", kind, leftOpType, rightOpType); + } else if (hasUnsignedAnnotation(rightOpType)) { + checker.reportError( + rightOp, "operation.unsignedrhs", kind, leftOpType, rightOpType); + } + break; + + case RIGHT_SHIFT: + if (hasUnsignedAnnotation(leftOpType) + && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) + && !SignednessShifts.isCastedShiftEitherSignedness( + tree, getCurrentPath())) { + checker.reportError(leftOp, "shift.signed", kind, leftOpType, rightOpType); + } + break; + + case UNSIGNED_RIGHT_SHIFT: + if (hasSignedAnnotation(leftOpType) + && !SignednessShifts.isMaskedShiftEitherSignedness(tree, getCurrentPath()) + && !SignednessShifts.isCastedShiftEitherSignedness( + tree, getCurrentPath())) { + checker.reportError(leftOp, "shift.unsigned", kind, leftOpType, rightOpType); + } + break; + + case LEFT_SHIFT: + break; + + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case LESS_THAN: + case LESS_THAN_EQUAL: + if (hasUnsignedAnnotation(leftOpType)) { + checker.reportError(leftOp, "comparison.unsignedlhs", leftOpType, rightOpType); + } else if (hasUnsignedAnnotation(rightOpType)) { + checker.reportError(rightOp, "comparison.unsignedrhs", leftOpType, rightOpType); + } + break; + + case EQUAL_TO: + case NOT_EQUAL_TO: + if (!atypeFactory.maybeIntegral(leftOpType) + || !atypeFactory.maybeIntegral(rightOpType)) { + break; + } + if (leftOpType.hasEffectiveAnnotation(Unsigned.class) + && rightOpType.hasEffectiveAnnotation(Signed.class)) { + checker.reportError( + tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); + } else if (leftOpType.hasEffectiveAnnotation(Signed.class) + && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { + checker.reportError( + tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); + } + break; + + case PLUS: + if (TreeUtils.isStringConcatenation(tree)) { + // Note that leftOpType.getUnderlyingType() and rightOpType.getUnderlyingType() + // are always java.lang.String. Please refer to binaryTreeArgTypes for more + // details. + // Here, the original types of operands can be something different from string. + // For example, "123" + obj is a string concatenation in which the original type + // of the right operand is java.lang.Object. + TypeMirror leftOpTM = TreeUtils.typeOf(leftOp); + AnnotationMirror leftAnno = + leftOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); + TypeMirror rightOpTM = TreeUtils.typeOf(rightOp); + AnnotationMirror rightAnno = + rightOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); + if (!TypesUtils.isCharType(leftOpTM) + && !qualHierarchy.isSubtypeQualifiersOnly( + leftAnno, atypeFactory.SIGNED)) { + checker.reportError(leftOp, "unsigned.concat"); + } else if (!TypesUtils.isCharType(rightOpTM) + && !qualHierarchy.isSubtypeQualifiersOnly( + rightAnno, atypeFactory.SIGNED)) { + checker.reportError(rightOp, "unsigned.concat"); + } + break; + } + // Other plus binary trees should be handled in the default case. + // fall through + default: + if (leftOpType.hasEffectiveAnnotation(Unsigned.class) + && rightOpType.hasEffectiveAnnotation(Signed.class)) { + checker.reportError( + tree, "operation.mixed.unsignedlhs", kind, leftOpType, rightOpType); + } else if (leftOpType.hasEffectiveAnnotation(Signed.class) + && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { + checker.reportError( + tree, "operation.mixed.unsignedrhs", kind, leftOpType, rightOpType); + } + break; } - break; - - case PLUS: - if (TreeUtils.isStringConcatenation(tree)) { - // Note that leftOpType.getUnderlyingType() and rightOpType.getUnderlyingType() - // are always java.lang.String. Please refer to binaryTreeArgTypes for more - // details. - // Here, the original types of operands can be something different from string. - // For example, "123" + obj is a string concatenation in which the original type - // of the right operand is java.lang.Object. - TypeMirror leftOpTM = TreeUtils.typeOf(leftOp); - AnnotationMirror leftAnno = - leftOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); - TypeMirror rightOpTM = TreeUtils.typeOf(rightOp); - AnnotationMirror rightAnno = - rightOpType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); - if (!TypesUtils.isCharType(leftOpTM) - && !qualHierarchy.isSubtypeQualifiersOnly(leftAnno, atypeFactory.SIGNED)) { - checker.reportError(leftOp, "unsigned.concat"); - } else if (!TypesUtils.isCharType(rightOpTM) - && !qualHierarchy.isSubtypeQualifiersOnly(rightAnno, atypeFactory.SIGNED)) { - checker.reportError(rightOp, "unsigned.concat"); - } - break; - } - // Other plus binary trees should be handled in the default case. - // fall through - default: - if (leftOpType.hasEffectiveAnnotation(Unsigned.class) - && rightOpType.hasEffectiveAnnotation(Signed.class)) { - checker.reportError(tree, "operation.mixed.unsignedlhs", kind, leftOpType, rightOpType); - } else if (leftOpType.hasEffectiveAnnotation(Signed.class) - && rightOpType.hasEffectiveAnnotation(Unsigned.class)) { - checker.reportError(tree, "operation.mixed.unsignedrhs", kind, leftOpType, rightOpType); + return super.visitBinary(tree, p); + } + + // Ensure that method annotations are not written on methods they don't apply to. + // Copied from InterningVisitor + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; + int params = methElt.getParameters().size(); + if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); } - break; + + return super.visitMethod(tree, p); } - return super.visitBinary(tree, p); - } - - // Ensure that method annotations are not written on methods they don't apply to. - // Copied from InterningVisitor - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement methElt = TreeUtils.elementFromDeclaration(tree); - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; - int params = methElt.getParameters().size(); - if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { - checker.reportError( - tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ExecutableElement methElt = TreeUtils.elementFromUse(tree); + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; + if (hasEqualsMethodAnno || InterningVisitor.isInvocationOfEquals(tree)) { + int params = methElt.getParameters().size(); + if (!(params == 1 || params == 2)) { + checker.reportError( + tree, + "invalid.method.annotation", + "@EqualsMethod", + "1 or 2", + methElt, + params); + } else { + AnnotatedTypeMirror leftOpType; + AnnotatedTypeMirror rightOpType; + if (params == 1) { + leftOpType = atypeFactory.getReceiverType(tree); + rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + } else if (params == 2) { + leftOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); + } else { + throw new BugInCF("Checked that params is 1 or 2"); + } + if (!atypeFactory.maybeIntegral(leftOpType) + || !atypeFactory.maybeIntegral(rightOpType)) { + // nothing to do + } else if (leftOpType.hasAnnotation(Unsigned.class) + && rightOpType.hasAnnotation(Signed.class)) { + checker.reportError( + tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); + } else if (leftOpType.hasAnnotation(Signed.class) + && rightOpType.hasAnnotation(Unsigned.class)) { + checker.reportError( + tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); + } + } + // Don't check against the annotated method declaration (which super would do). + return null; + } + + return super.visitMethodInvocation(tree, p); } - return super.visitMethod(tree, p); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ExecutableElement methElt = TreeUtils.elementFromUse(tree); - boolean hasEqualsMethodAnno = - atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; - if (hasEqualsMethodAnno || InterningVisitor.isInvocationOfEquals(tree)) { - int params = methElt.getParameters().size(); - if (!(params == 1 || params == 2)) { - checker.reportError( - tree, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); - } else { - AnnotatedTypeMirror leftOpType; - AnnotatedTypeMirror rightOpType; - if (params == 1) { - leftOpType = atypeFactory.getReceiverType(tree); - rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - } else if (params == 2) { - leftOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - rightOpType = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); + /** + * Returns a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if + * any. + * + * @param kind a tree kind + * @return a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if + * any + */ + private String kindWithoutAssignment(Tree.Kind kind) { + String result = kind.toString(); + if (result.endsWith("_ASSIGNMENT")) { + return result.substring(0, result.length() - "_ASSIGNMENT".length()); } else { - throw new BugInCF("Checked that params is 1 or 2"); - } - if (!atypeFactory.maybeIntegral(leftOpType) || !atypeFactory.maybeIntegral(rightOpType)) { - // nothing to do - } else if (leftOpType.hasAnnotation(Unsigned.class) - && rightOpType.hasAnnotation(Signed.class)) { - checker.reportError(tree, "comparison.mixed.unsignedlhs", leftOpType, rightOpType); - } else if (leftOpType.hasAnnotation(Signed.class) - && rightOpType.hasAnnotation(Unsigned.class)) { - checker.reportError(tree, "comparison.mixed.unsignedrhs", leftOpType, rightOpType); + return result; } - } - // Don't check against the annotated method declaration (which super would do). - return null; } - return super.visitMethodInvocation(tree, p); - } - - /** - * Returns a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if any. - * - * @param kind a tree kind - * @return a string representation of {@code kind}, with trailing _ASSIGNMENT stripped off if any - */ - private String kindWithoutAssignment(Tree.Kind kind) { - String result = kind.toString(); - if (result.endsWith("_ASSIGNMENT")) { - return result.substring(0, result.length() - "_ASSIGNMENT".length()); - } else { - return result; - } - } - - /** - * Enforces the following rules on compound assignments involving Unsigned and Signed types: - * - *
            - *
          • Do not allow any Unsigned types or PolySigned types in {@literal {/=, %=}} assignments. - *
          • Do not allow signed right shift {@literal {>>=}} to assign to an Unsigned type or a - * PolySigned type. - *
          • Do not allow unsigned right shift {@literal {>>>=}} to assign to a Signed type or a - * PolySigned type. - *
          • Allow any left shift {@literal {<<=}} assignment. - *
          • Do not allow mixing of Signed and Unsigned types. - *
          - */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - - ExpressionTree var = tree.getVariable(); - ExpressionTree expr = tree.getExpression(); - - IPair argTypes = - atypeFactory.compoundAssignmentTreeArgTypes(tree); - AnnotatedTypeMirror varType = argTypes.first; - AnnotatedTypeMirror exprType = argTypes.second; - - Tree.Kind kind = tree.getKind(); - - switch (kind) { - case DIVIDE_ASSIGNMENT: - case REMAINDER_ASSIGNMENT: - if (hasUnsignedAnnotation(varType)) { - checker.reportError( - var, - "compound.assignment.unsigned.variable", - kindWithoutAssignment(kind), - varType, - exprType); - } else if (hasUnsignedAnnotation(exprType)) { - checker.reportError( - expr, - "compound.assignment.unsigned.expression", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; - - case RIGHT_SHIFT_ASSIGNMENT: - if (hasUnsignedAnnotation(varType)) { - checker.reportError( - var, - "compound.assignment.shift.signed", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; - - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - if (hasSignedAnnotation(varType)) { - checker.reportError( - var, - "compound.assignment.shift.unsigned", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; - - case LEFT_SHIFT_ASSIGNMENT: - break; - - case PLUS_ASSIGNMENT: - if (TreeUtils.isStringCompoundConcatenation(tree)) { - // Note that exprType.getUnderlyingType() is always java.lang.String. - // Please refer to compoundAssignmentTreeArgTypes for more details. - if (TypesUtils.isCharType(TreeUtils.typeOf(expr))) { - break; - } - AnnotationMirror exprAnno = - exprType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); - if (!qualHierarchy.isSubtypeQualifiersOnly(exprAnno, atypeFactory.SIGNED)) { - checker.reportError(tree.getExpression(), "unsigned.concat"); - } - break; + /** + * Enforces the following rules on compound assignments involving Unsigned and Signed types: + * + *
            + *
          • Do not allow any Unsigned types or PolySigned types in {@literal {/=, %=}} assignments. + *
          • Do not allow signed right shift {@literal {>>=}} to assign to an Unsigned type or a + * PolySigned type. + *
          • Do not allow unsigned right shift {@literal {>>>=}} to assign to a Signed type or a + * PolySigned type. + *
          • Allow any left shift {@literal {<<=}} assignment. + *
          • Do not allow mixing of Signed and Unsigned types. + *
          + */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + + ExpressionTree var = tree.getVariable(); + ExpressionTree expr = tree.getExpression(); + + IPair argTypes = + atypeFactory.compoundAssignmentTreeArgTypes(tree); + AnnotatedTypeMirror varType = argTypes.first; + AnnotatedTypeMirror exprType = argTypes.second; + + Tree.Kind kind = tree.getKind(); + + switch (kind) { + case DIVIDE_ASSIGNMENT: + case REMAINDER_ASSIGNMENT: + if (hasUnsignedAnnotation(varType)) { + checker.reportError( + var, + "compound.assignment.unsigned.variable", + kindWithoutAssignment(kind), + varType, + exprType); + } else if (hasUnsignedAnnotation(exprType)) { + checker.reportError( + expr, + "compound.assignment.unsigned.expression", + kindWithoutAssignment(kind), + varType, + exprType); + } + break; + + case RIGHT_SHIFT_ASSIGNMENT: + if (hasUnsignedAnnotation(varType)) { + checker.reportError( + var, + "compound.assignment.shift.signed", + kindWithoutAssignment(kind), + varType, + exprType); + } + break; + + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + if (hasSignedAnnotation(varType)) { + checker.reportError( + var, + "compound.assignment.shift.unsigned", + kindWithoutAssignment(kind), + varType, + exprType); + } + break; + + case LEFT_SHIFT_ASSIGNMENT: + break; + + case PLUS_ASSIGNMENT: + if (TreeUtils.isStringCompoundConcatenation(tree)) { + // Note that exprType.getUnderlyingType() is always java.lang.String. + // Please refer to compoundAssignmentTreeArgTypes for more details. + if (TypesUtils.isCharType(TreeUtils.typeOf(expr))) { + break; + } + AnnotationMirror exprAnno = + exprType.getEffectiveAnnotationInHierarchy(atypeFactory.SIGNED); + if (!qualHierarchy.isSubtypeQualifiersOnly(exprAnno, atypeFactory.SIGNED)) { + checker.reportError(tree.getExpression(), "unsigned.concat"); + } + break; + } + // Other plus binary trees should be handled in the default case. + // fall through + default: + if (varType.hasAnnotation(Unsigned.class) && exprType.hasAnnotation(Signed.class)) { + checker.reportError( + expr, + "compound.assignment.mixed.unsigned.variable", + kindWithoutAssignment(kind), + varType, + exprType); + } else if (varType.hasAnnotation(Signed.class) + && exprType.hasAnnotation(Unsigned.class)) { + checker.reportError( + expr, + "compound.assignment.mixed.unsigned.expression", + kindWithoutAssignment(kind), + varType, + exprType); + } + break; } - // Other plus binary trees should be handled in the default case. - // fall through - default: - if (varType.hasAnnotation(Unsigned.class) && exprType.hasAnnotation(Signed.class)) { - checker.reportError( - expr, - "compound.assignment.mixed.unsigned.variable", - kindWithoutAssignment(kind), - varType, - exprType); - } else if (varType.hasAnnotation(Signed.class) && exprType.hasAnnotation(Unsigned.class)) { - checker.reportError( - expr, - "compound.assignment.mixed.unsigned.expression", - kindWithoutAssignment(kind), - varType, - exprType); - } - break; + return super.visitCompoundAssignment(tree, p); } - return super.visitCompoundAssignment(tree, p); - } - - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - if (!atypeFactory.maybeIntegral(castType)) { - // If the cast is not a number or a char, then it is legal. - return true; + + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + if (!atypeFactory.maybeIntegral(castType)) { + // If the cast is not a number or a char, then it is legal. + return true; + } + return super.isTypeCastSafe(castType, exprType); } - return super.isTypeCastSafe(castType, exprType); - } - @Override - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return new AnnotationMirrorSet(atypeFactory.SIGNED); - } + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return new AnnotationMirrorSet(atypeFactory.SIGNED); + } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} } diff --git a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java index a796fa0101d..93b9cc75519 100644 --- a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java @@ -1,35 +1,36 @@ package org.checkerframework.checker.tainting; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.element.AnnotationMirror; + /** Annotated type factory for the Tainting Checker. */ public class TaintingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@code @}{@link Untainted} annotation mirror. */ - private final AnnotationMirror UNTAINTED; + /** The {@code @}{@link Untainted} annotation mirror. */ + private final AnnotationMirror UNTAINTED; - /** A singleton set containing the {@code @}{@link Untainted} annotation mirror. */ - private final AnnotationMirrorSet setOfUntainted; + /** A singleton set containing the {@code @}{@link Untainted} annotation mirror. */ + private final AnnotationMirrorSet setOfUntainted; - /** - * Creates a {@link TaintingAnnotatedTypeFactory}. - * - * @param checker the tainting checker - */ - public TaintingAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.UNTAINTED = AnnotationBuilder.fromClass(getElementUtils(), Untainted.class); - this.setOfUntainted = AnnotationMirrorSet.singleton(UNTAINTED); - postInit(); - } + /** + * Creates a {@link TaintingAnnotatedTypeFactory}. + * + * @param checker the tainting checker + */ + public TaintingAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.UNTAINTED = AnnotationBuilder.fromClass(getElementUtils(), Untainted.class); + this.setOfUntainted = AnnotationMirrorSet.singleton(UNTAINTED); + postInit(); + } - @Override - protected AnnotationMirrorSet getEnumConstructorQualifiers() { - return setOfUntainted; - } + @Override + protected AnnotationMirrorSet getEnumConstructorQualifiers() { + return setOfUntainted; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java index 40f78e18ce5..15f5937aca6 100644 --- a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingVisitor.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.tainting; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import javax.lang.model.element.ExecutableElement; + /** Visitor for the {@link TaintingChecker}. */ public class TaintingVisitor extends BaseTypeVisitor { - /** - * Creates a {@link TaintingVisitor}. - * - * @param checker the checker that uses this visitor - */ - public TaintingVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Creates a {@link TaintingVisitor}. + * + * @param checker the checker that uses this visitor + */ + public TaintingVisitor(BaseTypeChecker checker) { + super(checker); + } - /** - * Don't check that the constructor result is top. Checking that the super() or this() call is a - * subtype of the constructor result is sufficient. - */ - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} + /** + * Don't check that the constructor result is top. Checking that the super() or this() call is a + * subtype of the constructor result is sufficient. + */ + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java index 8784eb31be0..c1f34a6ed2f 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFactory.java @@ -4,19 +4,7 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; -import java.io.File; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.util.Elements; -import javax.tools.Diagnostic; + import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; @@ -55,6 +43,21 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; +import java.io.File; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.util.Elements; +import javax.tools.Diagnostic; + /** * Annotated type factory for the Units Checker. * @@ -66,631 +69,653 @@ * correct unit for the result. */ public class UnitsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private static final Class - unitsRelationsAnnoClass = org.checkerframework.checker.units.qual.UnitsRelations.class; - - protected final AnnotationMirror mixedUnits = - AnnotationBuilder.fromClass(elements, MixedUnits.class); - protected final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownUnits.class); - protected final AnnotationMirror BOTTOM = - AnnotationBuilder.fromClass(elements, UnitsBottom.class); - - /** The UnitsMultiple.prefix argument/element. */ - private final ExecutableElement unitsMultiplePrefixElement = - TreeUtils.getMethod(UnitsMultiple.class, "prefix", 0, processingEnv); - - /** The UnitsMultiple.quantity argument/element. */ - private final ExecutableElement unitsMultipleQuantityElement = - TreeUtils.getMethod(UnitsMultiple.class, "quantity", 0, processingEnv); - - /** The UnitsRelations.value argument/element. */ - private final ExecutableElement unitsRelationsValueElement = - TreeUtils.getMethod( - org.checkerframework.checker.units.qual.UnitsRelations.class, "value", 0, processingEnv); - - /** - * Map from canonical class name to the corresponding UnitsRelations instance. We use the string - * to prevent instantiating the UnitsRelations multiple times. - */ - private @MonotonicNonNull Map<@CanonicalName String, UnitsRelations> unitsRel; - - /** Map from canonical name of external qualifiers, to their Class. */ - private static final Map<@CanonicalName String, Class> externalQualsMap = - new HashMap<>(); - - private static final Map aliasMap = new HashMap<>(); - - public UnitsAnnotatedTypeFactory(BaseTypeChecker checker) { - // use true to enable flow inference, false to disable it - super(checker, false); - - this.postInit(); - } - - // In Units Checker, we always want to print out the Invisible Qualifiers (UnknownUnits), and to - // format the print out of qualifiers by removing Prefix.one - @Override - protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { - return new UnitsAnnotatedTypeFormatter(checker); - } - - // Converts all metric-prefixed units' alias annotations (eg @kg) into base unit annotations - // with prefix values (eg @g(Prefix.kilo)) - @Override - public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { - // Get the name of the aliased annotation - String aname = AnnotationUtils.annotationName(anno); - - // See if we already have a map from this aliased annotation to its corresponding base unit - // annotation - if (aliasMap.containsKey(aname)) { - // if so return it - return aliasMap.get(aname); - } + private static final Class + unitsRelationsAnnoClass = org.checkerframework.checker.units.qual.UnitsRelations.class; + + protected final AnnotationMirror mixedUnits = + AnnotationBuilder.fromClass(elements, MixedUnits.class); + protected final AnnotationMirror TOP = + AnnotationBuilder.fromClass(elements, UnknownUnits.class); + protected final AnnotationMirror BOTTOM = + AnnotationBuilder.fromClass(elements, UnitsBottom.class); + + /** The UnitsMultiple.prefix argument/element. */ + private final ExecutableElement unitsMultiplePrefixElement = + TreeUtils.getMethod(UnitsMultiple.class, "prefix", 0, processingEnv); + + /** The UnitsMultiple.quantity argument/element. */ + private final ExecutableElement unitsMultipleQuantityElement = + TreeUtils.getMethod(UnitsMultiple.class, "quantity", 0, processingEnv); + + /** The UnitsRelations.value argument/element. */ + private final ExecutableElement unitsRelationsValueElement = + TreeUtils.getMethod( + org.checkerframework.checker.units.qual.UnitsRelations.class, + "value", + 0, + processingEnv); - boolean built = false; - AnnotationMirror result = null; - // if not, look for the UnitsMultiple meta annotations of this aliased annotation - for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) { - // see if the meta annotation is UnitsMultiple - if (isUnitsMultiple(metaAnno)) { - // retrieve the Class of the base unit annotation - Name baseUnitAnnoClass = - AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement); - - // retrieve the SI Prefix of the aliased annotation - Prefix prefix = - AnnotationUtils.getElementValueEnum( - metaAnno, unitsMultiplePrefixElement, Prefix.class, Prefix.one); - - // Build a base unit annotation with the prefix applied - result = - UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix( - processingEnv, baseUnitAnnoClass, prefix); - - // TODO: assert that this annotation is a prefix multiple of a Unit that's in the - // supported type qualifiers list currently this breaks for externally loaded - // annotations if the order was an alias before a base annotation. - // assert isSupportedQualifier(result); - - built = true; - break; - } - } + /** + * Map from canonical class name to the corresponding UnitsRelations instance. We use the string + * to prevent instantiating the UnitsRelations multiple times. + */ + private @MonotonicNonNull Map<@CanonicalName String, UnitsRelations> unitsRel; - if (built) { - // aliases shouldn't have Prefix.one, but if it does then clean it up here - if (UnitsRelationsTools.getPrefix(result) == Prefix.one) { - result = removePrefix(result); - } + /** Map from canonical name of external qualifiers, to their Class. */ + private static final Map<@CanonicalName String, Class> externalQualsMap = + new HashMap<>(); - // add this to the alias map - aliasMap.put(aname, result); - return result; - } + private static final Map aliasMap = new HashMap<>(); - return super.canonicalAnnotation(anno); - } - - /** - * Returns a map from canonical class name to the corresponding UnitsRelations instance. - * - * @return a map from canonical class name to the corresponding UnitsRelations instance - */ - protected Map<@CanonicalName String, UnitsRelations> getUnitsRel() { - if (unitsRel == null) { - unitsRel = new HashMap<>(); - // Always add the default units relations, for the standard units. - // Other code adds more relations. - unitsRel.put( - UnitsRelationsDefault.class.getCanonicalName(), - new UnitsRelationsDefault().init(processingEnv)); - } - return unitsRel; - } - - @Override - protected AnnotationClassLoader createAnnotationClassLoader() { - // Use the UnitsAnnotationClassLoader instead of the default one - return new UnitsAnnotationClassLoader(checker); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Get all the loaded annotations. - Set> qualSet = getBundledTypeQualifiers(); - - // Load all the units specified on the command line. - loadAllExternalUnits(); - qualSet.addAll(externalQualsMap.values()); - - return qualSet; - } - - /** Loads all the externnal units specified on the command line. */ - private void loadAllExternalUnits() { - // load external individually named units - for (String qualName : checker.getStringsOption("units", ',')) { - if (!Signatures.isBinaryName(qualName)) { - throw new UserError("Malformed qualifier name \"%s\" in -Aunits", qualName); - } - loadExternalUnit(qualName); - } + public UnitsAnnotatedTypeFactory(BaseTypeChecker checker) { + // use true to enable flow inference, false to disable it + super(checker, false); - // load external directories of units - for (String directoryName : checker.getStringsOption("unitsDirs", ':')) { - if (!new File(directoryName).exists()) { - throw new UserError("Nonexistent directory in -AunitsDirs: " + directoryName); - } - loadExternalDirectory(directoryName); + this.postInit(); } - } - - /** - * Loads and processes a single external units qualifier. - * - * @param annoName the name of a units qualifier - */ - private void loadExternalUnit(@BinaryName String annoName) { - // loadExternalAnnotationClass() returns null for alias units - Class loadedClass = loader.loadExternalAnnotationClass(annoName); - if (loadedClass != null) { - addUnitToExternalQualMap(loadedClass); + + // In Units Checker, we always want to print out the Invisible Qualifiers (UnknownUnits), and to + // format the print out of qualifiers by removing Prefix.one + @Override + protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { + return new UnitsAnnotatedTypeFormatter(checker); } - } - /** Loads and processes the units qualifiers from a single external directory. */ - private void loadExternalDirectory(String directoryName) { - Set> annoClassSet = - loader.loadExternalAnnotationClassesFromDirectory(directoryName); + // Converts all metric-prefixed units' alias annotations (eg @kg) into base unit annotations + // with prefix values (eg @g(Prefix.kilo)) + @Override + public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { + // Get the name of the aliased annotation + String aname = AnnotationUtils.annotationName(anno); + + // See if we already have a map from this aliased annotation to its corresponding base unit + // annotation + if (aliasMap.containsKey(aname)) { + // if so return it + return aliasMap.get(aname); + } - for (Class annoClass : annoClassSet) { - addUnitToExternalQualMap(annoClass); - } - } - - /** Adds the annotation class to the external qualifier map if it is not an alias annotation. */ - private void addUnitToExternalQualMap(Class annoClass) { - AnnotationMirror mirror = - UnitsRelationsTools.buildAnnoMirrorWithNoPrefix( - processingEnv, annoClass.getCanonicalName()); - - // if it is not an aliased annotation, add to external quals map if it isn't already in map - if (!isAliasedAnnotation(mirror)) { - String unitClassName = annoClass.getCanonicalName(); - if (!externalQualsMap.containsKey(unitClassName)) { - externalQualsMap.put(unitClassName, annoClass); - } - } - // if it is an aliased annotation - else { - // ensure it has a base unit - @CanonicalName Name baseUnitClass = getBaseUnitAnno(mirror); - if (baseUnitClass != null) { - // if the base unit isn't already added, add that first - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 - @DotSeparatedIdentifiers String baseUnitClassName = baseUnitClass.toString(); - if (!externalQualsMap.containsKey(baseUnitClassName)) { - loadExternalUnit(baseUnitClassName); + boolean built = false; + AnnotationMirror result = null; + // if not, look for the UnitsMultiple meta annotations of this aliased annotation + for (AnnotationMirror metaAnno : + anno.getAnnotationType().asElement().getAnnotationMirrors()) { + // see if the meta annotation is UnitsMultiple + if (isUnitsMultiple(metaAnno)) { + // retrieve the Class of the base unit annotation + Name baseUnitAnnoClass = + AnnotationUtils.getElementValueClassName( + metaAnno, unitsMultipleQuantityElement); + + // retrieve the SI Prefix of the aliased annotation + Prefix prefix = + AnnotationUtils.getElementValueEnum( + metaAnno, unitsMultiplePrefixElement, Prefix.class, Prefix.one); + + // Build a base unit annotation with the prefix applied + result = + UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix( + processingEnv, baseUnitAnnoClass, prefix); + + // TODO: assert that this annotation is a prefix multiple of a Unit that's in the + // supported type qualifiers list currently this breaks for externally loaded + // annotations if the order was an alias before a base annotation. + // assert isSupportedQualifier(result); + + built = true; + break; + } } - // then add the aliased annotation to the alias map - // TODO: refactor so we can directly add to alias map, skipping the assert check in - // canonicalAnnotation. - canonicalAnnotation(mirror); - } else { - // error: somehow the aliased annotation has @UnitsMultiple meta annotation, but no - // base class defined in that meta annotation - // TODO: error abort - } - } + if (built) { + // aliases shouldn't have Prefix.one, but if it does then clean it up here + if (UnitsRelationsTools.getPrefix(result) == Prefix.one) { + result = removePrefix(result); + } - // process the units annotation and add its corresponding units relations class - addUnitsRelations(annoClass); - } - - private boolean isAliasedAnnotation(AnnotationMirror anno) { - // loop through the meta annotations of the annotation, look for UnitsMultiple - for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) { - // see if the meta annotation is UnitsMultiple - if (isUnitsMultiple(metaAnno)) { - // TODO: does every alias have to have Prefix? - return true; - } - } + // add this to the alias map + aliasMap.put(aname, result); + return result; + } - // if we are unable to find UnitsMultiple meta annotation, then this is not an Aliased - // Annotation - return false; - } - - /** - * Return the name of the given annotation, if it is meta-annotated with UnitsMultiple; otherwise - * return null. - * - * @param anno the annotation to examine - * @return the annotation's name, if it is meta-annotated with UnitsMultiple; otherwise null - */ - private @Nullable @CanonicalName Name getBaseUnitAnno(AnnotationMirror anno) { - // loop through the meta annotations of the annotation, look for UnitsMultiple - for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) { - // see if the meta annotation is UnitsMultiple - if (isUnitsMultiple(metaAnno)) { - // TODO: does every alias have to have Prefix? - // Retrieve the base unit annotation. - Name baseUnitAnnoClass = - AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement); - return baseUnitAnnoClass; - } + return super.canonicalAnnotation(anno); } - return null; - } - - /** - * Returns true if {@code metaAnno} is {@link UnitsMultiple}. - * - * @param metaAnno an annotation mirror - * @return true if {@code metaAnno} is {@link UnitsMultiple} - */ - private boolean isUnitsMultiple(AnnotationMirror metaAnno) { - return areSameByClass(metaAnno, UnitsMultiple.class); - } - - /** A class loader for looking up annotations. */ - private static final ClassLoader CLASSLOADER = - InternalUtils.getClassLoaderForClass(AnnotationUtils.class); - - /** - * Look for an @UnitsRelations annotation on the qualifier and add it to the list of - * UnitsRelations. - * - * @param qual the qualifier to investigate - */ - private void addUnitsRelations(Class qual) { - AnnotationMirror am = AnnotationBuilder.fromClass(elements, qual); - - for (AnnotationMirror ama : am.getAnnotationType().asElement().getAnnotationMirrors()) { - if (areSameByClass(ama, unitsRelationsAnnoClass)) { - String theclassname = - AnnotationUtils.getElementValueClassName(ama, unitsRelationsValueElement).toString(); - if (!Signatures.isClassGetName(theclassname)) { - throw new UserError( - "Malformed class name \"%s\" should be in ClassGetName format in" + " annotation %s", - theclassname, ama); - } - Class valueElement; - try { - valueElement = Class.forName(theclassname, true, CLASSLOADER); - } catch (ClassNotFoundException e) { - String msg = - String.format( - "Could not load class '%s' for field 'value' in annotation %s", - theclassname, ama); - throw new UserError(msg, e); - } - Class unitsRelationsClass; - try { - unitsRelationsClass = valueElement.asSubclass(UnitsRelations.class); - } catch (ClassCastException ex) { - throw new UserError( - "Invalid @UnitsRelations meta-annotation found in %s. " - + "@UnitsRelations value %s is not a subclass of " - + "org.checkerframework.checker.units.UnitsRelations.", - qual, ama); - } - String classname = unitsRelationsClass.getCanonicalName(); - if (!getUnitsRel().containsKey(classname)) { - try { + /** + * Returns a map from canonical class name to the corresponding UnitsRelations instance. + * + * @return a map from canonical class name to the corresponding UnitsRelations instance + */ + protected Map<@CanonicalName String, UnitsRelations> getUnitsRel() { + if (unitsRel == null) { + unitsRel = new HashMap<>(); + // Always add the default units relations, for the standard units. + // Other code adds more relations. unitsRel.put( - classname, - unitsRelationsClass.getDeclaredConstructor().newInstance().init(processingEnv)); - } catch (Throwable e) { - throw new TypeSystemError("Throwable when instantiating UnitsRelations", e); - } + UnitsRelationsDefault.class.getCanonicalName(), + new UnitsRelationsDefault().init(processingEnv)); } - } - } - } - - @Override - public TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because it includes PropagationTreeAnnotator which - // is incorrect. - return new ListTreeAnnotator( - new UnitsPropagationTreeAnnotator(this), - new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), - new UnitsTreeAnnotator(this)); - } - - private static class UnitsPropagationTreeAnnotator extends PropagationTreeAnnotator { - - public UnitsPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + return unitsRel; } - // Handled completely by UnitsTreeAnnotator @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - return null; + protected AnnotationClassLoader createAnnotationClassLoader() { + // Use the UnitsAnnotationClassLoader instead of the default one + return new UnitsAnnotationClassLoader(checker); } - // Handled completely by UnitsTreeAnnotator @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - return null; + protected Set> createSupportedTypeQualifiers() { + // Get all the loaded annotations. + Set> qualSet = getBundledTypeQualifiers(); + + // Load all the units specified on the command line. + loadAllExternalUnits(); + qualSet.addAll(externalQualsMap.values()); + + return qualSet; } - } - /** A class for adding annotations based on tree. */ - private class UnitsTreeAnnotator extends TreeAnnotator { + /** Loads all the externnal units specified on the command line. */ + private void loadAllExternalUnits() { + // load external individually named units + for (String qualName : checker.getStringsOption("units", ',')) { + if (!Signatures.isBinaryName(qualName)) { + throw new UserError("Malformed qualifier name \"%s\" in -Aunits", qualName); + } + loadExternalUnit(qualName); + } - UnitsTreeAnnotator(UnitsAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + // load external directories of units + for (String directoryName : checker.getStringsOption("unitsDirs", ':')) { + if (!new File(directoryName).exists()) { + throw new UserError("Nonexistent directory in -AunitsDirs: " + directoryName); + } + loadExternalDirectory(directoryName); + } } - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); - AnnotatedTypeMirror rht = getAnnotatedType(tree.getRightOperand()); - Tree.Kind kind = tree.getKind(); - - // Remove Prefix.one - if (UnitsRelationsTools.getPrefix(lht) == Prefix.one) { - lht = UnitsRelationsTools.removePrefix(elements, lht); - } - if (UnitsRelationsTools.getPrefix(rht) == Prefix.one) { - rht = UnitsRelationsTools.removePrefix(elements, rht); - } - - AnnotationMirror bestres = null; - for (UnitsRelations ur : getUnitsRel().values()) { - AnnotationMirror res = useUnitsRelation(kind, ur, lht, rht); - - if (bestres != null && res != null && !bestres.equals(res)) { - checker.message( - Diagnostic.Kind.WARNING, - "UnitsRelation mismatch, taking neither! Previous: " - + bestres - + " and current: " - + res); - return null; // super.visitBinary(tree, type); + /** + * Loads and processes a single external units qualifier. + * + * @param annoName the name of a units qualifier + */ + private void loadExternalUnit(@BinaryName String annoName) { + // loadExternalAnnotationClass() returns null for alias units + Class loadedClass = loader.loadExternalAnnotationClass(annoName); + if (loadedClass != null) { + addUnitToExternalQualMap(loadedClass); } + } - if (res != null) { - bestres = res; + /** Loads and processes the units qualifiers from a single external directory. */ + private void loadExternalDirectory(String directoryName) { + Set> annoClassSet = + loader.loadExternalAnnotationClassesFromDirectory(directoryName); + + for (Class annoClass : annoClassSet) { + addUnitToExternalQualMap(annoClass); } - } - - if (bestres != null) { - type.replaceAnnotation(bestres); - } else { - // If none of the units relations classes could resolve the units, then apply - // default rules. - - switch (kind) { - case MINUS: - case PLUS: - if (lht.getAnnotations().equals(rht.getAnnotations())) { - // The sum or difference has the same units as both operands. - type.replaceAnnotations(lht.getAnnotations()); - } else { - // otherwise it results in mixed - type.replaceAnnotation(mixedUnits); - } - break; - case DIVIDE: - if (lht.getAnnotations().equals(rht.getAnnotations())) { - // If the units of the division match, return TOP - type.replaceAnnotation(TOP); - } else if (UnitsRelationsTools.hasNoUnits(rht)) { - // any unit divided by a scalar keeps that unit - type.replaceAnnotations(lht.getAnnotations()); - } else { - // Either UnitsRelationsTools.hasNoUnits(lht), which is a scalar divided - // by any unit returns mixed. - // Or else it is a division of two units that have no defined relations - // from a relations class return mixed. - type.replaceAnnotation(mixedUnits); + } + + /** Adds the annotation class to the external qualifier map if it is not an alias annotation. */ + private void addUnitToExternalQualMap(Class annoClass) { + AnnotationMirror mirror = + UnitsRelationsTools.buildAnnoMirrorWithNoPrefix( + processingEnv, annoClass.getCanonicalName()); + + // if it is not an aliased annotation, add to external quals map if it isn't already in map + if (!isAliasedAnnotation(mirror)) { + String unitClassName = annoClass.getCanonicalName(); + if (!externalQualsMap.containsKey(unitClassName)) { + externalQualsMap.put(unitClassName, annoClass); } - break; - case MULTIPLY: - if (UnitsRelationsTools.hasNoUnits(lht)) { - // any unit multiplied by a scalar keeps the unit - type.replaceAnnotations(rht.getAnnotations()); - } else if (UnitsRelationsTools.hasNoUnits(rht)) { - // any scalar multiplied by a unit becomes the unit - type.replaceAnnotations(lht.getAnnotations()); + } + // if it is an aliased annotation + else { + // ensure it has a base unit + @CanonicalName Name baseUnitClass = getBaseUnitAnno(mirror); + if (baseUnitClass != null) { + // if the base unit isn't already added, add that first + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 + @DotSeparatedIdentifiers String baseUnitClassName = baseUnitClass.toString(); + if (!externalQualsMap.containsKey(baseUnitClassName)) { + loadExternalUnit(baseUnitClassName); + } + + // then add the aliased annotation to the alias map + // TODO: refactor so we can directly add to alias map, skipping the assert check in + // canonicalAnnotation. + canonicalAnnotation(mirror); } else { - // else it is a multiplication of two units that have no defined - // relations from a relations class return mixed. - type.replaceAnnotation(mixedUnits); + // error: somehow the aliased annotation has @UnitsMultiple meta annotation, but no + // base class defined in that meta annotation + // TODO: error abort } - break; - case REMAINDER: - // in modulo operation, it always returns the left unit regardless of what - // it is (unknown, or some unit) - type.replaceAnnotations(lht.getAnnotations()); - break; - default: - // Placeholders for unhandled binary operations - // Do nothing } - } - return null; + // process the units annotation and add its corresponding units relations class + addUnitsRelations(annoClass); } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - ExpressionTree var = tree.getVariable(); - AnnotatedTypeMirror varType = getAnnotatedType(var); + private boolean isAliasedAnnotation(AnnotationMirror anno) { + // loop through the meta annotations of the annotation, look for UnitsMultiple + for (AnnotationMirror metaAnno : + anno.getAnnotationType().asElement().getAnnotationMirrors()) { + // see if the meta annotation is UnitsMultiple + if (isUnitsMultiple(metaAnno)) { + // TODO: does every alias have to have Prefix? + return true; + } + } - type.replaceAnnotations(varType.getAnnotations()); - return null; + // if we are unable to find UnitsMultiple meta annotation, then this is not an Aliased + // Annotation + return false; } - private @Nullable AnnotationMirror useUnitsRelation( - Tree.Kind kind, UnitsRelations ur, AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - - if (ur != null) { - switch (kind) { - case DIVIDE: - return ur.division(lht, rht); - case MULTIPLY: - return ur.multiplication(lht, rht); - default: - // Do nothing + /** + * Return the name of the given annotation, if it is meta-annotated with UnitsMultiple; + * otherwise return null. + * + * @param anno the annotation to examine + * @return the annotation's name, if it is meta-annotated with UnitsMultiple; otherwise null + */ + private @Nullable @CanonicalName Name getBaseUnitAnno(AnnotationMirror anno) { + // loop through the meta annotations of the annotation, look for UnitsMultiple + for (AnnotationMirror metaAnno : + anno.getAnnotationType().asElement().getAnnotationMirrors()) { + // see if the meta annotation is UnitsMultiple + if (isUnitsMultiple(metaAnno)) { + // TODO: does every alias have to have Prefix? + // Retrieve the base unit annotation. + Name baseUnitAnnoClass = + AnnotationUtils.getElementValueClassName( + metaAnno, unitsMultipleQuantityElement); + return baseUnitAnnoClass; + } } - } - return null; + return null; } - } - - /** Set the Bottom qualifier as the bottom of the hierarchy. */ - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new UnitsQualifierHierarchy(); - } - - /** Qualifier Hierarchy for the Units Checker. */ - @AnnotatedFor("nullness") - protected class UnitsQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /** Constructor. */ - public UnitsQualifierHierarchy() { - super( - UnitsAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), - elements, - UnitsAnnotatedTypeFactory.this); + + /** + * Returns true if {@code metaAnno} is {@link UnitsMultiple}. + * + * @param metaAnno an annotation mirror + * @return true if {@code metaAnno} is {@link UnitsMultiple} + */ + private boolean isUnitsMultiple(AnnotationMirror metaAnno) { + return areSameByClass(metaAnno, UnitsMultiple.class); } - @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization UnitsQualifierHierarchy this, - Collection> qualifierClasses) { - return new UnitsQualifierKindHierarchy(qualifierClasses, elements); + /** A class loader for looking up annotations. */ + private static final ClassLoader CLASSLOADER = + InternalUtils.getClassLoaderForClass(AnnotationUtils.class); + + /** + * Look for an @UnitsRelations annotation on the qualifier and add it to the list of + * UnitsRelations. + * + * @param qual the qualifier to investigate + */ + private void addUnitsRelations(Class qual) { + AnnotationMirror am = AnnotationBuilder.fromClass(elements, qual); + + for (AnnotationMirror ama : am.getAnnotationType().asElement().getAnnotationMirrors()) { + if (areSameByClass(ama, unitsRelationsAnnoClass)) { + String theclassname = + AnnotationUtils.getElementValueClassName(ama, unitsRelationsValueElement) + .toString(); + if (!Signatures.isClassGetName(theclassname)) { + throw new UserError( + "Malformed class name \"%s\" should be in ClassGetName format in" + + " annotation %s", + theclassname, ama); + } + Class valueElement; + try { + valueElement = Class.forName(theclassname, true, CLASSLOADER); + } catch (ClassNotFoundException e) { + String msg = + String.format( + "Could not load class '%s' for field 'value' in annotation %s", + theclassname, ama); + throw new UserError(msg, e); + } + Class unitsRelationsClass; + try { + unitsRelationsClass = valueElement.asSubclass(UnitsRelations.class); + } catch (ClassCastException ex) { + throw new UserError( + "Invalid @UnitsRelations meta-annotation found in %s. " + + "@UnitsRelations value %s is not a subclass of " + + "org.checkerframework.checker.units.UnitsRelations.", + qual, ama); + } + String classname = unitsRelationsClass.getCanonicalName(); + + if (!getUnitsRel().containsKey(classname)) { + try { + unitsRel.put( + classname, + unitsRelationsClass + .getDeclaredConstructor() + .newInstance() + .init(processingEnv)); + } catch (Throwable e) { + throw new TypeSystemError("Throwable when instantiating UnitsRelations", e); + } + } + } + } } @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - return AnnotationUtils.areSame(subAnno, superAnno); + public TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because it includes PropagationTreeAnnotator which + // is incorrect. + return new ListTreeAnnotator( + new UnitsPropagationTreeAnnotator(this), + new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), + new UnitsTreeAnnotator(this)); } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.isBottom()) { - return a2; - } else if (qualifierKind2.isBottom()) { - return a1; - } else if (qualifierKind1 == qualifierKind2) { - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - @SuppressWarnings({ - "nullness:assignment.type.incompatible" // Every qualifier kind is a - // key in directSuperQualifierMap. - }) - @NonNull AnnotationMirror lub = - ((UnitsQualifierKindHierarchy) qualifierKindHierarchy) - .directSuperQualifierMap.get(qualifierKind1); - return lub; + private static class UnitsPropagationTreeAnnotator extends PropagationTreeAnnotator { + + public UnitsPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + // Handled completely by UnitsTreeAnnotator + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + return null; + } + + // Handled completely by UnitsTreeAnnotator + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + return null; } - } - throw new TypeSystemError("Unexpected QualifierKinds: %s %s", qualifierKind1, qualifierKind2); } + /** A class for adding annotations based on tree. */ + private class UnitsTreeAnnotator extends TreeAnnotator { + + UnitsTreeAnnotator(UnitsAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rht = getAnnotatedType(tree.getRightOperand()); + Tree.Kind kind = tree.getKind(); + + // Remove Prefix.one + if (UnitsRelationsTools.getPrefix(lht) == Prefix.one) { + lht = UnitsRelationsTools.removePrefix(elements, lht); + } + if (UnitsRelationsTools.getPrefix(rht) == Prefix.one) { + rht = UnitsRelationsTools.removePrefix(elements, rht); + } + + AnnotationMirror bestres = null; + for (UnitsRelations ur : getUnitsRel().values()) { + AnnotationMirror res = useUnitsRelation(kind, ur, lht, rht); + + if (bestres != null && res != null && !bestres.equals(res)) { + checker.message( + Diagnostic.Kind.WARNING, + "UnitsRelation mismatch, taking neither! Previous: " + + bestres + + " and current: " + + res); + return null; // super.visitBinary(tree, type); + } + + if (res != null) { + bestres = res; + } + } + + if (bestres != null) { + type.replaceAnnotation(bestres); + } else { + // If none of the units relations classes could resolve the units, then apply + // default rules. + + switch (kind) { + case MINUS: + case PLUS: + if (lht.getAnnotations().equals(rht.getAnnotations())) { + // The sum or difference has the same units as both operands. + type.replaceAnnotations(lht.getAnnotations()); + } else { + // otherwise it results in mixed + type.replaceAnnotation(mixedUnits); + } + break; + case DIVIDE: + if (lht.getAnnotations().equals(rht.getAnnotations())) { + // If the units of the division match, return TOP + type.replaceAnnotation(TOP); + } else if (UnitsRelationsTools.hasNoUnits(rht)) { + // any unit divided by a scalar keeps that unit + type.replaceAnnotations(lht.getAnnotations()); + } else { + // Either UnitsRelationsTools.hasNoUnits(lht), which is a scalar divided + // by any unit returns mixed. + // Or else it is a division of two units that have no defined relations + // from a relations class return mixed. + type.replaceAnnotation(mixedUnits); + } + break; + case MULTIPLY: + if (UnitsRelationsTools.hasNoUnits(lht)) { + // any unit multiplied by a scalar keeps the unit + type.replaceAnnotations(rht.getAnnotations()); + } else if (UnitsRelationsTools.hasNoUnits(rht)) { + // any scalar multiplied by a unit becomes the unit + type.replaceAnnotations(lht.getAnnotations()); + } else { + // else it is a multiplication of two units that have no defined + // relations from a relations class return mixed. + type.replaceAnnotation(mixedUnits); + } + break; + case REMAINDER: + // in modulo operation, it always returns the left unit regardless of what + // it is (unknown, or some unit) + type.replaceAnnotations(lht.getAnnotations()); + break; + default: + // Placeholders for unhandled binary operations + // Do nothing + } + } + + return null; + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + ExpressionTree var = tree.getVariable(); + AnnotatedTypeMirror varType = getAnnotatedType(var); + + type.replaceAnnotations(varType.getAnnotations()); + return null; + } + + private @Nullable AnnotationMirror useUnitsRelation( + Tree.Kind kind, + UnitsRelations ur, + AnnotatedTypeMirror lht, + AnnotatedTypeMirror rht) { + + if (ur != null) { + switch (kind) { + case DIVIDE: + return ur.division(lht, rht); + case MULTIPLY: + return ur.multiplication(lht, rht); + default: + // Do nothing + } + } + return null; + } + } + + /** Set the Bottom qualifier as the bottom of the hierarchy. */ @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - return UnitsAnnotatedTypeFactory.this.BOTTOM; + protected QualifierHierarchy createQualifierHierarchy() { + return new UnitsQualifierHierarchy(); } - } - /** UnitsQualifierKindHierarchy. */ - @AnnotatedFor("nullness") - protected static class UnitsQualifierKindHierarchy extends DefaultQualifierKindHierarchy { + /** Qualifier Hierarchy for the Units Checker. */ + @AnnotatedFor("nullness") + protected class UnitsQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + /** Constructor. */ + public UnitsQualifierHierarchy() { + super( + UnitsAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), + elements, + UnitsAnnotatedTypeFactory.this); + } - /** - * Mapping from QualifierKind to an AnnotationMirror that represents its direct super qualifier. - * Every qualifier kind maps to a nonnull AnnotationMirror. - */ - private final Map directSuperQualifierMap; + @Override + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization UnitsQualifierHierarchy this, + Collection> qualifierClasses) { + return new UnitsQualifierKindHierarchy(qualifierClasses, elements); + } - /** - * Creates a UnitsQualifierKindHierarchy. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - public UnitsQualifierKindHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, UnitsBottom.class); - directSuperQualifierMap = createDirectSuperQualifierMap(elements); - } + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + return AnnotationUtils.areSame(subAnno, superAnno); + } - /** - * Creates the direct super qualifier map. - * - * @param elements element utils - * @return the map - */ - @RequiresNonNull("this.qualifierKinds") - private Map createDirectSuperQualifierMap( - @UnderInitialization UnitsQualifierKindHierarchy this, Elements elements) { - Map directSuperType = new TreeMap<>(); - for (QualifierKind qualifierKind : qualifierKinds) { - QualifierKind directSuperTypeKind = getDirectSuperQualifierKind(qualifierKind); - AnnotationMirror directSuperTypeAnno; - try { - directSuperTypeAnno = AnnotationBuilder.fromName(elements, directSuperTypeKind.getName()); - } catch (BugInCF ex) { - throw new TypeSystemError("Unit annotations must have a default for all elements."); + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.isBottom()) { + return a2; + } else if (qualifierKind2.isBottom()) { + return a1; + } else if (qualifierKind1 == qualifierKind2) { + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + @SuppressWarnings({ + "nullness:assignment.type.incompatible" // Every qualifier kind is a + // key in directSuperQualifierMap. + }) + @NonNull AnnotationMirror lub = + ((UnitsQualifierKindHierarchy) qualifierKindHierarchy) + .directSuperQualifierMap.get(qualifierKind1); + return lub; + } + } + throw new TypeSystemError( + "Unexpected QualifierKinds: %s %s", qualifierKind1, qualifierKind2); } - if (directSuperTypeAnno == null) { - throw new TypeSystemError("Could not create AnnotationMirror: %s", directSuperTypeAnno); + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + return UnitsAnnotatedTypeFactory.this.BOTTOM; } - directSuperType.put(qualifierKind, directSuperTypeAnno); - } - return directSuperType; } - /** - * Get the direct super qualifier for the given qualifier kind. - * - * @param qualifierKind qualifier kind - * @return direct super qualifier kind - */ - private QualifierKind getDirectSuperQualifierKind( - @UnderInitialization UnitsQualifierKindHierarchy this, QualifierKind qualifierKind) { - if (qualifierKind.isTop()) { - return qualifierKind; - } - Set superQuals = new TreeSet<>(qualifierKind.getStrictSuperTypes()); - while (superQuals.size() > 0) { - Set lowest = findLowestQualifiers(superQuals); - if (lowest.size() == 1) { - return lowest.iterator().next(); + /** UnitsQualifierKindHierarchy. */ + @AnnotatedFor("nullness") + protected static class UnitsQualifierKindHierarchy extends DefaultQualifierKindHierarchy { + + /** + * Mapping from QualifierKind to an AnnotationMirror that represents its direct super + * qualifier. Every qualifier kind maps to a nonnull AnnotationMirror. + */ + private final Map directSuperQualifierMap; + + /** + * Creates a UnitsQualifierKindHierarchy. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + public UnitsQualifierKindHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, UnitsBottom.class); + directSuperQualifierMap = createDirectSuperQualifierMap(elements); + } + + /** + * Creates the direct super qualifier map. + * + * @param elements element utils + * @return the map + */ + @RequiresNonNull("this.qualifierKinds") + private Map createDirectSuperQualifierMap( + @UnderInitialization UnitsQualifierKindHierarchy this, Elements elements) { + Map directSuperType = new TreeMap<>(); + for (QualifierKind qualifierKind : qualifierKinds) { + QualifierKind directSuperTypeKind = getDirectSuperQualifierKind(qualifierKind); + AnnotationMirror directSuperTypeAnno; + try { + directSuperTypeAnno = + AnnotationBuilder.fromName(elements, directSuperTypeKind.getName()); + } catch (BugInCF ex) { + throw new TypeSystemError( + "Unit annotations must have a default for all elements."); + } + if (directSuperTypeAnno == null) { + throw new TypeSystemError( + "Could not create AnnotationMirror: %s", directSuperTypeAnno); + } + directSuperType.put(qualifierKind, directSuperTypeAnno); + } + return directSuperType; + } + + /** + * Get the direct super qualifier for the given qualifier kind. + * + * @param qualifierKind qualifier kind + * @return direct super qualifier kind + */ + private QualifierKind getDirectSuperQualifierKind( + @UnderInitialization UnitsQualifierKindHierarchy this, + QualifierKind qualifierKind) { + if (qualifierKind.isTop()) { + return qualifierKind; + } + Set superQuals = new TreeSet<>(qualifierKind.getStrictSuperTypes()); + while (superQuals.size() > 0) { + Set lowest = findLowestQualifiers(superQuals); + if (lowest.size() == 1) { + return lowest.iterator().next(); + } + superQuals.removeAll(lowest); + } + throw new TypeSystemError("No direct super qualifier found for %s", qualifierKind); } - superQuals.removeAll(lowest); - } - throw new TypeSystemError("No direct super qualifier found for %s", qualifierKind); } - } - private AnnotationMirror removePrefix(AnnotationMirror anno) { - return UnitsRelationsTools.removePrefix(elements, anno); - } + private AnnotationMirror removePrefix(AnnotationMirror anno) { + return UnitsRelationsTools.removePrefix(elements, anno); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java index d173d5a128d..e4e017decfb 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotatedTypeFormatter.java @@ -1,9 +1,5 @@ package org.checkerframework.checker.units; -import java.util.Collection; -import java.util.Collections; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.units.qual.Prefix; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.DefaultAnnotatedTypeFormatter; @@ -11,79 +7,85 @@ import org.checkerframework.framework.util.DefaultAnnotationFormatter; import org.checkerframework.javacutil.AnnotationMirrorSet; -/** Formats units-of-measure annotations. */ -public class UnitsAnnotatedTypeFormatter extends DefaultAnnotatedTypeFormatter { - /** The checker. */ - protected final BaseTypeChecker checker; - - /** Javac element utilities. */ - protected final Elements elements; - - /** - * Create a UnitsAnnotatedTypeFormatter. - * - * @param checker the checker - */ - public UnitsAnnotatedTypeFormatter(BaseTypeChecker checker) { - // Utilize the Default Type Formatter, but force it to print out Invisible Qualifiers. - // Keep super call in sync with implementation in DefaultAnnotatedTypeFormatter. - // Keep checker options in sync with implementation in AnnotatedTypeFactory. - super( - new UnitsFormattingVisitor( - checker, - new UnitsAnnotationFormatter(checker), - checker.hasOption("printVerboseGenerics"), - true)); +import java.util.Collection; +import java.util.Collections; - this.checker = checker; - this.elements = checker.getElementUtils(); - } +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; - protected static class UnitsFormattingVisitor - extends DefaultAnnotatedTypeFormatter.FormattingVisitor { +/** Formats units-of-measure annotations. */ +public class UnitsAnnotatedTypeFormatter extends DefaultAnnotatedTypeFormatter { + /** The checker. */ protected final BaseTypeChecker checker; + + /** Javac element utilities. */ protected final Elements elements; - public UnitsFormattingVisitor( - BaseTypeChecker checker, - AnnotationFormatter annoFormatter, - boolean printVerboseGenerics, - boolean defaultInvisiblesSetting) { + /** + * Create a UnitsAnnotatedTypeFormatter. + * + * @param checker the checker + */ + public UnitsAnnotatedTypeFormatter(BaseTypeChecker checker) { + // Utilize the Default Type Formatter, but force it to print out Invisible Qualifiers. + // Keep super call in sync with implementation in DefaultAnnotatedTypeFormatter. + // Keep checker options in sync with implementation in AnnotatedTypeFactory. + super( + new UnitsFormattingVisitor( + checker, + new UnitsAnnotationFormatter(checker), + checker.hasOption("printVerboseGenerics"), + true)); - super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); - this.checker = checker; - this.elements = checker.getElementUtils(); + this.checker = checker; + this.elements = checker.getElementUtils(); } - } - /** Format the error printout of any units qualifier that uses Prefix.one. */ - protected static class UnitsAnnotationFormatter extends DefaultAnnotationFormatter { - protected final BaseTypeChecker checker; - protected final Elements elements; + protected static class UnitsFormattingVisitor + extends DefaultAnnotatedTypeFormatter.FormattingVisitor { + protected final BaseTypeChecker checker; + protected final Elements elements; - public UnitsAnnotationFormatter(BaseTypeChecker checker) { - this.checker = checker; - this.elements = checker.getElementUtils(); + public UnitsFormattingVisitor( + BaseTypeChecker checker, + AnnotationFormatter annoFormatter, + boolean printVerboseGenerics, + boolean defaultInvisiblesSetting) { + + super(annoFormatter, printVerboseGenerics, defaultInvisiblesSetting); + this.checker = checker; + this.elements = checker.getElementUtils(); + } } - @Override - public String formatAnnotationString( - Collection annos, boolean printInvisible) { - // create an empty annotation set - AnnotationMirrorSet trimmedAnnoSet = new AnnotationMirrorSet(); + /** Format the error printout of any units qualifier that uses Prefix.one. */ + protected static class UnitsAnnotationFormatter extends DefaultAnnotationFormatter { + protected final BaseTypeChecker checker; + protected final Elements elements; - // loop through all the annotation mirrors to see if they use Prefix.one, remove - // Prefix.one if it does - for (AnnotationMirror anno : annos) { - if (UnitsRelationsTools.getPrefix(anno) == Prefix.one) { - anno = UnitsRelationsTools.removePrefix(elements, anno); + public UnitsAnnotationFormatter(BaseTypeChecker checker) { + this.checker = checker; + this.elements = checker.getElementUtils(); } - // add to set - trimmedAnnoSet.add(anno); - } - return super.formatAnnotationString( - Collections.unmodifiableSet(trimmedAnnoSet), printInvisible); + @Override + public String formatAnnotationString( + Collection annos, boolean printInvisible) { + // create an empty annotation set + AnnotationMirrorSet trimmedAnnoSet = new AnnotationMirrorSet(); + + // loop through all the annotation mirrors to see if they use Prefix.one, remove + // Prefix.one if it does + for (AnnotationMirror anno : annos) { + if (UnitsRelationsTools.getPrefix(anno) == Prefix.one) { + anno = UnitsRelationsTools.removePrefix(elements, anno); + } + // add to set + trimmedAnnoSet.add(anno); + } + + return super.formatAnnotationString( + Collections.unmodifiableSet(trimmedAnnoSet), printInvisible); + } } - } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java index e62adb681d4..0c36171bfb3 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsAnnotationClassLoader.java @@ -1,50 +1,52 @@ package org.checkerframework.checker.units; -import java.lang.annotation.Annotation; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.units.qual.UnitsMultiple; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.AnnotationClassLoader; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; +import java.lang.annotation.Annotation; + +import javax.lang.model.element.AnnotationMirror; + public class UnitsAnnotationClassLoader extends AnnotationClassLoader { - public UnitsAnnotationClassLoader(BaseTypeChecker checker) { - super(checker); - } - - /** - * Custom filter for units annotations: - * - *

          This filter will ignore (by returning false) any units annotation which is an alias of - * another base unit annotation (identified via {@link UnitsMultiple} meta-annotation). Alias - * annotations can still be used in source code; they are converted into a base annotation by - * {@link UnitsAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}. This filter simply - * makes sure that the alias annotations themselves don't become part of the type hierarchy as - * their base annotations already are in the hierarchy. - */ - @Override - protected boolean isSupportedAnnotationClass(Class annoClass) { - // build the initial annotation mirror (missing prefix) - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoClass); - AnnotationMirror initialResult = builder.build(); - - // further refine to see if the annotation is an alias of some other SI Unit annotation - for (AnnotationMirror metaAnno : - initialResult.getAnnotationType().asElement().getAnnotationMirrors()) { - // TODO : special treatment of invisible qualifiers? - - // If the annotation is a SI prefix multiple of some base unit, then return false. - // Units checker does not need to load the annotations of SI prefix multiples of base - // units. - if (AnnotationUtils.areSameByName( - metaAnno, "org.checkerframework.checker.units.qual.UnitsMultiple")) { - return false; - } + public UnitsAnnotationClassLoader(BaseTypeChecker checker) { + super(checker); } - // Not an alias unit - return true; - } + /** + * Custom filter for units annotations: + * + *

          This filter will ignore (by returning false) any units annotation which is an alias of + * another base unit annotation (identified via {@link UnitsMultiple} meta-annotation). Alias + * annotations can still be used in source code; they are converted into a base annotation by + * {@link UnitsAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}. This filter simply + * makes sure that the alias annotations themselves don't become part of the type hierarchy as + * their base annotations already are in the hierarchy. + */ + @Override + protected boolean isSupportedAnnotationClass(Class annoClass) { + // build the initial annotation mirror (missing prefix) + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoClass); + AnnotationMirror initialResult = builder.build(); + + // further refine to see if the annotation is an alias of some other SI Unit annotation + for (AnnotationMirror metaAnno : + initialResult.getAnnotationType().asElement().getAnnotationMirrors()) { + // TODO : special treatment of invisible qualifiers? + + // If the annotation is a SI prefix multiple of some base unit, then return false. + // Units checker does not need to load the annotations of SI prefix multiples of base + // units. + if (AnnotationUtils.areSameByName( + metaAnno, "org.checkerframework.checker.units.qual.UnitsMultiple")) { + return false; + } + } + + // Not an alias unit + return true; + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java index b1f919299e7..e084ae7c65f 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsChecker.java @@ -1,10 +1,12 @@ package org.checkerframework.checker.units; -import java.util.NavigableSet; -import javax.annotation.processing.SupportedOptions; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.SubtypingChecker; +import java.util.NavigableSet; + +import javax.annotation.processing.SupportedOptions; + /** * Units Checker main class. * @@ -18,9 +20,9 @@ @SupportedOptions({"units", "unitsDirs"}) public class UnitsChecker extends BaseTypeChecker { - @Override - public NavigableSet getSuppressWarningsPrefixes() { - return SubtypingChecker.getSuppressWarningsPrefixes( - this.visitor, super.getSuppressWarningsPrefixes()); - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + return SubtypingChecker.getSuppressWarningsPrefixes( + this.visitor, super.getSuppressWarningsPrefixes()); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java index 10b6bdec2ff..e71c6d58e70 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelations.java @@ -1,41 +1,42 @@ package org.checkerframework.checker.units; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; + /** * Interface that is used to specify the relation between units. A class that implements this * interface is the argument to the {@link org.checkerframework.checker.units.qual.UnitsRelations} * annotation. */ public interface UnitsRelations { - /** - * Initialize the object. Needs to be called before any other method. - * - * @param env the ProcessingEnvironment to use - * @return a reference to "this" - */ - UnitsRelations init(ProcessingEnvironment env); + /** + * Initialize the object. Needs to be called before any other method. + * + * @param env the ProcessingEnvironment to use + * @return a reference to "this" + */ + UnitsRelations init(ProcessingEnvironment env); - /** - * Called for the multiplication of type lht and rht. - * - * @param lht left hand side in multiplication - * @param rht right hand side in multiplication - * @return the annotation to use for the result of the multiplication or null if no special - * relation is known - */ - @Nullable AnnotationMirror multiplication(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); + /** + * Called for the multiplication of type lht and rht. + * + * @param lht left hand side in multiplication + * @param rht right hand side in multiplication + * @return the annotation to use for the result of the multiplication or null if no special + * relation is known + */ + @Nullable AnnotationMirror multiplication(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); - /** - * Called for the division of type lht and rht. - * - * @param lht left hand side in division - * @param rht right hand side in division - * @return the annotation to use for the result of the division or null if no special relation is - * known - */ - @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); + /** + * Called for the division of type lht and rht. + * + * @param lht left hand side in division + * @param rht right hand side in division + * @return the annotation to use for the result of the division or null if no special relation + * is known + */ + @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht); } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java index 9c725a1a071..7199398fc97 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java @@ -1,8 +1,5 @@ package org.checkerframework.checker.units; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.units.qual.N; import org.checkerframework.checker.units.qual.Prefix; @@ -22,242 +19,252 @@ import org.checkerframework.checker.units.qual.t; import org.checkerframework.framework.type.AnnotatedTypeMirror; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** Default relations between SI units. */ public class UnitsRelationsDefault implements UnitsRelations { - /** SI base units. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror m, km, mm, s, g, kg; + /** SI base units. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror m, km, mm, s, g, kg; - /** Derived SI units without special names */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror m2, km2, mm2, m3, km3, mm3, mPERs, mPERs2; + /** Derived SI units without special names */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror m2, km2, mm2, m3, km3, mm3, mPERs, mPERs2; - /** Derived SI units with special names */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror N, kN; + /** Derived SI units with special names */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror N, kN; - /** Non-SI units */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected AnnotationMirror h, kmPERh, t; + /** Non-SI units */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected AnnotationMirror h, kmPERh, t; - /** The Element Utilities from the Units Checker's processing environment. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method - protected Elements elements; + /** The Element Utilities from the Units Checker's processing environment. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // init() method + protected Elements elements; - /** - * Constructs various AnnotationMirrors representing specific checker-framework provided Units - * involved in the rules resolved in this UnitsRelations implementation. - */ - @Override - public UnitsRelations init(ProcessingEnvironment env) { - elements = env.getElementUtils(); + /** + * Constructs various AnnotationMirrors representing specific checker-framework provided Units + * involved in the rules resolved in this UnitsRelations implementation. + */ + @Override + public UnitsRelations init(ProcessingEnvironment env) { + elements = env.getElementUtils(); - m = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, m.class); - km = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.kilo); - mm = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.milli); + m = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, m.class); + km = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.kilo); + mm = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, m.class, Prefix.milli); - m2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m2.class); - km2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km2.class); - mm2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm2.class); + m2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m2.class); + km2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km2.class); + mm2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm2.class); - m3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m3.class); - km3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km3.class); - mm3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm3.class); + m3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, m3.class); + km3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, km3.class); + mm3 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mm3.class); - s = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); - h = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, h.class); + s = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); + h = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, h.class); - mPERs = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs.class); - kmPERh = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, kmPERh.class); + mPERs = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs.class); + kmPERh = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, kmPERh.class); - mPERs2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs2.class); + mPERs2 = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, mPERs2.class); - g = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, g.class); - kg = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, g.class, Prefix.kilo); - t = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, t.class); - N = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, N.class); - kN = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, N.class, Prefix.kilo); + g = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, g.class); + kg = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, g.class, Prefix.kilo); + t = UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(env, t.class); + N = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, N.class); + kN = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, N.class, Prefix.kilo); - return this; - } + return this; + } - /** - * Provides rules for resolving the result Unit of the multiplication of checker-framework - * provided Units. - */ - @Override - public @Nullable AnnotationMirror multiplication( - AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - // TODO: does this handle scaling correctly? + /** + * Provides rules for resolving the result Unit of the multiplication of checker-framework + * provided Units. + */ + @Override + public @Nullable AnnotationMirror multiplication( + AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + // TODO: does this handle scaling correctly? - // length * length => area - // checking SI units only - if (UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(lht, m) - && UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(rht, m)) { - if (UnitsRelationsTools.hasNoPrefix(lht) && UnitsRelationsTools.hasNoPrefix(rht)) { - // m * m - return m2; - } + // length * length => area + // checking SI units only + if (UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(lht, m) + && UnitsRelationsTools.hasSpecificUnitIgnoringPrefix(rht, m)) { + if (UnitsRelationsTools.hasNoPrefix(lht) && UnitsRelationsTools.hasNoPrefix(rht)) { + // m * m + return m2; + } - Prefix lhtPrefix = UnitsRelationsTools.getPrefix(lht); - Prefix rhtPrefix = UnitsRelationsTools.getPrefix(rht); + Prefix lhtPrefix = UnitsRelationsTools.getPrefix(lht); + Prefix rhtPrefix = UnitsRelationsTools.getPrefix(rht); - if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.kilo)) { - // km * km - return km2; - } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.one)) { - // m(Prefix.one) * m(Prefix.one) - return m2; - } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.milli)) { - // mm * mm - return mm2; - } else { - return null; - } - } else if (havePairOfUnitsIgnoringOrder(lht, m, rht, m2)) { - return m3; - } else if (havePairOfUnitsIgnoringOrder(lht, km, rht, km2)) { - return km3; - } else if (havePairOfUnitsIgnoringOrder(lht, mm, rht, mm2)) { - return mm3; - } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs)) { - // s * mPERs or mPERs * s => m - return m; - } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs2)) { - // s * mPERs2 or mPERs2 * s => mPERs - return mPERs; - } else if (havePairOfUnitsIgnoringOrder(lht, h, rht, kmPERh)) { - // h * kmPERh or kmPERh * h => km - return km; - } else if (havePairOfUnitsIgnoringOrder(lht, kg, rht, mPERs2)) { - // kg * mPERs2 or mPERs2 * kg = N - return N; - } else if (havePairOfUnitsIgnoringOrder(lht, t, rht, mPERs2)) { - // t * mPERs2 or mPERs2 * t = kN - return kN; - } else { - return null; + if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.kilo)) { + // km * km + return km2; + } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.one)) { + // m(Prefix.one) * m(Prefix.one) + return m2; + } else if (bothHaveSpecificPrefix(lhtPrefix, rhtPrefix, Prefix.milli)) { + // mm * mm + return mm2; + } else { + return null; + } + } else if (havePairOfUnitsIgnoringOrder(lht, m, rht, m2)) { + return m3; + } else if (havePairOfUnitsIgnoringOrder(lht, km, rht, km2)) { + return km3; + } else if (havePairOfUnitsIgnoringOrder(lht, mm, rht, mm2)) { + return mm3; + } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs)) { + // s * mPERs or mPERs * s => m + return m; + } else if (havePairOfUnitsIgnoringOrder(lht, s, rht, mPERs2)) { + // s * mPERs2 or mPERs2 * s => mPERs + return mPERs; + } else if (havePairOfUnitsIgnoringOrder(lht, h, rht, kmPERh)) { + // h * kmPERh or kmPERh * h => km + return km; + } else if (havePairOfUnitsIgnoringOrder(lht, kg, rht, mPERs2)) { + // kg * mPERs2 or mPERs2 * kg = N + return N; + } else if (havePairOfUnitsIgnoringOrder(lht, t, rht, mPERs2)) { + // t * mPERs2 or mPERs2 * t = kN + return kN; + } else { + return null; + } } - } - /** - * Provides rules for resolving the result Unit of the division of checker-framework provided - * Units. - */ - @Override - public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - if (havePairOfUnits(lht, m, rht, s)) { - // m / s => mPERs - return mPERs; - } else if (havePairOfUnits(lht, km, rht, h)) { - // km / h => kmPERh - return kmPERh; - } else if (havePairOfUnits(lht, m2, rht, m)) { - // m2 / m => m - return m; - } else if (havePairOfUnits(lht, km2, rht, km)) { - // km2 / km => km - return km; - } else if (havePairOfUnits(lht, mm2, rht, mm)) { - // mm2 / mm => mm - return mm; - } else if (havePairOfUnits(lht, m3, rht, m)) { - // m3 / m => m2 - return m2; - } else if (havePairOfUnits(lht, km3, rht, km)) { - // km3 / km => km2 - return km2; - } else if (havePairOfUnits(lht, mm3, rht, mm)) { - // mm3 / mm => mm2 - return mm2; - } else if (havePairOfUnits(lht, m3, rht, m2)) { - // m3 / m2 => m - return m; - } else if (havePairOfUnits(lht, km3, rht, km2)) { - // km3 / km2 => km - return km; - } else if (havePairOfUnits(lht, mm3, rht, mm2)) { - // mm3 / mm2 => mm - return mm; - } else if (havePairOfUnits(lht, m, rht, mPERs)) { - // m / mPERs => s - return s; - } else if (havePairOfUnits(lht, km, rht, kmPERh)) { - // km / kmPERh => h - return h; - } else if (havePairOfUnits(lht, mPERs, rht, s)) { - // mPERs / s = mPERs2 - return mPERs2; - } else if (havePairOfUnits(lht, mPERs, rht, mPERs2)) { - // mPERs / mPERs2 => s (velocity / acceleration == time) - return s; - } else if (UnitsRelationsTools.hasSpecificUnit(lht, N)) { - if (UnitsRelationsTools.hasSpecificUnit(rht, kg)) { - // N / kg => mPERs2 - return mPERs2; - } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { - // N / mPERs2 => kg - return kg; - } - return null; - } else if (UnitsRelationsTools.hasSpecificUnit(lht, kN)) { - if (UnitsRelationsTools.hasSpecificUnit(rht, t)) { - // kN / t => mPERs2 - return mPERs2; - } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { - // kN / mPERs2 => t - return t; - } - return null; - } else { - return null; + /** + * Provides rules for resolving the result Unit of the division of checker-framework provided + * Units. + */ + @Override + public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + if (havePairOfUnits(lht, m, rht, s)) { + // m / s => mPERs + return mPERs; + } else if (havePairOfUnits(lht, km, rht, h)) { + // km / h => kmPERh + return kmPERh; + } else if (havePairOfUnits(lht, m2, rht, m)) { + // m2 / m => m + return m; + } else if (havePairOfUnits(lht, km2, rht, km)) { + // km2 / km => km + return km; + } else if (havePairOfUnits(lht, mm2, rht, mm)) { + // mm2 / mm => mm + return mm; + } else if (havePairOfUnits(lht, m3, rht, m)) { + // m3 / m => m2 + return m2; + } else if (havePairOfUnits(lht, km3, rht, km)) { + // km3 / km => km2 + return km2; + } else if (havePairOfUnits(lht, mm3, rht, mm)) { + // mm3 / mm => mm2 + return mm2; + } else if (havePairOfUnits(lht, m3, rht, m2)) { + // m3 / m2 => m + return m; + } else if (havePairOfUnits(lht, km3, rht, km2)) { + // km3 / km2 => km + return km; + } else if (havePairOfUnits(lht, mm3, rht, mm2)) { + // mm3 / mm2 => mm + return mm; + } else if (havePairOfUnits(lht, m, rht, mPERs)) { + // m / mPERs => s + return s; + } else if (havePairOfUnits(lht, km, rht, kmPERh)) { + // km / kmPERh => h + return h; + } else if (havePairOfUnits(lht, mPERs, rht, s)) { + // mPERs / s = mPERs2 + return mPERs2; + } else if (havePairOfUnits(lht, mPERs, rht, mPERs2)) { + // mPERs / mPERs2 => s (velocity / acceleration == time) + return s; + } else if (UnitsRelationsTools.hasSpecificUnit(lht, N)) { + if (UnitsRelationsTools.hasSpecificUnit(rht, kg)) { + // N / kg => mPERs2 + return mPERs2; + } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { + // N / mPERs2 => kg + return kg; + } + return null; + } else if (UnitsRelationsTools.hasSpecificUnit(lht, kN)) { + if (UnitsRelationsTools.hasSpecificUnit(rht, t)) { + // kN / t => mPERs2 + return mPERs2; + } else if (UnitsRelationsTools.hasSpecificUnit(rht, mPERs2)) { + // kN / mPERs2 => t + return t; + } + return null; + } else { + return null; + } } - } - /** - * Checks to see if both lhtPrefix and rhtPrefix have the same prefix as specificPrefix. - * - * @param lhtPrefix left hand side prefix - * @param rhtPrefix right hand side prefix - * @param specificPrefix specific desired prefix to match - * @return true if all 3 Prefix are the same, false otherwise - */ - protected boolean bothHaveSpecificPrefix( - Prefix lhtPrefix, Prefix rhtPrefix, Prefix specificPrefix) { - if (lhtPrefix == null || rhtPrefix == null || specificPrefix == null) { - return false; - } + /** + * Checks to see if both lhtPrefix and rhtPrefix have the same prefix as specificPrefix. + * + * @param lhtPrefix left hand side prefix + * @param rhtPrefix right hand side prefix + * @param specificPrefix specific desired prefix to match + * @return true if all 3 Prefix are the same, false otherwise + */ + protected boolean bothHaveSpecificPrefix( + Prefix lhtPrefix, Prefix rhtPrefix, Prefix specificPrefix) { + if (lhtPrefix == null || rhtPrefix == null || specificPrefix == null) { + return false; + } - return lhtPrefix == rhtPrefix && rhtPrefix == specificPrefix; - } + return lhtPrefix == rhtPrefix && rhtPrefix == specificPrefix; + } - /** - * Checks to see if lht has the unit ul and if rht has the unit ur all at the same time. - * - * @param lht left hand annotated type - * @param ul left hand unit - * @param rht right hand annotated type - * @param ur right hand unit - * @return true if lht has lu and rht has ru, false otherwise - */ - protected boolean havePairOfUnits( - AnnotatedTypeMirror lht, AnnotationMirror ul, AnnotatedTypeMirror rht, AnnotationMirror ur) { - return UnitsRelationsTools.hasSpecificUnit(lht, ul) - && UnitsRelationsTools.hasSpecificUnit(rht, ur); - } + /** + * Checks to see if lht has the unit ul and if rht has the unit ur all at the same time. + * + * @param lht left hand annotated type + * @param ul left hand unit + * @param rht right hand annotated type + * @param ur right hand unit + * @return true if lht has lu and rht has ru, false otherwise + */ + protected boolean havePairOfUnits( + AnnotatedTypeMirror lht, + AnnotationMirror ul, + AnnotatedTypeMirror rht, + AnnotationMirror ur) { + return UnitsRelationsTools.hasSpecificUnit(lht, ul) + && UnitsRelationsTools.hasSpecificUnit(rht, ur); + } - /** - * Checks to see if lht and rht have the pair of units u1 and u2 regardless of order. - * - * @param lht left hand annotated type - * @param u1 unit 1 - * @param rht right hand annotated type - * @param u2 unit 2 - * @return true if lht and rht have the pair of units u1 and u2 regardless of order, false - * otherwise - */ - protected boolean havePairOfUnitsIgnoringOrder( - AnnotatedTypeMirror lht, AnnotationMirror u1, AnnotatedTypeMirror rht, AnnotationMirror u2) { - return havePairOfUnits(lht, u1, rht, u2) || havePairOfUnits(lht, u2, rht, u1); - } + /** + * Checks to see if lht and rht have the pair of units u1 and u2 regardless of order. + * + * @param lht left hand annotated type + * @param u1 unit 1 + * @param rht right hand annotated type + * @param u2 unit 2 + * @return true if lht and rht have the pair of units u1 and u2 regardless of order, false + * otherwise + */ + protected boolean havePairOfUnitsIgnoringOrder( + AnnotatedTypeMirror lht, + AnnotationMirror u1, + AnnotatedTypeMirror rht, + AnnotationMirror u2) { + return havePairOfUnits(lht, u1, rht, u2) || havePairOfUnits(lht, u2, rht, u1); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java index 5e5ae26f2e3..134af783d4f 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsTools.java @@ -1,12 +1,5 @@ package org.checkerframework.checker.units; -import java.lang.annotation.Annotation; -import java.util.Map; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.FullyQualifiedName; import org.checkerframework.checker.units.qual.Prefix; @@ -16,297 +9,311 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; +import java.lang.annotation.Annotation; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** * A helper class for UnitsRelations, providing numerous methods which help process Annotations and * Annotated Types representing various units. */ public class UnitsRelationsTools { - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific Prefix - * p. - * - * @param env the Checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the fully-qualified name of an Annotation representing a Unit (eg m.class for - * meters) - * @param p a Prefix value - * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( - ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass, Prefix p) { - AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); - builder.setValue("value", p); - return builder.build(); - } + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific + * Prefix p. + * + * @param env the Checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the fully-qualified name of an Annotation representing a Unit (eg m.class + * for meters) + * @param p a Prefix value + * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be + * constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( + ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass, Prefix p) { + AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); + builder.setValue("value", p); + return builder.build(); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. - * - * @param env checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the getElementValueClassname of an Annotation representing a Unit (eg m.class - * for meters) - * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( - ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass) { - return AnnotationBuilder.fromName(env.getElementUtils(), annoClass); - } + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. + * + * @param env checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the getElementValueClassname of an Annotation representing a Unit (eg + * m.class for meters) + * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( + ProcessingEnvironment env, @FullyQualifiedName CharSequence annoClass) { + return AnnotationBuilder.fromName(env.getElementUtils(), annoClass); + } + + /** + * Retrieves the SI Prefix of an Annotated Type. + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return a Prefix value (including Prefix.one), or null if it has none + */ + public static @Nullable Prefix getPrefix(AnnotatedTypeMirror annoType) { + Prefix result = null; - /** - * Retrieves the SI Prefix of an Annotated Type. - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return a Prefix value (including Prefix.one), or null if it has none - */ - public static @Nullable Prefix getPrefix(AnnotatedTypeMirror annoType) { - Prefix result = null; + // go through each Annotation of an Annotated Type, find the prefix and return it + for (AnnotationMirror mirror : annoType.getAnnotations()) { + // try to get a prefix + result = getPrefix(mirror); + // if it is not null, then return the retrieved prefix immediately + if (result != null) { + return result; + } + } - // go through each Annotation of an Annotated Type, find the prefix and return it - for (AnnotationMirror mirror : annoType.getAnnotations()) { - // try to get a prefix - result = getPrefix(mirror); - // if it is not null, then return the retrieved prefix immediately - if (result != null) { + // if it can't find any prefix at all, then return null return result; - } } - // if it can't find any prefix at all, then return null - return result; - } + /** + * Retrieves the SI Prefix of an Annotation. + * + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation + * @return a Prefix value (including Prefix.one), or null if it has none + */ + public static @Nullable Prefix getPrefix(AnnotationMirror unitsAnnotation) { + AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); - /** - * Retrieves the SI Prefix of an Annotation. - * - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation - * @return a Prefix value (including Prefix.one), or null if it has none - */ - public static @Nullable Prefix getPrefix(AnnotationMirror unitsAnnotation) { - AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); + // if this Annotation has no prefix, return null + if (hasNoPrefix(annotationValue)) { + return null; + } - // if this Annotation has no prefix, return null - if (hasNoPrefix(annotationValue)) { - return null; - } + // if the Annotation has a value, then detect and match the string name of the prefix, and + // return the matching Prefix + String prefixString = annotationValue.getValue().toString(); + for (Prefix prefix : Prefix.values()) { + if (prefixString.equals(prefix.toString())) { + return prefix; + } + } - // if the Annotation has a value, then detect and match the string name of the prefix, and - // return the matching Prefix - String prefixString = annotationValue.getValue().toString(); - for (Prefix prefix : Prefix.values()) { - if (prefixString.equals(prefix.toString())) { - return prefix; - } + // if none of the strings match, then return null + return null; } - // if none of the strings match, then return null - return null; - } + /** + * Checks to see if an Annotated Type has no prefix. + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return true if it has no prefix, false otherwise + */ + public static boolean hasNoPrefix(AnnotatedTypeMirror annoType) { + for (AnnotationMirror mirror : annoType.getAnnotations()) { + // if any Annotation has a prefix, return false + if (!hasNoPrefix(mirror)) { + return false; + } + } - /** - * Checks to see if an Annotated Type has no prefix. - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return true if it has no prefix, false otherwise - */ - public static boolean hasNoPrefix(AnnotatedTypeMirror annoType) { - for (AnnotationMirror mirror : annoType.getAnnotations()) { - // if any Annotation has a prefix, return false - if (!hasNoPrefix(mirror)) { - return false; - } + return true; } - return true; - } - - /** - * Checks to see if an Annotation has no prefix (ie, no value element). - * - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation - * @return true if it has no prefix, false otherwise - */ - public static boolean hasNoPrefix(AnnotationMirror unitsAnnotation) { - AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); - return hasNoPrefix(annotationValue); - } + /** + * Checks to see if an Annotation has no prefix (ie, no value element). + * + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation + * @return true if it has no prefix, false otherwise + */ + public static boolean hasNoPrefix(AnnotationMirror unitsAnnotation) { + AnnotationValue annotationValue = getAnnotationMirrorPrefix(unitsAnnotation); + return hasNoPrefix(annotationValue); + } - private static boolean hasNoPrefix(AnnotationValue annotationValue) { - // Annotation has no element value (ie no SI prefix) - if (annotationValue == null) { - return true; - } else { - return false; + private static boolean hasNoPrefix(AnnotationValue annotationValue) { + // Annotation has no element value (ie no SI prefix) + if (annotationValue == null) { + return true; + } else { + return false; + } } - } - /** - * Given an Annotation, returns the prefix (eg kilo) as an AnnotationValue if there is any, - * otherwise returns null. - */ - private static @Nullable AnnotationValue getAnnotationMirrorPrefix( - AnnotationMirror unitsAnnotation) { - Map elementValues = - unitsAnnotation.getElementValues(); + /** + * Given an Annotation, returns the prefix (eg kilo) as an AnnotationValue if there is any, + * otherwise returns null. + */ + private static @Nullable AnnotationValue getAnnotationMirrorPrefix( + AnnotationMirror unitsAnnotation) { + Map elementValues = + unitsAnnotation.getElementValues(); - for (Map.Entry entry : - elementValues.entrySet()) { - if (entry.getKey().getSimpleName().contentEquals("value")) { - return entry.getValue(); - } - } + for (Map.Entry entry : + elementValues.entrySet()) { + if (entry.getKey().getSimpleName().contentEquals("value")) { + return entry.getValue(); + } + } - return null; - } + return null; + } - /** - * Removes the prefix value from an Annotation, by constructing and returning a copy of its base - * SI unit's Annotation. - * - * @param elements the Element Utilities from a checker's processing environment, typically - * obtained by calling env.getElementUtils() in init() of a Units Relations implementation - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation - * @return the base SI Unit's AnnotationMirror, or null if the base SI Unit cannot be constructed - */ - public static AnnotationMirror removePrefix(Elements elements, AnnotationMirror unitsAnnotation) { - if (hasNoPrefix(unitsAnnotation)) { - // Optimization, though the else case would also work. - return unitsAnnotation; - } else { - String unitsAnnoName = AnnotationUtils.annotationName(unitsAnnotation); - // In the Units Checker, the only annotation value is the prefix value. Therefore, - // fromName (which creates an annotation with no values) is acceptable. - // TODO: refine sensitivity of removal for extension units, in case extension - // Annotations have more than just Prefix in its values. - return AnnotationBuilder.fromName(elements, unitsAnnoName); + /** + * Removes the prefix value from an Annotation, by constructing and returning a copy of its base + * SI unit's Annotation. + * + * @param elements the Element Utilities from a checker's processing environment, typically + * obtained by calling env.getElementUtils() in init() of a Units Relations implementation + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation + * @return the base SI Unit's AnnotationMirror, or null if the base SI Unit cannot be + * constructed + */ + public static AnnotationMirror removePrefix( + Elements elements, AnnotationMirror unitsAnnotation) { + if (hasNoPrefix(unitsAnnotation)) { + // Optimization, though the else case would also work. + return unitsAnnotation; + } else { + String unitsAnnoName = AnnotationUtils.annotationName(unitsAnnotation); + // In the Units Checker, the only annotation value is the prefix value. Therefore, + // fromName (which creates an annotation with no values) is acceptable. + // TODO: refine sensitivity of removal for extension units, in case extension + // Annotations have more than just Prefix in its values. + return AnnotationBuilder.fromName(elements, unitsAnnoName); + } } - } - /** - * Removes the Prefix value from an Annotated Type, by constructing and returning a copy of the - * Annotated Type without the prefix. - * - * @param elements the Element Utilities from a checker's processing environment, typically - * obtained by calling env.getElementUtils() in init() of a Units Relations implementation - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return a copy of the Annotated Type without the prefix - */ - public static AnnotatedTypeMirror removePrefix(Elements elements, AnnotatedTypeMirror annoType) { - // deep copy the Annotated Type Mirror without any of the Annotations - AnnotatedTypeMirror result = annoType.deepCopy(false); + /** + * Removes the Prefix value from an Annotated Type, by constructing and returning a copy of the + * Annotated Type without the prefix. + * + * @param elements the Element Utilities from a checker's processing environment, typically + * obtained by calling env.getElementUtils() in init() of a Units Relations implementation + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return a copy of the Annotated Type without the prefix + */ + public static AnnotatedTypeMirror removePrefix( + Elements elements, AnnotatedTypeMirror annoType) { + // deep copy the Annotated Type Mirror without any of the Annotations + AnnotatedTypeMirror result = annoType.deepCopy(false); - // get all of the original Annotations in the Annotated Type - AnnotationMirrorSet annos = annoType.getAnnotations(); + // get all of the original Annotations in the Annotated Type + AnnotationMirrorSet annos = annoType.getAnnotations(); - // loop through all the Annotations to see if they use Prefix.one, remove Prefix.one if it - // does - for (AnnotationMirror anno : annos) { - // try to clean the Annotation Mirror of the Prefix - AnnotationMirror cleanedMirror = removePrefix(elements, anno); - // if successful, add the cleaned annotation to the deep copy - if (cleanedMirror != null) { - result.addAnnotation(cleanedMirror); - } - // if unsuccessful, add the original annotation - else { - result.addAnnotation(anno); - } - } + // loop through all the Annotations to see if they use Prefix.one, remove Prefix.one if it + // does + for (AnnotationMirror anno : annos) { + // try to clean the Annotation Mirror of the Prefix + AnnotationMirror cleanedMirror = removePrefix(elements, anno); + // if successful, add the cleaned annotation to the deep copy + if (cleanedMirror != null) { + result.addAnnotation(cleanedMirror); + } + // if unsuccessful, add the original annotation + else { + result.addAnnotation(anno); + } + } - return result; - } + return result; + } - /** - * Checks to see if a particular Annotated Type has no units, such as scalar constants in - * calculations. - * - *

          Any number that isn't assigned a unit will automatically get the Annotation UnknownUnits. - * eg: int x = 5; // x has @UnknownUnits - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @return true if the Type has no units, false otherwise - */ - public static boolean hasNoUnits(AnnotatedTypeMirror annoType) { - return (annoType.getAnnotation(UnknownUnits.class) != null); - } + /** + * Checks to see if a particular Annotated Type has no units, such as scalar constants in + * calculations. + * + *

          Any number that isn't assigned a unit will automatically get the Annotation UnknownUnits. + * eg: int x = 5; // x has @UnknownUnits + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @return true if the Type has no units, false otherwise + */ + public static boolean hasNoUnits(AnnotatedTypeMirror annoType) { + return (annoType.getAnnotation(UnknownUnits.class) != null); + } - /** - * Checks to see if a particular Annotated Type has a specific unit (represented by its - * Annotation). - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of a specific unit - * @return true if the Type has the specific unit, false otherwise - */ - public static boolean hasSpecificUnit( - AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { - return AnnotationUtils.containsSame(annoType.getAnnotations(), unitsAnnotation); - } + /** + * Checks to see if a particular Annotated Type has a specific unit (represented by its + * Annotation). + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of a specific unit + * @return true if the Type has the specific unit, false otherwise + */ + public static boolean hasSpecificUnit( + AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { + return AnnotationUtils.containsSame(annoType.getAnnotations(), unitsAnnotation); + } - /** - * Checks to see if a particular Annotated Type has a particular base unit (represented by its - * Annotation). - * - * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type - * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of the base unit - * @return true if the Type has the specific unit, false otherwise - */ - public static boolean hasSpecificUnitIgnoringPrefix( - AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { - return AnnotationUtils.containsSameByName(annoType.getAnnotations(), unitsAnnotation); - } + /** + * Checks to see if a particular Annotated Type has a particular base unit (represented by its + * Annotation). + * + * @param annoType an AnnotatedTypeMirror representing a Units Annotated Type + * @param unitsAnnotation an AnnotationMirror representing a Units Annotation of the base unit + * @return true if the Type has the specific unit, false otherwise + */ + public static boolean hasSpecificUnitIgnoringPrefix( + AnnotatedTypeMirror annoType, AnnotationMirror unitsAnnotation) { + return AnnotationUtils.containsSameByName(annoType.getAnnotations(), unitsAnnotation); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific Prefix - * p. - * - *

          This interface is intended only for subclasses of UnitsRelations; other clients should use - * {@link #buildAnnoMirrorWithSpecificPrefix(ProcessingEnvironment, CharSequence, Prefix)} - * - * @param env the Checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) - * @param p a Prefix value - * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( - ProcessingEnvironment env, Class annoClass, Prefix p) { - AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); - builder.setValue("value", p); - return builder.build(); - } + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with the specific + * Prefix p. + * + *

          This interface is intended only for subclasses of UnitsRelations; other clients should use + * {@link #buildAnnoMirrorWithSpecificPrefix(ProcessingEnvironment, CharSequence, Prefix)} + * + * @param env the Checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) + * @param p a Prefix value + * @return an AnnotationMirror of the Unit with the Prefix p, or null if it cannot be + * constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithSpecificPrefix( + ProcessingEnvironment env, Class annoClass, Prefix p) { + AnnotationBuilder builder = new AnnotationBuilder(env, annoClass); + builder.setValue("value", p); + return builder.build(); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with the default Prefix - * of {@code Prefix.one}. - * - *

          This interface is intended only for subclasses of UnitsRelations; other clients should not - * use it. - * - * @param env the Checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) - * @return an AnnotationMirror of the Unit with Prefix.one, or null if it cannot be constructed - */ - public static @Nullable AnnotationMirror buildAnnoMirrorWithDefaultPrefix( - ProcessingEnvironment env, Class annoClass) { - return buildAnnoMirrorWithSpecificPrefix(env, annoClass, Prefix.one); - } + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with the default Prefix + * of {@code Prefix.one}. + * + *

          This interface is intended only for subclasses of UnitsRelations; other clients should not + * use it. + * + * @param env the Checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) + * @return an AnnotationMirror of the Unit with Prefix.one, or null if it cannot be constructed + */ + public static @Nullable AnnotationMirror buildAnnoMirrorWithDefaultPrefix( + ProcessingEnvironment env, Class annoClass) { + return buildAnnoMirrorWithSpecificPrefix(env, annoClass, Prefix.one); + } - /** - * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. - * - *

          This interface is intended only for subclasses of UnitsRelations; other clients should use - * {@link #buildAnnoMirrorWithNoPrefix(ProcessingEnvironment, CharSequence)}. - * - * @param env checker Processing Environment, provided as a parameter in init() of a - * UnitsRelations implementation - * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) - * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed - */ - static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( - ProcessingEnvironment env, Class annoClass) { - return AnnotationBuilder.fromClass(env.getElementUtils(), annoClass); - } + /** + * Creates an AnnotationMirror representing a unit defined by annoClass, with no prefix. + * + *

          This interface is intended only for subclasses of UnitsRelations; other clients should use + * {@link #buildAnnoMirrorWithNoPrefix(ProcessingEnvironment, CharSequence)}. + * + * @param env checker Processing Environment, provided as a parameter in init() of a + * UnitsRelations implementation + * @param annoClass the Class of an Annotation representing a Unit (eg m.class for meters) + * @return an AnnotationMirror of the Unit with no prefix, or null if it cannot be constructed + */ + static @Nullable AnnotationMirror buildAnnoMirrorWithNoPrefix( + ProcessingEnvironment env, Class annoClass) { + return AnnotationBuilder.fromClass(env.getElementUtils(), annoClass); + } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java index b44334b6547..7937c850cb5 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsVisitor.java @@ -3,6 +3,7 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.units.qual.UnknownUnits; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -14,28 +15,29 @@ *

          Ensure consistent use of compound assignments. */ public class UnitsVisitor extends BaseTypeVisitor { - public UnitsVisitor(BaseTypeChecker checker) { - super(checker); - } + public UnitsVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - ExpressionTree var = tree.getVariable(); - ExpressionTree expr = tree.getExpression(); - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(var); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + ExpressionTree var = tree.getVariable(); + ExpressionTree expr = tree.getExpression(); + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(var); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(expr); - Tree.Kind kind = tree.getKind(); + Tree.Kind kind = tree.getKind(); - if ((kind == Tree.Kind.PLUS_ASSIGNMENT || kind == Tree.Kind.MINUS_ASSIGNMENT)) { - if (!typeHierarchy.isSubtypeShallowEffective(exprType, varType)) { - checker.reportError(tree, "compound.assignment.type.incompatible", varType, exprType); - } - } else if (!exprType.hasAnnotation(UnknownUnits.class)) { - // Only allow mul/div with unqualified units - checker.reportError(tree, "compound.assignment.type.incompatible", varType, exprType); - } + if ((kind == Tree.Kind.PLUS_ASSIGNMENT || kind == Tree.Kind.MINUS_ASSIGNMENT)) { + if (!typeHierarchy.isSubtypeShallowEffective(exprType, varType)) { + checker.reportError( + tree, "compound.assignment.type.incompatible", varType, exprType); + } + } else if (!exprType.hasAnnotation(UnknownUnits.class)) { + // Only allow mul/div with unqualified units + checker.reportError(tree, "compound.assignment.type.incompatible", varType, exprType); + } - return null; // super.visitCompoundAssignment(tree, p); - } + return null; // super.visitCompoundAssignment(tree, p); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java index 1041c10fd9a..57513de6147 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/AnnotatedForNullnessTest.java @@ -1,35 +1,36 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests the conservative defaults for Initialization Checker and Nullness Checker. */ public class AnnotatedForNullnessTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AnnotatedForNullnessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AnnotatedForNullnessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); + } - /** - * This method returns the directories containing test code. Each directory will be type-checked - * with {@code -AuseConservativeDefaultsForUncheckedCode=source,bytecode}. - * - * @return the directories containing test code - */ - @Parameters - public static String[] getTestDirs() { - return new String[] { - "nulless-conservative-defaults/annotatedfornullness", - "nulless-conservative-defaults/packageannotatedfornullness" - }; - } + /** + * This method returns the directories containing test code. Each directory will be type-checked + * with {@code -AuseConservativeDefaultsForUncheckedCode=source,bytecode}. + * + * @return the directories containing test code + */ + @Parameters + public static String[] getTestDirs() { + return new String[] { + "nulless-conservative-defaults/annotatedfornullness", + "nulless-conservative-defaults/packageannotatedfornullness" + }; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java index ee58c09852f..7605df2a60b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsAutoValueTest.java @@ -1,33 +1,34 @@ package org.checkerframework.checker.test.junit; +import org.checkerframework.checker.calledmethods.CalledMethodsChecker; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.checkerframework.checker.calledmethods.CalledMethodsChecker; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; /** Test case for Called Methods Checker's AutoValue support. */ public class CalledMethodsAutoValueTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsAutoValueTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "com.google.auto.value.extension.memoized.processor.MemoizedValidator", - "com.google.auto.value.processor.AutoAnnotationProcessor", - "com.google.auto.value.processor.AutoOneOfProcessor", - "com.google.auto.value.processor.AutoValueBuilderProcessor", - "com.google.auto.value.processor.AutoValueProcessor", - CalledMethodsChecker.class.getName()), - "calledmethods-autovalue", - Collections.emptyList(), - "-nowarn"); - } + public CalledMethodsAutoValueTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "com.google.auto.value.extension.memoized.processor.MemoizedValidator", + "com.google.auto.value.processor.AutoAnnotationProcessor", + "com.google.auto.value.processor.AutoOneOfProcessor", + "com.google.auto.value.processor.AutoValueBuilderProcessor", + "com.google.auto.value.processor.AutoValueProcessor", + CalledMethodsChecker.class.getName()), + "calledmethods-autovalue", + Collections.emptyList(), + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-autovalue"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-autovalue"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java index a58762c2c1e..05c1d984183 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableReturnsReceiverTest.java @@ -1,25 +1,26 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Basic tests for the Called Methods Checker. */ public class CalledMethodsDisableReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsDisableReturnsReceiverTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods-disablereturnsreceiver", - "-AdisableReturnsReceiver", - "-encoding", - "UTF-8"); - } + public CalledMethodsDisableReturnsReceiverTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods-disablereturnsreceiver", + "-AdisableReturnsReceiver", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-disablereturnsreceiver"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-disablereturnsreceiver"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java index 3f1d6a8f1f8..da5a50cc260 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsDisableframeworksTest.java @@ -1,36 +1,37 @@ package org.checkerframework.checker.test.junit; +import org.checkerframework.checker.calledmethods.CalledMethodsChecker; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.checkerframework.checker.calledmethods.CalledMethodsChecker; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; public class CalledMethodsDisableframeworksTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsDisableframeworksTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "com.google.auto.value.extension.memoized.processor.MemoizedValidator", - "com.google.auto.value.processor.AutoAnnotationProcessor", - "com.google.auto.value.processor.AutoOneOfProcessor", - "com.google.auto.value.processor.AutoValueBuilderProcessor", - "com.google.auto.value.processor.AutoValueProcessor", - CalledMethodsChecker.class.getName()), - "calledmethods-disableframeworks", - Collections.emptyList(), - "-AdisableBuilderFrameworkSupports=autovalue,lombok", - // The next option is so that we can run the usevaluechecker tests under this - // configuration. - "-ACalledMethodsChecker_useValueChecker", - "-nowarn"); - } + public CalledMethodsDisableframeworksTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "com.google.auto.value.extension.memoized.processor.MemoizedValidator", + "com.google.auto.value.processor.AutoAnnotationProcessor", + "com.google.auto.value.processor.AutoOneOfProcessor", + "com.google.auto.value.processor.AutoValueBuilderProcessor", + "com.google.auto.value.processor.AutoValueProcessor", + CalledMethodsChecker.class.getName()), + "calledmethods-disableframeworks", + Collections.emptyList(), + "-AdisableBuilderFrameworkSupports=autovalue,lombok", + // The next option is so that we can run the usevaluechecker tests under this + // configuration. + "-ACalledMethodsChecker_useValueChecker", + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-disableframeworks", "calledmethods-usevaluechecker"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-disableframeworks", "calledmethods-usevaluechecker"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java index 6717210f48f..4f73eb6a501 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsLombokTest.java @@ -1,24 +1,25 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Test that the Called Methods Checker's support for Lombok works correctly. */ public class CalledMethodsLombokTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsLombokTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods-delomboked", - "-nowarn", - "-AsuppressWarnings=type.anno.before.modifier"); - } + public CalledMethodsLombokTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods-delomboked", + "-nowarn", + "-AsuppressWarnings=type.anno.before.modifier"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-delomboked"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-delomboked"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java index 2c316d1b912..558ab47f4d0 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsNoDelombokTest.java @@ -1,9 +1,7 @@ package org.checkerframework.checker.test.junit; import com.google.common.collect.ImmutableList; -import java.io.File; -import java.util.Collections; -import java.util.List; + import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; import org.checkerframework.framework.test.TestConfigurationBuilder; @@ -12,49 +10,55 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.Collections; +import java.util.List; + /** * This test suite exists to demonstrate and keep a record of the unsoundness that occurs when * Lombok and the Checker Framework are run in the same invocation of javac. */ public class CalledMethodsNoDelombokTest extends CheckerFrameworkPerDirectoryTest { - private static final ImmutableList ANNOTATION_PROCS = - ImmutableList.of( - "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", - "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", - org.checkerframework.checker.calledmethods.CalledMethodsChecker.class.getName()); - - public CalledMethodsNoDelombokTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.calledmethods.CalledMethodsChecker.class, - "lombok", - "-nowarn"); - } - - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-nodelombok"}; - } - - /** - * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change the - * annotation processors to {@link #ANNOTATION_PROCS} - */ - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - classpathExtra, - ANNOTATION_PROCS, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); - TestUtilities.assertTestDidNotFail(adjustedTestResult); - } + private static final ImmutableList ANNOTATION_PROCS = + ImmutableList.of( + "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", + org.checkerframework.checker.calledmethods.CalledMethodsChecker.class + .getName()); + + public CalledMethodsNoDelombokTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.calledmethods.CalledMethodsChecker.class, + "lombok", + "-nowarn"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-nodelombok"}; + } + + /** + * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change + * the annotation processors to {@link #ANNOTATION_PROCS} + */ + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + classpathExtra, + ANNOTATION_PROCS, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); + TestUtilities.assertTestDidNotFail(adjustedTestResult); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java index c3defdf6cce..ac396d83db1 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsTest.java @@ -1,27 +1,28 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Basic tests for the Called Methods Checker. */ public class CalledMethodsTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods", - "-nowarn", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-encoding", - "UTF-8"); - } + public CalledMethodsTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods", + "-nowarn", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java index 40b5bb09f2c..651932cc8fa 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CalledMethodsUseValueCheckerTest.java @@ -1,23 +1,24 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.calledmethods.CalledMethodsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + public class CalledMethodsUseValueCheckerTest extends CheckerFrameworkPerDirectoryTest { - public CalledMethodsUseValueCheckerTest(List testFiles) { - super( - testFiles, - CalledMethodsChecker.class, - "calledmethods-usevaluechecker", - "-AuseValueChecker", - "-nowarn"); - } + public CalledMethodsUseValueCheckerTest(List testFiles) { + super( + testFiles, + CalledMethodsChecker.class, + "calledmethods-usevaluechecker", + "-AuseValueChecker", + "-nowarn"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"calledmethods-usevaluechecker"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"calledmethods-usevaluechecker"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java index 9343fd3005c..6ace53a5b48 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CompilerMessagesTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Compiler Messages Checker. Depends on the compiler.properties file. */ public class CompilerMessagesTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a CompilerMessagesTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public CompilerMessagesTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.compilermsgs.CompilerMessagesChecker.class, - "compilermsg", - "-Apropfiles=tests/compilermsg/compiler.properties"); - } + /** + * Create a CompilerMessagesTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public CompilerMessagesTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.compilermsgs.CompilerMessagesChecker.class, + "compilermsg", + "-Apropfiles=tests/compilermsg/compiler.properties"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"compilermsg", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"compilermsg", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java index b9bd2e5f719..caa4bdb64ad 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/CustomAliasTest.java @@ -1,31 +1,32 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the custom aliasing. */ public class CustomAliasTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a CustomAliasTest with the Nullness Checker and the Purity Checker. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public CustomAliasTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "custom-alias", - "-AaliasedTypeAnnos=org.checkerframework.checker.nullness.qual.NonNull:custom.alias.NonNull;" - + "org.checkerframework.checker.nullness.qual.Nullable:custom.alias.Nullable", - "-AaliasedDeclAnnos=org.checkerframework.dataflow.qual.Pure:custom.alias.Pure", - "-AcheckPurityAnnotations"); - } + /** + * Create a CustomAliasTest with the Nullness Checker and the Purity Checker. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public CustomAliasTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "custom-alias", + "-AaliasedTypeAnnos=org.checkerframework.checker.nullness.qual.NonNull:custom.alias.NonNull;" + + "org.checkerframework.checker.nullness.qual.Nullable:custom.alias.Nullable", + "-AaliasedDeclAnnos=org.checkerframework.dataflow.qual.Pure:custom.alias.Pure", + "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"custom-alias"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"custom-alias"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java index fc65d1e0ca2..a14c14b9fbb 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/DisbarUseTest.java @@ -1,34 +1,35 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.disbaruse.DisbarUseChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class DisbarUseTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a DisbarUseTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public DisbarUseTest(List testFiles) { - super( - testFiles, - DisbarUseChecker.class, - "disbaruse-records", - "-Astubs=tests/disbaruse-records", - "-AstubWarnIfNotFound"); - } + /** + * Create a DisbarUseTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public DisbarUseTest(List testFiles) { + super( + testFiles, + DisbarUseChecker.class, + "disbaruse-records", + "-Astubs=tests/disbaruse-records", + "-AstubWarnIfNotFound"); + } - @Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"disbaruse-records"}; - } else { - return new String[] {}; + @Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"disbaruse-records"}; + } else { + return new String[] {}; + } } - } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java index 2a3e4c102b5..dfc7f55fb4c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumSwingTest.java @@ -1,30 +1,31 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class FenumSwingTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FenumSwingTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FenumSwingTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.fenum.FenumChecker.class, - "fenum", - "-Aquals=org.checkerframework.checker.fenum.qual.SwingVerticalOrientation,org.checkerframework.checker.fenum.qual.SwingHorizontalOrientation,org.checkerframework.checker.fenum.qual.SwingBoxOrientation,org.checkerframework.checker.fenum.qual.SwingCompassDirection,org.checkerframework.checker.fenum.qual.SwingElementOrientation,org.checkerframework.checker.fenum.qual.SwingTextOrientation", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - // TODO: check all qualifiers - } + /** + * Create a FenumSwingTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FenumSwingTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.fenum.FenumChecker.class, + "fenum", + "-Aquals=org.checkerframework.checker.fenum.qual.SwingVerticalOrientation,org.checkerframework.checker.fenum.qual.SwingHorizontalOrientation,org.checkerframework.checker.fenum.qual.SwingBoxOrientation,org.checkerframework.checker.fenum.qual.SwingCompassDirection,org.checkerframework.checker.fenum.qual.SwingElementOrientation,org.checkerframework.checker.fenum.qual.SwingTextOrientation", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + // TODO: check all qualifiers + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"fenumswing", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"fenumswing", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java index 46c75c5577b..777086a832a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FenumTest.java @@ -1,23 +1,24 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class FenumTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FenumTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FenumTest(List testFiles) { - super(testFiles, org.checkerframework.checker.fenum.FenumChecker.class, "fenum"); - } + /** + * Create a FenumTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FenumTest(List testFiles) { + super(testFiles, org.checkerframework.checker.fenum.FenumChecker.class, "fenum"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"fenum", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"fenum", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java index 2562011117e..e5d9b426753 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterLubGlbCheckerTest.java @@ -4,25 +4,26 @@ // https://github.com/typetools/checker-framework/issues/691 // This exists to just run the FormatterLubGlbChecker. -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.lubglb.FormatterLubGlbChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class FormatterLubGlbCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FormatterLubGlbCheckerTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FormatterLubGlbCheckerTest(List testFiles) { - super(testFiles, FormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); - } + /** + * Create a FormatterLubGlbCheckerTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FormatterLubGlbCheckerTest(List testFiles) { + super(testFiles, FormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"formatter-lubglb"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"formatter-lubglb"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java index 8da204177a4..58f88dc12d3 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterTest.java @@ -1,22 +1,26 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class FormatterTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FormatterTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FormatterTest(List testFiles) { - super(testFiles, org.checkerframework.checker.formatter.FormatterChecker.class, "formatter"); - } + /** + * Create a FormatterTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FormatterTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.formatter.FormatterChecker.class, + "formatter"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"formatter", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"formatter", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java index bf88181d555..4096dfd7403 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUncheckedDefaultsTest.java @@ -1,26 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class FormatterUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a FormatterUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public FormatterUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.formatter.FormatterChecker.class, - "formatter", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create a FormatterUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public FormatterUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.formatter.FormatterChecker.class, + "formatter", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"formatter-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"formatter-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java index fd179ea1ac1..20b67868668 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/FormatterUnitTest.java @@ -6,40 +6,40 @@ public class FormatterUnitTest { - @SuppressWarnings("deprecation") // calls methods that are used only for testing - @Test - public void testConversionCharFromFormat() { - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$s")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tb")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$te")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tm")); - Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tY")); - Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%+10.4f")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$s")); - Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%(,.2f")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$2s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$s")); - Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("% testFiles) { - super(testFiles, org.checkerframework.checker.guieffect.GuiEffectChecker.class, "guieffect"); - // , "-Alint=debugSpew"); - } + /** + * Create a GuiEffectTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public GuiEffectTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.guieffect.GuiEffectChecker.class, + "guieffect"); + // , "-Alint=debugSpew"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"guieffect", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"guieffect", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java index 6f87e783e08..22f65cd1998 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterLubGlbCheckerTest.java @@ -4,25 +4,26 @@ // https://github.com/typetools/checker-framework/issues/723 // This exists to just run the I18nFormatterLubGlbChecker. -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.lubglb.I18nFormatterLubGlbChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class I18nFormatterLubGlbCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nFormatterLubGlbCheckerTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nFormatterLubGlbCheckerTest(List testFiles) { - super(testFiles, I18nFormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); - } + /** + * Create an I18nFormatterLubGlbCheckerTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nFormatterLubGlbCheckerTest(List testFiles) { + super(testFiles, I18nFormatterLubGlbChecker.class, "", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-formatter-lubglb"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-formatter-lubglb"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java index af3b2a2923f..003de4dff67 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterTest.java @@ -1,26 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class I18nFormatterTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nFormatterTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nFormatterTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, - "i18n-formatter"); - } + /** + * Create an I18nFormatterTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nFormatterTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, + "i18n-formatter"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-formatter", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-formatter", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java index 85ab191f97b..04cbbe0abcf 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUncheckedDefaultsTest.java @@ -1,27 +1,28 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class I18nFormatterUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nFormatterUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nFormatterUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, - "i18n-formatter", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create an I18nFormatterUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nFormatterUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18nformatter.I18nFormatterChecker.class, + "i18n-formatter", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-formatter-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-formatter-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java index 8f59efb1ab9..ca85671f9dc 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nFormatterUnitTest.java @@ -7,135 +7,146 @@ public class I18nFormatterUnitTest { - @Test - public void stringToI18nConversionCategoryTest() { - Assert.assertEquals( - I18nConversionCategory.NUMBER, - I18nConversionCategory.stringToI18nConversionCategory("number")); - Assert.assertEquals( - I18nConversionCategory.NUMBER, - I18nConversionCategory.stringToI18nConversionCategory("nuMber")); - Assert.assertEquals( - I18nConversionCategory.NUMBER, - I18nConversionCategory.stringToI18nConversionCategory("choice")); - Assert.assertEquals( - I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("TIME")); - Assert.assertEquals( - I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("DatE")); - Assert.assertEquals( - I18nConversionCategory.DATE, I18nConversionCategory.stringToI18nConversionCategory("date")); - } + @Test + public void stringToI18nConversionCategoryTest() { + Assert.assertEquals( + I18nConversionCategory.NUMBER, + I18nConversionCategory.stringToI18nConversionCategory("number")); + Assert.assertEquals( + I18nConversionCategory.NUMBER, + I18nConversionCategory.stringToI18nConversionCategory("nuMber")); + Assert.assertEquals( + I18nConversionCategory.NUMBER, + I18nConversionCategory.stringToI18nConversionCategory("choice")); + Assert.assertEquals( + I18nConversionCategory.DATE, + I18nConversionCategory.stringToI18nConversionCategory("TIME")); + Assert.assertEquals( + I18nConversionCategory.DATE, + I18nConversionCategory.stringToI18nConversionCategory("DatE")); + Assert.assertEquals( + I18nConversionCategory.DATE, + I18nConversionCategory.stringToI18nConversionCategory("date")); + } - @Test - public void isSubsetTest() { + @Test + public void isSubsetTest() { - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.UNUSED)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.GENERAL)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.UNUSED)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.GENERAL)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.UNUSED)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.GENERAL)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.UNUSED)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.GENERAL)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.UNUSED)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.GENERAL)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.DATE, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.UNUSED)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.GENERAL)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.DATE, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.UNUSED)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.GENERAL)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nConversionCategory.isSubsetOf( - I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); - } + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.UNUSED)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.GENERAL)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nConversionCategory.isSubsetOf( + I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); + } - @Test - public void hasFormatTest() { - Assert.assertTrue(I18nFormatUtil.hasFormat("{0}", I18nConversionCategory.GENERAL)); - Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1} {0, date}", I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{0} and {1,number}", I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1, number}", I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{2}", - I18nConversionCategory.UNUSED, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{3, number} {0} {1, time}", - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{0} {1, date} {2, time} {3, number} {5}", - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.DATE, - I18nConversionCategory.NUMBER, - I18nConversionCategory.UNUSED, - I18nConversionCategory.GENERAL)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1} {1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); - Assert.assertTrue( - I18nFormatUtil.hasFormat( - "{1, number} {1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER)); - Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date} {0, date}", I18nConversionCategory.DATE)); + @Test + public void hasFormatTest() { + Assert.assertTrue(I18nFormatUtil.hasFormat("{0}", I18nConversionCategory.GENERAL)); + Assert.assertTrue(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1} {0, date}", + I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{0} and {1,number}", + I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1, number}", + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1, date}", I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{2}", + I18nConversionCategory.UNUSED, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{3, number} {0} {1, time}", + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{0} {1, date} {2, time} {3, number} {5}", + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.DATE, + I18nConversionCategory.NUMBER, + I18nConversionCategory.UNUSED, + I18nConversionCategory.GENERAL)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1} {1, date}", + I18nConversionCategory.UNUSED, I18nConversionCategory.DATE)); + Assert.assertTrue( + I18nFormatUtil.hasFormat( + "{1, number} {1, date}", + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER)); + Assert.assertTrue( + I18nFormatUtil.hasFormat("{0, date} {0, date}", I18nConversionCategory.DATE)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{1}", I18nConversionCategory.GENERAL)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.DATE)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.GENERAL)); - Assert.assertFalse(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.GENERAL)); - Assert.assertFalse( - I18nFormatUtil.hasFormat( - "{0, date}", I18nConversionCategory.DATE, I18nConversionCategory.DATE)); - Assert.assertFalse( - I18nFormatUtil.hasFormat("{0, date} {1, date}", I18nConversionCategory.DATE)); - } + Assert.assertFalse(I18nFormatUtil.hasFormat("{1}", I18nConversionCategory.GENERAL)); + Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.DATE)); + Assert.assertFalse(I18nFormatUtil.hasFormat("{0, number}", I18nConversionCategory.GENERAL)); + Assert.assertFalse(I18nFormatUtil.hasFormat("{0, date}", I18nConversionCategory.GENERAL)); + Assert.assertFalse( + I18nFormatUtil.hasFormat( + "{0, date}", I18nConversionCategory.DATE, I18nConversionCategory.DATE)); + Assert.assertFalse( + I18nFormatUtil.hasFormat("{0, date} {1, date}", I18nConversionCategory.DATE)); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java index 277809dc5cb..bd0e3687a65 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class I18nTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18n.I18nChecker.class, - "i18n", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create an I18nTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18n.I18nChecker.class, + "i18n", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java index ad2e0e9b278..b93127e08b3 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/I18nUncheckedDefaultsTest.java @@ -1,27 +1,28 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class I18nUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an I18nUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public I18nUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.i18n.I18nChecker.class, - "i18n", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create an I18nUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public I18nUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.i18n.I18nChecker.class, + "i18n", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"i18n-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"i18n-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java index b70832cbb56..cdd19fb988a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexInitializedFieldsTest.java @@ -1,35 +1,36 @@ package org.checkerframework.checker.test.junit; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; /** JUnit tests for the Index Checker when running together with the InitializedFields Checker. */ public class IndexInitializedFieldsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an IndexTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public IndexInitializedFieldsTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "org.checkerframework.checker.index.IndexChecker", - "org.checkerframework.common.initializedfields.InitializedFieldsChecker"), - "index-initializedfields", - Collections.emptyList(), - "-Aajava=tests/index-initializedfields/input-annotation-files/", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create an IndexTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public IndexInitializedFieldsTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "org.checkerframework.checker.index.IndexChecker", + "org.checkerframework.common.initializedfields.InitializedFieldsChecker"), + "index-initializedfields", + Collections.emptyList(), + "-Aajava=tests/index-initializedfields/input-annotation-files/", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"index-initializedfields"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"index-initializedfields"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java index ddcc096f003..4cae656a319 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/IndexTest.java @@ -1,29 +1,30 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Index Checker. */ public class IndexTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an IndexTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public IndexTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.index.IndexChecker.class, - "index", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create an IndexTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public IndexTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.index.IndexChecker.class, + "index", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"index", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"index", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java index 94987f926c9..b20d0dc2a7d 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningTest.java @@ -1,24 +1,28 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Interning Checker, which tests the Interned annotation. */ public class InterningTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an InterningTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InterningTest(List testFiles) { - super(testFiles, org.checkerframework.checker.interning.InterningChecker.class, "interning"); - } + /** + * Create an InterningTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InterningTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.interning.InterningChecker.class, + "interning"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"interning", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"interning", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java index ff3d86e253e..20636c13551 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/InterningWarnRedundantAnnotationsTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Interning checker when AwarnRedundantAnnotations is used. */ public class InterningWarnRedundantAnnotationsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a InterningWarnRedundantAnnotationsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InterningWarnRedundantAnnotationsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.interning.InterningChecker.class, - "interning-warnredundantannotations", - "-AwarnRedundantAnnotations"); - } + /** + * Create a InterningWarnRedundantAnnotationsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InterningWarnRedundantAnnotationsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.interning.InterningChecker.class, + "interning-warnredundantannotations", + "-AwarnRedundantAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"interning-warnredundantannotations"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"interning-warnredundantannotations"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java index 1ab3ac9bc57..ab7bed9d0d6 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/LockSafeDefaultsTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Lock checker when using safe defaults for unchecked source code. */ public class LockSafeDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a LockSafeDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public LockSafeDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.lock.LockChecker.class, - "lock", - "-AuseConservativeDefaultsForUncheckedCode=source"); - } + /** + * Create a LockSafeDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public LockSafeDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.lock.LockChecker.class, + "lock", + "-AuseConservativeDefaultsForUncheckedCode=source"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"lock-safedefaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"lock-safedefaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java index c8a4bef88b2..7327a154632 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/LockTest.java @@ -1,33 +1,34 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class LockTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a LockTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public LockTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.lock.LockChecker.class, - "lock", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create a LockTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public LockTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.lock.LockChecker.class, + "lock", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"lock", "lock-records", "all-systems"}; - } else { - return new String[] {"lock", "all-systems"}; + @Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"lock", "lock-records", "all-systems"}; + } else { + return new String[] {"lock", "all-systems"}; + } } - } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java index 8f072476ebf..18d9f7e0519 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallNoLightweightOwnershipTest.java @@ -1,23 +1,24 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class MustCallNoLightweightOwnershipTest extends CheckerFrameworkPerDirectoryTest { - public MustCallNoLightweightOwnershipTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.mustcall.MustCallChecker.class, - "mustcall-nolightweightownership", - "-AnoLightweightOwnership", - // "-AstubDebug"); - "-nowarn"); - } + public MustCallNoLightweightOwnershipTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.mustcall.MustCallChecker.class, + "mustcall-nolightweightownership", + "-AnoLightweightOwnership", + // "-AstubDebug"); + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"mustcall-nolightweightownership"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"mustcall-nolightweightownership"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java index b245673d793..970fda0e605 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/MustCallTest.java @@ -1,22 +1,23 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class MustCallTest extends CheckerFrameworkPerDirectoryTest { - public MustCallTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.mustcall.MustCallChecker.class, - "mustcall", - // "-AstubDebug"); - "-nowarn"); - } + public MustCallTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.mustcall.MustCallChecker.class, + "mustcall", + // "-AstubDebug"); + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"mustcall"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"mustcall"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java index d8995e3dc2b..c2731b57045 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NestedAggregateCheckerTest.java @@ -4,25 +4,26 @@ // https://github.com/typetools/checker-framework/issues/343 // This exists to just run the NestedAggregateChecker. -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.NestedAggregateChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class NestedAggregateCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NestedAggregateCheckerTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NestedAggregateCheckerTest(List testFiles) { - super(testFiles, NestedAggregateChecker.class, "", "-AcheckPurityAnnotations"); - } + /** + * Create a NestedAggregateCheckerTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NestedAggregateCheckerTest(List testFiles) { + super(testFiles, NestedAggregateChecker.class, "", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"aggregate", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"aggregate", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java index b8ba91502df..ee3c12300b9 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssertsTest.java @@ -1,35 +1,37 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker. */ public class NullnessAssertsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssertsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssertsTest(List testFiles) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AassumeAssertionsAreEnabled", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessAssertsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssertsTest(List testFiles) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AassumeAssertionsAreEnabled", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-asserts"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-asserts"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java index 3cc7734beb6..c3716e3c199 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeAssertionsAreDisabledTest.java @@ -1,29 +1,30 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness Checker. */ public class NullnessAssumeAssertionsAreDisabledTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssumeAssertionsAreDisabledTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssumeAssertionsAreDisabledTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AassumeAssertionsAreDisabled", - "-Xlint:deprecation"); - } + /** + * Create a NullnessAssumeAssertionsAreDisabledTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssumeAssertionsAreDisabledTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AassumeAssertionsAreDisabled", + "-Xlint:deprecation"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-assumeassertions"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-assumeassertions"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java index 1e13913ea50..a5df11dcee7 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeInitializedTest.java @@ -1,36 +1,38 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness Checker without the Initialization Checker. */ public class NullnessAssumeInitializedTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssumeInitializedTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssumeInitializedTest(List testFiles) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AassumeInitialized", - "-AconservativeArgumentNullnessAfterInvocation=true", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessAssumeInitializedTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssumeInitializedTest(List testFiles) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AassumeInitialized", + "-AconservativeArgumentNullnessAfterInvocation=true", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness", "nullness-assumeinitialized", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness", "nullness-assumeinitialized", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java index 7d73f3b2c5c..4dc3ce056d2 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessAssumeKeyForTest.java @@ -1,35 +1,37 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker. */ public class NullnessAssumeKeyForTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessAssumeKeyForTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessAssumeKeyForTest(List testFiles) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AassumeKeyFor", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessAssumeKeyForTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessAssumeKeyForTest(List testFiles) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AassumeKeyFor", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-assumekeyfor"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-assumekeyfor"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java index 237a94b2b90..165d35d9f31 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessCheckCastElementTypeTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker when checkCastElementType is used. */ public class NullnessCheckCastElementTypeTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessCheckCastElementTypeTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessCheckCastElementTypeTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckCastElementType"); - } + /** + * Create a NullnessCheckCastElementTypeTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessCheckCastElementTypeTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckCastElementType"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-checkcastelementtype"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-checkcastelementtype"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java index ad7d3ee9b18..e556a67c1ab 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessConcurrentTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker when running with concurrent semantics. */ public class NullnessConcurrentTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessConcurrentTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessConcurrentTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AconcurrentSemantics"); - } + /** + * Create a NullnessConcurrentTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessConcurrentTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AconcurrentSemantics"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-concurrent-semantics"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-concurrent-semantics"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java index 8220fcd8e9b..4976c79e5b3 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessEnclosingExprTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * JUnit test for the Nullness Checker with checking of enclosing expressions of inner class * instantiations enabled. */ public class NullnessEnclosingExprTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessEnclosingExprTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessEnclosingExprTest(List testFiles) { - super(testFiles, NullnessChecker.class, "nullness", "-AcheckEnclosingExpr"); - } + /** + * Create a NullnessEnclosingExprTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessEnclosingExprTest(List testFiles) { + super(testFiles, NullnessChecker.class, "nullness", "-AcheckEnclosingExpr"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-enclosingexpr"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-enclosingexpr"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java index 5b4905b3614..59963e12190 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java @@ -1,8 +1,5 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.Collections; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; @@ -12,53 +9,57 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.Collections; +import java.util.List; + /** JUnit tests for the Nullness checker for issue #511. */ public class NullnessGenericWildcardTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessGenericWildcardTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessGenericWildcardTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - // This test reads bytecode .class files created by NullnessGenericWildcardLibTest - "-cp", - TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); - } + /** + * Create a NullnessGenericWildcardTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessGenericWildcardTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + // This test reads bytecode .class files created by NullnessGenericWildcardLibTest + "-cp", + TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-genericwildcard"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-genericwildcard"}; + } - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions1 = customizeOptions(Collections.emptyList()); - TestConfiguration config1 = - TestConfigurationBuilder.buildDefaultConfiguration( - "tests/nullness-genericwildcardlib", - new File("tests/nullness-genericwildcardlib", "GwiParent.java"), - NullnessChecker.class, - customizedOptions1, - shouldEmitDebugInfo); - TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); - TestUtilities.assertTestDidNotFail(testResult1); + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions1 = customizeOptions(Collections.emptyList()); + TestConfiguration config1 = + TestConfigurationBuilder.buildDefaultConfiguration( + "tests/nullness-genericwildcardlib", + new File("tests/nullness-genericwildcardlib", "GwiParent.java"), + NullnessChecker.class, + customizedOptions1, + shouldEmitDebugInfo); + TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); + TestUtilities.assertTestDidNotFail(testResult1); - List customizedOptions2 = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config2 = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - Collections.singleton(NullnessChecker.class.getName()), - customizedOptions2, - shouldEmitDebugInfo); - TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); - TestUtilities.assertTestDidNotFail(testResult2); - } + List customizedOptions2 = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config2 = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + Collections.singleton(NullnessChecker.class.getName()), + customizedOptions2, + shouldEmitDebugInfo); + TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); + TestUtilities.assertTestDidNotFail(testResult2); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java index 77bc0f979f7..15acf45cca6 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessInvariantArraysTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker when array subtyping is invariant. */ public class NullnessInvariantArraysTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessInvariantArraysTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessInvariantArraysTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AinvariantArrays"); - } + /** + * Create a NullnessInvariantArraysTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessInvariantArraysTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AinvariantArrays"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-invariantarrays"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-invariantarrays"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java index b34ddc86056..57886c339e2 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavacErrorsTest.java @@ -1,28 +1,30 @@ package org.checkerframework.checker.test.junit; -import java.io.File; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; + /** JUnit tests for the Nullness checker that issue javac errors. */ public class NullnessJavacErrorsTest extends CheckerFrameworkPerFileTest { - public NullnessJavacErrorsTest(File testFile) { - // TODO: remove soundArrayCreationNullness option once it's no - // longer needed. See issue #986: - // https://github.com/typetools/checker-framework/issues/986 - super( - testFile, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + public NullnessJavacErrorsTest(File testFile) { + // TODO: remove soundArrayCreationNullness option once it's no + // longer needed. See issue #986: + // https://github.com/typetools/checker-framework/issues/986 + super( + testFile, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-javac-errors"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-javac-errors"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java index dbfde8a6eeb..5751d82f3ff 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessJavadocTest.java @@ -1,45 +1,46 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.Collections; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.javacutil.SystemUtil; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.Collections; +import java.util.List; + /** * JUnit tests for the Nullness Checker -- testing type-checking of code that uses Javadoc classes. */ public class NullnessJavadocTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessJavadocTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - // required for JDK 8 (maybe not required for JDK 11, but it does no harm) - toolsJarList()); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessJavadocTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + // required for JDK 8 (maybe not required for JDK 11, but it does no harm) + toolsJarList()); + } - /** - * Return a list that contains the pathname to the tools.jar file, if it exists. - * - * @return a list that contains the pathname to the tools.jar file, if it exists - */ - private static List toolsJarList() { - String toolsJar = SystemUtil.getToolsJar(); - if (toolsJar == null) { - return Collections.emptyList(); - } else { - return Collections.singletonList(toolsJar); + /** + * Return a list that contains the pathname to the tools.jar file, if it exists. + * + * @return a list that contains the pathname to the tools.jar file, if it exists + */ + private static List toolsJarList() { + String toolsJar = SystemUtil.getToolsJar(); + if (toolsJar == null) { + return Collections.emptyList(); + } else { + return Collections.singletonList(toolsJar); + } } - } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-javadoc"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-javadoc"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java index fbeb439e35e..7c5a0d5e95d 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNoDelombokTest.java @@ -1,9 +1,7 @@ package org.checkerframework.checker.test.junit; import com.google.common.collect.ImmutableList; -import java.io.File; -import java.util.Collections; -import java.util.List; + import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; import org.checkerframework.framework.test.TestConfigurationBuilder; @@ -12,49 +10,54 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.Collections; +import java.util.List; + /** * This test suite exists to demonstrate and keep a record of the unsoundness that occurs when * Lombok and the Checker Framework are run in the same invocation of javac. */ public class NullnessNoDelombokTest extends CheckerFrameworkPerDirectoryTest { - private static final ImmutableList ANNOTATION_PROCS = - ImmutableList.of( - "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", - "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", - org.checkerframework.checker.nullness.NullnessChecker.class.getName()); - - public NullnessNoDelombokTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness-nodelombok", - "-nowarn"); - } - - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-nodelombok"}; - } - - /** - * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change the - * annotation processors to {@link #ANNOTATION_PROCS} - */ - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - classpathExtra, - ANNOTATION_PROCS, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); - TestUtilities.assertTestDidNotFail(adjustedTestResult); - } + private static final ImmutableList ANNOTATION_PROCS = + ImmutableList.of( + "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + "lombok.launch.AnnotationProcessorHider$ClaimingProcessor", + org.checkerframework.checker.nullness.NullnessChecker.class.getName()); + + public NullnessNoDelombokTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness-nodelombok", + "-nowarn"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-nodelombok"}; + } + + /** + * copy-pasted code from {@link CheckerFrameworkPerDirectoryTest#run()}, except that we change + * the annotation processors to {@link #ANNOTATION_PROCS} + */ + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + classpathExtra, + ANNOTATION_PROCS, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); + TestUtilities.assertTestDidNotFail(adjustedTestResult); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java index 3cf9444ccd8..df697164a87 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessNullMarkedTest.java @@ -1,39 +1,40 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestUtilities; import org.junit.Test; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker. */ public class NullnessNullMarkedTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessNullMarkedTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessNullMarkedTest(List testFiles) { - super(testFiles, org.checkerframework.checker.nullness.NullnessChecker.class, "nullness"); - } + /** + * Create a NullnessNullMarkedTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessNullMarkedTest(List testFiles) { + super(testFiles, org.checkerframework.checker.nullness.NullnessChecker.class, "nullness"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-nullmarked"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-nullmarked"}; + } - @Override - @Test - public void run() { - /* - * Skip under JDK8: checker/bin-devel/build.sh doesn't build JSpecify under that version - * (since the JSpecify build requires JDK9+), so there would be no JSpecify jar, and tests - * would fail on account of the missing classes. - */ - if (TestUtilities.IS_AT_LEAST_9_JVM) { - super.run(); + @Override + @Test + public void run() { + /* + * Skip under JDK8: checker/bin-devel/build.sh doesn't build JSpecify under that version + * (since the JSpecify build requires JDK9+), so there would be no JSpecify jar, and tests + * would fail on account of the missing classes. + */ + if (TestUtilities.IS_AT_LEAST_9_JVM) { + super.run(); + } } - } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java index e003b381fa8..9cb896513ca 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessPermitClearPropertyTest.java @@ -1,29 +1,30 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * JUnit tests for the Nullness Checker -- testing {@code -Alint=permitClearProperty} command-line * argument. */ public class NullnessPermitClearPropertyTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessPermitClearPropertyTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-Alint=permitClearProperty"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessPermitClearPropertyTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-Alint=permitClearProperty"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-permitClearProperty"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-permitClearProperty"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java index 8da74b63c2b..0637242b65b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessRecordsTest.java @@ -1,36 +1,37 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker with records (JDK16+ only). */ public class NullnessRecordsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessRecordsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessRecordsTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness-records", - "-AcheckPurityAnnotations", - "-Xlint:deprecation"); - } + /** + * Create a NullnessRecordsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessRecordsTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness-records", + "-AcheckPurityAnnotations", + "-Xlint:deprecation"); + } - @Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - // There is no decimal point in the JDK 17 version number. - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"nullness-records"}; - } else { - return new String[] {}; + @Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + // There is no decimal point in the JDK 17 version number. + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"nullness-records"}; + } else { + return new String[] {}; + } } - } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java index bd817017291..525a81bfa7d 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessReflectionTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker when reflection resolution is enabled. */ public class NullnessReflectionTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessReflectionTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessReflectionTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AresolveReflection"); - } + /** + * Create a NullnessReflectionTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessReflectionTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AresolveReflection"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-reflection"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-reflection"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java index 212efb85395..d5ffae2deab 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsBytecodeTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker when using safe defaults for unannotated bytecode. */ public class NullnessSafeDefaultsBytecodeTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSafeDefaultsBytecodeTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSafeDefaultsBytecodeTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AuseConservativeDefaultsForUncheckedCode=bytecode"); - } + /** + * Create a NullnessSafeDefaultsBytecodeTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSafeDefaultsBytecodeTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AuseConservativeDefaultsForUncheckedCode=bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-safedefaultsbytecode"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-safedefaultsbytecode"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java index 63fec6f6530..dc8e868d617 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java @@ -1,9 +1,5 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestConfiguration; @@ -13,55 +9,60 @@ import org.checkerframework.framework.test.TypecheckResult; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** JUnit tests for the Nullness checker when using safe defaults for unannotated source code. */ public class NullnessSafeDefaultsSourceCodeTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSafeDefaultsSourceCodeTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSafeDefaultsSourceCodeTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - "-AuseConservativeDefaultsForUncheckedCode=source", - "-cp", - TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); - } + /** + * Create a NullnessSafeDefaultsSourceCodeTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSafeDefaultsSourceCodeTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + "-AuseConservativeDefaultsForUncheckedCode=source", + "-cp", + TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-safedefaultssourcecode"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-safedefaultssourcecode"}; + } - @Override - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions1 = - customizeOptions( - Arrays.asList("-AuseConservativeDefaultsForUncheckedCode=source,bytecode")); - TestConfiguration config1 = - TestConfigurationBuilder.buildDefaultConfiguration( - "tests/nullness-safedefaultssourcecodelib", - new File("tests/nullness-safedefaultssourcecodelib", "Lib.java"), - NullnessChecker.class, - customizedOptions1, - shouldEmitDebugInfo); - TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); - TestUtilities.assertTestDidNotFail(testResult1); + @Override + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions1 = + customizeOptions( + Arrays.asList("-AuseConservativeDefaultsForUncheckedCode=source,bytecode")); + TestConfiguration config1 = + TestConfigurationBuilder.buildDefaultConfiguration( + "tests/nullness-safedefaultssourcecodelib", + new File("tests/nullness-safedefaultssourcecodelib", "Lib.java"), + NullnessChecker.class, + customizedOptions1, + shouldEmitDebugInfo); + TypecheckResult testResult1 = new TypecheckExecutor().runTest(config1); + TestUtilities.assertTestDidNotFail(testResult1); - List customizedOptions2 = - customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config2 = - TestConfigurationBuilder.buildDefaultConfiguration( - testDir, - testFiles, - Collections.singleton(NullnessChecker.class.getName()), - customizedOptions2, - shouldEmitDebugInfo); - TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); - TestUtilities.assertTestDidNotFail(testResult2); - } + List customizedOptions2 = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config2 = + TestConfigurationBuilder.buildDefaultConfiguration( + testDir, + testFiles, + Collections.singleton(NullnessChecker.class.getName()), + customizedOptions2, + shouldEmitDebugInfo); + TypecheckResult testResult2 = new TypecheckExecutor().runTest(config2); + TestUtilities.assertTestDidNotFail(testResult2); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java index 1ecb5f73ed7..7865131017c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDefsTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness Checker -- testing {@code -AskipDefs} command-line argument. */ public class NullnessSkipDefsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSkipDefsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSkipDefsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AskipDefs=SkipMe"); - } + /** + * Create a NullnessSkipDefsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSkipDefsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AskipDefs=SkipMe"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-skipdefs"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-skipdefs"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java index d3a4d224c50..fee89587752 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java @@ -1,23 +1,24 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness Checker -- testing {@code -AskipFiles} command-line argument. */ public class NullnessSkipDirsTest extends CheckerFrameworkPerDirectoryTest { - public NullnessSkipDirsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AskipFiles=/skip/this/.*"); - } + public NullnessSkipDirsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AskipFiles=/skip/this/.*"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-skipdirs"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-skipdirs"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java index 26b1e1b1117..077fc55365f 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipUsesTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness Checker -- testing {@code -AskipUses} command-line argument. */ public class NullnessSkipUsesTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessSkipUsesTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessSkipUsesTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AskipUses=SkipMe"); - } + /** + * Create a NullnessSkipUsesTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessSkipUsesTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AskipUses=SkipMe"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-skipuses"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-skipuses"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java index 8081d88aba4..99101a6033f 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java @@ -1,31 +1,32 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestUtilities; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class NullnessStubfileTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessStubfileTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessStubfileTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - TestUtilities.adapt( - "-Astubs=tests/nullness-stubfile/stubfile1.astub:" - + "tests/nullness-stubfile/stubfile2.astub:" - + "tests/nullness-stubfile/requireNonNull.astub")); - } + /** + * Create a NullnessStubfileTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessStubfileTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + TestUtilities.adapt( + "-Astubs=tests/nullness-stubfile/stubfile1.astub:" + + "tests/nullness-stubfile/stubfile2.astub:" + + "tests/nullness-stubfile/requireNonNull.astub")); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-stubfile"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-stubfile"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java index e2e4f6d4bee..c7d54b0cb94 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTempTest.java @@ -1,29 +1,31 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness Checker. */ public class NullnessTempTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessTempTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessTempTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessTempTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessTempTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-Alint=soundArrayCreationNullness," + + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-temp"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-temp"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java index d74c919da49..c4e519e96c1 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessTest.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * JUnit tests for the Nullness Checker with the Initialization Checker. * @@ -23,24 +24,27 @@ */ public class NullnessTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness", - "-AcheckPurityAnnotations", - "-AconservativeArgumentNullnessAfterInvocation=true", - "-Xlint:deprecation", - "-Alint=soundArrayCreationNullness," + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); - } + /** + * Create a NullnessTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness", + "-AcheckPurityAnnotations", + "-AconservativeArgumentNullnessAfterInvocation=true", + "-Xlint:deprecation", + "-Alint=soundArrayCreationNullness," + + NullnessChecker.LINT_REDUNDANTNULLCOMPARISON); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness", "nullness-initialization", "initialization", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] { + "nullness", "nullness-initialization", "initialization", "all-systems" + }; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java index 3bb2111262c..3f44fd1e469 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessWarnRedundantAnnotationsTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Nullness checker when AwarnRedundantAnnotations is used. */ public class NullnessWarnRedundantAnnotationsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a NullnessWarnRedundantAnnotationsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public NullnessWarnRedundantAnnotationsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "nullness-warnredundantannotations", - "-AwarnRedundantAnnotations"); - } + /** + * Create a NullnessWarnRedundantAnnotationsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public NullnessWarnRedundantAnnotationsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "nullness-warnredundantannotations", + "-AwarnRedundantAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nullness-warnredundantannotations"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nullness-warnredundantannotations"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java index be95a29a0df..ca7d19aeacf 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalPureGettersTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Optional Checker, which has the {@code @Present} annotation. */ public class OptionalPureGettersTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an OptionalPureGettersTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public OptionalPureGettersTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.optional.OptionalChecker.class, - "optional", - "-AassumePureGetters"); - } + /** + * Create an OptionalPureGettersTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public OptionalPureGettersTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.optional.OptionalChecker.class, + "optional", + "-AassumePureGetters"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"optional-pure-getters"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"optional-pure-getters"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java index ee2087f2303..e9e36acf9a5 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/OptionalTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Optional Checker, which has the {@code @Present} annotation. */ public class OptionalTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create an OptionalTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public OptionalTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.optional.OptionalChecker.class, - "optional", - "-AoptionalMapAssumeNonNull"); - } + /** + * Create an OptionalTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public OptionalTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.optional.OptionalChecker.class, + "optional", + "-AoptionalMapAssumeNonNull"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"optional", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"optional", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java index 88dd3d0de0d..0694f4fafec 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ParseAllJdkTest.java @@ -1,25 +1,26 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests {@code -AparseAllJdk} option. */ public class ParseAllJdkTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a ParseAllJdkTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public ParseAllJdkTest(List testFiles) { - super(testFiles, NullnessChecker.class, "parse-all-jdk", "-AparseAllJdk"); - } + /** + * Create a ParseAllJdkTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public ParseAllJdkTest(List testFiles) { + super(testFiles, NullnessChecker.class, "parse-all-jdk", "-AparseAllJdk"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"parse-all-jdk"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"parse-all-jdk"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java index 007998351ac..7d9f8688613 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/RegexTest.java @@ -1,23 +1,24 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class RegexTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a RegexTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public RegexTest(List testFiles) { - super(testFiles, org.checkerframework.checker.regex.RegexChecker.class, "regex"); - } + /** + * Create a RegexTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public RegexTest(List testFiles) { + super(testFiles, org.checkerframework.checker.regex.RegexChecker.class, "regex"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"regex", "regex_poly", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"regex", "regex_poly", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java index df6911b0620..bfecdb05d97 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakCustomIgnoredExceptionsTest.java @@ -1,25 +1,26 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + public class ResourceLeakCustomIgnoredExceptionsTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakCustomIgnoredExceptionsTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-customignoredexceptions", - "-AresourceLeakIgnoredExceptions=java.lang.Error, =java.lang.NullPointerException", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakCustomIgnoredExceptionsTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-customignoredexceptions", + "-AresourceLeakIgnoredExceptions=java.lang.Error, =java.lang.NullPointerException", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-customignoredexceptions"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-customignoredexceptions"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java index 9a69eac7c02..42addc33c03 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakExtraIgnoredExceptionsTest.java @@ -1,25 +1,26 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + public class ResourceLeakExtraIgnoredExceptionsTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakExtraIgnoredExceptionsTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-extraignoredexceptions", - "-AresourceLeakIgnoredExceptions=default,java.lang.IllegalStateException", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakExtraIgnoredExceptionsTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-extraignoredexceptions", + "-AresourceLeakIgnoredExceptions=default,java.lang.IllegalStateException", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-extraignoredexceptions"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-extraignoredexceptions"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java index 0243ed9ea4a..6b73c882cb5 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoCreatesMustCallForTest.java @@ -1,26 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the Resource Leak Checker. */ public class ResourceLeakNoCreatesMustCallForTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakNoCreatesMustCallForTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-nocreatesmustcallfor", - "-AnoCreatesMustCallFor", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakNoCreatesMustCallForTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-nocreatesmustcallfor", + "-AnoCreatesMustCallFor", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-nocreatesmustcallfor"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-nocreatesmustcallfor"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java index 629e93c5fd9..3f0354b894d 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoLightweightOwnershipTest.java @@ -1,26 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the Resource Leak Checker. */ public class ResourceLeakNoLightweightOwnershipTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakNoLightweightOwnershipTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-nolightweightownership", - "-AnoLightweightOwnership", - "-nowarn", - "-encoding", - "UTF-8"); - } + public ResourceLeakNoLightweightOwnershipTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-nolightweightownership", + "-AnoLightweightOwnership", + "-nowarn", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-nolightweightownership"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-nolightweightownership"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java index 4d18f3075ee..99d77e202be 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakNoResourceAliasesTest.java @@ -1,26 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the Resource Leak Checker. */ public class ResourceLeakNoResourceAliasesTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakNoResourceAliasesTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-noresourcealiases", - "-AnoResourceAliases", - "-nowarn", - "-encoding", - "UTF-8"); - } + public ResourceLeakNoResourceAliasesTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-noresourcealiases", + "-AnoResourceAliases", + "-nowarn", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-noresourcealiases"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-noresourcealiases"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java index 1b6c605c758..668217f1f08 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitInitializationLeak.java @@ -1,26 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the Resource Leak Checker. */ public class ResourceLeakPermitInitializationLeak extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakPermitInitializationLeak(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-permitinitializationleak", - "-ApermitInitializationLeak", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakPermitInitializationLeak(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-permitinitializationleak", + "-ApermitInitializationLeak", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-permitinitializationleak"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-permitinitializationleak"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java index ffb496ea1e4..41347bd58d8 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakPermitStaticOwning.java @@ -1,26 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the Resource Leak Checker. */ public class ResourceLeakPermitStaticOwning extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakPermitStaticOwning(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak-permitstaticowning", - "-ApermitStaticOwning", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakPermitStaticOwning(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak-permitstaticowning", + "-ApermitStaticOwning", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak-permitstaticowning"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak-permitstaticowning"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java index 1329fefed04..e0868c6f63c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ResourceLeakTest.java @@ -1,25 +1,26 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the Resource Leak Checker. */ public class ResourceLeakTest extends CheckerFrameworkPerDirectoryTest { - public ResourceLeakTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak", - "-AwarnUnneededSuppressions", - "-encoding", - "UTF-8"); - } + public ResourceLeakTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak", + "-AwarnUnneededSuppressions", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"resourceleak"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"resourceleak"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java index ce5f97880b2..c649e50fb31 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignatureTest.java @@ -1,23 +1,27 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class SignatureTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignatureTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignatureTest(List testFiles) { - super(testFiles, org.checkerframework.checker.signature.SignatureChecker.class, "signature"); - } + /** + * Create a SignatureTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignatureTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.signature.SignatureChecker.class, + "signature"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signature", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signature", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java index bd8f6caac2b..09ae2e1fe62 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessInitializedFieldsTest.java @@ -1,32 +1,33 @@ package org.checkerframework.checker.test.junit; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; public class SignednessInitializedFieldsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignednessInitializedFieldsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignednessInitializedFieldsTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "org.checkerframework.common.initializedfields.InitializedFieldsChecker", - "org.checkerframework.checker.signedness.SignednessChecker"), - "signedness-initialized-fields", - Collections.emptyList() // classpathextra - ); - } + /** + * Create a SignednessInitializedFieldsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignednessInitializedFieldsTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "org.checkerframework.common.initializedfields.InitializedFieldsChecker", + "org.checkerframework.checker.signedness.SignednessChecker"), + "signedness-initialized-fields", + Collections.emptyList() // classpathextra + ); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signedness-initialized-fields", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signedness-initialized-fields", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java index 47748c23945..c5f0946897b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class SignednessTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignednessTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignednessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.signedness.SignednessChecker.class, - "signedness", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create a SignednessTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignednessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.signedness.SignednessChecker.class, + "signedness", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signedness", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signedness", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java index 9a6166f5069..c75554f9e8b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/SignednessUncheckedDefaultsTest.java @@ -1,27 +1,28 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class SignednessUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a SignednessUncheckedDefaultsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public SignednessUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.signedness.SignednessChecker.class, - "signedness", - "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); - } + /** + * Create a SignednessUncheckedDefaultsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public SignednessUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.signedness.SignednessChecker.class, + "signedness", + "-AuseConservativeDefaultsForUncheckedCode=-source,bytecode"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"signedness-unchecked-defaults"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"signedness-unchecked-defaults"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java index 4fd17302f70..f64fe68d0dd 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserNullnessTest.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + /** Tests for stub parsing. */ public class StubparserNullnessTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a StubparserNullnessTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public StubparserNullnessTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "stubparser-nullness", - "-Astubs=tests/stubparser-nullness"); - } + /** + * Create a StubparserNullnessTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public StubparserNullnessTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "stubparser-nullness", + "-Astubs=tests/stubparser-nullness"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"stubparser-nullness"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"stubparser-nullness"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java index 2cd0a718f80..3b170c6a60a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserRecordTest.java @@ -1,34 +1,35 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + /** Tests for stub parsing with records. */ public class StubparserRecordTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a StubparserRecordTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public StubparserRecordTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.nullness.NullnessChecker.class, - "stubparser-records", - "-Astubs=tests/stubparser-records"); - } + /** + * Create a StubparserRecordTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public StubparserRecordTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.nullness.NullnessChecker.class, + "stubparser-records", + "-Astubs=tests/stubparser-records"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - // Check for JDK 16+ without using a library: - // There is no decimal point in the JDK 17 version number. - if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { - return new String[] {"stubparser-records"}; - } else { - return new String[] {}; + @Parameterized.Parameters + public static String[] getTestDirs() { + // Check for JDK 16+ without using a library: + // There is no decimal point in the JDK 17 version number. + if (System.getProperty("java.version").matches("^(1[6-9]|[2-9][0-9])(\\..*)?")) { + return new String[] {"stubparser-records"}; + } else { + return new String[] {}; + } } - } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java index 55bac7a9cc5..b9e7508207c 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/StubparserTaintingTest.java @@ -1,29 +1,30 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + /** Tests for stub parsing. */ public class StubparserTaintingTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a StubparserTaintingTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public StubparserTaintingTest(List testFiles) { - super( - testFiles, - org.checkerframework.checker.tainting.TaintingChecker.class, - "stubparser-tainting", - "-AmergeStubsWithSource", - "-Astubs=tests/stubparser-tainting"); - } + /** + * Create a StubparserTaintingTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public StubparserTaintingTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.tainting.TaintingChecker.class, + "stubparser-tainting", + "-AmergeStubsWithSource", + "-Astubs=tests/stubparser-tainting"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"stubparser-tainting", "all-systems"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"stubparser-tainting", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java index 5a2ce4ec997..1b6ddb9136a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/TaintingTest.java @@ -1,24 +1,25 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.checker.tainting.TaintingChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class TaintingTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a TaintingTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public TaintingTest(List testFiles) { - super(testFiles, TaintingChecker.class, "tainting"); - } + /** + * Create a TaintingTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public TaintingTest(List testFiles) { + super(testFiles, TaintingChecker.class, "tainting"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"tainting", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"tainting", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java index b22d5d041dc..f2f37767005 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/UnitsTest.java @@ -1,23 +1,24 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class UnitsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a UnitsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public UnitsTest(List testFiles) { - super(testFiles, org.checkerframework.checker.units.UnitsChecker.class, "units"); - } + /** + * Create a UnitsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public UnitsTest(List testFiles) { + super(testFiles, org.checkerframework.checker.units.UnitsChecker.class, "units"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"units", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"units", "all-systems"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java index b2fc0e98432..edcaa5767f4 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ValueIndexInteractionTest.java @@ -1,29 +1,30 @@ package org.checkerframework.checker.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Value Checker's interactions with the Index Checker. */ public class ValueIndexInteractionTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a ValueIndexInteractionTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueIndexInteractionTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value-index-interaction", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * Create a ValueIndexInteractionTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueIndexInteractionTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value-index-interaction", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"value-index-interaction"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"value-index-interaction"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java index 98f81fb75d7..b84d0bf8cad 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaGenerationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program inference with the aid of ajava files. This test is the first pass on the * test data, which generates the ajava files. This specific test suite is designed to elicit @@ -19,21 +20,21 @@ @Category(AinferIndexAjavaGenerationTest.class) public class AinferIndexAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferIndexAjavaGenerationTest(List testFiles) { - super( - testFiles, - IndexChecker.class, - "ainfer-index/non-annotated", - "-Ainfer=ajava", - // "-Aajava=tests/ainfer-index/input-annotation-files/", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferIndexAjavaGenerationTest(List testFiles) { + super( + testFiles, + IndexChecker.class, + "ainfer-index/non-annotated", + "-Ainfer=ajava", + // "-Aajava=tests/ainfer-index/input-annotation-files/", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-index/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-index/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java index 46c2f76ae3d..87cc53d482e 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferIndexAjavaValidationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.index.IndexChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program type inference with ajava files. This test is the second pass, which ensures * that with the ajava files in place, the errors that those annotations remove are no longer @@ -15,22 +16,22 @@ @Category(AinferIndexAjavaGenerationTest.class) public class AinferIndexAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferIndexAjavaValidationTest(List testFiles) { - super( - testFiles, - IndexChecker.class, - "index", - "ainfer-index/annotated", - AinferIndexAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "index"), - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferIndexAjavaValidationTest(List testFiles) { + super( + testFiles, + IndexChecker.class, + "index", + "ainfer-index/annotated", + AinferIndexAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "index"), + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-index/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-index/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java index d61fb6a7a96..e55b10de9cf 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaGenerationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program inference with the aid of ajava files. This test is the first pass on the * test data, which generates the ajava files. This specific test suite is designed to elicit @@ -19,21 +20,21 @@ @Category(AinferNullnessAjavaGenerationTest.class) public class AinferNullnessAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessAjavaGenerationTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "ainfer-nullness/non-annotated", - "-Ainfer=ajava", - "-Aajava=tests/ainfer-nullness/input-annotation-files/", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessAjavaGenerationTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "ainfer-nullness/non-annotated", + "-Ainfer=ajava", + "-Aajava=tests/ainfer-nullness/input-annotation-files/", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java index 1288354c5a2..334acae4f71 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessAjavaValidationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program type inference with ajava files. This test is the second pass, which ensures * that with the ajava files in place, the errors that those annotations remove are no longer @@ -15,22 +16,22 @@ @Category(AinferNullnessAjavaGenerationTest.class) public class AinferNullnessAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessAjavaValidationTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - "ainfer-nullness/annotated", - AinferNullnessAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "nullness"), - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessAjavaValidationTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + "ainfer-nullness/annotated", + AinferNullnessAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "nullness"), + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java index a755ab4bfc1..090023bc123 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsGenerationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Runs whole-program inference and inserts annotations into source code. * @@ -16,21 +17,21 @@ */ @Category(AinferNullnessJaifsGenerationTest.class) public class AinferNullnessJaifsGenerationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessJaifsGenerationTest(List testFiles) { - super( - testFiles, - NullnessChecker.class, - "nullness", - "-Ainfer=jaifs", - "-Awarns", - "-Aajava=tests/ainfer-nullness/input-annotation-files/"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessJaifsGenerationTest(List testFiles) { + super( + testFiles, + NullnessChecker.class, + "nullness", + "-Ainfer=jaifs", + "-Awarns", + "-Aajava=tests/ainfer-nullness/input-annotation-files/"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java index d332f78e91a..d9613d66b66 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferNullnessJaifsValidationTest.java @@ -1,38 +1,39 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.nullness.NullnessChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program type inference with the aid of .jaif files. This test is the second pass, * which ensures that with the annotations inserted, the errors are no longer issued. */ @Category(AinferNullnessJaifsGenerationTest.class) public class AinferNullnessJaifsValidationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferNullnessJaifsValidationTest(List testFiles) { - super(testFiles, NullnessChecker.class, "nullness"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferNullnessJaifsValidationTest(List testFiles) { + super(testFiles, NullnessChecker.class, "nullness"); + } - @Override - public void run() { - // Only run if annotated files have been created. - // See ainferTest task. - if (!new File("tests/ainfer-nullness/annotated/").exists()) { - throw new RuntimeException( - AinferNullnessJaifsGenerationTest.class + " must be run before this test."); + @Override + public void run() { + // Only run if annotated files have been created. + // See ainferTest task. + if (!new File("tests/ainfer-nullness/annotated/").exists()) { + throw new RuntimeException( + AinferNullnessJaifsGenerationTest.class + " must be run before this test."); + } + super.run(); } - super.run(); - } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-nullness/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-nullness/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java index 32a11b6c351..19760313b7a 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaGenerationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests RLC-specific inference features with the aid of ajava files. This test is the first pass on * the test data, which generates the ajava files. @@ -20,21 +21,21 @@ @Category(AinferResourceLeakAjavaGenerationTest.class) public class AinferResourceLeakAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferResourceLeakAjavaGenerationTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "ainfer-resourceleak/non-annotated", - "-Ainfer=ajava", - // "-Aajava=tests/ainfer-resourceleak/input-annotation-files/", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferResourceLeakAjavaGenerationTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "ainfer-resourceleak/non-annotated", + "-Ainfer=ajava", + // "-Aajava=tests/ainfer-resourceleak/input-annotation-files/", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-resourceleak/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-resourceleak/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java index 59493b9240f..88739d56fd6 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferResourceLeakAjavaValidationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.resourceleak.ResourceLeakChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests RLC-specific inference features with ajava files. This test is the second pass, which * ensures that with the ajava files in place, the errors that those annotations remove are no @@ -15,22 +16,22 @@ @Category(AinferResourceLeakAjavaGenerationTest.class) public class AinferResourceLeakAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferResourceLeakAjavaValidationTest(List testFiles) { - super( - testFiles, - ResourceLeakChecker.class, - "resourceleak", - "ainfer-resourceleak/annotated", - AinferResourceLeakAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "resourceleak"), - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferResourceLeakAjavaValidationTest(List testFiles) { + super( + testFiles, + ResourceLeakChecker.class, + "resourceleak", + "ainfer-resourceleak/annotated", + AinferResourceLeakAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "resourceleak"), + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-resourceleak/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-resourceleak/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java index 5d9949e1387..3807a530756 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaGenerationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program inference with the aid of ajava files. This test is the first pass on the * test data, which generates the ajava files. @@ -18,21 +19,21 @@ @Category(AinferTestCheckerAjavaGenerationTest.class) public class AinferTestCheckerAjavaGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerAjavaGenerationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "ainfer-testchecker/non-annotated", - "-Ainfer=ajava", - "-Aajava=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerAjavaGenerationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "ainfer-testchecker/non-annotated", + "-Ainfer=ajava", + "-Aajava=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java index 09e49a0e3fd..9cb1997fb73 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerAjavaValidationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program type inference with ajava files. This test is the second pass, which ensures * that with the ajava files in place, the errors that those annotations remove are no longer @@ -15,23 +16,23 @@ @Category(AinferTestCheckerAjavaGenerationTest.class) public class AinferTestCheckerAjavaValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerAjavaValidationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "testchecker", - "ainfer-testchecker/annotated", - AinferTestCheckerAjavaGenerationTest.class, - ajavaArgFromFiles(testFiles, "testchecker"), - "-AcheckPurityAnnotations", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerAjavaValidationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "testchecker", + "ainfer-testchecker/annotated", + AinferTestCheckerAjavaGenerationTest.class, + ajavaArgFromFiles(testFiles, "testchecker"), + "-AcheckPurityAnnotations", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java index bd109812699..eba528c9634 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsGenerationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Runs whole-program inference and inserts annotations into source code. * @@ -16,25 +17,25 @@ */ @Category(AinferTestCheckerJaifsGenerationTest.class) public class AinferTestCheckerJaifsGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerJaifsGenerationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "ainfer-testchecker/non-annotated", - "-Ainfer=jaifs", - // Use a stub file here, even though this is a JAIF test. This test can't pass - // without an external file that specifies that a method is pure, and there is no - // way to directly pass a JAIF file (in a real WPI run, the JAIF file's annotations - // would have been inserted into the source). - "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerJaifsGenerationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "ainfer-testchecker/non-annotated", + "-Ainfer=jaifs", + // Use a stub file here, even though this is a JAIF test. This test can't pass + // without an external file that specifies that a method is pure, and there is no + // way to directly pass a JAIF file (in a real WPI run, the JAIF file's annotations + // would have been inserted into the source). + "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java index 68fc2a17df6..1fe779d8fad 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerJaifsValidationTest.java @@ -1,34 +1,35 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program type inference with the aid of .jaif files. This test is the second pass, * which ensures that with the annotations inserted, the errors are no longer issued. */ @Category(AinferTestCheckerJaifsGenerationTest.class) public class AinferTestCheckerJaifsValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerJaifsValidationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "testchecker", - "ainfer-testchecker/non-annotated", - AinferTestCheckerJaifsGenerationTest.class, - "-Awarns", - "-AskipDefs=TestPure"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerJaifsValidationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "testchecker", + "ainfer-testchecker/non-annotated", + AinferTestCheckerJaifsGenerationTest.class, + "-Awarns", + "-AskipDefs=TestPure"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java index e7ebb3a505f..23f8becb43b 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsGenerationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferGeneratePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program inference with the aid of stub files. This test is the first pass on the test * data, which generates the stubs. @@ -18,21 +19,21 @@ @Category(AinferTestCheckerStubsGenerationTest.class) public class AinferTestCheckerStubsGenerationTest extends AinferGeneratePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerStubsGenerationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "ainfer-testchecker/non-annotated", - "-Ainfer=stubs", - "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", - "-Awarns"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerStubsGenerationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "ainfer-testchecker/non-annotated", + "-Ainfer=stubs", + "-Astubs=tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.astub", + "-Awarns"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/non-annotated"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/non-annotated"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java index 138aa29e71b..778b91e3c77 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/AinferTestCheckerStubsValidationTest.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.test.junit.ainferrunners; -import java.io.File; -import java.util.List; import org.checkerframework.checker.testchecker.ainfer.AinferTestChecker; import org.checkerframework.framework.test.AinferValidatePerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests whole-program type inference with stub files. This test is the second pass, which ensures * that with the stubs in place, the errors that those annotations remove are no longer issued. @@ -14,25 +15,25 @@ @Category(AinferTestCheckerStubsGenerationTest.class) public class AinferTestCheckerStubsValidationTest extends AinferValidatePerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AinferTestCheckerStubsValidationTest(List testFiles) { - super( - testFiles, - AinferTestChecker.class, - "testchecker", - "ainfer-testchecker/annotated", - AinferTestCheckerStubsGenerationTest.class, - astubsArgFromFiles(testFiles, "testchecker"), - // "-AstubDebug", - "-AmergeStubsWithSource", - "-Awarns", - "-AskipDefs=TestPure"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AinferTestCheckerStubsValidationTest(List testFiles) { + super( + testFiles, + AinferTestChecker.class, + "testchecker", + "ainfer-testchecker/annotated", + AinferTestCheckerStubsGenerationTest.class, + astubsArgFromFiles(testFiles, "testchecker"), + // "-AstubDebug", + "-AmergeStubsWithSource", + "-Awarns", + "-AskipDefs=TestPure"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"ainfer-testchecker/annotated/"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"ainfer-testchecker/annotated/"}; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java index 87c7887dfbd..567af32fc0b 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/NestedAggregateChecker.java @@ -3,8 +3,6 @@ // Test case for Issue 343 // https://github.com/typetools/checker-framework/issues/343 -import java.util.ArrayList; -import java.util.Collection; import org.checkerframework.checker.fenum.FenumChecker; import org.checkerframework.checker.i18n.I18nChecker; import org.checkerframework.checker.nullness.NullnessChecker; @@ -12,17 +10,20 @@ import org.checkerframework.framework.source.AggregateChecker; import org.checkerframework.framework.source.SourceChecker; +import java.util.ArrayList; +import java.util.Collection; + public class NestedAggregateChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - ArrayList> list = - new ArrayList>(); + @Override + protected Collection> getSupportedCheckers() { + ArrayList> list = + new ArrayList>(); - list.add(FenumChecker.class); - list.add(I18nChecker.class); // The I18nChecker is an aggregate checker - list.add(NullnessChecker.class); - list.add(RegexChecker.class); + list.add(FenumChecker.class); + list.add(I18nChecker.class); // The I18nChecker is an aggregate checker + list.add(NullnessChecker.class); + list.add(RegexChecker.class); - return list; - } + return list; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java index dff1fa0ab0d..4ba8d643131 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestAnnotatedTypeFactory.java @@ -1,16 +1,5 @@ package org.checkerframework.checker.testchecker.ainfer; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; import org.checkerframework.checker.testchecker.ainfer.qual.AinferBottom; import org.checkerframework.checker.testchecker.ainfer.qual.AinferDefaultType; import org.checkerframework.checker.testchecker.ainfer.qual.AinferImplicitAnno; @@ -38,6 +27,19 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** * AnnotatedTypeFactory to test whole-program inference using .jaif files. * @@ -62,231 +64,238 @@ */ public class AinferTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private final AnnotationMirror PARENT = - new AnnotationBuilder(processingEnv, AinferParent.class).build(); - private final AnnotationMirror BOTTOM = - new AnnotationBuilder(processingEnv, AinferBottom.class).build(); - private final AnnotationMirror IMPLICIT_ANNO = - new AnnotationBuilder(processingEnv, AinferImplicitAnno.class).build(); + private final AnnotationMirror PARENT = + new AnnotationBuilder(processingEnv, AinferParent.class).build(); + private final AnnotationMirror BOTTOM = + new AnnotationBuilder(processingEnv, AinferBottom.class).build(); + private final AnnotationMirror IMPLICIT_ANNO = + new AnnotationBuilder(processingEnv, AinferImplicitAnno.class).build(); - private final AnnotationMirror SIBLING1 = - new AnnotationBuilder(processingEnv, AinferSibling1.class).build(); + private final AnnotationMirror SIBLING1 = + new AnnotationBuilder(processingEnv, AinferSibling1.class).build(); - // NO-AFU - // private final AnnotationMirror TREAT_AS_SIBLING1 = - // new AnnotationBuilder(processingEnv, AinferTreatAsSibling1.class).build(); + // NO-AFU + // private final AnnotationMirror TREAT_AS_SIBLING1 = + // new AnnotationBuilder(processingEnv, AinferTreatAsSibling1.class).build(); - /** The AinferSiblingWithFields.value field/element. */ - private final ExecutableElement siblingWithFieldsValueElement = - TreeUtils.getMethod(AinferSiblingWithFields.class, "value", 0, processingEnv); + /** The AinferSiblingWithFields.value field/element. */ + private final ExecutableElement siblingWithFieldsValueElement = + TreeUtils.getMethod(AinferSiblingWithFields.class, "value", 0, processingEnv); - /** The AinferSiblingWithFields.value2 field/element. */ - private final ExecutableElement siblingWithFieldsValue2Element = - TreeUtils.getMethod(AinferSiblingWithFields.class, "value2", 0, processingEnv); - - /** - * Creates an AinferTestAnnotatedTypeFactory. - * - * @param checker the checker - */ - public AinferTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - // Support a declaration annotation that has the same meaning as @Sibling1, to test that the - // WPI feature allowing inference of declaration annotations works as intended. - addAliasedTypeAnnotation(AinferTreatAsSibling1.class, SIBLING1); - postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - AinferParent.class, - AinferDefaultType.class, - AinferTop.class, - AinferSibling1.class, - AinferSibling2.class, - AinferBottom.class, - AinferSiblingWithFields.class, - AinferImplicitAnno.class)); - } - - @Override - public TreeAnnotator createTreeAnnotator() { - LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); - literalTreeAnnotator.addLiteralKind(LiteralKind.INT, BOTTOM); - literalTreeAnnotator.addStandardLiteralQualifiers(); - - return new ListTreeAnnotator( - new PropagationTreeAnnotator(this), - literalTreeAnnotator, - new AinferTestTreeAnnotator(this)); - } - - protected static class AinferTestTreeAnnotator extends TreeAnnotator { + /** The AinferSiblingWithFields.value2 field/element. */ + private final ExecutableElement siblingWithFieldsValue2Element = + TreeUtils.getMethod(AinferSiblingWithFields.class, "value2", 0, processingEnv); /** - * Create a new AinferTestTreeAnnotator. + * Creates an AinferTestAnnotatedTypeFactory. * - * @param atypeFactory the type factory + * @param checker the checker */ - protected AinferTestTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + public AinferTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + // Support a declaration annotation that has the same meaning as @Sibling1, to test that the + // WPI feature allowing inference of declaration annotations works as intended. + addAliasedTypeAnnotation(AinferTreatAsSibling1.class, SIBLING1); + postInit(); } - /* NO-AFU @Override - public Void visitClass(ClassTree classTree, AnnotatedTypeMirror type) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - TypeElement classElt = TreeUtils.elementFromDeclaration(classTree); - if (wpi != null && classElt.getSimpleName().contentEquals("IShouldBeSibling1")) { - wpi.addClassDeclarationAnnotation(classElt, SIBLING1); - } - return super.visitClass(classTree, type); + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + AinferParent.class, + AinferDefaultType.class, + AinferTop.class, + AinferSibling1.class, + AinferSibling2.class, + AinferBottom.class, + AinferSiblingWithFields.class, + AinferImplicitAnno.class)); } @Override - public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - VariableElement varElt = TreeUtils.elementFromDeclaration(variableTree); - if (wpi != null && varElt.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { - wpi.addFieldDeclarationAnnotation(varElt, TREAT_AS_SIBLING1); - } - return super.visitVariable(variableTree, type); - } + public TreeAnnotator createTreeAnnotator() { + LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); + literalTreeAnnotator.addLiteralKind(LiteralKind.INT, BOTTOM); + literalTreeAnnotator.addStandardLiteralQualifiers(); - @Override - public Void visitMethod(MethodTree methodTree, AnnotatedTypeMirror type) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - if (wpi != null) { - ExecutableElement execElt = TreeUtils.elementFromDeclaration(methodTree); - int numParams = execElt.getParameters().size(); - for (int i = 0; i < numParams; ++i) { - VariableElement param = execElt.getParameters().get(i); - if (param.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { - wpi.addDeclarationAnnotationToFormalParameter(execElt, i + 1, TREAT_AS_SIBLING1); - } - } - } - return super.visitMethod(methodTree, type); + return new ListTreeAnnotator( + new PropagationTreeAnnotator(this), + literalTreeAnnotator, + new AinferTestTreeAnnotator(this)); } - end NO-AFU */ - } - @Override - public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(elt, type); - // If an element has an @AinferTreatAsSibling1 annotation, replace its type with - // @AinferSibling1. - // This should be handled by the fact that @AinferTreatAsSibling1 and @AinferSibling1 are - // aliases, but by default the CF does not look for declaration annotations - // that are aliases of type annotations in annotation files. - // TODO: is that a bug in the CF or expected behavior? - if (getDeclAnnotation(elt, AinferTreatAsSibling1.class) != null) { - type.replaceAnnotation(SIBLING1); - } - } + protected static class AinferTestTreeAnnotator extends TreeAnnotator { - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new AinferTestQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } + /** + * Create a new AinferTestTreeAnnotator. + * + * @param atypeFactory the type factory + */ + protected AinferTestTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - /** - * Using a MultiGraphQualifierHierarchy to enable tests with Annotations that contain fields. - * - * @see AinferSiblingWithFields - */ - protected class AinferTestQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + /* NO-AFU + @Override + public Void visitClass(ClassTree classTree, AnnotatedTypeMirror type) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + TypeElement classElt = TreeUtils.elementFromDeclaration(classTree); + if (wpi != null && classElt.getSimpleName().contentEquals("IShouldBeSibling1")) { + wpi.addClassDeclarationAnnotation(classElt, SIBLING1); + } + return super.visitClass(classTree, type); + } - private final QualifierKind SIBLING_WITH_FIELDS_KIND; + @Override + public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + VariableElement varElt = TreeUtils.elementFromDeclaration(variableTree); + if (wpi != null && varElt.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { + wpi.addFieldDeclarationAnnotation(varElt, TREAT_AS_SIBLING1); + } + return super.visitVariable(variableTree, type); + } - /** - * Creates a AinferTestQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - protected AinferTestQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, AinferTestAnnotatedTypeFactory.this); - SIBLING_WITH_FIELDS_KIND = getQualifierKind(AinferSiblingWithFields.class.getCanonicalName()); + @Override + public Void visitMethod(MethodTree methodTree, AnnotatedTypeMirror type) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + if (wpi != null) { + ExecutableElement execElt = TreeUtils.elementFromDeclaration(methodTree); + int numParams = execElt.getParameters().size(); + for (int i = 0; i < numParams; ++i) { + VariableElement param = execElt.getParameters().get(i); + if (param.getSimpleName().contentEquals("iShouldBeTreatedAsSibling1")) { + wpi.addDeclarationAnnotationToFormalParameter(execElt, i + 1, TREAT_AS_SIBLING1); + } + } + } + return super.visitMethod(methodTree, type); + } + end NO-AFU */ } @Override - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - return BOTTOM; + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(elt, type); + // If an element has an @AinferTreatAsSibling1 annotation, replace its type with + // @AinferSibling1. + // This should be handled by the fact that @AinferTreatAsSibling1 and @AinferSibling1 are + // aliases, but by default the CF does not look for declaration annotations + // that are aliases of type annotations in annotation files. + // TODO: is that a bug in the CF or expected behavior? + if (getDeclAnnotation(elt, AinferTreatAsSibling1.class) != null) { + type.replaceAnnotation(SIBLING1); + } } @Override - public AnnotationMirrorSet getBottomAnnotations() { - return new AnnotationMirrorSet(BOTTOM); + protected QualifierHierarchy createQualifierHierarchy() { + return new AinferTestQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { - return a1; - } else { - return IMPLICIT_ANNO; + /** + * Using a MultiGraphQualifierHierarchy to enable tests with Annotations that contain fields. + * + * @see AinferSiblingWithFields + */ + protected class AinferTestQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + private final QualifierKind SIBLING_WITH_FIELDS_KIND; + + /** + * Creates a AinferTestQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + protected AinferTestQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, AinferTestAnnotatedTypeFactory.this); + SIBLING_WITH_FIELDS_KIND = + getQualifierKind(AinferSiblingWithFields.class.getCanonicalName()); } - } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - return a1; - } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); - } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { - return a1; - } else { - return PARENT; + @Override + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + return BOTTOM; } - } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { - return a1; - } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { - return a2; - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); - } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == SIBLING_WITH_FIELDS_KIND && superKind == SIBLING_WITH_FIELDS_KIND) { - List subVal1 = - AnnotationUtils.getElementValueArray( - subAnno, siblingWithFieldsValueElement, String.class, Collections.emptyList()); - List supVal1 = - AnnotationUtils.getElementValueArray( - superAnno, siblingWithFieldsValueElement, String.class, Collections.emptyList()); - String subVal2 = - AnnotationUtils.getElementValue( - subAnno, siblingWithFieldsValue2Element, String.class, ""); - String supVal2 = - AnnotationUtils.getElementValue( - superAnno, siblingWithFieldsValue2Element, String.class, ""); - return subVal1.equals(supVal1) && subVal2.equals(supVal2); - } - throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); + @Override + public AnnotationMirrorSet getBottomAnnotations() { + return new AnnotationMirrorSet(BOTTOM); + } + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { + return a1; + } else { + return IMPLICIT_ANNO; + } + } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + return a1; + } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + } + + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2 && qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + if (isSubtypeWithElements(a1, qualifierKind1, a2, qualifierKind2)) { + return a1; + } else { + return PARENT; + } + } else if (qualifierKind1 == SIBLING_WITH_FIELDS_KIND) { + return a1; + } else if (qualifierKind2 == SIBLING_WITH_FIELDS_KIND) { + return a2; + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", a1, a2); + } + + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == SIBLING_WITH_FIELDS_KIND && superKind == SIBLING_WITH_FIELDS_KIND) { + List subVal1 = + AnnotationUtils.getElementValueArray( + subAnno, + siblingWithFieldsValueElement, + String.class, + Collections.emptyList()); + List supVal1 = + AnnotationUtils.getElementValueArray( + superAnno, + siblingWithFieldsValueElement, + String.class, + Collections.emptyList()); + String subVal2 = + AnnotationUtils.getElementValue( + subAnno, siblingWithFieldsValue2Element, String.class, ""); + String supVal2 = + AnnotationUtils.getElementValue( + superAnno, siblingWithFieldsValue2Element, String.class, ""); + return subVal1.equals(supVal1) && subVal2.equals(supVal2); + } + throw new TypeSystemError("Unexpected qualifiers: %s %s", subAnno, superAnno); + } } - } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java index a767ad6b2b0..1149f3a65c1 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestChecker.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.testchecker.ainfer; -import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; +import java.util.Set; + /** * Checker for a simple type system to test whole-program inference. Uses the Value Checker as a * subchecker to ensure that generated files contain annotations both from this checker and from the @@ -12,15 +13,15 @@ */ public class AinferTestChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new AinferTestVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new AinferTestVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - checkers.add(ValueChecker.class); - return checkers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(ValueChecker.class); + return checkers; + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java index f5908466b92..af24c61b1f8 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/AinferTestVisitor.java @@ -3,37 +3,39 @@ import com.sun.source.tree.AnnotationTree; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeInfo; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.checker.testchecker.ainfer.qual.AinferDefaultType; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; + /** Visitor for a simple type system to test whole-program inference using .jaif files. */ public class AinferTestVisitor extends BaseTypeVisitor { - public AinferTestVisitor(BaseTypeChecker checker) { - super(checker); - } + public AinferTestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected AinferTestAnnotatedTypeFactory createTypeFactory() { - return new AinferTestAnnotatedTypeFactory(checker); - } + @Override + protected AinferTestAnnotatedTypeFactory createTypeFactory() { + return new AinferTestAnnotatedTypeFactory(checker); + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + Element anno = TreeInfo.symbol((JCTree) tree.getAnnotationType()); + if (anno.toString().equals(AinferDefaultType.class.getName())) { + checker.reportError(tree, "annotation.not.allowed.in.src", anno.toString()); + } + return super.visitAnnotation(tree, p); + } - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - Element anno = TreeInfo.symbol((JCTree) tree.getAnnotationType()); - if (anno.toString().equals(AinferDefaultType.class.getName())) { - checker.reportError(tree, "annotation.not.allowed.in.src", anno.toString()); + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Skip this check. } - return super.visitAnnotation(tree, p); - } - - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // Skip this check. - } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java index 8634985e55c..cb325fd4f25 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferBottom.java @@ -1,12 +1,13 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** * Toy type system for testing field inference. * diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java index 8fbdc9ed922..ac18c134ec7 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferDefaultType.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** * AinferDefaultType is used to test the relaxInference option. Toy type system for testing field * inference. This annotation cannot be used in source code. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java index 51f2f7ab7d8..8e7f86a9702 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferImplicitAnno.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.testchecker.ainfer.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.IgnoreInWholeProgramInference; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** * Toy type system for testing field inference. * diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java index fdc86cdd25f..8ba9566c2c2 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferParent.java @@ -1,8 +1,9 @@ package org.checkerframework.checker.testchecker.ainfer.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java index cb2785b9de7..3dcf3b2ffd0 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling1.java @@ -1,8 +1,9 @@ package org.checkerframework.checker.testchecker.ainfer.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java index cd8bfa96c78..58f19ab886c 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSibling2.java @@ -1,8 +1,9 @@ package org.checkerframework.checker.testchecker.ainfer.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java index 75b159d1af2..d557f6cc549 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferSiblingWithFields.java @@ -1,8 +1,9 @@ package org.checkerframework.checker.testchecker.ainfer.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. @@ -14,7 +15,7 @@ @SubtypeOf(AinferParent.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface AinferSiblingWithFields { - String[] value() default {}; + String[] value() default {}; - String value2() default ""; + String value2() default ""; } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java index 9994c4c7f29..a8fadb9f548 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/ainfer/qual/AinferTop.java @@ -1,8 +1,9 @@ package org.checkerframework.checker.testchecker.ainfer.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing field inference. diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java index fbd5b07d94a..12618d40780 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseTypeFactory.java @@ -1,28 +1,29 @@ package org.checkerframework.checker.testchecker.disbaruse; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUseBottom; import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUseTop; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + /** The type factory for forbidding use of the DisbarUse type. */ public class DisbarUseTypeFactory extends BaseAnnotatedTypeFactory { - /** - * Creates a new DisbarUseTypeFactory. - * - * @param checker the checker - */ - public DisbarUseTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + /** + * Creates a new DisbarUseTypeFactory. + * + * @param checker the checker + */ + public DisbarUseTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet<>(Arrays.asList(DisbarUseTop.class, DisbarUseBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>(Arrays.asList(DisbarUseTop.class, DisbarUseBottom.class)); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java index 8c9b8506f93..d56cd25f9f0 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/DisbarUseVisitor.java @@ -5,65 +5,69 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.checker.testchecker.disbaruse.qual.DisbarUse; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; + public class DisbarUseVisitor extends BaseTypeVisitor { - public DisbarUseVisitor(BaseTypeChecker checker) { - super(checker); - } + public DisbarUseVisitor(BaseTypeChecker checker) { + super(checker); + } - protected DisbarUseVisitor(BaseTypeChecker checker, DisbarUseTypeFactory typeFactory) { - super(checker, typeFactory); - } + protected DisbarUseVisitor(BaseTypeChecker checker, DisbarUseTypeFactory typeFactory) { + super(checker, typeFactory); + } - @Override - protected DisbarUseTypeFactory createTypeFactory() { - return new DisbarUseTypeFactory(checker); - } + @Override + protected DisbarUseTypeFactory createTypeFactory() { + return new DisbarUseTypeFactory(checker); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - if (methodElt != null && atypeFactory.getDeclAnnotation(methodElt, DisbarUse.class) != null) { - checker.reportError(tree, "disbar.use"); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + if (methodElt != null + && atypeFactory.getDeclAnnotation(methodElt, DisbarUse.class) != null) { + checker.reportError(tree, "disbar.use"); + } + return super.visitMethodInvocation(tree, p); } - return super.visitMethodInvocation(tree, p); - } - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - ExecutableElement consElt = TreeUtils.elementFromUse(tree); - if (consElt != null && atypeFactory.getDeclAnnotation(consElt, DisbarUse.class) != null) { - checker.reportError(tree, "disbar.use"); + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + ExecutableElement consElt = TreeUtils.elementFromUse(tree); + if (consElt != null && atypeFactory.getDeclAnnotation(consElt, DisbarUse.class) != null) { + checker.reportError(tree, "disbar.use"); + } + return super.visitNewClass(tree, p); } - return super.visitNewClass(tree, p); - } - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - MemberSelectTree enclosingMemberSel = enclosingMemberSelect(); - ExpressionTree[] expressionTrees = - enclosingMemberSel == null - ? new ExpressionTree[] {tree} - : new ExpressionTree[] {enclosingMemberSel, tree}; + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + MemberSelectTree enclosingMemberSel = enclosingMemberSelect(); + ExpressionTree[] expressionTrees = + enclosingMemberSel == null + ? new ExpressionTree[] {tree} + : new ExpressionTree[] {enclosingMemberSel, tree}; - for (ExpressionTree memberSel : expressionTrees) { - Element elem = TreeUtils.elementFromUse(memberSel); + for (ExpressionTree memberSel : expressionTrees) { + Element elem = TreeUtils.elementFromUse(memberSel); - // We only issue errors for variables that are fields or parameters: - if (elem != null && (elem.getKind().isField() || elem.getKind() == ElementKind.PARAMETER)) { - if (atypeFactory.getDeclAnnotation(elem, DisbarUse.class) != null) { - checker.reportError(memberSel, "disbar.use"); + // We only issue errors for variables that are fields or parameters: + if (elem != null + && (elem.getKind().isField() || elem.getKind() == ElementKind.PARAMETER)) { + if (atypeFactory.getDeclAnnotation(elem, DisbarUse.class) != null) { + checker.reportError(memberSel, "disbar.use"); + } + } } - } - } - return super.visitIdentifier(tree, p); - } + return super.visitIdentifier(tree, p); + } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java index 43d9fecf6bb..0bd267c7504 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseBottom.java @@ -1,11 +1,12 @@ package org.checkerframework.checker.testchecker.disbaruse.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @DefaultFor(TypeUseLocation.LOWER_BOUND) @SubtypeOf(DisbarUseTop.class) @Target({ElementType.TYPE_USE}) diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java index 39749401b0e..36e8678e16c 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/disbaruse/qual/DisbarUseTop.java @@ -1,10 +1,11 @@ package org.checkerframework.checker.testchecker.disbaruse.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @Target({ElementType.TYPE_USE}) @SubtypeOf({}) @DefaultQualifierInHierarchy diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java index d416f1db9e3..fb1a74a4cf7 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/FormatterLubGlbChecker.java @@ -4,12 +4,6 @@ // https://github.com/typetools/checker-framework/issues/691 // https://github.com/typetools/checker-framework/issues/756 -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.formatter.FormatterAnnotatedTypeFactory; import org.checkerframework.checker.formatter.FormatterChecker; import org.checkerframework.checker.formatter.FormatterTreeUtil; @@ -25,6 +19,14 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** * This class tests the implementation of GLB computation in the Formatter Checker, but it does not * test for the crash described in issue 691. That is done by tests/all-systems/Issue691.java. It @@ -32,495 +34,510 @@ */ public class FormatterLubGlbChecker extends FormatterChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new FormatterVisitor(this) { - @Override - protected FormatterLubGlbAnnotatedTypeFactory createTypeFactory() { - return new FormatterLubGlbAnnotatedTypeFactory(checker); - } - }; - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new FormatterVisitor(this) { + @Override + protected FormatterLubGlbAnnotatedTypeFactory createTypeFactory() { + return new FormatterLubGlbAnnotatedTypeFactory(checker); + } + }; + } - /** FormatterLubGlbAnnotatedTypeFactory. */ - private static class FormatterLubGlbAnnotatedTypeFactory extends FormatterAnnotatedTypeFactory { + /** FormatterLubGlbAnnotatedTypeFactory. */ + private static class FormatterLubGlbAnnotatedTypeFactory extends FormatterAnnotatedTypeFactory { + + /** + * Constructor. + * + * @param checker checker + */ + public FormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + FormatBottom.class, + Format.class, + InvalidFormat.class, + UnknownFormat.class)); + } + } /** - * Constructor. + * Throws an exception if glb(arg1, arg2) != result. * - * @param checker checker + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result */ - public FormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); + private void glbAssert( + AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format( + "GLB of %s and %s should be %s, but is %s", + arg1, arg2, expected, result)); + } } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - FormatBottom.class, Format.class, InvalidFormat.class, UnknownFormat.class)); - } - } - - /** - * Throws an exception if glb(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void glbAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format("GLB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + /** + * Throws an exception if lub(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void lubAssert( + AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format( + "LUB of %s and %s should be %s, but is %s", + arg1, arg2, expected, result)); + } } - } - - /** - * Throws an exception if lub(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void lubAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format("LUB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + + @SuppressWarnings("checkstyle:localvariablename") + @Override + public void initChecker() { + super.initChecker(); + FormatterTreeUtil treeUtil = new FormatterTreeUtil(this); + + Elements elements = getElementUtils(); + AnnotationMirror UNKNOWNFORMAT = AnnotationBuilder.fromClass(elements, UnknownFormat.class); + AnnotationMirror FORMAT = + AnnotationBuilder.fromClass( + elements, + Format.class, + AnnotationBuilder.elementNamesValues("value", new ConversionCategory[0])); + AnnotationMirror INVALIDFORMAT = + AnnotationBuilder.fromClass( + elements, + InvalidFormat.class, + AnnotationBuilder.elementNamesValues("value", "dummy")); + AnnotationMirror FORMATBOTTOM = AnnotationBuilder.fromClass(elements, FormatBottom.class); + + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "Message"); + AnnotationMirror invalidFormatWithMessage = builder.build(); + + builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "Message2"); + AnnotationMirror invalidFormatWithMessage2 = builder.build(); + + builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "(\"Message\" or \"Message2\")"); + AnnotationMirror invalidFormatWithMessagesOred = builder.build(); + + builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); + builder.setValue("value", "(\"Message\" and \"Message2\")"); + AnnotationMirror invalidFormatWithMessagesAnded = builder.build(); + + ConversionCategory[] cc = new ConversionCategory[1]; + + cc[0] = ConversionCategory.UNUSED; + AnnotationMirror formatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.GENERAL; + AnnotationMirror formatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.CHAR; + AnnotationMirror formatCharAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.INT; + AnnotationMirror formatIntAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.TIME; + AnnotationMirror formatTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.FLOAT; + AnnotationMirror formatFloatAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.CHAR_AND_INT; + AnnotationMirror formatCharAndIntAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.INT_AND_TIME; + AnnotationMirror formatIntAndTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = ConversionCategory.NULL; + AnnotationMirror formatNullAnno = treeUtil.categoriesToFormatAnnotation(cc); + + // ** GLB tests ** + + glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); + + // GLB of UNUSED and others + + glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatGeneralAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatCharAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatIntAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatTimeAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatFloatAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatCharAndIntAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatIntAndTimeAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, formatNullAnno, formatUnusedAnno); + + // GLB of GENERAL and others + + glbAssert(formatGeneralAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatCharAndIntAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatIntAndTimeAnno, formatGeneralAnno); + glbAssert(formatGeneralAnno, formatNullAnno, formatGeneralAnno); + + // GLB of CHAR and others + + glbAssert(formatCharAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatCharAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatCharAnno, formatCharAnno); + glbAssert(formatCharAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatCharAndIntAnno, formatCharAnno); + glbAssert(formatCharAnno, formatIntAndTimeAnno, formatGeneralAnno); + glbAssert(formatCharAnno, formatNullAnno, formatCharAnno); + + // GLB of INT and others + + glbAssert(formatIntAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatIntAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatIntAnno, formatIntAnno); + glbAssert(formatIntAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatIntAnno, formatCharAndIntAnno, formatIntAnno); + glbAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAnno); + glbAssert(formatIntAnno, formatNullAnno, formatIntAnno); + + // GLB of TIME and others + + glbAssert(formatTimeAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatTimeAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); + glbAssert(formatTimeAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatCharAndIntAnno, formatGeneralAnno); + glbAssert(formatTimeAnno, formatIntAndTimeAnno, formatTimeAnno); + glbAssert(formatTimeAnno, formatNullAnno, formatTimeAnno); + + // GLB of FLOAT and others + + glbAssert(formatFloatAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatFloatAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatIntAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); + glbAssert(formatFloatAnno, formatCharAndIntAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatIntAndTimeAnno, formatGeneralAnno); + glbAssert(formatFloatAnno, formatNullAnno, formatFloatAnno); + + // GLB of CHAR_AND_INT and others + + glbAssert(formatCharAndIntAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatCharAndIntAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatCharAndIntAnno, formatCharAnno, formatCharAnno); + glbAssert(formatCharAndIntAnno, formatIntAnno, formatIntAnno); + glbAssert(formatCharAndIntAnno, formatTimeAnno, formatGeneralAnno); + glbAssert(formatCharAndIntAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); + glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); + glbAssert(formatCharAndIntAnno, formatNullAnno, formatCharAndIntAnno); + + // GLB of INT_AND_TIME and others + + glbAssert(formatIntAndTimeAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatIntAndTimeAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatIntAndTimeAnno, formatCharAnno, formatGeneralAnno); + glbAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAnno); + glbAssert(formatIntAndTimeAnno, formatTimeAnno, formatTimeAnno); + glbAssert(formatIntAndTimeAnno, formatFloatAnno, formatGeneralAnno); + glbAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatIntAnno); + glbAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + glbAssert(formatIntAndTimeAnno, formatNullAnno, formatIntAndTimeAnno); + + // GLB of NULL and others + + glbAssert(formatNullAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatNullAnno, formatGeneralAnno, formatGeneralAnno); + glbAssert(formatNullAnno, formatCharAnno, formatCharAnno); + glbAssert(formatNullAnno, formatIntAnno, formatIntAnno); + glbAssert(formatNullAnno, formatTimeAnno, formatTimeAnno); + glbAssert(formatNullAnno, formatFloatAnno, formatFloatAnno); + glbAssert(formatNullAnno, formatCharAndIntAnno, formatCharAndIntAnno); + glbAssert(formatNullAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + glbAssert(formatNullAnno, formatNullAnno, formatNullAnno); + + // Now test with two ConversionCategory at a time: + + ConversionCategory[] cc2 = new ConversionCategory[2]; + + cc2[0] = ConversionCategory.CHAR_AND_INT; + cc2[1] = ConversionCategory.FLOAT; + AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = ConversionCategory.INT; + cc2[1] = ConversionCategory.CHAR; + AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = ConversionCategory.INT; + cc2[1] = ConversionCategory.GENERAL; + AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); + + glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); + + // Test that the GLB of two ConversionCategory arrays of different sizes is an array of the + // smallest size of the two: + + glbAssert(formatGeneralAnno, formatTwoConvCat1, formatGeneralAnno); + glbAssert(formatTwoConvCat2, formatNullAnno, formatIntAnno); + + // GLB of @UnknownFormat and others + + glbAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + glbAssert(UNKNOWNFORMAT, FORMAT, FORMAT); + glbAssert(UNKNOWNFORMAT, formatUnusedAnno, formatUnusedAnno); + glbAssert(UNKNOWNFORMAT, INVALIDFORMAT, INVALIDFORMAT); + glbAssert(UNKNOWNFORMAT, invalidFormatWithMessage, invalidFormatWithMessage); + glbAssert(UNKNOWNFORMAT, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @Format(null) and others + + glbAssert(FORMAT, UNKNOWNFORMAT, FORMAT); + // Computing the GLB of @Format(null) and @Format(null) should never occur in practice; + // skipping this case as it causes an expected crash. + // Computing the GLB of @Format(null) and @Format with a value should never occur in + // practice; skipping this case as it causes an expected crash. + glbAssert(FORMAT, INVALIDFORMAT, FORMATBOTTOM); + glbAssert(FORMAT, invalidFormatWithMessage, FORMATBOTTOM); + glbAssert(FORMAT, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @Format(UNUSED) and others + + glbAssert(formatUnusedAnno, UNKNOWNFORMAT, formatUnusedAnno); + // Computing the GLB of @Format with a value and @Format(null) should never occur in + // practice; skipping this case as it causes an expected crash. + glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + glbAssert(formatUnusedAnno, INVALIDFORMAT, FORMATBOTTOM); + glbAssert(formatUnusedAnno, invalidFormatWithMessage, FORMATBOTTOM); + glbAssert(formatUnusedAnno, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @InvalidFormat(null) and others + + glbAssert(INVALIDFORMAT, UNKNOWNFORMAT, INVALIDFORMAT); + glbAssert(INVALIDFORMAT, FORMAT, FORMATBOTTOM); + glbAssert(INVALIDFORMAT, formatUnusedAnno, FORMATBOTTOM); + glbAssert(INVALIDFORMAT, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @InvalidFormat("Message") and others + + glbAssert(invalidFormatWithMessage, UNKNOWNFORMAT, invalidFormatWithMessage); + glbAssert(invalidFormatWithMessage, FORMAT, FORMATBOTTOM); + glbAssert(invalidFormatWithMessage, formatUnusedAnno, FORMATBOTTOM); + glbAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); + glbAssert( + invalidFormatWithMessage, + invalidFormatWithMessage2, + invalidFormatWithMessagesAnded); + glbAssert(invalidFormatWithMessage, FORMATBOTTOM, FORMATBOTTOM); + + // GLB of @FormatBottom and others + + glbAssert(FORMATBOTTOM, UNKNOWNFORMAT, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, FORMAT, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, formatUnusedAnno, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, INVALIDFORMAT, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, invalidFormatWithMessage, FORMATBOTTOM); + glbAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); + + // ** LUB tests ** + + // LUB of UNUSED and others + + lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + lubAssert(formatUnusedAnno, formatGeneralAnno, formatGeneralAnno); + lubAssert(formatUnusedAnno, formatCharAnno, formatCharAnno); + lubAssert(formatUnusedAnno, formatIntAnno, formatIntAnno); + lubAssert(formatUnusedAnno, formatTimeAnno, formatTimeAnno); + lubAssert(formatUnusedAnno, formatFloatAnno, formatFloatAnno); + lubAssert(formatUnusedAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatUnusedAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatUnusedAnno, formatNullAnno, formatNullAnno); + + // LUB of GENERAL and others + + lubAssert(formatGeneralAnno, formatUnusedAnno, formatGeneralAnno); + lubAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); + lubAssert(formatGeneralAnno, formatCharAnno, formatCharAnno); + lubAssert(formatGeneralAnno, formatIntAnno, formatIntAnno); + lubAssert(formatGeneralAnno, formatTimeAnno, formatTimeAnno); + lubAssert(formatGeneralAnno, formatFloatAnno, formatFloatAnno); + lubAssert(formatGeneralAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatGeneralAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatGeneralAnno, formatNullAnno, formatNullAnno); + + // LUB of CHAR and others + + lubAssert(formatCharAnno, formatUnusedAnno, formatCharAnno); + lubAssert(formatCharAnno, formatGeneralAnno, formatCharAnno); + lubAssert(formatCharAnno, formatCharAnno, formatCharAnno); + lubAssert(formatCharAnno, formatIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatCharAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatCharAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatCharAnno, formatNullAnno, formatNullAnno); + + // LUB of INT and others + + lubAssert(formatIntAnno, formatUnusedAnno, formatIntAnno); + lubAssert(formatIntAnno, formatGeneralAnno, formatIntAnno); + lubAssert(formatIntAnno, formatCharAnno, formatCharAndIntAnno); + lubAssert(formatIntAnno, formatIntAnno, formatIntAnno); + lubAssert(formatIntAnno, formatTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAnno, formatNullAnno, formatNullAnno); + + // LUB of TIME and others + + lubAssert(formatTimeAnno, formatUnusedAnno, formatTimeAnno); + lubAssert(formatTimeAnno, formatGeneralAnno, formatTimeAnno); + lubAssert(formatTimeAnno, formatCharAnno, formatNullAnno); + lubAssert(formatTimeAnno, formatIntAnno, formatIntAndTimeAnno); + lubAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); + lubAssert(formatTimeAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatTimeAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatTimeAnno, formatNullAnno, formatNullAnno); + + // LUB of FLOAT and others + + lubAssert(formatFloatAnno, formatUnusedAnno, formatFloatAnno); + lubAssert(formatFloatAnno, formatGeneralAnno, formatFloatAnno); + lubAssert(formatFloatAnno, formatCharAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatIntAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); + lubAssert(formatFloatAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatFloatAnno, formatNullAnno, formatNullAnno); + + // LUB of CHAR_AND_INT and others + + lubAssert(formatCharAndIntAnno, formatUnusedAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatGeneralAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatCharAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatCharAndIntAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); + lubAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatCharAndIntAnno, formatNullAnno, formatNullAnno); + + // LUB of INT_AND_TIME and others + + lubAssert(formatIntAndTimeAnno, formatUnusedAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatGeneralAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatCharAnno, formatNullAnno); + lubAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); + lubAssert(formatIntAndTimeAnno, formatNullAnno, formatNullAnno); + + // LUB of NULL and others + + lubAssert(formatNullAnno, formatUnusedAnno, formatNullAnno); + lubAssert(formatNullAnno, formatGeneralAnno, formatNullAnno); + lubAssert(formatNullAnno, formatCharAnno, formatNullAnno); + lubAssert(formatNullAnno, formatIntAnno, formatNullAnno); + lubAssert(formatNullAnno, formatTimeAnno, formatNullAnno); + lubAssert(formatNullAnno, formatFloatAnno, formatNullAnno); + lubAssert(formatNullAnno, formatCharAndIntAnno, formatNullAnno); + lubAssert(formatNullAnno, formatIntAndTimeAnno, formatNullAnno); + lubAssert(formatNullAnno, formatNullAnno, formatNullAnno); + + // Now test with two ConversionCategory at a time: + + cc2[0] = ConversionCategory.CHAR_AND_INT; + cc2[1] = ConversionCategory.NULL; + AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = ConversionCategory.NULL; + cc2[1] = ConversionCategory.CHAR; + AnnotationMirror formatTwoConvCat5 = treeUtil.categoriesToFormatAnnotation(cc2); + + lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); + + // Test that the LUB of two ConversionCategory arrays of different sizes is an array of the + // largest size of the two: + + lubAssert(formatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); + lubAssert(formatTwoConvCat2, formatNullAnno, formatTwoConvCat5); + + // LUB of @UnknownFormat and others + + lubAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, FORMAT, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, formatUnusedAnno, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, INVALIDFORMAT, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); + lubAssert(UNKNOWNFORMAT, FORMATBOTTOM, UNKNOWNFORMAT); + + // LUB of @Format(null) and others + + lubAssert(FORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + // Computing the LUB of @Format(null) and @Format(null) should never occur in practice; + // skipping this case as it causes an expected crash. + // Computing the LUB of @Format(null) and @Format with a value should never occur in + // practice; skipping this case as it causes an expected crash. + lubAssert(FORMAT, INVALIDFORMAT, UNKNOWNFORMAT); + lubAssert(FORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); + lubAssert(FORMAT, FORMATBOTTOM, FORMAT); + + // LUB of @Format(UNUSED) and others + + lubAssert(formatUnusedAnno, UNKNOWNFORMAT, UNKNOWNFORMAT); + // Computing the LUB of @Format with a value and @Format(null) should never occur in + // practice; skipping this case as it causes an expected crash. + lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); + lubAssert(formatUnusedAnno, INVALIDFORMAT, UNKNOWNFORMAT); + lubAssert(formatUnusedAnno, invalidFormatWithMessage, UNKNOWNFORMAT); + lubAssert(formatUnusedAnno, FORMATBOTTOM, formatUnusedAnno); + + // LUB of @InvalidFormat(null) and others + + lubAssert(INVALIDFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(INVALIDFORMAT, FORMAT, UNKNOWNFORMAT); + lubAssert(INVALIDFORMAT, formatUnusedAnno, UNKNOWNFORMAT); + lubAssert(INVALIDFORMAT, FORMATBOTTOM, INVALIDFORMAT); + + // LUB of @InvalidFormat("Message") and others + + lubAssert(invalidFormatWithMessage, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(invalidFormatWithMessage, FORMAT, UNKNOWNFORMAT); + lubAssert(invalidFormatWithMessage, formatUnusedAnno, UNKNOWNFORMAT); + lubAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); + lubAssert( + invalidFormatWithMessage, invalidFormatWithMessage2, invalidFormatWithMessagesOred); + lubAssert(invalidFormatWithMessage, FORMATBOTTOM, invalidFormatWithMessage); + + // LUB of @FormatBottom and others + + lubAssert(FORMATBOTTOM, UNKNOWNFORMAT, UNKNOWNFORMAT); + lubAssert(FORMATBOTTOM, FORMAT, FORMAT); + lubAssert(FORMATBOTTOM, formatUnusedAnno, formatUnusedAnno); + lubAssert(FORMATBOTTOM, INVALIDFORMAT, INVALIDFORMAT); + lubAssert(FORMATBOTTOM, invalidFormatWithMessage, invalidFormatWithMessage); + lubAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); } - } - - @SuppressWarnings("checkstyle:localvariablename") - @Override - public void initChecker() { - super.initChecker(); - FormatterTreeUtil treeUtil = new FormatterTreeUtil(this); - - Elements elements = getElementUtils(); - AnnotationMirror UNKNOWNFORMAT = AnnotationBuilder.fromClass(elements, UnknownFormat.class); - AnnotationMirror FORMAT = - AnnotationBuilder.fromClass( - elements, - Format.class, - AnnotationBuilder.elementNamesValues("value", new ConversionCategory[0])); - AnnotationMirror INVALIDFORMAT = - AnnotationBuilder.fromClass( - elements, InvalidFormat.class, AnnotationBuilder.elementNamesValues("value", "dummy")); - AnnotationMirror FORMATBOTTOM = AnnotationBuilder.fromClass(elements, FormatBottom.class); - - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "Message"); - AnnotationMirror invalidFormatWithMessage = builder.build(); - - builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "Message2"); - AnnotationMirror invalidFormatWithMessage2 = builder.build(); - - builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "(\"Message\" or \"Message2\")"); - AnnotationMirror invalidFormatWithMessagesOred = builder.build(); - - builder = new AnnotationBuilder(processingEnv, InvalidFormat.class); - builder.setValue("value", "(\"Message\" and \"Message2\")"); - AnnotationMirror invalidFormatWithMessagesAnded = builder.build(); - - ConversionCategory[] cc = new ConversionCategory[1]; - - cc[0] = ConversionCategory.UNUSED; - AnnotationMirror formatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.GENERAL; - AnnotationMirror formatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.CHAR; - AnnotationMirror formatCharAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.INT; - AnnotationMirror formatIntAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.TIME; - AnnotationMirror formatTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.FLOAT; - AnnotationMirror formatFloatAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.CHAR_AND_INT; - AnnotationMirror formatCharAndIntAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.INT_AND_TIME; - AnnotationMirror formatIntAndTimeAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = ConversionCategory.NULL; - AnnotationMirror formatNullAnno = treeUtil.categoriesToFormatAnnotation(cc); - - // ** GLB tests ** - - glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); - - // GLB of UNUSED and others - - glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatGeneralAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatCharAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatIntAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatTimeAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatFloatAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatCharAndIntAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatIntAndTimeAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, formatNullAnno, formatUnusedAnno); - - // GLB of GENERAL and others - - glbAssert(formatGeneralAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatCharAndIntAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatIntAndTimeAnno, formatGeneralAnno); - glbAssert(formatGeneralAnno, formatNullAnno, formatGeneralAnno); - - // GLB of CHAR and others - - glbAssert(formatCharAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatCharAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatCharAnno, formatCharAnno); - glbAssert(formatCharAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatCharAndIntAnno, formatCharAnno); - glbAssert(formatCharAnno, formatIntAndTimeAnno, formatGeneralAnno); - glbAssert(formatCharAnno, formatNullAnno, formatCharAnno); - - // GLB of INT and others - - glbAssert(formatIntAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatIntAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatIntAnno, formatIntAnno); - glbAssert(formatIntAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatIntAnno, formatCharAndIntAnno, formatIntAnno); - glbAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAnno); - glbAssert(formatIntAnno, formatNullAnno, formatIntAnno); - - // GLB of TIME and others - - glbAssert(formatTimeAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatTimeAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); - glbAssert(formatTimeAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatCharAndIntAnno, formatGeneralAnno); - glbAssert(formatTimeAnno, formatIntAndTimeAnno, formatTimeAnno); - glbAssert(formatTimeAnno, formatNullAnno, formatTimeAnno); - - // GLB of FLOAT and others - - glbAssert(formatFloatAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatFloatAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatIntAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); - glbAssert(formatFloatAnno, formatCharAndIntAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatIntAndTimeAnno, formatGeneralAnno); - glbAssert(formatFloatAnno, formatNullAnno, formatFloatAnno); - - // GLB of CHAR_AND_INT and others - - glbAssert(formatCharAndIntAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatCharAndIntAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatCharAndIntAnno, formatCharAnno, formatCharAnno); - glbAssert(formatCharAndIntAnno, formatIntAnno, formatIntAnno); - glbAssert(formatCharAndIntAnno, formatTimeAnno, formatGeneralAnno); - glbAssert(formatCharAndIntAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); - glbAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatIntAnno); - glbAssert(formatCharAndIntAnno, formatNullAnno, formatCharAndIntAnno); - - // GLB of INT_AND_TIME and others - - glbAssert(formatIntAndTimeAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatIntAndTimeAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatIntAndTimeAnno, formatCharAnno, formatGeneralAnno); - glbAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAnno); - glbAssert(formatIntAndTimeAnno, formatTimeAnno, formatTimeAnno); - glbAssert(formatIntAndTimeAnno, formatFloatAnno, formatGeneralAnno); - glbAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatIntAnno); - glbAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - glbAssert(formatIntAndTimeAnno, formatNullAnno, formatIntAndTimeAnno); - - // GLB of NULL and others - - glbAssert(formatNullAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatNullAnno, formatGeneralAnno, formatGeneralAnno); - glbAssert(formatNullAnno, formatCharAnno, formatCharAnno); - glbAssert(formatNullAnno, formatIntAnno, formatIntAnno); - glbAssert(formatNullAnno, formatTimeAnno, formatTimeAnno); - glbAssert(formatNullAnno, formatFloatAnno, formatFloatAnno); - glbAssert(formatNullAnno, formatCharAndIntAnno, formatCharAndIntAnno); - glbAssert(formatNullAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - glbAssert(formatNullAnno, formatNullAnno, formatNullAnno); - - // Now test with two ConversionCategory at a time: - - ConversionCategory[] cc2 = new ConversionCategory[2]; - - cc2[0] = ConversionCategory.CHAR_AND_INT; - cc2[1] = ConversionCategory.FLOAT; - AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = ConversionCategory.INT; - cc2[1] = ConversionCategory.CHAR; - AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = ConversionCategory.INT; - cc2[1] = ConversionCategory.GENERAL; - AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); - - glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); - - // Test that the GLB of two ConversionCategory arrays of different sizes is an array of the - // smallest size of the two: - - glbAssert(formatGeneralAnno, formatTwoConvCat1, formatGeneralAnno); - glbAssert(formatTwoConvCat2, formatNullAnno, formatIntAnno); - - // GLB of @UnknownFormat and others - - glbAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - glbAssert(UNKNOWNFORMAT, FORMAT, FORMAT); - glbAssert(UNKNOWNFORMAT, formatUnusedAnno, formatUnusedAnno); - glbAssert(UNKNOWNFORMAT, INVALIDFORMAT, INVALIDFORMAT); - glbAssert(UNKNOWNFORMAT, invalidFormatWithMessage, invalidFormatWithMessage); - glbAssert(UNKNOWNFORMAT, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @Format(null) and others - - glbAssert(FORMAT, UNKNOWNFORMAT, FORMAT); - // Computing the GLB of @Format(null) and @Format(null) should never occur in practice; - // skipping this case as it causes an expected crash. - // Computing the GLB of @Format(null) and @Format with a value should never occur in - // practice; skipping this case as it causes an expected crash. - glbAssert(FORMAT, INVALIDFORMAT, FORMATBOTTOM); - glbAssert(FORMAT, invalidFormatWithMessage, FORMATBOTTOM); - glbAssert(FORMAT, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @Format(UNUSED) and others - - glbAssert(formatUnusedAnno, UNKNOWNFORMAT, formatUnusedAnno); - // Computing the GLB of @Format with a value and @Format(null) should never occur in - // practice; skipping this case as it causes an expected crash. - glbAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - glbAssert(formatUnusedAnno, INVALIDFORMAT, FORMATBOTTOM); - glbAssert(formatUnusedAnno, invalidFormatWithMessage, FORMATBOTTOM); - glbAssert(formatUnusedAnno, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @InvalidFormat(null) and others - - glbAssert(INVALIDFORMAT, UNKNOWNFORMAT, INVALIDFORMAT); - glbAssert(INVALIDFORMAT, FORMAT, FORMATBOTTOM); - glbAssert(INVALIDFORMAT, formatUnusedAnno, FORMATBOTTOM); - glbAssert(INVALIDFORMAT, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @InvalidFormat("Message") and others - - glbAssert(invalidFormatWithMessage, UNKNOWNFORMAT, invalidFormatWithMessage); - glbAssert(invalidFormatWithMessage, FORMAT, FORMATBOTTOM); - glbAssert(invalidFormatWithMessage, formatUnusedAnno, FORMATBOTTOM); - glbAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); - glbAssert(invalidFormatWithMessage, invalidFormatWithMessage2, invalidFormatWithMessagesAnded); - glbAssert(invalidFormatWithMessage, FORMATBOTTOM, FORMATBOTTOM); - - // GLB of @FormatBottom and others - - glbAssert(FORMATBOTTOM, UNKNOWNFORMAT, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, FORMAT, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, formatUnusedAnno, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, INVALIDFORMAT, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, invalidFormatWithMessage, FORMATBOTTOM); - glbAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); - - // ** LUB tests ** - - // LUB of UNUSED and others - - lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - lubAssert(formatUnusedAnno, formatGeneralAnno, formatGeneralAnno); - lubAssert(formatUnusedAnno, formatCharAnno, formatCharAnno); - lubAssert(formatUnusedAnno, formatIntAnno, formatIntAnno); - lubAssert(formatUnusedAnno, formatTimeAnno, formatTimeAnno); - lubAssert(formatUnusedAnno, formatFloatAnno, formatFloatAnno); - lubAssert(formatUnusedAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatUnusedAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatUnusedAnno, formatNullAnno, formatNullAnno); - - // LUB of GENERAL and others - - lubAssert(formatGeneralAnno, formatUnusedAnno, formatGeneralAnno); - lubAssert(formatGeneralAnno, formatGeneralAnno, formatGeneralAnno); - lubAssert(formatGeneralAnno, formatCharAnno, formatCharAnno); - lubAssert(formatGeneralAnno, formatIntAnno, formatIntAnno); - lubAssert(formatGeneralAnno, formatTimeAnno, formatTimeAnno); - lubAssert(formatGeneralAnno, formatFloatAnno, formatFloatAnno); - lubAssert(formatGeneralAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatGeneralAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatGeneralAnno, formatNullAnno, formatNullAnno); - - // LUB of CHAR and others - - lubAssert(formatCharAnno, formatUnusedAnno, formatCharAnno); - lubAssert(formatCharAnno, formatGeneralAnno, formatCharAnno); - lubAssert(formatCharAnno, formatCharAnno, formatCharAnno); - lubAssert(formatCharAnno, formatIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatCharAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatCharAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatCharAnno, formatNullAnno, formatNullAnno); - - // LUB of INT and others - - lubAssert(formatIntAnno, formatUnusedAnno, formatIntAnno); - lubAssert(formatIntAnno, formatGeneralAnno, formatIntAnno); - lubAssert(formatIntAnno, formatCharAnno, formatCharAndIntAnno); - lubAssert(formatIntAnno, formatIntAnno, formatIntAnno); - lubAssert(formatIntAnno, formatTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatIntAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAnno, formatNullAnno, formatNullAnno); - - // LUB of TIME and others - - lubAssert(formatTimeAnno, formatUnusedAnno, formatTimeAnno); - lubAssert(formatTimeAnno, formatGeneralAnno, formatTimeAnno); - lubAssert(formatTimeAnno, formatCharAnno, formatNullAnno); - lubAssert(formatTimeAnno, formatIntAnno, formatIntAndTimeAnno); - lubAssert(formatTimeAnno, formatTimeAnno, formatTimeAnno); - lubAssert(formatTimeAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatTimeAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatTimeAnno, formatNullAnno, formatNullAnno); - - // LUB of FLOAT and others - - lubAssert(formatFloatAnno, formatUnusedAnno, formatFloatAnno); - lubAssert(formatFloatAnno, formatGeneralAnno, formatFloatAnno); - lubAssert(formatFloatAnno, formatCharAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatIntAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatFloatAnno, formatFloatAnno); - lubAssert(formatFloatAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatFloatAnno, formatNullAnno, formatNullAnno); - - // LUB of CHAR_AND_INT and others - - lubAssert(formatCharAndIntAnno, formatUnusedAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatGeneralAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatCharAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatCharAndIntAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatCharAndIntAnno, formatCharAndIntAnno, formatCharAndIntAnno); - lubAssert(formatCharAndIntAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatCharAndIntAnno, formatNullAnno, formatNullAnno); - - // LUB of INT_AND_TIME and others - - lubAssert(formatIntAndTimeAnno, formatUnusedAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatGeneralAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatCharAnno, formatNullAnno); - lubAssert(formatIntAndTimeAnno, formatIntAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatIntAndTimeAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatIntAndTimeAnno, formatIntAndTimeAnno, formatIntAndTimeAnno); - lubAssert(formatIntAndTimeAnno, formatNullAnno, formatNullAnno); - - // LUB of NULL and others - - lubAssert(formatNullAnno, formatUnusedAnno, formatNullAnno); - lubAssert(formatNullAnno, formatGeneralAnno, formatNullAnno); - lubAssert(formatNullAnno, formatCharAnno, formatNullAnno); - lubAssert(formatNullAnno, formatIntAnno, formatNullAnno); - lubAssert(formatNullAnno, formatTimeAnno, formatNullAnno); - lubAssert(formatNullAnno, formatFloatAnno, formatNullAnno); - lubAssert(formatNullAnno, formatCharAndIntAnno, formatNullAnno); - lubAssert(formatNullAnno, formatIntAndTimeAnno, formatNullAnno); - lubAssert(formatNullAnno, formatNullAnno, formatNullAnno); - - // Now test with two ConversionCategory at a time: - - cc2[0] = ConversionCategory.CHAR_AND_INT; - cc2[1] = ConversionCategory.NULL; - AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = ConversionCategory.NULL; - cc2[1] = ConversionCategory.CHAR; - AnnotationMirror formatTwoConvCat5 = treeUtil.categoriesToFormatAnnotation(cc2); - - lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); - - // Test that the LUB of two ConversionCategory arrays of different sizes is an array of the - // largest size of the two: - - lubAssert(formatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); - lubAssert(formatTwoConvCat2, formatNullAnno, formatTwoConvCat5); - - // LUB of @UnknownFormat and others - - lubAssert(UNKNOWNFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, FORMAT, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, formatUnusedAnno, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, INVALIDFORMAT, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); - lubAssert(UNKNOWNFORMAT, FORMATBOTTOM, UNKNOWNFORMAT); - - // LUB of @Format(null) and others - - lubAssert(FORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - // Computing the LUB of @Format(null) and @Format(null) should never occur in practice; - // skipping this case as it causes an expected crash. - // Computing the LUB of @Format(null) and @Format with a value should never occur in - // practice; skipping this case as it causes an expected crash. - lubAssert(FORMAT, INVALIDFORMAT, UNKNOWNFORMAT); - lubAssert(FORMAT, invalidFormatWithMessage, UNKNOWNFORMAT); - lubAssert(FORMAT, FORMATBOTTOM, FORMAT); - - // LUB of @Format(UNUSED) and others - - lubAssert(formatUnusedAnno, UNKNOWNFORMAT, UNKNOWNFORMAT); - // Computing the LUB of @Format with a value and @Format(null) should never occur in - // practice; skipping this case as it causes an expected crash. - lubAssert(formatUnusedAnno, formatUnusedAnno, formatUnusedAnno); - lubAssert(formatUnusedAnno, INVALIDFORMAT, UNKNOWNFORMAT); - lubAssert(formatUnusedAnno, invalidFormatWithMessage, UNKNOWNFORMAT); - lubAssert(formatUnusedAnno, FORMATBOTTOM, formatUnusedAnno); - - // LUB of @InvalidFormat(null) and others - - lubAssert(INVALIDFORMAT, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(INVALIDFORMAT, FORMAT, UNKNOWNFORMAT); - lubAssert(INVALIDFORMAT, formatUnusedAnno, UNKNOWNFORMAT); - lubAssert(INVALIDFORMAT, FORMATBOTTOM, INVALIDFORMAT); - - // LUB of @InvalidFormat("Message") and others - - lubAssert(invalidFormatWithMessage, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(invalidFormatWithMessage, FORMAT, UNKNOWNFORMAT); - lubAssert(invalidFormatWithMessage, formatUnusedAnno, UNKNOWNFORMAT); - lubAssert(invalidFormatWithMessage, invalidFormatWithMessage, invalidFormatWithMessage); - lubAssert(invalidFormatWithMessage, invalidFormatWithMessage2, invalidFormatWithMessagesOred); - lubAssert(invalidFormatWithMessage, FORMATBOTTOM, invalidFormatWithMessage); - - // LUB of @FormatBottom and others - - lubAssert(FORMATBOTTOM, UNKNOWNFORMAT, UNKNOWNFORMAT); - lubAssert(FORMATBOTTOM, FORMAT, FORMAT); - lubAssert(FORMATBOTTOM, formatUnusedAnno, formatUnusedAnno); - lubAssert(FORMATBOTTOM, INVALIDFORMAT, INVALIDFORMAT); - lubAssert(FORMATBOTTOM, invalidFormatWithMessage, invalidFormatWithMessage); - lubAssert(FORMATBOTTOM, FORMATBOTTOM, FORMATBOTTOM); - } } diff --git a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java index a444889b802..851e1ad3830 100644 --- a/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java +++ b/checker/src/test/java/org/checkerframework/checker/testchecker/lubglb/I18nFormatterLubGlbChecker.java @@ -4,12 +4,6 @@ // https://github.com/typetools/checker-framework/issues/723 // https://github.com/typetools/checker-framework/issues/756 -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.i18nformatter.I18nFormatterAnnotatedTypeFactory; import org.checkerframework.checker.i18nformatter.I18nFormatterChecker; import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil; @@ -26,6 +20,14 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** * This class tests the implementation of GLB computation in the I18n Format String Checker (see * issue 723), but it does not test for the crash that occurs if I18nFormatterAnnotatedTypeFactory @@ -33,426 +35,439 @@ * tests the implementation of LUB computation in the I18n Format String Checker. */ public class I18nFormatterLubGlbChecker extends I18nFormatterChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new I18nFormatterVisitor(this) { - @Override - protected I18nFormatterAnnotatedTypeFactory createTypeFactory() { - return new I18nFormatterLubGlbAnnotatedTypeFactory(checker); - } - }; - } - - /** I18nFormatterLubGlbAnnotatedTypeFactory. */ - private static class I18nFormatterLubGlbAnnotatedTypeFactory - extends I18nFormatterAnnotatedTypeFactory { + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new I18nFormatterVisitor(this) { + @Override + protected I18nFormatterAnnotatedTypeFactory createTypeFactory() { + return new I18nFormatterLubGlbAnnotatedTypeFactory(checker); + } + }; + } - /** - * Constructor. - * - * @param checker checker - */ - public I18nFormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); + /** I18nFormatterLubGlbAnnotatedTypeFactory. */ + private static class I18nFormatterLubGlbAnnotatedTypeFactory + extends I18nFormatterAnnotatedTypeFactory { + + /** + * Constructor. + * + * @param checker checker + */ + public I18nFormatterLubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + I18nUnknownFormat.class, + I18nFormatBottom.class, + I18nFormat.class, + I18nInvalidFormat.class, + I18nFormatFor.class)); + } } + @SuppressWarnings("checkstyle:localvariablename") @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - I18nUnknownFormat.class, - I18nFormatBottom.class, - I18nFormat.class, - I18nInvalidFormat.class, - I18nFormatFor.class)); - } - } - - @SuppressWarnings("checkstyle:localvariablename") - @Override - public void initChecker() { - super.initChecker(); - I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(this); - - Elements elements = getElementUtils(); - AnnotationMirror I18NUNKNOWNFORMAT = - AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); - AnnotationMirror I18NFORMAT = - AnnotationBuilder.fromClass( - elements, - I18nFormat.class, - AnnotationBuilder.elementNamesValues("value", new I18nConversionCategory[0])); - AnnotationMirror I18NINVALIDFORMAT = - AnnotationBuilder.fromClass( - elements, - I18nInvalidFormat.class, - AnnotationBuilder.elementNamesValues("value", "dummy")); - AnnotationMirror I18NFORMATFOR = - AnnotationBuilder.fromClass( - elements, I18nFormatFor.class, AnnotationBuilder.elementNamesValues("value", "dummy")); - AnnotationMirror I18NFORMATBOTTOM = - AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); - - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "Message"); - AnnotationMirror i18nInvalidFormatWithMessage = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "Message2"); - AnnotationMirror i18nInvalidFormatWithMessage2 = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "(\"Message\" or \"Message2\")"); - AnnotationMirror i18nInvalidFormatWithMessagesOred = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); - builder.setValue("value", "(\"Message\" and \"Message2\")"); - AnnotationMirror i18nInvalidFormatWithMessagesAnded = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); - builder.setValue("value", "#1"); - AnnotationMirror i18nFormatForWithValue1 = builder.build(); - - builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); - builder.setValue("value", "#2"); - AnnotationMirror i18nFormatForWithValue2 = builder.build(); - - I18nConversionCategory[] cc = new I18nConversionCategory[1]; - - cc[0] = I18nConversionCategory.UNUSED; - AnnotationMirror i18nFormatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = I18nConversionCategory.GENERAL; - AnnotationMirror i18nFormatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = I18nConversionCategory.DATE; - AnnotationMirror i18nFormatDateAnno = treeUtil.categoriesToFormatAnnotation(cc); - cc[0] = I18nConversionCategory.NUMBER; - AnnotationMirror i18nFormatNumberAnno = treeUtil.categoriesToFormatAnnotation(cc); - - // ** GLB tests ** - - // GLB of UNUSED and others - - glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatUnusedAnno); - - // GLB of GENERAL and others - - glbAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatGeneralAnno); - - // GLB of DATE and others - - glbAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); - glbAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatDateAnno); - - // GLB of NUMBER and others - - glbAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - glbAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatDateAnno); - glbAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // Now test with two I18nConversionCategory at a time: - - I18nConversionCategory[] cc2 = new I18nConversionCategory[2]; - - cc2[0] = I18nConversionCategory.DATE; - cc2[1] = I18nConversionCategory.DATE; - AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = I18nConversionCategory.UNUSED; - cc2[1] = I18nConversionCategory.NUMBER; - AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); - cc2[0] = I18nConversionCategory.UNUSED; - cc2[1] = I18nConversionCategory.DATE; - AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); - - glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); - - // Test that the GLB of two I18nConversionCategory arrays of different sizes is an array of - // the smallest size of the two: - - glbAssert(i18nFormatGeneralAnno, formatTwoConvCat1, i18nFormatGeneralAnno); - glbAssert(formatTwoConvCat2, i18nFormatDateAnno, i18nFormatUnusedAnno); - - // GLB of two distinct @I18nFormatFor(...) annotations is @I18nFormatBottom - - glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NFORMATBOTTOM); - - // GLB of @I18nUnknownFormat and others - - glbAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - glbAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NFORMAT); - glbAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NINVALIDFORMAT); - glbAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); - glbAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NFORMATFOR); - glbAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, i18nFormatForWithValue1); - glbAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormat(null) and others - - glbAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NFORMAT); - // Computing the GLB of @I18nFormat(null) and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - // Computing the GLB of @I18nFormat(null) and @I18nFormat with a value should never occur in - // practice. Skipping this case as it causes an expected crash. - glbAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormat(UNUSED) and others - - glbAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, i18nFormatUnusedAnno); - // Computing the GLB of @I18nFormat with a value and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - glbAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nInvalidFormat(null) and others - - glbAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NINVALIDFORMAT); - glbAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + public void initChecker() { + super.initChecker(); + I18nFormatterTreeUtil treeUtil = new I18nFormatterTreeUtil(this); + + Elements elements = getElementUtils(); + AnnotationMirror I18NUNKNOWNFORMAT = + AnnotationBuilder.fromClass(elements, I18nUnknownFormat.class); + AnnotationMirror I18NFORMAT = + AnnotationBuilder.fromClass( + elements, + I18nFormat.class, + AnnotationBuilder.elementNamesValues( + "value", new I18nConversionCategory[0])); + AnnotationMirror I18NINVALIDFORMAT = + AnnotationBuilder.fromClass( + elements, + I18nInvalidFormat.class, + AnnotationBuilder.elementNamesValues("value", "dummy")); + AnnotationMirror I18NFORMATFOR = + AnnotationBuilder.fromClass( + elements, + I18nFormatFor.class, + AnnotationBuilder.elementNamesValues("value", "dummy")); + AnnotationMirror I18NFORMATBOTTOM = + AnnotationBuilder.fromClass(elements, I18nFormatBottom.class); + + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "Message"); + AnnotationMirror i18nInvalidFormatWithMessage = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "Message2"); + AnnotationMirror i18nInvalidFormatWithMessage2 = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "(\"Message\" or \"Message2\")"); + AnnotationMirror i18nInvalidFormatWithMessagesOred = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class); + builder.setValue("value", "(\"Message\" and \"Message2\")"); + AnnotationMirror i18nInvalidFormatWithMessagesAnded = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); + builder.setValue("value", "#1"); + AnnotationMirror i18nFormatForWithValue1 = builder.build(); + + builder = new AnnotationBuilder(processingEnv, I18nFormatFor.class); + builder.setValue("value", "#2"); + AnnotationMirror i18nFormatForWithValue2 = builder.build(); + + I18nConversionCategory[] cc = new I18nConversionCategory[1]; + + cc[0] = I18nConversionCategory.UNUSED; + AnnotationMirror i18nFormatUnusedAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = I18nConversionCategory.GENERAL; + AnnotationMirror i18nFormatGeneralAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = I18nConversionCategory.DATE; + AnnotationMirror i18nFormatDateAnno = treeUtil.categoriesToFormatAnnotation(cc); + cc[0] = I18nConversionCategory.NUMBER; + AnnotationMirror i18nFormatNumberAnno = treeUtil.categoriesToFormatAnnotation(cc); + + // ** GLB tests ** + + // GLB of UNUSED and others + + glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatUnusedAnno); + + // GLB of GENERAL and others + + glbAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatGeneralAnno); + + // GLB of DATE and others + + glbAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); + glbAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatDateAnno); + + // GLB of NUMBER and others + + glbAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + glbAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatDateAnno); + glbAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // Now test with two I18nConversionCategory at a time: + + I18nConversionCategory[] cc2 = new I18nConversionCategory[2]; + + cc2[0] = I18nConversionCategory.DATE; + cc2[1] = I18nConversionCategory.DATE; + AnnotationMirror formatTwoConvCat1 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = I18nConversionCategory.UNUSED; + cc2[1] = I18nConversionCategory.NUMBER; + AnnotationMirror formatTwoConvCat2 = treeUtil.categoriesToFormatAnnotation(cc2); + cc2[0] = I18nConversionCategory.UNUSED; + cc2[1] = I18nConversionCategory.DATE; + AnnotationMirror formatTwoConvCat3 = treeUtil.categoriesToFormatAnnotation(cc2); + + glbAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat3); + + // Test that the GLB of two I18nConversionCategory arrays of different sizes is an array of + // the smallest size of the two: + + glbAssert(i18nFormatGeneralAnno, formatTwoConvCat1, i18nFormatGeneralAnno); + glbAssert(formatTwoConvCat2, i18nFormatDateAnno, i18nFormatUnusedAnno); + + // GLB of two distinct @I18nFormatFor(...) annotations is @I18nFormatBottom + + glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NFORMATBOTTOM); + + // GLB of @I18nUnknownFormat and others + + glbAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + glbAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NFORMAT); + glbAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NINVALIDFORMAT); + glbAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); + glbAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NFORMATFOR); + glbAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, i18nFormatForWithValue1); + glbAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormat(null) and others + + glbAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NFORMAT); + // Computing the GLB of @I18nFormat(null) and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + // Computing the GLB of @I18nFormat(null) and @I18nFormat with a value should never occur in + // practice. Skipping this case as it causes an expected crash. + glbAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormat(UNUSED) and others + + glbAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, i18nFormatUnusedAnno); + // Computing the GLB of @I18nFormat with a value and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + glbAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + glbAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nInvalidFormat(null) and others + + glbAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NINVALIDFORMAT); + glbAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nInvalidFormat("Message") and others - // GLB of @I18nInvalidFormat("Message") and others + glbAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage); + + glbAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert( + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage); + glbAssert( + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage2, + i18nInvalidFormatWithMessagesAnded); + glbAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormatFor(null) and others + + glbAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NFORMATFOR); + glbAssert(I18NFORMATFOR, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); + glbAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormatFor("#1") and others + + glbAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, i18nFormatForWithValue1); + glbAssert(i18nFormatForWithValue1, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); + glbAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // GLB of @I18nFormatBottom and others + + glbAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, I18NFORMATBOTTOM); + glbAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + + // ** LUB tests ** + + // LUB of UNUSED and others + + lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + lubAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + lubAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatDateAnno); + lubAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // LUB of GENERAL and others + + lubAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatGeneralAnno); + lubAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); + lubAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatDateAnno); + lubAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // LUB of DATE and others + + lubAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatDateAnno); + lubAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatDateAnno); + lubAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); + lubAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // LUB of NUMBER and others + + lubAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatNumberAnno); + lubAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatNumberAnno); + lubAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatNumberAnno); + lubAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); + + // Now test with two I18nConversionCategory at a time: + + cc2[0] = I18nConversionCategory.DATE; + cc2[1] = I18nConversionCategory.NUMBER; + AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); + + lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); + + // Test that the LUB of two I18nConversionCategory arrays of different sizes is an array of + // the largest size of the two: + + lubAssert(i18nFormatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); + lubAssert(formatTwoConvCat2, i18nFormatDateAnno, formatTwoConvCat4); + + // LUB of two distinct @I18nFormatFor(...) annotations is @I18nUnknownFormat + + lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NUNKNOWNFORMAT); + + // LUB of @I18nUnknownFormat and others + + lubAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NUNKNOWNFORMAT); + + // LUB of @I18nFormat(null) and others + + lubAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + // Computing the LUB of @I18nFormat(null) and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + // Computing the LUB of @I18nFormat(null) and @I18nFormat with a value should never occur in + // practice. Skipping this case as it causes an expected crash. + lubAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMAT); + + // LUB of @I18nFormat(UNUSED) and others + + lubAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + // Computing the LUB of @I18nFormat with a value and @I18nFormat(null) should never occur in + // practice. Skipping this case as it causes an expected crash. + lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + lubAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, i18nFormatUnusedAnno); + + // LUB of @I18nInvalidFormat(null) and others + + lubAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NINVALIDFORMAT); + + // LUB of @I18nInvalidFormat("Message") and others + + lubAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + + lubAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert( + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage); + lubAssert( + i18nInvalidFormatWithMessage, + i18nInvalidFormatWithMessage2, + i18nInvalidFormatWithMessagesOred); + lubAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, i18nInvalidFormatWithMessage); + + // LUB of @I18nFormatFor(null) and others + + lubAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); + lubAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATFOR); + + // LUB of @I18nFormatFor("#1") and others + + lubAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, I18NFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NUNKNOWNFORMAT); + lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); + lubAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, i18nFormatForWithValue1); + + // LUB of @I18nFormatBottom and others + + lubAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); + lubAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMAT); + lubAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, i18nFormatUnusedAnno); + lubAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NINVALIDFORMAT); + lubAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); + lubAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATFOR); + lubAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, i18nFormatForWithValue1); + lubAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); + } - glbAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage); - - glbAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert( - i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); - glbAssert( - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage2, - i18nInvalidFormatWithMessagesAnded); - glbAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormatFor(null) and others - - glbAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NFORMATFOR); - glbAssert(I18NFORMATFOR, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); - glbAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormatFor("#1") and others - - glbAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, i18nFormatForWithValue1); - glbAssert(i18nFormatForWithValue1, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); - glbAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // GLB of @I18nFormatBottom and others - - glbAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, I18NFORMATBOTTOM); - glbAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - - // ** LUB tests ** - - // LUB of UNUSED and others - - lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - lubAssert(i18nFormatUnusedAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - lubAssert(i18nFormatUnusedAnno, i18nFormatDateAnno, i18nFormatDateAnno); - lubAssert(i18nFormatUnusedAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // LUB of GENERAL and others - - lubAssert(i18nFormatGeneralAnno, i18nFormatUnusedAnno, i18nFormatGeneralAnno); - lubAssert(i18nFormatGeneralAnno, i18nFormatGeneralAnno, i18nFormatGeneralAnno); - lubAssert(i18nFormatGeneralAnno, i18nFormatDateAnno, i18nFormatDateAnno); - lubAssert(i18nFormatGeneralAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // LUB of DATE and others - - lubAssert(i18nFormatDateAnno, i18nFormatUnusedAnno, i18nFormatDateAnno); - lubAssert(i18nFormatDateAnno, i18nFormatGeneralAnno, i18nFormatDateAnno); - lubAssert(i18nFormatDateAnno, i18nFormatDateAnno, i18nFormatDateAnno); - lubAssert(i18nFormatDateAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // LUB of NUMBER and others - - lubAssert(i18nFormatNumberAnno, i18nFormatUnusedAnno, i18nFormatNumberAnno); - lubAssert(i18nFormatNumberAnno, i18nFormatGeneralAnno, i18nFormatNumberAnno); - lubAssert(i18nFormatNumberAnno, i18nFormatDateAnno, i18nFormatNumberAnno); - lubAssert(i18nFormatNumberAnno, i18nFormatNumberAnno, i18nFormatNumberAnno); - - // Now test with two I18nConversionCategory at a time: - - cc2[0] = I18nConversionCategory.DATE; - cc2[1] = I18nConversionCategory.NUMBER; - AnnotationMirror formatTwoConvCat4 = treeUtil.categoriesToFormatAnnotation(cc2); - - lubAssert(formatTwoConvCat1, formatTwoConvCat2, formatTwoConvCat4); - - // Test that the LUB of two I18nConversionCategory arrays of different sizes is an array of - // the largest size of the two: - - lubAssert(i18nFormatGeneralAnno, formatTwoConvCat1, formatTwoConvCat1); - lubAssert(formatTwoConvCat2, i18nFormatDateAnno, formatTwoConvCat4); - - // LUB of two distinct @I18nFormatFor(...) annotations is @I18nUnknownFormat - - lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue2, I18NUNKNOWNFORMAT); - - // LUB of @I18nUnknownFormat and others - - lubAssert(I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NUNKNOWNFORMAT, I18NFORMATBOTTOM, I18NUNKNOWNFORMAT); - - // LUB of @I18nFormat(null) and others - - lubAssert(I18NFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - // Computing the LUB of @I18nFormat(null) and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - // Computing the LUB of @I18nFormat(null) and @I18nFormat with a value should never occur in - // practice. Skipping this case as it causes an expected crash. - lubAssert(I18NFORMAT, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMAT, I18NFORMATBOTTOM, I18NFORMAT); - - // LUB of @I18nFormat(UNUSED) and others - - lubAssert(i18nFormatUnusedAnno, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - // Computing the LUB of @I18nFormat with a value and @I18nFormat(null) should never occur in - // practice. Skipping this case as it causes an expected crash. - lubAssert(i18nFormatUnusedAnno, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - lubAssert(i18nFormatUnusedAnno, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatUnusedAnno, I18NFORMATBOTTOM, i18nFormatUnusedAnno); - - // LUB of @I18nInvalidFormat(null) and others - - lubAssert(I18NINVALIDFORMAT, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NINVALIDFORMAT, I18NFORMATBOTTOM, I18NINVALIDFORMAT); - - // LUB of @I18nInvalidFormat("Message") and others - - lubAssert(i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - - lubAssert(i18nInvalidFormatWithMessage, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nInvalidFormatWithMessage, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert( - i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); - lubAssert( - i18nInvalidFormatWithMessage, - i18nInvalidFormatWithMessage2, - i18nInvalidFormatWithMessagesOred); - lubAssert(i18nInvalidFormatWithMessage, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(i18nInvalidFormatWithMessage, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(i18nInvalidFormatWithMessage, I18NFORMATBOTTOM, i18nInvalidFormatWithMessage); - - // LUB of @I18nFormatFor(null) and others - - lubAssert(I18NFORMATFOR, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NFORMATFOR, I18NFORMATFOR); - lubAssert(I18NFORMATFOR, i18nFormatForWithValue1, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATFOR, I18NFORMATBOTTOM, I18NFORMATFOR); - - // LUB of @I18nFormatFor("#1") and others - - lubAssert(i18nFormatForWithValue1, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, I18NFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, i18nFormatUnusedAnno, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, I18NINVALIDFORMAT, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, i18nInvalidFormatWithMessage, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, I18NFORMATFOR, I18NUNKNOWNFORMAT); - lubAssert(i18nFormatForWithValue1, i18nFormatForWithValue1, i18nFormatForWithValue1); - lubAssert(i18nFormatForWithValue1, I18NFORMATBOTTOM, i18nFormatForWithValue1); - - // LUB of @I18nFormatBottom and others - - lubAssert(I18NFORMATBOTTOM, I18NUNKNOWNFORMAT, I18NUNKNOWNFORMAT); - lubAssert(I18NFORMATBOTTOM, I18NFORMAT, I18NFORMAT); - lubAssert(I18NFORMATBOTTOM, i18nFormatUnusedAnno, i18nFormatUnusedAnno); - lubAssert(I18NFORMATBOTTOM, I18NINVALIDFORMAT, I18NINVALIDFORMAT); - lubAssert(I18NFORMATBOTTOM, i18nInvalidFormatWithMessage, i18nInvalidFormatWithMessage); - lubAssert(I18NFORMATBOTTOM, I18NFORMATFOR, I18NFORMATFOR); - lubAssert(I18NFORMATBOTTOM, i18nFormatForWithValue1, i18nFormatForWithValue1); - lubAssert(I18NFORMATBOTTOM, I18NFORMATBOTTOM, I18NFORMATBOTTOM); - } - - /** - * Throws an exception if glb(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void glbAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format("GLB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + /** + * Throws an exception if glb(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void glbAssert( + AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format( + "GLB of %s and %s should be %s, but is %s", + arg1, arg2, expected, result)); + } } - } - - /** - * Throws an exception if lub(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void lubAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format("LUB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + + /** + * Throws an exception if lub(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void lubAssert( + AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format( + "LUB of %s and %s should be %s, but is %s", + arg1, arg2, expected, result)); + } } - } } diff --git a/checker/src/testannotations/java/android/support/annotation/NonNull.java b/checker/src/testannotations/java/android/support/annotation/NonNull.java index 05a67f4fd0e..24e84cfa2fc 100644 --- a/checker/src/testannotations/java/android/support/annotation/NonNull.java +++ b/checker/src/testannotations/java/android/support/annotation/NonNull.java @@ -12,11 +12,11 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.ANNOTATION_TYPE, - ElementType.PACKAGE + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.ANNOTATION_TYPE, + ElementType.PACKAGE }) public @interface NonNull {} diff --git a/checker/src/testannotations/java/android/support/annotation/Nullable.java b/checker/src/testannotations/java/android/support/annotation/Nullable.java index 0bd40d3b505..b6472495a34 100644 --- a/checker/src/testannotations/java/android/support/annotation/Nullable.java +++ b/checker/src/testannotations/java/android/support/annotation/Nullable.java @@ -12,11 +12,11 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.ANNOTATION_TYPE, - ElementType.PACKAGE + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.ANNOTATION_TYPE, + ElementType.PACKAGE }) public @interface Nullable {} diff --git a/checker/src/testannotations/java/javax/annotation/Nonnull.java b/checker/src/testannotations/java/javax/annotation/Nonnull.java index 5d4b646cf26..a6f2e3bc127 100644 --- a/checker/src/testannotations/java/javax/annotation/Nonnull.java +++ b/checker/src/testannotations/java/javax/annotation/Nonnull.java @@ -3,10 +3,11 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; + import javax.annotation.meta.When; @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Nonnull { - When when() default When.ALWAYS; + When when() default When.ALWAYS; } diff --git a/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java b/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java index 0a4770b9ac4..a34b6f2e881 100644 --- a/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java +++ b/checker/src/testannotations/java/javax/annotation/concurrent/GuardedBy.java @@ -1,12 +1,13 @@ package javax.annotation.concurrent; +import org.checkerframework.checker.lock.qual.LockHeld; +import org.checkerframework.framework.qual.PreconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.lock.qual.LockHeld; -import org.checkerframework.framework.qual.PreconditionAnnotation; // The JCIP annotation can be used on a field (in which case it corresponds to the Lock Checker's // @GuardedBy annotation) or on a method (in which case it is a declaration annotation corresponding @@ -20,11 +21,11 @@ @Target({ElementType.METHOD, ElementType.FIELD}) @PreconditionAnnotation(qualifier = LockHeld.class) public @interface GuardedBy { - /** - * The Java expressions that need to be held. - * - * @see Syntax of Java - * expressions - */ - String[] value() default {}; + /** + * The Java expressions that need to be held. + * + * @see Syntax of + * Java expressions + */ + String[] value() default {}; } diff --git a/checker/src/testannotations/java/javax/annotation/meta/When.java b/checker/src/testannotations/java/javax/annotation/meta/When.java index a1082078711..0749f6d0d2b 100644 --- a/checker/src/testannotations/java/javax/annotation/meta/When.java +++ b/checker/src/testannotations/java/javax/annotation/meta/When.java @@ -5,12 +5,12 @@ * annotated element. */ public enum When { - /** S is a subset of T */ - ALWAYS, - /** nothing definitive is known about the relation between S and T */ - UNKNOWN, - /** S intersection T is non empty and S - T is nonempty. */ - MAYBE, - /** S intersection T is empty. */ - NEVER; + /** S is a subset of T */ + ALWAYS, + /** nothing definitive is known about the relation between S and T */ + UNKNOWN, + /** S intersection T is non empty and S - T is nonempty. */ + MAYBE, + /** S intersection T is empty. */ + NEVER; } diff --git a/checker/src/testannotations/java/lombok/NonNull.java b/checker/src/testannotations/java/lombok/NonNull.java index 347cfc4b038..fac1e99c1e9 100644 --- a/checker/src/testannotations/java/lombok/NonNull.java +++ b/checker/src/testannotations/java/lombok/NonNull.java @@ -12,10 +12,10 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.FIELD, - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.LOCAL_VARIABLE, - ElementType.TYPE_USE + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.LOCAL_VARIABLE, + ElementType.TYPE_USE }) public @interface NonNull {} diff --git a/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java b/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java index 24b4402052c..e88cc22622a 100644 --- a/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java +++ b/checker/src/testannotations/java/net/jcip/annotations/GuardedBy.java @@ -3,13 +3,14 @@ package net.jcip.annotations; +import org.checkerframework.checker.lock.qual.LockHeld; +import org.checkerframework.framework.qual.PreconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.lock.qual.LockHeld; -import org.checkerframework.framework.qual.PreconditionAnnotation; // The JCIP annotation can be used on a field (in which case it corresponds to the Lock Checker's // @GuardedBy annotation) or on a method (in which case it is a declaration annotation corresponding @@ -23,11 +24,11 @@ @Target({ElementType.FIELD, ElementType.METHOD}) @PreconditionAnnotation(qualifier = LockHeld.class) public @interface GuardedBy { - /** - * The Java expressions that need to be held. - * - * @see Syntax of Java - * expressions - */ - String[] value() default {}; + /** + * The Java expressions that need to be held. + * + * @see Syntax of + * Java expressions + */ + String[] value() default {}; } diff --git a/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java b/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java index 9b78ffcbced..d193e4c03d8 100644 --- a/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java +++ b/checker/src/testannotations/java/org/jetbrains/annotations/NotNull.java @@ -12,12 +12,12 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.FIELD, - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.LOCAL_VARIABLE, - ElementType.TYPE_USE + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.LOCAL_VARIABLE, + ElementType.TYPE_USE }) public @interface NotNull { - String value() default ""; + String value() default ""; } diff --git a/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java b/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java index 1c6fc456895..d189bf6ce7a 100644 --- a/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java +++ b/checker/src/testannotations/java/org/jetbrains/annotations/Nullable.java @@ -12,12 +12,12 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ - ElementType.FIELD, - ElementType.METHOD, - ElementType.PARAMETER, - ElementType.LOCAL_VARIABLE, - ElementType.TYPE_USE + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.LOCAL_VARIABLE, + ElementType.TYPE_USE }) public @interface Nullable { - String value() default ""; + String value() default ""; } diff --git a/checker/tests/aggregate/NullnessAndRegex.java b/checker/tests/aggregate/NullnessAndRegex.java index 10c8bbfd55a..43f77b96088 100644 --- a/checker/tests/aggregate/NullnessAndRegex.java +++ b/checker/tests/aggregate/NullnessAndRegex.java @@ -4,17 +4,17 @@ import org.checkerframework.checker.regex.qual.Regex; public class NullnessAndRegex { - // :: error: (assignment.type.incompatible) - @Regex String s1 = "De(mo"; - // :: error: (assignment.type.incompatible) - Object f = null; - // :: error: (assignment.type.incompatible) - @Regex String s2 = "De(mo"; + // :: error: (assignment.type.incompatible) + @Regex String s1 = "De(mo"; + // :: error: (assignment.type.incompatible) + Object f = null; + // :: error: (assignment.type.incompatible) + @Regex String s2 = "De(mo"; - void localized(@Localized String s) {} + void localized(@Localized String s) {} - void method() { - // :: error: (argument.type.incompatible) - localized("ldskjfldj"); // error - } + void method() { + // :: error: (argument.type.incompatible) + localized("ldskjfldj"); // error + } } diff --git a/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java index 0c0caae3568..e62a641d31a 100644 --- a/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java +++ b/checker/tests/ainfer-index/non-annotated/Dataset6Crash.java @@ -7,50 +7,50 @@ public class Dataset6Crash { - public static Iterator limit( - final Iterator base, final CountingPredicate filter) { - return new Iterator() { - - private T next; - - private boolean end; - - private int index = 0; - - public boolean hasNext() { - return true; - } - - public T next() { - fetch(); - T r = next; - next = null; - return r; - } - - private void fetch() { - if (next == null && !end) { - if (base.hasNext()) { - next = base.next(); - if (!filter.apply(index++, next)) { - next = null; - end = true; + public static Iterator limit( + final Iterator base, final CountingPredicate filter) { + return new Iterator() { + + private T next; + + private boolean end; + + private int index = 0; + + public boolean hasNext() { + return true; + } + + public T next() { + fetch(); + T r = next; + next = null; + return r; + } + + private void fetch() { + if (next == null && !end) { + if (base.hasNext()) { + next = base.next(); + if (!filter.apply(index++, next)) { + next = null; + end = true; + } + } else { + end = true; + } + } } - } else { - end = true; - } - } - } - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } - private static class CountingPredicate { - public boolean apply(int i, T next) { - return false; + private static class CountingPredicate { + public boolean apply(int i, T next) { + return false; + } } - } } diff --git a/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java b/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java index c8798e763a6..a1f7a86df85 100644 --- a/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java +++ b/checker/tests/ainfer-index/non-annotated/Dataset9Crash.java @@ -6,79 +6,79 @@ public class Dataset9Crash { - @SuppressWarnings("all") - private class NotificationList implements Collection { - @Override - public int size() { - return 0; + @SuppressWarnings("all") + private class NotificationList implements Collection { + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object o) { + return false; + } + + @Override + public Iterator iterator() { + return null; + } + + @Override + public Object[] toArray() { + return new Object[0]; + } + + @Override + public boolean add(Object o) { + return false; + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean addAll(Collection c) { + return false; + } + + @Override + public void clear() {} + + @Override + public boolean equals(Object o) { + return false; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean containsAll(Collection c) { + return false; + } + + @Override + public Object[] toArray(Object[] a) { + return new Object[0]; + } } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean contains(Object o) { - return false; - } - - @Override - public Iterator iterator() { - return null; - } - - @Override - public Object[] toArray() { - return new Object[0]; - } - - @Override - public boolean add(Object o) { - return false; - } - - @Override - public boolean remove(Object o) { - return false; - } - - @Override - public boolean addAll(Collection c) { - return false; - } - - @Override - public void clear() {} - - @Override - public boolean equals(Object o) { - return false; - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public boolean retainAll(Collection c) { - return false; - } - - @Override - public boolean removeAll(Collection c) { - return false; - } - - @Override - public boolean containsAll(Collection c) { - return false; - } - - @Override - public Object[] toArray(Object[] a) { - return new Object[0]; - } - } } diff --git a/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java b/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java index da454d2d73a..5df8af294a4 100644 --- a/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java +++ b/checker/tests/ainfer-index/non-annotated/DelocalizeAtCallsites.java @@ -6,53 +6,53 @@ public class DelocalizeAtCallsites { - void a1(int[][] a, @IndexFor("#1") int y) { - int[] z = a[y]; - } - - void a2(int y, int x) {} - - void testArrayAccess() { - int[][] arr = new int[5][5]; - int x1 = 3; - @SuppressWarnings("all") - @IndexFor(value = {"arr[x1]", "arr"}) int y1 = 2; - // test that out-of-scope indices are handled properly - a1(arr, y1); - // test that out-of-scope arrays are handled properly - a2(y1, x1); - } - - void a3(int x) {} - - void testArrayCreation() { - int x = 10; - @SuppressWarnings("all") - @IndexFor("new int[x]") int y = 0; - a3(y); - @SuppressWarnings("all") - @IndexFor("new int[x]{x, x, x, x, x, x, x, x, x, x}") int z = 0; - a3(z); - } - - void testBinary() { - int x1 = 5; - @LessThan("x1 + 1") int y = 4; - a3(y); - } - - void testUnary() { - int x1 = 5; - @LessThan("x1++") int y = 4; - a3(y); - } - - public int f; - - void testFieldAccess() { - DelocalizeAtCallsites d = new DelocalizeAtCallsites(); - d.f = 3; - @LessThan("d.f") int y = 2; - a3(y); - } + void a1(int[][] a, @IndexFor("#1") int y) { + int[] z = a[y]; + } + + void a2(int y, int x) {} + + void testArrayAccess() { + int[][] arr = new int[5][5]; + int x1 = 3; + @SuppressWarnings("all") + @IndexFor(value = {"arr[x1]", "arr"}) int y1 = 2; + // test that out-of-scope indices are handled properly + a1(arr, y1); + // test that out-of-scope arrays are handled properly + a2(y1, x1); + } + + void a3(int x) {} + + void testArrayCreation() { + int x = 10; + @SuppressWarnings("all") + @IndexFor("new int[x]") int y = 0; + a3(y); + @SuppressWarnings("all") + @IndexFor("new int[x]{x, x, x, x, x, x, x, x, x, x}") int z = 0; + a3(z); + } + + void testBinary() { + int x1 = 5; + @LessThan("x1 + 1") int y = 4; + a3(y); + } + + void testUnary() { + int x1 = 5; + @LessThan("x1++") int y = 4; + a3(y); + } + + public int f; + + void testFieldAccess() { + DelocalizeAtCallsites d = new DelocalizeAtCallsites(); + d.f = 3; + @LessThan("d.f") int y = 2; + a3(y); + } } diff --git a/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java b/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java index 80f8159f67b..2e7b84af542 100644 --- a/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java +++ b/checker/tests/ainfer-index/non-annotated/DependentTypesViewpointAdaptationTest.java @@ -6,66 +6,68 @@ public class DependentTypesViewpointAdaptationTest { - public static void run() { - String word1 = "\"Hamburg\""; - String word2 = "burg"; - // The existence of word3 here forces the inferred @SameLen - // annotation to include a local variable that isn't a parameter - // to compute(), to test that such local variables are viewpoint-adapted - // correctly. - String word3 = word1; - compute(word1, word2); - } + public static void run() { + String word1 = "\"Hamburg\""; + String word2 = "burg"; + // The existence of word3 here forces the inferred @SameLen + // annotation to include a local variable that isn't a parameter + // to compute(), to test that such local variables are viewpoint-adapted + // correctly. + String word3 = word1; + compute(word1, word2); + } - public static void compute(String word1, String otherWord) { - // content doesn't matter - } + public static void compute(String word1, String otherWord) { + // content doesn't matter + } - public static void receiverTest( - @SameLen("#2") DependentTypesViewpointAdaptationTest t1, - @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { - t1.compute2(t2); - } + public static void receiverTest( + @SameLen("#2") DependentTypesViewpointAdaptationTest t1, + @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { + t1.compute2(t2); + } - public void compute2( - DependentTypesViewpointAdaptationTest this, DependentTypesViewpointAdaptationTest other) { - // content doesn't matter - } + public void compute2( + DependentTypesViewpointAdaptationTest this, + DependentTypesViewpointAdaptationTest other) { + // content doesn't matter + } - public void thisTest(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { - compute3(this, t1); - } + public void thisTest(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { + compute3(this, t1); + } - public static void compute3( - DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { - // content doesn't matter - } + public static void compute3( + DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { + // content doesn't matter + } - public void thisTestNoUse(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { - compute4(t1, t1); - } + public void thisTestNoUse(@SameLen("this") DependentTypesViewpointAdaptationTest t1) { + compute4(t1, t1); + } - public static void compute4( - DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { - // content doesn't matter - } + public static void compute4( + DependentTypesViewpointAdaptationTest t1, DependentTypesViewpointAdaptationTest t2) { + // content doesn't matter + } - public static void testThisInference( - DependentTypesViewpointAdaptationTest t1, - @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { - t1.compute5(t2); - t1.compute6(t2); - } + public static void testThisInference( + DependentTypesViewpointAdaptationTest t1, + @SameLen("#1") DependentTypesViewpointAdaptationTest t2) { + t1.compute5(t2); + t1.compute6(t2); + } - public void compute5( - DependentTypesViewpointAdaptationTest this, DependentTypesViewpointAdaptationTest other) { - // :: warning: (assignment.type.incompatible) - @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; - } + public void compute5( + DependentTypesViewpointAdaptationTest this, + DependentTypesViewpointAdaptationTest other) { + // :: warning: (assignment.type.incompatible) + @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; + } - // Same as compute5, but without an explicit this parameter. - public void compute6(DependentTypesViewpointAdaptationTest other) { - // :: warning: (assignment.type.incompatible) - @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; - } + // Same as compute5, but without an explicit this parameter. + public void compute6(DependentTypesViewpointAdaptationTest other) { + // :: warning: (assignment.type.incompatible) + @SameLen("this") DependentTypesViewpointAdaptationTest myOther = other; + } } diff --git a/checker/tests/ainfer-index/non-annotated/InternCrash.java b/checker/tests/ainfer-index/non-annotated/InternCrash.java index 57d3e140f6f..f1daf6b5bb5 100644 --- a/checker/tests/ainfer-index/non-annotated/InternCrash.java +++ b/checker/tests/ainfer-index/non-annotated/InternCrash.java @@ -4,16 +4,16 @@ public final class InternCrash { - public static String[] intern(String[] a) { - return a; - } + public static String[] intern(String[] a) { + return a; + } - public static Object intern(Object a) { - if (a instanceof String[]) { - String[] asArray = (String[]) a; - return intern(asArray); - } else { - return null; + public static Object intern(Object a) { + if (a instanceof String[]) { + String[] asArray = (String[]) a; + return intern(asArray); + } else { + return null; + } } - } } diff --git a/checker/tests/ainfer-index/non-annotated/LongMathTest.java b/checker/tests/ainfer-index/non-annotated/LongMathTest.java index 7edffd7e6fe..ff5e2e9f058 100644 --- a/checker/tests/ainfer-index/non-annotated/LongMathTest.java +++ b/checker/tests/ainfer-index/non-annotated/LongMathTest.java @@ -1,5 +1,5 @@ public class LongMathTest { - public static long mod(long x, long y) { - return x % y; - } + public static long mod(long x, long y) { + return x % y; + } } diff --git a/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java b/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java index c2835f68b03..284b04c2fba 100644 --- a/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java +++ b/checker/tests/ainfer-index/non-annotated/RequireJavadocCrash.java @@ -4,17 +4,18 @@ public class RequireJavadocCrash { - public static void main(String[] args) { - RequireJavadocCrash rj = new RequireJavadocCrash(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); + public static void main(String[] args) { + RequireJavadocCrash rj = new RequireJavadocCrash(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - } + rj.setJavaFiles(remainingArgs); + } - private RequireJavadocCrash() {} + private RequireJavadocCrash() {} - private void setJavaFiles(String[] args) {} + private void setJavaFiles(String[] args) {} } diff --git a/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java b/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java index 8284319cfbc..2dfe2ba6a7d 100644 --- a/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java +++ b/checker/tests/ainfer-index/non-annotated/TypeVarAssignment.java @@ -1,7 +1,7 @@ public class TypeVarAssignment { - T t; + T t; - void foo(S s) { - t = s; - } + void foo(S s) { + t = s; + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/Bug.java b/checker/tests/ainfer-nullness/non-annotated/Bug.java index aec77c8a525..b90b483496c 100644 --- a/checker/tests/ainfer-nullness/non-annotated/Bug.java +++ b/checker/tests/ainfer-nullness/non-annotated/Bug.java @@ -2,66 +2,68 @@ import org.checkerframework.checker.nullness.qual.*; public class Bug { - /** Actions that MultiVersionControl can perform. */ - static enum Action { - /** Clone a repository. */ - CLONE, - /** Show the working tree status. */ - STATUS, - /** Pull changes from upstream. */ - PULL, - /** List the known repositories. */ - LIST - } + /** Actions that MultiVersionControl can perform. */ + static enum Action { + /** Clone a repository. */ + CLONE, + /** Show the working tree status. */ + STATUS, + /** Pull changes from upstream. */ + PULL, + /** List the known repositories. */ + LIST + } - private @MonotonicNonNull Action action; - public static String home = System.getProperty("user.home"); + private @MonotonicNonNull Action action; + public static String home = System.getProperty("user.home"); - void other() { - this.action = Action.LIST; - expandTilde(""); - } + void other() { + this.action = Action.LIST; + expandTilde(""); + } - /** - * Replace "~" by the expansion of "$HOME". - * - * @param path the input path, which might contain "~" - * @return path with "~" expanded - */ - private static String expandTilde(String path) { - return path.replaceFirst("^~", home); - } + /** + * Replace "~" by the expansion of "$HOME". + * + * @param path the input path, which might contain "~" + * @return path with "~" expanded + */ + private static String expandTilde(String path) { + return path.replaceFirst("^~", home); + } - public static void main(String[] args) { - Bug b = new Bug(); - Bug.expandTilde(""); - } + public static void main(String[] args) { + Bug b = new Bug(); + Bug.expandTilde(""); + } - Bug() { - parseArgs(new String[0]); - } + Bug() { + parseArgs(new String[0]); + } - @EnsuresNonNull("action") - public void parseArgs(@UnknownInitialization Bug this, String[] args) { - String actionString = args[0]; - if ("checkout".startsWith(actionString)) { - action = Action.CLONE; - } else if ("clone".startsWith(actionString)) { - action = Action.CLONE; - } else if ("list".startsWith(actionString)) { - action = Action.LIST; - } else if ("pull".startsWith(actionString)) { - action = Action.PULL; - } else if ("status".startsWith(actionString)) { - action = Action.STATUS; - } else if ("update".startsWith(actionString)) { - action = Action.PULL; - } else { - System.out.printf("Unrecognized action \"%s\"", actionString); - System.exit(1); + @EnsuresNonNull("action") + public void parseArgs(@UnknownInitialization Bug this, String[] args) { + String actionString = args[0]; + if ("checkout".startsWith(actionString)) { + action = Action.CLONE; + } else if ("clone".startsWith(actionString)) { + action = Action.CLONE; + } else if ("list".startsWith(actionString)) { + action = Action.LIST; + } else if ("pull".startsWith(actionString)) { + action = Action.PULL; + } else if ("status".startsWith(actionString)) { + action = Action.STATUS; + } else if ("update".startsWith(actionString)) { + action = Action.PULL; + } else { + System.out.printf("Unrecognized action \"%s\"", actionString); + System.exit(1); + } } - } - public static final @Nullable String VERSION = - Bug.class.getPackage() != null ? Bug.class.getPackage().getImplementationVersion() : null; + public static final @Nullable String VERSION = + Bug.class.getPackage() != null + ? Bug.class.getPackage().getImplementationVersion() + : null; } diff --git a/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java b/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java index 2249bf0df89..f0a9b6768a8 100644 --- a/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java +++ b/checker/tests/ainfer-nullness/non-annotated/MonotonicNonNullInferenceTest.java @@ -2,70 +2,70 @@ public class MonotonicNonNullInferenceTest { - // :: warning: (initialization.static.field.uninitialized) - static String staticString1; + // :: warning: (initialization.static.field.uninitialized) + static String staticString1; - // :: warning: (assignment.type.incompatible) - static String staticString2 = null; - - static String staticString3; - - String instanceString1; - - // :: warning: (assignment.type.incompatible) - String instanceString2 = null; - - String instanceString3; - - static { // :: warning: (assignment.type.incompatible) - staticString3 = null; - } + static String staticString2 = null; - // :: warning: (initialization.fields.uninitialized) - MonotonicNonNullInferenceTest() { - String instanceString3 = "hello"; - } + static String staticString3; - static void m1(String arg) { - staticString1 = arg; - staticString2 = arg; - staticString3 = arg; - } + String instanceString1; - void m2(String arg) { - instanceString1 = arg; - instanceString2 = arg; - instanceString3 = arg; - } + // :: warning: (assignment.type.incompatible) + String instanceString2 = null; - void hasSideEffect() {} + String instanceString3; - void testMonotonicNonNull() { - @NonNull String s; - if (staticString1 != null) { - hasSideEffect(); - s = staticString1; + static { + // :: warning: (assignment.type.incompatible) + staticString3 = null; } - if (staticString2 != null) { - hasSideEffect(); - s = staticString2; - } - if (staticString3 != null) { - hasSideEffect(); - s = staticString3; + + // :: warning: (initialization.fields.uninitialized) + MonotonicNonNullInferenceTest() { + String instanceString3 = "hello"; } - if (instanceString1 != null) { - hasSideEffect(); - s = instanceString1; + + static void m1(String arg) { + staticString1 = arg; + staticString2 = arg; + staticString3 = arg; } - if (instanceString2 != null) { - hasSideEffect(); - s = instanceString2; + + void m2(String arg) { + instanceString1 = arg; + instanceString2 = arg; + instanceString3 = arg; } - if (instanceString3 != null) { - hasSideEffect(); - s = instanceString3; + + void hasSideEffect() {} + + void testMonotonicNonNull() { + @NonNull String s; + if (staticString1 != null) { + hasSideEffect(); + s = staticString1; + } + if (staticString2 != null) { + hasSideEffect(); + s = staticString2; + } + if (staticString3 != null) { + hasSideEffect(); + s = staticString3; + } + if (instanceString1 != null) { + hasSideEffect(); + s = instanceString1; + } + if (instanceString2 != null) { + hasSideEffect(); + s = instanceString2; + } + if (instanceString3 != null) { + hasSideEffect(); + s = instanceString3; + } } - } } diff --git a/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java b/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java index 7d6d2a17a08..16e6d27802d 100644 --- a/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java +++ b/checker/tests/ainfer-nullness/non-annotated/NullTypeVarTest.java @@ -8,25 +8,25 @@ public class NullTypeVarTest { - // :: warning: assignment.type.incompatible - private String indentString = null; + // :: warning: assignment.type.incompatible + private String indentString = null; - private List indentStrings; + private List indentStrings; - private final String INDENT_STR_ONE_LEVEL = " "; + private final String INDENT_STR_ONE_LEVEL = " "; - public NullTypeVarTest() { - indentStrings = new ArrayList(); - indentStrings.add(""); - } + public NullTypeVarTest() { + indentStrings = new ArrayList(); + indentStrings.add(""); + } - private String getIndentString(int indentLevel) { - if (indentString == null) { - for (int i = indentStrings.size(); i <= indentLevel; i++) { - indentStrings.add(indentStrings.get(i - 1) + INDENT_STR_ONE_LEVEL); - } - indentString = indentStrings.get(indentLevel); + private String getIndentString(int indentLevel) { + if (indentString == null) { + for (int i = indentStrings.size(); i <= indentLevel; i++) { + indentStrings.add(indentStrings.get(i - 1) + INDENT_STR_ONE_LEVEL); + } + indentString = indentStrings.get(indentLevel); + } + return indentString; } - return indentString; - } } diff --git a/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java b/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java index 9cd984f2818..cf1bfb52eb7 100644 --- a/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java +++ b/checker/tests/ainfer-nullness/non-annotated/TwoCtorGenericAbstract.java @@ -3,14 +3,14 @@ import java.util.Set; public abstract class TwoCtorGenericAbstract implements Set { - protected T value; + protected T value; - protected TwoCtorGenericAbstract() { - // :: warning: (assignment.type.incompatible) - this.value = null; - } + protected TwoCtorGenericAbstract() { + // :: warning: (assignment.type.incompatible) + this.value = null; + } - protected TwoCtorGenericAbstract(T v) { - this.value = v; - } + protected TwoCtorGenericAbstract(T v) { + this.value = v; + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java b/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java index afeb3f4a149..133c4a1938d 100644 --- a/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java +++ b/checker/tests/ainfer-nullness/non-annotated/TypeVarPlumeUtil.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TypeVarPlumeUtil { - @SuppressWarnings({ - "nullness" // only check for crashes. Also, was present in the original source file (so the - // annotations in this code were preserved by RemoveAnnotationsForInference). - }) - public V merge(@NonNull V value) { - return value; - } + @SuppressWarnings({ + "nullness" // only check for crashes. Also, was present in the original source file (so the + // annotations in this code were preserved by RemoveAnnotationsForInference). + }) + public V merge(@NonNull V value) { + return value; + } - public V mergeNullable(@Nullable V value) { - // :: warning: (return) - return value; - } + public V mergeNullable(@Nullable V value) { + // :: warning: (return) + return value; + } } diff --git a/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java b/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java index 42cfc3be95b..da50c30866f 100644 --- a/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java +++ b/checker/tests/ainfer-nullness/non-annotated/TypeVarReturnAnnotated.java @@ -3,7 +3,7 @@ // https://github.com/dd482IT/cache2k-wpi/blob/0eaa156bdecd617b2aa4c745d0f8844a32609697/cache2k-api/src/main/java/org/cache2k/config/ToggleFeature.java#L73 public class TypeVarReturnAnnotated { - public static T extract() { - return null; - } + public static T extract() { + return null; + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java b/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java index 83321766e00..393e86e6ca4 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/AddNotOwning.java @@ -6,87 +6,87 @@ class AddNotOwning { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - static class NonEmptyMustCallFinalField { - final Foo f; // expect owning annotation for this field - - NonEmptyMustCallFinalField() { - // :: warning: (required.method.not.called) - f = new Foo(); - } - - Foo getField() { - return f; - } - - Foo getFieldOnSomePath() { - if (true) { - return null; - } else { - return f; - } - } - - void testNotOwningOnFinal() { - // :: warning: (required.method.not.called) - Foo f = getField(); - } - - void testNotOwningOnGetFieldOnSomePath() { - // :: warning: (required.method.not.called) - Foo f = getFieldOnSomePath(); - } - - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - void dispose() { - f.a(); - } - } - - @InheritableMustCall("dispose") - static class NonEmptyMustCallNonFinalField { - Foo f; // expect owning annotation for this field - - @SuppressWarnings("missing.creates.mustcall.for") - void initialyzeFoo() { - f.a(); - // :: warning: (required.method.not.called) - f = new Foo(); - } - - Foo getField() { - return f; - } - - Foo getFieldOnSomePath() { - if (true) { - return null; - } else { - return f; - } - } - - void testNotOwningOnNonFinal() { - // :: warning: (required.method.not.called) - Foo f = getField(); + @InheritableMustCall("a") + static class Foo { + void a() {} } - void testNotOwningOnGetFieldOnSomePath() { - // :: warning: (required.method.not.called) - Foo f = getFieldOnSomePath(); + static class NonEmptyMustCallFinalField { + final Foo f; // expect owning annotation for this field + + NonEmptyMustCallFinalField() { + // :: warning: (required.method.not.called) + f = new Foo(); + } + + Foo getField() { + return f; + } + + Foo getFieldOnSomePath() { + if (true) { + return null; + } else { + return f; + } + } + + void testNotOwningOnFinal() { + // :: warning: (required.method.not.called) + Foo f = getField(); + } + + void testNotOwningOnGetFieldOnSomePath() { + // :: warning: (required.method.not.called) + Foo f = getFieldOnSomePath(); + } + + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + void dispose() { + f.a(); + } } - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - void dispose() { - f.a(); + @InheritableMustCall("dispose") + static class NonEmptyMustCallNonFinalField { + Foo f; // expect owning annotation for this field + + @SuppressWarnings("missing.creates.mustcall.for") + void initialyzeFoo() { + f.a(); + // :: warning: (required.method.not.called) + f = new Foo(); + } + + Foo getField() { + return f; + } + + Foo getFieldOnSomePath() { + if (true) { + return null; + } else { + return f; + } + } + + void testNotOwningOnNonFinal() { + // :: warning: (required.method.not.called) + Foo f = getField(); + } + + void testNotOwningOnGetFieldOnSomePath() { + // :: warning: (required.method.not.called) + Foo f = getFieldOnSomePath(); + } + + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + void dispose() { + f.a(); + } } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java b/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java index 6b011244f11..e19b06ccf95 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/ClassWithTwoOwningFieldsTest.java @@ -5,36 +5,36 @@ import org.checkerframework.checker.mustcall.qual.*; class ClassWithTwoOwningFieldsTest { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @InheritableMustCall("close") - private class ClassWithTwoOwningFields { - // :: warning: (required.method.not.called) - final @Owning Foo foo1; - // :: warning: (required.method.not.called) - final @Owning Foo foo2; + @InheritableMustCall("close") + private class ClassWithTwoOwningFields { + // :: warning: (required.method.not.called) + final @Owning Foo foo1; + // :: warning: (required.method.not.called) + final @Owning Foo foo2; - public ClassWithTwoOwningFields(Foo f1, Foo f2) { - foo1 = f1; - foo2 = f2; - } + public ClassWithTwoOwningFields(Foo f1, Foo f2) { + foo1 = f1; + foo2 = f2; + } - void close() { - foo1.a(); - foo2.a(); + void close() { + foo1.a(); + foo2.a(); + } } - } - void testTwoOwning() { - // :: warning: (required.method.not.called) - Foo f1 = new Foo(); - // :: warning: (required.method.not.called) - Foo f2 = new Foo(); + void testTwoOwning() { + // :: warning: (required.method.not.called) + Foo f1 = new Foo(); + // :: warning: (required.method.not.called) + Foo f2 = new Foo(); - ClassWithTwoOwningFields ff = new ClassWithTwoOwningFields(f1, f2); - ff.close(); - } + ClassWithTwoOwningFields ff = new ClassWithTwoOwningFields(f1, f2); + ff.close(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java b/checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java index ed72d1603f2..51442c7fe1a 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java @@ -4,23 +4,23 @@ */ public abstract class CrashForTempVar { - private final CrashForTempVar _base; + private final CrashForTempVar _base; - protected CrashForTempVar(final CrashForTempVar base) { - _base = base; - } + protected CrashForTempVar(final CrashForTempVar base) { + _base = base; + } - public T getValue() { - return _base.getValue(); - } + public T getValue() { + return _base.getValue(); + } - protected CrashForTempVar getBase() { - return _base; - } + protected CrashForTempVar getBase() { + return _base; + } - protected abstract boolean evaluateLayer(final T baseValue, final T testValue); + protected abstract boolean evaluateLayer(final T baseValue, final T testValue); - public boolean evaluate(final T testValue) { - return evaluateLayer(getBase().getValue(), testValue); - } + public boolean evaluate(final T testValue) { + return evaluateLayer(getBase().getValue(), testValue); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java b/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java index 8997c923653..0db8cca1c22 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/DatadirCleanupManager.java @@ -2,15 +2,15 @@ import java.util.*; public class DatadirCleanupManager { - static class PurgeTask extends TimerTask { + static class PurgeTask extends TimerTask { - @Override - public void run() { - try { - PurgeTxnLog.purge(); - } catch (Exception e) { + @Override + public void run() { + try { + PurgeTxnLog.purge(); + } catch (Exception e) { - } + } + } } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java b/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java index 7fecb377aa4..5bde4f629b4 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/ECMInference.java @@ -5,54 +5,54 @@ public class ECMInference { - class A1 { - void doStuff() { - toString(); + class A1 { + void doStuff() { + toString(); + } + + void clientA1() { + doStuff(); + // :: warning: (assignment) + @CalledMethods("toString") A1 a1 = this; + } + } + + class B1 extends A1 { + @Override + void doStuff() { + toString(); + } + + void clientB1() { + doStuff(); + // :: warning: (assignment) + @CalledMethods("toString") B1 b1 = this; + } + } + + class A2 { + void doStuff() { + toString(); + } + + void clientA2() { + doStuff(); + // :: warning: (assignment) + @CalledMethods("toString") A2 a2 = this; + } + } + + class B2 extends A2 { + @Override + void doStuff() { + toString(); + hashCode(); + } + + void clientB2() { + doStuff(); + // :: warning: (assignment) + @CalledMethods({"toString", "hashCode"}) B2 b2 = this; + } } - - void clientA1() { - doStuff(); - // :: warning: (assignment) - @CalledMethods("toString") A1 a1 = this; - } - } - - class B1 extends A1 { - @Override - void doStuff() { - toString(); - } - - void clientB1() { - doStuff(); - // :: warning: (assignment) - @CalledMethods("toString") B1 b1 = this; - } - } - - class A2 { - void doStuff() { - toString(); - } - - void clientA2() { - doStuff(); - // :: warning: (assignment) - @CalledMethods("toString") A2 a2 = this; - } - } - - class B2 extends A2 { - @Override - void doStuff() { - toString(); - hashCode(); - } - - void clientB2() { - doStuff(); - // :: warning: (assignment) - @CalledMethods({"toString", "hashCode"}) B2 b2 = this; - } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java index 65c1fb5cf4b..269918e0d53 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsTest.java @@ -2,28 +2,28 @@ import org.checkerframework.checker.mustcall.qual.*; class EnsuresCalledMethodsTest { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @InheritableMustCall("close") - class ECM { - // :: warning: (required.method.not.called) - @Owning Foo foo; + @InheritableMustCall("close") + class ECM { + // :: warning: (required.method.not.called) + @Owning Foo foo; - private void closePrivate() { - if (foo != null) { - foo.a(); - foo = null; - } - } + private void closePrivate() { + if (foo != null) { + foo.a(); + foo = null; + } + } - void close() { - if (foo != null) { - foo.a(); - foo = null; - } + void close() { + if (foo != null) { + foo.a(); + foo = null; + } + } } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java index a8ebf5b0fea..005ad4d531f 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/EnsuresCalledMethodsVarArgsTest.java @@ -3,58 +3,58 @@ class EnsuresCalledMethodsVarArgsTest { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - static class Utils { - @SuppressWarnings("ensuresvarargs.unverified") - @EnsuresCalledMethodsVarArgs("a") - public static void close(Foo... foos) { - for (Foo f : foos) { - if (f != null) { - f.a(); + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + static class Utils { + @SuppressWarnings("ensuresvarargs.unverified") + @EnsuresCalledMethodsVarArgs("a") + public static void close(Foo... foos) { + for (Foo f : foos) { + if (f != null) { + f.a(); + } + } } - } } - } - private class ECMVA { - final Foo foo; + private class ECMVA { + final Foo foo; - ECMVA() { - // :: warning: (required.method.not.called) - foo = new Foo(); - } + ECMVA() { + // :: warning: (required.method.not.called) + foo = new Foo(); + } - void finalyzer() { - Utils.close(foo); - } + void finalyzer() { + Utils.close(foo); + } - @EnsuresCalledMethods( - value = {"#1"}, - methods = {"a"}) - void closef(Foo f) { - if (f != null) { - Utils.close(f); - } - } + @EnsuresCalledMethods( + value = {"#1"}, + methods = {"a"}) + void closef(Foo f) { + if (f != null) { + Utils.close(f); + } + } - void owningParam(Foo f) { - Foo foo = f; - Utils.close(foo); - } + void owningParam(Foo f) { + Foo foo = f; + Utils.close(foo); + } - void testOwningParamOnOwningParam() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - owningParam(f); + void testOwningParamOnOwningParam() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + owningParam(f); + } } - } - void testCorrect() { - ECMVA e = new ECMVA(); - e.finalyzer(); - } + void testCorrect() { + ECMVA e = new ECMVA(); + e.finalyzer(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java b/checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java index b5143071b21..3f86fbbfd55 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java @@ -4,15 +4,15 @@ * AssertionErrors when processing such cases. */ class Generic { - public T data; + public T data; - public Generic(T data) { - this.data = data; - } + public Generic(T data) { + this.data = data; + } } public class GenericClassFieldCrash { - private void onPacket(Generic foo) { - String.format("socket received: data '%s'", foo.data); - } + private void onPacket(Generic foo) { + String.format("socket received: data '%s'", foo.data); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java b/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java index f617a0590ef..527b185c175 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/HadoopCrash.java @@ -1,9 +1,9 @@ class NullReceiverTest { - public static void testReceiver(NullReceiverTest nrt) { - nrt.nullReceiver(); - } + public static void testReceiver(NullReceiverTest nrt) { + nrt.nullReceiver(); + } - public static NullReceiverTest nullReceiver() { - return new NullReceiverTest(); - } + public static NullReceiverTest nullReceiver() { + return new NullReceiverTest(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java index c3c0190b3f2..20d0f9b5f8c 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnReceiver.java @@ -1,40 +1,41 @@ // @skip-test until we have support for adding annotation on the receiver parameter. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + @InheritableMustCall("close") public class MustCallAliasOnReceiver { - final @Owning InputStream is; + final @Owning InputStream is; - @MustCallAlias MustCallAliasOnReceiver(@MustCallAlias InputStream p, boolean b) { - this.is = p; - } + @MustCallAlias MustCallAliasOnReceiver(@MustCallAlias InputStream p, boolean b) { + this.is = p; + } - MustCallAliasOnReceiver returnReceiver(MustCallAliasOnReceiver this) { - return this; - } + MustCallAliasOnReceiver returnReceiver(MustCallAliasOnReceiver this) { + return this; + } - // :: warning: (required.method.not.called) - void testReceiverMCAAnnotation(@Owning InputStream inputStream) throws IOException { - MustCallAliasOnReceiver mcar = new MustCallAliasOnReceiver(is, false); - mcar.returnReceiver().close(); - } + // :: warning: (required.method.not.called) + void testReceiverMCAAnnotation(@Owning InputStream inputStream) throws IOException { + MustCallAliasOnReceiver mcar = new MustCallAliasOnReceiver(is, false); + mcar.returnReceiver().close(); + } - @EnsuresCalledMethods(value = "this.is", methods = "close") - public void close() throws IOException { - this.is.close(); - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } - public static MustCallAliasOnReceiver mcaneFactory(InputStream is) { - return new MustCallAliasOnReceiver(is, false); - } + public static MustCallAliasOnReceiver mcaneFactory(InputStream is) { + return new MustCallAliasOnReceiver(is, false); + } - // :: warning: (required.method.not.called) - public static void testUse(@Owning InputStream inputStream) throws Exception { - MustCallAliasOnReceiver mcane = mcaneFactory(inputStream); - mcane.close(); - } + // :: warning: (required.method.not.called) + public static void testUse(@Owning InputStream inputStream) throws Exception { + MustCallAliasOnReceiver mcane = mcaneFactory(inputStream); + mcane.close(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java index a5c40ba80c4..2199bc04604 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasOnRegularExits.java @@ -1,52 +1,53 @@ // This test ensures that the all-paths condition for inferring the @MustCallAlias annotation is // restricted to the examination of 'Regular' paths. -import java.io.IOException; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.IOException; + class MustCallAliasOnRegularExits { - @InheritableMustCall("a") - static class Foo { - void a() {} + @InheritableMustCall("a") + static class Foo { + void a() {} - int b() throws IOException { - return 0; + int b() throws IOException { + return 0; + } } - } - private class MCAConstructor extends Foo { - - protected final @Owning Foo f; - protected long s = 0L; - - // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a - // false positive. - @SuppressWarnings("assignment") - protected MCAConstructor(Foo foo) throws IOException { - if (foo == null) { - this.s = foo.b(); - } - this.f = foo; + private class MCAConstructor extends Foo { + + protected final @Owning Foo f; + protected long s = 0L; + + // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a + // false positive. + @SuppressWarnings("assignment") + protected MCAConstructor(Foo foo) throws IOException { + if (foo == null) { + this.s = foo.b(); + } + this.f = foo; + } + + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + public void a() { + f.a(); + } } - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - public void a() { - f.a(); - } - } - - void testMCAOnMCAConstructor() { - Foo f = new Foo(); - try { - // :: warning: (required.method.not.called) - MCAConstructor mcaf = new MCAConstructor(f); - } catch (IOException e) { - } finally { - f.a(); + void testMCAOnMCAConstructor() { + Foo f = new Foo(); + try { + // :: warning: (required.method.not.called) + MCAConstructor mcaf = new MCAConstructor(f); + } catch (IOException e) { + } finally { + f.a(); + } } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java index fbf6b29df9f..295183bc20d 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/MustCallAliasParams.java @@ -5,155 +5,155 @@ class MustCallAliasParams { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - @InheritableMustCall("a") - private class MCAConstructor { - - final @Owning Foo f; - - // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a - // false positive. - @SuppressWarnings("assignment") - MCAConstructor(Foo foo) { - f = foo; + @InheritableMustCall("a") + static class Foo { + void a() {} } - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - public void a() { - f.a(); - } - } - - void testMCAConstructor() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - MCAConstructor mcac = new MCAConstructor(f); - mcac.a(); - } - - @InheritableMustCall("a") - private class MCASuperClass { - int i; - final @Owning Foo f; - - // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a - // false positive. - @SuppressWarnings("assignment") - @MustCallAlias MCASuperClass(@MustCallAlias Foo foo, int i) { - f = foo; - i = i; - } + @InheritableMustCall("a") + private class MCAConstructor { - @EnsuresCalledMethods( - value = {"this.f"}, - methods = {"a"}) - public void a() { - f.a(); - } - } + final @Owning Foo f; - private class MCASuperCall extends MCASuperClass { - MCASuperCall(Foo foo) { - super(foo, 1); - } - } - - void mCASuperCallTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - MCASuperCall mcaSuperCall = new MCASuperCall(f); - mcaSuperCall.a(); - } - - private class MCAThisCall extends MCASuperClass { - @MustCallAlias MCAThisCall(@MustCallAlias Foo foo) { - super(foo, 1); - } + // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a + // false positive. + @SuppressWarnings("assignment") + MCAConstructor(Foo foo) { + f = foo; + } - MCAThisCall(Foo foo, boolean b) { - this(foo); - } - } - - void mCAThisCallTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - MCAThisCall mcaThisCall = new MCAThisCall(f, true); - mcaThisCall.a(); - } - - private class MCAMethod { - Foo returnFoo(Foo foo) { - return foo; + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + public void a() { + f.a(); + } } - void returnFooTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - Foo foo = returnFoo(f); - foo.a(); + void testMCAConstructor() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + MCAConstructor mcac = new MCAConstructor(f); + mcac.a(); } - Foo returnAliasFoo(Foo foo) { - Foo f = foo; - return f; - } + @InheritableMustCall("a") + private class MCASuperClass { + int i; + final @Owning Foo f; - MCASuperClass returnAliasFoo2(Foo foo, int i) { - MCASuperClass f = new MCASuperClass(foo, i); - return f; - } + // The Must Call Checker for assigning @MustCallAlias parameters to @Owning fields reports a + // false positive. + @SuppressWarnings("assignment") + @MustCallAlias MCASuperClass(@MustCallAlias Foo foo, int i) { + f = foo; + i = i; + } - void testReturnAliasFoo2() { - // :: warning: (required.method.not.called) - Foo foo = new Foo(); - MCASuperClass f = returnAliasFoo2(foo, 0); - f.a(); + @EnsuresCalledMethods( + value = {"this.f"}, + methods = {"a"}) + public void a() { + f.a(); + } } - void returnAliasFooTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - Foo foo = returnAliasFoo(f); - foo.a(); + private class MCASuperCall extends MCASuperClass { + MCASuperCall(Foo foo) { + super(foo, 1); + } } - Foo returnFooAllPaths(Foo foo) { - if (true) { - Foo f = foo; - return f; - } else { - return foo; - } + void mCASuperCallTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + MCASuperCall mcaSuperCall = new MCASuperCall(f); + mcaSuperCall.a(); } - void returnFooAllPathsTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - Foo foo = returnFooAllPaths(f); - foo.a(); - } + private class MCAThisCall extends MCASuperClass { + @MustCallAlias MCAThisCall(@MustCallAlias Foo foo) { + super(foo, 1); + } - Foo returnFooSomePath(Foo foo) { - if (true) { - Foo f = new Foo(); - return f; - } else { - return foo; - } + MCAThisCall(Foo foo, boolean b) { + this(foo); + } } - void returnFooSomePathTest() { - Foo f = new Foo(); - Foo foo = returnFooSomePath(f); - f.a(); - foo.a(); + void mCAThisCallTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + MCAThisCall mcaThisCall = new MCAThisCall(f, true); + mcaThisCall.a(); + } + + private class MCAMethod { + Foo returnFoo(Foo foo) { + return foo; + } + + void returnFooTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + Foo foo = returnFoo(f); + foo.a(); + } + + Foo returnAliasFoo(Foo foo) { + Foo f = foo; + return f; + } + + MCASuperClass returnAliasFoo2(Foo foo, int i) { + MCASuperClass f = new MCASuperClass(foo, i); + return f; + } + + void testReturnAliasFoo2() { + // :: warning: (required.method.not.called) + Foo foo = new Foo(); + MCASuperClass f = returnAliasFoo2(foo, 0); + f.a(); + } + + void returnAliasFooTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + Foo foo = returnAliasFoo(f); + foo.a(); + } + + Foo returnFooAllPaths(Foo foo) { + if (true) { + Foo f = foo; + return f; + } else { + return foo; + } + } + + void returnFooAllPathsTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + Foo foo = returnFooAllPaths(f); + foo.a(); + } + + Foo returnFooSomePath(Foo foo) { + if (true) { + Foo f = new Foo(); + return f; + } else { + return foo; + } + } + + void returnFooSomePathTest() { + Foo f = new Foo(); + Foo foo = returnFooSomePath(f); + f.a(); + foo.a(); + } } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java b/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java index 96b4382db38..267624cebc5 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwnershipTransferOnConstructor.java @@ -1,28 +1,29 @@ -import java.io.IOException; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.IOException; +import java.net.*; + class OwnershipTransferOnConstructor { - static class Foo { - Foo(@Owning Socket s) { - try { - s.close(); - } catch (IOException e) { + static class Foo { + Foo(@Owning Socket s) { + try { + s.close(); + } catch (IOException e) { - } + } + } } - } - private class Bar { - void baz(Socket s) { - Foo f = new Foo(s); - } + private class Bar { + void baz(Socket s) { + Foo f = new Foo(s); + } - // :: warning: (required.method.not.called) - void testOwningOnBaz(@Owning Socket s) { - Socket s2 = s; - baz(s2); + // :: warning: (required.method.not.called) + void testOwningOnBaz(@Owning Socket s) { + Socket s2 = s; + baz(s2); + } } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java b/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java index 02771551046..614266af4b0 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwningField.java @@ -2,22 +2,22 @@ class OwningField { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @InheritableMustCall("dispose") - static class FinalOwningField { - final Foo f; + @InheritableMustCall("dispose") + static class FinalOwningField { + final Foo f; - FinalOwningField() { - // :: warning: (required.method.not.called) - f = new Foo(); - } + FinalOwningField() { + // :: warning: (required.method.not.called) + f = new Foo(); + } - void dispose() { - f.a(); + void dispose() { + f.a(); + } } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java b/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java index 7c2c542e9b7..edf4117ed2d 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwningFieldIndirectCall.java @@ -3,35 +3,35 @@ class OwningFieldIndirectCall { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - static class Utility { - @EnsuresCalledMethods(value = "#1", methods = "a") - public static void closeStream(Foo f) { - if (f != null) { - f.a(); - } + static class Utility { + @EnsuresCalledMethods(value = "#1", methods = "a") + public static void closeStream(Foo f) { + if (f != null) { + f.a(); + } + } } - } - static class DisposeFieldUsingECM { - final Foo f; // expect owning annotation for this field + static class DisposeFieldUsingECM { + final Foo f; // expect owning annotation for this field - DisposeFieldUsingECM() { - // :: warning: (required.method.not.called) - f = new Foo(); - } + DisposeFieldUsingECM() { + // :: warning: (required.method.not.called) + f = new Foo(); + } - void dispose() { - Utility.closeStream(f); + void dispose() { + Utility.closeStream(f); + } } - } - void testCorrect() { - DisposeFieldUsingECM d = new DisposeFieldUsingECM(); - d.dispose(); - } + void testCorrect() { + DisposeFieldUsingECM d = new DisposeFieldUsingECM(); + d.dispose(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java b/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java index 1c1cd29eeef..97f7040595f 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/OwningParams.java @@ -2,59 +2,59 @@ import org.checkerframework.checker.mustcall.qual.*; class OwningParams { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - private class OwningParamsDirectCall { - void passOwnership(Foo f) { - f.a(); + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + private class OwningParamsDirectCall { + void passOwnership(Foo f) { + f.a(); + } + + void passOwnershipTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + passOwnership(f); + } + } + + private class OwningParamsIndirectCall { + @EnsuresCalledMethods( + value = {"#1"}, + methods = {"a"}) + void hasECM(Foo f) { + f.a(); + } + + void owningFoo(@Owning Foo f) { + f.a(); + } + + void passOwnership(Foo f1, Foo f2) { + Foo f11 = f1; + hasECM(f11); + Foo f22 = f2; + owningFoo(f22); + } + + void checkAlias(Foo f1) { + Foo f2 = f1; + f2.a(); + } + + void checkAliasTest() { + // :: warning: (required.method.not.called) + Foo f = new Foo(); + checkAlias(f); + } + + void passOwnershipTest() { + // :: warning: (required.method.not.called) + Foo f1 = new Foo(); + // :: warning: (required.method.not.called) + Foo f2 = new Foo(); + passOwnership(f1, f2); + } } - - void passOwnershipTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - passOwnership(f); - } - } - - private class OwningParamsIndirectCall { - @EnsuresCalledMethods( - value = {"#1"}, - methods = {"a"}) - void hasECM(Foo f) { - f.a(); - } - - void owningFoo(@Owning Foo f) { - f.a(); - } - - void passOwnership(Foo f1, Foo f2) { - Foo f11 = f1; - hasECM(f11); - Foo f22 = f2; - owningFoo(f22); - } - - void checkAlias(Foo f1) { - Foo f2 = f1; - f2.a(); - } - - void checkAliasTest() { - // :: warning: (required.method.not.called) - Foo f = new Foo(); - checkAlias(f); - } - - void passOwnershipTest() { - // :: warning: (required.method.not.called) - Foo f1 = new Foo(); - // :: warning: (required.method.not.called) - Foo f2 = new Foo(); - passOwnership(f1, f2); - } - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java b/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java index 957cb0bb53f..13dc3762f9e 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/PurgeTxnLog.java @@ -3,20 +3,20 @@ public class PurgeTxnLog { - public static void purge() throws IOException { - staticMethod(); - } + public static void purge() throws IOException { + staticMethod(); + } - static void staticMethod() { + static void staticMethod() { - class MyFileFilter implements FileFilter { - MyFileFilter() {} + class MyFileFilter implements FileFilter { + MyFileFilter() {} - public boolean accept(File f) { - return true; - } - } + public boolean accept(File f) { + return true; + } + } - MyFileFilter m = new MyFileFilter(); - } + MyFileFilter m = new MyFileFilter(); + } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java b/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java index b0599501ff3..e1ce8cf4fd0 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/ReplaceMustCallAliasAnnotation.java @@ -6,37 +6,37 @@ class ReplaceMustCallAliasAnnotation { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - @InheritableMustCall("a") - private class TwoOwningFields { - - final @Owning Foo f1; - final @Owning Foo f2; + @InheritableMustCall("a") + static class Foo { + void a() {} + } - @SuppressWarnings({"assignment", "mustcallalias.out.of.scope"}) - @MustCallAlias TwoOwningFields(@MustCallAlias Foo foo1, Foo foo2) { - f1 = foo1; - f2 = foo2; + @InheritableMustCall("a") + private class TwoOwningFields { + + final @Owning Foo f1; + final @Owning Foo f2; + + @SuppressWarnings({"assignment", "mustcallalias.out.of.scope"}) + @MustCallAlias TwoOwningFields(@MustCallAlias Foo foo1, Foo foo2) { + f1 = foo1; + f2 = foo2; + } + + @EnsuresCalledMethods( + value = {"this.f1", "this.f2"}, + methods = {"a"}) + public void a() { + f1.a(); + f2.a(); + } } - @EnsuresCalledMethods( - value = {"this.f1", "this.f2"}, - methods = {"a"}) - public void a() { - f1.a(); - f2.a(); + void testOwningAnnotations() { + Foo f1 = new Foo(); + // :: warning: (required.method.not.called) + Foo f2 = new Foo(); + TwoOwningFields t = new TwoOwningFields(f1, f2); + t.a(); } - } - - void testOwningAnnotations() { - Foo f1 = new Foo(); - // :: warning: (required.method.not.called) - Foo f2 = new Foo(); - TwoOwningFields t = new TwoOwningFields(f1, f2); - t.a(); - } } diff --git a/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java b/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java index 622b9f0d1b6..adac77e685f 100644 --- a/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java +++ b/checker/tests/ainfer-resourceleak/non-annotated/UnwantedECMInference.java @@ -1,19 +1,19 @@ public class UnwantedECMInference { - class Bar { - Object field; + class Bar { + Object field; - void doStuff() { - field.toString(); + void doStuff() { + field.toString(); + } } - } - class Baz extends Bar { - @Override - void doStuff() { - // This method does not call toString(), so an @EnsuresCalledMethods("toString") - // annotation on either this method or on the overridden method is an error! - field.hashCode(); + class Baz extends Bar { + @Override + void doStuff() { + // This method does not call toString(), so an @EnsuresCalledMethods("toString") + // annotation on either this method or on the overridden method is an error! + field.hashCode(); + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java b/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java index 88d96a23c32..d4d6eff2339 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AddAnnosToFormalParameterTest.java @@ -3,26 +3,26 @@ import java.io.*; class JavaSerialization { - private interface Serializer {} + private interface Serializer {} - static class JavaSerializationSerializer implements Serializer { + static class JavaSerializationSerializer implements Serializer { - private ObjectOutputStream oos; + private ObjectOutputStream oos; - // Note that it is important to reproduce the crash that the name of this parameter not - // be changed: if it is e.g., "iShouldBeTreatedAsSibling1", no crash occurs. - public JavaSerializationSerializer(OutputStream out) throws IOException { - oos = - new ObjectOutputStream(out) { - @Override - protected void writeStreamHeader() { - // no header - } - }; - } + // Note that it is important to reproduce the crash that the name of this parameter not + // be changed: if it is e.g., "iShouldBeTreatedAsSibling1", no crash occurs. + public JavaSerializationSerializer(OutputStream out) throws IOException { + oos = + new ObjectOutputStream(out) { + @Override + protected void writeStreamHeader() { + // no header + } + }; + } - public void close() throws IOException { - oos.close(); + public void close() throws IOException { + oos.close(); + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java b/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java index 60a562f0cc0..3f7886f5a60 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AinferSibling1.java @@ -7,7 +7,7 @@ */ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface AinferSibling1 { - String value() default "Sibling1"; + String value() default "Sibling1"; - String anotherValue() default "foo"; + String anotherValue() default "foo"; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java b/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java index 165ad14a755..72ac6ada89f 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnnotationWithFieldTest.java @@ -2,49 +2,49 @@ public class AnnotationWithFieldTest { - private String fields; - - private String emptyFields; - - void testAnnotationWithFields() { - fields = getAinferSiblingWithFields(); - // :: warning: (argument.type.incompatible) - expectsAinferSiblingWithFields(fields); - } - - void testAnnotationWithEmptyFields() { - emptyFields = getAinferSiblingWithFieldsEmpty(); - // :: warning: (argument.type.incompatible) - expectsAinferSiblingWithEmptyFields(emptyFields); - } - - void expectsAinferSiblingWithFields( - @AinferSiblingWithFields( - value = {"test", "test2"}, - value2 = "test3") - String s) {} - - void expectsAinferSiblingWithEmptyFields( - @AinferSiblingWithFields( - value = {}, - value2 = "") - String s) {} - - @SuppressWarnings("cast.unsafe") - String getAinferSiblingWithFields() { - return (@AinferSiblingWithFields( - value = {"test", "test2"}, - value2 = "test3") - String) - ""; - } - - @SuppressWarnings("cast.unsafe") - String getAinferSiblingWithFieldsEmpty() { - return (@AinferSiblingWithFields( - value = {}, - value2 = "") - String) - ""; - } + private String fields; + + private String emptyFields; + + void testAnnotationWithFields() { + fields = getAinferSiblingWithFields(); + // :: warning: (argument.type.incompatible) + expectsAinferSiblingWithFields(fields); + } + + void testAnnotationWithEmptyFields() { + emptyFields = getAinferSiblingWithFieldsEmpty(); + // :: warning: (argument.type.incompatible) + expectsAinferSiblingWithEmptyFields(emptyFields); + } + + void expectsAinferSiblingWithFields( + @AinferSiblingWithFields( + value = {"test", "test2"}, + value2 = "test3") + String s) {} + + void expectsAinferSiblingWithEmptyFields( + @AinferSiblingWithFields( + value = {}, + value2 = "") + String s) {} + + @SuppressWarnings("cast.unsafe") + String getAinferSiblingWithFields() { + return (@AinferSiblingWithFields( + value = {"test", "test2"}, + value2 = "test3") + String) + ""; + } + + @SuppressWarnings("cast.unsafe") + String getAinferSiblingWithFieldsEmpty() { + return (@AinferSiblingWithFields( + value = {}, + value2 = "") + String) + ""; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java b/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java index ca5e9bcf8a0..38518230da5 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Anonymous.java @@ -5,33 +5,33 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; public class Anonymous { - public static int field1; // parent - public static int field2; // sib2 + public static int field1; // parent + public static int field2; // sib2 - public Anonymous() { - field1 = getAinferSibling1(); - } + public Anonymous() { + field1 = getAinferSibling1(); + } - void testPublicInference() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(field2); - // :: warning: (argument.type.incompatible) - expectsParent(field1); - // :: warning: (argument.type.incompatible) - expectsParent(field2); - } + void testPublicInference() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(field2); + // :: warning: (argument.type.incompatible) + expectsParent(field1); + // :: warning: (argument.type.incompatible) + expectsParent(field2); + } - void expectsBottom(@AinferBottom int t) {} + void expectsBottom(@AinferBottom int t) {} - void expectsAinferSibling1(@AinferSibling1 int t) {} + void expectsAinferSibling1(@AinferSibling1 int t) {} - void expectsAinferSibling2(@AinferSibling2 int t) {} + void expectsAinferSibling2(@AinferSibling2 int t) {} - void expectsAinferTop(@AinferTop int t) {} + void expectsAinferTop(@AinferTop int t) {} - void expectsParent(@AinferParent int t) {} + void expectsParent(@AinferParent int t) {} - @AinferSibling1 int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + @AinferSibling1 int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java index 56648c3962e..030f88cbabd 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java @@ -3,39 +3,39 @@ // For WPI, this test is actually useful for testing that varargs are handled properly. public class AnonymousAndInnerClass { - class MyInnerClass { - public MyInnerClass() {} + class MyInnerClass { + public MyInnerClass() {} - public MyInnerClass(String s) {} + public MyInnerClass(String s) {} - public MyInnerClass(int... i) {} - } + public MyInnerClass(int... i) {} + } - static class MyClass { - public MyClass() {} + static class MyClass { + public MyClass() {} - public MyClass(String s) {} + public MyClass(String s) {} - public MyClass(int... i) {} - } + public MyClass(int... i) {} + } - void test(AnonymousAndInnerClass outer, String tainted) { - new MyClass() {}; - new MyClass(tainted) {}; - new MyClass(1, 2, 3) {}; - new MyClass(1) {}; - new MyInnerClass() {}; - new MyInnerClass(tainted) {}; - new MyInnerClass(1) {}; - new MyInnerClass(1, 2, 3) {}; - this.new MyInnerClass() {}; - this.new MyInnerClass(tainted) {}; - this.new MyInnerClass(1) {}; - this.new MyInnerClass(1, 2, 3) {}; - outer.new MyInnerClass() {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(1) {}; - outer.new MyInnerClass(1, 2, 3) {}; - } + void test(AnonymousAndInnerClass outer, String tainted) { + new MyClass() {}; + new MyClass(tainted) {}; + new MyClass(1, 2, 3) {}; + new MyClass(1) {}; + new MyInnerClass() {}; + new MyInnerClass(tainted) {}; + new MyInnerClass(1) {}; + new MyInnerClass(1, 2, 3) {}; + this.new MyInnerClass() {}; + this.new MyInnerClass(tainted) {}; + this.new MyInnerClass(1) {}; + this.new MyInnerClass(1, 2, 3) {}; + outer.new MyInnerClass() {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(1) {}; + outer.new MyInnerClass(1, 2, 3) {}; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java index 74a25110e83..e97b9ff0d9f 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassField.java @@ -4,5 +4,5 @@ import java.util.*; public class AnonymousClassField { - public static final List foo = new ArrayList() {}; + public static final List foo = new ArrayList() {}; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java index f7a59e06f9b..49aa669c4bc 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousClassWithField.java @@ -3,25 +3,25 @@ public class AnonymousClassWithField { - public void scan(InterfaceTest foo) { - // do nothing - } + public void scan(InterfaceTest foo) { + // do nothing + } - public void test() { - this.scan( - new InterfaceTestExtension() { - private String s1 = InterfaceTest.getAinferSibling1(); + public void test() { + this.scan( + new InterfaceTestExtension() { + private String s1 = InterfaceTest.getAinferSibling1(); - @Override - public void testX() { - // :: warning: (argument) - requireAinferSibling1(s1); - } + @Override + public void testX() { + // :: warning: (argument) + requireAinferSibling1(s1); + } - public void testY() { - // :: warning: (argument) - requireAinferSibling1(toaster); - } - }); - } + public void testY() { + // :: warning: (argument) + requireAinferSibling1(toaster); + } + }); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java b/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java index 4e279363a95..4426b2fc58b 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java +++ b/checker/tests/ainfer-testchecker/non-annotated/AnonymousOverride.java @@ -2,17 +2,17 @@ // anonymous class that overrides a method. class AnonymousOverride { - public static void main(String[] args) { - (new SpecialThread() { - @Override - public void run() { - System.out.println("starting a thread!"); - } - }) - .start(); - } + public static void main(String[] args) { + (new SpecialThread() { + @Override + public void run() { + System.out.println("starting a thread!"); + } + }) + .start(); + } - private static class SpecialThread extends Thread { - public T t; - } + private static class SpecialThread extends Thread { + public T t; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java b/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java index 38b59005ebf..b7488365d56 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ArrayAndTypevar.java @@ -3,15 +3,15 @@ class ArrayAndTypevar { - private class MyClass { - private T t; + private class MyClass { + private T t; - public MyClass(T t) { - this.t = t; + public MyClass(T t) { + this.t = t; + } } - } - public void test() { - MyClass m = new MyClass(new String[] {"foo", "bar"}); - } + public void test() { + MyClass m = new MyClass(new String[] {"foo", "bar"}); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java index bd42985d9fc..07f75d63e10 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest.java @@ -2,23 +2,23 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class CompoundTypeTest { - // The default type for fields is @AinferDefaultType. - Object[] field; + // The default type for fields is @AinferDefaultType. + Object[] field; - void assign() { - field = getCompoundType(); - } + void assign() { + field = getCompoundType(); + } - void test() { - // :: warning: (argument.type.incompatible) - expectsCompoundType(field); - } + void test() { + // :: warning: (argument.type.incompatible) + expectsCompoundType(field); + } - void expectsCompoundType(@AinferSibling1 Object @AinferSibling2 [] obj) {} + void expectsCompoundType(@AinferSibling1 Object @AinferSibling2 [] obj) {} - @AinferSibling1 Object @AinferSibling2 [] getCompoundType() { - @SuppressWarnings("cast.unsafe") - @AinferSibling1 Object @AinferSibling2 [] out = (@AinferSibling1 Object @AinferSibling2 []) new Object[1]; - return out; - } + @AinferSibling1 Object @AinferSibling2 [] getCompoundType() { + @SuppressWarnings("cast.unsafe") + @AinferSibling1 Object @AinferSibling2 [] out = (@AinferSibling1 Object @AinferSibling2 []) new Object[1]; + return out; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java index 7b51f68f45d..91fb7315d3c 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/CompoundTypeTest2.java @@ -1,4 +1,4 @@ public class CompoundTypeTest2 { - private static int[] foo = new int[10]; - private static String[] bar = new String[10]; + private static int[] foo = new int[10]; + private static String[] bar = new String[10]; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java b/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java index 2214046fc7c..6ff64312e7f 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ConflictingAnnotationsTest.java @@ -6,26 +6,26 @@ public class ConflictingAnnotationsTest { - int getWPINamespaceAinferSibling1() { - return getAinferSibling1(); - } + int getWPINamespaceAinferSibling1() { + return getAinferSibling1(); + } - // This version of AinferSibling1 is not typechecked - it doesn't belong to the checker and - // instead is - // defined in the AinferSibling1.java file in this directory. - @AinferSibling1 Object getLocalAinferSibling1(Object o) { - return o; - } + // This version of AinferSibling1 is not typechecked - it doesn't belong to the checker and + // instead is + // defined in the AinferSibling1.java file in this directory. + @AinferSibling1 Object getLocalAinferSibling1(Object o) { + return o; + } - void test() { - // :: warning: argument.type.incompatible - expectsAinferSibling1(getWPINamespaceAinferSibling1()); - } + void test() { + // :: warning: argument.type.incompatible + expectsAinferSibling1(getWPINamespaceAinferSibling1()); + } - @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int getAinferSibling1() { - return 1; - } + @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int getAinferSibling1() { + return 1; + } - void expectsAinferSibling1( - @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int i) {} + void expectsAinferSibling1( + @org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1 int i) {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java b/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java index 74e34d77351..8d3cdddb66b 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ConstructorTest.java @@ -2,11 +2,11 @@ public class ConstructorTest { - public ConstructorTest(int top) {} + public ConstructorTest(int top) {} - void test() { - @AinferTop int top = (@AinferTop int) 0; - // :: warning: (argument.type.incompatible) - new ConstructorTest(top); - } + void test() { + @AinferTop int top = (@AinferTop int) 0; + // :: warning: (argument.type.incompatible) + new ConstructorTest(top); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java b/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java index 492a2fd3edd..fd9a9064b0d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java +++ b/checker/tests/ainfer-testchecker/non-annotated/CrazyEnum.java @@ -1,20 +1,20 @@ @SuppressWarnings("all") // Check for crashes. public class CrazyEnum { - private enum MyEnum { - ENUM_CONST1 { - private final String s = method(); + private enum MyEnum { + ENUM_CONST1 { + private final String s = method(); - private String method() { - return "hello"; - } - }, + private String method() { + return "hello"; + } + }, - ENUM_CONST2 { - private final String s = this.method(); + ENUM_CONST2 { + private final String s = this.method(); - private String method() { - return "hello"; - } + private String method() { + return "hello"; + } + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java b/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java index 1e863b8617e..c0d466cbbf8 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/DefaultsTest.java @@ -6,24 +6,24 @@ // annotated version of this class (in the annotated folder) should have no explicit // @AinferDefaultType annotations. public class DefaultsTest { - String defaultField = ""; - String defaultField2; + String defaultField = ""; + String defaultField2; - void test() { - @SuppressWarnings("all") // To allow the use of the explicit @AinferDefaultType. - @AinferDefaultType String explicitDefault = ""; - defaultField2 = explicitDefault; - } + void test() { + @SuppressWarnings("all") // To allow the use of the explicit @AinferDefaultType. + @AinferDefaultType String explicitDefault = ""; + defaultField2 = explicitDefault; + } - // This method's return type should not be updated by the whole-program inference - // since it is the default. - String lubTest() { - if (Math.random() > 0.5) { - return ""; // @AinferDefaultType - } else { - @SuppressWarnings("cast.unsafe") - @AinferBottom String s = (@AinferBottom String) ""; - return s; + // This method's return type should not be updated by the whole-program inference + // since it is the default. + String lubTest() { + if (Math.random() > 0.5) { + return ""; // @AinferDefaultType + } else { + @SuppressWarnings("cast.unsafe") + @AinferBottom String s = (@AinferBottom String) ""; + return s; + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java b/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java index ca9c1015a00..bb9743d84d6 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/DeviceTypeTest.java @@ -2,13 +2,13 @@ // instead of "enum". public class DeviceTypeTest { - public enum DeviceType { - TRACKER; - } + public enum DeviceType { + TRACKER; + } - private final DeviceType deviceType; + private final DeviceType deviceType; - public DeviceTypeTest() { - deviceType = DeviceType.valueOf("tracker"); - } + public DeviceTypeTest() { + deviceType = DeviceType.valueOf("tracker"); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java b/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java index fc968b0ea10..6844994e7fa 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java +++ b/checker/tests/ainfer-testchecker/non-annotated/DoubleGeneric.java @@ -4,5 +4,5 @@ import java.util.Map; public class DoubleGeneric { - static Map> map10 = new HashMap>(); + static Map> map10 = new HashMap>(); } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java index 8816a3f5f18..5e16ac6eaba 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierFieldDecl.java @@ -7,8 +7,8 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; class EnsuresQualifierFieldDecl { - @AinferSibling1 Object bar; + @AinferSibling1 Object bar; - // No annotation should be inferred here. - void test() {} + // No annotation should be inferred here. + void test() {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java index 6cb3fd23091..079c061bbb6 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierParamsTest.java @@ -6,179 +6,179 @@ class EnsuresQualifierParamsTest { - // these methods are used to infer types + // these methods are used to infer types - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferParent.class) - void becomeParent(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferParent.class) + void becomeParent(Object arg) {} - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferSibling1.class) - void becomeAinferSibling1(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferSibling1.class) + void becomeAinferSibling1(Object arg) {} - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferSibling2.class) - void becomeAinferSibling2(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferSibling2.class) + void becomeAinferSibling2(Object arg) {} - @SuppressWarnings("contracts.postcondition") // establish ground truth - @EnsuresQualifier(expression = "#1", qualifier = AinferBottom.class) - void becomeBottom(Object arg) {} + @SuppressWarnings("contracts.postcondition") // establish ground truth + @EnsuresQualifier(expression = "#1", qualifier = AinferBottom.class) + void becomeBottom(Object arg) {} - // these methods should have types inferred for them - - void argIsParent(Object arg) { - becomeParent(arg); - } - - void argIsParent_2(Object arg, boolean b) { - if (b) { - becomeAinferSibling1(arg); - } else { - becomeAinferSibling2(arg); - } - } - - void argIsAinferSibling2(Object arg) { - becomeAinferSibling2(arg); - } - - void argIsAinferSibling2_2(Object arg, boolean b) { - if (b) { - becomeAinferSibling2(arg); - } else { - becomeBottom(arg); - } - } - - void thisIsParent() { - becomeParent(this); - } - - void thisIsParent_2(boolean b) { - if (b) { - becomeAinferSibling1(this); - } else { - becomeAinferSibling2(this); - } - } - - void thisIsParent_2_2(boolean b) { - if (b) { - becomeAinferSibling2(this); - } else { - becomeAinferSibling1(this); - } - } - - void thisIsParent_3(boolean b) { - if (b) { - becomeAinferSibling1(this); - } else { - becomeAinferSibling2(this); - } - noEnsures(); - } - - void thisIsEmpty(boolean b) { - if (b) { - // do nothing - this.noEnsures(); - } else { - becomeAinferSibling1(this); - } - } - - void thisIsAinferSibling2() { - becomeAinferSibling2(this); - } - - void thisIsAinferSibling2_2(boolean b) { - if (b) { - becomeAinferSibling2(this); - } else { - becomeBottom(this); - } - } - - void thisIsAinferSibling2_2_2(boolean b) { - if (b) { - becomeBottom(this); - } else { - becomeAinferSibling2(this); - } - } - - void noEnsures() {} - - void client1(Object arg) { - argIsParent(arg); - // :: warning: (assignment.type.incompatible) - @AinferParent Object p = arg; - } - - void client2(Object arg) { - argIsParent_2(arg, true); - // :: warning: (assignment.type.incompatible) - @AinferParent Object p = arg; - } - - void client3(Object arg) { - argIsAinferSibling2(arg); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object x = arg; - } - - void client4(Object arg) { - argIsAinferSibling2_2(arg, true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object x = arg; - } - - void clientThis1() { - thisIsParent(); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis2() { - thisIsParent_2(true); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis2_2() { - thisIsParent_2(false); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis2_3() { - thisIsParent_3(false); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } - - void clientThis3() { - thisIsAinferSibling2(); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = this; - } - - void clientThis4() { - thisIsAinferSibling2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = this; - } - - void clientThis5() { - thisIsAinferSibling2_2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = this; - } - - void clientThis6() { - thisIsParent_2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferParent Object o = this; - } + // these methods should have types inferred for them + + void argIsParent(Object arg) { + becomeParent(arg); + } + + void argIsParent_2(Object arg, boolean b) { + if (b) { + becomeAinferSibling1(arg); + } else { + becomeAinferSibling2(arg); + } + } + + void argIsAinferSibling2(Object arg) { + becomeAinferSibling2(arg); + } + + void argIsAinferSibling2_2(Object arg, boolean b) { + if (b) { + becomeAinferSibling2(arg); + } else { + becomeBottom(arg); + } + } + + void thisIsParent() { + becomeParent(this); + } + + void thisIsParent_2(boolean b) { + if (b) { + becomeAinferSibling1(this); + } else { + becomeAinferSibling2(this); + } + } + + void thisIsParent_2_2(boolean b) { + if (b) { + becomeAinferSibling2(this); + } else { + becomeAinferSibling1(this); + } + } + + void thisIsParent_3(boolean b) { + if (b) { + becomeAinferSibling1(this); + } else { + becomeAinferSibling2(this); + } + noEnsures(); + } + + void thisIsEmpty(boolean b) { + if (b) { + // do nothing + this.noEnsures(); + } else { + becomeAinferSibling1(this); + } + } + + void thisIsAinferSibling2() { + becomeAinferSibling2(this); + } + + void thisIsAinferSibling2_2(boolean b) { + if (b) { + becomeAinferSibling2(this); + } else { + becomeBottom(this); + } + } + + void thisIsAinferSibling2_2_2(boolean b) { + if (b) { + becomeBottom(this); + } else { + becomeAinferSibling2(this); + } + } + + void noEnsures() {} + + void client1(Object arg) { + argIsParent(arg); + // :: warning: (assignment.type.incompatible) + @AinferParent Object p = arg; + } + + void client2(Object arg) { + argIsParent_2(arg, true); + // :: warning: (assignment.type.incompatible) + @AinferParent Object p = arg; + } + + void client3(Object arg) { + argIsAinferSibling2(arg); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object x = arg; + } + + void client4(Object arg) { + argIsAinferSibling2_2(arg, true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object x = arg; + } + + void clientThis1() { + thisIsParent(); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis2() { + thisIsParent_2(true); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis2_2() { + thisIsParent_2(false); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis2_3() { + thisIsParent_3(false); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } + + void clientThis3() { + thisIsAinferSibling2(); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = this; + } + + void clientThis4() { + thisIsAinferSibling2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = this; + } + + void clientThis5() { + thisIsAinferSibling2_2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = this; + } + + void clientThis6() { + thisIsParent_2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferParent Object o = this; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java index 877352e242e..03718f90639 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnsuresQualifierTest.java @@ -6,77 +6,77 @@ class EnsuresQualifierTest { - @AinferTop int field1; - @AinferTop int field2; + @AinferTop int field1; + @AinferTop int field2; - @AinferTop int top; - @AinferParent int parent; - @AinferSibling1 int sibling1; - @AinferSibling2 int sibling2; - @AinferBottom int bottom; + @AinferTop int top; + @AinferParent int parent; + @AinferSibling1 int sibling1; + @AinferSibling2 int sibling2; + @AinferBottom int bottom; - void field1IsParent() { - field1 = parent; - } + void field1IsParent() { + field1 = parent; + } - void field1IsParent_2(boolean b) { - if (b) { - field1 = sibling1; - } else { - field1 = sibling2; + void field1IsParent_2(boolean b) { + if (b) { + field1 = sibling1; + } else { + field1 = sibling2; + } } - } - void field1IsAinferSibling2() { - field1 = sibling2; - } + void field1IsAinferSibling2() { + field1 = sibling2; + } - void field1IsAinferSibling2_2(boolean b) { - if (b) { - field1 = sibling2; - } else { - field1 = bottom; + void field1IsAinferSibling2_2(boolean b) { + if (b) { + field1 = sibling2; + } else { + field1 = bottom; + } } - } - void parentIsAinferSibling1() { - parent = sibling1; - } + void parentIsAinferSibling1() { + parent = sibling1; + } - // Prevent refinement of the `parent` field variable. - void parentIsParent(@AinferParent int x) { - parent = x; - } + // Prevent refinement of the `parent` field variable. + void parentIsParent(@AinferParent int x) { + parent = x; + } - void noEnsures() {} + void noEnsures() {} - void client1() { - field1IsParent(); - // :: warning: (assignment.type.incompatible) - @AinferParent int p = field1; - } + void client1() { + field1IsParent(); + // :: warning: (assignment.type.incompatible) + @AinferParent int p = field1; + } - void client2() { - field1IsParent_2(true); - // :: warning: (assignment.type.incompatible) - @AinferParent int p = field1; - } + void client2() { + field1IsParent_2(true); + // :: warning: (assignment.type.incompatible) + @AinferParent int p = field1; + } - void client3() { - field1IsAinferSibling2(); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 int x = field1; - } + void client3() { + field1IsAinferSibling2(); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 int x = field1; + } - void client4() { - field1IsAinferSibling2_2(true); - // :: warning: (assignment.type.incompatible) - @AinferSibling2 int x = field1; - } + void client4() { + field1IsAinferSibling2_2(true); + // :: warning: (assignment.type.incompatible) + @AinferSibling2 int x = field1; + } - void client5() { - parentIsAinferSibling1(); - // :: warning: (assignment.type.incompatible) - @AinferSibling1 int x = parent; - } + void client5() { + parentIsAinferSibling1(); + // :: warning: (assignment.type.incompatible) + @AinferSibling1 int x = parent; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java b/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java index 0a3d45d9dcc..d971edba23d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumConstants.java @@ -7,15 +7,15 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class EnumConstants { - enum MyEnum { - ONE, - TWO; - } + enum MyEnum { + ONE, + TWO; + } - void requiresS1(@AinferSibling1 MyEnum e) {} + void requiresS1(@AinferSibling1 MyEnum e) {} - void test() { - // :: warning: argument.type.incompatible - requiresS1(MyEnum.ONE); - } + void test() { + // :: warning: argument.type.incompatible + requiresS1(MyEnum.ONE); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java b/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java index 8c7b8295e61..622d6e6ee08 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumMapCrash.java @@ -4,23 +4,23 @@ @SuppressWarnings("all") // only check for crashes public class EnumMapCrash { - private class Holder { - public T held; + private class Holder { + public T held; - public Holder(T held) { - this.held = held; - } + public Holder(T held) { + this.held = held; + } - @Override - public String toString() { - return String.valueOf(held); + @Override + public String toString() { + return String.valueOf(held); + } } - } - private enum FSEditLogOpCodes {} + private enum FSEditLogOpCodes {} - void callHolder(FSEditLogOpCodes f, EnumMap> opCounts) { - Holder holder = opCounts.get(f); - holder.held++; - } + void callHolder(FSEditLogOpCodes f, EnumMap> opCounts) { + Holder holder = opCounts.get(f); + holder.held++; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java b/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java index 183fa6d69d1..6e07594ad34 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumTest.java @@ -1,29 +1,29 @@ public class EnumTest { - public enum MyEnum { - ONE("ONE"), - TWO("TWO"), - THREE("THREE"); + public enum MyEnum { + ONE("ONE"), + TWO("TWO"), + THREE("THREE"); - private final String value; + private final String value; - private MyEnum(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } + private MyEnum(String value) { + this.value = value; + } - public static MyEnum fromValue(String value) throws IllegalArgumentException { - for (MyEnum method : MyEnum.values()) { - String methodString = method.toString(); - if (methodString != null && methodString.equals(value)) { - return method; + @Override + public String toString() { + return value; } - } - throw new IllegalArgumentException("Cannot create enum from: " + value); + public static MyEnum fromValue(String value) throws IllegalArgumentException { + for (MyEnum method : MyEnum.values()) { + String methodString = method.toString(); + if (methodString != null && methodString.equals(value)) { + return method; + } + } + + throw new IllegalArgumentException("Cannot create enum from: " + value); + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java b/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java index facb8ab99b5..32a1b3ae419 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java +++ b/checker/tests/ainfer-testchecker/non-annotated/EnumWithInnerClass.java @@ -4,18 +4,18 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; enum EnumWithInnerClass { - CONSTANT; + CONSTANT; - private static class MyInnerClass { - int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + private static class MyInnerClass { + int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } - void requireAinferSibling1(@AinferSibling1 int x) {} + void requireAinferSibling1(@AinferSibling1 int x) {} - void test() { - // :: warning: argument.type.incompatible - requireAinferSibling1(getAinferSibling1()); + void test() { + // :: warning: argument.type.incompatible + requireAinferSibling1(getAinferSibling1()); + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java b/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java index 97fd0b72274..5a8e9d7453d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ExistingPurityAnnotations.java @@ -7,27 +7,27 @@ public class ExistingPurityAnnotations { - Object obj; + Object obj; - public Object pureMethod(Object object) { - return null; - } + public Object pureMethod(Object object) { + return null; + } - @SuppressWarnings("ainfertest") - @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) - public boolean checkAinferSibling1(Object obj1) { - return true; - } + @SuppressWarnings("ainfertest") + @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) + public boolean checkAinferSibling1(Object obj1) { + return true; + } - public @AinferSibling1 Object usePureMethod() { + public @AinferSibling1 Object usePureMethod() { - if (checkAinferSibling1(obj)) { - // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should - // unrefine the type of obj, and an error will be issued when - // we try to return obj on the next line. - pureMethod(obj); - return obj; + if (checkAinferSibling1(obj)) { + // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should + // unrefine the type of obj, and an error will be issued when + // we try to return obj on the next line. + pureMethod(obj); + return obj; + } + return null; } - return null; - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java b/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java index ed491538d10..9c63af4c83f 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ExpectedErrors.java @@ -1,4 +1,3 @@ -import java.lang.reflect.Field; import org.checkerframework.checker.testchecker.ainfer.qual.AinferBottom; import org.checkerframework.checker.testchecker.ainfer.qual.AinferParent; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; @@ -7,237 +6,239 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; import org.checkerframework.framework.qual.IgnoreInWholeProgramInference; +import java.lang.reflect.Field; + /** * This file contains expected errors that should exist even after the jaif type inference occurs. */ public class ExpectedErrors { - // Case where the declared type is a supertype of the refined type. - private @AinferTop int privateDeclaredField; - public @AinferTop int publicDeclaredField; - - // The type of both privateDeclaredField and publicDeclaredField are - // not refined to @AinferBottom. - void assignFieldsToAinferSibling1() { - privateDeclaredField = getAinferSibling1(); - publicDeclaredField = getAinferSibling1(); - } - - void testFields() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(privateDeclaredField); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(publicDeclaredField); - } - - // Case where the declared type is a subtype of the refined type. - private @AinferBottom int privateDeclaredField2; - public @AinferBottom int publicDeclaredField2; - - // The refinement cannot happen and an assignemnt type incompatible error occurs. - void assignFieldsToAinferTop() { - // :: warning: (assignment.type.incompatible) - privateDeclaredField2 = getAinferTop(); - // :: warning: (assignment.type.incompatible) - publicDeclaredField2 = getAinferTop(); - } - - // No errors should be issued below: - void assignFieldsToBot() { - privateDeclaredField2 = getBottom(); - publicDeclaredField2 = getBottom(); - } - - // Testing that the types above were not widened. - void testFields2() { - expectsBottom(privateDeclaredField2); - expectsBottom(publicDeclaredField2); - } - - // LUB TEST - // The default type for fields is @AinferTop. - private static int lubPrivateField; - public static int lubPublicField; - - void assignLubFieldsToAinferSibling1() { - lubPrivateField = getAinferSibling1(); - lubPublicField = getAinferSibling1(); - } - - static { - lubPrivateField = getAinferSibling2(); - lubPublicField = getAinferSibling2(); - } - - void testLUBFields1() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(lubPrivateField); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(lubPublicField); - } - - void testLUBFields2() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(lubPrivateField); - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(lubPublicField); - } - - private static boolean bool = false; - - public static int lubTest() { - if (bool) { - return (@AinferSibling1 int) 0; - } else { - return (@AinferSibling2 int) 0; - } - } - - public @AinferSibling1 int getAinferSibling1Wrong() { - int x = lubTest(); - // :: warning: (return.type.incompatible) - return x; - } - - public @AinferSibling2 int getAinferSibling2Wrong() { - int x = lubTest(); - // :: warning: (return.type.incompatible) - return x; - } - - void expectsAinferSibling1(@AinferSibling1 int t) {} - - void expectsAinferSibling2(@AinferSibling2 int t) {} - - void expectsBottom(@AinferBottom int t) {} - - void expectsBottom(@AinferBottom String t) {} - - void expectsAinferTop(@AinferTop int t) {} - - void expectsParent(@AinferParent int t) {} - - static @AinferSibling1 int getAinferSibling1() { - return 0; - } - - static @AinferSibling2 int getAinferSibling2() { - return 0; - } - - @AinferBottom int getBottom() { - return 0; - } - - @AinferTop int getAinferTop() { - return 0; - } - - // Method Field.setBoolean != ExpectedErrors.setBoolean. - // No refinement should happen. - void test(Field f) throws Exception { - f.setBoolean(null, false); - } - - void setBoolean(Object o, boolean b) { - // :: warning: (assignment.type.incompatible) - @AinferBottom Object bot = o; - } - - public class SuppressWarningsTest { - // Tests that whole-program inference in a @SuppressWarnings block is ignored. - private int i; - private int i2; + // Case where the declared type is a supertype of the refined type. + private @AinferTop int privateDeclaredField; + public @AinferTop int publicDeclaredField; - @SuppressWarnings("all") - public void suppressWarningsTest() { - i = (@AinferSibling1 int) 0; - i2 = getAinferSibling1(); + // The type of both privateDeclaredField and publicDeclaredField are + // not refined to @AinferBottom. + void assignFieldsToAinferSibling1() { + privateDeclaredField = getAinferSibling1(); + publicDeclaredField = getAinferSibling1(); } - public void suppressWarningsTest2() { - SuppressWarningsInner.i = (@AinferSibling1 int) 0; - SuppressWarningsInner.i2 = getAinferSibling1(); + void testFields() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(privateDeclaredField); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(publicDeclaredField); } - public void suppressWarningsValidation() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(i); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(i2); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(SuppressWarningsInner.i); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(SuppressWarningsInner.i2); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(suppressWarningsMethodReturn()); + // Case where the declared type is a subtype of the refined type. + private @AinferBottom int privateDeclaredField2; + public @AinferBottom int publicDeclaredField2; - suppressWarningsMethodParams(getAinferSibling1()); + // The refinement cannot happen and an assignemnt type incompatible error occurs. + void assignFieldsToAinferTop() { + // :: warning: (assignment.type.incompatible) + privateDeclaredField2 = getAinferTop(); + // :: warning: (assignment.type.incompatible) + publicDeclaredField2 = getAinferTop(); } - @SuppressWarnings("all") - public int suppressWarningsMethodReturn() { - return getAinferSibling1(); + // No errors should be issued below: + void assignFieldsToBot() { + privateDeclaredField2 = getBottom(); + publicDeclaredField2 = getBottom(); } - // It is problematic to automatically test whole-program inference for method params when - // suppressing warnings. - // Since we must use @SuppressWarnings() for the method, we won't be able to catch any error - // inside the method body. Verified manually that in the "annotated" folder param's type - // wasn't updated. - @SuppressWarnings("all") - public void suppressWarningsMethodParams(int param) {} - } + // Testing that the types above were not widened. + void testFields2() { + expectsBottom(privateDeclaredField2); + expectsBottom(publicDeclaredField2); + } - @SuppressWarnings("all") - static class SuppressWarningsInner { - public static int i; - public static int i2; - } + // LUB TEST + // The default type for fields is @AinferTop. + private static int lubPrivateField; + public static int lubPublicField; - class NullTest { - // The default type for fields is @AinferDefaultType. - private String privateField; - public String publicField; + void assignLubFieldsToAinferSibling1() { + lubPrivateField = getAinferSibling1(); + lubPublicField = getAinferSibling1(); + } - // The types of both fields are not refined to @AinferBottom, as whole-program - // inference never performs refinement in the presence of the null literal. - @SuppressWarnings("value") - void assignFieldsToBottom() { - privateField = null; - publicField = null; + static { + lubPrivateField = getAinferSibling2(); + lubPublicField = getAinferSibling2(); } - // Testing the refinement above. - void testFields() { - // :: warning: (argument.type.incompatible) - expectsBottom(privateField); - // :: warning: (argument.type.incompatible) - expectsBottom(publicField); + void testLUBFields1() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(lubPrivateField); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(lubPublicField); } - } - class IgnoreMetaAnnotationTest2 { - @AinferToIgnore int field; - @IgnoreInWholeProgramInference int field2; + void testLUBFields2() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(lubPrivateField); + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(lubPublicField); + } + + private static boolean bool = false; + + public static int lubTest() { + if (bool) { + return (@AinferSibling1 int) 0; + } else { + return (@AinferSibling2 int) 0; + } + } + + public @AinferSibling1 int getAinferSibling1Wrong() { + int x = lubTest(); + // :: warning: (return.type.incompatible) + return x; + } + + public @AinferSibling2 int getAinferSibling2Wrong() { + int x = lubTest(); + // :: warning: (return.type.incompatible) + return x; + } + + void expectsAinferSibling1(@AinferSibling1 int t) {} + + void expectsAinferSibling2(@AinferSibling2 int t) {} + + void expectsBottom(@AinferBottom int t) {} + + void expectsBottom(@AinferBottom String t) {} + + void expectsAinferTop(@AinferTop int t) {} + + void expectsParent(@AinferParent int t) {} + + static @AinferSibling1 int getAinferSibling1() { + return 0; + } + + static @AinferSibling2 int getAinferSibling2() { + return 0; + } + + @AinferBottom int getBottom() { + return 0; + } + + @AinferTop int getAinferTop() { + return 0; + } + + // Method Field.setBoolean != ExpectedErrors.setBoolean. + // No refinement should happen. + void test(Field f) throws Exception { + f.setBoolean(null, false); + } + + void setBoolean(Object o, boolean b) { + // :: warning: (assignment.type.incompatible) + @AinferBottom Object bot = o; + } + + public class SuppressWarningsTest { + // Tests that whole-program inference in a @SuppressWarnings block is ignored. + private int i; + private int i2; + + @SuppressWarnings("all") + public void suppressWarningsTest() { + i = (@AinferSibling1 int) 0; + i2 = getAinferSibling1(); + } + + public void suppressWarningsTest2() { + SuppressWarningsInner.i = (@AinferSibling1 int) 0; + SuppressWarningsInner.i2 = getAinferSibling1(); + } + + public void suppressWarningsValidation() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(i); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(i2); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(SuppressWarningsInner.i); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(SuppressWarningsInner.i2); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(suppressWarningsMethodReturn()); + + suppressWarningsMethodParams(getAinferSibling1()); + } + + @SuppressWarnings("all") + public int suppressWarningsMethodReturn() { + return getAinferSibling1(); + } + + // It is problematic to automatically test whole-program inference for method params when + // suppressing warnings. + // Since we must use @SuppressWarnings() for the method, we won't be able to catch any error + // inside the method body. Verified manually that in the "annotated" folder param's type + // wasn't updated. + @SuppressWarnings("all") + public void suppressWarningsMethodParams(int param) {} + } + + @SuppressWarnings("all") + static class SuppressWarningsInner { + public static int i; + public static int i2; + } - void foo() { - field = getAinferSibling1(); - field2 = getAinferSibling1(); + class NullTest { + // The default type for fields is @AinferDefaultType. + private String privateField; + public String publicField; + + // The types of both fields are not refined to @AinferBottom, as whole-program + // inference never performs refinement in the presence of the null literal. + @SuppressWarnings("value") + void assignFieldsToBottom() { + privateField = null; + publicField = null; + } + + // Testing the refinement above. + void testFields() { + // :: warning: (argument.type.incompatible) + expectsBottom(privateField); + // :: warning: (argument.type.incompatible) + expectsBottom(publicField); + } } - void test() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(field); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(field2); + class IgnoreMetaAnnotationTest2 { + @AinferToIgnore int field; + @IgnoreInWholeProgramInference int field2; + + void foo() { + field = getAinferSibling1(); + field2 = getAinferSibling1(); + } + + void test() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(field); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(field2); + } } - } - class AssignParam { - public void f(@AinferBottom Object param) { - // :: warning: assignment.type.incompatible - param = ((@AinferTop Object) null); + class AssignParam { + public void f(@AinferBottom Object param) { + // :: warning: assignment.type.incompatible + param = ((@AinferTop Object) null); + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java b/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java index aad961e0c6e..b19093a4baf 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java +++ b/checker/tests/ainfer-testchecker/non-annotated/FieldInOtherCompilationUnit.java @@ -1,15 +1,16 @@ -import java.util.GregorianCalendar; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; +import java.util.GregorianCalendar; + public class FieldInOtherCompilationUnit { - static @AinferSibling1 int myTime; + static @AinferSibling1 int myTime; - static void test() { - new GregorianCalendar() { - public void newMethod() { - this.time = myTime; - } - }; - } + static void test() { + new GregorianCalendar() { + public void newMethod() { + this.time = myTime; + } + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java b/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java index b836ba11ad6..894bcfa0259 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java +++ b/checker/tests/ainfer-testchecker/non-annotated/FromReceiver.java @@ -5,46 +5,46 @@ public class FromReceiver { - public void source(@AinferSibling1 FromReceiver this) { - this.sinkNoThis(); - this.sinkExplicitThis(); - - sinkNoThis2(); - sinkExplicitThis2(); - } - - public void sinkNoThis() { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkExplicitThis(FromReceiver this) { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkNoThis2() { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkExplicitThis2(FromReceiver this) { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public static void source2(@AinferSibling1 FromReceiver f1) { - f1.sinkNoThis3(); - f1.sinkExplicitThis3(); - } - - public void sinkNoThis3() { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } - - public void sinkExplicitThis3(FromReceiver this) { - // :: warning: assignment - @AinferSibling1 FromReceiver f = this; - } + public void source(@AinferSibling1 FromReceiver this) { + this.sinkNoThis(); + this.sinkExplicitThis(); + + sinkNoThis2(); + sinkExplicitThis2(); + } + + public void sinkNoThis() { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkExplicitThis(FromReceiver this) { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkNoThis2() { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkExplicitThis2(FromReceiver this) { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public static void source2(@AinferSibling1 FromReceiver f1) { + f1.sinkNoThis3(); + f1.sinkExplicitThis3(); + } + + public void sinkNoThis3() { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } + + public void sinkExplicitThis3(FromReceiver this) { + // :: warning: assignment + @AinferSibling1 FromReceiver f = this; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java b/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java index 0f69ff066d9..7bd45940232 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java +++ b/checker/tests/ainfer-testchecker/non-annotated/IShouldBeSibling1.java @@ -7,8 +7,8 @@ @SuppressWarnings("super.invocation") // Intentional. public class IShouldBeSibling1 { - public static void test(IShouldBeSibling1 s1) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 IShouldBeSibling1 s = s1; - } + public static void test(IShouldBeSibling1 s1) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 IShouldBeSibling1 s = s1; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java b/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java index b418a95c2ca..ae75e631512 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java +++ b/checker/tests/ainfer-testchecker/non-annotated/IgnoreMetaAnnotationTest1.java @@ -3,20 +3,20 @@ // See ExpectedErrors#IgnoreMetaAnnotationTest2 public class IgnoreMetaAnnotationTest1 { - int field2; + int field2; - void foo() { - field2 = getAinferSibling1(); - } + void foo() { + field2 = getAinferSibling1(); + } - void test() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(field2); - } + void test() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(field2); + } - void expectsAinferSibling1(@AinferSibling1 int t) {} + void expectsAinferSibling1(@AinferSibling1 int t) {} - static @AinferSibling1 int getAinferSibling1() { - return 0; - } + static @AinferSibling1 int getAinferSibling1() { + return 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java b/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java index a5de87704ef..e5b02727622 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ImplicitAnnosTest.java @@ -1,7 +1,7 @@ public class ImplicitAnnosTest { - void test() { - StringBuffer sb = new StringBuffer(); - StringBuffer sb2 = sb; - } + void test() { + StringBuffer sb = new StringBuffer(); + StringBuffer sb2 = sb; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java b/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java index 4c03d0779d3..05543bdf34d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InheritanceTest.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferBottom; public class InheritanceTest { - class IParent { - int field; + class IParent { + int field; - public void expectsBotNoSignature(int t) { - // :: warning: (argument.type.incompatible) - expectsBot(t); - // :: warning: (argument.type.incompatible) - expectsBot(field); - } + public void expectsBotNoSignature(int t) { + // :: warning: (argument.type.incompatible) + expectsBot(t); + // :: warning: (argument.type.incompatible) + expectsBot(field); + } - void expectsBot(@AinferBottom int t) {} - } + void expectsBot(@AinferBottom int t) {} + } - class IChild extends IParent { - void test1() { - @AinferBottom int bot = (@AinferBottom int) 0; - expectsBotNoSignature(bot); - field = bot; + class IChild extends IParent { + void test1() { + @AinferBottom int bot = (@AinferBottom int) 0; + expectsBotNoSignature(bot); + field = bot; + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java b/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java index 20a1eb20dcb..c8b5cb366a7 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerClassFieldDeclAnno.java @@ -7,18 +7,18 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTreatAsSibling1; public class InnerClassFieldDeclAnno { - static class Outer { - static class Inner {} - } + static class Outer { + static class Inner {} + } - public Outer.Inner iShouldBeTreatedAsSibling1 = new Outer.Inner(); + public Outer.Inner iShouldBeTreatedAsSibling1 = new Outer.Inner(); - @AinferTreatAsSibling1 public Outer.Inner preAnnotated = null; + @AinferTreatAsSibling1 public Outer.Inner preAnnotated = null; - public static void test(InnerClassFieldDeclAnno a) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 Object obj = a.iShouldBeTreatedAsSibling1; - // Test that the annotation works as expected. - @AinferSibling1 Object obj2 = a.preAnnotated; - } + public static void test(InnerClassFieldDeclAnno a) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 Object obj = a.iShouldBeTreatedAsSibling1; + // Test that the annotation works as expected. + @AinferSibling1 Object obj2 = a.preAnnotated; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java index 65d56a44874..b713d30dfc6 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest.java @@ -1,17 +1,17 @@ public class InnerTypeTest { - public static String toStringQuoted(Object[] a) { - return toString(a, true); - } + public static String toStringQuoted(Object[] a) { + return toString(a, true); + } - public static String toString(Object[] a, boolean quoted) { - if (a == null) { - return "null"; + public static String toString(Object[] a, boolean quoted) { + if (a == null) { + return "null"; + } + StringBuffer sb = new StringBuffer(); + return sb.toString(); } - StringBuffer sb = new StringBuffer(); - return sb.toString(); - } - public void bar() { - assert InnerTypeTest.toStringQuoted((Object[]) null).equals("null"); - } + public void bar() { + assert InnerTypeTest.toStringQuoted((Object[]) null).equals("null"); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java index 3c90c7d348e..52e8111d4ec 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest2.java @@ -1,10 +1,10 @@ public class InnerTypeTest2 { - public static int[] min_max(int[] a) { - if (a.length == 0) { - return null; + public static int[] min_max(int[] a) { + if (a.length == 0) { + return null; + } + int result_min = a[0]; + int result_max = a[0]; + return new int[] {result_min, result_max}; } - int result_min = a[0]; - int result_max = a[0]; - return new int[] {result_min, result_max}; - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java index 522d352a2ea..eb3386cc5e6 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InnerTypeTest3.java @@ -1,9 +1,9 @@ public class InnerTypeTest3 { - private int[] nums; + private int[] nums; - private static byte[] buffer = new byte[4096]; + private static byte[] buffer = new byte[4096]; - private static final char[] digits = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; + private static final char[] digits = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java b/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java index 2472f7938e8..6ceeb0109a5 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/InterfaceTest.java @@ -4,16 +4,16 @@ @SuppressWarnings("cast.unsafe") public interface InterfaceTest { - public String toaster = getAinferSibling1(); + public String toaster = getAinferSibling1(); - public static @AinferSibling1 String getAinferSibling1() { - return (@AinferSibling1 String) "foo"; - } + public static @AinferSibling1 String getAinferSibling1() { + return (@AinferSibling1 String) "foo"; + } - default void requireAinferSibling1(@AinferSibling1 String x) {} + default void requireAinferSibling1(@AinferSibling1 String x) {} - default void testX() { - // :: warning: (argument.type.incompatible) - requireAinferSibling1(toaster); - } + default void testX() { + // :: warning: (argument.type.incompatible) + requireAinferSibling1(toaster); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java b/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java index 5f40371a983..6d2e3c26295 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LUBAssignmentTest.java @@ -3,48 +3,48 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class LUBAssignmentTest { - // The default type for fields is @AinferDefaultType. - private static int privateField; - public static int publicField; - - void assignFieldsToAinferSibling1() { - privateField = getAinferSibling1(); - publicField = getAinferSibling1(); - } - - static { - privateField = getAinferSibling2(); - publicField = getAinferSibling2(); - } - - // LUB between @AinferSibling1 and @AinferSibling2 is @AinferParent, therefore the assignments - // above refine the type of privateField to @AinferParent. - void testFields() { - // :: warning: (argument.type.incompatible) - expectsParent(privateField); - // :: warning: (argument.type.incompatible) - expectsParent(publicField); - } - - void expectsParent(@AinferParent int t) {} - - static @AinferSibling1 int getAinferSibling1() { - return 0; - } - - static @AinferSibling2 int getAinferSibling2() { - return 0; - } - - String lubTest2() { - if (Math.random() > 0.5) { - @SuppressWarnings("cast.unsafe") - @AinferSibling1 String s = (@AinferSibling1 String) ""; - return s; - } else { - @SuppressWarnings("cast.unsafe") - @AinferSibling2 String s = (@AinferSibling2 String) ""; - return s; + // The default type for fields is @AinferDefaultType. + private static int privateField; + public static int publicField; + + void assignFieldsToAinferSibling1() { + privateField = getAinferSibling1(); + publicField = getAinferSibling1(); + } + + static { + privateField = getAinferSibling2(); + publicField = getAinferSibling2(); + } + + // LUB between @AinferSibling1 and @AinferSibling2 is @AinferParent, therefore the assignments + // above refine the type of privateField to @AinferParent. + void testFields() { + // :: warning: (argument.type.incompatible) + expectsParent(privateField); + // :: warning: (argument.type.incompatible) + expectsParent(publicField); + } + + void expectsParent(@AinferParent int t) {} + + static @AinferSibling1 int getAinferSibling1() { + return 0; + } + + static @AinferSibling2 int getAinferSibling2() { + return 0; + } + + String lubTest2() { + if (Math.random() > 0.5) { + @SuppressWarnings("cast.unsafe") + @AinferSibling1 String s = (@AinferSibling1 String) ""; + return s; + } else { + @SuppressWarnings("cast.unsafe") + @AinferSibling2 String s = (@AinferSibling2 String) ""; + return s; + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java b/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java index e9e9d6a655d..91497b956b2 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LambdaParamCrash.java @@ -8,18 +8,18 @@ @SuppressWarnings("all") // only checking for crashes public class LambdaParamCrash { - void groupAndSend() { - addListener( - (r, e1) -> { - if (e1 == null) { - e1 = badResponse(); - } - }); - } + void groupAndSend() { + addListener( + (r, e1) -> { + if (e1 == null) { + e1 = badResponse(); + } + }); + } - private IOException badResponse() { - return new IOException(); - } + private IOException badResponse() { + return new IOException(); + } - public static void addListener(BiConsumer action) {} + public static void addListener(BiConsumer action) {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java b/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java index 44300afcd5d..7b24565b178 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LambdaReturn.java @@ -4,15 +4,15 @@ import java.io.FileFilter; public class LambdaReturn { - void test() { - FileFilter docxFilter = - pathname -> { - // We only want to process *.docx files, everything else can be skipped. - if (pathname.isFile() && pathname.getName().matches(".*\\.docx")) { - return true; - } + void test() { + FileFilter docxFilter = + pathname -> { + // We only want to process *.docx files, everything else can be skipped. + if (pathname.isFile() && pathname.getName().matches(".*\\.docx")) { + return true; + } - return false; - }; - } + return false; + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java b/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java index a4c1d572bc6..cc1fb6d4b12 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/LocalClassTest.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class LocalClassTest { - public void method() { - class Local { - Object o = (@AinferSibling1 Object) null; + public void method() { + class Local { + Object o = (@AinferSibling1 Object) null; + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java b/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java index ccd791d74bb..3683d404925 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodDefinedInSupertype.java @@ -3,21 +3,21 @@ abstract class MethodDefinedInSupertype { - void test() { - // :: warning: argument.type.incompatible - expectsAinferSibling1(shouldReturnAinferSibling1()); - } + void test() { + // :: warning: argument.type.incompatible + expectsAinferSibling1(shouldReturnAinferSibling1()); + } - public void expectsAinferSibling1(@AinferSibling1 int t) {} + public void expectsAinferSibling1(@AinferSibling1 int t) {} - public abstract int shouldReturnAinferSibling1(); + public abstract int shouldReturnAinferSibling1(); - void testMultipleOverrides() { - // :: warning: argument.type.incompatible - expectsParent(shouldReturnParent()); - } + void testMultipleOverrides() { + // :: warning: argument.type.incompatible + expectsParent(shouldReturnParent()); + } - public void expectsParent(@AinferParent int t1) {} + public void expectsParent(@AinferParent int t1) {} - public abstract int shouldReturnParent(); + public abstract int shouldReturnParent(); } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java index e6989d46120..00d52190aa3 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class MethodOverrideInSubtype extends MethodDefinedInSupertype { - @java.lang.Override - public int shouldReturnAinferSibling1() { - return getAinferSibling1(); - } + @java.lang.Override + public int shouldReturnAinferSibling1() { + return getAinferSibling1(); + } - private @AinferSibling1 int getAinferSibling1() { - return 0; - } + private @AinferSibling1 int getAinferSibling1() { + return 0; + } - @java.lang.Override - public int shouldReturnParent() { - return getAinferSibling1(); - } + @java.lang.Override + public int shouldReturnParent() { + return getAinferSibling1(); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java index 1290f2dc51f..0ef69af68a2 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodOverrideInSubtype2.java @@ -2,12 +2,12 @@ abstract class MethodOverrideInSubtype2 extends MethodDefinedInSupertype { - private @AinferSibling2 int getAinferSibling2() { - return 0; - } + private @AinferSibling2 int getAinferSibling2() { + return 0; + } - @java.lang.Override - public int shouldReturnParent() { - return getAinferSibling2(); - } + @java.lang.Override + public int shouldReturnParent() { + return getAinferSibling2(); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java b/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java index b325425dbce..ff01c061fbb 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodParameterInferenceTest.java @@ -3,11 +3,11 @@ // TODO: Like this one, some tests must verify that it contains the expected // output after performing the whole-program inference. public class MethodParameterInferenceTest { - void foo(int i) { - i = getAinferSibling1(); // The type of i must be inferred to @AinferSibling1. - } + void foo(int i) { + i = getAinferSibling1(); // The type of i must be inferred to @AinferSibling1. + } - @AinferSibling1 int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + @AinferSibling1 int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java b/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java index d761c7a682c..811d3ead6f0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MethodReturnTest.java @@ -4,49 +4,49 @@ public class MethodReturnTest { - static int getAinferSibling1NotAnnotated() { - return (@AinferSibling1 int) 0; - } - - static @AinferSibling1 int getAinferSibling1() { - // :: warning: (return.type.incompatible) - return getAinferSibling1NotAnnotated(); - } - - public static boolean bool = false; - - public static int lubTest() { - if (bool) { - return (@AinferSibling1 int) 0; - } else { - return (@AinferSibling2 int) 0; + static int getAinferSibling1NotAnnotated() { + return (@AinferSibling1 int) 0; } - } - public static @AinferParent int getParent() { - int x = lubTest(); - // :: warning: (return.type.incompatible) - return x; - } + static @AinferSibling1 int getAinferSibling1() { + // :: warning: (return.type.incompatible) + return getAinferSibling1NotAnnotated(); + } - class InnerClass { - int field = 0; + public static boolean bool = false; - int getParent2() { - field = getParent(); - return getParent(); + public static int lubTest() { + if (bool) { + return (@AinferSibling1 int) 0; + } else { + return (@AinferSibling2 int) 0; + } } - void receivesAinferSibling1(int i) { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(i); + public static @AinferParent int getParent() { + int x = lubTest(); + // :: warning: (return.type.incompatible) + return x; } - void expectsAinferSibling1(@AinferSibling1 int i) {} + class InnerClass { + int field = 0; + + int getParent2() { + field = getParent(); + return getParent(); + } + + void receivesAinferSibling1(int i) { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(i); + } + + void expectsAinferSibling1(@AinferSibling1 int i) {} - void test() { - @AinferSibling1 int sib = (@AinferSibling1 int) 0; - receivesAinferSibling1(sib); + void test() { + @AinferSibling1 int sib = (@AinferSibling1 int) 0; + receivesAinferSibling1(sib); + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java b/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java index fc9e3b51b75..de610342ef9 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MultiDimensionalArrays.java @@ -1,7 +1,6 @@ // This test ensures that annotations on different component types of multidimensional arrays // are printed correctly. -import java.util.List; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; import org.checkerframework.checker.testchecker.ainfer.qual.AinferSiblingWithFields; @@ -9,257 +8,259 @@ import org.checkerframework.common.aliasing.qual.NonLeaked; import org.checkerframework.common.aliasing.qual.Unique; +import java.util.List; + public class MultiDimensionalArrays { - // two dimensional arrays + // two dimensional arrays + + void requiresS1S2(@AinferSibling1 int @AinferSibling2 [] x) {} + + int[] twoDimArray; + + void testField() { + // :: warning: argument.type.incompatible + requiresS1S2(twoDimArray); + } + + void useField(@AinferSibling1 int @AinferSibling2 [] x) { + twoDimArray = x; + } - void requiresS1S2(@AinferSibling1 int @AinferSibling2 [] x) {} + void testParam(int[] x) { + // :: warning: argument.type.incompatible + requiresS1S2(x); + } - int[] twoDimArray; + void useParam(@AinferSibling1 int @AinferSibling2 [] x) { + testParam(x); + } - void testField() { - // :: warning: argument.type.incompatible - requiresS1S2(twoDimArray); - } + int[] useReturn(@AinferSibling1 int @AinferSibling2 [] x) { + return x; + } - void useField(@AinferSibling1 int @AinferSibling2 [] x) { - twoDimArray = x; - } + void testReturn() { + requiresS1S2( + // :: warning: argument.type.incompatible + useReturn( + // :: warning: argument.type.incompatible + twoDimArray)); + } - void testParam(int[] x) { - // :: warning: argument.type.incompatible - requiresS1S2(x); - } + // three dimensional arrays - void useParam(@AinferSibling1 int @AinferSibling2 [] x) { - testParam(x); - } + void requiresS1S2S1(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) {} - int[] useReturn(@AinferSibling1 int @AinferSibling2 [] x) { - return x; - } + int[][] threeDimArray; - void testReturn() { - requiresS1S2( + void testField2() { // :: warning: argument.type.incompatible - useReturn( - // :: warning: argument.type.incompatible - twoDimArray)); - } + requiresS1S2S1(threeDimArray); + } - // three dimensional arrays + void useField2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { + threeDimArray = x; + } - void requiresS1S2S1(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) {} + void testParam2(int[][] x) { + // :: warning: argument.type.incompatible + requiresS1S2S1(x); + } - int[][] threeDimArray; + void useParam2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { + testParam2(x); + } - void testField2() { - // :: warning: argument.type.incompatible - requiresS1S2S1(threeDimArray); - } + int[][] useReturn2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { + return x; + } - void useField2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { - threeDimArray = x; - } + void testReturn2() { + // :: warning: argument.type.incompatible + requiresS1S2S1(useReturn2(threeDimArray)); + } - void testParam2(int[][] x) { - // :: warning: argument.type.incompatible - requiresS1S2S1(x); - } + // three dimensional array with annotations only on two inner types - void useParam2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { - testParam2(x); - } + void requiresS1S2N(@AinferSibling1 int @AinferSibling2 [][] x) {} - int[][] useReturn2(@AinferSibling1 int @AinferSibling2 [] @AinferSibling1 [] x) { - return x; - } + int[][] threeDimArray2; - void testReturn2() { - // :: warning: argument.type.incompatible - requiresS1S2S1(useReturn2(threeDimArray)); - } + void testField3() { + // :: warning: argument.type.incompatible + requiresS1S2N(threeDimArray2); + } - // three dimensional array with annotations only on two inner types + void useField3(@AinferSibling1 int @AinferSibling2 [][] x) { + threeDimArray2 = x; + } - void requiresS1S2N(@AinferSibling1 int @AinferSibling2 [][] x) {} + void testParam3(int[][] x) { + // :: warning: argument.type.incompatible + requiresS1S2N(x); + } + + void useParam3(@AinferSibling1 int @AinferSibling2 [][] x) { + testParam3(x); + } - int[][] threeDimArray2; + int[][] useReturn3(@AinferSibling1 int @AinferSibling2 [][] x) { + return x; + } - void testField3() { - // :: warning: argument.type.incompatible - requiresS1S2N(threeDimArray2); - } + void testReturn3() { + // :: warning: argument.type.incompatible + requiresS1S2N(useReturn3(threeDimArray2)); + } - void useField3(@AinferSibling1 int @AinferSibling2 [][] x) { - threeDimArray2 = x; - } + // three dimensional array with annotations only on two array types, not innermost type - void testParam3(int[][] x) { - // :: warning: argument.type.incompatible - requiresS1S2N(x); - } + void requiresS2S1(int @AinferSibling2 [] @AinferSibling1 [] x) {} - void useParam3(@AinferSibling1 int @AinferSibling2 [][] x) { - testParam3(x); - } + int[][] threeDimArray3; - int[][] useReturn3(@AinferSibling1 int @AinferSibling2 [][] x) { - return x; - } + void testField4() { + // :: warning: argument.type.incompatible + requiresS2S1(threeDimArray3); + } - void testReturn3() { - // :: warning: argument.type.incompatible - requiresS1S2N(useReturn3(threeDimArray2)); - } + void useField4(int @AinferSibling2 [] @AinferSibling1 [] x) { + threeDimArray3 = x; + } - // three dimensional array with annotations only on two array types, not innermost type + void testParam4(int[][] x) { + // :: warning: argument.type.incompatible + requiresS2S1(x); + } - void requiresS2S1(int @AinferSibling2 [] @AinferSibling1 [] x) {} + void useParam4(int @AinferSibling2 [] @AinferSibling1 [] x) { + testParam4(x); + } - int[][] threeDimArray3; - - void testField4() { - // :: warning: argument.type.incompatible - requiresS2S1(threeDimArray3); - } - - void useField4(int @AinferSibling2 [] @AinferSibling1 [] x) { - threeDimArray3 = x; - } + int[][] useReturn4(int @AinferSibling2 [] @AinferSibling1 [] x) { + return x; + } - void testParam4(int[][] x) { - // :: warning: argument.type.incompatible - requiresS2S1(x); - } + void testReturn4() { + // :: warning: argument.type.incompatible + requiresS2S1(useReturn4(threeDimArray3)); + } - void useParam4(int @AinferSibling2 [] @AinferSibling1 [] x) { - testParam4(x); - } + // three-dimensional arrays with arguments in annotations - int[][] useReturn4(int @AinferSibling2 [] @AinferSibling1 [] x) { - return x; - } + void requiresSf1Sf2Sf3( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) {} - void testReturn4() { - // :: warning: argument.type.incompatible - requiresS2S1(useReturn4(threeDimArray3)); - } + int[][] threeDimArray4; - // three-dimensional arrays with arguments in annotations + void testField5() { + // :: warning: argument.type.incompatible + requiresSf1Sf2Sf3(threeDimArray4); + } + + void useField5( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) { + threeDimArray4 = x; + } + + void testParam5(int[][] x) { + // :: warning: argument.type.incompatible + requiresSf1Sf2Sf3(x); + } + + void useParam5( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) { + testParam5(x); + } + + int[][] useReturn5( + @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] + @AinferSiblingWithFields(value = {"test3"}) [] + x) { + return x; + } + + void testReturn5() { + // :: warning: argument.type.incompatible + requiresSf1Sf2Sf3(useReturn5(threeDimArray4)); + } - void requiresSf1Sf2Sf3( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) {} - - int[][] threeDimArray4; - - void testField5() { - // :: warning: argument.type.incompatible - requiresSf1Sf2Sf3(threeDimArray4); - } - - void useField5( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) { - threeDimArray4 = x; - } - - void testParam5(int[][] x) { - // :: warning: argument.type.incompatible - requiresSf1Sf2Sf3(x); - } - - void useParam5( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) { - testParam5(x); - } - - int[][] useReturn5( - @AinferSiblingWithFields(value = {"test1", "test1"}) int @AinferSiblingWithFields(value = {"test2", "test2"}) [] - @AinferSiblingWithFields(value = {"test3"}) [] - x) { - return x; - } - - void testReturn5() { - // :: warning: argument.type.incompatible - requiresSf1Sf2Sf3(useReturn5(threeDimArray4)); - } - - // three dimensional array with annotations from other hierarchies that ought to be preserved - - int[][] threeDimArray5; - - void testField6() { - // :: warning: argument.type.incompatible - requiresS1S2S1(threeDimArray5); - } - - void useField6( - @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { - threeDimArray5 = x; - } - - void testParam6(int[][] x) { - // :: warning: argument.type.incompatible - requiresS1S2S1(x); - } - - void useParam6( - @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { - testParam6(x); - } - - int[][] useReturn6( - @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { - return x; - } - - void testReturn6() { - // :: warning: argument.type.incompatible - requiresS1S2S1(useReturn6(threeDimArray)); - } - - // Shenanigans with lists + arrays; commented out annotations can't be inferred by either - // jaif or stub based WPI for now due to limitations in generics inference. - - List[] arrayofListsOfStringArrays; - - void testField7() { - // :: warning: argument.type.incompatible - requiresS1S2L(arrayofListsOfStringArrays); - } - - void requiresS1S2L( - @AinferSibling1 List @AinferSibling2 [] la) {} - - void useField7( - @AinferSibling1 List @AinferSibling2 [] x) { - arrayofListsOfStringArrays = x; - } - - void testParam7(List[] x) { - // :: warning: argument.type.incompatible - requiresS1S2L(x); - } - - void useParam7( - @AinferSibling1 List @AinferSibling2 [] x) { - testParam7(x); - } - - List[] useReturn7( - @AinferSibling1 List @AinferSibling2 [] x) { - return x; - } - - void testReturn7() { - // :: warning: argument.type.incompatible - requiresS1S2L(useReturn7(arrayofListsOfStringArrays)); - } + // three dimensional array with annotations from other hierarchies that ought to be preserved + + int[][] threeDimArray5; + + void testField6() { + // :: warning: argument.type.incompatible + requiresS1S2S1(threeDimArray5); + } + + void useField6( + @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { + threeDimArray5 = x; + } + + void testParam6(int[][] x) { + // :: warning: argument.type.incompatible + requiresS1S2S1(x); + } + + void useParam6( + @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { + testParam6(x); + } + + int[][] useReturn6( + @AinferSibling1 @Unique int @AinferSibling2 @NonLeaked [] @AinferSibling1 @MaybeAliased [] x) { + return x; + } + + void testReturn6() { + // :: warning: argument.type.incompatible + requiresS1S2S1(useReturn6(threeDimArray)); + } + + // Shenanigans with lists + arrays; commented out annotations can't be inferred by either + // jaif or stub based WPI for now due to limitations in generics inference. + + List[] arrayofListsOfStringArrays; + + void testField7() { + // :: warning: argument.type.incompatible + requiresS1S2L(arrayofListsOfStringArrays); + } + + void requiresS1S2L( + @AinferSibling1 List @AinferSibling2 [] la) {} + + void useField7( + @AinferSibling1 List @AinferSibling2 [] x) { + arrayofListsOfStringArrays = x; + } + + void testParam7(List[] x) { + // :: warning: argument.type.incompatible + requiresS1S2L(x); + } + + void useParam7( + @AinferSibling1 List @AinferSibling2 [] x) { + testParam7(x); + } + + List[] useReturn7( + @AinferSibling1 List @AinferSibling2 [] x) { + return x; + } + + void testReturn7() { + // :: warning: argument.type.incompatible + requiresS1S2L(useReturn7(arrayofListsOfStringArrays)); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java b/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java index 3c7a255935e..59fcc6cfd81 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java +++ b/checker/tests/ainfer-testchecker/non-annotated/MultidimensionalAnnotatedArray.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class MultidimensionalAnnotatedArray { - boolean[][] field = getArray(); + boolean[][] field = getArray(); - public boolean[] @AinferSibling1 [] getArray() { - return null; - } + public boolean[] @AinferSibling1 [] getArray() { + return null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java b/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java index 5f60e43981b..8b2935885cb 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java +++ b/checker/tests/ainfer-testchecker/non-annotated/NamedInnerClassInAnonymous.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class NamedInnerClassInAnonymous { - void test() { - Object o = - new NamedInnerClassInAnonymous() { - class NamedInner { - // The stub parser cannot parse inner classes, so stub-based WPI should - // not attempt to print a stub file for this. - public int myAinferSibling1() { - return ((@AinferSibling1 int) 0); - } - } - }; - } + void test() { + Object o = + new NamedInnerClassInAnonymous() { + class NamedInner { + // The stub parser cannot parse inner classes, so stub-based WPI should + // not attempt to print a stub file for this. + public int myAinferSibling1() { + return ((@AinferSibling1 int) 0); + } + } + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java b/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java index fcfbb134d7d..14d4870e2e5 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OptionGroup.java @@ -12,7 +12,7 @@ @Target(ElementType.FIELD) public @interface OptionGroup { - String value(); + String value(); - boolean unpublicized() default false; + boolean unpublicized() default false; } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java b/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java index 2388c683c13..916acb526af 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OtherAnnotations.java @@ -5,38 +5,38 @@ public class OtherAnnotations { - void requireAinferSibling1(@AinferSibling1 int a) {} + void requireAinferSibling1(@AinferSibling1 int a) {} - @Unique int x; + @Unique int x; - void assignX(@AinferSibling1 int y) { - x = y; - } + void assignX(@AinferSibling1 int y) { + x = y; + } - void useX() { - // :: warning: argument.type.incompatible - requireAinferSibling1(x); - } + void useX() { + // :: warning: argument.type.incompatible + requireAinferSibling1(x); + } - void methodWithAnnotatedParam(@Unique int z) { - // :: warning: argument.type.incompatible - requireAinferSibling1(z); - } + void methodWithAnnotatedParam(@Unique int z) { + // :: warning: argument.type.incompatible + requireAinferSibling1(z); + } - void useMethodWithAnnotatedParam(@AinferSibling1 int w) { - methodWithAnnotatedParam(w); - } + void useMethodWithAnnotatedParam(@AinferSibling1 int w) { + methodWithAnnotatedParam(w); + } - @AinferSibling1 int getAinferSibling1() { - return 5; - } + @AinferSibling1 int getAinferSibling1() { + return 5; + } - @Unique int getIntVal5() { - return getAinferSibling1(); - } + @Unique int getIntVal5() { + return getAinferSibling1(); + } - void useGetIntVal5() { - // :: warning: argument.type.incompatible - requireAinferSibling1(getIntVal5()); - } + void useGetIntVal5() { + // :: warning: argument.type.incompatible + requireAinferSibling1(getIntVal5()); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java b/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java index 05f14ad67f0..d1142070bb9 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OuterClassWithTypeParam.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class OuterClassWithTypeParam { - public class InnerClass { - Object o = (@AinferSibling1 Object) null; - } + public class InnerClass { + Object o = (@AinferSibling1 Object) null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java b/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java index c37872f75f5..f77316e4b52 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverloadedMethodsTest.java @@ -4,17 +4,17 @@ public class OverloadedMethodsTest { - String f; + String f; - String m1() { - return this.f; - } + String m1() { + return this.f; + } - String m1(String x) { - return getAinferSibling1(); - } + String m1(String x) { + return getAinferSibling1(); + } - @AinferSibling1 String getAinferSibling1() { - return null; - } + @AinferSibling1 String getAinferSibling1() { + return null; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java index 52a45306315..cd99468cf98 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTest.java @@ -2,56 +2,57 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class OverriddenMethodsTest { - static class OverriddenMethodsTestParent { - public void foo(@AinferSibling1 Object obj, @AinferSibling2 Object obj2) {} + static class OverriddenMethodsTestParent { + public void foo(@AinferSibling1 Object obj, @AinferSibling2 Object obj2) {} - public void bar(@AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} + public void bar( + @AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} - public void barz( - @AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} + public void barz( + @AinferSibling1 OverriddenMethodsTestParent this, @AinferSibling2 Object obj) {} - public void qux(Object obj1, Object obj2) { - // :: warning: (argument.type.incompatible) - foo(obj1, obj2); - } - - public void thud(Object obj1, Object obj2) { - // :: warning: (argument.type.incompatible) - foo(obj1, obj2); - } - } - - class OverriddenMethodsTestChild extends OverriddenMethodsTestParent { - @Override - public void foo(Object obj, Object obj2) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 Object o = obj; - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o2 = obj2; - } - - @Override - public void bar(Object obj) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 OverriddenMethodsTestChild child = this; - // :: warning: (assignment.type.incompatible) - @AinferSibling2 Object o = obj; - } - - @SuppressWarnings("all") - @Override - public void barz(Object obj) {} + public void qux(Object obj1, Object obj2) { + // :: warning: (argument.type.incompatible) + foo(obj1, obj2); + } - public void callbarz(Object obj) { - // If the @SuppressWarnings("all") on the overridden version of barz above is not - // respected, and the annotations on the receiver and parameter of barz are - // inferred, then the following call to barz will result in a method.invocation - // and an argument type checking errors. - barz(obj); + public void thud(Object obj1, Object obj2) { + // :: warning: (argument.type.incompatible) + foo(obj1, obj2); + } } - public void callqux(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { - qux(obj1, obj2); + class OverriddenMethodsTestChild extends OverriddenMethodsTestParent { + @Override + public void foo(Object obj, Object obj2) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 Object o = obj; + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o2 = obj2; + } + + @Override + public void bar(Object obj) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 OverriddenMethodsTestChild child = this; + // :: warning: (assignment.type.incompatible) + @AinferSibling2 Object o = obj; + } + + @SuppressWarnings("all") + @Override + public void barz(Object obj) {} + + public void callbarz(Object obj) { + // If the @SuppressWarnings("all") on the overridden version of barz above is not + // respected, and the annotations on the receiver and parameter of barz are + // inferred, then the following call to barz will result in a method.invocation + // and an argument type checking errors. + barz(obj); + } + + public void callqux(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { + qux(obj1, obj2); + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java index 919fcadbf1d..f8265f05529 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverriddenMethodsTestChildInAnotherCompilationUnit.java @@ -2,8 +2,8 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class OverriddenMethodsTestChildInAnotherCompilationUnit - extends OverriddenMethodsTest.OverriddenMethodsTestParent { - public void callthud(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { - thud(obj1, obj2); - } + extends OverriddenMethodsTest.OverriddenMethodsTestParent { + public void callthud(@AinferSibling1 Object obj1, @AinferSibling2 Object obj2) { + thud(obj1, obj2); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java b/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java index 527118ed04b..1f4fbd51f41 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java +++ b/checker/tests/ainfer-testchecker/non-annotated/OverrideIncompatiblePurity.java @@ -6,43 +6,43 @@ public class OverrideIncompatiblePurity { - interface MyInterface { - // WPI should not infer @Pure for this unless all implementations are pure. - void method(); - } + interface MyInterface { + // WPI should not infer @Pure for this unless all implementations are pure. + void method(); + } - class MyImplementation implements MyInterface { + class MyImplementation implements MyInterface { - int field; + int field; - @java.lang.Override - public void method() { - // Side effect! - field = 5; + @java.lang.Override + public void method() { + // Side effect! + field = 5; + } } - } - class Foo { + class Foo { - // This implementation is pure, but an overriding implementation in Bar is not. - String getA(int x) { - return "A"; + // This implementation is pure, but an overriding implementation in Bar is not. + String getA(int x) { + return "A"; + } } - } - class Bar extends Foo { + class Bar extends Foo { - String y; + String y; - // This implementation is neither deterministic nor side-effect free. - @java.lang.Override - String getA(int x) { - if (new Random().nextInt(5) > x) { - return "B"; - } else { - y = "C"; - return y; - } + // This implementation is neither deterministic nor side-effect free. + @java.lang.Override + String getA(int x) { + if (new Random().nextInt(5) > x) { + return "B"; + } else { + y = "C"; + return y; + } + } } - } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java b/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java index 0143c443a11..8aeed48d668 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ParameterInferenceTest.java @@ -3,21 +3,21 @@ public class ParameterInferenceTest { - void test1() { - @AinferParent int parent = (@AinferParent int) 0; - expectsParentNoSignature(parent); - } + void test1() { + @AinferParent int parent = (@AinferParent int) 0; + expectsParentNoSignature(parent); + } - void expectsParentNoSignature(int t) { - // :: warning: (assignment.type.incompatible) - @AinferParent int parent = t; - } + void expectsParentNoSignature(int t) { + // :: warning: (assignment.type.incompatible) + @AinferParent int parent = t; + } - void test2() { - @AinferTop int top = (@AinferTop int) 0; - // :: warning: (argument.type.incompatible) - expectsAinferTopNoSignature(top); - } + void test2() { + @AinferTop int top = (@AinferTop int) 0; + // :: warning: (argument.type.incompatible) + expectsAinferTopNoSignature(top); + } - void expectsAinferTopNoSignature(int t) {} + void expectsAinferTopNoSignature(int t) {} } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Planet.java b/checker/tests/ainfer-testchecker/non-annotated/Planet.java index aadb5a3d6b3..cacf7f10f50 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Planet.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Planet.java @@ -3,54 +3,54 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; @SuppressWarnings( - "value" // Do not generate Value Checker annotations, because IndexFileParser cannot handle + "value" // Do not generate Value Checker annotations, because IndexFileParser cannot handle // scientific notation. ) public enum Planet { - MERCURY(3.303e+23, 2.4397e6), - VENUS(4.869e+24, 6.0518e6), - EARTH(5.976e+24, 6.37814e6), - MARS(6.421e+23, 3.3972e6), - JUPITER(1.9e+27, 7.1492e7), - SATURN(5.688e+26, 6.0268e7), - URANUS(8.686e+25, 2.5559e7), - NEPTUNE(1.024e+26, 2.4746e7); - - public int foo; - - private final double mass; // in kilograms - private final double radius; // in meters - - Planet(double mass, double radius) { - this.mass = mass; - this.radius = radius; - } - - private double mass() { - return mass; - } - - private double radius() { - return radius; - } - - // universal gravitational constant (m3 kg-1 s-2) - public static final double G = 6.67300E-11; - - double surfaceGravity() { - return G * mass / (radius * radius); - } - - double surfaceWeight(double otherMass) { - return otherMass * surfaceGravity(); - } - - void test(@AinferSibling1 int x) { - foo = x; - } - - void test2() { - // :: warning: argument.type.incompatible - test(foo); - } + MERCURY(3.303e+23, 2.4397e6), + VENUS(4.869e+24, 6.0518e6), + EARTH(5.976e+24, 6.37814e6), + MARS(6.421e+23, 3.3972e6), + JUPITER(1.9e+27, 7.1492e7), + SATURN(5.688e+26, 6.0268e7), + URANUS(8.686e+25, 2.5559e7), + NEPTUNE(1.024e+26, 2.4746e7); + + public int foo; + + private final double mass; // in kilograms + private final double radius; // in meters + + Planet(double mass, double radius) { + this.mass = mass; + this.radius = radius; + } + + private double mass() { + return mass; + } + + private double radius() { + return radius; + } + + // universal gravitational constant (m3 kg-1 s-2) + public static final double G = 6.67300E-11; + + double surfaceGravity() { + return G * mass / (radius * radius); + } + + double surfaceWeight(double otherMass) { + return otherMass * surfaceGravity(); + } + + void test(@AinferSibling1 int x) { + foo = x; + } + + void test2() { + // :: warning: argument.type.incompatible + test(foo); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java b/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java index b6db26644d9..bb221671bc5 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/PublicFieldTest.java @@ -5,63 +5,63 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; public class PublicFieldTest { - public static int field1; // parent - public static int field2; // sib2 + public static int field1; // parent + public static int field2; // sib2 - public PublicFieldTest() { - field1 = getAinferSibling1(); - } + public PublicFieldTest() { + field1 = getAinferSibling1(); + } - void testPublicInference() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(field2); - // :: warning: (argument.type.incompatible) - expectsParent(field1); - // :: warning: (argument.type.incompatible) - expectsParent(field2); - } + void testPublicInference() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(field2); + // :: warning: (argument.type.incompatible) + expectsParent(field1); + // :: warning: (argument.type.incompatible) + expectsParent(field2); + } - void expectsBottom(@AinferBottom int t) {} + void expectsBottom(@AinferBottom int t) {} - void expectsAinferSibling1(@AinferSibling1 int t) {} + void expectsAinferSibling1(@AinferSibling1 int t) {} - void expectsAinferSibling2(@AinferSibling2 int t) {} + void expectsAinferSibling2(@AinferSibling2 int t) {} - void expectsAinferTop(@AinferTop int t) {} + void expectsAinferTop(@AinferTop int t) {} - void expectsParent(@AinferParent int t) {} + void expectsParent(@AinferParent int t) {} - @AinferSibling1 int getAinferSibling1() { - return (@AinferSibling1 int) 0; - } + @AinferSibling1 int getAinferSibling1() { + return (@AinferSibling1 int) 0; + } - class AnotherClass { + class AnotherClass { - int innerField; + int innerField; - public AnotherClass() { - PublicFieldTest.field1 = getAinferSibling2(); - PublicFieldTest.field2 = getAinferSibling2(); - innerField = getAinferSibling2(); - } + public AnotherClass() { + PublicFieldTest.field1 = getAinferSibling2(); + PublicFieldTest.field2 = getAinferSibling2(); + innerField = getAinferSibling2(); + } - void innerFieldTest() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(innerField); - } + void innerFieldTest() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(innerField); + } - @AinferBottom int getBottom() { - return (@AinferBottom int) 0; - } + @AinferBottom int getBottom() { + return (@AinferBottom int) 0; + } - @AinferTop int getAinferTop() { - return (@AinferTop int) 0; - } + @AinferTop int getAinferTop() { + return (@AinferTop int) 0; + } - @AinferSibling2 int getAinferSibling2() { - return (@AinferSibling2 int) 0; - } + @AinferSibling2 int getAinferSibling2() { + return (@AinferSibling2 int) 0; + } - void expectsAinferSibling2(@AinferSibling2 int t) {} - } + void expectsAinferSibling2(@AinferSibling2 int t) {} + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Purity.java b/checker/tests/ainfer-testchecker/non-annotated/Purity.java index f2193ce6d16..4d6b6851f8a 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Purity.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Purity.java @@ -6,24 +6,24 @@ import org.checkerframework.dataflow.qual.*; interface PureFunc { - @Pure - String doNothing(); + @Pure + String doNothing(); } class TestPure1 { - static String myMethod() { - return ""; - } + static String myMethod() { + return ""; + } - @Pure - static String myPureMethod() { - return ""; - } + @Pure + static String myPureMethod() { + return ""; + } - void context() { - PureFunc f1 = TestPure1::myPureMethod; - // :: warning: (purity.methodref) - PureFunc f2 = TestPure1::myMethod; - } + void context() { + PureFunc f1 = TestPure1::myPureMethod; + // :: warning: (purity.methodref) + PureFunc f2 = TestPure1::myMethod; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java b/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java index 8fd44468de9..a4700f65e89 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/RequiresQualifierTest.java @@ -6,49 +6,49 @@ class RequiresQualifierTest { - @AinferTop int field1; - @AinferTop int field2; - - @AinferTop int top; - @AinferParent int parent; - @AinferSibling1 int sibling1; - @AinferSibling2 int sibling2; - @AinferBottom int bottom; - - void field1IsParent() { - // :: warning: (assignment.type.incompatible) - @AinferParent int x = field1; - } - - void field1IsAinferSibling2() { - // :: warning: (assignment.type.incompatible) - @AinferSibling2 int x = field1; - } - - void parentIsAinferSibling1() { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 int x = parent; - } - - void noRequirements() {} - - void client2(@AinferParent int p) { - field1 = p; - field1IsParent(); - } - - void client1() { - noRequirements(); - - field1 = parent; - field1IsParent(); - - field1 = sibling2; - field1IsAinferSibling2(); - field1 = bottom; - field1IsAinferSibling2(); - - parent = sibling1; - parentIsAinferSibling1(); - } + @AinferTop int field1; + @AinferTop int field2; + + @AinferTop int top; + @AinferParent int parent; + @AinferSibling1 int sibling1; + @AinferSibling2 int sibling2; + @AinferBottom int bottom; + + void field1IsParent() { + // :: warning: (assignment.type.incompatible) + @AinferParent int x = field1; + } + + void field1IsAinferSibling2() { + // :: warning: (assignment.type.incompatible) + @AinferSibling2 int x = field1; + } + + void parentIsAinferSibling1() { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 int x = parent; + } + + void noRequirements() {} + + void client2(@AinferParent int p) { + field1 = p; + field1IsParent(); + } + + void client1() { + noRequirements(); + + field1 = parent; + field1IsParent(); + + field1 = sibling2; + field1IsAinferSibling2(); + field1 = bottom; + field1IsAinferSibling2(); + + parent = sibling1; + parentIsAinferSibling1(); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java b/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java index 87425884a85..c5e55eaa80b 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/StringConcatenationTest.java @@ -2,31 +2,31 @@ public class StringConcatenationTest { - private String options_str; - private String options_str2; - private String options_str3; + private String options_str; + private String options_str2; + private String options_str3; - void foo() { - options_str = getAinferSibling1(); + void foo() { + options_str = getAinferSibling1(); - // Addition lubs the results, so these have no effect on the (default) type of the fields. - // Also, the following two lines should behave identically. - options_str2 += getAinferSibling1(); - options_str3 = options_str3 + getAinferSibling1(); - } + // Addition lubs the results, so these have no effect on the (default) type of the fields. + // Also, the following two lines should behave identically. + options_str2 += getAinferSibling1(); + options_str3 = options_str3 + getAinferSibling1(); + } - void test() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(options_str); - // :: warning: (argument.type.incompatible) - expectsAinferSibling1(options_str2); - expectsAinferSibling1(options_str3); - } + void test() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(options_str); + // :: warning: (argument.type.incompatible) + expectsAinferSibling1(options_str2); + expectsAinferSibling1(options_str3); + } - void expectsAinferSibling1(@AinferSibling1 String t) {} + void expectsAinferSibling1(@AinferSibling1 String t) {} - @SuppressWarnings("cast.unsafe") - @AinferSibling1 String getAinferSibling1() { - return (@AinferSibling1 String) " "; - } + @SuppressWarnings("cast.unsafe") + @AinferSibling1 String getAinferSibling1() { + return (@AinferSibling1 String) " "; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java b/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java index dc0b1a4fa24..a46b67df0c0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java +++ b/checker/tests/ainfer-testchecker/non-annotated/Tempvars.java @@ -1,8 +1,8 @@ // test case for https://github.com/typetools/checker-framework/issues/3442 public class Tempvars { - static { - int i = 0; - i++; - } + static { + int i = 0; + i++; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java index 0e26718817a..b2991170f48 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1InferenceTest.java @@ -6,8 +6,8 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; public class TreatAsSibling1InferenceTest { - public void test(Object iShouldBeTreatedAsSibling1) { - // :: warning: (assignment.type.incompatible) - @AinferSibling1 Object x = iShouldBeTreatedAsSibling1; - } + public void test(Object iShouldBeTreatedAsSibling1) { + // :: warning: (assignment.type.incompatible) + @AinferSibling1 Object x = iShouldBeTreatedAsSibling1; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java index e8f84538794..eef11cac87e 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TreatAsSibling1Test.java @@ -5,7 +5,7 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTreatAsSibling1; public class TreatAsSibling1Test { - public void test(@AinferTreatAsSibling1 Object y) { - @AinferSibling1 Object x = y; - } + public void test(@AinferTreatAsSibling1 Object y) { + @AinferSibling1 Object x = y; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java b/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java index aaec92546dc..84604f2b7da 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TwoMethodsSameName.java @@ -6,20 +6,20 @@ public class TwoMethodsSameName { - void test(int x, int y) { - // :: warning: assignment.type.incompatible - @AinferSibling1 int x1 = x; - // :: warning: assignment.type.incompatible - @AinferSibling2 int y1 = y; - } + void test(int x, int y) { + // :: warning: assignment.type.incompatible + @AinferSibling1 int x1 = x; + // :: warning: assignment.type.incompatible + @AinferSibling2 int y1 = y; + } - void test(int z) { - // :: warning: assignment.type.incompatible - @AinferSibling2 int z1 = z; - } + void test(int z) { + // :: warning: assignment.type.incompatible + @AinferSibling2 int z1 = z; + } - void uses(@AinferSibling1 int a, @AinferSibling2 int b) { - test(a, b); - test(b); - } + void uses(@AinferSibling1 int a, @AinferSibling2 int b) { + test(a, b); + test(b); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java index dcdd80de065..041d16d9fc8 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest.java @@ -4,29 +4,30 @@ public class TypeVariablesTest { - // This method's parameter type should not be updated by the whole-program inference. - // Even though there is only one call to foo with argument of type @AinferBottom, - // the method has in its signature that the parameter is a subtype of @AinferParent, - // therefore no annotation should be added. - public static - TypeVariablesTest foo(A a, B b) { - return null; - } + // This method's parameter type should not be updated by the whole-program inference. + // Even though there is only one call to foo with argument of type @AinferBottom, + // the method has in its signature that the parameter is a subtype of @AinferParent, + // therefore no annotation should be added. + public static + TypeVariablesTest foo(A a, B b) { + return null; + } - public static void typeVarWithTypeVarUB(A a, B b) {} + public static void typeVarWithTypeVarUB( + A a, B b) {} - void test1() { - @SuppressWarnings("cast.unsafe") - @AinferParent String s = (@AinferParent String) ""; - foo(getAinferSibling1(), getAinferSibling2()); - typeVarWithTypeVarUB(getAinferSibling1(), getAinferSibling2()); - } + void test1() { + @SuppressWarnings("cast.unsafe") + @AinferParent String s = (@AinferParent String) ""; + foo(getAinferSibling1(), getAinferSibling2()); + typeVarWithTypeVarUB(getAinferSibling1(), getAinferSibling2()); + } - static @AinferSibling1 int getAinferSibling1() { - return 0; - } + static @AinferSibling1 int getAinferSibling1() { + return 0; + } - static @AinferSibling2 int getAinferSibling2() { - return 0; - } + static @AinferSibling2 int getAinferSibling2() { + return 0; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java index b9e1c00f149..4e814159d58 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest2.java @@ -3,9 +3,9 @@ public class TypeVariablesTest2 { - Map map = new HashMap<>(); + Map map = new HashMap<>(); - public V getValue(K key) { - return map.get(key); - } + public V getValue(K key) { + return map.get(key); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java index 233acefde83..3705e4fac8d 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java +++ b/checker/tests/ainfer-testchecker/non-annotated/TypeVariablesTest3.java @@ -2,22 +2,22 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class TypeVariablesTest3<@AinferSibling1 T extends @AinferSibling1 Object> { - public @AinferSibling2 T sibling2; - public @AinferSibling1 T sibling1; + public @AinferSibling2 T sibling2; + public @AinferSibling1 T sibling1; - public T tField; + public T tField; - void foo(T param) { - // :: warning: (assignment.type.incompatible) - param = sibling2; - } + void foo(T param) { + // :: warning: (assignment.type.incompatible) + param = sibling2; + } - void baz(T param) { - param = sibling1; - } + void baz(T param) { + param = sibling1; + } - void bar(@AinferSibling2 T param) { - // :: warning: (assignment.type.incompatible) - tField = param; - } + void bar(@AinferSibling2 T param) { + // :: warning: (assignment.type.incompatible) + tField = param; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java b/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java index 13985bfd6fb..c387ad33643 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java +++ b/checker/tests/ainfer-testchecker/non-annotated/UsesAnnotationAsClass.java @@ -5,10 +5,10 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling2; public class UsesAnnotationAsClass { - public static String test(@AinferSibling2 AinferSibling1 annotation) { - String value = annotation.value(); - // goal of this is to trigger inference for AinferSibling1's definition. - String anotherValue = annotation.anotherValue(); - return value + anotherValue; - } + public static String test(@AinferSibling2 AinferSibling1 annotation) { + String value = annotation.value(); + // goal of this is to trigger inference for AinferSibling1's definition. + String anotherValue = annotation.anotherValue(); + return value + anotherValue; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java b/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java index 4f9925e30e3..b32f587f931 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java +++ b/checker/tests/ainfer-testchecker/non-annotated/UsesAnonymous.java @@ -3,35 +3,35 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferTop; public class UsesAnonymous { - void method() { - Anonymous a = - new Anonymous() { - int innerField; + void method() { + Anonymous a = + new Anonymous() { + int innerField; - public void method2() { - Anonymous.field1 = getAinferSibling2(); - Anonymous.field2 = getAinferSibling2(); - innerField = getAinferSibling2(); - } + public void method2() { + Anonymous.field1 = getAinferSibling2(); + Anonymous.field2 = getAinferSibling2(); + innerField = getAinferSibling2(); + } - void innerFieldTest() { - // :: warning: (argument.type.incompatible) - expectsAinferSibling2(innerField); - } + void innerFieldTest() { + // :: warning: (argument.type.incompatible) + expectsAinferSibling2(innerField); + } - @AinferBottom int getBottom() { - return (@AinferBottom int) 0; - } + @AinferBottom int getBottom() { + return (@AinferBottom int) 0; + } - @AinferTop int getAinferTop() { - return (@AinferTop int) 0; - } + @AinferTop int getAinferTop() { + return (@AinferTop int) 0; + } - @AinferSibling2 int getAinferSibling2() { - return (@AinferSibling2 int) 0; - } + @AinferSibling2 int getAinferSibling2() { + return (@AinferSibling2 int) 0; + } - void expectsAinferSibling2(@AinferSibling2 int t) {} - }; - } + void expectsAinferSibling2(@AinferSibling2 int t) {} + }; + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java b/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java index 1adb9d6e2d0..0b8d48162ba 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java +++ b/checker/tests/ainfer-testchecker/non-annotated/ValueCheck.java @@ -7,20 +7,20 @@ public class ValueCheck { - // return value should be @AinferSibling1 @IntVal(5) int - int getAinferSibling1withValue5() { - return ((@AinferSibling1 int) 5); - } + // return value should be @AinferSibling1 @IntVal(5) int + int getAinferSibling1withValue5() { + return ((@AinferSibling1 int) 5); + } - void requireAinferSibling1(@AinferSibling1 int x) {} + void requireAinferSibling1(@AinferSibling1 int x) {} - void requireIntVal5(@IntVal(5) int x) {} + void requireIntVal5(@IntVal(5) int x) {} - void test() { - int x = getAinferSibling1withValue5(); - // :: warning: argument.type.incompatible - requireAinferSibling1(x); - // :: warning: argument.type.incompatible - requireIntVal5(x); - } + void test() { + int x = getAinferSibling1withValue5(); + // :: warning: argument.type.incompatible + requireAinferSibling1(x); + // :: warning: argument.type.incompatible + requireIntVal5(x); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java b/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java index 2ae50ad8b83..a16423d50f0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java +++ b/checker/tests/ainfer-testchecker/non-annotated/VarargsTest.java @@ -3,31 +3,31 @@ public class VarargsTest { - static void m1Varargs(Object... args) {} + static void m1Varargs(Object... args) {} - static void m1ArrArgs(Object[] args) {} + static void m1ArrArgs(Object[] args) {} - static void m2Varargs(Object... args) {} + static void m2Varargs(Object... args) {} - static void m2ArrArgs(Object[] args) {} + static void m2ArrArgs(Object[] args) {} - static void m3Varargs(Object... args) {} + static void m3Varargs(Object... args) {} - static void m3ArrArgs(Object[] args) {} + static void m3ArrArgs(Object[] args) {} - static @AinferSibling1 Object @AinferParent [] p_s1_array; - static @AinferSibling1 Object @AinferSibling1 [] s1_s1_array; + static @AinferSibling1 Object @AinferParent [] p_s1_array; + static @AinferSibling1 Object @AinferSibling1 [] s1_s1_array; - static void client() { - m1Varargs(s1_s1_array); - m1ArrArgs(s1_s1_array); + static void client() { + m1Varargs(s1_s1_array); + m1ArrArgs(s1_s1_array); - m2Varargs(p_s1_array); - m2ArrArgs(p_s1_array); + m2Varargs(p_s1_array); + m2ArrArgs(p_s1_array); - m3Varargs(s1_s1_array); - m3ArrArgs(s1_s1_array); - m3Varargs(p_s1_array); - m3ArrArgs(p_s1_array); - } + m3Varargs(s1_s1_array); + m3ArrArgs(s1_s1_array); + m3Varargs(p_s1_array); + m3ArrArgs(p_s1_array); + } } diff --git a/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java b/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java index c3245e1e39d..122dc6e3af0 100644 --- a/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java +++ b/checker/tests/ainfer-testchecker/non-annotated/WildcardReturn.java @@ -6,13 +6,13 @@ import java.util.stream.Collectors; public class WildcardReturn { - public Set getCredentialIdsForUsername(String username) { - return getRegistrationsByUsername(username).stream() - .map(registration -> registration.toString()) - .collect(Collectors.toSet()); - } + public Set getCredentialIdsForUsername(String username) { + return getRegistrationsByUsername(username).stream() + .map(registration -> registration.toString()) + .collect(Collectors.toSet()); + } - public Collection getRegistrationsByUsername(String username) { - return null; - } + public Collection getRegistrationsByUsername(String username) { + return null; + } } diff --git a/checker/tests/calledmethods-autovalue/Animal.java b/checker/tests/calledmethods-autovalue/Animal.java index a52ebdd3445..424bcc82147 100644 --- a/checker/tests/calledmethods-autovalue/Animal.java +++ b/checker/tests/calledmethods-autovalue/Animal.java @@ -1,86 +1,88 @@ import com.google.auto.value.AutoValue; -import java.util.Optional; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; +import java.util.Optional; + /** * Adapted from the standard AutoValue example code: * https://github.com/google/auto/blob/master/value/userguide/builders.md */ @AutoValue abstract class Animal { - abstract String name(); + abstract String name(); - abstract @Nullable String habitat(); + abstract @Nullable String habitat(); - abstract int numberOfLegs(); + abstract int numberOfLegs(); - // does not need to be explicitly set - abstract Optional extra(); + // does not need to be explicitly set + abstract Optional extra(); - public String getStr() { - return "str"; - } + public String getStr() { + return "str"; + } - public abstract Builder toBuilder(); + public abstract Builder toBuilder(); - static Builder builder() { - return new AutoValue_Animal.Builder(); - } + static Builder builder() { + return new AutoValue_Animal.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract Builder setNumberOfLegs(int value); + abstract Builder setNumberOfLegs(int value); - abstract Builder setHabitat(String value); + abstract Builder setHabitat(String value); - abstract Builder setExtra(String value); + abstract Builder setExtra(String value); - abstract Animal build(); - } + abstract Animal build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.build(); + } - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHabitat("jungle"); - b.build(); - } + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHabitat("jungle"); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().setName("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().setName("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).build(); - } + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).build(); + } - public static void buildWithToBuilder() { - Animal a1 = builder().setName("Jim").setNumberOfLegs(7).build(); - a1.toBuilder().build(); - } + public static void buildWithToBuilder() { + Animal a1 = builder().setName("Jim").setNumberOfLegs(7).build(); + a1.toBuilder().build(); + } - public static void buildSomethingRightFluentWithLocal() { - Builder b = builder(); - b.setName("Jim").setNumberOfLegs(7); - b.build(); - } + public static void buildSomethingRightFluentWithLocal() { + Builder b = builder(); + b.setName("Jim").setNumberOfLegs(7); + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/AnimalNoSet.java b/checker/tests/calledmethods-autovalue/AnimalNoSet.java index 6276ef97ca6..2e011578f77 100644 --- a/checker/tests/calledmethods-autovalue/AnimalNoSet.java +++ b/checker/tests/calledmethods-autovalue/AnimalNoSet.java @@ -1,4 +1,5 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @@ -8,60 +9,60 @@ */ @AutoValue abstract class AnimalNoSet { - abstract String name(); + abstract String name(); - abstract @Nullable String habitat(); + abstract @Nullable String habitat(); - abstract int numberOfLegs(); + abstract int numberOfLegs(); - public String getStr() { - return "str"; - } + public String getStr() { + return "str"; + } - static Builder builder() { - return new AutoValue_AnimalNoSet.Builder(); - } + static Builder builder() { + return new AutoValue_AnimalNoSet.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder name(String value); + abstract Builder name(String value); - abstract Builder numberOfLegs(int value); + abstract Builder numberOfLegs(int value); - abstract Builder habitat(String value); + abstract Builder habitat(String value); - abstract AnimalNoSet build(); - } + abstract AnimalNoSet build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.name("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.name("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.name("Frank"); - b.numberOfLegs(4); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.name("Frank"); + b.numberOfLegs(4); + b.build(); + } - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.name("Frank"); - b.numberOfLegs(4); - b.habitat("jungle"); - b.build(); - } + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.name("Frank"); + b.numberOfLegs(4); + b.habitat("jungle"); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().name("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().name("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().name("Jim").numberOfLegs(7).build(); - } + public static void buildSomethingRightFluent() { + builder().name("Jim").numberOfLegs(7).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/BuilderGetter.java b/checker/tests/calledmethods-autovalue/BuilderGetter.java index d4fdd97848a..15b58d870fb 100644 --- a/checker/tests/calledmethods-autovalue/BuilderGetter.java +++ b/checker/tests/calledmethods-autovalue/BuilderGetter.java @@ -1,36 +1,37 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class BuilderGetter { - public abstract String name(); + public abstract String name(); - static Builder builder() { - return new AutoValue_BuilderGetter.Builder(); - } + static Builder builder() { + return new AutoValue_BuilderGetter.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String name); + abstract Builder setName(String name); - abstract String name(); + abstract String name(); - abstract BuilderGetter build(); - } + abstract BuilderGetter build(); + } - static void correct() { - Builder b = builder(); - b.setName("Phil"); - b.build(); - } + static void correct() { + Builder b = builder(); + b.setName("Phil"); + b.build(); + } - static void wrong() { - Builder b = builder(); - b.name(); - // :: error: finalizer.invocation.invalid - b.build(); - } + static void wrong() { + Builder b = builder(); + b.name(); + // :: error: finalizer.invocation.invalid + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/CallWithinBuilder.java b/checker/tests/calledmethods-autovalue/CallWithinBuilder.java index 6cc3ca4c860..0760d00789d 100644 --- a/checker/tests/calledmethods-autovalue/CallWithinBuilder.java +++ b/checker/tests/calledmethods-autovalue/CallWithinBuilder.java @@ -1,35 +1,37 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; -import java.util.Collection; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; +import java.util.Collection; + @AutoValue abstract class CallWithinBuilder { - public abstract ImmutableList names(); + public abstract ImmutableList names(); - static Builder builder() { - return new AutoValue_CallWithinBuilder.Builder(); - } + static Builder builder() { + return new AutoValue_CallWithinBuilder.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract ImmutableList.Builder namesBuilder(); + abstract ImmutableList.Builder namesBuilder(); - public Builder addName(String name) { - namesBuilder().add(name); - return this; - } + public Builder addName(String name) { + namesBuilder().add(name); + return this; + } - public Builder addNames(Collection names) { - for (String n : names) { - addName(n); - } - return this; - } + public Builder addNames(Collection names) { + for (String n : names) { + addName(n); + } + return this; + } - abstract CallWithinBuilder build(); - } + abstract CallWithinBuilder build(); + } } diff --git a/checker/tests/calledmethods-autovalue/FooParcelable.java b/checker/tests/calledmethods-autovalue/FooParcelable.java index a43d573a56a..18b7453af74 100644 --- a/checker/tests/calledmethods-autovalue/FooParcelable.java +++ b/checker/tests/calledmethods-autovalue/FooParcelable.java @@ -1,6 +1,7 @@ -import android.os.Parcelable; import com.google.auto.value.AutoValue; +import android.os.Parcelable; + /** * Test for support of AutoValue Parcel extension. This test currently passes, but only because we * ignore cases where we cannot find a matching setter for a method we think corresponds to an @@ -8,29 +9,29 @@ */ @AutoValue abstract class FooParcelable implements Parcelable { - abstract String name(); + abstract String name(); - static Builder builder() { - return new AutoValue_FooParcelable.Builder(); - } + static Builder builder() { + return new AutoValue_FooParcelable.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract FooParcelable build(); - } + abstract FooParcelable build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GetAndIs.java b/checker/tests/calledmethods-autovalue/GetAndIs.java index 6eeca1f536e..f52a4ce9b8b 100644 --- a/checker/tests/calledmethods-autovalue/GetAndIs.java +++ b/checker/tests/calledmethods-autovalue/GetAndIs.java @@ -1,47 +1,48 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class GetAndIs { - abstract String get(); + abstract String get(); - abstract boolean is(); + abstract boolean is(); - static Builder builder() { - return new AutoValue_GetAndIs.Builder(); - } + static Builder builder() { + return new AutoValue_GetAndIs.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setGet(String value); + abstract Builder setGet(String value); - abstract Builder setIs(boolean value); + abstract Builder setIs(boolean value); - abstract GetAndIs build(); - } + abstract GetAndIs build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setGet("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setGet("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setGet("Frank"); - b.setIs(false); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setGet("Frank"); + b.setIs(false); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().setGet("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().setGet("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setGet("Jim").setIs(true).build(); - } + public static void buildSomethingRightFluent() { + builder().setGet("Jim").setIs(true).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GetAnimal.java b/checker/tests/calledmethods-autovalue/GetAnimal.java index c7a3cbaa881..4b83cc4059e 100644 --- a/checker/tests/calledmethods-autovalue/GetAnimal.java +++ b/checker/tests/calledmethods-autovalue/GetAnimal.java @@ -1,4 +1,5 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @@ -8,62 +9,62 @@ */ @AutoValue abstract class GetAnimal { - abstract String getName(); + abstract String getName(); - abstract @Nullable String getHabitat(); + abstract @Nullable String getHabitat(); - abstract int getNumberOfLegs(); + abstract int getNumberOfLegs(); - abstract boolean isHasArms(); + abstract boolean isHasArms(); - static Builder builder() { - return new AutoValue_GetAnimal.Builder(); - } + static Builder builder() { + return new AutoValue_GetAnimal.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract Builder setNumberOfLegs(int value); + abstract Builder setNumberOfLegs(int value); - abstract Builder setHabitat(String value); + abstract Builder setHabitat(String value); - abstract Builder setHasArms(boolean b); + abstract Builder setHasArms(boolean b); - abstract GetAnimal build(); - } + abstract GetAnimal build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - // :: error: finalizer.invocation.invalid - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + // :: error: finalizer.invocation.invalid + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHasArms(true); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHasArms(true); + b.build(); + } - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHabitat("jungle"); - b.setHasArms(true); - b.build(); - } + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHabitat("jungle"); + b.setHasArms(true); + b.build(); + } - public static void buildSomethingWrongFluent() { - // :: error: finalizer.invocation.invalid - builder().setName("Frank").build(); - } + public static void buildSomethingWrongFluent() { + // :: error: finalizer.invocation.invalid + builder().setName("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).setHasArms(false).build(); - } + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).setHasArms(false).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GuavaImmutable.java b/checker/tests/calledmethods-autovalue/GuavaImmutable.java index b66cba4ec10..1db3f3b23ba 100644 --- a/checker/tests/calledmethods-autovalue/GuavaImmutable.java +++ b/checker/tests/calledmethods-autovalue/GuavaImmutable.java @@ -1,31 +1,32 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class GuavaImmutable { - public abstract ImmutableList names(); + public abstract ImmutableList names(); - static Builder builder() { - return new AutoValue_GuavaImmutable.Builder(); - } + static Builder builder() { + return new AutoValue_GuavaImmutable.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder names(ImmutableList value); + abstract Builder names(ImmutableList value); - abstract GuavaImmutable build(); - } + abstract GuavaImmutable build(); + } - public static void buildSomethingWrong() { - // :: error: finalizer.invocation.invalid - builder().build(); - } + public static void buildSomethingWrong() { + // :: error: finalizer.invocation.invalid + builder().build(); + } - public static void buildSomethingRight() { - builder().names(ImmutableList.of()).build(); - } + public static void buildSomethingRight() { + builder().names(ImmutableList.of()).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java b/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java index dbf258abe6b..88aa9f654fc 100644 --- a/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java +++ b/checker/tests/calledmethods-autovalue/GuavaImmutablePropBuilder.java @@ -1,27 +1,28 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class GuavaImmutablePropBuilder { - public abstract ImmutableList names(); + public abstract ImmutableList names(); - static Builder builder() { - return new AutoValue_GuavaImmutablePropBuilder.Builder(); - } + static Builder builder() { + return new AutoValue_GuavaImmutablePropBuilder.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract ImmutableList.Builder namesBuilder(); + abstract ImmutableList.Builder namesBuilder(); - abstract GuavaImmutablePropBuilder build(); - } + abstract GuavaImmutablePropBuilder build(); + } - public static void buildSomething() { - // don't need to explicitly set the names - builder().build(); - } + public static void buildSomething() { + // don't need to explicitly set the names + builder().build(); + } } diff --git a/checker/tests/calledmethods-autovalue/Inheritance.java b/checker/tests/calledmethods-autovalue/Inheritance.java index aefa1bb4bf5..fb5643d95b7 100644 --- a/checker/tests/calledmethods-autovalue/Inheritance.java +++ b/checker/tests/calledmethods-autovalue/Inheritance.java @@ -1,47 +1,48 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.common.returnsreceiver.qual.This; public class Inheritance { - static interface Props { - String name(); + static interface Props { + String name(); - String somethingElse(); + String somethingElse(); - abstract class Builder> { - public abstract @This B name(String value); + abstract class Builder> { + public abstract @This B name(String value); + } } - } - @AutoValue - abstract static class PropHolder implements Props { - abstract int size(); + @AutoValue + abstract static class PropHolder implements Props { + abstract int size(); - @Override - public String somethingElse() { - return "hi"; - } + @Override + public String somethingElse() { + return "hi"; + } + + static Builder builder() { + return new AutoValue_Inheritance_PropHolder.Builder(); + } - static Builder builder() { - return new AutoValue_Inheritance_PropHolder.Builder(); + @AutoValue.Builder + public abstract static class Builder extends Props.Builder { + public abstract Builder size(int value); + + public abstract PropHolder build(); + } } - @AutoValue.Builder - public abstract static class Builder extends Props.Builder { - public abstract Builder size(int value); + static void correct() { + PropHolder.Builder b = PropHolder.builder(); + b.name("Manu").size(1).build(); + } - public abstract PropHolder build(); + static void wrong() { + PropHolder.Builder b = PropHolder.builder(); + // :: error: finalizer.invocation.invalid + b.size(1).build(); } - } - - static void correct() { - PropHolder.Builder b = PropHolder.builder(); - b.name("Manu").size(1).build(); - } - - static void wrong() { - PropHolder.Builder b = PropHolder.builder(); - // :: error: finalizer.invocation.invalid - b.size(1).build(); - } } diff --git a/checker/tests/calledmethods-autovalue/IsPreserved.java b/checker/tests/calledmethods-autovalue/IsPreserved.java index f8fa2694bcf..697e1ad72cd 100644 --- a/checker/tests/calledmethods-autovalue/IsPreserved.java +++ b/checker/tests/calledmethods-autovalue/IsPreserved.java @@ -1,41 +1,42 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class IsPreserved { - abstract String name(); + abstract String name(); - abstract String getAddress(); + abstract String getAddress(); - abstract boolean isPresent(); + abstract boolean isPresent(); - static Builder builder() { - return new AutoValue_IsPreserved.Builder(); - } + static Builder builder() { + return new AutoValue_IsPreserved.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder name(String val); + abstract Builder name(String val); - abstract Builder getAddress(String val); + abstract Builder getAddress(String val); - abstract Builder isPresent(boolean value); + abstract Builder isPresent(boolean value); - abstract IsPreserved build(); - } + abstract IsPreserved build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.name("Frank"); - b.getAddress("something"); - b.isPresent(true); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.name("Frank"); + b.getAddress("something"); + b.isPresent(true); + b.build(); + } - public static void buildSomethingRightFluent() { - builder().name("Bill").getAddress("something").isPresent(false).build(); - } + public static void buildSomethingRightFluent() { + builder().name("Bill").getAddress("something").isPresent(false).build(); + } } diff --git a/checker/tests/calledmethods-autovalue/NonAVBuilder.java b/checker/tests/calledmethods-autovalue/NonAVBuilder.java index 7f9dc0313b0..f869e11ddb1 100644 --- a/checker/tests/calledmethods-autovalue/NonAVBuilder.java +++ b/checker/tests/calledmethods-autovalue/NonAVBuilder.java @@ -1,18 +1,19 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class NonAVBuilder { - abstract String name(); + abstract String name(); - public Builder toBuilder() { - return new Builder(this); - } + public Builder toBuilder() { + return new Builder(this); + } - // NOT an AutoValue builder - static final class Builder { + // NOT an AutoValue builder + static final class Builder { - Builder(NonAVBuilder b) {} - } + Builder(NonAVBuilder b) {} + } } diff --git a/checker/tests/calledmethods-autovalue/NonBuildName.java b/checker/tests/calledmethods-autovalue/NonBuildName.java index 1bd2a58f370..6d1dbc2cdca 100644 --- a/checker/tests/calledmethods-autovalue/NonBuildName.java +++ b/checker/tests/calledmethods-autovalue/NonBuildName.java @@ -1,33 +1,34 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class NonBuildName { - public abstract String name(); + public abstract String name(); - static Builder builder() { - return new AutoValue_NonBuildName.Builder(); - } + static Builder builder() { + return new AutoValue_NonBuildName.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String name); + abstract Builder setName(String name); - abstract NonBuildName makeIt(); - } + abstract NonBuildName makeIt(); + } - static void correct() { - Builder b = builder(); - b.setName("Phil"); - b.makeIt(); - } + static void correct() { + Builder b = builder(); + b.setName("Phil"); + b.makeIt(); + } - static void wrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.makeIt(); - } + static void wrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.makeIt(); + } } diff --git a/checker/tests/calledmethods-autovalue/Parcel.java b/checker/tests/calledmethods-autovalue/Parcel.java index 81203608e62..3015789cfc2 100644 --- a/checker/tests/calledmethods-autovalue/Parcel.java +++ b/checker/tests/calledmethods-autovalue/Parcel.java @@ -3,9 +3,9 @@ /** stub to avoid bringing in Android dependence */ public final class Parcel { - public String readString() { - return ""; - } + public String readString() { + return ""; + } - public void writeString(String val) {} + public void writeString(String val) {} } diff --git a/checker/tests/calledmethods-autovalue/Parcelable.java b/checker/tests/calledmethods-autovalue/Parcelable.java index 7b9fb4fa227..f01a75dd8df 100644 --- a/checker/tests/calledmethods-autovalue/Parcelable.java +++ b/checker/tests/calledmethods-autovalue/Parcelable.java @@ -2,14 +2,14 @@ /** stub to avoid bringing in Android dependence */ public interface Parcelable { - public interface Creator { + public interface Creator { - public T createFromParcel(Parcel source); + public T createFromParcel(Parcel source); - public T[] newArray(int size); - } + public T[] newArray(int size); + } - public int describeContents(); + public int describeContents(); - public void writeToParcel(Parcel dest, int flags); + public void writeToParcel(Parcel dest, int flags); } diff --git a/checker/tests/calledmethods-autovalue/SetInsideBuild.java b/checker/tests/calledmethods-autovalue/SetInsideBuild.java index 88942c25b45..691df85f941 100644 --- a/checker/tests/calledmethods-autovalue/SetInsideBuild.java +++ b/checker/tests/calledmethods-autovalue/SetInsideBuild.java @@ -1,40 +1,41 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class SetInsideBuild { - public abstract String name(); + public abstract String name(); + + public abstract int size(); + + static Builder builder() { + return new AutoValue_SetInsideBuild.Builder(); + } - public abstract int size(); + @AutoValue.Builder + abstract static class Builder { + abstract Builder setName(String name); - static Builder builder() { - return new AutoValue_SetInsideBuild.Builder(); - } + abstract Builder setSize(int value); - @AutoValue.Builder - abstract static class Builder { - abstract Builder setName(String name); + abstract SetInsideBuild autoBuild(); - abstract Builder setSize(int value); + public SetInsideBuild build(@CalledMethods({"setName"}) Builder this) { - abstract SetInsideBuild autoBuild(); + return this.setSize(4).autoBuild(); + } + } - public SetInsideBuild build(@CalledMethods({"setName"}) Builder this) { + public static void buildSomethingWrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); + } - return this.setSize(4).autoBuild(); + public static void buildSomethingCorrect() { + Builder b = builder(); + b.setName("Frank"); + b.build(); } - } - - public static void buildSomethingWrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); - } - - public static void buildSomethingCorrect() { - Builder b = builder(); - b.setName("Frank"); - b.build(); - } } diff --git a/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java b/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java index 2bdac94246e..6ed7b942674 100644 --- a/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java +++ b/checker/tests/calledmethods-autovalue/SetInsideBuildWithCM.java @@ -1,40 +1,41 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class SetInsideBuildWithCM { - public abstract String name(); + public abstract String name(); + + public abstract int size(); + + static Builder builder() { + return new AutoValue_SetInsideBuildWithCM.Builder(); + } - public abstract int size(); + @AutoValue.Builder + abstract static class Builder { + abstract Builder setName(String name); - static Builder builder() { - return new AutoValue_SetInsideBuildWithCM.Builder(); - } + abstract Builder setSize(int value); - @AutoValue.Builder - abstract static class Builder { - abstract Builder setName(String name); + abstract SetInsideBuildWithCM autoBuild(); - abstract Builder setSize(int value); + public SetInsideBuildWithCM build() { + return this.autoBuild(); + } + } - abstract SetInsideBuildWithCM autoBuild(); + public static void buildSomethingCorrect() { + Builder b = builder(); + b.setName("Frank"); + b.setSize(2); + b.build(); + } - public SetInsideBuildWithCM build() { - return this.autoBuild(); + public static void buildSomethingWrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); } - } - - public static void buildSomethingCorrect() { - Builder b = builder(); - b.setName("Frank"); - b.setSize(2); - b.build(); - } - - public static void buildSomethingWrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); - } } diff --git a/checker/tests/calledmethods-autovalue/Validation.java b/checker/tests/calledmethods-autovalue/Validation.java index c8f7f359315..f77bcd710fa 100644 --- a/checker/tests/calledmethods-autovalue/Validation.java +++ b/checker/tests/calledmethods-autovalue/Validation.java @@ -1,41 +1,42 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.nullness.qual.*; @AutoValue abstract class Validation { - public abstract String name(); + public abstract String name(); + + static Builder builder() { + return new AutoValue_Validation.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { - static Builder builder() { - return new AutoValue_Validation.Builder(); - } + abstract Builder setName(String name); - @AutoValue.Builder - abstract static class Builder { + abstract Validation autoBuild(); - abstract Builder setName(String name); + public Validation build(@CalledMethods("setName") Builder this) { + Validation v = autoBuild(); + if (v.name().length() < 5) { + throw new RuntimeException("name too short!"); + } + return v; + } + } - abstract Validation autoBuild(); + static void correct() { + Builder b = builder(); + b.setName("Phil"); + b.build(); + } - public Validation build(@CalledMethods("setName") Builder this) { - Validation v = autoBuild(); - if (v.name().length() < 5) { - throw new RuntimeException("name too short!"); - } - return v; + static void wrong() { + Builder b = builder(); + // :: error: finalizer.invocation.invalid + b.build(); } - } - - static void correct() { - Builder b = builder(); - b.setName("Phil"); - b.build(); - } - - static void wrong() { - Builder b = builder(); - // :: error: finalizer.invocation.invalid - b.build(); - } } diff --git a/checker/tests/calledmethods-disableframeworks/AnimalSimple.java b/checker/tests/calledmethods-disableframeworks/AnimalSimple.java index 538b4bf79c6..d0f36c399f9 100644 --- a/checker/tests/calledmethods-disableframeworks/AnimalSimple.java +++ b/checker/tests/calledmethods-disableframeworks/AnimalSimple.java @@ -6,48 +6,48 @@ */ @AutoValue abstract class AnimalSimple { - abstract String name(); + abstract String name(); - abstract int numberOfLegs(); + abstract int numberOfLegs(); - static Builder builder() { - return new AutoValue_AnimalSimple.Builder(); - } + static Builder builder() { + return new AutoValue_AnimalSimple.Builder(); + } - @AutoValue.Builder - abstract static class Builder { + @AutoValue.Builder + abstract static class Builder { - abstract Builder setName(String value); + abstract Builder setName(String value); - abstract Builder setNumberOfLegs(int value); + abstract Builder setNumberOfLegs(int value); - abstract AnimalSimple build(); - } + abstract AnimalSimple build(); + } - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - b.build(); - } + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + b.build(); + } - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.build(); - } + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.build(); + } - public static void buildSomethingWrongFluent() { - builder().setName("Frank").build(); - } + public static void buildSomethingWrongFluent() { + builder().setName("Frank").build(); + } - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).build(); - } + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).build(); + } - public static void buildSomethingRightFluentWithLocal() { - Builder b = builder(); - b.setName("Jim").setNumberOfLegs(7); - b.build(); - } + public static void buildSomethingRightFluentWithLocal() { + Builder b = builder(); + b.setName("Jim").setNumberOfLegs(7); + b.build(); + } } diff --git a/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java b/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java index 0e6ce11bce2..926d12ecdc8 100644 --- a/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java +++ b/checker/tests/calledmethods-disableframeworks/LombokBuilderExample.java @@ -1,82 +1,82 @@ // Generated by delombok at Wed Aug 14 15:57:15 PDT 2019 // A test for support for the builder() method in Lombok builders. public class LombokBuilderExample { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - builder().build(); - } - - @java.lang.SuppressWarnings("all") - @lombok.Generated - LombokBuilderExample(@lombok.NonNull final Object foo, @lombok.NonNull final Object bar) { - if (foo == null) { - throw new java.lang.NullPointerException("foo is marked non-null but is null"); - } - if (bar == null) { - throw new java.lang.NullPointerException("bar is marked non-null but is null"); + static void test() { + builder().build(); } - this.foo = foo; - this.bar = bar; - } - @java.lang.SuppressWarnings("all") - @lombok.Generated - public static class LombokBuilderExampleBuilder { @java.lang.SuppressWarnings("all") @lombok.Generated - private Object foo; + LombokBuilderExample(@lombok.NonNull final Object foo, @lombok.NonNull final Object bar) { + if (foo == null) { + throw new java.lang.NullPointerException("foo is marked non-null but is null"); + } + if (bar == null) { + throw new java.lang.NullPointerException("bar is marked non-null but is null"); + } + this.foo = foo; + this.bar = bar; + } @java.lang.SuppressWarnings("all") @lombok.Generated - private Object bar; + public static class LombokBuilderExampleBuilder { + @java.lang.SuppressWarnings("all") + @lombok.Generated + private Object foo; - @java.lang.SuppressWarnings("all") - @lombok.Generated - LombokBuilderExampleBuilder() {} + @java.lang.SuppressWarnings("all") + @lombok.Generated + private Object bar; - @java.lang.SuppressWarnings("all") - @lombok.Generated - public LombokBuilderExampleBuilder foo(@lombok.NonNull final Object foo) { - if (foo == null) { - throw new java.lang.NullPointerException("foo is marked non-null but is null"); - } - this.foo = foo; - return this; - } + @java.lang.SuppressWarnings("all") + @lombok.Generated + LombokBuilderExampleBuilder() {} - @java.lang.SuppressWarnings("all") - @lombok.Generated - public LombokBuilderExampleBuilder bar(@lombok.NonNull final Object bar) { - if (bar == null) { - throw new java.lang.NullPointerException("bar is marked non-null but is null"); - } - this.bar = bar; - return this; - } + @java.lang.SuppressWarnings("all") + @lombok.Generated + public LombokBuilderExampleBuilder foo(@lombok.NonNull final Object foo) { + if (foo == null) { + throw new java.lang.NullPointerException("foo is marked non-null but is null"); + } + this.foo = foo; + return this; + } - @java.lang.SuppressWarnings("all") - @lombok.Generated - public LombokBuilderExample build() { - return new LombokBuilderExample(foo, bar); + @java.lang.SuppressWarnings("all") + @lombok.Generated + public LombokBuilderExampleBuilder bar(@lombok.NonNull final Object bar) { + if (bar == null) { + throw new java.lang.NullPointerException("bar is marked non-null but is null"); + } + this.bar = bar; + return this; + } + + @java.lang.SuppressWarnings("all") + @lombok.Generated + public LombokBuilderExample build() { + return new LombokBuilderExample(foo, bar); + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + @lombok.Generated + public java.lang.String toString() { + return "LombokBuilderExample.LombokBuilderExampleBuilder(foo=" + + this.foo + + ", bar=" + + this.bar + + ")"; + } } - @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated - public java.lang.String toString() { - return "LombokBuilderExample.LombokBuilderExampleBuilder(foo=" - + this.foo - + ", bar=" - + this.bar - + ")"; + public static LombokBuilderExampleBuilder builder() { + return new LombokBuilderExampleBuilder(); } - } - - @java.lang.SuppressWarnings("all") - @lombok.Generated - public static LombokBuilderExampleBuilder builder() { - return new LombokBuilderExampleBuilder(); - } } diff --git a/checker/tests/calledmethods-disableframeworks/Parcel.java b/checker/tests/calledmethods-disableframeworks/Parcel.java index 81203608e62..3015789cfc2 100644 --- a/checker/tests/calledmethods-disableframeworks/Parcel.java +++ b/checker/tests/calledmethods-disableframeworks/Parcel.java @@ -3,9 +3,9 @@ /** stub to avoid bringing in Android dependence */ public final class Parcel { - public String readString() { - return ""; - } + public String readString() { + return ""; + } - public void writeString(String val) {} + public void writeString(String val) {} } diff --git a/checker/tests/calledmethods-disableframeworks/Parcelable.java b/checker/tests/calledmethods-disableframeworks/Parcelable.java index 7b9fb4fa227..f01a75dd8df 100644 --- a/checker/tests/calledmethods-disableframeworks/Parcelable.java +++ b/checker/tests/calledmethods-disableframeworks/Parcelable.java @@ -2,14 +2,14 @@ /** stub to avoid bringing in Android dependence */ public interface Parcelable { - public interface Creator { + public interface Creator { - public T createFromParcel(Parcel source); + public T createFromParcel(Parcel source); - public T[] newArray(int size); - } + public T[] newArray(int size); + } - public int describeContents(); + public int describeContents(); - public void writeToParcel(Parcel dest, int flags); + public void writeToParcel(Parcel dest, int flags); } diff --git a/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java b/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java index 61c8c7e3ce6..20fbf9605a5 100644 --- a/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java +++ b/checker/tests/calledmethods-disablereturnsreceiver/SimpleFluentInference.java @@ -7,70 +7,70 @@ /* Simple inference of a fluent builder */ public class SimpleFluentInference { - SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { - return this; - } + SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { + return this; + } - SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { - return this; - } + SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { + return this; + } - @This SimpleFluentInference a() { - return this; - } + @This SimpleFluentInference a() { + return this; + } - @This SimpleFluentInference b() { - return this; - } + @This SimpleFluentInference b() { + return this; + } - // intentionally does not have an @This annotation - SimpleFluentInference c() { - return new SimpleFluentInference(); - } + // intentionally does not have an @This annotation + SimpleFluentInference c() { + return new SimpleFluentInference(); + } - static void doStuffCorrect() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - .b() - // :: error: finalizer.invocation.invalid - .build(); - } + static void doStuffCorrect() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + .b() + // :: error: finalizer.invocation.invalid + .build(); + } - static void doStuffWrong() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - // :: error: finalizer.invocation.invalid - .build(); - } + static void doStuffWrong() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + // :: error: finalizer.invocation.invalid + .build(); + } - static void doStuffRightWeak() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - // :: error: finalizer.invocation.invalid - .weakbuild(); - } + static void doStuffRightWeak() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + // :: error: finalizer.invocation.invalid + .weakbuild(); + } - static void noReturnsReceiverAnno() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - .b() - .c() - // :: error: finalizer.invocation.invalid - .build(); - } + static void noReturnsReceiverAnno() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + .b() + .c() + // :: error: finalizer.invocation.invalid + .build(); + } - static void fluentLoop() { - SimpleFluentInference s = new SimpleFluentInference().a(); - int i = 10; - while (i > 0) { - // :: error: finalizer.invocation.invalid - s.b().build(); - i--; - s = new SimpleFluentInference(); + static void fluentLoop() { + SimpleFluentInference s = new SimpleFluentInference().a(); + int i = 10; + while (i > 0) { + // :: error: finalizer.invocation.invalid + s.b().build(); + i--; + s = new SimpleFluentInference(); + } } - } } diff --git a/checker/tests/calledmethods-lombok/BuilderTest.java b/checker/tests/calledmethods-lombok/BuilderTest.java index c4c0af687b0..40106a0c678 100644 --- a/checker/tests/calledmethods-lombok/BuilderTest.java +++ b/checker/tests/calledmethods-lombok/BuilderTest.java @@ -3,25 +3,25 @@ @Builder public class BuilderTest { - private Integer x; - @NonNull private Integer y; - @Builder.Default @NonNull private Integer z = 5; + private Integer x; + @NonNull private Integer y; + @Builder.Default @NonNull private Integer z = 5; - public static void test_simplePattern() { - BuilderTest.builder().x(0).y(0).build(); // good builder - BuilderTest.builder().y(0).build(); // good builder - BuilderTest.builder().y(0).z(5).build(); // good builder - // :: error: (finalizer.invocation.invalid) - BuilderTest.builder().x(0).build(); // bad builder - } + public static void test_simplePattern() { + BuilderTest.builder().x(0).y(0).build(); // good builder + BuilderTest.builder().y(0).build(); // good builder + BuilderTest.builder().y(0).z(5).build(); // good builder + // :: error: (finalizer.invocation.invalid) + BuilderTest.builder().x(0).build(); // bad builder + } - public static void test_builderVar() { - final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); - goodBuilder.x(0); - goodBuilder.y(0); - goodBuilder.build(); - final BuilderTest.BuilderTestBuilder badBuilder = new BuilderTestBuilder(); - // :: error: (finalizer.invocation.invalid) - badBuilder.build(); - } + public static void test_builderVar() { + final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); + goodBuilder.x(0); + goodBuilder.y(0); + goodBuilder.build(); + final BuilderTest.BuilderTestBuilder badBuilder = new BuilderTestBuilder(); + // :: error: (finalizer.invocation.invalid) + badBuilder.build(); + } } diff --git a/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java b/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java index e1945e48cca..5241db55261 100644 --- a/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java +++ b/checker/tests/calledmethods-lombok/CheckerFrameworkBuilder.java @@ -3,192 +3,202 @@ public class CheckerFrameworkBuilder { - /** - * Most of this test was copied from - * https://raw.githubusercontent.com/projectlombok/lombok/master/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java - * with the exception of the following lines until the next long comment. I have made one change - * outside the scope of these comments: - I fixed the placement of the type annotations, which - * were originally on scoping constructs. I think this is a bug in the delombok pretty-printer - * used to generate this code, but I wasn't able to find the configuration options used to - * reproduce it in the public release. - * - *

          This test represents exactly the code that Lombok generates with the checkerframework = True - * option in a lombok.config file, including the weird package names they use for the CF and the - * {@code @NotCalledMethods} annotation that they use even though we don't (and never have) - * supported such a thing. - * - *

          The new code added in this block ensures that the Called Methods checker handles clients of - * the copied code correctly. - */ - public static void testOldCalledMethodsGood( - @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder pb) { - pb.build(); - } - - public static void testOldCalledMethodsBad( - @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y"}) CheckerFrameworkBuilderBuilder pb) { - // :: error: finalizer.invocation.invalid - pb.build(); // pb requires y, z - } - - public static void testOldRRGood() { - CheckerFrameworkBuilder b = CheckerFrameworkBuilder.builder().y(5).z(6).build(); - } - - public static void testOldRRBad() { - CheckerFrameworkBuilder b = - // :: error: finalizer.invocation.invalid - CheckerFrameworkBuilder.builder().z(6).build(); // also needs to call y - } - - /** End new, non-copied code. */ - int x; - - int y; - int z; - List names; - - @java.lang.SuppressWarnings("all") - private static int $default$x() { - return 5; - } - - @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") - CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) { - this.x = x; - this.y = y; - this.z = z; - this.names = names; - } - - @java.lang.SuppressWarnings("all") - public static class CheckerFrameworkBuilderBuilder { - @java.lang.SuppressWarnings("all") - private boolean x$set; - - @java.lang.SuppressWarnings("all") - private int x$value; - - @java.lang.SuppressWarnings("all") - private int y; - - @java.lang.SuppressWarnings("all") - private int z; - - @java.lang.SuppressWarnings("all") - private java.util.ArrayList names; - - @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") - CheckerFrameworkBuilderBuilder() {} - - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder x( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods("x") CheckerFrameworkBuilderBuilder - this, - final int x) { - this.x$value = x; - x$set = true; - return this; + /** + * Most of this test was copied from + * https://raw.githubusercontent.com/projectlombok/lombok/master/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java + * with the exception of the following lines until the next long comment. I have made one change + * outside the scope of these comments: - I fixed the placement of the type annotations, which + * were originally on scoping constructs. I think this is a bug in the delombok pretty-printer + * used to generate this code, but I wasn't able to find the configuration options used to + * reproduce it in the public release. + * + *

          This test represents exactly the code that Lombok generates with the checkerframework = + * True option in a lombok.config file, including the weird package names they use for the CF + * and the {@code @NotCalledMethods} annotation that they use even though we don't (and never + * have) supported such a thing. + * + *

          The new code added in this block ensures that the Called Methods checker handles clients + * of the copied code correctly. + */ + public static void testOldCalledMethodsGood( + @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder pb) { + pb.build(); } - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder y( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods("y") CheckerFrameworkBuilderBuilder - this, - final int y) { - this.y = y; - return this; + public static void testOldCalledMethodsBad( + @org.checkerframework.checker.calledmethods.qual.CalledMethods({"y"}) CheckerFrameworkBuilderBuilder pb) { + // :: error: finalizer.invocation.invalid + pb.build(); // pb requires y, z } - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder z( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods("z") CheckerFrameworkBuilderBuilder - this, - final int z) { - this.z = z; - return this; + public static void testOldRRGood() { + CheckerFrameworkBuilder b = CheckerFrameworkBuilder.builder().y(5).z(6).build(); } - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder name(final String name) { - if (this.names == null) { - this.names = new java.util.ArrayList(); - } - this.names.add(name); - return this; + public static void testOldRRBad() { + CheckerFrameworkBuilder b = + // :: error: finalizer.invocation.invalid + CheckerFrameworkBuilder.builder().z(6).build(); // also needs to call y } - @org.checkerframework.checker.builder.qual.ReturnsReceiver + /** End new, non-copied code. */ + int x; + + int y; + int z; + List names; + @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder names( - final java.util.Collection names) { - if (names == null) { - throw new java.lang.NullPointerException("names cannot be null"); - } - if (this.names == null) { - this.names = new java.util.ArrayList(); - } - this.names.addAll(names); - return this; + private static int $default$x() { + return 5; } - @org.checkerframework.checker.builder.qual.ReturnsReceiver - @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder clearNames() { - if (this.names != null) { - this.names.clear(); - } - return this; + @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") + CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) { + this.x = x; + this.y = y; + this.z = z; + this.names = names; } - @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") - public CheckerFrameworkBuilder build( - CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.CalledMethods({"y", "z"}) CheckerFrameworkBuilderBuilder - this) { - java.util.List names; - switch (this.names == null ? 0 : this.names.size()) { - case 0: - names = java.util.Collections.emptyList(); - break; - case 1: - names = java.util.Collections.singletonList(this.names.get(0)); - break; - default: - names = - java.util.Collections.unmodifiableList(new java.util.ArrayList(this.names)); - } - int x$value = this.x$value; - if (!this.x$set) { - x$value = CheckerFrameworkBuilder.$default$x(); - } - return new CheckerFrameworkBuilder(x$value, this.y, this.z, names); + public static class CheckerFrameworkBuilderBuilder { + @java.lang.SuppressWarnings("all") + private boolean x$set; + + @java.lang.SuppressWarnings("all") + private int x$value; + + @java.lang.SuppressWarnings("all") + private int y; + + @java.lang.SuppressWarnings("all") + private int z; + + @java.lang.SuppressWarnings("all") + private java.util.ArrayList names; + + @org.checkerframework.common.aliasing.qual.Unique @java.lang.SuppressWarnings("all") + CheckerFrameworkBuilderBuilder() {} + + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder x( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods( + "x") + CheckerFrameworkBuilderBuilder + this, + final int x) { + this.x$value = x; + x$set = true; + return this; + } + + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder y( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods( + "y") + CheckerFrameworkBuilderBuilder + this, + final int y) { + this.y = y; + return this; + } + + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder z( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.NotCalledMethods( + "z") + CheckerFrameworkBuilderBuilder + this, + final int z) { + this.z = z; + return this; + } + + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder name(final String name) { + if (this.names == null) { + this.names = new java.util.ArrayList(); + } + this.names.add(name); + return this; + } + + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder names( + final java.util.Collection names) { + if (names == null) { + throw new java.lang.NullPointerException("names cannot be null"); + } + if (this.names == null) { + this.names = new java.util.ArrayList(); + } + this.names.addAll(names); + return this; + } + + @org.checkerframework.checker.builder.qual.ReturnsReceiver + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder clearNames() { + if (this.names != null) { + this.names.clear(); + } + return this; + } + + @org.checkerframework.dataflow.qual.SideEffectFree + @java.lang.SuppressWarnings("all") + public CheckerFrameworkBuilder build( + CheckerFrameworkBuilder.@org.checkerframework.checker.builder.qual.CalledMethods({ + "y", "z" + }) + CheckerFrameworkBuilderBuilder + this) { + java.util.List names; + switch (this.names == null ? 0 : this.names.size()) { + case 0: + names = java.util.Collections.emptyList(); + break; + case 1: + names = java.util.Collections.singletonList(this.names.get(0)); + break; + default: + names = + java.util.Collections.unmodifiableList( + new java.util.ArrayList(this.names)); + } + int x$value = this.x$value; + if (!this.x$set) { + x$value = CheckerFrameworkBuilder.$default$x(); + } + return new CheckerFrameworkBuilder(x$value, this.y, this.z, names); + } + + @org.checkerframework.dataflow.qual.SideEffectFree + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(x$value=" + + this.x$value + + ", y=" + + this.y + + ", z=" + + this.z + + ", names=" + + this.names + + ")"; + } } @org.checkerframework.dataflow.qual.SideEffectFree - @java.lang.Override @java.lang.SuppressWarnings("all") - public java.lang.String toString() { - return "CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(x$value=" - + this.x$value - + ", y=" - + this.y - + ", z=" - + this.z - + ", names=" - + this.names - + ")"; + public static CheckerFrameworkBuilder.@org.checkerframework.common.aliasing.qual.Unique CheckerFrameworkBuilderBuilder + builder() { + return new CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(); } - } - - @org.checkerframework.dataflow.qual.SideEffectFree - @java.lang.SuppressWarnings("all") - public static CheckerFrameworkBuilder.@org.checkerframework.common.aliasing.qual.Unique CheckerFrameworkBuilderBuilder - builder() { - return new CheckerFrameworkBuilder.CheckerFrameworkBuilderBuilder(); - } } diff --git a/checker/tests/calledmethods-lombok/DefaultedName.java b/checker/tests/calledmethods-lombok/DefaultedName.java index 02cb09ccc06..b9974aef1e2 100644 --- a/checker/tests/calledmethods-lombok/DefaultedName.java +++ b/checker/tests/calledmethods-lombok/DefaultedName.java @@ -2,17 +2,17 @@ @Builder public class DefaultedName { - @Builder.Default @lombok.NonNull String name = "Martin"; + @Builder.Default @lombok.NonNull String name = "Martin"; - static void test1() { - builder().build(); - } + static void test1() { + builder().build(); + } - static void test2(Object foo) { - DefaultedNameBuilder b = builder(); - if (foo != null) { - b.name(foo.toString()); + static void test2(Object foo) { + DefaultedNameBuilder b = builder(); + if (foo != null) { + b.name(foo.toString()); + } + b.build(); } - b.build(); - } } diff --git a/checker/tests/calledmethods-lombok/LombokBuilderExample.java b/checker/tests/calledmethods-lombok/LombokBuilderExample.java index 5cf70721e06..9a8ca6843e7 100644 --- a/checker/tests/calledmethods-lombok/LombokBuilderExample.java +++ b/checker/tests/calledmethods-lombok/LombokBuilderExample.java @@ -4,11 +4,11 @@ @Builder public class LombokBuilderExample { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - // :: error: (finalizer.invocation.invalid) - builder().build(); - } + static void test() { + // :: error: (finalizer.invocation.invalid) + builder().build(); + } } diff --git a/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java b/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java index df474dcaff6..2adc444fd71 100644 --- a/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java +++ b/checker/tests/calledmethods-lombok/LombokBuilderSubclassExample.java @@ -1,6 +1,7 @@ import lombok.Builder; import lombok.NonNull; import lombok.Value; + import org.checkerframework.checker.calledmethods.qual.CalledMethods; import org.checkerframework.common.returnsreceiver.qual.This; @@ -8,32 +9,32 @@ @Value public class LombokBuilderSubclassExample { - @NonNull Integer attribute; + @NonNull Integer attribute; - public static LombokBuilderSubclassExampleBuilder builder() { - return new LombokBuilderSubclassExampleBuilder(); - } + public static LombokBuilderSubclassExampleBuilder builder() { + return new LombokBuilderSubclassExampleBuilder(); + } - public static class LombokBuilderSubclassExampleBuilder extends BaseBuilder { + public static class LombokBuilderSubclassExampleBuilder extends BaseBuilder { - @Override - @This public LombokBuilderSubclassExampleBuilder attribute(@NonNull Integer attribute) { - return (LombokBuilderSubclassExampleBuilder) super.attribute(attribute); - } + @Override + @This public LombokBuilderSubclassExampleBuilder attribute(@NonNull Integer attribute) { + return (LombokBuilderSubclassExampleBuilder) super.attribute(attribute); + } - @Override - public LombokBuilderSubclassExample build( - @CalledMethods("attribute") LombokBuilderSubclassExampleBuilder this) { - final LombokBuilderSubclassExample result = super.build(); - // here result.getAttribute() is guaranteed to be non null, so we do not have to check - // this - // ourselves + @Override + public LombokBuilderSubclassExample build( + @CalledMethods("attribute") LombokBuilderSubclassExampleBuilder this) { + final LombokBuilderSubclassExample result = super.build(); + // here result.getAttribute() is guaranteed to be non null, so we do not have to check + // this + // ourselves - if (result.getAttribute() < 0) { - throw new IllegalArgumentException("attribute must be >= 0"); - } + if (result.getAttribute() < 0) { + throw new IllegalArgumentException("attribute must be >= 0"); + } - return result; + return result; + } } - } } diff --git a/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java b/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java index a2c164e89c7..dcba963492c 100644 --- a/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java +++ b/checker/tests/calledmethods-lombok/LombokDefaultAssignments.java @@ -1,15 +1,16 @@ -import java.util.Optional; import lombok.Builder; +import java.util.Optional; + @Builder public class LombokDefaultAssignments { - @lombok.NonNull Optional bar; + @lombok.NonNull Optional bar; - public static class LombokDefaultAssignmentsBuilder { - private Optional bar = Optional.empty(); - } + public static class LombokDefaultAssignmentsBuilder { + private Optional bar = Optional.empty(); + } - static void test() { - LombokDefaultAssignments.builder().build(); - } + static void test() { + LombokDefaultAssignments.builder().build(); + } } diff --git a/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java b/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java index 0022f2674a7..5ddf7275c78 100644 --- a/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java +++ b/checker/tests/calledmethods-lombok/LombokNoSingularButClearMethodExample.java @@ -1,22 +1,23 @@ -import java.util.List; import lombok.Builder; +import java.util.List; + @Builder public class LombokNoSingularButClearMethodExample { - @lombok.NonNull List items; + @lombok.NonNull List items; - // This one should throw an error, because the field isn't - // automatically initialized. - public static void testNoItems() { - // :: error: finalizer.invocation.invalid - LombokNoSingularButClearMethodExample.builder().build(); - } + // This one should throw an error, because the field isn't + // automatically initialized. + public static void testNoItems() { + // :: error: finalizer.invocation.invalid + LombokNoSingularButClearMethodExample.builder().build(); + } - public static void testWithList(List l) { - LombokNoSingularButClearMethodExample.builder().items(l).build(); - } + public static void testWithList(List l) { + LombokNoSingularButClearMethodExample.builder().items(l).build(); + } - public static class LombokNoSingularButClearMethodExampleBuilder { - public void clearItems() {} - } + public static class LombokNoSingularButClearMethodExampleBuilder { + public void clearItems() {} + } } diff --git a/checker/tests/calledmethods-lombok/LombokSingularExample.java b/checker/tests/calledmethods-lombok/LombokSingularExample.java index 82b171e2059..647f629f3b1 100644 --- a/checker/tests/calledmethods-lombok/LombokSingularExample.java +++ b/checker/tests/calledmethods-lombok/LombokSingularExample.java @@ -1,24 +1,25 @@ -import java.util.List; import lombok.Builder; import lombok.Singular; +import java.util.List; + @Builder public class LombokSingularExample { - @Singular @lombok.NonNull List items; + @Singular @lombok.NonNull List items; - // This one should be permitted, because @Singular will - // produce an empty list even if one is not specified directly. - public static void testNoItems() { - LombokSingularExample.builder().build(); - } + // This one should be permitted, because @Singular will + // produce an empty list even if one is not specified directly. + public static void testNoItems() { + LombokSingularExample.builder().build(); + } - // This call should also be permitted, even though items() is - // not called, because the list will be automatically initialized. - public static void testOneItem() { - LombokSingularExample.builder().item(new Object()).build(); - } + // This call should also be permitted, even though items() is + // not called, because the list will be automatically initialized. + public static void testOneItem() { + LombokSingularExample.builder().item(new Object()).build(); + } - public static void testWithList(List l) { - LombokSingularExample.builder().items(l).build(); - } + public static void testWithList(List l) { + LombokSingularExample.builder().items(l).build(); + } } diff --git a/checker/tests/calledmethods-lombok/LombokToBuilderExample.java b/checker/tests/calledmethods-lombok/LombokToBuilderExample.java index 4c527710577..c1cbdb8e567 100644 --- a/checker/tests/calledmethods-lombok/LombokToBuilderExample.java +++ b/checker/tests/calledmethods-lombok/LombokToBuilderExample.java @@ -4,14 +4,14 @@ @Builder(toBuilder = true) public class LombokToBuilderExample { - @lombok.NonNull String req; + @lombok.NonNull String req; - static void test(LombokToBuilderExample foo) { - foo.toBuilder().build(); - } + static void test(LombokToBuilderExample foo) { + foo.toBuilder().build(); + } - static void ensureThatErrorIssued() { - // :: error: finalizer.invocation.invalid - LombokToBuilderExample.builder().build(); - } + static void ensureThatErrorIssued() { + // :: error: finalizer.invocation.invalid + LombokToBuilderExample.builder().build(); + } } diff --git a/checker/tests/calledmethods-lombok/OldInherited.java b/checker/tests/calledmethods-lombok/OldInherited.java index aaa624a2562..d163c1b13c6 100644 --- a/checker/tests/calledmethods-lombok/OldInherited.java +++ b/checker/tests/calledmethods-lombok/OldInherited.java @@ -5,41 +5,41 @@ import org.checkerframework.checker.calledmethods.qual.*; public class OldInherited { - @ReturnsReceiver - OldInherited getThis() { - return this; - } - - static class OldInheritedChild extends OldInherited { - @java.lang.Override + @ReturnsReceiver OldInherited getThis() { - return this; + return this; + } + + static class OldInheritedChild extends OldInherited { + @java.lang.Override + OldInherited getThis() { + return this; + } + } + + void requiresGetThis(@CalledMethods("getThis") OldInherited this) {} + + public static void testGoodParent() { + OldInherited o = new OldInherited(); + o.getThis(); + o.requiresGetThis(); + } + + public static void testGoodChild() { + OldInheritedChild o = new OldInheritedChild(); + o.getThis(); + o.requiresGetThis(); + } + + public static void testBadParent() { + OldInherited o = new OldInherited(); + // :: error: finalizer.invocation.invalid + o.requiresGetThis(); + } + + public static void testBadChild() { + OldInheritedChild o = new OldInheritedChild(); + // :: error: finalizer.invocation.invalid + o.requiresGetThis(); } - } - - void requiresGetThis(@CalledMethods("getThis") OldInherited this) {} - - public static void testGoodParent() { - OldInherited o = new OldInherited(); - o.getThis(); - o.requiresGetThis(); - } - - public static void testGoodChild() { - OldInheritedChild o = new OldInheritedChild(); - o.getThis(); - o.requiresGetThis(); - } - - public static void testBadParent() { - OldInherited o = new OldInherited(); - // :: error: finalizer.invocation.invalid - o.requiresGetThis(); - } - - public static void testBadChild() { - OldInheritedChild o = new OldInheritedChild(); - // :: error: finalizer.invocation.invalid - o.requiresGetThis(); - } } diff --git a/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java b/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java index f20b84a5f72..e4ee4ee4006 100644 --- a/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java +++ b/checker/tests/calledmethods-nodelombok/UnsoundnessTest.java @@ -3,19 +3,19 @@ @lombok.Builder class UnsoundnessTest { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - // An error should be issued here, but the code has not been delombok'd. - // If the CF and Lombok are ever able to work in the same invocation of javac - // (i.e. without delomboking first), then this error should be changed back to an - // expected error by re-adding the leading "::". - // error: (finalizer.invocation) - builder().build(); - } + static void test() { + // An error should be issued here, but the code has not been delombok'd. + // If the CF and Lombok are ever able to work in the same invocation of javac + // (i.e. without delomboking first), then this error should be changed back to an + // expected error by re-adding the leading "::". + // error: (finalizer.invocation) + builder().build(); + } - static void test2() { - builder().foo(null).bar(null).build(); - } + static void test2() { + builder().foo(null).bar(null).build(); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/Cve.java b/checker/tests/calledmethods-usevaluechecker/Cve.java index 72183cf3ff6..056d7e2f267 100644 --- a/checker/tests/calledmethods-usevaluechecker/Cve.java +++ b/checker/tests/calledmethods-usevaluechecker/Cve.java @@ -7,101 +7,101 @@ // https://nvd.nist.gov/vuln/detail/CVE-2018-15869 public class Cve { - private static final String IMG_NAME = "some_linux_img"; + private static final String IMG_NAME = "some_linux_img"; - public static void onlyNames(AmazonEC2 client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + public static void onlyNames(AmazonEC2 client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); + } - public static void correct3(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withExecutableUsers("myUsers")); - } + public static void correct3(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withExecutableUsers("myUsers")); + } - // Using impl class instead of interface - public static void onlyNamesImpl(AmazonEC2Client client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + // Using impl class instead of interface + public static void onlyNamesImpl(AmazonEC2Client client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1Impl(AmazonEC2Client client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1Impl(AmazonEC2Client client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2Impl(AmazonEC2Client client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2Impl(AmazonEC2Client client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); + } - // Using async impl class - public static void onlyNamesAsync(AmazonEC2AsyncClient client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + // Using async impl class + public static void onlyNamesAsync(AmazonEC2AsyncClient client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1Async(AmazonEC2AsyncClient client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1Async(AmazonEC2AsyncClient client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2Async(AmazonEC2AsyncClient client) { - DescribeImagesResult result = - client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2Async(AmazonEC2AsyncClient client) { + DescribeImagesResult result = + client.describeImages(new DescribeImagesRequest().withImageIds("myImageId")); + } - // Using async methods - public static void onlyNamesAsync2(AmazonEC2AsyncClient client) { - // Should not be allowed unless .withOwner is also used - Object result = - client.describeImagesAsync( - new DescribeImagesRequest() - // :: error: argument.type.incompatible - .withFilters(new Filter("name").withValues(IMG_NAME))); - } + // Using async methods + public static void onlyNamesAsync2(AmazonEC2AsyncClient client) { + // Should not be allowed unless .withOwner is also used + Object result = + client.describeImagesAsync( + new DescribeImagesRequest() + // :: error: argument.type.incompatible + .withFilters(new Filter("name").withValues(IMG_NAME))); + } - public static void correct1Async2(AmazonEC2AsyncClient client) { - Object result = - client.describeImagesAsync( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withOwners("martin")); - } + public static void correct1Async2(AmazonEC2AsyncClient client) { + Object result = + client.describeImagesAsync( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withOwners("martin")); + } - public static void correct2Async2(AmazonEC2AsyncClient client) { - Object result = - client.describeImagesAsync(new DescribeImagesRequest().withImageIds("myImageId")); - } + public static void correct2Async2(AmazonEC2AsyncClient client) { + Object result = + client.describeImagesAsync(new DescribeImagesRequest().withImageIds("myImageId")); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/Cve2.java b/checker/tests/calledmethods-usevaluechecker/Cve2.java index e049bd73e48..844d7a82b44 100644 --- a/checker/tests/calledmethods-usevaluechecker/Cve2.java +++ b/checker/tests/calledmethods-usevaluechecker/Cve2.java @@ -2,40 +2,41 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; + import java.util.Collections; // https://nvd.nist.gov/vuln/detail/CVE-2018-15869 public class Cve2 { - private static final String IMG_NAME = "some_linux_img"; + private static final String IMG_NAME = "some_linux_img"; - public static void onlyNames(AmazonEC2 client) { - // Should not be allowed unless .withOwner is also used - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter("name").withValues(IMG_NAME)); + public static void onlyNames(AmazonEC2 client) { + // Should not be allowed unless .withOwner is also used + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter("name").withValues(IMG_NAME)); - // :: error: argument.type.incompatible - DescribeImagesResult result = client.describeImages(request); - } + // :: error: argument.type.incompatible + DescribeImagesResult result = client.describeImages(request); + } - public static void correct1(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter("name").withValues(IMG_NAME)); - request.withOwners("martin"); + public static void correct1(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter("name").withValues(IMG_NAME)); + request.withOwners("martin"); - DescribeImagesResult result = client.describeImages(request); - } + DescribeImagesResult result = client.describeImages(request); + } - public static void correct2(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withImageIds("myImageId"); + public static void correct2(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withImageIds("myImageId"); - DescribeImagesResult result = client.describeImages(request); - } + DescribeImagesResult result = client.describeImages(request); + } - public static void correct3(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.setExecutableUsers(Collections.singletonList("myUser1")); + public static void correct3(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.setExecutableUsers(Collections.singletonList("myUser1")); - DescribeImagesResult result = client.describeImages(request); - } + DescribeImagesResult result = client.describeImages(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java b/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java index 92e87662112..eec56d9792f 100644 --- a/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java +++ b/checker/tests/calledmethods-usevaluechecker/GenerateDataKeyRequestExamples.java @@ -3,110 +3,111 @@ import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.model.DataKeySpec; import com.amazonaws.services.kms.model.GenerateDataKeyRequest; + import org.checkerframework.checker.calledmethods.qual.*; public class GenerateDataKeyRequestExamples { - void correctWithKeySpec(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void correctWithNumberOfBytes(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withNumberOfBytes(32); - client.generateDataKey(request); - } - - void correctSetKeySpec(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void correctSetNumberOfBytes(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setNumberOfBytes(32); - client.generateDataKey(request); - } - - // The next four examples are "both" - void incorrect1(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setKeySpec(DataKeySpec.AES_256); - request.setNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - void incorrect2(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.setNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - void incorrect3(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setKeySpec(DataKeySpec.AES_256); - request.withNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - void incorrect4(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.withNumberOfBytes(32); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - // This example is "neither" - void incorrect5(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - // :: error: argument.type.incompatible - client.generateDataKey(request); - } - - // Calling these methods are idempotent, including between with/set versions of the same. - // TODO: Verify that these calls should be permitted. - void setTwice1(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.withKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void setTwice2(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withKeySpec(DataKeySpec.AES_256); - request.setKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } - - void setTwice3(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.withNumberOfBytes(32); - request.setNumberOfBytes(32); - client.generateDataKey(request); - } - - void setTwice4(AWSKMS client) { - GenerateDataKeyRequest request = new GenerateDataKeyRequest(); - request.setNumberOfBytes(32); - request.setNumberOfBytes(32); - client.generateDataKey(request); - } - - /// Interprocedural - - void callee2( - AWSKMS client, - @CalledMethodsPredicate("(!withNumberOfBytes) && (!setNumberOfBytes)") GenerateDataKeyRequest request) { - request.withKeySpec(DataKeySpec.AES_256); - client.generateDataKey(request); - } + void correctWithKeySpec(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void correctWithNumberOfBytes(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withNumberOfBytes(32); + client.generateDataKey(request); + } + + void correctSetKeySpec(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void correctSetNumberOfBytes(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setNumberOfBytes(32); + client.generateDataKey(request); + } + + // The next four examples are "both" + void incorrect1(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setKeySpec(DataKeySpec.AES_256); + request.setNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + void incorrect2(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.setNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + void incorrect3(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setKeySpec(DataKeySpec.AES_256); + request.withNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + void incorrect4(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.withNumberOfBytes(32); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + // This example is "neither" + void incorrect5(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + // :: error: argument.type.incompatible + client.generateDataKey(request); + } + + // Calling these methods are idempotent, including between with/set versions of the same. + // TODO: Verify that these calls should be permitted. + void setTwice1(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.withKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void setTwice2(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withKeySpec(DataKeySpec.AES_256); + request.setKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } + + void setTwice3(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.withNumberOfBytes(32); + request.setNumberOfBytes(32); + client.generateDataKey(request); + } + + void setTwice4(AWSKMS client) { + GenerateDataKeyRequest request = new GenerateDataKeyRequest(); + request.setNumberOfBytes(32); + request.setNumberOfBytes(32); + client.generateDataKey(request); + } + + /// Interprocedural + + void callee2( + AWSKMS client, + @CalledMethodsPredicate("(!withNumberOfBytes) && (!setNumberOfBytes)") GenerateDataKeyRequest request) { + request.withKeySpec(DataKeySpec.AES_256); + client.generateDataKey(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java b/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java index ebff3fac54d..1e04c13a35d 100644 --- a/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java +++ b/checker/tests/calledmethods-usevaluechecker/MorePreciseFilters.java @@ -2,68 +2,69 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; + import java.util.Arrays; import java.util.Collections; public class MorePreciseFilters { - /* TODO: handle lists - void ownerAliasList(AmazonEC2 ec2Client) { - DescribeImagesRequest imagesRequest = new DescribeImagesRequest(); - List imageFilters = new ArrayList(); - imageFilters.add(new Filter().withName("owner-alias").withValues("microsoft")); - ec2Client.describeImages(imagesRequest.withFilters(imageFilters)).getImages(); - } - */ + /* TODO: handle lists + void ownerAliasList(AmazonEC2 ec2Client) { + DescribeImagesRequest imagesRequest = new DescribeImagesRequest(); + List imageFilters = new ArrayList(); + imageFilters.add(new Filter().withName("owner-alias").withValues("microsoft")); + ec2Client.describeImages(imagesRequest.withFilters(imageFilters)).getImages(); + } + */ - void withFilterNameInList(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.setFilters( - Collections.singletonList(new Filter().withName("image-id").withValues("12345"))); + void withFilterNameInList(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.setFilters( + Collections.singletonList(new Filter().withName("image-id").withValues("12345"))); - DescribeImagesResult result = ec2Client.describeImages(request); - } + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withOwnerId(AmazonEC2 ec2) { - DescribeImagesRequest request = - new DescribeImagesRequest() - .withFilters( - new Filter("name", Arrays.asList("my_image_name")), - new Filter("owner-id", Arrays.asList("12345"))); - DescribeImagesResult result = ec2.describeImages(request); - } + void withOwnerId(AmazonEC2 ec2) { + DescribeImagesRequest request = + new DescribeImagesRequest() + .withFilters( + new Filter("name", Arrays.asList("my_image_name")), + new Filter("owner-id", Arrays.asList("12345"))); + DescribeImagesResult result = ec2.describeImages(request); + } - void withName(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter().withName("image-id").withValues("12345")); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter().withName("image-id").withValues("12345")); + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName2(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter().withName("image-id").withName("foo").withValues("12345")); - // :: error: (argument.type.incompatible) - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName2(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter().withName("image-id").withName("foo").withValues("12345")); + // :: error: (argument.type.incompatible) + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName3(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter().withName("foo").withName("image-id").withValues("12345")); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName3(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter().withName("foo").withName("image-id").withValues("12345")); + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName4(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters( - new Filter().withName("owner-id").withName("foo").withValues("12345"), - new Filter("owner-id", Arrays.asList("12345"))); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName4(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters( + new Filter().withName("owner-id").withName("foo").withValues("12345"), + new Filter("owner-id", Arrays.asList("12345"))); + DescribeImagesResult result = ec2Client.describeImages(request); + } - void withName5(AmazonEC2 ec2Client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - Filter f = new Filter(); - request.withFilters(f.withName("owner-id").withValues("12345")); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void withName5(AmazonEC2 ec2Client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + Filter f = new Filter(); + request.withFilters(f.withName("owner-id").withValues("12345")); + DescribeImagesResult result = ec2Client.describeImages(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java b/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java index 0d3b2057f44..2c303a323fc 100644 --- a/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java +++ b/checker/tests/calledmethods-usevaluechecker/OnlyOwnersFalsePositive.java @@ -1,15 +1,16 @@ import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; + import java.util.Collections; // Tests that just setting with/setOwners is permitted, since there are legitimate reasons to do // that. // Originally, we required with/setFilters && with/setOwners. public class OnlyOwnersFalsePositive { - void test(AmazonEC2 ec2Client) { - DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest(); - describeImagesRequest.setOwners(Collections.singleton("self")); - DescribeImagesResult describeImagesResult = ec2Client.describeImages(describeImagesRequest); - } + void test(AmazonEC2 ec2Client) { + DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest(); + describeImagesRequest.setOwners(Collections.singleton("self")); + DescribeImagesResult describeImagesResult = ec2Client.describeImages(describeImagesRequest); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java b/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java index 21ab8d83952..8396729d963 100644 --- a/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java +++ b/checker/tests/calledmethods-usevaluechecker/RequestCreatedInCall.java @@ -2,14 +2,16 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; + import java.util.*; // A test to ensure that requests that are created in the call to describeImages work correctly. public class RequestCreatedInCall { - void test(AmazonEC2 ec2) { - List filters = new ArrayList<>(); - filters.add(new Filter().withName("foo").withValues("bar")); - DescribeImagesResult describeImagesResult = - ec2.describeImages(new DescribeImagesRequest().withOwners("martin").withFilters(filters)); - } + void test(AmazonEC2 ec2) { + List filters = new ArrayList<>(); + filters.add(new Filter().withName("foo").withValues("bar")); + DescribeImagesResult describeImagesResult = + ec2.describeImages( + new DescribeImagesRequest().withOwners("martin").withFilters(filters)); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java b/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java index bd3655f234a..4cea89cf842 100644 --- a/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java +++ b/checker/tests/calledmethods-usevaluechecker/SimpleFalsePositive.java @@ -2,18 +2,21 @@ import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; + import java.util.*; // A simple (potential) false positive case with mutliple filters. public class SimpleFalsePositive { - void test(AmazonEC2 ec2Client, String namePrefix) { - DescribeImagesRequest request = - new DescribeImagesRequest() - .withOwners("martin") - .withFilters( - Arrays.asList( - new Filter("platform", Arrays.asList("windows")), - new Filter("name", Arrays.asList(String.format("%s*", namePrefix))))); - DescribeImagesResult result = ec2Client.describeImages(request); - } + void test(AmazonEC2 ec2Client, String namePrefix) { + DescribeImagesRequest request = + new DescribeImagesRequest() + .withOwners("martin") + .withFilters( + Arrays.asList( + new Filter("platform", Arrays.asList("windows")), + new Filter( + "name", + Arrays.asList(String.format("%s*", namePrefix))))); + DescribeImagesResult result = ec2Client.describeImages(request); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/SpecialNames.java b/checker/tests/calledmethods-usevaluechecker/SpecialNames.java index da20341ff04..74fe28ae862 100644 --- a/checker/tests/calledmethods-usevaluechecker/SpecialNames.java +++ b/checker/tests/calledmethods-usevaluechecker/SpecialNames.java @@ -5,56 +5,56 @@ import org.checkerframework.common.returnsreceiver.qual.*; public class SpecialNames { - @This SpecialNames withFilters() { - return this; - } + @This SpecialNames withFilters() { + return this; + } - void setFilters() {} + void setFilters() {} - @This SpecialNames withFilters(SpecialNames f) { - return this; - } + @This SpecialNames withFilters(SpecialNames f) { + return this; + } - void setFilters(SpecialNames f) {} + void setFilters(SpecialNames f) {} - @This SpecialNames withName() { - return this; - } + @This SpecialNames withName() { + return this; + } - @This SpecialNames withName(String f) { - return this; - } + @This SpecialNames withName(String f) { + return this; + } - SpecialNames() {} + SpecialNames() {} - SpecialNames(String x) {} + SpecialNames(String x) {} - static void test(SpecialNames s) { - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); - } + static void test(SpecialNames s) { + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); + } - static void test2(SpecialNames s) { - s.setFilters(new SpecialNames("owner")); - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s; - } + static void test2(SpecialNames s) { + s.setFilters(new SpecialNames("owner")); + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s; + } - static void test3(SpecialNames s) { - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); - } + static void test3(SpecialNames s) { + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s.withFilters(new SpecialNames().withName("owner")); + } - static void test4(SpecialNames s) { - s.setFilters(new SpecialNames("owner")); - // :: error: assignment.type.incompatible - @CalledMethods("withOwners") SpecialNames x = s; - } + static void test4(SpecialNames s) { + s.setFilters(new SpecialNames("owner")); + // :: error: assignment.type.incompatible + @CalledMethods("withOwners") SpecialNames x = s; + } - static void testForCrashes(SpecialNames s) { - s.setFilters(); - s.withFilters(); + static void testForCrashes(SpecialNames s) { + s.setFilters(); + s.withFilters(); - s.setFilters(new SpecialNames().withName()); - } + s.setFilters(new SpecialNames().withName()); + } } diff --git a/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java b/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java index 0e1b274f22f..df3167eb673 100644 --- a/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java +++ b/checker/tests/calledmethods-usevaluechecker/WithOwnersFilter.java @@ -4,27 +4,28 @@ import com.amazonaws.services.ec2.model.Filter; public class WithOwnersFilter { - private static final String IMG_NAME = "some_linux_img"; + private static final String IMG_NAME = "some_linux_img"; - public static void correct1(AmazonEC2 client) { - DescribeImagesResult result = - client.describeImages( - new DescribeImagesRequest() - .withFilters(new Filter("name").withValues(IMG_NAME)) - .withFilters(new Filter("owner").withValues("my_aws_acct"))); - } + public static void correct1(AmazonEC2 client) { + DescribeImagesResult result = + client.describeImages( + new DescribeImagesRequest() + .withFilters(new Filter("name").withValues(IMG_NAME)) + .withFilters(new Filter("owner").withValues("my_aws_acct"))); + } - public static void correct2(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters(new Filter("name").withValues(IMG_NAME)); - request.withFilters(new Filter("owner").withValues("my_aws_acct")); - client.describeImages(request); - } + public static void correct2(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters(new Filter("name").withValues(IMG_NAME)); + request.withFilters(new Filter("owner").withValues("my_aws_acct")); + client.describeImages(request); + } - public static void correct3(AmazonEC2 client) { - DescribeImagesRequest request = new DescribeImagesRequest(); - request.withFilters( - new Filter("name").withValues(IMG_NAME), new Filter("owner").withValues("my_aws_acct")); - client.describeImages(request); - } + public static void correct3(AmazonEC2 client) { + DescribeImagesRequest request = new DescribeImagesRequest(); + request.withFilters( + new Filter("name").withValues(IMG_NAME), + new Filter("owner").withValues("my_aws_acct")); + client.describeImages(request); + } } diff --git a/checker/tests/calledmethods/CmCfgCrash.java b/checker/tests/calledmethods/CmCfgCrash.java index 5cd97cf3602..bf2ae2b892d 100644 --- a/checker/tests/calledmethods/CmCfgCrash.java +++ b/checker/tests/calledmethods/CmCfgCrash.java @@ -1,6 +1,6 @@ public class CmCfgCrash { - static void f() { - for (int i = 0; ; ) {} - } + static void f() { + for (int i = 0; ; ) {} + } } diff --git a/checker/tests/calledmethods/CmPredicate.java b/checker/tests/calledmethods/CmPredicate.java index 80f602b649a..52046bbdb4c 100644 --- a/checker/tests/calledmethods/CmPredicate.java +++ b/checker/tests/calledmethods/CmPredicate.java @@ -2,606 +2,606 @@ public class CmPredicate { - void testOr1() { - MyClass m1 = new MyClass(); + void testOr1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.c(); - } - - void testOr2() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.c(); - } - - void testOr3() { - MyClass m1 = new MyClass(); - - m1.b(); - m1.c(); - } - - void testAnd1() { - MyClass m1 = new MyClass(); - - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd2() { - MyClass m1 = new MyClass(); - - m1.a(); - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd3() { - MyClass m1 = new MyClass(); - - m1.b(); - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd4() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.c(); - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd5() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.b(); - m1.d(); - } - - void testAnd6() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.b(); - m1.c(); - m1.d(); - } - - void testAndOr1() { - MyClass m1 = new MyClass(); - - // :: error: method.invocation.invalid - m1.e(); - } - - void testAndOr2() { - MyClass m1 = new MyClass(); + // :: error: method.invocation.invalid + m1.c(); + } - m1.a(); - m1.e(); - } + void testOr2() { + MyClass m1 = new MyClass(); - void testAndOr3() { - MyClass m1 = new MyClass(); + m1.a(); + m1.c(); + } - m1.b(); - // :: error: method.invocation.invalid - m1.e(); - } + void testOr3() { + MyClass m1 = new MyClass(); - void testAndOr4() { - MyClass m1 = new MyClass(); + m1.b(); + m1.c(); + } - m1.b(); - m1.c(); - m1.e(); - } + void testAnd1() { + MyClass m1 = new MyClass(); - void testAndOr5() { - MyClass m1 = new MyClass(); + // :: error: method.invocation.invalid + m1.d(); + } - m1.a(); - m1.b(); - m1.c(); - m1.d(); - m1.e(); - } + void testAnd2() { + MyClass m1 = new MyClass(); - void testPrecedence1() { - MyClass m1 = new MyClass(); + m1.a(); + // :: error: method.invocation.invalid + m1.d(); + } - // :: error: method.invocation.invalid - m1.f(); - } + void testAnd3() { + MyClass m1 = new MyClass(); - void testPrecedence2() { - MyClass m1 = new MyClass(); + m1.b(); + // :: error: method.invocation.invalid + m1.d(); + } - m1.a(); - // :: error: method.invocation.invalid - m1.f(); - } + void testAnd4() { + MyClass m1 = new MyClass(); - void testPrecedence3() { - MyClass m1 = new MyClass(); + m1.a(); + m1.c(); + // :: error: method.invocation.invalid + m1.d(); + } - m1.b(); - // :: error: method.invocation.invalid - m1.f(); - } + void testAnd5() { + MyClass m1 = new MyClass(); - void testPrecedence4() { - MyClass m1 = new MyClass(); + m1.a(); + m1.b(); + m1.d(); + } - m1.a(); - m1.b(); - m1.f(); - } + void testAnd6() { + MyClass m1 = new MyClass(); - void testPrecedence5() { - MyClass m1 = new MyClass(); + m1.a(); + m1.b(); + m1.c(); + m1.d(); + } - m1.a(); - m1.c(); - m1.f(); - } + void testAndOr1() { + MyClass m1 = new MyClass(); - void testPrecedence6() { - MyClass m1 = new MyClass(); + // :: error: method.invocation.invalid + m1.e(); + } - m1.b(); - m1.c(); - m1.f(); - } + void testAndOr2() { + MyClass m1 = new MyClass(); - private static class MyClass { + m1.a(); + m1.e(); + } - @CalledMethods("a") MyClass cmA; + void testAndOr3() { + MyClass m1 = new MyClass(); - @CalledMethodsPredicate("a") MyClass cmpA; + m1.b(); + // :: error: method.invocation.invalid + m1.e(); + } - @CalledMethods({"a", "b"}) MyClass aB; + void testAndOr4() { + MyClass m1 = new MyClass(); - @CalledMethodsPredicate("a || b") MyClass aOrB; + m1.b(); + m1.c(); + m1.e(); + } - @CalledMethodsPredicate("a && b") MyClass aAndB; + void testAndOr5() { + MyClass m1 = new MyClass(); - @CalledMethodsPredicate("a || b && c") MyClass bAndCOrA; + m1.a(); + m1.b(); + m1.c(); + m1.d(); + m1.e(); + } - @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParens; + void testPrecedence1() { + MyClass m1 = new MyClass(); - @CalledMethodsPredicate("a && b || c") MyClass aAndBOrC; + // :: error: method.invocation.invalid + m1.f(); + } - @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParens; + void testPrecedence2() { + MyClass m1 = new MyClass(); - @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndC; + m1.a(); + // :: error: method.invocation.invalid + m1.f(); + } - @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndA; + void testPrecedence3() { + MyClass m1 = new MyClass(); - @CalledMethodsPredicate("b && c") MyClass bAndC; + m1.b(); + // :: error: method.invocation.invalid + m1.f(); + } - @CalledMethodsPredicate("(b && c)") MyClass bAndCParens; + void testPrecedence4() { + MyClass m1 = new MyClass(); - void a() {} + m1.a(); + m1.b(); + m1.f(); + } - void b() {} + void testPrecedence5() { + MyClass m1 = new MyClass(); - void c(@CalledMethodsPredicate("a || b") MyClass this) {} + m1.a(); + m1.c(); + m1.f(); + } - void d(@CalledMethodsPredicate("a && b") MyClass this) {} + void testPrecedence6() { + MyClass m1 = new MyClass(); - void e(@CalledMethodsPredicate("a || (b && c)") MyClass this) {} + m1.b(); + m1.c(); + m1.f(); + } - void f(@CalledMethodsPredicate("a && b || c") MyClass this) {} + private static class MyClass { - static void testAssignability1(@CalledMethodsPredicate("a || b") MyClass cAble) { - cAble.c(); - // :: error: method.invocation.invalid - cAble.d(); - // :: error: method.invocation.invalid - cAble.e(); - // :: error: method.invocation.invalid - cAble.f(); - } + @CalledMethods("a") MyClass cmA; - static void testAssignability2(@CalledMethodsPredicate("a && b") MyClass dAble) { - // These calls would work if subtyping between predicates was by implication. They issue - // errors, because it is not. - // :: error: method.invocation.invalid - dAble.c(); - dAble.d(); - // :: error: method.invocation.invalid - dAble.e(); - // :: error: method.invocation.invalid - dAble.f(); - } + @CalledMethodsPredicate("a") MyClass cmpA; - void testAllAssignability() { - - @CalledMethods("a") MyClass cmALocal; - @CalledMethodsPredicate("a") MyClass cmpALocal; - @CalledMethodsPredicate("a || b") MyClass aOrBLocal; - @CalledMethods({"a", "b"}) MyClass aBLocal; - @CalledMethodsPredicate("a && b") MyClass aAndBLocal; - @CalledMethodsPredicate("a || b && c") MyClass bAndCOrALocal; - @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; - @CalledMethodsPredicate("a && b || c") MyClass aAndBOrCLocal; - @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; - @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndCLocal; - @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndALocal; - @CalledMethodsPredicate("b && c") MyClass bAndCLocal; - @CalledMethodsPredicate("(b && c)") MyClass bAndCParensLocal; - - cmALocal = cmA; - cmALocal = cmpA; - // :: error: assignment.type.incompatible - cmALocal = aOrB; - cmALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = aAndB; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmALocal = bAndC; - // :: error: assignment.type.incompatible - cmALocal = bAndCParens; - - cmpALocal = cmA; - cmpALocal = cmpA; - // :: error: assignment.type.incompatible - cmpALocal = aOrB; - cmpALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = aAndB; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmpALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmpALocal = bAndC; - // :: error: assignment.type.incompatible - cmpALocal = bAndCParens; - - aOrBLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = cmpA; - aOrBLocal = aOrB; - aOrBLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrC; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aBLocal = cmA; - // :: error: (assignment.type.incompatible) - aBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aBLocal = aOrB; - aBLocal = aB; - aBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrB; - aAndBLocal = aB; - aAndBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCParens; - - bAndCOrALocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aOrB; - bAndCOrALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aAndB; - bAndCOrALocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCParens; - - bAndCOrAParensLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aOrB; - bAndCOrAParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCOrA; - bAndCOrAParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = aOrB; - aAndBOrCLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrAParens; - aAndBOrCLocal = aAndBOrC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = aOrB; - aAndBOrCParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrAParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndBOrC; - aAndBOrCParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrCParens; - aOrBAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmpA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrB; - bOrCAndALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bOrCAndALocal = aAndB; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrBAndC; - bOrCAndALocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCLocal = bOrCAndA; - bAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCParensLocal = bAndC; - bAndCParensLocal = bAndCParens; + @CalledMethods({"a", "b"}) MyClass aB; + + @CalledMethodsPredicate("a || b") MyClass aOrB; + + @CalledMethodsPredicate("a && b") MyClass aAndB; + + @CalledMethodsPredicate("a || b && c") MyClass bAndCOrA; + + @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParens; + + @CalledMethodsPredicate("a && b || c") MyClass aAndBOrC; + + @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParens; + + @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndC; + + @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndA; + + @CalledMethodsPredicate("b && c") MyClass bAndC; + + @CalledMethodsPredicate("(b && c)") MyClass bAndCParens; + + void a() {} + + void b() {} + + void c(@CalledMethodsPredicate("a || b") MyClass this) {} + + void d(@CalledMethodsPredicate("a && b") MyClass this) {} + + void e(@CalledMethodsPredicate("a || (b && c)") MyClass this) {} + + void f(@CalledMethodsPredicate("a && b || c") MyClass this) {} + + static void testAssignability1(@CalledMethodsPredicate("a || b") MyClass cAble) { + cAble.c(); + // :: error: method.invocation.invalid + cAble.d(); + // :: error: method.invocation.invalid + cAble.e(); + // :: error: method.invocation.invalid + cAble.f(); + } + + static void testAssignability2(@CalledMethodsPredicate("a && b") MyClass dAble) { + // These calls would work if subtyping between predicates was by implication. They issue + // errors, because it is not. + // :: error: method.invocation.invalid + dAble.c(); + dAble.d(); + // :: error: method.invocation.invalid + dAble.e(); + // :: error: method.invocation.invalid + dAble.f(); + } + + void testAllAssignability() { + + @CalledMethods("a") MyClass cmALocal; + @CalledMethodsPredicate("a") MyClass cmpALocal; + @CalledMethodsPredicate("a || b") MyClass aOrBLocal; + @CalledMethods({"a", "b"}) MyClass aBLocal; + @CalledMethodsPredicate("a && b") MyClass aAndBLocal; + @CalledMethodsPredicate("a || b && c") MyClass bAndCOrALocal; + @CalledMethodsPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; + @CalledMethodsPredicate("a && b || c") MyClass aAndBOrCLocal; + @CalledMethodsPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; + @CalledMethodsPredicate("(a || b) && c") MyClass aOrBAndCLocal; + @CalledMethodsPredicate("a && (b || c)") MyClass bOrCAndALocal; + @CalledMethodsPredicate("b && c") MyClass bAndCLocal; + @CalledMethodsPredicate("(b && c)") MyClass bAndCParensLocal; + + cmALocal = cmA; + cmALocal = cmpA; + // :: error: assignment.type.incompatible + cmALocal = aOrB; + cmALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = aAndB; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmALocal = bAndC; + // :: error: assignment.type.incompatible + cmALocal = bAndCParens; + + cmpALocal = cmA; + cmpALocal = cmpA; + // :: error: assignment.type.incompatible + cmpALocal = aOrB; + cmpALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = aAndB; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmpALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmpALocal = bAndC; + // :: error: assignment.type.incompatible + cmpALocal = bAndCParens; + + aOrBLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = cmpA; + aOrBLocal = aOrB; + aOrBLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrC; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aBLocal = cmA; + // :: error: (assignment.type.incompatible) + aBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aBLocal = aOrB; + aBLocal = aB; + aBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrB; + aAndBLocal = aB; + aAndBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCParens; + + bAndCOrALocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aOrB; + bAndCOrALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aAndB; + bAndCOrALocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCParens; + + bAndCOrAParensLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aOrB; + bAndCOrAParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCOrA; + bAndCOrAParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = aOrB; + aAndBOrCLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrAParens; + aAndBOrCLocal = aAndBOrC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = aOrB; + aAndBOrCParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrAParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndBOrC; + aAndBOrCParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrCParens; + aOrBAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmpA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrB; + bOrCAndALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bOrCAndALocal = aAndB; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrBAndC; + bOrCAndALocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCLocal = bOrCAndA; + bAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCParensLocal = bAndC; + bAndCParensLocal = bAndCParens; + } } - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java b/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java index 94a2c783031..a3c2e20e80b 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsIfRepeatable.java @@ -1,54 +1,55 @@ +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; + import java.io.Closeable; import java.io.IOException; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; public class EnsuresCalledMethodsIfRepeatable { - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") - public boolean close2MissingFirst(Closeable r1, Closeable r2) throws IOException { - r1.close(); - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") - public boolean close2MissingSecond(Closeable r1, Closeable r2) throws IOException { - r2.close(); - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") - public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { - try { - r1.close(); - } finally { - r2.close(); + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public boolean close2MissingFirst(Closeable r1, Closeable r2) throws IOException { + r1.close(); + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; } - return true; - } - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") - public boolean close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { - return close2Correct(r1, r2); - } + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public boolean close2MissingSecond(Closeable r1, Closeable r2) throws IOException { + r2.close(); + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - public static class SubclassWrong extends EnsuresCalledMethodsIfRepeatable { - @Override + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; + try { + r1.close(); + } finally { + r2.close(); + } + return true; } - } - public static class SubclassRight extends EnsuresCalledMethodsIfRepeatable { - @Override - public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { - return false; + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + @EnsuresCalledMethodsIf(expression = "#2", result = true, methods = "close") + public boolean close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { + return close2Correct(r1, r2); + } + + public static class SubclassWrong extends EnsuresCalledMethodsIfRepeatable { + @Override + public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + } + + public static class SubclassRight extends EnsuresCalledMethodsIfRepeatable { + @Override + public boolean close2Correct(Closeable r1, Closeable r2) throws IOException { + return false; + } } - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java b/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java index 184576d194d..02c896ce10f 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsIfSubclass.java @@ -1,29 +1,30 @@ +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; + import java.io.Closeable; import java.io.IOException; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; public class EnsuresCalledMethodsIfSubclass { - public static class Parent { - @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") - public boolean method(Closeable x) throws IOException { - x.close(); - return true; + public static class Parent { + @EnsuresCalledMethodsIf(expression = "#1", result = true, methods = "close") + public boolean method(Closeable x) throws IOException { + x.close(); + return true; + } } - } - public static class SubclassWrong extends Parent { - @Override - public boolean method(Closeable x) throws IOException { - // ::error: (contracts.conditional.postcondition.not.satisfied) - return true; + public static class SubclassWrong extends Parent { + @Override + public boolean method(Closeable x) throws IOException { + // ::error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } - } - public static class SubclassRight extends Parent { - @Override - public boolean method(Closeable x) throws IOException { - return false; + public static class SubclassRight extends Parent { + @Override + public boolean method(Closeable x) throws IOException { + return false; + } } - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java b/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java index 2f38c3fbc9c..7773cd5af43 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsIfTest.java @@ -1,62 +1,63 @@ // Test case for https://github.com/typetools/checker-framework/issues/4699 -import java.io.IOException; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsIf; +import java.io.IOException; + class EnsuresCalledMethodsIfTest { - @EnsuresCalledMethods(value = "#1", methods = "close") - // If `sock` is null, `sock.close()` will not be called, and the method will exit normally, as - // the - // NullPointerException is caught. But, the Called Methods Checker - // assumes the program is free of NullPointerExceptions, delegating verification of that - // property to the Nullness Checker. So, the postcondition is verified. - public static void closeSock(EnsuresCalledMethodsIfTest sock) throws Exception { - if (!sock.isOpen()) { - return; + @EnsuresCalledMethods(value = "#1", methods = "close") + // If `sock` is null, `sock.close()` will not be called, and the method will exit normally, as + // the + // NullPointerException is caught. But, the Called Methods Checker + // assumes the program is free of NullPointerExceptions, delegating verification of that + // property to the Nullness Checker. So, the postcondition is verified. + public static void closeSock(EnsuresCalledMethodsIfTest sock) throws Exception { + if (!sock.isOpen()) { + return; + } + try { + sock.close(); + } catch (Exception e) { + } } - try { - sock.close(); - } catch (Exception e) { - } - } - @EnsuresCalledMethods(value = "#1", methods = "close") - public static void closeSockOK(EnsuresCalledMethodsIfTest sock) throws Exception { - if (!sock.isOpen()) { - return; + @EnsuresCalledMethods(value = "#1", methods = "close") + public static void closeSockOK(EnsuresCalledMethodsIfTest sock) throws Exception { + if (!sock.isOpen()) { + return; + } + try { + sock.close(); + } catch (IOException e) { + } } - try { - sock.close(); - } catch (IOException e) { + + @EnsuresCalledMethods(value = "#1", methods = "close") + public static void closeSockOK1(EnsuresCalledMethodsIfTest sock) throws Exception { + if (!sock.isOpen()) { + return; + } + sock.close(); } - } - @EnsuresCalledMethods(value = "#1", methods = "close") - public static void closeSockOK1(EnsuresCalledMethodsIfTest sock) throws Exception { - if (!sock.isOpen()) { - return; + @EnsuresCalledMethods(value = "#1", methods = "close") + public static void closeSockOK2(EnsuresCalledMethodsIfTest sock) throws Exception { + if (sock.isOpen()) { + sock.close(); + } } - sock.close(); - } - @EnsuresCalledMethods(value = "#1", methods = "close") - public static void closeSockOK2(EnsuresCalledMethodsIfTest sock) throws Exception { - if (sock.isOpen()) { - sock.close(); + void close() throws IOException {} + + @SuppressWarnings( + "calledmethods") // like the JDK's isOpen methods; makes this test case self-contained + @EnsuresCalledMethodsIf( + expression = "this", + result = false, + methods = {"close"}) + boolean isOpen() { + return true; } - } - - void close() throws IOException {} - - @SuppressWarnings( - "calledmethods") // like the JDK's isOpen methods; makes this test case self-contained - @EnsuresCalledMethodsIf( - expression = "this", - result = false, - methods = {"close"}) - boolean isOpen() { - return true; - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java index d1683dc64b6..180856f4d1b 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionRepeatable.java @@ -1,45 +1,46 @@ // Test that @EnsuresCalledMethodsOnException can be repeated. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException; +import java.io.*; + class EnsuresCalledMethodsOnExceptionRepeatable { - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { - r1.close(); - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { - r2.close(); - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") - public void close2Correct(Closeable r1, Closeable r2) throws IOException { - try { - r1.close(); - } finally { - r2.close(); + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { + r1.close(); } - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - @EnsuresCalledMethodsOnException(value = "#2", methods = "close") - public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { - close2Correct(r1, r2); - } - public static class Subclass extends EnsuresCalledMethodsOnExceptionRepeatable { - @Override + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { + r2.close(); + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") public void close2Correct(Closeable r1, Closeable r2) throws IOException { - throw new IOException(); + try { + r1.close(); + } finally { + r2.close(); + } + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + @EnsuresCalledMethodsOnException(value = "#2", methods = "close") + public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { + close2Correct(r1, r2); + } + + public static class Subclass extends EnsuresCalledMethodsOnExceptionRepeatable { + @Override + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close2Correct(Closeable r1, Closeable r2) throws IOException { + throw new IOException(); + } } - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java index af204778861..e799b6f57a7 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionSubclass.java @@ -1,29 +1,30 @@ // Test that @EnsuresCalledMethodsOnException is inherited by overridden methods. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethodsOnException; +import java.io.*; + public class EnsuresCalledMethodsOnExceptionSubclass { - public static class Parent { - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - public void method(Closeable x) throws IOException { - x.close(); + public static class Parent { + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + public void method(Closeable x) throws IOException { + x.close(); + } } - } - public static class SubclassWrong extends Parent { - @Override - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void method(Closeable x) throws IOException { - throw new IOException(); + public static class SubclassWrong extends Parent { + @Override + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void method(Closeable x) throws IOException { + throw new IOException(); + } } - } - public static class SubclassCorrect extends Parent { - @Override - public void method(Closeable x) throws IOException { - // No exception thrown ==> no contract to satisfy! + public static class SubclassCorrect extends Parent { + @Override + public void method(Closeable x) throws IOException { + // No exception thrown ==> no contract to satisfy! + } } - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java index c1cd220ccbc..0defb3e4dbf 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsOnExceptionTest.java @@ -1,106 +1,107 @@ // Test that @EnsuresCalledMethodsOnException behaves as expected. -import java.io.IOException; import org.checkerframework.checker.calledmethods.qual.*; +import java.io.IOException; + public abstract class EnsuresCalledMethodsOnExceptionTest { - static class Resource { - void a() {} + static class Resource { + void a() {} - void b() throws IOException {} - } + void b() throws IOException {} + } - abstract boolean arbitraryChoice(); + abstract boolean arbitraryChoice(); - abstract void throwArbitraryException() throws Exception; + abstract void throwArbitraryException() throws Exception; - @EnsuresCalledMethodsOnException(value = "#1", methods = "b") - void blanketCase(Resource r) throws IOException { - // OK: r.b() counts as called even if it itself throws an exception. - r.b(); - } + @EnsuresCalledMethodsOnException(value = "#1", methods = "b") + void blanketCase(Resource r) throws IOException { + // OK: r.b() counts as called even if it itself throws an exception. + r.b(); + } - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void noCall(Resource r) { - // OK: this method does not throw exceptions. - } + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void noCall(Resource r) { + // OK: this method does not throw exceptions. + } - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - // :: error: (contracts.exceptional.postcondition.not.satisfied) - void callAfterThrow(Resource r) throws Exception { - if (arbitraryChoice()) { - // Not OK: r.a() has not been called yet - throwArbitraryException(); + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + // :: error: (contracts.exceptional.postcondition.not.satisfied) + void callAfterThrow(Resource r) throws Exception { + if (arbitraryChoice()) { + // Not OK: r.a() has not been called yet + throwArbitraryException(); + } + r.a(); } - r.a(); - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void callInFinallyBlock(Resource r) throws Exception { - try { - if (arbitraryChoice()) { - // OK: r.a() will be called in the finally block - throwArbitraryException(); - } - } finally { - r.a(); + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void callInFinallyBlock(Resource r) throws Exception { + try { + if (arbitraryChoice()) { + // OK: r.a() will be called in the finally block + throwArbitraryException(); + } + } finally { + r.a(); + } } - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void callInCatchBlock(Resource r) throws Exception { - try { - if (arbitraryChoice()) { - // OK: r.a() will be called in the catch block - throwArbitraryException(); - } - } catch (Exception e) { - r.a(); - throw e; + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void callInCatchBlock(Resource r) throws Exception { + try { + if (arbitraryChoice()) { + // OK: r.a() will be called in the catch block + throwArbitraryException(); + } + } catch (Exception e) { + r.a(); + throw e; + } } - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - // :: error: (contracts.exceptional.postcondition.not.satisfied) - void callInSpecificCatchBlock(Resource r) throws Exception { - try { - if (arbitraryChoice()) { - // Not OK: the catch block only catches IOException - throwArbitraryException(); - } - } catch (IOException e) { - r.a(); - throw e; + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + // :: error: (contracts.exceptional.postcondition.not.satisfied) + void callInSpecificCatchBlock(Resource r) throws Exception { + try { + if (arbitraryChoice()) { + // Not OK: the catch block only catches IOException + throwArbitraryException(); + } + } catch (IOException e) { + r.a(); + throw e; + } + } + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + abstract void callMethodOnException(Resource r) throws Exception; + + @EnsuresCalledMethodsOnException(value = "#1", methods = "a") + void propagateSubtypeOfException(Resource r) throws Exception { + // OK: the call satisfies our contract + callMethodOnException(r); } - } - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - abstract void callMethodOnException(Resource r) throws Exception; - - @EnsuresCalledMethodsOnException(value = "#1", methods = "a") - void propagateSubtypeOfException(Resource r) throws Exception { - // OK: the call satisfies our contract - callMethodOnException(r); - } - - @EnsuresCalledMethods(value = "#1", methods = "a") - void exploitCalledMethodsOnException(Resource r) throws Exception { - try { - callMethodOnException(r); - } catch (Exception e) { - // OK: the other call ensured the contract - return; + + @EnsuresCalledMethods(value = "#1", methods = "a") + void exploitCalledMethodsOnException(Resource r) throws Exception { + try { + callMethodOnException(r); + } catch (Exception e) { + // OK: the other call ensured the contract + return; + } + // OK: although r.a() was not called, this method promises nothing on exceptional return + throw new Exception("Phooey"); + } + + @EnsuresCalledMethods(value = "#1", methods = "a") + // :: error: (contracts.postcondition.not.satisfied) + void exceptionalCallsDoNotSatisfyNormalPaths(Resource r) throws Exception { + // Not OK: this call is not enough to satisfy our contract, since it only promises something + // on exceptional return. + callMethodOnException(r); } - // OK: although r.a() was not called, this method promises nothing on exceptional return - throw new Exception("Phooey"); - } - - @EnsuresCalledMethods(value = "#1", methods = "a") - // :: error: (contracts.postcondition.not.satisfied) - void exceptionalCallsDoNotSatisfyNormalPaths(Resource r) throws Exception { - // Not OK: this call is not enough to satisfy our contract, since it only promises something - // on exceptional return. - callMethodOnException(r); - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java b/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java index 21753cf25fb..1b08f5f8528 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsRepeatable.java @@ -1,53 +1,54 @@ +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; + import java.io.Closeable; import java.io.IOException; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; class EnsuresCalledMethodsRepeatable { - @EnsuresCalledMethods( - value = "#1", - methods = {"toString"}) - @EnsuresCalledMethods( - value = "#1", - methods = {"hashCode"}) - void test(Object obj) { - obj.toString(); - obj.hashCode(); - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") - // ::error: (contracts.postcondition.not.satisfied) - public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { - r1.close(); - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") - // ::error: (contracts.postcondition.not.satisfied) - public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { - r2.close(); - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") - public void close2Correct(Closeable r1, Closeable r2) throws IOException { - try { - r1.close(); - } finally { - r2.close(); + @EnsuresCalledMethods( + value = "#1", + methods = {"toString"}) + @EnsuresCalledMethods( + value = "#1", + methods = {"hashCode"}) + void test(Object obj) { + obj.toString(); + obj.hashCode(); } - } - @EnsuresCalledMethods(value = "#1", methods = "close") - @EnsuresCalledMethods(value = "#2", methods = "close") - public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { - close2Correct(r1, r2); - } + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") + // ::error: (contracts.postcondition.not.satisfied) + public void close2MissingFirst(Closeable r1, Closeable r2) throws IOException { + r1.close(); + } - public static class Subclass extends EnsuresCalledMethodsRepeatable { - @Override + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") // ::error: (contracts.postcondition.not.satisfied) - public void close2Correct(Closeable r1, Closeable r2) throws IOException {} - } + public void close2MissingSecond(Closeable r1, Closeable r2) throws IOException { + r2.close(); + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") + public void close2Correct(Closeable r1, Closeable r2) throws IOException { + try { + r1.close(); + } finally { + r2.close(); + } + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + @EnsuresCalledMethods(value = "#2", methods = "close") + public void close2CorrectViaCall(Closeable r1, Closeable r2) throws IOException { + close2Correct(r1, r2); + } + + public static class Subclass extends EnsuresCalledMethodsRepeatable { + @Override + // ::error: (contracts.postcondition.not.satisfied) + public void close2Correct(Closeable r1, Closeable r2) throws IOException {} + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java b/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java index 6d6fb71ba0f..75652976706 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsSubclass.java @@ -1,19 +1,20 @@ +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; + import java.io.Closeable; import java.io.IOException; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; public class EnsuresCalledMethodsSubclass { - public static class Parent { - @EnsuresCalledMethods(value = "#1", methods = "close") - public void method(Closeable x) throws IOException { - x.close(); + public static class Parent { + @EnsuresCalledMethods(value = "#1", methods = "close") + public void method(Closeable x) throws IOException { + x.close(); + } } - } - public static class Subclass extends Parent { - @Override - // ::error: (contracts.postcondition.not.satisfied) - public void method(Closeable x) throws IOException {} - } + public static class Subclass extends Parent { + @Override + // ::error: (contracts.postcondition.not.satisfied) + public void method(Closeable x) throws IOException {} + } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java b/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java index 97aa817795e..6e4f676167f 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsThisLub.java @@ -3,41 +3,41 @@ class EnsuresCalledMethodsThisLub { - @EnsuresCalledMethods( - value = "#1", - methods = {"toString", "equals"}) - void call1(Object obj) { - obj.toString(); - obj.equals(null); - } + @EnsuresCalledMethods( + value = "#1", + methods = {"toString", "equals"}) + void call1(Object obj) { + obj.toString(); + obj.equals(null); + } - @EnsuresCalledMethods( - value = "#1", - methods = {"toString", "hashCode"}) - void call2(Object obj) { - obj.toString(); - obj.hashCode(); - } + @EnsuresCalledMethods( + value = "#1", + methods = {"toString", "hashCode"}) + void call2(Object obj) { + obj.toString(); + obj.hashCode(); + } - void test(boolean b) { - if (b) { - call1(this); - } else { - call2(this); + void test(boolean b) { + if (b) { + call1(this); + } else { + call2(this); + } + @CalledMethods("toString") Object obj1 = this; + // :: error: (assignment.type.incompatible) + @CalledMethods({"toString", "equals"}) Object obj2 = this; } - @CalledMethods("toString") Object obj1 = this; - // :: error: (assignment.type.incompatible) - @CalledMethods({"toString", "equals"}) Object obj2 = this; - } - void test_arg(Object arg, boolean b) { - if (b) { - call1(arg); - } else { - call2(arg); + void test_arg(Object arg, boolean b) { + if (b) { + call1(arg); + } else { + call2(arg); + } + @CalledMethods("toString") Object obj1 = arg; + // :: error: (assignment.type.incompatible) + @CalledMethods({"toString", "equals"}) Object obj2 = arg; } - @CalledMethods("toString") Object obj1 = arg; - // :: error: (assignment.type.incompatible) - @CalledMethods({"toString", "equals"}) Object obj2 = arg; - } } diff --git a/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java b/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java index 610446f2af6..ab82f7b0472 100644 --- a/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java +++ b/checker/tests/calledmethods/EnsuresCalledMethodsVarArgsSimple.java @@ -1,38 +1,39 @@ // A simple test for the @EnsuresCalledMethodsVarArgs annotation. +import org.checkerframework.checker.calledmethods.qual.*; + import java.io.IOException; import java.net.Socket; import java.util.List; -import org.checkerframework.checker.calledmethods.qual.*; class EnsuresCalledMethodsVarArgsSimple { - // :: error: ensuresvarargs.unverified - @EnsuresCalledMethodsVarArgs("close") - void closeAll(Socket... sockets) { - for (Socket s : sockets) { - try { - s.close(); - } catch (IOException e) { - } + // :: error: ensuresvarargs.unverified + @EnsuresCalledMethodsVarArgs("close") + void closeAll(Socket... sockets) { + for (Socket s : sockets) { + try { + s.close(); + } catch (IOException e) { + } + } } - } - // :: error: ensuresvarargs.unverified - @EnsuresCalledMethodsVarArgs("close") - // :: error: ensuresvarargs.invalid - void closeAllNotVA(List sockets) { - for (Socket s : sockets) { - try { - s.close(); - } catch (IOException e) { - } + // :: error: ensuresvarargs.unverified + @EnsuresCalledMethodsVarArgs("close") + // :: error: ensuresvarargs.invalid + void closeAllNotVA(List sockets) { + for (Socket s : sockets) { + try { + s.close(); + } catch (IOException e) { + } + } } - } - void test(Socket s1, Socket s2) { - closeAll(s1, s2); - @CalledMethods("close") Socket s1_1 = s1; - @CalledMethods("close") Socket s2_1 = s2; - } + void test(Socket s1, Socket s2) { + closeAll(s1, s2); + @CalledMethods("close") Socket s1_1 = s1; + @CalledMethods("close") Socket s2_1 = s2; + } } diff --git a/checker/tests/calledmethods/ExceptionalPath.java b/checker/tests/calledmethods/ExceptionalPath.java index 084dd7394d7..93bb908adfc 100644 --- a/checker/tests/calledmethods/ExceptionalPath.java +++ b/checker/tests/calledmethods/ExceptionalPath.java @@ -1,17 +1,18 @@ // A test that calling a method with exceptional exit paths leads to that method being // considered "definitely called" (i.e. @CalledMethods of the method) on all paths. +import org.checkerframework.checker.calledmethods.qual.*; + import java.io.IOException; import java.net.Socket; -import org.checkerframework.checker.calledmethods.qual.*; class ExceptionalPath { - void test(Socket s) { - try { - s.close(); - @CalledMethods("close") Socket s1 = s; - } catch (IOException e) { - @CalledMethods("close") Socket s2 = s; + void test(Socket s) { + try { + s.close(); + @CalledMethods("close") Socket s1 = s; + } catch (IOException e) { + @CalledMethods("close") Socket s2 = s; + } } - } } diff --git a/checker/tests/calledmethods/ExceptionalPath2.java b/checker/tests/calledmethods/ExceptionalPath2.java index fbaae26f616..715b729380c 100644 --- a/checker/tests/calledmethods/ExceptionalPath2.java +++ b/checker/tests/calledmethods/ExceptionalPath2.java @@ -1,37 +1,38 @@ -import java.io.IOException; import org.checkerframework.checker.calledmethods.qual.*; +import java.io.IOException; + class ExceptionalPath2 { - interface Resource { - void a(); + interface Resource { + void a(); - void b() throws IOException; - } + void b() throws IOException; + } - Resource r; + Resource r; - // Regression test for an obscure bug: in some cases, the called - // methods transfer function would silently fail to update the - // set of known called methods along exceptional paths. That - // would a spurious precondition error on this method. - @EnsuresCalledMethods( - value = "this.r", - methods = {"b"}) - void test() { - try { - try { - r.a(); - } finally { - r.b(); - } - } catch (IOException ignored) { - // The only way to get here is if `r.b()` started running and - // threw an IOException. We no longer know whether `r.a()` - // has been called, since `r.b()` might have overwritten `r` - // before throwing. - // ::error: (assignment.type.incompatible) - @CalledMethods({"a"}) Resource x = r; + // Regression test for an obscure bug: in some cases, the called + // methods transfer function would silently fail to update the + // set of known called methods along exceptional paths. That + // would a spurious precondition error on this method. + @EnsuresCalledMethods( + value = "this.r", + methods = {"b"}) + void test() { + try { + try { + r.a(); + } finally { + r.b(); + } + } catch (IOException ignored) { + // The only way to get here is if `r.b()` started running and + // threw an IOException. We no longer know whether `r.a()` + // has been called, since `r.b()` might have overwritten `r` + // before throwing. + // ::error: (assignment.type.incompatible) + @CalledMethods({"a"}) Resource x = r; + } } - } } diff --git a/checker/tests/calledmethods/FinallyClose.java b/checker/tests/calledmethods/FinallyClose.java index 25417367031..d3177bb3478 100644 --- a/checker/tests/calledmethods/FinallyClose.java +++ b/checker/tests/calledmethods/FinallyClose.java @@ -1,65 +1,66 @@ // Test case involving some complicated try-finally control flow. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; +import java.io.*; + abstract class FinallyClose { - abstract Closeable alloc() throws IOException; + abstract Closeable alloc() throws IOException; - abstract Closeable derive(Closeable r) throws IOException; + abstract Closeable derive(Closeable r) throws IOException; - abstract String compute(Closeable resource) throws IOException; + abstract String compute(Closeable resource) throws IOException; - abstract void makeNotes() throws IOException; + abstract void makeNotes() throws IOException; - String run1() throws IOException { - Closeable resource = null; - try { - resource = alloc(); - return compute(resource); - } finally { - try { - makeNotes(); - } finally { - closeResource(resource); - } + String run1() throws IOException { + Closeable resource = null; + try { + resource = alloc(); + return compute(resource); + } finally { + try { + makeNotes(); + } finally { + closeResource(resource); + } + } } - } - String run2() throws IOException { - Closeable resource = null; - Closeable subresource = null; - try { - resource = alloc(); - subresource = derive(resource); - return compute(subresource); - } finally { - try { - makeNotes(); - } finally { + String run2() throws IOException { + Closeable resource = null; + Closeable subresource = null; try { - closeResource(subresource); + resource = alloc(); + subresource = derive(resource); + return compute(subresource); } finally { - closeResource(resource); + try { + makeNotes(); + } finally { + try { + closeResource(subresource); + } finally { + closeResource(resource); + } + } } - } } - } - @EnsuresCalledMethods( - value = "#1", - methods = {"close"}) - @EnsuresCalledMethodsOnException( - value = "#1", - methods = {"close"}) - void closeResource(Closeable resource) throws IOException { - if (resource != null) { - try { - resource.close(); - } catch (Exception e) { - System.out.println(e); - } + @EnsuresCalledMethods( + value = "#1", + methods = {"close"}) + @EnsuresCalledMethodsOnException( + value = "#1", + methods = {"close"}) + void closeResource(Closeable resource) throws IOException { + if (resource != null) { + try { + resource.close(); + } catch (Exception e) { + System.out.println(e); + } + } } - } } diff --git a/checker/tests/calledmethods/Generics.java b/checker/tests/calledmethods/Generics.java index bfc75d63fec..3e642a7c03c 100644 --- a/checker/tests/calledmethods/Generics.java +++ b/checker/tests/calledmethods/Generics.java @@ -1,54 +1,55 @@ +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.common.returnsreceiver.qual.*; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.common.returnsreceiver.qual.*; public class Generics { - static interface Symbol { + static interface Symbol { - boolean isStatic(); + boolean isStatic(); - void finalize(@CalledMethods("isStatic") Symbol this); - } + void finalize(@CalledMethods("isStatic") Symbol this); + } - static List<@CalledMethods("isStatic") Symbol> makeList(Symbol s) { - s.isStatic(); - ArrayList<@CalledMethods("isStatic") Symbol> l = new ArrayList<>(); - l.add(s); - return l; - } + static List<@CalledMethods("isStatic") Symbol> makeList(Symbol s) { + s.isStatic(); + ArrayList<@CalledMethods("isStatic") Symbol> l = new ArrayList<>(); + l.add(s); + return l; + } - static void useList() { - Symbol s = null; - for (Symbol t : makeList(s)) { - t.finalize(); + static void useList() { + Symbol s = null; + for (Symbol t : makeList(s)) { + t.finalize(); + } } - } - - // reduced from real-world code - private <@CalledMethods() T extends Symbol> T getMember(Class type, boolean b) { - if (b) { - T sym = getMember(type, !b); - if (sym != null && sym.isStatic()) { - return sym; - } - } else { - T sym = getMember(type, b); - if (sym != null) { - return sym; - } + + // reduced from real-world code + private <@CalledMethods() T extends Symbol> T getMember(Class type, boolean b) { + if (b) { + T sym = getMember(type, !b); + if (sym != null && sym.isStatic()) { + return sym; + } + } else { + T sym = getMember(type, b); + if (sym != null) { + return sym; + } + } + return null; + } + + static Stream stringList() { + String s = "hi"; + // dummy method call + s.contains("h"); + // should infer type Stream<@CalledMethods() String> + return Arrays.asList(s).stream(); } - return null; - } - - static Stream stringList() { - String s = "hi"; - // dummy method call - s.contains("h"); - // should infer type Stream<@CalledMethods() String> - return Arrays.asList(s).stream(); - } } diff --git a/checker/tests/calledmethods/Issue20.java b/checker/tests/calledmethods/Issue20.java index c4b0ac213b7..77a95871ea2 100644 --- a/checker/tests/calledmethods/Issue20.java +++ b/checker/tests/calledmethods/Issue20.java @@ -4,33 +4,33 @@ public class Issue20 { - private boolean enableProtoAnnotations; - - @SuppressWarnings({"unchecked"}) - private T getProtoExtension( - E element, GeneratedExtension extension) { - // Use this method as the chokepoint for all field annotations processing, so we can - // toggle on/off annotations processing in one place. - if (!enableProtoAnnotations) { - return null; + private boolean enableProtoAnnotations; + + @SuppressWarnings({"unchecked"}) + private T getProtoExtension( + E element, GeneratedExtension extension) { + // Use this method as the chokepoint for all field annotations processing, so we can + // toggle on/off annotations processing in one place. + if (!enableProtoAnnotations) { + return null; + } + return (T) element.getOptionFields().get(extension.getDescriptor()); } - return (T) element.getOptionFields().get(extension.getDescriptor()); - } - // stubs of relevant classes - private class Message {} + // stubs of relevant classes + private class Message {} - private class ProtoElement { - public Map getOptionFields() { - return null; + private class ProtoElement { + public Map getOptionFields() { + return null; + } } - } - private class FieldDescriptor {} + private class FieldDescriptor {} - private class GeneratedExtension { - public FieldDescriptor getDescriptor() { - return null; + private class GeneratedExtension { + public FieldDescriptor getDescriptor() { + return null; + } } - } } diff --git a/checker/tests/calledmethods/Issue5402.java b/checker/tests/calledmethods/Issue5402.java index e66dd51cb90..a891fd6806f 100644 --- a/checker/tests/calledmethods/Issue5402.java +++ b/checker/tests/calledmethods/Issue5402.java @@ -9,62 +9,62 @@ public class Issue5402 {} class Issue5402_Ok1 { - public @This Issue5402_Ok1 bar() { - return this; - } + public @This Issue5402_Ok1 bar() { + return this; + } - public void baz(@CalledMethods("bar") Issue5402_Ok1 this) {} + public void baz(@CalledMethods("bar") Issue5402_Ok1 this) {} - public static void test() { - final Issue5402_Ok1 foo = new Issue5402_Ok1(); - foo.bar().baz(); // No error - } + public static void test() { + final Issue5402_Ok1 foo = new Issue5402_Ok1(); + foo.bar().baz(); // No error + } } class Issue5402_Ok2 { - public @This Issue5402_Ok2 bar() { - return this; - } + public @This Issue5402_Ok2 bar() { + return this; + } - @RequiresCalledMethods(value = "this", methods = "bar") - public void baz() {} + @RequiresCalledMethods(value = "this", methods = "bar") + public void baz() {} - public static void test() { - final Issue5402_Ok2 foo = new Issue5402_Ok2(); - final Issue5402_Ok2 foo1 = foo.bar(); - foo1.baz(); // No error - } + public static void test() { + final Issue5402_Ok2 foo = new Issue5402_Ok2(); + final Issue5402_Ok2 foo1 = foo.bar(); + foo1.baz(); // No error + } } class Issue5402_Ok3 { - @Deterministic - public @This Issue5402_Ok3 bar() { - return this; - } + @Deterministic + public @This Issue5402_Ok3 bar() { + return this; + } - @RequiresCalledMethods(value = "this", methods = "bar") - public void baz() {} + @RequiresCalledMethods(value = "this", methods = "bar") + public void baz() {} - public static void test() { - final Issue5402_Ok2 foo = new Issue5402_Ok2(); - final Issue5402_Ok2 foo1 = foo.bar(); - foo1.baz(); // No error - } + public static void test() { + final Issue5402_Ok2 foo = new Issue5402_Ok2(); + final Issue5402_Ok2 foo1 = foo.bar(); + foo1.baz(); // No error + } } class Issue5402_Bad { - public @This Issue5402_Bad bar() { - return this; - } + public @This Issue5402_Bad bar() { + return this; + } - @RequiresCalledMethods(value = "this", methods = "bar") - public void baz() {} + @RequiresCalledMethods(value = "this", methods = "bar") + public void baz() {} - public static void test() { - final Issue5402_Bad foo = new Issue5402_Bad(); - foo.bar().baz(); // Error - } + public static void test() { + final Issue5402_Bad foo = new Issue5402_Bad(); + foo.bar().baz(); // Error + } } diff --git a/checker/tests/calledmethods/Not.java b/checker/tests/calledmethods/Not.java index 4cbe2c2011e..9d91fbdd8fd 100644 --- a/checker/tests/calledmethods/Not.java +++ b/checker/tests/calledmethods/Not.java @@ -2,74 +2,74 @@ public class Not { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - void notA(@CalledMethodsPredicate("!a") Foo this) {} + void notA(@CalledMethodsPredicate("!a") Foo this) {} - void notB(@CalledMethodsPredicate("!b") Foo this) {} - } + void notB(@CalledMethodsPredicate("!b") Foo this) {} + } - void test1(Foo f) { - f.notA(); - f.notB(); - } + void test1(Foo f) { + f.notA(); + f.notB(); + } - void test2(Foo f) { - f.c(); - f.notA(); - f.notB(); - } + void test2(Foo f) { + f.c(); + f.notA(); + f.notB(); + } - void test3(Foo f) { - f.a(); - // :: error: method.invocation.invalid - f.notA(); - f.notB(); - } + void test3(Foo f) { + f.a(); + // :: error: method.invocation.invalid + f.notA(); + f.notB(); + } - void test4(Foo f) { - f.b(); - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test4(Foo f) { + f.b(); + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - // DEMONSTRATION OF UNSOUNDNESS - f.notA(); - } + void test6(Foo f) { + callA(f); + // DEMONSTRATION OF UNSOUNDNESS + f.notA(); + } - void test7(@CalledMethods("a") Foo f) { - // :: error: method.invocation.invalid - f.notA(); - } + void test7(@CalledMethods("a") Foo f) { + // :: error: method.invocation.invalid + f.notA(); + } - void test8(Foo f, boolean test) { - if (test) { - f.a(); - } else { - f.b(); + void test8(Foo f, boolean test) { + if (test) { + f.a(); + } else { + f.b(); + } + // DEMONSTRATION OF UNSOUNDNESS + f.notA(); } - // DEMONSTRATION OF UNSOUNDNESS - f.notA(); - } } diff --git a/checker/tests/calledmethods/Parens.java b/checker/tests/calledmethods/Parens.java index c8f4329b132..c86f80abec5 100644 --- a/checker/tests/calledmethods/Parens.java +++ b/checker/tests/calledmethods/Parens.java @@ -1,5 +1,5 @@ public class Parens { - public synchronized void incrementPushed(long[] pushed, int operationType) { - // ++(pushed[operationType]); - } + public synchronized void incrementPushed(long[] pushed, int operationType) { + // ++(pushed[operationType]); + } } diff --git a/checker/tests/calledmethods/Postconditions.java b/checker/tests/calledmethods/Postconditions.java index 39953087c36..983341aa949 100644 --- a/checker/tests/calledmethods/Postconditions.java +++ b/checker/tests/calledmethods/Postconditions.java @@ -2,131 +2,131 @@ /** Test for postcondition support via @EnsuresCalledMethods. */ public class Postconditions { - void build(@CalledMethods({"a", "b", "c"}) Postconditions this) {} - - void a() {} - - void b() {} - - void c() {} - - @EnsuresCalledMethods(value = "#1", methods = "b") - static void callB(Postconditions x) { - x.b(); - } - - @EnsuresCalledMethods(value = "#1", methods = "b") - // :: error: contracts.postcondition.not.satisfied - static void doesNotCallB(Postconditions x) {} - - @EnsuresCalledMethods( - value = "#1", - methods = {"b", "c"}) - static void callBAndC(Postconditions x) { - x.b(); - x.c(); - } - - static void allInOneMethod() { - Postconditions y = new Postconditions(); - y.a(); - y.b(); - y.c(); - y.build(); - } - - static void invokeCallB() { - Postconditions y = new Postconditions(); - y.a(); - callB(y); - y.c(); - y.build(); - } - - static void invokeCallBLast() { - Postconditions y = new Postconditions(); - y.a(); - y.c(); - callB(y); - y.build(); - } - - static void invokeCallBAndC() { - Postconditions y = new Postconditions(); - y.a(); - callBAndC(y); - y.build(); - } - - static void invokeCallBAndCWrong() { - Postconditions y = new Postconditions(); - callBAndC(y); - // :: error: finalizer.invocation.invalid - y.build(); - } - - @EnsuresCalledMethodsIf( - expression = "#1", - methods = {"a", "b", "c"}, - result = true) - static boolean ensuresABCIfTrue(Postconditions p, boolean b) { - if (b) { - p.a(); - p.b(); - p.c(); - return true; + void build(@CalledMethods({"a", "b", "c"}) Postconditions this) {} + + void a() {} + + void b() {} + + void c() {} + + @EnsuresCalledMethods(value = "#1", methods = "b") + static void callB(Postconditions x) { + x.b(); } - return false; - } - - static void testEnsuresCalledMethodsIf(Postconditions p, boolean b) { - if (ensuresABCIfTrue(p, b)) { - p.build(); - } else { - // :: error: finalizer.invocation.invalid - p.build(); + + @EnsuresCalledMethods(value = "#1", methods = "b") + // :: error: contracts.postcondition.not.satisfied + static void doesNotCallB(Postconditions x) {} + + @EnsuresCalledMethods( + value = "#1", + methods = {"b", "c"}) + static void callBAndC(Postconditions x) { + x.b(); + x.c(); } - } - - @EnsuresCalledMethods(value = "#1", methods = "a") - static void callWithException(Postconditions p) { - try { - p.a(); - throw new java.io.IOException(); - } catch (java.io.IOException e) { + + static void allInOneMethod() { + Postconditions y = new Postconditions(); + y.a(); + y.b(); + y.c(); + y.build(); } - } - - @EnsuresCalledMethods( - value = {"#1", "#2"}, - methods = "a") - static void callAOnBoth(Postconditions p1, Postconditions p2) { - p1.a(); - p2.a(); - } - - @EnsuresCalledMethods( - value = {"#1", "#2"}, - methods = "a") - static void callAOnBothCatchNPE(Postconditions p1, Postconditions p2) { - // postcondition is verified because the checker assumes NullPointerExceptions cannot occur - try { - p1.a(); - } catch (NullPointerException e) { + + static void invokeCallB() { + Postconditions y = new Postconditions(); + y.a(); + callB(y); + y.c(); + y.build(); + } + + static void invokeCallBLast() { + Postconditions y = new Postconditions(); + y.a(); + y.c(); + callB(y); + y.build(); } - p2.a(); - } - - @EnsuresCalledMethods( - value = {"#1", "#2"}, - methods = "a") - static int callAOnBothFinallyNPE(Postconditions p1, Postconditions p2) { - // postcondition is verified because the checker assumes NullPointerExceptions cannot occur - try { - p1.a(); - } finally { - p2.a(); - return 0; + + static void invokeCallBAndC() { + Postconditions y = new Postconditions(); + y.a(); + callBAndC(y); + y.build(); + } + + static void invokeCallBAndCWrong() { + Postconditions y = new Postconditions(); + callBAndC(y); + // :: error: finalizer.invocation.invalid + y.build(); + } + + @EnsuresCalledMethodsIf( + expression = "#1", + methods = {"a", "b", "c"}, + result = true) + static boolean ensuresABCIfTrue(Postconditions p, boolean b) { + if (b) { + p.a(); + p.b(); + p.c(); + return true; + } + return false; + } + + static void testEnsuresCalledMethodsIf(Postconditions p, boolean b) { + if (ensuresABCIfTrue(p, b)) { + p.build(); + } else { + // :: error: finalizer.invocation.invalid + p.build(); + } + } + + @EnsuresCalledMethods(value = "#1", methods = "a") + static void callWithException(Postconditions p) { + try { + p.a(); + throw new java.io.IOException(); + } catch (java.io.IOException e) { + } + } + + @EnsuresCalledMethods( + value = {"#1", "#2"}, + methods = "a") + static void callAOnBoth(Postconditions p1, Postconditions p2) { + p1.a(); + p2.a(); + } + + @EnsuresCalledMethods( + value = {"#1", "#2"}, + methods = "a") + static void callAOnBothCatchNPE(Postconditions p1, Postconditions p2) { + // postcondition is verified because the checker assumes NullPointerExceptions cannot occur + try { + p1.a(); + } catch (NullPointerException e) { + } + p2.a(); + } + + @EnsuresCalledMethods( + value = {"#1", "#2"}, + methods = "a") + static int callAOnBothFinallyNPE(Postconditions p1, Postconditions p2) { + // postcondition is verified because the checker assumes NullPointerExceptions cannot occur + try { + p1.a(); + } finally { + p2.a(); + return 0; + } } - } } diff --git a/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java b/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java index dc9efd5aa91..77454b59bbc 100644 --- a/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java +++ b/checker/tests/calledmethods/RequiresCalledMethodsRepeatable.java @@ -1,32 +1,34 @@ -import java.io.Closeable; import org.checkerframework.checker.calledmethods.qual.*; +import java.io.Closeable; + public class RequiresCalledMethodsRepeatable { - @RequiresCalledMethods(value = "#1", methods = "close") - @RequiresCalledMethods(value = "#2", methods = "close") - public void requires2(Closeable r1, Closeable r2) { - @CalledMethods("close") Closeable r3 = r1; - @CalledMethods("close") Closeable r4 = r2; - } + @RequiresCalledMethods(value = "#1", methods = "close") + @RequiresCalledMethods(value = "#2", methods = "close") + public void requires2(Closeable r1, Closeable r2) { + @CalledMethods("close") Closeable r3 = r1; + @CalledMethods("close") Closeable r4 = r2; + } - public void requires2Wrong(Closeable r1, Closeable r2) { - // ::error: (contracts.precondition.not.satisfied) - requires2(r1, r2); - } + public void requires2Wrong(Closeable r1, Closeable r2) { + // ::error: (contracts.precondition.not.satisfied) + requires2(r1, r2); + } - @RequiresCalledMethods(value = "#1", methods = "close") - @RequiresCalledMethods(value = "#2", methods = "close") - public void requires2Correct(Closeable r1, Closeable r2) { - requires2(r1, r2); - } + @RequiresCalledMethods(value = "#1", methods = "close") + @RequiresCalledMethods(value = "#2", methods = "close") + public void requires2Correct(Closeable r1, Closeable r2) { + requires2(r1, r2); + } - public static class Subclass extends RequiresCalledMethodsRepeatable { - @Override - public void requires2Correct(Closeable r1, Closeable r2) {} + public static class Subclass extends RequiresCalledMethodsRepeatable { + @Override + public void requires2Correct(Closeable r1, Closeable r2) {} - public void caller(Closeable r1, Closeable r2) { - requires2Correct(r1, r2); // OK: we override requires2Correct() with a weaker precondition + public void caller(Closeable r1, Closeable r2) { + requires2Correct( + r1, r2); // OK: we override requires2Correct() with a weaker precondition + } } - } } diff --git a/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java b/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java index e5932f45998..11a51ab0f1d 100644 --- a/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java +++ b/checker/tests/calledmethods/RequiresCalledMethodsSubclass.java @@ -1,25 +1,26 @@ +import org.checkerframework.checker.calledmethods.qual.RequiresCalledMethods; + import java.io.Closeable; import java.io.IOException; -import org.checkerframework.checker.calledmethods.qual.RequiresCalledMethods; public class RequiresCalledMethodsSubclass { - public static class Parent { - @RequiresCalledMethods(value = "#1", methods = "close") - public void method(Closeable x) throws IOException {} + public static class Parent { + @RequiresCalledMethods(value = "#1", methods = "close") + public void method(Closeable x) throws IOException {} - public void caller(Closeable x) throws IOException { - // ::error: (contracts.precondition.not.satisfied) - method(x); + public void caller(Closeable x) throws IOException { + // ::error: (contracts.precondition.not.satisfied) + method(x); + } } - } - public static class Subclass extends Parent { - @Override - public void method(Closeable x) throws IOException {} + public static class Subclass extends Parent { + @Override + public void method(Closeable x) throws IOException {} - public void caller(Closeable x) throws IOException { - method(x); // OK: we override method() with a weaker precondition + public void caller(Closeable x) throws IOException { + method(x); // OK: we override method() with a weaker precondition + } } - } } diff --git a/checker/tests/calledmethods/RequiresCalledMethodsTest.java b/checker/tests/calledmethods/RequiresCalledMethodsTest.java index a83bd506cac..741194ae804 100644 --- a/checker/tests/calledmethods/RequiresCalledMethodsTest.java +++ b/checker/tests/calledmethods/RequiresCalledMethodsTest.java @@ -4,19 +4,19 @@ class RequiresCalledMethodsTest { - Object foo; + Object foo; - @RequiresCalledMethods(value = "this.foo", methods = "toString") - void afterFooToString() {} + @RequiresCalledMethods(value = "this.foo", methods = "toString") + void afterFooToString() {} - void test_ok() { - foo.toString(); - afterFooToString(); - } + void test_ok() { + foo.toString(); + afterFooToString(); + } - void test_bad() { - // foo.toString(); - // :: error: contracts.precondition.not.satisfied - afterFooToString(); - } + void test_bad() { + // foo.toString(); + // :: error: contracts.precondition.not.satisfied + afterFooToString(); + } } diff --git a/checker/tests/calledmethods/SimpleFluentInference.java b/checker/tests/calledmethods/SimpleFluentInference.java index 7f5eb3fe5e5..ae17946aecd 100644 --- a/checker/tests/calledmethods/SimpleFluentInference.java +++ b/checker/tests/calledmethods/SimpleFluentInference.java @@ -3,61 +3,61 @@ /* Simple inference of a fluent builder */ public class SimpleFluentInference { - SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { - return this; - } - - SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { - return this; - } - - @This SimpleFluentInference a() { - return this; - } - - @This SimpleFluentInference b() { - return this; - } - - // intentionally does not have an @This annotation - SimpleFluentInference c() { - return new SimpleFluentInference(); - } - - static void doStuffCorrect() { - SimpleFluentInference s = new SimpleFluentInference().a().b().build(); - } - - static void doStuffWrong() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - // :: error: finalizer.invocation.invalid - .build(); - } - - static void doStuffRightWeak() { - SimpleFluentInference s = new SimpleFluentInference().a().weakbuild(); - } - - static void noReturnsReceiverAnno() { - SimpleFluentInference s = - new SimpleFluentInference() - .a() - .b() - .c() + SimpleFluentInference build(@CalledMethods({"a", "b"}) SimpleFluentInference this) { + return this; + } + + SimpleFluentInference weakbuild(@CalledMethods({"a"}) SimpleFluentInference this) { + return this; + } + + @This SimpleFluentInference a() { + return this; + } + + @This SimpleFluentInference b() { + return this; + } + + // intentionally does not have an @This annotation + SimpleFluentInference c() { + return new SimpleFluentInference(); + } + + static void doStuffCorrect() { + SimpleFluentInference s = new SimpleFluentInference().a().b().build(); + } + + static void doStuffWrong() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + // :: error: finalizer.invocation.invalid + .build(); + } + + static void doStuffRightWeak() { + SimpleFluentInference s = new SimpleFluentInference().a().weakbuild(); + } + + static void noReturnsReceiverAnno() { + SimpleFluentInference s = + new SimpleFluentInference() + .a() + .b() + .c() + // :: error: finalizer.invocation.invalid + .build(); + } + + static void fluentLoop() { + SimpleFluentInference s = new SimpleFluentInference().a(); + int i = 10; + while (i > 0) { // :: error: finalizer.invocation.invalid - .build(); - } - - static void fluentLoop() { - SimpleFluentInference s = new SimpleFluentInference().a(); - int i = 10; - while (i > 0) { - // :: error: finalizer.invocation.invalid - s.b().build(); - i--; - s = new SimpleFluentInference(); - } - } + s.b().build(); + i--; + s = new SimpleFluentInference(); + } + } } diff --git a/checker/tests/calledmethods/SimpleInference.java b/checker/tests/calledmethods/SimpleInference.java index 7d8af81c903..0f86b96de5b 100644 --- a/checker/tests/calledmethods/SimpleInference.java +++ b/checker/tests/calledmethods/SimpleInference.java @@ -2,19 +2,19 @@ /* The simplest inference test case Martin could think of */ public class SimpleInference { - void build(@CalledMethods({"a"}) SimpleInference this) {} + void build(@CalledMethods({"a"}) SimpleInference this) {} - void a() {} + void a() {} - static void doStuffCorrect() { - SimpleInference s = new SimpleInference(); - s.a(); - s.build(); - } + static void doStuffCorrect() { + SimpleInference s = new SimpleInference(); + s.a(); + s.build(); + } - static void doStuffWrong() { - SimpleInference s = new SimpleInference(); - // :: error: finalizer.invocation.invalid - s.build(); - } + static void doStuffWrong() { + SimpleInference s = new SimpleInference(); + // :: error: finalizer.invocation.invalid + s.build(); + } } diff --git a/checker/tests/calledmethods/SimpleInferenceMerge.java b/checker/tests/calledmethods/SimpleInferenceMerge.java index 98b6d15ca41..601e34572f3 100644 --- a/checker/tests/calledmethods/SimpleInferenceMerge.java +++ b/checker/tests/calledmethods/SimpleInferenceMerge.java @@ -2,37 +2,37 @@ /* The simplest inference test case Martin could think of */ public class SimpleInferenceMerge { - void build(@CalledMethods({"a", "b"}) SimpleInferenceMerge this) {} + void build(@CalledMethods({"a", "b"}) SimpleInferenceMerge this) {} - void a() {} + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - static void doStuffCorrectMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.a(); - s.c(); + static void doStuffCorrectMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.a(); + s.c(); + } + s.build(); } - s.build(); - } - static void doStuffWrongMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.c(); + static void doStuffWrongMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.c(); + } + // :: error: finalizer.invocation.invalid + s.build(); } - // :: error: finalizer.invocation.invalid - s.build(); - } } diff --git a/checker/tests/calledmethods/Subtyping.java b/checker/tests/calledmethods/Subtyping.java index 1364c4b33c8..4d53e30f637 100644 --- a/checker/tests/calledmethods/Subtyping.java +++ b/checker/tests/calledmethods/Subtyping.java @@ -2,73 +2,73 @@ // basic subtyping checks public class Subtyping { - @CalledMethods({}) Object top_top(@CalledMethods({}) Object o) { - return o; - } - - @CalledMethods({}) Object top_a(@CalledMethods({"a"}) Object o) { - return o; - } - - @CalledMethods({}) Object top_ab(@CalledMethods({"a", "b"}) Object o) { - return o; - } - - @CalledMethods({}) Object top_bot(@CalledMethodsBottom Object o) { - return o; - } - - @CalledMethods({"a"}) Object a_top(@CalledMethods({}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethods({"a"}) Object a_a(@CalledMethods({"a"}) Object o) { - return o; - } - - @CalledMethods({"a"}) Object a_ab(@CalledMethods({"a", "b"}) Object o) { - return o; - } - - @CalledMethods({"a"}) Object a_bot(@CalledMethodsBottom Object o) { - return o; - } - - @CalledMethods({"a", "b"}) Object ab_top(@CalledMethods({}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethods({"a", "b"}) Object ab_a(@CalledMethods({"a"}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethods({"a", "b"}) Object ab_ab(@CalledMethods({"a", "b"}) Object o) { - return o; - } - - @CalledMethods({"a", "b"}) Object ab_bot(@CalledMethodsBottom Object o) { - return o; - } - - @CalledMethodsBottom Object bot_top(@CalledMethods({}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethodsBottom Object bot_a(@CalledMethods({"a"}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethodsBottom Object bot_ab(@CalledMethods({"a", "b"}) Object o) { - // :: error: return.type.incompatible - return o; - } - - @CalledMethodsBottom Object bot_bot(@CalledMethodsBottom Object o) { - return o; - } + @CalledMethods({}) Object top_top(@CalledMethods({}) Object o) { + return o; + } + + @CalledMethods({}) Object top_a(@CalledMethods({"a"}) Object o) { + return o; + } + + @CalledMethods({}) Object top_ab(@CalledMethods({"a", "b"}) Object o) { + return o; + } + + @CalledMethods({}) Object top_bot(@CalledMethodsBottom Object o) { + return o; + } + + @CalledMethods({"a"}) Object a_top(@CalledMethods({}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethods({"a"}) Object a_a(@CalledMethods({"a"}) Object o) { + return o; + } + + @CalledMethods({"a"}) Object a_ab(@CalledMethods({"a", "b"}) Object o) { + return o; + } + + @CalledMethods({"a"}) Object a_bot(@CalledMethodsBottom Object o) { + return o; + } + + @CalledMethods({"a", "b"}) Object ab_top(@CalledMethods({}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethods({"a", "b"}) Object ab_a(@CalledMethods({"a"}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethods({"a", "b"}) Object ab_ab(@CalledMethods({"a", "b"}) Object o) { + return o; + } + + @CalledMethods({"a", "b"}) Object ab_bot(@CalledMethodsBottom Object o) { + return o; + } + + @CalledMethodsBottom Object bot_top(@CalledMethods({}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethodsBottom Object bot_a(@CalledMethods({"a"}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethodsBottom Object bot_ab(@CalledMethods({"a", "b"}) Object o) { + // :: error: return.type.incompatible + return o; + } + + @CalledMethodsBottom Object bot_bot(@CalledMethodsBottom Object o) { + return o; + } } diff --git a/checker/tests/calledmethods/UnparsablePredicate.java b/checker/tests/calledmethods/UnparsablePredicate.java index c87456e0a11..78bf61d2bfe 100644 --- a/checker/tests/calledmethods/UnparsablePredicate.java +++ b/checker/tests/calledmethods/UnparsablePredicate.java @@ -2,49 +2,49 @@ public class UnparsablePredicate { - // :: error: predicate.invalid - void unclosedOpen(@CalledMethodsPredicate("(foo && bar") Object x) {} + // :: error: predicate.invalid + void unclosedOpen(@CalledMethodsPredicate("(foo && bar") Object x) {} - // :: error: predicate.invalid - void unopenedClose(@CalledMethodsPredicate("foo || bar)") Object x) {} + // :: error: predicate.invalid + void unopenedClose(@CalledMethodsPredicate("foo || bar)") Object x) {} - // :: error: predicate.invalid - void badKeywords1(@CalledMethodsPredicate("foo OR bar") Object x) {} + // :: error: predicate.invalid + void badKeywords1(@CalledMethodsPredicate("foo OR bar") Object x) {} - // :: error: predicate.invalid - void badKeywords2(@CalledMethodsPredicate("foo AND bar") Object x) {} + // :: error: predicate.invalid + void badKeywords2(@CalledMethodsPredicate("foo AND bar") Object x) {} - // These tests check that valid Java identifiers don't cause problems - // when evaluating predicates. Examples of identifiers from - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 + // These tests check that valid Java identifiers don't cause problems + // when evaluating predicates. Examples of identifiers from + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 - void jls0Example(@CalledMethodsPredicate("String") Object x) {} + void jls0Example(@CalledMethodsPredicate("String") Object x) {} - void callJls0Example(@CalledMethods("String") Object y) { - jls0Example(y); - } + void callJls0Example(@CalledMethods("String") Object y) { + jls0Example(y); + } - void jls1Example(@CalledMethodsPredicate("i3") Object x) {} + void jls1Example(@CalledMethodsPredicate("i3") Object x) {} - void callJls1Example(@CalledMethods("i3") Object y) { - jls1Example(y); - } + void callJls1Example(@CalledMethods("i3") Object y) { + jls1Example(y); + } - // TODO: support Unicode. SPEL, which we use to parse expressions, doesn't. - /* void jls2Example(@CalledMethodsPredicate("αρετη") Object x) { } - void callJls2Example(@CalledMethods("αρετη") Object y) { - jls2Example(y); - }*/ + // TODO: support Unicode. SPEL, which we use to parse expressions, doesn't. + /* void jls2Example(@CalledMethodsPredicate("αρετη") Object x) { } + void callJls2Example(@CalledMethods("αρετη") Object y) { + jls2Example(y); + }*/ - void jls3Example(@CalledMethodsPredicate("MAX_VALUE") Object x) {} + void jls3Example(@CalledMethodsPredicate("MAX_VALUE") Object x) {} - void callJls3Example(@CalledMethods("MAX_VALUE") Object y) { - jls3Example(y); - } + void callJls3Example(@CalledMethods("MAX_VALUE") Object y) { + jls3Example(y); + } - void jls4Example(@CalledMethodsPredicate("isLetterOrDigit") Object x) {} + void jls4Example(@CalledMethodsPredicate("isLetterOrDigit") Object x) {} - void callJls4Example(@CalledMethods("isLetterOrDigit") Object y) { - jls4Example(y); - } + void callJls4Example(@CalledMethods("isLetterOrDigit") Object y) { + jls4Example(y); + } } diff --git a/checker/tests/calledmethods/Xor.java b/checker/tests/calledmethods/Xor.java index 97e37e6d4d7..af65d3a12f5 100644 --- a/checker/tests/calledmethods/Xor.java +++ b/checker/tests/calledmethods/Xor.java @@ -2,65 +2,65 @@ public class Xor { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - // SPEL doesn't support XOR directly (it represents exponentiation as ^ instead), - // so use a standard gate encoding - void aXorB(@CalledMethodsPredicate("(a || b) && !(a && b)") Foo this) {} - } + // SPEL doesn't support XOR directly (it represents exponentiation as ^ instead), + // so use a standard gate encoding + void aXorB(@CalledMethodsPredicate("(a || b) && !(a && b)") Foo this) {} + } - void test1(Foo f) { - // :: error: method.invocation.invalid - f.aXorB(); - } + void test1(Foo f) { + // :: error: method.invocation.invalid + f.aXorB(); + } - void test2(Foo f) { - f.c(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test2(Foo f) { + f.c(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void test3(Foo f) { - f.a(); - f.aXorB(); - } + void test3(Foo f) { + f.a(); + f.aXorB(); + } - void test4(Foo f) { - f.b(); - f.aXorB(); - } + void test4(Foo f) { + f.b(); + f.aXorB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - f.b(); - // DEMONSTRATION OF UNSOUNDNESS - f.aXorB(); - } + void test6(Foo f) { + callA(f); + f.b(); + // DEMONSTRATION OF UNSOUNDNESS + f.aXorB(); + } - void test7(@CalledMethods("a") Foo f) { - f.aXorB(); - } + void test7(@CalledMethods("a") Foo f) { + f.aXorB(); + } - void test8(Foo f) { - callA(f); - // THIS IS AN UNAVOIDABLE FALSE POSITIVE - // :: error: method.invocation.invalid - f.aXorB(); - } + void test8(Foo f) { + callA(f); + // THIS IS AN UNAVOIDABLE FALSE POSITIVE + // :: error: method.invocation.invalid + f.aXorB(); + } } diff --git a/checker/tests/command-line/issue618/TwoCheckers.java b/checker/tests/command-line/issue618/TwoCheckers.java index 0c188f3876c..33e446d6e77 100644 --- a/checker/tests/command-line/issue618/TwoCheckers.java +++ b/checker/tests/command-line/issue618/TwoCheckers.java @@ -23,10 +23,10 @@ public class TwoCheckers { - void client(String a) { - // :: error: (argument.type.incompatible) - requiresUntainted(a); - } + void client(String a) { + // :: error: (argument.type.incompatible) + requiresUntainted(a); + } - void requiresUntainted(@Untainted String b) {} + void requiresUntainted(@Untainted String b) {} } diff --git a/checker/tests/compilermsg/Basic.java b/checker/tests/compilermsg/Basic.java index e7362b99242..fb503bc5a8b 100644 --- a/checker/tests/compilermsg/Basic.java +++ b/checker/tests/compilermsg/Basic.java @@ -2,9 +2,9 @@ public class Basic { - void required(@CompilerMessageKey String in) {} + void required(@CompilerMessageKey String in) {} - void test() { - required("test.property"); - } + void test() { + required("test.property"); + } } diff --git a/checker/tests/compilermsg/Diamonds.java b/checker/tests/compilermsg/Diamonds.java index 9da14e49194..836dd667d70 100644 --- a/checker/tests/compilermsg/Diamonds.java +++ b/checker/tests/compilermsg/Diamonds.java @@ -1,18 +1,19 @@ +import org.checkerframework.checker.compilermsgs.qual.UnknownCompilerMessageKey; + import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; -import org.checkerframework.checker.compilermsgs.qual.UnknownCompilerMessageKey; public class Diamonds { - void method(List arrays) { - arrays = new ArrayList<>(new HashSet<>(arrays)); - arrays = newArrayList(new HashSet<>(arrays)); - arrays = new ArrayList<>(new HashSet<@UnknownCompilerMessageKey String>(arrays)); - } + void method(List arrays) { + arrays = new ArrayList<>(new HashSet<>(arrays)); + arrays = newArrayList(new HashSet<>(arrays)); + arrays = new ArrayList<>(new HashSet<@UnknownCompilerMessageKey String>(arrays)); + } - ArrayList newArrayList(Collection param) { - return new ArrayList<>(param); - } + ArrayList newArrayList(Collection param) { + return new ArrayList<>(param); + } } diff --git a/checker/tests/custom-alias/CustomAliasedAnnotations.java b/checker/tests/custom-alias/CustomAliasedAnnotations.java index dc2d92e1a00..6ef76da0faa 100644 --- a/checker/tests/custom-alias/CustomAliasedAnnotations.java +++ b/checker/tests/custom-alias/CustomAliasedAnnotations.java @@ -2,28 +2,28 @@ public class CustomAliasedAnnotations { - void useNonNullAnnotations() { - // :: error: (assignment.type.incompatible) - @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; - // :: error: (assignment.type.incompatible) - @NonNull Object nn2 = null; - } + void useNonNullAnnotations() { + // :: error: (assignment.type.incompatible) + @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; + // :: error: (assignment.type.incompatible) + @NonNull Object nn2 = null; + } - void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations2(@Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations2(@Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - void setMutable1() {} + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + void setMutable1() {} - @Pure - // :: warning: (purity.deterministic.void.method) - void setMutable2() {} + @Pure + // :: warning: (purity.deterministic.void.method) + void setMutable2() {} } diff --git a/checker/tests/disbaruse-records/DisbarredClass.java b/checker/tests/disbaruse-records/DisbarredClass.java index 255084d17f6..e24cb6430d3 100644 --- a/checker/tests/disbaruse-records/DisbarredClass.java +++ b/checker/tests/disbaruse-records/DisbarredClass.java @@ -2,40 +2,40 @@ class DisbarredClass { - @DisbarUse String barred; - String fine; - - DisbarredClass() {} - - @DisbarUse - DisbarredClass(String param) {} - - DisbarredClass(@DisbarUse Integer param) {} - - DisbarredClass(@DisbarUse Long param) { - // :: error: (disbar.use) - param = 7L; - // :: error: (disbar.use) - param.toString(); - } - - @DisbarUse - void disbarredMethod() {} - - void invalid() { - // :: error: (disbar.use) - disbarredMethod(); - // :: error: (disbar.use) - new DisbarredClass(""); - // :: error: (disbar.use) - barred = ""; - // :: error: (disbar.use) - int x = barred.length(); - } - - void valid() { - new DisbarredClass(); - fine = ""; - int x = fine.length(); - } + @DisbarUse String barred; + String fine; + + DisbarredClass() {} + + @DisbarUse + DisbarredClass(String param) {} + + DisbarredClass(@DisbarUse Integer param) {} + + DisbarredClass(@DisbarUse Long param) { + // :: error: (disbar.use) + param = 7L; + // :: error: (disbar.use) + param.toString(); + } + + @DisbarUse + void disbarredMethod() {} + + void invalid() { + // :: error: (disbar.use) + disbarredMethod(); + // :: error: (disbar.use) + new DisbarredClass(""); + // :: error: (disbar.use) + barred = ""; + // :: error: (disbar.use) + int x = barred.length(); + } + + void valid() { + new DisbarredClass(); + fine = ""; + int x = fine.length(); + } } diff --git a/checker/tests/disbaruse-records/DisbarredRecord.java b/checker/tests/disbaruse-records/DisbarredRecord.java index 34c9cdae464..77a890ac6a0 100644 --- a/checker/tests/disbaruse-records/DisbarredRecord.java +++ b/checker/tests/disbaruse-records/DisbarredRecord.java @@ -2,20 +2,20 @@ record DisbarredRecord(@DisbarUse String barred, String fine) { - DisbarredRecord { - // :: error: (disbar.use) - int x = barred.length(); - } + DisbarredRecord { + // :: error: (disbar.use) + int x = barred.length(); + } - void invalid() { - // :: error: (disbar.use) - barred(); - // :: error: (disbar.use) - int x = barred.length(); - } + void invalid() { + // :: error: (disbar.use) + barred(); + // :: error: (disbar.use) + int x = barred.length(); + } - void valid() { - fine(); - int x = fine.length(); - } + void valid() { + fine(); + int x = fine.length(); + } } diff --git a/checker/tests/disbaruse-records/DisbarredRecordByStubs.java b/checker/tests/disbaruse-records/DisbarredRecordByStubs.java index 13730e728f1..e31f975f08d 100644 --- a/checker/tests/disbaruse-records/DisbarredRecordByStubs.java +++ b/checker/tests/disbaruse-records/DisbarredRecordByStubs.java @@ -1,19 +1,19 @@ record DisbarredRecordByStubs(String barred, String fine) { - DisbarredRecordByStubs { - // :: error: (disbar.use) - int x = barred.length(); - } + DisbarredRecordByStubs { + // :: error: (disbar.use) + int x = barred.length(); + } - void invalid() { - // :: error: (disbar.use) - barred(); - // :: error: (disbar.use) - int x = barred.length(); - } + void invalid() { + // :: error: (disbar.use) + barred(); + // :: error: (disbar.use) + int x = barred.length(); + } - void valid() { - fine(); - int x = fine.length(); - } + void valid() { + fine(); + int x = fine.length(); + } } diff --git a/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java b/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java index 8df03d950aa..a3b0d9b698e 100644 --- a/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java +++ b/checker/tests/disbaruse-records/DisbarredRecordByStubs2.java @@ -1,22 +1,22 @@ record DisbarredRecordByStubs2(String barred, String fine) { - // Annotation isn't copied to explicitly declared constructor parameters: - DisbarredRecordByStubs2(String barred, String fine) { - int x = barred.length(); - // :: error: (disbar.use) - this.barred = ""; - this.fine = fine; - } + // Annotation isn't copied to explicitly declared constructor parameters: + DisbarredRecordByStubs2(String barred, String fine) { + int x = barred.length(); + // :: error: (disbar.use) + this.barred = ""; + this.fine = fine; + } - void invalid() { - // :: error: (disbar.use) - barred(); - // :: error: (disbar.use) - int x = barred.length(); - } + void invalid() { + // :: error: (disbar.use) + barred(); + // :: error: (disbar.use) + int x = barred.length(); + } - void valid() { - fine(); - int x = fine.length(); - } + void valid() { + fine(); + int x = fine.length(); + } } diff --git a/checker/tests/fenum/CastsFenum.java b/checker/tests/fenum/CastsFenum.java index edc3c146623..a3be27f799e 100644 --- a/checker/tests/fenum/CastsFenum.java +++ b/checker/tests/fenum/CastsFenum.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.fenum.qual.Fenum; public class CastsFenum { - @Fenum("A") Object fa; + @Fenum("A") Object fa; - void m(Object p, @Fenum("A") Object a) { - fa = (Object) a; - // :: error: (assignment.type.incompatible) - fa = (Object) p; + void m(Object p, @Fenum("A") Object a) { + fa = (Object) a; + // :: error: (assignment.type.incompatible) + fa = (Object) p; - // TODO: How can we test the behavior for - // instanceof? It should be the same as for casts. - // if (p instanceof Object) { - } + // TODO: How can we test the behavior for + // instanceof? It should be the same as for casts. + // if (p instanceof Object) { + } } diff --git a/checker/tests/fenum/CatchFenumUnqualified.java b/checker/tests/fenum/CatchFenumUnqualified.java index 320770d03e5..cb54ce747c5 100644 --- a/checker/tests/fenum/CatchFenumUnqualified.java +++ b/checker/tests/fenum/CatchFenumUnqualified.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.fenum.qual.Fenum; public class CatchFenumUnqualified { - void method() { - try { - } catch ( - // :: error: (exception.parameter.invalid) - @Fenum("A") RuntimeException e) { + void method() { + try { + } catch ( + // :: error: (exception.parameter.invalid) + @Fenum("A") RuntimeException e) { - } - try { - // :: error: (exception.parameter.invalid) - } catch (@Fenum("A") NullPointerException | @Fenum("A") ArrayIndexOutOfBoundsException e) { + } + try { + // :: error: (exception.parameter.invalid) + } catch (@Fenum("A") NullPointerException | @Fenum("A") ArrayIndexOutOfBoundsException e) { + } } - } } diff --git a/checker/tests/fenum/TestFlow.java b/checker/tests/fenum/TestFlow.java index 23c849f0700..38e1ba3982a 100644 --- a/checker/tests/fenum/TestFlow.java +++ b/checker/tests/fenum/TestFlow.java @@ -2,39 +2,39 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestFlow { - public final @Fenum("A") Object ACONST1 = new Object(); + public final @Fenum("A") Object ACONST1 = new Object(); - public final @Fenum("B") Object BCONST1 = new Object(); + public final @Fenum("B") Object BCONST1 = new Object(); } class FenumUser { - @Fenum("A") Object state1 = new TestFlow().ACONST1; + @Fenum("A") Object state1 = new TestFlow().ACONST1; - void foo(TestFlow t) { - // :: error: (method.invocation.invalid) - state1.hashCode(); - state1 = null; - state1.hashCode(); - m(); - // :: error: (method.invocation.invalid) - state1.hashCode(); + void foo(TestFlow t) { + // :: error: (method.invocation.invalid) + state1.hashCode(); + state1 = null; + state1.hashCode(); + m(); + // :: error: (method.invocation.invalid) + state1.hashCode(); - Object o = new Object(); - o = t.ACONST1; - methodA(o); - // :: error: (argument.type.incompatible) - methodB(o); + Object o = new Object(); + o = t.ACONST1; + methodA(o); + // :: error: (argument.type.incompatible) + methodB(o); - o = t.BCONST1; - // :: error: (argument.type.incompatible) - methodA(o); - methodB(o); - } + o = t.BCONST1; + // :: error: (argument.type.incompatible) + methodA(o); + methodB(o); + } - void m() {} + void m() {} - void methodA(@Fenum("A") Object a) {} + void methodA(@Fenum("A") Object a) {} - void methodB(@Fenum("B") Object a) {} + void methodB(@Fenum("B") Object a) {} } diff --git a/checker/tests/fenum/TestInstance.java b/checker/tests/fenum/TestInstance.java index 01f4504fbd2..6f93172706e 100644 --- a/checker/tests/fenum/TestInstance.java +++ b/checker/tests/fenum/TestInstance.java @@ -2,43 +2,43 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestInstance { - public final @Fenum("A") Object ACONST1 = new Object(); - public final @Fenum("A") Object ACONST2 = new Object(); - public final @Fenum("A") Object ACONST3 = new Object(); + public final @Fenum("A") Object ACONST1 = new Object(); + public final @Fenum("A") Object ACONST2 = new Object(); + public final @Fenum("A") Object ACONST3 = new Object(); - public final @Fenum("B") Object BCONST1 = new Object(); - public final @Fenum("B") Object BCONST2 = new Object(); - public final @Fenum("B") Object BCONST3 = new Object(); + public final @Fenum("B") Object BCONST1 = new Object(); + public final @Fenum("B") Object BCONST2 = new Object(); + public final @Fenum("B") Object BCONST3 = new Object(); } class FenumUserTestInstance { - @Fenum("A") Object state1 = new TestInstance().ACONST1; + @Fenum("A") Object state1 = new TestInstance().ACONST1; - // :: error: (assignment.type.incompatible) - @Fenum("B") Object state2 = new TestInstance().ACONST1; - - void foo(TestInstance t) { // :: error: (assignment.type.incompatible) - state1 = new Object(); + @Fenum("B") Object state2 = new TestInstance().ACONST1; - state1 = t.ACONST2; - state1 = t.ACONST3; + void foo(TestInstance t) { + // :: error: (assignment.type.incompatible) + state1 = new Object(); - // :: error: (assignment.type.incompatible) - state1 = t.BCONST1; + state1 = t.ACONST2; + state1 = t.ACONST3; + + // :: error: (assignment.type.incompatible) + state1 = t.BCONST1; - // :: error: (method.invocation.invalid) - state1.hashCode(); - // :: error: (method.invocation.invalid) - t.ACONST1.hashCode(); + // :: error: (method.invocation.invalid) + state1.hashCode(); + // :: error: (method.invocation.invalid) + t.ACONST1.hashCode(); - // sanity check: unqualified instantiation and call work. - Object o = new Object(); - o.hashCode(); + // sanity check: unqualified instantiation and call work. + Object o = new Object(); + o.hashCode(); - if (t.ACONST1 == t.ACONST2) {} + if (t.ACONST1 == t.ACONST2) {} - // :: error: (binary.type.incompatible) - if (t.ACONST1 == t.BCONST2) {} - } + // :: error: (binary.type.incompatible) + if (t.ACONST1 == t.BCONST2) {} + } } diff --git a/checker/tests/fenum/TestPrimitive.java b/checker/tests/fenum/TestPrimitive.java index 40a20c348b4..49db9f7bdfd 100644 --- a/checker/tests/fenum/TestPrimitive.java +++ b/checker/tests/fenum/TestPrimitive.java @@ -2,45 +2,45 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestPrimitive { - public final @Fenum("A") int ACONST1 = 1; - public final @Fenum("A") int ACONST2 = 2; - public final @Fenum("A") int ACONST3 = 3; + public final @Fenum("A") int ACONST1 = 1; + public final @Fenum("A") int ACONST2 = 2; + public final @Fenum("A") int ACONST3 = 3; - public final @Fenum("B") int BCONST1 = 4; - public final @Fenum("B") int BCONST2 = 5; - public final @Fenum("B") int BCONST3 = 6; + public final @Fenum("B") int BCONST1 = 4; + public final @Fenum("B") int BCONST2 = 5; + public final @Fenum("B") int BCONST3 = 6; } class FenumUserTestPrimitive { - @Fenum("A") int state1 = new TestPrimitive().ACONST1; + @Fenum("A") int state1 = new TestPrimitive().ACONST1; - @Fenum("A") int state3 = this.state1; + @Fenum("A") int state3 = this.state1; - // :: error: (assignment.type.incompatible) - @Fenum("B") int state2 = new TestPrimitive().ACONST1; - - void foo(TestPrimitive t) { // :: error: (assignment.type.incompatible) - state1 = 4; + @Fenum("B") int state2 = new TestPrimitive().ACONST1; - state1 = t.ACONST2; - state1 = t.ACONST3; + void foo(TestPrimitive t) { + // :: error: (assignment.type.incompatible) + state1 = 4; - // :: error: (assignment.type.incompatible) - state1 = t.BCONST1; + state1 = t.ACONST2; + state1 = t.ACONST3; - if (t.ACONST1 < t.ACONST2) { - // ok - } + // :: error: (assignment.type.incompatible) + state1 = t.BCONST1; - // :: error: (binary.type.incompatible) - if (t.ACONST1 < t.BCONST2) {} - // :: error: (binary.type.incompatible) - if (t.ACONST1 == t.BCONST2) {} + if (t.ACONST1 < t.ACONST2) { + // ok + } - // :: error: (binary.type.incompatible) - if (t.ACONST1 < 5) {} - // :: error: (binary.type.incompatible) - if (t.ACONST1 == 5) {} - } + // :: error: (binary.type.incompatible) + if (t.ACONST1 < t.BCONST2) {} + // :: error: (binary.type.incompatible) + if (t.ACONST1 == t.BCONST2) {} + + // :: error: (binary.type.incompatible) + if (t.ACONST1 < 5) {} + // :: error: (binary.type.incompatible) + if (t.ACONST1 == 5) {} + } } diff --git a/checker/tests/fenum/TestStatic.java b/checker/tests/fenum/TestStatic.java index 19aa578eead..e679856a157 100644 --- a/checker/tests/fenum/TestStatic.java +++ b/checker/tests/fenum/TestStatic.java @@ -2,52 +2,52 @@ @SuppressWarnings("fenum:assignment.type.incompatible") public class TestStatic { - public static final @Fenum("A") int ACONST1 = 1; - public static final @Fenum("A") int ACONST2 = 2; - public static final @Fenum("A") int ACONST3 = 3; + public static final @Fenum("A") int ACONST1 = 1; + public static final @Fenum("A") int ACONST2 = 2; + public static final @Fenum("A") int ACONST3 = 3; - public static final @Fenum("B") int BCONST1 = 4; - public static final @Fenum("B") int BCONST2 = 5; - public static final @Fenum("B") int BCONST3 = 6; + public static final @Fenum("B") int BCONST1 = 4; + public static final @Fenum("B") int BCONST2 = 5; + public static final @Fenum("B") int BCONST3 = 6; } class FenumUserTestStatic { - @Fenum("A") int state1 = TestStatic.ACONST1; + @Fenum("A") int state1 = TestStatic.ACONST1; - // :: error: (assignment.type.incompatible) - @Fenum("B") int state2 = TestStatic.ACONST1; + // :: error: (assignment.type.incompatible) + @Fenum("B") int state2 = TestStatic.ACONST1; - void bar(@Fenum("A") int p) {} + void bar(@Fenum("A") int p) {} - void foo() { - // :: error: (assignment.type.incompatible) - state1 = 4; + void foo() { + // :: error: (assignment.type.incompatible) + state1 = 4; - state1 = TestStatic.ACONST2; - state1 = TestStatic.ACONST3; + state1 = TestStatic.ACONST2; + state1 = TestStatic.ACONST3; - state2 = TestStatic.BCONST3; + state2 = TestStatic.BCONST3; - // :: error: (assignment.type.incompatible) - state1 = TestStatic.BCONST1; - - // :: error: (argument.type.incompatible) - bar(5); - bar(TestStatic.ACONST1); - // :: error: (argument.type.incompatible) - bar(TestStatic.BCONST1); - } - - @SuppressWarnings("fenum") - void ignoreAll() { - state1 = 4; - bar(5); - } - - @SuppressWarnings("fenum:assignment.type.incompatible") - void ignoreOne() { - state1 = 4; - // :: error: (argument.type.incompatible) - bar(5); - } + // :: error: (assignment.type.incompatible) + state1 = TestStatic.BCONST1; + + // :: error: (argument.type.incompatible) + bar(5); + bar(TestStatic.ACONST1); + // :: error: (argument.type.incompatible) + bar(TestStatic.BCONST1); + } + + @SuppressWarnings("fenum") + void ignoreAll() { + state1 = 4; + bar(5); + } + + @SuppressWarnings("fenum:assignment.type.incompatible") + void ignoreOne() { + state1 = 4; + // :: error: (argument.type.incompatible) + bar(5); + } } diff --git a/checker/tests/fenum/TestSwitch.java b/checker/tests/fenum/TestSwitch.java index a9a4c89ebd4..8085c5a289b 100644 --- a/checker/tests/fenum/TestSwitch.java +++ b/checker/tests/fenum/TestSwitch.java @@ -1,38 +1,38 @@ import org.checkerframework.checker.fenum.qual.Fenum; public class TestSwitch { - void m() { - @SuppressWarnings("fenum:assignment.type.incompatible") - @Fenum("TEST") final int annotated = 3; + void m() { + @SuppressWarnings("fenum:assignment.type.incompatible") + @Fenum("TEST") final int annotated = 3; - @SuppressWarnings("fenum:assignment.type.incompatible") - @Fenum("TEST") final int annotated2 = 6; + @SuppressWarnings("fenum:assignment.type.incompatible") + @Fenum("TEST") final int annotated2 = 6; - int plain = 9; // FenumUnqualified + int plain = 9; // FenumUnqualified - switch (plain) { - // :: error: (switch.type.incompatible) - case annotated: - default: - } + switch (plain) { + // :: error: (switch.type.incompatible) + case annotated: + default: + } - // un-annotated still working - switch (plain) { - case 1: - case 2: - default: - } + // un-annotated still working + switch (plain) { + case 1: + case 2: + default: + } - switch (annotated) { - // :: error: (switch.type.incompatible) - case 45: - default: - } + switch (annotated) { + // :: error: (switch.type.incompatible) + case 45: + default: + } - // annotated working - switch (annotated) { - case annotated2: - default: + // annotated working + switch (annotated) { + case annotated2: + default: + } } - } } diff --git a/checker/tests/fenum/TypeVariable.java b/checker/tests/fenum/TypeVariable.java index f1122830a98..75f5cc672c4 100644 --- a/checker/tests/fenum/TypeVariable.java +++ b/checker/tests/fenum/TypeVariable.java @@ -2,11 +2,11 @@ * Make sure that unqualified type variables still work. */ public class TypeVariable { - X m() { - return null; - } + X m() { + return null; + } - Y bar() { - return null; - } + Y bar() { + return null; + } } diff --git a/checker/tests/fenum/UpperBoundsInByteCode.java b/checker/tests/fenum/UpperBoundsInByteCode.java index 6d1ae033ea5..c7bae5d732d 100644 --- a/checker/tests/fenum/UpperBoundsInByteCode.java +++ b/checker/tests/fenum/UpperBoundsInByteCode.java @@ -2,24 +2,24 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class UpperBoundsInByteCode { - UncheckedByteCode<@Fenum("Foo") String> foo; - UncheckedByteCode<@Fenum("Bar") Object> bar; + UncheckedByteCode<@Fenum("Foo") String> foo; + UncheckedByteCode<@Fenum("Bar") Object> bar; - void typeVarWithNonObjectUpperBound(@Fenum("A") int a) { - // :: error: (type.arguments.not.inferred) - UncheckedByteCode.methodWithTypeVarBoundedByNumber(a); - UncheckedByteCode.methodWithTypeVarBoundedByNumber(1); - } + void typeVarWithNonObjectUpperBound(@Fenum("A") int a) { + // :: error: (type.arguments.not.inferred) + UncheckedByteCode.methodWithTypeVarBoundedByNumber(a); + UncheckedByteCode.methodWithTypeVarBoundedByNumber(1); + } - void wildcardsInByteCode() { - UncheckedByteCode.unboundedWildcardParam(foo); - UncheckedByteCode.lowerboundedWildcardParam(bar); - // :: error: (argument.type.incompatible) - UncheckedByteCode.upperboundedWildcardParam(foo); - } + void wildcardsInByteCode() { + UncheckedByteCode.unboundedWildcardParam(foo); + UncheckedByteCode.lowerboundedWildcardParam(bar); + // :: error: (argument.type.incompatible) + UncheckedByteCode.upperboundedWildcardParam(foo); + } - // :: error: (type.argument.type.incompatible) - SourceCode<@Fenum("Foo") String> foo2; + // :: error: (type.argument.type.incompatible) + SourceCode<@Fenum("Foo") String> foo2; - class SourceCode {} + class SourceCode {} } diff --git a/checker/tests/fenumswing/FlowBreak.java b/checker/tests/fenumswing/FlowBreak.java index 4dfeff49b7b..20c51a69ca3 100644 --- a/checker/tests/fenumswing/FlowBreak.java +++ b/checker/tests/fenumswing/FlowBreak.java @@ -2,37 +2,37 @@ import org.checkerframework.checker.fenum.qual.SwingVerticalOrientation; public class FlowBreak { - static @SwingHorizontalOrientation Object CENTER; - static @SwingHorizontalOrientation Object LEFT; + static @SwingHorizontalOrientation Object CENTER; + static @SwingHorizontalOrientation Object LEFT; - boolean flag; + boolean flag; - @SwingHorizontalOrientation Object testInference() { - Object o; - // initially o is @FenumTop - o = null; - // o is @Bottom - while (flag) { - if (flag) { - o = CENTER; - // o is @SwingHorizontalOrientation - } else { - o = new @SwingVerticalOrientation Object(); - // o is @SwingVerticalOrientation - break; - } - // We can only come here from the then-branch, the else-branch is dead. - // Therefore, we only take the annotations at the end of the then-branch and ignore the - // results of the else-branch. - // Therefore, o is @SwingHorizontalOrientation and the following is valid: - @SwingHorizontalOrientation Object pla = o; + @SwingHorizontalOrientation Object testInference() { + Object o; + // initially o is @FenumTop + o = null; + // o is @Bottom + while (flag) { + if (flag) { + o = CENTER; + // o is @SwingHorizontalOrientation + } else { + o = new @SwingVerticalOrientation Object(); + // o is @SwingVerticalOrientation + break; + } + // We can only come here from the then-branch, the else-branch is dead. + // Therefore, we only take the annotations at the end of the then-branch and ignore the + // results of the else-branch. + // Therefore, o is @SwingHorizontalOrientation and the following is valid: + @SwingHorizontalOrientation Object pla = o; + } + // Here we have to merge three paths: + // 1. The entry to the loop, if the condition is false [@Bottom] + // 2. The normal end of the loop body [@SwingHorizontalOrientation] + // 3. The path from the break to here [@SwingVerticalOrientation] + // Currently, the third path is ignored and we do not get this error message. + // :: error: (return.type.incompatible) + return o; } - // Here we have to merge three paths: - // 1. The entry to the loop, if the condition is false [@Bottom] - // 2. The normal end of the loop body [@SwingHorizontalOrientation] - // 3. The path from the break to here [@SwingVerticalOrientation] - // Currently, the third path is ignored and we do not get this error message. - // :: error: (return.type.incompatible) - return o; - } } diff --git a/checker/tests/fenumswing/IdentityArrayListTest.java b/checker/tests/fenumswing/IdentityArrayListTest.java index 3359da58102..8b60b7c6c1b 100644 --- a/checker/tests/fenumswing/IdentityArrayListTest.java +++ b/checker/tests/fenumswing/IdentityArrayListTest.java @@ -1,36 +1,37 @@ -import java.util.Arrays; import org.checkerframework.checker.fenum.qual.FenumTop; +import java.util.Arrays; + /* * This test case violates an assertion in the compiler. * It does not depend on the Fenum Checker, it breaks for any checker. */ public class IdentityArrayListTest { - // The type of the third argument to Arrays.copyOf should be: - // Class - // But the annotated JDK does not have annotations for the Fenum Checker. - @SuppressWarnings("argument.type.incompatible") - public T[] toArray(T[] a) { - // Warnings only with -Alint=cast:strict. - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - return (T[]) Arrays.copyOf(null, 0, a.getClass()); - } + // The type of the third argument to Arrays.copyOf should be: + // Class + // But the annotated JDK does not have annotations for the Fenum Checker. + @SuppressWarnings("argument.type.incompatible") + public T[] toArray(T[] a) { + // Warnings only with -Alint=cast:strict. + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + return (T[]) Arrays.copyOf(null, 0, a.getClass()); + } - public T[] toArray2(T[] a) { - wc(null, 0, new java.util.LinkedList()); - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - return (T[]) myCopyOf(null, 0, a.getClass()); - } + public T[] toArray2(T[] a) { + wc(null, 0, new java.util.LinkedList()); + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + return (T[]) myCopyOf(null, 0, a.getClass()); + } - public static T[] myCopyOf( - U[] original, int newLength, Class newType) { - return null; - } + public static T[] myCopyOf( + U[] original, int newLength, Class newType) { + return null; + } - public static T[] wc( - U[] original, int newLength, java.util.List arr) { - return null; - } + public static T[] wc( + U[] original, int newLength, java.util.List arr) { + return null; + } } diff --git a/checker/tests/fenumswing/PolyTest.java b/checker/tests/fenumswing/PolyTest.java index eab1c7695e7..faa80d86f2a 100644 --- a/checker/tests/fenumswing/PolyTest.java +++ b/checker/tests/fenumswing/PolyTest.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.fenum.qual.SwingCompassDirection; public class PolyTest { - public static boolean flag = false; + public static boolean flag = false; - @PolyFenum String merge( - @PolyFenum String a, - @PolyFenum String b, - @SwingCompassDirection String x, - @FenumBottom String bot) { - // Test lub with poly and a qualifier that isn't top or bottom. - String y = flag ? a : x; - // :: error: (assignment.type.incompatible) - @PolyFenum String y2 = flag ? a : x; + @PolyFenum String merge( + @PolyFenum String a, + @PolyFenum String b, + @SwingCompassDirection String x, + @FenumBottom String bot) { + // Test lub with poly and a qualifier that isn't top or bottom. + String y = flag ? a : x; + // :: error: (assignment.type.incompatible) + @PolyFenum String y2 = flag ? a : x; - // Test lub with poly and bottom. - // Test lub with poly and bottom. - @PolyFenum String z = flag ? a : bot; + // Test lub with poly and bottom. + // Test lub with poly and bottom. + @PolyFenum String z = flag ? a : bot; - // Test lub with two polys - return flag ? a : b; - } + // Test lub with two polys + return flag ? a : b; + } } diff --git a/checker/tests/fenumswing/SwingTest.java b/checker/tests/fenumswing/SwingTest.java index d946ac323ad..69b808f8289 100644 --- a/checker/tests/fenumswing/SwingTest.java +++ b/checker/tests/fenumswing/SwingTest.java @@ -5,263 +5,263 @@ public class SwingTest { - static @SwingVerticalOrientation int BOTTOM; - static @SwingCompassDirection int NORTH; - - static @SwingHorizontalOrientation int CENTER; - static @SwingHorizontalOrientation int LEFT; - - static void m(@SwingVerticalOrientation int box) {} - - public static void main(String[] args) { - // ok - m(BOTTOM); - - // :: error: (argument.type.incompatible) - m(5); - - // :: error: (argument.type.incompatible) - m(NORTH); - } - - @SuppressWarnings("swingverticalorientation") - static void ignoreAll() { - m(NORTH); - - @SwingVerticalOrientation int b = 5; - } - - @SuppressWarnings("fenum:argument.type.incompatible") - static void ignoreOne() { - m(NORTH); - - // :: error: (assignment.type.incompatible) - @SwingVerticalOrientation int b = 5; - } - - void testNull() { - // This enum should only be used on ints, but I wanted to - // test how an Object enum and null interact. - @SwingVerticalOrientation Object box = null; - } - - @SwingVerticalOrientation Object testNullb() { - return null; - } - - @SwingVerticalOrientation Object testNullc() { - Object o = null; - return o; - } - - @SwingVerticalOrientation int testInference0() { - // :: error: (assignment.type.incompatible) - @SwingVerticalOrientation int boxint = 5; - int box = boxint; - return box; - } - - Object testInference1() { - Object o = new String(); - return o; - } - - @SwingVerticalOrientation int testInference2() { - int i = BOTTOM; - return i; - } - - @SwingVerticalOrientation Object testInference3() { - // :: error: (assignment.type.incompatible) - @SwingVerticalOrientation Object boxobj = new Object(); - Object obox = boxobj; - return obox; - } - - int testInference4() { - int aint = 5; - return aint; - } - - Object testInference5() { - Object o = null; - if (5 == 4) { - o = new Object(); + static @SwingVerticalOrientation int BOTTOM; + static @SwingCompassDirection int NORTH; + + static @SwingHorizontalOrientation int CENTER; + static @SwingHorizontalOrientation int LEFT; + + static void m(@SwingVerticalOrientation int box) {} + + public static void main(String[] args) { + // ok + m(BOTTOM); + + // :: error: (argument.type.incompatible) + m(5); + + // :: error: (argument.type.incompatible) + m(NORTH); + } + + @SuppressWarnings("swingverticalorientation") + static void ignoreAll() { + m(NORTH); + + @SwingVerticalOrientation int b = 5; + } + + @SuppressWarnings("fenum:argument.type.incompatible") + static void ignoreOne() { + m(NORTH); + + // :: error: (assignment.type.incompatible) + @SwingVerticalOrientation int b = 5; + } + + void testNull() { + // This enum should only be used on ints, but I wanted to + // test how an Object enum and null interact. + @SwingVerticalOrientation Object box = null; + } + + @SwingVerticalOrientation Object testNullb() { + return null; + } + + @SwingVerticalOrientation Object testNullc() { + Object o = null; + return o; + } + + @SwingVerticalOrientation int testInference0() { + // :: error: (assignment.type.incompatible) + @SwingVerticalOrientation int boxint = 5; + int box = boxint; + return box; + } + + Object testInference1() { + Object o = new String(); + return o; + } + + @SwingVerticalOrientation int testInference2() { + int i = BOTTOM; + return i; + } + + @SwingVerticalOrientation Object testInference3() { + // :: error: (assignment.type.incompatible) + @SwingVerticalOrientation Object boxobj = new Object(); + Object obox = boxobj; + return obox; + } + + int testInference4() { + int aint = 5; + return aint; + } + + Object testInference5() { + Object o = null; + if (5 == 4) { + o = new Object(); + } + return o; + } + + Object testInference5b() { + Object o = null; + if (5 == 4) { + o = new Object(); + } else { + } + // the empty else branch actually covers a different code path! + return o; + } + + @SwingHorizontalOrientation int testInference5c() { + int o; + if (5 == 4) { + o = CENTER; + } else { + o = LEFT; + } + return o; + } + + int testInference6() { + int last = 0; + last += 1; + return last; + } + + @SwingBoxOrientation Object testInference7() { + Object o = new @SwingVerticalOrientation Object(); + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + // o = new @SwingVerticalOrientation Object(); + } + return o; + } + + @SwingBoxOrientation Object testInference7b() { + Object o; + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + } + return o; } - return o; - } - - Object testInference5b() { - Object o = null; - if (5 == 4) { - o = new Object(); - } else { + + @SwingBoxOrientation Object testInference7c() { + Object o = null; + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + } + return o; } - // the empty else branch actually covers a different code path! - return o; - } - - @SwingHorizontalOrientation int testInference5c() { - int o; - if (5 == 4) { - o = CENTER; - } else { - o = LEFT; + + int s1 = 0; + int c = 3; + + void testInference8() { + int s2 = s1; + s1 = (s2 &= c); } - return o; - } - - int testInference6() { - int last = 0; - last += 1; - return last; - } - - @SwingBoxOrientation Object testInference7() { - Object o = new @SwingVerticalOrientation Object(); - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - // o = new @SwingVerticalOrientation Object(); + + void testInference8b() { + // :: error: (assignment.type.incompatible) + @SwingHorizontalOrientation int s2 = 5; + // :: error: (compound.assignment.type.incompatible) + s2 += 1; + + // :: error: (assignment.type.incompatible) + s1 = (s2 += s2); + + // :: error: (assignment.type.incompatible) + @SwingHorizontalOrientation String str = "abc"; + // yes, somebody in the Swing API really wrote this. + str += null; } - return o; - } - - @SwingBoxOrientation Object testInference7b() { - Object o; - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); + + boolean flag; + + Object testInference9() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } + } + // :: error: (return.type.incompatible) + return o; } - return o; - } - - @SwingBoxOrientation Object testInference7c() { - Object o = null; - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); + + @SwingBoxOrientation Object testInference9b() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } + } + return o; } - return o; - } - - int s1 = 0; - int c = 3; - - void testInference8() { - int s2 = s1; - s1 = (s2 &= c); - } - - void testInference8b() { - // :: error: (assignment.type.incompatible) - @SwingHorizontalOrientation int s2 = 5; - // :: error: (compound.assignment.type.incompatible) - s2 += 1; - - // :: error: (assignment.type.incompatible) - s1 = (s2 += s2); - - // :: error: (assignment.type.incompatible) - @SwingHorizontalOrientation String str = "abc"; - // yes, somebody in the Swing API really wrote this. - str += null; - } - - boolean flag; - - Object testInference9() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } + + @SwingHorizontalOrientation Object testInference9c() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } + } + // :: error: (return.type.incompatible) + return o; } - // :: error: (return.type.incompatible) - return o; - } - - @SwingBoxOrientation Object testInference9b() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } + + @SwingVerticalOrientation Object testInference9d() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + // note that this break makes a difference! + break; + } + } + // :: error: (return.type.incompatible) + return o; } - return o; - } - - @SwingHorizontalOrientation Object testInference9c() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } + + @SwingBoxOrientation Object testInference9e() { + Object o = null; + while (flag) { + if (5 == 4) { + o = new @SwingHorizontalOrientation Object(); + } else { + o = new @SwingVerticalOrientation Object(); + } + } + return o; } - // :: error: (return.type.incompatible) - return o; - } - - @SwingVerticalOrientation Object testInference9d() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - // note that this break makes a difference! - break; - } + + /* TODO: Flow inference does not handle dead branches correctly. + * The return statement is only reachable with i being unqualified. + * However, the else-branch does not initialize the variable, leaving it + * at the @FenumTop initial value. + int testInferenceThrow() { + int i; + if ( 5==4 ) { + i = 5; + } else { + throw new RuntimeException("bla"); + } + return i; } - // :: error: (return.type.incompatible) - return o; - } - - @SwingBoxOrientation Object testInference9e() { - Object o = null; - while (flag) { - if (5 == 4) { - o = new @SwingHorizontalOrientation Object(); - } else { - o = new @SwingVerticalOrientation Object(); - } + */ + + @SwingVerticalOrientation Object testDefaulting0() { + @org.checkerframework.framework.qual.DefaultQualifier(SwingVerticalOrientation.class) + // :: error: (assignment.type.incompatible) + Object o = new String(); + return o; } - return o; - } - - /* TODO: Flow inference does not handle dead branches correctly. - * The return statement is only reachable with i being unqualified. - * However, the else-branch does not initialize the variable, leaving it - * at the @FenumTop initial value. - int testInferenceThrow() { - int i; - if ( 5==4 ) { - i = 5; - } else { - throw new RuntimeException("bla"); - } - return i; - } - */ - - @SwingVerticalOrientation Object testDefaulting0() { - @org.checkerframework.framework.qual.DefaultQualifier(SwingVerticalOrientation.class) - // :: error: (assignment.type.incompatible) - Object o = new String(); - return o; - } } diff --git a/checker/tests/fenumswing/TypeVariable.java b/checker/tests/fenumswing/TypeVariable.java index f1122830a98..75f5cc672c4 100644 --- a/checker/tests/fenumswing/TypeVariable.java +++ b/checker/tests/fenumswing/TypeVariable.java @@ -2,11 +2,11 @@ * Make sure that unqualified type variables still work. */ public class TypeVariable { - X m() { - return null; - } + X m() { + return null; + } - Y bar() { - return null; - } + Y bar() { + return null; + } } diff --git a/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java index 42fd0131aa0..705681b6dee 100644 --- a/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/formatter-unchecked-defaults/TestUncheckedByteCode.java @@ -1,18 +1,18 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - Object field; + Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - field = param.getInt(1); - field = param.getInteger(i); - field = param.identity("hello"); + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + field = param.getInt(1); + field = param.getInteger(i); + field = param.identity("hello"); - // String and Object are relevant types and must be annotated in bytecode - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - } + // String and Object are relevant types and must be annotated in bytecode + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + } } diff --git a/checker/tests/formatter/ConversionBasic.java b/checker/tests/formatter/ConversionBasic.java index 5e4dc44c295..d1b4413052f 100644 --- a/checker/tests/formatter/ConversionBasic.java +++ b/checker/tests/formatter/ConversionBasic.java @@ -1,76 +1,77 @@ +import org.checkerframework.checker.formatter.qual.ConversionCategory; +import org.checkerframework.checker.formatter.qual.Format; + import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Formatter; -import org.checkerframework.checker.formatter.qual.ConversionCategory; -import org.checkerframework.checker.formatter.qual.Format; public class ConversionBasic { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - // test GENERAL, there is nothing we can do wrong - @Format({ConversionCategory.GENERAL}) String s = "%s"; - f.format("Suc-%s-ful", "cess"); - f.format("%b", 4); - f.format("%B", 7.5); - f.format("%h", new Date()); - f.format("%H", Integer.valueOf(4)); - f.format("%s", new ArrayList()); + // test GENERAL, there is nothing we can do wrong + @Format({ConversionCategory.GENERAL}) String s = "%s"; + f.format("Suc-%s-ful", "cess"); + f.format("%b", 4); + f.format("%B", 7.5); + f.format("%h", new Date()); + f.format("%H", Integer.valueOf(4)); + f.format("%s", new ArrayList()); - // test CHAR - @Format({ConversionCategory.CHAR}) String c = "%c"; - f.format("%c", 'c'); - f.format("%c", (byte) 67); - f.format("%c", (int) 67); - f.format("%c", Character.valueOf('c')); - f.format("%c", Byte.valueOf((byte) 67)); - f.format("%c", Short.valueOf((short) 67)); - f.format("%C", Integer.valueOf(67)); - // :: error: (argument.type.incompatible) - f.format("%c", 7.5); - // :: error: (argument.type.incompatible) - f.format("%C", "Value"); + // test CHAR + @Format({ConversionCategory.CHAR}) String c = "%c"; + f.format("%c", 'c'); + f.format("%c", (byte) 67); + f.format("%c", (int) 67); + f.format("%c", Character.valueOf('c')); + f.format("%c", Byte.valueOf((byte) 67)); + f.format("%c", Short.valueOf((short) 67)); + f.format("%C", Integer.valueOf(67)); + // :: error: (argument.type.incompatible) + f.format("%c", 7.5); + // :: error: (argument.type.incompatible) + f.format("%C", "Value"); - // test INT - @Format({ConversionCategory.INT}) String i = "%d"; - f.format("%d", (byte) 67); - f.format("%o", (short) 67); - f.format("%x", (int) 67); - f.format("%X", (long) 67); - f.format("%d", Long.valueOf(67)); - f.format("%d", BigInteger.ONE); - // :: error: (argument.type.incompatible) - f.format("%d", 'c'); - // :: error: (argument.type.incompatible) - f.format("%d", BigDecimal.ONE); + // test INT + @Format({ConversionCategory.INT}) String i = "%d"; + f.format("%d", (byte) 67); + f.format("%o", (short) 67); + f.format("%x", (int) 67); + f.format("%X", (long) 67); + f.format("%d", Long.valueOf(67)); + f.format("%d", BigInteger.ONE); + // :: error: (argument.type.incompatible) + f.format("%d", 'c'); + // :: error: (argument.type.incompatible) + f.format("%d", BigDecimal.ONE); - // test FLOAT - @Format({ConversionCategory.FLOAT}) String d = "%f"; - f.format("%e", (float) 67.1); - f.format("%E", (double) 67.3); - f.format("%f", Float.valueOf(42.5f)); - f.format("%g", Double.valueOf(42.5)); - f.format("%G", 67.87); - f.format("%a", BigDecimal.ONE); - // :: error: (argument.type.incompatible) - f.format("%A", 1325); - // :: error: (argument.type.incompatible) - f.format("%a", BigInteger.ONE); + // test FLOAT + @Format({ConversionCategory.FLOAT}) String d = "%f"; + f.format("%e", (float) 67.1); + f.format("%E", (double) 67.3); + f.format("%f", Float.valueOf(42.5f)); + f.format("%g", Double.valueOf(42.5)); + f.format("%G", 67.87); + f.format("%a", BigDecimal.ONE); + // :: error: (argument.type.incompatible) + f.format("%A", 1325); + // :: error: (argument.type.incompatible) + f.format("%a", BigInteger.ONE); - // test TIME - @Format({ConversionCategory.TIME}) String t = "%tT"; - f.format("%tD", new Date()); - f.format("%TM", (long) 32165456); - f.format("%TD", Calendar.getInstance()); - // :: error: (argument.type.incompatible) - f.format("%tD", 1321543512); - // :: error: (argument.type.incompatible) - f.format("%tD", new Object()); + // test TIME + @Format({ConversionCategory.TIME}) String t = "%tT"; + f.format("%tD", new Date()); + f.format("%TM", (long) 32165456); + f.format("%TD", Calendar.getInstance()); + // :: error: (argument.type.incompatible) + f.format("%tD", 1321543512); + // :: error: (argument.type.incompatible) + f.format("%tD", new Object()); - System.out.println(f.toString()); - f.close(); - } + System.out.println(f.toString()); + f.close(); + } } diff --git a/checker/tests/formatter/ConversionNull.java b/checker/tests/formatter/ConversionNull.java index 2fe0bdcbe83..8ac77b13dde 100644 --- a/checker/tests/formatter/ConversionNull.java +++ b/checker/tests/formatter/ConversionNull.java @@ -2,29 +2,29 @@ import java.util.Formatter; public class ConversionNull { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - f.format("%d %s", 0, null); - f.format("%s", (Object) null); + f.format("%d %s", 0, null); + f.format("%s", (Object) null); - f.format("%d %c", 0, null); - f.format("%c", (Character) null); - f.format("%c", (Object) null); + f.format("%d %c", 0, null); + f.format("%c", (Character) null); + f.format("%c", (Object) null); - f.format("%d %d", 0, null); - f.format("%d", (Integer) null); - f.format("%d", (Object) null); + f.format("%d %d", 0, null); + f.format("%d", (Integer) null); + f.format("%d", (Object) null); - f.format("%d %f", 0, null); - f.format("%f", (Float) null); - f.format("%f", (Object) null); + f.format("%d %f", 0, null); + f.format("%f", (Float) null); + f.format("%f", (Object) null); - f.format("%d %tD", 0, null); - f.format("%tD", (Date) null); - f.format("%tD", (Object) null); + f.format("%d %tD", 0, null); + f.format("%tD", (Date) null); + f.format("%tD", (Object) null); - System.out.println(f.toString()); - f.close(); - } + System.out.println(f.toString()); + f.close(); + } } diff --git a/checker/tests/formatter/ConversionNull2.java b/checker/tests/formatter/ConversionNull2.java index 99d4ebf70d2..a546e0b4f6e 100644 --- a/checker/tests/formatter/ConversionNull2.java +++ b/checker/tests/formatter/ConversionNull2.java @@ -1,16 +1,17 @@ -import java.util.Formatter; import org.checkerframework.checker.formatter.qual.FormatMethod; +import java.util.Formatter; + public class ConversionNull2 { - void foo(Formatter f1, MyFormatter f2) { - f1.format("%d %c", 0, null); - f2.format("%d %c", 0, null); - } + void foo(Formatter f1, MyFormatter f2) { + f1.format("%d %c", 0, null); + f2.format("%d %c", 0, null); + } } class MyFormatter { - @FormatMethod - public MyFormatter format(String format, Object... args) { - return null; - } + @FormatMethod + public MyFormatter format(String format, Object... args) { + return null; + } } diff --git a/checker/tests/formatter/FlowFormatter.java b/checker/tests/formatter/FlowFormatter.java index 9cf6ce0cafb..a0350f625a4 100644 --- a/checker/tests/formatter/FlowFormatter.java +++ b/checker/tests/formatter/FlowFormatter.java @@ -1,83 +1,84 @@ -import java.util.Date; -import java.util.Formatter; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; import org.checkerframework.checker.formatter.util.FormatUtil; import org.junit.Assert; +import java.util.Date; +import java.util.Formatter; + public class FlowFormatter { - public static String callUnqual(String u) { - return u; - } + public static String callUnqual(String u) { + return u; + } - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - String unqual = System.lineSeparator(); - String qual = "%s %d %f"; - String wrong = "%$s"; - callUnqual("%s"); - callUnqual(qual); - callUnqual(wrong); - callUnqual(null); - // :: error: (format.string.invalid) - f.format(null); - @Format({ConversionCategory.GENERAL}) String nullAssign = null; - // :: error: (format.string.invalid) - f.format(nullAssign, "string"); - if (false) { - nullAssign = "%s"; - } - f.format(nullAssign, "string"); - // :: error: (assignment.type.incompatible) - @Format({ConversionCategory.GENERAL}) String err0 = unqual; - // :: error: (assignment.type.incompatible) - @Format({ConversionCategory.GENERAL}) String err2 = "%$s"; - @Format({ConversionCategory.GENERAL}) String ok = "%s"; + String unqual = System.lineSeparator(); + String qual = "%s %d %f"; + String wrong = "%$s"; + callUnqual("%s"); + callUnqual(qual); + callUnqual(wrong); + callUnqual(null); + // :: error: (format.string.invalid) + f.format(null); + @Format({ConversionCategory.GENERAL}) String nullAssign = null; + // :: error: (format.string.invalid) + f.format(nullAssign, "string"); + if (false) { + nullAssign = "%s"; + } + f.format(nullAssign, "string"); + // :: error: (assignment.type.incompatible) + @Format({ConversionCategory.GENERAL}) String err0 = unqual; + // :: error: (assignment.type.incompatible) + @Format({ConversionCategory.GENERAL}) String err2 = "%$s"; + @Format({ConversionCategory.GENERAL}) String ok = "%s"; - String u = "%s" + " %" + "d"; - String v = FormatUtil.asFormat(u, ConversionCategory.GENERAL, ConversionCategory.INT); - f.format(u, "String", 1337); - // :: error: (argument.type.incompatible) - f.format(u, "String", 7.4); + String u = "%s" + " %" + "d"; + String v = FormatUtil.asFormat(u, ConversionCategory.GENERAL, ConversionCategory.INT); + f.format(u, "String", 1337); + // :: error: (argument.type.incompatible) + f.format(u, "String", 7.4); - try { - String l = FormatUtil.asFormat(u, ConversionCategory.FLOAT, ConversionCategory.INT); - Assert.fail("Expected Exception"); - } catch (Error e) { - } + try { + String l = FormatUtil.asFormat(u, ConversionCategory.FLOAT, ConversionCategory.INT); + Assert.fail("Expected Exception"); + } catch (Error e) { + } - String a = "Success: %s %d %f"; - f.format(a, "String", 1337, 7.5); + String a = "Success: %s %d %f"; + f.format(a, "String", 1337, 7.5); - String b = "Fail: %d"; - // :: error: (argument.type.incompatible) - f.format(b, "Wrong"); + String b = "Fail: %d"; + // :: error: (argument.type.incompatible) + f.format(b, "Wrong"); - @Format({ - ConversionCategory.GENERAL, - ConversionCategory.INT, - ConversionCategory.FLOAT, - ConversionCategory.CHAR - }) - String s = "Success: %s %d %f %c"; - f.format(s, "OK", 42, 3.14, 'c'); + @Format({ + ConversionCategory.GENERAL, + ConversionCategory.INT, + ConversionCategory.FLOAT, + ConversionCategory.CHAR + }) + String s = "Success: %s %d %f %c"; + f.format(s, "OK", 42, 3.14, 'c'); - @Format({ConversionCategory.GENERAL, ConversionCategory.INT, ConversionCategory.FLOAT}) String t = "Fail: %s %d %f"; - // :: error: (argument.type.incompatible) - f.format(t, "OK", "Wrong", 3.14); + @Format({ConversionCategory.GENERAL, ConversionCategory.INT, ConversionCategory.FLOAT}) String t = "Fail: %s %d %f"; + // :: error: (argument.type.incompatible) + f.format(t, "OK", "Wrong", 3.14); - call(f, "Success: %tM"); - // :: error: (argument.type.incompatible) - call(f, "Fail: %d"); + call(f, "Success: %tM"); + // :: error: (argument.type.incompatible) + call(f, "Fail: %d"); - System.out.println(f.toString()); - f.close(); - } + System.out.println(f.toString()); + f.close(); + } - public static void call(Formatter f, @Format({ConversionCategory.TIME}) String s) { - f.format(s, new Date()); - // :: error: (argument.type.incompatible) - f.format(s, "Wrong"); - } + public static void call(Formatter f, @Format({ConversionCategory.TIME}) String s) { + f.format(s, new Date()); + // :: error: (argument.type.incompatible) + f.format(s, "Wrong"); + } } diff --git a/checker/tests/formatter/FormatBasic.java b/checker/tests/formatter/FormatBasic.java index 97fb3e16f95..1ff29f0ec54 100644 --- a/checker/tests/formatter/FormatBasic.java +++ b/checker/tests/formatter/FormatBasic.java @@ -4,33 +4,35 @@ import java.util.GregorianCalendar; public class FormatBasic { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - f.format("String"); - f.format("String %20% %n"); - f.format("%% %s", "str"); - f.format("%4$2s %3$2s %2$2s %1$2s", "a", "b", "c", "d"); - f.format("e = %+10.4f", Math.E); - f.format("Amount gained or lost since last statement: $ %(,.2f", -6217.58); - f.format("Local time: %tT", Calendar.getInstance()); - f.format("Unable to open file '%1$s': %2$s", "food", "No such file or directory"); - f.format("Duke's Birthday: %1$tm %1$te,%1$tY", new GregorianCalendar(1995, Calendar.MAY, 23)); - f.format("Duke's Birthday: %tm % { + static class MyClass implements Interface { - MyClass returnType; + MyClass returnType; - void method(MyClass result) { - result.returnType = Interface.method(this.returnType); + void method(MyClass result) { + result.returnType = Interface.method(this.returnType); + } } - } - interface Interface { - static > T2 method(T2 object) { - throw new RuntimeException(); + interface Interface { + static > T2 method(T2 object) { + throw new RuntimeException(); + } } - } } diff --git a/checker/tests/formatter/VarargsFormatter.java b/checker/tests/formatter/VarargsFormatter.java index 25f21f603cb..cd612a25b21 100644 --- a/checker/tests/formatter/VarargsFormatter.java +++ b/checker/tests/formatter/VarargsFormatter.java @@ -2,59 +2,59 @@ import java.util.Formatter; public class VarargsFormatter { - public static void main(String... p) { - Formatter f = new Formatter(); + public static void main(String... p) { + Formatter f = new Formatter(); - // vararg as parameter - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - f.format("Nothing", null); // equivalent to (Object[])null - f.format("Nothing", (Object[]) null); - f.format("%s", (Object[]) null); - f.format("%s %d %x", (Object[]) null); - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - f.format("%s %d %x", null); // equivalent to (Object[])null - // :: warning: (format.indirect.arguments) - f.format("%d", new Object[1]); - // :: warning: (format.indirect.arguments) - f.format("%s", new Object[2]); - // :: warning: (format.indirect.arguments) - f.format("%s %s", new Object[0]); - // :: warning: (format.indirect.arguments) - f.format("Empty", new Object[0]); - // :: warning: (format.indirect.arguments) - f.format("Empty", new Object[5]); - f.format("%s", new ArrayList()); - f.format("%d %s", 132, new Object[2]); - f.format("%s %d", new Object[2], 123); - // :: error: (format.missing.arguments) - f.format("%d %s %s", 132, new Object[2]); - // :: error: (argument.type.incompatible) - f.format("%d %d", new Object[2], 123); - // "error: (format.specifier.null)" could be a warning rather than an error, but that would - // require reasoning about the values in an array construction expression. - // :: error: (format.specifier.null) :: warning: (format.indirect.arguments) - f.format("%d %()); + f.format("%d %s", 132, new Object[2]); + f.format("%s %d", new Object[2], 123); + // :: error: (format.missing.arguments) + f.format("%d %s %s", 132, new Object[2]); + // :: error: (argument.type.incompatible) + f.format("%d %d", new Object[2], 123); + // "error: (format.specifier.null)" could be a warning rather than an error, but that would + // require reasoning about the values in an array construction expression. + // :: error: (format.specifier.null) :: warning: (format.indirect.arguments) + f.format("%d % { - public void executeAlwaysSafe(T arg); - } - - private interface UIFunctionalInterface { - @UIEffect - public void executeUI(T arg); - } - - @PolyUIType - private interface PolymorphicFunctionalInterface { - @PolyUIEffect - public void executePolymorphic(T arg); - } - - private static class LambdaRunner { - private final UIElement arg; - - public LambdaRunner(UIElement arg) { - this.arg = arg; + private interface SafeFunctionalInterface { + public void executeAlwaysSafe(T arg); } - public void doSafe(SafeFunctionalInterface func) { - func.executeAlwaysSafe(this.arg); + private interface UIFunctionalInterface { + @UIEffect + public void executeUI(T arg); } - @UIEffect - public void doUI(UIFunctionalInterface func) { - func.executeUI(this.arg); + @PolyUIType + private interface PolymorphicFunctionalInterface { + @PolyUIEffect + public void executePolymorphic(T arg); } - // Needs to be @UIEffect, because the functional interface method is @UIEffect - public void unsafeDoUI(UIFunctionalInterface func) { - // :: error: (call.invalid.ui) - func.executeUI(this.arg); + private static class LambdaRunner { + private final UIElement arg; + + public LambdaRunner(UIElement arg) { + this.arg = arg; + } + + public void doSafe(SafeFunctionalInterface func) { + func.executeAlwaysSafe(this.arg); + } + + @UIEffect + public void doUI(UIFunctionalInterface func) { + func.executeUI(this.arg); + } + + // Needs to be @UIEffect, because the functional interface method is @UIEffect + public void unsafeDoUI(UIFunctionalInterface func) { + // :: error: (call.invalid.ui) + func.executeUI(this.arg); + } + + public void doEither(@PolyUI PolymorphicFunctionalInterface func) { + // In a real program some intelligent dispatch could be done here to avoid running on UI + // thread unless needed. + arg.runOnUIThread( + new IAsyncUITask() { + final UIElement e2 = arg; + + @Override + public void doStuff() { // should inherit UI effect + func.executePolymorphic(e2); // should be okay + } + }); + } + + public void doUISafely(@UI PolymorphicFunctionalInterface func) { + // In a real program some intelligent dispatch could be done here to avoid running on UI + // thread unless needed. + arg.runOnUIThread( + new IAsyncUITask() { + final UIElement e2 = arg; + + @Override + public void doStuff() { // should inherit UI effect + func.executePolymorphic(e2); // should be okay + } + }); + } } - public void doEither(@PolyUI PolymorphicFunctionalInterface func) { - // In a real program some intelligent dispatch could be done here to avoid running on UI - // thread unless needed. - arg.runOnUIThread( - new IAsyncUITask() { - final UIElement e2 = arg; + @PolyUIType + private static class PolymorphicLambdaRunner { + private final UIElement arg; - @Override - public void doStuff() { // should inherit UI effect - func.executePolymorphic(e2); // should be okay - } - }); - } + public PolymorphicLambdaRunner(UIElement arg) { + this.arg = arg; + } - public void doUISafely(@UI PolymorphicFunctionalInterface func) { - // In a real program some intelligent dispatch could be done here to avoid running on UI - // thread unless needed. - arg.runOnUIThread( - new IAsyncUITask() { - final UIElement e2 = arg; + @PolyUIEffect + public void doEither(@PolyUI PolymorphicFunctionalInterface func) { + func.executePolymorphic(this.arg); + } + } - @Override - public void doStuff() { // should inherit UI effect - func.executePolymorphic(e2); // should be okay - } - }); + public static void safeContextTestCases(UIElement elem) { + LambdaRunner runner = new LambdaRunner(elem); + runner.doSafe(e -> e.repaint()); + // :: error: (call.invalid.ui) + runner.doSafe(e -> e.dangerous()); // Not allowed in doSafe + // :: error: (call.invalid.ui) + runner.doUI(e -> e.repaint()); // Not allowed in safe context + // :: error: (call.invalid.ui) + runner.doUI(e -> e.dangerous()); // Not allowed in safe context + runner.doEither(e -> e.repaint()); + runner.doEither(e -> e.dangerous()); + runner.doUISafely(e -> e.dangerous()); + @AlwaysSafe PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); + safePolymorphicLambdaRunner.doEither(e -> e.repaint()); + safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); + safePolymorphicLambdaRunner.doEither( + new @UI PolymorphicFunctionalInterface() { + public void executePolymorphic(UIElement arg) { + arg.dangerous(); + } + }); + @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither( + e -> e.repaint()); // Safe at runtime, but not by the type system! + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); + PolymorphicFunctionalInterface func1 = e -> e.repaint(); + // :: error: (assignment.type.incompatible) + PolymorphicFunctionalInterface func2 = e -> e.dangerous(); // Incompatible types! + PolymorphicFunctionalInterface func2p = + // :: error: (assignment.type.incompatible) + (new @UI PolymorphicFunctionalInterface() { + public void executePolymorphic(UIElement arg) { + arg.dangerous(); + } + }); + @UI PolymorphicFunctionalInterface func3 = e -> e.dangerous(); + safePolymorphicLambdaRunner.doEither(func1); + safePolymorphicLambdaRunner.doEither(func2); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(func1); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(func2); + // :: error: (call.invalid.ui) + uiPolymorphicLambdaRunner.doEither(func3); } - } - @PolyUIType - private static class PolymorphicLambdaRunner { - private final UIElement arg; + @UIEffect + public static void uiContextTestCases(UIElement elem) { + LambdaRunner runner = new LambdaRunner(elem); + // :: error: (call.invalid.ui) + runner.doSafe(e -> e.dangerous()); + runner.doUI(e -> e.repaint()); + runner.doUI(e -> e.dangerous()); + PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); + safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); + @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); + uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); + } - public PolymorphicLambdaRunner(UIElement arg) { - this.arg = arg; + public @UI PolymorphicFunctionalInterface returnLambdasTest1() { + return e -> e.dangerous(); } - @PolyUIEffect - public void doEither(@PolyUI PolymorphicFunctionalInterface func) { - func.executePolymorphic(this.arg); + // This should be an error without an @UI annotation on the return type. No? + public PolymorphicFunctionalInterface returnLambdasTest2() { + // :: error: (return.type.incompatible) + return e -> { + e.dangerous(); + }; } - } - - public static void safeContextTestCases(UIElement elem) { - LambdaRunner runner = new LambdaRunner(elem); - runner.doSafe(e -> e.repaint()); - // :: error: (call.invalid.ui) - runner.doSafe(e -> e.dangerous()); // Not allowed in doSafe - // :: error: (call.invalid.ui) - runner.doUI(e -> e.repaint()); // Not allowed in safe context - // :: error: (call.invalid.ui) - runner.doUI(e -> e.dangerous()); // Not allowed in safe context - runner.doEither(e -> e.repaint()); - runner.doEither(e -> e.dangerous()); - runner.doUISafely(e -> e.dangerous()); - @AlwaysSafe PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); - safePolymorphicLambdaRunner.doEither(e -> e.repaint()); - safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); - safePolymorphicLambdaRunner.doEither( - new @UI PolymorphicFunctionalInterface() { - public void executePolymorphic(UIElement arg) { - arg.dangerous(); - } - }); - @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither( - e -> e.repaint()); // Safe at runtime, but not by the type system! - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); - PolymorphicFunctionalInterface func1 = e -> e.repaint(); - // :: error: (assignment.type.incompatible) - PolymorphicFunctionalInterface func2 = e -> e.dangerous(); // Incompatible types! - PolymorphicFunctionalInterface func2p = - // :: error: (assignment.type.incompatible) - (new @UI PolymorphicFunctionalInterface() { - public void executePolymorphic(UIElement arg) { - arg.dangerous(); - } + + // Just to check + public PolymorphicFunctionalInterface returnLambdasTest3() { + // :: error: (return.type.incompatible) + return (new @UI PolymorphicFunctionalInterface() { + public void executePolymorphic(UIElement arg) { + arg.dangerous(); + } }); - @UI PolymorphicFunctionalInterface func3 = e -> e.dangerous(); - safePolymorphicLambdaRunner.doEither(func1); - safePolymorphicLambdaRunner.doEither(func2); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(func1); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(func2); - // :: error: (call.invalid.ui) - uiPolymorphicLambdaRunner.doEither(func3); - } - - @UIEffect - public static void uiContextTestCases(UIElement elem) { - LambdaRunner runner = new LambdaRunner(elem); - // :: error: (call.invalid.ui) - runner.doSafe(e -> e.dangerous()); - runner.doUI(e -> e.repaint()); - runner.doUI(e -> e.dangerous()); - PolymorphicLambdaRunner safePolymorphicLambdaRunner = new PolymorphicLambdaRunner(elem); - safePolymorphicLambdaRunner.doEither(e -> e.dangerous()); - @UI PolymorphicLambdaRunner uiPolymorphicLambdaRunner = new @UI PolymorphicLambdaRunner(elem); - uiPolymorphicLambdaRunner.doEither(e -> e.dangerous()); - } - - public @UI PolymorphicFunctionalInterface returnLambdasTest1() { - return e -> e.dangerous(); - } - - // This should be an error without an @UI annotation on the return type. No? - public PolymorphicFunctionalInterface returnLambdasTest2() { - // :: error: (return.type.incompatible) - return e -> { - e.dangerous(); - }; - } - - // Just to check - public PolymorphicFunctionalInterface returnLambdasTest3() { - // :: error: (return.type.incompatible) - return (new @UI PolymorphicFunctionalInterface() { - public void executePolymorphic(UIElement arg) { - arg.dangerous(); - } - }); - } + } } diff --git a/checker/tests/guieffect/MouseTest.java b/checker/tests/guieffect/MouseTest.java index 9ab979cb75b..595d80012a3 100644 --- a/checker/tests/guieffect/MouseTest.java +++ b/checker/tests/guieffect/MouseTest.java @@ -1,13 +1,14 @@ +import org.checkerframework.checker.guieffect.qual.UIType; + import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import org.checkerframework.checker.guieffect.qual.UIType; // Test the stub file handling @UIType public class MouseTest extends MouseAdapter { - @Override - public void mouseEntered(MouseEvent arg0) { - IAsyncUITask t = null; - t.doStuff(); - } + @Override + public void mouseEntered(MouseEvent arg0) { + IAsyncUITask t = null; + t.doStuff(); + } } diff --git a/checker/tests/guieffect/NoAnnotationsTest.java b/checker/tests/guieffect/NoAnnotationsTest.java index 1a3c8a13aee..5c6c45a32ad 100644 --- a/checker/tests/guieffect/NoAnnotationsTest.java +++ b/checker/tests/guieffect/NoAnnotationsTest.java @@ -1,3 +1,3 @@ public class NoAnnotationsTest { - public boolean b; + public boolean b; } diff --git a/checker/tests/guieffect/SafeParent.java b/checker/tests/guieffect/SafeParent.java index 06015b2043c..077003e4ecc 100644 --- a/checker/tests/guieffect/SafeParent.java +++ b/checker/tests/guieffect/SafeParent.java @@ -1,6 +1,6 @@ import org.checkerframework.checker.guieffect.qual.SafeEffect; public class SafeParent { - @SafeEffect - void m() {} + @SafeEffect + void m() {} } diff --git a/checker/tests/guieffect/Specialization.java b/checker/tests/guieffect/Specialization.java index ea68d415cd8..bfe794e4e5e 100644 --- a/checker/tests/guieffect/Specialization.java +++ b/checker/tests/guieffect/Specialization.java @@ -2,49 +2,49 @@ public class Specialization { - @PolyUIType - public static interface I { - @PolyUIEffect - public void m(); - } - - public static void reqSafe(@AlwaysSafe I i) {} - - @UIEffect - public static void reqUI(@UI I i) {} - - @PolyUIType - public static interface Doer { - - public void doStuff(@PolyUI Doer this, @PolyUI I i); - } - - @AlwaysSafe public static class SafeDoer implements @AlwaysSafe Doer { - // :: error: (override.param.invalid) - public void doStuff(@AlwaysSafe SafeDoer this, @AlwaysSafe I i) {} - } - - public void q(@AlwaysSafe Doer doer, @UI I i) { - doer.doStuff(i); - } - - public static void main(String[] args) { - - @AlwaysSafe Doer d = - new @AlwaysSafe Doer() { - @SafeEffect - // :: error: (override.param.invalid) - public void doStuff(@AlwaysSafe I i) { - reqSafe(i); - } - }; - @UI I ui = - new @UI I() { - public void m() { - reqUI(null); - } - }; - Specialization q = new Specialization(); - q.q(d, ui); - } + @PolyUIType + public static interface I { + @PolyUIEffect + public void m(); + } + + public static void reqSafe(@AlwaysSafe I i) {} + + @UIEffect + public static void reqUI(@UI I i) {} + + @PolyUIType + public static interface Doer { + + public void doStuff(@PolyUI Doer this, @PolyUI I i); + } + + @AlwaysSafe public static class SafeDoer implements @AlwaysSafe Doer { + // :: error: (override.param.invalid) + public void doStuff(@AlwaysSafe SafeDoer this, @AlwaysSafe I i) {} + } + + public void q(@AlwaysSafe Doer doer, @UI I i) { + doer.doStuff(i); + } + + public static void main(String[] args) { + + @AlwaysSafe Doer d = + new @AlwaysSafe Doer() { + @SafeEffect + // :: error: (override.param.invalid) + public void doStuff(@AlwaysSafe I i) { + reqSafe(i); + } + }; + @UI I ui = + new @UI I() { + public void m() { + reqUI(null); + } + }; + Specialization q = new Specialization(); + q.q(d, ui); + } } diff --git a/checker/tests/guieffect/TestProgram.java b/checker/tests/guieffect/TestProgram.java index a0904d2b77f..1604681260d 100644 --- a/checker/tests/guieffect/TestProgram.java +++ b/checker/tests/guieffect/TestProgram.java @@ -1,80 +1,81 @@ import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.UI; import org.checkerframework.checker.guieffect.qual.UIEffect; + import packagetests.SafeByDecl; import packagetests.UIByPackageDecl; public class TestProgram { - public void nonUIStuff( - final UIElement e, - final GenericTaskUIConsumer uicons, - final GenericTaskSafeConsumer safecons) { - // :: error: (call.invalid.ui) - e.dangerous(); // should be bad - e.runOnUIThread( - new IAsyncUITask() { - final UIElement e2 = e; + public void nonUIStuff( + final UIElement e, + final GenericTaskUIConsumer uicons, + final GenericTaskSafeConsumer safecons) { + // :: error: (call.invalid.ui) + e.dangerous(); // should be bad + e.runOnUIThread( + new IAsyncUITask() { + final UIElement e2 = e; - @Override - public void doStuff() { // should inherit UI effect - e2.dangerous(); // should be okay - } - }); - uicons.runAsync( - new @UI IGenericTask() { - final UIElement e2 = e; + @Override + public void doStuff() { // should inherit UI effect + e2.dangerous(); // should be okay + } + }); + uicons.runAsync( + new @UI IGenericTask() { + final UIElement e2 = e; - @Override - public void doGenericStuff() { // Should be inst. w/ @UI eff. - e2.dangerous(); // should be okay - } - }); - safecons.runAsync( - new @AlwaysSafe IGenericTask() { - final UIElement e2 = e; + @Override + public void doGenericStuff() { // Should be inst. w/ @UI eff. + e2.dangerous(); // should be okay + } + }); + safecons.runAsync( + new @AlwaysSafe IGenericTask() { + final UIElement e2 = e; - @Override - public void doGenericStuff() { // Should be inst. w/ @AlwaysSafe - // :: error: (call.invalid.ui) - e2.dangerous(); // should be an error - safecons.runAsync(this); // Should be okay, this:@AlwaysSafe - } - }); - safecons.runAsync( - // :: error: (argument.type.incompatible) - new @UI IGenericTask() { - final UIElement e2 = e; + @Override + public void doGenericStuff() { // Should be inst. w/ @AlwaysSafe + // :: error: (call.invalid.ui) + e2.dangerous(); // should be an error + safecons.runAsync(this); // Should be okay, this:@AlwaysSafe + } + }); + safecons.runAsync( + // :: error: (argument.type.incompatible) + new @UI IGenericTask() { + final UIElement e2 = e; - @Override - public void doGenericStuff() { // Should be inst. w/ @UI - e2.dangerous(); // should be ok - // :: error: (argument.type.incompatible) - safecons.runAsync(this); // Should be error, this:@UI - } - }); - // Test that the package annotation works - // :: error: (call.invalid.ui) - UIByPackageDecl.implicitlyUI(); - // Test that @SafeType works: SafeByDecl is inside a @UIPackage - SafeByDecl.safeByTypeDespiteUIPackage(); - safecons.runAsync( - // :: error: (argument.type.incompatible) - new IGenericTask() { - @Override - public void doGenericStuff() { - // Safe here due to anonymous inner class effect inference, but will trigger - // an error above due to safecons.runAsync not taking an @UI IGenericTask. - UIByPackageDecl.implicitlyUI(); - } - }); - safecons.runAsync( - new IGenericTask() { - @Override - @UIEffect - // :: error: (override.effect.invalid.nonui) - public void doGenericStuff() { - UIByPackageDecl.implicitlyUI(); - } - }); - } + @Override + public void doGenericStuff() { // Should be inst. w/ @UI + e2.dangerous(); // should be ok + // :: error: (argument.type.incompatible) + safecons.runAsync(this); // Should be error, this:@UI + } + }); + // Test that the package annotation works + // :: error: (call.invalid.ui) + UIByPackageDecl.implicitlyUI(); + // Test that @SafeType works: SafeByDecl is inside a @UIPackage + SafeByDecl.safeByTypeDespiteUIPackage(); + safecons.runAsync( + // :: error: (argument.type.incompatible) + new IGenericTask() { + @Override + public void doGenericStuff() { + // Safe here due to anonymous inner class effect inference, but will trigger + // an error above due to safecons.runAsync not taking an @UI IGenericTask. + UIByPackageDecl.implicitlyUI(); + } + }); + safecons.runAsync( + new IGenericTask() { + @Override + @UIEffect + // :: error: (override.effect.invalid.nonui) + public void doGenericStuff() { + UIByPackageDecl.implicitlyUI(); + } + }); + } } diff --git a/checker/tests/guieffect/ThrowCatchTest.java b/checker/tests/guieffect/ThrowCatchTest.java index 574ffa28fba..06f68544099 100644 --- a/checker/tests/guieffect/ThrowCatchTest.java +++ b/checker/tests/guieffect/ThrowCatchTest.java @@ -1,120 +1,121 @@ -import java.util.List; import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.PolyUIType; import org.checkerframework.checker.guieffect.qual.UI; +import java.util.List; + public class ThrowCatchTest { - List ooo; + List ooo; - // :: error: (type.invalid.annotations.on.use) - List iii; + // :: error: (type.invalid.annotations.on.use) + List iii; - class Inner {} + class Inner {} - boolean flag = true; + boolean flag = true; - // Type var test - void throwTypeVarUI1(E ex1, @UI E ex2) throws PolyUIException { - if (flag) { - // :: error: (throw.type.invalid) - throw ex1; + // Type var test + void throwTypeVarUI1(E ex1, @UI E ex2) throws PolyUIException { + if (flag) { + // :: error: (throw.type.invalid) + throw ex1; + } + // :: error: (throw.type.invalid) + throw ex2; } - // :: error: (throw.type.invalid) - throw ex2; - } - - <@UI E extends @UI PolyUIException> void throwTypeVarUI2(E ex1) throws PolyUIException { - // :: error: (throw.type.invalid) - throw ex1; - } - - void throwTypeVarAlwaysSafe1(E ex1, @AlwaysSafe E ex2) - throws PolyUIException { - if (flag) { - throw ex1; + + <@UI E extends @UI PolyUIException> void throwTypeVarUI2(E ex1) throws PolyUIException { + // :: error: (throw.type.invalid) + throw ex1; } - throw ex2; - } - <@AlwaysSafe E extends PolyUIException> void throwTypeVarAlwaysSafe2(E ex1, @AlwaysSafe E ex2) - throws PolyUIException { - if (flag) { - throw ex1; + void throwTypeVarAlwaysSafe1(E ex1, @AlwaysSafe E ex2) + throws PolyUIException { + if (flag) { + throw ex1; + } + throw ex2; } - throw ex2; - } - - <@AlwaysSafe E extends @UI PolyUIException> void throwTypeVarMixed(E ex1, @AlwaysSafe E ex2) - throws PolyUIException { - if (flag) { - // :: error: (throw.type.invalid) - throw ex1; + + <@AlwaysSafe E extends PolyUIException> void throwTypeVarAlwaysSafe2(E ex1, @AlwaysSafe E ex2) + throws PolyUIException { + if (flag) { + throw ex1; + } + throw ex2; } - throw ex2; - } - - // Wildcards - void throwWildcard( - List ui, - List alwaysSafe) - throws PolyUIException { - if (flag) { - throw ui.get(0); + + <@AlwaysSafe E extends @UI PolyUIException> void throwTypeVarMixed(E ex1, @AlwaysSafe E ex2) + throws PolyUIException { + if (flag) { + // :: error: (throw.type.invalid) + throw ex1; + } + throw ex2; } - throw alwaysSafe.get(0); - } - void throwNull() { - throw null; - } + // Wildcards + void throwWildcard( + List ui, + List alwaysSafe) + throws PolyUIException { + if (flag) { + throw ui.get(0); + } + throw alwaysSafe.get(0); + } - // Declared - @UI PolyUIException ui = new PolyUIException(); - @AlwaysSafe PolyUIException alwaysSafe = new PolyUIException(); + void throwNull() { + throw null; + } - void throwDeclared() { - try { - // :: error: (throw.type.invalid) - throw ui; - } catch (@UI PolyUIException UIParam) { + // Declared + @UI PolyUIException ui = new PolyUIException(); + @AlwaysSafe PolyUIException alwaysSafe = new PolyUIException(); - } + void throwDeclared() { + try { + // :: error: (throw.type.invalid) + throw ui; + } catch (@UI PolyUIException UIParam) { - try { - throw alwaysSafe; - } catch (@AlwaysSafe PolyUIException alwaysSafeParam) { + } + try { + throw alwaysSafe; + } catch (@AlwaysSafe PolyUIException alwaysSafeParam) { + + } } - } - // Test Exception parameters - void unionTypes() { - try { - } catch (@AlwaysSafe NullPointerPolyUIException - | @AlwaysSafe ArrayStorePolyUIException unionParam) { + // Test Exception parameters + void unionTypes() { + try { + } catch (@AlwaysSafe NullPointerPolyUIException + | @AlwaysSafe ArrayStorePolyUIException unionParam) { - } + } - try { - } catch (@UI NullPointerPolyUIException | @UI ArrayStorePolyUIException unionParam) { + try { + } catch (@UI NullPointerPolyUIException | @UI ArrayStorePolyUIException unionParam) { + } } - } - void defaults() { - try { - throw new PolyUIException(); - } catch (PolyUIException e) { + void defaults() { + try { + throw new PolyUIException(); + } catch (PolyUIException e) { + } } - } - @PolyUIType - class PolyUIException extends Exception {} + @PolyUIType + class PolyUIException extends Exception {} - @PolyUIType - class NullPointerPolyUIException extends NullPointerException {} + @PolyUIType + class NullPointerPolyUIException extends NullPointerException {} - @PolyUIType - class ArrayStorePolyUIException extends ArrayStoreException {} + @PolyUIType + class ArrayStorePolyUIException extends ArrayStoreException {} } diff --git a/checker/tests/guieffect/TransitiveInheritance.java b/checker/tests/guieffect/TransitiveInheritance.java index e6542e19c92..ffe217505e0 100644 --- a/checker/tests/guieffect/TransitiveInheritance.java +++ b/checker/tests/guieffect/TransitiveInheritance.java @@ -2,57 +2,57 @@ public class TransitiveInheritance { - public static class TopLevel { - // Implicitly safe - public void foo() {} - } - - public static interface ITop { - public void bar(); - } - - public static interface IIndirect { - public void baz(); - } - - // Mid-level class and interface that do not redeclare or override the default safe methods - public abstract static class MidLevel extends TopLevel implements IIndirect {} - - public static interface IMid extends ITop {} - - // Issue #3287 is that if foo or bar is overridden with a @UIEffect implementation here, the - // "skip" in declarations causes the override error to not be issued - // We check both classes and interfaces; the reported issue is related only to methods whose - // nearest explicit definition lives in an interface - public static class Base extends MidLevel implements IMid { - - // Should catch when the override is for a method originating in a class two levels up (here - // TopLevel) - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void foo() {} - - // Should catch when the override is for a method originating in an interface two levels up - // (here ITop) - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void bar() {} - - // Should catch when the override is for a method originating in an interface two levels up, - // but which is implemented via class inheritance (here IIndirect, which is implemented by - // MidLevel). - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void baz() {} - } - - public static interface IBase extends IMid { - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void bar(); - } + public static class TopLevel { + // Implicitly safe + public void foo() {} + } + + public static interface ITop { + public void bar(); + } + + public static interface IIndirect { + public void baz(); + } + + // Mid-level class and interface that do not redeclare or override the default safe methods + public abstract static class MidLevel extends TopLevel implements IIndirect {} + + public static interface IMid extends ITop {} + + // Issue #3287 is that if foo or bar is overridden with a @UIEffect implementation here, the + // "skip" in declarations causes the override error to not be issued + // We check both classes and interfaces; the reported issue is related only to methods whose + // nearest explicit definition lives in an interface + public static class Base extends MidLevel implements IMid { + + // Should catch when the override is for a method originating in a class two levels up (here + // TopLevel) + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void foo() {} + + // Should catch when the override is for a method originating in an interface two levels up + // (here ITop) + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void bar() {} + + // Should catch when the override is for a method originating in an interface two levels up, + // but which is implemented via class inheritance (here IIndirect, which is implemented by + // MidLevel). + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void baz() {} + } + + public static interface IBase extends IMid { + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void bar(); + } } diff --git a/checker/tests/guieffect/UIChild.java b/checker/tests/guieffect/UIChild.java index 50106d6bba1..5dbe001853d 100644 --- a/checker/tests/guieffect/UIChild.java +++ b/checker/tests/guieffect/UIChild.java @@ -4,35 +4,35 @@ // Should not inherit @UI! public class UIChild extends UIParent { - @Override - public void doingUIStuff() { - // :: error: (call.invalid.ui) - thingy.dangerous(); - } + @Override + public void doingUIStuff() { + // :: error: (call.invalid.ui) + thingy.dangerous(); + } - // Should be an error to make this @UI - @Override - @UIEffect - // :: error: (override.effect.invalid) - public void doingSafeStuff() {} + // Should be an error to make this @UI + @Override + @UIEffect + // :: error: (override.effect.invalid) + public void doingSafeStuff() {} - public void shouldNotBeUI() { - // :: error: (call.invalid.ui) - thingy.dangerous(); - } + public void shouldNotBeUI() { + // :: error: (call.invalid.ui) + thingy.dangerous(); + } - @UIEffect - @SafeEffect - // :: error: (annotations.conflicts) - public void doubleAnnot1() {} + @UIEffect + @SafeEffect + // :: error: (annotations.conflicts) + public void doubleAnnot1() {} - @UIEffect - @PolyUIEffect - // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) - public void doubleAnnot2() {} + @UIEffect + @PolyUIEffect + // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) + public void doubleAnnot2() {} - @PolyUIEffect - @SafeEffect - // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) - public void doubleAnnot3() {} + @PolyUIEffect + @SafeEffect + // :: error: (annotations.conflicts) :: error: (polymorphism.invalid) + public void doubleAnnot3() {} } diff --git a/checker/tests/guieffect/UIElement.java b/checker/tests/guieffect/UIElement.java index ee4517ed115..00db2c6a365 100644 --- a/checker/tests/guieffect/UIElement.java +++ b/checker/tests/guieffect/UIElement.java @@ -3,11 +3,11 @@ @UIType public interface UIElement { - public void dangerous(); + public void dangerous(); - @SafeEffect - public void repaint(); + @SafeEffect + public void repaint(); - @SafeEffect - public void runOnUIThread(IAsyncUITask task); + @SafeEffect + public void runOnUIThread(IAsyncUITask task); } diff --git a/checker/tests/guieffect/UIParent.java b/checker/tests/guieffect/UIParent.java index 67274434c74..03cf37b6c5a 100644 --- a/checker/tests/guieffect/UIParent.java +++ b/checker/tests/guieffect/UIParent.java @@ -3,15 +3,15 @@ @UIType public class UIParent { - protected UIElement thingy; + protected UIElement thingy; - @SafeEffect // Making this ctor safe to allow easy safe subclasses - public UIParent() {} + @SafeEffect // Making this ctor safe to allow easy safe subclasses + public UIParent() {} - public void doingUIStuff() { - thingy.dangerous(); - } // should have UI effect + public void doingUIStuff() { + thingy.dangerous(); + } // should have UI effect - @SafeEffect - public void doingSafeStuff() {} // non-UI + @SafeEffect + public void doingSafeStuff() {} // non-UI } diff --git a/checker/tests/guieffect/WeakeningChild.java b/checker/tests/guieffect/WeakeningChild.java index a4f815c7bbc..12cc60f02af 100644 --- a/checker/tests/guieffect/WeakeningChild.java +++ b/checker/tests/guieffect/WeakeningChild.java @@ -2,11 +2,11 @@ // Should not inherit @UI! public class WeakeningChild extends UIParent { - // Should be valid to override @UI methods with @AlwaysSafe methods - @Override - @SafeEffect - public void doingUIStuff() {} + // Should be valid to override @UI methods with @AlwaysSafe methods + @Override + @SafeEffect + public void doingUIStuff() {} - @Override - public void doingSafeStuff() {} + @Override + public void doingSafeStuff() {} } diff --git a/checker/tests/guieffect/packagetests/SafeByDecl.java b/checker/tests/guieffect/packagetests/SafeByDecl.java index 20fe6cd4d74..b489f4b3d36 100644 --- a/checker/tests/guieffect/packagetests/SafeByDecl.java +++ b/checker/tests/guieffect/packagetests/SafeByDecl.java @@ -4,5 +4,5 @@ @SafeType public class SafeByDecl { - public static void safeByTypeDespiteUIPackage() {} + public static void safeByTypeDespiteUIPackage() {} } diff --git a/checker/tests/guieffect/packagetests/UIByPackageDecl.java b/checker/tests/guieffect/packagetests/UIByPackageDecl.java index 2a519f67b26..2aa0e35019b 100644 --- a/checker/tests/guieffect/packagetests/UIByPackageDecl.java +++ b/checker/tests/guieffect/packagetests/UIByPackageDecl.java @@ -1,7 +1,7 @@ package packagetests; public class UIByPackageDecl { - public static void implicitlyUI() { - // don't need to do anything here - } + public static void implicitlyUI() { + // don't need to do anything here + } } diff --git a/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java index 42fd0131aa0..705681b6dee 100644 --- a/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/i18n-formatter-unchecked-defaults/TestUncheckedByteCode.java @@ -1,18 +1,18 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - Object field; + Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - field = param.getInt(1); - field = param.getInteger(i); - field = param.identity("hello"); + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + field = param.getInt(1); + field = param.getInteger(i); + field = param.identity("hello"); - // String and Object are relevant types and must be annotated in bytecode - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - } + // String and Object are relevant types and must be annotated in bytecode + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + } } diff --git a/checker/tests/i18n-formatter/ConversionCategoryTest.java b/checker/tests/i18n-formatter/ConversionCategoryTest.java index 0c76aa7cfd1..73b26e6f344 100644 --- a/checker/tests/i18n-formatter/ConversionCategoryTest.java +++ b/checker/tests/i18n-formatter/ConversionCategoryTest.java @@ -3,67 +3,67 @@ public class ConversionCategoryTest { - public static void main(String[] args) { - @I18nFormat({I18nConversionCategory.GENERAL}) String s1 = "{0}"; + public static void main(String[] args) { + @I18nFormat({I18nConversionCategory.GENERAL}) String s1 = "{0}"; - @I18nFormat({I18nConversionCategory.DATE}) String s2 = "{0, date}"; - @I18nFormat({I18nConversionCategory.NUMBER}) String s3 = "{0, number}"; + @I18nFormat({I18nConversionCategory.DATE}) String s2 = "{0, date}"; + @I18nFormat({I18nConversionCategory.NUMBER}) String s3 = "{0, number}"; - @I18nFormat({I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER}) String s4 = "{1} {0, date}"; - // :: warning: (i18nformat.missing.arguments) - s4 = "{0}"; + @I18nFormat({I18nConversionCategory.NUMBER, I18nConversionCategory.NUMBER}) String s4 = "{1} {0, date}"; + // :: warning: (i18nformat.missing.arguments) + s4 = "{0}"; - @I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER}) String s5 = "{0} and {1, number}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s6 = "{1, number}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s7 = "{1, date}"; + @I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER}) String s5 = "{0} and {1, number}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s6 = "{1, number}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s7 = "{1, date}"; - @I18nFormat({ - I18nConversionCategory.UNUSED, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER - }) - String s8 = "{2}"; + @I18nFormat({ + I18nConversionCategory.UNUSED, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER + }) + String s8 = "{2}"; - @I18nFormat({ - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.UNUSED, - I18nConversionCategory.NUMBER - }) - String s9 = "{3, number} {0} {1, time}"; + @I18nFormat({ + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.UNUSED, + I18nConversionCategory.NUMBER + }) + String s9 = "{3, number} {0} {1, time}"; - @I18nFormat({ - I18nConversionCategory.GENERAL, - I18nConversionCategory.DATE, - I18nConversionCategory.DATE, - I18nConversionCategory.NUMBER, - I18nConversionCategory.UNUSED, - I18nConversionCategory.GENERAL - }) - String s10 = "{0} {1, date} {2, time} {3, number} {5}"; + @I18nFormat({ + I18nConversionCategory.GENERAL, + I18nConversionCategory.DATE, + I18nConversionCategory.DATE, + I18nConversionCategory.NUMBER, + I18nConversionCategory.UNUSED, + I18nConversionCategory.GENERAL + }) + String s10 = "{0} {1, date} {2, time} {3, number} {5}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s11 = "{1} {1, date}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.DATE}) String s11 = "{1} {1, date}"; - @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s12 = "{1, number} {1, date}"; + @I18nFormat({I18nConversionCategory.UNUSED, I18nConversionCategory.NUMBER}) String s12 = "{1, number} {1, date}"; - @I18nFormat({I18nConversionCategory.DATE}) String s13 = "{0, date} {0, date}"; + @I18nFormat({I18nConversionCategory.DATE}) String s13 = "{0, date} {0, date}"; - // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.GENERAL}) String b1 = "{1}"; + // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.GENERAL}) String b1 = "{1}"; - // :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.DATE}) String b2 = "{0, number}"; + // :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.DATE}) String b2 = "{0, number}"; - // :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.GENERAL}) String b3 = "{0, number}"; + // :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.GENERAL}) String b3 = "{0, number}"; - // :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.GENERAL}) String b4 = "{0, date}"; + // :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.GENERAL}) String b4 = "{0, date}"; - // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) - @I18nFormat({I18nConversionCategory.DATE}) String b5 = "{0, date} {1, date}"; + // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) + @I18nFormat({I18nConversionCategory.DATE}) String b5 = "{0, date} {1, date}"; - // :: warning: (i18nformat.missing.arguments) - @I18nFormat({I18nConversionCategory.DATE, I18nConversionCategory.DATE}) String b6 = "{0, date}"; - } + // :: warning: (i18nformat.missing.arguments) + @I18nFormat({I18nConversionCategory.DATE, I18nConversionCategory.DATE}) String b6 = "{0, date}"; + } } diff --git a/checker/tests/i18n-formatter/HasFormat.java b/checker/tests/i18n-formatter/HasFormat.java index 86969c51e67..fce01829a38 100644 --- a/checker/tests/i18n-formatter/HasFormat.java +++ b/checker/tests/i18n-formatter/HasFormat.java @@ -1,62 +1,63 @@ -import java.text.MessageFormat; -import java.util.Date; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil; +import java.text.MessageFormat; +import java.util.Date; + public class HasFormat { - void test1(String format) { - if (I18nFormatUtil.hasFormat( - format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { - MessageFormat.format(format, "S", 1); - // :: warning: (i18nformat.missing.arguments) - MessageFormat.format(format, "S"); - // :: error: (argument.type.incompatible) - MessageFormat.format(format, "S", "S"); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format(format, "S", 1, 2); + void test1(String format) { + if (I18nFormatUtil.hasFormat( + format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { + MessageFormat.format(format, "S", 1); + // :: warning: (i18nformat.missing.arguments) + MessageFormat.format(format, "S"); + // :: error: (argument.type.incompatible) + MessageFormat.format(format, "S", "S"); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format(format, "S", 1, 2); + } } - } - void test2(String format) { - if (!I18nFormatUtil.hasFormat( - format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(format, "S", 1); + void test2(String format) { + if (!I18nFormatUtil.hasFormat( + format, I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER)) { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(format, "S", 1); + } } - } - void test3(String format) { - if (I18nFormatUtil.hasFormat( - format, - I18nConversionCategory.GENERAL, - I18nConversionCategory.UNUSED, - I18nConversionCategory.GENERAL)) { - // :: warning: (i18nformat.argument.unused) - MessageFormat.format(format, "S", 1, "S"); + void test3(String format) { + if (I18nFormatUtil.hasFormat( + format, + I18nConversionCategory.GENERAL, + I18nConversionCategory.UNUSED, + I18nConversionCategory.GENERAL)) { + // :: warning: (i18nformat.argument.unused) + MessageFormat.format(format, "S", 1, "S"); + } } - } - void test4(String format) throws Exception { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(format, "S"); - if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.GENERAL)) { - MessageFormat.format(format, "S"); - MessageFormat.format(format, new Date()); - MessageFormat.format(format, 1); - } else { - throw new Exception(); + void test4(String format) throws Exception { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(format, "S"); + if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.GENERAL)) { + MessageFormat.format(format, "S"); + MessageFormat.format(format, new Date()); + MessageFormat.format(format, 1); + } else { + throw new Exception(); + } } - } - void tes5(String format) { - if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.NUMBER)) { - // :: error: (argument.type.incompatible) - MessageFormat.format(format, "S"); - MessageFormat.format(format, 1); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(format, 1); + void tes5(String format) { + if (I18nFormatUtil.hasFormat(format, I18nConversionCategory.NUMBER)) { + // :: error: (argument.type.incompatible) + MessageFormat.format(format, "S"); + MessageFormat.format(format, 1); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(format, 1); + } } - } } diff --git a/checker/tests/i18n-formatter/I18nFormat.java b/checker/tests/i18n-formatter/I18nFormat.java index e6b59925b74..2dc8969c388 100644 --- a/checker/tests/i18n-formatter/I18nFormat.java +++ b/checker/tests/i18n-formatter/I18nFormat.java @@ -3,50 +3,50 @@ public class I18nFormat { - void test() { + void test() { - MessageFormat.format( - "{0} {1, number} {2, time} {3, date} {4, choice, 0#zero}", - "S", 1, new Date(), new Date(), 0); - MessageFormat.format("{0, number}{1, number}", 1, 2); - MessageFormat.format("{0, number}{0}", 1); + MessageFormat.format( + "{0} {1, number} {2, time} {3, date} {4, choice, 0#zero}", + "S", 1, new Date(), new Date(), 0); + MessageFormat.format("{0, number}{1, number}", 1, 2); + MessageFormat.format("{0, number}{0}", 1); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{0, number}'", new Date(12)); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{0, number}'", new Date(12)); - // :: warning: (i18nformat.missing.arguments) - MessageFormat.format("''{0, time, short}''{1}{2, time} {33, number}{4444}'{''''", 0); + // :: warning: (i18nformat.missing.arguments) + MessageFormat.format("''{0, time, short}''{1}{2, time} {33, number}{4444}'{''''", 0); - // :: warning: (i18nformat.missing.arguments) - MessageFormat.format("{0, number}{1, number}", 1); + // :: warning: (i18nformat.missing.arguments) + MessageFormat.format("{0, number}{1, number}", 1); - // :: warning: (i18nformat.argument.unused) - MessageFormat.format("{1, number}", 1, 1); + // :: warning: (i18nformat.argument.unused) + MessageFormat.format("{1, number}", 1, 1); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("{0, number}", 1, new Date()); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("{0, number}", 1, new Date()); - // :: warning: (i18nformat.indirect.arguments) - MessageFormat.format("{0, number}", new Object[2]); + // :: warning: (i18nformat.indirect.arguments) + MessageFormat.format("{0, number}", new Object[2]); - MessageFormat.format("{0}", "S"); - MessageFormat.format("{0}", 1); - MessageFormat.format("{0}", new Date()); + MessageFormat.format("{0}", "S"); + MessageFormat.format("{0}", 1); + MessageFormat.format("{0}", new Date()); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, number}", "S"); - MessageFormat.format("{0, number}", 1); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, number}", new Date()); + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, number}", "S"); + MessageFormat.format("{0, number}", 1); + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, number}", new Date()); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, time}", "S"); - MessageFormat.format("{0, time}", 1); - MessageFormat.format("{0, time}", new Date()); + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, time}", "S"); + MessageFormat.format("{0, time}", 1); + MessageFormat.format("{0, time}", new Date()); - // :: error: (argument.type.incompatible) - MessageFormat.format("{0, date}", "S"); - MessageFormat.format("{0, date}", 1); - MessageFormat.format("{0, date}", new Date()); - } + // :: error: (argument.type.incompatible) + MessageFormat.format("{0, date}", "S"); + MessageFormat.format("{0, date}", 1); + MessageFormat.format("{0, date}", new Date()); + } } diff --git a/checker/tests/i18n-formatter/I18nFormatForTest.java b/checker/tests/i18n-formatter/I18nFormatForTest.java index 7a945e24042..011c37fdbc6 100644 --- a/checker/tests/i18n-formatter/I18nFormatForTest.java +++ b/checker/tests/i18n-formatter/I18nFormatForTest.java @@ -1,96 +1,101 @@ +import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor; + import java.text.MessageFormat; import java.util.Date; -import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor; public class I18nFormatForTest { - static class A { - public void methodA(@I18nFormatFor("#2") String format, Object... args) {} - } - - public static void main(String[] args) { - - A a1 = new A(); - - // :: error: (i18nformat.string.invalid) - a1.methodA("{0, number", new Date(12)); - - // :: warning: (i18nformat.excess.arguments) - a1.methodA("'{0{}", 1); - a1.methodA("{0}", "A"); - - // :: error: (i18nformat.string.invalid) - a(1, 1.2, "{0, number", 1.2, new Date(12)); - a(1, 1.2, "{0, number}{1}", 1.2, 1, "A"); - // :: warning: (i18nformat.missing.arguments) - a(1, 1.2, "{0, number}{1}", 1.2, 1); - // :: warning: (i18nformat.excess.arguments) - a(1, 1.2, "{0, number}{1}", 1.2, 1, "A", 2); - b("{0, number}{1}", 1, "A"); - - // :: error: (i18nformat.string.invalid) - b("{0, number", new Date(12)); - b("{0, number}{1}", 1, "A"); - b("{0}", "a string"); - // :: error: (argument.type.incompatible) - b("{0, number}", "a string"); - - // :: error: (i18nformat.invalid.formatfor) - c("{0, number}{1}", 1, "A"); - - // :: error: (i18nformat.invalid.formatfor) - e(1, 2); - - f("{0}", 2); - - // :: error: (i18nformat.invalid.formatfor) - h("{0}", "a string"); - - // :: error: (i18nformat.invalid.formatfor) - i("{0}", "a string"); - - j("{0}"); - // :: error: (argument.type.incompatible) - j("{0, number}"); - } - - // Normal use - static void b(@I18nFormatFor("#2") String f, Object... args) { - MessageFormat.format(f, args); - } - - // @II18nFormatFor can be annotated anywhere - static void a( - int dummy1, double dummy2, @I18nFormatFor("#5") String f, Object dummy3, Object... args2) { - MessageFormat.format(f, args2); - } - - // Invalid index - static void c(@I18nFormatFor("#-1") String f, Object... args) { - MessageFormat.format("{0}", "A"); - } - - // @I18nFormatFor needs to be annotated to a string. - // :: error: (anno.on.irrelevant) - static void e(@I18nFormatFor("#2") int f, Object... args) {} - - // The parameter type is not necessary to an array of objects - static void f(@I18nFormatFor("#2") String f, int args) { - MessageFormat.format(f, args); - } - - // Invalid formatfor argument - static void h(@I18nFormatFor("2") String f, String args) { - MessageFormat.format(f, args); - } - - // We don't support this form of argument. You need to specify the parameter index. - static void i(@I18nFormatFor("arg") String f, Object... arg) { - MessageFormat.format(f, arg); - } - - // This is also a valid thing to do. - static void j(@I18nFormatFor("#1") String f) { - MessageFormat.format(f, f); - } + static class A { + public void methodA(@I18nFormatFor("#2") String format, Object... args) {} + } + + public static void main(String[] args) { + + A a1 = new A(); + + // :: error: (i18nformat.string.invalid) + a1.methodA("{0, number", new Date(12)); + + // :: warning: (i18nformat.excess.arguments) + a1.methodA("'{0{}", 1); + a1.methodA("{0}", "A"); + + // :: error: (i18nformat.string.invalid) + a(1, 1.2, "{0, number", 1.2, new Date(12)); + a(1, 1.2, "{0, number}{1}", 1.2, 1, "A"); + // :: warning: (i18nformat.missing.arguments) + a(1, 1.2, "{0, number}{1}", 1.2, 1); + // :: warning: (i18nformat.excess.arguments) + a(1, 1.2, "{0, number}{1}", 1.2, 1, "A", 2); + b("{0, number}{1}", 1, "A"); + + // :: error: (i18nformat.string.invalid) + b("{0, number", new Date(12)); + b("{0, number}{1}", 1, "A"); + b("{0}", "a string"); + // :: error: (argument.type.incompatible) + b("{0, number}", "a string"); + + // :: error: (i18nformat.invalid.formatfor) + c("{0, number}{1}", 1, "A"); + + // :: error: (i18nformat.invalid.formatfor) + e(1, 2); + + f("{0}", 2); + + // :: error: (i18nformat.invalid.formatfor) + h("{0}", "a string"); + + // :: error: (i18nformat.invalid.formatfor) + i("{0}", "a string"); + + j("{0}"); + // :: error: (argument.type.incompatible) + j("{0, number}"); + } + + // Normal use + static void b(@I18nFormatFor("#2") String f, Object... args) { + MessageFormat.format(f, args); + } + + // @II18nFormatFor can be annotated anywhere + static void a( + int dummy1, + double dummy2, + @I18nFormatFor("#5") String f, + Object dummy3, + Object... args2) { + MessageFormat.format(f, args2); + } + + // Invalid index + static void c(@I18nFormatFor("#-1") String f, Object... args) { + MessageFormat.format("{0}", "A"); + } + + // @I18nFormatFor needs to be annotated to a string. + // :: error: (anno.on.irrelevant) + static void e(@I18nFormatFor("#2") int f, Object... args) {} + + // The parameter type is not necessary to an array of objects + static void f(@I18nFormatFor("#2") String f, int args) { + MessageFormat.format(f, args); + } + + // Invalid formatfor argument + static void h(@I18nFormatFor("2") String f, String args) { + MessageFormat.format(f, args); + } + + // We don't support this form of argument. You need to specify the parameter index. + static void i(@I18nFormatFor("arg") String f, Object... arg) { + MessageFormat.format(f, arg); + } + + // This is also a valid thing to do. + static void j(@I18nFormatFor("#1") String f) { + MessageFormat.format(f, f); + } } diff --git a/checker/tests/i18n-formatter/IsFormat.java b/checker/tests/i18n-formatter/IsFormat.java index b16e6016a0d..641862e75ac 100644 --- a/checker/tests/i18n-formatter/IsFormat.java +++ b/checker/tests/i18n-formatter/IsFormat.java @@ -1,37 +1,38 @@ -import java.text.MessageFormat; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.util.I18nFormatUtil; +import java.text.MessageFormat; + public class IsFormat { - public static void test1(String cc) { - if (!I18nFormatUtil.isFormat(cc)) { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.GENERAL)) { - MessageFormat.format(cc, "A"); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } + public static void test1(String cc) { + if (!I18nFormatUtil.isFormat(cc)) { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.GENERAL)) { + MessageFormat.format(cc, "A"); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } + } } - } - public static void test2(String cc) { - if (!I18nFormatUtil.isFormat(cc)) { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.NUMBER)) { - MessageFormat.format(cc, 1); - } else { - // :: error: (i18nformat.string.invalid) - MessageFormat.format(cc, "A"); - } + public static void test2(String cc) { + if (!I18nFormatUtil.isFormat(cc)) { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + if (I18nFormatUtil.hasFormat(cc, I18nConversionCategory.NUMBER)) { + MessageFormat.format(cc, 1); + } else { + // :: error: (i18nformat.string.invalid) + MessageFormat.format(cc, "A"); + } + } } - } } diff --git a/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java b/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java index 5986b95819a..0c9df3e3768 100644 --- a/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java +++ b/checker/tests/i18n-formatter/ManualExampleI18nFormatter.java @@ -7,31 +7,31 @@ public class ManualExampleI18nFormatter { - void m(boolean flag) { - - @I18nFormat({NUMBER, DATE}) String f; - - f = "{0, number, #.#} {1, date}"; // OK - f = "{0, number} {1}"; // OK, GENERAL is weaker (less restrictive) than DATE - f = "{0} {1, date}"; // OK, GENERAL is weaker (less restrictive) than NUMBER - // :: warning: (i18nformat.missing.arguments) - f = "{0, number}"; // warning: last argument is ignored - // :: warning: (i18nformat.missing.arguments) - f = "{0}"; // warning: last argument is ignored - // :: warning: (i18nformat.missing.arguments) - f = flag ? "{0, number} {1}" : "{0, number}"; - - if (flag) { - f = "{0, number} {1}"; - } else { - // :: warning: (i18nformat.missing.arguments) - f = "{0, number}"; + void m(boolean flag) { + + @I18nFormat({NUMBER, DATE}) String f; + + f = "{0, number, #.#} {1, date}"; // OK + f = "{0, number} {1}"; // OK, GENERAL is weaker (less restrictive) than DATE + f = "{0} {1, date}"; // OK, GENERAL is weaker (less restrictive) than NUMBER + // :: warning: (i18nformat.missing.arguments) + f = "{0, number}"; // warning: last argument is ignored + // :: warning: (i18nformat.missing.arguments) + f = "{0}"; // warning: last argument is ignored + // :: warning: (i18nformat.missing.arguments) + f = flag ? "{0, number} {1}" : "{0, number}"; + + if (flag) { + f = "{0, number} {1}"; + } else { + // :: warning: (i18nformat.missing.arguments) + f = "{0, number}"; + } + @I18nFormat({NUMBER, DATE}) String f2 = f; + + // :: error: (assignment.type.incompatible) + f = "{0, number} {1, number}"; // error: NUMBER is stronger (more restrictive) than DATE + // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) + f = "{0} {1} {2}"; // error: too many arguments } - @I18nFormat({NUMBER, DATE}) String f2 = f; - - // :: error: (assignment.type.incompatible) - f = "{0, number} {1, number}"; // error: NUMBER is stronger (more restrictive) than DATE - // :: error: (i18nformat.excess.arguments) :: error: (assignment.type.incompatible) - f = "{0} {1} {2}"; // error: too many arguments - } } diff --git a/checker/tests/i18n-formatter/Syntax.java b/checker/tests/i18n-formatter/Syntax.java index a9d5bd2f759..4861d2d8030 100644 --- a/checker/tests/i18n-formatter/Syntax.java +++ b/checker/tests/i18n-formatter/Syntax.java @@ -3,95 +3,95 @@ public class Syntax { - // Test 2.1.1: Missing '}' at end of message format (Unmatched braces in the pattern) - public static void unmatchedBraces() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, number", new Date(12)); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0}{", 1); - - // good - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{0{}", 1); - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{0{}'", 1); - } - - // Test 2.1.2.1: The argument number needs to be an integer - public static void integerRequired() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{{0}}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0.2}", 1); - - // good - // :: warning: (i18nformat.excess.arguments) - MessageFormat.format("'{{0}}'", 1); - } - - // Test 2.1.2.2: The argument number can't be negative - public static void nonNegativeRequired() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{-1, number}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{-123}", 1); - - // good - MessageFormat.format("{0}", 1); - } - - // Test 2.1.3: Format Style required for choice format - public static void formatStyleRequired() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, choice}", 1); - - // good - MessageFormat.format("{0, choice, 0#zero}", 1); - } - - // Test 2.1.4: Wrong format Style - public static void wrongFormatStyle() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, time, number}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, number, y.m.d}", 1); - - // good - MessageFormat.format("{0, time, short}", 1); - MessageFormat.format("{0, number, currency}", 1); - } - - // Test 2.1.5: Unknown format type - public static void unknownFormatType() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, general}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, fool}", 1); - - // good - MessageFormat.format("{0}", 1); - MessageFormat.format("{0, time}", 1); - MessageFormat.format("{0, date}", 1); - MessageFormat.format("{0, number}", 1); - MessageFormat.format("{0, daTe}", 1); - MessageFormat.format("{0, NUMBER}", 1); - } - - // Test 2.1.6: Invalid Subformat Pattern - public static void invalidSubformatPattern() { - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, number, #.#.#}", 1); - // :: error: (i18nformat.string.invalid) - MessageFormat.format("{0, date, y.m.d.x}", new Date()); - - // TODO: This pattern is valid starting with JDK 23. Decide how to handle version-specific - // issues. - // TODO :: error: (i18nformat.string.invalid) - // MessageFormat.format("{0, choice, 0##zero}", 0); - - // good - MessageFormat.format("{0, number, #.#}", 1); - MessageFormat.format("{0, date, y.m.d}", new Date()); - MessageFormat.format("{0, choice, 0>zero}", 0); - } + // Test 2.1.1: Missing '}' at end of message format (Unmatched braces in the pattern) + public static void unmatchedBraces() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, number", new Date(12)); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0}{", 1); + + // good + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{0{}", 1); + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{0{}'", 1); + } + + // Test 2.1.2.1: The argument number needs to be an integer + public static void integerRequired() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{{0}}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0.2}", 1); + + // good + // :: warning: (i18nformat.excess.arguments) + MessageFormat.format("'{{0}}'", 1); + } + + // Test 2.1.2.2: The argument number can't be negative + public static void nonNegativeRequired() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{-1, number}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{-123}", 1); + + // good + MessageFormat.format("{0}", 1); + } + + // Test 2.1.3: Format Style required for choice format + public static void formatStyleRequired() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, choice}", 1); + + // good + MessageFormat.format("{0, choice, 0#zero}", 1); + } + + // Test 2.1.4: Wrong format Style + public static void wrongFormatStyle() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, time, number}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, number, y.m.d}", 1); + + // good + MessageFormat.format("{0, time, short}", 1); + MessageFormat.format("{0, number, currency}", 1); + } + + // Test 2.1.5: Unknown format type + public static void unknownFormatType() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, general}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, fool}", 1); + + // good + MessageFormat.format("{0}", 1); + MessageFormat.format("{0, time}", 1); + MessageFormat.format("{0, date}", 1); + MessageFormat.format("{0, number}", 1); + MessageFormat.format("{0, daTe}", 1); + MessageFormat.format("{0, NUMBER}", 1); + } + + // Test 2.1.6: Invalid Subformat Pattern + public static void invalidSubformatPattern() { + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, number, #.#.#}", 1); + // :: error: (i18nformat.string.invalid) + MessageFormat.format("{0, date, y.m.d.x}", new Date()); + + // TODO: This pattern is valid starting with JDK 23. Decide how to handle version-specific + // issues. + // TODO :: error: (i18nformat.string.invalid) + // MessageFormat.format("{0, choice, 0##zero}", 0); + + // good + MessageFormat.format("{0, number, #.#}", 1); + MessageFormat.format("{0, date, y.m.d}", new Date()); + MessageFormat.format("{0, choice, 0>zero}", 0); + } } diff --git a/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java index 42fd0131aa0..705681b6dee 100644 --- a/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/i18n-unchecked-defaults/TestUncheckedByteCode.java @@ -1,18 +1,18 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - Object field; + Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - field = param.getInt(1); - field = param.getInteger(i); - field = param.identity("hello"); + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + field = param.getInt(1); + field = param.getInteger(i); + field = param.identity("hello"); - // String and Object are relevant types and must be annotated in bytecode - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - } + // String and Object are relevant types and must be annotated in bytecode + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + } } diff --git a/checker/tests/i18n/I18nCollectorsToList.java b/checker/tests/i18n/I18nCollectorsToList.java index c1dd9eb1050..f30602ae473 100644 --- a/checker/tests/i18n/I18nCollectorsToList.java +++ b/checker/tests/i18n/I18nCollectorsToList.java @@ -1,25 +1,26 @@ +import org.checkerframework.checker.i18n.qual.Localized; + import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.checkerframework.checker.i18n.qual.Localized; public class I18nCollectorsToList { - void m(List strings) { - Stream s = strings.stream(); + void m(List strings) { + Stream s = strings.stream(); - List collectedStrings1 = s.collect(Collectors.toList()); - List collectedStrings = s.collect(Collectors.toList()); + List collectedStrings1 = s.collect(Collectors.toList()); + List collectedStrings = s.collect(Collectors.toList()); - // :: error: (methodref.param) - collectedStrings.forEach(System.out::println); - } + // :: error: (methodref.param) + collectedStrings.forEach(System.out::println); + } - void m2(List<@Localized String> strings) { - Stream<@Localized String> s = strings.stream(); + void m2(List<@Localized String> strings) { + Stream<@Localized String> s = strings.stream(); - List<@Localized String> collectedStrings = s.collect(Collectors.toList()); + List<@Localized String> collectedStrings = s.collect(Collectors.toList()); - collectedStrings.forEach(System.out::println); - } + collectedStrings.forEach(System.out::println); + } } diff --git a/checker/tests/i18n/LocalizedMessage.java b/checker/tests/i18n/LocalizedMessage.java index 6ce89f2692f..bac2f4e1468 100644 --- a/checker/tests/i18n/LocalizedMessage.java +++ b/checker/tests/i18n/LocalizedMessage.java @@ -1,59 +1,59 @@ import org.checkerframework.checker.i18n.qual.Localized; public class LocalizedMessage { - @Localized String localize(String s) { - throw new RuntimeException(); - } - - void localized(@Localized String s) {} - - void any(String s) {} - - void stringLiteral() { - // :: error: (argument.type.incompatible) - localized("ldskjfldj"); // error - any("lksjdflkjdf"); - } - - void stringRef(String ref) { - // :: error: (argument.type.incompatible) - localized(ref); // error - any(ref); - } - - void localizedRef(@Localized String ref) { - localized(ref); - any(ref); - } - - void methodRet(String ref) { - localized(localize(ref)); - localized(localize(ref)); - } - - void concatenation(@Localized String s1, String s2) { - // :: error: (argument.type.incompatible) - localized(s1 + s1); // error - // :: error: (argument.type.incompatible) :: error: (compound.assignment.type.incompatible) - localized(s1 += s1); // error - // :: error: (argument.type.incompatible) - localized(s1 + "m"); // error - // :: error: (argument.type.incompatible) - localized(s1 + s2); // error - - // :: error: (argument.type.incompatible) - localized(s2 + s1); // error - // :: error: (argument.type.incompatible) - localized(s2 + "m"); // error - // :: error: (argument.type.incompatible) - localized(s2 + s2); // error - - any(s1 + s1); - any(s1 + "m"); - any(s1 + s2); - - any(s2 + s1); - any(s2 + "m"); - any(s2 + s2); - } + @Localized String localize(String s) { + throw new RuntimeException(); + } + + void localized(@Localized String s) {} + + void any(String s) {} + + void stringLiteral() { + // :: error: (argument.type.incompatible) + localized("ldskjfldj"); // error + any("lksjdflkjdf"); + } + + void stringRef(String ref) { + // :: error: (argument.type.incompatible) + localized(ref); // error + any(ref); + } + + void localizedRef(@Localized String ref) { + localized(ref); + any(ref); + } + + void methodRet(String ref) { + localized(localize(ref)); + localized(localize(ref)); + } + + void concatenation(@Localized String s1, String s2) { + // :: error: (argument.type.incompatible) + localized(s1 + s1); // error + // :: error: (argument.type.incompatible) :: error: (compound.assignment.type.incompatible) + localized(s1 += s1); // error + // :: error: (argument.type.incompatible) + localized(s1 + "m"); // error + // :: error: (argument.type.incompatible) + localized(s1 + s2); // error + + // :: error: (argument.type.incompatible) + localized(s2 + s1); // error + // :: error: (argument.type.incompatible) + localized(s2 + "m"); // error + // :: error: (argument.type.incompatible) + localized(s2 + s2); // error + + any(s1 + s1); + any(s1 + "m"); + any(s1 + s2); + + any(s2 + s1); + any(s2 + "m"); + any(s2 + s2); + } } diff --git a/checker/tests/index-initializedfields/RequireJavadoc.java b/checker/tests/index-initializedfields/RequireJavadoc.java index 9718a9fa42e..8c67a2a7c73 100644 --- a/checker/tests/index-initializedfields/RequireJavadoc.java +++ b/checker/tests/index-initializedfields/RequireJavadoc.java @@ -34,6 +34,13 @@ import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.options.Option; +import org.plumelib.options.Options; + import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -51,11 +58,6 @@ import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.options.Option; -import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -65,907 +67,914 @@ */ public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - @Option("Don't check files or directories whose pathname matches the regex") - public @MonotonicNonNull Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no problems - * should be reported. - */ - @Option("Don't report problems in Java elements whose name matches the regex") - public @MonotonicNonNull Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - @Option("Don't report problems in elements with private access") - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - @Option("Don't report problems in constructors with zero formal parameters") - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

          Trivial getters and setters are of the form: - * - *

          {@code
          -   * SomeType getFoo() {
          -   *   return foo;
          -   * }
          -   *
          -   * SomeType foo() {
          -   *   return foo;
          -   * }
          -   *
          -   * void setFoo(SomeType foo) {
          -   *   this.foo = foo;
          -   * }
          -   *
          -   * boolean hasFoo() {
          -   *   return foo;
          -   * }
          -   *
          -   * boolean isFoo() {
          -   *   return foo;
          -   * }
          -   *
          -   * boolean notFoo() {
          -   *   return !foo;
          -   * }
          -   * }
          - */ - @Option("Don't report problems in trivial getters and setters") - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - @Option("Don't report problems in type declarations") - public boolean dont_require_type; - - /** If true, don't check fields. */ - @Option("Don't report problems in fields") - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - @Option("Don't report problems in methods and constructors") - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - @Option("Require package-info.java file to exist") - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if the - * command-line arguments were absolute pathnames, or no command-line arguments were supplied. - */ - @Option("Report relative rather than absolute filenames") - public boolean relative = false; - - /** If true, output debug information. */ - @Option("Print diagnostic information") - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); - - rj.setJavaFiles(remainingArgs); - - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); - } - - /** Creates a new RequireJavadoc instance. */ - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - @SuppressWarnings("lock:methodref.receiver") // no locking here - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } + /** Matches name of file or directory where no problems should be reported. */ + @Option("Don't check files or directories whose pathname matches the regex") + public @MonotonicNonNull Pattern exclude = null; - FileVisitor walker = new JavaFilesVisitor(); - - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no + * problems should be reported. + */ + @Option("Don't report problems in Java elements whose name matches the regex") + public @MonotonicNonNull Pattern dont_require = null; - javaFiles.sort(Comparator.comparing(Object::toString)); - - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - @SuppressWarnings("nullness:assignment") // the file is not "/", so getParent() is non-null - @NonNull Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } - } + /** If true, don't check elements with private access. */ + @Option("Don't report problems in elements with private access") + public boolean dont_require_private; - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + @Option("Don't report problems in constructors with zero formal parameters") + public boolean dont_require_noarg_constructor; - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** + * If true, don't check trivial getters and setters. + * + *

          Trivial getters and setters are of the form: + * + *

          {@code
          +     * SomeType getFoo() {
          +     *   return foo;
          +     * }
          +     *
          +     * SomeType foo() {
          +     *   return foo;
          +     * }
          +     *
          +     * void setFoo(SomeType foo) {
          +     *   this.foo = foo;
          +     * }
          +     *
          +     * boolean hasFoo() {
          +     *   return foo;
          +     * }
          +     *
          +     * boolean isFoo() {
          +     *   return foo;
          +     * }
          +     *
          +     * boolean notFoo() {
          +     *   return !foo;
          +     * }
          +     * }
          + */ + @Option("Don't report problems in trivial getters and setters") + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + @Option("Don't report problems in type declarations") + public boolean dont_require_type; + + /** If true, don't check fields. */ + @Option("Don't report problems in fields") + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + @Option("Don't report problems in methods and constructors") + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + @Option("Require package-info.java file to exist") + public boolean require_package_info; - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); + /** + * If true, print filenames relative to working directory. Setting this only has an effect if + * the command-line arguments were absolute pathnames, or no command-line arguments were + * supplied. + */ + @Option("Report relative rather than absolute filenames") + public boolean relative = false; + + /** If true, output debug information. */ + @Option("Print diagnostic information") + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); + + rj.setJavaFiles(remainingArgs); + + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } } - } - return FileVisitResult.CONTINUE; + for (String error : rj.errors) { + System.out.println(error); + } + System.exit(rj.errors.isEmpty() ? 0 : 1); } - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Creates a new RequireJavadoc instance. */ + private RequireJavadoc() {} - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + @SuppressWarnings("lock:methodref.receiver") // no locking here + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - } - - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; + FileVisitor walker = new JavaFilesVisitor(); + + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + + javaFiles.sort(Comparator.comparing(Object::toString)); + + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + @SuppressWarnings( + "nullness:assignment") // the file is not "/", so getParent() is non-null + @NonNull Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { + + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); + } + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID; - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; /** - * Create a new PropertyKind. + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; } /** - * Returns true if this is a getter. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @return true if this is a getter + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped */ - boolean isGetter() { - return this != SETTER; + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; } /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor method. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @param md the method to check - * @return the PropertyKind for the given method + * @param path a Java file or directory + * @return true if the file or directory should be skipped */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } - } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
            - *
          • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or - * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} - * or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
          • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and - * has a body of the form {@code this.foo = foo}. - *
          - * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); - } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); - } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

          Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private @Nullable String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - @SuppressWarnings("index") // https://github.com/typetools/checker-framework/issues/5201 - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } - } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; - } - - /** - * Returns true if the body of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } - } - - /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. - * - * @param md a method declaration - * @return its sole statement, or null - */ - private @Nullable Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); - } + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID; + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; + + /** + * Create a new PropertyKind. + * + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type + */ + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; + } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { + /** + * Returns true if this is a getter. + * + * @return true if this is a getter + */ + boolean isGetter() { + return this != SETTER; + } - /** The file being visited. Used for constructing error messages. */ - private Path filename; + /** + * Return the PropertyKind for the given method, or null if it isn't a property accessor + * method. + * + * @param md the method to check + * @return the PropertyKind for the given method + */ + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } /** - * Create a new RequireJavadocVisitor. + * Return true if this method declaration is a trivial getter or setter. * - * @param filename the file being visited; used for diagnostic messages + *

            + *
          • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, + * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return + * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
          • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, + * and has a body of the form {@code this.foo = foo}. + *
          + * + * @param md the method to check + * @return true if this method is a trivial getter or setter */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** - * Return a string stating that documentation is missing on the given construct. + * Return true if this method declaration is a trivial getter or setter of the given kind. * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); } - @Override - public void visit(CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

          Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private @Nullable String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + @SuppressWarnings("index") // https://github.com/typetools/checker-framework/issues/5201 + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } } - @Override - public void visit(ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; } - @Override - public void visit(ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } } - @Override - public void visit(MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private @Nullable Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); } - @Override - public void visit(FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { - @Override - public void visit(EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + /** The file being visited. Used for constructing error messages. */ + private Path filename; - @Override - public void visit(EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + /** + * Create a new RequireJavadocVisitor. + * + * @param filename the file being visited; used for diagnostic messages + */ + public RequireJavadocVisitor(Path filename) { + this.filename = filename; + } - @Override - public void visit(AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + /** + * Return a string stating that documentation is missing on the given construct. + * + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct + */ + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", + path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } + } - @Override - public void visit(AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); + @Override + public void visit(CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } + + @Override + public void visit(ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + @Override + public void visit(ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + @Override + public void visit(MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf( + "skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } + + @Override + public void visit(FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } + + @Override + public void visit(EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } + + @Override + public void visit(EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } + + @Override + public void visit(AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); + } + + @Override + public void visit(AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); + } + + /** + * Return true if this method is annotated with {@code @Override}. + * + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} + */ + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } } /** - * Return true if this method is annotated with {@code @Override}. + * Return true if this node has a Javadoc comment. * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } - } - - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; - } - - /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

          {@code
          -   * /** ... *}{@code /
          -   * // text 1
          -   * // text 2
          -   * void m() { ... }
          -   * }
          - * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. - */ - @SuppressWarnings({ - "JdkObsolete", // for LinkedList - "interning:not.interned", // element of a list - "ReferenceEquality", - }) - // This implementation is from Randoop's `Minimize.java` file, and before that from JavaParser's - // PrettyPrintVisitor.printOrphanCommentsBeforeThisChildNode. The JavaParser maintainers refuse - // to provide such functionality in JavaParser proper. - private static void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; - } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
          {@code
          +     * /** ... *}{@code /
          +     * // text 1
          +     * // text 2
          +     * void m() { ... }
          +     * }
          + * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + @SuppressWarnings({ + "JdkObsolete", // for LinkedList + "interning:not.interned", // element of a list + "ReferenceEquality", + }) + // This implementation is from Randoop's `Minimize.java` file, and before that from JavaParser's + // PrettyPrintVisitor.printOrphanCommentsBeforeThisChildNode. The JavaParser maintainers refuse + // to provide such functionality in JavaParser proper. + private static void getOrphanCommentsBeforeThisChildNode( + final Node node, List result) { + if (node instanceof Comment) { + return; + } + + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); + } } - } } diff --git a/checker/tests/index/AndExample.java b/checker/tests/index/AndExample.java index 580b3c8b0ef..cbef93875e4 100644 --- a/checker/tests/index/AndExample.java +++ b/checker/tests/index/AndExample.java @@ -3,14 +3,14 @@ public class AndExample { - @SuppressWarnings("index") // forward reference to field iYearInfoCache - private static final @IndexOrHigh("iYearInfoCache") int CACHE_SIZE = 1 << 10; + @SuppressWarnings("index") // forward reference to field iYearInfoCache + private static final @IndexOrHigh("iYearInfoCache") int CACHE_SIZE = 1 << 10; - private static final @IndexFor("iYearInfoCache") int CACHE_MASK = CACHE_SIZE - 1; + private static final @IndexFor("iYearInfoCache") int CACHE_MASK = CACHE_SIZE - 1; - private static final String[] iYearInfoCache = new String[CACHE_SIZE]; + private static final String[] iYearInfoCache = new String[CACHE_SIZE]; - private String getYearInfo(int year) { - return iYearInfoCache[year & CACHE_MASK]; - } + private String getYearInfo(int year) { + return iYearInfoCache[year & CACHE_MASK]; + } } diff --git a/checker/tests/index/AnnotatedJDKTest.java b/checker/tests/index/AnnotatedJDKTest.java index c8008a159f1..9dd1da5a0b4 100644 --- a/checker/tests/index/AnnotatedJDKTest.java +++ b/checker/tests/index/AnnotatedJDKTest.java @@ -2,9 +2,9 @@ public class AnnotatedJDKTest { - public void printWriterWrite(PrintWriter writer) { - writer.write(-1); + public void printWriterWrite(PrintWriter writer) { + writer.write(-1); - writer.write(8); - } + writer.write(8); + } } diff --git a/checker/tests/index/ArrayAsList.java b/checker/tests/index/ArrayAsList.java index 744fca8fffb..5b143003bff 100644 --- a/checker/tests/index/ArrayAsList.java +++ b/checker/tests/index/ArrayAsList.java @@ -1,26 +1,27 @@ +import org.checkerframework.common.value.qual.MinLen; + import java.util.Arrays; import java.util.List; -import org.checkerframework.common.value.qual.MinLen; // @skip-test until we bring list support back public class ArrayAsList { - public static void toList(Integer @MinLen(10) [] arg) { - @MinLen(10) List list = Arrays.asList(arg); - System.out.println("Integer: " + list.size()); - } + public static void toList(Integer @MinLen(10) [] arg) { + @MinLen(10) List list = Arrays.asList(arg); + System.out.println("Integer: " + list.size()); + } - public static void toList2(int @MinLen(10) [] arg2) { - // :: error: (assignment.type.incompatible) - @MinLen(10) List list = Arrays.asList(arg2); - System.out.println("int: " + list.size()); + public static void toList2(int @MinLen(10) [] arg2) { + // :: error: (assignment.type.incompatible) + @MinLen(10) List list = Arrays.asList(arg2); + System.out.println("int: " + list.size()); - @MinLen(1) List list2 = Arrays.asList(arg2); - } + @MinLen(1) List list2 = Arrays.asList(arg2); + } - public static void toList3() { - @MinLen(4) List list = Arrays.asList(1, 2, 3, 6); - System.out.println("args: " + list.size()); - } + public static void toList3() { + @MinLen(4) List list = Arrays.asList(1, 2, 3, 6); + System.out.println("args: " + list.size()); + } } diff --git a/checker/tests/index/ArrayAssignmentSameLen.java b/checker/tests/index/ArrayAssignmentSameLen.java index 4e861d852fe..a68a2f288ff 100644 --- a/checker/tests/index/ArrayAssignmentSameLen.java +++ b/checker/tests/index/ArrayAssignmentSameLen.java @@ -2,56 +2,56 @@ public class ArrayAssignmentSameLen { - private final int[] i_array; - private final @IndexFor("i_array") int i_index; - - ArrayAssignmentSameLen(int[] array, @IndexFor("#1") int index) { - i_array = array; - i_index = index; - } - - void test1(int[] a, int[] b, @LTEqLengthOf("#1") int index) { - int[] array = a; - @LTLengthOf( - value = {"array", "b"}, - offset = {"0", "-3"}) - // :: error: (assignment.type.incompatible) - int i = index; - } - - void test2(int[] a, int[] b, @LTLengthOf("#1") int i) { - int[] c = a; - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = {"c", "b"}) int x = i; - @LTLengthOf("c") int y = i; - } - - void test3(int[] a, @LTLengthOf("#1") int i, @NonNegative int x) { - int[] c1 = a; - // See useTest3 for an example of why this assignment should fail. - @LTLengthOf( - value = {"c1", "c1"}, - offset = {"0", "x"}) - // :: error: (assignment.type.incompatible) - int z = i; - } - - void test4( - int[] a, - @LTLengthOf( - value = {"#1", "#1"}, - offset = {"0", "#3"}) - int i, - @NonNegative int x) { - int[] c1 = a; - @LTLengthOf( - value = {"c1", "c1"}, - offset = {"0", "x"}) - int z = i; - } - - void useTest3() { - int[] a = {1, 3}; - test3(a, 0, 10); - } + private final int[] i_array; + private final @IndexFor("i_array") int i_index; + + ArrayAssignmentSameLen(int[] array, @IndexFor("#1") int index) { + i_array = array; + i_index = index; + } + + void test1(int[] a, int[] b, @LTEqLengthOf("#1") int index) { + int[] array = a; + @LTLengthOf( + value = {"array", "b"}, + offset = {"0", "-3"}) + // :: error: (assignment.type.incompatible) + int i = index; + } + + void test2(int[] a, int[] b, @LTLengthOf("#1") int i) { + int[] c = a; + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = {"c", "b"}) int x = i; + @LTLengthOf("c") int y = i; + } + + void test3(int[] a, @LTLengthOf("#1") int i, @NonNegative int x) { + int[] c1 = a; + // See useTest3 for an example of why this assignment should fail. + @LTLengthOf( + value = {"c1", "c1"}, + offset = {"0", "x"}) + // :: error: (assignment.type.incompatible) + int z = i; + } + + void test4( + int[] a, + @LTLengthOf( + value = {"#1", "#1"}, + offset = {"0", "#3"}) + int i, + @NonNegative int x) { + int[] c1 = a; + @LTLengthOf( + value = {"c1", "c1"}, + offset = {"0", "x"}) + int z = i; + } + + void useTest3() { + int[] a = {1, 3}; + test3(a, 0, 10); + } } diff --git a/checker/tests/index/ArrayAssignmentSameLenComplex.java b/checker/tests/index/ArrayAssignmentSameLenComplex.java index 8f95666bae0..5181c7561da 100644 --- a/checker/tests/index/ArrayAssignmentSameLenComplex.java +++ b/checker/tests/index/ArrayAssignmentSameLenComplex.java @@ -5,19 +5,19 @@ public class ArrayAssignmentSameLenComplex { - static class Partial { - private final int[] iValues; + static class Partial { + private final int[] iValues; - Partial(@NonNegative int n) { - iValues = new int[n]; + Partial(@NonNegative int n) { + iValues = new int[n]; + } } - } - private final Partial iBase; - private final @IndexFor("iBase.iValues") int iFieldIndex; + private final Partial iBase; + private final @IndexFor("iBase.iValues") int iFieldIndex; - ArrayAssignmentSameLenComplex(Partial partial, @IndexFor("#1.iValues") int fieldIndex) { - iBase = partial; - iFieldIndex = fieldIndex; - } + ArrayAssignmentSameLenComplex(Partial partial, @IndexFor("#1.iValues") int fieldIndex) { + iBase = partial; + iFieldIndex = fieldIndex; + } } diff --git a/checker/tests/index/ArrayConstructionPositiveLength.java b/checker/tests/index/ArrayConstructionPositiveLength.java index c6ee6dcc6cc..2fd48604fad 100644 --- a/checker/tests/index/ArrayConstructionPositiveLength.java +++ b/checker/tests/index/ArrayConstructionPositiveLength.java @@ -6,7 +6,7 @@ public class ArrayConstructionPositiveLength { - public void makeArray(@Positive int max_values) { - String @MinLen(1) [] a = new String[max_values]; - } + public void makeArray(@Positive int max_values) { + String @MinLen(1) [] a = new String[max_values]; + } } diff --git a/checker/tests/index/ArrayCopy.java b/checker/tests/index/ArrayCopy.java index b73f95ca37f..10d18580238 100644 --- a/checker/tests/index/ArrayCopy.java +++ b/checker/tests/index/ArrayCopy.java @@ -2,9 +2,9 @@ public class ArrayCopy { - void copy(int @MinLen(1) [] nums) { - int[] nums_copy = new int[nums.length]; - System.arraycopy(nums, 0, nums_copy, 0, nums.length); - nums = nums_copy; - } + void copy(int @MinLen(1) [] nums) { + int[] nums_copy = new int[nums.length]; + System.arraycopy(nums, 0, nums_copy, 0, nums.length); + nums = nums_copy; + } } diff --git a/checker/tests/index/ArrayCreationChecks.java b/checker/tests/index/ArrayCreationChecks.java index 49ae5942738..452399e4073 100644 --- a/checker/tests/index/ArrayCreationChecks.java +++ b/checker/tests/index/ArrayCreationChecks.java @@ -4,47 +4,47 @@ public class ArrayCreationChecks { - void test1(@Positive int x, @Positive int y) { - int[] newArray = new int[x + y]; - @IndexFor("newArray") int i = x; - @IndexFor("newArray") int j = y; - } - - void test2(@NonNegative int x, @Positive int y) { - int[] newArray = new int[x + y]; - @IndexFor("newArray") int i = x; - @IndexOrHigh("newArray") int j = y; - } - - void test3(@NonNegative int x, @NonNegative int y) { - int[] newArray = new int[x + y]; - @IndexOrHigh("newArray") int i = x; - @IndexOrHigh("newArray") int j = y; - } - - void test4(@GTENegativeOne int x, @NonNegative int y) { - // :: error: (array.length.negative) - int[] newArray = new int[x + y]; - @LTEqLengthOf("newArray") int i = x; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int j = y; - } - - void test5(@GTENegativeOne int x, @GTENegativeOne int y) { - // :: error: (array.length.negative) - int[] newArray = new int[x + y]; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int i = x; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int j = y; - } - - void test6(int x, int y) { - // :: error: (array.length.negative) - int[] newArray = new int[x + y]; - // :: error: (assignment.type.incompatible) - @IndexFor("newArray") int i = x; - // :: error: (assignment.type.incompatible) - @IndexOrHigh("newArray") int j = y; - } + void test1(@Positive int x, @Positive int y) { + int[] newArray = new int[x + y]; + @IndexFor("newArray") int i = x; + @IndexFor("newArray") int j = y; + } + + void test2(@NonNegative int x, @Positive int y) { + int[] newArray = new int[x + y]; + @IndexFor("newArray") int i = x; + @IndexOrHigh("newArray") int j = y; + } + + void test3(@NonNegative int x, @NonNegative int y) { + int[] newArray = new int[x + y]; + @IndexOrHigh("newArray") int i = x; + @IndexOrHigh("newArray") int j = y; + } + + void test4(@GTENegativeOne int x, @NonNegative int y) { + // :: error: (array.length.negative) + int[] newArray = new int[x + y]; + @LTEqLengthOf("newArray") int i = x; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int j = y; + } + + void test5(@GTENegativeOne int x, @GTENegativeOne int y) { + // :: error: (array.length.negative) + int[] newArray = new int[x + y]; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int i = x; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int j = y; + } + + void test6(int x, int y) { + // :: error: (array.length.negative) + int[] newArray = new int[x + y]; + // :: error: (assignment.type.incompatible) + @IndexFor("newArray") int i = x; + // :: error: (assignment.type.incompatible) + @IndexOrHigh("newArray") int j = y; + } } diff --git a/checker/tests/index/ArrayCreationParam.java b/checker/tests/index/ArrayCreationParam.java index 188cc212201..55375ecbe4f 100644 --- a/checker/tests/index/ArrayCreationParam.java +++ b/checker/tests/index/ArrayCreationParam.java @@ -4,14 +4,14 @@ public class ArrayCreationParam { - public static void m1() { - int n = 5; - int[] a = new int[n + 1]; - // Index Checker correctly issues no warning on the lines below - @LTLengthOf("a") int j = n; - @IndexFor("a") int k = n; - for (int i = 1; i <= n; i++) { - int x = a[i]; + public static void m1() { + int n = 5; + int[] a = new int[n + 1]; + // Index Checker correctly issues no warning on the lines below + @LTLengthOf("a") int j = n; + @IndexFor("a") int k = n; + for (int i = 1; i <= n; i++) { + int x = a[i]; + } } - } } diff --git a/checker/tests/index/ArrayCreationTest.java b/checker/tests/index/ArrayCreationTest.java index f526c0f7acd..2fa6fd9f0fd 100644 --- a/checker/tests/index/ArrayCreationTest.java +++ b/checker/tests/index/ArrayCreationTest.java @@ -4,8 +4,8 @@ // makes both @SameLen of each other. public class ArrayCreationTest { - void test(int[] a, int[] d) { - int[] b = new int[a.length]; - int @SameLen({"a", "b"}) [] c = b; - } + void test(int[] a, int[] d) { + int[] b = new int[a.length]; + int @SameLen({"a", "b"}) [] c = b; + } } diff --git a/checker/tests/index/ArrayIntro.java b/checker/tests/index/ArrayIntro.java index 48bb0f62e9c..51218711aa2 100644 --- a/checker/tests/index/ArrayIntro.java +++ b/checker/tests/index/ArrayIntro.java @@ -2,18 +2,18 @@ @SuppressWarnings("lowerbound") public class ArrayIntro { - void test() { - int @MinLen(5) [] arr = new int[5]; - int a = 9; - a += 5; - a -= 2; - int @MinLen(12) [] arr1 = new int[a]; - int @MinLen(3) [] arr2 = {1, 2, 3}; - // :: error: (assignment.type.incompatible) - int @MinLen(4) [] arr3 = {4, 5, 6}; - // :: error: (assignment.type.incompatible) - int @MinLen(7) [] arr4 = new int[4]; - // :: error: (assignment.type.incompatible) - int @MinLen(16) [] arr5 = new int[a]; - } + void test() { + int @MinLen(5) [] arr = new int[5]; + int a = 9; + a += 5; + a -= 2; + int @MinLen(12) [] arr1 = new int[a]; + int @MinLen(3) [] arr2 = {1, 2, 3}; + // :: error: (assignment.type.incompatible) + int @MinLen(4) [] arr3 = {4, 5, 6}; + // :: error: (assignment.type.incompatible) + int @MinLen(7) [] arr4 = new int[4]; + // :: error: (assignment.type.incompatible) + int @MinLen(16) [] arr5 = new int[a]; + } } diff --git a/checker/tests/index/ArrayIntroWithCast.java b/checker/tests/index/ArrayIntroWithCast.java index b55a4bc60d6..e2068c29fea 100644 --- a/checker/tests/index/ArrayIntroWithCast.java +++ b/checker/tests/index/ArrayIntroWithCast.java @@ -2,14 +2,14 @@ public class ArrayIntroWithCast { - void test(String[] a, String[] b) { - Object result = new Object[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - } + void test(String[] a, String[] b) { + Object result = new Object[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + } - void test2(String[] a, String[] b) { - @SuppressWarnings("unchecked") - T[] result = (T[]) new Object[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - } + void test2(String[] a, String[] b) { + @SuppressWarnings("unchecked") + T[] result = (T[]) new Object[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + } } diff --git a/checker/tests/index/ArrayLenTest.java b/checker/tests/index/ArrayLenTest.java index 932ed3364ab..55eebbbba7b 100644 --- a/checker/tests/index/ArrayLenTest.java +++ b/checker/tests/index/ArrayLenTest.java @@ -1,13 +1,13 @@ import org.checkerframework.common.value.qual.*; public class ArrayLenTest { - public static String esc_quantify(String @ArrayLen({1, 2}) ... vars) { - if (vars.length == 1) { - return vars[0]; - } else { - @IntVal({2}) int i = vars.length; - String @ArrayLen({2}) [] a = vars; - return vars[0] + vars[1]; + public static String esc_quantify(String @ArrayLen({1, 2}) ... vars) { + if (vars.length == 1) { + return vars[0]; + } else { + @IntVal({2}) int i = vars.length; + String @ArrayLen({2}) [] a = vars; + return vars[0] + vars[1]; + } } - } } diff --git a/checker/tests/index/ArrayLength.java b/checker/tests/index/ArrayLength.java index 7dece22d390..b646fb2de1d 100644 --- a/checker/tests/index/ArrayLength.java +++ b/checker/tests/index/ArrayLength.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.index.qual.LTEqLengthOf; public class ArrayLength { - void test() { - int[] arr = {1, 2, 3}; - @LTEqLengthOf({"arr"}) int a = arr.length; - } + void test() { + int[] arr = {1, 2, 3}; + @LTEqLengthOf({"arr"}) int a = arr.length; + } } diff --git a/checker/tests/index/ArrayLength2.java b/checker/tests/index/ArrayLength2.java index 5fece54fb68..2dd0d8240a0 100644 --- a/checker/tests/index/ArrayLength2.java +++ b/checker/tests/index/ArrayLength2.java @@ -5,9 +5,9 @@ import org.checkerframework.common.value.qual.MinLen; public class ArrayLength2 { - public static void main(String[] args) { - int N = 8; - int @MinLen(8) [] Grid = new int[N]; - @LTLengthOf("Grid") int i = 0; - } + public static void main(String[] args) { + int N = 8; + int @MinLen(8) [] Grid = new int[N]; + @LTLengthOf("Grid") int i = 0; + } } diff --git a/checker/tests/index/ArrayLength3.java b/checker/tests/index/ArrayLength3.java index 4299e5ba82c..a7588470b5b 100644 --- a/checker/tests/index/ArrayLength3.java +++ b/checker/tests/index/ArrayLength3.java @@ -5,12 +5,12 @@ import org.checkerframework.common.value.qual.ArrayLen; public class ArrayLength3 { - String getFirst(String @ArrayLen(2) [] sa) { - return sa[0]; - } + String getFirst(String @ArrayLen(2) [] sa) { + return sa[0]; + } - void m() { - Integer[] a = new Integer[10]; - @LTLengthOf("a") int i = 5; - } + void m() { + Integer[] a = new Integer[10]; + @LTLengthOf("a") int i = 5; + } } diff --git a/checker/tests/index/ArrayLengthEquality.java b/checker/tests/index/ArrayLengthEquality.java index 0d209389a84..13e09dada30 100644 --- a/checker/tests/index/ArrayLengthEquality.java +++ b/checker/tests/index/ArrayLengthEquality.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.index.qual.SameLen; public class ArrayLengthEquality { - void test(int[] a, int[] b) { - if (a.length == b.length) { - int @SameLen({"a", "b"}) [] c = a; - int @SameLen({"a", "b"}) [] d = b; + void test(int[] a, int[] b) { + if (a.length == b.length) { + int @SameLen({"a", "b"}) [] c = a; + int @SameLen({"a", "b"}) [] d = b; + } + if (a.length != b.length) { + // Do nothing. + int x = 0; + } else { + int @SameLen({"a", "b"}) [] e = a; + int @SameLen({"a", "b"}) [] f = b; + } } - if (a.length != b.length) { - // Do nothing. - int x = 0; - } else { - int @SameLen({"a", "b"}) [] e = a; - int @SameLen({"a", "b"}) [] f = b; - } - } } diff --git a/checker/tests/index/ArrayLengthLBC.java b/checker/tests/index/ArrayLengthLBC.java index 84caa60b64a..b326ddf93ba 100644 --- a/checker/tests/index/ArrayLengthLBC.java +++ b/checker/tests/index/ArrayLengthLBC.java @@ -2,12 +2,12 @@ public class ArrayLengthLBC { - public static Date[] add_date(Date[] dates, Date new_date) { - Date[] new_dates = new Date[dates.length + 1]; - System.arraycopy(dates, 0, new_dates, 0, dates.length); - new_dates[dates.length] = new_date; - Date[] new_dates_cast = new_dates; - return (new_dates_cast); - } + public static Date[] add_date(Date[] dates, Date new_date) { + Date[] new_dates = new Date[dates.length + 1]; + System.arraycopy(dates, 0, new_dates, 0, dates.length); + new_dates[dates.length] = new_date; + Date[] new_dates_cast = new_dates; + return (new_dates_cast); + } } // a comment diff --git a/checker/tests/index/ArrayNull.java b/checker/tests/index/ArrayNull.java index deb5739aa01..bdb6a057d9f 100644 --- a/checker/tests/index/ArrayNull.java +++ b/checker/tests/index/ArrayNull.java @@ -1,4 +1,4 @@ public class ArrayNull { - Object[][] a = new Object[][] {new Object[] {null}, null}; + Object[][] a = new Object[][] {new Object[] {null}, null}; } diff --git a/checker/tests/index/ArrayWrapper.java b/checker/tests/index/ArrayWrapper.java index 88ae8fccfd6..514bc450d61 100644 --- a/checker/tests/index/ArrayWrapper.java +++ b/checker/tests/index/ArrayWrapper.java @@ -13,53 +13,53 @@ /** ArrayWrapper is a fixed-size generic collection. */ public class ArrayWrapper { - private final Object @SameLen("this") [] delegate; + private final Object @SameLen("this") [] delegate; - @SuppressWarnings("index") // constructor creates object of size @SameLen(this) by definition - ArrayWrapper(@NonNegative int size) { - delegate = new Object[size]; - } + @SuppressWarnings("index") // constructor creates object of size @SameLen(this) by definition + ArrayWrapper(@NonNegative int size) { + delegate = new Object[size]; + } - public @LengthOf("this") int size() { - return delegate.length; - } + public @LengthOf("this") int size() { + return delegate.length; + } - public void set(@IndexFor("this") int index, T obj) { - delegate[index] = obj; - } + public void set(@IndexFor("this") int index, T obj) { + delegate[index] = obj; + } - @SuppressWarnings("unchecked") // required for normal Java compilation due to unchecked cast - public T get(@IndexFor("this") int index) { - return (T) delegate[index]; - } + @SuppressWarnings("unchecked") // required for normal Java compilation due to unchecked cast + public T get(@IndexFor("this") int index) { + return (T) delegate[index]; + } - public static void clearIndex1(ArrayWrapper a, @IndexFor("#1") int i) { - a.set(i, null); - } + public static void clearIndex1(ArrayWrapper a, @IndexFor("#1") int i) { + a.set(i, null); + } - public static void clearIndex2(ArrayWrapper a, int i) { - if (0 <= i && i < a.size()) { - a.set(i, null); + public static void clearIndex2(ArrayWrapper a, int i) { + if (0 <= i && i < a.size()) { + a.set(i, null); + } } - } - public static void clearIndex3(ArrayWrapper a, @NonNegative int i) { - if (i < a.size()) { - a.set(i, null); + public static void clearIndex3(ArrayWrapper a, @NonNegative int i) { + if (i < a.size()) { + a.set(i, null); + } } - } - // The following methods are tests that sequence annotations work correctly with - // user-defined sequence types. + // The following methods are tests that sequence annotations work correctly with + // user-defined sequence types. - public static Object testSameLen( - @SameLen("#2") ArrayWrapper a, - @SameLen("#1") ArrayWrapper b, - @IndexFor("#1") int i) { - return b.get(i); - } + public static Object testSameLen( + @SameLen("#2") ArrayWrapper a, + @SameLen("#1") ArrayWrapper b, + @IndexFor("#1") int i) { + return b.get(i); + } - public static Object testMinLen(@MinLen(3) ArrayWrapper a) { - return a.get(2); - } + public static Object testMinLen(@MinLen(3) ArrayWrapper a) { + return a.get(2); + } } diff --git a/checker/tests/index/ArraysSort.java b/checker/tests/index/ArraysSort.java index a9bdc139666..d8963d05b84 100644 --- a/checker/tests/index/ArraysSort.java +++ b/checker/tests/index/ArraysSort.java @@ -1,12 +1,13 @@ -import java.util.Arrays; import org.checkerframework.common.value.qual.MinLen; +import java.util.Arrays; + public class ArraysSort { - void sortInt(int @MinLen(10) [] nums) { - // Checks the correct handling of the toIndex parameter - Arrays.sort(nums, 0, 10); - // :: error: (argument.type.incompatible) - Arrays.sort(nums, 0, 11); - } + void sortInt(int @MinLen(10) [] nums) { + // Checks the correct handling of the toIndex parameter + Arrays.sort(nums, 0, 10); + // :: error: (argument.type.incompatible) + Arrays.sort(nums, 0, 11); + } } diff --git a/checker/tests/index/BasicSubsequence.java b/checker/tests/index/BasicSubsequence.java index 2c34408b475..e021f25df13 100644 --- a/checker/tests/index/BasicSubsequence.java +++ b/checker/tests/index/BasicSubsequence.java @@ -1,47 +1,47 @@ import org.checkerframework.checker.index.qual.*; public class BasicSubsequence { - // :: error: not.final - @HasSubsequence(subsequence = "this", from = "this.x", to = "this.y") - int[] b; - - int x; - int y; - - void test2(@NonNegative @LessThan("y + 1") int x1, int[] a) { - x = x1; - // :: error: to.not.ltel - b = a; - } - - void test3(@NonNegative @LessThan("y") int x1, int[] a) { - x = x1; - // :: error: to.not.ltel - b = a; - } - - void test4(@NonNegative int x1, int[] a) { - x = x1; - // :: error: from.gt.to :: error: to.not.ltel - b = a; - } - - void test5(@GTENegativeOne @LessThan("y + 1") int x1, int[] a) { - x = x1; - // :: error: from.not.nonnegative :: error: to.not.ltel - b = a; - } - - void test6(@GTENegativeOne int x1, int[] a) { - x = x1; - // :: error: from.not.nonnegative :: error: to.not.ltel :: error: from.gt.to - b = a; - } - - void test7(@IndexFor("this") @LessThan("y") int x1, @IndexOrHigh("this") int y1, int[] a) { - x = x1; - y = y1; - // :: warning: which.subsequence - b = a; - } + // :: error: not.final + @HasSubsequence(subsequence = "this", from = "this.x", to = "this.y") + int[] b; + + int x; + int y; + + void test2(@NonNegative @LessThan("y + 1") int x1, int[] a) { + x = x1; + // :: error: to.not.ltel + b = a; + } + + void test3(@NonNegative @LessThan("y") int x1, int[] a) { + x = x1; + // :: error: to.not.ltel + b = a; + } + + void test4(@NonNegative int x1, int[] a) { + x = x1; + // :: error: from.gt.to :: error: to.not.ltel + b = a; + } + + void test5(@GTENegativeOne @LessThan("y + 1") int x1, int[] a) { + x = x1; + // :: error: from.not.nonnegative :: error: to.not.ltel + b = a; + } + + void test6(@GTENegativeOne int x1, int[] a) { + x = x1; + // :: error: from.not.nonnegative :: error: to.not.ltel :: error: from.gt.to + b = a; + } + + void test7(@IndexFor("this") @LessThan("y") int x1, @IndexOrHigh("this") int y1, int[] a) { + x = x1; + y = y1; + // :: warning: which.subsequence + b = a; + } } diff --git a/checker/tests/index/BasicSubsequence2.java b/checker/tests/index/BasicSubsequence2.java index 6ac9ccc08eb..2b572221c98 100644 --- a/checker/tests/index/BasicSubsequence2.java +++ b/checker/tests/index/BasicSubsequence2.java @@ -3,34 +3,34 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class BasicSubsequence2 { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - int[] array; + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + int[] array; - @HasSubsequence(subsequence = "this", from = "start", to = "end") - int[] array2; + @HasSubsequence(subsequence = "this", from = "start", to = "end") + int[] array2; - final @IndexFor("array") int start; + final @IndexFor("array") int start; - final @IndexOrHigh("array") int end; + final @IndexOrHigh("array") int end; - private BasicSubsequence2(@IndexFor("array") int s, @IndexOrHigh("array") int e) { - start = s; - end = e; - } + private BasicSubsequence2(@IndexFor("array") int s, @IndexOrHigh("array") int e) { + start = s; + end = e; + } - void testStartIndex(@IndexFor("this") int x) { - @IndexFor("array") int y = x + start; - } + void testStartIndex(@IndexFor("this") int x) { + @IndexFor("array") int y = x + start; + } - void testViewpointAdaption(@IndexFor("this") int x) { - @IndexFor("array2") int y = x + start; - } + void testViewpointAdaption(@IndexFor("this") int x) { + @IndexFor("array2") int y = x + start; + } - void testArrayAccess(@IndexFor("this") int x) { - int y = array[x + start]; - } + void testArrayAccess(@IndexFor("this") int x) { + int y = array[x + start]; + } - void testCommutativity(@IndexFor("this") int x) { - @IndexFor("array") int y = start + x; - } + void testCommutativity(@IndexFor("this") int x) { + @IndexFor("array") int y = start + x; + } } diff --git a/checker/tests/index/BasicSubsequence3.java b/checker/tests/index/BasicSubsequence3.java index b99257052b8..bb25d8058c1 100644 --- a/checker/tests/index/BasicSubsequence3.java +++ b/checker/tests/index/BasicSubsequence3.java @@ -5,26 +5,26 @@ @SuppressWarnings("lowerbound") public class BasicSubsequence3 { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - int[] array; + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + int[] array; - @HasSubsequence(subsequence = "this", from = "start", to = "end") - int[] array2; + @HasSubsequence(subsequence = "this", from = "start", to = "end") + int[] array2; - final @IndexFor("array") int start; + final @IndexFor("array") int start; - final @IndexOrHigh("array") int end; + final @IndexOrHigh("array") int end; - private BasicSubsequence3(@IndexFor("array") int s, @IndexOrHigh("array") int e) { - start = s; - end = e; - } + private BasicSubsequence3(@IndexFor("array") int s, @IndexOrHigh("array") int e) { + start = s; + end = e; + } - void testStartIndex(@IndexFor("array") @LessThan("this.end") int x) { - @IndexFor("this") int y = x - start; - } + void testStartIndex(@IndexFor("array") @LessThan("this.end") int x) { + @IndexFor("this") int y = x - start; + } - void testViewpointAdaption(@IndexFor("array2") @LessThan("end") int x) { - @IndexFor("this") int y = x - start; - } + void testViewpointAdaption(@IndexFor("array2") @LessThan("end") int x) { + @IndexFor("this") int y = x - start; + } } diff --git a/checker/tests/index/BigBinaryExpr.java b/checker/tests/index/BigBinaryExpr.java index 039f7f2a8d8..318cdb9cd8f 100644 --- a/checker/tests/index/BigBinaryExpr.java +++ b/checker/tests/index/BigBinaryExpr.java @@ -1,111 +1,111 @@ public class BigBinaryExpr { - void test() { - int i0 = 163; - int i1 = 153; - int i2 = 75; - int i3 = -72; - int i4 = 61; - int i5 = 7; - int i6 = 83; - int i7 = -36; - int i8 = -90; - int i9 = -93; - int i10 = 187; - int i11 = -76; - int i12 = -16; - int i13 = -99; - int i14 = 113; - int i15 = 72; - int i16 = 58; - int i17 = -97; - int i18 = 115; - int i19 = -85; - int i20 = 156; - int i21 = -10; - int i22 = -85; - int i23 = 81; - int i24 = 63; - int i25 = -49; - int i26 = 158; - int i27 = 158; - int i28 = 25; - int i29 = 136; - int i30 = -90; - int i31 = 115; - int i32 = 179; - int i33 = 11; - int i34 = -100; - int i35 = 70; - int i36 = -46; - int i37 = -56; - int i38 = 108; - int i39 = -41; - int i40 = 124; - int i41 = -88; - int i42 = 54; - int i43 = 117; - int i44 = -92; - int i45 = 7; - int i46 = -94; - int i47 = 162; - int i48 = -34; - int i49 = 104; - int i50 = 111; - int i51 = -16; - int i52 = 197; - int i53 = -8; - int i54 = 101; - int i55 = 96; - int i56 = 132; - int i57 = -36; - int i58 = 148; - int i59 = 43; - int i60 = -59; - int i61 = 150; - int i62 = 48; - int i63 = 130; - int i64 = 74; - int i65 = -1; - int i66 = 79; - int i67 = 109; - int i68 = -70; - int i69 = 111; - int i70 = 78; - int i71 = 155; - int i72 = 176; - int i73 = 80; - int i74 = 181; - int i75 = 41; - int i76 = -85; - int i77 = 189; - int i78 = 97; - int i79 = 139; - int i80 = 9; - int i81 = 42; - int i82 = -50; - int i83 = 82; - int i84 = -70; - int i85 = 162; - int i86 = -20; - int i87 = 52; - int i88 = -94; - int i89 = 133; - int i90 = 136; - int i91 = 129; - int i92 = -55; - int i93 = 153; - int i94 = 6; - int i95 = -18; - int i96 = 132; - int i97 = 45; - int i98 = 120; - int i99 = 60; - // int result = i0 +i1 +i2 +i3 +i4 +i5 +i6 +i7 +i8 +i9 +i10 +i11 +i12 +i13 +i14 +i15 +i16 - // +i17 +i18 +i19 +i20 +i21 +i22 +i23 +i24 +i25 +i26 +i27 +i28 +i29 +i30 +i31 +i32 +i33 +i34 - // +i35 +i36 +i37 +i38 +i39 +i40 +i41 +i42 +i43 +i44 +i45 +i46 +i47 +i48 +i49 +i50 +i51 +i52 - // +i53 +i54 +i55 +i56 +i57 +i58 +i59 +i60 +i61 +i62 +i63 +i64 +i65 +i66 +i67 +i68 +i69 +i70 - // +i71 +i72 +i73 +i74 +i75 +i76 +i77 +i78 +i79 +i80 +i81 +i82 +i83 +i84 +i85 +i86 +i87 +i88 - // +i89 +i90 +i91 +i92 +i93 +i94 +i95 +i96 +i97 +i98 +i99; - } + void test() { + int i0 = 163; + int i1 = 153; + int i2 = 75; + int i3 = -72; + int i4 = 61; + int i5 = 7; + int i6 = 83; + int i7 = -36; + int i8 = -90; + int i9 = -93; + int i10 = 187; + int i11 = -76; + int i12 = -16; + int i13 = -99; + int i14 = 113; + int i15 = 72; + int i16 = 58; + int i17 = -97; + int i18 = 115; + int i19 = -85; + int i20 = 156; + int i21 = -10; + int i22 = -85; + int i23 = 81; + int i24 = 63; + int i25 = -49; + int i26 = 158; + int i27 = 158; + int i28 = 25; + int i29 = 136; + int i30 = -90; + int i31 = 115; + int i32 = 179; + int i33 = 11; + int i34 = -100; + int i35 = 70; + int i36 = -46; + int i37 = -56; + int i38 = 108; + int i39 = -41; + int i40 = 124; + int i41 = -88; + int i42 = 54; + int i43 = 117; + int i44 = -92; + int i45 = 7; + int i46 = -94; + int i47 = 162; + int i48 = -34; + int i49 = 104; + int i50 = 111; + int i51 = -16; + int i52 = 197; + int i53 = -8; + int i54 = 101; + int i55 = 96; + int i56 = 132; + int i57 = -36; + int i58 = 148; + int i59 = 43; + int i60 = -59; + int i61 = 150; + int i62 = 48; + int i63 = 130; + int i64 = 74; + int i65 = -1; + int i66 = 79; + int i67 = 109; + int i68 = -70; + int i69 = 111; + int i70 = 78; + int i71 = 155; + int i72 = 176; + int i73 = 80; + int i74 = 181; + int i75 = 41; + int i76 = -85; + int i77 = 189; + int i78 = 97; + int i79 = 139; + int i80 = 9; + int i81 = 42; + int i82 = -50; + int i83 = 82; + int i84 = -70; + int i85 = 162; + int i86 = -20; + int i87 = 52; + int i88 = -94; + int i89 = 133; + int i90 = 136; + int i91 = 129; + int i92 = -55; + int i93 = 153; + int i94 = 6; + int i95 = -18; + int i96 = 132; + int i97 = 45; + int i98 = 120; + int i99 = 60; + // int result = i0 +i1 +i2 +i3 +i4 +i5 +i6 +i7 +i8 +i9 +i10 +i11 +i12 +i13 +i14 +i15 +i16 + // +i17 +i18 +i19 +i20 +i21 +i22 +i23 +i24 +i25 +i26 +i27 +i28 +i29 +i30 +i31 +i32 +i33 +i34 + // +i35 +i36 +i37 +i38 +i39 +i40 +i41 +i42 +i43 +i44 +i45 +i46 +i47 +i48 +i49 +i50 +i51 +i52 + // +i53 +i54 +i55 +i56 +i57 +i58 +i59 +i60 +i61 +i62 +i63 +i64 +i65 +i66 +i67 +i68 +i69 +i70 + // +i71 +i72 +i73 +i74 +i75 +i76 +i77 +i78 +i79 +i80 +i81 +i82 +i83 +i84 +i85 +i86 +i87 +i88 + // +i89 +i90 +i91 +i92 +i93 +i94 +i95 +i96 +i97 +i98 +i99; + } } // a comment diff --git a/checker/tests/index/BinarySearchTest.java b/checker/tests/index/BinarySearchTest.java index df6ea8bab09..267901e5b60 100644 --- a/checker/tests/index/BinarySearchTest.java +++ b/checker/tests/index/BinarySearchTest.java @@ -1,40 +1,42 @@ -import java.util.Arrays; import org.checkerframework.checker.index.qual.*; -public class BinarySearchTest { +import java.util.Arrays; - private final long @SameLen("iNameKeys") [] iTransitions; - private final String @SameLen("iTransitions") [] iNameKeys; +public class BinarySearchTest { - private BinarySearchTest( - long @SameLen("iNameKeys") [] transitions, String @SameLen("iTransitions") [] nameKeys) { - iTransitions = transitions; - iNameKeys = nameKeys; - } + private final long @SameLen("iNameKeys") [] iTransitions; + private final String @SameLen("iTransitions") [] iNameKeys; - public String getNameKey(long instant) { - long[] transitions = iTransitions; - int i = Arrays.binarySearch(transitions, instant); - if (i >= 0) { - return iNameKeys[i]; - } - i = ~i; - if (i > 0) { - return iNameKeys[i - 1]; + private BinarySearchTest( + long @SameLen("iNameKeys") [] transitions, + String @SameLen("iTransitions") [] nameKeys) { + iTransitions = transitions; + iNameKeys = nameKeys; } - return ""; - } - public String getNameKey2(long instant) { - long[] transitions = iTransitions; - int i = Arrays.binarySearch(transitions, instant); - if (i >= 0) { - return iNameKeys[i]; + public String getNameKey(long instant) { + long[] transitions = iTransitions; + int i = Arrays.binarySearch(transitions, instant); + if (i >= 0) { + return iNameKeys[i]; + } + i = ~i; + if (i > 0) { + return iNameKeys[i - 1]; + } + return ""; } - i = ~i; - if (i < iNameKeys.length) { - return iNameKeys[i]; + + public String getNameKey2(long instant) { + long[] transitions = iTransitions; + int i = Arrays.binarySearch(transitions, instant); + if (i >= 0) { + return iNameKeys[i]; + } + i = ~i; + if (i < iNameKeys.length) { + return iNameKeys[i]; + } + return ""; } - return ""; - } } diff --git a/checker/tests/index/BinomialTest.java b/checker/tests/index/BinomialTest.java index dc2f72e4ef2..33396da60cc 100644 --- a/checker/tests/index/BinomialTest.java +++ b/checker/tests/index/BinomialTest.java @@ -3,71 +3,71 @@ public class BinomialTest { - static final long @MinLen(1) [] factorials = {1L, 1L, 1L * 2}; + static final long @MinLen(1) [] factorials = {1L, 1L, 1L * 2}; - public static long binomial( - @NonNegative @LTLengthOf("BinomialTest.factorials") int n, - @NonNegative @LessThan("#1 + 1") int k) { - return factorials[k]; - } + public static long binomial( + @NonNegative @LTLengthOf("BinomialTest.factorials") int n, + @NonNegative @LessThan("#1 + 1") int k) { + return factorials[k]; + } - public static void binomial0( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { - @LTLengthOf(value = "factorials", offset = "1") int i = k; - } + public static void binomial0( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { + @LTLengthOf(value = "factorials", offset = "1") int i = k; + } - public static void binomial0Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "2") int i = k; - } + public static void binomial0Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "2") int i = k; + } - public static void binomial0Weak( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { - @LTLengthOf("factorials") int i = k; - } + public static void binomial0Weak( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1") int k) { + @LTLengthOf("factorials") int i = k; + } - public static void binomial1( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { - @LTLengthOf("factorials") int i = k; - } + public static void binomial1( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { + @LTLengthOf("factorials") int i = k; + } - public static void binomial1Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "1") int i = k; - } + public static void binomial1Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 1") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "1") int i = k; + } - public static void binomial2( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { - @LTLengthOf(value = "factorials", offset = "-1") int i = k; - } + public static void binomial2( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { + @LTLengthOf(value = "factorials", offset = "-1") int i = k; + } - public static void binomial2Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "0") int i = k; - } + public static void binomial2Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 + 2") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "0") int i = k; + } - public static void binomial_1( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { - @LTLengthOf(value = "factorials", offset = "2") int i = k; - } + public static void binomial_1( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { + @LTLengthOf(value = "factorials", offset = "2") int i = k; + } - public static void binomial_1Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "3") int i = k; - } + public static void binomial_1Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 1") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "3") int i = k; + } - public static void binomial_2( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { - @LTLengthOf(value = "factorials", offset = "3") int i = k; - } + public static void binomial_2( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { + @LTLengthOf(value = "factorials", offset = "3") int i = k; + } - public static void binomial_2Error( - @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "factorials", offset = "4") int i = k; - } + public static void binomial_2Error( + @LTLengthOf("BinomialTest.factorials") int n, @LessThan("#1 - 2") int k) { + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "factorials", offset = "4") int i = k; + } } diff --git a/checker/tests/index/BitSetLowerBound.java b/checker/tests/index/BitSetLowerBound.java index a95160e901a..c08e56fc58a 100644 --- a/checker/tests/index/BitSetLowerBound.java +++ b/checker/tests/index/BitSetLowerBound.java @@ -1,19 +1,20 @@ // Test case for Issue 185: // https://github.com/typetools/kelloggm/issues/185 -import java.util.BitSet; import org.checkerframework.checker.index.qual.GTENegativeOne; +import java.util.BitSet; + public class BitSetLowerBound { - private void m(BitSet b) { - b.set(b.nextClearBit(0)); - // next set bit does not have to exist - // :: error: (argument.type.incompatible) - b.clear(b.nextSetBit(0)); - @GTENegativeOne int i = b.nextSetBit(0); + private void m(BitSet b) { + b.set(b.nextClearBit(0)); + // next set bit does not have to exist + // :: error: (argument.type.incompatible) + b.clear(b.nextSetBit(0)); + @GTENegativeOne int i = b.nextSetBit(0); - @GTENegativeOne int j = b.previousClearBit(-1); - @GTENegativeOne int k = b.previousSetBit(-1); - } + @GTENegativeOne int j = b.previousClearBit(-1); + @GTENegativeOne int k = b.previousSetBit(-1); + } } diff --git a/checker/tests/index/Boilerplate.java b/checker/tests/index/Boilerplate.java index e4a84fc7cc9..1acdaad6fcc 100644 --- a/checker/tests/index/Boilerplate.java +++ b/checker/tests/index/Boilerplate.java @@ -2,9 +2,9 @@ public class Boilerplate { - void test() { - // :: error: (assignment.type.incompatible) - @Positive int a = -1; - } + void test() { + // :: error: (assignment.type.incompatible) + @Positive int a = -1; + } } // a comment diff --git a/checker/tests/index/BottomValTest.java b/checker/tests/index/BottomValTest.java index 213ddcef6b5..51bbd36a9f2 100644 --- a/checker/tests/index/BottomValTest.java +++ b/checker/tests/index/BottomValTest.java @@ -2,15 +2,15 @@ import org.checkerframework.common.value.qual.*; public class BottomValTest { - @NonNegative int foo(@BottomVal int bottom) { - return bottom; - } + @NonNegative int foo(@BottomVal int bottom) { + return bottom; + } - @Positive int bar(@BottomVal int bottom) { - return bottom; - } + @Positive int bar(@BottomVal int bottom) { + return bottom; + } - @LTLengthOf("#1") int baz(int[] a, @BottomVal int bottom) { - return bottom; - } + @LTLengthOf("#1") int baz(int[] a, @BottomVal int bottom) { + return bottom; + } } diff --git a/checker/tests/index/CastArray.java b/checker/tests/index/CastArray.java index c6c779774ae..0ffb750b222 100644 --- a/checker/tests/index/CastArray.java +++ b/checker/tests/index/CastArray.java @@ -1,5 +1,5 @@ public class CastArray { - void test(Object a) { - int[] b = (int[]) a; - } + void test(Object a) { + int[] b = (int[]) a; + } } diff --git a/checker/tests/index/CharPrintedAsVariable.java b/checker/tests/index/CharPrintedAsVariable.java index a143eae338c..b3eea0fd25d 100644 --- a/checker/tests/index/CharPrintedAsVariable.java +++ b/checker/tests/index/CharPrintedAsVariable.java @@ -1,15 +1,15 @@ // Test case for https://github.com/typetools/checker-framework/issues/3167 . public class CharPrintedAsVariable { - void m1(char c) { - if (c <= 'A') { - int x = (int) c; + void m1(char c) { + if (c <= 'A') { + int x = (int) c; + } } - } - void m2(char c) { - if (c <= '\377') { - int x = (int) c; + void m2(char c) { + if (c <= '\377') { + int x = (int) c; + } } - } } diff --git a/checker/tests/index/CharSequenceTest.java b/checker/tests/index/CharSequenceTest.java index 17a5254f58d..d741b721c13 100644 --- a/checker/tests/index/CharSequenceTest.java +++ b/checker/tests/index/CharSequenceTest.java @@ -1,81 +1,82 @@ // Tests suport for index annotations applied to CharSequence and related indices. -import java.io.IOException; -import java.io.StringWriter; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.common.value.qual.MinLen; import org.checkerframework.common.value.qual.StringVal; -public class CharSequenceTest { - // Tests that minlen is correctly applied to CharSequence assigned from String, but not - // StringBuilder - void minLenCharSequence() { - @MinLen(10) CharSequence str = "0123456789"; - // :: error: (assignment.type.incompatible) - @MinLen(10) CharSequence sb = new StringBuilder("0123456789"); - } +import java.io.IOException; +import java.io.StringWriter; - // Tests the subSequence method - void testSubSequence() { - // Local variable used because of https://github.com/kelloggm/checker-framework/issues/165 - String str = "0123456789"; - str.subSequence(5, 8); - // :: error: (argument.type.incompatible) - str.subSequence(5, 13); - } +public class CharSequenceTest { + // Tests that minlen is correctly applied to CharSequence assigned from String, but not + // StringBuilder + void minLenCharSequence() { + @MinLen(10) CharSequence str = "0123456789"; + // :: error: (assignment.type.incompatible) + @MinLen(10) CharSequence sb = new StringBuilder("0123456789"); + } - // Dummy method that takes a CharSequence and its index - void sink(CharSequence cs, @IndexOrHigh("#1") int i) {} + // Tests the subSequence method + void testSubSequence() { + // Local variable used because of https://github.com/kelloggm/checker-framework/issues/165 + String str = "0123456789"; + str.subSequence(5, 8); + // :: error: (argument.type.incompatible) + str.subSequence(5, 13); + } - // Tests passing sequences as CharSequence - void argumentPassing() { - String s = "0123456789"; - sink(s, 8); - StringBuilder sb = new StringBuilder("0123456789"); - // :: error: (argument.type.incompatible) - sink(sb, 8); - } + // Dummy method that takes a CharSequence and its index + void sink(CharSequence cs, @IndexOrHigh("#1") int i) {} - // Tests forwardning sequences as CharSequence - void agumentForwarding(String s, @IndexOrHigh("#1") int i) { - sink(s, i); - } + // Tests passing sequences as CharSequence + void argumentPassing() { + String s = "0123456789"; + sink(s, 8); + StringBuilder sb = new StringBuilder("0123456789"); + // :: error: (argument.type.incompatible) + sink(sb, 8); + } - // Tests concatenation of CharSequence and String - void concat() { - CharSequence a = "a"; - @StringVal({"nullb", "ab"}) CharSequence ab = a + "b"; - sink(ab, 2); - } + // Tests forwardning sequences as CharSequence + void agumentForwarding(String s, @IndexOrHigh("#1") int i) { + sink(s, i); + } - // Tests that length retrieved from CharSequence can be used as an index - void getLength(CharSequence cs, int i) { - if (i >= 0 && i < cs.length()) { - cs.charAt(i); + // Tests concatenation of CharSequence and String + void concat() { + CharSequence a = "a"; + @StringVal({"nullb", "ab"}) CharSequence ab = a + "b"; + sink(ab, 2); } - @IndexOrHigh("cs") int l = cs.length(); - } + // Tests that length retrieved from CharSequence can be used as an index + void getLength(CharSequence cs, int i) { + if (i >= 0 && i < cs.length()) { + cs.charAt(i); + } - void testCharAt(CharSequence cs, int i, @IndexFor("#1") int j) { - cs.charAt(j); - cs.subSequence(j, j); - // :: error: (argument.type.incompatible) - cs.charAt(i); - // :: error: (argument.type.incompatible) - cs.subSequence(i, j); - } + @IndexOrHigh("cs") int l = cs.length(); + } - void testAppend(Appendable app, CharSequence cs, @IndexFor("#2") int i) throws IOException { - app.append(cs, i, i); - // :: error: (argument.type.incompatible) - app.append(cs, 1, 2); - } + void testCharAt(CharSequence cs, int i, @IndexFor("#1") int j) { + cs.charAt(j); + cs.subSequence(j, j); + // :: error: (argument.type.incompatible) + cs.charAt(i); + // :: error: (argument.type.incompatible) + cs.subSequence(i, j); + } - void testAppend(StringWriter app, CharSequence cs, @IndexFor("#2") int i) throws IOException { - app.append(cs, i, i); - // :: error: (argument.type.incompatible) - app.append(cs, 1, 2); - } + void testAppend(Appendable app, CharSequence cs, @IndexFor("#2") int i) throws IOException { + app.append(cs, i, i); + // :: error: (argument.type.incompatible) + app.append(cs, 1, 2); + } + + void testAppend(StringWriter app, CharSequence cs, @IndexFor("#2") int i) throws IOException { + app.append(cs, i, i); + // :: error: (argument.type.incompatible) + app.append(cs, 1, 2); + } } diff --git a/checker/tests/index/CharToIntCast.java b/checker/tests/index/CharToIntCast.java index 1344b43b571..61359040de3 100644 --- a/checker/tests/index/CharToIntCast.java +++ b/checker/tests/index/CharToIntCast.java @@ -4,15 +4,15 @@ public class CharToIntCast { - public static void charRange(char c) { - @IntRange(from = 0, to = Character.MAX_VALUE) int i = c; - } + public static void charRange(char c) { + @IntRange(from = 0, to = Character.MAX_VALUE) int i = c; + } - public static void charShift(char c) { - char c2 = (char) (c >> 4); - } + public static void charShift(char c) { + char c2 = (char) (c >> 4); + } - public static void rangeShiftOk(@IntRange(from = 0, to = Character.MAX_VALUE) int i) { - char c2 = (char) (i >> 4); - } + public static void rangeShiftOk(@IntRange(from = 0, to = Character.MAX_VALUE) int i) { + char c2 = (char) (i >> 4); + } } diff --git a/checker/tests/index/CheckAgainstNegativeOne.java b/checker/tests/index/CheckAgainstNegativeOne.java index 211e9221546..6c61942bfa0 100644 --- a/checker/tests/index/CheckAgainstNegativeOne.java +++ b/checker/tests/index/CheckAgainstNegativeOne.java @@ -2,20 +2,20 @@ public class CheckAgainstNegativeOne { - public static String replaceString(String target, String oldStr, String newStr) { - if (oldStr.equals("")) { - throw new IllegalArgumentException(); - } + public static String replaceString(String target, String oldStr, String newStr) { + if (oldStr.equals("")) { + throw new IllegalArgumentException(); + } - StringBuffer result = new StringBuffer(); - @IndexOrHigh("target") int lastend = 0; - int pos; - while ((pos = target.indexOf(oldStr, lastend)) != -1) { - result.append(target.substring(lastend, pos)); - result.append(newStr); - lastend = pos + oldStr.length(); + StringBuffer result = new StringBuffer(); + @IndexOrHigh("target") int lastend = 0; + int pos; + while ((pos = target.indexOf(oldStr, lastend)) != -1) { + result.append(target.substring(lastend, pos)); + result.append(newStr); + lastend = pos + oldStr.length(); + } + result.append(target.substring(lastend)); + return result.toString(); } - result.append(target.substring(lastend)); - return result.toString(); - } } diff --git a/checker/tests/index/CheckNotNull1.java b/checker/tests/index/CheckNotNull1.java index dcc18547290..afd2b7c418e 100644 --- a/checker/tests/index/CheckNotNull1.java +++ b/checker/tests/index/CheckNotNull1.java @@ -1,9 +1,9 @@ public class CheckNotNull1 { - T checkNotNull(T ref) { - return ref; - } + T checkNotNull(T ref) { + return ref; + } - void test(S ref) { - checkNotNull(ref); - } + void test(S ref) { + checkNotNull(ref); + } } diff --git a/checker/tests/index/CheckNotNull2.java b/checker/tests/index/CheckNotNull2.java index 03047e6bf69..8304f277582 100644 --- a/checker/tests/index/CheckNotNull2.java +++ b/checker/tests/index/CheckNotNull2.java @@ -1,9 +1,9 @@ public class CheckNotNull2 { - T checkNotNull(T ref) { - return ref; - } + T checkNotNull(T ref) { + return ref; + } - void test(T ref) { - checkNotNull(ref); - } + void test(T ref) { + checkNotNull(ref); + } } diff --git a/checker/tests/index/CombineFacts.java b/checker/tests/index/CombineFacts.java index 817eecb4386..70a88ccc56e 100644 --- a/checker/tests/index/CombineFacts.java +++ b/checker/tests/index/CombineFacts.java @@ -2,14 +2,14 @@ @SuppressWarnings("lowerbound") public class CombineFacts { - void test(int[] a1) { - @LTLengthOf("a1") int len = a1.length - 1; - int[] a2 = new int[len]; - a2[len - 1] = 1; - a1[len] = 1; + void test(int[] a1) { + @LTLengthOf("a1") int len = a1.length - 1; + int[] a2 = new int[len]; + a2[len - 1] = 1; + a1[len] = 1; - // This access should issue an error. - // :: error: (array.access.unsafe.high) - a2[len] = 1; - } + // This access should issue an error. + // :: error: (array.access.unsafe.high) + a2[len] = 1; + } } diff --git a/checker/tests/index/CompareBySubtraction.java b/checker/tests/index/CompareBySubtraction.java index 8ac834cccb8..20912bad226 100644 --- a/checker/tests/index/CompareBySubtraction.java +++ b/checker/tests/index/CompareBySubtraction.java @@ -1,20 +1,20 @@ // @skip-test until fixed. public class CompareBySubtraction { - public int compare(int[] a1, int[] a2) { - if (a1 == a2) { - return 0; + public int compare(int[] a1, int[] a2) { + if (a1 == a2) { + return 0; + } + int tmp; + tmp = a1.length - a2.length; + if (tmp != 0) { + return tmp; + } + for (int i = 0; i < a1.length; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); + } + } + return 0; } - int tmp; - tmp = a1.length - a2.length; - if (tmp != 0) { - return tmp; - } - for (int i = 0; i < a1.length; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); - } - } - return 0; - } } diff --git a/checker/tests/index/CompoundAssignmentCheck.java b/checker/tests/index/CompoundAssignmentCheck.java index 65f577b1113..7518d9c4668 100644 --- a/checker/tests/index/CompoundAssignmentCheck.java +++ b/checker/tests/index/CompoundAssignmentCheck.java @@ -1,8 +1,8 @@ public class CompoundAssignmentCheck { - void test() { - int a = 9; - a += 5; - a -= 2; - int[] arr5 = new int[a]; // LBC shouldn't warn here. - } + void test() { + int a = 9; + a += 5; + a -= 2; + int[] arr5 = new int[a]; // LBC shouldn't warn here. + } } diff --git a/checker/tests/index/ComputeConst.java b/checker/tests/index/ComputeConst.java index 2616ddbc8da..851dcdc53dc 100644 --- a/checker/tests/index/ComputeConst.java +++ b/checker/tests/index/ComputeConst.java @@ -1,11 +1,11 @@ public class ComputeConst { - public static int hash(long l) { - // If possible, use the value itself. - if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { - return (int) l; - } + public static int hash(long l) { + // If possible, use the value itself. + if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { + return (int) l; + } - return Long.hashCode(l); - } + return Long.hashCode(l); + } } diff --git a/checker/tests/index/ConditionalIndex.java b/checker/tests/index/ConditionalIndex.java index 1ee3ec1d318..cace653309a 100644 --- a/checker/tests/index/ConditionalIndex.java +++ b/checker/tests/index/ConditionalIndex.java @@ -1,15 +1,15 @@ // test case for issue 162: https://github.com/kelloggm/checker-framework/issues/162 public class ConditionalIndex { - public void f(boolean cond) { - int[] a = new int[10]; - int[] b = new int[1]; - if (cond) { - int[] c = a; - } else { - int[] c = b; - } + public void f(boolean cond) { + int[] a = new int[10]; + int[] b = new int[1]; + if (cond) { + int[] c = a; + } else { + int[] c = b; + } - int[] d = (cond ? a : b); - } + int[] d = (cond ? a : b); + } } diff --git a/checker/tests/index/ConstantArrays.java b/checker/tests/index/ConstantArrays.java index b044098891f..c6a56da5ca7 100644 --- a/checker/tests/index/ConstantArrays.java +++ b/checker/tests/index/ConstantArrays.java @@ -1,33 +1,33 @@ import org.checkerframework.checker.index.qual.*; public class ConstantArrays { - void basic_test() { - int[] b = new int[4]; - @LTLengthOf("b") int[] a = {0, 1, 2, 3}; + void basic_test() { + int[] b = new int[4]; + @LTLengthOf("b") int[] a = {0, 1, 2, 3}; - // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) - @LTLengthOf("b") int[] a1 = {0, 1, 2, 4}; + // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) + @LTLengthOf("b") int[] a1 = {0, 1, 2, 4}; - @LTEqLengthOf("b") int[] c = {-1, 4, 3, 1}; + @LTEqLengthOf("b") int[] c = {-1, 4, 3, 1}; - // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) - @LTEqLengthOf("b") int[] c2 = {-1, 4, 5, 1}; - } + // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) + @LTEqLengthOf("b") int[] c2 = {-1, 4, 5, 1}; + } - void offset_test() { - int[] b = new int[4]; - int[] b2 = new int[10]; - @LTLengthOf( - value = {"b", "b2"}, - offset = {"-2", "5"}) - int[] a = {2, 3, 0}; + void offset_test() { + int[] b = new int[4]; + int[] b2 = new int[10]; + @LTLengthOf( + value = {"b", "b2"}, + offset = {"-2", "5"}) + int[] a = {2, 3, 0}; - @LTLengthOf( - value = {"b", "b2"}, - offset = {"-2", "5"}) - // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) - int[] a2 = {2, 3, 5}; + @LTLengthOf( + value = {"b", "b2"}, + offset = {"-2", "5"}) + // :: error: (array.initializer.type.incompatible)::error: (assignment.type.incompatible) + int[] a2 = {2, 3, 5}; - // Non-constant offsets don't work correctly. See kelloggm#120. - } + // Non-constant offsets don't work correctly. See kelloggm#120. + } } diff --git a/checker/tests/index/ConstantOffsets.java b/checker/tests/index/ConstantOffsets.java index bd5cccfb8b8..d821704aab0 100644 --- a/checker/tests/index/ConstantOffsets.java +++ b/checker/tests/index/ConstantOffsets.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class ConstantOffsets { - void method1(int[] a, int offset, @LTLengthOf(value = "#1", offset = "-#2 - 1") int x) {} + void method1(int[] a, int offset, @LTLengthOf(value = "#1", offset = "-#2 - 1") int x) {} - void test1() { - int offset = -4; - int x = 4; - int[] f1 = new int[x - offset]; - method1(f1, offset, x); - } + void test1() { + int offset = -4; + int x = 4; + int[] f1 = new int[x - offset]; + method1(f1, offset, x); + } - void method2(int[] a, int offset, @LTLengthOf(value = "#1", offset = "#2 - 1") int x) {} + void method2(int[] a, int offset, @LTLengthOf(value = "#1", offset = "#2 - 1") int x) {} - void test2() { - int offset = 4; - int x = 4; - int[] f1 = new int[x + offset]; - method2(f1, offset, x); - } + void test2() { + int offset = 4; + int x = 4; + int[] f1 = new int[x + offset]; + method2(f1, offset, x); + } } diff --git a/checker/tests/index/ConstantsIndex.java b/checker/tests/index/ConstantsIndex.java index 22871601e5c..672b15332b6 100644 --- a/checker/tests/index/ConstantsIndex.java +++ b/checker/tests/index/ConstantsIndex.java @@ -2,10 +2,10 @@ public class ConstantsIndex { - void test() { - int @MinLen(3) [] arr = {1, 2, 3}; - int i = arr[1]; - // :: error: (array.access.unsafe.high.constant) - int j = arr[3]; - } + void test() { + int @MinLen(3) [] arr = {1, 2, 3}; + int i = arr[1]; + // :: error: (array.access.unsafe.high.constant) + int j = arr[3]; + } } diff --git a/checker/tests/index/CustomContractWithArgs.java b/checker/tests/index/CustomContractWithArgs.java index 902f579822a..1c12bd5b255 100644 --- a/checker/tests/index/CustomContractWithArgs.java +++ b/checker/tests/index/CustomContractWithArgs.java @@ -7,155 +7,155 @@ import org.checkerframework.framework.qual.QualifierArgument; public class CustomContractWithArgs { - // Postcondition for MinLen - @PostconditionAnnotation(qualifier = MinLen.class) - @interface EnsuresMinLen { - public String[] value(); - - @QualifierArgument("value") - public int targetValue(); - } - - // Conditional postcondition for LTLengthOf - @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) - @interface EnsuresLTLIf { - public boolean result(); - - public String[] expression(); - - @JavaExpression - @QualifierArgument("value") - public String[] targetValue(); - - @JavaExpression - @QualifierArgument("offset") - public String[] targetOffset(); - } - - // Precondition for LTLengthOf - @PreconditionAnnotation(qualifier = LTLengthOf.class) - @interface RequiresLTL { - public String[] value(); - - @JavaExpression - @QualifierArgument("value") - public String[] targetValue(); - - @JavaExpression - @QualifierArgument("offset") - public String[] targetOffset(); - } - - class Base { - @EnsuresMinLen(value = "#1", targetValue = 10) - void minLenContract(int[] a) { - if (a.length < 10) throw new RuntimeException(); - } + // Postcondition for MinLen + @PostconditionAnnotation(qualifier = MinLen.class) + @interface EnsuresMinLen { + public String[] value(); - @EnsuresMinLen(value = "#1", targetValue = 10) - // :: error: (contracts.postcondition.not.satisfied) - void minLenWrong(int[] a) { - if (a.length < 9) throw new RuntimeException(); + @QualifierArgument("value") + public int targetValue(); } - void minLenUse(int[] b) { - minLenContract(b); - int @MinLen(10) [] c = b; - } + // Conditional postcondition for LTLengthOf + @ConditionalPostconditionAnnotation(qualifier = LTLengthOf.class) + @interface EnsuresLTLIf { + public boolean result(); - public int b, y; - - @EnsuresLTLIf( - expression = "b", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "10"}, - result = true) - boolean ltlPost(int[] a, int c) { - if (b < a.length - c - 1 && b < a.length - 10) { - return true; - } else { - return false; - } - } + public String[] expression(); - @EnsuresLTLIf(expression = "b", targetValue = "#1", targetOffset = "#3", result = true) - // :: error: (flowexpr.parse.error) - boolean ltlPostInvalid(int[] a, int c) { - return false; - } + @JavaExpression + @QualifierArgument("value") + public String[] targetValue(); - @RequiresLTL( - value = "b", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "-10"}) - void ltlPre(int[] a, int c) { - @LTLengthOf(value = "a", offset = "c+1") int i = b; + @JavaExpression + @QualifierArgument("offset") + public String[] targetOffset(); } - void ltlUse(int[] a, int c) { - if (ltlPost(a, c)) { - @LTLengthOf(value = "a", offset = "c+1") int i = b; + // Precondition for LTLengthOf + @PreconditionAnnotation(qualifier = LTLengthOf.class) + @interface RequiresLTL { + public String[] value(); - ltlPre(a, c); - } - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "a", offset = "c+1") int j = b; - } - } - - class Derived extends Base { - public int x; - - @Override - @EnsuresLTLIf( - expression = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "11"}, - result = true) - boolean ltlPost(int[] a, int d) { - return false; + @JavaExpression + @QualifierArgument("value") + public String[] targetValue(); + + @JavaExpression + @QualifierArgument("offset") + public String[] targetOffset(); } - @Override - @RequiresLTL( - value = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "-11"}) - void ltlPre(int[] a, int d) { - @LTLengthOf( - value = {"a", "a"}, - offset = {"d+1", "-10"}) - // :: error: (assignment.type.incompatible) - int i = b; + class Base { + @EnsuresMinLen(value = "#1", targetValue = 10) + void minLenContract(int[] a) { + if (a.length < 10) throw new RuntimeException(); + } + + @EnsuresMinLen(value = "#1", targetValue = 10) + // :: error: (contracts.postcondition.not.satisfied) + void minLenWrong(int[] a) { + if (a.length < 9) throw new RuntimeException(); + } + + void minLenUse(int[] b) { + minLenContract(b); + int @MinLen(10) [] c = b; + } + + public int b, y; + + @EnsuresLTLIf( + expression = "b", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "10"}, + result = true) + boolean ltlPost(int[] a, int c) { + if (b < a.length - c - 1 && b < a.length - 10) { + return true; + } else { + return false; + } + } + + @EnsuresLTLIf(expression = "b", targetValue = "#1", targetOffset = "#3", result = true) + // :: error: (flowexpr.parse.error) + boolean ltlPostInvalid(int[] a, int c) { + return false; + } + + @RequiresLTL( + value = "b", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "-10"}) + void ltlPre(int[] a, int c) { + @LTLengthOf(value = "a", offset = "c+1") int i = b; + } + + void ltlUse(int[] a, int c) { + if (ltlPost(a, c)) { + @LTLengthOf(value = "a", offset = "c+1") int i = b; + + ltlPre(a, c); + } + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "a", offset = "c+1") int j = b; + } } - } - - class DerivedInvalid extends Base { - public int x; - - @Override - @EnsuresLTLIf( - expression = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "9"}, - result = true) - // :: error: (contracts.conditional.postcondition.true.override.invalid) - boolean ltlPost(int[] a, int c) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; + + class Derived extends Base { + public int x; + + @Override + @EnsuresLTLIf( + expression = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "11"}, + result = true) + boolean ltlPost(int[] a, int d) { + return false; + } + + @Override + @RequiresLTL( + value = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "-11"}) + void ltlPre(int[] a, int d) { + @LTLengthOf( + value = {"a", "a"}, + offset = {"d+1", "-10"}) + // :: error: (assignment.type.incompatible) + int i = b; + } } - @Override - @RequiresLTL( - value = "b ", - targetValue = {"#1", "#1"}, - targetOffset = {"#2 + 1", "-9"}) - // :: error: (contracts.precondition.override.invalid) - void ltlPre(int[] a, int d) { - @LTLengthOf( - value = {"a", "a"}, - offset = {"d+1", "-10"}) - int i = b; + class DerivedInvalid extends Base { + public int x; + + @Override + @EnsuresLTLIf( + expression = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "9"}, + result = true) + // :: error: (contracts.conditional.postcondition.true.override.invalid) + boolean ltlPost(int[] a, int c) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @Override + @RequiresLTL( + value = "b ", + targetValue = {"#1", "#1"}, + targetOffset = {"#2 + 1", "-9"}) + // :: error: (contracts.precondition.override.invalid) + void ltlPre(int[] a, int d) { + @LTLengthOf( + value = {"a", "a"}, + offset = {"d+1", "-10"}) + int i = b; + } } - } } diff --git a/checker/tests/index/DaikonCrash.java b/checker/tests/index/DaikonCrash.java index 4a4beb8acf2..45bf79754ea 100644 --- a/checker/tests/index/DaikonCrash.java +++ b/checker/tests/index/DaikonCrash.java @@ -1,14 +1,15 @@ -import java.util.Arrays; import org.checkerframework.dataflow.qual.Pure; +import java.util.Arrays; + public class DaikonCrash { - void method(Object[] a1) { - int[] u = union(new int[] {}, new int[] {}); - Arrays.sort(u); - } + void method(Object[] a1) { + int[] u = union(new int[] {}, new int[] {}); + Arrays.sort(u); + } - @Pure - private int[] union(int[] ints, int[] ints1) { - throw new RuntimeException(); - } + @Pure + private int[] union(int[] ints, int[] ints1) { + throw new RuntimeException(); + } } diff --git a/checker/tests/index/DefaultingForEach.java b/checker/tests/index/DefaultingForEach.java index 64f348ed8c2..5cbe49fddc3 100644 --- a/checker/tests/index/DefaultingForEach.java +++ b/checker/tests/index/DefaultingForEach.java @@ -7,16 +7,16 @@ class DefaultForEach { - @DefaultQualifier(NonNegative.class) - static int[] foo() { - throw new RuntimeException(); - } + @DefaultQualifier(NonNegative.class) + static int[] foo() { + throw new RuntimeException(); + } - void bar() { - for (Integer p : foo()) { - // :: error: assignment.type.incompatible - @Positive int x = p; - @NonNegative int y = p; + void bar() { + for (Integer p : foo()) { + // :: error: assignment.type.incompatible + @Positive int x = p; + @NonNegative int y = p; + } } - } } diff --git a/checker/tests/index/Dimension.java b/checker/tests/index/Dimension.java index b2ab086e61e..256da19eb11 100644 --- a/checker/tests/index/Dimension.java +++ b/checker/tests/index/Dimension.java @@ -1,18 +1,18 @@ @SuppressWarnings("lowerbound") public class Dimension { - void test(int expr) { - int[] array = new int[expr]; - // :: error: (array.access.unsafe.high) - array[expr] = 0; - array[expr - 1] = 0; - } + void test(int expr) { + int[] array = new int[expr]; + // :: error: (array.access.unsafe.high) + array[expr] = 0; + array[expr - 1] = 0; + } - String[] arrayField = new String[1]; + String[] arrayField = new String[1]; - void test2(int expr) { - arrayField = new String[expr]; - // :: error: (array.access.unsafe.high) - this.arrayField[expr] = ""; - this.arrayField[expr - 1] = ""; - } + void test2(int expr) { + arrayField = new String[expr]; + // :: error: (array.access.unsafe.high) + this.arrayField[expr] = ""; + this.arrayField[expr - 1] = ""; + } } diff --git a/checker/tests/index/DivisionTest.java b/checker/tests/index/DivisionTest.java index 26003be29c7..3407f4eef93 100644 --- a/checker/tests/index/DivisionTest.java +++ b/checker/tests/index/DivisionTest.java @@ -2,7 +2,7 @@ public class DivisionTest { - public static void division() { - System.out.println(1 / (2.0)); - } + public static void division() { + System.out.println(1 / (2.0)); + } } diff --git a/checker/tests/index/EndsWith.java b/checker/tests/index/EndsWith.java index a41ff8bcc19..cf5c180430b 100644 --- a/checker/tests/index/EndsWith.java +++ b/checker/tests/index/EndsWith.java @@ -5,9 +5,9 @@ public class EndsWith { - void testEndsWith(String arg) { - if (arg.endsWith("[]")) { - @MinLen(2) String arg2 = arg; + void testEndsWith(String arg) { + if (arg.endsWith("[]")) { + @MinLen(2) String arg2 = arg; + } } - } } diff --git a/checker/tests/index/EndsWith2.java b/checker/tests/index/EndsWith2.java index 5034b5930d3..54a5c06afaa 100644 --- a/checker/tests/index/EndsWith2.java +++ b/checker/tests/index/EndsWith2.java @@ -2,16 +2,16 @@ public class EndsWith2 { - public static String invertBrackets(String classname) { + public static String invertBrackets(String classname) { - // Get the array depth (if any) - int array_depth = 0; - String brackets = ""; - while (classname.endsWith("[]")) { - brackets = brackets + classname.substring(classname.length() - 2); - classname = classname.substring(0, classname.length() - 2); - array_depth++; + // Get the array depth (if any) + int array_depth = 0; + String brackets = ""; + while (classname.endsWith("[]")) { + brackets = brackets + classname.substring(classname.length() - 2); + classname = classname.substring(0, classname.length() - 2); + array_depth++; + } + return brackets + classname; } - return brackets + classname; - } } diff --git a/checker/tests/index/EnumValues.java b/checker/tests/index/EnumValues.java index 7a23d1f2616..49452b3fae8 100644 --- a/checker/tests/index/EnumValues.java +++ b/checker/tests/index/EnumValues.java @@ -2,21 +2,21 @@ public class EnumValues { - public static enum Direction { - NORTH, - SOUTH, - EAST, - WEST - }; + public static enum Direction { + NORTH, + SOUTH, + EAST, + WEST + }; - public static void enumValues() { - Direction @ArrayLen(4) [] arr4 = Direction.values(); - Direction[] arr = Direction.values(); - Direction a = arr[0]; - Direction b = arr[1]; - Direction c = arr[2]; - Direction d = arr[3]; - // :: error: (array.access.unsafe.high.constant) - Direction e = arr[4]; - } + public static void enumValues() { + Direction @ArrayLen(4) [] arr4 = Direction.values(); + Direction[] arr = Direction.values(); + Direction a = arr[0]; + Direction b = arr[1]; + Direction c = arr[2]; + Direction d = arr[3]; + // :: error: (array.access.unsafe.high.constant) + Direction e = arr[4]; + } } diff --git a/checker/tests/index/EqualToIndex.java b/checker/tests/index/EqualToIndex.java index b9cd7f50300..33aae4b82b2 100644 --- a/checker/tests/index/EqualToIndex.java +++ b/checker/tests/index/EqualToIndex.java @@ -2,11 +2,11 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class EqualToIndex { - static int[] a = {0}; + static int[] a = {0}; - public static void equalToUpper(@LTLengthOf("a") int m, @LTEqLengthOf("a") int r) { - if (r == m) { - @LTLengthOf("a") int j = r; + public static void equalToUpper(@LTLengthOf("a") int m, @LTEqLengthOf("a") int r) { + if (r == m) { + @LTLengthOf("a") int j = r; + } } - } } diff --git a/checker/tests/index/EqualToTransfer.java b/checker/tests/index/EqualToTransfer.java index 37c642dd2fc..54d622b3219 100644 --- a/checker/tests/index/EqualToTransfer.java +++ b/checker/tests/index/EqualToTransfer.java @@ -1,19 +1,19 @@ import org.checkerframework.common.value.qual.MinLen; public class EqualToTransfer { - void eq_check(int[] a) { - if (1 == a.length) { - int @MinLen(1) [] b = a; + void eq_check(int[] a) { + if (1 == a.length) { + int @MinLen(1) [] b = a; + } + if (a.length == 1) { + int @MinLen(1) [] b = a; + } } - if (a.length == 1) { - int @MinLen(1) [] b = a; - } - } - void eq_bad_check(int[] a) { - if (1 == a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void eq_bad_check(int[] a) { + if (1 == a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/checker/tests/index/ErrorMessageCheck.java b/checker/tests/index/ErrorMessageCheck.java index 9c225942e49..c6a8a3d325d 100644 --- a/checker/tests/index/ErrorMessageCheck.java +++ b/checker/tests/index/ErrorMessageCheck.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.index.qual.NonNegative; public class ErrorMessageCheck { - @NonNegative int size; - int[] vDown = new int[size]; + @NonNegative int size; + int[] vDown = new int[size]; - void method3(@NonNegative int size, @NonNegative int value) { - this.size = size; - this.vDown = new int[this.size]; - // :: error: (array.access.unsafe.high) - vDown[1 + value] = 10; - } + void method3(@NonNegative int size, @NonNegative int value) { + this.size = size; + this.vDown = new int[this.size]; + // :: error: (array.access.unsafe.high) + vDown[1 + value] = 10; + } } diff --git a/checker/tests/index/Errors.java b/checker/tests/index/Errors.java index d68201cd9a9..0ad9b55e402 100644 --- a/checker/tests/index/Errors.java +++ b/checker/tests/index/Errors.java @@ -5,25 +5,25 @@ public class Errors { - void test() { - int[] arr = new int[5]; + void test() { + int[] arr = new int[5]; - // unsafe - @GTENegativeOne int n1p = -1; - @LowerBoundUnknown int u = -10; + // unsafe + @GTENegativeOne int n1p = -1; + @LowerBoundUnknown int u = -10; - // safe - @NonNegative int nn = 0; - @Positive int p = 1; + // safe + @NonNegative int nn = 0; + @Positive int p = 1; - // :: error: (array.access.unsafe.low) - int a = arr[n1p]; + // :: error: (array.access.unsafe.low) + int a = arr[n1p]; - // :: error: (array.access.unsafe.low) - int b = arr[u]; + // :: error: (array.access.unsafe.low) + int b = arr[u]; - int c = arr[nn]; - int d = arr[p]; - } + int c = arr[nn]; + int d = arr[p]; + } } // a comment diff --git a/checker/tests/index/ExampleUsage.java b/checker/tests/index/ExampleUsage.java index 005c1475fad..1f959249925 100644 --- a/checker/tests/index/ExampleUsage.java +++ b/checker/tests/index/ExampleUsage.java @@ -1,33 +1,33 @@ public class ExampleUsage { - /** - * this class contains a set of test methods that are supposed to show how the lowerbound checker - * should work in practice. They contain no annotations - the only test is whether or not it - * alarms on particular code constructs that are or are not safe - */ - void safe_loop_const() { - int[] arr = new int[5]; - int k; - for (int i = 0; i < 5; i++) { - k = arr[i]; + /** + * this class contains a set of test methods that are supposed to show how the lowerbound + * checker should work in practice. They contain no annotations - the only test is whether or + * not it alarms on particular code constructs that are or are not safe + */ + void safe_loop_const() { + int[] arr = new int[5]; + int k; + for (int i = 0; i < 5; i++) { + k = arr[i]; + } } - } - void safe_loop_spooky() { - int[] arr = new int[5]; - int k; - for (int i = -1; i < 4; ) { - i++; - k = arr[i]; + void safe_loop_spooky() { + int[] arr = new int[5]; + int k; + for (int i = -1; i < 4; ) { + i++; + k = arr[i]; + } } - } - void obviously_unsafe_loop() { - int[] arr = new int[5]; - int k; - for (int i = -1; i < 5; i++) { - // :: error: (array.access.unsafe.low) - k = arr[i]; + void obviously_unsafe_loop() { + int[] arr = new int[5]; + int k; + for (int i = -1; i < 5; i++) { + // :: error: (array.access.unsafe.low) + k = arr[i]; + } } - } } // a comment diff --git a/checker/tests/index/GenericAssignment.java b/checker/tests/index/GenericAssignment.java index 7d4f361387f..bfb406b3d37 100644 --- a/checker/tests/index/GenericAssignment.java +++ b/checker/tests/index/GenericAssignment.java @@ -1,28 +1,29 @@ -import java.util.List; import org.checkerframework.checker.index.qual.GTENegativeOne; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.value.qual.IntRange; +import java.util.List; + public class GenericAssignment { - public void assignNonNegativeList(List<@NonNegative Integer> l) { - List<@NonNegative Integer> i = l; // line 10 - } + public void assignNonNegativeList(List<@NonNegative Integer> l) { + List<@NonNegative Integer> i = l; // line 10 + } - public void assignPositiveList(List<@Positive Integer> l) { - List<@Positive Integer> i = l; // line 13 - } + public void assignPositiveList(List<@Positive Integer> l) { + List<@Positive Integer> i = l; // line 13 + } - public void assignGTENOList(List<@GTENegativeOne Integer> l) { - List<@GTENegativeOne Integer> i = l; // line 16 - } + public void assignGTENOList(List<@GTENegativeOne Integer> l) { + List<@GTENegativeOne Integer> i = l; // line 16 + } - // Similar examples that work - public void assignNonNegativeArrayOK(@NonNegative Integer[] l) { - @NonNegative Integer[] i = l; - } + // Similar examples that work + public void assignNonNegativeArrayOK(@NonNegative Integer[] l) { + @NonNegative Integer[] i = l; + } - public void assignIntRangeListOK(List<@IntRange(from = 0) Integer> l) { - List<@IntRange(from = 0) Integer> i = l; - } + public void assignIntRangeListOK(List<@IntRange(from = 0) Integer> l) { + List<@IntRange(from = 0) Integer> i = l; + } } diff --git a/checker/tests/index/GreaterThanOrEqualTransfer.java b/checker/tests/index/GreaterThanOrEqualTransfer.java index 61138be674f..09deb96fbd7 100644 --- a/checker/tests/index/GreaterThanOrEqualTransfer.java +++ b/checker/tests/index/GreaterThanOrEqualTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class GreaterThanOrEqualTransfer { - void gte_check(int[] a) { - if (a.length >= 1) { - int @MinLen(1) [] b = a; + void gte_check(int[] a) { + if (a.length >= 1) { + int @MinLen(1) [] b = a; + } } - } - void gte_bad_check(int[] a) { - if (a.length >= 1) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void gte_bad_check(int[] a) { + if (a.length >= 1) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/checker/tests/index/GreaterThanTransfer.java b/checker/tests/index/GreaterThanTransfer.java index a0cee5304a8..dbacd8374ef 100644 --- a/checker/tests/index/GreaterThanTransfer.java +++ b/checker/tests/index/GreaterThanTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class GreaterThanTransfer { - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; + } } - } - void gt_bad_check(int[] a) { - if (a.length > 0) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void gt_bad_check(int[] a) { + if (a.length > 0) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/checker/tests/index/GuavaPrimitives.java b/checker/tests/index/GuavaPrimitives.java index 9b2f33b330b..bac19a8055c 100644 --- a/checker/tests/index/GuavaPrimitives.java +++ b/checker/tests/index/GuavaPrimitives.java @@ -1,6 +1,3 @@ -import java.util.AbstractList; -import java.util.Collections; -import java.util.List; import org.checkerframework.checker.index.qual.HasSubsequence; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; @@ -11,126 +8,130 @@ import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.value.qual.MinLen; +import java.util.AbstractList; +import java.util.Collections; +import java.util.List; + /** * A simplified version of the Guava primitives classes (such as Bytes, Longs, Shorts, etc.) with * all expected warnings suppressed. */ public class GuavaPrimitives extends AbstractList { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - final short @MinLen(1) [] array; - - final @IndexFor("array") @LessThan("end") int start; - final @Positive @LTEqLengthOf("array") int end; - - public static @IndexOrLow("#1") int indexOf(short[] array, short target) { - return indexOf(array, target, 0, array.length); - } - - private static @IndexOrLow("#1") @LessThan("#4") int indexOf( - short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + final short @MinLen(1) [] array; + + final @IndexFor("array") @LessThan("end") int start; + final @Positive @LTEqLengthOf("array") int end; + + public static @IndexOrLow("#1") int indexOf(short[] array, short target) { + return indexOf(array, target, 0, array.length); + } + + private static @IndexOrLow("#1") @LessThan("#4") int indexOf( + short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( + short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + GuavaPrimitives(short @MinLen(1) [] array) { + this(array, 0, array.length); + } + + @SuppressWarnings( + "index" // these three fields need to be initialized in some order, and any ordering + // leads to the first two issuing errors - since each field is dependent on at least one of the + // others + ) + GuavaPrimitives( + short @MinLen(1) [] array, + @IndexFor("#1") @LessThan("#3") int start, + @Positive @LTEqLengthOf("#1") int end) { + // warnings in here might just need to be suppressed. A single @SuppressWarnings("index") to + // establish rep. invariant might be okay? + this.array = array; + this.start = start; + this.end = end; + } + + public @Positive @LTLengthOf( + value = {"this", "array"}, + offset = {"-1", "start - 1"}) int + size() { // INDEX: Annotation on a public method refers to private member. + return end - start; + } + + public boolean isEmpty() { + return false; } - return -1; - } - - private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( - short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } + + public Short get(@IndexFor("this") int index) { + return array[start + index]; } - return -1; - } - - GuavaPrimitives(short @MinLen(1) [] array) { - this(array, 0, array.length); - } - - @SuppressWarnings( - "index" // these three fields need to be initialized in some order, and any ordering - // leads to the first two issuing errors - since each field is dependent on at least one of the - // others - ) - GuavaPrimitives( - short @MinLen(1) [] array, - @IndexFor("#1") @LessThan("#3") int start, - @Positive @LTEqLengthOf("#1") int end) { - // warnings in here might just need to be suppressed. A single @SuppressWarnings("index") to - // establish rep. invariant might be okay? - this.array = array; - this.start = start; - this.end = end; - } - - public @Positive @LTLengthOf( - value = {"this", "array"}, - offset = {"-1", "start - 1"}) int - size() { // INDEX: Annotation on a public method refers to private member. - return end - start; - } - - public boolean isEmpty() { - return false; - } - - public Short get(@IndexFor("this") int index) { - return array[start + index]; - } - - @SuppressWarnings( - "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 indexOf() - public @IndexOrLow("this") int indexOf(Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Short) { - int i = GuavaPrimitives.indexOf(array, (Short) target, start, end); - if (i >= 0) { - return i - start; - } + + @SuppressWarnings( + "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 indexOf() + public @IndexOrLow("this") int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = GuavaPrimitives.indexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; } - return -1; - } - - @SuppressWarnings( - "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 lastIndexOf() - public @IndexOrLow("this") int lastIndexOf(Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Short) { - int i = GuavaPrimitives.lastIndexOf(array, (Short) target, start, end); - if (i >= 0) { - return i - start; - } + + @SuppressWarnings( + "lowerbound") // https://github.com/kelloggm/checker-framework/issues/227 lastIndexOf() + public @IndexOrLow("this") int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = GuavaPrimitives.lastIndexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; } - return -1; - } - - public Short set(@IndexFor("this") int index, Short element) { - short oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = element; - return oldValue; - } - - @SuppressWarnings("index") // needs https://github.com/kelloggm/checker-framework/issues/229 - public List subList( - @IndexOrHigh("this") @LessThan("#2") int fromIndex, @IndexOrHigh("this") int toIndex) { - int size = size(); - if (fromIndex == toIndex) { - return Collections.emptyList(); + + public Short set(@IndexFor("this") int index, Short element) { + short oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = element; + return oldValue; } - return new GuavaPrimitives(array, start + fromIndex, start + toIndex); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 6); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); + + @SuppressWarnings("index") // needs https://github.com/kelloggm/checker-framework/issues/229 + public List subList( + @IndexOrHigh("this") @LessThan("#2") int fromIndex, @IndexOrHigh("this") int toIndex) { + int size = size(); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new GuavaPrimitives(array, start + fromIndex, start + toIndex); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 6); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); } - return builder.append(']').toString(); - } } diff --git a/checker/tests/index/HexEncode.java b/checker/tests/index/HexEncode.java index 77f3be1d284..e9d4bc8091c 100644 --- a/checker/tests/index/HexEncode.java +++ b/checker/tests/index/HexEncode.java @@ -1,16 +1,16 @@ @SuppressWarnings("array.access.unsafe.high") public class HexEncode { - private static final char[] digits = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; + private static final char[] digits = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; - public static String hexEncode(byte[] bytes) { - StringBuffer s = new StringBuffer(bytes.length * 2); - for (int i = 0; i < bytes.length; i++) { - byte b = bytes[i]; - s.append(digits[(b & 0xf0) >> 4]); - s.append(digits[b & 0x0f]); + public static String hexEncode(byte[] bytes) { + StringBuffer s = new StringBuffer(bytes.length * 2); + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + s.append(digits[(b & 0xf0) >> 4]); + s.append(digits[b & 0x0f]); + } + return s.toString(); } - return s.toString(); - } } diff --git a/checker/tests/index/Index115.java b/checker/tests/index/Index115.java index ae9cc8e78c7..080f02ec798 100644 --- a/checker/tests/index/Index115.java +++ b/checker/tests/index/Index115.java @@ -2,27 +2,27 @@ public class Index115 { - public static void main(String[] args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); + public static void main(String[] args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); + } } - } - public static void main2(String... args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); + public static void main2(String... args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); + } } - } - public static void main3(String @ArrayLen({1, 2}) [] args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); + public static void main3(String @ArrayLen({1, 2}) [] args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); + } } - } - public static void main4(String @ArrayLen({1, 2}) ... args) { - if ((args.length > 1) && (args[1].equals("foo"))) { - System.out.println("First argument is foo"); + public static void main4(String @ArrayLen({1, 2}) ... args) { + if ((args.length > 1) && (args[1].equals("foo"))) { + System.out.println("First argument is foo"); + } } - } } diff --git a/checker/tests/index/Index118.java b/checker/tests/index/Index118.java index 411851bb7ed..6dda5c701ea 100644 --- a/checker/tests/index/Index118.java +++ b/checker/tests/index/Index118.java @@ -3,16 +3,16 @@ public class Index118 { - public static void foo(String @ArrayLen(4) [] args) { - for (int i = 1; i <= 3; i++) { - @IntRange(from = 1, to = 3) int x = i; - System.out.println(args[i]); + public static void foo(String @ArrayLen(4) [] args) { + for (int i = 1; i <= 3; i++) { + @IntRange(from = 1, to = 3) int x = i; + System.out.println(args[i]); + } } - } - public static void bar(@NonNegative int i, String @ArrayLen(4) [] args) { - if (i <= 3) { - System.out.println(args[i]); + public static void bar(@NonNegative int i, String @ArrayLen(4) [] args) { + if (i <= 3) { + System.out.println(args[i]); + } } - } } diff --git a/checker/tests/index/Index118NoLoop.java b/checker/tests/index/Index118NoLoop.java index 6a3ba94a823..1006e87c5cf 100644 --- a/checker/tests/index/Index118NoLoop.java +++ b/checker/tests/index/Index118NoLoop.java @@ -2,9 +2,9 @@ public class Index118NoLoop { - public static void foo(String @ArrayLen(4) [] args, int i) { - if (i >= 1 && i <= 3) { - System.out.println(args[i]); + public static void foo(String @ArrayLen(4) [] args, int i) { + if (i >= 1 && i <= 3) { + System.out.println(args[i]); + } } - } } diff --git a/checker/tests/index/Index132.java b/checker/tests/index/Index132.java index 4cbfe2629c9..f7e321b124a 100644 --- a/checker/tests/index/Index132.java +++ b/checker/tests/index/Index132.java @@ -1,11 +1,11 @@ import org.checkerframework.common.value.qual.*; public class Index132 { - public static String @ArrayLen({3, 4}) [] esc_quantify(String @ArrayLen({1, 2}) ... vars) { - if (vars.length == 1) { - return new String[] {"hello", vars[0], ")"}; - } else { - return new String[] {"hello", vars[0], vars[1], ")"}; + public static String @ArrayLen({3, 4}) [] esc_quantify(String @ArrayLen({1, 2}) ... vars) { + if (vars.length == 1) { + return new String[] {"hello", vars[0], ")"}; + } else { + return new String[] {"hello", vars[0], vars[1], ")"}; + } } - } } diff --git a/checker/tests/index/Index166.java b/checker/tests/index/Index166.java index 62ac0cdcac8..93df6809dab 100644 --- a/checker/tests/index/Index166.java +++ b/checker/tests/index/Index166.java @@ -5,11 +5,11 @@ public class Index166 { - public void testMethodInvocation() { - requiresIndex("012345", 5); - // :: error: (argument.type.incompatible) - requiresIndex("012345", 6); - } + public void testMethodInvocation() { + requiresIndex("012345", 5); + // :: error: (argument.type.incompatible) + requiresIndex("012345", 6); + } - public void requiresIndex(String str, @IndexFor("#1") int index) {} + public void requiresIndex(String str, @IndexFor("#1") int index) {} } diff --git a/checker/tests/index/Index167.java b/checker/tests/index/Index167.java index d0d2b6a69e9..3f37b24cb64 100644 --- a/checker/tests/index/Index167.java +++ b/checker/tests/index/Index167.java @@ -6,22 +6,22 @@ import org.checkerframework.checker.index.qual.NonNegative; public class Index167 { - static void fn1(int[] arr, @IndexFor("#1") int i) { - if (i >= 33) { - // :: error: (argument.type.incompatible) - fn2(arr, i); + static void fn1(int[] arr, @IndexFor("#1") int i) { + if (i >= 33) { + // :: error: (argument.type.incompatible) + fn2(arr, i); + } + if (i > 33) { + // :: error: (argument.type.incompatible) + fn2(arr, i); + } + if (i != 33) { + // :: error: (argument.type.incompatible) + fn2(arr, i); + } } - if (i > 33) { - // :: error: (argument.type.incompatible) - fn2(arr, i); - } - if (i != 33) { - // :: error: (argument.type.incompatible) - fn2(arr, i); - } - } - static void fn2(int[] arr, @NonNegative @LTOMLengthOf("#1") int i) { - int c = arr[i + 1]; - } + static void fn2(int[] arr, @NonNegative @LTOMLengthOf("#1") int i) { + int c = arr[i + 1]; + } } diff --git a/checker/tests/index/Index176.java b/checker/tests/index/Index176.java index c5030804c18..b0aadbc7168 100644 --- a/checker/tests/index/Index176.java +++ b/checker/tests/index/Index176.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.index.qual.IndexFor; public class Index176 { - void test(String arglist, @IndexFor("#1") int pos) { - int semi_pos = arglist.indexOf(";"); - if (semi_pos == -1) { - throw new Error("Malformed arglist: " + arglist); + void test(String arglist, @IndexFor("#1") int pos) { + int semi_pos = arglist.indexOf(";"); + if (semi_pos == -1) { + throw new Error("Malformed arglist: " + arglist); + } + arglist.substring(pos, semi_pos + 1); + // :: error: (argument.type.incompatible) + arglist.substring(pos, semi_pos + 2); } - arglist.substring(pos, semi_pos + 1); - // :: error: (argument.type.incompatible) - arglist.substring(pos, semi_pos + 2); - } } diff --git a/checker/tests/index/IndexByChar.java b/checker/tests/index/IndexByChar.java index df049c0f8b3..263a0b656b8 100644 --- a/checker/tests/index/IndexByChar.java +++ b/checker/tests/index/IndexByChar.java @@ -1,10 +1,10 @@ public class IndexByChar { - public int m(char c) { - int[] i = new int[128]; - if (c < 128) { - return i[c]; - } else { - return -1; + public int m(char c) { + int[] i = new int[128]; + if (c < 128) { + return i[c]; + } else { + return -1; + } } - } } diff --git a/checker/tests/index/IndexConditionalReport.java b/checker/tests/index/IndexConditionalReport.java index 469b1fd333c..ef590170532 100644 --- a/checker/tests/index/IndexConditionalReport.java +++ b/checker/tests/index/IndexConditionalReport.java @@ -2,12 +2,12 @@ public class IndexConditionalReport { - public int getI(int len) { - for (int i = 0; i < len; i++) { - if (false) { - return i == 0 ? -1 : i; // unexpected error issued here - } + public int getI(int len) { + for (int i = 0; i < len; i++) { + if (false) { + return i == 0 ? -1 : i; // unexpected error issued here + } + } + return -1; } - return -1; - } } diff --git a/checker/tests/index/IndexForAverage.java b/checker/tests/index/IndexForAverage.java index 756227a97b5..a11712b76d0 100644 --- a/checker/tests/index/IndexForAverage.java +++ b/checker/tests/index/IndexForAverage.java @@ -4,13 +4,13 @@ public class IndexForAverage { - public static void bug(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @IndexFor("a") int k = (i + j) / 2; - } + public static void bug(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @IndexFor("a") int k = (i + j) / 2; + } - public static void bug2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @LTLengthOf("a") int k = ((i - 1) + j) / 2; - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int h = ((i + 1) + j) / 2; - } + public static void bug2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @LTLengthOf("a") int k = ((i - 1) + j) / 2; + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int h = ((i + 1) + j) / 2; + } } diff --git a/checker/tests/index/IndexForTest.java b/checker/tests/index/IndexForTest.java index a9a3f599737..74d0cbbcbb4 100644 --- a/checker/tests/index/IndexForTest.java +++ b/checker/tests/index/IndexForTest.java @@ -2,77 +2,77 @@ import org.checkerframework.common.value.qual.MinLen; public class IndexForTest { - int @MinLen(1) [] array = {0}; - - void test1(@IndexFor("array") int i) { - int x = array[i]; - } - - void callTest1(int x) { - test1(0); - // :: error: (argument.type.incompatible) - test1(1); - // :: error: (argument.type.incompatible) - test1(2); - // :: error: (argument.type.incompatible) - test1(array.length); - - if (array.length > 0) { - test1(array.length - 1); + int @MinLen(1) [] array = {0}; + + void test1(@IndexFor("array") int i) { + int x = array[i]; } - test1(array.length - 1); + void callTest1(int x) { + test1(0); + // :: error: (argument.type.incompatible) + test1(1); + // :: error: (argument.type.incompatible) + test1(2); + // :: error: (argument.type.incompatible) + test1(array.length); - // :: error: (argument.type.incompatible) - test1(this.array.length); + if (array.length > 0) { + test1(array.length - 1); + } - if (array.length > 0) { - test1(this.array.length - 1); - } + test1(array.length - 1); - test1(this.array.length - 1); + // :: error: (argument.type.incompatible) + test1(this.array.length); - if (this.array.length > x && x >= 0) { - test1(x); - } + if (array.length > 0) { + test1(this.array.length - 1); + } + + test1(this.array.length - 1); + + if (this.array.length > x && x >= 0) { + test1(x); + } - if (array.length == x) { - // :: error: (argument.type.incompatible) - test1(x); + if (array.length == x) { + // :: error: (argument.type.incompatible) + test1(x); + } } - } - - void test2(@IndexFor("this.array") int i) { - int x = array[i]; - } - - void callTest2(int x) { - test2(0); - // :: error: (argument.type.incompatible) - test2(1); - // :: error: (argument.type.incompatible) - test2(2); - // :: error: (argument.type.incompatible) - test2(array.length); - - if (array.length > 0) { - test2(array.length - 1); + + void test2(@IndexFor("this.array") int i) { + int x = array[i]; } - test2(array.length - 1); + void callTest2(int x) { + test2(0); + // :: error: (argument.type.incompatible) + test2(1); + // :: error: (argument.type.incompatible) + test2(2); + // :: error: (argument.type.incompatible) + test2(array.length); - // :: error: (argument.type.incompatible) - test2(this.array.length); + if (array.length > 0) { + test2(array.length - 1); + } - if (array.length > 0) { - test2(this.array.length - 1); - } + test2(array.length - 1); + + // :: error: (argument.type.incompatible) + test2(this.array.length); + + if (array.length > 0) { + test2(this.array.length - 1); + } - test2(this.array.length - 1); + test2(this.array.length - 1); - if (array.length == x && x >= 0) { - // :: error: (argument.type.incompatible) - test2(x); + if (array.length == x && x >= 0) { + // :: error: (argument.type.incompatible) + test2(x); + } } - } } diff --git a/checker/tests/index/IndexForTestLBC.java b/checker/tests/index/IndexForTestLBC.java index 66eaf7dac82..74497b6df86 100644 --- a/checker/tests/index/IndexForTestLBC.java +++ b/checker/tests/index/IndexForTestLBC.java @@ -4,26 +4,26 @@ @SuppressWarnings("upperbound") public class IndexForTestLBC { - int[] array = {0}; + int[] array = {0}; - void test1(@IndexFor("array") int i) { - int x = this.array[i]; - } - - void callTest1(int x) { - test1(0); - test1(1); - test1(2); - test1(array.length); - // :: error: (argument.type.incompatible) - test1(array.length - 1); - if (array.length > x) { - // :: error: (argument.type.incompatible) - test1(x); + void test1(@IndexFor("array") int i) { + int x = this.array[i]; } - if (array.length == x) { - test1(x); + void callTest1(int x) { + test1(0); + test1(1); + test1(2); + test1(array.length); + // :: error: (argument.type.incompatible) + test1(array.length - 1); + if (array.length > x) { + // :: error: (argument.type.incompatible) + test1(x); + } + + if (array.length == x) { + test1(x); + } } - } } diff --git a/checker/tests/index/IndexForTwoArrays.java b/checker/tests/index/IndexForTwoArrays.java index 4e31190b64c..1afe76eb19a 100644 --- a/checker/tests/index/IndexForTwoArrays.java +++ b/checker/tests/index/IndexForTwoArrays.java @@ -1,15 +1,15 @@ public class IndexForTwoArrays { - public int compare(double[] a1, double[] a2) { - if (a1 == a2) { - return 0; + public int compare(double[] a1, double[] a2) { + if (a1 == a2) { + return 0; + } + int len = Math.min(a1.length, a2.length); + for (int i = 0; i < len; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); + } + } + return a1.length - a2.length; } - int len = Math.min(a1.length, a2.length); - for (int i = 0; i < len; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); - } - } - return a1.length - a2.length; - } } diff --git a/checker/tests/index/IndexForTwoArrays2.java b/checker/tests/index/IndexForTwoArrays2.java index 9606190477b..b1f2d940ccf 100644 --- a/checker/tests/index/IndexForTwoArrays2.java +++ b/checker/tests/index/IndexForTwoArrays2.java @@ -2,17 +2,17 @@ public class IndexForTwoArrays2 { - public boolean equals(int[] da1, int[] da2) { - if (da1.length != da2.length) { - return false; - } - int k = 0; + public boolean equals(int[] da1, int[] da2) { + if (da1.length != da2.length) { + return false; + } + int k = 0; - for (int i = 0; i < da1.length; i++) { - if (da1[i] != da2[i]) { - return false; - } + for (int i = 0; i < da1.length; i++) { + if (da1[i] != da2[i]) { + return false; + } + } + return true; } - return true; - } } diff --git a/checker/tests/index/IndexForVarargs.java b/checker/tests/index/IndexForVarargs.java index 1673e062893..58445f31227 100644 --- a/checker/tests/index/IndexForVarargs.java +++ b/checker/tests/index/IndexForVarargs.java @@ -1,33 +1,33 @@ import org.checkerframework.checker.index.qual.IndexFor; public class IndexForVarargs { - String get(@IndexFor("#2") int i, String... varargs) { - return varargs[i]; - } + String get(@IndexFor("#2") int i, String... varargs) { + return varargs[i]; + } - void method(@IndexFor("#2") int i, String[]... varargs) {} + void method(@IndexFor("#2") int i, String[]... varargs) {} - void m() { - // :: error: (argument.type.incompatible) - get(1); + void m() { + // :: error: (argument.type.incompatible) + get(1); - get(1, "a", "b"); + get(1, "a", "b"); - // :: error: (argument.type.incompatible) - get(2, "abc"); + // :: error: (argument.type.incompatible) + get(2, "abc"); - String[] stringArg1 = new String[] {"a", "b"}; - String[] stringArg2 = new String[] {"c", "d", "e"}; - String[] stringArg3 = new String[] {"a", "b", "c"}; + String[] stringArg1 = new String[] {"a", "b"}; + String[] stringArg2 = new String[] {"c", "d", "e"}; + String[] stringArg3 = new String[] {"a", "b", "c"}; - method(1, stringArg1, stringArg2); + method(1, stringArg1, stringArg2); - // :: error: (argument.type.incompatible) - method(2, stringArg3); + // :: error: (argument.type.incompatible) + method(2, stringArg3); - get(1, stringArg1); + get(1, stringArg1); - // :: error: (argument.type.incompatible) - get(3, stringArg2); - } + // :: error: (argument.type.incompatible) + get(3, stringArg2); + } } diff --git a/checker/tests/index/IndexIntValVsConstant.java b/checker/tests/index/IndexIntValVsConstant.java index 8c0c8f53c3c..113ad9baf3d 100644 --- a/checker/tests/index/IndexIntValVsConstant.java +++ b/checker/tests/index/IndexIntValVsConstant.java @@ -8,22 +8,22 @@ public class IndexIntValVsConstant { - void m() { + void m() { - int @ArrayLen(7) [] a1 = new int[] {1, 2, 3, 4, 5, 6, 7}; + int @ArrayLen(7) [] a1 = new int[] {1, 2, 3, 4, 5, 6, 7}; - @IntVal(2) int i = 2; - @IntVal(4) int j = 4; + @IntVal(2) int i = 2; + @IntVal(4) int j = 4; - int[] s0 = internSubsequence(a1, 2, 4); - int[] s1 = internSubsequence(a1, i, j); - } + int[] s0 = internSubsequence(a1, 2, 4); + int[] s1 = internSubsequence(a1, i, j); + } - int @Interned [] internSubsequence( - int @Interned [] seq, - @IndexFor("#1") @LessThan("#3") int start, - @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { - // dummy implementation - return new int[0]; - } + int @Interned [] internSubsequence( + int @Interned [] seq, + @IndexFor("#1") @LessThan("#3") int start, + @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int end) { + // dummy implementation + return new int[0]; + } } diff --git a/checker/tests/index/IndexIssue6046.java b/checker/tests/index/IndexIssue6046.java index 3c486b147e8..c29372bcdf9 100644 --- a/checker/tests/index/IndexIssue6046.java +++ b/checker/tests/index/IndexIssue6046.java @@ -1,3 +1,5 @@ +import org.checkerframework.common.value.qual.ArrayLen; + import java.util.Formattable; import java.util.LinkedHashMap; import java.util.List; @@ -5,40 +7,39 @@ import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; -import org.checkerframework.common.value.qual.ArrayLen; public class IndexIssue6046 { - public interface Record extends Comparable, Formattable {} - - public interface Result extends List, Formattable {} - - @SuppressWarnings("unchecked") - public static - Collector>> intoResultGroups( - Function keyMapper) { - - return Collectors.groupingBy( - keyMapper, - LinkedHashMap::new, - Collector.[], Result>of( - // :: error: (array.access.unsafe.high.constant) - () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); - } - - @SuppressWarnings("unchecked") - public static - Collector>> intoResultGroups2( - Function keyMapper) { - - return Collectors.groupingBy( - keyMapper, - LinkedHashMap::new, - Collector. @ArrayLen(1) [], Result>of( - () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); - } - - public static Result result(R record) { - throw new RuntimeException(); - } + public interface Record extends Comparable, Formattable {} + + public interface Result extends List, Formattable {} + + @SuppressWarnings("unchecked") + public static + Collector>> intoResultGroups( + Function keyMapper) { + + return Collectors.groupingBy( + keyMapper, + LinkedHashMap::new, + Collector.[], Result>of( + // :: error: (array.access.unsafe.high.constant) + () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); + } + + @SuppressWarnings("unchecked") + public static + Collector>> intoResultGroups2( + Function keyMapper) { + + return Collectors.groupingBy( + keyMapper, + LinkedHashMap::new, + Collector. @ArrayLen(1) [], Result>of( + () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); + } + + public static Result result(R record) { + throw new RuntimeException(); + } } diff --git a/checker/tests/index/IndexOf.java b/checker/tests/index/IndexOf.java index 0596d409593..78c747404b0 100644 --- a/checker/tests/index/IndexOf.java +++ b/checker/tests/index/IndexOf.java @@ -4,12 +4,12 @@ public class IndexOf { - public static String m(String arg) { - int split_pos = arg.indexOf(",-"); - if (split_pos == 0) { - // Just discard the ',' if ",-" occurs at begining of string - arg = arg.substring(1); + public static String m(String arg) { + int split_pos = arg.indexOf(",-"); + if (split_pos == 0) { + // Just discard the ',' if ",-" occurs at begining of string + arg = arg.substring(1); + } + return arg; } - return arg; - } } diff --git a/checker/tests/index/IndexOrLowTests.java b/checker/tests/index/IndexOrLowTests.java index 9eafdccea45..150b6aad1e1 100644 --- a/checker/tests/index/IndexOrLowTests.java +++ b/checker/tests/index/IndexOrLowTests.java @@ -4,29 +4,29 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class IndexOrLowTests { - int[] array = {1, 2}; + int[] array = {1, 2}; - @IndexOrLow("array") int index = -1; + @IndexOrLow("array") int index = -1; - void test() { + void test() { - if (index != -1) { - array[index] = 1; - } + if (index != -1) { + array[index] = 1; + } - @IndexOrHigh("array") int y = index + 1; - // :: error: (array.access.unsafe.high) - array[y] = 1; - if (y < array.length) { - array[y] = 1; + @IndexOrHigh("array") int y = index + 1; + // :: error: (array.access.unsafe.high) + array[y] = 1; + if (y < array.length) { + array[y] = 1; + } + // :: error: (assignment.type.incompatible) + index = array.length; } - // :: error: (assignment.type.incompatible) - index = array.length; - } - void test2(@LTLengthOf("array") @GTENegativeOne int param) { - index = array.length - 1; - @LTLengthOf("array") @GTENegativeOne int x = index; - index = param; - } + void test2(@LTLengthOf("array") @GTENegativeOne int param) { + index = array.length - 1; + @LTLengthOf("array") @GTENegativeOne int x = index; + index = param; + } } diff --git a/checker/tests/index/IndexSameLen.java b/checker/tests/index/IndexSameLen.java index 90d2bb18a0d..40b89ed8fdb 100644 --- a/checker/tests/index/IndexSameLen.java +++ b/checker/tests/index/IndexSameLen.java @@ -2,15 +2,15 @@ public class IndexSameLen { - public static void bug2() { - int[] a = {1, 2, 3, 4, 5}; - int @SameLen("a") [] b = a; + public static void bug2() { + int[] a = {1, 2, 3, 4, 5}; + int @SameLen("a") [] b = a; - @IndexFor("a") int i = 2; - a[i] = b[i]; + @IndexFor("a") int i = 2; + a[i] = b[i]; - for (int j = 0; j < a.length; j++) { - a[j] = b[j]; + for (int j = 0; j < a.length; j++) { + a[j] = b[j]; + } } - } } diff --git a/checker/tests/index/IntroAdd.java b/checker/tests/index/IntroAdd.java index 2545374c6ea..42316529eca 100644 --- a/checker/tests/index/IntroAdd.java +++ b/checker/tests/index/IntroAdd.java @@ -2,18 +2,18 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class IntroAdd { - void test(int[] arr) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int a = 3; - } + void test(int[] arr) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int a = 3; + } - void test(int[] arr, @LTLengthOf({"#1"}) int a) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int c = a + 1; - @LTEqLengthOf({"arr"}) int c1 = a + 1; - @LTLengthOf({"arr"}) int d = a + 0; - @LTLengthOf({"arr"}) int e = a + (-7); - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int f = a + 7; - } + void test(int[] arr, @LTLengthOf({"#1"}) int a) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int c = a + 1; + @LTEqLengthOf({"arr"}) int c1 = a + 1; + @LTLengthOf({"arr"}) int d = a + 0; + @LTLengthOf({"arr"}) int e = a + (-7); + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int f = a + 7; + } } diff --git a/checker/tests/index/IntroAnd.java b/checker/tests/index/IntroAnd.java index bcdb28119dd..340da58b8ae 100644 --- a/checker/tests/index/IntroAnd.java +++ b/checker/tests/index/IntroAnd.java @@ -1,39 +1,39 @@ import org.checkerframework.checker.index.qual.*; public class IntroAnd { - void test() { - @NonNegative int a = 1 & 0; - @NonNegative int b = a & 5; + void test() { + @NonNegative int a = 1 & 0; + @NonNegative int b = a & 5; - // :: error: (assignment.type.incompatible) - @Positive int c = a & b; - @NonNegative int d = a & b; - @NonNegative int e = b & a; - } - - void test_ubc_and( - @IndexFor("#2") int i, int[] a, @LTLengthOf("#2") int j, int k, @NonNegative int m) { - int x = a[i & k]; - int x1 = a[k & i]; - // :: error: (array.access.unsafe.low) :: error: (array.access.unsafe.high) - int y = a[j & k]; - if (j > -1) { - int z = a[j & k]; + // :: error: (assignment.type.incompatible) + @Positive int c = a & b; + @NonNegative int d = a & b; + @NonNegative int e = b & a; } - // :: error: (array.access.unsafe.high) - int w = a[m & k]; - if (m < a.length) { - int u = a[m & k]; + + void test_ubc_and( + @IndexFor("#2") int i, int[] a, @LTLengthOf("#2") int j, int k, @NonNegative int m) { + int x = a[i & k]; + int x1 = a[k & i]; + // :: error: (array.access.unsafe.low) :: error: (array.access.unsafe.high) + int y = a[j & k]; + if (j > -1) { + int z = a[j & k]; + } + // :: error: (array.access.unsafe.high) + int w = a[m & k]; + if (m < a.length) { + int u = a[m & k]; + } } - } - void two_arrays(int[] a, int[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { - int l = a[i & j]; - l = b[i & j]; - } + void two_arrays(int[] a, int[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { + int l = a[i & j]; + l = b[i & j]; + } - void test_pos(@Positive int x, @Positive int y) { - // :: error: (assignment.type.incompatible) - @Positive int z = x & y; - } + void test_pos(@Positive int x, @Positive int y) { + // :: error: (assignment.type.incompatible) + @Positive int z = x & y; + } } diff --git a/checker/tests/index/IntroRules.java b/checker/tests/index/IntroRules.java index 65bd632b426..75b1493fa05 100644 --- a/checker/tests/index/IntroRules.java +++ b/checker/tests/index/IntroRules.java @@ -5,31 +5,31 @@ public class IntroRules { - void test() { - @Positive int a = 10; - @NonNegative int b = 9; - @GTENegativeOne int c = 8; - @LowerBoundUnknown int d = 7; + void test() { + @Positive int a = 10; + @NonNegative int b = 9; + @GTENegativeOne int c = 8; + @LowerBoundUnknown int d = 7; - // :: error: (assignment.type.incompatible) - @Positive int e = 0; - // :: error: (assignment.type.incompatible) - @Positive int f = -1; - // :: error: (assignment.type.incompatible) - @Positive int g = -6; + // :: error: (assignment.type.incompatible) + @Positive int e = 0; + // :: error: (assignment.type.incompatible) + @Positive int f = -1; + // :: error: (assignment.type.incompatible) + @Positive int g = -6; - @NonNegative int h = 0; - @GTENegativeOne int i = 0; - @LowerBoundUnknown int j = 0; - // :: error: (assignment.type.incompatible) - @NonNegative int k = -1; - // :: error: (assignment.type.incompatible) - @NonNegative int l = -4; + @NonNegative int h = 0; + @GTENegativeOne int i = 0; + @LowerBoundUnknown int j = 0; + // :: error: (assignment.type.incompatible) + @NonNegative int k = -1; + // :: error: (assignment.type.incompatible) + @NonNegative int l = -4; - @GTENegativeOne int m = -1; - @LowerBoundUnknown int n = -1; - // :: error: (assignment.type.incompatible) - @GTENegativeOne int o = -9; - } + @GTENegativeOne int m = -1; + @LowerBoundUnknown int n = -1; + // :: error: (assignment.type.incompatible) + @GTENegativeOne int o = -9; + } } // a comment diff --git a/checker/tests/index/IntroShift.java b/checker/tests/index/IntroShift.java index ec075157e9b..dff7b471fe8 100644 --- a/checker/tests/index/IntroShift.java +++ b/checker/tests/index/IntroShift.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.NonNegative; public class IntroShift { - void test() { - @NonNegative int a = 1 >> 1; - // :: error: (assignment.type.incompatible) - @NonNegative int b = -1 >> 0; - } + void test() { + @NonNegative int a = 1 >> 1; + // :: error: (assignment.type.incompatible) + @NonNegative int b = -1 >> 0; + } } diff --git a/checker/tests/index/IntroSub.java b/checker/tests/index/IntroSub.java index 42192843845..778c6610756 100644 --- a/checker/tests/index/IntroSub.java +++ b/checker/tests/index/IntroSub.java @@ -2,21 +2,21 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class IntroSub { - void test(int[] arr) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int a = 3; - } + void test(int[] arr) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int a = 3; + } - void test(int[] arr, @LTLengthOf({"#1"}) int a) { - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int c = a - (-1); - @LTEqLengthOf({"arr"}) int c1 = a - (-1); - @LTLengthOf({"arr"}) int d = a - 0; - @LTLengthOf({"arr"}) int e = a - 7; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int f = a - (-7); + void test(int[] arr, @LTLengthOf({"#1"}) int a) { + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int c = a - (-1); + @LTEqLengthOf({"arr"}) int c1 = a - (-1); + @LTLengthOf({"arr"}) int d = a - 0; + @LTLengthOf({"arr"}) int e = a - 7; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int f = a - (-7); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"arr"}) int j = 7; - } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"arr"}) int j = 7; + } } diff --git a/checker/tests/index/InvalidSubsequence.java b/checker/tests/index/InvalidSubsequence.java index e4e29653ca5..f15b85d8a17 100644 --- a/checker/tests/index/InvalidSubsequence.java +++ b/checker/tests/index/InvalidSubsequence.java @@ -4,43 +4,43 @@ import org.checkerframework.checker.index.qual.LessThan; public class InvalidSubsequence { - // :: error: flowexpr.parse.error :: error: not.final - @HasSubsequence(subsequence = "banana", from = "this.from", to = "this.to") - int[] a; + // :: error: flowexpr.parse.error :: error: not.final + @HasSubsequence(subsequence = "banana", from = "this.from", to = "this.to") + int[] a; - // :: error: flowexpr.parse.error :: error: not.final - @HasSubsequence(subsequence = "this", from = "banana", to = "this.to") - int[] b; + // :: error: flowexpr.parse.error :: error: not.final + @HasSubsequence(subsequence = "this", from = "banana", to = "this.to") + int[] b; - // :: error: flowexpr.parse.error :: error: not.final - @HasSubsequence(subsequence = "this", from = "this.from", to = "banana") - int[] c; + // :: error: flowexpr.parse.error :: error: not.final + @HasSubsequence(subsequence = "this", from = "this.from", to = "banana") + int[] c; - // :: error: not.final - @HasSubsequence(subsequence = "this", from = "this.from", to = "10") - int[] e; + // :: error: not.final + @HasSubsequence(subsequence = "this", from = "this.from", to = "10") + int[] e; - @IndexFor("a") @LessThan("to") int from; + @IndexFor("a") @LessThan("to") int from; - @IndexOrHigh("a") int to; + @IndexOrHigh("a") int to; - void assignA(int[] d) { - // :: error: to.not.ltel - a = d; - } + void assignA(int[] d) { + // :: error: to.not.ltel + a = d; + } - void assignB(int[] d) { - // :: error: from.gt.to :: error: from.not.nonnegative :: error: to.not.ltel - b = d; - } + void assignB(int[] d) { + // :: error: from.gt.to :: error: from.not.nonnegative :: error: to.not.ltel + b = d; + } - void assignC(int[] d) { - // :: error: from.gt.to :: error: to.not.ltel - c = d; - } + void assignC(int[] d) { + // :: error: from.gt.to :: error: to.not.ltel + c = d; + } - void assignE(int[] d) { - // :: error: from.gt.to :: error: to.not.ltel - e = d; - } + void assignE(int[] d) { + // :: error: from.gt.to :: error: to.not.ltel + e = d; + } } diff --git a/checker/tests/index/Issue1411.java b/checker/tests/index/Issue1411.java index 75b9bf10655..ea57a3a761a 100644 --- a/checker/tests/index/Issue1411.java +++ b/checker/tests/index/Issue1411.java @@ -4,14 +4,14 @@ import org.checkerframework.dataflow.qual.Pure; interface IGeneric { - @Pure - public V get(); + @Pure + public V get(); } interface IConcrete extends IGeneric {} public class Issue1411 { - static void m(IConcrete ic) { - char[] val = ic.get(); - } + static void m(IConcrete ic) { + char[] val = ic.get(); + } } diff --git a/checker/tests/index/Issue194.java b/checker/tests/index/Issue194.java index 19e06f1889a..a4788cbf9b9 100644 --- a/checker/tests/index/Issue194.java +++ b/checker/tests/index/Issue194.java @@ -6,38 +6,38 @@ import org.checkerframework.checker.index.qual.SameLen; public class Issue194 { - class Custom { - public @LengthOf("this") int length() { - throw new RuntimeException(); - } + class Custom { + public @LengthOf("this") int length() { + throw new RuntimeException(); + } - public Object get(@IndexFor("this") int i) { - return null; - } + public Object get(@IndexFor("this") int i) { + return null; + } - void call() { - length(); + void call() { + length(); + } } - } - public boolean m(Custom a, Custom b) { - if (a.length() != b.length()) { - return false; - } - for (int i = 0; i < a.length(); ++i) { - if (a.get(i) != b.get(i)) { - return false; - } + public boolean m(Custom a, Custom b) { + if (a.length() != b.length()) { + return false; + } + for (int i = 0; i < a.length(); ++i) { + if (a.get(i) != b.get(i)) { + return false; + } + } + return true; } - return true; - } - @SuppressWarnings("anno.on.irrelevant") - public void m2(Custom a, Custom b) { - if (a.length() != b.length()) { - return; + @SuppressWarnings("anno.on.irrelevant") + public void m2(Custom a, Custom b) { + if (a.length() != b.length()) { + return; + } + @SameLen("a") Custom a2 = b; + @SameLen("b") Custom b2 = a; } - @SameLen("a") Custom a2 = b; - @SameLen("b") Custom b2 = a; - } } diff --git a/checker/tests/index/Issue1984.java b/checker/tests/index/Issue1984.java index 1916f000ccf..fd2e65709e9 100644 --- a/checker/tests/index/Issue1984.java +++ b/checker/tests/index/Issue1984.java @@ -4,8 +4,8 @@ import org.checkerframework.common.value.qual.IntRange; public class Issue1984 { - public int m(int[] a, @IntRange(from = 0, to = 12) int i) { - // :: error: (array.access.unsafe.high.range) - return a[i]; - } + public int m(int[] a, @IntRange(from = 0, to = 12) int i) { + // :: error: (array.access.unsafe.high.range) + return a[i]; + } } diff --git a/checker/tests/index/Issue20.java b/checker/tests/index/Issue20.java index 67d4a515da1..f2dc36f942b 100644 --- a/checker/tests/index/Issue20.java +++ b/checker/tests/index/Issue20.java @@ -2,10 +2,10 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class Issue20 { - // An issue with LUB that results in losing information when unifying. - int[] a, b; + // An issue with LUB that results in losing information when unifying. + int[] a, b; - void test(@LTLengthOf("a") int i, @LTEqLengthOf({"a", "b"}) int j, boolean flag) { - @LTEqLengthOf("a") int k = flag ? i : j; - } + void test(@LTLengthOf("a") int i, @LTEqLengthOf({"a", "b"}) int j, boolean flag) { + @LTEqLengthOf("a") int k = flag ? i : j; + } } diff --git a/checker/tests/index/Issue2029.java b/checker/tests/index/Issue2029.java index 5e49ce36311..d948712a273 100644 --- a/checker/tests/index/Issue2029.java +++ b/checker/tests/index/Issue2029.java @@ -3,27 +3,30 @@ import org.checkerframework.checker.index.qual.NonNegative; public class Issue2029 { - void lessThanUpperBound(@NonNegative @LessThan("#2") int index, @NonNegative int size, char val) { - char[] arr = new char[size]; - arr[index] = val; - } + void lessThanUpperBound( + @NonNegative @LessThan("#2") int index, @NonNegative int size, char val) { + char[] arr = new char[size]; + arr[index] = val; + } - void LessThanOffsetLowerBound( - int[] array, @NonNegative @LTLengthOf("#1") int n, @NonNegative @LessThan("#2 + 1") int k) { - array[n - k] = 10; - } + void LessThanOffsetLowerBound( + int[] array, + @NonNegative @LTLengthOf("#1") int n, + @NonNegative @LessThan("#2 + 1") int k) { + array[n - k] = 10; + } - void LessThanOffsetUpperBound( - @NonNegative int n, - @NonNegative @LessThan("#1 + 1") int k, - @NonNegative int size, - @NonNegative @LessThan("#3 + 1") int index) { - @NonNegative int m = n - k; - int[] arr = new int[size]; - // TODO: understand whether this is a false positive - // :: error: (unary.increment.type.incompatible) - for (; index < arr.length - 1; index++) { - arr[index] = 10; + void LessThanOffsetUpperBound( + @NonNegative int n, + @NonNegative @LessThan("#1 + 1") int k, + @NonNegative int size, + @NonNegative @LessThan("#3 + 1") int index) { + @NonNegative int m = n - k; + int[] arr = new int[size]; + // TODO: understand whether this is a false positive + // :: error: (unary.increment.type.incompatible) + for (; index < arr.length - 1; index++) { + arr[index] = 10; + } } - } } diff --git a/checker/tests/index/Issue2030.java b/checker/tests/index/Issue2030.java index b6465b073b5..a6a1a0b08c3 100644 --- a/checker/tests/index/Issue2030.java +++ b/checker/tests/index/Issue2030.java @@ -1,9 +1,9 @@ public class Issue2030 { - double roundIntermediate(double x) { - if (x >= 0.0) { - return x; - } else { - return (long) x - 1; + double roundIntermediate(double x) { + if (x >= 0.0) { + return x; + } else { + return (long) x - 1; + } } - } } diff --git a/checker/tests/index/Issue21.java b/checker/tests/index/Issue21.java index 226a558201b..f474d7893f5 100644 --- a/checker/tests/index/Issue21.java +++ b/checker/tests/index/Issue21.java @@ -1,8 +1,8 @@ public class Issue21 { - void test(int[] arr, int[] arr2) { - for (int i = 0; i < arr2.length && i < arr.length; i++) { - arr[i] = arr2[i]; + void test(int[] arr, int[] arr2) { + for (int i = 0; i < arr2.length && i < arr.length; i++) { + arr[i] = arr2[i]; + } } - } } diff --git a/checker/tests/index/Issue2334.java b/checker/tests/index/Issue2334.java index 9213a89a403..c81a8b8115e 100644 --- a/checker/tests/index/Issue2334.java +++ b/checker/tests/index/Issue2334.java @@ -4,37 +4,37 @@ public class Issue2334 { - void hasSideEffect() {} + void hasSideEffect() {} - String stringField; + String stringField; - void m1(String stringFormal) { - if (stringFormal.indexOf('d') != -1) { - hasSideEffect(); - @NonNegative int i = stringFormal.indexOf('d'); + void m1(String stringFormal) { + if (stringFormal.indexOf('d') != -1) { + hasSideEffect(); + @NonNegative int i = stringFormal.indexOf('d'); + } } - } - void m2() { - if (stringField.indexOf('d') != -1) { - hasSideEffect(); - // :: error: (assignment.type.incompatible) - @NonNegative int i = stringField.indexOf('d'); + void m2() { + if (stringField.indexOf('d') != -1) { + hasSideEffect(); + // :: error: (assignment.type.incompatible) + @NonNegative int i = stringField.indexOf('d'); + } } - } - void m3(String stringFormal) { - if (stringFormal.indexOf('d') != -1) { - System.out.println("hey"); - @NonNegative int i = stringFormal.indexOf('d'); + void m3(String stringFormal) { + if (stringFormal.indexOf('d') != -1) { + System.out.println("hey"); + @NonNegative int i = stringFormal.indexOf('d'); + } } - } - void m4() { - if (stringField.indexOf('d') != -1) { - System.out.println("hey"); - // :: error: (assignment.type.incompatible) - @NonNegative int i = stringField.indexOf('d'); + void m4() { + if (stringField.indexOf('d') != -1) { + System.out.println("hey"); + // :: error: (assignment.type.incompatible) + @NonNegative int i = stringField.indexOf('d'); + } } - } } diff --git a/checker/tests/index/Issue2420.java b/checker/tests/index/Issue2420.java index 6820f56cedb..7ed8cc7dd82 100644 --- a/checker/tests/index/Issue2420.java +++ b/checker/tests/index/Issue2420.java @@ -2,16 +2,16 @@ import org.checkerframework.common.value.qual.*; public class Issue2420 { - static void str(String argStr) { - if (argStr.isEmpty()) { - return; + static void str(String argStr) { + if (argStr.isEmpty()) { + return; + } + if (argStr == "abc") { + return; + } + // :: error: (argument.type.incompatible) + char c = "abc".charAt(argStr.length() - 1); + // :: error: (argument.type.incompatible) + char c2 = "abc".charAt(argStr.length()); } - if (argStr == "abc") { - return; - } - // :: error: (argument.type.incompatible) - char c = "abc".charAt(argStr.length() - 1); - // :: error: (argument.type.incompatible) - char c2 = "abc".charAt(argStr.length()); - } } diff --git a/checker/tests/index/Issue2452.java b/checker/tests/index/Issue2452.java index eca2e220508..02374f96d24 100644 --- a/checker/tests/index/Issue2452.java +++ b/checker/tests/index/Issue2452.java @@ -1,6 +1,5 @@ // Test case for https://github.com/typetools/checker-framework/issues/2452 -import java.lang.reflect.Array; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.checker.index.qual.LTEqLengthOf; @@ -8,32 +7,34 @@ import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.value.qual.MinLen; +import java.lang.reflect.Array; + class Issue2452 { - Object m1(Object[] a1) { - if (Array.getLength(a1) > 0) { - return Array.get(a1, 0); - } else { - return null; + Object m1(Object[] a1) { + if (Array.getLength(a1) > 0) { + return Array.get(a1, 0); + } else { + return null; + } } - } - void m2() { - int[] arr = {1, 2, 3}; - @LTEqLengthOf({"arr"}) int a = Array.getLength(arr); - } + void m2() { + int[] arr = {1, 2, 3}; + @LTEqLengthOf({"arr"}) int a = Array.getLength(arr); + } - void testMinLenSubtractPositive(String @MinLen(10) [] s) { - @Positive int i1 = s.length - 9; - @NonNegative int i0 = Array.getLength(s) - 10; - // :: error: (assignment.type.incompatible) - @NonNegative int im1 = Array.getLength(s) - 11; - } + void testMinLenSubtractPositive(String @MinLen(10) [] s) { + @Positive int i1 = s.length - 9; + @NonNegative int i0 = Array.getLength(s) - 10; + // :: error: (assignment.type.incompatible) + @NonNegative int im1 = Array.getLength(s) - 11; + } - void testLessThanLength(String[] s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { - if (i < Array.getLength(s)) { - @IndexFor("s") int in = i; - // :: error: (assignment.type.incompatible) - @IndexFor("s") int jn = j; + void testLessThanLength(String[] s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { + if (i < Array.getLength(s)) { + @IndexFor("s") int in = i; + // :: error: (assignment.type.incompatible) + @IndexFor("s") int jn = j; + } } - } } diff --git a/checker/tests/index/Issue2493.java b/checker/tests/index/Issue2493.java index 8dc274718dc..2a65c9cd930 100644 --- a/checker/tests/index/Issue2493.java +++ b/checker/tests/index/Issue2493.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.index.qual.*; public class Issue2493 { - public static void test(int a[], int @SameLen("#1") [] b) { - for (@IndexOrHigh("b") int i = 0; i < a.length; i++) {} - } + public static void test(int a[], int @SameLen("#1") [] b) { + for (@IndexOrHigh("b") int i = 0; i < a.length; i++) {} + } } diff --git a/checker/tests/index/Issue2494.java b/checker/tests/index/Issue2494.java index a4bf87bc234..9ad50bca77c 100644 --- a/checker/tests/index/Issue2494.java +++ b/checker/tests/index/Issue2494.java @@ -8,20 +8,20 @@ public final class Issue2494 { - static final long @MinLen(1) [] factorials = { - 1L, - 1L, - 1L * 2, - 1L * 2 * 3, - 1L * 2 * 3 * 4, - 1L * 2 * 3 * 4 * 5, - 1L * 2 * 3 * 4 * 5 * 6, - 1L * 2 * 3 * 4 * 5 * 6 * 7 - }; + static final long @MinLen(1) [] factorials = { + 1L, + 1L, + 1L * 2, + 1L * 2 * 3, + 1L * 2 * 3 * 4, + 1L * 2 * 3 * 4 * 5, + 1L * 2 * 3 * 4 * 5 * 6, + 1L * 2 * 3 * 4 * 5 * 6 * 7 + }; - static void binomialA( - @NonNegative @LTLengthOf("Issue2494.factorials") int n, - @NonNegative @LessThan("#1 + 1") int k) { - @IndexFor("factorials") int j = k; - } + static void binomialA( + @NonNegative @LTLengthOf("Issue2494.factorials") int n, + @NonNegative @LessThan("#1 + 1") int k) { + @IndexFor("factorials") int j = k; + } } diff --git a/checker/tests/index/Issue2505.java b/checker/tests/index/Issue2505.java index 5318f5b3626..e20533009ac 100644 --- a/checker/tests/index/Issue2505.java +++ b/checker/tests/index/Issue2505.java @@ -1,10 +1,10 @@ import org.checkerframework.common.value.qual.MinLen; public class Issue2505 { - public static void warningIfStatement(int @MinLen(1) [] a) { - int i = a.length; - if (--i >= 0) { - a[i] = 0; + public static void warningIfStatement(int @MinLen(1) [] a) { + int i = a.length; + if (--i >= 0) { + a[i] = 0; + } } - } } diff --git a/checker/tests/index/Issue2613.java b/checker/tests/index/Issue2613.java index 0dc7603e917..ac47cc6bb5e 100644 --- a/checker/tests/index/Issue2613.java +++ b/checker/tests/index/Issue2613.java @@ -3,22 +3,22 @@ public class Issue2613 { - private static final String STRING_CONSTANT = "Hello"; + private static final String STRING_CONSTANT = "Hello"; - void integerConstant() { - require_lt(0, Integer.MAX_VALUE); - } + void integerConstant() { + require_lt(0, Integer.MAX_VALUE); + } - void StringConstant() { - require_lt(0, STRING_CONSTANT); - } + void StringConstant() { + require_lt(0, STRING_CONSTANT); + } - void require_lt(@LessThan("#2") int a, int b) {} + void require_lt(@LessThan("#2") int a, int b) {} - void require_lt(@LTLengthOf("#2") int a, String b) {} + void require_lt(@LTLengthOf("#2") int a, String b) {} - void method(@LessThan("Integer.MAX_VALUE") long x, @LessThan("Integer.MAX_VALUE") long y) { - x = y; - @LessThan("2147483647") long z = y; - } + void method(@LessThan("Integer.MAX_VALUE") long x, @LessThan("Integer.MAX_VALUE") long y) { + x = y; + @LessThan("2147483647") long z = y; + } } diff --git a/checker/tests/index/Issue2629.java b/checker/tests/index/Issue2629.java index 851c8accc90..ea16381a0c3 100644 --- a/checker/tests/index/Issue2629.java +++ b/checker/tests/index/Issue2629.java @@ -4,7 +4,7 @@ import org.checkerframework.checker.index.qual.LessThan; public class Issue2629 { - @LessThan("#1 + 1") int test(int a) { - return a; - } + @LessThan("#1 + 1") int test(int a) { + return a; + } } diff --git a/checker/tests/index/Issue3207.java b/checker/tests/index/Issue3207.java index 01b6e306d66..b2f72a87e2c 100644 --- a/checker/tests/index/Issue3207.java +++ b/checker/tests/index/Issue3207.java @@ -5,16 +5,16 @@ public class Issue3207 { - void m(int @MinLen(1) [] arr) { - @LTLengthOf("arr") int j = 0; - } + void m(int @MinLen(1) [] arr) { + @LTLengthOf("arr") int j = 0; + } - void m2(int @MinLen(1) [] @MinLen(1) [] arr) { - @LTLengthOf("arr[0]") int j = 0; - } + void m2(int @MinLen(1) [] @MinLen(1) [] arr) { + @LTLengthOf("arr[0]") int j = 0; + } - void m3(int @MinLen(1) [] @MinLen(1) [] arr) { - int @MinLen(1) [] arr0 = arr[0]; - @LTLengthOf("arr0") int j = 0; - } + void m3(int @MinLen(1) [] @MinLen(1) [] arr) { + int @MinLen(1) [] arr0 = arr[0]; + @LTLengthOf("arr0") int j = 0; + } } diff --git a/checker/tests/index/Issue3224.java b/checker/tests/index/Issue3224.java index fa042b62c3e..d37a6606c57 100644 --- a/checker/tests/index/Issue3224.java +++ b/checker/tests/index/Issue3224.java @@ -1,40 +1,41 @@ // Test case for https://tinyurl.com/cfissue/3224 -import java.util.Arrays; import org.checkerframework.common.value.qual.IntRange; import org.checkerframework.common.value.qual.MinLen; +import java.util.Arrays; + public class Issue3224 { - static class Arrays { - static String[] copyOf(String[] args, int length) { - return args; + static class Arrays { + static String[] copyOf(String[] args, int length) { + return args; + } + } + + public static void m1(String @MinLen(1) [] args) { + int i = 4; + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, i); + } + + public static void m2(String @MinLen(1) [] args) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); + } + + public static void m3(String @MinLen(1) ... args) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); + } + + public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, len); + } + + public static void m5(String @MinLen(1) [] args, String[] otherArray) { + // :: error: assignment.type.incompatible + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, otherArray.length); + } + + public static void m6(String @MinLen(1) [] args) { + // :: error: assignment.type.incompatible + String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length); } - } - - public static void m1(String @MinLen(1) [] args) { - int i = 4; - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, i); - } - - public static void m2(String @MinLen(1) [] args) { - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); - } - - public static void m3(String @MinLen(1) ... args) { - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); - } - - public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) { - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, len); - } - - public static void m5(String @MinLen(1) [] args, String[] otherArray) { - // :: error: assignment.type.incompatible - String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, otherArray.length); - } - - public static void m6(String @MinLen(1) [] args) { - // :: error: assignment.type.incompatible - String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length); - } } diff --git a/checker/tests/index/Issue5471.java b/checker/tests/index/Issue5471.java index 9de1bf61766..9f28c2c9150 100644 --- a/checker/tests/index/Issue5471.java +++ b/checker/tests/index/Issue5471.java @@ -3,20 +3,20 @@ import org.checkerframework.checker.index.qual.IndexFor; public class Issue5471 { - private static boolean atTheBeginning(@IndexFor("#2") int index, String line) { - return (index == 0); - } + private static boolean atTheBeginning(@IndexFor("#2") int index, String line) { + return (index == 0); + } - private static boolean hasDoubleQuestionMarkAtTheBeginning(String line) { - int i = line.indexOf("??"); - if (i != -1) { - return (atTheBeginning(i, line)); + private static boolean hasDoubleQuestionMarkAtTheBeginning(String line) { + int i = line.indexOf("??"); + if (i != -1) { + return (atTheBeginning(i, line)); + } + return false; } - return false; - } - public static void main(String[] args) { - String x = "Hello?World, this is our new program"; - if (hasDoubleQuestionMarkAtTheBeginning(x)) System.out.println("TRUE"); - } + public static void main(String[] args) { + String x = "Hello?World, this is our new program"; + if (hasDoubleQuestionMarkAtTheBeginning(x)) System.out.println("TRUE"); + } } diff --git a/checker/tests/index/Issue58Minimization.java b/checker/tests/index/Issue58Minimization.java index d384bbee93a..1f8fcb98348 100644 --- a/checker/tests/index/Issue58Minimization.java +++ b/checker/tests/index/Issue58Minimization.java @@ -8,55 +8,55 @@ public class Issue58Minimization { - void test(@GTENegativeOne int x) { - int z; - if ((z = x) != -1) { - @NonNegative int y = z; + void test(@GTENegativeOne int x) { + int z; + if ((z = x) != -1) { + @NonNegative int y = z; + } + if ((z = x) != 1) { + // :: error: (assignment.type.incompatible) + @NonNegative int y = z; + } } - if ((z = x) != 1) { - // :: error: (assignment.type.incompatible) - @NonNegative int y = z; - } - } - void test2(@NonNegative int x) { - int z; - if ((z = x) != 0) { - @Positive int y = z; - } - if ((z = x) == 0) { - // do nothing - int y = 5; - } else { - @Positive int y = x; + void test2(@NonNegative int x) { + int z; + if ((z = x) != 0) { + @Positive int y = z; + } + if ((z = x) == 0) { + // do nothing + int y = 5; + } else { + @Positive int y = x; + } } - } - void ubc_test(int[] a, @LTEqLengthOf("#1") int x) { - int z; - if ((z = x) != a.length) { - @LTLengthOf("a") int y = z; + void ubc_test(int[] a, @LTEqLengthOf("#1") int x) { + int z; + if ((z = x) != a.length) { + @LTLengthOf("a") int y = z; + } } - } - void samelen_test(int[] a, int[] c) { - int[] b; - if ((b = a) == c) { - int @SameLen({"a", "b", "c"}) [] d = b; + void samelen_test(int[] a, int[] c) { + int[] b; + if ((b = a) == c) { + int @SameLen({"a", "b", "c"}) [] d = b; + } } - } - void minlen_test(int[] a, int @MinLen(1) [] c) { - int[] b; - if ((b = a) == c) { - int @MinLen(1) [] d = b; + void minlen_test(int[] a, int @MinLen(1) [] c) { + int[] b; + if ((b = a) == c) { + int @MinLen(1) [] d = b; + } } - } - void minlen_test2(int[] a, int x) { - int one = 1; - if ((x = one) == a.length) { - int @MinLen(1) [] b = a; + void minlen_test2(int[] a, int x) { + int one = 1; + if ((x = one) == a.length) { + int @MinLen(1) [] b = a; + } } - } } diff --git a/checker/tests/index/Issue60.java b/checker/tests/index/Issue60.java index 7c678b0ab86..eddfb9dc238 100644 --- a/checker/tests/index/Issue60.java +++ b/checker/tests/index/Issue60.java @@ -5,16 +5,16 @@ public class Issue60 { - public static int[] fn_compose(@IndexFor("#2") int[] a, int[] b) { - int[] result = new int[a.length]; - for (int i = 0; i < a.length; i++) { - int inner = a[i]; - if (inner == -1) { - result[i] = -1; - } else { - result[i] = b[inner]; - } + public static int[] fn_compose(@IndexFor("#2") int[] a, int[] b) { + int[] result = new int[a.length]; + for (int i = 0; i < a.length; i++) { + int inner = a[i]; + if (inner == -1) { + result[i] = -1; + } else { + result[i] = b[inner]; + } + } + return result; } - return result; - } } diff --git a/checker/tests/index/IteratorVoid.java b/checker/tests/index/IteratorVoid.java index db77a3f2036..36f7fc80fdd 100644 --- a/checker/tests/index/IteratorVoid.java +++ b/checker/tests/index/IteratorVoid.java @@ -4,10 +4,10 @@ // that Void is given the Positive type. public class IteratorVoid { - T next1; - Iterator itor1; + T next1; + Iterator itor1; - private void setnext1() { - next1 = itor1.hasNext() ? itor1.next() : null; - } + private void setnext1() { + next1 = itor1.hasNext() ? itor1.next() : null; + } } diff --git a/checker/tests/index/JavaxAnnotationNonnegative.java b/checker/tests/index/JavaxAnnotationNonnegative.java index 36c1300c66d..044508fd3dc 100644 --- a/checker/tests/index/JavaxAnnotationNonnegative.java +++ b/checker/tests/index/JavaxAnnotationNonnegative.java @@ -2,9 +2,9 @@ public class JavaxAnnotationNonnegative { - public static void test(@javax.annotation.Nonnegative int y) { - get(y); - } + public static void test(@javax.annotation.Nonnegative int y) { + get(y); + } - public static void get(@org.checkerframework.checker.index.qual.NonNegative int x) {} + public static void get(@org.checkerframework.checker.index.qual.NonNegative int x) {} } diff --git a/checker/tests/index/Kelloggm225.java b/checker/tests/index/Kelloggm225.java index 1cb54bac9ea..0bc334b6a53 100644 --- a/checker/tests/index/Kelloggm225.java +++ b/checker/tests/index/Kelloggm225.java @@ -2,12 +2,12 @@ import org.checkerframework.common.value.qual.*; public class Kelloggm225 { - void method(int @MinLen(1) [] bar) { - foo(bar, 0, bar.length); - } + void method(int @MinLen(1) [] bar) { + foo(bar, 0, bar.length); + } - void foo( - int @MinLen(1) [] bar, - @IndexFor("#1") @LessThan("#3") int start, - @IndexOrHigh("#1") int end) {} + void foo( + int @MinLen(1) [] bar, + @IndexFor("#1") @LessThan("#3") int start, + @IndexOrHigh("#1") int end) {} } diff --git a/checker/tests/index/Kelloggm228.java b/checker/tests/index/Kelloggm228.java index 4be1b6b91a4..3cc97b32389 100644 --- a/checker/tests/index/Kelloggm228.java +++ b/checker/tests/index/Kelloggm228.java @@ -3,12 +3,13 @@ import org.checkerframework.checker.index.qual.Positive; public class Kelloggm228 { - public void subList( - @IndexOrHigh("this") @LessThan("#2 + 1") int fromIndex, @IndexOrHigh("this") int toIndex) { - if (fromIndex == toIndex) { - return; - } + public void subList( + @IndexOrHigh("this") @LessThan("#2 + 1") int fromIndex, + @IndexOrHigh("this") int toIndex) { + if (fromIndex == toIndex) { + return; + } - @Positive int x = toIndex; - } + @Positive int x = toIndex; + } } diff --git a/checker/tests/index/LBCSubtyping.java b/checker/tests/index/LBCSubtyping.java index 0facc88224d..3ef5a1df2dc 100644 --- a/checker/tests/index/LBCSubtyping.java +++ b/checker/tests/index/LBCSubtyping.java @@ -5,42 +5,42 @@ public class LBCSubtyping { - void foo() { + void foo() { - @GTENegativeOne int i = -1; + @GTENegativeOne int i = -1; - @LowerBoundUnknown int j = i; + @LowerBoundUnknown int j = i; - int k = -4; + int k = -4; - // not this one though - // :: error: (assignment.type.incompatible) - @GTENegativeOne int l = k; + // not this one though + // :: error: (assignment.type.incompatible) + @GTENegativeOne int l = k; - @NonNegative int n = 0; + @NonNegative int n = 0; - @Positive int a = 1; + @Positive int a = 1; - // check that everything is aboveboard - j = a; - j = n; - l = n; - n = a; + // check that everything is aboveboard + j = a; + j = n; + l = n; + n = a; - // error cases + // error cases - // :: error: (assignment.type.incompatible) - @NonNegative int p = i; - // :: error: (assignment.type.incompatible) - @Positive int b = i; + // :: error: (assignment.type.incompatible) + @NonNegative int p = i; + // :: error: (assignment.type.incompatible) + @Positive int b = i; - // :: error: (assignment.type.incompatible) - @NonNegative int r = k; - // :: error: (assignment.type.incompatible) - @Positive int c = k; + // :: error: (assignment.type.incompatible) + @NonNegative int r = k; + // :: error: (assignment.type.incompatible) + @Positive int c = k; - // :: error: (assignment.type.incompatible) - @Positive int d = r; - } + // :: error: (assignment.type.incompatible) + @Positive int d = r; + } } // a comment diff --git a/checker/tests/index/LTLDivide.java b/checker/tests/index/LTLDivide.java index d3bd176e74f..631c24cfa92 100644 --- a/checker/tests/index/LTLDivide.java +++ b/checker/tests/index/LTLDivide.java @@ -2,25 +2,25 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class LTLDivide { - int[] test(int[] array) { - // @LTLengthOf("array") int len = array.length / 2; - int len = array.length / 2; - int[] arr = new int[len]; - for (int a = 0; a < len; a++) { - arr[a] = array[a]; + int[] test(int[] array) { + // @LTLengthOf("array") int len = array.length / 2; + int len = array.length / 2; + int[] arr = new int[len]; + for (int a = 0; a < len; a++) { + arr[a] = array[a]; + } + return arr; } - return arr; - } - void test2(int[] array) { - int len = array.length; - int lenM1 = array.length - 1; - int lenP1 = array.length + 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int x = len / 2; - @LTLengthOf("array") int y = lenM1 / 3; - @LTEqLengthOf("array") int z = len / 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int w = lenP1 / 2; - } + void test2(int[] array) { + int len = array.length; + int lenM1 = array.length - 1; + int lenP1 = array.length + 1; + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int x = len / 2; + @LTLengthOf("array") int y = lenM1 / 3; + @LTEqLengthOf("array") int z = len / 1; + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int w = lenP1 / 2; + } } diff --git a/checker/tests/index/LTLengthOfPostcondition.java b/checker/tests/index/LTLengthOfPostcondition.java index 0c79595e2c8..8ec232b8372 100644 --- a/checker/tests/index/LTLengthOfPostcondition.java +++ b/checker/tests/index/LTLengthOfPostcondition.java @@ -1,42 +1,47 @@ -import java.util.Arrays; import org.checkerframework.checker.index.qual.EnsuresLTLengthOf; import org.checkerframework.checker.index.qual.EnsuresLTLengthOfIf; import org.checkerframework.checker.index.qual.LTEqLengthOf; import org.checkerframework.checker.index.qual.NonNegative; +import java.util.Arrays; + public class LTLengthOfPostcondition { - Object[] array; - - @NonNegative @LTEqLengthOf("array") int end; - - @EnsuresLTLengthOf(value = "end", targetValue = "array", offset = "#1 - 1") - public void shiftIndex(@NonNegative int x) { - int newEnd = end - x; - if (newEnd < 0) throw new RuntimeException(); - end = newEnd; - } - - public void useShiftIndex(@NonNegative int x) { - // :: error: (argument.type.incompatible) - Arrays.fill(array, end, end + x, null); - shiftIndex(x); - Arrays.fill(array, end, end + x, null); - } - - @EnsuresLTLengthOfIf(expression = "end", result = true, targetValue = "array", offset = "#1 - 1") - public boolean tryShiftIndex(@NonNegative int x) { - int newEnd = end - x; - if (newEnd < 0) { - return false; + Object[] array; + + @NonNegative @LTEqLengthOf("array") int end; + + @EnsuresLTLengthOf(value = "end", targetValue = "array", offset = "#1 - 1") + public void shiftIndex(@NonNegative int x) { + int newEnd = end - x; + if (newEnd < 0) throw new RuntimeException(); + end = newEnd; + } + + public void useShiftIndex(@NonNegative int x) { + // :: error: (argument.type.incompatible) + Arrays.fill(array, end, end + x, null); + shiftIndex(x); + Arrays.fill(array, end, end + x, null); + } + + @EnsuresLTLengthOfIf( + expression = "end", + result = true, + targetValue = "array", + offset = "#1 - 1") + public boolean tryShiftIndex(@NonNegative int x) { + int newEnd = end - x; + if (newEnd < 0) { + return false; + } + end = newEnd; + return true; } - end = newEnd; - return true; - } - public void useTryShiftIndex(@NonNegative int x) { - if (tryShiftIndex(x)) { - Arrays.fill(array, end, end + x, null); + public void useTryShiftIndex(@NonNegative int x) { + if (tryShiftIndex(x)) { + Arrays.fill(array, end, end + x, null); + } } - } } diff --git a/checker/tests/index/LengthOfArrayMinusOne.java b/checker/tests/index/LengthOfArrayMinusOne.java index 713002091d4..e575024e4d7 100644 --- a/checker/tests/index/LengthOfArrayMinusOne.java +++ b/checker/tests/index/LengthOfArrayMinusOne.java @@ -1,10 +1,10 @@ public class LengthOfArrayMinusOne { - void test(int[] arr) { - // :: error: (array.access.unsafe.low) - int i = arr[arr.length - 1]; + void test(int[] arr) { + // :: error: (array.access.unsafe.low) + int i = arr[arr.length - 1]; - if (arr.length > 0) { - int j = arr[arr.length - 1]; + if (arr.length > 0) { + int j = arr[arr.length - 1]; + } } - } } diff --git a/checker/tests/index/LengthOfTest.java b/checker/tests/index/LengthOfTest.java index ba0c0e05a8a..68f79675aca 100644 --- a/checker/tests/index/LengthOfTest.java +++ b/checker/tests/index/LengthOfTest.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.index.qual.*; public class LengthOfTest { - void foo(int[] a, @LengthOf("#1") int x) { - @IndexOrHigh("a") int y = x; - // :: error: (assignment.type.incompatible) - @IndexFor("a") int w = x; - @LengthOf("a") int z = a.length; - } + void foo(int[] a, @LengthOf("#1") int x) { + @IndexOrHigh("a") int y = x; + // :: error: (assignment.type.incompatible) + @IndexFor("a") int w = x; + @LengthOf("a") int z = a.length; + } } diff --git a/checker/tests/index/LengthTransfer.java b/checker/tests/index/LengthTransfer.java index f8ad5db91cc..c7e5bbf6380 100644 --- a/checker/tests/index/LengthTransfer.java +++ b/checker/tests/index/LengthTransfer.java @@ -1,21 +1,21 @@ public class LengthTransfer { - void exceptional_control_flow(int[] a) { - if (a.length == 0) { - throw new IllegalArgumentException(); + void exceptional_control_flow(int[] a) { + if (a.length == 0) { + throw new IllegalArgumentException(); + } + int i = a[0]; } - int i = a[0]; - } - void equal_to_return(int[] a) { - if (a.length == 0) { - return; + void equal_to_return(int[] a) { + if (a.length == 0) { + return; + } + int i = a[0]; } - int i = a[0]; - } - void gt_check(int[] a) { - if (a.length > 0) { - int i = a[0]; + void gt_check(int[] a) { + if (a.length > 0) { + int i = a[0]; + } } - } } diff --git a/checker/tests/index/LengthTransfer2.java b/checker/tests/index/LengthTransfer2.java index 108a83c452f..b4b9ef91fe5 100644 --- a/checker/tests/index/LengthTransfer2.java +++ b/checker/tests/index/LengthTransfer2.java @@ -3,17 +3,17 @@ import org.checkerframework.common.value.qual.*; public class LengthTransfer2 { - public static void main(String[] args) { - if (args.length != 2) { - System.err.println("Needs 2 arguments, got " + args.length); - System.exit(1); + public static void main(String[] args) { + if (args.length != 2) { + System.err.println("Needs 2 arguments, got " + args.length); + System.exit(1); + } + int limit = Integer.parseInt(args[0]); + int period = Integer.parseInt(args[1]); } - int limit = Integer.parseInt(args[0]); - int period = Integer.parseInt(args[1]); - } - void m(String @ArrayLen(2) [] args) { - int limit = Integer.parseInt(args[0]); - int period = Integer.parseInt(args[1]); - } + void m(String @ArrayLen(2) [] args) { + int limit = Integer.parseInt(args[0]); + int period = Integer.parseInt(args[1]); + } } diff --git a/checker/tests/index/LengthTransferForMinLen.java b/checker/tests/index/LengthTransferForMinLen.java index e1f62cd9606..21d9879f76d 100644 --- a/checker/tests/index/LengthTransferForMinLen.java +++ b/checker/tests/index/LengthTransferForMinLen.java @@ -1,23 +1,23 @@ import org.checkerframework.common.value.qual.MinLen; public class LengthTransferForMinLen { - void exceptional_control_flow(int[] a) { - if (a.length == 0) { - throw new IllegalArgumentException(); + void exceptional_control_flow(int[] a) { + if (a.length == 0) { + throw new IllegalArgumentException(); + } + int @MinLen(1) [] b = a; } - int @MinLen(1) [] b = a; - } - void equal_to_return(int[] a) { - if (a.length == 0) { - return; + void equal_to_return(int[] a) { + if (a.length == 0) { + return; + } + int @MinLen(1) [] b = a; } - int @MinLen(1) [] b = a; - } - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; + } } - } } diff --git a/checker/tests/index/LessThanBug.java b/checker/tests/index/LessThanBug.java index b1ad7a9c1f0..dd557f22311 100644 --- a/checker/tests/index/LessThanBug.java +++ b/checker/tests/index/LessThanBug.java @@ -4,12 +4,12 @@ public class LessThanBug { - void call() { - bug(30, 1); - } + void call() { + bug(30, 1); + } - void bug(@IntRange(to = 42) int a, @IntVal(1) int c) { - // :: error: (assignment.type.incompatible) - @LessThan("c") int x = a; - } + void bug(@IntRange(to = 42) int a, @IntVal(1) int c) { + // :: error: (assignment.type.incompatible) + @LessThan("c") int x = a; + } } diff --git a/checker/tests/index/LessThanConstantAddition.java b/checker/tests/index/LessThanConstantAddition.java index 1986089e4c0..c1fbb492350 100644 --- a/checker/tests/index/LessThanConstantAddition.java +++ b/checker/tests/index/LessThanConstantAddition.java @@ -2,9 +2,9 @@ public class LessThanConstantAddition { - public static void checkedPow(int b) { - if (b <= 2) { - int c = (int) b; + public static void checkedPow(int b) { + if (b <= 2) { + int c = (int) b; + } } - } } diff --git a/checker/tests/index/LessThanCustomCollection.java b/checker/tests/index/LessThanCustomCollection.java index 0568250caf7..35699f956cb 100644 --- a/checker/tests/index/LessThanCustomCollection.java +++ b/checker/tests/index/LessThanCustomCollection.java @@ -12,65 +12,67 @@ // This class has a similar implementation to several Immutable*Array class in Guava, // such as com.google.common.primitives.ImmutableDoubleArray. public class LessThanCustomCollection { - // This object is a subset of array. So, if something is an index for "this" - // then it is >= start and < end. - private final int[] array; - private final @IndexOrHigh("array") @LessThan("end + 1") int start; - private final @LTLengthOf( - value = {"array", "this"}, - offset = {" - 1", "- start"}) int end; + // This object is a subset of array. So, if something is an index for "this" + // then it is >= start and < end. + private final int[] array; + private final @IndexOrHigh("array") @LessThan("end + 1") int start; + private final @LTLengthOf( + value = {"array", "this"}, + offset = {" - 1", "- start"}) int end; - private LessThanCustomCollection(int[] array) { - this(array, 0, array.length); - } + private LessThanCustomCollection(int[] array) { + this(array, 0, array.length); + } - private LessThanCustomCollection( - int[] array, @IndexOrHigh("#1") @LessThan("#3 + 1") int start, @IndexOrHigh("#1") int end) { - this.array = array; - // can't est. that end - start is the length of this. - // :: error: (assignment.type.incompatible) - this.end = end; - // start is @LessThan(end + 1) but should be @LessThan(this.end + 1) - // :: error: (assignment.type.incompatible) - this.start = start; - } + private LessThanCustomCollection( + int[] array, + @IndexOrHigh("#1") @LessThan("#3 + 1") int start, + @IndexOrHigh("#1") int end) { + this.array = array; + // can't est. that end - start is the length of this. + // :: error: (assignment.type.incompatible) + this.end = end; + // start is @LessThan(end + 1) but should be @LessThan(this.end + 1) + // :: error: (assignment.type.incompatible) + this.start = start; + } - @Pure - public @LengthOf("this") int length() { - return end - start; - } + @Pure + public @LengthOf("this") int length() { + return end - start; + } - public double get(@IndexFor("this") int index) { - // TODO: This is a bug. - // :: error: (argument.type.incompatible) - checkElementIndex(index, length()); - // Because index is an index for "this" the index + start - // must be an index for array. - // :: error: (array.access.unsafe.high) - return array[start + index]; - } + public double get(@IndexFor("this") int index) { + // TODO: This is a bug. + // :: error: (argument.type.incompatible) + checkElementIndex(index, length()); + // Because index is an index for "this" the index + start + // must be an index for array. + // :: error: (array.access.unsafe.high) + return array[start + index]; + } - public static @NonNegative int checkElementIndex( - @LessThan("#2") @NonNegative int index, @NonNegative int size) { - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(); + public static @NonNegative int checkElementIndex( + @LessThan("#2") @NonNegative int index, @NonNegative int size) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(); + } + return index; } - return index; - } - public @IndexOrLow("this") int indexOf(double target) { - for (int i = start; i < end; i++) { - if (areEqual(array[i], target)) { - // Don't know that is is greater than start. - // :: error: (return.type.incompatible) - return i - start; - } + public @IndexOrLow("this") int indexOf(double target) { + for (int i = start; i < end; i++) { + if (areEqual(array[i], target)) { + // Don't know that is is greater than start. + // :: error: (return.type.incompatible) + return i - start; + } + } + return -1; } - return -1; - } - static boolean areEqual(int item, double target) { - // implementation not relevant - return true; - } + static boolean areEqual(int item, double target) { + // implementation not relevant + return true; + } } diff --git a/checker/tests/index/LessThanDec.java b/checker/tests/index/LessThanDec.java index 17bfebc9d91..782119ad5b6 100644 --- a/checker/tests/index/LessThanDec.java +++ b/checker/tests/index/LessThanDec.java @@ -3,13 +3,13 @@ import org.checkerframework.checker.index.qual.LessThan; public class LessThanDec { - private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( - short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } + private static @IndexOrLow("#1") @LessThan("#4") int lastIndexOf( + short[] array, short target, @IndexOrHigh("#1") int start, @IndexOrHigh("#1") int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; } - return -1; - } } diff --git a/checker/tests/index/LessThanFloat.java b/checker/tests/index/LessThanFloat.java index 519f8c9de1a..9136a5da364 100644 --- a/checker/tests/index/LessThanFloat.java +++ b/checker/tests/index/LessThanFloat.java @@ -1,62 +1,62 @@ import org.checkerframework.checker.index.qual.LessThan; public class LessThanFloat { - int bigger; + int bigger; - @LessThan("bigger") byte b; + @LessThan("bigger") byte b; - @LessThan("bigger") short s; + @LessThan("bigger") short s; - @LessThan("bigger") int i; + @LessThan("bigger") int i; - @LessThan("bigger") long l; + @LessThan("bigger") long l; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") float f; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") float f; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") double d; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") double d; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") boolean bool; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") boolean bool; - @LessThan("bigger") char c; + @LessThan("bigger") char c; - @LessThan("bigger") Byte bBoxed; + @LessThan("bigger") Byte bBoxed; - @LessThan("bigger") Short sBoxed; + @LessThan("bigger") Short sBoxed; - @LessThan("bigger") Integer iBoxed; + @LessThan("bigger") Integer iBoxed; - @LessThan("bigger") Long lBoxed; + @LessThan("bigger") Long lBoxed; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") Float fBoxed; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") Float fBoxed; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") Double dBoxed; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") Double dBoxed; - // :: error: (anno.on.irrelevant) - @LessThan("bigger") Boolean boolBoxed; + // :: error: (anno.on.irrelevant) + @LessThan("bigger") Boolean boolBoxed; - @LessThan("bigger") Character cBoxed; + @LessThan("bigger") Character cBoxed; - java.lang.@LessThan("bigger") Byte bBoxed2; + java.lang.@LessThan("bigger") Byte bBoxed2; - java.lang.@LessThan("bigger") Short sBoxed2; + java.lang.@LessThan("bigger") Short sBoxed2; - java.lang.@LessThan("bigger") Integer iBoxed2; + java.lang.@LessThan("bigger") Integer iBoxed2; - java.lang.@LessThan("bigger") Long lBoxed2; + java.lang.@LessThan("bigger") Long lBoxed2; - // :: error: (anno.on.irrelevant) - java.lang.@LessThan("bigger") Float fBoxed2; + // :: error: (anno.on.irrelevant) + java.lang.@LessThan("bigger") Float fBoxed2; - // :: error: (anno.on.irrelevant) - java.lang.@LessThan("bigger") Double dBoxed2; + // :: error: (anno.on.irrelevant) + java.lang.@LessThan("bigger") Double dBoxed2; - // :: error: (anno.on.irrelevant) - java.lang.@LessThan("bigger") Boolean boolBoxed2; + // :: error: (anno.on.irrelevant) + java.lang.@LessThan("bigger") Boolean boolBoxed2; - java.lang.@LessThan("bigger") Character cBoxed2; + java.lang.@LessThan("bigger") Character cBoxed2; } diff --git a/checker/tests/index/LessThanFloatLiteral.java b/checker/tests/index/LessThanFloatLiteral.java index 659294de815..5242da9c202 100644 --- a/checker/tests/index/LessThanFloatLiteral.java +++ b/checker/tests/index/LessThanFloatLiteral.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.index.qual.LessThan; class LessThanFloatLiteral { - void test(int x) { - if (1.0 > x) { - // TODO: It might be nice to handle comparisons against floats, - // but an array index is not generally compared to a float. - // :: error: assignment.type.incompatible - @LessThan("1") int y = x; + void test(int x) { + if (1.0 > x) { + // TODO: It might be nice to handle comparisons against floats, + // but an array index is not generally compared to a float. + // :: error: assignment.type.incompatible + @LessThan("1") int y = x; + } } - } } diff --git a/checker/tests/index/LessThanLen.java b/checker/tests/index/LessThanLen.java index fbb6475d075..432b30d246b 100644 --- a/checker/tests/index/LessThanLen.java +++ b/checker/tests/index/LessThanLen.java @@ -3,55 +3,55 @@ public class LessThanLen { - public static void m1() { - int[] shorter = new int[5]; - int[] longer = new int[shorter.length * 2]; - for (int i = 0; i < shorter.length; i++) { - longer[i] = shorter[i]; + public static void m1() { + int[] shorter = new int[5]; + int[] longer = new int[shorter.length * 2]; + for (int i = 0; i < shorter.length; i++) { + longer[i] = shorter[i]; + } } - } - public static void m2(int @MinLen(1) [] shorter) { - int[] longer = new int[shorter.length * 2]; - for (int i = 0; i < shorter.length; i++) { - longer[i] = shorter[i]; + public static void m2(int @MinLen(1) [] shorter) { + int[] longer = new int[shorter.length * 2]; + for (int i = 0; i < shorter.length; i++) { + longer[i] = shorter[i]; + } } - } - public static void m3(int[] shorter) { - int[] longer = new int[shorter.length + 1]; - for (int i = 0; i < shorter.length; i++) { - longer[i] = shorter[i]; + public static void m3(int[] shorter) { + int[] longer = new int[shorter.length + 1]; + for (int i = 0; i < shorter.length; i++) { + longer[i] = shorter[i]; + } + } + + public static void m4(int @MinLen(1) [] shorter) { + int[] longer = new int[shorter.length * 1]; + // :: error: (assignment.type.incompatible) + @LTLengthOf("longer") int x = shorter.length; + @LTEqLengthOf("longer") int y = shorter.length; + } + + public static void m5(int[] shorter) { + // :: error: (array.length.negative) + int[] longer = new int[shorter.length * -1]; + // :: error: (assignment.type.incompatible) + @LTLengthOf("longer") int x = shorter.length; + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("longer") int y = shorter.length; + } + + public static void m6(int @MinLen(1) [] shorter) { + int[] longer = new int[4 * shorter.length]; + // TODO: enable when https://github.com/kelloggm/checker-framework/issues/211 is fixed + // // :: error: (assignment.type.incompatible) + // @LTLengthOf("longer") int x = shorter.length; + @LTEqLengthOf("longer") int y = shorter.length; + } + + public static void m7(int @MinLen(1) [] shorter) { + int[] longer = new int[4 * shorter.length]; + @LTLengthOf("longer") int x = shorter.length; + @LTEqLengthOf("longer") int y = shorter.length; } - } - - public static void m4(int @MinLen(1) [] shorter) { - int[] longer = new int[shorter.length * 1]; - // :: error: (assignment.type.incompatible) - @LTLengthOf("longer") int x = shorter.length; - @LTEqLengthOf("longer") int y = shorter.length; - } - - public static void m5(int[] shorter) { - // :: error: (array.length.negative) - int[] longer = new int[shorter.length * -1]; - // :: error: (assignment.type.incompatible) - @LTLengthOf("longer") int x = shorter.length; - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("longer") int y = shorter.length; - } - - public static void m6(int @MinLen(1) [] shorter) { - int[] longer = new int[4 * shorter.length]; - // TODO: enable when https://github.com/kelloggm/checker-framework/issues/211 is fixed - // // :: error: (assignment.type.incompatible) - // @LTLengthOf("longer") int x = shorter.length; - @LTEqLengthOf("longer") int y = shorter.length; - } - - public static void m7(int @MinLen(1) [] shorter) { - int[] longer = new int[4 * shorter.length]; - @LTLengthOf("longer") int x = shorter.length; - @LTEqLengthOf("longer") int y = shorter.length; - } } diff --git a/checker/tests/index/LessThanLenBug.java b/checker/tests/index/LessThanLenBug.java index 572d86c44c4..7fc1a40d551 100644 --- a/checker/tests/index/LessThanLenBug.java +++ b/checker/tests/index/LessThanLenBug.java @@ -2,20 +2,20 @@ import org.checkerframework.common.value.qual.MinLen; public class LessThanLenBug { - public static void m1(int[] shorter) { - int[] longer = new int[4 * shorter.length]; - // :: error: (assignment.type.incompatible) - @LTLengthOf("longer") int x = shorter.length; - int i = longer[x]; - } + public static void m1(int[] shorter) { + int[] longer = new int[4 * shorter.length]; + // :: error: (assignment.type.incompatible) + @LTLengthOf("longer") int x = shorter.length; + int i = longer[x]; + } - public static void m2(int @MinLen(1) [] shorter) { - int[] longer = new int[4 * shorter.length]; - @LTLengthOf("longer") int x = shorter.length; - int i = longer[x]; - } + public static void m2(int @MinLen(1) [] shorter) { + int[] longer = new int[4 * shorter.length]; + @LTLengthOf("longer") int x = shorter.length; + int i = longer[x]; + } - public static void main(String[] args) { - m1(new int[0]); - } + public static void main(String[] args) { + m1(new int[0]); + } } diff --git a/checker/tests/index/LessThanOrEqualTransfer.java b/checker/tests/index/LessThanOrEqualTransfer.java index 3ab28347f58..cfc7efb01d4 100644 --- a/checker/tests/index/LessThanOrEqualTransfer.java +++ b/checker/tests/index/LessThanOrEqualTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class LessThanOrEqualTransfer { - void lte_check(int[] a) { - if (1 <= a.length) { - int @MinLen(1) [] b = a; + void lte_check(int[] a) { + if (1 <= a.length) { + int @MinLen(1) [] b = a; + } } - } - void lte_bad_check(int[] a) { - if (1 <= a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void lte_bad_check(int[] a) { + if (1 <= a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/checker/tests/index/LessThanTransferTest.java b/checker/tests/index/LessThanTransferTest.java index 2ee908abe9a..bdb55226ea3 100644 --- a/checker/tests/index/LessThanTransferTest.java +++ b/checker/tests/index/LessThanTransferTest.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.MinLen; public class LessThanTransferTest { - void lt_check(int[] a) { - if (0 < a.length) { - int @MinLen(1) [] b = a; + void lt_check(int[] a) { + if (0 < a.length) { + int @MinLen(1) [] b = a; + } } - } - void lt_bad_check(int[] a) { - if (0 < a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void lt_bad_check(int[] a) { + if (0 < a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/checker/tests/index/LessThanValue.java b/checker/tests/index/LessThanValue.java index 63fa4bc8651..a59f8117d93 100644 --- a/checker/tests/index/LessThanValue.java +++ b/checker/tests/index/LessThanValue.java @@ -9,115 +9,115 @@ // Test for LessThanChecker public class LessThanValue { - void subtyping(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { - @LessThan("x") int q = a; - @LessThan({"x", "y"}) - // :: error: (assignment.type.incompatible) - int r = b; - } - - public static boolean flag; - - void lub(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { - @LessThan("x") int r = flag ? a : b; - @LessThan({"x", "y"}) - // :: error: (assignment.type.incompatible) - int s = flag ? a : b; - } - - void transitive(int a, int b, int c) { - if (a < b) { - if (b < c) { - // TODO: False positive + void subtyping(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { + @LessThan("x") int q = a; + @LessThan({"x", "y"}) // :: error: (assignment.type.incompatible) - @LessThan("c") int x = a; - } + int r = b; } - } - void calls() { - isLessThan(0, 1); - isLessThanOrEqual(0, 0); - } + public static boolean flag; - void isLessThan(@LessThan("#2") @NonNegative int start, int end) { - @NonNegative int x = end - start - 1; - @Positive int y = end - start; - } - - @NonNegative int isLessThanOrEqual(@LessThan("#2 + 1") @NonNegative int start, int end) { - return end - start; - } - - public void setMaximumItemCount(int maximum) { - if (maximum < 0) { - throw new IllegalArgumentException("Negative 'maximum' argument."); + void lub(int x, int y, @LessThan({"#1", "#2"}) int a, @LessThan("#1") int b) { + @LessThan("x") int r = flag ? a : b; + @LessThan({"x", "y"}) + // :: error: (assignment.type.incompatible) + int s = flag ? a : b; } - int count = getCount(); - if (count > maximum) { - @Positive int y = count - maximum; - @NonNegative int deleteIndex = count - maximum - 1; + + void transitive(int a, int b, int c) { + if (a < b) { + if (b < c) { + // TODO: False positive + // :: error: (assignment.type.incompatible) + @LessThan("c") int x = a; + } + } } - } - int getCount() { - throw new RuntimeException(); - } + void calls() { + isLessThan(0, 1); + isLessThanOrEqual(0, 0); + } - void method(@NonNegative int m) { - boolean[] has_modulus = new boolean[m]; - @LessThan("m") int x = foo(m); - @IndexFor("has_modulus") int rem = foo(m); - } + void isLessThan(@LessThan("#2") @NonNegative int start, int end) { + @NonNegative int x = end - start - 1; + @Positive int y = end - start; + } - @LessThan("#1") @NonNegative int foo(int in) { - throw new RuntimeException(); - } + @NonNegative int isLessThanOrEqual(@LessThan("#2 + 1") @NonNegative int start, int end) { + return end - start; + } - void test(int maximum, int count) { - if (maximum < 0) { - throw new IllegalArgumentException("Negative 'maximum' argument."); + public void setMaximumItemCount(int maximum) { + if (maximum < 0) { + throw new IllegalArgumentException("Negative 'maximum' argument."); + } + int count = getCount(); + if (count > maximum) { + @Positive int y = count - maximum; + @NonNegative int deleteIndex = count - maximum - 1; + } } - if (count > maximum) { - int deleteIndex = count - maximum - 1; - // TODO: shouldn't error - // :: error: (argument.type.incompatible) - isLessThanOrEqual(0, deleteIndex); + + int getCount() { + throw new RuntimeException(); } - } - void count(int count) { - if (count > 0) { - if (count % 2 == 1) { + void method(@NonNegative int m) { + boolean[] has_modulus = new boolean[m]; + @LessThan("m") int x = foo(m); + @IndexFor("has_modulus") int rem = foo(m); + } - } else { - // TODO: improve value checker - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int countDivMinus = count / 2 - 1; - // Reasign to update the value in the store. - countDivMinus = countDivMinus; - // :: error: (argument.type.incompatible) - isLessThan(0, countDivMinus); - isLessThanOrEqual(0, countDivMinus); - } + @LessThan("#1") @NonNegative int foo(int in) { + throw new RuntimeException(); } - } - static @NonNegative @LessThan("#2 + 1") int expandedCapacity( - @NonNegative int oldCapacity, @NonNegative int minCapacity) { - if (minCapacity < 0) { - throw new AssertionError("cannot store more than MAX_VALUE elements"); + void test(int maximum, int count) { + if (maximum < 0) { + throw new IllegalArgumentException("Negative 'maximum' argument."); + } + if (count > maximum) { + int deleteIndex = count - maximum - 1; + // TODO: shouldn't error + // :: error: (argument.type.incompatible) + isLessThanOrEqual(0, deleteIndex); + } } - // careful of overflow! - int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; // expand by %50 - if (newCapacity < minCapacity) { - newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + + void count(int count) { + if (count > 0) { + if (count % 2 == 1) { + + } else { + // TODO: improve value checker + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int countDivMinus = count / 2 - 1; + // Reasign to update the value in the store. + countDivMinus = countDivMinus; + // :: error: (argument.type.incompatible) + isLessThan(0, countDivMinus); + isLessThanOrEqual(0, countDivMinus); + } + } } - if (newCapacity < 0) { - newCapacity = Integer.MAX_VALUE; - // guaranteed to be >= newCapacity + + static @NonNegative @LessThan("#2 + 1") int expandedCapacity( + @NonNegative int oldCapacity, @NonNegative int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; // expand by %50 + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; + // guaranteed to be >= newCapacity + } + // :: error: (return.type.incompatible) + return newCapacity; } - // :: error: (return.type.incompatible) - return newCapacity; - } } diff --git a/checker/tests/index/LessThanZeroArrayLength.java b/checker/tests/index/LessThanZeroArrayLength.java index ba58d4c2ca8..ea0ccd2e11b 100644 --- a/checker/tests/index/LessThanZeroArrayLength.java +++ b/checker/tests/index/LessThanZeroArrayLength.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.LessThan; public class LessThanZeroArrayLength { - void test(int[] a) { - foo(0, a.length); - } + void test(int[] a) { + foo(0, a.length); + } - void foo(@LessThan("#2 + 1") int x, int y) {} + void foo(@LessThan("#2 + 1") int x, int y) {} } diff --git a/checker/tests/index/ListAdd.java b/checker/tests/index/ListAdd.java index 2fd3250db15..51a75b6330d 100644 --- a/checker/tests/index/ListAdd.java +++ b/checker/tests/index/ListAdd.java @@ -7,69 +7,70 @@ public class ListAdd { - List listField; + List listField; - void ListAdd(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.add(index, 4); + void ListAdd( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.add(index, 4); - // :: error: (list.access.unsafe.high) - list.add(notIndex + 1, 4); - } + // :: error: (list.access.unsafe.high) + list.add(notIndex + 1, 4); + } - int[] arr = {0}; + int[] arr = {0}; - void ListAddWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.add(index, 4); - } + void ListAddWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.add(index, 4); + } - void ListAddField() { - listField.add(listField.size() - 1, 4); - listField.add(this.listField.size() - 1, 4); - this.listField.add(listField.size() - 1, 4); - this.listField.add(this.listField.size() - 1, 4); + void ListAddField() { + listField.add(listField.size() - 1, 4); + listField.add(this.listField.size() - 1, 4); + this.listField.add(listField.size() - 1, 4); + this.listField.add(this.listField.size() - 1, 4); - // :: error: (list.access.unsafe.high) - listField.add(listField.size(), 4); - // :: error: (list.access.unsafe.high) - listField.add(this.listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.add(listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.add(this.listField.size(), 4); - } + // :: error: (list.access.unsafe.high) + listField.add(listField.size(), 4); + // :: error: (list.access.unsafe.high) + listField.add(this.listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.add(listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.add(this.listField.size(), 4); + } - void ListAddFieldUserAnnotation(@IndexFor("listField") int i) { - listField.add(i, 4); - this.listField.add(i, 4); + void ListAddFieldUserAnnotation(@IndexFor("listField") int i) { + listField.add(i, 4); + this.listField.add(i, 4); - // :: error: (list.access.unsafe.high) - listField.add(i + 4, 4); - // :: error: (list.access.unsafe.high) - this.listField.add(i + 4, 4); - } + // :: error: (list.access.unsafe.high) + listField.add(i + 4, 4); + // :: error: (list.access.unsafe.high) + this.listField.add(i + 4, 4); + } - void ListAddUserAnnotation(@IndexFor("#2") int i, List list) { - list.add(i, 4); + void ListAddUserAnnotation(@IndexFor("#2") int i, List list) { + list.add(i, 4); - // :: error: (list.access.unsafe.high) - list.add(i + 4, 4); - } + // :: error: (list.access.unsafe.high) + list.add(i + 4, 4); + } - void ListAddUpdateValue(List list) { - @LTEqLengthOf("list") int i = list.size(); - @LTLengthOf("list") int r = list.size() - 1; - list.add(0); - @LTLengthOf("list") int k = i; - @LTOMLengthOf("list") int p = r; - } + void ListAddUpdateValue(List list) { + @LTEqLengthOf("list") int i = list.size(); + @LTLengthOf("list") int r = list.size() - 1; + list.add(0); + @LTLengthOf("list") int k = i; + @LTOMLengthOf("list") int p = r; + } - void ListAddTwo(@LTEqLengthOf({"#2", "#3"}) int i, List list, List list2) { - @LTEqLengthOf({"list", "list2"}) int j = i; - list.add(0); - // :: error: (list.access.unsafe.high) - list.get(i); - // :: error: (list.access.unsafe.high) - list2.get(i); - } + void ListAddTwo(@LTEqLengthOf({"#2", "#3"}) int i, List list, List list2) { + @LTEqLengthOf({"list", "list2"}) int j = i; + list.add(0); + // :: error: (list.access.unsafe.high) + list.get(i); + // :: error: (list.access.unsafe.high) + list2.get(i); + } } diff --git a/checker/tests/index/ListAddAll.java b/checker/tests/index/ListAddAll.java index b858491f62e..8fed1ddf4d9 100644 --- a/checker/tests/index/ListAddAll.java +++ b/checker/tests/index/ListAddAll.java @@ -6,54 +6,54 @@ public class ListAddAll { - List listField; - List coll; - - void ListAddAll( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.addAll(index, coll); - - // :: error: (list.access.unsafe.high) - list.addAll(notIndex, coll); - } - - int[] arr = {0}; - - void ListAddAllWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.addAll(index, coll); - } - - void ListAddAllField() { - listField.addAll(listField.size() - 1, coll); - listField.addAll(this.listField.size() - 1, coll); - this.listField.addAll(listField.size() - 1, coll); - this.listField.addAll(this.listField.size() - 1, coll); - - // :: error: (list.access.unsafe.high) - listField.addAll(listField.size(), coll); - // :: error: (list.access.unsafe.high) - listField.addAll(this.listField.size(), coll); - // :: error: (list.access.unsafe.high) - this.listField.addAll(listField.size(), coll); - // :: error: (list.access.unsafe.high) - this.listField.addAll(this.listField.size(), coll); - } - - void ListAddAllFieldUserAnnotation(@IndexFor("listField") int i) { - listField.addAll(i, coll); - this.listField.addAll(i, coll); - - // :: error: (list.access.unsafe.high) - listField.addAll(i + 1, coll); - // :: error: (list.access.unsafe.high) - this.listField.addAll(i + 1, coll); - } - - void ListAddAllUserAnnotation(@IndexFor("#2") int i, List list) { - list.addAll(i, coll); - - // :: error: (list.access.unsafe.high) - list.addAll(i + 1, coll); - } + List listField; + List coll; + + void ListAddAll( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.addAll(index, coll); + + // :: error: (list.access.unsafe.high) + list.addAll(notIndex, coll); + } + + int[] arr = {0}; + + void ListAddAllWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.addAll(index, coll); + } + + void ListAddAllField() { + listField.addAll(listField.size() - 1, coll); + listField.addAll(this.listField.size() - 1, coll); + this.listField.addAll(listField.size() - 1, coll); + this.listField.addAll(this.listField.size() - 1, coll); + + // :: error: (list.access.unsafe.high) + listField.addAll(listField.size(), coll); + // :: error: (list.access.unsafe.high) + listField.addAll(this.listField.size(), coll); + // :: error: (list.access.unsafe.high) + this.listField.addAll(listField.size(), coll); + // :: error: (list.access.unsafe.high) + this.listField.addAll(this.listField.size(), coll); + } + + void ListAddAllFieldUserAnnotation(@IndexFor("listField") int i) { + listField.addAll(i, coll); + this.listField.addAll(i, coll); + + // :: error: (list.access.unsafe.high) + listField.addAll(i + 1, coll); + // :: error: (list.access.unsafe.high) + this.listField.addAll(i + 1, coll); + } + + void ListAddAllUserAnnotation(@IndexFor("#2") int i, List list) { + list.addAll(i, coll); + + // :: error: (list.access.unsafe.high) + list.addAll(i + 1, coll); + } } diff --git a/checker/tests/index/ListAddInfiniteLoop.java b/checker/tests/index/ListAddInfiniteLoop.java index 36a36bbd96e..6c9fbd7ceb7 100644 --- a/checker/tests/index/ListAddInfiniteLoop.java +++ b/checker/tests/index/ListAddInfiniteLoop.java @@ -2,9 +2,9 @@ public class ListAddInfiniteLoop { - void ListLoop(List list) { - while (true) { - list.add(4); + void ListLoop(List list) { + while (true) { + list.add(4); + } } - } } diff --git a/checker/tests/index/ListGet.java b/checker/tests/index/ListGet.java index 7487ab7fa81..e60cea1aef2 100644 --- a/checker/tests/index/ListGet.java +++ b/checker/tests/index/ListGet.java @@ -6,51 +6,52 @@ public class ListGet { - List listField; - int[] arr = {0}; - - void ListGet(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.get(index); - - // :: error: (list.access.unsafe.high) - list.get(notIndex); - } - - void ListGetWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.get(index); - } - - void ListGetField() { - listField.get(listField.size() - 1); - listField.get(this.listField.size() - 1); - this.listField.get(listField.size() - 1); - this.listField.get(this.listField.size() - 1); - - // :: error: (list.access.unsafe.high) - listField.get(listField.size()); - // :: error: (list.access.unsafe.high) - listField.get(this.listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.get(listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.get(this.listField.size()); - } - - void ListGetFieldUserAnnotation(@IndexFor("listField") int i) { - listField.get(i); - this.listField.get(i); - - // :: error: (list.access.unsafe.high) - listField.get(i + 1); - // :: error: (list.access.unsafe.high) - this.listField.get(i + 1); - } - - void ListGetUserAnnotation(@IndexFor("#2") int i, List list) { - list.get(i); - - // :: error: (list.access.unsafe.high) - list.get(i + 1); - } + List listField; + int[] arr = {0}; + + void ListGet( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.get(index); + + // :: error: (list.access.unsafe.high) + list.get(notIndex); + } + + void ListGetWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.get(index); + } + + void ListGetField() { + listField.get(listField.size() - 1); + listField.get(this.listField.size() - 1); + this.listField.get(listField.size() - 1); + this.listField.get(this.listField.size() - 1); + + // :: error: (list.access.unsafe.high) + listField.get(listField.size()); + // :: error: (list.access.unsafe.high) + listField.get(this.listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.get(listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.get(this.listField.size()); + } + + void ListGetFieldUserAnnotation(@IndexFor("listField") int i) { + listField.get(i); + this.listField.get(i); + + // :: error: (list.access.unsafe.high) + listField.get(i + 1); + // :: error: (list.access.unsafe.high) + this.listField.get(i + 1); + } + + void ListGetUserAnnotation(@IndexFor("#2") int i, List list) { + list.get(i); + + // :: error: (list.access.unsafe.high) + list.get(i + 1); + } } diff --git a/checker/tests/index/ListIterator.java b/checker/tests/index/ListIterator.java index 8e29d028c68..e2d85d69413 100644 --- a/checker/tests/index/ListIterator.java +++ b/checker/tests/index/ListIterator.java @@ -6,53 +6,53 @@ public class ListIterator { - List listField; - - void ListIterator( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.listIterator(index); - - // :: error: (list.access.unsafe.high) - list.listIterator(notIndex); - } - - int[] arr = {0}; - - void ListIteratorWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.listIterator(index); - } - - void ListIteratorField() { - listField.listIterator(listField.size() - 1); - listField.listIterator(this.listField.size() - 1); - this.listField.listIterator(listField.size() - 1); - this.listField.listIterator(this.listField.size() - 1); - - // :: error: (list.access.unsafe.high) - listField.listIterator(listField.size()); - // :: error: (list.access.unsafe.high) - listField.listIterator(this.listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.listIterator(listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.listIterator(this.listField.size()); - } - - void ListIteratorFieldUserAnnotation(@IndexFor("listField") int i) { - listField.listIterator(i); - this.listField.listIterator(i); - - // :: error: (list.access.unsafe.high) - listField.listIterator(i + 1); - // :: error: (list.access.unsafe.high) - this.listField.listIterator(i + 1); - } - - void ListIteratorUserAnnotation(@IndexFor("#2") int i, List list) { - list.listIterator(i); - - // :: error: (list.access.unsafe.high) - list.listIterator(i + 1); - } + List listField; + + void ListIterator( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.listIterator(index); + + // :: error: (list.access.unsafe.high) + list.listIterator(notIndex); + } + + int[] arr = {0}; + + void ListIteratorWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.listIterator(index); + } + + void ListIteratorField() { + listField.listIterator(listField.size() - 1); + listField.listIterator(this.listField.size() - 1); + this.listField.listIterator(listField.size() - 1); + this.listField.listIterator(this.listField.size() - 1); + + // :: error: (list.access.unsafe.high) + listField.listIterator(listField.size()); + // :: error: (list.access.unsafe.high) + listField.listIterator(this.listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.listIterator(listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.listIterator(this.listField.size()); + } + + void ListIteratorFieldUserAnnotation(@IndexFor("listField") int i) { + listField.listIterator(i); + this.listField.listIterator(i); + + // :: error: (list.access.unsafe.high) + listField.listIterator(i + 1); + // :: error: (list.access.unsafe.high) + this.listField.listIterator(i + 1); + } + + void ListIteratorUserAnnotation(@IndexFor("#2") int i, List list) { + list.listIterator(i); + + // :: error: (list.access.unsafe.high) + list.listIterator(i + 1); + } } diff --git a/checker/tests/index/ListLowerBound.java b/checker/tests/index/ListLowerBound.java index c8a520db72a..fef7cfeacfc 100644 --- a/checker/tests/index/ListLowerBound.java +++ b/checker/tests/index/ListLowerBound.java @@ -1,18 +1,19 @@ // @skip-test until we bring list support back -import java.util.List; -import java.util.ListIterator; import org.checkerframework.checker.index.qual.GTENegativeOne; import org.checkerframework.checker.index.qual.NonNegative; +import java.util.List; +import java.util.ListIterator; + public class ListLowerBound { - private void m(List l) { - // :: error: (argument.type.incompatible) - l.get(-1); - // :: error: (argument.type.incompatible) - ListIterator li = l.listIterator(-1); + private void m(List l) { + // :: error: (argument.type.incompatible) + l.get(-1); + // :: error: (argument.type.incompatible) + ListIterator li = l.listIterator(-1); - @NonNegative int ni = li.nextIndex(); - @GTENegativeOne int pi = li.previousIndex(); - } + @NonNegative int ni = li.nextIndex(); + @GTENegativeOne int pi = li.previousIndex(); + } } diff --git a/checker/tests/index/ListRemove.java b/checker/tests/index/ListRemove.java index 8561a3316e0..32146eb10bf 100644 --- a/checker/tests/index/ListRemove.java +++ b/checker/tests/index/ListRemove.java @@ -8,72 +8,72 @@ public class ListRemove { - List listField; + List listField; - void ListRemove( - @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.remove(index); + void ListRemove( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.remove(index); - // :: error: (list.access.unsafe.high) - list.remove(notIndex); - } - - void ListRemoveWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.remove(index); - } + // :: error: (list.access.unsafe.high) + list.remove(notIndex); + } - void ListRemoveField() { - listField.remove(listField.size() - 1); - listField.remove(this.listField.size() - 1); - this.listField.remove(listField.size() - 1); - this.listField.remove(this.listField.size() - 1); + void ListRemoveWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.remove(index); + } - // :: error: (list.access.unsafe.high) - listField.remove(listField.size()); - // :: error: (list.access.unsafe.high) - listField.remove(this.listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.remove(listField.size()); - // :: error: (list.access.unsafe.high) - this.listField.remove(this.listField.size()); - } + void ListRemoveField() { + listField.remove(listField.size() - 1); + listField.remove(this.listField.size() - 1); + this.listField.remove(listField.size() - 1); + this.listField.remove(this.listField.size() - 1); - void ListRemoveFieldUserAnnotation(@IndexFor("listField") int i) { - listField.remove(i); - this.listField.remove(i); + // :: error: (list.access.unsafe.high) + listField.remove(listField.size()); + // :: error: (list.access.unsafe.high) + listField.remove(this.listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.remove(listField.size()); + // :: error: (list.access.unsafe.high) + this.listField.remove(this.listField.size()); + } - // :: error: (list.access.unsafe.high) - listField.remove(i + 1); - // :: error: (list.access.unsafe.high) - this.listField.remove(i + 1); - } + void ListRemoveFieldUserAnnotation(@IndexFor("listField") int i) { + listField.remove(i); + this.listField.remove(i); - void ListRemoveUserAnnotation(@IndexFor("list") int i, List list) { - list.remove(i); + // :: error: (list.access.unsafe.high) + listField.remove(i + 1); + // :: error: (list.access.unsafe.high) + this.listField.remove(i + 1); + } - // :: error: (list.access.unsafe.high) - list.remove(i + 1); - // :: error: (list.access.unsafe.high) - list.remove(i); - } + void ListRemoveUserAnnotation(@IndexFor("list") int i, List list) { + list.remove(i); - void FailRemove(List list) { - @LTLengthOf("list") int i = list.size() - 1; - try { - list.remove(1); - } catch (Exception e) { + // :: error: (list.access.unsafe.high) + list.remove(i + 1); + // :: error: (list.access.unsafe.high) + list.remove(i); } - @LTLengthOf("list") int m = i; - } + void FailRemove(List list) { + @LTLengthOf("list") int i = list.size() - 1; + try { + list.remove(1); + } catch (Exception e) { + } + + @LTLengthOf("list") int m = i; + } - void RemoveUpdate(List list) { - @LTLength("list") - int m = list.size() - 1; - list.get(m); - list.remove(m); - // :: error: (list.access.unsafe.high) - list.get(m); - } + void RemoveUpdate(List list) { + @LTLength("list") + int m = list.size() - 1; + list.get(m); + list.remove(m); + // :: error: (list.access.unsafe.high) + list.get(m); + } } diff --git a/checker/tests/index/ListSet.java b/checker/tests/index/ListSet.java index 9b4a70c5f1f..943caac69e9 100644 --- a/checker/tests/index/ListSet.java +++ b/checker/tests/index/ListSet.java @@ -6,52 +6,53 @@ public class ListSet { - List listField; - - void ListSet(@LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { - list.set(index, 4); - - // :: error: (list.access.unsafe.high) - list.set(notIndex, 4); - } - - int[] arr = {0}; - - void ListSetWrongName(@LTLengthOf("arr") int index, List list) { - // :: error: (list.access.unsafe.high) - list.set(index, 4); - } - - void ListSetField() { - listField.set(listField.size() - 1, 4); - listField.set(this.listField.size() - 1, 4); - this.listField.set(listField.size() - 1, 4); - this.listField.set(this.listField.size() - 1, 4); - - // :: error: (list.access.unsafe.high) - listField.set(listField.size(), 4); - // :: error: (list.access.unsafe.high) - listField.set(this.listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.set(listField.size(), 4); - // :: error: (list.access.unsafe.high) - this.listField.set(this.listField.size(), 4); - } - - void ListSetFieldUserAnnotation(@IndexFor("listField") int i) { - listField.set(i, 4); - this.listField.set(i, 4); - - // :: error: (list.access.unsafe.high) - listField.set(i + 1, 4); - // :: error: (list.access.unsafe.high) - this.listField.set(i + 1, 4); - } - - void ListSetUserAnnotation(@IndexFor("#2") int i, List list) { - list.set(i, 4); - - // :: error: (list.access.unsafe.high) - list.set(i + 1, 4); - } + List listField; + + void ListSet( + @LTLengthOf("#3") int index, @LTEqLengthOf("#3") int notIndex, List list) { + list.set(index, 4); + + // :: error: (list.access.unsafe.high) + list.set(notIndex, 4); + } + + int[] arr = {0}; + + void ListSetWrongName(@LTLengthOf("arr") int index, List list) { + // :: error: (list.access.unsafe.high) + list.set(index, 4); + } + + void ListSetField() { + listField.set(listField.size() - 1, 4); + listField.set(this.listField.size() - 1, 4); + this.listField.set(listField.size() - 1, 4); + this.listField.set(this.listField.size() - 1, 4); + + // :: error: (list.access.unsafe.high) + listField.set(listField.size(), 4); + // :: error: (list.access.unsafe.high) + listField.set(this.listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.set(listField.size(), 4); + // :: error: (list.access.unsafe.high) + this.listField.set(this.listField.size(), 4); + } + + void ListSetFieldUserAnnotation(@IndexFor("listField") int i) { + listField.set(i, 4); + this.listField.set(i, 4); + + // :: error: (list.access.unsafe.high) + listField.set(i + 1, 4); + // :: error: (list.access.unsafe.high) + this.listField.set(i + 1, 4); + } + + void ListSetUserAnnotation(@IndexFor("#2") int i, List list) { + list.set(i, 4); + + // :: error: (list.access.unsafe.high) + list.set(i + 1, 4); + } } diff --git a/checker/tests/index/ListSupport.java b/checker/tests/index/ListSupport.java index 2f30cc3aa5b..de94282ddf2 100644 --- a/checker/tests/index/ListSupport.java +++ b/checker/tests/index/ListSupport.java @@ -6,61 +6,62 @@ public class ListSupport { - void indexOf(List list) { - int index = list.indexOf(0); + void indexOf(List list) { + int index = list.indexOf(0); - @LTLengthOf("list") int i = index; + @LTLengthOf("list") int i = index; - // :: error: (assignment.type.incompatible) - @UpperBoundBottom int i2 = index; - } + // :: error: (assignment.type.incompatible) + @UpperBoundBottom int i2 = index; + } - void lastIndexOf(List list) { - int index = list.lastIndexOf(0); + void lastIndexOf(List list) { + int index = list.lastIndexOf(0); - @LTLengthOf("list") int i = index; + @LTLengthOf("list") int i = index; - // :: error: (assignment.type.incompatible) - @UpperBoundBottom int i2 = index; - } + // :: error: (assignment.type.incompatible) + @UpperBoundBottom int i2 = index; + } - void subList(List list, @LTLengthOf("#1") int index, @LTEqLengthOf("#1") int endIndex) { - List list2 = list.subList(index, endIndex); + void subList( + List list, @LTLengthOf("#1") int index, @LTEqLengthOf("#1") int endIndex) { + List list2 = list.subList(index, endIndex); - // start index must be strictly lessthanlength - // :: error: (argument.type.incompatible) - list2 = list.subList(endIndex, endIndex); + // start index must be strictly lessthanlength + // :: error: (argument.type.incompatible) + list2 = list.subList(endIndex, endIndex); - // edindex must be less than or equal to Length - // :: error: (argument.type.incompatible) - list2 = list.subList(index, 28); - } + // edindex must be less than or equal to Length + // :: error: (argument.type.incompatible) + list2 = list.subList(index, 28); + } - void size(List list) { - int i = list.size(); - @LTEqLengthOf("list") int k = i; + void size(List list) { + int i = list.size(); + @LTEqLengthOf("list") int k = i; - // :: error: (assignment.type.incompatible) - @LTLengthOf("list") int m = i; - } + // :: error: (assignment.type.incompatible) + @LTLengthOf("list") int m = i; + } - void clear(List list) { - int lessThanLength = list.size() - 1; - int lessThanOrEq = list.size(); - list.get(lessThanLength); + void clear(List list) { + int lessThanLength = list.size() - 1; + int lessThanOrEq = list.size(); + list.get(lessThanLength); - list.clear(); + list.clear(); - // :: error: (list.access.unsafe.high) - list.get(lessThanLength); + // :: error: (list.access.unsafe.high) + list.get(lessThanLength); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("list") int m = lessThanLength; + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("list") int m = lessThanLength; - // :: error: (assignment.type.incompatible) - m = lessThanOrEq; + // :: error: (assignment.type.incompatible) + m = lessThanOrEq; - // :: error: (assignment.type.incompatible) - @LTLengthOf("list") int i = lessThanLength; - } + // :: error: (assignment.type.incompatible) + @LTLengthOf("list") int i = lessThanLength; + } } diff --git a/checker/tests/index/ListSupportLBC.java b/checker/tests/index/ListSupportLBC.java index bb63870a8c0..4b570c514f7 100644 --- a/checker/tests/index/ListSupportLBC.java +++ b/checker/tests/index/ListSupportLBC.java @@ -1,89 +1,90 @@ -import java.util.ArrayList; import org.checkerframework.checker.index.qual.GTENegativeOne; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.index.qual.Positive; +import java.util.ArrayList; + // @skip-test until we bring list support back public class ListSupportLBC { - void testGet() { + void testGet() { - List list = new ArrayList<>(); - int i = -1; - int j = 0; + List list = new ArrayList<>(); + int i = -1; + int j = 0; - // try and use a negative to get, should fail - // :: error: (argument.type.incompatible) - Integer m = list.get(i); + // try and use a negative to get, should fail + // :: error: (argument.type.incompatible) + Integer m = list.get(i); - // try and use a nonnegative, should work - Integer l = list.get(j); - } + // try and use a nonnegative, should work + Integer l = list.get(j); + } - void testArrayListGet() { + void testArrayListGet() { - ArrayList list = new ArrayList<>(); - int i = -1; - int j = 0; + ArrayList list = new ArrayList<>(); + int i = -1; + int j = 0; - // try and use a negative to get, should fail - // :: error: (argument.type.incompatible) - Integer m = list.get(i); + // try and use a negative to get, should fail + // :: error: (argument.type.incompatible) + Integer m = list.get(i); - // try and use a nonnegative, should work - Integer l = list.get(j); - } + // try and use a nonnegative, should work + Integer l = list.get(j); + } - void testSet() { - List list = new ArrayList<>(); - int i = -1; - int j = 0; + void testSet() { + List list = new ArrayList<>(); + int i = -1; + int j = 0; - // try and use a negative to get, should fail - // :: error: (argument.type.incompatible) - Integer m = list.set(i, 34); + // try and use a negative to get, should fail + // :: error: (argument.type.incompatible) + Integer m = list.set(i, 34); - // try and use a nonnegative, should work - Integer l = list.set(j, 34); - } + // try and use a nonnegative, should work + Integer l = list.set(j, 34); + } - void testIndexOf() { - List list = new ArrayList<>(); - @GTENegativeOne int a = list.indexOf(1); + void testIndexOf() { + List list = new ArrayList<>(); + @GTENegativeOne int a = list.indexOf(1); - // :: error: (assignment.type.incompatible) - @NonNegative int n = a; + // :: error: (assignment.type.incompatible) + @NonNegative int n = a; - @GTENegativeOne int b = list.lastIndexOf(1); + @GTENegativeOne int b = list.lastIndexOf(1); - // :: error: (assignment.type.incompatible) - @NonNegative int m = b; - } + // :: error: (assignment.type.incompatible) + @NonNegative int m = b; + } - void testSize() { - List list = new ArrayList<>(); - @NonNegative int s = list.size(); + void testSize() { + List list = new ArrayList<>(); + @NonNegative int s = list.size(); - // :: error: (assignment.type.incompatible) - @Positive int r = s; - } + // :: error: (assignment.type.incompatible) + @Positive int r = s; + } - void testSublist() { - List list = new ArrayList<>(); - int i = -1; - int j = 0; + void testSublist() { + List list = new ArrayList<>(); + int i = -1; + int j = 0; - // :: error: (argument.type.incompatible) - List k = list.subList(i, i); + // :: error: (argument.type.incompatible) + List k = list.subList(i, i); - // :: error: (argument.type.incompatible) - List a = list.subList(i, j); + // :: error: (argument.type.incompatible) + List a = list.subList(i, j); - // :: error: (argument.type.incompatible) - List b = list.subList(j, i); + // :: error: (argument.type.incompatible) + List b = list.subList(j, i); - // should work since both are nonnegative - List c = list.subList(j, j); - } + // should work since both are nonnegative + List c = list.subList(j, j); + } } diff --git a/checker/tests/index/ListSupportML.java b/checker/tests/index/ListSupportML.java index 26c0762f0a7..bbe2e99e3d1 100644 --- a/checker/tests/index/ListSupportML.java +++ b/checker/tests/index/ListSupportML.java @@ -1,70 +1,71 @@ -import java.util.ArrayList; import org.checkerframework.common.value.qual.MinLen; +import java.util.ArrayList; + // @skip-test until we bring list support back public class ListSupportML { - void newListMinLen() { - List list = new ArrayList<>(); + void newListMinLen() { + List list = new ArrayList<>(); - // :: error: (assignment.type.incompatible) - @MinLen(1) List list2 = list; + // :: error: (assignment.type.incompatible) + @MinLen(1) List list2 = list; - @MinLen(0) List list3 = list; - } + @MinLen(0) List list3 = list; + } - void listRemove(@MinLen(10) List lst) { - List list = lst; - list.remove(0); + void listRemove(@MinLen(10) List lst) { + List list = lst; + list.remove(0); - // :: error: (assignment.type.incompatible) - @MinLen(10) List list2 = list; + // :: error: (assignment.type.incompatible) + @MinLen(10) List list2 = list; - @MinLen(9) List list3 = list; - } + @MinLen(9) List list3 = list; + } - void listRemoveAliasing(@MinLen(10) List lst) { - List list = lst; - @MinLen(10) List list2 = list; + void listRemoveAliasing(@MinLen(10) List lst) { + List list = lst; + @MinLen(10) List list2 = list; - list2.remove(0); + list2.remove(0); - // :: error: (assignment.type.incompatible) - @MinLen(10) List list3 = list; + // :: error: (assignment.type.incompatible) + @MinLen(10) List list3 = list; - @MinLen(9) List list4 = list; - } + @MinLen(9) List list4 = list; + } - void listAdd(@MinLen(10) List lst) { - List list = lst; - list.add(0); + void listAdd(@MinLen(10) List lst) { + List list = lst; + list.add(0); - @MinLen(11) List list2 = list; - } + @MinLen(11) List list2 = list; + } - void listClear(@MinLen(10) List lst) { - List list = lst; - list.clear(); + void listClear(@MinLen(10) List lst) { + List list = lst; + list.clear(); - // :: error: (assignment.type.incompatible) - @MinLen(1) List list2 = list; + // :: error: (assignment.type.incompatible) + @MinLen(1) List list2 = list; - @MinLen(0) List list3 = list; - } + @MinLen(0) List list3 = list; + } - void listRemoveArrayAlter(@MinLen(10) List lst) { - int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - int @MinLen(10) [] arr1 = arr; - List list = lst; - @MinLen(10) List list2 = list; + void listRemoveArrayAlter(@MinLen(10) List lst) { + int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int @MinLen(10) [] arr1 = arr; + List list = lst; + @MinLen(10) List list2 = list; - list2.remove(0); + list2.remove(0); - // :: error: (assignment.type.incompatible) - @MinLen(10) List list3 = list; + // :: error: (assignment.type.incompatible) + @MinLen(10) List list3 = list; - int @MinLen(10) [] arr2 = arr; - @MinLen(9) List list4 = list; - } + int @MinLen(10) [] arr2 = arr; + @MinLen(9) List list4 = list; + } } diff --git a/checker/tests/index/LiteralArray.java b/checker/tests/index/LiteralArray.java index 78148df166a..264ed32202d 100644 --- a/checker/tests/index/LiteralArray.java +++ b/checker/tests/index/LiteralArray.java @@ -5,15 +5,15 @@ public class LiteralArray { - private static final String[] timeFormat = { - ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), - }; + private static final String[] timeFormat = { + ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), + }; - public String format() { - return format(1); - } + public String format() { + return format(1); + } - public String format(@IndexFor("LiteralArray.timeFormat") int digits) { - return ""; - } + public String format(@IndexFor("LiteralArray.timeFormat") int digits) { + return ""; + } } diff --git a/checker/tests/index/LiteralString.java b/checker/tests/index/LiteralString.java index 3e8866dd205..3cb4cda097b 100644 --- a/checker/tests/index/LiteralString.java +++ b/checker/tests/index/LiteralString.java @@ -6,15 +6,15 @@ public class LiteralString { - private static final String[] finalField = {"This", "is", "an", "array"}; + private static final String[] finalField = {"This", "is", "an", "array"}; - void testLiteralString() { - @MinLen(10) String s = "This string is long enough"; - } + void testLiteralString() { + @MinLen(10) String s = "This string is long enough"; + } - void testLiteralArray() { - String @MinLen(2) [] a = new String[] {"This", "array", "is", "long", "enough"}; - String @MinLen(2) [] b = finalField; - @IndexFor("finalField") int i = 0; - } + void testLiteralArray() { + String @MinLen(2) [] a = new String[] {"This", "array", "is", "long", "enough"}; + String @MinLen(2) [] b = finalField; + @IndexFor("finalField") int i = 0; + } } diff --git a/checker/tests/index/LongAndIntegerBitsMethods.java b/checker/tests/index/LongAndIntegerBitsMethods.java index 1ce36bbf970..864222c242f 100644 --- a/checker/tests/index/LongAndIntegerBitsMethods.java +++ b/checker/tests/index/LongAndIntegerBitsMethods.java @@ -1,14 +1,14 @@ import org.checkerframework.common.value.qual.MinLen; public class LongAndIntegerBitsMethods { - void caseInteger( - int index, int @MinLen(33) [] arr1, int @MinLen(33) [] arr2, int val1, int val2) { - arr1[Integer.numberOfLeadingZeros(index)] = val1; - arr2[Integer.numberOfTrailingZeros(index)] = val2; - } + void caseInteger( + int index, int @MinLen(33) [] arr1, int @MinLen(33) [] arr2, int val1, int val2) { + arr1[Integer.numberOfLeadingZeros(index)] = val1; + arr2[Integer.numberOfTrailingZeros(index)] = val2; + } - void caseLong(int index, int @MinLen(65) [] arr1, int @MinLen(65) [] arr2, int val1, int val2) { - arr1[Long.numberOfLeadingZeros(index)] = val1; - arr2[Long.numberOfTrailingZeros(index)] = val2; - } + void caseLong(int index, int @MinLen(65) [] arr1, int @MinLen(65) [] arr2, int val1, int val2) { + arr1[Long.numberOfLeadingZeros(index)] = val1; + arr2[Long.numberOfTrailingZeros(index)] = val2; + } } diff --git a/checker/tests/index/Loops.java b/checker/tests/index/Loops.java index 607df814d4b..977e56ba9fb 100644 --- a/checker/tests/index/Loops.java +++ b/checker/tests/index/Loops.java @@ -1,83 +1,83 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public final class Loops { - public static boolean flag = false; + public static boolean flag = false; - public void test1a(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (unary.increment.type.incompatible) - offset++; + public void test1a(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (unary.increment.type.incompatible) + offset++; + } } - } - public void test1b(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (compound.assignment.type.incompatible) - offset += 1; + public void test1b(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (compound.assignment.type.incompatible) + offset += 1; + } } - } - public void test1c(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (compound.assignment.type.incompatible) - offset2 += offset; + public void test1c(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (compound.assignment.type.incompatible) + offset2 += offset; + } } - } - public void test2(int[] a, int[] array) { - int offset = array.length - 1; - int offset2 = array.length - 1; + public void test2(int[] a, int[] array) { + int offset = array.length - 1; + int offset2 = array.length - 1; - while (flag) { - offset++; - offset2 += offset; + while (flag) { + offset++; + offset2 += offset; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int x = offset; + // :: error: (assignment.type.incompatible) + @LTLengthOf("array") int y = offset2; } - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int x = offset; - // :: error: (assignment.type.incompatible) - @LTLengthOf("array") int y = offset2; - } - public void test3(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - offset--; - // :: error: (compound.assignment.type.incompatible) - offset2 -= offset; + public void test3(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + offset--; + // :: error: (compound.assignment.type.incompatible) + offset2 -= offset; + } } - } - public void test4(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { - while (flag) { - // :: error: (unary.increment.type.incompatible) - offset++; - // :: error: (compound.assignment.type.incompatible) - offset += 1; - // :: error: (compound.assignment.type.incompatible) - offset2 += offset; + public void test4(int[] a, @LTLengthOf("#1") int offset, @LTLengthOf("#1") int offset2) { + while (flag) { + // :: error: (unary.increment.type.incompatible) + offset++; + // :: error: (compound.assignment.type.incompatible) + offset += 1; + // :: error: (compound.assignment.type.incompatible) + offset2 += offset; + } } - } - public void test4(int[] src) { - int patternLength = src.length; - int[] optoSft = new int[patternLength]; - for (int i = patternLength; i > 0; i--) {} - } + public void test4(int[] src) { + int patternLength = src.length; + int[] optoSft = new int[patternLength]; + for (int i = patternLength; i > 0; i--) {} + } - public void test5( - int[] a, - @LTLengthOf(value = "#1", offset = "-1000") int offset, - @LTLengthOf("#1") int offset2) { - int otherOffset = offset; - while (flag) { - otherOffset += 1; - // :: error: (unary.increment.type.incompatible) - offset++; - // :: error: (compound.assignment.type.incompatible) - offset += 1; - // :: error: (compound.assignment.type.incompatible) - offset2 += offset; + public void test5( + int[] a, + @LTLengthOf(value = "#1", offset = "-1000") int offset, + @LTLengthOf("#1") int offset2) { + int otherOffset = offset; + while (flag) { + otherOffset += 1; + // :: error: (unary.increment.type.incompatible) + offset++; + // :: error: (compound.assignment.type.incompatible) + offset += 1; + // :: error: (compound.assignment.type.incompatible) + offset2 += offset; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "#1", offset = "-1000") int x = otherOffset; } - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "#1", offset = "-1000") int x = otherOffset; - } } diff --git a/checker/tests/index/LubIndex.java b/checker/tests/index/LubIndex.java index 81e1918af48..202fa55e0dd 100644 --- a/checker/tests/index/LubIndex.java +++ b/checker/tests/index/LubIndex.java @@ -3,42 +3,42 @@ public class LubIndex { - public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; + public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; + } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; - } - public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; + public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; + } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; - } - public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; + public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; + } + int @MinLen(10) [] res = arr; + int @BottomVal [] res2 = arr; } - int @MinLen(10) [] res = arr; - int @BottomVal [] res2 = arr; - } } diff --git a/checker/tests/index/MLEqualTo.java b/checker/tests/index/MLEqualTo.java index 1f96bcebbbf..642ebbe8309 100644 --- a/checker/tests/index/MLEqualTo.java +++ b/checker/tests/index/MLEqualTo.java @@ -2,9 +2,9 @@ public class MLEqualTo { - public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { - if (r == m) { - int @MinLen(2) [] j = r; + public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { + if (r == m) { + int @MinLen(2) [] j = r; + } } - } } diff --git a/checker/tests/index/MathPlumeClasscastCrash.java b/checker/tests/index/MathPlumeClasscastCrash.java index 8a68c914eab..c490963f5ab 100644 --- a/checker/tests/index/MathPlumeClasscastCrash.java +++ b/checker/tests/index/MathPlumeClasscastCrash.java @@ -8,9 +8,9 @@ public final class MathPlumeClasscastCrash { - @SuppressWarnings("index:return") - public static @NonNegative @LessThan("#2") @PolyUpperBound long modPositive( - long x, @PolyUpperBound long y) { - return 0; - } + @SuppressWarnings("index:return") + public static @NonNegative @LessThan("#2") @PolyUpperBound long modPositive( + long x, @PolyUpperBound long y) { + return 0; + } } diff --git a/checker/tests/index/MethodOverrides.java b/checker/tests/index/MethodOverrides.java index 64b946ef019..9dd4f7bff83 100644 --- a/checker/tests/index/MethodOverrides.java +++ b/checker/tests/index/MethodOverrides.java @@ -7,14 +7,14 @@ import org.checkerframework.checker.index.qual.GTENegativeOne; public class MethodOverrides { - @GTENegativeOne int read() { - return -1; - } + @GTENegativeOne int read() { + return -1; + } } class MethodOverrides2 extends MethodOverrides { - // :: error: (override.return.invalid) - int read() { - return -1; - } + // :: error: (override.return.invalid) + int read() { + return -1; + } } diff --git a/checker/tests/index/MinLenFieldInvar.java b/checker/tests/index/MinLenFieldInvar.java index 1794052b2fb..376a2e4648d 100644 --- a/checker/tests/index/MinLenFieldInvar.java +++ b/checker/tests/index/MinLenFieldInvar.java @@ -6,58 +6,58 @@ import org.checkerframework.framework.qual.FieldInvariant; public class MinLenFieldInvar { - class Super { - public final int @MinLen(2) [] minlen2; + class Super { + public final int @MinLen(2) [] minlen2; - public Super(int @MinLen(2) [] minlen2) { - this.minlen2 = minlen2; + public Super(int @MinLen(2) [] minlen2) { + this.minlen2 = minlen2; + } } - } - // :: error: (field.invariant.not.subtype) - @MinLenFieldInvariant(field = "minlen2", minLen = 1) - class InvalidSub extends Super { - public InvalidSub() { - super(new int[] {1, 2}); + // :: error: (field.invariant.not.subtype) + @MinLenFieldInvariant(field = "minlen2", minLen = 1) + class InvalidSub extends Super { + public InvalidSub() { + super(new int[] {1, 2}); + } } - } - @MinLenFieldInvariant(field = "minlen2", minLen = 4) - class ValidSub extends Super { - public final int[] validSubField; + @MinLenFieldInvariant(field = "minlen2", minLen = 4) + class ValidSub extends Super { + public final int[] validSubField; - public ValidSub(int[] validSubField) { - super(new int[] {1, 2, 3, 4}); - this.validSubField = validSubField; + public ValidSub(int[] validSubField) { + super(new int[] {1, 2, 3, 4}); + this.validSubField = validSubField; + } } - } - // :: error: (field.invariant.not.found.superclass) - @MinLenFieldInvariant(field = "validSubField", minLen = 3) - class InvalidSubSub1 extends ValidSub { - public InvalidSubSub1() { - super(new int[] {1, 2}); + // :: error: (field.invariant.not.found.superclass) + @MinLenFieldInvariant(field = "validSubField", minLen = 3) + class InvalidSubSub1 extends ValidSub { + public InvalidSubSub1() { + super(new int[] {1, 2}); + } } - } - // :: error: (field.invariant.not.subtype.superclass) - @MinLenFieldInvariant(field = "minlen2", minLen = 3) - class InvalidSubSub2 extends ValidSub { - public InvalidSubSub2() { - super(new int[] {1, 2}); + // :: error: (field.invariant.not.subtype.superclass) + @MinLenFieldInvariant(field = "minlen2", minLen = 3) + class InvalidSubSub2 extends ValidSub { + public InvalidSubSub2() { + super(new int[] {1, 2}); + } } - } - @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) - @MinLenFieldInvariant(field = "validSubField", minLen = 4) - class ValidSubSub extends ValidSub { - public ValidSubSub() { - super(null); + @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) + @MinLenFieldInvariant(field = "validSubField", minLen = 4) + class ValidSubSub extends ValidSub { + public ValidSubSub() { + super(null); + } + + void test() { + int @BottomVal [] bot = minlen2; + int @MinLen(4) [] four = validSubField; + } } - - void test() { - int @BottomVal [] bot = minlen2; - int @MinLen(4) [] four = validSubField; - } - } } diff --git a/checker/tests/index/MinLenFourShenanigans.java b/checker/tests/index/MinLenFourShenanigans.java index 6dcb8e17228..5e99cc45d12 100644 --- a/checker/tests/index/MinLenFourShenanigans.java +++ b/checker/tests/index/MinLenFourShenanigans.java @@ -1,23 +1,23 @@ package index; public class MinLenFourShenanigans { - public static boolean isInterned(Object value) { - if (value == null) { - // nothing to do - return true; - } else if (value instanceof String) { - // Used to issue the below error. - // MinLenFourShenanigans.java:7: warning: [cast.unsafe] "@MinLen(0) Object" may not be - // casted to the type "@MinLen(4) String" - return (value == ((String) value).intern()); + public static boolean isInterned(Object value) { + if (value == null) { + // nothing to do + return true; + } else if (value instanceof String) { + // Used to issue the below error. + // MinLenFourShenanigans.java:7: warning: [cast.unsafe] "@MinLen(0) Object" may not be + // casted to the type "@MinLen(4) String" + return (value == ((String) value).intern()); + } + return false; } - return false; - } - public static boolean isInterned2(Object value) { - if (value instanceof String) { - return (value == ((String) value).intern()); + public static boolean isInterned2(Object value) { + if (value instanceof String) { + return (value == ((String) value).intern()); + } + return false; } - return false; - } } diff --git a/checker/tests/index/MinLenFromPositive.java b/checker/tests/index/MinLenFromPositive.java index f9d39fff264..7f0cb27af3f 100644 --- a/checker/tests/index/MinLenFromPositive.java +++ b/checker/tests/index/MinLenFromPositive.java @@ -3,61 +3,61 @@ public class MinLenFromPositive { - void test(@Positive int x) { - int @MinLen(1) [] y = new int[x]; - @IntRange(from = 1) int z = x; - @Positive int q = x; - } - - @SuppressWarnings("index") - void foo(int x) { - test(x); - } - - void foo2(int x) { - // :: error: (argument.type.incompatible) - test(x); - } - - void test_lub1(boolean flag, @Positive int x, @IntRange(from = 6, to = 25) int y) { - int z; - if (flag) { - z = x; - } else { - z = y; + void test(@Positive int x) { + int @MinLen(1) [] y = new int[x]; + @IntRange(from = 1) int z = x; + @Positive int q = x; } - @Positive int q = z; - @IntRange(from = 1) int w = z; - } - - void test_lub2(boolean flag, @Positive int x, @IntRange(from = -1, to = 11) int y) { - int z; - if (flag) { - z = x; - } else { - z = y; + + @SuppressWarnings("index") + void foo(int x) { + test(x); + } + + void foo2(int x) { + // :: error: (argument.type.incompatible) + test(x); + } + + void test_lub1(boolean flag, @Positive int x, @IntRange(from = 6, to = 25) int y) { + int z; + if (flag) { + z = x; + } else { + z = y; + } + @Positive int q = z; + @IntRange(from = 1) int w = z; + } + + void test_lub2(boolean flag, @Positive int x, @IntRange(from = -1, to = 11) int y) { + int z; + if (flag) { + z = x; + } else { + z = y; + } + // :: error: (assignment.type.incompatible) + @Positive int q = z; + @IntRange(from = -1) int w = z; } - // :: error: (assignment.type.incompatible) - @Positive int q = z; - @IntRange(from = -1) int w = z; - } - @Positive int id(@Positive int x) { - return x; - } + @Positive int id(@Positive int x) { + return x; + } - void test_id(int param) { - @Positive int x = id(5); - @IntRange(from = 1) int y = id(5); + void test_id(int param) { + @Positive int x = id(5); + @IntRange(from = 1) int y = id(5); - int @MinLen(1) [] a = new int[id(100)]; - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] c = new int[id(100)]; + int @MinLen(1) [] a = new int[id(100)]; + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] c = new int[id(100)]; - int q = id(10); + int q = id(10); - if (param == q) { - int @MinLen(1) [] d = new int[param]; + if (param == q) { + int @MinLen(1) [] d = new int[param]; + } } - } } diff --git a/checker/tests/index/MinLenIndexFor.java b/checker/tests/index/MinLenIndexFor.java index 6b3ba9f4743..f3ba544ff78 100644 --- a/checker/tests/index/MinLenIndexFor.java +++ b/checker/tests/index/MinLenIndexFor.java @@ -3,39 +3,39 @@ import org.checkerframework.common.value.qual.MinLen; public class MinLenIndexFor { - int @MinLen(2) [] arrayLen2 = {0, 1, 2}; + int @MinLen(2) [] arrayLen2 = {0, 1, 2}; - void test(@IndexFor("this.arrayLen2") int i) { - int j = arrayLen2[i]; - int j2 = arrayLen2[1]; - } + void test(@IndexFor("this.arrayLen2") int i) { + int j = arrayLen2[i]; + int j2 = arrayLen2[1]; + } - void callTest(int x) { - test(0); - test(1); - // :: error: (argument.type.incompatible) - test(2); - // :: error: (argument.type.incompatible) - test(3); - test(arrayLen2.length - 1); - } + void callTest(int x) { + test(0); + test(1); + // :: error: (argument.type.incompatible) + test(2); + // :: error: (argument.type.incompatible) + test(3); + test(arrayLen2.length - 1); + } - int @MinLen(4) [] arrayLen4 = {0, 1, 2, 4, 5}; + int @MinLen(4) [] arrayLen4 = {0, 1, 2, 4, 5}; - void test2(@IndexOrHigh("this.arrayLen4") int i) { - if (i > 0) { - int j = arrayLen4[i - 1]; + void test2(@IndexOrHigh("this.arrayLen4") int i) { + if (i > 0) { + int j = arrayLen4[i - 1]; + } + int j2 = arrayLen4[1]; } - int j2 = arrayLen4[1]; - } - void callTest2(int x) { - test2(0); - test2(1); - test2(2); - test2(4); - // :: error: (argument.type.incompatible) - test2(5); - test2(arrayLen4.length); - } + void callTest2(int x) { + test2(0); + test2(1); + test2(2); + test2(4); + // :: error: (argument.type.incompatible) + test2(5); + test2(arrayLen4.length); + } } diff --git a/checker/tests/index/MinLenOneAndLength.java b/checker/tests/index/MinLenOneAndLength.java index ee50de3714a..be6a1dc78bd 100644 --- a/checker/tests/index/MinLenOneAndLength.java +++ b/checker/tests/index/MinLenOneAndLength.java @@ -2,9 +2,9 @@ import org.checkerframework.common.value.qual.MinLen; public class MinLenOneAndLength { - public void m1(int @MinLen(1) [] a, int[] b) { - @IndexFor("a") int i = a.length / 2; - // :: error: (assignment.type.incompatible) - @IndexFor("b") int j = b.length / 2; - } + public void m1(int @MinLen(1) [] a, int[] b) { + @IndexFor("a") int i = a.length / 2; + // :: error: (assignment.type.incompatible) + @IndexFor("b") int j = b.length / 2; + } } diff --git a/checker/tests/index/MinLenSameLenInteraction.java b/checker/tests/index/MinLenSameLenInteraction.java index 51ac8a76da0..3c2b3dfe5cd 100644 --- a/checker/tests/index/MinLenSameLenInteraction.java +++ b/checker/tests/index/MinLenSameLenInteraction.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.*; public class MinLenSameLenInteraction { - void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { - if (a.length == 1) { - int x = b[0]; + void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { + if (a.length == 1) { + int x = b[0]; + } } - } } diff --git a/checker/tests/index/MinMax.java b/checker/tests/index/MinMax.java index fe693778176..8357a70b748 100644 --- a/checker/tests/index/MinMax.java +++ b/checker/tests/index/MinMax.java @@ -2,12 +2,12 @@ import org.checkerframework.checker.index.qual.Positive; public class MinMax { - // They call me a power gamer. I stole the test cases from issue 26. - void mathmax() { - @Positive int i = Math.max(-15, 2); - } + // They call me a power gamer. I stole the test cases from issue 26. + void mathmax() { + @Positive int i = Math.max(-15, 2); + } - void mathmin() { - @GTENegativeOne int i = Math.min(-1, 2); - } + void mathmin() { + @GTENegativeOne int i = Math.min(-1, 2); + } } diff --git a/checker/tests/index/MinMaxIndex.java b/checker/tests/index/MinMaxIndex.java index f20a6d1ca76..588d19deace 100644 --- a/checker/tests/index/MinMaxIndex.java +++ b/checker/tests/index/MinMaxIndex.java @@ -6,31 +6,31 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class MinMaxIndex { - // Both min and max preserve IndexFor - void indexFor(char[] array, @IndexFor("#1") int i1, @IndexFor("#1") int i2) { - char c = array[Math.max(i1, i2)]; - char d = array[Math.min(i1, i2)]; - } + // Both min and max preserve IndexFor + void indexFor(char[] array, @IndexFor("#1") int i1, @IndexFor("#1") int i2) { + char c = array[Math.max(i1, i2)]; + char d = array[Math.min(i1, i2)]; + } - // Both min and max preserve IndexOrHigh - void indexOrHigh(String str, @IndexOrHigh("#1") int i1, @IndexOrHigh("#1") int i2) { - str.substring(Math.max(i1, i2)); - str.substring(Math.min(i1, i2)); - } + // Both min and max preserve IndexOrHigh + void indexOrHigh(String str, @IndexOrHigh("#1") int i1, @IndexOrHigh("#1") int i2) { + str.substring(Math.max(i1, i2)); + str.substring(Math.min(i1, i2)); + } - // Combining IndexFor and IndexOrHigh - void indexForOrHigh(String str, @IndexFor("#1") int i1, @IndexOrHigh("#1") int i2) { - str.substring(Math.max(i1, i2)); - str.substring(Math.min(i1, i2)); - // :: error: (argument.type.incompatible) - str.charAt(Math.max(i1, i2)); - str.charAt(Math.min(i1, i2)); - } + // Combining IndexFor and IndexOrHigh + void indexForOrHigh(String str, @IndexFor("#1") int i1, @IndexOrHigh("#1") int i2) { + str.substring(Math.max(i1, i2)); + str.substring(Math.min(i1, i2)); + // :: error: (argument.type.incompatible) + str.charAt(Math.max(i1, i2)); + str.charAt(Math.min(i1, i2)); + } - // max does not work with different sequences, min does - void twoSequences(String str1, String str2, @IndexFor("#1") int i1, @IndexFor("#2") int i2) { - // :: error: (argument.type.incompatible) - str1.charAt(Math.max(i1, i2)); - str1.charAt(Math.min(i1, i2)); - } + // max does not work with different sequences, min does + void twoSequences(String str1, String str2, @IndexFor("#1") int i1, @IndexFor("#2") int i2) { + // :: error: (argument.type.incompatible) + str1.charAt(Math.max(i1, i2)); + str1.charAt(Math.min(i1, i2)); + } } diff --git a/checker/tests/index/Modulo.java b/checker/tests/index/Modulo.java index 313200d3973..edac3125886 100644 --- a/checker/tests/index/Modulo.java +++ b/checker/tests/index/Modulo.java @@ -8,19 +8,19 @@ import org.checkerframework.checker.index.qual.Positive; public class Modulo { - void m1(Object[] a, @IndexOrHigh("#1") int i, @NonNegative int j) { - @IndexFor("a") int k = j % i; - } + void m1(Object[] a, @IndexOrHigh("#1") int i, @NonNegative int j) { + @IndexFor("a") int k = j % i; + } - void m1p(Object[] a, @Positive @LTEqLengthOf("#1") int i, @Positive int j) { - @IndexFor("a") int k = j % i; - } + void m1p(Object[] a, @Positive @LTEqLengthOf("#1") int i, @Positive int j) { + @IndexFor("a") int k = j % i; + } - void m2(Object[] a, int i, @IndexFor("#1") int j) { - @IndexFor("a") int k = j % i; - } + void m2(Object[] a, int i, @IndexFor("#1") int j) { + @IndexFor("a") int k = j % i; + } - void m2(Object[] a, Object[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { - @IndexFor({"a", "b"}) int k = j % i; - } + void m2(Object[] a, Object[] b, @IndexFor("#1") int i, @IndexFor("#2") int j) { + @IndexFor({"a", "b"}) int k = j % i; + } } diff --git a/checker/tests/index/NegativeArray.java b/checker/tests/index/NegativeArray.java index 7335ce6af51..5848fea58cf 100644 --- a/checker/tests/index/NegativeArray.java +++ b/checker/tests/index/NegativeArray.java @@ -2,8 +2,8 @@ public class NegativeArray { - public static void negativeArray(@GTENegativeOne int len) { - // :: error: (array.length.negative) - int[] arr = new int[len]; - } + public static void negativeArray(@GTENegativeOne int len) { + // :: error: (array.length.negative) + int[] arr = new int[len]; + } } diff --git a/checker/tests/index/NegativeIndex.java b/checker/tests/index/NegativeIndex.java index f21fc10217a..93b7372dc33 100644 --- a/checker/tests/index/NegativeIndex.java +++ b/checker/tests/index/NegativeIndex.java @@ -4,8 +4,8 @@ import org.checkerframework.common.value.qual.*; public class NegativeIndex { - @SuppressWarnings("lowerbound") - void m(int[] a) { - int x = a[-100]; - } + @SuppressWarnings("lowerbound") + void m(int[] a) { + int x = a[-100]; + } } diff --git a/checker/tests/index/NonNegArrayLength.java b/checker/tests/index/NonNegArrayLength.java index 7c14042cea6..559284533b8 100644 --- a/checker/tests/index/NonNegArrayLength.java +++ b/checker/tests/index/NonNegArrayLength.java @@ -3,7 +3,7 @@ public class NonNegArrayLength { - public static void NonNegArrayLength(int @MinLen(4) [] arr) { - @Positive int i = arr.length - 2; - } + public static void NonNegArrayLength(int @MinLen(4) [] arr) { + @Positive int i = arr.length - 2; + } } diff --git a/checker/tests/index/NonNegativeCharValue.java b/checker/tests/index/NonNegativeCharValue.java index 4dcfa459283..2c6e65ae056 100644 --- a/checker/tests/index/NonNegativeCharValue.java +++ b/checker/tests/index/NonNegativeCharValue.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.index.qual.NonNegative; public class NonNegativeCharValue { - public static String toString(final @NonNegative Character ch) { - return toString(ch.charValue()); - } + public static String toString(final @NonNegative Character ch) { + return toString(ch.charValue()); + } } diff --git a/checker/tests/index/NonnegativeChar.java b/checker/tests/index/NonnegativeChar.java index 9b8faca95b1..e3fe527d4aa 100644 --- a/checker/tests/index/NonnegativeChar.java +++ b/checker/tests/index/NonnegativeChar.java @@ -1,37 +1,38 @@ -import java.util.ArrayList; import org.checkerframework.checker.index.qual.LowerBoundBottom; import org.checkerframework.checker.index.qual.PolyLowerBound; +import java.util.ArrayList; + public class NonnegativeChar { - void foreach(char[] array) { - for (char value : array) {} - } + void foreach(char[] array) { + for (char value : array) {} + } - char constant() { - return Character.MAX_VALUE; - } + char constant() { + return Character.MAX_VALUE; + } - char conversion(int i) { - return (char) i; - } + char conversion(int i) { + return (char) i; + } - public void takeList(ArrayList z) {} + public void takeList(ArrayList z) {} - public void passList() { - takeList(new ArrayList()); - } + public void passList() { + takeList(new ArrayList()); + } - static class CustomList extends ArrayList {} + static class CustomList extends ArrayList {} - public void passCustomList() { - takeList(new CustomList()); - } + public void passCustomList() { + takeList(new CustomList()); + } - public @LowerBoundBottom char bottomLB(@LowerBoundBottom char c) { - return c; - } + public @LowerBoundBottom char bottomLB(@LowerBoundBottom char c) { + return c; + } - public @PolyLowerBound char polyLB(@PolyLowerBound char c) { - return c; - } + public @PolyLowerBound char polyLB(@PolyLowerBound char c) { + return c; + } } diff --git a/checker/tests/index/NotEnoughOffsets.java b/checker/tests/index/NotEnoughOffsets.java index d311537a5fe..58a1fbc02ad 100644 --- a/checker/tests/index/NotEnoughOffsets.java +++ b/checker/tests/index/NotEnoughOffsets.java @@ -2,21 +2,21 @@ public class NotEnoughOffsets { - int[] a; - int[] b; - int c, d; + int[] a; + int[] b; + int c, d; - void badParam( - // :: error: (different.length.sequences.offsets) - @LTLengthOf( - value = {"a", "b"}, - offset = {"c"}) - int x) {} + void badParam( + // :: error: (different.length.sequences.offsets) + @LTLengthOf( + value = {"a", "b"}, + offset = {"c"}) + int x) {} - void badParam2( - // :: error: (different.length.sequences.offsets) - @LTLengthOf( - value = {"a"}, - offset = {"c", "d"}) - int x) {} + void badParam2( + // :: error: (different.length.sequences.offsets) + @LTLengthOf( + value = {"a"}, + offset = {"c", "d"}) + int x) {} } diff --git a/checker/tests/index/NotEqualTransfer.java b/checker/tests/index/NotEqualTransfer.java index 5cd3dbbceaa..24a21afae81 100644 --- a/checker/tests/index/NotEqualTransfer.java +++ b/checker/tests/index/NotEqualTransfer.java @@ -1,26 +1,26 @@ import org.checkerframework.common.value.qual.MinLen; public class NotEqualTransfer { - void neq_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - int @MinLen(1) [] b = a; + void neq_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + int @MinLen(1) [] b = a; + } } - } - void neq_bad_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void neq_bad_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } - void neq_zero_special_case(int[] a) { - if (a.length != 0) { - int @MinLen(1) [] b = a; + void neq_zero_special_case(int[] a) { + if (a.length != 0) { + int @MinLen(1) [] b = a; + } } - } } diff --git a/checker/tests/index/ObjectClone.java b/checker/tests/index/ObjectClone.java index ad7f8fc49fc..ab9faaf3b2d 100644 --- a/checker/tests/index/ObjectClone.java +++ b/checker/tests/index/ObjectClone.java @@ -1,27 +1,28 @@ // Test case for issue 146: https://github.com/kelloggm/checker-framework/issues/146 -import java.util.Arrays; import org.checkerframework.checker.index.qual.*; +import java.util.Arrays; + public class ObjectClone { - void test(int[] a, int @SameLen("#1") [] b) { - int @SameLen("a") [] c = b.clone(); - int @SameLen({"a", "d"}) [] d = b.clone(); - int @SameLen({"a", "e"}) [] e = b; - int @SameLen("f") [] f = b; - } + void test(int[] a, int @SameLen("#1") [] b) { + int @SameLen("a") [] c = b.clone(); + int @SameLen({"a", "d"}) [] d = b.clone(); + int @SameLen({"a", "e"}) [] e = b; + int @SameLen("f") [] f = b; + } - public static void main(String[] args) { - String @SameLen("args") [] args2 = args; - String @SameLen({"args", "args_sorted"}) [] args_sorted = args.clone(); - Arrays.sort(args_sorted); - String @SameLen({"args", "args_sorted"}) [] args_sorted2 = args_sorted.clone(); - if (args_sorted.length == 1) { - @IndexFor("args_sorted") int i = 0; - @IndexFor("args") int j = 0; - String @SameLen({"args", "args_sorted"}) [] k = args; - System.out.println(args[0]); + public static void main(String[] args) { + String @SameLen("args") [] args2 = args; + String @SameLen({"args", "args_sorted"}) [] args_sorted = args.clone(); + Arrays.sort(args_sorted); + String @SameLen({"args", "args_sorted"}) [] args_sorted2 = args_sorted.clone(); + if (args_sorted.length == 1) { + @IndexFor("args_sorted") int i = 0; + @IndexFor("args") int j = 0; + String @SameLen({"args", "args_sorted"}) [] k = args; + System.out.println(args[0]); + } } - } } diff --git a/checker/tests/index/Offset97.java b/checker/tests/index/Offset97.java index 057736fa047..7ef8afaed1f 100644 --- a/checker/tests/index/Offset97.java +++ b/checker/tests/index/Offset97.java @@ -3,12 +3,12 @@ import org.checkerframework.checker.index.qual.*; public class Offset97 { - public static void m2() { - int[] a = {1, 2, 3, 4, 5}; - @IndexFor("a") int i = 4; - @IndexFor("a") int j = 4; - if (j < a.length - i) { - @IndexFor("a") int k = i + j; + public static void m2() { + int[] a = {1, 2, 3, 4, 5}; + @IndexFor("a") int i = 4; + @IndexFor("a") int j = 4; + if (j < a.length - i) { + @IndexFor("a") int k = i + j; + } } - } } diff --git a/checker/tests/index/OffsetAnnotations.java b/checker/tests/index/OffsetAnnotations.java index ffafaeefd6c..c0440f9848f 100644 --- a/checker/tests/index/OffsetAnnotations.java +++ b/checker/tests/index/OffsetAnnotations.java @@ -1,17 +1,17 @@ import java.io.*; public class OffsetAnnotations { - public static void OffsetAnnotationsReader() throws IOException { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); - char[] buffer = new char[10]; - // :: error: (argument.type.incompatible) - bufferedReader.read(buffer, 5, 7); - } + public static void OffsetAnnotationsReader() throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + char[] buffer = new char[10]; + // :: error: (argument.type.incompatible) + bufferedReader.read(buffer, 5, 7); + } - public static void OffsetAnnotationsWriter() throws IOException { - BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out)); - char[] buffer = new char[10]; - // :: error: (argument.type.incompatible) - bufferedWriter.write(buffer, 5, 7); - } + public static void OffsetAnnotationsWriter() throws IOException { + BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out)); + char[] buffer = new char[10]; + // :: error: (argument.type.incompatible) + bufferedWriter.write(buffer, 5, 7); + } } diff --git a/checker/tests/index/OffsetExample.java b/checker/tests/index/OffsetExample.java index 5adf3724d61..39481908a9a 100644 --- a/checker/tests/index/OffsetExample.java +++ b/checker/tests/index/OffsetExample.java @@ -1,86 +1,87 @@ -import java.util.List; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.common.value.qual.MinLen; +import java.util.List; + @SuppressWarnings("lowerbound") public class OffsetExample { - void example1(int @MinLen(2) [] a) { - int j = 2; - int x = a.length; - int y = x - j; - for (int i = 0; i < y; i++) { - a[i + j] = 1; + void example1(int @MinLen(2) [] a) { + int j = 2; + int x = a.length; + int y = x - j; + for (int i = 0; i < y; i++) { + a[i + j] = 1; + } } - } - void example2(int @MinLen(2) [] a) { - int j = 2; - int x = a.length; - int y = x - j; - a[y] = 0; - for (int i = 0; i < y; i++) { - a[i + j] = 1; - a[j + i] = 1; - a[i + 0] = 1; - a[i - 1] = 1; - // ::error: (array.access.unsafe.high) - a[i + 2 + j] = 1; + void example2(int @MinLen(2) [] a) { + int j = 2; + int x = a.length; + int y = x - j; + a[y] = 0; + for (int i = 0; i < y; i++) { + a[i + j] = 1; + a[j + i] = 1; + a[i + 0] = 1; + a[i - 1] = 1; + // ::error: (array.access.unsafe.high) + a[i + 2 + j] = 1; + } } - } - void example3(int @MinLen(2) [] a) { - int j = 2; - for (int i = 0; i < a.length - 2; i++) { - a[i + j] = 1; + void example3(int @MinLen(2) [] a) { + int j = 2; + for (int i = 0; i < a.length - 2; i++) { + a[i + j] = 1; + } } - } - void example4(int[] a, int offset) { - int max_index = a.length - offset; - for (int i = 0; i < max_index; i++) { - a[i + offset] = 0; + void example4(int[] a, int offset) { + int max_index = a.length - offset; + for (int i = 0; i < max_index; i++) { + a[i + offset] = 0; + } } - } - void example5(int[] a, int offset) { - for (int i = 0; i < a.length - offset; i++) { - a[i + offset] = 0; + void example5(int[] a, int offset) { + for (int i = 0; i < a.length - offset; i++) { + a[i + offset] = 0; + } } - } - void test(@IndexFor("#3") int start, @IndexOrHigh("#3") int end, int[] a) { - if (end > start) { - // If start == 0, then end - start is end. end might be equal to the length of a. So - // the array access might be too high. - // ::error: (array.access.unsafe.high) - a[end - start] = 0; - } + void test(@IndexFor("#3") int start, @IndexOrHigh("#3") int end, int[] a) { + if (end > start) { + // If start == 0, then end - start is end. end might be equal to the length of a. So + // the array access might be too high. + // ::error: (array.access.unsafe.high) + a[end - start] = 0; + } - if (end > start) { - a[end - start - 1] = 0; + if (end > start) { + a[end - start - 1] = 0; + } } - } - public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { - int a_len = a.length - a_offset; - int sub_len = sub.length; - if (a_len < sub_len) { - return false; - } - for (int i = 0; i < sub_len; i++) { - if (sub[i] == a[a_offset + i]) { - return false; - } + public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { + int a_len = a.length - a_offset; + int sub_len = sub.length; + if (a_len < sub_len) { + return false; + } + for (int i = 0; i < sub_len; i++) { + if (sub[i] == a[a_offset + i]) { + return false; + } + } + return true; } - return true; - } - public void test2(int[] a, List b) { - int b_size = b.size(); - Object[] result = new Object[a.length + b_size]; - for (int i = 0; i < b_size; i++) { - result[i + a.length] = b.get(i); + public void test2(int[] a, List b) { + int b_size = b.size(); + Object[] result = new Object[a.length + b_size]; + for (int i = 0; i < b_size; i++) { + result[i + a.length] = b.get(i); + } } - } } diff --git a/checker/tests/index/OffsetsAndConstants.java b/checker/tests/index/OffsetsAndConstants.java index 93c79cf5319..e7bebd04658 100644 --- a/checker/tests/index/OffsetsAndConstants.java +++ b/checker/tests/index/OffsetsAndConstants.java @@ -3,28 +3,28 @@ import org.checkerframework.checker.index.qual.NonNegative; public class OffsetsAndConstants { - static int read( - char[] a, - @IndexOrHigh("#1") int off, - @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int len) { - int sum = 0; - for (int i = 0; i < len; i++) { - sum += a[i + off]; + static int read( + char[] a, + @IndexOrHigh("#1") int off, + @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int len) { + int sum = 0; + for (int i = 0; i < len; i++) { + sum += a[i + off]; + } + return sum; } - return sum; - } - public static void main(String[] args) { - char[] a = new char[10]; + public static void main(String[] args) { + char[] a = new char[10]; - read(a, 5, 4); + read(a, 5, 4); - read(a, 5, 5); + read(a, 5, 5); - // :: error: (argument.type.incompatible) - read(a, 5, 6); + // :: error: (argument.type.incompatible) + read(a, 5, 6); - // :: error: (argument.type.incompatible) - read(a, 5, 7); - } + // :: error: (argument.type.incompatible) + read(a, 5, 7); + } } diff --git a/checker/tests/index/OneLTL.java b/checker/tests/index/OneLTL.java index de83bb62cba..a47bc8fbe8a 100644 --- a/checker/tests/index/OneLTL.java +++ b/checker/tests/index/OneLTL.java @@ -1,10 +1,10 @@ public class OneLTL { - public static boolean sorted(int[] a) { - for (int i = 0; i < a.length - 1; i++) { - if (a[i + 1] < a[i]) { - return false; - } + public static boolean sorted(int[] a) { + for (int i = 0; i < a.length - 1; i++) { + if (a[i + 1] < a[i]) { + return false; + } + } + return true; } - return true; - } } diff --git a/checker/tests/index/OneOrTwo.java b/checker/tests/index/OneOrTwo.java index 7718be27a38..8adb3f5a2a6 100644 --- a/checker/tests/index/OneOrTwo.java +++ b/checker/tests/index/OneOrTwo.java @@ -1,17 +1,17 @@ import org.checkerframework.common.value.qual.*; public class OneOrTwo { - @IntVal({1, 2}) int getOneOrTwo() { - return 1; - } + @IntVal({1, 2}) int getOneOrTwo() { + return 1; + } - void test(@BottomVal int x) { - int[] a = new int[Integer.valueOf(getOneOrTwo())]; - // :: error: (array.length.negative) - int[] b = new int[Integer.valueOf(x)]; - } + void test(@BottomVal int x) { + int[] a = new int[Integer.valueOf(getOneOrTwo())]; + // :: error: (array.length.negative) + int[] b = new int[Integer.valueOf(x)]; + } - @PolyValue int poly(@PolyValue int y) { - return y; - } + @PolyValue int poly(@PolyValue int y) { + return y; + } } diff --git a/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java b/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java index a7780d2dca2..2ec214612e4 100644 --- a/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java +++ b/checker/tests/index/OnlyCheckSubsequenceWhenAssigningToArray.java @@ -3,25 +3,25 @@ import org.checkerframework.checker.index.qual.IndexOrHigh; public class OnlyCheckSubsequenceWhenAssigningToArray { - @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") - int[] array; + @HasSubsequence(subsequence = "this", from = "this.start", to = "this.end") + int[] array; - final @IndexFor("array") int start; + final @IndexFor("array") int start; - final @IndexOrHigh("array") int end; + final @IndexOrHigh("array") int end; - private OnlyCheckSubsequenceWhenAssigningToArray( - @IndexFor("array") int s, @IndexOrHigh("array") int e) { - start = s; - end = e; - } + private OnlyCheckSubsequenceWhenAssigningToArray( + @IndexFor("array") int s, @IndexOrHigh("array") int e) { + start = s; + end = e; + } - void testAssignmentToArrayElement(@IndexFor("this") int x, int y) { - array[start + x] = y; - } + void testAssignmentToArrayElement(@IndexFor("this") int x, int y) { + array[start + x] = y; + } - void testAssignmentToArray(int[] a) { - // :: error: to.not.ltel :: error: from.gt.to - array = a; - } + void testAssignmentToArray(int[] a) { + // :: error: to.not.ltel :: error: from.gt.to + array = a; + } } diff --git a/checker/tests/index/OuterThisJavaExpression.java b/checker/tests/index/OuterThisJavaExpression.java index 6b6180b31f4..9941ec5b55d 100644 --- a/checker/tests/index/OuterThisJavaExpression.java +++ b/checker/tests/index/OuterThisJavaExpression.java @@ -6,54 +6,54 @@ public abstract class OuterThisJavaExpression { - String s; + String s; - OuterThisJavaExpression(String s) { - this.s = s; - } + OuterThisJavaExpression(String s) { + this.s = s; + } - final class Inner { + final class Inner { - String s = "different from " + OuterThisJavaExpression.this.s; + String s = "different from " + OuterThisJavaExpression.this.s; - @SameLen("s") String f1() { - return s; - } + @SameLen("s") String f1() { + return s; + } - @SameLen("s") String f2() { - return this.s; - } + @SameLen("s") String f2() { + return this.s; + } - @SameLen("s") String f3() { - // :: error: (return.type.incompatible) - return OuterThisJavaExpression.this.s; - } + @SameLen("s") String f3() { + // :: error: (return.type.incompatible) + return OuterThisJavaExpression.this.s; + } - @SameLen("this.s") String f4() { - return s; - } + @SameLen("this.s") String f4() { + return s; + } - @SameLen("this.s") String f5() { - return this.s; - } + @SameLen("this.s") String f5() { + return this.s; + } - @SameLen("this.s") String f6() { - // :: error: (return.type.incompatible) - return OuterThisJavaExpression.this.s; - } + @SameLen("this.s") String f6() { + // :: error: (return.type.incompatible) + return OuterThisJavaExpression.this.s; + } - @SameLen("OuterThisJavaExpression.this.s") String f7() { - // :: error: (return.type.incompatible) - return s; - } + @SameLen("OuterThisJavaExpression.this.s") String f7() { + // :: error: (return.type.incompatible) + return s; + } - @SameLen("OuterThisJavaExpression.this.s") String f8() { - // :: error: (return.type.incompatible) - return this.s; - } + @SameLen("OuterThisJavaExpression.this.s") String f8() { + // :: error: (return.type.incompatible) + return this.s; + } - @SameLen("OuterThisJavaExpression.this.s") String f9() { - return OuterThisJavaExpression.this.s; + @SameLen("OuterThisJavaExpression.this.s") String f9() { + return OuterThisJavaExpression.this.s; + } } - } } diff --git a/checker/tests/index/ParserOffsetTest.java b/checker/tests/index/ParserOffsetTest.java index 125dea33c59..cbc088c306d 100644 --- a/checker/tests/index/ParserOffsetTest.java +++ b/checker/tests/index/ParserOffsetTest.java @@ -3,91 +3,91 @@ public class ParserOffsetTest { - public void subtraction1(String[] a, @IndexFor("#1") int i) { - int length = a.length; - if (i >= length - 1 || a[i + 1] == null) { - // body is irrelevant + public void subtraction1(String[] a, @IndexFor("#1") int i) { + int length = a.length; + if (i >= length - 1 || a[i + 1] == null) { + // body is irrelevant + } } - } - public void addition1(String[] a, @IndexFor("#1") int i) { - int length = a.length; - if ((i + 1) >= length || a[i + 1] == null) { - // body is irrelevant + public void addition1(String[] a, @IndexFor("#1") int i) { + int length = a.length; + if ((i + 1) >= length || a[i + 1] == null) { + // body is irrelevant + } } - } - public void subtraction2(String[] a, @IndexFor("#1") int i) { - if (i < a.length - 1) { - @IndexFor("a") int j = i + 1; + public void subtraction2(String[] a, @IndexFor("#1") int i) { + if (i < a.length - 1) { + @IndexFor("a") int j = i + 1; + } } - } - public void addition2(String[] a, @IndexFor("#1") int i) { - if ((i + 1) < a.length) { - @IndexFor("a") int j = i + 1; + public void addition2(String[] a, @IndexFor("#1") int i) { + if ((i + 1) < a.length) { + @IndexFor("a") int j = i + 1; + } } - } - public void addition3(String[] a, @IndexFor("#1") int i) { - if ((i + 5) < a.length) { - @IndexFor("a") int j = i + 5; + public void addition3(String[] a, @IndexFor("#1") int i) { + if ((i + 5) < a.length) { + @IndexFor("a") int j = i + 5; + } } - } - @SuppressWarnings("lowerbound") - public void subtraction3(String[] a, @NonNegative int k) { - if (k - 5 < a.length) { - String s = a[k - 5]; - @IndexFor("a") int j = k - 5; + @SuppressWarnings("lowerbound") + public void subtraction3(String[] a, @NonNegative int k) { + if (k - 5 < a.length) { + String s = a[k - 5]; + @IndexFor("a") int j = k - 5; + } } - } - @SuppressWarnings("lowerbound") - public void subtraction4(String[] a, @IndexFor("#1") int i) { - if (1 - i < a.length) { - // The error on this assignment is a false positive. - // :: error: (assignment.type.incompatible) - @IndexFor("a") int j = 1 - i; + @SuppressWarnings("lowerbound") + public void subtraction4(String[] a, @IndexFor("#1") int i) { + if (1 - i < a.length) { + // The error on this assignment is a false positive. + // :: error: (assignment.type.incompatible) + @IndexFor("a") int j = 1 - i; - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "a", offset = "1") int k = i; + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "a", offset = "1") int k = i; + } } - } - @SuppressWarnings("lowerbound") - public void subtraction5(String[] a, int i) { - if (1 - i < a.length) { - // :: error: (assignment.type.incompatible) - @IndexFor("a") int j = i; + @SuppressWarnings("lowerbound") + public void subtraction5(String[] a, int i) { + if (1 - i < a.length) { + // :: error: (assignment.type.incompatible) + @IndexFor("a") int j = i; + } } - } - @SuppressWarnings("lowerbound") - public void subtraction6(String[] a, int i, int j) { - if (i - j < a.length - 1) { - @IndexFor("a") int k = i - j; - // :: error: (assignment.type.incompatible) - @IndexFor("a") int k1 = i; + @SuppressWarnings("lowerbound") + public void subtraction6(String[] a, int i, int j) { + if (i - j < a.length - 1) { + @IndexFor("a") int k = i - j; + // :: error: (assignment.type.incompatible) + @IndexFor("a") int k1 = i; + } } - } - public void multiplication1(String[] a, int i, @Positive int j) { - if ((i * j) < (a.length + j)) { - // :: error: (assignment.type.incompatible) - @IndexFor("a") int k = i; - // :: error: (assignment.type.incompatible) - @IndexFor("a") int k1 = j; + public void multiplication1(String[] a, int i, @Positive int j) { + if ((i * j) < (a.length + j)) { + // :: error: (assignment.type.incompatible) + @IndexFor("a") int k = i; + // :: error: (assignment.type.incompatible) + @IndexFor("a") int k1 = j; + } } - } - public void multiplication2(String @ArrayLen(5) [] a, @IntVal(-2) int i, @IntVal(20) int j) { - if ((i * j) < (a.length - 20)) { - @LTLengthOf("a") int k1 = i; - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "a", offset = "20") int k2 = i; - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int k3 = j; + public void multiplication2(String @ArrayLen(5) [] a, @IntVal(-2) int i, @IntVal(20) int j) { + if ((i * j) < (a.length - 20)) { + @LTLengthOf("a") int k1 = i; + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "a", offset = "20") int k2 = i; + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int k3 = j; + } } - } } diff --git a/checker/tests/index/ParsingBug.java b/checker/tests/index/ParsingBug.java index 153e5595a78..d597ed21432 100644 --- a/checker/tests/index/ParsingBug.java +++ b/checker/tests/index/ParsingBug.java @@ -1,10 +1,10 @@ public class ParsingBug { - void test() { - String[] saOrig = new String[] {"foo", "bar"}; - Object o1 = do_things((Object) saOrig); - } + void test() { + String[] saOrig = new String[] {"foo", "bar"}; + Object o1 = do_things((Object) saOrig); + } - Object do_things(Object o) { - return o; - } + Object do_things(Object o) { + return o; + } } diff --git a/checker/tests/index/Pilot2HalfLength.java b/checker/tests/index/Pilot2HalfLength.java index 04696f17a65..601a7d5a564 100644 --- a/checker/tests/index/Pilot2HalfLength.java +++ b/checker/tests/index/Pilot2HalfLength.java @@ -3,11 +3,11 @@ // @skip-test until fixed public class Pilot2HalfLength { - private static int[] getFirstHalf(int[] array) { - int[] firstHalf = new int[array.length / 2]; - for (int i = 0; i < firstHalf.length; i++) { - firstHalf[i] = array[i]; + private static int[] getFirstHalf(int[] array) { + int[] firstHalf = new int[array.length / 2]; + for (int i = 0; i < firstHalf.length; i++) { + firstHalf[i] = array[i]; + } + return firstHalf; } - return firstHalf; - } } diff --git a/checker/tests/index/Pilot3ArrayCreation.java b/checker/tests/index/Pilot3ArrayCreation.java index 6494c07e4a2..493b6c36c3d 100644 --- a/checker/tests/index/Pilot3ArrayCreation.java +++ b/checker/tests/index/Pilot3ArrayCreation.java @@ -1,10 +1,10 @@ // This test case is for issue 44: https://github.com/kelloggm/checker-framework/issues/44 public class Pilot3ArrayCreation { - void test(int[] firstArray, int[] secondArray[]) { - int[] newArray = new int[firstArray.length + secondArray.length]; - for (int i = 0; i < firstArray.length; i++) { - newArray[i] = firstArray[i]; // or newArray[i] = secondArray[i]; + void test(int[] firstArray, int[] secondArray[]) { + int[] newArray = new int[firstArray.length + secondArray.length]; + for (int i = 0; i < firstArray.length; i++) { + newArray[i] = firstArray[i]; // or newArray[i] = secondArray[i]; + } } - } } diff --git a/checker/tests/index/Pilot4Subtraction.java b/checker/tests/index/Pilot4Subtraction.java index 36e85c4674d..534fe0ab60d 100644 --- a/checker/tests/index/Pilot4Subtraction.java +++ b/checker/tests/index/Pilot4Subtraction.java @@ -4,14 +4,14 @@ public class Pilot4Subtraction { - private static int[] getSecondHalf(int[] array) { - int len = array.length / 2; - int b = len - 1; - int[] arr = new int[len]; - for (int a = 0; a < len; a++) { - arr[a] = array[b]; - b--; + private static int[] getSecondHalf(int[] array) { + int len = array.length / 2; + int b = len - 1; + int[] arr = new int[len]; + for (int a = 0; a < len; a++) { + arr[a] = array[b]; + b--; + } + return arr; } - return arr; - } } diff --git a/checker/tests/index/PlumeFail.java b/checker/tests/index/PlumeFail.java index 12fe95a7ad9..1ae9cd296cb 100644 --- a/checker/tests/index/PlumeFail.java +++ b/checker/tests/index/PlumeFail.java @@ -1,18 +1,19 @@ // Test case affected by eisop Issue 22: // https://github.com/eisop/checker-framework/issues/22 -import java.util.Arrays; import org.checkerframework.common.value.qual.MinLen; +import java.util.Arrays; + public class PlumeFail { - void method() { - // Workaround by casting. - @SuppressWarnings({"index", "value"}) - String @MinLen(1) [] args = (String @MinLen(1) []) getArray(); - String[] argArray = Arrays.copyOfRange(args, 1, args.length); - } + void method() { + // Workaround by casting. + @SuppressWarnings({"index", "value"}) + String @MinLen(1) [] args = (String @MinLen(1) []) getArray(); + String[] argArray = Arrays.copyOfRange(args, 1, args.length); + } - String[] getArray() { - return null; - } + String[] getArray() { + return null; + } } diff --git a/checker/tests/index/PlumeFailMin.java b/checker/tests/index/PlumeFailMin.java index 8bf413a6414..bf5a3691e46 100644 --- a/checker/tests/index/PlumeFailMin.java +++ b/checker/tests/index/PlumeFailMin.java @@ -7,19 +7,19 @@ import org.checkerframework.common.value.qual.MinLen; abstract class PlumeFailMin { - void ok() { - String @MinLen(1) [] args = getArrayOk(); - @IndexOrHigh("args") int x = 1; - } + void ok() { + String @MinLen(1) [] args = getArrayOk(); + @IndexOrHigh("args") int x = 1; + } - abstract String @MinLen(1) [] getArrayOk(); + abstract String @MinLen(1) [] getArrayOk(); - void fail() { - // Workaround by casting. - @SuppressWarnings({"index", "value"}) - String @MinLen(1) [] args = (String @MinLen(1) []) getArrayFail(); - @IndexOrHigh("args") int x = 1; - } + void fail() { + // Workaround by casting. + @SuppressWarnings({"index", "value"}) + String @MinLen(1) [] args = (String @MinLen(1) []) getArrayFail(); + @IndexOrHigh("args") int x = 1; + } - abstract String[] getArrayFail(); + abstract String[] getArrayFail(); } diff --git a/checker/tests/index/PlusPlusBug.java b/checker/tests/index/PlusPlusBug.java index 16183384dfd..676ff8abaf7 100644 --- a/checker/tests/index/PlusPlusBug.java +++ b/checker/tests/index/PlusPlusBug.java @@ -1,14 +1,14 @@ import org.checkerframework.checker.index.qual.*; public class PlusPlusBug { - int[] array = {}; + int[] array = {}; - void test(@LTLengthOf("array") int x) { - // :: error: (unary.increment.type.incompatible) - x++; - // :: error: (unary.increment.type.incompatible) - ++x; - // :: error: (assignment.type.incompatible) - x = x + 1; - } + void test(@LTLengthOf("array") int x) { + // :: error: (unary.increment.type.incompatible) + x++; + // :: error: (unary.increment.type.incompatible) + ++x; + // :: error: (assignment.type.incompatible) + x = x + 1; + } } diff --git a/checker/tests/index/PolyCrash.java b/checker/tests/index/PolyCrash.java index 4db86d61e6b..121af3c1373 100644 --- a/checker/tests/index/PolyCrash.java +++ b/checker/tests/index/PolyCrash.java @@ -1,5 +1,5 @@ public class PolyCrash { - void test1(Integer integer) { - ++integer; - } + void test1(Integer integer) { + ++integer; + } } diff --git a/checker/tests/index/PolyLengthTest.java b/checker/tests/index/PolyLengthTest.java index 52205816989..379bd367d4e 100644 --- a/checker/tests/index/PolyLengthTest.java +++ b/checker/tests/index/PolyLengthTest.java @@ -2,19 +2,19 @@ import org.checkerframework.common.value.qual.*; public class PolyLengthTest { - int @PolyLength [] id(int @PolyLength [] a) { - return a; - } + int @PolyLength [] id(int @PolyLength [] a) { + return a; + } - int @SameLen("#2") [] test0(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { - return id(a); - } + int @SameLen("#2") [] test0(int @SameLen("#2") [] a, int @SameLen("#1") [] b) { + return id(a); + } - int @ArrayLen(3) [] test1(int @ArrayLen(3) [] a) { - return id(a); - } + int @ArrayLen(3) [] test1(int @ArrayLen(3) [] a) { + return id(a); + } - int @MinLen(3) [] test2(int @MinLen(3) [] a) { - return id(a); - } + int @MinLen(3) [] test2(int @MinLen(3) [] a) { + return id(a); + } } diff --git a/checker/tests/index/Polymorphic.java b/checker/tests/index/Polymorphic.java index 0585ba5835c..31f07063b0c 100644 --- a/checker/tests/index/Polymorphic.java +++ b/checker/tests/index/Polymorphic.java @@ -10,66 +10,66 @@ public class Polymorphic { - // Identity functions + // Identity functions - @PolyLowerBound int lbc_identity(@PolyLowerBound int a) { - return a; - } + @PolyLowerBound int lbc_identity(@PolyLowerBound int a) { + return a; + } - int @PolySameLen [] samelen_identity(int @PolySameLen [] a) { - int @SameLen("a") [] x = a; - return a; - } + int @PolySameLen [] samelen_identity(int @PolySameLen [] a) { + int @SameLen("a") [] x = a; + return a; + } - @PolyUpperBound int ubc_identity(@PolyUpperBound int a) { - return a; - } + @PolyUpperBound int ubc_identity(@PolyUpperBound int a) { + return a; + } - // SameLen tests - void samelen_id(int @SameLen("#2") [] a, int[] a2) { - int[] banana; - int @SameLen("a2") [] b = samelen_identity(a); - // :: error: (assignment.type.incompatible) - int @SameLen("banana") [] c = samelen_identity(b); - } + // SameLen tests + void samelen_id(int @SameLen("#2") [] a, int[] a2) { + int[] banana; + int @SameLen("a2") [] b = samelen_identity(a); + // :: error: (assignment.type.incompatible) + int @SameLen("banana") [] c = samelen_identity(b); + } - // UpperBound tests - void ubc_id( - int[] a, - int[] b, - @LTLengthOf("#1") int ai, - @LTEqLengthOf("#1") int al, - @LTLengthOf({"#1", "#2"}) int abi, - @LTEqLengthOf({"#1", "#2"}) int abl) { - int[] c; + // UpperBound tests + void ubc_id( + int[] a, + int[] b, + @LTLengthOf("#1") int ai, + @LTEqLengthOf("#1") int al, + @LTLengthOf({"#1", "#2"}) int abi, + @LTEqLengthOf({"#1", "#2"}) int abl) { + int[] c; - @LTLengthOf("a") int ai1 = ubc_identity(ai); - // :: error: (assignment.type.incompatible) - @LTLengthOf("b") int ai2 = ubc_identity(ai); + @LTLengthOf("a") int ai1 = ubc_identity(ai); + // :: error: (assignment.type.incompatible) + @LTLengthOf("b") int ai2 = ubc_identity(ai); - @LTEqLengthOf("a") int al1 = ubc_identity(al); - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int al2 = ubc_identity(al); + @LTEqLengthOf("a") int al1 = ubc_identity(al); + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int al2 = ubc_identity(al); - @LTLengthOf({"a", "b"}) int abi1 = ubc_identity(abi); - // :: error: (assignment.type.incompatible) - @LTLengthOf({"a", "b", "c"}) int abi2 = ubc_identity(abi); + @LTLengthOf({"a", "b"}) int abi1 = ubc_identity(abi); + // :: error: (assignment.type.incompatible) + @LTLengthOf({"a", "b", "c"}) int abi2 = ubc_identity(abi); - @LTEqLengthOf({"a", "b"}) int abl1 = ubc_identity(abl); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"a", "b", "c"}) int abl2 = ubc_identity(abl); - } + @LTEqLengthOf({"a", "b"}) int abl1 = ubc_identity(abl); + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"a", "b", "c"}) int abl2 = ubc_identity(abl); + } - // LowerBound tests - void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { - @NonNegative int an = lbc_identity(n); - // :: error: (assignment.type.incompatible) - @Positive int bn = lbc_identity(n); + // LowerBound tests + void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { + @NonNegative int an = lbc_identity(n); + // :: error: (assignment.type.incompatible) + @Positive int bn = lbc_identity(n); - @GTENegativeOne int ag = lbc_identity(g); - // :: error: (assignment.type.incompatible) - @NonNegative int bg = lbc_identity(g); + @GTENegativeOne int ag = lbc_identity(g); + // :: error: (assignment.type.incompatible) + @NonNegative int bg = lbc_identity(g); - @Positive int ap = lbc_identity(p); - } + @Positive int ap = lbc_identity(p); + } } diff --git a/checker/tests/index/Polymorphic2.java b/checker/tests/index/Polymorphic2.java index 6913fab2874..9ed046ec2de 100644 --- a/checker/tests/index/Polymorphic2.java +++ b/checker/tests/index/Polymorphic2.java @@ -8,46 +8,46 @@ import org.checkerframework.checker.index.qual.SameLen; public class Polymorphic2 { - public static boolean flag = false; - - int @PolySameLen [] mergeSameLen(int @PolySameLen [] a, int @PolySameLen [] b) { - return flag ? a : b; - } - - int[] array1 = new int[2]; - int[] array2 = new int[2]; - - void testSameLen(int @SameLen("array1") [] a, int @SameLen("array2") [] b) { - int[] x = mergeSameLen(a, b); - // :: error: (assignment.type.incompatible) - int @SameLen("array1") [] y = mergeSameLen(a, b); - } - - @PolyUpperBound int mergeUpperBound(@PolyUpperBound int a, @PolyUpperBound int b) { - return flag ? a : b; - } - - // UpperBound tests - void testUpperBound(@LTLengthOf("array1") int a, @LTLengthOf("array2") int b) { - int z = mergeUpperBound(a, b); - // :: error: (assignment.type.incompatible) - @LTLengthOf("array1") int zz = mergeUpperBound(a, b); - } - - void testUpperBound2(@LTLengthOf("array1") int a, @LTEqLengthOf("array1") int b) { - @LTEqLengthOf("array1") int z = mergeUpperBound(a, b); - // :: error: (assignment.type.incompatible) - @LTLengthOf("array1") int zz = mergeUpperBound(a, b); - } - - @PolyLowerBound int mergeLowerBound(@PolyLowerBound int a, @PolyLowerBound int b) { - return flag ? a : b; - } - - // LowerBound tests - void lbc_id(@NonNegative int n, @Positive int p) { - @NonNegative int z = mergeLowerBound(n, p); - // :: error: (assignment.type.incompatible) - @Positive int zz = mergeLowerBound(n, p); - } + public static boolean flag = false; + + int @PolySameLen [] mergeSameLen(int @PolySameLen [] a, int @PolySameLen [] b) { + return flag ? a : b; + } + + int[] array1 = new int[2]; + int[] array2 = new int[2]; + + void testSameLen(int @SameLen("array1") [] a, int @SameLen("array2") [] b) { + int[] x = mergeSameLen(a, b); + // :: error: (assignment.type.incompatible) + int @SameLen("array1") [] y = mergeSameLen(a, b); + } + + @PolyUpperBound int mergeUpperBound(@PolyUpperBound int a, @PolyUpperBound int b) { + return flag ? a : b; + } + + // UpperBound tests + void testUpperBound(@LTLengthOf("array1") int a, @LTLengthOf("array2") int b) { + int z = mergeUpperBound(a, b); + // :: error: (assignment.type.incompatible) + @LTLengthOf("array1") int zz = mergeUpperBound(a, b); + } + + void testUpperBound2(@LTLengthOf("array1") int a, @LTEqLengthOf("array1") int b) { + @LTEqLengthOf("array1") int z = mergeUpperBound(a, b); + // :: error: (assignment.type.incompatible) + @LTLengthOf("array1") int zz = mergeUpperBound(a, b); + } + + @PolyLowerBound int mergeLowerBound(@PolyLowerBound int a, @PolyLowerBound int b) { + return flag ? a : b; + } + + // LowerBound tests + void lbc_id(@NonNegative int n, @Positive int p) { + @NonNegative int z = mergeLowerBound(n, p); + // :: error: (assignment.type.incompatible) + @Positive int zz = mergeLowerBound(n, p); + } } diff --git a/checker/tests/index/Polymorphic3.java b/checker/tests/index/Polymorphic3.java index 541bb8eae16..5c29323b1c0 100644 --- a/checker/tests/index/Polymorphic3.java +++ b/checker/tests/index/Polymorphic3.java @@ -2,49 +2,49 @@ public class Polymorphic3 { - // Identity functions - - @PolyIndex int identity(@PolyIndex int a) { - return a; - } - - // UpperBound tests - void ubc_id( - int[] a, - int[] b, - @LTLengthOf("#1") int ai, - @LTEqLengthOf("#1") int al, - @LTLengthOf({"#1", "#2"}) int abi, - @LTEqLengthOf({"#1", "#2"}) int abl) { - int[] c; - - @LTLengthOf("a") int ai1 = identity(ai); - // :: error: (assignment.type.incompatible) - @LTLengthOf("b") int ai2 = identity(ai); - - @LTEqLengthOf("a") int al1 = identity(al); - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int al2 = identity(al); - - @LTLengthOf({"a", "b"}) int abi1 = identity(abi); - // :: error: (assignment.type.incompatible) - @LTLengthOf({"a", "b", "c"}) int abi2 = identity(abi); - - @LTEqLengthOf({"a", "b"}) int abl1 = identity(abl); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"a", "b", "c"}) int abl2 = identity(abl); - } - - // LowerBound tests - void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { - @NonNegative int an = identity(n); - // :: error: (assignment.type.incompatible) - @Positive int bn = identity(n); - - @GTENegativeOne int ag = identity(g); - // :: error: (assignment.type.incompatible) - @NonNegative int bg = identity(g); - - @Positive int ap = identity(p); - } + // Identity functions + + @PolyIndex int identity(@PolyIndex int a) { + return a; + } + + // UpperBound tests + void ubc_id( + int[] a, + int[] b, + @LTLengthOf("#1") int ai, + @LTEqLengthOf("#1") int al, + @LTLengthOf({"#1", "#2"}) int abi, + @LTEqLengthOf({"#1", "#2"}) int abl) { + int[] c; + + @LTLengthOf("a") int ai1 = identity(ai); + // :: error: (assignment.type.incompatible) + @LTLengthOf("b") int ai2 = identity(ai); + + @LTEqLengthOf("a") int al1 = identity(al); + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int al2 = identity(al); + + @LTLengthOf({"a", "b"}) int abi1 = identity(abi); + // :: error: (assignment.type.incompatible) + @LTLengthOf({"a", "b", "c"}) int abi2 = identity(abi); + + @LTEqLengthOf({"a", "b"}) int abl1 = identity(abl); + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"a", "b", "c"}) int abl2 = identity(abl); + } + + // LowerBound tests + void lbc_id(@NonNegative int n, @Positive int p, @GTENegativeOne int g) { + @NonNegative int an = identity(n); + // :: error: (assignment.type.incompatible) + @Positive int bn = identity(n); + + @GTENegativeOne int ag = identity(g); + // :: error: (assignment.type.incompatible) + @NonNegative int bg = identity(g); + + @Positive int ap = identity(p); + } } diff --git a/checker/tests/index/Polymorphic4.java b/checker/tests/index/Polymorphic4.java index f57677bc27e..c1b2fcc41e4 100644 --- a/checker/tests/index/Polymorphic4.java +++ b/checker/tests/index/Polymorphic4.java @@ -4,10 +4,10 @@ public class Polymorphic4 { - public static String @PolyValue [] quantify(String @PolyValue [] vars) { + public static String @PolyValue [] quantify(String @PolyValue [] vars) { - String[] result = new String[vars.length]; + String[] result = new String[vars.length]; - return result; - } + return result; + } } diff --git a/checker/tests/index/PreAndPostDec.java b/checker/tests/index/PreAndPostDec.java index ff876162a71..d50709f0ccc 100644 --- a/checker/tests/index/PreAndPostDec.java +++ b/checker/tests/index/PreAndPostDec.java @@ -1,34 +1,34 @@ public class PreAndPostDec { - void pre1(int[] args) { - int ii = 0; - while ((ii < args.length)) { - // :: error: (array.access.unsafe.high) - int m = args[++ii]; + void pre1(int[] args) { + int ii = 0; + while ((ii < args.length)) { + // :: error: (array.access.unsafe.high) + int m = args[++ii]; + } } - } - void pre2(int[] args) { - int ii = 0; - while ((ii < args.length)) { - ii++; - // :: error: (array.access.unsafe.high) - int m = args[ii]; + void pre2(int[] args) { + int ii = 0; + while ((ii < args.length)) { + ii++; + // :: error: (array.access.unsafe.high) + int m = args[ii]; + } } - } - void post1(int[] args) { - int ii = 0; - while ((ii < args.length)) { - int m = args[ii++]; + void post1(int[] args) { + int ii = 0; + while ((ii < args.length)) { + int m = args[ii++]; + } } - } - void post2(int[] args) { - int ii = 0; - while ((ii < args.length)) { - int m = args[ii]; - ii++; + void post2(int[] args) { + int ii = 0; + while ((ii < args.length)) { + int m = args[ii]; + ii++; + } } - } } diff --git a/checker/tests/index/PredecrementTest.java b/checker/tests/index/PredecrementTest.java index bc5e152d10b..a52ca2cf13d 100644 --- a/checker/tests/index/PredecrementTest.java +++ b/checker/tests/index/PredecrementTest.java @@ -3,39 +3,39 @@ public class PredecrementTest { - public static void warningForLoop(int @MinLen(1) [] a) { - for (int i = a.length; --i >= 0; ) { - a[i] = 0; + public static void warningForLoop(int @MinLen(1) [] a) { + for (int i = a.length; --i >= 0; ) { + a[i] = 0; + } } - } - public static void warningIfStatement(int @MinLen(1) [] a) { - int i = a.length; - if (--i >= 0) { - a[i] = 0; + public static void warningIfStatement(int @MinLen(1) [] a) { + int i = a.length; + if (--i >= 0) { + a[i] = 0; + } } - } - public static void warningIfStatementRange1( - int @MinLen(1) [] a, @IntRange(from = 1, to = 1) int j) { - int i = j; - if (--i >= 0) { - a[i] = 0; + public static void warningIfStatementRange1( + int @MinLen(1) [] a, @IntRange(from = 1, to = 1) int j) { + int i = j; + if (--i >= 0) { + a[i] = 0; + } } - } - public static void warningIfStatementVal1(int @MinLen(1) [] a, @IntVal(1) int j) { - int i = j; - if (--i >= 0) { - a[i] = 0; + public static void warningIfStatementVal1(int @MinLen(1) [] a, @IntVal(1) int j) { + int i = j; + if (--i >= 0) { + a[i] = 0; + } } - } - public static void warningIfStatementRange12( - int @MinLen(2) [] a, @IntRange(from = 1, to = 2) int j) { - int i = j; - if (--i >= 0) { - a[i] = 0; + public static void warningIfStatementRange12( + int @MinLen(2) [] a, @IntRange(from = 1, to = 2) int j) { + int i = j; + if (--i >= 0) { + a[i] = 0; + } } - } } diff --git a/checker/tests/index/PrimitiveWrappers.java b/checker/tests/index/PrimitiveWrappers.java index ab066fff05d..c07fb338ce2 100644 --- a/checker/tests/index/PrimitiveWrappers.java +++ b/checker/tests/index/PrimitiveWrappers.java @@ -7,12 +7,12 @@ public class PrimitiveWrappers { - void int_Integer_access_equivalent(@IndexFor("#3") Integer i, @IndexFor("#3") int j, int[] a) { - a[i] = a[j]; - } + void int_Integer_access_equivalent(@IndexFor("#3") Integer i, @IndexFor("#3") int j, int[] a) { + a[i] = a[j]; + } - void array_creation(@NonNegative Integer i, @NonNegative int j) { - int[] a = new int[j]; - int[] b = new int[i]; - } + void array_creation(@NonNegative Integer i, @NonNegative int j) { + int[] a = new int[j]; + int[] b = new int[i]; + } } diff --git a/checker/tests/index/PrivateSameLen.java b/checker/tests/index/PrivateSameLen.java index 1510a9fe2b2..653bb7601ef 100644 --- a/checker/tests/index/PrivateSameLen.java +++ b/checker/tests/index/PrivateSameLen.java @@ -3,13 +3,13 @@ public class PrivateSameLen { - @Pure - private @SameLen("#1") String getSameLenString(String in) { - return in; - } + @Pure + private @SameLen("#1") String getSameLenString(String in) { + return in; + } - private void test() { - String in = "foo"; - @SameLen("this.getSameLenString(in)") String myStr = getSameLenString(in); - } + private void test() { + String in = "foo"; + @SameLen("this.getSameLenString(in)") String myStr = getSameLenString(in); + } } diff --git a/checker/tests/index/RandomTest.java b/checker/tests/index/RandomTest.java index 14e3aa27476..85196ace814 100644 --- a/checker/tests/index/RandomTest.java +++ b/checker/tests/index/RandomTest.java @@ -1,14 +1,15 @@ -import java.util.Random; import org.checkerframework.checker.index.qual.LTLengthOf; +import java.util.Random; + public class RandomTest { - void test() { - Random rand = new Random(); - int[] a = new int[8]; - // :: error: (anno.on.irrelevant) - @LTLengthOf("a") double d1 = Math.random() * a.length; - @LTLengthOf("a") int deref = (int) (Math.random() * a.length); - @LTLengthOf("a") int deref2 = (int) (rand.nextDouble() * a.length); - @LTLengthOf("a") int deref3 = rand.nextInt(a.length); - } + void test() { + Random rand = new Random(); + int[] a = new int[8]; + // :: error: (anno.on.irrelevant) + @LTLengthOf("a") double d1 = Math.random() * a.length; + @LTLengthOf("a") int deref = (int) (Math.random() * a.length); + @LTLengthOf("a") int deref2 = (int) (rand.nextDouble() * a.length); + @LTLengthOf("a") int deref3 = rand.nextInt(a.length); + } } diff --git a/checker/tests/index/RandomTestLBC.java b/checker/tests/index/RandomTestLBC.java index 55ae50e8d2d..8e22657befc 100644 --- a/checker/tests/index/RandomTestLBC.java +++ b/checker/tests/index/RandomTestLBC.java @@ -1,18 +1,19 @@ -import java.util.Random; import org.checkerframework.checker.index.qual.NonNegative; +import java.util.Random; + public class RandomTestLBC { - void test() { - Random rand = new Random(); - int[] a = new int[8]; - // Math.random() and Math.nextDouble() are always non-negative, but the Index Checker - // does not reason about floating-point values. - // :: error: (anno.on.irrelevant) - @NonNegative double d1 = Math.random() * a.length; - // :: error: (assignment.type.incompatible) - @NonNegative int deref = (int) (Math.random() * a.length); - // :: error: (assignment.type.incompatible) - @NonNegative int deref2 = (int) (rand.nextDouble() * a.length); - @NonNegative int deref3 = rand.nextInt(a.length); - } + void test() { + Random rand = new Random(); + int[] a = new int[8]; + // Math.random() and Math.nextDouble() are always non-negative, but the Index Checker + // does not reason about floating-point values. + // :: error: (anno.on.irrelevant) + @NonNegative double d1 = Math.random() * a.length; + // :: error: (assignment.type.incompatible) + @NonNegative int deref = (int) (Math.random() * a.length); + // :: error: (assignment.type.incompatible) + @NonNegative int deref2 = (int) (rand.nextDouble() * a.length); + @NonNegative int deref3 = rand.nextInt(a.length); + } } diff --git a/checker/tests/index/RangeIndex.java b/checker/tests/index/RangeIndex.java index ba1740e6249..eb705b2db10 100644 --- a/checker/tests/index/RangeIndex.java +++ b/checker/tests/index/RangeIndex.java @@ -1,8 +1,8 @@ import org.checkerframework.common.value.qual.*; public class RangeIndex { - void foo(@IntRange(from = 0, to = 11) int x, int @MinLen(10) [] a) { - // :: error: (array.access.unsafe.high.range) - int y = a[x]; - } + void foo(@IntRange(from = 0, to = 11) int x, int @MinLen(10) [] a) { + // :: error: (array.access.unsafe.high.range) + int y = a[x]; + } } diff --git a/checker/tests/index/Reassignment.java b/checker/tests/index/Reassignment.java index fbec46c6466..0eb46be2598 100644 --- a/checker/tests/index/Reassignment.java +++ b/checker/tests/index/Reassignment.java @@ -3,11 +3,11 @@ // is worse than no solution and an obvious issue. public class Reassignment { - void test(int[] arr, int i) { - if (i > 0 && i < arr.length) { - arr = new int[0]; - // :: error: (array.access.unsafe.high) - int j = arr[i]; + void test(int[] arr, int i) { + if (i > 0 && i < arr.length) { + arr = new int[0]; + // :: error: (array.access.unsafe.high) + int j = arr[i]; + } } - } } diff --git a/checker/tests/index/RefineEq.java b/checker/tests/index/RefineEq.java index fea2b274ca1..5c4e13cdcc6 100644 --- a/checker/tests/index/RefineEq.java +++ b/checker/tests/index/RefineEq.java @@ -2,38 +2,38 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineEq { - int[] arr = {1}; + int[] arr = {1}; - void testLTL(@LTLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); + void testLTL(@LTLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); - int b = 1; - if (test == b) { - @LTLengthOf("arr") int c = b; + int b = 1; + if (test == b) { + @LTLengthOf("arr") int c = b; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int e = b; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int e = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - int b = 1; - if (test == b) { - @LTEqLengthOf("arr") int c = b; + int b = 1; + if (test == b) { + @LTEqLengthOf("arr") int c = b; - @LTLengthOf("arr") int g = b; - } else { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int e = b; + @LTLengthOf("arr") int g = b; + } else { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int e = b; + } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; - } } diff --git a/checker/tests/index/RefineGT.java b/checker/tests/index/RefineGT.java index abb87c3a390..a73613c6d7d 100644 --- a/checker/tests/index/RefineGT.java +++ b/checker/tests/index/RefineGT.java @@ -2,52 +2,52 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineGT { - int[] arr = {1}; - - void testLTL(@LTLengthOf("arr") int test) { - // The reason for the parsing is so that the Value Checker - // can't figure it out but normal humans can. - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test > b) { - @LTLengthOf("arr") int c = b; + int[] arr = {1}; + + void testLTL(@LTLengthOf("arr") int test) { + // The reason for the parsing is so that the Value Checker + // can't figure it out but normal humans can. + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test > b) { + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (a > b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (a > b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } - } - - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test > b) { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (a > b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test > b) { + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (a > b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } } - } } diff --git a/checker/tests/index/RefineGTE.java b/checker/tests/index/RefineGTE.java index 3f956a17f3e..766d11562e2 100644 --- a/checker/tests/index/RefineGTE.java +++ b/checker/tests/index/RefineGTE.java @@ -2,52 +2,52 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineGTE { - int[] arr = {1}; - - void testLTL(@LTLengthOf("arr") int test) { - // The reason for the parsing is so that the Value Checker - // can't figure it out but normal humans can. - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test >= b) { - @LTLengthOf("arr") int c = b; + int[] arr = {1}; + + void testLTL(@LTLengthOf("arr") int test) { + // The reason for the parsing is so that the Value Checker + // can't figure it out but normal humans can. + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test >= b) { + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (a >= b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (a >= b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } - } - - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (test >= b) { - @LTEqLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int c1 = b; - - if (a >= b) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (test >= b) { + @LTEqLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int c1 = b; + + if (a >= b) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; + } } - } } diff --git a/checker/tests/index/RefineLT.java b/checker/tests/index/RefineLT.java index b45c9fa395f..8412cd7fbfc 100644 --- a/checker/tests/index/RefineLT.java +++ b/checker/tests/index/RefineLT.java @@ -2,43 +2,43 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineLT { - int[] arr = {1}; + int[] arr = {1}; - void testLTL(@LTLengthOf("arr") int test, @LTLengthOf("arr") int a, @LTLengthOf("arr") int a3) { - int b = 2; - if (b < test) { - @LTLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; + void testLTL(@LTLengthOf("arr") int test, @LTLengthOf("arr") int a, @LTLengthOf("arr") int a3) { + int b = 2; + if (b < test) { + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; - if (b < a3) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; + if (b < a3) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } } - } - void testLTEL(@LTLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + void testLTEL(@LTLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - int b = 2; - if (b < test) { - @LTEqLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int c1 = b; + int b = 2; + if (b < test) { + @LTEqLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int c1 = b; - if (b < a) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; + if (b < a) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; + } } - } } diff --git a/checker/tests/index/RefineLTE.java b/checker/tests/index/RefineLTE.java index 50ea7c27cbc..bc0056aa3c8 100644 --- a/checker/tests/index/RefineLTE.java +++ b/checker/tests/index/RefineLTE.java @@ -2,52 +2,52 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineLTE { - int[] arr = {1}; - - void testLTL(@LTLengthOf("arr") int test) { - // The reason for the parsing is so that the Value Checker - // can't figure it out but normal humans can. - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); - - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (b <= test) { - @LTLengthOf("arr") int c = b; + int[] arr = {1}; + + void testLTL(@LTLengthOf("arr") int test) { + // The reason for the parsing is so that the Value Checker + // can't figure it out but normal humans can. + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (b <= test) { + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (b <= a) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (b <= a) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } - } - - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); - - int b = 2; - if (b <= test) { - @LTEqLengthOf("arr") int c = b; - } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int c1 = b; - - if (b <= a) { - int potato = 7; - } else { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a3 = Integer.parseInt("3"); + + int b = 2; + if (b <= test) { + @LTEqLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int c1 = b; + + if (b <= a) { + int potato = 7; + } else { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; + } } - } } diff --git a/checker/tests/index/RefineLTE2.java b/checker/tests/index/RefineLTE2.java index 435b24e9bf3..cdbcee45315 100644 --- a/checker/tests/index/RefineLTE2.java +++ b/checker/tests/index/RefineLTE2.java @@ -7,18 +7,18 @@ @SuppressWarnings("lowerbound") public class RefineLTE2 { - protected int @MinLen(1) [] values; + protected int @MinLen(1) [] values; - @LTEqLengthOf("values") int num_values; + @LTEqLengthOf("values") int num_values; - public void add(int elt) { - if (num_values == values.length) { - values = null; - // :: error: (unary.increment.type.incompatible) - num_values++; - return; + public void add(int elt) { + if (num_values == values.length) { + values = null; + // :: error: (unary.increment.type.incompatible) + num_values++; + return; + } + values[num_values] = elt; + num_values++; } - values[num_values] = elt; - num_values++; - } } diff --git a/checker/tests/index/RefineNeq.java b/checker/tests/index/RefineNeq.java index 9c138aa49b0..55327d513db 100644 --- a/checker/tests/index/RefineNeq.java +++ b/checker/tests/index/RefineNeq.java @@ -2,39 +2,39 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class RefineNeq { - int[] arr = {1}; + int[] arr = {1}; - void testLTL(@LTLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int a = Integer.parseInt("1"); + void testLTL(@LTLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int a = Integer.parseInt("1"); - int b = 1; - if (test != b) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int e = b; + int b = 1; + if (test != b) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int e = b; - } else { + } else { - @LTLengthOf("arr") int c = b; + @LTLengthOf("arr") int c = b; + } + // :: error: (assignment.type.incompatible) + @LTLengthOf("arr") int d = b; } - // :: error: (assignment.type.incompatible) - @LTLengthOf("arr") int d = b; - } - - void testLTEL(@LTEqLengthOf("arr") int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int a = Integer.parseInt("1"); - - int b = 1; - if (test != b) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int e = b; - } else { - @LTEqLengthOf("arr") int c = b; - - @LTLengthOf("arr") int g = b; + + void testLTEL(@LTEqLengthOf("arr") int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int a = Integer.parseInt("1"); + + int b = 1; + if (test != b) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int e = b; + } else { + @LTEqLengthOf("arr") int c = b; + + @LTLengthOf("arr") int g = b; + } + // :: error: (assignment.type.incompatible) + @LTEqLengthOf("arr") int d = b; } - // :: error: (assignment.type.incompatible) - @LTEqLengthOf("arr") int d = b; - } } diff --git a/checker/tests/index/RefineNeqLength.java b/checker/tests/index/RefineNeqLength.java index 7f3c54971c8..d11d3ce2b8d 100644 --- a/checker/tests/index/RefineNeqLength.java +++ b/checker/tests/index/RefineNeqLength.java @@ -9,73 +9,73 @@ import org.checkerframework.common.value.qual.IntVal; public class RefineNeqLength { - void refineNeqLength(int[] array, @IndexOrHigh("#1") int i) { - // Refines i <= array.length to i < array.length - if (i != array.length) { - refineNeqLengthMOne(array, i); + void refineNeqLength(int[] array, @IndexOrHigh("#1") int i) { + // Refines i <= array.length to i < array.length + if (i != array.length) { + refineNeqLengthMOne(array, i); + } + // No refinement + if (i != array.length - 1) { + // :: error: (argument.type.incompatible) + refineNeqLengthMOne(array, i); + } } - // No refinement - if (i != array.length - 1) { - // :: error: (argument.type.incompatible) - refineNeqLengthMOne(array, i); - } - } - void refineNeqLengthMOne(int[] array, @IndexFor("#1") int i) { - // Refines i < array.length to i < array.length - 1 - if (i != array.length - 1) { - refineNeqLengthMTwo(array, i); - // :: error: (argument.type.incompatible) - refineNeqLengthMThree(array, i); + void refineNeqLengthMOne(int[] array, @IndexFor("#1") int i) { + // Refines i < array.length to i < array.length - 1 + if (i != array.length - 1) { + refineNeqLengthMTwo(array, i); + // :: error: (argument.type.incompatible) + refineNeqLengthMThree(array, i); + } } - } - void refineNeqLengthMTwo(int[] array, @NonNegative @LTOMLengthOf("#1") int i) { - // Refines i < array.length - 1 to i < array.length - 2 - if (i != array.length - 2) { - refineNeqLengthMThree(array, i); - } - // No refinement - if (i != array.length - 1) { - // :: error: (argument.type.incompatible) - refineNeqLengthMThree(array, i); + void refineNeqLengthMTwo(int[] array, @NonNegative @LTOMLengthOf("#1") int i) { + // Refines i < array.length - 1 to i < array.length - 2 + if (i != array.length - 2) { + refineNeqLengthMThree(array, i); + } + // No refinement + if (i != array.length - 1) { + // :: error: (argument.type.incompatible) + refineNeqLengthMThree(array, i); + } } - } - void refineNeqLengthMTwoNonLiteral( - int[] array, - @NonNegative @LTOMLengthOf("#1") int i, - @IntVal(3) int c3, - @IntVal({2, 3}) int c23) { - // Refines i < array.length - 1 to i < array.length - 2 - if (i != array.length - (5 - c3)) { - refineNeqLengthMThree(array, i); - } - // No refinement - if (i != array.length - c23) { - // :: error: (argument.type.incompatible) - refineNeqLengthMThree(array, i); + void refineNeqLengthMTwoNonLiteral( + int[] array, + @NonNegative @LTOMLengthOf("#1") int i, + @IntVal(3) int c3, + @IntVal({2, 3}) int c23) { + // Refines i < array.length - 1 to i < array.length - 2 + if (i != array.length - (5 - c3)) { + refineNeqLengthMThree(array, i); + } + // No refinement + if (i != array.length - c23) { + // :: error: (argument.type.incompatible) + refineNeqLengthMThree(array, i); + } } - } - @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( - int[] array, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { - // Refines i < array.length - 2 to i < array.length - 3 - if (i != array.length - 3) { - return i; + @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( + int[] array, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { + // Refines i < array.length - 2 to i < array.length - 3 + if (i != array.length - 3) { + return i; + } + // :: error: (return.type.incompatible) + return i; } - // :: error: (return.type.incompatible) - return i; - } - // The same test for a string. - @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( - String str, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { - // Refines i < str.length() - 2 to i < str.length() - 3 - if (i != str.length() - 3) { - return i; + // The same test for a string. + @LTLengthOf(value = "#1", offset = "3") int refineNeqLengthMThree( + String str, @NonNegative @LTLengthOf(value = "#1", offset = "2") int i) { + // Refines i < str.length() - 2 to i < str.length() - 3 + if (i != str.length() - 3) { + return i; + } + // :: error: (return.type.incompatible) + return i; } - // :: error: (return.type.incompatible) - return i; - } } diff --git a/checker/tests/index/RefineSubtrahend.java b/checker/tests/index/RefineSubtrahend.java index ded8a7ef890..363878f8418 100644 --- a/checker/tests/index/RefineSubtrahend.java +++ b/checker/tests/index/RefineSubtrahend.java @@ -4,49 +4,49 @@ import org.checkerframework.checker.index.qual.NonNegative; public class RefineSubtrahend { - void withConstant(int[] a, @NonNegative int l) { - if (a.length - l > 10) { - int x = a[l + 10]; + void withConstant(int[] a, @NonNegative int l) { + if (a.length - l > 10) { + int x = a[l + 10]; + } + if (a.length - 10 > l) { + int x = a[l + 10]; + } + if (a.length - l >= 10) { + // :: error: (array.access.unsafe.high) + int x = a[l + 10]; + int x1 = a[l + 9]; + } } - if (a.length - 10 > l) { - int x = a[l + 10]; - } - if (a.length - l >= 10) { - // :: error: (array.access.unsafe.high) - int x = a[l + 10]; - int x1 = a[l + 9]; - } - } - void withVariable(int[] a, @NonNegative int l, @NonNegative int j, @NonNegative int k) { - if (a.length - l > j) { - if (k <= j) { - int x = a[l + k]; - } - } - if (a.length - j > l) { - if (k <= j) { - int x = a[l + k]; - } - } - if (a.length - j >= l) { - if (k <= j) { - // :: error: (array.access.unsafe.high) - int x = a[l + k]; - // :: error: (array.access.unsafe.low) - int x1 = a[l + k - 1]; - } + void withVariable(int[] a, @NonNegative int l, @NonNegative int j, @NonNegative int k) { + if (a.length - l > j) { + if (k <= j) { + int x = a[l + k]; + } + } + if (a.length - j > l) { + if (k <= j) { + int x = a[l + k]; + } + } + if (a.length - j >= l) { + if (k <= j) { + // :: error: (array.access.unsafe.high) + int x = a[l + k]; + // :: error: (array.access.unsafe.low) + int x1 = a[l + k - 1]; + } + } } - } - void cases(int[] a, @NonNegative int l) { - switch (a.length - l) { - case 1: - int x = a[l]; - break; - case 2: - int y = a[l + 1]; - break; + void cases(int[] a, @NonNegative int l) { + switch (a.length - l) { + case 1: + int x = a[l]; + break; + case 2: + int y = a[l + 1]; + break; + } } - } } diff --git a/checker/tests/index/RefinementEq.java b/checker/tests/index/RefinementEq.java index 3b96ef4b525..9ae58aa43fb 100644 --- a/checker/tests/index/RefinementEq.java +++ b/checker/tests/index/RefinementEq.java @@ -4,28 +4,28 @@ public class RefinementEq { - void test_equal(int a, int j, int s) { + void test_equal(int a, int j, int s) { - if (-1 == a) { - @GTENegativeOne int b = a; - } else { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int c = a; - } + if (-1 == a) { + @GTENegativeOne int b = a; + } else { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int c = a; + } - if (0 == j) { - @NonNegative int k = j; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int l = j; - } + if (0 == j) { + @NonNegative int k = j; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int l = j; + } - if (1 == s) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; + if (1 == s) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; + } } - } } // a comment diff --git a/checker/tests/index/RefinementGT.java b/checker/tests/index/RefinementGT.java index 086c3d1575e..f5704fbcdd1 100644 --- a/checker/tests/index/RefinementGT.java +++ b/checker/tests/index/RefinementGT.java @@ -4,58 +4,58 @@ public class RefinementGT { - void test_forward(int a, int j, int s) { - /** forwards greater than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (a > -1) { - /** a is NN now */ - @NonNegative int b = a; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (a > -1) { + /** a is NN now */ + @NonNegative int b = a; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int c = a; + } - if (j > 0) { - /** j is POS now */ - @Positive int k = j; - } else { - // :: error: (assignment.type.incompatible) - @Positive int l = j; - } + if (j > 0) { + /** j is POS now */ + @Positive int k = j; + } else { + // :: error: (assignment.type.incompatible) + @Positive int l = j; + } - if (s > 1) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; + if (s > 1) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; + } } - } - void test_backwards(int a, int j, int s) { - /** backwards greater than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 > a) { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int b = a; - } else { - @GTENegativeOne int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 > a) { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int b = a; + } else { + @GTENegativeOne int c = a; + } - if (0 > j) { - // :: error: (assignment.type.incompatible) - @NonNegative int k = j; - } else { - @NonNegative int l = j; - } + if (0 > j) { + // :: error: (assignment.type.incompatible) + @NonNegative int k = j; + } else { + @NonNegative int l = j; + } - if (1 > s) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; + if (1 > s) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; + } } - } } // a comment diff --git a/checker/tests/index/RefinementGTE.java b/checker/tests/index/RefinementGTE.java index 7ce66a80578..a929386ffb3 100644 --- a/checker/tests/index/RefinementGTE.java +++ b/checker/tests/index/RefinementGTE.java @@ -4,49 +4,49 @@ public class RefinementGTE { - void test_forward(int a, int j, int s) { - /** forwards greater than or equals */ - // :: error: (assignment.type.incompatible) - @GTENegativeOne int aa = a; - if (a >= -1) { - @GTENegativeOne int b = a; - } else { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than or equals */ + // :: error: (assignment.type.incompatible) + @GTENegativeOne int aa = a; + if (a >= -1) { + @GTENegativeOne int b = a; + } else { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int c = a; + } - if (j >= 0) { - @NonNegative int k = j; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int l = j; + if (j >= 0) { + @NonNegative int k = j; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int l = j; + } } - } - void test_backwards(int a, int j, int s) { - /** backwards greater than or equal */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 >= a) { - // :: error: (assignment.type.incompatible) - @NonNegative int b = a; - } else { - @NonNegative int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than or equal */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 >= a) { + // :: error: (assignment.type.incompatible) + @NonNegative int b = a; + } else { + @NonNegative int c = a; + } - if (0 >= j) { - // :: error: (assignment.type.incompatible) - @Positive int k = j; - } else { - @Positive int l = j; - } + if (0 >= j) { + // :: error: (assignment.type.incompatible) + @Positive int k = j; + } else { + @Positive int l = j; + } - if (1 >= s) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; + if (1 >= s) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; + } } - } } // a comment diff --git a/checker/tests/index/RefinementLT.java b/checker/tests/index/RefinementLT.java index 6cf7b75c7b8..388863c837b 100644 --- a/checker/tests/index/RefinementLT.java +++ b/checker/tests/index/RefinementLT.java @@ -4,56 +4,56 @@ public class RefinementLT { - void test_backwards(int a, int j, int s) { - /** backwards less than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 < a) { - @NonNegative int b = a; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 < a) { + @NonNegative int b = a; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int c = a; + } - if (0 < j) { - @Positive int k = j; - } else { - // :: error: (assignment.type.incompatible) - @Positive int l = j; - } + if (0 < j) { + @Positive int k = j; + } else { + // :: error: (assignment.type.incompatible) + @Positive int l = j; + } - if (1 < s) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; + if (1 < s) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; + } } - } - void test_forwards(int a, int j, int s) { - /** forwards less than */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (a < -1) { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int b = a; - } else { - @GTENegativeOne int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (a < -1) { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int b = a; + } else { + @GTENegativeOne int c = a; + } - if (j < 0) { - // :: error: (assignment.type.incompatible) - @NonNegative int k = j; - } else { - @NonNegative int l = j; - } + if (j < 0) { + // :: error: (assignment.type.incompatible) + @NonNegative int k = j; + } else { + @NonNegative int l = j; + } - if (s < 1) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; + if (s < 1) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; + } } - } } // a comment diff --git a/checker/tests/index/RefinementLTE.java b/checker/tests/index/RefinementLTE.java index 48c4c148d80..abe2342c4d2 100644 --- a/checker/tests/index/RefinementLTE.java +++ b/checker/tests/index/RefinementLTE.java @@ -4,56 +4,56 @@ public class RefinementLTE { - void test_backwards(int a, int j, int s) { - /** backwards less than or equals */ - // :: error: (assignment.type.incompatible) - @GTENegativeOne int aa = a; - if (-1 <= a) { - @GTENegativeOne int b = a; - } else { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than or equals */ + // :: error: (assignment.type.incompatible) + @GTENegativeOne int aa = a; + if (-1 <= a) { + @GTENegativeOne int b = a; + } else { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int c = a; + } - if (0 <= j) { - @NonNegative int k = j; - } else { - // :: error: (assignment.type.incompatible) - @NonNegative int l = j; - } + if (0 <= j) { + @NonNegative int k = j; + } else { + // :: error: (assignment.type.incompatible) + @NonNegative int l = j; + } - if (1 <= s) { - @Positive int t = s; - } else { - // :: error: (assignment.type.incompatible) - @Positive int u = s; + if (1 <= s) { + @Positive int t = s; + } else { + // :: error: (assignment.type.incompatible) + @Positive int u = s; + } } - } - void test_forwards(int a, int j, int s) { - /** forwards less than or equal */ - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (a <= -1) { - // :: error: (assignment.type.incompatible) - @NonNegative int b = a; - } else { - @NonNegative int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than or equal */ + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (a <= -1) { + // :: error: (assignment.type.incompatible) + @NonNegative int b = a; + } else { + @NonNegative int c = a; + } - if (j <= 0) { - // :: error: (assignment.type.incompatible) - @Positive int k = j; - } else { - @Positive int l = j; - } + if (j <= 0) { + // :: error: (assignment.type.incompatible) + @Positive int k = j; + } else { + @Positive int l = j; + } - if (s <= 1) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; + if (s <= 1) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; + } } - } } // a comment diff --git a/checker/tests/index/RefinementNEq.java b/checker/tests/index/RefinementNEq.java index 80b3e6c271e..7963bd6f535 100644 --- a/checker/tests/index/RefinementNEq.java +++ b/checker/tests/index/RefinementNEq.java @@ -4,30 +4,30 @@ public class RefinementNEq { - void test_not_equal(int a, int j, int s) { + void test_not_equal(int a, int j, int s) { - // :: error: (assignment.type.incompatible) - @NonNegative int aa = a; - if (-1 != a) { - // :: error: (assignment.type.incompatible) - @GTENegativeOne int b = a; - } else { - @GTENegativeOne int c = a; - } + // :: error: (assignment.type.incompatible) + @NonNegative int aa = a; + if (-1 != a) { + // :: error: (assignment.type.incompatible) + @GTENegativeOne int b = a; + } else { + @GTENegativeOne int c = a; + } - if (0 != j) { - // :: error: (assignment.type.incompatible) - @NonNegative int k = j; - } else { - @NonNegative int l = j; - } + if (0 != j) { + // :: error: (assignment.type.incompatible) + @NonNegative int k = j; + } else { + @NonNegative int l = j; + } - if (1 != s) { - // :: error: (assignment.type.incompatible) - @Positive int t = s; - } else { - @Positive int u = s; + if (1 != s) { + // :: error: (assignment.type.incompatible) + @Positive int t = s; + } else { + @Positive int u = s; + } } - } } // a comment diff --git a/checker/tests/index/ReflectArray.java b/checker/tests/index/ReflectArray.java index 126d836a744..a974f08d154 100644 --- a/checker/tests/index/ReflectArray.java +++ b/checker/tests/index/ReflectArray.java @@ -1,25 +1,26 @@ -import java.lang.reflect.Array; import org.checkerframework.common.value.qual.MinLen; +import java.lang.reflect.Array; + public class ReflectArray { - void testNewInstance(int i) { - // :: error: (argument.type.incompatible) - Array.newInstance(Object.class, i); - if (i >= 0) { - Array.newInstance(Object.class, i); + void testNewInstance(int i) { + // :: error: (argument.type.incompatible) + Array.newInstance(Object.class, i); + if (i >= 0) { + Array.newInstance(Object.class, i); + } } - } - void testFor(Object a) { - for (int i = 0; i < Array.getLength(a); ++i) { - Array.setInt(a, i, 1 + Array.getInt(a, i)); + void testFor(Object a) { + for (int i = 0; i < Array.getLength(a); ++i) { + Array.setInt(a, i, 1 + Array.getInt(a, i)); + } } - } - void testMinLen(Object @MinLen(1) [] a) { - Array.get(a, 0); - // :: error: (argument.type.incompatible) - Array.get(a, 1); - } + void testMinLen(Object @MinLen(1) [] a) { + Array.get(a, 0); + // :: error: (argument.type.incompatible) + Array.get(a, 1); + } } diff --git a/checker/tests/index/RegexMatcher.java b/checker/tests/index/RegexMatcher.java index cd3911910d7..fae3bcc7745 100644 --- a/checker/tests/index/RegexMatcher.java +++ b/checker/tests/index/RegexMatcher.java @@ -1,24 +1,25 @@ // Test case for Issue panacekcz#8: // https://github.com/panacekcz/checker-framework/issues/8 +import org.checkerframework.checker.index.qual.NonNegative; + import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.checkerframework.checker.index.qual.NonNegative; public class RegexMatcher { - static void m(String p, String s) { - Matcher matcher = Pattern.compile(p).matcher(s); - // The following line cannot be used as a test, because the relation of matcher to p is not - // tracked, so the upper bound is not known. + static void m(String p, String s) { + Matcher matcher = Pattern.compile(p).matcher(s); + // The following line cannot be used as a test, because the relation of matcher to p is not + // tracked, so the upper bound is not known. - // s.substring(matcher.start(), matcher.end()); + // s.substring(matcher.start(), matcher.end()); - @NonNegative int i; - i = matcher.start(); - i = matcher.end(); - // :: error: (assignment.type.incompatible) - i = matcher.start(1); - // :: error: (assignment.type.incompatible) - i = matcher.end(1); - } + @NonNegative int i; + i = matcher.start(); + i = matcher.end(); + // :: error: (assignment.type.incompatible) + i = matcher.start(1); + // :: error: (assignment.type.incompatible) + i = matcher.end(1); + } } diff --git a/checker/tests/index/RepeatLTLengthOf.java b/checker/tests/index/RepeatLTLengthOf.java index 03307ecb5b6..14565c74034 100644 --- a/checker/tests/index/RepeatLTLengthOf.java +++ b/checker/tests/index/RepeatLTLengthOf.java @@ -3,97 +3,105 @@ public class RepeatLTLengthOf { - protected String value1; - protected String value2; - protected String value3; - protected int v1; - protected int v2; - protected int v3; + protected String value1; + protected String value2; + protected String value3; + protected int v1; + protected int v2; + protected int v3; - public void func1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + public void func1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - public boolean func2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } + public boolean func2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void client1() { - withpostconditionsfunc1(); - } - - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean client2() { - return withcondpostconditionsfunc2(); - } - - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void client3() { - withpostconditionfunc1(); - } + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void client1() { + withpostconditionsfunc1(); + } - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true), + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean client4() { - return withcondpostconditionfunc2(); - } + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean client2() { + return withcondpostconditionsfunc2(); + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void withpostconditionsfunc1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void client3() { + withpostconditionfunc1(); + } - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean withcondpostconditionsfunc2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf( + expression = "v1", + targetValue = "value1", + offset = "2", + result = true), + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean client4() { + return withcondpostconditionfunc2(); + } - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2") @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") - public void withpostconditionfunc1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void withpostconditionsfunc1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true), + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "2", result = true) @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) - public boolean withcondpostconditionfunc2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean withcondpostconditionsfunc2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } + + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "2"), + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "1") + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "0") + public void withpostconditionfunc1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } + + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf( + expression = "v1", + targetValue = "value1", + offset = "2", + result = true), + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "1", result = true) + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "0", result = true) + public boolean withcondpostconditionfunc2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } } diff --git a/checker/tests/index/RepeatLTLengthOfWithError.java b/checker/tests/index/RepeatLTLengthOfWithError.java index 6eb05c17329..fd8b63a79a2 100644 --- a/checker/tests/index/RepeatLTLengthOfWithError.java +++ b/checker/tests/index/RepeatLTLengthOfWithError.java @@ -3,101 +3,109 @@ public class RepeatLTLengthOfWithError { - protected String value1; - protected String value2; - protected String value3; - protected int v1; - protected int v2; - protected int v3; + protected String value1; + protected String value2; + protected String value3; + protected int v1; + protected int v2; + protected int v3; - public void func1() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + public void func1() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - public boolean func2() { - v1 = value1.length() - 3; - v2 = value2.length() - 3; - v3 = value3.length() - 3; - return true; - } + public boolean func2() { + v1 = value1.length() - 3; + v2 = value2.length() - 3; + v3 = value3.length() - 3; + return true; + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - public void client1() { - withpostconditionsfunc1(); - } - - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean client2() { - return withcondpostconditionsfunc2(); - } - - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - public void client3() { - withpostconditionfunc1(); - } + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + public void client1() { + withpostconditionsfunc1(); + } - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true), + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean client4() { - return withcondpostconditionfunc2(); - } + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean client2() { + return withcondpostconditionsfunc2(); + } - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") - @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionsfunc1() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + public void client3() { + withpostconditionfunc1(); + } - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) - @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean withcondpostconditionsfunc2() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf( + expression = "v1", + targetValue = "value1", + offset = "3", + result = true), + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean client4() { + return withcondpostconditionfunc2(); + } - @EnsuresLTLengthOf.List({ - @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3") @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") - }) - @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionfunc1() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - } + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionsfunc1() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } - @EnsuresLTLengthOfIf.List({ - @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true), + @EnsuresLTLengthOfIf(expression = "v1", targetValue = "value1", offset = "3", result = true) @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) - }) - @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) - public boolean withcondpostconditionfunc2() { - v1 = value1.length() - 3; // condition not satisfied here - v2 = value2.length() - 3; - v3 = value3.length() - 3; - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean withcondpostconditionsfunc2() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresLTLengthOf.List({ + @EnsuresLTLengthOf(value = "v1", targetValue = "value1", offset = "3"), + @EnsuresLTLengthOf(value = "v2", targetValue = "value2", offset = "2") + }) + @EnsuresLTLengthOf(value = "v3", targetValue = "value3", offset = "1") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionfunc1() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + } + + @EnsuresLTLengthOfIf.List({ + @EnsuresLTLengthOfIf( + expression = "v1", + targetValue = "value1", + offset = "3", + result = true), + @EnsuresLTLengthOfIf(expression = "v2", targetValue = "value2", offset = "2", result = true) + }) + @EnsuresLTLengthOfIf(expression = "v3", targetValue = "value3", offset = "1", result = true) + public boolean withcondpostconditionfunc2() { + v1 = value1.length() - 3; // condition not satisfied here + v2 = value2.length() - 3; + v3 = value3.length() - 3; + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/index/Return.java b/checker/tests/index/Return.java index 71df9276000..008776c851a 100644 --- a/checker/tests/index/Return.java +++ b/checker/tests/index/Return.java @@ -1,5 +1,5 @@ public class Return { - int[] test() { - return null; - } + int[] test() { + return null; + } } diff --git a/checker/tests/index/SLSubtyping.java b/checker/tests/index/SLSubtyping.java index 848a546b76b..c99695bd776 100644 --- a/checker/tests/index/SLSubtyping.java +++ b/checker/tests/index/SLSubtyping.java @@ -3,22 +3,22 @@ // This test checks whether the SameLen type system works as expected. public class SLSubtyping { - int[] f = {1}; + int[] f = {1}; - void subtype(int @SameLen("#2") [] a, int[] b) { - int @SameLen({"a", "b"}) [] c = a; + void subtype(int @SameLen("#2") [] a, int[] b) { + int @SameLen({"a", "b"}) [] c = a; - // :: error: (assignment.type.incompatible) - int @SameLen("c") [] q = {1, 2}; - int @SameLen("c") [] d = q; + // :: error: (assignment.type.incompatible) + int @SameLen("c") [] q = {1, 2}; + int @SameLen("c") [] d = q; - // :: error: (assignment.type.incompatible) - int @SameLen("f") [] e = a; - } + // :: error: (assignment.type.incompatible) + int @SameLen("f") [] e = a; + } - void subtype2(int[] a, int @SameLen("#1") [] b) { - a = b; - int @SameLen("b") [] c = b; - int @SameLen("f") [] d = f; - } + void subtype2(int[] a, int @SameLen("#1") [] b) { + a = b; + int @SameLen("b") [] c = b; + int @SameLen("f") [] d = f; + } } diff --git a/checker/tests/index/SameLenAssignmentTransfer.java b/checker/tests/index/SameLenAssignmentTransfer.java index 850ed85bb91..f1fe52fccb4 100644 --- a/checker/tests/index/SameLenAssignmentTransfer.java +++ b/checker/tests/index/SameLenAssignmentTransfer.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.index.qual.*; public class SameLenAssignmentTransfer { - void transfer5(int @SameLen("#2") [] a, int[] b) { - int[] c = a; - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; + void transfer5(int @SameLen("#2") [] a, int[] b) { + int[] c = a; + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; + } } - } } diff --git a/checker/tests/index/SameLenEqualsRefinement.java b/checker/tests/index/SameLenEqualsRefinement.java index a19dd6d3eec..6af7a52c528 100644 --- a/checker/tests/index/SameLenEqualsRefinement.java +++ b/checker/tests/index/SameLenEqualsRefinement.java @@ -2,41 +2,41 @@ import org.checkerframework.dataflow.qual.Pure; public class SameLenEqualsRefinement { - void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { - if (a == c) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; - } + void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { + if (a == c) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; + } + } } - } - void transfer4(int[] a, int[] b, int[] c) { - if (b == c) { - if (a == b) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - a[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; + void transfer4(int[] a, int[] b, int[] c) { + if (b == c) { + if (a == b) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + a[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; + } + } } - } } - } - void transfer5(int[] a, int[] b, int[] c, int[] d) { - if (a == b && b == c) { - int[] x = a; - int[] y = x; - int index = x.length - 1; - if (index > 0) { - f(a[index]); - f(b[index]); - f(c[index]); - f(x[index]); - f(y[index]); - } + void transfer5(int[] a, int[] b, int[] c, int[] d) { + if (a == b && b == c) { + int[] x = a; + int[] y = x; + int index = x.length - 1; + if (index > 0) { + f(a[index]); + f(b[index]); + f(c[index]); + f(x[index]); + f(y[index]); + } + } } - } - @Pure - void f(Object o) {} + @Pure + void f(Object o) {} } diff --git a/checker/tests/index/SameLenFormalParameter2.java b/checker/tests/index/SameLenFormalParameter2.java index 1c4b333ef09..4e9d881c66e 100644 --- a/checker/tests/index/SameLenFormalParameter2.java +++ b/checker/tests/index/SameLenFormalParameter2.java @@ -2,10 +2,10 @@ public class SameLenFormalParameter2 { - void lib(Object @SameLen({"#1", "#2"}) [] valsArg, int @SameLen({"#1", "#2"}) [] modsArg) {} + void lib(Object @SameLen({"#1", "#2"}) [] valsArg, int @SameLen({"#1", "#2"}) [] modsArg) {} - void client(Object[] myvals, int[] mymods) { - // :: error: (argument.type.incompatible) - lib(myvals, mymods); - } + void client(Object[] myvals, int[] mymods) { + // :: error: (argument.type.incompatible) + lib(myvals, mymods); + } } diff --git a/checker/tests/index/SameLenIrrelevant.java b/checker/tests/index/SameLenIrrelevant.java index c59ceefe46a..103f92aabda 100644 --- a/checker/tests/index/SameLenIrrelevant.java +++ b/checker/tests/index/SameLenIrrelevant.java @@ -8,18 +8,18 @@ import org.checkerframework.checker.index.qual.SameLen; public class SameLenIrrelevant { - // NO :: error: (anno.on.irrelevant) - public void test(@SameLen("#2") int x, int y) { - // do nothing - } + // NO :: error: (anno.on.irrelevant) + public void test(@SameLen("#2") int x, int y) { + // do nothing + } - // NO :: error: (anno.on.irrelevant) - public void test(@SameLen("#2") double x, double y) { - // do nothing - } + // NO :: error: (anno.on.irrelevant) + public void test(@SameLen("#2") double x, double y) { + // do nothing + } - // NO :: error: (anno.on.irrelevant) - public void test(@SameLen("#2") char x, char y) { - // do nothing - } + // NO :: error: (anno.on.irrelevant) + public void test(@SameLen("#2") char x, char y) { + // do nothing + } } diff --git a/checker/tests/index/SameLenLUBStrangeness.java b/checker/tests/index/SameLenLUBStrangeness.java index d258ca2ca1d..4d432f134e3 100644 --- a/checker/tests/index/SameLenLUBStrangeness.java +++ b/checker/tests/index/SameLenLUBStrangeness.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.index.qual.SameLen; public class SameLenLUBStrangeness { - void test(int[] a, boolean cond) { - int[] b; - if (cond) { - b = a; + void test(int[] a, boolean cond) { + int[] b; + if (cond) { + b = a; + } + // :: error: (assignment.type.incompatible) + int @SameLen({"a", "b"}) [] c = a; } - // :: error: (assignment.type.incompatible) - int @SameLen({"a", "b"}) [] c = a; - } } diff --git a/checker/tests/index/SameLenManyArrays.java b/checker/tests/index/SameLenManyArrays.java index db502c46ff1..0e0c519c36b 100644 --- a/checker/tests/index/SameLenManyArrays.java +++ b/checker/tests/index/SameLenManyArrays.java @@ -2,55 +2,55 @@ import org.checkerframework.dataflow.qual.Pure; public class SameLenManyArrays { - void transfer1(int @SameLen("#2") [] a, int[] b) { - int[] c = new int[a.length]; - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; + void transfer1(int @SameLen("#2") [] a, int[] b) { + int[] c = new int[a.length]; + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; + } } - } - void transfer2(int @SameLen("#2") [] a, int[] b) { - for (int i = 0; i < b.length; i++) { // i's type is @LTL("b") - a[i] = 1; + void transfer2(int @SameLen("#2") [] a, int[] b) { + for (int i = 0; i < b.length; i++) { // i's type is @LTL("b") + a[i] = 1; + } } - } - void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { - if (a.length == c.length) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - b[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; - } + void transfer3(int @SameLen("#2") [] a, int[] b, int[] c) { + if (a.length == c.length) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + b[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; + } + } } - } - void transfer4(int[] a, int[] b, int[] c) { - if (b.length == c.length) { - if (a.length == b.length) { - for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") - a[i] = 1; - int @SameLen({"a", "b", "c"}) [] d = c; + void transfer4(int[] a, int[] b, int[] c) { + if (b.length == c.length) { + if (a.length == b.length) { + for (int i = 0; i < c.length; i++) { // i's type is @LTL("c") + a[i] = 1; + int @SameLen({"a", "b", "c"}) [] d = c; + } + } } - } } - } - void transfer5(int[] a, int[] b, int[] c, int[] d) { - if (a.length == b.length && b.length == c.length) { - int[] x = a; - int[] y = x; - int index = x.length - 1; - if (index > 0) { - f(a[index]); - f(b[index]); - f(c[index]); - f(x[index]); - f(y[index]); - } + void transfer5(int[] a, int[] b, int[] c, int[] d) { + if (a.length == b.length && b.length == c.length) { + int[] x = a; + int[] y = x; + int index = x.length - 1; + if (index > 0) { + f(a[index]); + f(b[index]); + f(c[index]); + f(x[index]); + f(y[index]); + } + } } - } - @Pure - void f(Object o) {} + @Pure + void f(Object o) {} } diff --git a/checker/tests/index/SameLenNewArrayWithSameLength.java b/checker/tests/index/SameLenNewArrayWithSameLength.java index 346143121a0..e4bd9654746 100644 --- a/checker/tests/index/SameLenNewArrayWithSameLength.java +++ b/checker/tests/index/SameLenNewArrayWithSameLength.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.index.qual.*; public class SameLenNewArrayWithSameLength { - public void m1(int[] a) { - int @SameLen("a") [] b = new int[a.length]; - } + public void m1(int[] a) { + int @SameLen("a") [] b = new int[a.length]; + } - public void m2(int[] a, int @SameLen("#1") [] b) { - int @SameLen({"a", "b"}) [] c = new int[b.length]; - } + public void m2(int[] a, int @SameLen("#1") [] b) { + int @SameLen({"a", "b"}) [] c = new int[b.length]; + } } diff --git a/checker/tests/index/SameLenOnFormalParameter.java b/checker/tests/index/SameLenOnFormalParameter.java index bef9d7dd0d9..b8bb360cdf9 100644 --- a/checker/tests/index/SameLenOnFormalParameter.java +++ b/checker/tests/index/SameLenOnFormalParameter.java @@ -3,26 +3,26 @@ import org.checkerframework.checker.index.qual.*; public class SameLenOnFormalParameter { - public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} + public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} - public void requiresSameLen2(@SameLen("#2") String x2, String y2) {} + public void requiresSameLen2(@SameLen("#2") String x2, String y2) {} - public void m1(@SameLen("#2") String a1, String b1) { - requiresSameLen1(a1, b1); - } + public void m1(@SameLen("#2") String a1, String b1) { + requiresSameLen1(a1, b1); + } - public void m2(@SameLen("#2") String a2, String b2) { - @SameLen("a2") String b22 = b2; - requiresSameLen1(a2, b22); - } + public void m2(@SameLen("#2") String a2, String b2) { + @SameLen("a2") String b22 = b2; + requiresSameLen1(a2, b22); + } - public void m3(@SameLen("#2") String a3, String b3) { - @SameLen("b3") String a2 = a3; - @SameLen("a3") String b32 = b3; - requiresSameLen1(a3, b32); - } + public void m3(@SameLen("#2") String a3, String b3) { + @SameLen("b3") String a2 = a3; + @SameLen("a3") String b32 = b3; + requiresSameLen1(a3, b32); + } - public void m4(@SameLen("#2") String a4, String b4) { - requiresSameLen2(a4, b4); - } + public void m4(@SameLen("#2") String a4, String b4) { + requiresSameLen2(a4, b4); + } } diff --git a/checker/tests/index/SameLenOnFormalParameterSimple.java b/checker/tests/index/SameLenOnFormalParameterSimple.java index a067e975fd5..53e11cd1ae1 100644 --- a/checker/tests/index/SameLenOnFormalParameterSimple.java +++ b/checker/tests/index/SameLenOnFormalParameterSimple.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.index.qual.SameLen; public class SameLenOnFormalParameterSimple { - public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} + public void requiresSameLen1(String x1, @SameLen("#1") String y1) {} - public void m1(@SameLen("#2") String a1, String b1) { - requiresSameLen1(a1, b1); - } + public void m1(@SameLen("#2") String a1, String b1) { + requiresSameLen1(a1, b1); + } } diff --git a/checker/tests/index/SameLenSelf.java b/checker/tests/index/SameLenSelf.java index 1eb337164d9..f4740b26d37 100644 --- a/checker/tests/index/SameLenSelf.java +++ b/checker/tests/index/SameLenSelf.java @@ -3,12 +3,12 @@ import org.checkerframework.checker.index.qual.*; public class SameLenSelf { - int @SameLen("this.field") [] field = new int[10]; - int @SameLen("field2") [] field2 = new int[10]; - int @SameLen("field3") [] field3 = field2; + int @SameLen("this.field") [] field = new int[10]; + int @SameLen("field2") [] field2 = new int[10]; + int @SameLen("field3") [] field3 = field2; - void foo(int[] b) { - int @SameLen("a") [] a = b; - int @SameLen("c") [] c = new int[10]; - } + void foo(int[] b) { + int @SameLen("a") [] a = b; + int @SameLen("c") [] c = new int[10]; + } } diff --git a/checker/tests/index/SameLenSimpleCase.java b/checker/tests/index/SameLenSimpleCase.java index eef3a5c6ded..dceaf6dc163 100644 --- a/checker/tests/index/SameLenSimpleCase.java +++ b/checker/tests/index/SameLenSimpleCase.java @@ -1,13 +1,13 @@ public class SameLenSimpleCase { - public int compare(int[] a1, int[] a2) { - if (a1.length != a2.length) { - return a1.length - a2.length; + public int compare(int[] a1, int[] a2) { + if (a1.length != a2.length) { + return a1.length - a2.length; + } + for (int i = 0; i < a1.length; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); + } + } + return 0; } - for (int i = 0; i < a1.length; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); - } - } - return 0; - } } diff --git a/checker/tests/index/SameLenTripleThreat.java b/checker/tests/index/SameLenTripleThreat.java index 6bd016a6815..4051099b3f7 100644 --- a/checker/tests/index/SameLenTripleThreat.java +++ b/checker/tests/index/SameLenTripleThreat.java @@ -1,32 +1,32 @@ import org.checkerframework.checker.index.qual.*; public class SameLenTripleThreat { - public void foo(String[] vars) { - String[] qrets = new String[vars.length]; - String @SameLen("vars") [] y = qrets; - String[] indices = new String[vars.length]; - String @SameLen("qrets") [] x = indices; - } + public void foo(String[] vars) { + String[] qrets = new String[vars.length]; + String @SameLen("vars") [] y = qrets; + String[] indices = new String[vars.length]; + String @SameLen("qrets") [] x = indices; + } - String[] indices; + String[] indices; - public void foo2(String... vars) { - String[] qrets = new String[vars.length]; - indices = new String[vars.length]; - String[] indicesLocal = new String[vars.length]; - for (int i = 0; i < qrets.length; i++) { - indices[i] = "hello"; - indicesLocal[i] = "hello"; + public void foo2(String... vars) { + String[] qrets = new String[vars.length]; + indices = new String[vars.length]; + String[] indicesLocal = new String[vars.length]; + for (int i = 0; i < qrets.length; i++) { + indices[i] = "hello"; + indicesLocal[i] = "hello"; + } } - } - public void foo3(String... vars) { - String[] qrets = new String[vars.length]; - String[] indicesLocal = new String[vars.length]; - indices = new String[vars.length]; - for (int i = 0; i < qrets.length; i++) { - indices[i] = "hello"; - indicesLocal[i] = "hello"; + public void foo3(String... vars) { + String[] qrets = new String[vars.length]; + String[] indicesLocal = new String[vars.length]; + indices = new String[vars.length]; + for (int i = 0; i < qrets.length; i++) { + indices[i] = "hello"; + indicesLocal[i] = "hello"; + } } - } } diff --git a/checker/tests/index/SameLenWithObjects.java b/checker/tests/index/SameLenWithObjects.java index 9b1c96b16ce..60165d4cd9d 100644 --- a/checker/tests/index/SameLenWithObjects.java +++ b/checker/tests/index/SameLenWithObjects.java @@ -2,18 +2,18 @@ public class SameLenWithObjects { - class SimpleCollection { - Object[] var_infos; - } + class SimpleCollection { + Object[] var_infos; + } - static final class Invocation1 { - SimpleCollection sc; - Object @SameLen({"vals1", "this.sc.var_infos"}) [] vals1; + static final class Invocation1 { + SimpleCollection sc; + Object @SameLen({"vals1", "this.sc.var_infos"}) [] vals1; - void format1() { - for (int j = 0; j < vals1.length; j++) { - System.out.println(sc.var_infos[j]); - } + void format1() { + for (int j = 0; j < vals1.length; j++) { + System.out.println(sc.var_infos[j]); + } + } } - } } diff --git a/checker/tests/index/SearchIndexTests.java b/checker/tests/index/SearchIndexTests.java index 4a5766090fe..9cfce47efa8 100644 --- a/checker/tests/index/SearchIndexTests.java +++ b/checker/tests/index/SearchIndexTests.java @@ -1,54 +1,55 @@ -import java.util.Arrays; import org.checkerframework.checker.index.qual.*; +import java.util.Arrays; + public class SearchIndexTests { - public void test(short[] a, short instant) { - int i = Arrays.binarySearch(a, instant); - @SearchIndexFor("a") int z = i; - // :: error: (assignment.type.incompatible) - @SearchIndexFor("a") int y = 7; - @LTLengthOf("a") int x = i; - } + public void test(short[] a, short instant) { + int i = Arrays.binarySearch(a, instant); + @SearchIndexFor("a") int z = i; + // :: error: (assignment.type.incompatible) + @SearchIndexFor("a") int y = 7; + @LTLengthOf("a") int x = i; + } - void test2(int[] a, @SearchIndexFor("#1") int xyz) { - if (0 > xyz) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; + void test2(int[] a, @SearchIndexFor("#1") int xyz) { + if (0 > xyz) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; + } } - } - void test3(int[] a, @SearchIndexFor("#1") int xyz) { - if (-1 >= xyz) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; + void test3(int[] a, @SearchIndexFor("#1") int xyz) { + if (-1 >= xyz) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; + } } - } - void test4(int[] a, @SearchIndexFor("#1") int xyz) { - if (xyz < 0) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; + void test4(int[] a, @SearchIndexFor("#1") int xyz) { + if (xyz < 0) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; + } } - } - void test5(int[] a, @SearchIndexFor("#1") int xyz) { - if (xyz <= -1) { - @NegativeIndexFor("a") int w = xyz; - @NonNegative int y = ~xyz; - @LTEqLengthOf("a") int z = ~xyz; + void test5(int[] a, @SearchIndexFor("#1") int xyz) { + if (xyz <= -1) { + @NegativeIndexFor("a") int w = xyz; + @NonNegative int y = ~xyz; + @LTEqLengthOf("a") int z = ~xyz; + } } - } - void subtyping1( - @SearchIndexFor({"#3", "#4"}) int x, @NegativeIndexFor("#3") int y, int[] a, int[] b) { - // :: error: (assignment.type.incompatible) - @SearchIndexFor({"a", "b"}) int z = y; - @SearchIndexFor("a") int w = y; - @SearchIndexFor("b") int p = x; - // :: error: (assignment.type.incompatible) - @NegativeIndexFor({"a", "b"}) int q = x; - } + void subtyping1( + @SearchIndexFor({"#3", "#4"}) int x, @NegativeIndexFor("#3") int y, int[] a, int[] b) { + // :: error: (assignment.type.incompatible) + @SearchIndexFor({"a", "b"}) int z = y; + @SearchIndexFor("a") int w = y; + @SearchIndexFor("b") int p = x; + // :: error: (assignment.type.incompatible) + @NegativeIndexFor({"a", "b"}) int q = x; + } } diff --git a/checker/tests/index/ShiftRight.java b/checker/tests/index/ShiftRight.java index d8aef910948..038b9f6667c 100644 --- a/checker/tests/index/ShiftRight.java +++ b/checker/tests/index/ShiftRight.java @@ -6,22 +6,22 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class ShiftRight { - void indexFor(Object[] a, @IndexFor("#1") int i) { - @IndexFor("a") int o = i >> 2; - @IndexFor("a") int p = i >>> 2; - } + void indexFor(Object[] a, @IndexFor("#1") int i) { + @IndexFor("a") int o = i >> 2; + @IndexFor("a") int p = i >>> 2; + } - void indexOrHigh(Object[] a, @IndexOrHigh("#1") int i) { - @IndexOrHigh("a") int o = i >> 2; - @IndexOrHigh("a") int p = i >>> 2; - // Not true if a.length == 0 - // :: error: (assignment.type.incompatible) - @IndexFor("a") int q = i >> 2; - } + void indexOrHigh(Object[] a, @IndexOrHigh("#1") int i) { + @IndexOrHigh("a") int o = i >> 2; + @IndexOrHigh("a") int p = i >>> 2; + // Not true if a.length == 0 + // :: error: (assignment.type.incompatible) + @IndexFor("a") int q = i >> 2; + } - void negative(Object[] a, @LTLengthOf(value = "#1", offset = "100") int i) { - // Not true for some negative i - // :: error: (assignment.type.incompatible) - @LTLengthOf(value = "#1", offset = "100") int q = i >> 2; - } + void negative(Object[] a, @LTLengthOf(value = "#1", offset = "100") int i) { + // Not true for some negative i + // :: error: (assignment.type.incompatible) + @LTLengthOf(value = "#1", offset = "100") int q = i >> 2; + } } diff --git a/checker/tests/index/ShiftRightAverage.java b/checker/tests/index/ShiftRightAverage.java index 174b2239201..d78edd8466b 100644 --- a/checker/tests/index/ShiftRightAverage.java +++ b/checker/tests/index/ShiftRightAverage.java @@ -5,12 +5,12 @@ import org.checkerframework.checker.index.qual.LTLengthOf; public class ShiftRightAverage { - public static void m(Object[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @IndexFor("#1") int k = (i + j) >> 1; - } + public static void m(Object[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @IndexFor("#1") int k = (i + j) >> 1; + } - public static void m2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - // :: error: (assignment.type.incompatible) - @LTLengthOf("a") int h = ((i + 1) + j) >> 1; - } + public static void m2(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + // :: error: (assignment.type.incompatible) + @LTLengthOf("a") int h = ((i + 1) + j) >> 1; + } } diff --git a/checker/tests/index/SimpleCollection.java b/checker/tests/index/SimpleCollection.java index e263534502d..24a2da1d8ab 100644 --- a/checker/tests/index/SimpleCollection.java +++ b/checker/tests/index/SimpleCollection.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.index.qual.*; public class SimpleCollection { - private int[] values; + private int[] values; - @IndexOrHigh("values") int size() { - return values.length; - } - - void interact_with_other(SimpleCollection other) { - int[] othervalues = other.values; - int @SameLen("other.values") [] x = othervalues; - for (int i = 0; i < other.size(); i++) { - int k = othervalues[i]; + @IndexOrHigh("values") int size() { + return values.length; } - for (int j = 0; j < other.size(); j++) { - int k = other.values[j]; + + void interact_with_other(SimpleCollection other) { + int[] othervalues = other.values; + int @SameLen("other.values") [] x = othervalues; + for (int i = 0; i < other.size(); i++) { + int k = othervalues[i]; + } + for (int j = 0; j < other.size(); j++) { + int k = other.values[j]; + } } - } } diff --git a/checker/tests/index/SimpleTransferAdd.java b/checker/tests/index/SimpleTransferAdd.java index a037c0e45fb..38b5a330183 100644 --- a/checker/tests/index/SimpleTransferAdd.java +++ b/checker/tests/index/SimpleTransferAdd.java @@ -2,16 +2,16 @@ import org.checkerframework.checker.index.qual.Positive; public class SimpleTransferAdd { - void test() { - int bs = -1; - // :: error: (assignment.type.incompatible) - @NonNegative int es = bs; + void test() { + int bs = -1; + // :: error: (assignment.type.incompatible) + @NonNegative int es = bs; - // @NonNegative int ds = 2 + bs; - int ds = 0; - // :: error: (assignment.type.incompatible) - @Positive int cs = ds++; - @Positive int fs = ds; - } + // @NonNegative int ds = 2 + bs; + int ds = 0; + // :: error: (assignment.type.incompatible) + @Positive int cs = ds++; + @Positive int fs = ds; + } } // a comment diff --git a/checker/tests/index/SimpleTransferSub.java b/checker/tests/index/SimpleTransferSub.java index 96769ae0541..1546726c8e4 100644 --- a/checker/tests/index/SimpleTransferSub.java +++ b/checker/tests/index/SimpleTransferSub.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.index.qual.Positive; public class SimpleTransferSub { - void test() { - // shows a bug in the Checker Framework. I don't think we can get around this bit... - int bs = 0; - // :: error: (assignment.type.incompatible) - @Positive int ds = bs--; - } + void test() { + // shows a bug in the Checker Framework. I don't think we can get around this bit... + int bs = 0; + // :: error: (assignment.type.incompatible) + @Positive int ds = bs--; + } } // a comment diff --git a/checker/tests/index/SizeVsLength.java b/checker/tests/index/SizeVsLength.java index 4459461d718..00208a61d3d 100644 --- a/checker/tests/index/SizeVsLength.java +++ b/checker/tests/index/SizeVsLength.java @@ -4,11 +4,11 @@ public class SizeVsLength { - public int[] getArray(@NonNegative int size) { - int[] values = new int[size]; - for (int i = 0; i < size; i++) { - values[i] = 22; + public int[] getArray(@NonNegative int size) { + int[] values = new int[size]; + for (int i = 0; i < size; i++) { + values[i] = 22; + } + return values; } - return values; - } } diff --git a/checker/tests/index/SkipBufferedReader.java b/checker/tests/index/SkipBufferedReader.java index c688c921fda..4ab69fd2e8c 100644 --- a/checker/tests/index/SkipBufferedReader.java +++ b/checker/tests/index/SkipBufferedReader.java @@ -3,12 +3,12 @@ import java.io.InputStreamReader; public class SkipBufferedReader { - public static void method() throws IOException { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + public static void method() throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); - // :: error: (argument.type.incompatible) - bufferedReader.skip(-1); + // :: error: (argument.type.incompatible) + bufferedReader.skip(-1); - bufferedReader.skip(1); - } + bufferedReader.skip(1); + } } diff --git a/checker/tests/index/SpecialTransfersForEquality.java b/checker/tests/index/SpecialTransfersForEquality.java index 3ce5de8d9e2..2a399df8238 100644 --- a/checker/tests/index/SpecialTransfersForEquality.java +++ b/checker/tests/index/SpecialTransfersForEquality.java @@ -4,19 +4,19 @@ public class SpecialTransfersForEquality { - void gteN1Test(@GTENegativeOne int y) { - int[] arr = new int[10]; - if (-1 != y) { - @NonNegative int z = y; - if (z < 10) { - int k = arr[z]; - } + void gteN1Test(@GTENegativeOne int y) { + int[] arr = new int[10]; + if (-1 != y) { + @NonNegative int z = y; + if (z < 10) { + int k = arr[z]; + } + } } - } - void nnTest(@NonNegative int i) { - if (i != 0) { - @Positive int m = i; + void nnTest(@NonNegative int i) { + if (i != 0) { + @Positive int m = i; + } } - } } diff --git a/checker/tests/index/Split.java b/checker/tests/index/Split.java index 38ec5c2392a..161feb8de48 100644 --- a/checker/tests/index/Split.java +++ b/checker/tests/index/Split.java @@ -1,10 +1,11 @@ -import java.util.regex.Pattern; import org.checkerframework.common.value.qual.MinLen; +import java.util.regex.Pattern; + public class Split { - Pattern p = Pattern.compile(".*"); + Pattern p = Pattern.compile(".*"); - void test() { - String @MinLen(1) [] s = p.split("sdf"); - } + void test() { + String @MinLen(1) [] s = p.split("sdf"); + } } diff --git a/checker/tests/index/StartsEndsWith.java b/checker/tests/index/StartsEndsWith.java index 38121c26c2c..781d4c0c006 100644 --- a/checker/tests/index/StartsEndsWith.java +++ b/checker/tests/index/StartsEndsWith.java @@ -5,31 +5,32 @@ public class StartsEndsWith { - final String prefix; + final String prefix; - StartsEndsWith(String prefix) { - this.prefix = prefix; - } + StartsEndsWith(String prefix) { + this.prefix = prefix; + } - String propertyName(String methodName) { - if (methodName.startsWith(prefix)) { - @SuppressWarnings("index") // BUG: https://github.com/typetools/checker-framework/issues/5201 - String result = methodName.substring(prefix.length()); - return result; - } else { - return null; + String propertyName(String methodName) { + if (methodName.startsWith(prefix)) { + @SuppressWarnings( + "index") // BUG: https://github.com/typetools/checker-framework/issues/5201 + String result = methodName.substring(prefix.length()); + return result; + } else { + return null; + } } - } - // This particular test is here rather than in the framework tests because it depends on purity - // annotations for these particular JDK methods. - static void refineStartsConditional(String str, String prefix) { - if (prefix.length() > 10 && str.startsWith(prefix)) { - @MinLen(11) String s11 = str; + // This particular test is here rather than in the framework tests because it depends on purity + // annotations for these particular JDK methods. + static void refineStartsConditional(String str, String prefix) { + if (prefix.length() > 10 && str.startsWith(prefix)) { + @MinLen(11) String s11 = str; + } } - } } class StartsEndsWithExternal { - public static final String staticFinalField = "str"; + public static final String staticFinalField = "str"; } diff --git a/checker/tests/index/StaticInitializer.java b/checker/tests/index/StaticInitializer.java index 8c7955716bd..209f5c92c82 100644 --- a/checker/tests/index/StaticInitializer.java +++ b/checker/tests/index/StaticInitializer.java @@ -1,3 +1,3 @@ public final class StaticInitializer { - static final int MAX_SIGNED_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + static final int MAX_SIGNED_POWER_OF_TWO = 1 << (Integer.SIZE - 2); } diff --git a/checker/tests/index/Stopwatch.java b/checker/tests/index/Stopwatch.java index 08a4b522d40..6fa60d89d8b 100644 --- a/checker/tests/index/Stopwatch.java +++ b/checker/tests/index/Stopwatch.java @@ -1,16 +1,17 @@ -import java.text.DecimalFormat; import org.checkerframework.checker.index.qual.IndexFor; +import java.text.DecimalFormat; + public final class Stopwatch { - private static final DecimalFormat[] timeFormat = { - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - new DecimalFormat("#.#"), - }; + private static final DecimalFormat[] timeFormat = { + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + new DecimalFormat("#.#"), + }; - public DecimalFormat format(@IndexFor("Stopwatch.timeFormat") int digits) { - return Stopwatch.timeFormat[digits]; - } + public DecimalFormat format(@IndexFor("Stopwatch.timeFormat") int digits) { + return Stopwatch.timeFormat[digits]; + } } diff --git a/checker/tests/index/StringBuilderOffset.java b/checker/tests/index/StringBuilderOffset.java index 3ea9423c944..d3fd340de67 100644 --- a/checker/tests/index/StringBuilderOffset.java +++ b/checker/tests/index/StringBuilderOffset.java @@ -1,11 +1,11 @@ public class StringBuilderOffset { - public static void OffsetStringBuilder() { - StringBuilder stringBuilder = new StringBuilder(); - char[] chars = new char[10]; + public static void OffsetStringBuilder() { + StringBuilder stringBuilder = new StringBuilder(); + char[] chars = new char[10]; - // :: error: (argument.type.incompatible) - stringBuilder.append(chars, 5, 7); + // :: error: (argument.type.incompatible) + stringBuilder.append(chars, 5, 7); - stringBuilder.append(chars, 5, 4); - } + stringBuilder.append(chars, 5, 4); + } } diff --git a/checker/tests/index/StringIndexOf.java b/checker/tests/index/StringIndexOf.java index c3c7edc22c0..003aea18b3a 100644 --- a/checker/tests/index/StringIndexOf.java +++ b/checker/tests/index/StringIndexOf.java @@ -2,43 +2,43 @@ public class StringIndexOf { - public static String remove(String l, String s) { - int i = l.indexOf(s); - if (i != -1) { - return l.substring(0, i) + l.substring(i + s.length()); + public static String remove(String l, String s) { + int i = l.indexOf(s); + if (i != -1) { + return l.substring(0, i) + l.substring(i + s.length()); + } + return l; } - return l; - } - public static String nocheck(String l, String s) { - int i = l.indexOf(s); - // :: error: (argument.type.incompatible) - return l.substring(0, i) + l.substring(i + s.length()); - } + public static String nocheck(String l, String s) { + int i = l.indexOf(s); + // :: error: (argument.type.incompatible) + return l.substring(0, i) + l.substring(i + s.length()); + } - public static String remove(String l, String s, int from, boolean last) { - int i = last ? l.lastIndexOf(s, from) : l.indexOf(s, from); - if (i >= 0) { - return l.substring(0, i) + l.substring(i + s.length()); + public static String remove(String l, String s, int from, boolean last) { + int i = last ? l.lastIndexOf(s, from) : l.indexOf(s, from); + if (i >= 0) { + return l.substring(0, i) + l.substring(i + s.length()); + } + return l; } - return l; - } - public static String stringLiteral(String l) { - int i = l.indexOf("constant"); - if (i != -1) { - return l.substring(0, i) + l.substring(i + "constant".length()); + public static String stringLiteral(String l) { + int i = l.indexOf("constant"); + if (i != -1) { + return l.substring(0, i) + l.substring(i + "constant".length()); + } + // :: error: (argument.type.incompatible) + return l.substring(0, i) + l.substring(i + "constant".length()); } - // :: error: (argument.type.incompatible) - return l.substring(0, i) + l.substring(i + "constant".length()); - } - public static char character(String l, char c) { - int i = l.indexOf(c); - if (i > -1) { - return l.charAt(i); + public static char character(String l, char c) { + int i = l.indexOf(c); + if (i > -1) { + return l.charAt(i); + } + // :: error: (argument.type.incompatible) + return l.charAt(i); } - // :: error: (argument.type.incompatible) - return l.charAt(i); - } } diff --git a/checker/tests/index/StringLenRefinement.java b/checker/tests/index/StringLenRefinement.java index dd55e365222..d7556bfa4e4 100644 --- a/checker/tests/index/StringLenRefinement.java +++ b/checker/tests/index/StringLenRefinement.java @@ -4,39 +4,39 @@ public class StringLenRefinement { - void refineLenRange( - @ArrayLenRange(from = 3, to = 10) String range, - @ArrayLen({4, 6, 12}) String lens, - @StringVal({"aaaa", "bbbb", "cccccc", "dddddddddddd"}) String vals) { - if (range.length() <= 7) { - @ArrayLenRange(from = 3, to = 7) String shortRange = range; - } else { - @ArrayLenRange(from = 8, to = 10) String longRange = range; + void refineLenRange( + @ArrayLenRange(from = 3, to = 10) String range, + @ArrayLen({4, 6, 12}) String lens, + @StringVal({"aaaa", "bbbb", "cccccc", "dddddddddddd"}) String vals) { + if (range.length() <= 7) { + @ArrayLenRange(from = 3, to = 7) String shortRange = range; + } else { + @ArrayLenRange(from = 8, to = 10) String longRange = range; + } + + if (lens.length() <= 7) { + @ArrayLen({4, 6}) String shortLens = lens; + } else { + @ArrayLen({12}) String longLens = lens; + } + + if (vals.length() <= 7) { + @StringVal({"aaaa", "bbbb", "cccccc"}) String shortVals = vals; + } else { + + @StringVal({"dddddddddddd"}) String longVals = vals; + } } - if (lens.length() <= 7) { - @ArrayLen({4, 6}) String shortLens = lens; - } else { - @ArrayLen({12}) String longLens = lens; - } - - if (vals.length() <= 7) { - @StringVal({"aaaa", "bbbb", "cccccc"}) String shortVals = vals; - } else { - - @StringVal({"dddddddddddd"}) String longVals = vals; - } - } - - void refineLen( - @ArrayLenRange(from = 3, to = 10) String range, @ArrayLen({4, 8, 12}) String lens) { + void refineLen( + @ArrayLenRange(from = 3, to = 10) String range, @ArrayLen({4, 8, 12}) String lens) { - if (range.length() == 5 || range.length() == 8 || range.length() == 13) { - @ArrayLen({5, 8}) String refinedArg = range; - } + if (range.length() == 5 || range.length() == 8 || range.length() == 13) { + @ArrayLen({5, 8}) String refinedArg = range; + } - if (lens.length() == 5 || lens.length() == 8 || lens.length() == 13) { - @ArrayLen({8}) String refinedLens = lens; + if (lens.length() == 5 || lens.length() == 8 || lens.length() == 13) { + @ArrayLen({8}) String refinedLens = lens; + } } - } } diff --git a/checker/tests/index/StringLength.java b/checker/tests/index/StringLength.java index 9207147d646..ab26225e537 100644 --- a/checker/tests/index/StringLength.java +++ b/checker/tests/index/StringLength.java @@ -1,6 +1,5 @@ // Tests that String.length() is supported in the same situations as array length -import java.util.Random; import org.checkerframework.checker.index.qual.IndexFor; import org.checkerframework.checker.index.qual.IndexOrHigh; import org.checkerframework.checker.index.qual.LTLengthOf; @@ -9,72 +8,74 @@ import org.checkerframework.checker.index.qual.SameLen; import org.checkerframework.common.value.qual.MinLen; +import java.util.Random; + public class StringLength { - void testMinLenSubtractPositive(@MinLen(10) String s) { - @Positive int i1 = s.length() - 9; - @NonNegative int i0 = s.length() - 10; - // :: error: (assignment.type.incompatible) - @NonNegative int im1 = s.length() - 11; - } + void testMinLenSubtractPositive(@MinLen(10) String s) { + @Positive int i1 = s.length() - 9; + @NonNegative int i0 = s.length() - 10; + // :: error: (assignment.type.incompatible) + @NonNegative int im1 = s.length() - 11; + } - void testNewArraySameLen(String s) { - int @SameLen("s") [] array = new int[s.length()]; - // :: error: (assignment.type.incompatible) - int @SameLen("s") [] array1 = new int[s.length() + 1]; - } + void testNewArraySameLen(String s) { + int @SameLen("s") [] array = new int[s.length()]; + // :: error: (assignment.type.incompatible) + int @SameLen("s") [] array1 = new int[s.length() + 1]; + } - void testStringAssignSameLen(String s, String r) { - @SameLen("s") String t = s; - // :: error: (assignment.type.incompatible) - @SameLen("s") String tN = r; - } + void testStringAssignSameLen(String s, String r) { + @SameLen("s") String t = s; + // :: error: (assignment.type.incompatible) + @SameLen("s") String tN = r; + } - void testStringLenEqualSameLen(String s, String r) { - if (s.length() == r.length()) { - @SameLen("s") String tN = r; + void testStringLenEqualSameLen(String s, String r) { + if (s.length() == r.length()) { + @SameLen("s") String tN = r; + } } - } - void testStringEqualSameLen(String s, String r) { - if (s == r) { - @SameLen("s") String tN = r; + void testStringEqualSameLen(String s, String r) { + if (s == r) { + @SameLen("s") String tN = r; + } } - } - void testOffsetRemoval( - String s, - String t, - @LTLengthOf(value = "#1", offset = "#2.length()") int i, - @LTLengthOf(value = "#2") int j, - int k) { - @LTLengthOf("s") int ij = i + j; - // :: error: (assignment.type.incompatible) - @LTLengthOf("s") int ik = i + k; - } + void testOffsetRemoval( + String s, + String t, + @LTLengthOf(value = "#1", offset = "#2.length()") int i, + @LTLengthOf(value = "#2") int j, + int k) { + @LTLengthOf("s") int ij = i + j; + // :: error: (assignment.type.incompatible) + @LTLengthOf("s") int ik = i + k; + } - void testLengthDivide(@MinLen(1) String s) { - @IndexFor("s") int i = s.length() / 2; - } + void testLengthDivide(@MinLen(1) String s) { + @IndexFor("s") int i = s.length() / 2; + } - void testAddDivide(@MinLen(1) String s, @IndexFor("#1") int i, @IndexFor("#1") int j) { - @IndexFor("s") int ij = (i + j) / 2; - } + void testAddDivide(@MinLen(1) String s, @IndexFor("#1") int i, @IndexFor("#1") int j) { + @IndexFor("s") int ij = (i + j) / 2; + } - void testRandomMultiply(@MinLen(1) String s, Random r) { - @LTLengthOf("s") int i = (int) (Math.random() * s.length()); - @LTLengthOf("s") int j = (int) (r.nextDouble() * s.length()); - } + void testRandomMultiply(@MinLen(1) String s, Random r) { + @LTLengthOf("s") int i = (int) (Math.random() * s.length()); + @LTLengthOf("s") int j = (int) (r.nextDouble() * s.length()); + } - void testNotEqualLength(String s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { - if (i != s.length()) { - @IndexFor("s") int in = i; - // :: error: (assignment.type.incompatible) - @IndexFor("s") int jn = j; + void testNotEqualLength(String s, @IndexOrHigh("#1") int i, @IndexOrHigh("#1") int j) { + if (i != s.length()) { + @IndexFor("s") int in = i; + // :: error: (assignment.type.incompatible) + @IndexFor("s") int jn = j; + } } - } - void testLength(String s) { - @IndexOrHigh("s") int i = s.length(); - @LTLengthOf("s") int j = s.length() - 1; - } + void testLength(String s) { + @IndexOrHigh("s") int i = s.length(); + @LTLengthOf("s") int j = s.length() - 1; + } } diff --git a/checker/tests/index/StringMethods.java b/checker/tests/index/StringMethods.java index f26c8e23937..0c3368d5918 100644 --- a/checker/tests/index/StringMethods.java +++ b/checker/tests/index/StringMethods.java @@ -2,43 +2,43 @@ public class StringMethods { - void testCharAt(String s, int i) { - // :: error: (argument.type.incompatible) - s.charAt(i); - // :: error: (argument.type.incompatible) - s.codePointAt(i); + void testCharAt(String s, int i) { + // :: error: (argument.type.incompatible) + s.charAt(i); + // :: error: (argument.type.incompatible) + s.codePointAt(i); - if (i >= 0 && i < s.length()) { - s.charAt(i); - s.codePointAt(i); + if (i >= 0 && i < s.length()) { + s.charAt(i); + s.codePointAt(i); + } } - } - void testCodePointBefore(String s) { - // :: error: (argument.type.incompatible) - s.codePointBefore(0); + void testCodePointBefore(String s) { + // :: error: (argument.type.incompatible) + s.codePointBefore(0); - if (s.length() > 0) { - s.codePointBefore(s.length()); + if (s.length() > 0) { + s.codePointBefore(s.length()); + } } - } - void testSubstring(String s) { - s.substring(0); - s.substring(0, 0); - s.substring(s.length()); - s.substring(s.length(), s.length()); - s.substring(0, s.length()); - // :: error: (argument.type.incompatible) - s.substring(1); - // :: error: (argument.type.incompatible) - s.substring(0, 1); - } + void testSubstring(String s) { + s.substring(0); + s.substring(0, 0); + s.substring(s.length()); + s.substring(s.length(), s.length()); + s.substring(0, s.length()); + // :: error: (argument.type.incompatible) + s.substring(1); + // :: error: (argument.type.incompatible) + s.substring(0, 1); + } - void testIndexOf(String s, char c) { - int i = s.indexOf(c); - if (i != -1) { - s.charAt(i); + void testIndexOf(String s, char c) { + int i = s.indexOf(c); + if (i != -1) { + s.charAt(i); + } } - } } diff --git a/checker/tests/index/StringOffsetTest.java b/checker/tests/index/StringOffsetTest.java index 2005c714edb..b1b588c19e6 100644 --- a/checker/tests/index/StringOffsetTest.java +++ b/checker/tests/index/StringOffsetTest.java @@ -1,10 +1,10 @@ public class StringOffsetTest { - public static void OffsetString() { - char[] chars = new char[10]; + public static void OffsetString() { + char[] chars = new char[10]; - // :: error: (argument.type.incompatible) - String string2 = new String(chars, 5, 7); + // :: error: (argument.type.incompatible) + String string2 = new String(chars, 5, 7); - String string3 = new String(chars, 5, 4); - } + String string3 = new String(chars, 5, 4); + } } diff --git a/checker/tests/index/StringSameLen.java b/checker/tests/index/StringSameLen.java index fc854b8881f..1286990355b 100644 --- a/checker/tests/index/StringSameLen.java +++ b/checker/tests/index/StringSameLen.java @@ -1,53 +1,53 @@ public class StringSameLen { - public void m(String s) { - String t = s; + public void m(String s) { + String t = s; - for (int i = 0; i < s.length(); ++i) { - char c = t.charAt(i); + for (int i = 0; i < s.length(); ++i) { + char c = t.charAt(i); + } } - } - public void m2(String s) { - String t = s.toString(); + public void m2(String s) { + String t = s.toString(); - for (int i = 0; i < s.length(); ++i) { - char c = t.charAt(i); + for (int i = 0; i < s.length(); ++i) { + char c = t.charAt(i); + } } - } - public void m4(String s) { - char[] t = s.toCharArray(); + public void m4(String s) { + char[] t = s.toCharArray(); - for (int i = 0; i < s.length(); ++i) { - char c = t[i]; + for (int i = 0; i < s.length(); ++i) { + char c = t[i]; + } } - } - public void m6(char[] s) { - String t = String.valueOf(s); + public void m6(char[] s) { + String t = String.valueOf(s); - for (int i = 0; i < s.length; ++i) { - char c = t.charAt(i); + for (int i = 0; i < s.length; ++i) { + char c = t.charAt(i); + } } - } - public void m7(char[] s) { - String t = String.copyValueOf(s); + public void m7(char[] s) { + String t = String.copyValueOf(s); - for (int i = 0; i < s.length; ++i) { - char c = t.charAt(i); + for (int i = 0; i < s.length; ++i) { + char c = t.charAt(i); + } } - } - public void m8(String s) { - String t = s.intern(); + public void m8(String s) { + String t = s.intern(); - for (int i = 0; i < s.length(); ++i) { - char c = t.charAt(i); + for (int i = 0; i < s.length(); ++i) { + char c = t.charAt(i); + } } - } - public void constructor(String s) { - String t = new String(new char[] {'a'}); - } + public void constructor(String s) { + String t = new String(new char[] {'a'}); + } } diff --git a/checker/tests/index/StringTokenizerMinLen.java b/checker/tests/index/StringTokenizerMinLen.java index 97b697642d4..d91c481387e 100644 --- a/checker/tests/index/StringTokenizerMinLen.java +++ b/checker/tests/index/StringTokenizerMinLen.java @@ -4,11 +4,11 @@ import java.util.StringTokenizer; public class StringTokenizerMinLen { - void test(String str, String delim, boolean returnDelims) { - StringTokenizer st = new StringTokenizer(str, delim, returnDelims); - while (st.hasMoreTokens()) { - String token = st.nextToken(); - char c = token.charAt(0); + void test(String str, String delim, boolean returnDelims) { + StringTokenizer st = new StringTokenizer(str, delim, returnDelims); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + char c = token.charAt(0); + } } - } } diff --git a/checker/tests/index/SubstringIndexForIrrelevant.java b/checker/tests/index/SubstringIndexForIrrelevant.java index 289a2394afb..0a1ea70b543 100644 --- a/checker/tests/index/SubstringIndexForIrrelevant.java +++ b/checker/tests/index/SubstringIndexForIrrelevant.java @@ -3,12 +3,12 @@ public class SubstringIndexForIrrelevant { - @SuppressWarnings( - "substringindex:return" // https://github.com/kelloggm/checker-framework/issues/206, - // 207, 208 - ) - public static @LTEqLengthOf("#1") @SubstringIndexFor(value = "#1", offset = "#2.length - 1") int - indexOf(boolean[] array, boolean[] target) { - return -1; - } + @SuppressWarnings( + "substringindex:return" // https://github.com/kelloggm/checker-framework/issues/206, + // 207, 208 + ) + public static @LTEqLengthOf("#1") @SubstringIndexFor(value = "#1", offset = "#2.length - 1") int + indexOf(boolean[] array, boolean[] target) { + return -1; + } } diff --git a/checker/tests/index/SubtractingNonNegatives.java b/checker/tests/index/SubtractingNonNegatives.java index e9cd23835de..9ca1662e0d8 100644 --- a/checker/tests/index/SubtractingNonNegatives.java +++ b/checker/tests/index/SubtractingNonNegatives.java @@ -3,30 +3,30 @@ import org.checkerframework.checker.index.qual.*; public class SubtractingNonNegatives { - public static void m4(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { - int k = i; - if (k >= j) { - @IndexFor("a") int y = k; + public static void m4(int[] a, @IndexFor("#1") int i, @IndexFor("#1") int j) { + int k = i; + if (k >= j) { + @IndexFor("a") int y = k; + } + for (k = i; k >= j; k -= j) { + @IndexFor("a") int x = k; + } } - for (k = i; k >= j; k -= j) { - @IndexFor("a") int x = k; - } - } - @SuppressWarnings("lowerbound") - void test(int[] a, @Positive int y) { - @LTLengthOf("a") int x = a.length - 1; - @LTLengthOf( - value = {"a", "a"}, - offset = {"0", "y"}) - int z = x - y; - a[z + y] = 0; - } + @SuppressWarnings("lowerbound") + void test(int[] a, @Positive int y) { + @LTLengthOf("a") int x = a.length - 1; + @LTLengthOf( + value = {"a", "a"}, + offset = {"0", "y"}) + int z = x - y; + a[z + y] = 0; + } - @SuppressWarnings("lowerbound") - void test2(int[] a, @Positive int y) { - @LTLengthOf("a") int x = a.length - 1; - int z = x - y; - a[z + y] = 0; - } + @SuppressWarnings("lowerbound") + void test2(int[] a, @Positive int y) { + @LTLengthOf("a") int x = a.length - 1; + int z = x - y; + a[z + y] = 0; + } } diff --git a/checker/tests/index/SubtractionIndex.java b/checker/tests/index/SubtractionIndex.java index d032f581cc5..ddfdfa60cc1 100644 --- a/checker/tests/index/SubtractionIndex.java +++ b/checker/tests/index/SubtractionIndex.java @@ -7,25 +7,25 @@ public class SubtractionIndex { - // Version without annotations - public static void main(String[] args) { - int N = 8; - int[] grid = new int[N]; - for (int i = 0; i < N; i++) { - System.out.println(grid[(N - 1) - i]); + // Version without annotations + public static void main(String[] args) { + int N = 8; + int[] grid = new int[N]; + for (int i = 0; i < N; i++) { + System.out.println(grid[(N - 1) - i]); + } } - } - // Version with annotations - public static void mainAnnotated(String[] args) { - int N = 8; - int @MinLen(8) [] grid = new int[N]; - @SuppressWarnings("upperbound") - @LTLengthOf("grid") int zero = 0; - for (@LTLengthOf("grid") int i = zero; i < N; i++) { - System.out.println(grid[(N - 1) - i]); - System.out.println(grid[(N - i)]); - System.out.println(grid[(N - i) - 1]); + // Version with annotations + public static void mainAnnotated(String[] args) { + int N = 8; + int @MinLen(8) [] grid = new int[N]; + @SuppressWarnings("upperbound") + @LTLengthOf("grid") int zero = 0; + for (@LTLengthOf("grid") int i = zero; i < N; i++) { + System.out.println(grid[(N - 1) - i]); + System.out.println(grid[(N - i)]); + System.out.println(grid[(N - i) - 1]); + } } - } } diff --git a/checker/tests/index/SwitchDataflowRefinement.java b/checker/tests/index/SwitchDataflowRefinement.java index 6d0ea169444..358f84fba09 100644 --- a/checker/tests/index/SwitchDataflowRefinement.java +++ b/checker/tests/index/SwitchDataflowRefinement.java @@ -1,23 +1,23 @@ public class SwitchDataflowRefinement { - void readInfo(String[] parts) { + void readInfo(String[] parts) { - if (parts.length >= 1) { - Integer.parseInt(parts[0]); - } + if (parts.length >= 1) { + Integer.parseInt(parts[0]); + } - switch (parts.length) { - case 1: - Integer.parseInt(parts[0]); - break; - } + switch (parts.length) { + case 1: + Integer.parseInt(parts[0]); + break; + } - switch (parts.length) { - case 0: - break; - default: - Integer.parseInt(parts[0]); - break; + switch (parts.length) { + case 0: + break; + default: + Integer.parseInt(parts[0]); + break; + } } - } } diff --git a/checker/tests/index/SwitchTest.java b/checker/tests/index/SwitchTest.java index 2dd6ec73923..8f4aae6c550 100644 --- a/checker/tests/index/SwitchTest.java +++ b/checker/tests/index/SwitchTest.java @@ -2,17 +2,17 @@ public class SwitchTest { - public String findSlice_unordered(String[] vis) { - switch (vis.length) { - case 1: - @IntVal(1) int x = vis.length; - return vis[0]; - case 2: - return vis[0] + vis[1]; - case 3: - return vis[0] + vis[1] + vis[2]; - default: - throw new RuntimeException("Bad length " + vis.length); + public String findSlice_unordered(String[] vis) { + switch (vis.length) { + case 1: + @IntVal(1) int x = vis.length; + return vis[0]; + case 2: + return vis[0] + vis[1]; + case 3: + return vis[0] + vis[1] + vis[2]; + default: + throw new RuntimeException("Bad length " + vis.length); + } } - } } diff --git a/checker/tests/index/TestAgainstLength.java b/checker/tests/index/TestAgainstLength.java index 4f749e72fb1..27e754ba7b0 100644 --- a/checker/tests/index/TestAgainstLength.java +++ b/checker/tests/index/TestAgainstLength.java @@ -5,16 +5,16 @@ public class TestAgainstLength { - protected int[] values; + protected int[] values; - /** The number of active elements (equivalently, the first unused index). */ - @IndexOrHigh("values") int num_values; + /** The number of active elements (equivalently, the first unused index). */ + @IndexOrHigh("values") int num_values; - public void add(int elt) { - if (num_values == values.length) { - return; + public void add(int elt) { + if (num_values == values.length) { + return; + } + values[num_values] = elt; + num_values++; } - values[num_values] = elt; - num_values++; - } } diff --git a/checker/tests/index/ToArrayIndex.java b/checker/tests/index/ToArrayIndex.java index 7b438c5592c..343d4ba16a8 100644 --- a/checker/tests/index/ToArrayIndex.java +++ b/checker/tests/index/ToArrayIndex.java @@ -1,11 +1,12 @@ -import java.util.ArrayList; import org.checkerframework.common.value.qual.MinLen; +import java.util.ArrayList; + // @skip-test until we bring list support back public class ToArrayIndex { - public String @MinLen(1) [] m(@MinLen(1) ArrayList compiler) { - return compiler.toArray(new String[0]); - } + public String @MinLen(1) [] m(@MinLen(1) ArrayList compiler) { + return compiler.toArray(new String[0]); + } } diff --git a/checker/tests/index/TransferAdd.java b/checker/tests/index/TransferAdd.java index 497feacc132..031d0209b20 100644 --- a/checker/tests/index/TransferAdd.java +++ b/checker/tests/index/TransferAdd.java @@ -4,82 +4,82 @@ public class TransferAdd { - void test() { + void test() { - // adding zero and one and two + // adding zero and one and two - int a = -1; + int a = -1; - @Positive int a1 = a + 2; + @Positive int a1 = a + 2; - @NonNegative int b = a + 1; - @NonNegative int c = 1 + a; + @NonNegative int b = a + 1; + @NonNegative int c = 1 + a; - @GTENegativeOne int d = a + 0; - @GTENegativeOne int e = 0 + a; + @GTENegativeOne int d = a + 0; + @GTENegativeOne int e = 0 + a; - // :: error: (assignment.type.incompatible) - @Positive int f = a + 1; + // :: error: (assignment.type.incompatible) + @Positive int f = a + 1; - @NonNegative int g = b + 0; + @NonNegative int g = b + 0; - @Positive int h = b + 1; + @Positive int h = b + 1; - @Positive int i = h + 1; - @Positive int j = h + 0; + @Positive int i = h + 1; + @Positive int j = h + 0; - // adding values + // adding values - @Positive int k = i + j; - // :: error: (assignment.type.incompatible) - @Positive int l = b + c; - // :: error: (assignment.type.incompatible) - @Positive int m = d + c; - // :: error: (assignment.type.incompatible) - @Positive int n = d + e; + @Positive int k = i + j; + // :: error: (assignment.type.incompatible) + @Positive int l = b + c; + // :: error: (assignment.type.incompatible) + @Positive int m = d + c; + // :: error: (assignment.type.incompatible) + @Positive int n = d + e; - @Positive int o = h + g; - // :: error: (assignment.type.incompatible) - @Positive int p = h + d; + @Positive int o = h + g; + // :: error: (assignment.type.incompatible) + @Positive int p = h + d; - @NonNegative int q = b + c; - // :: error: (assignment.type.incompatible) - @NonNegative int r = q + d; + @NonNegative int q = b + c; + // :: error: (assignment.type.incompatible) + @NonNegative int r = q + d; - @NonNegative int s = k + d; - @GTENegativeOne int t = s + d; + @NonNegative int s = k + d; + @GTENegativeOne int t = s + d; - // increments + // increments - // :: error: (assignment.type.incompatible) - @Positive int u = b++; + // :: error: (assignment.type.incompatible) + @Positive int u = b++; - @Positive int u1 = b; + @Positive int u1 = b; - @Positive int v = ++c; + @Positive int v = ++c; - @Positive int v1 = c; + @Positive int v1 = c; - int n1p1 = -1, n1p2 = -1; + int n1p1 = -1, n1p2 = -1; - @NonNegative int w = ++n1p1; + @NonNegative int w = ++n1p1; - @NonNegative int w1 = n1p1; + @NonNegative int w1 = n1p1; - // :: error: (assignment.type.incompatible) - @Positive int w2 = n1p1; - // :: error: (assignment.type.incompatible) - @Positive int w3 = n1p1++; + // :: error: (assignment.type.incompatible) + @Positive int w2 = n1p1; + // :: error: (assignment.type.incompatible) + @Positive int w3 = n1p1++; - // :: error: (assignment.type.incompatible) - @NonNegative int x = n1p2++; + // :: error: (assignment.type.incompatible) + @NonNegative int x = n1p2++; - @NonNegative int x1 = n1p2; + @NonNegative int x1 = n1p2; - // :: error: (assignment.type.incompatible) - @Positive int y = ++d; - // :: error: (assignment.type.incompatible) - @Positive int z = e++; - } + // :: error: (assignment.type.incompatible) + @Positive int y = ++d; + // :: error: (assignment.type.incompatible) + @Positive int z = e++; + } } // a comment diff --git a/checker/tests/index/TransferDivide.java b/checker/tests/index/TransferDivide.java index 5f60605ab97..8805cb3e518 100644 --- a/checker/tests/index/TransferDivide.java +++ b/checker/tests/index/TransferDivide.java @@ -4,58 +4,58 @@ public class TransferDivide { - void test() { - int a = -1; - int b = 0; - int c = 1; - int d = 2; - - /** literals */ - @Positive int e = -1 / -1; - - /** 0 / * -> NN */ - @NonNegative int f = 0 / a; - @NonNegative int g = 0 / d; - - /** * / 1 -> * */ - @GTENegativeOne int h = a / 1; - @NonNegative int i = b / 1; - @Positive int j = c / 1; - @Positive int k = d / 1; - - /** pos / pos -> nn */ - @NonNegative int l = d / c; - @NonNegative int m = c / d; - // :: error: (assignment.type.incompatible) - @Positive int n = c / d; - - /** nn / pos -> nn */ - @NonNegative int o = b / c; - // :: error: (assignment.type.incompatible) - @Positive int p = b / d; - - /** pos / nn -> nn */ - @NonNegative int q = d / l; - // :: error: (assignment.type.incompatible) - @Positive int r = c / l; - - /** nn / nn -> nn */ - @NonNegative int s = b / q; - // :: error: (assignment.type.incompatible) - @Positive int t = b / q; - - /** n1p / pos -> n1p */ - @GTENegativeOne int u = a / d; - @GTENegativeOne int v = a / c; - // :: error: (assignment.type.incompatible) - @NonNegative int w = a / c; - - /** n1p / nn -> n1p */ - @GTENegativeOne int x = a / l; - } - - void testDivideByTwo(@NonNegative int x) { - @NonNegative int y = x / 2; - } + void test() { + int a = -1; + int b = 0; + int c = 1; + int d = 2; + + /** literals */ + @Positive int e = -1 / -1; + + /** 0 / * -> NN */ + @NonNegative int f = 0 / a; + @NonNegative int g = 0 / d; + + /** * / 1 -> * */ + @GTENegativeOne int h = a / 1; + @NonNegative int i = b / 1; + @Positive int j = c / 1; + @Positive int k = d / 1; + + /** pos / pos -> nn */ + @NonNegative int l = d / c; + @NonNegative int m = c / d; + // :: error: (assignment.type.incompatible) + @Positive int n = c / d; + + /** nn / pos -> nn */ + @NonNegative int o = b / c; + // :: error: (assignment.type.incompatible) + @Positive int p = b / d; + + /** pos / nn -> nn */ + @NonNegative int q = d / l; + // :: error: (assignment.type.incompatible) + @Positive int r = c / l; + + /** nn / nn -> nn */ + @NonNegative int s = b / q; + // :: error: (assignment.type.incompatible) + @Positive int t = b / q; + + /** n1p / pos -> n1p */ + @GTENegativeOne int u = a / d; + @GTENegativeOne int v = a / c; + // :: error: (assignment.type.incompatible) + @NonNegative int w = a / c; + + /** n1p / nn -> n1p */ + @GTENegativeOne int x = a / l; + } + + void testDivideByTwo(@NonNegative int x) { + @NonNegative int y = x / 2; + } } // a comment diff --git a/checker/tests/index/TransferMod.java b/checker/tests/index/TransferMod.java index 2144ac4c13b..a98d62cd76b 100644 --- a/checker/tests/index/TransferMod.java +++ b/checker/tests/index/TransferMod.java @@ -4,31 +4,31 @@ public class TransferMod { - void test() { - int aa = -100; - int a = -1; - int b = 0; - int c = 1; - int d = 2; + void test() { + int aa = -100; + int a = -1; + int b = 0; + int c = 1; + int d = 2; - @Positive int e = 5 % 3; - @NonNegative int f = -100 % 1; + @Positive int e = 5 % 3; + @NonNegative int f = -100 % 1; - @NonNegative int g = aa % -1; - @NonNegative int h = aa % 1; - @NonNegative int i = d % -1; - @NonNegative int j = d % 1; + @NonNegative int g = aa % -1; + @NonNegative int h = aa % 1; + @NonNegative int i = d % -1; + @NonNegative int j = d % 1; - @NonNegative int k = d % c; - @NonNegative int l = b % c; - @NonNegative int m = c % d; + @NonNegative int k = d % c; + @NonNegative int l = b % c; + @NonNegative int m = c % d; - @NonNegative int n = c % a; - @NonNegative int o = b % a; + @NonNegative int n = c % a; + @NonNegative int o = b % a; - @GTENegativeOne int p = a % a; - @GTENegativeOne int q = a % d; - @GTENegativeOne int r = a % c; - } + @GTENegativeOne int p = a % a; + @GTENegativeOne int q = a % d; + @GTENegativeOne int r = a % c; + } } // a comment diff --git a/checker/tests/index/TransferSub.java b/checker/tests/index/TransferSub.java index bfab5416901..209e092128a 100644 --- a/checker/tests/index/TransferSub.java +++ b/checker/tests/index/TransferSub.java @@ -4,74 +4,74 @@ public class TransferSub { - void test() { - // zero, one, and two - int a = 1; - - @NonNegative int b = a - 1; - // :: error: (assignment.type.incompatible) - @Positive int c = a - 1; - @GTENegativeOne int d = a - 2; - - // :: error: (assignment.type.incompatible) - @NonNegative int e = a - 2; - - @GTENegativeOne int f = b - 1; - // :: error: (assignment.type.incompatible) - @NonNegative int g = b - 1; - - // :: error: (assignment.type.incompatible) - @GTENegativeOne int h = f - 1; - - @GTENegativeOne int i = f - 0; - @NonNegative int j = b - 0; - @Positive int k = a - 0; - - // :: error: (assignment.type.incompatible) - @Positive int l = j - 0; - // :: error: (assignment.type.incompatible) - @NonNegative int m = i - 0; - - // :: error: (assignment.type.incompatible) - @Positive int n = a - k; - // this would be an error if the values of b and j (both zero) weren't known at compile time - @NonNegative int o = b - j; - /* i and d both have compile time value -1, so this is legal. - The general case of GTEN1 - GTEN1 is not, though. */ - @GTENegativeOne int p = i - d; - - // decrements - - // :: error: (unary.decrement.type.incompatible) :: error: - // (assignment.type.incompatible) - @Positive int q = --k; // k = 0 - - // :: error: (unary.decrement.type.incompatible) - @NonNegative int r = k--; // after this k = -1 - - int k1 = 0; - @NonNegative int s = k1--; - - // :: error: (assignment.type.incompatible) - @NonNegative int s1 = k1; - - // transferred to SimpleTransferSub.java - // this section is failing due to CF bug - // int k2 = 0; - // // :: error: (assignment.type.incompatible) - // @Positive int s2 = k2--; - - k1 = 1; - @NonNegative int t = --k1; - - k1 = 1; - // :: error: (assignment.type.incompatible) - @Positive int t1 = --k1; - - int u1 = -1; - @GTENegativeOne int x = u1--; - // :: error: (assignment.type.incompatible) - @GTENegativeOne int x1 = u1; - } + void test() { + // zero, one, and two + int a = 1; + + @NonNegative int b = a - 1; + // :: error: (assignment.type.incompatible) + @Positive int c = a - 1; + @GTENegativeOne int d = a - 2; + + // :: error: (assignment.type.incompatible) + @NonNegative int e = a - 2; + + @GTENegativeOne int f = b - 1; + // :: error: (assignment.type.incompatible) + @NonNegative int g = b - 1; + + // :: error: (assignment.type.incompatible) + @GTENegativeOne int h = f - 1; + + @GTENegativeOne int i = f - 0; + @NonNegative int j = b - 0; + @Positive int k = a - 0; + + // :: error: (assignment.type.incompatible) + @Positive int l = j - 0; + // :: error: (assignment.type.incompatible) + @NonNegative int m = i - 0; + + // :: error: (assignment.type.incompatible) + @Positive int n = a - k; + // this would be an error if the values of b and j (both zero) weren't known at compile time + @NonNegative int o = b - j; + /* i and d both have compile time value -1, so this is legal. + The general case of GTEN1 - GTEN1 is not, though. */ + @GTENegativeOne int p = i - d; + + // decrements + + // :: error: (unary.decrement.type.incompatible) :: error: + // (assignment.type.incompatible) + @Positive int q = --k; // k = 0 + + // :: error: (unary.decrement.type.incompatible) + @NonNegative int r = k--; // after this k = -1 + + int k1 = 0; + @NonNegative int s = k1--; + + // :: error: (assignment.type.incompatible) + @NonNegative int s1 = k1; + + // transferred to SimpleTransferSub.java + // this section is failing due to CF bug + // int k2 = 0; + // // :: error: (assignment.type.incompatible) + // @Positive int s2 = k2--; + + k1 = 1; + @NonNegative int t = --k1; + + k1 = 1; + // :: error: (assignment.type.incompatible) + @Positive int t1 = --k1; + + int u1 = -1; + @GTENegativeOne int x = u1--; + // :: error: (assignment.type.incompatible) + @GTENegativeOne int x1 = u1; + } } // a comment diff --git a/checker/tests/index/TransferTimes.java b/checker/tests/index/TransferTimes.java index 9d95d84c91a..2ad8745521d 100644 --- a/checker/tests/index/TransferTimes.java +++ b/checker/tests/index/TransferTimes.java @@ -3,26 +3,26 @@ public class TransferTimes { - void test() { - int a = 1; - @Positive int b = a * 1; - @Positive int c = 1 * a; - @NonNegative int d = 0 * a; - // :: error: (assignment.type.incompatible) - @NonNegative int e = -1 * a; + void test() { + int a = 1; + @Positive int b = a * 1; + @Positive int c = 1 * a; + @NonNegative int d = 0 * a; + // :: error: (assignment.type.incompatible) + @NonNegative int e = -1 * a; - int g = -1; - @NonNegative int h = g * 0; - // :: error: (assignment.type.incompatible) - @Positive int i = g * 0; - // :: error: (assignment.type.incompatible) - @Positive int j = g * a; + int g = -1; + @NonNegative int h = g * 0; + // :: error: (assignment.type.incompatible) + @Positive int i = g * 0; + // :: error: (assignment.type.incompatible) + @Positive int j = g * a; - int k = 0; - int l = 1; - @Positive int m = a * l; - @NonNegative int n = k * l; - @NonNegative int o = k * k; - } + int k = 0; + int l = 1; + @Positive int m = a * l; + @NonNegative int n = k * l; + @NonNegative int o = k * k; + } } // a comment diff --git a/checker/tests/index/TypeArrayLengthWithSameLen.java b/checker/tests/index/TypeArrayLengthWithSameLen.java index e134075123c..a35bed67998 100644 --- a/checker/tests/index/TypeArrayLengthWithSameLen.java +++ b/checker/tests/index/TypeArrayLengthWithSameLen.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.index.qual.*; public class TypeArrayLengthWithSameLen { - void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b, int[] c) { - if (a.length == c.length) { - @LTEqLengthOf({"a", "b", "c"}) int x = b.length; + void test(int @SameLen("#2") [] a, int @SameLen("#1") [] b, int[] c) { + if (a.length == c.length) { + @LTEqLengthOf({"a", "b", "c"}) int x = b.length; + } } - } } diff --git a/checker/tests/index/UBLiteralFlow.java b/checker/tests/index/UBLiteralFlow.java index 7fdf479bd39..2ffbdbf35a9 100644 --- a/checker/tests/index/UBLiteralFlow.java +++ b/checker/tests/index/UBLiteralFlow.java @@ -4,183 +4,185 @@ public class UBLiteralFlow { - private static @IndexOrLow("#1") int lineStartIndexPartial( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; + private static @IndexOrLow("#1") int lineStartIndexPartial( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; + } + return result; } - return result; - } - private static @LTLengthOf("#1") int lineStartIndexPartial2( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; + private static @LTLengthOf("#1") int lineStartIndexPartial2( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; + } + return result; } - return result; - } - private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndexPartial3( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; + private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndexPartial3( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; + } + // :: error: (return.type.incompatible) + return result; } - // :: error: (return.type.incompatible) - return result; - } - private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndexPartial4( - String s, @GTENegativeOne int lineStart) { - int result; - if (lineStart >= s.length()) { - result = -1; - } else { - result = lineStart; + private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndexPartial4( + String s, @GTENegativeOne int lineStart) { + int result; + if (lineStart >= s.length()) { + result = -1; + } else { + result = lineStart; + } + return result; } - return result; - } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @IndexOrLow("#1") int lineStartIndex(String s, int start) { - if (s.length() == 0) { - return -1; + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @IndexOrLow("#1") int lineStartIndex(String s, int start) { + if (s.length() == 0) { + return -1; + } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + return lineStart; + } } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - return lineStart; - } - } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @LTLengthOf("#1") int lineStartIndex2(String s, int start) { - if (s.length() == 0) { - return -1; - } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - return lineStart; + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @LTLengthOf("#1") int lineStartIndex2(String s, int start) { + if (s.length() == 0) { + return -1; + } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + return lineStart; + } } - } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndex3(String s, int start) { - if (s.length() == 0) { - // :: error: (return.type.incompatible) - return -1; + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @LTLengthOf(value = "#1", offset = "1") int lineStartIndex3( + String s, int start) { + if (s.length() == 0) { + // :: error: (return.type.incompatible) + return -1; + } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + // :: error: (return.type.incompatible) + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + // :: error: (return.type.incompatible) + return lineStart; + } } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - // :: error: (return.type.incompatible) - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - // :: error: (return.type.incompatible) - return lineStart; - } - } - /** - * Given a string, return the index of the start of a line, after {@code start}. - * - * @param s the string in which to find the start of a line - * @param start the index at which to start looking for the start of a line - * @return the index of the start of a line, or -1 if no such exists - */ - private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndex4(String s, int start) { - if (s.length() == 0) { - return -1; - } - if (start == 0) { - // It doesn't make sense to call this routine with 0, but return 0 anyway. - return 0; - } - if (start > s.length()) { - return -1; - } - // possible line terminators: "\n", "\r\n", "\r". - int newlinePos = s.indexOf("\n", start - 1); - int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; - int returnPos1 = s.indexOf("\r\n", start - 2); - int returnPos2 = s.indexOf("\r", start - 1); - int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; - int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; - int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); - if (lineStart >= s.length()) { - return -1; - } else { - return lineStart; + /** + * Given a string, return the index of the start of a line, after {@code start}. + * + * @param s the string in which to find the start of a line + * @param start the index at which to start looking for the start of a line + * @return the index of the start of a line, or -1 if no such exists + */ + private static @LTLengthOf(value = "#1", offset = "-1") int lineStartIndex4( + String s, int start) { + if (s.length() == 0) { + return -1; + } + if (start == 0) { + // It doesn't make sense to call this routine with 0, but return 0 anyway. + return 0; + } + if (start > s.length()) { + return -1; + } + // possible line terminators: "\n", "\r\n", "\r". + int newlinePos = s.indexOf("\n", start - 1); + int afterNewline = (newlinePos == -1) ? Integer.MAX_VALUE : newlinePos + 1; + int returnPos1 = s.indexOf("\r\n", start - 2); + int returnPos2 = s.indexOf("\r", start - 1); + int afterReturn1 = (returnPos1 == -1) ? Integer.MAX_VALUE : returnPos1 + 2; + int afterReturn2 = (returnPos2 == -1) ? Integer.MAX_VALUE : returnPos2 + 1; + int lineStart = Math.min(afterNewline, Math.min(afterReturn1, afterReturn2)); + if (lineStart >= s.length()) { + return -1; + } else { + return lineStart; + } } - } } diff --git a/checker/tests/index/UBPoly.java b/checker/tests/index/UBPoly.java index 4ef9e2963e4..177650d45be 100644 --- a/checker/tests/index/UBPoly.java +++ b/checker/tests/index/UBPoly.java @@ -5,17 +5,17 @@ import org.checkerframework.checker.index.qual.PolyUpperBound; public class UBPoly { - public static void main(String[] args) { - char[] a = new char[10]; - poly(a, 100); - } + public static void main(String[] args) { + char[] a = new char[10]; + poly(a, 100); + } - public static void poly(char[] a, @NonNegative @PolyUpperBound int i) { - // :: error: (argument.type.incompatible) - access(a, i); - } + public static void poly(char[] a, @NonNegative @PolyUpperBound int i) { + // :: error: (argument.type.incompatible) + access(a, i); + } - public static void access(char[] a, @NonNegative @LTLengthOf("#1") int j) { - char c = a[j]; - } + public static void access(char[] a, @NonNegative @LTLengthOf("#1") int j) { + char c = a[j]; + } } diff --git a/checker/tests/index/UBSubtyping.java b/checker/tests/index/UBSubtyping.java index 128c0d1e940..8559353b99c 100644 --- a/checker/tests/index/UBSubtyping.java +++ b/checker/tests/index/UBSubtyping.java @@ -3,28 +3,28 @@ import org.checkerframework.checker.index.qual.UpperBoundUnknown; public class UBSubtyping { - int[] arr = {1}; - int[] arr2 = {1}; - int[] arr3 = {1}; + int[] arr = {1}; + int[] arr2 = {1}; + int[] arr3 = {1}; - void test(@LTEqLengthOf({"arr", "arr2", "arr3"}) int test) { - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"arr"}) int a = 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int a1 = 1; + void test(@LTEqLengthOf({"arr", "arr2", "arr3"}) int test) { + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"arr"}) int a = 1; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int a1 = 1; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr"}) int b = a; - @UpperBoundUnknown int d = a; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr"}) int b = a; + @UpperBoundUnknown int d = a; - // :: error: (assignment.type.incompatible) - @LTLengthOf({"arr2"}) int g = a; + // :: error: (assignment.type.incompatible) + @LTLengthOf({"arr2"}) int g = a; - // :: error: (assignment.type.incompatible) - @LTEqLengthOf({"arr", "arr2", "arr3"}) int h = 2; + // :: error: (assignment.type.incompatible) + @LTEqLengthOf({"arr", "arr2", "arr3"}) int h = 2; - @LTEqLengthOf({"arr", "arr2"}) int h2 = test; - @LTEqLengthOf({"arr"}) int i = test; - @LTEqLengthOf({"arr", "arr3"}) int j = test; - } + @LTEqLengthOf({"arr", "arr2"}) int h2 = test; + @LTEqLengthOf({"arr"}) int i = test; + @LTEqLengthOf({"arr", "arr3"}) int j = test; + } } diff --git a/checker/tests/index/UnaryOperationParsedIncorrectly.java b/checker/tests/index/UnaryOperationParsedIncorrectly.java index 70964935d12..5b0a7b63356 100644 --- a/checker/tests/index/UnaryOperationParsedIncorrectly.java +++ b/checker/tests/index/UnaryOperationParsedIncorrectly.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.index.qual.LessThan; public class UnaryOperationParsedIncorrectly { - void method1(@LessThan("#2") int var1, int var2) { - // Function implementation - } + void method1(@LessThan("#2") int var1, int var2) { + // Function implementation + } - void method2() { - method1(-10, 10); - method1(-10, +10); - } + void method2() { + method1(-10, 10); + method1(-10, +10); + } } diff --git a/checker/tests/index/UncheckedMinLen.java b/checker/tests/index/UncheckedMinLen.java index be202073ee3..bdf254a0dad 100644 --- a/checker/tests/index/UncheckedMinLen.java +++ b/checker/tests/index/UncheckedMinLen.java @@ -6,40 +6,40 @@ // test case for kelloggm#183: https://github.com/kelloggm/checker-framework/issues/183 public class UncheckedMinLen { - void addToNonNegative(@NonNegative int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToNonNegative(@NonNegative int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void addToPositive(@Positive int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToPositive(@Positive int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - // Similar code that correctly gives warnings - void addToPositiveOK(@NonNegative int l, Object v) { - Object[] o = new Object[l + 1]; - // :: error: (array.access.unsafe.high.constant) - o[99] = v; - } + // Similar code that correctly gives warnings + void addToPositiveOK(@NonNegative int l, Object v) { + Object[] o = new Object[l + 1]; + // :: error: (array.access.unsafe.high.constant) + o[99] = v; + } - void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void subtractFromPositiveOK(@Positive int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l - 1]; - o[99] = v; - } + void subtractFromPositiveOK(@Positive int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l - 1]; + o[99] = v; + } } diff --git a/checker/tests/index/UpperBoundRefinement.java b/checker/tests/index/UpperBoundRefinement.java index 9209b5f2554..14f1d7870ad 100644 --- a/checker/tests/index/UpperBoundRefinement.java +++ b/checker/tests/index/UpperBoundRefinement.java @@ -2,40 +2,40 @@ @SuppressWarnings("lowerbound") public class UpperBoundRefinement { - // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and expression - // j is less than or equal to the length of f1, then the type of i + j is @LTLengthOf("f2") - void test(int[] f1, int[] f2) { - @LTLengthOf(value = "f2", offset = "f1.length") int i = (f2.length - 1) - f1.length; - @LTLengthOf("f1") int j = f1.length - 1; - @LTLengthOf("f2") int x = i + j; - @LTLengthOf("f2") int y = i + f1.length; - } + // If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and expression + // j is less than or equal to the length of f1, then the type of i + j is @LTLengthOf("f2") + void test(int[] f1, int[] f2) { + @LTLengthOf(value = "f2", offset = "f1.length") int i = (f2.length - 1) - f1.length; + @LTLengthOf("f1") int j = f1.length - 1; + @LTLengthOf("f2") int x = i + j; + @LTLengthOf("f2") int y = i + f1.length; + } - void test2() { - double[] f1 = new double[10]; - double[] f2 = new double[20]; + void test2() { + double[] f1 = new double[10]; + double[] f2 = new double[20]; - for (int j = 0; j < f2.length; j++) { - f2[j] = j; - } - for (int i = 0; i < f2.length - f1.length; i++) { - // fill up f1 with elements of f2 - for (int j = 0; j < f1.length; j++) { - f1[j] = f2[i + j]; - } + for (int j = 0; j < f2.length; j++) { + f2[j] = j; + } + for (int i = 0; i < f2.length - f1.length; i++) { + // fill up f1 with elements of f2 + for (int j = 0; j < f1.length; j++) { + f1[j] = f2[i + j]; + } + } } - } - public void test3(double[] a, double[] sub) { - int a_index_max = a.length - sub.length; - // Has type @LTL(value={"a","sub"}, offset={"-1 + sub.length", "-1 + a.length"}) + public void test3(double[] a, double[] sub) { + int a_index_max = a.length - sub.length; + // Has type @LTL(value={"a","sub"}, offset={"-1 + sub.length", "-1 + a.length"}) - for (int i = 0; i <= a_index_max; i++) { // i has the same type as a_index_max - for (int j = 0; j < sub.length; j++) { // j is @LTL("sub") - // i + j is safe here. - // Because j is LTL("sub"), it should count as ("-1 + sub.length") - double d = a[i + j]; - } + for (int i = 0; i <= a_index_max; i++) { // i has the same type as a_index_max + for (int j = 0; j < sub.length; j++) { // j is @LTL("sub") + // i + j is safe here. + // Because j is LTL("sub"), it should count as ("-1 + sub.length") + double d = a[i + j]; + } + } } - } } diff --git a/checker/tests/index/ValueCheckerProblem.java b/checker/tests/index/ValueCheckerProblem.java index 2377ca0d5d1..63da5b99f35 100644 --- a/checker/tests/index/ValueCheckerProblem.java +++ b/checker/tests/index/ValueCheckerProblem.java @@ -1,5 +1,5 @@ public class ValueCheckerProblem { - void test() { - Object o = new Object[][] {null}; - } + void test() { + Object o = new Object[][] {null}; + } } diff --git a/checker/tests/index/VarArgsIncompatible.java b/checker/tests/index/VarArgsIncompatible.java index 5bf8739e85f..c585fca2764 100644 --- a/checker/tests/index/VarArgsIncompatible.java +++ b/checker/tests/index/VarArgsIncompatible.java @@ -1,10 +1,10 @@ public class VarArgsIncompatible { - public static void test(int[] arr) { - help(arr); - } + public static void test(int[] arr) { + help(arr); + } - @SafeVarargs - @SuppressWarnings("varargs") - public static void help(T... arr) {} + @SafeVarargs + @SuppressWarnings("varargs") + public static void help(T... arr) {} } diff --git a/checker/tests/index/VarLteVar.java b/checker/tests/index/VarLteVar.java index 4ea0eb10f7f..5059c64e279 100644 --- a/checker/tests/index/VarLteVar.java +++ b/checker/tests/index/VarLteVar.java @@ -10,32 +10,32 @@ public class VarLteVar { - /** Returns an array that is equivalent to the set difference of seq1 and seq2. */ - public static boolean[] setDiff(boolean[] seq1, boolean[] seq2) { - boolean[] intermediate = new boolean[seq1.length]; - int length = 0; - for (int i = 0; i < seq1.length; i++) { - if (!memberOf(seq1[i], seq2)) { - intermediate[length++] = seq1[i]; - } + /** Returns an array that is equivalent to the set difference of seq1 and seq2. */ + public static boolean[] setDiff(boolean[] seq1, boolean[] seq2) { + boolean[] intermediate = new boolean[seq1.length]; + int length = 0; + for (int i = 0; i < seq1.length; i++) { + if (!memberOf(seq1[i], seq2)) { + intermediate[length++] = seq1[i]; + } + } + return subarray(intermediate, 0, length); } - return subarray(intermediate, 0, length); - } - public static boolean memberOf(boolean elt, boolean[] arr) { - for (int i = 0; i < arr.length; i++) { - if (arr[i] == elt) { - return true; - } + public static boolean memberOf(boolean elt, boolean[] arr) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] == elt) { + return true; + } + } + return false; } - return false; - } - @SuppressWarnings("index") // not relevant to this test case - public static boolean[] subarray( - boolean[] a, @IndexOrHigh("#1") int startindex, @IndexOrHigh("#1") int length) { - boolean[] result = new boolean[length]; - System.arraycopy(a, startindex, result, 0, length); - return result; - } + @SuppressWarnings("index") // not relevant to this test case + public static boolean[] subarray( + boolean[] a, @IndexOrHigh("#1") int startindex, @IndexOrHigh("#1") int length) { + boolean[] result = new boolean[length]; + System.arraycopy(a, startindex, result, 0, length); + return result; + } } diff --git a/checker/tests/index/ViewpointAdaptTest.java b/checker/tests/index/ViewpointAdaptTest.java index b634143f7f4..a15114fa2d8 100644 --- a/checker/tests/index/ViewpointAdaptTest.java +++ b/checker/tests/index/ViewpointAdaptTest.java @@ -5,12 +5,12 @@ public class ViewpointAdaptTest { - void ListGet( - @LTLengthOf("list") int index, @LTEqLengthOf("list") int notIndex, List list) { - // :: error: (argument.type.incompatible) - list.get(index); + void ListGet( + @LTLengthOf("list") int index, @LTEqLengthOf("list") int notIndex, List list) { + // :: error: (argument.type.incompatible) + list.get(index); - // :: error: (argument.type.incompatible) - list.get(notIndex); - } + // :: error: (argument.type.incompatible) + list.get(notIndex); + } } diff --git a/checker/tests/index/VoidType.java b/checker/tests/index/VoidType.java index ad6ec89a0bf..b6b7b89d248 100644 --- a/checker/tests/index/VoidType.java +++ b/checker/tests/index/VoidType.java @@ -1,3 +1,3 @@ public class VoidType { - private Class main_class = Void.TYPE; + private Class main_class = Void.TYPE; } diff --git a/checker/tests/index/ZeroMinLen.java b/checker/tests/index/ZeroMinLen.java index 3e2776bac83..2b74b88103d 100644 --- a/checker/tests/index/ZeroMinLen.java +++ b/checker/tests/index/ZeroMinLen.java @@ -3,16 +3,16 @@ public class ZeroMinLen { - int @MinLen(1) [] nums; - int[] nums2; + int @MinLen(1) [] nums; + int[] nums2; - @IndexFor("nums") int current_index; + @IndexFor("nums") int current_index; - @IndexFor("nums2") int current_index2; + @IndexFor("nums2") int current_index2; - void test() { - current_index = 0; - // :: error: (assignment.type.incompatible) - current_index2 = 0; - } + void test() { + current_index = 0; + // :: error: (assignment.type.incompatible) + current_index2 = 0; + } } diff --git a/checker/tests/initialization/AnonymousInit.java b/checker/tests/initialization/AnonymousInit.java index 984217f99e4..fdf72d86fd2 100644 --- a/checker/tests/initialization/AnonymousInit.java +++ b/checker/tests/initialization/AnonymousInit.java @@ -1,21 +1,21 @@ // Ensure field initialization checks for anonymous // classes work. public class AnonymousInit { - Object o1 = - new Object() { - // :: error: (initialization.field.uninitialized) - Object s; + Object o1 = + new Object() { + // :: error: (initialization.field.uninitialized) + Object s; - public String toString() { - return s.toString(); - } - }; - Object o2 = - new Object() { - Object s = "hi"; + public String toString() { + return s.toString(); + } + }; + Object o2 = + new Object() { + Object s = "hi"; - public String toString() { - return s.toString(); - } - }; + public String toString() { + return s.toString(); + } + }; } diff --git a/checker/tests/initialization/CastInit.java b/checker/tests/initialization/CastInit.java index 15587ba8802..631b7da1bc4 100644 --- a/checker/tests/initialization/CastInit.java +++ b/checker/tests/initialization/CastInit.java @@ -3,9 +3,9 @@ public class CastInit { - public CastInit() { - @UnknownInitialization CastInit t1 = (@UnknownInitialization CastInit) this; - // :: warning: (cast.unsafe) - @Initialized CastInit t2 = (@Initialized CastInit) this; - } + public CastInit() { + @UnknownInitialization CastInit t1 = (@UnknownInitialization CastInit) this; + // :: warning: (cast.unsafe) + @Initialized CastInit t2 = (@Initialized CastInit) this; + } } diff --git a/checker/tests/initialization/ChainedInitialization.java b/checker/tests/initialization/ChainedInitialization.java index 7892432536e..774106348ea 100644 --- a/checker/tests/initialization/ChainedInitialization.java +++ b/checker/tests/initialization/ChainedInitialization.java @@ -2,13 +2,13 @@ public class ChainedInitialization { - @NonNull String f; - @NonNull String g = f = "hello"; + @NonNull String f; + @NonNull String g = f = "hello"; - // Adding this empty initializer suppresses the warning. - // {} + // Adding this empty initializer suppresses the warning. + // {} - // Adding this constructor does not suppress the warning. - // ChainedInitialization() {} + // Adding this constructor does not suppress the warning. + // ChainedInitialization() {} } diff --git a/checker/tests/initialization/Commitment.java b/checker/tests/initialization/Commitment.java index f5790beec3e..43de35194d6 100644 --- a/checker/tests/initialization/Commitment.java +++ b/checker/tests/initialization/Commitment.java @@ -6,72 +6,72 @@ public class Commitment { - @NonNull String t; - - // :: error: (initialization.invalid.field.type) - @NonNull @UnderInitialization String a; - // :: error: (initialization.invalid.field.type) - @Initialized String b; - @UnknownInitialization @Nullable String c; - - // :: error: (initialization.invalid.constructor.return.type) - public @UnderInitialization Commitment(int i) { - a = ""; - t = ""; - b = ""; - } - - // :: error: (initialization.invalid.constructor.return.type) - public @Initialized Commitment(int i, int j) { - a = ""; - t = ""; - b = ""; - } - - // :: error: (initialization.invalid.constructor.return.type) - // :: error: (nullness.on.constructor) - public @Initialized @NonNull Commitment(boolean i) { - a = ""; - t = ""; - b = ""; - } - - public - // :: error: (nullness.on.constructor) - @Nullable Commitment(char i) { - a = ""; - t = ""; - b = ""; - } - - // :: error: (initialization.fields.uninitialized) - public Commitment() { - // :: error: (dereference.of.nullable) - t.toLowerCase(); - - t = ""; - - @UnderInitialization @NonNull Commitment c = this; - - @UnknownInitialization @NonNull Commitment c1 = this; - - // :: error: (assignment.type.incompatible) - @Initialized @NonNull Commitment c2 = this; - } - - // :: error: (initialization.fields.uninitialized) - public Commitment(@UnknownInitialization Commitment arg) { - t = ""; - - // :: error: (argument.type.incompatible) - @UnderInitialization Commitment t = new Commitment(this, 1); - - // :: error: (assignment.type.incompatible) - @Initialized Commitment t1 = new Commitment(this); - - @UnderInitialization Commitment t2 = new Commitment(this); - } - - // :: error: (initialization.fields.uninitialized) - public Commitment(Commitment arg, int i) {} + @NonNull String t; + + // :: error: (initialization.invalid.field.type) + @NonNull @UnderInitialization String a; + // :: error: (initialization.invalid.field.type) + @Initialized String b; + @UnknownInitialization @Nullable String c; + + // :: error: (initialization.invalid.constructor.return.type) + public @UnderInitialization Commitment(int i) { + a = ""; + t = ""; + b = ""; + } + + // :: error: (initialization.invalid.constructor.return.type) + public @Initialized Commitment(int i, int j) { + a = ""; + t = ""; + b = ""; + } + + // :: error: (initialization.invalid.constructor.return.type) + // :: error: (nullness.on.constructor) + public @Initialized @NonNull Commitment(boolean i) { + a = ""; + t = ""; + b = ""; + } + + public + // :: error: (nullness.on.constructor) + @Nullable Commitment(char i) { + a = ""; + t = ""; + b = ""; + } + + // :: error: (initialization.fields.uninitialized) + public Commitment() { + // :: error: (dereference.of.nullable) + t.toLowerCase(); + + t = ""; + + @UnderInitialization @NonNull Commitment c = this; + + @UnknownInitialization @NonNull Commitment c1 = this; + + // :: error: (assignment.type.incompatible) + @Initialized @NonNull Commitment c2 = this; + } + + // :: error: (initialization.fields.uninitialized) + public Commitment(@UnknownInitialization Commitment arg) { + t = ""; + + // :: error: (argument.type.incompatible) + @UnderInitialization Commitment t = new Commitment(this, 1); + + // :: error: (assignment.type.incompatible) + @Initialized Commitment t1 = new Commitment(this); + + @UnderInitialization Commitment t2 = new Commitment(this); + } + + // :: error: (initialization.fields.uninitialized) + public Commitment(Commitment arg, int i) {} } diff --git a/checker/tests/initialization/Commitment2.java b/checker/tests/initialization/Commitment2.java index 5221ed436cf..9eea37287c1 100644 --- a/checker/tests/initialization/Commitment2.java +++ b/checker/tests/initialization/Commitment2.java @@ -4,32 +4,32 @@ public class Commitment2 { - // :: error: (assignment.type.incompatible) - Commitment2 g = create(); + // :: error: (assignment.type.incompatible) + Commitment2 g = create(); - Commitment2 h; + Commitment2 h; - @NotOnlyInitialized Commitment2 c; + @NotOnlyInitialized Commitment2 c; - @NotOnlyInitialized Commitment2 f; + @NotOnlyInitialized Commitment2 f; - public void test(@UnderInitialization Commitment2 c) { - // :: error: (initialization.invalid.field.write.initialized) - f = c; - } + public void test(@UnderInitialization Commitment2 c) { + // :: error: (initialization.invalid.field.write.initialized) + f = c; + } - public static @UnknownInitialization Commitment2 create() { - return new Commitment2(); - } + public static @UnknownInitialization Commitment2 create() { + return new Commitment2(); + } - // :: error: (initialization.fields.uninitialized) - public Commitment2() {} + // :: error: (initialization.fields.uninitialized) + public Commitment2() {} - // :: error: (initialization.fields.uninitialized) - public Commitment2(@UnderInitialization Commitment2 likeAnEagle) { - // :: error: (assignment.type.incompatible) - h = likeAnEagle; + // :: error: (initialization.fields.uninitialized) + public Commitment2(@UnderInitialization Commitment2 likeAnEagle) { + // :: error: (assignment.type.incompatible) + h = likeAnEagle; - c = likeAnEagle; - } + c = likeAnEagle; + } } diff --git a/checker/tests/initialization/CommitmentFlow.java b/checker/tests/initialization/CommitmentFlow.java index c9fd2107beb..e6a4d1d4abf 100644 --- a/checker/tests/initialization/CommitmentFlow.java +++ b/checker/tests/initialization/CommitmentFlow.java @@ -4,21 +4,22 @@ public class CommitmentFlow { - @NonNull CommitmentFlow t; + @NonNull CommitmentFlow t; - public CommitmentFlow(CommitmentFlow arg) { - t = arg; - } + public CommitmentFlow(CommitmentFlow arg) { + t = arg; + } - void foo( - @UnknownInitialization CommitmentFlow mystery, @Initialized CommitmentFlow triedAndTrue) { - CommitmentFlow local = null; + void foo( + @UnknownInitialization CommitmentFlow mystery, + @Initialized CommitmentFlow triedAndTrue) { + CommitmentFlow local = null; - local = mystery; - // :: error: (method.invocation.invalid) - local.hashCode(); + local = mystery; + // :: error: (method.invocation.invalid) + local.hashCode(); - local = triedAndTrue; - local.hashCode(); // should determine that it is Initialized based on flow - } + local = triedAndTrue; + local.hashCode(); // should determine that it is Initialized based on flow + } } diff --git a/checker/tests/initialization/FBCList.java b/checker/tests/initialization/FBCList.java index a04bc8f7fe7..14f1f5beb74 100644 --- a/checker/tests/initialization/FBCList.java +++ b/checker/tests/initialization/FBCList.java @@ -5,50 +5,50 @@ // This example is taken from the FBC paper, figure 1 (and has some additional code in main below). // We made the list generic. public class FBCList { - @NotOnlyInitialized FBCNode sentinel; + @NotOnlyInitialized FBCNode sentinel; - public FBCList() { - this.sentinel = new FBCNode<>(this); - } + public FBCList() { + this.sentinel = new FBCNode<>(this); + } - void insert(@Nullable T data) { - this.sentinel.insertAfter(data); - } + void insert(@Nullable T data) { + this.sentinel.insertAfter(data); + } - public static void main() { - FBCList l = new FBCList<>(); - l.insert(1); - l.insert(2); - } + public static void main() { + FBCList l = new FBCList<>(); + l.insert(1); + l.insert(2); + } } class FBCNode { - @NotOnlyInitialized FBCNode prev; - - @NotOnlyInitialized FBCNode next; - - @NotOnlyInitialized FBCList parent; - - @Nullable T data; - - // for sentinel construction - FBCNode(@UnderInitialization FBCList parent) { - this.parent = parent; - this.prev = this; - this.next = this; - } - - // for data node construction - FBCNode(FBCNode prev, FBCNode next, @Nullable T data) { - this.parent = prev.parent; - this.prev = prev; - this.next = next; - this.data = data; - } - - void insertAfter(@Nullable T data) { - FBCNode n = new FBCNode<>(this, this.next, data); - this.next.prev = n; - this.next = n; - } + @NotOnlyInitialized FBCNode prev; + + @NotOnlyInitialized FBCNode next; + + @NotOnlyInitialized FBCList parent; + + @Nullable T data; + + // for sentinel construction + FBCNode(@UnderInitialization FBCList parent) { + this.parent = parent; + this.prev = this; + this.next = this; + } + + // for data node construction + FBCNode(FBCNode prev, FBCNode next, @Nullable T data) { + this.parent = prev.parent; + this.prev = prev; + this.next = next; + this.data = data; + } + + void insertAfter(@Nullable T data) { + FBCNode n = new FBCNode<>(this, this.next, data); + this.next.prev = n; + this.next = n; + } } diff --git a/checker/tests/initialization/FieldSuppressWarnings.java b/checker/tests/initialization/FieldSuppressWarnings.java index 38b5498f535..7248baa3973 100644 --- a/checker/tests/initialization/FieldSuppressWarnings.java +++ b/checker/tests/initialization/FieldSuppressWarnings.java @@ -1,29 +1,29 @@ public class FieldSuppressWarnings { - static class FieldSuppressWarnings1 { - // :: error: (initialization.field.uninitialized) - private Object notInitialized; - } + static class FieldSuppressWarnings1 { + // :: error: (initialization.field.uninitialized) + private Object notInitialized; + } - static class FieldSuppressWarnings2 { - @SuppressWarnings("initialization.field.uninitialized") - private Object notInitializedButSuppressed1; - } + static class FieldSuppressWarnings2 { + @SuppressWarnings("initialization.field.uninitialized") + private Object notInitializedButSuppressed1; + } - static class FieldSuppressWarnings3 { - @SuppressWarnings("initialization") - private Object notInitializedButSuppressed2; - } + static class FieldSuppressWarnings3 { + @SuppressWarnings("initialization") + private Object notInitializedButSuppressed2; + } - static class FieldSuppressWarnings4 { - private Object initialized1; + static class FieldSuppressWarnings4 { + private Object initialized1; - { - initialized1 = new Object(); + { + initialized1 = new Object(); + } } - } - static class FieldSuppressWarnings5 { - private Object initialized2 = new Object(); - } + static class FieldSuppressWarnings5 { + private Object initialized2 = new Object(); + } } diff --git a/checker/tests/initialization/FieldWithInit.java b/checker/tests/initialization/FieldWithInit.java index 223ac5cef46..b42ea03adce 100644 --- a/checker/tests/initialization/FieldWithInit.java +++ b/checker/tests/initialization/FieldWithInit.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class FieldWithInit { - Object f = foo(); + Object f = foo(); - Object foo(@UnknownInitialization FieldWithInit this) { - return new Object(); - } + Object foo(@UnknownInitialization FieldWithInit this) { + return new Object(); + } } diff --git a/checker/tests/initialization/FlowFbc.java b/checker/tests/initialization/FlowFbc.java index 770cae8040c..e9feac6f8bb 100644 --- a/checker/tests/initialization/FlowFbc.java +++ b/checker/tests/initialization/FlowFbc.java @@ -5,42 +5,42 @@ public class FlowFbc { - @NonNull String f; - @NotOnlyInitialized @NonNull String g; - - public FlowFbc(String arg) { - // :: error: (dereference.of.nullable) - f.toLowerCase(); - - // We get a dereference.of.nullable error by the Nullness Checker because g may be null, - // as well as a method.invocation.invalid error by the Initialization Checker because g - // is declared as @NotOnlyInitialized and thus may not be @Initialized, - // but toLowerCase()'s receiver type is, by default, @Initialized. - // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) - g.toLowerCase(); - - f = arg; - g = arg; - foo(); - f.toLowerCase(); - // :: error: (method.invocation.invalid) - g.toLowerCase(); - f = arg; - } - - void test() { - @Nullable String s = null; - s = "a"; - s.toLowerCase(); - } - - void test2(@Nullable String s) { - if (s != null) { - s.toLowerCase(); + @NonNull String f; + @NotOnlyInitialized @NonNull String g; + + public FlowFbc(String arg) { + // :: error: (dereference.of.nullable) + f.toLowerCase(); + + // We get a dereference.of.nullable error by the Nullness Checker because g may be null, + // as well as a method.invocation.invalid error by the Initialization Checker because g + // is declared as @NotOnlyInitialized and thus may not be @Initialized, + // but toLowerCase()'s receiver type is, by default, @Initialized. + // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) + g.toLowerCase(); + + f = arg; + g = arg; + foo(); + f.toLowerCase(); + // :: error: (method.invocation.invalid) + g.toLowerCase(); + f = arg; } - } - void foo(@UnknownInitialization FlowFbc this) {} + void test() { + @Nullable String s = null; + s = "a"; + s.toLowerCase(); + } + + void test2(@Nullable String s) { + if (s != null) { + s.toLowerCase(); + } + } + + void foo(@UnknownInitialization FlowFbc this) {} - // TODO Pure, etc. + // TODO Pure, etc. } diff --git a/checker/tests/initialization/GenericTest12b.java b/checker/tests/initialization/GenericTest12b.java index f76c266131f..6a0cc594a63 100644 --- a/checker/tests/initialization/GenericTest12b.java +++ b/checker/tests/initialization/GenericTest12b.java @@ -2,21 +2,21 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class GenericTest12b { - class Cell {} + class Cell {} - class Node { - public Node(Cell userObject) {} + class Node { + public Node(Cell userObject) {} - void nodecall(@UnderInitialization Node this, Cell userObject) {} - } - - class RootNode extends Node { - public RootNode() { - super(new Cell()); - call(new Cell()); - nodecall(new Cell()); + void nodecall(@UnderInitialization Node this, Cell userObject) {} } - void call(@UnderInitialization RootNode this, Cell userObject) {} - } + class RootNode extends Node { + public RootNode() { + super(new Cell()); + call(new Cell()); + nodecall(new Cell()); + } + + void call(@UnderInitialization RootNode this, Cell userObject) {} + } } diff --git a/checker/tests/initialization/InstanceOf.java b/checker/tests/initialization/InstanceOf.java index 6dbada5e77c..6ced9d107e7 100644 --- a/checker/tests/initialization/InstanceOf.java +++ b/checker/tests/initialization/InstanceOf.java @@ -4,23 +4,23 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; class PptTopLevel { - class Ppt { - Object method() { - return ""; + class Ppt { + Object method() { + return ""; + } } - } - class OtherPpt extends Ppt {} + class OtherPpt extends Ppt {} } public class InstanceOf { - void foo(PptTopLevel.@UnknownInitialization(PptTopLevel.class) Ppt ppt) { - // :: error: (method.invocation.invalid) - ppt.method(); - if (ppt instanceof PptTopLevel.OtherPpt) { - PptTopLevel.OtherPpt pslice = (PptTopLevel.OtherPpt) ppt; - // :: error: (method.invocation.invalid) - String samp_str = " s" + pslice.method(); + void foo(PptTopLevel.@UnknownInitialization(PptTopLevel.class) Ppt ppt) { + // :: error: (method.invocation.invalid) + ppt.method(); + if (ppt instanceof PptTopLevel.OtherPpt) { + PptTopLevel.OtherPpt pslice = (PptTopLevel.OtherPpt) ppt; + // :: error: (method.invocation.invalid) + String samp_str = " s" + pslice.method(); + } } - } } diff --git a/checker/tests/initialization/Issue1044.java b/checker/tests/initialization/Issue1044.java index 8a8d72c220d..8660957d65a 100644 --- a/checker/tests/initialization/Issue1044.java +++ b/checker/tests/initialization/Issue1044.java @@ -5,69 +5,69 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1044 { - static class Inner1 { - // :: error: (initialization.field.uninitialized) - V f; - } + static class Inner1 { + // :: error: (initialization.field.uninitialized) + V f; + } - static class Inner2<@Nullable T extends @Nullable Object> { - // :: error: (initialization.field.uninitialized) - @NonNull T f; - } + static class Inner2<@Nullable T extends @Nullable Object> { + // :: error: (initialization.field.uninitialized) + @NonNull T f; + } - static class Inner3 { - V f; + static class Inner3 { + V f; - // :: error: (initialization.fields.uninitialized) - Inner3() {} - } + // :: error: (initialization.fields.uninitialized) + Inner3() {} + } - static class Inner4<@Nullable T extends @Nullable Object> { - @NonNull T f; + static class Inner4<@Nullable T extends @Nullable Object> { + @NonNull T f; - // :: error: (initialization.fields.uninitialized) - Inner4() {} - } + // :: error: (initialization.fields.uninitialized) + Inner4() {} + } - static class Inner5 { - @Nullable V f; - } + static class Inner5 { + @Nullable V f; + } - static class Inner6<@Nullable T extends @Nullable Object> { - T f; - } + static class Inner6<@Nullable T extends @Nullable Object> { + T f; + } - static class Inner7 { - @Nullable V f; + static class Inner7 { + @Nullable V f; - Inner7() {} - } + Inner7() {} + } - static class Inner8<@Nullable T extends @Nullable Object> { - T f; + static class Inner8<@Nullable T extends @Nullable Object> { + T f; - Inner8() {} - } + Inner8() {} + } - static class Inner9 { - // :: error: (initialization.field.uninitialized) - V f; - } + static class Inner9 { + // :: error: (initialization.field.uninitialized) + V f; + } - static class Inner10 { - V f; + static class Inner10 { + V f; - // :: error: (initialization.fields.uninitialized) - Inner10() {} - } + // :: error: (initialization.fields.uninitialized) + Inner10() {} + } - static class Inner11 { - @Nullable V f; - } + static class Inner11 { + @Nullable V f; + } - static class Inner12 { - @Nullable V f; + static class Inner12 { + @Nullable V f; - Inner12() {} - } + Inner12() {} + } } diff --git a/checker/tests/initialization/Issue1120.java b/checker/tests/initialization/Issue1120.java index 1cdd3709883..3cb2358100c 100644 --- a/checker/tests/initialization/Issue1120.java +++ b/checker/tests/initialization/Issue1120.java @@ -4,32 +4,32 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; class Issue1120Super { - Object f = new Object(); + Object f = new Object(); } final class Issue1120Sub extends Issue1120Super { - Object g; + Object g; - Issue1120Sub() { - this.party(); - // this is @UnderInitialization(A.class) - g = new Object(); - // this is @Initialized now - this.party(); - this.bar(); - } + Issue1120Sub() { + this.party(); + // this is @UnderInitialization(A.class) + g = new Object(); + // this is @Initialized now + this.party(); + this.bar(); + } - Issue1120Sub(int i) { - // this is @UnderInitialization(A.class) - this.party(); - // :: error: (method.invocation.invalid) - this.bar(); - g = new Object(); - } + Issue1120Sub(int i) { + // this is @UnderInitialization(A.class) + this.party(); + // :: error: (method.invocation.invalid) + this.bar(); + g = new Object(); + } - void bar() { - g.toString(); - } + void bar() { + g.toString(); + } - void party(@UnknownInitialization Issue1120Sub this) {} + void party(@UnknownInitialization Issue1120Sub this) {} } diff --git a/checker/tests/initialization/Issue1347.java b/checker/tests/initialization/Issue1347.java index b473f2ea522..b0e2702e6b1 100644 --- a/checker/tests/initialization/Issue1347.java +++ b/checker/tests/initialization/Issue1347.java @@ -2,23 +2,23 @@ // https://github.com/typetools/checker-framework/issues/1347 public class Issue1347 { - T t; - T t2; - Object o; + T t; + T t2; + Object o; - Issue1347(T t) { - this(t, 0); - } + Issue1347(T t) { + this(t, 0); + } - Issue1347(T t, int i) { - this.t = t; - this.t2 = t; - this.o = new Object(); - } + Issue1347(T t, int i) { + this.t = t; + this.t2 = t; + this.o = new Object(); + } - // :: error: (initialization.fields.uninitialized) - Issue1347(T t, String s) { - this.t = t; - this.o = new Object(); - } + // :: error: (initialization.fields.uninitialized) + Issue1347(T t, String s) { + this.t = t; + this.o = new Object(); + } } diff --git a/checker/tests/initialization/Issue3407.java b/checker/tests/initialization/Issue3407.java index 9a0ebe9624b..6bb7c3c1a62 100644 --- a/checker/tests/initialization/Issue3407.java +++ b/checker/tests/initialization/Issue3407.java @@ -1,20 +1,20 @@ // @below-java9-jdk-skip-test public class Issue3407 { - final String foo; + final String foo; - String getFoo() { - return foo; - } + String getFoo() { + return foo; + } - Issue3407() { - var anon = - new Object() { - String bar() { - // :: error: (method.invocation.invalid) - return Issue3407.this.getFoo().substring(1); - } - }; - anon.bar(); // / WHOOPS... NPE, `getFoo()` returns `foo` which is still null - this.foo = "Hello world"; - } + Issue3407() { + var anon = + new Object() { + String bar() { + // :: error: (method.invocation.invalid) + return Issue3407.this.getFoo().substring(1); + } + }; + anon.bar(); // / WHOOPS... NPE, `getFoo()` returns `foo` which is still null + this.foo = "Hello world"; + } } diff --git a/checker/tests/initialization/Issue408Init.java b/checker/tests/initialization/Issue408Init.java index bfa60a230cf..5ad4aad5921 100644 --- a/checker/tests/initialization/Issue408Init.java +++ b/checker/tests/initialization/Issue408Init.java @@ -4,27 +4,27 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class Issue408Init { - static class Bar { - Bar() { - doFoo(); - } + static class Bar { + Bar() { + doFoo(); + } - String doFoo(@UnderInitialization Bar this) { - return ""; + String doFoo(@UnderInitialization Bar this) { + return ""; + } } - } - static class Baz extends Bar { - String myString = "hello"; + static class Baz extends Bar { + String myString = "hello"; - @Override - String doFoo(@UnderInitialization Baz this) { - // :: error: (dereference.of.nullable) - return myString.toLowerCase(); + @Override + String doFoo(@UnderInitialization Baz this) { + // :: error: (dereference.of.nullable) + return myString.toLowerCase(); + } } - } - public static void main(String[] args) { - new Baz(); - } + public static void main(String[] args) { + new Baz(); + } } diff --git a/checker/tests/initialization/Issue409.java b/checker/tests/initialization/Issue409.java index 0f7e46a7af6..34dc6e5c79a 100644 --- a/checker/tests/initialization/Issue409.java +++ b/checker/tests/initialization/Issue409.java @@ -4,31 +4,32 @@ // @skip-test until the issue is fixed public class Issue409 { - public static void main(String[] args) { - new Callback(); - } + public static void main(String[] args) { + new Callback(); + } } class Callback { - class MyProc { - public void call() { - doStuff(); + class MyProc { + public void call() { + doStuff(); + } } - } - String foo; + String foo; - Callback() { - MyProc p = new MyProc(); - // This call is illegal. It passes an @UnderInitialization outer this, but MyProc.call is - // declared to take an @Initialized outer this (whith is the default type). - // :: error: (method.invocation.invalid) - p.call(); - foo = "hello"; - } + Callback() { + MyProc p = new MyProc(); + // This call is illegal. It passes an @UnderInitialization outer this, but MyProc.call is + // declared to take an @Initialized outer this (whith is the default type). + // :: error: (method.invocation.invalid) + p.call(); + foo = "hello"; + } - void doStuff() { - System.out.println(foo.toLowerCase()); // this line throws a NullPointerException at run time - } + void doStuff() { + System.out.println( + foo.toLowerCase()); // this line throws a NullPointerException at run time + } } diff --git a/checker/tests/initialization/Issue4567.java b/checker/tests/initialization/Issue4567.java index ecdb2f29321..fed5256e684 100644 --- a/checker/tests/initialization/Issue4567.java +++ b/checker/tests/initialization/Issue4567.java @@ -3,9 +3,9 @@ public class Issue4567 { - public Issue4567() { - this(null); - } + public Issue4567() { + this(null); + } - protected Issue4567(final @UnderInitialization @Nullable Object variableScope) {} + protected Issue4567(final @UnderInitialization @Nullable Object variableScope) {} } diff --git a/checker/tests/initialization/Issue556a.java b/checker/tests/initialization/Issue556a.java index 9618ccffeb7..f0d80f6cad5 100644 --- a/checker/tests/initialization/Issue556a.java +++ b/checker/tests/initialization/Issue556a.java @@ -7,11 +7,11 @@ public class Issue556a { - public static final Issue556a SELF = new Issue556a(); - private static final Object OBJ = new Object(); + public static final Issue556a SELF = new Issue556a(); + private static final Object OBJ = new Object(); - private Issue556a() { - // :: error: (assignment.type.incompatible) - @NonNull Object o = OBJ; - } + private Issue556a() { + // :: error: (assignment.type.incompatible) + @NonNull Object o = OBJ; + } } diff --git a/checker/tests/initialization/Issue556b.java b/checker/tests/initialization/Issue556b.java index e1cc1543c15..c0414feb3ef 100644 --- a/checker/tests/initialization/Issue556b.java +++ b/checker/tests/initialization/Issue556b.java @@ -15,82 +15,82 @@ // assumed to be initialized within the constructor. public class Issue556b { - static class Parent { - private final Object o; + static class Parent { + private final Object o; - public Parent(final Object o) { - this.o = o; - } + public Parent(final Object o) { + this.o = o; + } - @Override - public String toString() { - return o.toString(); - } - } - - static class Child extends Parent { - public static final Child CHILD = new Child(); - private static final Object OBJ = new Object(); - - private Child() { - // This call should not be legal, because at the time that the call occurs, the static - // initializers of Child have not yet finished executing and therefore CHILD and OBJ are - // not necessarily initialized and are not necessarily non-null. - // :: error: (method.invocation.invalid) - super(OBJ); + @Override + public String toString() { + return o.toString(); + } } - } - - static class Child2 extends Parent { - public static final Child2 CHILD; - private static final Object OBJ; - static { - CHILD = new Child2(); - OBJ = new Object(); + static class Child extends Parent { + public static final Child CHILD = new Child(); + private static final Object OBJ = new Object(); + + private Child() { + // This call should not be legal, because at the time that the call occurs, the static + // initializers of Child have not yet finished executing and therefore CHILD and OBJ are + // not necessarily initialized and are not necessarily non-null. + // :: error: (method.invocation.invalid) + super(OBJ); + } } - private Child2() { - // This call should not be legal, because at the time that the call occurs, the static - // initializers of Child have not yet finished executing and therefore CHILD and OBJ are - // not necessarily initialized and are not necessarily non-null. - // :: error: (method.invocation.invalid) - super(OBJ); + static class Child2 extends Parent { + public static final Child2 CHILD; + private static final Object OBJ; + + static { + CHILD = new Child2(); + OBJ = new Object(); + } + + private Child2() { + // This call should not be legal, because at the time that the call occurs, the static + // initializers of Child have not yet finished executing and therefore CHILD and OBJ are + // not necessarily initialized and are not necessarily non-null. + // :: error: (method.invocation.invalid) + super(OBJ); + } } - } - - // Changing the order of the OBJ and CHILD fields prevents a null pointer exception. - static class ChildOk1 extends Parent { - private static final Object OBJ = new Object(); - public static final Child CHILD = new Child(); - - private ChildOk1() { - // This call is legal, because OBJ is non-null at the time of the - // call. That's because OBJ is initialized before CHILD and - // therefore before the call to "new Child()". - super(OBJ); - } - } - - // Changing the order of the OBJ and CHILD field assignments prevents a null pointer exception. - static class ChildOk2 extends Parent { - public static final ChildOk2 CHILD; - private static final Object OBJ; - static { - OBJ = new Object(); - CHILD = new ChildOk2(); + // Changing the order of the OBJ and CHILD fields prevents a null pointer exception. + static class ChildOk1 extends Parent { + private static final Object OBJ = new Object(); + public static final Child CHILD = new Child(); + + private ChildOk1() { + // This call is legal, because OBJ is non-null at the time of the + // call. That's because OBJ is initialized before CHILD and + // therefore before the call to "new Child()". + super(OBJ); + } } - private ChildOk2() { - // This call is legal, because OBJ is non-null at the time of the - // call. That's because OBJ is initialized before CHILD and - // therefore before the call to "new Child()". - super(OBJ); + // Changing the order of the OBJ and CHILD field assignments prevents a null pointer exception. + static class ChildOk2 extends Parent { + public static final ChildOk2 CHILD; + private static final Object OBJ; + + static { + OBJ = new Object(); + CHILD = new ChildOk2(); + } + + private ChildOk2() { + // This call is legal, because OBJ is non-null at the time of the + // call. That's because OBJ is initialized before CHILD and + // therefore before the call to "new Child()". + super(OBJ); + } } - } - public static void main(final String[] args) { - System.out.println(Child.CHILD); - } + public static void main(final String[] args) { + System.out.println(Child.CHILD); + } } diff --git a/checker/tests/initialization/Issue574.java b/checker/tests/initialization/Issue574.java index ef97ed310a1..6a26eec4ebf 100644 --- a/checker/tests/initialization/Issue574.java +++ b/checker/tests/initialization/Issue574.java @@ -3,51 +3,51 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; @SuppressWarnings({ - // A warning is issued that fields are not initialized in the constructor. - // That is expected and it is not what is being verified in this test. - "initialization.field.uninitialized", - // Normally @UnknownInitialization is the only initialization annotation allowed on fields. - // However, for the purposes of this test, fields must be annotated with @UnderInitialization. - "initialization.invalid.field.type" + // A warning is issued that fields are not initialized in the constructor. + // That is expected and it is not what is being verified in this test. + "initialization.field.uninitialized", + // Normally @UnknownInitialization is the only initialization annotation allowed on fields. + // However, for the purposes of this test, fields must be annotated with @UnderInitialization. + "initialization.invalid.field.type" }) public class Issue574 { - @UnderInitialization(Object.class) Object o1; + @UnderInitialization(Object.class) Object o1; - @UnderInitialization(String.class) Object o2; + @UnderInitialization(String.class) Object o2; - @UnderInitialization(Character.class) Object o3; + @UnderInitialization(Character.class) Object o3; - @UnderInitialization(Number.class) Object o4; + @UnderInitialization(Number.class) Object o4; - @UnderInitialization(Double.class) Object o5; + @UnderInitialization(Double.class) Object o5; - @UnderInitialization(Integer.class) Object o6; + @UnderInitialization(Integer.class) Object o6; - @UnderInitialization(CharSequence.class) Object i1; // CharSequence is an interface + @UnderInitialization(CharSequence.class) Object i1; // CharSequence is an interface - void testLubOfClasses(boolean flag) { - @UnderInitialization(Object.class) Object l1 = flag ? o2 : o3; - @UnderInitialization(Number.class) Object l2 = flag ? o5 : o6; + void testLubOfClasses(boolean flag) { + @UnderInitialization(Object.class) Object l1 = flag ? o2 : o3; + @UnderInitialization(Number.class) Object l2 = flag ? o5 : o6; - @UnderInitialization(Object.class) Object l3 = flag ? o1 : o2; - @UnderInitialization(Object.class) Object l4 = flag ? o1 : o3; + @UnderInitialization(Object.class) Object l3 = flag ? o1 : o2; + @UnderInitialization(Object.class) Object l4 = flag ? o1 : o3; - @UnderInitialization(Number.class) Object l5 = flag ? o4 : o5; - @UnderInitialization(Number.class) Object l6 = flag ? o4 : o6; + @UnderInitialization(Number.class) Object l5 = flag ? o4 : o5; + @UnderInitialization(Number.class) Object l6 = flag ? o4 : o6; - // :: error: (assignment.type.incompatible) - @UnderInitialization(Character.class) Object l7 = flag ? o1 : o2; - // :: error: (assignment.type.incompatible) - @UnderInitialization(Integer.class) Object l8 = flag ? o4 : o5; - } + // :: error: (assignment.type.incompatible) + @UnderInitialization(Character.class) Object l7 = flag ? o1 : o2; + // :: error: (assignment.type.incompatible) + @UnderInitialization(Integer.class) Object l8 = flag ? o4 : o5; + } - void testLubOfClassesAndInterfaces(boolean flag) { - @UnderInitialization(Object.class) Object l1 = flag ? i1 : o3; + void testLubOfClassesAndInterfaces(boolean flag) { + @UnderInitialization(Object.class) Object l1 = flag ? i1 : o3; - @UnderInitialization(Object.class) Object l2 = flag ? o1 : i1; - @UnderInitialization(Object.class) Object l3 = flag ? o1 : o3; + @UnderInitialization(Object.class) Object l2 = flag ? o1 : i1; + @UnderInitialization(Object.class) Object l3 = flag ? o1 : o3; - // :: error: (assignment.type.incompatible) - @UnderInitialization(Character.class) Object l4 = flag ? o1 : i1; - } + // :: error: (assignment.type.incompatible) + @UnderInitialization(Character.class) Object l4 = flag ? o1 : i1; + } } diff --git a/checker/tests/initialization/Issue779.java b/checker/tests/initialization/Issue779.java index 97e49c2ee2d..1d2db241f76 100644 --- a/checker/tests/initialization/Issue779.java +++ b/checker/tests/initialization/Issue779.java @@ -5,28 +5,28 @@ import org.checkerframework.checker.nullness.qual.*; class A { - Object g = new Object(); + Object g = new Object(); - A() { - foo(); - } + A() { + foo(); + } - void foo(@UnderInitialization(A.class) A this) { - System.out.println("foo A " + g.toString()); - } + void foo(@UnderInitialization(A.class) A this) { + System.out.println("foo A " + g.toString()); + } } class B extends A { - Object f = new Object(); + Object f = new Object(); - void foo(@UnderInitialization(A.class) B this) { - // :: error: (dereference.of.nullable) - System.out.println("foo B " + this.f.toString()); - } + void foo(@UnderInitialization(A.class) B this) { + // :: error: (dereference.of.nullable) + System.out.println("foo B " + this.f.toString()); + } } public class Issue779 { - public static void main(String[] args) { - new B(); - } + public static void main(String[] args) { + new B(); + } } diff --git a/checker/tests/initialization/Issue813.java b/checker/tests/initialization/Issue813.java index fc8187d6412..30689628c14 100644 --- a/checker/tests/initialization/Issue813.java +++ b/checker/tests/initialization/Issue813.java @@ -6,21 +6,21 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class Issue813 { - static interface MyInterface {} + static interface MyInterface {} - static class MyClass { - MyClass(@UnderInitialization MyInterface stuff) {} - } + static class MyClass { + MyClass(@UnderInitialization MyInterface stuff) {} + } - static class Fails implements MyInterface { - @NotOnlyInitialized MyClass bar = new MyClass(this); - } + static class Fails implements MyInterface { + @NotOnlyInitialized MyClass bar = new MyClass(this); + } - static class Works implements MyInterface { - @NotOnlyInitialized MyClass bar; + static class Works implements MyInterface { + @NotOnlyInitialized MyClass bar; - { - bar = new MyClass(this); // works + { + bar = new MyClass(this); // works + } } - } } diff --git a/checker/tests/initialization/Issue904.java b/checker/tests/initialization/Issue904.java index f8c2806a64e..19f2b844a00 100644 --- a/checker/tests/initialization/Issue904.java +++ b/checker/tests/initialization/Issue904.java @@ -2,22 +2,22 @@ // https://github.com/typetools/checker-framework/issues/904 public class Issue904 { - final Object mBar; - final Runnable mRunnable = - new Runnable() { - @Override - public void run() { - // :: error: (dereference.of.nullable) - mBar.toString(); - } - }; + final Object mBar; + final Runnable mRunnable = + new Runnable() { + @Override + public void run() { + // :: error: (dereference.of.nullable) + mBar.toString(); + } + }; - public Issue904() { - mRunnable.run(); - mBar = ""; - } + public Issue904() { + mRunnable.run(); + mBar = ""; + } - public static void main(String[] args) { - new Issue904(); - } + public static void main(String[] args) { + new Issue904(); + } } diff --git a/checker/tests/initialization/Issue905.java b/checker/tests/initialization/Issue905.java index ecd45675b48..dda7b6e8d0a 100644 --- a/checker/tests/initialization/Issue905.java +++ b/checker/tests/initialization/Issue905.java @@ -4,25 +4,25 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class Issue905 { - final Object mBar; + final Object mBar; - Issue905() { - // this should be @UnderInitialization(Object.class), so this call should be forbidden. - // :: error: (method.invocation.invalid) - baz(); - mBar = ""; - } + Issue905() { + // this should be @UnderInitialization(Object.class), so this call should be forbidden. + // :: error: (method.invocation.invalid) + baz(); + mBar = ""; + } - Issue905(int i) { - mBar = ""; - baz(); - } + Issue905(int i) { + mBar = ""; + baz(); + } - void baz(@UnknownInitialization(Issue905.class) Issue905 this) { - mBar.toString(); - } + void baz(@UnknownInitialization(Issue905.class) Issue905 this) { + mBar.toString(); + } - public static void main(String[] args) { - new Issue905(); - } + public static void main(String[] args) { + new Issue905(); + } } diff --git a/checker/tests/initialization/NotOnlyInitializedTest.java b/checker/tests/initialization/NotOnlyInitializedTest.java index 5b5fe382aaa..690de5b4610 100644 --- a/checker/tests/initialization/NotOnlyInitializedTest.java +++ b/checker/tests/initialization/NotOnlyInitializedTest.java @@ -3,37 +3,37 @@ public class NotOnlyInitializedTest { - @NotOnlyInitialized NotOnlyInitializedTest f; - NotOnlyInitializedTest g; + @NotOnlyInitialized NotOnlyInitializedTest f; + NotOnlyInitializedTest g; - public NotOnlyInitializedTest() { - f = new NotOnlyInitializedTest(); - g = new NotOnlyInitializedTest(); - } + public NotOnlyInitializedTest() { + f = new NotOnlyInitializedTest(); + g = new NotOnlyInitializedTest(); + } - public NotOnlyInitializedTest(char i) { - // we can store something that is under initialization (like this) in f, but not in g - f = this; - // :: error: (assignment.type.incompatible) - g = this; - } + public NotOnlyInitializedTest(char i) { + // we can store something that is under initialization (like this) in f, but not in g + f = this; + // :: error: (assignment.type.incompatible) + g = this; + } - static void testDeref(NotOnlyInitializedTest o) { - // o is fully iniatlized, so we can dereference its fields - o.f.toString(); - o.g.toString(); - } + static void testDeref(NotOnlyInitializedTest o) { + // o is fully iniatlized, so we can dereference its fields + o.f.toString(); + o.g.toString(); + } - static void testDeref2(@UnderInitialization NotOnlyInitializedTest o) { - // o is not fully iniatlized, so we cannot dereference its fields. - // We thus get a dereference.of.nullable error by the Nullness Checker for both o.f and o.g. - // For o.f, we also get a method.invocation.invalid error by the Initialization Checker - // because o.f is declared as @NotOnlyInitialized and thus may not be @Initialized, - // but toLowerCase()'s receiver type is, by default, @Initialized. + static void testDeref2(@UnderInitialization NotOnlyInitializedTest o) { + // o is not fully iniatlized, so we cannot dereference its fields. + // We thus get a dereference.of.nullable error by the Nullness Checker for both o.f and o.g. + // For o.f, we also get a method.invocation.invalid error by the Initialization Checker + // because o.f is declared as @NotOnlyInitialized and thus may not be @Initialized, + // but toLowerCase()'s receiver type is, by default, @Initialized. - // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) - o.f.toString(); - // :: error: (dereference.of.nullable) - o.g.toString(); - } + // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) + o.f.toString(); + // :: error: (dereference.of.nullable) + o.g.toString(); + } } diff --git a/checker/tests/initialization/RawMethodInvocation.java b/checker/tests/initialization/RawMethodInvocation.java index 0a24048204c..8aa9117238d 100644 --- a/checker/tests/initialization/RawMethodInvocation.java +++ b/checker/tests/initialization/RawMethodInvocation.java @@ -4,46 +4,46 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class RawMethodInvocation { - @NonNull String a; - @NonNull String b; - - RawMethodInvocation(boolean constructor_inits_a) { - a = ""; - init_b(); - } - - @EnsuresNonNull("b") - void init_b(@UnknownInitialization RawMethodInvocation this) { - b = ""; - } - - // :: error: (initialization.fields.uninitialized) - RawMethodInvocation(Byte constructor_inits_b) { - init_b(); - } - - // :: error: (initialization.fields.uninitialized) - RawMethodInvocation(byte constructor_inits_b) { - b = ""; - init_b(); - } - - RawMethodInvocation(int constructor_inits_none) { - init_ab(); - } - - @EnsuresNonNull({"a", "b"}) - void init_ab(@UnknownInitialization RawMethodInvocation this) { - a = ""; - b = ""; - } - - RawMethodInvocation(long constructor_escapes_raw) { - a = ""; - // :: error: (method.invocation.invalid) - nonRawMethod(); - b = ""; - } - - void nonRawMethod() {} + @NonNull String a; + @NonNull String b; + + RawMethodInvocation(boolean constructor_inits_a) { + a = ""; + init_b(); + } + + @EnsuresNonNull("b") + void init_b(@UnknownInitialization RawMethodInvocation this) { + b = ""; + } + + // :: error: (initialization.fields.uninitialized) + RawMethodInvocation(Byte constructor_inits_b) { + init_b(); + } + + // :: error: (initialization.fields.uninitialized) + RawMethodInvocation(byte constructor_inits_b) { + b = ""; + init_b(); + } + + RawMethodInvocation(int constructor_inits_none) { + init_ab(); + } + + @EnsuresNonNull({"a", "b"}) + void init_ab(@UnknownInitialization RawMethodInvocation this) { + a = ""; + b = ""; + } + + RawMethodInvocation(long constructor_escapes_raw) { + a = ""; + // :: error: (method.invocation.invalid) + nonRawMethod(); + b = ""; + } + + void nonRawMethod() {} } diff --git a/checker/tests/initialization/RawTypesInit.java b/checker/tests/initialization/RawTypesInit.java index d4c727d4519..ec828c2c8d1 100644 --- a/checker/tests/initialization/RawTypesInit.java +++ b/checker/tests/initialization/RawTypesInit.java @@ -7,278 +7,278 @@ public class RawTypesInit { - class Bad { - @NonNull String field; - - public Bad() { - // :: error: (method.invocation.invalid) - this.init(); // error - // :: error: (method.invocation.invalid) - init(); // error - - this.field = "field"; // valid - // :: error: (assignment.type.incompatible) - this.field = null; // error - field = "field"; // valid - // :: error: (assignment.type.incompatible) - field = null; // error + class Bad { + @NonNull String field; + + public Bad() { + // :: error: (method.invocation.invalid) + this.init(); // error + // :: error: (method.invocation.invalid) + init(); // error + + this.field = "field"; // valid + // :: error: (assignment.type.incompatible) + this.field = null; // error + field = "field"; // valid + // :: error: (assignment.type.incompatible) + field = null; // error + } + + void init() { + output(this.field.length()); // valid + } } - void init() { - output(this.field.length()); // valid + class A { + @NonNull String field; + + public A() { + this.field = "field"; // valid + field = "field"; // valid + this.init(); // valid + init(); // valid + } + + public void init(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); + } + + public void initExpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(this.field); + } + + public void initImpl1(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(field.length()); + } + + public void initImpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(field); + } } - } - class A { - @NonNull String field; - - public A() { - this.field = "field"; // valid - field = "field"; // valid - this.init(); // valid - init(); // valid + class B extends A { + @NonNull String otherField; + + public B() { + super(); + // :: error: (assignment.type.incompatible) + this.otherField = null; // error + this.otherField = "otherField"; // valid + } + + @Override + public void init(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); // error (TODO: substitution) + super.init(); // valid + } + + public void initImpl1(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(field.length()); // error (TODO: substitution) + } + + public void initExpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.otherField.length()); // error + } + + public void initImpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(otherField.length()); // error + } + + void other() { + init(); // valid + this.init(); // valid + } + + void otherRaw(@UnknownInitialization B this) { + init(); // valid + this.init(); // valid + } } - public void init(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); - } + class C extends B { - public void initExpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(this.field); - } - - public void initImpl1(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(field.length()); - } + // :: error: (initialization.field.uninitialized) + @NonNull String[] strings; - public void initImpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(field); + @Override + public void init(@UnknownInitialization C this) { + // :: error: (dereference.of.nullable) + output(this.strings.length); // error + System.out.println(); // valid + } } - } - class B extends A { - @NonNull String otherField; + // To test whether the argument is @NonNull and @Initialized + static void output(@NonNull Object o) {} - public B() { - super(); - // :: error: (assignment.type.incompatible) - this.otherField = null; // error - this.otherField = "otherField"; // valid + class D extends C { + @Override + public void init(@UnknownInitialization D this) { + this.field = "s"; + output(this.field.length()); + } } - @Override - public void init(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); // error (TODO: substitution) - super.init(); // valid - } + class MyTest { + Integer i; - public void initImpl1(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(field.length()); // error (TODO: substitution) - } + MyTest(int i) { + this.i = i; + } - public void initExpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.otherField.length()); // error + void myTest(@UnknownInitialization MyTest this) { + // :: error: (unboxing.of.nullable) + i = i + 1; + } } - public void initImpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(otherField.length()); // error - } + class AllFieldsInitialized { + Integer elapsedMillis = 0; + Integer startTime = 0; - void other() { - init(); // valid - this.init(); // valid - } + public AllFieldsInitialized() { + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - void otherRaw(@UnknownInitialization B this) { - init(); // valid - this.init(); // valid + public void nonRawMethod() {} } - } - class C extends B { - - // :: error: (initialization.field.uninitialized) - @NonNull String[] strings; - - @Override - public void init(@UnknownInitialization C this) { - // :: error: (dereference.of.nullable) - output(this.strings.length); // error - System.out.println(); // valid + class AFSIICell { + // :: error: (initialization.field.uninitialized) + AllFieldsSetInInitializer afsii; } - } - // To test whether the argument is @NonNull and @Initialized - static void output(@NonNull Object o) {} - - class D extends C { - @Override - public void init(@UnknownInitialization D this) { - this.field = "s"; - output(this.field.length()); + class AllFieldsSetInInitializer { + Integer elapsedMillis; + Integer startTime; + + public AllFieldsSetInInitializer() { + elapsedMillis = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); // error + startTime = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); // error + // :: error: (initialization.invalid.field.write.initialized) + new AFSIICell().afsii = this; + } + + // :: error: (initialization.fields.uninitialized) + public AllFieldsSetInInitializer(boolean b) { + // :: error: (method.invocation.invalid) + nonRawMethod(); // error + } + + public void nonRawMethod() {} } - } - - class MyTest { - Integer i; - MyTest(int i) { - this.i = i; - } + class ConstructorInvocations { + Integer v; - void myTest(@UnknownInitialization MyTest this) { - // :: error: (unboxing.of.nullable) - i = i + 1; - } - } + public ConstructorInvocations(int v) { + this.v = v; + } - class AllFieldsInitialized { - Integer elapsedMillis = 0; - Integer startTime = 0; + public ConstructorInvocations() { + this(0); + // :: error: (method.invocation.invalid) + nonRawMethod(); // invalid + } - public AllFieldsInitialized() { - // :: error: (method.invocation.invalid) - nonRawMethod(); + public void nonRawMethod() {} } - public void nonRawMethod() {} - } - - class AFSIICell { - // :: error: (initialization.field.uninitialized) - AllFieldsSetInInitializer afsii; - } - - class AllFieldsSetInInitializer { - Integer elapsedMillis; - Integer startTime; - - public AllFieldsSetInInitializer() { - elapsedMillis = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); // error - startTime = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); // error - // :: error: (initialization.invalid.field.write.initialized) - new AFSIICell().afsii = this; - } + class MethodAccess { + public MethodAccess() { + @NonNull String s = string(); + } - // :: error: (initialization.fields.uninitialized) - public AllFieldsSetInInitializer(boolean b) { - // :: error: (method.invocation.invalid) - nonRawMethod(); // error + public @NonNull String string(@UnknownInitialization MethodAccess this) { + return "nonnull"; + } } - public void nonRawMethod() {} - } + void cast(@UnknownInitialization Object... args) { - class ConstructorInvocations { - Integer v; + @SuppressWarnings("rawtypes") + // :: error: (assignment.type.incompatible) + Object[] argsNonRaw1 = args; - public ConstructorInvocations(int v) { - this.v = v; + @SuppressWarnings("cast") + Object[] argsNonRaw2 = (Object[]) args; } - public ConstructorInvocations() { - this(0); - // :: error: (method.invocation.invalid) - nonRawMethod(); // invalid - } + class RawAfterConstructorBad { + Object o; - public void nonRawMethod() {} - } - - class MethodAccess { - public MethodAccess() { - @NonNull String s = string(); - } - - public @NonNull String string(@UnknownInitialization MethodAccess this) { - return "nonnull"; + // :: error: (initialization.fields.uninitialized) + RawAfterConstructorBad() {} } - } - - void cast(@UnknownInitialization Object... args) { - - @SuppressWarnings("rawtypes") - // :: error: (assignment.type.incompatible) - Object[] argsNonRaw1 = args; - - @SuppressWarnings("cast") - Object[] argsNonRaw2 = (Object[]) args; - } - - class RawAfterConstructorBad { - Object o; - - // :: error: (initialization.fields.uninitialized) - RawAfterConstructorBad() {} - } - class RawAfterConstructorOK1 { - @Nullable Object o; + class RawAfterConstructorOK1 { + @Nullable Object o; - RawAfterConstructorOK1() {} - } - - class RawAfterConstructorOK2 { - Integer a; - - // :: error: (initialization.fields.uninitialized) - RawAfterConstructorOK2() {} - } - - // TODO: reinstate. This shows desired features, for initialization in - // a helper method rather than in the constructor. - class InitInHelperMethod { - Integer a; - Integer b; - - InitInHelperMethod(short constructor_inits_ab) { - a = 1; - b = 1; - // :: error: (method.invocation.invalid) - nonRawMethod(); + RawAfterConstructorOK1() {} } - InitInHelperMethod(boolean constructor_inits_a) { - a = 1; - init_b(); - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - @RequiresNonNull("a") - @EnsuresNonNull("b") - void init_b(@UnknownInitialization InitInHelperMethod this) { - b = 2; - // :: error: (method.invocation.invalid) - nonRawMethod(); - } + class RawAfterConstructorOK2 { + Integer a; - InitInHelperMethod(int constructor_inits_none) { - init_ab(); - // :: error: (method.invocation.invalid) - nonRawMethod(); + // :: error: (initialization.fields.uninitialized) + RawAfterConstructorOK2() {} } - @EnsuresNonNull({"a", "b"}) - void init_ab(@UnknownInitialization InitInHelperMethod this) { - a = 1; - b = 2; - // :: error: (method.invocation.invalid) - nonRawMethod(); + // TODO: reinstate. This shows desired features, for initialization in + // a helper method rather than in the constructor. + class InitInHelperMethod { + Integer a; + Integer b; + + InitInHelperMethod(short constructor_inits_ab) { + a = 1; + b = 1; + // :: error: (method.invocation.invalid) + nonRawMethod(); + } + + InitInHelperMethod(boolean constructor_inits_a) { + a = 1; + init_b(); + // :: error: (method.invocation.invalid) + nonRawMethod(); + } + + @RequiresNonNull("a") + @EnsuresNonNull("b") + void init_b(@UnknownInitialization InitInHelperMethod this) { + b = 2; + // :: error: (method.invocation.invalid) + nonRawMethod(); + } + + InitInHelperMethod(int constructor_inits_none) { + init_ab(); + // :: error: (method.invocation.invalid) + nonRawMethod(); + } + + @EnsuresNonNull({"a", "b"}) + void init_ab(@UnknownInitialization InitInHelperMethod this) { + a = 1; + b = 2; + // :: error: (method.invocation.invalid) + nonRawMethod(); + } + + void nonRawMethod() {} } - - void nonRawMethod() {} - } } diff --git a/checker/tests/initialization/ReceiverSuperInvocation.java b/checker/tests/initialization/ReceiverSuperInvocation.java index 47d24ff8e34..6ec0be1e7cb 100644 --- a/checker/tests/initialization/ReceiverSuperInvocation.java +++ b/checker/tests/initialization/ReceiverSuperInvocation.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class ReceiverSuperInvocation { - void foo(@UnderInitialization(ReceiverSuperInvocation.class) ReceiverSuperInvocation this) {} + void foo(@UnderInitialization(ReceiverSuperInvocation.class) ReceiverSuperInvocation this) {} } class ReceiverSuperInvocationSubclass extends ReceiverSuperInvocation { - @Override - void foo(@UnderInitialization(Object.class) ReceiverSuperInvocationSubclass this) { - // :: error: (method.invocation.invalid) - super.foo(); - } + @Override + void foo(@UnderInitialization(Object.class) ReceiverSuperInvocationSubclass this) { + // :: error: (method.invocation.invalid) + super.foo(); + } } diff --git a/checker/tests/initialization/SimpleFbc.java b/checker/tests/initialization/SimpleFbc.java index c9c39df89c4..967608be45c 100644 --- a/checker/tests/initialization/SimpleFbc.java +++ b/checker/tests/initialization/SimpleFbc.java @@ -7,51 +7,51 @@ public class SimpleFbc { - SimpleFbc f; - @NotOnlyInitialized SimpleFbc g; + SimpleFbc f; + @NotOnlyInitialized SimpleFbc g; - @Pure - int pure() { - return 1; - } + @Pure + int pure() { + return 1; + } - // :: error: (initialization.fields.uninitialized) - public SimpleFbc(String arg) {} + // :: error: (initialization.fields.uninitialized) + public SimpleFbc(String arg) {} - void test() { - @NonNull String s = "234"; + void test() { + @NonNull String s = "234"; - // :: error: (assignment.type.incompatible) - s = null; - System.out.println(s); - } + // :: error: (assignment.type.incompatible) + s = null; + System.out.println(s); + } - void test2(@UnknownInitialization @NonNull SimpleFbc t) { - // :: error: (assignment.type.incompatible) - @Initialized @NonNull SimpleFbc a = t.f; - } + void test2(@UnknownInitialization @NonNull SimpleFbc t) { + // :: error: (assignment.type.incompatible) + @Initialized @NonNull SimpleFbc a = t.f; + } - // check initialized-only semantics for fields - void test3(@UnknownInitialization @NonNull SimpleFbc t) { - @Initialized @Nullable SimpleFbc a = t.f; + // check initialized-only semantics for fields + void test3(@UnknownInitialization @NonNull SimpleFbc t) { + @Initialized @Nullable SimpleFbc a = t.f; - // :: error: (assignment.type.incompatible) - @Initialized @Nullable SimpleFbc b = t.g; - } + // :: error: (assignment.type.incompatible) + @Initialized @Nullable SimpleFbc b = t.g; + } - void simplestTestEver() { - @NonNull String a = "abc"; + void simplestTestEver() { + @NonNull String a = "abc"; - // :: error: (assignment.type.incompatible) - a = null; + // :: error: (assignment.type.incompatible) + a = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = null; - } + // :: error: (assignment.type.incompatible) + @NonNull String b = null; + } - void anotherMethod() { - @Nullable String s = null; + void anotherMethod() { + @Nullable String s = null; - @Initialized @Nullable String t = s; - } + @Initialized @Nullable String t = s; + } } diff --git a/checker/tests/initialization/StaticInit.java b/checker/tests/initialization/StaticInit.java index 01827e0fd50..64f15adc396 100644 --- a/checker/tests/initialization/StaticInit.java +++ b/checker/tests/initialization/StaticInit.java @@ -4,9 +4,9 @@ public class StaticInit { - static String a; + static String a; - static { - a.toString(); - } + static { + a.toString(); + } } diff --git a/checker/tests/initialization/Subtyping.java b/checker/tests/initialization/Subtyping.java index f302126929f..4ea0b1b36da 100644 --- a/checker/tests/initialization/Subtyping.java +++ b/checker/tests/initialization/Subtyping.java @@ -2,50 +2,50 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class Subtyping { - void test1( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - // ::error: (assignment.type.incompatible) - underObject = unknownObject; - underObject = underSubtyping; - // ::error: (assignment.type.incompatible) - underObject = unknownSubtyping; - } + void test1( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + // ::error: (assignment.type.incompatible) + underObject = unknownObject; + underObject = underSubtyping; + // ::error: (assignment.type.incompatible) + underObject = unknownSubtyping; + } - void test2( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - unknownObject = underSubtyping; - unknownObject = unknownSubtyping; - unknownObject = underObject; - } + void test2( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + unknownObject = underSubtyping; + unknownObject = unknownSubtyping; + unknownObject = underObject; + } - void test3( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - // ::error: (assignment.type.incompatible) - underSubtyping = unknownObject; - // ::error: (assignment.type.incompatible) - underSubtyping = unknownSubtyping; - // ::error: (assignment.type.incompatible) - underSubtyping = underObject; - } + void test3( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + // ::error: (assignment.type.incompatible) + underSubtyping = unknownObject; + // ::error: (assignment.type.incompatible) + underSubtyping = unknownSubtyping; + // ::error: (assignment.type.incompatible) + underSubtyping = underObject; + } - void test4( - @UnknownInitialization(Object.class) Object unknownObject, - @UnderInitialization(Object.class) Object underObject, - @UnknownInitialization(Subtyping.class) Object unknownSubtyping, - @UnderInitialization(Subtyping.class) Object underSubtyping) { - // ::error: (assignment.type.incompatible) - unknownSubtyping = unknownObject; - unknownSubtyping = underSubtyping; - // ::error: (assignment.type.incompatible) - unknownSubtyping = underObject; - } + void test4( + @UnknownInitialization(Object.class) Object unknownObject, + @UnderInitialization(Object.class) Object underObject, + @UnknownInitialization(Subtyping.class) Object unknownSubtyping, + @UnderInitialization(Subtyping.class) Object underSubtyping) { + // ::error: (assignment.type.incompatible) + unknownSubtyping = unknownObject; + unknownSubtyping = underSubtyping; + // ::error: (assignment.type.incompatible) + unknownSubtyping = underObject; + } } diff --git a/checker/tests/initialization/Suppression.java b/checker/tests/initialization/Suppression.java index ebc53041e72..4c96654b453 100644 --- a/checker/tests/initialization/Suppression.java +++ b/checker/tests/initialization/Suppression.java @@ -6,13 +6,13 @@ public class Suppression { - Suppression t; + Suppression t; - @SuppressWarnings("initialization.fields.uninitialized") - public Suppression(Suppression arg) {} + @SuppressWarnings("initialization.fields.uninitialized") + public Suppression(Suppression arg) {} - @SuppressWarnings({"initialization"}) - void foo(@UnknownInitialization Suppression arg) { - t = arg; // initialization error - } + @SuppressWarnings({"initialization"}) + void foo(@UnknownInitialization Suppression arg) { + t = arg; // initialization error + } } diff --git a/checker/tests/initialization/TestPolyInitialized.java b/checker/tests/initialization/TestPolyInitialized.java index 45caa073e42..0e34a2ec713 100644 --- a/checker/tests/initialization/TestPolyInitialized.java +++ b/checker/tests/initialization/TestPolyInitialized.java @@ -5,44 +5,44 @@ public class TestPolyInitialized { - @NotOnlyInitialized String testStr; - - String test = "test"; - - TestPolyInitialized(@UnknownInitialization String str) { - this.testStr = identity(str); - // :: error: (assignment.type.incompatible) - this.test = identity(str); - } - - @PolyInitialized String identity(@UnknownInitialization TestPolyInitialized this, @PolyInitialized String str) { - return str; - } - - void test1() { - @UnknownInitialization String receiver = identity(testStr); - } - - void test2() { - @Initialized String receiver = identity(test); - } - - @Initialized String test3(@UnknownInitialization String str) { - @UnknownInitialization String localStr = str; - // :: error: (return.type.incompatible) - return identity(str); - } - - @UnknownInitialization String test4(@Initialized String str) { - return identity(str); - } - - @UnknownInitialization(Object.class) String test5(@Initialized String str) { - return identity(str); - } - - @Initialized String test6(@UnknownInitialization(Object.class) String str) { - // :: error: (return.type.incompatible) - return identity(str); - } + @NotOnlyInitialized String testStr; + + String test = "test"; + + TestPolyInitialized(@UnknownInitialization String str) { + this.testStr = identity(str); + // :: error: (assignment.type.incompatible) + this.test = identity(str); + } + + @PolyInitialized String identity(@UnknownInitialization TestPolyInitialized this, @PolyInitialized String str) { + return str; + } + + void test1() { + @UnknownInitialization String receiver = identity(testStr); + } + + void test2() { + @Initialized String receiver = identity(test); + } + + @Initialized String test3(@UnknownInitialization String str) { + @UnknownInitialization String localStr = str; + // :: error: (return.type.incompatible) + return identity(str); + } + + @UnknownInitialization String test4(@Initialized String str) { + return identity(str); + } + + @UnknownInitialization(Object.class) String test5(@Initialized String str) { + return identity(str); + } + + @Initialized String test6(@UnknownInitialization(Object.class) String str) { + // :: error: (return.type.incompatible) + return identity(str); + } } diff --git a/checker/tests/initialization/TryFinally.java b/checker/tests/initialization/TryFinally.java index ff5d973fc6f..91c8e636ddc 100644 --- a/checker/tests/initialization/TryFinally.java +++ b/checker/tests/initialization/TryFinally.java @@ -21,31 +21,31 @@ public class TryFinally {} class TestCabsentFabsent { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private final String foo; + private final String foo; - public TestCabsentFabsent() { - this.foo = getFoo(); - } + public TestCabsentFabsent() { + this.foo = getFoo(); + } } class TestCabsentFnoaction { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private final String foo; + private final String foo; - public TestCabsentFnoaction() { - try { - this.foo = getFoo(); - } finally { - // no action in finally clause + public TestCabsentFnoaction() { + try { + this.foo = getFoo(); + } finally { + // no action in finally clause + } } - } } // Not legal in Java: error: variable foo might not have been initialized @@ -217,237 +217,237 @@ public TestCabsentFnoaction() { // } class TestCabsentFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCabsentFabsentNonfinal() { - this.foo = getFoo(); - } + public TestCabsentFabsentNonfinal() { + this.foo = getFoo(); + } } class TestCabsentFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCabsentFnoactionNonfinal() { - try { - this.foo = getFoo(); - } finally { - // no action in finally clause + public TestCabsentFnoactionNonfinal() { + try { + this.foo = getFoo(); + } finally { + // no action in finally clause + } } - } } class TestCtnoactionFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - // :: error: (initialization.fields.uninitialized) - public TestCtnoactionFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - // no action on exception + // :: error: (initialization.fields.uninitialized) + public TestCtnoactionFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + // no action on exception + } } - } } class TestCtnoactionFnoactionNonfinal { - static String getFoo() { - return "foo"; - } - - private String foo; - - // :: error: (initialization.fields.uninitialized) - public TestCtnoactionFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - // no action on exception - } finally { - // no action in finally clause - } - } + static String getFoo() { + return "foo"; + } + + private String foo; + + // :: error: (initialization.fields.uninitialized) + public TestCtnoactionFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + // no action on exception + } finally { + // no action in finally clause + } + } } class TestCtmethodFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtmethodFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = getFoo(); + public TestCtmethodFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = getFoo(); + } } - } } class TestCtmethodFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtmethodFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = getFoo(); - } finally { - // no action in finally clause + public TestCtmethodFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = getFoo(); + } finally { + // no action in finally clause + } } - } } class TestCtstringFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtstringFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = "foo"; + public TestCtstringFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = "foo"; + } } - } } class TestCtstringFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCtstringFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Throwable t) { - this.foo = "foo"; - } finally { - // no action in finally clause + public TestCtstringFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Throwable t) { + this.foo = "foo"; + } finally { + // no action in finally clause + } } - } } class TestCenoactionFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - // :: error: (initialization.fields.uninitialized) - public TestCenoactionFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - // no action on exception + // :: error: (initialization.fields.uninitialized) + public TestCenoactionFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + // no action on exception + } } - } } class TestCenoactionFnoactionNonfinal { - static String getFoo() { - return "foo"; - } - - private String foo; - - // :: error: (initialization.fields.uninitialized) - public TestCenoactionFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - // no action on exception - } finally { - // no action in finally clause - } - } + static String getFoo() { + return "foo"; + } + + private String foo; + + // :: error: (initialization.fields.uninitialized) + public TestCenoactionFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + // no action on exception + } finally { + // no action in finally clause + } + } } class TestCemethodFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCemethodFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = getFoo(); + public TestCemethodFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = getFoo(); + } } - } } class TestCemethodFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCemethodFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = getFoo(); - } finally { - // no action in finally clause + public TestCemethodFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = getFoo(); + } finally { + // no action in finally clause + } } - } } class TestCestringFabsentNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCestringFabsentNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = "foo"; + public TestCestringFabsentNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = "foo"; + } } - } } class TestCestringFnoactionNonfinal { - static String getFoo() { - return "foo"; - } + static String getFoo() { + return "foo"; + } - private String foo; + private String foo; - public TestCestringFnoactionNonfinal() { - try { - this.foo = getFoo(); - } catch (Exception t) { - this.foo = "foo"; - } finally { - // no action in finally clause + public TestCestringFnoactionNonfinal() { + try { + this.foo = getFoo(); + } catch (Exception t) { + this.foo = "foo"; + } finally { + // no action in finally clause + } } - } } diff --git a/checker/tests/initialization/TryFinally2.java b/checker/tests/initialization/TryFinally2.java index dcc1a832335..ce57f1d75b1 100644 --- a/checker/tests/initialization/TryFinally2.java +++ b/checker/tests/initialization/TryFinally2.java @@ -1,30 +1,31 @@ // Test case for Issue 1500: // https://github.com/typetools/checker-framework/issues/1500 -import java.io.InputStream; import org.checkerframework.checker.nullness.qual.Nullable; +import java.io.InputStream; + public class TryFinally2 { - @SuppressWarnings("nullness") // dummy implementation - Process getProcess() { - return null; - } + @SuppressWarnings("nullness") // dummy implementation + Process getProcess() { + return null; + } - void performCommand() { - Process proc = null; - InputStream in = null; - try { - proc = getProcess(); - in = proc.getInputStream(); - return; - } finally { - closeQuietly(in); - if (proc != null) { - proc.destroy(); - } + void performCommand() { + Process proc = null; + InputStream in = null; + try { + proc = getProcess(); + in = proc.getInputStream(); + return; + } finally { + closeQuietly(in); + if (proc != null) { + proc.destroy(); + } + } } - } - public static void closeQuietly(final @Nullable InputStream input) {} + public static void closeQuietly(final @Nullable InputStream input) {} } diff --git a/checker/tests/initialization/TryFinallyBreak.java b/checker/tests/initialization/TryFinallyBreak.java index 9d9eec468be..c78d8fd7b7a 100644 --- a/checker/tests/initialization/TryFinallyBreak.java +++ b/checker/tests/initialization/TryFinallyBreak.java @@ -2,571 +2,571 @@ // https://github.com/typetools/checker-framework/issues/548 public class TryFinallyBreak { - String testWhile1() { - String ans = "x"; - while (this.hashCode() > 10000) { - try { - // empty body - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile2() { - String ans = "x"; - while (true) { - try { - // Note the additional break; - break; - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile3() { - String ans = "x"; - while (true) { - try { - testWhile3(); - } catch (Exception e) { - break; - } finally { - ans = null; - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile4() { - String ans = "x"; - while (true) { - if (true) { - try { - break; - } finally { - ans = null; - break; + String testWhile1() { + String ans = "x"; + while (this.hashCode() > 10000) { + try { + // empty body + } finally { + ans = null; + } } - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile5() { - String ans = "x"; - while (true) { - while (true) { - try { - // Note the additional break; - break; - } finally { - ans = null; + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile2() { + String ans = "x"; + while (true) { + try { + // Note the additional break; + break; + } finally { + ans = null; + } } - } - ans = "x"; - break; - } - return ans; - } - - String testWhile6(boolean cond) { - String ans = "x"; - OUTER: - while (cond) { - while (cond) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile3() { + String ans = "x"; + while (true) { + try { + testWhile3(); + } catch (Exception e) { + break; + } finally { + ans = null; + } + ans = "x"; } - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testWhile7(boolean cond) { - String ans = "x"; - OUTER: - while (cond) { - try { - while (cond) { - try { - if (cond) { - break OUTER; + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile4() { + String ans = "x"; + while (true) { + if (true) { + try { + break; + } finally { + ans = null; + break; + } } - } finally { - ans = null; - } + ans = "x"; } - } finally { - ans = "x"; - } - } - return ans; - } - - String testWhile8(boolean cond) { - String ans = "x"; - OUTER: - while (cond) { - try { - while (cond) { - try { - if (cond) { - break OUTER; + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile5() { + String ans = "x"; + while (true) { + while (true) { + try { + // Note the additional break; + break; + } finally { + ans = null; + } } - } finally { ans = "x"; - } + break; } - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile1() { - String ans = "x"; - do { - try { - // empty body - } finally { - ans = null; - } - } while (this.hashCode() > 10000); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile2() { - String ans = "x"; - do { - try { - // Note the additional break; - break; - } finally { - ans = null; - } - } while (true); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile3() { - String ans = "x"; - do { - try { - testWhile3(); - } catch (Exception e) { - break; - } finally { - ans = null; - } - ans = "x"; - } while (true); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile4() { - String ans = "x"; - do { - if (true) { - try { - break; - } finally { - ans = null; - break; + return ans; + } + + String testWhile6(boolean cond) { + String ans = "x"; + OUTER: + while (cond) { + while (cond) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; + } + } + ans = "x"; } - } - ans = "x"; - } while (true); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile5() { - String ans = "x"; - do { - do { - try { - // Note the additional break; - break; - } finally { - ans = null; + // :: error: (return.type.incompatible) + return ans; + } + + String testWhile7(boolean cond) { + String ans = "x"; + OUTER: + while (cond) { + try { + while (cond) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; + } + } + } finally { + ans = "x"; + } } - } while (true); - ans = "x"; - break; - } while (true); - return ans; - } - - String testDoWhile6(boolean cond) { - String ans = "x"; - OUTER: - do { - do { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; + return ans; + } + + String testWhile8(boolean cond) { + String ans = "x"; + OUTER: + while (cond) { + try { + while (cond) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = "x"; + } + } + } finally { + ans = null; + } } - } while (cond); - ans = "x"; - } while (cond); - // :: error: (return.type.incompatible) - return ans; - } - - String testDoWhile7(boolean cond) { - String ans = "x"; - OUTER: - do { - try { + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile1() { + String ans = "x"; do { - try { - if (cond) { - break OUTER; + try { + // empty body + } finally { + ans = null; } - } finally { - ans = null; - } - } while (cond); - } finally { - ans = "x"; - } - } while (cond); - return ans; - } - - String testDoWhile8(boolean cond) { - String ans = "x"; - OUTER: - do { - try { + } while (this.hashCode() > 10000); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile2() { + String ans = "x"; do { - try { - if (cond) { - break OUTER; + try { + // Note the additional break; + break; + } finally { + ans = null; + } + } while (true); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile3() { + String ans = "x"; + do { + try { + testWhile3(); + } catch (Exception e) { + break; + } finally { + ans = null; + } + ans = "x"; + } while (true); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile4() { + String ans = "x"; + do { + if (true) { + try { + break; + } finally { + ans = null; + break; + } } - } finally { ans = "x"; - } + } while (true); + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile5() { + String ans = "x"; + do { + do { + try { + // Note the additional break; + break; + } finally { + ans = null; + } + } while (true); + ans = "x"; + break; + } while (true); + return ans; + } + + String testDoWhile6(boolean cond) { + String ans = "x"; + OUTER: + do { + do { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; + } + } while (cond); + ans = "x"; } while (cond); - } finally { - ans = null; - } - } while (cond); - // :: error: (return.type.incompatible) - return ans; - } - - String testFor1() { - String ans = "x"; - for (; this.hashCode() > 10000; ) { - try { - // empty body - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor2() { - String ans = "x"; - for (; ; ) { - try { - // Note the additional break; - break; - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor3() { - String ans = "x"; - for (; ; ) { - try { - testFor3(); - } catch (Exception e) { - break; - } finally { - ans = null; - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor4() { - String ans = "x"; - for (; ; ) { - if (true) { - try { - break; - } finally { - ans = null; - break; + // :: error: (return.type.incompatible) + return ans; + } + + String testDoWhile7(boolean cond) { + String ans = "x"; + OUTER: + do { + try { + do { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; + } + } while (cond); + } finally { + ans = "x"; + } + } while (cond); + return ans; + } + + String testDoWhile8(boolean cond) { + String ans = "x"; + OUTER: + do { + try { + do { + try { + if (cond) { + break OUTER; + } + } finally { + ans = "x"; + } + } while (cond); + } finally { + ans = null; + } + } while (cond); + // :: error: (return.type.incompatible) + return ans; + } + + String testFor1() { + String ans = "x"; + for (; this.hashCode() > 10000; ) { + try { + // empty body + } finally { + ans = null; + } } - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor5() { - String ans = "x"; - for (; ; ) { - for (; ; ) { - try { - // Note the additional break; - break; - } finally { - ans = null; + // :: error: (return.type.incompatible) + return ans; + } + + String testFor2() { + String ans = "x"; + for (; ; ) { + try { + // Note the additional break; + break; + } finally { + ans = null; + } } - } - ans = "x"; - break; - } - return ans; - } - - String testFor6(boolean cond) { - String ans = "x"; - OUTER: - for (; ; ) { - for (; cond; ) { - try { - if (cond) { - break OUTER; - } - } finally { - ans = null; + // :: error: (return.type.incompatible) + return ans; + } + + String testFor3() { + String ans = "x"; + for (; ; ) { + try { + testFor3(); + } catch (Exception e) { + break; + } finally { + ans = null; + } + ans = "x"; } - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testFor7(boolean cond) { - String ans = "x"; - OUTER: - for (; ; ) { - try { + // :: error: (return.type.incompatible) + return ans; + } + + String testFor4() { + String ans = "x"; for (; ; ) { - try { - if (cond) { - break OUTER; + if (true) { + try { + break; + } finally { + ans = null; + break; + } } - } finally { - ans = null; - } + ans = "x"; } - } finally { - ans = "x"; - } - } - return ans; - } - - String testFor8(boolean cond) { - String ans = "x"; - OUTER: - for (; ; ) { - try { + // :: error: (return.type.incompatible) + return ans; + } + + String testFor5() { + String ans = "x"; for (; ; ) { - try { - if (cond) { - break OUTER; + for (; ; ) { + try { + // Note the additional break; + break; + } finally { + ans = null; + } } - } finally { ans = "x"; - } + break; } - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testIf1() { - String ans = "x"; - IF: - if (true) { - try { - break IF; - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testIf2(boolean cond) { - String ans = "x"; - IF: - if (cond) { - if (cond) { - try { - if (cond) { - break IF; - } - } finally { - ans = null; + return ans; + } + + String testFor6(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + for (; cond; ) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; + } + } + ans = "x"; } - } - ans = "x"; - } - // :: error: (return.type.incompatible) - return ans; - } - - String testIf3(boolean cond) { - String ans = "x"; - IF: - if (cond) { - try { - if (cond) { - try { - if (cond) { - break IF; + // :: error: (return.type.incompatible) + return ans; + } + + String testFor7(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + try { + for (; ; ) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = null; + } + } + } finally { + ans = "x"; + } + } + return ans; + } + + String testFor8(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + try { + for (; ; ) { + try { + if (cond) { + break OUTER; + } + } finally { + ans = "x"; + } + } + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testIf1() { + String ans = "x"; + IF: + if (true) { + try { + break IF; + } finally { + ans = null; } - } finally { - ans = null; - } } - } finally { - ans = "x"; - } - } - return ans; - } - - String testIf4(boolean cond) { - String ans = "x"; - IF: - if (cond) { - try { + // :: error: (return.type.incompatible) + return ans; + } + + String testIf2(boolean cond) { + String ans = "x"; + IF: if (cond) { - try { if (cond) { - break IF; + try { + if (cond) { + break IF; + } + } finally { + ans = null; + } } - } finally { ans = "x"; - } } - } finally { - ans = null; - } - } - // :: error: (return.type.incompatible) - return ans; - } - - String testSwitch1() { - String ans = "x"; - switch (ans) { - case "x": - try { - break; - } finally { - ans = null; + // :: error: (return.type.incompatible) + return ans; + } + + String testIf3(boolean cond) { + String ans = "x"; + IF: + if (cond) { + try { + if (cond) { + try { + if (cond) { + break IF; + } + } finally { + ans = null; + } + } + } finally { + ans = "x"; + } } + return ans; } - // :: error: (return.type.incompatible) - return ans; - } - String testSwitch2(boolean cond) { - String ans = "x"; - SWITCH: - switch (ans) { - case "x": - switch (ans) { - case "x": + String testIf4(boolean cond) { + String ans = "x"; + IF: + if (cond) { try { - break SWITCH; + if (cond) { + try { + if (cond) { + break IF; + } + } finally { + ans = "x"; + } + } } finally { - ans = null; + ans = null; } } + // :: error: (return.type.incompatible) + return ans; } - // :: error: (return.type.incompatible) - return ans; - } - - String testSwitch3(boolean cond) { - String ans = "x"; - SWITCH: - switch (ans) { - case "x": - try { - switch (ans) { + + String testSwitch1() { + String ans = "x"; + switch (ans) { case "x": - try { - break SWITCH; - } finally { - ans = null; - } - } - } finally { - ans = "x"; + try { + break; + } finally { + ans = null; + } + } + // :: error: (return.type.incompatible) + return ans; + } + + String testSwitch2(boolean cond) { + String ans = "x"; + SWITCH: + switch (ans) { + case "x": + switch (ans) { + case "x": + try { + break SWITCH; + } finally { + ans = null; + } + } } + // :: error: (return.type.incompatible) + return ans; } - return ans; - } - String testSwitch4(boolean cond) { - String ans = "x"; - SWITCH: - switch (ans) { - case "x": - try { - switch (ans) { + String testSwitch3(boolean cond) { + String ans = "x"; + SWITCH: + switch (ans) { case "x": - try { - break SWITCH; - } finally { - ans = "x"; - } - } - } finally { - ans = null; + try { + switch (ans) { + case "x": + try { + break SWITCH; + } finally { + ans = null; + } + } + } finally { + ans = "x"; + } + } + return ans; + } + + String testSwitch4(boolean cond) { + String ans = "x"; + SWITCH: + switch (ans) { + case "x": + try { + switch (ans) { + case "x": + try { + break SWITCH; + } finally { + ans = "x"; + } + } + } finally { + ans = null; + } } + // :: error: (return.type.incompatible) + return ans; } - // :: error: (return.type.incompatible) - return ans; - } } diff --git a/checker/tests/initialization/TryFinallyContinue.java b/checker/tests/initialization/TryFinallyContinue.java index 62285aec6f1..6b22d5bcb04 100644 --- a/checker/tests/initialization/TryFinallyContinue.java +++ b/checker/tests/initialization/TryFinallyContinue.java @@ -2,123 +2,123 @@ // https://github.com/typetools/checker-framework/issues/548 public class TryFinallyContinue { - String testWhile1() { - String ans = "x"; - while (true) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } - if (true) { - try { - continue; - } finally { - ans = null; + String testWhile1() { + String ans = "x"; + while (true) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } + if (true) { + try { + continue; + } finally { + ans = null; + } + } + ans = "x"; } - } - ans = "x"; } - } - String testWhile2(boolean cond) { - String ans = "x"; - while (cond) { - if (true) { + String testWhile2(boolean cond) { + String ans = "x"; + while (cond) { + if (true) { + return ans; + } + try { + ans = null; + continue; + } finally { + ans = "x"; + } + } return ans; - } - try { - ans = null; - continue; - } finally { - ans = "x"; - } } - return ans; - } - String testWhile3(boolean cond) { - String ans = "x"; - OUTER: - while (true) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } + String testWhile3(boolean cond) { + String ans = "x"; + OUTER: + while (true) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } - try { - while (cond) { - if (true) { try { - continue OUTER; + while (cond) { + if (true) { + try { + continue OUTER; + } finally { + ans = "x"; + } + } + } } finally { - ans = "x"; + ans = null; } - } + ans = "x"; } - } finally { - ans = null; - } - ans = "x"; } - } - String testFor1() { - String ans = "x"; - for (; ; ) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } - if (true) { - try { - continue; - } finally { - ans = null; + String testFor1() { + String ans = "x"; + for (; ; ) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } + if (true) { + try { + continue; + } finally { + ans = null; + } + } + ans = "x"; } - } - ans = "x"; } - } - String testFor2(boolean cond) { - String ans = "x"; - for (; cond; ) { - if (true) { + String testFor2(boolean cond) { + String ans = "x"; + for (; cond; ) { + if (true) { + return ans; + } + try { + ans = null; + continue; + } finally { + ans = "x"; + } + } return ans; - } - try { - ans = null; - continue; - } finally { - ans = "x"; - } } - return ans; - } - String testFor3(boolean cond) { - String ans = "x"; - OUTER: - for (; ; ) { - if (true) { - // :: error: (return.type.incompatible) - return ans; - } + String testFor3(boolean cond) { + String ans = "x"; + OUTER: + for (; ; ) { + if (true) { + // :: error: (return.type.incompatible) + return ans; + } - try { - for (; cond; ) { - if (true) { try { - continue OUTER; + for (; cond; ) { + if (true) { + try { + continue OUTER; + } finally { + ans = "x"; + } + } + } } finally { - ans = "x"; + ans = null; } - } + ans = "x"; } - } finally { - ans = null; - } - ans = "x"; } - } } diff --git a/checker/tests/initialization/TypeFrames.java b/checker/tests/initialization/TypeFrames.java index 73f27d57b52..9d819cf871f 100644 --- a/checker/tests/initialization/TypeFrames.java +++ b/checker/tests/initialization/TypeFrames.java @@ -3,38 +3,38 @@ public class TypeFrames { - class A { - @NonNull String a; + class A { + @NonNull String a; - public A() { - @UnderInitialization A l1 = this; - // :: error: (assignment.type.incompatible) - @UnderInitialization(A.class) A l2 = this; - a = ""; - @UnderInitialization(A.class) A l3 = this; + public A() { + @UnderInitialization A l1 = this; + // :: error: (assignment.type.incompatible) + @UnderInitialization(A.class) A l2 = this; + a = ""; + @UnderInitialization(A.class) A l3 = this; + } } - } - interface I {} + interface I {} - class B extends A implements I { - @NonNull String b; + class B extends A implements I { + @NonNull String b; - public B() { - super(); - @UnderInitialization(A.class) A l1 = this; - // :: error: (assignment.type.incompatible) - @UnderInitialization(B.class) A l2 = this; - b = ""; - @UnderInitialization(B.class) A l3 = this; + public B() { + super(); + @UnderInitialization(A.class) A l1 = this; + // :: error: (assignment.type.incompatible) + @UnderInitialization(B.class) A l2 = this; + b = ""; + @UnderInitialization(B.class) A l3 = this; + } } - } - // subtyping - void t1(@UnderInitialization(A.class) B b1, @UnderInitialization(B.class) B b2) { - @UnderInitialization(A.class) B l1 = b1; - @UnderInitialization(A.class) B l2 = b2; - // :: error: (assignment.type.incompatible) - @UnderInitialization(B.class) B l3 = b1; - } + // subtyping + void t1(@UnderInitialization(A.class) B b1, @UnderInitialization(B.class) B b2) { + @UnderInitialization(A.class) B l1 = b1; + @UnderInitialization(A.class) B l2 = b2; + // :: error: (assignment.type.incompatible) + @UnderInitialization(B.class) B l3 = b1; + } } diff --git a/checker/tests/initialization/TypeFrames2.java b/checker/tests/initialization/TypeFrames2.java index 369f6282a10..0b328a56614 100644 --- a/checker/tests/initialization/TypeFrames2.java +++ b/checker/tests/initialization/TypeFrames2.java @@ -3,31 +3,31 @@ public class TypeFrames2 { - class A { - @NonNull String a; + class A { + @NonNull String a; - public A() { - // :: error: (method.invocation.invalid) - this.foo(); - a = ""; - this.foo(); + public A() { + // :: error: (method.invocation.invalid) + this.foo(); + a = ""; + this.foo(); + } + + public void foo(@UnderInitialization(A.class) A this) {} } - public void foo(@UnderInitialization(A.class) A this) {} - } + class B extends A { + @NonNull String b; - class B extends A { - @NonNull String b; + public B() { + super(); + this.foo(); + // :: error: (method.invocation.invalid) + this.bar(); + b = ""; + this.bar(); + } - public B() { - super(); - this.foo(); - // :: error: (method.invocation.invalid) - this.bar(); - b = ""; - this.bar(); + public void bar(@UnderInitialization(B.class) B this) {} } - - public void bar(@UnderInitialization(B.class) B this) {} - } } diff --git a/checker/tests/initialization/TypeFrames3.java b/checker/tests/initialization/TypeFrames3.java index bafd73322d3..1a411dd4af4 100644 --- a/checker/tests/initialization/TypeFrames3.java +++ b/checker/tests/initialization/TypeFrames3.java @@ -2,23 +2,23 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class TypeFrames3 { - public Object f; + public Object f; - public TypeFrames3(boolean dummy) { - initF(); - foo(); - } + public TypeFrames3(boolean dummy) { + initF(); + foo(); + } - public TypeFrames3(int dummy) { - // :: error: (method.invocation.invalid) - foo(); - f = new Object(); - } + public TypeFrames3(int dummy) { + // :: error: (method.invocation.invalid) + foo(); + f = new Object(); + } - @EnsuresNonNull("this.f") - public void initF(@UnknownInitialization TypeFrames3 this) { - f = new Object(); - } + @EnsuresNonNull("this.f") + public void initF(@UnknownInitialization TypeFrames3 this) { + f = new Object(); + } - public void foo(@UnknownInitialization(TypeFrames3.class) TypeFrames3 this) {} + public void foo(@UnknownInitialization(TypeFrames3.class) TypeFrames3 this) {} } diff --git a/checker/tests/initialization/TypeFrames4.java b/checker/tests/initialization/TypeFrames4.java index 5f0a749be33..73d947353e1 100644 --- a/checker/tests/initialization/TypeFrames4.java +++ b/checker/tests/initialization/TypeFrames4.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class TypeFrames4 { - public Object f; + public Object f; - public TypeFrames4(boolean dummy) { - initF(); - @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; - } + public TypeFrames4(boolean dummy) { + initF(); + @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; + } - public TypeFrames4(int dummy) { - // :: error: (assignment.type.incompatible) - @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; - f = new Object(); - } + public TypeFrames4(int dummy) { + // :: error: (assignment.type.incompatible) + @UnderInitialization(TypeFrames4.class) TypeFrames4 a = this; + f = new Object(); + } - @EnsuresNonNull("this.f") - public void initF(@UnknownInitialization TypeFrames4 this) { - f = new Object(); - } + @EnsuresNonNull("this.f") + public void initF(@UnknownInitialization TypeFrames4 this) { + f = new Object(); + } } diff --git a/checker/tests/initialization/TypeFrames5.java b/checker/tests/initialization/TypeFrames5.java index 4322da3983e..054722a48b9 100644 --- a/checker/tests/initialization/TypeFrames5.java +++ b/checker/tests/initialization/TypeFrames5.java @@ -3,14 +3,14 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TypeFrames5 { - public @Nullable Object f; + public @Nullable Object f; - public TypeFrames5(boolean dummy) { - @UnderInitialization(TypeFrames5.class) TypeFrames5 a = this; - } + public TypeFrames5(boolean dummy) { + @UnderInitialization(TypeFrames5.class) TypeFrames5 a = this; + } - public TypeFrames5(int dummy) { - // :: error: (assignment.type.incompatible) - @Initialized TypeFrames5 a = this; - } + public TypeFrames5(int dummy) { + // :: error: (assignment.type.incompatible) + @Initialized TypeFrames5 a = this; + } } diff --git a/checker/tests/initialization/UnboxUninitalizedFieldTest.java b/checker/tests/initialization/UnboxUninitalizedFieldTest.java index b30c252ba77..ad8347ba826 100644 --- a/checker/tests/initialization/UnboxUninitalizedFieldTest.java +++ b/checker/tests/initialization/UnboxUninitalizedFieldTest.java @@ -3,10 +3,10 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class UnboxUninitalizedFieldTest { - @UnknownInitialization Integer n; + @UnknownInitialization Integer n; - UnboxUninitalizedFieldTest() { - // :: error: (unboxing.of.nullable) - int y = n; - } + UnboxUninitalizedFieldTest() { + // :: error: (unboxing.of.nullable) + int y = n; + } } diff --git a/checker/tests/initialization/Uninit.java b/checker/tests/initialization/Uninit.java index 3ac1787c4d7..db69ec25d8b 100644 --- a/checker/tests/initialization/Uninit.java +++ b/checker/tests/initialization/Uninit.java @@ -1,4 +1,4 @@ public class Uninit { - // :: error: (initialization.field.uninitialized) - Object a; + // :: error: (initialization.field.uninitialized) + Object a; } diff --git a/checker/tests/initialization/Uninit10.java b/checker/tests/initialization/Uninit10.java index 8179491a93d..c5849cb19bd 100644 --- a/checker/tests/initialization/Uninit10.java +++ b/checker/tests/initialization/Uninit10.java @@ -2,16 +2,16 @@ public class Uninit10 { - @NonNull String[] strings; + @NonNull String[] strings; - // :: error: (initialization.fields.uninitialized) - Uninit10() {} + // :: error: (initialization.fields.uninitialized) + Uninit10() {} - public class Inner { + public class Inner { - @NonNull String[] stringsInner; + @NonNull String[] stringsInner; - // :: error: (initialization.fields.uninitialized) - Inner() {} - } + // :: error: (initialization.fields.uninitialized) + Inner() {} + } } diff --git a/checker/tests/initialization/Uninit11.java b/checker/tests/initialization/Uninit11.java index fa0f1d8911e..d4e83fcbb8f 100644 --- a/checker/tests/initialization/Uninit11.java +++ b/checker/tests/initialization/Uninit11.java @@ -1,25 +1,26 @@ -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.Unused; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({}) @Target(ElementType.TYPE_USE) @interface DoesNotUseF {} public class Uninit11 { - @Unused(when = DoesNotUseF.class) - public Object f; + @Unused(when = DoesNotUseF.class) + public Object f; - // parameter disambiguate_overloads is just to distinguish the overloaded constructors - public @DoesNotUseF Uninit11(int disambiguate_overloads) {} + // parameter disambiguate_overloads is just to distinguish the overloaded constructors + public @DoesNotUseF Uninit11(int disambiguate_overloads) {} - // :: error: (initialization.fields.uninitialized) - public Uninit11(boolean disambiguate_overloads) {} + // :: error: (initialization.fields.uninitialized) + public Uninit11(boolean disambiguate_overloads) {} - public Uninit11(long x) { - f = new Object(); - } + public Uninit11(long x) { + f = new Object(); + } } diff --git a/checker/tests/initialization/Uninit12.java b/checker/tests/initialization/Uninit12.java index 5d30e1f2853..ff778e78713 100644 --- a/checker/tests/initialization/Uninit12.java +++ b/checker/tests/initialization/Uninit12.java @@ -5,29 +5,29 @@ public class Uninit12 { - // :: error: (initialization.static.field.uninitialized) - static Object f; + // :: error: (initialization.static.field.uninitialized) + static Object f; - public Uninit12() { - f.toString(); - } + public Uninit12() { + f.toString(); + } - static Object g = new Object(); + static Object g = new Object(); - static Object h; + static Object h; - static { - h = new Object(); - } + static { + h = new Object(); + } } class Uninit12_OK { - static Object g = new Object(); + static Object g = new Object(); - static Object h; + static Object h; - static { - h = new Object(); - } + static { + h = new Object(); + } } diff --git a/checker/tests/initialization/Uninit13.java b/checker/tests/initialization/Uninit13.java index 27678e80f1f..21bcfce3493 100644 --- a/checker/tests/initialization/Uninit13.java +++ b/checker/tests/initialization/Uninit13.java @@ -1,9 +1,9 @@ public class Uninit13 { - { - x = 1; - o = new Object(); - } + { + x = 1; + o = new Object(); + } - int x; - Object o; + int x; + Object o; } diff --git a/checker/tests/initialization/Uninit14.java b/checker/tests/initialization/Uninit14.java index b6fc2131200..64e5abc8365 100644 --- a/checker/tests/initialization/Uninit14.java +++ b/checker/tests/initialization/Uninit14.java @@ -1,13 +1,13 @@ // Test case for Issue 144 (now fixed): // https://github.com/typetools/checker-framework/issues/144 public class Uninit14 { - private final Object o; + private final Object o; - { - try { - o = new Object(); - } catch (Exception e) { - throw new RuntimeException(e); + { + try { + o = new Object(); + } catch (Exception e) { + throw new RuntimeException(e); + } } - } } diff --git a/checker/tests/initialization/Uninit2.java b/checker/tests/initialization/Uninit2.java index 4f68bd43938..f84a9137193 100644 --- a/checker/tests/initialization/Uninit2.java +++ b/checker/tests/initialization/Uninit2.java @@ -1,7 +1,7 @@ public class Uninit2 { - Object a; + Object a; - Uninit2() { - a = new Object(); - } + Uninit2() { + a = new Object(); + } } diff --git a/checker/tests/initialization/Uninit3.java b/checker/tests/initialization/Uninit3.java index 3b23d994973..2176f9751b8 100644 --- a/checker/tests/initialization/Uninit3.java +++ b/checker/tests/initialization/Uninit3.java @@ -1,3 +1,3 @@ public class Uninit3 { - Object a = new Object(); + Object a = new Object(); } diff --git a/checker/tests/initialization/Uninit4.java b/checker/tests/initialization/Uninit4.java index 45fcb9bb70c..ac747824df0 100644 --- a/checker/tests/initialization/Uninit4.java +++ b/checker/tests/initialization/Uninit4.java @@ -1,36 +1,36 @@ public class Uninit4 { - class Mam { - Object a = new Object(); - } + class Mam { + Object a = new Object(); + } - class BadSon { - // :: error: (initialization.field.uninitialized) - Object b; - } + class BadSon { + // :: error: (initialization.field.uninitialized) + Object b; + } - class GoodSon { - Object b = new Object(); - } + class GoodSon { + Object b = new Object(); + } - class WeirdSon { - Object b; + class WeirdSon { + Object b; - // :: error: (initialization.fields.uninitialized) - WeirdSon() { - super(); + // :: error: (initialization.fields.uninitialized) + WeirdSon() { + super(); + } } - } - class Daughter { - Object b; + class Daughter { + Object b; - // :: error: (initialization.fields.uninitialized) - Daughter() {} + // :: error: (initialization.fields.uninitialized) + Daughter() {} - Daughter(Object val) { - this(); - b = val; + Daughter(Object val) { + this(); + b = val; + } } - } } diff --git a/checker/tests/initialization/Uninit5.java b/checker/tests/initialization/Uninit5.java index 9a2c0781abc..a151d5189ff 100644 --- a/checker/tests/initialization/Uninit5.java +++ b/checker/tests/initialization/Uninit5.java @@ -1,4 +1,4 @@ public class Uninit5 { - // :: error: (initialization.field.uninitialized) - String x; + // :: error: (initialization.field.uninitialized) + String x; } diff --git a/checker/tests/initialization/Uninit6.java b/checker/tests/initialization/Uninit6.java index 7ca509407f5..856c5b7961a 100644 --- a/checker/tests/initialization/Uninit6.java +++ b/checker/tests/initialization/Uninit6.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.nullness.qual.*; public class Uninit6 { - // Failure to initialize these fields does not directly compromise the - // guarantee of no null pointer errors. - @MonotonicNonNull Object f; - @Nullable Object g; + // Failure to initialize these fields does not directly compromise the + // guarantee of no null pointer errors. + @MonotonicNonNull Object f; + @Nullable Object g; - Uninit6() {} + Uninit6() {} } diff --git a/checker/tests/initialization/Uninit7.java b/checker/tests/initialization/Uninit7.java index da19da82976..d50ff2ce7c8 100644 --- a/checker/tests/initialization/Uninit7.java +++ b/checker/tests/initialization/Uninit7.java @@ -1,7 +1,7 @@ public class Uninit7 { - Object f; + Object f; - Uninit7() { - throw new Error(); - } + Uninit7() { + throw new Error(); + } } diff --git a/checker/tests/initialization/Uninit8.java b/checker/tests/initialization/Uninit8.java index f19f310fa84..606ec377116 100644 --- a/checker/tests/initialization/Uninit8.java +++ b/checker/tests/initialization/Uninit8.java @@ -3,15 +3,15 @@ public class Uninit8 { - Object f; + Object f; - Uninit8() { - setFields(); - f.toString(); - } + Uninit8() { + setFields(); + f.toString(); + } - @EnsuresNonNull("f") - void setFields(@UnknownInitialization Uninit8 this) { - f = new Object(); - } + @EnsuresNonNull("f") + void setFields(@UnknownInitialization Uninit8 this) { + f = new Object(); + } } diff --git a/checker/tests/initialization/Uninit9.java b/checker/tests/initialization/Uninit9.java index f94971c849d..76a4e13ed56 100644 --- a/checker/tests/initialization/Uninit9.java +++ b/checker/tests/initialization/Uninit9.java @@ -3,19 +3,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Uninit9 { - public Object f; + public Object f; - Uninit9() { - f = new Object(); - } + Uninit9() { + f = new Object(); + } } class Uninit9Sub extends Uninit9 { - Uninit9Sub() { - super(); - fIsSetOnEntry(); - } + Uninit9Sub() { + super(); + fIsSetOnEntry(); + } - @RequiresNonNull("f") - void fIsSetOnEntry(@UnknownInitialization Uninit9Sub this) {} + @RequiresNonNull("f") + void fIsSetOnEntry(@UnknownInitialization Uninit9Sub this) {} } diff --git a/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java b/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java index 3e3be2af221..d2d4b4c8e4e 100644 --- a/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java +++ b/checker/tests/interning-warnredundantannotations/RedundantAnnotationOnField.java @@ -1,5 +1,5 @@ import org.checkerframework.checker.interning.qual.Interned; public class RedundantAnnotationOnField { - static final @Interned String A_STRING = "a string"; + static final @Interned String A_STRING = "a string"; } diff --git a/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java b/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java index 27b9faa26bc..7e8f33aa4b9 100644 --- a/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java +++ b/checker/tests/interning-warnredundantannotations/StaticFinalStringDefault.java @@ -1,6 +1,6 @@ import org.checkerframework.checker.interning.qual.Interned; public class StaticFinalStringDefault { - // The default type of str is not @Interned, even though it is later refined to it. - static final @Interned String str = "a"; + // The default type of str is not @Interned, even though it is later refined to it. + static final @Interned String str = "a"; } diff --git a/checker/tests/interning/ArrayInitializers.java b/checker/tests/interning/ArrayInitializers.java index d2cf14d7d2b..cb24dfc0181 100644 --- a/checker/tests/interning/ArrayInitializers.java +++ b/checker/tests/interning/ArrayInitializers.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.interning.qual.Interned; public class ArrayInitializers { - public static final String STATIC_FIELD = "m"; - public static final @Interned String OTHER_FIELD = "n"; + public static final String STATIC_FIELD = "m"; + public static final @Interned String OTHER_FIELD = "n"; - public static final @Interned String[] STATIC_ARRAY = {STATIC_FIELD, OTHER_FIELD}; + public static final @Interned String[] STATIC_ARRAY = {STATIC_FIELD, OTHER_FIELD}; } diff --git a/checker/tests/interning/Arrays.java b/checker/tests/interning/Arrays.java index ee98724f3b1..83fa107f30a 100644 --- a/checker/tests/interning/Arrays.java +++ b/checker/tests/interning/Arrays.java @@ -1,76 +1,77 @@ -import java.util.ArrayList; -import java.util.List; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.checker.interning.qual.PolyInterned; +import java.util.ArrayList; +import java.util.List; + public class Arrays { - public static Integer[] arrayclone_simple(Integer[] a_old) { - int len = a_old.length; - Integer[] a_new = new Integer[len]; - for (int i = 0; i < len; i++) { - a_new[i] = Integer.valueOf(a_old[i]); // valid + public static Integer[] arrayclone_simple(Integer[] a_old) { + int len = a_old.length; + Integer[] a_new = new Integer[len]; + for (int i = 0; i < len; i++) { + a_new[i] = Integer.valueOf(a_old[i]); // valid + } + return a_new; + } + + public static void test(@Interned Integer i, @Interned String s) { + String @Interned [] iarray1 = new String @Interned [2]; + String @Interned [] iarray2 = new String @Interned [] {"foo", "bar"}; + // :: error: (assignment.type.incompatible) + s = iarray1[1]; // error + + String[] sa = new String[22]; + // :: error: (assignment.type.incompatible) + iarray1 = sa; // error + sa = iarray1; // OK + + @Interned String[] istrings1 = new @Interned String[2]; + @Interned String[] istrings2 = new @Interned String[] {"foo", "bar"}; + s = istrings1[1]; // OK + + @Interned String @Interned [][] multi1 = new @Interned String @Interned [2][3]; + @Interned String @Interned [][] multi2 = new @Interned String @Interned [2][]; + } + + public final @Interned class InternedClass {} + + private static InternedClass[] returnToArray() { + List li = new ArrayList<>(); + return li.toArray(new InternedClass[li.size()]); + } + + private static void sortIt() { + java.util.Arrays.sort(new InternedClass[22]); + } + + private @Interned String[] elts_String; + + public @Interned String min_elt() { + return elts_String[0]; + } + + private double @Interned [] @Interned [] elts_da; + + public void add_mod_elem(double @Interned [] v, int count) { + elts_da[0] = v; + } + + public static @PolyInterned Object[] subarray( + @PolyInterned Object[] a, int startindex, int length) { + @PolyInterned Object[] result = new @PolyInterned Object[length]; + System.arraycopy(a, startindex, result, 0, length); + return result; + } + + public static void trim(int len) { + @Interned Object @Interned [] vals = null; + @Interned Object[] new_vals = subarray(vals, 0, len); + } + + public static @Interned Object @Interned [] internSubsequence( + @Interned Object @Interned [] seq, int start, int end) { + @Interned Object[] subseq_uninterned = subarray(seq, start, end - start); + return null; } - return a_new; - } - - public static void test(@Interned Integer i, @Interned String s) { - String @Interned [] iarray1 = new String @Interned [2]; - String @Interned [] iarray2 = new String @Interned [] {"foo", "bar"}; - // :: error: (assignment.type.incompatible) - s = iarray1[1]; // error - - String[] sa = new String[22]; - // :: error: (assignment.type.incompatible) - iarray1 = sa; // error - sa = iarray1; // OK - - @Interned String[] istrings1 = new @Interned String[2]; - @Interned String[] istrings2 = new @Interned String[] {"foo", "bar"}; - s = istrings1[1]; // OK - - @Interned String @Interned [][] multi1 = new @Interned String @Interned [2][3]; - @Interned String @Interned [][] multi2 = new @Interned String @Interned [2][]; - } - - public final @Interned class InternedClass {} - - private static InternedClass[] returnToArray() { - List li = new ArrayList<>(); - return li.toArray(new InternedClass[li.size()]); - } - - private static void sortIt() { - java.util.Arrays.sort(new InternedClass[22]); - } - - private @Interned String[] elts_String; - - public @Interned String min_elt() { - return elts_String[0]; - } - - private double @Interned [] @Interned [] elts_da; - - public void add_mod_elem(double @Interned [] v, int count) { - elts_da[0] = v; - } - - public static @PolyInterned Object[] subarray( - @PolyInterned Object[] a, int startindex, int length) { - @PolyInterned Object[] result = new @PolyInterned Object[length]; - System.arraycopy(a, startindex, result, 0, length); - return result; - } - - public static void trim(int len) { - @Interned Object @Interned [] vals = null; - @Interned Object[] new_vals = subarray(vals, 0, len); - } - - public static @Interned Object @Interned [] internSubsequence( - @Interned Object @Interned [] seq, int start, int end) { - @Interned Object[] subseq_uninterned = subarray(seq, start, end - start); - return null; - } } diff --git a/checker/tests/interning/ArraysMDETest.java b/checker/tests/interning/ArraysMDETest.java index 15495382868..88ed380e2e2 100644 --- a/checker/tests/interning/ArraysMDETest.java +++ b/checker/tests/interning/ArraysMDETest.java @@ -4,10 +4,10 @@ public final class ArraysMDETest { - public static @PolyInterned Object[] subarray( - @PolyInterned Object[] a, int startindex, int length) { - @PolyInterned Object[] result = new @PolyInterned Object[length]; - System.arraycopy(a, startindex, result, 0, length); - return result; - } + public static @PolyInterned Object[] subarray( + @PolyInterned Object[] a, int startindex, int length) { + @PolyInterned Object[] result = new @PolyInterned Object[length]; + System.arraycopy(a, startindex, result, 0, length); + return result; + } } diff --git a/checker/tests/interning/Autoboxing.java b/checker/tests/interning/Autoboxing.java index b1c2c7451c5..cc53021822a 100644 --- a/checker/tests/interning/Autoboxing.java +++ b/checker/tests/interning/Autoboxing.java @@ -1,170 +1,170 @@ public class Autoboxing { - Byte b; - Short s; - Short sInterned; - Integer i; - Integer iInterned; - Long l; - Float f; - Double d; - Boolean z; - Character c; - Character cInterned; + Byte b; + Short s; + Short sInterned; + Integer i; + Integer iInterned; + Long l; + Float f; + Double d; + Boolean z; + Character c; + Character cInterned; - Autoboxing() { - b = -126; - s = 32000; - sInterned = 32; - i = 1234567; - iInterned = 123; - l = 1234567L; - f = 3.14f; - d = 3.14; - z = true; - c = 65000; - cInterned = 65; - } + Autoboxing() { + b = -126; + s = 32000; + sInterned = 32; + i = 1234567; + iInterned = 123; + l = 1234567L; + f = 3.14f; + d = 3.14; + z = true; + c = 65000; + cInterned = 65; + } - public static void main(String[] args) { - new Autoboxing().test(); - } + public static void main(String[] args) { + new Autoboxing().test(); + } - public void test() { - System.out.println(); - System.out.println("Byte"); - Byte b1 = -126; - Byte b2 = -126; - Byte b3 = Byte.valueOf((byte) -126); - System.out.println(b1 == b2); - // :: warning: (unnecessary.equals) - System.out.println(b1.equals(b2)); - // :: warning: (unnecessary.equals) - System.out.println(b3.equals(b2)); - System.out.println(b.equals(b2)); - System.out.println(b == -126); - // :: warning: (unnecessary.equals) - System.out.println(b1.equals(126)); + public void test() { + System.out.println(); + System.out.println("Byte"); + Byte b1 = -126; + Byte b2 = -126; + Byte b3 = Byte.valueOf((byte) -126); + System.out.println(b1 == b2); + // :: warning: (unnecessary.equals) + System.out.println(b1.equals(b2)); + // :: warning: (unnecessary.equals) + System.out.println(b3.equals(b2)); + System.out.println(b.equals(b2)); + System.out.println(b == -126); + // :: warning: (unnecessary.equals) + System.out.println(b1.equals(126)); - System.out.println(); - System.out.println("Short"); - Short s1 = 32000; - Short s2 = 32000; - Short s3 = Short.valueOf((short) 32000); - // :: error: (not.interned) - System.out.println(s1 == s2); - System.out.println(s1.equals(s2)); - System.out.println(s3.equals(s2)); - System.out.println(s.equals(s2)); - // TODO - // Short s1interned = 32; - // Short s2interned = 32; - // Short s3interned = Short.valueOf((short) 32); - // System.out.println(s1interned==s2interned); - // // :: warning: (unnecessary.equals) - // System.out.println(s1interned.equals(s2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(s3interned.equals(s2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(sInterned.equals(s2interned)); + System.out.println(); + System.out.println("Short"); + Short s1 = 32000; + Short s2 = 32000; + Short s3 = Short.valueOf((short) 32000); + // :: error: (not.interned) + System.out.println(s1 == s2); + System.out.println(s1.equals(s2)); + System.out.println(s3.equals(s2)); + System.out.println(s.equals(s2)); + // TODO + // Short s1interned = 32; + // Short s2interned = 32; + // Short s3interned = Short.valueOf((short) 32); + // System.out.println(s1interned==s2interned); + // // :: warning: (unnecessary.equals) + // System.out.println(s1interned.equals(s2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(s3interned.equals(s2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(sInterned.equals(s2interned)); - System.out.println(); - System.out.println("Integer"); - Integer i1 = 1234567; - Integer i2 = 1234567; - Integer i3 = Integer.valueOf(1234567); - // :: error: (not.interned) - System.out.println(i1 == i2); - System.out.println(i1.equals(i2)); - System.out.println(i3.equals(i2)); - System.out.println(i.equals(i2)); + System.out.println(); + System.out.println("Integer"); + Integer i1 = 1234567; + Integer i2 = 1234567; + Integer i3 = Integer.valueOf(1234567); + // :: error: (not.interned) + System.out.println(i1 == i2); + System.out.println(i1.equals(i2)); + System.out.println(i3.equals(i2)); + System.out.println(i.equals(i2)); - System.out.println(); - Integer i1interned = 123; - Integer i2interned = 123; - Integer i3interned = Integer.valueOf(123); - // TODO: - // Would be legal to use ==, but Interning Checker does not check the - // actual int value when deciding whether to warn for unnecessary.equals. - // // :: warning: (unnecessary.equals) - // System.out.println(i1interned==i2interned); - // // :: warning: (unnecessary.equals) - // System.out.println(i1interned.equals(i2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(i3interned.equals(i2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(iInterned.equals(i2interned)); - // System.out.println(i1interned==123); // ok - // // :: warning: (unnecessary.equals) - // System.out.println(i1interned.equals(123)); + System.out.println(); + Integer i1interned = 123; + Integer i2interned = 123; + Integer i3interned = Integer.valueOf(123); + // TODO: + // Would be legal to use ==, but Interning Checker does not check the + // actual int value when deciding whether to warn for unnecessary.equals. + // // :: warning: (unnecessary.equals) + // System.out.println(i1interned==i2interned); + // // :: warning: (unnecessary.equals) + // System.out.println(i1interned.equals(i2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(i3interned.equals(i2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(iInterned.equals(i2interned)); + // System.out.println(i1interned==123); // ok + // // :: warning: (unnecessary.equals) + // System.out.println(i1interned.equals(123)); - System.out.println(); - System.out.println("Long"); - Long l1 = 1234567L; - Long l2 = 1234567L; - Long l3 = Long.valueOf(1234567L); - // :: error: (not.interned) - System.out.println(l1 == l2); - System.out.println(l1.equals(l2)); - System.out.println(l3.equals(l2)); - System.out.println(l.equals(l2)); + System.out.println(); + System.out.println("Long"); + Long l1 = 1234567L; + Long l2 = 1234567L; + Long l3 = Long.valueOf(1234567L); + // :: error: (not.interned) + System.out.println(l1 == l2); + System.out.println(l1.equals(l2)); + System.out.println(l3.equals(l2)); + System.out.println(l.equals(l2)); - System.out.println(); - System.out.println("Float"); - Float f1 = 3.14f; - Float f2 = 3.14f; - Float f3 = Float.valueOf(3.14f); - // :: error: (not.interned) - System.out.println(f1 == f2); - System.out.println(f1.equals(f2)); - System.out.println(f3.equals(f2)); - System.out.println(f.equals(f2)); + System.out.println(); + System.out.println("Float"); + Float f1 = 3.14f; + Float f2 = 3.14f; + Float f3 = Float.valueOf(3.14f); + // :: error: (not.interned) + System.out.println(f1 == f2); + System.out.println(f1.equals(f2)); + System.out.println(f3.equals(f2)); + System.out.println(f.equals(f2)); - System.out.println(); - System.out.println("Double"); - Double d1 = 3.14; - Double d2 = 3.14; - Double d3 = Double.valueOf(3.14); - // :: error: (not.interned) - System.out.println(d1 == d2); - System.out.println(d1.equals(d2)); - System.out.println(d3.equals(d2)); - System.out.println(d.equals(d2)); + System.out.println(); + System.out.println("Double"); + Double d1 = 3.14; + Double d2 = 3.14; + Double d3 = Double.valueOf(3.14); + // :: error: (not.interned) + System.out.println(d1 == d2); + System.out.println(d1.equals(d2)); + System.out.println(d3.equals(d2)); + System.out.println(d.equals(d2)); - System.out.println(); - System.out.println("Boolean"); - Boolean z1 = true; - Boolean z2 = true; - Boolean z3 = Boolean.valueOf(true); - System.out.println(z1 == z2); - // :: warning: (unnecessary.equals) - System.out.println(z1.equals(z2)); - // :: warning: (unnecessary.equals) - System.out.println(z3.equals(z2)); - System.out.println(z.equals(z2)); - System.out.println(z1 == true); // ok - // :: warning: (unnecessary.equals) - System.out.println(z1.equals(true)); + System.out.println(); + System.out.println("Boolean"); + Boolean z1 = true; + Boolean z2 = true; + Boolean z3 = Boolean.valueOf(true); + System.out.println(z1 == z2); + // :: warning: (unnecessary.equals) + System.out.println(z1.equals(z2)); + // :: warning: (unnecessary.equals) + System.out.println(z3.equals(z2)); + System.out.println(z.equals(z2)); + System.out.println(z1 == true); // ok + // :: warning: (unnecessary.equals) + System.out.println(z1.equals(true)); - System.out.println(); - System.out.println("Character"); - Character c1 = 65000; - Character c2 = 65000; - Character c3 = Character.valueOf((char) 65000); - // :: error: (not.interned) - System.out.println(c1 == c2); - System.out.println(c1.equals(c2)); - System.out.println(c3.equals(c2)); - System.out.println(c.equals(c2)); - // TODO - // Character c1interned = 65; - // Character c2interned = 65; - // Character c3interned = Character.valueOf((char) 65); - // System.out.println(c1interned==c2interned); - // // :: warning: (unnecessary.equals) - // System.out.println(c1interned.equals(c2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(c3interned.equals(c2interned)); - // // :: warning: (unnecessary.equals) - // System.out.println(cInterned.equals(c2interned)); - } + System.out.println(); + System.out.println("Character"); + Character c1 = 65000; + Character c2 = 65000; + Character c3 = Character.valueOf((char) 65000); + // :: error: (not.interned) + System.out.println(c1 == c2); + System.out.println(c1.equals(c2)); + System.out.println(c3.equals(c2)); + System.out.println(c.equals(c2)); + // TODO + // Character c1interned = 65; + // Character c2interned = 65; + // Character c3interned = Character.valueOf((char) 65); + // System.out.println(c1interned==c2interned); + // // :: warning: (unnecessary.equals) + // System.out.println(c1interned.equals(c2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(c3interned.equals(c2interned)); + // // :: warning: (unnecessary.equals) + // System.out.println(cInterned.equals(c2interned)); + } } diff --git a/checker/tests/interning/BoxingInterning.java b/checker/tests/interning/BoxingInterning.java index 5e33788d682..cacceaae67f 100644 --- a/checker/tests/interning/BoxingInterning.java +++ b/checker/tests/interning/BoxingInterning.java @@ -12,68 +12,68 @@ public class BoxingInterning { - void needsInterned(@Interned Object arg) {} - - void method() { - - boolean aprimitive = true; - needsInterned(aprimitive); - @Interned Boolean aboxed = aprimitive; - - byte bprimitive = 5; - needsInterned(bprimitive); - @Interned Byte bboxed = bprimitive; - - char cprimitive = 'a'; - needsInterned(cprimitive); - @Interned Character c2 = c; - - char cprimitive2 = (char) 0x2202; - // :: (argument.type.incompatible) - needsInterned(cprimitive2); - // :: (assignment.type.incompatible) - @Interned Character cboxed2 = cprimitive2; - - short dprimitive = 5; - needsInterned(dprimitive); - @Interned Short dboxed = dprimitive; - - short dprimitive2 = 500; - // :: (argument.type.incompatible) - needsInterned(dprimitive2); - // :: (assignment.type.incompatible) - @Interned Short dboxed2 = dprimitive2; - - int eprimitive = 5; - needsInterned(eprimitive); - @Interned Integer eboxed = eprimitive; - - int eprimitive2 = 500; - // :: (argument.type.incompatible) - needsInterned(eprimitive2); - // :: (assignment.type.incompatible) - @Interned Integer eboxed2 = eprimitive2; - - long fprimitive = 5; - needsInterned(fprimitive); - @Interned Long fboxed = fboxed; - - long fprimitive2 = 500; - // :: (argument.type.incompatible) - needsInterned(fprimitive2); - // :: (assignment.type.incompatible) - @Interned Long fboxed2 = fboxed2; - - float g = (float) 3.14; - // :: (argument.type.incompatible) - needsInterned(g); - // :: (assignment.type.incompatible) - @Interned Float gboxed = g; - - double h = 3.14; - // :: (argument.type.incompatible) - needsInterned(h); - // :: (assignment.type.incompatible) - @Interned Double hboxed = h; - } + void needsInterned(@Interned Object arg) {} + + void method() { + + boolean aprimitive = true; + needsInterned(aprimitive); + @Interned Boolean aboxed = aprimitive; + + byte bprimitive = 5; + needsInterned(bprimitive); + @Interned Byte bboxed = bprimitive; + + char cprimitive = 'a'; + needsInterned(cprimitive); + @Interned Character c2 = c; + + char cprimitive2 = (char) 0x2202; + // :: (argument.type.incompatible) + needsInterned(cprimitive2); + // :: (assignment.type.incompatible) + @Interned Character cboxed2 = cprimitive2; + + short dprimitive = 5; + needsInterned(dprimitive); + @Interned Short dboxed = dprimitive; + + short dprimitive2 = 500; + // :: (argument.type.incompatible) + needsInterned(dprimitive2); + // :: (assignment.type.incompatible) + @Interned Short dboxed2 = dprimitive2; + + int eprimitive = 5; + needsInterned(eprimitive); + @Interned Integer eboxed = eprimitive; + + int eprimitive2 = 500; + // :: (argument.type.incompatible) + needsInterned(eprimitive2); + // :: (assignment.type.incompatible) + @Interned Integer eboxed2 = eprimitive2; + + long fprimitive = 5; + needsInterned(fprimitive); + @Interned Long fboxed = fboxed; + + long fprimitive2 = 500; + // :: (argument.type.incompatible) + needsInterned(fprimitive2); + // :: (assignment.type.incompatible) + @Interned Long fboxed2 = fboxed2; + + float g = (float) 3.14; + // :: (argument.type.incompatible) + needsInterned(g); + // :: (assignment.type.incompatible) + @Interned Float gboxed = g; + + double h = 3.14; + // :: (argument.type.incompatible) + needsInterned(h); + // :: (assignment.type.incompatible) + @Interned Double hboxed = h; + } } diff --git a/checker/tests/interning/Casts.java b/checker/tests/interning/Casts.java index 2a239ca665d..0ae9a3c4e3d 100644 --- a/checker/tests/interning/Casts.java +++ b/checker/tests/interning/Casts.java @@ -1,5 +1,5 @@ public class Casts { - void method(Object o) { - char c = (char) o; - } + void method(Object o) { + char c = (char) o; + } } diff --git a/checker/tests/interning/ClassDefaults.java b/checker/tests/interning/ClassDefaults.java index 7ac06830b13..01bc6f33b6e 100644 --- a/checker/tests/interning/ClassDefaults.java +++ b/checker/tests/interning/ClassDefaults.java @@ -1,27 +1,28 @@ -import java.util.List; import org.checkerframework.checker.interning.qual.Interned; +import java.util.List; + /* * This test case excercises the interaction between class annotations * and method type argument inference. * A previously existing Unqualified annotation wasn't correctly removed. */ public class ClassDefaults { - @Interned class Test {} + @Interned class Test {} - public static interface Visitor {} + public static interface Visitor {} - class GuardingVisitor implements Visitor> { - void call() { - test(this); + class GuardingVisitor implements Visitor> { + void call() { + test(this); + } } - } - T test(Visitor p) { - return null; - } + T test(Visitor p) { + return null; + } - void call(GuardingVisitor p) { - test(p); - } + void call(GuardingVisitor p) { + test(p); + } } diff --git a/checker/tests/interning/Comparison.java b/checker/tests/interning/Comparison.java index cf9d84bbad3..b00a2ea874e 100644 --- a/checker/tests/interning/Comparison.java +++ b/checker/tests/interning/Comparison.java @@ -2,41 +2,41 @@ public class Comparison { - void testInterned() { - - @Interned String a = "foo"; - @Interned String b = "bar"; - - if (a == b) { - System.out.println("yes"); - } else { - System.out.println("no"); + void testInterned() { + + @Interned String a = "foo"; + @Interned String b = "bar"; + + if (a == b) { + System.out.println("yes"); + } else { + System.out.println("no"); + } + + if (a != b) { + System.out.println("no"); + } else { + System.out.println("yes"); + } } - if (a != b) { - System.out.println("no"); - } else { - System.out.println("yes"); - } - } + void testNotInterned() { - void testNotInterned() { + String c = new String("foo"); + String d = new String("bar"); - String c = new String("foo"); - String d = new String("bar"); - - // :: error: (not.interned) - if (c == d) { - System.out.println("yes"); - } else { - System.out.println("no"); - } + // :: error: (not.interned) + if (c == d) { + System.out.println("yes"); + } else { + System.out.println("no"); + } - // :: error: (not.interned) - if (c != d) { - System.out.println("no"); - } else { - System.out.println("yes"); + // :: error: (not.interned) + if (c != d) { + System.out.println("no"); + } else { + System.out.println("yes"); + } } - } } diff --git a/checker/tests/interning/CompileTimeConstants.java b/checker/tests/interning/CompileTimeConstants.java index 28597dc7178..fe6e257405e 100644 --- a/checker/tests/interning/CompileTimeConstants.java +++ b/checker/tests/interning/CompileTimeConstants.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.interning.qual.Interned; public class CompileTimeConstants { - class A { - static final String a1 = "hello"; - @Interned String a2 = "a2"; + class A { + static final String a1 = "hello"; + @Interned String a2 = "a2"; - void method() { - if (a1 == "hello") {} + void method() { + if (a1 == "hello") {} + } } - } - class B { - static final String b1 = "hello"; + class B { + static final String b1 = "hello"; - void method() { - if (b1 == A.a1) {} + void method() { + if (b1 == A.a1) {} + } } - } } diff --git a/checker/tests/interning/CompileTimeConstants2.java b/checker/tests/interning/CompileTimeConstants2.java index f04e7b216f7..f4c725db444 100644 --- a/checker/tests/interning/CompileTimeConstants2.java +++ b/checker/tests/interning/CompileTimeConstants2.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.interning.qual.Interned; public class CompileTimeConstants2 { - @Interned String s1 = "" + ("" + 1); + @Interned String s1 = "" + ("" + 1); - @Interned String s2 = (("" + ("" + 1))); + @Interned String s2 = (("" + ("" + 1))); - @Interned String s3 = ("" + (("")) + 1); + @Interned String s3 = ("" + (("")) + 1); - @Interned String s4 = "" + Math.PI; + @Interned String s4 = "" + Math.PI; - // To make sure that we would get an error if the RHS is not interned - // :: error: (assignment.type.incompatible) - @Interned String err = "" + new Object(); + // To make sure that we would get an error if the RHS is not interned + // :: error: (assignment.type.incompatible) + @Interned String err = "" + new Object(); } diff --git a/checker/tests/interning/ComplexComparison.java b/checker/tests/interning/ComplexComparison.java index d50c8048796..cc29f2b9e8e 100644 --- a/checker/tests/interning/ComplexComparison.java +++ b/checker/tests/interning/ComplexComparison.java @@ -1,93 +1,94 @@ -import java.util.Comparator; import org.checkerframework.checker.interning.qual.Interned; +import java.util.Comparator; + public class ComplexComparison { - void testInterned() { + void testInterned() { - @Interned String a = "foo"; - @Interned String b = "bar"; + @Interned String a = "foo"; + @Interned String b = "bar"; - if (a != null && b != null && a == b) { - System.out.println("yes"); - } else { - System.out.println("no"); + if (a != null && b != null && a == b) { + System.out.println("yes"); + } else { + System.out.println("no"); + } } - } - void testInternedDueToFlow() { + void testInternedDueToFlow() { - String c = "foo"; - String d = "bar"; + String c = "foo"; + String d = "bar"; - if (c != null && d != null && c == d) { - System.out.println("yes"); - } else { - System.out.println("no"); + if (c != null && d != null && c == d) { + System.out.println("yes"); + } else { + System.out.println("no"); + } } - } - void testNotInterned() { + void testNotInterned() { - String e = new String("foo"); - String f = new String("bar"); + String e = new String("foo"); + String f = new String("bar"); - // :: error: (not.interned) - if (e != null && f != null && e == f) { - System.out.println("yes"); - } else { - System.out.println("no"); + // :: error: (not.interned) + if (e != null && f != null && e == f) { + System.out.println("yes"); + } else { + System.out.println("no"); + } } - } - - /* @ pure */ public class DoubleArrayComparatorLexical implements Comparator { - - /** - * Lexically compares o1 and o2 as double arrays. - * - * @return positive if o1 > 02, 0 if 01 == 02, negative if 01 < 02 - */ - public int compare(double[] a1, double[] a2) { - // Heuristic: permit "arg1 == arg2" in a test in the first statement - // of a "Comparator.compare" method, if the body just returns 0. - if (a1 == a2) { - return 0; - } - int len = Math.min(a1.length, a2.length); - for (int i = 0; i < len; i++) { - if (a1[i] != a2[i]) { - return ((a1[i] > a2[i]) ? 1 : -1); + + /* @ pure */ public class DoubleArrayComparatorLexical implements Comparator { + + /** + * Lexically compares o1 and o2 as double arrays. + * + * @return positive if o1 > 02, 0 if 01 == 02, negative if 01 < 02 + */ + public int compare(double[] a1, double[] a2) { + // Heuristic: permit "arg1 == arg2" in a test in the first statement + // of a "Comparator.compare" method, if the body just returns 0. + if (a1 == a2) { + return 0; + } + int len = Math.min(a1.length, a2.length); + for (int i = 0; i < len; i++) { + if (a1[i] != a2[i]) { + return ((a1[i] > a2[i]) ? 1 : -1); + } + } + return a1.length - a2.length; } - } - return a1.length - a2.length; } - } - - class C { - @Override - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object other) { - // Heuristic: permit "this == arg1" in a test in the first statement - // of a "Comparator.compare" method, if the body just returns true. - if (this == other) { - return true; - } - return super.equals(other); + + class C { + @Override + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object other) { + // Heuristic: permit "this == arg1" in a test in the first statement + // of a "Comparator.compare" method, if the body just returns true. + if (this == other) { + return true; + } + return super.equals(other); + } } - } - - // // TODO - // class D { - // @Override - // public boolean equals(Object other) { - // // Don't suppress warnings at "this == arg1" if arg1 has been reassigned - // other = new Object(); - // - // if (this == other) { - // return true; - // } - // return super.equals(other); - // } - // } + + // // TODO + // class D { + // @Override + // public boolean equals(Object other) { + // // Don't suppress warnings at "this == arg1" if arg1 has been reassigned + // other = new Object(); + // + // if (this == other) { + // return true; + // } + // return super.equals(other); + // } + // } } diff --git a/checker/tests/interning/ConditionalInterning.java b/checker/tests/interning/ConditionalInterning.java index 0ae5b59af4b..342cd77e981 100644 --- a/checker/tests/interning/ConditionalInterning.java +++ b/checker/tests/interning/ConditionalInterning.java @@ -1,7 +1,7 @@ public class ConditionalInterning { - int a, b, c; + int a, b, c; - boolean cmp() { - return (a > b ? a < c : a > c); - } + boolean cmp() { + return (a > b ? a < c : a > c); + } } diff --git a/checker/tests/interning/ConstantsInterning.java b/checker/tests/interning/ConstantsInterning.java index ac50373b211..1c416237214 100644 --- a/checker/tests/interning/ConstantsInterning.java +++ b/checker/tests/interning/ConstantsInterning.java @@ -2,36 +2,36 @@ public class ConstantsInterning { - // All but D should be inferred to be @Interned String. - final String A = "A"; - final String B = "B"; - final String AB = A + B; - final String AC = A + "C"; - final String D = new String("D"); - final @Interned String E = new String("E").intern(); - final Object F = "F"; + // All but D should be inferred to be @Interned String. + final String A = "A"; + final String B = "B"; + final String AB = A + B; + final String AC = A + "C"; + final String D = new String("D"); + final @Interned String E = new String("E").intern(); + final Object F = "F"; - void foo() { - @Interned String is; - is = A; - is = B; - is = AB; - is = A + B; - is = AC; - is = A + "C"; - is = A + B + "C"; - // :: error: (assignment.type.incompatible) - is = D; - // :: error: (assignment.type.incompatible) - is = A + E; - // :: error: (assignment.type.incompatible) - is = is + is; - is = Constants2.E; - // :: error: (assignment.type.incompatible) - is = (String) F; - } + void foo() { + @Interned String is; + is = A; + is = B; + is = AB; + is = A + B; + is = AC; + is = A + "C"; + is = A + B + "C"; + // :: error: (assignment.type.incompatible) + is = D; + // :: error: (assignment.type.incompatible) + is = A + E; + // :: error: (assignment.type.incompatible) + is = is + is; + is = Constants2.E; + // :: error: (assignment.type.incompatible) + is = (String) F; + } } class Constants2 { - public static final String E = "e"; + public static final String E = "e"; } diff --git a/checker/tests/interning/Creation.java b/checker/tests/interning/Creation.java index f79182869e5..54f1675ad4b 100644 --- a/checker/tests/interning/Creation.java +++ b/checker/tests/interning/Creation.java @@ -1,48 +1,48 @@ import org.checkerframework.checker.interning.qual.Interned; public class Creation { - @Interned Foo[] a = new @Interned Foo[22]; // valid - - class Foo {} - - @Interned Foo[] fa_field1 = new @Interned Foo[22]; // valid - @Interned Foo[] fa_field2 = new @Interned Foo[22]; // valid - - public void test() { - // :: error: (assignment.type.incompatible) - @Interned Foo f = new Foo(); // error - Foo g = new Foo(); // valid - // :: warning: (cast.unsafe.constructor.invocation) - @Interned Foo h = new @Interned Foo(); // valid - // :: error: (not.interned) - boolean b = (f == g); // error - - @Interned Foo[] fa1 = new @Interned Foo[22]; // valid - @Interned Foo[] fa2 = new @Interned Foo[22]; // valid - } - - public @Interned Object read_data_0() { - // :: error: (return.type.incompatible) - return new Object(); - } - - public @Interned Object read_data_1() { - // :: error: (return.type.incompatible) - return Integer.valueOf(22); - } - - public @Interned Integer read_data_2() { - // :: error: (return.type.incompatible) - return Integer.valueOf(22); - } - - public @Interned Object read_data_3() { - // :: error: (return.type.incompatible) - return new String("hello"); - } - - public @Interned String read_data_4() { - // :: error: (return.type.incompatible) - return new String("hello"); - } + @Interned Foo[] a = new @Interned Foo[22]; // valid + + class Foo {} + + @Interned Foo[] fa_field1 = new @Interned Foo[22]; // valid + @Interned Foo[] fa_field2 = new @Interned Foo[22]; // valid + + public void test() { + // :: error: (assignment.type.incompatible) + @Interned Foo f = new Foo(); // error + Foo g = new Foo(); // valid + // :: warning: (cast.unsafe.constructor.invocation) + @Interned Foo h = new @Interned Foo(); // valid + // :: error: (not.interned) + boolean b = (f == g); // error + + @Interned Foo[] fa1 = new @Interned Foo[22]; // valid + @Interned Foo[] fa2 = new @Interned Foo[22]; // valid + } + + public @Interned Object read_data_0() { + // :: error: (return.type.incompatible) + return new Object(); + } + + public @Interned Object read_data_1() { + // :: error: (return.type.incompatible) + return Integer.valueOf(22); + } + + public @Interned Integer read_data_2() { + // :: error: (return.type.incompatible) + return Integer.valueOf(22); + } + + public @Interned Object read_data_3() { + // :: error: (return.type.incompatible) + return new String("hello"); + } + + public @Interned String read_data_4() { + // :: error: (return.type.incompatible) + return new String("hello"); + } } diff --git a/checker/tests/interning/Creation2.java b/checker/tests/interning/Creation2.java index 26bea580a5b..0cf9eb03f2b 100644 --- a/checker/tests/interning/Creation2.java +++ b/checker/tests/interning/Creation2.java @@ -2,12 +2,12 @@ public class Creation2 { - @Interned class Baz { - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - @Interned Baz() {} - } + @Interned class Baz { + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @Interned Baz() {} + } - void test() { - Baz b = new Baz(); - } + void test() { + Baz b = new Baz(); + } } diff --git a/checker/tests/interning/Distinct.java b/checker/tests/interning/Distinct.java index db1e1ec6790..4c66f0860e3 100644 --- a/checker/tests/interning/Distinct.java +++ b/checker/tests/interning/Distinct.java @@ -3,66 +3,66 @@ public class Distinct { - class Foo {} + class Foo {} - Foo f1; - Foo f2; - @Interned Foo i1; - @Interned Foo i2; - @InternedDistinct Foo d1; - @InternedDistinct Foo d2; + Foo f1; + Foo f2; + @Interned Foo i1; + @Interned Foo i2; + @InternedDistinct Foo d1; + @InternedDistinct Foo d2; - public void testEquals() { - // :: error: (not.interned) - if (f1 == f2) {} - // :: error: (not.interned) - if (f1 == i2) {} - if (f1 == d2) {} - // :: error: (not.interned) - if (i1 == f2) {} - if (i1 == i2) {} - if (i1 == d2) {} - if (d1 == f2) {} - if (d1 == i2) {} - if (d1 == d2) {} - } + public void testEquals() { + // :: error: (not.interned) + if (f1 == f2) {} + // :: error: (not.interned) + if (f1 == i2) {} + if (f1 == d2) {} + // :: error: (not.interned) + if (i1 == f2) {} + if (i1 == i2) {} + if (i1 == d2) {} + if (d1 == f2) {} + if (d1 == i2) {} + if (d1 == d2) {} + } - public void testAssignment1() { - f1 = f2; - } + public void testAssignment1() { + f1 = f2; + } - public void testAssignment2() { - f1 = i2; - } + public void testAssignment2() { + f1 = i2; + } - public void testAssignment3() { - f1 = d2; - } + public void testAssignment3() { + f1 = d2; + } - public void testAssignment4() { - // :: error: (assignment.type.incompatible) - i1 = f2; - } + public void testAssignment4() { + // :: error: (assignment.type.incompatible) + i1 = f2; + } - public void testAssignment5() { - i1 = i2; - } + public void testAssignment5() { + i1 = i2; + } - public void testAssignment6() { - i1 = d2; - } + public void testAssignment6() { + i1 = d2; + } - public void testAssignment7() { - // :: error: (assignment.type.incompatible) - d1 = f2; - } + public void testAssignment7() { + // :: error: (assignment.type.incompatible) + d1 = f2; + } - public void testAssignment8() { - // :: error: (assignment.type.incompatible) - d1 = i2; - } + public void testAssignment8() { + // :: error: (assignment.type.incompatible) + d1 = i2; + } - public void testAssignment9() { - d1 = d2; - } + public void testAssignment9() { + d1 = d2; + } } diff --git a/checker/tests/interning/DontCrash.java b/checker/tests/interning/DontCrash.java index 979fff9c61c..985f882b9ed 100644 --- a/checker/tests/interning/DontCrash.java +++ b/checker/tests/interning/DontCrash.java @@ -2,26 +2,27 @@ // shouldn't crash. (Maybe they shouldn't run at all if javac issues any errors?) // @skip-test +import org.checkerframework.checker.interning.qual.Interned; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.interning.qual.Interned; public class DontCrash { - // from VarInfoAux - static class VIA { - // :: non-static variable this cannot be referenced from a static context - // :: inner classes cannot have static declarations - // :: non-static variable this cannot be referenced from a static context - // :: inner classes cannot have static declarations - private static VIA theDefault = new VIA(); - private Map<@Interned String, @Interned String> map; + // from VarInfoAux + static class VIA { + // :: non-static variable this cannot be referenced from a static context + // :: inner classes cannot have static declarations + // :: non-static variable this cannot be referenced from a static context + // :: inner classes cannot have static declarations + private static VIA theDefault = new VIA(); + private Map<@Interned String, @Interned String> map; - void testMap() { - Map<@Interned String, @Interned String> mymap; - mymap = theDefault.map; - mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); - mymap = new HashMap<>(theDefault.map); + void testMap() { + Map<@Interned String, @Interned String> mymap; + mymap = theDefault.map; + mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); + mymap = new HashMap<>(theDefault.map); + } } - } } diff --git a/checker/tests/interning/Enumerations.java b/checker/tests/interning/Enumerations.java index 449a44754e3..40047c45ef7 100644 --- a/checker/tests/interning/Enumerations.java +++ b/checker/tests/interning/Enumerations.java @@ -1,29 +1,29 @@ public class Enumerations { - // All enumeration instances are interned; there should be no need for an annotation. - enum StudentYear { - FRESHMAN, - SOPHOMORE, - JUNIOR, - SENIOR; + // All enumeration instances are interned; there should be no need for an annotation. + enum StudentYear { + FRESHMAN, + SOPHOMORE, + JUNIOR, + SENIOR; - // check that receiver is OK - @org.checkerframework.dataflow.qual.Pure - public String toString() { - return "StudentYear: ..."; + // check that receiver is OK + @org.checkerframework.dataflow.qual.Pure + public String toString() { + return "StudentYear: ..."; + } } - } - public boolean isSophomore(StudentYear sy) { - return sy == StudentYear.SOPHOMORE; - } + public boolean isSophomore(StudentYear sy) { + return sy == StudentYear.SOPHOMORE; + } - public boolean flow(StudentYear s) { - StudentYear m = StudentYear.SOPHOMORE; - return s == m; - } + public boolean flow(StudentYear s) { + StudentYear m = StudentYear.SOPHOMORE; + return s == m; + } - StudentYear cast(Object o) { - return (StudentYear) o; - } + StudentYear cast(Object o) { + return (StudentYear) o; + } } diff --git a/checker/tests/interning/ExpressionsInterning.java b/checker/tests/interning/ExpressionsInterning.java index 4c4f1857f35..e8fe534ae01 100644 --- a/checker/tests/interning/ExpressionsInterning.java +++ b/checker/tests/interning/ExpressionsInterning.java @@ -2,53 +2,53 @@ public class ExpressionsInterning { - class A { - B b; - } + class A { + B b; + } + + class B { + C c; - class B { - C c; + D d() { + return new D(); + } - D d() { - return new D(); + Boolean bBoolean() { + return true; + } } - Boolean bBoolean() { - return true; + class C {} + + class D {} + + public Boolean fieldThenMethod(A a) { + Boolean temp = a.b.bBoolean(); + return temp; } - } - class C {} + class Foo { + public @Interned Foo returnThis(@Interned Foo this, @Interned Foo other) { + if (other == this) { + return this; + } else { + return null; + } + } + } - class D {} + // :: warning: (cast.unsafe.constructor.invocation) + public @Interned Foo THEONE = new @Interned Foo(); - public Boolean fieldThenMethod(A a) { - Boolean temp = a.b.bBoolean(); - return temp; - } + public boolean isItTheOne(Foo f) { + return THEONE.equals(f); + } - class Foo { - public @Interned Foo returnThis(@Interned Foo this, @Interned Foo other) { - if (other == this) { - return this; - } else { - return null; - } + // A warning when interned objects are compared via .equals helps me in determining whether it + // is a good idea to convert a given class or reference to @Interned -- I can see whether there + // are places that it is compared with .equals, which I might need to examine. + public boolean dontUseEqualsMethod(@Interned Foo f1, @Interned Foo f2) { + // :: warning: (unnecessary.equals) + return f1.equals(f2); } - } - - // :: warning: (cast.unsafe.constructor.invocation) - public @Interned Foo THEONE = new @Interned Foo(); - - public boolean isItTheOne(Foo f) { - return THEONE.equals(f); - } - - // A warning when interned objects are compared via .equals helps me in determining whether it - // is a good idea to convert a given class or reference to @Interned -- I can see whether there - // are places that it is compared with .equals, which I might need to examine. - public boolean dontUseEqualsMethod(@Interned Foo f1, @Interned Foo f2) { - // :: warning: (unnecessary.equals) - return f1.equals(f2); - } } diff --git a/checker/tests/interning/FieldsImplicits.java b/checker/tests/interning/FieldsImplicits.java index e46a07e377d..149e9536822 100644 --- a/checker/tests/interning/FieldsImplicits.java +++ b/checker/tests/interning/FieldsImplicits.java @@ -1,13 +1,13 @@ /** Tests that a final field annotation is inferred. */ public class FieldsImplicits { - final String finalField = "asdf"; - static final String finalStaticField = "asdf"; - String nonFinalField = "asdf"; + final String finalField = "asdf"; + static final String finalStaticField = "asdf"; + String nonFinalField = "asdf"; - void test() { - boolean a = finalField == "asdf"; - boolean b = finalStaticField == "asdf"; - // :: error: (not.interned) - boolean c = nonFinalField == "asdf"; - } + void test() { + boolean a = finalField == "asdf"; + boolean b = finalStaticField == "asdf"; + // :: error: (not.interned) + boolean c = nonFinalField == "asdf"; + } } diff --git a/checker/tests/interning/FindDistinctTest.java b/checker/tests/interning/FindDistinctTest.java index 238256349de..d1ab75cd3d6 100644 --- a/checker/tests/interning/FindDistinctTest.java +++ b/checker/tests/interning/FindDistinctTest.java @@ -4,28 +4,28 @@ public class FindDistinctTest { - public void ok1(@FindDistinct Object o) { - // TODO: The fact that this type-checks is an (undesired) artifact of the current - // implementation of @FindDistinct. - @InternedDistinct Object o2 = o; - } + public void ok1(@FindDistinct Object o) { + // TODO: The fact that this type-checks is an (undesired) artifact of the current + // implementation of @FindDistinct. + @InternedDistinct Object o2 = o; + } - public void ok2(@FindDistinct Object findIt, Object other) { - boolean b = findIt == other; - } + public void ok2(@FindDistinct Object findIt, Object other) { + boolean b = findIt == other; + } - public void useOk1(Object notinterned, @Interned Object interned) { - ok1(notinterned); - ok1(interned); - } + public void useOk1(Object notinterned, @Interned Object interned) { + ok1(notinterned); + ok1(interned); + } - public void bad1(Object o) { - // :: error: (assignment.type.incompatible) - @InternedDistinct Object o2 = o; - } + public void bad1(Object o) { + // :: error: (assignment.type.incompatible) + @InternedDistinct Object o2 = o; + } - public void bad2(Object findIt, Object other) { - // :: error: (not.interned) - boolean b = findIt == other; - } + public void bad2(Object findIt, Object other) { + // :: error: (not.interned) + boolean b = findIt == other; + } } diff --git a/checker/tests/interning/FlowInterning.java b/checker/tests/interning/FlowInterning.java index 247e3359fe2..bd78ffe7a53 100644 --- a/checker/tests/interning/FlowInterning.java +++ b/checker/tests/interning/FlowInterning.java @@ -3,58 +3,58 @@ public class FlowInterning { - // @skip-test - // Look at issue 47 - // public boolean isSame(Object a, Object b) { - // return ((a == null) - // ? (a == b) - // : (a.equals(b))); - // } + // @skip-test + // Look at issue 47 + // public boolean isSame(Object a, Object b) { + // return ((a == null) + // ? (a == b) + // : (a.equals(b))); + // } - public void testAppendingChar() { - String arg = ""; - arg += ' '; + public void testAppendingChar() { + String arg = ""; + arg += ' '; - // Interning Checker should NOT suggest == here. - if (!arg.equals("")) {} - } + // Interning Checker should NOT suggest == here. + if (!arg.equals("")) {} + } - public String[] parse(String args) { + public String[] parse(String args) { - // Split the args string on whitespace boundaries accounting for quoted strings. - args = args.trim(); - List arg_list = new ArrayList<>(); - String arg = ""; - char active_quote = 0; - for (int ii = 0; ii < args.length(); ii++) { - char ch = args.charAt(ii); - if ((ch == '\'') || (ch == '"')) { - arg += ch; - ii++; - while ((ii < args.length()) && (args.charAt(ii) != ch)) { - arg += args.charAt(ii++); - } - arg += ch; - } else if (Character.isWhitespace(ch)) { - // System.out.printf ("adding argument '%s'%n", arg); - arg_list.add(arg); - arg = ""; - while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { - ii++; + // Split the args string on whitespace boundaries accounting for quoted strings. + args = args.trim(); + List arg_list = new ArrayList<>(); + String arg = ""; + char active_quote = 0; + for (int ii = 0; ii < args.length(); ii++) { + char ch = args.charAt(ii); + if ((ch == '\'') || (ch == '"')) { + arg += ch; + ii++; + while ((ii < args.length()) && (args.charAt(ii) != ch)) { + arg += args.charAt(ii++); + } + arg += ch; + } else if (Character.isWhitespace(ch)) { + // System.out.printf ("adding argument '%s'%n", arg); + arg_list.add(arg); + arg = ""; + while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { + ii++; + } + if (ii < args.length()) { + ii--; + } + } else { // must be part of current argument + arg += ch; + } } - if (ii < args.length()) { - ii--; + // Interning Checker should NOT suggest == here. + if (!arg.equals("")) { + arg_list.add(arg); } - } else { // must be part of current argument - arg += ch; - } - } - // Interning Checker should NOT suggest == here. - if (!arg.equals("")) { - arg_list.add(arg); - } - String[] argsArray = arg_list.toArray(new String[arg_list.size()]); - return argsArray; - } + String[] argsArray = arg_list.toArray(new String[arg_list.size()]); + return argsArray; + } } diff --git a/checker/tests/interning/FlowInterning1.java b/checker/tests/interning/FlowInterning1.java index ef13e1f0ced..436a6f94daf 100644 --- a/checker/tests/interning/FlowInterning1.java +++ b/checker/tests/interning/FlowInterning1.java @@ -3,14 +3,14 @@ */ public class FlowInterning1 { - void test(String[] tokens, int i) { - String arg_type_name = tokens[i].intern(); - if (i + 1 >= tokens.length) { - throw new RuntimeException("No matching arg val for argument type " + arg_type_name); + void test(String[] tokens, int i) { + String arg_type_name = tokens[i].intern(); + if (i + 1 >= tokens.length) { + throw new RuntimeException("No matching arg val for argument type " + arg_type_name); + } + String arg_val = tokens[i + 1]; + if (arg_type_name == "boolean") { // interned + // ... + } } - String arg_val = tokens[i + 1]; - if (arg_type_name == "boolean") { // interned - // ... - } - } } diff --git a/checker/tests/interning/Generics.java b/checker/tests/interning/Generics.java index aa6b014b29d..5f145af44de 100644 --- a/checker/tests/interning/Generics.java +++ b/checker/tests/interning/Generics.java @@ -1,3 +1,5 @@ +import org.checkerframework.checker.interning.qual.Interned; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -5,136 +7,135 @@ import java.util.List; import java.util.Map; import java.util.Vector; -import org.checkerframework.checker.interning.qual.Interned; public class Generics { - void testGenerics() { + void testGenerics() { - Map map = null; - map = new HashMap<>(); + Map map = null; + map = new HashMap<>(); - String a = new String("foo"); - @Interned String b = "bar"; + String a = new String("foo"); + @Interned String b = "bar"; - String notInterned; - @Interned String interned; + String notInterned; + @Interned String interned; - map.put(a, b); // valid - // :: error: (argument.type.incompatible) - map.put(b, a); // error + map.put(a, b); // valid + // :: error: (argument.type.incompatible) + map.put(b, a); // error - notInterned = map.get(a); // valid - interned = map.get(b); // valid + notInterned = map.get(a); // valid + interned = map.get(b); // valid - Collection<@Interned String> internedSet; - Collection notInternedSet; + Collection<@Interned String> internedSet; + Collection notInternedSet; - notInternedSet = map.keySet(); // valid - // :: error: (assignment.type.incompatible) - internedSet = map.keySet(); // error + notInternedSet = map.keySet(); // valid + // :: error: (assignment.type.incompatible) + internedSet = map.keySet(); // error - // :: error: (assignment.type.incompatible) - notInternedSet = map.values(); // error - internedSet = map.values(); // valid + // :: error: (assignment.type.incompatible) + notInternedSet = map.values(); // error + internedSet = map.values(); // valid - HashMap<@Interned String, Vector<@Interned Integer>> all_nums = new HashMap<>(); - Vector<@Interned Integer> v = all_nums.get("Hello"); - } + HashMap<@Interned String, Vector<@Interned Integer>> all_nums = new HashMap<>(); + Vector<@Interned Integer> v = all_nums.get("Hello"); + } - // The cells aren't interned, but their contents are - class CellOfImm { - T value; + // The cells aren't interned, but their contents are + class CellOfImm { + T value; - boolean equals(CellOfImm other) { - return value == other.value; // valid + boolean equals(CellOfImm other) { + return value == other.value; // valid + } } - } - - List<@Interned String> istrings = new ArrayList<>(); - List strings = new ArrayList<>(); - @Interned String istring = "interned"; - String string = new String("uninterned"); - - void testGenerics2() { - istrings.add(istring); - // :: error: (argument.type.incompatible) - istrings.add(string); // invalid - strings.add(istring); - strings.add(string); - istring = istrings.get(0); - string = istrings.get(0); - // :: error: (assignment.type.incompatible) - istring = strings.get(0); // invalid - string = strings.get(0); - } - - void testCollections() { - Collection strings = Collections.unmodifiableCollection(new ArrayList()); - - Collection<@Interned String> istrings = - Collections.unmodifiableCollection(new ArrayList<@Interned String>()); // valid - } - - class MyList extends ArrayList<@Interned String> { - // Correct return value is Iterator<@Interned String> - // :: error: (override.return.invalid) - public Iterator iterator() { - return null; + + List<@Interned String> istrings = new ArrayList<>(); + List strings = new ArrayList<>(); + @Interned String istring = "interned"; + String string = new String("uninterned"); + + void testGenerics2() { + istrings.add(istring); + // :: error: (argument.type.incompatible) + istrings.add(string); // invalid + strings.add(istring); + strings.add(string); + istring = istrings.get(0); + string = istrings.get(0); + // :: error: (assignment.type.incompatible) + istring = strings.get(0); // invalid + string = strings.get(0); } - } - - // from VarInfoAux - static class VIA { - private static VIA theDefault = new VIA(); - private Map<@Interned String, @Interned String> map; - - void testMap() { - Map<@Interned String, @Interned String> mymap; - mymap = theDefault.map; - mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); - mymap = new HashMap<>(theDefault.map); + + void testCollections() { + Collection strings = Collections.unmodifiableCollection(new ArrayList()); + + Collection<@Interned String> istrings = + Collections.unmodifiableCollection(new ArrayList<@Interned String>()); // valid } - } - - // type inference - T id(T m, Object t) { - return m; - } - - void useID() { - String o = id("m", null); - } - - // raw types again - void testRawTypes() { - ArrayList lst = null; - Collections.sort(lst); - } - - public static class Pair { - public T1 a; - public T2 b; - - public Pair(T1 a, T2 b) { - this.a = a; - this.b = b; + + class MyList extends ArrayList<@Interned String> { + // Correct return value is Iterator<@Interned String> + // :: error: (override.return.invalid) + public Iterator iterator() { + return null; + } } - /** Factory method with short name and no need to name type parameters. */ - public static Pair of(A a, B b) { - return new Pair<>(a, b); + // from VarInfoAux + static class VIA { + private static VIA theDefault = new VIA(); + private Map<@Interned String, @Interned String> map; + + void testMap() { + Map<@Interned String, @Interned String> mymap; + mymap = theDefault.map; + mymap = new HashMap<@Interned String, @Interned String>(theDefault.map); + mymap = new HashMap<>(theDefault.map); + } + } + + // type inference + T id(T m, Object t) { + return m; + } + + void useID() { + String o = id("m", null); + } + + // raw types again + void testRawTypes() { + ArrayList lst = null; + Collections.sort(lst); + } + + public static class Pair { + public T1 a; + public T2 b; + + public Pair(T1 a, T2 b) { + this.a = a; + this.b = b; + } + + /** Factory method with short name and no need to name type parameters. */ + public static Pair of(A a, B b) { + return new Pair<>(a, b); + } + } + + static class C { + T next1; + + // @skip-test + // This test might be faulty + // private Pair return1() { + // Pair result = Pair.of(next1, (T)null); + // return result; + // } } - } - - static class C { - T next1; - - // @skip-test - // This test might be faulty - // private Pair return1() { - // Pair result = Pair.of(next1, (T)null); - // return result; - // } - } } diff --git a/checker/tests/interning/HeuristicsTest.java b/checker/tests/interning/HeuristicsTest.java index bda33b5599c..a5bb85ec070 100644 --- a/checker/tests/interning/HeuristicsTest.java +++ b/checker/tests/interning/HeuristicsTest.java @@ -1,238 +1,239 @@ -import java.util.Comparator; import org.checkerframework.checker.interning.qual.CompareToMethod; import org.checkerframework.checker.interning.qual.EqualsMethod; +import java.util.Comparator; + public class HeuristicsTest implements Comparable { - public static final class MyComparator implements Comparator { - // Using == is OK if it's the first statement in the compare method, - // it's comparing the arguments, and the return value is 0. - public int compare(String s1, String s2) { - if (s1 == s2) { + public static final class MyComparator implements Comparator { + // Using == is OK if it's the first statement in the compare method, + // it's comparing the arguments, and the return value is 0. + public int compare(String s1, String s2) { + if (s1 == s2) { + return 0; + } + return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); + } + } + + @Override + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @EqualsMethod + @org.checkerframework.dataflow.qual.Pure + public boolean equals2(Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @org.checkerframework.dataflow.qual.Pure + public boolean equals3(Object o) { + // Not equals() or annotated as @EqualsMethod. + // :: error: (not.interned) + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @EqualsMethod + @org.checkerframework.dataflow.qual.Pure + public static boolean equals4(Object thisOne, Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (thisOne == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == thisOne) { + return true; + } + return false; + } + + @org.checkerframework.dataflow.qual.Pure + public static boolean equals5(Object thisOne, Object o) { + // Not equals() or annotated as @EqualsMethod. + // :: error: (not.interned) + if (thisOne == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == thisOne) { + return true; + } + return false; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean equals6() { + return true; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean equals7(int a, int b, int c) { + return true; + } + + @Override + @org.checkerframework.dataflow.qual.Pure + public int compareTo(HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @CompareToMethod + @org.checkerframework.dataflow.qual.Pure + public int compareTo2(HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @org.checkerframework.dataflow.qual.Pure + public int compareTo3(HeuristicsTest o) { + // Not compareTo or annotated as @CompareToMethod + // :: error: (not.interned) + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @CompareToMethod + @org.checkerframework.dataflow.qual.Pure + public static int compareTo4(HeuristicsTest thisOne, HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == thisOne) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (thisOne == o) { + return 0; + } + return 0; + } + + @org.checkerframework.dataflow.qual.Pure + public static int compareTo5(HeuristicsTest thisOne, HeuristicsTest o) { + // Not compareTo or annotated as @CompareToMethod + // :: error: (not.interned) + if (o == thisOne) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (thisOne == o) { + return 0; + } return 0; - } - return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); - } - } - - @Override - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - if (this == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == this) { - return true; - } - return false; - } - - @EqualsMethod - @org.checkerframework.dataflow.qual.Pure - public boolean equals2(Object o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - if (this == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == this) { - return true; - } - return false; - } - - @org.checkerframework.dataflow.qual.Pure - public boolean equals3(Object o) { - // Not equals() or annotated as @EqualsMethod. - // :: error: (not.interned) - if (this == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == this) { - return true; - } - return false; - } - - @EqualsMethod - @org.checkerframework.dataflow.qual.Pure - public static boolean equals4(Object thisOne, Object o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - if (thisOne == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == thisOne) { - return true; - } - return false; - } - - @org.checkerframework.dataflow.qual.Pure - public static boolean equals5(Object thisOne, Object o) { - // Not equals() or annotated as @EqualsMethod. - // :: error: (not.interned) - if (thisOne == o) { - return true; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (o == thisOne) { - return true; - } - return false; - } - - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean equals6() { - return true; - } - - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean equals7(int a, int b, int c) { - return true; - } - - @Override - @org.checkerframework.dataflow.qual.Pure - public int compareTo(HeuristicsTest o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - - if (o == this) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (this == o) { - return 0; - } - return 0; - } - - @CompareToMethod - @org.checkerframework.dataflow.qual.Pure - public int compareTo2(HeuristicsTest o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - - if (o == this) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (this == o) { - return 0; - } - return 0; - } - - @org.checkerframework.dataflow.qual.Pure - public int compareTo3(HeuristicsTest o) { - // Not compareTo or annotated as @CompareToMethod - // :: error: (not.interned) - if (o == this) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (this == o) { - return 0; - } - return 0; - } - - @CompareToMethod - @org.checkerframework.dataflow.qual.Pure - public static int compareTo4(HeuristicsTest thisOne, HeuristicsTest o) { - // Using == is OK if it's the first statement in the equals method - // and it compares "this" against the argument. - - if (o == thisOne) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (thisOne == o) { - return 0; - } - return 0; - } - - @org.checkerframework.dataflow.qual.Pure - public static int compareTo5(HeuristicsTest thisOne, HeuristicsTest o) { - // Not compareTo or annotated as @CompareToMethod - // :: error: (not.interned) - if (o == thisOne) { - return 0; - } - // Not the first statement in the method. - // :: error: (not.interned) - if (thisOne == o) { - return 0; - } - return 0; - } - - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean compareTo6() { - return true; - } - - @EqualsMethod - // :: error: (invalid.method.annotation) - public boolean compareTo7(int a, int b, int c) { - return true; - } + } - public boolean optimizeEqualsClient(Object a, Object b, Object[] arr) { - // Using == is OK if it's the left-hand side of an || whose right-hand - // side is a call to equals with the same arguments. - if (a == b || a.equals(b)) { - System.out.println("one"); + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean compareTo6() { + return true; } - if (a == b || b.equals(a)) { - System.out.println("two"); + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean compareTo7(int a, int b, int c) { + return true; } - boolean c = (a == b || a.equals(b)); - c = (a == b || b.equals(a)); + public boolean optimizeEqualsClient(Object a, Object b, Object[] arr) { + // Using == is OK if it's the left-hand side of an || whose right-hand + // side is a call to equals with the same arguments. + if (a == b || a.equals(b)) { + System.out.println("one"); + } + if (a == b || b.equals(a)) { + System.out.println("two"); + } - boolean d = (a == b) || (a != null ? a.equals(b) : false); + boolean c = (a == b || a.equals(b)); + c = (a == b || b.equals(a)); - boolean e = (a == b || (a != null && a.equals(b))); + boolean d = (a == b) || (a != null ? a.equals(b) : false); - boolean f = (arr[0] == a || arr[0].equals(a)); + boolean e = (a == b || (a != null && a.equals(b))); - return (a == b || a.equals(b)); - } + boolean f = (arr[0] == a || arr[0].equals(a)); - public > boolean optimizeCompareToClient(T a, T b) { - // Using == is OK if it's the left-hand side of an || whose right-hand - // side is a call to compareTo with the same arguments. - if (a == b || a.compareTo(b) == 0) { - System.out.println("one"); - } - if (a == b || b.compareTo(a) == 0) { - System.out.println("two"); + return (a == b || a.equals(b)); } - boolean c = (a == b || a.compareTo(b) == 0); - c = (a == b || a.compareTo(b) == 0); + public > boolean optimizeCompareToClient(T a, T b) { + // Using == is OK if it's the left-hand side of an || whose right-hand + // side is a call to compareTo with the same arguments. + if (a == b || a.compareTo(b) == 0) { + System.out.println("one"); + } + if (a == b || b.compareTo(a) == 0) { + System.out.println("two"); + } - return (a == b || a.compareTo(b) == 0); - } + boolean c = (a == b || a.compareTo(b) == 0); + c = (a == b || a.compareTo(b) == 0); + + return (a == b || a.compareTo(b) == 0); + } } diff --git a/checker/tests/interning/InternMethodTest.java b/checker/tests/interning/InternMethodTest.java index 3b7a43663f8..20ad501590e 100644 --- a/checker/tests/interning/InternMethodTest.java +++ b/checker/tests/interning/InternMethodTest.java @@ -1,28 +1,29 @@ +import org.checkerframework.checker.interning.qual.Interned; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.interning.qual.Interned; public class InternMethodTest { - private static Map pool = new HashMap<>(); + private static Map pool = new HashMap<>(); - class Foo { + class Foo { - @SuppressWarnings("interning") - public @Interned Foo intern() { - if (!pool.containsKey(this)) { - pool.put(this, (@Interned Foo) this); - } - return pool.get(this); + @SuppressWarnings("interning") + public @Interned Foo intern() { + if (!pool.containsKey(this)) { + pool.put(this, (@Interned Foo) this); + } + return pool.get(this); + } } - } - void test() { - Foo f = new Foo(); - @Interned Foo g = f.intern(); - } + void test() { + Foo f = new Foo(); + @Interned Foo g = f.intern(); + } - public static @Interned String intern(String a) { - return (a == null) ? null : a.intern(); - } + public static @Interned String intern(String a) { + return (a == null) ? null : a.intern(); + } } diff --git a/checker/tests/interning/InternUnbox.java b/checker/tests/interning/InternUnbox.java index dccd14e1603..4139e2ba6eb 100644 --- a/checker/tests/interning/InternUnbox.java +++ b/checker/tests/interning/InternUnbox.java @@ -1,12 +1,12 @@ public class InternUnbox { - void method() { - Boolean leftBoolean = getBooleanValue(); - createBooleanCFValue(!leftBoolean); - } + void method() { + Boolean leftBoolean = getBooleanValue(); + createBooleanCFValue(!leftBoolean); + } - private void createBooleanCFValue(boolean b) {} + private void createBooleanCFValue(boolean b) {} - private Boolean getBooleanValue() { - return Boolean.FALSE; - } + private Boolean getBooleanValue() { + return Boolean.FALSE; + } } diff --git a/checker/tests/interning/InternedClass.java b/checker/tests/interning/InternedClass.java index 47dd3468a6a..deaab6457e5 100644 --- a/checker/tests/interning/InternedClass.java +++ b/checker/tests/interning/InternedClass.java @@ -1,156 +1,157 @@ +import org.checkerframework.checker.interning.qual.InternMethod; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.interning.qual.UnknownInterned; + import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Vector; -import org.checkerframework.checker.interning.qual.InternMethod; -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.interning.qual.UnknownInterned; // The @Interned annotation indicates that much like an enum, all variables // declared of this type are interned (except the constructor return value). public @Interned class InternedClass { - int value; - - InternedClass factory(int i) { - return new InternedClass(i).intern(); - } - - // Private constructor - private InternedClass(int i) { - value = i; - // "this" in the constructor is not interned. - // :: error: (assignment.type.incompatible) - @Interned InternedClass that = this; - } - - // Overriding method - @org.checkerframework.dataflow.qual.Pure - public String toString() { - @Interned InternedClass c = this; - return Integer.valueOf(value).toString(); - } - - // Factory method - private InternedClass(InternedClass ic) { - value = ic.value; - } - - // Equals method (used only by interning; clients should use ==) - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object other) { - if (!(other instanceof InternedClass)) { - return false; - } - return value == ((InternedClass) other).value; - } - - // Interning method - @SuppressWarnings("type.invalid.annotations.on.use") - private static Map<@UnknownInterned InternedClass, @Interned InternedClass> pool = - new HashMap<>(); - - @InternMethod - public @Interned InternedClass intern() { - if (!pool.containsKey(this)) { - pool.put(this, (@Interned InternedClass) this); - } - return pool.get(this); - } - - public void myMethod(InternedClass ic, InternedClass[] ica) { - boolean b1 = (this == ic); // valid - boolean b2 = (this == returnInternedObject()); // valid - boolean b3 = (this == ica[0]); // valid - InternedClass ic2 = returnArray()[0]; // valid - // :: error: (interned.object.creation) - ica[0] = new InternedClass(22); - InternedClass[] arr1 = returnArray(); // valid - InternedClass[] arr2 = new InternedClass[22]; // valid - InternedClass[] arr3 = new InternedClass[] {}; // valid - - Map map = new LinkedHashMap<>(); - for (Map.Entry e : map.entrySet()) { - InternedClass ic3 = e.getKey(); // valid - } - } - - public InternedClass returnInternedObject() { - return this; - } - - public InternedClass[] returnArray() { - return new InternedClass[] {}; - } - - public void internedVarargs(String name, InternedClass... args) { - InternedClass arg = args[0]; // valid - } - - public void internedVarargs2(String name, @Interned String... args) { - @Interned String arg = args[0]; // valid - } - - public static InternedClass[] arrayclone_simple(InternedClass[] a_old) { - int len = a_old.length; - InternedClass[] a_new = new InternedClass[len]; - for (int i = 0; i < len; i++) { - // :: error: (interned.object.creation) - a_new[i] = new InternedClass(a_old[i]); - } - return a_new; - } - - public @Interned class Subclass extends InternedClass { + int value; + + InternedClass factory(int i) { + return new InternedClass(i).intern(); + } + // Private constructor - private Subclass(int i) { - super(i); - } - } - - public static void castFromInternedClass(InternedClass ic) { - Subclass s = (Subclass) ic; - } - - public static void castToInternedClass(Object o) { - InternedClass ic = (InternedClass) o; - } - - // Default implementation - @org.checkerframework.dataflow.qual.Pure - public InternedClass clone() throws CloneNotSupportedException { - return (InternedClass) super.clone(); - } - - // java.lang.Class should be considered interned - public static void classTest() { - Integer i = 5; - assert i.getClass() == Integer.class; - } - - // java.lang.Class is interned - public static void arrayOfClass() throws Exception { - Class c = String.class; - Class[] parameterTypes = new Class[1]; - parameterTypes[0] = String.class; - java.lang.reflect.Constructor ctor = c.getConstructor(parameterTypes); - } - - Class[] getSuperClasses(Class c) { - Vector> v = new Vector<>(); - while (true) { - // :: warning: (unnecessary.equals) - if (c.getSuperclass().equals((new Object()).getClass())) { - break; - } - c = c.getSuperclass(); - v.addElement(c); - } - return (Class[]) v.toArray(new Class[0]); - } - - void testCast(Object o) { - Object i = (InternedClass) o; - if (i == this) {} - } + private InternedClass(int i) { + value = i; + // "this" in the constructor is not interned. + // :: error: (assignment.type.incompatible) + @Interned InternedClass that = this; + } + + // Overriding method + @org.checkerframework.dataflow.qual.Pure + public String toString() { + @Interned InternedClass c = this; + return Integer.valueOf(value).toString(); + } + + // Factory method + private InternedClass(InternedClass ic) { + value = ic.value; + } + + // Equals method (used only by interning; clients should use ==) + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object other) { + if (!(other instanceof InternedClass)) { + return false; + } + return value == ((InternedClass) other).value; + } + + // Interning method + @SuppressWarnings("type.invalid.annotations.on.use") + private static Map<@UnknownInterned InternedClass, @Interned InternedClass> pool = + new HashMap<>(); + + @InternMethod + public @Interned InternedClass intern() { + if (!pool.containsKey(this)) { + pool.put(this, (@Interned InternedClass) this); + } + return pool.get(this); + } + + public void myMethod(InternedClass ic, InternedClass[] ica) { + boolean b1 = (this == ic); // valid + boolean b2 = (this == returnInternedObject()); // valid + boolean b3 = (this == ica[0]); // valid + InternedClass ic2 = returnArray()[0]; // valid + // :: error: (interned.object.creation) + ica[0] = new InternedClass(22); + InternedClass[] arr1 = returnArray(); // valid + InternedClass[] arr2 = new InternedClass[22]; // valid + InternedClass[] arr3 = new InternedClass[] {}; // valid + + Map map = new LinkedHashMap<>(); + for (Map.Entry e : map.entrySet()) { + InternedClass ic3 = e.getKey(); // valid + } + } + + public InternedClass returnInternedObject() { + return this; + } + + public InternedClass[] returnArray() { + return new InternedClass[] {}; + } + + public void internedVarargs(String name, InternedClass... args) { + InternedClass arg = args[0]; // valid + } + + public void internedVarargs2(String name, @Interned String... args) { + @Interned String arg = args[0]; // valid + } + + public static InternedClass[] arrayclone_simple(InternedClass[] a_old) { + int len = a_old.length; + InternedClass[] a_new = new InternedClass[len]; + for (int i = 0; i < len; i++) { + // :: error: (interned.object.creation) + a_new[i] = new InternedClass(a_old[i]); + } + return a_new; + } + + public @Interned class Subclass extends InternedClass { + // Private constructor + private Subclass(int i) { + super(i); + } + } + + public static void castFromInternedClass(InternedClass ic) { + Subclass s = (Subclass) ic; + } + + public static void castToInternedClass(Object o) { + InternedClass ic = (InternedClass) o; + } + + // Default implementation + @org.checkerframework.dataflow.qual.Pure + public InternedClass clone() throws CloneNotSupportedException { + return (InternedClass) super.clone(); + } + + // java.lang.Class should be considered interned + public static void classTest() { + Integer i = 5; + assert i.getClass() == Integer.class; + } + + // java.lang.Class is interned + public static void arrayOfClass() throws Exception { + Class c = String.class; + Class[] parameterTypes = new Class[1]; + parameterTypes[0] = String.class; + java.lang.reflect.Constructor ctor = c.getConstructor(parameterTypes); + } + + Class[] getSuperClasses(Class c) { + Vector> v = new Vector<>(); + while (true) { + // :: warning: (unnecessary.equals) + if (c.getSuperclass().equals((new Object()).getClass())) { + break; + } + c = c.getSuperclass(); + v.addElement(c); + } + return (Class[]) v.toArray(new Class[0]); + } + + void testCast(Object o) { + Object i = (InternedClass) o; + if (i == this) {} + } } diff --git a/checker/tests/interning/InternedClass2.java b/checker/tests/interning/InternedClass2.java index 12177c55d43..b4ff45972ed 100644 --- a/checker/tests/interning/InternedClass2.java +++ b/checker/tests/interning/InternedClass2.java @@ -1,78 +1,79 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.interning.qual.InternMethod; import org.checkerframework.checker.interning.qual.Interned; -public @Interned class InternedClass2 { - private final int i; - - // @UnknownInterned is the default annotation on constructor results even for @Interned classes. - private InternedClass2(int i) { - // Type of "this" inside a constructor of an @Interned class is @UnknownInterned. - // :: error: (assignment.type.incompatible) - @Interned InternedClass2 that = this; - this.i = i; - } +import java.util.HashMap; +import java.util.Map; - InternedClass2 factory(int i) { - // :: error: (interned.object.creation) :: error: (method.invocation.invalid) - new InternedClass2(i).someMethod(); // error, call to constructor on for @Interned class. - (new InternedClass2(i)).intern(); // ok, call to constructor receiver to @InternMethod - ((((new InternedClass2(i))))).intern(); // ok, call to constructor receiver to @InternMethod - return new InternedClass2(i).intern(); // ok, call to constructor receiver to @InternMethod - } +public @Interned class InternedClass2 { + private final int i; - void someMethod() { - // Type of "this" inside a method (not marked @InternedMethod) is @Interned, - // assuming the method is declared in an @Interned class. - @Interned InternedClass2 that = this; // ok - } + // @UnknownInterned is the default annotation on constructor results even for @Interned classes. + private InternedClass2(int i) { + // Type of "this" inside a constructor of an @Interned class is @UnknownInterned. + // :: error: (assignment.type.incompatible) + @Interned InternedClass2 that = this; + this.i = i; + } - private static Map pool = new HashMap<>(); + InternedClass2 factory(int i) { + // :: error: (interned.object.creation) :: error: (method.invocation.invalid) + new InternedClass2(i).someMethod(); // error, call to constructor on for @Interned class. + (new InternedClass2(i)).intern(); // ok, call to constructor receiver to @InternMethod + ((((new InternedClass2(i))))).intern(); // ok, call to constructor receiver to @InternMethod + return new InternedClass2(i).intern(); // ok, call to constructor receiver to @InternMethod + } - @InternMethod - public InternedClass2 intern() { - // Type of "this" inside an @InternMethod is @UnknownInterned - // :: error: (assignment.type.incompatible) - @Interned InternedClass2 that = this; - if (!pool.containsKey(this.i)) { - // The above check proves "this" is interned. - @SuppressWarnings("interning:assignment.type.incompatible") - @Interned InternedClass2 internedThis = this; - pool.put(this.i, internedThis); + void someMethod() { + // Type of "this" inside a method (not marked @InternedMethod) is @Interned, + // assuming the method is declared in an @Interned class. + @Interned InternedClass2 that = this; // ok } - return pool.get(this.i); - } - @Override // ok, no override invalid receiver error is issued. - public String toString() { - @Interned InternedClass2 that = this; // ok - return super.toString(); - } + private static Map pool = new HashMap<>(); - @Override - public boolean equals(Object object) { - if (this == object) { - return true; + @InternMethod + public InternedClass2 intern() { + // Type of "this" inside an @InternMethod is @UnknownInterned + // :: error: (assignment.type.incompatible) + @Interned InternedClass2 that = this; + if (!pool.containsKey(this.i)) { + // The above check proves "this" is interned. + @SuppressWarnings("interning:assignment.type.incompatible") + @Interned InternedClass2 internedThis = this; + pool.put(this.i, internedThis); + } + return pool.get(this.i); } - if (object == null || getClass() != object.getClass()) { - return false; + + @Override // ok, no override invalid receiver error is issued. + public String toString() { + @Interned InternedClass2 that = this; // ok + return super.toString(); } - InternedClass2 that = (InternedClass2) object; + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } - return i == that.i; - } + InternedClass2 that = (InternedClass2) object; - @Override - public int hashCode() { - return i; - } + return i == that.i; + } + + @Override + public int hashCode() { + return i; + } - public boolean hasNodeOfType(Class type) { - if (type == this.getClass()) { - return true; + public boolean hasNodeOfType(Class type) { + if (type == this.getClass()) { + return true; + } + return false; } - return false; - } } diff --git a/checker/tests/interning/InternedClassDecl.java b/checker/tests/interning/InternedClassDecl.java index b2974546e49..b11da3bd1e1 100644 --- a/checker/tests/interning/InternedClassDecl.java +++ b/checker/tests/interning/InternedClassDecl.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.interning.qual.Interned; public class InternedClassDecl { - static @Interned class InternedClass {} + static @Interned class InternedClass {} - static class Generic {} + static class Generic {} - static @Interned class RecursiveClass2> {} + static @Interned class RecursiveClass2> {} } diff --git a/checker/tests/interning/Issue2809.java b/checker/tests/interning/Issue2809.java index 90a7f67024b..437c2f4f1ff 100644 --- a/checker/tests/interning/Issue2809.java +++ b/checker/tests/interning/Issue2809.java @@ -6,26 +6,26 @@ public class Issue2809 { - void new1(MyType t, int @Interned [] non) { - t.self(new MyType<>(non)); - } + void new1(MyType t, int @Interned [] non) { + t.self(new MyType<>(non)); + } - void new2(MyType t, int @Interned [] non) { - t.self(new MyType(non)); - } + void new2(MyType t, int @Interned [] non) { + t.self(new MyType(non)); + } - void new3(MyType<@Interned MyType> t, @Interned MyType non) { - t.self(new MyType<>(non)); - } + void new3(MyType<@Interned MyType> t, @Interned MyType non) { + t.self(new MyType<>(non)); + } - void newFail(MyType t, int @UnknownInterned [] non) { - // :: error: (type.arguments.not.inferred) - t.self(new MyType<>(non)); - } + void newFail(MyType t, int @UnknownInterned [] non) { + // :: error: (type.arguments.not.inferred) + t.self(new MyType<>(non)); + } - class MyType { - MyType(T p) {} + class MyType { + MyType(T p) {} - void self(MyType myType) {} - } + void self(MyType myType) {} + } } diff --git a/checker/tests/interning/Issue3594.java b/checker/tests/interning/Issue3594.java index ab461faa641..4d231c06166 100644 --- a/checker/tests/interning/Issue3594.java +++ b/checker/tests/interning/Issue3594.java @@ -1,10 +1,10 @@ public class Issue3594 { - // Throwable is annotated with @UsesObjectEquals, which is an inherited annotation. - // So, MyThrowable should be treated as @UsesObjectEquals, too. - static class MyThrowable extends Throwable {} + // Throwable is annotated with @UsesObjectEquals, which is an inherited annotation. + // So, MyThrowable should be treated as @UsesObjectEquals, too. + static class MyThrowable extends Throwable {} - void use(MyThrowable t, MyThrowable t2) { - boolean b = t == t2; - } + void use(MyThrowable t, MyThrowable t2) { + boolean b = t == t2; + } } diff --git a/checker/tests/interning/IterableGenerics.java b/checker/tests/interning/IterableGenerics.java index 6b194e1c59e..8123cf74a7a 100644 --- a/checker/tests/interning/IterableGenerics.java +++ b/checker/tests/interning/IterableGenerics.java @@ -1,11 +1,11 @@ public class IterableGenerics { - interface Data extends Iterable {} + interface Data extends Iterable {} - void typeParam(T t) { - for (String s : t) {} - } + void typeParam(T t) { + for (String s : t) {} + } - void wildcard(Iterable t) { - for (Object a : t.iterator().next()) {} - } + void wildcard(Iterable t) { + for (Object a : t.iterator().next()) {} + } } diff --git a/checker/tests/interning/MapEntryLubError.java b/checker/tests/interning/MapEntryLubError.java index e20d5701d57..557b20d8d67 100644 --- a/checker/tests/interning/MapEntryLubError.java +++ b/checker/tests/interning/MapEntryLubError.java @@ -1,8 +1,8 @@ import java.util.Map; public class MapEntryLubError { - public boolean lubError(Map.Entry ent) { - Object v; - return (v = ent.getValue()) == null; - } + public boolean lubError(Map.Entry ent) { + Object v; + return (v = ent.getValue()) == null; + } } diff --git a/checker/tests/interning/MethodInvocation.java b/checker/tests/interning/MethodInvocation.java index dcae2e1fb7b..d5eb4e077a0 100644 --- a/checker/tests/interning/MethodInvocation.java +++ b/checker/tests/interning/MethodInvocation.java @@ -1,53 +1,53 @@ import org.checkerframework.checker.interning.qual.*; public class MethodInvocation { - @Interned MethodInvocation interned; - MethodInvocation nonInterned; - - void nonInternedMethod() { - nonInternedMethod(); - // :: error: (method.invocation.invalid) - internedMethod(); // should emit error - - this.nonInternedMethod(); - // :: error: (method.invocation.invalid) - this.internedMethod(); // should emit error - - interned.nonInternedMethod(); - interned.internedMethod(); - - nonInterned.nonInternedMethod(); - // :: error: (method.invocation.invalid) - nonInterned.internedMethod(); // should emit error - } - - void internedMethod(@Interned MethodInvocation this) { - nonInternedMethod(); - internedMethod(); - - this.nonInternedMethod(); - this.internedMethod(); - - interned.nonInternedMethod(); - interned.internedMethod(); - - nonInterned.nonInternedMethod(); - // :: error: (method.invocation.invalid) - nonInterned.internedMethod(); // should emit error - } - - // Now, test method parameters - void internedCharacterParameter(@Interned Character a) {} - - // See https://github.com/typetools/checker-framework/issues/84 - void internedCharacterParametersClient() { - // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) - internedCharacterParameter('\u00E4'); // lowercase a with umlaut - // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) - internedCharacterParameter('a'); - // :: error: (argument.type.incompatible) - internedCharacterParameter(Character.valueOf('a')); - // :: error: (argument.type.incompatible) - internedCharacterParameter(Character.valueOf('a')); - } + @Interned MethodInvocation interned; + MethodInvocation nonInterned; + + void nonInternedMethod() { + nonInternedMethod(); + // :: error: (method.invocation.invalid) + internedMethod(); // should emit error + + this.nonInternedMethod(); + // :: error: (method.invocation.invalid) + this.internedMethod(); // should emit error + + interned.nonInternedMethod(); + interned.internedMethod(); + + nonInterned.nonInternedMethod(); + // :: error: (method.invocation.invalid) + nonInterned.internedMethod(); // should emit error + } + + void internedMethod(@Interned MethodInvocation this) { + nonInternedMethod(); + internedMethod(); + + this.nonInternedMethod(); + this.internedMethod(); + + interned.nonInternedMethod(); + interned.internedMethod(); + + nonInterned.nonInternedMethod(); + // :: error: (method.invocation.invalid) + nonInterned.internedMethod(); // should emit error + } + + // Now, test method parameters + void internedCharacterParameter(@Interned Character a) {} + + // See https://github.com/typetools/checker-framework/issues/84 + void internedCharacterParametersClient() { + // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) + internedCharacterParameter('\u00E4'); // lowercase a with umlaut + // TODO: autoboxing from char to Character // :: error: (argument.type.incompatible) + internedCharacterParameter('a'); + // :: error: (argument.type.incompatible) + internedCharacterParameter(Character.valueOf('a')); + // :: error: (argument.type.incompatible) + internedCharacterParameter(Character.valueOf('a')); + } } diff --git a/checker/tests/interning/NestedGenerics.java b/checker/tests/interning/NestedGenerics.java index 1c186f46cce..07ab9e8b979 100644 --- a/checker/tests/interning/NestedGenerics.java +++ b/checker/tests/interning/NestedGenerics.java @@ -1,13 +1,14 @@ -import java.util.List; import org.checkerframework.checker.interning.qual.Interned; +import java.util.List; + public class NestedGenerics { - public void test() { - List> foo = bar(); - } + public void test() { + List> foo = bar(); + } - public List> bar() { - return null; - } + public List> bar() { + return null; + } } diff --git a/checker/tests/interning/Options.java b/checker/tests/interning/Options.java index 0d0efa4a7e5..e6b6c1c470d 100644 --- a/checker/tests/interning/Options.java +++ b/checker/tests/interning/Options.java @@ -1,90 +1,91 @@ +import org.checkerframework.checker.interning.qual.*; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.interning.qual.*; // Test case lifted from plume.Options public class Options { - public void minimal(String s) { - String arg = ""; // interned here - @Interned String arg2 = arg; - arg += s; // no longer interned - // :: error: (assignment.type.incompatible) - arg2 = arg; - } + public void minimal(String s) { + String arg = ""; // interned here + @Interned String arg2 = arg; + arg += s; // no longer interned + // :: error: (assignment.type.incompatible) + arg2 = arg; + } - public void minimal2(char c) { - String arg = ""; // interned here - @Interned String arg2 = arg; - arg += c; // no longer interned - // :: error: (assignment.type.incompatible) - arg2 = arg; - } + public void minimal2(char c) { + String arg = ""; // interned here + @Interned String arg2 = arg; + arg += c; // no longer interned + // :: error: (assignment.type.incompatible) + arg2 = arg; + } - public String[] otherparse(String args) { + public String[] otherparse(String args) { - // Split the args string on whitespace boundaries accounting for quoted strings. - args = args.trim(); - List arg_list = new ArrayList<>(); - String arg = ""; - char active_quote = 0; - // for (int ii = 0; ii < args.length(); ii++) { - char ch = args.charAt(0); - // arg = arg + ch; + // Split the args string on whitespace boundaries accounting for quoted strings. + args = args.trim(); + List arg_list = new ArrayList<>(); + String arg = ""; + char active_quote = 0; + // for (int ii = 0; ii < args.length(); ii++) { + char ch = args.charAt(0); + // arg = arg + ch; - // if ((ch == '\'') || (ch == '"')) { - arg += ch; - // } - // } - // :: error: (assignment.type.incompatible) - @Interned String arg2 = arg; + // if ((ch == '\'') || (ch == '"')) { + arg += ch; + // } + // } + // :: error: (assignment.type.incompatible) + @Interned String arg2 = arg; - if (!arg.equals("")) { - arg_list.add(arg); - } + if (!arg.equals("")) { + arg_list.add(arg); + } - String[] argsArray = arg_list.toArray(new String[arg_list.size()]); - return null; - } + String[] argsArray = arg_list.toArray(new String[arg_list.size()]); + return null; + } - public String[] parse(String args) { + public String[] parse(String args) { - // Split the args string on whitespace boundaries accounting for quoted strings. - args = args.trim(); - List arg_list = new ArrayList<>(); - String arg = ""; - char active_quote = 0; - for (int ii = 0; ii < args.length(); ii++) { - char ch = args.charAt(ii); - if ((ch == '\'') || (ch == '"')) { - arg += ch; - ii++; - while ((ii < args.length()) && (args.charAt(ii) != ch)) { - arg += args.charAt(ii++); - } - arg += ch; - } else if (Character.isWhitespace(ch)) { - // System.out.printf ("adding argument '%s'%n", arg); - arg_list.add(arg); - arg = ""; - while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { - ii++; + // Split the args string on whitespace boundaries accounting for quoted strings. + args = args.trim(); + List arg_list = new ArrayList<>(); + String arg = ""; + char active_quote = 0; + for (int ii = 0; ii < args.length(); ii++) { + char ch = args.charAt(ii); + if ((ch == '\'') || (ch == '"')) { + arg += ch; + ii++; + while ((ii < args.length()) && (args.charAt(ii) != ch)) { + arg += args.charAt(ii++); + } + arg += ch; + } else if (Character.isWhitespace(ch)) { + // System.out.printf ("adding argument '%s'%n", arg); + arg_list.add(arg); + arg = ""; + while ((ii < args.length()) && Character.isWhitespace(args.charAt(ii))) { + ii++; + } + if (ii < args.length()) { + ii--; + } + } else { // must be part of current argument + arg += ch; + } } - if (ii < args.length()) { - ii--; + // :: error: (assignment.type.incompatible) + @Interned String arg2 = arg; + + if (!arg.equals("")) { + arg_list.add(arg); } - } else { // must be part of current argument - arg += ch; - } - } - // :: error: (assignment.type.incompatible) - @Interned String arg2 = arg; - if (!arg.equals("")) { - arg_list.add(arg); + String[] argsArray = arg_list.toArray(new String[arg_list.size()]); + return null; } - - String[] argsArray = arg_list.toArray(new String[arg_list.size()]); - return null; - } } diff --git a/checker/tests/interning/OverrideInterned.java b/checker/tests/interning/OverrideInterned.java index a3774c42541..ac22ca4bf32 100644 --- a/checker/tests/interning/OverrideInterned.java +++ b/checker/tests/interning/OverrideInterned.java @@ -2,61 +2,61 @@ public class OverrideInterned { - // This code is extracted from FreePastry + // This code is extracted from FreePastry - @Interned class NodeHandle {} + @Interned class NodeHandle {} - public interface TransportLayer { - public void sendMessage(IDENTIFIER i); - } - - public class CommonAPITransportLayerImpl - implements TransportLayer { - public void sendMessage(IDENTIFIER i) {} - } + public interface TransportLayer { + public void sendMessage(IDENTIFIER i); + } - interface MessageReceipt { - public NodeHandle getHint(); - } + public class CommonAPITransportLayerImpl + implements TransportLayer { + public void sendMessage(IDENTIFIER i) {} + } - void useAnonymousClass() { - MessageReceipt ret = - new MessageReceipt() { - public NodeHandle getHint() { - return null; - } - }; - } + interface MessageReceipt { + public NodeHandle getHint(); + } - // This code is from Daikon + void useAnonymousClass() { + MessageReceipt ret = + new MessageReceipt() { + public NodeHandle getHint() { + return null; + } + }; + } - public abstract class TwoSequenceString { - public abstract Object check_modified1(@Interned String @Interned [] v1); + // This code is from Daikon - public abstract Object check_modified2(String @Interned [] v1); - } + public abstract class TwoSequenceString { + public abstract Object check_modified1(@Interned String @Interned [] v1); - /* Changing the array component type in the overriding method is illegal. */ - public class PairwiseStringEqualBad extends TwoSequenceString { - // TODOINVARR:: error: (override.param.invalid) - public Object check_modified1(String @Interned [] a1) { - return new Object(); + public abstract Object check_modified2(String @Interned [] v1); } - // :: error: (override.param.invalid) - public Object check_modified2(@Interned String @Interned [] a1) { - return new Object(); + /* Changing the array component type in the overriding method is illegal. */ + public class PairwiseStringEqualBad extends TwoSequenceString { + // TODOINVARR:: error: (override.param.invalid) + public Object check_modified1(String @Interned [] a1) { + return new Object(); + } + + // :: error: (override.param.invalid) + public Object check_modified2(@Interned String @Interned [] a1) { + return new Object(); + } } - } - /* Changing the main reference type is allowed, if it is a supertype. */ - public class PairwiseStringEqualGood extends TwoSequenceString { - public Object check_modified1(@Interned String[] a1) { - return new Object(); - } + /* Changing the main reference type is allowed, if it is a supertype. */ + public class PairwiseStringEqualGood extends TwoSequenceString { + public Object check_modified1(@Interned String[] a1) { + return new Object(); + } - public Object check_modified2(String[] a1) { - return new Object(); + public Object check_modified2(String[] a1) { + return new Object(); + } } - } } diff --git a/checker/tests/interning/Polymorphism.java b/checker/tests/interning/Polymorphism.java index 6cf53c43464..f55431a74ff 100644 --- a/checker/tests/interning/Polymorphism.java +++ b/checker/tests/interning/Polymorphism.java @@ -1,90 +1,92 @@ +import org.checkerframework.checker.interning.qual.*; + import java.lang.ref.WeakReference; import java.util.Date; import java.util.List; import java.util.Map; -import org.checkerframework.checker.interning.qual.*; public class Polymorphism { - // Test parameter - public @PolyInterned String identity(@PolyInterned String s) { - return s; - } - - void testParam() { - String notInterned = new String("not interned"); - @Interned String interned = "interned"; - - interned = identity(interned); - // :: error: (assignment.type.incompatible) - interned = identity(notInterned); // invalid - } - - // test as receiver - @PolyInterned Polymorphism getSelf(@PolyInterned Polymorphism this) { - return this; - } - - void testReceiver() { - Polymorphism notInterned = new Polymorphism(); - @Interned Polymorphism interned = null; - - interned = interned.getSelf(); - // :: error: (assignment.type.incompatible) - interned = notInterned.getSelf(); // invalid - } - - // Test assinging interned to PolyInterned - public @PolyInterned String always(@PolyInterned String s) { - if (s.equals("n")) { - // This code type-checkd when the hierarchy contained just @UnknownInterned and - // @Interned, but no longer does because of @InternedDistinct. - // :: error: (return.type.incompatible) - return "m"; - } else { - // :: error: (return.type.incompatible) - return new String("m"); // invalid + // Test parameter + public @PolyInterned String identity(@PolyInterned String s) { + return s; + } + + void testParam() { + String notInterned = new String("not interned"); + @Interned String interned = "interned"; + + interned = identity(interned); + // :: error: (assignment.type.incompatible) + interned = identity(notInterned); // invalid + } + + // test as receiver + @PolyInterned Polymorphism getSelf(@PolyInterned Polymorphism this) { + return this; + } + + void testReceiver() { + Polymorphism notInterned = new Polymorphism(); + @Interned Polymorphism interned = null; + + interned = interned.getSelf(); + // :: error: (assignment.type.incompatible) + interned = notInterned.getSelf(); // invalid + } + + // Test assinging interned to PolyInterned + public @PolyInterned String always(@PolyInterned String s) { + if (s.equals("n")) { + // This code type-checkd when the hierarchy contained just @UnknownInterned and + // @Interned, but no longer does because of @InternedDistinct. + // :: error: (return.type.incompatible) + return "m"; + } else { + // :: error: (return.type.incompatible) + return new String("m"); // invalid + } + } + + public static @PolyInterned Object[] id(@PolyInterned Object[] a) { + return a; + } + + public static void idTest(@Interned Object @Interned [] seq) { + @Interned Object[] copy_uninterned = id(seq); + } + + private static Map< + List<@Interned String @Interned []>, + WeakReference<@Interned String @Interned []>> + internedStringSequenceAndIndices; + private static List<@Interned String @Interned []> sai; + private static WeakReference<@Interned String @Interned []> wr; + + public static void testArrayInGeneric() { + internedStringSequenceAndIndices.put(sai, wr); + } + + // check for a crash when using raw types + void processMap(Map map) {} + + void testRaw() { + Map m = null; + // TODO: RAW TYPES WILL EVENTUALLY REQUIRE THAT THERE BOUNDS BE EXACTLY THE QUALIFIER + // EXPECTED. + // :: warning: [unchecked] unchecked method invocation: method processMap in class + // Polymorphism is applied to given types :: warning: [unchecked] unchecked conversion + processMap(m); + } + + // test anonymous classes + private void testAnonymous() { + new Object() { + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object o) { + return true; + } + }.equals(null); + + Date d = new Date() {}; } - } - - public static @PolyInterned Object[] id(@PolyInterned Object[] a) { - return a; - } - - public static void idTest(@Interned Object @Interned [] seq) { - @Interned Object[] copy_uninterned = id(seq); - } - - private static Map< - List<@Interned String @Interned []>, WeakReference<@Interned String @Interned []>> - internedStringSequenceAndIndices; - private static List<@Interned String @Interned []> sai; - private static WeakReference<@Interned String @Interned []> wr; - - public static void testArrayInGeneric() { - internedStringSequenceAndIndices.put(sai, wr); - } - - // check for a crash when using raw types - void processMap(Map map) {} - - void testRaw() { - Map m = null; - // TODO: RAW TYPES WILL EVENTUALLY REQUIRE THAT THERE BOUNDS BE EXACTLY THE QUALIFIER - // EXPECTED. - // :: warning: [unchecked] unchecked method invocation: method processMap in class - // Polymorphism is applied to given types :: warning: [unchecked] unchecked conversion - processMap(m); - } - - // test anonymous classes - private void testAnonymous() { - new Object() { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object o) { - return true; - } - }.equals(null); - - Date d = new Date() {}; - } } diff --git a/checker/tests/interning/PrimitivesInterning.java b/checker/tests/interning/PrimitivesInterning.java index 683637c3410..6f7ab30d1ed 100644 --- a/checker/tests/interning/PrimitivesInterning.java +++ b/checker/tests/interning/PrimitivesInterning.java @@ -1,123 +1,124 @@ +import org.checkerframework.checker.interning.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.interning.qual.*; public class PrimitivesInterning { - void test() { - int a = 3; - - if (a == 3) { - System.out.println("yes"); - } else { - System.out.println("no"); + void test() { + int a = 3; + + if (a == 3) { + System.out.println("yes"); + } else { + System.out.println("no"); + } + + if (a != 2) { + System.out.println("yes"); + } else { + System.out.println("no"); + } + + String name = "Interning"; + if ((name.indexOf('[') == -1) && (name.indexOf('(') == -1)) { + System.out.println("has no open punctuation"); + } else { + System.out.println("has open punctuation"); + } + + Number n = Integer.valueOf(22); + boolean is_double = (n instanceof Double); // valid + + int index = 0; + index = Integer.decode("22"); // valid: auto-unboxing conversion + + // auto-unboxing conversion again + Map m = new HashMap<>(); + if (m.get("hello") == 22) { + System.out.println("hello maps to 22"); + } } - if (a != 2) { - System.out.println("yes"); - } else { - System.out.println("no"); + public static int pow_fast(int base, int expt) throws ArithmeticException { + if (expt < 0) { + throw new ArithmeticException("Negative base passed to pow"); + } + + int this_square_pow = base; + int result = 1; + while (expt > 0) { + if ((expt & 1) != 0) { + result *= this_square_pow; + } + expt >>= 1; + this_square_pow *= this_square_pow; + } + return result; } - String name = "Interning"; - if ((name.indexOf('[') == -1) && (name.indexOf('(') == -1)) { - System.out.println("has no open punctuation"); - } else { - System.out.println("has open punctuation"); + /** Return the greatest common divisor of the two arguments. */ + public static int gcd(int a, int b) { + + // Euclid's method + if (b == 0) { + return (Math.abs(a)); + } + a = Math.abs(a); + b = Math.abs(b); + while (b != 0) { + int tmp = b; + b = a % b; + a = tmp; + } + return a; } - Number n = Integer.valueOf(22); - boolean is_double = (n instanceof Double); // valid - - int index = 0; - index = Integer.decode("22"); // valid: auto-unboxing conversion - - // auto-unboxing conversion again - Map m = new HashMap<>(); - if (m.get("hello") == 22) { - System.out.println("hello maps to 22"); + /** Return the greatest common divisor of the elements of int array a. */ + public static int gcd(int[] a) { + // Euclid's method + if (a.length == 0) { + return 0; + } + int result = a[0]; + for (int i = 1; i < a.length; i++) { + result = gcd(a[i], result); + if ((result == 1) || (result == 0)) { + return result; + } + } + return result; } - } - public static int pow_fast(int base, int expt) throws ArithmeticException { - if (expt < 0) { - throw new ArithmeticException("Negative base passed to pow"); + /** + * Return the gcd (greatest common divisor) of the differences between the elements of int array + * a. + */ + public static int gcd_differences(int[] a) { + // Euclid's method + if (a.length < 2) { + return 0; + } + int result = a[1] - a[0]; + for (int i = 2; i < a.length; i++) { + result = gcd(a[i] - a[i - 1], result); + if ((result == 1) || (result == 0)) { + return result; + } + } + return result; } - int this_square_pow = base; - int result = 1; - while (expt > 0) { - if ((expt & 1) != 0) { - result *= this_square_pow; - } - expt >>= 1; - this_square_pow *= this_square_pow; + void compounds() { + int res = 0; + res += 5; + res /= 9; } - return result; - } - /** Return the greatest common divisor of the two arguments. */ - public static int gcd(int a, int b) { - - // Euclid's method - if (b == 0) { - return (Math.abs(a)); - } - a = Math.abs(a); - b = Math.abs(b); - while (b != 0) { - int tmp = b; - b = a % b; - a = tmp; - } - return a; - } - - /** Return the greatest common divisor of the elements of int array a. */ - public static int gcd(int[] a) { - // Euclid's method - if (a.length == 0) { - return 0; - } - int result = a[0]; - for (int i = 1; i < a.length; i++) { - result = gcd(a[i], result); - if ((result == 1) || (result == 0)) { - return result; - } - } - return result; - } - - /** - * Return the gcd (greatest common divisor) of the differences between the elements of int array - * a. - */ - public static int gcd_differences(int[] a) { - // Euclid's method - if (a.length < 2) { - return 0; - } - int result = a[1] - a[0]; - for (int i = 2; i < a.length; i++) { - result = gcd(a[i] - a[i - 1], result); - if ((result == 1) || (result == 0)) { - return result; - } - } - return result; - } - - void compounds() { - int res = 0; - res += 5; - res /= 9; - } - - // TODO: enable after boxing is improved in AST creation - // void negation() { - // Boolean t = new Boolean(true); - // boolean b = !t; - // } + // TODO: enable after boxing is improved in AST creation + // void negation() { + // Boolean t = new Boolean(true); + // boolean b = !t; + // } } diff --git a/checker/tests/interning/Raw3.java b/checker/tests/interning/Raw3.java index ac3850f65e4..509bd3e295e 100644 --- a/checker/tests/interning/Raw3.java +++ b/checker/tests/interning/Raw3.java @@ -1,6 +1,7 @@ +import org.checkerframework.checker.interning.qual.*; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.interning.qual.*; /* * TODO: Make diamond cleverer: @@ -11,75 +12,75 @@ */ public class Raw3 { - // We would like behavior that is as similar as possible between the - // versions with no raw types and those with raw types. - - // no raw types - List foo1() { - List sl = new ArrayList<>(); - return (List) sl; - } - - // with raw types - List foo2() { - List sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } - - // no raw types - List foo3() { - List<@Interned String> sl = new ArrayList<>(); - // :: error: (return.type.incompatible) - return (List<@Interned String>) sl; - } + // We would like behavior that is as similar as possible between the + // versions with no raw types and those with raw types. - // with raw types - List foo4() { - List<@Interned String> sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } + // no raw types + List foo1() { + List sl = new ArrayList<>(); + return (List) sl; + } - // no raw types - List<@Interned String> foo5() { - List sl = new ArrayList<>(); - // :: error: (return.type.incompatible) - return (List) sl; - } + // with raw types + List foo2() { + List sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } - // with raw types - List<@Interned String> foo6() { - List sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; - } + // no raw types + List foo3() { + List<@Interned String> sl = new ArrayList<>(); + // :: error: (return.type.incompatible) + return (List<@Interned String>) sl; + } - class TestList { - List bar1() { - List sl = new ArrayList<>(); - return (List) sl; + // with raw types + List foo4() { + List<@Interned String> sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; } - List bar2() { - List sl = new ArrayList<>(); - // :: warning: [unchecked] unchecked conversion - return (List) sl; + // no raw types + List<@Interned String> foo5() { + List sl = new ArrayList<>(); + // :: error: (return.type.incompatible) + return (List) sl; } - List bar3(List sl) { - // :: warning: [unchecked] unchecked conversion - return (List) sl; + // with raw types + List<@Interned String> foo6() { + List sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; } - class DuoList extends ArrayList {} + class TestList { + List bar1() { + List sl = new ArrayList<>(); + return (List) sl; + } + + List bar2() { + List sl = new ArrayList<>(); + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } + + List bar3(List sl) { + // :: warning: [unchecked] unchecked conversion + return (List) sl; + } + + class DuoList extends ArrayList {} - List bar4(List sl) { - // This line was previously failing because we couldn't adequately infer the type of - // DuoList as a List; it works now, though the future checking of rawtypes may be more - // strict. - // :: warning: [unchecked] unchecked conversion - return (DuoList) sl; + List bar4(List sl) { + // This line was previously failing because we couldn't adequately infer the type of + // DuoList as a List; it works now, though the future checking of rawtypes may be more + // strict. + // :: warning: [unchecked] unchecked conversion + return (DuoList) sl; + } } - } } diff --git a/checker/tests/interning/RecursiveClass.java b/checker/tests/interning/RecursiveClass.java index b100994bbe8..6bd2974c418 100644 --- a/checker/tests/interning/RecursiveClass.java +++ b/checker/tests/interning/RecursiveClass.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.interning.qual.Interned; public @Interned class RecursiveClass< - T extends RecursiveClass, F extends RecursiveClass> { - static @Interned class InternedClass {} + T extends RecursiveClass, F extends RecursiveClass> { + static @Interned class InternedClass {} - static class Generic {} + static class Generic {} - static @Interned class RecursiveClass2> {} + static @Interned class RecursiveClass2> {} } diff --git a/checker/tests/interning/SequenceAndIndices.java b/checker/tests/interning/SequenceAndIndices.java index e5c4e3b39d4..5a3838be660 100644 --- a/checker/tests/interning/SequenceAndIndices.java +++ b/checker/tests/interning/SequenceAndIndices.java @@ -7,47 +7,47 @@ * subsequences on the same sequence. */ public final class SequenceAndIndices { - public T seq; - public int start; - public int end; + public T seq; + public int start; + public int end; - /** - * Create a SequenceAndIndices. - * - * @param seqpar an interned array - */ - public SequenceAndIndices(T seqpar, int start, int end) { - this.seq = seqpar; - this.start = start; - this.end = end; - // assert isInterned(seq); - } + /** + * Create a SequenceAndIndices. + * + * @param seqpar an interned array + */ + public SequenceAndIndices(T seqpar, int start, int end) { + this.seq = seqpar; + this.start = start; + this.end = end; + // assert isInterned(seq); + } - @SuppressWarnings("unchecked") - @Pure - public boolean equals(Object other) { - if (other instanceof SequenceAndIndices) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - return equals((SequenceAndIndices) other); // unchecked - } else { - return false; + @SuppressWarnings("unchecked") + @Pure + public boolean equals(Object other) { + if (other instanceof SequenceAndIndices) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + return equals((SequenceAndIndices) other); // unchecked + } else { + return false; + } } - } - public boolean equals(SequenceAndIndices other) { - return (this.seq == other.seq) && this.start == other.start && this.end == other.end; - } + public boolean equals(SequenceAndIndices other) { + return (this.seq == other.seq) && this.start == other.start && this.end == other.end; + } - @Pure - public int hashCode() { - return seq.hashCode() + start * 30 - end * 2; - } + @Pure + public int hashCode() { + return seq.hashCode() + start * 30 - end * 2; + } - // For debugging - @Pure - public String toString() { - // return "SAI(" + start + "," + end + ") from: " + ArraysMDE.toString(seq); - return "SAI(" + start + "," + end + ") from: " + seq; - } + // For debugging + @Pure + public String toString() { + // return "SAI(" + start + "," + end + ") from: " + ArraysMDE.toString(seq); + return "SAI(" + start + "," + end + ") from: " + seq; + } } diff --git a/checker/tests/interning/StaticInternMethod.java b/checker/tests/interning/StaticInternMethod.java index a270256f7ec..07efcc538f6 100644 --- a/checker/tests/interning/StaticInternMethod.java +++ b/checker/tests/interning/StaticInternMethod.java @@ -1,29 +1,30 @@ +import org.checkerframework.checker.interning.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.interning.qual.*; public class StaticInternMethod { - private static Map pool = new HashMap<>(); + private static Map pool = new HashMap<>(); - @SuppressWarnings("interning") - public static @Interned Foo intern(Integer i) { - if (pool.containsKey(i)) { - return pool.get(i); - } + @SuppressWarnings("interning") + public static @Interned Foo intern(Integer i) { + if (pool.containsKey(i)) { + return pool.get(i); + } - @Interned Foo f = new @Interned Foo(i); - pool.put(i, f); - return f; - } + @Interned Foo f = new @Interned Foo(i); + pool.put(i, f); + return f; + } - static class Foo { - public Foo(Integer i) {} - } + static class Foo { + public Foo(Integer i) {} + } - void test() { - Integer i = 0; - Foo f = new Foo(i); - @Interned Foo g = intern(i); - } + void test() { + Integer i = 0; + Foo f = new Foo(i); + @Interned Foo g = intern(i); + } } diff --git a/checker/tests/interning/StringIntern.java b/checker/tests/interning/StringIntern.java index e5e840fd33b..0085d3f0d15 100644 --- a/checker/tests/interning/StringIntern.java +++ b/checker/tests/interning/StringIntern.java @@ -1,65 +1,66 @@ +import org.checkerframework.checker.interning.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.interning.qual.*; public class StringIntern { - // It would be very handy (and would eliminate quite a few annotations) - // if any final variable that is initialized to something interned - // (essentially, to a literal) were treated as implicitly @Interned. - final String finalStringInitializedToInterned = "foo"; // implicitly @Interned - final String finalString2 = new String("foo"); - static final String finalStringStatic1 = "foo"; // implicitly @Interned - static final String finalStringStatic2 = new String("foo"); + // It would be very handy (and would eliminate quite a few annotations) + // if any final variable that is initialized to something interned + // (essentially, to a literal) were treated as implicitly @Interned. + final String finalStringInitializedToInterned = "foo"; // implicitly @Interned + final String finalString2 = new String("foo"); + static final String finalStringStatic1 = "foo"; // implicitly @Interned + static final String finalStringStatic2 = new String("foo"); - static class HasFields { - static final String finalStringStatic3 = "foo"; // implicitly @Interned - static final String finalStringStatic4 = new String("foo"); - } + static class HasFields { + static final String finalStringStatic3 = "foo"; // implicitly @Interned + static final String finalStringStatic4 = new String("foo"); + } - static class Foo { - private static Map pool = new HashMap<>(); + static class Foo { + private static Map pool = new HashMap<>(); - @SuppressWarnings("interning") - public @Interned Foo intern() { - if (!pool.containsKey(this)) { - pool.put(this, (@Interned Foo) this); - } - return pool.get(this); + @SuppressWarnings("interning") + public @Interned Foo intern() { + if (!pool.containsKey(this)) { + pool.put(this, (@Interned Foo) this); + } + return pool.get(this); + } } - } - // Another example of the "final initialized to interned" rule - final Foo finalFooInitializedToInterned = new Foo().intern(); + // Another example of the "final initialized to interned" rule + final Foo finalFooInitializedToInterned = new Foo().intern(); - public void test(@Interned String arg) { - String notInternedStr = new String("foo"); - @Interned String internedStr = notInternedStr.intern(); - internedStr = finalStringInitializedToInterned; // OK - // :: error: (assignment.type.incompatible) - internedStr = finalString2; // error - // :: error: (assignment.type.incompatible) - @Interned Foo internedFoo = finalFooInitializedToInterned; - if (arg == finalStringStatic1) {} // OK - // :: error: (not.interned) - if (arg == finalStringStatic2) {} // error - if (arg == HasFields.finalStringStatic3) {} // OK - // :: error: (not.interned) - if (arg == HasFields.finalStringStatic4) {} // error - } + public void test(@Interned String arg) { + String notInternedStr = new String("foo"); + @Interned String internedStr = notInternedStr.intern(); + internedStr = finalStringInitializedToInterned; // OK + // :: error: (assignment.type.incompatible) + internedStr = finalString2; // error + // :: error: (assignment.type.incompatible) + @Interned Foo internedFoo = finalFooInitializedToInterned; + if (arg == finalStringStatic1) {} // OK + // :: error: (not.interned) + if (arg == finalStringStatic2) {} // error + if (arg == HasFields.finalStringStatic3) {} // OK + // :: error: (not.interned) + if (arg == HasFields.finalStringStatic4) {} // error + } - private @Interned String base; - static final String BASE_HASHCODE = "hashcode"; + private @Interned String base; + static final String BASE_HASHCODE = "hashcode"; - public void foo() { - if (base == BASE_HASHCODE) {} - } + public void foo() { + if (base == BASE_HASHCODE) {} + } - public @Interned String emptyString(boolean b) { - if (b) { - return ""; - } else { - return (""); + public @Interned String emptyString(boolean b) { + if (b) { + return ""; + } else { + return (""); + } } - } } diff --git a/checker/tests/interning/Subclass.java b/checker/tests/interning/Subclass.java index 8fe2a85c4aa..12435c3e717 100644 --- a/checker/tests/interning/Subclass.java +++ b/checker/tests/interning/Subclass.java @@ -4,8 +4,8 @@ public abstract class Subclass implements Comparable // note non-generic { - @Pure - public int compareTo(Subclass other) { - return 0; - } + @Pure + public int compareTo(Subclass other) { + return 0; + } } diff --git a/checker/tests/interning/SuppressWarningsClass.java b/checker/tests/interning/SuppressWarningsClass.java index 30a788dda69..d736aef4d79 100644 --- a/checker/tests/interning/SuppressWarningsClass.java +++ b/checker/tests/interning/SuppressWarningsClass.java @@ -3,8 +3,8 @@ @SuppressWarnings("interning") public class SuppressWarningsClass { - public static void myMethod() { + public static void myMethod() { - @Interned String s = new String(); - } + @Interned String s = new String(); + } } diff --git a/checker/tests/interning/SuppressWarningsVar.java b/checker/tests/interning/SuppressWarningsVar.java index 7aa4b6c7965..d26ba8f4ffb 100644 --- a/checker/tests/interning/SuppressWarningsVar.java +++ b/checker/tests/interning/SuppressWarningsVar.java @@ -2,9 +2,9 @@ public class SuppressWarningsVar { - public static void myMethod() { + public static void myMethod() { - @SuppressWarnings("interning") - @Interned String s = new String(); - } + @SuppressWarnings("interning") + @Interned String s = new String(); + } } diff --git a/checker/tests/interning/TVWCSuper.java b/checker/tests/interning/TVWCSuper.java index d3a6415a10a..896bf0e9983 100644 --- a/checker/tests/interning/TVWCSuper.java +++ b/checker/tests/interning/TVWCSuper.java @@ -1,5 +1,5 @@ public class TVWCSuper { - class L {} + class L {} - public static > void sort(L t) {} + public static > void sort(L t) {} } diff --git a/checker/tests/interning/TestExtSup.java b/checker/tests/interning/TestExtSup.java index c05555d7d75..85e20d74829 100644 --- a/checker/tests/interning/TestExtSup.java +++ b/checker/tests/interning/TestExtSup.java @@ -3,14 +3,14 @@ import java.util.List; interface A { - public abstract int transform(List function); + public abstract int transform(List function); } class B implements A { - @Override - public int transform(List function) { - return 0; - } + @Override + public int transform(List function) { + return 0; + } } public class TestExtSup {} diff --git a/checker/tests/interning/TestInfer.java b/checker/tests/interning/TestInfer.java index e08103d0e1f..16d5efed497 100644 --- a/checker/tests/interning/TestInfer.java +++ b/checker/tests/interning/TestInfer.java @@ -4,27 +4,27 @@ import java.util.List; class TestInfer1 { - T getValue(List l) { - return l.get(0); - } + T getValue(List l) { + return l.get(0); + } - void bar(Object o) {} + void bar(Object o) {} - void foo() { - List ls = new ArrayList<>(); - bar(getValue(ls)); - } + void foo() { + List ls = new ArrayList<>(); + bar(getValue(ls)); + } } class TestInfer2 { - T getValue(List l) { - return l.get(0); - } + T getValue(List l) { + return l.get(0); + } - void bar(String o) {} + void bar(String o) {} - void foo() { - List ls = new ArrayList<>(); - bar(getValue(ls)); - } + void foo() { + List ls = new ArrayList<>(); + bar(getValue(ls)); + } } diff --git a/checker/tests/interning/ThreadUsesObjectEquals.java b/checker/tests/interning/ThreadUsesObjectEquals.java index ee96770e620..e494d1fd364 100644 --- a/checker/tests/interning/ThreadUsesObjectEquals.java +++ b/checker/tests/interning/ThreadUsesObjectEquals.java @@ -1,5 +1,5 @@ public class ThreadUsesObjectEquals { - boolean p(Thread a, Thread b) { - return a == b; - } + boolean p(Thread a, Thread b) { + return a == b; + } } diff --git a/checker/tests/interning/TypeVarPrimitivesInterning.java b/checker/tests/interning/TypeVarPrimitivesInterning.java index 497fbbdf5ff..1c43fc52bec 100644 --- a/checker/tests/interning/TypeVarPrimitivesInterning.java +++ b/checker/tests/interning/TypeVarPrimitivesInterning.java @@ -3,19 +3,20 @@ import org.checkerframework.checker.interning.qual.*; public class TypeVarPrimitivesInterning { - void method(T tLong) { - long l = tLong; - } + void method(T tLong) { + long l = tLong; + } - void methodIntersection(T tLong) { - long l = tLong; - } + void methodIntersection( + T tLong) { + long l = tLong; + } - void method2(T tLong) { - long l = tLong; - } + void method2(T tLong) { + long l = tLong; + } - void methodIntersection2(T tLong) { - long l = tLong; - } + void methodIntersection2(T tLong) { + long l = tLong; + } } diff --git a/checker/tests/interning/UnboxUninterned.java b/checker/tests/interning/UnboxUninterned.java index 46490d01a7a..324f46bd8fd 100644 --- a/checker/tests/interning/UnboxUninterned.java +++ b/checker/tests/interning/UnboxUninterned.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.interning.qual.*; public class UnboxUninterned { - void negation() { - Boolean t = Boolean.valueOf(true); - boolean b1 = !t.booleanValue(); - boolean b2 = !t; + void negation() { + Boolean t = Boolean.valueOf(true); + boolean b1 = !t.booleanValue(); + boolean b2 = !t; - Integer x = Integer.valueOf(222222); - int i1 = -x.intValue(); - int i2 = -x; - } + Integer x = Integer.valueOf(222222); + int i1 = -x.intValue(); + int i2 = -x; + } } diff --git a/checker/tests/interning/UsesObjectEqualsTest.java b/checker/tests/interning/UsesObjectEqualsTest.java index 8a713ad9ce4..d878062678b 100644 --- a/checker/tests/interning/UsesObjectEqualsTest.java +++ b/checker/tests/interning/UsesObjectEqualsTest.java @@ -1,94 +1,95 @@ -import java.util.LinkedList; -import java.util.prefs.*; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.checker.interning.qual.UsesObjectEquals; +import java.util.LinkedList; +import java.util.prefs.*; + public class UsesObjectEqualsTest { - public @UsesObjectEquals class A { - public A() {} - } + public @UsesObjectEquals class A { + public A() {} + } - @UsesObjectEquals - class B extends A {} + @UsesObjectEquals + class B extends A {} - // :: error: (overrides.equals) - class B2 extends A { - @Override - public boolean equals(Object o) { - return super.equals(o); + // :: error: (overrides.equals) + class B2 extends A { + @Override + public boolean equals(Object o) { + return super.equals(o); + } } - } - @UsesObjectEquals - class B3 extends A { - @Override - public boolean equals(Object o3) { - return this == o3; + @UsesObjectEquals + class B3 extends A { + @Override + public boolean equals(Object o3) { + return this == o3; + } } - } - @UsesObjectEquals - class B4 extends A { - @Override - public boolean equals(Object o4) { - return o4 == this; + @UsesObjectEquals + class B4 extends A { + @Override + public boolean equals(Object o4) { + return o4 == this; + } } - } - // changed to inherited, no (superclass.annotated) warning - class C extends A {} + // changed to inherited, no (superclass.annotated) warning + class C extends A {} - class D {} + class D {} - @UsesObjectEquals - // :: error: (superclass.notannotated) - class E extends D {} + @UsesObjectEquals + // :: error: (superclass.notannotated) + class E extends D {} - @UsesObjectEquals - // :: error: (overrides.equals) - class TestEquals { + @UsesObjectEquals + // :: error: (overrides.equals) + class TestEquals { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(Object o) { - return true; + @org.checkerframework.dataflow.qual.Pure + public boolean equals(Object o) { + return true; + } } - } - - class TestComparison { - - public void comp(@Interned Object o, A a1, A a2) { - if (a1 == a2) { - System.out.println("one"); - } - if (a1 == o) { - System.out.println("two"); - } - if (o == a1) { - System.out.println("three"); - } + + class TestComparison { + + public void comp(@Interned Object o, A a1, A a2) { + if (a1 == a2) { + System.out.println("one"); + } + if (a1 == o) { + System.out.println("two"); + } + if (o == a1) { + System.out.println("three"); + } + } } - } - @UsesObjectEquals - class ExtendsInner1 extends UsesObjectEqualsTest.A {} + @UsesObjectEquals + class ExtendsInner1 extends UsesObjectEqualsTest.A {} - class ExtendsInner2 extends UsesObjectEqualsTest.A {} + class ExtendsInner2 extends UsesObjectEqualsTest.A {} - class MyList extends LinkedList {} + class MyList extends LinkedList {} - class DoesNotUseObjectEquals { - @Override - public boolean equals(Object o) { - return super.equals(o); + class DoesNotUseObjectEquals { + @Override + public boolean equals(Object o) { + return super.equals(o); + } } - } - @UsesObjectEquals - class SubclassUsesObjectEquals extends DoesNotUseObjectEquals { - @Override - public boolean equals(Object o) { - return this == o; + @UsesObjectEquals + class SubclassUsesObjectEquals extends DoesNotUseObjectEquals { + @Override + public boolean equals(Object o) { + return this == o; + } } - } } diff --git a/checker/tests/lock-records/LockRecord.java b/checker/tests/lock-records/LockRecord.java index c9f179e6bfa..86cbe1cbedf 100644 --- a/checker/tests/lock-records/LockRecord.java +++ b/checker/tests/lock-records/LockRecord.java @@ -1,11 +1,12 @@ -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.LockingFree; +import java.util.concurrent.locks.ReentrantLock; + public record LockRecord(String s, ReentrantLock lock) { - @LockingFree - // :: error: (method.guarantee.violated) - public LockRecord { + @LockingFree // :: error: (method.guarantee.violated) - lock.lock(); - } + public LockRecord { + // :: error: (method.guarantee.violated) + lock.lock(); + } } diff --git a/checker/tests/lock-safedefaults/BasicLockTest.java b/checker/tests/lock-safedefaults/BasicLockTest.java index b1f33fda921..72816357286 100644 --- a/checker/tests/lock-safedefaults/BasicLockTest.java +++ b/checker/tests/lock-safedefaults/BasicLockTest.java @@ -1,103 +1,104 @@ -import java.util.concurrent.locks.*; import org.checkerframework.checker.lock.qual.*; import org.checkerframework.framework.qual.AnnotatedFor; +import java.util.concurrent.locks.*; + public class BasicLockTest { - class MyClass { - public Object field; - } - - Object someValue = new Object(); - - MyClass newMyClass = new MyClass(); - - MyClass myUnannotatedMethod(MyClass param) { - return param; - } - - void myUnannotatedMethod2() {} - - @AnnotatedFor("lock") - MyClass myAnnotatedMethod(MyClass param) { - return param; - } - - @AnnotatedFor("lock") - void myAnnotatedMethod2() {} - - final @GuardedBy({}) ReentrantLock lockField = new ReentrantLock(); - - @GuardedBy("lockField") MyClass m; - - @GuardedBy({}) MyClass o1 = new MyClass(), p1; - - @AnnotatedFor("lock") - @MayReleaseLocks - void testFields() { - // Test in two ways that return values are @GuardedByUnknown. - // The first way is more durable as cannot.dereference is tied specifically to - // @GuardedByUnknown (and @GuardedByBottom, but it is unlikely to become the default for - // return values on unannotated methods). - // :: error: (lock.not.held) :: error: (argument.type.incompatible) - myUnannotatedMethod(o1).field = someValue; - // The second way is less durable because the default for fields is currently @GuardedBy({}) - // but could be changed to @GuardedByUnknown. - // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) - p1 = myUnannotatedMethod(o1); - - // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks - lockField.lock(); - myAnnotatedMethod2(); - m.field = someValue; - myUnannotatedMethod2(); - // :: error: (lock.not.held) - m.field = someValue; - } - - void unannotatedReleaseLock(ReentrantLock lock) { - lock.unlock(); - } - - @AnnotatedFor("lock") - @MayReleaseLocks - void testLocalVariables1() { - MyClass o2 = new MyClass(), p2; - // :: error: (argument.type.incompatible) - p2 = myUnannotatedMethod(o2); - MyClass o3 = new MyClass(); - myAnnotatedMethod(o3); - } - - @AnnotatedFor("lock") - @MayReleaseLocks - void testLocalVariables2() { - // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks - final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); - @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement - @GuardedBy("lock") MyClass q = newMyClass; - lock.lock(); - myAnnotatedMethod2(); - q.field = someValue; - // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local - // variable lock. - myUnannotatedMethod2(); - // :: error: (lock.not.held) - q.field = someValue; - } - - @AnnotatedFor("lock") - @MayReleaseLocks - void testLocalVariables3() { - // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks - final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); - @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement - @GuardedBy("lock") MyClass q = newMyClass; - lock.lock(); - // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local - // variable lock. - // :: error: (argument.type.incompatible) - unannotatedReleaseLock(lock); - // :: error: (lock.not.held) - q.field = someValue; - } + class MyClass { + public Object field; + } + + Object someValue = new Object(); + + MyClass newMyClass = new MyClass(); + + MyClass myUnannotatedMethod(MyClass param) { + return param; + } + + void myUnannotatedMethod2() {} + + @AnnotatedFor("lock") + MyClass myAnnotatedMethod(MyClass param) { + return param; + } + + @AnnotatedFor("lock") + void myAnnotatedMethod2() {} + + final @GuardedBy({}) ReentrantLock lockField = new ReentrantLock(); + + @GuardedBy("lockField") MyClass m; + + @GuardedBy({}) MyClass o1 = new MyClass(), p1; + + @AnnotatedFor("lock") + @MayReleaseLocks + void testFields() { + // Test in two ways that return values are @GuardedByUnknown. + // The first way is more durable as cannot.dereference is tied specifically to + // @GuardedByUnknown (and @GuardedByBottom, but it is unlikely to become the default for + // return values on unannotated methods). + // :: error: (lock.not.held) :: error: (argument.type.incompatible) + myUnannotatedMethod(o1).field = someValue; + // The second way is less durable because the default for fields is currently @GuardedBy({}) + // but could be changed to @GuardedByUnknown. + // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) + p1 = myUnannotatedMethod(o1); + + // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks + lockField.lock(); + myAnnotatedMethod2(); + m.field = someValue; + myUnannotatedMethod2(); + // :: error: (lock.not.held) + m.field = someValue; + } + + void unannotatedReleaseLock(ReentrantLock lock) { + lock.unlock(); + } + + @AnnotatedFor("lock") + @MayReleaseLocks + void testLocalVariables1() { + MyClass o2 = new MyClass(), p2; + // :: error: (argument.type.incompatible) + p2 = myUnannotatedMethod(o2); + MyClass o3 = new MyClass(); + myAnnotatedMethod(o3); + } + + @AnnotatedFor("lock") + @MayReleaseLocks + void testLocalVariables2() { + // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks + final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); + @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement + @GuardedBy("lock") MyClass q = newMyClass; + lock.lock(); + myAnnotatedMethod2(); + q.field = someValue; + // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local + // variable lock. + myUnannotatedMethod2(); + // :: error: (lock.not.held) + q.field = someValue; + } + + @AnnotatedFor("lock") + @MayReleaseLocks + void testLocalVariables3() { + // Now test that an unannotated method behaves as if it's annotated with @MayReleaseLocks + final @GuardedBy({}) ReentrantLock lock = new ReentrantLock(); + @SuppressWarnings("lock:assignment") // prevents flow-sensitive type refinement + @GuardedBy("lock") MyClass q = newMyClass; + lock.lock(); + // Should behave as @MayReleaseLocks, and *should* reset @LockHeld assumption about local + // variable lock. + // :: error: (argument.type.incompatible) + unannotatedReleaseLock(lock); + // :: error: (lock.not.held) + q.field = someValue; + } } diff --git a/checker/tests/lock/ChapterExamples.java b/checker/tests/lock/ChapterExamples.java index e4eadc52d10..f06c1797da6 100644 --- a/checker/tests/lock/ChapterExamples.java +++ b/checker/tests/lock/ChapterExamples.java @@ -1,12 +1,6 @@ // This test contains the sample code from the Lock Checker manual chapter modified to fit testing // instead of illustrative purposes, and contains other miscellaneous Lock Checker testing. -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByBottom; @@ -17,579 +11,586 @@ import org.checkerframework.checker.lock.qual.ReleasesNoLocks; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.locks.ReentrantLock; + public class ChapterExamples { - // This code crashed when there was a bug before issue 524 was fixed. - // An attempt to take the LUB between 'val' in the store with type 'long' - // and 'val' in another store with type 'none' resulted in a crash. - private void foo(boolean b, int a) { - if (b) { - if (a == 0) { - boolean val = false; - } else if (a == 1) { - int val = 0; - } else if (a == 2) { - long val = 0; - } else if (a == 3) { - } - } else { - if (true) {} + // This code crashed when there was a bug before issue 524 was fixed. + // An attempt to take the LUB between 'val' in the store with type 'long' + // and 'val' in another store with type 'none' resulted in a crash. + private void foo(boolean b, int a) { + if (b) { + if (a == 0) { + boolean val = false; + } else if (a == 1) { + int val = 0; + } else if (a == 2) { + long val = 0; + } else if (a == 3) { + } + } else { + if (true) {} + } } - } - private abstract class Values extends AbstractCollection { - @SuppressWarnings("method.guarantee.violated") // side effect is only to local iterator - public T[] toArray(T[] a) { - Collection c = new ArrayList(size()); - for (Iterator i = iterator(); i.hasNext(); ) { - c.add(i.next()); - } - return c.toArray(a); + private abstract class Values extends AbstractCollection { + @SuppressWarnings("method.guarantee.violated") // side effect is only to local iterator + public T[] toArray(T[] a) { + Collection c = new ArrayList(size()); + for (Iterator i = iterator(); i.hasNext(); ) { + c.add(i.next()); + } + return c.toArray(a); + } } - } - // @GuardedByBottom, which represents the 'null' literal, is the default lower bound, - // so null can be returned in the following two methods: - T method1(T t, boolean b) { - return b ? null : t; - } + // @GuardedByBottom, which represents the 'null' literal, is the default lower bound, + // so null can be returned in the following two methods: + T method1(T t, boolean b) { + return b ? null : t; + } - T method2(T t, boolean b) { - return null; - } + T method2(T t, boolean b) { + return null; + } - void bar(@NonNull Object nn1, boolean b) { - @NonNull Object nn2 = method1(nn1, b); - @NonNull Object nn3 = method2(nn1, b); - } + void bar(@NonNull Object nn1, boolean b) { + @NonNull Object nn2 = method1(nn1, b); + @NonNull Object nn3 = method2(nn1, b); + } - void bar2(@GuardedByBottom Object bottomParam, boolean b) { - @GuardedByUnknown Object refinedToBottom1 = method1(bottomParam, b); - @GuardedByUnknown Object refinedToBottom2 = method2(bottomParam, b); - @GuardedByBottom Object bottom1 = method1(bottomParam, b); - @GuardedByBottom Object bottom2 = method2(bottomParam, b); - } + void bar2(@GuardedByBottom Object bottomParam, boolean b) { + @GuardedByUnknown Object refinedToBottom1 = method1(bottomParam, b); + @GuardedByUnknown Object refinedToBottom2 = method2(bottomParam, b); + @GuardedByBottom Object bottom1 = method1(bottomParam, b); + @GuardedByBottom Object bottom2 = method2(bottomParam, b); + } - private static boolean eq(@GuardSatisfied Object o1, @GuardSatisfied Object o2) { - return (o1 == null ? o2 == null : o1.equals(o2)); - } + private static boolean eq(@GuardSatisfied Object o1, @GuardSatisfied Object o2) { + return (o1 == null ? o2 == null : o1.equals(o2)); + } - public void put( - K key, V value) { - @SuppressWarnings("unchecked") - K k = (K) maskNull(key); - } + public void put( + K key, V value) { + @SuppressWarnings("unchecked") + K k = (K) maskNull(key); + } - class GuardedByUnknownTest { + class GuardedByUnknownTest { - T m; + T m; - void test() { - // :: error: (method.invocation.invalid) - m.method(); + void test() { + // :: error: (method.invocation.invalid) + m.method(); - @GuardedByUnknown MyClass local = new @GuardedByUnknown MyClass(); - // :: error: (lock.not.held) - local.field = new Object(); - // :: error: (method.invocation.invalid) - local.method(); + @GuardedByUnknown MyClass local = new @GuardedByUnknown MyClass(); + // :: error: (lock.not.held) + local.field = new Object(); + // :: error: (method.invocation.invalid) + local.method(); - // :: error: (lock.not.held) - m.field = new Object(); + // :: error: (lock.not.held) + m.field = new Object(); + } } - } - class MyClass { - Object field = new Object(); + class MyClass { + Object field = new Object(); - @LockingFree - Object method(@GuardSatisfied MyClass this) { - return new Object(); + @LockingFree + Object method(@GuardSatisfied MyClass this) { + return new Object(); + } + + @LockingFree + public @GuardSatisfied(1) MyClass append( + @GuardSatisfied(1) MyClass this, @GuardSatisfied(2) MyClass m) { + return this; + } + + final Object myLock = new Object(); + + void testCallToMethod(@GuardedBy("myLock") MyClass this) { + // :: error: (lock.not.held) + this.method(); // method()'s receiver is annotated as @GuardSatisfied + } + } + + @MayReleaseLocks + @ReleasesNoLocks + // TODO: enable (multiple.sideeffect.annotation) + void testMultipleSideEffectAnnotations() {} + + void guardedByItselfOnReceiver(@GuardedBy("") ChapterExamples this) { + synchronized (this) { // Tests translation of '' to 'this' + // myField = new MyClass(); + myField.toString(); + this.myField = new MyClass(); + this.myField.toString(); + } + // :: error: (lock.not.held) + myField = new MyClass(); + // :: error: (lock.not.held) + myField.toString(); + // :: error: (lock.not.held) + this.myField = new MyClass(); + // :: error: (lock.not.held) + this.myField.toString(); + } + + void guardedByThisOnReceiver(@GuardedBy("this") ChapterExamples this) { + // :: error: (lock.not.held) + myField = new MyClass(); + // :: error: (lock.not.held) + myField.toString(); + // :: error: (lock.not.held) + this.myField = new MyClass(); + // :: error: (lock.not.held) + this.myField.toString(); + synchronized (this) { + myField = new MyClass(); + myField.toString(); + this.myField = new MyClass(); + this.myField.toString(); + } } + void testDereferenceOfReceiverAndParameter( + @GuardedBy("lock") ChapterExamples this, @GuardedBy("lock") MyClass m) { + // :: error: (lock.not.held) + myField = new MyClass(); + // :: error: (lock.not.held) + myField.toString(); + // :: error: (lock.not.held) + this.myField = new MyClass(); + // :: error: (lock.not.held) + this.myField.toString(); + // :: error: (lock.not.held) + m.field = new Object(); + // :: error: (lock.not.held) + m.field.toString(); + // The following error is due to the fact that you cannot access "this.lock" without first + // having acquired "lock". The right fix in a user scenario would be to not guard "this" + // with "this.lock". The current object could instead be guarded by "" or by some + // other lock expression that is not one of its fields. We are keeping this test case here + // to make sure this scenario issues a warning. + // :: error: (lock.not.held) + synchronized (lock) { + myField = new MyClass(); + myField.toString(); + this.myField = new MyClass(); + this.myField.toString(); + m.field = new Object(); + m.field.toString(); + } + } + + @GuardedBy("lock") MyClass myObj = new MyClass(); + @LockingFree - public @GuardSatisfied(1) MyClass append( - @GuardSatisfied(1) MyClass this, @GuardSatisfied(2) MyClass m) { - return this; - } - - final Object myLock = new Object(); - - void testCallToMethod(@GuardedBy("myLock") MyClass this) { - // :: error: (lock.not.held) - this.method(); // method()'s receiver is annotated as @GuardSatisfied - } - } - - @MayReleaseLocks - @ReleasesNoLocks - // TODO: enable (multiple.sideeffect.annotation) - void testMultipleSideEffectAnnotations() {} - - void guardedByItselfOnReceiver(@GuardedBy("") ChapterExamples this) { - synchronized (this) { // Tests translation of '' to 'this' - // myField = new MyClass(); - myField.toString(); - this.myField = new MyClass(); - this.myField.toString(); - } - // :: error: (lock.not.held) - myField = new MyClass(); - // :: error: (lock.not.held) - myField.toString(); - // :: error: (lock.not.held) - this.myField = new MyClass(); - // :: error: (lock.not.held) - this.myField.toString(); - } - - void guardedByThisOnReceiver(@GuardedBy("this") ChapterExamples this) { - // :: error: (lock.not.held) - myField = new MyClass(); - // :: error: (lock.not.held) - myField.toString(); - // :: error: (lock.not.held) - this.myField = new MyClass(); - // :: error: (lock.not.held) - this.myField.toString(); - synchronized (this) { - myField = new MyClass(); - myField.toString(); - this.myField = new MyClass(); - this.myField.toString(); - } - } - - void testDereferenceOfReceiverAndParameter( - @GuardedBy("lock") ChapterExamples this, @GuardedBy("lock") MyClass m) { - // :: error: (lock.not.held) - myField = new MyClass(); - // :: error: (lock.not.held) - myField.toString(); - // :: error: (lock.not.held) - this.myField = new MyClass(); - // :: error: (lock.not.held) - this.myField.toString(); - // :: error: (lock.not.held) - m.field = new Object(); - // :: error: (lock.not.held) - m.field.toString(); - // The following error is due to the fact that you cannot access "this.lock" without first - // having acquired "lock". The right fix in a user scenario would be to not guard "this" - // with "this.lock". The current object could instead be guarded by "" or by some - // other lock expression that is not one of its fields. We are keeping this test case here - // to make sure this scenario issues a warning. - // :: error: (lock.not.held) - synchronized (lock) { - myField = new MyClass(); - myField.toString(); - this.myField = new MyClass(); - this.myField.toString(); - m.field = new Object(); - m.field.toString(); - } - } - - @GuardedBy("lock") MyClass myObj = new MyClass(); - - @LockingFree - @GuardedBy("lock") MyClass myMethodReturningMyObj() { - return myObj; - } - - ChapterExamples() { - lock = new Object(); - } - - void myMethod8() { - // :: error: (lock.not.held) - boolean b4 = compare(p1, myMethod()); - - // An error is issued indicating that p2 might be dereferenced without - // "lock" being held. The method call need not be modified, since - // @GuardedBy({}) <: @GuardedByUnknown and @GuardedBy("lock") <: @GuardedByUnknown, - // but the lock must be acquired prior to the method call. - // :: error: (lock.not.held) - boolean b2 = compare(p1, p2); - // :: error: (lock.not.held) - boolean b3 = compare(p1, this.p2); - // :: error: (lock.not.held) - boolean b5 = compare(p1, this.myMethod()); - synchronized (lock) { - boolean b6 = compare(p1, p2); // OK - boolean b7 = compare(p1, this.p2); // OK - boolean b8 = compare(p1, myMethod()); // OK - boolean b9 = compare(p1, this.myMethod()); // OK - } - } - - // Keep in mind, the expression itself may or may not be a - // method call. Simple examples of expression.identifier : - // myObject.field - // myMethod().field - // myObject.method() - // myMethod().method() - - void myMethod7() { - // :: error: (lock.not.held) - Object f = myObj.field; - // :: error: (lock.not.held) - Object f2 = myMethodReturningMyObj().field; - // :: error: (lock.not.held) - myObj.method(); // method()'s receiver is annotated as @GuardSatisfied - // :: error: (lock.not.held) - myMethodReturningMyObj().method(); // method()'s receiver is annotated as @GuardSatisfied - - synchronized (lock) { - f = myObj.field; - f2 = myMethodReturningMyObj().field; - myObj.method(); - myMethodReturningMyObj().method(); - } - - // :: error: (lock.not.held) - myMethodReturningMyObj().field = new Object(); - // :: error: (lock.not.held) - x.field = new Object(); - synchronized (lock) { - myMethod().field = new Object(); - } - synchronized (lock) { - x.field = new Object(); // toString is not LockingFree. How annoying. - } - - this.x = new MyClass(); - } - - final Object lock; // Initialized in the constructor - - @GuardedBy("lock") MyClass x = new MyClass(); - - @GuardedBy("lock") MyClass y = x; // OK, because dereferences of y will require "lock" to be held. - - // :: error: (assignment.type.incompatible) - @GuardedBy({}) MyClass z = x; // ILLEGAL because dereferences of z do not require "lock" to be held. - - @LockingFree - @GuardedBy("lock") MyClass myMethod() { - return x; // OK because the return type is @GuardedBy("lock") - } - - void exampleMethod() { - // :: error: (lock.not.held) - x.field = new Object(); // ILLEGAL because the lock is not known to be held - // :: error: (lock.not.held) - y.field = new Object(); // ILLEGAL because the lock is not known to be held - // :: error: (lock.not.held) - myMethod().field = new Object(); // ILLEGAL because the lock is not known to be held - synchronized (lock) { - x.field = new Object(); // OK: the lock is known to be held - y.field = new Object(); // OK: the lock is known to be held - myMethod().field = new Object(); // OK: the lock is known to be held - } - } - - final MyClass a = new MyClass(); - final MyClass b = new MyClass(); - - @GuardedBy("a") MyClass x5 = new MyClass(); - - @GuardedBy({"a", "b"}) MyClass y5 = new MyClass(); - - void myMethod2() { - // :: error: (assignment.type.incompatible) - y5 = x5; // ILLEGAL - } + @GuardedBy("lock") MyClass myMethodReturningMyObj() { + return myObj; + } + + ChapterExamples() { + lock = new Object(); + } - // :: error: (immutable.type.guardedby) - @GuardedBy("a") String s = "string"; + void myMethod8() { + // :: error: (lock.not.held) + boolean b4 = compare(p1, myMethod()); + + // An error is issued indicating that p2 might be dereferenced without + // "lock" being held. The method call need not be modified, since + // @GuardedBy({}) <: @GuardedByUnknown and @GuardedBy("lock") <: @GuardedByUnknown, + // but the lock must be acquired prior to the method call. + // :: error: (lock.not.held) + boolean b2 = compare(p1, p2); + // :: error: (lock.not.held) + boolean b3 = compare(p1, this.p2); + // :: error: (lock.not.held) + boolean b5 = compare(p1, this.myMethod()); + synchronized (lock) { + boolean b6 = compare(p1, p2); // OK + boolean b7 = compare(p1, this.p2); // OK + boolean b8 = compare(p1, myMethod()); // OK + boolean b9 = compare(p1, this.myMethod()); // OK + } + } - @GuardedBy({}) MyClass o1; + // Keep in mind, the expression itself may or may not be a + // method call. Simple examples of expression.identifier : + // myObject.field + // myMethod().field + // myObject.method() + // myMethod().method() + + void myMethod7() { + // :: error: (lock.not.held) + Object f = myObj.field; + // :: error: (lock.not.held) + Object f2 = myMethodReturningMyObj().field; + // :: error: (lock.not.held) + myObj.method(); // method()'s receiver is annotated as @GuardSatisfied + // :: error: (lock.not.held) + myMethodReturningMyObj().method(); // method()'s receiver is annotated as @GuardSatisfied + + synchronized (lock) { + f = myObj.field; + f2 = myMethodReturningMyObj().field; + myObj.method(); + myMethodReturningMyObj().method(); + } + + // :: error: (lock.not.held) + myMethodReturningMyObj().field = new Object(); + // :: error: (lock.not.held) + x.field = new Object(); + synchronized (lock) { + myMethod().field = new Object(); + } + synchronized (lock) { + x.field = new Object(); // toString is not LockingFree. How annoying. + } + + this.x = new MyClass(); + } - @GuardedBy("lock") MyClass o2; + final Object lock; // Initialized in the constructor - @GuardedBy("lock") MyClass o3; + @GuardedBy("lock") MyClass x = new MyClass(); - void someMethod() { - o3 = o2; // OK, since o2 and o3 are guarded by exactly the same lock set. + @GuardedBy("lock") MyClass y = x; // OK, because dereferences of y will require "lock" to be held. // :: error: (assignment.type.incompatible) - o1 = o2; // Assignment type incompatible errors are issued for both assignments, since - // :: error: (assignment.type.incompatible) - o2 = o1; // {"lock"} and {} are not identical sets. - } - - @SuppressWarnings("lock:cast.unsafe") - void someMethod2() { - // A cast can be used if the user knows it is safe to do so. - // However, the @SuppressWarnings must be added. - o1 = (@GuardedBy({}) MyClass) o2; - } - - static final Object myLock = new Object(); - - @GuardedBy("ChapterExamples.myLock") MyClass myMethod3() { - return new MyClass(); - } - - // reassignments without holding the lock are OK. - @GuardedBy("ChapterExamples.myLock") MyClass x2 = myMethod3(); - - @GuardedBy("ChapterExamples.myLock") MyClass y2 = x2; - - void myMethod4() { - // :: error: (lock.not.held) - x2.field = new Object(); // ILLEGAL because the lock is not held - synchronized (ChapterExamples.myLock) { - y2.field = new Object(); // OK: the lock is held - } - } - - void myMethod5(@GuardedBy("ChapterExamples.myLock") MyClass a) { - // :: error: (lock.not.held) - a.field = new Object(); // ILLEGAL: the lock is not held - synchronized (ChapterExamples.myLock) { - a.field = new Object(); // OK: the lock is held - } - } - - @LockingFree - boolean compare(@GuardSatisfied MyClass a, @GuardSatisfied MyClass b) { - return true; - } - - @GuardedBy({}) MyClass p1; - - @GuardedBy("lock") MyClass p2; - - void myMethod6() { - // It is the responsibility of callers to 'compare' to acquire the lock. - synchronized (lock) { - boolean b1 = compare(p1, p2); // OK. No error issued. - } - // :: error: (lock.not.held) - p2.field = new Object(); - // An error is issued indicating that p2 might be dereferenced without "lock" being held. - // The method call need not be modified, since @GuardedBy({}) <: @GuardedByUnknown and - // @GuardedBy("lock") <: @GuardedByUnknown, but the lock must be acquired prior to the - // method call. - // :: error: (lock.not.held) - boolean b2 = compare(p1, p2); - } - - void helper1(@GuardedBy("ChapterExamples.myLock") MyClass a) { - // :: error: (lock.not.held) - a.field = new Object(); // ILLEGAL: the lock is not held - synchronized (ChapterExamples.myLock) { - a.field = new Object(); // OK: the lock is held - } - } - - @Holding("ChapterExamples.myLock") - @LockingFree - void helper2(@GuardedBy("ChapterExamples.myLock") MyClass b) { - b.field = new Object(); // OK: the lock is held - } - - @LockingFree - void helper3(@GuardSatisfied MyClass c) { - c.field = new Object(); // OK: the guard is satisfied - } - - @LockingFree - void helper4(@GuardedBy("ChapterExamples.myLock") MyClass d) { - // :: error: (lock.not.held) - d.field = new Object(); // ILLEGAL: the lock is not held - } - - @ReleasesNoLocks - void helper5() {} - - // No annotation means @ReleasesNoLocks - void helper6() {} - - void myMethod2(@GuardedBy("ChapterExamples.myLock") MyClass e) { - helper1(e); // OK to pass to another routine without holding the lock. - // :: error: (lock.not.held) - e.field = new Object(); // ILLEGAL: the lock is not held - // :: error: (contracts.precondition.not.satisfied) - helper2(e); - // :: error: (lock.not.held) - helper3(e); - synchronized (ChapterExamples.myLock) { - helper2(e); - helper3(e); // OK, since parameter is @GuardSatisfied - helper4(e); // OK, but helper4's body still has an error. - helper5(); - helper6(); - helper2(e); // Can still be called after helper5() and helper6() - } - } - - private @GuardedBy({}) MyClass myField; - - int someInt = 1; - - // TODO: For now, boxed types are treated as primitive types. This may change in the future. - @SuppressWarnings({"deprecation", "removal"}) // new Integer - void unboxing() { - int a = someInt; - // :: error: (immutable.type.guardedby) - @GuardedBy("lock") Integer c; - synchronized (lock) { - // :: error: (assignment.type.incompatible) - c = a; + @GuardedBy({}) MyClass z = x; // ILLEGAL because dereferences of z do not require "lock" to be held. + + @LockingFree + @GuardedBy("lock") MyClass myMethod() { + return x; // OK because the return type is @GuardedBy("lock") + } + + void exampleMethod() { + // :: error: (lock.not.held) + x.field = new Object(); // ILLEGAL because the lock is not known to be held + // :: error: (lock.not.held) + y.field = new Object(); // ILLEGAL because the lock is not known to be held + // :: error: (lock.not.held) + myMethod().field = new Object(); // ILLEGAL because the lock is not known to be held + synchronized (lock) { + x.field = new Object(); // OK: the lock is known to be held + y.field = new Object(); // OK: the lock is known to be held + myMethod().field = new Object(); // OK: the lock is known to be held + } + } + + final MyClass a = new MyClass(); + final MyClass b = new MyClass(); + + @GuardedBy("a") MyClass x5 = new MyClass(); + + @GuardedBy({"a", "b"}) MyClass y5 = new MyClass(); + + void myMethod2() { + // :: error: (assignment.type.incompatible) + y5 = x5; // ILLEGAL } // :: error: (immutable.type.guardedby) - @GuardedBy("lock") Integer b = 1; - int d; - synchronized (lock) { - d = b; - d = b.intValue(); // The de-sugared version + @GuardedBy("a") String s = "string"; + + @GuardedBy({}) MyClass o1; + + @GuardedBy("lock") MyClass o2; + + @GuardedBy("lock") MyClass o3; + + void someMethod() { + o3 = o2; // OK, since o2 and o3 are guarded by exactly the same lock set. + + // :: error: (assignment.type.incompatible) + o1 = o2; // Assignment type incompatible errors are issued for both assignments, since + // :: error: (assignment.type.incompatible) + o2 = o1; // {"lock"} and {} are not identical sets. } - c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). + @SuppressWarnings("lock:cast.unsafe") + void someMethod2() { + // A cast can be used if the user knows it is safe to do so. + // However, the @SuppressWarnings must be added. + o1 = (@GuardedBy({}) MyClass) o2; + } - c = new Integer(c.intValue() + b.intValue()); // The de-sugared version - c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version + static final Object myLock = new Object(); - synchronized (lock) { - c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). + @GuardedBy("ChapterExamples.myLock") MyClass myMethod3() { + return new MyClass(); + } - c = new Integer(c.intValue() + b.intValue()); // The de-sugared version - c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version + // reassignments without holding the lock are OK. + @GuardedBy("ChapterExamples.myLock") MyClass x2 = myMethod3(); + + @GuardedBy("ChapterExamples.myLock") MyClass y2 = x2; + + void myMethod4() { + // :: error: (lock.not.held) + x2.field = new Object(); // ILLEGAL because the lock is not held + synchronized (ChapterExamples.myLock) { + y2.field = new Object(); // OK: the lock is held + } + } + + void myMethod5(@GuardedBy("ChapterExamples.myLock") MyClass a) { + // :: error: (lock.not.held) + a.field = new Object(); // ILLEGAL: the lock is not held + synchronized (ChapterExamples.myLock) { + a.field = new Object(); // OK: the lock is held + } + } + + @LockingFree + boolean compare(@GuardSatisfied MyClass a, @GuardSatisfied MyClass b) { + return true; + } + + @GuardedBy({}) MyClass p1; + + @GuardedBy("lock") MyClass p2; + + void myMethod6() { + // It is the responsibility of callers to 'compare' to acquire the lock. + synchronized (lock) { + boolean b1 = compare(p1, p2); // OK. No error issued. + } + // :: error: (lock.not.held) + p2.field = new Object(); + // An error is issued indicating that p2 might be dereferenced without "lock" being held. + // The method call need not be modified, since @GuardedBy({}) <: @GuardedByUnknown and + // @GuardedBy("lock") <: @GuardedByUnknown, but the lock must be acquired prior to the + // method call. + // :: error: (lock.not.held) + boolean b2 = compare(p1, p2); + } + + void helper1(@GuardedBy("ChapterExamples.myLock") MyClass a) { + // :: error: (lock.not.held) + a.field = new Object(); // ILLEGAL: the lock is not held + synchronized (ChapterExamples.myLock) { + a.field = new Object(); // OK: the lock is held + } + } + + @Holding("ChapterExamples.myLock") + @LockingFree + void helper2(@GuardedBy("ChapterExamples.myLock") MyClass b) { + b.field = new Object(); // OK: the lock is held } - a = b; - b = c; // OK - } + @LockingFree + void helper3(@GuardSatisfied MyClass c) { + c.field = new Object(); // OK: the guard is satisfied + } - /* TODO Re-enable when guarding primitives is supported by the Lock Checker. - void boxingUnboxing() { - @GuardedBy("lock") int a = 1; - @GuardedBy({}) Integer c; - synchronized(lock) { - c = a; + @LockingFree + void helper4(@GuardedBy("ChapterExamples.myLock") MyClass d) { + // :: error: (lock.not.held) + d.field = new Object(); // ILLEGAL: the lock is not held } - @GuardedBy("lock") Integer b = 1; - @GuardedBy({}) int d; - synchronized(lock) { - // TODO re-enable this error (assignment.type.incompatible) - d = b; // TODO: This should not result in "assignment.type.incompatible" because 'b' is actually syntactic sugar for b.intValue(). - d = b.intValue(); // The de-sugared version does not issue an error. + @ReleasesNoLocks + void helper5() {} + + // No annotation means @ReleasesNoLocks + void helper6() {} + + void myMethod2(@GuardedBy("ChapterExamples.myLock") MyClass e) { + helper1(e); // OK to pass to another routine without holding the lock. + // :: error: (lock.not.held) + e.field = new Object(); // ILLEGAL: the lock is not held + // :: error: (contracts.precondition.not.satisfied) + helper2(e); + // :: error: (lock.not.held) + helper3(e); + synchronized (ChapterExamples.myLock) { + helper2(e); + helper3(e); // OK, since parameter is @GuardSatisfied + helper4(e); // OK, but helper4's body still has an error. + helper5(); + helper6(); + helper2(e); // Can still be called after helper5() and helper6() + } + } + + private @GuardedBy({}) MyClass myField; + + int someInt = 1; + + // TODO: For now, boxed types are treated as primitive types. This may change in the future. + @SuppressWarnings({"deprecation", "removal"}) // new Integer + void unboxing() { + int a = someInt; + // :: error: (immutable.type.guardedby) + @GuardedBy("lock") Integer c; + synchronized (lock) { + // :: error: (assignment.type.incompatible) + c = a; + } + + // :: error: (immutable.type.guardedby) + @GuardedBy("lock") Integer b = 1; + int d; + synchronized (lock) { + d = b; + d = b.intValue(); // The de-sugared version + } + + c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). + + c = new Integer(c.intValue() + b.intValue()); // The de-sugared version + c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version + + synchronized (lock) { + c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()). + + c = new Integer(c.intValue() + b.intValue()); // The de-sugared version + c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version + } + + a = b; + b = c; // OK } - // TODO re-enable this error (lock.not.held) - c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()), hence 'lock' must be held. - // TODO re-enable this error (lock.not.held) - c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version + /* TODO Re-enable when guarding primitives is supported by the Lock Checker. + void boxingUnboxing() { + @GuardedBy("lock") int a = 1; + @GuardedBy({}) Integer c; + synchronized(lock) { + c = a; + } + + @GuardedBy("lock") Integer b = 1; + @GuardedBy({}) int d; + synchronized(lock) { + // TODO re-enable this error (assignment.type.incompatible) + d = b; // TODO: This should not result in "assignment.type.incompatible" because 'b' is actually syntactic sugar for b.intValue(). + d = b.intValue(); // The de-sugared version does not issue an error. + } - synchronized(lock) { + // TODO re-enable this error (lock.not.held) c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()), hence 'lock' must be held. + // TODO re-enable this error (lock.not.held) c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version + + synchronized(lock) { + c = c + b; // Syntactic sugar for c = Integer.valueOf(c.intValue() + b.intValue()), hence 'lock' must be held. + c = Integer.valueOf(c.intValue() + b.intValue()); // The de-sugared version + } + + // TODO re-enable this error (lock.not.held) + a = b; // TODO: This assignment between two reference types should not require a lock to be held. + }*/ + + final ReentrantLock lock1 = new ReentrantLock(); + final ReentrantLock lock2 = new ReentrantLock(); + + @GuardedBy("lock1") MyClass filename; + + @GuardedBy("lock2") MyClass extension; + + void method0() { + // :: error: (lock.not.held) :: error: (lock.not.held) + filename = filename.append(extension); + } + + void method1() { + lock1.lock(); + // :: error: (lock.not.held) + filename = filename.append(extension); + } + + void method2() { + lock2.lock(); + // :: error: (lock.not.held) + filename = filename.append(extension); + } + + void method3() { + lock1.lock(); + lock2.lock(); + filename = filename.append(extension); + filename = filename.append(null); + // :: error: (assignment.type.incompatible) + filename = extension.append(extension); + // :: error: (assignment.type.incompatible) + filename = extension.append(filename); } - // TODO re-enable this error (lock.not.held) - a = b; // TODO: This assignment between two reference types should not require a lock to be held. - }*/ + void matchingGSparams(@GuardSatisfied(1) MyClass m1, @GuardSatisfied(1) MyClass m2) {} - final ReentrantLock lock1 = new ReentrantLock(); - final ReentrantLock lock2 = new ReentrantLock(); + void method4() { + lock1.lock(); + lock2.lock(); + matchingGSparams(filename, null); + matchingGSparams(null, filename); + } - @GuardedBy("lock1") MyClass filename; + public static boolean deepEquals(Object o1, Object o2) { + if (o1 instanceof Object[] && o2 instanceof Object[]) { + return Arrays.deepEquals((Object[]) o1, (Object[]) o2); + } + return false; + } - @GuardedBy("lock2") MyClass extension; + public static final class Comparer> { + public boolean compare(T[] a1, T[] a2) { + T elt1 = a1[0]; + T elt2 = a2[0]; + return elt1.equals(elt2); + } + } - void method0() { - // :: error: (lock.not.held) :: error: (lock.not.held) - filename = filename.append(extension); - } + public static boolean indexOf(T[] a, Object elt) { + if (elt.equals(a[0])) { + return false; + } + return true; + // found : (@org.checkerframework.checker.lock.qual.GuardedBy({}) :: T)[ extends + // @GuardedByUnknown @LockPossiblyHeld Object super @GuardedBy({}) @LockHeld Void] + // required: @GuardedBy @LockPossiblyHeld Object + } - void method1() { - lock1.lock(); - // :: error: (lock.not.held) - filename = filename.append(extension); - } + private static final Object NULL_KEY = new Object(); - void method2() { - lock2.lock(); - // :: error: (lock.not.held) - filename = filename.append(extension); - } + // A guardsatisfied.location.disallowed error is issued for the cast. + @SuppressWarnings({"cast.unsafe", "guardsatisfied.location.disallowed"}) + private static @GuardSatisfied(1) Object maskNull(@GuardSatisfied(1) Object key) { + return (key == null ? (@GuardSatisfied(1) Object) NULL_KEY : key); + } - void method3() { - lock1.lock(); - lock2.lock(); - filename = filename.append(extension); - filename = filename.append(null); - // :: error: (assignment.type.incompatible) - filename = extension.append(extension); - // :: error: (assignment.type.incompatible) - filename = extension.append(filename); - } - - void matchingGSparams(@GuardSatisfied(1) MyClass m1, @GuardSatisfied(1) MyClass m2) {} - - void method4() { - lock1.lock(); - lock2.lock(); - matchingGSparams(filename, null); - matchingGSparams(null, filename); - } - - public static boolean deepEquals(Object o1, Object o2) { - if (o1 instanceof Object[] && o2 instanceof Object[]) { - return Arrays.deepEquals((Object[]) o1, (Object[]) o2); - } - return false; - } - - public static final class Comparer> { - public boolean compare(T[] a1, T[] a2) { - T elt1 = a1[0]; - T elt2 = a2[0]; - return elt1.equals(elt2); - } - } - - public static boolean indexOf(T[] a, Object elt) { - if (elt.equals(a[0])) { - return false; - } - return true; - // found : (@org.checkerframework.checker.lock.qual.GuardedBy({}) :: T)[ extends - // @GuardedByUnknown @LockPossiblyHeld Object super @GuardedBy({}) @LockHeld Void] - // required: @GuardedBy @LockPossiblyHeld Object - } - - private static final Object NULL_KEY = new Object(); - - // A guardsatisfied.location.disallowed error is issued for the cast. - @SuppressWarnings({"cast.unsafe", "guardsatisfied.location.disallowed"}) - private static @GuardSatisfied(1) Object maskNull(@GuardSatisfied(1) Object key) { - return (key == null ? (@GuardSatisfied(1) Object) NULL_KEY : key); - } - - public void assignmentOfGSWithNoIndex(@GuardSatisfied Object a, @GuardSatisfied Object b) { - // :: error: (guardsatisfied.assignment.disallowed) - a = b; - } - - class Session { - @Holding("this") - public void kill(@GuardSatisfied Session this) {} - } - - class SessionManager { - private @GuardedBy("") Session session = new Session(); - - private void session_done() { - final @GuardedBy("") Session tmp = session; - session = null; - synchronized (tmp) { - tmp.kill(); - } + public void assignmentOfGSWithNoIndex(@GuardSatisfied Object a, @GuardSatisfied Object b) { + // :: error: (guardsatisfied.assignment.disallowed) + a = b; + } + + class Session { + @Holding("this") + public void kill(@GuardSatisfied Session this) {} + } + + class SessionManager { + private @GuardedBy("") Session session = new Session(); + + private void session_done() { + final @GuardedBy("") Session tmp = session; + session = null; + synchronized (tmp) { + tmp.kill(); + } + } } - } } diff --git a/checker/tests/lock/ClassLiterals.java b/checker/tests/lock/ClassLiterals.java index 6024614f849..04564529af6 100644 --- a/checker/tests/lock/ClassLiterals.java +++ b/checker/tests/lock/ClassLiterals.java @@ -3,30 +3,30 @@ import org.checkerframework.checker.lock.qual.Holding; public class ClassLiterals { - @Holding("ClassLiterals.class") - static Object method1() { - return new Object(); - } + @Holding("ClassLiterals.class") + static Object method1() { + return new Object(); + } - // a class literal may not terminate a JavaExpression string - @Holding("ClassLiterals") - // :: error: (flowexpr.parse.error) - static void method2() {} + // a class literal may not terminate a JavaExpression string + @Holding("ClassLiterals") + // :: error: (flowexpr.parse.error) + static void method2() {} - @Holding("ClassLiterals.method1()") - static void method3() {} + @Holding("ClassLiterals.method1()") + static void method3() {} - @Holding("testpackage.ClassLiterals.class") - static void method4() {} + @Holding("testpackage.ClassLiterals.class") + static void method4() {} - // a class literal may not terminate a JavaExpression string - @Holding("testpackage.ClassLiterals") - // :: error: (flowexpr.parse.error) - static void method5() {} + // a class literal may not terminate a JavaExpression string + @Holding("testpackage.ClassLiterals") + // :: error: (flowexpr.parse.error) + static void method5() {} - @Holding("testpackage.ClassLiterals.method1()") - static void method6() {} + @Holding("testpackage.ClassLiterals.method1()") + static void method6() {} - @Holding("java.lang.Comparable.class") - static void method7() {} + @Holding("java.lang.Comparable.class") + static void method7() {} } diff --git a/checker/tests/lock/ConstructorReturnNPE.java b/checker/tests/lock/ConstructorReturnNPE.java index 4f41dca2e03..2d1be3cfd33 100644 --- a/checker/tests/lock/ConstructorReturnNPE.java +++ b/checker/tests/lock/ConstructorReturnNPE.java @@ -5,6 +5,6 @@ // :: error: (expression.unparsable.type.invalid) @GuardedBy("lock") class ConstructorReturnNPE { - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("lock") ConstructorReturnNPE() {} + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("lock") ConstructorReturnNPE() {} } diff --git a/checker/tests/lock/ConstructorsLock.java b/checker/tests/lock/ConstructorsLock.java index c7307953c5c..a5e3d5de475 100644 --- a/checker/tests/lock/ConstructorsLock.java +++ b/checker/tests/lock/ConstructorsLock.java @@ -4,45 +4,45 @@ // but not over their class's fields public @GuardedBy({}) class ConstructorsLock { - static class MyClass { - public Object field; - } + static class MyClass { + public Object field; + } - final MyClass unlocked = new MyClass(); + final MyClass unlocked = new MyClass(); - @GuardedBy("this") MyClass guardedThis = new MyClass(); + @GuardedBy("this") MyClass guardedThis = new MyClass(); - @GuardedBy("unlocked") MyClass guardedOther = new MyClass(); + @GuardedBy("unlocked") MyClass guardedOther = new MyClass(); - static final MyClass unlockedStatic = new MyClass(); + static final MyClass unlockedStatic = new MyClass(); - @GuardedBy("unlockedStatic") MyClass nonstaticGuardedByStatic = new MyClass(); + @GuardedBy("unlockedStatic") MyClass nonstaticGuardedByStatic = new MyClass(); - // :: error: (expression.unparsable.type.invalid) - static @GuardedBy("unlocked") MyClass staticGuardedByNonStatic = new MyClass(); - static @GuardedBy("unlockedStatic") MyClass staticGuardedByStatic = new MyClass(); - - Object initializedObject1 = unlocked.field; - Object initializedObject2 = guardedThis.field; - // :: error: (lock.not.held) - Object initializedObject3 = guardedOther.field; - // :: error: (expression.unparsable.type.invalid) - Object initializedObject4 = staticGuardedByNonStatic.field; - // :: error: (lock.not.held) - Object initializedObject5 = nonstaticGuardedByStatic.field; - // :: error: (lock.not.held) - Object initializedObject6 = staticGuardedByStatic.field; + // :: error: (expression.unparsable.type.invalid) + static @GuardedBy("unlocked") MyClass staticGuardedByNonStatic = new MyClass(); + static @GuardedBy("unlockedStatic") MyClass staticGuardedByStatic = new MyClass(); - ConstructorsLock() { - unlocked.field.toString(); - guardedThis.field.toString(); + Object initializedObject1 = unlocked.field; + Object initializedObject2 = guardedThis.field; // :: error: (lock.not.held) - guardedOther.field.toString(); + Object initializedObject3 = guardedOther.field; // :: error: (expression.unparsable.type.invalid) - staticGuardedByNonStatic.field.toString(); + Object initializedObject4 = staticGuardedByNonStatic.field; // :: error: (lock.not.held) - nonstaticGuardedByStatic.field.toString(); + Object initializedObject5 = nonstaticGuardedByStatic.field; // :: error: (lock.not.held) - staticGuardedByStatic.field.toString(); - } + Object initializedObject6 = staticGuardedByStatic.field; + + ConstructorsLock() { + unlocked.field.toString(); + guardedThis.field.toString(); + // :: error: (lock.not.held) + guardedOther.field.toString(); + // :: error: (expression.unparsable.type.invalid) + staticGuardedByNonStatic.field.toString(); + // :: error: (lock.not.held) + nonstaticGuardedByStatic.field.toString(); + // :: error: (lock.not.held) + staticGuardedByStatic.field.toString(); + } } diff --git a/checker/tests/lock/Fields.java b/checker/tests/lock/Fields.java index 27c6c44dbe1..7cc74005736 100644 --- a/checker/tests/lock/Fields.java +++ b/checker/tests/lock/Fields.java @@ -1,103 +1,103 @@ import org.checkerframework.checker.lock.qual.*; public class Fields { - class MyClass { - public Object field; - } - - static @GuardedBy("Fields.class") MyClass lockedStatically; + class MyClass { + public Object field; + } - static synchronized void ssMethod() { - lockedStatically.field = new Object(); - } + static @GuardedBy("Fields.class") MyClass lockedStatically; - @GuardedBy("lockingObject") MyClass locked; + static synchronized void ssMethod() { + lockedStatically.field = new Object(); + } - final Object lockingObject = new Object(); + @GuardedBy("lockingObject") MyClass locked; - synchronized void wrongLock1() { - // locking over wrong lock - // :: error: (lock.not.held) - locked.field = new Object(); // error - } + final Object lockingObject = new Object(); - synchronized void wrongLock2() { - // locking over wrong lock - synchronized (this) { - // :: error: (lock.not.held) - locked.field = new Object(); // error + synchronized void wrongLock1() { + // locking over wrong lock + // :: error: (lock.not.held) + locked.field = new Object(); // error } - } - void rightLock() { - synchronized (lockingObject) { - locked.field = new Object(); + synchronized void wrongLock2() { + // locking over wrong lock + synchronized (this) { + // :: error: (lock.not.held) + locked.field = new Object(); // error + } } - // accessing after the synchronized object - // :: error: (lock.not.held) - locked.field = new Object(); // error - } + void rightLock() { + synchronized (lockingObject) { + locked.field = new Object(); + } - @Holding("lockingObject") - void usingHolding() { - locked.field = new Object(); - } + // accessing after the synchronized object + // :: error: (lock.not.held) + locked.field = new Object(); // error + } - @GuardedBy("this") MyClass lockedByThis; + @Holding("lockingObject") + void usingHolding() { + locked.field = new Object(); + } - void wrongLocksb() { - // without locking - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error + @GuardedBy("this") MyClass lockedByThis; - synchronized (Fields.class) { - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error - } - } + void wrongLocksb() { + // without locking + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error - void rightLockb() { - synchronized (this) { - lockedByThis.field = new Object(); + synchronized (Fields.class) { + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error + } } - // accessing after the synchronized object - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error - } - - synchronized void synchronizedMethodb() { - lockedByThis.field = new Object(); - } - - void test() { - // synchronized over the right object - final Fields a = new Fields(); - final Fields b = new Fields(); - - synchronized (this) { - lockedByThis.field = new Object(); - // :: error: (lock.not.held) - a.lockedByThis.field = new Object(); // error - // :: error: (lock.not.held) - b.lockedByThis.field = new Object(); // error + void rightLockb() { + synchronized (this) { + lockedByThis.field = new Object(); + } + + // accessing after the synchronized object + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error } - synchronized (a) { - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error - a.lockedByThis.field = new Object(); - // :: error: (lock.not.held) - b.lockedByThis.field = new Object(); // error + synchronized void synchronizedMethodb() { + lockedByThis.field = new Object(); } - synchronized (b) { - // :: error: (lock.not.held) - lockedByThis.field = new Object(); // error - // :: error: (lock.not.held) - a.lockedByThis.field = new Object(); // error - b.lockedByThis.field = new Object(); + void test() { + // synchronized over the right object + final Fields a = new Fields(); + final Fields b = new Fields(); + + synchronized (this) { + lockedByThis.field = new Object(); + // :: error: (lock.not.held) + a.lockedByThis.field = new Object(); // error + // :: error: (lock.not.held) + b.lockedByThis.field = new Object(); // error + } + + synchronized (a) { + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error + a.lockedByThis.field = new Object(); + // :: error: (lock.not.held) + b.lockedByThis.field = new Object(); // error + } + + synchronized (b) { + // :: error: (lock.not.held) + lockedByThis.field = new Object(); // error + // :: error: (lock.not.held) + a.lockedByThis.field = new Object(); // error + b.lockedByThis.field = new Object(); + } } - } } diff --git a/checker/tests/lock/FlowExpressionsTest.java b/checker/tests/lock/FlowExpressionsTest.java index 6c1c164f00e..3902637684a 100644 --- a/checker/tests/lock/FlowExpressionsTest.java +++ b/checker/tests/lock/FlowExpressionsTest.java @@ -2,36 +2,36 @@ import org.checkerframework.dataflow.qual.Pure; public class FlowExpressionsTest { - class MyClass { - public Object field; - } - - private final @GuardedBy({""}) MyClass m; + class MyClass { + public Object field; + } - FlowExpressionsTest() { - m = new MyClass(); - } + private final @GuardedBy({""}) MyClass m; - // private @GuardedBy({"nonexistentfield"}) MyClass m2; + FlowExpressionsTest() { + m = new MyClass(); + } - @Pure - private @GuardedBy({""}) MyClass getm() { - return m; - } + // private @GuardedBy({"nonexistentfield"}) MyClass m2; - public void method() { - // :: error: (lock.not.held) - getm().field = new Object(); - // :: error: (lock.not.held) - m.field = new Object(); - // TODO: fix the Lock Checker code so that a flowexpr.parse.error is issued (due to the - // guard of "nonexistentfield" on m2) - // m2.field = new Object(); - synchronized (m) { - m.field = new Object(); + @Pure + private @GuardedBy({""}) MyClass getm() { + return m; } - synchronized (getm()) { - getm().field = new Object(); + + public void method() { + // :: error: (lock.not.held) + getm().field = new Object(); + // :: error: (lock.not.held) + m.field = new Object(); + // TODO: fix the Lock Checker code so that a flowexpr.parse.error is issued (due to the + // guard of "nonexistentfield" on m2) + // m2.field = new Object(); + synchronized (m) { + m.field = new Object(); + } + synchronized (getm()) { + getm().field = new Object(); + } } - } } diff --git a/checker/tests/lock/FullyQualified.java b/checker/tests/lock/FullyQualified.java index ba37de468ee..23308ea8950 100644 --- a/checker/tests/lock/FullyQualified.java +++ b/checker/tests/lock/FullyQualified.java @@ -1,15 +1,16 @@ package com.example.mypackage; +import org.checkerframework.checker.lock.qual.GuardedBy; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.lock.qual.GuardedBy; public class FullyQualified { - public static final @GuardedBy("") List all_classes = new ArrayList<>(); + public static final @GuardedBy("") List all_classes = new ArrayList<>(); - void test() { - synchronized (com.example.mypackage.FullyQualified.all_classes) { - com.example.mypackage.FullyQualified.all_classes.add(new Object()); + void test() { + synchronized (com.example.mypackage.FullyQualified.all_classes) { + com.example.mypackage.FullyQualified.all_classes.add(new Object()); + } } - } } diff --git a/checker/tests/lock/GuardSatisfiedArray.java b/checker/tests/lock/GuardSatisfiedArray.java index f64ef02a4ce..9d69fe5fa96 100644 --- a/checker/tests/lock/GuardSatisfiedArray.java +++ b/checker/tests/lock/GuardSatisfiedArray.java @@ -3,18 +3,19 @@ // Test case for Issue #917: // https://github.com/typetools/checker-framework/issues/917 -import java.util.List; import org.checkerframework.checker.lock.qual.GuardSatisfied; +import java.util.List; + public class GuardSatisfiedArray { - void foo(@GuardSatisfied Object arg1, @GuardSatisfied Object arg2) {} + void foo(@GuardSatisfied Object arg1, @GuardSatisfied Object arg2) {} - void bar(@GuardSatisfied Object[] args) { - foo(args[0], args[1]); - } + void bar(@GuardSatisfied Object[] args) { + foo(args[0], args[1]); + } - void baz(@GuardSatisfied List<@GuardSatisfied Object> args) { - foo(args.get(0), args.get(1)); - } + void baz(@GuardSatisfied List<@GuardSatisfied Object> args) { + foo(args.get(0), args.get(1)); + } } diff --git a/checker/tests/lock/GuardSatisfiedTest.java b/checker/tests/lock/GuardSatisfiedTest.java index b0b86a588bf..4d4b40e92be 100644 --- a/checker/tests/lock/GuardSatisfiedTest.java +++ b/checker/tests/lock/GuardSatisfiedTest.java @@ -4,302 +4,302 @@ import org.checkerframework.checker.lock.qual.MayReleaseLocks; public class GuardSatisfiedTest { - void testGuardSatisfiedIndexMatching( - @GuardSatisfied GuardSatisfiedTest this, - @GuardSatisfied(1) Object o, - @GuardSatisfied(2) Object p, - @GuardSatisfied Object q) { - methodToCall1(o, o); - methodToCall1(p, p); - // :: error: (guardsatisfied.parameters.must.match) - methodToCall1(o, p); - // :: error: (guardsatisfied.parameters.must.match) - methodToCall1(p, o); - } - - // Test defaulting of parameters - they must default to @GuardedBy({}), not @GuardSatisfied - void testDefaulting(Object mustDefaultToGuardedByNothing, @GuardSatisfied Object p) { - // Must assign in this direction to test the defaulting because assigning a RHS of - // @GuardedBy({}) to a LHS @GuardSatisfied is legal. - // :: error: (assignment.type.incompatible) - mustDefaultToGuardedByNothing = p; - @GuardedBy({}) Object q = mustDefaultToGuardedByNothing; - } - - void testMethodCall( - @GuardSatisfied GuardSatisfiedTest this, - @GuardedBy("lock1") Object o, - @GuardedBy("lock2") Object p, - @GuardSatisfied Object q) { - // Test matching parameters - - // :: error: (lock.not.held) - methodToCall1(o, o); - // :: error: (lock.not.held) :: error: (guardsatisfied.parameters.must.match) - methodToCall1(o, p); - // :: error: (lock.not.held) - methodToCall1(p, p); - synchronized (lock2) { - // :: error: (lock.not.held) - methodToCall1(o, o); - // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) - methodToCall1(o, p); - methodToCall1(p, p); - synchronized (lock1) { + void testGuardSatisfiedIndexMatching( + @GuardSatisfied GuardSatisfiedTest this, + @GuardSatisfied(1) Object o, + @GuardSatisfied(2) Object p, + @GuardSatisfied Object q) { methodToCall1(o, o); + methodToCall1(p, p); // :: error: (guardsatisfied.parameters.must.match) methodToCall1(o, p); - methodToCall1(p, p); - } + // :: error: (guardsatisfied.parameters.must.match) + methodToCall1(p, o); } - // Test a return type matching a parameter. - - // :: error: (lock.not.held) - o = methodToCall2(o); - // :: error: (lock.not.held) :: error: (assignment.type.incompatible) - p = methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(p); - synchronized (lock2) { - // :: error: (lock.not.held) - o = methodToCall2(o); - // :: error: (lock.not.held) :: error: (assignment.type.incompatible) - p = methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(o); - methodToCall2(p); - } - synchronized (lock1) { - o = methodToCall2(o); - // :: error: (assignment.type.incompatible) - p = methodToCall2(o); - methodToCall2(o); - // :: error: (lock.not.held) - methodToCall2(p); + // Test defaulting of parameters - they must default to @GuardedBy({}), not @GuardSatisfied + void testDefaulting(Object mustDefaultToGuardedByNothing, @GuardSatisfied Object p) { + // Must assign in this direction to test the defaulting because assigning a RHS of + // @GuardedBy({}) to a LHS @GuardSatisfied is legal. + // :: error: (assignment.type.incompatible) + mustDefaultToGuardedByNothing = p; + @GuardedBy({}) Object q = mustDefaultToGuardedByNothing; } - // Test the receiver type matching a parameter - - // Two @GS parameters with no index are incomparable (as is the case for 'this' and 'q'). - // :: error: (guardsatisfied.parameters.must.match) - methodToCall3(q); - - // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) - methodToCall3(p); - synchronized (lock1) { - // Two @GS parameters with no index are incomparable (as is the case for 'this' and - // 'q'). - // :: error: (guardsatisfied.parameters.must.match) - methodToCall3(q); - // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) - methodToCall3(p); - synchronized (lock2) { - // Two @GS parameters with no index are incomparable (as is the case for 'this' and - // 'q'). + void testMethodCall( + @GuardSatisfied GuardSatisfiedTest this, + @GuardedBy("lock1") Object o, + @GuardedBy("lock2") Object p, + @GuardSatisfied Object q) { + // Test matching parameters + + // :: error: (lock.not.held) + methodToCall1(o, o); + // :: error: (lock.not.held) :: error: (guardsatisfied.parameters.must.match) + methodToCall1(o, p); + // :: error: (lock.not.held) + methodToCall1(p, p); + synchronized (lock2) { + // :: error: (lock.not.held) + methodToCall1(o, o); + // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) + methodToCall1(o, p); + methodToCall1(p, p); + synchronized (lock1) { + methodToCall1(o, o); + // :: error: (guardsatisfied.parameters.must.match) + methodToCall1(o, p); + methodToCall1(p, p); + } + } + + // Test a return type matching a parameter. + + // :: error: (lock.not.held) + o = methodToCall2(o); + // :: error: (lock.not.held) :: error: (assignment.type.incompatible) + p = methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(p); + synchronized (lock2) { + // :: error: (lock.not.held) + o = methodToCall2(o); + // :: error: (lock.not.held) :: error: (assignment.type.incompatible) + p = methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(o); + methodToCall2(p); + } + synchronized (lock1) { + o = methodToCall2(o); + // :: error: (assignment.type.incompatible) + p = methodToCall2(o); + methodToCall2(o); + // :: error: (lock.not.held) + methodToCall2(p); + } + + // Test the receiver type matching a parameter + + // Two @GS parameters with no index are incomparable (as is the case for 'this' and 'q'). // :: error: (guardsatisfied.parameters.must.match) methodToCall3(q); - // :: error: (guardsatisfied.parameters.must.match) + + // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) methodToCall3(p); - } + synchronized (lock1) { + // Two @GS parameters with no index are incomparable (as is the case for 'this' and + // 'q'). + // :: error: (guardsatisfied.parameters.must.match) + methodToCall3(q); + // :: error: (guardsatisfied.parameters.must.match) :: error: (lock.not.held) + methodToCall3(p); + synchronized (lock2) { + // Two @GS parameters with no index are incomparable (as is the case for 'this' and + // 'q'). + // :: error: (guardsatisfied.parameters.must.match) + methodToCall3(q); + // :: error: (guardsatisfied.parameters.must.match) + methodToCall3(p); + } + } + + // Test the return type matching the receiver type + + methodToCall4(); + } + + // Test the return type NOT matching the receiver type + void testMethodCall(@GuardedBy("lock1") GuardSatisfiedTest this) { + @GuardedBy("lock2") Object g; + // :: error: (lock.not.held) + methodToCall4(); + // TODO: lock.not.held is getting swallowed below + // error (assignment.type.incompatible) error (lock.not.held) + // g = methodToCall4(); + + // Separate the above test case into two for now + // :: error: (lock.not.held) + methodToCall4(); + + // The following error is due to the fact that you cannot access "this.lock1" without first + // having acquired "lock1". The right fix in a user scenario would be to not guard "this" + // with "this.lock1". The current object could instead be guarded by "" or by some + // other lock expression that is not one of its fields. We are keeping this test case here + // to make sure this scenario issues a warning. + // :: error: (lock.not.held) + synchronized (lock1) { + // :: error: (assignment.type.incompatible) + g = methodToCall4(); + } + } + + // :: error: (guardsatisfied.return.must.have.index) + @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex1(@GuardSatisfied Object o) { + // If the two @GuardSatisfied had an index, this error would not be issued: + // :: error: (guardsatisfied.assignment.disallowed) + return o; } - // Test the return type matching the receiver type - - methodToCall4(); - } - - // Test the return type NOT matching the receiver type - void testMethodCall(@GuardedBy("lock1") GuardSatisfiedTest this) { - @GuardedBy("lock2") Object g; - // :: error: (lock.not.held) - methodToCall4(); - // TODO: lock.not.held is getting swallowed below - // error (assignment.type.incompatible) error (lock.not.held) - // g = methodToCall4(); - - // Separate the above test case into two for now - // :: error: (lock.not.held) - methodToCall4(); - - // The following error is due to the fact that you cannot access "this.lock1" without first - // having acquired "lock1". The right fix in a user scenario would be to not guard "this" - // with "this.lock1". The current object could instead be guarded by "" or by some - // other lock expression that is not one of its fields. We are keeping this test case here - // to make sure this scenario issues a warning. - // :: error: (lock.not.held) - synchronized (lock1) { - // :: error: (assignment.type.incompatible) - g = methodToCall4(); + @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex2(@GuardSatisfied(1) Object o) { + return o; } - } - - // :: error: (guardsatisfied.return.must.have.index) - @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex1(@GuardSatisfied Object o) { - // If the two @GuardSatisfied had an index, this error would not be issued: - // :: error: (guardsatisfied.assignment.disallowed) - return o; - } - - @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex2(@GuardSatisfied(1) Object o) { - return o; - } - - @GuardSatisfied(0) Object testReturnTypesMustMatchAndMustHaveAnIndex3(@GuardSatisfied(0) Object o) { - return o; - } - - // @GuardSatisfied is equivalent to @GuardSatisfied(-1). - // :: error: (guardsatisfied.return.must.have.index) - @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex4(@GuardSatisfied(-1) Object o) { - // If the two @GuardSatisfied had an index, this error would not be issued: - // :: error: (guardsatisfied.assignment.disallowed) - return o; - } - - @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex5(@GuardSatisfied(2) Object o) { - // :: error: (return.type.incompatible) - return o; - } - - // :: error: (guardsatisfied.return.must.have.index) - @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex6(@GuardSatisfied(2) Object o) { - // :: error: (return.type.incompatible) - return o; - } - - void testParamsMustMatch(@GuardSatisfied(1) Object o, @GuardSatisfied(2) Object p) { - // :: error: (assignment.type.incompatible) - o = p; - } - - void methodToCall1( - @GuardSatisfied GuardSatisfiedTest this, - @GuardSatisfied(1) Object o, - @GuardSatisfied(1) Object p) {} - - @GuardSatisfied(1) Object methodToCall2(@GuardSatisfied GuardSatisfiedTest this, @GuardSatisfied(1) Object o) { - return o; - } - - void methodToCall3(@GuardSatisfied(1) GuardSatisfiedTest this, @GuardSatisfied(1) Object o) {} - - @GuardSatisfied(1) Object methodToCall4(@GuardSatisfied(1) GuardSatisfiedTest this) { - return this; - } - - final Object lock1 = new Object(); - final Object lock2 = new Object(); - - // This method exists to prevent flow-sensitive refinement. - @GuardedBy({"lock1", "lock2"}) Object guardedByLock1Lock2() { - return new Object(); - } - - void testAssignment(@GuardSatisfied Object o) { - @GuardedBy({"lock1", "lock2"}) Object p = guardedByLock1Lock2(); - // :: error: (lock.not.held) - o = p; - synchronized (lock1) { - // :: error: (lock.not.held) - o = p; - synchronized (lock2) { + + @GuardSatisfied(0) Object testReturnTypesMustMatchAndMustHaveAnIndex3(@GuardSatisfied(0) Object o) { + return o; + } + + // @GuardSatisfied is equivalent to @GuardSatisfied(-1). + // :: error: (guardsatisfied.return.must.have.index) + @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex4(@GuardSatisfied(-1) Object o) { + // If the two @GuardSatisfied had an index, this error would not be issued: + // :: error: (guardsatisfied.assignment.disallowed) + return o; + } + + @GuardSatisfied(1) Object testReturnTypesMustMatchAndMustHaveAnIndex5(@GuardSatisfied(2) Object o) { + // :: error: (return.type.incompatible) + return o; + } + + // :: error: (guardsatisfied.return.must.have.index) + @GuardSatisfied Object testReturnTypesMustMatchAndMustHaveAnIndex6(@GuardSatisfied(2) Object o) { + // :: error: (return.type.incompatible) + return o; + } + + void testParamsMustMatch(@GuardSatisfied(1) Object o, @GuardSatisfied(2) Object p) { + // :: error: (assignment.type.incompatible) o = p; - } } - } - // Test disallowed @GuardSatisfied locations. - // Whenever a disallowed location can be located within a method return type, receiver or - // parameter, test it there, because it's important to check that those are not mistakenly - // allowed, since annotations on method return types, receivers and parameters are allowed. By - // definition, fields and non-parameter local variables cannot be in one of these locations on a - // method declaration, but other locations can be. + void methodToCall1( + @GuardSatisfied GuardSatisfiedTest this, + @GuardSatisfied(1) Object o, + @GuardSatisfied(1) Object p) {} - // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied Object field; + @GuardSatisfied(1) Object methodToCall2(@GuardSatisfied GuardSatisfiedTest this, @GuardSatisfied(1) Object o) { + return o; + } - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArrayElementAndLocalVariable(@GuardSatisfied Object[] array) { - // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied Object local; - } + void methodToCall3(@GuardSatisfied(1) GuardSatisfiedTest this, @GuardSatisfied(1) Object o) {} - // :: error: (guardsatisfied.location.disallowed) - T testGuardSatisfiedOnBound(T t) { - return t; - } + @GuardSatisfied(1) Object methodToCall4(@GuardSatisfied(1) GuardSatisfiedTest this) { + return this; + } - class MyParameterizedClass1 { - void testGuardSatisfiedOnReceiverOfParameterizedClass( - @GuardSatisfied MyParameterizedClass1 this) {} + final Object lock1 = new Object(); + final Object lock2 = new Object(); - void testGuardSatisfiedOnArrayOfParameterizedType( - MyParameterizedClass1 @GuardSatisfied [] array) {} + // This method exists to prevent flow-sensitive refinement. + @GuardedBy({"lock1", "lock2"}) Object guardedByLock1Lock2() { + return new Object(); + } - void testGuardSatisfiedOnArrayComponentOfParameterizedType( + void testAssignment(@GuardSatisfied Object o) { + @GuardedBy({"lock1", "lock2"}) Object p = guardedByLock1Lock2(); + // :: error: (lock.not.held) + o = p; + synchronized (lock1) { + // :: error: (lock.not.held) + o = p; + synchronized (lock2) { + o = p; + } + } + } + + // Test disallowed @GuardSatisfied locations. + // Whenever a disallowed location can be located within a method return type, receiver or + // parameter, test it there, because it's important to check that those are not mistakenly + // allowed, since annotations on method return types, receivers and parameters are allowed. By + // definition, fields and non-parameter local variables cannot be in one of these locations on a + // method declaration, but other locations can be. + + // :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied Object field; + + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArrayElementAndLocalVariable(@GuardSatisfied Object[] array) { // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied MyParameterizedClass1[] array) {} - } + @GuardSatisfied Object local; + } - void testGuardSatisfiedOnWildCardExtendsBound( - // :: error: (guardsatisfied.location.disallowed) - MyParameterizedClass1 l) {} + // :: error: (guardsatisfied.location.disallowed) + T testGuardSatisfiedOnBound(T t) { + return t; + } - void testGuardSatisfiedOnWildCardSuperBound( - // :: error: (guardsatisfied.location.disallowed) - MyParameterizedClass1 l) {} + class MyParameterizedClass1 { + void testGuardSatisfiedOnReceiverOfParameterizedClass( + @GuardSatisfied MyParameterizedClass1 this) {} - @GuardSatisfied(1) Object testGuardSatisfiedOnParameters( - @GuardSatisfied GuardSatisfiedTest this, - Object @GuardSatisfied [] array, - @GuardSatisfied Object param, - @GuardSatisfied(1) Object param2) { - return param2; - } + void testGuardSatisfiedOnArrayOfParameterizedType( + MyParameterizedClass1 @GuardSatisfied [] array) {} - void testGuardSatisfiedOnArray1(Object @GuardSatisfied [][][] array) {} + void testGuardSatisfiedOnArrayComponentOfParameterizedType( + // :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied MyParameterizedClass1[] array) {} + } - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArray2(@GuardSatisfied Object[][][] array) {} + void testGuardSatisfiedOnWildCardExtendsBound( + // :: error: (guardsatisfied.location.disallowed) + MyParameterizedClass1 l) {} - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArray3(Object[] @GuardSatisfied [][] array) {} + void testGuardSatisfiedOnWildCardSuperBound( + // :: error: (guardsatisfied.location.disallowed) + MyParameterizedClass1 l) {} - // :: error: (guardsatisfied.location.disallowed) - void testGuardSatisfiedOnArray4(Object[][] @GuardSatisfied [] array) {} + @GuardSatisfied(1) Object testGuardSatisfiedOnParameters( + @GuardSatisfied GuardSatisfiedTest this, + Object @GuardSatisfied [] array, + @GuardSatisfied Object param, + @GuardSatisfied(1) Object param2) { + return param2; + } + + void testGuardSatisfiedOnArray1(Object @GuardSatisfied [][][] array) {} + + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArray2(@GuardSatisfied Object[][][] array) {} + + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArray3(Object[] @GuardSatisfied [][] array) {} + + // :: error: (guardsatisfied.location.disallowed) + void testGuardSatisfiedOnArray4(Object[][] @GuardSatisfied [] array) {} } class Foo { - @MayReleaseLocks - void m1() {} - - @MayReleaseLocks - // :: error: (guardsatisfied.with.mayreleaselocks) - void m2(@GuardSatisfied Foo f) { - // :: error: (method.invocation.invalid) - f.m1(); - } - - @MayReleaseLocks - void m2_2(Foo f) { - f.m1(); - } - - void m3(@GuardSatisfied Foo f) { - // :: error: (method.guarantee.violated) :: error: (method.invocation.invalid) - f.m1(); - } - - @MayReleaseLocks - void m4(Foo f) { - f.m1(); - } - - @MayReleaseLocks - void m5(Foo f) { - m3(f); - } + @MayReleaseLocks + void m1() {} + + @MayReleaseLocks + // :: error: (guardsatisfied.with.mayreleaselocks) + void m2(@GuardSatisfied Foo f) { + // :: error: (method.invocation.invalid) + f.m1(); + } + + @MayReleaseLocks + void m2_2(Foo f) { + f.m1(); + } + + void m3(@GuardSatisfied Foo f) { + // :: error: (method.guarantee.violated) :: error: (method.invocation.invalid) + f.m1(); + } + + @MayReleaseLocks + void m4(Foo f) { + f.m1(); + } + + @MayReleaseLocks + void m5(Foo f) { + m3(f); + } } diff --git a/checker/tests/lock/GuardedByLocalVariable.java b/checker/tests/lock/GuardedByLocalVariable.java index 311b368ff0e..083067823e4 100644 --- a/checker/tests/lock/GuardedByLocalVariable.java +++ b/checker/tests/lock/GuardedByLocalVariable.java @@ -1,38 +1,40 @@ // Test for Checker Framework issue 795 // https://github.com/typetools/checker-framework/issues/795 +import org.checkerframework.checker.lock.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.lock.qual.*; public class GuardedByLocalVariable { - public static void localVariableShadowing() { - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("m0") Object kk; - { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - final Map m0 = someValue(); - @GuardedBy("m0") Object k = "key"; - // If the type of kk were legal, this assignment would be illegal because the two - // instances of "m0" would refer to different variables. - kk = k; + public static void localVariableShadowing() { + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("m0") Object kk; + { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + final Map m0 = someValue(); + @GuardedBy("m0") Object k = "key"; + // If the type of kk were legal, this assignment would be illegal because the two + // instances of "m0" would refer to different variables. + kk = k; + } + { + @SuppressWarnings( + "assignment.type.incompatible") // prevent flow-sensitive type refinement + final Map m0 = someValue(); + // If the type of kk were legal, this assignment would be illegal because the two + // instances of "m0" would refer to different variables. + @GuardedBy("m0") Object k2 = kk; + } } - { - @SuppressWarnings("assignment.type.incompatible") // prevent flow-sensitive type refinement - final Map m0 = someValue(); - // If the type of kk were legal, this assignment would be illegal because the two - // instances of "m0" would refer to different variables. - @GuardedBy("m0") Object k2 = kk; - } - } - public static void invalidLocalVariable() { - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("foobar") Object kk; - } + public static void invalidLocalVariable() { + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("foobar") Object kk; + } - static @GuardedByUnknown Map someValue() { - return new HashMap<>(); - } + static @GuardedByUnknown Map someValue() { + return new HashMap<>(); + } } diff --git a/checker/tests/lock/Issue152.java b/checker/tests/lock/Issue152.java index 84477e5bb6c..8606d146ec8 100644 --- a/checker/tests/lock/Issue152.java +++ b/checker/tests/lock/Issue152.java @@ -5,33 +5,33 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class Issue152 { - static class SuperClass { - protected final Object myLock = new Object(); + static class SuperClass { + protected final Object myLock = new Object(); - private @GuardedBy("myLock") Object locked; - } + private @GuardedBy("myLock") Object locked; + } - static class SubClass extends SuperClass { - private final Object myLock = new Object(); + static class SubClass extends SuperClass { + private final Object myLock = new Object(); - private @GuardedBy("myLock") Object locked; + private @GuardedBy("myLock") Object locked; - void method() { - // :: error: (assignment.type.incompatible) - this.locked = super.locked; + void method() { + // :: error: (assignment.type.incompatible) + this.locked = super.locked; + } } - } - class OuterClass { - private final Object lock = new Object(); + class OuterClass { + private final Object lock = new Object(); - @GuardedBy("this.lock") Object field; + @GuardedBy("this.lock") Object field; - class InnerClass { - private final Object lock = new Object(); + class InnerClass { + private final Object lock = new Object(); - // :: error: (assignment.type.incompatible) - @GuardedBy("this.lock") Object field2 = field; + // :: error: (assignment.type.incompatible) + @GuardedBy("this.lock") Object field2 = field; + } } - } } diff --git a/checker/tests/lock/Issue2163Lock.java b/checker/tests/lock/Issue2163Lock.java index 10695205d49..0b73b90922d 100644 --- a/checker/tests/lock/Issue2163Lock.java +++ b/checker/tests/lock/Issue2163Lock.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.lock.qual.*; public class Issue2163Lock { - @GuardedBy Issue2163Lock() {} + @GuardedBy Issue2163Lock() {} - void test() { - // :: error: (constructor.invocation.invalid) :: error: (guardsatisfied.location.disallowed) - new @GuardSatisfied Issue2163Lock(); - } + void test() { + // :: error: (constructor.invocation.invalid) :: error: (guardsatisfied.location.disallowed) + new @GuardSatisfied Issue2163Lock(); + } } diff --git a/checker/tests/lock/Issue523.java b/checker/tests/lock/Issue523.java index 96f2a06a828..1a29887ddba 100644 --- a/checker/tests/lock/Issue523.java +++ b/checker/tests/lock/Issue523.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.lock.qual.*; public class Issue523 { - static class MyClass { - Object field; - } + static class MyClass { + Object field; + } - static final @GuardedBy("") MyClass m = new MyClass(); + static final @GuardedBy("") MyClass m = new MyClass(); - static void foo() { - Thread t = - new Thread() { - public void run() { - synchronized (m) { - m.field = new Object(); - } - } - }; - } + static void foo() { + Thread t = + new Thread() { + public void run() { + synchronized (m) { + m.field = new Object(); + } + } + }; + } } diff --git a/checker/tests/lock/Issue524.java b/checker/tests/lock/Issue524.java index 8e3b82651e6..6a6a43b328e 100644 --- a/checker/tests/lock/Issue524.java +++ b/checker/tests/lock/Issue524.java @@ -1,10 +1,11 @@ // Test case for Issue 524: // https://github.com/typetools/checker-framework/issues/524 -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByUnknown; +import java.util.concurrent.locks.ReentrantLock; + // WARNING: this test is nondeterministic, and has already been minimized - if you modify it by // removing what appears to be redundant code, it may no longer reproduce the issue or provide // coverage for the issue after a fix for the issue has been made. @@ -17,28 +18,28 @@ // Unfortunately a test case that always fails to typecheck using a Checker Framework build // prior to the fix for issue 524 has not been found. public class Issue524 { - class MyClass { - public Object field; - } + class MyClass { + public Object field; + } - @GuardedByUnknown MyClass someValue() { - return new MyClass(); - } + @GuardedByUnknown MyClass someValue() { + return new MyClass(); + } - void testLocalVariables() { - @GuardedBy({}) ReentrantLock localLock = new ReentrantLock(); + void testLocalVariables() { + @GuardedBy({}) ReentrantLock localLock = new ReentrantLock(); - { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - // :: error: (lock.expression.not.final) - @GuardedBy("localLock") MyClass q = someValue(); - localLock.lock(); - localLock.lock(); - // Without a fix for issue 524 in place, the error lock.not.held - // (unguarded access to field, variable or parameter 'q' guarded by 'localLock') is - // issued for the following line. - // :: error: (expression.unparsable.type.invalid) - q.field.toString(); + { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + // :: error: (lock.expression.not.final) + @GuardedBy("localLock") MyClass q = someValue(); + localLock.lock(); + localLock.lock(); + // Without a fix for issue 524 in place, the error lock.not.held + // (unguarded access to field, variable or parameter 'q' guarded by 'localLock') is + // issued for the following line. + // :: error: (expression.unparsable.type.invalid) + q.field.toString(); + } } - } } diff --git a/checker/tests/lock/Issue753.java b/checker/tests/lock/Issue753.java index b5826062e84..3a64bc53134 100644 --- a/checker/tests/lock/Issue753.java +++ b/checker/tests/lock/Issue753.java @@ -1,127 +1,128 @@ // Test case for Issue 753: // https://github.com/typetools/checker-framework/issues/753 -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.*; import org.checkerframework.dataflow.qual.*; +import java.util.concurrent.locks.ReentrantLock; + @SuppressWarnings({ - "purity", - "contracts.precondition.not.satisfied", - "lock.expression.possibly.not.final" + "purity", + "contracts.precondition.not.satisfied", + "lock.expression.possibly.not.final" }) // Only test parsing public class Issue753 extends ReentrantLock { - final Issue753 field = new Issue753(); - final Issue753[] fields = {this, field}; - final Issue753[][] fieldsArray = {fields}; - final int zero = 0; - final int[] zeros = {0}; - - @Pure - Issue753 getField(Object param) { - return field; - } - - @Pure - Issue753 getField2() { - return field; - } - - @Pure - Issue753 getField3(String str) { - return field; - } - - @Pure - Issue753[] getFields() { - return fields; - } - - @Pure - Issue753[][] getFieldsArray() { - return fieldsArray; - } - - @Pure - int length(String str) { - return str.length(); - } - - @Pure - int[] zeros() { - return zeros; - } - - void method() { - getField(field.field).field.lock(); - method2(); - getField(field.field).getField(field.field).field.lock(); - method3(); - getField(field.field).getField2().field.lock(); - method4(); - getField2().getField2().field.lock(); - method5(); - getField2().getField2().lock(); - method6(); - getField(getField(getField2()).field).field.lock(); - method7(); - this.getField3(")(in string.;))\")(still so.)\"").field.lock(); - method8(); - this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]].lock(); - method9(); - this.fieldsArray[length("[")][length("[")].fields[length("[")].field.lock(); - method10(); - this.fieldsArray["[".length()]["[".length()].fields["[".length()].field.lock(); - method11(); - this.getFields()[this.zero].field.lock(); - method12(); - this.getFieldsArray()[this.getField2().zero][this.zero].field.lock(); - method13(); - this.getFields()["][in string.;)]\"][still so.]\"".length()].field.lock(); - method14(); - this.fields[(("][in string.;)]\"][still so.]\"").length())].field.lock(); - method15(); - } - - @Holding("this.getField(this.field.field).field") - void method2() {} - - @Holding("this.getField(this.field.field).getField(this.field.field).field") - void method3() {} - - @Holding("this.getField(this.field.field).getField2().field") - void method4() {} - - @Holding("this.getField2().getField2().field") - void method5() {} - - @Holding("this.getField2().getField2()") - void method6() {} - - @Holding("this.getField(this.getField(this.getField2()).field).field") - void method7() {} - - @Holding("this.getField3(\")(in string.;))\\\")(still so.)\\\"\").field") - void method8() {} - - @Holding("this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]]") - void method9() {} - - @Holding("this.fieldsArray[length(\"[\")][length(\"[\")].fields[length(\"[\")].field") - void method10() {} - - @Holding("this.fieldsArray[\"[\".length()][\"[\".length()].fields[\"[\".length()].field") - void method11() {} - - @Holding("this.getFields()[this.zero].field") - void method12() {} - - @Holding("this.getFieldsArray()[this.getField2().zero][this.zero].field") - void method13() {} - - @Holding("this.getFields()[\"][in string.;)]\\\"][still so.]\\\"\".length()].field") - void method14() {} - - @Holding("this.fields[((\"][in string.;)]\\\"][still so.]\\\"\").length())].field") - void method15() {} + final Issue753 field = new Issue753(); + final Issue753[] fields = {this, field}; + final Issue753[][] fieldsArray = {fields}; + final int zero = 0; + final int[] zeros = {0}; + + @Pure + Issue753 getField(Object param) { + return field; + } + + @Pure + Issue753 getField2() { + return field; + } + + @Pure + Issue753 getField3(String str) { + return field; + } + + @Pure + Issue753[] getFields() { + return fields; + } + + @Pure + Issue753[][] getFieldsArray() { + return fieldsArray; + } + + @Pure + int length(String str) { + return str.length(); + } + + @Pure + int[] zeros() { + return zeros; + } + + void method() { + getField(field.field).field.lock(); + method2(); + getField(field.field).getField(field.field).field.lock(); + method3(); + getField(field.field).getField2().field.lock(); + method4(); + getField2().getField2().field.lock(); + method5(); + getField2().getField2().lock(); + method6(); + getField(getField(getField2()).field).field.lock(); + method7(); + this.getField3(")(in string.;))\")(still so.)\"").field.lock(); + method8(); + this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]].lock(); + method9(); + this.fieldsArray[length("[")][length("[")].fields[length("[")].field.lock(); + method10(); + this.fieldsArray["[".length()]["[".length()].fields["[".length()].field.lock(); + method11(); + this.getFields()[this.zero].field.lock(); + method12(); + this.getFieldsArray()[this.getField2().zero][this.zero].field.lock(); + method13(); + this.getFields()["][in string.;)]\"][still so.]\"".length()].field.lock(); + method14(); + this.fields[(("][in string.;)]\"][still so.]\"").length())].field.lock(); + method15(); + } + + @Holding("this.getField(this.field.field).field") + void method2() {} + + @Holding("this.getField(this.field.field).getField(this.field.field).field") + void method3() {} + + @Holding("this.getField(this.field.field).getField2().field") + void method4() {} + + @Holding("this.getField2().getField2().field") + void method5() {} + + @Holding("this.getField2().getField2()") + void method6() {} + + @Holding("this.getField(this.getField(this.getField2()).field).field") + void method7() {} + + @Holding("this.getField3(\")(in string.;))\\\")(still so.)\\\"\").field") + void method8() {} + + @Holding("this.fieldsArray[zeros()[0]][zeros()[0]].fields[zeros()[0]]") + void method9() {} + + @Holding("this.fieldsArray[length(\"[\")][length(\"[\")].fields[length(\"[\")].field") + void method10() {} + + @Holding("this.fieldsArray[\"[\".length()][\"[\".length()].fields[\"[\".length()].field") + void method11() {} + + @Holding("this.getFields()[this.zero].field") + void method12() {} + + @Holding("this.getFieldsArray()[this.getField2().zero][this.zero].field") + void method13() {} + + @Holding("this.getFields()[\"][in string.;)]\\\"][still so.]\\\"\".length()].field") + void method14() {} + + @Holding("this.fields[((\"][in string.;)]\\\"][still so.]\\\"\").length())].field") + void method15() {} } diff --git a/checker/tests/lock/Issue804.java b/checker/tests/lock/Issue804.java index 220e1d18dbd..122b8a724af 100644 --- a/checker/tests/lock/Issue804.java +++ b/checker/tests/lock/Issue804.java @@ -1,21 +1,22 @@ // Test case for Issue 804: // https://github.com/typetools/checker-framework/issues/804 -import java.util.concurrent.locks.*; import org.checkerframework.checker.lock.qual.*; +import java.util.concurrent.locks.*; + public class Issue804 extends ReentrantLock { - @Holding("this") - @MayReleaseLocks - void bar() { - this.unlock(); - } + @Holding("this") + @MayReleaseLocks + void bar() { + this.unlock(); + } - @Holding("this") - @MayReleaseLocks - void method() { - bar(); - // :: error: (contracts.precondition.not.satisfied) - bar(); - } + @Holding("this") + @MayReleaseLocks + void method() { + bar(); + // :: error: (contracts.precondition.not.satisfied) + bar(); + } } diff --git a/checker/tests/lock/Issue805.java b/checker/tests/lock/Issue805.java index 301212d83fe..6a721e772c4 100644 --- a/checker/tests/lock/Issue805.java +++ b/checker/tests/lock/Issue805.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.lock.qual.Holding; public class Issue805 { - @Holding("this.Issue805.class") - // :: error: (flowexpr.parse.error) - void method() {} + @Holding("this.Issue805.class") + // :: error: (flowexpr.parse.error) + void method() {} - @Holding("Issue805.class") - void method2() {} + @Holding("Issue805.class") + void method2() {} - @Holding("java.lang.String.class") - void method3() {} + @Holding("java.lang.String.class") + void method3() {} } diff --git a/checker/tests/lock/ItselfExpressionCases.java b/checker/tests/lock/ItselfExpressionCases.java index 4c1231dc222..dc09b416af9 100644 --- a/checker/tests/lock/ItselfExpressionCases.java +++ b/checker/tests/lock/ItselfExpressionCases.java @@ -3,142 +3,142 @@ import org.checkerframework.dataflow.qual.*; public class ItselfExpressionCases { - final Object somelock = new Object(); - - private @GuardedBy({""}) MyClass guardedBySelf() { - return new MyClass(); - } - - private final @GuardedBy({""}) MyClass m = guardedBySelf(); - - @Pure - private @GuardedBy({""}) MyClass getm() { - return m; - } - - @Pure - private @GuardedBy({""}) MyClass getm2(@GuardedBy("") ItselfExpressionCases this) { - // The following error is due to the precondition of the this.m field dereference not being - // satisfied. - // :: error: (lock.not.held) - return m; - } - - @Pure - private Object getmfield() { - // :: error: (lock.not.held) - return getm().field; - } - - public void arrayTest(final Object @GuardedBy("") [] a1) { - // :: error: (lock.not.held) - Object a = a1[0]; - synchronized (a1) { - a = a1[0]; - } - } + final Object somelock = new Object(); - Object @GuardedBy("") [] a2; + private @GuardedBy({""}) MyClass guardedBySelf() { + return new MyClass(); + } - @Pure - public Object @GuardedBy("") [] geta2() { - return a2; - } + private final @GuardedBy({""}) MyClass m = guardedBySelf(); - public void arrayTest() { - // :: error: (lock.not.held) - Object a = geta2()[0]; - synchronized (geta2()) { - a = geta2()[0]; - } - } - - public void testCheckPreconditions( - final @GuardedBy("") MyClass o, - @GuardSatisfied Object gs, - @GuardSatisfied MyClass gsMyClass) { - // :: error: (lock.not.held) - getm().field = new Object(); - synchronized (getm()) { - getm().field = new Object(); + @Pure + private @GuardedBy({""}) MyClass getm() { + return m; } - // :: error: (lock.not.held) - m.field = new Object(); - synchronized (m) { - m.field = new Object(); + @Pure + private @GuardedBy({""}) MyClass getm2(@GuardedBy("") ItselfExpressionCases this) { + // The following error is due to the precondition of the this.m field dereference not being + // satisfied. + // :: error: (lock.not.held) + return m; } - // :: error: (lock.not.held) - gs = m.field; - synchronized (m) { - gs = m.field; + @Pure + private Object getmfield() { + // :: error: (lock.not.held) + return getm().field; } - // :: error: (lock.not.held) - gs = getm().field; - synchronized (getm()) { - gs = getm().field; + public void arrayTest(final Object @GuardedBy("") [] a1) { + // :: error: (lock.not.held) + Object a = a1[0]; + synchronized (a1) { + a = a1[0]; + } } - // :: error: (lock.not.held) - gsMyClass = getm(); - synchronized (getm()) { - gsMyClass = getm(); - } + Object @GuardedBy("") [] a2; - // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) - o.foo(); - synchronized (o) { - // :: error: (contracts.precondition.not.satisfied) - o.foo(); - synchronized (somelock) { - // o.foo() requires o.somelock is held, not this.somelock. - // :: error: (contracts.precondition.not.satisfied) - o.foo(); - } + @Pure + public Object @GuardedBy("") [] geta2() { + return a2; } - // :: error: (lock.not.held) - o.foo2(); - synchronized (o) { - o.foo2(); + public void arrayTest() { + // :: error: (lock.not.held) + Object a = geta2()[0]; + synchronized (geta2()) { + a = geta2()[0]; + } } - } - class MyClass { - Object field = new Object(); + public void testCheckPreconditions( + final @GuardedBy("") MyClass o, + @GuardSatisfied Object gs, + @GuardSatisfied MyClass gsMyClass) { + // :: error: (lock.not.held) + getm().field = new Object(); + synchronized (getm()) { + getm().field = new Object(); + } + + // :: error: (lock.not.held) + m.field = new Object(); + synchronized (m) { + m.field = new Object(); + } - @Holding("somelock") - void foo(@GuardSatisfied MyClass this) {} + // :: error: (lock.not.held) + gs = m.field; + synchronized (m) { + gs = m.field; + } - void foo2(@GuardSatisfied MyClass this) {} + // :: error: (lock.not.held) + gs = getm().field; + synchronized (getm()) { + gs = getm().field; + } - void method(@GuardedBy("") MyClass this) { - // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) - this.foo(); - // :: error: (lock.not.held):: error: (contracts.precondition.not.satisfied) - foo(); - // :: error: (lock.not.held) - synchronized (somelock) { // :: error: (lock.not.held) - this.foo(); + gsMyClass = getm(); + synchronized (getm()) { + gsMyClass = getm(); + } + + // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) + o.foo(); + synchronized (o) { + // :: error: (contracts.precondition.not.satisfied) + o.foo(); + synchronized (somelock) { + // o.foo() requires o.somelock is held, not this.somelock. + // :: error: (contracts.precondition.not.satisfied) + o.foo(); + } + } + // :: error: (lock.not.held) - foo(); - synchronized (this) { - this.foo(); - foo(); + o.foo2(); + synchronized (o) { + o.foo2(); + } + } + + class MyClass { + Object field = new Object(); + + @Holding("somelock") + void foo(@GuardSatisfied MyClass this) {} + + void foo2(@GuardSatisfied MyClass this) {} + + void method(@GuardedBy("") MyClass this) { + // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied) + this.foo(); + // :: error: (lock.not.held):: error: (contracts.precondition.not.satisfied) + foo(); + // :: error: (lock.not.held) + synchronized (somelock) { + // :: error: (lock.not.held) + this.foo(); + // :: error: (lock.not.held) + foo(); + synchronized (this) { + this.foo(); + foo(); + } + } + + // :: error: (lock.not.held) + this.foo2(); + // :: error: (lock.not.held) + foo2(); + synchronized (this) { + this.foo2(); + foo2(); + } } - } - - // :: error: (lock.not.held) - this.foo2(); - // :: error: (lock.not.held) - foo2(); - synchronized (this) { - this.foo2(); - foo2(); - } } - } } diff --git a/checker/tests/lock/JCIPAnnotations.java b/checker/tests/lock/JCIPAnnotations.java index 0b8ef42932e..57c7fbbc4ae 100644 --- a/checker/tests/lock/JCIPAnnotations.java +++ b/checker/tests/lock/JCIPAnnotations.java @@ -1,114 +1,115 @@ -import net.jcip.annotations.GuardedBy; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.lock.qual.Holding; import org.checkerframework.checker.lock.qual.LockingFree; +import net.jcip.annotations.GuardedBy; + // Smoke test for supporting JCIP and Javax annotations. // Note that JCIP and Javax @GuardedBy can only be written on fields and methods. public class JCIPAnnotations { - class MyClass { - Object field; - - @LockingFree - void methodWithUnguardedReceiver() {} - - @LockingFree - void methodWithGuardedReceiver( - @org.checkerframework.checker.lock.qual.GuardedBy("lock") MyClass this) {} - - @LockingFree - void methodWithGuardSatisfiedReceiver(@GuardSatisfied MyClass this) {} - } - - final Object lock = new Object(); - - @GuardedBy("lock") MyClass jcipGuardedField; - - @javax.annotation.concurrent.GuardedBy("lock") MyClass javaxGuardedField; - - // Tests that Javax and JCIP @GuardedBy(...) typecheck against the Lock Checker @GuardedBy on a - // receiver. - void testReceivers() { - // :: error: (method.invocation.invalid) - jcipGuardedField.methodWithUnguardedReceiver(); - // :: error: (method.invocation.invalid) - jcipGuardedField.methodWithGuardedReceiver(); - // :: error: (lock.not.held) - jcipGuardedField.methodWithGuardSatisfiedReceiver(); - // :: error: (method.invocation.invalid) - javaxGuardedField.methodWithUnguardedReceiver(); - // :: error: (method.invocation.invalid) - javaxGuardedField.methodWithGuardedReceiver(); - // :: error: (lock.not.held) - javaxGuardedField.methodWithGuardSatisfiedReceiver(); - } - - void testDereferences() { - // :: error: (lock.not.held) - this.jcipGuardedField.field.toString(); - // :: error: (lock.not.held) - this.javaxGuardedField.field.toString(); - synchronized (lock) { - this.jcipGuardedField.field.toString(); - this.javaxGuardedField.field.toString(); + class MyClass { + Object field; + + @LockingFree + void methodWithUnguardedReceiver() {} + + @LockingFree + void methodWithGuardedReceiver( + @org.checkerframework.checker.lock.qual.GuardedBy("lock") MyClass this) {} + + @LockingFree + void methodWithGuardSatisfiedReceiver(@GuardSatisfied MyClass this) {} } - } - - @GuardedBy("lock") - void testGuardedByAsHolding() { - this.jcipGuardedField.field.toString(); - this.javaxGuardedField.field.toString(); - jcipGuardedField.field.toString(); - javaxGuardedField.field.toString(); - } - - @GuardedBy("lock") Object testGuardedByAsHolding2( - @org.checkerframework.checker.lock.qual.GuardedBy({}) Object param) { - testGuardedByAsHolding(); - // Test that the JCIP GuardedBy applies to the method but not the return type. - return param; - } - - void testGuardedByAsHolding3() { - synchronized (lock) { - testGuardedByAsHolding(); + + final Object lock = new Object(); + + @GuardedBy("lock") MyClass jcipGuardedField; + + @javax.annotation.concurrent.GuardedBy("lock") MyClass javaxGuardedField; + + // Tests that Javax and JCIP @GuardedBy(...) typecheck against the Lock Checker @GuardedBy on a + // receiver. + void testReceivers() { + // :: error: (method.invocation.invalid) + jcipGuardedField.methodWithUnguardedReceiver(); + // :: error: (method.invocation.invalid) + jcipGuardedField.methodWithGuardedReceiver(); + // :: error: (lock.not.held) + jcipGuardedField.methodWithGuardSatisfiedReceiver(); + // :: error: (method.invocation.invalid) + javaxGuardedField.methodWithUnguardedReceiver(); + // :: error: (method.invocation.invalid) + javaxGuardedField.methodWithGuardedReceiver(); + // :: error: (lock.not.held) + javaxGuardedField.methodWithGuardSatisfiedReceiver(); + } + + void testDereferences() { + // :: error: (lock.not.held) + this.jcipGuardedField.field.toString(); + // :: error: (lock.not.held) + this.javaxGuardedField.field.toString(); + synchronized (lock) { + this.jcipGuardedField.field.toString(); + this.javaxGuardedField.field.toString(); + } } - // :: error: (contracts.precondition.not.satisfied) - testGuardedByAsHolding(); - } - - @Holding("lock") - @GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations1() {} - - @Holding("lock") - @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations2() {} - - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations3() {} - - @Holding("lock") - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.lock.precondition.annotations) - void testMultipleMethodAnnotations4() {} - - @GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations1; - - @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations2; - - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations3; - - @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") - // :: error: (multiple.guardedby.annotations) - Object fieldWithMultipleGuardedByAnnotations4; + + @GuardedBy("lock") + void testGuardedByAsHolding() { + this.jcipGuardedField.field.toString(); + this.javaxGuardedField.field.toString(); + jcipGuardedField.field.toString(); + javaxGuardedField.field.toString(); + } + + @GuardedBy("lock") Object testGuardedByAsHolding2( + @org.checkerframework.checker.lock.qual.GuardedBy({}) Object param) { + testGuardedByAsHolding(); + // Test that the JCIP GuardedBy applies to the method but not the return type. + return param; + } + + void testGuardedByAsHolding3() { + synchronized (lock) { + testGuardedByAsHolding(); + } + // :: error: (contracts.precondition.not.satisfied) + testGuardedByAsHolding(); + } + + @Holding("lock") + @GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations1() {} + + @Holding("lock") + @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations2() {} + + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations3() {} + + @Holding("lock") + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.lock.precondition.annotations) + void testMultipleMethodAnnotations4() {} + + @GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations1; + + @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations2; + + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations3; + + @GuardedBy("lock") @javax.annotation.concurrent.GuardedBy("lock") @org.checkerframework.checker.lock.qual.GuardedBy("lock") + // :: error: (multiple.guardedby.annotations) + Object fieldWithMultipleGuardedByAnnotations4; } diff --git a/checker/tests/lock/LockEffectAnnotations.java b/checker/tests/lock/LockEffectAnnotations.java index c37479bf0df..7f4bcddc817 100644 --- a/checker/tests/lock/LockEffectAnnotations.java +++ b/checker/tests/lock/LockEffectAnnotations.java @@ -1,4 +1,3 @@ -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByBottom; @@ -9,144 +8,147 @@ import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; +import java.util.concurrent.locks.ReentrantLock; + public class LockEffectAnnotations { - class MyClass { - Object field = new Object(); - } - - private @GuardedBy({}) MyClass myField; - - private final ReentrantLock myLock2 = new ReentrantLock(); - private @GuardedBy("myLock2") MyClass x3; - - // This method does not use locks or synchronization but cannot - // be annotated as @SideEffectFree since it alters myField. - @LockingFree - void myMethod5() { - myField = new MyClass(); - } - - @SideEffectFree - int mySideEffectFreeMethod() { - return 0; - } - - @MayReleaseLocks - void myUnlockingMethod() { - myLock2.unlock(); - } - - @MayReleaseLocks - void myReleaseLocksEmptyMethod() {} - - @MayReleaseLocks - // :: error: (guardsatisfied.with.mayreleaselocks) - void methodGuardSatisfiedReceiver(@GuardSatisfied LockEffectAnnotations this) {} - - @MayReleaseLocks - // :: error: (guardsatisfied.with.mayreleaselocks) - void methodGuardSatisfiedParameter(@GuardSatisfied Object o) {} - - @MayReleaseLocks - void myOtherMethod() { - if (myLock2.tryLock()) { - x3.field = new Object(); // OK: the lock is held - myMethod5(); - x3.field = new Object(); // OK: the lock is still held since myMethod is locking-free - mySideEffectFreeMethod(); - x3.field = new Object(); // OK: the lock is still held since mySideEffectFreeMethod is - // side-effect-free - myUnlockingMethod(); - // :: error: (lock.not.held) - x3.field = new Object(); // ILLEGAL: myLockingMethod is not locking-free + class MyClass { + Object field = new Object(); + } + + private @GuardedBy({}) MyClass myField; + + private final ReentrantLock myLock2 = new ReentrantLock(); + private @GuardedBy("myLock2") MyClass x3; + + // This method does not use locks or synchronization but cannot + // be annotated as @SideEffectFree since it alters myField. + @LockingFree + void myMethod5() { + myField = new MyClass(); + } + + @SideEffectFree + int mySideEffectFreeMethod() { + return 0; } - if (myLock2.tryLock()) { - x3.field = new Object(); // OK: the lock is held - myReleaseLocksEmptyMethod(); - // :: error: (lock.not.held) - x3.field = new Object(); // ILLEGAL: even though myUnannotatedEmptyMethod is empty, since - // myReleaseLocksEmptyMethod() is annotated with @MayReleaseLocks and the Lock Checker - // no longer knows the state of the lock. - if (myLock2.isHeldByCurrentThread()) { - x3.field = new Object(); // OK: the lock is known to be held - } + + @MayReleaseLocks + void myUnlockingMethod() { + myLock2.unlock(); } - } - @ReleasesNoLocks - void innerClassTest() { - class InnerClass { - @MayReleaseLocks - void innerClassMethod() {} + @MayReleaseLocks + void myReleaseLocksEmptyMethod() {} + + @MayReleaseLocks + // :: error: (guardsatisfied.with.mayreleaselocks) + void methodGuardSatisfiedReceiver(@GuardSatisfied LockEffectAnnotations this) {} + + @MayReleaseLocks + // :: error: (guardsatisfied.with.mayreleaselocks) + void methodGuardSatisfiedParameter(@GuardSatisfied Object o) {} + + @MayReleaseLocks + void myOtherMethod() { + if (myLock2.tryLock()) { + x3.field = new Object(); // OK: the lock is held + myMethod5(); + x3.field = new Object(); // OK: the lock is still held since myMethod is locking-free + mySideEffectFreeMethod(); + x3.field = new Object(); // OK: the lock is still held since mySideEffectFreeMethod is + // side-effect-free + myUnlockingMethod(); + // :: error: (lock.not.held) + x3.field = new Object(); // ILLEGAL: myLockingMethod is not locking-free + } + if (myLock2.tryLock()) { + x3.field = new Object(); // OK: the lock is held + myReleaseLocksEmptyMethod(); + // :: error: (lock.not.held) + x3.field = + new Object(); // ILLEGAL: even though myUnannotatedEmptyMethod is empty, since + // myReleaseLocksEmptyMethod() is annotated with @MayReleaseLocks and the Lock Checker + // no longer knows the state of the lock. + if (myLock2.isHeldByCurrentThread()) { + x3.field = new Object(); // OK: the lock is known to be held + } + } } - InnerClass ic = new InnerClass(); - // :: error: (method.guarantee.violated) - ic.innerClassMethod(); - } + @ReleasesNoLocks + void innerClassTest() { + class InnerClass { + @MayReleaseLocks + void innerClassMethod() {} + } + + InnerClass ic = new InnerClass(); + // :: error: (method.guarantee.violated) + ic.innerClassMethod(); + } - @MayReleaseLocks - synchronized void mayReleaseLocksSynchronizedMethod() {} + @MayReleaseLocks + synchronized void mayReleaseLocksSynchronizedMethod() {} - @ReleasesNoLocks - synchronized void releasesNoLocksSynchronizedMethod() {} + @ReleasesNoLocks + synchronized void releasesNoLocksSynchronizedMethod() {} - @LockingFree - // :: error: (lockingfree.synchronized.method) - synchronized void lockingFreeSynchronizedMethod() {} + @LockingFree + // :: error: (lockingfree.synchronized.method) + synchronized void lockingFreeSynchronizedMethod() {} - @SideEffectFree - // :: error: (lockingfree.synchronized.method) - synchronized void sideEffectFreeSynchronizedMethod() {} + @SideEffectFree + // :: error: (lockingfree.synchronized.method) + synchronized void sideEffectFreeSynchronizedMethod() {} - @Pure - // :: error: (lockingfree.synchronized.method) - synchronized void pureSynchronizedMethod() {} + @Pure + // :: error: (lockingfree.synchronized.method) + synchronized void pureSynchronizedMethod() {} - @MayReleaseLocks - void mayReleaseLocksMethodWithSynchronizedBlock() { - synchronized (this) { + @MayReleaseLocks + void mayReleaseLocksMethodWithSynchronizedBlock() { + synchronized (this) { + } } - } - @ReleasesNoLocks - void releasesNoLocksMethodWithSynchronizedBlock() { - synchronized (this) { + @ReleasesNoLocks + void releasesNoLocksMethodWithSynchronizedBlock() { + synchronized (this) { + } } - } - @LockingFree - void lockingFreeMethodWithSynchronizedBlock() { - // :: error: (synchronized.block.in.lockingfree.method) - synchronized (this) { + @LockingFree + void lockingFreeMethodWithSynchronizedBlock() { + // :: error: (synchronized.block.in.lockingfree.method) + synchronized (this) { + } } - } - @SideEffectFree - void sideEffectFreeMethodWithSynchronizedBlock() { - // :: error: (synchronized.block.in.lockingfree.method) - synchronized (this) { + @SideEffectFree + void sideEffectFreeMethodWithSynchronizedBlock() { + // :: error: (synchronized.block.in.lockingfree.method) + synchronized (this) { + } } - } - @Pure - void pureMethodWithSynchronizedBlock() { - // :: error: (synchronized.block.in.lockingfree.method) - synchronized (this) { + @Pure + void pureMethodWithSynchronizedBlock() { + // :: error: (synchronized.block.in.lockingfree.method) + synchronized (this) { + } } - } - // :: warning: (inconsistent.constructor.type) - @GuardedByUnknown class MyClass2 {} + // :: warning: (inconsistent.constructor.type) + @GuardedByUnknown class MyClass2 {} - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("lock") class MyClass3 {} + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("lock") class MyClass3 {} - @GuardedBy({}) class MyClass4 {} + @GuardedBy({}) class MyClass4 {} - // :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied class MyClass5 {} + // :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied class MyClass5 {} - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @GuardedByBottom class MyClass6 {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @GuardedByBottom class MyClass6 {} } diff --git a/checker/tests/lock/LockExpressionIsFinal.java b/checker/tests/lock/LockExpressionIsFinal.java index da2316b3e5a..23de75eb20f 100644 --- a/checker/tests/lock/LockExpressionIsFinal.java +++ b/checker/tests/lock/LockExpressionIsFinal.java @@ -1,328 +1,331 @@ -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByUnknown; import org.checkerframework.checker.lock.qual.MayReleaseLocks; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; -public class LockExpressionIsFinal { - - class MyClass { - Object field = new Object(); - } +import java.util.concurrent.locks.ReentrantLock; - class C1 { - final C1 field = new C1(); // Infinite loop. This code is not meant to be executed, only type - // checked. - C1 field2; +public class LockExpressionIsFinal { - @Deterministic - C1 getFieldDeterministic() { - return field; + class MyClass { + Object field = new Object(); } - @Pure - C1 getFieldPure(Object param1, Object param2) { - return field; + class C1 { + final C1 field = + new C1(); // Infinite loop. This code is not meant to be executed, only type + // checked. + C1 field2; + + @Deterministic + C1 getFieldDeterministic() { + return field; + } + + @Pure + C1 getFieldPure(Object param1, Object param2) { + return field; + } + + @Pure + C1 getFieldPure2() { + return field; + } + + C1 getField() { + return field; + } } - @Pure - C1 getFieldPure2() { - return field; - } + final C1 c1 = new C1(); - C1 getField() { - return field; - } - } + // Analogous to testExplicitLockExpressionIsFinal and testGuardedByExpressionIsFinal, but for + // monitor locks acquired in synchronized blocks. + void testSynchronizedExpressionIsFinal(boolean b) { + synchronized (c1) { + } + + Object o1 = new Object(); // o1 is effectively final - it is never reassigned + Object o2 = new Object(); // o2 is reassigned later - it is not effectively final + synchronized (o1) { + } + // :: error: (lock.expression.not.final) + synchronized (o2) { + } - final C1 c1 = new C1(); + o2 = new Object(); // Reassignment that makes o2 not have been effectively final earlier. - // Analogous to testExplicitLockExpressionIsFinal and testGuardedByExpressionIsFinal, but for - // monitor locks acquired in synchronized blocks. - void testSynchronizedExpressionIsFinal(boolean b) { - synchronized (c1) { - } + // Tests that package names are considered final. + synchronized (java.lang.String.class) { + } - Object o1 = new Object(); // o1 is effectively final - it is never reassigned - Object o2 = new Object(); // o2 is reassigned later - it is not effectively final - synchronized (o1) { - } - // :: error: (lock.expression.not.final) - synchronized (o2) { - } + // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal + // :: error: (lock.expression.possibly.not.final) + synchronized (c1.getFieldPure(b ? c1 : o1, c1)) { + } - o2 = new Object(); // Reassignment that makes o2 not have been effectively final earlier. + synchronized ( + c1.field.field.field.getFieldPure( + c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) + .field) { + } - // Tests that package names are considered final. - synchronized (java.lang.String.class) { - } + // The following negative test cases are the same as the one above but with one modification + // in each. - // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal - // :: error: (lock.expression.possibly.not.final) - synchronized (c1.getFieldPure(b ? c1 : o1, c1)) { + synchronized ( + // :: error: (lock.expression.not.final) + c1.field.field2.field.getFieldPure( + c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) + .field) { + } + synchronized ( + c1.field.field.field.getFieldPure( + // :: error: (lock.expression.not.final) + c1.field, c1.getField().getFieldPure(c1, c1.field)) + .field) { + } } - synchronized ( - c1.field.field.field.getFieldPure( - c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) - .field) { + class C2 extends ReentrantLock { + final C2 field = + new C2(); // Infinite loop. The code is not meant to be executed, only type-checked. + C2 field2; + + @Deterministic + C2 getFieldDeterministic() { + return field; + } + + @Pure + C2 getFieldPure(Object param1, Object param2) { + return field; + } + + C2 getField() { + return field; + } } - // The following negative test cases are the same as the one above but with one modification - // in each. + final C2 c2 = new C2(); - synchronized ( + // Analogous to testSynchronizedExpressionIsFinal and testGuardedByExpressionIsFinal, but for + // explicit locks. + @MayReleaseLocks + void testExplicitLockExpressionIsFinal(boolean b) { + c2.lock(); + + ReentrantLock rl1 = + new ReentrantLock(); // rl1 is effectively final - it is never reassigned + ReentrantLock rl2 = + new ReentrantLock(); // rl2 is reassigned later - it is not effectively final + rl1.lock(); + rl1.unlock(); // :: error: (lock.expression.not.final) - c1.field.field2.field.getFieldPure( - c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) - .field) { - } - synchronized ( - c1.field.field.field.getFieldPure( + rl2.lock(); + // :: error: (lock.expression.not.final) + rl2.unlock(); + + rl2 = new ReentrantLock(); // Reassignment that makes rl2 not have been effectively final + // earlier. + + // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal + // :: error: (lock.expression.possibly.not.final) + c2.getFieldPure(b ? c2 : rl1, c2).lock(); + // :: error: (lock.expression.possibly.not.final) + c2.getFieldPure(b ? c2 : rl1, c2).unlock(); + + c2.field + .field + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .lock(); + c2.field + .field + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .unlock(); + + // The following negative test cases are the same as the one above but with one modification + // in each. + + c2.field + // :: error: (lock.expression.not.final) + .field2 + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .lock(); + c2.field + // :: error: (lock.expression.not.final) + .field2 + .field + .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) + .field + .unlock(); + + c2.field + .field + .field + // :: error: (lock.expression.not.final) + .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) + .field + .lock(); + c2.field + .field + .field // :: error: (lock.expression.not.final) - c1.field, c1.getField().getFieldPure(c1, c1.field)) - .field) { + .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) + .field + .unlock(); } - } - class C2 extends ReentrantLock { - final C2 field = - new C2(); // Infinite loop. The code is not meant to be executed, only type-checked. - C2 field2; + // Analogous to testSynchronizedExpressionIsFinal and testExplicitLockExpressionIsFinal, but for + // expressions in @GuardedBy annotations. + void testGuardedByExpressionIsFinal() { + @GuardedBy("c1") Object guarded1; - @Deterministic - C2 getFieldDeterministic() { - return field; - } + final Object o1 = new Object(); + Object o2 = new Object(); + // reassign so it's not effectively final + o2 = new Object(); - @Pure - C2 getFieldPure(Object param1, Object param2) { - return field; - } + @GuardedBy("o1") Object guarded2 = new Object(); + // :: error: (lock.expression.not.final) + @GuardedBy("o2") Object guarded3 = new Object(); - C2 getField() { - return field; - } - } + // Test expressions that are not supported by LockVisitor.ensureExpressionIsEffectivelyFinal + @GuardedBy("java.lang.String.class") Object guarded4; + // :: error: (expression.unparsable.type.invalid) + @GuardedBy("c1.getFieldPure(b ? c1 : o1, c1)") Object guarded5; - final C2 c2 = new C2(); + @GuardedBy( + "c1.field.field.field.getFieldPure" + + "(c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)).field") + Object guarded6; - // Analogous to testSynchronizedExpressionIsFinal and testGuardedByExpressionIsFinal, but for - // explicit locks. - @MayReleaseLocks - void testExplicitLockExpressionIsFinal(boolean b) { - c2.lock(); + @GuardedBy("c1.field.field.field.getFieldPure2().getFieldDeterministic().field") Object guarded7; + + // The following negative test cases are the same as the one above but with one modification + // in each. - ReentrantLock rl1 = new ReentrantLock(); // rl1 is effectively final - it is never reassigned - ReentrantLock rl2 = - new ReentrantLock(); // rl2 is reassigned later - it is not effectively final - rl1.lock(); - rl1.unlock(); - // :: error: (lock.expression.not.final) - rl2.lock(); - // :: error: (lock.expression.not.final) - rl2.unlock(); - - rl2 = new ReentrantLock(); // Reassignment that makes rl2 not have been effectively final - // earlier. - - // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal - // :: error: (lock.expression.possibly.not.final) - c2.getFieldPure(b ? c2 : rl1, c2).lock(); - // :: error: (lock.expression.possibly.not.final) - c2.getFieldPure(b ? c2 : rl1, c2).unlock(); - - c2.field - .field - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .lock(); - c2.field - .field - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .unlock(); - - // The following negative test cases are the same as the one above but with one modification - // in each. - - c2.field // :: error: (lock.expression.not.final) - .field2 - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .lock(); - c2.field + @GuardedBy("c1.field.field2.field.getFieldPure2().getFieldDeterministic().field") Object guarded8; // :: error: (lock.expression.not.final) - .field2 - .field - .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) - .field - .unlock(); - - c2.field - .field - .field + @GuardedBy("c1.field.field.field.getField().getFieldDeterministic().field") Object guarded9; + + // Additional test cases to test that method parameters (in this case the parameters to + // getFieldPure) are parsed. + @GuardedBy("c1.field.field.field.getFieldPure(c1, c1).getFieldDeterministic().field") Object guarded10; + @GuardedBy("c1.field.field.field.getFieldPure(c1, o1).getFieldDeterministic().field") Object guarded11; // :: error: (lock.expression.not.final) - .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) - .field - .lock(); - c2.field - .field - .field + @GuardedBy("c1.field.field.field.getFieldPure(c1, o2).getFieldDeterministic().field") Object guarded12; + + // Test that @GuardedBy annotations on various tree kinds inside a method are visited + + Object guarded13 = (@GuardedBy("o1") Object) guarded2; // :: error: (lock.expression.not.final) - .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) - .field - .unlock(); - } - - // Analogous to testSynchronizedExpressionIsFinal and testExplicitLockExpressionIsFinal, but for - // expressions in @GuardedBy annotations. - void testGuardedByExpressionIsFinal() { - @GuardedBy("c1") Object guarded1; - - final Object o1 = new Object(); - Object o2 = new Object(); - // reassign so it's not effectively final - o2 = new Object(); - - @GuardedBy("o1") Object guarded2 = new Object(); - // :: error: (lock.expression.not.final) - @GuardedBy("o2") Object guarded3 = new Object(); + Object guarded14 = (@GuardedBy("o2") Object) guarded3; - // Test expressions that are not supported by LockVisitor.ensureExpressionIsEffectivelyFinal - @GuardedBy("java.lang.String.class") Object guarded4; - // :: error: (expression.unparsable.type.invalid) - @GuardedBy("c1.getFieldPure(b ? c1 : o1, c1)") Object guarded5; + @GuardedBy("o1") Object guarded15[] = new @GuardedBy("o1") MyClass[3]; + // :: error: (lock.expression.not.final) + @GuardedBy("o2") Object guarded16[] = new @GuardedBy("o2") MyClass[3]; - @GuardedBy( - "c1.field.field.field.getFieldPure" - + "(c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)).field") - Object guarded6; + // Tests that the location of the @GB annotation inside a VariableTree does not matter (i.e. + // it does not need to be the leftmost subtree). + Object guarded17 @GuardedBy("o1") []; + // :: error: (lock.expression.not.final) + Object guarded18 @GuardedBy("o2") []; - @GuardedBy("c1.field.field.field.getFieldPure2().getFieldDeterministic().field") Object guarded7; + @GuardedBy("o1") Object guarded19[]; + // :: error: (lock.expression.not.final) + @GuardedBy("o2") Object guarded20[]; - // The following negative test cases are the same as the one above but with one modification - // in each. + MyParameterizedClass1<@GuardedBy("o1") Object> m1; + // :: error: (lock.expression.not.final) + MyParameterizedClass1<@GuardedBy("o2") Object> m2; - // :: error: (lock.expression.not.final) - @GuardedBy("c1.field.field2.field.getFieldPure2().getFieldDeterministic().field") Object guarded8; - // :: error: (lock.expression.not.final) - @GuardedBy("c1.field.field.field.getField().getFieldDeterministic().field") Object guarded9; + boolean b = c1 instanceof @GuardedBy("o1") Object; + // instanceof expression have not effect on the type. + // // :: error: (lock.expression.not.final) + b = c1 instanceof @GuardedBy("o2") Object; - // Additional test cases to test that method parameters (in this case the parameters to - // getFieldPure) are parsed. - @GuardedBy("c1.field.field.field.getFieldPure(c1, c1).getFieldDeterministic().field") Object guarded10; - @GuardedBy("c1.field.field.field.getFieldPure(c1, o1).getFieldDeterministic().field") Object guarded11; - // :: error: (lock.expression.not.final) - @GuardedBy("c1.field.field.field.getFieldPure(c1, o2).getFieldDeterministic().field") Object guarded12; + // Additional tests just outside of this method below: + } - // Test that @GuardedBy annotations on various tree kinds inside a method are visited + // Test that @GuardedBy annotations on various tree kinds outside a method are visited - Object guarded13 = (@GuardedBy("o1") Object) guarded2; - // :: error: (lock.expression.not.final) - Object guarded14 = (@GuardedBy("o2") Object) guarded3; + // Test that @GuardedBy annotations on method return types are visited. No need to test method + // receivers and parameters as they are covered by tests above that visit VariableTree. - @GuardedBy("o1") Object guarded15[] = new @GuardedBy("o1") MyClass[3]; - // :: error: (lock.expression.not.final) - @GuardedBy("o2") Object guarded16[] = new @GuardedBy("o2") MyClass[3]; + final Object finalField = new Object(); + Object nonFinalField = new Object(); + + @GuardedBy("finalField") Object testGuardedByExprIsFinal1() { + return null; + } - // Tests that the location of the @GB annotation inside a VariableTree does not matter (i.e. - // it does not need to be the leftmost subtree). - Object guarded17 @GuardedBy("o1") []; // :: error: (lock.expression.not.final) - Object guarded18 @GuardedBy("o2") []; + @GuardedBy("nonFinalField") Object testGuardedByExprIsFinal2() { + return null; + } + + T myMethodThatReturnsT_1(T t) { + return t; + } - @GuardedBy("o1") Object guarded19[]; // :: error: (lock.expression.not.final) - @GuardedBy("o2") Object guarded20[]; + T myMethodThatReturnsT_2(T t) { + return t; + } + + class MyParameterizedClass1 {} - MyParameterizedClass1<@GuardedBy("o1") Object> m1; + MyParameterizedClass1 m1; // :: error: (lock.expression.not.final) - MyParameterizedClass1<@GuardedBy("o2") Object> m2; - - boolean b = c1 instanceof @GuardedBy("o1") Object; - // instanceof expression have not effect on the type. - // // :: error: (lock.expression.not.final) - b = c1 instanceof @GuardedBy("o2") Object; - - // Additional tests just outside of this method below: - } - - // Test that @GuardedBy annotations on various tree kinds outside a method are visited - - // Test that @GuardedBy annotations on method return types are visited. No need to test method - // receivers and parameters as they are covered by tests above that visit VariableTree. - - final Object finalField = new Object(); - Object nonFinalField = new Object(); - - @GuardedBy("finalField") Object testGuardedByExprIsFinal1() { - return null; - } - - // :: error: (lock.expression.not.final) - @GuardedBy("nonFinalField") Object testGuardedByExprIsFinal2() { - return null; - } - - T myMethodThatReturnsT_1(T t) { - return t; - } - - // :: error: (lock.expression.not.final) - T myMethodThatReturnsT_2(T t) { - return t; - } - - class MyParameterizedClass1 {} - - MyParameterizedClass1 m1; - // :: error: (lock.expression.not.final) - MyParameterizedClass1 m2; - - MyParameterizedClass1 m3; - // :: error: (lock.expression.not.final) - MyParameterizedClass1 m4; - - class MyClassContainingALock { - final ReentrantLock finalLock = new ReentrantLock(); - ReentrantLock nonFinalLock = new ReentrantLock(); - Object field; - } - - void testItselfFinalLock() { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - final @GuardedBy(".finalLock") MyClassContainingALock m = someValue(); - // :: error: (lock.not.held) - m.field = new Object(); - // Ignore this error: it is expected that an error will be issued for dereferencing 'm' in - // order to take the 'm.finalLock' lock. Typically, the Lock Checker does not support an - // object being guarded by one of its fields, but this is sometimes done in user code with a - // ReentrantLock field guarding its containing object. This unfortunately makes it a bit - // difficult for users since they have to add a @SuppressWarnings for this call while still - // making sure that warnings for other dereferences are not suppressed. - // :: error: (lock.not.held) - m.finalLock.lock(); - m.field = new Object(); - } - - void testItselfNonFinalLock() { - @SuppressWarnings("assignment") // prevent flow-sensitive type refinement - final @GuardedBy(".nonFinalLock") MyClassContainingALock m = someValue(); - // ::error: (lock.not.held) :: error: (lock.expression.not.final) - m.field = new Object(); - // ::error: (lock.not.held) :: error: (lock.expression.not.final) - m.nonFinalLock.lock(); + MyParameterizedClass1 m2; + + MyParameterizedClass1 m3; // :: error: (lock.expression.not.final) - m.field = new Object(); - } + MyParameterizedClass1 m4; - @GuardedByUnknown MyClassContainingALock someValue() { - return new MyClassContainingALock(); - } + class MyClassContainingALock { + final ReentrantLock finalLock = new ReentrantLock(); + ReentrantLock nonFinalLock = new ReentrantLock(); + Object field; + } + + void testItselfFinalLock() { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + final @GuardedBy(".finalLock") MyClassContainingALock m = someValue(); + // :: error: (lock.not.held) + m.field = new Object(); + // Ignore this error: it is expected that an error will be issued for dereferencing 'm' in + // order to take the 'm.finalLock' lock. Typically, the Lock Checker does not support an + // object being guarded by one of its fields, but this is sometimes done in user code with a + // ReentrantLock field guarding its containing object. This unfortunately makes it a bit + // difficult for users since they have to add a @SuppressWarnings for this call while still + // making sure that warnings for other dereferences are not suppressed. + // :: error: (lock.not.held) + m.finalLock.lock(); + m.field = new Object(); + } + + void testItselfNonFinalLock() { + @SuppressWarnings("assignment") // prevent flow-sensitive type refinement + final @GuardedBy(".nonFinalLock") MyClassContainingALock m = someValue(); + // ::error: (lock.not.held) :: error: (lock.expression.not.final) + m.field = new Object(); + // ::error: (lock.not.held) :: error: (lock.expression.not.final) + m.nonFinalLock.lock(); + // :: error: (lock.expression.not.final) + m.field = new Object(); + } + + @GuardedByUnknown MyClassContainingALock someValue() { + return new MyClassContainingALock(); + } } diff --git a/checker/tests/lock/LockInterfaceTest.java b/checker/tests/lock/LockInterfaceTest.java index 434bdf62c05..295b832eb70 100644 --- a/checker/tests/lock/LockInterfaceTest.java +++ b/checker/tests/lock/LockInterfaceTest.java @@ -1,55 +1,56 @@ // Test of use of Lock interface -import java.util.Date; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.Holding; import org.checkerframework.checker.lock.qual.MayReleaseLocks; import org.checkerframework.checker.lock.qual.ReleasesNoLocks; +import java.util.Date; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + public class LockInterfaceTest { - static final Lock myStaticLock = new ReentrantLock(true); - - private static final Date x = new Date((long) (System.currentTimeMillis() * Math.random())); - - @Holding("myStaticLock") - @ReleasesNoLocks - static void method4() { - System.out.println(x); - } - - @Holding("LockInterfaceTest.myStaticLock") - @ReleasesNoLocks - static void method5() { - System.out.println(x); - } - - @MayReleaseLocks - public static void test1() { - LockInterfaceTest.myStaticLock.lock(); - method4(); - LockInterfaceTest.myStaticLock.unlock(); - } - - @MayReleaseLocks - public static void test2() { - LockInterfaceTest.myStaticLock.lock(); - method5(); - LockInterfaceTest.myStaticLock.unlock(); - } - - @MayReleaseLocks - public static void test3() { - myStaticLock.lock(); - method4(); - myStaticLock.unlock(); - } - - @MayReleaseLocks - public static void test4() { - myStaticLock.lock(); - method5(); - myStaticLock.unlock(); - } + static final Lock myStaticLock = new ReentrantLock(true); + + private static final Date x = new Date((long) (System.currentTimeMillis() * Math.random())); + + @Holding("myStaticLock") + @ReleasesNoLocks + static void method4() { + System.out.println(x); + } + + @Holding("LockInterfaceTest.myStaticLock") + @ReleasesNoLocks + static void method5() { + System.out.println(x); + } + + @MayReleaseLocks + public static void test1() { + LockInterfaceTest.myStaticLock.lock(); + method4(); + LockInterfaceTest.myStaticLock.unlock(); + } + + @MayReleaseLocks + public static void test2() { + LockInterfaceTest.myStaticLock.lock(); + method5(); + LockInterfaceTest.myStaticLock.unlock(); + } + + @MayReleaseLocks + public static void test3() { + myStaticLock.lock(); + method4(); + myStaticLock.unlock(); + } + + @MayReleaseLocks + public static void test4() { + myStaticLock.lock(); + method5(); + myStaticLock.unlock(); + } } diff --git a/checker/tests/lock/Methods.java b/checker/tests/lock/Methods.java index 2f3c92e919c..464083d1ec4 100644 --- a/checker/tests/lock/Methods.java +++ b/checker/tests/lock/Methods.java @@ -2,55 +2,55 @@ public class Methods { - final Object lock = new Object(); - - @Holding("lock") - void lockedByLock() {} - - @Holding("this") - void lockedByThis() {} - - // unguarded calls - void unguardedCalls() { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - // :: error: (contracts.precondition.not.satisfied) - lockedByThis(); // error - } - - @Holding("lock") - void usingHolding1() { - lockedByLock(); - // :: error: (contracts.precondition.not.satisfied) - lockedByThis(); // error - } - - @Holding("this") - void usingHolding2() { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - lockedByThis(); - } - - void usingSynchronization1() { - synchronized (lock) { - lockedByLock(); - // :: error: (contracts.precondition.not.satisfied) - lockedByThis(); // error + final Object lock = new Object(); + + @Holding("lock") + void lockedByLock() {} + + @Holding("this") + void lockedByThis() {} + + // unguarded calls + void unguardedCalls() { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + // :: error: (contracts.precondition.not.satisfied) + lockedByThis(); // error + } + + @Holding("lock") + void usingHolding1() { + lockedByLock(); + // :: error: (contracts.precondition.not.satisfied) + lockedByThis(); // error } - } - void usingSynchronization2() { - synchronized (this) { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - lockedByThis(); + @Holding("this") + void usingHolding2() { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + lockedByThis(); } - } - synchronized void usingMethodModifier() { - // :: error: (contracts.precondition.not.satisfied) - lockedByLock(); // error - lockedByThis(); - } + void usingSynchronization1() { + synchronized (lock) { + lockedByLock(); + // :: error: (contracts.precondition.not.satisfied) + lockedByThis(); // error + } + } + + void usingSynchronization2() { + synchronized (this) { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + lockedByThis(); + } + } + + synchronized void usingMethodModifier() { + // :: error: (contracts.precondition.not.satisfied) + lockedByLock(); // error + lockedByThis(); + } } diff --git a/checker/tests/lock/NestedSynchronizedBlocks.java b/checker/tests/lock/NestedSynchronizedBlocks.java index ccbf3446942..edf58aa3943 100644 --- a/checker/tests/lock/NestedSynchronizedBlocks.java +++ b/checker/tests/lock/NestedSynchronizedBlocks.java @@ -1,42 +1,42 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class NestedSynchronizedBlocks { - class MyClass { - public Object field; - } + class MyClass { + public Object field; + } - @GuardedBy("lock1") MyClass m1; + @GuardedBy("lock1") MyClass m1; - @GuardedBy("lock2") MyClass m2; + @GuardedBy("lock2") MyClass m2; - @GuardedBy("lock3") MyClass m3; + @GuardedBy("lock3") MyClass m3; - @GuardedBy("lock4") MyClass m4; + @GuardedBy("lock4") MyClass m4; - final Object lock1 = new Object(), - lock2 = new Object(), - lock3 = new Object(), - lock4 = new Object(); + final Object lock1 = new Object(), + lock2 = new Object(), + lock3 = new Object(), + lock4 = new Object(); - void foo() { - synchronized (lock1) { - synchronized (lock2) { - synchronized (lock3) { - synchronized (lock4) { - } + void foo() { + synchronized (lock1) { + synchronized (lock2) { + synchronized (lock3) { + synchronized (lock4) { + } + } + } } - } - } - // Test that the locks are known to have been released. - - // :: error:(lock.not.held) - m1.field = new Object(); - // :: error:(lock.not.held) - m2.field = new Object(); - // :: error:(lock.not.held) - m3.field = new Object(); - // :: error:(lock.not.held) - m4.field = new Object(); - } + // Test that the locks are known to have been released. + + // :: error:(lock.not.held) + m1.field = new Object(); + // :: error:(lock.not.held) + m2.field = new Object(); + // :: error:(lock.not.held) + m3.field = new Object(); + // :: error:(lock.not.held) + m4.field = new Object(); + } } diff --git a/checker/tests/lock/Overriding.java b/checker/tests/lock/Overriding.java index 670ff3c29e6..fae304c2070 100644 --- a/checker/tests/lock/Overriding.java +++ b/checker/tests/lock/Overriding.java @@ -3,158 +3,158 @@ public class Overriding { - class SuperClass { - protected Object a, b, c; - - @Holding("a") - void guardedByOne() {} - - @Holding({"a", "b"}) - void guardedByTwo() {} - - @Holding({"a", "b", "c"}) - void guardedByThree() {} - - @ReleasesNoLocks - void rnlMethod() { - // :: error: (method.guarantee.violated) - mrlMethod(); - rnlMethod(); - implicitRnlMethod(); - lfMethod(); - } - - void implicitRnlMethod() { - // :: error: (method.guarantee.violated) - mrlMethod(); - rnlMethod(); - implicitRnlMethod(); - lfMethod(); - } - - @LockingFree - void lfMethod() { - // :: error: (method.guarantee.violated) - mrlMethod(); - // :: error: (method.guarantee.violated) - rnlMethod(); - // :: error: (method.guarantee.violated) - implicitRnlMethod(); - lfMethod(); + class SuperClass { + protected Object a, b, c; + + @Holding("a") + void guardedByOne() {} + + @Holding({"a", "b"}) + void guardedByTwo() {} + + @Holding({"a", "b", "c"}) + void guardedByThree() {} + + @ReleasesNoLocks + void rnlMethod() { + // :: error: (method.guarantee.violated) + mrlMethod(); + rnlMethod(); + implicitRnlMethod(); + lfMethod(); + } + + void implicitRnlMethod() { + // :: error: (method.guarantee.violated) + mrlMethod(); + rnlMethod(); + implicitRnlMethod(); + lfMethod(); + } + + @LockingFree + void lfMethod() { + // :: error: (method.guarantee.violated) + mrlMethod(); + // :: error: (method.guarantee.violated) + rnlMethod(); + // :: error: (method.guarantee.violated) + implicitRnlMethod(); + lfMethod(); + } + + @MayReleaseLocks + void mrlMethod() { + mrlMethod(); + rnlMethod(); + implicitRnlMethod(); + lfMethod(); + } + + @ReleasesNoLocks + void rnlMethod2() {} + + void implicitRnlMethod2() {} + + @LockingFree + void lfMethod2() {} + + @MayReleaseLocks + void mrlMethod2() {} + + @ReleasesNoLocks + void rnlMethod3() {} + + void implicitRnlMethod3() {} + + @LockingFree + void lfMethod3() {} } - @MayReleaseLocks - void mrlMethod() { - mrlMethod(); - rnlMethod(); - implicitRnlMethod(); - lfMethod(); + class SubClass extends SuperClass { + @Holding({"a", "b"}) // error + @Override + // :: error: (contracts.precondition.override.invalid) + void guardedByOne() {} + + @Holding({"a", "b"}) + @Override + void guardedByTwo() {} + + @Holding({"a", "b"}) + @Override + void guardedByThree() {} + + @MayReleaseLocks + @Override + // :: error: (override.sideeffect.invalid) + void rnlMethod() {} + + @MayReleaseLocks + @Override + // :: error: (override.sideeffect.invalid) + void implicitRnlMethod() {} + + @ReleasesNoLocks + @Override + // :: error: (override.sideeffect.invalid) + void lfMethod() {} + + @MayReleaseLocks + @Override + void mrlMethod() {} + + @ReleasesNoLocks + @Override + void rnlMethod2() {} + + @Override + void implicitRnlMethod2() {} + + @LockingFree + @Override + void lfMethod2() {} + + @ReleasesNoLocks + @Override + void mrlMethod2() {} + + @LockingFree + @Override + void rnlMethod3() {} + + @LockingFree + @Override + void implicitRnlMethod3() {} + + @SideEffectFree + @Override + void lfMethod3() {} } - @ReleasesNoLocks - void rnlMethod2() {} - - void implicitRnlMethod2() {} - - @LockingFree - void lfMethod2() {} - - @MayReleaseLocks - void mrlMethod2() {} - - @ReleasesNoLocks - void rnlMethod3() {} - - void implicitRnlMethod3() {} - - @LockingFree - void lfMethod3() {} - } - - class SubClass extends SuperClass { - @Holding({"a", "b"}) // error - @Override - // :: error: (contracts.precondition.override.invalid) - void guardedByOne() {} - - @Holding({"a", "b"}) - @Override - void guardedByTwo() {} + // Test overriding @Holding with JCIP @GuardedBy. + class SubClassJcip extends SuperClass { + @net.jcip.annotations.GuardedBy({"a", "b"}) @Override + // :: error: (contracts.precondition.override.invalid) + void guardedByOne() {} - @Holding({"a", "b"}) - @Override - void guardedByThree() {} + @net.jcip.annotations.GuardedBy({"a", "b"}) @Override + void guardedByTwo() {} - @MayReleaseLocks - @Override - // :: error: (override.sideeffect.invalid) - void rnlMethod() {} - - @MayReleaseLocks - @Override - // :: error: (override.sideeffect.invalid) - void implicitRnlMethod() {} - - @ReleasesNoLocks - @Override - // :: error: (override.sideeffect.invalid) - void lfMethod() {} - - @MayReleaseLocks - @Override - void mrlMethod() {} - - @ReleasesNoLocks - @Override - void rnlMethod2() {} - - @Override - void implicitRnlMethod2() {} - - @LockingFree - @Override - void lfMethod2() {} - - @ReleasesNoLocks - @Override - void mrlMethod2() {} - - @LockingFree - @Override - void rnlMethod3() {} - - @LockingFree - @Override - void implicitRnlMethod3() {} - - @SideEffectFree - @Override - void lfMethod3() {} - } - - // Test overriding @Holding with JCIP @GuardedBy. - class SubClassJcip extends SuperClass { - @net.jcip.annotations.GuardedBy({"a", "b"}) @Override - // :: error: (contracts.precondition.override.invalid) - void guardedByOne() {} - - @net.jcip.annotations.GuardedBy({"a", "b"}) @Override - void guardedByTwo() {} - - @net.jcip.annotations.GuardedBy({"a", "b"}) @Override - void guardedByThree() {} - } + @net.jcip.annotations.GuardedBy({"a", "b"}) @Override + void guardedByThree() {} + } - // Test overriding @Holding with Javax @GuardedBy. - class SubClassJavax extends SuperClass { - @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override - // :: error: (contracts.precondition.override.invalid) - void guardedByOne() {} + // Test overriding @Holding with Javax @GuardedBy. + class SubClassJavax extends SuperClass { + @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override + // :: error: (contracts.precondition.override.invalid) + void guardedByOne() {} - @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override - void guardedByTwo() {} + @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override + void guardedByTwo() {} - @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override - void guardedByThree() {} - } + @javax.annotation.concurrent.GuardedBy({"a", "b"}) @Override + void guardedByThree() {} + } } diff --git a/checker/tests/lock/PrimitivesLocking.java b/checker/tests/lock/PrimitivesLocking.java index 38ab2845c03..2149feb8f25 100644 --- a/checker/tests/lock/PrimitivesLocking.java +++ b/checker/tests/lock/PrimitivesLocking.java @@ -5,157 +5,157 @@ // Note that testing of the immutable.type.guardedby error message is done in TestTreeKinds.java public class PrimitivesLocking { - // @GuardedByName("lock") - int primitive = 1; - - // @GuardedByName("lock") - boolean primitiveBoolean; - - public void testOperationsWithPrimitives() { // @GuardedByName("lock") - int i = 0; + int primitive = 1; + // @GuardedByName("lock") - boolean b; - - // TODO reenable this error: (lock.not.held) - i = i >>> primitive; - // TODO reenable this error: (lock.not.held) - i = primitive >>> i; - - // TODO reenable this error: (lock.not.held) - i >>>= primitive; - // TODO reenable this error: (lock.not.held) - primitive >>>= i; - - // TODO reenable this error: (lock.not.held) - i %= primitive; - // TODO reenable this error: (lock.not.held) - i = 4 % primitive; - // TODO reenable this error: (lock.not.held) - i = primitive % 4; - - // TODO reenable this error: (lock.not.held) - primitive++; - // TODO reenable this error: (lock.not.held) - primitive--; - // TODO reenable this error: (lock.not.held) - ++primitive; - // TODO reenable this error: (lock.not.held) - --primitive; - - // TODO reenable this error: (lock.not.held) - if (primitive != 5) {} - - // TODO reenable this error: (lock.not.held) - i = primitive >> i; - // TODO reenable this error: (lock.not.held) - i = primitive << i; - // TODO reenable this error: (lock.not.held) - i = i >> primitive; - // TODO reenable this error: (lock.not.held) - i = i << primitive; - - // TODO reenable this error: (lock.not.held) - i <<= primitive; - // TODO reenable this error: (lock.not.held) - i >>= primitive; - // TODO reenable this error: (lock.not.held) - primitive <<= i; - // TODO reenable this error: (lock.not.held) - primitive >>= i; - - // TODO reenable this error: (lock.not.held) - assert (primitiveBoolean); - - // TODO reenable this error: (lock.not.held) - b = primitive >= i; - // TODO reenable this error: (lock.not.held) - b = primitive <= i; - // TODO reenable this error: (lock.not.held) - b = primitive > i; - // TODO reenable this error: (lock.not.held) - b = primitive < i; - // TODO reenable this error: (lock.not.held) - b = i >= primitive; - // TODO reenable this error: (lock.not.held) - b = i <= primitive; - // TODO reenable this error: (lock.not.held) - b = i > primitive; - // TODO reenable this error: (lock.not.held) - b = i < primitive; - - // TODO reenable this error: (lock.not.held) - i += primitive; - // TODO reenable this error: (lock.not.held) - i -= primitive; - // TODO reenable this error: (lock.not.held) - i *= primitive; - // TODO reenable this error: (lock.not.held) - i /= primitive; - - // TODO reenable this error: (lock.not.held) - i = 4 + primitive; - // TODO reenable this error: (lock.not.held) - i = 4 - primitive; - // TODO reenable this error: (lock.not.held) - i = 4 * primitive; - // TODO reenable this error: (lock.not.held) - i = 4 / primitive; - - // TODO reenable this error: (lock.not.held) - i = primitive + 4; - // TODO reenable this error: (lock.not.held) - i = primitive - 4; - // TODO reenable this error: (lock.not.held) - i = primitive * 4; - // TODO reenable this error: (lock.not.held) - i = primitive / 4; - - // TODO reenable this error: (lock.not.held) - if (primitiveBoolean) {} - - // TODO reenable this error: (lock.not.held) - i = ~primitive; - - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean || false; - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean | false; - - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean ^ true; - - // TODO reenable this error: (lock.not.held) - b &= primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b |= primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b ^= primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = !primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - i = primitive; - - // TODO reenable this error: (lock.not.held) - b = true && primitiveBoolean; - // TODO reenable this error: (lock.not.held) - b = true & primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = false || primitiveBoolean; - // TODO reenable this error: (lock.not.held) - b = false | primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = false ^ primitiveBoolean; - - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean && true; - // TODO reenable this error: (lock.not.held) - b = primitiveBoolean & true; - } + boolean primitiveBoolean; + + public void testOperationsWithPrimitives() { + // @GuardedByName("lock") + int i = 0; + // @GuardedByName("lock") + boolean b; + + // TODO reenable this error: (lock.not.held) + i = i >>> primitive; + // TODO reenable this error: (lock.not.held) + i = primitive >>> i; + + // TODO reenable this error: (lock.not.held) + i >>>= primitive; + // TODO reenable this error: (lock.not.held) + primitive >>>= i; + + // TODO reenable this error: (lock.not.held) + i %= primitive; + // TODO reenable this error: (lock.not.held) + i = 4 % primitive; + // TODO reenable this error: (lock.not.held) + i = primitive % 4; + + // TODO reenable this error: (lock.not.held) + primitive++; + // TODO reenable this error: (lock.not.held) + primitive--; + // TODO reenable this error: (lock.not.held) + ++primitive; + // TODO reenable this error: (lock.not.held) + --primitive; + + // TODO reenable this error: (lock.not.held) + if (primitive != 5) {} + + // TODO reenable this error: (lock.not.held) + i = primitive >> i; + // TODO reenable this error: (lock.not.held) + i = primitive << i; + // TODO reenable this error: (lock.not.held) + i = i >> primitive; + // TODO reenable this error: (lock.not.held) + i = i << primitive; + + // TODO reenable this error: (lock.not.held) + i <<= primitive; + // TODO reenable this error: (lock.not.held) + i >>= primitive; + // TODO reenable this error: (lock.not.held) + primitive <<= i; + // TODO reenable this error: (lock.not.held) + primitive >>= i; + + // TODO reenable this error: (lock.not.held) + assert (primitiveBoolean); + + // TODO reenable this error: (lock.not.held) + b = primitive >= i; + // TODO reenable this error: (lock.not.held) + b = primitive <= i; + // TODO reenable this error: (lock.not.held) + b = primitive > i; + // TODO reenable this error: (lock.not.held) + b = primitive < i; + // TODO reenable this error: (lock.not.held) + b = i >= primitive; + // TODO reenable this error: (lock.not.held) + b = i <= primitive; + // TODO reenable this error: (lock.not.held) + b = i > primitive; + // TODO reenable this error: (lock.not.held) + b = i < primitive; + + // TODO reenable this error: (lock.not.held) + i += primitive; + // TODO reenable this error: (lock.not.held) + i -= primitive; + // TODO reenable this error: (lock.not.held) + i *= primitive; + // TODO reenable this error: (lock.not.held) + i /= primitive; + + // TODO reenable this error: (lock.not.held) + i = 4 + primitive; + // TODO reenable this error: (lock.not.held) + i = 4 - primitive; + // TODO reenable this error: (lock.not.held) + i = 4 * primitive; + // TODO reenable this error: (lock.not.held) + i = 4 / primitive; + + // TODO reenable this error: (lock.not.held) + i = primitive + 4; + // TODO reenable this error: (lock.not.held) + i = primitive - 4; + // TODO reenable this error: (lock.not.held) + i = primitive * 4; + // TODO reenable this error: (lock.not.held) + i = primitive / 4; + + // TODO reenable this error: (lock.not.held) + if (primitiveBoolean) {} + + // TODO reenable this error: (lock.not.held) + i = ~primitive; + + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean || false; + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean | false; + + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean ^ true; + + // TODO reenable this error: (lock.not.held) + b &= primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b |= primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b ^= primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = !primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + i = primitive; + + // TODO reenable this error: (lock.not.held) + b = true && primitiveBoolean; + // TODO reenable this error: (lock.not.held) + b = true & primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = false || primitiveBoolean; + // TODO reenable this error: (lock.not.held) + b = false | primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = false ^ primitiveBoolean; + + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean && true; + // TODO reenable this error: (lock.not.held) + b = primitiveBoolean & true; + } } diff --git a/checker/tests/lock/ReturnsNewObjectTest.java b/checker/tests/lock/ReturnsNewObjectTest.java index 371f5118dde..6d780b938ab 100644 --- a/checker/tests/lock/ReturnsNewObjectTest.java +++ b/checker/tests/lock/ReturnsNewObjectTest.java @@ -2,16 +2,16 @@ import org.checkerframework.checker.lock.qual.NewObject; public class ReturnsNewObjectTest { - @NewObject Object factoryMethod() { - return new Object(); - } + @NewObject Object factoryMethod() { + return new Object(); + } - void m() { - @GuardedBy("this") Object x = factoryMethod(); - } + void m() { + @GuardedBy("this") Object x = factoryMethod(); + } - void m2() { - String @GuardedBy("this") [] a2 = new String[4]; - String @GuardedBy("this") [] a3 = new String[] {"a", "b", "c"}; - } + void m2() { + String @GuardedBy("this") [] a2 = new String[4]; + String @GuardedBy("this") [] a3 = new String[] {"a", "b", "c"}; + } } diff --git a/checker/tests/lock/SimpleLockTest.java b/checker/tests/lock/SimpleLockTest.java index 3709f9900a8..88222a99c9a 100644 --- a/checker/tests/lock/SimpleLockTest.java +++ b/checker/tests/lock/SimpleLockTest.java @@ -2,36 +2,39 @@ import org.checkerframework.checker.lock.qual.GuardedByUnknown; public class SimpleLockTest { - final Object lock1 = new Object(), lock2 = new Object(); + final Object lock1 = new Object(), lock2 = new Object(); - void testMethodCall(@GuardedBy("lock1") SimpleLockTest this) { - // :: error: (lock.not.held) - synchronized (lock1) { - } - // :: error: (lock.not.held) - synchronized (this.lock1) { - } - // :: error: (lock.not.held) - synchronized (lock2) { - } - // :: error: (lock.not.held) - synchronized (this.lock2) { - } + void testMethodCall(@GuardedBy("lock1") SimpleLockTest this) { + // :: error: (lock.not.held) + synchronized (lock1) { + } + // :: error: (lock.not.held) + synchronized (this.lock1) { + } + // :: error: (lock.not.held) + synchronized (lock2) { + } + // :: error: (lock.not.held) + synchronized (this.lock2) { + } - @SuppressWarnings({"assignment", "method.invocation"}) // prevent flow-sensitive type refinement - final @GuardedBy("myClass.field") MyClass myClass = someValue(); - // :: error: (lock.not.held) - synchronized (myClass.field) { + @SuppressWarnings({ + "assignment", + "method.invocation" + }) // prevent flow-sensitive type refinement + final @GuardedBy("myClass.field") MyClass myClass = someValue(); + // :: error: (lock.not.held) + synchronized (myClass.field) { + } + synchronized (myClass) { + } } - synchronized (myClass) { - } - } - @GuardedByUnknown MyClass someValue() { - return new MyClass(); - } + @GuardedByUnknown MyClass someValue() { + return new MyClass(); + } - class MyClass { - final Object field = new Object(); - } + class MyClass { + final Object field = new Object(); + } } diff --git a/checker/tests/lock/Strings.java b/checker/tests/lock/Strings.java index 5328a0d3d98..c29200e1d47 100644 --- a/checker/tests/lock/Strings.java +++ b/checker/tests/lock/Strings.java @@ -4,60 +4,60 @@ import org.checkerframework.checker.lock.qual.GuardedByUnknown; public class Strings { - final Object lock = new Object(); - - // These casts are safe because if the casted Object is a String, it must be @GuardedBy({}) - void StringIsGBnothing( - @GuardedByUnknown Object o1, - @GuardedBy("lock") Object o2, - @GuardSatisfied Object o3, - @GuardedByBottom Object o4) { - String s1 = (String) o1; - String s2 = (String) o2; - String s3 = (String) o3; - String s4 = (String) o4; // OK - } - - // Tests that the resulting type of string concatenation is always @GuardedBy({}) - // (and not @GuardedByUnknown, which is the LUB of @GuardedBy({}) (the type of the - // string literal "a") and @GuardedBy("lock") (the type of param)) - void StringConcat(@GuardedBy("lock") MyClass param) { - { - String s1a = "a" + "a"; - // :: error: (lock.not.held) - String s1b = "a" + param; - // :: error: (lock.not.held) - String s1c = param + "a"; - // :: error: (lock.not.held) - String s1d = param.toString(); - - String s2 = "a"; - // :: error: (lock.not.held) - s2 += param; - - String s3 = "a"; - // In addition to testing whether "lock" is held, tests that the result of a string - // concatenation has type @GuardedBy({}). - // :: error: (lock.not.held) - String s4 = s3 += param; + final Object lock = new Object(); + + // These casts are safe because if the casted Object is a String, it must be @GuardedBy({}) + void StringIsGBnothing( + @GuardedByUnknown Object o1, + @GuardedBy("lock") Object o2, + @GuardSatisfied Object o3, + @GuardedByBottom Object o4) { + String s1 = (String) o1; + String s2 = (String) o2; + String s3 = (String) o3; + String s4 = (String) o4; // OK } - synchronized (lock) { - String s1a = "a" + "a"; - String s1b = "a" + param; - String s1c = param + "a"; - String s1d = param.toString(); - - String s2 = "a"; - s2 += param; - - String s3 = "a"; - // In addition to testing whether "lock" is held, tests that the result of a string - // concatenation has type @GuardedBy({}). - String s4 = s3 += param; + + // Tests that the resulting type of string concatenation is always @GuardedBy({}) + // (and not @GuardedByUnknown, which is the LUB of @GuardedBy({}) (the type of the + // string literal "a") and @GuardedBy("lock") (the type of param)) + void StringConcat(@GuardedBy("lock") MyClass param) { + { + String s1a = "a" + "a"; + // :: error: (lock.not.held) + String s1b = "a" + param; + // :: error: (lock.not.held) + String s1c = param + "a"; + // :: error: (lock.not.held) + String s1d = param.toString(); + + String s2 = "a"; + // :: error: (lock.not.held) + s2 += param; + + String s3 = "a"; + // In addition to testing whether "lock" is held, tests that the result of a string + // concatenation has type @GuardedBy({}). + // :: error: (lock.not.held) + String s4 = s3 += param; + } + synchronized (lock) { + String s1a = "a" + "a"; + String s1b = "a" + param; + String s1c = param + "a"; + String s1d = param.toString(); + + String s2 = "a"; + s2 += param; + + String s3 = "a"; + // In addition to testing whether "lock" is held, tests that the result of a string + // concatenation has type @GuardedBy({}). + String s4 = s3 += param; + } } - } - class MyClass { - Object field = new Object(); - } + class MyClass { + Object field = new Object(); + } } diff --git a/checker/tests/lock/TestAnon.java b/checker/tests/lock/TestAnon.java index 0bb105edaea..0966c61c641 100644 --- a/checker/tests/lock/TestAnon.java +++ b/checker/tests/lock/TestAnon.java @@ -1,10 +1,10 @@ public class TestAnon { - public void foo() { - String s = ""; - new Object() { - public String bar() { - return s; - } - }; - } + public void foo() { + String s = ""; + new Object() { + public String bar() { + return s; + } + }; + } } diff --git a/checker/tests/lock/TestConcurrentSemantics1.java b/checker/tests/lock/TestConcurrentSemantics1.java index d3cce3b82a0..8636d166225 100644 --- a/checker/tests/lock/TestConcurrentSemantics1.java +++ b/checker/tests/lock/TestConcurrentSemantics1.java @@ -1,53 +1,54 @@ -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByUnknown; +import java.util.concurrent.locks.ReentrantLock; + public class TestConcurrentSemantics1 { - /* This class tests the following critical scenario. - * - * Suppose the following lines from method1 are executed on thread A. - * - *
          {@code
          -   * @GuardedBy("lock1") MyClass local;
          -   * m = local;
          -   * }
          - * - * Then a context switch occurs to method2 on thread B and the following lines are executed: - * - *
          {@code
          -   * @GuardedBy("lock2") MyClass local;
          -   * m = local;
          -   * }
          - * - * Then a context switch back to method1 on thread A occurs and the following lines are executed: - * - *
          {@code
          -   * lock1.lock();
          -   * m.field = new Object();
          -   * }
          - * - * In this case, it is absolutely critical that the dereference above not be allowed. - * - */ + /* This class tests the following critical scenario. + * + * Suppose the following lines from method1 are executed on thread A. + * + *
          {@code
          +     * @GuardedBy("lock1") MyClass local;
          +     * m = local;
          +     * }
          + * + * Then a context switch occurs to method2 on thread B and the following lines are executed: + * + *
          {@code
          +     * @GuardedBy("lock2") MyClass local;
          +     * m = local;
          +     * }
          + * + * Then a context switch back to method1 on thread A occurs and the following lines are executed: + * + *
          {@code
          +     * lock1.lock();
          +     * m.field = new Object();
          +     * }
          + * + * In this case, it is absolutely critical that the dereference above not be allowed. + * + */ - @GuardedByUnknown MyClass m; - final ReentrantLock lock1 = new ReentrantLock(); - final ReentrantLock lock2 = new ReentrantLock(); + @GuardedByUnknown MyClass m; + final ReentrantLock lock1 = new ReentrantLock(); + final ReentrantLock lock2 = new ReentrantLock(); - void method1() { - @GuardedBy("lock1") MyClass local = new MyClass(); - m = local; - lock1.lock(); - // :: error: (lock.not.held) - m.field = new Object(); - } + void method1() { + @GuardedBy("lock1") MyClass local = new MyClass(); + m = local; + lock1.lock(); + // :: error: (lock.not.held) + m.field = new Object(); + } - void method2() { - @GuardedBy("lock2") MyClass local = new MyClass(); - m = local; - } + void method2() { + @GuardedBy("lock2") MyClass local = new MyClass(); + m = local; + } - class MyClass { - Object field = new Object(); - } + class MyClass { + Object field = new Object(); + } } diff --git a/checker/tests/lock/TestConcurrentSemantics2.java b/checker/tests/lock/TestConcurrentSemantics2.java index 39323991dda..73253cf0ad9 100644 --- a/checker/tests/lock/TestConcurrentSemantics2.java +++ b/checker/tests/lock/TestConcurrentSemantics2.java @@ -1,29 +1,29 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class TestConcurrentSemantics2 { - final Object a = new Object(); - final Object b = new Object(); + final Object a = new Object(); + final Object b = new Object(); - @GuardedBy("a") Object o; + @GuardedBy("a") Object o; - void method() { - o = null; - // Assume the following happens: - // * Context switch to a different thread. - // * bar() is called on the other thread. - // * Context switch back to this thread. - // o is no longer null and an "assignment.type.incompatible" error should be issued. - // :: error: (assignment.type.incompatible) - @GuardedBy("b") Object o2 = o; - } + void method() { + o = null; + // Assume the following happens: + // * Context switch to a different thread. + // * bar() is called on the other thread. + // * Context switch back to this thread. + // o is no longer null and an "assignment.type.incompatible" error should be issued. + // :: error: (assignment.type.incompatible) + @GuardedBy("b") Object o2 = o; + } - void bar() { - o = new Object(); - } + void bar() { + o = new Object(); + } - // Test that field assignments do not cause their type to be refined: - @GuardedBy("a") Object myObject1 = null; + // Test that field assignments do not cause their type to be refined: + @GuardedBy("a") Object myObject1 = null; - // :: error: (assignment.type.incompatible) - @GuardedBy("b") Object myObject2 = myObject1; + // :: error: (assignment.type.incompatible) + @GuardedBy("b") Object myObject2 = myObject1; } diff --git a/checker/tests/lock/TestTreeKinds.java b/checker/tests/lock/TestTreeKinds.java index b177aaab82c..5d5c2dc72e8 100644 --- a/checker/tests/lock/TestTreeKinds.java +++ b/checker/tests/lock/TestTreeKinds.java @@ -1,528 +1,529 @@ -import java.util.Random; -import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.*; import org.checkerframework.checker.lock.qual.GuardedByUnknown; import org.checkerframework.dataflow.qual.SideEffectFree; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; + public class TestTreeKinds { - class MyClass { - Object field = new Object(); + class MyClass { + Object field = new Object(); - @LockingFree - Object method(@GuardSatisfied MyClass this) { - return new Object(); - } + @LockingFree + Object method(@GuardSatisfied MyClass this) { + return new Object(); + } - void method2(@GuardSatisfied MyClass this) {} - } + void method2(@GuardSatisfied MyClass this) {} + } - MyClass[] newMyClassArray() { - return new MyClass[3]; - } + MyClass[] newMyClassArray() { + return new MyClass[3]; + } - @GuardedBy("lock") MyClass m; + @GuardedBy("lock") MyClass m; - { - // In constructor/initializer, it's OK not to hold the lock on 'this', but other locks must - // be respected. - // :: error: (lock.not.held) - m.field = new Object(); - } + { + // In constructor/initializer, it's OK not to hold the lock on 'this', but other locks must + // be respected. + // :: error: (lock.not.held) + m.field = new Object(); + } - final ReentrantLock lock = new ReentrantLock(); - final ReentrantLock lock2 = new ReentrantLock(); + final ReentrantLock lock = new ReentrantLock(); + final ReentrantLock lock2 = new ReentrantLock(); - @GuardedBy("lock") MyClass foo = new MyClass(); + @GuardedBy("lock") MyClass foo = new MyClass(); - MyClass unguardedFoo = new MyClass(); + MyClass unguardedFoo = new MyClass(); - @EnsuresLockHeld("lock") - void lockTheLock() { - lock.lock(); - } + @EnsuresLockHeld("lock") + void lockTheLock() { + lock.lock(); + } - @EnsuresLockHeld("lock2") - void lockTheLock2() { - lock2.lock(); - } + @EnsuresLockHeld("lock2") + void lockTheLock2() { + lock2.lock(); + } - @EnsuresLockHeldIf(expression = "lock", result = true) - boolean tryToLockTheLock() { - return lock.tryLock(); - } + @EnsuresLockHeldIf(expression = "lock", result = true) + boolean tryToLockTheLock() { + return lock.tryLock(); + } - // This @MayReleaseLocks annotation causes dataflow analysis to assume 'lock' is released after - // unlockTheLock() is called. - @MayReleaseLocks - void unlockTheLock() {} + // This @MayReleaseLocks annotation causes dataflow analysis to assume 'lock' is released after + // unlockTheLock() is called. + @MayReleaseLocks + void unlockTheLock() {} - @SideEffectFree - void sideEffectFreeMethod() {} + @SideEffectFree + void sideEffectFreeMethod() {} - @LockingFree - void lockingFreeMethod() {} + @LockingFree + void lockingFreeMethod() {} - @MayReleaseLocks - void nonSideEffectFreeMethod() {} + @MayReleaseLocks + void nonSideEffectFreeMethod() {} - @Holding("lock") - void requiresLockHeldMethod() {} + @Holding("lock") + void requiresLockHeldMethod() {} - @SuppressWarnings("assignment") - MyClass @GuardedBy("lock") [] fooArray = new MyClass[3]; + @SuppressWarnings("assignment") + MyClass @GuardedBy("lock") [] fooArray = new MyClass[3]; - @GuardedBy("lock") MyClass[] fooArray2 = new MyClass[3]; + @GuardedBy("lock") MyClass[] fooArray2 = new MyClass[3]; - @SuppressWarnings("assignment") - @GuardedBy("lock") MyClass[][] fooArray3 = new MyClass[3][3]; + @SuppressWarnings("assignment") + @GuardedBy("lock") MyClass[][] fooArray3 = new MyClass[3][3]; - @SuppressWarnings("assignment") - MyClass @GuardedBy("lock") [][] fooArray4 = new MyClass[3][3]; + @SuppressWarnings("assignment") + MyClass @GuardedBy("lock") [][] fooArray4 = new MyClass[3][3]; - MyClass[] @GuardedBy("lock") [] fooArray5 = new MyClass[3][3]; + MyClass[] @GuardedBy("lock") [] fooArray5 = new MyClass[3][3]; - class myClass { - int i = 0; - } + class myClass { + int i = 0; + } - @GuardedBy("lock") myClass myClassInstance = new myClass(); + @GuardedBy("lock") myClass myClassInstance = new myClass(); - @GuardedBy("lock") Exception exception = new Exception(); + @GuardedBy("lock") Exception exception = new Exception(); - class MyParametrizedType { - T foo; - int l; - } + class MyParametrizedType { + T foo; + int l; + } - @GuardedBy("lock") MyParametrizedType myParametrizedType = new MyParametrizedType<>(); + @GuardedBy("lock") MyParametrizedType myParametrizedType = new MyParametrizedType<>(); - MyClass getFooWithWrongReturnType() { - // :: error: (return.type.incompatible) - return foo; // return of guarded object - } + MyClass getFooWithWrongReturnType() { + // :: error: (return.type.incompatible) + return foo; // return of guarded object + } - @GuardedBy("lock") MyClass getFoo() { - return foo; - } + @GuardedBy("lock") MyClass getFoo() { + return foo; + } - MyClass @GuardedBy("lock") [] getFooArray() { - return fooArray; - } + MyClass @GuardedBy("lock") [] getFooArray() { + return fooArray; + } - @GuardedBy("lock") MyClass[] getFooArray2() { - return fooArray2; - } + @GuardedBy("lock") MyClass[] getFooArray2() { + return fooArray2; + } - @GuardedBy("lock") MyClass[][] getFooArray3() { - return fooArray3; - } + @GuardedBy("lock") MyClass[][] getFooArray3() { + return fooArray3; + } - MyClass @GuardedBy("lock") [][] getFooArray4() { - return fooArray4; - } + MyClass @GuardedBy("lock") [][] getFooArray4() { + return fooArray4; + } - MyClass[] @GuardedBy("lock") [] getFooArray5() { - return fooArray5; - } + MyClass[] @GuardedBy("lock") [] getFooArray5() { + return fooArray5; + } - enum myEnumType { - ABC, - DEF - } + enum myEnumType { + ABC, + DEF + } - @GuardedBy("lock") myEnumType myEnum; + @GuardedBy("lock") myEnumType myEnum; - void testEnumType() { - // TODO: "assignment.type.incompatible" is technically correct, but we could - // make it friendlier for the user if constant enum values on the RHS - // automatically cast to the @GuardedBy annotation of the LHS. - // :: error: (assignment.type.incompatible) - myEnum = myEnumType.ABC; - } + void testEnumType() { + // TODO: "assignment.type.incompatible" is technically correct, but we could + // make it friendlier for the user if constant enum values on the RHS + // automatically cast to the @GuardedBy annotation of the LHS. + // :: error: (assignment.type.incompatible) + myEnum = myEnumType.ABC; + } - final Object intrinsicLock = new Object(); + final Object intrinsicLock = new Object(); - void testThreadHoldsLock(@GuardedBy("intrinsicLock") MyClass m) { - if (Thread.holdsLock(intrinsicLock)) { - m.field.toString(); - } else { - // :: error: (lock.not.held) - m.field.toString(); - } - } - - void testTreeTypes() { - int i, l; - - MyClass o = new MyClass(); - MyClass f = new MyClass(); - - // The following test cases were inspired by - // org.checkerframework.afu.annotator.find.ASTPathCriterion.isSatisfiedBy - // in the Annotation File Utilities. - - // TODO: File a bug for the dataflow issue mentioned in the line below. - // TODO: uncomment: Hits a bug in dataflow: - // do { - // break; - // } while (foo.field != null); // access to guarded object in condition of do/while loop - // :: error: (lock.not.held) - for (foo = new MyClass(); foo.field != null; foo = new MyClass()) { - break; - } // access to guarded object in condition of for loop - // assignment to guarded object (OK) --- foo is still refined to @GuardedBy("lock") after - // this point, though. - foo = new MyClass(); - // A simple method call to a guarded object is not considered a dereference (only field - // accesses are considered dereferences). - unguardedFoo.method2(); - // Same as above, but the guard must be satisfied if the receiver is @GuardSatisfied. - // :: error: (lock.not.held) - foo.method2(); - // attempt to use guarded object in a switch statement - // :: error: (lock.not.held) - switch (foo.field.hashCode()) { + void testThreadHoldsLock(@GuardedBy("intrinsicLock") MyClass m) { + if (Thread.holdsLock(intrinsicLock)) { + m.field.toString(); + } else { + // :: error: (lock.not.held) + m.field.toString(); + } } - // attempt to use guarded object inside a try with resources - // try(foo = new MyClass()) { foo.field.toString(); } - - // Retrieving an element from a guarded array is a dereference - // :: error: (lock.not.held) - MyClass m = fooArray[0]; - - // method call on dereference of unguarded element of *guarded* array - // :: error: (lock.not.held) - fooArray[0].field.toString(); - // :: error: (lock.not.held) - l = fooArray.length; // dereference of guarded array itself - - // method call on dereference of guarded array element - // :: error: (lock.not.held) - fooArray2[0].field.toString(); - // method call on dereference of unguarded array - TODO: currently preconditions are not - // retrieved correctly from array types. This is not unique to the Lock Checker. - // fooArray2.field.toString(); - - // method call on dereference of guarded array element of multidimensional array - // :: error: (lock.not.held) - fooArray3[0][0].field.toString(); - // method call on dereference of unguarded single-dimensional array element of unguarded - // multidimensional array - TODO: currently preconditions are not retrieved correctly from - // array types. This is not unique to the Lock Checker. - // fooArray3[0].field.toString(); - // method call on dereference of unguarded multidimensional array - TODO: currently - // preconditions are not retrieved correctly from array types. This is not unique to the - // Lock Checker. - // fooArray3.field.toString(); - - // method call on dereference of unguarded array element of *guarded* multidimensional array - // :: error: (lock.not.held) - fooArray4[0][0].field.toString(); - // dereference of unguarded single-dimensional array element of *guarded* multidimensional - // array - // :: error: (lock.not.held) - l = fooArray4[0].length; - // dereference of guarded multidimensional array - // :: error: (lock.not.held) - l = fooArray4.length; - - // method call on dereference of unguarded array element of *guarded subarray* of - // multidimensional array - // :: error: (lock.not.held) - fooArray5[0][0].field.toString(); - // dereference of guarded single-dimensional array element of multidimensional array - // :: error: (lock.not.held) - l = fooArray5[0].length; - // dereference of unguarded multidimensional array - l = fooArray5.length; - - // :: error: (lock.not.held) - l = getFooArray().length; // dereference of guarded array returned by a method - - // method call on dereference of guarded array element returned by a method - // :: error: (lock.not.held) - getFooArray2()[0].field.toString(); - // dereference of unguarded array returned by a method - l = getFooArray2().length; - - // method call on dereference of guarded array element of multidimensional array returned by - // a method - // :: error: (lock.not.held) - getFooArray3()[0][0].field.toString(); - // dereference of unguarded single-dimensional array element of multidimensional array - // returned by a method - l = getFooArray3()[0].length; - // dereference of unguarded multidimensional array returned by a method - l = getFooArray3().length; - - // method call on dereference of unguarded array element of *guarded* multidimensional array - // returned by a method - // :: error: (lock.not.held) - getFooArray4()[0][0].field.toString(); - // dereference of unguarded single-dimensional array element of *guarded* multidimensional - // array returned by a method - // :: error: (lock.not.held) - l = getFooArray4()[0].length; - // dereference of guarded multidimensional array returned by a method - // :: error: (lock.not.held) - l = getFooArray4().length; - - // method call on dereference of unguarded array element of *guarded subarray* of - // multidimensional array returned by a method - // :: error: (lock.not.held) - getFooArray5()[0][0].field.toString(); - // dereference of guarded single-dimensional array element of multidimensional array - // returned by a method - // :: error: (lock.not.held) - l = getFooArray5()[0].length; - // dereference of unguarded multidimensional array returned by a method - l = getFooArray5().length; - - // Test different @GuardedBy(...) present on the element and array locations. - @SuppressWarnings("lock:assignment") // prevent flow-sensitive type refinement - @GuardedBy("lock") MyClass @GuardedBy("lock2") [] array = newMyClassArray(); - // :: error: (lock.not.held) - array[0].field = new Object(); - if (lock.isHeldByCurrentThread()) { - // :: error: (lock.not.held) - array[0].field = new Object(); - if (lock2.isHeldByCurrentThread()) { + + void testTreeTypes() { + int i, l; + + MyClass o = new MyClass(); + MyClass f = new MyClass(); + + // The following test cases were inspired by + // org.checkerframework.afu.annotator.find.ASTPathCriterion.isSatisfiedBy + // in the Annotation File Utilities. + + // TODO: File a bug for the dataflow issue mentioned in the line below. + // TODO: uncomment: Hits a bug in dataflow: + // do { + // break; + // } while (foo.field != null); // access to guarded object in condition of do/while loop + // :: error: (lock.not.held) + for (foo = new MyClass(); foo.field != null; foo = new MyClass()) { + break; + } // access to guarded object in condition of for loop + // assignment to guarded object (OK) --- foo is still refined to @GuardedBy("lock") after + // this point, though. + foo = new MyClass(); + // A simple method call to a guarded object is not considered a dereference (only field + // accesses are considered dereferences). + unguardedFoo.method2(); + // Same as above, but the guard must be satisfied if the receiver is @GuardSatisfied. + // :: error: (lock.not.held) + foo.method2(); + // attempt to use guarded object in a switch statement + // :: error: (lock.not.held) + switch (foo.field.hashCode()) { + } + // attempt to use guarded object inside a try with resources + // try(foo = new MyClass()) { foo.field.toString(); } + + // Retrieving an element from a guarded array is a dereference + // :: error: (lock.not.held) + MyClass m = fooArray[0]; + + // method call on dereference of unguarded element of *guarded* array + // :: error: (lock.not.held) + fooArray[0].field.toString(); + // :: error: (lock.not.held) + l = fooArray.length; // dereference of guarded array itself + + // method call on dereference of guarded array element + // :: error: (lock.not.held) + fooArray2[0].field.toString(); + // method call on dereference of unguarded array - TODO: currently preconditions are not + // retrieved correctly from array types. This is not unique to the Lock Checker. + // fooArray2.field.toString(); + + // method call on dereference of guarded array element of multidimensional array + // :: error: (lock.not.held) + fooArray3[0][0].field.toString(); + // method call on dereference of unguarded single-dimensional array element of unguarded + // multidimensional array - TODO: currently preconditions are not retrieved correctly from + // array types. This is not unique to the Lock Checker. + // fooArray3[0].field.toString(); + // method call on dereference of unguarded multidimensional array - TODO: currently + // preconditions are not retrieved correctly from array types. This is not unique to the + // Lock Checker. + // fooArray3.field.toString(); + + // method call on dereference of unguarded array element of *guarded* multidimensional array + // :: error: (lock.not.held) + fooArray4[0][0].field.toString(); + // dereference of unguarded single-dimensional array element of *guarded* multidimensional + // array + // :: error: (lock.not.held) + l = fooArray4[0].length; + // dereference of guarded multidimensional array + // :: error: (lock.not.held) + l = fooArray4.length; + + // method call on dereference of unguarded array element of *guarded subarray* of + // multidimensional array + // :: error: (lock.not.held) + fooArray5[0][0].field.toString(); + // dereference of guarded single-dimensional array element of multidimensional array + // :: error: (lock.not.held) + l = fooArray5[0].length; + // dereference of unguarded multidimensional array + l = fooArray5.length; + + // :: error: (lock.not.held) + l = getFooArray().length; // dereference of guarded array returned by a method + + // method call on dereference of guarded array element returned by a method + // :: error: (lock.not.held) + getFooArray2()[0].field.toString(); + // dereference of unguarded array returned by a method + l = getFooArray2().length; + + // method call on dereference of guarded array element of multidimensional array returned by + // a method + // :: error: (lock.not.held) + getFooArray3()[0][0].field.toString(); + // dereference of unguarded single-dimensional array element of multidimensional array + // returned by a method + l = getFooArray3()[0].length; + // dereference of unguarded multidimensional array returned by a method + l = getFooArray3().length; + + // method call on dereference of unguarded array element of *guarded* multidimensional array + // returned by a method + // :: error: (lock.not.held) + getFooArray4()[0][0].field.toString(); + // dereference of unguarded single-dimensional array element of *guarded* multidimensional + // array returned by a method + // :: error: (lock.not.held) + l = getFooArray4()[0].length; + // dereference of guarded multidimensional array returned by a method + // :: error: (lock.not.held) + l = getFooArray4().length; + + // method call on dereference of unguarded array element of *guarded subarray* of + // multidimensional array returned by a method + // :: error: (lock.not.held) + getFooArray5()[0][0].field.toString(); + // dereference of guarded single-dimensional array element of multidimensional array + // returned by a method + // :: error: (lock.not.held) + l = getFooArray5()[0].length; + // dereference of unguarded multidimensional array returned by a method + l = getFooArray5().length; + + // Test different @GuardedBy(...) present on the element and array locations. + @SuppressWarnings("lock:assignment") // prevent flow-sensitive type refinement + @GuardedBy("lock") MyClass @GuardedBy("lock2") [] array = newMyClassArray(); + // :: error: (lock.not.held) array[0].field = new Object(); - } + if (lock.isHeldByCurrentThread()) { + // :: error: (lock.not.held) + array[0].field = new Object(); + if (lock2.isHeldByCurrentThread()) { + array[0].field = new Object(); + } + } + + // method call on guarded object within parenthesized expression + // :: error: (lock.not.held) + String s = (foo.field.toString()); + // :: error: (lock.not.held) + foo.field.toString(); // method call on guarded object + // :: error: (lock.not.held) + getFoo().field.toString(); // method call on guarded object returned by a method + // :: error: (lock.not.held) + this.foo.field.toString(); // method call on guarded object using 'this' literal + // dereference of guarded object in labeled statement + label: + // :: error: (lock.not.held) + foo.field.toString(); + // access to guarded object in instanceof expression (OK) + if (foo instanceof MyClass) {} + // access to guarded object in while condition of while loop (OK) + while (foo != null) { + break; + } + // binary operator on guarded object in else if condition (OK) + if (false) { + } else if (foo == o) { + } + // access to guarded object in a lambda expression + Runnable rn = + () -> { + // :: error: (lock.not.held) + foo.field.toString(); + }; + // :: error: (lock.not.held) + i = myClassInstance.i; // access to member field of guarded object + // MemberReferenceTrees? how do they work + fooArray = new MyClass[3]; // second allocation of guarded array (OK) + // dereference of guarded object in conditional expression tree + // :: error: (lock.not.held) + s = i == 5 ? foo.field.toString() : f.field.toString(); + // dereference of guarded object in conditional expression tree + // :: error: (lock.not.held) + s = i == 5 ? f.field.toString() : foo.field.toString(); + // Testing of 'return' is done in getFooWithWrongReturnType() + // throwing a guarded object - when throwing an exception, it must be @GuardedBy({}). Even + // @GuardedByUnknown is not allowed. + try { + // :: error: (throw.type.invalid) + throw exception; + } catch (Exception e) { + } + // casting of a guarded object to an unguarded object + // :: error: (assignment.type.incompatible) + @GuardedBy({}) Object e1 = (Object) exception; + // OK, since the local variable's type gets refined to @GuardedBy("lock") + Object e2 = (Object) exception; + // :: error: (lock.not.held) + l = myParametrizedType.l; // dereference of guarded object having a parameterized type + + // We need to support locking on local variables and protecting local variables because + // these locals may contain references to fields. Somehow we need to pass along the + // information of which field it was. + + if (foo == o) { // binary operator on guarded object (OK) + o.field.toString(); + } + + if (foo == null) { + // With -AconcurrentSemantics turned off, a cannot.dereference error would be expected, + // since there is an attempt to dereference an expression whose type has been refined to + // @GuardedByBottom (due to the comparison to null). However, with -AconcurrentSemantics + // turned on, foo may no longer be null by now, the refinement to @GuardedByBottom is + // lost and the refined type of foo is now the declared type ( @GuardedBy("lock") ), + // resulting in the lock.not.held error. + // :: error: (lock.not.held) + foo.field.toString(); + } + + // TODO: Reenable: + // @PolyGuardedBy should not be written here, but it is not explicitly forbidden by the + // framework. + // @PolyGuardedBy MyClass m2 = new MyClass(); + // (cannot.dereference) + // m2.field.toString(); } - // method call on guarded object within parenthesized expression - // :: error: (lock.not.held) - String s = (foo.field.toString()); - // :: error: (lock.not.held) - foo.field.toString(); // method call on guarded object - // :: error: (lock.not.held) - getFoo().field.toString(); // method call on guarded object returned by a method - // :: error: (lock.not.held) - this.foo.field.toString(); // method call on guarded object using 'this' literal - // dereference of guarded object in labeled statement - label: - // :: error: (lock.not.held) - foo.field.toString(); - // access to guarded object in instanceof expression (OK) - if (foo instanceof MyClass) {} - // access to guarded object in while condition of while loop (OK) - while (foo != null) { - break; - } - // binary operator on guarded object in else if condition (OK) - if (false) { - } else if (foo == o) { - } - // access to guarded object in a lambda expression - Runnable rn = - () -> { - // :: error: (lock.not.held) - foo.field.toString(); - }; - // :: error: (lock.not.held) - i = myClassInstance.i; // access to member field of guarded object - // MemberReferenceTrees? how do they work - fooArray = new MyClass[3]; // second allocation of guarded array (OK) - // dereference of guarded object in conditional expression tree - // :: error: (lock.not.held) - s = i == 5 ? foo.field.toString() : f.field.toString(); - // dereference of guarded object in conditional expression tree - // :: error: (lock.not.held) - s = i == 5 ? f.field.toString() : foo.field.toString(); - // Testing of 'return' is done in getFooWithWrongReturnType() - // throwing a guarded object - when throwing an exception, it must be @GuardedBy({}). Even - // @GuardedByUnknown is not allowed. - try { - // :: error: (throw.type.invalid) - throw exception; - } catch (Exception e) { - } - // casting of a guarded object to an unguarded object - // :: error: (assignment.type.incompatible) - @GuardedBy({}) Object e1 = (Object) exception; - // OK, since the local variable's type gets refined to @GuardedBy("lock") - Object e2 = (Object) exception; - // :: error: (lock.not.held) - l = myParametrizedType.l; // dereference of guarded object having a parameterized type - - // We need to support locking on local variables and protecting local variables because - // these locals may contain references to fields. Somehow we need to pass along the - // information of which field it was. - - if (foo == o) { // binary operator on guarded object (OK) - o.field.toString(); + @GuardedBy("lock") MyClass guardedByLock() { + return new MyClass(); } - if (foo == null) { - // With -AconcurrentSemantics turned off, a cannot.dereference error would be expected, - // since there is an attempt to dereference an expression whose type has been refined to - // @GuardedByBottom (due to the comparison to null). However, with -AconcurrentSemantics - // turned on, foo may no longer be null by now, the refinement to @GuardedByBottom is - // lost and the refined type of foo is now the declared type ( @GuardedBy("lock") ), - // resulting in the lock.not.held error. - // :: error: (lock.not.held) - foo.field.toString(); + @GuardedByUnknown MyClass someValue() { + return new MyClass(); } - // TODO: Reenable: - // @PolyGuardedBy should not be written here, but it is not explicitly forbidden by the - // framework. - // @PolyGuardedBy MyClass m2 = new MyClass(); - // (cannot.dereference) - // m2.field.toString(); - } - - @GuardedBy("lock") MyClass guardedByLock() { - return new MyClass(); - } - - @GuardedByUnknown MyClass someValue() { - return new MyClass(); - } + @MayReleaseLocks + public void testLocals() { + final ReentrantLock localLock = new ReentrantLock(); - @MayReleaseLocks - public void testLocals() { - final ReentrantLock localLock = new ReentrantLock(); + @SuppressWarnings("assignment") // prevent flow-sensitive refinement + @GuardedBy("localLock") MyClass guardedByLocalLock = someValue(); - @SuppressWarnings("assignment") // prevent flow-sensitive refinement - @GuardedBy("localLock") MyClass guardedByLocalLock = someValue(); + // :: error: (lock.not.held) + guardedByLocalLock.field.toString(); - // :: error: (lock.not.held) - guardedByLocalLock.field.toString(); + @GuardedBy("lock") MyClass local = guardedByLock(); - @GuardedBy("lock") MyClass local = guardedByLock(); + // :: error: (lock.not.held) + local.field.toString(); - // :: error: (lock.not.held) - local.field.toString(); + lockTheLock(); - lockTheLock(); + local.field.toString(); // No warning output - local.field.toString(); // No warning output - - unlockTheLock(); - } - - @MayReleaseLocks - public void testMethodAnnotations() { - Random r = new Random(); + unlockTheLock(); + } - if (r.nextBoolean()) { - lockTheLock(); - requiresLockHeldMethod(); - } else { - // :: error: (contracts.precondition.not.satisfied) - requiresLockHeldMethod(); + @MayReleaseLocks + public void testMethodAnnotations() { + Random r = new Random(); + + if (r.nextBoolean()) { + lockTheLock(); + requiresLockHeldMethod(); + } else { + // :: error: (contracts.precondition.not.satisfied) + requiresLockHeldMethod(); + } + + if (r.nextBoolean()) { + lockTheLock(); + foo.field.toString(); + + unlockTheLock(); + + // :: error: (lock.not.held) + foo.field.toString(); + } else { + // :: error: (lock.not.held) + foo.field.toString(); + } + + if (tryToLockTheLock()) { + foo.field.toString(); + } else { + // :: error: (lock.not.held) + foo.field.toString(); + } + + if (r.nextBoolean()) { + lockTheLock(); + sideEffectFreeMethod(); + foo.field.toString(); + } else { + lockTheLock(); + nonSideEffectFreeMethod(); + // :: error: (lock.not.held) + foo.field.toString(); + } + + if (r.nextBoolean()) { + lockTheLock(); + lockingFreeMethod(); + foo.field.toString(); + } else { + lockTheLock(); + nonSideEffectFreeMethod(); + // :: error: (lock.not.held) + foo.field.toString(); + } } - if (r.nextBoolean()) { - lockTheLock(); - foo.field.toString(); + void methodThatTakesAnInteger(Integer i) {} - unlockTheLock(); + void testBoxedPrimitiveType() { + Integer i = null; + if (i == null) {} - // :: error: (lock.not.held) - foo.field.toString(); - } else { - // :: error: (lock.not.held) - foo.field.toString(); + methodThatTakesAnInteger(i); } - if (tryToLockTheLock()) { - foo.field.toString(); - } else { - // :: error: (lock.not.held) - foo.field.toString(); + void testReceiverGuardedByItself(@GuardedBy("") TestTreeKinds this) { + // :: error: (lock.not.held) + method(); + synchronized (this) { + method(); + } } - if (r.nextBoolean()) { - lockTheLock(); - sideEffectFreeMethod(); - foo.field.toString(); - } else { - lockTheLock(); - nonSideEffectFreeMethod(); - // :: error: (lock.not.held) - foo.field.toString(); - } + void method(@GuardSatisfied TestTreeKinds this) {} - if (r.nextBoolean()) { - lockTheLock(); - lockingFreeMethod(); - foo.field.toString(); - } else { - lockTheLock(); - nonSideEffectFreeMethod(); - // :: error: (lock.not.held) - foo.field.toString(); + void testOtherClassReceiverGuardedByItself(final @GuardedBy("") OtherClass o) { + // :: error: (lock.not.held) + o.foo(); + synchronized (o) { + o.foo(); + } } - } - - void methodThatTakesAnInteger(Integer i) {} - void testBoxedPrimitiveType() { - Integer i = null; - if (i == null) {} - - methodThatTakesAnInteger(i); - } - - void testReceiverGuardedByItself(@GuardedBy("") TestTreeKinds this) { - // :: error: (lock.not.held) - method(); - synchronized (this) { - method(); + class OtherClass { + void foo(@GuardSatisfied OtherClass this) {} } - } - - void method(@GuardSatisfied TestTreeKinds this) {} - void testOtherClassReceiverGuardedByItself(final @GuardedBy("") OtherClass o) { - // :: error: (lock.not.held) - o.foo(); - synchronized (o) { - o.foo(); + void testExplicitLockSynchronized() { + final ReentrantLock lock = new ReentrantLock(); + // :: error: (explicit.lock.synchronized) + synchronized (lock) { + } } - } - class OtherClass { - void foo(@GuardSatisfied OtherClass this) {} - } + void testPrimitiveTypeGuardedby() { + // :: error: (immutable.type.guardedby) + @GuardedBy("lock") int a = 0; + // :: error: (immutable.type.guardedby) + @GuardedBy int b = 0; + // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied int c = 0; + // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) + @GuardSatisfied(1) int d = 0; + int e = 0; + // :: error: (immutable.type.guardedby) + @GuardedByUnknown int f = 0; + // :: error: (immutable.type.guardedby) :: error: (assignment.type.incompatible) + @GuardedByBottom int g = 0; + } - void testExplicitLockSynchronized() { - final ReentrantLock lock = new ReentrantLock(); - // :: error: (explicit.lock.synchronized) - synchronized (lock) { + void testBinaryOperatorBooleanResultIsAlwaysGuardedByNothing() { + @GuardedBy("lock") Object o1 = new Object(); + Object o2 = new Object(); + // boolean variables are implicitly @GuardedBy({}). + boolean b1 = o1 == o2; + boolean b2 = o2 == o1; + boolean b3 = o1 != o2; + boolean b4 = o2 != o1; + boolean b5 = o1 instanceof Object; + boolean b6 = o2 instanceof Object; + boolean b7 = o1 instanceof @GuardedBy("lock") Object; + boolean b8 = o2 instanceof @GuardedBy("lock") Object; } - } - - void testPrimitiveTypeGuardedby() { - // :: error: (immutable.type.guardedby) - @GuardedBy("lock") int a = 0; - // :: error: (immutable.type.guardedby) - @GuardedBy int b = 0; - // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied int c = 0; - // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) - @GuardSatisfied(1) int d = 0; - int e = 0; - // :: error: (immutable.type.guardedby) - @GuardedByUnknown int f = 0; - // :: error: (immutable.type.guardedby) :: error: (assignment.type.incompatible) - @GuardedByBottom int g = 0; - } - - void testBinaryOperatorBooleanResultIsAlwaysGuardedByNothing() { - @GuardedBy("lock") Object o1 = new Object(); - Object o2 = new Object(); - // boolean variables are implicitly @GuardedBy({}). - boolean b1 = o1 == o2; - boolean b2 = o2 == o1; - boolean b3 = o1 != o2; - boolean b4 = o2 != o1; - boolean b5 = o1 instanceof Object; - boolean b6 = o2 instanceof Object; - boolean b7 = o1 instanceof @GuardedBy("lock") Object; - boolean b8 = o2 instanceof @GuardedBy("lock") Object; - } } diff --git a/checker/tests/lock/ThisPostCondition.java b/checker/tests/lock/ThisPostCondition.java index 59c58248504..0e31d1ea5ef 100644 --- a/checker/tests/lock/ThisPostCondition.java +++ b/checker/tests/lock/ThisPostCondition.java @@ -4,65 +4,65 @@ import org.checkerframework.checker.lock.qual.Holding; class MyReentrantLock { - final Object myfield = new Object(); + final Object myfield = new Object(); - @Holding("myfield") - @EnsuresLockHeld("this") - void lock() { - this.lock(); - } + @Holding("myfield") + @EnsuresLockHeld("this") + void lock() { + this.lock(); + } - @EnsuresLockHeld("this") - void lock2() { - this.lock2(); - } + @EnsuresLockHeld("this") + void lock2() { + this.lock2(); + } - @Holding("myfield") - void notLock() {} + @Holding("myfield") + void notLock() {} - boolean b = false; + boolean b = false; - @EnsuresLockHeldIf(expression = "this", result = true) - boolean tryLock() { - if (b) { - lock2(); - return true; + @EnsuresLockHeldIf(expression = "this", result = true) + boolean tryLock() { + if (b) { + lock2(); + return true; + } + return false; } - return false; - } } public class ThisPostCondition { - final MyReentrantLock myLock = new MyReentrantLock(); + final MyReentrantLock myLock = new MyReentrantLock(); - @GuardedBy("myLock") Bar bar = new Bar(); + @GuardedBy("myLock") Bar bar = new Bar(); - @Holding("myLock.myfield") - void lockTheLock() { - myLock.lock(); - bar.field.toString(); - } + @Holding("myLock.myfield") + void lockTheLock() { + myLock.lock(); + bar.field.toString(); + } - void lockTheLock2() { - myLock.lock2(); - bar.field.toString(); - } + void lockTheLock2() { + myLock.lock2(); + bar.field.toString(); + } - void doNotLock() { - // :: error: (lock.not.held) - bar.field.toString(); - } + void doNotLock() { + // :: error: (lock.not.held) + bar.field.toString(); + } - void tryTryLock() { - if (myLock.tryLock()) { - bar.field.toString(); - } else { - // :: error: (lock.not.held) - bar.field.toString(); + void tryTryLock() { + if (myLock.tryLock()) { + bar.field.toString(); + } else { + // :: error: (lock.not.held) + bar.field.toString(); + } } - } } class Bar { - Object field; + Object field; } diff --git a/checker/tests/lock/ThisSuper.java b/checker/tests/lock/ThisSuper.java index a3116a77743..a6a5b85b508 100644 --- a/checker/tests/lock/ThisSuper.java +++ b/checker/tests/lock/ThisSuper.java @@ -7,84 +7,85 @@ public class ThisSuper { - class MyClass { - Object field; - } + class MyClass { + Object field; + } - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @GuardedBy("myLock") MyClass locked; + protected @GuardedBy("myLock") MyClass locked; - @GuardedBy("this.myLock") MyClass m1; + @GuardedBy("this.myLock") MyClass m1; - protected @GuardedBy("this.myLock") MyClass locked2; + protected @GuardedBy("this.myLock") MyClass locked2; - public void accessLock() { - synchronized (myLock) { - this.locked.field = new Object(); - } - } - } - - class LockExampleSubclass extends LockExample { - private final Object myLock = new Object(); - - private @GuardedBy("this.myLock") MyClass locked; - - @GuardedBy("this.myLock") MyClass m2; - - public LockExampleSubclass() { - final LockExampleSubclass les1 = new LockExampleSubclass(); - final LockExampleSubclass les2 = new LockExampleSubclass(); - final LockExampleSubclass les3 = les2; - LockExample le1 = new LockExample(); - - synchronized (super.myLock) { - super.locked.toString(); - super.locked2.toString(); - // :: error: (contracts.precondition.not.satisfied) - locked.toString(); - } - synchronized (myLock) { - // :: error: (contracts.precondition.not.satisfied) - super.locked.toString(); - // :: error: (contracts.precondition.not.satisfied) - super.locked2.toString(); - locked.toString(); - } - - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked; - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked2; - - // :: error: (assignment.type.incompatible) - les1.locked = les2.locked; - - // :: error: (assignment.type.incompatible) - this.locked = super.locked; - // :: error: (assignment.type.incompatible) - this.locked = super.locked2; - - // :: error: (assignment.type.incompatible) - m1 = m2; + public void accessLock() { + synchronized (myLock) { + this.locked.field = new Object(); + } + } } - @Override - public void accessLock() { - synchronized (myLock) { - this.locked.field = new Object(); - // :: error: (lock.not.held) - super.locked.field = new Object(); - System.out.println( - this.locked.field - + " " - + + class LockExampleSubclass extends LockExample { + private final Object myLock = new Object(); + + private @GuardedBy("this.myLock") MyClass locked; + + @GuardedBy("this.myLock") MyClass m2; + + public LockExampleSubclass() { + final LockExampleSubclass les1 = new LockExampleSubclass(); + final LockExampleSubclass les2 = new LockExampleSubclass(); + final LockExampleSubclass les3 = les2; + LockExample le1 = new LockExample(); + + synchronized (super.myLock) { + super.locked.toString(); + super.locked2.toString(); + // :: error: (contracts.precondition.not.satisfied) + locked.toString(); + } + synchronized (myLock) { + // :: error: (contracts.precondition.not.satisfied) + super.locked.toString(); + // :: error: (contracts.precondition.not.satisfied) + super.locked2.toString(); + locked.toString(); + } + + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked; + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked2; + + // :: error: (assignment.type.incompatible) + les1.locked = les2.locked; + + // :: error: (assignment.type.incompatible) + this.locked = super.locked; + // :: error: (assignment.type.incompatible) + this.locked = super.locked2; + + // :: error: (assignment.type.incompatible) + m1 = m2; + } + + @Override + public void accessLock() { + synchronized (myLock) { + this.locked.field = new Object(); // :: error: (lock.not.held) - super.locked.field); - System.out.println("Are locks equal? " + (super.locked == this.locked ? "yes" : "no")); - } + super.locked.field = new Object(); + System.out.println( + this.locked.field + + " " + + + // :: error: (lock.not.held) + super.locked.field); + System.out.println( + "Are locks equal? " + (super.locked == this.locked ? "yes" : "no")); + } + } } - } } diff --git a/checker/tests/lock/TypeVarNull.java b/checker/tests/lock/TypeVarNull.java index c3509773dc2..a4713a7d846 100644 --- a/checker/tests/lock/TypeVarNull.java +++ b/checker/tests/lock/TypeVarNull.java @@ -1,3 +1,3 @@ public class TypeVarNull { - T t = null; + T t = null; } diff --git a/checker/tests/lock/Update.java b/checker/tests/lock/Update.java index d1f3cd12320..66bd10f95d6 100644 --- a/checker/tests/lock/Update.java +++ b/checker/tests/lock/Update.java @@ -2,12 +2,12 @@ public class Update { - void test() { - Object o1 = new Object(); - @GuardedBy({}) Object o2 = o1; - synchronized (o1) { + void test() { + Object o1 = new Object(); + @GuardedBy({}) Object o2 = o1; + synchronized (o1) { + } + // o1 used to loss it refinement because of a bug. + @GuardedBy({}) Object o3 = o1; } - // o1 used to loss it refinement because of a bug. - @GuardedBy({}) Object o3 = o1; - } } diff --git a/checker/tests/lock/ViewpointAdaptation.java b/checker/tests/lock/ViewpointAdaptation.java index fbc0afe3005..a5dc4db90b7 100644 --- a/checker/tests/lock/ViewpointAdaptation.java +++ b/checker/tests/lock/ViewpointAdaptation.java @@ -4,46 +4,46 @@ import org.checkerframework.checker.lock.qual.GuardedBy; public class ViewpointAdaptation { - // :: error: (expression.unparsable.type.invalid) - private final @GuardedBy("a") ViewpointAdaptation f = new ViewpointAdaptation(); + // :: error: (expression.unparsable.type.invalid) + private final @GuardedBy("a") ViewpointAdaptation f = new ViewpointAdaptation(); - private @GuardedBy("this.lock") ViewpointAdaptation g = new ViewpointAdaptation(); + private @GuardedBy("this.lock") ViewpointAdaptation g = new ViewpointAdaptation(); - private final Object lock = new Object(); + private final Object lock = new Object(); - private int counter; + private int counter; - public void method1(final String a) { - synchronized (a) { - // :: error: (expression.unparsable.type.invalid) - f.counter++; + public void method1(final String a) { + synchronized (a) { + // :: error: (expression.unparsable.type.invalid) + f.counter++; + } } - } - public void method2() { - ViewpointAdaptation t = new ViewpointAdaptation(); + public void method2() { + ViewpointAdaptation t = new ViewpointAdaptation(); - // :: error: (assignment.type.incompatible) - t.g = g; // "t.lock" != "this.lock" + // :: error: (assignment.type.incompatible) + t.g = g; // "t.lock" != "this.lock" - synchronized (t.lock) { - // :: error: (lock.not.held) - g.counter++; + synchronized (t.lock) { + // :: error: (lock.not.held) + g.counter++; + } } - } - - public void method3() { - final ViewpointAdaptation t = new ViewpointAdaptation(); - // The type of 'g' is refined from @GuardedByUnknown (the default for - // a local variable due to CLIMB-to-top semantics) to @GuardedBy("t.g") - final ViewpointAdaptation g = t.g; - Object l = t.lock; - - synchronized (l) { - // Aliasing of lock expressions is not tracked by the Lock Checker. - // The Lock Checker does not know that l == t.lock - // :: error: (lock.not.held) - g.counter++; + + public void method3() { + final ViewpointAdaptation t = new ViewpointAdaptation(); + // The type of 'g' is refined from @GuardedByUnknown (the default for + // a local variable due to CLIMB-to-top semantics) to @GuardedBy("t.g") + final ViewpointAdaptation g = t.g; + Object l = t.lock; + + synchronized (l) { + // Aliasing of lock expressions is not tracked by the Lock Checker. + // The Lock Checker does not know that l == t.lock + // :: error: (lock.not.held) + g.counter++; + } } - } } diff --git a/checker/tests/lock/ViewpointAdaptation2.java b/checker/tests/lock/ViewpointAdaptation2.java index 6cd3b74ce46..a5a878b1a1e 100644 --- a/checker/tests/lock/ViewpointAdaptation2.java +++ b/checker/tests/lock/ViewpointAdaptation2.java @@ -5,44 +5,44 @@ public class ViewpointAdaptation2 { - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @GuardedBy("myLock") Object locked; + protected @GuardedBy("myLock") Object locked; - protected @GuardedBy("this.myLock") Object locked2; + protected @GuardedBy("this.myLock") Object locked2; - public @GuardedBy("myLock") Object getLocked() { - return locked; + public @GuardedBy("myLock") Object getLocked() { + return locked; + } } - } - class Use { - final LockExample lockExample1 = new LockExample(); - final Object myLock = new Object(); + class Use { + final LockExample lockExample1 = new LockExample(); + final Object myLock = new Object(); - @GuardedBy("lockExample1.myLock") Object o1 = lockExample1.locked; + @GuardedBy("lockExample1.myLock") Object o1 = lockExample1.locked; - @GuardedBy("lockExample1.myLock") Object o2 = lockExample1.locked2; + @GuardedBy("lockExample1.myLock") Object o2 = lockExample1.locked2; - // :: error: (assignment.type.incompatible) - @GuardedBy("myLock") Object o3 = lockExample1.locked; + // :: error: (assignment.type.incompatible) + @GuardedBy("myLock") Object o3 = lockExample1.locked; - // :: error: (assignment.type.incompatible) - @GuardedBy("this.myLock") Object o4 = lockExample1.locked2; + // :: error: (assignment.type.incompatible) + @GuardedBy("this.myLock") Object o4 = lockExample1.locked2; - @GuardedBy("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); + @GuardedBy("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); - // :: error: (assignment.type.incompatible) - @GuardedBy("myLock") Object oM2 = lockExample1.getLocked(); + // :: error: (assignment.type.incompatible) + @GuardedBy("myLock") Object oM2 = lockExample1.getLocked(); - // :: error: (assignment.type.incompatible) - @GuardedBy("this.myLock") Object oM3 = lockExample1.getLocked(); + // :: error: (assignment.type.incompatible) + @GuardedBy("this.myLock") Object oM3 = lockExample1.getLocked(); - void uses() { - lockExample1.locked = o1; - // :: error: (assignment.type.incompatible) - lockExample1.locked = o3; + void uses() { + lockExample1.locked = o1; + // :: error: (assignment.type.incompatible) + lockExample1.locked = o3; + } } - } } diff --git a/checker/tests/lock/ViewpointAdaptation3.java b/checker/tests/lock/ViewpointAdaptation3.java index 3eeb524d3dd..9cce33d8f1c 100644 --- a/checker/tests/lock/ViewpointAdaptation3.java +++ b/checker/tests/lock/ViewpointAdaptation3.java @@ -5,110 +5,110 @@ public class ViewpointAdaptation3 { - class MyClass { - Object field; - } + class MyClass { + Object field; + } - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @GuardedBy("myLock") MyClass locked; + protected @GuardedBy("myLock") MyClass locked; - @GuardedBy("this.myLock") MyClass m1; + @GuardedBy("this.myLock") MyClass m1; - protected @GuardedBy("this.myLock") MyClass locked2; + protected @GuardedBy("this.myLock") MyClass locked2; - public void accessLock() { - synchronized (myLock) { - this.locked.field = new Object(); - } + public void accessLock() { + synchronized (myLock) { + this.locked.field = new Object(); + } + } } - } - class LockExampleSubclass extends LockExample { - private final Object myLock = new Object(); + class LockExampleSubclass extends LockExample { + private final Object myLock = new Object(); - private @GuardedBy("this.myLock") MyClass locked; + private @GuardedBy("this.myLock") MyClass locked; - @GuardedBy("this.myLock") MyClass m2; + @GuardedBy("this.myLock") MyClass m2; - public LockExampleSubclass() { - final LockExampleSubclass les1 = new LockExampleSubclass(); - final LockExampleSubclass les2 = new LockExampleSubclass(); - final LockExampleSubclass les3 = les2; - LockExample le1 = new LockExample(); + public LockExampleSubclass() { + final LockExampleSubclass les1 = new LockExampleSubclass(); + final LockExampleSubclass les2 = new LockExampleSubclass(); + final LockExampleSubclass les3 = les2; + LockExample le1 = new LockExample(); - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked; - // :: error: (assignment.type.incompatible) - les1.locked = le1.locked2; + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked; + // :: error: (assignment.type.incompatible) + les1.locked = le1.locked2; - // :: error: (assignment.type.incompatible) - les1.locked = les2.locked; + // :: error: (assignment.type.incompatible) + les1.locked = les2.locked; + } } - } - - class Class1 { - public final Object lock = new Object(); - - @GuardedBy("lock") MyClass m = new MyClass(); - } - - class Class2 { - public final Object lock = new Object(); - - @GuardedBy("lock") MyClass m = new MyClass(); - - void method(final Class1 a) { - final Object lock = new Object(); - @GuardedBy("lock") MyClass local = new MyClass(); - - // :: error: (assignment.type.incompatible) - local = m; - - // :: error: (lock.not.held) - local.field = new Object(); - - synchronized (lock) { - // :: error: (lock.not.held) - a.m.field = new Object(); - } - synchronized (this.lock) { - // :: error: (lock.not.held) - a.m.field = new Object(); - } - synchronized (a.lock) { - a.m.field = new Object(); - } - - synchronized (lock) { - local.field = new Object(); - } - synchronized (this.lock) { - // :: error: (lock.not.held) - local.field = new Object(); - } - synchronized (a.lock) { - // :: error: (lock.not.held) - local.field = new Object(); - } - - synchronized (lock) { - // :: error: (lock.not.held) - this.m.field = new Object(); - // :: error: (lock.not.held) - m.field = new Object(); - } - synchronized (this.lock) { - this.m.field = new Object(); - m.field = new Object(); - } - synchronized (a.lock) { - // :: error: (lock.not.held) - this.m.field = new Object(); - // :: error: (lock.not.held) - m.field = new Object(); - } + + class Class1 { + public final Object lock = new Object(); + + @GuardedBy("lock") MyClass m = new MyClass(); + } + + class Class2 { + public final Object lock = new Object(); + + @GuardedBy("lock") MyClass m = new MyClass(); + + void method(final Class1 a) { + final Object lock = new Object(); + @GuardedBy("lock") MyClass local = new MyClass(); + + // :: error: (assignment.type.incompatible) + local = m; + + // :: error: (lock.not.held) + local.field = new Object(); + + synchronized (lock) { + // :: error: (lock.not.held) + a.m.field = new Object(); + } + synchronized (this.lock) { + // :: error: (lock.not.held) + a.m.field = new Object(); + } + synchronized (a.lock) { + a.m.field = new Object(); + } + + synchronized (lock) { + local.field = new Object(); + } + synchronized (this.lock) { + // :: error: (lock.not.held) + local.field = new Object(); + } + synchronized (a.lock) { + // :: error: (lock.not.held) + local.field = new Object(); + } + + synchronized (lock) { + // :: error: (lock.not.held) + this.m.field = new Object(); + // :: error: (lock.not.held) + m.field = new Object(); + } + synchronized (this.lock) { + this.m.field = new Object(); + m.field = new Object(); + } + synchronized (a.lock) { + // :: error: (lock.not.held) + this.m.field = new Object(); + // :: error: (lock.not.held) + m.field = new Object(); + } + } } - } } diff --git a/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java b/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java index be274e3dabe..67a25a1a297 100644 --- a/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java +++ b/checker/tests/mustcall-nolightweightownership/BorrowOnReturn.java @@ -3,47 +3,47 @@ import org.checkerframework.checker.mustcall.qual.*; class BorrowOnReturn { - @InheritableMustCall("a") - class Foo { - void a() {} - } - - @Owning - Object getOwnedFoo() { - // :: error: (return.type.incompatible) - return new Foo(); - } - - Object getNoAnnoFoo() { - // Treat as owning, so warn - // :: error: (return.type.incompatible) - return new Foo(); - } - - @NotOwning - Object getNotOwningFooWrong() { - // :: error: (return.type.incompatible) - return new Foo(); - } - - Object getNotOwningFooRightButNoNotOwningAnno() { - Foo f = new Foo(); - f.a(); - // This is still an error for now, because it's treated as an owning pointer. TODO: fix this - // kind of FP? - // :: error: (return.type.incompatible) - return f; - } - - @NotOwning - Object getNotOwningFooRight() { - Foo f = new Foo(); - f.a(); - // :: error: (return.type.incompatible) - return f; - } - - @MustCall("a") Object getNotOwningFooRight2() { - return new Foo(); - } + @InheritableMustCall("a") + class Foo { + void a() {} + } + + @Owning + Object getOwnedFoo() { + // :: error: (return.type.incompatible) + return new Foo(); + } + + Object getNoAnnoFoo() { + // Treat as owning, so warn + // :: error: (return.type.incompatible) + return new Foo(); + } + + @NotOwning + Object getNotOwningFooWrong() { + // :: error: (return.type.incompatible) + return new Foo(); + } + + Object getNotOwningFooRightButNoNotOwningAnno() { + Foo f = new Foo(); + f.a(); + // This is still an error for now, because it's treated as an owning pointer. TODO: fix this + // kind of FP? + // :: error: (return.type.incompatible) + return f; + } + + @NotOwning + Object getNotOwningFooRight() { + Foo f = new Foo(); + f.a(); + // :: error: (return.type.incompatible) + return f; + } + + @MustCall("a") Object getNotOwningFooRight2() { + return new Foo(); + } } diff --git a/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java b/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java index a93deae7637..5ad580b4f78 100644 --- a/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java +++ b/checker/tests/mustcall-nolightweightownership/NonOwningPolyInteraction.java @@ -4,41 +4,42 @@ // This version is modified to expect that owning/notowning annotations do nothing, // for the -AnoLightweightOwnership flag. -import java.io.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class NonOwningPolyInteraction { - void foo(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - void bar(@Owning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - void baz(InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - NonOwningPolyInteraction(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // extra param(s) here and on the next constructor because Java requires constructors to have - // different signatures. - NonOwningPolyInteraction(@Owning InputStream instream, int x) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - NonOwningPolyInteraction(InputStream instream, int x, int y) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } + void foo(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + void bar(@Owning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + void baz(InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + NonOwningPolyInteraction(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // extra param(s) here and on the next constructor because Java requires constructors to have + // different signatures. + NonOwningPolyInteraction(@Owning InputStream instream, int x) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + NonOwningPolyInteraction(InputStream instream, int x, int y) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } } diff --git a/checker/tests/mustcall-nolightweightownership/OwningParams.java b/checker/tests/mustcall-nolightweightownership/OwningParams.java index 29dd91ac418..4099b1a4434 100644 --- a/checker/tests/mustcall-nolightweightownership/OwningParams.java +++ b/checker/tests/mustcall-nolightweightownership/OwningParams.java @@ -5,9 +5,9 @@ import org.checkerframework.checker.mustcall.qual.*; class OwningParams { - static void o1(@Owning OwningParams o) {} + static void o1(@Owning OwningParams o) {} - void test(@Owning @MustCall({"a"}) OwningParams o) { - o1(o); - } + void test(@Owning @MustCall({"a"}) OwningParams o) { + o1(o); + } } diff --git a/checker/tests/mustcall/BinaryInputArchive.java b/checker/tests/mustcall/BinaryInputArchive.java index 8f414af5724..978782c263e 100644 --- a/checker/tests/mustcall/BinaryInputArchive.java +++ b/checker/tests/mustcall/BinaryInputArchive.java @@ -5,13 +5,13 @@ class BinaryInputArchive { - private DataInput in; + private DataInput in; - public BinaryInputArchive(DataInput in) { - this.in = in; - } + public BinaryInputArchive(DataInput in) { + this.in = in; + } - public static BinaryInputArchive getArchive(InputStream strm) { - return new BinaryInputArchive(new DataInputStream(strm)); - } + public static BinaryInputArchive getArchive(InputStream strm) { + return new BinaryInputArchive(new DataInputStream(strm)); + } } diff --git a/checker/tests/mustcall/BorrowOnReturn.java b/checker/tests/mustcall/BorrowOnReturn.java index af292dd4e04..fb432793112 100644 --- a/checker/tests/mustcall/BorrowOnReturn.java +++ b/checker/tests/mustcall/BorrowOnReturn.java @@ -3,46 +3,46 @@ import org.checkerframework.checker.mustcall.qual.*; class BorrowOnReturn { - @InheritableMustCall("a") - class Foo { - void a() {} - } - - @Owning - Object getOwnedFoo() { - // :: error: (return.type.incompatible) - return new Foo(); - } - - Object getNoAnnoFoo() { - // Treat as owning, so warn - // :: error: (return.type.incompatible) - return new Foo(); - } - - @NotOwning - Object getNotOwningFooWrong() { - // OCC must-call checker will warn about this; MC checker isn't responsible - return new Foo(); - } - - Object getNotOwningFooRightButNoNotOwningAnno() { - Foo f = new Foo(); - f.a(); - // This is still an error for now, because it's treated as an owning pointer. TODO: fix this - // kind of FP? - // :: error: (return.type.incompatible) - return f; - } - - @NotOwning - Object getNotOwningFooRight() { - Foo f = new Foo(); - f.a(); - return f; - } - - @MustCall("a") Object getNotOwningFooRight2() { - return new Foo(); - } + @InheritableMustCall("a") + class Foo { + void a() {} + } + + @Owning + Object getOwnedFoo() { + // :: error: (return.type.incompatible) + return new Foo(); + } + + Object getNoAnnoFoo() { + // Treat as owning, so warn + // :: error: (return.type.incompatible) + return new Foo(); + } + + @NotOwning + Object getNotOwningFooWrong() { + // OCC must-call checker will warn about this; MC checker isn't responsible + return new Foo(); + } + + Object getNotOwningFooRightButNoNotOwningAnno() { + Foo f = new Foo(); + f.a(); + // This is still an error for now, because it's treated as an owning pointer. TODO: fix this + // kind of FP? + // :: error: (return.type.incompatible) + return f; + } + + @NotOwning + Object getNotOwningFooRight() { + Foo f = new Foo(); + f.a(); + return f; + } + + @MustCall("a") Object getNotOwningFooRight2() { + return new Foo(); + } } diff --git a/checker/tests/mustcall/ClassForNameInit.java b/checker/tests/mustcall/ClassForNameInit.java index de5904b791f..28b83dc344a 100644 --- a/checker/tests/mustcall/ClassForNameInit.java +++ b/checker/tests/mustcall/ClassForNameInit.java @@ -9,31 +9,31 @@ class ClassForNameInit { - public static InputStream inputStreamFactory() throws Exception { - // FYI this code will always fail if you run it, so don't. - // There's no ByteArrayInputStream constructor that takes no arguments. - Class baisClass = Class.forName("java.io.ByteArrayInputStream"); - Object bais = baisClass.getConstructor().newInstance(); - return (InputStream) bais; - } - - public static Object objectFactory() throws Exception { - Class objClass = Class.forName("java.lang.Object"); - Object obj = objClass.getConstructor().newInstance(); - return (Object) obj; - } + public static InputStream inputStreamFactory() throws Exception { + // FYI this code will always fail if you run it, so don't. + // There's no ByteArrayInputStream constructor that takes no arguments. + Class baisClass = Class.forName("java.io.ByteArrayInputStream"); + Object bais = baisClass.getConstructor().newInstance(); + return (InputStream) bais; + } - private static Object getAuditLogger(String auditLoggerClass) { - if (auditLoggerClass == null) { - auditLoggerClass = Object.class.getName(); + public static Object objectFactory() throws Exception { + Class objClass = Class.forName("java.lang.Object"); + Object obj = objClass.getConstructor().newInstance(); + return (Object) obj; } - try { - Constructor clientCxnConstructor = - Class.forName(auditLoggerClass).getDeclaredConstructor(); - Object auditLogger = (Object) clientCxnConstructor.newInstance(); - return auditLogger; - } catch (Exception e) { - throw new RuntimeException("Couldn't instantiate " + auditLoggerClass, e); + + private static Object getAuditLogger(String auditLoggerClass) { + if (auditLoggerClass == null) { + auditLoggerClass = Object.class.getName(); + } + try { + Constructor clientCxnConstructor = + Class.forName(auditLoggerClass).getDeclaredConstructor(); + Object auditLogger = (Object) clientCxnConstructor.newInstance(); + return auditLogger; + } catch (Exception e) { + throw new RuntimeException("Couldn't instantiate " + auditLoggerClass, e); + } } - } } diff --git a/checker/tests/mustcall/CommandResponse.java b/checker/tests/mustcall/CommandResponse.java index d36f2885cba..189fa73fa67 100644 --- a/checker/tests/mustcall/CommandResponse.java +++ b/checker/tests/mustcall/CommandResponse.java @@ -3,13 +3,13 @@ import java.util.Map; class CommandResponse { - Map data; + Map data; - public void putAll(Map m) { - data.putAll(m); - } + public void putAll(Map m) { + data.putAll(m); + } - public void putAll2(Map m) { - data.putAll(m); - } + public void putAll2(Map m) { + data.putAll(m); + } } diff --git a/checker/tests/mustcall/CreatesMustCallForSimple.java b/checker/tests/mustcall/CreatesMustCallForSimple.java index 5ab7be89f43..097f4101e75 100644 --- a/checker/tests/mustcall/CreatesMustCallForSimple.java +++ b/checker/tests/mustcall/CreatesMustCallForSimple.java @@ -5,53 +5,53 @@ @InheritableMustCall("a") class CreatesMustCallForSimple { - @CreatesMustCallFor - void reset() {} - - @CreatesMustCallFor("this") - void resetThis() {} - - static @MustCall({}) CreatesMustCallForSimple makeNoMC() { - return null; - } - - static void test1() { - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.reset(); - // :: error: assignment.type.incompatible - @MustCall({}) CreatesMustCallForSimple b = cos; - @MustCall("a") CreatesMustCallForSimple c = cos; - } - - static void test2() { - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.resetThis(); - // :: error: assignment.type.incompatible - @MustCall({}) CreatesMustCallForSimple b = cos; - @MustCall("a") CreatesMustCallForSimple c = cos; - } - - static void test3() { - Object cos = makeNoMC(); - @MustCall({}) Object a = cos; - // :: error: createsmustcallfor.target.unparseable - ((CreatesMustCallForSimple) cos).reset(); - // It would be better to issue an assignment incompatible error here, but the - // error above is okay too. - @MustCall({}) Object b = cos; - @MustCall("a") Object c = cos; - } - - // Rewrite of test3 that follows the instructions in the error message. - static void test4() { - Object cos = makeNoMC(); - @MustCall({}) Object a = cos; - CreatesMustCallForSimple r = ((CreatesMustCallForSimple) cos); - r.reset(); - // :: error: assignment.type.incompatible - @MustCall({}) Object b = r; - @MustCall("a") Object c = r; - } + @CreatesMustCallFor + void reset() {} + + @CreatesMustCallFor("this") + void resetThis() {} + + static @MustCall({}) CreatesMustCallForSimple makeNoMC() { + return null; + } + + static void test1() { + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.reset(); + // :: error: assignment.type.incompatible + @MustCall({}) CreatesMustCallForSimple b = cos; + @MustCall("a") CreatesMustCallForSimple c = cos; + } + + static void test2() { + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.resetThis(); + // :: error: assignment.type.incompatible + @MustCall({}) CreatesMustCallForSimple b = cos; + @MustCall("a") CreatesMustCallForSimple c = cos; + } + + static void test3() { + Object cos = makeNoMC(); + @MustCall({}) Object a = cos; + // :: error: createsmustcallfor.target.unparseable + ((CreatesMustCallForSimple) cos).reset(); + // It would be better to issue an assignment incompatible error here, but the + // error above is okay too. + @MustCall({}) Object b = cos; + @MustCall("a") Object c = cos; + } + + // Rewrite of test3 that follows the instructions in the error message. + static void test4() { + Object cos = makeNoMC(); + @MustCall({}) Object a = cos; + CreatesMustCallForSimple r = ((CreatesMustCallForSimple) cos); + r.reset(); + // :: error: assignment.type.incompatible + @MustCall({}) Object b = r; + @MustCall("a") Object c = r; + } } diff --git a/checker/tests/mustcall/EditLogInputStream.java b/checker/tests/mustcall/EditLogInputStream.java index c8ad96750a9..91a74a62be2 100644 --- a/checker/tests/mustcall/EditLogInputStream.java +++ b/checker/tests/mustcall/EditLogInputStream.java @@ -2,10 +2,10 @@ import java.util.*; abstract class EditLogInputStream implements Closeable { - public abstract boolean isLocalLog(); + public abstract boolean isLocalLog(); } interface JournalSet extends Closeable { - static final Comparator LOCAL_LOG_PREFERENCE_COMPARATOR = - Comparator.comparing(EditLogInputStream::isLocalLog).reversed(); + static final Comparator LOCAL_LOG_PREFERENCE_COMPARATOR = + Comparator.comparing(EditLogInputStream::isLocalLog).reversed(); } diff --git a/checker/tests/mustcall/FieldInitializationWithGeneric.java b/checker/tests/mustcall/FieldInitializationWithGeneric.java index 717d0b3f143..d3025a86e16 100644 --- a/checker/tests/mustcall/FieldInitializationWithGeneric.java +++ b/checker/tests/mustcall/FieldInitializationWithGeneric.java @@ -4,6 +4,6 @@ import java.util.concurrent.ConcurrentHashMap; class FieldInitializationWithGeneric { - private Set activeObservers = - Collections.newSetFromMap(new ConcurrentHashMap()); + private Set activeObservers = + Collections.newSetFromMap(new ConcurrentHashMap()); } diff --git a/checker/tests/mustcall/FileDescriptors.java b/checker/tests/mustcall/FileDescriptors.java index 4f38ccc6fea..5586abd420c 100644 --- a/checker/tests/mustcall/FileDescriptors.java +++ b/checker/tests/mustcall/FileDescriptors.java @@ -1,18 +1,19 @@ // A test for some issues related to the getFD() method in RandomAccessFile. -import java.io.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class FileDescriptors { - void test(@Owning RandomAccessFile r) throws Exception { - @MustCall("close") FileDescriptor fd = r.getFD(); - // :: error: assignment.type.incompatible - @MustCall({}) FileDescriptor fd2 = r.getFD(); - } + void test(@Owning RandomAccessFile r) throws Exception { + @MustCall("close") FileDescriptor fd = r.getFD(); + // :: error: assignment.type.incompatible + @MustCall({}) FileDescriptor fd2 = r.getFD(); + } - void test2(@Owning RandomAccessFile r) throws Exception { - @MustCall("close") FileInputStream f = new FileInputStream(r.getFD()); - // :: error: assignment.type.incompatible - @MustCall({}) FileInputStream f2 = new FileInputStream(r.getFD()); - } + void test2(@Owning RandomAccessFile r) throws Exception { + @MustCall("close") FileInputStream f = new FileInputStream(r.getFD()); + // :: error: assignment.type.incompatible + @MustCall({}) FileInputStream f2 = new FileInputStream(r.getFD()); + } } diff --git a/checker/tests/mustcall/InferTypeArgs.java b/checker/tests/mustcall/InferTypeArgs.java index 86f6a18b400..7563486e277 100644 --- a/checker/tests/mustcall/InferTypeArgs.java +++ b/checker/tests/mustcall/InferTypeArgs.java @@ -6,16 +6,16 @@ class CFAbstractValue> {} class CFAbstractAnalysis> {} class GenericAnnotatedTypeFactoryMustCallTest< - Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { + Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { - protected FlowAnalysis createFlowAnalysis() { - FlowAnalysis result = invokeConstructorFor(); - return result; - } + protected FlowAnalysis createFlowAnalysis() { + FlowAnalysis result = invokeConstructorFor(); + return result; + } - // The difference between this version of this test and the all-systems version is the "extends - // Object" on the next line. - public static T invokeConstructorFor() { - return null; - } + // The difference between this version of this test and the all-systems version is the "extends + // Object" on the next line. + public static T invokeConstructorFor() { + return null; + } } diff --git a/checker/tests/mustcall/InheritableMustCallEmpty.java b/checker/tests/mustcall/InheritableMustCallEmpty.java index 5eb1f16abce..113c69efaac 100644 --- a/checker/tests/mustcall/InheritableMustCallEmpty.java +++ b/checker/tests/mustcall/InheritableMustCallEmpty.java @@ -1,25 +1,26 @@ // A simple test for @InheritableMustCall({}). -import java.io.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class InheritableMustCallEmpty { - @InheritableMustCall({}) - // :: error: inconsistent.mustcall.subtype - class NoObligationCloseable implements Closeable { - @Override - public void close() throws IOException { - // no resource, nothing to do + @InheritableMustCall({}) + // :: error: inconsistent.mustcall.subtype + class NoObligationCloseable implements Closeable { + @Override + public void close() throws IOException { + // no resource, nothing to do + } } - } - @InheritableMustCall() - // :: error: inconsistent.mustcall.subtype - class NoObligationCloseable2 implements Closeable { - @Override - public void close() throws IOException { - // no resource, nothing to do + @InheritableMustCall() + // :: error: inconsistent.mustcall.subtype + class NoObligationCloseable2 implements Closeable { + @Override + public void close() throws IOException { + // no resource, nothing to do + } } - } } diff --git a/checker/tests/mustcall/ListOfMustCall.java b/checker/tests/mustcall/ListOfMustCall.java index cd32d530fbb..c2521752b25 100644 --- a/checker/tests/mustcall/ListOfMustCall.java +++ b/checker/tests/mustcall/ListOfMustCall.java @@ -6,36 +6,37 @@ // (or equivalently List, etc.). // We should revisit this test if we ever revisit the idea of adding support for owning generics. -import java.util.*; import org.checkerframework.checker.mustcall.qual.*; +import java.util.*; + @InheritableMustCall("a") class ListOfMustCall { - static void test(ListOfMustCall lm) { - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - List l = new ArrayList<>(); - // add(E e) takes an object of the type argument's type - l.add(lm); - // remove(Object e) takes an object - l.remove(lm); - } + static void test(ListOfMustCall lm) { + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + List l = new ArrayList<>(); + // add(E e) takes an object of the type argument's type + l.add(lm); + // remove(Object e) takes an object + l.remove(lm); + } - static void test2(ListOfMustCall lm) { - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - List<@MustCall("a") ListOfMustCall> l = new ArrayList<>(); - l.add(lm); - l.remove(lm); - } + static void test2(ListOfMustCall lm) { + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + List<@MustCall("a") ListOfMustCall> l = new ArrayList<>(); + l.add(lm); + l.remove(lm); + } - static void test3(ListOfMustCall lm) { - List l = new ArrayList<>(); - l.remove(lm); - } + static void test3(ListOfMustCall lm) { + List l = new ArrayList<>(); + l.remove(lm); + } - static void test4(ListOfMustCall lm) { - List l = new ArrayList<>(); - l.remove(lm); - } + static void test4(ListOfMustCall lm) { + List l = new ArrayList<>(); + l.remove(lm); + } } diff --git a/checker/tests/mustcall/LogTheSocket.java b/checker/tests/mustcall/LogTheSocket.java index a7b2ba1c692..ddc235a9dc7 100644 --- a/checker/tests/mustcall/LogTheSocket.java +++ b/checker/tests/mustcall/LogTheSocket.java @@ -17,60 +17,61 @@ // This test is also coincidentally a test case for // https://github.com/typetools/checker-framework/pull/3867. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.IOException; import java.net.ServerSocket; import java.nio.channels.SocketChannel; -import org.checkerframework.checker.mustcall.qual.*; class LogTheSocket { - @NotOwning ServerSocket s; + @NotOwning ServerSocket s; - @MustCall("") Object s2; + @MustCall("") Object s2; - void testAssign(@Owning ServerSocket s1) { - s = s1; - // :: error: assignment.type.incompatible - s2 = s1; - } + void testAssign(@Owning ServerSocket s1) { + s = s1; + // :: error: assignment.type.incompatible + s2 = s1; + } - void logVarargs(String s, Object... objects) {} + void logVarargs(String s, Object... objects) {} - void logNoVarargs(String s, Object object) {} + void logNoVarargs(String s, Object object) {} - void test(ServerSocket serverSocket) { - if (!serverSocket.isClosed()) { - try { - serverSocket.close(); - } catch (IOException e) { - logVarargs("Ignoring unexpected exception during close {}", serverSocket, e); - } + void test(ServerSocket serverSocket) { + if (!serverSocket.isClosed()) { + try { + serverSocket.close(); + } catch (IOException e) { + logVarargs("Ignoring unexpected exception during close {}", serverSocket, e); + } + } } - } - void test2(ServerSocket serverSocket) { - if (!serverSocket.isClosed()) { - try { - serverSocket.close(); - } catch (IOException e) { - logNoVarargs("Ignoring unexpected exception during close {}", serverSocket); - } + void test2(ServerSocket serverSocket) { + if (!serverSocket.isClosed()) { + try { + serverSocket.close(); + } catch (IOException e) { + logNoVarargs("Ignoring unexpected exception during close {}", serverSocket); + } + } } - } - // This is (mostly) copied from ACSocketTest; under a previous implementation of the - // ownership-transfer scheme, - // it caused false positive warnings from the Must Call checker. - SocketChannel createSock() throws IOException { - SocketChannel sock; - sock = SocketChannel.open(); - sock.configureBlocking(false); - sock.socket().setSoLinger(false, -1); - sock.socket().setTcpNoDelay(true); - return sock; - } + // This is (mostly) copied from ACSocketTest; under a previous implementation of the + // ownership-transfer scheme, + // it caused false positive warnings from the Must Call checker. + SocketChannel createSock() throws IOException { + SocketChannel sock; + sock = SocketChannel.open(); + sock.configureBlocking(false); + sock.socket().setSoLinger(false, -1); + sock.socket().setTcpNoDelay(true); + return sock; + } - void testPrintln(ServerSocket s) { - System.out.println(s); - } + void testPrintln(ServerSocket s) { + System.out.println(s); + } } diff --git a/checker/tests/mustcall/MapWrap.java b/checker/tests/mustcall/MapWrap.java index f2a65cdbc37..52f792c691f 100644 --- a/checker/tests/mustcall/MapWrap.java +++ b/checker/tests/mustcall/MapWrap.java @@ -1,21 +1,22 @@ // A test for a class that wraps a map. I found a similar example in Zookeeper that causes false // positives. -import java.util.HashMap; import org.checkerframework.checker.mustcall.qual.*; +import java.util.HashMap; + class MapWrap { - HashMap impl = new HashMap(); + HashMap impl = new HashMap(); - String remove(E e) { - // remove should permit any object: its signature is remove(Object key), *not* remove(E key) - String old = impl.remove(e); - return old; - } + String remove(E e) { + // remove should permit any object: its signature is remove(Object key), *not* remove(E key) + String old = impl.remove(e); + return old; + } - String remove2(@MustCall({}) E e) { - // remove should permit any object: its signature is remove(Object key), *not* remove(E key) - String old = impl.remove(e); - return old; - } + String remove2(@MustCall({}) E e) { + // remove should permit any object: its signature is remove(Object key), *not* remove(E key) + String old = impl.remove(e); + return old; + } } diff --git a/checker/tests/mustcall/MustCallAliasImpl.java b/checker/tests/mustcall/MustCallAliasImpl.java index 3c23f659824..e2f87781f7a 100644 --- a/checker/tests/mustcall/MustCallAliasImpl.java +++ b/checker/tests/mustcall/MustCallAliasImpl.java @@ -1,19 +1,20 @@ // A simple test that MustCallAlias annotations in source code don't issue // bogus annotations.on.use errors. -import java.io.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class MustCallAliasImpl implements Closeable { - @Owning final Closeable foo; + @Owning final Closeable foo; - public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { - this.foo = foo; - } + public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { + this.foo = foo; + } - @Override - public void close() throws IOException { - this.foo.close(); - } + @Override + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/mustcall/MustCallSubtypingTest.java b/checker/tests/mustcall/MustCallSubtypingTest.java index c0e5903f914..affa13cb52b 100644 --- a/checker/tests/mustcall/MustCallSubtypingTest.java +++ b/checker/tests/mustcall/MustCallSubtypingTest.java @@ -7,126 +7,126 @@ public class MustCallSubtypingTest { - @MustCall({"toString"}) String foo(@MustCall({"hashCode"}) String arg) { - // :: (return) - return arg; - } - - @MustCall({}) String mcEmpty; - - @MustCall({"hashCode"}) String mcHashCode; - - @MustCall({"toString"}) String mcToString; - - @MustCallUnknown String mcUnknown; - - void clientSetMcEmpty() { - mcEmpty = mcHashCode; - mcEmpty = mcToString; - mcEmpty = mcUnknown; - } - - void clientSetMcHashCode() { - mcHashCode = mcEmpty; - mcHashCode = mcToString; - mcHashCode = mcUnknown; - } - - void clientSetMcToString() { - mcToString = mcEmpty; - mcToString = mcHashCode; - mcToString = mcUnknown; - } - - void clientSetMcUnknown() { - mcUnknown = mcEmpty; - mcUnknown = mcHashCode; - mcUnknown = mcToString; - } - - void requiresMustCallEmptyObject(@MustCall({}) Object o) {} - - void requiresMustCallHashCodeObject(@MustCall({"hashCode"}) Object o) {} - - void requiresMustCallToStringObject(@MustCall({"toString"}) Object o) {} - - void requiresMustCallUnknownObject(@MustCallUnknown Object o) {} - - void requiresMustCallEmptyString(@MustCall({}) String s) {} - - void requiresMustCallHashCodeString(@MustCall({"hashCode"}) String s) {} - - void requiresMustCallToStringString(@MustCall({"toString"}) String s) {} - - void requiresMustCallUnknownString(@MustCallUnknown String s) {} - - void client(Integer i, Integer[] ia) { - requiresMustCallEmptyObject(i); - requiresMustCallEmptyObject(ia); - // :: (argument) - requiresMustCallEmptyObject(mcHashCode); - // :: (argument) - requiresMustCallEmptyObject(mcToString); - requiresMustCallEmptyObject(mcEmpty); - // :: (argument) - requiresMustCallEmptyObject(mcUnknown); - - // :: (argument) - requiresMustCallEmptyString(mcHashCode); - // :: (argument) - requiresMustCallEmptyString(mcToString); - requiresMustCallEmptyString(mcEmpty); - // :: (argument) - requiresMustCallEmptyString(mcUnknown); - - requiresMustCallHashCodeObject(i); - requiresMustCallHashCodeObject(ia); - requiresMustCallHashCodeObject(mcHashCode); - // :: (argument) - requiresMustCallHashCodeObject(mcToString); - requiresMustCallHashCodeObject(mcEmpty); - // :: (argument) - requiresMustCallHashCodeObject(mcUnknown); - - requiresMustCallHashCodeString(mcHashCode); - // :: (argument) - requiresMustCallHashCodeString(mcToString); - requiresMustCallHashCodeString(mcEmpty); - // :: (argument) - requiresMustCallHashCodeString(mcUnknown); - - requiresMustCallToStringObject(i); - requiresMustCallToStringObject(ia); - // :: (argument) - requiresMustCallToStringObject(mcHashCode); - requiresMustCallToStringObject(mcToString); - requiresMustCallToStringObject(mcEmpty); - // :: (argument) - requiresMustCallToStringObject(mcUnknown); - - // :: (argument) - requiresMustCallToStringString(mcHashCode); - requiresMustCallToStringString(mcToString); - requiresMustCallToStringString(mcEmpty); - // :: (argument) - requiresMustCallToStringString(mcUnknown); - - requiresMustCallUnknownObject(i); - requiresMustCallUnknownObject(ia); - // :: (argument) - requiresMustCallUnknownObject(mcHashCode); - // :: (argument) - requiresMustCallUnknownObject(mcToString); - // :: (argument) - requiresMustCallUnknownObject(mcEmpty); - requiresMustCallUnknownObject(mcUnknown); - - // :: (argument) - requiresMustCallUnknownString(mcHashCode); - // :: (argument) - requiresMustCallUnknownString(mcToString); - // :: (argument) - requiresMustCallUnknownString(mcEmpty); - requiresMustCallUnknownString(mcUnknown); - } + @MustCall({"toString"}) String foo(@MustCall({"hashCode"}) String arg) { + // :: (return) + return arg; + } + + @MustCall({}) String mcEmpty; + + @MustCall({"hashCode"}) String mcHashCode; + + @MustCall({"toString"}) String mcToString; + + @MustCallUnknown String mcUnknown; + + void clientSetMcEmpty() { + mcEmpty = mcHashCode; + mcEmpty = mcToString; + mcEmpty = mcUnknown; + } + + void clientSetMcHashCode() { + mcHashCode = mcEmpty; + mcHashCode = mcToString; + mcHashCode = mcUnknown; + } + + void clientSetMcToString() { + mcToString = mcEmpty; + mcToString = mcHashCode; + mcToString = mcUnknown; + } + + void clientSetMcUnknown() { + mcUnknown = mcEmpty; + mcUnknown = mcHashCode; + mcUnknown = mcToString; + } + + void requiresMustCallEmptyObject(@MustCall({}) Object o) {} + + void requiresMustCallHashCodeObject(@MustCall({"hashCode"}) Object o) {} + + void requiresMustCallToStringObject(@MustCall({"toString"}) Object o) {} + + void requiresMustCallUnknownObject(@MustCallUnknown Object o) {} + + void requiresMustCallEmptyString(@MustCall({}) String s) {} + + void requiresMustCallHashCodeString(@MustCall({"hashCode"}) String s) {} + + void requiresMustCallToStringString(@MustCall({"toString"}) String s) {} + + void requiresMustCallUnknownString(@MustCallUnknown String s) {} + + void client(Integer i, Integer[] ia) { + requiresMustCallEmptyObject(i); + requiresMustCallEmptyObject(ia); + // :: (argument) + requiresMustCallEmptyObject(mcHashCode); + // :: (argument) + requiresMustCallEmptyObject(mcToString); + requiresMustCallEmptyObject(mcEmpty); + // :: (argument) + requiresMustCallEmptyObject(mcUnknown); + + // :: (argument) + requiresMustCallEmptyString(mcHashCode); + // :: (argument) + requiresMustCallEmptyString(mcToString); + requiresMustCallEmptyString(mcEmpty); + // :: (argument) + requiresMustCallEmptyString(mcUnknown); + + requiresMustCallHashCodeObject(i); + requiresMustCallHashCodeObject(ia); + requiresMustCallHashCodeObject(mcHashCode); + // :: (argument) + requiresMustCallHashCodeObject(mcToString); + requiresMustCallHashCodeObject(mcEmpty); + // :: (argument) + requiresMustCallHashCodeObject(mcUnknown); + + requiresMustCallHashCodeString(mcHashCode); + // :: (argument) + requiresMustCallHashCodeString(mcToString); + requiresMustCallHashCodeString(mcEmpty); + // :: (argument) + requiresMustCallHashCodeString(mcUnknown); + + requiresMustCallToStringObject(i); + requiresMustCallToStringObject(ia); + // :: (argument) + requiresMustCallToStringObject(mcHashCode); + requiresMustCallToStringObject(mcToString); + requiresMustCallToStringObject(mcEmpty); + // :: (argument) + requiresMustCallToStringObject(mcUnknown); + + // :: (argument) + requiresMustCallToStringString(mcHashCode); + requiresMustCallToStringString(mcToString); + requiresMustCallToStringString(mcEmpty); + // :: (argument) + requiresMustCallToStringString(mcUnknown); + + requiresMustCallUnknownObject(i); + requiresMustCallUnknownObject(ia); + // :: (argument) + requiresMustCallUnknownObject(mcHashCode); + // :: (argument) + requiresMustCallUnknownObject(mcToString); + // :: (argument) + requiresMustCallUnknownObject(mcEmpty); + requiresMustCallUnknownObject(mcUnknown); + + // :: (argument) + requiresMustCallUnknownString(mcHashCode); + // :: (argument) + requiresMustCallUnknownString(mcToString); + // :: (argument) + requiresMustCallUnknownString(mcEmpty); + requiresMustCallUnknownString(mcUnknown); + } } diff --git a/checker/tests/mustcall/MyDataInputStream.java b/checker/tests/mustcall/MyDataInputStream.java index d77a39d6941..4cbdf61c395 100644 --- a/checker/tests/mustcall/MyDataInputStream.java +++ b/checker/tests/mustcall/MyDataInputStream.java @@ -4,20 +4,20 @@ import java.nio.ByteBuffer; public class MyDataInputStream extends DataInputStream { - public MyDataInputStream(InputStream in) { - super(in); - } + public MyDataInputStream(InputStream in) { + super(in); + } - public void readFully(long position, ByteBuffer buf) throws IOException { - if (in instanceof ByteBufferPositionedReadable) { - ((ByteBufferPositionedReadable) in).readFully(position, buf); - } else { - throw new UnsupportedOperationException( - "Byte-buffer pread unsupported by " + in.getClass().getCanonicalName()); + public void readFully(long position, ByteBuffer buf) throws IOException { + if (in instanceof ByteBufferPositionedReadable) { + ((ByteBufferPositionedReadable) in).readFully(position, buf); + } else { + throw new UnsupportedOperationException( + "Byte-buffer pread unsupported by " + in.getClass().getCanonicalName()); + } } - } - interface ByteBufferPositionedReadable { - void readFully(long position, ByteBuffer buf) throws IOException; - } + interface ByteBufferPositionedReadable { + void readFully(long position, ByteBuffer buf) throws IOException; + } } diff --git a/checker/tests/mustcall/NonOwningPolyInteraction.java b/checker/tests/mustcall/NonOwningPolyInteraction.java index 39542fecd7c..b8183f41264 100644 --- a/checker/tests/mustcall/NonOwningPolyInteraction.java +++ b/checker/tests/mustcall/NonOwningPolyInteraction.java @@ -1,43 +1,44 @@ // A test that non-owning method parameters are really treated as @MustCall({}) // wrt polymorphic types. Based on some false positives in Zookeeper. -import java.io.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class NonOwningPolyInteraction { - void foo(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - void bar(@Owning InputStream instream) { - // :: error: assignment.type.incompatible - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - void baz(InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - NonOwningPolyInteraction(@NotOwning InputStream instream) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // extra param(s) here and on the next constructor because Java requires constructors to have - // different signatures. - NonOwningPolyInteraction(@Owning InputStream instream, int x) { - // :: error: assignment.type.incompatible - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } - - // default anno for params in @NotOwning - NonOwningPolyInteraction(InputStream instream, int x, int y) { - @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); - @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); - } + void foo(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + void bar(@Owning InputStream instream) { + // :: error: assignment.type.incompatible + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + void baz(InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + NonOwningPolyInteraction(@NotOwning InputStream instream) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // extra param(s) here and on the next constructor because Java requires constructors to have + // different signatures. + NonOwningPolyInteraction(@Owning InputStream instream, int x) { + // :: error: assignment.type.incompatible + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } + + // default anno for params in @NotOwning + NonOwningPolyInteraction(InputStream instream, int x, int y) { + @MustCall({}) BufferedInputStream bis = new BufferedInputStream(instream); + @MustCall({"close"}) BufferedInputStream bis2 = new BufferedInputStream(instream); + } } diff --git a/checker/tests/mustcall/NotInheritableMustCallOnClassError.java b/checker/tests/mustcall/NotInheritableMustCallOnClassError.java index bce0288f500..12e053868b7 100644 --- a/checker/tests/mustcall/NotInheritableMustCallOnClassError.java +++ b/checker/tests/mustcall/NotInheritableMustCallOnClassError.java @@ -7,5 +7,5 @@ @MustCall("foo") // :: warning: mustcall.not.inheritable public class NotInheritableMustCallOnClassError { - public void foo() {} + public void foo() {} } diff --git a/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java b/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java index 002cde3cae9..d60849ae05f 100644 --- a/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java +++ b/checker/tests/mustcall/NotInheritableMustCallOnFinalClassNoError.java @@ -4,5 +4,5 @@ import org.checkerframework.checker.mustcall.qual.*; @MustCall("foo") public final class NotInheritableMustCallOnFinalClassNoError { - public void foo() {} + public void foo() {} } diff --git a/checker/tests/mustcall/NullOutputStreamTest.java b/checker/tests/mustcall/NullOutputStreamTest.java index 184fc2ca142..6ece0518145 100644 --- a/checker/tests/mustcall/NullOutputStreamTest.java +++ b/checker/tests/mustcall/NullOutputStreamTest.java @@ -1,11 +1,12 @@ // @below-java11-jdk-skip-test OutputStream.nullOutputStream() was introduced in JDK 11. -import java.io.OutputStream; import org.checkerframework.checker.mustcall.qual.MustCall; +import java.io.OutputStream; + class NullOutputStreamTest { - void m() { - @MustCall() OutputStream nullOS = OutputStream.nullOutputStream(); - } + void m() { + @MustCall() OutputStream nullOS = OutputStream.nullOutputStream(); + } } diff --git a/checker/tests/mustcall/NullableTransfer.java b/checker/tests/mustcall/NullableTransfer.java index 1505b4782f9..d716f8137a3 100644 --- a/checker/tests/mustcall/NullableTransfer.java +++ b/checker/tests/mustcall/NullableTransfer.java @@ -1,18 +1,19 @@ // A test that the must-call type of an object tested against null // is always empty. -import java.io.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class NullableTransfer { - void test(@Owning InputStream is) { - if (is == null) { - @MustCall({}) InputStream is2 = is; - } else { - // :: error: assignment.type.incompatible - @MustCall({}) InputStream is3 = is; - @MustCall("close") InputStream is4 = is; + void test(@Owning InputStream is) { + if (is == null) { + @MustCall({}) InputStream is2 = is; + } else { + // :: error: assignment.type.incompatible + @MustCall({}) InputStream is3 = is; + @MustCall("close") InputStream is4 = is; + } } - } } diff --git a/checker/tests/mustcall/OwningMustCallNothing.java b/checker/tests/mustcall/OwningMustCallNothing.java index e5b1eda6411..2035a60b9c5 100644 --- a/checker/tests/mustcall/OwningMustCallNothing.java +++ b/checker/tests/mustcall/OwningMustCallNothing.java @@ -1,48 +1,49 @@ -import java.io.Closeable; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; +import java.io.Closeable; + @InheritableMustCall({}) // :: error: (inconsistent.mustcall.subtype) public class OwningMustCallNothing implements Closeable { - protected @Owning AnnotationClassLoader loader; + protected @Owning AnnotationClassLoader loader; - @CreatesMustCallFor("this") - private void loadTypeAnnotationsFromQualDir() { - if (loader != null) { - loader.close(); + @CreatesMustCallFor("this") + private void loadTypeAnnotationsFromQualDir() { + if (loader != null) { + loader.close(); + } + loader = createAnnotationClassLoader(); } - loader = createAnnotationClassLoader(); - } - AnnotationClassLoader createAnnotationClassLoader() { - return null; - } + AnnotationClassLoader createAnnotationClassLoader() { + return null; + } - public void close() {} + public void close() {} } // :: error: (inconsistent.mustcall.subtype) @MustCall({}) class OwningMustCallNothing2 implements Closeable { - protected @Owning AnnotationClassLoader loader; + protected @Owning AnnotationClassLoader loader; - @CreatesMustCallFor("this") - private void loadTypeAnnotationsFromQualDir() { - if (loader != null) { - loader.close(); + @CreatesMustCallFor("this") + private void loadTypeAnnotationsFromQualDir() { + if (loader != null) { + loader.close(); + } + loader = createAnnotationClassLoader(); } - loader = createAnnotationClassLoader(); - } - AnnotationClassLoader createAnnotationClassLoader() { - return null; - } + AnnotationClassLoader createAnnotationClassLoader() { + return null; + } - public void close() {} + public void close() {} } @InheritableMustCall("close") @@ -62,5 +63,5 @@ class SubclassMustCallClose3 extends OwningMustCallNothing2 {} @InheritableMustCall({}) // Don't check whether AnnotationClassLoaders are closed. // :: error: (inconsistent.mustcall.subtype) class AnnotationClassLoader implements Closeable { - public void close() {} + public void close() {} } diff --git a/checker/tests/mustcall/OwningParams.java b/checker/tests/mustcall/OwningParams.java index 93eb9c71f4b..5430d509f3c 100644 --- a/checker/tests/mustcall/OwningParams.java +++ b/checker/tests/mustcall/OwningParams.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.mustcall.qual.*; class OwningParams { - static void o1(@Owning OwningParams o) {} + static void o1(@Owning OwningParams o) {} - void test(@Owning @MustCall({"a"}) OwningParams o, @Owning OwningParams p) { - // :: error: argument.type.incompatible - o1(o); - o1(p); - } + void test(@Owning @MustCall({"a"}) OwningParams o, @Owning OwningParams p) { + // :: error: argument.type.incompatible + o1(o); + o1(p); + } } diff --git a/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java b/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java index d1da8166c26..f5fdedb0d5d 100644 --- a/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java +++ b/checker/tests/mustcall/PlumeUtilRequiredAnnotations.java @@ -14,46 +14,47 @@ // and https://github.com/plume-lib/plume-util/pull/126 for more details, especially // on why changing the default isn't feasible. -import java.util.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.*; + class PlumeUtilRequiredAnnotations { - // In the real version of this code, there is only one type parameter. - // T is the unannotated version of the parameter - i.e., what it was before - // we first ran the Must Call Checker. S is the annotated version. Adding the - // annotation itself is immaterial - what's important is that the bound - // must be explicit rather than implicit (see that the eqR field never issue errors, - // just like the eqS fields). - class MultiRandSelector { - // a type.argument error used to be issued here. - private Partitioner eqT; - private Partitioner eqS; - private Partitioner eqR; - - // Adding annotations to the definition of Partitioner doesn't fix this problem: - // a type.argument error used to be issued here. - private Partitioner2 eqT2; - private Partitioner2 eqS2; - private Partitioner2 eqR2; - - // But removing the explicit bounds on Partitioner does (not feasible in this case, though, - // because of the @Nullable annotations): - private Partitioner3 eqT3; - private Partitioner3 eqS3; - private Partitioner3 eqR3; - } - - interface Partitioner { - CLASS assignToBucket(ELEMENT obj); - } - - interface Partitioner2< - ELEMENT extends @Nullable @MustCall Object, CLASS extends @Nullable @MustCall Object> { - CLASS assignToBucket(ELEMENT obj); - } - - interface Partitioner3 { - CLASS assignToBucket(ELEMENT obj); - } + // In the real version of this code, there is only one type parameter. + // T is the unannotated version of the parameter - i.e., what it was before + // we first ran the Must Call Checker. S is the annotated version. Adding the + // annotation itself is immaterial - what's important is that the bound + // must be explicit rather than implicit (see that the eqR field never issue errors, + // just like the eqS fields). + class MultiRandSelector { + // a type.argument error used to be issued here. + private Partitioner eqT; + private Partitioner eqS; + private Partitioner eqR; + + // Adding annotations to the definition of Partitioner doesn't fix this problem: + // a type.argument error used to be issued here. + private Partitioner2 eqT2; + private Partitioner2 eqS2; + private Partitioner2 eqR2; + + // But removing the explicit bounds on Partitioner does (not feasible in this case, though, + // because of the @Nullable annotations): + private Partitioner3 eqT3; + private Partitioner3 eqS3; + private Partitioner3 eqR3; + } + + interface Partitioner { + CLASS assignToBucket(ELEMENT obj); + } + + interface Partitioner2< + ELEMENT extends @Nullable @MustCall Object, CLASS extends @Nullable @MustCall Object> { + CLASS assignToBucket(ELEMENT obj); + } + + interface Partitioner3 { + CLASS assignToBucket(ELEMENT obj); + } } diff --git a/checker/tests/mustcall/PolyMustCallDifferentNames.java b/checker/tests/mustcall/PolyMustCallDifferentNames.java index edc6caa92ee..ca29351dd09 100644 --- a/checker/tests/mustcall/PolyMustCallDifferentNames.java +++ b/checker/tests/mustcall/PolyMustCallDifferentNames.java @@ -6,71 +6,71 @@ class PolyMustCallDifferentNames { - @InheritableMustCall("a") - static class Wrapped { - void a() {} - } + @InheritableMustCall("a") + static class Wrapped { + void a() {} + } + + @InheritableMustCall("b") + static class Wrapper1 { + private final @Owning Wrapped field; - @InheritableMustCall("b") - static class Wrapper1 { - private final @Owning Wrapped field; + public @PolyMustCall Wrapper1(@PolyMustCall Wrapped w) { + // we get this error since we only have a field-assignment special case for + // @MustCallAlias, not @PolyMustCall. + // :: error: (assignment.type.incompatible) + this.field = w; + } - public @PolyMustCall Wrapper1(@PolyMustCall Wrapped w) { - // we get this error since we only have a field-assignment special case for - // @MustCallAlias, not @PolyMustCall. - // :: error: (assignment.type.incompatible) - this.field = w; + @EnsuresCalledMethods( + value = {"this.field"}, + methods = {"a"}) + void b() { + this.field.a(); + } } - @EnsuresCalledMethods( - value = {"this.field"}, - methods = {"a"}) - void b() { - this.field.a(); + static @PolyMustCall Wrapper1 getWrapper1(@PolyMustCall Wrapped w) { + return new Wrapper1(w); } - } - static @PolyMustCall Wrapper1 getWrapper1(@PolyMustCall Wrapped w) { - return new Wrapper1(w); - } + @InheritableMustCall("c") + static class Wrapper2 { + private final @Owning Wrapped field; - @InheritableMustCall("c") - static class Wrapper2 { - private final @Owning Wrapped field; + public @MustCallAlias Wrapper2(@MustCallAlias Wrapped w) { + this.field = w; + } - public @MustCallAlias Wrapper2(@MustCallAlias Wrapped w) { - this.field = w; + @EnsuresCalledMethods( + value = {"this.field"}, + methods = {"a"}) + void c() { + this.field.a(); + } } - @EnsuresCalledMethods( - value = {"this.field"}, - methods = {"a"}) - void c() { - this.field.a(); + static @MustCallAlias Wrapper2 getWrapper2(@MustCallAlias Wrapped w) { + return new Wrapper2(w); } - } - - static @MustCallAlias Wrapper2 getWrapper2(@MustCallAlias Wrapped w) { - return new Wrapper2(w); - } - static void test1() { - @MustCall("a") Wrapped x = new Wrapped(); - @MustCall("b") Wrapper1 w1 = new Wrapper1(x); - @MustCall("b") Wrapper1 w2 = getWrapper1(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper1 w3 = new Wrapper1(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper1 w4 = getWrapper1(x); - } + static void test1() { + @MustCall("a") Wrapped x = new Wrapped(); + @MustCall("b") Wrapper1 w1 = new Wrapper1(x); + @MustCall("b") Wrapper1 w2 = getWrapper1(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper1 w3 = new Wrapper1(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper1 w4 = getWrapper1(x); + } - static void test2() { - @MustCall("a") Wrapped x = new Wrapped(); - @MustCall("c") Wrapper2 w1 = new Wrapper2(x); - @MustCall("c") Wrapper2 w2 = getWrapper2(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper2 w3 = new Wrapper2(x); - // :: error: (assignment.type.incompatible) - @MustCall("a") Wrapper2 w4 = getWrapper2(x); - } + static void test2() { + @MustCall("a") Wrapped x = new Wrapped(); + @MustCall("c") Wrapper2 w1 = new Wrapper2(x); + @MustCall("c") Wrapper2 w2 = getWrapper2(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper2 w3 = new Wrapper2(x); + // :: error: (assignment.type.incompatible) + @MustCall("a") Wrapper2 w4 = getWrapper2(x); + } } diff --git a/checker/tests/mustcall/PolyTests.java b/checker/tests/mustcall/PolyTests.java index e2ecc02358b..4b986363052 100644 --- a/checker/tests/mustcall/PolyTests.java +++ b/checker/tests/mustcall/PolyTests.java @@ -4,39 +4,39 @@ @InheritableMustCall("close") class PolyTests { - static @PolyMustCall Object id(@PolyMustCall Object obj) { - return obj; - } - - static void test1(@Owning @MustCall("close") Object o) { - @MustCall("close") Object o1 = id(o); - // :: error: assignment.type.incompatible - @MustCall({}) Object o2 = id(o); - } - - static void test2(@Owning @MustCall({}) Object o) { - @MustCall("close") Object o1 = id(o); - @MustCall({}) Object o2 = id(o); - } - - // These sort of constructors will always appear in stub files and are unverifiable for now. - @SuppressWarnings("mustcall:annotations.on.use") - @PolyMustCall PolyTests(@PolyMustCall Object obj) {} - - static void test3(@Owning @MustCall({"close"}) Object o) { - @MustCall("close") Object o1 = new PolyTests(o); - // :: error: assignment.type.incompatible - @MustCall({}) Object o2 = new PolyTests(o); - } - - static void test4(@Owning @MustCall({}) Object o) { - @MustCall("close") Object o1 = new PolyTests(o); - @MustCall({}) Object o2 = new PolyTests(o); - } - - static void testArbitary(@Owning PolyTests p) { - @MustCall("close") Object o1 = p; - // :: error: assignment.type.incompatible - @MustCall({}) Object o2 = p; - } + static @PolyMustCall Object id(@PolyMustCall Object obj) { + return obj; + } + + static void test1(@Owning @MustCall("close") Object o) { + @MustCall("close") Object o1 = id(o); + // :: error: assignment.type.incompatible + @MustCall({}) Object o2 = id(o); + } + + static void test2(@Owning @MustCall({}) Object o) { + @MustCall("close") Object o1 = id(o); + @MustCall({}) Object o2 = id(o); + } + + // These sort of constructors will always appear in stub files and are unverifiable for now. + @SuppressWarnings("mustcall:annotations.on.use") + @PolyMustCall PolyTests(@PolyMustCall Object obj) {} + + static void test3(@Owning @MustCall({"close"}) Object o) { + @MustCall("close") Object o1 = new PolyTests(o); + // :: error: assignment.type.incompatible + @MustCall({}) Object o2 = new PolyTests(o); + } + + static void test4(@Owning @MustCall({}) Object o) { + @MustCall("close") Object o1 = new PolyTests(o); + @MustCall({}) Object o2 = new PolyTests(o); + } + + static void testArbitary(@Owning PolyTests p) { + @MustCall("close") Object o1 = p; + // :: error: assignment.type.incompatible + @MustCall({}) Object o2 = p; + } } diff --git a/checker/tests/mustcall/SimpleException.java b/checker/tests/mustcall/SimpleException.java index b989d992920..d6cf38f2e51 100644 --- a/checker/tests/mustcall/SimpleException.java +++ b/checker/tests/mustcall/SimpleException.java @@ -1,15 +1,15 @@ // A test that throwing and catching exceptions doesn't cause false positives. class SimpleException { - void thrower() throws Exception { - throw new RuntimeException("some exception"); - } + void thrower() throws Exception { + throw new RuntimeException("some exception"); + } - void test() { - try { - thrower(); - } catch (Exception e) { - e.printStackTrace(); + void test() { + try { + thrower(); + } catch (Exception e) { + e.printStackTrace(); + } } - } } diff --git a/checker/tests/mustcall/SimpleSocketField.java b/checker/tests/mustcall/SimpleSocketField.java index 5536bc89234..a6a4e3c30ff 100644 --- a/checker/tests/mustcall/SimpleSocketField.java +++ b/checker/tests/mustcall/SimpleSocketField.java @@ -1,21 +1,22 @@ // a test that sockets in fields are considered @MustCall("close") -import java.net.Socket; import org.checkerframework.checker.mustcall.qual.MustCall; +import java.net.Socket; + class SimpleSocketField { - Socket mySock = new Socket(); + Socket mySock = new Socket(); - SimpleSocketField() throws Exception { - @MustCall("close") Socket s = mySock; - // This assignment is safe, because the only possible value of mySock here is the - // unconnected socket in the field initializer. - @MustCall({}) Socket s1 = mySock; - } + SimpleSocketField() throws Exception { + @MustCall("close") Socket s = mySock; + // This assignment is safe, because the only possible value of mySock here is the + // unconnected socket in the field initializer. + @MustCall({}) Socket s1 = mySock; + } - void test() { - @MustCall("close") Socket s = mySock; - // :: error: assignment.type.incompatible - @MustCall({}) Socket s1 = mySock; - } + void test() { + @MustCall("close") Socket s = mySock; + // :: error: assignment.type.incompatible + @MustCall({}) Socket s1 = mySock; + } } diff --git a/checker/tests/mustcall/SimpleStreamExample.java b/checker/tests/mustcall/SimpleStreamExample.java index 510978bb955..8bb103f64f5 100644 --- a/checker/tests/mustcall/SimpleStreamExample.java +++ b/checker/tests/mustcall/SimpleStreamExample.java @@ -3,7 +3,7 @@ import java.util.*; class SimpleStreamExample { - static void test(List s) { - s.stream().filter(str -> str == null); - } + static void test(List s) { + s.stream().filter(str -> str == null); + } } diff --git a/checker/tests/mustcall/SocketBufferedReader.java b/checker/tests/mustcall/SocketBufferedReader.java index 2650d004edc..40c9fdee0ea 100644 --- a/checker/tests/mustcall/SocketBufferedReader.java +++ b/checker/tests/mustcall/SocketBufferedReader.java @@ -1,23 +1,24 @@ // a test for missing mustcall propagation that might have caused a false positive? +import org.checkerframework.checker.mustcall.qual.*; + import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.*; -import org.checkerframework.checker.mustcall.qual.*; class SocketBufferedReader { - void test(String address, int port) { - try { - Socket socket = new Socket(address, 80); - PrintStream out = new PrintStream(socket.getOutputStream()); - BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - @MustCall("close") BufferedReader reader = in; - // :: error: assignment.type.incompatible - @MustCall({}) BufferedReader reader2 = in; - in.close(); - } catch (Exception e) { - e.printStackTrace(); + void test(String address, int port) { + try { + Socket socket = new Socket(address, 80); + PrintStream out = new PrintStream(socket.getOutputStream()); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + @MustCall("close") BufferedReader reader = in; + // :: error: assignment.type.incompatible + @MustCall({}) BufferedReader reader2 = in; + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } } - } } diff --git a/checker/tests/mustcall/StreamBool.java b/checker/tests/mustcall/StreamBool.java index f6c2b0fe71e..6826763d9c6 100644 --- a/checker/tests/mustcall/StreamBool.java +++ b/checker/tests/mustcall/StreamBool.java @@ -3,9 +3,9 @@ import java.io.InputStream; class StreamBool { - InputStream stream; + InputStream stream; - boolean isActive() { - return stream != null; - } + boolean isActive() { + return stream != null; + } } diff --git a/checker/tests/mustcall/StringSort.java b/checker/tests/mustcall/StringSort.java index c76a0977a71..bfb10cfd0e7 100644 --- a/checker/tests/mustcall/StringSort.java +++ b/checker/tests/mustcall/StringSort.java @@ -3,8 +3,8 @@ import java.util.*; class StringSort { - public static void sort() { - List myList = new ArrayList(); - Collections.sort(myList); - } + public static void sort() { + List myList = new ArrayList(); + Collections.sort(myList); + } } diff --git a/checker/tests/mustcall/Subtype0.java b/checker/tests/mustcall/Subtype0.java index 3cc8ae8d214..cfd9950a337 100644 --- a/checker/tests/mustcall/Subtype0.java +++ b/checker/tests/mustcall/Subtype0.java @@ -5,57 +5,57 @@ @InheritableMustCall("a") public class Subtype0 { - public class Subtype1 extends Subtype0 { - void m1() {} - } + public class Subtype1 extends Subtype0 { + void m1() {} + } - public class Subtype2 extends Subtype1 {} + public class Subtype2 extends Subtype1 {} - static void test( - @Owning Subtype0 s0, - @Owning Subtype1 s1, - @Owning Subtype2 s2, - @Owning Subtype3 s3, - @Owning Subtype4 s4) { - // :: error: assignment.type.incompatible - @MustCall({}) Object obj1 = s0; - @MustCall({"a"}) Object obj2 = s0; + static void test( + @Owning Subtype0 s0, + @Owning Subtype1 s1, + @Owning Subtype2 s2, + @Owning Subtype3 s3, + @Owning Subtype4 s4) { + // :: error: assignment.type.incompatible + @MustCall({}) Object obj1 = s0; + @MustCall({"a"}) Object obj2 = s0; - // :: error: assignment.type.incompatible - @MustCall({}) Object obj3 = s1; - @MustCall({"a"}) Object obj4 = s1; + // :: error: assignment.type.incompatible + @MustCall({}) Object obj3 = s1; + @MustCall({"a"}) Object obj4 = s1; - // :: error: assignment.type.incompatible - @MustCall({}) Object obj5 = s2; - @MustCall({"a"}) Object obj6 = s2; + // :: error: assignment.type.incompatible + @MustCall({}) Object obj5 = s2; + @MustCall({"a"}) Object obj6 = s2; - @MustCall({}) Object obj7 = s3; - @MustCall({"a"}) Object obj8 = s3; + @MustCall({}) Object obj7 = s3; + @MustCall({"a"}) Object obj8 = s3; - @MustCall({}) Object obj9 = s4; - @MustCall({"a"}) Object obj10 = s4; - } + @MustCall({}) Object obj9 = s4; + @MustCall({"a"}) Object obj10 = s4; + } - @MustCall({}) - // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid - public class Subtype3 extends Subtype0 {} + @MustCall({}) + // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid + public class Subtype3 extends Subtype0 {} - @InheritableMustCall({}) - // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid - public class Subtype4 extends Subtype0 {} + @InheritableMustCall({}) + // :: error: inconsistent.mustcall.subtype :: error: super.invocation.invalid + public class Subtype4 extends Subtype0 {} - @MustCall({"a"}) public class Subtype5 extends Subtype0 {} + @MustCall({"a"}) public class Subtype5 extends Subtype0 {} - @InheritableMustCall({"a"}) - public class Subtype6 extends Subtype0 {} + @InheritableMustCall({"a"}) + public class Subtype6 extends Subtype0 {} - public class Container { - Subtype0 in; + public class Container { + Subtype0 in; - void test() { - if (in instanceof Subtype1) { - ((Subtype1) in).m1(); - } + void test() { + if (in instanceof Subtype1) { + ((Subtype1) in).m1(); + } + } } - } } diff --git a/checker/tests/mustcall/Subtyping.java b/checker/tests/mustcall/Subtyping.java index 8f1c8ecf0e2..df785f446e0 100644 --- a/checker/tests/mustcall/Subtyping.java +++ b/checker/tests/mustcall/Subtyping.java @@ -4,54 +4,54 @@ class Subtyping { - Object unannotatedObj; - - void test_act(@Owning @MustCallUnknown Object o) { - @MustCallUnknown Object act = o; - // :: error: assignment.type.incompatible - @MustCall("close") Object file = o; - // :: error: assignment.type.incompatible - @MustCall({"close", "read"}) Object f2 = o; - // :: error: assignment.type.incompatible - @MustCall({}) Object notAfile = o; - // :: error: assignment.type.incompatible - unannotatedObj = o; - } - - void test_close(@Owning @MustCall("close") Object o) { - @MustCallUnknown Object act = o; - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - // :: error: assignment.type.incompatible - @MustCall({}) Object notAfile = o; - // :: error: assignment.type.incompatible - unannotatedObj = o; - } - - void test_close_read(@Owning @MustCall({"close", "read"}) Object o) { - @MustCallUnknown Object act = o; - // :: error: assignment.type.incompatible - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - // :: error: assignment.type.incompatible - @MustCall({}) Object notAfile = o; - // :: error: assignment.type.incompatible - unannotatedObj = o; - } - - void test_blank(@Owning @MustCall({}) Object o) { - @MustCallUnknown Object act = o; - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - @MustCall({}) Object notAfile = o; - unannotatedObj = o; - } - - void test_unannotated(@Owning Object o) { - @MustCallUnknown Object act = o; - @MustCall("close") Object file = o; - @MustCall({"close", "read"}) Object f2 = o; - @MustCall({}) Object notAfile = o; - unannotatedObj = o; - } + Object unannotatedObj; + + void test_act(@Owning @MustCallUnknown Object o) { + @MustCallUnknown Object act = o; + // :: error: assignment.type.incompatible + @MustCall("close") Object file = o; + // :: error: assignment.type.incompatible + @MustCall({"close", "read"}) Object f2 = o; + // :: error: assignment.type.incompatible + @MustCall({}) Object notAfile = o; + // :: error: assignment.type.incompatible + unannotatedObj = o; + } + + void test_close(@Owning @MustCall("close") Object o) { + @MustCallUnknown Object act = o; + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + // :: error: assignment.type.incompatible + @MustCall({}) Object notAfile = o; + // :: error: assignment.type.incompatible + unannotatedObj = o; + } + + void test_close_read(@Owning @MustCall({"close", "read"}) Object o) { + @MustCallUnknown Object act = o; + // :: error: assignment.type.incompatible + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + // :: error: assignment.type.incompatible + @MustCall({}) Object notAfile = o; + // :: error: assignment.type.incompatible + unannotatedObj = o; + } + + void test_blank(@Owning @MustCall({}) Object o) { + @MustCallUnknown Object act = o; + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + @MustCall({}) Object notAfile = o; + unannotatedObj = o; + } + + void test_unannotated(@Owning Object o) { + @MustCallUnknown Object act = o; + @MustCall("close") Object file = o; + @MustCall({"close", "read"}) Object f2 = o; + @MustCall({}) Object notAfile = o; + unannotatedObj = o; + } } diff --git a/checker/tests/mustcall/SystemInOut.java b/checker/tests/mustcall/SystemInOut.java index 944974f779c..145c86a56e2 100644 --- a/checker/tests/mustcall/SystemInOut.java +++ b/checker/tests/mustcall/SystemInOut.java @@ -1,14 +1,15 @@ // A test that the checker doesn't ask you to close System.in, System.out, or System.err. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.util.Scanner; -import org.checkerframework.checker.mustcall.qual.*; class SystemInOut { - void test() { - @MustCall({}) InputStream in = System.in; - @MustCall({}) OutputStream out = System.out; - @MustCall({}) OutputStream err = System.err; - @MustCall({}) Scanner sysIn = new Scanner(System.in); - } + void test() { + @MustCall({}) InputStream in = System.in; + @MustCall({}) OutputStream out = System.out; + @MustCall({}) OutputStream err = System.err; + @MustCall({}) Scanner sysIn = new Scanner(System.in); + } } diff --git a/checker/tests/mustcall/ToStringOnSocket.java b/checker/tests/mustcall/ToStringOnSocket.java index 02d80d8e9f5..3a93f9becc0 100644 --- a/checker/tests/mustcall/ToStringOnSocket.java +++ b/checker/tests/mustcall/ToStringOnSocket.java @@ -4,15 +4,15 @@ import java.net.Socket; class ToStringOnSocket { - void log(String string) { - System.out.println(string); - } + void log(String string) { + System.out.println(string); + } - void test(Socket socket) { - log("bad socket: " + socket); - } + void test(Socket socket) { + log("bad socket: " + socket); + } - void test2(Socket socket) { - log("bad socket: " + socket.toString()); - } + void test2(Socket socket) { + log("bad socket: " + socket.toString()); + } } diff --git a/checker/tests/mustcall/TryWithResourcesCrash.java b/checker/tests/mustcall/TryWithResourcesCrash.java index 8990b8d14bd..f9a52f43477 100644 --- a/checker/tests/mustcall/TryWithResourcesCrash.java +++ b/checker/tests/mustcall/TryWithResourcesCrash.java @@ -6,26 +6,26 @@ import java.io.OutputStream; class TryWithResourcesCrash { - void test(FileSystem fs, byte[] bytes, String path) throws IOException { - try (FSDataOutputStream out = fs.createFile(path).overwrite(true).build()) { - out.write(bytes); + void test(FileSystem fs, byte[] bytes, String path) throws IOException { + try (FSDataOutputStream out = fs.createFile(path).overwrite(true).build()) { + out.write(bytes); + } } - } - class FSDataOutputStream extends DataOutputStream { - FSDataOutputStream(OutputStream os) { - super(os); + class FSDataOutputStream extends DataOutputStream { + FSDataOutputStream(OutputStream os) { + super(os); + } } - } - abstract class FSDataOutputStreamBuilder< - S extends FSDataOutputStream, B extends FSDataOutputStreamBuilder> { - abstract S build(); + abstract class FSDataOutputStreamBuilder< + S extends FSDataOutputStream, B extends FSDataOutputStreamBuilder> { + abstract S build(); - abstract B overwrite(boolean b); - } + abstract B overwrite(boolean b); + } - abstract class FileSystem implements Closeable { - abstract FSDataOutputStreamBuilder createFile(String s); - } + abstract class FileSystem implements Closeable { + abstract FSDataOutputStreamBuilder createFile(String s); + } } diff --git a/checker/tests/mustcall/TryWithResourcesSimple.java b/checker/tests/mustcall/TryWithResourcesSimple.java index c1fea21640b..d71be20c953 100644 --- a/checker/tests/mustcall/TryWithResourcesSimple.java +++ b/checker/tests/mustcall/TryWithResourcesSimple.java @@ -1,51 +1,52 @@ // A test that try-with-resources variables are always @MustCall({"close"}). +import org.checkerframework.checker.mustcall.qual.MustCall; + import java.io.*; import java.net.*; -import org.checkerframework.checker.mustcall.qual.MustCall; public class TryWithResourcesSimple { - static void test(String address, int port) { - try (Socket socket = new Socket(address, port)) { - @MustCall({"close"}) Object s = socket; - } catch (Exception e) { + static void test(String address, int port) { + try (Socket socket = new Socket(address, port)) { + @MustCall({"close"}) Object s = socket; + } catch (Exception e) { + } } - } - - @SuppressWarnings("mustcall:annotations.on.use") - public static @MustCall({"close", "myMethod"}) Socket getFancySocket() { - return null; - } - - void test_fancy_sock(String address, int port) { - // This is illegal, because getFancySock()'s return type has another MC method beyond - // "close", - // which is the only MC method for Socket itself. - try (Socket socket = getFancySocket()) { - // :: error: (assignment.type.incompatible) - @MustCall({"close"}) Object s = socket; - } catch (Exception e) { + @SuppressWarnings("mustcall:annotations.on.use") + public static @MustCall({"close", "myMethod"}) Socket getFancySocket() { + return null; + } + + void test_fancy_sock(String address, int port) { + // This is illegal, because getFancySock()'s return type has another MC method beyond + // "close", + // which is the only MC method for Socket itself. + try (Socket socket = getFancySocket()) { + // :: error: (assignment.type.incompatible) + @MustCall({"close"}) Object s = socket; + } catch (Exception e) { + + } } - } - static void test_poly(String address, int port) { - try (Socket socket = new Socket(address, port)) { - // getChannel is @MustCallAlias (= poly) with the socket, so it should also be - // @MC({"close"}) - @MustCall({"close"}) Object s = socket.getChannel(); - } catch (Exception e) { + static void test_poly(String address, int port) { + try (Socket socket = new Socket(address, port)) { + // getChannel is @MustCallAlias (= poly) with the socket, so it should also be + // @MC({"close"}) + @MustCall({"close"}) Object s = socket.getChannel(); + } catch (Exception e) { + } } - } - static void test_two_mca_variables(String address, int port) { - try (Socket socket = new Socket(address, port); - InputStream in = socket.getInputStream()) { - @MustCall({"close"}) Object s = in; - } catch (Exception e) { + static void test_two_mca_variables(String address, int port) { + try (Socket socket = new Socket(address, port); + InputStream in = socket.getInputStream()) { + @MustCall({"close"}) Object s = in; + } catch (Exception e) { + } } - } } diff --git a/checker/tests/mustcall/TypeArgs.java b/checker/tests/mustcall/TypeArgs.java index cb6181fa42d..180001e919e 100644 --- a/checker/tests/mustcall/TypeArgs.java +++ b/checker/tests/mustcall/TypeArgs.java @@ -2,89 +2,90 @@ public class TypeArgs { - static class A {} - - static class B extends A {} - - public void f1(Generic real, Generic other, boolean flag) { - f2(flag ? real : other); - } - - <@MustCall({"carly"}) Q extends @MustCall({"carly"}) Object> void f2(Generic parm) {} - - interface Generic {} - - void m3( - @MustCall({}) Object a, - @MustCall({"foo"}) Object b, - @MustCall({"bar"}) Object c, - @MustCall({"foo", "bar"}) Object d) { - requireNothing1(a); - requireNothing2(a); - requireNothing1(b); - requireNothing2(b); - requireNothing1(c); - requireNothing2(c); - requireNothing1(d); - requireNothing2(d); - - requireFoo1(a); - requireFoo2(a); - requireFoo1(b); - requireFoo2(b); - requireFoo1(c); - requireFoo2(c); - requireFoo1(d); - requireFoo2(d); - - requireBar1(a); - requireBar2(a); - requireBar1(b); - requireBar2(b); - requireBar1(c); - requireBar2(c); - requireBar1(d); - requireBar2(d); - - requireFooBar1(a); - requireFooBar2(a); - requireFooBar1(b); - requireFooBar2(b); - requireFooBar1(c); - requireFooBar2(c); - requireFooBar1(d); - requireFooBar2(d); - } - - public static T requireNothing1(T obj) { - return obj; - } - - public static @MustCall({}) T requireNothing2(@MustCall({}) T obj) { - return obj; - } - - public static T requireFoo1(T obj) { - return obj; - } - - public static @MustCall({"foo"}) T requireFoo2(@MustCall({"foo"}) T obj) { - return obj; - } - - public static T requireBar1(T obj) { - return obj; - } - - public static @MustCall({"bar"}) T requireBar2(@MustCall({"bar"}) T obj) { - return obj; - } - - public static T requireFooBar1(T obj) { - return obj; - } - - public static @MustCall({"foo", "bar"}) T requireFooBar2(@MustCall({"foo", "bar"}) T obj) { - return obj; - } + static class A {} + + static class B extends A {} + + public void f1(Generic real, Generic other, boolean flag) { + f2(flag ? real : other); + } + + <@MustCall({"carly"}) Q extends @MustCall({"carly"}) Object> void f2( + Generic parm) {} + + interface Generic {} + + void m3( + @MustCall({}) Object a, + @MustCall({"foo"}) Object b, + @MustCall({"bar"}) Object c, + @MustCall({"foo", "bar"}) Object d) { + requireNothing1(a); + requireNothing2(a); + requireNothing1(b); + requireNothing2(b); + requireNothing1(c); + requireNothing2(c); + requireNothing1(d); + requireNothing2(d); + + requireFoo1(a); + requireFoo2(a); + requireFoo1(b); + requireFoo2(b); + requireFoo1(c); + requireFoo2(c); + requireFoo1(d); + requireFoo2(d); + + requireBar1(a); + requireBar2(a); + requireBar1(b); + requireBar2(b); + requireBar1(c); + requireBar2(c); + requireBar1(d); + requireBar2(d); + + requireFooBar1(a); + requireFooBar2(a); + requireFooBar1(b); + requireFooBar2(b); + requireFooBar1(c); + requireFooBar2(c); + requireFooBar1(d); + requireFooBar2(d); + } + + public static T requireNothing1(T obj) { + return obj; + } + + public static @MustCall({}) T requireNothing2(@MustCall({}) T obj) { + return obj; + } + + public static T requireFoo1(T obj) { + return obj; + } + + public static @MustCall({"foo"}) T requireFoo2(@MustCall({"foo"}) T obj) { + return obj; + } + + public static T requireBar1(T obj) { + return obj; + } + + public static @MustCall({"bar"}) T requireBar2(@MustCall({"bar"}) T obj) { + return obj; + } + + public static T requireFooBar1(T obj) { + return obj; + } + + public static @MustCall({"foo", "bar"}) T requireFooBar2(@MustCall({"foo", "bar"}) T obj) { + return obj; + } } diff --git a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java index a84df311681..b0c4472ec50 100644 --- a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java +++ b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java @@ -5,72 +5,72 @@ public class AnnotatedForNullness { - @Initialized @NonNull Object initializedField = new Object(); - @Initialized @KeyForBottom @NonNull Object initializedKeyForBottomField = new Object(); + @Initialized @NonNull Object initializedField = new Object(); + @Initialized @KeyForBottom @NonNull Object initializedKeyForBottomField = new Object(); - @AnnotatedFor("initialization") - // No errors because AnnotatedFor("initialization") does not change the default for nullness. - Object annotatedForInitialization(Object test) { - return null; - } + @AnnotatedFor("initialization") + // No errors because AnnotatedFor("initialization") does not change the default for nullness. + Object annotatedForInitialization(Object test) { + return null; + } - @AnnotatedFor("nullness") - Object annotatedForNullness(Object test) { - // ::error: (return.type.incompatible) - return null; - } + @AnnotatedFor("nullness") + Object annotatedForNullness(Object test) { + // ::error: (return.type.incompatible) + return null; + } - // Method annotatedFor with both `nullness` and `initialization` should behave the same as - // annotatedForNullness. - @AnnotatedFor({"nullness", "initialization"}) - Object annotatedForNullnessAndInitialization(Object test) { - // ::error: (return.type.incompatible) - return null; - } + // Method annotatedFor with both `nullness` and `initialization` should behave the same as + // annotatedForNullness. + @AnnotatedFor({"nullness", "initialization"}) + Object annotatedForNullnessAndInitialization(Object test) { + // ::error: (return.type.incompatible) + return null; + } - Object unannotatedFor(Object test) { - return null; - } + Object unannotatedFor(Object test) { + return null; + } - @AnnotatedFor("nullness") - void foo(@Initialized AnnotatedForNullness this) { - // Expect two [argument.type.incompatible] errors in KeyForChecker and InitilizationChecker - // because conservative defaults are applied to `unannotatedFor` and it expects a @FBCBottom - // @KeyForBottom @Nonull Object. - // ::error: (argument.type.incompatible) - unannotatedFor(initializedField); - // Expect an error in KeyForChecker because conservative defaults are applied to - // `annotatedForInitialization` for hierarchies other than the Initialization Checker and - // it expects an @Initialized @KeyForBottom @Nonull Object. - // ::error: (argument.type.incompatible) - annotatedForInitialization(initializedField); - // Do not expect an error when conservative defaults are applied to - // `annotatedForInitialization` for hierarchies other than the Initialization Checker and - // it expects an @Initialized @KeyForBottom @Nonull Object. - annotatedForInitialization(initializedKeyForBottomField); - // Do not expect an error because these are AnnotatedFor("nullness") and these expect - // @Initialized @UnknownKeyFor @Nonnull Object. - annotatedForNullness(initializedField); - annotatedForNullnessAndInitialization(initializedField); - } + @AnnotatedFor("nullness") + void foo(@Initialized AnnotatedForNullness this) { + // Expect two [argument.type.incompatible] errors in KeyForChecker and InitilizationChecker + // because conservative defaults are applied to `unannotatedFor` and it expects a @FBCBottom + // @KeyForBottom @Nonull Object. + // ::error: (argument.type.incompatible) + unannotatedFor(initializedField); + // Expect an error in KeyForChecker because conservative defaults are applied to + // `annotatedForInitialization` for hierarchies other than the Initialization Checker and + // it expects an @Initialized @KeyForBottom @Nonull Object. + // ::error: (argument.type.incompatible) + annotatedForInitialization(initializedField); + // Do not expect an error when conservative defaults are applied to + // `annotatedForInitialization` for hierarchies other than the Initialization Checker and + // it expects an @Initialized @KeyForBottom @Nonull Object. + annotatedForInitialization(initializedKeyForBottomField); + // Do not expect an error because these are AnnotatedFor("nullness") and these expect + // @Initialized @UnknownKeyFor @Nonnull Object. + annotatedForNullness(initializedField); + annotatedForNullnessAndInitialization(initializedField); + } - @AnnotatedFor("initialization") - void bar() { - // Expect an error in InitilizationChecker because conservative defaults are applied to - // `unannotatedFor` and it expects a @FBCBottom @UnknownKeyFor @Nonull Object. - // ::error: (argument.type.incompatible) - unannotatedFor(initializedField); - // Do not expect an error because the warning is suppressed other than initialzation - // hierarchy when conservative defaults are applied to source code and it expects an - // @Initialized @KeyForBottom @Nonull Object. - annotatedForInitialization(initializedField); - // Do not expect an error when conservative defaults are applied to - // `annotatedForInitialization` for hierarchies other than the Initialization Checker and - // it expects an @Initialized @KeyForBottom @Nonull Object. - annotatedForInitialization(initializedKeyForBottomField); - // Do not expect an error because these are AnnotatedFor("nullness") and these expect - // @Initialized @UnknownKeyFor @Nonnull Object. - annotatedForNullness(initializedField); - annotatedForNullnessAndInitialization(initializedField); - } + @AnnotatedFor("initialization") + void bar() { + // Expect an error in InitilizationChecker because conservative defaults are applied to + // `unannotatedFor` and it expects a @FBCBottom @UnknownKeyFor @Nonull Object. + // ::error: (argument.type.incompatible) + unannotatedFor(initializedField); + // Do not expect an error because the warning is suppressed other than initialzation + // hierarchy when conservative defaults are applied to source code and it expects an + // @Initialized @KeyForBottom @Nonull Object. + annotatedForInitialization(initializedField); + // Do not expect an error when conservative defaults are applied to + // `annotatedForInitialization` for hierarchies other than the Initialization Checker and + // it expects an @Initialized @KeyForBottom @Nonull Object. + annotatedForInitialization(initializedKeyForBottomField); + // Do not expect an error because these are AnnotatedFor("nullness") and these expect + // @Initialized @UnknownKeyFor @Nonnull Object. + annotatedForNullness(initializedField); + annotatedForNullnessAndInitialization(initializedField); + } } diff --git a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java index aa44be38fc9..01c4054ae2e 100644 --- a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java +++ b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/annotated/Test.java @@ -3,8 +3,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Test { - void foo(@Nullable Object o) { - // :: error: (dereference.of.nullable) - o.toString(); - } + void foo(@Nullable Object o) { + // :: error: (dereference.of.nullable) + o.toString(); + } } diff --git a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java index 536fb635c08..7b949d49943 100644 --- a/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java +++ b/checker/tests/nulless-conservative-defaults/packageannotatedfornullness/notannotated/Test.java @@ -3,8 +3,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Test { - void foo(@Nullable Object o) { - // No error because this package is not annotated for nullness. - o.toString(); - } + void foo(@Nullable Object o) { + // No error because this package is not annotated for nullness. + o.toString(); + } } diff --git a/checker/tests/nullness-asserts/NonNullMapValue.java b/checker/tests/nullness-asserts/NonNullMapValue.java index a24db9c18e3..aa86bc5b8ce 100644 --- a/checker/tests/nullness-asserts/NonNullMapValue.java +++ b/checker/tests/nullness-asserts/NonNullMapValue.java @@ -1,3 +1,8 @@ +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.KeyFor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.io.PrintStream; import java.util.Date; import java.util.HashMap; @@ -6,206 +11,202 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.KeyFor; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; public class NonNullMapValue { - // Discussion: - // - // It can be useful to indicate that all the values in a map are non-null. - // ("@NonNull" is redundant in this declaration, but I've written it - // explicitly because it is the annotation we are talking about.) - // HashMap myMap; - // - // However, the get method's declaration is misleading (in the context of the nullness type - // system), since it can always return null no matter whether the map values are non-null: - // V get(Object key) { ... return null; } - // The Nullness Checker does not use the signature as written. It has hard-coded rules for the - // get() method. It checks that the passed key to has type @KeyFor("theMap"). - - Map myMap; - - NonNullMapValue(Map myMap) { - this.myMap = myMap; - } - - void testMyMap(String key) { - @NonNull String value; - // :: error: (assignment.type.incompatible) - value = myMap.get(key); // should issue warning - if (myMap.containsKey(key)) { - value = myMap.get(key); - } - for (String keyInMap : myMap.keySet()) { - // :: error: (assignment.type.incompatible) - value = myMap.get(key); // should issue warning - } - for (String keyInMap : myMap.keySet()) { - value = myMap.get(keyInMap); - } - for (Map.Entry<@KeyFor("myMap") String, @NonNull String> entry : myMap.entrySet()) { - String keyInMap = entry.getKey(); - value = entry.getValue(); - } - for (Iterator<@KeyFor("myMap") String> iter = myMap.keySet().iterator(); iter.hasNext(); ) { - String keyInMap = iter.next(); - // value = myMap.get(keyInMap); - } - value = myMap.containsKey(key) ? myMap.get(key) : "hello"; - } - - public static void print( - Map> graph, PrintStream ps, int indent) { - for (T node : graph.keySet()) { - for (T child : graph.get(node)) { - ps.printf(" %s%n", child); - } - @NonNull List children = graph.get(node); - for (T child : children) { - ps.printf(" %s%n", child); - } - } - } - - public static void testAssertFlow(Map> preds, T node) { - assert preds.containsKey(node); - for (T pred : preds.get(node)) {} - } - - public static void testContainsKey1(Map> dom, T pred) { - assert dom.containsKey(pred); - // Both of the next two lines should type-check. The second one won't - // unless the checker knows that pred is a key in the map. - List dom_of_pred1 = dom.get(pred); - @NonNull List dom_of_pred2 = dom.get(pred); - } - - public static void testContainsKey2(Map> dom, T pred) { - if (!dom.containsKey(pred)) { - throw new Error(); - } - // Both of the next two lines should type-check. The second one won't - // unless the checker knows that pred is a key in the map. - List dom_of_pred1 = dom.get(pred); - @NonNull List dom_of_pred2 = dom.get(pred); - } - - public static void process_unmatched_procedure_entries() { - HashMap call_hashmap = new HashMap<>(); - for (Integer i : call_hashmap.keySet()) { - @NonNull Date d = call_hashmap.get(i); - } - Set<@KeyFor("call_hashmap") Integer> keys = call_hashmap.keySet(); - for (Integer i : keys) { - @NonNull Date d = call_hashmap.get(i); - } - Set<@KeyFor("call_hashmap") Integer> keys_sorted = - new TreeSet<@KeyFor("call_hashmap") Integer>(call_hashmap.keySet()); - for (Integer i : keys_sorted) { - @NonNull Date d = call_hashmap.get(i); - } - } - - public static Object testPut(Map map, Object key) { - if (!map.containsKey(key)) { - map.put(key, new Object()); - } - return map.get(key); - } - - public static Object testAssertGet(Map map, Object key) { - assert map.get(key) != null; - return map.get(key); - } - - public static Object testThrow(Map map, Object key) { - if (!map.containsKey(key)) { - if (true) { - return "m"; - } else { - throw new RuntimeException(); - } - } - return map.get(key); - } - - public void negateMap(Map map, Object key) { - if (!map.containsKey(key)) { - } else { - @NonNull Object v = map.get(key); - } - } - - public void withinElseInvalid(Map map, Object key) { - if (map.containsKey(key)) { - } else { - // :: error: (assignment.type.incompatible) - @NonNull Object v = map.get(key); // should issue warning - } - } - - // Map.get should be annotated as @org.checkerframework.dataflow.qual.Pure - public static int mapGetSize(MyMap> covered, Object file) { - return (covered.get(file) == null) ? 0 : covered.get(file).size(); - } - - interface MyMap extends Map { - // TODO: @AssertGenericNullnessIfTrue("get(#1)") - @org.checkerframework.dataflow.qual.Pure - public abstract boolean containsKey(@Nullable Object a1); - - // We get an override warning, because we do not use the annotated JDK in the - // test suite. Ignore this. - @SuppressWarnings("override.return.invalid") - @org.checkerframework.dataflow.qual.Pure - public @Nullable V get(@Nullable Object o); - } - - private static final String KEY = "key"; - private static final String KEY2 = "key2"; - - void testAnd(MyMap map, MyMap map2) { - if (map.containsKey(KEY)) { - map.get(KEY).toString(); - } - // :: warning: (nulltest.redundant) - if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) {} - // :: error: (dereference.of.nullable) :: warning: (nulltest.redundant) - if (map2.containsKey(KEY2) && map2.get(KEY2).toString() != null) {} - } - - void testAndWithIllegalMapAnnotation(MyMap2 map) { - if (map.containsKey(KEY)) { - map.get(KEY).toString(); - } - // :: warning: (nulltest.redundant) - if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { - // do nothing - } - } - - interface MyMap2 { - @org.checkerframework.dataflow.qual.Pure - // This annotation is not legal on containsKey in general. If the Map is declared as (say) - // Map, then get returns a nullable value. We really want to say - // that if containsKey returns non-null, then get returns V rather than @Nullable V, but I - // don't know how to say that. - @EnsuresNonNullIf(result = true, expression = "get(#1)") - public abstract boolean containsKey(@Nullable Object a1); - - @org.checkerframework.dataflow.qual.Pure - public abstract @Nullable V get(@Nullable Object a1); - } - - interface MyMap3 { - @org.checkerframework.dataflow.qual.Pure - @EnsuresNonNullIf(result = true, expression = "get(#1)") - // The following error is issued because, unlike in interface MyMap2, - // this interface has no get() method. - // :: error: (flowexpr.parse.error) - boolean containsKey(@Nullable Object a1); - } + // Discussion: + // + // It can be useful to indicate that all the values in a map are non-null. + // ("@NonNull" is redundant in this declaration, but I've written it + // explicitly because it is the annotation we are talking about.) + // HashMap myMap; + // + // However, the get method's declaration is misleading (in the context of the nullness type + // system), since it can always return null no matter whether the map values are non-null: + // V get(Object key) { ... return null; } + // The Nullness Checker does not use the signature as written. It has hard-coded rules for the + // get() method. It checks that the passed key to has type @KeyFor("theMap"). + + Map myMap; + + NonNullMapValue(Map myMap) { + this.myMap = myMap; + } + + void testMyMap(String key) { + @NonNull String value; + // :: error: (assignment.type.incompatible) + value = myMap.get(key); // should issue warning + if (myMap.containsKey(key)) { + value = myMap.get(key); + } + for (String keyInMap : myMap.keySet()) { + // :: error: (assignment.type.incompatible) + value = myMap.get(key); // should issue warning + } + for (String keyInMap : myMap.keySet()) { + value = myMap.get(keyInMap); + } + for (Map.Entry<@KeyFor("myMap") String, @NonNull String> entry : myMap.entrySet()) { + String keyInMap = entry.getKey(); + value = entry.getValue(); + } + for (Iterator<@KeyFor("myMap") String> iter = myMap.keySet().iterator(); iter.hasNext(); ) { + String keyInMap = iter.next(); + // value = myMap.get(keyInMap); + } + value = myMap.containsKey(key) ? myMap.get(key) : "hello"; + } + + public static void print( + Map> graph, PrintStream ps, int indent) { + for (T node : graph.keySet()) { + for (T child : graph.get(node)) { + ps.printf(" %s%n", child); + } + @NonNull List children = graph.get(node); + for (T child : children) { + ps.printf(" %s%n", child); + } + } + } + + public static void testAssertFlow(Map> preds, T node) { + assert preds.containsKey(node); + for (T pred : preds.get(node)) {} + } + + public static void testContainsKey1(Map> dom, T pred) { + assert dom.containsKey(pred); + // Both of the next two lines should type-check. The second one won't + // unless the checker knows that pred is a key in the map. + List dom_of_pred1 = dom.get(pred); + @NonNull List dom_of_pred2 = dom.get(pred); + } + + public static void testContainsKey2(Map> dom, T pred) { + if (!dom.containsKey(pred)) { + throw new Error(); + } + // Both of the next two lines should type-check. The second one won't + // unless the checker knows that pred is a key in the map. + List dom_of_pred1 = dom.get(pred); + @NonNull List dom_of_pred2 = dom.get(pred); + } + + public static void process_unmatched_procedure_entries() { + HashMap call_hashmap = new HashMap<>(); + for (Integer i : call_hashmap.keySet()) { + @NonNull Date d = call_hashmap.get(i); + } + Set<@KeyFor("call_hashmap") Integer> keys = call_hashmap.keySet(); + for (Integer i : keys) { + @NonNull Date d = call_hashmap.get(i); + } + Set<@KeyFor("call_hashmap") Integer> keys_sorted = + new TreeSet<@KeyFor("call_hashmap") Integer>(call_hashmap.keySet()); + for (Integer i : keys_sorted) { + @NonNull Date d = call_hashmap.get(i); + } + } + + public static Object testPut(Map map, Object key) { + if (!map.containsKey(key)) { + map.put(key, new Object()); + } + return map.get(key); + } + + public static Object testAssertGet(Map map, Object key) { + assert map.get(key) != null; + return map.get(key); + } + + public static Object testThrow(Map map, Object key) { + if (!map.containsKey(key)) { + if (true) { + return "m"; + } else { + throw new RuntimeException(); + } + } + return map.get(key); + } + + public void negateMap(Map map, Object key) { + if (!map.containsKey(key)) { + } else { + @NonNull Object v = map.get(key); + } + } + + public void withinElseInvalid(Map map, Object key) { + if (map.containsKey(key)) { + } else { + // :: error: (assignment.type.incompatible) + @NonNull Object v = map.get(key); // should issue warning + } + } + + // Map.get should be annotated as @org.checkerframework.dataflow.qual.Pure + public static int mapGetSize(MyMap> covered, Object file) { + return (covered.get(file) == null) ? 0 : covered.get(file).size(); + } + + interface MyMap extends Map { + // TODO: @AssertGenericNullnessIfTrue("get(#1)") + @org.checkerframework.dataflow.qual.Pure + public abstract boolean containsKey(@Nullable Object a1); + + // We get an override warning, because we do not use the annotated JDK in the + // test suite. Ignore this. + @SuppressWarnings("override.return.invalid") + @org.checkerframework.dataflow.qual.Pure + public @Nullable V get(@Nullable Object o); + } + + private static final String KEY = "key"; + private static final String KEY2 = "key2"; + + void testAnd(MyMap map, MyMap map2) { + if (map.containsKey(KEY)) { + map.get(KEY).toString(); + } + // :: warning: (nulltest.redundant) + if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) {} + // :: error: (dereference.of.nullable) :: warning: (nulltest.redundant) + if (map2.containsKey(KEY2) && map2.get(KEY2).toString() != null) {} + } + + void testAndWithIllegalMapAnnotation(MyMap2 map) { + if (map.containsKey(KEY)) { + map.get(KEY).toString(); + } + // :: warning: (nulltest.redundant) + if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { + // do nothing + } + } + + interface MyMap2 { + @org.checkerframework.dataflow.qual.Pure + // This annotation is not legal on containsKey in general. If the Map is declared as (say) + // Map, then get returns a nullable value. We really want to say + // that if containsKey returns non-null, then get returns V rather than @Nullable V, but I + // don't know how to say that. + @EnsuresNonNullIf(result = true, expression = "get(#1)") + public abstract boolean containsKey(@Nullable Object a1); + + @org.checkerframework.dataflow.qual.Pure + public abstract @Nullable V get(@Nullable Object a1); + } + + interface MyMap3 { + @org.checkerframework.dataflow.qual.Pure + @EnsuresNonNullIf(result = true, expression = "get(#1)") + // The following error is issued because, unlike in interface MyMap2, + // this interface has no get() method. + // :: error: (flowexpr.parse.error) + boolean containsKey(@Nullable Object a1); + } } diff --git a/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java b/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java index 56aaed96169..3d639182b47 100644 --- a/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java +++ b/checker/tests/nullness-asserts/TestAssumeAssertionsAreEnabled.java @@ -2,13 +2,13 @@ public class TestAssumeAssertionsAreEnabled { - void foo(@Nullable String s1, @Nullable String s2) { - // :: error: (dereference.of.nullable) - assert s2.equals(s1); - } + void foo(@Nullable String s1, @Nullable String s2) { + // :: error: (dereference.of.nullable) + assert s2.equals(s1); + } - void bar(@Nullable String s1, @Nullable String s2) { - // :: error: (dereference.of.nullable) - assert s2.equals(s1) : "@AssumeAssertion(nullness)"; - } + void bar(@Nullable String s1, @Nullable String s2) { + // :: error: (dereference.of.nullable) + assert s2.equals(s1) : "@AssumeAssertion(nullness)"; + } } diff --git a/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java b/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java index ae3f09038b7..37b2fb419d6 100644 --- a/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java +++ b/checker/tests/nullness-assumeassertions/TestAssumeAssertionsAreDisabled.java @@ -2,13 +2,13 @@ public class TestAssumeAssertionsAreDisabled { - void foo(@Nullable String s1, @Nullable String s2) { + void foo(@Nullable String s1, @Nullable String s2) { - // If assertions are disabled, then this cannot throw a NullPointerException - assert s2.equals(s1); + // If assertions are disabled, then this cannot throw a NullPointerException + assert s2.equals(s1); - // However, even with assertions disabled, @AssumeAssertion is still respected - // :: error: (dereference.of.nullable) - assert s2.equals(s1) : "@AssumeAssertion(nullness)"; - } + // However, even with assertions disabled, @AssumeAssertion is still respected + // :: error: (dereference.of.nullable) + assert s2.equals(s1) : "@AssumeAssertion(nullness)"; + } } diff --git a/checker/tests/nullness-assumeinitialized/AssumeInitTest.java b/checker/tests/nullness-assumeinitialized/AssumeInitTest.java index 9a95b3acd14..f6155ad083b 100644 --- a/checker/tests/nullness-assumeinitialized/AssumeInitTest.java +++ b/checker/tests/nullness-assumeinitialized/AssumeInitTest.java @@ -3,27 +3,27 @@ public class AssumeInitTest { - AssumeInitTest f; + AssumeInitTest f; - public AssumeInitTest(String arg) {} + public AssumeInitTest(String arg) {} - void test() { - @NonNull String s = "234"; - // :: error: (assignment.type.incompatible) - s = null; - } + void test() { + @NonNull String s = "234"; + // :: error: (assignment.type.incompatible) + s = null; + } - void test2(@UnknownInitialization @NonNull AssumeInitTest t) { - @Initialized @NonNull AssumeInitTest a = t.f; - } + void test2(@UnknownInitialization @NonNull AssumeInitTest t) { + @Initialized @NonNull AssumeInitTest a = t.f; + } - void simplestTestEver() { - @NonNull String a = "abc"; + void simplestTestEver() { + @NonNull String a = "abc"; - // :: error: (assignment.type.incompatible) - a = null; + // :: error: (assignment.type.incompatible) + a = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = null; - } + // :: error: (assignment.type.incompatible) + @NonNull String b = null; + } } diff --git a/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java b/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java index b9f9abd5830..3c5fa2911e9 100644 --- a/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java +++ b/checker/tests/nullness-assumekeyfor/AssumeKeyForTest.java @@ -1,51 +1,52 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.HashMap; +import java.util.Map; + public class AssumeKeyForTest { - void m1(Map m, String k) { - @NonNull Integer x = m.get(k); - } + void m1(Map m, String k) { + @NonNull Integer x = m.get(k); + } - void m1b(HashMap m, String k) { - @NonNull Integer x = m.get(k); - } + void m1b(HashMap m, String k) { + @NonNull Integer x = m.get(k); + } - void m2(Map m, String k) { - @Nullable Integer x = m.get(k); - } + void m2(Map m, String k) { + @Nullable Integer x = m.get(k); + } - void m3(Map m, String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer x = m.get(k); - } + void m3(Map m, String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer x = m.get(k); + } - void m4(Map m, String k) { - @Nullable Integer x = m.get(k); - } + void m4(Map m, String k) { + @Nullable Integer x = m.get(k); + } - void m5(Map m, @KeyFor("#1") String k) { - @NonNull Integer x = m.get(k); - } + void m5(Map m, @KeyFor("#1") String k) { + @NonNull Integer x = m.get(k); + } - void m6(Map m, @KeyFor("#1") String k) { - @Nullable Integer x = m.get(k); - } + void m6(Map m, @KeyFor("#1") String k) { + @Nullable Integer x = m.get(k); + } - void m7(Map m, @KeyFor("#1") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer x = m.get(k); - } + void m7(Map m, @KeyFor("#1") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer x = m.get(k); + } - void m7b(HashMap m, @KeyFor("#1") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer x = m.get(k); - } + void m7b(HashMap m, @KeyFor("#1") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer x = m.get(k); + } - void m8(Map m, @KeyFor("#1") String k) { - @Nullable Integer x = m.get(k); - } + void m8(Map m, @KeyFor("#1") String k) { + @Nullable Integer x = m.get(k); + } } diff --git a/checker/tests/nullness-checkcastelementtype/Issue1315.java b/checker/tests/nullness-checkcastelementtype/Issue1315.java index 8ccdb486700..5c8b4ed68ab 100644 --- a/checker/tests/nullness-checkcastelementtype/Issue1315.java +++ b/checker/tests/nullness-checkcastelementtype/Issue1315.java @@ -4,34 +4,34 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1315 { - static class Box { - T f; + static class Box { + T f; - Box(T p) { - f = p; - } + Box(T p) { + f = p; + } - @SuppressWarnings("unchecked") - T test1(@Nullable Object p) { - // :: warning: (cast.unsafe) - return (T) p; - } + @SuppressWarnings("unchecked") + T test1(@Nullable Object p) { + // :: warning: (cast.unsafe) + return (T) p; + } - // The Nullness Checker should not issue a cast.unsafe warning, - // but the KeyFor Checker does, so suppress that warning. - @SuppressWarnings({"unchecked", "keyfor:cast.unsafe"}) - T test2(Object p) { - return (T) p; + // The Nullness Checker should not issue a cast.unsafe warning, + // but the KeyFor Checker does, so suppress that warning. + @SuppressWarnings({"unchecked", "keyfor:cast.unsafe"}) + T test2(Object p) { + return (T) p; + } } - } - static class Casts { - public static void test() { - Box bs = new Box<>(""); - bs.f = bs.test1(null); - // :: error: (argument.type.incompatible) - bs.f = bs.test2(null); - bs.f.toString(); + static class Casts { + public static void test() { + Box bs = new Box<>(""); + bs.f = bs.test1(null); + // :: error: (argument.type.incompatible) + bs.f = bs.test2(null); + bs.f.toString(); + } } - } } diff --git a/checker/tests/nullness-concurrent-semantics/Issue350.java b/checker/tests/nullness-concurrent-semantics/Issue350.java index 858acba80b9..ca5c49484f0 100644 --- a/checker/tests/nullness-concurrent-semantics/Issue350.java +++ b/checker/tests/nullness-concurrent-semantics/Issue350.java @@ -5,47 +5,47 @@ class Test1 { - public @Nullable String y; + public @Nullable String y; - public void test2() { - y = ""; - // Sanity check that -AconcurrentSemantics is set - // :: error: (dereference.of.nullable) - y.toString(); - } + public void test2() { + y = ""; + // Sanity check that -AconcurrentSemantics is set + // :: error: (dereference.of.nullable) + y.toString(); + } - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - void test() { - if (x == null) { - x = ""; + void test() { + if (x == null) { + x = ""; + } + x.toString(); } - x.toString(); - } } class Test2 { - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - void setX(String x) { - this.x = x; - } + void setX(String x) { + this.x = x; + } - void test() { - if (x == null) { - x = ""; + void test() { + if (x == null) { + x = ""; + } + setX(x); } - setX(x); - } } class Test3 { - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - @EnsuresNonNull("#1") - void setX(final String x) { - this.x = x; - } + @EnsuresNonNull("#1") + void setX(final String x) { + this.x = x; + } } diff --git a/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java b/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java index c19d828dd86..d698e742b78 100644 --- a/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java +++ b/checker/tests/nullness-enclosingexpr/NullnessEnclosingExprTest.java @@ -2,38 +2,38 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; class NullnessEnclosingExprTest { - class InnerWithImplicitEnclosingExpression { - // There is no possible NPE and therefore no expected error. - InnerWithImplicitEnclosingExpression() { - NullnessEnclosingExprTest.this.f.hashCode(); + class InnerWithImplicitEnclosingExpression { + // There is no possible NPE and therefore no expected error. + InnerWithImplicitEnclosingExpression() { + NullnessEnclosingExprTest.this.f.hashCode(); + } } - } - class InnerWithInitializedEnclosingExpression { - // The default type of enclosing expression is same as InnerWithImplicitEnclosingExpression, - // we just make it explicit for testing. - InnerWithInitializedEnclosingExpression( - @Initialized NullnessEnclosingExprTest NullnessEnclosingExprTest.this) {} - } + class InnerWithInitializedEnclosingExpression { + // The default type of enclosing expression is same as InnerWithImplicitEnclosingExpression, + // we just make it explicit for testing. + InnerWithInitializedEnclosingExpression( + @Initialized NullnessEnclosingExprTest NullnessEnclosingExprTest.this) {} + } - class InnerWithUnknownInitializationEnclosingExpression { - InnerWithUnknownInitializationEnclosingExpression( - @UnknownInitialization NullnessEnclosingExprTest NullnessEnclosingExprTest.this) { - // This should also never lead to an NPE, because that dereference should produce an - // type error. - // See Issue https://github.com/eisop/checker-framework/issues/412. - NullnessEnclosingExprTest.this.f.hashCode(); + class InnerWithUnknownInitializationEnclosingExpression { + InnerWithUnknownInitializationEnclosingExpression( + @UnknownInitialization NullnessEnclosingExprTest NullnessEnclosingExprTest.this) { + // This should also never lead to an NPE, because that dereference should produce an + // type error. + // See Issue https://github.com/eisop/checker-framework/issues/412. + NullnessEnclosingExprTest.this.f.hashCode(); + } } - } - NullnessEnclosingExprTest() { - // :: error: (enclosingexpr.type.incompatible) - this.new InnerWithImplicitEnclosingExpression(); - // :: error: (enclosingexpr.type.incompatible) - this.new InnerWithInitializedEnclosingExpression(); - this.new InnerWithUnknownInitializationEnclosingExpression(); - f = "a"; - } + NullnessEnclosingExprTest() { + // :: error: (enclosingexpr.type.incompatible) + this.new InnerWithImplicitEnclosingExpression(); + // :: error: (enclosingexpr.type.incompatible) + this.new InnerWithInitializedEnclosingExpression(); + this.new InnerWithUnknownInitializationEnclosingExpression(); + f = "a"; + } - Object f; + Object f; } diff --git a/checker/tests/nullness-extra/Bug109_A.java b/checker/tests/nullness-extra/Bug109_A.java index b3019e2a01c..784928e1c07 100644 --- a/checker/tests/nullness-extra/Bug109_A.java +++ b/checker/tests/nullness-extra/Bug109_A.java @@ -1,9 +1,9 @@ public class Bug109_A { - int one = "1".length(); + int one = "1".length(); - // fix 1: public final int one; { one = "1".length(); } - // fix 2: public final int one = 0 + "1".length(); + // fix 1: public final int one; { one = "1".length(); } + // fix 2: public final int one = 0 + "1".length(); - int nl = 5; - int two = nl; + int nl = 5; + int two = nl; } diff --git a/checker/tests/nullness-extra/Bug109_B.java b/checker/tests/nullness-extra/Bug109_B.java index 9dfaf203eae..b4beca96922 100644 --- a/checker/tests/nullness-extra/Bug109_B.java +++ b/checker/tests/nullness-extra/Bug109_B.java @@ -1,11 +1,11 @@ public class Bug109_B extends Bug109_A { - public Bug109_B() { - // Accessing field one causes NPE - // at org.checkerframework.checker.nullness.MapGetHeuristics.handle - // (MapGetHeuristics.java:91) + public Bug109_B() { + // Accessing field one causes NPE + // at org.checkerframework.checker.nullness.MapGetHeuristics.handle + // (MapGetHeuristics.java:91) - int myone = one; + int myone = one; - int mytwo = two; - } + int mytwo = two; + } } diff --git a/checker/tests/nullness-extra/compat/CompatTest.java b/checker/tests/nullness-extra/compat/CompatTest.java index 49dc9dfb634..2241d42030d 100644 --- a/checker/tests/nullness-extra/compat/CompatTest.java +++ b/checker/tests/nullness-extra/compat/CompatTest.java @@ -1,8 +1,9 @@ import lib.Lib; + import org.checkerframework.checker.nullness.qual.NonNull; public class CompatTest { - void m() { - @NonNull Object o = Lib.maybeGetObject(); - } + void m() { + @NonNull Object o = Lib.maybeGetObject(); + } } diff --git a/checker/tests/nullness-extra/compat/lib/Lib.java b/checker/tests/nullness-extra/compat/lib/Lib.java index 4625a83a019..427b841cf20 100644 --- a/checker/tests/nullness-extra/compat/lib/Lib.java +++ b/checker/tests/nullness-extra/compat/lib/Lib.java @@ -3,7 +3,7 @@ import javax.annotation.Nullable; public class Lib { - @Nullable public static Object maybeGetObject() { - return null; - } + @Nullable public static Object maybeGetObject() { + return null; + } } diff --git a/checker/tests/nullness-extra/issue265/Delta.java b/checker/tests/nullness-extra/issue265/Delta.java index bb5bb284b26..f8b67139c70 100644 --- a/checker/tests/nullness-extra/issue265/Delta.java +++ b/checker/tests/nullness-extra/issue265/Delta.java @@ -1,9 +1,9 @@ import java.util.List; public class Delta { - List field; + List field; - Delta(List field) { - this.field = ImmutableList.copyOf(field); - } + Delta(List field) { + this.field = ImmutableList.copyOf(field); + } } diff --git a/checker/tests/nullness-extra/issue265/ImmutableList.java b/checker/tests/nullness-extra/issue265/ImmutableList.java index b1b690b212e..4383eceb918 100644 --- a/checker/tests/nullness-extra/issue265/ImmutableList.java +++ b/checker/tests/nullness-extra/issue265/ImmutableList.java @@ -2,7 +2,7 @@ import java.util.List; public abstract class ImmutableList implements List { - public static List copyOf(Iterable elements) { - return new ArrayList(); - } + public static List copyOf(Iterable elements) { + return new ArrayList(); + } } diff --git a/checker/tests/nullness-extra/issue309/Issue309.java b/checker/tests/nullness-extra/issue309/Issue309.java index 07e5186d4b9..2c3a1029d3d 100644 --- a/checker/tests/nullness-extra/issue309/Issue309.java +++ b/checker/tests/nullness-extra/issue309/Issue309.java @@ -1,7 +1,7 @@ import lib.Lib; public class Issue309 { - void bar() { - Lib.foo(); - } + void bar() { + Lib.foo(); + } } diff --git a/checker/tests/nullness-extra/issue309/lib/Lib.java b/checker/tests/nullness-extra/issue309/lib/Lib.java index ead9c6bb5e2..a3344396a33 100644 --- a/checker/tests/nullness-extra/issue309/lib/Lib.java +++ b/checker/tests/nullness-extra/issue309/lib/Lib.java @@ -1,6 +1,6 @@ package lib; public class Lib { - @Anno - public static void foo() {} + @Anno + public static void foo() {} } diff --git a/checker/tests/nullness-extra/issue348/Issue348.java b/checker/tests/nullness-extra/issue348/Issue348.java index 72ba0649ce2..f7dd6b82823 100644 --- a/checker/tests/nullness-extra/issue348/Issue348.java +++ b/checker/tests/nullness-extra/issue348/Issue348.java @@ -2,8 +2,8 @@ public class Issue348 { - void test() { - Lib lib = new Lib(); - lib.foo(); - } + void test() { + Lib lib = new Lib(); + lib.foo(); + } } diff --git a/checker/tests/nullness-extra/issue348/lib/Lib.java b/checker/tests/nullness-extra/issue348/lib/Lib.java index aed0f3b3ea4..fc9edc7da06 100644 --- a/checker/tests/nullness-extra/issue348/lib/Lib.java +++ b/checker/tests/nullness-extra/issue348/lib/Lib.java @@ -1,6 +1,6 @@ package lib; public class Lib extends LibSuper { - @Override - public void foo() {} + @Override + public void foo() {} } diff --git a/checker/tests/nullness-extra/issue348/lib/LibSuper.java b/checker/tests/nullness-extra/issue348/lib/LibSuper.java index c995eed14e9..3d8ed92d015 100644 --- a/checker/tests/nullness-extra/issue348/lib/LibSuper.java +++ b/checker/tests/nullness-extra/issue348/lib/LibSuper.java @@ -1,6 +1,6 @@ package lib; public class LibSuper { - @Anno - public void foo() {} + @Anno + public void foo() {} } diff --git a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java index 11d72d63882..0a9be45d12c 100644 --- a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java +++ b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597A.java @@ -1,7 +1,7 @@ package testpkg; public class Issue3597A { - void f() { - System.err.println(new Issue3597B().f().toString()); - } + void f() { + System.err.println(new Issue3597B().f().toString()); + } } diff --git a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java index 4d0df950d9b..b365430b09f 100644 --- a/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java +++ b/checker/tests/nullness-extra/issue3597/testpkg/Issue3597B.java @@ -3,7 +3,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3597B { - @Nullable Object f() { - return new Object(); - } + @Nullable Object f() { + return new Object(); + } } diff --git a/checker/tests/nullness-extra/issue502/Issue502.java b/checker/tests/nullness-extra/issue502/Issue502.java index a1d3816b307..a39f3ff3749 100644 --- a/checker/tests/nullness-extra/issue502/Issue502.java +++ b/checker/tests/nullness-extra/issue502/Issue502.java @@ -2,8 +2,8 @@ // https://github.com/typetools/checker-framework/issues/502 public class Issue502 { - @Override - public String toString() { - return ""; - } + @Override + public String toString() { + return ""; + } } diff --git a/checker/tests/nullness-extra/issue5174/Issue5174.java b/checker/tests/nullness-extra/issue5174/Issue5174.java index da84b645e7e..a234c2bb760 100644 --- a/checker/tests/nullness-extra/issue5174/Issue5174.java +++ b/checker/tests/nullness-extra/issue5174/Issue5174.java @@ -2,68 +2,68 @@ // https://github.com/typetools/checker-framework/issues/5174 class Issue5174Super { - S methodInner(S in) { - return in; - } + S methodInner(S in) { + return in; + } - S f; - static Object sf = ""; + S f; + static Object sf = ""; - Issue5174Super(S f) { - this.f = f; - } + Issue5174Super(S f) { + this.f = f; + } } class Issue5174Sub extends Issue5174Super { - Issue5174Sub(T f) { - super(f); - } - - void accMethImpl(T in) { - Object o = methodInner(in); - } - - void accMethExpl(T in) { - Object o = this.methodInner(in); - } - - void accFieldImpl() { - Object o = f; - } - - void accFieldExpl() { - Object o = this.f; - } + Issue5174Sub(T f) { + super(f); + } - void accStaticField() { - Object o; - o = sf; - o = Issue5174Sub.sf; - o = Issue5174Super.sf; - } + void accMethImpl(T in) { + Object o = methodInner(in); + } - class SubNested { - void nestedaccMethImpl(T in) { - Object o = methodInner(in); + void accMethExpl(T in) { + Object o = this.methodInner(in); } - void nestedaccMethExpl(T in) { - Object o = Issue5174Sub.this.methodInner(in); + void accFieldImpl() { + Object o = f; } - void nestedaccFieldImpl() { - Object o = f; + void accFieldExpl() { + Object o = this.f; } - void nestedaccFieldExpl() { - Object o = Issue5174Sub.this.f; + void accStaticField() { + Object o; + o = sf; + o = Issue5174Sub.sf; + o = Issue5174Super.sf; } - void nestedaccStaticField() { - Object o; - o = sf; - o = Issue5174Sub.sf; - o = Issue5174Super.sf; + class SubNested { + void nestedaccMethImpl(T in) { + Object o = methodInner(in); + } + + void nestedaccMethExpl(T in) { + Object o = Issue5174Sub.this.methodInner(in); + } + + void nestedaccFieldImpl() { + Object o = f; + } + + void nestedaccFieldExpl() { + Object o = Issue5174Sub.this.f; + } + + void nestedaccStaticField() { + Object o; + o = sf; + o = Issue5174Sub.sf; + o = Issue5174Super.sf; + } } - } } diff --git a/checker/tests/nullness-extra/issue559/Issue559.java b/checker/tests/nullness-extra/issue559/Issue559.java index c18c74b1294..158dc6fa384 100644 --- a/checker/tests/nullness-extra/issue559/Issue559.java +++ b/checker/tests/nullness-extra/issue559/Issue559.java @@ -4,10 +4,10 @@ import java.util.Optional; public class Issue559 { - void bar(Optional o) { - // With myjdk.astub the following should fail with an - // argument.type.incompatible error. - o.orElse(null); - o.orElse("Hi"); - } + void bar(Optional o) { + // With myjdk.astub the following should fail with an + // argument.type.incompatible error. + o.orElse(null); + o.orElse("Hi"); + } } diff --git a/checker/tests/nullness-extra/issue594/Issue594.java b/checker/tests/nullness-extra/issue594/Issue594.java index a34d199cd65..af99af1d2e0 100644 --- a/checker/tests/nullness-extra/issue594/Issue594.java +++ b/checker/tests/nullness-extra/issue594/Issue594.java @@ -9,11 +9,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue594 { - private @Nullable T result = null; + private @Nullable T result = null; - // Should return @Nullable T - private T getResult() { - // :: error: (return.type.incompatible) - return result; - } + // Should return @Nullable T + private T getResult() { + // :: error: (return.type.incompatible) + return result; + } } diff --git a/checker/tests/nullness-extra/issue607/Issue607.java b/checker/tests/nullness-extra/issue607/Issue607.java index 100e38a9f80..c441fc5784f 100644 --- a/checker/tests/nullness-extra/issue607/Issue607.java +++ b/checker/tests/nullness-extra/issue607/Issue607.java @@ -1,7 +1,7 @@ public class Issue607 extends Issue607SuperClass { - static String simpleString = "a"; + static String simpleString = "a"; - Issue607() { - super(Issue607SuperClass.issue, string -> simpleString); - } + Issue607() { + super(Issue607SuperClass.issue, string -> simpleString); + } } diff --git a/checker/tests/nullness-extra/issue607/Issue607Interface.java b/checker/tests/nullness-extra/issue607/Issue607Interface.java index 4e942a4e40b..a83fd7d8a06 100644 --- a/checker/tests/nullness-extra/issue607/Issue607Interface.java +++ b/checker/tests/nullness-extra/issue607/Issue607Interface.java @@ -1,3 +1,3 @@ interface Issue607Interface { - Object method(Object string); + Object method(Object string); } diff --git a/checker/tests/nullness-extra/issue607/Issue607SuperClass.java b/checker/tests/nullness-extra/issue607/Issue607SuperClass.java index ec05dd3fd2c..f5633fa297b 100644 --- a/checker/tests/nullness-extra/issue607/Issue607SuperClass.java +++ b/checker/tests/nullness-extra/issue607/Issue607SuperClass.java @@ -1,5 +1,5 @@ public class Issue607SuperClass { - static Issue607Interface issue; + static Issue607Interface issue; - public Issue607SuperClass(Issue607Interface... issue) {} + public Issue607SuperClass(Issue607Interface... issue) {} } diff --git a/checker/tests/nullness-extra/multiple-errors/C1.java b/checker/tests/nullness-extra/multiple-errors/C1.java index 206a1613392..02e85c78db3 100644 --- a/checker/tests/nullness-extra/multiple-errors/C1.java +++ b/checker/tests/nullness-extra/multiple-errors/C1.java @@ -1,3 +1,3 @@ public class C1 { - Object o; + Object o; } diff --git a/checker/tests/nullness-extra/multiple-errors/C2.java b/checker/tests/nullness-extra/multiple-errors/C2.java index ab0a5a5b613..df3b294434e 100644 --- a/checker/tests/nullness-extra/multiple-errors/C2.java +++ b/checker/tests/nullness-extra/multiple-errors/C2.java @@ -1,3 +1,3 @@ public class C2 { - Object o = null; + Object o = null; } diff --git a/checker/tests/nullness-extra/multiple-errors/C3.java b/checker/tests/nullness-extra/multiple-errors/C3.java index c5cf1a64a72..ad56046a0cf 100644 --- a/checker/tests/nullness-extra/multiple-errors/C3.java +++ b/checker/tests/nullness-extra/multiple-errors/C3.java @@ -1,9 +1,9 @@ public class C3 { - void m() { - class C3b { - void bad(XXX p) { - p.toString(); - } + void m() { + class C3b { + void bad(XXX p) { + p.toString(); + } + } } - } } diff --git a/checker/tests/nullness-extra/multiple-errors/C4.java b/checker/tests/nullness-extra/multiple-errors/C4.java index b363b305c21..7249c80e9d0 100644 --- a/checker/tests/nullness-extra/multiple-errors/C4.java +++ b/checker/tests/nullness-extra/multiple-errors/C4.java @@ -1,5 +1,5 @@ public class C4 { - void m(@org.checkerframework.checker.nullness.qual.Nullable Object p) { - p.toString(); - } + void m(@org.checkerframework.checker.nullness.qual.Nullable Object p) { + p.toString(); + } } diff --git a/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java b/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java index dd9d005d7eb..87991eae4ff 100644 --- a/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java +++ b/checker/tests/nullness-extra/package-anno/test/PackageAnnotationTest.java @@ -1,6 +1,6 @@ package test; public class PackageAnnotationTest { - // Allowed because of package annotation. - Object f = null; + // Allowed because of package annotation. + Object f = null; } diff --git a/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java b/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java index 487d6336f4b..d9bc93ab5ad 100644 --- a/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java +++ b/checker/tests/nullness-extra/shorthand/NullnessRegexWithErrors.java @@ -3,10 +3,10 @@ import java.util.regex.Pattern; public class NullnessRegexWithErrors { - String str = "(str"; + String str = "(str"; - void context() { - str = null; - Pattern.compile("\\I"); - } + void context() { + str = null; + Pattern.compile("\\I"); + } } diff --git a/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java b/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java index da408842152..9d63bb3e53c 100644 --- a/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java +++ b/checker/tests/nullness-genericwildcard/GenericWildcardInheritance.java @@ -3,6 +3,6 @@ // If GwiParent is read from bytecode, the error occurs. public class GenericWildcardInheritance extends GwiParent { - @Override - public void syntaxError(Recognizer recognizer) {} + @Override + public void syntaxError(Recognizer recognizer) {} } diff --git a/checker/tests/nullness-genericwildcard/Issue511.java b/checker/tests/nullness-genericwildcard/Issue511.java index 5266ad7947a..48c09c71271 100644 --- a/checker/tests/nullness-genericwildcard/Issue511.java +++ b/checker/tests/nullness-genericwildcard/Issue511.java @@ -3,29 +3,29 @@ class MyGeneric {} class MySuperClass { - public void method(MyGeneric x) {} + public void method(MyGeneric x) {} } public class Issue511 extends MySuperClass { - @Override - public void method(MyGeneric x) { - super.method(x); - } + @Override + public void method(MyGeneric x) { + super.method(x); + } - // public void method(MyGeneric x) {} - // On the above method, javac issues the following error: - // Issue511.java:19: error: name clash: method(MyGeneric) in Issue511 and - // method(MyGeneric) in MySuperClass have the same erasure, yet neither - // overrides the other - // public void method(MyGeneric x) {} - // ^ - // 1 error + // public void method(MyGeneric x) {} + // On the above method, javac issues the following error: + // Issue511.java:19: error: name clash: method(MyGeneric) in Issue511 and + // method(MyGeneric) in MySuperClass have the same erasure, yet neither + // overrides the other + // public void method(MyGeneric x) {} + // ^ + // 1 error } class Use { - MyGeneric wildCardExtendsObject = new MyGeneric<>(); - MyGeneric wildCardExtendsNumber = wildCardExtendsObject; - MyGeneric wildCardNoBound = new MyGeneric<>(); - MyGeneric wildCardExtendsNumber2 = wildCardNoBound; + MyGeneric wildCardExtendsObject = new MyGeneric<>(); + MyGeneric wildCardExtendsNumber = wildCardExtendsObject; + MyGeneric wildCardNoBound = new MyGeneric<>(); + MyGeneric wildCardExtendsNumber2 = wildCardNoBound; } diff --git a/checker/tests/nullness-genericwildcardlib/GwiParent.java b/checker/tests/nullness-genericwildcardlib/GwiParent.java index 60826b2e71e..6fde1b3e7c2 100644 --- a/checker/tests/nullness-genericwildcardlib/GwiParent.java +++ b/checker/tests/nullness-genericwildcardlib/GwiParent.java @@ -1,7 +1,7 @@ // Library for issue #511: https://github.com/typetools/checker-framework/issues/511 public abstract class GwiParent { - abstract void syntaxError(Recognizer recognizer); + abstract void syntaxError(Recognizer recognizer); } abstract class ATNSimulator {} diff --git a/checker/tests/nullness-initialization/AssignmentDuringInitialization.java b/checker/tests/nullness-initialization/AssignmentDuringInitialization.java index 3f6f53f43fa..fc0623c71c6 100644 --- a/checker/tests/nullness-initialization/AssignmentDuringInitialization.java +++ b/checker/tests/nullness-initialization/AssignmentDuringInitialization.java @@ -1,42 +1,42 @@ // This test covers Issue345 at: // https://github.com/typetools/checker-framework/issues/345 public class AssignmentDuringInitialization { - String f1; - String f2; + String f1; + String f2; - String f3; - String f4; + String f3; + String f4; - String f5; - String f6; + String f5; + String f6; - { - // :: error: (assignment.type.incompatible) - f1 = f2; - f2 = f1; - f2.toString(); // Null pointer exception here - } + { + // :: error: (assignment.type.incompatible) + f1 = f2; + f2 = f1; + f2.toString(); // Null pointer exception here + } - public AssignmentDuringInitialization() { - // :: error: (assignment.type.incompatible) - f3 = f4; - f4 = f3; - f4.toString(); // Null pointer exception here + public AssignmentDuringInitialization() { + // :: error: (assignment.type.incompatible) + f3 = f4; + f4 = f3; + f4.toString(); // Null pointer exception here - f5 = "hello"; - f6 = f5; - } + f5 = "hello"; + f6 = f5; + } - public void goodBehavior() { - // This isn't a constructor or initializer. - // The receiver of this method should already be initialized - // and therefore f1 and f2 should already be initialized. - f5 = f6; - f6 = f5; - f6.toString(); // No exception here - } + public void goodBehavior() { + // This isn't a constructor or initializer. + // The receiver of this method should already be initialized + // and therefore f1 and f2 should already be initialized. + f5 = f6; + f6 = f5; + f6.toString(); // No exception here + } - public static void main(String[] args) { - AssignmentDuringInitialization a = new AssignmentDuringInitialization(); - } + public static void main(String[] args) { + AssignmentDuringInitialization a = new AssignmentDuringInitialization(); + } } diff --git a/checker/tests/nullness-initialization/EisopIssue635.java b/checker/tests/nullness-initialization/EisopIssue635.java index c50b8872c44..d8416628d57 100644 --- a/checker/tests/nullness-initialization/EisopIssue635.java +++ b/checker/tests/nullness-initialization/EisopIssue635.java @@ -2,21 +2,21 @@ class EisopIssue635 { - private @Nullable Runnable r; + private @Nullable Runnable r; - private void f() { - // No crash without this assignment first. - r = null; - r = - new Runnable() { - @Override - public void run() { - if (r != this) { - return; - } - // No crash without this call. - f(); - } - }; - } + private void f() { + // No crash without this assignment first. + r = null; + r = + new Runnable() { + @Override + public void run() { + if (r != this) { + return; + } + // No crash without this call. + f(); + } + }; + } } diff --git a/checker/tests/nullness-initialization/EnumFieldUninit.java b/checker/tests/nullness-initialization/EnumFieldUninit.java index e70439ce37b..2b8f2ad9ba9 100644 --- a/checker/tests/nullness-initialization/EnumFieldUninit.java +++ b/checker/tests/nullness-initialization/EnumFieldUninit.java @@ -1,23 +1,23 @@ enum EnumFieldUninit { - DUMMY; + DUMMY; - // :: error: (assignment.type.incompatible) - public static String s = null; + // :: error: (assignment.type.incompatible) + public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; + // :: error: (initialization.static.field.uninitialized) + public static String u; - static String[] arrayInit = new String[] {}; + static String[] arrayInit = new String[] {}; - static String[] arrayInitInBlock; + static String[] arrayInitInBlock; - static { - arrayInitInBlock = new String[] {}; - } + static { + arrayInitInBlock = new String[] {}; + } - // :: error: (assignment.type.incompatible) - static String[] arrayInitToNull = null; + // :: error: (assignment.type.incompatible) + static String[] arrayInitToNull = null; - // :: error: (initialization.static.field.uninitialized) - static String[] arrayUninit; + // :: error: (initialization.static.field.uninitialized) + static String[] arrayUninit; } diff --git a/checker/tests/nullness-initialization/FieldInit.java b/checker/tests/nullness-initialization/FieldInit.java index 883fc520b54..eb1ec8d773d 100644 --- a/checker/tests/nullness-initialization/FieldInit.java +++ b/checker/tests/nullness-initialization/FieldInit.java @@ -1,12 +1,12 @@ public class FieldInit { - // :: error: (argument.type.incompatible) :: error: (method.invocation.invalid) - String f = init(this); + // :: error: (argument.type.incompatible) :: error: (method.invocation.invalid) + String f = init(this); - String init(FieldInit o) { - return ""; - } + String init(FieldInit o) { + return ""; + } - void test() { - String local = init(this); - } + void test() { + String local = init(this); + } } diff --git a/checker/tests/nullness-initialization/FinalClass.java b/checker/tests/nullness-initialization/FinalClass.java index 38213da7838..6408858942c 100644 --- a/checker/tests/nullness-initialization/FinalClass.java +++ b/checker/tests/nullness-initialization/FinalClass.java @@ -9,37 +9,37 @@ import org.checkerframework.checker.nullness.qual.Nullable; final class EisopIssue610_1 { - @MonotonicNonNull String s; + @MonotonicNonNull String s; - EisopIssue610_1() { - init(); - } + EisopIssue610_1() { + init(); + } - void init() {} + void init() {} } final class EisopIssue610_2 { - @Nullable String s; + @Nullable String s; - EisopIssue610_2() { - init(); - } + EisopIssue610_2() { + init(); + } - void init() {} + void init() {} } final class EisopIssue610_3 { - @MonotonicNonNull String s; + @MonotonicNonNull String s; - EisopIssue610_3() { - @Initialized EisopIssue610_3 other = this; - } + EisopIssue610_3() { + @Initialized EisopIssue610_3 other = this; + } } final class EisopIssue610_4 { - @Nullable String s; + @Nullable String s; - EisopIssue610_4() { - @Initialized EisopIssue610_4 other = this; - } + EisopIssue610_4() { + @Initialized EisopIssue610_4 other = this; + } } diff --git a/checker/tests/nullness-initialization/FinalClassLambda.java b/checker/tests/nullness-initialization/FinalClassLambda.java index 5a0bf83fe65..f5f6afc4cd3 100644 --- a/checker/tests/nullness-initialization/FinalClassLambda.java +++ b/checker/tests/nullness-initialization/FinalClassLambda.java @@ -5,97 +5,97 @@ import org.checkerframework.checker.nullness.qual.Nullable; final class FinalClassLambda1 { - @Nullable String s; + @Nullable String s; - FinalClassLambda1() { - use(this::init); - } + FinalClassLambda1() { + use(this::init); + } - void init() {} + void init() {} - static void use(Runnable r) {} + static void use(Runnable r) {} } final class FinalClassLambda2 extends FinalClassLambda2Base { - @Nullable String s; - - FinalClassLambda2() { - use(() -> init()); - use( - new Runnable() { - @Override - public void run() { - init(); - } - }); - } - - void init() {} + @Nullable String s; + + FinalClassLambda2() { + use(() -> init()); + use( + new Runnable() { + @Override + public void run() { + init(); + } + }); + } + + void init() {} } class FinalClassLambda2Base { - void use(Runnable r) {} + void use(Runnable r) {} } final class FinalClassLambda3 { - String s; + String s; - FinalClassLambda3() { - s = "hello"; - use(this::init); - } + FinalClassLambda3() { + s = "hello"; + use(this::init); + } - void init() {} + void init() {} - static void use(Runnable r) {} + static void use(Runnable r) {} } final class FinalClassLambda4 extends FinalClassLambda2Base { - String s; - - FinalClassLambda4() { - s = "world"; - use(() -> init()); - use( - new Runnable() { - @Override - public void run() { - init(); - } - }); - } - - void init() {} + String s; + + FinalClassLambda4() { + s = "world"; + use(() -> init()); + use( + new Runnable() { + @Override + public void run() { + init(); + } + }); + } + + void init() {} } // Not a final class, but uses same name for consistency. class FinalClassLambda5 extends FinalClassLambda2Base { - String s; - - FinalClassLambda5() { - s = "hello"; - // :: error: (method.invocation.invalid) - use( - // :: error: (methodref.receiver.bound.invalid) - this::init); - } - - FinalClassLambda5(int dummy) { - s = "world"; - // :: error: (method.invocation.invalid) - use( + String s; + + FinalClassLambda5() { + s = "hello"; + // :: error: (method.invocation.invalid) + use( + // :: error: (methodref.receiver.bound.invalid) + this::init); + } + + FinalClassLambda5(int dummy) { + s = "world"; + // :: error: (method.invocation.invalid) + use( + // :: error: (method.invocation.invalid) + () -> init()); // :: error: (method.invocation.invalid) - () -> init()); - // :: error: (method.invocation.invalid) - use( - new Runnable() { - @Override - public void run() { - // :: error: (method.invocation.invalid) - init(); - } - }); - } - - void init() {} + use( + new Runnable() { + @Override + public void run() { + // :: error: (method.invocation.invalid) + init(); + } + }); + } + + void init() {} } diff --git a/checker/tests/nullness-initialization/FlowConstructor.java b/checker/tests/nullness-initialization/FlowConstructor.java index 7b70e333c3f..dbaea5c0fef 100644 --- a/checker/tests/nullness-initialization/FlowConstructor.java +++ b/checker/tests/nullness-initialization/FlowConstructor.java @@ -3,36 +3,36 @@ public class FlowConstructor { - String a; - String b; + String a; + String b; - public FlowConstructor(float f) { - a = "m"; - b = "n"; - semiRawMethod(); - } + public FlowConstructor(float f) { + a = "m"; + b = "n"; + semiRawMethod(); + } - public FlowConstructor(int p) { - a = "m"; - b = "n"; - // :: error: (method.invocation.invalid) - nonRawMethod(); - } + public FlowConstructor(int p) { + a = "m"; + b = "n"; + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - public FlowConstructor(double p) { - a = "m"; - // :: error: (method.invocation.invalid) - nonRawMethod(); // error - b = "n"; - } + public FlowConstructor(double p) { + a = "m"; + // :: error: (method.invocation.invalid) + nonRawMethod(); // error + b = "n"; + } - void nonRawMethod() { - a.toString(); - b.toString(); - } + void nonRawMethod() { + a.toString(); + b.toString(); + } - void semiRawMethod(@UnderInitialization(FlowConstructor.class) FlowConstructor this) { - a.toString(); - b.toString(); - } + void semiRawMethod(@UnderInitialization(FlowConstructor.class) FlowConstructor this) { + a.toString(); + b.toString(); + } } diff --git a/checker/tests/nullness-initialization/FlowConstructor2.java b/checker/tests/nullness-initialization/FlowConstructor2.java index 06b8409cb8c..9cd9771d370 100644 --- a/checker/tests/nullness-initialization/FlowConstructor2.java +++ b/checker/tests/nullness-initialization/FlowConstructor2.java @@ -1,8 +1,8 @@ public class FlowConstructor2 { - String f; + String f; - public FlowConstructor2() { - // :: error: (dereference.of.nullable) - f.hashCode(); - } + public FlowConstructor2() { + // :: error: (dereference.of.nullable) + f.hashCode(); + } } diff --git a/checker/tests/nullness-initialization/FlowInitialization.java b/checker/tests/nullness-initialization/FlowInitialization.java index df55a573a9f..bfcfa09da42 100644 --- a/checker/tests/nullness-initialization/FlowInitialization.java +++ b/checker/tests/nullness-initialization/FlowInitialization.java @@ -4,51 +4,51 @@ public class FlowInitialization { - @NonNull String f; - @Nullable String g; + @NonNull String f; + @Nullable String g; - // :: error: (initialization.fields.uninitialized) - public FlowInitialization() {} + // :: error: (initialization.fields.uninitialized) + public FlowInitialization() {} - public FlowInitialization(long l) { - g = ""; - f = g; - } + public FlowInitialization(long l) { + g = ""; + f = g; + } - // :: error: (initialization.fields.uninitialized) - public FlowInitialization(boolean b) { - if (b) { - f = ""; + // :: error: (initialization.fields.uninitialized) + public FlowInitialization(boolean b) { + if (b) { + f = ""; + } } - } - // :: error: (initialization.fields.uninitialized) - public FlowInitialization(int i) { - if (i == 0) { - throw new RuntimeException(); + // :: error: (initialization.fields.uninitialized) + public FlowInitialization(int i) { + if (i == 0) { + throw new RuntimeException(); + } } - } - // :: error: (initialization.fields.uninitialized) - public FlowInitialization(char c) { - if (c == 'c') { - return; + // :: error: (initialization.fields.uninitialized) + public FlowInitialization(char c) { + if (c == 'c') { + return; + } + f = ""; } - f = ""; - } - public FlowInitialization(double d) { - setField(); - } + public FlowInitialization(double d) { + setField(); + } - @EnsuresQualifier(expression = "f", qualifier = NonNull.class) - public void setField(@UnknownInitialization FlowInitialization this) { - f = ""; - } + @EnsuresQualifier(expression = "f", qualifier = NonNull.class) + public void setField(@UnknownInitialization FlowInitialization this) { + f = ""; + } } class FlowPrimitives { - boolean b; - int t; - char c; + boolean b; + int t; + char c; } diff --git a/checker/tests/nullness-initialization/Initializer.java b/checker/tests/nullness-initialization/Initializer.java index 6909e0d9277..7a08abd1d95 100644 --- a/checker/tests/nullness-initialization/Initializer.java +++ b/checker/tests/nullness-initialization/Initializer.java @@ -5,92 +5,92 @@ public class Initializer { - public String a; - public String b = "abc"; + public String a; + public String b = "abc"; - // :: error: (assignment.type.incompatible) - public String c = null; + // :: error: (assignment.type.incompatible) + public String c = null; - public String d = (""); + public String d = (""); - // :: error: (initialization.fields.uninitialized) - public Initializer() { - // :: error: (assignment.type.incompatible) - a = null; - a = ""; - c = ""; - } - - // :: error: (initialization.fields.uninitialized) - public Initializer(boolean foo) {} - - public Initializer(int foo) { - a = ""; - c = ""; - f = ""; - } - - public Initializer(float foo) { - setField(); - c = ""; - f = ""; - } - - public Initializer(double foo) { - if (!setFieldMaybe()) { - a = ""; + // :: error: (initialization.fields.uninitialized) + public Initializer() { + // :: error: (assignment.type.incompatible) + a = null; + a = ""; + c = ""; } - c = ""; - f = ""; - } - - // :: error: (initialization.fields.uninitialized) - public Initializer(double foo, boolean t) { - if (!setFieldMaybe()) { - // on this path, 'a' is not initialized + + // :: error: (initialization.fields.uninitialized) + public Initializer(boolean foo) {} + + public Initializer(int foo) { + a = ""; + c = ""; + f = ""; } - c = ""; - } - @EnsuresQualifier(expression = "a", qualifier = NonNull.class) - public void setField(@UnknownInitialization Initializer this) { - a = ""; - } + public Initializer(float foo) { + setField(); + c = ""; + f = ""; + } - @EnsuresQualifierIf(result = true, expression = "a", qualifier = NonNull.class) - public boolean setFieldMaybe(@UnknownInitialization Initializer this) { - a = ""; - return true; - } + public Initializer(double foo) { + if (!setFieldMaybe()) { + a = ""; + } + c = ""; + f = ""; + } - String f; + // :: error: (initialization.fields.uninitialized) + public Initializer(double foo, boolean t) { + if (!setFieldMaybe()) { + // on this path, 'a' is not initialized + } + c = ""; + } - void t1(@UnknownInitialization Initializer this) { - // :: error: (dereference.of.nullable) - this.f.toString(); - } + @EnsuresQualifier(expression = "a", qualifier = NonNull.class) + public void setField(@UnknownInitialization Initializer this) { + a = ""; + } - String fieldF = ""; + @EnsuresQualifierIf(result = true, expression = "a", qualifier = NonNull.class) + public boolean setFieldMaybe(@UnknownInitialization Initializer this) { + a = ""; + return true; + } + + String f; + + void t1(@UnknownInitialization Initializer this) { + // :: error: (dereference.of.nullable) + this.f.toString(); + } + + String fieldF = ""; } class SubInitializer extends Initializer { - // :: error: (initialization.field.uninitialized) - String f; - - void subt1(@UnknownInitialization(Initializer.class) SubInitializer this) { - fieldF.toString(); - super.f.toString(); - // :: error: (dereference.of.nullable) - this.f.toString(); - } - - void subt2(@UnknownInitialization SubInitializer this) { - // :: error: (dereference.of.nullable) - fieldF.toString(); - // :: error: (dereference.of.nullable) - super.f.toString(); - // :: error: (dereference.of.nullable) - this.f.toString(); - } + // :: error: (initialization.field.uninitialized) + String f; + + void subt1(@UnknownInitialization(Initializer.class) SubInitializer this) { + fieldF.toString(); + super.f.toString(); + // :: error: (dereference.of.nullable) + this.f.toString(); + } + + void subt2(@UnknownInitialization SubInitializer this) { + // :: error: (dereference.of.nullable) + fieldF.toString(); + // :: error: (dereference.of.nullable) + super.f.toString(); + // :: error: (dereference.of.nullable) + this.f.toString(); + } } diff --git a/checker/tests/nullness-initialization/Issue1096.java b/checker/tests/nullness-initialization/Issue1096.java index dda21ecd1d7..70b354fd650 100644 --- a/checker/tests/nullness-initialization/Issue1096.java +++ b/checker/tests/nullness-initialization/Issue1096.java @@ -6,58 +6,58 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; class PreCond { - Object f; - @Nullable Object g; - - PreCond() { - f = new Object(); - early(); - g = new Object(); - doNullable(); - } - - void earlyBad(@UnknownInitialization PreCond this) { - // :: error: (dereference.of.nullable) - f.toString(); - } - - @RequiresNonNull("this.f") - void early(@UnknownInitialization PreCond this) { - f.toString(); - } - - @RequiresNonNull("this.g") - void doNullable(@UnknownInitialization PreCond this) { - g.toString(); - } - - void foo(@UnknownInitialization PreCond this) { - // The receiver is not fully initialized, so raise an error. - // :: error: (contracts.precondition.not.satisfied) - early(); - } - - void bar() { - // The receiver is initialized, so non-null field f is definitely non-null. - early(); - // Nullable fields stay nullable - // :: error: (contracts.precondition.not.satisfied) - doNullable(); - } + Object f; + @Nullable Object g; + + PreCond() { + f = new Object(); + early(); + g = new Object(); + doNullable(); + } + + void earlyBad(@UnknownInitialization PreCond this) { + // :: error: (dereference.of.nullable) + f.toString(); + } + + @RequiresNonNull("this.f") + void early(@UnknownInitialization PreCond this) { + f.toString(); + } + + @RequiresNonNull("this.g") + void doNullable(@UnknownInitialization PreCond this) { + g.toString(); + } + + void foo(@UnknownInitialization PreCond this) { + // The receiver is not fully initialized, so raise an error. + // :: error: (contracts.precondition.not.satisfied) + early(); + } + + void bar() { + // The receiver is initialized, so non-null field f is definitely non-null. + early(); + // Nullable fields stay nullable + // :: error: (contracts.precondition.not.satisfied) + doNullable(); + } } class User { - void foo(@UnknownInitialization PreCond pc) { - // The receiver is not fully initialized, so raise an error. - // :: error: (contracts.precondition.not.satisfied) - pc.early(); - } - - void bar(PreCond pc) { - // The receiver is initialized, so non-null field f is definitely non-null. - pc.early(); - // Nullable fields stay nullable - // :: error: (contracts.precondition.not.satisfied) - pc.doNullable(); - } + void foo(@UnknownInitialization PreCond pc) { + // The receiver is not fully initialized, so raise an error. + // :: error: (contracts.precondition.not.satisfied) + pc.early(); + } + + void bar(PreCond pc) { + // The receiver is initialized, so non-null field f is definitely non-null. + pc.early(); + // Nullable fields stay nullable + // :: error: (contracts.precondition.not.satisfied) + pc.doNullable(); + } } diff --git a/checker/tests/nullness-initialization/Issue1590.java b/checker/tests/nullness-initialization/Issue1590.java index e9c492474d5..326db23408f 100644 --- a/checker/tests/nullness-initialization/Issue1590.java +++ b/checker/tests/nullness-initialization/Issue1590.java @@ -1,16 +1,16 @@ @SuppressWarnings("initialization") public class Issue1590 { - private String a; + private String a; - public Issue1590() { - // valid because of suppressed warnings - init(); - // :: error: (dereference.of.nullable) - a.length(); - } + public Issue1590() { + // valid because of suppressed warnings + init(); + // :: error: (dereference.of.nullable) + a.length(); + } - public void init() { - a = "gude"; - } + public void init() { + a = "gude"; + } } diff --git a/checker/tests/nullness-initialization/Issue1590a.java b/checker/tests/nullness-initialization/Issue1590a.java index 28dad73bbb3..f4de70f5f5a 100644 --- a/checker/tests/nullness-initialization/Issue1590a.java +++ b/checker/tests/nullness-initialization/Issue1590a.java @@ -2,15 +2,15 @@ @SuppressWarnings("initialization.") public class Issue1590a { - private String a; + private String a; - // :: error: (initialization.fields.uninitialized) - public Issue1590a() { - // :: error: (method.invocation.invalid) - init(); - } + // :: error: (initialization.fields.uninitialized) + public Issue1590a() { + // :: error: (method.invocation.invalid) + init(); + } - public void init() { - a = "gude"; - } + public void init() { + a = "gude"; + } } diff --git a/checker/tests/nullness-initialization/Issue261.java b/checker/tests/nullness-initialization/Issue261.java index f247cf76fa9..34630eafeae 100644 --- a/checker/tests/nullness-initialization/Issue261.java +++ b/checker/tests/nullness-initialization/Issue261.java @@ -1,18 +1,18 @@ // Test case for Issue 261 // https://github.com/typetools/checker-framework/issues/261 public class Issue261 { - boolean b; + boolean b; - class Flag { - // :: error: (initialization.field.uninitialized) - T value; - } + class Flag { + // :: error: (initialization.field.uninitialized) + T value; + } - static T getValue(Flag flag) { - return flag.value; - } + static T getValue(Flag flag) { + return flag.value; + } - Issue261(Flag flag) { - this.b = getValue(flag); - } + Issue261(Flag flag) { + this.b = getValue(flag); + } } diff --git a/checker/tests/nullness-initialization/Issue345.java b/checker/tests/nullness-initialization/Issue345.java index ea8f94775e4..235b64880c0 100644 --- a/checker/tests/nullness-initialization/Issue345.java +++ b/checker/tests/nullness-initialization/Issue345.java @@ -1,17 +1,17 @@ // This is a test case for Issue 345: // https://github.com/typetools/checker-framework/issues/345 public class Issue345 { - String f1; - String f2; + String f1; + String f2; - { - // :: error: (assignment.type.incompatible) - f1 = f2; - f2 = f1; - f2.toString(); // Null pointer exception here - } + { + // :: error: (assignment.type.incompatible) + f1 = f2; + f2 = f1; + f2.toString(); // Null pointer exception here + } - public static void main(String[] args) { - Issue345 a = new Issue345(); - } + public static void main(String[] args) { + Issue345 a = new Issue345(); + } } diff --git a/checker/tests/nullness-initialization/Issue354.java b/checker/tests/nullness-initialization/Issue354.java index cdeae0ea606..f0047696739 100644 --- a/checker/tests/nullness-initialization/Issue354.java +++ b/checker/tests/nullness-initialization/Issue354.java @@ -1,19 +1,19 @@ public class Issue354 { - String a; + String a; - { - Object o = - new Object() { - @Override - public String toString() { - // :: error: (dereference.of.nullable) - return a.toString(); - } - }.toString(); + { + Object o = + new Object() { + @Override + public String toString() { + // :: error: (dereference.of.nullable) + return a.toString(); + } + }.toString(); - // This is needed to avoid the initialization.fields.uninitialized warning. - // The NPE still occurs - a = ""; - } + // This is needed to avoid the initialization.fields.uninitialized warning. + // The NPE still occurs + a = ""; + } } diff --git a/checker/tests/nullness-initialization/Issue400.java b/checker/tests/nullness-initialization/Issue400.java index 460bd494cea..a66051580d1 100644 --- a/checker/tests/nullness-initialization/Issue400.java +++ b/checker/tests/nullness-initialization/Issue400.java @@ -2,18 +2,18 @@ import java.util.Collection; public class Issue400 { - final class YYPair { - // :: error: (initialization.field.uninitialized) - T first; - // :: error: (initialization.field.uninitialized) - V second; - } + final class YYPair { + // :: error: (initialization.field.uninitialized) + T first; + // :: error: (initialization.field.uninitialized) + V second; + } - class YY { - public Collection> getX() { - final Collection> out = new ArrayList>(); - out.add(new YYPair()); - return out; + class YY { + public Collection> getX() { + final Collection> out = new ArrayList>(); + out.add(new YYPair()); + return out; + } } - } } diff --git a/checker/tests/nullness-initialization/Issue408.java b/checker/tests/nullness-initialization/Issue408.java index b3229690a56..bec43dddbbe 100644 --- a/checker/tests/nullness-initialization/Issue408.java +++ b/checker/tests/nullness-initialization/Issue408.java @@ -4,27 +4,27 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class Issue408 { - static class Bar { - Bar() { - doIssue408(); - } + static class Bar { + Bar() { + doIssue408(); + } - String doIssue408(@UnderInitialization Bar this) { - return ""; + String doIssue408(@UnderInitialization Bar this) { + return ""; + } } - } - static class Baz extends Bar { - String myString = "hello"; + static class Baz extends Bar { + String myString = "hello"; - @Override - String doIssue408(@UnderInitialization Baz this) { - // :: error: (dereference.of.nullable) - return myString.toLowerCase(); + @Override + String doIssue408(@UnderInitialization Baz this) { + // :: error: (dereference.of.nullable) + return myString.toLowerCase(); + } } - } - public static void main(String[] args) { - new Baz(); - } + public static void main(String[] args) { + new Baz(); + } } diff --git a/checker/tests/nullness-initialization/KeyForValidation.java b/checker/tests/nullness-initialization/KeyForValidation.java index 12102addaed..41d74c24e3b 100644 --- a/checker/tests/nullness-initialization/KeyForValidation.java +++ b/checker/tests/nullness-initialization/KeyForValidation.java @@ -1,141 +1,142 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; public class KeyForValidation { - // :: error: (expression.unparsable.type.invalid) - // :: error: (initialization.static.field.uninitialized) - static @KeyFor("this") Object f; - - // :: error: (initialization.field.uninitialized) - @KeyFor("this") Object g; - - // :: error: (expression.unparsable.type.invalid) - void m(@KeyFor("#0") Object p) {} + // :: error: (expression.unparsable.type.invalid) + // :: error: (initialization.static.field.uninitialized) + static @KeyFor("this") Object f; - // :: error: (expression.unparsable.type.invalid) - void m2(@KeyFor("#4") Object p) {} + // :: error: (initialization.field.uninitialized) + @KeyFor("this") Object g; - // OK - void m3(@KeyFor("#2") Object p, Map m) {} + // :: error: (expression.unparsable.type.invalid) + void m(@KeyFor("#0") Object p) {} - // TODO: index for a non-map - void m4(@KeyFor("#1") Object p, Map m) {} + // :: error: (expression.unparsable.type.invalid) + void m2(@KeyFor("#4") Object p) {} - // TODO: index with wrong type - void m4(@KeyFor("#2") String p, Map m) {} + // OK + void m3(@KeyFor("#2") Object p, Map m) {} - // :: error: (expression.unparsable.type.invalid) - // :: error: (initialization.field.uninitialized) - @KeyFor("INVALID") Object h; + // TODO: index for a non-map + void m4(@KeyFor("#1") Object p, Map m) {} - // :: error: (initialization.field.uninitialized) - @KeyFor("f") Object i; + // TODO: index with wrong type + void m4(@KeyFor("#2") String p, Map m) {} - void foo(Object p) { // :: error: (expression.unparsable.type.invalid) - @KeyFor("ALSOBAD") Object j; - - @KeyFor("j") Object k; - @KeyFor("f") Object l; + // :: error: (initialization.field.uninitialized) + @KeyFor("INVALID") Object h; - @KeyFor("p") Object o; - } + // :: error: (initialization.field.uninitialized) + @KeyFor("f") Object i; - // :: error: (expression.unparsable.type.invalid) - void foo2(@KeyFor("ALSOBAD") Object o) {} + void foo(Object p) { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("ALSOBAD") Object j; - // :: error: (expression.unparsable.type.invalid) - void foo3(@KeyFor("ALSOBAD") Object[] o) {} + @KeyFor("j") Object k; + @KeyFor("f") Object l; - // :: error: (expression.unparsable.type.invalid) - void foo4(Map<@KeyFor("ALSOBAD") Object, Object> o) {} + @KeyFor("p") Object o; + } - // :: error: (expression.unparsable.type.invalid) - @KeyFor("ALSOBAD") Object[] foo5() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + void foo2(@KeyFor("ALSOBAD") Object o) {} - // :: error: (expression.unparsable.type.invalid) - @KeyFor("ALSOBAD") Object foo6() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + void foo3(@KeyFor("ALSOBAD") Object[] o) {} - // :: error: (expression.unparsable.type.invalid) - Map<@KeyFor("ALSOBAD") Object, Object> foo7() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + void foo4(Map<@KeyFor("ALSOBAD") Object, Object> o) {} - // :: error: (expression.unparsable.type.invalid) - <@KeyFor("ALSOBAD") T> void foo8() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + @KeyFor("ALSOBAD") Object[] foo5() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - <@KeyForBottom T extends @KeyFor("ALSOBAD") Object> void foo9() {} + // :: error: (expression.unparsable.type.invalid) + @KeyFor("ALSOBAD") Object foo6() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - void foo10(@KeyFor("ALSOBAD") KeyForValidation this) {} + // :: error: (expression.unparsable.type.invalid) + Map<@KeyFor("ALSOBAD") Object, Object> foo7() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - public void test(Set<@KeyFor("BAD") String> keySet) { // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("BAD") String>(keySet); + <@KeyFor("ALSOBAD") T> void foo8() { + throw new RuntimeException(); + } + // :: error: (expression.unparsable.type.invalid) - List<@KeyFor("BAD") String> list = new ArrayList<>(); + <@KeyForBottom T extends @KeyFor("ALSOBAD") Object> void foo9() {} // :: error: (expression.unparsable.type.invalid) - for (@KeyFor("BAD") String s : list) {} - } + void foo10(@KeyFor("ALSOBAD") KeyForValidation this) {} - // Test static context. + // :: error: (expression.unparsable.type.invalid) + public void test(Set<@KeyFor("BAD") String> keySet) { + // :: error: (expression.unparsable.type.invalid) + new ArrayList<@KeyFor("BAD") String>(keySet); + // :: error: (expression.unparsable.type.invalid) + List<@KeyFor("BAD") String> list = new ArrayList<>(); - Object instanceField = new Object(); + // :: error: (expression.unparsable.type.invalid) + for (@KeyFor("BAD") String s : list) {} + } - // :: error: (expression.unparsable.type.invalid) - static void bar2(@KeyFor("this.instanceField") Object o) {} + // Test static context. - // :: error: (expression.unparsable.type.invalid) - static void bar3(@KeyFor("this.instanceField") Object[] o) {} + Object instanceField = new Object(); - // :: error: (expression.unparsable.type.invalid) - static void bar4(Map<@KeyFor("this.instanceField") Object, Object> o) {} + // :: error: (expression.unparsable.type.invalid) + static void bar2(@KeyFor("this.instanceField") Object o) {} - // :: error: (expression.unparsable.type.invalid) - static @KeyFor("this.instanceField") Object[] bar5() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static void bar3(@KeyFor("this.instanceField") Object[] o) {} - // :: error: (expression.unparsable.type.invalid) - static @KeyFor("this.instanceField") Object bar6() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static void bar4(Map<@KeyFor("this.instanceField") Object, Object> o) {} - // :: error: (expression.unparsable.type.invalid) - static Map<@KeyFor("this.instanceField") Object, Object> bar7() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static @KeyFor("this.instanceField") Object[] bar5() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - static <@KeyFor("this.instanceField") T> void bar8() { - throw new RuntimeException(); - } + // :: error: (expression.unparsable.type.invalid) + static @KeyFor("this.instanceField") Object bar6() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - static <@KeyForBottom T extends @KeyFor("this.instanceField") Object> void bar9() {} + // :: error: (expression.unparsable.type.invalid) + static Map<@KeyFor("this.instanceField") Object, Object> bar7() { + throw new RuntimeException(); + } - // :: error: (expression.unparsable.type.invalid) - public static void test2(Set<@KeyFor("this.instanceField") String> keySet) { // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("this.instanceField") String>(keySet); + static <@KeyFor("this.instanceField") T> void bar8() { + throw new RuntimeException(); + } + // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("this.instanceField") String>(); + static <@KeyForBottom T extends @KeyFor("this.instanceField") Object> void bar9() {} - List list = new ArrayList<>(); - // :: error: (enhancedfor.type.incompatible) :: error: (expression.unparsable.type.invalid) - for (@KeyFor("this.instanceField") String s : list) {} - } + // :: error: (expression.unparsable.type.invalid) + public static void test2(Set<@KeyFor("this.instanceField") String> keySet) { + // :: error: (expression.unparsable.type.invalid) + new ArrayList<@KeyFor("this.instanceField") String>(keySet); + // :: error: (expression.unparsable.type.invalid) + new ArrayList<@KeyFor("this.instanceField") String>(); + + List list = new ArrayList<>(); + // :: error: (enhancedfor.type.incompatible) :: error: (expression.unparsable.type.invalid) + for (@KeyFor("this.instanceField") String s : list) {} + } } diff --git a/checker/tests/nullness-initialization/Listener.java b/checker/tests/nullness-initialization/Listener.java index 01eb9a20650..a83ee5cd2f0 100644 --- a/checker/tests/nullness-initialization/Listener.java +++ b/checker/tests/nullness-initialization/Listener.java @@ -2,27 +2,27 @@ public class Listener { - @NonNull String f; + @NonNull String f; - public Listener() { - Talker w = new Talker(); - // :: error: (argument.type.incompatible) - w.register(this); + public Listener() { + Talker w = new Talker(); + // :: error: (argument.type.incompatible) + w.register(this); - f = "abc"; - } + f = "abc"; + } - public void callback() { - System.out.println(f.toLowerCase()); - } + public void callback() { + System.out.println(f.toLowerCase()); + } - public static class Talker { - public void register(Listener s) { - s.callback(); + public static class Talker { + public void register(Listener s) { + s.callback(); + } } - } - public static void main(String[] args) { - new Listener(); - } + public static void main(String[] args) { + new Listener(); + } } diff --git a/checker/tests/nullness-initialization/MethodInvocation.java b/checker/tests/nullness-initialization/MethodInvocation.java index 3127d1a837b..5625f95e9ca 100644 --- a/checker/tests/nullness-initialization/MethodInvocation.java +++ b/checker/tests/nullness-initialization/MethodInvocation.java @@ -3,31 +3,31 @@ public class MethodInvocation { - String s; + String s; - public MethodInvocation() { - // :: error: (method.invocation.invalid) - a(); - b(); - c(); - s = "abc"; - } + public MethodInvocation() { + // :: error: (method.invocation.invalid) + a(); + b(); + c(); + s = "abc"; + } - public MethodInvocation(boolean p) { - // :: error: (method.invocation.invalid) - a(); // still not okay to be initialized - s = "abc"; - } + public MethodInvocation(boolean p) { + // :: error: (method.invocation.invalid) + a(); // still not okay to be initialized + s = "abc"; + } - public void a() {} + public void a() {} - public void b(@UnderInitialization MethodInvocation this) { - // :: error: (dereference.of.nullable) - s.hashCode(); - } + public void b(@UnderInitialization MethodInvocation this) { + // :: error: (dereference.of.nullable) + s.hashCode(); + } - public void c(@UnknownInitialization MethodInvocation this) { - // :: error: (dereference.of.nullable) - s.hashCode(); - } + public void c(@UnknownInitialization MethodInvocation this) { + // :: error: (dereference.of.nullable) + s.hashCode(); + } } diff --git a/checker/tests/nullness-initialization/MultiConstructorInit.java b/checker/tests/nullness-initialization/MultiConstructorInit.java index a0091a702e7..782a476533b 100644 --- a/checker/tests/nullness-initialization/MultiConstructorInit.java +++ b/checker/tests/nullness-initialization/MultiConstructorInit.java @@ -3,25 +3,25 @@ public class MultiConstructorInit { - String a; + String a; - public MultiConstructorInit(boolean t) { - a = ""; - } + public MultiConstructorInit(boolean t) { + a = ""; + } - public MultiConstructorInit() { - this(true); - } + public MultiConstructorInit() { + this(true); + } - // :: error: (initialization.fields.uninitialized) - public MultiConstructorInit(int t) { - new MultiConstructorInit(); - } + // :: error: (initialization.fields.uninitialized) + public MultiConstructorInit(int t) { + new MultiConstructorInit(); + } - // :: error: (initialization.fields.uninitialized) - public MultiConstructorInit(float t) {} + // :: error: (initialization.fields.uninitialized) + public MultiConstructorInit(float t) {} - public static void main(String[] args) { - new MultiConstructorInit(); - } + public static void main(String[] args) { + new MultiConstructorInit(); + } } diff --git a/checker/tests/nullness-initialization/ObjectArrayParam.java b/checker/tests/nullness-initialization/ObjectArrayParam.java index c7ed7f1d911..cf70acde51d 100644 --- a/checker/tests/nullness-initialization/ObjectArrayParam.java +++ b/checker/tests/nullness-initialization/ObjectArrayParam.java @@ -2,11 +2,11 @@ import org.checkerframework.checker.nullness.qual.*; class ObjectArrayParam { - void test(@UnknownInitialization Object... args) { - for (Object obj : args) { - boolean isClass = obj instanceof Class; - // :: warning: (cast.unsafe) - @Initialized @NonNull Class clazz = (isClass ? (@Initialized @NonNull Class) obj : obj.getClass()); + void test(@UnknownInitialization Object... args) { + for (Object obj : args) { + boolean isClass = obj instanceof Class; + // :: warning: (cast.unsafe) + @Initialized @NonNull Class clazz = (isClass ? (@Initialized @NonNull Class) obj : obj.getClass()); + } } - } } diff --git a/checker/tests/nullness-initialization/ObjectListParam.java b/checker/tests/nullness-initialization/ObjectListParam.java index d711c9fb6de..c2c28d0ad7e 100644 --- a/checker/tests/nullness-initialization/ObjectListParam.java +++ b/checker/tests/nullness-initialization/ObjectListParam.java @@ -1,14 +1,15 @@ -import java.util.List; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + class ObjectListParam { - // :: error: type.argument.type.incompatible - void test(List<@UnknownInitialization Object> args) { - for (Object obj : args) { - boolean isClass = obj instanceof Class; - // :: warning: (cast.unsafe) - @Initialized Class clazz = (isClass ? (@Initialized Class) obj : obj.getClass()); + // :: error: type.argument.type.incompatible + void test(List<@UnknownInitialization Object> args) { + for (Object obj : args) { + boolean isClass = obj instanceof Class; + // :: warning: (cast.unsafe) + @Initialized Class clazz = (isClass ? (@Initialized Class) obj : obj.getClass()); + } } - } } diff --git a/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java b/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java index 9611dd3a7cd..270e8a87c89 100644 --- a/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java +++ b/checker/tests/nullness-initialization/PrivateMethodUnknownInit.java @@ -2,17 +2,17 @@ public class PrivateMethodUnknownInit { - int x; + int x; - public PrivateMethodUnknownInit() { - x = 1; - m1(); - // :: error: (method.invocation.invalid) - m2(); - } + public PrivateMethodUnknownInit() { + x = 1; + m1(); + // :: error: (method.invocation.invalid) + m2(); + } - private void m1( - @UnknownInitialization(PrivateMethodUnknownInit.class) PrivateMethodUnknownInit this) {} + private void m1( + @UnknownInitialization(PrivateMethodUnknownInit.class) PrivateMethodUnknownInit this) {} - public void m2() {} + public void m2() {} } diff --git a/checker/tests/nullness-initialization/Raw2.java b/checker/tests/nullness-initialization/Raw2.java index 00fb53439df..f9b46115c77 100644 --- a/checker/tests/nullness-initialization/Raw2.java +++ b/checker/tests/nullness-initialization/Raw2.java @@ -2,30 +2,30 @@ import org.checkerframework.checker.nullness.qual.*; public class Raw2 { - private @NonNull Object field; + private @NonNull Object field; - // :: error: (initialization.fields.uninitialized) - public Raw2(int i) { - this.method(this); - } + // :: error: (initialization.fields.uninitialized) + public Raw2(int i) { + this.method(this); + } - public Raw2() { - try { - this.method(this); - } catch (NullPointerException e) { - e.printStackTrace(); + public Raw2() { + try { + this.method(this); + } catch (NullPointerException e) { + e.printStackTrace(); + } + field = 0L; } - field = 0L; - } - private void method(@UnknownInitialization Raw2 this, @UnknownInitialization Raw2 arg) { - // :: error: (dereference.of.nullable) - arg.field.hashCode(); - // :: error: (dereference.of.nullable) - this.field.hashCode(); - } + private void method(@UnknownInitialization Raw2 this, @UnknownInitialization Raw2 arg) { + // :: error: (dereference.of.nullable) + arg.field.hashCode(); + // :: error: (dereference.of.nullable) + this.field.hashCode(); + } - public static void test() { - new Raw2(); - } + public static void test() { + new Raw2(); + } } diff --git a/checker/tests/nullness-initialization/RawField.java b/checker/tests/nullness-initialization/RawField.java index f11ff1fcb44..e6be359f3d3 100644 --- a/checker/tests/nullness-initialization/RawField.java +++ b/checker/tests/nullness-initialization/RawField.java @@ -5,49 +5,49 @@ // see https://github.com/typetools/checker-framework/issues/223 class RawField { - public @UnknownInitialization RawField a; + public @UnknownInitialization RawField a; - public RawField() { - // :: error: (assignment.type.incompatible) - a = null; - this.a = this; - a = this; - } + public RawField() { + // :: error: (assignment.type.incompatible) + a = null; + this.a = this; + a = this; + } - // :: error: (initialization.fields.uninitialized) - public RawField(boolean foo) {} + // :: error: (initialization.fields.uninitialized) + public RawField(boolean foo) {} - void t1() { - // :: error: (method.invocation.invalid) - a.t1(); - } + void t1() { + // :: error: (method.invocation.invalid) + a.t1(); + } - void t2(@UnknownInitialization RawField a) { - this.a = a; - } + void t2(@UnknownInitialization RawField a) { + this.a = a; + } } class Options { - @UnknownInitialization Object arg; + @UnknownInitialization Object arg; - public Options(@UnknownInitialization Object arg) { - this.arg = arg; - } + public Options(@UnknownInitialization Object arg) { + this.arg = arg; + } - public void parse_or_usage() { - // use arg only under the assumption that it is @UnknownInitialization - } + public void parse_or_usage() { + // use arg only under the assumption that it is @UnknownInitialization + } } class MultiVersionControl { - @SuppressWarnings( - "initialization") // see https://github.com/typetools/checker-framework/issues/223 - public void parseArgs(@UnknownInitialization MultiVersionControl this) { - Options options = new Options(this); - options.parse_or_usage(); - } + @SuppressWarnings( + "initialization") // see https://github.com/typetools/checker-framework/issues/223 + public void parseArgs(@UnknownInitialization MultiVersionControl this) { + Options options = new Options(this); + options.parse_or_usage(); + } } // TODO: This checks that forbidden field assignments do not occur. (The diff --git a/checker/tests/nullness-initialization/RawMethodInvocation.java b/checker/tests/nullness-initialization/RawMethodInvocation.java index 83a781c69c3..761d16893d3 100644 --- a/checker/tests/nullness-initialization/RawMethodInvocation.java +++ b/checker/tests/nullness-initialization/RawMethodInvocation.java @@ -4,36 +4,36 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class RawMethodInvocation { - Object a; - Object b; + Object a; + Object b; - RawMethodInvocation(boolean constructor_inits_a) { - a = 1; - init_b(); - } + RawMethodInvocation(boolean constructor_inits_a) { + a = 1; + init_b(); + } - @EnsuresNonNull("b") - void init_b(@UnknownInitialization RawMethodInvocation this) { - b = 2; - } + @EnsuresNonNull("b") + void init_b(@UnknownInitialization RawMethodInvocation this) { + b = 2; + } - RawMethodInvocation(int constructor_inits_none) { - init_ab(); - } + RawMethodInvocation(int constructor_inits_none) { + init_ab(); + } - @EnsuresNonNull({"a", "b"}) - void init_ab(@UnknownInitialization RawMethodInvocation this) { - a = 1; - b = 2; - } + @EnsuresNonNull({"a", "b"}) + void init_ab(@UnknownInitialization RawMethodInvocation this) { + a = 1; + b = 2; + } - RawMethodInvocation(long constructor_escapes_raw) { - a = 1; - // this call is not valid, this is still raw - // :: error: (method.invocation.invalid) - nonRawMethod(); - b = 2; - } + RawMethodInvocation(long constructor_escapes_raw) { + a = 1; + // this call is not valid, this is still raw + // :: error: (method.invocation.invalid) + nonRawMethod(); + b = 2; + } - void nonRawMethod() {} + void nonRawMethod() {} } diff --git a/checker/tests/nullness-initialization/RawTypesBounded.java b/checker/tests/nullness-initialization/RawTypesBounded.java index 84cbf54219a..1edb3c1f12e 100644 --- a/checker/tests/nullness-initialization/RawTypesBounded.java +++ b/checker/tests/nullness-initialization/RawTypesBounded.java @@ -6,228 +6,228 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class RawTypesBounded { - class Bad { - @NonNull String field; - - public Bad() { - // :: error: (method.invocation.invalid) - this.init(); // error - // :: error: (method.invocation.invalid) - init(); // error - - this.field = "field"; // valid - // :: error: (assignment.type.incompatible) - this.field = null; // error - field = "field"; // valid - // :: error: (assignment.type.incompatible) - field = null; // error - } + class Bad { + @NonNull String field; + + public Bad() { + // :: error: (method.invocation.invalid) + this.init(); // error + // :: error: (method.invocation.invalid) + init(); // error + + this.field = "field"; // valid + // :: error: (assignment.type.incompatible) + this.field = null; // error + field = "field"; // valid + // :: error: (assignment.type.incompatible) + field = null; // error + } + + void init() { + output(this.field.length()); // valid + } + } + + class A { + @NonNull String field; + + public A() { + this.field = "field"; // valid + field = "field"; // valid + this.init(); // valid + init(); // valid + } + + public void init(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); + } + + public void initExpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(this.field); + } + + public void initImpl1(@UnknownInitialization A this) { + // :: error: (dereference.of.nullable) + output(field.length()); + } - void init() { - output(this.field.length()); // valid + public void initImpl2(@UnknownInitialization A this) { + // :: error: (argument.type.incompatible) + output(field); + } } - } - class A { - @NonNull String field; + class B extends A { + @NonNull String otherField; - public A() { - this.field = "field"; // valid - field = "field"; // valid - this.init(); // valid - init(); // valid - } + public B() { + super(); + // :: error: (assignment.type.incompatible) + this.otherField = null; // error + this.otherField = "otherField"; // valid + } - public void init(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); - } + @Override + public void init(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.field.length()); // error (TODO: substitution) + super.init(); // valid + } - public void initExpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(this.field); - } + public void initImpl1(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(field.length()); // error (TODO: substitution) + } - public void initImpl1(@UnknownInitialization A this) { - // :: error: (dereference.of.nullable) - output(field.length()); - } + public void initExpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(this.otherField.length()); // error + } - public void initImpl2(@UnknownInitialization A this) { - // :: error: (argument.type.incompatible) - output(field); - } - } + public void initImpl2(@UnknownInitialization B this) { + // :: error: (dereference.of.nullable) + output(otherField.length()); // error + } - class B extends A { - @NonNull String otherField; + void other() { + init(); // valid + this.init(); // valid + } - public B() { - super(); - // :: error: (assignment.type.incompatible) - this.otherField = null; // error - this.otherField = "otherField"; // valid + void otherRaw(@UnknownInitialization B this) { + init(); // valid + this.init(); // valid + } } - @Override - public void init(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.field.length()); // error (TODO: substitution) - super.init(); // valid - } + class C extends B { - public void initImpl1(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(field.length()); // error (TODO: substitution) - } - - public void initExpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(this.otherField.length()); // error - } + @NonNull String[] strings; - public void initImpl2(@UnknownInitialization B this) { - // :: error: (dereference.of.nullable) - output(otherField.length()); // error + @Override + public void init(@UnknownInitialization C this) { + // :: error: (dereference.of.nullable) + output(this.strings.length); // error + System.out.println(); // valid + } } - void other() { - init(); // valid - this.init(); // valid - } + // To test whether the argument is @NonNull and @Initialized + static void output(@NonNull Object o) {} - void otherRaw(@UnknownInitialization B this) { - init(); // valid - this.init(); // valid + class D extends C { + @Override + public void init(@UnknownInitialization D this) { + this.field = "s"; + output(this.field.length()); + } } - } - class C extends B { + class MyTest { + int i; - @NonNull String[] strings; + MyTest(int i) { + this.i = i; + } - @Override - public void init(@UnknownInitialization C this) { - // :: error: (dereference.of.nullable) - output(this.strings.length); // error - System.out.println(); // valid + void myTest(@UnknownInitialization MyTest this) { + i++; + } } - } - - // To test whether the argument is @NonNull and @Initialized - static void output(@NonNull Object o) {} - class D extends C { - @Override - public void init(@UnknownInitialization D this) { - this.field = "s"; - output(this.field.length()); - } - } + class AllFieldsInitialized { + long elapsedMillis = 0; + long startTime = 0; - class MyTest { - int i; + // If all fields have an initializer, then the type of "this" + // should still not be non-raw (there might be uninitilized subclasses) + public AllFieldsInitialized() { + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - MyTest(int i) { - this.i = i; + public void nonRawMethod() {} } - void myTest(@UnknownInitialization MyTest this) { - i++; + class AFSIICell { + AllFieldsSetInInitializer afsii; } - } - class AllFieldsInitialized { - long elapsedMillis = 0; - long startTime = 0; + class AllFieldsSetInInitializer { + long elapsedMillis; + long startTime; - // If all fields have an initializer, then the type of "this" - // should still not be non-raw (there might be uninitilized subclasses) - public AllFieldsInitialized() { - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - public void nonRawMethod() {} - } - - class AFSIICell { - AllFieldsSetInInitializer afsii; - } + public AllFieldsSetInInitializer() { + elapsedMillis = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); + startTime = 0; + // :: error: (method.invocation.invalid) + nonRawMethod(); // still error (subclasses...) + } - class AllFieldsSetInInitializer { - long elapsedMillis; - long startTime; + public AllFieldsSetInInitializer(boolean b) { + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - public AllFieldsSetInInitializer() { - elapsedMillis = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); - startTime = 0; - // :: error: (method.invocation.invalid) - nonRawMethod(); // still error (subclasses...) + public void nonRawMethod() {} } - public AllFieldsSetInInitializer(boolean b) { - // :: error: (method.invocation.invalid) - nonRawMethod(); - } - - public void nonRawMethod() {} - } + class ConstructorInvocations { + int v; - class ConstructorInvocations { - int v; + public ConstructorInvocations(int v) { + this.v = v; + } - public ConstructorInvocations(int v) { - this.v = v; - } + public ConstructorInvocations() { + this(0); + // :: error: (method.invocation.invalid) + nonRawMethod(); + } - public ConstructorInvocations() { - this(0); - // :: error: (method.invocation.invalid) - nonRawMethod(); + public void nonRawMethod() {} } - public void nonRawMethod() {} - } + class MethodAccess { + public MethodAccess() { + @NonNull String s = string(); + } - class MethodAccess { - public MethodAccess() { - @NonNull String s = string(); + public @NonNull String string(@UnknownInitialization MethodAccess this) { + return "nonnull"; + } } - public @NonNull String string(@UnknownInitialization MethodAccess this) { - return "nonnull"; - } - } - - void cast(@UnknownInitialization Object... args) { + void cast(@UnknownInitialization Object... args) { - @SuppressWarnings("rawtypes") - // :: error: (assignment.type.incompatible) - Object[] argsNonRaw1 = args; + @SuppressWarnings("rawtypes") + // :: error: (assignment.type.incompatible) + Object[] argsNonRaw1 = args; - @SuppressWarnings("cast") - Object[] argsNonRaw2 = (Object[]) args; - } + @SuppressWarnings("cast") + Object[] argsNonRaw2 = (Object[]) args; + } - // default qualifier is @Nullable, so this is OK. - class RawAfterConstructorBad { - Object o; + // default qualifier is @Nullable, so this is OK. + class RawAfterConstructorBad { + Object o; - RawAfterConstructorBad() {} - } + RawAfterConstructorBad() {} + } - class RawAfterConstructorOK1 { - @Nullable Object o; + class RawAfterConstructorOK1 { + @Nullable Object o; - RawAfterConstructorOK1() {} - } + RawAfterConstructorOK1() {} + } - class RawAfterConstructorOK2 { - int a; + class RawAfterConstructorOK2 { + int a; - RawAfterConstructorOK2() {} - } + RawAfterConstructorOK2() {} + } } diff --git a/checker/tests/nullness-initialization/Simple2.java b/checker/tests/nullness-initialization/Simple2.java index 02f299f372a..dbeb07b9b0e 100644 --- a/checker/tests/nullness-initialization/Simple2.java +++ b/checker/tests/nullness-initialization/Simple2.java @@ -2,26 +2,26 @@ public class Simple2 { - @NonNull String f; + @NonNull String f; - public Simple2() { - // :: error: (method.invocation.invalid) - test(); + public Simple2() { + // :: error: (method.invocation.invalid) + test(); - f = "abc"; - } + f = "abc"; + } - public void test() { - System.out.println(f.toLowerCase()); - } + public void test() { + System.out.println(f.toLowerCase()); + } - public void a(Simple2 arg) { - @Nullable String s = null; - // :: error: (dereference.of.nullable) - s.hashCode(); - } + public void a(Simple2 arg) { + @Nullable String s = null; + // :: error: (dereference.of.nullable) + s.hashCode(); + } - public static void main(String[] args) { - new Simple2(); - } + public static void main(String[] args) { + new Simple2(); + } } diff --git a/checker/tests/nullness-initialization/StaticInitialization.java b/checker/tests/nullness-initialization/StaticInitialization.java index b11f4e58f4c..b58c2ed138b 100644 --- a/checker/tests/nullness-initialization/StaticInitialization.java +++ b/checker/tests/nullness-initialization/StaticInitialization.java @@ -3,9 +3,9 @@ public class StaticInitialization { - @SuppressWarnings({"nullness", "initialization.fields.uninitialized"}) - public static Object dontWarnAboutThisField; + @SuppressWarnings({"nullness", "initialization.fields.uninitialized"}) + public static Object dontWarnAboutThisField; - static { - } + static { + } } diff --git a/checker/tests/nullness-initialization/StaticInitializer.java b/checker/tests/nullness-initialization/StaticInitializer.java index 5d8767964fe..6c6b5182a88 100644 --- a/checker/tests/nullness-initialization/StaticInitializer.java +++ b/checker/tests/nullness-initialization/StaticInitializer.java @@ -3,58 +3,58 @@ public class StaticInitializer { - public static String a; - // :: error: (initialization.static.field.uninitialized) - public static String b; + public static String a; + // :: error: (initialization.static.field.uninitialized) + public static String b; - static { - a = ""; - } + static { + a = ""; + } - public StaticInitializer() {} + public StaticInitializer() {} } class StaticInitializer2 { - // :: error: (initialization.static.field.uninitialized) - public static String a; - // :: error: (initialization.static.field.uninitialized) - public static String b; + // :: error: (initialization.static.field.uninitialized) + public static String a; + // :: error: (initialization.static.field.uninitialized) + public static String b; } class StaticInitializer3 { - public static String a = ""; + public static String a = ""; } class StaticInitializer4 { - public static String a = ""; - public static String b; + public static String a = ""; + public static String b; - static { - b = ""; - } + static { + b = ""; + } } class StaticInitializer5 { - public static String a = ""; + public static String a = ""; - static { - a.toString(); - } + static { + a.toString(); + } - public static String b = ""; + public static String b = ""; } class StaticInitializer6 { - public static String a = ""; + public static String a = ""; - public static String b; + public static String b; - static { - // TODO error expected. See #556. - b.toString(); - } + static { + // TODO error expected. See #556. + b.toString(); + } - static { - b = ""; - } + static { + b = ""; + } } diff --git a/checker/tests/nullness-initialization/SuperConstructorInit.java b/checker/tests/nullness-initialization/SuperConstructorInit.java index dee48326795..aa863fb05f9 100644 --- a/checker/tests/nullness-initialization/SuperConstructorInit.java +++ b/checker/tests/nullness-initialization/SuperConstructorInit.java @@ -2,19 +2,19 @@ public class SuperConstructorInit { - String a; + String a; - public SuperConstructorInit() { - a = ""; - } + public SuperConstructorInit() { + a = ""; + } - public static class B extends SuperConstructorInit { - String b; + public static class B extends SuperConstructorInit { + String b; - // :: error: (initialization.fields.uninitialized) - public B() { - super(); - a.toString(); + // :: error: (initialization.fields.uninitialized) + public B() { + super(); + a.toString(); + } } - } } diff --git a/checker/tests/nullness-initialization/Suppression.java b/checker/tests/nullness-initialization/Suppression.java index e182567a79b..93ea66a4f30 100644 --- a/checker/tests/nullness-initialization/Suppression.java +++ b/checker/tests/nullness-initialization/Suppression.java @@ -1,24 +1,24 @@ public class Suppression { - Object f; + Object f; - @SuppressWarnings("nullnessnoinit") - void test() { - String a = null; - a.toString(); - } + @SuppressWarnings("nullnessnoinit") + void test() { + String a = null; + a.toString(); + } - @SuppressWarnings("initialization") - void test2() { - String a = null; - // :: error: (dereference.of.nullable) - a.toString(); - } + @SuppressWarnings("initialization") + void test2() { + String a = null; + // :: error: (dereference.of.nullable) + a.toString(); + } - @SuppressWarnings("nullness") - Suppression() {} + @SuppressWarnings("nullness") + Suppression() {} - @SuppressWarnings("nullnessnoinit") - // :: error: (initialization.fields.uninitialized) - Suppression(int dummy) {} + @SuppressWarnings("nullnessnoinit") + // :: error: (initialization.fields.uninitialized) + Suppression(int dummy) {} } diff --git a/checker/tests/nullness-initialization/ThisNodeTest.java b/checker/tests/nullness-initialization/ThisNodeTest.java index d7ea48f2c25..1bb39b13975 100644 --- a/checker/tests/nullness-initialization/ThisNodeTest.java +++ b/checker/tests/nullness-initialization/ThisNodeTest.java @@ -2,20 +2,20 @@ import org.checkerframework.checker.nullness.qual.*; public class ThisNodeTest { - public ThisNodeTest() { - new Object() { - void test() { - @UnderInitialization ThisNodeTest l1 = ThisNodeTest.this; - // :: error: (assignment.type.incompatible) - @Initialized ThisNodeTest l2 = ThisNodeTest.this; + public ThisNodeTest() { + new Object() { + void test() { + @UnderInitialization ThisNodeTest l1 = ThisNodeTest.this; + // :: error: (assignment.type.incompatible) + @Initialized ThisNodeTest l2 = ThisNodeTest.this; - // :: error: (method.invocation.invalid) - ThisNodeTest.this.foo(); - // :: error: (method.invocation.invalid) - foo(); - } - }; - } + // :: error: (method.invocation.invalid) + ThisNodeTest.this.foo(); + // :: error: (method.invocation.invalid) + foo(); + } + }; + } - void foo() {} + void foo() {} } diff --git a/checker/tests/nullness-initialization/Throwing.java b/checker/tests/nullness-initialization/Throwing.java index 63e43bb5950..96b49e30070 100644 --- a/checker/tests/nullness-initialization/Throwing.java +++ b/checker/tests/nullness-initialization/Throwing.java @@ -2,21 +2,21 @@ public class Throwing { - String a; + String a; - // :: error: (initialization.fields.uninitialized) - public Throwing(boolean throwError) { - if (throwError) { - throw new RuntimeException("not a real error"); + // :: error: (initialization.fields.uninitialized) + public Throwing(boolean throwError) { + if (throwError) { + throw new RuntimeException("not a real error"); + } } - } - // :: error: (initialization.fields.uninitialized) - public Throwing(int input) { - try { - throw new RuntimeException("not a real error"); - } catch (RuntimeException e) { - // do nothing + // :: error: (initialization.fields.uninitialized) + public Throwing(int input) { + try { + throw new RuntimeException("not a real error"); + } catch (RuntimeException e) { + // do nothing + } } - } } diff --git a/checker/tests/nullness-initialization/TryCatch.java b/checker/tests/nullness-initialization/TryCatch.java index 2ed160d8407..6d8d9032849 100644 --- a/checker/tests/nullness-initialization/TryCatch.java +++ b/checker/tests/nullness-initialization/TryCatch.java @@ -1,41 +1,42 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.io.*; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; class EntryReader { - public EntryReader() throws IOException {} + public EntryReader() throws IOException {} } public class TryCatch { - void constructorException() throws IOException { - List file_errors = new ArrayList<>(); - try { - new EntryReader(); - } catch (FileNotFoundException e) { - file_errors.add(e); + void constructorException() throws IOException { + List file_errors = new ArrayList<>(); + try { + new EntryReader(); + } catch (FileNotFoundException e) { + file_errors.add(e); + } } - } - void unreachableCatch(String[] xs) { - String t = ""; - t.toString(); - try { - } catch (Throwable e) { - // Note that this code is dead. - // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) - t.toString(); + void unreachableCatch(String[] xs) { + String t = ""; + t.toString(); + try { + } catch (Throwable e) { + // Note that this code is dead. + // :: error: (dereference.of.nullable) :: error: (method.invocation.invalid) + t.toString(); + } } - } - void noClassDefFoundError(@Nullable Object x) { - try { - Class cls = EntryReader.class; - } catch (NoClassDefFoundError e) { - if (x != null) { - // OK - x.toString(); - } + void noClassDefFoundError(@Nullable Object x) { + try { + Class cls = EntryReader.class; + } catch (NoClassDefFoundError e) { + if (x != null) { + // OK + x.toString(); + } + } } - } } diff --git a/checker/tests/nullness-initialization/TwoStaticInitBlocks.java b/checker/tests/nullness-initialization/TwoStaticInitBlocks.java index 92657676bab..18873846461 100644 --- a/checker/tests/nullness-initialization/TwoStaticInitBlocks.java +++ b/checker/tests/nullness-initialization/TwoStaticInitBlocks.java @@ -4,42 +4,42 @@ // initializers and a few other things public class TwoStaticInitBlocks { - String f2; - String f1 = (f2 = ""); - - { - t = ""; - f1.toString(); - f2.toString(); - } - - final String ws_regexp; - String t; - String s; - - { - ws_regexp = "hello"; - t.toString(); - // :: error: (dereference.of.nullable) - s.toString(); - } + String f2; + String f1 = (f2 = ""); + + { + t = ""; + f1.toString(); + f2.toString(); + } + + final String ws_regexp; + String t; + String s; + + { + ws_regexp = "hello"; + t.toString(); + // :: error: (dereference.of.nullable) + s.toString(); + } } class TwoStaticInitBlocks2 { - static String f2; - static String f1 = (f2 = ""); - - static { - t = ""; - f1.toString(); - f2.toString(); - } - - static final String ws_regexp; - static String t; - - static { - ws_regexp = "hello"; - t.toString(); - } + static String f2; + static String f1 = (f2 = ""); + + static { + t = ""; + f1.toString(); + f2.toString(); + } + + static final String ws_regexp; + static String t; + + static { + ws_regexp = "hello"; + t.toString(); + } } diff --git a/checker/tests/nullness-initialization/ValidType.java b/checker/tests/nullness-initialization/ValidType.java index 0c0889f1dba..f98a5efb3ce 100644 --- a/checker/tests/nullness-initialization/ValidType.java +++ b/checker/tests/nullness-initialization/ValidType.java @@ -3,10 +3,10 @@ public class ValidType { - void t1() { - // :: error: (type.invalid.conflicting.annos) - @NonNull @Nullable String l1; - // :: error: (type.invalid.conflicting.annos) - @UnderInitialization @UnknownInitialization String f; - } + void t1() { + // :: error: (type.invalid.conflicting.annos) + @NonNull @Nullable String l1; + // :: error: (type.invalid.conflicting.annos) + @UnderInitialization @UnknownInitialization String f; + } } diff --git a/checker/tests/nullness-initialization/VarInfoName.java b/checker/tests/nullness-initialization/VarInfoName.java index 449f2c4b382..63c15569aa6 100644 --- a/checker/tests/nullness-initialization/VarInfoName.java +++ b/checker/tests/nullness-initialization/VarInfoName.java @@ -1,15 +1,15 @@ public abstract class VarInfoName { - public abstract T accept(Visitor v); + public abstract T accept(Visitor v); - public abstract static class Visitor {} + public abstract static class Visitor {} - public abstract static class BooleanAndVisitor extends Visitor { - private boolean result; + public abstract static class BooleanAndVisitor extends Visitor { + private boolean result; - public BooleanAndVisitor(VarInfoName name) { - // :: error: (argument.type.incompatible) :: warning: (nulltest.redundant) - result = (name.accept(this) != null); + public BooleanAndVisitor(VarInfoName name) { + // :: error: (argument.type.incompatible) :: warning: (nulltest.redundant) + result = (name.accept(this) != null); + } } - } } diff --git a/checker/tests/nullness-initialization/Wellformed.java b/checker/tests/nullness-initialization/Wellformed.java index 2aa83b2d16a..e7a0a59733a 100644 --- a/checker/tests/nullness-initialization/Wellformed.java +++ b/checker/tests/nullness-initialization/Wellformed.java @@ -1,69 +1,69 @@ import org.checkerframework.checker.nullness.qual.*; public class Wellformed { - // :: error: (type.invalid.conflicting.annos) - @NonNull @Nullable Object f = null; - - // :: error: (type.invalid.conflicting.annos) - class Gen1a {} - - class Gen1b { // :: error: (type.invalid.conflicting.annos) - void m(T p) {} + @NonNull @Nullable Object f = null; // :: error: (type.invalid.conflicting.annos) - <@NonNull @Nullable T> void m2(T p) {} - } + class Gen1a {} + + class Gen1b { + // :: error: (type.invalid.conflicting.annos) + void m(T p) {} + + // :: error: (type.invalid.conflicting.annos) + <@NonNull @Nullable T> void m2(T p) {} + } - // :: error: (type.invalid.conflicting.annos) - class Gen1c<@NonNull @Nullable TTT> {} + // :: error: (type.invalid.conflicting.annos) + class Gen1c<@NonNull @Nullable TTT> {} - class Gen2a<@Nullable T> {} + class Gen2a<@Nullable T> {} - // :: error: (bound.type.incompatible) - class Gen2b<@Nullable T extends Object> {} + // :: error: (bound.type.incompatible) + class Gen2b<@Nullable T extends Object> {} - // :: error: (bound.type.incompatible) - class Gen2c<@Nullable T extends @NonNull Object> {} + // :: error: (bound.type.incompatible) + class Gen2c<@Nullable T extends @NonNull Object> {} - class Gen3a { - @Nullable T f; + class Gen3a { + @Nullable T f; - @Nullable T get() { - return null; + @Nullable T get() { + return null; + } } - } - class Gen3b { - @Nullable T f; + class Gen3b { + @Nullable T f; - @Nullable T get() { - return null; + @Nullable T get() { + return null; + } } - } - class Gen4 { - // :: error: (initialization.field.uninitialized) - @NonNull T f; + class Gen4 { + // :: error: (initialization.field.uninitialized) + @NonNull T f; - @NonNull T get() { - throw new RuntimeException(); - } + @NonNull T get() { + throw new RuntimeException(); + } - void set(@NonNull T p) {} - } + void set(@NonNull T p) {} + } - class Gen5a {} + class Gen5a {} - class Gen5b extends Gen5a<@Nullable Object> {} + class Gen5b extends Gen5a<@Nullable Object> {} - class Gen5c extends Gen5a<@Nullable S> {} + class Gen5c extends Gen5a<@Nullable S> {} - class Gen6a {} + class Gen6a {} - // :: error: (type.argument.type.incompatible) - class Gen6b extends Gen6a<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class Gen6b extends Gen6a<@Nullable Object> {} - // :: error: (type.argument.type.incompatible) - class Gen6c extends Gen6a<@Nullable S> {} + // :: error: (type.argument.type.incompatible) + class Gen6c extends Gen6a<@Nullable S> {} } diff --git a/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java b/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java index e33ab78a5fc..3c2f038d9e8 100644 --- a/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java +++ b/checker/tests/nullness-initialization/generics/AnnotatedGenerics.java @@ -2,84 +2,84 @@ import org.checkerframework.dataflow.qual.*; public class AnnotatedGenerics { - public static void testNullableTypeVariable() { - class Test { - // :: error: (initialization.field.uninitialized) - T f; + public static void testNullableTypeVariable() { + class Test { + // :: error: (initialization.field.uninitialized) + T f; - @Nullable T get() { - return f; - } + @Nullable T get() { + return f; + } + } + Test> l = new Test<>(); + // :: error: (iterating.over.nullable) + for (String s : l.get()) {} } - Test> l = new Test<>(); - // :: error: (iterating.over.nullable) - for (String s : l.get()) {} - } - public static void testNonNullTypeVariable() { - class Test { - @NonNull T get() { - throw new RuntimeException(); - } + public static void testNonNullTypeVariable() { + class Test { + @NonNull T get() { + throw new RuntimeException(); + } + } + Test<@Nullable Iterable> l = new Test<>(); + for (String s : l.get()) {} + Test> n = new Test<>(); + for (String s : n.get()) {} } - Test<@Nullable Iterable> l = new Test<>(); - for (String s : l.get()) {} - Test> n = new Test<>(); - for (String s : n.get()) {} - } - static class MyClass implements MyIterator<@Nullable T> { - public boolean hasNext() { - return true; - } + static class MyClass implements MyIterator<@Nullable T> { + public boolean hasNext() { + return true; + } - public @Nullable T next() { - return null; - } + public @Nullable T next() { + return null; + } - public void remove() {} + public void remove() {} - static void test() { - MyClass c = new MyClass<>(); - String c1 = c.next(); - @Nullable String c2 = c.next(); - // :: error: (assignment.type.incompatible) - @NonNull String c3 = c.next(); + static void test() { + MyClass c = new MyClass<>(); + String c1 = c.next(); + @Nullable String c2 = c.next(); + // :: error: (assignment.type.incompatible) + @NonNull String c3 = c.next(); + } } - } - public static final class MyComprator> { - public void compare(T a1, T a2) { - a1.compareTo(a2); - } + public static final class MyComprator> { + public void compare(T a1, T a2) { + a1.compareTo(a2); + } - public void compare2(@NonNull T a1, @NonNull T a2) { - a1.compareTo(a2); - } + public void compare2(@NonNull T a1, @NonNull T a2) { + a1.compareTo(a2); + } - public void compare3(T a1, @Nullable T a2) { - // :: error: (argument.type.incompatible) - a1.compareTo(a2); + public void compare3(T a1, @Nullable T a2) { + // :: error: (argument.type.incompatible) + a1.compareTo(a2); + } } - } - class MyComparable { - @Pure - public int compareTo(@NonNull T a1) { - return 0; + class MyComparable { + @Pure + public int compareTo(@NonNull T a1) { + return 0; + } } - } - T test(java.util.List> l) { - test(new java.util.ArrayList()); - throw new Error(); - } + T test(java.util.List> l) { + test(new java.util.ArrayList()); + throw new Error(); + } - public interface MyIterator { - boolean hasNext(); + public interface MyIterator { + boolean hasNext(); - E next(); + E next(); - void remove(); - } + void remove(); + } } diff --git a/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java b/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java index 2e79b31aa64..c229d4d0638 100644 --- a/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java +++ b/checker/tests/nullness-initialization/generics/AnnotatedGenerics2.java @@ -1,135 +1,135 @@ import org.checkerframework.checker.nullness.qual.*; public class AnnotatedGenerics2 { - // Top-level class to ensure that both classes are processed. - - class AnnotatedGenerics2Nble { - // :: error: (initialization.field.uninitialized) - @NonNull T myFieldNN; - @Nullable T myFieldNble; - // :: error: (initialization.field.uninitialized) - T myFieldT; - - /* TODO: This test case gets affected by flow inference. - * Investigate what the desired behavior is later. - void fields() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // TODO:: error: (assignment.type.incompatible) - myFieldT = myFieldNble; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - */ - - void fields1() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; + // Top-level class to ensure that both classes are processed. + + class AnnotatedGenerics2Nble { + // :: error: (initialization.field.uninitialized) + @NonNull T myFieldNN; + @Nullable T myFieldNble; + // :: error: (initialization.field.uninitialized) + T myFieldT; + + /* TODO: This test case gets affected by flow inference. + * Investigate what the desired behavior is later. + void fields() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // TODO:: error: (assignment.type.incompatible) + myFieldT = myFieldNble; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + */ + + void fields1() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; + } + + void fields2() { + // :: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // :: error: (assignment.type.incompatible) + myFieldT = myFieldNble; + } + + void fields3() { + // :: error: (assignment.type.incompatible) + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + + void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { + myFieldNN = myParamNN; + myFieldNble = myParamNN; + myFieldT = myParamNN; + + // :: error: (assignment.type.incompatible) + myFieldNN = myParamNble; + myFieldNble = myParamNble; + // :: error: (assignment.type.incompatible) + myFieldT = myParamNble; + + // :: error: (assignment.type.incompatible) + myFieldNN = myParamT; + myFieldNble = myParamT; + myFieldT = myParamT; + } } - void fields2() { - // :: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // :: error: (assignment.type.incompatible) - myFieldT = myFieldNble; + class AnnotatedGenerics2NN { + // :: error: (initialization.field.uninitialized) + @NonNull T myFieldNN; + @Nullable T myFieldNble; + // :: error: (initialization.field.uninitialized) + T myFieldT; + + /* TODO: This test case gets affected by flow inference. + * Investigate what the desired behavior is later. + void fields() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // TODO:: error: (assignment.type.incompatible) + myFieldT = myFieldNble; + + // TODO:: error: (assignment.type.incompatible) + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + */ + + void fields1() { + myFieldNN = myFieldNN; + myFieldNble = myFieldNN; + myFieldT = myFieldNN; + } + + void fields2() { + // :: error: (assignment.type.incompatible) + myFieldNN = myFieldNble; + myFieldNble = myFieldNble; + // :: error: (assignment.type.incompatible) + myFieldT = myFieldNble; + } + + void fields3() { + myFieldNN = myFieldT; + myFieldNble = myFieldT; + myFieldT = myFieldT; + } + + void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { + myFieldNN = myParamNN; + myFieldNble = myParamNN; + myFieldT = myParamNN; + + // :: error: (assignment.type.incompatible) + myFieldNN = myParamNble; + myFieldNble = myParamNble; + // :: error: (assignment.type.incompatible) + myFieldT = myParamNble; + + myFieldNN = myParamT; + myFieldNble = myParamT; + myFieldT = myParamT; + } } - - void fields3() { - // :: error: (assignment.type.incompatible) - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - - void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { - myFieldNN = myParamNN; - myFieldNble = myParamNN; - myFieldT = myParamNN; - - // :: error: (assignment.type.incompatible) - myFieldNN = myParamNble; - myFieldNble = myParamNble; - // :: error: (assignment.type.incompatible) - myFieldT = myParamNble; - - // :: error: (assignment.type.incompatible) - myFieldNN = myParamT; - myFieldNble = myParamT; - myFieldT = myParamT; - } - } - - class AnnotatedGenerics2NN { - // :: error: (initialization.field.uninitialized) - @NonNull T myFieldNN; - @Nullable T myFieldNble; - // :: error: (initialization.field.uninitialized) - T myFieldT; - - /* TODO: This test case gets affected by flow inference. - * Investigate what the desired behavior is later. - void fields() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // TODO:: error: (assignment.type.incompatible) - myFieldT = myFieldNble; - - // TODO:: error: (assignment.type.incompatible) - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - */ - - void fields1() { - myFieldNN = myFieldNN; - myFieldNble = myFieldNN; - myFieldT = myFieldNN; - } - - void fields2() { - // :: error: (assignment.type.incompatible) - myFieldNN = myFieldNble; - myFieldNble = myFieldNble; - // :: error: (assignment.type.incompatible) - myFieldT = myFieldNble; - } - - void fields3() { - myFieldNN = myFieldT; - myFieldNble = myFieldT; - myFieldT = myFieldT; - } - - void params(@NonNull T myParamNN, @Nullable T myParamNble, T myParamT) { - myFieldNN = myParamNN; - myFieldNble = myParamNN; - myFieldT = myParamNN; - - // :: error: (assignment.type.incompatible) - myFieldNN = myParamNble; - myFieldNble = myParamNble; - // :: error: (assignment.type.incompatible) - myFieldT = myParamNble; - - myFieldNN = myParamT; - myFieldNble = myParamT; - myFieldT = myParamT; - } - } } diff --git a/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java b/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java index 42fdf78c5d3..cb5843ebe06 100644 --- a/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java +++ b/checker/tests/nullness-initialization/generics/GenericBoundsExplicit.java @@ -4,48 +4,49 @@ public class GenericBoundsExplicit<@NonNull T extends @Nullable Object> { - @SuppressWarnings("initialization.field.uninitialized") - T t; - - public void method() { - // :: error: (dereference.of.nullable) - String str = t.toString(); - } - - public static void doSomething() { - final GenericBoundsExplicit<@Nullable String> b = new GenericBoundsExplicit<@Nullable String>(); - b.method(); - } + @SuppressWarnings("initialization.field.uninitialized") + T t; + + public void method() { + // :: error: (dereference.of.nullable) + String str = t.toString(); + } + + public static void doSomething() { + final GenericBoundsExplicit<@Nullable String> b = + new GenericBoundsExplicit<@Nullable String>(); + b.method(); + } } class GenericBoundsExplicit2<@NonNull TT extends @Nullable Object> { - @Nullable TT tt1; - // :: error: (initialization.field.uninitialized) - @NonNull TT tt2; - // :: error: (initialization.field.uninitialized) - TT tt3; + @Nullable TT tt1; + // :: error: (initialization.field.uninitialized) + @NonNull TT tt2; + // :: error: (initialization.field.uninitialized) + TT tt3; - public void context() { + public void context() { - // :: error: (dereference.of.nullable) - tt1.toString(); - tt2.toString(); + // :: error: (dereference.of.nullable) + tt1.toString(); + tt2.toString(); - // :: error: (dereference.of.nullable) - tt3.toString(); - } + // :: error: (dereference.of.nullable) + tt3.toString(); + } } @SuppressWarnings("initialization.field.uninitialized") class GenericBoundsExplicit3<@NonNull TTT extends @NonNull Object> { - @Nullable TTT ttt1; - @NonNull TTT ttt2; - TTT ttt3; - - public void context() { - // :: error: (dereference.of.nullable) - ttt1.toString(); - ttt2.toString(); - ttt3.toString(); - } + @Nullable TTT ttt1; + @NonNull TTT ttt2; + TTT ttt3; + + public void context() { + // :: error: (dereference.of.nullable) + ttt1.toString(); + ttt2.toString(); + ttt3.toString(); + } } diff --git a/checker/tests/nullness-initialization/generics/Issue314.java b/checker/tests/nullness-initialization/generics/Issue314.java index 5da71291a77..419cabb048b 100644 --- a/checker/tests/nullness-initialization/generics/Issue314.java +++ b/checker/tests/nullness-initialization/generics/Issue314.java @@ -1,28 +1,29 @@ // Test case for Issue 314: // https://github.com/typetools/checker-framework/issues/314 -import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class Issue314 { - List m1(List<@NonNull T> l1) { - return l1; - } + List m1(List<@NonNull T> l1) { + return l1; + } - List m2(List<@NonNull T> l1) { - // :: error: (return.type.incompatible) - return l1; - } + List m2(List<@NonNull T> l1) { + // :: error: (return.type.incompatible) + return l1; + } - class Also { - S f1; - @NonNull S f2; + class Also { + S f1; + @NonNull S f2; - { - // :: error: (assignment.type.incompatible) - f1 = f2; - f2 = f1; + { + // :: error: (assignment.type.incompatible) + f1 = f2; + f2 = f1; + } } - } } diff --git a/checker/tests/nullness-initialization/generics/Issue783c.java b/checker/tests/nullness-initialization/generics/Issue783c.java index c84e72b3e21..9838d5759a0 100644 --- a/checker/tests/nullness-initialization/generics/Issue783c.java +++ b/checker/tests/nullness-initialization/generics/Issue783c.java @@ -1,12 +1,12 @@ public class Issue783c { - // :: error: (initialization.field.uninitialized) - private T val; + // :: error: (initialization.field.uninitialized) + private T val; - public void set(T val) { - this.val = val; - } + public void set(T val) { + this.val = val; + } - public T get() { - return val; - } + public T get() { + return val; + } } diff --git a/checker/tests/nullness-initialization/generics/NullableLUB.java b/checker/tests/nullness-initialization/generics/NullableLUB.java index 4c5e6a94a3e..87d88d7d384 100644 --- a/checker/tests/nullness-initialization/generics/NullableLUB.java +++ b/checker/tests/nullness-initialization/generics/NullableLUB.java @@ -5,29 +5,29 @@ * get raised, leading to a missed NPE. */ public class NullableLUB { - // :: error: (initialization.field.uninitialized) - T t; - @Nullable T nt; + // :: error: (initialization.field.uninitialized) + T t; + @Nullable T nt; - T m(boolean b, T p) { - T r1 = b ? p : null; - nt = r1; - // :: error: (assignment.type.incompatible) - t = r1; - // :: error: (return.type.incompatible) - return r1; - } + T m(boolean b, T p) { + T r1 = b ? p : null; + nt = r1; + // :: error: (assignment.type.incompatible) + t = r1; + // :: error: (return.type.incompatible) + return r1; + } - public static void main(String[] args) { - new NullableLUB<@NonNull Object>().m(false, new Object()).toString(); - } + public static void main(String[] args) { + new NullableLUB<@NonNull Object>().m(false, new Object()).toString(); + } - T m2(boolean b, T p) { - T r1 = b ? null : p; - nt = r1; - // :: error: (assignment.type.incompatible) - t = r1; - // :: error: (return.type.incompatible) - return r1; - } + T m2(boolean b, T p) { + T r1 = b ? null : p; + nt = r1; + // :: error: (assignment.type.incompatible) + t = r1; + // :: error: (return.type.incompatible) + return r1; + } } diff --git a/checker/tests/nullness-initialization/generics/WellformedBounds.java b/checker/tests/nullness-initialization/generics/WellformedBounds.java index f9ccd100ca3..1f09132efd1 100644 --- a/checker/tests/nullness-initialization/generics/WellformedBounds.java +++ b/checker/tests/nullness-initialization/generics/WellformedBounds.java @@ -1,39 +1,39 @@ import org.checkerframework.checker.nullness.qual.*; class Param { - // Field f needs to be set, because the upper bound is @Initialized - // :: error: (initialization.field.uninitialized) - T f; + // Field f needs to be set, because the upper bound is @Initialized + // :: error: (initialization.field.uninitialized) + T f; - void foo() { - // Valid, because upper bound is @Initialized @NonNull - f.toString(); - } + void foo() { + // Valid, because upper bound is @Initialized @NonNull + f.toString(); + } } // :: error: (type.argument.type.incompatible) class Invalid> { - void bar(S s) { - s.foo(); - } + void bar(S s) { + s.foo(); + } - // :: error: (type.argument.type.incompatible) - > void foobar(M p) {} + // :: error: (type.argument.type.incompatible) + > void foobar(M p) {} } interface ParamI {} class Invalid2< - S extends - Number & - // :: error: (type.argument.type.incompatible) - ParamI<@Nullable Object>> {} + S extends + Number & + // :: error: (type.argument.type.incompatible) + ParamI<@Nullable Object>> {} class Invalid3 { - < - M extends - Number & - // :: error: (type.argument.type.incompatible) - ParamI<@Nullable Object>> - void foobar(M p) {} + < + M extends + Number & + // :: error: (type.argument.type.incompatible) + ParamI<@Nullable Object>> + void foobar(M p) {} } diff --git a/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java b/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java index 6a5ffc3cb65..562ecdc9f95 100644 --- a/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java +++ b/checker/tests/nullness-initialization/java8/lambda/LambdaInit.java @@ -4,198 +4,198 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionInit { - R apply(T t); + R apply(T t); } interface Consumer { - void consume(T t); + void consume(T t); } public class LambdaInit { - String f1; - String f2 = ""; - @Nullable String f3 = ""; - - String f1b; - FunctionInit ff0 = - s -> { - // :: error: (dereference.of.nullable) - f1.toString(); - // :: error: (dereference.of.nullable) - f1b.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; - // Test field value refinement after initializer. f1b should still be @Nullable in the lambda. - Object o1 = f1b = ""; - - String f4; - - { - f3 = ""; - f4 = ""; - FunctionInit ff0 = - s -> { - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f4.toString(); - return ""; - }; - } - - String f5; - - @SuppressWarnings("initialization.fields.uninitialized") // f1 is not initialized - LambdaInit() { - f5 = ""; - FunctionInit ff0 = - s -> { - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f5.toString(); - return ""; - }; - } - - // Test for https://github.com/typetools/checker-framework/issues/5194 . - Object o = - new Object() { - @Override - public String toString() { - // BUG: this should not yield a warning. - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - return ""; - } - }; + String f1; + String f2 = ""; + @Nullable String f3 = ""; - // Works! - void method() { + String f1b; FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; - } - - // Test for nested - class Nested { - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; + s -> { + // :: error: (dereference.of.nullable) + f1.toString(); + // :: error: (dereference.of.nullable) + f1b.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; + // Test field value refinement after initializer. f1b should still be @Nullable in the lambda. + Object o1 = f1b = ""; String f4; { - f3 = ""; - f4 = ""; - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f4.toString(); - return ""; - }; + f3 = ""; + f4 = ""; + FunctionInit ff0 = + s -> { + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f4.toString(); + return ""; + }; } String f5; - Nested() { - f5 = ""; - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - f5.toString(); - return ""; - }; + @SuppressWarnings("initialization.fields.uninitialized") // f1 is not initialized + LambdaInit() { + f5 = ""; + FunctionInit ff0 = + s -> { + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f5.toString(); + return ""; + }; } + // Test for https://github.com/typetools/checker-framework/issues/5194 . + Object o = + new Object() { + @Override + public String toString() { + // BUG: this should not yield a warning. + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + return ""; + } + }; + + // Works! void method() { - FunctionInit ff0 = - s -> { - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); - return ""; - }; + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; + } + + // Test for nested + class Nested { + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; + + String f4; + + { + f3 = ""; + f4 = ""; + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f4.toString(); + return ""; + }; + } + + String f5; + + Nested() { + f5 = ""; + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + f5.toString(); + return ""; + }; + } + + void method() { + FunctionInit ff0 = + s -> { + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + return ""; + }; + } } - } - - // Test for nested in a lambda - Consumer func = - s -> { - Consumer ff0 = - s2 -> { - // :: error: (dereference.of.nullable) - f1.toString(); - f2.toString(); - // :: error: (dereference.of.nullable) - f3.toString(); + + // Test for nested in a lambda + Consumer func = + s -> { + Consumer ff0 = + s2 -> { + // :: error: (dereference.of.nullable) + f1.toString(); + f2.toString(); + // :: error: (dereference.of.nullable) + f3.toString(); + }; + }; + + // Tests for static initializers. + // :: error: (initialization.static.field.uninitialized) + static String sf1; + static String sf2 = ""; + static @Nullable String sf3 = ""; + static String sf1b; + static FunctionInit sff0 = + s -> { + + // This is an issue with static initializers in general + // // :: error: (dereference.of.nullable) + sf1.toString(); + // This is an issue with static initializers in general + // // :: error: (dereference.of.nullable) + sf1b.toString(); + sf2.toString(); + // :: error: (dereference.of.nullable) + sf3.toString(); + return ""; }; - }; - - // Tests for static initializers. - // :: error: (initialization.static.field.uninitialized) - static String sf1; - static String sf2 = ""; - static @Nullable String sf3 = ""; - static String sf1b; - static FunctionInit sff0 = - s -> { - - // This is an issue with static initializers in general - // // :: error: (dereference.of.nullable) - sf1.toString(); - // This is an issue with static initializers in general - // // :: error: (dereference.of.nullable) - sf1b.toString(); - sf2.toString(); - // :: error: (dereference.of.nullable) - sf3.toString(); - return ""; - }; - // Test field value refinement after initializer. f1b should still be null. - static Object so1 = sf1b = ""; - - static String sf4; - - static { - sf3 = ""; - sf4 = ""; - FunctionInit sff0 = - s -> { - - // This is an issue with static initializers in general - // // :: error: (dereference.of.nullable) - sf1.toString(); - sf2.toString(); - // :: error: (dereference.of.nullable) - sf3.toString(); - sf4.toString(); - return ""; - }; - } + // Test field value refinement after initializer. f1b should still be null. + static Object so1 = sf1b = ""; + + static String sf4; + + static { + sf3 = ""; + sf4 = ""; + FunctionInit sff0 = + s -> { + + // This is an issue with static initializers in general + // // :: error: (dereference.of.nullable) + sf1.toString(); + sf2.toString(); + // :: error: (dereference.of.nullable) + sf3.toString(); + sf4.toString(); + return ""; + }; + } } diff --git a/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java b/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java index 7b01d2b6f69..5f43e8df2d1 100644 --- a/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java +++ b/checker/tests/nullness-initialization/java8/lambda/ReceiversLambda.java @@ -4,30 +4,30 @@ // Tests for the nullable type system interface SupplierR { - @NonNull ReceiverTest supply(); + @NonNull ReceiverTest supply(); } interface FunctionRT { - R apply(T t); + R apply(T t); } class ReceiverTest { - // :: error: (method.invocation.invalid) - FunctionRT f1 = s -> this.toString(); - // :: error: (method.invocation.invalid) - FunctionRT f2 = s -> super.toString(); + // :: error: (method.invocation.invalid) + FunctionRT f1 = s -> this.toString(); + // :: error: (method.invocation.invalid) + FunctionRT f2 = s -> super.toString(); - // :: error: (nullness.on.receiver) - void context1(@NonNull ReceiverTest this) { - SupplierR s = () -> this; - } + // :: error: (nullness.on.receiver) + void context1(@NonNull ReceiverTest this) { + SupplierR s = () -> this; + } - // :: error: (nullness.on.receiver) - void context2(@Nullable ReceiverTest this) { - // TODO: This is bug that is not specific to lambdas - // https://github.com/typetools/checker-framework/issues/352 - // :: error: (return.type.incompatible) - SupplierR s = () -> this; - } + // :: error: (nullness.on.receiver) + void context2(@Nullable ReceiverTest this) { + // TODO: This is bug that is not specific to lambdas + // https://github.com/typetools/checker-framework/issues/352 + // :: error: (return.type.incompatible) + SupplierR s = () -> this; + } } diff --git a/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java b/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java index b0fceb272cf..9c745cb3db2 100644 --- a/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java +++ b/checker/tests/nullness-invariantarrays/TwoDimensionalArray.java @@ -3,11 +3,11 @@ public class TwoDimensionalArray { - public static void main(String[] args) { - assert any_null(new Object[][] {null}) == true; - } + public static void main(String[] args) { + assert any_null(new Object[][] {null}) == true; + } - public static boolean any_null(Object[] a) { - return true; - } + public static boolean any_null(Object[] a) { + return true; + } } diff --git a/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java b/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java index 4fdeae36d0e..2eeb33b2349 100644 --- a/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java +++ b/checker/tests/nullness-javadoc/JavadocJdkAnnotations.java @@ -1,21 +1,22 @@ import com.sun.javadoc.Doc; + import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; // @above-java11-jdk-skip-test com.sun.javadoc.Doc doesn't exist above 11. public class JavadocJdkAnnotations { - @Nullable Object f = null; + @Nullable Object f = null; - @SuppressWarnings("removal") - void testPureAnnotation(Doc d) { - // This tests that @Pure and @SideEffectFree annotations are read. + @SuppressWarnings("removal") + void testPureAnnotation(Doc d) { + // This tests that @Pure and @SideEffectFree annotations are read. - f = "non-null value"; - d.isIncluded(); - @NonNull Object x = f; - d.tags(); - // :: error: (assignment.type.incompatible) - @NonNull Object y = f; - } + f = "non-null value"; + d.isIncluded(); + @NonNull Object x = f; + d.tags(); + // :: error: (assignment.type.incompatible) + @NonNull Object y = f; + } } diff --git a/checker/tests/nullness-nodelombok/UnsoundnessTest.java b/checker/tests/nullness-nodelombok/UnsoundnessTest.java index 80c382c2e5a..2264637f10b 100644 --- a/checker/tests/nullness-nodelombok/UnsoundnessTest.java +++ b/checker/tests/nullness-nodelombok/UnsoundnessTest.java @@ -3,15 +3,15 @@ @lombok.Builder class UnsoundnessTest { - @lombok.NonNull Object foo; - @lombok.NonNull Object bar; + @lombok.NonNull Object foo; + @lombok.NonNull Object bar; - static void test() { - // An error should be issued here, but the code has not been delombok'd. - // If the CF and Lombok are ever able to work in the same invocation of javac - // (i.e. without delomboking first), then this error should be changed back to an - // expected error by re-adding the leading "::". - // error: (assignment.type.incompatible) - builder().foo(null).build(); - } + static void test() { + // An error should be issued here, but the code has not been delombok'd. + // If the CF and Lombok are ever able to work in the same invocation of javac + // (i.e. without delomboking first), then this error should be changed back to an + // expected error by re-adding the leading "::". + // error: (assignment.type.incompatible) + builder().foo(null).build(); + } } diff --git a/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java b/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java index 8d53b367003..7813d480d7b 100644 --- a/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java +++ b/checker/tests/nullness-nullmarked/parentandchildpackage/NotNullMarkedBecauseChildPackage.java @@ -3,5 +3,5 @@ import org.jspecify.annotations.Nullable; public class NotNullMarkedBecauseChildPackage { - void foo(NotNullMarkedBecauseChildPackage<@Nullable String> d) {} + void foo(NotNullMarkedBecauseChildPackage<@Nullable String> d) {} } diff --git a/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java b/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java index da62455e63b..0ac4b19cf17 100644 --- a/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java +++ b/checker/tests/nullness-nullmarked/singlepackage/NullMarkedBecausePackageIs.java @@ -3,6 +3,6 @@ import org.jspecify.annotations.Nullable; public class NullMarkedBecausePackageIs { - // :: error: (type.argument.type.incompatible) - void foo(NullMarkedBecausePackageIs<@Nullable String> d) {} + // :: error: (type.argument.type.incompatible) + void foo(NullMarkedBecausePackageIs<@Nullable String> d) {} } diff --git a/checker/tests/nullness-permitClearProperty/PermitClearProperty.java b/checker/tests/nullness-permitClearProperty/PermitClearProperty.java index 88b073aa11d..487decaca71 100644 --- a/checker/tests/nullness-permitClearProperty/PermitClearProperty.java +++ b/checker/tests/nullness-permitClearProperty/PermitClearProperty.java @@ -1,125 +1,126 @@ // Same code (but different expected errors) as test PreventClearProperty.java . -import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.common.value.qual.StringVal; +import java.util.Properties; + public class PermitClearProperty { - static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; - - static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; - - @NonNull String getLineSeparator1() { - // :: error: (return.type.incompatible) - return System.getProperty("line.separator"); - } - - @NonNull String getLineSeparator2() { - // :: error: (return.type.incompatible) - return System.getProperty(LINE_SEPARATOR); - } - - @NonNull String getMyProperty1() { - // :: error: (return.type.incompatible) - return System.getProperty("my.property.name"); - } - - @NonNull String getMyProperty2() { - // :: error: (return.type.incompatible) - return System.getProperty(MY_PROPERTY_NAME); - } - - @NonNull String getAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.getProperty(propName); - } - - @NonNull String clearLineSeparator1() { - // :: error: (return.type.incompatible) - return System.clearProperty("line.separator"); - } - - @NonNull String clearLineSeparator2() { - // :: error: (return.type.incompatible) - return System.clearProperty(LINE_SEPARATOR); - } - - @NonNull String clearMyProperty1() { - // :: error: (return.type.incompatible) - return System.clearProperty("my.property.name"); - } - - @NonNull String clearMyProperty2() { - // :: error: (return.type.incompatible) - return System.clearProperty(MY_PROPERTY_NAME); - } - - @NonNull String clearAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.clearProperty(propName); - } - - void callSetProperties(Properties p) { - System.setProperties(p); - } - - // All calls to setProperty are legal because they cannot unset a property. - - @NonNull String setLineSeparator1() { - // :: error: (return.type.incompatible) - return System.setProperty("line.separator", "somevalue"); - } - - @NonNull String setLineSeparator2() { - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, "somevalue"); - } - - @NonNull String setMyProperty1() { - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", "somevalue"); - } - - @NonNull String setMyProperty2() { - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, "somevalue"); - } - - @NonNull String setAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.setProperty(propName, "somevalue"); - } - - // These calls to setProperty are illegal because null is not a permitted value. - - @NonNull String setLineSeparatorNull1() { - // :: error: (return.type.incompatible) - // :: error: (argument.type.incompatible) - return System.setProperty("line.separator", null); - } - - @NonNull String setLineSeparatorNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, null); - } - - @NonNull String setMyPropertyNull1() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", null); - } - - @NonNull String setMyPropertyNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, null); - } - - @NonNull String setAPropertyNull(String propName) { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(propName, null); - } + static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; + + static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; + + @NonNull String getLineSeparator1() { + // :: error: (return.type.incompatible) + return System.getProperty("line.separator"); + } + + @NonNull String getLineSeparator2() { + // :: error: (return.type.incompatible) + return System.getProperty(LINE_SEPARATOR); + } + + @NonNull String getMyProperty1() { + // :: error: (return.type.incompatible) + return System.getProperty("my.property.name"); + } + + @NonNull String getMyProperty2() { + // :: error: (return.type.incompatible) + return System.getProperty(MY_PROPERTY_NAME); + } + + @NonNull String getAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.getProperty(propName); + } + + @NonNull String clearLineSeparator1() { + // :: error: (return.type.incompatible) + return System.clearProperty("line.separator"); + } + + @NonNull String clearLineSeparator2() { + // :: error: (return.type.incompatible) + return System.clearProperty(LINE_SEPARATOR); + } + + @NonNull String clearMyProperty1() { + // :: error: (return.type.incompatible) + return System.clearProperty("my.property.name"); + } + + @NonNull String clearMyProperty2() { + // :: error: (return.type.incompatible) + return System.clearProperty(MY_PROPERTY_NAME); + } + + @NonNull String clearAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.clearProperty(propName); + } + + void callSetProperties(Properties p) { + System.setProperties(p); + } + + // All calls to setProperty are legal because they cannot unset a property. + + @NonNull String setLineSeparator1() { + // :: error: (return.type.incompatible) + return System.setProperty("line.separator", "somevalue"); + } + + @NonNull String setLineSeparator2() { + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, "somevalue"); + } + + @NonNull String setMyProperty1() { + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", "somevalue"); + } + + @NonNull String setMyProperty2() { + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, "somevalue"); + } + + @NonNull String setAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.setProperty(propName, "somevalue"); + } + + // These calls to setProperty are illegal because null is not a permitted value. + + @NonNull String setLineSeparatorNull1() { + // :: error: (return.type.incompatible) + // :: error: (argument.type.incompatible) + return System.setProperty("line.separator", null); + } + + @NonNull String setLineSeparatorNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, null); + } + + @NonNull String setMyPropertyNull1() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", null); + } + + @NonNull String setMyPropertyNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, null); + } + + @NonNull String setAPropertyNull(String propName) { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(propName, null); + } } diff --git a/checker/tests/nullness-records/BasicRecord.java b/checker/tests/nullness-records/BasicRecord.java index 44b69654eae..0b2c793b76d 100644 --- a/checker/tests/nullness-records/BasicRecord.java +++ b/checker/tests/nullness-records/BasicRecord.java @@ -3,12 +3,12 @@ // @below-java16-jdk-skip-test public record BasicRecord(String str) { - public static BasicRecord makeNonNull(String s) { - return new BasicRecord(s); - } + public static BasicRecord makeNonNull(String s) { + return new BasicRecord(s); + } - public static BasicRecord makeNull(@Nullable String s) { - // :: error: (argument.type.incompatible) - return new BasicRecord(s); - } + public static BasicRecord makeNull(@Nullable String s) { + // :: error: (argument.type.incompatible) + return new BasicRecord(s); + } } diff --git a/checker/tests/nullness-records/BasicRecordCanon.java b/checker/tests/nullness-records/BasicRecordCanon.java index ec2f3985614..6785c803600 100644 --- a/checker/tests/nullness-records/BasicRecordCanon.java +++ b/checker/tests/nullness-records/BasicRecordCanon.java @@ -3,14 +3,14 @@ // @below-java16-jdk-skip-test public record BasicRecordCanon(String str) { - public static BasicRecordCanon makeNonNull(String s) { - return new BasicRecordCanon(s); - } + public static BasicRecordCanon makeNonNull(String s) { + return new BasicRecordCanon(s); + } - public static BasicRecordCanon makeNull(@Nullable String s) { - // :: error: (argument.type.incompatible) - return new BasicRecordCanon(s); - } + public static BasicRecordCanon makeNull(@Nullable String s) { + // :: error: (argument.type.incompatible) + return new BasicRecordCanon(s); + } - public BasicRecordCanon {} + public BasicRecordCanon {} } diff --git a/checker/tests/nullness-records/BasicRecordNullable.java b/checker/tests/nullness-records/BasicRecordNullable.java index 91e52ebbfcb..1c1064b3e11 100644 --- a/checker/tests/nullness-records/BasicRecordNullable.java +++ b/checker/tests/nullness-records/BasicRecordNullable.java @@ -3,29 +3,29 @@ // @below-java16-jdk-skip-test public record BasicRecordNullable(@Nullable String str) { - public static BasicRecordNullable makeNonNull(String s) { - return new BasicRecordNullable(s); - } + public static BasicRecordNullable makeNonNull(String s) { + return new BasicRecordNullable(s); + } - public static BasicRecordNullable makeNull(@Nullable String s) { - return new BasicRecordNullable(s); - } + public static BasicRecordNullable makeNull(@Nullable String s) { + return new BasicRecordNullable(s); + } - public @Nullable String getStringFromField() { - return str; - } + public @Nullable String getStringFromField() { + return str; + } - public @Nullable String getStringFromMethod() { - return str(); - } + public @Nullable String getStringFromMethod() { + return str(); + } - public String getStringFromFieldErr() { - // :: error: (return.type.incompatible) - return str; - } + public String getStringFromFieldErr() { + // :: error: (return.type.incompatible) + return str; + } - public String getStringFromMethodErr() { - // :: error: (return.type.incompatible) - return str(); - } + public String getStringFromMethodErr() { + // :: error: (return.type.incompatible) + return str(); + } } diff --git a/checker/tests/nullness-records/DefaultQualRecord.java b/checker/tests/nullness-records/DefaultQualRecord.java index cd2dfc1ab7f..781c1da2ab3 100644 --- a/checker/tests/nullness-records/DefaultQualRecord.java +++ b/checker/tests/nullness-records/DefaultQualRecord.java @@ -2,61 +2,61 @@ import org.checkerframework.framework.qual.DefaultQualifier; class StandardQualClass { - // :: error: (assignment.type.incompatible) - public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; + // :: error: (assignment.type.incompatible) + public static String s = null; + // :: error: (initialization.static.field.uninitialized) + public static String u; } @DefaultQualifier(Nullable.class) class DefaultQualClass { - public static String s = null; - public static String u; + public static String s = null; + public static String u; } interface StandardQualInterface { - // :: error: (assignment.type.incompatible) - public static String s = null; + // :: error: (assignment.type.incompatible) + public static String s = null; } @DefaultQualifier(Nullable.class) interface DefaultQualInterface { - public static String s = null; + public static String s = null; } enum StandardQualEnum { - DUMMY; - // :: error: (assignment.type.incompatible) - public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; + DUMMY; + // :: error: (assignment.type.incompatible) + public static String s = null; + // :: error: (initialization.static.field.uninitialized) + public static String u; } @DefaultQualifier(Nullable.class) enum DefaultQualEnum { - DUMMY; - public static String s = null; - public static String u; + DUMMY; + public static String s = null; + public static String u; } record StandardQualRecord(String m) { - // :: error: (assignment.type.incompatible) - public static String s = null; - // :: error: (initialization.static.field.uninitialized) - public static String u; - - StandardQualRecord { // :: error: (assignment.type.incompatible) - m = null; - } + public static String s = null; + // :: error: (initialization.static.field.uninitialized) + public static String u; + + StandardQualRecord { + // :: error: (assignment.type.incompatible) + m = null; + } } @DefaultQualifier(Nullable.class) record DefaultQualRecord(String m) { - public static String s = null; - public static String u; + public static String s = null; + public static String u; - DefaultQualRecord { - m = null; - } + DefaultQualRecord { + m = null; + } } diff --git a/checker/tests/nullness-records/GenericPair.java b/checker/tests/nullness-records/GenericPair.java index e2955305222..13e5a863ba3 100644 --- a/checker/tests/nullness-records/GenericPair.java +++ b/checker/tests/nullness-records/GenericPair.java @@ -3,9 +3,9 @@ // @below-java16-jdk-skip-test public record GenericPair(K key, V value) { - public static void foo() { - GenericPair p = new GenericPair<>("k", null); - // :: error: (dereference.of.nullable) - p.value().toString(); - } + public static void foo() { + GenericPair p = new GenericPair<>("k", null); + // :: error: (dereference.of.nullable) + p.value().toString(); + } } diff --git a/checker/tests/nullness-records/Issue5200.java b/checker/tests/nullness-records/Issue5200.java index 873021b5547..4044bf61b0a 100644 --- a/checker/tests/nullness-records/Issue5200.java +++ b/checker/tests/nullness-records/Issue5200.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Test { - record Foo(@Nullable String bar) { - @Nullable String baz() { - return Math.random() > 0.5 ? bar : null; + record Foo(@Nullable String bar) { + @Nullable String baz() { + return Math.random() > 0.5 ? bar : null; + } } - } - void main() { - checkEmpty(new Foo("")); - } - - void checkEmpty(Foo foo) { - if (foo.bar() != null && !foo.bar().isEmpty()) { - System.out.println("ok"); + void main() { + checkEmpty(new Foo("")); } - // :: error: (dereference.of.nullable) - if (foo.baz() != null && !foo.baz().isEmpty()) { - System.out.println("not ok"); + + void checkEmpty(Foo foo) { + if (foo.bar() != null && !foo.bar().isEmpty()) { + System.out.println("ok"); + } + // :: error: (dereference.of.nullable) + if (foo.baz() != null && !foo.baz().isEmpty()) { + System.out.println("not ok"); + } } - } } diff --git a/checker/tests/nullness-records/LocalRecords.java b/checker/tests/nullness-records/LocalRecords.java index 68f55c8d716..64cf96399da 100644 --- a/checker/tests/nullness-records/LocalRecords.java +++ b/checker/tests/nullness-records/LocalRecords.java @@ -2,11 +2,11 @@ // @below-java16-jdk-skip-test public class LocalRecords { - public static void foo() { - record L(String key, @Nullable Integer value) {} - L a = new L("one", 1); - L b = new L("i", null); - // :: error: (argument.type.incompatible) - L c = new L(null, 6); - } + public static void foo() { + record L(String key, @Nullable Integer value) {} + L a = new L("one", 1); + L b = new L("i", null); + // :: error: (argument.type.incompatible) + L c = new L(null, 6); + } } diff --git a/checker/tests/nullness-records/NestedRecordTest.java b/checker/tests/nullness-records/NestedRecordTest.java index 51154893298..bc984708113 100644 --- a/checker/tests/nullness-records/NestedRecordTest.java +++ b/checker/tests/nullness-records/NestedRecordTest.java @@ -5,94 +5,94 @@ public class NestedRecordTest { - static @NonNull String nn = "foo"; - static @Nullable String nble = null; - static @NonNull String nn2 = "foo"; - static @Nullable String nble2 = null; + static @NonNull String nn = "foo"; + static @Nullable String nble = null; + static @NonNull String nn2 = "foo"; + static @Nullable String nble2 = null; - public static class Nested { - public record NPerson(String familyName, @Nullable String maidenName) {} + public static class Nested { + public record NPerson(String familyName, @Nullable String maidenName) {} - void nclient() { - Nested.NPerson np1 = new Nested.NPerson(nn, nn); - Nested.NPerson np2 = new Nested.NPerson(nn, nble); - // :: error: (argument.type.incompatible) - Nested.NPerson np3 = new Nested.NPerson(nble, nn); - // :: error: (argument.type.incompatible) - Nested.NPerson np4 = new Nested.NPerson(nble, nble); - Inner.IPerson ip1 = new Inner.IPerson(nn, nn); - Inner.IPerson ip2 = new Inner.IPerson(nn, nble); - // :: error: (argument.type.incompatible) - Inner.IPerson ip3 = new Inner.IPerson(nble, nn); - // :: error: (argument.type.incompatible) - Inner.IPerson ip4 = new Inner.IPerson(nble, nble); + void nclient() { + Nested.NPerson np1 = new Nested.NPerson(nn, nn); + Nested.NPerson np2 = new Nested.NPerson(nn, nble); + // :: error: (argument.type.incompatible) + Nested.NPerson np3 = new Nested.NPerson(nble, nn); + // :: error: (argument.type.incompatible) + Nested.NPerson np4 = new Nested.NPerson(nble, nble); + Inner.IPerson ip1 = new Inner.IPerson(nn, nn); + Inner.IPerson ip2 = new Inner.IPerson(nn, nble); + // :: error: (argument.type.incompatible) + Inner.IPerson ip3 = new Inner.IPerson(nble, nn); + // :: error: (argument.type.incompatible) + Inner.IPerson ip4 = new Inner.IPerson(nble, nble); - nn2 = np2.familyName(); - nble2 = np2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = np2.maidenName(); - nble2 = np2.maidenName(); - nn2 = ip2.familyName(); - nble2 = ip2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = ip2.maidenName(); - nble2 = ip2.maidenName(); + nn2 = np2.familyName(); + nble2 = np2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = np2.maidenName(); + nble2 = np2.maidenName(); + nn2 = ip2.familyName(); + nble2 = ip2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = ip2.maidenName(); + nble2 = ip2.maidenName(); + } } - } - public class Inner { - public record IPerson(String familyName, @Nullable String maidenName) {} + public class Inner { + public record IPerson(String familyName, @Nullable String maidenName) {} - void iclient() { - Nested.NPerson np1 = new Nested.NPerson(nn, nn); - Nested.NPerson np2 = new Nested.NPerson(nn, nble); - // :: error: (argument.type.incompatible) - Nested.NPerson np3 = new Nested.NPerson(nble, nn); - // :: error: (argument.type.incompatible) - Nested.NPerson np4 = new Nested.NPerson(nble, nble); - Inner.IPerson ip1 = new Inner.IPerson(nn, nn); - Inner.IPerson ip2 = new Inner.IPerson(nn, nble); - // :: error: (argument.type.incompatible) - Inner.IPerson ip3 = new Inner.IPerson(nble, nn); - // :: error: (argument.type.incompatible) - Inner.IPerson ip4 = new Inner.IPerson(nble, nble); + void iclient() { + Nested.NPerson np1 = new Nested.NPerson(nn, nn); + Nested.NPerson np2 = new Nested.NPerson(nn, nble); + // :: error: (argument.type.incompatible) + Nested.NPerson np3 = new Nested.NPerson(nble, nn); + // :: error: (argument.type.incompatible) + Nested.NPerson np4 = new Nested.NPerson(nble, nble); + Inner.IPerson ip1 = new Inner.IPerson(nn, nn); + Inner.IPerson ip2 = new Inner.IPerson(nn, nble); + // :: error: (argument.type.incompatible) + Inner.IPerson ip3 = new Inner.IPerson(nble, nn); + // :: error: (argument.type.incompatible) + Inner.IPerson ip4 = new Inner.IPerson(nble, nble); - nn2 = np2.familyName(); - nble2 = np2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = np2.maidenName(); - nble2 = np2.maidenName(); - nn2 = ip2.familyName(); - nble2 = ip2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = ip2.maidenName(); - nble2 = ip2.maidenName(); + nn2 = np2.familyName(); + nble2 = np2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = np2.maidenName(); + nble2 = np2.maidenName(); + nn2 = ip2.familyName(); + nble2 = ip2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = ip2.maidenName(); + nble2 = ip2.maidenName(); + } } - } - void client() { - Nested.NPerson np1 = new Nested.NPerson(nn, nn); - Nested.NPerson np2 = new Nested.NPerson(nn, nble); - // :: error: (argument.type.incompatible) - Nested.NPerson np3 = new Nested.NPerson(nble, nn); - // :: error: (argument.type.incompatible) - Nested.NPerson np4 = new Nested.NPerson(nble, nble); - Inner.IPerson ip1 = new Inner.IPerson(nn, nn); - Inner.IPerson ip2 = new Inner.IPerson(nn, nble); - // :: error: (argument.type.incompatible) - Inner.IPerson ip3 = new Inner.IPerson(nble, nn); - // :: error: (argument.type.incompatible) - Inner.IPerson ip4 = new Inner.IPerson(nble, nble); + void client() { + Nested.NPerson np1 = new Nested.NPerson(nn, nn); + Nested.NPerson np2 = new Nested.NPerson(nn, nble); + // :: error: (argument.type.incompatible) + Nested.NPerson np3 = new Nested.NPerson(nble, nn); + // :: error: (argument.type.incompatible) + Nested.NPerson np4 = new Nested.NPerson(nble, nble); + Inner.IPerson ip1 = new Inner.IPerson(nn, nn); + Inner.IPerson ip2 = new Inner.IPerson(nn, nble); + // :: error: (argument.type.incompatible) + Inner.IPerson ip3 = new Inner.IPerson(nble, nn); + // :: error: (argument.type.incompatible) + Inner.IPerson ip4 = new Inner.IPerson(nble, nble); - nn2 = np2.familyName(); - nble2 = np2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = np2.maidenName(); - nble2 = np2.maidenName(); - nn2 = ip2.familyName(); - nble2 = ip2.familyName(); - // :: error: (assignment.type.incompatible) - nn2 = ip2.maidenName(); - nble2 = ip2.maidenName(); - } + nn2 = np2.familyName(); + nble2 = np2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = np2.maidenName(); + nble2 = np2.maidenName(); + nn2 = ip2.familyName(); + nble2 = ip2.familyName(); + // :: error: (assignment.type.incompatible) + nn2 = ip2.maidenName(); + nble2 = ip2.maidenName(); + } } diff --git a/checker/tests/nullness-records/NormalizingRecord.java b/checker/tests/nullness-records/NormalizingRecord.java index 664f4b51b27..f15b7c434c8 100644 --- a/checker/tests/nullness-records/NormalizingRecord.java +++ b/checker/tests/nullness-records/NormalizingRecord.java @@ -9,52 +9,52 @@ public class NormalizingRecord {} // Framework. record NormalizingRecord1(@Nullable String s) { - NormalizingRecord1(String s) { - if (s.equals("")) { - this.s = null; - } else { - this.s = s; + NormalizingRecord1(String s) { + if (s.equals("")) { + this.s = null; + } else { + this.s = s; + } } - } } record NormalizingRecord2(String s) { - NormalizingRecord2(@Nullable String s) { - if (s == null) { - s = ""; + NormalizingRecord2(@Nullable String s) { + if (s == null) { + s = ""; + } + this.s = s; } - this.s = s; - } } record NormalizingRecordIllegalConstructor1(String s) { - NormalizingRecordIllegalConstructor1(@Nullable String s) { - // :: error: (assignment.type.incompatible) - this.s = s; - } + NormalizingRecordIllegalConstructor1(@Nullable String s) { + // :: error: (assignment.type.incompatible) + this.s = s; + } } record NormalizingRecordIllegalConstructor2(@Nullable String s) { - NormalizingRecordIllegalConstructor2(String s) { - if (s.equals("")) { - // The formal parametr type is @NonNull, so this assignment to it is illegal. - // :: error: (assignment.type.incompatible) - s = null; + NormalizingRecordIllegalConstructor2(String s) { + if (s.equals("")) { + // The formal parametr type is @NonNull, so this assignment to it is illegal. + // :: error: (assignment.type.incompatible) + s = null; + } + this.s = s; } - this.s = s; - } } class Client { - // :: error: (argument.type.incompatible) - NormalizingRecord1 nr1_1 = new NormalizingRecord1(null); - NormalizingRecord1 nr1_2 = new NormalizingRecord1(""); - NormalizingRecord1 nr1_3 = new NormalizingRecord1("hello"); - @Nullable String nble = nr1_2.s(); + // :: error: (argument.type.incompatible) + NormalizingRecord1 nr1_1 = new NormalizingRecord1(null); + NormalizingRecord1 nr1_2 = new NormalizingRecord1(""); + NormalizingRecord1 nr1_3 = new NormalizingRecord1("hello"); + @Nullable String nble = nr1_2.s(); - NormalizingRecord2 nr2_1 = new NormalizingRecord2(null); - NormalizingRecord2 nr2_2 = new NormalizingRecord2(""); - NormalizingRecord2 nr2_3 = new NormalizingRecord2("hello"); - @NonNull String nn = nr2_1.s(); + NormalizingRecord2 nr2_1 = new NormalizingRecord2(null); + NormalizingRecord2 nr2_2 = new NormalizingRecord2(""); + NormalizingRecord2 nr2_3 = new NormalizingRecord2("hello"); + @NonNull String nn = nr2_1.s(); } diff --git a/checker/tests/nullness-records/RecordPurity.java b/checker/tests/nullness-records/RecordPurity.java index 83be0b5d06d..f6a866d10f3 100644 --- a/checker/tests/nullness-records/RecordPurity.java +++ b/checker/tests/nullness-records/RecordPurity.java @@ -3,90 +3,90 @@ // @below-java17-jdk-skip-test record RecordPurity(@Nullable String first, @Nullable String second) { - public String checkNullnessOfFields() { - // :: error: (dereference.of.nullable) - return first.toString() + " " + second.toString(); - } - - public String checkNullnessOfAccessors() { - // :: error: (dereference.of.nullable) - return first().toString() + " " + second().toString(); - } - - public String checkPurityOfFields() { - if (first == null || second == null) return ""; - else return "" + first.length() + second.length(); - } - - public static String checkPurityOfDefaultAccessor(RecordPurity r) { - if (r.first() == null || r.second() == null) return ""; - else return "" + r.first().length() + " " + r.second().length(); - } - - public String checkPurityOfDefaultAccessorSelf() { - if (first() == null || second() == null) return ""; - else return "" + first().length() + " " + second().length(); - } - - public String checkPurityOfDefaultAccessorSelf2() { - if (first() == null) return ""; - if (second() == null) return ""; - - return "" + first().length() + " " + second().length(); - } - - public String checkPurityOfDefaultAccessorSelfFirst() { - if (first() == null) return ""; - else return "" + "".length() + first().length(); - } - - public String checkPurityOfDefaultAccessorSelfFirst2() { - if (first() == null) return ""; - else return "" + "".length() + first().length() + first().length(); - } - - @Pure - public @Nullable String pureMethod() { - return ""; - } - - @Pure - public @Nullable String pureMethod2() { - return null; - } - - public String checkPurityOfAccessor4() { - if (pureMethod() == null) return ""; - else return "" + pureMethod().toString(); - } - - public String checkPurityOfAccessor5() { - if (pureMethod() == null || pureMethod2() == null) return ""; - else return "" + pureMethod().length() + pureMethod2().length(); - } - - // An unrelated non-pure method of same name: - public @Nullable String first(java.util.List ss) { - return ss.isEmpty() ? null : ss.get(0); - } - - // An unrelated pure method of same name: - @Pure - public @Nullable String second(java.util.List ss) { - return ss.isEmpty() ? null : ss.get(1); - } - - public String checkPurityOfImpureMethod() { - java.util.List ss = java.util.List.of(); - if (first(ss) == null) return ""; - else - // :: error: (dereference.of.nullable) - return "" + "".length() + first(ss).length(); - } - - public String checkPurityOfPureMethod() { - java.util.List ss = java.util.List.of(); - if (second(ss) == null) return ""; - else return "" + "".length() + second(ss).length(); - } + public String checkNullnessOfFields() { + // :: error: (dereference.of.nullable) + return first.toString() + " " + second.toString(); + } + + public String checkNullnessOfAccessors() { + // :: error: (dereference.of.nullable) + return first().toString() + " " + second().toString(); + } + + public String checkPurityOfFields() { + if (first == null || second == null) return ""; + else return "" + first.length() + second.length(); + } + + public static String checkPurityOfDefaultAccessor(RecordPurity r) { + if (r.first() == null || r.second() == null) return ""; + else return "" + r.first().length() + " " + r.second().length(); + } + + public String checkPurityOfDefaultAccessorSelf() { + if (first() == null || second() == null) return ""; + else return "" + first().length() + " " + second().length(); + } + + public String checkPurityOfDefaultAccessorSelf2() { + if (first() == null) return ""; + if (second() == null) return ""; + + return "" + first().length() + " " + second().length(); + } + + public String checkPurityOfDefaultAccessorSelfFirst() { + if (first() == null) return ""; + else return "" + "".length() + first().length(); + } + + public String checkPurityOfDefaultAccessorSelfFirst2() { + if (first() == null) return ""; + else return "" + "".length() + first().length() + first().length(); + } + + @Pure + public @Nullable String pureMethod() { + return ""; + } + + @Pure + public @Nullable String pureMethod2() { + return null; + } + + public String checkPurityOfAccessor4() { + if (pureMethod() == null) return ""; + else return "" + pureMethod().toString(); + } + + public String checkPurityOfAccessor5() { + if (pureMethod() == null || pureMethod2() == null) return ""; + else return "" + pureMethod().length() + pureMethod2().length(); + } + + // An unrelated non-pure method of same name: + public @Nullable String first(java.util.List ss) { + return ss.isEmpty() ? null : ss.get(0); + } + + // An unrelated pure method of same name: + @Pure + public @Nullable String second(java.util.List ss) { + return ss.isEmpty() ? null : ss.get(1); + } + + public String checkPurityOfImpureMethod() { + java.util.List ss = java.util.List.of(); + if (first(ss) == null) return ""; + else + // :: error: (dereference.of.nullable) + return "" + "".length() + first(ss).length(); + } + + public String checkPurityOfPureMethod() { + java.util.List ss = java.util.List.of(); + if (second(ss) == null) return ""; + else return "" + "".length() + second(ss).length(); + } } diff --git a/checker/tests/nullness-records/RecordPurityGeneric.java b/checker/tests/nullness-records/RecordPurityGeneric.java index be951f9691d..e139599a8b0 100644 --- a/checker/tests/nullness-records/RecordPurityGeneric.java +++ b/checker/tests/nullness-records/RecordPurityGeneric.java @@ -2,46 +2,46 @@ // @below-java17-jdk-skip-test record RecordPurityGeneric(A a, B b) { - public String checkNullnessOfFields() { - // :: error: (dereference.of.nullable) - return a.toString() + " " + b.toString(); - } - - public String checkNullnessOfAccessors() { - // :: error: (dereference.of.nullable) - return a().toString() + " " + b().toString(); - } - - public static String checkNullnessOfFields( - RecordPurityGeneric<@Nullable String, @Nullable String> r) { - // :: error: (dereference.of.nullable) - return r.a.toString() + " " + r.b.toString(); - } - - public static String checkNullnessOfAccessors( - RecordPurityGeneric<@Nullable String, @Nullable String> r) { - // :: error: (dereference.of.nullable) - return r.a().toString() + " " + r.b().toString(); - } - - public String checkPurityOfFields() { - if (a == null || b == null) return ""; - else return a.toString() + " " + b.toString(); - } - - public String checkPurityOfFields(RecordPurityGeneric<@Nullable String, @Nullable String> r) { - if (r.a == null || r.b == null) return ""; - else return r.a.toString() + " " + r.b.toString(); - } - - public static String checkPurityOfDefaultAccessor( - RecordPurityGeneric<@Nullable String, @Nullable String> r) { - if (r.a() == null || r.b() == null) return ""; - else return r.a().toString() + " " + r.b().toString(); - } - - public String checkPurityOfDefaultAccessorSelf() { - if (a() == null || b() == null) return ""; - else return a().toString() + " " + b().toString(); - } + public String checkNullnessOfFields() { + // :: error: (dereference.of.nullable) + return a.toString() + " " + b.toString(); + } + + public String checkNullnessOfAccessors() { + // :: error: (dereference.of.nullable) + return a().toString() + " " + b().toString(); + } + + public static String checkNullnessOfFields( + RecordPurityGeneric<@Nullable String, @Nullable String> r) { + // :: error: (dereference.of.nullable) + return r.a.toString() + " " + r.b.toString(); + } + + public static String checkNullnessOfAccessors( + RecordPurityGeneric<@Nullable String, @Nullable String> r) { + // :: error: (dereference.of.nullable) + return r.a().toString() + " " + r.b().toString(); + } + + public String checkPurityOfFields() { + if (a == null || b == null) return ""; + else return a.toString() + " " + b.toString(); + } + + public String checkPurityOfFields(RecordPurityGeneric<@Nullable String, @Nullable String> r) { + if (r.a == null || r.b == null) return ""; + else return r.a.toString() + " " + r.b.toString(); + } + + public static String checkPurityOfDefaultAccessor( + RecordPurityGeneric<@Nullable String, @Nullable String> r) { + if (r.a() == null || r.b() == null) return ""; + else return r.a().toString() + " " + r.b().toString(); + } + + public String checkPurityOfDefaultAccessorSelf() { + if (a() == null || b() == null) return ""; + else return a().toString() + " " + b().toString(); + } } diff --git a/checker/tests/nullness-records/RecordPurityOverride.java b/checker/tests/nullness-records/RecordPurityOverride.java index d372cf04619..422ef1b4fd7 100644 --- a/checker/tests/nullness-records/RecordPurityOverride.java +++ b/checker/tests/nullness-records/RecordPurityOverride.java @@ -2,37 +2,37 @@ import org.checkerframework.dataflow.qual.Pure; record RecordPurityOverride(@Nullable String pure, @Nullable String impure) { - @Pure - public @Nullable String pure() { - return pure; - } + @Pure + public @Nullable String pure() { + return pure; + } - // Note: not @Pure - public @Nullable String impure() { - return impure; - } + // Note: not @Pure + public @Nullable String impure() { + return impure; + } - public String checkPurityOfFields() { - if (pure == null || impure == null) return ""; - else return pure.toString() + " " + impure.toString(); - } + public String checkPurityOfFields() { + if (pure == null || impure == null) return ""; + else return pure.toString() + " " + impure.toString(); + } - public String checkPurityOfAccessor1() { - if (pure() == null || impure() == null) return ""; - else - // :: error: (dereference.of.nullable) - return pure().toString() + " " + impure().toString(); - } + public String checkPurityOfAccessor1() { + if (pure() == null || impure() == null) return ""; + else + // :: error: (dereference.of.nullable) + return pure().toString() + " " + impure().toString(); + } - public String checkPurityOfAccessor2() { - if (pure() == null) return ""; - else return pure().toString(); - } + public String checkPurityOfAccessor2() { + if (pure() == null) return ""; + else return pure().toString(); + } - public String checkPurityOfAccessor3() { - if (impure() == null) return ""; - else - // :: error: (dereference.of.nullable) - return impure().toString(); - } + public String checkPurityOfAccessor3() { + if (impure() == null) return ""; + else + // :: error: (dereference.of.nullable) + return impure().toString(); + } } diff --git a/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java b/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java index a900a84eeca..efe11600cbf 100644 --- a/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java +++ b/checker/tests/nullness-reflection/NullnessReflectionExampleTest.java @@ -1,33 +1,34 @@ -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.common.reflection.qual.MethodVal; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + /** Example used in the reflection resolution section of the Checker Framework manual. */ public class NullnessReflectionExampleTest { - @NonNull Location getCurrentLocation() { - // ... - return new Location(); - } + @NonNull Location getCurrentLocation() { + // ... + return new Location(); + } - String getCurrentCity() - throws NoSuchMethodException, - SecurityException, - IllegalAccessException, - IllegalArgumentException, - InvocationTargetException { - @MethodVal( - className = "NullnessReflectionExampleTest", - methodName = "getCurrentLocation", - params = 0) - Method toLowerCase = getClass().getMethod("getCurrentLocation"); - Location currentLocation = (Location) toLowerCase.invoke(this); - return currentLocation.nameOfCity(); - } + String getCurrentCity() + throws NoSuchMethodException, + SecurityException, + IllegalAccessException, + IllegalArgumentException, + InvocationTargetException { + @MethodVal( + className = "NullnessReflectionExampleTest", + methodName = "getCurrentLocation", + params = 0) + Method toLowerCase = getClass().getMethod("getCurrentLocation"); + Location currentLocation = (Location) toLowerCase.invoke(this); + return currentLocation.nameOfCity(); + } } class Location { - String nameOfCity() { - return "Seattle"; - } + String nameOfCity() { + return "Seattle"; + } } diff --git a/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java b/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java index e5d55c865f9..34c011a5fe2 100644 --- a/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java +++ b/checker/tests/nullness-reflection/NullnessReflectionResolutionTest.java @@ -1,49 +1,50 @@ -import java.lang.reflect.Method; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.reflection.qual.MethodVal; +import java.lang.reflect.Method; + /** Testing that reflection resolution uses more precise annotations for the Nullness Checker. */ public class NullnessReflectionResolutionTest { - @NonNull Object returnNonNull() { - return new Object(); - } + @NonNull Object returnNonNull() { + return new Object(); + } - void testReturnNonNull( - @MethodVal( - className = "NullnessReflectionResolutionTest", - methodName = "returnNonNull", - params = 0) - Method m) - throws Exception { - @NonNull Object o = m.invoke(this); - } + void testReturnNonNull( + @MethodVal( + className = "NullnessReflectionResolutionTest", + methodName = "returnNonNull", + params = 0) + Method m) + throws Exception { + @NonNull Object o = m.invoke(this); + } - void paramNullable(@Nullable Object param1, @Nullable Object param2) {} + void paramNullable(@Nullable Object param1, @Nullable Object param2) {} - void testParamNullable( - @MethodVal( - className = "NullnessReflectionResolutionTest", - methodName = "paramNullable", - params = 2) - Method m) - throws Exception { - @NonNull Object o = m.invoke(this, null, null); - } + void testParamNullable( + @MethodVal( + className = "NullnessReflectionResolutionTest", + methodName = "paramNullable", + params = 2) + Method m) + throws Exception { + @NonNull Object o = m.invoke(this, null, null); + } - static @NonNull Object paramAndReturnNonNullStatic( - @Nullable Object param1, @Nullable Object param2) { - return new Object(); - } + static @NonNull Object paramAndReturnNonNullStatic( + @Nullable Object param1, @Nullable Object param2) { + return new Object(); + } - void testParamAndReturnNonNullStatic( - @MethodVal( - className = "NullnessReflectionResolutionTest", - methodName = "paramAndReturnNonNullStatic", - params = 2) - Method m) - throws Exception { - @NonNull Object o1 = m.invoke(this, null, null); - @NonNull Object o2 = m.invoke(null, null, null); - } + void testParamAndReturnNonNullStatic( + @MethodVal( + className = "NullnessReflectionResolutionTest", + methodName = "paramAndReturnNonNullStatic", + params = 2) + Method m) + throws Exception { + @NonNull Object o1 = m.invoke(this, null, null); + @NonNull Object o2 = m.invoke(null, null, null); + } } diff --git a/checker/tests/nullness-reflection/VoidTest.java b/checker/tests/nullness-reflection/VoidTest.java index 663612d0f04..a6d88928b06 100644 --- a/checker/tests/nullness-reflection/VoidTest.java +++ b/checker/tests/nullness-reflection/VoidTest.java @@ -1,6 +1,6 @@ public class VoidTest { - Class test() { - return void.class; - } + Class test() { + return void.class; + } } diff --git a/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java b/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java index 976143529bf..8fd522c1d31 100644 --- a/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java +++ b/checker/tests/nullness-safedefaultsbytecode/AnnotatedJdkTest.java @@ -1,15 +1,16 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; // There should be no warnings for the following operations // if the annotated JDK is loaded properly public class AnnotatedJdkTest { - String toStringTest(Object v) { - return v.toString(); - } + String toStringTest(Object v) { + return v.toString(); + } - Set<@KeyFor("#1") String> keySetTest(HashMap map) { - return map.keySet(); - } + Set<@KeyFor("#1") String> keySetTest(HashMap map) { + return map.keySet(); + } } diff --git a/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java b/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java index 7dd3163a004..d5f02f2810d 100644 --- a/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java +++ b/checker/tests/nullness-safedefaultsbytecode/ArraysMDE.java @@ -4,27 +4,27 @@ public final class ArraysMDE { - public static int indexOf(Object[] a, Object[] sub) { - int a_index_max = a.length - sub.length + 1; - for (int i = 0; i <= a_index_max; i++) { - if (isSubarray(a, sub, i)) { - return i; - } + public static int indexOf(Object[] a, Object[] sub) { + int a_index_max = a.length - sub.length + 1; + for (int i = 0; i <= a_index_max; i++) { + if (isSubarray(a, sub, i)) { + return i; + } + } + return -1; } - return -1; - } - public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { - int a_len = a.length - a_offset; - int sub_len = sub.length; - if (a_len < sub_len) { - return false; + public static boolean isSubarray(Object[] a, Object[] sub, int a_offset) { + int a_len = a.length - a_offset; + int sub_len = sub.length; + if (a_len < sub_len) { + return false; + } + for (int i = 0; i < sub_len; i++) { + if (!Objects.equals(sub[i], a[a_offset + i])) { + return false; + } + } + return true; } - for (int i = 0; i < sub_len; i++) { - if (!Objects.equals(sub[i], a[a_offset + i])) { - return false; - } - } - return true; - } } diff --git a/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java b/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java index e636d06894e..b2265edc9aa 100644 --- a/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java +++ b/checker/tests/nullness-safedefaultsbytecode/BytecodeDefaultsTest.java @@ -4,24 +4,24 @@ // affect defaulting nor suppress errors in source code. public class BytecodeDefaultsTest { - void f() { - g(""); - } + void f() { + g(""); + } - void g(String s) {} + void g(String s) {} } @AnnotatedFor("nullness") class HasErrors { - Object f() { - // :: error: (return.type.incompatible) - return null; - } + Object f() { + // :: error: (return.type.incompatible) + return null; + } } class HasErrors2 { - Object f() { - // :: error: (return.type.incompatible) - return null; - } + Object f() { + // :: error: (return.type.incompatible) + return null; + } } diff --git a/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java b/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java index c9dc12b87c9..a12e7ebc23a 100644 --- a/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java +++ b/checker/tests/nullness-safedefaultssourcecode/BasicSafeDefaultsTest.java @@ -7,44 +7,44 @@ @AnnotatedFor("nullness") public class BasicSafeDefaultsTest { - void m1() { - @NonNull Object x1 = SdfuscLib.unannotated(); - // :: error: (assignment.type.incompatible) - @NonNull Object x2 = SdfuscLib.returnsNullable(); - @NonNull Object x3 = SdfuscLib.returnsNonNull(); - // :: error: (assignment.type.incompatible) - @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - // :: error: (assignment.type.incompatible) - @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m1() { + @NonNull Object x1 = SdfuscLib.unannotated(); + // :: error: (assignment.type.incompatible) + @NonNull Object x2 = SdfuscLib.returnsNullable(); + @NonNull Object x3 = SdfuscLib.returnsNonNull(); + // :: error: (assignment.type.incompatible) + @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + // :: error: (assignment.type.incompatible) + @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } - void m2() { - @Nullable Object x1 = SdfuscLib.unannotated(); - @Nullable Object x2 = SdfuscLib.returnsNullable(); - @Nullable Object x3 = SdfuscLib.returnsNonNull(); - @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m2() { + @Nullable Object x1 = SdfuscLib.unannotated(); + @Nullable Object x2 = SdfuscLib.returnsNullable(); + @Nullable Object x3 = SdfuscLib.returnsNonNull(); + @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } } class BasicTestNotAnnotatedFor { - void m1() { - @NonNull Object x1 = SdfuscLib.unannotated(); - @NonNull Object x2 = SdfuscLib.returnsNullable(); - @NonNull Object x3 = SdfuscLib.returnsNonNull(); - @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m1() { + @NonNull Object x1 = SdfuscLib.unannotated(); + @NonNull Object x2 = SdfuscLib.returnsNullable(); + @NonNull Object x3 = SdfuscLib.returnsNonNull(); + @NonNull Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + @NonNull Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @NonNull Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } - void m2() { - @Nullable Object x1 = SdfuscLib.unannotated(); - @Nullable Object x2 = SdfuscLib.returnsNullable(); - @Nullable Object x3 = SdfuscLib.returnsNonNull(); - @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); - @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); - @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); - } + void m2() { + @Nullable Object x1 = SdfuscLib.unannotated(); + @Nullable Object x2 = SdfuscLib.returnsNullable(); + @Nullable Object x3 = SdfuscLib.returnsNonNull(); + @Nullable Object x4 = SdfuscLibNotAnnotatedFor.unannotated(); + @Nullable Object x5 = SdfuscLibNotAnnotatedFor.returnsNullable(); + @Nullable Object x6 = SdfuscLibNotAnnotatedFor.returnsNonNull(); + } } diff --git a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java index b334898d2c9..417721ecb0a 100644 --- a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java +++ b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java @@ -5,11 +5,11 @@ @AnnotatedFor("nullness") public class Issue3449 { - int length; - Object[] objs; + int length; + Object[] objs; - public Issue3449(Object... args) { - length = args.length; - objs = args; - } + public Issue3449(Object... args) { + length = args.length; + objs = args; + } } diff --git a/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java b/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java index a2d2be0f170..1d70bcd18c4 100644 --- a/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java +++ b/checker/tests/nullness-safedefaultssourcecode/PrimitiveClassLiteral.java @@ -3,31 +3,31 @@ @AnnotatedFor("nullness") public class PrimitiveClassLiteral { - private static @Nullable Class unwrapPrimitive(Class c) { - if (c == Byte.class) { - return byte.class; + private static @Nullable Class unwrapPrimitive(Class c) { + if (c == Byte.class) { + return byte.class; + } + if (c == Character.class) { + return char.class; + } + if (c == Short.class) { + return short.class; + } + if (c == Integer.class) { + return int.class; + } + if (c == Long.class) { + return long.class; + } + if (c == Float.class) { + return float.class; + } + if (c == Double.class) { + return double.class; + } + if (c == Boolean.class) { + return boolean.class; + } + return c; } - if (c == Character.class) { - return char.class; - } - if (c == Short.class) { - return short.class; - } - if (c == Integer.class) { - return int.class; - } - if (c == Long.class) { - return long.class; - } - if (c == Float.class) { - return float.class; - } - if (c == Double.class) { - return double.class; - } - if (c == Boolean.class) { - return boolean.class; - } - return c; - } } diff --git a/checker/tests/nullness-safedefaultssourcecodelib/Lib.java b/checker/tests/nullness-safedefaultssourcecodelib/Lib.java index 330bd55c562..a6b2801d7e5 100644 --- a/checker/tests/nullness-safedefaultssourcecodelib/Lib.java +++ b/checker/tests/nullness-safedefaultssourcecodelib/Lib.java @@ -7,29 +7,29 @@ @AnnotatedFor("nullness") class SdfuscLib { - static Object unannotated() { - return new Object(); - } + static Object unannotated() { + return new Object(); + } - static @Nullable Object returnsNullable() { - return new Object(); - } + static @Nullable Object returnsNullable() { + return new Object(); + } - static @NonNull Object returnsNonNull() { - return new Object(); - } + static @NonNull Object returnsNonNull() { + return new Object(); + } } class SdfuscLibNotAnnotatedFor { - static Object unannotated() { - return new Object(); - } + static Object unannotated() { + return new Object(); + } - static @Nullable Object returnsNullable() { - return new Object(); - } + static @Nullable Object returnsNullable() { + return new Object(); + } - static @NonNull Object returnsNonNull() { - return new Object(); - } + static @NonNull Object returnsNonNull() { + return new Object(); + } } diff --git a/checker/tests/nullness-skipdefs/SkipDefs1.java b/checker/tests/nullness-skipdefs/SkipDefs1.java index ed8329fa066..ba0320c7cbd 100644 --- a/checker/tests/nullness-skipdefs/SkipDefs1.java +++ b/checker/tests/nullness-skipdefs/SkipDefs1.java @@ -2,16 +2,16 @@ public class SkipDefs1 { - static class SkipMe { - static Object foo() { - return null; + static class SkipMe { + static Object foo() { + return null; + } } - } - static class DontSkip { - static Object foo() { - // :: error: (return.type.incompatible) - return null; + static class DontSkip { + static Object foo() { + // :: error: (return.type.incompatible) + return null; + } } - } } diff --git a/checker/tests/nullness-skipdefs/SkipDefs2.java b/checker/tests/nullness-skipdefs/SkipDefs2.java index 3246aaa095e..da7c72e580e 100644 --- a/checker/tests/nullness-skipdefs/SkipDefs2.java +++ b/checker/tests/nullness-skipdefs/SkipDefs2.java @@ -2,17 +2,17 @@ public class SkipDefs2 { - static class SkipMe { - @Nullable Object f; + static class SkipMe { + @Nullable Object f; - @EnsuresNonNull("f") - static void foo() {} - } + @EnsuresNonNull("f") + static void foo() {} + } - static class DontSkip { - static Object foo() { - // :: error: (return.type.incompatible) - return null; + static class DontSkip { + static Object foo() { + // :: error: (return.type.incompatible) + return null; + } } - } } diff --git a/checker/tests/nullness-skipdirs/skip/SkipDirs1.java b/checker/tests/nullness-skipdirs/skip/SkipDirs1.java index dd3b88be43c..0c30c590c93 100644 --- a/checker/tests/nullness-skipdirs/skip/SkipDirs1.java +++ b/checker/tests/nullness-skipdirs/skip/SkipDirs1.java @@ -2,17 +2,17 @@ public class SkipDirs1 { - static class DontSkipMe { - static Object foo() { - // :: error: (return) - return null; + static class DontSkipMe { + static Object foo() { + // :: error: (return) + return null; + } } - } - static class DontSkip { - static Object foo() { - // :: error: (return) - return null; + static class DontSkip { + static Object foo() { + // :: error: (return) + return null; + } } - } } diff --git a/checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java b/checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java index b68e744d10f..9a870854d44 100644 --- a/checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java +++ b/checker/tests/nullness-skipdirs/skip/this/SkipDirs2.java @@ -1,14 +1,14 @@ import org.checkerframework.checker.nullness.qual.*; public class SkipDirs2 { - static class SkipMe { + static class SkipMe { - Object f; + Object f; - // If this test is NOT skipped, it should issue an "unexpected error" since - // There is a type error between f2 (Nullable) and f (NonNull). - void foo(@Nullable Object f2) { - f = f2; + // If this test is NOT skipped, it should issue an "unexpected error" since + // There is a type error between f2 (Nullable) and f (NonNull). + void foo(@Nullable Object f2) { + f = f2; + } } - } } diff --git a/checker/tests/nullness-skipuses/SkipUses1.java b/checker/tests/nullness-skipuses/SkipUses1.java index 4ef6a0ae8ec..24d71768742 100644 --- a/checker/tests/nullness-skipuses/SkipUses1.java +++ b/checker/tests/nullness-skipuses/SkipUses1.java @@ -2,26 +2,26 @@ public class SkipUses1 { - static class SkipMe { - static @Nullable Object foo() { - return null; + static class SkipMe { + static @Nullable Object foo() { + return null; + } } - } - static class DontSkip { - static @Nullable Object foo() { - return null; + static class DontSkip { + static @Nullable Object foo() { + return null; + } } - } - static class Main { - void bar(boolean b) { - @NonNull Object x = SkipMe.foo(); - // :: error: (assignment.type.incompatible) - @NonNull Object y = DontSkip.foo(); + static class Main { + void bar(boolean b) { + @NonNull Object x = SkipMe.foo(); + // :: error: (assignment.type.incompatible) + @NonNull Object y = DontSkip.foo(); - // :: error: (assignment.type.incompatible) - @NonNull Object z = b ? SkipMe.foo() : DontSkip.foo(); + // :: error: (assignment.type.incompatible) + @NonNull Object z = b ? SkipMe.foo() : DontSkip.foo(); + } } - } } diff --git a/checker/tests/nullness-skipuses/SkipUses2.java b/checker/tests/nullness-skipuses/SkipUses2.java index 9c2a5ff5a52..dd12f4c8cd8 100644 --- a/checker/tests/nullness-skipuses/SkipUses2.java +++ b/checker/tests/nullness-skipuses/SkipUses2.java @@ -3,29 +3,29 @@ public class SkipUses2 { - static class SkipMe { - static @Nullable Object f; + static class SkipMe { + static @Nullable Object f; - @RequiresNonNull("f") - static void foo() {} - } + @RequiresNonNull("f") + static void foo() {} + } - static class DontSkip { - static @Nullable Object f; + static class DontSkip { + static @Nullable Object f; - @RequiresNonNull("f") - static @Nullable Object foo() { - return null; + @RequiresNonNull("f") + static @Nullable Object foo() { + return null; + } } - } - static class Main { - void bar(boolean b) { - SkipMe.f = null; - SkipMe.foo(); - DontSkip.f = null; - // :: error: (contracts.precondition.not.satisfied) - DontSkip.foo(); + static class Main { + void bar(boolean b) { + SkipMe.f = null; + SkipMe.foo(); + DontSkip.f = null; + // :: error: (contracts.precondition.not.satisfied) + DontSkip.foo(); + } } - } } diff --git a/checker/tests/nullness-stubfile/Issue4598.java b/checker/tests/nullness-stubfile/Issue4598.java index a5ab401f384..3b4d568d5e5 100644 --- a/checker/tests/nullness-stubfile/Issue4598.java +++ b/checker/tests/nullness-stubfile/Issue4598.java @@ -1,15 +1,16 @@ // Test case for Issue #4598 -import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + public class Issue4598 { - final @Nullable Object d = null; + final @Nullable Object d = null; - public Object foo() { - Objects.requireNonNull(d, "destination"); - // :: error: (return.type.incompatible) - return d; - } + public Object foo() { + Objects.requireNonNull(d, "destination"); + // :: error: (return.type.incompatible) + return d; + } } diff --git a/checker/tests/nullness-stubfile/NullnessStubfileMerge.java b/checker/tests/nullness-stubfile/NullnessStubfileMerge.java index b1aec1cb611..6139ac75eb8 100644 --- a/checker/tests/nullness-stubfile/NullnessStubfileMerge.java +++ b/checker/tests/nullness-stubfile/NullnessStubfileMerge.java @@ -22,26 +22,26 @@ public final class String { } */ public class NullnessStubfileMerge { - @Nullable String nullString = null; - @NonNull String nonNull = "Hello!"; + @Nullable String nullString = null; + @NonNull String nonNull = "Hello!"; - void method() { - // below fails because of stub file overruling annotated JDK - // :: error: (type.argument.type.incompatible) - java.util.List<@NonNull String> l; + void method() { + // below fails because of stub file overruling annotated JDK + // :: error: (type.argument.type.incompatible) + java.util.List<@NonNull String> l; - // :: error: (assignment.type.incompatible) - @NonNull String error1 = nonNull.intern(); + // :: error: (assignment.type.incompatible) + @NonNull String error1 = nonNull.intern(); - nonNull.substring('!'); + nonNull.substring('!'); - @NonNull String y = nonNull.substring('!'); + @NonNull String y = nonNull.substring('!'); - char[] nonNullChars = {'1', '1'}; - char[] nullChars = null; - nonNull.getChars(1, 1, nonNullChars, 1); + char[] nonNullChars = {'1', '1'}; + char[] nullChars = null; + nonNull.getChars(1, 1, nonNullChars, 1); - // :: error: (argument.type.incompatible) - nonNull.getChars(1, 1, nullChars, 1); - } + // :: error: (argument.type.incompatible) + nonNull.getChars(1, 1, nullChars, 1); + } } diff --git a/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java b/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java index 08e27d7111f..1f04a655db4 100644 --- a/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java +++ b/checker/tests/nullness-warnredundantannotations/AnnoOnTypeVariableCrashCase.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class AnnoOnTypeVariableCrashCase { - @Nullable T test() { - return (@Nullable T) null; - } + @Nullable T test() { + return (@Nullable T) null; + } } diff --git a/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java b/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java index d1377fb53ec..1f541b09391 100644 --- a/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java +++ b/checker/tests/nullness-warnredundantannotations/RedundantAnnoWithDefaultQualifier.java @@ -5,15 +5,15 @@ @DefaultQualifier(Nullable.class) public class RedundantAnnoWithDefaultQualifier { - // :: warning: (redundant.anno) - void foo(@Nullable String message) {} + // :: warning: (redundant.anno) + void foo(@Nullable String message) {} - // :: warning: (redundant.anno) - @Nullable Integer foo() { - return 5; - } + // :: warning: (redundant.anno) + @Nullable Integer foo() { + return 5; + } - void bar(String p) {} + void bar(String p) {} - void baz(@NonNull String p) {} + void baz(@NonNull String p) {} } diff --git a/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java b/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java index be9c3747887..c7cc62fe006 100644 --- a/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java +++ b/checker/tests/nullness-warnredundantannotations/RedundantAnnotation.java @@ -1,6 +1,7 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.io.InputStream; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; /* Check for redundant annotations in the following locations @@ -26,65 +27,65 @@ */ @NonNull class RedundantAnnotation< - // TODO :: warning: (redundant.anno) - T extends @Nullable Object> { + // TODO :: warning: (redundant.anno) + T extends @Nullable Object> { + + enum InnerEnum { + // TODO :: warning: (redundant.anno) + // :: error: (nullness.on.enum) + @NonNull EXPLICIT, + IMPLICIT, + } - enum InnerEnum { - // TODO :: warning: (redundant.anno) - // :: error: (nullness.on.enum) - @NonNull EXPLICIT, - IMPLICIT, - } + // :: warning: (redundant.anno) + @NonNull Object f; - // :: warning: (redundant.anno) - @NonNull Object f; + // :: warning: (redundant.anno) + @NonNull Integer foo(InputStream arg) { + // :: warning: (redundant.anno) + @Nullable Object local; + return Integer.valueOf(1); + } - // :: warning: (redundant.anno) - @NonNull Integer foo(InputStream arg) { // :: warning: (redundant.anno) - @Nullable Object local; - return Integer.valueOf(1); - } - - // :: warning: (redundant.anno) - void foo2(@NonNull Integer i) {} - - // TODO :: warning: (redundant.anno) - // :: error: (nullness.on.constructor) - @NonNull RedundantAnnotation() { - f = new Object(); - } - - // :: error: (nullness.on.receiver) - // :: warning: (redundant.anno) - void bar(@NonNull RedundantAnnotation this, InputStream arg) throws Exception { + void foo2(@NonNull Integer i) {} + + // TODO :: warning: (redundant.anno) + // :: error: (nullness.on.constructor) + @NonNull RedundantAnnotation() { + f = new Object(); + } + + // :: error: (nullness.on.receiver) // :: warning: (redundant.anno) - try (@Nullable InputStream in = arg) { + void bar(@NonNull RedundantAnnotation this, InputStream arg) throws Exception { + // :: warning: (redundant.anno) + try (@Nullable InputStream in = arg) { - // :: warning: (redundant.anno) - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull Exception e) { + // :: warning: (redundant.anno) + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull Exception e) { - } + } - // TODO :: warning: (redundant.anno) warning on the upper bound - List l; + // TODO :: warning: (redundant.anno) warning on the upper bound + List l; - // TODO :: warning: (redundant.anno) warning on the lower bound - // :: error: (type.invalid.super.wildcard) - List l2; + // TODO :: warning: (redundant.anno) warning on the lower bound + // :: error: (type.invalid.super.wildcard) + List l2; - Object obj = null; - // TODO :: warning: (redundant.anno) for the typecast - String x = (@Nullable String) obj; + Object obj = null; + // TODO :: warning: (redundant.anno) for the typecast + String x = (@Nullable String) obj; - // TODO :: warning: (redundant.anno) for the instanceof - // :: error: (instanceof.nullable) - boolean b = x instanceof @Nullable String; + // TODO :: warning: (redundant.anno) for the instanceof + // :: error: (instanceof.nullable) + boolean b = x instanceof @Nullable String; - // TODO :: warning: (redundant.anno) on the component type - @NonNull String[] strs; - // TODO :: warning: (redundant.anno) on the component type - strs = new @NonNull String[10]; - } + // TODO :: warning: (redundant.anno) on the component type + @NonNull String[] strs; + // TODO :: warning: (redundant.anno) on the component type + strs = new @NonNull String[10]; + } } diff --git a/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java b/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java index 013bbcf798d..97ec97291f1 100644 --- a/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java +++ b/checker/tests/nullness-warnredundantannotations/RedundantAnnotationOptions.java @@ -1,19 +1,20 @@ -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + class RedundantAnnotationOptions { - private static @Nullable T safeGetAnnotation( - Field f, Class annotationClass) { - @Nullable T annotation; - try { - @Nullable T cast = f.getAnnotation((Class<@NonNull T>) annotationClass); - annotation = cast; - } catch (Exception e) { - annotation = null; - } + private static @Nullable T safeGetAnnotation( + Field f, Class annotationClass) { + @Nullable T annotation; + try { + @Nullable T cast = f.getAnnotation((Class<@NonNull T>) annotationClass); + annotation = cast; + } catch (Exception e) { + annotation = null; + } - return annotation; - } + return annotation; + } } diff --git a/checker/tests/nullness/AliasedAnnotations.java b/checker/tests/nullness/AliasedAnnotations.java index 9495b8f9d9e..2b1cfd08dd4 100644 --- a/checker/tests/nullness/AliasedAnnotations.java +++ b/checker/tests/nullness/AliasedAnnotations.java @@ -3,84 +3,84 @@ public class AliasedAnnotations { - void useNonNullAnnotations() { - // :: error: (assignment.type.incompatible) - @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; - // :: error: (assignment.type.incompatible) - @com.sun.istack.internal.NotNull Object nn2 = null; - // :: error: (assignment.type.incompatible) - @edu.umd.cs.findbugs.annotations.NonNull Object nn3 = null; - // :: error: (assignment.type.incompatible) - @javax.annotation.Nonnull Object nn4 = null; - // Invalid location for NonNull :: error: (assignment.type.incompatible) - // @javax.validation.constraints.NotNull Object nn5 = null; - // :: error: (assignment.type.incompatible) - @org.eclipse.jdt.annotation.NonNull Object nn6 = null; - // :: error: (assignment.type.incompatible) - @org.jetbrains.annotations.NotNull Object nn7 = null; - // :: error: (assignment.type.incompatible) - @org.netbeans.api.annotations.common.NonNull Object nn8 = null; - // :: error: (assignment.type.incompatible) - @org.jmlspecs.annotation.NonNull Object nn9 = null; - } + void useNonNullAnnotations() { + // :: error: (assignment.type.incompatible) + @org.checkerframework.checker.nullness.qual.NonNull Object nn1 = null; + // :: error: (assignment.type.incompatible) + @com.sun.istack.internal.NotNull Object nn2 = null; + // :: error: (assignment.type.incompatible) + @edu.umd.cs.findbugs.annotations.NonNull Object nn3 = null; + // :: error: (assignment.type.incompatible) + @javax.annotation.Nonnull Object nn4 = null; + // Invalid location for NonNull :: error: (assignment.type.incompatible) + // @javax.validation.constraints.NotNull Object nn5 = null; + // :: error: (assignment.type.incompatible) + @org.eclipse.jdt.annotation.NonNull Object nn6 = null; + // :: error: (assignment.type.incompatible) + @org.jetbrains.annotations.NotNull Object nn7 = null; + // :: error: (assignment.type.incompatible) + @org.netbeans.api.annotations.common.NonNull Object nn8 = null; + // :: error: (assignment.type.incompatible) + @org.jmlspecs.annotation.NonNull Object nn9 = null; + } - void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations1(@org.checkerframework.checker.nullness.qual.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations2(@com.sun.istack.internal.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations2(@com.sun.istack.internal.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations3(@edu.umd.cs.findbugs.annotations.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations3(@edu.umd.cs.findbugs.annotations.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations4(@edu.umd.cs.findbugs.annotations.CheckForNull Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations4(@edu.umd.cs.findbugs.annotations.CheckForNull Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations5(@edu.umd.cs.findbugs.annotations.UnknownNullness Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations5(@edu.umd.cs.findbugs.annotations.UnknownNullness Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations6(@javax.annotation.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations6(@javax.annotation.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations7(@javax.annotation.CheckForNull Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations7(@javax.annotation.CheckForNull Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations9(@org.eclipse.jdt.annotation.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations9(@org.eclipse.jdt.annotation.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations10(@org.jetbrains.annotations.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations10(@org.jetbrains.annotations.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations12(@org.netbeans.api.annotations.common.NullAllowed Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations12(@org.netbeans.api.annotations.common.NullAllowed Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations13(@org.netbeans.api.annotations.common.NullUnknown Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations13(@org.netbeans.api.annotations.common.NullUnknown Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } - void useNullableAnnotations14(@org.jmlspecs.annotation.Nullable Object nble) { - // :: error: (dereference.of.nullable) - nble.toString(); - } + void useNullableAnnotations14(@org.jmlspecs.annotation.Nullable Object nble) { + // :: error: (dereference.of.nullable) + nble.toString(); + } } diff --git a/checker/tests/nullness/Aliasing.java b/checker/tests/nullness/Aliasing.java index 8e5676039f9..40ff578c724 100644 --- a/checker/tests/nullness/Aliasing.java +++ b/checker/tests/nullness/Aliasing.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.nullness.qual.*; public class Aliasing { - @NonNull Object nno = new Object(); - @Nullable Object no = null; + @NonNull Object nno = new Object(); + @Nullable Object no = null; - public static void main(String[] args) { - Aliasing a = new Aliasing(); - Aliasing b = new Aliasing(); - m(a, b); - } + public static void main(String[] args) { + Aliasing a = new Aliasing(); + Aliasing b = new Aliasing(); + m(a, b); + } - static void m(@NonNull Aliasing a, @NonNull Aliasing b) { - a.no = b.nno; - // Changing a.no to nonnull does not mean that b.no is also nonnull - // :: error: (assignment.type.incompatible) - b.nno = b.no; + static void m(@NonNull Aliasing a, @NonNull Aliasing b) { + a.no = b.nno; + // Changing a.no to nonnull does not mean that b.no is also nonnull + // :: error: (assignment.type.incompatible) + b.nno = b.no; - System.out.println("@NonNull field b.nno is: " + b.nno); - } + System.out.println("@NonNull field b.nno is: " + b.nno); + } } diff --git a/checker/tests/nullness/AnnotatedJdkEqualsTest.java b/checker/tests/nullness/AnnotatedJdkEqualsTest.java index 9e1015b8f1a..d5ea25699aa 100644 --- a/checker/tests/nullness/AnnotatedJdkEqualsTest.java +++ b/checker/tests/nullness/AnnotatedJdkEqualsTest.java @@ -5,14 +5,14 @@ import java.net.URL; public class AnnotatedJdkEqualsTest { - void foo(URL u) { - // As of this writing, the annotated JDK does not contain a URL.java file - // for the java.net.URL class. - // Nonetheless, the following code should type-check. - // This could be handled via inheritance of annotations from superclasses either during JDK - // creation or during type-checking. It would be impractical to manually annotate every - // method in the entire JDK: it would be too labor-intensive and there would be certain to - // be some oversights. - u.equals(null); - } + void foo(URL u) { + // As of this writing, the annotated JDK does not contain a URL.java file + // for the java.net.URL class. + // Nonetheless, the following code should type-check. + // This could be handled via inheritance of annotations from superclasses either during JDK + // creation or during type-checking. It would be impractical to manually annotate every + // method in the entire JDK: it would be too labor-intensive and there would be certain to + // be some oversights. + u.equals(null); + } } diff --git a/checker/tests/nullness/AnnotatedJdkTest.java b/checker/tests/nullness/AnnotatedJdkTest.java index 86401e5250b..54267f3d2fb 100644 --- a/checker/tests/nullness/AnnotatedJdkTest.java +++ b/checker/tests/nullness/AnnotatedJdkTest.java @@ -1,17 +1,18 @@ // Test case for issue 370: https://github.com/typetools/checker-framework/issues/370 +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; public class AnnotatedJdkTest { - // This code should type-check because of the annotated JDK, which contains: - // class Arrays { - // public static List asList(T... a); - // } - // That JDK annotation should be equivalent to - // public static List asList(T... a); - // because of the CLIMB-to-top defaulting rule. + // This code should type-check because of the annotated JDK, which contains: + // class Arrays { + // public static List asList(T... a); + // } + // That JDK annotation should be equivalent to + // public static List asList(T... a); + // because of the CLIMB-to-top defaulting rule. - List<@Nullable String> lns = Arrays.asList("foo", null, "bar"); + List<@Nullable String> lns = Arrays.asList("foo", null, "bar"); } diff --git a/checker/tests/nullness/AnnotatedSupertype.java b/checker/tests/nullness/AnnotatedSupertype.java index 8922a988609..33b67fa2c08 100644 --- a/checker/tests/nullness/AnnotatedSupertype.java +++ b/checker/tests/nullness/AnnotatedSupertype.java @@ -1,18 +1,19 @@ -import java.io.Serializable; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.io.Serializable; + public class AnnotatedSupertype { - class NullableSupertype - // :: error: (nullness.on.supertype) - extends @Nullable Object - // :: error: (nullness.on.supertype) - implements @Nullable Serializable {} + class NullableSupertype + // :: error: (nullness.on.supertype) + extends @Nullable Object + // :: error: (nullness.on.supertype) + implements @Nullable Serializable {} - @NonNull class NonNullSupertype - // :: error: (nullness.on.supertype) - extends @NonNull Object - // :: error: (nullness.on.supertype) - implements @NonNull Serializable {} + @NonNull class NonNullSupertype + // :: error: (nullness.on.supertype) + extends @NonNull Object + // :: error: (nullness.on.supertype) + implements @NonNull Serializable {} } diff --git a/checker/tests/nullness/AnonymousSkipDefs.java b/checker/tests/nullness/AnonymousSkipDefs.java index 5374e68506d..7b019357009 100644 --- a/checker/tests/nullness/AnonymousSkipDefs.java +++ b/checker/tests/nullness/AnonymousSkipDefs.java @@ -3,20 +3,20 @@ public class AnonymousSkipDefs { - public static void main(String[] args) { - call( - new Runnable() { - @Override - public void run() { - @Nullable Object veryNull = null; - // :: error: (assignment.type.incompatible) - @NonNull Object notNull = veryNull; - notNull.toString(); - } - }); - } + public static void main(String[] args) { + call( + new Runnable() { + @Override + public void run() { + @Nullable Object veryNull = null; + // :: error: (assignment.type.incompatible) + @NonNull Object notNull = veryNull; + notNull.toString(); + } + }); + } - private static void call(Runnable r) { - r.run(); - } + private static void call(Runnable r) { + r.run(); + } } diff --git a/checker/tests/nullness/ArrayArgs.java b/checker/tests/nullness/ArrayArgs.java index a8c47ff2a00..39c69a17eb2 100644 --- a/checker/tests/nullness/ArrayArgs.java +++ b/checker/tests/nullness/ArrayArgs.java @@ -3,30 +3,30 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class ArrayArgs { - public void test(@NonNull String[] args) {} + public void test(@NonNull String[] args) {} - public void test(Class<@NonNull ? extends java.lang.annotation.Annotation> cls) {} + public void test(Class<@NonNull ? extends java.lang.annotation.Annotation> cls) {} - public void test() { - test(NonNull.class); + public void test() { + test(NonNull.class); - String[] s1 = new String[] {null, null, null}; - // :: error: (argument.type.incompatible) - test(s1); - String[] s2 = new String[] {"hello", null, "goodbye"}; - // :: error: (argument.type.incompatible) - test(s2); - // :: error: (assignment.type.incompatible) - @NonNull String[] s3 = new String[] {"hello", null, "goodbye"}; - // :: error: (new.array.type.invalid) - @NonNull String[] s4 = new String[3]; + String[] s1 = new String[] {null, null, null}; + // :: error: (argument.type.incompatible) + test(s1); + String[] s2 = new String[] {"hello", null, "goodbye"}; + // :: error: (argument.type.incompatible) + test(s2); + // :: error: (assignment.type.incompatible) + @NonNull String[] s3 = new String[] {"hello", null, "goodbye"}; + // :: error: (new.array.type.invalid) + @NonNull String[] s4 = new String[3]; - // TODO: when issue 25 is fixed, the following is safe - // and no error needs to be raised. - String[] s5 = new String[] {"hello", "goodbye"}; - // :: error: (argument.type.incompatible) - test(s5); - @NonNull String[] s6 = new String[] {"hello", "goodbye"}; - test(s6); - } + // TODO: when issue 25 is fixed, the following is safe + // and no error needs to be raised. + String[] s5 = new String[] {"hello", "goodbye"}; + // :: error: (argument.type.incompatible) + test(s5); + @NonNull String[] s6 = new String[] {"hello", "goodbye"}; + test(s6); + } } diff --git a/checker/tests/nullness/ArrayAssignmentFlow.java b/checker/tests/nullness/ArrayAssignmentFlow.java index 6fa0bad6f66..eeb50156d86 100644 --- a/checker/tests/nullness/ArrayAssignmentFlow.java +++ b/checker/tests/nullness/ArrayAssignmentFlow.java @@ -2,23 +2,23 @@ public class ArrayAssignmentFlow { - public void add_combined(MyPptTopLevel ppt) { + public void add_combined(MyPptTopLevel ppt) { - @Nullable Object[] vals = new Object[10]; + @Nullable Object[] vals = new Object[10]; - if (ppt.last_values != null) { - // Assigning to an array element should not cause flow information - // about ppt.last_values to be discarded. - vals[0] = ppt.last_values.vals; - ppt.last_values.toString(); + if (ppt.last_values != null) { + // Assigning to an array element should not cause flow information + // about ppt.last_values to be discarded. + vals[0] = ppt.last_values.vals; + ppt.last_values.toString(); + } } - } } class MyPptTopLevel { - public @Nullable MyValueTuple last_values = null; + public @Nullable MyValueTuple last_values = null; } class MyValueTuple { - public Object vals = new Object(); + public Object vals = new Object(); } diff --git a/checker/tests/nullness/ArrayCreation.java b/checker/tests/nullness/ArrayCreation.java index 12401e37786..71c2cd4a2fd 100644 --- a/checker/tests/nullness/ArrayCreation.java +++ b/checker/tests/nullness/ArrayCreation.java @@ -2,12 +2,12 @@ public class ArrayCreation { - void foo() { - // :: error: (nullness.on.new.array) - int[] o = new int @Nullable [10]; - } + void foo() { + // :: error: (nullness.on.new.array) + int[] o = new int @Nullable [10]; + } - void bar() { - int[] @Nullable [] o = new int[10] @Nullable []; - } + void bar() { + int[] @Nullable [] o = new int[10] @Nullable []; + } } diff --git a/checker/tests/nullness/ArrayCreationNullable.java b/checker/tests/nullness/ArrayCreationNullable.java index dc8f20c5522..3a8ca4533f7 100644 --- a/checker/tests/nullness/ArrayCreationNullable.java +++ b/checker/tests/nullness/ArrayCreationNullable.java @@ -8,178 +8,178 @@ */ public class ArrayCreationNullable { - void testObjectArray(@NonNull Object @NonNull [] p) { - @NonNull Object @NonNull [] objs; - // :: error: (new.array.type.invalid) - objs = new Object[10]; - objs[0].toString(); - // :: error: (assignment.type.incompatible) - objs = new @Nullable Object[10]; - objs[0].toString(); - // :: error: (new.array.type.invalid) - objs = new @NonNull Object[10]; - objs[0].toString(); - // Allowed. - objs = p; - objs[0].toString(); - } - - @DefaultQualifier(NonNull.class) - void testObjectArray2() { - Object[] objs; - // Even if the default qualifier is NonNull, array component - // types must be Nullable. - // :: error: (new.array.type.invalid) - objs = new Object[10]; - objs[0].toString(); - } + void testObjectArray(@NonNull Object @NonNull [] p) { + @NonNull Object @NonNull [] objs; + // :: error: (new.array.type.invalid) + objs = new Object[10]; + objs[0].toString(); + // :: error: (assignment.type.incompatible) + objs = new @Nullable Object[10]; + objs[0].toString(); + // :: error: (new.array.type.invalid) + objs = new @NonNull Object[10]; + objs[0].toString(); + // Allowed. + objs = p; + objs[0].toString(); + } - void testInitializers() { - Object[] objs = {1, 2, 3}; - objs = new Integer[] {1, 2, 3}; - objs = new Object[] {new Object(), "ha"}; + @DefaultQualifier(NonNull.class) + void testObjectArray2() { + Object[] objs; + // Even if the default qualifier is NonNull, array component + // types must be Nullable. + // :: error: (new.array.type.invalid) + objs = new Object[10]; + objs[0].toString(); + } - @NonNull Object[] objs2 = {}; - // :: error: (assignment.type.incompatible) - objs2 = new Integer[] {1, null, 3}; - // :: error: (assignment.type.incompatible) - objs2 = new Object[] {new Object(), "ha", null}; + void testInitializers() { + Object[] objs = {1, 2, 3}; + objs = new Integer[] {1, 2, 3}; + objs = new Object[] {new Object(), "ha"}; - @NonNull Object[] objs3 = new Integer[] {1, 2, 3}; - objs3 = new Integer[] {1, 2, 3}; - // :: error: (assignment.type.incompatible) - objs3 = new Integer[] {1, 2, 3, null}; + @NonNull Object[] objs2 = {}; + // :: error: (assignment.type.incompatible) + objs2 = new Integer[] {1, null, 3}; + // :: error: (assignment.type.incompatible) + objs2 = new Object[] {new Object(), "ha", null}; - (new Integer[] {1, 2, 3})[0].toString(); - // :: error: (dereference.of.nullable) - (new Integer[] {1, 2, 3, null})[0].toString(); + @NonNull Object[] objs3 = new Integer[] {1, 2, 3}; + objs3 = new Integer[] {1, 2, 3}; + // :: error: (assignment.type.incompatible) + objs3 = new Integer[] {1, 2, 3, null}; - // The assignment context is used to infer a @Nullable component type. - @Nullable Object[] objs4 = new Integer[] {1, 2, 3}; - // :: error: (dereference.of.nullable) - objs4[0].toString(); - objs4 = new Integer[] {1, 2, 3}; - } + (new Integer[] {1, 2, 3})[0].toString(); + // :: error: (dereference.of.nullable) + (new Integer[] {1, 2, 3, null})[0].toString(); - void testStringArray(@NonNull String @NonNull [] p) { - @NonNull String @NonNull [] strs; - // :: error: (new.array.type.invalid) - strs = new String[10]; - strs[0].toString(); - // :: error: (assignment.type.incompatible) - strs = new @Nullable String[10]; - strs[0].toString(); - // :: error: (new.array.type.invalid) - strs = new @NonNull String[10]; - strs[0].toString(); - // Allowed. - strs = p; - strs[0].toString(); - } - - void testIntegerArray(@NonNull Integer @NonNull [] p) { - @NonNull Integer @NonNull [] ints; - // :: error: (new.array.type.invalid) - ints = new Integer[10]; - ints[0].toString(); - // :: error: (assignment.type.incompatible) - ints = new @Nullable Integer[10]; - ints[0].toString(); - // :: error: (new.array.type.invalid) - ints = new @NonNull Integer[10]; - ints[0].toString(); - // Allowed. - ints = p; - ints[0].toString(); - } - - // The component type of zero-length arrays can be non-null - they will always generate - // IndexOutOfBoundsExceptions, but are usually just used for the type, e.g. in List.toArray. - void testLengthZero() { - @NonNull Object @NonNull [] objs; - objs = new Object[0]; - } - - /* Test case for Issue 153. - // toArray re-uses the passed array, if it is of appropriate size. - // It is only guaranteed to be non-null, if it is at most the same size. - void testToArray(java.util.Set nns) { - @NonNull Object [] nna = nns.toArray(new Object[nns.size()]); - // Given array is too small -> new one is created. - nna = nns.toArray(new Object[nns.size()-2]); - // Padding elements will be null. - // TODO:: error: (assignment.type.incompatible) - nna = nns.toArray(new Object[nns.size() + 2]); - @Nullable Object [] nbla = nns.toArray(new Object[nns.size() + 2]); - } - */ - - void testMultiDim() { - // new double[10][10] has type double @NonNull[] @Nullable[] - // :: error: (new.array.type.invalid) - double @NonNull [] @NonNull [] daa = new double[10][10]; - double @NonNull [] @Nullable [] daa2 = new double[10][10]; + // The assignment context is used to infer a @Nullable component type. + @Nullable Object[] objs4 = new Integer[] {1, 2, 3}; + // :: error: (dereference.of.nullable) + objs4[0].toString(); + objs4 = new Integer[] {1, 2, 3}; + } - // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] - // :: error: (new.array.type.invalid) - @Nullable Object @NonNull [] @NonNull [] oaa = new Object[10][10]; - @Nullable Object @NonNull [] @Nullable [] oaa2 = new Object[10][10]; + void testStringArray(@NonNull String @NonNull [] p) { + @NonNull String @NonNull [] strs; + // :: error: (new.array.type.invalid) + strs = new String[10]; + strs[0].toString(); + // :: error: (assignment.type.incompatible) + strs = new @Nullable String[10]; + strs[0].toString(); + // :: error: (new.array.type.invalid) + strs = new @NonNull String[10]; + strs[0].toString(); + // Allowed. + strs = p; + strs[0].toString(); + } + + void testIntegerArray(@NonNull Integer @NonNull [] p) { + @NonNull Integer @NonNull [] ints; + // :: error: (new.array.type.invalid) + ints = new Integer[10]; + ints[0].toString(); + // :: error: (assignment.type.incompatible) + ints = new @Nullable Integer[10]; + ints[0].toString(); + // :: error: (new.array.type.invalid) + ints = new @NonNull Integer[10]; + ints[0].toString(); + // Allowed. + ints = p; + ints[0].toString(); + } + + // The component type of zero-length arrays can be non-null - they will always generate + // IndexOutOfBoundsExceptions, but are usually just used for the type, e.g. in List.toArray. + void testLengthZero() { + @NonNull Object @NonNull [] objs; + objs = new Object[0]; + } + + /* Test case for Issue 153. + // toArray re-uses the passed array, if it is of appropriate size. + // It is only guaranteed to be non-null, if it is at most the same size. + void testToArray(java.util.Set nns) { + @NonNull Object [] nna = nns.toArray(new Object[nns.size()]); + // Given array is too small -> new one is created. + nna = nns.toArray(new Object[nns.size()-2]); + // Padding elements will be null. + // TODO:: error: (assignment.type.incompatible) + nna = nns.toArray(new Object[nns.size() + 2]); + @Nullable Object [] nbla = nns.toArray(new Object[nns.size() + 2]); + } + */ + + void testMultiDim() { + // new double[10][10] has type double @NonNull[] @Nullable[] + // :: error: (new.array.type.invalid) + double @NonNull [] @NonNull [] daa = new double[10][10]; + double @NonNull [] @Nullable [] daa2 = new double[10][10]; + + // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] + // :: error: (new.array.type.invalid) + @Nullable Object @NonNull [] @NonNull [] oaa = new Object[10][10]; + @Nullable Object @NonNull [] @Nullable [] oaa2 = new Object[10][10]; + + // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] + // :: error: (new.array.type.invalid) + oaa2 = new Object @NonNull [10] @NonNull [10]; + + @MonotonicNonNull Object @NonNull [] @MonotonicNonNull [] oaa3 = + new @MonotonicNonNull Object @NonNull [10] @MonotonicNonNull [10]; + oaa3[0] = new @MonotonicNonNull Object[4]; + // :: error: (assignment.type.incompatible) + oaa3[0] = null; + // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) + oaa3[0][0] = null; + } + + @PolyNull Object[] testPolyNull(@PolyNull Object[] in) { + @PolyNull Object[] out = new @PolyNull Object[in.length]; + for (int i = 0; i < in.length; ++i) { + if (in[i] == null) { + out[i] = null; + } else { + out[i] = in[i].getClass().toString(); + // :: error: (assignment.type.incompatible) + out[i] = null; + } + } + return out; + } + + void testMonotonicNonNull() { + @MonotonicNonNull Object @NonNull [] loa = new @MonotonicNonNull Object @NonNull [10]; + loa = new Object @NonNull [10]; + loa[0] = new Object(); + @MonotonicNonNull Object @NonNull [] loa2 = new Object @NonNull [10]; + // :: error: (dereference.of.nullable) + loa2[0].toString(); + } + + @MonotonicNonNull Object @NonNull [] testReturnContext() { + return new Object[10]; + } - // new Object[10][10] has type @Nullable Object @NonNull[] @Nullable[] // :: error: (new.array.type.invalid) - oaa2 = new Object @NonNull [10] @NonNull [10]; + @NonNull Object @NonNull [] oa0 = new Object[10]; + + // OK + @MonotonicNonNull Object @NonNull [] loa0 = new @MonotonicNonNull Object @NonNull [10]; + + Object[] oa1 = new Object[] {new Object()}; - @MonotonicNonNull Object @NonNull [] @MonotonicNonNull [] oaa3 = - new @MonotonicNonNull Object @NonNull [10] @MonotonicNonNull [10]; - oaa3[0] = new @MonotonicNonNull Object[4]; // :: error: (assignment.type.incompatible) - oaa3[0] = null; - // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) - oaa3[0][0] = null; - } - - @PolyNull Object[] testPolyNull(@PolyNull Object[] in) { - @PolyNull Object[] out = new @PolyNull Object[in.length]; - for (int i = 0; i < in.length; ++i) { - if (in[i] == null) { - out[i] = null; - } else { - out[i] = in[i].getClass().toString(); - // :: error: (assignment.type.incompatible) - out[i] = null; - } + Object[] oa2 = new Object[] {new Object(), null}; + + public static void main(String[] args) { + ArrayCreationNullable e = new ArrayCreationNullable(); + Integer[] ints = new Integer[] {5, 6}; + // This would result in a NPE, if there were no error. + e.testIntegerArray(ints); } - return out; - } - - void testMonotonicNonNull() { - @MonotonicNonNull Object @NonNull [] loa = new @MonotonicNonNull Object @NonNull [10]; - loa = new Object @NonNull [10]; - loa[0] = new Object(); - @MonotonicNonNull Object @NonNull [] loa2 = new Object @NonNull [10]; - // :: error: (dereference.of.nullable) - loa2[0].toString(); - } - - @MonotonicNonNull Object @NonNull [] testReturnContext() { - return new Object[10]; - } - - // :: error: (new.array.type.invalid) - @NonNull Object @NonNull [] oa0 = new Object[10]; - - // OK - @MonotonicNonNull Object @NonNull [] loa0 = new @MonotonicNonNull Object @NonNull [10]; - - Object[] oa1 = new Object[] {new Object()}; - - // :: error: (assignment.type.incompatible) - Object[] oa2 = new Object[] {new Object(), null}; - - public static void main(String[] args) { - ArrayCreationNullable e = new ArrayCreationNullable(); - Integer[] ints = new Integer[] {5, 6}; - // This would result in a NPE, if there were no error. - e.testIntegerArray(ints); - } } diff --git a/checker/tests/nullness/ArrayCreationSubArray.java b/checker/tests/nullness/ArrayCreationSubArray.java index 75a13b111fe..e11b36383eb 100644 --- a/checker/tests/nullness/ArrayCreationSubArray.java +++ b/checker/tests/nullness/ArrayCreationSubArray.java @@ -7,17 +7,17 @@ public class ArrayCreationSubArray { - void m() { + void m() { - Object o1a = new Integer[] {1, 2, null, 3, 4}; - @Nullable Integer[] o1b = new Integer[] {1, 2, null, 3, 4}; + Object o1a = new Integer[] {1, 2, null, 3, 4}; + @Nullable Integer[] o1b = new Integer[] {1, 2, null, 3, 4}; - Object o2a = new Object[] {null}; - Object o2b = new @Nullable Object[] {null}; + Object o2a = new Object[] {null}; + Object o2b = new @Nullable Object[] {null}; - Object o3a = new Object[][] {new Object[] {null}}; - Object o3b = new Object[][] {new @Nullable Object[] {null}}; - Object o3c = new @Nullable Object[][] {new @Nullable Object[] {null}}; - Object o3d = new Object[][] {{null}}; - } + Object o3a = new Object[][] {new Object[] {null}}; + Object o3b = new Object[][] {new @Nullable Object[] {null}}; + Object o3c = new @Nullable Object[][] {new @Nullable Object[] {null}}; + Object o3d = new Object[][] {{null}}; + } } diff --git a/checker/tests/nullness/ArrayIndex.java b/checker/tests/nullness/ArrayIndex.java index 6b7800daf9a..6be82600be4 100644 --- a/checker/tests/nullness/ArrayIndex.java +++ b/checker/tests/nullness/ArrayIndex.java @@ -1,16 +1,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ArrayIndex { - void foo(@Nullable Object[] a, int i) { - if (a[i] != null) { - a[i].hashCode(); + void foo(@Nullable Object[] a, int i) { + if (a[i] != null) { + a[i].hashCode(); + } + if (a[i + 1] != null) { + a[i + 1].hashCode(); + } + if (a[i + 1] != null) { + // :: error: (dereference.of.nullable) + a[i].hashCode(); + } } - if (a[i + 1] != null) { - a[i + 1].hashCode(); - } - if (a[i + 1] != null) { - // :: error: (dereference.of.nullable) - a[i].hashCode(); - } - } } diff --git a/checker/tests/nullness/ArrayInitBug.java b/checker/tests/nullness/ArrayInitBug.java index a0fd8f4c6db..19fbb8d7fe7 100644 --- a/checker/tests/nullness/ArrayInitBug.java +++ b/checker/tests/nullness/ArrayInitBug.java @@ -2,9 +2,9 @@ public class ArrayInitBug { - @Nullable Object @Nullable [] aa; + @Nullable Object @Nullable [] aa; - public ArrayInitBug() { - aa = null; - } + public ArrayInitBug() { + aa = null; + } } diff --git a/checker/tests/nullness/ArrayLazyNN.java b/checker/tests/nullness/ArrayLazyNN.java index 8a84a84a9ef..8054f1d23a1 100644 --- a/checker/tests/nullness/ArrayLazyNN.java +++ b/checker/tests/nullness/ArrayLazyNN.java @@ -6,14 +6,14 @@ * TODO: support for (i=0; i < a.length.... and change component type to non-null. */ public class ArrayLazyNN { - void test1() { - @MonotonicNonNull Object[] o1 = new @MonotonicNonNull Object[10]; - o1[0] = new Object(); - // :: error: (assignment.type.incompatible) - o1[0] = null; - // :: error: (assignment.type.incompatible) - @NonNull Object[] o2 = o1; - @SuppressWarnings("nullness") - @NonNull Object[] o3 = o1; - } + void test1() { + @MonotonicNonNull Object[] o1 = new @MonotonicNonNull Object[10]; + o1[0] = new Object(); + // :: error: (assignment.type.incompatible) + o1[0] = null; + // :: error: (assignment.type.incompatible) + @NonNull Object[] o2 = o1; + @SuppressWarnings("nullness") + @NonNull Object[] o3 = o1; + } } diff --git a/checker/tests/nullness/ArrayNew.java b/checker/tests/nullness/ArrayNew.java index 456173280d9..3afa35fd1ca 100644 --- a/checker/tests/nullness/ArrayNew.java +++ b/checker/tests/nullness/ArrayNew.java @@ -1,21 +1,22 @@ -import java.util.Collection; import org.checkerframework.checker.nullness.qual.*; +import java.util.Collection; + public class ArrayNew { - void m(Collection seq1) { - Integer[] seq1_array = new @NonNull Integer[] {5}; - Integer[] seq2_array = seq1.toArray(new @NonNull Integer[0]); - } + void m(Collection seq1) { + Integer[] seq1_array = new @NonNull Integer[] {5}; + Integer[] seq2_array = seq1.toArray(new @NonNull Integer[0]); + } - void takePrim1d(int[] ar) {} + void takePrim1d(int[] ar) {} - void callPrim1d() { - takePrim1d(new int[] {1, 2, 1}); - } + void callPrim1d() { + takePrim1d(new int[] {1, 2, 1}); + } - void takePrim2d(int[][] ar) {} + void takePrim2d(int[][] ar) {} - void callPrim2d() { - takePrim2d(new int[][] {{1, 2, 1}, {3, 3, 7}}); - } + void callPrim2d() { + takePrim2d(new int[][] {{1, 2, 1}, {3, 3, 7}}); + } } diff --git a/checker/tests/nullness/ArrayRefs.java b/checker/tests/nullness/ArrayRefs.java index 10991fb96f5..02f6fb2ef57 100644 --- a/checker/tests/nullness/ArrayRefs.java +++ b/checker/tests/nullness/ArrayRefs.java @@ -1,39 +1,40 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class ArrayRefs { - public void test() { + public void test() { + + String[] s = null; + + // :: error: (dereference.of.nullable) + if (s.length > 0) { + System.out.println("s.length > 0"); + } + } + + public static void test2() { + Object a = new Object(); + takeNNList(Arrays.asList(new Object[] {a})); + takeNNList(Arrays.asList(new Object[] {a})); + takeNNList(Arrays.asList(a, a, a)); + // :: error: (type.arguments.not.inferred) + takeNNList(Arrays.asList(a, a, null)); + } + + static void takeNNList(List p) {} - String[] s = null; + void test(T[] a) { + test(a); + } + + List @Nullable [] antecedents_for_suppressors() { + return null; + } - // :: error: (dereference.of.nullable) - if (s.length > 0) { - System.out.println("s.length > 0"); + public void find_suppressed_invs() { + List[] antecedents = antecedents_for_suppressors(); } - } - - public static void test2() { - Object a = new Object(); - takeNNList(Arrays.asList(new Object[] {a})); - takeNNList(Arrays.asList(new Object[] {a})); - takeNNList(Arrays.asList(a, a, a)); - // :: error: (type.arguments.not.inferred) - takeNNList(Arrays.asList(a, a, null)); - } - - static void takeNNList(List p) {} - - void test(T[] a) { - test(a); - } - - List @Nullable [] antecedents_for_suppressors() { - return null; - } - - public void find_suppressed_invs() { - List[] antecedents = antecedents_for_suppressors(); - } } diff --git a/checker/tests/nullness/AssertAfter.java b/checker/tests/nullness/AssertAfter.java index 328bd4275fe..5aa0eb81433 100644 --- a/checker/tests/nullness/AssertAfter.java +++ b/checker/tests/nullness/AssertAfter.java @@ -3,74 +3,74 @@ public class AssertAfter { - protected @Nullable String value; + protected @Nullable String value; - @EnsuresNonNull("value") - public boolean setRepNonNull() { - value = ""; - return true; - } + @EnsuresNonNull("value") + public boolean setRepNonNull() { + value = ""; + return true; + } - public void plain() { - // :: error: (dereference.of.nullable) - value.toString(); - } + public void plain() { + // :: error: (dereference.of.nullable) + value.toString(); + } - public void testAfter() { - setRepNonNull(); - value.toString(); - } + public void testAfter() { + setRepNonNull(); + value.toString(); + } - public void testBefore() { - // :: error: (dereference.of.nullable) - value.toString(); - setRepNonNull(); - } + public void testBefore() { + // :: error: (dereference.of.nullable) + value.toString(); + setRepNonNull(); + } - public void withCondition(@Nullable String t) { - if (t == null) { - setRepNonNull(); + public void withCondition(@Nullable String t) { + if (t == null) { + setRepNonNull(); + } + // :: error: (dereference.of.nullable) + value.toString(); } - // :: error: (dereference.of.nullable) - value.toString(); - } - public void inConditionInTrue() { - if (setRepNonNull()) { - value.toString(); - } else { - // nothing to do + public void inConditionInTrue() { + if (setRepNonNull()) { + value.toString(); + } else { + // nothing to do + } } - } - // skip-test: Come back when working on improved flow - public void asCondition() { - if (setRepNonNull()) { - } else { - value.toString(); // valid! + // skip-test: Come back when working on improved flow + public void asCondition() { + if (setRepNonNull()) { + } else { + value.toString(); // valid! + } } - } } // Test that private fields can be mentioned in pre- and post-conditions. class A { - private @Nullable String privateField = null; + private @Nullable String privateField = null; - @EnsuresNonNull("privateField") - public void m1() { - privateField = "hello"; - } + @EnsuresNonNull("privateField") + public void m1() { + privateField = "hello"; + } - @RequiresNonNull("privateField") - public void m2() {} + @RequiresNonNull("privateField") + public void m2() {} } class B { - void f() { - A a = new A(); - a.m1(); - a.m2(); - } + void f() { + A a = new A(); + a.m1(); + a.m2(); + } } diff --git a/checker/tests/nullness/AssertAfter2.java b/checker/tests/nullness/AssertAfter2.java index 68783180fb8..1cdaebb9e99 100644 --- a/checker/tests/nullness/AssertAfter2.java +++ b/checker/tests/nullness/AssertAfter2.java @@ -1,105 +1,106 @@ -import java.util.HashMap; -import java.util.List; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -public class AssertAfter2 { - - public class Graph { - - HashMap> childMap; - - public Graph(HashMap> childMap) { - this.childMap = childMap; - } - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("childMap.get(#1)") - public void addNode(final T n) { - // body omitted, not relevant to test case - } - - public void addEdge(T parent, T child) { - addNode(parent); - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); - } - - public void addEdgeBad1(T parent, T child) { - // :: error: (assignment.type.incompatible) - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); - } - - public void addEdgeBad2(T parent, T child) { - addNode(parent); - // :: error: (assignment.type.incompatible) - @NonNull List<@KeyFor("childMap") T> l = childMap.get(child); - } - - public void addEdgeBad3(T parent, T child) { - addNode(parent); - parent = child; - // :: error: (assignment.type.incompatible) - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); - } - - public void addEdgeOK(T parent, T child) { - List<@KeyFor("childMap") T> l = childMap.get(parent); - } - } - - class MultiParam { - - MultiParam(MultiParam thing, Object f1, Object f2, Object f3) { - this.thing = thing; - this.f1 = f1; - this.f2 = f2; - this.f3 = f3; - } - - MultiParam thing; - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("get(#1, #2, #3)") - void add(final Object o1, final Object o2, final Object o3) { - // body omitted, not relevant to test case - } - - @org.checkerframework.dataflow.qual.Pure - @Nullable Object get(Object o1, Object o2, Object o3) { - return null; - } - - Object f1, f2, f3; - - void addGood1() { - thing.add(f1, f2, f3); - @NonNull Object nn = thing.get(f1, f2, f3); - } - - void addBad1() { - // :: error: (assignment.type.incompatible) - @NonNull Object nn = get(f1, f2, f3); - } +import java.util.HashMap; +import java.util.List; - void addBad2() { - thing.add(f1, f2, f3); - f1 = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object nn = thing.get(f1, f2, f3); - } +public class AssertAfter2 { - void addBad3() { - thing.add(f1, f2, f3); - f2 = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object nn = thing.get(f1, f2, f3); + public class Graph { + + HashMap> childMap; + + public Graph(HashMap> childMap) { + this.childMap = childMap; + } + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("childMap.get(#1)") + public void addNode(final T n) { + // body omitted, not relevant to test case + } + + public void addEdge(T parent, T child) { + addNode(parent); + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); + } + + public void addEdgeBad1(T parent, T child) { + // :: error: (assignment.type.incompatible) + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); + } + + public void addEdgeBad2(T parent, T child) { + addNode(parent); + // :: error: (assignment.type.incompatible) + @NonNull List<@KeyFor("childMap") T> l = childMap.get(child); + } + + public void addEdgeBad3(T parent, T child) { + addNode(parent); + parent = child; + // :: error: (assignment.type.incompatible) + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent); + } + + public void addEdgeOK(T parent, T child) { + List<@KeyFor("childMap") T> l = childMap.get(parent); + } } - void addBad4() { - thing.add(f1, f2, f3); - f3 = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object nn = thing.get(f1, f2, f3); + class MultiParam { + + MultiParam(MultiParam thing, Object f1, Object f2, Object f3) { + this.thing = thing; + this.f1 = f1; + this.f2 = f2; + this.f3 = f3; + } + + MultiParam thing; + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("get(#1, #2, #3)") + void add(final Object o1, final Object o2, final Object o3) { + // body omitted, not relevant to test case + } + + @org.checkerframework.dataflow.qual.Pure + @Nullable Object get(Object o1, Object o2, Object o3) { + return null; + } + + Object f1, f2, f3; + + void addGood1() { + thing.add(f1, f2, f3); + @NonNull Object nn = thing.get(f1, f2, f3); + } + + void addBad1() { + // :: error: (assignment.type.incompatible) + @NonNull Object nn = get(f1, f2, f3); + } + + void addBad2() { + thing.add(f1, f2, f3); + f1 = new Object(); + // :: error: (assignment.type.incompatible) + @NonNull Object nn = thing.get(f1, f2, f3); + } + + void addBad3() { + thing.add(f1, f2, f3); + f2 = new Object(); + // :: error: (assignment.type.incompatible) + @NonNull Object nn = thing.get(f1, f2, f3); + } + + void addBad4() { + thing.add(f1, f2, f3); + f3 = new Object(); + // :: error: (assignment.type.incompatible) + @NonNull Object nn = thing.get(f1, f2, f3); + } } - } } diff --git a/checker/tests/nullness/AssertAfterChecked.java b/checker/tests/nullness/AssertAfterChecked.java index 8542d7f15ac..3baf7a82c44 100644 --- a/checker/tests/nullness/AssertAfterChecked.java +++ b/checker/tests/nullness/AssertAfterChecked.java @@ -3,149 +3,149 @@ public class AssertAfterChecked { - class InitField { - @Nullable Object f; - - @EnsuresNonNull("f") - void init() { - f = new Object(); - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - void initBad() {} - - void testInit() { - init(); - f.toString(); - } - } - - static class InitStaticField { - static @Nullable Object f; - - @EnsuresNonNull("f") - void init() { - f = new Object(); - } - - @EnsuresNonNull("f") - void init2() { - InitStaticField.f = new Object(); - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - void initBad() {} - - void testInit() { - init(); - f.toString(); - } - - @EnsuresNonNull("InitStaticField.f") - void initE() { - f = new Object(); - } - - @EnsuresNonNull("InitStaticField.f") - void initE2() { - InitStaticField.f = new Object(); - } - - @EnsuresNonNull("InitStaticField.f") - // :: error: (contracts.postcondition.not.satisfied) - void initBadE() {} - - void testInitE() { - initE(); - // TODO: we need to also support the unqualified static field access? - // f.toString(); - } - - void testInitE2() { - initE(); - InitStaticField.f.toString(); - } - } - - class TestParams { - @EnsuresNonNull("get(#1)") - // :: error: (contracts.postcondition.not.satisfied) - void init(final TestParams p) {} - - @org.checkerframework.dataflow.qual.Pure - @Nullable Object get(Object o) { - return null; - } - - void testInit1() { - init(this); - get(this).toString(); - } - - void testInit1b() { - init(this); - this.get(this).toString(); - } - - void testInit2(TestParams p) { - init(p); - get(p).toString(); - } - - void testInit3(TestParams p) { - p.init(this); - p.get(this).toString(); - } - - void testInit4(TestParams p) { - p.init(this); - // :: error: (dereference.of.nullable) - this.get(this).toString(); - } - } - - class WithReturn { - @Nullable Object f; - - @EnsuresNonNull("f") - int init1() { - f = new Object(); - return 0; - } - - @EnsuresNonNull("f") - int init2() { - if (5 == 5) { - f = new Object(); - return 0; - } else { - f = new Object(); - return 1; - } - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - int initBad1() { - return 0; - } - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - int initBad2() { - if (5 == 5) { - return 0; - } else { - f = new Object(); - return 1; - } - } - - void testInit() { - init1(); - f.toString(); + class InitField { + @Nullable Object f; + + @EnsuresNonNull("f") + void init() { + f = new Object(); + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + void initBad() {} + + void testInit() { + init(); + f.toString(); + } + } + + static class InitStaticField { + static @Nullable Object f; + + @EnsuresNonNull("f") + void init() { + f = new Object(); + } + + @EnsuresNonNull("f") + void init2() { + InitStaticField.f = new Object(); + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + void initBad() {} + + void testInit() { + init(); + f.toString(); + } + + @EnsuresNonNull("InitStaticField.f") + void initE() { + f = new Object(); + } + + @EnsuresNonNull("InitStaticField.f") + void initE2() { + InitStaticField.f = new Object(); + } + + @EnsuresNonNull("InitStaticField.f") + // :: error: (contracts.postcondition.not.satisfied) + void initBadE() {} + + void testInitE() { + initE(); + // TODO: we need to also support the unqualified static field access? + // f.toString(); + } + + void testInitE2() { + initE(); + InitStaticField.f.toString(); + } + } + + class TestParams { + @EnsuresNonNull("get(#1)") + // :: error: (contracts.postcondition.not.satisfied) + void init(final TestParams p) {} + + @org.checkerframework.dataflow.qual.Pure + @Nullable Object get(Object o) { + return null; + } + + void testInit1() { + init(this); + get(this).toString(); + } + + void testInit1b() { + init(this); + this.get(this).toString(); + } + + void testInit2(TestParams p) { + init(p); + get(p).toString(); + } + + void testInit3(TestParams p) { + p.init(this); + p.get(this).toString(); + } + + void testInit4(TestParams p) { + p.init(this); + // :: error: (dereference.of.nullable) + this.get(this).toString(); + } + } + + class WithReturn { + @Nullable Object f; + + @EnsuresNonNull("f") + int init1() { + f = new Object(); + return 0; + } + + @EnsuresNonNull("f") + int init2() { + if (5 == 5) { + f = new Object(); + return 0; + } else { + f = new Object(); + return 1; + } + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + int initBad1() { + return 0; + } + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + int initBad2() { + if (5 == 5) { + return 0; + } else { + f = new Object(); + return 1; + } + } + + void testInit() { + init1(); + f.toString(); + } } - } } diff --git a/checker/tests/nullness/AssertIfChecked.java b/checker/tests/nullness/AssertIfChecked.java index 86b02e156fd..dd86ff99f8d 100644 --- a/checker/tests/nullness/AssertIfChecked.java +++ b/checker/tests/nullness/AssertIfChecked.java @@ -3,161 +3,162 @@ public class AssertIfChecked { - boolean unknown = false; - - @Nullable Object value; - - @EnsuresNonNullIf(result = true, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public void badform1() {} - - @EnsuresNonNullIf(result = true, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public Object badform2() { - return new Object(); - } - - @EnsuresNonNullIf(result = false, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public void badform3() {} - - @EnsuresNonNullIf(result = false, expression = "value") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - public Object badform4() { - return new Object(); - } - - @EnsuresNonNullIf(result = true, expression = "value") - public boolean goodt1() { - return value != null; - } - - @EnsuresNonNullIf(result = true, expression = "value") - public boolean badt1() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value == null; - } - - @EnsuresNonNullIf(result = false, expression = "value") - public boolean goodf1() { - return value == null; - } - - @EnsuresNonNullIf(result = false, expression = "value") - public boolean badf1() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value != null; - } - - @EnsuresNonNullIf(result = true, expression = "value") - public boolean bad2() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value == null || unknown; - } - - @EnsuresNonNullIf(result = false, expression = "value") - public boolean bad3() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value == null && unknown; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testParam(final @Nullable Object param) { - return param != null; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testLitTTgood1(final @Nullable Object param) { - if (param == null) { - return false; - } - return true; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testLitTTbad1(final @Nullable Object param) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFgood1(final @Nullable Object param) { - return true; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFgood2(final @Nullable Object param) { - if (param == null) { - return true; - } - return false; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFbad1(final @Nullable Object param) { - if (param == null) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - return true; - } - - @EnsuresNonNullIf(result = false, expression = "#1") - boolean testLitFFbad2(final @Nullable Object param) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - - @Nullable Object getValueUnpure() { - return value; - } - - @org.checkerframework.dataflow.qual.Pure - @Nullable Object getValuePure() { - return value; - } - - @EnsuresNonNullIf(result = true, expression = "getValuePure()") - public boolean hasValuePure() { - return getValuePure() != null; - } - - @EnsuresNonNullIf(result = true, expression = "#1") - public static final boolean isComment(@Nullable String s) { - return s != null && (s.startsWith("//") || s.startsWith("#")); - } - - @EnsuresNonNullIf(result = true, expression = "#1") - public boolean myEquals(@Nullable Object o) { - return (o instanceof String) && equals((String) o); - } - - /* - * The next two methods are from Daikon's class Quant. They verify that - * EnsuresNonNullIf is correctly added to the assumptions after a check. - */ - - @EnsuresNonNullIf( - result = true, - expression = {"#1", "#2"}) - /* pure */ public static boolean sameLength( - boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - return ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length); - } - - /* pure */ public static boolean isReverse(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - if (!sameLength(seq1, seq2)) { - return false; - } - // This assert is not needed for inference. - // assert seq1 != null && seq2 != null; // because sameLength() = true - - int length = seq1.length; - for (int i = 0; i < length; i++) { - if (seq1[i] != seq2[length - i - 1]) { + boolean unknown = false; + + @Nullable Object value; + + @EnsuresNonNullIf(result = true, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public void badform1() {} + + @EnsuresNonNullIf(result = true, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public Object badform2() { + return new Object(); + } + + @EnsuresNonNullIf(result = false, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public void badform3() {} + + @EnsuresNonNullIf(result = false, expression = "value") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + public Object badform4() { + return new Object(); + } + + @EnsuresNonNullIf(result = true, expression = "value") + public boolean goodt1() { + return value != null; + } + + @EnsuresNonNullIf(result = true, expression = "value") + public boolean badt1() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value == null; + } + + @EnsuresNonNullIf(result = false, expression = "value") + public boolean goodf1() { + return value == null; + } + + @EnsuresNonNullIf(result = false, expression = "value") + public boolean badf1() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value != null; + } + + @EnsuresNonNullIf(result = true, expression = "value") + public boolean bad2() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value == null || unknown; + } + + @EnsuresNonNullIf(result = false, expression = "value") + public boolean bad3() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value == null && unknown; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testParam(final @Nullable Object param) { + return param != null; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testLitTTgood1(final @Nullable Object param) { + if (param == null) { + return false; + } + return true; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testLitTTbad1(final @Nullable Object param) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFgood1(final @Nullable Object param) { + return true; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFgood2(final @Nullable Object param) { + if (param == null) { + return true; + } + return false; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFbad1(final @Nullable Object param) { + if (param == null) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + return true; + } + + @EnsuresNonNullIf(result = false, expression = "#1") + boolean testLitFFbad2(final @Nullable Object param) { + // :: error: (contracts.conditional.postcondition.not.satisfied) return false; - } } - return true; - } + + @Nullable Object getValueUnpure() { + return value; + } + + @org.checkerframework.dataflow.qual.Pure + @Nullable Object getValuePure() { + return value; + } + + @EnsuresNonNullIf(result = true, expression = "getValuePure()") + public boolean hasValuePure() { + return getValuePure() != null; + } + + @EnsuresNonNullIf(result = true, expression = "#1") + public static final boolean isComment(@Nullable String s) { + return s != null && (s.startsWith("//") || s.startsWith("#")); + } + + @EnsuresNonNullIf(result = true, expression = "#1") + public boolean myEquals(@Nullable Object o) { + return (o instanceof String) && equals((String) o); + } + + /* + * The next two methods are from Daikon's class Quant. They verify that + * EnsuresNonNullIf is correctly added to the assumptions after a check. + */ + + @EnsuresNonNullIf( + result = true, + expression = {"#1", "#2"}) + /* pure */ public static boolean sameLength( + boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + return ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length); + } + + /* pure */ public static boolean isReverse( + boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + if (!sameLength(seq1, seq2)) { + return false; + } + // This assert is not needed for inference. + // assert seq1 != null && seq2 != null; // because sameLength() = true + + int length = seq1.length; + for (int i = 0; i < length; i++) { + if (seq1[i] != seq2[length - i - 1]) { + return false; + } + } + return true; + } } diff --git a/checker/tests/nullness/AssertIfClient.java b/checker/tests/nullness/AssertIfClient.java index 031158b01fb..1a2e808c270 100644 --- a/checker/tests/nullness/AssertIfClient.java +++ b/checker/tests/nullness/AssertIfClient.java @@ -3,57 +3,57 @@ public class AssertIfClient { - @RequiresNonNull("#1.rpcResponse()") - void rpcResponseNonNull(Proxy proxy) { - @NonNull Object response = proxy.rpcResponse(); - } - - void rpcResponseNullable(Proxy proxy) { - @Nullable Object response = proxy.rpcResponse(); - } - - void rpcResponseTypestate() { - Proxy proxy = new Proxy(); - // :: error: (assignment.type.incompatible) - @NonNull Object response1 = proxy.rpcResponse(); - // :: error: (contracts.precondition.not.satisfied) - rpcResponseNonNull(proxy); - rpcResponseNullable(proxy); - - proxy.issueRpc(); - @NonNull Object response2 = proxy.rpcResponse(); - @NonNull Object response3 = proxy.rpcResponse(); - rpcResponseNonNull(proxy); - rpcResponseNullable(proxy); - } + @RequiresNonNull("#1.rpcResponse()") + void rpcResponseNonNull(Proxy proxy) { + @NonNull Object response = proxy.rpcResponse(); + } + + void rpcResponseNullable(Proxy proxy) { + @Nullable Object response = proxy.rpcResponse(); + } + + void rpcResponseTypestate() { + Proxy proxy = new Proxy(); + // :: error: (assignment.type.incompatible) + @NonNull Object response1 = proxy.rpcResponse(); + // :: error: (contracts.precondition.not.satisfied) + rpcResponseNonNull(proxy); + rpcResponseNullable(proxy); + + proxy.issueRpc(); + @NonNull Object response2 = proxy.rpcResponse(); + @NonNull Object response3 = proxy.rpcResponse(); + rpcResponseNonNull(proxy); + rpcResponseNullable(proxy); + } } class Proxy { - // the RPC response, or null if not yet received - @MonotonicNonNull Object response = null; - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull({"response", "rpcResponse()"}) - void issueRpc() { - response = new Object(); - } - - // If this method returns true, - // then response is non-null and rpcResponse() returns non-null - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - @EnsuresNonNullIf( - expression = {"response", "rpcResponse()"}, - result = true) - boolean rpcResponseReceived() { - return response != null; - } - - // Returns non-null if the response has been received, null otherwise; but an - // @AssertNonNullIfNonNull annotation would states the converse, that if the result is non-null - // then the response hs been received. See rpcResponseReceived. - @Pure - @Nullable Object rpcResponse() { - return response; - } + // the RPC response, or null if not yet received + @MonotonicNonNull Object response = null; + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull({"response", "rpcResponse()"}) + void issueRpc() { + response = new Object(); + } + + // If this method returns true, + // then response is non-null and rpcResponse() returns non-null + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + @EnsuresNonNullIf( + expression = {"response", "rpcResponse()"}, + result = true) + boolean rpcResponseReceived() { + return response != null; + } + + // Returns non-null if the response has been received, null otherwise; but an + // @AssertNonNullIfNonNull annotation would states the converse, that if the result is non-null + // then the response hs been received. See rpcResponseReceived. + @Pure + @Nullable Object rpcResponse() { + return response; + } } diff --git a/checker/tests/nullness/AssertIfFalseTest.java b/checker/tests/nullness/AssertIfFalseTest.java index a2af7a9f324..79948ea07c9 100644 --- a/checker/tests/nullness/AssertIfFalseTest.java +++ b/checker/tests/nullness/AssertIfFalseTest.java @@ -3,52 +3,54 @@ public class AssertIfFalseTest { - @org.checkerframework.dataflow.qual.Pure - @Nullable Object get() { - return "m"; - } - - @EnsuresNonNullIf(result = false, expression = "get()") - boolean isGettable() { - // don't bother with the implementation - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - - void simple() { - // :: error: (dereference.of.nullable) - get().toString(); - } - - void checkWrongly() { - if (isGettable()) { - // :: error: (dereference.of.nullable) - get().toString(); - } - } - - void checkCorrectly() { - if (!isGettable()) { - get().toString(); - } - } - - /** Returns whether or not constant_value is a legal constant. */ - @EnsuresNonNullIf(result = false, expression = "#1") - static boolean legalConstant(final @Nullable Object constant_value) { - if ((constant_value == null) - || ((constant_value instanceof Long) || (constant_value instanceof Double))) return true; - return false; - } - - void useLegalConstant1(@Nullable Object static_constant_value) { - if (!legalConstant(static_constant_value)) { - throw new AssertionError("unexpected constant class " + static_constant_value.getClass()); - } - } - - void useLegalConstant2(@Nullable Object static_constant_value) { - assert legalConstant(static_constant_value) - : "unexpected constant class " + static_constant_value.getClass(); - } + @org.checkerframework.dataflow.qual.Pure + @Nullable Object get() { + return "m"; + } + + @EnsuresNonNullIf(result = false, expression = "get()") + boolean isGettable() { + // don't bother with the implementation + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + + void simple() { + // :: error: (dereference.of.nullable) + get().toString(); + } + + void checkWrongly() { + if (isGettable()) { + // :: error: (dereference.of.nullable) + get().toString(); + } + } + + void checkCorrectly() { + if (!isGettable()) { + get().toString(); + } + } + + /** Returns whether or not constant_value is a legal constant. */ + @EnsuresNonNullIf(result = false, expression = "#1") + static boolean legalConstant(final @Nullable Object constant_value) { + if ((constant_value == null) + || ((constant_value instanceof Long) || (constant_value instanceof Double))) + return true; + return false; + } + + void useLegalConstant1(@Nullable Object static_constant_value) { + if (!legalConstant(static_constant_value)) { + throw new AssertionError( + "unexpected constant class " + static_constant_value.getClass()); + } + } + + void useLegalConstant2(@Nullable Object static_constant_value) { + assert legalConstant(static_constant_value) + : "unexpected constant class " + static_constant_value.getClass(); + } } diff --git a/checker/tests/nullness/AssertIfFalseTest2.java b/checker/tests/nullness/AssertIfFalseTest2.java index da9f98ba0c6..b5964201066 100644 --- a/checker/tests/nullness/AssertIfFalseTest2.java +++ b/checker/tests/nullness/AssertIfFalseTest2.java @@ -3,28 +3,28 @@ public class AssertIfFalseTest2 { - public static void usePriorityQueue(PriorityQueue1<@NonNull Object> active) { - while (!(active.isEmpty())) { - @NonNull Object queueMinPathNode = active.peek(); + public static void usePriorityQueue(PriorityQueue1<@NonNull Object> active) { + while (!(active.isEmpty())) { + @NonNull Object queueMinPathNode = active.peek(); + } } - } - /////////////////////////////////////////////////////////////////////////// - /// Classes copied from the annotated JDK - /// + /////////////////////////////////////////////////////////////////////////// + /// Classes copied from the annotated JDK + /// - public class PriorityQueue1 { - @EnsuresNonNullIf( - result = false, - expression = {"peek()"}) - @Pure - public boolean isEmpty() { - return true; - } + public class PriorityQueue1 { + @EnsuresNonNullIf( + result = false, + expression = {"peek()"}) + @Pure + public boolean isEmpty() { + return true; + } - @Pure - public @Nullable E peek() { - return null; + @Pure + public @Nullable E peek() { + return null; + } } - } } diff --git a/checker/tests/nullness/AssertIfNonNullTest.java b/checker/tests/nullness/AssertIfNonNullTest.java index 020748f17ad..46feddc37a1 100644 --- a/checker/tests/nullness/AssertIfNonNullTest.java +++ b/checker/tests/nullness/AssertIfNonNullTest.java @@ -3,15 +3,15 @@ public class AssertIfNonNullTest { - Long id; + Long id; - public AssertIfNonNullTest(Long id) { - this.id = id; - } + public AssertIfNonNullTest(Long id) { + this.id = id; + } - @AssertNonNullIfNonNull("id") - @Pure - public @Nullable Long getId() { - return id; - } + @AssertNonNullIfNonNull("id") + @Pure + public @Nullable Long getId() { + return id; + } } diff --git a/checker/tests/nullness/AssertInStatic.java b/checker/tests/nullness/AssertInStatic.java index 099628527a2..277f810ee6a 100644 --- a/checker/tests/nullness/AssertInStatic.java +++ b/checker/tests/nullness/AssertInStatic.java @@ -1,11 +1,11 @@ public class AssertInStatic { - static { - long x = 0; - try { - x = 0; - } catch (Throwable e) { - assert true; + static { + long x = 0; + try { + x = 0; + } catch (Throwable e) { + assert true; + } } - } } diff --git a/checker/tests/nullness/AssertMethodTest.java b/checker/tests/nullness/AssertMethodTest.java index fc664332072..c8bd1b81326 100644 --- a/checker/tests/nullness/AssertMethodTest.java +++ b/checker/tests/nullness/AssertMethodTest.java @@ -4,84 +4,84 @@ import org.checkerframework.dataflow.qual.SideEffectFree; public class AssertMethodTest { - @interface Anno { - Class value(); - } + @interface Anno { + Class value(); + } - @AssertMethod - @SideEffectFree - void assertMethod(boolean b) { - if (!b) { - throw new RuntimeException(); + @AssertMethod + @SideEffectFree + void assertMethod(boolean b) { + if (!b) { + throw new RuntimeException(); + } } - } - void test1(@Nullable Object o) { - assertMethod(o != null); - o.toString(); - } + void test1(@Nullable Object o) { + assertMethod(o != null); + o.toString(); + } - @Nullable Object getO() { - return null; - } + @Nullable Object getO() { + return null; + } - void test2() { - assertMethod(getO() != null); - // :: error: dereference.of.nullable - getO().toString(); // error - } + void test2() { + assertMethod(getO() != null); + // :: error: dereference.of.nullable + getO().toString(); // error + } - @Pure - @Nullable Object getPureO() { - return ""; - } + @Pure + @Nullable Object getPureO() { + return ""; + } - void test3() { - assertMethod(getPureO() != null); - getPureO().toString(); - } + void test3() { + assertMethod(getPureO() != null); + getPureO().toString(); + } - @Nullable Object field = null; + @Nullable Object field = null; - void test4() { - assertMethod(field != null); - field.toString(); - } + void test4() { + assertMethod(field != null); + field.toString(); + } - String getError() { - field = null; - return "error"; - } + String getError() { + field = null; + return "error"; + } - @AssertMethod(value = RuntimeException.class, parameter = 2) - @SideEffectFree - void assertMethod(Object p, boolean b, Object error) { - if (!b) { - throw new RuntimeException(); + @AssertMethod(value = RuntimeException.class, parameter = 2) + @SideEffectFree + void assertMethod(Object p, boolean b, Object error) { + if (!b) { + throw new RuntimeException(); + } } - } - void test5() { - assertMethod(getError(), field != null, getError()); - // :: error: dereference.of.nullable - field.toString(); // error - } + void test5() { + assertMethod(getError(), field != null, getError()); + // :: error: dereference.of.nullable + field.toString(); // error + } - void test5b() { - assertMethod(getError(), field != null, ""); - field.toString(); - } + void test5b() { + assertMethod(getError(), field != null, ""); + field.toString(); + } - @AssertMethod(isAssertFalse = true) - @SideEffectFree - void assertFalse(boolean b) { - if (b) { - throw new RuntimeException(); + @AssertMethod(isAssertFalse = true) + @SideEffectFree + void assertFalse(boolean b) { + if (b) { + throw new RuntimeException(); + } } - } - void test6() { - assertFalse(field == null); - field.toString(); - } + void test6() { + assertFalse(field == null); + field.toString(); + } } diff --git a/checker/tests/nullness/AssertNonNullIfNonNullTest.java b/checker/tests/nullness/AssertNonNullIfNonNullTest.java index 93eabf390c0..e6a957ae63f 100644 --- a/checker/tests/nullness/AssertNonNullIfNonNullTest.java +++ b/checker/tests/nullness/AssertNonNullIfNonNullTest.java @@ -7,40 +7,40 @@ public class AssertNonNullIfNonNullTest { - private @Nullable String value; - - @Pure - @AssertNonNullIfNonNull("value") - public @Nullable String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - @EnsuresNonNullIf(expression = "value", result = true) - public boolean isValueNonNull1() { - return value != null; - } - - @EnsuresNonNullIf(expression = "getValue()", result = true) - public boolean isValueNonNull2() { - // The @AssertNonNullIfNonNull annotation implies that if getValue() is - // non-null, then is non-null, then value is non-null, but not the - // converse, so an error should be issued here. - // :: error: (contracts.conditional.postcondition.not.satisfied) - return value != null; - } - - // The @AssertNonNullIfNonNull annotation should enable suppressing this error. - @EnsuresNonNullIf(expression = "value", result = true) - public boolean isValueNonNull3() { - return getValue() != null; - } - - @EnsuresNonNullIf(expression = "getValue()", result = true) - public boolean isValueNonNull4() { - return getValue() != null; - } + private @Nullable String value; + + @Pure + @AssertNonNullIfNonNull("value") + public @Nullable String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @EnsuresNonNullIf(expression = "value", result = true) + public boolean isValueNonNull1() { + return value != null; + } + + @EnsuresNonNullIf(expression = "getValue()", result = true) + public boolean isValueNonNull2() { + // The @AssertNonNullIfNonNull annotation implies that if getValue() is + // non-null, then is non-null, then value is non-null, but not the + // converse, so an error should be issued here. + // :: error: (contracts.conditional.postcondition.not.satisfied) + return value != null; + } + + // The @AssertNonNullIfNonNull annotation should enable suppressing this error. + @EnsuresNonNullIf(expression = "value", result = true) + public boolean isValueNonNull3() { + return getValue() != null; + } + + @EnsuresNonNullIf(expression = "getValue()", result = true) + public boolean isValueNonNull4() { + return getValue() != null; + } } diff --git a/checker/tests/nullness/AssertNonNullTest.java b/checker/tests/nullness/AssertNonNullTest.java index 79f1c94b461..c8e4a9ab4ce 100644 --- a/checker/tests/nullness/AssertNonNullTest.java +++ b/checker/tests/nullness/AssertNonNullTest.java @@ -2,19 +2,19 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class AssertNonNullTest { - public @Nullable String s; + public @Nullable String s; - // :: error: (contracts.postcondition.not.satisfied) - public @EnsuresNonNull("s") void makeNN() { - s = null; - } + // :: error: (contracts.postcondition.not.satisfied) + public @EnsuresNonNull("s") void makeNN() { + s = null; + } - public static void main(String[] args) { - AssertNonNullTest a = new AssertNonNullTest(); - // :: error: (dereference.of.nullable) - a.s.equals("we"); - AssertNonNullTest b = new AssertNonNullTest(); - b.makeNN(); - b.s.equals("we"); - } + public static void main(String[] args) { + AssertNonNullTest a = new AssertNonNullTest(); + // :: error: (dereference.of.nullable) + a.s.equals("we"); + AssertNonNullTest b = new AssertNonNullTest(); + b.makeNN(); + b.s.equals("we"); + } } diff --git a/checker/tests/nullness/AssertNullable.java b/checker/tests/nullness/AssertNullable.java index 814538fe29b..fbcb59bb8f1 100644 --- a/checker/tests/nullness/AssertNullable.java +++ b/checker/tests/nullness/AssertNullable.java @@ -1,23 +1,23 @@ public class AssertNullable { - public static void main(String[] args) { - if (args.length >= 1) { - Boolean b = null; - // This will result in an NPE, not an AssertionError: - // Exception in thread "main" java.lang.NullPointerException - // Therefore, the Nullness Checker warns about this. - // :: error: (condition.nullable) - assert b; - } else { - String s = null; - // This is OK, the message will look like: - // Exception in thread "main" java.lang.AssertionError: null - assert 4 < 3 : s; + public static void main(String[] args) { + if (args.length >= 1) { + Boolean b = null; + // This will result in an NPE, not an AssertionError: + // Exception in thread "main" java.lang.NullPointerException + // Therefore, the Nullness Checker warns about this. + // :: error: (condition.nullable) + assert b; + } else { + String s = null; + // This is OK, the message will look like: + // Exception in thread "main" java.lang.AssertionError: null + assert 4 < 3 : s; + } } - } - void foo() { - String s = 3 > 2 ? null : "ba"; - // :: error: (dereference.of.nullable) - assert s.hashCode() > 4; - } + void foo() { + String s = 3 > 2 ? null : "ba"; + // :: error: (dereference.of.nullable) + assert s.hashCode() > 4; + } } diff --git a/checker/tests/nullness/AssertParameterNullness.java b/checker/tests/nullness/AssertParameterNullness.java index 8b9e1e111b5..51d760e15bb 100644 --- a/checker/tests/nullness/AssertParameterNullness.java +++ b/checker/tests/nullness/AssertParameterNullness.java @@ -3,29 +3,29 @@ public class AssertParameterNullness { - /** True iff both sequences are non-null and have the same length. */ - @EnsuresNonNullIf( - result = true, - expression = {"#1", "#2"}) - /* pure */ public static boolean sameLength( - final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { - if ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length) { - return true; + /** True iff both sequences are non-null and have the same length. */ + @EnsuresNonNullIf( + result = true, + expression = {"#1", "#2"}) + /* pure */ public static boolean sameLength( + final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { + if ((seq1 != null) && (seq2 != null) && seq1.length == seq2.length) { + return true; + } + return false; } - return false; - } - /* pure */ public static boolean pairwiseEqual( - boolean @Nullable [] seq3, boolean @Nullable [] seq4) { - if (sameLength(seq3, seq4)) { - boolean b1 = seq3[0]; - boolean b2 = seq4[0]; - } else { - // :: error: (accessing.nullable) - boolean b1 = seq3[0]; - // :: error: (accessing.nullable) - boolean b2 = seq4[0]; + /* pure */ public static boolean pairwiseEqual( + boolean @Nullable [] seq3, boolean @Nullable [] seq4) { + if (sameLength(seq3, seq4)) { + boolean b1 = seq3[0]; + boolean b2 = seq4[0]; + } else { + // :: error: (accessing.nullable) + boolean b1 = seq3[0]; + // :: error: (accessing.nullable) + boolean b2 = seq4[0]; + } + return true; } - return true; - } } diff --git a/checker/tests/nullness/AssertTwice.java b/checker/tests/nullness/AssertTwice.java index 41e45d5b7db..cfad53777d6 100644 --- a/checker/tests/nullness/AssertTwice.java +++ b/checker/tests/nullness/AssertTwice.java @@ -1,31 +1,31 @@ public class AssertTwice { - private void assertOnce() { - String methodDeclaration = null; - assert methodDeclaration != null; - methodDeclaration = null; - } + private void assertOnce() { + String methodDeclaration = null; + assert methodDeclaration != null; + methodDeclaration = null; + } - private void assertTwice() { - String methodDeclaration = null; - assert methodDeclaration != null; - assert methodDeclaration != null; - methodDeclaration = null; - } + private void assertTwice() { + String methodDeclaration = null; + assert methodDeclaration != null; + assert methodDeclaration != null; + methodDeclaration = null; + } - private void assertTwiceWithUse() { - String methodDeclaration = null; - assert methodDeclaration != null : "@AssumeAssertion(nullness)"; - methodDeclaration.toString(); - // :: warning: (nulltest.redundant) - assert methodDeclaration != null; - methodDeclaration = null; - } + private void assertTwiceWithUse() { + String methodDeclaration = null; + assert methodDeclaration != null : "@AssumeAssertion(nullness)"; + methodDeclaration.toString(); + // :: warning: (nulltest.redundant) + assert methodDeclaration != null; + methodDeclaration = null; + } - public static @org.checkerframework.checker.nullness.qual.Nullable Object n = "m"; + public static @org.checkerframework.checker.nullness.qual.Nullable Object n = "m"; - private void twiceWithChecks() { - assert n != null; - n = null; - } + private void twiceWithChecks() { + assert n != null; + n = null; + } } diff --git a/checker/tests/nullness/AssertWithStatic.java b/checker/tests/nullness/AssertWithStatic.java index ce819e2c753..1dc393e0dac 100644 --- a/checker/tests/nullness/AssertWithStatic.java +++ b/checker/tests/nullness/AssertWithStatic.java @@ -4,53 +4,53 @@ public class AssertWithStatic { - static @Nullable String f; - - @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") - public boolean hasSysOut1() { - return AssertWithStatic.f != null; - } - - @EnsuresNonNullIf(result = true, expression = "f") - public boolean hasSysOut2() { - return AssertWithStatic.f != null; - } - - @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") - public boolean hasSysOut3() { - return f != null; - } - - @EnsuresNonNullIf(result = true, expression = "f") - public boolean hasSysOut4() { - return f != null; - } - - @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") - public boolean noSysOut1() { - return AssertWithStatic.f == null; - } - - @EnsuresNonNullIf(result = false, expression = "f") - public boolean noSysOut2() { - return AssertWithStatic.f == null; - } - - @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") - public boolean noSysOut3() { - return f == null; - } - - @EnsuresNonNullIf(result = false, expression = "f") - public boolean noSysOut4() { - return f == null; - } - - @EnsuresNonNull("AssertWithStatic.f") - // :: error: (contracts.postcondition.not.satisfied) - public void sysOutAfter1() {} - - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.not.satisfied) - public void sysOutAfter2() {} + static @Nullable String f; + + @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") + public boolean hasSysOut1() { + return AssertWithStatic.f != null; + } + + @EnsuresNonNullIf(result = true, expression = "f") + public boolean hasSysOut2() { + return AssertWithStatic.f != null; + } + + @EnsuresNonNullIf(result = true, expression = "AssertWithStatic.f") + public boolean hasSysOut3() { + return f != null; + } + + @EnsuresNonNullIf(result = true, expression = "f") + public boolean hasSysOut4() { + return f != null; + } + + @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") + public boolean noSysOut1() { + return AssertWithStatic.f == null; + } + + @EnsuresNonNullIf(result = false, expression = "f") + public boolean noSysOut2() { + return AssertWithStatic.f == null; + } + + @EnsuresNonNullIf(result = false, expression = "AssertWithStatic.f") + public boolean noSysOut3() { + return f == null; + } + + @EnsuresNonNullIf(result = false, expression = "f") + public boolean noSysOut4() { + return f == null; + } + + @EnsuresNonNull("AssertWithStatic.f") + // :: error: (contracts.postcondition.not.satisfied) + public void sysOutAfter1() {} + + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.not.satisfied) + public void sysOutAfter2() {} } diff --git a/checker/tests/nullness/Asserts.java b/checker/tests/nullness/Asserts.java index 7baeb88e6a5..3e4db381dac 100644 --- a/checker/tests/nullness/Asserts.java +++ b/checker/tests/nullness/Asserts.java @@ -3,76 +3,76 @@ public class Asserts { - void propogateToExpr() { - String s = "m"; - assert false : s.getClass(); - } + void propogateToExpr() { + String s = "m"; + assert false : s.getClass(); + } - void incorrectAssertExpr() { - String s = null; - assert s != null : "@AssumeAssertion(nullness)"; // error - s.getClass(); // OK - } + void incorrectAssertExpr() { + String s = null; + assert s != null : "@AssumeAssertion(nullness)"; // error + s.getClass(); // OK + } - void correctAssertExpr() { - String s = null; - assert s == null : "@AssumeAssertion(nullness)"; - // :: error: (dereference.of.nullable) - s.getClass(); // error - } + void correctAssertExpr() { + String s = null; + assert s == null : "@AssumeAssertion(nullness)"; + // :: error: (dereference.of.nullable) + s.getClass(); // error + } - class ArrayCell { - @Nullable Object[] vals = new @Nullable Object[0]; - } + class ArrayCell { + @Nullable Object[] vals = new @Nullable Object[0]; + } - void assertComplexExpr(ArrayCell ac, int i) { - assert ac.vals[i] != null : "@AssumeAssertion(nullness)"; - @NonNull Object o = ac.vals[i]; - i = 10; - // :: error: (assignment.type.incompatible) - @NonNull Object o2 = ac.vals[i]; - } + void assertComplexExpr(ArrayCell ac, int i) { + assert ac.vals[i] != null : "@AssumeAssertion(nullness)"; + @NonNull Object o = ac.vals[i]; + i = 10; + // :: error: (assignment.type.incompatible) + @NonNull Object o2 = ac.vals[i]; + } - boolean pairwiseEqual(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - if (!sameLength(seq1, seq2)) { - return false; + boolean pairwiseEqual(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + if (!sameLength(seq1, seq2)) { + return false; + } + if (ne(seq1[0], seq2[0])) {} + return true; } - if (ne(seq1[0], seq2[0])) {} - return true; - } - @EnsuresNonNullIf( - result = true, - expression = {"#1", "#2"}) - boolean sameLength(final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { - // don't bother with the implementation - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf( + result = true, + expression = {"#1", "#2"}) + boolean sameLength(final boolean @Nullable [] seq1, final boolean @Nullable [] seq2) { + // don't bother with the implementation + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - static boolean ne(boolean a, boolean b) { - return true; - } + static boolean ne(boolean a, boolean b) { + return true; + } - void testAssertBad(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - assert sameLength(seq1, seq2); - // the @EnsuresNonNullIf is not taken from the assert, as it doesn't contain "nullness" - // :: error: (accessing.nullable) - if (seq1[0]) {} - } + void testAssertBad(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + assert sameLength(seq1, seq2); + // the @EnsuresNonNullIf is not taken from the assert, as it doesn't contain "nullness" + // :: error: (accessing.nullable) + if (seq1[0]) {} + } - void testAssertGood(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { - assert sameLength(seq1, seq2) : "@AssumeAssertion(nullness)"; - // The explanation contains "nullness" and we therefore take the additional assumption - if (seq1[0]) {} - } + void testAssertGood(boolean @Nullable [] seq1, boolean @Nullable [] seq2) { + assert sameLength(seq1, seq2) : "@AssumeAssertion(nullness)"; + // The explanation contains "nullness" and we therefore take the additional assumption + if (seq1[0]) {} + } - void testAssertAnd(@Nullable Object o) { - assert o != null && o.hashCode() > 6; - } + void testAssertAnd(@Nullable Object o) { + assert o != null && o.hashCode() > 6; + } - void testAssertOr(@Nullable Object o) { - // :: error: (dereference.of.nullable) - assert o != null || o.hashCode() > 6; - } + void testAssertOr(@Nullable Object o) { + // :: error: (dereference.of.nullable) + assert o != null || o.hashCode() > 6; + } } diff --git a/checker/tests/nullness/BinaryOp.java b/checker/tests/nullness/BinaryOp.java index 5ee9d3d29d1..64a96590dc8 100644 --- a/checker/tests/nullness/BinaryOp.java +++ b/checker/tests/nullness/BinaryOp.java @@ -2,7 +2,7 @@ import org.checkerframework.checker.nullness.qual.*; public class BinaryOp { - void test(@UnknownInitialization Object obj) { - throw new Error("" + obj); - } + void test(@UnknownInitialization Object obj) { + throw new Error("" + obj); + } } diff --git a/checker/tests/nullness/BinarySearch.java b/checker/tests/nullness/BinarySearch.java index 727b467d1c1..d1be308f32f 100644 --- a/checker/tests/nullness/BinarySearch.java +++ b/checker/tests/nullness/BinarySearch.java @@ -4,12 +4,12 @@ // Searching through nullable array components // and for nullable keys is forbidden. public class BinarySearch { - @Nullable Object @NonNull [] arr = {"a", "b", null}; + @Nullable Object @NonNull [] arr = {"a", "b", null}; - void search(@Nullable Object key) { - // :: error: (argument.type.incompatible) - int res = java.util.Arrays.binarySearch(arr, key); - // :: error: (argument.type.incompatible) - res = java.util.Arrays.binarySearch(arr, 0, 4, key); - } + void search(@Nullable Object key) { + // :: error: (argument.type.incompatible) + int res = java.util.Arrays.binarySearch(arr, key); + // :: error: (argument.type.incompatible) + res = java.util.Arrays.binarySearch(arr, 0, 4, key); + } } diff --git a/checker/tests/nullness/BoxingNullness.java b/checker/tests/nullness/BoxingNullness.java index 112aec26b9c..c6b30888c16 100644 --- a/checker/tests/nullness/BoxingNullness.java +++ b/checker/tests/nullness/BoxingNullness.java @@ -1,145 +1,145 @@ import org.checkerframework.checker.nullness.qual.*; public class BoxingNullness { - void withinOperation() { - Integer i1 = 3; - int i1u = i1 + 2; // valid - Integer i2 = null; - // :: error: (unboxing.of.nullable) - int i2u = i2 + 2; // invalid - Integer i3 = i1; - i3.toString(); - } - - void withinAssignment() { - Integer i1 = 5; - int i1u = i1; - Integer i2 = null; - // :: error: (unboxing.of.nullable) - int i2u = i2; // invalid - } - - void validWithinUnary() { - // within blocks to stop flow - Integer i1 = 1, i2 = 1, i3 = 1, i4 = 1; - ++i1; - i2++; - } - - void invalidWithinUnary() { - // within blocks to stop flow - Integer i1 = null, i2 = null, i3 = null, i4 = null; - // :: error: (unboxing.of.nullable) - ++i1; // invalid - // :: error: (unboxing.of.nullable) - i2++; // invalid - } - - void validCompoundAssignmentsAsVariable() { - @NonNull Integer i = 0; // nonnull is needed because flow is buggy - i += 1; - i -= 1; - @NonNull Boolean b = true; - b &= true; - } - - void invalidCompoundAssignmentsAsVariable() { - Integer i = null; - // :: error: (unboxing.of.nullable) - i += 1; // invalid - Boolean b = null; - // :: error: (unboxing.of.nullable) - b &= true; // invalid - } - - void invalidCompoundAssignmentAsValue() { - @NonNull Integer var = 3; - Integer val = null; - // :: error: (unboxing.of.nullable) - var += val; - Boolean b1 = null; - boolean b2 = true; - // :: error: (unboxing.of.nullable) - b2 &= b1; // invalid - } - - void randomValidStringOperations() { - String s = null; - s += null; - } - - void equalityTest() { - Integer bN = null; - Integer bN1 = null; - Integer bN2 = null; - Integer bN3 = null; - Integer bN4 = null; - Integer bN5 = null; - Integer bN6 = null; - Integer bN7 = null; - Integer b1 = 1; - int u1 = 1; - System.out.println(bN == bN1); // valid - System.out.println(bN2 == b1); // valid - System.out.println(bN3 != bN4); // valid - System.out.println(bN5 != b1); // valid - - System.out.println(u1 == b1); - System.out.println(u1 != b1); - System.out.println(u1 == u1); - System.out.println(u1 != u1); - - // :: error: (unboxing.of.nullable) - System.out.println(bN6 == u1); // invalid - // :: error: (unboxing.of.nullable) - System.out.println(bN7 != u1); // invalid - } - - void addition() { - Integer bN = null; - Integer bN1 = null; - Integer bN2 = null; - Integer bN3 = null; - Integer b1 = 1; - int u1 = 1; - // :: error: (unboxing.of.nullable) - System.out.println(bN + bN1); // invalid - // :: error: (unboxing.of.nullable) - System.out.println(bN2 + b1); // invalid - - System.out.println(u1 + b1); - System.out.println(u1 + u1); - - // :: error: (unboxing.of.nullable) - System.out.println(bN3 + u1); // invalid - } - - void visitCast() { - Integer bN = null; - Integer bN2 = null; - Integer b1 = 1; - int u1 = 1; - - println(bN); - // :: error: (unboxing.of.nullable) - println((int) bN2); // invalid - - println(b1); - println((int) b1); - - println(u1); - println((int) u1); - } - - void println(@Nullable Object o) {} - - void testObjectString() { - Object o = null; - o += "m"; - } - - void testCharString() { - CharSequence cs = null; - cs += "m"; - } + void withinOperation() { + Integer i1 = 3; + int i1u = i1 + 2; // valid + Integer i2 = null; + // :: error: (unboxing.of.nullable) + int i2u = i2 + 2; // invalid + Integer i3 = i1; + i3.toString(); + } + + void withinAssignment() { + Integer i1 = 5; + int i1u = i1; + Integer i2 = null; + // :: error: (unboxing.of.nullable) + int i2u = i2; // invalid + } + + void validWithinUnary() { + // within blocks to stop flow + Integer i1 = 1, i2 = 1, i3 = 1, i4 = 1; + ++i1; + i2++; + } + + void invalidWithinUnary() { + // within blocks to stop flow + Integer i1 = null, i2 = null, i3 = null, i4 = null; + // :: error: (unboxing.of.nullable) + ++i1; // invalid + // :: error: (unboxing.of.nullable) + i2++; // invalid + } + + void validCompoundAssignmentsAsVariable() { + @NonNull Integer i = 0; // nonnull is needed because flow is buggy + i += 1; + i -= 1; + @NonNull Boolean b = true; + b &= true; + } + + void invalidCompoundAssignmentsAsVariable() { + Integer i = null; + // :: error: (unboxing.of.nullable) + i += 1; // invalid + Boolean b = null; + // :: error: (unboxing.of.nullable) + b &= true; // invalid + } + + void invalidCompoundAssignmentAsValue() { + @NonNull Integer var = 3; + Integer val = null; + // :: error: (unboxing.of.nullable) + var += val; + Boolean b1 = null; + boolean b2 = true; + // :: error: (unboxing.of.nullable) + b2 &= b1; // invalid + } + + void randomValidStringOperations() { + String s = null; + s += null; + } + + void equalityTest() { + Integer bN = null; + Integer bN1 = null; + Integer bN2 = null; + Integer bN3 = null; + Integer bN4 = null; + Integer bN5 = null; + Integer bN6 = null; + Integer bN7 = null; + Integer b1 = 1; + int u1 = 1; + System.out.println(bN == bN1); // valid + System.out.println(bN2 == b1); // valid + System.out.println(bN3 != bN4); // valid + System.out.println(bN5 != b1); // valid + + System.out.println(u1 == b1); + System.out.println(u1 != b1); + System.out.println(u1 == u1); + System.out.println(u1 != u1); + + // :: error: (unboxing.of.nullable) + System.out.println(bN6 == u1); // invalid + // :: error: (unboxing.of.nullable) + System.out.println(bN7 != u1); // invalid + } + + void addition() { + Integer bN = null; + Integer bN1 = null; + Integer bN2 = null; + Integer bN3 = null; + Integer b1 = 1; + int u1 = 1; + // :: error: (unboxing.of.nullable) + System.out.println(bN + bN1); // invalid + // :: error: (unboxing.of.nullable) + System.out.println(bN2 + b1); // invalid + + System.out.println(u1 + b1); + System.out.println(u1 + u1); + + // :: error: (unboxing.of.nullable) + System.out.println(bN3 + u1); // invalid + } + + void visitCast() { + Integer bN = null; + Integer bN2 = null; + Integer b1 = 1; + int u1 = 1; + + println(bN); + // :: error: (unboxing.of.nullable) + println((int) bN2); // invalid + + println(b1); + println((int) b1); + + println(u1); + println((int) u1); + } + + void println(@Nullable Object o) {} + + void testObjectString() { + Object o = null; + o += "m"; + } + + void testCharString() { + CharSequence cs = null; + cs += "m"; + } } diff --git a/checker/tests/nullness/Bug102.java b/checker/tests/nullness/Bug102.java index b3602602aff..c178b8d653e 100644 --- a/checker/tests/nullness/Bug102.java +++ b/checker/tests/nullness/Bug102.java @@ -1,19 +1,19 @@ // Test case for Issue 102 public final class Bug102 { - class C {} + class C {} - void bug1() { - C c = new C<>(); - m(c); - m(c); // note: the bug disapear if calling m only once - } + void bug1() { + C c = new C<>(); + m(c); + m(c); // note: the bug disapear if calling m only once + } - void bug2() { - C c = new C<>(); - m(c); - } + void bug2() { + C c = new C<>(); + m(c); + } - // :: error: (invalid.polymorphic.qualifier) - <@org.checkerframework.checker.nullness.qual.PolyNull S> void m( - final C<@org.checkerframework.checker.nullness.qual.PolyNull String> a) {} + // :: error: (invalid.polymorphic.qualifier) + <@org.checkerframework.checker.nullness.qual.PolyNull S> void m( + final C<@org.checkerframework.checker.nullness.qual.PolyNull String> a) {} } diff --git a/checker/tests/nullness/Bug103.java b/checker/tests/nullness/Bug103.java index 7b2b16460dc..64f984d645b 100644 --- a/checker/tests/nullness/Bug103.java +++ b/checker/tests/nullness/Bug103.java @@ -10,78 +10,78 @@ class HR {} // Crazy: remove the "extends HR" and it compiles public class Bug103 extends HR { - // Crazy: add a 23th element as for example "hello" and it compiles - // Crazy: replace IG.C with IG.C+"" and it compiles - // Crazy: remove final and it compiles - // Crazy: replace with new String[22] and it compiles - // Crazy: reduce to less than 5 distinct values and it compiles (replace IG.D with IG.C) - final String[] ids = { - IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, - IG.C, IG.C, IG.D, IG.E, IG.F, IG.G - }; + // Crazy: add a 23th element as for example "hello" and it compiles + // Crazy: replace IG.C with IG.C+"" and it compiles + // Crazy: remove final and it compiles + // Crazy: replace with new String[22] and it compiles + // Crazy: reduce to less than 5 distinct values and it compiles (replace IG.D with IG.C) + final String[] ids = { + IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, IG.C, + IG.C, IG.C, IG.C, IG.D, IG.E, IG.F, IG.G + }; - // Crazy: remove arg u and it compiles - // Crazy: remove any line of m1 and it compiles - // Crazy: replace two o args by null and it compiles - void m1(CC o, Object u) { - String cc = m2(o); - String dd = m2(o); - } + // Crazy: remove arg u and it compiles + // Crazy: remove any line of m1 and it compiles + // Crazy: replace two o args by null and it compiles + void m1(CC o, Object u) { + String cc = m2(o); + String dd = m2(o); + } - String m2(final CC c) { - return "a"; - } + String m2(final CC c) { + return "a"; + } - // Crazy: remove ids.length and it compiles - // replace return type List with ArrayList and it compiles - List m3(CC c) { - ArrayList lc = new ArrayList<>(ids.length); - return lc; - } + // Crazy: remove ids.length and it compiles + // replace return type List with ArrayList and it compiles + List m3(CC c) { + ArrayList lc = new ArrayList<>(ids.length); + return lc; + } - // Crazy: comment out the whole unused LV class and it compiles - // Crazy: comment one of the following 4 lines out and it compiles - static class LV { - static String a = "a"; - static String b = "a"; - static String c = "a"; - static String d = "a"; - } + // Crazy: comment out the whole unused LV class and it compiles + // Crazy: comment one of the following 4 lines out and it compiles + static class LV { + static String a = "a"; + static String b = "a"; + static String c = "a"; + static String d = "a"; + } - class IG { - // Crazy: comment one of the following 8 lines out and it compiles - String C1 = "1"; - String C2 = "1"; - String C3 = "1"; - String C4 = "1"; - String C5 = "1"; - String C6 = "1"; - String C7 = "1"; - String C8 = "1"; + class IG { + // Crazy: comment one of the following 8 lines out and it compiles + String C1 = "1"; + String C2 = "1"; + String C3 = "1"; + String C4 = "1"; + String C5 = "1"; + String C6 = "1"; + String C7 = "1"; + String C8 = "1"; - static final String C = "c"; - static final String D = C; - static final String E = C; - static final String F = C; + static final String C = "c"; + static final String D = C; + static final String E = C; + static final String F = C; - // Crazy: comment one of the following 18 lines out and it compiles - static final String G = C; - static final String H = C; - static final String I = C; - static final String J = C; - static final String K = C; - static final String L = C; - static final String M = C; - static final String N = C; - static final String O = C; - static final String P = C; - static final String Q = C; - static final String R = C; - static final String S = C; - static final String T = C; - static final String U = C; - static final String V = C; - static final String W = C; - static final String X = C; - } + // Crazy: comment one of the following 18 lines out and it compiles + static final String G = C; + static final String H = C; + static final String I = C; + static final String J = C; + static final String K = C; + static final String L = C; + static final String M = C; + static final String N = C; + static final String O = C; + static final String P = C; + static final String Q = C; + static final String R = C; + static final String S = C; + static final String T = C; + static final String U = C; + static final String V = C; + static final String W = C; + static final String X = C; + } } diff --git a/checker/tests/nullness/CallSuper.java b/checker/tests/nullness/CallSuper.java index f9407105c0c..6f3ecbb9789 100644 --- a/checker/tests/nullness/CallSuper.java +++ b/checker/tests/nullness/CallSuper.java @@ -1,15 +1,16 @@ -import java.io.*; import org.checkerframework.checker.nullness.qual.*; +import java.io.*; + class MyFilterInputStream { - MyFilterInputStream(InputStream in) {} + MyFilterInputStream(InputStream in) {} } public class CallSuper extends MyFilterInputStream { - CallSuper(@Nullable InputStream in) { - // The MyFilterInputStream constructor takes a NonNull argument - // (but that's not true of FilterInputStream itself). - // :: error: (argument.type.incompatible) - super(in); - } + CallSuper(@Nullable InputStream in) { + // The MyFilterInputStream constructor takes a NonNull argument + // (but that's not true of FilterInputStream itself). + // :: error: (argument.type.incompatible) + super(in); + } } diff --git a/checker/tests/nullness/CastTypeVariable.java b/checker/tests/nullness/CastTypeVariable.java index 367af70b8e8..323ed8c0487 100644 --- a/checker/tests/nullness/CastTypeVariable.java +++ b/checker/tests/nullness/CastTypeVariable.java @@ -1,17 +1,17 @@ import java.util.Map; class MyAnnotatedTypeMirror { - void addAnnotations() {} + void addAnnotations() {} } class MyAnnotatedTypeVariable extends MyAnnotatedTypeMirror {} public class CastTypeVariable { - public static V mapGetHelper( - Map mappings, MyAnnotatedTypeVariable key) { - V possValue = (V) mappings.get(key); - // :: error: (dereference.of.nullable) - possValue.addAnnotations(); - return possValue; - } + public static V mapGetHelper( + Map mappings, MyAnnotatedTypeVariable key) { + V possValue = (V) mappings.get(key); + // :: error: (dereference.of.nullable) + possValue.addAnnotations(); + return possValue; + } } diff --git a/checker/tests/nullness/CastsNullness.java b/checker/tests/nullness/CastsNullness.java index ce91cd71d24..cb5deb34eec 100644 --- a/checker/tests/nullness/CastsNullness.java +++ b/checker/tests/nullness/CastsNullness.java @@ -2,101 +2,101 @@ public class CastsNullness { - void test(String nonNullParam) { - Object lc1 = (Object) nonNullParam; - lc1.toString(); + void test(String nonNullParam) { + Object lc1 = (Object) nonNullParam; + lc1.toString(); - String nullable = null; - Object lc2 = (Object) nullable; - // :: error: (dereference.of.nullable) - lc2.toString(); // error - } + String nullable = null; + Object lc2 = (Object) nullable; + // :: error: (dereference.of.nullable) + lc2.toString(); // error + } - void testBoxing() { - Integer b = null; - // :: error: (unboxing.of.nullable) - int i = b; - // no error, because there was already a nullpointer exception - Object o = (int) b; - } + void testBoxing() { + Integer b = null; + // :: error: (unboxing.of.nullable) + int i = b; + // no error, because there was already a nullpointer exception + Object o = (int) b; + } - void testUnsafeCast(@Nullable Object x) { - // :: warning: (cast.unsafe) - @NonNull Object y = (@NonNull Object) x; - y.toString(); - } + void testUnsafeCast(@Nullable Object x) { + // :: warning: (cast.unsafe) + @NonNull Object y = (@NonNull Object) x; + y.toString(); + } - void testUnsafeCastArray1(@Nullable Object[] x) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - @NonNull Object[] y = (@NonNull Object[]) x; - y[0].toString(); - } + void testUnsafeCastArray1(@Nullable Object[] x) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + @NonNull Object[] y = (@NonNull Object[]) x; + y[0].toString(); + } - void testUnsafeCastArray2(@NonNull Object x) { - // We don't know about the component type of x -> warn - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - @NonNull Object[] y = (@NonNull Object[]) x; - y[0].toString(); - } + void testUnsafeCastArray2(@NonNull Object x) { + // We don't know about the component type of x -> warn + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + @NonNull Object[] y = (@NonNull Object[]) x; + y[0].toString(); + } - void testUnsafeCastList1(java.util.ArrayList<@Nullable Object> x) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - java.util.List<@NonNull Object> y = (java.util.List<@NonNull Object>) x; - y.get(0).toString(); - // TODO:: warning: (cast.unsafe) - java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; - java.util.List<@Nullable Object> y3 = (java.util.List<@Nullable Object>) x; - } + void testUnsafeCastList1(java.util.ArrayList<@Nullable Object> x) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + java.util.List<@NonNull Object> y = (java.util.List<@NonNull Object>) x; + y.get(0).toString(); + // TODO:: warning: (cast.unsafe) + java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; + java.util.List<@Nullable Object> y3 = (java.util.List<@Nullable Object>) x; + } - void testUnsafeCastList2(java.util.List<@Nullable Object> x) { - java.util.List<@Nullable Object> y = (java.util.ArrayList<@Nullable Object>) x; - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; - } + void testUnsafeCastList2(java.util.List<@Nullable Object> x) { + java.util.List<@Nullable Object> y = (java.util.ArrayList<@Nullable Object>) x; + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; + } - void testUnsafeCastList3(@NonNull Object x) { - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - java.util.List<@Nullable Object> y = (java.util.List<@Nullable Object>) x; - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; - } + void testUnsafeCastList3(@NonNull Object x) { + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + java.util.List<@Nullable Object> y = (java.util.List<@Nullable Object>) x; + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + java.util.List<@NonNull Object> y2 = (java.util.ArrayList<@NonNull Object>) x; + } - void testSuppression(@Nullable Object x) { - // :: error: (assignment.type.incompatible) - @NonNull String s1 = (String) x; - @SuppressWarnings("nullness") - @NonNull String s2 = (String) x; - } + void testSuppression(@Nullable Object x) { + // :: error: (assignment.type.incompatible) + @NonNull String s1 = (String) x; + @SuppressWarnings("nullness") + @NonNull String s2 = (String) x; + } - class Generics { - T t; - @Nullable T nt; + class Generics { + T t; + @Nullable T nt; - Generics(T t) { - this.t = t; - this.nt = t; - } + Generics(T t) { + this.t = t; + this.nt = t; + } - void m() { - // :: error: (assignment.type.incompatible) - t = (@Nullable T) null; - nt = (@Nullable T) null; - // :: warning: (cast.unsafe) - t = (T) null; - // :: warning: (cast.unsafe) - nt = (T) null; + void m() { + // :: error: (assignment.type.incompatible) + t = (@Nullable T) null; + nt = (@Nullable T) null; + // :: warning: (cast.unsafe) + t = (T) null; + // :: warning: (cast.unsafe) + nt = (T) null; + } } - } - void testSafeCasts() { - // :: error: (nullness.on.primitive) - Integer x = (@Nullable int) 1; - } + void testSafeCasts() { + // :: error: (nullness.on.primitive) + Integer x = (@Nullable int) 1; + } } diff --git a/checker/tests/nullness/ChainAssignment.java b/checker/tests/nullness/ChainAssignment.java index db5c86a16d3..45557c6c4af 100644 --- a/checker/tests/nullness/ChainAssignment.java +++ b/checker/tests/nullness/ChainAssignment.java @@ -1,44 +1,44 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ChainAssignment { - @Nullable Object a; - @Nullable Object b; - Object x = new Object(); - Object y = new Object(); - - void m1() { - a = b = new Object(); - } - - void m2() { - this.a = this.b = new Object(); - } - - void m3() { - a = this.b = new Object(); - } - - void m4() { - this.a = b = new Object(); - } - - void n1() { - // :: error: (assignment.type.incompatible) - x = y = null; - } - - void n2() { - // :: error: (assignment.type.incompatible) - this.x = this.y = null; - } - - void n3() { - // :: error: (assignment.type.incompatible) - x = this.y = null; - } - - void n4() { - // :: error: (assignment.type.incompatible) - this.x = y = null; - } + @Nullable Object a; + @Nullable Object b; + Object x = new Object(); + Object y = new Object(); + + void m1() { + a = b = new Object(); + } + + void m2() { + this.a = this.b = new Object(); + } + + void m3() { + a = this.b = new Object(); + } + + void m4() { + this.a = b = new Object(); + } + + void n1() { + // :: error: (assignment.type.incompatible) + x = y = null; + } + + void n2() { + // :: error: (assignment.type.incompatible) + this.x = this.y = null; + } + + void n3() { + // :: error: (assignment.type.incompatible) + x = this.y = null; + } + + void n4() { + // :: error: (assignment.type.incompatible) + this.x = y = null; + } } diff --git a/checker/tests/nullness/ChicoryPremain.java b/checker/tests/nullness/ChicoryPremain.java index 98110b742ae..10c72e033a5 100644 --- a/checker/tests/nullness/ChicoryPremain.java +++ b/checker/tests/nullness/ChicoryPremain.java @@ -1,13 +1,13 @@ package daikon.chicory; public class ChicoryPremain { - public static void premain(ClassLoader loader) { - Object transformer = null; - try { - transformer = loader.loadClass("Foo").getDeclaredConstructor().newInstance(); - transformer.getClass(); - } catch (Exception e1) { - throw new RuntimeException("Exception", e1); + public static void premain(ClassLoader loader) { + Object transformer = null; + try { + transformer = loader.loadClass("Foo").getDeclaredConstructor().newInstance(); + transformer.getClass(); + } catch (Exception e1) { + throw new RuntimeException("Exception", e1); + } } - } } diff --git a/checker/tests/nullness/ClassGetCanonicalName.java b/checker/tests/nullness/ClassGetCanonicalName.java index 0c1779fa316..beaba3127fe 100644 --- a/checker/tests/nullness/ClassGetCanonicalName.java +++ b/checker/tests/nullness/ClassGetCanonicalName.java @@ -1,5 +1,5 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class ClassGetCanonicalName { - @NonNull String s = ClassGetCanonicalName.class.getCanonicalName(); + @NonNull String s = ClassGetCanonicalName.class.getCanonicalName(); } diff --git a/checker/tests/nullness/CompoundAssign.java b/checker/tests/nullness/CompoundAssign.java index ed7c53266e6..e9cf4075d09 100644 --- a/checker/tests/nullness/CompoundAssign.java +++ b/checker/tests/nullness/CompoundAssign.java @@ -1,13 +1,13 @@ public class CompoundAssign { - void m(String args) { - String arg = ""; - for (int ii = 0; ii < args.length(); ii++) { - if ('x' == 'y') { - arg += 'x'; - } else { - arg = ""; - } + void m(String args) { + String arg = ""; + for (int ii = 0; ii < args.length(); ii++) { + if ('x' == 'y') { + arg += 'x'; + } else { + arg = ""; + } + } + if (arg.equals("")) {} } - if (arg.equals("")) {} - } } diff --git a/checker/tests/nullness/ConditionalNullness.java b/checker/tests/nullness/ConditionalNullness.java index 167104f840f..549f4d9df6c 100644 --- a/checker/tests/nullness/ConditionalNullness.java +++ b/checker/tests/nullness/ConditionalNullness.java @@ -3,94 +3,94 @@ public class ConditionalNullness { - @EnsuresNonNullIf( - expression = {"field", "method()"}, - result = true) - boolean checkNonNull() { - // don't bother with the implementation - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf( + expression = {"field", "method()"}, + result = true) + boolean checkNonNull() { + // don't bother with the implementation + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @Nullable Object field = null; + @Nullable Object field = null; - @org.checkerframework.dataflow.qual.Pure - @Nullable Object method() { - return "m"; - } + @org.checkerframework.dataflow.qual.Pure + @Nullable Object method() { + return "m"; + } - void testSelfWithCheck() { - ConditionalNullness other = new ConditionalNullness(); - if (checkNonNull()) { - field.toString(); - method().toString(); - // :: error: (dereference.of.nullable) - other.field.toString(); // error - // :: error: (dereference.of.nullable) - other.method().toString(); // error + void testSelfWithCheck() { + ConditionalNullness other = new ConditionalNullness(); + if (checkNonNull()) { + field.toString(); + method().toString(); + // :: error: (dereference.of.nullable) + other.field.toString(); // error + // :: error: (dereference.of.nullable) + other.method().toString(); // error + } + // :: error: (dereference.of.nullable) + method().toString(); // error } - // :: error: (dereference.of.nullable) - method().toString(); // error - } - void testSelfWithoutCheck() { - // :: error: (dereference.of.nullable) - field.toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error - } + void testSelfWithoutCheck() { + // :: error: (dereference.of.nullable) + field.toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error + } - void testSelfWithCheckNegation() { - if (checkNonNull()) { - // nothing to do - } else { - // :: error: (dereference.of.nullable) - field.toString(); // error + void testSelfWithCheckNegation() { + if (checkNonNull()) { + // nothing to do + } else { + // :: error: (dereference.of.nullable) + field.toString(); // error + } + field.toString(); // error } - field.toString(); // error - } - void testOtherWithCheck() { - ConditionalNullness other = new ConditionalNullness(); - if (other.checkNonNull()) { - other.field.toString(); - other.method().toString(); - // :: error: (dereference.of.nullable) - field.toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error + void testOtherWithCheck() { + ConditionalNullness other = new ConditionalNullness(); + if (other.checkNonNull()) { + other.field.toString(); + other.method().toString(); + // :: error: (dereference.of.nullable) + field.toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error + } + // :: error: (dereference.of.nullable) + other.method().toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error } - // :: error: (dereference.of.nullable) - other.method().toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error - } - void testOtherWithoutCheck() { - ConditionalNullness other = new ConditionalNullness(); - // :: error: (dereference.of.nullable) - other.field.toString(); // error - // :: error: (dereference.of.nullable) - other.method().toString(); // error - // :: error: (dereference.of.nullable) - field.toString(); // error - // :: error: (dereference.of.nullable) - method().toString(); // error - } + void testOtherWithoutCheck() { + ConditionalNullness other = new ConditionalNullness(); + // :: error: (dereference.of.nullable) + other.field.toString(); // error + // :: error: (dereference.of.nullable) + other.method().toString(); // error + // :: error: (dereference.of.nullable) + field.toString(); // error + // :: error: (dereference.of.nullable) + method().toString(); // error + } - void testOtherWithCheckNegation() { - ConditionalNullness other = new ConditionalNullness(); - if (other.checkNonNull()) { - // nothing to do - } else { - // :: error: (dereference.of.nullable) - other.field.toString(); // error - // :: error: (dereference.of.nullable) - other.method().toString(); // error - // :: error: (dereference.of.nullable) - field.toString(); // error + void testOtherWithCheckNegation() { + ConditionalNullness other = new ConditionalNullness(); + if (other.checkNonNull()) { + // nothing to do + } else { + // :: error: (dereference.of.nullable) + other.field.toString(); // error + // :: error: (dereference.of.nullable) + other.method().toString(); // error + // :: error: (dereference.of.nullable) + field.toString(); // error + } + // :: error: (dereference.of.nullable) + field.toString(); // error } - // :: error: (dereference.of.nullable) - field.toString(); // error - } } diff --git a/checker/tests/nullness/ConditionalOr.java b/checker/tests/nullness/ConditionalOr.java index a4b66f2dd6b..86981c2b07a 100644 --- a/checker/tests/nullness/ConditionalOr.java +++ b/checker/tests/nullness/ConditionalOr.java @@ -2,9 +2,9 @@ public class ConditionalOr { - void test(@Nullable Object o) { - if (o == null || o.toString() == "...") { - // ... + void test(@Nullable Object o) { + if (o == null || o.toString() == "...") { + // ... + } } - } } diff --git a/checker/tests/nullness/ConditionalPolyNull.java b/checker/tests/nullness/ConditionalPolyNull.java index 91b183ad25f..69cad88a08a 100644 --- a/checker/tests/nullness/ConditionalPolyNull.java +++ b/checker/tests/nullness/ConditionalPolyNull.java @@ -3,38 +3,38 @@ import org.checkerframework.checker.nullness.qual.PolyNull; class ConditionalPolyNull { - @PolyNull String toLowerCaseA(@PolyNull String text) { - return text == null ? null : text.toLowerCase(); - } - - @PolyNull String toLowerCaseB(@PolyNull String text) { - return text != null ? text.toLowerCase() : null; - } + @PolyNull String toLowerCaseA(@PolyNull String text) { + return text == null ? null : text.toLowerCase(); + } - @PolyNull String toLowerCaseC(@PolyNull String text) { - // :: error: (dereference.of.nullable) - // :: error: (return.type.incompatible) - return text == null ? text.toLowerCase() : null; - } + @PolyNull String toLowerCaseB(@PolyNull String text) { + return text != null ? text.toLowerCase() : null; + } - @PolyNull String toLowerCaseD(@PolyNull String text) { - // :: error: (return.type.incompatible) - // :: error: (dereference.of.nullable) - return text != null ? null : text.toLowerCase(); - } + @PolyNull String toLowerCaseC(@PolyNull String text) { + // :: error: (dereference.of.nullable) + // :: error: (return.type.incompatible) + return text == null ? text.toLowerCase() : null; + } - @PolyNull String foo(@PolyNull String param) { - if (param != null) { - // @PolyNull is really @NonNull, so change - // the type of param to @NonNull. - return param.toString(); + @PolyNull String toLowerCaseD(@PolyNull String text) { + // :: error: (return.type.incompatible) + // :: error: (dereference.of.nullable) + return text != null ? null : text.toLowerCase(); } - if (param == null) { - // @PolyNull is really @Nullable, so change - // the type of param to @Nullable. - param = null; - return null; + + @PolyNull String foo(@PolyNull String param) { + if (param != null) { + // @PolyNull is really @NonNull, so change + // the type of param to @NonNull. + return param.toString(); + } + if (param == null) { + // @PolyNull is really @Nullable, so change + // the type of param to @Nullable. + param = null; + return null; + } + return param; } - return param; - } } diff --git a/checker/tests/nullness/Conditions.java b/checker/tests/nullness/Conditions.java index fb7b52ace03..68428001bbc 100644 --- a/checker/tests/nullness/Conditions.java +++ b/checker/tests/nullness/Conditions.java @@ -3,38 +3,38 @@ public class Conditions { - @Nullable Object f; + @Nullable Object f; - void test1(Conditions c) { - if (!(c.f != null)) { - return; + void test1(Conditions c) { + if (!(c.f != null)) { + return; + } + c.f.hashCode(); } - c.f.hashCode(); - } - void test2(Conditions c) { - if (!(c.f != null) || 5 > 9) { - return; + void test2(Conditions c) { + if (!(c.f != null) || 5 > 9) { + return; + } + c.f.hashCode(); } - c.f.hashCode(); - } - @EnsuresNonNullIf(expression = "f", result = true) - public boolean isNN() { - return (f != null); - } + @EnsuresNonNullIf(expression = "f", result = true) + public boolean isNN() { + return (f != null); + } - void test1m(Conditions c) { - if (!(c.isNN())) { - return; + void test1m(Conditions c) { + if (!(c.isNN())) { + return; + } + c.f.hashCode(); } - c.f.hashCode(); - } - void test2m(Conditions c) { - if (!(c.isNN()) || 5 > 9) { - return; + void test2m(Conditions c) { + if (!(c.isNN()) || 5 > 9) { + return; + } + c.f.hashCode(); } - c.f.hashCode(); - } } diff --git a/checker/tests/nullness/ConstructorPostcondition.java b/checker/tests/nullness/ConstructorPostcondition.java index da78185951e..3068540e5d0 100644 --- a/checker/tests/nullness/ConstructorPostcondition.java +++ b/checker/tests/nullness/ConstructorPostcondition.java @@ -2,21 +2,21 @@ public class ConstructorPostcondition { - class Box { - @Nullable Object f; - } + class Box { + @Nullable Object f; + } - @EnsuresNonNull("#1.f") - // :: error: (contracts.postcondition.not.satisfied) - ConstructorPostcondition(Box b) {} + @EnsuresNonNull("#1.f") + // :: error: (contracts.postcondition.not.satisfied) + ConstructorPostcondition(Box b) {} - @EnsuresNonNull("#1.f") - ConstructorPostcondition(Box b, Object o) { - b.f = o; - } + @EnsuresNonNull("#1.f") + ConstructorPostcondition(Box b, Object o) { + b.f = o; + } - void foo(Box b) { - ConstructorPostcondition x = new ConstructorPostcondition(b, "x"); - b.f.hashCode(); - } + void foo(Box b) { + ConstructorPostcondition x = new ConstructorPostcondition(b, "x"); + b.f.hashCode(); + } } diff --git a/checker/tests/nullness/ControlFlow.java b/checker/tests/nullness/ControlFlow.java index f8e614b8c24..fecc4f26bb5 100644 --- a/checker/tests/nullness/ControlFlow.java +++ b/checker/tests/nullness/ControlFlow.java @@ -2,15 +2,15 @@ // test-case for issue 160 public class ControlFlow { - public static void main(String[] args) { - String s = null; - if (s == null) { - // Important! - } else { - // Can also throw exception or call System#exit - return; + public static void main(String[] args) { + String s = null; + if (s == null) { + // Important! + } else { + // Can also throw exception or call System#exit + return; + } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); - } } diff --git a/checker/tests/nullness/CopyOfArray.java b/checker/tests/nullness/CopyOfArray.java index 8474905eb04..f93a9916163 100644 --- a/checker/tests/nullness/CopyOfArray.java +++ b/checker/tests/nullness/CopyOfArray.java @@ -1,13 +1,14 @@ -import java.util.Arrays; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Arrays; + public class CopyOfArray { - protected void makeCopy(Object[] args, int i) { - Object[] copyExact1 = Arrays.copyOf(args, args.length); - @Nullable Object[] copyExact2 = Arrays.copyOf(args, args.length); + protected void makeCopy(Object[] args, int i) { + Object[] copyExact1 = Arrays.copyOf(args, args.length); + @Nullable Object[] copyExact2 = Arrays.copyOf(args, args.length); - // :: error: (assignment.type.incompatible) - Object[] copyInexact1 = Arrays.copyOf(args, i); - @Nullable Object[] copyInexact2 = Arrays.copyOf(args, i); - } + // :: error: (assignment.type.incompatible) + Object[] copyInexact1 = Arrays.copyOf(args, i); + @Nullable Object[] copyInexact2 = Arrays.copyOf(args, i); + } } diff --git a/checker/tests/nullness/DaikonEnhancedFor.java b/checker/tests/nullness/DaikonEnhancedFor.java index bd30c426cd8..a0534eb4571 100644 --- a/checker/tests/nullness/DaikonEnhancedFor.java +++ b/checker/tests/nullness/DaikonEnhancedFor.java @@ -1,30 +1,31 @@ // Based on a false positive encountered in Daikon related to common CFGs // by the KeyFor checker. -import java.util.*; import org.checkerframework.checker.nullness.qual.*; -class DaikonEnhancedFor { +import java.util.*; - @SuppressWarnings("nullness") - Map> cmap = null; +class DaikonEnhancedFor { - @SuppressWarnings("nullness") - Object[] getObjects() { - return null; - } + @SuppressWarnings("nullness") + Map> cmap = null; - void process(@KeyFor("this.cmap") Object super_c) { - @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap - @KeyFor("this.cmap") Object[] clazzes = getObjects(); - // go through all of the classes and intialize the map - for (Object cd : clazzes) { - cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); + @SuppressWarnings("nullness") + Object[] getObjects() { + return null; } - // go through the list again and put in the derived class information - for (Object cd : clazzes) { - Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); - derived.add(cd); + + void process(@KeyFor("this.cmap") Object super_c) { + @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap + @KeyFor("this.cmap") Object[] clazzes = getObjects(); + // go through all of the classes and intialize the map + for (Object cd : clazzes) { + cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); + } + // go through the list again and put in the derived class information + for (Object cd : clazzes) { + Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); + derived.add(cd); + } } - } } diff --git a/checker/tests/nullness/DaikonEnhancedForNoThis.java b/checker/tests/nullness/DaikonEnhancedForNoThis.java index da3b6787133..e4f954ade27 100644 --- a/checker/tests/nullness/DaikonEnhancedForNoThis.java +++ b/checker/tests/nullness/DaikonEnhancedForNoThis.java @@ -3,30 +3,31 @@ // before it was modified to avoid missing standardization. See DaikonEnhancedFor.java // for the "fixed" version. There are no longer expected errors in this test. -import java.util.*; import org.checkerframework.checker.nullness.qual.*; -class DaikonEnhancedForNoThis { +import java.util.*; - @SuppressWarnings("nullness") - Map> cmap = null; +class DaikonEnhancedForNoThis { - @SuppressWarnings("nullness") - Object[] getObjects() { - return null; - } + @SuppressWarnings("nullness") + Map> cmap = null; - void process(@KeyFor("this.cmap") Object super_c) { - @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap - @KeyFor("cmap") Object[] clazzes = getObjects(); - // go through all of the classes and intialize the map - for (Object cd : clazzes) { - cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); + @SuppressWarnings("nullness") + Object[] getObjects() { + return null; } - // go through the list again and put in the derived class information - for (Object cd : clazzes) { - Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); - derived.add(cd); + + void process(@KeyFor("this.cmap") Object super_c) { + @SuppressWarnings("keyfor") // the loop below makes all these keys to cmap + @KeyFor("cmap") Object[] clazzes = getObjects(); + // go through all of the classes and intialize the map + for (Object cd : clazzes) { + cmap.put(cd, new TreeSet<@KeyFor("cmap") Object>()); + } + // go through the list again and put in the derived class information + for (Object cd : clazzes) { + Set<@KeyFor("this.cmap") Object> derived = cmap.get(super_c); + derived.add(cd); + } } - } } diff --git a/checker/tests/nullness/DaikonTests.java b/checker/tests/nullness/DaikonTests.java index a0923041239..903d3b2ab68 100644 --- a/checker/tests/nullness/DaikonTests.java +++ b/checker/tests/nullness/DaikonTests.java @@ -7,133 +7,133 @@ */ public class DaikonTests { - // Based on a problem found in PPtSlice. - class Bug1 { - @Nullable Object field; - - public void cond1() { - if (this.hashCode() > 6 && Bug1Other.field != null) { - // spurious dereference error - Bug1Other.field.toString(); - } + // Based on a problem found in PPtSlice. + class Bug1 { + @Nullable Object field; + + public void cond1() { + if (this.hashCode() > 6 && Bug1Other.field != null) { + // spurious dereference error + Bug1Other.field.toString(); + } + } + + public void cond1(Bug1 p) { + if (this.hashCode() > 6 && p.field != null) { + // works + p.field.toString(); + } + } + + public void cond2() { + if (Bug1Other.field != null && this.hashCode() > 6) { + // works + Bug1Other.field.toString(); + } + } } - public void cond1(Bug1 p) { - if (this.hashCode() > 6 && p.field != null) { - // works - p.field.toString(); - } - } - - public void cond2() { - if (Bug1Other.field != null && this.hashCode() > 6) { - // works - Bug1Other.field.toString(); - } - } - } - - // Based on problem found in PptCombined. - // Not yet able to reproduce the problem :-( - - class Bug2Data { - Bug2Data(Bug2Super o) {} - } + // Based on problem found in PptCombined. + // Not yet able to reproduce the problem :-( - class Bug2Super { - public @MonotonicNonNull Bug2Data field; - } - - class Bug2 extends Bug2Super { - private void m() { - field = new Bug2Data(this); - field.hashCode(); + class Bug2Data { + Bug2Data(Bug2Super o) {} } - } - // Based on problem found in FloatEqual. - class Bug3 { - @EnsuresNonNullIf(expression = "derived", result = true) - public boolean isDerived() { - return (derived != null); + class Bug2Super { + public @MonotonicNonNull Bug2Data field; } - @Nullable Object derived; - - void good1(Bug3 v1) { - if (!v1.isDerived() || !(5 > 9)) { - return; - } - v1.derived.hashCode(); + class Bug2 extends Bug2Super { + private void m() { + field = new Bug2Data(this); + field.hashCode(); + } } - // TODO: this is currently not supported - // void good2(Bug3 v1) { - // if (!(v1.isDerived() && (5 > 9))) - // return; - // v1.derived.hashCode(); - // } - - void good3(Bug3 v1) { - if (!v1.isDerived() || !(v1 instanceof Bug3)) { - return; - } - Object o = (Object) v1.derived; - o.hashCode(); + // Based on problem found in FloatEqual. + class Bug3 { + @EnsuresNonNullIf(expression = "derived", result = true) + public boolean isDerived() { + return (derived != null); + } + + @Nullable Object derived; + + void good1(Bug3 v1) { + if (!v1.isDerived() || !(5 > 9)) { + return; + } + v1.derived.hashCode(); + } + + // TODO: this is currently not supported + // void good2(Bug3 v1) { + // if (!(v1.isDerived() && (5 > 9))) + // return; + // v1.derived.hashCode(); + // } + + void good3(Bug3 v1) { + if (!v1.isDerived() || !(v1 instanceof Bug3)) { + return; + } + Object o = (Object) v1.derived; + o.hashCode(); + } } - } - - // Based on problem found in PrintInvariants. - // Not yet able to reproduce the problem :-( - class Bug4 { - @MonotonicNonNull Object field; - - void m(Bug4 p) { - if (false && p.field != null) { - p.field.hashCode(); - } - } - } + // Based on problem found in PrintInvariants. + // Not yet able to reproduce the problem :-( - // Based on problem found in chicory.Runtime: - class Bug5 { - @Nullable Object clazz; + class Bug4 { + @MonotonicNonNull Object field; - @EnsuresNonNull("clazz") - void init() { - clazz = new Object(); + void m(Bug4 p) { + if (false && p.field != null) { + p.field.hashCode(); + } + } } - void test(Bug5 b) { - if (b.clazz == null) { - b.init(); - } - - // The problem is: - // In the "then" branch, we have in "nnExpr" that "clazz" is non-null. - // In the "else" branch, we have in "annos" that the variable is non-null. - // However, as these are facts in two different representations, the merge keeps - // neither! - // - // no error message expected - b.clazz.hashCode(); + // Based on problem found in chicory.Runtime: + class Bug5 { + @Nullable Object clazz; + + @EnsuresNonNull("clazz") + void init() { + clazz = new Object(); + } + + void test(Bug5 b) { + if (b.clazz == null) { + b.init(); + } + + // The problem is: + // In the "then" branch, we have in "nnExpr" that "clazz" is non-null. + // In the "else" branch, we have in "annos" that the variable is non-null. + // However, as these are facts in two different representations, the merge keeps + // neither! + // + // no error message expected + b.clazz.hashCode(); + } } - } - // From LimitedSizeSet. The following initialization of the values array - // has caused a NullPointerException. - class Bug6 { - protected @Nullable T @Nullable [] values; + // From LimitedSizeSet. The following initialization of the values array + // has caused a NullPointerException. + class Bug6 { + protected @Nullable T @Nullable [] values; - public Bug6() { - // :: warning: [unchecked] unchecked cast - @Nullable T[] new_values_array = (@Nullable T[]) new @Nullable Object[4]; - values = new_values_array; + public Bug6() { + // :: warning: [unchecked] unchecked cast + @Nullable T[] new_values_array = (@Nullable T[]) new @Nullable Object[4]; + values = new_values_array; + } } - } } class Bug1Other { - static @Nullable Object field; + static @Nullable Object field; } diff --git a/checker/tests/nullness/DefaultAnnotation.java b/checker/tests/nullness/DefaultAnnotation.java index 87ceed970b3..c983c260110 100644 --- a/checker/tests/nullness/DefaultAnnotation.java +++ b/checker/tests/nullness/DefaultAnnotation.java @@ -1,139 +1,140 @@ -import java.util.Iterator; -import java.util.List; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; -public class DefaultAnnotation { - - public void testNoDefault() { - - String s = null; - } - - @DefaultQualifier.List( - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL})) - public void testDefault() { +import java.util.Iterator; +import java.util.List; - // :: error: (assignment.type.incompatible) - String s = null; // error - List lst = new List<>(); // valid - // :: error: (argument.type.incompatible) - lst.add(null); // error - } +public class DefaultAnnotation { - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL}) - public class InnerDefault { + public void testNoDefault() { - public void testDefault() { - // :: error: (assignment.type.incompatible) - String s = null; // error - List lst = new List<>(); // valid - // :: error: (argument.type.incompatible) - lst.add(null); // error - s = lst.get(0); // valid - - List<@Nullable String> nullList = new List<>(); // valid - nullList.add(null); // valid - // :: error: (assignment.type.incompatible) - s = nullList.get(0); // error + String s = null; } - } - - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL}) - public static class DefaultDefs { - public String getNNString() { - return "foo"; // valid - } + @DefaultQualifier.List( + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL})) + public void testDefault() { - public String getNNString2() { - // :: error: (return.type.incompatible) - return null; // error + // :: error: (assignment.type.incompatible) + String s = null; // error + List lst = new List<>(); // valid + // :: error: (argument.type.incompatible) + lst.add(null); // error } - public T getNull(T t) { - // :: error: (return.type.incompatible) - return null; // invalid + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL}) + public class InnerDefault { + + public void testDefault() { + // :: error: (assignment.type.incompatible) + String s = null; // error + List lst = new List<>(); // valid + // :: error: (argument.type.incompatible) + lst.add(null); // error + s = lst.get(0); // valid + + List<@Nullable String> nullList = new List<>(); // valid + nullList.add(null); // valid + // :: error: (assignment.type.incompatible) + s = nullList.get(0); // error + } } - public T getNonNull(T t) { - // :: error: (return.type.incompatible) - return null; // error + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL}) + public static class DefaultDefs { + + public String getNNString() { + return "foo"; // valid + } + + public String getNNString2() { + // :: error: (return.type.incompatible) + return null; // error + } + + public T getNull(T t) { + // :: error: (return.type.incompatible) + return null; // invalid + } + + public T getNonNull(T t) { + // :: error: (return.type.incompatible) + return null; // error + } } - } - public class DefaultUses { + public class DefaultUses { - public void test() { + public void test() { - DefaultDefs d = new DefaultDefs(); + DefaultDefs d = new DefaultDefs(); - @NonNull String s = d.getNNString(); // valid - } + @NonNull String s = d.getNNString(); // valid + } - @DefaultQualifier( - value = org.checkerframework.checker.nullness.qual.NonNull.class, - locations = {TypeUseLocation.ALL}) - public void testDefaultArgs() { + @DefaultQualifier( + value = org.checkerframework.checker.nullness.qual.NonNull.class, + locations = {TypeUseLocation.ALL}) + public void testDefaultArgs() { - DefaultDefs d = new DefaultDefs(); + DefaultDefs d = new DefaultDefs(); - // :: error: (assignment.type.incompatible) - String s1 = d.<@Nullable String>getNull(null); // error - String s2 = d.getNonNull("foo"); // valid - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - String s3 = d.<@Nullable String>getNonNull("foo"); // error + // :: error: (assignment.type.incompatible) + String s1 = d.<@Nullable String>getNull(null); // error + String s2 = d.getNonNull("foo"); // valid + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + String s3 = d.<@Nullable String>getNonNull("foo"); // error + } } - } - @DefaultQualifier(value = org.checkerframework.checker.nullness.qual.NonNull.class) - static class DefaultExtends implements Iterator, Iterable { + @DefaultQualifier(value = org.checkerframework.checker.nullness.qual.NonNull.class) + static class DefaultExtends implements Iterator, Iterable { - @Override - public boolean hasNext() { - throw new UnsupportedOperationException(); - } + @Override + public boolean hasNext() { + throw new UnsupportedOperationException(); + } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } - @Override - public String next() { - throw new UnsupportedOperationException(); - } + @Override + public String next() { + throw new UnsupportedOperationException(); + } - @Override - public Iterator iterator() { - return this; + @Override + public Iterator iterator() { + return this; + } } - } - class List { - public E get(int i) { - throw new RuntimeException(); - } + class List { + public E get(int i) { + throw new RuntimeException(); + } - public boolean add(E e) { - throw new RuntimeException(); + public boolean add(E e) { + throw new RuntimeException(); + } } - } - @DefaultQualifier(value = NonNull.class) - public void testDefaultUnqualified() { + @DefaultQualifier(value = NonNull.class) + public void testDefaultUnqualified() { - // :: error: (assignment.type.incompatible) - String s = null; // error - List lst = new List<>(); // valid - // :: error: (argument.type.incompatible) - lst.add(null); // error - } + // :: error: (assignment.type.incompatible) + String s = null; // error + List lst = new List<>(); // valid + // :: error: (argument.type.incompatible) + lst.add(null); // error + } } diff --git a/checker/tests/nullness/DefaultFlow.java b/checker/tests/nullness/DefaultFlow.java index 04fa42f665d..e04e8f7e18f 100644 --- a/checker/tests/nullness/DefaultFlow.java +++ b/checker/tests/nullness/DefaultFlow.java @@ -3,19 +3,19 @@ @org.checkerframework.framework.qual.DefaultQualifier(NonNull.class) public class DefaultFlow { - void test() { + void test() { - @Nullable String reader = null; - if (reader == null) { - return; - } + @Nullable String reader = null; + if (reader == null) { + return; + } - reader.startsWith("hello"); - } + reader.startsWith("hello"); + } - void tesVariableInitialization() { - @Nullable Object elts = null; - assert elts != null : "@AssumeAssertion(nullness)"; - @NonNull Object elem = elts; - } + void tesVariableInitialization() { + @Nullable Object elts = null; + assert elts != null : "@AssumeAssertion(nullness)"; + @NonNull Object elem = elts; + } } diff --git a/checker/tests/nullness/DefaultInterface.java b/checker/tests/nullness/DefaultInterface.java index 4da53e42e43..d5751902de4 100644 --- a/checker/tests/nullness/DefaultInterface.java +++ b/checker/tests/nullness/DefaultInterface.java @@ -3,14 +3,14 @@ @DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class) interface Foo { - void foo(String a, String b); + void foo(String a, String b); } @DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class) public class DefaultInterface { - public void test() { + public void test() { - @Nullable Foo foo = null; - } + @Nullable Foo foo = null; + } } diff --git a/checker/tests/nullness/DefaultLoops.java b/checker/tests/nullness/DefaultLoops.java index f440169df3c..5eafbcc3986 100644 --- a/checker/tests/nullness/DefaultLoops.java +++ b/checker/tests/nullness/DefaultLoops.java @@ -1,36 +1,37 @@ -import java.util.LinkedList; import org.checkerframework.checker.nullness.qual.*; +import java.util.LinkedList; + class MyTS extends LinkedList {} public class DefaultLoops { - void m() { - MyTS ts = new MyTS(); - // s should default to @Nullable - for (Object s : ts) {} - } - - void bar() { - for (int i = 0; i < 100; ++i) { - // nullable by default - Object o; - o = null; - // :: error: (dereference.of.nullable) - o.hashCode(); - o = new Object(); - o.hashCode(); - } - for (int i = 0; i < 100; ++i) { - // nullable by default - Object o; - o = new Object(); - o.hashCode(); + void m() { + MyTS ts = new MyTS(); + // s should default to @Nullable + for (Object s : ts) {} } - int i = 0; - // nullable by default - for (Object o2; i < 100; ++i) { - o2 = null; - int i3 = new Object().hashCode(); + + void bar() { + for (int i = 0; i < 100; ++i) { + // nullable by default + Object o; + o = null; + // :: error: (dereference.of.nullable) + o.hashCode(); + o = new Object(); + o.hashCode(); + } + for (int i = 0; i < 100; ++i) { + // nullable by default + Object o; + o = new Object(); + o.hashCode(); + } + int i = 0; + // nullable by default + for (Object o2; i < 100; ++i) { + o2 = null; + int i3 = new Object().hashCode(); + } } - } } diff --git a/checker/tests/nullness/DefaultingForEach.java b/checker/tests/nullness/DefaultingForEach.java index 4b8b170d80d..923aace8ba4 100644 --- a/checker/tests/nullness/DefaultingForEach.java +++ b/checker/tests/nullness/DefaultingForEach.java @@ -1,44 +1,45 @@ // Test case for issue #4248: https://github.com/typetools/checker-framework/issues/4248 -import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.DefaultQualifier; +import java.util.List; + class DefaultForEach { - @DefaultQualifier(Nullable.class) - Object @NonNull [] foo() { - return new Object[] {null}; - } - - void bar() { - for (Object p : foo()) { - // :: error: dereference.of.nullable - p.toString(); + @DefaultQualifier(Nullable.class) + Object @NonNull [] foo() { + return new Object[] {null}; } - } - @DefaultQualifier(Nullable.class) - @NonNull List foo2() { - throw new RuntimeException(); - } + void bar() { + for (Object p : foo()) { + // :: error: dereference.of.nullable + p.toString(); + } + } - void bar2() { - for (Object p : foo2()) { - // :: error: (dereference.of.nullable) - p.toString(); + @DefaultQualifier(Nullable.class) + @NonNull List foo2() { + throw new RuntimeException(); } - } - double[][] foo3() { - throw new RuntimeException(); - } + void bar2() { + for (Object p : foo2()) { + // :: error: (dereference.of.nullable) + p.toString(); + } + } + + double[][] foo3() { + throw new RuntimeException(); + } - void bar3() { - for (double[] pa : foo3()) { - for (Double p : pa) { - p.toString(); - } + void bar3() { + for (double[] pa : foo3()) { + for (Double p : pa) { + p.toString(); + } + } } - } } diff --git a/checker/tests/nullness/DefaultsNullness.java b/checker/tests/nullness/DefaultsNullness.java index f34f1459e3a..0c21ec58cc5 100644 --- a/checker/tests/nullness/DefaultsNullness.java +++ b/checker/tests/nullness/DefaultsNullness.java @@ -3,17 +3,17 @@ public class DefaultsNullness { - // local variable defaults - void test(@UnknownInitialization DefaultsNullness para, @Initialized DefaultsNullness comm) { - // @Nullable @UnknownInitialization by default - String s = "abc"; + // local variable defaults + void test(@UnknownInitialization DefaultsNullness para, @Initialized DefaultsNullness comm) { + // @Nullable @UnknownInitialization by default + String s = "abc"; - s = null; + s = null; - DefaultsNullness d; - d = null; // null okay (default == @Nullable) + DefaultsNullness d; + d = null; // null okay (default == @Nullable) - d = comm; // initialized okay (default == @Initialized) - d.hashCode(); - } + d = comm; // initialized okay (default == @Initialized) + d.hashCode(); + } } diff --git a/checker/tests/nullness/DependentTypeTypeInference.java b/checker/tests/nullness/DependentTypeTypeInference.java index bdcb48b248c..9e1f5b206b2 100644 --- a/checker/tests/nullness/DependentTypeTypeInference.java +++ b/checker/tests/nullness/DependentTypeTypeInference.java @@ -1,13 +1,14 @@ +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.KeyFor; public class DependentTypeTypeInference { - private final Map nameToPpt = new LinkedHashMap<>(); + private final Map nameToPpt = new LinkedHashMap<>(); - public Collection<@KeyFor("nameToPpt") String> nameStringSet() { - return Collections.unmodifiableSet(nameToPpt.keySet()); - } + public Collection<@KeyFor("nameToPpt") String> nameStringSet() { + return Collections.unmodifiableSet(nameToPpt.keySet()); + } } diff --git a/checker/tests/nullness/DotClass.java b/checker/tests/nullness/DotClass.java index ecd9c9fd694..058d19c9aa3 100644 --- a/checker/tests/nullness/DotClass.java +++ b/checker/tests/nullness/DotClass.java @@ -1,16 +1,17 @@ -import java.lang.annotation.Annotation; import org.checkerframework.checker.nullness.qual.NonNull; +import java.lang.annotation.Annotation; + public class DotClass { - void test() { - doStuff(NonNull.class); - } + void test() { + doStuff(NonNull.class); + } - void doStuff(Class cl) {} + void doStuff(Class cl) {} - void access() { - Object.class.toString(); - int.class.toString(); - } + void access() { + Object.class.toString(); + int.class.toString(); + } } diff --git a/checker/tests/nullness/EisopIssue308.java b/checker/tests/nullness/EisopIssue308.java index 68c4a2aff57..b5e613a8d6f 100644 --- a/checker/tests/nullness/EisopIssue308.java +++ b/checker/tests/nullness/EisopIssue308.java @@ -2,15 +2,15 @@ // https://github.com/eisop/checker-framework/issues/308 class EisopIssue308Other { - abstract class Inner implements Runnable {} + abstract class Inner implements Runnable {} } class EisopIssue308 { - EisopIssue308Other other = new EisopIssue308Other(); + EisopIssue308Other other = new EisopIssue308Other(); - EisopIssue308Other.Inner foo() { - return other.new Inner() { - public void run() {} - }; - } + EisopIssue308Other.Inner foo() { + return other.new Inner() { + public void run() {} + }; + } } diff --git a/checker/tests/nullness/EmptyConstructor.java b/checker/tests/nullness/EmptyConstructor.java index e0ab8494f66..c37bd11c117 100644 --- a/checker/tests/nullness/EmptyConstructor.java +++ b/checker/tests/nullness/EmptyConstructor.java @@ -4,11 +4,11 @@ import org.checkerframework.dataflow.qual.*; public class SuperClass { - static int count = 0; + static int count = 0; - public SuperClass() { - count++; - } + public SuperClass() { + count++; + } } // The error message is very confusing: @@ -20,6 +20,6 @@ public SuperClass() { // to non-side-effect-free superclass constructor not allowed in side-effect-free constructor" public class EmptyConstructor extends SuperClass { - @SideEffectFree - public EmptyConstructor() {} + @SideEffectFree + public EmptyConstructor() {} } diff --git a/checker/tests/nullness/EnsuresKeyForOverriding.java b/checker/tests/nullness/EnsuresKeyForOverriding.java index bdcde726c77..9d87eb34d71 100644 --- a/checker/tests/nullness/EnsuresKeyForOverriding.java +++ b/checker/tests/nullness/EnsuresKeyForOverriding.java @@ -1,23 +1,24 @@ -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; -public class EnsuresKeyForOverriding { - static class MyClass { - Object field = new Object(); - } +import java.util.Map; - MyClass o = new MyClass(); +public class EnsuresKeyForOverriding { + static class MyClass { + Object field = new Object(); + } - @EnsuresKeyFor(value = "#1.field", map = "#2") - void method(MyClass o, Map map) { - map.put(o.field, "Hello"); - } + MyClass o = new MyClass(); - static class SubEnsuresKeyForOverriding extends EnsuresKeyForOverriding { - @Override @EnsuresKeyFor(value = "#1.field", map = "#2") - void method(MyClass q, Map subMap) { - super.method(q, subMap); + void method(MyClass o, Map map) { + map.put(o.field, "Hello"); + } + + static class SubEnsuresKeyForOverriding extends EnsuresKeyForOverriding { + @Override + @EnsuresKeyFor(value = "#1.field", map = "#2") + void method(MyClass q, Map subMap) { + super.method(q, subMap); + } } - } } diff --git a/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java b/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java index b5b94d3c14c..2eb7d321bd6 100644 --- a/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java +++ b/checker/tests/nullness/EnsuresNonNullIfInheritedTest.java @@ -1,44 +1,44 @@ import org.checkerframework.checker.nullness.qual.*; class Node { - int id; - @Nullable Node next; + int id; + @Nullable Node next; - Node(int id, @Nullable Node next) { - this.id = id; - this.next = next; - } + Node(int id, @Nullable Node next) { + this.id = id; + this.next = next; + } } class SubEnumerate { - protected @Nullable Node current; + protected @Nullable Node current; - public SubEnumerate(Node node) { - this.current = node; - } + public SubEnumerate(Node node) { + this.current = node; + } - @EnsuresNonNullIf(expression = "current", result = true) - public boolean hasMoreElements() { - return (current != null); - } + @EnsuresNonNullIf(expression = "current", result = true) + public boolean hasMoreElements() { + return (current != null); + } } class Enumerate extends SubEnumerate { - public Enumerate(Node node) { - super(node); - } + public Enumerate(Node node) { + super(node); + } - public boolean hasMoreElements() { - return (current != null); - } + public boolean hasMoreElements() { + return (current != null); + } } class Main { - public static final void main(String args[]) { - Node n2 = new Node(2, null); - Node n1 = new Node(1, n2); - Enumerate e = new Enumerate(n1); - while (e.hasMoreElements()) {} - } + public static final void main(String args[]) { + Node n2 = new Node(2, null); + Node n1 = new Node(1, n2); + Enumerate e = new Enumerate(n1); + while (e.hasMoreElements()) {} + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTest.java b/checker/tests/nullness/EnsuresNonNullIfTest.java index ef5ee288afb..a7b88dd0792 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTest.java +++ b/checker/tests/nullness/EnsuresNonNullIfTest.java @@ -7,18 +7,18 @@ public class EnsuresNonNullIfTest { - public static void fromDirPos(File1 dbdir) { - if (dbdir.isDirectory()) { - File1 @NonNull [] files = dbdir.listFiles(); + public static void fromDirPos(File1 dbdir) { + if (dbdir.isDirectory()) { + File1 @NonNull [] files = dbdir.listFiles(); + } } - } - public static void fromDirNeg(File1 dbdir) { - if (!dbdir.isDirectory()) { - throw new Error("Not a directory: " + dbdir); + public static void fromDirNeg(File1 dbdir) { + if (!dbdir.isDirectory()) { + throw new Error("Not a directory: " + dbdir); + } + File1 @NonNull [] files = dbdir.listFiles(); } - File1 @NonNull [] files = dbdir.listFiles(); - } } /////////////////////////////////////////////////////////////////////////// @@ -31,36 +31,36 @@ public static void fromDirNeg(File1 dbdir) { // TODO: Have a way of saying the property holds no matter what value is used in a given expression. public class File1 { - @EnsuresNonNullIf( - result = true, - expression = { - "list()", - "list(String)", // TODO: has no effect - "listFiles()", - "listFiles(String)", // TODO: has no effect - "listFiles(Double)" // TODO: has no effect - }) - public boolean isDirectory() { - throw new RuntimeException("skeleton method"); - } + @EnsuresNonNullIf( + result = true, + expression = { + "list()", + "list(String)", // TODO: has no effect + "listFiles()", + "listFiles(String)", // TODO: has no effect + "listFiles(Double)" // TODO: has no effect + }) + public boolean isDirectory() { + throw new RuntimeException("skeleton method"); + } - public String @Nullable [] list() { - throw new RuntimeException("skeleton method"); - } + public String @Nullable [] list() { + throw new RuntimeException("skeleton method"); + } - public String @Nullable [] list(@Nullable String FilenameFilter_a1) { - throw new RuntimeException("skeleton method"); - } + public String @Nullable [] list(@Nullable String FilenameFilter_a1) { + throw new RuntimeException("skeleton method"); + } - public File1 @Nullable [] listFiles() { - throw new RuntimeException("skeleton method"); - } + public File1 @Nullable [] listFiles() { + throw new RuntimeException("skeleton method"); + } - public File1 @Nullable [] listFiles(@Nullable String FilenameFilter_a1) { - throw new RuntimeException("skeleton method"); - } + public File1 @Nullable [] listFiles(@Nullable String FilenameFilter_a1) { + throw new RuntimeException("skeleton method"); + } - public File1 @Nullable [] listFiles(@Nullable Double FileFilter_a1) { - throw new RuntimeException("skeleton method"); - } + public File1 @Nullable [] listFiles(@Nullable Double FileFilter_a1) { + throw new RuntimeException("skeleton method"); + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTest2.java b/checker/tests/nullness/EnsuresNonNullIfTest2.java index f4476519d7e..27f397d423e 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTest2.java +++ b/checker/tests/nullness/EnsuresNonNullIfTest2.java @@ -4,64 +4,64 @@ /** Test case for issue 53: https://github.com/typetools/checker-framework/issues/53 */ public class EnsuresNonNullIfTest2 { - private @Nullable Long id; + private @Nullable Long id; - public @org.checkerframework.dataflow.qual.Pure @Nullable Long getId() { - return id; - } - - @EnsuresNonNullIf(result = true, expression = "getId()") - public boolean hasId2() { - return getId() != null; - } - - @EnsuresNonNullIf(result = true, expression = "id") - public boolean hasId11() { - return id != null; - } - - @EnsuresNonNullIf(result = true, expression = "id") - public boolean hasId12() { - return this.id != null; - } - - @EnsuresNonNullIf(result = true, expression = "this.id") - public boolean hasId13() { - return id != null; - } + public @org.checkerframework.dataflow.qual.Pure @Nullable Long getId() { + return id; + } - @EnsuresNonNullIf(result = true, expression = "this.id") - public boolean hasId14() { - return this.id != null; - } + @EnsuresNonNullIf(result = true, expression = "getId()") + public boolean hasId2() { + return getId() != null; + } - void client() { - if (hasId11()) { - id.toString(); + @EnsuresNonNullIf(result = true, expression = "id") + public boolean hasId11() { + return id != null; } - if (hasId12()) { - id.toString(); + + @EnsuresNonNullIf(result = true, expression = "id") + public boolean hasId12() { + return this.id != null; } - if (hasId13()) { - id.toString(); + + @EnsuresNonNullIf(result = true, expression = "this.id") + public boolean hasId13() { + return id != null; } - if (hasId14()) { - id.toString(); + + @EnsuresNonNullIf(result = true, expression = "this.id") + public boolean hasId14() { + return this.id != null; } - // :: error: (dereference.of.nullable) - id.toString(); - } - // Expressions referring to enclosing classes should be resolved. - class Inner { - @EnsuresNonNullIf(result = true, expression = "getId()") - public boolean innerHasGetIdMethod() { - return getId() != null; + void client() { + if (hasId11()) { + id.toString(); + } + if (hasId12()) { + id.toString(); + } + if (hasId13()) { + id.toString(); + } + if (hasId14()) { + id.toString(); + } + // :: error: (dereference.of.nullable) + id.toString(); } - @EnsuresNonNullIf(result = true, expression = "id") - public boolean innerHasIdField() { - return id != null; + // Expressions referring to enclosing classes should be resolved. + class Inner { + @EnsuresNonNullIf(result = true, expression = "getId()") + public boolean innerHasGetIdMethod() { + return getId() != null; + } + + @EnsuresNonNullIf(result = true, expression = "id") + public boolean innerHasIdField() { + return id != null; + } } - } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTest4.java b/checker/tests/nullness/EnsuresNonNullIfTest4.java index c6ab3f7208a..d7f577fa904 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTest4.java +++ b/checker/tests/nullness/EnsuresNonNullIfTest4.java @@ -3,29 +3,29 @@ public class EnsuresNonNullIfTest4 { - public void add_bottom_up(MyInvariant inv) { + public void add_bottom_up(MyInvariant inv) { - // The problem goes away if the below line is deleted or replaced with: - // Object x = new Object[100]; - Object x = new @Nullable Object[100]; + // The problem goes away if the below line is deleted or replaced with: + // Object x = new Object[100]; + Object x = new @Nullable Object[100]; - if (inv.is_ni_suppressed()) { - Object ss = inv.get_ni_suppressions(); - ss.toString(); + if (inv.is_ni_suppressed()) { + Object ss = inv.get_ni_suppressions(); + ss.toString(); + } } - } } class MyInvariant { - @Pure - public @Nullable Object get_ni_suppressions() { - return (null); - } + @Pure + public @Nullable Object get_ni_suppressions() { + return (null); + } - @SuppressWarnings("nullness") - @EnsuresNonNullIf(result = true, expression = "get_ni_suppressions()") - @Pure - public boolean is_ni_suppressed() { - return true; - } + @SuppressWarnings("nullness") + @EnsuresNonNullIf(result = true, expression = "get_ni_suppressions()") + @Pure + public boolean is_ni_suppressed() { + return true; + } } diff --git a/checker/tests/nullness/EnsuresNonNullIfTestSimple.java b/checker/tests/nullness/EnsuresNonNullIfTestSimple.java index b07a41050c3..1ea3ccfff3d 100644 --- a/checker/tests/nullness/EnsuresNonNullIfTestSimple.java +++ b/checker/tests/nullness/EnsuresNonNullIfTestSimple.java @@ -7,44 +7,44 @@ */ public class EnsuresNonNullIfTestSimple { - protected int @Nullable [] values; + protected int @Nullable [] values; - @EnsuresNonNullIf(result = true, expression = "values") - public boolean repNulledBAD() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return values == null; - } - - @EnsuresNonNullIf(result = false, expression = "values") - public boolean repNulled() { - return values == null; - } - - public void addAll(EnsuresNonNullIfTestSimple s) { - if (repNulled()) { - return; + @EnsuresNonNullIf(result = true, expression = "values") + public boolean repNulledBAD() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return values == null; } - @NonNull Object x = values; - /* TODO skip-tests - * The two errors are not raised currently - * The assumption that "values" is NN is added above. - * However, as repNulled is not pure, it should be removed again here. - if (s.repNulled()) { - // : : (dereference.of.nullable) - values.hashCode(); - } else { - // we called on "s", so we don't know anything about "values". - // : : (assignment.type.incompatible) - @NonNull Object y = values; + @EnsuresNonNullIf(result = false, expression = "values") + public boolean repNulled() { + return values == null; } - */ - if (s.repNulled()) { - // :: error: (dereference.of.nullable) - s.values.hashCode(); - } else { - @NonNull Object y = s.values; + public void addAll(EnsuresNonNullIfTestSimple s) { + if (repNulled()) { + return; + } + @NonNull Object x = values; + + /* TODO skip-tests + * The two errors are not raised currently + * The assumption that "values" is NN is added above. + * However, as repNulled is not pure, it should be removed again here. + if (s.repNulled()) { + // : : (dereference.of.nullable) + values.hashCode(); + } else { + // we called on "s", so we don't know anything about "values". + // : : (assignment.type.incompatible) + @NonNull Object y = values; + } + */ + + if (s.repNulled()) { + // :: error: (dereference.of.nullable) + s.values.hashCode(); + } else { + @NonNull Object y = s.values; + } } - } } diff --git a/checker/tests/nullness/EnumStaticBlock.java b/checker/tests/nullness/EnumStaticBlock.java index a2defa09871..dc056ffd9c1 100644 --- a/checker/tests/nullness/EnumStaticBlock.java +++ b/checker/tests/nullness/EnumStaticBlock.java @@ -2,15 +2,15 @@ import java.util.List; public class EnumStaticBlock { - public enum Section { - ME, - OTHER; - private static final List l = new ArrayList<>(); + public enum Section { + ME, + OTHER; + private static final List l = new ArrayList<>(); - static { - for (int i = 0; i < 10; ++i) { - l.add(i); - } + static { + for (int i = 0; i < 10; ++i) { + l.add(i); + } + } } - } } diff --git a/checker/tests/nullness/EnumsNullness.java b/checker/tests/nullness/EnumsNullness.java index 7af06af98cf..203884ba4fd 100644 --- a/checker/tests/nullness/EnumsNullness.java +++ b/checker/tests/nullness/EnumsNullness.java @@ -2,38 +2,38 @@ public class EnumsNullness { - enum MyEnum { - A, - B, - C, - D - } - - // :: error: (assignment.type.incompatible) - MyEnum myEnum = null; // invalid - @Nullable MyEnum myNullableEnum = null; + enum MyEnum { + A, + B, + C, + D + } - void testLocalEnum() { - // Enums are allowed to be null: no error here. - MyEnum myNullableEnum = null; // :: error: (assignment.type.incompatible) - @NonNull MyEnum myEnum = null; // invalid - } + MyEnum myEnum = null; // invalid + @Nullable MyEnum myNullableEnum = null; + + void testLocalEnum() { + // Enums are allowed to be null: no error here. + MyEnum myNullableEnum = null; + // :: error: (assignment.type.incompatible) + @NonNull MyEnum myEnum = null; // invalid + } - enum EnumBadAnnos { - A, - // :: error: (nullness.on.enum) - @NonNull B, - // :: error: (nullness.on.enum) - @Nullable C, - D; + enum EnumBadAnnos { + A, + // :: error: (nullness.on.enum) + @NonNull B, + // :: error: (nullness.on.enum) + @Nullable C, + D; - public static final EnumBadAnnos A2 = A; - public static final @NonNull EnumBadAnnos B2 = B; - public static final @Nullable EnumBadAnnos C2 = C; + public static final EnumBadAnnos A2 = A; + public static final @NonNull EnumBadAnnos B2 = B; + public static final @Nullable EnumBadAnnos C2 = C; - @Nullable String method() { - return null; + @Nullable String method() { + return null; + } } - } } diff --git a/checker/tests/nullness/EqualToNullness.java b/checker/tests/nullness/EqualToNullness.java index 2c2dcca3574..9fd3280cba0 100644 --- a/checker/tests/nullness/EqualToNullness.java +++ b/checker/tests/nullness/EqualToNullness.java @@ -3,37 +3,37 @@ public class EqualToNullness { - // @Nullable String f; - // - // void t1(@Nullable String g) { - // // :: error: (dereference.of.nullable) - // g.toLowerCase(); - // if (g != null) { - // g.toLowerCase(); - // } - // } - // - // void t2() { - // // :: error: (dereference.of.nullable) - // f.toLowerCase(); - // if (f == null) {} else { - // f.toLowerCase(); - // } - // } - // - // void t1b(@Nullable String g) { - // // :: error: (dereference.of.nullable) - // g.toLowerCase(); - // if (null != g) { - // g.toLowerCase(); - // } - // } - // - // void t2b() { - // // :: error: (dereference.of.nullable) - // f.toLowerCase(); - // if (null == f) {} else { - // f.toLowerCase(); - // } - // } + // @Nullable String f; + // + // void t1(@Nullable String g) { + // // :: error: (dereference.of.nullable) + // g.toLowerCase(); + // if (g != null) { + // g.toLowerCase(); + // } + // } + // + // void t2() { + // // :: error: (dereference.of.nullable) + // f.toLowerCase(); + // if (f == null) {} else { + // f.toLowerCase(); + // } + // } + // + // void t1b(@Nullable String g) { + // // :: error: (dereference.of.nullable) + // g.toLowerCase(); + // if (null != g) { + // g.toLowerCase(); + // } + // } + // + // void t2b() { + // // :: error: (dereference.of.nullable) + // f.toLowerCase(); + // if (null == f) {} else { + // f.toLowerCase(); + // } + // } } diff --git a/checker/tests/nullness/ExceptionParam.java b/checker/tests/nullness/ExceptionParam.java index 5275ca91eca..bcbdbf32430 100644 --- a/checker/tests/nullness/ExceptionParam.java +++ b/checker/tests/nullness/ExceptionParam.java @@ -5,26 +5,26 @@ /** Exception parameters are non-null, even if the default is nullable. */ @DefaultQualifier(org.checkerframework.checker.nullness.qual.Nullable.class) public class ExceptionParam { - void exc1() { - try { - } catch (AssertionError e) { - @NonNull Object o = e; + void exc1() { + try { + } catch (AssertionError e) { + @NonNull Object o = e; + } } - } - void exc2() { - try { - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull AssertionError e) { - @NonNull Object o = e; + void exc2() { + try { + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull AssertionError e) { + @NonNull Object o = e; + } } - } - void exc3() { - try { - // :: warning: (nullness.on.exception.parameter) - } catch (@Nullable AssertionError e) { - @NonNull Object o = e; + void exc3() { + try { + // :: warning: (nullness.on.exception.parameter) + } catch (@Nullable AssertionError e) { + @NonNull Object o = e; + } } - } } diff --git a/checker/tests/nullness/Exceptions.java b/checker/tests/nullness/Exceptions.java index e255ecc1710..8e77f9457a7 100644 --- a/checker/tests/nullness/Exceptions.java +++ b/checker/tests/nullness/Exceptions.java @@ -1,47 +1,47 @@ import org.checkerframework.checker.nullness.qual.*; public class Exceptions { - void exceptionParam(@Nullable Exception m) { - // :: error: (dereference.of.nullable) - m.getClass(); // should emit error - } + void exceptionParam(@Nullable Exception m) { + // :: error: (dereference.of.nullable) + m.getClass(); // should emit error + } - void nonnullExceptionParam(@NonNull Exception m) { - m.getClass(); - } + void nonnullExceptionParam(@NonNull Exception m) { + m.getClass(); + } - void exception(@Nullable Exception m) { - try { - throwException(); - } catch (Exception e) { - e.getClass(); - // :: error: (dereference.of.nullable) - m.getClass(); // should emit error + void exception(@Nullable Exception m) { + try { + throwException(); + } catch (Exception e) { + e.getClass(); + // :: error: (dereference.of.nullable) + m.getClass(); // should emit error + } } - } - void throwException() { - int a = 0; - if (a == 0) { - // :: error: (throwing.nullable) - throw null; - } else if (a == 1) { - RuntimeException e = null; - // :: error: (throwing.nullable) - throw e; - } else { - RuntimeException e = new RuntimeException(); - throw e; + void throwException() { + int a = 0; + if (a == 0) { + // :: error: (throwing.nullable) + throw null; + } else if (a == 1) { + RuntimeException e = null; + // :: error: (throwing.nullable) + throw e; + } else { + RuntimeException e = new RuntimeException(); + throw e; + } } - } - void reassignException() { - try { - throwException(); - } catch (RuntimeException e) { - // :: error: (assignment.type.incompatible) - e = null; - throw e; + void reassignException() { + try { + throwException(); + } catch (RuntimeException e) { + // :: error: (assignment.type.incompatible) + e = null; + throw e; + } } - } } diff --git a/checker/tests/nullness/ExplictTypeVarAnnos.java b/checker/tests/nullness/ExplictTypeVarAnnos.java index 5149a3210c9..4f2d43ac9b1 100644 --- a/checker/tests/nullness/ExplictTypeVarAnnos.java +++ b/checker/tests/nullness/ExplictTypeVarAnnos.java @@ -2,46 +2,46 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ExplictTypeVarAnnos { - interface Consumer {} - - public static Consumer cast( - final @Nullable Consumer consumer) { - throw new RuntimeException(); - } - - public static Consumer getConsumer0() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - public static <@Nullable D> Consumer getConsumer1() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - public Consumer getConsumer2() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - public Consumer getConsumer3() { - Consumer<@Nullable Object> nullConsumer = null; - Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); - return result; - } - - @SuppressWarnings("method.invocation.invalid") - Consumer field = getConsumer2(); - - public Consumer getField() { - return field; - } - - static class A {} - - // :: error: (type.argument.type.incompatible) - static class B extends A {} + interface Consumer {} + + public static Consumer cast( + final @Nullable Consumer consumer) { + throw new RuntimeException(); + } + + public static Consumer getConsumer0() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + public static <@Nullable D> Consumer getConsumer1() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + public Consumer getConsumer2() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + public Consumer getConsumer3() { + Consumer<@Nullable Object> nullConsumer = null; + Consumer result = ExplictTypeVarAnnos.cast(nullConsumer); + return result; + } + + @SuppressWarnings("method.invocation.invalid") + Consumer field = getConsumer2(); + + public Consumer getField() { + return field; + } + + static class A {} + + // :: error: (type.argument.type.incompatible) + static class B extends A {} } diff --git a/checker/tests/nullness/ExpressionsNullness.java b/checker/tests/nullness/ExpressionsNullness.java index b0f026faea2..824c42b7233 100644 --- a/checker/tests/nullness/ExpressionsNullness.java +++ b/checker/tests/nullness/ExpressionsNullness.java @@ -1,3 +1,6 @@ +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.framework.qual.DefaultQualifier; + import java.io.*; import java.util.Date; import java.util.HashMap; @@ -6,64 +9,62 @@ import java.util.List; import java.util.Set; import java.util.regex.*; -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.framework.qual.DefaultQualifier; @DefaultQualifier(NonNull.class) public class ExpressionsNullness { - public static double[] returnDoubleArray() { - return new double[] {3.14, 2.7}; - } + public static double[] returnDoubleArray() { + return new double[] {3.14, 2.7}; + } - public static void staticMembers() { - Pattern.compile("^>entry *()"); - System.out.println(ExpressionsNullness.class); - ExpressionsNullness.class.getAnnotations(); // valid - } + public static void staticMembers() { + Pattern.compile("^>entry *()"); + System.out.println(ExpressionsNullness.class); + ExpressionsNullness.class.getAnnotations(); // valid + } - private HashMap map = new HashMap<>(); + private HashMap map = new HashMap<>(); - public void test() { - @SuppressWarnings("nullness") - String s = map.get("foo"); + public void test() { + @SuppressWarnings("nullness") + String s = map.get("foo"); - Class cl = Boolean.TYPE; + Class cl = Boolean.TYPE; - List foo = new LinkedList(); - // :: error: (dereference.of.nullable) - foo.get(0).toString(); // default applies to wildcard extends + List foo = new LinkedList(); + // :: error: (dereference.of.nullable) + foo.get(0).toString(); // default applies to wildcard extends - Set set = new HashSet(); - for (@Nullable Object o : set) System.out.println(); - } + Set set = new HashSet(); + for (@Nullable Object o : set) System.out.println(); + } - void test2() { - List lst = new LinkedList<@NonNull String>(); - for (String s : lst) { - s.length(); + void test2() { + List lst = new LinkedList<@NonNull String>(); + for (String s : lst) { + s.length(); + } } - } - void test3(T o) { - o.getClass(); // valid - } + void test3(T o) { + o.getClass(); // valid + } - void test4(List o) { - o.get(0).getClass(); // valid - } + void test4(List o) { + o.get(0).getClass(); // valid + } - void test5() { - Comparable d = new Date(); - } + void test5() { + Comparable d = new Date(); + } - void testIntersection() { - java.util.Arrays.asList("m", 1); - } + void testIntersection() { + java.util.Arrays.asList("m", 1); + } - Object obj; + Object obj; - public ExpressionsNullness(Object obj) { - this.obj = obj; - } + public ExpressionsNullness(Object obj) { + this.obj = obj; + } } diff --git a/checker/tests/nullness/ExtendsArrayList.java b/checker/tests/nullness/ExtendsArrayList.java index 82429b674ce..19740363155 100644 --- a/checker/tests/nullness/ExtendsArrayList.java +++ b/checker/tests/nullness/ExtendsArrayList.java @@ -3,10 +3,10 @@ public final class ExtendsArrayList extends ArrayList { - public int removeMany(List toRemove) { - for (String inv : this) { - if (!toRemove.contains(inv)) {} + public int removeMany(List toRemove) { + for (String inv : this) { + if (!toRemove.contains(inv)) {} + } + return 0; } - return 0; - } } diff --git a/checker/tests/nullness/FenumExplicit.java b/checker/tests/nullness/FenumExplicit.java index d2b5e78e403..7290269205d 100644 --- a/checker/tests/nullness/FenumExplicit.java +++ b/checker/tests/nullness/FenumExplicit.java @@ -6,18 +6,18 @@ class EnumExplicit { - public static enum EnumWithMethod { - VALUE { - @Override - public void call(@Nullable String string) { - // Null string is acceptable in this function. - } - }; + public static enum EnumWithMethod { + VALUE { + @Override + public void call(@Nullable String string) { + // Null string is acceptable in this function. + } + }; - public abstract void call(String string); - } + public abstract void call(String string); + } - public static void main(String[] args) { - EnumWithMethod.VALUE.call(null); - } + public static void main(String[] args) { + EnumWithMethod.VALUE.call(null); + } } diff --git a/checker/tests/nullness/FieldWithAnnotatedLambda.java b/checker/tests/nullness/FieldWithAnnotatedLambda.java index 34c311e999b..80151cb9f77 100644 --- a/checker/tests/nullness/FieldWithAnnotatedLambda.java +++ b/checker/tests/nullness/FieldWithAnnotatedLambda.java @@ -1,17 +1,18 @@ -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Function; + class FieldWithAnnotatedLambda { - Function f1 = (@Nullable Object in) -> in; + Function f1 = (@Nullable Object in) -> in; - class Outer { - class Inner {} - } + class Outer { + class Inner {} + } - Function f2 = (Outer.@Nullable Inner in) -> in; + Function f2 = (Outer.@Nullable Inner in) -> in; - // Lambda parameter is defaulted to be @NonNull, raising an error - // for the lambda parameter type. - // :: error: (lambda.param.type.incompatible) - Function f3 = (Outer.Inner in) -> in; + // Lambda parameter is defaulted to be @NonNull, raising an error + // for the lambda parameter type. + // :: error: (lambda.param.type.incompatible) + Function f3 = (Outer.Inner in) -> in; } diff --git a/checker/tests/nullness/FinalFields.java b/checker/tests/nullness/FinalFields.java index e7c0febccee..e385837b552 100644 --- a/checker/tests/nullness/FinalFields.java +++ b/checker/tests/nullness/FinalFields.java @@ -1,47 +1,47 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Upper { - @Nullable String fs = "NonNull init"; - private final @Nullable String ffs = "NonNull init"; + @Nullable String fs = "NonNull init"; + private final @Nullable String ffs = "NonNull init"; - void access() { - // Error, because non-final field type is not refined - // :: error: (dereference.of.nullable) - fs.hashCode(); - // private final field is refined - ffs.hashCode(); - } + void access() { + // Error, because non-final field type is not refined + // :: error: (dereference.of.nullable) + fs.hashCode(); + // private final field is refined + ffs.hashCode(); + } } public class FinalFields { - public void foo(Upper u) { - // Error, because final field in different class is not refined - // :: error: (dereference.of.nullable) - u.fs.hashCode(); - } + public void foo(Upper u) { + // Error, because final field in different class is not refined + // :: error: (dereference.of.nullable) + u.fs.hashCode(); + } - public void bar(Lower l) { - // Error, because final field in different class is not refined - // :: error: (dereference.of.nullable) - l.fs.hashCode(); - } + public void bar(Lower l) { + // Error, because final field in different class is not refined + // :: error: (dereference.of.nullable) + l.fs.hashCode(); + } - public void local() { - @Nullable String ls = "Locals"; - // Local variable is refined - ls.hashCode(); - } + public void local() { + @Nullable String ls = "Locals"; + // Local variable is refined + ls.hashCode(); + } } class Lower { - @Nullable String fs = "NonNull init, too"; - private final @Nullable String ffs = "NonNull init, too"; + @Nullable String fs = "NonNull init, too"; + private final @Nullable String ffs = "NonNull init, too"; - void access() { - // Error, because non-final field type is not refined - // :: error: (dereference.of.nullable) - fs.hashCode(); - // private final field is refined - ffs.hashCode(); - } + void access() { + // Error, because non-final field type is not refined + // :: error: (dereference.of.nullable) + fs.hashCode(); + // private final field is refined + ffs.hashCode(); + } } diff --git a/checker/tests/nullness/FinalVar.java b/checker/tests/nullness/FinalVar.java index 216a99e65ce..1aba0708f94 100644 --- a/checker/tests/nullness/FinalVar.java +++ b/checker/tests/nullness/FinalVar.java @@ -2,18 +2,18 @@ public class FinalVar { - public Object pptIterator() { - // Only test with (effectively) final variables; Java only permits final or - // effectively final variables to be accessed from an anonymous class. - final String iter_view_1 = "I am not null"; - @NonNull String iter_view_2 = "Neither am I"; - final @NonNull String iter_view_3 = "Dittos"; - return new Object() { - public void useFinalVar() { - iter_view_1.hashCode(); - iter_view_2.hashCode(); - iter_view_3.hashCode(); - } - }; - } + public Object pptIterator() { + // Only test with (effectively) final variables; Java only permits final or + // effectively final variables to be accessed from an anonymous class. + final String iter_view_1 = "I am not null"; + @NonNull String iter_view_2 = "Neither am I"; + final @NonNull String iter_view_3 = "Dittos"; + return new Object() { + public void useFinalVar() { + iter_view_1.hashCode(); + iter_view_2.hashCode(); + iter_view_3.hashCode(); + } + }; + } } diff --git a/checker/tests/nullness/FinalVar2.java b/checker/tests/nullness/FinalVar2.java index 9201396cfbb..af05348ff8c 100644 --- a/checker/tests/nullness/FinalVar2.java +++ b/checker/tests/nullness/FinalVar2.java @@ -5,43 +5,43 @@ public class FinalVar2 { - static Object method1(@Nullable Object arg) { - final Object tmp = arg; - if (tmp == null) { - return "hello"; + static Object method1(@Nullable Object arg) { + final Object tmp = arg; + if (tmp == null) { + return "hello"; + } + return new Object() { + public void useFinalVar() { + // should be OK + tmp.hashCode(); + } + }; } - return new Object() { - public void useFinalVar() { - // should be OK - tmp.hashCode(); - } - }; - } - static Object method2(final @Nullable Object arg) { - if (arg == null) { - return "hello"; + static Object method2(final @Nullable Object arg) { + if (arg == null) { + return "hello"; + } + return new Object() { + public void useFinalVar() { + // should be OK + arg.hashCode(); + } + }; } - return new Object() { - public void useFinalVar() { - // should be OK - arg.hashCode(); - } - }; - } - static Object method3(@Nullable Object arg) { - final Object tmp = arg; - Object result = - new Object() { - public void useFinalVar() { - // :: error: (dereference.of.nullable) - tmp.hashCode(); - } - }; - if (tmp == null) { - return "hello"; + static Object method3(@Nullable Object arg) { + final Object tmp = arg; + Object result = + new Object() { + public void useFinalVar() { + // :: error: (dereference.of.nullable) + tmp.hashCode(); + } + }; + if (tmp == null) { + return "hello"; + } + return result; } - return result; - } } diff --git a/checker/tests/nullness/FinalVar3.java b/checker/tests/nullness/FinalVar3.java index 05be5b25762..0a024047ec6 100644 --- a/checker/tests/nullness/FinalVar3.java +++ b/checker/tests/nullness/FinalVar3.java @@ -2,13 +2,13 @@ public class FinalVar3 { - static Object method1(@Nullable Object arg) { - final Object tmp = arg; - if (tmp == null) { - return "hello"; + static Object method1(@Nullable Object arg) { + final Object tmp = arg; + if (tmp == null) { + return "hello"; + } + // The type of the final variable is correctly refined. + tmp.hashCode(); + return "bye"; } - // The type of the final variable is correctly refined. - tmp.hashCode(); - return "bye"; - } } diff --git a/checker/tests/nullness/FindBugs.java b/checker/tests/nullness/FindBugs.java index 97456f5115e..8036af6f575 100644 --- a/checker/tests/nullness/FindBugs.java +++ b/checker/tests/nullness/FindBugs.java @@ -2,30 +2,30 @@ public class FindBugs { - @CheckForNull - Object getNull() { - return null; - } + @CheckForNull + Object getNull() { + return null; + } - @NonNull MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> getListOfNulls() { - // :: error: (return.type.incompatible) - return null; // error - } + @NonNull MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> getListOfNulls() { + // :: error: (return.type.incompatible) + return null; // error + } - void test() { - Object o = getNull(); - // :: error: (dereference.of.nullable) - o.toString(); // error + void test() { + Object o = getNull(); + // :: error: (dereference.of.nullable) + o.toString(); // error - MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> l = getListOfNulls(); - l.toString(); - // :: error: (dereference.of.nullable) - l.get().toString(); // error - } + MyList<@org.checkerframework.checker.nullness.qual.Nullable Object> l = getListOfNulls(); + l.toString(); + // :: error: (dereference.of.nullable) + l.get().toString(); // error + } } class MyList { - T get() { - throw new RuntimeException(); - } + T get() { + throw new RuntimeException(); + } } diff --git a/checker/tests/nullness/FlowAssignment.java b/checker/tests/nullness/FlowAssignment.java index 8d77c0bdb1c..55a86a58c11 100644 --- a/checker/tests/nullness/FlowAssignment.java +++ b/checker/tests/nullness/FlowAssignment.java @@ -2,10 +2,10 @@ public class FlowAssignment { - void test() { - @NonNull String s = "foo"; + void test() { + @NonNull String s = "foo"; - String t = s; - t.startsWith("f"); - } + String t = s; + t.startsWith("f"); + } } diff --git a/checker/tests/nullness/FlowCompound.java b/checker/tests/nullness/FlowCompound.java index 0178e2e3b26..67846105552 100644 --- a/checker/tests/nullness/FlowCompound.java +++ b/checker/tests/nullness/FlowCompound.java @@ -2,56 +2,56 @@ public class FlowCompound { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(@Nullable Object o) { - return o != null && this.getClass() != o.getClass(); - } - - void test(@Nullable String s) { - - if (s == null || s.length() > 0) { - // :: error: (assignment.type.incompatible) - @NonNull String test = s; - } - - String tmp; - @NonNull String notNull; - tmp = "hello"; - notNull = tmp; - notNull = tmp = "hello"; - } - - public static boolean equal(@Nullable Object a, @Nullable Object b) { - assert b != null : "suppress nullness"; - return a == b || (a != null && a.equals(b)); - } - - public static void testCompoundAssignmentWithString() { - String s = "m"; - s += "n"; - s.toString(); - } - - public static void testCompoundAssignmentWithChar() { - String s = "m"; - s += 'n'; - s.toString(); - } - - public static void testCompoundAssignWithNull() { - String s = "m"; - s += null; - s.toString(); - } - - public static void testPrimitiveArray() { - int[] a = {0}; - a[0] += 2; - System.out.println(a[0]); - } - - public static void testPrimitive() { - Integer i = 1; - i -= 2; - } + @org.checkerframework.dataflow.qual.Pure + public boolean equals(@Nullable Object o) { + return o != null && this.getClass() != o.getClass(); + } + + void test(@Nullable String s) { + + if (s == null || s.length() > 0) { + // :: error: (assignment.type.incompatible) + @NonNull String test = s; + } + + String tmp; + @NonNull String notNull; + tmp = "hello"; + notNull = tmp; + notNull = tmp = "hello"; + } + + public static boolean equal(@Nullable Object a, @Nullable Object b) { + assert b != null : "suppress nullness"; + return a == b || (a != null && a.equals(b)); + } + + public static void testCompoundAssignmentWithString() { + String s = "m"; + s += "n"; + s.toString(); + } + + public static void testCompoundAssignmentWithChar() { + String s = "m"; + s += 'n'; + s.toString(); + } + + public static void testCompoundAssignWithNull() { + String s = "m"; + s += null; + s.toString(); + } + + public static void testPrimitiveArray() { + int[] a = {0}; + a[0] += 2; + System.out.println(a[0]); + } + + public static void testPrimitive() { + Integer i = 1; + i -= 2; + } } diff --git a/checker/tests/nullness/FlowCompoundConcatenation.java b/checker/tests/nullness/FlowCompoundConcatenation.java index 81819d77490..8ab7c81a7a6 100644 --- a/checker/tests/nullness/FlowCompoundConcatenation.java +++ b/checker/tests/nullness/FlowCompoundConcatenation.java @@ -1,17 +1,17 @@ public class FlowCompoundConcatenation { - static String getNonNullString() { - return ""; - } + static String getNonNullString() { + return ""; + } - public static void testCompoundAssignWithNullAndMethodCall() { - String s = null; - s += getNonNullString(); - s.toString(); - } + public static void testCompoundAssignWithNullAndMethodCall() { + String s = null; + s += getNonNullString(); + s.toString(); + } - public static void testCompoundAssignWithNull() { - String s = null; - s += "hello"; - s.toString(); - } + public static void testCompoundAssignWithNull() { + String s = null; + s += "hello"; + s.toString(); + } } diff --git a/checker/tests/nullness/FlowConditions.java b/checker/tests/nullness/FlowConditions.java index 71c0e8b602b..bef7c35fa56 100644 --- a/checker/tests/nullness/FlowConditions.java +++ b/checker/tests/nullness/FlowConditions.java @@ -1,40 +1,41 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; public class FlowConditions { - void m(@Nullable Object x, @Nullable Object y) { - if (x == null || y == null) { - // :: error: (dereference.of.nullable) - x.toString(); - // :: error: (dereference.of.nullable) - y.toString(); - } else { - x.toString(); - y.toString(); + void m(@Nullable Object x, @Nullable Object y) { + if (x == null || y == null) { + // :: error: (dereference.of.nullable) + x.toString(); + // :: error: (dereference.of.nullable) + y.toString(); + } else { + x.toString(); + y.toString(); + } } - } - private final Map> graph = new HashMap<>(); + private final Map> graph = new HashMap<>(); - public void addEdge1(String e, String parent, String child) { - if (!graph.containsKey(parent)) { - throw new NoSuchElementException(); - } - if (!graph.containsKey(child)) { - throw new NoSuchElementException(); + public void addEdge1(String e, String parent, String child) { + if (!graph.containsKey(parent)) { + throw new NoSuchElementException(); + } + if (!graph.containsKey(child)) { + throw new NoSuchElementException(); + } + @NonNull Set edges = graph.get(parent); } - @NonNull Set edges = graph.get(parent); - } - // TODO: Re-enable when issue 221 is resolved. - // public void addEdge2(String e, String parent, String child) { - // if ( (!graph.containsKey(parent)) || - // (!graph.containsKey(child))) - // throw new NoSuchElementException(); - // @NonNull Set edges = graph.get(parent); - // } + // TODO: Re-enable when issue 221 is resolved. + // public void addEdge2(String e, String parent, String child) { + // if ( (!graph.containsKey(parent)) || + // (!graph.containsKey(child))) + // throw new NoSuchElementException(); + // @NonNull Set edges = graph.get(parent); + // } } diff --git a/checker/tests/nullness/FlowExpressionParsingBug.java b/checker/tests/nullness/FlowExpressionParsingBug.java index 0727a7be6e5..b2acf73c976 100644 --- a/checker/tests/nullness/FlowExpressionParsingBug.java +++ b/checker/tests/nullness/FlowExpressionParsingBug.java @@ -1,92 +1,93 @@ -import javax.swing.JMenuBar; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import javax.swing.JMenuBar; + public abstract class FlowExpressionParsingBug { - //// Check that JavaExpressions with explicit and implicit 'this' work - - protected @Nullable JMenuBar menuBar = null; - - @RequiresNonNull("menuBar") - public void addFavorite() {} - - @RequiresNonNull("this.menuBar") - public void addFavorite1() {} - - //// Check JavaExpressions for static fields with different ways to access the field - - static @Nullable String i = null; - - @RequiresNonNull("FlowExpressionParsingBug.i") - public void a() {} - - @RequiresNonNull("i") - public void b() {} - - @RequiresNonNull("this.i") - public void c() {} - - void test1() { - // :: error: (contracts.precondition.not.satisfied) - a(); - FlowExpressionParsingBug.i = ""; - a(); - } - - void test1b() { - // :: error: (contracts.precondition.not.satisfied) - a(); - i = ""; - a(); - } - - void test1c() { - // :: error: (contracts.precondition.not.satisfied) - a(); - this.i = ""; - a(); - } - - void test2() { - // :: error: (contracts.precondition.not.satisfied) - b(); - FlowExpressionParsingBug.i = ""; - b(); - } - - void test2b() { - // :: error: (contracts.precondition.not.satisfied) - b(); - i = ""; - b(); - } - - void test2c() { - // :: error: (contracts.precondition.not.satisfied) - b(); - this.i = ""; - b(); - } - - void test3() { - // :: error: (contracts.precondition.not.satisfied) - c(); - FlowExpressionParsingBug.i = ""; - c(); - } - - void test3b() { - // :: error: (contracts.precondition.not.satisfied) - c(); - i = ""; - c(); - } - - void test3c() { - // :: error: (contracts.precondition.not.satisfied) - c(); - this.i = ""; - c(); - } + //// Check that JavaExpressions with explicit and implicit 'this' work + + protected @Nullable JMenuBar menuBar = null; + + @RequiresNonNull("menuBar") + public void addFavorite() {} + + @RequiresNonNull("this.menuBar") + public void addFavorite1() {} + + //// Check JavaExpressions for static fields with different ways to access the field + + static @Nullable String i = null; + + @RequiresNonNull("FlowExpressionParsingBug.i") + public void a() {} + + @RequiresNonNull("i") + public void b() {} + + @RequiresNonNull("this.i") + public void c() {} + + void test1() { + // :: error: (contracts.precondition.not.satisfied) + a(); + FlowExpressionParsingBug.i = ""; + a(); + } + + void test1b() { + // :: error: (contracts.precondition.not.satisfied) + a(); + i = ""; + a(); + } + + void test1c() { + // :: error: (contracts.precondition.not.satisfied) + a(); + this.i = ""; + a(); + } + + void test2() { + // :: error: (contracts.precondition.not.satisfied) + b(); + FlowExpressionParsingBug.i = ""; + b(); + } + + void test2b() { + // :: error: (contracts.precondition.not.satisfied) + b(); + i = ""; + b(); + } + + void test2c() { + // :: error: (contracts.precondition.not.satisfied) + b(); + this.i = ""; + b(); + } + + void test3() { + // :: error: (contracts.precondition.not.satisfied) + c(); + FlowExpressionParsingBug.i = ""; + c(); + } + + void test3b() { + // :: error: (contracts.precondition.not.satisfied) + c(); + i = ""; + c(); + } + + void test3c() { + // :: error: (contracts.precondition.not.satisfied) + c(); + this.i = ""; + c(); + } } diff --git a/checker/tests/nullness/FlowField.java b/checker/tests/nullness/FlowField.java index 2663e1f87ce..9e2a29d08cd 100644 --- a/checker/tests/nullness/FlowField.java +++ b/checker/tests/nullness/FlowField.java @@ -3,74 +3,74 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class FlowField { - public @Nullable String s = null; + public @Nullable String s = null; - void test() { - if (s != null) { - s.startsWith("foo"); + void test() { + if (s != null) { + s.startsWith("foo"); + } } - } - static String field = "asdf"; + static String field = "asdf"; - static { - field = "asdf"; - } + static { + field = "asdf"; + } - void testFields() { - // :: error: (dereference.of.nullable) - System.out.println(field.length()); - } + void testFields() { + // :: error: (dereference.of.nullable) + System.out.println(field.length()); + } - // Fowrard reference to static finals - void test1() { - nonnull.toString(); - } + // Fowrard reference to static finals + void test1() { + nonnull.toString(); + } - private static final String nonnull = new String(); + private static final String nonnull = new String(); - class A { - protected String field = null; - } + class A { + protected String field = null; + } - class B extends A { - void test() { - assert field != null : "@AssumeAssertion(nullness)"; - field.length(); + class B extends A { + void test() { + assert field != null : "@AssumeAssertion(nullness)"; + field.length(); + } } - } - static class BooleanWrapper { - @Nullable Object b; - } + static class BooleanWrapper { + @Nullable Object b; + } - @Nullable BooleanWrapper bw; + @Nullable BooleanWrapper bw; - void testBitwise(@NonNull FlowField a, @NonNull FlowField b) { - Object r; - if (a.bw != null) { - if (b.bw != null) { - r = a.bw.b; - r = b.bw.b; - } + void testBitwise(@NonNull FlowField a, @NonNull FlowField b) { + Object r; + if (a.bw != null) { + if (b.bw != null) { + r = a.bw.b; + r = b.bw.b; + } + } + if (a.bw != null && b.bw != null) { + r = a.bw.b; + r = b.bw.b; + } } - if (a.bw != null && b.bw != null) { - r = a.bw.b; - r = b.bw.b; - } - } - void testInstanceOf(@NonNull FlowField a) { - if (!(a.s instanceof String)) { - return; + void testInstanceOf(@NonNull FlowField a) { + if (!(a.s instanceof String)) { + return; + } + @NonNull String s = a.s; } - @NonNull String s = a.s; - } - void testTwoLevels(@NonNull FlowField a, BooleanWrapper bwArg) { - // :: error: (dereference.of.nullable) - if (!(a.bw.hashCode() == 0)) // warning here - return; - Object o = a.bw.b; // but not here - } + void testTwoLevels(@NonNull FlowField a, BooleanWrapper bwArg) { + // :: error: (dereference.of.nullable) + if (!(a.bw.hashCode() == 0)) // warning here + return; + Object o = a.bw.b; // but not here + } } diff --git a/checker/tests/nullness/FlowLoop.java b/checker/tests/nullness/FlowLoop.java index 1beef9f7fac..6f91788a7ea 100644 --- a/checker/tests/nullness/FlowLoop.java +++ b/checker/tests/nullness/FlowLoop.java @@ -1,162 +1,162 @@ import org.checkerframework.checker.nullness.qual.*; public class FlowLoop { - void simpleWhileLoop() { - String s = "m"; + void simpleWhileLoop() { + String s = "m"; - while (s != null) { - s.toString(); - s = null; + while (s != null) { + s.toString(); + s = null; + } + // :: error: (dereference.of.nullable) + s.toString(); // error + } + + void whileConditionError() { + String s = "m"; + + // :: error: (dereference.of.nullable) + while (s.toString() == "m") { // error + s.toString(); + s = null; + } + s.toString(); + } + + void simpleForLoop() { + for (String s = "m"; s != null; s = null) { + s.toString(); + } + } + + void forLoopConditionError() { + for (String s = "m"; + // :: error: (dereference.of.nullable) + s.toString() != "m"; // error + s = null) { + s.toString(); + } + } + + class Link { + Object val; + @Nullable Link next; + + public Link(Object val, @Nullable Link next) { + this.val = val; + this.next = next; + } + } + + // Both dereferences of l should succeed + void test(@Nullable Link in) { + for (@Nullable Link l = in; l != null; l = l.next) { + Object o; + o = l.val; + } + } + + void multipleRuns() { + String s = "m"; + while (true) { + // :: error: (dereference.of.nullable) + s.toString(); // error + s = null; + } + } + + void multipleRunsDo() { + String s = "m"; + do { + // :: error: (dereference.of.nullable) + s.toString(); // error + s = null; + } while (true); + } + + void alwaysRunForLoop() { + String s = "m"; + for (s = null; s != null; s = "m") { + s.toString(); // ok + } + // :: error: (dereference.of.nullable) + s.toString(); // error + } + + public void badIterator() { + Class opt_doc1 = null; + // :: error: (dereference.of.nullable) + opt_doc1.getInterfaces(); + Class opt_doc2 = null; + // :: error: (dereference.of.nullable) + for (Class fd : opt_doc2.getInterfaces()) { + // empty loop body + } } - // :: error: (dereference.of.nullable) - s.toString(); // error - } - void whileConditionError() { - String s = "m"; + void testContinue(@Nullable Object o) { + for (; ; ) { + // :: error: (dereference.of.nullable) + o.toString(); + if (true) continue; + } + } - // :: error: (dereference.of.nullable) - while (s.toString() == "m") { // error - s.toString(); - s = null; + void testBreak(@Nullable Object o) { + while (true) { + // :: error: (dereference.of.nullable) + o.toString(); + if (true) break; + } } - s.toString(); - } - void simpleForLoop() { - for (String s = "m"; s != null; s = null) { - s.toString(); + void testSimpleNull() { + String r1 = null; + while (r1 != null) {} + // :: error: (dereference.of.nullable) + r1.toString(); // error } - } - void forLoopConditionError() { - for (String s = "m"; + void testMulticheckNull() { + String r1 = null; + while (r1 != null && r1.equals("m")) {} // :: error: (dereference.of.nullable) - s.toString() != "m"; // error - s = null) { - s.toString(); - } - } - - class Link { - Object val; - @Nullable Link next; - - public Link(Object val, @Nullable Link next) { - this.val = val; - this.next = next; - } - } - - // Both dereferences of l should succeed - void test(@Nullable Link in) { - for (@Nullable Link l = in; l != null; l = l.next) { - Object o; - o = l.val; - } - } - - void multipleRuns() { - String s = "m"; - while (true) { - // :: error: (dereference.of.nullable) - s.toString(); // error - s = null; - } - } - - void multipleRunsDo() { - String s = "m"; - do { - // :: error: (dereference.of.nullable) - s.toString(); // error - s = null; - } while (true); - } - - void alwaysRunForLoop() { - String s = "m"; - for (s = null; s != null; s = "m") { - s.toString(); // ok - } - // :: error: (dereference.of.nullable) - s.toString(); // error - } - - public void badIterator() { - Class opt_doc1 = null; - // :: error: (dereference.of.nullable) - opt_doc1.getInterfaces(); - Class opt_doc2 = null; - // :: error: (dereference.of.nullable) - for (Class fd : opt_doc2.getInterfaces()) { - // empty loop body - } - } - - void testContinue(@Nullable Object o) { - for (; ; ) { - // :: error: (dereference.of.nullable) - o.toString(); - if (true) continue; - } - } - - void testBreak(@Nullable Object o) { - while (true) { - // :: error: (dereference.of.nullable) - o.toString(); - if (true) break; - } - } - - void testSimpleNull() { - String r1 = null; - while (r1 != null) {} - // :: error: (dereference.of.nullable) - r1.toString(); // error - } - - void testMulticheckNull() { - String r1 = null; - while (r1 != null && r1.equals("m")) {} - // :: error: (dereference.of.nullable) - r1.toString(); // error - } - - void testAssignInLoopSimple() { - String r1 = ""; - while (r1 != null) { - r1 = null; - } - // :: error: (dereference.of.nullable) - r1.toString(); // error - } - - void testAssignInLoopMulti() { - String r1 = ""; - while (r1 != null && r1.isEmpty()) { - r1 = null; - } - // :: error: (dereference.of.nullable) - r1.toString(); // error - } - - void testBreakWithCheck() { - String s = null; - while (true) { - if (s == null) break; - s.toString(); - } - } - - void test1() { - while (true) { - String s = null; - if (s == null) { - return; - } - s.toString(); - } - } + r1.toString(); // error + } + + void testAssignInLoopSimple() { + String r1 = ""; + while (r1 != null) { + r1 = null; + } + // :: error: (dereference.of.nullable) + r1.toString(); // error + } + + void testAssignInLoopMulti() { + String r1 = ""; + while (r1 != null && r1.isEmpty()) { + r1 = null; + } + // :: error: (dereference.of.nullable) + r1.toString(); // error + } + + void testBreakWithCheck() { + String s = null; + while (true) { + if (s == null) break; + s.toString(); + } + } + + void test1() { + while (true) { + String s = null; + if (s == null) { + return; + } + s.toString(); + } + } } diff --git a/checker/tests/nullness/FlowNegation.java b/checker/tests/nullness/FlowNegation.java index 88afc48a148..cc3716447a4 100644 --- a/checker/tests/nullness/FlowNegation.java +++ b/checker/tests/nullness/FlowNegation.java @@ -2,98 +2,98 @@ public class FlowNegation { - void testSimpleValid() { - String s = "m"; - s.toString(); - } + void testSimpleValid() { + String s = "m"; + s.toString(); + } - void testCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s != null) { - } else { - // nothing to do + void testCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s != null) { + } else { + // nothing to do + } + s.toString(); } - s.toString(); - } - void testCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s == null) { - } else { - // nothing to do + void testCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s == null) { + } else { + // nothing to do + } + s.toString(); } - s.toString(); - } - void testInvalidCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s != null) { - s = null; - } else { - // nothing to do + void testInvalidCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s != null) { + s = null; + } else { + // nothing to do + } + // :: error: (dereference.of.nullable) + s.toString(); // error } - // :: error: (dereference.of.nullable) - s.toString(); // error - } - void testInvalidCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - if (s != null) { - // nothing to do - } else { - s = null; + void testInvalidCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + if (s != null) { + // nothing to do + } else { + s = null; + } + // :: error: (dereference.of.nullable) + s.toString(); // error } - // :: error: (dereference.of.nullable) - s.toString(); // error - } - void testSimpleValidTernary() { - String s = "m"; - s.toString(); - } + void testSimpleValidTernary() { + String s = "m"; + s.toString(); + } - void testTernaryCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s != null) ? "m" : "n"; - s.toString(); - } + void testTernaryCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s != null) ? "m" : "n"; + s.toString(); + } - void testTernaryCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s == null) ? "m" : "n"; - s.toString(); - } + void testTernaryCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s == null) ? "m" : "n"; + s.toString(); + } - void testTernaryInvalidCase1() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s != null) ? (s = null) : "n"; - // :: error: (dereference.of.nullable) - s.toString(); // error - } + void testTernaryInvalidCase1() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s != null) ? (s = null) : "n"; + // :: error: (dereference.of.nullable) + s.toString(); // error + } - void testTernaryInvalidCase2() { - String s = "m"; - // :: warning: (nulltest.redundant) - Object m = (s != null) ? "m" : (s = null); - // :: error: (dereference.of.nullable) - s.toString(); // error - } + void testTernaryInvalidCase2() { + String s = "m"; + // :: warning: (nulltest.redundant) + Object m = (s != null) ? "m" : (s = null); + // :: error: (dereference.of.nullable) + s.toString(); // error + } - void testAssignInCond() { - String s = "m"; - if ((s = null) != "m") { - // :: error: (assignment.type.incompatible) - @NonNull String l0 = s; - } else { + void testAssignInCond() { + String s = "m"; + if ((s = null) != "m") { + // :: error: (assignment.type.incompatible) + @NonNull String l0 = s; + } else { + } + // :: error: (assignment.type.incompatible) + @NonNull String l1 = s; } - // :: error: (assignment.type.incompatible) - @NonNull String l1 = s; - } } diff --git a/checker/tests/nullness/FlowNonThis.java b/checker/tests/nullness/FlowNonThis.java index 6a13e4bdf96..22dbf3c145b 100644 --- a/checker/tests/nullness/FlowNonThis.java +++ b/checker/tests/nullness/FlowNonThis.java @@ -1,42 +1,43 @@ -import java.io.*; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import java.io.*; + public class FlowNonThis { - @Nullable String c; - - public static void main(String[] args) { - FlowNonThis t = new FlowNonThis(); - t.setup(); - System.out.println(t.c.length()); - t.erase(); - // :: error: (dereference.of.nullable) - System.out.println(t.c.length()); - } - - public void setupThenErase() { - setup(); - System.out.println(c.length()); - erase(); - // :: error: (dereference.of.nullable) - System.out.println(c.length()); - } - - public void justErase() { - // :: error: (dereference.of.nullable) - System.out.println(c.length()); - erase(); - // :: error: (dereference.of.nullable) - System.out.println(c.length()); - } - - @EnsuresNonNull("c") - public void setup() { - c = "setup"; - } - - public void erase() { - c = null; - } + @Nullable String c; + + public static void main(String[] args) { + FlowNonThis t = new FlowNonThis(); + t.setup(); + System.out.println(t.c.length()); + t.erase(); + // :: error: (dereference.of.nullable) + System.out.println(t.c.length()); + } + + public void setupThenErase() { + setup(); + System.out.println(c.length()); + erase(); + // :: error: (dereference.of.nullable) + System.out.println(c.length()); + } + + public void justErase() { + // :: error: (dereference.of.nullable) + System.out.println(c.length()); + erase(); + // :: error: (dereference.of.nullable) + System.out.println(c.length()); + } + + @EnsuresNonNull("c") + public void setup() { + c = "setup"; + } + + public void erase() { + c = null; + } } diff --git a/checker/tests/nullness/FlowNullness.java b/checker/tests/nullness/FlowNullness.java index 4d5941840b0..308e39cec1e 100644 --- a/checker/tests/nullness/FlowNullness.java +++ b/checker/tests/nullness/FlowNullness.java @@ -2,337 +2,337 @@ public class FlowNullness { - public void testIf() { - - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str != null) { - a = str; + public void testIf() { + + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str != null) { + a = str; + } + + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testIfNoBlock() { - public void testIfNoBlock() { + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str != null) { + a = str; + } - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str != null) { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testElse() { - public void testElse() { + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + } else { + a = str; + } - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - } else { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testElseNoBlock() { - public void testElseNoBlock() { + String str = "foo"; + @NonNull String a; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + } else { + a = str; + } - String str = "foo"; - @NonNull String a; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - } else { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testReturnIf() { + + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + return; + } - public void testReturnIf() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - return; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; + public void testReturnElse() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str != null) { + testAssert(); + } else { + return; + } - public void testReturnElse() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str != null) { - testAssert(); - } else { - return; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; + public void testThrowIf() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str == null) { + testAssert(); + throw new RuntimeException("foo"); + } - public void testThrowIf() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str == null) { - testAssert(); - throw new RuntimeException("foo"); + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; + public void testThrowElse() { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str != null) { + testAssert(); + } else { + throw new RuntimeException("foo"); + } - public void testThrowElse() { + @NonNull String a = str; - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str != null) { - testAssert(); - } else { - throw new RuntimeException("foo"); + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; - - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testAssert() { - public void testAssert() { + String str = "foo"; + // :: warning: (nulltest.redundant) + assert str != null; - String str = "foo"; - // :: warning: (nulltest.redundant) - assert str != null; + @NonNull String a = str; - @NonNull String a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testWhile() { - public void testWhile() { + String str = "foo"; + // :: warning: (nulltest.redundant) + while (str != null) { + @NonNull String a = str; + break; + } - String str = "foo"; - // :: warning: (nulltest.redundant) - while (str != null) { - @NonNull String a = str; - break; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testIfInstanceOf() { - public void testIfInstanceOf() { + String str = "foo"; + @NonNull String a; + if (str instanceof String) { + a = str; + } - String str = "foo"; - @NonNull String a; - if (str instanceof String) { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testNew() { - public void testNew() { + String str = "foo"; + @NonNull String a = str; - String str = "foo"; - @NonNull String a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; + String s2 = new String(); + s2.toString(); + } - String s2 = new String(); - s2.toString(); - } + public void testExit() { - public void testExit() { + String str = "foo"; + // :: warning: (nulltest.redundant) + if (str == null) { + System.exit(0); + } - String str = "foo"; - // :: warning: (nulltest.redundant) - if (str == null) { - System.exit(0); + @NonNull String a = str; } - @NonNull String a = str; - } - - void testMore() { - String str = null + " foo"; - @NonNull String a = str; - } - - void orderOfEvaluation() { - class MyClass { - @org.checkerframework.dataflow.qual.Pure - public boolean equals(@Nullable Object o) { - return o != null; - } + void testMore() { + String str = null + " foo"; + @NonNull String a = str; + } - void test(@Nullable Object a, @Nullable Object b) {} + void orderOfEvaluation() { + class MyClass { + @org.checkerframework.dataflow.qual.Pure + public boolean equals(@Nullable Object o) { + return o != null; + } + + void test(@Nullable Object a, @Nullable Object b) {} + } + MyClass m = new MyClass(); + m.equals(m = null); + + MyClass n = new MyClass(); + // :: error: (dereference.of.nullable) + n.test(n = null, n.toString()); // error + + MyClass o = null; + // :: error: (dereference.of.nullable) + o.equals(o == new MyClass()); // error } - MyClass m = new MyClass(); - m.equals(m = null); - - MyClass n = new MyClass(); - // :: error: (dereference.of.nullable) - n.test(n = null, n.toString()); // error - - MyClass o = null; - // :: error: (dereference.of.nullable) - o.equals(o == new MyClass()); // error - } - - void instanceOf(@Nullable Object o) { - if (o instanceof String) { - // cannot be null here - o.toString(); - return; + + void instanceOf(@Nullable Object o) { + if (o instanceof String) { + // cannot be null here + o.toString(); + return; + } + // :: error: (dereference.of.nullable) + o.toString(); // error } - // :: error: (dereference.of.nullable) - o.toString(); // error - } - - public static void checkConditional1(@Nullable Object a) { - if (a == null) { - } else { - a.getClass(); // not an error + + public static void checkConditional1(@Nullable Object a) { + if (a == null) { + } else { + a.getClass(); // not an error + } } - } - public static void checkConditional2(@Nullable Object a) { - if (a == null) { - } else if (a instanceof String) { - } else { - a.getClass(); // not an error + public static void checkConditional2(@Nullable Object a) { + if (a == null) { + } else if (a instanceof String) { + } else { + a.getClass(); // not an error + } } - } - public static String spf(String format, @NonNull Object[] args) { - int current_arg = 0; - Object arg = args[current_arg]; - if (false) { - return arg.toString(); // not an error + public static String spf(String format, @NonNull Object[] args) { + int current_arg = 0; + Object arg = args[current_arg]; + if (false) { + return arg.toString(); // not an error + } + if (arg instanceof long[]) { + return "foo"; + } else { + return arg.toString(); // still not an error + } } - if (arg instanceof long[]) { - return "foo"; - } else { - return arg.toString(); // still not an error + + void empty_makes_no_change() { + String o1 = "not null!"; + if (false) { + // empty branch + } else { + o1 = "still not null!"; + } + System.out.println(o1.toString()); } - } - - void empty_makes_no_change() { - String o1 = "not null!"; - if (false) { - // empty branch - } else { - o1 = "still not null!"; + + @org.checkerframework.dataflow.qual.Pure + public boolean equals(@Nullable Object o) { + if (!(o instanceof Integer)) { + return false; + } + @NonNull Object nno = o; + @NonNull Integer nni = (Integer) o; + return true; } - System.out.println(o1.toString()); - } - @org.checkerframework.dataflow.qual.Pure - public boolean equals(@Nullable Object o) { - if (!(o instanceof Integer)) { - return false; + void while_set_and_test(@Nullable String s) { + String line; + // imagine "s" is "reader.readLine()" (but avoid use of libraries in unit tests) + while ((line = s) != null) { + line.trim(); + } } - @NonNull Object nno = o; - @NonNull Integer nni = (Integer) o; - return true; - } - - void while_set_and_test(@Nullable String s) { - String line; - // imagine "s" is "reader.readLine()" (but avoid use of libraries in unit tests) - while ((line = s) != null) { - line.trim(); + + void equality_test(@Nullable String s) { + @NonNull String n = "m"; + if (s == n) { + s.toString(); + } } - } - void equality_test(@Nullable String s) { - @NonNull String n = "m"; - if (s == n) { - s.toString(); + @Nullable Object returnNullable() { + return null; } - } - @Nullable Object returnNullable() { - return null; - } + void testNullableCall() { + if (returnNullable() != null) { + // :: error: (dereference.of.nullable) + returnNullable().toString(); // error + } + } - void testNullableCall() { - if (returnNullable() != null) { - // :: error: (dereference.of.nullable) - returnNullable().toString(); // error + void nonNullArg(@NonNull Object arg) { + // empty body } - } - - void nonNullArg(@NonNull Object arg) { - // empty body - } - - void testNonNullArg(@Nullable Object arg) { - // :: error: (argument.type.incompatible) - nonNullArg(arg); // error - nonNullArg(arg); // no error - } - - void test() { - String[] s = null; - // :: error: (dereference.of.nullable) - for (int i = 0; i < s.length; ++i) { // error - String m = s[i]; // fine.. s cannot be null + + void testNonNullArg(@Nullable Object arg) { + // :: error: (argument.type.incompatible) + nonNullArg(arg); // error + nonNullArg(arg); // no error } - } - - private double @MonotonicNonNull [] - intersect; // = null; TODO: do we want to allow assignments of null to MonotonicNonNull? - - public void add_modified(double[] a, int count) { - // System.out.println ("common: " + ArraysMDE.toString (a)); - // :: warning: (nulltest.redundant) - if (a == null) { - return; - } else if (intersect == null) { - intersect = a; - return; + + void test() { + String[] s = null; + // :: error: (dereference.of.nullable) + for (int i = 0; i < s.length; ++i) { // error + String m = s[i]; // fine.. s cannot be null + } } - double[] tmp = new double[intersect.length]; - } + private double @MonotonicNonNull [] + intersect; // = null; TODO: do we want to allow assignments of null to MonotonicNonNull? + + public void add_modified(double[] a, int count) { + // System.out.println ("common: " + ArraysMDE.toString (a)); + // :: warning: (nulltest.redundant) + if (a == null) { + return; + } else if (intersect == null) { + intersect = a; + return; + } + + double[] tmp = new double[intersect.length]; + } } diff --git a/checker/tests/nullness/FlowSelf.java b/checker/tests/nullness/FlowSelf.java index 8a49f5b8508..10ba4e5d3a4 100644 --- a/checker/tests/nullness/FlowSelf.java +++ b/checker/tests/nullness/FlowSelf.java @@ -2,14 +2,14 @@ public class FlowSelf { - void test(@Nullable String s) { + void test(@Nullable String s) { - if (s == null) { - return; - } - // :: warning: (nulltest.redundant) - assert s != null; + if (s == null) { + return; + } + // :: warning: (nulltest.redundant) + assert s != null; - s = s.substring(1); - } + s = s.substring(1); + } } diff --git a/checker/tests/nullness/ForEachMin.java b/checker/tests/nullness/ForEachMin.java index de66b833426..a3a27b9842f 100644 --- a/checker/tests/nullness/ForEachMin.java +++ b/checker/tests/nullness/ForEachMin.java @@ -1,21 +1,22 @@ +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.NonNull; class MyTop { - List children = new ArrayList<>(); + List children = new ArrayList<>(); } abstract class PptRelationMin { - abstract MyTop getPpt(); + abstract MyTop getPpt(); - void init_hierarchy_new() { - MyTop ppt = getPpt(); + void init_hierarchy_new() { + MyTop ppt = getPpt(); - @NonNull Object o1 = ppt.children; + @NonNull Object o1 = ppt.children; - for (String rel : ppt.children) {} + for (String rel : ppt.children) {} - @NonNull Object o2 = ppt.children; - } + @NonNull Object o2 = ppt.children; + } } diff --git a/checker/tests/nullness/FullyQualifiedAnnotation.java b/checker/tests/nullness/FullyQualifiedAnnotation.java index fda3122451d..438a9f2d6cf 100644 --- a/checker/tests/nullness/FullyQualifiedAnnotation.java +++ b/checker/tests/nullness/FullyQualifiedAnnotation.java @@ -2,27 +2,27 @@ public class FullyQualifiedAnnotation { - void client1(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client1(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } - void client2(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client2(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } - void client3(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client3(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } - void client4(Iterator i) { - @SuppressWarnings("nullness") - @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); - handle2.toString(); - } + void client4(Iterator i) { + @SuppressWarnings("nullness") + @org.checkerframework.checker.nullness.qual.NonNull Object handle2 = i.next(); + handle2.toString(); + } } diff --git a/checker/tests/nullness/GeneralATFStore.java b/checker/tests/nullness/GeneralATFStore.java index fa09f950fe9..e282e3a8502 100644 --- a/checker/tests/nullness/GeneralATFStore.java +++ b/checker/tests/nullness/GeneralATFStore.java @@ -9,14 +9,14 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface FailAnno { - String value(); + String value(); - boolean flag() default false; + boolean flag() default false; } class Fail { - @FailAnno(value = "Fail", flag = true) - String f = "fail"; + @FailAnno(value = "Fail", flag = true) + String f = "fail"; - Object x = Fail.class; + Object x = Fail.class; } diff --git a/checker/tests/nullness/GenericCast.java b/checker/tests/nullness/GenericCast.java index 739703fe433..5ea1ce3cd49 100644 --- a/checker/tests/nullness/GenericCast.java +++ b/checker/tests/nullness/GenericCast.java @@ -1,14 +1,14 @@ // @skip-test Fails, but commented out to avoid breaking the build public class GenericCast { - @SuppressWarnings("unchecked") - T tObject = (T) new Object(); + @SuppressWarnings("unchecked") + T tObject = (T) new Object(); - T field1 = tObject; + T field1 = tObject; - T field2; + T field2; - GenericCast() { - field2 = tObject; - } + GenericCast() { + field2 = tObject; + } } diff --git a/checker/tests/nullness/GetConstantStr.java b/checker/tests/nullness/GetConstantStr.java index 59a3a5d60fa..d40a7a3be9c 100644 --- a/checker/tests/nullness/GetConstantStr.java +++ b/checker/tests/nullness/GetConstantStr.java @@ -1,6 +1,6 @@ public class GetConstantStr { - public static void get_constant_str(Object obj) { - // :: warning: (nulltest.redundant) - assert obj != null; - } + public static void get_constant_str(Object obj) { + // :: warning: (nulltest.redundant) + assert obj != null; + } } diff --git a/checker/tests/nullness/GetInterfacesPurity.java b/checker/tests/nullness/GetInterfacesPurity.java index 8d27b531c57..1241779c4b1 100644 --- a/checker/tests/nullness/GetInterfacesPurity.java +++ b/checker/tests/nullness/GetInterfacesPurity.java @@ -2,10 +2,10 @@ public class GetInterfacesPurity { - @Pure - public static boolean isSubtypeTestMethod(Class sub, Class sup) { - // :: error: (purity.not.deterministic.call) - Class[] interfaces = sub.getInterfaces(); - return interfaces.length == 0; - } + @Pure + public static boolean isSubtypeTestMethod(Class sub, Class sup) { + // :: error: (purity.not.deterministic.call) + Class[] interfaces = sub.getInterfaces(); + return interfaces.length == 0; + } } diff --git a/checker/tests/nullness/GetPackage1.java b/checker/tests/nullness/GetPackage1.java index 9ae68d34da6..4171d19cdd5 100644 --- a/checker/tests/nullness/GetPackage1.java +++ b/checker/tests/nullness/GetPackage1.java @@ -4,9 +4,9 @@ public class GetPackage { - void callGetPackage() { + void callGetPackage() { - @NonNull Package p1 = GetPackage.class.getPackage(); - @NonNull Package p2 = java.util.List.class.getPackage(); - } + @NonNull Package p1 = GetPackage.class.getPackage(); + @NonNull Package p2 = java.util.List.class.getPackage(); + } } diff --git a/checker/tests/nullness/GetProperty.java b/checker/tests/nullness/GetProperty.java index 02ad7e94ab5..9c27c55e40f 100644 --- a/checker/tests/nullness/GetProperty.java +++ b/checker/tests/nullness/GetProperty.java @@ -1,25 +1,26 @@ -import java.util.Properties; import org.checkerframework.checker.nullness.qual.*; +import java.util.Properties; + public class GetProperty { - @NonNull Object nno = new Object(); + @NonNull Object nno = new Object(); - void m(Properties p) { + void m(Properties p) { - String s = "line.separator"; + String s = "line.separator"; - nno = System.getProperty("line.separator"); - // :: error: (assignment.type.incompatible) - nno = System.getProperty(s); - // :: error: (assignment.type.incompatible) - nno = System.getProperty("not.a.builtin.property"); + nno = System.getProperty("line.separator"); + // :: error: (assignment.type.incompatible) + nno = System.getProperty(s); + // :: error: (assignment.type.incompatible) + nno = System.getProperty("not.a.builtin.property"); - // :: error: (assignment.type.incompatible) - nno = p.getProperty("line.separator"); - // :: error: (assignment.type.incompatible) - nno = p.getProperty(s); - // :: error: (assignment.type.incompatible) - nno = p.getProperty("not.a.builtin.property"); - } + // :: error: (assignment.type.incompatible) + nno = p.getProperty("line.separator"); + // :: error: (assignment.type.incompatible) + nno = p.getProperty(s); + // :: error: (assignment.type.incompatible) + nno = p.getProperty("not.a.builtin.property"); + } } diff --git a/checker/tests/nullness/GetRefArg.java b/checker/tests/nullness/GetRefArg.java index 7df75fae6a1..2f398aca44a 100644 --- a/checker/tests/nullness/GetRefArg.java +++ b/checker/tests/nullness/GetRefArg.java @@ -1,10 +1,11 @@ -import java.lang.reflect.*; import org.checkerframework.checker.nullness.qual.*; +import java.lang.reflect.*; + public class GetRefArg { - private void get_ref_arg(Constructor constructor) throws Exception { - Object val = constructor.newInstance(); - // :: warning: (nulltest.redundant) - assert val != null; - } + private void get_ref_arg(Constructor constructor) throws Exception { + Object val = constructor.newInstance(); + // :: warning: (nulltest.redundant) + assert val != null; + } } diff --git a/checker/tests/nullness/HasInnerClass.java b/checker/tests/nullness/HasInnerClass.java index 298ef0faaef..0a93c3eaa17 100644 --- a/checker/tests/nullness/HasInnerClass.java +++ b/checker/tests/nullness/HasInnerClass.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.*; public class HasInnerClass { - public class InternalEdge { - public void m() { - HasInnerClass.InternalEdge other = null; + public class InternalEdge { + public void m() { + HasInnerClass.InternalEdge other = null; + } } - } } diff --git a/checker/tests/nullness/HierarchicalInit.java b/checker/tests/nullness/HierarchicalInit.java index 6987dd2f59b..eb85da39275 100644 --- a/checker/tests/nullness/HierarchicalInit.java +++ b/checker/tests/nullness/HierarchicalInit.java @@ -2,18 +2,18 @@ public class HierarchicalInit { - String a; + String a; - public HierarchicalInit() { - a = ""; - } + public HierarchicalInit() { + a = ""; + } - public static class B extends HierarchicalInit { - String b; + public static class B extends HierarchicalInit { + String b; - public B() { - super(); - b = ""; + public B() { + super(); + b = ""; + } } - } } diff --git a/checker/tests/nullness/ImplementInterface.java b/checker/tests/nullness/ImplementInterface.java index 45540596798..610c165307f 100644 --- a/checker/tests/nullness/ImplementInterface.java +++ b/checker/tests/nullness/ImplementInterface.java @@ -1,12 +1,12 @@ import org.checkerframework.checker.nullness.qual.*; interface TestInterface { - public char @Nullable [] getChars(); + public char @Nullable [] getChars(); } public class ImplementInterface implements TestInterface { - @Override - public char @Nullable [] getChars() { - return null; - } + @Override + public char @Nullable [] getChars() { + return null; + } } diff --git a/checker/tests/nullness/Imports1.java b/checker/tests/nullness/Imports1.java index d218d71ca48..7017b330fe5 100644 --- a/checker/tests/nullness/Imports1.java +++ b/checker/tests/nullness/Imports1.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.nullness.qual.*; public class Imports1 { - void call() { - java.util.Arrays.asList("m", 1); - } + void call() { + java.util.Arrays.asList("m", 1); + } } diff --git a/checker/tests/nullness/Imports2.java b/checker/tests/nullness/Imports2.java index 806ad1622c1..655b5225514 100644 --- a/checker/tests/nullness/Imports2.java +++ b/checker/tests/nullness/Imports2.java @@ -1,5 +1,5 @@ public class Imports2 { - void call() { - java.util.Arrays.asList("m", 1); - } + void call() { + java.util.Arrays.asList("m", 1); + } } diff --git a/checker/tests/nullness/InferListParam.java b/checker/tests/nullness/InferListParam.java index c1d8c405f34..981823e98d8 100644 --- a/checker/tests/nullness/InferListParam.java +++ b/checker/tests/nullness/InferListParam.java @@ -2,9 +2,9 @@ import java.util.List; public class InferListParam { - List fieldValues; + List fieldValues; - InferListParam() { - fieldValues = Collections.emptyList(); - } + InferListParam() { + fieldValues = Collections.emptyList(); + } } diff --git a/checker/tests/nullness/InferNullType.java b/checker/tests/nullness/InferNullType.java index 1e0a4c91ed7..90015f0343e 100644 --- a/checker/tests/nullness/InferNullType.java +++ b/checker/tests/nullness/InferNullType.java @@ -1,36 +1,36 @@ // Version of framework/tests/all-systems/InferNullType.java with expected Nullness Checker warnings public class InferNullType { - T toInfer(T input) { - return input; - } + T toInfer(T input) { + return input; + } - T toInfer2(T input) { - return input; - } + T toInfer2(T input) { + return input; + } - T toInfer3(T input, S p2) { - return input; - } + T toInfer3(T input, S p2) { + return input; + } - T toInfer4(T input, S p2) { - return input; - } + T toInfer4(T input, S p2) { + return input; + } - void x() { - // :: error: (type.arguments.not.inferred) - Object m = toInfer(null); - Object m2 = toInfer2(null); + void x() { + // :: error: (type.arguments.not.inferred) + Object m = toInfer(null); + Object m2 = toInfer2(null); - Object m3 = toInfer3(null, null); - Object m4 = toInfer3(1, null); - Object m5 = toInfer3(null, 1); + Object m3 = toInfer3(null, null); + Object m4 = toInfer3(1, null); + Object m5 = toInfer3(null, 1); - // :: error: (type.arguments.not.inferred) - Object m6 = toInfer4(null, null); - // :: error: (type.arguments.not.inferred) - Object m7 = toInfer4(1, null); - // :: error: (type.arguments.not.inferred) - Object m8 = toInfer4(null, 1); - } + // :: error: (type.arguments.not.inferred) + Object m6 = toInfer4(null, null); + // :: error: (type.arguments.not.inferred) + Object m7 = toInfer4(1, null); + // :: error: (type.arguments.not.inferred) + Object m8 = toInfer4(null, 1); + } } diff --git a/checker/tests/nullness/InferTypeArgsConditionalExpression.java b/checker/tests/nullness/InferTypeArgsConditionalExpression.java index cac9a5784a2..ca8d6c45be3 100644 --- a/checker/tests/nullness/InferTypeArgsConditionalExpression.java +++ b/checker/tests/nullness/InferTypeArgsConditionalExpression.java @@ -8,12 +8,12 @@ public class InferTypeArgsConditionalExpression { - public void foo(Generic real, Generic other, boolean flag) { - // :: error: (type.arguments.not.inferred) - bar(flag ? real : other); - } + public void foo(Generic real, Generic other, boolean flag) { + // :: error: (type.arguments.not.inferred) + bar(flag ? real : other); + } - <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} + <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} - interface Generic {} + interface Generic {} } diff --git a/checker/tests/nullness/InfiniteLoopIsSameType.java b/checker/tests/nullness/InfiniteLoopIsSameType.java index b37cbe188ab..4cd8017eebf 100644 --- a/checker/tests/nullness/InfiniteLoopIsSameType.java +++ b/checker/tests/nullness/InfiniteLoopIsSameType.java @@ -7,22 +7,22 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public class InfiniteLoopIsSameType { - private interface Intf1 { - R apply(); - } + private interface Intf1 { + R apply(); + } - public void compute( - // checker works fine if not annotated - Intf1 remappingFunction) { - // must assign null - Object nullval = null; - for (; ; ) { - // must assign to the null object - nullval = remappingFunction.apply(); - // break must be in an if statement - if (true) { - break; - } + public void compute( + // checker works fine if not annotated + Intf1 remappingFunction) { + // must assign null + Object nullval = null; + for (; ; ) { + // must assign to the null object + nullval = remappingFunction.apply(); + // break must be in an if statement + if (true) { + break; + } + } } - } } diff --git a/checker/tests/nullness/InitSuppressWarnings.java b/checker/tests/nullness/InitSuppressWarnings.java index ecc059a680a..5c28f43b770 100644 --- a/checker/tests/nullness/InitSuppressWarnings.java +++ b/checker/tests/nullness/InitSuppressWarnings.java @@ -3,8 +3,8 @@ public class InitSuppressWarnings { - private void init_vars(@UnderInitialization(Object.class) InitSuppressWarnings this) { - @SuppressWarnings({"initialization"}) - @Initialized InitSuppressWarnings initializedThis = this; - } + private void init_vars(@UnderInitialization(Object.class) InitSuppressWarnings this) { + @SuppressWarnings({"initialization"}) + @Initialized InitSuppressWarnings initializedThis = this; + } } diff --git a/checker/tests/nullness/InitThrows.java b/checker/tests/nullness/InitThrows.java index c496279a492..ae0fc36cf9d 100644 --- a/checker/tests/nullness/InitThrows.java +++ b/checker/tests/nullness/InitThrows.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.nullness.qual.*; public class InitThrows { - private final Object o; + private final Object o; - { - try { - o = new Object(); - } catch (Exception e) { - throw new RuntimeException(e); + { + try { + o = new Object(); + } catch (Exception e) { + throw new RuntimeException(e); + } } - } } diff --git a/checker/tests/nullness/InitializationAssertionFailure.java b/checker/tests/nullness/InitializationAssertionFailure.java index a083b04cb45..3c81951d5a5 100644 --- a/checker/tests/nullness/InitializationAssertionFailure.java +++ b/checker/tests/nullness/InitializationAssertionFailure.java @@ -5,7 +5,7 @@ public class InitializationAssertionFailure implements Serializable { - static final long serialVersionUID = 20030819L; + static final long serialVersionUID = 20030819L; - private InitializationAssertionFailure() {} + private InitializationAssertionFailure() {} } diff --git a/checker/tests/nullness/InitializedField.java b/checker/tests/nullness/InitializedField.java index 4fd0e2b2f90..86976a6274b 100644 --- a/checker/tests/nullness/InitializedField.java +++ b/checker/tests/nullness/InitializedField.java @@ -1,22 +1,23 @@ -import java.util.Stack; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.dataflow.qual.*; +import java.util.Stack; + public final class InitializedField { - private Stack stack; + private Stack stack; - InitializedField() { - stack = new Stack(); - iPeek(); - } + InitializedField() { + stack = new Stack(); + iPeek(); + } - @RequiresNonNull("stack") - public Object iPeek(@UnknownInitialization InitializedField this) { - return stack.peek(); - } + @RequiresNonNull("stack") + public Object iPeek(@UnknownInitialization InitializedField this) { + return stack.peek(); + } - public static void testJavaClass(InitializedField initField) { - initField.iPeek(); - } + public static void testJavaClass(InitializedField initField) { + initField.iPeek(); + } } diff --git a/checker/tests/nullness/InnerCrash.java b/checker/tests/nullness/InnerCrash.java index 3169bc0e101..3aafe26b9d0 100644 --- a/checker/tests/nullness/InnerCrash.java +++ b/checker/tests/nullness/InnerCrash.java @@ -1,14 +1,14 @@ import org.checkerframework.checker.nullness.qual.Nullable; class InnerCrash { - class Inner {} + class Inner {} - static @Nullable InnerCrash getInnerCrash() { - return null; - } + static @Nullable InnerCrash getInnerCrash() { + return null; + } - static void foo() { - // :: error: (dereference.of.nullable) - Object o = getInnerCrash().new Inner(); - } + static void foo() { + // :: error: (dereference.of.nullable) + Object o = getInnerCrash().new Inner(); + } } diff --git a/checker/tests/nullness/InvariantTypes.java b/checker/tests/nullness/InvariantTypes.java index 32f751c6907..2ea035b4cd2 100644 --- a/checker/tests/nullness/InvariantTypes.java +++ b/checker/tests/nullness/InvariantTypes.java @@ -1,30 +1,30 @@ import org.checkerframework.checker.nullness.qual.*; public class InvariantTypes { - // The RHS is @NonNull [], but context decides to make it @Nullable - @Nullable Object[] noa = {"non-null!"}; + // The RHS is @NonNull [], but context decides to make it @Nullable + @Nullable Object[] noa = {"non-null!"}; - // Type for array creation is propagated from LHS - @MonotonicNonNull Object[] f = new Object[5]; + // Type for array creation is propagated from LHS + @MonotonicNonNull Object[] f = new Object[5]; - void testAsLocal() { - @MonotonicNonNull Object[] lo; - lo = new Object[5]; - // :: error: (assignment.type.incompatible) - lo[0] = null; - lo[0] = new Object(); - // :: error: (dereference.of.nullable) - lo[1].toString(); - } + void testAsLocal() { + @MonotonicNonNull Object[] lo; + lo = new Object[5]; + // :: error: (assignment.type.incompatible) + lo[0] = null; + lo[0] = new Object(); + // :: error: (dereference.of.nullable) + lo[1].toString(); + } - // Type for array creation is propagated from LHS - @SuppressWarnings("invalid.polymorphic.qualifier.use") - @PolyNull Object[] po = new Object[5]; + // Type for array creation is propagated from LHS + @SuppressWarnings("invalid.polymorphic.qualifier.use") + @PolyNull Object[] po = new Object[5]; - void testDecl(@MonotonicNonNull Object[] p) {} + void testDecl(@MonotonicNonNull Object[] p) {} - void testCall() { - // Type for array creation is propaged from parameter type - testDecl(new Object[5]); - } + void testCall() { + // Type for array creation is propaged from parameter type + testDecl(new Object[5]); + } } diff --git a/checker/tests/nullness/IsEmptyPoll.java b/checker/tests/nullness/IsEmptyPoll.java index 0cd3be1e053..30f618aca32 100644 --- a/checker/tests/nullness/IsEmptyPoll.java +++ b/checker/tests/nullness/IsEmptyPoll.java @@ -3,28 +3,29 @@ // @skip-test until the issue is fixed -import java.util.ArrayList; -import java.util.Queue; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.ArrayList; +import java.util.Queue; + public final class IsEmptyPoll extends ArrayList { - void mNonNull(Queue q) { - while (!q.isEmpty()) { - @NonNull String firstNode = q.poll(); + void mNonNull(Queue q) { + while (!q.isEmpty()) { + @NonNull String firstNode = q.poll(); + } } - } - void mNullable(Queue<@Nullable String> q) { - while (!q.isEmpty()) { - // :: error: (assignment.type.incompatible) - @NonNull String firstNode = q.poll(); + void mNullable(Queue<@Nullable String> q) { + while (!q.isEmpty()) { + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q.poll(); + } } - } - void mNoCheck(Queue<@Nullable String> q) { - // :: error: (assignment.type.incompatible) - @NonNull String firstNode = q.poll(); - } + void mNoCheck(Queue<@Nullable String> q) { + // :: error: (assignment.type.incompatible) + @NonNull String firstNode = q.poll(); + } } diff --git a/checker/tests/nullness/Issue1027.java b/checker/tests/nullness/Issue1027.java index 26c90d90660..b593b288a4e 100644 --- a/checker/tests/nullness/Issue1027.java +++ b/checker/tests/nullness/Issue1027.java @@ -4,41 +4,42 @@ // Use -J-XX:MaxJavaStackTraceDepth=1000000 as parameter // to javac to see a longer stacktrace. +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.KeyFor; public class Issue1027 { - // Stand-alone reproduction + // Stand-alone reproduction - class Repr { - void bar(Function p) {} - } + class Repr { + void bar(Function p) {} + } - @SuppressWarnings({"nullness", "keyfor"}) - Repr<@KeyFor("this") String> foo() { - return null; - } + @SuppressWarnings({"nullness", "keyfor"}) + Repr<@KeyFor("this") String> foo() { + return null; + } - void zoo(Issue1027 p) { - p.foo().bar(x -> ""); - } + void zoo(Issue1027 p) { + p.foo().bar(x -> ""); + } - // Various longer versions that also used to give SOE + // Various longer versions that also used to give SOE - void foo(Map arg) { - arg.keySet().stream().map(key -> key); - } + void foo(Map arg) { + arg.keySet().stream().map(key -> key); + } - Stream foo(Set arg) { - return arg.stream().map(key -> key); - } + Stream foo(Set arg) { + return arg.stream().map(key -> key); + } - String foo(Stream stream) { - return stream.map(key -> key).collect(Collectors.joining()); - } + String foo(Stream stream) { + return stream.map(key -> key).collect(Collectors.joining()); + } } diff --git a/checker/tests/nullness/Issue1046Java7.java b/checker/tests/nullness/Issue1046Java7.java index b08381fbb41..a94041af67e 100644 --- a/checker/tests/nullness/Issue1046Java7.java +++ b/checker/tests/nullness/Issue1046Java7.java @@ -6,29 +6,29 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1046Java7 { - interface MyInterface {} + interface MyInterface {} - class MyClass implements MyInterface {} + class MyClass implements MyInterface {} - class Function {} + class Function {} - abstract static class NotSubtype2 { - static void transform(Function q) {} + abstract static class NotSubtype2 { + static void transform(Function q) {} - static void transform2(Function q) {} + static void transform2(Function q) {} - void test1(Function p, Function p2) { - transform(p); - // :: error: (argument.type.incompatible) - transform2(p); - transform(p2); - transform2(p2); - } + void test1(Function p, Function p2) { + transform(p); + // :: error: (argument.type.incompatible) + transform2(p); + transform(p2); + transform2(p2); + } - @Nullable Function NULL = null; + @Nullable Function NULL = null; - void test2(@Nullable Function queue) { - Function x = (queue == null) ? NULL : queue; + void test2(@Nullable Function queue) { + Function x = (queue == null) ? NULL : queue; + } } - } } diff --git a/checker/tests/nullness/Issue1059.java b/checker/tests/nullness/Issue1059.java index 1c2e7f49d6e..df652f8720e 100644 --- a/checker/tests/nullness/Issue1059.java +++ b/checker/tests/nullness/Issue1059.java @@ -5,18 +5,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1059 { - @Nullable Object f; + @Nullable Object f; - @EnsuresNonNull({"f"}) - void foo() { - f = new Object(); - } + @EnsuresNonNull({"f"}) + void foo() { + f = new Object(); + } - void bar() { - switch (this.hashCode()) { - case 1: - foo(); - Object dada = f.toString(); + void bar() { + switch (this.hashCode()) { + case 1: + foo(); + Object dada = f.toString(); + } } - } } diff --git a/checker/tests/nullness/Issue1102.java b/checker/tests/nullness/Issue1102.java index 3270b9efb06..799b34a1c73 100644 --- a/checker/tests/nullness/Issue1102.java +++ b/checker/tests/nullness/Issue1102.java @@ -9,20 +9,21 @@ interface Issue1102Itf {} class Issue1102Base {} class Issue1102Decl extends Issue1102Base { - static Issue1102Decl newInstance(T s) { - return new Issue1102Decl(); - } + static Issue1102Decl newInstance( + T s) { + return new Issue1102Decl(); + } } class Issue1102Use { - @SuppressWarnings("initialization.field.uninitialized") - U f; + @SuppressWarnings("initialization.field.uninitialized") + U f; - @Nullable U g = null; + @Nullable U g = null; - void bar() { - Issue1102Decl d = Issue1102Decl.newInstance(f); - // :: error: (type.arguments.not.inferred) - d = Issue1102Decl.newInstance(g); - } + void bar() { + Issue1102Decl d = Issue1102Decl.newInstance(f); + // :: error: (type.arguments.not.inferred) + d = Issue1102Decl.newInstance(g); + } } diff --git a/checker/tests/nullness/Issue1147.java b/checker/tests/nullness/Issue1147.java index 49921678397..65ae001664a 100644 --- a/checker/tests/nullness/Issue1147.java +++ b/checker/tests/nullness/Issue1147.java @@ -4,14 +4,14 @@ public class Issue1147 { - public static void main(String[] args) { + public static void main(String[] args) { - StringJoiner sj = new StringJoiner(","); + StringJoiner sj = new StringJoiner(","); - sj.add("a"); + sj.add("a"); - sj.add(null); + sj.add(null); - System.out.println(sj); - } + System.out.println(sj); + } } diff --git a/checker/tests/nullness/Issue1307.java b/checker/tests/nullness/Issue1307.java index 55c26de5e98..a5978de12d8 100644 --- a/checker/tests/nullness/Issue1307.java +++ b/checker/tests/nullness/Issue1307.java @@ -7,9 +7,9 @@ @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.FIELD) @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.PARAMETER) public class Issue1307 { - Object nullableField = null; + Object nullableField = null; - void perl(Integer a) { - a = null; - } + void perl(Integer a) { + a = null; + } } diff --git a/checker/tests/nullness/Issue1406.java b/checker/tests/nullness/Issue1406.java index c33418b004c..1f33f7bd2fc 100644 --- a/checker/tests/nullness/Issue1406.java +++ b/checker/tests/nullness/Issue1406.java @@ -1,37 +1,38 @@ // Test case for Issue 1406 // https://github.com/typetools/checker-framework/issues/1406 -import java.util.ArrayList; -import java.util.List; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.dataflow.qual.Pure; +import java.util.ArrayList; +import java.util.List; + @SuppressWarnings({"purity", "contracts.postcondition.not.satisfied"}) // Only test parsing public class Issue1406 { - public static void main(String[] args) {} - - @Pure - @EnsuresNonNull("myMethod(#1).get(0)") - List myMethod(int arg) { - List result = new ArrayList<>(); - result.add("non-null value"); - return result; - } - - String client(int arg) { - return myMethod(arg).get(0); - } - - @Pure - @EnsuresNonNull("myMethod2().get(0)") - List myMethod2() { - List result = new ArrayList<>(); - result.add("non-null value"); - return result; - } - - String client2() { - return myMethod2().get(0); - } + public static void main(String[] args) {} + + @Pure + @EnsuresNonNull("myMethod(#1).get(0)") + List myMethod(int arg) { + List result = new ArrayList<>(); + result.add("non-null value"); + return result; + } + + String client(int arg) { + return myMethod(arg).get(0); + } + + @Pure + @EnsuresNonNull("myMethod2().get(0)") + List myMethod2() { + List result = new ArrayList<>(); + result.add("non-null value"); + return result; + } + + String client2() { + return myMethod2().get(0); + } } diff --git a/checker/tests/nullness/Issue1522.java b/checker/tests/nullness/Issue1522.java index 67cb8e22401..032e1dee193 100644 --- a/checker/tests/nullness/Issue1522.java +++ b/checker/tests/nullness/Issue1522.java @@ -1,52 +1,53 @@ // Test case for Issue 1522 // https://github.com/typetools/checker-framework/issues/1522 -import java.util.Vector; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Vector; + public class Issue1522 { - void copyInto(String p) {} - - void bar() { - copyInto("Hi"); - } - - void copyVector(Vector v, Integer[] intArray, String[] stringArray) { - // Java types aren't compatible - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(intArray); - v.copyInto(stringArray); - } - - void copyStack(SubClassVector v, Integer[] intArray, String[] stringArray) { - // Java types aren't compatible - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(intArray); - v.copyInto(stringArray); - } - - void copyVectorErrors(Vector<@Nullable String> v, String[] stringArray) { - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(stringArray); - } - - void copyStackErrors(SubClassVector<@Nullable String> v, String[] stringArray) { - // :: error: (vector.copyinto.type.incompatible) - v.copyInto(stringArray); - } - - void copyVectorNullable(Vector<@Nullable String> v, @Nullable String[] stringArray) { - v.copyInto(stringArray); - } - - void copyStackNullable(SubClassVector<@Nullable String> v, @Nullable String[] stringArray) { - v.copyInto(stringArray); - } - - static class SubClassVector extends Vector { - @Override - public synchronized void copyInto(@Nullable Object[] anArray) { - super.copyInto(anArray); - } - } + void copyInto(String p) {} + + void bar() { + copyInto("Hi"); + } + + void copyVector(Vector v, Integer[] intArray, String[] stringArray) { + // Java types aren't compatible + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(intArray); + v.copyInto(stringArray); + } + + void copyStack(SubClassVector v, Integer[] intArray, String[] stringArray) { + // Java types aren't compatible + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(intArray); + v.copyInto(stringArray); + } + + void copyVectorErrors(Vector<@Nullable String> v, String[] stringArray) { + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(stringArray); + } + + void copyStackErrors(SubClassVector<@Nullable String> v, String[] stringArray) { + // :: error: (vector.copyinto.type.incompatible) + v.copyInto(stringArray); + } + + void copyVectorNullable(Vector<@Nullable String> v, @Nullable String[] stringArray) { + v.copyInto(stringArray); + } + + void copyStackNullable(SubClassVector<@Nullable String> v, @Nullable String[] stringArray) { + v.copyInto(stringArray); + } + + static class SubClassVector extends Vector { + @Override + public synchronized void copyInto(@Nullable Object[] anArray) { + super.copyInto(anArray); + } + } } diff --git a/checker/tests/nullness/Issue1555.java b/checker/tests/nullness/Issue1555.java index 7c355c0ea77..451989b72ab 100644 --- a/checker/tests/nullness/Issue1555.java +++ b/checker/tests/nullness/Issue1555.java @@ -6,9 +6,9 @@ public class Issue1555 { - private @MonotonicNonNull String x; + private @MonotonicNonNull String x; - String test() { - return NullnessUtil.castNonNull(x); - } + String test() { + return NullnessUtil.castNonNull(x); + } } diff --git a/checker/tests/nullness/Issue160.java b/checker/tests/nullness/Issue160.java index 9832262c291..69a3727a83c 100644 --- a/checker/tests/nullness/Issue160.java +++ b/checker/tests/nullness/Issue160.java @@ -1,58 +1,58 @@ public class Issue160 { - public static void t1() { - String s = null; - if (s != null) { - } else { - return; + public static void t1() { + String s = null; + if (s != null) { + } else { + return; + } + System.out.println(s.toString()); } - System.out.println(s.toString()); - } - public static void t2() { - String s = null; - if (s != null) { - } else { - throw new RuntimeException(); + public static void t2() { + String s = null; + if (s != null) { + } else { + throw new RuntimeException(); + } + System.out.println(s.toString()); } - System.out.println(s.toString()); - } - public static void t3() { - String s = null; - if (s != null) { - } else { - System.exit(0); + public static void t3() { + String s = null; + if (s != null) { + } else { + System.exit(0); + } + System.out.println(s.toString()); } - System.out.println(s.toString()); - } - public static void t1b() { - String s = null; - if (s == null) { - } else { - return; + public static void t1b() { + String s = null; + if (s == null) { + } else { + return; + } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); - } - public static void t2b() { - String s = null; - if (s == null) { - } else { - throw new RuntimeException(); + public static void t2b() { + String s = null; + if (s == null) { + } else { + throw new RuntimeException(); + } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); - } - public static void t3b() { - String s = null; - if (s == null) { - } else { - System.exit(0); + public static void t3b() { + String s = null; + if (s == null) { + } else { + System.exit(0); + } + // :: error: (dereference.of.nullable) + System.out.println(s.toString()); } - // :: error: (dereference.of.nullable) - System.out.println(s.toString()); - } } diff --git a/checker/tests/nullness/Issue1628.java b/checker/tests/nullness/Issue1628.java index 5dc324cb437..363571381cc 100644 --- a/checker/tests/nullness/Issue1628.java +++ b/checker/tests/nullness/Issue1628.java @@ -6,16 +6,16 @@ public class Issue1628> implements Issue1628R { - public boolean isEmpty() { - return false; - } + public boolean isEmpty() { + return false; + } - public boolean equals(@Nullable Object o) { - return (o instanceof Issue1628R) && ((Issue1628R) o).isEmpty(); - } + public boolean equals(@Nullable Object o) { + return (o instanceof Issue1628R) && ((Issue1628R) o).isEmpty(); + } } interface Issue1628R> { - @Pure - boolean isEmpty(); + @Pure + boolean isEmpty(); } diff --git a/checker/tests/nullness/Issue1712.java b/checker/tests/nullness/Issue1712.java index e5cddb429f8..f3275df48d9 100644 --- a/checker/tests/nullness/Issue1712.java +++ b/checker/tests/nullness/Issue1712.java @@ -3,24 +3,24 @@ abstract class Issue1712 { - abstract T match( - Function visitA, Function visitB); + abstract T match( + Function visitA, Function visitB); - class SubclassA extends Issue1712 { - @Override - T match(Function visitA, Function visitB) { - return visitA.apply(this); // line 11 + class SubclassA extends Issue1712 { + @Override + T match(Function visitA, Function visitB) { + return visitA.apply(this); // line 11 + } } - } - class SubclassB extends Issue1712 { - @Override - T match(Function visitA, Function visitB) { - return visitB.apply(this); // line 18 + class SubclassB extends Issue1712 { + @Override + T match(Function visitA, Function visitB) { + return visitB.apply(this); // line 18 + } } - } - abstract class Function { - abstract T2 apply(T1 arg); - } + abstract class Function { + abstract T2 apply(T1 arg); + } } diff --git a/checker/tests/nullness/Issue1797.java b/checker/tests/nullness/Issue1797.java index 92e31791487..f1bcf42dfe8 100644 --- a/checker/tests/nullness/Issue1797.java +++ b/checker/tests/nullness/Issue1797.java @@ -4,255 +4,255 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1797 { - void fooReturn(@Nullable Object o) { - try { - return; - } finally { - if (o != null) { - o.toString(); - } + void fooReturn(@Nullable Object o) { + try { + return; + } finally { + if (o != null) { + o.toString(); + } + } } - } - void fooWhileReturn(@Nullable Object o) { - while (this.hashCode() < 5) { - try { - return; - } finally { - if (o != null) { - o.toString(); + void fooWhileReturn(@Nullable Object o) { + while (this.hashCode() < 5) { + try { + return; + } finally { + if (o != null) { + o.toString(); + } + } } - } } - } - void fooReturnNested(@Nullable Object o) { - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { - try { - return; - } finally { - if (o != null) { - o.toString(); - } - // go to exit, not either loop + void fooReturnNested(@Nullable Object o) { + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { + try { + return; + } finally { + if (o != null) { + o.toString(); + } + // go to exit, not either loop + } + } } - } } - } - void fooBreak(@Nullable Object o) { - while (this.hashCode() < 5) { - try { - break; - } finally { - if (o != null) { - o.toString(); + void fooBreak(@Nullable Object o) { + while (this.hashCode() < 5) { + try { + break; + } finally { + if (o != null) { + o.toString(); + } + } } - } } - } - void fooBreakLabel(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { - try { - break outer; - } finally { - if (o != null) { - o.toString(); - } - // continue after outer + void fooBreakLabel(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { + try { + break outer; + } finally { + if (o != null) { + o.toString(); + } + // continue after outer + } + } } - } } - } - void fooBreakLabel2(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - try { - inner: - while (this.hashCode() < 5) { - if (this.hashCode() < 2) { - break outer; - // go to finally - } else { - break inner; - // do not go to finally - } + void fooBreakLabel2(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + try { + inner: + while (this.hashCode() < 5) { + if (this.hashCode() < 2) { + break outer; + // go to finally + } else { + break inner; + // do not go to finally + } + } + } finally { + if (o != null) { + o.toString(); + } + // continue either at outer or after outer + } } - } finally { - if (o != null) { - o.toString(); + } + + void fooBreakNoLabel(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + inner: + while (this.hashCode() < 5) { + try { + break; + } finally { + if (o != null) { + o.toString(); + } + // continue at outer + } + } } - // continue either at outer or after outer - } } - } - void fooBreakNoLabel(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - inner: - while (this.hashCode() < 5) { - try { - break; - } finally { - if (o != null) { - o.toString(); - } - // continue at outer + void fooContinue(@Nullable Object o) { + while (this.hashCode() < 5) { + try { + continue; + } finally { + if (o != null) { + o.toString(); + } + } + } + } + + void fooContinueLabel(@Nullable Object o) { + outer: + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { + try { + continue outer; + } finally { + if (o != null) { + o.toString(); + } + } + } } - } } - } - void fooContinue(@Nullable Object o) { - while (this.hashCode() < 5) { - try { - continue; - } finally { - if (o != null) { - o.toString(); + void fooSwitch(@Nullable Object o) { + switch (this.hashCode()) { + case 1: + try { + break; + } finally { + if (o != null) { + o.toString(); + } + } + default: } - } } - } - void fooContinueLabel(@Nullable Object o) { - outer: - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { + // A few tests to make sure also return with expression works. + + int barReturn(@Nullable Object o) { try { - continue outer; + return 5; } finally { - if (o != null) { - o.toString(); - } + if (o != null) { + o.toString(); + } } - } } - } - void fooSwitch(@Nullable Object o) { - switch (this.hashCode()) { - case 1: + int barReturnInFinally(@Nullable Object o) { try { - break; + return 5; } finally { - if (o != null) { - o.toString(); - } + if (o != null) { + o.toString(); + } + return 10; } - default: } - } - - // A few tests to make sure also return with expression works. - int barReturn(@Nullable Object o) { - try { - return 5; - } finally { - if (o != null) { - o.toString(); - } + int barReturnNested(@Nullable Object o) { + while (this.hashCode() < 10) { + while (this.hashCode() < 5) { + try { + return 5; + } finally { + if (o != null) { + o.toString(); + } + // goes to return 5, not either loop! + } + } + } + return 10; } - } - int barReturnInFinally(@Nullable Object o) { - try { - return 5; - } finally { - if (o != null) { - o.toString(); - } - return 10; + @FunctionalInterface + interface NullableParamFunction { + String takeVal(@Nullable Object x); } - } - int barReturnNested(@Nullable Object o) { - while (this.hashCode() < 10) { - while (this.hashCode() < 5) { + void testLambda() { + NullableParamFunction n1 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); try { - return 5; + NullableParamFunction n2 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); } finally { - if (o != null) { - o.toString(); - } - // goes to return 5, not either loop! + NullableParamFunction n3 = (x) -> (x == null) ? "null" : x.toString(); } - } } - return 10; - } - @FunctionalInterface - interface NullableParamFunction { - String takeVal(@Nullable Object x); - } - - void testLambda() { - NullableParamFunction n1 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); - try { - NullableParamFunction n2 = (@Nullable Object x) -> (x == null) ? "null" : x.toString(); - } finally { - NullableParamFunction n3 = (x) -> (x == null) ? "null" : x.toString(); - } - } - - boolean nestedCFGConstructionTest(@Nullable Object o) { - boolean result = true; - java.io.BufferedWriter out = null; - try { - try { - } finally { - out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); - } - if (o != null) { - o.toString(); - out.write(' '); - } - } catch (Exception e) { - } finally { + boolean nestedCFGConstructionTest(@Nullable Object o) { + boolean result = true; + java.io.BufferedWriter out = null; + try { + try { + } finally { + out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); + } + if (o != null) { + o.toString(); + out.write(' '); + } + } catch (Exception e) { + } finally { + } + return result; } - return result; - } - void nestedTryFinally() { - try { - try { - } finally { - } - } finally { + void nestedTryFinally() { + try { + try { + } finally { + } + } finally { + } } - } - boolean nestedCFGConstructionTest2() throws java.io.IOException { - java.io.BufferedWriter out = - new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); - try { - try { - return true; - } finally { - } - } finally { - out.write(' '); - out.close(); + boolean nestedCFGConstructionTest2() throws java.io.IOException { + java.io.BufferedWriter out = + new java.io.BufferedWriter(new java.io.OutputStreamWriter(System.err)); + try { + try { + return true; + } finally { + } + } finally { + out.write(' '); + out.close(); + } } - } - void nestedTryFinally2(java.io.BufferedWriter out) throws java.io.IOException { - try { - try { - return; - } finally { - } - } finally { - out.write(' '); - out.close(); + void nestedTryFinally2(java.io.BufferedWriter out) throws java.io.IOException { + try { + try { + return; + } finally { + } + } finally { + out.write(' '); + out.close(); + } } - } } diff --git a/checker/tests/nullness/Issue1847.java b/checker/tests/nullness/Issue1847.java index 50e2b5acc33..f4ff9f85520 100644 --- a/checker/tests/nullness/Issue1847.java +++ b/checker/tests/nullness/Issue1847.java @@ -1,30 +1,32 @@ +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.function.Function; -import org.checkerframework.checker.nullness.qual.KeyFor; public class Issue1847 { - final Map map = new HashMap<>(); + final Map map = new HashMap<>(); - public void test() { - // Should give null error here: - // :: error: (dereference.of.nullable) - withLookup((String myVar) -> map.get(myVar).length()); - for (Iterator> iterator = map.entrySet().iterator(); - iterator.hasNext(); ) { - Map.Entry<@KeyFor("map") String, String> entry = iterator.next(); - // Problem is that myVar gets inferred as @KeyFor("map") here, - // and this variable is not distinguished from the lambda variables of the same name, - // even though their scopes do not overlap and they are different variables. - // Change this variable name to myVar2 and you will see the null errors on the lambdas: - String myVar = entry.getKey(); - } + public void test() { + // Should give null error here: + // :: error: (dereference.of.nullable) + withLookup((String myVar) -> map.get(myVar).length()); + for (Iterator> iterator = + map.entrySet().iterator(); + iterator.hasNext(); ) { + Map.Entry<@KeyFor("map") String, String> entry = iterator.next(); + // Problem is that myVar gets inferred as @KeyFor("map") here, + // and this variable is not distinguished from the lambda variables of the same name, + // even though their scopes do not overlap and they are different variables. + // Change this variable name to myVar2 and you will see the null errors on the lambdas: + String myVar = entry.getKey(); + } - // Should also give null error here: - // :: error: (dereference.of.nullable) - withLookup(myVar -> map.get(myVar).length()); - } + // Should also give null error here: + // :: error: (dereference.of.nullable) + withLookup(myVar -> map.get(myVar).length()); + } - public void withLookup(Function getFromMap) {} + public void withLookup(Function getFromMap) {} } diff --git a/checker/tests/nullness/Issue1847B.java b/checker/tests/nullness/Issue1847B.java index ca198cab16e..5366b3e7277 100644 --- a/checker/tests/nullness/Issue1847B.java +++ b/checker/tests/nullness/Issue1847B.java @@ -1,19 +1,20 @@ -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Function; + public class Issue1847B { - public void test1() { - // :: error: (dereference.of.nullable) - Function<@Nullable String, String> f1 = (@Nullable String myVar) -> myVar.toString(); - { - String myVar = "hello"; + public void test1() { + // :: error: (dereference.of.nullable) + Function<@Nullable String, String> f1 = (@Nullable String myVar) -> myVar.toString(); + { + String myVar = "hello"; + } + // :: error: (dereference.of.nullable) + Function<@Nullable String, String> f2 = (@Nullable String myVar) -> myVar.toString(); } - // :: error: (dereference.of.nullable) - Function<@Nullable String, String> f2 = (@Nullable String myVar) -> myVar.toString(); - } - public void test2() { - // :: error: (dereference.of.nullable) - Function f1 = (@Nullable String myVar) -> myVar.toString(); - } + public void test2() { + // :: error: (dereference.of.nullable) + Function f1 = (@Nullable String myVar) -> myVar.toString(); + } } diff --git a/checker/tests/nullness/Issue1922.java b/checker/tests/nullness/Issue1922.java index 614daad7faf..097d187b074 100644 --- a/checker/tests/nullness/Issue1922.java +++ b/checker/tests/nullness/Issue1922.java @@ -1,32 +1,33 @@ -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + public class Issue1922 { - // A method to find a K in the collection and return it, or return null. - public static @Nullable K findKey(Collection<@NonNull K> keys, Object target) { - for (K key : keys) { - if (target.equals(key)) { - return key; - } + // A method to find a K in the collection and return it, or return null. + public static @Nullable K findKey(Collection<@NonNull K> keys, Object target) { + for (K key : keys) { + if (target.equals(key)) { + return key; + } + } + return null; } - return null; - } - // Find a key in a map and return String version of its value. - public static String findKeyAndFetchString(Map someMap) { - // :: error: (type.argument.type.incompatible) - @Nullable @KeyFor("someMap") String myKey = Issue1922.<@KeyFor("someMap") String>findKey(someMap.keySet(), "Foo"); + // Find a key in a map and return String version of its value. + public static String findKeyAndFetchString(Map someMap) { + // :: error: (type.argument.type.incompatible) + @Nullable @KeyFor("someMap") String myKey = Issue1922.<@KeyFor("someMap") String>findKey(someMap.keySet(), "Foo"); - // :: error: (argument.type.incompatible) - Object value = someMap.get(myKey); - return value.toString(); - } + // :: error: (argument.type.incompatible) + Object value = someMap.get(myKey); + return value.toString(); + } - public static void main(String[] args) { - findKeyAndFetchString(new HashMap<>()); - } + public static void main(String[] args) { + findKeyAndFetchString(new HashMap<>()); + } } diff --git a/checker/tests/nullness/Issue1949.java b/checker/tests/nullness/Issue1949.java index 0bd3216639b..6de4e8618d9 100644 --- a/checker/tests/nullness/Issue1949.java +++ b/checker/tests/nullness/Issue1949.java @@ -1,21 +1,22 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class Issue1949 { - public interface Base {} + public interface Base {} - public interface Child extends Base<@Nullable R> {} + public interface Child extends Base<@Nullable R> {} - public abstract static class BaseClass implements Child { - abstract List> foo(); - } + public abstract static class BaseClass implements Child { + abstract List> foo(); + } - public static class ChildClass extends BaseClass { + public static class ChildClass extends BaseClass { - @Override - public List> foo() { - return new ArrayList<>(); + @Override + public List> foo() { + return new ArrayList<>(); + } } - } } diff --git a/checker/tests/nullness/Issue1981.java b/checker/tests/nullness/Issue1981.java index 7328ba9c8ff..98dd8b92426 100644 --- a/checker/tests/nullness/Issue1981.java +++ b/checker/tests/nullness/Issue1981.java @@ -5,17 +5,17 @@ public class Issue1981 { - void test(List ids) { - for (List l : func2(func1(ids))) {} - } + void test(List ids) { + for (List l : func2(func1(ids))) {} + } - static > List func1(Iterable elements) { - // :: error: (return.type.incompatible) - return null; - } + static > List func1(Iterable elements) { + // :: error: (return.type.incompatible) + return null; + } - static List> func2(List list) { - // :: error: (return.type.incompatible) - return null; - } + static List> func2(List list) { + // :: error: (return.type.incompatible) + return null; + } } diff --git a/checker/tests/nullness/Issue1983.java b/checker/tests/nullness/Issue1983.java index 038cccb292f..80ea4fcad59 100644 --- a/checker/tests/nullness/Issue1983.java +++ b/checker/tests/nullness/Issue1983.java @@ -1,39 +1,40 @@ // Test case for Issue 1983: // https://github.com/typetools/checker-framework/issues/1983 +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.List; import java.util.function.Function; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1983 { - @SuppressWarnings("initialization.field.uninitialized") - Converter converter; + @SuppressWarnings("initialization.field.uninitialized") + Converter converter; + + void test(List params) { + func1(transform(params, p -> of(converter.as((String) p[0])))); + } + + static class Converter { - void test(List params) { - func1(transform(params, p -> of(converter.as((String) p[0])))); - } + @SuppressWarnings("nullness") + public Converter as(@Nullable T value) { + return null; + } + } + + @SuppressWarnings("nullness") + static List of(T t) { + return null; + } - static class Converter { + @SuppressWarnings("nullness") + V func1(List>> bulkParameterValues) { + return null; + } @SuppressWarnings("nullness") - public Converter as(@Nullable T value) { - return null; + static List transform(List fromList, Function function) { + return null; } - } - - @SuppressWarnings("nullness") - static List of(T t) { - return null; - } - - @SuppressWarnings("nullness") - V func1(List>> bulkParameterValues) { - return null; - } - - @SuppressWarnings("nullness") - static List transform(List fromList, Function function) { - return null; - } } diff --git a/checker/tests/nullness/Issue2013.java b/checker/tests/nullness/Issue2013.java index 791c36151b5..316932224d2 100644 --- a/checker/tests/nullness/Issue2013.java +++ b/checker/tests/nullness/Issue2013.java @@ -5,104 +5,104 @@ // @skip-test public class Issue2013 { - static class Super { - private @Nullable String name = null; - - @EnsuresNonNull("name()") - // :: error: (contracts.postcondition.not.satisfied) - void ensureNameNonNull() { - name = "name"; - } - - @RequiresNonNull("name()") - void requiresNameNonNull() { - name().equals("name"); + static class Super { + private @Nullable String name = null; + + @EnsuresNonNull("name()") + // :: error: (contracts.postcondition.not.satisfied) + void ensureNameNonNull() { + name = "name"; + } + + @RequiresNonNull("name()") + void requiresNameNonNull() { + name().equals("name"); + } + + @Pure + @Nullable String name() { + return name; + } } - @Pure - @Nullable String name() { - return name; - } - } - - static class Sub extends Super { - @Nullable String subname = null; - - @Override - // :: error: (contracts.postcondition.not.satisfied) - void ensureNameNonNull() { - super.ensureNameNonNull(); - subname = "Sub"; + static class Sub extends Super { + @Nullable String subname = null; + + @Override + // :: error: (contracts.postcondition.not.satisfied) + void ensureNameNonNull() { + super.ensureNameNonNull(); + subname = "Sub"; + } + + public static boolean flag; + + @Override + @RequiresNonNull("name()") + void requiresNameNonNull() { + if (flag) { + name().toString(); + } else { + super.requiresNameNonNull(); + } + } + + @Override + @Nullable String name() { + return subname; + } + + void use() { + if (super.name() != null) { + // :: error: (contracts.precondition.not.satisfied) + requiresNameNonNull(); + } + + if (this.name() != null) { + requiresNameNonNull(); + } + + if (super.name() != null) { + // :: error: (contracts.precondition.not.satisfied) + super.requiresNameNonNull(); + } + + if (this.name() != null) { + super.requiresNameNonNull(); + } + + super.ensureNameNonNull(); + // :: error: (contracts.precondition.not.satisfied) + requiresNameNonNull(); + + super.ensureNameNonNull(); + // :: error: (contracts.precondition.not.satisfied) + super.requiresNameNonNull(); + + ensureNameNonNull(); + super.requiresNameNonNull(); + + ensureNameNonNull(); + requiresNameNonNull(); + } } - public static boolean flag; + void method(Super superObj) { + if (superObj.name() != null) { + superObj.requiresNameNonNull(); + } - @Override - @RequiresNonNull("name()") - void requiresNameNonNull() { - if (flag) { - name().toString(); - } else { - super.requiresNameNonNull(); - } + superObj.ensureNameNonNull(); + superObj.requiresNameNonNull(); } - @Override - @Nullable String name() { - return subname; - } - - void use() { - if (super.name() != null) { - // :: error: (contracts.precondition.not.satisfied) - requiresNameNonNull(); - } - - if (this.name() != null) { - requiresNameNonNull(); - } - - if (super.name() != null) { - // :: error: (contracts.precondition.not.satisfied) - super.requiresNameNonNull(); - } - - if (this.name() != null) { - super.requiresNameNonNull(); - } - - super.ensureNameNonNull(); - // :: error: (contracts.precondition.not.satisfied) - requiresNameNonNull(); - - super.ensureNameNonNull(); - // :: error: (contracts.precondition.not.satisfied) - super.requiresNameNonNull(); - - ensureNameNonNull(); - super.requiresNameNonNull(); - - ensureNameNonNull(); - requiresNameNonNull(); - } - } - - void method(Super superObj) { - if (superObj.name() != null) { - superObj.requiresNameNonNull(); - } - - superObj.ensureNameNonNull(); - superObj.requiresNameNonNull(); - } - - void method2(Sub subObj) { - if (subObj.name() != null) { - subObj.requiresNameNonNull(); - } + void method2(Sub subObj) { + if (subObj.name() != null) { + subObj.requiresNameNonNull(); + } - if (subObj.name() != null) { - subObj.requiresNameNonNull(); + if (subObj.name() != null) { + subObj.requiresNameNonNull(); + } } - } } diff --git a/checker/tests/nullness/Issue2031.java b/checker/tests/nullness/Issue2031.java index 1ea36ed0c6b..81c68709e69 100644 --- a/checker/tests/nullness/Issue2031.java +++ b/checker/tests/nullness/Issue2031.java @@ -1,42 +1,43 @@ -import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Map; + public class Issue2031 { - public interface InterfaceA {} - - public interface InterfaceB {} - - abstract static class OperatorSection & InterfaceB> { - C makeExpression(Map expressions) { - @Nullable C e = expressions.get(""); - if (e != null) { - return e; - } else { - throw new RuntimeException(""); - } + public interface InterfaceA {} + + public interface InterfaceB {} + + abstract static class OperatorSection & InterfaceB> { + C makeExpression(Map expressions) { + @Nullable C e = expressions.get(""); + if (e != null) { + return e; + } else { + throw new RuntimeException(""); + } + } } - } - static class RecursiveTypes { - public interface A {} + static class RecursiveTypes { + public interface A {} - public interface B {} + public interface B {} - abstract static class OperatorSection & B> { - abstract EXPRESSION makeExpression(Map expressions); - } + abstract static class OperatorSection & B> { + abstract EXPRESSION makeExpression(Map expressions); + } - static class BinaryOperatorSection & B> - extends OperatorSection { - @Override - EXPRESSION makeExpression(Map expressions) { - @Nullable EXPRESSION e = expressions.get(""); - if (e != null) { - return e; - } else { - throw new RuntimeException(""); + static class BinaryOperatorSection & B> + extends OperatorSection { + @Override + EXPRESSION makeExpression(Map expressions) { + @Nullable EXPRESSION e = expressions.get(""); + if (e != null) { + return e; + } else { + throw new RuntimeException(""); + } + } } - } } - } } diff --git a/checker/tests/nullness/Issue2048.java b/checker/tests/nullness/Issue2048.java index 034fc05f771..0ca919e7d97 100644 --- a/checker/tests/nullness/Issue2048.java +++ b/checker/tests/nullness/Issue2048.java @@ -8,25 +8,25 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue2048 { - interface Foo {} + interface Foo {} - static class Fooer {} + static class Fooer {} - class UseNbl { - void foo(Fooer fooer) {} - } + class UseNbl { + void foo(Fooer fooer) {} + } - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - Fooer<@Nullable Foo> nblFooer = new Fooer<>(); - Fooer<@NonNull Foo> nnFooer = new Fooer<>(); + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + Fooer<@Nullable Foo> nblFooer = new Fooer<>(); + Fooer<@NonNull Foo> nnFooer = new Fooer<>(); - void use(UseNbl<@Nullable Foo> useNbl) { - useNbl.foo(nblFooer); - useNbl.foo(nnFooer); - } + void use(UseNbl<@Nullable Foo> useNbl) { + useNbl.foo(nblFooer); + useNbl.foo(nnFooer); + } - class UseNN { - void foo(Fooer fooer) {} - } + class UseNN { + void foo(Fooer fooer) {} + } } diff --git a/checker/tests/nullness/Issue2052.java b/checker/tests/nullness/Issue2052.java index 01b01cb9a38..365f5624027 100644 --- a/checker/tests/nullness/Issue2052.java +++ b/checker/tests/nullness/Issue2052.java @@ -1,18 +1,18 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization; public class Issue2052 { - public static class ParentW { - protected final String field; + public static class ParentW { + protected final String field; - public ParentW() { - // Initializing "field" at the declaration, did not trigger the bug. - field = ""; + public ParentW() { + // Initializing "field" at the declaration, did not trigger the bug. + field = ""; + } } - } - public static class ChildW extends ParentW { - public String getField(@UnknownInitialization(ParentW.class) ChildW this) { - return this.field; + public static class ChildW extends ParentW { + public String getField(@UnknownInitialization(ParentW.class) ChildW this) { + return this.field; + } } - } } diff --git a/checker/tests/nullness/Issue2171.java b/checker/tests/nullness/Issue2171.java index 481b013f146..bf93a890dab 100644 --- a/checker/tests/nullness/Issue2171.java +++ b/checker/tests/nullness/Issue2171.java @@ -1,35 +1,37 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + public class Issue2171 { - static void varArgsMethod(@PolyNull Object... args) {} + static void varArgsMethod(@PolyNull Object... args) {} - static void callToVarArgsObject(@PolyNull Object pn, @NonNull Object nn, @Nullable Object nble) { - varArgsMethod(nble, nble); - varArgsMethod(nble, nn); - varArgsMethod(nble, pn); - varArgsMethod(nn, nble); - varArgsMethod(nn, nn); - varArgsMethod(nn, pn); - varArgsMethod(pn, nble); - varArgsMethod(pn, nn); - varArgsMethod(pn, pn); - } + static void callToVarArgsObject( + @PolyNull Object pn, @NonNull Object nn, @Nullable Object nble) { + varArgsMethod(nble, nble); + varArgsMethod(nble, nn); + varArgsMethod(nble, pn); + varArgsMethod(nn, nble); + varArgsMethod(nn, nn); + varArgsMethod(nn, pn); + varArgsMethod(pn, nble); + varArgsMethod(pn, nn); + varArgsMethod(pn, pn); + } - @SuppressWarnings("unchecked") - static void genVarArgsMethod(List... args) {} + @SuppressWarnings("unchecked") + static void genVarArgsMethod(List... args) {} - @SuppressWarnings("unchecked") - static void genCallToVarArgsObject( - List<@PolyNull Object> pn, List<@NonNull Object> nn, List<@Nullable Object> nble) { - genVarArgsMethod(nble, nble); - genVarArgsMethod(nble, nn); - genVarArgsMethod(nble, pn); - genVarArgsMethod(nn, nble); - genVarArgsMethod(nn, nn); - genVarArgsMethod(nn, pn); - genVarArgsMethod(pn, nble); - genVarArgsMethod(pn, nn); - genVarArgsMethod(pn, pn); - } + @SuppressWarnings("unchecked") + static void genCallToVarArgsObject( + List<@PolyNull Object> pn, List<@NonNull Object> nn, List<@Nullable Object> nble) { + genVarArgsMethod(nble, nble); + genVarArgsMethod(nble, nn); + genVarArgsMethod(nble, pn); + genVarArgsMethod(nn, nble); + genVarArgsMethod(nn, nn); + genVarArgsMethod(nn, pn); + genVarArgsMethod(pn, nble); + genVarArgsMethod(pn, nn); + genVarArgsMethod(pn, pn); + } } diff --git a/checker/tests/nullness/Issue2247.java b/checker/tests/nullness/Issue2247.java index 872889f4958..4bc729457cc 100644 --- a/checker/tests/nullness/Issue2247.java +++ b/checker/tests/nullness/Issue2247.java @@ -1,41 +1,42 @@ // This is a test case for issue 2247: // https://github.com/typetools/checker-framework/issues/2247 -import java.util.List; -import java.util.Map; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; +import java.util.Map; + public class Issue2247 { - @NonNull static class DeclaredClass {} + @NonNull static class DeclaredClass {} - class ValidUseType { + class ValidUseType { - // :: error: (type.invalid.annotations.on.use) - void test1(@Nullable DeclaredClass object) {} + // :: error: (type.invalid.annotations.on.use) + void test1(@Nullable DeclaredClass object) {} - // :: error: (type.invalid.annotations.on.use) - @Nullable DeclaredClass test2() { - return null; - } + // :: error: (type.invalid.annotations.on.use) + @Nullable DeclaredClass test2() { + return null; + } - // :: error: (type.invalid.annotations.on.use) - void test3(List<@Nullable DeclaredClass> param) { - @Nullable DeclaredClass object = null; - // :: error: (type.invalid.annotations.on.use) - @Nullable DeclaredClass[] array = null; - } + // :: error: (type.invalid.annotations.on.use) + void test3(List<@Nullable DeclaredClass> param) { + @Nullable DeclaredClass object = null; + // :: error: (type.invalid.annotations.on.use) + @Nullable DeclaredClass[] array = null; + } - // :: error: (type.invalid.annotations.on.use) - void test4(@NonNull T t) {} + // :: error: (type.invalid.annotations.on.use) + void test4(@NonNull T t) {} - void test5(Map map) { - @Nullable DeclaredClass value = map.get("somekey"); - System.out.println(value); - if (value != null) { - @NonNull DeclaredClass nonnull = value; - } + void test5(Map map) { + @Nullable DeclaredClass value = map.get("somekey"); + System.out.println(value); + if (value != null) { + @NonNull DeclaredClass nonnull = value; + } + } } - } } diff --git a/checker/tests/nullness/Issue2407.java b/checker/tests/nullness/Issue2407.java index 53bc5bde174..1510ccbd2f1 100644 --- a/checker/tests/nullness/Issue2407.java +++ b/checker/tests/nullness/Issue2407.java @@ -4,20 +4,20 @@ public class Issue2407 { - @RequiresNonNull("#1") - void setMessage(String message) {} + @RequiresNonNull("#1") + void setMessage(String message) {} - @EnsuresNonNull("1") - // :: error: (flowexpr.parse.error) - void method() {} + @EnsuresNonNull("1") + // :: error: (flowexpr.parse.error) + void method() {} - @EnsuresNonNullIf(expression = "1", result = true) - // :: error: (flowexpr.parse.error) - void method2() {} + @EnsuresNonNullIf(expression = "1", result = true) + // :: error: (flowexpr.parse.error) + void method2() {} - void main() { - Issue2407 object = new Issue2407(); - // :: error: (contracts.precondition.not.satisfied) - object.setMessage(new Object() + "bar"); - } + void main() { + Issue2407 object = new Issue2407(); + // :: error: (contracts.precondition.not.satisfied) + object.setMessage(new Object() + "bar"); + } } diff --git a/checker/tests/nullness/Issue2432.java b/checker/tests/nullness/Issue2432.java index 74512737241..e35a633e07e 100644 --- a/checker/tests/nullness/Issue2432.java +++ b/checker/tests/nullness/Issue2432.java @@ -1,119 +1,120 @@ // Test case for issue 2432: // https://github.com/typetools/checker-framework/issues/2432 -import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; +import java.util.List; + public class Issue2432 { - void jdkAnnotation(List<@PolyNull Object> nl, @Nullable Object no, @PolyNull Object po) { - // :: error: (argument.type.incompatible) - nl.add(null); - // :: error: (argument.type.incompatible) - nl.add(no); - // OK - nl.add(po); - } - - // receiver's poly annotations in declaration are different from the ones in invocation - void polyReceiverType( - TypeArgClass<@PolyNull Object> tc, - @NonNull Object nno, - @Nullable Object no, - @PolyNull Object po) { - // :: error: (argument.type.incompatible) - Object object = tc.echo(no); - // :: error: (assignment.type.incompatible) - nno = tc.echo(po); - // No error. Return value remains @PolyNull. - // Note po's @PolyNull is unsubstitutable (from parameter but not from declaration) - po = tc.echo(po); - } - - // the following two methods tests pesudo assignment of arguments with poly annotation - void polyAssignment(TypeArgClass<@NonNull Object> nnc, @PolyNull Object po) { - // :: error: (argument.type.incompatible) - nnc.echo(po); - } - - void polyAssignment2(TypeArgClass<@Nullable Object> nc, @PolyNull Object po) { - // No error - nc.echo(po); - // :: error: (assignment.type.incompatible) - po = nc.echo(po); - } - - // a "foo function" with 2 poly annotations, where one of them appears in type argument - // purpose: test invocation without using explicit receiver - @PolyNull Object foo2PolyTypeArg( - TypeArgClass<@PolyNull Object> pc, @PolyNull Object po, @NonNull Object nno) { - return pc.add(nno, po); - } - - // lub tests without receiver - // lub combination: (@Nullable, @Nullable) = @Nullable - void lubWithTypeArgNoReceiver1(TypeArgClass<@Nullable Object> nc, @Nullable Object no) { - @NonNull Object nno = new Object(); - // No error - foo2PolyTypeArg(nc, no, nno); - } - - // lub combination: (@NonNull, @NonNull) = @NonNull - void lubWithTypeArgNoReceiver2(TypeArgClass<@NonNull Object> nnc, @NonNull Object nno) { - // No error - foo2PolyTypeArg(nnc, nno, nno); - } - - // lub combination: (@Nullable, @NonNull) = @Nullable - void lubWithTypeArgNoReceiver3(TypeArgClass<@Nullable Object> nc, @NonNull Object nno) { - // No error - foo2PolyTypeArg(nc, nno, nno); - } - - // lub combination: (@NonNull, @Nullable) = @Nullable - void lubWithTypeArgNoReceiver4(TypeArgClass<@NonNull Object> nnc, @Nullable Object no) { - // :: error: (argument.type.incompatible) - foo2PolyTypeArg(nnc, no, new Object()); - } - - // lub test with receiver - // T dummy in tripleAdd is to ensure poly annotations from declaration is handled separately - void lubWithReceiver( - TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { - // :: error: (argument.type.incompatible) - pc.tripleAdd(no, nno, no); - // No error - pc.tripleAdd(no, nno, nno); - } - - // ensure poly annotations from declaration is handled separately from poly from other context - void declarationPolyInParameter( - TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { - // No error - pc.echo(nno, no); - - // the invocation is valid, while the assignment is not - // :: error: (assignment.type.incompatible) - @NonNull Object nonnull = pc.echo(nno, no); - } - - private class TypeArgClass { - @PolyNull Object add(@PolyNull Object obj, T dummy) { - return obj; + void jdkAnnotation(List<@PolyNull Object> nl, @Nullable Object no, @PolyNull Object po) { + // :: error: (argument.type.incompatible) + nl.add(null); + // :: error: (argument.type.incompatible) + nl.add(no); + // OK + nl.add(po); + } + + // receiver's poly annotations in declaration are different from the ones in invocation + void polyReceiverType( + TypeArgClass<@PolyNull Object> tc, + @NonNull Object nno, + @Nullable Object no, + @PolyNull Object po) { + // :: error: (argument.type.incompatible) + Object object = tc.echo(no); + // :: error: (assignment.type.incompatible) + nno = tc.echo(po); + // No error. Return value remains @PolyNull. + // Note po's @PolyNull is unsubstitutable (from parameter but not from declaration) + po = tc.echo(po); + } + + // the following two methods tests pesudo assignment of arguments with poly annotation + void polyAssignment(TypeArgClass<@NonNull Object> nnc, @PolyNull Object po) { + // :: error: (argument.type.incompatible) + nnc.echo(po); + } + + void polyAssignment2(TypeArgClass<@Nullable Object> nc, @PolyNull Object po) { + // No error + nc.echo(po); + // :: error: (assignment.type.incompatible) + po = nc.echo(po); } - @PolyNull Object tripleAdd(@PolyNull Object o1, @PolyNull Object o2, T dummy) { - return o1; + // a "foo function" with 2 poly annotations, where one of them appears in type argument + // purpose: test invocation without using explicit receiver + @PolyNull Object foo2PolyTypeArg( + TypeArgClass<@PolyNull Object> pc, @PolyNull Object po, @NonNull Object nno) { + return pc.add(nno, po); } - T echo(T obj) { - return obj; + // lub tests without receiver + // lub combination: (@Nullable, @Nullable) = @Nullable + void lubWithTypeArgNoReceiver1(TypeArgClass<@Nullable Object> nc, @Nullable Object no) { + @NonNull Object nno = new Object(); + // No error + foo2PolyTypeArg(nc, no, nno); } - T echo(T obj, @PolyNull Object dummy) { - return obj; + // lub combination: (@NonNull, @NonNull) = @NonNull + void lubWithTypeArgNoReceiver2(TypeArgClass<@NonNull Object> nnc, @NonNull Object nno) { + // No error + foo2PolyTypeArg(nnc, nno, nno); + } + + // lub combination: (@Nullable, @NonNull) = @Nullable + void lubWithTypeArgNoReceiver3(TypeArgClass<@Nullable Object> nc, @NonNull Object nno) { + // No error + foo2PolyTypeArg(nc, nno, nno); + } + + // lub combination: (@NonNull, @Nullable) = @Nullable + void lubWithTypeArgNoReceiver4(TypeArgClass<@NonNull Object> nnc, @Nullable Object no) { + // :: error: (argument.type.incompatible) + foo2PolyTypeArg(nnc, no, new Object()); + } + + // lub test with receiver + // T dummy in tripleAdd is to ensure poly annotations from declaration is handled separately + void lubWithReceiver( + TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { + // :: error: (argument.type.incompatible) + pc.tripleAdd(no, nno, no); + // No error + pc.tripleAdd(no, nno, nno); + } + + // ensure poly annotations from declaration is handled separately from poly from other context + void declarationPolyInParameter( + TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) { + // No error + pc.echo(nno, no); + + // the invocation is valid, while the assignment is not + // :: error: (assignment.type.incompatible) + @NonNull Object nonnull = pc.echo(nno, no); + } + + private class TypeArgClass { + @PolyNull Object add(@PolyNull Object obj, T dummy) { + return obj; + } + + @PolyNull Object tripleAdd(@PolyNull Object o1, @PolyNull Object o2, T dummy) { + return o1; + } + + T echo(T obj) { + return obj; + } + + T echo(T obj, @PolyNull Object dummy) { + return obj; + } } - } } diff --git a/checker/tests/nullness/Issue2432b.java b/checker/tests/nullness/Issue2432b.java index de243108199..804b32eeba0 100644 --- a/checker/tests/nullness/Issue2432b.java +++ b/checker/tests/nullness/Issue2432b.java @@ -1,42 +1,43 @@ // Ensure correct handling of type parameters and arrays. // https://github.com/typetools/checker-framework/issues/2432 +import org.checkerframework.checker.nullness.qual.PolyNull; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.PolyNull; public class Issue2432b { - void objectAsTypeArg() { - List objs = new ArrayList<>(); - // no error - Object[] objarray = objs.toArray(); - } - - void myClassAsTypeArg() { - MyClass objs = new MyClass<>(); - Object[] objarray = objs.toArray(); - // no error - Object[] objarray2 = objs.toArrayPoly(); - } - - void stringAsTypeArg() { - List strs = new ArrayList<>(); - Object[] strarray = strs.toArray(); - } - - void listAsTypeArg() { - List lists = new ArrayList<>(); - Object[] listarray = lists.toArray(); - } - - private static class MyClass { - - Object[] toArray() { - return new Object[] {new Object()}; + void objectAsTypeArg() { + List objs = new ArrayList<>(); + // no error + Object[] objarray = objs.toArray(); } - @PolyNull Object[] toArrayPoly(MyClass<@PolyNull MyTypeParam> this) { - return new Object[] {new Object()}; + void myClassAsTypeArg() { + MyClass objs = new MyClass<>(); + Object[] objarray = objs.toArray(); + // no error + Object[] objarray2 = objs.toArrayPoly(); + } + + void stringAsTypeArg() { + List strs = new ArrayList<>(); + Object[] strarray = strs.toArray(); + } + + void listAsTypeArg() { + List lists = new ArrayList<>(); + Object[] listarray = lists.toArray(); + } + + private static class MyClass { + + Object[] toArray() { + return new Object[] {new Object()}; + } + + @PolyNull Object[] toArrayPoly(MyClass<@PolyNull MyTypeParam> this) { + return new Object[] {new Object()}; + } } - } } diff --git a/checker/tests/nullness/Issue2470.java b/checker/tests/nullness/Issue2470.java index 96d90806742..d5017e6d4ca 100644 --- a/checker/tests/nullness/Issue2470.java +++ b/checker/tests/nullness/Issue2470.java @@ -3,60 +3,60 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class Issue2470 { - static class Example { - @MonotonicNonNull String s; + static class Example { + @MonotonicNonNull String s; + + public Example() {} + + @EnsuresNonNull("this.s") + public Example setS(String s1) { + this.s = s1; + return this; + } + + // TODO: Support "return" in Java Expression syntax. + // @EnsuresNonNull("return.s") + @EnsuresNonNull("this.s") + public Example setS2(String s1) { + this.s = s1; + return this; + } + + @RequiresNonNull("this.s") + public void print() { + System.out.println(this.s.toString()); + } + } + + static void buggy() { + new Example() + // :: error: (contracts.precondition.not.satisfied) + .print(); + } - public Example() {} + static void ok() { + Example e = new Example(); + e.setS("test"); + e.print(); + } - @EnsuresNonNull("this.s") - public Example setS(String s1) { - this.s = s1; - return this; + static void buggy2() { + new Example() + .setS("test") + // :: error:(contracts.precondition.not.satisfied) + .print(); } - // TODO: Support "return" in Java Expression syntax. - // @EnsuresNonNull("return.s") - @EnsuresNonNull("this.s") - public Example setS2(String s1) { - this.s = s1; - return this; + // TODO: These should be legal, once "return" is supported in Java Expression syntax. + // of a method. + /* + static void ok3() { + Example e = new Example().setS2("test"); + e.print(); } - @RequiresNonNull("this.s") - public void print() { - System.out.println(this.s.toString()); + static void ok2() { + new Example().setS2("test").print(); } - } - - static void buggy() { - new Example() - // :: error: (contracts.precondition.not.satisfied) - .print(); - } - - static void ok() { - Example e = new Example(); - e.setS("test"); - e.print(); - } - - static void buggy2() { - new Example() - .setS("test") - // :: error:(contracts.precondition.not.satisfied) - .print(); - } - - // TODO: These should be legal, once "return" is supported in Java Expression syntax. - // of a method. - /* - static void ok3() { - Example e = new Example().setS2("test"); - e.print(); - } - - static void ok2() { - new Example().setS2("test").print(); - } - */ + */ } diff --git a/checker/tests/nullness/Issue2564.java b/checker/tests/nullness/Issue2564.java index 41f96aa2cde..769f7442e0a 100644 --- a/checker/tests/nullness/Issue2564.java +++ b/checker/tests/nullness/Issue2564.java @@ -1,20 +1,21 @@ +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.KeyFor; public abstract class Issue2564 { - public enum EnumType { - // :: error: (enum.declaration.type.incompatible) - @KeyFor("myMap") MY_KEY, - // :: error: (enum.declaration.type.incompatible) - @KeyFor("enumMap") ENUM_KEY; - private static final Map enumMap = new HashMap<>(); + public enum EnumType { + // :: error: (enum.declaration.type.incompatible) + @KeyFor("myMap") MY_KEY, + // :: error: (enum.declaration.type.incompatible) + @KeyFor("enumMap") ENUM_KEY; + private static final Map enumMap = new HashMap<>(); - void method() { - @KeyFor("enumMap") EnumType t = ENUM_KEY; - int x = enumMap.get(ENUM_KEY); + void method() { + @KeyFor("enumMap") EnumType t = ENUM_KEY; + int x = enumMap.get(ENUM_KEY); + } } - } - private static final Map myMap = new HashMap<>(); + private static final Map myMap = new HashMap<>(); } diff --git a/checker/tests/nullness/Issue2565.java b/checker/tests/nullness/Issue2565.java index 64e8b554b03..257ed7fb67b 100644 --- a/checker/tests/nullness/Issue2565.java +++ b/checker/tests/nullness/Issue2565.java @@ -2,10 +2,10 @@ public abstract class Issue2565 { - // Broken Case: - abstract void processErrors(List>> errors); + // Broken Case: + abstract void processErrors(List>> errors); - static class Error & Hoo> {} + static class Error & Hoo> {} - interface Hoo {} + interface Hoo {} } diff --git a/checker/tests/nullness/Issue2587.java b/checker/tests/nullness/Issue2587.java index 468e036b37d..bc1db838c01 100644 --- a/checker/tests/nullness/Issue2587.java +++ b/checker/tests/nullness/Issue2587.java @@ -1,38 +1,39 @@ +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.KeyFor; @SuppressWarnings({ - "enum.declaration.type.incompatible", - "assignment.type.incompatible" + "enum.declaration.type.incompatible", + "assignment.type.incompatible" }) // These warnings are not relevant public abstract class Issue2587 { - public enum EnumType { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("myMap") MY_KEY, + public enum EnumType { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("myMap") MY_KEY, - @KeyFor("enumMap") ENUM_KEY; - private static final Map enumMap = new HashMap<>(); + @KeyFor("enumMap") ENUM_KEY; + private static final Map enumMap = new HashMap<>(); - void method() { - @KeyFor("enumMap") EnumType t = ENUM_KEY; - int x = enumMap.get(ENUM_KEY); + void method() { + @KeyFor("enumMap") EnumType t = ENUM_KEY; + int x = enumMap.get(ENUM_KEY); + } } - } - public static class Inner { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("myMap") String MY_KEY = ""; + public static class Inner { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("myMap") String MY_KEY = ""; - public static class Inner2 { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("myMap") String MY_KEY2 = ""; + public static class Inner2 { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("myMap") String MY_KEY2 = ""; - @KeyFor("innerMap") String MY_KEY3 = ""; - } + @KeyFor("innerMap") String MY_KEY3 = ""; + } - private static final Map innerMap = new HashMap<>(); - } + private static final Map innerMap = new HashMap<>(); + } - private final Map myMap = new HashMap<>(); + private final Map myMap = new HashMap<>(); } diff --git a/checker/tests/nullness/Issue2619.java b/checker/tests/nullness/Issue2619.java index c89632f73cd..f72e1b177a8 100644 --- a/checker/tests/nullness/Issue2619.java +++ b/checker/tests/nullness/Issue2619.java @@ -1,51 +1,52 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.dataflow.qual.Pure; +import java.util.HashMap; +import java.util.Map; + public class Issue2619 { - public Map map = new HashMap<>(); + public Map map = new HashMap<>(); - void m00(Aux aux1) { - if (aux1.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map"}) String s1 = Aux.MINIMUM_VALUE; + void m00(Aux aux1) { + if (aux1.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map"}) String s1 = Aux.MINIMUM_VALUE; + } } - } - void m01(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; + void m01(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; + } } - } - void m02(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + void m02(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + } } - } - void m03(Aux aux1, Aux aux2) { - if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + void m03(Aux aux1, Aux aux2) { + if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + } } - } - static class Aux { + static class Aux { - public Map map = new HashMap<>(); + public Map map = new HashMap<>(); - public static final String MINIMUM_VALUE = "minvalue"; + public static final String MINIMUM_VALUE = "minvalue"; - @Pure - @EnsuresKeyForIf(result = true, expression = "#1", map = "map") - public boolean hasValue(String key) { - return map.containsKey(key); - } + @Pure + @EnsuresKeyForIf(result = true, expression = "#1", map = "map") + public boolean hasValue(String key) { + return map.containsKey(key); + } - @Pure - public int getInt(@KeyFor("this.map") String key) { - return 22; + @Pure + public int getInt(@KeyFor("this.map") String key) { + return 22; + } } - } } diff --git a/checker/tests/nullness/Issue2619b.java b/checker/tests/nullness/Issue2619b.java index 2ccb97aeb5b..8eda3e0e3e7 100644 --- a/checker/tests/nullness/Issue2619b.java +++ b/checker/tests/nullness/Issue2619b.java @@ -1,55 +1,56 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; import org.checkerframework.checker.nullness.qual.KeyFor; +import java.util.HashMap; +import java.util.Map; + public class Issue2619b { - public Map map = new HashMap<>(); - - void m01(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { - // hasValue is not side-effect-free, so the @KeyFor("aux1.map") is cleared rather than - // glb'ed. - // :: error: (assignment.type.incompatible) - @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; + public Map map = new HashMap<>(); + + void m01(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { + // hasValue is not side-effect-free, so the @KeyFor("aux1.map") is cleared rather than + // glb'ed. + // :: error: (assignment.type.incompatible) + @KeyFor({"aux1.map", "aux2.map"}) String s1 = Aux.MINIMUM_VALUE; + } } - } - void m02(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { - @KeyFor("aux2.map") String s1 = Aux.MINIMUM_VALUE; + void m02(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && aux2.hasValue(Aux.MINIMUM_VALUE)) { + @KeyFor("aux2.map") String s1 = Aux.MINIMUM_VALUE; + } } - } - void m03(Aux aux1, Aux aux2) { - if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { - // ok because map.containsKey is side-effect-free. - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; - @KeyFor("map") String s2 = Aux.MINIMUM_VALUE; + void m03(Aux aux1, Aux aux2) { + if (aux1.hasValue(Aux.MINIMUM_VALUE) && map.containsKey(Aux.MINIMUM_VALUE)) { + // ok because map.containsKey is side-effect-free. + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + @KeyFor("map") String s2 = Aux.MINIMUM_VALUE; + } } - } - void m04(Aux aux1, Aux aux2) { - if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { - // :: error: (assignment.type.incompatible) - @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; - @KeyFor("aux1.map") String s2 = Aux.MINIMUM_VALUE; + void m04(Aux aux1, Aux aux2) { + if (map.containsKey(Aux.MINIMUM_VALUE) && aux1.hasValue(Aux.MINIMUM_VALUE)) { + // :: error: (assignment.type.incompatible) + @KeyFor({"aux1.map", "map"}) String s1 = Aux.MINIMUM_VALUE; + @KeyFor("aux1.map") String s2 = Aux.MINIMUM_VALUE; + } } - } - static class Aux { + static class Aux { - public Map map = new HashMap<>(); + public Map map = new HashMap<>(); - public static String MINIMUM_VALUE = "minvalue"; + public static String MINIMUM_VALUE = "minvalue"; - @EnsuresKeyForIf(result = true, expression = "#1", map = "map") - public boolean hasValue(String key) { - return map.containsKey(key); - } + @EnsuresKeyForIf(result = true, expression = "#1", map = "map") + public boolean hasValue(String key) { + return map.containsKey(key); + } - public int getInt(@KeyFor("this.map") String key) { - return 22; + public int getInt(@KeyFor("this.map") String key) { + return 22; + } } - } } diff --git a/checker/tests/nullness/Issue266.java b/checker/tests/nullness/Issue266.java index bead2544396..0b1df73bc96 100644 --- a/checker/tests/nullness/Issue266.java +++ b/checker/tests/nullness/Issue266.java @@ -5,19 +5,19 @@ public class Issue266 { - abstract static class Inner { - abstract String getThing(); - } + abstract static class Inner { + abstract String getThing(); + } - static @Nullable Inner method(@Nullable Object arg) { - final Object tmp = arg; - if (tmp == null) { - return null; + static @Nullable Inner method(@Nullable Object arg) { + final Object tmp = arg; + if (tmp == null) { + return null; + } + return new Inner() { + String getThing() { + return tmp.toString(); + } + }; } - return new Inner() { - String getThing() { - return tmp.toString(); - } - }; - } } diff --git a/checker/tests/nullness/Issue266a.java b/checker/tests/nullness/Issue266a.java index 397dbdace14..9919404293c 100644 --- a/checker/tests/nullness/Issue266a.java +++ b/checker/tests/nullness/Issue266a.java @@ -4,18 +4,18 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue266a { - private final Object mBar; + private final Object mBar; - public Issue266a() { - mBar = "test"; - Runnable runnable = - new Runnable() { - @Override - public void run() { - // unexpected [dereference.of.nullable] error here - mBar.toString(); - } - }; - runnable.run(); - } + public Issue266a() { + mBar = "test"; + Runnable runnable = + new Runnable() { + @Override + public void run() { + // unexpected [dereference.of.nullable] error here + mBar.toString(); + } + }; + runnable.run(); + } } diff --git a/checker/tests/nullness/Issue2721.java b/checker/tests/nullness/Issue2721.java index 00ce5eea296..089a9828127 100644 --- a/checker/tests/nullness/Issue2721.java +++ b/checker/tests/nullness/Issue2721.java @@ -4,11 +4,11 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue2721 { - void foo() { - passThrough(asList(asList(1))).get(0).get(0).intValue(); - } + void foo() { + passThrough(asList(asList(1))).get(0).get(0).intValue(); + } - List passThrough(List object) { - return object; - } + List passThrough(List object) { + return object; + } } diff --git a/checker/tests/nullness/Issue273.java b/checker/tests/nullness/Issue273.java index ff343fdcb5a..bfb30c619ab 100644 --- a/checker/tests/nullness/Issue273.java +++ b/checker/tests/nullness/Issue273.java @@ -1,26 +1,27 @@ // Test case for issue #273: // https://github.com/typetools/checker-framework/issues/273 +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; public class Issue273 { - public static void main(String... p) { - Map m0 = new HashMap<>(); - Map m1 = new HashMap<>(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("m0") String k = "key"; - m0.put(k, 1); + public static void main(String... p) { + Map m0 = new HashMap<>(); + Map m1 = new HashMap<>(); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("m0") String k = "key"; + m0.put(k, 1); - // :: error: (argument.type.incompatible) - getMap2(m0, m1, k).toString(); - } + // :: error: (argument.type.incompatible) + getMap2(m0, m1, k).toString(); + } - public static @NonNull Integer getMap2( - Map m1, // m1,m0 flipped - Map m0, - @KeyFor("#2") String k) { - return m0.get(k); - } + public static @NonNull Integer getMap2( + Map m1, // m1,m0 flipped + Map m0, + @KeyFor("#2") String k) { + return m0.get(k); + } } diff --git a/checker/tests/nullness/Issue2865.java b/checker/tests/nullness/Issue2865.java index 44dd3af351a..faf135b850b 100644 --- a/checker/tests/nullness/Issue2865.java +++ b/checker/tests/nullness/Issue2865.java @@ -2,24 +2,24 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue2865 { - public class C { - public C(T a) {} + public class C { + public C(T a) {} - public void f(T a) { - new C(a); - // :: error: (argument.type.incompatible) - new C(null); + public void f(T a) { + new C(a); + // :: error: (argument.type.incompatible) + new C(null); + } } - } - void test(Issue2865<@NonNull String> s) { - // :: error: (argument.type.incompatible) - s.new C(null); - s.new C(""); - } + void test(Issue2865<@NonNull String> s) { + // :: error: (argument.type.incompatible) + s.new C(null); + s.new C(""); + } - void test2(Issue2865<@Nullable String> s) { - s.new C(null); - s.new C(""); - } + void test2(Issue2865<@Nullable String> s) { + s.new C(null); + s.new C(""); + } } diff --git a/checker/tests/nullness/Issue2888.java b/checker/tests/nullness/Issue2888.java index 18572568d7b..6713ae74329 100644 --- a/checker/tests/nullness/Issue2888.java +++ b/checker/tests/nullness/Issue2888.java @@ -1,33 +1,33 @@ import com.sun.istack.internal.Nullable; public class Issue2888 { - @Nullable Object[] noa; + @Nullable Object[] noa; - void foo() { - noa = null; - // :: error: (accessing.nullable) :: error: (assignment.type.incompatible) - noa[0] = null; - } + void foo() { + noa = null; + // :: error: (accessing.nullable) :: error: (assignment.type.incompatible) + noa[0] = null; + } - @Nullable Object[] foo2(@Nullable Object[] p) { - noa = p; - noa = foo2(noa); - noa = foo2(p); - return p; - } + @Nullable Object[] foo2(@Nullable Object[] p) { + noa = p; + noa = foo2(noa); + noa = foo2(p); + return p; + } - // The below is copied from Issue 2923. - public void bar1(@Nullable String... args) { - bar2(args); - } + // The below is copied from Issue 2923. + public void bar1(@Nullable String... args) { + bar2(args); + } - private void bar2(@Nullable String... args) { - if (args != null && args.length > 0) { - @Nullable final String arg0 = args[0]; - // :: warning: (nulltest.redundant) - if (arg0 != null) { - System.out.println("arg0: " + arg0); - } + private void bar2(@Nullable String... args) { + if (args != null && args.length > 0) { + @Nullable final String arg0 = args[0]; + // :: warning: (nulltest.redundant) + if (arg0 != null) { + System.out.println("arg0: " + arg0); + } + } } - } } diff --git a/checker/tests/nullness/Issue289.java b/checker/tests/nullness/Issue289.java index 48763453fe7..4caf0afb135 100644 --- a/checker/tests/nullness/Issue289.java +++ b/checker/tests/nullness/Issue289.java @@ -1,41 +1,42 @@ // Test for Issue 289: // https://github.com/typetools/checker-framework/issues/289 +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class Issue289 { - void simple() { - List lo = new ArrayList<>(); - List ls = new ArrayList<>(); + void simple() { + List lo = new ArrayList<>(); + List ls = new ArrayList<>(); - List<@Nullable Object> lno = new ArrayList<>(); - List<@Nullable String> lns = new ArrayList<>(); + List<@Nullable Object> lno = new ArrayList<>(); + List<@Nullable String> lns = new ArrayList<>(); - List> lls = new ArrayList<>(); - lls.add(new ArrayList<>()); + List> lls = new ArrayList<>(); + lls.add(new ArrayList<>()); - // TODO: add a similar test that uses method type variables. - } + // TODO: add a similar test that uses method type variables. + } - // TODO: work on more complex examples: + // TODO: work on more complex examples: - class Upper {} + class Upper {} - class Middle extends Upper {} + class Middle extends Upper {} - class Lower1 extends Middle {} + class Lower1 extends Middle {} - class Lower2 extends Middle {} + class Lower2 extends Middle {} - void complex() { - Upper<@Nullable String> uns = new Lower1<>(); - // :: error: (assignment.type.incompatible) - Upper us = new Lower1<>(); + void complex() { + Upper<@Nullable String> uns = new Lower1<>(); + // :: error: (assignment.type.incompatible) + Upper us = new Lower1<>(); - // :: error: (assignment.type.incompatible) - uns = new Lower2<>(); - us = new Lower2<>(); - } + // :: error: (assignment.type.incompatible) + uns = new Lower2<>(); + us = new Lower2<>(); + } } diff --git a/checker/tests/nullness/Issue293.java b/checker/tests/nullness/Issue293.java index 6bb42e041a3..c17ab75fe76 100644 --- a/checker/tests/nullness/Issue293.java +++ b/checker/tests/nullness/Issue293.java @@ -2,59 +2,59 @@ // https://github.com/typetools/checker-framework/issues/293 public class Issue293 { - void test1() { - String s; - try { - s = read(); - } catch (Exception e) { - // Because of definite assignment, s cannot be mentioned here. - write("Catch."); - return; - } finally { - // Because of definite assignment, s cannot be mentioned here. - write("Finally."); + void test1() { + String s; + try { + s = read(); + } catch (Exception e) { + // Because of definite assignment, s cannot be mentioned here. + write("Catch."); + return; + } finally { + // Because of definite assignment, s cannot be mentioned here. + write("Finally."); + } + + // s is definitely initialized here. + write(s); } - // s is definitely initialized here. - write(s); - } + void test2() { + String s2 = ""; + try { + } finally { + write(s2); + } + } - void test2() { - String s2 = ""; - try { - } finally { - write(s2); + void test3() throws Exception { + String s = ""; + try { + throw new Exception(); + } finally { + write(s); + } } - } - void test3() throws Exception { - String s = ""; - try { - throw new Exception(); - } finally { - write(s); + void test4() throws Exception { + String s = ""; + try { + if (true) { + throw new Exception(); + } else { + s = null; + } + } finally { + // :: error: argument.type.incompatible + write(s); + } } - } - void test4() throws Exception { - String s = ""; - try { - if (true) { + String read() throws Exception { throw new Exception(); - } else { - s = null; - } - } finally { - // :: error: argument.type.incompatible - write(s); } - } - String read() throws Exception { - throw new Exception(); - } - - void write(String p) { - System.out.println(p); - } + void write(String p) { + System.out.println(p); + } } diff --git a/checker/tests/nullness/Issue295.java b/checker/tests/nullness/Issue295.java index 95bc55e47ce..50f0c47aa05 100644 --- a/checker/tests/nullness/Issue295.java +++ b/checker/tests/nullness/Issue295.java @@ -5,46 +5,46 @@ abstract class Issue295 { - static class Box { - T value; + static class Box { + T value; - Box(T value) { - this.value = value; + Box(T value) { + this.value = value; + } } - } - abstract MTL load(Factory p); + abstract MTL load(Factory p); - abstract class Factory { - abstract TF create(); - } + abstract class Factory { + abstract TF create(); + } - void f1(Factory> f) { - Box<@Nullable MT1> v = f.create(); - v = load(f); - } + void f1(Factory> f) { + Box<@Nullable MT1> v = f.create(); + v = load(f); + } - void f2(Factory> f) { - Box v = load(f); - } + void f2(Factory> f) { + Box v = load(f); + } - void f3(Factory> f) { - Box v = load(f); - } + void f3(Factory> f) { + Box v = load(f); + } - void f4(Factory> f) { - Box v = load(f); - } + void f4(Factory> f) { + Box v = load(f); + } - void f5(Factory> f) { - Box v = load(f); - } + void f5(Factory> f) { + Box v = load(f); + } - void f6(Factory> f) { - Box v = load(f); - } + void f6(Factory> f) { + Box v = load(f); + } - void f1noquals(Factory> f) { - Box v = load(f); - } + void f1noquals(Factory> f) { + Box v = load(f); + } } diff --git a/checker/tests/nullness/Issue296.java b/checker/tests/nullness/Issue296.java index b1bbf3eef77..889164d0fad 100644 --- a/checker/tests/nullness/Issue296.java +++ b/checker/tests/nullness/Issue296.java @@ -1,29 +1,30 @@ // Test case for Issue 296: // https://github.com/typetools/checker-framework/issues/296 -import java.util.Arrays; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Arrays; + // Note that with -AinvariantArrays we would get additional errors. public class Issue296 { - public static void f1(T[] a) { - @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); - // :: error: (argument.type.incompatible) - @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); - @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); - } + public static void f1(T[] a) { + @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); + // :: error: (argument.type.incompatible) + @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); + @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); + } - public static void f2(@NonNull T[] a) { - @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); - @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); - @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); - } + public static void f2(@NonNull T[] a) { + @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); + @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); + @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); + } - public static void f3(@Nullable T[] a) { - @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); - // :: error: (argument.type.incompatible) - @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); - @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); - } + public static void f3(@Nullable T[] a) { + @Nullable T[] r1 = Arrays.copyOf(a, a.length + 1); + // :: error: (argument.type.incompatible) + @Nullable T[] r2 = Arrays.<@NonNull T>copyOf(a, a.length + 1); + @Nullable T[] r3 = Arrays.<@Nullable T>copyOf(a, a.length + 1); + } } diff --git a/checker/tests/nullness/Issue3013.java b/checker/tests/nullness/Issue3013.java index 0611ddf03d7..94040e59dfc 100644 --- a/checker/tests/nullness/Issue3013.java +++ b/checker/tests/nullness/Issue3013.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; abstract class Issue3013 { - static class Nested { - Nested(Issue3013 list) { - for (Object o : list.asIterable()) {} + static class Nested { + Nested(Issue3013 list) { + for (Object o : list.asIterable()) {} + } } - } - abstract Iterable asIterable(); + abstract Iterable asIterable(); } diff --git a/checker/tests/nullness/Issue3015.java b/checker/tests/nullness/Issue3015.java index 275600618ec..d4a503d66c2 100644 --- a/checker/tests/nullness/Issue3015.java +++ b/checker/tests/nullness/Issue3015.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/3015 class Issue3015 { - void acquire() { - String s = ""; + void acquire() { + String s = ""; - try { - return; - } finally { - try { - signal(); - } finally { - s.toString(); - } + try { + return; + } finally { + try { + signal(); + } finally { + s.toString(); + } + } } - } - void signal() {} + void signal() {} } diff --git a/checker/tests/nullness/Issue3020.java b/checker/tests/nullness/Issue3020.java index 4d10b8ab139..b8d735d9eba 100644 --- a/checker/tests/nullness/Issue3020.java +++ b/checker/tests/nullness/Issue3020.java @@ -1,17 +1,17 @@ enum Issue3020 { - INSTANCE; + INSTANCE; - void retrieveConstant() { - Class theClass = Issue3020.class; - // :: error: (accessing.nullable) - Object unused = passThrough(theClass.getEnumConstants())[0]; - } + void retrieveConstant() { + Class theClass = Issue3020.class; + // :: error: (accessing.nullable) + Object unused = passThrough(theClass.getEnumConstants())[0]; + } - void nonNullArray(String[] p) { - Object unused = passThrough(p)[0]; - } + void nonNullArray(String[] p) { + Object unused = passThrough(p)[0]; + } - T passThrough(T t) { - return t; - } + T passThrough(T t) { + return t; + } } diff --git a/checker/tests/nullness/Issue3022.java b/checker/tests/nullness/Issue3022.java index dd890d504ed..08fbc24ab2e 100644 --- a/checker/tests/nullness/Issue3022.java +++ b/checker/tests/nullness/Issue3022.java @@ -1,11 +1,11 @@ abstract class Super3022 { - class Wrapper { - Wrapper(T key) {} - } + class Wrapper { + Wrapper(T key) {} + } } class Sub3022 extends Super3022 { - void wrap(K key) { - new Wrapper(key); - } + void wrap(K key) { + new Wrapper(key); + } } diff --git a/checker/tests/nullness/Issue3033.java b/checker/tests/nullness/Issue3033.java index aeec5926a9a..c636dbd9b80 100644 --- a/checker/tests/nullness/Issue3033.java +++ b/checker/tests/nullness/Issue3033.java @@ -3,25 +3,25 @@ public class Issue3033 { - class Test { + class Test { - void main() { - Test obj1 = new Test(); + void main() { + Test obj1 = new Test(); - // No error as no explicit @Nullable or @NonNull annotation is given. - if (obj1 instanceof Test) { - Test obj2 = new Test(); + // No error as no explicit @Nullable or @NonNull annotation is given. + if (obj1 instanceof Test) { + Test obj2 = new Test(); - // :: error: (instanceof.nullable) - if (obj1 instanceof @Nullable Test) { - obj1 = null; - } + // :: error: (instanceof.nullable) + if (obj1 instanceof @Nullable Test) { + obj1 = null; + } - // :: warning: (instanceof.nonnull.redundant) - if (obj2 instanceof @NonNull Test) { - obj2 = obj1; + // :: warning: (instanceof.nonnull.redundant) + if (obj2 instanceof @NonNull Test) { + obj2 = obj1; + } + } } - } } - } } diff --git a/checker/tests/nullness/Issue306.java b/checker/tests/nullness/Issue306.java index 65a1dad52ba..5c81748c455 100644 --- a/checker/tests/nullness/Issue306.java +++ b/checker/tests/nullness/Issue306.java @@ -7,37 +7,37 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue306 { - @MonotonicNonNull Object x; - - static T check(T var) { - return var; - } - - void fakeMethod() { - // @MonotonicNonNull is not reflexive. - // However, it is the most specific type argument inferred for check. Therefore, an error is - // raised. - // We need a mechanism to not consider a qualifier in type inference. - check(x); - - // Ugly way around the problem: - Issue306.<@Nullable Object>check(x); - - // The following error has to be raised: from the signature it is not guaranteed that the - // parameter is returned. - // :: error: (monotonic.type.incompatible) - x = check(x); - } - - @MonotonicNonNull Object y; - - void realError(@MonotonicNonNull Object p) { - // :: error: (monotonic.type.incompatible) - x = y; - // :: error: (monotonic.type.incompatible) - x = p; - // It would be nice not to raise the following error. - // :: error: (monotonic.type.incompatible) - x = x; - } + @MonotonicNonNull Object x; + + static T check(T var) { + return var; + } + + void fakeMethod() { + // @MonotonicNonNull is not reflexive. + // However, it is the most specific type argument inferred for check. Therefore, an error is + // raised. + // We need a mechanism to not consider a qualifier in type inference. + check(x); + + // Ugly way around the problem: + Issue306.<@Nullable Object>check(x); + + // The following error has to be raised: from the signature it is not guaranteed that the + // parameter is returned. + // :: error: (monotonic.type.incompatible) + x = check(x); + } + + @MonotonicNonNull Object y; + + void realError(@MonotonicNonNull Object p) { + // :: error: (monotonic.type.incompatible) + x = y; + // :: error: (monotonic.type.incompatible) + x = p; + // It would be nice not to raise the following error. + // :: error: (monotonic.type.incompatible) + x = x; + } } diff --git a/checker/tests/nullness/Issue308.java b/checker/tests/nullness/Issue308.java index 6af58df8294..5b00672f072 100644 --- a/checker/tests/nullness/Issue308.java +++ b/checker/tests/nullness/Issue308.java @@ -1,23 +1,24 @@ -import javax.validation.constraints.NotNull; import org.checkerframework.checker.nullness.qual.*; +import javax.validation.constraints.NotNull; + // @skip-test The clean-room implementation of javax.validation.constraints.NotNull is not in this // repository because Oracle claims a license over its specification and is lawsuit-happy. public class Issue308 { - @NonNull Object nonnull = new Object(); - @Nullable Object nullable; + @NonNull Object nonnull = new Object(); + @Nullable Object nullable; - @NotNull(message = "hi") Object notnull1 = new Object(); + @NotNull(message = "hi") Object notnull1 = new Object(); - @NotNull(groups = {Object.class}) Object notnull2 = new Object(); + @NotNull(groups = {Object.class}) Object notnull2 = new Object(); - void foo() { - nonnull = notnull1; - notnull2 = nonnull; - notnull1 = notnull2; + void foo() { + nonnull = notnull1; + notnull2 = nonnull; + notnull1 = notnull2; - nullable = notnull1; - nullable = notnull2; - } + nullable = notnull1; + nullable = notnull2; + } } diff --git a/checker/tests/nullness/Issue3150.java b/checker/tests/nullness/Issue3150.java index 45e9902734b..b350a6dea0f 100644 --- a/checker/tests/nullness/Issue3150.java +++ b/checker/tests/nullness/Issue3150.java @@ -4,26 +4,26 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3150 { - void foo(@Nullable Object nble, @NonNull Object nn) { - // :: error: (type.arguments.not.inferred) - requireNonNull1(null); - // :: error: (type.arguments.not.inferred) - requireNonNull1(nble); - requireNonNull1("hello"); - requireNonNull1(nn); - // :: error: (argument.type.incompatible) - requireNonNull2(null); - // :: error: (argument.type.incompatible) - requireNonNull2(nble); - requireNonNull1("hello"); - requireNonNull1(nn); - } + void foo(@Nullable Object nble, @NonNull Object nn) { + // :: error: (type.arguments.not.inferred) + requireNonNull1(null); + // :: error: (type.arguments.not.inferred) + requireNonNull1(nble); + requireNonNull1("hello"); + requireNonNull1(nn); + // :: error: (argument.type.incompatible) + requireNonNull2(null); + // :: error: (argument.type.incompatible) + requireNonNull2(nble); + requireNonNull1("hello"); + requireNonNull1(nn); + } - public static T requireNonNull1(T obj) { - return obj; - } + public static T requireNonNull1(T obj) { + return obj; + } - public static @NonNull T requireNonNull2(@NonNull T obj) { - return obj; - } + public static @NonNull T requireNonNull2(@NonNull T obj) { + return obj; + } } diff --git a/checker/tests/nullness/Issue328.java b/checker/tests/nullness/Issue328.java index 94febdba971..81c902e1726 100644 --- a/checker/tests/nullness/Issue328.java +++ b/checker/tests/nullness/Issue328.java @@ -1,17 +1,18 @@ -import java.util.Map; import org.checkerframework.checker.nullness.qual.*; +import java.util.Map; + public class Issue328 { - public static void m(Map a, Map b, Object ka, Object kb) { - if (a.containsKey(ka)) { - @NonNull Object i = a.get(ka); // OK - } - if (b.containsKey(kb)) { - @NonNull Object i = b.get(kb); // OK - } - if (a.containsKey(ka) && b.containsKey(kb)) { - @NonNull Object i = a.get(ka); // ERROR - @NonNull Object j = b.get(kb); // ERROR + public static void m(Map a, Map b, Object ka, Object kb) { + if (a.containsKey(ka)) { + @NonNull Object i = a.get(ka); // OK + } + if (b.containsKey(kb)) { + @NonNull Object i = b.get(kb); // OK + } + if (a.containsKey(ka) && b.containsKey(kb)) { + @NonNull Object i = a.get(ka); // ERROR + @NonNull Object j = b.get(kb); // ERROR + } } - } } diff --git a/checker/tests/nullness/Issue331.java b/checker/tests/nullness/Issue331.java index efb22630a18..d3e4f043f20 100644 --- a/checker/tests/nullness/Issue331.java +++ b/checker/tests/nullness/Issue331.java @@ -4,8 +4,8 @@ import java.util.List; class TestTeranry { - void foo(boolean b, List res) { - Object o = b ? "x" : (b ? "y" : "z"); - res.add(o); - } + void foo(boolean b, List res) { + Object o = b ? "x" : (b ? "y" : "z"); + res.add(o); + } } diff --git a/checker/tests/nullness/Issue3349.java b/checker/tests/nullness/Issue3349.java index 7cd64d72966..7d38d5929b3 100644 --- a/checker/tests/nullness/Issue3349.java +++ b/checker/tests/nullness/Issue3349.java @@ -1,10 +1,11 @@ -import java.io.Serializable; import org.checkerframework.checker.nullness.qual.*; +import java.io.Serializable; + // :: warning: (explicit.annotation.ignored) public class Issue3349 { - void foo(T p1) { - @NonNull Serializable s = p1; - @NonNull Object o = p1; - } + void foo(T p1) { + @NonNull Serializable s = p1; + @NonNull Object o = p1; + } } diff --git a/checker/tests/nullness/Issue338.java b/checker/tests/nullness/Issue338.java index d7c22783ae1..f2f1e66099d 100644 --- a/checker/tests/nullness/Issue338.java +++ b/checker/tests/nullness/Issue338.java @@ -1,9 +1,9 @@ interface Foo338 { - Class get(); + Class get(); } public class Issue338 { - static void m2(Foo338 foo) { - Class clazz = foo.get(); - } + static void m2(Foo338 foo) { + Class clazz = foo.get(); + } } diff --git a/checker/tests/nullness/Issue3443.java b/checker/tests/nullness/Issue3443.java index 0e369b6a8bb..ce171e4c342 100644 --- a/checker/tests/nullness/Issue3443.java +++ b/checker/tests/nullness/Issue3443.java @@ -1,18 +1,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3443 { - static > Supplier3443 passThrough(T t) { - // :: error: (return.type.incompatible) - return t; - } + static > Supplier3443 passThrough(T t) { + // :: error: (return.type.incompatible) + return t; + } - public static void main(String[] args) { - Supplier3443<@Nullable String> s1 = () -> null; - Supplier3443 s2 = passThrough(s1); - s2.get().toString(); - } + public static void main(String[] args) { + Supplier3443<@Nullable String> s1 = () -> null; + Supplier3443 s2 = passThrough(s1); + s2.get().toString(); + } } interface Supplier3443 { - T get(); + T get(); } diff --git a/checker/tests/nullness/Issue355.java b/checker/tests/nullness/Issue355.java index 3ade143de9c..5a6849917e9 100644 --- a/checker/tests/nullness/Issue355.java +++ b/checker/tests/nullness/Issue355.java @@ -1,58 +1,59 @@ // Test case for Issue 355: // https://github.com/typetools/checker-framework/issues/355 -import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class Issue355 { - static @NonNull T checkNotNull(@Nullable T sample) { - throw new RuntimeException(); - } + static @NonNull T checkNotNull(@Nullable T sample) { + throw new RuntimeException(); + } - void m(List xs) { - for (String x : checkNotNull(xs)) {} - } + void m(List xs) { + for (String x : checkNotNull(xs)) {} + } } class Issue355b { - static T checkNotNull(T sample) { - throw new RuntimeException(); - } + static T checkNotNull(T sample) { + throw new RuntimeException(); + } - void m(List xs) { - for (Object x : checkNotNull(xs)) {} - } + void m(List xs) { + for (Object x : checkNotNull(xs)) {} + } } class Issue355c { - static T checkNotNull(@NonNull T sample) { - throw new RuntimeException(); - } + static T checkNotNull(@NonNull T sample) { + throw new RuntimeException(); + } - void m(List xs) { - for (Object x : checkNotNull(xs)) {} - } + void m(List xs) { + for (Object x : checkNotNull(xs)) {} + } } class Issue355d { - static @Nullable T checkNotNull(@NonNull T sample) { - throw new RuntimeException(); - } - - void m(List xs) { - // :: error: (iterating.over.nullable) - for (Object x : checkNotNull(xs)) {} - } + static @Nullable T checkNotNull(@NonNull T sample) { + throw new RuntimeException(); + } + + void m(List xs) { + // :: error: (iterating.over.nullable) + for (Object x : checkNotNull(xs)) {} + } } class Issue355e { - static @NonNull T checkNotNull(@NonNull T sample) { - throw new RuntimeException(); - } - - void m(@Nullable List xs) { - // :: error: (argument.type.incompatible) - for (Object x : checkNotNull(xs)) {} - } + static @NonNull T checkNotNull(@NonNull T sample) { + throw new RuntimeException(); + } + + void m(@Nullable List xs) { + // :: error: (argument.type.incompatible) + for (Object x : checkNotNull(xs)) {} + } } diff --git a/checker/tests/nullness/Issue3614.java b/checker/tests/nullness/Issue3614.java index 707d609a8c7..17e278775fb 100644 --- a/checker/tests/nullness/Issue3614.java +++ b/checker/tests/nullness/Issue3614.java @@ -6,112 +6,112 @@ public class Issue3614 { - public static @Nullable Boolean m1(@PolyNull Boolean b) { - return (b == null) ? b : b; - } - - public static @NonNull Boolean m2(@PolyNull Boolean b) { - return (b == null) ? Boolean.TRUE : !b; - } - - public static @PolyNull Boolean m3(@PolyNull Boolean b) { - return (b == null) ? null : Boolean.TRUE; - } - - public static @PolyNull Boolean m4(@PolyNull Boolean b) { - return (b == null) ? null : b; - } - - public static @PolyNull Boolean m5(@PolyNull Boolean b) { - return (b == null) ? b : Boolean.TRUE; - } - - public static @PolyNull Boolean not1(@PolyNull Boolean b) { - return (b == null) ? null : !b; - } - - public static @PolyNull Boolean not2(@PolyNull Boolean b) { - // :: error: (unboxing.of.nullable) - return (b == null) ? b : !b; - } - - public static @PolyNull Boolean not3(@PolyNull Boolean b) { - if (b == null) { - return null; - } else { - return !b; - } - } - - public static <@Nullable T> T of1(T a) { - return a == null ? null : a; - } - - public static <@Nullable T> T of2(T a) { - if (a == null) { - return null; - } else { - return a; - } - } - - public static @PolyNull Integer plus1(@PolyNull Integer b0, @PolyNull Integer b1) { - return (b0 == null || b1 == null) ? null : (b0 + b1); - } - - public static @PolyNull Integer plus2(@PolyNull Integer b0, @PolyNull Integer b1) { - if (b0 == null || b1 == null) { - return null; - } else { - return b0 + b1; - } - } - - public static @PolyNull Integer plus3(@PolyNull Integer a, @PolyNull Integer b) { - if (a == null) { - return null; - } - if (b == null) { - return null; - } - return a + b; - } - - public static @PolyNull Integer plus1Err(@PolyNull Integer b0, @PolyNull Integer b1) { - // :: error: (return.type.incompatible) :: error: (unboxing.of.nullable) - return (b0 == null) ? null : (b0 + b1); - } - - public static @PolyNull Integer plus2Err(@PolyNull Integer b0, @PolyNull Integer b1) { - if (b0 == null) { - return null; - } else { - // :: error: (unboxing.of.nullable) - return b0 + b1; - } - } - - public static @PolyNull Integer plus3Err(@PolyNull Integer a, @PolyNull Integer b) { - if (a == null) { - return null; - } - // :: error: (unboxing.of.nullable) - return a + b; - } - - public static @PolyNull /*("elt")*/ String @PolyNull /*("container")*/ [] typeArray( - @PolyNull /*("elt")*/ Object @PolyNull /*("container")*/ [] seq) { - if (seq == null) { - return null; - } - @PolyNull /*("elt")*/ String[] retval = new @PolyNull /*("elt")*/ String[seq.length]; - for (int i = 0; i < seq.length; i++) { - if (seq[i] == null) { - retval[i] = null; - } else { - retval[i] = seq[i].getClass().toString(); - } - } - return retval; - } + public static @Nullable Boolean m1(@PolyNull Boolean b) { + return (b == null) ? b : b; + } + + public static @NonNull Boolean m2(@PolyNull Boolean b) { + return (b == null) ? Boolean.TRUE : !b; + } + + public static @PolyNull Boolean m3(@PolyNull Boolean b) { + return (b == null) ? null : Boolean.TRUE; + } + + public static @PolyNull Boolean m4(@PolyNull Boolean b) { + return (b == null) ? null : b; + } + + public static @PolyNull Boolean m5(@PolyNull Boolean b) { + return (b == null) ? b : Boolean.TRUE; + } + + public static @PolyNull Boolean not1(@PolyNull Boolean b) { + return (b == null) ? null : !b; + } + + public static @PolyNull Boolean not2(@PolyNull Boolean b) { + // :: error: (unboxing.of.nullable) + return (b == null) ? b : !b; + } + + public static @PolyNull Boolean not3(@PolyNull Boolean b) { + if (b == null) { + return null; + } else { + return !b; + } + } + + public static <@Nullable T> T of1(T a) { + return a == null ? null : a; + } + + public static <@Nullable T> T of2(T a) { + if (a == null) { + return null; + } else { + return a; + } + } + + public static @PolyNull Integer plus1(@PolyNull Integer b0, @PolyNull Integer b1) { + return (b0 == null || b1 == null) ? null : (b0 + b1); + } + + public static @PolyNull Integer plus2(@PolyNull Integer b0, @PolyNull Integer b1) { + if (b0 == null || b1 == null) { + return null; + } else { + return b0 + b1; + } + } + + public static @PolyNull Integer plus3(@PolyNull Integer a, @PolyNull Integer b) { + if (a == null) { + return null; + } + if (b == null) { + return null; + } + return a + b; + } + + public static @PolyNull Integer plus1Err(@PolyNull Integer b0, @PolyNull Integer b1) { + // :: error: (return.type.incompatible) :: error: (unboxing.of.nullable) + return (b0 == null) ? null : (b0 + b1); + } + + public static @PolyNull Integer plus2Err(@PolyNull Integer b0, @PolyNull Integer b1) { + if (b0 == null) { + return null; + } else { + // :: error: (unboxing.of.nullable) + return b0 + b1; + } + } + + public static @PolyNull Integer plus3Err(@PolyNull Integer a, @PolyNull Integer b) { + if (a == null) { + return null; + } + // :: error: (unboxing.of.nullable) + return a + b; + } + + public static @PolyNull /*("elt")*/ String @PolyNull /*("container")*/ [] typeArray( + @PolyNull /*("elt")*/ Object @PolyNull /*("container")*/ [] seq) { + if (seq == null) { + return null; + } + @PolyNull /*("elt")*/ String[] retval = new @PolyNull /*("elt")*/ String[seq.length]; + for (int i = 0; i < seq.length; i++) { + if (seq[i] == null) { + retval[i] = null; + } else { + retval[i] = seq[i].getClass().toString(); + } + } + return retval; + } } diff --git a/checker/tests/nullness/Issue3622.java b/checker/tests/nullness/Issue3622.java index 48414cb6438..23b57dc5bd4 100644 --- a/checker/tests/nullness/Issue3622.java +++ b/checker/tests/nullness/Issue3622.java @@ -1,117 +1,118 @@ // Test case for https://tinyurl.com/cfissue/3622 -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class Issue3622 { - public class ImmutableIntList1 { + public class ImmutableIntList1 { - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof ImmutableIntList1) { - return true; - } else { - return obj instanceof List; - } + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof ImmutableIntList1) { + return true; + } else { + return obj instanceof List; + } + } } - } - public class ImmutableIntList2 { + public class ImmutableIntList2 { - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList2; + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList2; + } } - } - public class ImmutableIntList3 { + public class ImmutableIntList3 { - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof ImmutableIntList3) { - return true; - } else { - return false; - } + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof ImmutableIntList3) { + return true; + } else { + return false; + } + } } - } - public class ImmutableIntList4 { + public class ImmutableIntList4 { - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList4 ? true : obj instanceof List; + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList4 ? true : obj instanceof List; + } } - } - public class ImmutableIntList5 { + public class ImmutableIntList5 { - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList5 - ? obj instanceof ImmutableIntList5 - : obj instanceof ImmutableIntList5; + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList5 + ? obj instanceof ImmutableIntList5 + : obj instanceof ImmutableIntList5; + } } - } - public class ImmutableIntList6 { + public class ImmutableIntList6 { - @Override - public boolean equals(@Nullable Object obj) { - return true ? obj instanceof ImmutableIntList6 : obj instanceof ImmutableIntList6; + @Override + public boolean equals(@Nullable Object obj) { + return true ? obj instanceof ImmutableIntList6 : obj instanceof ImmutableIntList6; + } } - } - public class ImmutableIntList7 { - @Override - public boolean equals(@Nullable Object obj) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return (obj instanceof ImmutableIntList7) ? true : !(obj instanceof List); + public class ImmutableIntList7 { + @Override + public boolean equals(@Nullable Object obj) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return (obj instanceof ImmutableIntList7) ? true : !(obj instanceof List); + } } - } - - public class ImmutableIntList8 { - - @Override - // The ternary expression has the condition of literal `true`, so the false-expression is - // unreachable. However the store in the unreachable false-branch (where `obj` is @Nullable) - // is propagated to the merge point, which causes the false positive. - // TODO: prune the dead branch like https://github.com/typetools/checker-framework/pull/3389 - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - public boolean equals(@Nullable Object obj) { - return true ? obj instanceof ImmutableIntList8 : false; + + public class ImmutableIntList8 { + + @Override + // The ternary expression has the condition of literal `true`, so the false-expression is + // unreachable. However the store in the unreachable false-branch (where `obj` is @Nullable) + // is propagated to the merge point, which causes the false positive. + // TODO: prune the dead branch like https://github.com/typetools/checker-framework/pull/3389 + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + public boolean equals(@Nullable Object obj) { + return true ? obj instanceof ImmutableIntList8 : false; + } } - } - - public class ImmutableIntList9 { - - @Override - // The false expression of the tenary expression is literal `false`. In this case only the - // else-store after `false` should be propagated to the else-store of the merge point. - // TODO: adapt the way of store propagation for boolean variables. i.e. for `true`, only - // then-store is propagated; and for `false`, only else-store is propagated. - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - public boolean equals(@Nullable Object obj) { - return obj instanceof ImmutableIntList9 ? true : false; + + public class ImmutableIntList9 { + + @Override + // The false expression of the tenary expression is literal `false`. In this case only the + // else-store after `false` should be propagated to the else-store of the merge point. + // TODO: adapt the way of store propagation for boolean variables. i.e. for `true`, only + // then-store is propagated; and for `false`, only else-store is propagated. + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + public boolean equals(@Nullable Object obj) { + return obj instanceof ImmutableIntList9 ? true : false; + } } - } - - public class ImmutableIntList10 { - - @Override - // The false positive is because in the Nullness analysis the values of boolean variables - // are not stored, therefore the relation between boolean variable `b` and `obj` is not - // known - @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") - public boolean equals(@Nullable Object obj) { - boolean b; - if (obj instanceof ImmutableIntList10) { - b = true; - } else { - b = false; - } - return b; + + public class ImmutableIntList10 { + + @Override + // The false positive is because in the Nullness analysis the values of boolean variables + // are not stored, therefore the relation between boolean variable `b` and `obj` is not + // known + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") + public boolean equals(@Nullable Object obj) { + boolean b; + if (obj instanceof ImmutableIntList10) { + b = true; + } else { + b = false; + } + return b; + } } - } } diff --git a/checker/tests/nullness/Issue3631.java b/checker/tests/nullness/Issue3631.java index e88ce736de7..4704ec06e9e 100644 --- a/checker/tests/nullness/Issue3631.java +++ b/checker/tests/nullness/Issue3631.java @@ -2,17 +2,17 @@ public class Issue3631 { - void f(Object otherArg) { - // Casts aren't a supported JavaExpression. - // :: error: (contracts.precondition.not.satisfied) - ((Issue3631Helper) otherArg).m(); - } + void f(Object otherArg) { + // Casts aren't a supported JavaExpression. + // :: error: (contracts.precondition.not.satisfied) + ((Issue3631Helper) otherArg).m(); + } } class Issue3631Helper { - String type = "foo"; + String type = "foo"; - @RequiresNonNull("type") - void m() {} + @RequiresNonNull("type") + void m() {} } diff --git a/checker/tests/nullness/Issue3681.java b/checker/tests/nullness/Issue3681.java index b4a7f8d29f4..6637dedb049 100644 --- a/checker/tests/nullness/Issue3681.java +++ b/checker/tests/nullness/Issue3681.java @@ -4,46 +4,46 @@ package org.jro.tests.checkerfwk.utils; public class Issue3681 { - interface PartialFunction { - R apply(T t); - - boolean isDefinedAt(T value); - } - - interface Either { - R get(); - - boolean isRight(); - } - - public static PartialFunction, R> createKeepRight() { - return new PartialFunction<>() { - - @Override - public R apply(final Either either) { - return either.get(); - } - - @Override - public boolean isDefinedAt(final Either value) { - return value.isRight(); - } - }; - } - - public static - PartialFunction, ? extends R> createRCovariantKeepRight() { - return new PartialFunction<>() { - - @Override - public R apply(final Either either) { - return either.get(); - } - - @Override - public boolean isDefinedAt(final Either value) { - return value.isRight(); - } - }; - } + interface PartialFunction { + R apply(T t); + + boolean isDefinedAt(T value); + } + + interface Either { + R get(); + + boolean isRight(); + } + + public static PartialFunction, R> createKeepRight() { + return new PartialFunction<>() { + + @Override + public R apply(final Either either) { + return either.get(); + } + + @Override + public boolean isDefinedAt(final Either value) { + return value.isRight(); + } + }; + } + + public static + PartialFunction, ? extends R> createRCovariantKeepRight() { + return new PartialFunction<>() { + + @Override + public R apply(final Either either) { + return either.get(); + } + + @Override + public boolean isDefinedAt(final Either value) { + return value.isRight(); + } + }; + } } diff --git a/checker/tests/nullness/Issue369.java b/checker/tests/nullness/Issue369.java index ae1acdac700..165d687ce38 100644 --- a/checker/tests/nullness/Issue369.java +++ b/checker/tests/nullness/Issue369.java @@ -6,7 +6,7 @@ import java.util.stream.Stream; public class Issue369 { - static void test(Stream stream) { - stream.collect(toSet()); - } + static void test(Stream stream) { + stream.collect(toSet()); + } } diff --git a/checker/tests/nullness/Issue370.java b/checker/tests/nullness/Issue370.java index fae04fa74bc..293556fb5fb 100644 --- a/checker/tests/nullness/Issue370.java +++ b/checker/tests/nullness/Issue370.java @@ -5,7 +5,7 @@ public class Issue370 { - Iterable foo() { - return Collections.emptyList(); - } + Iterable foo() { + return Collections.emptyList(); + } } diff --git a/checker/tests/nullness/Issue372.java b/checker/tests/nullness/Issue372.java index f80773572ca..8fcdb417c66 100644 --- a/checker/tests/nullness/Issue372.java +++ b/checker/tests/nullness/Issue372.java @@ -1,15 +1,16 @@ // Test case for Issue 372: // https://github.com/typetools/checker-framework/issues/372 +import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; public class Issue372 { - private final Map labels = new HashMap<>(); + private final Map labels = new HashMap<>(); - @EnsuresKeyFor(value = "#1", map = "labels") - void foo(String v) { - labels.put(v, ""); - } + @EnsuresKeyFor(value = "#1", map = "labels") + void foo(String v) { + labels.put(v, ""); + } } diff --git a/checker/tests/nullness/Issue3754.java b/checker/tests/nullness/Issue3754.java index b26d7263265..1b669bd385b 100644 --- a/checker/tests/nullness/Issue3754.java +++ b/checker/tests/nullness/Issue3754.java @@ -1,19 +1,19 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue3754 { - interface Supplier { - U get(); - } + interface Supplier { + U get(); + } - Object x(Supplier bar) { - return bar.get(); - } + Object x(Supplier bar) { + return bar.get(); + } - interface Supplier2 { - U get(); - } + interface Supplier2 { + U get(); + } - Object x(Supplier2 bar) { - return bar.get(); - } + Object x(Supplier2 bar) { + return bar.get(); + } } diff --git a/checker/tests/nullness/Issue376.java b/checker/tests/nullness/Issue376.java index 4a918ee6a85..34219f1d53a 100644 --- a/checker/tests/nullness/Issue376.java +++ b/checker/tests/nullness/Issue376.java @@ -3,9 +3,9 @@ public class Issue376 { - interface I {} + interface I {} - & I> void m(Class clazz, String name) { - I i = Enum.valueOf(clazz, name); - } + & I> void m(Class clazz, String name) { + I i = Enum.valueOf(clazz, name); + } } diff --git a/checker/tests/nullness/Issue3764.java b/checker/tests/nullness/Issue3764.java index 3c704c9a378..15847c65ff0 100644 --- a/checker/tests/nullness/Issue3764.java +++ b/checker/tests/nullness/Issue3764.java @@ -1,27 +1,28 @@ -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.Collector; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collector; + @DefaultQualifier(locations = TypeUseLocation.LOWER_BOUND, value = KeyForBottom.class) class Issue3764 { - static void use(@Nullable Collector collector) {} + static void use(@Nullable Collector collector) {} - static @Nullable Collector> calc( - Function keyFunction, - Function valueFunction, - BiFunction mergeFunction) { - return null; - } + static @Nullable Collector> calc( + Function keyFunction, + Function valueFunction, + BiFunction mergeFunction) { + return null; + } - void foo(Function f) { - // No error when using a lambda - use(calc(f, f, (a, b) -> Math.max(a, b))); - // Error when using a method reference - use(calc(f, f, Math::max)); - } + void foo(Function f) { + // No error when using a lambda + use(calc(f, f, (a, b) -> Math.max(a, b))); + // Error when using a method reference + use(calc(f, f, Math::max)); + } } diff --git a/checker/tests/nullness/Issue3792.java b/checker/tests/nullness/Issue3792.java index 4f8f0b99fbf..d63f2b1a56e 100644 --- a/checker/tests/nullness/Issue3792.java +++ b/checker/tests/nullness/Issue3792.java @@ -2,13 +2,15 @@ import java.util.NavigableMap; public abstract class Issue3792 { - static class Instant {} + static class Instant {} - void method( - NavigableMap> contents, Instant minTimestamp, Instant limitTimestamp) { - contents.subMap(minTimestamp, true, limitTimestamp, false).entrySet().stream() - .flatMap(e -> e.getValue().stream().map(v -> of(v, e.getKey()))); - } + void method( + NavigableMap> contents, + Instant minTimestamp, + Instant limitTimestamp) { + contents.subMap(minTimestamp, true, limitTimestamp, false).entrySet().stream() + .flatMap(e -> e.getValue().stream().map(v -> of(v, e.getKey()))); + } - abstract Object of(T v, Instant key); + abstract Object of(T v, Instant key); } diff --git a/checker/tests/nullness/Issue3845.java b/checker/tests/nullness/Issue3845.java index b30f64c37fb..8decaf633fa 100644 --- a/checker/tests/nullness/Issue3845.java +++ b/checker/tests/nullness/Issue3845.java @@ -5,26 +5,26 @@ import org.checkerframework.framework.qual.TypeUseLocation; public class Issue3845 { - static class Holder { - final T value; + static class Holder { + final T value; - Holder(T value) { - this.value = value; + Holder(T value) { + this.value = value; + } } - } - interface HolderSupplier> { - H get(); - } + interface HolderSupplier> { + H get(); + } - @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.ALL) - static class DefaultClash { + @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.ALL) + static class DefaultClash { - Object go(HolderSupplier s) { - if (s != null) { - return s.get(); - } - return ""; + Object go(HolderSupplier s) { + if (s != null) { + return s.get(); + } + return ""; + } } - } } diff --git a/checker/tests/nullness/Issue3850.java b/checker/tests/nullness/Issue3850.java index 57c053513a9..056dbb9eb21 100644 --- a/checker/tests/nullness/Issue3850.java +++ b/checker/tests/nullness/Issue3850.java @@ -2,14 +2,14 @@ public class Issue3850 { - private static Iterable<@PolyNull String> toPos(Iterable nodes) { - // :: error: (return.type.incompatible) - return transform(nodes, node -> node == null ? null : node.toString()); - } + private static Iterable<@PolyNull String> toPos(Iterable nodes) { + // :: error: (return.type.incompatible) + return transform(nodes, node -> node == null ? null : node.toString()); + } - public static Iterable transform( - Iterable iterable, - java.util.function.Function function) { - throw new Error("implementation is irrelevant"); - } + public static Iterable transform( + Iterable iterable, + java.util.function.Function function) { + throw new Error("implementation is irrelevant"); + } } diff --git a/checker/tests/nullness/Issue388.java b/checker/tests/nullness/Issue388.java index 6712f9e3134..702eeb19016 100644 --- a/checker/tests/nullness/Issue388.java +++ b/checker/tests/nullness/Issue388.java @@ -4,15 +4,15 @@ import java.util.Map; public class Issue388 { - static class Holder { - static final String KEY = "key"; - } + static class Holder { + static final String KEY = "key"; + } - public String getOrDefault(Map map, String defaultValue) { - if (map.containsKey(Holder.KEY)) { - return map.get(Holder.KEY); - } else { - return defaultValue; + public String getOrDefault(Map map, String defaultValue) { + if (map.containsKey(Holder.KEY)) { + return map.get(Holder.KEY); + } else { + return defaultValue; + } } - } } diff --git a/checker/tests/nullness/Issue3884.java b/checker/tests/nullness/Issue3884.java index e59c071c49a..68609d9b1c9 100644 --- a/checker/tests/nullness/Issue3884.java +++ b/checker/tests/nullness/Issue3884.java @@ -1,18 +1,18 @@ interface Issue3884 { - String go(Kind kind); + String go(Kind kind); - Issue3884 FOO = - kind -> { - switch (kind) { - case A: - break; - } - return ""; - }; + Issue3884 FOO = + kind -> { + switch (kind) { + case A: + break; + } + return ""; + }; - enum Kind { - A, - B, - C; - } + enum Kind { + A, + B, + C; + } } diff --git a/checker/tests/nullness/Issue3888.java b/checker/tests/nullness/Issue3888.java index f999c50f304..585d3438e55 100644 --- a/checker/tests/nullness/Issue3888.java +++ b/checker/tests/nullness/Issue3888.java @@ -2,17 +2,17 @@ abstract class Issue3888 { - interface L {} + interface L {} - interface E {} + interface E {} - public interface F { - Y a(V v); - } + public interface F { + Y a(V v); + } - abstract void f(F f); + abstract void f(F f); - void c(F o) { - f((E vm) -> o.a(vm) == null); - } + void c(F o) { + f((E vm) -> o.a(vm) == null); + } } diff --git a/checker/tests/nullness/Issue391.java b/checker/tests/nullness/Issue391.java index e27c5eb3888..2a86823ef13 100644 --- a/checker/tests/nullness/Issue391.java +++ b/checker/tests/nullness/Issue391.java @@ -6,44 +6,44 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; class ClassA { - private @Nullable String value = null; + private @Nullable String value = null; - @EnsuresNonNull("value") - public void ensuresNonNull() { - value = ""; - } + @EnsuresNonNull("value") + public void ensuresNonNull() { + value = ""; + } - @RequiresNonNull("value") - public String getValue() { - return value; - } + @RequiresNonNull("value") + public String getValue() { + return value; + } } public class Issue391 { - ClassA field = new ClassA(); - - @RequiresNonNull("field.value") - void method() {} - - @EnsuresNonNull("field.value") - void ensuresNonNull() { - field.ensuresNonNull(); - } - - void method2() { - ClassA a = new ClassA(); - // :: error: (contracts.precondition.not.satisfied) - a.getValue(); - // :: error: (contracts.precondition.not.satisfied) - method(); - } - - void method3() { - ensuresNonNull(); - method(); - - ClassA a = new ClassA(); - a.ensuresNonNull(); - a.getValue(); - } + ClassA field = new ClassA(); + + @RequiresNonNull("field.value") + void method() {} + + @EnsuresNonNull("field.value") + void ensuresNonNull() { + field.ensuresNonNull(); + } + + void method2() { + ClassA a = new ClassA(); + // :: error: (contracts.precondition.not.satisfied) + a.getValue(); + // :: error: (contracts.precondition.not.satisfied) + method(); + } + + void method3() { + ensuresNonNull(); + method(); + + ClassA a = new ClassA(); + a.ensuresNonNull(); + a.getValue(); + } } diff --git a/checker/tests/nullness/Issue3929.java b/checker/tests/nullness/Issue3929.java index 574b196d259..ac6d4b37719 100644 --- a/checker/tests/nullness/Issue3929.java +++ b/checker/tests/nullness/Issue3929.java @@ -1,34 +1,35 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3929 { - public void endElement(MyClass3929 arg) { - for (Object o : arg.getKeys()) { - o.toString(); + public void endElement(MyClass3929 arg) { + for (Object o : arg.getKeys()) { + o.toString(); + } } - } - public void endElement(NullableMyClass3929 arg) { - for (Object o : arg.getKeys()) { - // TODO: add a conservative option to get a warning here - o.toString(); + public void endElement(NullableMyClass3929 arg) { + for (Object o : arg.getKeys()) { + // TODO: add a conservative option to get a warning here + o.toString(); + } } - } } class MyClass3929> { - public List getKeys() { - return new ArrayList<>(); - } + public List getKeys() { + return new ArrayList<>(); + } } // TODO: This is a false positive. // See https://github.com/typetools/checker-framework/issues/2174 // :: error: (type.argument.type.incompatible) class NullableMyClass3929> { - public List getKeys() { - return new ArrayList<>(); - } + public List getKeys() { + return new ArrayList<>(); + } } diff --git a/checker/tests/nullness/Issue3935.java b/checker/tests/nullness/Issue3935.java index 14a0a5e49ec..a180ae339bf 100644 --- a/checker/tests/nullness/Issue3935.java +++ b/checker/tests/nullness/Issue3935.java @@ -1,10 +1,10 @@ import android.annotation.Nullable; public class Issue3935 { - // Note: Nullable is a declaration annotation and applies to the array, not the array element. - private @Nullable byte[] data; + // Note: Nullable is a declaration annotation and applies to the array, not the array element. + private @Nullable byte[] data; - // Declaration annotations on primitives are ignored, but this should issue - // a nullness.on.primitive error. - @Nullable byte b; + // Declaration annotations on primitives are ignored, but this should issue + // a nullness.on.primitive error. + @Nullable byte b; } diff --git a/checker/tests/nullness/Issue3970.java b/checker/tests/nullness/Issue3970.java index abecf2801ed..5c7711fd70a 100644 --- a/checker/tests/nullness/Issue3970.java +++ b/checker/tests/nullness/Issue3970.java @@ -2,18 +2,18 @@ public class Issue3970 { - public interface InterfaceA> extends InterfaceB {} + public interface InterfaceA> extends InterfaceB {} - public interface InterfaceB> { - int f(); + public interface InterfaceB> { + int f(); - @Nullable T g(); - } + @Nullable T g(); + } - void t(InterfaceA a) { - if (a.f() == 1) { - // :: error: (assignment.type.incompatible) - InterfaceA a2 = a.g(); + void t(InterfaceA a) { + if (a.f() == 1) { + // :: error: (assignment.type.incompatible) + InterfaceA a2 = a.g(); + } } - } } diff --git a/checker/tests/nullness/Issue4007.java b/checker/tests/nullness/Issue4007.java index dd59d94892f..c85c937a29c 100644 --- a/checker/tests/nullness/Issue4007.java +++ b/checker/tests/nullness/Issue4007.java @@ -2,33 +2,34 @@ // @skip-test until the issue is fixed +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.List; import java.util.Optional; -import org.checkerframework.checker.nullness.qual.NonNull; final class Issue4007 { - Optional m1(List list) { - return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); - } - - Optional> m2(List list) { - return Optional.of(list.isEmpty() ? Optional.empty() : Optional.of(list.get(0))); - } - - Optional> m3(List list) { - return Optional.of( - list.isEmpty() ? Optional.<@NonNull String>empty() : Optional.of(list.get(0))); - } - - Optional> m4(List list) { - return Optional.of( - list.isEmpty() ? Optional.empty() : Optional.<@NonNull String>of(list.get(0))); - } - - Optional> m5(List list) { - return Optional.of( - list.isEmpty() - ? Optional.<@NonNull String>empty() - : Optional.<@NonNull String>of(list.get(0))); - } + Optional m1(List list) { + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); + } + + Optional> m2(List list) { + return Optional.of(list.isEmpty() ? Optional.empty() : Optional.of(list.get(0))); + } + + Optional> m3(List list) { + return Optional.of( + list.isEmpty() ? Optional.<@NonNull String>empty() : Optional.of(list.get(0))); + } + + Optional> m4(List list) { + return Optional.of( + list.isEmpty() ? Optional.empty() : Optional.<@NonNull String>of(list.get(0))); + } + + Optional> m5(List list) { + return Optional.of( + list.isEmpty() + ? Optional.<@NonNull String>empty() + : Optional.<@NonNull String>of(list.get(0))); + } } diff --git a/checker/tests/nullness/Issue411.java b/checker/tests/nullness/Issue411.java index 4e5cb14f0d5..6274162a797 100644 --- a/checker/tests/nullness/Issue411.java +++ b/checker/tests/nullness/Issue411.java @@ -5,26 +5,26 @@ public class Issue411 { - @MonotonicNonNull Object field1 = null; - final @Nullable Object field2 = null; + @MonotonicNonNull Object field1 = null; + final @Nullable Object field2 = null; - void m() { - if (field1 != null) { - new Object() { - void f() { - field1.toString(); + void m() { + if (field1 != null) { + new Object() { + void f() { + field1.toString(); + } + }; } - }; } - } - void n() { - if (field2 != null) { - new Object() { - void f() { - field2.toString(); + void n() { + if (field2 != null) { + new Object() { + void f() { + field2.toString(); + } + }; } - }; } - } } diff --git a/checker/tests/nullness/Issue414.java b/checker/tests/nullness/Issue414.java index 5da328b00dc..17e2b178292 100644 --- a/checker/tests/nullness/Issue414.java +++ b/checker/tests/nullness/Issue414.java @@ -1,54 +1,55 @@ // Test case for Issue 414. // https://github.com/typetools/checker-framework/issues/414 +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.qual.KeyFor; public class Issue414 { - void simple(String s) { - Map mymap = new HashMap<>(); - mymap.put(s, 1); - @KeyFor("mymap") String s2 = s; - } + void simple(String s) { + Map mymap = new HashMap<>(); + mymap.put(s, 1); + @KeyFor("mymap") String s2 = s; + } - Map someField = new HashMap<>(); + Map someField = new HashMap<>(); - void semiSimple(@KeyFor("this.someField") String s) { - Map mymap = new HashMap<>(); - mymap.put(s, 1); - @KeyFor({"this.someField", "mymap"}) String s2 = s; - } + void semiSimple(@KeyFor("this.someField") String s) { + Map mymap = new HashMap<>(); + mymap.put(s, 1); + @KeyFor({"this.someField", "mymap"}) String s2 = s; + } - void dominatorsNoGenerics(Map preds) { + void dominatorsNoGenerics(Map preds) { - Map dom = new HashMap<>(); - @KeyFor({"preds", "dom"}) String root; + Map dom = new HashMap<>(); + @KeyFor({"preds", "dom"}) String root; - List<@KeyFor({"preds", "dom"}) String> roots = new ArrayList(); + List<@KeyFor({"preds", "dom"}) String> roots = new ArrayList(); - for (String node : preds.keySet()) { - dom.put(node, 1); - root = node; - roots.add(node); + for (String node : preds.keySet()) { + dom.put(node, 1); + root = node; + roots.add(node); + } } - } - void dominators(Map> preds) { + void dominators(Map> preds) { - Map dom = new HashMap<>(); + Map dom = new HashMap<>(); - @KeyFor({"preds", "dom"}) T root; + @KeyFor({"preds", "dom"}) T root; - List<@KeyFor({"preds", "dom"}) T> roots = new ArrayList(); + List<@KeyFor({"preds", "dom"}) T> roots = new ArrayList(); - for (T node : preds.keySet()) { - dom.put(node, 1); - root = node; - roots.add(node); + for (T node : preds.keySet()) { + dom.put(node, 1); + root = node; + roots.add(node); + } } - } } diff --git a/checker/tests/nullness/Issue415.java b/checker/tests/nullness/Issue415.java index b461d045b0a..22d74fcb654 100644 --- a/checker/tests/nullness/Issue415.java +++ b/checker/tests/nullness/Issue415.java @@ -1,45 +1,46 @@ // Test case for Issue 415 // https://github.com/typetools/checker-framework/issues/415 +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.dataflow.qual.*; + import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; -import org.checkerframework.dataflow.qual.*; public final class Issue415 { - Map mymap = new HashMap<>(); + Map mymap = new HashMap<>(); - // :: error: (expression.unparsable.type.invalid) - public static void usesField(Set<@KeyFor("this.mymap") String> keySet) { // :: error: (expression.unparsable.type.invalid) - new ArrayList<@KeyFor("this.mymap") String>(keySet); - } - - public static void usesParameter(Map m, Set<@KeyFor("#1") String> keySet) { - new ArrayList<@KeyFor("#1") String>(keySet); - } - - public static void sortedKeySet1(Map m, Set<@KeyFor("#1") String> keySet) { - new ArrayList<@KeyFor("#1") String>(keySet); - } - - public static void sortedKeySet2(Map m) { - Set<@KeyFor("#1") String> keySet = m.keySet(); - } - - public static void sortedKeySet3(Map m) { - Set<@KeyFor("#1") String> keySet = m.keySet(); - new ArrayList<@KeyFor("#1") String>(keySet); - } - - public static void sortedKeySet4(Map m) { - new ArrayList<@KeyFor("#1") String>(m.keySet()); - } - - public static , V> void sortedKeySet(Map m) { - new ArrayList<@KeyFor("#1") K>(m.keySet()); - } + public static void usesField(Set<@KeyFor("this.mymap") String> keySet) { + // :: error: (expression.unparsable.type.invalid) + new ArrayList<@KeyFor("this.mymap") String>(keySet); + } + + public static void usesParameter(Map m, Set<@KeyFor("#1") String> keySet) { + new ArrayList<@KeyFor("#1") String>(keySet); + } + + public static void sortedKeySet1(Map m, Set<@KeyFor("#1") String> keySet) { + new ArrayList<@KeyFor("#1") String>(keySet); + } + + public static void sortedKeySet2(Map m) { + Set<@KeyFor("#1") String> keySet = m.keySet(); + } + + public static void sortedKeySet3(Map m) { + Set<@KeyFor("#1") String> keySet = m.keySet(); + new ArrayList<@KeyFor("#1") String>(keySet); + } + + public static void sortedKeySet4(Map m) { + new ArrayList<@KeyFor("#1") String>(m.keySet()); + } + + public static , V> void sortedKeySet(Map m) { + new ArrayList<@KeyFor("#1") K>(m.keySet()); + } } diff --git a/checker/tests/nullness/Issue419.java b/checker/tests/nullness/Issue419.java index 385145578e3..46e787ee69b 100644 --- a/checker/tests/nullness/Issue419.java +++ b/checker/tests/nullness/Issue419.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue419 { - @SuppressWarnings("nullness") - @NonNull T verifyNotNull(@Nullable T o) { - return o; - } + @SuppressWarnings("nullness") + @NonNull T verifyNotNull(@Nullable T o) { + return o; + } - interface Pair { - @Nullable A getFirst(); - } + interface Pair { + @Nullable A getFirst(); + } - void m(Pair p) { - for (String s : verifyNotNull(p.getFirst())) {} - } + void m(Pair p) { + for (String s : verifyNotNull(p.getFirst())) {} + } } diff --git a/checker/tests/nullness/Issue425.java b/checker/tests/nullness/Issue425.java index a581f03282d..4346cebabac 100644 --- a/checker/tests/nullness/Issue425.java +++ b/checker/tests/nullness/Issue425.java @@ -3,32 +3,33 @@ // @skip-test until the issue is fixed +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.HashSet; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue425 { - private @Nullable Set field = null; + private @Nullable Set field = null; - class EvilSet extends HashSet { - public boolean add(T e) { - field = null; - return super.add(e); + class EvilSet extends HashSet { + public boolean add(T e) { + field = null; + return super.add(e); + } } - } - public void fail() { - if (field == null) { - field = new EvilSet<>(); - } + public void fail() { + if (field == null) { + field = new EvilSet<>(); + } - field.add(1); - // This line throws an exception at run time. - field.add(2); - } + field.add(1); + // This line throws an exception at run time. + field.add(2); + } - public static void main(String[] args) throws Exception { - Issue425 m = new Issue425(); - m.fail(); - } + public static void main(String[] args) throws Exception { + Issue425 m = new Issue425(); + m.fail(); + } } diff --git a/checker/tests/nullness/Issue427.java b/checker/tests/nullness/Issue427.java index fc86027f6be..09c54640635 100644 --- a/checker/tests/nullness/Issue427.java +++ b/checker/tests/nullness/Issue427.java @@ -3,20 +3,21 @@ // We need to add a warning when an @AssumeAssertion is missing its @ symbol (as below). -import java.util.Map; import org.checkerframework.checker.nullness.qual.*; +import java.util.Map; + public class Issue427 { - public static void assumeAssertionKeyFor1(String var, Map m) { - assert m.containsKey(var) - : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; - boolean b = (m.get(var) >= 22); - } + public static void assumeAssertionKeyFor1(String var, Map m) { + assert m.containsKey(var) + : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; + boolean b = (m.get(var) >= 22); + } - public static void assumeAssertionKeyFor2(String var, Map m) { - assert m.containsKey(var) - : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; - int x = m.get(var); - } + public static void assumeAssertionKeyFor2(String var, Map m) { + assert m.containsKey(var) + : "@AssumeAssertion(keyfor): keys of leaders and timeKilled are the same"; + int x = m.get(var); + } } diff --git a/checker/tests/nullness/Issue4372.java b/checker/tests/nullness/Issue4372.java index 90858d799b4..aeffd463f17 100644 --- a/checker/tests/nullness/Issue4372.java +++ b/checker/tests/nullness/Issue4372.java @@ -2,19 +2,19 @@ import java.util.Optional; public class Issue4372 { - private Optional> o; + private Optional> o; - public Issue4372() { - this.o = Optional.>empty(); - } - - void f(String k, Optional x) { - if (!o.isPresent() || !o.get().containsKey(k)) { - return; + public Issue4372() { + this.o = Optional.>empty(); } - Integer y = o.get().get(k); - if (!x.isPresent()) { - return; + + void f(String k, Optional x) { + if (!o.isPresent() || !o.get().containsKey(k)) { + return; + } + Integer y = o.get().get(k); + if (!x.isPresent()) { + return; + } } - } } diff --git a/checker/tests/nullness/Issue4381.java b/checker/tests/nullness/Issue4381.java index 8e071879fdc..2ed7c491120 100644 --- a/checker/tests/nullness/Issue4381.java +++ b/checker/tests/nullness/Issue4381.java @@ -3,17 +3,17 @@ abstract class Issue4381 { - public void t() { - int m = 0; - try { - f(); - } catch (IllegalArgumentException | IOException e) { - } catch (GeneralSecurityException e) { - g(m); + public void t() { + int m = 0; + try { + f(); + } catch (IllegalArgumentException | IOException e) { + } catch (GeneralSecurityException e) { + g(m); + } } - } - abstract void g(int x); + abstract void g(int x); - abstract void f() throws IllegalArgumentException, IOException, GeneralSecurityException; + abstract void f() throws IllegalArgumentException, IOException, GeneralSecurityException; } diff --git a/checker/tests/nullness/Issue4412.java b/checker/tests/nullness/Issue4412.java index 6474cae1a9e..1410e155eae 100644 --- a/checker/tests/nullness/Issue4412.java +++ b/checker/tests/nullness/Issue4412.java @@ -1,210 +1,227 @@ // @skip-test This test passes, but is slow. So skip it until performance improves. -import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; -public interface Issue4412< - LeftLeftType extends Issue4412.LeftLeft, - LeftType extends Issue4412.Left, - RightType extends Issue4412.Right, - RightRightType extends - Issue4412.RightRight> { - - T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer); - - void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction); - - interface LeftLeft< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class LeftLeftImpl< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - implements LeftLeft { - - private final Class selfClass; - - protected LeftLeftImpl(Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return leftLeftReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - leftLeftAction.apply(getSelf()); - } - - private LeftLeftType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); - } - } - - interface Left< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class LeftImpl< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - implements Left { - - private final Class selfClass; - - protected LeftImpl(@NonNull Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return leftReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - leftAction.apply(getSelf()); - } - - private LeftType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); - } - } - - interface Right< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class RightImpl< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - implements Right { - - private final Class selfClass; - - protected RightImpl(@NonNull Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return rightReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - rightAction.apply(getSelf()); - } - - private RightType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); - } - } - - interface RightRight< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - extends Issue4412 {} - - // Not final to allow reification - abstract class RightRightImpl< - LeftLeftType extends LeftLeft, - LeftType extends Left, - RightType extends Right, - RightRightType extends RightRight> - implements RightRight { - - private final Class selfClass; - - protected RightRightImpl(@NonNull Class selfClass) { - this.selfClass = selfClass; - } - - @Override - public final T reduce( - @NonNull Function1 leftLeftReducer, - @NonNull Function1 leftReducer, - @NonNull Function1 rightReducer, - @NonNull Function1 rightRightReducer) { - return rightRightReducer.apply(getSelf()); - } - - @Override - public final void act( - @NonNull VoidFunction1 leftLeftAction, - @NonNull VoidFunction1 leftAction, - @NonNull VoidFunction1 rightAction, - @NonNull VoidFunction1 rightRightAction) { - rightRightAction.apply(getSelf()); - } +import java.util.Objects; - private RightRightType getSelf() { - return Objects.requireNonNull(selfClass.cast(this)); +public interface Issue4412< + LeftLeftType extends Issue4412.LeftLeft, + LeftType extends Issue4412.Left, + RightType extends Issue4412.Right, + RightRightType extends + Issue4412.RightRight> { + + T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer); + + void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction); + + interface LeftLeft< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class LeftLeftImpl< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + implements LeftLeft { + + private final Class selfClass; + + protected LeftLeftImpl(Class selfClass) { + this.selfClass = selfClass; + } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return leftLeftReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + leftLeftAction.apply(getSelf()); + } + + private LeftLeftType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface Left< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class LeftImpl< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + implements Left { + + private final Class selfClass; + + protected LeftImpl(@NonNull Class selfClass) { + this.selfClass = selfClass; + } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return leftReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + leftAction.apply(getSelf()); + } + + private LeftType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface Right< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class RightImpl< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + implements Right { + + private final Class selfClass; + + protected RightImpl(@NonNull Class selfClass) { + this.selfClass = selfClass; + } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return rightReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + rightAction.apply(getSelf()); + } + + private RightType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface RightRight< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + extends Issue4412 {} + + // Not final to allow reification + abstract class RightRightImpl< + LeftLeftType extends + LeftLeft, + LeftType extends Left, + RightType extends Right, + RightRightType extends + RightRight> + implements RightRight { + + private final Class selfClass; + + protected RightRightImpl(@NonNull Class selfClass) { + this.selfClass = selfClass; + } + + @Override + public final T reduce( + @NonNull Function1 leftLeftReducer, + @NonNull Function1 leftReducer, + @NonNull Function1 rightReducer, + @NonNull Function1 rightRightReducer) { + return rightRightReducer.apply(getSelf()); + } + + @Override + public final void act( + @NonNull VoidFunction1 leftLeftAction, + @NonNull VoidFunction1 leftAction, + @NonNull VoidFunction1 rightAction, + @NonNull VoidFunction1 rightRightAction) { + rightRightAction.apply(getSelf()); + } + + private RightRightType getSelf() { + return Objects.requireNonNull(selfClass.cast(this)); + } + } + + interface VoidFunction1 { + + void apply(@NonNull T t); + } + + interface Function1 { + + @NonNull R apply(@NonNull T t); } - } - - interface VoidFunction1 { - - void apply(@NonNull T t); - } - - interface Function1 { - - @NonNull R apply(@NonNull T t); - } } diff --git a/checker/tests/nullness/Issue4523.java b/checker/tests/nullness/Issue4523.java index 7864c7c0ee5..fdc85f383f5 100644 --- a/checker/tests/nullness/Issue4523.java +++ b/checker/tests/nullness/Issue4523.java @@ -3,14 +3,14 @@ public class Issue4523 { - interface InterfaceA> extends InterfaceB {} + interface InterfaceA> extends InterfaceB {} - interface InterfaceB> { - @Pure - @Nullable T g(); - } + interface InterfaceB> { + @Pure + @Nullable T g(); + } - void f(InterfaceA x) { - InterfaceA y = x.g() != null ? x.g() : x; - } + void f(InterfaceA x) { + InterfaceA y = x.g() != null ? x.g() : x; + } } diff --git a/checker/tests/nullness/Issue4579.java b/checker/tests/nullness/Issue4579.java index bd1ea4bb882..5c0e490340b 100644 --- a/checker/tests/nullness/Issue4579.java +++ b/checker/tests/nullness/Issue4579.java @@ -4,37 +4,38 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; + import javax.swing.JButton; public class Issue4579 { - public Issue4579() { - final JButton button = new JButton(); + public Issue4579() { + final JButton button = new JButton(); - // this reports a warning about under initialization - button.addActionListener(l -> doAction()); + // this reports a warning about under initialization + button.addActionListener(l -> doAction()); - // this reports no warnings - button.addActionListener(new ListenerClass()); + // this reports no warnings + button.addActionListener(new ListenerClass()); - // this reports a warning about under initialization - button.addActionListener( - new ActionListener() { - public void actionPerformed(final ActionEvent e) { - doAction(); - } - }); - } + // this reports a warning about under initialization + button.addActionListener( + new ActionListener() { + public void actionPerformed(final ActionEvent e) { + doAction(); + } + }); + } - private void doAction() { - System.out.println("Action"); - } + private void doAction() { + System.out.println("Action"); + } - private class ListenerClass implements ActionListener { + private class ListenerClass implements ActionListener { - @Override - public void actionPerformed(final ActionEvent e) { - doAction(); + @Override + public void actionPerformed(final ActionEvent e) { + doAction(); + } } - } } diff --git a/checker/tests/nullness/Issue4593.java b/checker/tests/nullness/Issue4593.java index 698127ebed5..c4552e6f02f 100644 --- a/checker/tests/nullness/Issue4593.java +++ b/checker/tests/nullness/Issue4593.java @@ -2,19 +2,20 @@ // @skip-test until the bug is fixed +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue4593 { - void getContext(@Nullable String nble) { - Map map = new HashMap<>(); - map.put("configDir", nble); - } + void getContext(@Nullable String nble) { + Map map = new HashMap<>(); + map.put("configDir", nble); + } - void getContextWithVar(@Nullable String nble) { - var map = new HashMap(); - map.put("configDir", nble); - } + void getContextWithVar(@Nullable String nble) { + var map = new HashMap(); + map.put("configDir", nble); + } } diff --git a/checker/tests/nullness/Issue4614.java b/checker/tests/nullness/Issue4614.java index ea67892fd62..e913ff1fce0 100644 --- a/checker/tests/nullness/Issue4614.java +++ b/checker/tests/nullness/Issue4614.java @@ -4,23 +4,23 @@ public final class Issue4614 { - public static Map getAllVersionInformation() { - return new HashMap<>(); - } + public static Map getAllVersionInformation() { + return new HashMap<>(); + } - public void method1() { - final String versionInfo = - Issue4614.getAllVersionInformation().entrySet().stream() // - .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // - .collect(Collectors.joining("\n")); - } + public void method1() { + final String versionInfo = + Issue4614.getAllVersionInformation().entrySet().stream() // + .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // + .collect(Collectors.joining("\n")); + } - Map allVersionInformation = new HashMap<>(); + Map allVersionInformation = new HashMap<>(); - public void method2() { - final String versionInfo = - allVersionInformation.entrySet().stream() // - .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // - .collect(Collectors.joining("\n")); - } + public void method2() { + final String versionInfo = + allVersionInformation.entrySet().stream() // + .map(e -> String.format("%s:%s", e.getKey(), e.getValue())) // + .collect(Collectors.joining("\n")); + } } diff --git a/checker/tests/nullness/Issue471.java b/checker/tests/nullness/Issue471.java index 2666c729ba3..7026790f3cc 100644 --- a/checker/tests/nullness/Issue471.java +++ b/checker/tests/nullness/Issue471.java @@ -5,9 +5,9 @@ import javax.annotation.Nullable; public class Issue471 { - @Nullable T t; + @Nullable T t; - Issue471(@Nullable T t) { - this.t = t; - } + Issue471(@Nullable T t) { + this.t = t; + } } diff --git a/checker/tests/nullness/Issue4853Nullness.java b/checker/tests/nullness/Issue4853Nullness.java index d6f4502e4bd..2d49a3000d2 100644 --- a/checker/tests/nullness/Issue4853Nullness.java +++ b/checker/tests/nullness/Issue4853Nullness.java @@ -1,18 +1,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue4853Nullness { - interface Interface {} + interface Interface {} - static class MyClass { - class InnerMyClass implements Interface {} - } - - abstract static class SubMyClass extends MyClass<@Nullable String> { - protected void f() { - // :: error: (argument.type.incompatible) - method(new InnerMyClass()); + static class MyClass { + class InnerMyClass implements Interface {} } - abstract void method(Interface callback); - } + abstract static class SubMyClass extends MyClass<@Nullable String> { + protected void f() { + // :: error: (argument.type.incompatible) + method(new InnerMyClass()); + } + + abstract void method(Interface callback); + } } diff --git a/checker/tests/nullness/Issue4889.java b/checker/tests/nullness/Issue4889.java index f30aa016776..e9a8e8f6e3b 100644 --- a/checker/tests/nullness/Issue4889.java +++ b/checker/tests/nullness/Issue4889.java @@ -1,14 +1,15 @@ -import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + class Issue4889 { - void f(@Nullable String s) { - Objects.toString(s, "").toString(); - } + void f(@Nullable String s) { + Objects.toString(s, "").toString(); + } - void g(@Nullable String s) { - @NonNull String x = Objects.toString(s, ""); - @Nullable String y = Objects.toString(s, null); - } + void g(@Nullable String s) { + @NonNull String x = Objects.toString(s, ""); + @Nullable String y = Objects.toString(s, null); + } } diff --git a/checker/tests/nullness/Issue4923.java b/checker/tests/nullness/Issue4923.java index 7d1e81f459a..829c8f61d08 100644 --- a/checker/tests/nullness/Issue4923.java +++ b/checker/tests/nullness/Issue4923.java @@ -1,19 +1,19 @@ class Issue4923 { - interface Go { - void go(); - } + interface Go { + void go(); + } - final Go go = - new Go() { - @Override - public void go() { - synchronized (x) { - } - } - }; - final Object x = new Object(); + final Go go = + new Go() { + @Override + public void go() { + synchronized (x) { + } + } + }; + final Object x = new Object(); - // Make sure that initializer type is compatible with declared type - // :: error: (assignment.type.incompatible) - final Object y = null; + // Make sure that initializer type is compatible with declared type + // :: error: (assignment.type.incompatible) + final Object y = null; } diff --git a/checker/tests/nullness/Issue4924.java b/checker/tests/nullness/Issue4924.java index cb24e8660c7..bd982f5c2a3 100644 --- a/checker/tests/nullness/Issue4924.java +++ b/checker/tests/nullness/Issue4924.java @@ -5,21 +5,21 @@ class Issue4924 {} interface Callback4924 {} class Template4924 { - interface Putter4924 { - void put(T result); - } + interface Putter4924 { + void put(T result); + } - class Adapter4924 implements Callback4924 { - Adapter4924(Putter4924 putter) {} - } + class Adapter4924 implements Callback4924 { + Adapter4924(Putter4924 putter) {} + } } class Super4924 extends Template4924 {} class Issue extends Super4924 { - void go(Callback4924 callback) {} + void go(Callback4924 callback) {} - void foo() { - go(new Adapter4924(result -> {})); - } + void foo() { + go(new Adapter4924(result -> {})); + } } diff --git a/checker/tests/nullness/Issue500.java b/checker/tests/nullness/Issue500.java index 0f8f032dabd..b905c84a03d 100644 --- a/checker/tests/nullness/Issue500.java +++ b/checker/tests/nullness/Issue500.java @@ -1,25 +1,29 @@ // Test case for Issue 500: // https://github.com/typetools/checker-framework/issues/500 +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.AbstractList; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue500 { - // Tests GLB - public Issue500(@Nullable List list) { - if (list instanceof ArrayList) {} - } + // Tests GLB + public Issue500(@Nullable List list) { + if (list instanceof ArrayList) {} + } - // Tests GLB - public Issue500(@Nullable AbstractList list) { - if (list instanceof ArrayList) {} - } + // Tests GLB + public Issue500(@Nullable AbstractList list) { + if (list instanceof ArrayList) {} + } - // Tests LUB - void foo( - @Nullable AbstractList l1, ArrayList l2, @Nullable AbstractList list, boolean b) { - list = b ? l1 : l2; - } + // Tests LUB + void foo( + @Nullable AbstractList l1, + ArrayList l2, + @Nullable AbstractList list, + boolean b) { + list = b ? l1 : l2; + } } diff --git a/checker/tests/nullness/Issue5042.java b/checker/tests/nullness/Issue5042.java index 0bc0e99d0a8..5f3f01a2d61 100644 --- a/checker/tests/nullness/Issue5042.java +++ b/checker/tests/nullness/Issue5042.java @@ -1,67 +1,68 @@ -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; -public class Issue5042 { - interface PromptViewModel { - boolean isPending(); +import java.util.function.Function; - @Nullable PromptButtonViewModel getConfirmationButton(); - } +public class Issue5042 { + interface PromptViewModel { + boolean isPending(); - interface PromptButtonViewModel { - @Nullable ConfirmationPopupViewModel getConfirmationPopup(); - } + @Nullable PromptButtonViewModel getConfirmationButton(); + } - interface ConfirmationPopupViewModel { - boolean isShowingConfirmation(); - } + interface PromptButtonViewModel { + @Nullable ConfirmationPopupViewModel getConfirmationPopup(); + } - boolean f(PromptViewModel viewModel) { - PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - } + interface ConfirmationPopupViewModel { + boolean isShowingConfirmation(); + } - static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = - (viewModel) -> { - @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - promptLambda != null ? promptLambda.getConfirmationPopup() : null; + boolean f(PromptViewModel viewModel) { + PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + } - final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = - (viewModel) -> { - @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - prompt == null ? null : prompt.getConfirmationPopup(); - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = + (viewModel) -> { + @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + promptLambda != null ? promptLambda.getConfirmationPopup() : null; + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; + + final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = + (viewModel) -> { + @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + prompt == null ? null : prompt.getConfirmationPopup(); + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; - @Nullable PromptButtonViewModel promptfield; - Producer o = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield == null ? null : promptfield.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + @Nullable PromptButtonViewModel promptfield; + Producer o = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield == null ? null : promptfield.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - static @Nullable PromptButtonViewModel promptfield2; + static @Nullable PromptButtonViewModel promptfield2; - static Producer o2 = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield2 == null ? null : promptfield2.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + static Producer o2 = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield2 == null ? null : promptfield2.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - interface Producer { - Object apply(); - } + interface Producer { + Object apply(); + } - Issue5042(int i, int i2) {} + Issue5042(int i, int i2) {} - Issue5042(int i) {} + Issue5042(int i) {} - Issue5042() {} + Issue5042() {} } diff --git a/checker/tests/nullness/Issue5075NPE.java b/checker/tests/nullness/Issue5075NPE.java index 114757fb80d..7cf68db88ec 100644 --- a/checker/tests/nullness/Issue5075NPE.java +++ b/checker/tests/nullness/Issue5075NPE.java @@ -3,43 +3,43 @@ public class Issue5075NPE { - static class C { - void useBoxer(Boxer b) { - // :: error: (assignment.type.incompatible) - Box o = b.getBox(); - o.get().toString(); + static class C { + void useBoxer(Boxer b) { + // :: error: (assignment.type.incompatible) + Box o = b.getBox(); + o.get().toString(); + } + + void useC(C c) {} + + class Boxer { + V v; + + Boxer(V in) { + this.v = in; + } + + Box getBox() { + return new Box(v); + } + } } - void useC(C c) {} + // Doesn't matter whether T's bound is explicit + static class Box { + T f; - class Boxer { - V v; + Box(T p) { + this.f = p; + } - Boxer(V in) { - this.v = in; - } - - Box getBox() { - return new Box(v); - } + T get() { + return f; + } } - } - - // Doesn't matter whether T's bound is explicit - static class Box { - T f; - Box(T p) { - this.f = p; + public static void main(String[] args) { + C<@Nullable Issue5075NPE> c = new C<>(); + c.useBoxer(c.new Boxer(null)); } - - T get() { - return f; - } - } - - public static void main(String[] args) { - C<@Nullable Issue5075NPE> c = new C<>(); - c.useBoxer(c.new Boxer(null)); - } } diff --git a/checker/tests/nullness/Issue5075a.java b/checker/tests/nullness/Issue5075a.java index 0f5a942cc57..42d4dd137d3 100644 --- a/checker/tests/nullness/Issue5075a.java +++ b/checker/tests/nullness/Issue5075a.java @@ -1,31 +1,31 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue5075a { - class AExpl { - I i1() { - // Type arguments aren't inferred correctly, test with #979 - // :: error: (return.type.incompatible) :: error: (argument.type.incompatible) - return new BExpl<>(this); - } + class AExpl { + I i1() { + // Type arguments aren't inferred correctly, test with #979 + // :: error: (return.type.incompatible) :: error: (argument.type.incompatible) + return new BExpl<>(this); + } - I i2() { - return new BExpl(this); + I i2() { + return new BExpl(this); + } } - } - class BExpl implements I { - BExpl(AExpl a) {} - } + class BExpl implements I { + BExpl(AExpl a) {} + } - class AImpl { - I i() { - return new BImpl<>(this); + class AImpl { + I i() { + return new BImpl<>(this); + } } - } - class BImpl implements I { - BImpl(AImpl a) {} - } + class BImpl implements I { + BImpl(AImpl a) {} + } - interface I {} + interface I {} } diff --git a/checker/tests/nullness/Issue5075b.java b/checker/tests/nullness/Issue5075b.java index 97d7b8f8674..021128efaa0 100644 --- a/checker/tests/nullness/Issue5075b.java +++ b/checker/tests/nullness/Issue5075b.java @@ -1,33 +1,33 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue5075b { - static class CExpl { - I c(N n) { - return h(n.i()); - } + static class CExpl { + I c(N n) { + return h(n.i()); + } - abstract class N { - abstract I i(); - } + abstract class N { + abstract I i(); + } - static I h(I i) { - return i; + static I h(I i) { + return i; + } } - } - static class CImpl { - I c(N n) { - return h(n.i()); - } + static class CImpl { + I c(N n) { + return h(n.i()); + } - abstract class N { - abstract I i(); - } + abstract class N { + abstract I i(); + } - static I h(I i) { - return i; + static I h(I i) { + return i; + } } - } - interface I {} + interface I {} } diff --git a/checker/tests/nullness/Issue5189a.java b/checker/tests/nullness/Issue5189a.java index 95de7f0ca65..5e37c9523f1 100644 --- a/checker/tests/nullness/Issue5189a.java +++ b/checker/tests/nullness/Issue5189a.java @@ -1,5 +1,5 @@ enum Issue5189a { - EMPTY() {}; + EMPTY() {}; - Issue5189a(String... args) {} + Issue5189a(String... args) {} } diff --git a/checker/tests/nullness/Issue5189b.java b/checker/tests/nullness/Issue5189b.java index ca2a3b2a78e..e82058012c8 100644 --- a/checker/tests/nullness/Issue5189b.java +++ b/checker/tests/nullness/Issue5189b.java @@ -1,9 +1,9 @@ class Issue5189b { - class Inner { - Inner(String... args) {} - } + class Inner { + Inner(String... args) {} + } - void foo() { - Object o = new Inner() {}; - } + void foo() { + Object o = new Inner() {}; + } } diff --git a/checker/tests/nullness/Issue520.java b/checker/tests/nullness/Issue520.java index 26d0cfaf45d..9d3da80a214 100644 --- a/checker/tests/nullness/Issue520.java +++ b/checker/tests/nullness/Issue520.java @@ -5,38 +5,40 @@ // compile with: $CHECKERFRAMEWORK/checker/bin/javac -g Issue520.java -processor nullness // -AprintAllQualifiers +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class Issue520 {} abstract class Parent { - protected final List list; + protected final List list; - public Parent(List list) { - this.list = list; - } + public Parent(List list) { + this.list = list; + } } abstract class Child extends Parent { - public Child(List list) { - super(list); - } + public Child(List list) { + super(list); + } - public void add(CharSequence seq) { - list.add(seq); - } + public void add(CharSequence seq) { + list.add(seq); + } } class WildCardAdd { - List<@UnknownKeyFor ? super @KeyForBottom CharSequence> wildCardList = - new ArrayList<@KeyForBottom CharSequence>(); - - void foo( - List<@KeyFor("m") CharSequence> keyForMCharSeq, @UnknownKeyFor CharSequence unknownCharSeq) { - wildCardList = keyForMCharSeq; - wildCardList.add(unknownCharSeq); - @KeyFor("y") Object o = wildCardList.get(0); - } + List<@UnknownKeyFor ? super @KeyForBottom CharSequence> wildCardList = + new ArrayList<@KeyForBottom CharSequence>(); + + void foo( + List<@KeyFor("m") CharSequence> keyForMCharSeq, + @UnknownKeyFor CharSequence unknownCharSeq) { + wildCardList = keyForMCharSeq; + wildCardList.add(unknownCharSeq); + @KeyFor("y") Object o = wildCardList.get(0); + } } diff --git a/checker/tests/nullness/Issue5245.java b/checker/tests/nullness/Issue5245.java index a0d02a81c83..c0d469ea1f4 100644 --- a/checker/tests/nullness/Issue5245.java +++ b/checker/tests/nullness/Issue5245.java @@ -3,7 +3,7 @@ import java.util.List; class Issue5245 { - final Issue5245> repro = new Issue5245<>(List.of()); + final Issue5245> repro = new Issue5245<>(List.of()); - Issue5245(V unknownObj) {} + Issue5245(V unknownObj) {} } diff --git a/checker/tests/nullness/Issue531.java b/checker/tests/nullness/Issue531.java index a1251875126..91b5f5cbd69 100644 --- a/checker/tests/nullness/Issue531.java +++ b/checker/tests/nullness/Issue531.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue531 { - public MyList test(MyStream stream) { - return stream.collect(toList()); - } + public MyList test(MyStream stream) { + return stream.collect(toList()); + } - void foo(MyStream stream) {} + void foo(MyStream stream) {} - static MyCollector> toList() { - return new MyCollector<>(); - } + static MyCollector> toList() { + return new MyCollector<>(); + } - static class MyList {} + static class MyList {} - static class MyCollector {} + static class MyCollector {} - abstract static class MyStream { - public abstract R collect(MyCollector c); - } + abstract static class MyStream { + public abstract R collect(MyCollector c); + } } diff --git a/checker/tests/nullness/Issue554.java b/checker/tests/nullness/Issue554.java index e81a741f567..05d27d1118b 100644 --- a/checker/tests/nullness/Issue554.java +++ b/checker/tests/nullness/Issue554.java @@ -5,65 +5,65 @@ import org.checkerframework.checker.nullness.qual.*; class MonotonicNonNullConstructorTest1 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest1(final Data data) { - this.data = data; - this.object = data.field; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest1(final Data data) { + this.data = data; + this.object = data.field; + } } class MonotonicNonNullConstructorTest2 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest2(final Data data) { - // reverse the assignments - this.object = data.field; - this.data = data; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest2(final Data data) { + // reverse the assignments + this.object = data.field; + this.data = data; + } } class MonotonicNonNullConstructorTest3 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest3(final Data dataParam) { - // use a parameter name that does not shadow the field - this.data = dataParam; - this.object = dataParam.field; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest3(final Data dataParam) { + // use a parameter name that does not shadow the field + this.data = dataParam; + this.object = dataParam.field; + } } class MonotonicNonNullConstructorTest4 { - static class Data { - @MonotonicNonNull Object field; - } + static class Data { + @MonotonicNonNull Object field; + } - Data data; - Object object; + Data data; + Object object; - @RequiresNonNull("#1.field") - MonotonicNonNullConstructorTest4(final Data dataParam) { - // use a parameter name that does not shadow the field - // and reverse the assignments - this.object = dataParam.field; - this.data = dataParam; - } + @RequiresNonNull("#1.field") + MonotonicNonNullConstructorTest4(final Data dataParam) { + // use a parameter name that does not shadow the field + // and reverse the assignments + this.object = dataParam.field; + this.data = dataParam; + } } diff --git a/checker/tests/nullness/Issue563.java b/checker/tests/nullness/Issue563.java index 238c37803c6..ab48ea41f83 100644 --- a/checker/tests/nullness/Issue563.java +++ b/checker/tests/nullness/Issue563.java @@ -1,10 +1,10 @@ // Test case for Issue 563: // https://github.com/typetools/checker-framework/issues/563 public class Issue563 { - void bar() { - Object x = null; - if (Object.class.isInstance(x)) { - x.toString(); + void bar() { + Object x = null; + if (Object.class.isInstance(x)) { + x.toString(); + } } - } } diff --git a/checker/tests/nullness/Issue577.java b/checker/tests/nullness/Issue577.java index 59af2bbac44..5d0057cdaf8 100644 --- a/checker/tests/nullness/Issue577.java +++ b/checker/tests/nullness/Issue577.java @@ -5,69 +5,69 @@ import org.checkerframework.checker.nullness.qual.*; class Banana extends Apple { - @Override - void fooOuter(int[] array) {} - - class InnerBanana extends InnerApple { @Override - // :: error: (override.param.invalid) - void foo(int[] array, long[] array2, F2 param3) {} - } + void fooOuter(int[] array) {} + + class InnerBanana extends InnerApple { + @Override + // :: error: (override.param.invalid) + void foo(int[] array, long[] array2, F2 param3) {} + } } class Apple { - void fooOuter(T param) {} + void fooOuter(T param) {} - class InnerApple { - void foo(T param, E param2, F param3) {} - } + class InnerApple { + void foo(T param, E param2, F param3) {} + } } class Pineapple extends Apple { - @Override - void fooOuter(E array) {} - - class InnerPineapple extends InnerApple<@Nullable String> { @Override - // :: error: (override.param.invalid) - void foo(E array, String array2, F3 param3) {} - } + void fooOuter(E array) {} + + class InnerPineapple extends InnerApple<@Nullable String> { + @Override + // :: error: (override.param.invalid) + void foo(E array, String array2, F3 param3) {} + } } class IntersectionAsMemberOf { - interface MyGenericInterface { - F getF(); - } + interface MyGenericInterface { + F getF(); + } - > void foo(T param) { - @NonNull String s = param.getF(); - } + > void foo(T param) { + @NonNull String s = param.getF(); + } } class UnionAsMemberOf { - interface MyInterface { - T getT(); - } + interface MyInterface { + T getT(); + } - class MyExceptionA extends Throwable implements Cloneable, MyInterface<@NonNull String> { - public String getT() { - return "t"; + class MyExceptionA extends Throwable implements Cloneable, MyInterface<@NonNull String> { + public String getT() { + return "t"; + } } - } - class MyExceptionB extends Throwable implements Cloneable, MyInterface { - public String getT() { - return "t"; + class MyExceptionB extends Throwable implements Cloneable, MyInterface { + public String getT() { + return "t"; + } } - } - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - @NonNull String s = ex1.getT(); + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + @NonNull String s = ex1.getT(); + } } - } } diff --git a/checker/tests/nullness/Issue578.java b/checker/tests/nullness/Issue578.java index 13228aefcf5..a80d4af3972 100644 --- a/checker/tests/nullness/Issue578.java +++ b/checker/tests/nullness/Issue578.java @@ -1,16 +1,16 @@ // Test case for issue #578: https://github.com/typetools/checker-framework/issues/578 public class Issue578 { - void eval(Helper helper, Interface anInterface) { - Object o = new SomeGenericClass<>(helper.helperMethod(anInterface)); - } + void eval(Helper helper, Interface anInterface) { + Object o = new SomeGenericClass<>(helper.helperMethod(anInterface)); + } } abstract class Helper { - abstract Interface helperMethod(Interface anInterface); + abstract Interface helperMethod(Interface anInterface); } interface Interface {} final class SomeGenericClass { - SomeGenericClass(Interface s) {} + SomeGenericClass(Interface s) {} } diff --git a/checker/tests/nullness/Issue579Error.java b/checker/tests/nullness/Issue579Error.java index d54efa36c6b..f011968cf73 100644 --- a/checker/tests/nullness/Issue579Error.java +++ b/checker/tests/nullness/Issue579Error.java @@ -5,12 +5,12 @@ public class Issue579Error { - public void foo(Generic real, Generic other, boolean flag) { - // :: error: (type.arguments.not.inferred) - bar(flag ? real : other); - } + public void foo(Generic real, Generic other, boolean flag) { + // :: error: (type.arguments.not.inferred) + bar(flag ? real : other); + } - <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} + <@NonNull Q extends @NonNull Object> void bar(Generic parm) {} - interface Generic {} + interface Generic {} } diff --git a/checker/tests/nullness/Issue580.java b/checker/tests/nullness/Issue580.java index b24c441ce40..983f62f29ed 100644 --- a/checker/tests/nullness/Issue580.java +++ b/checker/tests/nullness/Issue580.java @@ -1,9 +1,9 @@ // Test case for issue #580: https://github.com/typetools/checker-framework/issues/580 abstract class InitCheckAssertionFailure { - public static > void noneOf(F[] array) { - Enum[] universe = array; - // Accessing universe on this line causes the error. - int len = universe.length; - } + public static > void noneOf(F[] array) { + Enum[] universe = array; + // Accessing universe on this line causes the error. + int len = universe.length; + } } diff --git a/checker/tests/nullness/Issue602.java b/checker/tests/nullness/Issue602.java index cf4b9c7a6f9..1e9fcffb359 100644 --- a/checker/tests/nullness/Issue602.java +++ b/checker/tests/nullness/Issue602.java @@ -5,19 +5,19 @@ // https://github.com/typetools/checker-framework/issues/602 // @skip-test public class Issue602 { - @PolyNull String id(@PolyNull String o) { - return o; - } + @PolyNull String id(@PolyNull String o) { + return o; + } - void loop(boolean condition) { - @NonNull String notNull = "hello"; - String nullable = ""; - while (condition) { - // :: error: (assignment.type.incompatible) - notNull = nullable; - // :: error: (assignment.type.incompatible) - notNull = id(nullable); - nullable = null; + void loop(boolean condition) { + @NonNull String notNull = "hello"; + String nullable = ""; + while (condition) { + // :: error: (assignment.type.incompatible) + notNull = nullable; + // :: error: (assignment.type.incompatible) + notNull = id(nullable); + nullable = null; + } } - } } diff --git a/checker/tests/nullness/Issue6260.java b/checker/tests/nullness/Issue6260.java index 34cc607de31..a175ab9831c 100644 --- a/checker/tests/nullness/Issue6260.java +++ b/checker/tests/nullness/Issue6260.java @@ -1,21 +1,21 @@ // A similar test is in // framework/tests/all-systems/EnumSwitch.java public class Issue6260 { - enum MyE { - FOO; + enum MyE { + FOO; - MyE getIt() { - return FOO; - } + MyE getIt() { + return FOO; + } - String go() { - MyE e = getIt(); - switch (e) { - case FOO: - return "foo"; - } - // This is not dead code! - throw new AssertionError(e); + String go() { + MyE e = getIt(); + switch (e) { + case FOO: + return "foo"; + } + // This is not dead code! + throw new AssertionError(e); + } } - } } diff --git a/checker/tests/nullness/Issue6393.java b/checker/tests/nullness/Issue6393.java index 77bd93f343b..ee5c88b0049 100644 --- a/checker/tests/nullness/Issue6393.java +++ b/checker/tests/nullness/Issue6393.java @@ -1,26 +1,27 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.io.IOException; import java.io.Serializable; -import org.checkerframework.checker.nullness.qual.Nullable; class Issue6393 { - public static class AClass implements Serializable {} + public static class AClass implements Serializable {} - static class GClass { - public @Nullable T g(Class e) throws IOException { - throw new AssertionError(); + static class GClass { + public @Nullable T g(Class e) throws IOException { + throw new AssertionError(); + } } - } - @SuppressWarnings("unchecked") - static T f(Class x, GClass y) { - AClass z; - try { - z = y.g(x); - } catch (IOException e) { - throw new IllegalStateException(e); + @SuppressWarnings("unchecked") + static T f(Class x, GClass y) { + AClass z; + try { + z = y.g(x); + } catch (IOException e) { + throw new IllegalStateException(e); + } + // :: warning: (cast.unsafe) + return (T) z; } - // :: warning: (cast.unsafe) - return (T) z; - } } diff --git a/checker/tests/nullness/Issue653.java b/checker/tests/nullness/Issue653.java index 651eddcbfbe..84ed2694ad0 100644 --- a/checker/tests/nullness/Issue653.java +++ b/checker/tests/nullness/Issue653.java @@ -5,33 +5,33 @@ public class Issue653 { - public static @PolyNull String[] concat( - @PolyNull String @Nullable [] a, @PolyNull String @Nullable [] b) { - if (a == null) { - if (b == null) { - return new String[0]; - } else { - return b; - } - } else { - if (b == null) { - return a; - } else { - @PolyNull String[] result = new String[a.length + b.length]; + public static @PolyNull String[] concat( + @PolyNull String @Nullable [] a, @PolyNull String @Nullable [] b) { + if (a == null) { + if (b == null) { + return new String[0]; + } else { + return b; + } + } else { + if (b == null) { + return a; + } else { + @PolyNull String[] result = new String[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + } } - } - public static String[] debugTrackPpt = {}; + public static String[] debugTrackPpt = {}; - public static void add_track(String ppt) { - String[] newArray = new String[] {ppt}; - debugTrackPpt = concat(debugTrackPpt, newArray); + public static void add_track(String ppt) { + String[] newArray = new String[] {ppt}; + debugTrackPpt = concat(debugTrackPpt, newArray); - debugTrackPpt = concat(debugTrackPpt, new String[] {ppt}); - } + debugTrackPpt = concat(debugTrackPpt, new String[] {ppt}); + } } diff --git a/checker/tests/nullness/Issue67.java b/checker/tests/nullness/Issue67.java index 804137c743a..e2f6db08199 100644 --- a/checker/tests/nullness/Issue67.java +++ b/checker/tests/nullness/Issue67.java @@ -5,17 +5,17 @@ import java.util.Map; public class Issue67 { - private static final String KEY = "key"; - private static final String KEY2 = "key2"; + private static final String KEY = "key"; + private static final String KEY2 = "key2"; - void test() { - Map map = new HashMap<>(); - if (map.containsKey(KEY)) { - map.get(KEY).toString(); // no problem + void test() { + Map map = new HashMap<>(); + if (map.containsKey(KEY)) { + map.get(KEY).toString(); // no problem + } + // :: warning: (nulltest.redundant) + if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { // error + // do nothing + } } - // :: warning: (nulltest.redundant) - if (map.containsKey(KEY2) && map.get(KEY2).toString() != null) { // error - // do nothing - } - } } diff --git a/checker/tests/nullness/Issue672.java b/checker/tests/nullness/Issue672.java index 343cb344704..79c7d7c7d81 100644 --- a/checker/tests/nullness/Issue672.java +++ b/checker/tests/nullness/Issue672.java @@ -2,21 +2,21 @@ // https://github.com/typetools/checker-framework/issues/672 final class Issue672 extends Throwable { - final Throwable ex; + final Throwable ex; - Issue672(Throwable x) { - ex = x; - } + Issue672(Throwable x) { + ex = x; + } - static Issue672 test1(Throwable x, boolean flag) { - return new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x)))); - } + static Issue672 test1(Throwable x, boolean flag) { + return new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x)))); + } - static Issue672 test2(Throwable x, boolean flag) { - return (new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x))))); - } + static Issue672 test2(Throwable x, boolean flag) { + return (new Issue672(x instanceof Exception ? x : ((flag ? x : new Issue672(x))))); + } - static Issue672 test3(Throwable x) { - return test1(x instanceof Exception ? x : new Issue672(x), false); - } + static Issue672 test3(Throwable x) { + return test1(x instanceof Exception ? x : new Issue672(x), false); + } } diff --git a/checker/tests/nullness/Issue679.java b/checker/tests/nullness/Issue679.java index a6c182feadc..15d794cb1a2 100644 --- a/checker/tests/nullness/Issue679.java +++ b/checker/tests/nullness/Issue679.java @@ -5,11 +5,11 @@ // https://github.com/typetools/checker-framework/issues/679 // @skip-test public class Issue679 { - interface Interface {} + interface Interface {} - class B implements Interface<@NonNull Number> {} + class B implements Interface<@NonNull Number> {} - // :: error: Interface cannot be inherited with different arguments: <@NonNull Number> and - // <@Nullable Number> - class A extends B implements Interface<@Nullable Number> {} + // :: error: Interface cannot be inherited with different arguments: <@NonNull Number> and + // <@Nullable Number> + class A extends B implements Interface<@Nullable Number> {} } diff --git a/checker/tests/nullness/Issue738.java b/checker/tests/nullness/Issue738.java index c5103bc1be0..605f36b8a6a 100644 --- a/checker/tests/nullness/Issue738.java +++ b/checker/tests/nullness/Issue738.java @@ -5,35 +5,35 @@ // https://github.com/typetools/checker-framework/issues/738 // Also, see framework/tests/all-systems/Issue738.java public class Issue738 { - void methodA(int[] is, Object @Nullable [] os, int i) { - // The type argument to methodB* for each call below is Cloneable & Serializable + void methodA(int[] is, Object @Nullable [] os, int i) { + // The type argument to methodB* for each call below is Cloneable & Serializable - // NullnessTransfer changes the type of an argument that is assigned to a @NonNull parameter - // to @NonNull. Use a switch statement to prevent this. - switch (i) { - case 1: - methodB(is, os); - break; - case 2: - // :: error: (argument.type.incompatible) - methodB2(is, os); - break; - case 3: - // :: error: (type.arguments.not.inferred) - methodB3(is, os); - break; - case 4: - // :: error: (type.arguments.not.inferred) - methodB4(is, os); - break; + // NullnessTransfer changes the type of an argument that is assigned to a @NonNull parameter + // to @NonNull. Use a switch statement to prevent this. + switch (i) { + case 1: + methodB(is, os); + break; + case 2: + // :: error: (argument.type.incompatible) + methodB2(is, os); + break; + case 3: + // :: error: (type.arguments.not.inferred) + methodB3(is, os); + break; + case 4: + // :: error: (type.arguments.not.inferred) + methodB4(is, os); + break; + } } - } - void methodB(T paramA, T paramB) {} + void methodB(T paramA, T paramB) {} - void methodB2(T paramA, @NonNull T paramB) {} + void methodB2(T paramA, @NonNull T paramB) {} - <@NonNull T extends @NonNull Object> void methodB3(T paramA, T paramB) {} + <@NonNull T extends @NonNull Object> void methodB3(T paramA, T paramB) {} - void methodB4(T paramA, T paramB) {} + void methodB4(T paramA, T paramB) {} } diff --git a/checker/tests/nullness/Issue741.java b/checker/tests/nullness/Issue741.java index 2661c6de0fe..3e2fb4fd34a 100644 --- a/checker/tests/nullness/Issue741.java +++ b/checker/tests/nullness/Issue741.java @@ -2,18 +2,18 @@ // https://github.com/typetools/checker-framework/pull/741 // @skip-test public class Issue741 { - @SuppressWarnings("unchecked") - public T incompatibleTypes(Object o) { - final T x = (T) o; - if (x != null) {} - // invaild error here - return x; - } + @SuppressWarnings("unchecked") + public T incompatibleTypes(Object o) { + final T x = (T) o; + if (x != null) {} + // invaild error here + return x; + } - @SuppressWarnings("unchecked") - public T noIncompatibleTypes(Object o) { - final T x = (T) o; - // no error here - return x; - } + @SuppressWarnings("unchecked") + public T noIncompatibleTypes(Object o) { + final T x = (T) o; + // no error here + return x; + } } diff --git a/checker/tests/nullness/Issue752.java b/checker/tests/nullness/Issue752.java index 6ad259e75b5..c78cdb06ca2 100644 --- a/checker/tests/nullness/Issue752.java +++ b/checker/tests/nullness/Issue752.java @@ -4,67 +4,67 @@ public class Issue752 { - Issue752 field = new Issue752(); - static Issue752 staticField = new Issue752(); + Issue752 field = new Issue752(); + static Issue752 staticField = new Issue752(); - Issue752 method() { - return field; - } + Issue752 method() { + return field; + } - static Issue752 staticMethod() { - return staticField; - } + static Issue752 staticMethod() { + return staticField; + } - // A package name without a class name is not a valid JavaExpression string. - @RequiresNonNull("java.lang") - // :: error: (flowexpr.parse.error) - void method1() {} + // A package name without a class name is not a valid JavaExpression string. + @RequiresNonNull("java.lang") + // :: error: (flowexpr.parse.error) + void method1() {} - @RequiresNonNull("java.lang.String.class") - void method2() {} + @RequiresNonNull("java.lang.String.class") + void method2() {} - // A package name without a class name is not a valid JavaExpression string. - @RequiresNonNull("a.b.c") - // :: error: (flowexpr.parse.error) - void method3() {} + // A package name without a class name is not a valid JavaExpression string. + @RequiresNonNull("a.b.c") + // :: error: (flowexpr.parse.error) + void method3() {} - // notaclass does not exist. - @RequiresNonNull("a.b.c.notaclass") - // :: error: (flowexpr.parse.error) - void method4() {} + // notaclass does not exist. + @RequiresNonNull("a.b.c.notaclass") + // :: error: (flowexpr.parse.error) + void method4() {} - @RequiresNonNull("a.b.c.Issue752.class") - void method5() {} + @RequiresNonNull("a.b.c.Issue752.class") + void method5() {} - @RequiresNonNull("a.b.c.Issue752.staticField") - void method6() {} + @RequiresNonNull("a.b.c.Issue752.staticField") + void method6() {} - @RequiresNonNull("a.b.c.Issue752.staticField.field") - void method7() {} + @RequiresNonNull("a.b.c.Issue752.staticField.field") + void method7() {} - // field is an instance field, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.field") - // :: error: (flowexpr.parse.error) - void method8() {} + // field is an instance field, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.field") + // :: error: (flowexpr.parse.error) + void method8() {} - // field is an instance field, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.field.field") - // :: error: (flowexpr.parse.error) - void method9() {} + // field is an instance field, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.field.field") + // :: error: (flowexpr.parse.error) + void method9() {} - @RequiresNonNull("a.b.c.Issue752.staticMethod()") - void method10() {} + @RequiresNonNull("a.b.c.Issue752.staticMethod()") + void method10() {} - @RequiresNonNull("a.b.c.Issue752.staticMethod().field") - void method11() {} + @RequiresNonNull("a.b.c.Issue752.staticMethod().field") + void method11() {} - // method() is an instance method, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.method()") - // :: error: (flowexpr.parse.error) - void method12() {} + // method() is an instance method, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.method()") + // :: error: (flowexpr.parse.error) + void method12() {} - // method() is an instance method, and Issue752 is a class. - @RequiresNonNull("a.b.c.Issue752.method().field") - // :: error: (flowexpr.parse.error) - void method13() {} + // method() is an instance method, and Issue752 is a class. + @RequiresNonNull("a.b.c.Issue752.method().field") + // :: error: (flowexpr.parse.error) + void method13() {} } diff --git a/checker/tests/nullness/Issue759.java b/checker/tests/nullness/Issue759.java index eea87d3a8f9..e9260941b76 100644 --- a/checker/tests/nullness/Issue759.java +++ b/checker/tests/nullness/Issue759.java @@ -6,46 +6,46 @@ @SuppressWarnings("unchecked") public class Issue759 { - void possibleValues(final Class enumType) { - lowercase(enumType.getEnumConstants()); - lowercase2(enumType.getEnumConstants()); - lowercase3(enumType.getEnumConstants()); - } + void possibleValues(final Class enumType) { + lowercase(enumType.getEnumConstants()); + lowercase2(enumType.getEnumConstants()); + lowercase3(enumType.getEnumConstants()); + } - > void lowercase(final T @Nullable ... items) {} + > void lowercase(final T @Nullable ... items) {} - > void lowercase2(final T @Nullable [] items) {} + > void lowercase2(final T @Nullable [] items) {} - void lowercase3(final T items) {} + void lowercase3(final T items) {} } interface Gen> { - T[] getConstants(); + T[] getConstants(); - T @Nullable [] getNullableConstants(); + T @Nullable [] getNullableConstants(); } class IncompatibleTypes { - void possibleValues(final Gen genType) { - lowercase(genType.getConstants()); - lowercase(genType.getNullableConstants()); - } + void possibleValues(final Gen genType) { + lowercase(genType.getConstants()); + lowercase(genType.getNullableConstants()); + } - void lowercase(final S items) {} + void lowercase(final S items) {} - void possibleValues2(final Gen genType) { - lowercase2(genType.getConstants()); - // :: error: (type.arguments.not.inferred) - lowercase2(genType.getNullableConstants()); - } + void possibleValues2(final Gen genType) { + lowercase2(genType.getConstants()); + // :: error: (type.arguments.not.inferred) + lowercase2(genType.getNullableConstants()); + } - void lowercase2(final S items) {} + void lowercase2(final S items) {} - void possibleValues3(final Gen genType) { - lowercase3(genType.getConstants()); - // :: error: (argument.type.incompatible) - lowercase3(genType.getNullableConstants()); - } + void possibleValues3(final Gen genType) { + lowercase3(genType.getConstants()); + // :: error: (argument.type.incompatible) + lowercase3(genType.getNullableConstants()); + } - void lowercase3(final @NonNull S items) {} + void lowercase3(final @NonNull S items) {} } diff --git a/checker/tests/nullness/Issue764.java b/checker/tests/nullness/Issue764.java index e33bdc9b077..0d1eb660032 100644 --- a/checker/tests/nullness/Issue764.java +++ b/checker/tests/nullness/Issue764.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue764 { - public static @Nullable Object field = null; + public static @Nullable Object field = null; - static class MyClass { - @RequiresNonNull("field") - public static void method() {} + static class MyClass { + @RequiresNonNull("field") + public static void method() {} - public void otherMethod() { - field = new Object(); - method(); - } + public void otherMethod() { + field = new Object(); + method(); + } - public void otherMethod2() { - // :: error: (contracts.precondition.not.satisfied) - method(); + public void otherMethod2() { + // :: error: (contracts.precondition.not.satisfied) + method(); + } } - } } diff --git a/checker/tests/nullness/Issue765.java b/checker/tests/nullness/Issue765.java index 10d54fdadc5..ae47273e0ca 100644 --- a/checker/tests/nullness/Issue765.java +++ b/checker/tests/nullness/Issue765.java @@ -1,14 +1,14 @@ // Test case for Issue 765 // https://github.com/typetools/checker-framework/issues/765 public class Issue765 { - Thread thread = new Thread() {}; + Thread thread = new Thread() {}; - void execute() { - thread = - new Thread() { - @Override - public void run() {} - }; - thread.start(); - } + void execute() { + thread = + new Thread() { + @Override + public void run() {} + }; + thread.start(); + } } diff --git a/checker/tests/nullness/Issue811.java b/checker/tests/nullness/Issue811.java index 976981a7c95..24a175bfc61 100644 --- a/checker/tests/nullness/Issue811.java +++ b/checker/tests/nullness/Issue811.java @@ -4,25 +4,25 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class Issue811 { - static class T { - void xyz() {} - } + static class T { + void xyz() {} + } - interface U { - void method(); - } + interface U { + void method(); + } - private final @NonNull T tField; - private U uField; + private final @NonNull T tField; + private U uField; - public Issue811(@NonNull T t) { - tField = t; - uField = - new U() { - @Override - public void method() { - tField.xyz(); - } - }; - } + public Issue811(@NonNull T t) { + tField = t; + uField = + new U() { + @Override + public void method() { + tField.xyz(); + } + }; + } } diff --git a/checker/tests/nullness/Issue829.java b/checker/tests/nullness/Issue829.java index 09e47ac6e22..e5b196536de 100644 --- a/checker/tests/nullness/Issue829.java +++ b/checker/tests/nullness/Issue829.java @@ -4,12 +4,12 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue829 { - public static @Nullable Double getDouble(boolean flag) { - return flag ? null : 1.0; - } + public static @Nullable Double getDouble(boolean flag) { + return flag ? null : 1.0; + } - public static Double getDoubleError(boolean flag) { - // :: error: (return.type.incompatible) - return flag ? null : 1.0; - } + public static Double getDoubleError(boolean flag) { + // :: error: (return.type.incompatible) + return flag ? null : 1.0; + } } diff --git a/checker/tests/nullness/Issue868.java b/checker/tests/nullness/Issue868.java index 6d2aa61766f..0a2f9960082 100644 --- a/checker/tests/nullness/Issue868.java +++ b/checker/tests/nullness/Issue868.java @@ -5,55 +5,55 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue868 { - interface MyList {} - - void test1(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void test2(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void test3(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void test4(E e) { - e.toString(); - } - - // :: warning: (explicit.annotation.ignored) - void test5(E e) { - e.toString(); - } - - // :: warning: (explicit.annotation.ignored) - void test6(E e) { - // :: error: (dereference.of.nullable) - e.toString(); - } - - void use() { - this.<@Nullable MyList>test1(null); - this.<@Nullable MyList>test2(null); - this.<@Nullable MyList>test3(null); - // :: error: (type.argument.type.incompatible) - this.<@Nullable MyList>test4(null); - // :: error: (type.argument.type.incompatible) - this.<@Nullable MyList>test5(null); - this.<@Nullable MyList>test6(null); - } - - void use2(T t, @NonNull T nonNullT) { - this.test1(t); - // :: error: (argument.type.incompatible) - this.<@NonNull T>test3(t); - this.<@NonNull T>test3(nonNullT); - // :: error: (type.argument.type.incompatible) - this.test5(t); - } + interface MyList {} + + void test1(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void test2(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void test3(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void test4(E e) { + e.toString(); + } + + // :: warning: (explicit.annotation.ignored) + void test5(E e) { + e.toString(); + } + + // :: warning: (explicit.annotation.ignored) + void test6(E e) { + // :: error: (dereference.of.nullable) + e.toString(); + } + + void use() { + this.<@Nullable MyList>test1(null); + this.<@Nullable MyList>test2(null); + this.<@Nullable MyList>test3(null); + // :: error: (type.argument.type.incompatible) + this.<@Nullable MyList>test4(null); + // :: error: (type.argument.type.incompatible) + this.<@Nullable MyList>test5(null); + this.<@Nullable MyList>test6(null); + } + + void use2(T t, @NonNull T nonNullT) { + this.test1(t); + // :: error: (argument.type.incompatible) + this.<@NonNull T>test3(t); + this.<@NonNull T>test3(nonNullT); + // :: error: (type.argument.type.incompatible) + this.test5(t); + } } diff --git a/checker/tests/nullness/Issue906.java b/checker/tests/nullness/Issue906.java index d55ad077714..71a22872d0b 100644 --- a/checker/tests/nullness/Issue906.java +++ b/checker/tests/nullness/Issue906.java @@ -4,12 +4,12 @@ * @author Michael Grafl */ public class Issue906 { - @SuppressWarnings("unchecked") - public void start(A a, Class cb) { - // :: error: (dereference.of.nullable) - Class c = (Class) a.getClass(); - x(a, c); - } + @SuppressWarnings("unchecked") + public void start(A a, Class cb) { + // :: error: (dereference.of.nullable) + Class c = (Class) a.getClass(); + x(a, c); + } - private void x(B a, Class c) {} + private void x(B a, Class c) {} } diff --git a/checker/tests/nullness/Issue961.java b/checker/tests/nullness/Issue961.java index 19cf230f4f6..e9f5e621e92 100644 --- a/checker/tests/nullness/Issue961.java +++ b/checker/tests/nullness/Issue961.java @@ -1,48 +1,49 @@ +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.NonNull; // Test case for Issue 961 // https://github.com/typetools/checker-framework/issues/961 public class Issue961 { - T method(T param, Map map) { - if (map.containsKey(param)) { - @NonNull Object o = map.get(param); - return param; + T method(T param, Map map) { + if (map.containsKey(param)) { + @NonNull Object o = map.get(param); + return param; + } + return param; } - return param; - } - abstract class MapContains { - // this isn't initialized, but just ignore the error. - @SuppressWarnings("method.invocation.invalid") - V def = setDef(); + abstract class MapContains { + // this isn't initialized, but just ignore the error. + @SuppressWarnings("method.invocation.invalid") + V def = setDef(); - Map map = new HashMap<>(); + Map map = new HashMap<>(); - V get(K p) { - if (!map.containsKey(p)) { - return def; - } - return map.get(p); - } - - abstract V setDef(); - } + V get(K p) { + if (!map.containsKey(p)) { + return def; + } + return map.get(p); + } - class MapContains2 { - String get1(Map map, Object k) { - if (!map.containsKey(k)) { - return ""; - } - return map.get(k); + abstract V setDef(); } - String get2(Map map, KeyTV k) { - if (!map.containsKey(k)) { - return ""; - } - return map.get(k); + class MapContains2 { + String get1(Map map, Object k) { + if (!map.containsKey(k)) { + return ""; + } + return map.get(k); + } + + String get2(Map map, KeyTV k) { + if (!map.containsKey(k)) { + return ""; + } + return map.get(k); + } } - } } diff --git a/checker/tests/nullness/Issue986.java b/checker/tests/nullness/Issue986.java index c5cf5164c55..f92956e6a2f 100644 --- a/checker/tests/nullness/Issue986.java +++ b/checker/tests/nullness/Issue986.java @@ -5,31 +5,31 @@ public class Issue986 { - public static void main(String[] args) { - String array[] = new String[3]; - array[0].length(); // NPE here - } + public static void main(String[] args) { + String array[] = new String[3]; + array[0].length(); // NPE here + } - // Flow should refine @MonotonicNonNull component types to @NonNull. - void testArr4(@NonNull Object @NonNull [] nno1, @MonotonicNonNull Object @NonNull [] lnno1) { - @MonotonicNonNull Object[] lnno2; - @NonNull Object[] nno2; - nno2 = nno1; - lnno2 = lnno1; - lnno2 = nno1; - // :: error: (assignment.type.incompatible) - nno2 = lnno1; - lnno2 = NullnessUtil.castNonNullDeep(nno1); - nno2 = NullnessUtil.castNonNullDeep(lnno1); - lnno2 = NullnessUtil.castNonNullDeep(nno1); - nno2 = NullnessUtil.castNonNullDeep(lnno1); - } + // Flow should refine @MonotonicNonNull component types to @NonNull. + void testArr4(@NonNull Object @NonNull [] nno1, @MonotonicNonNull Object @NonNull [] lnno1) { + @MonotonicNonNull Object[] lnno2; + @NonNull Object[] nno2; + nno2 = nno1; + lnno2 = lnno1; + lnno2 = nno1; + // :: error: (assignment.type.incompatible) + nno2 = lnno1; + lnno2 = NullnessUtil.castNonNullDeep(nno1); + nno2 = NullnessUtil.castNonNullDeep(lnno1); + lnno2 = NullnessUtil.castNonNullDeep(nno1); + nno2 = NullnessUtil.castNonNullDeep(lnno1); + } - // Flow should refine @MonotonicNonNull component types to @NonNull. - // This is a prerequisite for issue #986 (or for workarounds to issue #986). - void testArr5(@MonotonicNonNull Object @NonNull [] a) { - @MonotonicNonNull Object[] l5 = NullnessUtil.castNonNullDeep(a); - @NonNull Object[] l6 = l5; - @NonNull Object[] l7 = NullnessUtil.castNonNullDeep(a); - } + // Flow should refine @MonotonicNonNull component types to @NonNull. + // This is a prerequisite for issue #986 (or for workarounds to issue #986). + void testArr5(@MonotonicNonNull Object @NonNull [] a) { + @MonotonicNonNull Object[] l5 = NullnessUtil.castNonNullDeep(a); + @NonNull Object[] l6 = l5; + @NonNull Object[] l7 = NullnessUtil.castNonNullDeep(a); + } } diff --git a/checker/tests/nullness/Issue989.java b/checker/tests/nullness/Issue989.java index 01f311792b3..5f96a498a54 100644 --- a/checker/tests/nullness/Issue989.java +++ b/checker/tests/nullness/Issue989.java @@ -1,10 +1,11 @@ // Test case for Issue 989: // https://github.com/typetools/checker-framework/issues/989 +import org.checkerframework.checker.nullness.qual.NonNull; + import java.io.Serializable; import java.util.Collection; import java.util.List; -import org.checkerframework.checker.nullness.qual.NonNull; interface ListWrapper989a extends List<@NonNull E> {} @@ -13,15 +14,15 @@ interface ListWrapper989b extends Serializable, List<@NonNull E> {} interface ListWrapper989c extends Collection<@NonNull E>, Serializable, List<@NonNull E> {} public class Issue989 { - void usea(ListWrapper989a list) { - list.get(0); - } + void usea(ListWrapper989a list) { + list.get(0); + } - void useb(ListWrapper989b list) { - list.get(0); - } + void useb(ListWrapper989b list) { + list.get(0); + } - void usec(ListWrapper989c list) { - list.get(0); - } + void usec(ListWrapper989c list) { + list.get(0); + } } diff --git a/checker/tests/nullness/Iterate.java b/checker/tests/nullness/Iterate.java index d36975723f8..5230aa14797 100644 --- a/checker/tests/nullness/Iterate.java +++ b/checker/tests/nullness/Iterate.java @@ -1,9 +1,9 @@ package wildcards; public class Iterate { - void method(Iterable files) { - for (Object file : files) { - file.getClass(); + void method(Iterable files) { + for (Object file : files) { + file.getClass(); + } } - } } diff --git a/checker/tests/nullness/IteratorEarlyExit.java b/checker/tests/nullness/IteratorEarlyExit.java index 44b30e59724..41f134027b9 100644 --- a/checker/tests/nullness/IteratorEarlyExit.java +++ b/checker/tests/nullness/IteratorEarlyExit.java @@ -1,38 +1,39 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.io.*; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class IteratorEarlyExit { - public static void m1() { - List array = new ArrayList<>(); - String local = null; - for (String str : array) { - local = str; - break; + public static void m1() { + List array = new ArrayList<>(); + String local = null; + for (String str : array) { + local = str; + break; + } + // :: error: (dereference.of.nullable) + System.out.println(local.length()); } - // :: error: (dereference.of.nullable) - System.out.println(local.length()); - } - public static void m2() { - List array = new ArrayList<>(); - String local = null; - for (String str : array) { - local = str; + public static void m2() { + List array = new ArrayList<>(); + String local = null; + for (String str : array) { + local = str; + } + // :: error: (dereference.of.nullable) + System.out.println(local.length()); } - // :: error: (dereference.of.nullable) - System.out.println(local.length()); - } - public static void m3() { - List array = new ArrayList<>(); - Object local = new Object(); - for (String str : array) { - // :: error: (dereference.of.nullable) - System.out.println(local.toString()); - // The next iteration might throw a NPE - local = null; + public static void m3() { + List array = new ArrayList<>(); + Object local = new Object(); + for (String str : array) { + // :: error: (dereference.of.nullable) + System.out.println(local.toString()); + // The next iteration might throw a NPE + local = null; + } } - } } diff --git a/checker/tests/nullness/JUnitNull.java b/checker/tests/nullness/JUnitNull.java index 5f060b4d53c..37b3e32d556 100644 --- a/checker/tests/nullness/JUnitNull.java +++ b/checker/tests/nullness/JUnitNull.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Assertions; class JUnitNull { - { - Assertions.assertEquals(null, "dummy"); - } + { + Assertions.assertEquals(null, "dummy"); + } } diff --git a/checker/tests/nullness/JavaCopExplosion.java b/checker/tests/nullness/JavaCopExplosion.java index ba36faad208..fde4396cc7c 100644 --- a/checker/tests/nullness/JavaCopExplosion.java +++ b/checker/tests/nullness/JavaCopExplosion.java @@ -1,123 +1,124 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class JavaCopExplosion { - public static class ExplosiveException extends Exception {} + public static class ExplosiveException extends Exception {} - @NonNull Integer m_nni = 1; - final String m_astring; + @NonNull Integer m_nni = 1; + final String m_astring; - JavaCopExplosion() { - // m_nni = 1;\ - m_astring = "hi"; - try { - throw new RuntimeException(); - } catch (Exception e) { - System.out.println(m_astring.length()); + JavaCopExplosion() { + // m_nni = 1;\ + m_astring = "hi"; + try { + throw new RuntimeException(); + } catch (Exception e) { + System.out.println(m_astring.length()); + } + return; } - return; - } - static void main(String @NonNull [] args) { - @NonNull String s = "Dan"; - String s2; - s2 = null; - // :: warning: (nulltest.redundant) - if (s2 != null || s != null) { - // :: error: (assignment.type.incompatible) - s = s2; - } else { - s = new String("Levitan"); - } - s2 = args[0]; - // :: error: (dereference.of.nullable) - System.out.println("Possibly cause null pointer with this: " + s2.length()); - // :: warning: (nulltest.redundant) - if (s2 == null) { - // do nothing - } else { - System.out.println("Can't cause null pointer here: " + s2.length()); - s = s2; - } - // :: warning: (nulltest.redundant) - if (s == null ? s2 != null : s2 != null) { - s = s2; + static void main(String @NonNull [] args) { + @NonNull String s = "Dan"; + String s2; + s2 = null; + // :: warning: (nulltest.redundant) + if (s2 != null || s != null) { + // :: error: (assignment.type.incompatible) + s = s2; + } else { + s = new String("Levitan"); + } + s2 = args[0]; + // :: error: (dereference.of.nullable) + System.out.println("Possibly cause null pointer with this: " + s2.length()); + // :: warning: (nulltest.redundant) + if (s2 == null) { + // do nothing + } else { + System.out.println("Can't cause null pointer here: " + s2.length()); + s = s2; + } + // :: warning: (nulltest.redundant) + if (s == null ? s2 != null : s2 != null) { + s = s2; + } + System.out.println("Hello " + s); + System.out.println("Hello " + s.length()); + f(); } - System.out.println("Hello " + s); - System.out.println("Hello " + s.length()); - f(); - } - private static int f() { - while (true) { - try { - throw new ExplosiveException(); - } finally { - // break; - return 1; - // throw new RuntimeException(); - } + private static int f() { + while (true) { + try { + throw new ExplosiveException(); + } finally { + // break; + return 1; + // throw new RuntimeException(); + } + } } - } - public static int foo() { - final int v; - int x; - Integer z; - Integer y; - @NonNull Integer nnz = 3; - z = Integer.valueOf(5); - try { - x = 3; - x = 5; - // y = z; - nnz = z; - z = null; - // :: error: (assignment.type.incompatible) - nnz = z; + public static int foo() { + final int v; + int x; + Integer z; + Integer y; + @NonNull Integer nnz = 3; + z = Integer.valueOf(5); + try { + x = 3; + x = 5; + // y = z; + nnz = z; + z = null; + // :: error: (assignment.type.incompatible) + nnz = z; - while (z == null) { - break; - } - // :: error: (assignment.type.incompatible) - nnz = z; - while (z == null) { - // do nothing - } - nnz = z; - // v = 1; - return 1; - // v = 2; - // throw new RuntimeException (); - } catch (NullPointerException e) { - e.printStackTrace(); - // e = null; - // v = 1; - } catch (RuntimeException e) { - // nnz = z; - // v = 2; - } finally { - // v = 1 + x; + while (z == null) { + break; + } + // :: error: (assignment.type.incompatible) + nnz = z; + while (z == null) { + // do nothing + } + nnz = z; + // v = 1; + return 1; + // v = 2; + // throw new RuntimeException (); + } catch (NullPointerException e) { + e.printStackTrace(); + // e = null; + // v = 1; + } catch (RuntimeException e) { + // nnz = z; + // v = 2; + } finally { + // v = 1 + x; + } + return 1; + // return v + x; } - return 1; - // return v + x; - } - private void bar(List<@NonNull String> ss, String b, String c) { - @NonNull String a; - // :: error: (iterating.over.nullable) - for (@NonNull String s : ss) { - a = s; - } - if (b == null || b.length() == 0) { - System.out.println("hey"); - } - if (b != null) { - // :: error: (dereference.of.nullable) - for (; b.length() > 0; b = null) { - System.out.println(b.length()); - } + private void bar(List<@NonNull String> ss, String b, String c) { + @NonNull String a; + // :: error: (iterating.over.nullable) + for (@NonNull String s : ss) { + a = s; + } + if (b == null || b.length() == 0) { + System.out.println("hey"); + } + if (b != null) { + // :: error: (dereference.of.nullable) + for (; b.length() > 0; b = null) { + System.out.println(b.length()); + } + } } - } } diff --git a/checker/tests/nullness/JavaCopFlow.java b/checker/tests/nullness/JavaCopFlow.java index 2c327331f42..959848862e0 100644 --- a/checker/tests/nullness/JavaCopFlow.java +++ b/checker/tests/nullness/JavaCopFlow.java @@ -3,218 +3,218 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class JavaCopFlow { - public void testIf(String str) { + public void testIf(String str) { - // String str = "foo"; - @NonNull String a; - if (str != null) { - a = str; - } - - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + // String str = "foo"; + @NonNull String a; + if (str != null) { + a = str; + } - public void testIfNoBlock(String str) { - - // String str = "foo"; - @NonNull String a; - if (str != null) { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testIfNoBlock(String str) { - public void testElse(String str) { + // String str = "foo"; + @NonNull String a; + if (str != null) { + a = str; + } - // String str = "foo"; - @NonNull String a; - if (str == null) { - testAssert(""); - } else { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testElse(String str) { - public void testElseNoBlock(String str) { + // String str = "foo"; + @NonNull String a; + if (str == null) { + testAssert(""); + } else { + a = str; + } - // String str = "foo"; - @NonNull String a; - if (str == null) { - testAssert(""); - } else { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testElseNoBlock(String str) { - public void testReturnIf(String str) { + // String str = "foo"; + @NonNull String a; + if (str == null) { + testAssert(""); + } else { + a = str; + } - // String str = "foo"; - if (str == null) { - testAssert(""); - return; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; + public void testReturnIf(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + // String str = "foo"; + if (str == null) { + testAssert(""); + return; + } - public void testReturnElse(String str) { + @NonNull String a = str; - // String str = "foo"; - if (str != null) { - testAssert(""); - } else { - return; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; + public void testReturnElse(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + // String str = "foo"; + if (str != null) { + testAssert(""); + } else { + return; + } - public void testThrowIf(String str) { + @NonNull String a = str; - // String str = "foo"; - if (str == null) { - testAssert(""); - throw new RuntimeException("foo"); + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; + public void testThrowIf(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + // String str = "foo"; + if (str == null) { + testAssert(""); + throw new RuntimeException("foo"); + } - public void testThrowElse(String str) { + @NonNull String a = str; - // String str = "foo"; - if (str != null) { - testAssert(""); - } else { - throw new RuntimeException("foo"); + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; + public void testThrowElse(String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + // String str = "foo"; + if (str != null) { + testAssert(""); + } else { + throw new RuntimeException("foo"); + } - public void testAssert(@Nullable String str) { + @NonNull String a = str; - assert str != null : "@AssumeAssertion(nullness)"; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - @NonNull String a = str; + public void testAssert(@Nullable String str) { - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + assert str != null : "@AssumeAssertion(nullness)"; - public void testWhile(String str) { + @NonNull String a = str; - // String str = "foo"; - while (str != null) { - @NonNull String a = str; - break; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testWhile(String str) { - public void testIfInstanceOf(String str) { + // String str = "foo"; + while (str != null) { + @NonNull String a = str; + break; + } - // String str = "foo"; - @NonNull String a; - if (str instanceof String) { - a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testIfInstanceOf(String str) { - public void testNew() { + // String str = "foo"; + @NonNull String a; + if (str instanceof String) { + a = str; + } - String str = "foo"; - @NonNull String a = str; + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; + } - str = null; - // :: error: (assignment.type.incompatible) - @NonNull String b = str; - } + public void testNew() { - public void testExit(String str) { + String str = "foo"; + @NonNull String a = str; - // String str = null; - if (str == null) { - System.exit(0); + str = null; + // :: error: (assignment.type.incompatible) + @NonNull String b = str; } - @NonNull String a = str; - } - - void methodThatThrowsRuntime() { - throw new RuntimeException(); - } + public void testExit(String str) { - public void retestWhile(@Nullable String str) { + // String str = null; + if (str == null) { + System.exit(0); + } - while (str != null) { - @NonNull String a = str; - break; + @NonNull String a = str; } - int i = 0; - while (true) { - // :: error: (assignment.type.incompatible) - @NonNull String a = str; - str = null; - i++; - if (i > 2) break; + void methodThatThrowsRuntime() { + throw new RuntimeException(); } - str = null; - @NonNull String b = "hi"; - try { - // :: error: (assignment.type.incompatible) - b = str; - methodThatThrowsRuntime(); - str = "bar"; - } finally { - // :: error: (assignment.type.incompatible) - b = str; + public void retestWhile(@Nullable String str) { + + while (str != null) { + @NonNull String a = str; + break; + } + + int i = 0; + while (true) { + // :: error: (assignment.type.incompatible) + @NonNull String a = str; + str = null; + i++; + if (i > 2) break; + } + + str = null; + @NonNull String b = "hi"; + try { + // :: error: (assignment.type.incompatible) + b = str; + methodThatThrowsRuntime(); + str = "bar"; + } finally { + // :: error: (assignment.type.incompatible) + b = str; + } + + str = null; + // :: error: (assignment.type.incompatible) + b = str; + + str = "hi"; + b = (String) str; } - - str = null; - // :: error: (assignment.type.incompatible) - b = str; - - str = "hi"; - b = (String) str; - } } diff --git a/checker/tests/nullness/JavaCopRandomTests.java b/checker/tests/nullness/JavaCopRandomTests.java index 185cc9ba463..8ecde2bdf51 100644 --- a/checker/tests/nullness/JavaCopRandomTests.java +++ b/checker/tests/nullness/JavaCopRandomTests.java @@ -1,27 +1,27 @@ import org.checkerframework.checker.nullness.qual.*; public class JavaCopRandomTests { - final int a; - final int b = 1; - final int c; + final int a; + final int b = 1; + final int c; - JavaCopRandomTests() { - String s = null; - a = 2; - } + JavaCopRandomTests() { + String s = null; + a = 2; + } - JavaCopRandomTests(String s) throws Exception { - // this(); - a = 2; - if (a > 1) { - throw new Exception("dude"); + JavaCopRandomTests(String s) throws Exception { + // this(); + a = 2; + if (a > 1) { + throw new Exception("dude"); + } + throw new RuntimeException("dude"); } - throw new RuntimeException("dude"); - } - // initializer block - { - c = 4; - // throw new Exception("dude"); - } + // initializer block + { + c = 4; + // throw new Exception("dude"); + } } diff --git a/checker/tests/nullness/JavaExprContext.java b/checker/tests/nullness/JavaExprContext.java index 5ec7b637964..28e5ceeafd6 100644 --- a/checker/tests/nullness/JavaExprContext.java +++ b/checker/tests/nullness/JavaExprContext.java @@ -1,6 +1,7 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; // See issue 241: https://github.com/typetools/checker-framework/issues/241 @@ -14,153 +15,162 @@ public class JavaExprContext { - // Classes to perform tests on + // Classes to perform tests on - // The methods return booleans instead of void simply so they can - // be tested as field initializers. + // The methods return booleans instead of void simply so they can + // be tested as field initializers. - public static class staticGraphClass { - private Map adjList = new HashMap<>(); + public static class staticGraphClass { + private Map adjList = new HashMap<>(); - public boolean addEdge(@KeyFor("adjList") String source) { - return true; - } + public boolean addEdge(@KeyFor("adjList") String source) { + return true; + } - public static boolean addEdge2(@KeyFor("#2.adjList") String source, staticGraphClass theGraph) { - return true; - } + public static boolean addEdge2( + @KeyFor("#2.adjList") String source, staticGraphClass theGraph) { + return true; + } - public boolean addEdge3(@KeyFor("this.adjList") String source) { - return true; + public boolean addEdge3(@KeyFor("this.adjList") String source) { + return true; + } } - } - public class nonstaticGraphClass { - private Map adjList = new HashMap<>(); + public class nonstaticGraphClass { + private Map adjList = new HashMap<>(); - public boolean addEdge(@KeyFor("adjList") String source) { - return true; - } + public boolean addEdge(@KeyFor("adjList") String source) { + return true; + } - public boolean addEdge2(@KeyFor("this.adjList") String source) { - return true; + public boolean addEdge2(@KeyFor("this.adjList") String source) { + return true; + } } - } - - // Non-static field initialization - - staticGraphClass graphField1 = new staticGraphClass(); - nonstaticGraphClass graphField2 = new nonstaticGraphClass(); - - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("graphField1.adjList") String key1 = ""; - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("graphField2.adjList") String key2 = ""; - - boolean b1 = staticGraphClass.addEdge2(key1, graphField1); - boolean b2 = graphField1.addEdge(key1); - boolean b3 = graphField1.addEdge2(key1, graphField1); - boolean b4 = graphField1.addEdge3(key1); - - boolean b5 = graphField2.addEdge(key2); - boolean b6 = graphField2.addEdge2(key2); - - // Classes that perform tests - - public class nonstaticTestClass { + // Non-static field initialization staticGraphClass graphField1 = new staticGraphClass(); nonstaticGraphClass graphField2 = new nonstaticGraphClass(); - public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField1); - graphField1.addEdge(hero); - graphField1.addEdge2( - hero, graphField1); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField1.addEdge3(hero); - } - - public void buildGraph2(@KeyFor("graphField2.adjList") String hero) { - graphField2.addEdge(hero); - graphField2.addEdge2(hero); - } - - public void buildGraph3(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - staticGraphClass.addEdge2(hero, myGraph); - myGraph.addEdge(hero); - myGraph.addEdge2( - hero, myGraph); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - myGraph.addEdge3(hero); - } - - public void buildGraph4(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - myGraph.addEdge(hero); - myGraph.addEdge2(hero); - } - } - - public static class staticTestClass { - - staticGraphClass graphField1 = new staticGraphClass(); - static staticGraphClass graphField2 = new staticGraphClass(); - - public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField1); - graphField1.addEdge(hero); - graphField1.addEdge2( - hero, graphField1); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField1.addEdge3(hero); - } - - public void buildGraph3(@KeyFor("graphField2.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField2); - graphField2.addEdge(hero); - graphField2.addEdge2( - hero, graphField2); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField2.addEdge3(hero); - } - - public void buildGraph5(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - staticGraphClass.addEdge2(hero, myGraph); - myGraph.addEdge(hero); - myGraph.addEdge2( - hero, myGraph); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - myGraph.addEdge3(hero); - } - - public void buildGraph6(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - myGraph.addEdge(hero); - myGraph.addEdge2(hero); - } - - public static void buildGraph7(@KeyFor("graphField2.adjList") String hero) { - staticGraphClass.addEdge2(hero, graphField2); - graphField2.addEdge(hero); - graphField2.addEdge2( - hero, graphField2); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - graphField2.addEdge3(hero); - } - - public static void buildGraph9(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - staticGraphClass.addEdge2(hero, myGraph); - myGraph.addEdge(hero); - myGraph.addEdge2( - hero, myGraph); // Calling a static method from an instance object. Ensuring this - // doesn't confuse the JavaExpression parsing. - myGraph.addEdge3(hero); - } - - public static void buildGraph10( - nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { - myGraph.addEdge(hero); - myGraph.addEdge2(hero); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("graphField1.adjList") String key1 = ""; + + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("graphField2.adjList") String key2 = ""; + + boolean b1 = staticGraphClass.addEdge2(key1, graphField1); + boolean b2 = graphField1.addEdge(key1); + boolean b3 = graphField1.addEdge2(key1, graphField1); + boolean b4 = graphField1.addEdge3(key1); + + boolean b5 = graphField2.addEdge(key2); + boolean b6 = graphField2.addEdge2(key2); + + // Classes that perform tests + + public class nonstaticTestClass { + + staticGraphClass graphField1 = new staticGraphClass(); + nonstaticGraphClass graphField2 = new nonstaticGraphClass(); + + public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField1); + graphField1.addEdge(hero); + graphField1.addEdge2( + hero, + graphField1); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField1.addEdge3(hero); + } + + public void buildGraph2(@KeyFor("graphField2.adjList") String hero) { + graphField2.addEdge(hero); + graphField2.addEdge2(hero); + } + + public void buildGraph3(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + staticGraphClass.addEdge2(hero, myGraph); + myGraph.addEdge(hero); + myGraph.addEdge2( + hero, + myGraph); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + myGraph.addEdge3(hero); + } + + public void buildGraph4(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + myGraph.addEdge(hero); + myGraph.addEdge2(hero); + } + } + + public static class staticTestClass { + + staticGraphClass graphField1 = new staticGraphClass(); + static staticGraphClass graphField2 = new staticGraphClass(); + + public void buildGraph1(@KeyFor("graphField1.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField1); + graphField1.addEdge(hero); + graphField1.addEdge2( + hero, + graphField1); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField1.addEdge3(hero); + } + + public void buildGraph3(@KeyFor("graphField2.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField2); + graphField2.addEdge(hero); + graphField2.addEdge2( + hero, + graphField2); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField2.addEdge3(hero); + } + + public void buildGraph5(staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + staticGraphClass.addEdge2(hero, myGraph); + myGraph.addEdge(hero); + myGraph.addEdge2( + hero, + myGraph); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + myGraph.addEdge3(hero); + } + + public void buildGraph6(nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + myGraph.addEdge(hero); + myGraph.addEdge2(hero); + } + + public static void buildGraph7(@KeyFor("graphField2.adjList") String hero) { + staticGraphClass.addEdge2(hero, graphField2); + graphField2.addEdge(hero); + graphField2.addEdge2( + hero, + graphField2); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + graphField2.addEdge3(hero); + } + + public static void buildGraph9( + staticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + staticGraphClass.addEdge2(hero, myGraph); + myGraph.addEdge(hero); + myGraph.addEdge2( + hero, + myGraph); // Calling a static method from an instance object. Ensuring this + // doesn't confuse the JavaExpression parsing. + myGraph.addEdge3(hero); + } + + public static void buildGraph10( + nonstaticGraphClass myGraph, @KeyFor("#1.adjList") String hero) { + myGraph.addEdge(hero); + myGraph.addEdge2(hero); + } } - } } diff --git a/checker/tests/nullness/KeyForAutoboxing.java b/checker/tests/nullness/KeyForAutoboxing.java index c960d6ef940..baecb423e3a 100644 --- a/checker/tests/nullness/KeyForAutoboxing.java +++ b/checker/tests/nullness/KeyForAutoboxing.java @@ -7,56 +7,56 @@ public abstract class KeyForAutoboxing { - public void working1(Object key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); + public void working1(Object key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); + } + m.get(key).toString(); } - m.get(key).toString(); - } - public void working2(Integer key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); + public void working2(Integer key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); + } + m.get(key).toString(); } - m.get(key).toString(); - } - public void working3(Double key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); + public void working3(Double key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); + } + m.get(key).toString(); } - m.get(key).toString(); - } - public void notWorking1(int key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); + public void notWorking1(int key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); + } + m.get(key).toString(); // Should not generate error but does } - m.get(key).toString(); // Should not generate error but does - } - public void notWorking2(double key, Map m) { - if (!m.containsKey(key)) { - m.put(key, new Object()); + public void notWorking2(double key, Map m) { + if (!m.containsKey(key)) { + m.put(key, new Object()); + } + m.get(key).toString(); // Should not generate error but does } - m.get(key).toString(); // Should not generate error but does - } - public void notWorking3(double key, Map m) { - if (m.containsKey(key)) { - m.get(key).toString(); // Should not generate error but does + public void notWorking3(double key, Map m) { + if (m.containsKey(key)) { + m.get(key).toString(); // Should not generate error but does + } } - } - public void notWorking4(double key, Map m) { - if (m.get(key) != null) { - m.get(key).toString(); // Should not generate error but does + public void notWorking4(double key, Map m) { + if (m.get(key) != null) { + m.get(key).toString(); // Should not generate error but does + } } - } - public void notWorking5(double key, Map m) { - if (m.get(Double.valueOf(key)) != null) { - m.get(Double.valueOf(key)).toString(); // Should not generate error but does + public void notWorking5(double key, Map m) { + if (m.get(Double.valueOf(key)) != null) { + m.get(Double.valueOf(key)).toString(); // Should not generate error but does + } } - } } diff --git a/checker/tests/nullness/KeyForChecked.java b/checker/tests/nullness/KeyForChecked.java index a736b48b2a7..ebfe724c751 100644 --- a/checker/tests/nullness/KeyForChecked.java +++ b/checker/tests/nullness/KeyForChecked.java @@ -1,9 +1,3 @@ -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.NonNull; @@ -13,147 +7,154 @@ import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND) public class KeyForChecked { - interface KFMap<@KeyForBottom K extends @NonNull Object, V extends @NonNull Object> { + interface KFMap<@KeyForBottom K extends @NonNull Object, V extends @NonNull Object> { + @Covariant(0) + public static interface Entry< + @KeyForBottom K1 extends @Nullable Object, V1 extends @Nullable Object> { + K1 getKey(); + + V1 getValue(); + } + + @Pure + boolean containsKey(@Nullable Object a1); + + @Pure + @Nullable V get(@Nullable Object a1); + + @Nullable V put(K a1, V a2); + + Set<@KeyFor("this") K> keySet(); + + Set> entrySet(); + + KFIterator iterator(); + } + + class KFHashMap<@KeyForBottom K2 extends @NonNull Object, V2 extends @NonNull Object> + implements KFMap { + @Pure + public boolean containsKey(@Nullable Object a1) { + return false; + } + + @Pure + public @Nullable V2 get(@Nullable Object a1) { + return null; + } + + public @Nullable V2 put(K2 a1, V2 a2) { + return null; + } + + public Set<@KeyFor("this") K2> keySet() { + return new HashSet<@KeyFor("this") K2>(); + } + + public Set> entrySet() { + return new HashSet>(); + } + + public KFIterator iterator() { + return new KFIterator(); + } + } + @Covariant(0) - public static interface Entry< - @KeyForBottom K1 extends @Nullable Object, V1 extends @Nullable Object> { - K1 getKey(); + class KFIterator<@KeyForBottom E extends @Nullable Object> {} + + void incorrect1(Object map) { + String nonkey = ""; + // :: error: (assignment.type.incompatible) + @KeyFor("map") String key = nonkey; + } - V1 getValue(); + void correct1(Object map) { + String nonkey = ""; + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("map") String key = nonkey; } - @Pure - boolean containsKey(@Nullable Object a1); + void incorrect2() { + KFMap m = new KFHashMap<>(); + m.put("a", new Object()); + m.put("b", new Object()); + m.put("c", new Object()); + + Collection<@KeyFor("m") String> coll = m.keySet(); - @Pure - @Nullable V get(@Nullable Object a1); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("m") String newkey = "new"; - @Nullable V put(K a1, V a2); + coll.add(newkey); + // TODO: at this point, the @KeyFor annotation is violated + m.put("new", new Object()); + } - Set<@KeyFor("this") K> keySet(); + void correct2() { + KFMap m = new KFHashMap<>(); + m.put("a", new Object()); + m.put("b", new Object()); + m.put("c", new Object()); - Set> entrySet(); + Collection<@KeyFor("m") String> coll = m.keySet(); - KFIterator iterator(); - } + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("m") String newkey = "new"; - class KFHashMap<@KeyForBottom K2 extends @NonNull Object, V2 extends @NonNull Object> - implements KFMap { - @Pure - public boolean containsKey(@Nullable Object a1) { - return false; + m.put(newkey, new Object()); + coll.add(newkey); } - @Pure - public @Nullable V2 get(@Nullable Object a1) { - return null; + void iter() { + KFMap emap = new KFHashMap<>(); + Set<@KeyFor("emap") String> s = emap.keySet(); + Iterator<@KeyFor("emap") String> it = emap.keySet().iterator(); + Iterator<@KeyFor("emap") String> it2 = s.iterator(); + + Collection<@KeyFor("emap") String> x = Collections.unmodifiableSet(emap.keySet()); + + for (@KeyFor("emap") String st : s) {} + for (String st : s) {} + Object bubu = new Object(); + // :: error: (enhancedfor.type.incompatible) + for (@KeyFor("bubu") String st : s) {} } - public @Nullable V2 put(K2 a1, V2 a2) { - return null; + void dominators(KFMap> preds) { + for (T node : preds.keySet()) {} + + for (@KeyFor("preds") T node : preds.keySet()) {} } - public Set<@KeyFor("this") K2> keySet() { - return new HashSet<@KeyFor("this") K2>(); + void entrySet() { + KFMap emap = new KFHashMap<>(); + Set> es = emap.entrySet(); + + // KeyFor has to be explicit on the component to Entry sets because + // a) it's not clear which map the Entry set may have come from + // b) and there is no guarantee the map is still accessible + // :: error: (assignment.type.incompatible) + Set> es2 = emap.entrySet(); } - public Set> entrySet() { - return new HashSet>(); + public static void mapToString(KFMap m) { + Set> eset = m.entrySet(); + + for (KFMap.Entry<@KeyFor("m") K, V> entry : m.entrySet()) {} } - public KFIterator iterator() { - return new KFIterator(); + void testWF(KFMap m) { + KFIterator it = m.iterator(); } - } - - @Covariant(0) - class KFIterator<@KeyForBottom E extends @Nullable Object> {} - - void incorrect1(Object map) { - String nonkey = ""; - // :: error: (assignment.type.incompatible) - @KeyFor("map") String key = nonkey; - } - - void correct1(Object map) { - String nonkey = ""; - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("map") String key = nonkey; - } - - void incorrect2() { - KFMap m = new KFHashMap<>(); - m.put("a", new Object()); - m.put("b", new Object()); - m.put("c", new Object()); - - Collection<@KeyFor("m") String> coll = m.keySet(); - - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("m") String newkey = "new"; - - coll.add(newkey); - // TODO: at this point, the @KeyFor annotation is violated - m.put("new", new Object()); - } - - void correct2() { - KFMap m = new KFHashMap<>(); - m.put("a", new Object()); - m.put("b", new Object()); - m.put("c", new Object()); - - Collection<@KeyFor("m") String> coll = m.keySet(); - - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("m") String newkey = "new"; - - m.put(newkey, new Object()); - coll.add(newkey); - } - - void iter() { - KFMap emap = new KFHashMap<>(); - Set<@KeyFor("emap") String> s = emap.keySet(); - Iterator<@KeyFor("emap") String> it = emap.keySet().iterator(); - Iterator<@KeyFor("emap") String> it2 = s.iterator(); - - Collection<@KeyFor("emap") String> x = Collections.unmodifiableSet(emap.keySet()); - - for (@KeyFor("emap") String st : s) {} - for (String st : s) {} - Object bubu = new Object(); - // :: error: (enhancedfor.type.incompatible) - for (@KeyFor("bubu") String st : s) {} - } - - void dominators(KFMap> preds) { - for (T node : preds.keySet()) {} - - for (@KeyFor("preds") T node : preds.keySet()) {} - } - - void entrySet() { - KFMap emap = new KFHashMap<>(); - Set> es = emap.entrySet(); - - // KeyFor has to be explicit on the component to Entry sets because - // a) it's not clear which map the Entry set may have come from - // b) and there is no guarantee the map is still accessible - // :: error: (assignment.type.incompatible) - Set> es2 = emap.entrySet(); - } - - public static void mapToString(KFMap m) { - Set> eset = m.entrySet(); - - for (KFMap.Entry<@KeyFor("m") K, V> entry : m.entrySet()) {} - } - - void testWF(KFMap m) { - KFIterator it = m.iterator(); - } } diff --git a/checker/tests/nullness/KeyForDiamond.java b/checker/tests/nullness/KeyForDiamond.java index 7a3a02a801c..15a3a6de8c0 100644 --- a/checker/tests/nullness/KeyForDiamond.java +++ b/checker/tests/nullness/KeyForDiamond.java @@ -1,16 +1,17 @@ -import java.util.*; import org.checkerframework.checker.nullness.qual.KeyFor; +import java.util.*; + public class KeyForDiamond { - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); - public void method() { - Map<@KeyFor("map") Integer, Double> paths = new HashMap<>(); - Set<@KeyFor("map") Integer> set = new HashSet<>(); - List<@KeyFor("map") Integer> list = new ArrayList<>(); - } + public void method() { + Map<@KeyFor("map") Integer, Double> paths = new HashMap<>(); + Set<@KeyFor("map") Integer> set = new HashSet<>(); + List<@KeyFor("map") Integer> list = new ArrayList<>(); + } - public static void main(String[] args) { - KeyForDiamond t = new KeyForDiamond(); - } + public static void main(String[] args) { + KeyForDiamond t = new KeyForDiamond(); + } } diff --git a/checker/tests/nullness/KeyForFlow.java b/checker/tests/nullness/KeyForFlow.java index 05ccf8c0613..6e3117dece2 100644 --- a/checker/tests/nullness/KeyForFlow.java +++ b/checker/tests/nullness/KeyForFlow.java @@ -1,172 +1,173 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Vector; -import org.checkerframework.checker.nullness.qual.*; public class KeyForFlow extends HashMap { - String k = "key"; - HashMap m = new HashMap<>(); + String k = "key"; + HashMap m = new HashMap<>(); + + void testContainsKeyForLocalKeyAndLocalMap() { + String k_local = "key"; + HashMap m_local = new HashMap<>(); + + if (m_local.containsKey(k_local)) { + @KeyFor("m_local") Object s = k_local; + } + + // :: error: (assignment.type.incompatible) + @KeyFor("m_local") String s2 = k_local; + } + + void testContainsKeyForLocalKeyAndFieldMap() { + String k_local = "key"; + + if (m.containsKey(k_local)) { + @KeyFor("m") Object s = k_local; + } + + // :: error: (assignment.type.incompatible) + @KeyFor("m") String s2 = k_local; + } + + void testContainsKeyForFieldKeyAndLocalMap() { + HashMap m_local = new HashMap<>(); + + if (m_local.containsKey(k)) { + @KeyFor("m_local") Object s = k; + } - void testContainsKeyForLocalKeyAndLocalMap() { - String k_local = "key"; - HashMap m_local = new HashMap<>(); + // :: error: (assignment.type.incompatible) + @KeyFor("m_local") String s2 = k; + } + + void testContainsKeyForFieldKeyAndFieldMap() { + if (m.containsKey(k)) { + @KeyFor("m") Object s = k; + } - if (m_local.containsKey(k_local)) { - @KeyFor("m_local") Object s = k_local; + // :: error: (assignment.type.incompatible) + @KeyFor("m") String s2 = k; } - // :: error: (assignment.type.incompatible) - @KeyFor("m_local") String s2 = k_local; - } + static String k_s = "key"; - void testContainsKeyForLocalKeyAndFieldMap() { - String k_local = "key"; + void testContainsKeyForStaticKeyAndFieldMap() { + if (m.containsKey(k_s)) { + @KeyFor("m") Object s = k_s; + } - if (m.containsKey(k_local)) { - @KeyFor("m") Object s = k_local; + // :: error: (assignment.type.incompatible) + @KeyFor("m") String s2 = k_s; } - // :: error: (assignment.type.incompatible) - @KeyFor("m") String s2 = k_local; - } + static HashMap m_s = new HashMap<>(); - void testContainsKeyForFieldKeyAndLocalMap() { - HashMap m_local = new HashMap<>(); + void testContainsKeyForFieldKeyAndStaticMap() { + if (m_s.containsKey(k)) { + // Currently for this to work, the user must write @KeyFor("classname.static_field") + @KeyFor("m_s") Object s = k; + } - if (m_local.containsKey(k)) { - @KeyFor("m_local") Object s = k; + // :: error: (assignment.type.incompatible) + @KeyFor("m_s") String s2 = k; } - // :: error: (assignment.type.incompatible) - @KeyFor("m_local") String s2 = k; - } + void testContainsKeyForFieldKeyAndReceiverMap() { + if (containsKey(k)) { + @KeyFor("this") Object s = k; + } - void testContainsKeyForFieldKeyAndFieldMap() { - if (m.containsKey(k)) { - @KeyFor("m") Object s = k; + // :: error: (assignment.type.incompatible) + @KeyFor("this") String s2 = k; } - // :: error: (assignment.type.incompatible) - @KeyFor("m") String s2 = k; - } + // TODO: The diamond operator does not work here: + // Vector<@KeyFor("m2") String> coll = new Vector<>(); + // Figure out why not. + Vector<@KeyFor("m2") String> coll = new Vector<@KeyFor("m2") String>(); + HashMap m2 = new HashMap<>(); + String k2 = "key2"; + + void testCallingPutAfterAdd() { + // :: error: (argument.type.incompatible) + coll.add(k2); + m2.put(k2, new Object()); + } - static String k_s = "key"; + void testPutForLocalKeyAndLocalMap() { + HashMap m2_local = new HashMap<>(); + Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); + String k2_local = "key2"; - void testContainsKeyForStaticKeyAndFieldMap() { - if (m.containsKey(k_s)) { - @KeyFor("m") Object s = k_s; + m2_local.put(k2_local, new Object()); + coll_local.add(k2_local); } - // :: error: (assignment.type.incompatible) - @KeyFor("m") String s2 = k_s; - } + void testPutForLocalKeyAndFieldMap() { + String k2_local = "key2"; - static HashMap m_s = new HashMap<>(); + m2.put(k2_local, new Object()); + coll.add(k2_local); + } + + void testPutForFieldKeyAndLocalMap() { + HashMap m2_local = new HashMap<>(); + Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); + + m2_local.put(k2, new Object()); + coll_local.add(k2); + } - void testContainsKeyForFieldKeyAndStaticMap() { - if (m_s.containsKey(k)) { - // Currently for this to work, the user must write @KeyFor("classname.static_field") - @KeyFor("m_s") Object s = k; + void testPutForFieldKeyAndFieldMap() { + m2.put(k2, new Object()); + coll.add(k2); } - // :: error: (assignment.type.incompatible) - @KeyFor("m_s") String s2 = k; - } + /* + This scenario is not working since in Vector, "this" gets translated to "coll_local". + The same thing happens if the collection is a field instead of a local. + However this seems like a low-priority scenario to enable. + + void testPutForFieldKeyAndReceiverMap() { + Vector<@KeyFor("this") String> coll_local = new Vector<>(); + + put(k2, new Object()); + coll_local.add(k2); + }*/ + + class foo { + public HashMap m = new HashMap<>(); + } + + void testContainsKeyForFieldKeyAndMapFieldOfOtherClass() { + foo f = new foo(); + + if (f.m.containsKey(k)) { + @KeyFor("f.m") Object s = k; + } - void testContainsKeyForFieldKeyAndReceiverMap() { - if (containsKey(k)) { - @KeyFor("this") Object s = k; + // :: error: (assignment.type.incompatible) + @KeyFor("f.m") String s2 = k; } - // :: error: (assignment.type.incompatible) - @KeyFor("this") String s2 = k; - } - - // TODO: The diamond operator does not work here: - // Vector<@KeyFor("m2") String> coll = new Vector<>(); - // Figure out why not. - Vector<@KeyFor("m2") String> coll = new Vector<@KeyFor("m2") String>(); - HashMap m2 = new HashMap<>(); - String k2 = "key2"; - - void testCallingPutAfterAdd() { - // :: error: (argument.type.incompatible) - coll.add(k2); - m2.put(k2, new Object()); - } - - void testPutForLocalKeyAndLocalMap() { - HashMap m2_local = new HashMap<>(); - Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); - String k2_local = "key2"; - - m2_local.put(k2_local, new Object()); - coll_local.add(k2_local); - } - - void testPutForLocalKeyAndFieldMap() { - String k2_local = "key2"; - - m2.put(k2_local, new Object()); - coll.add(k2_local); - } - - void testPutForFieldKeyAndLocalMap() { - HashMap m2_local = new HashMap<>(); - Vector<@KeyFor("m2_local") String> coll_local = new Vector<>(); - - m2_local.put(k2, new Object()); - coll_local.add(k2); - } - - void testPutForFieldKeyAndFieldMap() { - m2.put(k2, new Object()); - coll.add(k2); - } - - /* - This scenario is not working since in Vector, "this" gets translated to "coll_local". - The same thing happens if the collection is a field instead of a local. - However this seems like a low-priority scenario to enable. - - void testPutForFieldKeyAndReceiverMap() { - Vector<@KeyFor("this") String> coll_local = new Vector<>(); - - put(k2, new Object()); - coll_local.add(k2); - }*/ - - class foo { - public HashMap m = new HashMap<>(); - } - - void testContainsKeyForFieldKeyAndMapFieldOfOtherClass() { - foo f = new foo(); - - if (f.m.containsKey(k)) { - @KeyFor("f.m") Object s = k; + void testPutForFieldKeyAndMapFieldOfOtherClass() { + foo f = new foo(); + Vector<@KeyFor("f.m") String> coll_local = new Vector<>(); + f.m.put(k2, new Object()); + coll_local.add(k2); } - // :: error: (assignment.type.incompatible) - @KeyFor("f.m") String s2 = k; - } - - void testPutForFieldKeyAndMapFieldOfOtherClass() { - foo f = new foo(); - Vector<@KeyFor("f.m") String> coll_local = new Vector<>(); - f.m.put(k2, new Object()); - coll_local.add(k2); - } - - /*public void testAddToListInsteadOfMap(List<@KeyFor("#4") String> la, String b, @KeyFor("#4") String c, Map a) { - // Disabled error (assignment.type.incompatible) - List ls1 = la; - List<@KeyFor("#4") String> ls2 = la; - ls1.add(b); - // Disabled error (argument.type.incompatible) - la.add(b); - ls2.add(c); - la.add(c); - @NonNull String astr = a.get(ls2.get(0)); - }*/ + /*public void testAddToListInsteadOfMap(List<@KeyFor("#4") String> la, String b, @KeyFor("#4") String c, Map a) { + // Disabled error (assignment.type.incompatible) + List ls1 = la; + List<@KeyFor("#4") String> ls2 = la; + ls1.add(b); + // Disabled error (argument.type.incompatible) + la.add(b); + ls2.add(c); + la.add(c); + @NonNull String astr = a.get(ls2.get(0)); + }*/ } diff --git a/checker/tests/nullness/KeyForIssue328.java b/checker/tests/nullness/KeyForIssue328.java index 24ab6fac586..fe92c91e85c 100644 --- a/checker/tests/nullness/KeyForIssue328.java +++ b/checker/tests/nullness/KeyForIssue328.java @@ -1,20 +1,21 @@ // Test case for Issue 328: // https://github.com/typetools/checker-framework/issues/328 -import java.util.Map; import org.checkerframework.checker.nullness.qual.*; +import java.util.Map; + public class KeyForIssue328 { - public static void m(Map a, Map b, Object ka, Object kb) { - if (a.containsKey(ka)) { - @NonNull Object i = a.get(ka); // OK - } - if (b.containsKey(kb)) { - @NonNull Object i = b.get(kb); // OK - } - if (a.containsKey(ka) && b.containsKey(kb)) { - @NonNull Object i = a.get(ka); // OK - @NonNull Object j = b.get(kb); // OK + public static void m(Map a, Map b, Object ka, Object kb) { + if (a.containsKey(ka)) { + @NonNull Object i = a.get(ka); // OK + } + if (b.containsKey(kb)) { + @NonNull Object i = b.get(kb); // OK + } + if (a.containsKey(ka) && b.containsKey(kb)) { + @NonNull Object i = a.get(ka); // OK + @NonNull Object j = b.get(kb); // OK + } } - } } diff --git a/checker/tests/nullness/KeyForLocalSideEffect.java b/checker/tests/nullness/KeyForLocalSideEffect.java index c58f34cc128..5c1bca529e1 100644 --- a/checker/tests/nullness/KeyForLocalSideEffect.java +++ b/checker/tests/nullness/KeyForLocalSideEffect.java @@ -1,23 +1,24 @@ -import java.util.HashMap; import org.checkerframework.checker.nullness.qual.*; +import java.util.HashMap; + public class KeyForLocalSideEffect { - String k = "key"; - HashMap m = new HashMap<>(); + String k = "key"; + HashMap m = new HashMap<>(); - void testContainsKeyForFieldKeyAndLocalMap() { - HashMap m_local = m; + void testContainsKeyForFieldKeyAndLocalMap() { + HashMap m_local = m; - if (m_local.containsKey(k)) { - @KeyFor("m_local") String s = k; - havoc(); - // TODO: This should be an error, because s is no longer a key for m_local. - @NonNull Integer val = m_local.get(s); + if (m_local.containsKey(k)) { + @KeyFor("m_local") String s = k; + havoc(); + // TODO: This should be an error, because s is no longer a key for m_local. + @NonNull Integer val = m_local.get(s); + } } - } - void havoc() { - m = new HashMap<>(); - } + void havoc() { + m = new HashMap<>(); + } } diff --git a/checker/tests/nullness/KeyForLocalVariable.java b/checker/tests/nullness/KeyForLocalVariable.java index 089473c956d..baa1283bcb3 100644 --- a/checker/tests/nullness/KeyForLocalVariable.java +++ b/checker/tests/nullness/KeyForLocalVariable.java @@ -1,31 +1,32 @@ // Test for Checker Framework issue 795 // https://github.com/typetools/checker-framework/issues/795 +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; public class KeyForLocalVariable { - public static void localVariableShadowing() { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("m0") String kk; - { - Map m0 = new HashMap<>(); - @SuppressWarnings("keyfor") - @KeyFor("m0") String k = "key"; - // :: error: (assignment.type.incompatible) - kk = k; + public static void localVariableShadowing() { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("m0") String kk; + { + Map m0 = new HashMap<>(); + @SuppressWarnings("keyfor") + @KeyFor("m0") String k = "key"; + // :: error: (assignment.type.incompatible) + kk = k; + } + { + Map m0 = new HashMap<>(); + // :: error: (assignment.type.incompatible) + @KeyFor("m0") String k2 = kk; + } } - { - Map m0 = new HashMap<>(); - // :: error: (assignment.type.incompatible) - @KeyFor("m0") String k2 = kk; - } - } - public static void invalidLocalVariable() { - // :: error: (expression.unparsable.type.invalid) - @KeyFor("foobar") String kk; - } + public static void invalidLocalVariable() { + // :: error: (expression.unparsable.type.invalid) + @KeyFor("foobar") String kk; + } } diff --git a/checker/tests/nullness/KeyForLub.java b/checker/tests/nullness/KeyForLub.java index 0308fcca614..94b2b62fca7 100644 --- a/checker/tests/nullness/KeyForLub.java +++ b/checker/tests/nullness/KeyForLub.java @@ -1,42 +1,43 @@ package keyfor; -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.PolyKeyFor; import org.checkerframework.checker.nullness.qual.UnknownKeyFor; +import java.util.HashMap; +import java.util.Map; + public class KeyForLub { - public static boolean flag; - Map map1 = new HashMap<>(); - Map map2 = new HashMap<>(); - Map map3 = new HashMap<>(); - - void method( - @KeyFor({"map1", "map2"}) String key12, - @KeyFor({"map1", "map3"}) String key13, - @UnknownKeyFor String unknown) { - @KeyFor("map1") String key1 = flag ? key12 : key13; - - // :: error: (assignment.type.incompatible) - @KeyFor({"map1", "map2"}) String key2 = flag ? key12 : key13; - - // :: error: (assignment.type.incompatible) - @KeyFor({"map1", "map2"}) String key3 = flag ? key12 : unknown; - } - - @PolyKeyFor String poly1(@KeyFor("map1") String key1, @PolyKeyFor String poly) { - // :: error: (return.type.incompatible) - return flag ? key1 : poly; - } - - // :: error: (type.invalid.annotations.on.location) - void poly2(@PolyKeyFor String poly, @UnknownKeyFor String unknown, @KeyForBottom String bot) { - // :: error: (assignment.type.incompatible) - @PolyKeyFor String s1 = flag ? poly : unknown; - @PolyKeyFor String s2 = flag ? poly : bot; - // :: error: (assignment.type.incompatible) :: error: (type.invalid.annotations.on.location) - @KeyForBottom String s3 = flag ? poly : bot; - } + public static boolean flag; + Map map1 = new HashMap<>(); + Map map2 = new HashMap<>(); + Map map3 = new HashMap<>(); + + void method( + @KeyFor({"map1", "map2"}) String key12, + @KeyFor({"map1", "map3"}) String key13, + @UnknownKeyFor String unknown) { + @KeyFor("map1") String key1 = flag ? key12 : key13; + + // :: error: (assignment.type.incompatible) + @KeyFor({"map1", "map2"}) String key2 = flag ? key12 : key13; + + // :: error: (assignment.type.incompatible) + @KeyFor({"map1", "map2"}) String key3 = flag ? key12 : unknown; + } + + @PolyKeyFor String poly1(@KeyFor("map1") String key1, @PolyKeyFor String poly) { + // :: error: (return.type.incompatible) + return flag ? key1 : poly; + } + + // :: error: (type.invalid.annotations.on.location) + void poly2(@PolyKeyFor String poly, @UnknownKeyFor String unknown, @KeyForBottom String bot) { + // :: error: (assignment.type.incompatible) + @PolyKeyFor String s1 = flag ? poly : unknown; + @PolyKeyFor String s2 = flag ? poly : bot; + // :: error: (assignment.type.incompatible) :: error: (type.invalid.annotations.on.location) + @KeyForBottom String s3 = flag ? poly : bot; + } } diff --git a/checker/tests/nullness/KeyForMultiple.java b/checker/tests/nullness/KeyForMultiple.java index 58c4ac6029e..c22be7d16cd 100644 --- a/checker/tests/nullness/KeyForMultiple.java +++ b/checker/tests/nullness/KeyForMultiple.java @@ -2,42 +2,45 @@ // @skip-test until the bug is fixed. +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.KeyFor; public class KeyForMultiple { - void m1() { + void m1() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); - Set<@KeyFor({"sharedCounts1"}) String> sharedCountsKeys1 = sharedCounts1.keySet(); - } + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); + Set<@KeyFor({"sharedCounts1"}) String> sharedCountsKeys1 = sharedCounts1.keySet(); + } - void m2() { + void m2() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); - Set<@KeyFor({"sharedBooks", "sharedCounts1"}) String> otherChars1 = sharedCounts1.keySet(); - } + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedCounts1 = new HashMap<>(); + Set<@KeyFor({"sharedBooks", "sharedCounts1"}) String> otherChars1 = sharedCounts1.keySet(); + } - void m3() { + void m3() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = new HashMap<>(); - Set<@KeyFor({"sharedCounts2"}) String> sharedCountsKeys2 = sharedCounts2.keySet(); - } + Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = + new HashMap<>(); + Set<@KeyFor({"sharedCounts2"}) String> sharedCountsKeys2 = sharedCounts2.keySet(); + } - void m4() { + void m4() { - Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); + Map<@KeyFor({"sharedBooks"}) String, Integer> sharedBooks = new HashMap<>(); - Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = new HashMap<>(); - Set<@KeyFor({"sharedBooks", "sharedCounts2"}) String> otherChars2 = sharedCounts2.keySet(); - } + Map<@KeyFor({"sharedBooks", "sharedCounts2"}) String, Integer> sharedCounts2 = + new HashMap<>(); + Set<@KeyFor({"sharedBooks", "sharedCounts2"}) String> otherChars2 = sharedCounts2.keySet(); + } } diff --git a/checker/tests/nullness/KeyForPolymorphism.java b/checker/tests/nullness/KeyForPolymorphism.java index 8acc3b2b12c..462727d834e 100644 --- a/checker/tests/nullness/KeyForPolymorphism.java +++ b/checker/tests/nullness/KeyForPolymorphism.java @@ -1,19 +1,20 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; // test related to issue 429: https://github.com/typetools/checker-framework/issues/429 public class KeyForPolymorphism { - Map m1 = new HashMap<>(); - Map m2 = new HashMap<>(); + Map m1 = new HashMap<>(); + Map m2 = new HashMap<>(); - void method(@KeyFor("m1") String k1m1, @KeyFor("m2") String k1m2) { - @KeyFor("m1") String k2m1 = identity1(k1m1); - @KeyFor("m2") String k2m2 = identity1(k1m2); - } + void method(@KeyFor("m1") String k1m1, @KeyFor("m2") String k1m2) { + @KeyFor("m1") String k2m1 = identity1(k1m1); + @KeyFor("m2") String k2m2 = identity1(k1m2); + } - static @PolyKeyFor String identity1(@PolyKeyFor String arg) { - return arg; - } + static @PolyKeyFor String identity1(@PolyKeyFor String arg) { + return arg; + } } diff --git a/checker/tests/nullness/KeyForPostcondition.java b/checker/tests/nullness/KeyForPostcondition.java index 04c13fd4f75..efed1c0d8b2 100644 --- a/checker/tests/nullness/KeyForPostcondition.java +++ b/checker/tests/nullness/KeyForPostcondition.java @@ -1,47 +1,48 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; import org.checkerframework.checker.nullness.qual.KeyFor; -public class KeyForPostcondition { +import java.util.HashMap; +import java.util.Map; - public static Map m = new HashMap<>(); +public class KeyForPostcondition { - // public static @KeyFor("m") String key = "hello"; + public static Map m = new HashMap<>(); - public static boolean b; + // public static @KeyFor("m") String key = "hello"; - @EnsuresKeyFor(value = "#1", map = "m") - public void putKey(String x) { - m.put(x, 22); - } + public static boolean b; - public void usePutKey(String x) { - // :: error: (assignment.type.incompatible) - @KeyFor("m") String a = x; - putKey(x); - @KeyFor("m") String b = x; - } + @EnsuresKeyFor(value = "#1", map = "m") + public void putKey(String x) { + m.put(x, 22); + } - @EnsuresKeyForIf(expression = "#1", result = true, map = "m") - public boolean tryPutKey(String x) { - if (b) { - putKey(x); - return true; - } else { - return false; + public void usePutKey(String x) { + // :: error: (assignment.type.incompatible) + @KeyFor("m") String a = x; + putKey(x); + @KeyFor("m") String b = x; } - } - public void useTryPutKey(String x) { - // :: error: (assignment.type.incompatible) - @KeyFor("m") String a = x; - if (tryPutKey(x)) { - @KeyFor("m") String b = x; + @EnsuresKeyForIf(expression = "#1", result = true, map = "m") + public boolean tryPutKey(String x) { + if (b) { + putKey(x); + return true; + } else { + return false; + } } - // :: error: (assignment.type.incompatible) - @KeyFor("m") String c = x; - } + public void useTryPutKey(String x) { + // :: error: (assignment.type.incompatible) + @KeyFor("m") String a = x; + if (tryPutKey(x)) { + @KeyFor("m") String b = x; + } + + // :: error: (assignment.type.incompatible) + @KeyFor("m") String c = x; + } } diff --git a/checker/tests/nullness/KeyForPropagation.java b/checker/tests/nullness/KeyForPropagation.java index efec4c99932..9e5e7e8890f 100644 --- a/checker/tests/nullness/KeyForPropagation.java +++ b/checker/tests/nullness/KeyForPropagation.java @@ -1,8 +1,9 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; // interface Dest { // } @@ -15,21 +16,21 @@ public class KeyForPropagation { - { - List<@KeyFor("a") String> a = new ArrayList(); - } + { + List<@KeyFor("a") String> a = new ArrayList(); + } - static { - List<@KeyFor("b") String> b = new ArrayList(); - } + static { + List<@KeyFor("b") String> b = new ArrayList(); + } - List<@KeyFor("c") String> c = new ArrayList(); + List<@KeyFor("c") String> c = new ArrayList(); - void method() { - List<@KeyFor("d") String> d = new ArrayList(); - } + void method() { + List<@KeyFor("d") String> d = new ArrayList(); + } - void method(Map v) { - Set ks = v.keySet(); - } + void method(Map v) { + Set ks = v.keySet(); + } } diff --git a/checker/tests/nullness/KeyForShadowing.java b/checker/tests/nullness/KeyForShadowing.java index df517bf49ab..46d5aca3b7e 100644 --- a/checker/tests/nullness/KeyForShadowing.java +++ b/checker/tests/nullness/KeyForShadowing.java @@ -1,72 +1,73 @@ // Test for Checker Framework issue 273: // https://github.com/typetools/checker-framework/issues/273 +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; public class KeyForShadowing { - public static void main(String... p) { - Map m0 = new HashMap<>(); - Map m1 = new HashMap<>(); - String k = "key"; - m0.put(k, 1); // k is @KeyFor("m0") after this line + public static void main(String... p) { + Map m0 = new HashMap<>(); + Map m1 = new HashMap<>(); + String k = "key"; + m0.put(k, 1); // k is @KeyFor("m0") after this line - // We expect an error for the next one since we are not respecting the method contract. It - // expects the key to be for the second parameter, not the first. + // We expect an error for the next one since we are not respecting the method contract. It + // expects the key to be for the second parameter, not the first. - // :: error: (argument.type.incompatible) - getMap3(m0, m1, k).toString(); + // :: error: (argument.type.incompatible) + getMap3(m0, m1, k).toString(); - // We expect an error for the next one since although we are respecting the method contract, - // since the key is for the first parameter, the Nullness Checker is misinterpreting "m1" to - // be the local m1 to this method, and not the first parameter to the method. + // We expect an error for the next one since although we are respecting the method contract, + // since the key is for the first parameter, the Nullness Checker is misinterpreting "m1" to + // be the local m1 to this method, and not the first parameter to the method. - // :: error: (argument.type.incompatible) - getMap2(m0, m1, k).toString(); + // :: error: (argument.type.incompatible) + getMap2(m0, m1, k).toString(); - // :: error: (argument.type.incompatible) - getMap1(m0, m1, k).toString(); + // :: error: (argument.type.incompatible) + getMap1(m0, m1, k).toString(); - getMap4(m0, m1, k).toString(); - } + getMap4(m0, m1, k).toString(); + } - public static @NonNull Integer getMap1( - Map m1, // m1,m0 flipped - Map m0, - // :: error: (expression.unparsable.type.invalid) - @KeyFor("m0") String k) { - // :: error: (return.type.incompatible) - return m0.get(k); - } + public static @NonNull Integer getMap1( + Map m1, // m1,m0 flipped + Map m0, + // :: error: (expression.unparsable.type.invalid) + @KeyFor("m0") String k) { + // :: error: (return.type.incompatible) + return m0.get(k); + } - public static @NonNull Integer getMap2( - Map m1, // m1,m0 flipped - Map m0, - // :: error: (expression.unparsable.type.invalid) - @KeyFor("m1") String k) { - // This method body is incorrect. - // We expect this error because we are indicating that - // the key is for m1, so m0.get(k) is @Nullable. - // :: error: (return.type.incompatible) - return m0.get(k); - } + public static @NonNull Integer getMap2( + Map m1, // m1,m0 flipped + Map m0, + // :: error: (expression.unparsable.type.invalid) + @KeyFor("m1") String k) { + // This method body is incorrect. + // We expect this error because we are indicating that + // the key is for m1, so m0.get(k) is @Nullable. + // :: error: (return.type.incompatible) + return m0.get(k); + } - public static @NonNull Integer getMap3( - Map m1, // m1,m0 flipped - Map m0, - @KeyFor("#2") String k) { - return m0.get(k); - } + public static @NonNull Integer getMap3( + Map m1, // m1,m0 flipped + Map m0, + @KeyFor("#2") String k) { + return m0.get(k); + } - public static @NonNull Integer getMap4( - Map m1, // m1,m0 flipped - Map m0, - @KeyFor("#1") String k) { - // This method body is incorrect. - // We expect this error because we are indicating that - // the key is for m1, so m0.get(k) is @Nullable. - // :: error: (return.type.incompatible) - return m0.get(k); - } + public static @NonNull Integer getMap4( + Map m1, // m1,m0 flipped + Map m0, + @KeyFor("#1") String k) { + // This method body is incorrect. + // We expect this error because we are indicating that + // the key is for m1, so m0.get(k) is @Nullable. + // :: error: (return.type.incompatible) + return m0.get(k); + } } diff --git a/checker/tests/nullness/KeyForStaticField.java b/checker/tests/nullness/KeyForStaticField.java index 38c0da1e5c3..f87920ad6ed 100644 --- a/checker/tests/nullness/KeyForStaticField.java +++ b/checker/tests/nullness/KeyForStaticField.java @@ -2,30 +2,31 @@ // https://github.com/typetools/checker-framework/issues/877 // @skip-test until the issue is fixed. +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; public class KeyForStaticField { - @SuppressWarnings("keyfor") - public static final @KeyFor("this.map") String STATIC_KEY = "some text"; + @SuppressWarnings("keyfor") + public static final @KeyFor("this.map") String STATIC_KEY = "some text"; - private Map map; + private Map map; - public KeyForStaticField() { - map = new HashMap<>(); - map.put(STATIC_KEY, 0); - } + public KeyForStaticField() { + map = new HashMap<>(); + map.put(STATIC_KEY, 0); + } - /** Returns the value for the given key, which must be present in the map. */ - public Integer getValue(@KeyFor("this.map") String key) { - assert map.containsKey(key) : "Map does not contain key " + key; - return map.get(key); - } + /** Returns the value for the given key, which must be present in the map. */ + public Integer getValue(@KeyFor("this.map") String key) { + assert map.containsKey(key) : "Map does not contain key " + key; + return map.get(key); + } - public void m(KeyForStaticField other) { - getValue(STATIC_KEY); - this.getValue(STATIC_KEY); - other.getValue(STATIC_KEY); - } + public void m(KeyForStaticField other) { + getValue(STATIC_KEY); + this.getValue(STATIC_KEY); + other.getValue(STATIC_KEY); + } } diff --git a/checker/tests/nullness/KeyForSubst.java b/checker/tests/nullness/KeyForSubst.java index 0572c080438..a5c38ef908e 100644 --- a/checker/tests/nullness/KeyForSubst.java +++ b/checker/tests/nullness/KeyForSubst.java @@ -1,43 +1,44 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + public class KeyForSubst { - /* - static class MyClass { - public T next() { return null; } - } - */ - - @KeyFor("#1") String getMain(Object m) { - throw new RuntimeException(); - } - - List<@KeyFor("#1") String> getDeep(Object m) { - throw new RuntimeException(); - } - - @KeyFor("#1") List<@KeyFor("#2") String> getBoth(Object l, Object m) { - throw new RuntimeException(); - } - - // OK, I think the annotation on the index is overdoing it, but it works. - @KeyFor("#1") String @KeyFor("#2") [] getArray(Object l, Object m) { - throw new RuntimeException(); - } - - public void testAssignMain(Object lastMap) { - @KeyFor("lastMap") String key = getMain(lastMap); - } - - public void testAssignDeep(Object lastMap) { - List<@KeyFor("lastMap") String> key = getDeep(lastMap); - } - - public void testAssignBoth(Object lastMap, Object newMap) { - @KeyFor("lastMap") List<@KeyFor("newMap") String> key = getBoth(lastMap, newMap); - } - - public void testAssignArray(Object lastMap, Object newMap) { - @KeyFor("lastMap") String @KeyFor("newMap") [] key = getArray(lastMap, newMap); - } + /* + static class MyClass { + public T next() { return null; } + } + */ + + @KeyFor("#1") String getMain(Object m) { + throw new RuntimeException(); + } + + List<@KeyFor("#1") String> getDeep(Object m) { + throw new RuntimeException(); + } + + @KeyFor("#1") List<@KeyFor("#2") String> getBoth(Object l, Object m) { + throw new RuntimeException(); + } + + // OK, I think the annotation on the index is overdoing it, but it works. + @KeyFor("#1") String @KeyFor("#2") [] getArray(Object l, Object m) { + throw new RuntimeException(); + } + + public void testAssignMain(Object lastMap) { + @KeyFor("lastMap") String key = getMain(lastMap); + } + + public void testAssignDeep(Object lastMap) { + List<@KeyFor("lastMap") String> key = getDeep(lastMap); + } + + public void testAssignBoth(Object lastMap, Object newMap) { + @KeyFor("lastMap") List<@KeyFor("newMap") String> key = getBoth(lastMap, newMap); + } + + public void testAssignArray(Object lastMap, Object newMap) { + @KeyFor("lastMap") String @KeyFor("newMap") [] key = getArray(lastMap, newMap); + } } diff --git a/checker/tests/nullness/KeyForSubtyping.java b/checker/tests/nullness/KeyForSubtyping.java index bb0e03cd87b..7ba7654f9a0 100644 --- a/checker/tests/nullness/KeyForSubtyping.java +++ b/checker/tests/nullness/KeyForSubtyping.java @@ -1,129 +1,130 @@ -import java.util.HashMap; import org.checkerframework.checker.nullness.qual.*; +import java.util.HashMap; + public class KeyForSubtyping { - HashMap mapA = new HashMap<>(); - HashMap mapB = new HashMap<>(); - HashMap mapC = new HashMap<>(); - - public void testSubtypeAssignments( - String not_a_key, - @KeyFor("this.mapA") String a, - @KeyFor("this.mapB") String b, - @KeyFor({"this.mapA", "this.mapB"}) String ab) { - // Try the error cases first, otherwise dataflow will change the inferred annotations on the - // variables such that a line of code can have an effect on a subsequent line of code. We - // want each of these tests to be independent. - - // :: error: (assignment.type.incompatible) - ab = a; - // :: error: (assignment.type.incompatible) - ab = b; - // :: error: (assignment.type.incompatible) - a = b; - // :: error: (assignment.type.incompatible) - a = not_a_key; - // :: error: (assignment.type.incompatible) - b = not_a_key; - // :: error: (assignment.type.incompatible) - ab = not_a_key; - - // Now try the success cases - - a = ab; - b = ab; - not_a_key = ab; - not_a_key = a; - } - - public void testDataFlow( - String not_yet_a_key, - @KeyFor("this.mapA") String a, - @KeyFor("this.mapB") String b, - @KeyFor({"this.mapA", "this.mapB"}) String ab) { - // Test that when a valid assignment is made, dataflow transfers the - // KeyFor type qualifier from the right hand side to the left hand side. - - // :: error: (argument.type.incompatible) - method1(not_yet_a_key); - not_yet_a_key = a; - method1(not_yet_a_key); - - method1(a); - // :: error: (argument.type.incompatible) - method1(b); - method1(ab); - - b = ab; - method1(b); - } - - public void testSetOrdering( - @KeyFor({"this.mapC", "this.mapA"}) String ac, - @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { - // Test that the order of elements in the set doesn't matter when doing subtyping checks, - // @KeyFor("A, B, C") <: @KeyFor("C, A") - - // Try the error case first - see the note in method testSubtypeAssignments - - // :: error: (assignment.type.incompatible) - abc = ac; - - ac = abc; - } - - public void testDataflowTransitivity( - @KeyFor({"this.mapA"}) String a, - @KeyFor({"this.mapA", "this.mapB"}) String ab, - @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { - ab = abc; - // At this point, dataflow should have refined the type of ab to - // @KeyFor({"this.mapA","this.mapB","this.mapC"}) - a = ab; - // At this point, dataflow should have refined the type of a to - // @KeyFor({"this.mapA","this.mapB","this.mapC"}) - - // This would not succeed without the previous two assignments, but should now because of - // dataflow. - abc = a; - } - - private void method1(@KeyFor("this.mapA") String a) {} - - private void testWithNullnessAnnotation( - String not_a_key, - @KeyFor("this.mapA") String a, - @KeyFor("this.mapB") String b, - @Nullable @KeyFor({"this.mapA", "this.mapB"}) String ab) { - // These fail only because a @Nullable RHS cannot be assigned to a @NonNull LHS. - - // :: error: (assignment.type.incompatible) - a = ab; - // :: error: (assignment.type.incompatible) - b = ab; - // :: error: (assignment.type.incompatible) - not_a_key = ab; - - not_a_key = a; // Succeeds because both sides are @NonNull - } - - // Test overriding - - static class Super { - HashMap map1 = new HashMap<>(); - HashMap map2 = new HashMap<>(); - - void method1(@KeyFor({"this.map1", "this.map2"}) String s) {} - - void method2(@KeyFor("this.map1") String s) {} - } - - static class Sub extends Super { - @Override - void method1(@KeyFor("this.map1") String s) {} - - @Override - // :: error: (override.param.invalid) - void method2(@KeyFor({"this.map1", "this.map2"}) String s) {} - } + HashMap mapA = new HashMap<>(); + HashMap mapB = new HashMap<>(); + HashMap mapC = new HashMap<>(); + + public void testSubtypeAssignments( + String not_a_key, + @KeyFor("this.mapA") String a, + @KeyFor("this.mapB") String b, + @KeyFor({"this.mapA", "this.mapB"}) String ab) { + // Try the error cases first, otherwise dataflow will change the inferred annotations on the + // variables such that a line of code can have an effect on a subsequent line of code. We + // want each of these tests to be independent. + + // :: error: (assignment.type.incompatible) + ab = a; + // :: error: (assignment.type.incompatible) + ab = b; + // :: error: (assignment.type.incompatible) + a = b; + // :: error: (assignment.type.incompatible) + a = not_a_key; + // :: error: (assignment.type.incompatible) + b = not_a_key; + // :: error: (assignment.type.incompatible) + ab = not_a_key; + + // Now try the success cases + + a = ab; + b = ab; + not_a_key = ab; + not_a_key = a; + } + + public void testDataFlow( + String not_yet_a_key, + @KeyFor("this.mapA") String a, + @KeyFor("this.mapB") String b, + @KeyFor({"this.mapA", "this.mapB"}) String ab) { + // Test that when a valid assignment is made, dataflow transfers the + // KeyFor type qualifier from the right hand side to the left hand side. + + // :: error: (argument.type.incompatible) + method1(not_yet_a_key); + not_yet_a_key = a; + method1(not_yet_a_key); + + method1(a); + // :: error: (argument.type.incompatible) + method1(b); + method1(ab); + + b = ab; + method1(b); + } + + public void testSetOrdering( + @KeyFor({"this.mapC", "this.mapA"}) String ac, + @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { + // Test that the order of elements in the set doesn't matter when doing subtyping checks, + // @KeyFor("A, B, C") <: @KeyFor("C, A") + + // Try the error case first - see the note in method testSubtypeAssignments + + // :: error: (assignment.type.incompatible) + abc = ac; + + ac = abc; + } + + public void testDataflowTransitivity( + @KeyFor({"this.mapA"}) String a, + @KeyFor({"this.mapA", "this.mapB"}) String ab, + @KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) { + ab = abc; + // At this point, dataflow should have refined the type of ab to + // @KeyFor({"this.mapA","this.mapB","this.mapC"}) + a = ab; + // At this point, dataflow should have refined the type of a to + // @KeyFor({"this.mapA","this.mapB","this.mapC"}) + + // This would not succeed without the previous two assignments, but should now because of + // dataflow. + abc = a; + } + + private void method1(@KeyFor("this.mapA") String a) {} + + private void testWithNullnessAnnotation( + String not_a_key, + @KeyFor("this.mapA") String a, + @KeyFor("this.mapB") String b, + @Nullable @KeyFor({"this.mapA", "this.mapB"}) String ab) { + // These fail only because a @Nullable RHS cannot be assigned to a @NonNull LHS. + + // :: error: (assignment.type.incompatible) + a = ab; + // :: error: (assignment.type.incompatible) + b = ab; + // :: error: (assignment.type.incompatible) + not_a_key = ab; + + not_a_key = a; // Succeeds because both sides are @NonNull + } + + // Test overriding + + static class Super { + HashMap map1 = new HashMap<>(); + HashMap map2 = new HashMap<>(); + + void method1(@KeyFor({"this.map1", "this.map2"}) String s) {} + + void method2(@KeyFor("this.map1") String s) {} + } + + static class Sub extends Super { + @Override + void method1(@KeyFor("this.map1") String s) {} + + @Override + // :: error: (override.param.invalid) + void method2(@KeyFor({"this.map1", "this.map2"}) String s) {} + } } diff --git a/checker/tests/nullness/KeyForTypeVar.java b/checker/tests/nullness/KeyForTypeVar.java index cdfa81fbd1b..f14f9e14e23 100644 --- a/checker/tests/nullness/KeyForTypeVar.java +++ b/checker/tests/nullness/KeyForTypeVar.java @@ -1,11 +1,12 @@ -import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Map; + public class KeyForTypeVar { - <@KeyForBottom E extends @KeyFor("#1") T> T method(Map m, E key) { - @NonNull String s = m.get(key); - throw new RuntimeException(); - } + <@KeyForBottom E extends @KeyFor("#1") T> T method(Map m, E key) { + @NonNull String s = m.get(key); + throw new RuntimeException(); + } } diff --git a/checker/tests/nullness/KeyFor_DirectionsFinder.java b/checker/tests/nullness/KeyFor_DirectionsFinder.java index f5190ade1bc..9762b45cd20 100644 --- a/checker/tests/nullness/KeyFor_DirectionsFinder.java +++ b/checker/tests/nullness/KeyFor_DirectionsFinder.java @@ -1,44 +1,45 @@ // @skip-test +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; public class KeyFor_DirectionsFinder { - class GeoPoint {} + class GeoPoint {} - class StreetSegment {} + class StreetSegment {} - class Graph { - public void addEdge(StreetSegment endSeg, StreetSegment beginSeg) {} - } + class Graph { + public void addEdge(StreetSegment endSeg, StreetSegment beginSeg) {} + } - public void buildGraph(List segs) { - Map> endMap = new HashMap<>(); - Map<@KeyFor("endMap") GeoPoint, Set> beginMap = new HashMap<>(); - Graph graph = new Graph(); + public void buildGraph(List segs) { + Map> endMap = new HashMap<>(); + Map<@KeyFor("endMap") GeoPoint, Set> beginMap = new HashMap<>(); + Graph graph = new Graph(); - for (StreetSegment seg : segs) { - GeoPoint p1 = new GeoPoint(); + for (StreetSegment seg : segs) { + GeoPoint p1 = new GeoPoint(); - if (!(beginMap.containsKey(p1))) { - endMap.put(p1, new HashSet()); - beginMap.put(p1, new HashSet()); - } - endMap.get(p1).add(seg); - beginMap.get(p1).add(seg); - } + if (!(beginMap.containsKey(p1))) { + endMap.put(p1, new HashSet()); + beginMap.put(p1, new HashSet()); + } + endMap.get(p1).add(seg); + beginMap.get(p1).add(seg); + } - for (@KeyFor("endMap") GeoPoint p : beginMap.keySet()) { - for (StreetSegment beginSeg : beginMap.get(p)) { - for (StreetSegment endSeg : endMap.get(p)) { - graph.addEdge(endSeg, beginSeg); // endSeg and beginSeg are @NonNull + for (@KeyFor("endMap") GeoPoint p : beginMap.keySet()) { + for (StreetSegment beginSeg : beginMap.get(p)) { + for (StreetSegment endSeg : endMap.get(p)) { + graph.addEdge(endSeg, beginSeg); // endSeg and beginSeg are @NonNull + } + } } - } } - } } diff --git a/checker/tests/nullness/KeyFors.java b/checker/tests/nullness/KeyFors.java index 2fa06159d0f..d90caf6c5ac 100644 --- a/checker/tests/nullness/KeyFors.java +++ b/checker/tests/nullness/KeyFors.java @@ -1,3 +1,5 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -6,124 +8,123 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; public class KeyFors { - public void withoutKeyFor() { - Map map = new HashMap<>(); - String key = "key"; + public void withoutKeyFor() { + Map map = new HashMap<>(); + String key = "key"; + + // :: error: (assignment.type.incompatible) + @NonNull String value = map.get(key); + } + + public void withKeyFor() { + Map map = new HashMap<>(); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("map") String key = "key"; + + @NonNull String value = map.get(key); + } - // :: error: (assignment.type.incompatible) - @NonNull String value = map.get(key); - } + public void withCollection() { + Map map = new HashMap<>(); + List<@KeyFor("map") String> keys = new ArrayList<>(); - public void withKeyFor() { - Map map = new HashMap<>(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("map") String key = "key"; + @KeyFor("map") String key = keys.get(0); + @NonNull String value = map.get(key); + value = map.get(keys.get(0)); + } - @NonNull String value = map.get(key); - } + public void withIndirectReference() { + class Container { + Map map = new HashMap<>(); + } - public void withCollection() { - Map map = new HashMap<>(); - List<@KeyFor("map") String> keys = new ArrayList<>(); + Container container = new Container(); + @SuppressWarnings("assignment.type.incompatible") + @KeyFor("container.map") String key = "m"; - @KeyFor("map") String key = keys.get(0); - @NonNull String value = map.get(key); - value = map.get(keys.get(0)); - } + @NonNull String value = container.map.get(key); + } - public void withIndirectReference() { - class Container { - Map map = new HashMap<>(); + /** Returns a sorted version of m.keySet(). */ + public static , V> Collection<@KeyFor("#1") K> sortedKeySet( + Map m) { + throw new RuntimeException(); } - Container container = new Container(); - @SuppressWarnings("assignment.type.incompatible") - @KeyFor("container.map") String key = "m"; + static HashMap call_hashmap = new HashMap<>(); - @NonNull String value = container.map.get(key); - } + public void testForLoop(HashMap lastMap) { + Collection<@KeyFor("lastMap") String> sorted = sortedKeySet(lastMap); + for (@KeyFor("lastMap") String key : sorted) { + @NonNull String al = lastMap.get(key); + } + for (@KeyFor("call_hashmap") Integer i : sortedKeySet(call_hashmap)) {} + } - /** Returns a sorted version of m.keySet(). */ - public static , V> Collection<@KeyFor("#1") K> sortedKeySet( - Map m) { - throw new RuntimeException(); - } + static class Otherclass { + static Map map = new HashMap<>(); + } - static HashMap call_hashmap = new HashMap<>(); + public void testStaticKeyFor(@KeyFor("Otherclass.map") String s1, String s2) { + Otherclass.map.get(s1).toString(); + // :: error: (dereference.of.nullable) + Otherclass.map.get(s2).toString(); - public void testForLoop(HashMap lastMap) { - Collection<@KeyFor("lastMap") String> sorted = sortedKeySet(lastMap); - for (@KeyFor("lastMap") String key : sorted) { - @NonNull String al = lastMap.get(key); + Otherclass o = new Otherclass(); + o.map.get(s1).toString(); + // TODO:: error: (dereference.of.nullable) + o.map.get(s2).toString(); } - for (@KeyFor("call_hashmap") Integer i : sortedKeySet(call_hashmap)) {} - } - static class Otherclass { - static Map map = new HashMap<>(); - } + public class Graph { - public void testStaticKeyFor(@KeyFor("Otherclass.map") String s1, String s2) { - Otherclass.map.get(s1).toString(); - // :: error: (dereference.of.nullable) - Otherclass.map.get(s2).toString(); + HashMap> childMap; - Otherclass o = new Otherclass(); - o.map.get(s1).toString(); - // TODO:: error: (dereference.of.nullable) - o.map.get(s2).toString(); - } + public Graph(HashMap> childMap) { + this.childMap = childMap; + } - public class Graph { + public void addNode(T n) { + // body omitted, not relevant to test case + } - HashMap> childMap; + public void addEdge2(T parent, T child) { + addNode(parent); + @SuppressWarnings("cast.unsafe") + @KeyFor("childMap") T parent2 = (@KeyFor("childMap") T) parent; + @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent2); + } - public Graph(HashMap> childMap) { - this.childMap = childMap; - } + // TODO: This is a feature request to have KeyFor inferred + // public void addEdge3( T parent, T child ) { + // addNode(parent); + // parent = (@KeyFor("childMap") T) parent; + // @NonNull List l = childMap.get(parent); + // } - public void addNode(T n) { - // body omitted, not relevant to test case } - public void addEdge2(T parent, T child) { - addNode(parent); - @SuppressWarnings("cast.unsafe") - @KeyFor("childMap") T parent2 = (@KeyFor("childMap") T) parent; - @NonNull List<@KeyFor("childMap") T> l = childMap.get(parent2); + /* TODO: add logic that after a call to "put" the first argument is + annotated with @KeyFor. A "@KeyForAfter" annotation to + support this in a general way might be overkill. + Similarly, for calls to "remove" we need to invalidate all (?) + KeyFor annotations.*/ + + void keyForFlow() { + Map leaders = new LinkedHashMap<>(); + Set<@KeyFor("leaders") String> varsUsedPreviously = + new LinkedHashSet<@KeyFor("leaders") String>(); + String varName = "hello"; + leaders.put(varName, "goodbye"); + @KeyFor("leaders") String kf = varName; } - // TODO: This is a feature request to have KeyFor inferred - // public void addEdge3( T parent, T child ) { - // addNode(parent); - // parent = (@KeyFor("childMap") T) parent; - // @NonNull List l = childMap.get(parent); - // } - - } - - /* TODO: add logic that after a call to "put" the first argument is - annotated with @KeyFor. A "@KeyForAfter" annotation to - support this in a general way might be overkill. - Similarly, for calls to "remove" we need to invalidate all (?) - KeyFor annotations.*/ - - void keyForFlow() { - Map leaders = new LinkedHashMap<>(); - Set<@KeyFor("leaders") String> varsUsedPreviously = - new LinkedHashSet<@KeyFor("leaders") String>(); - String varName = "hello"; - leaders.put(varName, "goodbye"); - @KeyFor("leaders") String kf = varName; - } - - public static void mapPut(String start) { - Map n2e = new HashMap<>(); - n2e.put(start, Integer.valueOf(0)); - @KeyFor("n2e") String start2 = start; - } + public static void mapPut(String start) { + Map n2e = new HashMap<>(); + n2e.put(start, Integer.valueOf(0)); + @KeyFor("n2e") String start2 = start; + } } diff --git a/checker/tests/nullness/Lazy.java b/checker/tests/nullness/Lazy.java index c1ac9a9fe66..20afaa22b73 100644 --- a/checker/tests/nullness/Lazy.java +++ b/checker/tests/nullness/Lazy.java @@ -3,55 +3,55 @@ public class Lazy { - @NonNull String f; - @MonotonicNonNull String g; - @MonotonicNonNull String g2; - @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g; - @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g2; - - // Initialization with null is allowed for legacy reasons. - @MonotonicNonNull String init = null; - - public Lazy() { - f = ""; - // does not have to initialize g - } - - void test() { - g = ""; - test2(); // retain non-null property across method calls - g.toLowerCase(); - } - - void _test() { - _g = ""; - test2(); // retain non-null property across method calls - _g.toLowerCase(); - } - - void test2() {} - - void test3() { - // :: error: (dereference.of.nullable) - g.toLowerCase(); - } - - void test4() { - // :: error: (assignment.type.incompatible) - g = null; - // :: error: (monotonic.type.incompatible) - g = g2; - } - - void _test3() { - // :: error: (dereference.of.nullable) - _g.toLowerCase(); - } - - void _test4() { - // :: error: (assignment.type.incompatible) - _g = null; - // :: error: (monotonic.type.incompatible) - _g = _g2; - } + @NonNull String f; + @MonotonicNonNull String g; + @MonotonicNonNull String g2; + @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g; + @org.checkerframework.checker.nullness.qual.MonotonicNonNull String _g2; + + // Initialization with null is allowed for legacy reasons. + @MonotonicNonNull String init = null; + + public Lazy() { + f = ""; + // does not have to initialize g + } + + void test() { + g = ""; + test2(); // retain non-null property across method calls + g.toLowerCase(); + } + + void _test() { + _g = ""; + test2(); // retain non-null property across method calls + _g.toLowerCase(); + } + + void test2() {} + + void test3() { + // :: error: (dereference.of.nullable) + g.toLowerCase(); + } + + void test4() { + // :: error: (assignment.type.incompatible) + g = null; + // :: error: (monotonic.type.incompatible) + g = g2; + } + + void _test3() { + // :: error: (dereference.of.nullable) + _g.toLowerCase(); + } + + void _test4() { + // :: error: (assignment.type.incompatible) + _g = null; + // :: error: (monotonic.type.incompatible) + _g = _g2; + } } diff --git a/checker/tests/nullness/LazyInitialization.java b/checker/tests/nullness/LazyInitialization.java index 2466f13fae8..5f44913c446 100644 --- a/checker/tests/nullness/LazyInitialization.java +++ b/checker/tests/nullness/LazyInitialization.java @@ -1,100 +1,100 @@ import org.checkerframework.checker.nullness.qual.*; public class LazyInitialization { - @Nullable Object nullable; - @NonNull Object nonnull; - @MonotonicNonNull Object lazy; - @MonotonicNonNull Object lazy2 = null; - final @Nullable Object lazy3; - - public LazyInitialization(@Nullable Object arg) { - lazy3 = arg; - nonnull = new Object(); - } - - void randomMethod() {} - - void testAssignment() { - lazy = "m"; - // :: error: (assignment.type.incompatible) - lazy = null; // null - } - - void testLazyBeingNull() { - // :: error: (dereference.of.nullable) - nullable.toString(); // error - nonnull.toString(); - // :: error: (dereference.of.nullable) - lazy.toString(); // error - // :: error: (dereference.of.nullable) - lazy3.toString(); // error - } - - void testAfterInvocation() { - nullable = "m"; - nonnull = "m"; - lazy = "m"; - if (lazy3 == null) { - return; + @Nullable Object nullable; + @NonNull Object nonnull; + @MonotonicNonNull Object lazy; + @MonotonicNonNull Object lazy2 = null; + final @Nullable Object lazy3; + + public LazyInitialization(@Nullable Object arg) { + lazy3 = arg; + nonnull = new Object(); } - randomMethod(); + void randomMethod() {} - // :: error: (dereference.of.nullable) - nullable.toString(); // error - nonnull.toString(); - lazy.toString(); - lazy3.toString(); - } + void testAssignment() { + lazy = "m"; + // :: error: (assignment.type.incompatible) + lazy = null; // null + } + + void testLazyBeingNull() { + // :: error: (dereference.of.nullable) + nullable.toString(); // error + nonnull.toString(); + // :: error: (dereference.of.nullable) + lazy.toString(); // error + // :: error: (dereference.of.nullable) + lazy3.toString(); // error + } + + void testAfterInvocation() { + nullable = "m"; + nonnull = "m"; + lazy = "m"; + if (lazy3 == null) { + return; + } + + randomMethod(); + + // :: error: (dereference.of.nullable) + nullable.toString(); // error + nonnull.toString(); + lazy.toString(); + lazy3.toString(); + } + + private double @MonotonicNonNull [] intersect; - private double @MonotonicNonNull [] intersect; + public void check_modified(double[] a, int count) { + if (intersect != null) { + double @NonNull [] nnda = intersect; + } + } - public void check_modified(double[] a, int count) { - if (intersect != null) { - double @NonNull [] nnda = intersect; + class PptRelation1 { + public void init_hierarchy_new(PptTopLevel ppt, Object eq) { + ppt.equality_view = eq; + ppt.equality_view.toString(); + } } - } - class PptRelation1 { - public void init_hierarchy_new(PptTopLevel ppt, Object eq) { - ppt.equality_view = eq; - ppt.equality_view.toString(); + class PptTopLevel { + public @MonotonicNonNull Object equality_view; } - } - - class PptTopLevel { - public @MonotonicNonNull Object equality_view; - } - - class PptRelation1b { - // This is the same code as in PptRelation1, but comes after the class - // declaration of PptTopLevel. This works as expected. - public void init_hierarchy_new(PptTopLevel ppt, Object eq) { - ppt.equality_view = eq; - ppt.equality_view.toString(); + + class PptRelation1b { + // This is the same code as in PptRelation1, but comes after the class + // declaration of PptTopLevel. This works as expected. + public void init_hierarchy_new(PptTopLevel ppt, Object eq) { + ppt.equality_view = eq; + ppt.equality_view.toString(); + } } - } - class PptRelation2 { - public @MonotonicNonNull Object equality_view2; + class PptRelation2 { + public @MonotonicNonNull Object equality_view2; - public void init_hierarchy_new(PptRelation2 pr1, PptRelation2 pr2, Object eq) { - // :: error: (dereference.of.nullable) - pr1.equality_view2.toString(); + public void init_hierarchy_new(PptRelation2 pr1, PptRelation2 pr2, Object eq) { + // :: error: (dereference.of.nullable) + pr1.equality_view2.toString(); - pr1.equality_view2 = eq; - pr1.equality_view2.toString(); + pr1.equality_view2 = eq; + pr1.equality_view2.toString(); - // :: error: (dereference.of.nullable) - pr2.equality_view2.toString(); - // :: error: (dereference.of.nullable) - this.equality_view2.toString(); + // :: error: (dereference.of.nullable) + pr2.equality_view2.toString(); + // :: error: (dereference.of.nullable) + this.equality_view2.toString(); - pr2.equality_view2 = eq; - pr2.equality_view2.toString(); + pr2.equality_view2 = eq; + pr2.equality_view2.toString(); - this.equality_view2 = eq; - this.equality_view2.toString(); + this.equality_view2 = eq; + this.equality_view2.toString(); + } } - } } diff --git a/checker/tests/nullness/LogRecordTest.java b/checker/tests/nullness/LogRecordTest.java index 9d605da6bbd..ffd91f7b88a 100644 --- a/checker/tests/nullness/LogRecordTest.java +++ b/checker/tests/nullness/LogRecordTest.java @@ -5,22 +5,22 @@ public class LogRecordTest { - void test(Level level) { + void test(Level level) { - LogRecord logRecord = new LogRecord(level, null); + LogRecord logRecord = new LogRecord(level, null); - logRecord.setLoggerName(null); + logRecord.setLoggerName(null); - logRecord.setResourceBundle(null); + logRecord.setResourceBundle(null); - logRecord.setSourceClassName(null); + logRecord.setSourceClassName(null); - logRecord.setMessage(null); + logRecord.setMessage(null); - logRecord.setSourceMethodName(null); + logRecord.setSourceMethodName(null); - logRecord.setParameters(null); + logRecord.setParameters(null); - logRecord.setThrown(null); - } + logRecord.setThrown(null); + } } diff --git a/checker/tests/nullness/LogicOperations.java b/checker/tests/nullness/LogicOperations.java index deb9973b08b..3333958daf6 100644 --- a/checker/tests/nullness/LogicOperations.java +++ b/checker/tests/nullness/LogicOperations.java @@ -2,71 +2,71 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class LogicOperations { - void andTrueClause(@Nullable Object a) { - if (a != null && helper()) { - a.toString(); + void andTrueClause(@Nullable Object a) { + if (a != null && helper()) { + a.toString(); + } } - } - void andTrueClauseReverse(@Nullable Object a) { - if (helper() && a != null) { - a.toString(); + void andTrueClauseReverse(@Nullable Object a) { + if (helper() && a != null) { + a.toString(); + } } - } - void oneAndComplement(@Nullable Object a) { - if (a != null && helper()) { - a.toString(); - return; + void oneAndComplement(@Nullable Object a) { + if (a != null && helper()) { + a.toString(); + return; + } + // :: error: (dereference.of.nullable) + a.toString(); // error } - // :: error: (dereference.of.nullable) - a.toString(); // error - } - void repAndComplement(@Nullable Object a, @Nullable Object b) { - if (a == null && b == null) { - // :: error: (dereference.of.nullable) - a.toString(); // error - return; + void repAndComplement(@Nullable Object a, @Nullable Object b) { + if (a == null && b == null) { + // :: error: (dereference.of.nullable) + a.toString(); // error + return; + } + // :: error: (dereference.of.nullable) + a.toString(); // error } - // :: error: (dereference.of.nullable) - a.toString(); // error - } - void oneOrComplement(@Nullable Object a) { - if (a == null || helper()) { - // :: error: (dereference.of.nullable) - a.toString(); // error - return; + void oneOrComplement(@Nullable Object a) { + if (a == null || helper()) { + // :: error: (dereference.of.nullable) + a.toString(); // error + return; + } + a.toString(); } - a.toString(); - } - void simpleOr1(@Nullable Object a, @Nullable Object b) { - if (a != null || b != null) { - // :: error: (dereference.of.nullable) - a.toString(); // error + void simpleOr1(@Nullable Object a, @Nullable Object b) { + if (a != null || b != null) { + // :: error: (dereference.of.nullable) + a.toString(); // error + } } - } - void simpleOr2(@Nullable Object a, @Nullable Object b) { - if (a != null || b != null) { - // :: error: (dereference.of.nullable) - b.toString(); // error + void simpleOr2(@Nullable Object a, @Nullable Object b) { + if (a != null || b != null) { + // :: error: (dereference.of.nullable) + b.toString(); // error + } } - } - void sideeffect() { - Object a = "m"; - if ((a = null) != "n") { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = a; + void sideeffect() { + Object a = "m"; + if ((a = null) != "n") { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = a; + } + // :: error: (assignment.type.incompatible) + @NonNull Object l2 = a; } - // :: error: (assignment.type.incompatible) - @NonNull Object l2 = a; - } - static boolean helper() { - return true; - } + static boolean helper() { + return true; + } } diff --git a/checker/tests/nullness/LubTest.java b/checker/tests/nullness/LubTest.java index 708cb4d1318..b5e1ad2df9e 100644 --- a/checker/tests/nullness/LubTest.java +++ b/checker/tests/nullness/LubTest.java @@ -2,25 +2,25 @@ public class LubTest { - @Nullable String str; + @Nullable String str; - public void setStr(@Nullable String text) { - str = text; - } + public void setStr(@Nullable String text) { + str = text; + } - public @Nullable String getStr() { - return str; - } + public @Nullable String getStr() { + return str; + } - public void ok(@Nullable LubTest t) { - if (t == null) { - this.setStr(""); - } else { - this.setStr(t.getStr()); + public void ok(@Nullable LubTest t) { + if (t == null) { + this.setStr(""); + } else { + this.setStr(t.getStr()); + } } - } - public void notok(@Nullable LubTest t) { - this.setStr((t == null) ? "" : t.getStr()); - } + public void notok(@Nullable LubTest t) { + this.setStr((t == null) ? "" : t.getStr()); + } } diff --git a/checker/tests/nullness/MapGetNullable.java b/checker/tests/nullness/MapGetNullable.java index 514350ab758..306098ce470 100644 --- a/checker/tests/nullness/MapGetNullable.java +++ b/checker/tests/nullness/MapGetNullable.java @@ -1,146 +1,147 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.HashMap; +import java.util.Map; + public class MapGetNullable { - void foo0(Map m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + void foo0(Map m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - V get0(Map m, @KeyFor("#1") String key) { - return m.get(key); - } + V get0(Map m, @KeyFor("#1") String key) { + return m.get(key); + } - public static class MyMap1 extends HashMap { - // TODO: These test cases do not work yet, because of the generic types. - // void useget(@KeyFor("this") String k) { - // V val = get(k); - // } - // void useget2(@KeyFor("this") String k) { - // V val = this.get(k); - // } - } + public static class MyMap1 extends HashMap { + // TODO: These test cases do not work yet, because of the generic types. + // void useget(@KeyFor("this") String k) { + // V val = get(k); + // } + // void useget2(@KeyFor("this") String k) { + // V val = this.get(k); + // } + } + + void foo1(MyMap1 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - void foo1(MyMap1 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + V get1(MyMap1 m, @KeyFor("#1") String key) { + return m.get(key); + } - V get1(MyMap1 m, @KeyFor("#1") String key) { - return m.get(key); - } + public static class MyMap2 extends HashMap {} - public static class MyMap2 extends HashMap {} + void foo2(MyMap2<@Nullable Integer, String> m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - void foo2(MyMap2<@Nullable Integer, String> m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + V get2(MyMap2 m, @KeyFor("#1") String key) { + return m.get(key); + } - V get2(MyMap2 m, @KeyFor("#1") String key) { - return m.get(key); - } + public static class MyMap3 extends HashMap {} - public static class MyMap3 extends HashMap {} + void foo3(MyMap3 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - void foo3(MyMap3 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + @Nullable Integer get3(MyMap3 m, @KeyFor("#1") String key) { + return m.get(key); + } - @Nullable Integer get3(MyMap3 m, @KeyFor("#1") String key) { - return m.get(key); - } + public static class MyMap4 extends HashMap {} - public static class MyMap4 extends HashMap {} + void foo4(MyMap4 m, @KeyFor("#1") String key) { + Integer val = m.get(key); + } - void foo4(MyMap4 m, @KeyFor("#1") String key) { - Integer val = m.get(key); - } + Integer get4(MyMap4 m, @KeyFor("#1") String key) { + return m.get(key); + } - Integer get4(MyMap4 m, @KeyFor("#1") String key) { - return m.get(key); - } + public static class MyMap5 extends HashMap {} - public static class MyMap5 extends HashMap {} + void foo5(MyMap5<@Nullable Integer> m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - void foo5(MyMap5<@Nullable Integer> m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + V get5(MyMap5 m, @KeyFor("#1") String key) { + return m.get(key); + } - V get5(MyMap5 m, @KeyFor("#1") String key) { - return m.get(key); - } + public static class MyMap6 extends HashMap { + void useget(@KeyFor("this") String k) { + @NonNull Integer val = get(k); + } - public static class MyMap6 extends HashMap { - void useget(@KeyFor("this") String k) { - @NonNull Integer val = get(k); + void useget2(@KeyFor("this") String k) { + @NonNull Integer val = this.get(k); + } } - void useget2(@KeyFor("this") String k) { - @NonNull Integer val = this.get(k); + void foo6(MyMap6 m, @KeyFor("#1") String key) { + @NonNull Integer val = m.get(key); } - } - void foo6(MyMap6 m, @KeyFor("#1") String key) { - @NonNull Integer val = m.get(key); - } + Integer get6(MyMap6 m, @KeyFor("#1") String key) { + return m.get(key); + } - Integer get6(MyMap6 m, @KeyFor("#1") String key) { - return m.get(key); - } + public static class MyMap7 extends HashMap { + void useget(@KeyFor("this") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = get(k); + } - public static class MyMap7 extends HashMap { - void useget(@KeyFor("this") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = get(k); + void useget2(@KeyFor("this") String k) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = this.get(k); + } } - void useget2(@KeyFor("this") String k) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = this.get(k); + void foo7(MyMap7 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); } - } - void foo7(MyMap7 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } - - Integer get7(MyMap7 m, @KeyFor("#1") String key) { - // :: error: (return.type.incompatible) - return m.get(key); - } + Integer get7(MyMap7 m, @KeyFor("#1") String key) { + // :: error: (return.type.incompatible) + return m.get(key); + } - // MyMap9 ensures that no changes are made to the return type of overloaded versions of get(). + // MyMap9 ensures that no changes are made to the return type of overloaded versions of get(). - public static class MyMap9 extends HashMap { - @Nullable V get(@Nullable Object key, int itIsOverloaded) { - return null; + public static class MyMap9 extends HashMap { + @Nullable V get(@Nullable Object key, int itIsOverloaded) { + return null; + } } - } - void foo9(MyMap9 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key); - } + void foo9(MyMap9 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key); + } - void foo9a(MyMap9 m, @KeyFor("#1") String key) { - // :: error: (assignment.type.incompatible) - @NonNull Integer val = m.get(key, 22); - } + void foo9a(MyMap9 m, @KeyFor("#1") String key) { + // :: error: (assignment.type.incompatible) + @NonNull Integer val = m.get(key, 22); + } - V get9(MyMap9 m, @KeyFor("#1") String key) { - return m.get(key); - } + V get9(MyMap9 m, @KeyFor("#1") String key) { + return m.get(key); + } - V get9a(MyMap9 m, @KeyFor("#1") String key) { - // :: error: (return.type.incompatible) - return m.get(key, 22); - } + V get9a(MyMap9 m, @KeyFor("#1") String key) { + // :: error: (return.type.incompatible) + return m.get(key, 22); + } } diff --git a/checker/tests/nullness/MapMerge.java b/checker/tests/nullness/MapMerge.java index fae8eacc58d..24922889f36 100644 --- a/checker/tests/nullness/MapMerge.java +++ b/checker/tests/nullness/MapMerge.java @@ -3,23 +3,23 @@ import java.util.function.BiFunction; public class MapMerge { - public static void main(String[] args) { - Map map = new HashMap<>(); - map.put("k", "v"); - // :: error: (return.type.incompatible) - map.merge("k", "v", (a, b) -> null).toString(); - } + public static void main(String[] args) { + Map map = new HashMap<>(); + map.put("k", "v"); + // :: error: (return.type.incompatible) + map.merge("k", "v", (a, b) -> null).toString(); + } - void foo(Map map) { - // :: error: (return.type.incompatible) - merge(map, "k", "v", (a, b) -> null).toString(); - } + void foo(Map map) { + // :: error: (return.type.incompatible) + merge(map, "k", "v", (a, b) -> null).toString(); + } - V merge( - Map map, - K key, - V value, - BiFunction remappingFunction) { - return value; - } + V merge( + Map map, + K key, + V value, + BiFunction remappingFunction) { + return value; + } } diff --git a/checker/tests/nullness/Marino.java b/checker/tests/nullness/Marino.java index 69fde3ba53a..028cef7bc61 100644 --- a/checker/tests/nullness/Marino.java +++ b/checker/tests/nullness/Marino.java @@ -4,62 +4,62 @@ @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class Marino { - @NonNull String m_str; - static String ms_str; - String m_nullableStr; + @NonNull String m_str; + static String ms_str; + String m_nullableStr; - public Marino(@NonNull String m_str, String m_nullableStr) { - this.m_str = m_str; - this.m_nullableStr = m_nullableStr; - } - - void testWhile() throws Exception { - String s = "foo"; - while (true) { - @NonNull String a = s; - System.out.println("a has length: " + a.length()); - break; + public Marino(@NonNull String m_str, String m_nullableStr) { + this.m_str = m_str; + this.m_nullableStr = m_nullableStr; } - int i = 1; - while (true) { - @NonNull String a = s; // s cannot be null here - s = null; - // :: error: (dereference.of.nullable) - System.out.println("hi" + s.length()); - if (i > 2) break; - // :: error: (assignment.type.incompatible) - a = null; - } - // Checker doesn't catch that m_str not initialized. - // This is Caveat 2 in the manual, but note that it is not limited to contructors. - System.out.println("Member string has length: " + m_str.length()); + void testWhile() throws Exception { + String s = "foo"; + while (true) { + @NonNull String a = s; + System.out.println("a has length: " + a.length()); + break; + } + int i = 1; + while (true) { - // Dereference of any static field is allowed. - // I suppose this is a design decision for practicality in interacting with libraries...? - // :: error: (dereference.of.nullable) - System.out.println("Member string has length: " + ms_str.length()); - System.out.println( - "Everyone should get this error: " - + + @NonNull String a = s; // s cannot be null here + s = null; // :: error: (dereference.of.nullable) - m_nullableStr.length()); + System.out.println("hi" + s.length()); + if (i > 2) break; + // :: error: (assignment.type.incompatible) + a = null; + } + // Checker doesn't catch that m_str not initialized. + // This is Caveat 2 in the manual, but note that it is not limited to contructors. + System.out.println("Member string has length: " + m_str.length()); - s = null; - @NonNull String b = "hi"; - try { - System.out.println("b has length: " + b.length()); - methodThatThrowsEx(); - s = "bye"; - } finally { - // Checker doesn't catch that s will be null here. - // :: error: (assignment.type.incompatible) - b = s; - System.out.println("b has length: " + b.length()); + // Dereference of any static field is allowed. + // I suppose this is a design decision for practicality in interacting with libraries...? + // :: error: (dereference.of.nullable) + System.out.println("Member string has length: " + ms_str.length()); + System.out.println( + "Everyone should get this error: " + + + // :: error: (dereference.of.nullable) + m_nullableStr.length()); + + s = null; + @NonNull String b = "hi"; + try { + System.out.println("b has length: " + b.length()); + methodThatThrowsEx(); + s = "bye"; + } finally { + // Checker doesn't catch that s will be null here. + // :: error: (assignment.type.incompatible) + b = s; + System.out.println("b has length: " + b.length()); + } } - } - void methodThatThrowsEx() throws Exception { - throw new Exception(); - } + void methodThatThrowsEx() throws Exception { + throw new Exception(); + } } diff --git a/checker/tests/nullness/MethodOverloadingContractsKeyFor.java b/checker/tests/nullness/MethodOverloadingContractsKeyFor.java index 9afffdde326..8ad6f6ad193 100644 --- a/checker/tests/nullness/MethodOverloadingContractsKeyFor.java +++ b/checker/tests/nullness/MethodOverloadingContractsKeyFor.java @@ -1,41 +1,42 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; import org.checkerframework.dataflow.qual.Pure; +import java.util.HashMap; +import java.util.Map; + public class MethodOverloadingContractsKeyFor { - static class ClassA {} + static class ClassA {} - static class ClassB extends ClassA {} + static class ClassB extends ClassA {} - @Pure - String name(ClassA classA) { - return "asClassA"; - } + @Pure + String name(ClassA classA) { + return "asClassA"; + } - @Pure - Object name(ClassB classB) { - return "asClassB"; - } + @Pure + Object name(ClassB classB) { + return "asClassB"; + } - Map map = new HashMap<>(); + Map map = new HashMap<>(); - @EnsuresKeyFor(value = "name(#1)", map = "map") - void put(ClassA classA) { - map.put(name(classA), ""); - } + @EnsuresKeyFor(value = "name(#1)", map = "map") + void put(ClassA classA) { + map.put(name(classA), ""); + } - void test(ClassA classA, ClassB classB) { - put(classA); - map.get(name(classA)).toString(); + void test(ClassA classA, ClassB classB) { + put(classA); + map.get(name(classA)).toString(); - put(classB); - // :: error: (dereference.of.nullable) - map.get(name(classB)).toString(); - } + put(classB); + // :: error: (dereference.of.nullable) + map.get(name(classB)).toString(); + } - public static void main(String[] args) { - new MethodOverloadingContractsKeyFor().test(new ClassA(), new ClassB()); - } + public static void main(String[] args) { + new MethodOverloadingContractsKeyFor().test(new ClassA(), new ClassB()); + } } diff --git a/checker/tests/nullness/MethodTypeVars4.java b/checker/tests/nullness/MethodTypeVars4.java index a0ff26e25ef..7bc8442a105 100644 --- a/checker/tests/nullness/MethodTypeVars4.java +++ b/checker/tests/nullness/MethodTypeVars4.java @@ -1,29 +1,30 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; +import java.util.List; + public class MethodTypeVars4 { - @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND) - interface I { - T doit(); + @DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.IMPLICIT_UPPER_BOUND) + interface I { + T doit(); - List doit2(); + List doit2(); - T doit3(); - } + T doit3(); + } - void f1(I i) { - // s is implicitly Nullable - String s = i.doit(); - List ls = i.doit2(); - String s2 = i.doit3(); - } + void f1(I i) { + // s is implicitly Nullable + String s = i.doit(); + List ls = i.doit2(); + String s2 = i.doit3(); + } - void f2(I i) { - @NonNull String s = i.doit(); - s = i.doit3(); - // :: error: (type.arguments.not.inferred) - List<@Nullable String> ls = i.doit2(); - } + void f2(I i) { + @NonNull String s = i.doit(); + s = i.doit3(); + // :: error: (type.arguments.not.inferred) + List<@Nullable String> ls = i.doit2(); + } } diff --git a/checker/tests/nullness/MissingBoundAnnotations.java b/checker/tests/nullness/MissingBoundAnnotations.java index c96a8bdea44..096ca97ea12 100644 --- a/checker/tests/nullness/MissingBoundAnnotations.java +++ b/checker/tests/nullness/MissingBoundAnnotations.java @@ -1,21 +1,22 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; public final class MissingBoundAnnotations { - public static , V> Collection<@KeyFor("#1") K> sortedKeySet( - Map m) { - ArrayList<@KeyFor("m") K> theKeys = new ArrayList<>(m.keySet()); - Collections.sort(theKeys); - return theKeys; - } + public static , V> Collection<@KeyFor("#1") K> sortedKeySet( + Map m) { + ArrayList<@KeyFor("m") K> theKeys = new ArrayList<>(m.keySet()); + Collections.sort(theKeys); + return theKeys; + } - public static , V> - Collection<@KeyFor("#1") K> sortedKeySetSimpler(ArrayList<@KeyFor("#1") K> theKeys) { - Collections.sort(theKeys); - return theKeys; - } + public static , V> + Collection<@KeyFor("#1") K> sortedKeySetSimpler(ArrayList<@KeyFor("#1") K> theKeys) { + Collections.sort(theKeys); + return theKeys; + } } diff --git a/checker/tests/nullness/MisuseProperties.java b/checker/tests/nullness/MisuseProperties.java index 6872b03dca0..999dabce412 100644 --- a/checker/tests/nullness/MisuseProperties.java +++ b/checker/tests/nullness/MisuseProperties.java @@ -1,3 +1,5 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.Collection; import java.util.Collections; import java.util.Dictionary; @@ -5,79 +7,78 @@ import java.util.Map; import java.util.Properties; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; public class MisuseProperties { - void propertiesToHashtable(Properties p) { - // :: error: (argument.type.incompatible) - p.setProperty("line.separator", null); - // :: error: (argument.type.incompatible) - p.put("line.separator", null); - Hashtable h = p; - // Error, because HashTable value has NonNull bound. - // put(K,V) as a member of the raw type java.util.Hashtable - // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type - // java.util.Hashtable - // :: error: (argument.type.incompatible) - h.put("line.separator", null); - // :: error: (argument.type.incompatible) - System.setProperty("line.separator", null); - - Dictionary d1 = p; - // No error, because Dictionary value has Nullable bound. - // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type - // java.util.Dictionary - d1.put("line.separator", null); - - // :: error: (assignment.type.incompatible) - Dictionary d2 = p; - d2.put("line.separator", null); - - // :: error: (clear.system.property) - System.setProperties(p); // OK; p has no null values - - System.clearProperty("foo.bar"); // OK - - // Each of the following should cause an error, because it leaves line.separator null. - - // These first few need to be special-cased, I think: - - // :: error: (clear.system.property) - System.clearProperty("line.separator"); - - p.remove("line.separator"); - p.clear(); - - // These are OK because they seem to only add, not remove, properties: - // p.load(InputStream), p.load(Reader), p.loadFromXML(InputStream) - - // The following problems are a result of treating a Properties as one - // of its supertypes. Here are some solutions: - // * Forbid treating a Properties object as any of its supertypes. - // * Create an annotation on a Properties object, such as - // @HasSystemProperties, and forbid some operations (or any - // treatment as a supertype) for such properties. - - Set<@KeyFor("p") Object> keys = p.keySet(); - // now remove "line.separator" from the set - keys.remove("line.separator"); - keys.removeAll(keys); - keys.clear(); - keys.retainAll(Collections.EMPTY_SET); - - Set> entries = p.entrySet(); - // now remove the pair containing "line.separator" from the set, as above - - Collection values = p.values(); - // now remove the line separator value from values, as above - - Hashtable h9 = p; - h9.remove("line.separator"); - h9.clear(); - // also access via entrySet, keySet, values - - Dictionary d9 = p; - d9.remove("line.separator"); - } + void propertiesToHashtable(Properties p) { + // :: error: (argument.type.incompatible) + p.setProperty("line.separator", null); + // :: error: (argument.type.incompatible) + p.put("line.separator", null); + Hashtable h = p; + // Error, because HashTable value has NonNull bound. + // put(K,V) as a member of the raw type java.util.Hashtable + // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type + // java.util.Hashtable + // :: error: (argument.type.incompatible) + h.put("line.separator", null); + // :: error: (argument.type.incompatible) + System.setProperty("line.separator", null); + + Dictionary d1 = p; + // No error, because Dictionary value has Nullable bound. + // :: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type + // java.util.Dictionary + d1.put("line.separator", null); + + // :: error: (assignment.type.incompatible) + Dictionary d2 = p; + d2.put("line.separator", null); + + // :: error: (clear.system.property) + System.setProperties(p); // OK; p has no null values + + System.clearProperty("foo.bar"); // OK + + // Each of the following should cause an error, because it leaves line.separator null. + + // These first few need to be special-cased, I think: + + // :: error: (clear.system.property) + System.clearProperty("line.separator"); + + p.remove("line.separator"); + p.clear(); + + // These are OK because they seem to only add, not remove, properties: + // p.load(InputStream), p.load(Reader), p.loadFromXML(InputStream) + + // The following problems are a result of treating a Properties as one + // of its supertypes. Here are some solutions: + // * Forbid treating a Properties object as any of its supertypes. + // * Create an annotation on a Properties object, such as + // @HasSystemProperties, and forbid some operations (or any + // treatment as a supertype) for such properties. + + Set<@KeyFor("p") Object> keys = p.keySet(); + // now remove "line.separator" from the set + keys.remove("line.separator"); + keys.removeAll(keys); + keys.clear(); + keys.retainAll(Collections.EMPTY_SET); + + Set> entries = p.entrySet(); + // now remove the pair containing "line.separator" from the set, as above + + Collection values = p.values(); + // now remove the line separator value from values, as above + + Hashtable h9 = p; + h9.remove("line.separator"); + h9.clear(); + // also access via entrySet, keySet, values + + Dictionary d9 = p; + d9.remove("line.separator"); + } } diff --git a/checker/tests/nullness/MonotonicNonNullFieldTest.java b/checker/tests/nullness/MonotonicNonNullFieldTest.java index 3666d5ddb16..cb69a4f1335 100644 --- a/checker/tests/nullness/MonotonicNonNullFieldTest.java +++ b/checker/tests/nullness/MonotonicNonNullFieldTest.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.nullness.qual.*; public class MonotonicNonNullFieldTest { - class Data { - @MonotonicNonNull Object field; - } + class Data { + @MonotonicNonNull Object field; + } - void method(Object object) {} + void method(Object object) {} - @RequiresNonNull("#1.field") - void test(final Data data) { - method(data.field); // checks OK + @RequiresNonNull("#1.field") + void test(final Data data) { + method(data.field); // checks OK - Runnable callback = - new Runnable() { - public void run() { - method(data.field); // used to issue error - } - }; - } + Runnable callback = + new Runnable() { + public void run() { + method(data.field); // used to issue error + } + }; + } } diff --git a/checker/tests/nullness/MonotonicNonNullTest.java b/checker/tests/nullness/MonotonicNonNullTest.java index 8059c00e87a..39c295025e1 100644 --- a/checker/tests/nullness/MonotonicNonNullTest.java +++ b/checker/tests/nullness/MonotonicNonNullTest.java @@ -2,15 +2,15 @@ public final class MonotonicNonNullTest { - public static @MonotonicNonNull Boolean new_decl_format = null; + public static @MonotonicNonNull Boolean new_decl_format = null; - static final class SerialFormat { + static final class SerialFormat { - public boolean new_decl_format = false; + public boolean new_decl_format = false; - @RequiresNonNull("MonotonicNonNullTest.new_decl_format") - public SerialFormat() { - this.new_decl_format = MonotonicNonNullTest.new_decl_format; + @RequiresNonNull("MonotonicNonNullTest.new_decl_format") + public SerialFormat() { + this.new_decl_format = MonotonicNonNullTest.new_decl_format; + } } - } } diff --git a/checker/tests/nullness/MultiAnnotations.java b/checker/tests/nullness/MultiAnnotations.java index e0c2a7800c1..ee0a76ca71b 100644 --- a/checker/tests/nullness/MultiAnnotations.java +++ b/checker/tests/nullness/MultiAnnotations.java @@ -2,11 +2,11 @@ public final @Interned class MultiAnnotations { - private MultiAnnotations() {} + private MultiAnnotations() {} - public static final MultiAnnotations NO_CHANGE = new MultiAnnotations(); + public static final MultiAnnotations NO_CHANGE = new MultiAnnotations(); - MultiAnnotations foo() { - return MultiAnnotations.NO_CHANGE; - } + MultiAnnotations foo() { + return MultiAnnotations.NO_CHANGE; + } } diff --git a/checker/tests/nullness/MultipleErrors.java b/checker/tests/nullness/MultipleErrors.java index ebcede7bf1b..c23ea1c1fb7 100644 --- a/checker/tests/nullness/MultipleErrors.java +++ b/checker/tests/nullness/MultipleErrors.java @@ -2,16 +2,16 @@ // the same compilation unit are all shown. class MultipleErrors1 { - // :: error: (assignment.type.incompatible) - Object o1 = null; + // :: error: (assignment.type.incompatible) + Object o1 = null; } class MultipleErrors2 { - // :: error: (assignment.type.incompatible) - Object o2 = null; + // :: error: (assignment.type.incompatible) + Object o2 = null; } interface MultipleErrors3 { - // :: error: (assignment.type.incompatible) - Object o3 = null; + // :: error: (assignment.type.incompatible) + Object o3 = null; } diff --git a/checker/tests/nullness/MyException.java b/checker/tests/nullness/MyException.java index eefe65288cf..3867e50b04a 100644 --- a/checker/tests/nullness/MyException.java +++ b/checker/tests/nullness/MyException.java @@ -1,22 +1,22 @@ @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.Nullable.class) + org.checkerframework.checker.nullness.qual.Nullable.class) public class MyException extends Exception { - public MyException() {} + public MyException() {} - public final String getTotalTrace() { - final StringBuilder sb = new StringBuilder(); - // :: error: (iterating.over.nullable) - for (StackTraceElement st : getStackTrace()) { - // :: error: (dereference.of.nullable) - sb.append(st.toString()); - sb.append(System.lineSeparator()); + public final String getTotalTrace() { + final StringBuilder sb = new StringBuilder(); + // :: error: (iterating.over.nullable) + for (StackTraceElement st : getStackTrace()) { + // :: error: (dereference.of.nullable) + sb.append(st.toString()); + sb.append(System.lineSeparator()); + } + return sb.toString(); } - return sb.toString(); - } - @SuppressWarnings("nullness") - public StackTraceElement[] getStackTrace() { - throw new RuntimeException("not implemented yet"); - } + @SuppressWarnings("nullness") + public StackTraceElement[] getStackTrace() { + throw new RuntimeException("not implemented yet"); + } } diff --git a/checker/tests/nullness/NNOEMoreTests.java b/checker/tests/nullness/NNOEMoreTests.java index c88e66cc705..0e8cb3f7f81 100644 --- a/checker/tests/nullness/NNOEMoreTests.java +++ b/checker/tests/nullness/NNOEMoreTests.java @@ -2,46 +2,46 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class NNOEMoreTests { - class NNOEMain { - protected @Nullable String nullable = null; - @Nullable String otherNullable = null; - - @RequiresNonNull("nullable") - void test1() { - nullable.toString(); - } - - @RequiresNonNull("xxx") - // :: error: (flowexpr.parse.error) - void test2() { - // :: error: (dereference.of.nullable) - nullable.toString(); + class NNOEMain { + protected @Nullable String nullable = null; + @Nullable String otherNullable = null; + + @RequiresNonNull("nullable") + void test1() { + nullable.toString(); + } + + @RequiresNonNull("xxx") + // :: error: (flowexpr.parse.error) + void test2() { + // :: error: (dereference.of.nullable) + nullable.toString(); + } } - } - class NNOESeparate { - void call1(NNOEMain p) { - // :: error: (contracts.precondition.not.satisfied) - p.test1(); + class NNOESeparate { + void call1(NNOEMain p) { + // :: error: (contracts.precondition.not.satisfied) + p.test1(); - Object xxx = new Object(); - // :: error: (flowexpr.parse.error) - p.test2(); - } + Object xxx = new Object(); + // :: error: (flowexpr.parse.error) + p.test2(); + } - void call2(NNOEMain p) { - p.nullable = ""; - p.test1(); + void call2(NNOEMain p) { + p.nullable = ""; + p.test1(); + } } - } - @Nullable Object field1; + @Nullable Object field1; - @RequiresNonNull("field1") - void methWithIf1() { - if (5 < 99) { - } else { - field1.hashCode(); + @RequiresNonNull("field1") + void methWithIf1() { + if (5 < 99) { + } else { + field1.hashCode(); + } } - } } diff --git a/checker/tests/nullness/NNOEStaticFields.java b/checker/tests/nullness/NNOEStaticFields.java index b0ec82a28a0..a3f61adfc07 100644 --- a/checker/tests/nullness/NNOEStaticFields.java +++ b/checker/tests/nullness/NNOEStaticFields.java @@ -1,114 +1,115 @@ -import java.util.Collections; -import java.util.Set; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import java.util.Collections; +import java.util.Set; + public class NNOEStaticFields { - static @Nullable String nullable = null; - static @Nullable String otherNullable = null; - - @RequiresNonNull("nullable") - void testF() { - nullable.toString(); - } - - @RequiresNonNull("NNOEStaticFields.nullable") - void testF2() { - nullable.toString(); - } - - @RequiresNonNull("nullable") - void testF3() { - NNOEStaticFields.nullable.toString(); - } - - @RequiresNonNull("NNOEStaticFields.nullable") - void testF4() { - NNOEStaticFields.nullable.toString(); - } - - class Inner { - void m1(NNOEStaticFields out) { - NNOEStaticFields.nullable = "haha!"; - out.testF4(); + static @Nullable String nullable = null; + static @Nullable String otherNullable = null; + + @RequiresNonNull("nullable") + void testF() { + nullable.toString(); } @RequiresNonNull("NNOEStaticFields.nullable") - void m2(NNOEStaticFields out) { - out.testF4(); + void testF2() { + nullable.toString(); + } + + @RequiresNonNull("nullable") + void testF3() { + NNOEStaticFields.nullable.toString(); + } + + @RequiresNonNull("NNOEStaticFields.nullable") + void testF4() { + NNOEStaticFields.nullable.toString(); + } + + class Inner { + void m1(NNOEStaticFields out) { + NNOEStaticFields.nullable = "haha!"; + out.testF4(); + } + + @RequiresNonNull("NNOEStaticFields.nullable") + void m2(NNOEStaticFields out) { + out.testF4(); + } + } + + @RequiresNonNull("NoClueWhatThisShouldBe") + // :: error: (flowexpr.parse.error) + void testF5() { + // :: error: (dereference.of.nullable) + NNOEStaticFields.nullable.toString(); + } + + void trueNegative() { + // :: error: (dereference.of.nullable) + nullable.toString(); + // :: error: (dereference.of.nullable) + otherNullable.toString(); + } + + @RequiresNonNull("nullable") + void test1() { + nullable.toString(); + // :: error: (dereference.of.nullable) + otherNullable.toString(); + } + + @RequiresNonNull("otherNullable") + void test2() { + // :: error: (dereference.of.nullable) + nullable.toString(); + otherNullable.toString(); } - } - - @RequiresNonNull("NoClueWhatThisShouldBe") - // :: error: (flowexpr.parse.error) - void testF5() { - // :: error: (dereference.of.nullable) - NNOEStaticFields.nullable.toString(); - } - - void trueNegative() { - // :: error: (dereference.of.nullable) - nullable.toString(); - // :: error: (dereference.of.nullable) - otherNullable.toString(); - } - - @RequiresNonNull("nullable") - void test1() { - nullable.toString(); - // :: error: (dereference.of.nullable) - otherNullable.toString(); - } - - @RequiresNonNull("otherNullable") - void test2() { - // :: error: (dereference.of.nullable) - nullable.toString(); - otherNullable.toString(); - } - - @RequiresNonNull({"nullable", "otherNullable"}) - void test3() { - nullable.toString(); - otherNullable.toString(); - } - - @RequiresNonNull("System.out") - void test4() { - @NonNull Object f = System.out; - } - - /////////////////////////////////////////////////////////////////////////// - /// Copied from Daikon's ChicoryPremain - /// - - static class ChicoryPremain1 { - - // Non-null if doPurity == true - private static @MonotonicNonNull Set pureMethods = null; - - private static boolean doPurity = false; - - @EnsuresNonNullIf(result = true, expression = "ChicoryPremain1.pureMethods") - // this postcondition cannot be proved with the Checker Framework, as the relation - // between doPurity and pureMethods is not explicit - public static boolean shouldDoPurity() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return doPurity; + + @RequiresNonNull({"nullable", "otherNullable"}) + void test3() { + nullable.toString(); + otherNullable.toString(); } - @RequiresNonNull("ChicoryPremain1.pureMethods") - public static Set getPureMethods() { - return Collections.unmodifiableSet(pureMethods); + @RequiresNonNull("System.out") + void test4() { + @NonNull Object f = System.out; + } + + /////////////////////////////////////////////////////////////////////////// + /// Copied from Daikon's ChicoryPremain + /// + + static class ChicoryPremain1 { + + // Non-null if doPurity == true + private static @MonotonicNonNull Set pureMethods = null; + + private static boolean doPurity = false; + + @EnsuresNonNullIf(result = true, expression = "ChicoryPremain1.pureMethods") + // this postcondition cannot be proved with the Checker Framework, as the relation + // between doPurity and pureMethods is not explicit + public static boolean shouldDoPurity() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return doPurity; + } + + @RequiresNonNull("ChicoryPremain1.pureMethods") + public static Set getPureMethods() { + return Collections.unmodifiableSet(pureMethods); + } } - } - static class ClassInfo1 { - public void initViaReflection() { - if (ChicoryPremain1.shouldDoPurity()) { - for (String pureMeth : ChicoryPremain1.getPureMethods()) {} - } + static class ClassInfo1 { + public void initViaReflection() { + if (ChicoryPremain1.shouldDoPurity()) { + for (String pureMeth : ChicoryPremain1.getPureMethods()) {} + } + } } - } } diff --git a/checker/tests/nullness/NegatingConditionalNullness.java b/checker/tests/nullness/NegatingConditionalNullness.java index c28602dcf7c..a193d51308f 100644 --- a/checker/tests/nullness/NegatingConditionalNullness.java +++ b/checker/tests/nullness/NegatingConditionalNullness.java @@ -1,57 +1,58 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import java.util.List; + public class NegatingConditionalNullness { - public @MonotonicNonNull List splitters = null; + public @MonotonicNonNull List splitters = null; - @EnsuresNonNullIf(result = true, expression = "splitters") - public boolean has_splitters() { - return (splitters != null); - } + @EnsuresNonNullIf(result = true, expression = "splitters") + public boolean has_splitters() { + return (splitters != null); + } - static void test(NegatingConditionalNullness ppt) { - if (!ppt.has_splitters()) { - return; + static void test(NegatingConditionalNullness ppt) { + if (!ppt.has_splitters()) { + return; + } + @NonNull Object s2 = ppt.splitters; } - @NonNull Object s2 = ppt.splitters; - } - static void testAssert(NegatingConditionalNullness ppt) { - assert ppt.has_splitters() : "@AssumeAssertion(nullness)"; - @NonNull Object s2 = ppt.splitters; - } + static void testAssert(NegatingConditionalNullness ppt) { + assert ppt.has_splitters() : "@AssumeAssertion(nullness)"; + @NonNull Object s2 = ppt.splitters; + } - static void testSimple(NegatingConditionalNullness ppt) { - if (ppt.has_splitters()) { - @NonNull Object s2 = ppt.splitters; + static void testSimple(NegatingConditionalNullness ppt) { + if (ppt.has_splitters()) { + @NonNull Object s2 = ppt.splitters; + } } - } - - // False tests - static void testFalse(NegatingConditionalNullness ppt) { - // :: error: (dereference.of.nullable) - ppt.splitters.toString(); // error - } - - static void testFalseNoAssertion(NegatingConditionalNullness ppt) { - ppt.has_splitters(); - // :: error: (dereference.of.nullable) - ppt.splitters.toString(); // error - } - - static void testFalseIf(NegatingConditionalNullness ppt) { - if (ppt.has_splitters()) { - return; + + // False tests + static void testFalse(NegatingConditionalNullness ppt) { + // :: error: (dereference.of.nullable) + ppt.splitters.toString(); // error } - // :: error: (dereference.of.nullable) - ppt.splitters.toString(); // error - } - - // static void testFalseIfBody(NegatingConditionalNullness ppt) { - // if (!ppt.has_splitters()) { - // // :: error: (dereference.of.nullable) - // ppt.splitters.toString(); // error - // } - // } + + static void testFalseNoAssertion(NegatingConditionalNullness ppt) { + ppt.has_splitters(); + // :: error: (dereference.of.nullable) + ppt.splitters.toString(); // error + } + + static void testFalseIf(NegatingConditionalNullness ppt) { + if (ppt.has_splitters()) { + return; + } + // :: error: (dereference.of.nullable) + ppt.splitters.toString(); // error + } + + // static void testFalseIfBody(NegatingConditionalNullness ppt) { + // if (!ppt.has_splitters()) { + // // :: error: (dereference.of.nullable) + // ppt.splitters.toString(); // error + // } + // } } diff --git a/checker/tests/nullness/NewNullable.java b/checker/tests/nullness/NewNullable.java index 0e0370ecf62..861dd794758 100644 --- a/checker/tests/nullness/NewNullable.java +++ b/checker/tests/nullness/NewNullable.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.nullness.qual.*; public class NewNullable { - Object o = new Object(); - Object nn = new @NonNull Object(); - // :: error: (nullness.on.new.object) - @Nullable Object lazy = new @MonotonicNonNull Object(); - // :: error: (nullness.on.new.object) - // :: error: (invalid.polymorphic.qualifier.use) - @Nullable Object poly = new @PolyNull Object(); - // :: error: (nullness.on.new.object) - @Nullable Object nbl = new @Nullable Object(); + Object o = new Object(); + Object nn = new @NonNull Object(); + // :: error: (nullness.on.new.object) + @Nullable Object lazy = new @MonotonicNonNull Object(); + // :: error: (nullness.on.new.object) + // :: error: (invalid.polymorphic.qualifier.use) + @Nullable Object poly = new @PolyNull Object(); + // :: error: (nullness.on.new.object) + @Nullable Object nbl = new @Nullable Object(); } diff --git a/checker/tests/nullness/NewObjectNonNull.java b/checker/tests/nullness/NewObjectNonNull.java index 66b78927424..b58321dd5d2 100644 --- a/checker/tests/nullness/NewObjectNonNull.java +++ b/checker/tests/nullness/NewObjectNonNull.java @@ -2,19 +2,19 @@ import org.checkerframework.framework.qual.DefaultQualifier; public class NewObjectNonNull { - @DefaultQualifier(Nullable.class) - class A { - A() {} - } + @DefaultQualifier(Nullable.class) + class A { + A() {} + } - @DefaultQualifier(Nullable.class) - class B { - // No explicit constructor. - // B() {} - } + @DefaultQualifier(Nullable.class) + class B { + // No explicit constructor. + // B() {} + } - void m() { - new A().toString(); - new B().toString(); - } + void m() { + new A().toString(); + new B().toString(); + } } diff --git a/checker/tests/nullness/NonEmptyCollection.java b/checker/tests/nullness/NonEmptyCollection.java index a230ceb93af..80edd7fb450 100644 --- a/checker/tests/nullness/NonEmptyCollection.java +++ b/checker/tests/nullness/NonEmptyCollection.java @@ -1,49 +1,50 @@ -import java.io.*; -import java.util.SortedMap; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -public class NonEmptyCollection { - - public static @NonNull String returnRemove(@NonNull PriorityQueue1<@NonNull String> pq) { - return pq.remove(); - } +import java.io.*; +import java.util.SortedMap; - public static @NonNull String returnPoll1(PriorityQueue1<@NonNull String> pq) { - // :: error: (return.type.incompatible) - return pq.poll(); - } +public class NonEmptyCollection { - public static @NonNull String returnPoll2(PriorityQueue1<@NonNull String> pq) { - if (pq.isEmpty()) { - return "hello"; - } else { - return pq.poll(); + public static @NonNull String returnRemove(@NonNull PriorityQueue1<@NonNull String> pq) { + return pq.remove(); } - } - public static @NonNull String returnFirstKey(SortedMap sm) { - return sm.firstKey(); - } - - /////////////////////////////////////////////////////////////////////////// - /// Helper classes copied from JDK - /// + public static @NonNull String returnPoll1(PriorityQueue1<@NonNull String> pq) { + // :: error: (return.type.incompatible) + return pq.poll(); + } - public class PriorityQueue1 { - @SuppressWarnings("purity") // object creation is forbidden in pure methods - @org.checkerframework.dataflow.qual.Pure - public @Nullable E poll() { - throw new RuntimeException("skeleton method"); + public static @NonNull String returnPoll2(PriorityQueue1<@NonNull String> pq) { + if (pq.isEmpty()) { + return "hello"; + } else { + return pq.poll(); + } } - public E remove() { - throw new RuntimeException("skeleton method"); + public static @NonNull String returnFirstKey(SortedMap sm) { + return sm.firstKey(); } - @EnsuresNonNullIf(result = false, expression = "poll()") - public boolean isEmpty() { - throw new RuntimeException("skeleton method"); + /////////////////////////////////////////////////////////////////////////// + /// Helper classes copied from JDK + /// + + public class PriorityQueue1 { + @SuppressWarnings("purity") // object creation is forbidden in pure methods + @org.checkerframework.dataflow.qual.Pure + public @Nullable E poll() { + throw new RuntimeException("skeleton method"); + } + + public E remove() { + throw new RuntimeException("skeleton method"); + } + + @EnsuresNonNullIf(result = false, expression = "poll()") + public boolean isEmpty() { + throw new RuntimeException("skeleton method"); + } } - } } diff --git a/checker/tests/nullness/NonNullInitialization.java b/checker/tests/nullness/NonNullInitialization.java index a8acf65a8ce..9e39a99636c 100644 --- a/checker/tests/nullness/NonNullInitialization.java +++ b/checker/tests/nullness/NonNullInitialization.java @@ -1,10 +1,10 @@ import org.checkerframework.checker.nullness.qual.*; public class NonNullInitialization { - private String test = "test"; + private String test = "test"; - public static void main(String[] args) { - NonNullInitialization n = new NonNullInitialization(); - n.test.equals("ASD"); - } + public static void main(String[] args) { + NonNullInitialization n = new NonNullInitialization(); + n.test.equals("ASD"); + } } diff --git a/checker/tests/nullness/NonNullIteratorNext.java b/checker/tests/nullness/NonNullIteratorNext.java index bd2e94b19d2..7930447d4d2 100644 --- a/checker/tests/nullness/NonNullIteratorNext.java +++ b/checker/tests/nullness/NonNullIteratorNext.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class NonNullIteratorNext { - interface MyIterator extends java.util.Iterator { - @NonNull E next(); - } + interface MyIterator extends java.util.Iterator { + @NonNull E next(); + } - interface MyList extends java.util.Collection { - MyIterator iterator(); - } + interface MyList extends java.util.Collection { + MyIterator iterator(); + } - void forEachLoop(MyList list) { - for (T elem : list) {} - } + void forEachLoop(MyList list) { + for (T elem : list) {} + } } diff --git a/checker/tests/nullness/NullableArrays.java b/checker/tests/nullness/NullableArrays.java index f068d18e149..5a9cc220d2c 100644 --- a/checker/tests/nullness/NullableArrays.java +++ b/checker/tests/nullness/NullableArrays.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.*; public class NullableArrays { - private byte @Nullable [] padding; + private byte @Nullable [] padding; - public NullableArrays(byte @Nullable [] padding) { - this.padding = padding; - } + public NullableArrays(byte @Nullable [] padding) { + this.padding = padding; + } } diff --git a/checker/tests/nullness/NullableConstructor.java b/checker/tests/nullness/NullableConstructor.java index a6f8d4bf9cb..deeea46ea72 100644 --- a/checker/tests/nullness/NullableConstructor.java +++ b/checker/tests/nullness/NullableConstructor.java @@ -2,6 +2,6 @@ public class NullableConstructor { - // :: error: (nullness.on.constructor) - @Nullable NullableConstructor() {} + // :: error: (nullness.on.constructor) + @Nullable NullableConstructor() {} } diff --git a/checker/tests/nullness/NullableObject.java b/checker/tests/nullness/NullableObject.java index c95cdb5551e..2567fd58aca 100644 --- a/checker/tests/nullness/NullableObject.java +++ b/checker/tests/nullness/NullableObject.java @@ -2,12 +2,12 @@ public class NullableObject { - void foo() { - // :: error: (nullness.on.new.object) - Object nbl = new @Nullable Object(); - } + void foo() { + // :: error: (nullness.on.new.object) + Object nbl = new @Nullable Object(); + } - void bar() { - Object nn = new Object(); - } + void bar() { + Object nn = new Object(); + } } diff --git a/checker/tests/nullness/NullnessFieldInvar.java b/checker/tests/nullness/NullnessFieldInvar.java index 9eb8fbc55e8..44c4365092f 100644 --- a/checker/tests/nullness/NullnessFieldInvar.java +++ b/checker/tests/nullness/NullnessFieldInvar.java @@ -5,172 +5,172 @@ import org.checkerframework.framework.qual.FieldInvariant; public class NullnessFieldInvar { - public class Super { - public final @Nullable Object o; - public @Nullable Object nonfinal = null; + public class Super { + public final @Nullable Object o; + public @Nullable Object nonfinal = null; - public Super(@Nullable Object o) { - this.o = o; + public Super(@Nullable Object o) { + this.o = o; + } } - } - @FieldInvariant(field = "o", qualifier = NonNull.class) - class Sub extends Super { - public final @Nullable Object subO; - - public Sub(@NonNull Object o) { - super(o); - subO = null; + @FieldInvariant(field = "o", qualifier = NonNull.class) + class Sub extends Super { + public final @Nullable Object subO; + + public Sub(@NonNull Object o) { + super(o); + subO = null; + } + + public Sub(@NonNull Object o, @Nullable Object subO) { + super(o); + this.subO = subO; + } + + void test() { + @NonNull Object x1 = this.o; + @NonNull Object x2 = o; + @NonNull Object x3 = super.o; + } } - public Sub(@NonNull Object o, @Nullable Object subO) { - super(o); - this.subO = subO; + class SubSub1 extends Sub { + public SubSub1(@NonNull Object o) { + super(o); + } } - void test() { - @NonNull Object x1 = this.o; - @NonNull Object x2 = o; - @NonNull Object x3 = super.o; + @FieldInvariant( + field = {"o", "subO"}, + qualifier = NonNull.class) + class SubSub2 extends Sub { + public SubSub2(@NonNull Object o) { + super(o); + } } - } - class SubSub1 extends Sub { - public SubSub1(@NonNull Object o) { - super(o); - } - } - - @FieldInvariant( - field = {"o", "subO"}, - qualifier = NonNull.class) - class SubSub2 extends Sub { - public SubSub2(@NonNull Object o) { - super(o); - } - } - - class Use { - void test(Super superO, Sub sub, SubSub1 subSub1, SubSub2 subSub2) { - // :: error: (assignment.type.incompatible) - @NonNull Object x1 = superO.o; - @NonNull Object x2 = sub.o; - @NonNull Object x3 = subSub1.o; - - // :: error: (assignment.type.incompatible) - @NonNull Object x5 = sub.subO; - // :: error: (assignment.type.incompatible) - @NonNull Object x6 = subSub1.subO; - @NonNull Object x7 = subSub2.subO; + class Use { + void test(Super superO, Sub sub, SubSub1 subSub1, SubSub2 subSub2) { + // :: error: (assignment.type.incompatible) + @NonNull Object x1 = superO.o; + @NonNull Object x2 = sub.o; + @NonNull Object x3 = subSub1.o; + + // :: error: (assignment.type.incompatible) + @NonNull Object x5 = sub.subO; + // :: error: (assignment.type.incompatible) + @NonNull Object x6 = subSub1.subO; + @NonNull Object x7 = subSub2.subO; + } + + void test2( + SP superO, SB sub, SS1 subSub1, SS2 subSub2) { + // :: error: (assignment.type.incompatible) + @NonNull Object x1 = superO.o; + @NonNull Object x2 = sub.o; + @NonNull Object x3 = subSub1.o; + + // :: error: (assignment.type.incompatible) + @NonNull Object x5 = sub.subO; + // :: error: (assignment.type.incompatible) + @NonNull Object x6 = subSub1.subO; + @NonNull Object x7 = subSub2.subO; + } } - void test2( - SP superO, SB sub, SS1 subSub1, SS2 subSub2) { - // :: error: (assignment.type.incompatible) - @NonNull Object x1 = superO.o; - @NonNull Object x2 = sub.o; - @NonNull Object x3 = subSub1.o; - - // :: error: (assignment.type.incompatible) - @NonNull Object x5 = sub.subO; - // :: error: (assignment.type.incompatible) - @NonNull Object x6 = subSub1.subO; - @NonNull Object x7 = subSub2.subO; + class SuperWithNonFinal { + @Nullable Object nonfinal = null; } - } - - class SuperWithNonFinal { - @Nullable Object nonfinal = null; - } - - // nonfinal isn't final - // :: error: (field.invariant.not.final) - @FieldInvariant(field = "nonfinal", qualifier = NonNull.class) - class SubSubInvalid extends SuperWithNonFinal {} - - // field is declared in this class - // :: error: (field.invariant.not.found) - @FieldInvariant(field = "field", qualifier = NonNull.class) - class Invalid { - final Object field = new Object(); - } - - @FieldInvariant( - field = {"o", "subO"}, - qualifier = NonNull.class) - class Shadowing extends SubSub2 { - @Nullable Object o; - @Nullable Object subO; - - void test() { - // :: error: (assignment.type.incompatible) - @NonNull Object x = o; // error - // :: error: (assignment.type.incompatible) - @NonNull Object x2 = subO; // error - - @NonNull Object x3 = super.o; - @NonNull Object x4 = super.subO; + + // nonfinal isn't final + // :: error: (field.invariant.not.final) + @FieldInvariant(field = "nonfinal", qualifier = NonNull.class) + class SubSubInvalid extends SuperWithNonFinal {} + + // field is declared in this class + // :: error: (field.invariant.not.found) + @FieldInvariant(field = "field", qualifier = NonNull.class) + class Invalid { + final Object field = new Object(); } - public Shadowing() { - super(""); + @FieldInvariant( + field = {"o", "subO"}, + qualifier = NonNull.class) + class Shadowing extends SubSub2 { + @Nullable Object o; + @Nullable Object subO; + + void test() { + // :: error: (assignment.type.incompatible) + @NonNull Object x = o; // error + // :: error: (assignment.type.incompatible) + @NonNull Object x2 = subO; // error + + @NonNull Object x3 = super.o; + @NonNull Object x4 = super.subO; + } + + public Shadowing() { + super(""); + } } - } - // inherits: @FieldInvariant(field = {"o", "subO"}, qualifier = NonNull.class) - class Inherits extends SubSub2 { - @Nullable Object o; - @Nullable Object subO; + // inherits: @FieldInvariant(field = {"o", "subO"}, qualifier = NonNull.class) + class Inherits extends SubSub2 { + @Nullable Object o; + @Nullable Object subO; + + void test() { + // :: error: (assignment.type.incompatible) + @NonNull Object x = o; // error + // :: error: (assignment.type.incompatible) + @NonNull Object x2 = subO; // error - void test() { - // :: error: (assignment.type.incompatible) - @NonNull Object x = o; // error - // :: error: (assignment.type.incompatible) - @NonNull Object x2 = subO; // error + @NonNull Object x3 = super.o; + @NonNull Object x4 = super.subO; + } - @NonNull Object x3 = super.o; - @NonNull Object x4 = super.subO; + public Inherits() { + super(""); + } } - public Inherits() { - super(""); + class Super2 {} + + // :: error: (field.invariant.not.wellformed) + @FieldInvariant( + field = {}, + qualifier = NonNull.class) + class Invalid1 extends Super2 {} + + // :: error: (field.invariant.not.wellformed) + @FieldInvariant( + field = {"a", "b"}, + qualifier = {NonNull.class, NonNull.class, NonNull.class}) + class Invalid2 extends Super2 {} + + // :: error: (field.invariant.not.found) + @FieldInvariant(field = "x", qualifier = NonNull.class) + class NoSuper {} + + class SuperManyFields { + public final @Nullable Object field1 = null; + public final @Nullable Object field2 = null; + public final @Nullable Object field3 = null; + public final @Nullable Object field4 = null; } - } - - class Super2 {} - - // :: error: (field.invariant.not.wellformed) - @FieldInvariant( - field = {}, - qualifier = NonNull.class) - class Invalid1 extends Super2 {} - - // :: error: (field.invariant.not.wellformed) - @FieldInvariant( - field = {"a", "b"}, - qualifier = {NonNull.class, NonNull.class, NonNull.class}) - class Invalid2 extends Super2 {} - - // :: error: (field.invariant.not.found) - @FieldInvariant(field = "x", qualifier = NonNull.class) - class NoSuper {} - - class SuperManyFields { - public final @Nullable Object field1 = null; - public final @Nullable Object field2 = null; - public final @Nullable Object field3 = null; - public final @Nullable Object field4 = null; - } - - @FieldInvariant( - field = {"field1", "field2", "field3", "field4"}, - qualifier = NonNull.class) - class SubManyFields extends SuperManyFields { - void test() { - field1.toString(); - field2.toString(); - field3.toString(); - field4.toString(); + + @FieldInvariant( + field = {"field1", "field2", "field3", "field4"}, + qualifier = NonNull.class) + class SubManyFields extends SuperManyFields { + void test() { + field1.toString(); + field2.toString(); + field3.toString(); + field4.toString(); + } } - } } diff --git a/checker/tests/nullness/NullnessIssue4996.java b/checker/tests/nullness/NullnessIssue4996.java index 9362f87a41c..05688b3a00b 100644 --- a/checker/tests/nullness/NullnessIssue4996.java +++ b/checker/tests/nullness/NullnessIssue4996.java @@ -1,21 +1,21 @@ class NullnessIssue4996 { - abstract class CaptureOuter { - abstract T get(); + abstract class CaptureOuter { + abstract T get(); - abstract class Inner { - abstract T get(); + abstract class Inner { + abstract T get(); + } } - } - class Client { - Object getFrom(CaptureOuter o) { - // :: error: (return.type.incompatible) - return o.get(); - } + class Client { + Object getFrom(CaptureOuter o) { + // :: error: (return.type.incompatible) + return o.get(); + } - Object getFrom(CaptureOuter.Inner o) { - // :: error: (return.type.incompatible) - return o.get(); + Object getFrom(CaptureOuter.Inner o) { + // :: error: (return.type.incompatible) + return o.get(); + } } - } } diff --git a/checker/tests/nullness/ObjectsRequireNonNull.java b/checker/tests/nullness/ObjectsRequireNonNull.java index 3d2850c955c..a4274f05b49 100644 --- a/checker/tests/nullness/ObjectsRequireNonNull.java +++ b/checker/tests/nullness/ObjectsRequireNonNull.java @@ -1,16 +1,17 @@ // Test case for https://tinyurl.com/cfissue/3149 . -import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + public class ObjectsRequireNonNull { - void foo(@Nullable Object nble, @NonNull Object nn) { - // :: error: (argument.type.incompatible) - Objects.requireNonNull(null); - // :: error: (argument.type.incompatible) - Objects.requireNonNull(nble); - Objects.requireNonNull("hello"); - Objects.requireNonNull(nn); - } + void foo(@Nullable Object nble, @NonNull Object nn) { + // :: error: (argument.type.incompatible) + Objects.requireNonNull(null); + // :: error: (argument.type.incompatible) + Objects.requireNonNull(nble); + Objects.requireNonNull("hello"); + Objects.requireNonNull(nn); + } } diff --git a/checker/tests/nullness/ObjectsRequireNonNullElse.java b/checker/tests/nullness/ObjectsRequireNonNullElse.java index 54615b74bcc..90d1c24c182 100644 --- a/checker/tests/nullness/ObjectsRequireNonNullElse.java +++ b/checker/tests/nullness/ObjectsRequireNonNullElse.java @@ -7,12 +7,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class ObjectsRequireNonNullElse { - public static void main(String[] args) { - @NonNull String value = requireNonNullElse(null, "Something"); - System.err.println(requireNonNullElse(null, "Something")); + public static void main(String[] args) { + @NonNull String value = requireNonNullElse(null, "Something"); + System.err.println(requireNonNullElse(null, "Something")); - // This should fail typechecks, because it fails at run time. - // :: error: (argument.type.incompatible) - System.err.println((Object) requireNonNullElse(null, null)); - } + // This should fail typechecks, because it fails at run time. + // :: error: (argument.type.incompatible) + System.err.println((Object) requireNonNullElse(null, null)); + } } diff --git a/checker/tests/nullness/OptTest.java b/checker/tests/nullness/OptTest.java index 026f8c514a5..be6f20fabfe 100644 --- a/checker/tests/nullness/OptTest.java +++ b/checker/tests/nullness/OptTest.java @@ -5,12 +5,12 @@ public class OptTest { - @SuppressWarnings("dereference.of.nullable") // requires refinement like for Optional.ifPresent. - void m1(@Nullable String o) { - Opt.ifPresent(o, s -> o.toString()); - } + @SuppressWarnings("dereference.of.nullable") // requires refinement like for Optional.ifPresent. + void m1(@Nullable String o) { + Opt.ifPresent(o, s -> o.toString()); + } - void m2(@Nullable String o) { - Opt.ifPresent(o, s -> s.toString()); - } + void m2(@Nullable String o) { + Opt.ifPresent(o, s -> s.toString()); + } } diff --git a/checker/tests/nullness/OverrideANNA.java b/checker/tests/nullness/OverrideANNA.java index 1da46a28534..1cc7cba6e54 100644 --- a/checker/tests/nullness/OverrideANNA.java +++ b/checker/tests/nullness/OverrideANNA.java @@ -2,27 +2,27 @@ import org.checkerframework.checker.nullness.qual.*; public class OverrideANNA { - static class Super { - Object f; + static class Super { + Object f; - @EnsuresNonNull("f") - void setf(@UnknownInitialization Super this) { - f = new Object(); - } + @EnsuresNonNull("f") + void setf(@UnknownInitialization Super this) { + f = new Object(); + } - Super() { - setf(); + Super() { + setf(); + } } - } - static class Sub extends Super { - @Override - // :: error: (contracts.postcondition.not.satisfied) - void setf(@UnknownInitialization Sub this) {} - } + static class Sub extends Super { + @Override + // :: error: (contracts.postcondition.not.satisfied) + void setf(@UnknownInitialization Sub this) {} + } - public static void main(String[] args) { - Super s = new Sub(); - s.f.hashCode(); - } + public static void main(String[] args) { + Super s = new Sub(); + s.f.hashCode(); + } } diff --git a/checker/tests/nullness/OverrideANNA2.java b/checker/tests/nullness/OverrideANNA2.java index 693d9312ff6..90f3782358b 100644 --- a/checker/tests/nullness/OverrideANNA2.java +++ b/checker/tests/nullness/OverrideANNA2.java @@ -2,38 +2,38 @@ import org.checkerframework.checker.nullness.qual.*; public class OverrideANNA2 { - static class Super { - Object f; + static class Super { + Object f; - @EnsuresNonNull("f") // Super.f must be non-null - void setf(@UnknownInitialization Super this) { - f = new Object(); - } + @EnsuresNonNull("f") // Super.f must be non-null + void setf(@UnknownInitialization Super this) { + f = new Object(); + } - Super() { - setf(); + Super() { + setf(); + } } - } - static class Sub extends Super { - Object f; // This shadows super.f + static class Sub extends Super { + Object f; // This shadows super.f - @Override - @EnsuresNonNull("f") - // We cannot ensure that Super.f is non-null since it is - // shadowed by Sub.f, hence we get an error. - // :: error: (contracts.postcondition.override.invalid) - void setf(@UnknownInitialization Sub this) { - f = new Object(); - } + @Override + @EnsuresNonNull("f") + // We cannot ensure that Super.f is non-null since it is + // shadowed by Sub.f, hence we get an error. + // :: error: (contracts.postcondition.override.invalid) + void setf(@UnknownInitialization Sub this) { + f = new Object(); + } - Sub() { - setf(); + Sub() { + setf(); + } } - } - public static void main(String[] args) { - Super s = new Sub(); - s.f.hashCode(); - } + public static void main(String[] args) { + Super s = new Sub(); + s.f.hashCode(); + } } diff --git a/checker/tests/nullness/OverrideANNA3.java b/checker/tests/nullness/OverrideANNA3.java index 1abd4f5c611..98f38ca0ea1 100644 --- a/checker/tests/nullness/OverrideANNA3.java +++ b/checker/tests/nullness/OverrideANNA3.java @@ -2,32 +2,32 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; public class OverrideANNA3 { - static class Super { - Object f; - Object g; + static class Super { + Object f; + Object g; - @EnsuresNonNull({"f", "g"}) - void setfg(@UnknownInitialization Super this) { - f = new Object(); - g = new Object(); - } + @EnsuresNonNull({"f", "g"}) + void setfg(@UnknownInitialization Super this) { + f = new Object(); + g = new Object(); + } - Super() { - setfg(); + Super() { + setfg(); + } } - } - static class Sub extends Super { - @Override - @EnsuresNonNull("f") - // :: error: (contracts.postcondition.override.invalid) - void setfg(@UnknownInitialization Sub this) { - f = new Object(); + static class Sub extends Super { + @Override + @EnsuresNonNull("f") + // :: error: (contracts.postcondition.override.invalid) + void setfg(@UnknownInitialization Sub this) { + f = new Object(); + } } - } - public static void main(String[] args) { - Super s = new Sub(); - s.g.hashCode(); - } + public static void main(String[] args) { + Super s = new Sub(); + s.g.hashCode(); + } } diff --git a/checker/tests/nullness/OverrideGenerics.java b/checker/tests/nullness/OverrideGenerics.java index 893905ea241..faed45db09a 100644 --- a/checker/tests/nullness/OverrideGenerics.java +++ b/checker/tests/nullness/OverrideGenerics.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.nullness.qual.*; class OGSuper { - public void m(S p) {} + public void m(S p) {} } class OGImpl1 extends OGSuper { - public void m(T p) {} + public void m(T p) {} } class OGImpl2 extends OGSuper { - public void m(T p) {} + public void m(T p) {} } diff --git a/checker/tests/nullness/OverrideNNOE.java b/checker/tests/nullness/OverrideNNOE.java index 090612af73a..dd96eea0cde 100644 --- a/checker/tests/nullness/OverrideNNOE.java +++ b/checker/tests/nullness/OverrideNNOE.java @@ -2,23 +2,23 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class OverrideNNOE { - static class Super { - @Nullable Object f; + static class Super { + @Nullable Object f; - void call() {} - } + void call() {} + } - static class Sub extends Super { - @Override - @RequiresNonNull("f") - // :: error: (contracts.precondition.override.invalid) - void call() { - f.hashCode(); + static class Sub extends Super { + @Override + @RequiresNonNull("f") + // :: error: (contracts.precondition.override.invalid) + void call() { + f.hashCode(); + } } - } - public static void main(String[] args) { - Super s = new Sub(); - s.call(); - } + public static void main(String[] args) { + Super s = new Sub(); + s.call(); + } } diff --git a/checker/tests/nullness/OverrideNNOE2.java b/checker/tests/nullness/OverrideNNOE2.java index e122ea9f629..7f98f003967 100644 --- a/checker/tests/nullness/OverrideNNOE2.java +++ b/checker/tests/nullness/OverrideNNOE2.java @@ -2,27 +2,27 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class OverrideNNOE2 { - static class Super { - @Nullable Object f; + static class Super { + @Nullable Object f; - @RequiresNonNull("f") - void call() {} - } + @RequiresNonNull("f") + void call() {} + } - static class Sub extends Super { - @Nullable Object g; + static class Sub extends Super { + @Nullable Object g; - @Override - @RequiresNonNull({"f", "g"}) - // :: error: (contracts.precondition.override.invalid) - void call() { - g.hashCode(); + @Override + @RequiresNonNull({"f", "g"}) + // :: error: (contracts.precondition.override.invalid) + void call() { + g.hashCode(); + } } - } - public static void main(String[] args) { - Super s = new Sub(); - s.f = new Object(); - s.call(); - } + public static void main(String[] args) { + Super s = new Sub(); + s.f = new Object(); + s.call(); + } } diff --git a/checker/tests/nullness/ParameterExpression.java b/checker/tests/nullness/ParameterExpression.java index 8fa8ec24be6..24dae25094e 100644 --- a/checker/tests/nullness/ParameterExpression.java +++ b/checker/tests/nullness/ParameterExpression.java @@ -1,170 +1,171 @@ -import java.util.Map; import org.checkerframework.checker.nullness.qual.*; +import java.util.Map; + public class ParameterExpression { - public void m1( - @Nullable Object o, @Nullable Object o1, @Nullable Object o2, @Nullable Object o3) { - // :: error: (flowexpr.parse.error.postcondition) - m2(o); - // :: error: (dereference.of.nullable) - o.toString(); - m3(o); - o.toString(); - m4(o1, o2, o3); - // :: error: (dereference.of.nullable) - o1.toString(); - // :: error: (dereference.of.nullable) - o2.toString(); - o3.toString(); - } - - @SuppressWarnings("assert.postcondition.not.satisfied") - // "#0" is illegal syntax; it should be "#1" - @EnsuresNonNull("#0") - // :: error: (flowexpr.parse.error) - public void m2(final @Nullable Object o) {} - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("#1") - public void m3(final @Nullable Object o) {} - - @SuppressWarnings("contracts.postcondition.not.satisfied") - @EnsuresNonNull("#3") - public void m4(@Nullable Object x1, @Nullable Object x2, final @Nullable Object x3) {} - - // Formal parameter names should not be used in signatures (pre/postcondition, conditional - // postcondition, and formal parameter annotations). Use "#paramNum", because the parameter - // names are not saved in bytecode. - - @Nullable Object field = null; - - // Postconditions - @EnsuresNonNull("field") // OK - public void m5() { - field = new Object(); - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6a(Object param) { - param = new Object(); - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6b(Object param) { - // :: error: (assignment.type.incompatible) - param = null; - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6c(@Nullable Object param) { - param = new Object(); - } - - @EnsuresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m6d(@Nullable Object param) { - param = null; - } - - @EnsuresNonNull("param.toString()") - // :: error: (flowexpr.parse.error) - public void m6e(@Nullable Object param) { - param = null; - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7a(Object field) { - field = new Object(); - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7b(Object field) { - // :: error: (assignment.type.incompatible) - field = null; - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7c(@Nullable Object field) { - field = new Object(); - } - - @EnsuresNonNull("field") - // :: error: (contracts.postcondition.not.satisfied) - // :: warning: (expression.parameter.name.shadows.field) - public void m7d(@Nullable Object field) { - field = null; - } - - // Preconditions - @RequiresNonNull("field") // OK - public void m8() {} - - @RequiresNonNull("param") - // :: error: (flowexpr.parse.error) - public void m9(Object param) {} - - // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a - // formal parameter. - @RequiresNonNull("field") - // :: warning: (expression.parameter.name.shadows.field) - public void m10(Object field) {} - - // Conditional postconditions - @EnsuresNonNullIf(result = true, expression = "field") // OK - public boolean m11() { - field = new Object(); - return true; - } - - @EnsuresNonNullIf(result = true, expression = "param") - // :: error: (flowexpr.parse.error) - public boolean m12(Object param) { - param = new Object(); - return true; - } - - // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a - // formal parameter. - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13a(@Nullable Object field) { - field = new Object(); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13b(@Nullable Object field) { - field = new Object(); - return false; - } - - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13c(@Nullable Object field) { - field = null; - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @EnsuresNonNullIf(result = true, expression = "field") - // :: warning: (expression.parameter.name.shadows.field) - public boolean m13d(@Nullable Object field) { - field = null; - return false; - } - - // Annotations on formal parameters referring to a formal parameter of the same method. - // :: error: (expression.unparsable.type.invalid) - public void m14(@KeyFor("param2") Object param1, Map param2) {} + public void m1( + @Nullable Object o, @Nullable Object o1, @Nullable Object o2, @Nullable Object o3) { + // :: error: (flowexpr.parse.error.postcondition) + m2(o); + // :: error: (dereference.of.nullable) + o.toString(); + m3(o); + o.toString(); + m4(o1, o2, o3); + // :: error: (dereference.of.nullable) + o1.toString(); + // :: error: (dereference.of.nullable) + o2.toString(); + o3.toString(); + } + + @SuppressWarnings("assert.postcondition.not.satisfied") + // "#0" is illegal syntax; it should be "#1" + @EnsuresNonNull("#0") + // :: error: (flowexpr.parse.error) + public void m2(final @Nullable Object o) {} + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("#1") + public void m3(final @Nullable Object o) {} + + @SuppressWarnings("contracts.postcondition.not.satisfied") + @EnsuresNonNull("#3") + public void m4(@Nullable Object x1, @Nullable Object x2, final @Nullable Object x3) {} + + // Formal parameter names should not be used in signatures (pre/postcondition, conditional + // postcondition, and formal parameter annotations). Use "#paramNum", because the parameter + // names are not saved in bytecode. + + @Nullable Object field = null; + + // Postconditions + @EnsuresNonNull("field") // OK + public void m5() { + field = new Object(); + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6a(Object param) { + param = new Object(); + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6b(Object param) { + // :: error: (assignment.type.incompatible) + param = null; + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6c(@Nullable Object param) { + param = new Object(); + } + + @EnsuresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m6d(@Nullable Object param) { + param = null; + } + + @EnsuresNonNull("param.toString()") + // :: error: (flowexpr.parse.error) + public void m6e(@Nullable Object param) { + param = null; + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7a(Object field) { + field = new Object(); + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7b(Object field) { + // :: error: (assignment.type.incompatible) + field = null; + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7c(@Nullable Object field) { + field = new Object(); + } + + @EnsuresNonNull("field") + // :: error: (contracts.postcondition.not.satisfied) + // :: warning: (expression.parameter.name.shadows.field) + public void m7d(@Nullable Object field) { + field = null; + } + + // Preconditions + @RequiresNonNull("field") // OK + public void m8() {} + + @RequiresNonNull("param") + // :: error: (flowexpr.parse.error) + public void m9(Object param) {} + + // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a + // formal parameter. + @RequiresNonNull("field") + // :: warning: (expression.parameter.name.shadows.field) + public void m10(Object field) {} + + // Conditional postconditions + @EnsuresNonNullIf(result = true, expression = "field") // OK + public boolean m11() { + field = new Object(); + return true; + } + + @EnsuresNonNullIf(result = true, expression = "param") + // :: error: (flowexpr.parse.error) + public boolean m12(Object param) { + param = new Object(); + return true; + } + + // Warning issued. 'field' is a field, but in this case what matters is that it is the name of a + // formal parameter. + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13a(@Nullable Object field) { + field = new Object(); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13b(@Nullable Object field) { + field = new Object(); + return false; + } + + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13c(@Nullable Object field) { + field = null; + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @EnsuresNonNullIf(result = true, expression = "field") + // :: warning: (expression.parameter.name.shadows.field) + public boolean m13d(@Nullable Object field) { + field = null; + return false; + } + + // Annotations on formal parameters referring to a formal parameter of the same method. + // :: error: (expression.unparsable.type.invalid) + public void m14(@KeyFor("param2") Object param1, Map param2) {} } diff --git a/checker/tests/nullness/PolyTest.java b/checker/tests/nullness/PolyTest.java index 22621772c10..05bdee71b0f 100644 --- a/checker/tests/nullness/PolyTest.java +++ b/checker/tests/nullness/PolyTest.java @@ -1,15 +1,15 @@ import org.checkerframework.checker.nullness.qual.PolyNull; class PolyTest { - void foo1(@PolyNull Object nbl) { - if (nbl == null) { - // :: error: (dereference.of.nullable) - nbl.toString(); + void foo1(@PolyNull Object nbl) { + if (nbl == null) { + // :: error: (dereference.of.nullable) + nbl.toString(); + } } - } - void foo2(@PolyNull Object nbl) { - // :: error: (dereference.of.nullable) - nbl.toString(); - } + void foo2(@PolyNull Object nbl) { + // :: error: (dereference.of.nullable) + nbl.toString(); + } } diff --git a/checker/tests/nullness/Polymorphism.java b/checker/tests/nullness/Polymorphism.java index 6fdc99a4f6c..9d833825508 100644 --- a/checker/tests/nullness/Polymorphism.java +++ b/checker/tests/nullness/Polymorphism.java @@ -1,40 +1,40 @@ import org.checkerframework.checker.nullness.qual.*; public class Polymorphism { - // Test parameters - @PolyNull String identity(@PolyNull String s) { - return s; - } + // Test parameters + @PolyNull String identity(@PolyNull String s) { + return s; + } - void testParam() { - // Test without inference - String nullable = null; - @NonNull String nonNull = "m"; + void testParam() { + // Test without inference + String nullable = null; + @NonNull String nonNull = "m"; - // :: error: (assignment.type.incompatible) - nonNull = identity(nullable); // invalid - nonNull = identity(nonNull); + // :: error: (assignment.type.incompatible) + nonNull = identity(nullable); // invalid + nonNull = identity(nonNull); - // test flow - nullable = "m"; - nonNull = identity(nullable); // valid - } + // test flow + nullable = "m"; + nonNull = identity(nullable); // valid + } - // Test within a method - @PolyNull String random(@PolyNull String m) { - if (m == "d") { - // :: error: (return.type.incompatible) - return null; // invalid + // Test within a method + @PolyNull String random(@PolyNull String m) { + if (m == "d") { + // :: error: (return.type.incompatible) + return null; // invalid + } + return "m"; // valid } - return "m"; // valid - } - public static @PolyNull Object staticIdentity(@PolyNull Object a) { - return a; - } + public static @PolyNull Object staticIdentity(@PolyNull Object a) { + return a; + } - void testStatic(@Nullable Object nullable, @NonNull Object nonnull) { - @Nullable Object nullable2 = staticIdentity(nullable); - @NonNull Object nonnull2 = staticIdentity(nonnull); - } + void testStatic(@Nullable Object nullable, @NonNull Object nonnull) { + @Nullable Object nullable2 = staticIdentity(nullable); + @NonNull Object nonnull2 = staticIdentity(nonnull); + } } diff --git a/checker/tests/nullness/PolymorphismArrays.java b/checker/tests/nullness/PolymorphismArrays.java index 18a60f71975..1311b3998bd 100644 --- a/checker/tests/nullness/PolymorphismArrays.java +++ b/checker/tests/nullness/PolymorphismArrays.java @@ -2,56 +2,56 @@ public class PolymorphismArrays { - public PolymorphismArrays(String[][] elts) { - this.elts = elts; - } - - public static boolean @PolyNull [] bad(boolean @PolyNull [] seq) { - // Cannot directly return null; - // :: error: (return.type.incompatible) - return null; - } - - public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, int start, int end) { - // Know from comparison that argument is nullable -> also return is nullable. - if (seq == null) { - return null; - } - return new boolean[] {}; - } - - public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, long start, int end) { - return slice(seq, (int) start, end); - } - - public static @PolyNull String[] intern(@PolyNull String[] a) { - return a; - } - - // from OneOfStringSequence.java - private String[][] elts; - - @SuppressWarnings("purity") // ignore, analysis too strict. - @org.checkerframework.dataflow.qual.Pure - public PolymorphismArrays clone() { - PolymorphismArrays result = new PolymorphismArrays(elts.clone()); - for (int i = 0; i < elts.length; i++) { - result.elts[i] = intern(elts[i].clone()); - } - return result; - } - - public void simplified() { - String[][] elts = new String[0][0]; - String[][] clone = elts.clone(); - String[] results = intern(elts[0].clone()); - } - - public static int indexOf(T[] a) { - return indexOfEq(a); - } - - public static int indexOfEq(@PolyNull Object[] a) { - return -1; - } + public PolymorphismArrays(String[][] elts) { + this.elts = elts; + } + + public static boolean @PolyNull [] bad(boolean @PolyNull [] seq) { + // Cannot directly return null; + // :: error: (return.type.incompatible) + return null; + } + + public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, int start, int end) { + // Know from comparison that argument is nullable -> also return is nullable. + if (seq == null) { + return null; + } + return new boolean[] {}; + } + + public static boolean @PolyNull [] slice(boolean @PolyNull [] seq, long start, int end) { + return slice(seq, (int) start, end); + } + + public static @PolyNull String[] intern(@PolyNull String[] a) { + return a; + } + + // from OneOfStringSequence.java + private String[][] elts; + + @SuppressWarnings("purity") // ignore, analysis too strict. + @org.checkerframework.dataflow.qual.Pure + public PolymorphismArrays clone() { + PolymorphismArrays result = new PolymorphismArrays(elts.clone()); + for (int i = 0; i < elts.length; i++) { + result.elts[i] = intern(elts[i].clone()); + } + return result; + } + + public void simplified() { + String[][] elts = new String[0][0]; + String[][] clone = elts.clone(); + String[] results = intern(elts[0].clone()); + } + + public static int indexOf(T[] a) { + return indexOfEq(a); + } + + public static int indexOfEq(@PolyNull Object[] a) { + return -1; + } } diff --git a/checker/tests/nullness/PostconditionBug.java b/checker/tests/nullness/PostconditionBug.java index 25174618d25..55e5b740644 100644 --- a/checker/tests/nullness/PostconditionBug.java +++ b/checker/tests/nullness/PostconditionBug.java @@ -3,9 +3,9 @@ public class PostconditionBug { - void a(@UnknownInitialization PostconditionBug this) { - @NonNull String f = "abc"; - // :: error: (assignment.type.incompatible) - f = null; - } + void a(@UnknownInitialization PostconditionBug this) { + @NonNull String f = "abc"; + // :: error: (assignment.type.incompatible) + f = null; + } } diff --git a/checker/tests/nullness/PreconditionFieldNotInStore.java b/checker/tests/nullness/PreconditionFieldNotInStore.java index 4c1aa59fe4c..aab732f1cc6 100644 --- a/checker/tests/nullness/PreconditionFieldNotInStore.java +++ b/checker/tests/nullness/PreconditionFieldNotInStore.java @@ -6,20 +6,20 @@ class PreconditionFieldNotInStore { - private @org.checkerframework.checker.nullness.qual.MonotonicNonNull String filename; + private @org.checkerframework.checker.nullness.qual.MonotonicNonNull String filename; - @org.checkerframework.framework.qual.RequiresQualifier( - expression = {"this.filename"}, - qualifier = org.checkerframework.checker.nullness.qual.Nullable.class) - @org.checkerframework.checker.nullness.qual.NonNull String getIndentString() { - return "indentString"; - } + @org.checkerframework.framework.qual.RequiresQualifier( + expression = {"this.filename"}, + qualifier = org.checkerframework.checker.nullness.qual.Nullable.class) + @org.checkerframework.checker.nullness.qual.NonNull String getIndentString() { + return "indentString"; + } - public void logStackTrace(PrintStream logfile, int[] ste_arr) { - for (int ii = 2; ii < ste_arr.length; ii++) { - int ste = ste_arr[ii]; - logfile.printf("%s %s%n", getIndentString(), ste); + public void logStackTrace(PrintStream logfile, int[] ste_arr) { + for (int ii = 2; ii < ste_arr.length; ii++) { + int ste = ste_arr[ii]; + logfile.printf("%s %s%n", getIndentString(), ste); + } + logfile.flush(); } - logfile.flush(); - } } diff --git a/checker/tests/nullness/PreventClearProperty.java b/checker/tests/nullness/PreventClearProperty.java index 9c8158fbd1c..64fa8700d2c 100644 --- a/checker/tests/nullness/PreventClearProperty.java +++ b/checker/tests/nullness/PreventClearProperty.java @@ -1,127 +1,128 @@ // Same code (but different expected errors) as test PermitClearProperty.java . -import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.common.value.qual.StringVal; +import java.util.Properties; + public class PreventClearProperty { - static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; - - static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; - - @NonNull String getLineSeparator1() { - return System.getProperty("line.separator"); - } - - @NonNull String getLineSeparator2() { - // :: error: (return.type.incompatible) - return System.getProperty(LINE_SEPARATOR); - } - - @NonNull String getMyProperty1() { - // :: error: (return.type.incompatible) - return System.getProperty("my.property.name"); - } - - @NonNull String getMyProperty2() { - // :: error: (return.type.incompatible) - return System.getProperty(MY_PROPERTY_NAME); - } - - @NonNull String getAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.getProperty(propName); - } - - @NonNull String clearLineSeparator1() { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty("line.separator"); - } - - @NonNull String clearLineSeparator2() { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty(LINE_SEPARATOR); - } - - @NonNull String clearMyProperty1() { - // :: error: (return.type.incompatible) - return System.clearProperty("my.property.name"); - } - - @NonNull String clearMyProperty2() { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty(MY_PROPERTY_NAME); - } - - @NonNull String clearAProperty(String propName) { - // :: error: (return.type.incompatible) - // :: error: (clear.system.property) - return System.clearProperty(propName); - } - - void callSetProperties(Properties p) { - // :: error: (clear.system.property) - System.setProperties(p); - } - - // All calls to setProperty are legal because they cannot unset a property. - - @NonNull String setLineSeparator1() { - return System.setProperty("line.separator", "somevalue"); - } - - @NonNull String setLineSeparator2() { - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, "somevalue"); - } - - @NonNull String setMyProperty1() { - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", "somevalue"); - } - - @NonNull String setMyProperty2() { - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, "somevalue"); - } - - @NonNull String setAProperty(String propName) { - // :: error: (return.type.incompatible) - return System.setProperty(propName, "somevalue"); - } - - // These calls to setProperty are illegal because null is not a permitted value. - - @NonNull String setLineSeparatorNull1() { - // :: error: (argument.type.incompatible) - return System.setProperty("line.separator", null); - } - - @NonNull String setLineSeparatorNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(LINE_SEPARATOR, null); - } - - @NonNull String setMyPropertyNull1() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty("my.property.name", null); - } - - @NonNull String setMyPropertyNull2() { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(MY_PROPERTY_NAME, null); - } - - @NonNull String setAPropertyNull(String propName) { - // :: error: (argument.type.incompatible) - // :: error: (return.type.incompatible) - return System.setProperty(propName, null); - } + static final @StringVal("line.separator") String LINE_SEPARATOR = "line.separator"; + + static final @StringVal("my.property.name") String MY_PROPERTY_NAME = "my.property.name"; + + @NonNull String getLineSeparator1() { + return System.getProperty("line.separator"); + } + + @NonNull String getLineSeparator2() { + // :: error: (return.type.incompatible) + return System.getProperty(LINE_SEPARATOR); + } + + @NonNull String getMyProperty1() { + // :: error: (return.type.incompatible) + return System.getProperty("my.property.name"); + } + + @NonNull String getMyProperty2() { + // :: error: (return.type.incompatible) + return System.getProperty(MY_PROPERTY_NAME); + } + + @NonNull String getAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.getProperty(propName); + } + + @NonNull String clearLineSeparator1() { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty("line.separator"); + } + + @NonNull String clearLineSeparator2() { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty(LINE_SEPARATOR); + } + + @NonNull String clearMyProperty1() { + // :: error: (return.type.incompatible) + return System.clearProperty("my.property.name"); + } + + @NonNull String clearMyProperty2() { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty(MY_PROPERTY_NAME); + } + + @NonNull String clearAProperty(String propName) { + // :: error: (return.type.incompatible) + // :: error: (clear.system.property) + return System.clearProperty(propName); + } + + void callSetProperties(Properties p) { + // :: error: (clear.system.property) + System.setProperties(p); + } + + // All calls to setProperty are legal because they cannot unset a property. + + @NonNull String setLineSeparator1() { + return System.setProperty("line.separator", "somevalue"); + } + + @NonNull String setLineSeparator2() { + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, "somevalue"); + } + + @NonNull String setMyProperty1() { + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", "somevalue"); + } + + @NonNull String setMyProperty2() { + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, "somevalue"); + } + + @NonNull String setAProperty(String propName) { + // :: error: (return.type.incompatible) + return System.setProperty(propName, "somevalue"); + } + + // These calls to setProperty are illegal because null is not a permitted value. + + @NonNull String setLineSeparatorNull1() { + // :: error: (argument.type.incompatible) + return System.setProperty("line.separator", null); + } + + @NonNull String setLineSeparatorNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(LINE_SEPARATOR, null); + } + + @NonNull String setMyPropertyNull1() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty("my.property.name", null); + } + + @NonNull String setMyPropertyNull2() { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(MY_PROPERTY_NAME, null); + } + + @NonNull String setAPropertyNull(String propName) { + // :: error: (argument.type.incompatible) + // :: error: (return.type.incompatible) + return System.setProperty(propName, null); + } } diff --git a/checker/tests/nullness/PrimitivesNullness.java b/checker/tests/nullness/PrimitivesNullness.java index 570a563eb3b..74d9d1b9ddf 100644 --- a/checker/tests/nullness/PrimitivesNullness.java +++ b/checker/tests/nullness/PrimitivesNullness.java @@ -1,6 +1,6 @@ public class PrimitivesNullness { - public static void main(String[] args) { - for (String line : new String[] {"a"}) {} - } + public static void main(String[] args) { + for (String line : new String[] {"a"}) {} + } } diff --git a/checker/tests/nullness/PureTest.java b/checker/tests/nullness/PureTest.java index 4b559ef008c..bcad3f3d378 100644 --- a/checker/tests/nullness/PureTest.java +++ b/checker/tests/nullness/PureTest.java @@ -2,131 +2,131 @@ import org.checkerframework.dataflow.qual.*; public class PureTest { - @org.checkerframework.dataflow.qual.Pure - @Nullable Object puremethod(@Nullable Object a) { - return a; - } - - public void test() { - // :: error: (assignment.type.incompatible) - @NonNull Object l0 = puremethod(null); - - if (puremethod(null) == null) { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = puremethod(null); + @org.checkerframework.dataflow.qual.Pure + @Nullable Object puremethod(@Nullable Object a) { + return a; } - if (puremethod("m") != null) { - @NonNull Object l1 = puremethod("m"); - } + public void test() { + // :: error: (assignment.type.incompatible) + @NonNull Object l0 = puremethod(null); - if (puremethod("m") != null) { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = puremethod(null); - } + if (puremethod(null) == null) { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = puremethod(null); + } - if (puremethod("m") != null) { - // :: error: (assignment.type.incompatible) - @NonNull Object l1 = puremethod("n"); - } + if (puremethod("m") != null) { + @NonNull Object l1 = puremethod("m"); + } - Object x = new Object(); + if (puremethod("m") != null) { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = puremethod(null); + } - if (puremethod(x) == null) { - return; - } + if (puremethod("m") != null) { + // :: error: (assignment.type.incompatible) + @NonNull Object l1 = puremethod("n"); + } + + Object x = new Object(); + + if (puremethod(x) == null) { + return; + } - @NonNull Object l2 = puremethod(x); + @NonNull Object l2 = puremethod(x); - x = new Object(); + x = new Object(); - // :: error: (assignment.type.incompatible) - @NonNull Object l3 = puremethod(x); + // :: error: (assignment.type.incompatible) + @NonNull Object l3 = puremethod(x); - // :: error: (assignment.type.incompatible) - @NonNull Object l4 = puremethod("n"); - } + // :: error: (assignment.type.incompatible) + @NonNull Object l4 = puremethod("n"); + } + + public @org.checkerframework.dataflow.qual.Pure @Nullable Object getSuperclass() { + return null; + } - public @org.checkerframework.dataflow.qual.Pure @Nullable Object getSuperclass() { - return null; - } + static void shortCircuitAnd(PureTest pt) { + if ((pt.getSuperclass() != null) && pt.getSuperclass().equals(Enum.class)) { + // empty body + } + } - static void shortCircuitAnd(PureTest pt) { - if ((pt.getSuperclass() != null) && pt.getSuperclass().equals(Enum.class)) { - // empty body + static void shortCircuitOr(PureTest pt) { + if ((pt.getSuperclass() == null) || pt.getSuperclass().equals(Enum.class)) { + // empty body + } } - } - static void shortCircuitOr(PureTest pt) { - if ((pt.getSuperclass() == null) || pt.getSuperclass().equals(Enum.class)) { - // empty body + static void testInstanceofNegative(PureTest pt) { + if (pt.getSuperclass() instanceof Object) { + return; + } + // :: error: (dereference.of.nullable) + pt.getSuperclass().toString(); } - } - static void testInstanceofNegative(PureTest pt) { - if (pt.getSuperclass() instanceof Object) { - return; + static void testInstanceofPositive(PureTest pt) { + if (!(pt.getSuperclass() instanceof Object)) { + return; + } + pt.getSuperclass().toString(); } - // :: error: (dereference.of.nullable) - pt.getSuperclass().toString(); - } - static void testInstanceofPositive(PureTest pt) { - if (!(pt.getSuperclass() instanceof Object)) { - return; + static void testInstanceofPositive2(PureTest pt) { + if (!(pt.getSuperclass() instanceof Object)) { + } else { + pt.getSuperclass().toString(); + } } - pt.getSuperclass().toString(); - } - static void testInstanceofPositive2(PureTest pt) { - if (!(pt.getSuperclass() instanceof Object)) { - } else { - pt.getSuperclass().toString(); + static void testInstanceofNegative2(PureTest pt) { + if (pt.getSuperclass() instanceof Object) { + } else { + return; + } + pt.getSuperclass().toString(); } - } - static void testInstanceofNegative2(PureTest pt) { - if (pt.getSuperclass() instanceof Object) { - } else { - return; + static void testInstanceofString(PureTest pt) { + if (!(pt.getSuperclass() instanceof String)) { + return; + } + pt.getSuperclass().toString(); } - pt.getSuperclass().toString(); - } - static void testInstanceofString(PureTest pt) { - if (!(pt.getSuperclass() instanceof String)) { - return; + static void testContinue(PureTest pt) { + for (; ; ) { + if (pt.getSuperclass() == null) { + System.out.println("m"); + continue; + } + pt.getSuperclass().toString(); + } } - pt.getSuperclass().toString(); - } - - static void testContinue(PureTest pt) { - for (; ; ) { - if (pt.getSuperclass() == null) { - System.out.println("m"); - continue; - } - pt.getSuperclass().toString(); + + void setSuperclass(@Nullable Object no) { + // set the field returned by getSuperclass. } - } - void setSuperclass(@Nullable Object no) { - // set the field returned by getSuperclass. - } + static void testInstanceofPositive3(PureTest pt) { + if (!(pt.getSuperclass() instanceof Object)) { + return; + } else { + pt.setSuperclass(null); + } + // :: error: (dereference.of.nullable) + pt.getSuperclass().toString(); + } - static void testInstanceofPositive3(PureTest pt) { - if (!(pt.getSuperclass() instanceof Object)) { - return; - } else { - pt.setSuperclass(null); + @Override + @SideEffectFree + public String toString() { + return "foo"; } - // :: error: (dereference.of.nullable) - pt.getSuperclass().toString(); - } - - @Override - @SideEffectFree - public String toString() { - return "foo"; - } } diff --git a/checker/tests/nullness/RawAndPrimitive.java b/checker/tests/nullness/RawAndPrimitive.java index 1b106ce56ee..2d50290f8b6 100644 --- a/checker/tests/nullness/RawAndPrimitive.java +++ b/checker/tests/nullness/RawAndPrimitive.java @@ -1,15 +1,15 @@ public class RawAndPrimitive { - public T foo(T startValue) { - return startValue; - } + public T foo(T startValue) { + return startValue; + } - // this tests that DefaultTypeHierarchy.visitPrimitive_Wildcard works - public static void bar(float f) { - // the lower bound of the resultant wildcard (which replaces the raw type argument) will be - // lower than the default annotation on float + // this tests that DefaultTypeHierarchy.visitPrimitive_Wildcard works + public static void bar(float f) { + // the lower bound of the resultant wildcard (which replaces the raw type argument) will be + // lower than the default annotation on float - // :: warning: [unchecked] unchecked call to foo(T) as a member of the raw type - // RawAndPrimitive - new RawAndPrimitive().foo(f); - } + // :: warning: [unchecked] unchecked call to foo(T) as a member of the raw type + // RawAndPrimitive + new RawAndPrimitive().foo(f); + } } diff --git a/checker/tests/nullness/RawInt.java b/checker/tests/nullness/RawInt.java index 2337e2b65a6..abca2cddf22 100644 --- a/checker/tests/nullness/RawInt.java +++ b/checker/tests/nullness/RawInt.java @@ -1,15 +1,15 @@ public class RawInt { - public void compare(int name1in2, int name2in1, VarInfo vi) { - int cmp1 = (name1in2 == -1) ? 0 : vi.varinfo_index - name1in2; - MathMDE.sign(cmp1); - } + public void compare(int name1in2, int name2in1, VarInfo vi) { + int cmp1 = (name1in2 == -1) ? 0 : vi.varinfo_index - name1in2; + MathMDE.sign(cmp1); + } } class VarInfo { - int varinfo_index; + int varinfo_index; } class MathMDE { - public static void sign(int a) {} + public static void sign(int a) {} } diff --git a/checker/tests/nullness/RawInt2.java b/checker/tests/nullness/RawInt2.java index f936301e5e1..83213b49ebe 100644 --- a/checker/tests/nullness/RawInt2.java +++ b/checker/tests/nullness/RawInt2.java @@ -1,18 +1,18 @@ public abstract class RawInt2 { - public void compare(MyVarInfo vi1, MyVarInfo vi2) { + public void compare(MyVarInfo vi1, MyVarInfo vi2) { - int name1in2 = 1; - int name2in1 = 2; - int cmp1 = (name1in2 == -1) ? 0 : vi1.varinfo_index - 1; - // Removing this line eliminates the error, even though cmp2 is not used - int cmp2 = false ? 0 : 15; - sign(cmp1); - } + int name1in2 = 1; + int name2in1 = 2; + int cmp1 = (name1in2 == -1) ? 0 : vi1.varinfo_index - 1; + // Removing this line eliminates the error, even though cmp2 is not used + int cmp2 = false ? 0 : 15; + sign(cmp1); + } - public void sign(int x) {} + public void sign(int x) {} } final class MyVarInfo { - public int varinfo_index = 22; + public int varinfo_index = 22; } diff --git a/checker/tests/nullness/RawParameter.java b/checker/tests/nullness/RawParameter.java index f192c7930ad..c1d18f4ac06 100644 --- a/checker/tests/nullness/RawParameter.java +++ b/checker/tests/nullness/RawParameter.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class RawParameter { - private @Nullable T payload; + private @Nullable T payload; - // Using a parameterized type here avoids the exception - @SuppressWarnings("unchecked") - public void clearPayload(RawParameter node) { - node.payload = null; - } + // Using a parameterized type here avoids the exception + @SuppressWarnings("unchecked") + public void clearPayload(RawParameter node) { + node.payload = null; + } } diff --git a/checker/tests/nullness/RawSuper.java b/checker/tests/nullness/RawSuper.java index faad56f499b..74b9e92a675 100644 --- a/checker/tests/nullness/RawSuper.java +++ b/checker/tests/nullness/RawSuper.java @@ -4,82 +4,82 @@ // This test is broken as it uses multiple classes. Javac halts when seeing the first error public class RawSuper { - class A { - @NonNull Object afield; - - A() { - super(); - mRA(this); - // :: error: (type.incompatible) - mA(this); - afield = new Object(); - mRA(this); - mA(this); + class A { + @NonNull Object afield; + + A() { + super(); + mRA(this); + // :: error: (type.incompatible) + mA(this); + afield = new Object(); + mRA(this); + mA(this); + } + + A(int ignore) { + this.raw(); + afield = new Object(); + } + + void raw(A this) {} + + void nonRaw() {} } - A(int ignore) { - this.raw(); - afield = new Object(); + class B extends A { + @NonNull Object bfield; + + B() { + mRA(this); + mA(this); + mRB(this); + // :: error: (type.incompatible) + mB(this); + bfield = new Object(); + mRA(this); + mA(this); + mRB(this); + mB(this); + } + + void raw(B this) { + // :: error: (type.incompatible) + super.nonRaw(); + } } - void raw(A this) {} - - void nonRaw() {} - } - - class B extends A { - @NonNull Object bfield; - - B() { - mRA(this); - mA(this); - mRB(this); - // :: error: (type.incompatible) - mB(this); - bfield = new Object(); - mRA(this); - mA(this); - mRB(this); - mB(this); + // This test may be extraneous + class C extends B { + @NonNull Object cfield; + + C() { + mRA(this); + mA(this); + mRB(this); + mB(this); + mRC(this); + // :: error: (type.incompatible) + mC(this); + cfield = new Object(); + mRA(this); + mA(this); + mRB(this); + mB(this); + mRC(this); + mC(this); + } } - void raw(B this) { - // :: error: (type.incompatible) - super.nonRaw(); - } - } - - // This test may be extraneous - class C extends B { - @NonNull Object cfield; - - C() { - mRA(this); - mA(this); - mRB(this); - mB(this); - mRC(this); - // :: error: (type.incompatible) - mC(this); - cfield = new Object(); - mRA(this); - mA(this); - mRB(this); - mB(this); - mRC(this); - mC(this); - } - } - - void mA(A a) {} + void mA(A a) {} - void mRA(A a) {} + void mRA(A a) {} - void mB(B b) {} + void mB(B b) {} - void mRB(B b) {} + void mRB(B b) {} - void mC(C c) {} + void mC(C c) {} - void mRC(C c) {} + void mRC(C c) {} } diff --git a/checker/tests/nullness/RawTypesAssignment.java b/checker/tests/nullness/RawTypesAssignment.java index 965733f1311..481fbcf1069 100644 --- a/checker/tests/nullness/RawTypesAssignment.java +++ b/checker/tests/nullness/RawTypesAssignment.java @@ -1,30 +1,31 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; public class RawTypesAssignment { - Map rawMap = new HashMap(); - Map> notRawMapDiamondRec = new HashMap<>(); - // :: warning: [unchecked] unchecked conversion - Map> notRawMapRawHashMapRec = new HashMap(); + Map rawMap = new HashMap(); + Map> notRawMapDiamondRec = new HashMap<>(); + // :: warning: [unchecked] unchecked conversion + Map> notRawMapRawHashMapRec = new HashMap(); - Map notRawMapDiamond = new HashMap<>(); - // :: warning: [unchecked] unchecked conversion - Map notRawMapRawHashMap = new HashMap(); + Map notRawMapDiamond = new HashMap<>(); + // :: warning: [unchecked] unchecked conversion + Map notRawMapRawHashMap = new HashMap(); - Map notRawMapDiamondObjectObject = new HashMap<>(); - // :: warning: [unchecked] unchecked conversion - Map notRawMapDiamondObjectObjectRaw = new HashMap(); + Map notRawMapDiamondObjectObject = new HashMap<>(); + // :: warning: [unchecked] unchecked conversion + Map notRawMapDiamondObjectObjectRaw = new HashMap(); - RecursiveGeneric rawRecursiveGeneric = new RecursiveGeneric(); - RecursiveGeneric notRawRecursiveGenericDiamond = new RecursiveGeneric<>(); - // :: warning: [unchecked] unchecked conversion - RecursiveGeneric notRawRecursiveGenericRaw = new RecursiveGeneric(); + RecursiveGeneric rawRecursiveGeneric = new RecursiveGeneric(); + RecursiveGeneric notRawRecursiveGenericDiamond = new RecursiveGeneric<>(); + // :: warning: [unchecked] unchecked conversion + RecursiveGeneric notRawRecursiveGenericRaw = new RecursiveGeneric(); - class Generic {} + class Generic {} - class RecursiveGeneric> {} + class RecursiveGeneric> {} - class MyClass extends Generic {} + class MyClass extends Generic {} } diff --git a/checker/tests/nullness/RawTypesNullness.java b/checker/tests/nullness/RawTypesNullness.java index 8040fea02bf..f33d39b90f5 100644 --- a/checker/tests/nullness/RawTypesNullness.java +++ b/checker/tests/nullness/RawTypesNullness.java @@ -7,69 +7,69 @@ class MyClass extends Generic {} class BoundedGeneric {} class RawTypesNullness { - Generic rawReturn() { - return new Generic(); - } + Generic rawReturn() { + return new Generic(); + } - Generic rawField = new Generic(); + Generic rawField = new Generic(); - void use() { - Generic rawLocal = new Generic<>(); - Generic generic1 = rawReturn(); - Generic generic2 = rawField; - Generic generic3 = rawLocal; - } + void use() { + Generic rawLocal = new Generic<>(); + Generic generic1 = rawReturn(); + Generic generic2 = rawField; + Generic generic3 = rawLocal; + } } class TestBounded { - BoundedGeneric rawReturn() { - return new BoundedGeneric<>(); - } + BoundedGeneric rawReturn() { + return new BoundedGeneric<>(); + } - BoundedGeneric rawField = new BoundedGeneric(); + BoundedGeneric rawField = new BoundedGeneric(); - void useWildCard() { - BoundedGeneric rawLocal = new BoundedGeneric(); - BoundedGeneric generic1 = rawReturn(); - BoundedGeneric generic2 = rawField; - BoundedGeneric generic3 = rawLocal; - } + void useWildCard() { + BoundedGeneric rawLocal = new BoundedGeneric(); + BoundedGeneric generic1 = rawReturn(); + BoundedGeneric generic2 = rawField; + BoundedGeneric generic3 = rawLocal; + } - @SuppressWarnings("unchecked") // only needed on JDK 17 and lower - void useBoundedWildCard() { - BoundedGeneric rawLocal = new BoundedGeneric(); - BoundedGeneric generic1 = rawReturn(); - BoundedGeneric generic2 = rawField; - BoundedGeneric generic3 = rawLocal; - } + @SuppressWarnings("unchecked") // only needed on JDK 17 and lower + void useBoundedWildCard() { + BoundedGeneric rawLocal = new BoundedGeneric(); + BoundedGeneric generic1 = rawReturn(); + BoundedGeneric generic2 = rawField; + BoundedGeneric generic3 = rawLocal; + } - void useBoundedWildCard2() { - BoundedGeneric rawLocal = new BoundedGeneric(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic1 = rawReturn(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic2 = rawField; - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic3 = rawLocal; - } + void useBoundedWildCard2() { + BoundedGeneric rawLocal = new BoundedGeneric(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic1 = rawReturn(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic2 = rawField; + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic3 = rawLocal; + } - void useTypeArg() { - BoundedGeneric rawLocal = new BoundedGeneric(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic1 = rawReturn(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic2 = rawField; - // :: warning: [unchecked] unchecked conversion - BoundedGeneric generic3 = rawLocal; - } + void useTypeArg() { + BoundedGeneric rawLocal = new BoundedGeneric(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic1 = rawReturn(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic2 = rawField; + // :: warning: [unchecked] unchecked conversion + BoundedGeneric generic3 = rawLocal; + } - void useAnnotatedTypeArg() { - BoundedGeneric rawLocal = new BoundedGeneric(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric<@Nullable String> generic1 = rawReturn(); - // :: warning: [unchecked] unchecked conversion - BoundedGeneric<@Nullable String> generic2 = rawField; - // :: warning: [unchecked] unchecked conversion - BoundedGeneric<@Nullable String> generic3 = rawLocal; - } + void useAnnotatedTypeArg() { + BoundedGeneric rawLocal = new BoundedGeneric(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric<@Nullable String> generic1 = rawReturn(); + // :: warning: [unchecked] unchecked conversion + BoundedGeneric<@Nullable String> generic2 = rawField; + // :: warning: [unchecked] unchecked conversion + BoundedGeneric<@Nullable String> generic3 = rawLocal; + } } diff --git a/checker/tests/nullness/RawTypesUses.java b/checker/tests/nullness/RawTypesUses.java index f1ac24a9fe1..45a974dbcc8 100644 --- a/checker/tests/nullness/RawTypesUses.java +++ b/checker/tests/nullness/RawTypesUses.java @@ -2,50 +2,50 @@ import org.checkerframework.checker.nullness.qual.Nullable; public abstract class RawTypesUses { - class Generic { - G foo() { - throw new RuntimeException(); + class Generic { + G foo() { + throw new RuntimeException(); + } + } + + void foo() { + Generic<@Nullable String> notRawNullable = new Generic<>(); + // :: error: (assignment.type.incompatible) + @NonNull Object o1 = notRawNullable.foo(); + + Generic rawNullable = new Generic<@Nullable String>(); + // :: error: (assignment.type.incompatible) + @NonNull Object o2 = rawNullable.foo(); + + Generic<@NonNull String> notRawNonNull = new Generic<>(); + @NonNull Object o3 = notRawNonNull.foo(); + + Generic rawNonNull = new Generic<@NonNull String>(); + Generic rawNonNullAlais = rawNonNull; + // :: error: (assignment.type.incompatible) + @NonNull Object o4 = rawNonNull.foo(); + // :: error: (assignment.type.incompatible) + @NonNull Object o5 = rawNonNullAlais.foo(); + } + + abstract Generic rawReturn(); + + void bar() { + // :: warning: [unchecked] unchecked conversion + Generic<@Nullable String> notRawNullable = rawReturn(); + // :: error: (assignment.type.incompatible) + @NonNull Object o1 = notRawNullable.foo(); + + Generic rawNullable = rawReturn(); + // :: error: (assignment.type.incompatible) + @NonNull Object o2 = rawNullable.foo(); + + // :: error: (assignment.type.incompatible) + @NonNull Object o3 = rawReturn().foo(); + + Generic local = rawReturn(); + Generic localAlias = local; + // :: error: (assignment.type.incompatible) + @NonNull Object o4 = local.foo(); } - } - - void foo() { - Generic<@Nullable String> notRawNullable = new Generic<>(); - // :: error: (assignment.type.incompatible) - @NonNull Object o1 = notRawNullable.foo(); - - Generic rawNullable = new Generic<@Nullable String>(); - // :: error: (assignment.type.incompatible) - @NonNull Object o2 = rawNullable.foo(); - - Generic<@NonNull String> notRawNonNull = new Generic<>(); - @NonNull Object o3 = notRawNonNull.foo(); - - Generic rawNonNull = new Generic<@NonNull String>(); - Generic rawNonNullAlais = rawNonNull; - // :: error: (assignment.type.incompatible) - @NonNull Object o4 = rawNonNull.foo(); - // :: error: (assignment.type.incompatible) - @NonNull Object o5 = rawNonNullAlais.foo(); - } - - abstract Generic rawReturn(); - - void bar() { - // :: warning: [unchecked] unchecked conversion - Generic<@Nullable String> notRawNullable = rawReturn(); - // :: error: (assignment.type.incompatible) - @NonNull Object o1 = notRawNullable.foo(); - - Generic rawNullable = rawReturn(); - // :: error: (assignment.type.incompatible) - @NonNull Object o2 = rawNullable.foo(); - - // :: error: (assignment.type.incompatible) - @NonNull Object o3 = rawReturn().foo(); - - Generic local = rawReturn(); - Generic localAlias = local; - // :: error: (assignment.type.incompatible) - @NonNull Object o4 = local.foo(); - } } diff --git a/checker/tests/nullness/ReadyReadLine.java b/checker/tests/nullness/ReadyReadLine.java index 8acc3540835..77a7c5eac64 100644 --- a/checker/tests/nullness/ReadyReadLine.java +++ b/checker/tests/nullness/ReadyReadLine.java @@ -4,30 +4,30 @@ public class ReadyReadLine { - void m(MyBufferedReader buf) throws Exception { - if (buf.ready()) { - String line = buf.readLine(); - line.toString(); - } + void m(MyBufferedReader buf) throws Exception { + if (buf.ready()) { + String line = buf.readLine(); + line.toString(); + } - if (buf.readLine() != null) { - // :: error: (dereference.of.nullable) - buf.readLine().toString(); + if (buf.readLine() != null) { + // :: error: (dereference.of.nullable) + buf.readLine().toString(); + } } - } } // This is a replication of the JDK BufferedReader, with only the relevant methods. class MyBufferedReader { - public @Nullable String readLine() throws Exception { - return null; - } + public @Nullable String readLine() throws Exception { + return null; + } - @EnsuresNonNullIf(expression = "readLine()", result = true) - @Pure - public boolean ready() throws Exception { - // don't bother with implementation. - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf(expression = "readLine()", result = true) + @Pure + public boolean ready() throws Exception { + // don't bother with implementation. + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/ReceiverAnnotation.java b/checker/tests/nullness/ReceiverAnnotation.java index 1a4cef23920..9f2fc729fdf 100644 --- a/checker/tests/nullness/ReceiverAnnotation.java +++ b/checker/tests/nullness/ReceiverAnnotation.java @@ -3,11 +3,11 @@ public class ReceiverAnnotation { - void receiver1(ReceiverAnnotation this) {} + void receiver1(ReceiverAnnotation this) {} - // :: error: (nullness.on.receiver) - void receiver2(@NonNull ReceiverAnnotation this) {} + // :: error: (nullness.on.receiver) + void receiver2(@NonNull ReceiverAnnotation this) {} - // :: error: (nullness.on.receiver) - void receiver3(@Nullable ReceiverAnnotation this) {} + // :: error: (nullness.on.receiver) + void receiver3(@Nullable ReceiverAnnotation this) {} } diff --git a/checker/tests/nullness/ReferencesDefaults.java b/checker/tests/nullness/ReferencesDefaults.java index 0f5bd9fe13f..f68110b49eb 100644 --- a/checker/tests/nullness/ReferencesDefaults.java +++ b/checker/tests/nullness/ReferencesDefaults.java @@ -1,17 +1,17 @@ public class ReferencesDefaults { - @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.Nullable.class) - class Decl { - Object test() { - // legal, because of changed default. - return null; + @org.checkerframework.framework.qual.DefaultQualifier( + org.checkerframework.checker.nullness.qual.Nullable.class) + class Decl { + Object test() { + // legal, because of changed default. + return null; + } } - } - class Use { - Decl d = new Decl(); - // here the default for f is NonNull -> error - // :: error: (assignment.type.incompatible) - Object f = d.test(); - } + class Use { + Decl d = new Decl(); + // here the default for f is NonNull -> error + // :: error: (assignment.type.incompatible) + Object f = d.test(); + } } diff --git a/checker/tests/nullness/RefineArray.java b/checker/tests/nullness/RefineArray.java index 7c40ef78c17..a9e34be7b1f 100644 --- a/checker/tests/nullness/RefineArray.java +++ b/checker/tests/nullness/RefineArray.java @@ -2,26 +2,26 @@ // TODO: Add as test public class RefineArray { - public static T[] concat(T @Nullable [] a, T @Nullable [] b) { - if (a == null) { - if (b != null) { - return b; - } else { - @SuppressWarnings("unchecked") - T[] result = (T[]) new Object[0]; - return result; - } - } else { - if (b == null) { - return a; - } else { - @SuppressWarnings("unchecked") - T[] result = (T[]) new @MonotonicNonNull Object[a.length + b.length]; + public static T[] concat(T @Nullable [] a, T @Nullable [] b) { + if (a == null) { + if (b != null) { + return b; + } else { + @SuppressWarnings("unchecked") + T[] result = (T[]) new Object[0]; + return result; + } + } else { + if (b == null) { + return a; + } else { + @SuppressWarnings("unchecked") + T[] result = (T[]) new @MonotonicNonNull Object[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; - } + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + } } - } } diff --git a/checker/tests/nullness/RefineOverride.java b/checker/tests/nullness/RefineOverride.java index 9c2762f2dbe..afd025365bb 100644 --- a/checker/tests/nullness/RefineOverride.java +++ b/checker/tests/nullness/RefineOverride.java @@ -5,214 +5,214 @@ public class RefineOverride { - void m(Sub<@Nullable String> snb, Sub<@NonNull String> snn) { - snb.m7(null); - snn.m7(null); - } + void m(Sub<@Nullable String> snb, Sub<@NonNull String> snn) { + snb.m7(null); + snn.m7(null); + } - class Super { + class Super { - void m1(@NonNull String s) {} + void m1(@NonNull String s) {} - void m2(@NonNull String s) {} + void m2(@NonNull String s) {} - void m5(@NonNull String s) {} + void m5(@NonNull String s) {} - void m6(@Nullable String s) {} + void m6(@Nullable String s) {} - void m7(T s) {} + void m7(T s) {} - void m11(@NonNull String s1, @NonNull String s2) {} + void m11(@NonNull String s1, @NonNull String s2) {} - void m12(@NonNull String s1, @Nullable String s2) {} + void m12(@NonNull String s1, @Nullable String s2) {} - void m13(@Nullable String s1, @NonNull String s2) {} + void m13(@Nullable String s1, @NonNull String s2) {} - void m14(@Nullable String s1, @Nullable String s2) {} + void m14(@Nullable String s1, @Nullable String s2) {} - void m15(T s1, T s2) {} + void m15(T s1, T s2) {} - void m16(@NonNull T s1, @NonNull T s2) {} + void m16(@NonNull T s1, @NonNull T s2) {} - void m17(@NonNull T s1, @Nullable T s2) {} + void m17(@NonNull T s1, @Nullable T s2) {} - void m18(@Nullable T s1, @NonNull T s2) {} + void m18(@Nullable T s1, @NonNull T s2) {} - void m19(@Nullable T s1, @Nullable T s2) {} + void m19(@Nullable T s1, @Nullable T s2) {} - void m21(@Nullable String[] a) {} + void m21(@Nullable String[] a) {} - void m22(@NonNull String[] a) {} + void m22(@NonNull String[] a) {} - void m23(@Nullable String[] a) {} + void m23(@Nullable String[] a) {} - void m24(@NonNull String[] a) {} + void m24(@NonNull String[] a) {} - void m25(T[] a) {} + void m25(T[] a) {} - void m26(@Nullable T[] a) {} + void m26(@Nullable T[] a) {} - void m27(@NonNull T[] a) {} + void m27(@NonNull T[] a) {} - void m28(@Nullable T[] a) {} + void m28(@Nullable T[] a) {} - void m29(@NonNull T[] a) {} - } + void m29(@NonNull T[] a) {} + } - class Sub extends Super { + class Sub extends Super { - @Override - void m1(@Nullable String s) {} + @Override + void m1(@Nullable String s) {} - @Override - void m2(@PolyNull String s) {} + @Override + void m2(@PolyNull String s) {} - // In the following declarations, all previously-valid invocations remain - // valid, so the compiler should permit the overriding. + // In the following declarations, all previously-valid invocations remain + // valid, so the compiler should permit the overriding. - // Case 1. A single parameter type is changed from anything to @PolyNull - // in an overriding method. + // Case 1. A single parameter type is changed from anything to @PolyNull + // in an overriding method. - @Override - void m5(@PolyNull String s) {} + @Override + void m5(@PolyNull String s) {} - // TODO: should be legal - @Override - void m6(@PolyNull String s) {} + // TODO: should be legal + @Override + void m6(@PolyNull String s) {} - // TODO: should be legal - @Override - void m7(@PolyNull T s) {} + // TODO: should be legal + @Override + void m7(@PolyNull T s) {} - // Case 2. Multiple parameter types are changed to @PolyNull in an - // overriding method. + // Case 2. Multiple parameter types are changed to @PolyNull in an + // overriding method. - // (The types for m14 might be better written as "@PolyNull(1) - // ... @PolyNull(2)", but all invocations remain valid. + // (The types for m14 might be better written as "@PolyNull(1) + // ... @PolyNull(2)", but all invocations remain valid. - @Override - void m11(@PolyNull String s1, @PolyNull String s2) {} + @Override + void m11(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m12(@PolyNull String s1, @PolyNull String s2) {} + // TODO: should be legal + @Override + void m12(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m13(@PolyNull String s1, @PolyNull String s2) {} + // TODO: should be legal + @Override + void m13(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m14(@PolyNull String s1, @PolyNull String s2) {} + // TODO: should be legal + @Override + void m14(@PolyNull String s1, @PolyNull String s2) {} - // TODO: should be legal - @Override - void m15(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m15(@PolyNull T s1, @PolyNull T s2) {} - @Override - void m16(@PolyNull T s1, @PolyNull T s2) {} + @Override + void m16(@PolyNull T s1, @PolyNull T s2) {} - // TODO: should be legal - @Override - void m17(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m17(@PolyNull T s1, @PolyNull T s2) {} - // TODO: should be legal - @Override - void m18(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m18(@PolyNull T s1, @PolyNull T s2) {} - // TODO: should be legal - @Override - void m19(@PolyNull T s1, @PolyNull T s2) {} + // TODO: should be legal + @Override + void m19(@PolyNull T s1, @PolyNull T s2) {} - // Case 3. Expand the element type of an array. - // The new permissible types are not supertypes of the old types, - // but they still expand the set of permitted invocations. + // Case 3. Expand the element type of an array. + // The new permissible types are not supertypes of the old types, + // but they still expand the set of permitted invocations. - // :: error: (override.param.invalid) - @Override - void m21(@NonNull String[] a) {} + // :: error: (override.param.invalid) + @Override + void m21(@NonNull String[] a) {} - // :: error: Changing incompatibly to forbid old invocations is not permitted. - @Override - void m22(@Nullable String[] a) {} + // :: error: Changing incompatibly to forbid old invocations is not permitted. + @Override + void m22(@Nullable String[] a) {} - // TODO: should be legal - @Override - void m23(@PolyNull String[] a) {} + // TODO: should be legal + @Override + void m23(@PolyNull String[] a) {} - @Override - void m24(@PolyNull String[] a) {} + @Override + void m24(@PolyNull String[] a) {} - @Override - void m25(@PolyNull T[] a) {} + @Override + void m25(@PolyNull T[] a) {} - // :: error: (override.param.invalid) - @Override - void m26(@NonNull T[] a) {} + // :: error: (override.param.invalid) + @Override + void m26(@NonNull T[] a) {} - // :: error: Changing incompatibly to forbid old invocations is not permitted. - @Override - void m27(@Nullable T[] a) {} + // :: error: Changing incompatibly to forbid old invocations is not permitted. + @Override + void m27(@Nullable T[] a) {} - // TODO: should be legal - @Override - void m28(@PolyNull T[] a) {} + // TODO: should be legal + @Override + void m28(@PolyNull T[] a) {} - @Override - void m29(@PolyNull T[] a) {} - } + @Override + void m29(@PolyNull T[] a) {} + } - class Super2 { + class Super2 { - void t1(String s) {} + void t1(String s) {} - void t2(String s) {} + void t2(String s) {} - void t3(@Nullable String s) {} + void t3(@Nullable String s) {} - void t4(String s) {} + void t4(String s) {} - void t5(String[] s) {} + void t5(String[] s) {} - void t6(T s) {} + void t6(T s) {} - void t7(T s) {} + void t7(T s) {} - void t8(T[] s) {} + void t8(T[] s) {} - void t9(T[] s) {} - } + void t9(T[] s) {} + } - class Sub2 extends Super2 { + class Sub2 extends Super2 { - @Override - void t1(String s) {} + @Override + void t1(String s) {} - @Override - void t2(@Nullable String s) {} + @Override + void t2(@Nullable String s) {} - // :: error: (override.param.invalid) - @Override - void t3(String s) {} + // :: error: (override.param.invalid) + @Override + void t3(String s) {} - @Override - void t4(@PolyNull String s) {} + @Override + void t4(@PolyNull String s) {} - @Override - void t5(@PolyNull String[] s) {} + @Override + void t5(@PolyNull String[] s) {} - @Override - void t6(@Nullable T s) {} + @Override + void t6(@Nullable T s) {} - // TODO: should be legal - @Override - void t7(@PolyNull T s) {} + // TODO: should be legal + @Override + void t7(@PolyNull T s) {} - @Override - void t8(@Nullable T[] s) {} + @Override + void t8(@Nullable T[] s) {} - // TODO: should be legal - @Override - void t9(@PolyNull T[] s) {} - } + // TODO: should be legal + @Override + void t9(@PolyNull T[] s) {} + } } diff --git a/checker/tests/nullness/RepeatEnsuresKeyFor.java b/checker/tests/nullness/RepeatEnsuresKeyFor.java index f911bb85346..7bc85529c78 100644 --- a/checker/tests/nullness/RepeatEnsuresKeyFor.java +++ b/checker/tests/nullness/RepeatEnsuresKeyFor.java @@ -1,101 +1,102 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; +import java.util.HashMap; +import java.util.Map; + public class RepeatEnsuresKeyFor { - Map map = new HashMap<>(); + Map map = new HashMap<>(); - public void func1(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - } + public void func1(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + } - public boolean func2(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - return true; - } + public boolean func2(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + return true; + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client1(String a, String b, String c) { - withpostconditionsfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client1(String a, String b, String c) { + withpostconditionsfunc1(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client2(String a, String b, String c) { - withpostconditionfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client2(String a, String b, String c) { + withpostconditionfunc1(a, b, c); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client3(String a, String b, String c) { - return withcondpostconditionsfunc2(a, b, c); - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client3(String a, String b, String c) { + return withcondpostconditionsfunc2(a, b, c); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client4(String a, String b, String c) { - return withcondpostconditionfunc2(a, b, c); - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client4(String a, String b, String c) { + return withcondpostconditionfunc2(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void withpostconditionsfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void withpostconditionsfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionsfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - return true; - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionsfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + return true; + } - @EnsuresKeyFor.List({ - @EnsuresKeyFor(value = "#1", map = "map"), - @EnsuresKeyFor(value = "#2", map = "map"), - }) - @EnsuresKeyFor(value = "#3", map = "map") - public void withpostconditionfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - } + @EnsuresKeyFor.List({ + @EnsuresKeyFor(value = "#1", map = "map"), + @EnsuresKeyFor(value = "#2", map = "map"), + }) + @EnsuresKeyFor(value = "#3", map = "map") + public void withpostconditionfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(b, 2); - map.put(c, 3); - return true; - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(b, 2); + map.put(c, 3); + return true; + } } diff --git a/checker/tests/nullness/RepeatEnsuresKeyForWithError.java b/checker/tests/nullness/RepeatEnsuresKeyForWithError.java index 025af3478f1..8a2e77b8ed0 100644 --- a/checker/tests/nullness/RepeatEnsuresKeyForWithError.java +++ b/checker/tests/nullness/RepeatEnsuresKeyForWithError.java @@ -1,99 +1,100 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresKeyFor; import org.checkerframework.checker.nullness.qual.EnsuresKeyForIf; +import java.util.HashMap; +import java.util.Map; + public class RepeatEnsuresKeyForWithError { - Map map = new HashMap<>(); + Map map = new HashMap<>(); - public void func1(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - } + public void func1(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + } - public boolean func2(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - return true; - } + public boolean func2(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + return true; + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client1(String a, String b, String c) { - withpostconditionsfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client1(String a, String b, String c) { + withpostconditionsfunc1(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - public void client2(String a, String b, String c) { - withpostconditionfunc1(a, b, c); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + public void client2(String a, String b, String c) { + withpostconditionfunc1(a, b, c); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client3(String a, String b, String c) { - return withcondpostconditionsfunc2(a, b, c); - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client3(String a, String b, String c) { + return withcondpostconditionsfunc2(a, b, c); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean client4(String a, String b, String c) { - return withcondpostconditionfunc2(a, b, c); - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean client4(String a, String b, String c) { + return withcondpostconditionfunc2(a, b, c); + } - @EnsuresKeyFor( - value = {"#1", "#2"}, - map = "map") - @EnsuresKeyFor(value = "#3", map = "map") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionsfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - } + @EnsuresKeyFor( + value = {"#1", "#2"}, + map = "map") + @EnsuresKeyFor(value = "#3", map = "map") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionsfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + } - @EnsuresKeyForIf( - expression = {"#1", "#2"}, - map = "map", - result = true) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionsfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresKeyForIf( + expression = {"#1", "#2"}, + map = "map", + result = true) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionsfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresKeyFor.List({ - @EnsuresKeyFor(value = "#1", map = "map"), - @EnsuresKeyFor(value = "#2", map = "map"), - }) - @EnsuresKeyFor(value = "#3", map = "map") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionfunc1(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - } + @EnsuresKeyFor.List({ + @EnsuresKeyFor(value = "#1", map = "map"), + @EnsuresKeyFor(value = "#2", map = "map"), + }) + @EnsuresKeyFor(value = "#3", map = "map") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionfunc1(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + } - @EnsuresKeyForIf.List({ - @EnsuresKeyForIf(expression = "#1", map = "map", result = true), - @EnsuresKeyForIf(expression = "#2", map = "map", result = true) - }) - @EnsuresKeyForIf(expression = "#3", map = "map", result = true) - public boolean withcondpostconditionfunc2(String a, String b, String c) { - map.put(a, 1); - map.put(c, 3); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresKeyForIf.List({ + @EnsuresKeyForIf(expression = "#1", map = "map", result = true), + @EnsuresKeyForIf(expression = "#2", map = "map", result = true) + }) + @EnsuresKeyForIf(expression = "#3", map = "map", result = true) + public boolean withcondpostconditionfunc2(String a, String b, String c) { + map.put(a, 1); + map.put(c, 3); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/RepeatEnsuresNonNull.java b/checker/tests/nullness/RepeatEnsuresNonNull.java index 7c82a52ede0..67e9f96a6d1 100644 --- a/checker/tests/nullness/RepeatEnsuresNonNull.java +++ b/checker/tests/nullness/RepeatEnsuresNonNull.java @@ -4,88 +4,88 @@ public class RepeatEnsuresNonNull { - protected @Nullable String value1; - protected @Nullable String value2; - protected @Nullable String value3; + protected @Nullable String value1; + protected @Nullable String value2; + protected @Nullable String value3; - public boolean func1() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - return true; - } + public boolean func1() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + return true; + } - public void func2() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - } + public void func2() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - public void client2() { - withpostconditionsfunc2(); - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + public void client2() { + withpostconditionsfunc2(); + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client3() { - return withcondpostconditionfunc1(); - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client3() { + return withcondpostconditionfunc1(); + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - public void client4() { - withpostconditionfunc2(); - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + public void client4() { + withpostconditionfunc2(); + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionsfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - return true; - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionsfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + return true; + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - public void withpostconditionsfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + public void withpostconditionsfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - return true; - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + return true; + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - public void withpostconditionfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = "value3"; - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + public void withpostconditionfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = "value3"; + } } diff --git a/checker/tests/nullness/RepeatEnsuresNonNullWithError.java b/checker/tests/nullness/RepeatEnsuresNonNullWithError.java index a2b0782dde7..decf53a6cc9 100644 --- a/checker/tests/nullness/RepeatEnsuresNonNullWithError.java +++ b/checker/tests/nullness/RepeatEnsuresNonNullWithError.java @@ -4,92 +4,92 @@ public class RepeatEnsuresNonNullWithError { - protected @Nullable String value1; - protected @Nullable String value2; - protected @Nullable String value3; + protected @Nullable String value1; + protected @Nullable String value2; + protected @Nullable String value3; - public boolean func1() { - value1 = "value1"; - value2 = "value2"; - value3 = null; - return true; - } + public boolean func1() { + value1 = "value1"; + value2 = "value2"; + value3 = null; + return true; + } - public void func2() { - value1 = "value1"; - value2 = "value2"; - value3 = null; - } + public void func2() { + value1 = "value1"; + value2 = "value2"; + value3 = null; + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - public void client2() { - withpostconditionsfunc2(); - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + public void client2() { + withpostconditionsfunc2(); + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean client3() { - return withcondpostconditionfunc1(); - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean client3() { + return withcondpostconditionfunc1(); + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - public void client4() { - withpostconditionfunc2(); - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + public void client4() { + withpostconditionfunc2(); + } - @EnsuresNonNullIf( - expression = {"value1", "value2"}, - result = true) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionsfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf( + expression = {"value1", "value2"}, + result = true) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionsfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresNonNull("value1") - @EnsuresNonNull(value = {"value2", "value3"}) - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionsfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - } + @EnsuresNonNull("value1") + @EnsuresNonNull(value = {"value2", "value3"}) + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionsfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + } - @EnsuresNonNullIf.List({ - @EnsuresNonNullIf(expression = "value1", result = true), - @EnsuresNonNullIf(expression = "value2", result = true), - }) - @EnsuresNonNullIf(expression = "value3", result = true) - public boolean withcondpostconditionfunc1() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresNonNullIf.List({ + @EnsuresNonNullIf(expression = "value1", result = true), + @EnsuresNonNullIf(expression = "value2", result = true), + }) + @EnsuresNonNullIf(expression = "value3", result = true) + public boolean withcondpostconditionfunc1() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) - @EnsuresNonNull("value3") - // :: error: (contracts.postcondition.not.satisfied) - public void withpostconditionfunc2() { - value1 = "value1"; - value2 = "value2"; - value3 = null; // condition not satisfied here - } + @EnsuresNonNull.List({@EnsuresNonNull("value1"), @EnsuresNonNull("value2")}) + @EnsuresNonNull("value3") + // :: error: (contracts.postcondition.not.satisfied) + public void withpostconditionfunc2() { + value1 = "value1"; + value2 = "value2"; + value3 = null; // condition not satisfied here + } } diff --git a/checker/tests/nullness/RepeatedRequiresNonNull.java b/checker/tests/nullness/RepeatedRequiresNonNull.java index dfc460e804a..5809f8afb09 100644 --- a/checker/tests/nullness/RepeatedRequiresNonNull.java +++ b/checker/tests/nullness/RepeatedRequiresNonNull.java @@ -5,74 +5,74 @@ import org.checkerframework.framework.qual.RequiresQualifier; class RepeatedRequiresNonNull { - @Nullable Object f1; - @Nullable Object f2; + @Nullable Object f1; + @Nullable Object f2; - @RequiresNonNull("this.f1") - @RequiresNonNull("this.f2") - void test() { - f1.toString(); - f2.toString(); - } + @RequiresNonNull("this.f1") + @RequiresNonNull("this.f2") + void test() { + f1.toString(); + f2.toString(); + } - void use1() { - // :: error: (contracts.precondition.not.satisfied) - test(); - } + void use1() { + // :: error: (contracts.precondition.not.satisfied) + test(); + } - void use2() { - if (this.f1 != null) { - // :: error: (contracts.precondition.not.satisfied) - test(); + void use2() { + if (this.f1 != null) { + // :: error: (contracts.precondition.not.satisfied) + test(); + } } - } - void use3() { - if (this.f2 != null) { - // :: error: (contracts.precondition.not.satisfied) - test(); + void use3() { + if (this.f2 != null) { + // :: error: (contracts.precondition.not.satisfied) + test(); + } } - } - void use4() { - if (this.f1 != null && this.f2 != null) { - test(); + void use4() { + if (this.f1 != null && this.f2 != null) { + test(); + } } - } - // This part of the test is to ensure that @RequiresNonNull and @RequiresQualifier behave - // the same way. It is identical, but uses @RequiresQualifier on the test2() method instead - // of the @RequiresNonNull on the test() method. + // This part of the test is to ensure that @RequiresNonNull and @RequiresQualifier behave + // the same way. It is identical, but uses @RequiresQualifier on the test2() method instead + // of the @RequiresNonNull on the test() method. - @RequiresQualifier(expression = "this.f1", qualifier = NonNull.class) - @RequiresQualifier(expression = "this.f2", qualifier = NonNull.class) - void test2() { - f1.toString(); - f2.toString(); - } + @RequiresQualifier(expression = "this.f1", qualifier = NonNull.class) + @RequiresQualifier(expression = "this.f2", qualifier = NonNull.class) + void test2() { + f1.toString(); + f2.toString(); + } - void use21() { - // :: error: (contracts.precondition.not.satisfied) - test2(); - } + void use21() { + // :: error: (contracts.precondition.not.satisfied) + test2(); + } - void use22() { - if (this.f1 != null) { - // :: error: (contracts.precondition.not.satisfied) - test2(); + void use22() { + if (this.f1 != null) { + // :: error: (contracts.precondition.not.satisfied) + test2(); + } } - } - void use23() { - if (this.f2 != null) { - // :: error: (contracts.precondition.not.satisfied) - test2(); + void use23() { + if (this.f2 != null) { + // :: error: (contracts.precondition.not.satisfied) + test2(); + } } - } - void use24() { - if (this.f1 != null && this.f2 != null) { - test2(); + void use24() { + if (this.f1 != null && this.f2 != null) { + test2(); + } } - } } diff --git a/checker/tests/nullness/RequiresNonNullTest.java b/checker/tests/nullness/RequiresNonNullTest.java index da2a4b759ae..2966042e1a5 100644 --- a/checker/tests/nullness/RequiresNonNullTest.java +++ b/checker/tests/nullness/RequiresNonNullTest.java @@ -3,151 +3,151 @@ public class RequiresNonNullTest { - @Nullable Object field1; - @Nullable Object field2; - - @RequiresNonNull("field1") - void method1() { - field1.toString(); // OK, field1 is known to be non-null - this.field1.toString(); // OK, field1 is known to be non-null - // :: error: (dereference.of.nullable) - field2.toString(); // error, might throw NullPointerException - } - - @RequiresNonNull("field1") - void method1also() { - // ok, precondition satisfied by NNOE - method1(); - } - - void method2() { - field1 = new Object(); - method1(); // OK, satisfies method precondition - field1 = null; - // :: error: (contracts.precondition.not.satisfied) - method1(); // error, does not satisfy method precondition - } - - protected @Nullable Object field; - - @RequiresNonNull("field") - public void requiresNonNullField() {} - - public void clientFail(RequiresNonNullTest arg1) { - // :: error: (contracts.precondition.not.satisfied) - arg1.requiresNonNullField(); - } - - public void clientOK(RequiresNonNullTest arg2) { - arg2.field = new Object(); - // note that the following line works - @NonNull Object o = arg2.field; - - arg2.requiresNonNullField(); // OK, field is known to be non-null - } - - // TODO: forbid the field in @NNOE to be less visible than the method - - protected static @Nullable Object staticfield; - - @Pure - @RequiresNonNull("staticfield") - // :: warning: (purity.deterministic.void.method) - public void reqStaticName() { - reqStaticQualName(); - } - - @Pure - @RequiresNonNull("RequiresNonNullTest.staticfield") - // :: warning: (purity.deterministic.void.method) - public void reqStaticQualName() { - reqStaticName(); - } - - public void statClientOK(RequiresNonNullTest arg1) { - staticfield = new Object(); - arg1.reqStaticName(); - - staticfield = new Object(); - arg1.reqStaticQualName(); - - RequiresNonNullTest.staticfield = new Object(); - arg1.reqStaticName(); - RequiresNonNullTest.staticfield = new Object(); - arg1.reqStaticQualName(); - } - - public void statClientFail(RequiresNonNullTest arg1) { - // :: error: (contracts.precondition.not.satisfied) - arg1.reqStaticName(); - // :: error: (contracts.precondition.not.satisfied) - arg1.reqStaticQualName(); - } - - class NNOESubTest extends RequiresNonNullTest { - public void subClientOK(NNOESubTest arg3) { - arg3.field = new Object(); - arg3.requiresNonNullField(); + @Nullable Object field1; + @Nullable Object field2; + + @RequiresNonNull("field1") + void method1() { + field1.toString(); // OK, field1 is known to be non-null + this.field1.toString(); // OK, field1 is known to be non-null + // :: error: (dereference.of.nullable) + field2.toString(); // error, might throw NullPointerException } - public void subClientFail(NNOESubTest arg4) { - // :: error: (contracts.precondition.not.satisfied) - arg4.requiresNonNullField(); + @RequiresNonNull("field1") + void method1also() { + // ok, precondition satisfied by NNOE + method1(); } - public void subStat(NNOESubTest arg5) { - RequiresNonNullTest.staticfield = new Object(); - arg5.reqStaticQualName(); + void method2() { + field1 = new Object(); + method1(); // OK, satisfies method precondition + field1 = null; + // :: error: (contracts.precondition.not.satisfied) + method1(); // error, does not satisfy method precondition + } + + protected @Nullable Object field; - staticfield = new Object(); - arg5.reqStaticQualName(); + @RequiresNonNull("field") + public void requiresNonNullField() {} - NNOESubTest.staticfield = new Object(); - arg5.reqStaticQualName(); + public void clientFail(RequiresNonNullTest arg1) { + // :: error: (contracts.precondition.not.satisfied) + arg1.requiresNonNullField(); } - } - private @Nullable Object notHidden; + public void clientOK(RequiresNonNullTest arg2) { + arg2.field = new Object(); + // note that the following line works + @NonNull Object o = arg2.field; - class NNOEHidingTest extends RequiresNonNullTest { + arg2.requiresNonNullField(); // OK, field is known to be non-null + } - protected @Nullable String field; + // TODO: forbid the field in @NNOE to be less visible than the method - public void hidingClient1(NNOEHidingTest arg5) { - arg5.field = "ha!"; + protected static @Nullable Object staticfield; - // TODO: The error message should say something about the hidden field. - // :: error: (contracts.precondition.not.satisfied) - arg5.requiresNonNullField(); + @Pure + @RequiresNonNull("staticfield") + // :: warning: (purity.deterministic.void.method) + public void reqStaticName() { + reqStaticQualName(); + } - // TODO: Add test like: - // arg5.ensuresNonNullField(); - // arg5.requiresNonNullField(); + @Pure + @RequiresNonNull("RequiresNonNullTest.staticfield") + // :: warning: (purity.deterministic.void.method) + public void reqStaticQualName() { + reqStaticName(); } - public void hidingClient2(NNOEHidingTest arg6) { - // :: error: (contracts.precondition.not.satisfied) - arg6.requiresNonNullField(); + public void statClientOK(RequiresNonNullTest arg1) { + staticfield = new Object(); + arg1.reqStaticName(); + + staticfield = new Object(); + arg1.reqStaticQualName(); + + RequiresNonNullTest.staticfield = new Object(); + arg1.reqStaticName(); + RequiresNonNullTest.staticfield = new Object(); + arg1.reqStaticQualName(); + } + + public void statClientFail(RequiresNonNullTest arg1) { + // :: error: (contracts.precondition.not.satisfied) + arg1.reqStaticName(); + // :: error: (contracts.precondition.not.satisfied) + arg1.reqStaticQualName(); + } + + class NNOESubTest extends RequiresNonNullTest { + public void subClientOK(NNOESubTest arg3) { + arg3.field = new Object(); + arg3.requiresNonNullField(); + } + + public void subClientFail(NNOESubTest arg4) { + // :: error: (contracts.precondition.not.satisfied) + arg4.requiresNonNullField(); + } + + public void subStat(NNOESubTest arg5) { + RequiresNonNullTest.staticfield = new Object(); + arg5.reqStaticQualName(); + + staticfield = new Object(); + arg5.reqStaticQualName(); + + NNOESubTest.staticfield = new Object(); + arg5.reqStaticQualName(); + } } - protected @Nullable Object notHidden; + private @Nullable Object notHidden; + + class NNOEHidingTest extends RequiresNonNullTest { + + protected @Nullable String field; + + public void hidingClient1(NNOEHidingTest arg5) { + arg5.field = "ha!"; - @RequiresNonNull("notHidden") - void notHiddenTest() { - // the field in the superclass is private -> don't complain about hiding + // TODO: The error message should say something about the hidden field. + // :: error: (contracts.precondition.not.satisfied) + arg5.requiresNonNullField(); + + // TODO: Add test like: + // arg5.ensuresNonNullField(); + // arg5.requiresNonNullField(); + } + + public void hidingClient2(NNOEHidingTest arg6) { + // :: error: (contracts.precondition.not.satisfied) + arg6.requiresNonNullField(); + } + + protected @Nullable Object notHidden; + + @RequiresNonNull("notHidden") + void notHiddenTest() { + // the field in the superclass is private -> don't complain about hiding + } } - } - static @Nullable Object o = "m"; + static @Nullable Object o = "m"; - @RequiresNonNull("o") - void test() { - o = null; - } + @RequiresNonNull("o") + void test() { + o = null; + } - @RequiresNonNull("thisShouldIssue1Error") - // Test case for Issue 1051 - // https://github.com/typetools/checker-framework/issues/1051 - // :: error: (flowexpr.parse.error) - void testIssue1051() {} + @RequiresNonNull("thisShouldIssue1Error") + // Test case for Issue 1051 + // https://github.com/typetools/checker-framework/issues/1051 + // :: error: (flowexpr.parse.error) + void testIssue1051() {} } diff --git a/checker/tests/nullness/RequiresPrivateField.java b/checker/tests/nullness/RequiresPrivateField.java index 127f81852cf..4be9012fdb7 100644 --- a/checker/tests/nullness/RequiresPrivateField.java +++ b/checker/tests/nullness/RequiresPrivateField.java @@ -5,13 +5,13 @@ public class RequiresPrivateField { - @RequiresNonNull("PptCombined.assemblies") - public void testFindIntermediateBlocks1() { - // no body - } + @RequiresNonNull("PptCombined.assemblies") + public void testFindIntermediateBlocks1() { + // no body + } } class PptCombined { - @SpecPublic private static @MonotonicNonNull String assemblies = null; + @SpecPublic private static @MonotonicNonNull String assemblies = null; } diff --git a/checker/tests/nullness/SAMLineParser.java b/checker/tests/nullness/SAMLineParser.java index f02dc8bf615..221978acf8c 100644 --- a/checker/tests/nullness/SAMLineParser.java +++ b/checker/tests/nullness/SAMLineParser.java @@ -1,8 +1,8 @@ public class SAMLineParser { - private int x; + private int x; - private String makeErrorString() { - return "" + (this.x <= 0 ? "" : this.x); - } + private String makeErrorString() { + return "" + (this.x <= 0 ? "" : this.x); + } } diff --git a/checker/tests/nullness/SamFileValidator.java b/checker/tests/nullness/SamFileValidator.java index 8a10b245a2f..f0f470f2167 100644 --- a/checker/tests/nullness/SamFileValidator.java +++ b/checker/tests/nullness/SamFileValidator.java @@ -5,9 +5,9 @@ public class SamFileValidator { - private class Codec { - public Map.Entry decode() { - return new AbstractMap.SimpleEntry("hello", "goodbye"); + private class Codec { + public Map.Entry decode() { + return new AbstractMap.SimpleEntry("hello", "goodbye"); + } } - } } diff --git a/checker/tests/nullness/ScopingConstruct.java b/checker/tests/nullness/ScopingConstruct.java index e8cb7e47ce5..22099cee3d6 100644 --- a/checker/tests/nullness/ScopingConstruct.java +++ b/checker/tests/nullness/ScopingConstruct.java @@ -3,508 +3,508 @@ @SuppressWarnings("initialization.field.uninitialized") public class ScopingConstruct { - // TODO: add nested classes within these two? - static class StaticNested implements AutoCloseable { - public void close() {} + // TODO: add nested classes within these two? + static class StaticNested implements AutoCloseable { + public void close() {} - static class NestedNested implements AutoCloseable { - public void close() {} - } + static class NestedNested implements AutoCloseable { + public void close() {} + } - class NestedInner implements AutoCloseable { - public void close() {} + class NestedInner implements AutoCloseable { + public void close() {} + } } - } - class Inner implements AutoCloseable { - public void close() {} + class Inner implements AutoCloseable { + public void close() {} - // This is a Java error. - // static class InnerNested {} + // This is a Java error. + // static class InnerNested {} - class InnerInner implements AutoCloseable { - public void close() {} + class InnerInner implements AutoCloseable { + public void close() {} + } } - } - StaticNested sn; + StaticNested sn; - @Nullable StaticNested nsn; + @Nullable StaticNested nsn; - Inner i; + Inner i; - @Nullable Inner ni; + @Nullable Inner ni; - ScopingConstruct.StaticNested scsn; + ScopingConstruct.StaticNested scsn; - // This is a Java error. - // @Nullable ScopingConstruct.StaticNested nscsn; + // This is a Java error. + // @Nullable ScopingConstruct.StaticNested nscsn; - ScopingConstruct.@Nullable StaticNested scnsn; + ScopingConstruct.@Nullable StaticNested scnsn; - // This is a Java error. - // ScopingConstruct.@Nullable StaticNested.NestedNested scnsnnn; + // This is a Java error. + // ScopingConstruct.@Nullable StaticNested.NestedNested scnsnnn; - // This is a Java error. - // ScopingConstruct.@Nullable StaticNested.@Nullable NestedNested scnsnnnn; + // This is a Java error. + // ScopingConstruct.@Nullable StaticNested.@Nullable NestedNested scnsnnnn; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; - ScopingConstruct.Inner sci; + ScopingConstruct.Inner sci; - ScopingConstruct.Inner.InnerInner sciii; + ScopingConstruct.Inner.InnerInner sciii; - ScopingConstruct.Inner.@Nullable InnerInner scinii; + ScopingConstruct.Inner.@Nullable InnerInner scinii; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner nsci; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner nsci; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner nsciii; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner nsciii; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; - ScopingConstruct.@Nullable Inner scni; + ScopingConstruct.@Nullable Inner scni; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner scniii; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner scniii; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; - ScopingConstruct.StaticNested.NestedInner scsnni; + ScopingConstruct.StaticNested.NestedInner scsnni; - ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; + ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; - // This is a Java error. - // @Nullable ScopingConstruct.StaticNested.NestedInner nscsnni; + // This is a Java error. + // @Nullable ScopingConstruct.StaticNested.NestedInner nscsnni; - // This is a Java error. - // @Nullable ScopingConstruct.StaticNested.@Nullable NestedInner nscsnnni; + // This is a Java error. + // @Nullable ScopingConstruct.StaticNested.@Nullable NestedInner nscsnnni; - // This is a Java error. - // @Nullable ScopingConstruct.@Nullable StaticNested.NestedInner nscnsnni; + // This is a Java error. + // @Nullable ScopingConstruct.@Nullable StaticNested.NestedInner nscnsnni; - // This is a Java error. - // @Nullable ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner nscnsnnni; + // This is a Java error. + // @Nullable ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner nscnsnnni; - ScopingConstruct.Inner @Nullable [] scina; + ScopingConstruct.Inner @Nullable [] scina; - ScopingConstruct.Inner.InnerInner @Nullable [] sciiina; + ScopingConstruct.Inner.InnerInner @Nullable [] sciiina; - ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniina; + ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniina; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner @Nullable [] nscina; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner @Nullable [] nscina; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiina; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiina; - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniina; + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniina; - ScopingConstruct.@Nullable Inner @Nullable [] scnina; + ScopingConstruct.@Nullable Inner @Nullable [] scnina; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniina; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniina; - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniina; + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniina; - ScopingConstruct.Inner sci() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner sci() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.InnerInner sciii() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.InnerInner sciii() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.@Nullable InnerInner scinii() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.@Nullable InnerInner scinii() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner nsci() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner nsci() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner nsciii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner nsciii() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii() { + throw new Error("not implemented"); + } - ScopingConstruct.@Nullable Inner scni() { - throw new Error("not implemented"); - } + ScopingConstruct.@Nullable Inner scni() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner scniii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner scniii() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner @Nullable [] scin() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner @Nullable [] scin() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.InnerInner @Nullable [] sciiin() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.InnerInner @Nullable [] sciiin() { + throw new Error("not implemented"); + } - ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniin() { - throw new Error("not implemented"); - } + ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] sciniin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner @Nullable [] nscin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner @Nullable [] nscin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner @Nullable [] nsciiin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner @Nullable [] nsciniin() { + throw new Error("not implemented"); + } - ScopingConstruct.@Nullable Inner @Nullable [] scnin() { - throw new Error("not implemented"); - } + ScopingConstruct.@Nullable Inner @Nullable [] scnin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniiin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner @Nullable [] scniiin() { + throw new Error("not implemented"); + } - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniin() { - throw new Error("not implemented"); - } + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner @Nullable [] scniniin() { + throw new Error("not implemented"); + } - /// - /// Formal parameters - /// + /// + /// Formal parameters + /// - void fsn(StaticNested sn) {} + void fsn(StaticNested sn) {} - void fnsn(@Nullable StaticNested nsn) {} + void fnsn(@Nullable StaticNested nsn) {} - void fi(Inner i) {} + void fi(Inner i) {} - void fni(@Nullable Inner ni) {} + void fni(@Nullable Inner ni) {} - void fscsn(ScopingConstruct.StaticNested scsn) {} + void fscsn(ScopingConstruct.StaticNested scsn) {} - void fscnsn(ScopingConstruct.@Nullable StaticNested scnsn) {} + void fscnsn(ScopingConstruct.@Nullable StaticNested scnsn) {} - // :: error: (nullness.on.outer) - void fscnsnni(ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni) {} + // :: error: (nullness.on.outer) + void fscnsnni(ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni) {} - // :: error: (nullness.on.outer) - void fscnsnnni(ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni) {} + // :: error: (nullness.on.outer) + void fscnsnnni(ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni) {} - void fsci(ScopingConstruct.Inner sci) {} + void fsci(ScopingConstruct.Inner sci) {} - void fsciii(ScopingConstruct.Inner.InnerInner sciii) {} + void fsciii(ScopingConstruct.Inner.InnerInner sciii) {} - void fscinii(ScopingConstruct.Inner.@Nullable InnerInner scinii) {} + void fscinii(ScopingConstruct.Inner.@Nullable InnerInner scinii) {} - // :: error: (nullness.on.outer) - void fnsci(@Nullable ScopingConstruct.Inner nsci) {} + // :: error: (nullness.on.outer) + void fnsci(@Nullable ScopingConstruct.Inner nsci) {} - // :: error: (nullness.on.outer) - void fnsciii(@Nullable ScopingConstruct.Inner.InnerInner nsciii) {} + // :: error: (nullness.on.outer) + void fnsciii(@Nullable ScopingConstruct.Inner.InnerInner nsciii) {} - // :: error: (nullness.on.outer) - void fnscinii(@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii) {} + // :: error: (nullness.on.outer) + void fnscinii(@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii) {} - void fscni(ScopingConstruct.@Nullable Inner scni) {} + void fscni(ScopingConstruct.@Nullable Inner scni) {} - // :: error: (nullness.on.outer) - void fscniii(ScopingConstruct.@Nullable Inner.InnerInner scniii) {} + // :: error: (nullness.on.outer) + void fscniii(ScopingConstruct.@Nullable Inner.InnerInner scniii) {} - // :: error: (nullness.on.outer) - void fscninii(ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii) {} + // :: error: (nullness.on.outer) + void fscninii(ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii) {} - void fscsnni(ScopingConstruct.StaticNested.NestedInner scsnni) {} + void fscsnni(ScopingConstruct.StaticNested.NestedInner scsnni) {} - void fscsnnni(ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni) {} + void fscsnnni(ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni) {} - /// - /// Local variables - /// + /// + /// Local variables + /// - void lvsn() { - StaticNested sn; - } + void lvsn() { + StaticNested sn; + } - void lvnsn() { - @Nullable StaticNested nsn; - } + void lvnsn() { + @Nullable StaticNested nsn; + } - void lvi() { - Inner i; - } + void lvi() { + Inner i; + } - void lvni() { - @Nullable Inner ni; - } + void lvni() { + @Nullable Inner ni; + } - void lvscsn() { - ScopingConstruct.StaticNested scsn; - } + void lvscsn() { + ScopingConstruct.StaticNested scsn; + } - void lvscnsn() { - ScopingConstruct.@Nullable StaticNested scnsn; - } + void lvscnsn() { + ScopingConstruct.@Nullable StaticNested scnsn; + } - void lvscnsnni() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; - } + void lvscnsnni() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni; + } - void lvscnsnnni() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; - } + void lvscnsnnni() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni; + } - void lvsci() { - ScopingConstruct.Inner sci; - } + void lvsci() { + ScopingConstruct.Inner sci; + } - void lvsciii() { - ScopingConstruct.Inner.InnerInner sciii; - } + void lvsciii() { + ScopingConstruct.Inner.InnerInner sciii; + } - void lvscinii() { - ScopingConstruct.Inner.@Nullable InnerInner scinii; - } + void lvscinii() { + ScopingConstruct.Inner.@Nullable InnerInner scinii; + } - void lvnsci() { - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner nsci; - } + void lvnsci() { + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner nsci; + } - void lvnsciii() { - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.InnerInner nsciii; - } + void lvnsciii() { + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.InnerInner nsciii; + } - void lvnscinii() { - // :: error: (nullness.on.outer) - @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; - } + void lvnscinii() { + // :: error: (nullness.on.outer) + @Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii; + } - void lvscni() { - ScopingConstruct.@Nullable Inner scni; - } + void lvscni() { + ScopingConstruct.@Nullable Inner scni; + } - void lvscniii() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.InnerInner scniii; - } + void lvscniii() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.InnerInner scniii; + } - void lvscninii() { - // :: error: (nullness.on.outer) - ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; - } + void lvscninii() { + // :: error: (nullness.on.outer) + ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii; + } - void lvscsnni() { - ScopingConstruct.StaticNested.NestedInner scsnni; - } + void lvscsnni() { + ScopingConstruct.StaticNested.NestedInner scsnni; + } - void lvscsnnni() { - ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; - } + void lvscsnnni() { + ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni; + } - /// - /// Resource variables - /// + /// + /// Resource variables + /// - void rvsn() { - try (StaticNested sn = null) {} - } + void rvsn() { + try (StaticNested sn = null) {} + } - void rvnsn() { - try (@Nullable StaticNested nsn = null) {} - } + void rvnsn() { + try (@Nullable StaticNested nsn = null) {} + } - void rvi() { - try (Inner i = null) {} - } + void rvi() { + try (Inner i = null) {} + } - void rvni() { - try (@Nullable Inner ni = null) {} - } + void rvni() { + try (@Nullable Inner ni = null) {} + } - void rvscsn() { - try (ScopingConstruct.StaticNested scsn = null) {} - } + void rvscsn() { + try (ScopingConstruct.StaticNested scsn = null) {} + } - void rvscnsn() { - try (ScopingConstruct.@Nullable StaticNested scnsn = null) {} - } + void rvscnsn() { + try (ScopingConstruct.@Nullable StaticNested scnsn = null) {} + } - void rvscnsnni() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null) {} - } + void rvscnsnni() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null) {} + } - void rvscnsnnni() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null) {} - } + void rvscnsnnni() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null) {} + } - void rvsci() { - try (ScopingConstruct.Inner sci = null) {} - } + void rvsci() { + try (ScopingConstruct.Inner sci = null) {} + } - void rvsciii() { - try (ScopingConstruct.Inner.InnerInner sciii = null) {} - } + void rvsciii() { + try (ScopingConstruct.Inner.InnerInner sciii = null) {} + } - void rvscinii() { - try (ScopingConstruct.Inner.@Nullable InnerInner scinii = null) {} - } + void rvscinii() { + try (ScopingConstruct.Inner.@Nullable InnerInner scinii = null) {} + } - void rvnsci() { - // :: error: (nullness.on.outer) - try (@Nullable ScopingConstruct.Inner nsci = null) {} - } + void rvnsci() { + // :: error: (nullness.on.outer) + try (@Nullable ScopingConstruct.Inner nsci = null) {} + } - void rvnsciii() { - // :: error: (nullness.on.outer) - try (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null) {} - } + void rvnsciii() { + // :: error: (nullness.on.outer) + try (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null) {} + } - void rvnscinii() { - // :: error: (nullness.on.outer) - try (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null) {} - } + void rvnscinii() { + // :: error: (nullness.on.outer) + try (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null) {} + } - void rvscni() { - try (ScopingConstruct.@Nullable Inner scni = null) {} - } + void rvscni() { + try (ScopingConstruct.@Nullable Inner scni = null) {} + } - void rvscniii() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable Inner.InnerInner scniii = null) {} - } + void rvscniii() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable Inner.InnerInner scniii = null) {} + } - void rvscninii() { - // :: error: (nullness.on.outer) - try (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null) {} - } + void rvscninii() { + // :: error: (nullness.on.outer) + try (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null) {} + } - void rvscsnni() { - try (ScopingConstruct.StaticNested.NestedInner scsnni = null) {} - } + void rvscsnni() { + try (ScopingConstruct.StaticNested.NestedInner scsnni = null) {} + } - void rvscsnnni() { - try (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null) {} - } + void rvscsnnni() { + try (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null) {} + } - /// - /// For variables - /// + /// + /// For variables + /// - void fvsn() { - for (StaticNested sn = null; ; ) {} - } + void fvsn() { + for (StaticNested sn = null; ; ) {} + } - void fvnsn() { - for (@Nullable StaticNested nsn = null; ; ) {} - } + void fvnsn() { + for (@Nullable StaticNested nsn = null; ; ) {} + } - void fvi() { - for (Inner i = null; ; ) {} - } + void fvi() { + for (Inner i = null; ; ) {} + } - void fvni() { - for (@Nullable Inner ni = null; ; ) {} - } + void fvni() { + for (@Nullable Inner ni = null; ; ) {} + } - void fvscsn() { - for (ScopingConstruct.StaticNested scsn = null; ; ) {} - } + void fvscsn() { + for (ScopingConstruct.StaticNested scsn = null; ; ) {} + } - void fvscnsn() { - for (ScopingConstruct.@Nullable StaticNested scnsn = null; ; ) {} - } + void fvscnsn() { + for (ScopingConstruct.@Nullable StaticNested scnsn = null; ; ) {} + } - void fvscnsnni() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null; ; ) {} - } + void fvscnsnni() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable StaticNested.NestedInner scnsnni = null; ; ) {} + } - void fvscnsnnni() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null; ; ) {} - } + void fvscnsnnni() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable StaticNested.@Nullable NestedInner scnsnnni = null; ; ) {} + } - void fvsci() { - for (ScopingConstruct.Inner sci = null; ; ) {} - } + void fvsci() { + for (ScopingConstruct.Inner sci = null; ; ) {} + } - void fvsciii() { - for (ScopingConstruct.Inner.InnerInner sciii = null; ; ) {} - } + void fvsciii() { + for (ScopingConstruct.Inner.InnerInner sciii = null; ; ) {} + } - void fvscinii() { - for (ScopingConstruct.Inner.@Nullable InnerInner scinii = null; ; ) {} - } + void fvscinii() { + for (ScopingConstruct.Inner.@Nullable InnerInner scinii = null; ; ) {} + } - void fvnsci() { - // :: error: (nullness.on.outer) - for (@Nullable ScopingConstruct.Inner nsci = null; ; ) {} - } + void fvnsci() { + // :: error: (nullness.on.outer) + for (@Nullable ScopingConstruct.Inner nsci = null; ; ) {} + } - void fvnsciii() { - // :: error: (nullness.on.outer) - for (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null; ; ) {} - } + void fvnsciii() { + // :: error: (nullness.on.outer) + for (@Nullable ScopingConstruct.Inner.InnerInner nsciii = null; ; ) {} + } - void fvnscinii() { - // :: error: (nullness.on.outer) - for (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null; ; ) {} - } + void fvnscinii() { + // :: error: (nullness.on.outer) + for (@Nullable ScopingConstruct.Inner.@Nullable InnerInner nscinii = null; ; ) {} + } - void fvscni() { - for (ScopingConstruct.@Nullable Inner scni = null; ; ) {} - } + void fvscni() { + for (ScopingConstruct.@Nullable Inner scni = null; ; ) {} + } - void fvscniii() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable Inner.InnerInner scniii = null; ; ) {} - } + void fvscniii() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable Inner.InnerInner scniii = null; ; ) {} + } - void fvscninii() { - // :: error: (nullness.on.outer) - for (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null; ; ) {} - } + void fvscninii() { + // :: error: (nullness.on.outer) + for (ScopingConstruct.@Nullable Inner.@Nullable InnerInner scninii = null; ; ) {} + } - void fvscsnni() { - for (ScopingConstruct.StaticNested.NestedInner scsnni = null; ; ) {} - } + void fvscsnni() { + for (ScopingConstruct.StaticNested.NestedInner scsnni = null; ; ) {} + } - void fvscsnnni() { - for (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null; ; ) {} - } + void fvscsnnni() { + for (ScopingConstruct.StaticNested.@Nullable NestedInner scsnnni = null; ; ) {} + } } diff --git a/checker/tests/nullness/SelfAssignment.java b/checker/tests/nullness/SelfAssignment.java index 6f85b0d4ea3..3fb4a63cee9 100644 --- a/checker/tests/nullness/SelfAssignment.java +++ b/checker/tests/nullness/SelfAssignment.java @@ -5,15 +5,15 @@ public class SelfAssignment { - void test(@Nullable String s) { - assertNonNull(s); - s = s.trim(); - } + void test(@Nullable String s) { + assertNonNull(s); + s = s.trim(); + } - @EnsuresNonNull("#1") - void assertNonNull(final @Nullable Object o) { - if (o == null) { - throw new AssertionError(); + @EnsuresNonNull("#1") + void assertNonNull(final @Nullable Object o) { + if (o == null) { + throw new AssertionError(); + } } - } } diff --git a/checker/tests/nullness/SelfDependentType.java b/checker/tests/nullness/SelfDependentType.java index a3eb3059ea1..155f8272719 100644 --- a/checker/tests/nullness/SelfDependentType.java +++ b/checker/tests/nullness/SelfDependentType.java @@ -2,151 +2,152 @@ // @skip-test until the issue is fixed +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class SelfDependentType { - public void copy1( - HashMap> a, - HashMap> b) { - a = b; - } + public void copy1( + HashMap> a, + HashMap> b) { + a = b; + } - public void copy2() { - HashMap> a = null; - HashMap> b = null; - a = b; - } + public void copy2() { + HashMap> a = null; + HashMap> b = null; + a = b; + } - class SdtGraph1 { + class SdtGraph1 { - HashMap> childMap; + HashMap> childMap; - // :: error: (expression.parameter.name) - public SdtGraph1(HashMap> childMap) { - this.childMap = childMap; + // :: error: (expression.parameter.name) + public SdtGraph1(HashMap> childMap) { + this.childMap = childMap; + } } - } - class SdtGraph2 { + class SdtGraph2 { - HashMap> childMap; + HashMap> childMap; - // :: error: (expression.parameter.name) - public SdtGraph2(HashMap> childMap) { - this.childMap = childMap; + // :: error: (expression.parameter.name) + public SdtGraph2(HashMap> childMap) { + this.childMap = childMap; + } } - } - class SdtGraph3 { + class SdtGraph3 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph3(HashMap> childMap) { - this.childMap = childMap; + public SdtGraph3(HashMap> childMap) { + this.childMap = childMap; + } } - } - class SdtGraph4 { + class SdtGraph4 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph4(HashMap> childMap) { - this.childMap = childMap; + public SdtGraph4(HashMap> childMap) { + this.childMap = childMap; + } } - } - class SdtGraph5 { + class SdtGraph5 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph5(HashMap> childMap) { - this.childMap = childMap; + public SdtGraph5(HashMap> childMap) { + this.childMap = childMap; + } } - } - class SdtGraph6 { + class SdtGraph6 { - HashMap> childMap; + HashMap> childMap; - public SdtGraph6(HashMap> childMap) { - this.childMap = childMap; + public SdtGraph6(HashMap> childMap) { + this.childMap = childMap; + } } - } - class SdtGraph11 { + class SdtGraph11 { - HashMap> childMapField; + HashMap> childMapField; - // :: error: (expression.parameter.name) - public SdtGraph11(HashMap> childMap) { - this.childMapField = childMap; + // :: error: (expression.parameter.name) + public SdtGraph11(HashMap> childMap) { + this.childMapField = childMap; + } } - } - class SdtGraph12 { + class SdtGraph12 { - HashMap> childMapField; + HashMap> childMapField; - // :: error: (expression.parameter.name) - public SdtGraph12(HashMap> childMap) { - this.childMapField = childMap; + // :: error: (expression.parameter.name) + public SdtGraph12(HashMap> childMap) { + this.childMapField = childMap; + } } - } - class SdtGraph13 { + class SdtGraph13 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph13(HashMap> childMap) { - this.childMapField = childMap; + public SdtGraph13(HashMap> childMap) { + this.childMapField = childMap; + } } - } - class SdtGraph14 { + class SdtGraph14 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph14(HashMap> childMap) { - this.childMapField = childMap; + public SdtGraph14(HashMap> childMap) { + this.childMapField = childMap; + } } - } - class SdtGraph15 { + class SdtGraph15 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph15(HashMap> childMap) { - this.childMapField = childMap; + public SdtGraph15(HashMap> childMap) { + this.childMapField = childMap; + } } - } - class SdtGraph16 { + class SdtGraph16 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph16(HashMap> childMap) { - this.childMapField = childMap; + public SdtGraph16(HashMap> childMap) { + this.childMapField = childMap; + } } - } - class SdtGraph17 { + class SdtGraph17 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph17(HashMap> childMap) { - this.childMapField = childMap; + public SdtGraph17(HashMap> childMap) { + this.childMapField = childMap; + } } - } - class SdtGraph18 { + class SdtGraph18 { - HashMap> childMapField; + HashMap> childMapField; - public SdtGraph18(HashMap> childMap) { - this.childMapField = childMap; + public SdtGraph18(HashMap> childMap) { + this.childMapField = childMap; + } } - } } diff --git a/checker/tests/nullness/SequenceAndIndices.java b/checker/tests/nullness/SequenceAndIndices.java index f94a0a98255..0d319bd9af2 100644 --- a/checker/tests/nullness/SequenceAndIndices.java +++ b/checker/tests/nullness/SequenceAndIndices.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.interning.qual.*; public final class SequenceAndIndices { - public T seq; + public T seq; - public SequenceAndIndices(T seq) { - this.seq = seq; - } + public SequenceAndIndices(T seq) { + this.seq = seq; + } } diff --git a/checker/tests/nullness/SetIteratorTest.java b/checker/tests/nullness/SetIteratorTest.java index 9c60dbbba1d..8f73508d1ad 100644 --- a/checker/tests/nullness/SetIteratorTest.java +++ b/checker/tests/nullness/SetIteratorTest.java @@ -9,45 +9,45 @@ public class SetIteratorTest { - private SortedSet nodes; - private Map>> edges; - - public SetIteratorTest() { - nodes = new TreeSet(); - edges = new HashMap>>(); - } - - public Set listNodes() { - return Collections.unmodifiableSet(nodes); - } - - public String listChildren(String parentNode) { - String childrenString = ""; - - if (edges.get(parentNode) != null) { - for (String childNode : edges.get(parentNode).keySet()) { - // :: error: (dereference.of.nullable) - edges.get(parentNode).toString(); - for (String childNodeEdgeX : edges.get(parentNode).get(childNode)) { - childrenString += " " + childNode + "(" + childNodeEdgeX + ")"; - } - } + private SortedSet nodes; + private Map>> edges; + + public SetIteratorTest() { + nodes = new TreeSet(); + edges = new HashMap>>(); } - return childrenString; - } + public Set listNodes() { + return Collections.unmodifiableSet(nodes); + } + + public String listChildren(String parentNode) { + String childrenString = ""; + + if (edges.get(parentNode) != null) { + for (String childNode : edges.get(parentNode).keySet()) { + // :: error: (dereference.of.nullable) + edges.get(parentNode).toString(); + for (String childNodeEdgeX : edges.get(parentNode).get(childNode)) { + childrenString += " " + childNode + "(" + childNodeEdgeX + ")"; + } + } + } - public void listChildren2(String parentNode) { - if (edges.get(parentNode) != null) { - Iterator itor = edges.get(parentNode).keySet().iterator(); - edges.get(parentNode).toString(); - String s = itor.next(); - // :: error: (dereference.of.nullable) - edges.get(parentNode).toString(); + return childrenString; } - } - public boolean containsNode(String node) { - return nodes.contains(node); - } + public void listChildren2(String parentNode) { + if (edges.get(parentNode) != null) { + Iterator itor = edges.get(parentNode).keySet().iterator(); + edges.get(parentNode).toString(); + String s = itor.next(); + // :: error: (dereference.of.nullable) + edges.get(parentNode).toString(); + } + } + + public boolean containsNode(String node) { + return nodes.contains(node); + } } diff --git a/checker/tests/nullness/SortingCollection.java b/checker/tests/nullness/SortingCollection.java index 8b3adef60c0..dc68e8fb178 100644 --- a/checker/tests/nullness/SortingCollection.java +++ b/checker/tests/nullness/SortingCollection.java @@ -11,13 +11,13 @@ public class SortingCollection { - class MergingIterator { - private final PollableTreeSet queue = null; + class MergingIterator { + private final PollableTreeSet queue = null; - public boolean hasNext() { - return !queue.isEmpty(); + public boolean hasNext() { + return !queue.isEmpty(); + } } - } - static class PollableTreeSet extends TreeSet {} + static class PollableTreeSet extends TreeSet {} } diff --git a/checker/tests/nullness/StaticInLoop.java b/checker/tests/nullness/StaticInLoop.java index 736bc146dc9..faa2ca3ac25 100644 --- a/checker/tests/nullness/StaticInLoop.java +++ b/checker/tests/nullness/StaticInLoop.java @@ -3,13 +3,13 @@ public final class StaticInLoop { - public static @MonotonicNonNull String data_trace_state = null; + public static @MonotonicNonNull String data_trace_state = null; - @RequiresNonNull("StaticInLoop.data_trace_state") - private static void read_vals_and_mods_from_trace_file(Object[] vals, int[] mods) { - for (; ; ) { - data_trace_state.toString(); - vals[0] = "hello"; + @RequiresNonNull("StaticInLoop.data_trace_state") + private static void read_vals_and_mods_from_trace_file(Object[] vals, int[] mods) { + for (; ; ) { + data_trace_state.toString(); + vals[0] = "hello"; + } } - } } diff --git a/checker/tests/nullness/StaticInitializer2.java b/checker/tests/nullness/StaticInitializer2.java index 5e100734f71..dfcf577ba6c 100644 --- a/checker/tests/nullness/StaticInitializer2.java +++ b/checker/tests/nullness/StaticInitializer2.java @@ -10,10 +10,10 @@ public class StaticInitializer2 { - static String a; + static String a; - static { - // :: error: (dereference.of.nullable) - a.toString(); - } + static { + // :: error: (dereference.of.nullable) + a.toString(); + } } diff --git a/checker/tests/nullness/Stats.java b/checker/tests/nullness/Stats.java index 58a550f7da3..63cf1782e71 100644 --- a/checker/tests/nullness/Stats.java +++ b/checker/tests/nullness/Stats.java @@ -1,18 +1,19 @@ // @skip-tests Failing, but commented out to avoid breaking the build -import java.util.Map; import org.checkerframework.checker.nullness.qual.*; +import java.util.Map; + public class Stats { - @Nullable Map inv_map = null; + @Nullable Map inv_map = null; - void dump() { + void dump() { - assert inv_map != null : "@AssumeAssertion(nullness)"; + assert inv_map != null : "@AssumeAssertion(nullness)"; - for (Integer inv_class : inv_map.keySet()) { - inv_map.get(inv_class); + for (Integer inv_class : inv_map.keySet()) { + inv_map.get(inv_class); + } } - } } diff --git a/checker/tests/nullness/StringTernaryConcat.java b/checker/tests/nullness/StringTernaryConcat.java index b72e8e6e731..d0bb6952e41 100644 --- a/checker/tests/nullness/StringTernaryConcat.java +++ b/checker/tests/nullness/StringTernaryConcat.java @@ -1,6 +1,6 @@ public class StringTernaryConcat { - public String s(Integer start) { - return start + (start.equals(start) ? "" : "-"); - } + public String s(Integer start) { + return start + (start.equals(start) ? "" : "-"); + } } diff --git a/checker/tests/nullness/SuperCall.java b/checker/tests/nullness/SuperCall.java index 7ed9954d26d..d38af0f6591 100644 --- a/checker/tests/nullness/SuperCall.java +++ b/checker/tests/nullness/SuperCall.java @@ -2,14 +2,14 @@ public class SuperCall { - public static class A { - public A(@NonNull Object arg) {} - } + public static class A { + public A(@NonNull Object arg) {} + } - public static class B extends A { - public B(@Nullable Object arg) { - // :: error: (argument.type.incompatible) - super(arg); + public static class B extends A { + public B(@Nullable Object arg) { + // :: error: (argument.type.incompatible) + super(arg); + } } - } } diff --git a/checker/tests/nullness/SuppressDeprecation.java b/checker/tests/nullness/SuppressDeprecation.java index 453155df1d0..97b88c300b0 100644 --- a/checker/tests/nullness/SuppressDeprecation.java +++ b/checker/tests/nullness/SuppressDeprecation.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.nullness.qual.*; class SuppressDeprecationOther { - @Deprecated - void old() {} + @Deprecated + void old() {} } public class SuppressDeprecation { - @MonotonicNonNull String tz1; + @MonotonicNonNull String tz1; - @SuppressWarnings("deprecation") - void processOptions(String tz, SuppressDeprecationOther o) { - tz1 = tz; + @SuppressWarnings("deprecation") + void processOptions(String tz, SuppressDeprecationOther o) { + tz1 = tz; - // There should be no deprecation warning here. - o.old(); + // There should be no deprecation warning here. + o.old(); - parseTime("hello"); - } + parseTime("hello"); + } - @RequiresNonNull("tz1") - void parseTime(String time) {} + @RequiresNonNull("tz1") + void parseTime(String time) {} } diff --git a/checker/tests/nullness/SuppressWarningsPartialKeys.java b/checker/tests/nullness/SuppressWarningsPartialKeys.java index 2a49b5cac34..b06fa284045 100644 --- a/checker/tests/nullness/SuppressWarningsPartialKeys.java +++ b/checker/tests/nullness/SuppressWarningsPartialKeys.java @@ -2,115 +2,115 @@ public class SuppressWarningsPartialKeys { - @SuppressWarnings("return.type.incompatible") - @NonNull Object suppressed2() { - return null; - } - - @SuppressWarnings("return.type") - @NonNull Object suppressed3() { - return null; - } - - @SuppressWarnings("type.incompatible") - @NonNull Object suppressed4() { - return null; - } - - @SuppressWarnings("type") - @NonNull Object suppressed5() { - return null; - } - - @SuppressWarnings("nullness:return.type.incompatible") - @NonNull Object suppressedn2() { - return null; - } - - @SuppressWarnings("nullness:return.type") - @NonNull Object suppressedn3() { - return null; - } - - @SuppressWarnings("nullness:type.incompatible") - @NonNull Object suppressedn4() { - return null; - } - - @SuppressWarnings("nullness:type") - @NonNull Object suppressedn5() { - return null; - } - - @SuppressWarnings("i") - @NonNull Object err1() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("eturn.type") - @NonNull Object err2() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("typ") - @NonNull Object err3() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("ype.incompatible") - @NonNull Object err4() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("return.type.") - @NonNull Object err5() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings(".type.incompatible") - @NonNull Object err6() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:i") - @NonNull Object errn1() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:eturn.type") - @NonNull Object errn2() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:typ") - @NonNull Object errn3() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:ype.incompatible") - @NonNull Object errn4() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:return.type.") - @NonNull Object errn5() { - // :: error: (return.type.incompatible) - return null; - } - - @SuppressWarnings("nullness:.type.incompatible") - @NonNull Object errn6() { - // :: error: (return.type.incompatible) - return null; - } + @SuppressWarnings("return.type.incompatible") + @NonNull Object suppressed2() { + return null; + } + + @SuppressWarnings("return.type") + @NonNull Object suppressed3() { + return null; + } + + @SuppressWarnings("type.incompatible") + @NonNull Object suppressed4() { + return null; + } + + @SuppressWarnings("type") + @NonNull Object suppressed5() { + return null; + } + + @SuppressWarnings("nullness:return.type.incompatible") + @NonNull Object suppressedn2() { + return null; + } + + @SuppressWarnings("nullness:return.type") + @NonNull Object suppressedn3() { + return null; + } + + @SuppressWarnings("nullness:type.incompatible") + @NonNull Object suppressedn4() { + return null; + } + + @SuppressWarnings("nullness:type") + @NonNull Object suppressedn5() { + return null; + } + + @SuppressWarnings("i") + @NonNull Object err1() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("eturn.type") + @NonNull Object err2() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("typ") + @NonNull Object err3() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("ype.incompatible") + @NonNull Object err4() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("return.type.") + @NonNull Object err5() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings(".type.incompatible") + @NonNull Object err6() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:i") + @NonNull Object errn1() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:eturn.type") + @NonNull Object errn2() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:typ") + @NonNull Object errn3() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:ype.incompatible") + @NonNull Object errn4() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:return.type.") + @NonNull Object errn5() { + // :: error: (return.type.incompatible) + return null; + } + + @SuppressWarnings("nullness:.type.incompatible") + @NonNull Object errn6() { + // :: error: (return.type.incompatible) + return null; + } } diff --git a/checker/tests/nullness/SuppressWarningsTest.java b/checker/tests/nullness/SuppressWarningsTest.java index 73e09dc6c0c..8a64666a133 100644 --- a/checker/tests/nullness/SuppressWarningsTest.java +++ b/checker/tests/nullness/SuppressWarningsTest.java @@ -2,9 +2,9 @@ public class SuppressWarningsTest { - @SuppressWarnings("all") - void test() { - String a = null; - a.toString(); - } + @SuppressWarnings("all") + void test() { + String a = null; + a.toString(); + } } diff --git a/checker/tests/nullness/SwitchTest.java b/checker/tests/nullness/SwitchTest.java index b972fe2c9c0..f54088485fd 100644 --- a/checker/tests/nullness/SwitchTest.java +++ b/checker/tests/nullness/SwitchTest.java @@ -1,36 +1,36 @@ import org.checkerframework.checker.nullness.qual.*; public class SwitchTest { - public static void main(String[] args) { - // :: error: (switching.nullable) - switch (getNbl()) { - case X: - System.out.println("X"); - break; - default: - System.out.println("default"); + public static void main(String[] args) { + // :: error: (switching.nullable) + switch (getNbl()) { + case X: + System.out.println("X"); + break; + default: + System.out.println("default"); + } } - } - public static void goodUse() { - switch (getNN()) { - case X: - System.out.println("X"); - break; - default: - System.out.println("default"); + public static void goodUse() { + switch (getNN()) { + case X: + System.out.println("X"); + break; + default: + System.out.println("default"); + } } - } - public static @Nullable A getNbl() { - return null; - } + public static @Nullable A getNbl() { + return null; + } - public static A getNN() { - return A.X; - } + public static A getNN() { + return A.X; + } - public static enum A { - X - } + public static enum A { + X + } } diff --git a/checker/tests/nullness/Synchronization.java b/checker/tests/nullness/Synchronization.java index 5a4b07314cb..9205578f37f 100644 --- a/checker/tests/nullness/Synchronization.java +++ b/checker/tests/nullness/Synchronization.java @@ -2,34 +2,34 @@ public class Synchronization { - // Plain - public void bad() { - Object o = null; - // :: error: (locking.nullable) - synchronized (o) { - } // should emit error - } + // Plain + public void bad() { + Object o = null; + // :: error: (locking.nullable) + synchronized (o) { + } // should emit error + } - public void ok() { - // NonNull specifically - @NonNull Object o1 = "m"; - synchronized (o1) { + public void ok() { + // NonNull specifically + @NonNull Object o1 = "m"; + synchronized (o1) { + } } - } - public void flow() { - Object o = null; - o = "m"; - synchronized (o) { - } // valid - o = null; - // :: error: (locking.nullable) - synchronized (o) { - } // invalid - } + public void flow() { + Object o = null; + o = "m"; + synchronized (o) { + } // valid + o = null; + // :: error: (locking.nullable) + synchronized (o) { + } // invalid + } - public Synchronization() { - synchronized (this) { + public Synchronization() { + synchronized (this) { + } } - } } diff --git a/checker/tests/nullness/TernaryNested.java b/checker/tests/nullness/TernaryNested.java index 293e327d99c..84926f7226a 100644 --- a/checker/tests/nullness/TernaryNested.java +++ b/checker/tests/nullness/TernaryNested.java @@ -1,18 +1,19 @@ // Test case for Issue 331: // https://github.com/typetools/checker-framework/issues/331 -import java.util.List; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + public class TernaryNested { - Object foo(boolean b) { - Object o = b ? "" : (b ? "" : ""); - return o; - } + Object foo(boolean b) { + Object o = b ? "" : (b ? "" : ""); + return o; + } - void bar(List l, boolean b) { - Object o = b ? "" : (b ? "" : ""); - l.add(o); - } + void bar(List l, boolean b) { + Object o = b ? "" : (b ? "" : ""); + l.add(o); + } } diff --git a/checker/tests/nullness/TernaryNullness.java b/checker/tests/nullness/TernaryNullness.java index 43806b630f9..a094ec04d22 100644 --- a/checker/tests/nullness/TernaryNullness.java +++ b/checker/tests/nullness/TernaryNullness.java @@ -3,9 +3,9 @@ import org.checkerframework.checker.nullness.qual.*; abstract class TernaryNullness { - void f(@Nullable Object o) { - g(42, o != null ? o.hashCode() : 0); - } + void f(@Nullable Object o) { + g(42, o != null ? o.hashCode() : 0); + } - abstract void g(Object x, Object xs); + abstract void g(Object x, Object xs); } diff --git a/checker/tests/nullness/TestAssignment.java b/checker/tests/nullness/TestAssignment.java index e687de23f6b..463bb4e877a 100644 --- a/checker/tests/nullness/TestAssignment.java +++ b/checker/tests/nullness/TestAssignment.java @@ -5,14 +5,14 @@ public class TestAssignment { - void a() { - @NonNull String f = "abc"; + void a() { + @NonNull String f = "abc"; - // :: error: (assignment.type.incompatible) - f = null; - } + // :: error: (assignment.type.incompatible) + f = null; + } - void b() { - @UnknownInitialization @NonNull TestAssignment f = new TestAssignment(); - } + void b() { + @UnknownInitialization @NonNull TestAssignment f = new TestAssignment(); + } } diff --git a/checker/tests/nullness/TestFromPullRequest880.java b/checker/tests/nullness/TestFromPullRequest880.java index 7d3104cf1ca..3f8624f00ef 100644 --- a/checker/tests/nullness/TestFromPullRequest880.java +++ b/checker/tests/nullness/TestFromPullRequest880.java @@ -5,22 +5,23 @@ // Also note a test that uses multiple compilation units at: // checker/jtreg/nullness/annotationsOnExtends/ +import org.checkerframework.checker.nullness.qual.NonNull; + import java.io.Serializable; import java.util.List; -import org.checkerframework.checker.nullness.qual.NonNull; class TFPR880Test implements Serializable {} class TFPR880Use { - void foo() { - TFPR880Test other = null; - } + void foo() { + TFPR880Test other = null; + } } abstract class TFPR880TestSub extends TFPR880Test implements List<@NonNull String> {} class TFPR880SubUse { - void foo() { - TFPR880TestSub other = null; - } + void foo() { + TFPR880TestSub other = null; + } } diff --git a/checker/tests/nullness/TestInfer.java b/checker/tests/nullness/TestInfer.java index c20857d09b2..a16b8fed2d5 100644 --- a/checker/tests/nullness/TestInfer.java +++ b/checker/tests/nullness/TestInfer.java @@ -1,21 +1,22 @@ // Test case for issue #238: https://github.com/typetools/checker-framework/issues/238 +import org.checkerframework.checker.nullness.qual.UnknownKeyFor; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.UnknownKeyFor; public class TestInfer { - T getValue(List l) { - return l.get(0); - } + T getValue(List l) { + return l.get(0); + } - void bar(Object o) {} + void bar(Object o) {} - void foo() { - List<@UnknownKeyFor ? extends Object> ls = new ArrayList<>(); - bar(getValue(ls)); // this fails, but just getValue(ls) is OK - // casting is also OK, ie bar((Object)getValue(ls)) - // The constraint should be T<:Object, which should typecheck since ls:List unifies with List where T<:Object. - } + void foo() { + List<@UnknownKeyFor ? extends Object> ls = new ArrayList<>(); + bar(getValue(ls)); // this fails, but just getValue(ls) is OK + // casting is also OK, ie bar((Object)getValue(ls)) + // The constraint should be T<:Object, which should typecheck since ls:List unifies with List where T<:Object. + } } diff --git a/checker/tests/nullness/TestPolyNull.java b/checker/tests/nullness/TestPolyNull.java index f730bcecb5d..452205b0263 100644 --- a/checker/tests/nullness/TestPolyNull.java +++ b/checker/tests/nullness/TestPolyNull.java @@ -1,50 +1,50 @@ import org.checkerframework.checker.nullness.qual.*; public class TestPolyNull { - @PolyNull String identity(@PolyNull String str) { - return str; - } + @PolyNull String identity(@PolyNull String str) { + return str; + } - void test1() { - identity(null); - } + void test1() { + identity(null); + } - void test2() { - identity((@Nullable String) null); - } + void test2() { + identity((@Nullable String) null); + } - public static @PolyNull String[] typeArray(@PolyNull Object[] seq, @Nullable String nullable) { - @SuppressWarnings("nullness") // ignore array initialization here. - @PolyNull String[] retval = new @Nullable String[seq.length]; - for (int i = 0; i < seq.length; i++) { - if (seq[i] == null) { - // null can be assigned into the PolyNull array, because we - // performed a test on seq and know that it is nullable. - retval[i] = null; - // and so can something that is nullable - retval[i] = nullable; - // One can always add a dummy value: nonnull is the bottom - // type and legal for any instantiation of PolyNull. - retval[i] = "dummy"; - } else { - retval[i] = seq[i].getClass().toString(); - // :: error: (assignment.type.incompatible) - retval[i] = null; - // :: error: (assignment.type.incompatible) - retval[i] = nullable; - } + public static @PolyNull String[] typeArray(@PolyNull Object[] seq, @Nullable String nullable) { + @SuppressWarnings("nullness") // ignore array initialization here. + @PolyNull String[] retval = new @Nullable String[seq.length]; + for (int i = 0; i < seq.length; i++) { + if (seq[i] == null) { + // null can be assigned into the PolyNull array, because we + // performed a test on seq and know that it is nullable. + retval[i] = null; + // and so can something that is nullable + retval[i] = nullable; + // One can always add a dummy value: nonnull is the bottom + // type and legal for any instantiation of PolyNull. + retval[i] = "dummy"; + } else { + retval[i] = seq[i].getClass().toString(); + // :: error: (assignment.type.incompatible) + retval[i] = null; + // :: error: (assignment.type.incompatible) + retval[i] = nullable; + } + } + return retval; } - return retval; - } - public static @PolyNull String identity2(@PolyNull String a) { - return (a == null) ? null : a; - } + public static @PolyNull String identity2(@PolyNull String a) { + return (a == null) ? null : a; + } - public static @PolyNull String identity3(@PolyNull String a) { - if (a == null) { - return null; + public static @PolyNull String identity3(@PolyNull String a) { + if (a == null) { + return null; + } + return a; } - return a; - } } diff --git a/checker/tests/nullness/TestValOf.java b/checker/tests/nullness/TestValOf.java index 32cf9af0f58..31952742d68 100644 --- a/checker/tests/nullness/TestValOf.java +++ b/checker/tests/nullness/TestValOf.java @@ -2,13 +2,13 @@ public class TestValOf> { - private final Class enumClass; + private final Class enumClass; - private TestValOf(Class enumClass) { - this.enumClass = enumClass; - } + private TestValOf(Class enumClass) { + this.enumClass = enumClass; + } - T foo(String value) { - return Enum.valueOf(enumClass, value); - } + T foo(String value) { + return Enum.valueOf(enumClass, value); + } } diff --git a/checker/tests/nullness/ThisIsNN.java b/checker/tests/nullness/ThisIsNN.java index d9d904155a7..139e94ad590 100644 --- a/checker/tests/nullness/ThisIsNN.java +++ b/checker/tests/nullness/ThisIsNN.java @@ -1,29 +1,29 @@ import org.checkerframework.checker.nullness.qual.*; public class ThisIsNN { - Object out = new Object(); + Object out = new Object(); - class Inner { - void test1() { - out = this; - out = ThisIsNN.this; - } + class Inner { + void test1() { + out = this; + out = ThisIsNN.this; + } - Object in = new Object(); + Object in = new Object(); - void test2(Inner this) { - Object nonRawThis = this; - out = nonRawThis; - } + void test2(Inner this) { + Object nonRawThis = this; + out = nonRawThis; + } - void test3(Inner this) { - Object nonRawThis = ThisIsNN.this; - out = nonRawThis; + void test3(Inner this) { + Object nonRawThis = ThisIsNN.this; + out = nonRawThis; + } } - } - void test4(ThisIsNN this) { - Object nonRawThis = this; - out = nonRawThis; - } + void test4(ThisIsNN this) { + Object nonRawThis = this; + out = nonRawThis; + } } diff --git a/checker/tests/nullness/ThisQualified.java b/checker/tests/nullness/ThisQualified.java index fb182d9f634..87c8f07efd6 100644 --- a/checker/tests/nullness/ThisQualified.java +++ b/checker/tests/nullness/ThisQualified.java @@ -4,9 +4,9 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; public class ThisQualified { - public ThisQualified() { - super(); - @UnderInitialization ThisQualified a = this; - @UnderInitialization ThisQualified b = ThisQualified.this; - } + public ThisQualified() { + super(); + @UnderInitialization ThisQualified a = this; + @UnderInitialization ThisQualified b = ThisQualified.this; + } } diff --git a/checker/tests/nullness/ThisTest.java b/checker/tests/nullness/ThisTest.java index 8961a8a7bbc..3c33ff02678 100644 --- a/checker/tests/nullness/ThisTest.java +++ b/checker/tests/nullness/ThisTest.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.nullness.qual.*; @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.NonNull.class) + org.checkerframework.checker.nullness.qual.NonNull.class) public class ThisTest { - public String field; + public String field; - public ThisTest(String field) { - this.field = field; - } + public ThisTest(String field) { + this.field = field; + } - void doNothing() {} + void doNothing() {} - class InnerClass { - public void accessOuterThis() { - ThisTest.this.doNothing(); - String s = ThisTest.this.field; + class InnerClass { + public void accessOuterThis() { + ThisTest.this.doNothing(); + String s = ThisTest.this.field; + } } - } } diff --git a/checker/tests/nullness/ThreadLocalTest.java b/checker/tests/nullness/ThreadLocalTest.java index c86b35f86c3..e0042449a6e 100644 --- a/checker/tests/nullness/ThreadLocalTest.java +++ b/checker/tests/nullness/ThreadLocalTest.java @@ -2,22 +2,22 @@ public class ThreadLocalTest { - // implementation MUST override initialValue(), or SuppressWarnings is unsound - @SuppressWarnings("nullness:type.argument.type.incompatible") - class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { - @Override - protected Integer initialValue() { - return Integer.valueOf(0); + // implementation MUST override initialValue(), or SuppressWarnings is unsound + @SuppressWarnings("nullness:type.argument.type.incompatible") + class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { + @Override + protected Integer initialValue() { + return Integer.valueOf(0); + } } - } - void foo() { - // :: error: (type.argument.type.incompatible) - new ThreadLocal<@NonNull Object>(); - // :: error: (type.argument.type.incompatible) - new InheritableThreadLocal<@NonNull Object>(); - new ThreadLocal<@Nullable Object>(); - new InheritableThreadLocal<@Nullable Object>(); - new MyThreadLocalNN(); - } + void foo() { + // :: error: (type.argument.type.incompatible) + new ThreadLocal<@NonNull Object>(); + // :: error: (type.argument.type.incompatible) + new InheritableThreadLocal<@NonNull Object>(); + new ThreadLocal<@Nullable Object>(); + new InheritableThreadLocal<@Nullable Object>(); + new MyThreadLocalNN(); + } } diff --git a/checker/tests/nullness/ThreadLocalTest2.java b/checker/tests/nullness/ThreadLocalTest2.java index 9dd03f79d46..53f205b369b 100644 --- a/checker/tests/nullness/ThreadLocalTest2.java +++ b/checker/tests/nullness/ThreadLocalTest2.java @@ -8,64 +8,64 @@ public class ThreadLocalTest2 { - private static int unwrap(final ThreadLocal tl) { - return tl.get().intValue(); - } + private static int unwrap(final ThreadLocal tl) { + return tl.get().intValue(); + } + + private static void wrap(final ThreadLocal tl, final int value) { + tl.set(Integer.valueOf(value)); + } - private static void wrap(final ThreadLocal tl, final int value) { - tl.set(Integer.valueOf(value)); - } + private static ThreadLocal consumed_chars = + new ThreadLocal() { - private static ThreadLocal consumed_chars = - new ThreadLocal() { + @Override + protected Integer initialValue() { + return Integer.valueOf(0); + } + }; + class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { @Override protected Integer initialValue() { - return Integer.valueOf(0); + return Integer.valueOf(0); } - }; - - class MyThreadLocalNN extends ThreadLocal<@NonNull Integer> { - @Override - protected Integer initialValue() { - return Integer.valueOf(0); } - } - class MyThreadLocalNnIncorrectOverride extends ThreadLocal<@NonNull Integer> { - @Override - // :: error: (override.return.invalid) - protected @Nullable Integer initialValue() { - return null; + class MyThreadLocalNnIncorrectOverride extends ThreadLocal<@NonNull Integer> { + @Override + // :: error: (override.return.invalid) + protected @Nullable Integer initialValue() { + return null; + } } - } - // :: error: (method.not.overridden) - class MyThreadLocalNnNoOverride extends ThreadLocal<@NonNull Integer> {} + // :: error: (method.not.overridden) + class MyThreadLocalNnNoOverride extends ThreadLocal<@NonNull Integer> {} - class MyThreadLocalNble extends ThreadLocal<@Nullable Integer> { - @Override - protected @Nullable Integer initialValue() { - return null; + class MyThreadLocalNble extends ThreadLocal<@Nullable Integer> { + @Override + protected @Nullable Integer initialValue() { + return null; + } } - } - class MyThreadLocalNbleStrongerOverride extends ThreadLocal<@Nullable Integer> { - @Override - protected @NonNull Integer initialValue() { - return Integer.valueOf(0); + class MyThreadLocalNbleStrongerOverride extends ThreadLocal<@Nullable Integer> { + @Override + protected @NonNull Integer initialValue() { + return Integer.valueOf(0); + } } - } - class MyThreadLocalNbleNoOverride extends ThreadLocal<@Nullable Integer> {} + class MyThreadLocalNbleNoOverride extends ThreadLocal<@Nullable Integer> {} - void foo() { - // :: error: (type.argument.type.incompatible) - new ThreadLocal<@NonNull Object>(); - // :: error: (type.argument.type.incompatible) - new InheritableThreadLocal<@NonNull Object>(); - new ThreadLocal<@Nullable Object>(); - new InheritableThreadLocal<@Nullable Object>(); - new MyThreadLocalNN(); - } + void foo() { + // :: error: (type.argument.type.incompatible) + new ThreadLocal<@NonNull Object>(); + // :: error: (type.argument.type.incompatible) + new InheritableThreadLocal<@NonNull Object>(); + new ThreadLocal<@Nullable Object>(); + new InheritableThreadLocal<@Nullable Object>(); + new MyThreadLocalNN(); + } } diff --git a/checker/tests/nullness/ToArrayDiagnostics.java b/checker/tests/nullness/ToArrayDiagnostics.java index 13d047b86a3..174e51ee0e8 100644 --- a/checker/tests/nullness/ToArrayDiagnostics.java +++ b/checker/tests/nullness/ToArrayDiagnostics.java @@ -2,29 +2,29 @@ public class ToArrayDiagnostics { - String[] ok2(ArrayList list) { - return list.toArray(new String[] {}); - } + String[] ok2(ArrayList list) { + return list.toArray(new String[] {}); + } - String[] ok3(ArrayList list) { - return list.toArray(new String[0]); - } + String[] ok3(ArrayList list) { + return list.toArray(new String[0]); + } - String[] ok4(ArrayList list) { - return list.toArray(new String[list.size()]); - } + String[] ok4(ArrayList list) { + return list.toArray(new String[list.size()]); + } - String[] warn1(ArrayList list) { - // :: error: (new.array.type.invalid) - String[] resultArray = new String[list.size()]; - // :: error: (return.type.incompatible) :: warning: (toarray.nullable.elements.not.newarray) - return list.toArray(resultArray); - } + String[] warn1(ArrayList list) { + // :: error: (new.array.type.invalid) + String[] resultArray = new String[list.size()]; + // :: error: (return.type.incompatible) :: warning: (toarray.nullable.elements.not.newarray) + return list.toArray(resultArray); + } - String[] warn2(ArrayList list) { - int size = list.size(); - // :: error: (new.array.type.invalid) :: error: (return.type.incompatible) :: warning: - // (toarray.nullable.elements.mismatched.size) - return list.toArray(new String[size]); - } + String[] warn2(ArrayList list) { + int size = list.size(); + // :: error: (new.array.type.invalid) :: error: (return.type.incompatible) :: warning: + // (toarray.nullable.elements.mismatched.size) + return list.toArray(new String[size]); + } } diff --git a/checker/tests/nullness/ToArrayNullness.java b/checker/tests/nullness/ToArrayNullness.java index 77590ea8d1d..c42943bf234 100644 --- a/checker/tests/nullness/ToArrayNullness.java +++ b/checker/tests/nullness/ToArrayNullness.java @@ -1,107 +1,108 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class ToArrayNullness { - private List<@Nullable String> nullableList = new ArrayList<>(); - private List<@NonNull String> nonnullList = new ArrayList<>(); + private List<@Nullable String> nullableList = new ArrayList<>(); + private List<@NonNull String> nonnullList = new ArrayList<>(); - void listToArrayObject() { - for (@Nullable Object o : nullableList.toArray()) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull Object o : nullableList.toArray()) {} + void listToArrayObject() { + for (@Nullable Object o : nullableList.toArray()) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull Object o : nullableList.toArray()) {} - for (@Nullable Object o : nonnullList.toArray()) {} - for (@NonNull Object o : nonnullList.toArray()) {} - } + for (@Nullable Object o : nonnullList.toArray()) {} + for (@NonNull Object o : nonnullList.toArray()) {} + } - void listToArrayE() { - for (@Nullable String o : nullableList.toArray(new @Nullable String[0])) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableList.toArray(new @Nullable String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - for (@Nullable String o : nullableList.toArray(new @NonNull String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableList.toArray(new @NonNull String[0])) {} + void listToArrayE() { + for (@Nullable String o : nullableList.toArray(new @Nullable String[0])) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableList.toArray(new @Nullable String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + for (@Nullable String o : nullableList.toArray(new @NonNull String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableList.toArray(new @NonNull String[0])) {} - for (@Nullable String o : nonnullList.toArray(new String[0])) {} - // No error expected here. Note that the heuristics determine that the given array - // is not used and that a new one will be created. - for (@NonNull String o : nonnullList.toArray(new @Nullable String[0])) {} - for (@Nullable String o : nonnullList.toArray(new @NonNull String[0])) {} - for (@NonNull String o : nonnullList.toArray(new @NonNull String[0])) {} - } + for (@Nullable String o : nonnullList.toArray(new String[0])) {} + // No error expected here. Note that the heuristics determine that the given array + // is not used and that a new one will be created. + for (@NonNull String o : nonnullList.toArray(new @Nullable String[0])) {} + for (@Nullable String o : nonnullList.toArray(new @NonNull String[0])) {} + for (@NonNull String o : nonnullList.toArray(new @NonNull String[0])) {} + } - private Collection<@Nullable String> nullableCol = new ArrayList<@Nullable String>(); - private Collection<@NonNull String> nonnullCol = new ArrayList<@NonNull String>(); + private Collection<@Nullable String> nullableCol = new ArrayList<@Nullable String>(); + private Collection<@NonNull String> nonnullCol = new ArrayList<@NonNull String>(); - void colToArrayObject() { - for (@Nullable Object o : nullableCol.toArray()) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull Object o : nullableCol.toArray()) {} + void colToArrayObject() { + for (@Nullable Object o : nullableCol.toArray()) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull Object o : nullableCol.toArray()) {} - for (@Nullable Object o : nonnullCol.toArray()) {} - for (@NonNull Object o : nonnullCol.toArray()) {} - } + for (@Nullable Object o : nonnullCol.toArray()) {} + for (@NonNull Object o : nonnullCol.toArray()) {} + } - void colToArrayE() { - for (@Nullable String o : nullableCol.toArray(new @Nullable String[0])) {} - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableCol.toArray(new @Nullable String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - for (@Nullable String o : nullableCol.toArray(new @NonNull String[0])) {} - // TODOINVARR:: error: (argument.type.incompatible) - // :: error: (enhancedfor.type.incompatible) - for (@NonNull String o : nullableCol.toArray(new @NonNull String[0])) {} + void colToArrayE() { + for (@Nullable String o : nullableCol.toArray(new @Nullable String[0])) {} + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableCol.toArray(new @Nullable String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + for (@Nullable String o : nullableCol.toArray(new @NonNull String[0])) {} + // TODOINVARR:: error: (argument.type.incompatible) + // :: error: (enhancedfor.type.incompatible) + for (@NonNull String o : nullableCol.toArray(new @NonNull String[0])) {} - for (@Nullable String o : nonnullCol.toArray(new String[0])) {} - // No error expected here. Note that the heuristics determine that the given array - // is not used and that a new one will be created. - for (@NonNull String o : nonnullCol.toArray(new @Nullable String[0])) {} - for (@Nullable String o : nonnullCol.toArray(new @NonNull String[0])) {} - for (@NonNull String o : nonnullCol.toArray(new @NonNull String[0])) {} - } + for (@Nullable String o : nonnullCol.toArray(new String[0])) {} + // No error expected here. Note that the heuristics determine that the given array + // is not used and that a new one will be created. + for (@NonNull String o : nonnullCol.toArray(new @Nullable String[0])) {} + for (@Nullable String o : nonnullCol.toArray(new @NonNull String[0])) {} + for (@NonNull String o : nonnullCol.toArray(new @NonNull String[0])) {} + } - void testHearusitics() { - for (@Nullable String o : nonnullCol.toArray(new String[] {})) {} - for (@NonNull String o : nonnullCol.toArray(new String[] {})) {} - for (@Nullable String o : nonnullCol.toArray(new String[0])) {} - for (@NonNull String o : nonnullCol.toArray(new String[0])) {} - for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} - for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} + void testHearusitics() { + for (@Nullable String o : nonnullCol.toArray(new String[] {})) {} + for (@NonNull String o : nonnullCol.toArray(new String[] {})) {} + for (@Nullable String o : nonnullCol.toArray(new String[0])) {} + for (@NonNull String o : nonnullCol.toArray(new String[0])) {} + for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} + for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size()])) {} - // :: warning: (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new @Nullable String[] {null})) {} - // :: error: (enhancedfor.type.incompatible) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new @Nullable String[] {null})) {} - // Size 1 is too big for an empty array. Complain. TODO: Could allow as result is Nullable. - // :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new String[1])) {} - // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new String[1])) {} - // Array too big -> complain. TODO: Could allow as result is Nullable. - // :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} - // Array too big -> complain. - // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} + // :: warning: (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new @Nullable String[] {null})) {} + // :: error: (enhancedfor.type.incompatible) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new @Nullable String[] {null})) {} + // Size 1 is too big for an empty array. Complain. TODO: Could allow as result is Nullable. + // :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new String[1])) {} + // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new String[1])) {} + // Array too big -> complain. TODO: Could allow as result is Nullable. + // :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} + // Array too big -> complain. + // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() + 1])) {} - // cannot handle the following cases for now - // new array not size 0 or .size -> complain about cration. TODO: Could allow as result is - // Nullable. - // :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} - // New array not size 0 or .size -> complain about creation. - // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: - // (toarray.nullable.elements.mismatched.size) - for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} - } + // cannot handle the following cases for now + // new array not size 0 or .size -> complain about cration. TODO: Could allow as result is + // Nullable. + // :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@Nullable String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} + // New array not size 0 or .size -> complain about creation. + // :: error: (enhancedfor.type.incompatible) :: error: (new.array.type.invalid) :: warning: + // (toarray.nullable.elements.mismatched.size) + for (@NonNull String o : nonnullCol.toArray(new String[nonnullCol.size() - 1])) {} + } } diff --git a/checker/tests/nullness/ToArrayTest.java b/checker/tests/nullness/ToArrayTest.java index ff8809e913f..513578bcaa5 100644 --- a/checker/tests/nullness/ToArrayTest.java +++ b/checker/tests/nullness/ToArrayTest.java @@ -1,20 +1,21 @@ -import java.util.Collection; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Collection; + public final class ToArrayTest { - public static void isReverse1(@NonNull Collection seq1) { - Object[] seq1_array_TMP2 = seq1.toArray(); - Object[] seq1_array = seq1.toArray(new Object[] {}); - } + public static void isReverse1(@NonNull Collection seq1) { + Object[] seq1_array_TMP2 = seq1.toArray(); + Object[] seq1_array = seq1.toArray(new Object[] {}); + } - public static void isReverse2(@NonNull Collection seq1) { - @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; - Object[] seq1_array = seq1.toArray(new Object[] {}); - } + public static void isReverse2(@NonNull Collection seq1) { + @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; + Object[] seq1_array = seq1.toArray(new Object[] {}); + } - public static void isReverse3(@NonNull Collection seq1) { - @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; - Object[] seq1_array = seq1.toArray(new Object[] {}); - } + public static void isReverse3(@NonNull Collection seq1) { + @NonNull Object @NonNull [] seq1_array_TMP = new Object[] {}; + Object[] seq1_array = seq1.toArray(new Object[] {}); + } } diff --git a/checker/tests/nullness/TryWithResources.java b/checker/tests/nullness/TryWithResources.java index 5436db01274..cbe611cecd4 100644 --- a/checker/tests/nullness/TryWithResources.java +++ b/checker/tests/nullness/TryWithResources.java @@ -1,45 +1,46 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.io.*; import java.util.zip.ZipFile; -import org.checkerframework.checker.nullness.qual.Nullable; public class TryWithResources { - void m1(InputStream stream) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) { - in.toString(); - } catch (Exception e) { + void m1(InputStream stream) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) { + in.toString(); + } catch (Exception e) { + } } - } - void m2() { - try (BufferedReader in = null) { - // :: error: (dereference.of.nullable) - in.toString(); - } catch (Exception e) { + void m2() { + try (BufferedReader in = null) { + // :: error: (dereference.of.nullable) + in.toString(); + } catch (Exception e) { + } } - } - // Check that catch blocks and code after try-catch are part of CFG (and flow-sensitive - // type-refinements work there). - boolean m3(@Nullable Object x) { - try (ZipFile f = openZipFile()) { - return true; - } catch (IOException e) { - if (x != null) { - // OK - x.toString(); - } - } + // Check that catch blocks and code after try-catch are part of CFG (and flow-sensitive + // type-refinements work there). + boolean m3(@Nullable Object x) { + try (ZipFile f = openZipFile()) { + return true; + } catch (IOException e) { + if (x != null) { + // OK + x.toString(); + } + } - if (x != null) { - // OK - return x.equals(x); - } + if (x != null) { + // OK + return x.equals(x); + } - return false; - } + return false; + } - // Helper - private static ZipFile openZipFile() throws IOException { - throw new IOException("No zip-file for you!"); - } + // Helper + private static ZipFile openZipFile() throws IOException { + throw new IOException("No zip-file for you!"); + } } diff --git a/checker/tests/nullness/TryWithResourcesAnno.java b/checker/tests/nullness/TryWithResourcesAnno.java index 18b6cecef7c..a2639a16a86 100644 --- a/checker/tests/nullness/TryWithResourcesAnno.java +++ b/checker/tests/nullness/TryWithResourcesAnno.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TryWithResourcesAnno { - public static void f() { - try (@Nullable AutoCloseable obj = null) { - } catch (Exception e) { + public static void f() { + try (@Nullable AutoCloseable obj = null) { + } catch (Exception e) { + } } - } - public static void g() { - try (@Nullable AutoCloseable obj1 = null; - AutoCloseable obj2 = null) { - } catch (Exception e) { + public static void g() { + try (@Nullable AutoCloseable obj1 = null; + AutoCloseable obj2 = null) { + } catch (Exception e) { + } } - } - public static void h() { - try (AutoCloseable obj1 = null; - @Nullable AutoCloseable obj2 = null) { - } catch (Exception e) { + public static void h() { + try (AutoCloseable obj1 = null; + @Nullable AutoCloseable obj2 = null) { + } catch (Exception e) { + } } - } } diff --git a/checker/tests/nullness/TypeVarPrimitivesNullness.java b/checker/tests/nullness/TypeVarPrimitivesNullness.java index 0049b465a45..939129406eb 100644 --- a/checker/tests/nullness/TypeVarPrimitivesNullness.java +++ b/checker/tests/nullness/TypeVarPrimitivesNullness.java @@ -3,31 +3,31 @@ import org.checkerframework.checker.nullness.qual.*; public class TypeVarPrimitivesNullness { - void method(T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void method(T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } - void methodIntersection(T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void methodIntersection(T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } - void method2(@NonNull T tLong) { - long l = tLong; - } + void method2(@NonNull T tLong) { + long l = tLong; + } - void methodIntersection2(@NonNull T tLong) { - long l = tLong; - } + void methodIntersection2(@NonNull T tLong) { + long l = tLong; + } - void method3(@Nullable T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void method3(@Nullable T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } - void methodIntersection3(@Nullable T tLong) { - // :: error: (unboxing.of.nullable) - long l = tLong; - } + void methodIntersection3(@Nullable T tLong) { + // :: error: (unboxing.of.nullable) + long l = tLong; + } } diff --git a/checker/tests/nullness/UnannoPrimitives.java b/checker/tests/nullness/UnannoPrimitives.java index c2bb0ce6bef..4e70f9ae072 100644 --- a/checker/tests/nullness/UnannoPrimitives.java +++ b/checker/tests/nullness/UnannoPrimitives.java @@ -1,58 +1,58 @@ import org.checkerframework.checker.nullness.qual.*; public class UnannoPrimitives { - // :: error: (nullness.on.primitive) - @Nullable int f; - - // :: error: (nullness.on.primitive) - @NonNull int g; - - void local() { - // test whether an arbitrary declaration annotation gets confused - @SuppressWarnings("tata") - int h = Integer.valueOf(5); - - int i = Integer.valueOf(99) + 1900; - int j = 7 + 1900; - // :: error: (nullness.on.primitive) @Nullable int f; // :: error: (nullness.on.primitive) @NonNull int g; - } - static void testDate() { - @SuppressWarnings("deprecation") // for iCal4j - int year = new java.util.Date().getYear() + 1900; - String strDate = "/" + year; - } + void local() { + // test whether an arbitrary declaration annotation gets confused + @SuppressWarnings("tata") + int h = Integer.valueOf(5); - // :: error: (nullness.on.primitive) - @Nullable byte[] d1 = {4}; - byte @Nullable [] d1b = {4}; + int i = Integer.valueOf(99) + 1900; + int j = 7 + 1900; - // :: error: (nullness.on.primitive) - @Nullable byte[][] twoD = {{4}}; + // :: error: (nullness.on.primitive) + @Nullable int f; - // :: error: (nullness.on.primitive) - @Nullable byte[][][] threeD = {{{4}}}; + // :: error: (nullness.on.primitive) + @NonNull int g; + } - // :: error: (nullness.on.primitive) - @Nullable byte[][][][] fourD = {{{{4}}}}; + static void testDate() { + @SuppressWarnings("deprecation") // for iCal4j + int year = new java.util.Date().getYear() + 1900; + String strDate = "/" + year; + } - @SuppressWarnings("ha!") - byte[] d2 = {4}; + // :: error: (nullness.on.primitive) + @Nullable byte[] d1 = {4}; + byte @Nullable [] d1b = {4}; - // :: error: (nullness.on.primitive) - Object ar = new @Nullable byte[] {4}; + // :: error: (nullness.on.primitive) + @Nullable byte[][] twoD = {{4}}; - // :: error: (nullness.on.primitive) - Object ar2 = new @NonNull byte[] {42}; + // :: error: (nullness.on.primitive) + @Nullable byte[][][] threeD = {{{4}}}; - void testCasts(Integer i1) { - Object i2 = (int) i1; // :: error: (nullness.on.primitive) - Object i3 = (@Nullable int) i1; - } + @Nullable byte[][][][] fourD = {{{{4}}}}; + + @SuppressWarnings("ha!") + byte[] d2 = {4}; + + // :: error: (nullness.on.primitive) + Object ar = new @Nullable byte[] {4}; + + // :: error: (nullness.on.primitive) + Object ar2 = new @NonNull byte[] {42}; + + void testCasts(Integer i1) { + Object i2 = (int) i1; + // :: error: (nullness.on.primitive) + Object i3 = (@Nullable int) i1; + } } diff --git a/checker/tests/nullness/UnannoPrimitivesDefaults.java b/checker/tests/nullness/UnannoPrimitivesDefaults.java index 016d0596111..53f20b3271b 100644 --- a/checker/tests/nullness/UnannoPrimitivesDefaults.java +++ b/checker/tests/nullness/UnannoPrimitivesDefaults.java @@ -1,16 +1,16 @@ public class UnannoPrimitivesDefaults { - @org.checkerframework.framework.qual.DefaultQualifier( - org.checkerframework.checker.nullness.qual.NonNull.class) - class Decl { - // The return type is not annotated with @NonNull, because - // the implicit annotation for @Primitive takes precedence. - int test() { - return 5; + @org.checkerframework.framework.qual.DefaultQualifier( + org.checkerframework.checker.nullness.qual.NonNull.class) + class Decl { + // The return type is not annotated with @NonNull, because + // the implicit annotation for @Primitive takes precedence. + int test() { + return 5; + } } - } - class Use { - Decl d = new Decl(); - int x = d.test(); - } + class Use { + Decl d = new Decl(); + int x = d.test(); + } } diff --git a/checker/tests/nullness/UnboxConditions.java b/checker/tests/nullness/UnboxConditions.java index 8ac19930616..856e9e0c85a 100644 --- a/checker/tests/nullness/UnboxConditions.java +++ b/checker/tests/nullness/UnboxConditions.java @@ -1,26 +1,26 @@ public class UnboxConditions { - public static void main(String[] args) { - Boolean b = null; - Boolean b1 = null; - Boolean b2 = null; - Boolean b3 = null; - Boolean b4 = null; - // :: error: (condition.nullable) - if (b) {} - // :: error: (condition.nullable) - b = b1 ? b : b; - // :: error: (condition.nullable) - while (b2) {} - do { - // :: error: (condition.nullable) - } while (b3); - // :: error: (condition.nullable) - for (; b4; ) {} - // legal! - for (; ; ) { - break; + public static void main(String[] args) { + Boolean b = null; + Boolean b1 = null; + Boolean b2 = null; + Boolean b3 = null; + Boolean b4 = null; + // :: error: (condition.nullable) + if (b) {} + // :: error: (condition.nullable) + b = b1 ? b : b; + // :: error: (condition.nullable) + while (b2) {} + do { + // :: error: (condition.nullable) + } while (b3); + // :: error: (condition.nullable) + for (; b4; ) {} + // legal! + for (; ; ) { + break; + } + // Eliding the condition in a "while" is illegal Java syntax. + // while () {} } - // Eliding the condition in a "while" is illegal Java syntax. - // while () {} - } } diff --git a/checker/tests/nullness/Unboxing.java b/checker/tests/nullness/Unboxing.java index 30682b45b1d..3a8c762a04e 100644 --- a/checker/tests/nullness/Unboxing.java +++ b/checker/tests/nullness/Unboxing.java @@ -2,42 +2,42 @@ public class Unboxing { - @Nullable Integer f; + @Nullable Integer f; - public void t1() { - // :: error: (unboxing.of.nullable) - int l = f + 1; - // no error, since f has been unboxed - f.toString(); - } + public void t1() { + // :: error: (unboxing.of.nullable) + int l = f + 1; + // no error, since f has been unboxed + f.toString(); + } - public void t2() { - try { - // :: error: (unboxing.of.nullable) - int l = f + 1; - } catch (NullPointerException npe) { - // f is known to be null on the exception edge - // :: error: (unboxing.of.nullable) - int m = f + 1; + public void t2() { + try { + // :: error: (unboxing.of.nullable) + int l = f + 1; + } catch (NullPointerException npe) { + // f is known to be null on the exception edge + // :: error: (unboxing.of.nullable) + int m = f + 1; + } + // after the merge, f cannot be null + f.toString(); } - // after the merge, f cannot be null - f.toString(); - } - void foo(@Nullable Integer in) { - // :: error: (unboxing.of.nullable) - int q = in; - } + void foo(@Nullable Integer in) { + // :: error: (unboxing.of.nullable) + int q = in; + } - int bar(@Nullable Integer in) { - // :: error: (unboxing.of.nullable) - return in; - } + int bar(@Nullable Integer in) { + // :: error: (unboxing.of.nullable) + return in; + } - int barT(T in) { - // :: error: (unboxing.of.nullable) - int q = in; - // :: error: (unboxing.of.nullable) - return in; - } + int barT(T in) { + // :: error: (unboxing.of.nullable) + int q = in; + // :: error: (unboxing.of.nullable) + return in; + } } diff --git a/checker/tests/nullness/UnexpectedRaw.java b/checker/tests/nullness/UnexpectedRaw.java index dbcfd040fa8..dfaf44027d6 100644 --- a/checker/tests/nullness/UnexpectedRaw.java +++ b/checker/tests/nullness/UnexpectedRaw.java @@ -1,58 +1,58 @@ import org.checkerframework.checker.nullness.qual.*; interface Consumer { - public void consume(A object); + public void consume(A object); } class Utils { - public static Consumer cast( - final @Nullable Consumer consumer) { - throw new RuntimeException(); - } + public static Consumer cast( + final @Nullable Consumer consumer) { + throw new RuntimeException(); + } - public static Consumer getConsumer() { - // null for simplicity, but could be anything - Consumer<@Nullable Object> nullConsumer = null; + public static Consumer getConsumer() { + // null for simplicity, but could be anything + Consumer<@Nullable Object> nullConsumer = null; - // Previous reasoning for this to generate an (argument.type.incompatible) error was: - // C could be @NonNull Object, so argument is incompatible? - // - // This is poor reasoning, however, because the type of the formal parameter should be: - // @Nullable Consumer< ? [ - // super C[ extends @Nullable Object - // super @NonNull Void - // ] - // extends @Nullable Object - // ] - // The primary annotations on nullConsumer and the formal parameter consumer are - // identical, so it comes down to the annotations on the type arguments. + // Previous reasoning for this to generate an (argument.type.incompatible) error was: + // C could be @NonNull Object, so argument is incompatible? + // + // This is poor reasoning, however, because the type of the formal parameter should be: + // @Nullable Consumer< ? [ + // super C[ extends @Nullable Object + // super @NonNull Void + // ] + // extends @Nullable Object + // ] + // The primary annotations on nullConsumer and the formal parameter consumer are + // identical, so it comes down to the annotations on the type arguments. - // Let X stand in for the type argument of nullConsumer. For it to be a valid parameter, X - // must be contained by the type argument of the formal parameter, ? super C. - // - // In other words, the following constraints must hold: - // - // C1: X <: upper bound of (? super C) - // C2: lower bound of (? super C) <: X - // - // we can simplify these constraints by substituting out the lower and upper bound of - // ? super C. - // C1: X <: @Nullable Object - // C2: C <: X - // - // we can simplify the constraints again by substituting X with the actual type argument to - // nullConsumer and in C2, we can substitute C with its upper bound, since for the - // constraint to hold X must be above C's upper bound. This yields: - // - // C1: @Nullable Object <: @Nullable Object - // C2: @Nullable Object <: @Nullable Object - // - // Since, for all type's T => T <: T, both C1 and C2 are upheld and the following statement - // should NOT report an error - Consumer result = Utils.cast(nullConsumer); + // Let X stand in for the type argument of nullConsumer. For it to be a valid parameter, X + // must be contained by the type argument of the formal parameter, ? super C. + // + // In other words, the following constraints must hold: + // + // C1: X <: upper bound of (? super C) + // C2: lower bound of (? super C) <: X + // + // we can simplify these constraints by substituting out the lower and upper bound of + // ? super C. + // C1: X <: @Nullable Object + // C2: C <: X + // + // we can simplify the constraints again by substituting X with the actual type argument to + // nullConsumer and in C2, we can substitute C with its upper bound, since for the + // constraint to hold X must be above C's upper bound. This yields: + // + // C1: @Nullable Object <: @Nullable Object + // C2: @Nullable Object <: @Nullable Object + // + // Since, for all type's T => T <: T, both C1 and C2 are upheld and the following statement + // should NOT report an error + Consumer result = Utils.cast(nullConsumer); - // on a side note, I am not sure why this is called unexpected raw - return result; - } + // on a side note, I am not sure why this is called unexpected raw + return result; + } } diff --git a/checker/tests/nullness/UnusedNullness.java b/checker/tests/nullness/UnusedNullness.java index b0f3fadd770..e35f8e5d00d 100644 --- a/checker/tests/nullness/UnusedNullness.java +++ b/checker/tests/nullness/UnusedNullness.java @@ -1,9 +1,10 @@ -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.Unused; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + // TODO: feature request: the Nullness Checker should be aware of the @Unused annotation. // This is difficult to implement: one needs to determine the correct AnnotatedTypeFactory for the // "when" type system and use it to determine the right annotated type. We currently don't have a @@ -12,42 +13,42 @@ // @skip-test public class UnusedNullness { - @SubtypeOf({}) - @Target(ElementType.TYPE_USE) - public @interface Prototype {} - - @Unused(when = Prototype.class) - public Object ppt; - - protected @Prototype UnusedNullness() { - // It should be legal to initialize an unused field to null in - // a constructor with @Prototype receiver. - this.ppt = null; - } - - protected @Prototype UnusedNullness(int disambiguate_overloading) { - // It should be legal to NOT initialize an unused field in - // a constructor with @Prototype receiver. - } - - protected void protometh(@Prototype UnusedNullness this) { - // It should be legal to initialize the unused field to null in - // a method with @Prototype receiver. - this.ppt = null; - } - - protected void meth() { - // Otherwise it's not legal. - // :: error: (assignment.type.incompatible) - this.ppt = null; - } - - protected void useUnusedField1(@Prototype UnusedNullness this) { - // :: error: (assignment.type.incompatible) - @NonNull Object x = this.ppt; - } - - protected void useUnusedField2() { - @NonNull Object x = this.ppt; - } + @SubtypeOf({}) + @Target(ElementType.TYPE_USE) + public @interface Prototype {} + + @Unused(when = Prototype.class) + public Object ppt; + + protected @Prototype UnusedNullness() { + // It should be legal to initialize an unused field to null in + // a constructor with @Prototype receiver. + this.ppt = null; + } + + protected @Prototype UnusedNullness(int disambiguate_overloading) { + // It should be legal to NOT initialize an unused field in + // a constructor with @Prototype receiver. + } + + protected void protometh(@Prototype UnusedNullness this) { + // It should be legal to initialize the unused field to null in + // a method with @Prototype receiver. + this.ppt = null; + } + + protected void meth() { + // Otherwise it's not legal. + // :: error: (assignment.type.incompatible) + this.ppt = null; + } + + protected void useUnusedField1(@Prototype UnusedNullness this) { + // :: error: (assignment.type.incompatible) + @NonNull Object x = this.ppt; + } + + protected void useUnusedField2() { + @NonNull Object x = this.ppt; + } } diff --git a/checker/tests/nullness/UnusedOnClass.java b/checker/tests/nullness/UnusedOnClass.java index ec2c73c8db0..26b554089df 100644 --- a/checker/tests/nullness/UnusedOnClass.java +++ b/checker/tests/nullness/UnusedOnClass.java @@ -1,19 +1,20 @@ -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.Unused; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + public final class UnusedOnClass { - public static void read_serialized_pptmap2(@MyNonPrototype MyInvariant2 inv) { - inv.ppt.toString(); - } + public static void read_serialized_pptmap2(@MyNonPrototype MyInvariant2 inv) { + inv.ppt.toString(); + } } @MyPrototype abstract class MyInvariant2 { - @Unused(when = MyPrototype.class) - public String ppt = "hello"; + @Unused(when = MyPrototype.class) + public String ppt = "hello"; } @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/checker/tests/nullness/UtilArrays.java b/checker/tests/nullness/UtilArrays.java index 66b5c31be03..5c9f3e91ad6 100644 --- a/checker/tests/nullness/UtilArrays.java +++ b/checker/tests/nullness/UtilArrays.java @@ -1,51 +1,52 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; // Illustrate various mis-uses of java.util.Arrays. public class UtilArrays { - public static void main(String[] args) { - @Nullable Object[] arrayWithNull = {"", null, ""}; - Object[] arrayWithoutNull = {""}; + public static void main(String[] args) { + @Nullable Object[] arrayWithNull = {"", null, ""}; + Object[] arrayWithoutNull = {""}; - try { - // :: error: (argument.type.incompatible) - Arrays.binarySearch(arrayWithNull, ""); - } catch (NullPointerException e) { - System.out.println("got NPE for array containing null"); - } + try { + // :: error: (argument.type.incompatible) + Arrays.binarySearch(arrayWithNull, ""); + } catch (NullPointerException e) { + System.out.println("got NPE for array containing null"); + } - try { - // :: error: (argument.type.incompatible) - Arrays.binarySearch(arrayWithoutNull, null); - } catch (NullPointerException e) { - System.out.println("got NPE for null key"); - } + try { + // :: error: (argument.type.incompatible) + Arrays.binarySearch(arrayWithoutNull, null); + } catch (NullPointerException e) { + System.out.println("got NPE for null key"); + } - try { - // :: error: (argument.type.incompatible) - Arrays.sort(arrayWithNull, null); - } catch (NullPointerException e) { - System.out.println("got NPE for sort"); - } + try { + // :: error: (argument.type.incompatible) + Arrays.sort(arrayWithNull, null); + } catch (NullPointerException e) { + System.out.println("got NPE for sort"); + } - try { - // TODO: false negative: covariant arrays and polymorphism cause that this call is - // allowed. - Arrays.fill(arrayWithoutNull, null); - arrayWithoutNull[0].toString(); - } catch (NullPointerException e) { - System.out.println("got NPE for fill"); - } + try { + // TODO: false negative: covariant arrays and polymorphism cause that this call is + // allowed. + Arrays.fill(arrayWithoutNull, null); + arrayWithoutNull[0].toString(); + } catch (NullPointerException e) { + System.out.println("got NPE for fill"); + } - try { - // TODO: false negative: covariant arrays and captured argument cause that this call is - // allowed. - List<@Nullable Object> ls = Arrays.asList(arrayWithoutNull); - ls.set(0, null); - arrayWithoutNull[0].toString(); - } catch (NullPointerException e) { - System.out.println("got NPE for asList"); + try { + // TODO: false negative: covariant arrays and captured argument cause that this call is + // allowed. + List<@Nullable Object> ls = Arrays.asList(arrayWithoutNull); + ls.set(0, null); + arrayWithoutNull[0].toString(); + } catch (NullPointerException e) { + System.out.println("got NPE for asList"); + } } - } } diff --git a/checker/tests/nullness/VarargsNullness.java b/checker/tests/nullness/VarargsNullness.java index b0c637780d9..32b1771e177 100644 --- a/checker/tests/nullness/VarargsNullness.java +++ b/checker/tests/nullness/VarargsNullness.java @@ -2,52 +2,52 @@ public class VarargsNullness { - public void test(@NonNull Object @NonNull ... o) { - for (@NonNull Object p : o) { - System.out.println(p); - } - } - - public void test2(Object o1, Object o2) { - System.out.println(o1); - System.out.println(o2); - } - - public void testVarargs() { - test("foo", "bar", "baz"); - } - - public void testVarargsNoArgs() { - test(); - } - - public void testNonVarargs() { - test2("foo", "bar"); - } - - public void format1(java.lang.String a1, java.lang.@Nullable Object... a2) { - int x = a2.length; // no warning - // :: error: (enhancedfor.type.incompatible) - for (@NonNull Object p : a2) // warning - System.out.println(p); - } - - public void format2(java.lang.String a1, java.lang.Object @Nullable ... a2) { - // :: error: (dereference.of.nullable) - int x = a2.length; // warning - for (@NonNull Object p : a2) // no warning - System.out.println(p); - } - - public void testPrintf() { - String s = null; - printf("%s", s); - // tests do not use annotated JDK - // System.out.printf ("%s", s); - } - - // printf declaration is taken from PrintStream - public java.io.PrintStream printf(java.lang.String a1, java.lang.@Nullable Object... a2) { - throw new RuntimeException("skeleton method"); - } + public void test(@NonNull Object @NonNull ... o) { + for (@NonNull Object p : o) { + System.out.println(p); + } + } + + public void test2(Object o1, Object o2) { + System.out.println(o1); + System.out.println(o2); + } + + public void testVarargs() { + test("foo", "bar", "baz"); + } + + public void testVarargsNoArgs() { + test(); + } + + public void testNonVarargs() { + test2("foo", "bar"); + } + + public void format1(java.lang.String a1, java.lang.@Nullable Object... a2) { + int x = a2.length; // no warning + // :: error: (enhancedfor.type.incompatible) + for (@NonNull Object p : a2) // warning + System.out.println(p); + } + + public void format2(java.lang.String a1, java.lang.Object @Nullable ... a2) { + // :: error: (dereference.of.nullable) + int x = a2.length; // warning + for (@NonNull Object p : a2) // no warning + System.out.println(p); + } + + public void testPrintf() { + String s = null; + printf("%s", s); + // tests do not use annotated JDK + // System.out.printf ("%s", s); + } + + // printf declaration is taken from PrintStream + public java.io.PrintStream printf(java.lang.String a1, java.lang.@Nullable Object... a2) { + throw new RuntimeException("skeleton method"); + } } diff --git a/checker/tests/nullness/VarargsNullness2.java b/checker/tests/nullness/VarargsNullness2.java index 2b079ace922..2e9ec028052 100644 --- a/checker/tests/nullness/VarargsNullness2.java +++ b/checker/tests/nullness/VarargsNullness2.java @@ -2,17 +2,17 @@ public class VarargsNullness2 { - public static void method1(Object... args) {} + public static void method1(Object... args) {} - public static void method2(Object @Nullable ... args) {} + public static void method2(Object @Nullable ... args) {} - public static void main(String[] args) { - // :: error: (argument.type.incompatible) - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - method1(null); - // :: warning: non-varargs call of varargs method with inexact argument type for last - // parameter; - method2(null); - } + public static void main(String[] args) { + // :: error: (argument.type.incompatible) + // :: warning: non-varargs call of varargs method with inexact argument type for last + // parameter; + method1(null); + // :: warning: non-varargs call of varargs method with inexact argument type for last + // parameter; + method2(null); + } } diff --git a/checker/tests/nullness/VoidUse.java b/checker/tests/nullness/VoidUse.java index 6b519387b33..ca636bc26b3 100644 --- a/checker/tests/nullness/VoidUse.java +++ b/checker/tests/nullness/VoidUse.java @@ -2,52 +2,52 @@ public class VoidUse { - private Class main_class1 = Void.TYPE; + private Class main_class1 = Void.TYPE; - private Class main_class2 = Void.TYPE; + private Class main_class2 = Void.TYPE; - public Void voidReturn(Void p) { - voidReturn(null); - return null; - } + public Void voidReturn(Void p) { + voidReturn(null); + return null; + } - // Void is treated as Nullable. Is there a value on having it be NonNull? - public abstract static class VoidTestNode {} + // Void is treated as Nullable. Is there a value on having it be NonNull? + public abstract static class VoidTestNode {} - public static class VoidTestInvNode extends VoidTestNode<@NonNull Void> {} + public static class VoidTestInvNode extends VoidTestNode<@NonNull Void> {} - class Scanner

          { - public void scan(Object tree, P p) {} - } + class Scanner

          { + public void scan(Object tree, P p) {} + } - // :: error: (type.argument.type.incompatible) - class MyScanner extends Scanner { - void use(MyScanner ms) { - ms.scan(new Object(), null); + // :: error: (type.argument.type.incompatible) + class MyScanner extends Scanner { + void use(MyScanner ms) { + ms.scan(new Object(), null); + } } - } - // :: error: (type.argument.type.incompatible) - class MyScanner2 extends Scanner<@Nullable Object> { - void use(MyScanner2 ms) { - ms.scan(new Object(), null); + // :: error: (type.argument.type.incompatible) + class MyScanner2 extends Scanner<@Nullable Object> { + void use(MyScanner2 ms) { + ms.scan(new Object(), null); + } } - } - // Test case for issue #230 - Class voidClass() { - return void.class; - } + // Test case for issue #230 + Class voidClass() { + return void.class; + } - Class VoidClass() { - return Void.class; - } + Class VoidClass() { + return Void.class; + } - Class intClass() { - return int.class; - } + Class intClass() { + return int.class; + } - Class ListClass() { - return java.util.List.class; - } + Class ListClass() { + return java.util.List.class; + } } diff --git a/checker/tests/nullness/WeakHasherMapNonNull.java b/checker/tests/nullness/WeakHasherMapNonNull.java index 94deaf9747e..05e1325c065 100644 --- a/checker/tests/nullness/WeakHasherMapNonNull.java +++ b/checker/tests/nullness/WeakHasherMapNonNull.java @@ -1,20 +1,21 @@ -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.initialization.qual.*; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.regex.qual.*; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; + public abstract class WeakHasherMapNonNull extends AbstractMap implements Map { - private Map hash = new HashMap<>(); + private Map hash = new HashMap<>(); - @org.checkerframework.dataflow.qual.Pure - public boolean containsKey(@NonNull Object key) { - // :: warning: [unchecked] unchecked cast - K kkey = (K) key; - // :: error: (argument.type.incompatible) - hash.containsKey(null); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @org.checkerframework.dataflow.qual.Pure + public boolean containsKey(@NonNull Object key) { + // :: warning: [unchecked] unchecked cast + K kkey = (K) key; + // :: error: (argument.type.incompatible) + hash.containsKey(null); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/WeakHasherMapNullable.java b/checker/tests/nullness/WeakHasherMapNullable.java index ee33890cf70..4949ec8021f 100644 --- a/checker/tests/nullness/WeakHasherMapNullable.java +++ b/checker/tests/nullness/WeakHasherMapNullable.java @@ -1,19 +1,20 @@ +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; + import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; public abstract class WeakHasherMapNullable extends AbstractMap implements Map { - private Map hash = new HashMap<>(); + private Map hash = new HashMap<>(); - @Pure - public boolean containsKey(@Nullable Object key) { - // :: warning: [unchecked] unchecked cast - K kkey = (K) key; - // :: error: (argument.type.incompatible) - hash.containsKey(null); - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @Pure + public boolean containsKey(@Nullable Object key) { + // :: warning: [unchecked] unchecked cast + K kkey = (K) key; + // :: error: (argument.type.incompatible) + hash.containsKey(null); + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/checker/tests/nullness/WeakIdentityPair.java b/checker/tests/nullness/WeakIdentityPair.java index 4a16b36189d..d2b608e0c49 100644 --- a/checker/tests/nullness/WeakIdentityPair.java +++ b/checker/tests/nullness/WeakIdentityPair.java @@ -1,15 +1,16 @@ -import java.lang.ref.WeakReference; import org.checkerframework.checker.nullness.qual.*; +import java.lang.ref.WeakReference; + public class WeakIdentityPair { - private final WeakReference a; + private final WeakReference a; - public WeakIdentityPair(T1 a) { - this.a = new WeakReference<>(a); - } + public WeakIdentityPair(T1 a) { + this.a = new WeakReference<>(a); + } - public @Nullable T1 getA() { - return a.get(); - } + public @Nullable T1 getA() { + return a.get(); + } } diff --git a/checker/tests/nullness/WeakRef.java b/checker/tests/nullness/WeakRef.java index 5b7fcddd39f..fc846f494c7 100644 --- a/checker/tests/nullness/WeakRef.java +++ b/checker/tests/nullness/WeakRef.java @@ -1,8 +1,9 @@ -import java.lang.ref.WeakReference; import org.checkerframework.checker.nullness.qual.*; +import java.lang.ref.WeakReference; + public class WeakRef { - @PolyNull Object @Nullable [] foo(WeakReference<@PolyNull Object[]> lookup) { - return lookup.get(); - } + @PolyNull Object @Nullable [] foo(WeakReference<@PolyNull Object[]> lookup) { + return lookup.get(); + } } diff --git a/checker/tests/nullness/WhileTest.java b/checker/tests/nullness/WhileTest.java index 9ddf95e2948..2b21b114e25 100644 --- a/checker/tests/nullness/WhileTest.java +++ b/checker/tests/nullness/WhileTest.java @@ -2,59 +2,59 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class WhileTest { - @Nullable Integer z; - @NonNull Integer nnz = Integer.valueOf(22); + @Nullable Integer z; + @NonNull Integer nnz = Integer.valueOf(22); - public static void main(String[] args) { - new WhileTest().testwhile1(); - } + public static void main(String[] args) { + new WhileTest().testwhile1(); + } - public void testwhile1() { - z = null; - // :: error: (assignment.type.incompatible) - nnz = z; + public void testwhile1() { + z = null; + // :: error: (assignment.type.incompatible) + nnz = z; + + while (z == null) { + break; + } + // :: error: (assignment.type.incompatible) + nnz = z; + nnz.toString(); + } - while (z == null) { - break; + public void testwhile2() { + z = null; + while (z == null) {} + nnz = z; } - // :: error: (assignment.type.incompatible) - nnz = z; - nnz.toString(); - } - - public void testwhile2() { - z = null; - while (z == null) {} - nnz = z; - } - - public void testdo1() { - z = null; - do { - break; - } while (z == null); - // :: error: (assignment.type.incompatible) - nnz = z; - } - - public void testdo2() { - z = null; - do {} while (z == null); - nnz = z; - } - - public void testfor1() { - z = null; - for (; z == null; ) { - break; + + public void testdo1() { + z = null; + do { + break; + } while (z == null); + // :: error: (assignment.type.incompatible) + nnz = z; + } + + public void testdo2() { + z = null; + do {} while (z == null); + nnz = z; + } + + public void testfor1() { + z = null; + for (; z == null; ) { + break; + } + // :: error: (assignment.type.incompatible) + nnz = z; + } + + public void testfor2() { + z = null; + for (; z == null; ) {} + nnz = z; } - // :: error: (assignment.type.incompatible) - nnz = z; - } - - public void testfor2() { - z = null; - for (; z == null; ) {} - nnz = z; - } } diff --git a/checker/tests/nullness/Widening.java b/checker/tests/nullness/Widening.java index 53f51e33e7e..4da7df66ecf 100644 --- a/checker/tests/nullness/Widening.java +++ b/checker/tests/nullness/Widening.java @@ -1,11 +1,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Widening { - @Nullable Integer i; + @Nullable Integer i; - void inc(long amt) {} + void inc(long amt) {} - void foo() { - inc(i == null ? 0 : i); - } + void foo() { + inc(i == null ? 0 : i); + } } diff --git a/checker/tests/nullness/WildcardGLB.java b/checker/tests/nullness/WildcardGLB.java index 3dfa129f63d..0d7634a29da 100644 --- a/checker/tests/nullness/WildcardGLB.java +++ b/checker/tests/nullness/WildcardGLB.java @@ -1,66 +1,67 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; public class WildcardGLB { - static class MyClass> { - E getE() { - throw new RuntimeException(); + static class MyClass> { + E getE() { + throw new RuntimeException(); + } } - } - // The captured type variable for - // ? extends @List<@NonNull String> - // is - // capture#865 extends @NonNull List<@Nullable String> - // . The upper bound of the captured type variable is not a subtype of the extends bound of the - // wildcard because the glb of the type parameter bound and the wildcard extends bound does not - // exist. I don't think this leads to unsoundness, but it makes it so that this method can't be - // called without an error. The method testUse below demos this. - // :: error: (type.argument.type.incompatible) - void use(MyClass> s) { - // :: error: (assignment.type.incompatible) - List f = s.getE(); - List<@Nullable String> f2 = s.getE(); - } + // The captured type variable for + // ? extends @List<@NonNull String> + // is + // capture#865 extends @NonNull List<@Nullable String> + // . The upper bound of the captured type variable is not a subtype of the extends bound of the + // wildcard because the glb of the type parameter bound and the wildcard extends bound does not + // exist. I don't think this leads to unsoundness, but it makes it so that this method can't be + // called without an error. The method testUse below demos this. + // :: error: (type.argument.type.incompatible) + void use(MyClass> s) { + // :: error: (assignment.type.incompatible) + List f = s.getE(); + List<@Nullable String> f2 = s.getE(); + } - void testUse( - // :: error: (type.argument.type.incompatible) - MyClass> p1, - // A comment to force a line break. - MyClass> p2) { - use(p1); - // :: error: (argument.type.incompatible) - use(p2); - } + void testUse( + // :: error: (type.argument.type.incompatible) + MyClass> p1, + // A comment to force a line break. + MyClass> p2) { + use(p1); + // :: error: (argument.type.incompatible) + use(p2); + } - // capture#196 extends @NonNull ArrayList<@NonNull String> - // :: error: (type.argument.type.incompatible) - void use2(MyClass> s) { // error: type.argument - List f = s.getE(); - // :: error: (assignment.type.incompatible) - List<@Nullable String> f2 = s.getE(); // error: assignment - } + // capture#196 extends @NonNull ArrayList<@NonNull String> + // :: error: (type.argument.type.incompatible) + void use2(MyClass> s) { // error: type.argument + List f = s.getE(); + // :: error: (assignment.type.incompatible) + List<@Nullable String> f2 = s.getE(); // error: assignment + } - static class MyClass2> { - E getE() { - throw new RuntimeException(); + static class MyClass2> { + E getE() { + throw new RuntimeException(); + } } - } - // capture#952 extends @NonNull ArrayList<@Nullable String> - // :: error: (type.argument.type.incompatible) - void use3(MyClass2> s) { - // :: error: (assignment.type.incompatible) - List f = s.getE(); - List<@Nullable String> f2 = s.getE(); - } + // capture#952 extends @NonNull ArrayList<@Nullable String> + // :: error: (type.argument.type.incompatible) + void use3(MyClass2> s) { + // :: error: (assignment.type.incompatible) + List f = s.getE(); + List<@Nullable String> f2 = s.getE(); + } - // :: error: (type.argument.type.incompatible) - void use4(MyClass2> s) { - // :: error: (assignment.type.incompatible) - List f = s.getE(); - List<@Nullable String> f2 = s.getE(); // ok - } + // :: error: (type.argument.type.incompatible) + void use4(MyClass2> s) { + // :: error: (assignment.type.incompatible) + List f = s.getE(); + List<@Nullable String> f2 = s.getE(); // ok + } } diff --git a/checker/tests/nullness/WildcardSubtype.java b/checker/tests/nullness/WildcardSubtype.java index 9bc4bf1f598..66fea84d5c8 100644 --- a/checker/tests/nullness/WildcardSubtype.java +++ b/checker/tests/nullness/WildcardSubtype.java @@ -2,53 +2,53 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class WildcardSubtype { - class MyClass {} + class MyClass {} - class Visitor { - String visit(T p) { - return ""; + class Visitor { + String visit(T p) { + return ""; + } + } + + class MyClassVisitor extends Visitor<@Nullable MyClass> {} + + class NonNullMyClassVisitor extends Visitor<@NonNull MyClass> {} + + void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object>()); + + Visitor visitor1 = myClassVisitor; + Visitor visitor2 = nonNullMyClassVisitor; + + // :: error: (assignment.type.incompatible) + Visitor visitor3 = myClassVisitor; + Visitor visitor4 = nonNullMyClassVisitor; + + // :: error: (assignment.type.incompatible) + Visitor visitor5 = new MyClassVisitor(); + // :: error: (assignment.type.incompatible) + Visitor visitor6 = new MyClassVisitor(); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void take(Visitor<@NonNull ? extends @NonNull Object> v) {} + + void bar() { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void baz() { + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + take(new NonNullMyClassVisitor()); } - } - - class MyClassVisitor extends Visitor<@Nullable MyClass> {} - - class NonNullMyClassVisitor extends Visitor<@NonNull MyClass> {} - - void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object>()); - - Visitor visitor1 = myClassVisitor; - Visitor visitor2 = nonNullMyClassVisitor; - - // :: error: (assignment.type.incompatible) - Visitor visitor3 = myClassVisitor; - Visitor visitor4 = nonNullMyClassVisitor; - - // :: error: (assignment.type.incompatible) - Visitor visitor5 = new MyClassVisitor(); - // :: error: (assignment.type.incompatible) - Visitor visitor6 = new MyClassVisitor(); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } - - void take(Visitor<@NonNull ? extends @NonNull Object> v) {} - - void bar() { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } - - void baz() { - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - take(new NonNullMyClassVisitor()); - } } diff --git a/checker/tests/nullness/WildcardSubtype2.java b/checker/tests/nullness/WildcardSubtype2.java index c0f5802de64..77e1eb0717c 100644 --- a/checker/tests/nullness/WildcardSubtype2.java +++ b/checker/tests/nullness/WildcardSubtype2.java @@ -2,54 +2,55 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class WildcardSubtype2 { - class MyClass {} + class MyClass {} - class Visitor { - String visit(T p) { - return ""; + class Visitor { + String visit(T p) { + return ""; + } } - } - class MyClassVisitor extends Visitor<@Nullable MyClass, @Nullable MyClass> {} + class MyClassVisitor extends Visitor<@Nullable MyClass, @Nullable MyClass> {} - class NonNullMyClassVisitor extends Visitor<@NonNull MyClass, @NonNull MyClass> {} + class NonNullMyClassVisitor extends Visitor<@NonNull MyClass, @NonNull MyClass> {} - void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object, @Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object, @Nullable Object>()); - Visitor visitor1 = myClassVisitor; - Visitor visitor2 = nonNullMyClassVisitor; + void test(MyClassVisitor myClassVisitor, NonNullMyClassVisitor nonNullMyClassVisitor) { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object, @Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object, @Nullable Object>()); + Visitor visitor1 = myClassVisitor; + Visitor visitor2 = nonNullMyClassVisitor; - // :: error: (assignment.type.incompatible) - Visitor visitor3 = myClassVisitor; - Visitor visitor4 = nonNullMyClassVisitor; - - Visitor visitor5 = - // :: error: (assignment.type.incompatible) - new MyClassVisitor(); - Visitor visitor6 = // :: error: (assignment.type.incompatible) - new MyClassVisitor(); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } - - void take(Visitor<@NonNull ? extends @NonNull Object, @NonNull ? extends @NonNull Object> v) {} - - void bar() { - // :: error: (argument.type.incompatible) - take(new Visitor<@Nullable Object, @Nullable Object>()); - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - } - - void baz() { - // :: error: (argument.type.incompatible) - take(new MyClassVisitor()); - take(new NonNullMyClassVisitor()); - } + Visitor visitor3 = myClassVisitor; + Visitor visitor4 = + nonNullMyClassVisitor; + + Visitor visitor5 = + // :: error: (assignment.type.incompatible) + new MyClassVisitor(); + Visitor visitor6 = + // :: error: (assignment.type.incompatible) + new MyClassVisitor(); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void take(Visitor<@NonNull ? extends @NonNull Object, @NonNull ? extends @NonNull Object> v) {} + + void bar() { + // :: error: (argument.type.incompatible) + take(new Visitor<@Nullable Object, @Nullable Object>()); + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + } + + void baz() { + // :: error: (argument.type.incompatible) + take(new MyClassVisitor()); + take(new NonNullMyClassVisitor()); + } } diff --git a/checker/tests/nullness/Wildcards.java b/checker/tests/nullness/Wildcards.java index 5ee09b86497..54dd0cef5d9 100644 --- a/checker/tests/nullness/Wildcards.java +++ b/checker/tests/nullness/Wildcards.java @@ -1,37 +1,38 @@ // Test case for issue #234. +import org.checkerframework.checker.nullness.qual.*; + import java.util.Iterator; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; public class Wildcards { - public static void client1(List strings) { - join1(strings.iterator()); - join2(strings.iterator()); - join3(strings.iterator()); - join4(strings.iterator()); - } + public static void client1(List strings) { + join1(strings.iterator()); + join2(strings.iterator()); + join3(strings.iterator()); + join4(strings.iterator()); + } - public static void client2(Iterator itor) { - join1(itor); - join2(itor); - join3(itor); - join4(itor); - } + public static void client2(Iterator itor) { + join1(itor); + join2(itor); + join3(itor); + join4(itor); + } - public static void client3(Iterator itor) { - Iterator parts1 = itor; - Iterator parts2 = itor; - Iterator parts3 = itor; - Iterator parts4 = itor; - } + public static void client3(Iterator itor) { + Iterator parts1 = itor; + Iterator parts2 = itor; + Iterator parts3 = itor; + Iterator parts4 = itor; + } - static void join1(Iterator parts) {} + static void join1(Iterator parts) {} - static void join2(Iterator parts) {} + static void join2(Iterator parts) {} - static void join3(Iterator parts) {} + static void join3(Iterator parts) {} - static void join4(Iterator parts) {} + static void join4(Iterator parts) {} } diff --git a/checker/tests/nullness/ZeroVarargs.java b/checker/tests/nullness/ZeroVarargs.java index 92654c7016a..42d8ea7d359 100644 --- a/checker/tests/nullness/ZeroVarargs.java +++ b/checker/tests/nullness/ZeroVarargs.java @@ -1,7 +1,7 @@ // Test case for https://tinyurl.com/cfissue/5189 enum CFRepro { - EMPTY() {}; + EMPTY() {}; - CFRepro(String... args) {} + CFRepro(String... args) {} } diff --git a/checker/tests/nullness/flow/EisopIssue300.java b/checker/tests/nullness/flow/EisopIssue300.java index 36b661eff32..097f5c870e1 100644 --- a/checker/tests/nullness/flow/EisopIssue300.java +++ b/checker/tests/nullness/flow/EisopIssue300.java @@ -4,24 +4,24 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class EisopIssue300 { - class Bug { - void setFieldNull(Bug b) { - EisopIssue300.this.currentNode = null; + class Bug { + void setFieldNull(Bug b) { + EisopIssue300.this.currentNode = null; + } } - } - @Nullable Bug currentNode = new Bug(); + @Nullable Bug currentNode = new Bug(); - void test() { - if (currentNode == null) { - return; + void test() { + if (currentNode == null) { + return; + } + currentNode.setFieldNull(currentNode); + // :: error: (dereference.of.nullable) + currentNode.toString(); } - currentNode.setFieldNull(currentNode); - // :: error: (dereference.of.nullable) - currentNode.toString(); - } - public static void main(String[] args) { - new EisopIssue300().test(); - } + public static void main(String[] args) { + new EisopIssue300().test(); + } } diff --git a/checker/tests/nullness/flow/EisopIssue300B.java b/checker/tests/nullness/flow/EisopIssue300B.java index d5c9aab6318..704f8303437 100644 --- a/checker/tests/nullness/flow/EisopIssue300B.java +++ b/checker/tests/nullness/flow/EisopIssue300B.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class EisopIssue300B { - @Nullable Object f = ""; + @Nullable Object f = ""; - void m(Object o) { - f = null; - } - - public static void main(String[] args) { - EisopIssue300B r = new EisopIssue300B(); - if (r.f == null) { - return; + void m(Object o) { + f = null; } - r.m(r.f); - // :: error: (dereference.of.nullable) - r.f.toString(); - } + public static void main(String[] args) { + EisopIssue300B r = new EisopIssue300B(); + if (r.f == null) { + return; + } + + r.m(r.f); + // :: error: (dereference.of.nullable) + r.f.toString(); + } } diff --git a/checker/tests/nullness/flow/EisopIssue300C.java b/checker/tests/nullness/flow/EisopIssue300C.java index eaf66f9863c..9c64deeabde 100644 --- a/checker/tests/nullness/flow/EisopIssue300C.java +++ b/checker/tests/nullness/flow/EisopIssue300C.java @@ -6,28 +6,28 @@ import org.checkerframework.dataflow.qual.*; public final class EisopIssue300C { - @NotOnlyInitialized @Nullable EisopIssue300C f; + @NotOnlyInitialized @Nullable EisopIssue300C f; - EisopIssue300C() { - this.f = this; - } + EisopIssue300C() { + this.f = this; + } - void m2() { - f = null; - } + void m2() { + f = null; + } - @Pure - @Nullable EisopIssue300C getF() { - return f; - } + @Pure + @Nullable EisopIssue300C getF() { + return f; + } - public static void main(String[] args) { - EisopIssue300C r = new EisopIssue300C(); + public static void main(String[] args) { + EisopIssue300C r = new EisopIssue300C(); - if (r.getF() != null) { - r.getF().m2(); - // :: error: (dereference.of.nullable) - r.getF().toString(); + if (r.getF() != null) { + r.getF().m2(); + // :: error: (dereference.of.nullable) + r.getF().toString(); + } } - } } diff --git a/checker/tests/nullness/flow/EisopIssue553.java b/checker/tests/nullness/flow/EisopIssue553.java index 24ce4ec4f20..acdf33bd716 100644 --- a/checker/tests/nullness/flow/EisopIssue553.java +++ b/checker/tests/nullness/flow/EisopIssue553.java @@ -3,23 +3,23 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class EisopIssue553 { - static @Nullable Object sfield = ""; - Object field = ""; + static @Nullable Object sfield = ""; + Object field = ""; - static void n(Object o) { - sfield = null; - } + static void n(Object o) { + sfield = null; + } - public static void main(String[] args) { - EisopIssue553 x = null; - Object o = x.sfield; - // :: error: (dereference.of.nullable) - o = x.field; - if (x.sfield == null) { - return; + public static void main(String[] args) { + EisopIssue553 x = null; + Object o = x.sfield; + // :: error: (dereference.of.nullable) + o = x.field; + if (x.sfield == null) { + return; + } + x.n(x.sfield); + // :: error: (dereference.of.nullable) + x.sfield.toString(); } - x.n(x.sfield); - // :: error: (dereference.of.nullable) - x.sfield.toString(); - } } diff --git a/checker/tests/nullness/flow/Issue1214.java b/checker/tests/nullness/flow/Issue1214.java index 1b12713a21f..81b461f5edd 100644 --- a/checker/tests/nullness/flow/Issue1214.java +++ b/checker/tests/nullness/flow/Issue1214.java @@ -2,125 +2,125 @@ // https://github.com/typetools/checker-framework/issues/1214 public class Issue1214 { - static String ng1() { - String s = "not null"; - try { - int data = 50 / 0; - } catch (Exception e) { - s = null; + static String ng1() { + String s = "not null"; + try { + int data = 50 / 0; + } catch (Exception e) { + s = null; + } + // :: error: (return.type.incompatible) + return s; } - // :: error: (return.type.incompatible) - return s; - } - static String ng2(int x) { - String s = "not null"; - try { - short data = (short) (50 / x); - } catch (Exception e) { - try { - s = null; - } catch (Exception ee) { - } + static String ng2(int x) { + String s = "not null"; + try { + short data = (short) (50 / x); + } catch (Exception e) { + try { + s = null; + } catch (Exception ee) { + } + } + // :: error: (return.type.incompatible) + return s; } - // :: error: (return.type.incompatible) - return s; - } - static String ng3() { - String s = "not null"; - try { - int data = 50 % 0; - } catch (Exception e) { - try { - // some statements... - } catch (Exception ee) { - } finally { - s = null; - } + static String ng3() { + String s = "not null"; + try { + int data = 50 % 0; + } catch (Exception e) { + try { + // some statements... + } catch (Exception ee) { + } finally { + s = null; + } + } + // :: error: (return.type.incompatible) + return s; } - // :: error: (return.type.incompatible) - return s; - } - static String ng4(int data) { - String s = "not null"; - try { - data /= 0; - } catch (Exception e) { - s = null; + static String ng4(int data) { + String s = "not null"; + try { + data /= 0; + } catch (Exception e) { + s = null; + } + // :: error: (return.type.incompatible) + return s; } - // :: error: (return.type.incompatible) - return s; - } - static String ng5(short data) { - String s = "not null"; - try { - data /= 0; - } catch (Exception e) { - try { - s = null; - } catch (Exception ee) { - } + static String ng5(short data) { + String s = "not null"; + try { + data /= 0; + } catch (Exception e) { + try { + s = null; + } catch (Exception ee) { + } + } + // :: error: (return.type.incompatible) + return s; } - // :: error: (return.type.incompatible) - return s; - } - static String ng6(int data) { - String s = "not null"; - try { - data %= 0; - } catch (Exception e) { - try { - // some statements... - } catch (Exception ee) { - } finally { - s = null; - } + static String ng6(int data) { + String s = "not null"; + try { + data %= 0; + } catch (Exception e) { + try { + // some statements... + } catch (Exception ee) { + } finally { + s = null; + } + } + // :: error: (return.type.incompatible) + return s; } - // :: error: (return.type.incompatible) - return s; - } - static String ok1() { - String s = "not null"; - try { - double data = 50 / 0.0; - } catch (Exception e) { - s = null; + static String ok1() { + String s = "not null"; + try { + double data = 50 / 0.0; + } catch (Exception e) { + s = null; + } + return s; } - return s; - } - static String ok2() { - String s = "not null"; - try { - double data = 50 % 0.0; - } catch (Exception e) { - s = null; + static String ok2() { + String s = "not null"; + try { + double data = 50 % 0.0; + } catch (Exception e) { + s = null; + } + return s; } - return s; - } - static String ok3(double data) { - String s = "not null"; - try { - data /= 0; - } catch (Exception e) { - s = null; + static String ok3(double data) { + String s = "not null"; + try { + data /= 0; + } catch (Exception e) { + s = null; + } + return s; } - return s; - } - static String ok4(float data) { - String s = "not null"; - try { - data %= 0; - } catch (Exception e) { - s = null; + static String ok4(float data) { + String s = "not null"; + try { + data %= 0; + } catch (Exception e) { + s = null; + } + return s; } - return s; - } } diff --git a/checker/tests/nullness/flow/Issue1345.java b/checker/tests/nullness/flow/Issue1345.java index 1dcb1548713..859f31e13bc 100644 --- a/checker/tests/nullness/flow/Issue1345.java +++ b/checker/tests/nullness/flow/Issue1345.java @@ -3,23 +3,24 @@ // @skip-test until the issue is resolved -import java.math.BigDecimal; -import java.util.stream.Stream; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.util.Opt; +import java.math.BigDecimal; +import java.util.stream.Stream; + public class Issue1345 { - @EnsuresNonNullIf(expression = "#1", result = true) - static boolean isNonNull(@Nullable Object o) { - return o != null; - } + @EnsuresNonNullIf(expression = "#1", result = true) + static boolean isNonNull(@Nullable Object o) { + return o != null; + } - void filterPresent_Optional(Stream<@Nullable BigDecimal> s) { - Stream<@NonNull BigDecimal> filtered = s.filter(Issue1345::isNonNull); - } + void filterPresent_Optional(Stream<@Nullable BigDecimal> s) { + Stream<@NonNull BigDecimal> filtered = s.filter(Issue1345::isNonNull); + } - void filterPresent_Opt(@Nullable Object p) { - @NonNull Object o = Opt.filter(p, Opt::isPresent); - } + void filterPresent_Opt(@Nullable Object p) { + @NonNull Object o = Opt.filter(p, Opt::isPresent); + } } diff --git a/checker/tests/nullness/flow/Issue1727.java b/checker/tests/nullness/flow/Issue1727.java index 8a6dc26b744..bf730923992 100644 --- a/checker/tests/nullness/flow/Issue1727.java +++ b/checker/tests/nullness/flow/Issue1727.java @@ -7,25 +7,25 @@ class B {} public class Issue1727 { - private B foo() { - // Default type for local variable b is @UnknownInitialization @Nullable - B b; + private B foo() { + // Default type for local variable b is @UnknownInitialization @Nullable + B b; - while (true) { - B op = getB(); - if (op == null) { - b = new B(); - break; - } else { - b = op; - break; - } - } + while (true) { + B op = getB(); + if (op == null) { + b = new B(); + break; + } else { + b = op; + break; + } + } - return b; - } + return b; + } - private @Nullable B getB() { - return new B(); - } + private @Nullable B getB() { + return new B(); + } } diff --git a/checker/tests/nullness/flow/Issue3249.java b/checker/tests/nullness/flow/Issue3249.java index 7baf899d4d9..d3ca0f5c81d 100644 --- a/checker/tests/nullness/flow/Issue3249.java +++ b/checker/tests/nullness/flow/Issue3249.java @@ -3,62 +3,62 @@ public class Issue3249 { - private final double field; + private final double field; - Issue3249() { - double local; - while (true) { - local = 1; - break; + Issue3249() { + double local; + while (true) { + local = 1; + break; + } + field = local; } - field = local; - } - Issue3249(int x) { - double local; - while (!false) { - local = 1; - break; + Issue3249(int x) { + double local; + while (!false) { + local = 1; + break; + } + field = local; } - field = local; - } - Issue3249(float x) { - double local; - while (true || x > 0) { - local = 1; - break; + Issue3249(float x) { + double local; + while (true || x > 0) { + local = 1; + break; + } + field = local; } - field = local; - } - Issue3249(double x) { - double local; - while (!false && true && !false) { - local = 1; - break; + Issue3249(double x) { + double local; + while (!false && true && !false) { + local = 1; + break; + } + field = local; } - field = local; - } - // Case for while conditions that contain final variables, - // which are treated as constant. - Issue3249(String x) { - double local; - final int i = 1; - while ((i > 0) && !false) { - local = 1; - break; + // Case for while conditions that contain final variables, + // which are treated as constant. + Issue3249(String x) { + double local; + final int i = 1; + while ((i > 0) && !false) { + local = 1; + break; + } + field = local; } - field = local; - } - Issue3249(boolean x) { - double local; - while (6 > 4) { - local = 1; - break; + Issue3249(boolean x) { + double local; + while (6 > 4) { + local = 1; + break; + } + field = local; } - field = local; - } } diff --git a/checker/tests/nullness/flow/Issue3267.java b/checker/tests/nullness/flow/Issue3267.java index fac6ccd9f8e..57b392e9b8b 100644 --- a/checker/tests/nullness/flow/Issue3267.java +++ b/checker/tests/nullness/flow/Issue3267.java @@ -4,36 +4,36 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3267 { - void m1(@Nullable Object obj) { - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); + void m1(@Nullable Object obj) { + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void m2(@Nullable Object obj) { - if (obj != null) {} - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); + void m2(@Nullable Object obj) { + if (obj != null) {} + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void m3(@Nullable Object obj) { - if (obj != null) { - } else { + void m3(@Nullable Object obj) { + if (obj != null) { + } else { + } + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); - } - } - void m4(@Nullable Object obj) { - boolean bool = obj != null; - if (true) { - // :: error: (dereference.of.nullable) - obj.toString(); + void m4(@Nullable Object obj) { + boolean bool = obj != null; + if (true) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } } diff --git a/checker/tests/nullness/flow/Issue3275.java b/checker/tests/nullness/flow/Issue3275.java index 4809fd31fe2..05b615b457f 100644 --- a/checker/tests/nullness/flow/Issue3275.java +++ b/checker/tests/nullness/flow/Issue3275.java @@ -5,202 +5,202 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue3275 { - public @NonNull Object f = new Object(); - public boolean b = false; + public @NonNull Object f = new Object(); + public boolean b = false; - void return_n(@Nullable Object obj) { - if (obj != null) { - obj.toString(); + void return_n(@Nullable Object obj) { + if (obj != null) { + obj.toString(); + } } - } - void return_np(@Nullable Object obj) { - if ((obj != null)) { - obj.toString(); + void return_np(@Nullable Object obj) { + if ((obj != null)) { + obj.toString(); + } } - } - void return_en(@Nullable Object obj) { - if (!(obj == null)) { - obj.toString(); + void return_en(@Nullable Object obj) { + if (!(obj == null)) { + obj.toString(); + } } - } - void return_eet(@Nullable Object obj) { - if ((obj == null) == true) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_eet(@Nullable Object obj) { + if ((obj == null) == true) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void return_eef(@Nullable Object obj) { - if ((obj == null) == false) { - obj.toString(); + void return_eef(@Nullable Object obj) { + if ((obj == null) == false) { + obj.toString(); + } } - } - void return_eeb(@Nullable Object obj) { - if ((obj == null) == b) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_eeb(@Nullable Object obj) { + if ((obj == null) == b) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void return_ent(@Nullable Object obj) { - if ((obj == null) != true) { - obj.toString(); + void return_ent(@Nullable Object obj) { + if ((obj == null) != true) { + obj.toString(); + } } - } - void return_enf(@Nullable Object obj) { - if ((obj == null) != false) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_enf(@Nullable Object obj) { + if ((obj == null) != false) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void return_enb(@Nullable Object obj) { - if ((obj == null) != b) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_enb(@Nullable Object obj) { + if ((obj == null) != b) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void return_net(@Nullable Object obj) { - if ((obj != null) == true) { - obj.toString(); + void return_net(@Nullable Object obj) { + if ((obj != null) == true) { + obj.toString(); + } } - } - void return_nef(@Nullable Object obj) { - if ((obj != null) == false) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_nef(@Nullable Object obj) { + if ((obj != null) == false) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void return_neb(@Nullable Object obj) { - if ((obj != null) == b) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_neb(@Nullable Object obj) { + if ((obj != null) == b) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void return_nnt(@Nullable Object obj) { - if ((obj != null) != true) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_nnt(@Nullable Object obj) { + if ((obj != null) != true) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void return_nnf(@Nullable Object obj) { - if ((obj != null) != false) { - obj.toString(); + void return_nnf(@Nullable Object obj) { + if ((obj != null) != false) { + obj.toString(); + } } - } - void return_nnb(@Nullable Object obj) { - if ((obj != null) != b) { - // :: error: (dereference.of.nullable) - obj.toString(); + void return_nnb(@Nullable Object obj) { + if ((obj != null) != b) { + // :: error: (dereference.of.nullable) + obj.toString(); + } } - } - void assign_n(@Nullable Object obj) { - if (obj != null) { - f = obj; + void assign_n(@Nullable Object obj) { + if (obj != null) { + f = obj; + } } - } - void assign_np(@Nullable Object obj) { - if ((obj != null)) { - f = obj; + void assign_np(@Nullable Object obj) { + if ((obj != null)) { + f = obj; + } } - } - void assign_en(@Nullable Object obj) { - if (!(obj == null)) { - f = obj; + void assign_en(@Nullable Object obj) { + if (!(obj == null)) { + f = obj; + } } - } - void assign_eet(@Nullable Object obj) { - if ((obj == null) == true) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_eet(@Nullable Object obj) { + if ((obj == null) == true) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } - void assign_eef(@Nullable Object obj) { - if ((obj == null) == false) { - f = obj; + void assign_eef(@Nullable Object obj) { + if ((obj == null) == false) { + f = obj; + } } - } - void assign_eeb(@Nullable Object obj) { - if ((obj == null) == b) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_eeb(@Nullable Object obj) { + if ((obj == null) == b) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } - void assign_ent(@Nullable Object obj) { - if ((obj == null) != true) { - f = obj; + void assign_ent(@Nullable Object obj) { + if ((obj == null) != true) { + f = obj; + } } - } - void assign_enf(@Nullable Object obj) { - if ((obj == null) != false) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_enf(@Nullable Object obj) { + if ((obj == null) != false) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } - void assign_enb(@Nullable Object obj) { - if ((obj == null) != b) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_enb(@Nullable Object obj) { + if ((obj == null) != b) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } - void assign_net(@Nullable Object obj) { - if ((obj != null) == true) { - f = obj; + void assign_net(@Nullable Object obj) { + if ((obj != null) == true) { + f = obj; + } } - } - void assign_nef(@Nullable Object obj) { - if ((obj != null) == false) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_nef(@Nullable Object obj) { + if ((obj != null) == false) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } - void assign_neb(@Nullable Object obj) { - if ((obj != null) == b) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_neb(@Nullable Object obj) { + if ((obj != null) == b) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } - void assign_nnt(@Nullable Object obj) { - if ((obj != null) != true) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_nnt(@Nullable Object obj) { + if ((obj != null) != true) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } - void assign_nnf(@Nullable Object obj) { - if ((obj != null) != false) { - f = obj; + void assign_nnf(@Nullable Object obj) { + if ((obj != null) != false) { + f = obj; + } } - } - void assign_nnb(@Nullable Object obj) { - if ((obj != null) != b) { - // :: error: (assignment.type.incompatible) - f = obj; + void assign_nnb(@Nullable Object obj) { + if ((obj != null) != b) { + // :: error: (assignment.type.incompatible) + f = obj; + } } - } } diff --git a/checker/tests/nullness/flow/Issue341.java b/checker/tests/nullness/flow/Issue341.java index ce96a4d3acc..a22df59fe93 100644 --- a/checker/tests/nullness/flow/Issue341.java +++ b/checker/tests/nullness/flow/Issue341.java @@ -3,16 +3,16 @@ public class Issue341 { - static class Provider { - public final Object get = new Object(); - } + static class Provider { + public final Object get = new Object(); + } - Object execute(Provider p) { - final Object result; - try { - result = p.get; - } finally { + Object execute(Provider p) { + final Object result; + try { + result = p.get; + } finally { + } + return result; } - return result; - } } diff --git a/checker/tests/nullness/flow/Issue818.java b/checker/tests/nullness/flow/Issue818.java index d0ac6e414bb..7005fa925ce 100644 --- a/checker/tests/nullness/flow/Issue818.java +++ b/checker/tests/nullness/flow/Issue818.java @@ -4,69 +4,69 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue818 { - public static @Nullable Object o = null; + public static @Nullable Object o = null; - void method() { - Issue818.o = new Object(); - o.toString(); - } - - void method2() { - o = new Object(); - Issue818.o.toString(); - } - - void method3() { - o = new Object(); - o.toString(); - } - - void method4() { - Issue818.o = new Object(); - Issue818.o.toString(); - } - - static class StaticInnerClass { void method() { - Issue818.o = new Object(); - o.toString(); + Issue818.o = new Object(); + o.toString(); } void method2() { - o = new Object(); - Issue818.o.toString(); + o = new Object(); + Issue818.o.toString(); } void method3() { - o = new Object(); - o.toString(); + o = new Object(); + o.toString(); } void method4() { - Issue818.o = new Object(); - Issue818.o.toString(); + Issue818.o = new Object(); + Issue818.o.toString(); } - } - class NonStaticInnerClass { - void method() { - Issue818.o = new Object(); - o.toString(); - } + static class StaticInnerClass { + void method() { + Issue818.o = new Object(); + o.toString(); + } - void method2() { - o = new Object(); - Issue818.o.toString(); - } + void method2() { + o = new Object(); + Issue818.o.toString(); + } - void method3() { - o = new Object(); - o.toString(); + void method3() { + o = new Object(); + o.toString(); + } + + void method4() { + Issue818.o = new Object(); + Issue818.o.toString(); + } } - void method4() { - Issue818.o = new Object(); - Issue818.o.toString(); + class NonStaticInnerClass { + void method() { + Issue818.o = new Object(); + o.toString(); + } + + void method2() { + o = new Object(); + Issue818.o.toString(); + } + + void method3() { + o = new Object(); + o.toString(); + } + + void method4() { + Issue818.o = new Object(); + Issue818.o.toString(); + } } - } } diff --git a/checker/tests/nullness/flow/MapGet.java b/checker/tests/nullness/flow/MapGet.java index f66def0ebe9..7d74f678ea3 100644 --- a/checker/tests/nullness/flow/MapGet.java +++ b/checker/tests/nullness/flow/MapGet.java @@ -3,26 +3,27 @@ // @skip-test until the issue is fixed -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.HashMap; +import java.util.Map; + public class MapGet { - private final Map labels = new HashMap<>(); + private final Map labels = new HashMap<>(); - void foo1(String v) { - labels.put(v, ""); - labels.get(v).toString(); - } + void foo1(String v) { + labels.put(v, ""); + labels.get(v).toString(); + } - @NonNull String foo2(String v) { - labels.put(v, ""); - return labels.get(v); - } + @NonNull String foo2(String v) { + labels.put(v, ""); + return labels.get(v); + } - @EnsuresNonNull("labels.get(#1)") - void foo3(String v) { - labels.put(v, ""); - } + @EnsuresNonNull("labels.get(#1)") + void foo3(String v) { + labels.put(v, ""); + } } diff --git a/checker/tests/nullness/flow/PathJoins.java b/checker/tests/nullness/flow/PathJoins.java index 7e8c88a6b2b..305a7faa028 100644 --- a/checker/tests/nullness/flow/PathJoins.java +++ b/checker/tests/nullness/flow/PathJoins.java @@ -2,16 +2,16 @@ public class PathJoins { - public void testJoiningMultipleBranches() { - Object intersect = null; - if (false) { - return; - } else if (intersect == null) { - return; - } else { - intersect = "m"; - } + public void testJoiningMultipleBranches() { + Object intersect = null; + if (false) { + return; + } else if (intersect == null) { + return; + } else { + intersect = "m"; + } - intersect.toString(); - } + intersect.toString(); + } } diff --git a/checker/tests/nullness/flow/PureAndFlow.java b/checker/tests/nullness/flow/PureAndFlow.java index 7ff0e1cf243..88d2d99a414 100644 --- a/checker/tests/nullness/flow/PureAndFlow.java +++ b/checker/tests/nullness/flow/PureAndFlow.java @@ -2,59 +2,59 @@ public abstract class PureAndFlow { - @Nullable String s1; - @Nullable String s2; + @Nullable String s1; + @Nullable String s2; - void nonpure(String s1) {} + void nonpure(String s1) {} - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - void pure(String s2) {} - - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.void.method) - void det(String s3) {} + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + void pure(String s2) {} - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - abstract void abstractpure(String s4); + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.void.method) + void det(String s3) {} - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.void.method) - abstract void abstractdet(String s4); + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + abstract void abstractpure(String s4); - void withNonRow() { - if (s2 != null) { - nonpure("m"); - // :: error: (argument.type.incompatible) - pure(s2); + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.void.method) + abstract void abstractdet(String s4); + + void withNonRow() { + if (s2 != null) { + nonpure("m"); + // :: error: (argument.type.incompatible) + pure(s2); + } } - } - void withPure() { - if (s2 != null) { - pure("m"); - pure(s2); + void withPure() { + if (s2 != null) { + pure("m"); + pure(s2); + } } - } - interface IFace { - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.void.method) - void ifacepure(String s); + interface IFace { + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.void.method) + void ifacepure(String s); - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.void.method) - void ifacedet(String s); - } + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.void.method) + void ifacedet(String s); + } - class Cons { - @org.checkerframework.dataflow.qual.Pure - // :: warning: (purity.deterministic.constructor) - Cons(String s) {} + class Cons { + @org.checkerframework.dataflow.qual.Pure + // :: warning: (purity.deterministic.constructor) + Cons(String s) {} - @org.checkerframework.dataflow.qual.Deterministic - // :: warning: (purity.deterministic.constructor) - Cons(int i) {} - } + @org.checkerframework.dataflow.qual.Deterministic + // :: warning: (purity.deterministic.constructor) + Cons(int i) {} + } } diff --git a/checker/tests/nullness/flow/PurityError.java b/checker/tests/nullness/flow/PurityError.java index 9e52cdc9c6a..746dfbd6316 100644 --- a/checker/tests/nullness/flow/PurityError.java +++ b/checker/tests/nullness/flow/PurityError.java @@ -2,13 +2,13 @@ import org.checkerframework.dataflow.qual.SideEffectFree; public class PurityError { - @SideEffectFree - void method() {} + @SideEffectFree + void method() {} - @Pure - Object method2() { - // :: error: (purity.not.deterministic.call) - method(); - return ""; - } + @Pure + Object method2() { + // :: error: (purity.not.deterministic.call) + method(); + return ""; + } } diff --git a/checker/tests/nullness/flow/TestNullnessUtil.java b/checker/tests/nullness/flow/TestNullnessUtil.java index 09c26af066e..ace665efc82 100644 --- a/checker/tests/nullness/flow/TestNullnessUtil.java +++ b/checker/tests/nullness/flow/TestNullnessUtil.java @@ -3,89 +3,89 @@ /** Test class org.checkerframework.checker.nullness.util.NullnessUtil. */ public class TestNullnessUtil { - void testRef1(@Nullable Object o) { - // one way to use as a cast: - @NonNull Object l1 = NullnessUtil.castNonNull(o); - } + void testRef1(@Nullable Object o) { + // one way to use as a cast: + @NonNull Object l1 = NullnessUtil.castNonNull(o); + } - void testRef2(@Nullable Object o) { - // another way to use as a cast: - NullnessUtil.castNonNull(o).toString(); - } + void testRef2(@Nullable Object o) { + // another way to use as a cast: + NullnessUtil.castNonNull(o).toString(); + } - void testRef3(@Nullable Object o) { - // use as statement: - NullnessUtil.castNonNull(o); - o.toString(); - } + void testRef3(@Nullable Object o) { + // use as statement: + NullnessUtil.castNonNull(o); + o.toString(); + } - void testArr1(@Nullable Object @NonNull [] a) { - // one way to use as a cast: - @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); - // Careful, the non-deep version only casts the main modifier. - // :: error: (assignment.type.incompatible) - // :: error: (type.arguments.not.inferred) - @NonNull Object[] l2b = NullnessUtil.castNonNull(a); - // OK - @Nullable Object[] l2c = NullnessUtil.castNonNull(a); - } + void testArr1(@Nullable Object @NonNull [] a) { + // one way to use as a cast: + @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); + // Careful, the non-deep version only casts the main modifier. + // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) + @NonNull Object[] l2b = NullnessUtil.castNonNull(a); + // OK + @Nullable Object[] l2c = NullnessUtil.castNonNull(a); + } - void testArr1b(@Nullable Object @Nullable [] a) { - // one way to use as a cast: - @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); - // Careful, the non-deep version only casts the main modifier. - // :: error: (assignment.type.incompatible) - // :: error: (type.arguments.not.inferred) - @NonNull Object[] l2b = NullnessUtil.castNonNull(a); - // OK - @Nullable Object[] l2c = NullnessUtil.castNonNull(a); - } + void testArr1b(@Nullable Object @Nullable [] a) { + // one way to use as a cast: + @NonNull Object[] l2 = NullnessUtil.castNonNullDeep(a); + // Careful, the non-deep version only casts the main modifier. + // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) + @NonNull Object[] l2b = NullnessUtil.castNonNull(a); + // OK + @Nullable Object[] l2c = NullnessUtil.castNonNull(a); + } - void testArr2(@Nullable Object @NonNull [] a) { - // another way to use as a cast: - NullnessUtil.castNonNullDeep(a)[0].toString(); - } + void testArr2(@Nullable Object @NonNull [] a) { + // another way to use as a cast: + NullnessUtil.castNonNullDeep(a)[0].toString(); + } - void testArr3(@Nullable Object @NonNull [] a) { - // use as statement: - NullnessUtil.castNonNullDeep(a); - a.toString(); - // TODO: @EnsuresNonNull cannot express that - // all the array components are non-null. - // a[0].toString(); - } + void testArr3(@Nullable Object @NonNull [] a) { + // use as statement: + NullnessUtil.castNonNullDeep(a); + a.toString(); + // TODO: @EnsuresNonNull cannot express that + // all the array components are non-null. + // a[0].toString(); + } - /* - // TODO: flow does not propagate component types. - void testArr3(@Nullable Object @NonNull [] a) { - // one way to use as a statement: - NullnessUtil.castNonNull(a); - a[0].toString(); - } - */ + /* + // TODO: flow does not propagate component types. + void testArr3(@Nullable Object @NonNull [] a) { + // one way to use as a statement: + NullnessUtil.castNonNull(a); + a[0].toString(); + } + */ - void testMultiArr1(@Nullable Object @NonNull [] @Nullable [] a) { - // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) - @NonNull Object l3 = a[0][0]; - // one way to use as a cast: - @NonNull Object[][] l4 = NullnessUtil.castNonNullDeep(a); - } + void testMultiArr1(@Nullable Object @NonNull [] @Nullable [] a) { + // :: error: (assignment.type.incompatible) :: error: (accessing.nullable) + @NonNull Object l3 = a[0][0]; + // one way to use as a cast: + @NonNull Object[][] l4 = NullnessUtil.castNonNullDeep(a); + } - void testMultiArr2(@Nullable Object @NonNull [] @Nullable [] a) { - // another way to use as a cast: - NullnessUtil.castNonNullDeep(a)[0][0].toString(); - } + void testMultiArr2(@Nullable Object @NonNull [] @Nullable [] a) { + // another way to use as a cast: + NullnessUtil.castNonNullDeep(a)[0][0].toString(); + } - void testMultiArr3(@Nullable Object @Nullable [] @Nullable [] @Nullable [] a) { - // :: error: (dereference.of.nullable) :: error: (accessing.nullable) - a[0][0][0].toString(); - // another way to use as a cast: - NullnessUtil.castNonNullDeep(a)[0][0][0].toString(); - } + void testMultiArr3(@Nullable Object @Nullable [] @Nullable [] @Nullable [] a) { + // :: error: (dereference.of.nullable) :: error: (accessing.nullable) + a[0][0][0].toString(); + // another way to use as a cast: + NullnessUtil.castNonNullDeep(a)[0][0][0].toString(); + } - public static void main(String[] args) { - Object[] @Nullable [] err = new Object[10][10]; - Object[][] e1 = NullnessUtil.castNonNullDeep(err); - e1[0][0].toString(); - } + public static void main(String[] args) { + Object[] @Nullable [] err = new Object[10][10]; + Object[][] e1 = NullnessUtil.castNonNullDeep(err); + e1[0][0].toString(); + } } diff --git a/checker/tests/nullness/flow/TestOpt.java b/checker/tests/nullness/flow/TestOpt.java index 72cf3c667be..0e5e3635c71 100644 --- a/checker/tests/nullness/flow/TestOpt.java +++ b/checker/tests/nullness/flow/TestOpt.java @@ -3,73 +3,73 @@ /** Test class org.checkerframework.checker.nullness.util.Opt. */ public class TestOpt { - void foo1(@Nullable Object p) { - if (Opt.isPresent(p)) { - p.toString(); // Flow refinement + void foo1(@Nullable Object p) { + if (Opt.isPresent(p)) { + p.toString(); // Flow refinement + } } - } - void foo1b(@Nullable Object p) { - if (!Opt.isPresent(p)) { - // :: error: (dereference.of.nullable) - p.toString(); + void foo1b(@Nullable Object p) { + if (!Opt.isPresent(p)) { + // :: error: (dereference.of.nullable) + p.toString(); + } } - } - void foo2(@Nullable Object p) { - Opt.ifPresent(p, x -> System.out.println("Got: " + x)); - } + void foo2(@Nullable Object p) { + Opt.ifPresent(p, x -> System.out.println("Got: " + x)); + } - void foo2b(@Nullable Object p) { - Opt.ifPresent(p, x -> System.out.println("Got: " + x.toString())); - } + void foo2b(@Nullable Object p) { + Opt.ifPresent(p, x -> System.out.println("Got: " + x.toString())); + } - void foo3(@Nullable Object p) { - Object o = Opt.filter(p, x -> x.hashCode() > 10); - } + void foo3(@Nullable Object p) { + Object o = Opt.filter(p, x -> x.hashCode() > 10); + } - void foo4(@Nullable Object p) { - String s = Opt.map(p, x -> x.toString()); - } + void foo4(@Nullable Object p) { + String s = Opt.map(p, x -> x.toString()); + } - void foo4b(@Nullable Object p) { - // :: error: (argument.type.incompatible) - String s = Opt.map(p, null); - } + void foo4b(@Nullable Object p) { + // :: error: (argument.type.incompatible) + String s = Opt.map(p, null); + } - void foo5(@Nullable Object p) { - @NonNull Object o = Opt.orElse(p, new Object()); - } + void foo5(@Nullable Object p) { + @NonNull Object o = Opt.orElse(p, new Object()); + } - void foo5b(@Nullable Object p) { - // :: error: (argument.type.incompatible) - @NonNull Object o = Opt.orElse(p, null); - } + void foo5b(@Nullable Object p) { + // :: error: (argument.type.incompatible) + @NonNull Object o = Opt.orElse(p, null); + } - void foo6(@Nullable Object p) { - @NonNull Object o = Opt.orElseGet(p, () -> new Object()); - } + void foo6(@Nullable Object p) { + @NonNull Object o = Opt.orElseGet(p, () -> new Object()); + } - void foo6b(@Nullable Object p) { - // :: error: (return.type.incompatible) - @NonNull Object o = Opt.orElseGet(p, () -> null); - } + void foo6b(@Nullable Object p) { + // :: error: (return.type.incompatible) + @NonNull Object o = Opt.orElseGet(p, () -> null); + } - void foo7(Object p) { - try { - @NonNull Object o = Opt.orElseThrow(p, () -> new Throwable()); - } catch (Throwable t) { - // p was null + void foo7(Object p) { + try { + @NonNull Object o = Opt.orElseThrow(p, () -> new Throwable()); + } catch (Throwable t) { + // p was null + } } - } - void foo7b(@Nullable Object p) { - try { - // :: error: (assignment.type.incompatible) - // :: error: (type.arguments.not.inferred) - @NonNull Object o = Opt.orElseThrow(p, () -> null); - } catch (Throwable t) { - // p was null + void foo7b(@Nullable Object p) { + try { + // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) + @NonNull Object o = Opt.orElseThrow(p, () -> null); + } catch (Throwable t) { + // p was null + } } - } } diff --git a/checker/tests/nullness/generics/AnnotatedGenerics3.java b/checker/tests/nullness/generics/AnnotatedGenerics3.java index 97c3829f634..31490a8d7f9 100644 --- a/checker/tests/nullness/generics/AnnotatedGenerics3.java +++ b/checker/tests/nullness/generics/AnnotatedGenerics3.java @@ -1,42 +1,42 @@ import org.checkerframework.checker.nullness.qual.*; public class AnnotatedGenerics3 { - class Cell { - T f; - - Cell(T i) { - f = i; + class Cell { + T f; + + Cell(T i) { + f = i; + } + + void setNull(Cell<@Nullable T> p) { + p.f = null; + } + + void indirect(Cell p) { + // :: error: (argument.type.incompatible) + setNull(p); + } + + void setField(@Nullable T p) { + // :: error: (assignment.type.incompatible) + this.f = p; + } } - void setNull(Cell<@Nullable T> p) { - p.f = null; - } + void run() { + Cell<@NonNull Object> c = new Cell<>(new Object()); + // :: error: (argument.type.incompatible) + c.setNull(c); + c.f.hashCode(); - void indirect(Cell p) { - // :: error: (argument.type.incompatible) - setNull(p); - } + c.indirect(c); + c.f.hashCode(); - void setField(@Nullable T p) { - // :: error: (assignment.type.incompatible) - this.f = p; + c.setField(null); + c.f.hashCode(); } - } - - void run() { - Cell<@NonNull Object> c = new Cell<>(new Object()); - // :: error: (argument.type.incompatible) - c.setNull(c); - c.f.hashCode(); - c.indirect(c); - c.f.hashCode(); - - c.setField(null); - c.f.hashCode(); - } - - public static void main(String[] args) { - new AnnotatedGenerics3().run(); - } + public static void main(String[] args) { + new AnnotatedGenerics3().run(); + } } diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams.java b/checker/tests/nullness/generics/AnnotatedTypeParams.java index fa413d9e56b..eaa84903061 100644 --- a/checker/tests/nullness/generics/AnnotatedTypeParams.java +++ b/checker/tests/nullness/generics/AnnotatedTypeParams.java @@ -1,18 +1,18 @@ import org.checkerframework.checker.nullness.qual.*; class MyClass<@Nullable T> { - T get() { - throw new RuntimeException(); - } + T get() { + throw new RuntimeException(); + } - void testPositive() { - MyClass<@Nullable String> l = new MyClass<>(); - // :: error: (dereference.of.nullable) - l.get().toString(); - } + void testPositive() { + MyClass<@Nullable String> l = new MyClass<>(); + // :: error: (dereference.of.nullable) + l.get().toString(); + } - void testInvalidParam() { - // :: error: (type.argument.type.incompatible) - MyClass<@NonNull String> l; - } + void testInvalidParam() { + // :: error: (type.argument.type.incompatible) + MyClass<@NonNull String> l; + } } diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams2.java b/checker/tests/nullness/generics/AnnotatedTypeParams2.java index 94f19a60187..99c89df3ba0 100644 --- a/checker/tests/nullness/generics/AnnotatedTypeParams2.java +++ b/checker/tests/nullness/generics/AnnotatedTypeParams2.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.nullness.qual.*; class SomeClass<@Nullable T> { - T get() { - throw new RuntimeException(); - } + T get() { + throw new RuntimeException(); + } } public class AnnotatedTypeParams2 { - void testPositive() { - SomeClass<@Nullable String> l = new SomeClass<>(); - // :: error: (dereference.of.nullable) - l.get().toString(); - } + void testPositive() { + SomeClass<@Nullable String> l = new SomeClass<>(); + // :: error: (dereference.of.nullable) + l.get().toString(); + } - void testInvalidParam() { - // :: error: (type.argument.type.incompatible) - SomeClass<@NonNull String> l; - } + void testInvalidParam() { + // :: error: (type.argument.type.incompatible) + SomeClass<@NonNull String> l; + } } diff --git a/checker/tests/nullness/generics/AnnotatedTypeParams4.java b/checker/tests/nullness/generics/AnnotatedTypeParams4.java index e53ae36086b..ee3c93ce00b 100644 --- a/checker/tests/nullness/generics/AnnotatedTypeParams4.java +++ b/checker/tests/nullness/generics/AnnotatedTypeParams4.java @@ -2,76 +2,76 @@ public class AnnotatedTypeParams4 { - class Test1 { - CONTENT a; + class Test1 { + CONTENT a; - // To prevent the warning about un-initialized fields. - Test1(CONTENT p1) { - a = p1; - } + // To prevent the warning about un-initialized fields. + Test1(CONTENT p1) { + a = p1; + } - public CONTENT get() { - return a; - } + public CONTENT get() { + return a; + } - @org.checkerframework.dataflow.qual.Pure - public CONTENT get2() { - return a; + @org.checkerframework.dataflow.qual.Pure + public CONTENT get2() { + return a; + } } - } - class Test2 { - @NonNull CONTENT a; + class Test2 { + @NonNull CONTENT a; - // To prevent the warning about un-initialized fields. - Test2(@NonNull CONTENT p1) { - a = p1; - } + // To prevent the warning about un-initialized fields. + Test2(@NonNull CONTENT p1) { + a = p1; + } - public @NonNull CONTENT get() { - return a; - } + public @NonNull CONTENT get() { + return a; + } - @org.checkerframework.dataflow.qual.Pure - public @NonNull CONTENT get2() { - return a; + @org.checkerframework.dataflow.qual.Pure + public @NonNull CONTENT get2() { + return a; + } } - } - /* - class Test3 { - // Change @Pure to be allowed on fields, or add some other anno. - @Pure CONTENT f; - // Strangely this assignment succeeded - Test3(CONTENT p1) { f = p1; } - // But this assignment failed, because the @Pure caused the - // other annotations to be erased. - public void get3(CONTENT p) { - f = p; - } - } - */ + /* + class Test3 { + // Change @Pure to be allowed on fields, or add some other anno. + @Pure CONTENT f; + // Strangely this assignment succeeded + Test3(CONTENT p1) { f = p1; } + // But this assignment failed, because the @Pure caused the + // other annotations to be erased. + public void get3(CONTENT p) { + f = p; + } + } + */ - class Test4 { - private MyPair userObject; + class Test4 { + private MyPair userObject; - Test4(MyPair p) { - userObject = p; - } + Test4(MyPair p) { + userObject = p; + } - @org.checkerframework.dataflow.qual.Pure - public CONTENT getUserLeft() { - return userObject.a; - } + @org.checkerframework.dataflow.qual.Pure + public CONTENT getUserLeft() { + return userObject.a; + } - public class MyPair { - public T1 a; - public T2 b; + public class MyPair { + public T1 a; + public T2 b; - public MyPair(T1 a, T2 b) { - this.a = a; - this.b = b; - } + public MyPair(T1 a, T2 b) { + this.a = a; + this.b = b; + } + } } - } } diff --git a/checker/tests/nullness/generics/AnonymousClass.java b/checker/tests/nullness/generics/AnonymousClass.java index a22112b2d14..9fd3545eda8 100644 --- a/checker/tests/nullness/generics/AnonymousClass.java +++ b/checker/tests/nullness/generics/AnonymousClass.java @@ -2,16 +2,16 @@ public class AnonymousClass { - class Bound {} + class Bound {} - void test() { - // :: error: (type.argument.type.incompatible) - new Bound<@Nullable String>() {}; - } + void test() { + // :: error: (type.argument.type.incompatible) + new Bound<@Nullable String>() {}; + } - // The dummy parameter tests ParamApplier - void test(Object dummy) { - // :: error: (type.argument.type.incompatible) - new Bound<@Nullable String>() {}; - } + // The dummy parameter tests ParamApplier + void test(Object dummy) { + // :: error: (type.argument.type.incompatible) + new Bound<@Nullable String>() {}; + } } diff --git a/checker/tests/nullness/generics/BoundedWildcardTest.java b/checker/tests/nullness/generics/BoundedWildcardTest.java index 51b015837a7..71876cede1e 100644 --- a/checker/tests/nullness/generics/BoundedWildcardTest.java +++ b/checker/tests/nullness/generics/BoundedWildcardTest.java @@ -1,44 +1,45 @@ // Test case from // http://stackoverflow.com/questions/38339332/in-a-bounded-wildcard-where-does-the-annotation-belong -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + class Styleable {} public class BoundedWildcardTest { - private void locChildren(Styleable c) { - // ... - } + private void locChildren(Styleable c) { + // ... + } - public void initLoc(List s) { - for (Styleable c : s) { - locChildren(c); + public void initLoc(List s) { + for (Styleable c : s) { + locChildren(c); + } } - } - // :: error: (bound.type.incompatible) - public void initLoc1(@Nullable List<@Nullable ? extends Styleable> s) { - // :: error: (iterating.over.nullable) - for (Styleable c : s) { - locChildren(c); + // :: error: (bound.type.incompatible) + public void initLoc1(@Nullable List<@Nullable ? extends Styleable> s) { + // :: error: (iterating.over.nullable) + for (Styleable c : s) { + locChildren(c); + } } - } - public void initLoc2(@Nullable List<@Nullable ? extends @Nullable Styleable> s) { - // :: error: (iterating.over.nullable) - for (Styleable c : s) { - // :: error: argument.type.incompatible - locChildren(c); + public void initLoc2(@Nullable List<@Nullable ? extends @Nullable Styleable> s) { + // :: error: (iterating.over.nullable) + for (Styleable c : s) { + // :: error: argument.type.incompatible + locChildren(c); + } } - } - public void initLoc3(@Nullable List s) { - // :: error: (iterating.over.nullable) - for (Styleable c : s) { - // :: error: argument.type.incompatible - locChildren(c); + public void initLoc3(@Nullable List s) { + // :: error: (iterating.over.nullable) + for (Styleable c : s) { + // :: error: argument.type.incompatible + locChildren(c); + } } - } } diff --git a/checker/tests/nullness/generics/BoxingGenerics.java b/checker/tests/nullness/generics/BoxingGenerics.java index a97441a4975..13fb50f2bb9 100644 --- a/checker/tests/nullness/generics/BoxingGenerics.java +++ b/checker/tests/nullness/generics/BoxingGenerics.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.nullness.qual.*; public class BoxingGenerics { - static class X { - public static X foo(T x) { - return new X<>(); - } + static class X { + public static X foo(T x) { + return new X<>(); + } - public void bar(X x) {} - } + public void bar(X x) {} + } - public void getText() { - X var = new X<>(); - X.foo(Integer.valueOf(5)).bar(var); - X.foo(5).bar(var); - } + public void getText() { + X var = new X<>(); + X.foo(Integer.valueOf(5)).bar(var); + X.foo(5).bar(var); + } } diff --git a/checker/tests/nullness/generics/CapturedWildcards.java b/checker/tests/nullness/generics/CapturedWildcards.java index f193812ccf2..96984bc06f2 100644 --- a/checker/tests/nullness/generics/CapturedWildcards.java +++ b/checker/tests/nullness/generics/CapturedWildcards.java @@ -1,17 +1,18 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class CapturedWildcards { - abstract static class MyClass { - abstract boolean contains(MyClass other); - } + abstract static class MyClass { + abstract boolean contains(MyClass other); + } - public boolean pass(List list, MyClass other) { - return list.stream().anyMatch(je -> je != null && je.contains(other)); - } + public boolean pass(List list, MyClass other) { + return list.stream().anyMatch(je -> je != null && je.contains(other)); + } - public boolean fail(List list, MyClass other) { - // :: error: (dereference.of.nullable) - return list.stream().anyMatch(je -> je.contains(other)); - } + public boolean fail(List list, MyClass other) { + // :: error: (dereference.of.nullable) + return list.stream().anyMatch(je -> je.contains(other)); + } } diff --git a/checker/tests/nullness/generics/CollectionsAnnotations.java b/checker/tests/nullness/generics/CollectionsAnnotations.java index cd6cdbd0ad6..d1183453227 100644 --- a/checker/tests/nullness/generics/CollectionsAnnotations.java +++ b/checker/tests/nullness/generics/CollectionsAnnotations.java @@ -2,67 +2,67 @@ // This is how I propose the Collection interface be annotated: interface Collection1 { - public void add(E elt); + public void add(E elt); } class PriorityQueue1 implements Collection1 { - public void add(E elt) { - // just to dereference elt - elt.hashCode(); - } + public void add(E elt) { + // just to dereference elt + elt.hashCode(); + } } class PriorityQueue2 implements Collection1 { - public void add(E elt) { - // just to dereference elt - elt.hashCode(); - } + public void add(E elt) { + // just to dereference elt + elt.hashCode(); + } } // This is how the Collection interface is currently annotated interface Collection2 { - public void add(E elt); + public void add(E elt); } class PriorityQueue3 implements Collection2 { - public void add(E elt) { - // just to dereference elt - elt.hashCode(); - } + public void add(E elt) { + // just to dereference elt + elt.hashCode(); + } } class Methods { - static void addNull1(Collection1 l) { - // Allowed, because upper bound of Collection1 is Nullable. - // :: warning: [unchecked] unchecked call to add(E) as a member of the raw type Collection1 - l.add(null); - } + static void addNull1(Collection1 l) { + // Allowed, because upper bound of Collection1 is Nullable. + // :: warning: [unchecked] unchecked call to add(E) as a member of the raw type Collection1 + l.add(null); + } - static void bad1() { - addNull1(new PriorityQueue1()); - } + static void bad1() { + addNull1(new PriorityQueue1()); + } - // If the types are parameterized (as they should be) - static <@Nullable E extends @Nullable Object> void addNull2(Collection1 l) { - l.add(null); - } + // If the types are parameterized (as they should be) + static <@Nullable E extends @Nullable Object> void addNull2(Collection1 l) { + l.add(null); + } - static void bad2() { - // :: error: (type.arguments.not.inferred) - addNull2(new PriorityQueue1<@NonNull Object>()); - } + static void bad2() { + // :: error: (type.arguments.not.inferred) + addNull2(new PriorityQueue1<@NonNull Object>()); + } - public static void main(String[] args) { - bad2(); - } + public static void main(String[] args) { + bad2(); + } - static void bad3() { - // :: error: (type.arguments.not.inferred) - addNull2(new PriorityQueue2<@NonNull Object>()); - } + static void bad3() { + // :: error: (type.arguments.not.inferred) + addNull2(new PriorityQueue2<@NonNull Object>()); + } - // :: error: (type.argument.type.incompatible) - static <@Nullable E> void addNull3(Collection2 l) { - l.add(null); - } + // :: error: (type.argument.type.incompatible) + static <@Nullable E> void addNull3(Collection2 l) { + l.add(null); + } } diff --git a/checker/tests/nullness/generics/CollectionsAnnotationsMin.java b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java index 75c4cdf44e1..fddbe69896e 100644 --- a/checker/tests/nullness/generics/CollectionsAnnotationsMin.java +++ b/checker/tests/nullness/generics/CollectionsAnnotationsMin.java @@ -1,58 +1,58 @@ import org.checkerframework.checker.nullness.qual.*; public class CollectionsAnnotationsMin { - static class Collection1 { - public void add(E elt) { - // :: error: (dereference.of.nullable) - elt.hashCode(); + static class Collection1 { + public void add(E elt) { + // :: error: (dereference.of.nullable) + elt.hashCode(); + } } - } - static class PriorityQueue1 extends Collection1 { - public void add(E elt) { - // dereference allowed here - elt.hashCode(); + static class PriorityQueue1 extends Collection1 { + public void add(E elt) { + // dereference allowed here + elt.hashCode(); + } } - } - // This is allowed, as "null" cannot be added to f1 - static Collection1 f1 = new PriorityQueue1<@NonNull Object>(); + // This is allowed, as "null" cannot be added to f1 + static Collection1 f1 = new PriorityQueue1<@NonNull Object>(); - // :: error: (assignment.type.incompatible) - static Collection1<@Nullable Object> f2 = new PriorityQueue1<@NonNull Object>(); + // :: error: (assignment.type.incompatible) + static Collection1<@Nullable Object> f2 = new PriorityQueue1<@NonNull Object>(); - static void addNull1(Collection1<@Nullable Object> l) { - l.add(null); - } + static void addNull1(Collection1<@Nullable Object> l) { + l.add(null); + } - // The upper bound on E is implicitly from Collection1 - static void addNull2(Collection1 l) { - // :: error: (argument.type.incompatible) - l.add(null); - } + // The upper bound on E is implicitly from Collection1 + static void addNull2(Collection1 l) { + // :: error: (argument.type.incompatible) + l.add(null); + } - // The upper bound on E is implicitly from Collection1 - static E addNull2b(Collection1 l, E p) { - // :: error: (argument.type.incompatible) - l.add(null); - return p; - } + // The upper bound on E is implicitly from Collection1 + static E addNull2b(Collection1 l, E p) { + // :: error: (argument.type.incompatible) + l.add(null); + return p; + } - static <@Nullable E extends @Nullable Object> void addNull3(Collection1 l) { - l.add(null); - } + static <@Nullable E extends @Nullable Object> void addNull3(Collection1 l) { + l.add(null); + } - static void bad() { - // :: error: (argument.type.incompatible) - addNull1(new PriorityQueue1<@NonNull Object>()); + static void bad() { + // :: error: (argument.type.incompatible) + addNull1(new PriorityQueue1<@NonNull Object>()); - addNull2(new PriorityQueue1<@NonNull Object>()); - addNull2b(new PriorityQueue1<@NonNull Object>(), new Object()); + addNull2(new PriorityQueue1<@NonNull Object>()); + addNull2b(new PriorityQueue1<@NonNull Object>(), new Object()); - // :: error: (type.arguments.not.inferred) - addNull3(new PriorityQueue1<@NonNull Object>()); + // :: error: (type.arguments.not.inferred) + addNull3(new PriorityQueue1<@NonNull Object>()); - // :: error: (argument.type.incompatible) - f1.add(null); - } + // :: error: (argument.type.incompatible) + f1.add(null); + } } diff --git a/checker/tests/nullness/generics/GenericArgs.java b/checker/tests/nullness/generics/GenericArgs.java index a89f9bc5b76..b685fe8762e 100644 --- a/checker/tests/nullness/generics/GenericArgs.java +++ b/checker/tests/nullness/generics/GenericArgs.java @@ -1,61 +1,62 @@ -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + @org.checkerframework.framework.qual.DefaultQualifier(Nullable.class) public class GenericArgs { - public @NonNull Set<@NonNull String> strings = new HashSet<>(); + public @NonNull Set<@NonNull String> strings = new HashSet<>(); + + void test() { + @NonNull HashSet<@NonNull String> s = new HashSet<>(); + + strings.addAll(s); + strings.add("foo"); + } - void test() { - @NonNull HashSet<@NonNull String> s = new HashSet<>(); + static class X<@NonNull T extends @NonNull Object> { + T value() { + // :: error: (return.type.incompatible) + return null; + } + } - strings.addAll(s); - strings.add("foo"); - } + public static void test2() { + // :: error: (type.argument.type.incompatible) + Object o = new X().value(); + } - static class X<@NonNull T extends @NonNull Object> { - T value() { - // :: error: (return.type.incompatible) - return null; + static <@NonNull Z extends @NonNull Object> void test3(Z z) {} + + void test4() { + // :: error: (type.argument.type.incompatible) + GenericArgs.<@Nullable Object>test3(null); + // :: error: (argument.type.incompatible) + GenericArgs.<@NonNull Object>test3(null); } - } - - public static void test2() { - // :: error: (type.argument.type.incompatible) - Object o = new X().value(); - } - - static <@NonNull Z extends @NonNull Object> void test3(Z z) {} - - void test4() { - // :: error: (type.argument.type.incompatible) - GenericArgs.<@Nullable Object>test3(null); - // :: error: (argument.type.incompatible) - GenericArgs.<@NonNull Object>test3(null); - } - - static class GenericConstructor { - <@NonNull T extends @NonNull Object> GenericConstructor(T t) {} - } - - void test5() { - // :: error: (argument.type.incompatible) - new <@NonNull String>GenericConstructor(null); - } - - void testRecursiveDeclarations() { - class MyComparator<@NonNull @KeyForBottom T extends @NonNull Comparable> - implements Comparator { - @Pure - public int compare(T[] a, T[] b) { - return 0; - } + + static class GenericConstructor { + <@NonNull T extends @NonNull Object> GenericConstructor(T t) {} + } + + void test5() { + // :: error: (argument.type.incompatible) + new <@NonNull String>GenericConstructor(null); + } + + void testRecursiveDeclarations() { + class MyComparator<@NonNull @KeyForBottom T extends @NonNull Comparable> + implements Comparator { + @Pure + public int compare(T[] a, T[] b) { + return 0; + } + } + Comparator<@NonNull String @NonNull []> temp = new MyComparator<@NonNull String>(); } - Comparator<@NonNull String @NonNull []> temp = new MyComparator<@NonNull String>(); - } } diff --git a/checker/tests/nullness/generics/GenericArgs2.java b/checker/tests/nullness/generics/GenericArgs2.java index dde3d4e6418..29d7de40d90 100644 --- a/checker/tests/nullness/generics/GenericArgs2.java +++ b/checker/tests/nullness/generics/GenericArgs2.java @@ -1,45 +1,48 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.io.*; import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.*; class Cell { - void add(T arg) {} + void add(T arg) {} } public class GenericArgs2 { - static void test1(Cell collection) { - // :: error: (argument.type.incompatible) - collection.add(null); // should fail - } - - static void test2(Cell collection) { - // :: error: (argument.type.incompatible) - collection.add(null); // should fail - } - - static void test3(Cell<@Nullable Object> collection) { - collection.add(null); // valid - } - - // No "" version of the above, as that is illegal in Java. - - static class InvariantFilter {} - - static class Invariant {} - - HashMap, Map, Integer>> filter_map1; - MyMap<@Nullable Class, Map, Integer>> - filter_map2; - - public GenericArgs2( - HashMap, Map, Integer>> - filter_map1, - MyMap<@Nullable Class, Map, Integer>> - filter_map2) { - this.filter_map1 = filter_map1; - this.filter_map2 = filter_map2; - } - - class MyMap {} + static void test1(Cell collection) { + // :: error: (argument.type.incompatible) + collection.add(null); // should fail + } + + static void test2(Cell collection) { + // :: error: (argument.type.incompatible) + collection.add(null); // should fail + } + + static void test3(Cell<@Nullable Object> collection) { + collection.add(null); // valid + } + + // No "" version of the above, as that is illegal in Java. + + static class InvariantFilter {} + + static class Invariant {} + + HashMap, Map, Integer>> filter_map1; + MyMap<@Nullable Class, Map, Integer>> + filter_map2; + + public GenericArgs2( + HashMap, Map, Integer>> + filter_map1, + MyMap< + @Nullable Class, + Map, Integer>> + filter_map2) { + this.filter_map1 = filter_map1; + this.filter_map2 = filter_map2; + } + + class MyMap {} } diff --git a/checker/tests/nullness/generics/GenericArgs3.java b/checker/tests/nullness/generics/GenericArgs3.java index 6f8f5ee4860..8d4f008df9c 100644 --- a/checker/tests/nullness/generics/GenericArgs3.java +++ b/checker/tests/nullness/generics/GenericArgs3.java @@ -1,88 +1,89 @@ +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; + import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; class Other { - public static final class StaticIterator implements Iterator { - Enumeration e; + public static final class StaticIterator implements Iterator { + Enumeration e; - public StaticIterator(Enumeration e) { - this.e = e; - } + public StaticIterator(Enumeration e) { + this.e = e; + } - public boolean hasNext() { - return e.hasMoreElements(); - } + public boolean hasNext() { + return e.hasMoreElements(); + } - public T next() { - return e.nextElement(); - } + public T next() { + return e.nextElement(); + } - public void remove() { - throw new UnsupportedOperationException(); + public void remove() { + throw new UnsupportedOperationException(); + } } - } - public final class FinalIterator implements Iterator { - Enumeration e; + public final class FinalIterator implements Iterator { + Enumeration e; - public FinalIterator(Enumeration e) { - this.e = e; - } + public FinalIterator(Enumeration e) { + this.e = e; + } - public boolean hasNext() { - return e.hasMoreElements(); - } + public boolean hasNext() { + return e.hasMoreElements(); + } - public T next() { - return e.nextElement(); - } + public T next() { + return e.nextElement(); + } - public void remove() { - throw new UnsupportedOperationException(); + public void remove() { + throw new UnsupportedOperationException(); + } } - } } class Entry implements Map.Entry { - public V setValue(V newValue) { - throw new RuntimeException(); - } - - @SuppressWarnings("purity") // new and throw are not allowed, ignore - @Pure - public K getKey() { - throw new RuntimeException(); - } - - @SuppressWarnings("purity") // new and throw are not allowed, ignore - @Pure - public V getValue() { - throw new RuntimeException(); - } + public V setValue(V newValue) { + throw new RuntimeException(); + } + + @SuppressWarnings("purity") // new and throw are not allowed, ignore + @Pure + public K getKey() { + throw new RuntimeException(); + } + + @SuppressWarnings("purity") // new and throw are not allowed, ignore + @Pure + public V getValue() { + throw new RuntimeException(); + } } interface Function { - T apply(@Nullable F from); + T apply(@Nullable F from); - @Pure - boolean equals(@Nullable Object obj); + @Pure + boolean equals(@Nullable Object obj); } enum IdentityFunction implements Function { - INSTANCE; + INSTANCE; - public @Nullable Object apply(@Nullable Object o) { - return o; - } + public @Nullable Object apply(@Nullable Object o) { + return o; + } } abstract class FilteredCollection implements Collection { - public boolean addAll(Collection collection) { - for (E element : collection) {} - return true; - } + public boolean addAll(Collection collection) { + for (E element : collection) {} + return true; + } } diff --git a/checker/tests/nullness/generics/GenericReturnField.java b/checker/tests/nullness/generics/GenericReturnField.java index 26331091651..6983deea9d6 100644 --- a/checker/tests/nullness/generics/GenericReturnField.java +++ b/checker/tests/nullness/generics/GenericReturnField.java @@ -6,11 +6,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class GenericReturnField { - private @Nullable T result = null; + private @Nullable T result = null; - // Should return @Nullable T - private T getResult() { - // :: error: (return.type.incompatible) - return result; - } + // Should return @Nullable T + private T getResult() { + // :: error: (return.type.incompatible) + return result; + } } diff --git a/checker/tests/nullness/generics/GenericTest11.java b/checker/tests/nullness/generics/GenericTest11.java index 6abd7d3b8a2..560303c00b3 100644 --- a/checker/tests/nullness/generics/GenericTest11.java +++ b/checker/tests/nullness/generics/GenericTest11.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericTest11 { - public void m(BeanManager beanManager) { - Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); - CreationalContext context = beanManager.createCreationalContext(bean); - } + public void m(BeanManager beanManager) { + Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); + CreationalContext context = beanManager.createCreationalContext(bean); + } - static interface BeanManager { - java.util.Set> getBeans( - java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); + static interface BeanManager { + java.util.Set> getBeans( + java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); - CreationalContext createCreationalContext(Contextual arg0); - } + CreationalContext createCreationalContext(Contextual arg0); + } - static interface Contextual {} + static interface Contextual {} - static interface Bean extends Contextual {} + static interface Bean extends Contextual {} - static interface CreationalContext {} + static interface CreationalContext {} } diff --git a/checker/tests/nullness/generics/GenericsBounds1.java b/checker/tests/nullness/generics/GenericsBounds1.java index b16047c7f89..8508234b1cb 100644 --- a/checker/tests/nullness/generics/GenericsBounds1.java +++ b/checker/tests/nullness/generics/GenericsBounds1.java @@ -1,7 +1,7 @@ import org.checkerframework.checker.nullness.qual.*; interface GBList { - void add(E p); + void add(E p); } /* @@ -10,18 +10,18 @@ interface GBList { * annotation on the type variable itself. */ public class GenericsBounds1 { - void m1(@NonNull GBList g1, @NonNull GBList<@Nullable X> g2) { - // :: error: (assignment.type.incompatible) - g1 = null; - // :: error: (argument.type.incompatible) - g1.add(null); + void m1(@NonNull GBList g1, @NonNull GBList<@Nullable X> g2) { + // :: error: (assignment.type.incompatible) + g1 = null; + // :: error: (argument.type.incompatible) + g1.add(null); - // :: error: (assignment.type.incompatible) - g2 = null; - g2.add(null); + // :: error: (assignment.type.incompatible) + g2 = null; + g2.add(null); - // :: error: (assignment.type.incompatible) - g2 = g1; - g2.add(null); - } + // :: error: (assignment.type.incompatible) + g2 = g1; + g2.add(null); + } } diff --git a/checker/tests/nullness/generics/GenericsBounds2.java b/checker/tests/nullness/generics/GenericsBounds2.java index ab0a418d206..d23d33de6fb 100644 --- a/checker/tests/nullness/generics/GenericsBounds2.java +++ b/checker/tests/nullness/generics/GenericsBounds2.java @@ -4,26 +4,26 @@ * Illustrate a problem with annotations on type variables. */ public class GenericsBounds2 { - void m1(X @NonNull [] a1, @Nullable X @NonNull [] a2) { - // :: error: (assignment.type.incompatible) - a1 = null; - // :: error: (assignment.type.incompatible) - a1[0] = null; + void m1(X @NonNull [] a1, @Nullable X @NonNull [] a2) { + // :: error: (assignment.type.incompatible) + a1 = null; + // :: error: (assignment.type.incompatible) + a1[0] = null; - // :: error: (assignment.type.incompatible) - a2 = null; - a2[0] = null; + // :: error: (assignment.type.incompatible) + a2 = null; + a2[0] = null; - // This error is expected when arrays are invariant. - // Currently, this error is not raised. - // TODOINVARR:: error: (assignment.type.incompatible) - a2 = a1; - a2[0] = null; - } + // This error is expected when arrays are invariant. + // Currently, this error is not raised. + // TODOINVARR:: error: (assignment.type.incompatible) + a2 = a1; + a2[0] = null; + } - void aaa(@Nullable Object[] p1, @NonNull Object[] p2) { - // This one is only expected when we switch the default for arrays to be invariant. - // TODOINVARR:: error: (assignment.type.incompatible) - p1 = p2; - } + void aaa(@Nullable Object[] p1, @NonNull Object[] p2) { + // This one is only expected when we switch the default for arrays to be invariant. + // TODOINVARR:: error: (assignment.type.incompatible) + p1 = p2; + } } diff --git a/checker/tests/nullness/generics/GenericsBounds3.java b/checker/tests/nullness/generics/GenericsBounds3.java index 9c4faaaecf5..d28713e9625 100644 --- a/checker/tests/nullness/generics/GenericsBounds3.java +++ b/checker/tests/nullness/generics/GenericsBounds3.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericsBounds3 { - class Sup {} + class Sup {} - // :: error: (type.argument.type.incompatible) - class Sub extends Sup<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class Sub extends Sup<@Nullable Object> {} - class SubGood extends Sup<@NonNull Object> {} + class SubGood extends Sup<@NonNull Object> {} - interface ISup {} + interface ISup {} - // :: error: (type.argument.type.incompatible) - class ISub implements ISup<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class ISub implements ISup<@Nullable Object> {} - class ISubGood implements ISup<@NonNull Object> {} + class ISubGood implements ISup<@NonNull Object> {} - // :: error: (type.argument.type.incompatible) - class ISub2 extends Sup implements java.io.Serializable, ISup<@Nullable Object> {} + // :: error: (type.argument.type.incompatible) + class ISub2 extends Sup implements java.io.Serializable, ISup<@Nullable Object> {} } diff --git a/checker/tests/nullness/generics/GenericsBounds4.java b/checker/tests/nullness/generics/GenericsBounds4.java index fc055c832d3..6c87882a971 100644 --- a/checker/tests/nullness/generics/GenericsBounds4.java +++ b/checker/tests/nullness/generics/GenericsBounds4.java @@ -1,28 +1,28 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericsBounds4 { - class Collection1 { - public void add(E elt) { - // :: error: (dereference.of.nullable) - elt.hashCode(); + class Collection1 { + public void add(E elt) { + // :: error: (dereference.of.nullable) + elt.hashCode(); + } } - } - Collection1 f1 = new Collection1<@NonNull Object>(); - // :: error: (assignment.type.incompatible) - Collection1<@Nullable ? extends @Nullable Object> f2 = new Collection1<@NonNull Object>(); - Collection1<@Nullable ? extends @Nullable Object> f3 = new Collection1<@Nullable Object>(); + Collection1 f1 = new Collection1<@NonNull Object>(); + // :: error: (assignment.type.incompatible) + Collection1<@Nullable ? extends @Nullable Object> f2 = new Collection1<@NonNull Object>(); + Collection1<@Nullable ? extends @Nullable Object> f3 = new Collection1<@Nullable Object>(); - void bad() { - // This has to be forbidden, because f1 might refer to a - // collection that has NonNull as type argument. - // :: error: (argument.type.incompatible) - f1.add(null); + void bad() { + // This has to be forbidden, because f1 might refer to a + // collection that has NonNull as type argument. + // :: error: (argument.type.incompatible) + f1.add(null); - // This is forbidden by the Java type rules: - // f1.add(new Object()); + // This is forbidden by the Java type rules: + // f1.add(new Object()); - // ok - f3.add(null); - } + // ok + f3.add(null); + } } diff --git a/checker/tests/nullness/generics/GenericsBounds5.java b/checker/tests/nullness/generics/GenericsBounds5.java index 204019bf253..b853a1d8342 100644 --- a/checker/tests/nullness/generics/GenericsBounds5.java +++ b/checker/tests/nullness/generics/GenericsBounds5.java @@ -1,46 +1,46 @@ import org.checkerframework.checker.nullness.qual.*; public class GenericsBounds5 { - class Collection1 { - public void add(E elt) { - // This call is forbidden, because elt might be null. - // :: error: (dereference.of.nullable) - elt.hashCode(); + class Collection1 { + public void add(E elt) { + // This call is forbidden, because elt might be null. + // :: error: (dereference.of.nullable) + elt.hashCode(); + } + } + + <@Nullable F extends @Nullable Object> void addNull1(Collection1 l) { + // This call is allowed, because F is definitely @Nullable. + l.add(null); + } + + // Effectively, this should be the same signature as above. + // TODO: the type "@Nullable ?" is "@Nullable ? extends @NonNull Object", + // with the wrong extends bound. + void addNull2(Collection1<@Nullable ? extends @Nullable Object> l) { + // This call has to pass, like above. + l.add(null); + } + + <@Nullable F extends @Nullable Object> void addNull3(Collection1 l, F p) { + // This call is allowed, because F is definitely @Nullable. + l.add(null); + l.add(p); + } + + // :: error: (assignment.type.incompatible) + Collection1<@Nullable ? extends @Nullable Integer> f = new Collection1<@NonNull Integer>(); + + void bad(Collection1<@NonNull Integer> nnarg) { + // These have to be forbidden, because f1 might refer to a + // collection that has NonNull as type argument. + // :: error: (type.arguments.not.inferred) + addNull1(nnarg); + + // :: error: (argument.type.incompatible) + addNull2(nnarg); + + // :: error: (type.arguments.not.inferred) + addNull3(nnarg, Integer.valueOf(4)); } - } - - <@Nullable F extends @Nullable Object> void addNull1(Collection1 l) { - // This call is allowed, because F is definitely @Nullable. - l.add(null); - } - - // Effectively, this should be the same signature as above. - // TODO: the type "@Nullable ?" is "@Nullable ? extends @NonNull Object", - // with the wrong extends bound. - void addNull2(Collection1<@Nullable ? extends @Nullable Object> l) { - // This call has to pass, like above. - l.add(null); - } - - <@Nullable F extends @Nullable Object> void addNull3(Collection1 l, F p) { - // This call is allowed, because F is definitely @Nullable. - l.add(null); - l.add(p); - } - - // :: error: (assignment.type.incompatible) - Collection1<@Nullable ? extends @Nullable Integer> f = new Collection1<@NonNull Integer>(); - - void bad(Collection1<@NonNull Integer> nnarg) { - // These have to be forbidden, because f1 might refer to a - // collection that has NonNull as type argument. - // :: error: (type.arguments.not.inferred) - addNull1(nnarg); - - // :: error: (argument.type.incompatible) - addNull2(nnarg); - - // :: error: (type.arguments.not.inferred) - addNull3(nnarg, Integer.valueOf(4)); - } } diff --git a/checker/tests/nullness/generics/GenericsConstructor.java b/checker/tests/nullness/generics/GenericsConstructor.java index 764b44195e0..1434d3ec6f1 100644 --- a/checker/tests/nullness/generics/GenericsConstructor.java +++ b/checker/tests/nullness/generics/GenericsConstructor.java @@ -1,15 +1,15 @@ public class GenericsConstructor { - class Test { - Test(T param) {} + class Test { + Test(T param) {} - Test(T1 p1, T2 p2) {} - } + Test(T1 p1, T2 p2) {} + } - void call() { - new Test("Ha!"); - new Test("Ha!"); - new Test(new Object()); + void call() { + new Test("Ha!"); + new Test("Ha!"); + new Test(new Object()); - // new Test("Hi", "Ho"); - } + // new Test("Hi", "Ho"); + } } diff --git a/checker/tests/nullness/generics/GenericsExample.java b/checker/tests/nullness/generics/GenericsExample.java index 8108b5627e1..4b4ad2dd653 100644 --- a/checker/tests/nullness/generics/GenericsExample.java +++ b/checker/tests/nullness/generics/GenericsExample.java @@ -5,175 +5,175 @@ // whose source code is ../../../docs/manual/advanced-features.tex public class GenericsExample { - class MyList1<@Nullable T> { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList1(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; + class MyList1<@Nullable T> { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList1(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + t = null; + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + // :: error: (assignment.type.incompatible) + nn = this.get(0); + this.add(t); + this.add(nble); + this.add(nn); + } } - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - t = null; - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - // :: error: (assignment.type.incompatible) - nn = this.get(0); - this.add(t); - this.add(nble); - this.add(nn); - } - } - - class MyList1a<@Nullable T extends @Nullable Object> { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList1a(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; + class MyList1a<@Nullable T extends @Nullable Object> { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList1a(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + t = null; + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + // :: error: (assignment.type.incompatible) + nn = this.get(0); + this.add(t); + this.add(nble); + this.add(nn); + } } - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - t = null; - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - // :: error: (assignment.type.incompatible) - nn = this.get(0); - this.add(t); - this.add(nble); - this.add(nn); - } - } - - class MyList2<@NonNull T extends @NonNull Object> { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList2(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; + class MyList2<@NonNull T extends @NonNull Object> { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList2(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + // :: error: (assignment.type.incompatible) + t = null; + // :: error: (assignment.type.incompatible) + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + nn = this.get(0); + this.add(t); + // :: error: (argument.type.incompatible) + this.add(nble); + this.add(nn); + } } - void m() { - // :: error: (assignment.type.incompatible) - t = null; - // :: error: (assignment.type.incompatible) - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - nn = this.get(0); - this.add(t); - // :: error: (argument.type.incompatible) - this.add(nble); - this.add(nn); - } - } - - class MyList2a { // same as MyList2 - T t; - @Nullable T nble; - @NonNull T nn; - - MyList2a(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; - } - - void m() { - // :: error: (assignment.type.incompatible) - t = null; - // :: error: (assignment.type.incompatible) - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - nn = this.get(0); - this.add(t); - // :: error: (argument.type.incompatible) - this.add(nble); - this.add(nn); - } - } - - class MyList3 { - T t; - @Nullable T nble; - @NonNull T nn; - - MyList3(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - } - - void add(T arg) {} - - T get(int i) { - return t; + class MyList2a { // same as MyList2 + T t; + @Nullable T nble; + @NonNull T nn; + + MyList2a(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + // :: error: (assignment.type.incompatible) + t = null; + // :: error: (assignment.type.incompatible) + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + nn = this.get(0); + this.add(t); + // :: error: (argument.type.incompatible) + this.add(nble); + this.add(nn); + } } - void m() { - // :: error: (assignment.type.incompatible) - t = null; - // :: error: (assignment.type.incompatible) - t = nble; - nble = null; - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - // :: error: (assignment.type.incompatible) - nn = this.get(0); - this.add(t); - // :: error: (argument.type.incompatible) - this.add(nble); - this.add(nn); + class MyList3 { + T t; + @Nullable T nble; + @NonNull T nn; + + MyList3(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + } + + void add(T arg) {} + + T get(int i) { + return t; + } + + void m() { + // :: error: (assignment.type.incompatible) + t = null; + // :: error: (assignment.type.incompatible) + t = nble; + nble = null; + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + // :: error: (assignment.type.incompatible) + nn = this.get(0); + this.add(t); + // :: error: (argument.type.incompatible) + this.add(nble); + this.add(nn); + } } - } } diff --git a/checker/tests/nullness/generics/GenericsExampleMin.java b/checker/tests/nullness/generics/GenericsExampleMin.java index 9097387d622..4dfa292039d 100644 --- a/checker/tests/nullness/generics/GenericsExampleMin.java +++ b/checker/tests/nullness/generics/GenericsExampleMin.java @@ -5,89 +5,89 @@ // whose source code is ../../../docs/manual/advanced-features.tex public class GenericsExampleMin { - class MyList1<@Nullable T> { - T t; - @Nullable T nble; - @NonNull T nn; + class MyList1<@Nullable T> { + T t; + @Nullable T nble; + @NonNull T nn; - public MyList1(T t, @Nullable T nble, @NonNull T nn) { - this.t = t; - this.nble = nble; - this.nn = nn; - this.t = this.nble; - } + public MyList1(T t, @Nullable T nble, @NonNull T nn) { + this.t = t; + this.nble = nble; + this.nn = nn; + this.t = this.nble; + } - T get(int i) { - return t; - } + T get(int i) { + return t; + } - // This method works. - // Note that it fails to work if it is moved after m2() in the syntax tree. - // TODO: the above comment seems out-of-date, as method m3 below works. - void m1() { - t = this.get(0); - nble = this.get(0); - } + // This method works. + // Note that it fails to work if it is moved after m2() in the syntax tree. + // TODO: the above comment seems out-of-date, as method m3 below works. + void m1() { + t = this.get(0); + nble = this.get(0); + } - // When the assignment to nn is added, the assignments to t and nble also fail, which is - // unexpected. - void m2() { - // :: error: (assignment.type.incompatible) - nn = null; - t = this.get(0); - nble = this.get(0); - } + // When the assignment to nn is added, the assignments to t and nble also fail, which is + // unexpected. + void m2() { + // :: error: (assignment.type.incompatible) + nn = null; + t = this.get(0); + nble = this.get(0); + } - void m3() { - t = this.get(0); - nble = this.get(0); + void m3() { + t = this.get(0); + nble = this.get(0); + } } - } - class MyList2<@NonNull T> { - T t; - @Nullable T nble; + class MyList2<@NonNull T> { + T t; + @Nullable T nble; - public MyList2(T t, @Nullable T nble) { - // :: error: (assignment.type.incompatible) - this.t = this.nble; // error - // :: error: (assignment.type.incompatible) - this.t = nble; // error + public MyList2(T t, @Nullable T nble) { + // :: error: (assignment.type.incompatible) + this.t = this.nble; // error + // :: error: (assignment.type.incompatible) + this.t = nble; // error + } } - } - class MyList3 { - T t; - @Nullable T nble; - @NonNull T nn; + class MyList3 { + T t; + @Nullable T nble; + @NonNull T nn; - public MyList3(T t, @Nullable T nble, @NonNull T nn) { - // :: error: (assignment.type.incompatible) - this.t = nble; - this.t = nn; - // :: error: (assignment.type.incompatible) - this.nn = t; - // :: error: (assignment.type.incompatible) - this.nn = nble; - this.nn = nn; + public MyList3(T t, @Nullable T nble, @NonNull T nn) { + // :: error: (assignment.type.incompatible) + this.t = nble; + this.t = nn; + // :: error: (assignment.type.incompatible) + this.nn = t; + // :: error: (assignment.type.incompatible) + this.nn = nble; + this.nn = nn; + } } - } - class MyList4 { - T t; - @Nullable T nble; - @NonNull T nn; + class MyList4 { + T t; + @Nullable T nble; + @NonNull T nn; - public MyList4(T t, @Nullable T nble, @NonNull T nn) { - // :: error: (assignment.type.incompatible) - this.t = nble; - this.t = nn; - this.nn = t; - // :: error: (assignment.type.incompatible) - this.nn = nble; - this.nn = nn; - this.nn = t; - this.nble = t; + public MyList4(T t, @Nullable T nble, @NonNull T nn) { + // :: error: (assignment.type.incompatible) + this.t = nble; + this.t = nn; + this.nn = t; + // :: error: (assignment.type.incompatible) + this.nn = nble; + this.nn = nn; + this.nn = t; + this.nble = t; + } } - } } diff --git a/checker/tests/nullness/generics/InferMethod.java b/checker/tests/nullness/generics/InferMethod.java index 49f3e8501ff..6691d26b129 100644 --- a/checker/tests/nullness/generics/InferMethod.java +++ b/checker/tests/nullness/generics/InferMethod.java @@ -2,36 +2,36 @@ // https://github.com/typetools/checker-framework/issues/216 public class InferMethod { - public abstract static class Generic { - public class Nested { - public void nestedMethod(T item) {} - } + public abstract static class Generic { + public class Nested { + public void nestedMethod(T item) {} + } - public static class NestedStatic { - public void nestedMethod2(TInner item) {} - } + public static class NestedStatic { + public void nestedMethod2(TInner item) {} + } - public abstract void method(); + public abstract void method(); - public abstract void method2(); + public abstract void method2(); - public void method3(T item) {} - } + public void method3(T item) {} + } - public static class Concrete extends Generic { + public static class Concrete extends Generic { - @Override - public void method() { - Nested o = new Nested(); - o.nestedMethod("test"); - } + @Override + public void method() { + Nested o = new Nested(); + o.nestedMethod("test"); + } - @Override - public void method2() { - NestedStatic o = new NestedStatic<>(); - o.nestedMethod2("test"); + @Override + public void method2() { + NestedStatic o = new NestedStatic<>(); + o.nestedMethod2("test"); - this.method3("test"); + this.method3("test"); + } } - } } diff --git a/checker/tests/nullness/generics/InferredPrimitive.java b/checker/tests/nullness/generics/InferredPrimitive.java index fbe61401729..68288facda1 100644 --- a/checker/tests/nullness/generics/InferredPrimitive.java +++ b/checker/tests/nullness/generics/InferredPrimitive.java @@ -1,6 +1,6 @@ /** Test case for Issue 143: https://github.com/typetools/checker-framework/issues/143 */ public class InferredPrimitive { - public static void main(String[] args) { - java.util.Set s = java.util.Collections.singleton(123L); - } + public static void main(String[] args) { + java.util.Set s = java.util.Collections.singleton(123L); + } } diff --git a/checker/tests/nullness/generics/Issue134.java b/checker/tests/nullness/generics/Issue134.java index 8ad53cff216..8e9a262edd0 100644 --- a/checker/tests/nullness/generics/Issue134.java +++ b/checker/tests/nullness/generics/Issue134.java @@ -4,24 +4,24 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Wrap { - class Inner { - T of(T in) { - return in; + class Inner { + T of(T in) { + return in; + } } - } - Inner get() { - return new Inner(); - } + Inner get() { + return new Inner(); + } } class Bug { - void bar(Wrap w, Integer f) { - w.get().of(f).toString(); - } + void bar(Wrap w, Integer f) { + w.get().of(f).toString(); + } - void baz(Wrap<@Nullable Integer> w, Integer f) { - // :: error: (dereference.of.nullable) - w.get().of(f).toString(); - } + void baz(Wrap<@Nullable Integer> w, Integer f) { + // :: error: (dereference.of.nullable) + w.get().of(f).toString(); + } } diff --git a/checker/tests/nullness/generics/Issue1838.java b/checker/tests/nullness/generics/Issue1838.java index 1cd3a1f8d9c..269f1995d0a 100644 --- a/checker/tests/nullness/generics/Issue1838.java +++ b/checker/tests/nullness/generics/Issue1838.java @@ -1,29 +1,30 @@ // Test case for Issue 1838: // https://github.com/typetools/checker-framework/issues/1838 +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1838 { - public static void main(String[] args) { - f(); - } + public static void main(String[] args) { + f(); + } - public static void f() { - List<@Nullable Object> list = new ArrayList<>(); - list.add(null); - List> listList = new ArrayList>(); - listList.add(list); - // :: error: (argument.type.incompatible) - processElements(listList); - } + public static void f() { + List<@Nullable Object> list = new ArrayList<>(); + list.add(null); + List> listList = new ArrayList>(); + listList.add(list); + // :: error: (argument.type.incompatible) + processElements(listList); + } - private static void processElements(List> listList) { - for (List list : listList) { - for (Object element : list) { - element.toString(); - } + private static void processElements(List> listList) { + for (List list : listList) { + for (Object element : list) { + element.toString(); + } + } } - } } diff --git a/checker/tests/nullness/generics/Issue1838Min.java b/checker/tests/nullness/generics/Issue1838Min.java index e7e04cc7a7a..96ae2c2ecca 100644 --- a/checker/tests/nullness/generics/Issue1838Min.java +++ b/checker/tests/nullness/generics/Issue1838Min.java @@ -1,12 +1,13 @@ // Test case for Issue 1838: // https://github.com/typetools/checker-framework/issues/1838 +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1838Min { - List> llno = new ArrayList<>(); - // :: error: (assignment.type.incompatible) - List> lweo = llno; + List> llno = new ArrayList<>(); + // :: error: (assignment.type.incompatible) + List> lweo = llno; } diff --git a/checker/tests/nullness/generics/Issue269.java b/checker/tests/nullness/generics/Issue269.java index 1ffb14ce51c..4705dc6aba7 100644 --- a/checker/tests/nullness/generics/Issue269.java +++ b/checker/tests/nullness/generics/Issue269.java @@ -1,28 +1,28 @@ // Test case for Issue 269 // https://github.com/typetools/checker-framework/issues/269 class Issue269 { - // Implicitly G has bound @Nullable Object - interface Callback { - public boolean handler(G arg); - } + // Implicitly G has bound @Nullable Object + interface Callback { + public boolean handler(G arg); + } - void method1(Callback callback) { - // Allow this call. - // :: warning: [unchecked] unchecked call to handler(G) as a member of the raw type - // Issue269.Callback - callback.handler(this); - } + void method1(Callback callback) { + // Allow this call. + // :: warning: [unchecked] unchecked call to handler(G) as a member of the raw type + // Issue269.Callback + callback.handler(this); + } - // Implicitly H has bound @NonNull Object - interface CallbackNN { - public boolean handler(H arg); - } + // Implicitly H has bound @NonNull Object + interface CallbackNN { + public boolean handler(H arg); + } - void method2(CallbackNN callback) { - // Forbid this call, because the bound is not respected. - // :: error: (argument.type.incompatible) - // :: warning: [unchecked] unchecked call to handler(H) as a member of the raw type - // Issue269.CallbackNN - callback.handler(null); - } + void method2(CallbackNN callback) { + // Forbid this call, because the bound is not respected. + // :: error: (argument.type.incompatible) + // :: warning: [unchecked] unchecked call to handler(H) as a member of the raw type + // Issue269.CallbackNN + callback.handler(null); + } } diff --git a/checker/tests/nullness/generics/Issue270.java b/checker/tests/nullness/generics/Issue270.java index 0ea00972084..500fcdc5d78 100644 --- a/checker/tests/nullness/generics/Issue270.java +++ b/checker/tests/nullness/generics/Issue270.java @@ -2,11 +2,11 @@ // ::error: (bound.type.incompatible) public class Issue270<@Nullable TypeParam extends @NonNull Object> { - public static void main() { + public static void main() { - // ::error: (type.argument.type.incompatible) - @Nullable Issue270<@Nullable String> strWAtv = null; - // ::error: (type.argument.type.incompatible) - @Nullable Issue270<@NonNull Integer> intWAtv = null; - } + // ::error: (type.argument.type.incompatible) + @Nullable Issue270<@Nullable String> strWAtv = null; + // ::error: (type.argument.type.incompatible) + @Nullable Issue270<@NonNull Integer> intWAtv = null; + } } diff --git a/checker/tests/nullness/generics/Issue2722.java b/checker/tests/nullness/generics/Issue2722.java index ec1eca2d1c4..d364e68ef6a 100644 --- a/checker/tests/nullness/generics/Issue2722.java +++ b/checker/tests/nullness/generics/Issue2722.java @@ -5,15 +5,15 @@ import java.util.List; class Issue2722 { - void foo() { - passThrough(Arrays.asList("x")).get(0).length(); - } + void foo() { + passThrough(Arrays.asList("x")).get(0).length(); + } - String bar() { - return passThrough(Arrays.asList("x")).get(0); - } + String bar() { + return passThrough(Arrays.asList("x")).get(0); + } - List passThrough(List object) { - return object; - } + List passThrough(List object) { + return object; + } } diff --git a/checker/tests/nullness/generics/Issue282.java b/checker/tests/nullness/generics/Issue282.java index 635b883c6f8..268d7b60fbf 100644 --- a/checker/tests/nullness/generics/Issue282.java +++ b/checker/tests/nullness/generics/Issue282.java @@ -1,29 +1,30 @@ // Test case for Issue 282 // https://github.com/typetools/checker-framework/issues/282 +import org.checkerframework.checker.nullness.qual.*; + import java.util.Collection; import java.util.Comparator; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; @SuppressWarnings("nullness") abstract class ImmutableSortedSet implements Set { - static ImmutableSortedSet copyOf( - Comparator comparator, Collection elements) { - return null; - } + static ImmutableSortedSet copyOf( + Comparator comparator, Collection elements) { + return null; + } } @SuppressWarnings("nullness") abstract class Ordering implements Comparator { - static Ordering usingToString() { - return null; - } + static Ordering usingToString() { + return null; + } } abstract class Example { - private static <@NonNull T extends @NonNull Object> ImmutableSortedSet setSortedByToString( - Collection set) { - return ImmutableSortedSet.copyOf(Ordering.usingToString(), set); - } + private static <@NonNull T extends @NonNull Object> ImmutableSortedSet setSortedByToString( + Collection set) { + return ImmutableSortedSet.copyOf(Ordering.usingToString(), set); + } } diff --git a/checker/tests/nullness/generics/Issue282Min.java b/checker/tests/nullness/generics/Issue282Min.java index 7b590a2187d..887fa641f71 100644 --- a/checker/tests/nullness/generics/Issue282Min.java +++ b/checker/tests/nullness/generics/Issue282Min.java @@ -1,20 +1,21 @@ // Test case for Issue 282 (minimized) // https://github.com/typetools/checker-framework/issues/282 +import org.checkerframework.checker.nullness.qual.*; + import java.util.Collection; import java.util.Comparator; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; public class Issue282Min { - static Set copyOf(Comparator comparator, Collection elements) { - // :: error: (return.type.incompatible) - return null; - } + static Set copyOf(Comparator comparator, Collection elements) { + // :: error: (return.type.incompatible) + return null; + } } class Example282Min { - Set foo(Comparator ord, Collection set) { - return Issue282Min.copyOf(ord, set); - } + Set foo(Comparator ord, Collection set) { + return Issue282Min.copyOf(ord, set); + } } diff --git a/checker/tests/nullness/generics/Issue2995.java b/checker/tests/nullness/generics/Issue2995.java index a21a7c8f268..0a0baa1a2d2 100644 --- a/checker/tests/nullness/generics/Issue2995.java +++ b/checker/tests/nullness/generics/Issue2995.java @@ -4,11 +4,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue2995 { - interface Set {} + interface Set {} - class Map { - Set keySet = new KeySet(); + class Map { + Set keySet = new KeySet(); - class KeySet implements Set {} - } + class KeySet implements Set {} + } } diff --git a/checker/tests/nullness/generics/Issue3025.java b/checker/tests/nullness/generics/Issue3025.java index 1fd11bb8f4c..8e7bab4f2c2 100644 --- a/checker/tests/nullness/generics/Issue3025.java +++ b/checker/tests/nullness/generics/Issue3025.java @@ -5,12 +5,12 @@ // Classes need to be separate top-level classes to reproduce the issue class Issue3025Caller { - void foo(Issue3025Sub arg) { - bar(arg); - hashCode(); - } + void foo(Issue3025Sub arg) { + bar(arg); + hashCode(); + } - void bar(Issue3025Sub arg) {} + void bar(Issue3025Sub arg) {} } interface Issue3025Super {} diff --git a/checker/tests/nullness/generics/Issue3027.java b/checker/tests/nullness/generics/Issue3027.java index 56d8dbfa6f4..c58933839b1 100644 --- a/checker/tests/nullness/generics/Issue3027.java +++ b/checker/tests/nullness/generics/Issue3027.java @@ -4,15 +4,15 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Issue3027 { - class Caller { - void foo(Multiset multiset) { - Entry entry = multiset.someEntry(); + class Caller { + void foo(Multiset multiset) { + Entry entry = multiset.someEntry(); + } } - } - interface Multiset { - Entry someEntry(); - } + interface Multiset { + Entry someEntry(); + } - interface Entry {} + interface Entry {} } diff --git a/checker/tests/nullness/generics/Issue312.java b/checker/tests/nullness/generics/Issue312.java index daf35b9b7e7..45b2b1b064d 100644 --- a/checker/tests/nullness/generics/Issue312.java +++ b/checker/tests/nullness/generics/Issue312.java @@ -7,23 +7,23 @@ // see below for the code that uses Guava. @SuppressWarnings("nullness") class Ordering312 { - public static Ordering312 natural() { - return null; - } + public static Ordering312 natural() { + return null; + } - public Ordering312 reverse() { - return null; - } + public Ordering312 reverse() { + return null; + } - public List sortedCopy(Iterable elements) { - return null; - } + public List sortedCopy(Iterable elements) { + return null; + } } public class Issue312 { - void test(List list) { - Ordering312.natural().reverse().sortedCopy(list); - } + void test(List list) { + Ordering312.natural().reverse().sortedCopy(list); + } } /* Original test using Guava: diff --git a/checker/tests/nullness/generics/Issue313.java b/checker/tests/nullness/generics/Issue313.java index 583ce6775e2..fcaa819ffda 100644 --- a/checker/tests/nullness/generics/Issue313.java +++ b/checker/tests/nullness/generics/Issue313.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue313 { - class A<@NonNull T extends @Nullable Object> {} + class A<@NonNull T extends @Nullable Object> {} - <@NonNull X extends @Nullable Object> void m() { - new A(); - } + <@NonNull X extends @Nullable Object> void m() { + new A(); + } } diff --git a/checker/tests/nullness/generics/Issue319.java b/checker/tests/nullness/generics/Issue319.java index 4cc7dbb8bfc..fadf581879a 100644 --- a/checker/tests/nullness/generics/Issue319.java +++ b/checker/tests/nullness/generics/Issue319.java @@ -4,46 +4,46 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue319 { - class Foo { - Foo(@Nullable T t) {} - } + class Foo { + Foo(@Nullable T t) {} + } - Foo newFoo(@Nullable T t) { - return new Foo<>(t); - } + Foo newFoo(@Nullable T t) { + return new Foo<>(t); + } - void pass() { - Foo f = newFoo(Boolean.FALSE); - } + void pass() { + Foo f = newFoo(Boolean.FALSE); + } - void fail() { - Foo f = newFoo(null); - } + void fail() { + Foo f = newFoo(null); + } - void workaround() { - Foo f = Issue319.this.newFoo(null); - } + void workaround() { + Foo f = Issue319.this.newFoo(null); + } } class Issue319NN { - class Foo { - Foo(@NonNull T t) {} - } - - Foo newFoo(@NonNull T t) { - return new Foo<>(t); - } - - void pass() { - Foo f = newFoo(Boolean.FALSE); - } - - void fail() { - // :: error: (argument.type.incompatible) - Foo f = newFoo(null); - } - - void pass2() { - Foo<@Nullable Boolean> f = newFoo(Boolean.FALSE); - } + class Foo { + Foo(@NonNull T t) {} + } + + Foo newFoo(@NonNull T t) { + return new Foo<>(t); + } + + void pass() { + Foo f = newFoo(Boolean.FALSE); + } + + void fail() { + // :: error: (argument.type.incompatible) + Foo f = newFoo(null); + } + + void pass2() { + Foo<@Nullable Boolean> f = newFoo(Boolean.FALSE); + } } diff --git a/checker/tests/nullness/generics/Issue326.java b/checker/tests/nullness/generics/Issue326.java index dc275a9c88a..2a3c7048379 100644 --- a/checker/tests/nullness/generics/Issue326.java +++ b/checker/tests/nullness/generics/Issue326.java @@ -1,11 +1,12 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.HashSet; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue326 { - { - Set<@Nullable String> local = new HashSet<>(); - } + { + Set<@Nullable String> local = new HashSet<>(); + } - Set<@Nullable String> field = new HashSet<>(); + Set<@Nullable String> field = new HashSet<>(); } diff --git a/checker/tests/nullness/generics/Issue329.java b/checker/tests/nullness/generics/Issue329.java index de42a45acc9..5eb65e281a1 100644 --- a/checker/tests/nullness/generics/Issue329.java +++ b/checker/tests/nullness/generics/Issue329.java @@ -4,36 +4,36 @@ import org.checkerframework.checker.nullness.qual.Nullable; abstract class Issue329 { - interface Flag {} + interface Flag {} - abstract void setExtension(X value); + abstract void setExtension(X value); - abstract T getValue(Flag flag); + abstract T getValue(Flag flag); - void f(Flag flag) { - String s = getValue(flag); - setExtension(s); + void f(Flag flag) { + String s = getValue(flag); + setExtension(s); - setExtension(getValue(flag)); - } + setExtension(getValue(flag)); + } } abstract class Issue329NN { - interface Flag {} + interface Flag {} - // Explicit bound makes it NonNull - abstract void setExtension(X value); + // Explicit bound makes it NonNull + abstract void setExtension(X value); - abstract T getValue(Flag flag); + abstract T getValue(Flag flag); - void f1(Flag<@Nullable String> flag) { - String s = getValue(flag); - // :: error: (type.arguments.not.inferred) - setExtension(s); - } + void f1(Flag<@Nullable String> flag) { + String s = getValue(flag); + // :: error: (type.arguments.not.inferred) + setExtension(s); + } - void f2(Flag<@Nullable String> flag) { - // :: error: (type.arguments.not.inferred) - setExtension(getValue(flag)); - } + void f2(Flag<@Nullable String> flag) { + // :: error: (type.arguments.not.inferred) + setExtension(getValue(flag)); + } } diff --git a/checker/tests/nullness/generics/Issue335.java b/checker/tests/nullness/generics/Issue335.java index ddf023c7625..78037ab5d7c 100644 --- a/checker/tests/nullness/generics/Issue335.java +++ b/checker/tests/nullness/generics/Issue335.java @@ -4,19 +4,19 @@ import org.checkerframework.checker.nullness.qual.Nullable; class Pair { - static Pair of(@Nullable C first, @Nullable D second) { - throw new RuntimeException(); - } + static Pair of(@Nullable C first, @Nullable D second) { + throw new RuntimeException(); + } } class Optional { - static Optional of(T reference) { - throw new RuntimeException(); - } + static Optional of(T reference) { + throw new RuntimeException(); + } } public class Issue335 { - Optional> m(String one, String two) { - return Optional.of(Pair.of(one, two)); - } + Optional> m(String one, String two) { + return Optional.of(Pair.of(one, two)); + } } diff --git a/checker/tests/nullness/generics/Issue337.java b/checker/tests/nullness/generics/Issue337.java index 34fa80d2624..142b24be048 100644 --- a/checker/tests/nullness/generics/Issue337.java +++ b/checker/tests/nullness/generics/Issue337.java @@ -4,29 +4,29 @@ import javax.annotation.Nullable; abstract class Issue337 { - abstract R getThing(String key); + abstract R getThing(String key); - @Nullable R m1(@Nullable String key) { - return (key == null) ? null : getThing(key); - } + @Nullable R m1(@Nullable String key) { + return (key == null) ? null : getThing(key); + } - @Nullable R m1b(@Nullable String key) { - return (key != null) ? getThing(key) : null; - } + @Nullable R m1b(@Nullable String key) { + return (key != null) ? getThing(key) : null; + } - @Nullable R m2(@Nullable String key) { - return (key == null) - ? - // :: error: (argument.type.incompatible) - getThing(key) - : null; - } + @Nullable R m2(@Nullable String key) { + return (key == null) + ? + // :: error: (argument.type.incompatible) + getThing(key) + : null; + } - @Nullable R m2b(@Nullable String key) { - return (key != null) - ? null - : - // :: error: (argument.type.incompatible) - getThing(key); - } + @Nullable R m2b(@Nullable String key) { + return (key != null) + ? null + : + // :: error: (argument.type.incompatible) + getThing(key); + } } diff --git a/checker/tests/nullness/generics/Issue339.java b/checker/tests/nullness/generics/Issue339.java index a092331e3b6..34c67a9544d 100644 --- a/checker/tests/nullness/generics/Issue339.java +++ b/checker/tests/nullness/generics/Issue339.java @@ -4,13 +4,13 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue339 { - static @NonNull T checkNotNull(T p) { - throw new RuntimeException(); - } + static @NonNull T checkNotNull(T p) { + throw new RuntimeException(); + } - void m(@Nullable S s) { - @NonNull S r1 = Issue339.<@Nullable S>checkNotNull(s); - @NonNull S r2 = Issue339.checkNotNull(s); - @NonNull S r3 = Issue339.checkNotNull(null); - } + void m(@Nullable S s) { + @NonNull S r1 = Issue339.<@Nullable S>checkNotNull(s); + @NonNull S r2 = Issue339.checkNotNull(s); + @NonNull S r3 = Issue339.checkNotNull(null); + } } diff --git a/checker/tests/nullness/generics/Issue421.java b/checker/tests/nullness/generics/Issue421.java index fd0d400e9cb..02f80b90b8a 100644 --- a/checker/tests/nullness/generics/Issue421.java +++ b/checker/tests/nullness/generics/Issue421.java @@ -1,16 +1,16 @@ public class Issue421 { - abstract static class C { - abstract X getX(); - } + abstract static class C { + abstract X getX(); + } - interface X {} + interface X {} - abstract static class R { - abstract boolean d(X id); - } + abstract static class R { + abstract boolean d(X id); + } - private void f(C c, R r) { - X x = c.getX(); - boolean bval = r.d(x); - } + private void f(C c, R r) { + X x = c.getX(); + boolean bval = r.d(x); + } } diff --git a/checker/tests/nullness/generics/Issue422.java b/checker/tests/nullness/generics/Issue422.java index 5ebec4ce3c7..3d9ee4dad52 100644 --- a/checker/tests/nullness/generics/Issue422.java +++ b/checker/tests/nullness/generics/Issue422.java @@ -1,6 +1,6 @@ public class Issue422 { - public boolean f(T newValue, T oldValue) { - return (oldValue instanceof Boolean || oldValue instanceof Integer) - && oldValue.equals(newValue); - } + public boolean f(T newValue, T oldValue) { + return (oldValue instanceof Boolean || oldValue instanceof Integer) + && oldValue.equals(newValue); + } } diff --git a/checker/tests/nullness/generics/Issue428.java b/checker/tests/nullness/generics/Issue428.java index 26bf85889e0..c5bca87fe00 100644 --- a/checker/tests/nullness/generics/Issue428.java +++ b/checker/tests/nullness/generics/Issue428.java @@ -6,7 +6,7 @@ public interface Issue428 {} class Test428 { - void m(List> is) { - Issue428 i = is.get(0); - } + void m(List> is) { + Issue428 i = is.get(0); + } } diff --git a/checker/tests/nullness/generics/Issue459.java b/checker/tests/nullness/generics/Issue459.java index 18a7f84d230..aff1ad8e6ec 100644 --- a/checker/tests/nullness/generics/Issue459.java +++ b/checker/tests/nullness/generics/Issue459.java @@ -1,20 +1,20 @@ import org.checkerframework.checker.nullness.qual.*; public class Issue459 { - public class Generic {} + public class Generic {} - interface Iface { - public Generic foo(Generic arg); + interface Iface { + public Generic foo(Generic arg); - public Generic foo2( - Generic arg, K2 strArg); - } + public Generic foo2( + Generic arg, K2 strArg); + } - void f(Iface arg, @NonNull String nnString) { - final Generic obj = new Generic<>(); - arg.foo(obj); + void f(Iface arg, @NonNull String nnString) { + final Generic obj = new Generic<>(); + arg.foo(obj); - final Generic<@Nullable String, Integer> obj2 = new Generic<>(); - arg.foo2(obj2, nnString); - } + final Generic<@Nullable String, Integer> obj2 = new Generic<>(); + arg.foo2(obj2, nnString); + } } diff --git a/checker/tests/nullness/generics/Issue5006.java b/checker/tests/nullness/generics/Issue5006.java index 5689a117131..30c88dc5393 100644 --- a/checker/tests/nullness/generics/Issue5006.java +++ b/checker/tests/nullness/generics/Issue5006.java @@ -1,18 +1,18 @@ public class Issue5006 { - static class C { - T get() { - throw new RuntimeException(""); + static class C { + T get() { + throw new RuntimeException(""); + } } - } - interface X { - C get(); - } + interface X { + C get(); + } - interface Y extends X { - @Override - // :: error: (type.invalid.super.wildcard) - C get(); - } + interface Y extends X { + @Override + // :: error: (type.invalid.super.wildcard) + C get(); + } } diff --git a/checker/tests/nullness/generics/Issue6374.java b/checker/tests/nullness/generics/Issue6374.java index 37c6933f54e..199a46d9ffc 100644 --- a/checker/tests/nullness/generics/Issue6374.java +++ b/checker/tests/nullness/generics/Issue6374.java @@ -7,31 +7,31 @@ public class Issue6374 { - @SuppressWarnings("unchecked") // ignore heap pollution - static class Lib { - // element type inferred, array non-null - static void none(T... o) {} + @SuppressWarnings("unchecked") // ignore heap pollution + static class Lib { + // element type inferred, array non-null + static void none(T... o) {} - // element type inferred, array non-null - static void decl(@NonNull T... o) {} + // element type inferred, array non-null + static void decl(@NonNull T... o) {} - // element type nullable, array non-null - static void type(@Nullable T... o) {} + // element type nullable, array non-null + static void type(@Nullable T... o) {} - // element type nullable, array nullable - static void typenn(@Nullable T @Nullable ... o) {} - } + // element type nullable, array nullable + static void typenn(@Nullable T @Nullable ... o) {} + } - class User { - void go() { - Lib.decl("", null); - // :: error: (argument.type.incompatible) - Lib.decl((Object[]) null); - Lib.type("", null); - // :: error: (argument.type.incompatible) - Lib.type((Object[]) null); - Lib.typenn("", null); - Lib.typenn((Object[]) null); + class User { + void go() { + Lib.decl("", null); + // :: error: (argument.type.incompatible) + Lib.decl((Object[]) null); + Lib.type("", null); + // :: error: (argument.type.incompatible) + Lib.type((Object[]) null); + Lib.typenn("", null); + Lib.typenn((Object[]) null); + } } - } } diff --git a/checker/tests/nullness/generics/Issue783a.java b/checker/tests/nullness/generics/Issue783a.java index 7b3649ed89e..aabebcfc67c 100644 --- a/checker/tests/nullness/generics/Issue783a.java +++ b/checker/tests/nullness/generics/Issue783a.java @@ -10,13 +10,13 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue783a { - private @Nullable T val; + private @Nullable T val; - public void set(@Nullable T val) { - this.val = val; - } + public void set(@Nullable T val) { + this.val = val; + } - public @Nullable T get() { - return val; - } + public @Nullable T get() { + return val; + } } diff --git a/checker/tests/nullness/generics/Issue783b.java b/checker/tests/nullness/generics/Issue783b.java index 5e1bc1a3aff..684dcdf3b98 100644 --- a/checker/tests/nullness/generics/Issue783b.java +++ b/checker/tests/nullness/generics/Issue783b.java @@ -10,13 +10,13 @@ import javax.annotation.Nullable; public class Issue783b { - private @Nullable T val; + private @Nullable T val; - public void set(@Nullable T val) { - this.val = val; - } + public void set(@Nullable T val) { + this.val = val; + } - @Nullable public T get() { - return val; - } + @Nullable public T get() { + return val; + } } diff --git a/checker/tests/nullness/generics/Issue849.java b/checker/tests/nullness/generics/Issue849.java index 48a654e23fa..fc51a7ee45a 100644 --- a/checker/tests/nullness/generics/Issue849.java +++ b/checker/tests/nullness/generics/Issue849.java @@ -5,10 +5,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Issue849 { - class Gen {} + class Gen {} - void nullness(Gen> genGenNonNull) { - // :: error: (assignment.type.incompatible) - Gen<@Nullable ? extends @Nullable Gen<@Nullable Object>> a = genGenNonNull; - } + void nullness(Gen> genGenNonNull) { + // :: error: (assignment.type.incompatible) + Gen<@Nullable ? extends @Nullable Gen<@Nullable Object>> a = genGenNonNull; + } } diff --git a/checker/tests/nullness/generics/KeyForPolyKeyFor.java b/checker/tests/nullness/generics/KeyForPolyKeyFor.java index a9f0ea4a331..4ed32c406e3 100644 --- a/checker/tests/nullness/generics/KeyForPolyKeyFor.java +++ b/checker/tests/nullness/generics/KeyForPolyKeyFor.java @@ -1,26 +1,27 @@ package nullness.generics; +import org.checkerframework.checker.nullness.qual.*; + import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; // test related to issue 429: https://github.com/typetools/checker-framework/issues/429 public class KeyForPolyKeyFor { - // TODO: Figure out why diamond operator does not work: - // Map<@KeyFor("dict") String, String> dict = new HashMap<>(); - Map<@KeyFor("dict") String, String> dict = new HashMap<@KeyFor("dict") String, String>(); + // TODO: Figure out why diamond operator does not work: + // Map<@KeyFor("dict") String, String> dict = new HashMap<>(); + Map<@KeyFor("dict") String, String> dict = new HashMap<@KeyFor("dict") String, String>(); - void m() { - Set<@KeyFor("dict") String> s = nounSubset(dict.keySet()); + void m() { + Set<@KeyFor("dict") String> s = nounSubset(dict.keySet()); - for (@KeyFor("dict") String noun : nounSubset(dict.keySet())) {} - } + for (@KeyFor("dict") String noun : nounSubset(dict.keySet())) {} + } - // This method's declaration uses no @KeyFor annotations because in addition to being used by - // the dictionary feature, it is also used by a spell checker that only stores sets of words and - // does not use the notions of dictionaries, maps or keys. - Set<@PolyKeyFor String> nounSubset(Set<@PolyKeyFor String> words) { - return words; - } + // This method's declaration uses no @KeyFor annotations because in addition to being used by + // the dictionary feature, it is also used by a spell checker that only stores sets of words and + // does not use the notions of dictionaries, maps or keys. + Set<@PolyKeyFor String> nounSubset(Set<@PolyKeyFor String> words) { + return words; + } } diff --git a/checker/tests/nullness/generics/MapLoop.java b/checker/tests/nullness/generics/MapLoop.java index 616ac7364be..0017b9463b5 100644 --- a/checker/tests/nullness/generics/MapLoop.java +++ b/checker/tests/nullness/generics/MapLoop.java @@ -1,17 +1,18 @@ -import java.util.Map; import org.checkerframework.checker.nullness.qual.*; +import java.util.Map; + public class MapLoop { - void test1(Map map) { - for (Map.Entry<@KeyFor("map") String, String> entry : map.entrySet()) {} - } + void test1(Map map) { + for (Map.Entry<@KeyFor("map") String, String> entry : map.entrySet()) {} + } - void test2(Map map) { - for (Map.Entry entry : map.entrySet()) {} - } + void test2(Map map) { + for (Map.Entry entry : map.entrySet()) {} + } - void test3(Map map) { - for (Map.Entry entry : map.entrySet()) {} - for (Object val : map.values()) {} - } + void test3(Map map) { + for (Map.Entry entry : map.entrySet()) {} + for (Object val : map.values()) {} + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars.java b/checker/tests/nullness/generics/MethodTypeVars.java index 15c8bd01d5c..0e6670798ad 100644 --- a/checker/tests/nullness/generics/MethodTypeVars.java +++ b/checker/tests/nullness/generics/MethodTypeVars.java @@ -5,34 +5,34 @@ * https://github.com/typetools/checker-framework/issues/93 */ public class MethodTypeVars { - void m() { - // :: error: (type.arguments.not.inferred) - Object a = A.badMethod(null); - Object b = A.badMethod(new Object()); + void m() { + // :: error: (type.arguments.not.inferred) + Object a = A.badMethod(null); + Object b = A.badMethod(new Object()); - // :: error: (type.arguments.not.inferred) - A.goodMethod(null); - A.goodMethod(new Object()); - } + // :: error: (type.arguments.not.inferred) + A.goodMethod(null); + A.goodMethod(new Object()); + } } class A { - public static T badMethod(T t) { - // :: warning: [unchecked] unchecked cast - return (T) new Object(); - } + public static T badMethod(T t) { + // :: warning: [unchecked] unchecked cast + return (T) new Object(); + } - public static void goodMethod(T t) {} + public static void goodMethod(T t) {} } class B { - public void indexOf1(T[] a, @Nullable Object elt) {} + public void indexOf1(T[] a, @Nullable Object elt) {} - // This is not valid Java syntax. - // public void indexOf2(?[] a, @Nullable Object elt) {} + // This is not valid Java syntax. + // public void indexOf2(?[] a, @Nullable Object elt) {} - void call() { - Integer[] arg = new Integer[] {1, 2, 3, 4}; - indexOf1(arg, Integer.valueOf(5)); - } + void call() { + Integer[] arg = new Integer[] {1, 2, 3, 4}; + indexOf1(arg, Integer.valueOf(5)); + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars2.java b/checker/tests/nullness/generics/MethodTypeVars2.java index 603b7060609..68885a3a54b 100644 --- a/checker/tests/nullness/generics/MethodTypeVars2.java +++ b/checker/tests/nullness/generics/MethodTypeVars2.java @@ -2,34 +2,34 @@ public class MethodTypeVars2 { - class GeoSegment {} + class GeoSegment {} - interface Path> {} + interface Path> {} - private static > @Nullable Object pathToRoute( - Path path) { - return null; - } + private static > @Nullable Object pathToRoute( + Path path) { + return null; + } - class StreetSegment extends GeoSegment {} + class StreetSegment extends GeoSegment {} - class StreetSegmentPath implements Path {} + class StreetSegmentPath implements Path {} - void call(StreetSegmentPath p) { - Object r = pathToRoute(p); - } + void call(StreetSegmentPath p) { + Object r = pathToRoute(p); + } - static class WorkingWithOne { - interface GPath

          > {} + static class WorkingWithOne { + interface GPath

          > {} - class GStreetSegmentPath implements GPath {} + class GStreetSegmentPath implements GPath {} - private static

          > @Nullable Object pathToRoute(GPath

          path) { - return null; - } + private static

          > @Nullable Object pathToRoute(GPath

          path) { + return null; + } - void call(GStreetSegmentPath p) { - Object r = pathToRoute(p); + void call(GStreetSegmentPath p) { + Object r = pathToRoute(p); + } } - } } diff --git a/checker/tests/nullness/generics/MethodTypeVars3.java b/checker/tests/nullness/generics/MethodTypeVars3.java index 3880bcb60af..79e2cbf10db 100644 --- a/checker/tests/nullness/generics/MethodTypeVars3.java +++ b/checker/tests/nullness/generics/MethodTypeVars3.java @@ -1,43 +1,44 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; public class MethodTypeVars3 { - public static <@KeyFor("#1") T extends @KeyFor("#1") Object> Map> dominators( - Map> preds) { - List nodes = new ArrayList<>(preds.keySet()); - - // Compute roots & non-roots, for convenience - List<@KeyFor("preds") T> roots = new ArrayList<>(); - List<@KeyFor("preds") T> non_roots = new ArrayList<>(); - - Map<@KeyFor("preds") T, List> dom = new HashMap<>(); - - // Initialize result: for roots just the root, otherwise everything - for (@KeyFor("preds") T node : preds.keySet()) { - if (preds.get(node).isEmpty()) { - // This is a root - roots.add(node); - // Its only dominator is itself. - Set<@KeyFor("preds") T> set = Collections.singleton(node); - - dom.put(node, new ArrayList(set)); - - dom.put(node, new ArrayList(Collections.singleton(node))); - } else { - non_roots.add(node); - dom.put(node, new ArrayList(nodes)); - } + public static <@KeyFor("#1") T extends @KeyFor("#1") Object> Map> dominators( + Map> preds) { + List nodes = new ArrayList<>(preds.keySet()); + + // Compute roots & non-roots, for convenience + List<@KeyFor("preds") T> roots = new ArrayList<>(); + List<@KeyFor("preds") T> non_roots = new ArrayList<>(); + + Map<@KeyFor("preds") T, List> dom = new HashMap<>(); + + // Initialize result: for roots just the root, otherwise everything + for (@KeyFor("preds") T node : preds.keySet()) { + if (preds.get(node).isEmpty()) { + // This is a root + roots.add(node); + // Its only dominator is itself. + Set<@KeyFor("preds") T> set = Collections.singleton(node); + + dom.put(node, new ArrayList(set)); + + dom.put(node, new ArrayList(Collections.singleton(node))); + } else { + non_roots.add(node); + dom.put(node, new ArrayList(nodes)); + } + } + + return dom; } - return dom; - } - - void test(Map> dom, XXX node) { - dom.put(node, new ArrayList(Collections.singleton(node))); - } + void test(Map> dom, XXX node) { + dom.put(node, new ArrayList(Collections.singleton(node))); + } } diff --git a/checker/tests/nullness/generics/MethodTypeVars5.java b/checker/tests/nullness/generics/MethodTypeVars5.java index f0255c2223a..2b8803de492 100644 --- a/checker/tests/nullness/generics/MethodTypeVars5.java +++ b/checker/tests/nullness/generics/MethodTypeVars5.java @@ -1,92 +1,92 @@ import org.checkerframework.checker.nullness.qual.*; public class MethodTypeVars5 { - class B { - S t; + class B { + S t; - B(S t) { - this.t = t; + B(S t) { + this.t = t; + } + + S get() { + return t; + } + } + + B b = new B<>("Hello World"); + + String doit1() { + return doit1(b); + } + + U doit1(B x) { + return x.get(); + } + + String doit2() { + // Passing the null argument has no effect on the inferred type argument: + // the second parameter type doesn't contain the type variable at all. + return doit2(b, null); + } + + T doit2(B x, @Nullable String y) { + return x.get(); + } + + String doit3() { + // Passing the null argument has no effect on the inferred type argument: + // the type variable only appears as nested type. + return doit3(null); + } + + String doit3b() { + return doit3(new B("Hi")); } - S get() { - return t; + String doit3b2() { + return doit3(new B<>("Hi")); } - } - - B b = new B<>("Hello World"); - - String doit1() { - return doit1(b); - } - - U doit1(B x) { - return x.get(); - } - - String doit2() { - // Passing the null argument has no effect on the inferred type argument: - // the second parameter type doesn't contain the type variable at all. - return doit2(b, null); - } - - T doit2(B x, @Nullable String y) { - return x.get(); - } - - String doit3() { - // Passing the null argument has no effect on the inferred type argument: - // the type variable only appears as nested type. - return doit3(null); - } - - String doit3b() { - return doit3(new B("Hi")); - } - - String doit3b2() { - return doit3(new B<>("Hi")); - } - - String doit3c() { - // :: error: (return.type.incompatible) - // :: error: (type.arguments.not.inferred) - return doit3(new B<@Nullable String>("Hi")); - } - - void doit3d() { - // :: error: (assignment.type.incompatible) - // :: error: (type.arguments.not.inferred) - @NonNull String s = doit3(new B<@Nullable String>("Hi")); - } - - void doit3e() { - String s = doit3(new B("Hi")); - } - - void doit3e2() { - String s = doit3(new B<>("Hi")); - } - - T doit3(@Nullable B x) { - if (x != null) { - return x.get(); - } else { - // This won't work at runtime, but whatever. - @SuppressWarnings("unchecked") - T res = (T) new Object(); - return res; + + String doit3c() { + // :: error: (return.type.incompatible) + // :: error: (type.arguments.not.inferred) + return doit3(new B<@Nullable String>("Hi")); + } + + void doit3d() { + // :: error: (assignment.type.incompatible) + // :: error: (type.arguments.not.inferred) + @NonNull String s = doit3(new B<@Nullable String>("Hi")); + } + + void doit3e() { + String s = doit3(new B("Hi")); + } + + void doit3e2() { + String s = doit3(new B<>("Hi")); + } + + T doit3(@Nullable B x) { + if (x != null) { + return x.get(); + } else { + // This won't work at runtime, but whatever. + @SuppressWarnings("unchecked") + T res = (T) new Object(); + return res; + } + } + + String doit4() { + // Passing the null argument has an impact on the inferred type argument: + // the type variable appears as the top-level type. + // :: error: (return.type.incompatible) + // :: error: (type.arguments.not.inferred) + return doit4("Ha!", null); + } + + T doit4(T x, T y) { + return x; } - } - - String doit4() { - // Passing the null argument has an impact on the inferred type argument: - // the type variable appears as the top-level type. - // :: error: (return.type.incompatible) - // :: error: (type.arguments.not.inferred) - return doit4("Ha!", null); - } - - T doit4(T x, T y) { - return x; - } } diff --git a/checker/tests/nullness/generics/MethodTypeVars6.java b/checker/tests/nullness/generics/MethodTypeVars6.java index f93070b41e8..ef73e1b6568 100644 --- a/checker/tests/nullness/generics/MethodTypeVars6.java +++ b/checker/tests/nullness/generics/MethodTypeVars6.java @@ -1,62 +1,62 @@ import org.checkerframework.checker.nullness.qual.*; class APair { - static APair of(U p1, V p2) { - return new APair(); - } + static APair of(U p1, V p2) { + return new APair(); + } - static APair of2(U p1, V p2) { - return new APair<>(); - } + static APair of2(U p1, V p2) { + return new APair<>(); + } } class PairSub extends APair { - static PairSub of( - US p1, VS p2) { - return new PairSub(); - } + static PairSub of( + US p1, VS p2) { + return new PairSub(); + } } class PairSubSwitching - extends APair { - static PairSubSwitching ofPSS( - US p1, VS p2) { - return new PairSubSwitching(); - } + extends APair { + static + PairSubSwitching ofPSS(US p1, VS p2) { + return new PairSubSwitching(); + } } class Test1 { - APair<@Nullable X, @Nullable X> test1(@Nullable X p) { - return APair.<@Nullable X, @Nullable X>of(p, (X) null); - } + APair<@Nullable X, @Nullable X> test1(@Nullable X p) { + return APair.<@Nullable X, @Nullable X>of(p, (X) null); + } } class Test2 { - APair<@Nullable X, @Nullable X> test1(@Nullable X p) { - return APair.of(p, (@Nullable X) null); - } - /* - APair<@Nullable X, @Nullable X> test2(@Nullable X p) { - // TODO cast: should this X mean the same as above?? - return APair.of(p, (X) null); - } - */ + APair<@Nullable X, @Nullable X> test1(@Nullable X p) { + return APair.of(p, (@Nullable X) null); + } + /* + APair<@Nullable X, @Nullable X> test2(@Nullable X p) { + // TODO cast: should this X mean the same as above?? + return APair.of(p, (X) null); + } + */ } class Test3 { - APair<@NonNull X, @NonNull X> test1(@Nullable X p) { - // :: error: (type.arguments.not.inferred) - return APair.of(p, (X) null); - } + APair<@NonNull X, @NonNull X> test1(@Nullable X p) { + // :: error: (type.arguments.not.inferred) + return APair.of(p, (X) null); + } } class Test4 { - APair<@Nullable String, Integer> psi = PairSub.of("Hi", 42); - APair<@Nullable String, Integer> psi2 = PairSub.of(null, 42); - // :: error: (type.arguments.not.inferred) - APair psi3 = PairSub.of(null, 42); - - APair<@Nullable String, Integer> psisw = PairSubSwitching.ofPSS(42, null); - // :: error: (type.arguments.not.inferred) - APair psisw2 = PairSubSwitching.ofPSS(42, null); + APair<@Nullable String, Integer> psi = PairSub.of("Hi", 42); + APair<@Nullable String, Integer> psi2 = PairSub.of(null, 42); + // :: error: (type.arguments.not.inferred) + APair psi3 = PairSub.of(null, 42); + + APair<@Nullable String, Integer> psisw = PairSubSwitching.ofPSS(42, null); + // :: error: (type.arguments.not.inferred) + APair psisw2 = PairSubSwitching.ofPSS(42, null); } diff --git a/checker/tests/nullness/generics/MethodTypeVars7.java b/checker/tests/nullness/generics/MethodTypeVars7.java index 56c96be1f88..aad846e5f47 100644 --- a/checker/tests/nullness/generics/MethodTypeVars7.java +++ b/checker/tests/nullness/generics/MethodTypeVars7.java @@ -6,76 +6,76 @@ abstract class MethodTypeVars7 { - abstract T val(@Nullable T value, T defaultValue); + abstract T val(@Nullable T value, T defaultValue); - void tests(@Nullable String t1, @NonNull String t2) { - @Nullable String s3 = val(t1, null); - } + void tests(@Nullable String t1, @NonNull String t2) { + @Nullable String s3 = val(t1, null); + } + + T validate(@Nullable T value, T defaultValue) { + return value != null && !value.toString().isEmpty() ? value : defaultValue; + } + + T validateIf(@Nullable T value, T defaultValue) { + if (value != null && !value.toString().isEmpty()) { + return value; + } else { + return defaultValue; + } + } + + T validate2(@Nullable T value, T defaultValue) { + return value == null || value.toString().isEmpty() ? defaultValue : value; + } + + T validate3(@Nullable T value, T defaultValue) { + return value != null ? value : defaultValue; + } + + T validate4(@Nullable T value, T defaultValue) { + return value == null ? defaultValue : value; + } - T validate(@Nullable T value, T defaultValue) { - return value != null && !value.toString().isEmpty() ? value : defaultValue; - } + T validatefail(@Nullable T value, T defaultValue) { + // :: error: (return.type.incompatible) + return ((value == null || !value.toString().isEmpty()) ? value : defaultValue); + } + + T validate2fail(@Nullable T value, T defaultValue) { + // :: error: (return.type.incompatible) + return ((value != null && value.toString().isEmpty()) ? defaultValue : value); + } + + T validate3fail(@Nullable T value3, T defaultValue3) { + // :: error: (return.type.incompatible) + return value3 == null ? value3 : defaultValue3; + } + + T validate4fail(@Nullable T value, T defaultValue) { + // :: error: (return.type.incompatible) + return value != null ? defaultValue : value; + } + + String test1(@Nullable String t1, @NonNull String t2) { + @Nullable String s1 = validate(t1, null); + @Nullable String s2 = validate(t2, null); + @NonNull String s3 = validate(t1, "N/A"); + @NonNull String s4 = validate(t2, "N/A"); + return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; + } + + String test2(@Nullable String t1, @NonNull String t2) { + @Nullable String s1 = validate(t1, t1); + @Nullable String s2 = validate(t2, t1); + @NonNull String s3 = validate(t1, t2); + @NonNull String s4 = validate(t2, t2); + return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; + } - T validateIf(@Nullable T value, T defaultValue) { - if (value != null && !value.toString().isEmpty()) { - return value; - } else { - return defaultValue; + void main(String[] args) { + System.out.println("test 1 " + test1("s_1", "s_2")); + System.out.println("test 2 " + test2("s_1", "s_2")); + System.out.println("test 1 " + test1(null, "s_2")); + System.out.println("test 2 " + test2(null, "s_2")); } - } - - T validate2(@Nullable T value, T defaultValue) { - return value == null || value.toString().isEmpty() ? defaultValue : value; - } - - T validate3(@Nullable T value, T defaultValue) { - return value != null ? value : defaultValue; - } - - T validate4(@Nullable T value, T defaultValue) { - return value == null ? defaultValue : value; - } - - T validatefail(@Nullable T value, T defaultValue) { - // :: error: (return.type.incompatible) - return ((value == null || !value.toString().isEmpty()) ? value : defaultValue); - } - - T validate2fail(@Nullable T value, T defaultValue) { - // :: error: (return.type.incompatible) - return ((value != null && value.toString().isEmpty()) ? defaultValue : value); - } - - T validate3fail(@Nullable T value3, T defaultValue3) { - // :: error: (return.type.incompatible) - return value3 == null ? value3 : defaultValue3; - } - - T validate4fail(@Nullable T value, T defaultValue) { - // :: error: (return.type.incompatible) - return value != null ? defaultValue : value; - } - - String test1(@Nullable String t1, @NonNull String t2) { - @Nullable String s1 = validate(t1, null); - @Nullable String s2 = validate(t2, null); - @NonNull String s3 = validate(t1, "N/A"); - @NonNull String s4 = validate(t2, "N/A"); - return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; - } - - String test2(@Nullable String t1, @NonNull String t2) { - @Nullable String s1 = validate(t1, t1); - @Nullable String s2 = validate(t2, t1); - @NonNull String s3 = validate(t1, t2); - @NonNull String s4 = validate(t2, t2); - return "[" + s1 + "\t" + s2 + "\t" + s3 + "\t" + s4 + "]"; - } - - void main(String[] args) { - System.out.println("test 1 " + test1("s_1", "s_2")); - System.out.println("test 2 " + test2("s_1", "s_2")); - System.out.println("test 1 " + test1(null, "s_2")); - System.out.println("test 2 " + test2(null, "s_2")); - } } diff --git a/checker/tests/nullness/generics/MixTypeAndDeclAnno.java b/checker/tests/nullness/generics/MixTypeAndDeclAnno.java index 358e9e0f630..3e326cce681 100644 --- a/checker/tests/nullness/generics/MixTypeAndDeclAnno.java +++ b/checker/tests/nullness/generics/MixTypeAndDeclAnno.java @@ -4,35 +4,35 @@ import org.checkerframework.checker.nullness.qual.Nullable; class MixTypeAndDeclAnno { - @NonNull T t; - @android.annotation.NonNull T tdecl; - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @Nullable T tdecl2; - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @android.annotation.Nullable Object f1; - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @Nullable Object f2; - // Nullable applies to the array itself, while NonNull apply to components of the array. - @android.annotation.Nullable @NonNull Object[] g; - // :: error: (type.invalid.conflicting.annos) - @android.annotation.Nullable Object @NonNull [] k; - - MixTypeAndDeclAnno( - @NonNull T t, - @android.annotation.NonNull T tdecl, - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @android.annotation.Nullable Object f1, - // :: error: (type.invalid.conflicting.annos) - @android.annotation.NonNull @Nullable Object f2, - // :: error: (type.invalid.conflicting.annos) - @android.annotation.Nullable Object @NonNull [] k) { - this.t = t; - this.tdecl = tdecl; + @NonNull T t; + @android.annotation.NonNull T tdecl; + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @Nullable T tdecl2; // :: error: (type.invalid.conflicting.annos) - this.f1 = f1; + @android.annotation.NonNull @android.annotation.Nullable Object f1; // :: error: (type.invalid.conflicting.annos) - this.f2 = f2; + @android.annotation.NonNull @Nullable Object f2; + // Nullable applies to the array itself, while NonNull apply to components of the array. + @android.annotation.Nullable @NonNull Object[] g; // :: error: (type.invalid.conflicting.annos) - this.k = k; - } + @android.annotation.Nullable Object @NonNull [] k; + + MixTypeAndDeclAnno( + @NonNull T t, + @android.annotation.NonNull T tdecl, + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @android.annotation.Nullable Object f1, + // :: error: (type.invalid.conflicting.annos) + @android.annotation.NonNull @Nullable Object f2, + // :: error: (type.invalid.conflicting.annos) + @android.annotation.Nullable Object @NonNull [] k) { + this.t = t; + this.tdecl = tdecl; + // :: error: (type.invalid.conflicting.annos) + this.f1 = f1; + // :: error: (type.invalid.conflicting.annos) + this.f2 = f2; + // :: error: (type.invalid.conflicting.annos) + this.k = k; + } } diff --git a/checker/tests/nullness/generics/MyMap.java b/checker/tests/nullness/generics/MyMap.java index b3d46b281d5..f81f38ba34a 100644 --- a/checker/tests/nullness/generics/MyMap.java +++ b/checker/tests/nullness/generics/MyMap.java @@ -1,19 +1,20 @@ -import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Map; + // Test case for Issue 173 // https://github.com/typetools/checker-framework/issues/173 public abstract class MyMap implements Map { - @Override - // :: error: (contracts.postcondition.not.satisfied) - public @Nullable V put(K key, V value) { - return null; - } + @Override + // :: error: (contracts.postcondition.not.satisfied) + public @Nullable V put(K key, V value) { + return null; + } - @Override - public void putAll(Map map) { - for (Map.Entry entry : map.entrySet()) { - put(entry.getKey(), entry.getValue()); + @Override + public void putAll(Map map) { + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } } - } } diff --git a/checker/tests/nullness/generics/NullableGeneric.java b/checker/tests/nullness/generics/NullableGeneric.java index 801bae356a0..447dd5e659a 100644 --- a/checker/tests/nullness/generics/NullableGeneric.java +++ b/checker/tests/nullness/generics/NullableGeneric.java @@ -2,36 +2,36 @@ public class NullableGeneric { - public static class NullablePair { - public @Nullable T1 a; - public @Nullable T2 b; - public @NonNull T1 nna; - public @NonNull T2 nnb; + public static class NullablePair { + public @Nullable T1 a; + public @Nullable T2 b; + public @NonNull T1 nna; + public @NonNull T2 nnb; - public NullablePair(T1 a, T2 b) { - this.a = a; - this.b = b; - // :: error: (assignment.type.incompatible) - this.nna = a; - // :: error: (assignment.type.incompatible) - this.nnb = b; + public NullablePair(T1 a, T2 b) { + this.a = a; + this.b = b; + // :: error: (assignment.type.incompatible) + this.nna = a; + // :: error: (assignment.type.incompatible) + this.nnb = b; + } } - } - @Nullable T next1 = null, next2 = null; + @Nullable T next1 = null, next2 = null; - private NullablePair<@Nullable T, @Nullable T> return1() { - NullablePair<@Nullable T, @Nullable T> result = - new NullablePair<@Nullable T, @Nullable T>(next1, null); - // setnext1(); - return result; - } + private NullablePair<@Nullable T, @Nullable T> return1() { + NullablePair<@Nullable T, @Nullable T> result = + new NullablePair<@Nullable T, @Nullable T>(next1, null); + // setnext1(); + return result; + } - public static @NonNull T3 checkNotNull(@Nullable T3 object) { - if (object == null) { - throw new NullPointerException(); - } else { - return object; + public static @NonNull T3 checkNotNull(@Nullable T3 object) { + if (object == null) { + throw new NullPointerException(); + } else { + return object; + } } - } } diff --git a/checker/tests/nullness/generics/NullnessBound.java b/checker/tests/nullness/generics/NullnessBound.java index bf407d2ef93..70911d72edf 100644 --- a/checker/tests/nullness/generics/NullnessBound.java +++ b/checker/tests/nullness/generics/NullnessBound.java @@ -2,23 +2,23 @@ public class NullnessBound { - public void test() { - Gen1<@Nullable String> t1 = new Gen1<>(); - t1.add(null); + public void test() { + Gen1<@Nullable String> t1 = new Gen1<>(); + t1.add(null); - Gen2<@Nullable String> t2 = new Gen2<>(); - t2.add(null); + Gen2<@Nullable String> t2 = new Gen2<>(); + t2.add(null); - Gen1<@NonNull String> t3; - // :: error: (type.argument.type.incompatible) - Gen2<@NonNull String> t4; - } + Gen1<@NonNull String> t3; + // :: error: (type.argument.type.incompatible) + Gen2<@NonNull String> t4; + } - class Gen1 { - public void add(E e) {} - } + class Gen1 { + public void add(E e) {} + } - class Gen2<@Nullable E> { - public void add(E e) {} - } + class Gen2<@Nullable E> { + public void add(E e) {} + } } diff --git a/checker/tests/nullness/generics/OptionsTest.java b/checker/tests/nullness/generics/OptionsTest.java index 6b1b1da6605..d8d5f3a3acc 100644 --- a/checker/tests/nullness/generics/OptionsTest.java +++ b/checker/tests/nullness/generics/OptionsTest.java @@ -2,27 +2,27 @@ public class OptionsTest { - class MyAnnotation {} + class MyAnnotation {} - // Annotated identically to java.lang.reflect.Field.getAnnotation - public static @Nullable T1 getAnnotation( - Class<@NonNull T1> obj) { - return null; - } + // Annotated identically to java.lang.reflect.Field.getAnnotation + public static @Nullable T1 getAnnotation( + Class<@NonNull T1> obj) { + return null; + } - public static @Nullable MyAnnotation safeGetAnnotationNonGeneric( - Class<@NonNull MyAnnotation> annotationClass) { - @Nullable MyAnnotation cast = getAnnotation(annotationClass); - @Nullable MyAnnotation annotation = cast; - return annotation; - } + public static @Nullable MyAnnotation safeGetAnnotationNonGeneric( + Class<@NonNull MyAnnotation> annotationClass) { + @Nullable MyAnnotation cast = getAnnotation(annotationClass); + @Nullable MyAnnotation annotation = cast; + return annotation; + } - public static @Nullable T2 safeGetAnnotationGeneric( - Class<@NonNull T2> annotationClass) { - @Nullable T2 cast = getAnnotation(annotationClass); - @Nullable T2 annotation = cast; - return annotation; - } + public static @Nullable T2 safeGetAnnotationGeneric( + Class<@NonNull T2> annotationClass) { + @Nullable T2 cast = getAnnotation(annotationClass); + @Nullable T2 annotation = cast; + return annotation; + } } /* Local Variables: */ diff --git a/checker/tests/nullness/generics/RawTypesGenerics.java b/checker/tests/nullness/generics/RawTypesGenerics.java index fbc5ee84bda..634f23fa1f6 100644 --- a/checker/tests/nullness/generics/RawTypesGenerics.java +++ b/checker/tests/nullness/generics/RawTypesGenerics.java @@ -1,26 +1,26 @@ import org.checkerframework.checker.nullness.qual.*; public class RawTypesGenerics { - void m() throws ClassNotFoundException { - Class c1 = Class.forName("bla"); - Class c2 = Class.forName("bla"); - } + void m() throws ClassNotFoundException { + Class c1 = Class.forName("bla"); + Class c2 = Class.forName("bla"); + } - class Test {} + class Test {} - void bar() { - // Java will complain about this: - // Test x = new Test(); + void bar() { + // Java will complain about this: + // Test x = new Test(); - // ok - Test y = new Test(); + // ok + Test y = new Test(); - // :: error: (type.argument.type.incompatible) - Test z = new Test<@Nullable Integer>(); - } + // :: error: (type.argument.type.incompatible) + Test z = new Test<@Nullable Integer>(); + } - void m(java.lang.reflect.Constructor c) { - Class cls1 = c.getParameterTypes()[0]; - Class cls2 = c.getParameterTypes()[0]; - } + void m(java.lang.reflect.Constructor c) { + Class cls1 = c.getParameterTypes()[0]; + Class cls2 = c.getParameterTypes()[0]; + } } diff --git a/checker/tests/nullness/generics/SourceVsJdk.java b/checker/tests/nullness/generics/SourceVsJdk.java index 415fe6ade7e..48d5b8e2966 100644 --- a/checker/tests/nullness/generics/SourceVsJdk.java +++ b/checker/tests/nullness/generics/SourceVsJdk.java @@ -8,7 +8,7 @@ import java.util.Map; public class SourceVsJdk { - public Map getMap() { - return Collections.emptyMap(); - } + public Map getMap() { + return Collections.emptyMap(); + } } diff --git a/checker/tests/nullness/generics/SuperRawness.java b/checker/tests/nullness/generics/SuperRawness.java index d506b7aa654..d6f7e9f6e90 100644 --- a/checker/tests/nullness/generics/SuperRawness.java +++ b/checker/tests/nullness/generics/SuperRawness.java @@ -2,11 +2,11 @@ import java.util.Set; public class SuperRawness { - // :: warning: [unchecked] Possible heap pollution from parameterized vararg type - // java.util.Set - static void test(Set... args) { - test2(Arrays.asList(args)); - } + // :: warning: [unchecked] Possible heap pollution from parameterized vararg type + // java.util.Set + static void test(Set... args) { + test2(Arrays.asList(args)); + } - static void test2(Iterable> args) {} + static void test2(Iterable> args) {} } diff --git a/checker/tests/nullness/generics/TernaryGenerics.java b/checker/tests/nullness/generics/TernaryGenerics.java index 0076f5c7666..dadbdc49b09 100644 --- a/checker/tests/nullness/generics/TernaryGenerics.java +++ b/checker/tests/nullness/generics/TernaryGenerics.java @@ -1,48 +1,48 @@ import org.checkerframework.checker.nullness.qual.*; public class TernaryGenerics { - class Generic1 { - void cond(boolean b, T p) { - // :: error: (assignment.type.incompatible) - @NonNull T r1 = b ? p : null; - // :: error: (assignment.type.incompatible) - @NonNull T r2 = b ? null : p; + class Generic1 { + void cond(boolean b, T p) { + // :: error: (assignment.type.incompatible) + @NonNull T r1 = b ? p : null; + // :: error: (assignment.type.incompatible) + @NonNull T r2 = b ? null : p; + } } - } - class Generic2 { - void cond(boolean b, T p) { - // :: error: (assignment.type.incompatible) - @NonNull T r1 = b ? p : null; - // :: error: (assignment.type.incompatible) - @NonNull T r2 = b ? null : p; + class Generic2 { + void cond(boolean b, T p) { + // :: error: (assignment.type.incompatible) + @NonNull T r1 = b ? p : null; + // :: error: (assignment.type.incompatible) + @NonNull T r2 = b ? null : p; + } } - } - class Generic3 { - void cond(boolean b, @Nullable T p) { - @Nullable T r1 = b ? p : null; - @Nullable T r2 = b ? null : p; - // :: error: (assignment.type.incompatible) - @NonNull T r3 = b ? null : p; + class Generic3 { + void cond(boolean b, @Nullable T p) { + @Nullable T r1 = b ? p : null; + @Nullable T r2 = b ? null : p; + // :: error: (assignment.type.incompatible) + @NonNull T r3 = b ? null : p; + } } - } - void array(boolean b) { - String[] s = b ? new String[] {""} : null; - // :: error: (dereference.of.nullable) - s.toString(); - } + void array(boolean b) { + String[] s = b ? new String[] {""} : null; + // :: error: (dereference.of.nullable) + s.toString(); + } - void generic(boolean b, Generic1 p) { - Generic1 s = b ? p : null; - // :: error: (dereference.of.nullable) - s.toString(); - } + void generic(boolean b, Generic1 p) { + Generic1 s = b ? p : null; + // :: error: (dereference.of.nullable) + s.toString(); + } - void primarray(boolean b) { - long[] result = b ? null : new long[10]; - // :: error: (dereference.of.nullable) - result.toString(); - } + void primarray(boolean b) { + long[] result = b ? null : new long[10]; + // :: error: (dereference.of.nullable) + result.toString(); + } } diff --git a/checker/tests/nullness/generics/VarArgsTest.java b/checker/tests/nullness/generics/VarArgsTest.java index 835ae0cd753..3c563308536 100644 --- a/checker/tests/nullness/generics/VarArgsTest.java +++ b/checker/tests/nullness/generics/VarArgsTest.java @@ -2,11 +2,11 @@ import java.util.Set; public class VarArgsTest { - // :: warning: [unchecked] Possible heap pollution from parameterized vararg type - // java.util.Set - void test(Set... args) { - Arrays.asList(args); - } - // static void test(Set... args) { test2(Arrays.asList(args)); } - // static void test2(Iterable> args) {} + // :: warning: [unchecked] Possible heap pollution from parameterized vararg type + // java.util.Set + void test(Set... args) { + Arrays.asList(args); + } + // static void test(Set... args) { test2(Arrays.asList(args)); } + // static void test2(Iterable> args) {} } diff --git a/checker/tests/nullness/generics/WildcardAnnos.java b/checker/tests/nullness/generics/WildcardAnnos.java index 6b02a2ca05e..efb5a21ca58 100644 --- a/checker/tests/nullness/generics/WildcardAnnos.java +++ b/checker/tests/nullness/generics/WildcardAnnos.java @@ -1,38 +1,39 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + public class WildcardAnnos { - // :: error: (bound.type.incompatible) - @Nullable List<@Nullable ? extends @NonNull Object> l1 = null; - @Nullable List<@NonNull ? extends @Nullable Object> l2 = null; - - // The implicit upper bound is Nullable, because the annotation - // on the wildcard is propagated. Therefore this type is: - // @Nullable List l3 = null; - @Nullable List<@Nullable ? super @NonNull String> l3 = null; - - // The bounds need to have the same annotations because capture conversion - // converts the type argument to just Object. - // :: error: (type.invalid.super.wildcard) - @Nullable List<@Nullable ? super @NonNull Object> l3b = null; - - // :: error: (bound.type.incompatible) - @Nullable List<@NonNull ? super @Nullable String> l4 = null; - - @Nullable List l5 = null; - - @Nullable List inReturn() { - return null; - } - - void asParam(List p) {} - - // :: error: (type.invalid.conflicting.annos) - @Nullable List<@Nullable @NonNull ? extends @Nullable Object> l6 = null; - // :: error: (type.invalid.conflicting.annos) - @Nullable List<@Nullable @NonNull ? super @NonNull String> l7 = null; - // :: error: (type.invalid.conflicting.annos) - @Nullable List l8 = null; - // :: error: (type.invalid.conflicting.annos) - @Nullable List l9 = null; + // :: error: (bound.type.incompatible) + @Nullable List<@Nullable ? extends @NonNull Object> l1 = null; + @Nullable List<@NonNull ? extends @Nullable Object> l2 = null; + + // The implicit upper bound is Nullable, because the annotation + // on the wildcard is propagated. Therefore this type is: + // @Nullable List l3 = null; + @Nullable List<@Nullable ? super @NonNull String> l3 = null; + + // The bounds need to have the same annotations because capture conversion + // converts the type argument to just Object. + // :: error: (type.invalid.super.wildcard) + @Nullable List<@Nullable ? super @NonNull Object> l3b = null; + + // :: error: (bound.type.incompatible) + @Nullable List<@NonNull ? super @Nullable String> l4 = null; + + @Nullable List l5 = null; + + @Nullable List inReturn() { + return null; + } + + void asParam(List p) {} + + // :: error: (type.invalid.conflicting.annos) + @Nullable List<@Nullable @NonNull ? extends @Nullable Object> l6 = null; + // :: error: (type.invalid.conflicting.annos) + @Nullable List<@Nullable @NonNull ? super @NonNull String> l7 = null; + // :: error: (type.invalid.conflicting.annos) + @Nullable List l8 = null; + // :: error: (type.invalid.conflicting.annos) + @Nullable List l9 = null; } diff --git a/checker/tests/nullness/generics/WildcardBoundDefault.java b/checker/tests/nullness/generics/WildcardBoundDefault.java index ee6a3fa3050..b922e8df12d 100644 --- a/checker/tests/nullness/generics/WildcardBoundDefault.java +++ b/checker/tests/nullness/generics/WildcardBoundDefault.java @@ -6,13 +6,13 @@ class MyGenClass {} @DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.UPPER_BOUND) public class WildcardBoundDefault { - void test() { - ignore(newInstance()); - } + void test() { + ignore(newInstance()); + } - static void ignore(MyGenClass... consumer) {} + static void ignore(MyGenClass... consumer) {} - static MyGenClass newInstance() { - return new MyGenClass(); - } + static MyGenClass newInstance() { + return new MyGenClass(); + } } diff --git a/checker/tests/nullness/generics/WildcardBounds.java b/checker/tests/nullness/generics/WildcardBounds.java index 8b35636c961..7faadc6add1 100644 --- a/checker/tests/nullness/generics/WildcardBounds.java +++ b/checker/tests/nullness/generics/WildcardBounds.java @@ -3,132 +3,132 @@ class WildcardBounds { - abstract class OuterNbl { - abstract T get(); - - abstract class Inner { - abstract U get(); - - abstract class Chain { - abstract W get(); - } + abstract class OuterNbl { + abstract T get(); + + abstract class Inner { + abstract U get(); + + abstract class Chain { + abstract W get(); + } + + Object m0(Chain p) { + return p.get(); + } + + Object m1(Chain p) { + return p.get(); + } + + Object m2(Chain p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m3(Chain p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m4(Chain<@NonNull ?, @NonNull ?> p) { + return p.get(); + } + + void callsNonNull( + OuterNbl.Inner i, + OuterNbl.Inner.Chain n) { + i.m0(n); + i.m1(n); + i.m2(n); + i.m3(n); + i.m4(n); + } + + void callsNullable( + OuterNbl<@Nullable Object>.Inner<@Nullable Number> i, + OuterNbl<@Nullable Object>.Inner<@Nullable Number>.Chain< + @Nullable Integer, @Nullable Integer> + n) { + // :: error: (argument.type.incompatible) + i.m0(n); + // :: error: (argument.type.incompatible) + i.m1(n); + // OK + i.m2(n); + // OK + i.m3(n); + // :: error: (argument.type.incompatible) + i.m4(n); + } + } + + Object m0(Inner p) { + return p.get(); + } + + Object m1(Inner p) { + return p.get(); + } + + Object m2(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m3(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + Object m4(Inner<@NonNull ?> p) { + return p.get(); + } + + // We could add calls for these methods. + } - Object m0(Chain p) { + Object m0(OuterNbl p) { return p.get(); - } + } - Object m1(Chain p) { + Object m1(OuterNbl p) { return p.get(); - } + } - Object m2(Chain p) { + Object m2(OuterNbl p) { // :: error: (return.type.incompatible) return p.get(); - } + } - Object m3(Chain p) { + Object m3(OuterNbl p) { // :: error: (return.type.incompatible) return p.get(); - } + } - Object m4(Chain<@NonNull ?, @NonNull ?> p) { + Object m4(OuterNbl<@NonNull ?> p) { return p.get(); - } - - void callsNonNull( - OuterNbl.Inner i, - OuterNbl.Inner.Chain n) { - i.m0(n); - i.m1(n); - i.m2(n); - i.m3(n); - i.m4(n); - } - - void callsNullable( - OuterNbl<@Nullable Object>.Inner<@Nullable Number> i, - OuterNbl<@Nullable Object>.Inner<@Nullable Number>.Chain< - @Nullable Integer, @Nullable Integer> - n) { + } + + void callsOuter(OuterNbl s, OuterNbl<@Nullable String> ns) { + m0(s); + m1(s); + m2(s); + m3(s); + m4(s); + // :: error: (argument.type.incompatible) - i.m0(n); + m0(ns); // :: error: (argument.type.incompatible) - i.m1(n); + m1(ns); // OK - i.m2(n); + m2(ns); // OK - i.m3(n); + m3(ns); // :: error: (argument.type.incompatible) - i.m4(n); - } - } - - Object m0(Inner p) { - return p.get(); - } - - Object m1(Inner p) { - return p.get(); - } - - Object m2(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m3(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m4(Inner<@NonNull ?> p) { - return p.get(); + m4(ns); } - // We could add calls for these methods. - } - - Object m0(OuterNbl p) { - return p.get(); - } - - Object m1(OuterNbl p) { - return p.get(); - } - - Object m2(OuterNbl p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m3(OuterNbl p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - Object m4(OuterNbl<@NonNull ?> p) { - return p.get(); - } - - void callsOuter(OuterNbl s, OuterNbl<@Nullable String> ns) { - m0(s); - m1(s); - m2(s); - m3(s); - m4(s); - - // :: error: (argument.type.incompatible) - m0(ns); - // :: error: (argument.type.incompatible) - m1(ns); - // OK - m2(ns); - // OK - m3(ns); - // :: error: (argument.type.incompatible) - m4(ns); - } - - // We could add an OuterNonNull to also test with a non-null upper bound. - // But we probably already test that enough. + // We could add an OuterNonNull to also test with a non-null upper bound. + // But we probably already test that enough. } diff --git a/checker/tests/nullness/generics/WildcardOverride.java b/checker/tests/nullness/generics/WildcardOverride.java index 7ea7f0037af..c57fe4dc71f 100644 --- a/checker/tests/nullness/generics/WildcardOverride.java +++ b/checker/tests/nullness/generics/WildcardOverride.java @@ -2,28 +2,29 @@ // see also framework/tests/all-systems/WildcardSuper2 -import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.List; + interface ToOverride { - public abstract int transform(List function); + public abstract int transform(List function); } public class WildcardOverride implements ToOverride { - @Override - public int transform(List function) { - return 0; - } + @Override + public int transform(List function) { + return 0; + } } interface ToOverride2 { - // :: error: (bound.type.incompatible) - public abstract int transform(List<@NonNull ? super T> function); + // :: error: (bound.type.incompatible) + public abstract int transform(List<@NonNull ? super T> function); } class WildcardOverride2 implements ToOverride2 { - @Override - public int transform(List function) { - return 0; - } + @Override + public int transform(List function) { + return 0; + } } diff --git a/checker/tests/nullness/generics/WildcardOverrideMore.java b/checker/tests/nullness/generics/WildcardOverrideMore.java index 5077071dc2a..8e49434f8de 100644 --- a/checker/tests/nullness/generics/WildcardOverrideMore.java +++ b/checker/tests/nullness/generics/WildcardOverrideMore.java @@ -2,47 +2,47 @@ import org.checkerframework.checker.nullness.qual.Nullable; class WildcardOverrideMore { - interface Box {} + interface Box {} - interface Super { - void foo(Box lib); + interface Super { + void foo(Box lib); - Box retfoo(); + Box retfoo(); - void bar(Box lib); + void bar(Box lib); - Box retbar(); - } + Box retbar(); + } - interface Sub extends Super { - @Override - void foo(Box lib); + interface Sub extends Super { + @Override + void foo(Box lib); - @Override - Box retfoo(); + @Override + Box retfoo(); - @Override - void bar(Box lib); + @Override + void bar(Box lib); - @Override - Box retbar(); - } + @Override + Box retbar(); + } - interface SubErrors extends Super { - @Override - // :: error: (override.param.invalid) - void foo(Box lib); + interface SubErrors extends Super { + @Override + // :: error: (override.param.invalid) + void foo(Box lib); - @Override - // :: error: (override.return.invalid) - Box retfoo(); + @Override + // :: error: (override.return.invalid) + Box retfoo(); - @Override - // :: error: (override.param.invalid) - void bar(Box<@NonNull ? super @NonNull W> lib); + @Override + // :: error: (override.param.invalid) + void bar(Box<@NonNull ? super @NonNull W> lib); - @Override - // :: error: (override.return.invalid) - Box<@Nullable ? super @NonNull W> retbar(); - } + @Override + // :: error: (override.return.invalid) + Box<@Nullable ? super @NonNull W> retbar(); + } } diff --git a/checker/tests/nullness/generics/WildcardSubtyping.java b/checker/tests/nullness/generics/WildcardSubtyping.java index 944c80780a8..80d8300870f 100644 --- a/checker/tests/nullness/generics/WildcardSubtyping.java +++ b/checker/tests/nullness/generics/WildcardSubtyping.java @@ -1,65 +1,66 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.*; class Utils { - void test(List list, A object) { - list.add(object); - } + void test(List list, A object) { + list.add(object); + } - interface Consumer { - public void consume(A object); - } + interface Consumer { + public void consume(A object); + } - public static Consumer cast( - final Consumer<@Nullable ? super A> consumer) { - return new Consumer() { - @Override - public void consume(A object) { - consumer.consume(object); - } - }; - } + public static Consumer cast( + final Consumer<@Nullable ? super A> consumer) { + return new Consumer() { + @Override + public void consume(A object) { + consumer.consume(object); + } + }; + } - public static Consumer getConsumer( - Consumer<@Nullable Object> nullConsumer) { - return Utils.cast(nullConsumer); - } + public static Consumer getConsumer( + Consumer<@Nullable Object> nullConsumer) { + return Utils.cast(nullConsumer); + } - Map> mss = new HashMap<>(); + Map> mss = new HashMap<>(); - Set> foo() { - Set> l = new HashSet<>(this.foo()); - return l; - } + Set> foo() { + Set> l = new HashSet<>(this.foo()); + return l; + } } class MyGeneric<@NonNull T extends @Nullable Number> {} class UseMyGeneric { - MyGeneric wildcardUnbounded = new MyGeneric<>(); + MyGeneric wildcardUnbounded = new MyGeneric<>(); - // :: error: (assignment.type.incompatible) - MyGeneric wildcardOutsideUB = wildcardUnbounded; - MyGeneric wildcardInsideUB = wildcardOutsideUB; - // :: error: (assignment.type.incompatible) - MyGeneric wildcardInsideUB2 = wildcardUnbounded; + // :: error: (assignment.type.incompatible) + MyGeneric wildcardOutsideUB = wildcardUnbounded; + MyGeneric wildcardInsideUB = wildcardOutsideUB; + // :: error: (assignment.type.incompatible) + MyGeneric wildcardInsideUB2 = wildcardUnbounded; - MyGeneric wildcardInsideUBNullable = wildcardOutsideUB; + MyGeneric wildcardInsideUBNullable = wildcardOutsideUB; } class MyGenericExactBounds<@NonNull T extends @NonNull Number> {} class UseMyGenericExactBounds { - MyGenericExactBounds wildcardOutsideUBError = - new MyGenericExactBounds<>(); - MyGenericExactBounds wildcardOutside = new MyGenericExactBounds<>(); - MyGenericExactBounds wildcardInsideUB = wildcardOutside; + MyGenericExactBounds wildcardOutsideUBError = + new MyGenericExactBounds<>(); + MyGenericExactBounds wildcardOutside = new MyGenericExactBounds<>(); + MyGenericExactBounds wildcardInsideUB = wildcardOutside; - MyGenericExactBounds wildcardOutsideUB = wildcardOutside; + MyGenericExactBounds wildcardOutsideUB = wildcardOutside; } diff --git a/checker/tests/nullness/generics/WildcardSubtyping2.java b/checker/tests/nullness/generics/WildcardSubtyping2.java index 0f01445e41f..c4ce1a34d4d 100644 --- a/checker/tests/nullness/generics/WildcardSubtyping2.java +++ b/checker/tests/nullness/generics/WildcardSubtyping2.java @@ -1,29 +1,29 @@ import org.checkerframework.checker.nullness.qual.*; public class WildcardSubtyping2 { - class MyClass {} + class MyClass {} - class MyCloneClass extends MyClass implements Cloneable {} + class MyCloneClass extends MyClass implements Cloneable {} - class MyGeneric<@NonNull T extends @Nullable MyClass> {} + class MyGeneric<@NonNull T extends @Nullable MyClass> {} - class UseMyGeneric { - MyGeneric<@NonNull MyCloneClass> nonNull = new MyGeneric<>(); - MyGeneric<@Nullable MyCloneClass> nullable = new MyGeneric<>(); + class UseMyGeneric { + MyGeneric<@NonNull MyCloneClass> nonNull = new MyGeneric<>(); + MyGeneric<@Nullable MyCloneClass> nullable = new MyGeneric<>(); - MyGeneric interfaceNN = nonNull; - MyGeneric interfaceNull = nullable; - } + MyGeneric interfaceNN = nonNull; + MyGeneric interfaceNull = nullable; + } - class MyGenericEB<@NonNull T extends @NonNull MyClass> {} + class MyGenericEB<@NonNull T extends @NonNull MyClass> {} - class UseMyGenericEB { - MyGenericEB<@NonNull MyCloneClass> nonNull = new MyGenericEB<>(); - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - MyGenericEB<@Nullable MyCloneClass> nullable = new MyGenericEB<>(); + class UseMyGenericEB { + MyGenericEB<@NonNull MyCloneClass> nonNull = new MyGenericEB<>(); + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + MyGenericEB<@Nullable MyCloneClass> nullable = new MyGenericEB<>(); - MyGenericEB interfaceNN = nonNull; - MyGenericEB interfaceNull = nullable; - } + MyGenericEB interfaceNN = nonNull; + MyGenericEB interfaceNull = nullable; + } } diff --git a/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java b/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java index e202a666916..1735657e68f 100644 --- a/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java +++ b/checker/tests/nullness/generics/WildcardSubtypingTypeArray.java @@ -1,10 +1,11 @@ -import java.util.List; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + public class WildcardSubtypingTypeArray { - void test(List list) { - test2(list.get(0)); - } + void test(List list) { + test2(list.get(0)); + } - void test2(A x) {} + void test2(A x) {} } diff --git a/checker/tests/nullness/generics/WildcardSuper.java b/checker/tests/nullness/generics/WildcardSuper.java index 8629e8bc2a0..ae7a9f22463 100644 --- a/checker/tests/nullness/generics/WildcardSuper.java +++ b/checker/tests/nullness/generics/WildcardSuper.java @@ -3,35 +3,35 @@ public class WildcardSuper { - void testWithSuper(Cell cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithSuper(Cell cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - void testWithContradiction(Cell cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithContradiction(Cell cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - @DefaultQualifier(Nullable.class) - void testWithImplicitNullable(@NonNull Cell cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + @DefaultQualifier(Nullable.class) + void testWithImplicitNullable(@NonNull Cell cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - void testWithExplicitNullable(Cell<@Nullable ? extends @Nullable String> cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithExplicitNullable(Cell<@Nullable ? extends @Nullable String> cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - void testWithDoubleNullable(Cell<@Nullable ? extends @Nullable String> cell) { - // :: error: (dereference.of.nullable) - cell.get().toString(); - } + void testWithDoubleNullable(Cell<@Nullable ? extends @Nullable String> cell) { + // :: error: (dereference.of.nullable) + cell.get().toString(); + } - class Cell { - E get() { - throw new RuntimeException(); + class Cell { + E get() { + throw new RuntimeException(); + } } - } } diff --git a/checker/tests/nullness/java-unsound/Figure1.java b/checker/tests/nullness/java-unsound/Figure1.java index 38a62f3bfe5..77ae99a175c 100644 --- a/checker/tests/nullness/java-unsound/Figure1.java +++ b/checker/tests/nullness/java-unsound/Figure1.java @@ -2,22 +2,22 @@ // @skip-test no need to test for the javac error. public class Figure1 { - static class Constrain {} + static class Constrain {} - static class Bind { - A upcast(Constrain constrain, B b) { - return b; + static class Bind { + A upcast(Constrain constrain, B b) { + return b; + } } - } - static U coerce(T t) { - Constrain constrain = null; - Bind bind = new Bind(); - // :: error: method upcast in class Figure1.Bind cannot be applied to given types; - return bind.upcast(constrain, t); - } + static U coerce(T t) { + Constrain constrain = null; + Bind bind = new Bind(); + // :: error: method upcast in class Figure1.Bind cannot be applied to given types; + return bind.upcast(constrain, t); + } - public static void main(String[] args) { - String zero = Figure1.coerce(0); - } + public static void main(String[] args) { + String zero = Figure1.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure3.java b/checker/tests/nullness/java-unsound/Figure3.java index cf831e43286..37dc8925901 100644 --- a/checker/tests/nullness/java-unsound/Figure3.java +++ b/checker/tests/nullness/java-unsound/Figure3.java @@ -1,41 +1,41 @@ public class Figure3 { - static class Type { - class Constraint extends Type {} + static class Type { + class Constraint extends Type {} - Constraint bad() { - // :: error: (return.type.incompatible) - return null; - } + Constraint bad() { + // :: error: (return.type.incompatible) + return null; + } - A coerce(B b) { - // type of expression: capture#703[ extends @Initialized @Nullable Object super B[ - // extends @Initialized @Nullable Object super @Initialized @NonNull Void]] - // method return type: A[ extends @Initialized @Nullable Object super @Initialized - // @NonNull Void] - return pair(this.bad(), b).value; + A coerce(B b) { + // type of expression: capture#703[ extends @Initialized @Nullable Object super B[ + // extends @Initialized @Nullable Object super @Initialized @NonNull Void]] + // method return type: A[ extends @Initialized @Nullable Object super @Initialized + // @NonNull Void] + return pair(this.bad(), b).value; + } } - } - static class Sum { - Type type; - T value; + static class Sum { + Type type; + T value; - Sum(Type t, T v) { - type = t; - value = v; + Sum(Type t, T v) { + type = t; + value = v; + } } - } - static Sum pair(Type type, T value) { - return new Sum(type, value); - } + static Sum pair(Type type, T value) { + return new Sum(type, value); + } - static U coerce(T t) { - Type type = new Type(); - return type.coerce(t); - } + static U coerce(T t) { + Type type = new Type(); + return type.coerce(t); + } - public static void main(String[] args) { - String zero = Figure3.coerce(0); - } + public static void main(String[] args) { + String zero = Figure3.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure3NC.java b/checker/tests/nullness/java-unsound/Figure3NC.java index f579d7932ed..a312b41278c 100644 --- a/checker/tests/nullness/java-unsound/Figure3NC.java +++ b/checker/tests/nullness/java-unsound/Figure3NC.java @@ -3,38 +3,38 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Figure3NC { - static class Type { - class Constraint extends Type {} + static class Type { + class Constraint extends Type {} - @Nullable Constraint bad() { - return null; - } + @Nullable Constraint bad() { + return null; + } - A coerce(B b) { - return pair(this.bad(), b).value; + A coerce(B b) { + return pair(this.bad(), b).value; + } } - } - static class Sum { - @Nullable Type type; - T value; + static class Sum { + @Nullable Type type; + T value; - Sum(@Nullable Type t, T v) { - type = t; - value = v; + Sum(@Nullable Type t, T v) { + type = t; + value = v; + } } - } - static Sum pair(@Nullable Type type, T value) { - return new Sum(type, value); - } + static Sum pair(@Nullable Type type, T value) { + return new Sum(type, value); + } - static U coerce(T t) { - Type type = new Type(); - return type.coerce(t); - } + static U coerce(T t) { + Type type = new Type(); + return type.coerce(t); + } - public static void main(String[] args) { - String zero = Figure3NC.coerce(0); - } + public static void main(String[] args) { + String zero = Figure3NC.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure4.java b/checker/tests/nullness/java-unsound/Figure4.java index efb885e2334..b6df5c030f4 100644 --- a/checker/tests/nullness/java-unsound/Figure4.java +++ b/checker/tests/nullness/java-unsound/Figure4.java @@ -2,19 +2,19 @@ // @skip-test no need to test for the javac error. public class Figure4 { - static class Constrain {} + static class Constrain {} - static A upcast(Constrain constrain, B b) { - return b; - } + static A upcast(Constrain constrain, B b) { + return b; + } - static U coerce(T t) { - Constrain constrain = null; - // :: error: method upcast in class Figure4 cannot be applied to given types; - return upcast(constrain, t); - } + static U coerce(T t) { + Constrain constrain = null; + // :: error: method upcast in class Figure4 cannot be applied to given types; + return upcast(constrain, t); + } - public static void main(String[] args) { - String zero = coerce(0); - } + public static void main(String[] args) { + String zero = coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure6.java b/checker/tests/nullness/java-unsound/Figure6.java index 79ca5c41dd2..a15aa5bd5a7 100644 --- a/checker/tests/nullness/java-unsound/Figure6.java +++ b/checker/tests/nullness/java-unsound/Figure6.java @@ -1,26 +1,26 @@ public class Figure6 { - static class Bind { - class Curry { - A curry(B b) { - return b; - } - } + static class Bind { + class Curry { + A curry(B b) { + return b; + } + } - Curry upcast(Constraint constraint) { - return new Curry(); - } + Curry upcast(Constraint constraint) { + return new Curry(); + } - class Constraint {} + class Constraint {} - A coerce(B t) { - Constraint constraint = null; - // :: error: (argument.type.incompatible) - return upcast(constraint).curry(t); + A coerce(B t) { + Constraint constraint = null; + // :: error: (argument.type.incompatible) + return upcast(constraint).curry(t); + } } - } - public static void main(String[] args) { - Bind bind = new Bind(); - String zero = bind.coerce(0); - } + public static void main(String[] args) { + Bind bind = new Bind(); + String zero = bind.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure6NC.java b/checker/tests/nullness/java-unsound/Figure6NC.java index a6047ed567c..39f0b987d2b 100644 --- a/checker/tests/nullness/java-unsound/Figure6NC.java +++ b/checker/tests/nullness/java-unsound/Figure6NC.java @@ -3,27 +3,27 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class Figure6NC { - static class Bind { - class Curry { - A curry(B b) { - return b; - } - } + static class Bind { + class Curry { + A curry(B b) { + return b; + } + } - Curry upcast(@Nullable Constraint constraint) { - return new Curry(); - } + Curry upcast(@Nullable Constraint constraint) { + return new Curry(); + } - class Constraint {} + class Constraint {} - A coerce(B t) { - Constraint constraint = null; - return upcast(constraint).curry(t); + A coerce(B t) { + Constraint constraint = null; + return upcast(constraint).curry(t); + } } - } - public static void main(String[] args) { - Bind bind = new Bind(); - String zero = bind.coerce(0); - } + public static void main(String[] args) { + Bind bind = new Bind(); + String zero = bind.coerce(0); + } } diff --git a/checker/tests/nullness/java-unsound/Figure7.java b/checker/tests/nullness/java-unsound/Figure7.java index e75b05dda54..9abf06ef270 100644 --- a/checker/tests/nullness/java-unsound/Figure7.java +++ b/checker/tests/nullness/java-unsound/Figure7.java @@ -2,30 +2,30 @@ // @skip-test no need to test for the javac error. public class Figure7 { - class Constrain {} + class Constrain {} - final Constrain constrain; - final U u; + final Constrain constrain; + final U u; - Figure7(T t) { - u = coerce(t); - constrain = getConstrain(); - } + Figure7(T t) { + u = coerce(t); + constrain = getConstrain(); + } - U upcast(Constrain constrain, B b) { - return b; - } + U upcast(Constrain constrain, B b) { + return b; + } - U coerce(T t) { - // :: error: method upcast in class Figure7 cannot be applied to given types; - return upcast(constrain, t); - } + U coerce(T t) { + // :: error: method upcast in class Figure7 cannot be applied to given types; + return upcast(constrain, t); + } - Constrain getConstrain() { - return constrain; - } + Constrain getConstrain() { + return constrain; + } - public static void main(String[] args) { - String zero = new Figure7(0).u; - } + public static void main(String[] args) { + String zero = new Figure7(0).u; + } } diff --git a/checker/tests/nullness/java17/Greeting.java b/checker/tests/nullness/java17/Greeting.java index e9dcf4cd28e..3507794645e 100644 --- a/checker/tests/nullness/java17/Greeting.java +++ b/checker/tests/nullness/java17/Greeting.java @@ -2,28 +2,29 @@ // Test case for https://github.com/typetools/checker-framework/issues/5039 package com.example.hello_world; -import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + public final class Greeting { - public final @Nullable String name; + public final @Nullable String name; - public Greeting(@Nullable String name) { - this.name = name; - } + public Greeting(@Nullable String name) { + this.name = name; + } - @Override - public int hashCode() { - return Objects.hash(name); - } + @Override + public int hashCode() { + return Objects.hash(name); + } - @Override - public boolean equals(@Nullable Object o) { - return o == this || o instanceof Greeting that && Objects.equals(name, that.name); - } + @Override + public boolean equals(@Nullable Object o) { + return o == this || o instanceof Greeting that && Objects.equals(name, that.name); + } - @Override - public String toString() { - return name == null ? "World" : name; - } + @Override + public String toString() { + return name == null ? "World" : name; + } } diff --git a/checker/tests/nullness/java17/InferSwitchExpr.java b/checker/tests/nullness/java17/InferSwitchExpr.java index dd6933b84b3..d218cc347b1 100644 --- a/checker/tests/nullness/java17/InferSwitchExpr.java +++ b/checker/tests/nullness/java17/InferSwitchExpr.java @@ -1,37 +1,38 @@ // @below-java17-jdk-skip-test -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class InferSwitchExpr { - enum Letter { - A, - B, - C, - D; - } + enum Letter { + A, + B, + C, + D; + } - List singletonList(T t) { - throw new RuntimeException(); - } + List singletonList(T t) { + throw new RuntimeException(); + } - @Nullable List method(Letter letter, boolean a, boolean b) { - return switch (letter) { - case A -> { - if (a) { - if (b) { - // :: error: (type.arguments.not.inferred) - yield singletonList(null); - } - // :: error: (type.arguments.not.inferred) - yield singletonList(null); - } - // :: error: (type.arguments.not.inferred) - yield singletonList(null); - } - case B -> null; - case C -> null; - case D -> null; - }; - } + @Nullable List method(Letter letter, boolean a, boolean b) { + return switch (letter) { + case A -> { + if (a) { + if (b) { + // :: error: (type.arguments.not.inferred) + yield singletonList(null); + } + // :: error: (type.arguments.not.inferred) + yield singletonList(null); + } + // :: error: (type.arguments.not.inferred) + yield singletonList(null); + } + case B -> null; + case C -> null; + case D -> null; + }; + } } diff --git a/checker/tests/nullness/java17/InstanceOfPatternVariable.java b/checker/tests/nullness/java17/InstanceOfPatternVariable.java index 0c9e300c473..6159498aecb 100644 --- a/checker/tests/nullness/java17/InstanceOfPatternVariable.java +++ b/checker/tests/nullness/java17/InstanceOfPatternVariable.java @@ -1,16 +1,17 @@ // @below-java17-jdk-skip-test // Test case for https://github.com/typetools/checker-framework/issues/5240 -import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; +import java.util.Map; + public class InstanceOfPatternVariable { - public void doSomething(final Object x) { - if (x instanceof Map m) { - // final var ct = (ClassOrInterfaceType) type; + public void doSomething(final Object x) { + if (x instanceof Map m) { + // final var ct = (ClassOrInterfaceType) type; - @KeyFor("m") Object y = m.keySet().iterator().next(); + @KeyFor("m") Object y = m.keySet().iterator().next(); + } } - } } diff --git a/checker/tests/nullness/java17/Issue5047.java b/checker/tests/nullness/java17/Issue5047.java index bb320d493f1..595f68e5d64 100644 --- a/checker/tests/nullness/java17/Issue5047.java +++ b/checker/tests/nullness/java17/Issue5047.java @@ -1,24 +1,25 @@ // @below-java16-jdk-skip-test // Test case for issue #5047: https://tinyurl.com/cfissue/5047 -import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + public class Issue5047 {} class NumberParameterBuilder { - @Nullable Object minimum; - @Nullable Object maximum; + @Nullable Object minimum; + @Nullable Object maximum; - public boolean equals(final @Nullable Object o) { + public boolean equals(final @Nullable Object o) { - if (o instanceof NumberParameterBuilder b) { - return super.equals(o) - && Objects.equals(this.minimum, b.minimum) - && Objects.equals(this.maximum, b.maximum); - } else { - return false; + if (o instanceof NumberParameterBuilder b) { + return super.equals(o) + && Objects.equals(this.minimum, b.minimum) + && Objects.equals(this.maximum, b.maximum); + } else { + return false; + } } - } } diff --git a/checker/tests/nullness/java17/Issue5967.java b/checker/tests/nullness/java17/Issue5967.java index 78708e93310..4531b034d8e 100644 --- a/checker/tests/nullness/java17/Issue5967.java +++ b/checker/tests/nullness/java17/Issue5967.java @@ -1,24 +1,25 @@ // @below-java17-jdk-skip-test -import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.function.Supplier; + public final class Issue5967 { - enum TestEnum { - FIRST, - SECOND; - } + enum TestEnum { + FIRST, + SECOND; + } - public static void main(String[] args) { - TestEnum testEnum = TestEnum.FIRST; - Supplier supplier = - switch (testEnum) { - case FIRST: - yield () -> 1; - case SECOND: - yield () -> 2; - }; - @NonNull Supplier supplier1 = supplier; - } + public static void main(String[] args) { + TestEnum testEnum = TestEnum.FIRST; + Supplier supplier = + switch (testEnum) { + case FIRST: + yield () -> 1; + case SECOND: + yield () -> 2; + }; + @NonNull Supplier supplier1 = supplier; + } } diff --git a/checker/tests/nullness/java17/NullnessInstanceOf.java b/checker/tests/nullness/java17/NullnessInstanceOf.java index 3881650ec6d..f65b99629e6 100644 --- a/checker/tests/nullness/java17/NullnessInstanceOf.java +++ b/checker/tests/nullness/java17/NullnessInstanceOf.java @@ -4,50 +4,50 @@ public class NullnessInstanceOf { - public void testClassicInstanceOfNullable(Object x) { - // :: error: (instanceof.nullable) - if (x instanceof @Nullable String) { - System.out.println("Nullable String instanceof check."); + public void testClassicInstanceOfNullable(Object x) { + // :: error: (instanceof.nullable) + if (x instanceof @Nullable String) { + System.out.println("Nullable String instanceof check."); + } } - } - public void testClassicInstanceOfNonNull(Object x) { - // :: warning: (instanceof.nonnull.redundant) - if (x instanceof @NonNull Number) { - System.out.println("NonNull Number instanceof check."); + public void testClassicInstanceOfNonNull(Object x) { + // :: warning: (instanceof.nonnull.redundant) + if (x instanceof @NonNull Number) { + System.out.println("NonNull Number instanceof check."); + } } - } - public void testPatternVariableNullable(Object x) { - // :: error: (instanceof.nullable) - if (x instanceof @Nullable String n) { - System.out.println("Length of String: " + n.length()); + public void testPatternVariableNullable(Object x) { + // :: error: (instanceof.nullable) + if (x instanceof @Nullable String n) { + System.out.println("Length of String: " + n.length()); + } } - } - public void testPatternVariableNonNull(Object x) { - // :: warning: (instanceof.nonnull.redundant) - if (x instanceof @NonNull Number nn) { - System.out.println("Number's hashCode: " + nn.hashCode()); + public void testPatternVariableNonNull(Object x) { + // :: warning: (instanceof.nonnull.redundant) + if (x instanceof @NonNull Number nn) { + System.out.println("Number's hashCode: " + nn.hashCode()); + } } - } - public void testUnannotatedClassic(Object x) { - if (x instanceof String) { - System.out.println("Unannotated String instanceof check."); + public void testUnannotatedClassic(Object x) { + if (x instanceof String) { + System.out.println("Unannotated String instanceof check."); + } } - } - public void testUnannotatedPatternVariable(Object x) { - if (x instanceof String unannotatedString) { - System.out.println("Unannotated String length: " + unannotatedString.length()); + public void testUnannotatedPatternVariable(Object x) { + if (x instanceof String unannotatedString) { + System.out.println("Unannotated String length: " + unannotatedString.length()); + } + } + + public void testUnusedPatternVariable(Object x) { + // :: error: (instanceof.nullable) + if (x instanceof @Nullable String unusedString) {} + // :: warning: (instanceof.nonnull.redundant) + if (x instanceof @NonNull Number unusedNumber) {} } - } - - public void testUnusedPatternVariable(Object x) { - // :: error: (instanceof.nullable) - if (x instanceof @Nullable String unusedString) {} - // :: warning: (instanceof.nonnull.redundant) - if (x instanceof @NonNull Number unusedNumber) {} - } } diff --git a/checker/tests/nullness/java17/NullnessSwitchArrows.java b/checker/tests/nullness/java17/NullnessSwitchArrows.java index 34f56e79906..4ee842d21cb 100644 --- a/checker/tests/nullness/java17/NullnessSwitchArrows.java +++ b/checker/tests/nullness/java17/NullnessSwitchArrows.java @@ -1,78 +1,78 @@ // @below-java14-jdk-skip-test public class NullnessSwitchArrows { - public enum Day { - SUNDAY, - MONDAY, - TUESDAY, - WEDNESDAY, - THURSDAY, - FRIDAY, - SATURDAY; - } - - void method1() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> o = "hello"; - case TUESDAY -> o = null; - case THURSDAY, SATURDAY -> o = "hello"; - case WEDNESDAY -> o = "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); + public enum Day { + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY; } - // :: error: (dereference.of.nullable) - o.toString(); - } + void method1() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> o = "hello"; + case TUESDAY -> o = null; + case THURSDAY, SATURDAY -> o = "hello"; + case WEDNESDAY -> o = "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + } - void method2() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> o = "hello"; - case TUESDAY -> o = "hello"; - case THURSDAY, SATURDAY -> o = "hello"; - case WEDNESDAY -> o = "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); + // :: error: (dereference.of.nullable) + o.toString(); } - o.toString(); - } + void method2() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> o = "hello"; + case TUESDAY -> o = "hello"; + case THURSDAY, SATURDAY -> o = "hello"; + case WEDNESDAY -> o = "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + } - void method2b() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY: - o = "hello"; - break; - case TUESDAY: - o = "hello"; - break; - case THURSDAY, SATURDAY: - o = "hello"; - break; - case WEDNESDAY: - o = "hello"; - break; - default: - throw new IllegalStateException("Invalid day: " + day); + o.toString(); } - o.toString(); - } + void method2b() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY: + o = "hello"; + break; + case TUESDAY: + o = "hello"; + break; + case THURSDAY, SATURDAY: + o = "hello"; + break; + case WEDNESDAY: + o = "hello"; + break; + default: + throw new IllegalStateException("Invalid day: " + day); + } - void method3() { - Object o; - Day day = Day.WEDNESDAY; - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> o = "hello"; - case TUESDAY -> o = "hello"; - case THURSDAY, SATURDAY -> o = "hello"; - case WEDNESDAY -> o = "hello"; - default -> o = "hello"; + o.toString(); } - o.toString(); - } + void method3() { + Object o; + Day day = Day.WEDNESDAY; + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> o = "hello"; + case TUESDAY -> o = "hello"; + case THURSDAY, SATURDAY -> o = "hello"; + case WEDNESDAY -> o = "hello"; + default -> o = "hello"; + } + + o.toString(); + } } diff --git a/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java b/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java index c54c39d44c5..a42799c0d0d 100644 --- a/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java +++ b/checker/tests/nullness/java17/NullnessSwitchExpressionLambda.java @@ -1,19 +1,20 @@ // @below-java14-jdk-skip-test -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Function; + public class NullnessSwitchExpressionLambda { - int anInt; + int anInt; - void switchExprLambda() { - Function f = - (n) -> - switch (n.anInt) { - case 3, 4, 5 -> new Object(); - default -> null; - }; - Object o = f.apply(new NullnessSwitchExpressionLambda()); - // :: error: (dereference.of.nullable) - o.toString(); - } + void switchExprLambda() { + Function f = + (n) -> + switch (n.anInt) { + case 3, 4, 5 -> new Object(); + default -> null; + }; + Object o = f.apply(new NullnessSwitchExpressionLambda()); + // :: error: (dereference.of.nullable) + o.toString(); + } } diff --git a/checker/tests/nullness/java17/NullnessSwitchExpressions.java b/checker/tests/nullness/java17/NullnessSwitchExpressions.java index c81971b4e8b..bc70a587b79 100644 --- a/checker/tests/nullness/java17/NullnessSwitchExpressions.java +++ b/checker/tests/nullness/java17/NullnessSwitchExpressions.java @@ -1,63 +1,63 @@ // @below-java14-jdk-skip-test public class NullnessSwitchExpressions { - public enum Day { - SUNDAY, - MONDAY, - TUESDAY, - WEDNESDAY, - THURSDAY, - FRIDAY, - SATURDAY; - } + public enum Day { + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY; + } - void method1() { - Day day = Day.WEDNESDAY; - Object o = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> "hello"; - case TUESDAY -> null; - case THURSDAY, SATURDAY -> "hello"; - case WEDNESDAY -> "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - }; + void method1() { + Day day = Day.WEDNESDAY; + Object o = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> "hello"; + case TUESDAY -> null; + case THURSDAY, SATURDAY -> "hello"; + case WEDNESDAY -> "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + }; - // :: error: (dereference.of.nullable) - o.toString(); - } + // :: error: (dereference.of.nullable) + o.toString(); + } - void method2() { - Day day = Day.WEDNESDAY; - Object o = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> "hello"; - case TUESDAY -> "hello"; - case THURSDAY, SATURDAY -> "hello"; - case WEDNESDAY -> "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - }; + void method2() { + Day day = Day.WEDNESDAY; + Object o = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> "hello"; + case TUESDAY -> "hello"; + case THURSDAY, SATURDAY -> "hello"; + case WEDNESDAY -> "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + }; - o.toString(); - } + o.toString(); + } - void method3() { - Day day = Day.WEDNESDAY; - Object o = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> "hello"; - case TUESDAY -> "hello"; - case THURSDAY, SATURDAY -> { - String s = null; - if (day == Day.THURSDAY) { - s = "hello"; - s.toString(); - } - yield s; - } - case WEDNESDAY -> "hello"; - default -> throw new IllegalStateException("Invalid day: " + day); - }; + void method3() { + Day day = Day.WEDNESDAY; + Object o = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> "hello"; + case TUESDAY -> "hello"; + case THURSDAY, SATURDAY -> { + String s = null; + if (day == Day.THURSDAY) { + s = "hello"; + s.toString(); + } + yield s; + } + case WEDNESDAY -> "hello"; + default -> throw new IllegalStateException("Invalid day: " + day); + }; - // :: error: (dereference.of.nullable) - o.toString(); - } + // :: error: (dereference.of.nullable) + o.toString(); + } } diff --git a/checker/tests/nullness/java17/NullnessSwitchStatementRules.java b/checker/tests/nullness/java17/NullnessSwitchStatementRules.java index 3cb143d46e4..bf460af93f1 100644 --- a/checker/tests/nullness/java17/NullnessSwitchStatementRules.java +++ b/checker/tests/nullness/java17/NullnessSwitchStatementRules.java @@ -2,42 +2,42 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class NullnessSwitchStatementRules { - @Nullable Object field = null; + @Nullable Object field = null; - void method(int selector) { - field = new Object(); - switch (selector) { - case 1 -> field = null; - case 2 -> field.toString(); - } + void method(int selector) { + field = new Object(); + switch (selector) { + case 1 -> field = null; + case 2 -> field.toString(); + } - field = new Object(); - switch (selector) { - case 1 -> { - field = null; - } - case 2 -> { - field.toString(); - } - } + field = new Object(); + switch (selector) { + case 1 -> { + field = null; + } + case 2 -> { + field.toString(); + } + } - field = new Object(); - switch (selector) { - case 1 -> { - field = null; - } - case 2 -> { - field.toString(); - } - } + field = new Object(); + switch (selector) { + case 1 -> { + field = null; + } + case 2 -> { + field.toString(); + } + } - field = new Object(); - switch (selector) { - case 1: - field = null; - case 2: - // :: error: (dereference.of.nullable) - field.toString(); + field = new Object(); + switch (selector) { + case 1: + field = null; + case 2: + // :: error: (dereference.of.nullable) + field.toString(); + } } - } } diff --git a/checker/tests/nullness/java17/SwitchExpressionInvariant.java b/checker/tests/nullness/java17/SwitchExpressionInvariant.java index 6f5e03a3028..c43ab5a23bf 100644 --- a/checker/tests/nullness/java17/SwitchExpressionInvariant.java +++ b/checker/tests/nullness/java17/SwitchExpressionInvariant.java @@ -1,27 +1,30 @@ // @below-java14-jdk-skip-test -import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class SwitchExpressionInvariant { - public static boolean flag = false; + public static boolean flag = false; - void method( - List<@NonNull String> nonnullStrings, List<@Nullable String> nullableStrings, int fenum) { + void method( + List<@NonNull String> nonnullStrings, + List<@Nullable String> nullableStrings, + int fenum) { - List<@NonNull String> list = - // :: error: (assignment.type.incompatible) - switch (fenum) { - // :: error: (switch.expression.type.incompatible) - case 1 -> nonnullStrings; - default -> nullableStrings; - }; + List<@NonNull String> list = + // :: error: (assignment.type.incompatible) + switch (fenum) { + // :: error: (switch.expression.type.incompatible) + case 1 -> nonnullStrings; + default -> nullableStrings; + }; - List<@Nullable String> list2 = - switch (fenum) { - // :: error: (switch.expression.type.incompatible) - case 1 -> nonnullStrings; - default -> nullableStrings; - }; - } + List<@Nullable String> list2 = + switch (fenum) { + // :: error: (switch.expression.type.incompatible) + case 1 -> nonnullStrings; + default -> nullableStrings; + }; + } } diff --git a/checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java b/checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java index 58ca103d5b7..af1c35ec918 100644 --- a/checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java +++ b/checker/tests/nullness/java17/SwitchExpressionTypeArgInference.java @@ -3,35 +3,35 @@ // @below-java17-jdk-skip-test public class SwitchExpressionTypeArgInference { - T method(T t) { - return t; - } + T method(T t) { + return t; + } - void test1(int i, @Nullable String nullable) { - @NonNull String s = - // :: error: (assignment) - // :: error: (type.arguments.not.inferred) - method( - switch (i) { - case 0: - yield method(nullable); - case 1: - yield ""; - default: - yield ""; - }); - } + void test1(int i, @Nullable String nullable) { + @NonNull String s = + // :: error: (assignment) + // :: error: (type.arguments.not.inferred) + method( + switch (i) { + case 0: + yield method(nullable); + case 1: + yield ""; + default: + yield ""; + }); + } - void test2(int i, @Nullable String nullable) { - @NonNull String s = - method( - switch (i) { - case 0: - yield method("nullable"); - case 1: - yield ""; - default: - yield ""; - }); - } + void test2(int i, @Nullable String nullable) { + @NonNull String s = + method( + switch (i) { + case 0: + yield method("nullable"); + case 1: + yield ""; + default: + yield ""; + }); + } } diff --git a/checker/tests/nullness/java17/SwitchTestIssue5412.java b/checker/tests/nullness/java17/SwitchTestIssue5412.java index 23c0fa52969..d7055c3190f 100644 --- a/checker/tests/nullness/java17/SwitchTestIssue5412.java +++ b/checker/tests/nullness/java17/SwitchTestIssue5412.java @@ -5,125 +5,125 @@ import org.checkerframework.checker.nullness.qual.NonNull; enum MyEnum { - VAL1, - VAL2, - VAL3 + VAL1, + VAL2, + VAL3 } class SwitchTestExhaustive { - public String foo1(MyEnum b) { - final var s = - switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; - }; - return s; - } - - public String foo1a(MyEnum b) { - final var s = - switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; - // The default case is dead code, so it would be possible for type-checking - // to skip it and not issue this warning. But giving the warning is also - // good. - default -> null; - }; - // :: error: (return.type.incompatible) - return s; - } - - public String foo2(MyEnum b) { - final var s = - switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; - default -> throw new RuntimeException(); - }; - return s; - } + public String foo1(MyEnum b) { + final var s = + switch (b) { + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + }; + return s; + } - public String foo3(MyEnum b) { - return switch (b) { - case VAL1 -> "1"; - case VAL2 -> "2"; - case VAL3 -> "3"; - }; - } + public String foo1a(MyEnum b) { + final var s = + switch (b) { + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + // The default case is dead code, so it would be possible for type-checking + // to skip it and not issue this warning. But giving the warning is also + // good. + default -> null; + }; + // :: error: (return.type.incompatible) + return s; + } - public String foo4(MyEnum b) { - String aString = "foo"; - switch (b) { - case VAL1: - return "a"; - case VAL2: - return "b"; - case VAL3: - return "c"; - default: - System.out.println(aString.hashCode()); - throw new Error(); + public String foo2(MyEnum b) { + final var s = + switch (b) { + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + default -> throw new RuntimeException(); + }; + return s; } - } - public String foo4a(MyEnum b) { - String aString = null; - switch (b) { - case VAL1: - aString = "a"; - break; - case VAL2: - aString = "b"; - break; - case VAL3: - aString = "c"; - break; - // The `default:` case is dead code, so it is acceptable for this method to compile - // without nullness errors. - default: - break; + public String foo3(MyEnum b) { + return switch (b) { + case VAL1 -> "1"; + case VAL2 -> "2"; + case VAL3 -> "3"; + }; } - // :: error: (return.type.incompatible) - return aString; - } - public String foo4b(MyEnum b) { - String aString; - switch (b) { - case VAL1: - aString = "a"; - break; - case VAL2: - aString = "b"; - break; - case VAL3: - aString = "c"; - break; - // The `default:` case is dead code, so it is acceptable for this method to compile - // without nullness errors. - default: - aString = null; - break; + public String foo4(MyEnum b) { + String aString = "foo"; + switch (b) { + case VAL1: + return "a"; + case VAL2: + return "b"; + case VAL3: + return "c"; + default: + System.out.println(aString.hashCode()); + throw new Error(); + } } - // :: error: (return.type.incompatible) - return aString; - } - // TODO: test fallthrough to the default: case. - public @NonNull String foo5(MyEnum b) { - String aString = "foo"; - switch (b) { - case VAL1: - return aString; - case VAL2: + public String foo4a(MyEnum b) { + String aString = null; + switch (b) { + case VAL1: + aString = "a"; + break; + case VAL2: + aString = "b"; + break; + case VAL3: + aString = "c"; + break; + // The `default:` case is dead code, so it is acceptable for this method to compile + // without nullness errors. + default: + break; + } + // :: error: (return.type.incompatible) return aString; - case VAL3: - default: + } + + public String foo4b(MyEnum b) { + String aString; + switch (b) { + case VAL1: + aString = "a"; + break; + case VAL2: + aString = "b"; + break; + case VAL3: + aString = "c"; + break; + // The `default:` case is dead code, so it is acceptable for this method to compile + // without nullness errors. + default: + aString = null; + break; + } + // :: error: (return.type.incompatible) return aString; } - } + + // TODO: test fallthrough to the default: case. + public @NonNull String foo5(MyEnum b) { + String aString = "foo"; + switch (b) { + case VAL1: + return aString; + case VAL2: + return aString; + case VAL3: + default: + return aString; + } + } } diff --git a/checker/tests/nullness/java21/FlowSwitch.java b/checker/tests/nullness/java21/FlowSwitch.java index 89f48d91a5e..dcaa29bbf13 100644 --- a/checker/tests/nullness/java21/FlowSwitch.java +++ b/checker/tests/nullness/java21/FlowSwitch.java @@ -8,102 +8,102 @@ public class FlowSwitch { - void test0(Number n) { - String s = null; - switch (n) { - case null, default: - { - // TODO: this should issue a dereference of nullable error. - n.toString(); - s = ""; + void test0(Number n) { + String s = null; + switch (n) { + case null, default: + { + // TODO: this should issue a dereference of nullable error. + n.toString(); + s = ""; + } } + s.toString(); } - s.toString(); - } - void test1(Integer i) { - String msg = null; - switch (i) { - case -1, 1: - msg = "-1 or 1"; - break; - case Integer j - when j > 0: - msg = "pos"; - break; - case Integer j: - msg = "everything else"; - break; + void test1(Integer i) { + String msg = null; + switch (i) { + case -1, 1: + msg = "-1 or 1"; + break; + case Integer j + when j > 0: + msg = "pos"; + break; + case Integer j: + msg = "everything else"; + break; + } + msg.toString(); } - msg.toString(); - } - void test2(Integer i) { - String msg = null; - switch (i) { - case -1, 1: - msg = "-1 or 1"; - break; - default: - msg = "everythingything else"; - break; - case 2: - msg = "pos"; - break; + void test2(Integer i) { + String msg = null; + switch (i) { + case -1, 1: + msg = "-1 or 1"; + break; + default: + msg = "everythingything else"; + break; + case 2: + msg = "pos"; + break; + } + msg.toString(); } - msg.toString(); - } - class A {} + class A {} - class B extends A {} + class B extends A {} - sealed interface I permits C, D {} + sealed interface I permits C, D {} - final class C implements I {} + final class C implements I {} - final class D implements I {} + final class D implements I {} - record Pair(T x, T y) {} + record Pair(T x, T y) {} - void testE(Pair p1) { - B e = + void testE(Pair p1) { + B e = + switch (p1) { + case Pair(A a, B b) -> b; + case Pair(B b, A a) -> b; + default -> null; + }; + B e2 = null; switch (p1) { - case Pair(A a, B b) -> b; - case Pair(B b, A a) -> b; - default -> null; - }; - B e2 = null; - switch (p1) { - case Pair(A a, B b) -> e2 = b; - case Pair(B b, A a) -> e2 = b; - default -> e2 = new B(); + case Pair(A a, B b) -> e2 = b; + case Pair(B b, A a) -> e2 = b; + default -> e2 = new B(); + } + e2.toString(); } - e2.toString(); - } - void test3(Pair p2) { - String s = null; - I e = null; - switch (p2) { - case Pair(I i, C c) -> { - e = c; - s = ""; - } - case Pair(I i, D d) -> { - e = d; - s = ""; - } - } - s.toString(); - e.toString(); + void test3(Pair p2) { + String s = null; + I e = null; + switch (p2) { + case Pair(I i, C c) -> { + e = c; + s = ""; + } + case Pair(I i, D d) -> { + e = d; + s = ""; + } + } + s.toString(); + e.toString(); - I e2 = null; - switch (p2) { - case Pair(C c, I i) -> e2 = c; - case Pair(D d, C c) -> e2 = d; - case Pair(D d1, D d2) -> e2 = d2; + I e2 = null; + switch (p2) { + case Pair(C c, I i) -> e2 = c; + case Pair(D d, C c) -> e2 = d; + case Pair(D d1, D d2) -> e2 = d2; + } + e2.toString(); } - e2.toString(); - } } diff --git a/checker/tests/nullness/java21/Issue6290.java b/checker/tests/nullness/java21/Issue6290.java index e44a696ed55..96f421d9587 100644 --- a/checker/tests/nullness/java21/Issue6290.java +++ b/checker/tests/nullness/java21/Issue6290.java @@ -3,9 +3,9 @@ // @below-java17-jdk-skip-test public class Issue6290 { - public Optional test(String param) { - var first = Optional.ofNullable(param); - var second = first.isPresent() ? first : Optional.ofNullable(param); - return second; - } + public Optional test(String param) { + var first = Optional.ofNullable(param); + var second = first.isPresent() ? first : Optional.ofNullable(param); + return second; + } } diff --git a/checker/tests/nullness/java21/NullRedundant.java b/checker/tests/nullness/java21/NullRedundant.java index b4351dfa5a9..9a671569014 100644 --- a/checker/tests/nullness/java21/NullRedundant.java +++ b/checker/tests/nullness/java21/NullRedundant.java @@ -6,107 +6,107 @@ import org.checkerframework.checker.nullness.qual.Nullable; class NullRedundant { - void test1(Object o) { - // :: warning: (nulltest.redundant) - if (o == null) { - System.out.println("o is null"); - } + void test1(Object o) { + // :: warning: (nulltest.redundant) + if (o == null) { + System.out.println("o is null"); + } - switch (o) { - case Number n: - System.out.println("Number: " + n); - break; - // :: warning: (nulltest.redundant) - case null: - System.out.println("null"); - break; - default: - System.out.println("anything else"); - } + switch (o) { + case Number n: + System.out.println("Number: " + n); + break; + // :: warning: (nulltest.redundant) + case null: + System.out.println("null"); + break; + default: + System.out.println("anything else"); + } - switch (o) { - // :: warning: (nulltest.redundant) - case null, default: - System.out.println("null"); - break; - } - } - - Object test2(Object o) { - switch (o) { - case Number n -> System.out.println("Number: " + n); - // :: warning: (nulltest.redundant) - case null -> System.out.println("null"); - default -> System.out.println("anything else"); + switch (o) { + // :: warning: (nulltest.redundant) + case null, default: + System.out.println("null"); + break; + } } - ; - switch (o) { - // :: warning: (nulltest.redundant) - case null, default -> System.out.println("null"); - } + Object test2(Object o) { + switch (o) { + case Number n -> System.out.println("Number: " + n); + // :: warning: (nulltest.redundant) + case null -> System.out.println("null"); + default -> System.out.println("anything else"); + } + ; - var output = switch (o) { - case Number n -> "Number: " + n; - // :: warning: (nulltest.redundant) - case null -> "null"; - default -> "anything else"; + // :: warning: (nulltest.redundant) + case null, default -> System.out.println("null"); + } + + var output = + switch (o) { + case Number n -> "Number: " + n; + // :: warning: (nulltest.redundant) + case null -> "null"; + default -> "anything else"; + }; + + return switch (o) { + // :: warning: (nulltest.redundant) + case null -> "null"; + default -> "anything else"; }; - - return switch (o) { - // :: warning: (nulltest.redundant) - case null -> "null"; - default -> "anything else"; - }; - } - - // Test with Nullable argument to make sure there is no false positive. - void test3(@Nullable Object o) { - if (o == null) { - System.out.println("o is null"); } - switch (o) { - case Number n: - System.out.println("Number: " + n); - break; - case null: - System.out.println("null"); - break; - default: - System.out.println("anything else"); - } + // Test with Nullable argument to make sure there is no false positive. + void test3(@Nullable Object o) { + if (o == null) { + System.out.println("o is null"); + } - switch (o) { - case null, default: - System.out.println("null"); - break; - } - } + switch (o) { + case Number n: + System.out.println("Number: " + n); + break; + case null: + System.out.println("null"); + break; + default: + System.out.println("anything else"); + } - Object test4(@Nullable Object o) { - switch (o) { - case Number n -> System.out.println("Number: " + n); - case null -> System.out.println("null"); - default -> System.out.println("anything else"); + switch (o) { + case null, default: + System.out.println("null"); + break; + } } - ; - switch (o) { - case null, default -> System.out.println("null"); - } + Object test4(@Nullable Object o) { + switch (o) { + case Number n -> System.out.println("Number: " + n); + case null -> System.out.println("null"); + default -> System.out.println("anything else"); + } + ; - var output = switch (o) { - case Number n -> "Number: " + n; - case null -> "null"; - default -> "anything else"; + case null, default -> System.out.println("null"); + } + + var output = + switch (o) { + case Number n -> "Number: " + n; + case null -> "null"; + default -> "anything else"; + }; + + return switch (o) { + case null -> "null"; + default -> "anything else"; }; - - return switch (o) { - case null -> "null"; - default -> "anything else"; - }; - } + } } diff --git a/checker/tests/nullness/java21/NullableSwitchSelector.java b/checker/tests/nullness/java21/NullableSwitchSelector.java index 8439bd05c54..079e1cf8b21 100644 --- a/checker/tests/nullness/java21/NullableSwitchSelector.java +++ b/checker/tests/nullness/java21/NullableSwitchSelector.java @@ -9,34 +9,34 @@ // @infer-stubs-skip-test public class NullableSwitchSelector { - static String formatterPatternSwitch1(@Nullable Object obj) { - return switch (obj) { - case Integer i -> obj.toString(); - case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) - case null -> obj.toString(); - default -> obj.toString(); - }; - } + static String formatterPatternSwitch1(@Nullable Object obj) { + return switch (obj) { + case Integer i -> obj.toString(); + case String s -> String.format("String %s", s); + // :: error: (dereference.of.nullable) + case null -> obj.toString(); + default -> obj.toString(); + }; + } - static String formatterPatternSwitch2(@Nullable Object obj) { - // :: error: (switching.nullable) - return switch (obj) { - case Integer i -> obj.toString(); - case String s -> String.format("String %s", s); - // TODO: If obj is null, this case isn't reachable, because a null pointer exception - // happens at the selector expression. - // :: error: (dereference.of.nullable) - default -> obj.toString(); - }; - } + static String formatterPatternSwitch2(@Nullable Object obj) { + // :: error: (switching.nullable) + return switch (obj) { + case Integer i -> obj.toString(); + case String s -> String.format("String %s", s); + // TODO: If obj is null, this case isn't reachable, because a null pointer exception + // happens at the selector expression. + // :: error: (dereference.of.nullable) + default -> obj.toString(); + }; + } - static String formatterPatternSwitch3(@Nullable Object obj) { - return switch (obj) { - case Integer i -> obj.toString(); - case String s -> String.format("String %s", s); - // :: error: (dereference.of.nullable) - case null, default -> obj.toString(); - }; - } + static String formatterPatternSwitch3(@Nullable Object obj) { + return switch (obj) { + case Integer i -> obj.toString(); + case String s -> String.format("String %s", s); + // :: error: (dereference.of.nullable) + case null, default -> obj.toString(); + }; + } } diff --git a/checker/tests/nullness/java21/SimpleCaseGuard.java b/checker/tests/nullness/java21/SimpleCaseGuard.java index ba7d8402aba..0b1789dbad3 100644 --- a/checker/tests/nullness/java21/SimpleCaseGuard.java +++ b/checker/tests/nullness/java21/SimpleCaseGuard.java @@ -11,21 +11,21 @@ public class SimpleCaseGuard { - @Nullable String field; + @Nullable String field; - void test2(Object obj, boolean b) { - switch (obj) { - case String s when field != null -> { - @NonNull String z = field; - } - case String s -> { - // :: error: (assignment.type.incompatible) - @NonNull String z = field; - } - default -> { - // :: error: (assignment.type.incompatible) - @NonNull String z = field; - } + void test2(Object obj, boolean b) { + switch (obj) { + case String s when field != null -> { + @NonNull String z = field; + } + case String s -> { + // :: error: (assignment.type.incompatible) + @NonNull String z = field; + } + default -> { + // :: error: (assignment.type.incompatible) + @NonNull String z = field; + } + } } - } } diff --git a/checker/tests/nullness/java8/DefaultMethods.java b/checker/tests/nullness/java8/DefaultMethods.java index de4be578b90..8dcc96ed18b 100644 --- a/checker/tests/nullness/java8/DefaultMethods.java +++ b/checker/tests/nullness/java8/DefaultMethods.java @@ -1,15 +1,15 @@ interface DefaultMethods { - default void method(String param) { - // :: error: (assignment.type.incompatible) - param = null; + default void method(String param) { + // :: error: (assignment.type.incompatible) + param = null; - String s = null; - // :: error: (dereference.of.nullable) - s.toString(); + String s = null; + // :: error: (dereference.of.nullable) + s.toString(); - // Ensure dataflow is running - s = ""; - s.toString(); - } + // Ensure dataflow is running + s = ""; + s.toString(); + } } diff --git a/checker/tests/nullness/java8/Issue1000.java b/checker/tests/nullness/java8/Issue1000.java index df048fe39bd..bdffbc42a62 100644 --- a/checker/tests/nullness/java8/Issue1000.java +++ b/checker/tests/nullness/java8/Issue1000.java @@ -1,18 +1,19 @@ // Test case for issue #1000: // https://github.com/typetools/checker-framework/issues/1000 -import java.util.Optional; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Optional; + public class Issue1000 { - void illegalInstantiation(Optional<@Nullable String> arg) {} + void illegalInstantiation(Optional<@Nullable String> arg) {} - String orElseAppliedToNonNull(Optional opt) { - return opt.orElse(""); - } + String orElseAppliedToNonNull(Optional opt) { + return opt.orElse(""); + } - String orElseAppliedToNullable(Optional opt) { - // :: error: (return.type.incompatible) - return opt.orElse(null); - } + String orElseAppliedToNullable(Optional opt) { + // :: error: (return.type.incompatible) + return opt.orElse(null); + } } diff --git a/checker/tests/nullness/java8/Issue1046Java8.java b/checker/tests/nullness/java8/Issue1046Java8.java index 9a525c09d1b..81a20acc3f8 100644 --- a/checker/tests/nullness/java8/Issue1046Java8.java +++ b/checker/tests/nullness/java8/Issue1046Java8.java @@ -2,35 +2,36 @@ // https://github.com/typetools/checker-framework/issues/1046 // Additonal test case: checker/tests/nullness/Issue1046.java +import org.checkerframework.checker.nullness.qual.UnknownKeyFor; + import java.util.List; import java.util.function.Function; -import org.checkerframework.checker.nullness.qual.UnknownKeyFor; public class Issue1046Java8 { - interface EnumMarker {} - - enum MyEnum implements EnumMarker { - A, - B; - } + interface EnumMarker {} - static class NS2Lists { - @SuppressWarnings("nullness") - static List transform(List p, Function q) { - return null; + enum MyEnum implements EnumMarker { + A, + B; } - static List transform2(List p, Function q) { - return p; - } - } + static class NS2Lists { + @SuppressWarnings("nullness") + static List transform(List p, Function q) { + return null; + } - abstract class NotSubtype2 { - void test(List p) { - NS2Lists.transform2(p, foo()); - NS2Lists.transform(p, foo()); + static List transform2(List p, Function q) { + return p; + } } - abstract Function foo(); - } + abstract class NotSubtype2 { + void test(List p) { + NS2Lists.transform2(p, foo()); + NS2Lists.transform(p, foo()); + } + + abstract Function foo(); + } } diff --git a/checker/tests/nullness/java8/Issue1098.java b/checker/tests/nullness/java8/Issue1098.java index b6af741597e..0c430e72eec 100644 --- a/checker/tests/nullness/java8/Issue1098.java +++ b/checker/tests/nullness/java8/Issue1098.java @@ -4,13 +4,13 @@ import java.util.Optional; public class Issue1098 { - void opt(Optional p1, T p2) {} + void opt(Optional p1, T p2) {} - void cls(Class p1, T p2) {} + void cls(Class p1, T p2) {} - @SuppressWarnings("keyfor:type.argument") - void use() { - opt(Optional.empty(), null); - cls(this.getClass(), null); - } + @SuppressWarnings("keyfor:type.argument") + void use() { + opt(Optional.empty(), null); + cls(this.getClass(), null); + } } diff --git a/checker/tests/nullness/java8/Issue1098NoJdk.java b/checker/tests/nullness/java8/Issue1098NoJdk.java index c4a61677770..674b817c426 100644 --- a/checker/tests/nullness/java8/Issue1098NoJdk.java +++ b/checker/tests/nullness/java8/Issue1098NoJdk.java @@ -3,15 +3,15 @@ @SuppressWarnings({"nullness", "initialization.fields.uninitialized"}) class MyObject { - Class getMyClass() { - return null; - } + Class getMyClass() { + return null; + } } class Issue1098NoJdk { - void cls2(Class p1, T p2) {} + void cls2(Class p1, T p2) {} - void use2(MyObject ths) { - cls2(ths.getMyClass(), null); - } + void use2(MyObject ths) { + cls2(ths.getMyClass(), null); + } } diff --git a/checker/tests/nullness/java8/Issue1633.java b/checker/tests/nullness/java8/Issue1633.java index 4a880bb2148..5855aa43073 100644 --- a/checker/tests/nullness/java8/Issue1633.java +++ b/checker/tests/nullness/java8/Issue1633.java @@ -1,128 +1,129 @@ // Test case for Issue 1633: // https://github.com/typetools/checker-framework/issues/1633 -import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.Covariant; +import java.util.function.Supplier; + public class Issue1633 { - // supplyNullable is a supplier that may return null. - // supplyNonNull is a supplier that does not return null. - - void foo1(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - // :: error: (argument.type.incompatible) - @Nullable String str = o.orElseGetUnannotated(supplyNullable); - } - - void foo2(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str1 = o.orElseGetNullable(supplyNullable); - } - - void foo2nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str1 = o.orElseGetNullableNoWildcard(supplyNullable); - } - - void foo3(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - // :: error: (argument.type.incompatible) - @Nullable String str2 = o.orElseGetNonNull(supplyNullable); - } - - void foo4(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str3 = o.orElseGetPolyNull(supplyNullable); - } - - void foo4nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { - @Nullable String str3 = o.orElseGetPolyNullNoWildcard(supplyNullable); - } - - void foo41(Optional1633 o) { - // This is a false postive because inference doesn't work with poly qualifiers. - // :: error: (return.type.incompatible) - @Nullable String str3 = o.orElseGetPolyNull(() -> null); - } - - void foo41nw(Optional1633 o) { - // This is a false postive because inference doesn't work with poly qualifiers. - // :: error: (return.type.incompatible) - @Nullable String str3 = o.orElseGetPolyNullNoWildcard(() -> null); - } - - void foo5(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str = o.orElseGetUnannotated(supplyNonNull); - } - - void foo6(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - // :: error: (assignment.type.incompatible) - @NonNull String str1 = o.orElseGetNullable(supplyNonNull); - } - - void foo6nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - // :: error: (assignment.type.incompatible) - @NonNull String str1 = o.orElseGetNullableNoWildcard(supplyNonNull); - } - - void foo7(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str2 = o.orElseGetNonNull(supplyNonNull); - } - - void foo8(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str3 = o.orElseGetPolyNull(supplyNonNull); - } - - void foo8nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - @NonNull String str3 = o.orElseGetPolyNullNoWildcard(supplyNonNull); - } + // supplyNullable is a supplier that may return null. + // supplyNonNull is a supplier that does not return null. + + void foo1(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + // :: error: (argument.type.incompatible) + @Nullable String str = o.orElseGetUnannotated(supplyNullable); + } + + void foo2(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str1 = o.orElseGetNullable(supplyNullable); + } + + void foo2nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str1 = o.orElseGetNullableNoWildcard(supplyNullable); + } + + void foo3(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + // :: error: (argument.type.incompatible) + @Nullable String str2 = o.orElseGetNonNull(supplyNullable); + } + + void foo4(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str3 = o.orElseGetPolyNull(supplyNullable); + } + + void foo4nw(Optional1633 o, Supplier<@Nullable String> supplyNullable) { + @Nullable String str3 = o.orElseGetPolyNullNoWildcard(supplyNullable); + } + + void foo41(Optional1633 o) { + // This is a false postive because inference doesn't work with poly qualifiers. + // :: error: (return.type.incompatible) + @Nullable String str3 = o.orElseGetPolyNull(() -> null); + } + + void foo41nw(Optional1633 o) { + // This is a false postive because inference doesn't work with poly qualifiers. + // :: error: (return.type.incompatible) + @Nullable String str3 = o.orElseGetPolyNullNoWildcard(() -> null); + } + + void foo5(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str = o.orElseGetUnannotated(supplyNonNull); + } + + void foo6(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + // :: error: (assignment.type.incompatible) + @NonNull String str1 = o.orElseGetNullable(supplyNonNull); + } + + void foo6nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + // :: error: (assignment.type.incompatible) + @NonNull String str1 = o.orElseGetNullableNoWildcard(supplyNonNull); + } + + void foo7(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str2 = o.orElseGetNonNull(supplyNonNull); + } + + void foo8(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str3 = o.orElseGetPolyNull(supplyNonNull); + } + + void foo8nw(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { + @NonNull String str3 = o.orElseGetPolyNullNoWildcard(supplyNonNull); + } } // From the JDK @Covariant(0) @NonNull final class Optional1633 { - /** If non-null, the value; if null, indicates no value is present. */ - private final @Nullable T value = null; - - // TODO: there are conceptually two versions of this method: - // public @Nullable T orElseGet(Supplier other) { - // public @NonNull T orElseGet(Supplier other) { - // Issue #1633 says that this annotation doesn't help at all: - // public @PolyNull T orElseGet(Supplier other) { - // but it does seem to work in this test case. - public T orElseGetUnannotated(Supplier other) { - return value != null ? value : other.get(); - } - - public @Nullable T orElseGetNullable(Supplier<@Nullable ? extends @Nullable T> other) { - return value != null ? value : other.get(); - } - - public @Nullable T orElseGetNullableNoWildcard(Supplier other) { - // The commented-out line fails to typecheck. - // return value != null ? value : other.get(); - if (value != null) { - return value; - } else { - return other.get(); - } - } - - public @NonNull T orElseGetNonNull(Supplier<@NonNull ? extends @NonNull T> other) { - return value != null ? value : other.get(); - } - - public @PolyNull T orElseGetPolyNull(Supplier<@PolyNull ? extends @PolyNull T> other) { - return value != null ? value : other.get(); - } - - public @PolyNull T orElseGetPolyNullNoWildcard(Supplier other) { - // The commented-out line fails to typecheck. - // return value != null ? value : other.get(); - if (value != null) { - return value; - } else { - return other.get(); - } - } + /** If non-null, the value; if null, indicates no value is present. */ + private final @Nullable T value = null; + + // TODO: there are conceptually two versions of this method: + // public @Nullable T orElseGet(Supplier other) { + // public @NonNull T orElseGet(Supplier other) { + // Issue #1633 says that this annotation doesn't help at all: + // public @PolyNull T orElseGet(Supplier other) { + // but it does seem to work in this test case. + public T orElseGetUnannotated(Supplier other) { + return value != null ? value : other.get(); + } + + public @Nullable T orElseGetNullable(Supplier<@Nullable ? extends @Nullable T> other) { + return value != null ? value : other.get(); + } + + public @Nullable T orElseGetNullableNoWildcard(Supplier other) { + // The commented-out line fails to typecheck. + // return value != null ? value : other.get(); + if (value != null) { + return value; + } else { + return other.get(); + } + } + + public @NonNull T orElseGetNonNull(Supplier<@NonNull ? extends @NonNull T> other) { + return value != null ? value : other.get(); + } + + public @PolyNull T orElseGetPolyNull(Supplier<@PolyNull ? extends @PolyNull T> other) { + return value != null ? value : other.get(); + } + + public @PolyNull T orElseGetPolyNullNoWildcard(Supplier other) { + // The commented-out line fails to typecheck. + // return value != null ? value : other.get(); + if (value != null) { + return value; + } else { + return other.get(); + } + } } diff --git a/checker/tests/nullness/java8/Issue363.java b/checker/tests/nullness/java8/Issue363.java index 900ab481b3d..e20d6dc25e8 100644 --- a/checker/tests/nullness/java8/Issue363.java +++ b/checker/tests/nullness/java8/Issue363.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/363 public class Issue363 { - void foo(java.util.OptionalInt value) { - value.orElseThrow(() -> new Error()); - } + void foo(java.util.OptionalInt value) { + value.orElseThrow(() -> new Error()); + } - void bar(java.util.OptionalInt value) { - java.util.function.Supplier s = () -> new Error(); - value.orElseThrow(s); - } + void bar(java.util.OptionalInt value) { + java.util.function.Supplier s = () -> new Error(); + value.orElseThrow(s); + } } diff --git a/checker/tests/nullness/java8/Issue366.java b/checker/tests/nullness/java8/Issue366.java index 01d3a0ff1dc..fa46b9ffb25 100644 --- a/checker/tests/nullness/java8/Issue366.java +++ b/checker/tests/nullness/java8/Issue366.java @@ -3,16 +3,17 @@ // but amended for Issue 1098: // https://github.com/typetools/checker-framework/issues/1098 -import java.util.Optional; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Optional; + public class Issue366 { - static Optional<@NonNull String> getPossiblyEmptyString() { - return Optional.ofNullable(null); - } + static Optional<@NonNull String> getPossiblyEmptyString() { + return Optional.ofNullable(null); + } - static Optional<@Nullable String> getPossiblyEmptyString2() { - return Optional.ofNullable(null); - } + static Optional<@Nullable String> getPossiblyEmptyString2() { + return Optional.ofNullable(null); + } } diff --git a/checker/tests/nullness/java8/Issue448.java b/checker/tests/nullness/java8/Issue448.java index 271492ef963..a36a32c79ca 100644 --- a/checker/tests/nullness/java8/Issue448.java +++ b/checker/tests/nullness/java8/Issue448.java @@ -4,9 +4,9 @@ import java.util.Arrays; enum Issue448 { - ONE; + ONE; - void method() { - Arrays.stream(values()).filter(key -> true); - } + void method() { + Arrays.stream(values()).filter(key -> true); + } } diff --git a/checker/tests/nullness/java8/Issue448Ext.java b/checker/tests/nullness/java8/Issue448Ext.java index 9356e39814d..866b1c08dfa 100644 --- a/checker/tests/nullness/java8/Issue448Ext.java +++ b/checker/tests/nullness/java8/Issue448Ext.java @@ -6,15 +6,17 @@ import java.util.stream.IntStream; public class Issue448Ext { - void getFor(int[] ia, int index) { - Arrays.stream(ia).filter(x -> true); - } + void getFor(int[] ia, int index) { + Arrays.stream(ia).filter(x -> true); + } - Object getFor(int[] ia, IntPredicate p) { - return Arrays.stream(ia).filter(p); - } + Object getFor(int[] ia, IntPredicate p) { + return Arrays.stream(ia).filter(p); + } - Object getFor(IntStream is, int index) { - return is.filter(key -> key == index).findFirst().orElseThrow(IllegalArgumentException::new); - } + Object getFor(IntStream is, int index) { + return is.filter(key -> key == index) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + } } diff --git a/checker/tests/nullness/java8/Issue496.java b/checker/tests/nullness/java8/Issue496.java index 071be672b14..00961e91ff7 100644 --- a/checker/tests/nullness/java8/Issue496.java +++ b/checker/tests/nullness/java8/Issue496.java @@ -5,17 +5,17 @@ public class Issue496 { - public static class Entity { - public final T value; - public final Class cls; + public static class Entity { + public final T value; + public final Class cls; - public Entity(T value, Class cls) { - this.value = value; - this.cls = cls; + public Entity(T value, Class cls) { + this.value = value; + this.cls = cls; + } } - } - public static Optional> testCase(Class targetClass) { - return Optional.empty().map((T val) -> new Entity(val, targetClass)); - } + public static Optional> testCase(Class targetClass) { + return Optional.empty().map((T val) -> new Entity(val, targetClass)); + } } diff --git a/checker/tests/nullness/java8/Issue529.java b/checker/tests/nullness/java8/Issue529.java index 158f91a5b22..3ea4b4d70e5 100644 --- a/checker/tests/nullness/java8/Issue529.java +++ b/checker/tests/nullness/java8/Issue529.java @@ -6,18 +6,18 @@ public class Issue529 { - // Crashes: - public Stream test(List list) { - return list.stream().map(e -> e); - } + // Crashes: + public Stream test(List list) { + return list.stream().map(e -> e); + } - // OK: - public Stream test2(List list) { - return list.stream(); - } + // OK: + public Stream test2(List list) { + return list.stream(); + } - // OK: - public Stream test3(Stream stream) { - return stream.map(e -> e); - } + // OK: + public Stream test3(Stream stream) { + return stream.map(e -> e); + } } diff --git a/checker/tests/nullness/java8/Issue557.java b/checker/tests/nullness/java8/Issue557.java index 9fed28c2145..706f2ddffb6 100644 --- a/checker/tests/nullness/java8/Issue557.java +++ b/checker/tests/nullness/java8/Issue557.java @@ -1,88 +1,89 @@ // Test case for issue 557: // https://github.com/typetools/checker-framework/issues/557 -import java.util.Optional; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Optional; + @SuppressWarnings("nullness") class MyOpt { - static MyOpt of(S p) { - return null; - } + static MyOpt of(S p) { + return null; + } - static MyOpt empty() { - return null; - } + static MyOpt empty() { + return null; + } } @SuppressWarnings("nullness") class MyOpt2 { - static MyOpt2 of(S p) { - return null; - } + static MyOpt2 of(S p) { + return null; + } - static MyOpt2 empty() { - return null; - } + static MyOpt2 empty() { + return null; + } } @SuppressWarnings("nullness") class MyOpt3 { - static MyOpt3 of(S p) { - return null; - } + static MyOpt3 of(S p) { + return null; + } - static MyOpt3 empty() { - return null; - } + static MyOpt3 empty() { + return null; + } } class Issue557a { - MyOpt opt(boolean flag) { - return flag ? MyOpt.of("Hello") : MyOpt.empty(); - } + MyOpt opt(boolean flag) { + return flag ? MyOpt.of("Hello") : MyOpt.empty(); + } - MyOpt opt2() { - return MyOpt.empty(); - } + MyOpt opt2() { + return MyOpt.empty(); + } - MyOpt opt3(boolean flag) { - return flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty()); - } + MyOpt opt3(boolean flag) { + return flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty()); + } - void foo(MyOpt param) {} + void foo(MyOpt param) {} - void callFoo(boolean flag) { - foo(flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty())); - } + void callFoo(boolean flag) { + foo(flag ? MyOpt.of("Hello") : (flag ? MyOpt.empty() : MyOpt.empty())); + } } class Issue557b { - MyOpt2 opt(boolean flag) { - return flag ? MyOpt2.of("Hello") : MyOpt2.empty(); - } + MyOpt2 opt(boolean flag) { + return flag ? MyOpt2.of("Hello") : MyOpt2.empty(); + } - MyOpt2 opt2() { - return MyOpt2.empty(); - } + MyOpt2 opt2() { + return MyOpt2.empty(); + } } class Issue557c { - MyOpt3 opt(boolean flag) { - return flag ? MyOpt3.of("Hello") : MyOpt3.empty(); - } + MyOpt3 opt(boolean flag) { + return flag ? MyOpt3.of("Hello") : MyOpt3.empty(); + } - MyOpt3 opt2() { - return MyOpt3.empty(); - } + MyOpt3 opt2() { + return MyOpt3.empty(); + } } class Issue557d { - Optional opt(boolean flag) { - return flag ? Optional.of("Hello") : Optional.empty(); - } + Optional opt(boolean flag) { + return flag ? Optional.of("Hello") : Optional.empty(); + } - Optional opt2() { - return Optional.empty(); - } + Optional opt2() { + return Optional.empty(); + } } diff --git a/checker/tests/nullness/java8/Issue579.java b/checker/tests/nullness/java8/Issue579.java index 7e25ded7780..b658af227f0 100644 --- a/checker/tests/nullness/java8/Issue579.java +++ b/checker/tests/nullness/java8/Issue579.java @@ -4,21 +4,21 @@ import java.util.Comparator; public class Issue579 implements Comparator { - private final Comparator real; + private final Comparator real; - @SuppressWarnings("unchecked") - Issue579(Comparator real) { - this.real = (Comparator) real; - } + @SuppressWarnings("unchecked") + Issue579(Comparator real) { + this.real = (Comparator) real; + } - @Override - public int compare(T a, T b) { - throw new RuntimeException(); - } + @Override + public int compare(T a, T b) { + throw new RuntimeException(); + } - @Override - public Comparator thenComparing(Comparator other) { - // :: warning: (nulltest.redundant) - return new Issue579<>(real == null ? other : real.thenComparing(other)); - } + @Override + public Comparator thenComparing(Comparator other) { + // :: warning: (nulltest.redundant) + return new Issue579<>(real == null ? other : real.thenComparing(other)); + } } diff --git a/checker/tests/nullness/java8/Issue596.java b/checker/tests/nullness/java8/Issue596.java index c1978e9ea16..cc52b1cd7a8 100644 --- a/checker/tests/nullness/java8/Issue596.java +++ b/checker/tests/nullness/java8/Issue596.java @@ -1,24 +1,25 @@ // Test case for Issue 596: // https://github.com/typetools/checker-framework/issues/596 -import java.util.concurrent.atomic.AtomicReference; import org.checkerframework.checker.nullness.qual.*; +import java.util.concurrent.atomic.AtomicReference; + public class Issue596 { - private static String getOrEmpty(AtomicReference<@Nullable String> ref) { - return Optional596.fromNullable(ref.get()).or(""); - } + private static String getOrEmpty(AtomicReference<@Nullable String> ref) { + return Optional596.fromNullable(ref.get()).or(""); + } } // From Google Guava class Optional596 { - public static Optional596 fromNullable(@Nullable T nullableReference) { - return new Optional596(); - } + public static Optional596 fromNullable(@Nullable T nullableReference) { + return new Optional596(); + } - public T or(T defaultValue) { - return defaultValue; - } + public T or(T defaultValue) { + return defaultValue; + } } diff --git a/checker/tests/nullness/java8/Issue704.java b/checker/tests/nullness/java8/Issue704.java index 401c8db90a7..905e668bef4 100644 --- a/checker/tests/nullness/java8/Issue704.java +++ b/checker/tests/nullness/java8/Issue704.java @@ -4,5 +4,5 @@ import java.util.function.IntSupplier; interface Issue704 { - IntSupplier zero = () -> 0; + IntSupplier zero = () -> 0; } diff --git a/checker/tests/nullness/java8/Issue720.java b/checker/tests/nullness/java8/Issue720.java index 92beb01ad41..d747160f515 100644 --- a/checker/tests/nullness/java8/Issue720.java +++ b/checker/tests/nullness/java8/Issue720.java @@ -4,9 +4,9 @@ import java.util.function.IntConsumer; public class Issue720 { - static IntConsumer consumer = Issue720::method; + static IntConsumer consumer = Issue720::method; - static int method(int x) { - return x; - } + static int method(int x) { + return x; + } } diff --git a/checker/tests/nullness/java8/UnionTypeBug.java b/checker/tests/nullness/java8/UnionTypeBug.java index 4140e30dcdc..4d441a037fc 100644 --- a/checker/tests/nullness/java8/UnionTypeBug.java +++ b/checker/tests/nullness/java8/UnionTypeBug.java @@ -9,31 +9,32 @@ abstract class UnionTypeBug { - void method() { - try { + void method() { + try { - badBoy(); + badBoy(); - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull InnerException1 | @NonNull InnerException2 e) { + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull InnerException1 | @NonNull InnerException2 e) { - // :: warning: (nullness.on.exception.parameter) - } catch (@NonNull InnerException3 | @NonNull InnerException4 e) { + // :: warning: (nullness.on.exception.parameter) + } catch (@NonNull InnerException3 | @NonNull InnerException4 e) { + } } - } - abstract void badBoy() throws InnerException1, InnerException2, InnerException3, InnerException4; + abstract void badBoy() + throws InnerException1, InnerException2, InnerException3, InnerException4; - @PolyUIType - class InnerException1 extends Exception {} + @PolyUIType + class InnerException1 extends Exception {} - @PolyUIType - class InnerException2 extends Exception {} + @PolyUIType + class InnerException2 extends Exception {} - @PolyUIType - class InnerException3 extends Exception {} + @PolyUIType + class InnerException3 extends Exception {} - @PolyUIType - class InnerException4 extends Exception {} + @PolyUIType + class InnerException4 extends Exception {} } diff --git a/checker/tests/nullness/java8/lambda/Dataflow.java b/checker/tests/nullness/java8/lambda/Dataflow.java index 49946fce85f..0eae76bdbb4 100644 --- a/checker/tests/nullness/java8/lambda/Dataflow.java +++ b/checker/tests/nullness/java8/lambda/Dataflow.java @@ -3,18 +3,18 @@ import org.checkerframework.checker.nullness.qual.*; public class Dataflow { - void context() { - FunctionDF<@Nullable Object, Object> o = - a -> { - // :: error: (dereference.of.nullable) - a.toString(); - a = ""; - a.toString(); - return ""; - }; - } + void context() { + FunctionDF<@Nullable Object, Object> o = + a -> { + // :: error: (dereference.of.nullable) + a.toString(); + a = ""; + a.toString(); + return ""; + }; + } } interface FunctionDF { - R apply(T t); + R apply(T t); } diff --git a/checker/tests/nullness/java8/lambda/FinalLocalVariables.java b/checker/tests/nullness/java8/lambda/FinalLocalVariables.java index aa04782bfe9..b73365e1d8b 100644 --- a/checker/tests/nullness/java8/lambda/FinalLocalVariables.java +++ b/checker/tests/nullness/java8/lambda/FinalLocalVariables.java @@ -3,101 +3,101 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionLE { - R apply(T t); + R apply(T t); } class LambdaEnclosing { - // Test static initializer - static { - String local1 = ""; - String local2 = null; - FunctionLE f0 = - s -> { - local1.toString(); - // :: error: (dereference.of.nullable) - local2.toString(); - return ""; - }; - } - - // Test instance initializer - { - String local1 = ""; - String local2 = null; - FunctionLE f0 = - s -> { - local1.toString(); - // :: error: (dereference.of.nullable) - local2.toString(); - return ""; - }; - } - - FunctionLE functionField = - s -> { + // Test static initializer + static { String local1 = ""; String local2 = null; FunctionLE f0 = - s2 -> { - local1.toString(); - // :: error: (dereference.of.nullable) - local2.toString(); - return ""; - }; - return ""; - }; - - void context() { - String local1 = ""; - String local2 = null; - - FunctionLE f1 = - s -> { - local1.toString(); - // :: error: (dereference.of.nullable) - local2.toString(); - class Inner { - - void context2() { - String local3 = ""; - String local4 = null; - - FunctionLE f2 = - s2 -> { + s -> { local1.toString(); - local2.toString(); - local3.toString(); // :: error: (dereference.of.nullable) - local4.toString(); - + local2.toString(); return ""; - }; - } - } + }; + } - new Object() { + // Test instance initializer + { + String local1 = ""; + String local2 = null; + FunctionLE f0 = + s -> { + local1.toString(); + // :: error: (dereference.of.nullable) + local2.toString(); + return ""; + }; + } + + FunctionLE functionField = + s -> { + String local1 = ""; + String local2 = null; + FunctionLE f0 = + s2 -> { + local1.toString(); + // :: error: (dereference.of.nullable) + local2.toString(); + return ""; + }; + return ""; + }; - @Override() - public String toString() { - String local3 = ""; - String local4 = null; + void context() { + String local1 = ""; + String local2 = null; - FunctionLE f2 = - s2 -> { + FunctionLE f1 = + s -> { local1.toString(); - local2.toString(); - local3.toString(); // :: error: (dereference.of.nullable) - local4.toString(); + local2.toString(); + class Inner { + + void context2() { + String local3 = ""; + String local4 = null; + + FunctionLE f2 = + s2 -> { + local1.toString(); + local2.toString(); + local3.toString(); + // :: error: (dereference.of.nullable) + local4.toString(); + + return ""; + }; + } + } + + new Object() { + + @Override() + public String toString() { + String local3 = ""; + String local4 = null; + + FunctionLE f2 = + s2 -> { + local1.toString(); + local2.toString(); + local3.toString(); + // :: error: (dereference.of.nullable) + local4.toString(); + + return ""; + }; + return ""; + } + }.toString(); return ""; - }; - return ""; - } - }.toString(); - - return ""; - }; - } + }; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue1864.java b/checker/tests/nullness/java8/lambda/Issue1864.java index 895efc088ca..77952073622 100644 --- a/checker/tests/nullness/java8/lambda/Issue1864.java +++ b/checker/tests/nullness/java8/lambda/Issue1864.java @@ -5,14 +5,14 @@ import java.util.function.Supplier; abstract class Issue1864 { - interface A {} + interface A {} - abstract List g(); + abstract List g(); - abstract void h(Supplier> s); + abstract void h(Supplier> s); - void f() { - Iterable xs = g(); - h(() -> xs); - } + void f() { + Iterable xs = g(); + h(() -> xs); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue1864b.java b/checker/tests/nullness/java8/lambda/Issue1864b.java index 0b6c6ab3ab3..074488ffd7b 100644 --- a/checker/tests/nullness/java8/lambda/Issue1864b.java +++ b/checker/tests/nullness/java8/lambda/Issue1864b.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/1864 public class Issue1864b { - interface Supplier { - Object get(); - } + interface Supplier { + Object get(); + } - Supplier foo() { - Object foo = new Object(); - return () -> foo; - } + Supplier foo() { + Object foo = new Object(); + return () -> foo; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue1897.java b/checker/tests/nullness/java8/lambda/Issue1897.java index 0aefd11ce2f..aba8e531382 100644 --- a/checker/tests/nullness/java8/lambda/Issue1897.java +++ b/checker/tests/nullness/java8/lambda/Issue1897.java @@ -4,10 +4,10 @@ import java.util.function.Function; public class Issue1897 { - Issue1897() { - final int length = 1; - takesLambda(s -> length); - } + Issue1897() { + final int length = 1; + takesLambda(s -> length); + } - static void takesLambda(Function function) {} + static void takesLambda(Function function) {} } diff --git a/checker/tests/nullness/java8/lambda/Issue3217.java b/checker/tests/nullness/java8/lambda/Issue3217.java index 2af70615bb7..b1540e06687 100644 --- a/checker/tests/nullness/java8/lambda/Issue3217.java +++ b/checker/tests/nullness/java8/lambda/Issue3217.java @@ -1,20 +1,21 @@ -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Function; + public class Issue3217 { - private final Function, Function> - proxyFunction; + private final Function, Function> + proxyFunction; - public Issue3217( - Function, Function> - proxyFunction) { - this.proxyFunction = proxyFunction; - } + public Issue3217( + Function, Function> + proxyFunction) { + this.proxyFunction = proxyFunction; + } } class SubClass extends Issue3217 { - public SubClass() { - super(x -> x); - Function, Function> p = y -> y; - } + public SubClass() { + super(x -> x); + Function, Function> p = y -> y; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue367.java b/checker/tests/nullness/java8/lambda/Issue367.java index cad34103a99..816cc13d6a0 100644 --- a/checker/tests/nullness/java8/lambda/Issue367.java +++ b/checker/tests/nullness/java8/lambda/Issue367.java @@ -1,7 +1,7 @@ // Test case for Issue 367: // https://github.com/typetools/checker-framework/issues/367 public class Issue367 { - static void test(Iterable threads) { - threads.forEach(thread -> System.out.println(thread)); - } + static void test(Iterable threads) { + threads.forEach(thread -> System.out.println(thread)); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue403.java b/checker/tests/nullness/java8/lambda/Issue403.java index 3b84fc9f85c..6bb0a82f8a5 100644 --- a/checker/tests/nullness/java8/lambda/Issue403.java +++ b/checker/tests/nullness/java8/lambda/Issue403.java @@ -4,11 +4,11 @@ import java.util.Comparator; public class Issue403 { - Comparator COMPARATOR = Comparator.comparing(w -> w.value); + Comparator COMPARATOR = Comparator.comparing(w -> w.value); - String value; + String value; - Issue403(final String value) { - this.value = value; - } + Issue403(final String value) { + this.value = value; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue436.java b/checker/tests/nullness/java8/lambda/Issue436.java index 2dddaa80be1..5710d916e0b 100644 --- a/checker/tests/nullness/java8/lambda/Issue436.java +++ b/checker/tests/nullness/java8/lambda/Issue436.java @@ -4,16 +4,16 @@ import java.util.function.Supplier; public class Issue436 { - public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) { - // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the - // lambda return for those return statements below - Supplier> supplier = - () -> { - if (makeAll) { - return asList("beer", "peanuts"); - } else { - return asList("cheese", "wine"); - } - }; - } + public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) { + // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the + // lambda return for those return statements below + Supplier> supplier = + () -> { + if (makeAll) { + return asList("beer", "peanuts"); + } else { + return asList("cheese", "wine"); + } + }; + } } diff --git a/checker/tests/nullness/java8/lambda/Issue572.java b/checker/tests/nullness/java8/lambda/Issue572.java index 0569b67aa12..c2a0bbd4049 100644 --- a/checker/tests/nullness/java8/lambda/Issue572.java +++ b/checker/tests/nullness/java8/lambda/Issue572.java @@ -1,16 +1,17 @@ // Test case for issue #572: https://github.com/typetools/checker-framework/issues/572 -import java.util.function.BiFunction; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.BiFunction; + public class Issue572 { - static C biApply(BiFunction f, A a, B b) { - return f.apply(a, b); - } + static C biApply(BiFunction f, A a, B b) { + return f.apply(a, b); + } - public A konst(@NonNull A a, @Nullable B b) { - // :: error: (argument.type.incompatible) - return biApply(((first, second) -> first), a, b); - } + public A konst(@NonNull A a, @Nullable B b) { + // :: error: (argument.type.incompatible) + return biApply(((first, second) -> first), a, b); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue870.java b/checker/tests/nullness/java8/lambda/Issue870.java index 389c3ac4aca..9ee246006f4 100644 --- a/checker/tests/nullness/java8/lambda/Issue870.java +++ b/checker/tests/nullness/java8/lambda/Issue870.java @@ -5,13 +5,15 @@ import java.util.zip.ZipFile; public class Issue870 { - public static Stream entries(ZipFile zipFile) { - return zipFile.stream() - .filter(entry -> !entry.isDirectory() && entry.getName().endsWith(".xml")); - } + public static Stream entries(ZipFile zipFile) { + return zipFile.stream() + .filter(entry -> !entry.isDirectory() && entry.getName().endsWith(".xml")); + } - public static Stream entries2(ZipFile zipFile) { - return zipFile.stream() - .filter((ZipEntry entry) -> !entry.isDirectory() && entry.getName().endsWith(".xml")); - } + public static Stream entries2(ZipFile zipFile) { + return zipFile.stream() + .filter( + (ZipEntry entry) -> + !entry.isDirectory() && entry.getName().endsWith(".xml")); + } } diff --git a/checker/tests/nullness/java8/lambda/Issue953bLambda.java b/checker/tests/nullness/java8/lambda/Issue953bLambda.java index ee855686bac..50b93240f18 100644 --- a/checker/tests/nullness/java8/lambda/Issue953bLambda.java +++ b/checker/tests/nullness/java8/lambda/Issue953bLambda.java @@ -1,26 +1,27 @@ // Test case for #953 // https://github.com/typetools/checker-framework/issues/953 +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.ArrayList; import java.util.List; import java.util.function.Function; -import org.checkerframework.checker.nullness.qual.NonNull; @SuppressWarnings("all") public class Issue953bLambda { - private static List> strs = new ArrayList<>(); + private static List> strs = new ArrayList<>(); - public static List<@NonNull R> mapList( - List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { - throw new RuntimeException(); - } + public static List<@NonNull R> mapList( + List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { + throw new RuntimeException(); + } - public static void test() { - List list = - mapList( - strs, - s -> { - return ""; - }); - } + public static void test() { + List list = + mapList( + strs, + s -> { + return ""; + }); + } } diff --git a/checker/tests/nullness/java8/lambda/LambdaNullness.java b/checker/tests/nullness/java8/lambda/LambdaNullness.java index 18688ff8cbd..74377dbd719 100644 --- a/checker/tests/nullness/java8/lambda/LambdaNullness.java +++ b/checker/tests/nullness/java8/lambda/LambdaNullness.java @@ -3,107 +3,107 @@ import org.checkerframework.checker.nullness.qual.*; interface Noop { - void noop(); + void noop(); } interface FunctionNull { - R apply(T t); + R apply(T t); } interface Supplier { - R supply(); + R supply(); } interface BiFunctionNull { - R apply(T t, U u); + R apply(T t, U u); } public class LambdaNullness { - // Annotations in lamba expressions, in static, instance of fields initializers are stored on - // the last declared constructor. - // - // For example, the annotation for @Nullable Integer x on f7's initializer - // is stored on here because it is the last defined constructor. - // - // See TypeFromElement::annotateParam - LambdaNullness(FunctionNull f, Object e) {} - - // No parameters; result is void - Noop f1 = () -> {}; - // No parameters, expression body - Supplier f2a = () -> 42; - - // No parameters, expression body - // :: error: (return.type.incompatible) - Supplier f2b = () -> null; - - // No parameters, expression body - Supplier<@Nullable Void> f3 = () -> null; - // No parameters, block body with return - Supplier f4a = - () -> { - return 42; - }; - // No parameters, block body with return - Supplier<@Nullable Integer> f4b = - () -> { - // :: error: (assignment.type.incompatible) - @NonNull String s = null; - - return null; - }; - // No parameters, void block body - Noop f5 = - () -> { - System.gc(); - }; - - // Complex block body with returns - Supplier f6 = - () -> { - if (true) { - return 12; - } else { - int result = 15; - for (int i = 1; i < 10; i++) { - result *= i; - } - // :: error: (return.type.incompatible) - return null; - } - }; - - // Single declared-type parameter - FunctionNull<@Nullable Integer, Integer> f7 = (@Nullable Integer x) -> 1; - - // Single declared-type parameter - FunctionNull<@Nullable String, String> f9 = - // :: error: (lambda.param.type.incompatible) - (@NonNull String x) -> { - return x + ""; - }; - // Single inferred-type parameter - FunctionNull<@NonNull Integer, Integer> f10 = (x) -> x + 1; - // Parentheses optional for single - FunctionNull<@Nullable Integer, Integer> f11 = x -> 1; - - // Multiple declared-type parameters - BiFunctionNull f16 = - (@Nullable Integer x, final Integer y) -> { - x = null; - // :: error: (unboxing.of.nullable) - return x + y; - }; - - // Multiple inferred-type parameters - BiFunctionNull f18 = (x, y) -> x + y; - - // Infer based on context. - FunctionNull<@Nullable String, String> fn = - (s) -> { - // :: error: (dereference.of.nullable) - s.toString(); - return ""; - }; + // Annotations in lamba expressions, in static, instance of fields initializers are stored on + // the last declared constructor. + // + // For example, the annotation for @Nullable Integer x on f7's initializer + // is stored on here because it is the last defined constructor. + // + // See TypeFromElement::annotateParam + LambdaNullness(FunctionNull f, Object e) {} + + // No parameters; result is void + Noop f1 = () -> {}; + // No parameters, expression body + Supplier f2a = () -> 42; + + // No parameters, expression body + // :: error: (return.type.incompatible) + Supplier f2b = () -> null; + + // No parameters, expression body + Supplier<@Nullable Void> f3 = () -> null; + // No parameters, block body with return + Supplier f4a = + () -> { + return 42; + }; + // No parameters, block body with return + Supplier<@Nullable Integer> f4b = + () -> { + // :: error: (assignment.type.incompatible) + @NonNull String s = null; + + return null; + }; + // No parameters, void block body + Noop f5 = + () -> { + System.gc(); + }; + + // Complex block body with returns + Supplier f6 = + () -> { + if (true) { + return 12; + } else { + int result = 15; + for (int i = 1; i < 10; i++) { + result *= i; + } + // :: error: (return.type.incompatible) + return null; + } + }; + + // Single declared-type parameter + FunctionNull<@Nullable Integer, Integer> f7 = (@Nullable Integer x) -> 1; + + // Single declared-type parameter + FunctionNull<@Nullable String, String> f9 = + // :: error: (lambda.param.type.incompatible) + (@NonNull String x) -> { + return x + ""; + }; + // Single inferred-type parameter + FunctionNull<@NonNull Integer, Integer> f10 = (x) -> x + 1; + // Parentheses optional for single + FunctionNull<@Nullable Integer, Integer> f11 = x -> 1; + + // Multiple declared-type parameters + BiFunctionNull f16 = + (@Nullable Integer x, final Integer y) -> { + x = null; + // :: error: (unboxing.of.nullable) + return x + y; + }; + + // Multiple inferred-type parameters + BiFunctionNull f18 = (x, y) -> x + y; + + // Infer based on context. + FunctionNull<@Nullable String, String> fn = + (s) -> { + // :: error: (dereference.of.nullable) + s.toString(); + return ""; + }; } diff --git a/checker/tests/nullness/java8/lambda/Parameters.java b/checker/tests/nullness/java8/lambda/Parameters.java index 3fa4b8a2abb..bdc6bfca4e7 100644 --- a/checker/tests/nullness/java8/lambda/Parameters.java +++ b/checker/tests/nullness/java8/lambda/Parameters.java @@ -3,68 +3,68 @@ import org.checkerframework.checker.nullness.qual.*; interface NullConsumer { - void method(@Nullable String s); + void method(@Nullable String s); } interface NNConsumer { - void method(@NonNull String s); + void method(@NonNull String s); } class LambdaParam { - NullConsumer fn1 = - // :: error: (lambda.param.type.incompatible) - (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; - // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - - { - // :: error: (lambda.param.type.incompatible) - NullConsumer fn1 = (@NonNull String i) -> {}; + NullConsumer fn1 = + // :: error: (lambda.param.type.incompatible) + (@NonNull String i) -> {}; NullConsumer fn2 = (@Nullable String i) -> {}; // :: error: (lambda.param.type.incompatible) NullConsumer fn3 = (String i) -> {}; NNConsumer fn4 = (String i) -> {}; NNConsumer fn5 = (@Nullable String i) -> {}; NNConsumer fn6 = (@NonNull String i) -> {}; - } - static { - // :: error: (lambda.param.type.incompatible) - NullConsumer fn1 = (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; - // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - } + { + // :: error: (lambda.param.type.incompatible) + NullConsumer fn1 = (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; + // :: error: (lambda.param.type.incompatible) + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + } - static void foo() { - NullConsumer fn1 = + static { // :: error: (lambda.param.type.incompatible) - (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; - // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - } + NullConsumer fn1 = (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; + // :: error: (lambda.param.type.incompatible) + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + } - void bar() { - NullConsumer fn1 = + static void foo() { + NullConsumer fn1 = + // :: error: (lambda.param.type.incompatible) + (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; // :: error: (lambda.param.type.incompatible) - (@NonNull String i) -> {}; - NullConsumer fn2 = (@Nullable String i) -> {}; - // :: error: (lambda.param.type.incompatible) - NullConsumer fn3 = (String i) -> {}; - NNConsumer fn4 = (String i) -> {}; - NNConsumer fn5 = (@Nullable String i) -> {}; - NNConsumer fn6 = (@NonNull String i) -> {}; - } + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + } + + void bar() { + NullConsumer fn1 = + // :: error: (lambda.param.type.incompatible) + (@NonNull String i) -> {}; + NullConsumer fn2 = (@Nullable String i) -> {}; + // :: error: (lambda.param.type.incompatible) + NullConsumer fn3 = (String i) -> {}; + NNConsumer fn4 = (String i) -> {}; + NNConsumer fn5 = (@Nullable String i) -> {}; + NNConsumer fn6 = (@NonNull String i) -> {}; + } } diff --git a/checker/tests/nullness/java8/lambda/ParametersInBody.java b/checker/tests/nullness/java8/lambda/ParametersInBody.java index a5932aecbed..234793a2a3a 100644 --- a/checker/tests/nullness/java8/lambda/ParametersInBody.java +++ b/checker/tests/nullness/java8/lambda/ParametersInBody.java @@ -3,48 +3,48 @@ import org.checkerframework.checker.nullness.qual.*; interface ConsumerLPB { - void method(@Nullable String s); + void method(@Nullable String s); } interface NNConsumerLPB { - void method(@NonNull String s); + void method(@NonNull String s); } class LambdaParamBody { - // :: error: (lambda.param.type.incompatible) - ConsumerLPB fn0 = (String i) -> i.toString(); - ConsumerLPB fn2 = - (@Nullable String i) -> { - // :: error: (dereference.of.nullable) - i.toString(); - }; - ConsumerLPB fn3 = - // :: error: (lambda.param.type.incompatible) - (String i) -> { - i.toString(); - }; - ConsumerLPB fn3b = - (i) -> { - // :: error: (dereference.of.nullable) - i.toString(); - }; + // :: error: (lambda.param.type.incompatible) + ConsumerLPB fn0 = (String i) -> i.toString(); + ConsumerLPB fn2 = + (@Nullable String i) -> { + // :: error: (dereference.of.nullable) + i.toString(); + }; + ConsumerLPB fn3 = + // :: error: (lambda.param.type.incompatible) + (String i) -> { + i.toString(); + }; + ConsumerLPB fn3b = + (i) -> { + // :: error: (dereference.of.nullable) + i.toString(); + }; - NNConsumerLPB fn4 = - (String i) -> { - i.toString(); - }; - NNConsumerLPB fn4b = - (i) -> { - i.toString(); - }; - NNConsumerLPB fn5 = - (@Nullable String i) -> { - // :: error: (dereference.of.nullable) - i.toString(); - }; - NNConsumerLPB fn6 = - (@NonNull String i) -> { - i.toString(); - }; + NNConsumerLPB fn4 = + (String i) -> { + i.toString(); + }; + NNConsumerLPB fn4b = + (i) -> { + i.toString(); + }; + NNConsumerLPB fn5 = + (@Nullable String i) -> { + // :: error: (dereference.of.nullable) + i.toString(); + }; + NNConsumerLPB fn6 = + (@NonNull String i) -> { + i.toString(); + }; } diff --git a/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java b/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java index f847f64a228..c7ae4909590 100644 --- a/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java +++ b/checker/tests/nullness/java8/lambda/ParametersInBodyGenerics.java @@ -1,43 +1,44 @@ // Test that parameter annotations are correct in the body of a lambda -import java.util.List; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + public class ParametersInBodyGenerics { - interface NullableConsumer { - void method(List<@Nullable String> s); - } + interface NullableConsumer { + void method(List<@Nullable String> s); + } - interface NonNullConsumer { - void method(@NonNull List s); - } + interface NonNullConsumer { + void method(@NonNull List s); + } - void test() { - // :: error: (lambda.param.type.incompatible) - NullableConsumer fn0 = (List i) -> i.get(0).toString(); - NullableConsumer fn2 = - (List<@Nullable String> i) -> { - // :: error: (dereference.of.nullable) - i.get(0).toString(); - }; - NullableConsumer fn3 = + void test() { // :: error: (lambda.param.type.incompatible) - (List i) -> { - i.get(0).toString(); - }; - NullableConsumer fn3b = - (i) -> { - // :: error: (dereference.of.nullable) - i.get(0).toString(); - }; + NullableConsumer fn0 = (List i) -> i.get(0).toString(); + NullableConsumer fn2 = + (List<@Nullable String> i) -> { + // :: error: (dereference.of.nullable) + i.get(0).toString(); + }; + NullableConsumer fn3 = + // :: error: (lambda.param.type.incompatible) + (List i) -> { + i.get(0).toString(); + }; + NullableConsumer fn3b = + (i) -> { + // :: error: (dereference.of.nullable) + i.get(0).toString(); + }; - NonNullConsumer fn4 = - (List i) -> { - i.get(0).toString(); - }; - NonNullConsumer fn4b = - (i) -> { - i.get(0).toString(); - }; - } + NonNullConsumer fn4 = + (List i) -> { + i.get(0).toString(); + }; + NonNullConsumer fn4b = + (i) -> { + i.get(0).toString(); + }; + } } diff --git a/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java b/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java index b9370f609e9..f723d6dc61f 100644 --- a/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java +++ b/checker/tests/nullness/java8/lambda/RefinedLocalInLambda.java @@ -1,34 +1,35 @@ // Test case for issue #1248: // https://github.com/typetools/checker-framework/issues/1248 -import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Predicate; + public class RefinedLocalInLambda { - public static void main(String[] args) { - printIntegersGreaterThan(10); - } + public static void main(String[] args) { + printIntegersGreaterThan(10); + } - public static void printIntegersGreaterThan(@Nullable Integer limit) { - // :: error: (unboxing.of.nullable) - printIntegersWithPredicate(i -> i > limit); // type-checking fails - if (limit == null) { - return; + public static void printIntegersGreaterThan(@Nullable Integer limit) { + // :: error: (unboxing.of.nullable) + printIntegersWithPredicate(i -> i > limit); // type-checking fails + if (limit == null) { + return; + } + printIntegersWithPredicate(i -> i > limit); // type-checking succeeds + @NonNull Integer limit2 = limit; + printIntegersWithPredicate(i -> i > limit2); // type-checking succeeds + Integer limit3 = limit; + printIntegersWithPredicate(i -> i > limit3); // type-checking succeeds } - printIntegersWithPredicate(i -> i > limit); // type-checking succeeds - @NonNull Integer limit2 = limit; - printIntegersWithPredicate(i -> i > limit2); // type-checking succeeds - Integer limit3 = limit; - printIntegersWithPredicate(i -> i > limit3); // type-checking succeeds - } - public static void printIntegersWithPredicate(Predicate tester) { - for (int i = 0; i < 100; i++) { - if (tester.test(i)) { - System.out.println(i); - } + public static void printIntegersWithPredicate(Predicate tester) { + for (int i = 0; i < 100; i++) { + if (tester.test(i)) { + System.out.println(i); + } + } } - } } diff --git a/checker/tests/nullness/java8/lambda/Returns.java b/checker/tests/nullness/java8/lambda/Returns.java index c3f20140ade..072a36ab004 100644 --- a/checker/tests/nullness/java8/lambda/Returns.java +++ b/checker/tests/nullness/java8/lambda/Returns.java @@ -3,41 +3,41 @@ // The return of a lambda is a lambda interface ConsumerSupplier { - ConsumerR get(); + ConsumerR get(); } interface ConsumerR { - void method(@Nullable String s); + void method(@Nullable String s); } interface SupplierSupplier { - SupplierRe get(); + SupplierRe get(); } interface SupplierRe { - @NonNull String method(); + @NonNull String method(); } class MetaReturn { - // :: error: (dereference.of.nullable) - ConsumerSupplier t1 = () -> (s) -> s.toString(); - ConsumerSupplier t2 = - () -> { - // :: error: (lambda.param.type.incompatible) - return (String s) -> { - s.toString(); - }; - }; - - SupplierSupplier t3 = - () -> { - // :: error: (return.type.incompatible) - return () -> null; - }; - - SupplierSupplier t4 = - () -> { - return ""::toString; - }; + // :: error: (dereference.of.nullable) + ConsumerSupplier t1 = () -> (s) -> s.toString(); + ConsumerSupplier t2 = + () -> { + // :: error: (lambda.param.type.incompatible) + return (String s) -> { + s.toString(); + }; + }; + + SupplierSupplier t3 = + () -> { + // :: error: (return.type.incompatible) + return () -> null; + }; + + SupplierSupplier t4 = + () -> { + return ""::toString; + }; } diff --git a/checker/tests/nullness/java8/lambda/Shadowed.java b/checker/tests/nullness/java8/lambda/Shadowed.java index aa2b665ddfe..ff706b5ba43 100644 --- a/checker/tests/nullness/java8/lambda/Shadowed.java +++ b/checker/tests/nullness/java8/lambda/Shadowed.java @@ -3,26 +3,26 @@ // Test shadowing of parameters interface ConsumerS { - void take(@Nullable String s); + void take(@Nullable String s); } interface NNConsumerS { - void take(String s); + void take(String s); } public class Shadowed { - ConsumerS c = - s -> { - // :: error: (dereference.of.nullable) - s.toString(); - - class Inner { - NNConsumerS n = - s -> { - // No error + ConsumerS c = + s -> { + // :: error: (dereference.of.nullable) s.toString(); - }; - } - }; + + class Inner { + NNConsumerS n = + s -> { + // No error + s.toString(); + }; + } + }; } diff --git a/checker/tests/nullness/java8/lambda/TypeVarAssign.java b/checker/tests/nullness/java8/lambda/TypeVarAssign.java index 729dc9afffb..1dcd39cbc49 100644 --- a/checker/tests/nullness/java8/lambda/TypeVarAssign.java +++ b/checker/tests/nullness/java8/lambda/TypeVarAssign.java @@ -1,13 +1,13 @@ import org.checkerframework.checker.nullness.qual.*; interface Fn { - T func(T t); + T func(T t); } class TestAssign { - void foo(Fn f) {} + void foo(Fn f) {} - void context() { - foo((@NonNull String s) -> s); - } + void context() { + foo((@NonNull String s) -> s); + } } diff --git a/checker/tests/nullness/java8/methodref/AssignmentContextTest.java b/checker/tests/nullness/java8/methodref/AssignmentContextTest.java index a5bf3819d92..8c18c233916 100644 --- a/checker/tests/nullness/java8/methodref/AssignmentContextTest.java +++ b/checker/tests/nullness/java8/methodref/AssignmentContextTest.java @@ -1,43 +1,43 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionAC { - String apply(String s); + String apply(String s); } interface FunctionAC2 { - String apply(@Nullable String s); + String apply(@Nullable String s); } public class AssignmentContextTest { - // Test assign - FunctionAC f1 = String::toString; - // :: error: (methodref.receiver.invalid) - FunctionAC2 f2 = String::toString; - - // Test casts - Object o1 = (Object) (FunctionAC) String::toString; - // :: error: (methodref.receiver.invalid) - Object o2 = (Object) (FunctionAC2) String::toString; - - void take(FunctionAC f) { - // Test argument assingment - take(String::toString); - } - - void take2(FunctionAC2 f) { - // Test argument assingment + // Test assign + FunctionAC f1 = String::toString; // :: error: (methodref.receiver.invalid) - take2(String::toString); - } + FunctionAC2 f2 = String::toString; - FunctionAC supply() { - // Test return assingment - return String::toString; - } - - FunctionAC2 supply2() { - // Test return assingment + // Test casts + Object o1 = (Object) (FunctionAC) String::toString; // :: error: (methodref.receiver.invalid) - return String::toString; - } + Object o2 = (Object) (FunctionAC2) String::toString; + + void take(FunctionAC f) { + // Test argument assingment + take(String::toString); + } + + void take2(FunctionAC2 f) { + // Test argument assingment + // :: error: (methodref.receiver.invalid) + take2(String::toString); + } + + FunctionAC supply() { + // Test return assingment + return String::toString; + } + + FunctionAC2 supply2() { + // Test return assingment + // :: error: (methodref.receiver.invalid) + return String::toString; + } } diff --git a/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java index 480f2185f6b..01a3a1978f0 100644 --- a/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java +++ b/checker/tests/nullness/java8/methodref/ClassTypeArgInference.java @@ -1,38 +1,38 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ClassTypeArgInference { - public static void main(String[] args) { - Gen o = new Gen<>(""); - // :: error: (type.arguments.not.inferred) - Factory f = Gen::make; - // :: error: (methodref.param.invalid) - Factory f2 = Gen::make; - // :: error: (methodref.receiver.invalid) :: error: (methodref.return.invalid) - Factory f3 = Gen<@Nullable String>::make; - f2.make(o, null).toString(); - } + public static void main(String[] args) { + Gen o = new Gen<>(""); + // :: error: (type.arguments.not.inferred) + Factory f = Gen::make; + // :: error: (methodref.param.invalid) + Factory f2 = Gen::make; + // :: error: (methodref.receiver.invalid) :: error: (methodref.return.invalid) + Factory f3 = Gen<@Nullable String>::make; + f2.make(o, null).toString(); + } - static class Gen { - G field; + static class Gen { + G field; - Gen(G g) { - field = g; - } + Gen(G g) { + field = g; + } - public G getField() { - return field; - } + public G getField() { + return field; + } - G make(G g) { - return g; - } + G make(G g) { + return g; + } - Gen id() { - return this; + Gen id() { + return this; + } } - } - interface Factory { - String make(Gen g, @Nullable String t); - } + interface Factory { + String make(Gen g, @Nullable String t); + } } diff --git a/checker/tests/nullness/java8/methodref/FromByteCode.java b/checker/tests/nullness/java8/methodref/FromByteCode.java index 4981d2c3a39..89cb5611488 100644 --- a/checker/tests/nullness/java8/methodref/FromByteCode.java +++ b/checker/tests/nullness/java8/methodref/FromByteCode.java @@ -1,14 +1,14 @@ import org.checkerframework.checker.nullness.qual.*; interface FunctionBC { - R apply(T t); + R apply(T t); } public class FromByteCode { - FunctionBC f1 = String::toString; + FunctionBC f1 = String::toString; - // Make sure there aren't any issues generating an error with a method from byte code - // :: error: (methodref.param.invalid) - FunctionBC<@Nullable String, String> f2 = String::new; + // Make sure there aren't any issues generating an error with a method from byte code + // :: error: (methodref.param.invalid) + FunctionBC<@Nullable String, String> f2 = String::new; } diff --git a/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java b/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java index a13b6ed85e0..082321a3dcb 100644 --- a/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java +++ b/checker/tests/nullness/java8/methodref/GroundTargetTypeLub.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.nullness.qual.*; interface Supplier { - T supply(); + T supply(); } interface Supplier2 { - T supply(); + T supply(); } class GroundTargetType { - static @Nullable Object myMethod() { - return null; - } + static @Nullable Object myMethod() { + return null; + } - Supplier fn = GroundTargetType::myMethod; - // :: error: (methodref.return.invalid) - Supplier fn2 = GroundTargetType::myMethod; + Supplier fn = GroundTargetType::myMethod; + // :: error: (methodref.return.invalid) + Supplier fn2 = GroundTargetType::myMethod; - // Supplier2 - // :: error: (methodref.return.invalid) - Supplier2 fn3 = GroundTargetType::myMethod; + // Supplier2 + // :: error: (methodref.return.invalid) + Supplier2 fn3 = GroundTargetType::myMethod; } diff --git a/checker/tests/nullness/java8/methodref/MemberReferences.java b/checker/tests/nullness/java8/methodref/MemberReferences.java index 07a828f1a9e..bda36457e0a 100644 --- a/checker/tests/nullness/java8/methodref/MemberReferences.java +++ b/checker/tests/nullness/java8/methodref/MemberReferences.java @@ -2,46 +2,46 @@ abstract class References { - void context(References c) { + void context(References c) { - // No error - FuncA funcA1 = References::aMethod1; - // No error, covariant parameters - FuncA funcA2 = References::aMethod2; - // :: error: (methodref.return.invalid) - FuncA funcA3 = References::aMethod3; - // :: error: (methodref.return.invalid) - FuncA funcA4 = References::aMethod4; + // No error + FuncA funcA1 = References::aMethod1; + // No error, covariant parameters + FuncA funcA2 = References::aMethod2; + // :: error: (methodref.return.invalid) + FuncA funcA3 = References::aMethod3; + // :: error: (methodref.return.invalid) + FuncA funcA4 = References::aMethod4; - // :: error: (methodref.param.invalid) - FuncB funcB1 = References::aMethod1; - // No error - FuncB funcB2 = References::aMethod2; - // :: error: (methodref.return.invalid) :: error: (methodref.param.invalid) - FuncB funcB3 = References::aMethod3; - // :: error: (methodref.return.invalid) - FuncB funcB4 = References::aMethod4; + // :: error: (methodref.param.invalid) + FuncB funcB1 = References::aMethod1; + // No error + FuncB funcB2 = References::aMethod2; + // :: error: (methodref.return.invalid) :: error: (methodref.param.invalid) + FuncB funcB3 = References::aMethod3; + // :: error: (methodref.return.invalid) + FuncB funcB4 = References::aMethod4; - FuncA typeArg1 = References::<@NonNull String>aMethod5; - // :: error: (methodref.param.invalid) - FuncB typeArg2 = References::<@NonNull String>aMethod5; - } + FuncA typeArg1 = References::<@NonNull String>aMethod5; + // :: error: (methodref.param.invalid) + FuncB typeArg2 = References::<@NonNull String>aMethod5; + } - abstract @NonNull String aMethod1(@NonNull String s); + abstract @NonNull String aMethod1(@NonNull String s); - abstract @NonNull String aMethod2(@Nullable String s); + abstract @NonNull String aMethod2(@Nullable String s); - abstract @Nullable String aMethod3(@NonNull String s); + abstract @Nullable String aMethod3(@NonNull String s); - abstract @Nullable String aMethod4(@Nullable String s); + abstract @Nullable String aMethod4(@Nullable String s); - abstract T aMethod5(T t); + abstract T aMethod5(T t); - interface FuncA { - @NonNull String method(References a, @NonNull String b); - } + interface FuncA { + @NonNull String method(References a, @NonNull String b); + } - interface FuncB { - @NonNull String method(References a, @Nullable String b); - } + interface FuncB { + @NonNull String method(References a, @Nullable String b); + } } diff --git a/checker/tests/nullness/java8/methodref/PolyNullness.java b/checker/tests/nullness/java8/methodref/PolyNullness.java index 6a941f46ce1..5eebf1397e8 100644 --- a/checker/tests/nullness/java8/methodref/PolyNullness.java +++ b/checker/tests/nullness/java8/methodref/PolyNullness.java @@ -1,34 +1,34 @@ import org.checkerframework.checker.nullness.qual.*; interface PolyFunc { - @PolyNull String method(@PolyNull String in); + @PolyNull String method(@PolyNull String in); } interface NonNullFunc { - @NonNull String method(@NonNull String in); + @NonNull String method(@NonNull String in); } interface MixedFunc { - @NonNull String method(@Nullable String in); + @NonNull String method(@Nullable String in); } class Context { - static @PolyNull String poly(@PolyNull String in) { - return in; - } + static @PolyNull String poly(@PolyNull String in) { + return in; + } - static String nonPoly(String in) { - return in; - } + static String nonPoly(String in) { + return in; + } - void context() { - PolyFunc f1 = Context::poly; - // :: error: (methodref.param.invalid) - PolyFunc f2 = Context::nonPoly; + void context() { + PolyFunc f1 = Context::poly; + // :: error: (methodref.param.invalid) + PolyFunc f2 = Context::nonPoly; - NonNullFunc f3 = Context::poly; - // :: error: (methodref.return.invalid) - MixedFunc f4 = Context::poly; - } + NonNullFunc f3 = Context::poly; + // :: error: (methodref.return.invalid) + MixedFunc f4 = Context::poly; + } } diff --git a/checker/tests/nullness/java8/methodref/Postconditions.java b/checker/tests/nullness/java8/methodref/Postconditions.java index cf53f38ea85..5dd0d9f02f4 100644 --- a/checker/tests/nullness/java8/methodref/Postconditions.java +++ b/checker/tests/nullness/java8/methodref/Postconditions.java @@ -6,28 +6,28 @@ import org.checkerframework.checker.nullness.qual.*; interface AssertFunc { - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testParam(final @Nullable Object param); + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testParam(final @Nullable Object param); } interface AssertFunc2 { - @EnsuresNonNullIf(result = true, expression = "#1") - boolean testParam(final @Nullable Object param); + @EnsuresNonNullIf(result = true, expression = "#1") + boolean testParam(final @Nullable Object param); } public class AssertionTest { - @EnsuresNonNullIf(result = true, expression = "#1") - static boolean override(final @Nullable Object param) { - return param != null; - } + @EnsuresNonNullIf(result = true, expression = "#1") + static boolean override(final @Nullable Object param) { + return param != null; + } - static boolean overrideAssertFunc2(final @Nullable Object param) { - return param != null; - } + static boolean overrideAssertFunc2(final @Nullable Object param) { + return param != null; + } - void context() { - AssertFunc f = AssertionTest::override; - // :: error: (methodref.receiver.postcondition) - AssertFunc2 f2 = AssertionTest::overrideAssertFunc2; - } + void context() { + AssertFunc f = AssertionTest::override; + // :: error: (methodref.receiver.postcondition) + AssertFunc2 f2 = AssertionTest::overrideAssertFunc2; + } } diff --git a/checker/tests/nullness/java8/methodref/ReceiversMethodref.java b/checker/tests/nullness/java8/methodref/ReceiversMethodref.java index eb64281ef06..153ca368665 100644 --- a/checker/tests/nullness/java8/methodref/ReceiversMethodref.java +++ b/checker/tests/nullness/java8/methodref/ReceiversMethodref.java @@ -5,92 +5,92 @@ // It could just use tainted. interface Unbound1 { - void apply(@NonNull MyClass my); + void apply(@NonNull MyClass my); } interface Unbound2 { - void apply(@Nullable MyClass my); + void apply(@Nullable MyClass my); } interface Supplier1 { - R supply(); + R supply(); } interface Bound { - void apply(); + void apply(); } class MyClass { - // :: error: (nullness.on.receiver) - void take(@NonNull MyClass this) {} - - // :: error: (nullness.on.receiver) - void context1(@Nullable MyClass this, @NonNull MyClass my1, @Nullable MyClass my2) { - - Unbound1 u1 = MyClass::take; - // :: error: (methodref.receiver.invalid) - Unbound2 u2 = MyClass::take; - - Bound b1 = my1::take; - // :: error: (methodref.receiver.bound.invalid) - Bound b2 = my2::take; + // :: error: (nullness.on.receiver) + void take(@NonNull MyClass this) {} - // :: error: (methodref.receiver.bound.invalid) - Bound b11 = this::take; - } + // :: error: (nullness.on.receiver) + void context1(@Nullable MyClass this, @NonNull MyClass my1, @Nullable MyClass my2) { - // :: error: (nullness.on.receiver) - void context2(@NonNull MyClass this) { - Bound b21 = this::take; - } + Unbound1 u1 = MyClass::take; + // :: error: (methodref.receiver.invalid) + Unbound2 u2 = MyClass::take; - class MySubClass extends MyClass { + Bound b1 = my1::take; + // :: error: (methodref.receiver.bound.invalid) + Bound b2 = my2::take; - // :: error: (nullness.on.receiver) - void context1(@Nullable MySubClass this) { - // :: error: (methodref.receiver.bound.invalid) - Bound b = super::take; + // :: error: (methodref.receiver.bound.invalid) + Bound b11 = this::take; } // :: error: (nullness.on.receiver) - void context2(@NonNull MySubClass this) { - Bound b = super::take; + void context2(@NonNull MyClass this) { + Bound b21 = this::take; } - class Nested { - // :: error: (nullness.on.receiver) - void context1(@Nullable Nested this) { - // :: error: (methodref.receiver.bound.invalid) - Bound b = MySubClass.super::take; - } - - // :: error: (nullness.on.receiver) - void context2(@NonNull Nested this) { - Bound b = MySubClass.super::take; - } + class MySubClass extends MyClass { + + // :: error: (nullness.on.receiver) + void context1(@Nullable MySubClass this) { + // :: error: (methodref.receiver.bound.invalid) + Bound b = super::take; + } + + // :: error: (nullness.on.receiver) + void context2(@NonNull MySubClass this) { + Bound b = super::take; + } + + class Nested { + // :: error: (nullness.on.receiver) + void context1(@Nullable Nested this) { + // :: error: (methodref.receiver.bound.invalid) + Bound b = MySubClass.super::take; + } + + // :: error: (nullness.on.receiver) + void context2(@NonNull Nested this) { + Bound b = MySubClass.super::take; + } + } } - } } class Outer { - class Inner1 { - // :: error: (nullness.on.receiver) - Inner1(@Nullable Outer Outer.this) {} - } + class Inner1 { + // :: error: (nullness.on.receiver) + Inner1(@Nullable Outer Outer.this) {} + } + + class Inner2 { + // :: error: (nullness.on.receiver) + Inner2(@NonNull Outer Outer.this) {} + } - class Inner2 { // :: error: (nullness.on.receiver) - Inner2(@NonNull Outer Outer.this) {} - } - - // :: error: (nullness.on.receiver) - void context(@Nullable Outer this) { - // This one is unbound and needs an Outer as a param - Supplier1 f1 = Inner1::new; - // :: error: (methodref.receiver.bound.invalid) - Supplier1 f2 = Inner2::new; - - // Supplier1 f = /*4*/Inner::new; - // 4 <: 3? Constructor annotations? - } + void context(@Nullable Outer this) { + // This one is unbound and needs an Outer as a param + Supplier1 f1 = Inner1::new; + // :: error: (methodref.receiver.bound.invalid) + Supplier1 f2 = Inner2::new; + + // Supplier1 f = /*4*/Inner::new; + // 4 <: 3? Constructor annotations? + } } diff --git a/checker/tests/nullness/java8/methodref/TestGenFunc.java b/checker/tests/nullness/java8/methodref/TestGenFunc.java index 9b5d9440fad..1d915020487 100644 --- a/checker/tests/nullness/java8/methodref/TestGenFunc.java +++ b/checker/tests/nullness/java8/methodref/TestGenFunc.java @@ -1,41 +1,41 @@ import org.checkerframework.checker.nullness.qual.*; public class TestGenFunc { - interface FuncNullableParam { - T nullableParam(U u); - } - - interface FuncNonNullParam { - T nonNullParam(U u); - } - - static V nonNullReturn(P u) { - throw new RuntimeException(""); - } - - static V nonNullParameter(P u) { - throw new RuntimeException(""); - } - - static V allNullable(P u) { - throw new RuntimeException(""); - } - - void context() { - // :: error: (type.arguments.not.inferred) - FuncNullableParam f = TestGenFunc::nonNullReturn; - // :: error: (type.arguments.not.inferred) - FuncNonNullParam f2 = TestGenFunc::nonNullReturn; - } - - void context2() { - // :: error: (type.arguments.not.inferred) - FuncNullableParam f = TestGenFunc::nonNullParameter; - FuncNonNullParam f2 = TestGenFunc::nonNullParameter; - } - - void context3() { - FuncNullableParam f = TestGenFunc::allNullable; - FuncNonNullParam f2 = TestGenFunc::allNullable; - } + interface FuncNullableParam { + T nullableParam(U u); + } + + interface FuncNonNullParam { + T nonNullParam(U u); + } + + static V nonNullReturn(P u) { + throw new RuntimeException(""); + } + + static V nonNullParameter(P u) { + throw new RuntimeException(""); + } + + static V allNullable(P u) { + throw new RuntimeException(""); + } + + void context() { + // :: error: (type.arguments.not.inferred) + FuncNullableParam f = TestGenFunc::nonNullReturn; + // :: error: (type.arguments.not.inferred) + FuncNonNullParam f2 = TestGenFunc::nonNullReturn; + } + + void context2() { + // :: error: (type.arguments.not.inferred) + FuncNullableParam f = TestGenFunc::nonNullParameter; + FuncNonNullParam f2 = TestGenFunc::nonNullParameter; + } + + void context3() { + FuncNullableParam f = TestGenFunc::allNullable; + FuncNonNullParam f2 = TestGenFunc::allNullable; + } } diff --git a/checker/tests/nullness/java8inference/FalsePositives.java b/checker/tests/nullness/java8inference/FalsePositives.java index c8ff17b0a64..340975a60f2 100644 --- a/checker/tests/nullness/java8inference/FalsePositives.java +++ b/checker/tests/nullness/java8inference/FalsePositives.java @@ -9,55 +9,58 @@ import java.util.Queue; public class FalsePositives { - static class Partitioning {} + static class Partitioning {} - public static List> partitionInto(Queue elts, int k) { - if (elts.size() < k) { - throw new IllegalArgumentException(); + public static List> partitionInto(Queue elts, int k) { + if (elts.size() < k) { + throw new IllegalArgumentException(); + } + return partitionIntoHelper(elts, Arrays.asList(new Partitioning()), k, 0); } - return partitionIntoHelper(elts, Arrays.asList(new Partitioning()), k, 0); - } - public static List> partitionIntoHelper( - Queue elts, List> resultSoFar, int numEmptyParts, int numNonemptyParts) { - throw new RuntimeException(); - } - - interface Box {} + public static List> partitionIntoHelper( + Queue elts, + List> resultSoFar, + int numEmptyParts, + int numNonemptyParts) { + throw new RuntimeException(); + } - interface Function { - R apply(P p); - } + interface Box {} - interface Utils { - Box foo(Box input, Function function); + interface Function { + R apply(P p); + } - Function bar(Function function); - } + interface Utils { + Box foo(Box input, Function function); - class Test { - Box demo(Utils u, Box bs) { - return u.foo(bs, u.bar((String s) -> 5)); + Function bar(Function function); } - Integer ugh(String n) { - return 5; - } + class Test { + Box demo(Utils u, Box bs) { + return u.foo(bs, u.bar((String s) -> 5)); + } + + Integer ugh(String n) { + return 5; + } - Box demo2(Utils u, Box bs) { - return u.foo(bs, u.bar(this::ugh)); + Box demo2(Utils u, Box bs) { + return u.foo(bs, u.bar(this::ugh)); + } } - } - abstract class Test2 { - abstract Box> foo(Box> p); + abstract class Test2 { + abstract Box> foo(Box> p); - abstract Box> bar(Function f); + abstract Box> bar(Function f); - abstract String baz(Number p); + abstract String baz(Number p); - Box> demo() { - return foo(bar(this::baz)); + Box> demo() { + return foo(bar(this::baz)); + } } - } } diff --git a/checker/tests/nullness/java8inference/InLambda.java b/checker/tests/nullness/java8inference/InLambda.java index b82b9dc3501..e7ff0430fdf 100644 --- a/checker/tests/nullness/java8inference/InLambda.java +++ b/checker/tests/nullness/java8inference/InLambda.java @@ -1,29 +1,29 @@ public class InLambda { - static class Mine { - @SuppressWarnings("nullness") // just a utility - static Mine some() { - return null; + static class Mine { + @SuppressWarnings("nullness") // just a utility + static Mine some() { + return null; + } } - } - interface Function { - R apply(T t); - } + interface Function { + R apply(T t); + } - interface Box {} + interface Box {} - static class Boxes { - @SuppressWarnings("nullness") // just a utility - static Box transform(Function function) { - return null; + static class Boxes { + @SuppressWarnings("nullness") // just a utility + static Box transform(Function function) { + return null; + } } - } - class Infer { - Box> f = - Boxes.transform( - el -> { - return Mine.some(); - }); - } + class Infer { + Box> f = + Boxes.transform( + el -> { + return Mine.some(); + }); + } } diff --git a/checker/tests/nullness/java8inference/InLambdaAnnotated.java b/checker/tests/nullness/java8inference/InLambdaAnnotated.java index 973a04b553c..3135cacd779 100644 --- a/checker/tests/nullness/java8inference/InLambdaAnnotated.java +++ b/checker/tests/nullness/java8inference/InLambdaAnnotated.java @@ -1,36 +1,36 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class InLambdaAnnotated { - static class Mine { - @SuppressWarnings("nullness") // just a utility - static Mine some() { - return null; + static class Mine { + @SuppressWarnings("nullness") // just a utility + static Mine some() { + return null; + } } - } - interface Function { - R apply(T t); - } + interface Function { + R apply(T t); + } - interface Box {} + interface Box {} - static class Boxes { - @SuppressWarnings("nullness") // just a utility - static Box transform(Function function) { - return null; + static class Boxes { + @SuppressWarnings("nullness") // just a utility + static Box transform(Function function) { + return null; + } } - } - class Infer { - // The nested Mine.some() needs to infer the right type. - Box> g = - Boxes.transform( - el -> { - return Mine.some(); - }); + class Infer { + // The nested Mine.some() needs to infer the right type. + Box> g = + Boxes.transform( + el -> { + return Mine.some(); + }); - void bar(Function> fun) { - Box> h = Boxes.transform(fun); + void bar(Function> fun) { + Box> h = Boxes.transform(fun); + } } - } } diff --git a/checker/tests/nullness/java8inference/Inference.java b/checker/tests/nullness/java8inference/Inference.java index 3aec44122c9..7152027ce85 100644 --- a/checker/tests/nullness/java8inference/Inference.java +++ b/checker/tests/nullness/java8inference/Inference.java @@ -2,28 +2,28 @@ // https://github.com/typetools/checker-framework/issues/979 class MyStream { - @SuppressWarnings("nullness") - R collect(MyCollector collector) { - return null; - } + @SuppressWarnings("nullness") + R collect(MyCollector collector) { + return null; + } } interface MyCollector {} public class Inference { - @SuppressWarnings("nullness") - static MyCollector> toImmutableStream() { - return null; - } + @SuppressWarnings("nullness") + static MyCollector> toImmutableStream() { + return null; + } - MyStream test(MyStream p) { - /* Need Java 8 assignment context to correctly infer type arguments. - return p.collect(toImmutableStream()); - ^ - found : @Initialized @NonNull MyStream - required: @Initialized @NonNull MyStream<@Initialized @NonNull String> - */ - return p.collect(toImmutableStream()); - } + MyStream test(MyStream p) { + /* Need Java 8 assignment context to correctly infer type arguments. + return p.collect(toImmutableStream()); + ^ + found : @Initialized @NonNull MyStream + required: @Initialized @NonNull MyStream<@Initialized @NonNull String> + */ + return p.collect(toImmutableStream()); + } } diff --git a/checker/tests/nullness/java8inference/InferenceSimpler.java b/checker/tests/nullness/java8inference/InferenceSimpler.java index f7ea9b8adca..909e797c7ab 100644 --- a/checker/tests/nullness/java8inference/InferenceSimpler.java +++ b/checker/tests/nullness/java8inference/InferenceSimpler.java @@ -5,17 +5,17 @@ @SuppressWarnings("nullness") // don't bother with implementations class ISOuter { - static List wrap(V value) { - return null; - } + static List wrap(V value) { + return null; + } - static List empty() { - return null; - } + static List empty() { + return null; + } } public class InferenceSimpler { - List> foo() { - return ISOuter.wrap(ISOuter.empty()); - } + List> foo() { + return ISOuter.wrap(ISOuter.empty()); + } } diff --git a/checker/tests/nullness/java8inference/Issue1032.java b/checker/tests/nullness/java8inference/Issue1032.java index bf2661bb3b8..e6b1b21934c 100644 --- a/checker/tests/nullness/java8inference/Issue1032.java +++ b/checker/tests/nullness/java8inference/Issue1032.java @@ -1,30 +1,31 @@ // Test case for issue #1032: // https://github.com/typetools/checker-framework/issues/1032 -import java.util.stream.Stream; import org.checkerframework.checker.nullness.qual.*; +import java.util.stream.Stream; + public class Issue1032 { - @SuppressWarnings("nullness") - static @NonNull String castStringToNonNull(@Nullable String arg) { - return (@NonNull String) arg; - } + @SuppressWarnings("nullness") + static @NonNull String castStringToNonNull(@Nullable String arg) { + return (@NonNull String) arg; + } - Stream<@NonNull String> mapStringCast1(Stream<@Nullable String> arg) { - return arg.map(Issue1032::castStringToNonNull); - } + Stream<@NonNull String> mapStringCast1(Stream<@Nullable String> arg) { + return arg.map(Issue1032::castStringToNonNull); + } - @SuppressWarnings("nullness") - static @NonNull T castTToNonNull(@Nullable T arg) { - return (@NonNull T) arg; - } + @SuppressWarnings("nullness") + static @NonNull T castTToNonNull(@Nullable T arg) { + return (@NonNull T) arg; + } - Stream<@NonNull String> mapStringCast2(Stream<@Nullable String> arg) { - return arg.map(Issue1032::castTToNonNull); - } + Stream<@NonNull String> mapStringCast2(Stream<@Nullable String> arg) { + return arg.map(Issue1032::castTToNonNull); + } - Stream<@NonNull T> mapTCast(Stream<@Nullable T> arg) { - return arg.map(Issue1032::castTToNonNull); - } + Stream<@NonNull T> mapTCast(Stream<@Nullable T> arg) { + return arg.map(Issue1032::castTToNonNull); + } } diff --git a/checker/tests/nullness/java8inference/Issue1084.java b/checker/tests/nullness/java8inference/Issue1084.java index 521a264d5dd..62dfeedf970 100644 --- a/checker/tests/nullness/java8inference/Issue1084.java +++ b/checker/tests/nullness/java8inference/Issue1084.java @@ -4,20 +4,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; class MyOpt { - static MyOpt<@NonNull S> empty() { - throw new RuntimeException(); - } + static MyOpt<@NonNull S> empty() { + throw new RuntimeException(); + } - // :: error: (type.argument.type.incompatible) - static MyOpt of(S p) { - throw new RuntimeException(); - } + // :: error: (type.argument.type.incompatible) + static MyOpt of(S p) { + throw new RuntimeException(); + } } public class Issue1084 { - MyOpt get() { - return this.hashCode() > 0 ? MyOpt.of(5L) : MyOpt.empty(); - } + MyOpt get() { + return this.hashCode() > 0 ? MyOpt.of(5L) : MyOpt.empty(); + } - MyOpt oba = MyOpt.empty(); + MyOpt oba = MyOpt.empty(); } diff --git a/checker/tests/nullness/java8inference/Issue1366.java b/checker/tests/nullness/java8inference/Issue1366.java index 9cb3b376513..b4ca486bc66 100644 --- a/checker/tests/nullness/java8inference/Issue1366.java +++ b/checker/tests/nullness/java8inference/Issue1366.java @@ -1,13 +1,13 @@ // Test case for Issue 1366. // https://github.com/typetools/checker-framework/issues/1366 abstract class Issue1366 { - abstract Issue1366 m1(Issue1366 p1, Issue1366 p2); + abstract Issue1366 m1(Issue1366 p1, Issue1366 p2); - abstract Issue1366 m2(Issue1366 p); + abstract Issue1366 m2(Issue1366 p); - abstract void m3(Issue1366 p); + abstract void m3(Issue1366 p); - void foo(Issue1366 s) { - s.m3(s.m2(s.m1(s, s))); - } + void foo(Issue1366 s) { + s.m3(s.m2(s.m1(s, s))); + } } diff --git a/checker/tests/nullness/java8inference/Issue1464.java b/checker/tests/nullness/java8inference/Issue1464.java index 743f014a097..b01adb35728 100644 --- a/checker/tests/nullness/java8inference/Issue1464.java +++ b/checker/tests/nullness/java8inference/Issue1464.java @@ -3,20 +3,20 @@ public class Issue1464 { - public interface Variable { + public interface Variable { - void addChangedListener(VariableChangedListener listener); - } + void addChangedListener(VariableChangedListener listener); + } - public interface VariableChangedListener { + public interface VariableChangedListener { - void variableChanged(final Variable variable); - } + void variableChanged(final Variable variable); + } - protected void addChangedListener( - final Variable variable, final VariableChangedListener listener) {} + protected void addChangedListener( + final Variable variable, final VariableChangedListener listener) {} - public void main(final Variable tmp) { - addChangedListener(tmp, variable -> System.out.println(variable)); - } + public void main(final Variable tmp) { + addChangedListener(tmp, variable -> System.out.println(variable)); + } } diff --git a/checker/tests/nullness/java8inference/Issue1630.java b/checker/tests/nullness/java8inference/Issue1630.java index 0b3e2286eee..0d1831de7b3 100644 --- a/checker/tests/nullness/java8inference/Issue1630.java +++ b/checker/tests/nullness/java8inference/Issue1630.java @@ -1,26 +1,33 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1630 { - static @Nullable String toString(Object o) { - return null; - } + static @Nullable String toString(Object o) { + return null; + } - public static List<@Nullable String> f(@Nullable List xs) { - return xs != null - ? xs.stream().map(Issue1630::toString).filter(Objects::nonNull).collect(Collectors.toList()) - : Collections.emptyList(); - } + public static List<@Nullable String> f(@Nullable List xs) { + return xs != null + ? xs.stream() + .map(Issue1630::toString) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + : Collections.emptyList(); + } - public static List f2(@Nullable List xs) { - return xs != null - // TODO: we could refine the type of filter is the postconditions of the predicate - // is @EnsuresNonNull("#1"). - // :: error: (type.arguments.not.inferred) - ? xs.stream().map(Issue1630::toString).filter(Objects::nonNull).collect(Collectors.toList()) - : Collections.emptyList(); - } + public static List f2(@Nullable List xs) { + return xs != null + // TODO: we could refine the type of filter is the postconditions of the predicate + // is @EnsuresNonNull("#1"). + // :: error: (type.arguments.not.inferred) + ? xs.stream() + .map(Issue1630::toString) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + : Collections.emptyList(); + } } diff --git a/checker/tests/nullness/java8inference/Issue1818.java b/checker/tests/nullness/java8inference/Issue1818.java index df18d9c60f3..79a6ec85b77 100644 --- a/checker/tests/nullness/java8inference/Issue1818.java +++ b/checker/tests/nullness/java8inference/Issue1818.java @@ -1,11 +1,12 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.List; import java.util.function.Consumer; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue1818 { - void f() { - Consumer> c = values -> values.forEach(value -> g(value)); - } + void f() { + Consumer> c = values -> values.forEach(value -> g(value)); + } - void g(@Nullable Object o) {} + void g(@Nullable Object o) {} } diff --git a/checker/tests/nullness/java8inference/Issue1954.java b/checker/tests/nullness/java8inference/Issue1954.java index f5091c84283..85129b1e55d 100644 --- a/checker/tests/nullness/java8inference/Issue1954.java +++ b/checker/tests/nullness/java8inference/Issue1954.java @@ -1,26 +1,27 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.function.*; import java.util.stream.*; -import org.checkerframework.checker.nullness.qual.*; // @skip-test public class Issue1954 { - public interface Getter { - R get(); - } + public interface Getter { + R get(); + } - public interface NullStringGetter extends Getter<@Nullable String> {} + public interface NullStringGetter extends Getter<@Nullable String> {} - public Getter transform(Function, R> fn, Getter getter) { - return () -> fn.apply(Stream.of(getter.get())); - } + public Getter transform(Function, R> fn, Getter getter) { + return () -> fn.apply(Stream.of(getter.get())); + } - public static @Nullable T fn(Stream arg) { - return arg.findFirst().orElse(null); - } + public static @Nullable T fn(Stream arg) { + return arg.findFirst().orElse(null); + } - public void doo() { - NullStringGetter nullStringGetter = () -> null; - // :: error: type inference failed. - transform(Issue1954::fn, nullStringGetter).get(); - } + public void doo() { + NullStringGetter nullStringGetter = () -> null; + // :: error: type inference failed. + transform(Issue1954::fn, nullStringGetter).get(); + } } diff --git a/checker/tests/nullness/java8inference/Issue2235.java b/checker/tests/nullness/java8inference/Issue2235.java index 6ce8743b642..a6568f28af7 100644 --- a/checker/tests/nullness/java8inference/Issue2235.java +++ b/checker/tests/nullness/java8inference/Issue2235.java @@ -2,25 +2,25 @@ // by a false negative from Issue 979 // https://github.com/typetools/checker-framework/issues/979 public class Issue2235 { - // Simple wrapper class with a public generic method - // to make an instance: - static class Holder { - T t; + // Simple wrapper class with a public generic method + // to make an instance: + static class Holder { + T t; - private Holder(T t) { - this.t = t; - } + private Holder(T t) { + this.t = t; + } - public static Holder make(T t) { - return new Holder<>(t); + public static Holder make(T t) { + return new Holder<>(t); + } } - } - public static void main(String[] args) throws Exception { - // Null is hidden via nested calls, but assigned to a non-null type: - // :: error: (type.arguments.not.inferred) - Holder> h = Holder.make(Holder.make(null)); - // NullPointerException will fire here: - h.t.t.toString(); - } + public static void main(String[] args) throws Exception { + // Null is hidden via nested calls, but assigned to a non-null type: + // :: error: (type.arguments.not.inferred) + Holder> h = Holder.make(Holder.make(null)); + // NullPointerException will fire here: + h.t.t.toString(); + } } diff --git a/checker/tests/nullness/java8inference/Issue2719.java b/checker/tests/nullness/java8inference/Issue2719.java index ca85681148d..40b42cebbc1 100644 --- a/checker/tests/nullness/java8inference/Issue2719.java +++ b/checker/tests/nullness/java8inference/Issue2719.java @@ -1,18 +1,19 @@ import static java.util.Arrays.asList; -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + public class Issue2719 { - public static void main(String[] args) { - List iList = asList(0); - List<@Nullable Integer> jList = asList((Integer) null); - // :: error: (type.arguments.not.inferred) - List> both = passThrough(asList(iList, jList)); - System.out.println(both.get(1).get(0).intValue()); - } + public static void main(String[] args) { + List iList = asList(0); + List<@Nullable Integer> jList = asList((Integer) null); + // :: error: (type.arguments.not.inferred) + List> both = passThrough(asList(iList, jList)); + System.out.println(both.get(1).get(0).intValue()); + } - static T passThrough(T object) { - return object; - } + static T passThrough(T object) { + return object; + } } diff --git a/checker/tests/nullness/java8inference/Issue402.java b/checker/tests/nullness/java8inference/Issue402.java index da63eb9d52b..f6db65dbe1b 100644 --- a/checker/tests/nullness/java8inference/Issue402.java +++ b/checker/tests/nullness/java8inference/Issue402.java @@ -2,30 +2,33 @@ // https://github.com/typetools/checker-framework/issues/979 import java.util.Comparator; + import javax.annotation.CheckForNull; import javax.annotation.Nullable; // @skip-test 979 public final class Issue402 { - static final Comparator COMPARATOR = - Comparator.comparing(Issue402::getStr1, Comparator.nullsFirst(Comparator.naturalOrder())) - .thenComparing(Issue402::getStr2, Comparator.nullsFirst(Comparator.naturalOrder())); + static final Comparator COMPARATOR = + Comparator.comparing( + Issue402::getStr1, Comparator.nullsFirst(Comparator.naturalOrder())) + .thenComparing( + Issue402::getStr2, Comparator.nullsFirst(Comparator.naturalOrder())); - @CheckForNull private final String str1; - @CheckForNull private final String str2; + @CheckForNull private final String str1; + @CheckForNull private final String str2; - Issue402(@Nullable final String str1, @Nullable final String str2) { - this.str1 = str1; - this.str2 = str2; - } + Issue402(@Nullable final String str1, @Nullable final String str2) { + this.str1 = str1; + this.str2 = str2; + } - @CheckForNull - String getStr1() { - return this.str1; - } + @CheckForNull + String getStr1() { + return this.str1; + } - @CheckForNull - String getStr2() { - return this.str2; - } + @CheckForNull + String getStr2() { + return this.str2; + } } diff --git a/checker/tests/nullness/java8inference/Issue4048.java b/checker/tests/nullness/java8inference/Issue4048.java index 846b8a06a8a..a573de57734 100644 --- a/checker/tests/nullness/java8inference/Issue4048.java +++ b/checker/tests/nullness/java8inference/Issue4048.java @@ -2,19 +2,20 @@ // @skip-test until the issue is fixed -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + abstract class Issue4048 { - @Nullable Number m1(List numbers) { - return getOnlyElement1(numbers); - } + @Nullable Number m1(List numbers) { + return getOnlyElement1(numbers); + } - abstract @Nullable T getOnlyElement1(Iterable values); + abstract @Nullable T getOnlyElement1(Iterable values); - @Nullable Number m2(List numbers) { - return getOnlyElement2(numbers); - } + @Nullable Number m2(List numbers) { + return getOnlyElement2(numbers); + } - abstract @Nullable T getOnlyElement2(Iterable values); + abstract @Nullable T getOnlyElement2(Iterable values); } diff --git a/checker/tests/nullness/java8inference/Issue887.java b/checker/tests/nullness/java8inference/Issue887.java index 1d18314607b..2f5e14131b2 100644 --- a/checker/tests/nullness/java8inference/Issue887.java +++ b/checker/tests/nullness/java8inference/Issue887.java @@ -2,21 +2,22 @@ // https://github.com/typetools/checker-framework/issues/887 // Additional test case in framework/tests/all-systems/Issue887.java -import java.util.List; import org.checkerframework.checker.nullness.qual.*; +import java.util.List; + public abstract class Issue887 { - void test() { - // :: error: (type.arguments.not.inferred) - method(foo(null).get(0)); - methodNullable(fooNullable(null).get(0)); - } + void test() { + // :: error: (type.arguments.not.inferred) + method(foo(null).get(0)); + methodNullable(fooNullable(null).get(0)); + } - void method(Number o) {} + void method(Number o) {} - void methodNullable(@Nullable Number o) {} + void methodNullable(@Nullable Number o) {} - abstract List foo(T t); + abstract List foo(T t); - abstract List fooNullable(T t); + abstract List fooNullable(T t); } diff --git a/checker/tests/nullness/java8inference/Issue953bInference.java b/checker/tests/nullness/java8inference/Issue953bInference.java index 3c68a878214..e9081a9b140 100644 --- a/checker/tests/nullness/java8inference/Issue953bInference.java +++ b/checker/tests/nullness/java8inference/Issue953bInference.java @@ -1,25 +1,26 @@ // Test case that was submitted in Issue 953, but was combined with Issue 979 // https://github.com/typetools/checker-framework/issues/979 +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.*; import java.util.function.Function; -import org.checkerframework.checker.nullness.qual.NonNull; public class Issue953bInference { - private static List> strs = new ArrayList<>(); + private static List> strs = new ArrayList<>(); - public static List<@NonNull R> mapList( - List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { - ArrayList<@NonNull R> r = new ArrayList<>(list.size()); - for (T t : list) r.add(func.apply(t)); - return r; - } + public static List<@NonNull R> mapList( + List<@NonNull T> list, Function<@NonNull T, @NonNull R> func) { + ArrayList<@NonNull R> r = new ArrayList<>(list.size()); + for (T t : list) r.add(func.apply(t)); + return r; + } - public static List test() { - return mapList( - strs, - s -> { - return new String(); - }); - } + public static List test() { + return mapList( + strs, + s -> { + return new String(); + }); + } } diff --git a/checker/tests/nullness/java8inference/Issue980.java b/checker/tests/nullness/java8inference/Issue980.java index 9fa8abe1557..f3919a6e3b3 100644 --- a/checker/tests/nullness/java8inference/Issue980.java +++ b/checker/tests/nullness/java8inference/Issue980.java @@ -1,23 +1,24 @@ // Test case that was submitted in Issue 402, but was combined with Issue 979 // https://github.com/typetools/checker-framework/issues/979 +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue980 { - void m(List strings) { - Stream s = strings.stream(); + void m(List strings) { + Stream s = strings.stream(); - // This works: - List collectedStrings1 = s.collect(Collectors.toList()); - // This works: - List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); + // This works: + List collectedStrings1 = s.collect(Collectors.toList()); + // This works: + List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); - List collectedStrings = s.collect(Collectors.toList()); + List collectedStrings = s.collect(Collectors.toList()); - collectedStrings.forEach(System.out::println); - } + collectedStrings.forEach(System.out::println); + } } diff --git a/checker/tests/nullness/java8inference/NullnessBeamCrash3.java b/checker/tests/nullness/java8inference/NullnessBeamCrash3.java index 81186e44b7e..61d83f14b6d 100644 --- a/checker/tests/nullness/java8inference/NullnessBeamCrash3.java +++ b/checker/tests/nullness/java8inference/NullnessBeamCrash3.java @@ -2,32 +2,33 @@ public class NullnessBeamCrash3 { - @SuppressWarnings({"unchecked", "rawtypes"}) - private CoderOrFailure inferCoderOrFail(PInput input, PTransform transform) { - return new CoderOrFailure<>(((PTransform) transform).getDefaultOutputCoder(input, this), null); - } + @SuppressWarnings({"unchecked", "rawtypes"}) + private CoderOrFailure inferCoderOrFail(PInput input, PTransform transform) { + return new CoderOrFailure<>( + ((PTransform) transform).getDefaultOutputCoder(input, this), null); + } - private static class CoderOrFailure { + private static class CoderOrFailure { - private final @Nullable Coder coder; - private final @Nullable String failure; + private final @Nullable Coder coder; + private final @Nullable String failure; - public CoderOrFailure(@Nullable Coder coder, @Nullable String failure) { - this.coder = coder; - this.failure = failure; + public CoderOrFailure(@Nullable Coder coder, @Nullable String failure) { + this.coder = coder; + this.failure = failure; + } } - } - public interface PInput {} + public interface PInput {} - public abstract static class Coder {} + public abstract static class Coder {} - public abstract static class PTransform { + public abstract static class PTransform { - public Coder getDefaultOutputCoder(InputT input, NullnessBeamCrash3 output) { - throw new RuntimeException(); + public Coder getDefaultOutputCoder(InputT input, NullnessBeamCrash3 output) { + throw new RuntimeException(); + } } - } - public interface POutput {} + public interface POutput {} } diff --git a/checker/tests/nullness/java8inference/OneOf.java b/checker/tests/nullness/java8inference/OneOf.java index 0bc7f253539..bbcc2e5bc48 100644 --- a/checker/tests/nullness/java8inference/OneOf.java +++ b/checker/tests/nullness/java8inference/OneOf.java @@ -5,20 +5,20 @@ import java.util.List; public class OneOf { - static List alist = new ArrayList<>(); + static List alist = new ArrayList<>(); - static V oneof(V v1, V v2) { - return v1; - } + static V oneof(V v1, V v2) { + return v1; + } - @SuppressWarnings("nullness") // don't bother with implementations - static List empty() { - return null; - } + @SuppressWarnings("nullness") // don't bother with implementations + static List empty() { + return null; + } } class OneOfUse { - List foo() { - return OneOf.oneof(OneOf.alist, OneOf.empty()); - } + List foo() { + return OneOf.oneof(OneOf.alist, OneOf.empty()); + } } diff --git a/checker/tests/nullness/java8inference/SimpleLambda.java b/checker/tests/nullness/java8inference/SimpleLambda.java index cbd0c55371a..d5c2fee767a 100644 --- a/checker/tests/nullness/java8inference/SimpleLambda.java +++ b/checker/tests/nullness/java8inference/SimpleLambda.java @@ -1,15 +1,16 @@ -import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Supplier; + public class SimpleLambda { - T perform(Supplier p) { - return p.get(); - } + T perform(Supplier p) { + return p.get(); + } - void test() { - @Nullable String s1 = perform(() -> (String) null); - @Nullable String s2 = this.<@Nullable String>perform(() -> (String) null); - @NonNull String s3 = perform(() -> ""); - } + void test() { + @Nullable String s1 = perform(() -> (String) null); + @Nullable String s2 = this.<@Nullable String>perform(() -> (String) null); + @NonNull String s3 = perform(() -> ""); + } } diff --git a/checker/tests/nullness/jdkannotations/EisopIssue270.java b/checker/tests/nullness/jdkannotations/EisopIssue270.java index 56135b16945..b77e1be6c12 100644 --- a/checker/tests/nullness/jdkannotations/EisopIssue270.java +++ b/checker/tests/nullness/jdkannotations/EisopIssue270.java @@ -1,10 +1,10 @@ import java.util.Set; public class EisopIssue270 { - // In annotated jdk, the package-info of java.util defines KeyForBottom as the - // default qualifier for lower bound. - void foo(Set so, Set seo) { - // No errors if package-info is loaded correctly. - so.retainAll(seo); - } + // In annotated jdk, the package-info of java.util defines KeyForBottom as the + // default qualifier for lower bound. + void foo(Set so, Set seo) { + // No errors if package-info is loaded correctly. + so.retainAll(seo); + } } diff --git a/checker/tests/nullness/jdkannotations/HashtableTest.java b/checker/tests/nullness/jdkannotations/HashtableTest.java index 221d49840e2..952b94787cd 100644 --- a/checker/tests/nullness/jdkannotations/HashtableTest.java +++ b/checker/tests/nullness/jdkannotations/HashtableTest.java @@ -1,22 +1,23 @@ -import java.util.Hashtable; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Hashtable; + public class HashtableTest { - public static void main(String[] args) { + public static void main(String[] args) { - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - Hashtable<@Nullable Integer, String> ht1 = new Hashtable<>(); + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + Hashtable<@Nullable Integer, String> ht1 = new Hashtable<>(); - // Suffers null pointer exception - ht1.put(null, "hello"); + // Suffers null pointer exception + ht1.put(null, "hello"); - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - Hashtable ht2 = new Hashtable<>(); + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + Hashtable ht2 = new Hashtable<>(); - // Suffers null pointer exception - ht2.put(42, null); - } + // Suffers null pointer exception + ht2.put(42, null); + } } diff --git a/checker/tests/nullness/jdkannotations/Issue1142.java b/checker/tests/nullness/jdkannotations/Issue1142.java index 88e99d78daa..badff2d91f6 100644 --- a/checker/tests/nullness/jdkannotations/Issue1142.java +++ b/checker/tests/nullness/jdkannotations/Issue1142.java @@ -1,14 +1,15 @@ // Issue 1142 https://github.com/typetools/checker-framework/issues/1142 -import java.util.concurrent.ConcurrentHashMap; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.concurrent.ConcurrentHashMap; + public class Issue1142 { - void foo() { - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - ConcurrentHashMap chm1 = new ConcurrentHashMap<>(); - chm1.put(1, null); - } + void foo() { + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + ConcurrentHashMap chm1 = new ConcurrentHashMap<>(); + chm1.put(1, null); + } } diff --git a/checker/tests/nullness/jdkannotations/Issue1402EnumName.java b/checker/tests/nullness/jdkannotations/Issue1402EnumName.java index 880e7805a4e..c3f8a28e8a3 100644 --- a/checker/tests/nullness/jdkannotations/Issue1402EnumName.java +++ b/checker/tests/nullness/jdkannotations/Issue1402EnumName.java @@ -4,12 +4,12 @@ // https://github.com/typetools/checker-framework/issues/1402 public enum Issue1402EnumName { - TEST_ONE("abc"), - TEST_TWO("def"); + TEST_ONE("abc"), + TEST_TWO("def"); - private final String newName; + private final String newName; - Issue1402EnumName(String customData) { - this.newName = name(); - } + Issue1402EnumName(String customData) { + this.newName = name(); + } } diff --git a/checker/tests/nullness/jdkannotations/TreeSetTest.java b/checker/tests/nullness/jdkannotations/TreeSetTest.java index 932f9af9d7f..dbe751224e9 100644 --- a/checker/tests/nullness/jdkannotations/TreeSetTest.java +++ b/checker/tests/nullness/jdkannotations/TreeSetTest.java @@ -3,17 +3,18 @@ // @skip-test until we fix the issue -import java.util.TreeSet; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.TreeSet; + public class TreeSetTest { - public static void main(String[] args) { + public static void main(String[] args) { - // :: error: (type.argument.type.incompatible) - TreeSet<@Nullable Integer> ts = new TreeSet<>(); + // :: error: (type.argument.type.incompatible) + TreeSet<@Nullable Integer> ts = new TreeSet<>(); - // This throws a null pointer exception - ts.add(null); - } + // This throws a null pointer exception + ts.add(null); + } } diff --git a/checker/tests/optional-pure-getters/PureGetterTest.java b/checker/tests/optional-pure-getters/PureGetterTest.java index 5252974b96e..2ca45545806 100644 --- a/checker/tests/optional-pure-getters/PureGetterTest.java +++ b/checker/tests/optional-pure-getters/PureGetterTest.java @@ -2,77 +2,77 @@ class PureGetterTest { - @SuppressWarnings("optional.field") - Optional field; + @SuppressWarnings("optional.field") + Optional field; - // This method will be treated as @Pure because of -AassumePureGetters. - Optional getOptional() { - return Optional.of("hello"); - } + // This method will be treated as @Pure because of -AassumePureGetters. + Optional getOptional() { + return Optional.of("hello"); + } - Optional otherOptional() { - return Optional.of("hello"); - } + Optional otherOptional() { + return Optional.of("hello"); + } - void sideEffect() {} + void sideEffect() {} - void foo() { - if (field.isPresent()) { - field.get(); - } - if (field.isPresent()) { - sideEffect(); - // :: error: (method.invocation.invalid) - field.get(); - } - if (field.isPresent()) { - getOptional(); - field.get(); - } - if (field.isPresent()) { - otherOptional(); - // :: error: (method.invocation.invalid) - field.get(); - } + void foo() { + if (field.isPresent()) { + field.get(); + } + if (field.isPresent()) { + sideEffect(); + // :: error: (method.invocation.invalid) + field.get(); + } + if (field.isPresent()) { + getOptional(); + field.get(); + } + if (field.isPresent()) { + otherOptional(); + // :: error: (method.invocation.invalid) + field.get(); + } - if (getOptional().isPresent()) { - getOptional().get(); - } - if (getOptional().isPresent()) { - sideEffect(); - // :: error: (method.invocation.invalid) - getOptional().get(); - } - if (getOptional().isPresent()) { - getOptional(); - getOptional().get(); - } - if (getOptional().isPresent()) { - otherOptional(); - // :: error: (method.invocation.invalid) - getOptional().get(); - } + if (getOptional().isPresent()) { + getOptional().get(); + } + if (getOptional().isPresent()) { + sideEffect(); + // :: error: (method.invocation.invalid) + getOptional().get(); + } + if (getOptional().isPresent()) { + getOptional(); + getOptional().get(); + } + if (getOptional().isPresent()) { + otherOptional(); + // :: error: (method.invocation.invalid) + getOptional().get(); + } - if (otherOptional().isPresent()) { - // BUG: https://github.com/typetools/checker-framework/issues/6291 error: - // (method.invocation.invalid) - otherOptional().get(); - } - if (otherOptional().isPresent()) { - sideEffect(); - // :: error: (method.invocation.invalid) - otherOptional().get(); - } - if (otherOptional().isPresent()) { - getOptional(); - // BUG: https://github.com/typetools/checker-framework/issues/6291 error: - // (method.invocation.invalid) - otherOptional().get(); - } - if (otherOptional().isPresent()) { - otherOptional(); - // :: error: (method.invocation.invalid) - otherOptional().get(); + if (otherOptional().isPresent()) { + // BUG: https://github.com/typetools/checker-framework/issues/6291 error: + // (method.invocation.invalid) + otherOptional().get(); + } + if (otherOptional().isPresent()) { + sideEffect(); + // :: error: (method.invocation.invalid) + otherOptional().get(); + } + if (otherOptional().isPresent()) { + getOptional(); + // BUG: https://github.com/typetools/checker-framework/issues/6291 error: + // (method.invocation.invalid) + otherOptional().get(); + } + if (otherOptional().isPresent()) { + otherOptional(); + // :: error: (method.invocation.invalid) + otherOptional().get(); + } } - } } diff --git a/checker/tests/optional/Base.java b/checker/tests/optional/Base.java index 4e16e1c2b9d..30e1c8ce488 100644 --- a/checker/tests/optional/Base.java +++ b/checker/tests/optional/Base.java @@ -1,17 +1,21 @@ public class Base { - static class OneClass, B extends OneClass> { - TwoClass get() { - return null; + static class OneClass, B extends OneClass> { + TwoClass get() { + return null; + } } - } - static class TwoClass< - C extends ThreeClass, D extends SubOneClass, E extends TwoClass> {} + static class TwoClass< + C extends ThreeClass, + D extends SubOneClass, + E extends TwoClass> {} - static class ThreeClass, G extends OneClass> {} + static class ThreeClass, G extends OneClass> {} - class SubOneClass< - H extends ThreeClass, I extends SubOneClass, J extends TwoClass> - extends OneClass {} + class SubOneClass< + H extends ThreeClass, + I extends SubOneClass, + J extends TwoClass> + extends OneClass {} } diff --git a/checker/tests/optional/EnsuresPresentIfTest.java b/checker/tests/optional/EnsuresPresentIfTest.java index 55c18ac1cd6..804fa121792 100644 --- a/checker/tests/optional/EnsuresPresentIfTest.java +++ b/checker/tests/optional/EnsuresPresentIfTest.java @@ -1,85 +1,86 @@ -import java.util.Optional; import org.checkerframework.checker.optional.qual.EnsuresPresentIf; import org.checkerframework.checker.optional.qual.Present; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.framework.qual.EnsuresQualifierIf; -public class EnsuresPresentIfTest { - - // :: warning: (optional.field) - private Optional optId = Optional.of("abc"); - - @Pure - public Optional getOptId() { - return Optional.of("abc"); - } - - @EnsuresPresentIf(result = true, expression = "getOptId()") - public boolean hasPresentId1() { - return getOptId().isPresent(); - } - - @EnsuresPresentIf(result = true, expression = "this.getOptId()") - public boolean hasPresentId2() { - return getOptId().isPresent(); - } - - @EnsuresQualifierIf(result = true, expression = "getOptId()", qualifier = Present.class) - public boolean hasPresentId3() { - return getOptId().isPresent(); - } - - @EnsuresQualifierIf(result = true, expression = "this.getOptId()", qualifier = Present.class) - public boolean hasPresentId4() { - return getOptId().isPresent(); - } +import java.util.Optional; - @EnsuresPresentIf(result = true, expression = "optId") - public boolean hasPresentId5() { - return optId.isPresent(); - } +public class EnsuresPresentIfTest { - @EnsuresPresentIf(result = true, expression = "this.optId") - public boolean hasPresentId6() { - return optId.isPresent(); - } + // :: warning: (optional.field) + private Optional optId = Optional.of("abc"); - @EnsuresQualifierIf(result = true, expression = "optId", qualifier = Present.class) - public boolean hasPresentId7() { - return optId.isPresent(); - } + @Pure + public Optional getOptId() { + return Optional.of("abc"); + } - @EnsuresQualifierIf(result = true, expression = "this.optId", qualifier = Present.class) - public boolean hasPresentId8() { - return optId.isPresent(); - } + @EnsuresPresentIf(result = true, expression = "getOptId()") + public boolean hasPresentId1() { + return getOptId().isPresent(); + } - void client() { - if (hasPresentId1()) { - getOptId().get(); + @EnsuresPresentIf(result = true, expression = "this.getOptId()") + public boolean hasPresentId2() { + return getOptId().isPresent(); } - if (hasPresentId2()) { - getOptId().get(); + + @EnsuresQualifierIf(result = true, expression = "getOptId()", qualifier = Present.class) + public boolean hasPresentId3() { + return getOptId().isPresent(); } - if (hasPresentId3()) { - getOptId().get(); + + @EnsuresQualifierIf(result = true, expression = "this.getOptId()", qualifier = Present.class) + public boolean hasPresentId4() { + return getOptId().isPresent(); } - if (hasPresentId4()) { - getOptId().get(); + + @EnsuresPresentIf(result = true, expression = "optId") + public boolean hasPresentId5() { + return optId.isPresent(); } - if (hasPresentId5()) { - optId.get(); + + @EnsuresPresentIf(result = true, expression = "this.optId") + public boolean hasPresentId6() { + return optId.isPresent(); } - if (hasPresentId6()) { - optId.get(); + + @EnsuresQualifierIf(result = true, expression = "optId", qualifier = Present.class) + public boolean hasPresentId7() { + return optId.isPresent(); } - if (hasPresentId7()) { - optId.get(); + + @EnsuresQualifierIf(result = true, expression = "this.optId", qualifier = Present.class) + public boolean hasPresentId8() { + return optId.isPresent(); } - if (hasPresentId8()) { - optId.get(); + + void client() { + if (hasPresentId1()) { + getOptId().get(); + } + if (hasPresentId2()) { + getOptId().get(); + } + if (hasPresentId3()) { + getOptId().get(); + } + if (hasPresentId4()) { + getOptId().get(); + } + if (hasPresentId5()) { + optId.get(); + } + if (hasPresentId6()) { + optId.get(); + } + if (hasPresentId7()) { + optId.get(); + } + if (hasPresentId8()) { + optId.get(); + } + // :: error: (method.invocation.invalid) + optId.get(); } - // :: error: (method.invocation.invalid) - optId.get(); - } } diff --git a/checker/tests/optional/FilterIspresentMapGetTest.java b/checker/tests/optional/FilterIspresentMapGetTest.java index cb993c151f2..7dfaab9b185 100644 --- a/checker/tests/optional/FilterIspresentMapGetTest.java +++ b/checker/tests/optional/FilterIspresentMapGetTest.java @@ -3,7 +3,7 @@ class FilterIspresentMapGetTest { - void m(Stream> ss) { - ss.filter(Optional::isPresent).map(Optional::get); - } + void m(Stream> ss) { + ss.filter(Optional::isPresent).map(Optional::get); + } } diff --git a/checker/tests/optional/FlowSensitivity.java b/checker/tests/optional/FlowSensitivity.java index ba23547bf6b..d1f45d4259a 100644 --- a/checker/tests/optional/FlowSensitivity.java +++ b/checker/tests/optional/FlowSensitivity.java @@ -4,23 +4,23 @@ @SuppressWarnings("optional.parameter") public class FlowSensitivity { - String noCheck(Optional opt) { - // :: error: (method.invocation.invalid) - return opt.get(); - } + String noCheck(Optional opt) { + // :: error: (method.invocation.invalid) + return opt.get(); + } - String hasCheck1(Optional opt) { - if (opt.isPresent()) { - return opt.get(); - } else { - return "default"; + String hasCheck1(Optional opt) { + if (opt.isPresent()) { + return opt.get(); + } else { + return "default"; + } } - } - String hasCheck2(Optional opt) { - if (!opt.isPresent()) { - return "default"; + String hasCheck2(Optional opt) { + if (!opt.isPresent()) { + return "default"; + } + return opt.get(); } - return opt.get(); - } } diff --git a/checker/tests/optional/IfPresentRefinement.java b/checker/tests/optional/IfPresentRefinement.java index 25c72b00f6f..d0a62644219 100644 --- a/checker/tests/optional/IfPresentRefinement.java +++ b/checker/tests/optional/IfPresentRefinement.java @@ -5,16 +5,16 @@ @SuppressWarnings("optional.parameter") public class IfPresentRefinement { - void m1(Optional o) { - o.ifPresent(s -> o.get()); - } + void m1(Optional o) { + o.ifPresent(s -> o.get()); + } - void m2(Optional o) { - o.ifPresentOrElse(s -> o.get(), () -> {}); - } + void m2(Optional o) { + o.ifPresentOrElse(s -> o.get(), () -> {}); + } - void m3(Optional o) { - // :: error: (method.invocation.invalid) - o.ifPresentOrElse(s -> o.get(), () -> o.get()); - } + void m3(Optional o) { + // :: error: (method.invocation.invalid) + o.ifPresentOrElse(s -> o.get(), () -> o.get()); + } } diff --git a/checker/tests/optional/JdkCheck.java b/checker/tests/optional/JdkCheck.java index 0403ede8dfa..14dcf8e3b72 100644 --- a/checker/tests/optional/JdkCheck.java +++ b/checker/tests/optional/JdkCheck.java @@ -1,77 +1,78 @@ -import java.util.Optional; -import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.optional.qual.Present; +import java.util.Optional; +import java.util.function.Supplier; + /** Test JDK annotations. */ @SuppressWarnings("optional.parameter") public class JdkCheck { - boolean isPresentTest1(@Present Optional pos) { - return pos.isPresent(); - } - - boolean isPresentTest2(Optional mos) { - return mos.isPresent(); - } - - String orElseThrowTest1( - @Present Optional pos, Supplier exceptionSupplier) { - return pos.orElseThrow(exceptionSupplier); - } - - String orElseThrowTest2(Optional mos, Supplier exceptionSupplier) { - return mos.orElseThrow(exceptionSupplier); - } - - String orElseThrowTestFlow(Optional mos, Supplier exceptionSupplier) { - mos.orElseThrow(exceptionSupplier); - return mos.get(); - } - - String getTest1(@Present Optional pos) { - return pos.get(); - } - - String getTest2(Optional mos) { - // :: error: (method.invocation.invalid) - return mos.get(); - } - - @Present Optional ofTestPNn(String s) { - return Optional.of(s); - } - - Optional ofTestMNn(String s) { - return Optional.of(s); - } - - @Present Optional ofTestPNble(@Nullable String s) { - // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) - return Optional.of(s); - } - - Optional ofTestMNble(@Nullable String s) { - // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) - return Optional.of(s); - } - - @Present Optional ofNullableTestPNble(@Nullable String s) { - // :: error: (return.type.incompatible) - return Optional.ofNullable(s); - } - - /* TODO: ofNullable with non-null arg gives @Present (+ a warning?) - @Present Optional ofNullableTestPNn(String s) { - return Optional.ofNullable(s); - } - */ - - Optional ofNullableTestMNble(@Nullable String s) { - return Optional.ofNullable(s); - } - - Optional ofNullableTestMNn(String s) { - return Optional.ofNullable(s); - } + boolean isPresentTest1(@Present Optional pos) { + return pos.isPresent(); + } + + boolean isPresentTest2(Optional mos) { + return mos.isPresent(); + } + + String orElseThrowTest1( + @Present Optional pos, Supplier exceptionSupplier) { + return pos.orElseThrow(exceptionSupplier); + } + + String orElseThrowTest2(Optional mos, Supplier exceptionSupplier) { + return mos.orElseThrow(exceptionSupplier); + } + + String orElseThrowTestFlow(Optional mos, Supplier exceptionSupplier) { + mos.orElseThrow(exceptionSupplier); + return mos.get(); + } + + String getTest1(@Present Optional pos) { + return pos.get(); + } + + String getTest2(Optional mos) { + // :: error: (method.invocation.invalid) + return mos.get(); + } + + @Present Optional ofTestPNn(String s) { + return Optional.of(s); + } + + Optional ofTestMNn(String s) { + return Optional.of(s); + } + + @Present Optional ofTestPNble(@Nullable String s) { + // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) + return Optional.of(s); + } + + Optional ofTestMNble(@Nullable String s) { + // TODO :: error: (of.nullable.argument) :: error: (return.type.incompatible) + return Optional.of(s); + } + + @Present Optional ofNullableTestPNble(@Nullable String s) { + // :: error: (return.type.incompatible) + return Optional.ofNullable(s); + } + + /* TODO: ofNullable with non-null arg gives @Present (+ a warning?) + @Present Optional ofNullableTestPNn(String s) { + return Optional.ofNullable(s); + } + */ + + Optional ofNullableTestMNble(@Nullable String s) { + return Optional.ofNullable(s); + } + + Optional ofNullableTestMNn(String s) { + return Optional.ofNullable(s); + } } diff --git a/checker/tests/optional/JdkCheck11.java b/checker/tests/optional/JdkCheck11.java index 84790aaf56a..94fd70dd9db 100644 --- a/checker/tests/optional/JdkCheck11.java +++ b/checker/tests/optional/JdkCheck11.java @@ -1,31 +1,32 @@ // @below-java11-jdk-skip-test -import java.util.Optional; import org.checkerframework.checker.optional.qual.Present; +import java.util.Optional; + /** Test JDK annotations, for methods added after JDK 8. */ @SuppressWarnings("optional.parameter") public class JdkCheck11 { - String isEmptyTest1(Optional pos, String fallback) { - if (pos.isEmpty()) { - return fallback; + String isEmptyTest1(Optional pos, String fallback) { + if (pos.isEmpty()) { + return fallback; + } + return pos.get(); } - return pos.get(); - } - String orElseThrowTest1(@Present Optional pos) { - return pos.orElseThrow(); - } + String orElseThrowTest1(@Present Optional pos) { + return pos.orElseThrow(); + } - String orElseThrowTest2(Optional mos) { - // :: error: (method.invocation.invalid) - return mos.orElseThrow(); - } + String orElseThrowTest2(Optional mos) { + // :: error: (method.invocation.invalid) + return mos.orElseThrow(); + } - String orElseThrowTestFlow(Optional mos) { - // :: error: (method.invocation.invalid) - mos.orElseThrow(); - return mos.get(); - } + String orElseThrowTestFlow(Optional mos) { + // :: error: (method.invocation.invalid) + mos.orElseThrow(); + return mos.get(); + } } diff --git a/checker/tests/optional/MapNoNewNull.java b/checker/tests/optional/MapNoNewNull.java index f9148ee235b..8552b38b6d4 100644 --- a/checker/tests/optional/MapNoNewNull.java +++ b/checker/tests/optional/MapNoNewNull.java @@ -3,14 +3,15 @@ class MapNoNewNull { - @SuppressWarnings("optional.parameter") - void m(Optional digitsAnnotation) { - if (digitsAnnotation.isPresent()) { - BigInteger maxValue = digitsAnnotation.map(Digits::integer).map(BigInteger::valueOf).get(); + @SuppressWarnings("optional.parameter") + void m(Optional digitsAnnotation) { + if (digitsAnnotation.isPresent()) { + BigInteger maxValue = + digitsAnnotation.map(Digits::integer).map(BigInteger::valueOf).get(); + } } - } } @interface Digits { - public int integer(); + public int integer(); } diff --git a/checker/tests/optional/Marks1Partial.java b/checker/tests/optional/Marks1Partial.java index 56e58f1a9a6..8b48b8d0282 100644 --- a/checker/tests/optional/Marks1Partial.java +++ b/checker/tests/optional/Marks1Partial.java @@ -9,70 +9,70 @@ */ public class Marks1Partial { - @SuppressWarnings("optional.field") - Optional optField = Optional.ofNullable("f1"); + @SuppressWarnings("optional.field") + Optional optField = Optional.ofNullable("f1"); - @SuppressWarnings("optional.parameter") - void simpleEqualsCheck(Optional o1) { - // :: warning: (optional.null.comparison) - if (o1 != null) { - System.out.println("Don't compare optionals (lhs) to null literals."); + @SuppressWarnings("optional.parameter") + void simpleEqualsCheck(Optional o1) { + // :: warning: (optional.null.comparison) + if (o1 != null) { + System.out.println("Don't compare optionals (lhs) to null literals."); + } + // :: warning: (optional.null.comparison) + if (null != o1) { + System.out.println("Don't compare optionals (rhs) to null literals."); + } + // :: warning: (optional.null.comparison) + if (o1 == null) { + System.out.println("Don't compare optionals (lhs) to null literals."); + } + // :: warning: (optional.null.comparison) + if (null == o1) { + System.out.println("Don't compare optionals (rhs) to null literals."); + } } - // :: warning: (optional.null.comparison) - if (null != o1) { - System.out.println("Don't compare optionals (rhs) to null literals."); - } - // :: warning: (optional.null.comparison) - if (o1 == null) { - System.out.println("Don't compare optionals (lhs) to null literals."); - } - // :: warning: (optional.null.comparison) - if (null == o1) { - System.out.println("Don't compare optionals (rhs) to null literals."); - } - } - @SuppressWarnings("optional.parameter") - void moreComplexEqualsChecks(Optional o1) { - // :: warning: (optional.null.comparison) - if (o1 != null || 1 + 2 == 4) { - System.out.println("Don't compare optionals (lhs) to null literals."); + @SuppressWarnings("optional.parameter") + void moreComplexEqualsChecks(Optional o1) { + // :: warning: (optional.null.comparison) + if (o1 != null || 1 + 2 == 4) { + System.out.println("Don't compare optionals (lhs) to null literals."); + } } - } - @SuppressWarnings("optional.parameter") - void checkAgainstOptionalField() { - // :: warning: (optional.null.comparison) - if (this.getOptField() != null || 1 + 2 == 4) { - System.out.println("Don't compare optionals (lhs) to null literals."); + @SuppressWarnings("optional.parameter") + void checkAgainstOptionalField() { + // :: warning: (optional.null.comparison) + if (this.getOptField() != null || 1 + 2 == 4) { + System.out.println("Don't compare optionals (lhs) to null literals."); + } } - } - public Optional getOptField() { - return optField; - } + public Optional getOptField() { + return optField; + } - public void assignOptField() { - // :: warning: (optional.null.assignment) - optField = null; - } + public void assignOptField() { + // :: warning: (optional.null.assignment) + optField = null; + } - public void assignOptionalDeclaration() { - // :: warning: (optional.null.assignment) - Optional os1 = null; - Optional os2; - if (Math.random() > 0.5) { - os2 = Optional.of("hello"); - } else { - // :: warning: (optional.null.assignment) - os2 = null; + public void assignOptionalDeclaration() { + // :: warning: (optional.null.assignment) + Optional os1 = null; + Optional os2; + if (Math.random() > 0.5) { + os2 = Optional.of("hello"); + } else { + // :: warning: (optional.null.assignment) + os2 = null; + } + // :: warning: (optional.null.assignment) + Optional os3 = Math.random() > 0.5 ? Optional.of("hello") : null; } - // :: warning: (optional.null.assignment) - Optional os3 = Math.random() > 0.5 ? Optional.of("hello") : null; - } - public Optional returnNullOptional() { - // :: warning: (optional.null.assignment) - return (null); - } + public Optional returnNullOptional() { + // :: warning: (optional.null.assignment) + return (null); + } } diff --git a/checker/tests/optional/Marks2.java b/checker/tests/optional/Marks2.java index b85b6b9475b..ef6108d52d1 100644 --- a/checker/tests/optional/Marks2.java +++ b/checker/tests/optional/Marks2.java @@ -7,19 +7,19 @@ */ public class Marks2 { - class Customer { - int getID() { - return 42; - } + class Customer { + int getID() { + return 42; + } - String getName() { - return "Fozzy Bear"; + String getName() { + return "Fozzy Bear"; + } } - } - String customerNameByID(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - // :: error: (method.invocation.invalid) - return opt.get().getName(); - } + String customerNameByID(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + // :: error: (method.invocation.invalid) + return opt.get().getName(); + } } diff --git a/checker/tests/optional/Marks3a.java b/checker/tests/optional/Marks3a.java index 3850624829c..293b3a8e5df 100644 --- a/checker/tests/optional/Marks3a.java +++ b/checker/tests/optional/Marks3a.java @@ -6,33 +6,33 @@ */ public class Marks3a { - class Customer { - int getID() { - return 42; + class Customer { + int getID() { + return 42; + } + + String getName() { + return "Fozzy Bear"; + } } - String getName() { - return "Fozzy Bear"; - } - } - - String customerNameByID_acceptable(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + String customerNameByID_acceptable(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - // :: warning: (prefer.map.and.orelse) - return opt.isPresent() ? opt.get().getName() : "UNKNOWN"; - } + // :: warning: (prefer.map.and.orelse) + return opt.isPresent() ? opt.get().getName() : "UNKNOWN"; + } - String customerNameByID_acceptable2(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + String customerNameByID_acceptable2(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - // :: warning: (prefer.map.and.orelse) - return !opt.isPresent() ? "UNKNOWN" : opt.get().getName(); - } + // :: warning: (prefer.map.and.orelse) + return !opt.isPresent() ? "UNKNOWN" : opt.get().getName(); + } - String customerNameByID_better(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + String customerNameByID_better(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - return opt.map(Customer::getName).orElse("UNKNOWN"); - } + return opt.map(Customer::getName).orElse("UNKNOWN"); + } } diff --git a/checker/tests/optional/Marks3aJdk11.java b/checker/tests/optional/Marks3aJdk11.java index ab38e5d01e7..1114afdd945 100644 --- a/checker/tests/optional/Marks3aJdk11.java +++ b/checker/tests/optional/Marks3aJdk11.java @@ -8,20 +8,20 @@ */ public class Marks3aJdk11 { - class Customer { - int getID() { - return 42; - } + class Customer { + int getID() { + return 42; + } - String getName() { - return "Fozzy Bear"; + String getName() { + return "Fozzy Bear"; + } } - } - String customerNameByID_acceptable3(List custList, int custID) { - Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); + String customerNameByID_acceptable3(List custList, int custID) { + Optional opt = custList.stream().filter(c -> c.getID() == custID).findFirst(); - // :: warning: (prefer.map.and.orelse) - return opt.isEmpty() ? "UNKNOWN" : opt.get().getName(); - } + // :: warning: (prefer.map.and.orelse) + return opt.isEmpty() ? "UNKNOWN" : opt.get().getName(); + } } diff --git a/checker/tests/optional/Marks3b.java b/checker/tests/optional/Marks3b.java index 877cf3e90f6..742a8cddd7c 100644 --- a/checker/tests/optional/Marks3b.java +++ b/checker/tests/optional/Marks3b.java @@ -6,27 +6,27 @@ @SuppressWarnings("optional.parameter") public class Marks3b { - class Task {} + class Task {} - class Executor { - void runTask(Task t) {} - } + class Executor { + void runTask(Task t) {} + } - Executor executor = new Executor(); + Executor executor = new Executor(); - void bad(Optional oTask) { - // :: warning: (prefer.ifpresent) - if (oTask.isPresent()) { - executor.runTask(oTask.get()); + void bad(Optional oTask) { + // :: warning: (prefer.ifpresent) + if (oTask.isPresent()) { + executor.runTask(oTask.get()); + } } - } - void better(Optional oTask) { - // no warning; better code is possible but has nothing to do with Optional - oTask.ifPresent(task -> executor.runTask(task)); - } + void better(Optional oTask) { + // no warning; better code is possible but has nothing to do with Optional + oTask.ifPresent(task -> executor.runTask(task)); + } - void best(Optional oTask) { - oTask.ifPresent(executor::runTask); - } + void best(Optional oTask) { + oTask.ifPresent(executor::runTask); + } } diff --git a/checker/tests/optional/Marks3bJdk11.java b/checker/tests/optional/Marks3bJdk11.java index feae955395f..0e9209390ee 100644 --- a/checker/tests/optional/Marks3bJdk11.java +++ b/checker/tests/optional/Marks3bJdk11.java @@ -8,26 +8,26 @@ @SuppressWarnings("optional.parameter") public class Marks3bJdk11 { - class Task {} + class Task {} - class Executor { - void runTask(Task t) {} - } + class Executor { + void runTask(Task t) {} + } - Executor executor = new Executor(); + Executor executor = new Executor(); - void bad2(Optional oTask) { - // :: warning: (prefer.ifpresent) - if (!oTask.isEmpty()) { - executor.runTask(oTask.get()); + void bad2(Optional oTask) { + // :: warning: (prefer.ifpresent) + if (!oTask.isEmpty()) { + executor.runTask(oTask.get()); + } } - } - void bad3(Optional oTask) { - // :: warning: (prefer.ifpresent) - if (oTask.isEmpty()) { - } else { - executor.runTask(oTask.get()); + void bad3(Optional oTask) { + // :: warning: (prefer.ifpresent) + if (oTask.isEmpty()) { + } else { + executor.runTask(oTask.get()); + } } - } } diff --git a/checker/tests/optional/Marks4.java b/checker/tests/optional/Marks4.java index 87f6eabe094..9f93aa02ab9 100644 --- a/checker/tests/optional/Marks4.java +++ b/checker/tests/optional/Marks4.java @@ -7,54 +7,54 @@ */ public class Marks4 { - String getDefault() { - return "Fozzy Bear"; - } - - String process_bad(String s) { - // :: warning: (introduce.eliminate) - return Optional.ofNullable(s).orElseGet(this::getDefault); - } - - String process_bad2(String s) { - // :: warning: (introduce.eliminate) - return Optional.empty().orElseGet(this::getDefault); - } - - String process_bad3(String s) { - // :: warning: (introduce.eliminate) - return Optional.of(s).orElseGet(this::getDefault); - } - - String process_good(String s) { - return (s != null) ? s : getDefault(); - } - - String m1(String s) { - // :: warning: (introduce.eliminate) - return Optional.ofNullable(s).orElseGet(this::getDefault) + "hello"; - } - - boolean m2(String s) { - // :: warning: (introduce.eliminate) - return Objects.equals("hello", Optional.ofNullable(s).orElseGet(this::getDefault)); - } - - boolean m3(String s) { - // :: warning: (introduce.eliminate) - return "hello" == Optional.ofNullable(s).orElseGet(this::getDefault); - } - - String m4(String s) { - // :: warning: (introduce.eliminate) - return Optional.ofNullable(s).map(Object::toString).orElseGet(this::getDefault); - } - - String m5(String s) { - return Optional.ofNullable(s) - .map(Object::toString) - .map(Object::toString) + String getDefault() { + return "Fozzy Bear"; + } + + String process_bad(String s) { + // :: warning: (introduce.eliminate) + return Optional.ofNullable(s).orElseGet(this::getDefault); + } + + String process_bad2(String s) { + // :: warning: (introduce.eliminate) + return Optional.empty().orElseGet(this::getDefault); + } + + String process_bad3(String s) { + // :: warning: (introduce.eliminate) + return Optional.of(s).orElseGet(this::getDefault); + } + + String process_good(String s) { + return (s != null) ? s : getDefault(); + } + + String m1(String s) { + // :: warning: (introduce.eliminate) + return Optional.ofNullable(s).orElseGet(this::getDefault) + "hello"; + } + + boolean m2(String s) { + // :: warning: (introduce.eliminate) + return Objects.equals("hello", Optional.ofNullable(s).orElseGet(this::getDefault)); + } + + boolean m3(String s) { + // :: warning: (introduce.eliminate) + return "hello" == Optional.ofNullable(s).orElseGet(this::getDefault); + } + + String m4(String s) { // :: warning: (introduce.eliminate) - .orElseGet(this::getDefault); - } + return Optional.ofNullable(s).map(Object::toString).orElseGet(this::getDefault); + } + + String m5(String s) { + return Optional.ofNullable(s) + .map(Object::toString) + .map(Object::toString) + // :: warning: (introduce.eliminate) + .orElseGet(this::getDefault); + } } diff --git a/checker/tests/optional/Marks5.java b/checker/tests/optional/Marks5.java index 2bd2e2fae37..61cc33a447e 100644 --- a/checker/tests/optional/Marks5.java +++ b/checker/tests/optional/Marks5.java @@ -1,7 +1,8 @@ +import org.checkerframework.checker.optional.qual.Present; + import java.math.BigDecimal; import java.util.Optional; import java.util.stream.Stream; -import org.checkerframework.checker.optional.qual.Present; /** * Test case for rule #5: "If an Optional chain has a nested Optional chain, or has an intermediate @@ -10,45 +11,45 @@ @SuppressWarnings("optional.parameter") public class Marks5 { - // Each method adds first and second, treating empty as zero, returning an Optional of the sum, - // unless BOTH are empty, in which case return an empty Optional. + // Each method adds first and second, treating empty as zero, returning an Optional of the sum, + // unless BOTH are empty, in which case return an empty Optional. - Optional clever(Optional first, Optional second) { - @SuppressWarnings({"methodref.inference.unimplemented", "methodref.receiver.invalid"}) - Optional result = - Stream.of(first, second) - .filter(Optional::isPresent) - .map(Optional::get) - .reduce(BigDecimal::add); - return result; - } + Optional clever(Optional first, Optional second) { + @SuppressWarnings({"methodref.inference.unimplemented", "methodref.receiver.invalid"}) + Optional result = + Stream.of(first, second) + .filter(Optional::isPresent) + .map(Optional::get) + .reduce(BigDecimal::add); + return result; + } - Optional clever2(Optional first, Optional second) { - Stream> s = Stream.of(first, second); - @SuppressWarnings("assignment.type.incompatible") - Stream<@Present Optional> filtered = - s.>filter(Optional::isPresent); - Stream present = filtered.map(Optional::get); - Optional result = present.reduce(BigDecimal::add); - return result; - } + Optional clever2(Optional first, Optional second) { + Stream> s = Stream.of(first, second); + @SuppressWarnings("assignment.type.incompatible") + Stream<@Present Optional> filtered = + s.>filter(Optional::isPresent); + Stream present = filtered.map(Optional::get); + Optional result = present.reduce(BigDecimal::add); + return result; + } - // The use of `map(Optional::of)` creates Optional, so a warning should be issued - // there. - Optional moreClever(Optional first, Optional second) { - Optional result = - // :: error: (argument) - first.map(b -> second.map(b::add).orElse(b)).map(Optional::of).orElse(second); - return result; - } + // The use of `map(Optional::of)` creates Optional, so a warning should be issued + // there. + Optional moreClever(Optional first, Optional second) { + Optional result = + // :: error: (argument) + first.map(b -> second.map(b::add).orElse(b)).map(Optional::of).orElse(second); + return result; + } - Optional clear(Optional first, Optional second) { - Optional result; - if (!first.isPresent() && !second.isPresent()) { - result = Optional.empty(); - } else { - result = Optional.of(first.orElse(BigDecimal.ZERO).add(second.orElse(BigDecimal.ZERO))); + Optional clear(Optional first, Optional second) { + Optional result; + if (!first.isPresent() && !second.isPresent()) { + result = Optional.empty(); + } else { + result = Optional.of(first.orElse(BigDecimal.ZERO).add(second.orElse(BigDecimal.ZERO))); + } + return result; } - return result; - } } diff --git a/checker/tests/optional/Marks6.java b/checker/tests/optional/Marks6.java index 4d780a5b413..d82d4eb3968 100644 --- a/checker/tests/optional/Marks6.java +++ b/checker/tests/optional/Marks6.java @@ -7,23 +7,23 @@ /** Test cases for Rule #6: "Avoid using Optional in fields, method parameters, and collections." */ public class Marks6 { - // :: warning: (optional.field) - Optional optionalField = Optional.ofNullable(null); + // :: warning: (optional.field) + Optional optionalField = Optional.ofNullable(null); - // :: warning: (optional.parameter) - void optionalParameter(Optional arg) {} + // :: warning: (optional.parameter) + void optionalParameter(Optional arg) {} - Optional okUses() { - Optional os = Optional.of("hello world"); - return os; - } + Optional okUses() { + Optional os = Optional.of("hello world"); + return os; + } - void illegalInstantiations() { - // :: warning: (optional.as.element.type) - List> los = new ArrayList<>(); - // :: warning: (optional.as.element.type) - List> los2 = new ArrayList>(); - // :: warning: (optional.as.element.type) - Set> sos = new HashSet<>(); - } + void illegalInstantiations() { + // :: warning: (optional.as.element.type) + List> los = new ArrayList<>(); + // :: warning: (optional.as.element.type) + List> los2 = new ArrayList>(); + // :: warning: (optional.as.element.type) + Set> sos = new HashSet<>(); + } } diff --git a/checker/tests/optional/Marks7.java b/checker/tests/optional/Marks7.java index c3d677caaf9..0161389f6ea 100644 --- a/checker/tests/optional/Marks7.java +++ b/checker/tests/optional/Marks7.java @@ -10,10 +10,10 @@ */ public class Marks7 { - void illegalInstantiations() { - // :: warning: (optional.collection) - Optional> ols = Optional.of(new ArrayList()); - // :: warning: (optional.collection) - Optional> oss = Optional.of(new HashSet()); - } + void illegalInstantiations() { + // :: warning: (optional.collection) + Optional> ols = Optional.of(new ArrayList()); + // :: warning: (optional.collection) + Optional> oss = Optional.of(new HashSet()); + } } diff --git a/checker/tests/optional/NestedOptionalTest.java b/checker/tests/optional/NestedOptionalTest.java index c982f108bcc..4ce96e2994c 100644 --- a/checker/tests/optional/NestedOptionalTest.java +++ b/checker/tests/optional/NestedOptionalTest.java @@ -5,29 +5,29 @@ class NestedOptional { - Object field; + Object field; - @SuppressWarnings("optional.parameter") - // :: warning: (optional.nesting) - Optional bar(Optional> optOptStr) { - if (optOptStr.isPresent()) { - return optOptStr.get(); + @SuppressWarnings("optional.parameter") + // :: warning: (optional.nesting) + Optional bar(Optional> optOptStr) { + if (optOptStr.isPresent()) { + return optOptStr.get(); + } + return Optional.empty(); } - return Optional.empty(); - } - void foo() { - // Explicitly providing a type annotation triggers the error - // :: warning: (optional.nesting) - var x = Optional.of(Optional.of("foo")); // I expect an error here. + void foo() { + // Explicitly providing a type annotation triggers the error + // :: warning: (optional.nesting) + var x = Optional.of(Optional.of("foo")); // I expect an error here. - // :: warning: (optional.nesting) - bar(Optional.of(Optional.of("bar"))); + // :: warning: (optional.nesting) + bar(Optional.of(Optional.of("bar"))); - // :: warning: (optional.nesting) - field = Optional.of(Optional.of("baz")); + // :: warning: (optional.nesting) + field = Optional.of(Optional.of("baz")); - // :: warning: (optional.collection) - field = Optional.of(Collections.singleton("baz")); - } + // :: warning: (optional.collection) + field = Optional.of(Collections.singleton("baz")); + } } diff --git a/checker/tests/optional/OptionalBoxed.java b/checker/tests/optional/OptionalBoxed.java index 766d10ff428..c13430d402f 100644 --- a/checker/tests/optional/OptionalBoxed.java +++ b/checker/tests/optional/OptionalBoxed.java @@ -5,20 +5,20 @@ class OptionalBoxed { - // :: warning: (optional.field) - OptionalDouble aField; + // :: warning: (optional.field) + OptionalDouble aField; - // :: warning: (optional.parameter) - void m(OptionalInt aParam) { - // :: warning: (introduce.eliminate) - int x = OptionalLong.of(1L).hashCode(); - // :: warning: (introduce.eliminate) - long y = OptionalLong.of(1L).orElse(2L); - // :: warning: (introduce.eliminate) - boolean b = Optional.empty().isPresent(); - // :: warning: (introduce.eliminate) - OptionalDouble.empty().ifPresent(d -> {}); - // :: warning: (introduce.eliminate) - boolean b4 = OptionalLong.empty().isPresent(); - } + // :: warning: (optional.parameter) + void m(OptionalInt aParam) { + // :: warning: (introduce.eliminate) + int x = OptionalLong.of(1L).hashCode(); + // :: warning: (introduce.eliminate) + long y = OptionalLong.of(1L).orElse(2L); + // :: warning: (introduce.eliminate) + boolean b = Optional.empty().isPresent(); + // :: warning: (introduce.eliminate) + OptionalDouble.empty().ifPresent(d -> {}); + // :: warning: (introduce.eliminate) + boolean b4 = OptionalLong.empty().isPresent(); + } } diff --git a/checker/tests/optional/OptionalMapMethodReference.java b/checker/tests/optional/OptionalMapMethodReference.java index 02efa7d6ddf..f3f9d47026d 100644 --- a/checker/tests/optional/OptionalMapMethodReference.java +++ b/checker/tests/optional/OptionalMapMethodReference.java @@ -1,34 +1,35 @@ -import java.util.Optional; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.checker.optional.qual.Present; +import java.util.Optional; + public class OptionalMapMethodReference { - Optional getString() { - return Optional.of(""); - } + Optional getString() { + return Optional.of(""); + } - @Present Optional method() { - Optional o = getString(); - @Present Optional oInt; - if (o.isPresent()) { - // :: error: (assignment.type.incompatible) - oInt = o.map(this::convertNull); - oInt = o.map(this::convertPoly); - return o.map(this::convert); + @Present Optional method() { + Optional o = getString(); + @Present Optional oInt; + if (o.isPresent()) { + // :: error: (assignment.type.incompatible) + oInt = o.map(this::convertNull); + oInt = o.map(this::convertPoly); + return o.map(this::convert); + } + return Optional.of(0); } - return Optional.of(0); - } - @Nullable Integer convertNull(String s) { - return null; - } + @Nullable Integer convertNull(String s) { + return null; + } - @PolyNull Integer convertPoly(@PolyNull String s) { - return null; - } + @PolyNull Integer convertPoly(@PolyNull String s) { + return null; + } - Integer convert(String s) { - return 0; - } + Integer convert(String s) { + return 0; + } } diff --git a/checker/tests/optional/OptionalParameterTest.java b/checker/tests/optional/OptionalParameterTest.java index eb68d1d24a0..d1f200e1bc5 100644 --- a/checker/tests/optional/OptionalParameterTest.java +++ b/checker/tests/optional/OptionalParameterTest.java @@ -4,10 +4,10 @@ import java.util.stream.Stream; class OptionalParameterTest { - public void findDatesByIds2(List ids) { - ids.stream() - .map(Optional::ofNullable) - .flatMap(optional -> optional.map(Stream::of).orElseGet(Stream::empty)) - .collect(Collectors.toList()); - } + public void findDatesByIds2(List ids) { + ids.stream() + .map(Optional::ofNullable) + .flatMap(optional -> optional.map(Stream::of).orElseGet(Stream::empty)) + .collect(Collectors.toList()); + } } diff --git a/checker/tests/optional/RequiresPresentTest.java b/checker/tests/optional/RequiresPresentTest.java index 8e7d833b223..966ae641c5b 100644 --- a/checker/tests/optional/RequiresPresentTest.java +++ b/checker/tests/optional/RequiresPresentTest.java @@ -1,72 +1,73 @@ -import java.util.Optional; import org.checkerframework.checker.optional.qual.*; +import java.util.Optional; + public class RequiresPresentTest { - // :: warning: (optional.field) - Optional field1 = Optional.of("abc"); - // :: warning: (optional.field) - Optional field2 = Optional.empty(); - - @RequiresPresent("field1") - void method1() { - field1.get().length(); // OK, field1 is known to be present (non-empty) - this.field1.get().length(); // OK, field1 is known to be present (non-empty) - // :: error: (method.invocation.invalid) - field2.get().length(); // error, might throw NoSuchElementException - } - - @RequiresPresent("field1") - void method2() { - // OK, an indirect call to method1. - method1(); - } - - void method3() { - field1 = Optional.of("abc"); - method1(); // OK, satisfied method precondition. - field1 = Optional.empty(); - // :: error: (contracts.precondition.not.satisfied) - method1(); // error, does not satisfy method precondition. - } - - // :: warning: (optional.field) - protected Optional field; - - @RequiresPresent("field") - public void requiresPresentField() {} - - public void clientFail(RequiresPresentTest arg1) { - // :: error: (contracts.precondition.not.satisfied) - arg1.requiresPresentField(); - } - - public void clientOK(RequiresPresentTest arg2) { - arg2.field = Optional.of("def"); - - // this is legal. - @Present Optional optField = arg2.field; - - // OK, field is known to be present. - arg2.requiresPresentField(); - } - - @RequiresPresent({"field1", "field2"}) - void method4() { - field1.get().length(); // OK, field1 is known to be present (non-empty) - this.field1.get().length(); // OK, field1 is known to be present (non-empty) - - field2.get().length(); // OK, field2 is known to be preent (non-empty) - this.field2.get().length(); // OK, field2 is known to be present (non-empty) - } - - void method5() { - field1 = Optional.of("abc"); - field2 = Optional.of("def"); - method4(); // OK, both preconditions now hold at this point. - - field1 = Optional.empty(); - // :: error: (contracts.precondition.not.satisfied) - method4(); // error, field1 is no longer present. - } + // :: warning: (optional.field) + Optional field1 = Optional.of("abc"); + // :: warning: (optional.field) + Optional field2 = Optional.empty(); + + @RequiresPresent("field1") + void method1() { + field1.get().length(); // OK, field1 is known to be present (non-empty) + this.field1.get().length(); // OK, field1 is known to be present (non-empty) + // :: error: (method.invocation.invalid) + field2.get().length(); // error, might throw NoSuchElementException + } + + @RequiresPresent("field1") + void method2() { + // OK, an indirect call to method1. + method1(); + } + + void method3() { + field1 = Optional.of("abc"); + method1(); // OK, satisfied method precondition. + field1 = Optional.empty(); + // :: error: (contracts.precondition.not.satisfied) + method1(); // error, does not satisfy method precondition. + } + + // :: warning: (optional.field) + protected Optional field; + + @RequiresPresent("field") + public void requiresPresentField() {} + + public void clientFail(RequiresPresentTest arg1) { + // :: error: (contracts.precondition.not.satisfied) + arg1.requiresPresentField(); + } + + public void clientOK(RequiresPresentTest arg2) { + arg2.field = Optional.of("def"); + + // this is legal. + @Present Optional optField = arg2.field; + + // OK, field is known to be present. + arg2.requiresPresentField(); + } + + @RequiresPresent({"field1", "field2"}) + void method4() { + field1.get().length(); // OK, field1 is known to be present (non-empty) + this.field1.get().length(); // OK, field1 is known to be present (non-empty) + + field2.get().length(); // OK, field2 is known to be preent (non-empty) + this.field2.get().length(); // OK, field2 is known to be present (non-empty) + } + + void method5() { + field1 = Optional.of("abc"); + field2 = Optional.of("def"); + method4(); // OK, both preconditions now hold at this point. + + field1 = Optional.empty(); + // :: error: (contracts.precondition.not.satisfied) + method4(); // error, field1 is no longer present. + } } diff --git a/checker/tests/optional/SubtypeCheck.java b/checker/tests/optional/SubtypeCheck.java index f45131a9d8a..1d4d926a2d2 100644 --- a/checker/tests/optional/SubtypeCheck.java +++ b/checker/tests/optional/SubtypeCheck.java @@ -1,27 +1,28 @@ -import java.util.Optional; import org.checkerframework.checker.optional.qual.MaybePresent; import org.checkerframework.checker.optional.qual.OptionalBottom; import org.checkerframework.checker.optional.qual.Present; +import java.util.Optional; + /** Basic test of subtyping. */ public class SubtypeCheck { - @SuppressWarnings("optional.parameter") - void foo( - @MaybePresent Optional mp, - @Present Optional p, - @OptionalBottom Optional ob) { - @MaybePresent Optional mp2 = mp; - @MaybePresent Optional mp3 = p; - @MaybePresent Optional mp4 = ob; - // :: error: assignment.type.incompatible - @Present Optional p2 = mp; - @Present Optional p3 = p; - @Present Optional p4 = ob; - // :: error: assignment.type.incompatible - @OptionalBottom Optional ob2 = mp; - // :: error: assignment.type.incompatible - @OptionalBottom Optional ob3 = p; - @OptionalBottom Optional ob4 = ob; - } + @SuppressWarnings("optional.parameter") + void foo( + @MaybePresent Optional mp, + @Present Optional p, + @OptionalBottom Optional ob) { + @MaybePresent Optional mp2 = mp; + @MaybePresent Optional mp3 = p; + @MaybePresent Optional mp4 = ob; + // :: error: assignment.type.incompatible + @Present Optional p2 = mp; + @Present Optional p3 = p; + @Present Optional p4 = ob; + // :: error: assignment.type.incompatible + @OptionalBottom Optional ob2 = mp; + // :: error: assignment.type.incompatible + @OptionalBottom Optional ob3 = p; + @OptionalBottom Optional ob4 = ob; + } } diff --git a/checker/tests/optional/java17/OptionalSwitch.java b/checker/tests/optional/java17/OptionalSwitch.java index ea93f342bbd..656c9a01fcc 100644 --- a/checker/tests/optional/java17/OptionalSwitch.java +++ b/checker/tests/optional/java17/OptionalSwitch.java @@ -1,11 +1,11 @@ // @below-java17-jdk-skip-test public class OptionalSwitch { - public static boolean flag; + public static boolean flag; - public Object test(int c) { - return switch (c) { - case 3 -> flag ? "" : "obj.getBaseStat();"; - default -> null; - }; - } + public Object test(int c) { + return switch (c) { + case 3 -> flag ? "" : "obj.getBaseStat();"; + default -> null; + }; + } } diff --git a/checker/tests/regex/AllowedTypes.java b/checker/tests/regex/AllowedTypes.java index 50573dc899b..28860cbc062 100644 --- a/checker/tests/regex/AllowedTypes.java +++ b/checker/tests/regex/AllowedTypes.java @@ -1,59 +1,61 @@ +import org.checkerframework.checker.regex.qual.Regex; + import java.util.ArrayList; import java.util.List; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; + import javax.swing.text.Segment; -import org.checkerframework.checker.regex.qual.Regex; public class AllowedTypes { - @Regex CharSequence cs; - @Regex String s11; - @Regex StringBuilder sb; - @Regex Segment s21; - @Regex char c; - @Regex Pattern p; - @Regex Matcher m; - @Regex Character c2; - @Regex Object o; - - abstract static class MyMatchResult implements MatchResult {} - - @Regex MyMatchResult mp; - - // :: error: (anno.on.irrelevant) - @Regex List l; - // :: error: (anno.on.irrelevant) - ArrayList<@Regex Double> al; - // :: error: (anno.on.irrelevant) - @Regex int i; - // :: error: (anno.on.irrelevant) - @Regex boolean b; - // :: error: (anno.on.irrelevant) - @Regex Integer i2; - - void testAllowedTypes() { @Regex CharSequence cs; @Regex String s11; @Regex StringBuilder sb; @Regex Segment s21; @Regex char c; + @Regex Pattern p; + @Regex Matcher m; + @Regex Character c2; @Regex Object o; + abstract static class MyMatchResult implements MatchResult {} + + @Regex MyMatchResult mp; + // :: error: (anno.on.irrelevant) - @Regex List l; // error + @Regex List l; // :: error: (anno.on.irrelevant) - ArrayList<@Regex Double> al; // error + ArrayList<@Regex Double> al; // :: error: (anno.on.irrelevant) - @Regex int i; // error + @Regex int i; // :: error: (anno.on.irrelevant) - @Regex boolean b; // error - - @Regex String regex = "a"; - // :: error: (compound.assignment.type.incompatible) - regex += "("; - - String nonRegex = "a"; - nonRegex += "("; - } + @Regex boolean b; + // :: error: (anno.on.irrelevant) + @Regex Integer i2; + + void testAllowedTypes() { + @Regex CharSequence cs; + @Regex String s11; + @Regex StringBuilder sb; + @Regex Segment s21; + @Regex char c; + @Regex Object o; + + // :: error: (anno.on.irrelevant) + @Regex List l; // error + // :: error: (anno.on.irrelevant) + ArrayList<@Regex Double> al; // error + // :: error: (anno.on.irrelevant) + @Regex int i; // error + // :: error: (anno.on.irrelevant) + @Regex boolean b; // error + + @Regex String regex = "a"; + // :: error: (compound.assignment.type.incompatible) + regex += "("; + + String nonRegex = "a"; + nonRegex += "("; + } } diff --git a/checker/tests/regex/AnnotatedTypeParams3.java b/checker/tests/regex/AnnotatedTypeParams3.java index 031e88c52b9..cde1ef5ec9d 100644 --- a/checker/tests/regex/AnnotatedTypeParams3.java +++ b/checker/tests/regex/AnnotatedTypeParams3.java @@ -1,49 +1,50 @@ +import org.checkerframework.checker.regex.qual.Regex; + import java.lang.annotation.Annotation; import java.lang.reflect.*; -import org.checkerframework.checker.regex.qual.Regex; public class AnnotatedTypeParams3 { - private T safeGetAnnotation(Field f, Class annotationClass) { - T annotation; - try { - annotation = f.getAnnotation((Class) annotationClass); - } catch (Exception e) { - annotation = null; + private T safeGetAnnotation(Field f, Class annotationClass) { + T annotation; + try { + annotation = f.getAnnotation((Class) annotationClass); + } catch (Exception e) { + annotation = null; + } + return annotation; } - return annotation; - } - - private T safeGetAnnotation2(Field f, Class annotationClass) { - T annotation; - try { - annotation = f.getAnnotation(annotationClass); - } catch (Exception e) { - annotation = null; + + private T safeGetAnnotation2(Field f, Class annotationClass) { + T annotation; + try { + annotation = f.getAnnotation(annotationClass); + } catch (Exception e) { + annotation = null; + } + return annotation; + } + + <@Regex T extends @Regex Object> void test(T p) { + Object o = p; + @Regex Object re = o; + } + + void test2(T p) { + Object o = p; + @Regex Object re = o; + } + + // TODO: do we want to infer the type variable annotation on local variable "o"? + void test3(@Regex T p) { + T o = p; + @Regex T re = o; } - return annotation; - } - - <@Regex T extends @Regex Object> void test(T p) { - Object o = p; - @Regex Object re = o; - } - - void test2(T p) { - Object o = p; - @Regex Object re = o; - } - - // TODO: do we want to infer the type variable annotation on local variable "o"? - void test3(@Regex T p) { - T o = p; - @Regex T re = o; - } } class OuterClass { - public InnerClass method() { - return new InnerClass<>(); - } + public InnerClass method() { + return new InnerClass<>(); + } - class InnerClass {} + class InnerClass {} } diff --git a/checker/tests/regex/Annotation.java b/checker/tests/regex/Annotation.java index db17e526cc1..a0b60517db9 100644 --- a/checker/tests/regex/Annotation.java +++ b/checker/tests/regex/Annotation.java @@ -1,21 +1,21 @@ @interface A1 { - String[] value() default {}; + String[] value() default {}; } @interface A2 { - String[] value(); + String[] value(); } public class Annotation { - @A1({"a", "b"}) - void m1() {} + @A1({"a", "b"}) + void m1() {} - @A1(value = {"a", "b"}) - void m2() {} + @A1(value = {"a", "b"}) + void m2() {} - @A2({"a", "b"}) - void m3() {} + @A2({"a", "b"}) + void m3() {} - @A2(value = {"a", "b"}) - void m4() {} + @A2(value = {"a", "b"}) + void m4() {} } diff --git a/checker/tests/regex/Constructors.java b/checker/tests/regex/Constructors.java index 635a8f4527f..2a7ff6c30d1 100644 --- a/checker/tests/regex/Constructors.java +++ b/checker/tests/regex/Constructors.java @@ -1,27 +1,27 @@ import org.checkerframework.checker.regex.qual.Regex; public class Constructors { - public Constructors(Constructors con) {} + public Constructors(Constructors con) {} - public Constructors(@Regex String s, int i) {} + public Constructors(@Regex String s, int i) {} - public class MyConstructors extends Constructors { - public MyConstructors(@Regex String s) { - super(s, 0); + public class MyConstructors extends Constructors { + public MyConstructors(@Regex String s) { + super(s, 0); + } } - } - public void testAnonymousConstructor(String s) { + public void testAnonymousConstructor(String s) { - Constructors m = new Constructors(null); + Constructors m = new Constructors(null); - // :: error: (argument.type.incompatible) - new MyConstructors(s); - // :: error: (argument.type.incompatible) - new MyConstructors(s) {}; - // :: error: (argument.type.incompatible) - m.new MyConstructors(s); - // :: error: (argument.type.incompatible) - m.new MyConstructors(s) {}; - } + // :: error: (argument.type.incompatible) + new MyConstructors(s); + // :: error: (argument.type.incompatible) + new MyConstructors(s) {}; + // :: error: (argument.type.incompatible) + m.new MyConstructors(s); + // :: error: (argument.type.incompatible) + m.new MyConstructors(s) {}; + } } diff --git a/checker/tests/regex/Continue.java b/checker/tests/regex/Continue.java index e497e0fec61..ecca0b44614 100644 --- a/checker/tests/regex/Continue.java +++ b/checker/tests/regex/Continue.java @@ -1,55 +1,56 @@ -import java.util.regex.Pattern; import org.checkerframework.checker.regex.util.RegexUtil; +import java.util.regex.Pattern; + public class Continue { - void test1(String[] a) { - for (String s : a) { - if (!RegexUtil.isRegex(s)) { - continue; - } - Pattern.compile(s); + void test1(String[] a) { + for (String s : a) { + if (!RegexUtil.isRegex(s)) { + continue; + } + Pattern.compile(s); + } } - } - void test2(String[] a, boolean b) { - for (String s : a) { - if (!RegexUtil.isRegex(s)) { - continue; - } else if (b) { - continue; - } - Pattern.compile(s); + void test2(String[] a, boolean b) { + for (String s : a) { + if (!RegexUtil.isRegex(s)) { + continue; + } else if (b) { + continue; + } + Pattern.compile(s); + } } - } - // Reverse the if statements from the previous test. - void test3(String[] a, boolean b) { - for (String s : a) { - if (b) { - continue; - } else if (!RegexUtil.isRegex(s)) { - continue; - } - Pattern.compile(s); + // Reverse the if statements from the previous test. + void test3(String[] a, boolean b) { + for (String s : a) { + if (b) { + continue; + } else if (!RegexUtil.isRegex(s)) { + continue; + } + Pattern.compile(s); + } } - } - void twoThrows(String s) { - if (s == null) { - throw new RuntimeException(); - } else if (!RegexUtil.isRegex(s)) { - throw new RuntimeException(); + void twoThrows(String s) { + if (s == null) { + throw new RuntimeException(); + } else if (!RegexUtil.isRegex(s)) { + throw new RuntimeException(); + } + Pattern.compile(s); } - Pattern.compile(s); - } - void twoReturns(String s) { - if (s == null) { - return; - } else if (!RegexUtil.isRegex(s)) { - return; + void twoReturns(String s) { + if (s == null) { + return; + } else if (!RegexUtil.isRegex(s)) { + return; + } + Pattern.compile(s); } - Pattern.compile(s); - } } diff --git a/checker/tests/regex/ForEach.java b/checker/tests/regex/ForEach.java index ffa8d214407..e7c90c7873c 100644 --- a/checker/tests/regex/ForEach.java +++ b/checker/tests/regex/ForEach.java @@ -1,8 +1,8 @@ public class ForEach { - T iterate(T[] constants) { - for (T constant : constants) { - return constant; + T iterate(T[] constants) { + for (T constant : constants) { + return constant; + } + return null; } - return null; - } } diff --git a/checker/tests/regex/GenericsBoundsRange.java b/checker/tests/regex/GenericsBoundsRange.java index 8b3c7c2f5b8..88df34b1c88 100644 --- a/checker/tests/regex/GenericsBoundsRange.java +++ b/checker/tests/regex/GenericsBoundsRange.java @@ -1,37 +1,38 @@ package regex; +import org.checkerframework.checker.regex.qual.Regex; + import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.checkerframework.checker.regex.qual.Regex; /** Designed to test whether or not a bounds range of generics actually works. */ public class GenericsBoundsRange<@Regex(3) T extends @Regex(1) String> { - public T t; + public T t; - public GenericsBoundsRange(T t) { - Matcher matcher = Pattern.compile(t).matcher("some str"); - if (matcher.matches()) { - matcher.group(0); - matcher.group(1); + public GenericsBoundsRange(T t) { + Matcher matcher = Pattern.compile(t).matcher("some str"); + if (matcher.matches()) { + matcher.group(0); + matcher.group(1); - // T has at least 1 group so the above 2 group calls are good - // however, T MAY or MAY NOT have 2 or 3 groups, so issue an error + // T has at least 1 group so the above 2 group calls are good + // however, T MAY or MAY NOT have 2 or 3 groups, so issue an error - // :: error: (group.count.invalid) - matcher.group(2); + // :: error: (group.count.invalid) + matcher.group(2); - // :: error: (group.count.invalid) - matcher.group(3); + // :: error: (group.count.invalid) + matcher.group(3); - // T definitely does not have 4 groups, issue an error + // T definitely does not have 4 groups, issue an error - // :: error: (group.count.invalid) - matcher.group(4); + // :: error: (group.count.invalid) + matcher.group(4); + } } - } - // Bounds used to not actually be bounds but instead exactly the lower bound - // so line below would fail because the argument could only be Regex(0). So this - // tests BaseTypeValidator.checkTypeArguments range checking. - public void method(GenericsBoundsRange<@Regex(2) String> gbr) {} + // Bounds used to not actually be bounds but instead exactly the lower bound + // so line below would fail because the argument could only be Regex(0). So this + // tests BaseTypeValidator.checkTypeArguments range checking. + public void method(GenericsBoundsRange<@Regex(2) String> gbr) {} } diff --git a/checker/tests/regex/GenericsEnclosing.java b/checker/tests/regex/GenericsEnclosing.java index 90f46503577..4bf3e7d8666 100644 --- a/checker/tests/regex/GenericsEnclosing.java +++ b/checker/tests/regex/GenericsEnclosing.java @@ -7,24 +7,24 @@ *

          Also see all-systems/GenericsEnclosing for the type-system independent test. */ class MyG { - X f; + X f; - void m(X p) {} + void m(X p) {} } class ExtMyG extends MyG<@Regex String> { - class EInner1 { - class EInner2 { - void bar() { - String s = f; - f = "hi"; - // :: error: (assignment.type.incompatible) - f = "\\ no regex("; + class EInner1 { + class EInner2 { + void bar() { + String s = f; + f = "hi"; + // :: error: (assignment.type.incompatible) + f = "\\ no regex("; - m("hi!"); - // :: error: (argument.type.incompatible) - m("\\ no regex("); - } + m("hi!"); + // :: error: (argument.type.incompatible) + m("\\ no regex("); + } + } } - } } diff --git a/checker/tests/regex/GroupCounts.java b/checker/tests/regex/GroupCounts.java index 6b17458c583..c610fdf74ca 100644 --- a/checker/tests/regex/GroupCounts.java +++ b/checker/tests/regex/GroupCounts.java @@ -1,122 +1,123 @@ -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.checker.regex.util.RegexUtil; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class GroupCounts { - void testGroupCount() { - @Regex(0) String s1 = "abc"; - @Regex(1) String s2 = "(abc)"; - @Regex(2) String s3 = "()(abc)"; - @Regex(3) String s4 = "(abc())()"; - @Regex(4) String s5 = "((((abc))))"; - - @Regex(0) String s7 = "(abc)"; - @Regex String s9 = "()()(())"; - @Regex(2) String s10 = "()()(())"; - @Regex(3) String s11 = "()()(())"; - - // :: error: (assignment.type.incompatible) - @Regex(2) String s6 = "nonregex("; // error - // :: error: (assignment.type.incompatible) - @Regex(1) String s8 = "abc"; // error - // :: error: (assignment.type.incompatible) - @Regex(3) String s12 = "()()"; // error - // :: error: (assignment.type.incompatible) - @Regex(4) String s13 = "(())()"; // error - } - - void testPatternCompileGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { - @Regex(5) Pattern p1 = Pattern.compile(r5); - @Regex(5) Pattern p1a = Pattern.compile(r5, 0); - @Regex Pattern p2 = Pattern.compile(r5); - @Regex Pattern p3 = Pattern.compile(r); - - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p4 = Pattern.compile(r5); // error - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p4a = Pattern.compile(r5, 0); // error - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p5 = Pattern.compile(r3); // error - // :: error: (assignment.type.incompatible) - @Regex(6) Pattern p5a = Pattern.compile(r3, 0); // error - - // Make sure Pattern.compile still works when passed an @UnknownRegex String - // that's actually a regex, with the warning suppressed. - @SuppressWarnings("regex:argument.type.incompatible") - Pattern p6 = Pattern.compile("(" + r + ")"); - @SuppressWarnings("regex:argument.type.incompatible") - Pattern p6a = Pattern.compile("(" + r + ")", 0); - } - - void testConcatenationGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { - @Regex(0) String s1 = r + r; - @Regex(3) String s2 = r + r3; - @Regex(8) String s3 = r3 + r5; - - // :: error: (assignment.type.incompatible) - @Regex(1) String s4 = r + r; - // :: error: (assignment.type.incompatible) - @Regex(4) String s5 = r + r3; - // :: error: (assignment.type.incompatible) - @Regex(9) String s6 = r3 + r5; - } - - void testCompoundConcatenationWithGroups( - @Regex String s0, @Regex(1) String s1, @Regex(3) String s3) { - s0 += s0; - @Regex String test0 = s0; - // :: error: (assignment.type.incompatible) - @Regex(1) String test01 = s0; - - s0 += s1; - @Regex(1) String test1 = s0; - // :: error: (assignment.type.incompatible) - @Regex(2) String test12 = s0; - - s1 += s3; - @Regex(4) String test4 = s1; - // :: error: (assignment.type.incompatible) - @Regex(5) String test45 = s1; - } - - void testAsRegexGroupCounts(String s) { - @Regex String test1 = RegexUtil.asRegex(s); - // :: error: (assignment.type.incompatible) - @Regex(1) String test2 = RegexUtil.asRegex(s); - - @Regex(3) String test3 = RegexUtil.asRegex(s, 3); - // :: error: (assignment.type.incompatible) - @Regex(4) String test4 = RegexUtil.asRegex(s, 3); - } - - void testMatcherGroupCounts( - @Regex Matcher m0, @Regex(1) Matcher m1, @Regex(4) Matcher m4, int n) { - m0.end(0); - m0.group(0); - m0.start(0); - - // :: error: (group.count.invalid) - m0.end(1); - // :: error: (group.count.invalid) - m0.group(1); - // :: error: (group.count.invalid) - m0.start(1); - - m1.start(0); - m1.start(1); - - // :: error: (group.count.invalid) - m1.start(2); - - m4.start(0); - m4.start(2); - m4.start(4); - - // :: error: (group.count.invalid) - m4.start(5); - - // :: warning: (group.count.unknown) - m0.start(n); - } + void testGroupCount() { + @Regex(0) String s1 = "abc"; + @Regex(1) String s2 = "(abc)"; + @Regex(2) String s3 = "()(abc)"; + @Regex(3) String s4 = "(abc())()"; + @Regex(4) String s5 = "((((abc))))"; + + @Regex(0) String s7 = "(abc)"; + @Regex String s9 = "()()(())"; + @Regex(2) String s10 = "()()(())"; + @Regex(3) String s11 = "()()(())"; + + // :: error: (assignment.type.incompatible) + @Regex(2) String s6 = "nonregex("; // error + // :: error: (assignment.type.incompatible) + @Regex(1) String s8 = "abc"; // error + // :: error: (assignment.type.incompatible) + @Regex(3) String s12 = "()()"; // error + // :: error: (assignment.type.incompatible) + @Regex(4) String s13 = "(())()"; // error + } + + void testPatternCompileGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { + @Regex(5) Pattern p1 = Pattern.compile(r5); + @Regex(5) Pattern p1a = Pattern.compile(r5, 0); + @Regex Pattern p2 = Pattern.compile(r5); + @Regex Pattern p3 = Pattern.compile(r); + + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p4 = Pattern.compile(r5); // error + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p4a = Pattern.compile(r5, 0); // error + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p5 = Pattern.compile(r3); // error + // :: error: (assignment.type.incompatible) + @Regex(6) Pattern p5a = Pattern.compile(r3, 0); // error + + // Make sure Pattern.compile still works when passed an @UnknownRegex String + // that's actually a regex, with the warning suppressed. + @SuppressWarnings("regex:argument.type.incompatible") + Pattern p6 = Pattern.compile("(" + r + ")"); + @SuppressWarnings("regex:argument.type.incompatible") + Pattern p6a = Pattern.compile("(" + r + ")", 0); + } + + void testConcatenationGroupCount(@Regex String r, @Regex(3) String r3, @Regex(5) String r5) { + @Regex(0) String s1 = r + r; + @Regex(3) String s2 = r + r3; + @Regex(8) String s3 = r3 + r5; + + // :: error: (assignment.type.incompatible) + @Regex(1) String s4 = r + r; + // :: error: (assignment.type.incompatible) + @Regex(4) String s5 = r + r3; + // :: error: (assignment.type.incompatible) + @Regex(9) String s6 = r3 + r5; + } + + void testCompoundConcatenationWithGroups( + @Regex String s0, @Regex(1) String s1, @Regex(3) String s3) { + s0 += s0; + @Regex String test0 = s0; + // :: error: (assignment.type.incompatible) + @Regex(1) String test01 = s0; + + s0 += s1; + @Regex(1) String test1 = s0; + // :: error: (assignment.type.incompatible) + @Regex(2) String test12 = s0; + + s1 += s3; + @Regex(4) String test4 = s1; + // :: error: (assignment.type.incompatible) + @Regex(5) String test45 = s1; + } + + void testAsRegexGroupCounts(String s) { + @Regex String test1 = RegexUtil.asRegex(s); + // :: error: (assignment.type.incompatible) + @Regex(1) String test2 = RegexUtil.asRegex(s); + + @Regex(3) String test3 = RegexUtil.asRegex(s, 3); + // :: error: (assignment.type.incompatible) + @Regex(4) String test4 = RegexUtil.asRegex(s, 3); + } + + void testMatcherGroupCounts( + @Regex Matcher m0, @Regex(1) Matcher m1, @Regex(4) Matcher m4, int n) { + m0.end(0); + m0.group(0); + m0.start(0); + + // :: error: (group.count.invalid) + m0.end(1); + // :: error: (group.count.invalid) + m0.group(1); + // :: error: (group.count.invalid) + m0.start(1); + + m1.start(0); + m1.start(1); + + // :: error: (group.count.invalid) + m1.start(2); + + m4.start(0); + m4.start(2); + m4.start(4); + + // :: error: (group.count.invalid) + m4.start(5); + + // :: warning: (group.count.unknown) + m0.start(n); + } } diff --git a/checker/tests/regex/IntCast.java b/checker/tests/regex/IntCast.java index 100690c30b3..0973b19a2f4 100644 --- a/checker/tests/regex/IntCast.java +++ b/checker/tests/regex/IntCast.java @@ -1,6 +1,6 @@ public class IntCast { - int m() { - return (int) '\n'; - } + int m() { + return (int) '\n'; + } } diff --git a/checker/tests/regex/InvariantTypes.java b/checker/tests/regex/InvariantTypes.java index 0546fb75ea6..fd214c54797 100644 --- a/checker/tests/regex/InvariantTypes.java +++ b/checker/tests/regex/InvariantTypes.java @@ -1,109 +1,110 @@ +import org.checkerframework.checker.regex.qual.Regex; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import org.checkerframework.checker.regex.qual.Regex; public class InvariantTypes { - String[] sa = {"a"}; - String[] sa2 = {"a", "b"}; - public String[] sa3 = {"a", "b"}; - public static String[] sa4 = {"a", "b"}; - public final String[] sa5 = {"a", "b"}; - public static final String[] sa6 = {"a", "b"}; - final String[] sa7 = {"a", "b"}; - - // tested above: String[] sa = {"a"}; - @Regex String[] rsa = {"a"}; - String[] nrsa = {"(a"}; - // :: error: (array.initializer.type.incompatible) :: error: (assignment.type.incompatible) - @Regex String[] rsaerr = {"(a"}; - - List ls = Arrays.asList("alice", "bob", "carol"); - List<@Regex String> lrs = Arrays.asList("alice", "bob", "carol"); - List lnrs = Arrays.asList("(alice", "bob", "carol"); - // :: error: (type.arguments.not.inferred) - List<@Regex String> lrserr = Arrays.asList("(alice", "bob", "carol"); - - void unqm(String[] sa) {} - - void rem(@Regex String[] rsa) {} - - void recalls() { - unqm(new String[] {"a"}); - // TODOINVARR:: error: (argument.type.incompatible) - unqm(new @Regex String[] {"a"}); - rem(new String[] {"a"}); - rem(new @Regex String[] {"a"}); - } - - void unqcalls() { - unqm(new String[] {"a("}); - // TODOINVARR:: error: (argument.type.incompatible) - // :: error: (array.initializer.type.incompatible) - unqm(new @Regex String[] {"a("}); - // :: error: (argument.type.incompatible) - rem(new String[] {"a("}); - // :: error: (array.initializer.type.incompatible) - rem(new @Regex String[] {"a("}); - } - - // method argument context - - String[] retunqm(String[] sa) { - return sa; - } - - @Regex String[] retrem(@Regex String[] rsa) { - return rsa; - } - - @Regex String[] mixedm(String[] rsa) { - return null; - } - - void retunqcalls() { - @Regex String[] re = mixedm(new String[] {"a("}); - // TODOINVARR:: error: (argument.type.incompatible) - String[] u = retunqm(new String[] {"a"}); - // TODOINVARR:: error: (argument.type.incompatible) - re = mixedm(new String[2]); - } - - void lrem(List<@Regex String> p) {} - - void lunqm(List p) {} - - void listcalls() { - lunqm(Arrays.asList("alice", "bob", "carol")); - lrem(Arrays.asList("alice", "bob", "carol")); - lunqm(Arrays.asList("(alice", "bob", "carol")); - // :: error: (type.arguments.not.inferred) - lrem(Arrays.asList("(alice", "bob", "carol")); - } - - class ReTests { - ReTests(List<@Regex String> p) {} - - ReTests(List p, int i) {} - } - - void listctrs() { - new ReTests(Arrays.asList("alice", "bob", "carol"), 0); - new ReTests(Arrays.asList("alice", "bob", "carol")); - new ReTests(Arrays.asList("(alice", "bob", "carol"), 0); + String[] sa = {"a"}; + String[] sa2 = {"a", "b"}; + public String[] sa3 = {"a", "b"}; + public static String[] sa4 = {"a", "b"}; + public final String[] sa5 = {"a", "b"}; + public static final String[] sa6 = {"a", "b"}; + final String[] sa7 = {"a", "b"}; + + // tested above: String[] sa = {"a"}; + @Regex String[] rsa = {"a"}; + String[] nrsa = {"(a"}; + // :: error: (array.initializer.type.incompatible) :: error: (assignment.type.incompatible) + @Regex String[] rsaerr = {"(a"}; + + List ls = Arrays.asList("alice", "bob", "carol"); + List<@Regex String> lrs = Arrays.asList("alice", "bob", "carol"); + List lnrs = Arrays.asList("(alice", "bob", "carol"); // :: error: (type.arguments.not.inferred) - new ReTests(Arrays.asList("(alice", "bob", "carol")); - } - - String join(final String delimiter, final Collection objs) { - return delimiter; - } - - String s1 = join(" ", Arrays.asList("1", "2", "3")); - String s2 = "xxx" + join(" ", Arrays.asList("1", "2", "3")); - - class TV { - List> emptylist = Collections.emptyList(); - } + List<@Regex String> lrserr = Arrays.asList("(alice", "bob", "carol"); + + void unqm(String[] sa) {} + + void rem(@Regex String[] rsa) {} + + void recalls() { + unqm(new String[] {"a"}); + // TODOINVARR:: error: (argument.type.incompatible) + unqm(new @Regex String[] {"a"}); + rem(new String[] {"a"}); + rem(new @Regex String[] {"a"}); + } + + void unqcalls() { + unqm(new String[] {"a("}); + // TODOINVARR:: error: (argument.type.incompatible) + // :: error: (array.initializer.type.incompatible) + unqm(new @Regex String[] {"a("}); + // :: error: (argument.type.incompatible) + rem(new String[] {"a("}); + // :: error: (array.initializer.type.incompatible) + rem(new @Regex String[] {"a("}); + } + + // method argument context + + String[] retunqm(String[] sa) { + return sa; + } + + @Regex String[] retrem(@Regex String[] rsa) { + return rsa; + } + + @Regex String[] mixedm(String[] rsa) { + return null; + } + + void retunqcalls() { + @Regex String[] re = mixedm(new String[] {"a("}); + // TODOINVARR:: error: (argument.type.incompatible) + String[] u = retunqm(new String[] {"a"}); + // TODOINVARR:: error: (argument.type.incompatible) + re = mixedm(new String[2]); + } + + void lrem(List<@Regex String> p) {} + + void lunqm(List p) {} + + void listcalls() { + lunqm(Arrays.asList("alice", "bob", "carol")); + lrem(Arrays.asList("alice", "bob", "carol")); + lunqm(Arrays.asList("(alice", "bob", "carol")); + // :: error: (type.arguments.not.inferred) + lrem(Arrays.asList("(alice", "bob", "carol")); + } + + class ReTests { + ReTests(List<@Regex String> p) {} + + ReTests(List p, int i) {} + } + + void listctrs() { + new ReTests(Arrays.asList("alice", "bob", "carol"), 0); + new ReTests(Arrays.asList("alice", "bob", "carol")); + new ReTests(Arrays.asList("(alice", "bob", "carol"), 0); + // :: error: (type.arguments.not.inferred) + new ReTests(Arrays.asList("(alice", "bob", "carol")); + } + + String join(final String delimiter, final Collection objs) { + return delimiter; + } + + String s1 = join(" ", Arrays.asList("1", "2", "3")); + String s2 = "xxx" + join(" ", Arrays.asList("1", "2", "3")); + + class TV { + List> emptylist = Collections.emptyList(); + } } diff --git a/checker/tests/regex/InvariantTypesAtm.java b/checker/tests/regex/InvariantTypesAtm.java index 8d358c96253..b5492eb4063 100644 --- a/checker/tests/regex/InvariantTypesAtm.java +++ b/checker/tests/regex/InvariantTypesAtm.java @@ -1,13 +1,14 @@ -import java.util.Map; import org.checkerframework.framework.type.AnnotatedTypeMirror; +import java.util.Map; + public class InvariantTypesAtm { - V mapGetHelper( - Map mappings) { - return null; - } + V mapGetHelper( + Map mappings) { + return null; + } - Map mappings; - AnnotatedTypeMirror found = mapGetHelper(mappings); + Map mappings; + AnnotatedTypeMirror found = mapGetHelper(mappings); } diff --git a/checker/tests/regex/Issue3267.java b/checker/tests/regex/Issue3267.java index dff63c6abbf..91f866aa11f 100644 --- a/checker/tests/regex/Issue3267.java +++ b/checker/tests/regex/Issue3267.java @@ -1,17 +1,18 @@ // Test case for issue #3267: // https://github.com/typetools/checker-framework/issues/3267 -import java.util.regex.Pattern; import org.checkerframework.checker.regex.util.RegexUtil; +import java.util.regex.Pattern; + public class Issue3267 { - void foo(String s) { - if (RegexUtil.isRegex(s)) { - } else { - } - if (true) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); + void foo(String s) { + if (RegexUtil.isRegex(s)) { + } else { + } + if (true) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); + } } - } } diff --git a/checker/tests/regex/Issue3281.java b/checker/tests/regex/Issue3281.java index 215009a94c2..95feaa90bfb 100644 --- a/checker/tests/regex/Issue3281.java +++ b/checker/tests/regex/Issue3281.java @@ -1,80 +1,81 @@ // Test case for Issue 3281: // https://github.com/typetools/checker-framework/issues/3281 -import java.util.regex.Pattern; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.checker.regex.util.RegexUtil; +import java.util.regex.Pattern; + public class Issue3281 { - @Regex String f = null; + @Regex String f = null; - public boolean b = false; + public boolean b = false; - void m1(String s) { - if (true) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); + void m1(String s) { + if (true) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); + } } - } - void m2(String s) { - RegexUtil.isRegex(s); - if (true) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); + void m2(String s) { + RegexUtil.isRegex(s); + if (true) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); + } } - } - void m2f(String s) { - RegexUtil.isRegex(s); - if (true) { - // :: error: (assignment.type.incompatible) - f = s; + void m2f(String s) { + RegexUtil.isRegex(s); + if (true) { + // :: error: (assignment.type.incompatible) + f = s; + } } - } - void m3(String s) { - if (RegexUtil.isRegex(s)) { - Pattern.compile(s); + void m3(String s) { + if (RegexUtil.isRegex(s)) { + Pattern.compile(s); + } } - } - void m4(String s, String s2) { - RegexUtil.isRegex(s); - if (RegexUtil.isRegex(s2)) { - // :: error: (argument.type.incompatible) - Pattern.compile(s); + void m4(String s, String s2) { + RegexUtil.isRegex(s); + if (RegexUtil.isRegex(s2)) { + // :: error: (argument.type.incompatible) + Pattern.compile(s); + } } - } - void m4f(String s, String s2) { - RegexUtil.isRegex(s); - if (RegexUtil.isRegex(s2)) { - // :: error: (assignment.type.incompatible) - f = s; + void m4f(String s, String s2) { + RegexUtil.isRegex(s); + if (RegexUtil.isRegex(s2)) { + // :: error: (assignment.type.incompatible) + f = s; + } } - } - void m5f(String s, String s2) { - RegexUtil.isRegex(s); - if (b) { - // :: error: (assignment.type.incompatible) - f = s; + void m5f(String s, String s2) { + RegexUtil.isRegex(s); + if (b) { + // :: error: (assignment.type.incompatible) + f = s; + } } - } - void foo(String s1, String s2) { - bar( - RegexUtil.isRegex(s1), - // :: error: (argument.type.incompatible) - Pattern.compile(s1)); - boolean b; - bar( - b = RegexUtil.isRegex(s2), - // :: error: (argument.type.incompatible) - Pattern.compile(s2)); - } + void foo(String s1, String s2) { + bar( + RegexUtil.isRegex(s1), + // :: error: (argument.type.incompatible) + Pattern.compile(s1)); + boolean b; + bar( + b = RegexUtil.isRegex(s2), + // :: error: (argument.type.incompatible) + Pattern.compile(s2)); + } - void bar(boolean b, Object o) {} + void bar(boolean b, Object o) {} } diff --git a/checker/tests/regex/Issue809.java b/checker/tests/regex/Issue809.java index d064ecd4228..6ce471420fa 100644 --- a/checker/tests/regex/Issue809.java +++ b/checker/tests/regex/Issue809.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/809 public class Issue809> { - K[] array; + K[] array; - int index = 0; + int index = 0; - String m() { - return array[index] + "="; - } + String m() { + return array[index] + "="; + } } diff --git a/checker/tests/regex/LubRegex.java b/checker/tests/regex/LubRegex.java index 2e9dde3ff3a..b97214aa136 100644 --- a/checker/tests/regex/LubRegex.java +++ b/checker/tests/regex/LubRegex.java @@ -2,65 +2,65 @@ public class LubRegex { - void test1(@Regex(4) String s4, boolean b) { - String s = null; - if (b) { - s = s4; + void test1(@Regex(4) String s4, boolean b) { + String s = null; + if (b) { + s = s4; + } + @Regex(4) String test = s; + + // :: error: (assignment.type.incompatible) + @Regex(5) String test2 = s; } - @Regex(4) String test = s; - // :: error: (assignment.type.incompatible) - @Regex(5) String test2 = s; - } + void test2(@Regex(2) String s2, @Regex(4) String s4, boolean b) { + String s = s4; + if (b) { + s = s2; + } + @Regex(2) String test = s; - void test2(@Regex(2) String s2, @Regex(4) String s4, boolean b) { - String s = s4; - if (b) { - s = s2; + // :: error: (assignment.type.incompatible) + @Regex(3) String test2 = s; } - @Regex(2) String test = s; - // :: error: (assignment.type.incompatible) - @Regex(3) String test2 = s; - } + void test3(@Regex(6) String s6, boolean b) { + String s; + if (b) { + s = s6; + } else { + s = null; + } + @Regex(6) String test = s; - void test3(@Regex(6) String s6, boolean b) { - String s; - if (b) { - s = s6; - } else { - s = null; + // :: error: (assignment.type.incompatible) + @Regex(7) String test2 = s; } - @Regex(6) String test = s; - // :: error: (assignment.type.incompatible) - @Regex(7) String test2 = s; - } + void test4(@Regex(8) String s8, @Regex(9) String s9, boolean b) { + String s; + if (b) { + s = s8; + } else { + s = s9; + } + @Regex(8) String test = s; - void test4(@Regex(8) String s8, @Regex(9) String s9, boolean b) { - String s; - if (b) { - s = s8; - } else { - s = s9; + // :: error: (assignment.type.incompatible) + @Regex(9) String test2 = s; } - @Regex(8) String test = s; - // :: error: (assignment.type.incompatible) - @Regex(9) String test2 = s; - } + void test5(@Regex(10) String s10, @Regex(11) String s11, boolean b) { + String s; + if (b) { + s = s11; + } else { + s = s10; + return; + } + @Regex(11) String test = s; - void test5(@Regex(10) String s10, @Regex(11) String s11, boolean b) { - String s; - if (b) { - s = s11; - } else { - s = s10; - return; + // :: error: (assignment.type.incompatible) + @Regex(12) String test2 = s; } - @Regex(11) String test = s; - - // :: error: (assignment.type.incompatible) - @Regex(12) String test2 = s; - } } diff --git a/checker/tests/regex/MatcherGroupCount.java b/checker/tests/regex/MatcherGroupCount.java index f138e39cb9a..11cabfe28db 100644 --- a/checker/tests/regex/MatcherGroupCount.java +++ b/checker/tests/regex/MatcherGroupCount.java @@ -1,48 +1,52 @@ // Test case for Issue 291 // https://github.com/typetools/checker-framework/issues/291 -import java.util.regex.*; import org.checkerframework.checker.regex.util.RegexUtil; +import java.util.regex.*; + public class MatcherGroupCount { - public static void main(String[] args) { - String regex = args[0]; - String content = args[1]; + public static void main(String[] args) { + String regex = args[0]; + String content = args[1]; - if (!RegexUtil.isRegex(regex)) { - System.out.println( - "Error parsing regex \"" + regex + "\": " + RegexUtil.regexException(regex).getMessage()); - System.exit(1); - } + if (!RegexUtil.isRegex(regex)) { + System.out.println( + "Error parsing regex \"" + + regex + + "\": " + + RegexUtil.regexException(regex).getMessage()); + System.exit(1); + } - Pattern pat = Pattern.compile(regex); - Matcher mat = pat.matcher(content); + Pattern pat = Pattern.compile(regex); + Matcher mat = pat.matcher(content); - if (mat.matches()) { - if (mat.groupCount() > 0) { - System.out.println("Group: " + mat.group(1)); - } else { - System.out.println("No group found!"); - } - if (mat.groupCount() >= 2) { - System.out.println("Group: " + mat.group(2)); - } - if (mat.groupCount() >= 2) { - // :: error: (group.count.invalid) - System.out.println("Group: " + mat.group(3)); - } - if (2 < mat.groupCount()) { - System.out.println("Group: " + mat.group(3)); - } - if (!(mat.groupCount() > 4)) { - System.out.println("Group: " + mat.group(0)); - } else { - System.out.println("Group: " + mat.group(5)); - // :: error: (group.count.invalid) - System.out.println("Group: " + mat.group(6)); - } - } else { - System.out.println("No match!"); + if (mat.matches()) { + if (mat.groupCount() > 0) { + System.out.println("Group: " + mat.group(1)); + } else { + System.out.println("No group found!"); + } + if (mat.groupCount() >= 2) { + System.out.println("Group: " + mat.group(2)); + } + if (mat.groupCount() >= 2) { + // :: error: (group.count.invalid) + System.out.println("Group: " + mat.group(3)); + } + if (2 < mat.groupCount()) { + System.out.println("Group: " + mat.group(3)); + } + if (!(mat.groupCount() > 4)) { + System.out.println("Group: " + mat.group(0)); + } else { + System.out.println("Group: " + mat.group(5)); + // :: error: (group.count.invalid) + System.out.println("Group: " + mat.group(6)); + } + } else { + System.out.println("No match!"); + } } - } } diff --git a/checker/tests/regex/MyMatchResult.java b/checker/tests/regex/MyMatchResult.java index e08362735b1..670b9ccabd4 100644 --- a/checker/tests/regex/MyMatchResult.java +++ b/checker/tests/regex/MyMatchResult.java @@ -5,45 +5,45 @@ @SuppressWarnings("regex") public class MyMatchResult implements MatchResult { - @Override - public int start() { - group(0); - group(); - end(); - end(1); - groupCount(); - start(); - start(19); - return 0; - } - - @Override - public int start(int group) { - return 0; - } - - @Override - public int end() { - return 0; - } - - @Override - public int end(int group) { - return 0; - } - - @Override - public String group() { - return null; - } - - @Override - public String group(int group) { - return null; - } - - @Override - public int groupCount() { - return 0; - } + @Override + public int start() { + group(0); + group(); + end(); + end(1); + groupCount(); + start(); + start(19); + return 0; + } + + @Override + public int start(int group) { + return 0; + } + + @Override + public int end() { + return 0; + } + + @Override + public int end(int group) { + return 0; + } + + @Override + public String group() { + return null; + } + + @Override + public String group(int group) { + return null; + } + + @Override + public int groupCount() { + return 0; + } } diff --git a/checker/tests/regex/Nested.java b/checker/tests/regex/Nested.java index fa868683fce..add8452bc3f 100644 --- a/checker/tests/regex/Nested.java +++ b/checker/tests/regex/Nested.java @@ -3,15 +3,15 @@ // TODO: @Regex is not allowed on arbitrary types. Find a better test case. public class Nested { - // :: error: (anno.on.irrelevant) - OuterI.@Regex InnerA fa = new OuterI.@Regex InnerA() {}; + // :: error: (anno.on.irrelevant) + OuterI.@Regex InnerA fa = new OuterI.@Regex InnerA() {}; - // :: error: (anno.on.irrelevant) - OuterI.@Regex InnerB fb = new OuterI.@Regex InnerB() {}; + // :: error: (anno.on.irrelevant) + OuterI.@Regex InnerB fb = new OuterI.@Regex InnerB() {}; } class OuterI { - static class InnerA {} + static class InnerA {} - static class InnerB {} + static class InnerB {} } diff --git a/checker/tests/regex/PartialRegex.java b/checker/tests/regex/PartialRegex.java index d11f758e8eb..abfee658f46 100644 --- a/checker/tests/regex/PartialRegex.java +++ b/checker/tests/regex/PartialRegex.java @@ -1,21 +1,21 @@ import org.checkerframework.checker.regex.qual.Regex; public class PartialRegex { - void m(@Regex String re, String non) { - String l = "("; - String r = ")"; + void m(@Regex String re, String non) { + String l = "("; + String r = ")"; - @Regex String test1 = l + r; - @Regex String test2 = l + re + r; - @Regex String test3 = l + r + l + r; - @Regex String test4 = l + l + r + r; - @Regex String test5 = l + l + re + r + r; + @Regex String test1 = l + r; + @Regex String test2 = l + re + r; + @Regex String test3 = l + r + l + r; + @Regex String test4 = l + l + r + r; + @Regex String test5 = l + l + re + r + r; - // :: error: (assignment.type.incompatible) - @Regex String fail1 = r + l; - // :: error: (assignment.type.incompatible) - @Regex String fail2 = r + non + l; - // :: error: (assignment.type.incompatible) - @Regex String fail3 = l + r + r; - } + // :: error: (assignment.type.incompatible) + @Regex String fail1 = r + l; + // :: error: (assignment.type.incompatible) + @Regex String fail2 = r + non + l; + // :: error: (assignment.type.incompatible) + @Regex String fail3 = l + r + r; + } } diff --git a/checker/tests/regex/RawTypeTest.java b/checker/tests/regex/RawTypeTest.java index c2fdf1c3923..71a6406b9d0 100644 --- a/checker/tests/regex/RawTypeTest.java +++ b/checker/tests/regex/RawTypeTest.java @@ -1,104 +1,105 @@ +import org.checkerframework.checker.regex.qual.*; + import java.lang.ref.WeakReference; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.List; -import org.checkerframework.checker.regex.qual.*; public class RawTypeTest { - public void m1(Class c) { - Class x = c.asSubclass(I2.class); - - new WeakReference(x); - new WeakReference(x); - new WeakReference>(x); - - new WeakReference(c.asSubclass(I2.class)); - new WeakReference(c.asSubclass(I2.class)); - new WeakReference>(c.asSubclass(I2.class)); - } - - /* It would be desirable to optionally check the following code without - * warnings. See issue 119: - * - * https://github.com/typetools/checker-framework/issues/119 - * - class Raw { - public void m2(Class c) {} - - public void m3(Class c) { - m2(c); - } - - @SuppressWarnings("removal") // AccessController in JDK 17 - public void m4() { - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - return null; - }}); - } - - public void m5(List list, C4 c) { - list.add(c); - } - - public void m6(List list, long l) { - list.add(l); - } - }*/ - - class NonRaw { - public void m2(Class c) {} - - public void m3(Class c) { - m2(c); - } + public void m1(Class c) { + Class x = c.asSubclass(I2.class); - @SuppressWarnings("removal") // AccessController is deprecated for removal in Java 17 - public void m4() { - AccessController.doPrivileged( - new PrivilegedAction() { - public Object run() { - return null; - } - }); - } + new WeakReference(x); + new WeakReference(x); + new WeakReference>(x); - public void m5(List list, C4 c) { - list.add(c); + new WeakReference(c.asSubclass(I2.class)); + new WeakReference(c.asSubclass(I2.class)); + new WeakReference>(c.asSubclass(I2.class)); } - public void m6(List list, long l) { - list.add(l); + /* It would be desirable to optionally check the following code without + * warnings. See issue 119: + * + * https://github.com/typetools/checker-framework/issues/119 + * + class Raw { + public void m2(Class c) {} + + public void m3(Class c) { + m2(c); + } + + @SuppressWarnings("removal") // AccessController in JDK 17 + public void m4() { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return null; + }}); + } + + public void m5(List list, C4 c) { + list.add(c); + } + + public void m6(List list, long l) { + list.add(l); + } + }*/ + + class NonRaw { + public void m2(Class c) {} + + public void m3(Class c) { + m2(c); + } + + @SuppressWarnings("removal") // AccessController is deprecated for removal in Java 17 + public void m4() { + AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return null; + } + }); + } + + public void m5(List list, C4 c) { + list.add(c); + } + + public void m6(List list, long l) { + list.add(l); + } } - } - class MyList { - X f; - } + class MyList { + X f; + } - interface I1 { - public void m(MyList l); - } + interface I1 { + public void m(MyList l); + } - class C1 implements I1 { - public void m(MyList par) { - @Regex String xxx = par.f; + class C1 implements I1 { + public void m(MyList par) { + @Regex String xxx = par.f; + } } - } - interface I2 { - public void m(MyList<@Regex String> l); - } + interface I2 { + public void m(MyList<@Regex String> l); + } - class C2 implements I2 { - public void m(MyList<@Regex String> l) {} - } + class C2 implements I2 { + public void m(MyList<@Regex String> l) {} + } - class C3 implements I2 { - // :: error: (override.param.invalid) :: error: (type.argument.type.incompatible) - public void m(MyList l) {} - } + class C3 implements I2 { + // :: error: (override.param.invalid) :: error: (type.argument.type.incompatible) + public void m(MyList l) {} + } - class C4 {} + class C4 {} } diff --git a/checker/tests/regex/RegexUtilClient.java b/checker/tests/regex/RegexUtilClient.java index ebca45f73ea..9ed183bb1e4 100644 --- a/checker/tests/regex/RegexUtilClient.java +++ b/checker/tests/regex/RegexUtilClient.java @@ -2,96 +2,96 @@ import org.checkerframework.framework.qual.EnsuresQualifierIf; public class RegexUtilClient { - void fullyQualifiedRegexUtil(String s) { - if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s, 2)) { - @Regex(2) String s2 = s; + void fullyQualifiedRegexUtil(String s) { + if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s, 2)) { + @Regex(2) String s2 = s; + } + @Regex(2) String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s, 2); } - @Regex(2) String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s, 2); - } - void unqualifiedRegexUtil(String s) { - if (RegexUtil.isRegex(s, 2)) { - @Regex(2) String s2 = s; + void unqualifiedRegexUtil(String s) { + if (RegexUtil.isRegex(s, 2)) { + @Regex(2) String s2 = s; + } + @Regex(2) String s2 = RegexUtil.asRegex(s, 2); } - @Regex(2) String s2 = RegexUtil.asRegex(s, 2); - } - void fullyQualifiedRegexUtilNoParamsArg(String s) { - if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s)) { - @Regex String s2 = s; - @Regex(0) String s3 = s; + void fullyQualifiedRegexUtilNoParamsArg(String s) { + if (org.checkerframework.checker.regex.util.RegexUtil.isRegex(s)) { + @Regex String s2 = s; + @Regex(0) String s3 = s; + } + @Regex String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); + @Regex(0) String s3 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); } - @Regex String s2 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); - @Regex(0) String s3 = org.checkerframework.checker.regex.util.RegexUtil.asRegex(s); - } - void unqualifiedRegexUtilNoParamsArg(String s) { - if (RegexUtil.isRegex(s)) { - @Regex String s2 = s; - @Regex(0) String s3 = s; + void unqualifiedRegexUtilNoParamsArg(String s) { + if (RegexUtil.isRegex(s)) { + @Regex String s2 = s; + @Regex(0) String s3 = s; + } + @Regex String s2 = RegexUtil.asRegex(s, 2); + @Regex(0) String s3 = RegexUtil.asRegex(s, 2); } - @Regex String s2 = RegexUtil.asRegex(s, 2); - @Regex(0) String s3 = RegexUtil.asRegex(s, 2); - } - void illegalName(String s) { - if (IllegalName.isRegex(s, 2)) { - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = s; + void illegalName(String s) { + if (IllegalName.isRegex(s, 2)) { + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = s; + } + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = IllegalName.asRegex(s, 2); } - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = IllegalName.asRegex(s, 2); - } - void illegalNameRegexUtil(String s) { - if (IllegalNameRegexUtil.isRegex(s, 2)) { - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = s; + void illegalNameRegexUtil(String s) { + if (IllegalNameRegexUtil.isRegex(s, 2)) { + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = s; + } + // :: error: (assignment.type.incompatible) + @Regex(2) String s2 = IllegalNameRegexUtil.asRegex(s, 2); } - // :: error: (assignment.type.incompatible) - @Regex(2) String s2 = IllegalNameRegexUtil.asRegex(s, 2); - } } // A dummy RegexUtil class to make sure RegexUtil in no package works. class RegexUtil { - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(final String s, int n) { - return false; - } + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(final String s, int n) { + return false; + } - public static @Regex String asRegex(String s, int n) { - return null; - } + public static @Regex String asRegex(String s, int n) { + return null; + } - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(final String s) { - return false; - } + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(final String s) { + return false; + } - public static @Regex String asRegex(String s) { - return null; - } + public static @Regex String asRegex(String s) { + return null; + } } // These methods shouldn't work. class IllegalName { - public static boolean isRegex(String s, int n) { - return false; - } + public static boolean isRegex(String s, int n) { + return false; + } - public static @Regex String asRegex(String s, int n) { - return null; - } + public static @Regex String asRegex(String s, int n) { + return null; + } } // These methods shouldn't work. class IllegalNameRegexUtil { - public static boolean isRegex(String s, int n) { - return false; - } + public static boolean isRegex(String s, int n) { + return false; + } - public static @Regex String asRegex(String s, int n) { - return null; - } + public static @Regex String asRegex(String s, int n) { + return null; + } } diff --git a/checker/tests/regex/SimpleRegex.java b/checker/tests/regex/SimpleRegex.java index f0a073f2dc8..989e2b97093 100644 --- a/checker/tests/regex/SimpleRegex.java +++ b/checker/tests/regex/SimpleRegex.java @@ -1,141 +1,142 @@ -import java.util.regex.Pattern; import org.checkerframework.checker.regex.qual.Regex; +import java.util.regex.Pattern; + public class SimpleRegex { - void regString() { - String s1 = "validRegex"; - String s2 = "(InvalidRegex"; - } - - void validRegString() { - @Regex String s1 = "validRegex"; - // :: error: (assignment.type.incompatible) - @Regex String s2 = "(InvalidRegex"; // error - } - - void compileCall() { - Pattern.compile("test.*[^123]$"); - // :: error: (argument.type.incompatible) - Pattern.compile("$test.*[^123"); // error - } - - void requireValidReg(@Regex String reg, String nonReg) { - Pattern.compile(reg); - // :: error: (argument.type.incompatible) - Pattern.compile(nonReg); // error - } - - void testAddition(@Regex String reg, String nonReg) { - @Regex String s1 = reg; - @Regex String s2 = reg + "d.*sf"; - @Regex String s3 = reg + reg; - - // :: error: (assignment.type.incompatible) - @Regex String n1 = nonReg; // error - // :: error: (assignment.type.incompatible) - @Regex String n2 = reg + "(df"; // error - // :: error: (assignment.type.incompatible) - @Regex String n3 = reg + nonReg; // error - - // :: error: (assignment.type.incompatible) - @Regex String o1 = nonReg; // error - // :: error: (assignment.type.incompatible) - @Regex String o2 = nonReg + "sdf"; // error - // :: error: (assignment.type.incompatible) - @Regex String o3 = nonReg + reg; // error - } - - @Regex String regex = "()"; - String nonRegex = "()"; - - void testCompoundConcatenation() { - takesRegex(regex); - // :: error: (compound.assignment.type.incompatible) - regex += ")"; // error - takesRegex(regex); - - nonRegex = "()"; - // nonRegex is refined by flow to be a regular expression - takesRegex(nonRegex); - nonRegex += ")"; - // :: error: (argument.type.incompatible) - takesRegex(nonRegex); // error - } - - void takesRegex(@Regex String s) {} - - void testChar() { - @Regex char c1 = 'c'; - @Regex Character c2 = 'c'; - - // :: error: (assignment.type.incompatible) - @Regex char c3 = '('; // error - // :: error: (assignment.type.incompatible) - @Regex Character c4 = '('; // error - } - - void testCharConcatenation() { - @Regex String s1 = "rege" + 'x'; - @Regex String s2 = 'r' + "egex"; - - // :: error: (assignment.type.incompatible) - @Regex String s4 = "rege" + '('; // error - // :: error: (assignment.type.incompatible) - @Regex String s5 = "reg(" + 'x'; // error - // :: error: (assignment.type.incompatible) - @Regex String s6 = '(' + "egex"; // error - // :: error: (assignment.type.incompatible) - @Regex String s7 = 'r' + "ege("; // error - } - - void testPatternLiteral() { - Pattern.compile("non(", Pattern.LITERAL); - Pattern.compile(foo("regex"), Pattern.LITERAL); - - // :: error: (argument.type.incompatible) - Pattern.compile(foo("regex("), Pattern.LITERAL); // error - // :: error: (argument.type.incompatible) - Pattern.compile("non("); // error - // :: error: (argument.type.incompatible) - Pattern.compile(foo("regex")); // error - // :: error: (argument.type.incompatible) - Pattern.compile("non(", Pattern.CASE_INSENSITIVE); // error - } - - public static String foo(@Regex String s) { - return "non(("; - } - - // TODO: This is not supported until the framework can read explicit - // annotations from arrays. - // void testArrayAllowedTypes() { - // @Regex char[] ca1; - // char @Regex [] ca2; - // @Regex char @Regex [] ca3; - // @Regex String[] s1; - // - // // :: error: (type.invalid) - // @Regex double[] da1; // error - // // :: error: (type.invalid) - // double @Regex [] da2; // error - // // :: error: (type.invalid) - // @Regex double @Regex [] da3; // error - // // :: error: (type.invalid) - // String @Regex [] s2; // error - // } - - // TODO: This is not supported until the Regex Checker supports flow - // sensitivity. See the associated comment at - // org.checkerframework.checker/regex/RegexAnnotatedTypeFactory.java:visitNewArray - // void testCharArrays(char c, @Regex char r) { - // char @Regex [] c1 = {'r', 'e', 'g', 'e', 'x'}; - // char @Regex [] c2 = {'(', 'r', 'e', 'g', 'e', 'x', ')', '.', '*'}; - // char @Regex [] c3 = {r, 'e', 'g', 'e', 'x'}; - // - // // :: error: (assignment.type.incompatible) - // char @Regex [] c4 = {'(', 'r', 'e', 'g', 'e', 'x'}; // error - // // :: error: (assignment.type.incompatible) - // char @Regex [] c5 = {c, '.', '*'}; // error - // } + void regString() { + String s1 = "validRegex"; + String s2 = "(InvalidRegex"; + } + + void validRegString() { + @Regex String s1 = "validRegex"; + // :: error: (assignment.type.incompatible) + @Regex String s2 = "(InvalidRegex"; // error + } + + void compileCall() { + Pattern.compile("test.*[^123]$"); + // :: error: (argument.type.incompatible) + Pattern.compile("$test.*[^123"); // error + } + + void requireValidReg(@Regex String reg, String nonReg) { + Pattern.compile(reg); + // :: error: (argument.type.incompatible) + Pattern.compile(nonReg); // error + } + + void testAddition(@Regex String reg, String nonReg) { + @Regex String s1 = reg; + @Regex String s2 = reg + "d.*sf"; + @Regex String s3 = reg + reg; + + // :: error: (assignment.type.incompatible) + @Regex String n1 = nonReg; // error + // :: error: (assignment.type.incompatible) + @Regex String n2 = reg + "(df"; // error + // :: error: (assignment.type.incompatible) + @Regex String n3 = reg + nonReg; // error + + // :: error: (assignment.type.incompatible) + @Regex String o1 = nonReg; // error + // :: error: (assignment.type.incompatible) + @Regex String o2 = nonReg + "sdf"; // error + // :: error: (assignment.type.incompatible) + @Regex String o3 = nonReg + reg; // error + } + + @Regex String regex = "()"; + String nonRegex = "()"; + + void testCompoundConcatenation() { + takesRegex(regex); + // :: error: (compound.assignment.type.incompatible) + regex += ")"; // error + takesRegex(regex); + + nonRegex = "()"; + // nonRegex is refined by flow to be a regular expression + takesRegex(nonRegex); + nonRegex += ")"; + // :: error: (argument.type.incompatible) + takesRegex(nonRegex); // error + } + + void takesRegex(@Regex String s) {} + + void testChar() { + @Regex char c1 = 'c'; + @Regex Character c2 = 'c'; + + // :: error: (assignment.type.incompatible) + @Regex char c3 = '('; // error + // :: error: (assignment.type.incompatible) + @Regex Character c4 = '('; // error + } + + void testCharConcatenation() { + @Regex String s1 = "rege" + 'x'; + @Regex String s2 = 'r' + "egex"; + + // :: error: (assignment.type.incompatible) + @Regex String s4 = "rege" + '('; // error + // :: error: (assignment.type.incompatible) + @Regex String s5 = "reg(" + 'x'; // error + // :: error: (assignment.type.incompatible) + @Regex String s6 = '(' + "egex"; // error + // :: error: (assignment.type.incompatible) + @Regex String s7 = 'r' + "ege("; // error + } + + void testPatternLiteral() { + Pattern.compile("non(", Pattern.LITERAL); + Pattern.compile(foo("regex"), Pattern.LITERAL); + + // :: error: (argument.type.incompatible) + Pattern.compile(foo("regex("), Pattern.LITERAL); // error + // :: error: (argument.type.incompatible) + Pattern.compile("non("); // error + // :: error: (argument.type.incompatible) + Pattern.compile(foo("regex")); // error + // :: error: (argument.type.incompatible) + Pattern.compile("non(", Pattern.CASE_INSENSITIVE); // error + } + + public static String foo(@Regex String s) { + return "non(("; + } + + // TODO: This is not supported until the framework can read explicit + // annotations from arrays. + // void testArrayAllowedTypes() { + // @Regex char[] ca1; + // char @Regex [] ca2; + // @Regex char @Regex [] ca3; + // @Regex String[] s1; + // + // // :: error: (type.invalid) + // @Regex double[] da1; // error + // // :: error: (type.invalid) + // double @Regex [] da2; // error + // // :: error: (type.invalid) + // @Regex double @Regex [] da3; // error + // // :: error: (type.invalid) + // String @Regex [] s2; // error + // } + + // TODO: This is not supported until the Regex Checker supports flow + // sensitivity. See the associated comment at + // org.checkerframework.checker/regex/RegexAnnotatedTypeFactory.java:visitNewArray + // void testCharArrays(char c, @Regex char r) { + // char @Regex [] c1 = {'r', 'e', 'g', 'e', 'x'}; + // char @Regex [] c2 = {'(', 'r', 'e', 'g', 'e', 'x', ')', '.', '*'}; + // char @Regex [] c3 = {r, 'e', 'g', 'e', 'x'}; + // + // // :: error: (assignment.type.incompatible) + // char @Regex [] c4 = {'(', 'r', 'e', 'g', 'e', 'x'}; // error + // // :: error: (assignment.type.incompatible) + // char @Regex [] c5 = {c, '.', '*'}; // error + // } } diff --git a/checker/tests/regex/TestIsRegex.java b/checker/tests/regex/TestIsRegex.java index b07f51d8275..71b27d195f7 100644 --- a/checker/tests/regex/TestIsRegex.java +++ b/checker/tests/regex/TestIsRegex.java @@ -1,125 +1,132 @@ -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.checkerframework.checker.regex.qual.*; import org.checkerframework.checker.regex.util.RegexUtil; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class TestIsRegex { - void test1(String str1) throws Exception { - if (!RegexUtil.isRegex(str1)) { - throw new Exception(); + void test1(String str1) throws Exception { + if (!RegexUtil.isRegex(str1)) { + throw new Exception(); + } + Pattern.compile(str1); } - Pattern.compile(str1); - } - void test2(String str2) throws Exception { - if (!RegexUtil.isRegex(str2)) { - // :: error: (argument.type.incompatible) - Pattern.compile(str2); + void test2(String str2) throws Exception { + if (!RegexUtil.isRegex(str2)) { + // :: error: (argument.type.incompatible) + Pattern.compile(str2); + } } - } - void test3(String str3) throws Exception { - if (RegexUtil.isRegex(str3)) { - Pattern.compile(str3); - } else { - throw new Exception(); + void test3(String str3) throws Exception { + if (RegexUtil.isRegex(str3)) { + Pattern.compile(str3); + } else { + throw new Exception(); + } } - } - - void test4(String str4) throws Exception { - if (RegexUtil.isRegex(str4)) { - Pattern.compile(str4); - } else { - // :: error: (argument.type.incompatible) - Pattern.compile(str4); + + void test4(String str4) throws Exception { + if (RegexUtil.isRegex(str4)) { + Pattern.compile(str4); + } else { + // :: error: (argument.type.incompatible) + Pattern.compile(str4); + } } - } - void test5(String str5) throws Exception { - if (!RegexUtil.isRegex(str5, 3)) { - throw new Exception(); + void test5(String str5) throws Exception { + if (!RegexUtil.isRegex(str5, 3)) { + throw new Exception(); + } + Pattern.compile(str5).matcher("test").group(3); } - Pattern.compile(str5).matcher("test").group(3); - } - - void test6(String str6) throws Exception { - if (RegexUtil.isRegex(str6, 4)) { - Pattern.compile(str6).matcher("4kdfj").group(4); - } else { - // :: error: (argument.type.incompatible) - Pattern.compile(str6); + + void test6(String str6) throws Exception { + if (RegexUtil.isRegex(str6, 4)) { + Pattern.compile(str6).matcher("4kdfj").group(4); + } else { + // :: error: (argument.type.incompatible) + Pattern.compile(str6); + } } - } - void test7(String str7) throws Exception { - if (RegexUtil.isRegex(str7, 5)) { - // :: error: (group.count.invalid) - Pattern.compile(str7).matcher("4kdfj").group(6); + void test7(String str7) throws Exception { + if (RegexUtil.isRegex(str7, 5)) { + // :: error: (group.count.invalid) + Pattern.compile(str7).matcher("4kdfj").group(6); + } } - } - - @Regex Pattern test8(String input) { - String datePattern = null; - - if (input != null) { - datePattern = "regexkdafj"; - if (!RegexUtil.isRegex(datePattern, 1)) { - throw new Error( - "error parsing regex " + datePattern + ": " + RegexUtil.regexError(datePattern)); - } - return Pattern.compile(datePattern); + + @Regex Pattern test8(String input) { + String datePattern = null; + + if (input != null) { + datePattern = "regexkdafj"; + if (!RegexUtil.isRegex(datePattern, 1)) { + throw new Error( + "error parsing regex " + + datePattern + + ": " + + RegexUtil.regexError(datePattern)); + } + return Pattern.compile(datePattern); + } + @Regex(1) String dp = datePattern; + + if (input != null) { // just some test... + Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; + return pattern; + } else { + Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; + return pattern; + } } - @Regex(1) String dp = datePattern; - - if (input != null) { // just some test... - Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; - return pattern; - } else { - Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; - return pattern; + + @Regex(1) Pattern test9(String input) { + String datePattern = null; + + if (input != null) { + datePattern = "regexkdafj"; + if (!RegexUtil.isRegex(datePattern, 1)) { + throw new Error( + "error parsing regex " + + datePattern + + ": " + + RegexUtil.regexError(datePattern)); + } + return Pattern.compile(datePattern); + } + @Regex(1) String dp = datePattern; + + if (input != null) { // just some test... + Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; + return pattern; + } else { + Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; + return pattern; + } } - } - - @Regex(1) Pattern test9(String input) { - String datePattern = null; - - if (input != null) { - datePattern = "regexkdafj"; - if (!RegexUtil.isRegex(datePattern, 1)) { - throw new Error( - "error parsing regex " + datePattern + ": " + RegexUtil.regexError(datePattern)); - } - return Pattern.compile(datePattern); + + void test10(String s) throws Exception { + if (!RegexUtil.isRegex(s, 2)) { + throw new Exception(); + } + Pattern p = Pattern.compile(s); + Matcher m = p.matcher("abc"); + String g = m.group(1); } - @Regex(1) String dp = datePattern; - - if (input != null) { // just some test... - Pattern pattern = datePattern != null ? Pattern.compile(datePattern) : null; - return pattern; - } else { - Pattern pattern = datePattern != null ? Pattern.compile(dp) : null; - return pattern; + + void test11(String s) throws Exception { + @Regex(2) String l1 = RegexUtil.asRegex(s, 2); + @Regex(1) String l2 = RegexUtil.asRegex(s, 2); + @Regex String l3 = RegexUtil.asRegex(s, 2); + // :: error: (assignment.type.incompatible) + @Regex(3) String l4 = RegexUtil.asRegex(s, 2); } - } - void test10(String s) throws Exception { - if (!RegexUtil.isRegex(s, 2)) { - throw new Exception(); + @Regex(2) String test12(String s, boolean b) throws Exception { + return b ? null : RegexUtil.asRegex(s, 2); } - Pattern p = Pattern.compile(s); - Matcher m = p.matcher("abc"); - String g = m.group(1); - } - - void test11(String s) throws Exception { - @Regex(2) String l1 = RegexUtil.asRegex(s, 2); - @Regex(1) String l2 = RegexUtil.asRegex(s, 2); - @Regex String l3 = RegexUtil.asRegex(s, 2); - // :: error: (assignment.type.incompatible) - @Regex(3) String l4 = RegexUtil.asRegex(s, 2); - } - - @Regex(2) String test12(String s, boolean b) throws Exception { - return b ? null : RegexUtil.asRegex(s, 2); - } } diff --git a/checker/tests/regex/TestRegex.java b/checker/tests/regex/TestRegex.java index b5a1b459281..f932b12e7a7 100644 --- a/checker/tests/regex/TestRegex.java +++ b/checker/tests/regex/TestRegex.java @@ -3,19 +3,19 @@ // test-case for issue 128 public class TestRegex { - public void Concatenation2() { - @Regex String a = "a"; - // :: error: (compound.assignment.type.incompatible) - a += "("; - } + public void Concatenation2() { + @Regex String a = "a"; + // :: error: (compound.assignment.type.incompatible) + a += "("; + } } // test-case for issue 148 class Search { - public static void main(String[] args) { - if (!org.checkerframework.checker.regex.util.RegexUtil.isRegex(args[0], 4)) { - return; + public static void main(String[] args) { + if (!org.checkerframework.checker.regex.util.RegexUtil.isRegex(args[0], 4)) { + return; + } + @Regex(4) String regex = args[0]; } - @Regex(4) String regex = args[0]; - } } diff --git a/checker/tests/regex/TypeParamSubtype.java b/checker/tests/regex/TypeParamSubtype.java index 2eeff3937d8..372ba987a9b 100644 --- a/checker/tests/regex/TypeParamSubtype.java +++ b/checker/tests/regex/TypeParamSubtype.java @@ -1,23 +1,24 @@ -import java.util.Collection; import org.checkerframework.checker.regex.qual.Regex; +import java.util.Collection; + public class TypeParamSubtype { - // These are legal because null has type @Regex String - // void nullRegexSubtype(Collection col) { - // // :: error: (argument.type.incompatible) - // col.add(null); - // } - // - // void nullSimpleSubtype(Collection col) { - // // :: error: (argument.type.incompatible) - // col.add(null); - // } + // These are legal because null has type @Regex String + // void nullRegexSubtype(Collection col) { + // // :: error: (argument.type.incompatible) + // col.add(null); + // } + // + // void nullSimpleSubtype(Collection col) { + // // :: error: (argument.type.incompatible) + // col.add(null); + // } - void nullRegexSubtype(Collection col, U u) { - col.add(u); - } + void nullRegexSubtype(Collection col, U u) { + col.add(u); + } - void nullSimpleSubtype(Collection col, U u) { - col.add(u); - } + void nullSimpleSubtype(Collection col, U u) { + col.add(u); + } } diff --git a/checker/tests/regex/TypeVarMemberSelect.java b/checker/tests/regex/TypeVarMemberSelect.java index 207a71ce99d..38f58acafac 100644 --- a/checker/tests/regex/TypeVarMemberSelect.java +++ b/checker/tests/regex/TypeVarMemberSelect.java @@ -1,19 +1,19 @@ import org.checkerframework.checker.regex.qual.*; class Box { - @Regex(1) T t1; + @Regex(1) T t1; - T t2; + T t2; } class TypeVarMemberSelect> { - void test(V v) { - // :: error: (assignment.type.incompatible) - @Regex(2) String local1 = v.t1; + void test(V v) { + // :: error: (assignment.type.incompatible) + @Regex(2) String local1 = v.t1; - // Previously the type of the right hand side would have been T which is wrong. This test - // was added to make sure we call viewpoint adaptation when type variables are the receiver. - @Regex(2) String local2 = v.t2; - } + // Previously the type of the right hand side would have been T which is wrong. This test + // was added to make sure we call viewpoint adaptation when type variables are the receiver. + @Regex(2) String local2 = v.t2; + } } diff --git a/checker/tests/regex/WildcardInvoke.java b/checker/tests/regex/WildcardInvoke.java index 646bb0ff8da..de0135bb944 100644 --- a/checker/tests/regex/WildcardInvoke.java +++ b/checker/tests/regex/WildcardInvoke.java @@ -1,10 +1,10 @@ public class WildcardInvoke { - class Demo { - void call(T p) {} - } + class Demo { + void call(T p) {} + } - void m() { - Demo d = null; - d.call(null); - } + void m() { + Demo d = null; + d.call(null); + } } diff --git a/checker/tests/regex_poly/PolyRegexTests.java b/checker/tests/regex_poly/PolyRegexTests.java index 45c46fdeff2..fb3b00fc914 100644 --- a/checker/tests/regex_poly/PolyRegexTests.java +++ b/checker/tests/regex_poly/PolyRegexTests.java @@ -3,80 +3,80 @@ public class PolyRegexTests { - @Regex(0) String field1 = "abc".toString(); - - public static @PolyRegex String method(@PolyRegex String s) { - return s; - } - - public void testRegex(@Regex String str) { - @Regex String s = method(str); - } - - public void testNonRegex(String str) { - // :: error: (assignment.type.incompatible) - @Regex String s = method(str); // error - } - - public void testInternRegex(@Regex String str) { - @Regex String s = str.intern(); - } - - public void testInternNonRegex(String str) { - // :: error: (assignment.type.incompatible) - @Regex String s = str.intern(); // error - } - - public void testToStringRegex(@Regex String str) { - @Regex String s = str.toString(); - } - - public void testToStringNonRegex(String str) { - // :: error: (assignment.type.incompatible) - @Regex String s = str.toString(); // error - } - - public @PolyRegex String testPolyRegexConcat(@PolyRegex String s1, @PolyRegex String s2) { - return s1 + s2; - } - - public void testPolyRegexConcatErrors(@PolyRegex String polyReg, String nonPolyReg) { - // :: error: (assignment.type.incompatible) - @PolyRegex String test1 = polyReg + nonPolyReg; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test2 = nonPolyReg + polyReg; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test3 = nonPolyReg + nonPolyReg; // error - } - - public void testRegexPolyRegexConcat(@PolyRegex String polyReg, @Regex String reg) { - @PolyRegex String test1 = polyReg + reg; - @PolyRegex String test2 = reg + polyReg; - } - - public void testRegexPolyRegexConcatErrors( - @PolyRegex String polyReg, @Regex String reg, String str) { - // :: error: (assignment.type.incompatible) - @PolyRegex String test1 = polyReg + str; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test2 = str + polyReg; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test3 = reg + str; // error - // :: error: (assignment.type.incompatible) - @PolyRegex String test4 = str + reg; // error - - // :: error: (assignment.type.incompatible) - @PolyRegex String test5 = str + str; // error - } - - public static @PolyRegex String slice(@PolyRegex String seq, int start, int end) { - if (seq == null) { - return null; + @Regex(0) String field1 = "abc".toString(); + + public static @PolyRegex String method(@PolyRegex String s) { + return s; + } + + public void testRegex(@Regex String str) { + @Regex String s = method(str); + } + + public void testNonRegex(String str) { + // :: error: (assignment.type.incompatible) + @Regex String s = method(str); // error + } + + public void testInternRegex(@Regex String str) { + @Regex String s = str.intern(); + } + + public void testInternNonRegex(String str) { + // :: error: (assignment.type.incompatible) + @Regex String s = str.intern(); // error + } + + public void testToStringRegex(@Regex String str) { + @Regex String s = str.toString(); + } + + public void testToStringNonRegex(String str) { + // :: error: (assignment.type.incompatible) + @Regex String s = str.toString(); // error + } + + public @PolyRegex String testPolyRegexConcat(@PolyRegex String s1, @PolyRegex String s2) { + return s1 + s2; + } + + public void testPolyRegexConcatErrors(@PolyRegex String polyReg, String nonPolyReg) { + // :: error: (assignment.type.incompatible) + @PolyRegex String test1 = polyReg + nonPolyReg; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test2 = nonPolyReg + polyReg; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test3 = nonPolyReg + nonPolyReg; // error + } + + public void testRegexPolyRegexConcat(@PolyRegex String polyReg, @Regex String reg) { + @PolyRegex String test1 = polyReg + reg; + @PolyRegex String test2 = reg + polyReg; + } + + public void testRegexPolyRegexConcatErrors( + @PolyRegex String polyReg, @Regex String reg, String str) { + // :: error: (assignment.type.incompatible) + @PolyRegex String test1 = polyReg + str; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test2 = str + polyReg; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test3 = reg + str; // error + // :: error: (assignment.type.incompatible) + @PolyRegex String test4 = str + reg; // error + + // :: error: (assignment.type.incompatible) + @PolyRegex String test5 = str + str; // error } - return seq; - } - public static @PolyRegex String slice(@PolyRegex String seq, long start, int end) { - return slice(seq, (int) start, end); - } + public static @PolyRegex String slice(@PolyRegex String seq, int start, int end) { + if (seq == null) { + return null; + } + return seq; + } + + public static @PolyRegex String slice(@PolyRegex String seq, long start, int end) { + return slice(seq, (int) start, end); + } } diff --git a/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java b/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java index 514601a0da9..82b3636b09c 100644 --- a/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java +++ b/checker/tests/regex_poly/StringBuilderToStringPolyRegex.java @@ -7,7 +7,7 @@ class StringBuilderToStringPolyRegex { - void createPattern(final @Regex(1) StringBuilder regex) { - @Regex(1) String s = regex.toString(); - } + void createPattern(final @Regex(1) StringBuilder regex) { + @Regex(1) String s = regex.toString(); + } } diff --git a/checker/tests/resourceleak-customignoredexceptions/BasicTest.java b/checker/tests/resourceleak-customignoredexceptions/BasicTest.java index e6f41bcd264..a9d7987c318 100644 --- a/checker/tests/resourceleak-customignoredexceptions/BasicTest.java +++ b/checker/tests/resourceleak-customignoredexceptions/BasicTest.java @@ -1,59 +1,60 @@ -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + abstract class BasicTest { - abstract Closeable alloc(); + abstract Closeable alloc(); - abstract void method(); + abstract void method(); - public void runtimeExceptionManuallyThrown() throws IOException { - // this code is obviously wrong - // ::error: (required.method.not.called) - Closeable r = alloc(); - if (true) { - throw new RuntimeException(); + public void runtimeExceptionManuallyThrown() throws IOException { + // this code is obviously wrong + // ::error: (required.method.not.called) + Closeable r = alloc(); + if (true) { + throw new RuntimeException(); + } + r.close(); } - r.close(); - } - - public void runtimeExceptionFromMethod() throws IOException { - // method() may throw RuntimeException, so this code is not OK - // ::error: (required.method.not.called) - Closeable r = alloc(); - method(); - r.close(); - } - - // Note that even just constructing an instance of NullPointerException can throw all kinds - // of exceptions: ClassCircularityError, OutOfMemoryError, etc. Even RuntimeException is - // possible in theory. So, to really test what we're trying to test, we have to isolate - // the construction of the exception out here. - static final NullPointerException NPE = new NullPointerException(); - - public void ignoreNPE() throws IOException { - // this code is obviously wrong, but it is allowed because our ignored exceptions list - // includes NullPointerException - Closeable r = alloc(); - if (true) { - throw NPE; + + public void runtimeExceptionFromMethod() throws IOException { + // method() may throw RuntimeException, so this code is not OK + // ::error: (required.method.not.called) + Closeable r = alloc(); + method(); + r.close(); } - r.close(); - } - - static class CustomNPESubtype extends NullPointerException { - static final CustomNPESubtype INSTANCE = new CustomNPESubtype(); - } - - public void doNotIgnoreNPESubtype() throws IOException { - // Only NullPointerException should be ignored, not its subtypes, since the options - // specified "=java.lang.NullPointerException". - // ::error: (required.method.not.called) - Closeable r = alloc(); - if (true) { - throw CustomNPESubtype.INSTANCE; + + // Note that even just constructing an instance of NullPointerException can throw all kinds + // of exceptions: ClassCircularityError, OutOfMemoryError, etc. Even RuntimeException is + // possible in theory. So, to really test what we're trying to test, we have to isolate + // the construction of the exception out here. + static final NullPointerException NPE = new NullPointerException(); + + public void ignoreNPE() throws IOException { + // this code is obviously wrong, but it is allowed because our ignored exceptions list + // includes NullPointerException + Closeable r = alloc(); + if (true) { + throw NPE; + } + r.close(); + } + + static class CustomNPESubtype extends NullPointerException { + static final CustomNPESubtype INSTANCE = new CustomNPESubtype(); + } + + public void doNotIgnoreNPESubtype() throws IOException { + // Only NullPointerException should be ignored, not its subtypes, since the options + // specified "=java.lang.NullPointerException". + // ::error: (required.method.not.called) + Closeable r = alloc(); + if (true) { + throw CustomNPESubtype.INSTANCE; + } + r.close(); } - r.close(); - } } diff --git a/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java b/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java index d1a3324910e..5d1942c30d2 100644 --- a/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java +++ b/checker/tests/resourceleak-extraignoredexceptions/BasicTest.java @@ -1,36 +1,37 @@ -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + abstract class BasicTest { - abstract Closeable alloc(); + abstract Closeable alloc(); - abstract void method(); + abstract void method(); - public void runtimeExceptionManuallyThrown() throws IOException { - // this code is obviously wrong, but RuntimeException is ignored by default - Closeable r = alloc(); - if (true) { - throw new RuntimeException(); + public void runtimeExceptionManuallyThrown() throws IOException { + // this code is obviously wrong, but RuntimeException is ignored by default + Closeable r = alloc(); + if (true) { + throw new RuntimeException(); + } + r.close(); } - r.close(); - } - public void runtimeExceptionFromMethod() throws IOException { - // method() may throw RuntimeException, but RuntimeException is ignored by default - Closeable r = alloc(); - method(); - r.close(); - } + public void runtimeExceptionFromMethod() throws IOException { + // method() may throw RuntimeException, but RuntimeException is ignored by default + Closeable r = alloc(); + method(); + r.close(); + } - public void ignoreIllegalStateException() throws IOException { - // this code is obviously wrong, but it is allowed because our ignored exceptions list - // includes IllegalStateException - Closeable r = alloc(); - if (true) { - throw new IllegalStateException(); + public void ignoreIllegalStateException() throws IOException { + // this code is obviously wrong, but it is allowed because our ignored exceptions list + // includes IllegalStateException + Closeable r = alloc(); + if (true) { + throw new IllegalStateException(); + } + r.close(); } - r.close(); - } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java index 1dae1480586..76de8c024ae 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingServerSockets.java @@ -5,46 +5,47 @@ // @CreatesMustCallFor annotation // and its accompanying logic) has been disabled. -import java.net.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.*; + class ConnectingServerSockets { - static void simple_ss_test(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - ServerSocket s = new ServerSocket(); - s.bind(sa); - } - - static void simple_ss_test2(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - ServerSocket s = new ServerSocket(); - // s.bind(sa); - } - - static void simple_ss_test4(SocketAddress sa, int to) throws Exception { - // :: error: (required.method.not.called) - ServerSocket s = new ServerSocket(); - s.bind(sa, to); - } - - static @MustCall({}) ServerSocket makeUnconnected() throws Exception { - // :: error: (return.type.incompatible) - return new ServerSocket(); - } - - static void simple_ss_test5(SocketAddress sa) throws Exception { - ServerSocket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ss_test6(SocketAddress sa) throws Exception { - ServerSocket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ss_test8(SocketAddress sa, int to) throws Exception { - ServerSocket s = makeUnconnected(); - s.bind(sa, to); - } + static void simple_ss_test(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + ServerSocket s = new ServerSocket(); + s.bind(sa); + } + + static void simple_ss_test2(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + ServerSocket s = new ServerSocket(); + // s.bind(sa); + } + + static void simple_ss_test4(SocketAddress sa, int to) throws Exception { + // :: error: (required.method.not.called) + ServerSocket s = new ServerSocket(); + s.bind(sa, to); + } + + static @MustCall({}) ServerSocket makeUnconnected() throws Exception { + // :: error: (return.type.incompatible) + return new ServerSocket(); + } + + static void simple_ss_test5(SocketAddress sa) throws Exception { + ServerSocket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ss_test6(SocketAddress sa) throws Exception { + ServerSocket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ss_test8(SocketAddress sa, int to) throws Exception { + ServerSocket s = makeUnconnected(); + s.bind(sa, to); + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java index 32b99390943..d57057a1c73 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/ConnectingSockets.java @@ -1,57 +1,58 @@ // a set of test cases that demonstrate that errors are actually issued in appropriate // places when Sockets are connected -import java.net.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.*; + class ConnectingSockets { - static void simple_ns_test(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - s.bind(sa); - } - - static void simple_ns_test2(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - // s.bind(sa); - } - - static void simple_ns_test3(SocketAddress sa) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - s.connect(sa); - } - - static void simple_ns_test4(SocketAddress sa, int to) throws Exception { - // :: error: (required.method.not.called) - Socket s = new Socket(); - s.connect(sa, to); - } - - static @MustCall({}) Socket makeUnconnected() throws Exception { - // :: error: (return.type.incompatible) - return new Socket(); - } - - static void simple_ns_test5(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ns_test6(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ns_test7(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - s.connect(sa); - } - - static void simple_ns_test8(SocketAddress sa, int to) throws Exception { - Socket s = makeUnconnected(); - s.connect(sa, to); - } + static void simple_ns_test(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + s.bind(sa); + } + + static void simple_ns_test2(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + // s.bind(sa); + } + + static void simple_ns_test3(SocketAddress sa) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + s.connect(sa); + } + + static void simple_ns_test4(SocketAddress sa, int to) throws Exception { + // :: error: (required.method.not.called) + Socket s = new Socket(); + s.connect(sa, to); + } + + static @MustCall({}) Socket makeUnconnected() throws Exception { + // :: error: (return.type.incompatible) + return new Socket(); + } + + static void simple_ns_test5(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ns_test6(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ns_test7(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + s.connect(sa); + } + + static void simple_ns_test8(SocketAddress sa, int to) throws Exception { + Socket s = makeUnconnected(); + s.connect(sa, to); + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java b/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java index ec19c425fdf..cdb72d010ca 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/CreatesMustCallForSimpler.java @@ -8,24 +8,24 @@ @InheritableMustCall("a") class CreatesMustCallForSimpler { - @CreatesMustCallFor - void reset() {} - - @CreatesMustCallFor("this") - void resetThis() {} - - void a() {} - - static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { - // :: error: (return.type.incompatible) - return new CreatesMustCallForSimpler(); - } - - static void test1() { - CreatesMustCallForSimpler cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimpler a = cos; - cos.reset(); - @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; - @CalledMethods({}) CreatesMustCallForSimpler c = cos; - } + @CreatesMustCallFor + void reset() {} + + @CreatesMustCallFor("this") + void resetThis() {} + + void a() {} + + static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { + // :: error: (return.type.incompatible) + return new CreatesMustCallForSimpler(); + } + + static void test1() { + CreatesMustCallForSimpler cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimpler a = cos; + cos.reset(); + @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; + @CalledMethods({}) CreatesMustCallForSimpler c = cos; + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java b/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java index b9d626f214d..8b0a5b0edde 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/DifferentSWKeys.java @@ -6,21 +6,21 @@ @SuppressWarnings("required.method.not.called") class DifferentSWKeys { - void test(@Owning @MustCall("foo") Object obj) { - // :: warning: unneeded.suppression - @SuppressWarnings("mustcall") - @MustCall("foo") Object bar = obj; - } + void test(@Owning @MustCall("foo") Object obj) { + // :: warning: unneeded.suppression + @SuppressWarnings("mustcall") + @MustCall("foo") Object bar = obj; + } - void test2(@Owning @MustCall("foo") Object obj) { - // actually needed suppression - @SuppressWarnings("mustcall") - @MustCall({}) Object bar = obj; - } + void test2(@Owning @MustCall("foo") Object obj) { + // actually needed suppression + @SuppressWarnings("mustcall") + @MustCall({}) Object bar = obj; + } - void test3(@Owning @MustCall("foo") Object obj) { - // test that the option-specific suppression key works - @SuppressWarnings("mustcallnocreatesmustcallfor") - @MustCall({}) Object bar = obj; - } + void test3(@Owning @MustCall("foo") Object obj) { + // test that the option-specific suppression key works + @SuppressWarnings("mustcallnocreatesmustcallfor") + @MustCall({}) Object bar = obj; + } } diff --git a/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java b/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java index 2ca752820ef..5d2e4c3858b 100644 --- a/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java +++ b/checker/tests/resourceleak-nocreatesmustcallfor/SocketContainer.java @@ -2,33 +2,34 @@ // This is a modified version of tests/socket/SocketContainer.java // for checking that without CO support we can't assign to non-final owning fields at all. -import java.io.*; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.net.*; + @InheritableMustCall("close") class SocketContainer { - @Owning Socket sock; + @Owning Socket sock; - public SocketContainer(String host, int port) throws Exception { - // Assignments to owning fields should not be permitted. - // :: error: required.method.not.called - sock = new Socket(host, port); - } + public SocketContainer(String host, int port) throws Exception { + // Assignments to owning fields should not be permitted. + // :: error: required.method.not.called + sock = new Socket(host, port); + } - // No missing create obligation error is issued, since CO is disabled... - public void reassign(String host, int port) throws Exception { - sock.close(); - // For the RHS, because the field can't take ownership - // :: error: required.method.not.called - Socket sr = new Socket(host, port); - // No warning for overwriting the field, since it can't take ownership! - sock = sr; - } + // No missing create obligation error is issued, since CO is disabled... + public void reassign(String host, int port) throws Exception { + sock.close(); + // For the RHS, because the field can't take ownership + // :: error: required.method.not.called + Socket sr = new Socket(host, port); + // No warning for overwriting the field, since it can't take ownership! + sock = sr; + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + } } diff --git a/checker/tests/resourceleak-nolightweightownership/ACOwning.java b/checker/tests/resourceleak-nolightweightownership/ACOwning.java index c8c00786890..ddbbbe61103 100644 --- a/checker/tests/resourceleak-nolightweightownership/ACOwning.java +++ b/checker/tests/resourceleak-nolightweightownership/ACOwning.java @@ -8,53 +8,53 @@ class ACOwning { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - Foo makeFoo() { - return new Foo(); - } - - static void takeOwnership(@Owning Foo foo) { - foo.a(); - } - - static void noOwnership(Foo foo) {} - - static void takeOwnershipWrong(@Owning Foo foo) {} - - static @NotOwning Foo getNonOwningFoo() { - return new Foo(); - } - - static void callGetNonOwningFoo() { - // :: error: (required.method.not.called) - getNonOwningFoo(); - } - - static void ownershipInCallee() { - // :: error: (required.method.not.called) - Foo f = new Foo(); - takeOwnership(f); - // :: error: (required.method.not.called) - Foo g = new Foo(); - noOwnership(g); - } - - @Owning - public Foo owningAtReturn() { - return new Foo(); - } - - void owningAtReturnTest() { - // :: error: (required.method.not.called) - Foo f = owningAtReturn(); - } - - void ownershipTest() { - // :: error: (required.method.not.called) - takeOwnership(new Foo()); - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + Foo makeFoo() { + return new Foo(); + } + + static void takeOwnership(@Owning Foo foo) { + foo.a(); + } + + static void noOwnership(Foo foo) {} + + static void takeOwnershipWrong(@Owning Foo foo) {} + + static @NotOwning Foo getNonOwningFoo() { + return new Foo(); + } + + static void callGetNonOwningFoo() { + // :: error: (required.method.not.called) + getNonOwningFoo(); + } + + static void ownershipInCallee() { + // :: error: (required.method.not.called) + Foo f = new Foo(); + takeOwnership(f); + // :: error: (required.method.not.called) + Foo g = new Foo(); + noOwnership(g); + } + + @Owning + public Foo owningAtReturn() { + return new Foo(); + } + + void owningAtReturnTest() { + // :: error: (required.method.not.called) + Foo f = owningAtReturn(); + } + + void ownershipTest() { + // :: error: (required.method.not.called) + takeOwnership(new Foo()); + } } diff --git a/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java b/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java index e7f65924150..946bd3e826a 100644 --- a/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java +++ b/checker/tests/resourceleak-noresourcealiases/MustCallAliasExamples.java @@ -2,61 +2,62 @@ // This version has been modified to expect that @MustCallAlias annotations // are always ignored, as if running with -AnoResourceAliases. +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.io.IOException; import java.net.*; -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; class MustCallAliasExamples { - void test_two_locals(String address) { - Socket socket = null; - try { - socket = new Socket(address, 8000); - // :: error: required.method.not.called - DataInputStream d = new DataInputStream(socket.getInputStream()); - } catch (IOException e) { - - } finally { - closeSocket(socket); + void test_two_locals(String address) { + Socket socket = null; + try { + socket = new Socket(address, 8000); + // :: error: required.method.not.called + DataInputStream d = new DataInputStream(socket.getInputStream()); + } catch (IOException e) { + + } finally { + closeSocket(socket); + } } - } - - // :: error: required.method.not.called - void test_close_wrapper(@Owning InputStream b) throws IOException { - DataInputStream d = new DataInputStream(b); - d.close(); - } - void test_close_nonwrapper(@Owning InputStream b) throws IOException { // :: error: required.method.not.called - DataInputStream d = new DataInputStream(b); - b.close(); - } + void test_close_wrapper(@Owning InputStream b) throws IOException { + DataInputStream d = new DataInputStream(b); + d.close(); + } + + void test_close_nonwrapper(@Owning InputStream b) throws IOException { + // :: error: required.method.not.called + DataInputStream d = new DataInputStream(b); + b.close(); + } - // :: error: required.method.not.called - void test_no_close(@Owning InputStream b) { // :: error: required.method.not.called - DataInputStream d = new DataInputStream(b); - } + void test_no_close(@Owning InputStream b) { + // :: error: required.method.not.called + DataInputStream d = new DataInputStream(b); + } - // :: error: required.method.not.called - void test_no_assign(@Owning InputStream b) { // :: error: required.method.not.called - new DataInputStream( + void test_no_assign(@Owning InputStream b) { // :: error: required.method.not.called - new BufferedInputStream(b)); - } + new DataInputStream( + // :: error: required.method.not.called + new BufferedInputStream(b)); + } - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeSocket(Socket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeSocket(Socket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { + } } - } } diff --git a/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java b/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java index fdba664ba89..12ec04fe639 100644 --- a/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java +++ b/checker/tests/resourceleak-noresourcealiases/MustCallAliasPassthroughLocal.java @@ -2,24 +2,25 @@ // This version has been modified to expect errors, as if running under // -AnoResourceAliases - so @MustCallAlias annotations are ignored. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughLocal extends FilterInputStream { - MustCallAliasPassthroughLocal(File f) throws Exception { - // This is safe - this MCA constructor of FilterInputStream means that the result of this - // constructor - i.e. the caller - is taking ownership of this newly-created output stream. - // :: error: required.method.not.called - super(new FileInputStream(f)); - } + MustCallAliasPassthroughLocal(File f) throws Exception { + // This is safe - this MCA constructor of FilterInputStream means that the result of this + // constructor - i.e. the caller - is taking ownership of this newly-created output stream. + // :: error: required.method.not.called + super(new FileInputStream(f)); + } - static void test(File f) throws Exception { - // :: error: required.method.not.called - new MustCallAliasPassthroughLocal(f); - } + static void test(File f) throws Exception { + // :: error: required.method.not.called + new MustCallAliasPassthroughLocal(f); + } - static void test_ok(File f) throws Exception { - new MustCallAliasPassthroughLocal(f).close(); - } + static void test_ok(File f) throws Exception { + new MustCallAliasPassthroughLocal(f).close(); + } } diff --git a/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java b/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java index 890a5ef2765..17c268604a2 100644 --- a/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java +++ b/checker/tests/resourceleak-permitinitializationleak/InstanceInitializer.java @@ -2,52 +2,53 @@ // In the resourceleak-permitinitializationleak/ directory, it's a test that the // checker is unsound with the -ApermitInitializationLeak command-line argument. -import java.net.Socket; import org.checkerframework.checker.mustcall.qual.*; -class InstanceInitializer { - // :: error: required.method.not.called - private @Owning Socket s; - - private final int DEFAULT_PORT = 5; - private final String DEFAULT_ADDR = "localhost"; +import java.net.Socket; - { - try { - // This assignment is OK, because it's the first assignment. - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { +class InstanceInitializer { + // :: error: required.method.not.called + private @Owning Socket s; + + private final int DEFAULT_PORT = 5; + private final String DEFAULT_ADDR = "localhost"; + + { + try { + // This assignment is OK, because it's the first assignment. + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } } - } - - { - try { - // This assignment is not OK, because it's a reassignment without satisfying the - // mustcall obligations of the previous value of `s`. - // With -ApermitInitializationLeak, the Resource Leak Checker unsoundly permits it. - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { + + { + try { + // This assignment is not OK, because it's a reassignment without satisfying the + // mustcall obligations of the previous value of `s`. + // With -ApermitInitializationLeak, the Resource Leak Checker unsoundly permits it. + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } } - } - { - try { - // :: error: required.method.not.called - Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { + { + try { + // :: error: required.method.not.called + Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } } - } - { - Socket s1 = null; - try { - s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { + { + Socket s1 = null; + try { + s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } + s1.close(); } - s1.close(); - } - public InstanceInitializer() throws Exception { - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } + public InstanceInitializer() throws Exception { + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } } diff --git a/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java b/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java index 954fe8732f3..96e6c96effc 100644 --- a/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java +++ b/checker/tests/resourceleak-permitinitializationleak/SocketContainer.java @@ -2,28 +2,29 @@ // This test exists to check that we gracefully handle assignments 1) // in the constructor and 2) to null. -import java.io.*; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.net.*; + @InheritableMustCall("close") class SocketContainer { - @Owning Socket sock; + @Owning Socket sock; - public SocketContainer(String host, int port) throws Exception { - sock = new Socket(host, port); - try { - sock = new Socket(host, port); - } catch (Exception ignored) { + public SocketContainer(String host, int port) throws Exception { + sock = new Socket(host, port); + try { + sock = new Socket(host, port); + } catch (Exception ignored) { + } } - } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - // It's okay to assign a field to null after its obligations have been fulfilled, - // without inducing a reset. - sock = null; - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + // It's okay to assign a field to null after its obligations have been fulfilled, + // without inducing a reset. + sock = null; + } } diff --git a/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java b/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java index 19e69eae1a1..9016c5d1ebe 100644 --- a/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java +++ b/checker/tests/resourceleak-permitstaticowning/StaticOwningField.java @@ -1,63 +1,64 @@ -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintStream; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; + @InheritableMustCall("close") class StaticOwningField implements Closeable { - // Instance field + // Instance field - private @Owning @MustCall("close") PrintStream ps_instance; + private @Owning @MustCall("close") PrintStream ps_instance; - @CreatesMustCallFor("this") - void m_instance() throws IOException { - ps_instance.close(); - ps_instance = new PrintStream("filename.txt"); - } + @CreatesMustCallFor("this") + void m_instance() throws IOException { + ps_instance.close(); + ps_instance = new PrintStream("filename.txt"); + } - @EnsuresCalledMethods(value = "ps_instance", methods = "close") - @Override - public void close() { - ps_instance.close(); - } + @EnsuresCalledMethods(value = "ps_instance", methods = "close") + @Override + public void close() { + ps_instance.close(); + } - // Static field + // Static field - private static @Owning @MustCall("close") PrintStream ps_static; + private static @Owning @MustCall("close") PrintStream ps_static; - static void m_static() throws IOException { - ps_static.close(); - ps_static = new PrintStream("filename.txt"); - } + static void m_static() throws IOException { + ps_static.close(); + ps_static = new PrintStream("filename.txt"); + } - private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = - newPrintStreamWithoutExceptions(); + private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = + newPrintStreamWithoutExceptions(); - private static @Owning @MustCall("close") PrintStream ps_static_initialized2; + private static @Owning @MustCall("close") PrintStream ps_static_initialized2; - static { - ps_static_initialized2 = newPrintStreamWithoutExceptions(); - } + static { + ps_static_initialized2 = newPrintStreamWithoutExceptions(); + } - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = - newPrintStreamWithoutExceptions(); + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = + newPrintStreamWithoutExceptions(); - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; - static { - ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); - } + static { + ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); + } - public static PrintStream newPrintStreamWithoutExceptions() { - try { - return new PrintStream("filename.txt"); - } catch (Exception e) { - throw new Error(e); + public static PrintStream newPrintStreamWithoutExceptions() { + try { + return new PrintStream("filename.txt"); + } catch (Exception e) { + throw new Error(e); + } } - } } diff --git a/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java b/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java index 151693e7dae..6bddb48357c 100644 --- a/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java +++ b/checker/tests/resourceleak-permitstaticowning/StaticOwningFieldOtherClass.java @@ -1,20 +1,21 @@ +import org.checkerframework.checker.mustcall.qual.Owning; + import java.io.FileWriter; import java.io.IOException; import java.io.UncheckedIOException; -import org.checkerframework.checker.mustcall.qual.Owning; public class StaticOwningFieldOtherClass {} abstract class HasStaticOwningField { - public static @Owning FileWriter log = null; + public static @Owning FileWriter log = null; } class TestUtils { - public static void setLog(String filename) { - try { - HasStaticOwningField.log = new FileWriter(filename); - } catch (IOException ioe) { - throw new UncheckedIOException("Cannot write file " + filename, ioe); + public static void setLog(String filename) { + try { + HasStaticOwningField.log = new FileWriter(filename); + } catch (IOException ioe) { + throw new UncheckedIOException("Cannot write file " + filename, ioe); + } } - } } diff --git a/checker/tests/resourceleak/ACExceptionalExitPointTest.java b/checker/tests/resourceleak/ACExceptionalExitPointTest.java index efd7d23c556..d547df21583 100644 --- a/checker/tests/resourceleak/ACExceptionalExitPointTest.java +++ b/checker/tests/resourceleak/ACExceptionalExitPointTest.java @@ -4,36 +4,36 @@ class ACExceptionalExitPointTest { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; + @This Foo b() { + return this; + } + + void c() {} + } + + Foo makeFoo() { + return new Foo(); + } + + @CalledMethods({"a"}) Foo makeFoo2() { + Foo f = new Foo(); + f.a(); + return f; + } + + void exceptionalExitWrong() throws Exception { + // :: error: required.method.not.called + Foo fw = makeFoo(); + throw new Exception(); } - void c() {} - } - - Foo makeFoo() { - return new Foo(); - } - - @CalledMethods({"a"}) Foo makeFoo2() { - Foo f = new Foo(); - f.a(); - return f; - } - - void exceptionalExitWrong() throws Exception { - // :: error: required.method.not.called - Foo fw = makeFoo(); - throw new Exception(); - } - - void exceptionalExitCorrect() throws Exception { - Foo fw = new Foo(); - fw.a(); - throw new Exception(); - } + void exceptionalExitCorrect() throws Exception { + Foo fw = new Foo(); + fw.a(); + throw new Exception(); + } } diff --git a/checker/tests/resourceleak/ACMethodInvocationTest.java b/checker/tests/resourceleak/ACMethodInvocationTest.java index ca59ef15f4c..5d1850e24d7 100644 --- a/checker/tests/resourceleak/ACMethodInvocationTest.java +++ b/checker/tests/resourceleak/ACMethodInvocationTest.java @@ -4,95 +4,95 @@ class ACMethodInvocationTest { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; + @This Foo b() { + return this; + } + + void c() {} + } + + @Owning + Foo makeFoo() { + return new Foo(); + } + + @CalledMethods({"a"}) Foo makeFooFinalize() { + Foo f = new Foo(); + f.a(); + return f; + } + + @Owning + @CalledMethods({"b"}) Foo makeFooFinalize2() { + Foo f = new Foo(); + f.b(); + return f; + } + + void CallMethodsInSequence() { + makeFoo().a(); + } + + void CallMethodsInSequence2() { + makeFoo().b().a(); + } + + void testFluentAPIWrong() { + // :: error: required.method.not.called + makeFoo().b(); + } + + void testFluentAPIWrong2() { + // :: error: required.method.not.called + makeFoo(); + } + + void invokeMethodWithCallA() { + makeFooFinalize(); + } + + void invokeMethodWithCallBWrong() { + // :: error: required.method.not.called + makeFooFinalize2(); + } + + void invokeMethodAndCallCWrong() { + // :: error: required.method.not.called + makeFoo().c(); + } + + Foo returnMakeFoo() { + return makeFoo(); + } + + Foo testField1; + Foo testField2; + Foo testField3; + + void testStoringInField() { + // :: error: required.method.not.called + testField1 = makeFoo(); + // :: error: required.method.not.called + testField2 = new Foo(); + + testField3 = makeFooFinalize(); } - void c() {} - } - - @Owning - Foo makeFoo() { - return new Foo(); - } - - @CalledMethods({"a"}) Foo makeFooFinalize() { - Foo f = new Foo(); - f.a(); - return f; - } - - @Owning - @CalledMethods({"b"}) Foo makeFooFinalize2() { - Foo f = new Foo(); - f.b(); - return f; - } - - void CallMethodsInSequence() { - makeFoo().a(); - } - - void CallMethodsInSequence2() { - makeFoo().b().a(); - } - - void testFluentAPIWrong() { - // :: error: required.method.not.called - makeFoo().b(); - } - - void testFluentAPIWrong2() { - // :: error: required.method.not.called - makeFoo(); - } - - void invokeMethodWithCallA() { - makeFooFinalize(); - } - - void invokeMethodWithCallBWrong() { - // :: error: required.method.not.called - makeFooFinalize2(); - } - - void invokeMethodAndCallCWrong() { - // :: error: required.method.not.called - makeFoo().c(); - } - - Foo returnMakeFoo() { - return makeFoo(); - } - - Foo testField1; - Foo testField2; - Foo testField3; - - void testStoringInField() { - // :: error: required.method.not.called - testField1 = makeFoo(); - // :: error: required.method.not.called - testField2 = new Foo(); - - testField3 = makeFooFinalize(); - } - - void tryCatchFinally() { - Foo f = null; - try { - f = new Foo(); - try { - throw new RuntimeException(); - } catch (Exception e) { - - } - } finally { - f.a(); + void tryCatchFinally() { + Foo f = null; + try { + f = new Foo(); + try { + throw new RuntimeException(); + } catch (Exception e) { + + } + } finally { + f.a(); + } } - } } diff --git a/checker/tests/resourceleak/ACOwning.java b/checker/tests/resourceleak/ACOwning.java index 1263c515100..2b5f0da9363 100644 --- a/checker/tests/resourceleak/ACOwning.java +++ b/checker/tests/resourceleak/ACOwning.java @@ -3,79 +3,79 @@ class ACOwning { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - Foo makeFoo() { - return new Foo(); - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - static void takeOwnership(@Owning Foo foo, Foo f) { - foo.a(); - } + Foo makeFoo() { + return new Foo(); + } - static void noOwnership(Foo foo) {} + static void takeOwnership(@Owning Foo foo, Foo f) { + foo.a(); + } - // :: error: required.method.not.called - static void takeOwnershipWrong(@Owning Foo foo) {} + static void noOwnership(Foo foo) {} - static @NotOwning Foo getNonOwningFoo() { // :: error: required.method.not.called - return new Foo(); - } + static void takeOwnershipWrong(@Owning Foo foo) {} - static void callGetNonOwningFoo() { - getNonOwningFoo(); - } + static @NotOwning Foo getNonOwningFoo() { + // :: error: required.method.not.called + return new Foo(); + } - static void ownershipInCallee() { - Foo f = new Foo(); - // :: error: required.method.not.called - takeOwnership(f, new Foo()); - // :: error: required.method.not.called - Foo g = new Foo(); - noOwnership(g); - } - - // make sure enum doesn't crash things - static enum TestEnum { - CASE1, - CASE2, - CASE3 - } - - @Owning - public Foo owningAtReturn() { - return new Foo(); - } - - void owningAtReturnTest() { - // :: error: required.method.not.called - Foo f = owningAtReturn(); - } + static void callGetNonOwningFoo() { + getNonOwningFoo(); + } - void ownershipTest() { - // :: error: required.method.not.called - takeOwnership(new Foo(), makeFoo()); - } + static void ownershipInCallee() { + Foo f = new Foo(); + // :: error: required.method.not.called + takeOwnership(f, new Foo()); + // :: error: required.method.not.called + Foo g = new Foo(); + noOwnership(g); + } + + // make sure enum doesn't crash things + static enum TestEnum { + CASE1, + CASE2, + CASE3 + } - @InheritableMustCall({}) - // :: error: super.invocation.invalid :: error: inconsistent.mustcall.subtype - private class SubFoo extends Foo { + @Owning + public Foo owningAtReturn() { + return new Foo(); + } - void test() { - SubFoo f = new SubFoo(); + void owningAtReturnTest() { + // :: error: required.method.not.called + Foo f = owningAtReturn(); } - void test2() { - // :: error: required.method.not.called - Foo f = new Foo(); + void ownershipTest() { + // :: error: required.method.not.called + takeOwnership(new Foo(), makeFoo()); } - void test3() { - Foo f = new SubFoo(); + @InheritableMustCall({}) + // :: error: super.invocation.invalid :: error: inconsistent.mustcall.subtype + private class SubFoo extends Foo { + + void test() { + SubFoo f = new SubFoo(); + } + + void test2() { + // :: error: required.method.not.called + Foo f = new Foo(); + } + + void test3() { + Foo f = new SubFoo(); + } } - } } diff --git a/checker/tests/resourceleak/ACRegularExitPointTest.java b/checker/tests/resourceleak/ACRegularExitPointTest.java index 39d18f46dad..16f19a8693a 100644 --- a/checker/tests/resourceleak/ACRegularExitPointTest.java +++ b/checker/tests/resourceleak/ACRegularExitPointTest.java @@ -1,308 +1,309 @@ -import java.io.IOException; -import java.util.function.Function; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; +import java.io.IOException; +import java.util.function.Function; + class ACRegularExitPointTest { - @InheritableMustCall("a") - class Foo { - void a() {} - - @This Foo b() { - return this; - } - - void c(@CalledMethods("a") Foo this) {} - } - - class SubFoo extends Foo {} - - Foo makeFoo() { - return new Foo(); - } - - @CalledMethods("a") Foo makeFooCallA() { - Foo f = new Foo(); - f.a(); - return f; - } - - @EnsuresCalledMethods(value = "#1", methods = "a") - void callA(Foo f) { - f.a(); - } - - void makeFooFinalize() { - Foo f = new Foo(); - f.a(); - } - - void makeFooFinalizeWrong() { - Foo m; - // :: error: required.method.not.called - m = new Foo(); - // :: error: required.method.not.called - Foo f = new Foo(); - f.b(); - } - - void testStoringInLocalWrong() { - // :: error: required.method.not.called - Foo foo = makeFoo(); - } - - void testStoringInLocalWrong2() { - Foo f; - // :: error: required.method.not.called - f = makeFoo(); - } - - void testStoringInLocal() { - Foo foo = makeFooCallA(); - } - - void testStoringInLocalWrong3() { - // :: error: required.method.not.called - Foo foo = new Foo(); - } - - void emptyFuncWithFormalPram(Foo f) {} - - void innerFunc(Foo f) { - Runnable r = - new Runnable() { - public void run() { - Foo f; - } - ; - }; - r.run(); - } - - void innerFuncWrong(Foo f) { - Runnable r = - new Runnable() { - public void run() { + @InheritableMustCall("a") + class Foo { + void a() {} + + @This Foo b() { + return this; + } + + void c(@CalledMethods("a") Foo this) {} + } + + class SubFoo extends Foo {} + + Foo makeFoo() { + return new Foo(); + } + + @CalledMethods("a") Foo makeFooCallA() { + Foo f = new Foo(); + f.a(); + return f; + } + + @EnsuresCalledMethods(value = "#1", methods = "a") + void callA(Foo f) { + f.a(); + } + + void makeFooFinalize() { + Foo f = new Foo(); + f.a(); + } + + void makeFooFinalizeWrong() { + Foo m; + // :: error: required.method.not.called + m = new Foo(); + // :: error: required.method.not.called + Foo f = new Foo(); + f.b(); + } + + void testStoringInLocalWrong() { + // :: error: required.method.not.called + Foo foo = makeFoo(); + } + + void testStoringInLocalWrong2() { + Foo f; + // :: error: required.method.not.called + f = makeFoo(); + } + + void testStoringInLocal() { + Foo foo = makeFooCallA(); + } + + void testStoringInLocalWrong3() { + // :: error: required.method.not.called + Foo foo = new Foo(); + } + + void emptyFuncWithFormalPram(Foo f) {} + + void innerFunc(Foo f) { + Runnable r = + new Runnable() { + public void run() { + Foo f; + } + ; + }; + r.run(); + } + + void innerFuncWrong(Foo f) { + Runnable r = + new Runnable() { + public void run() { + // :: error: required.method.not.called + Foo g = new Foo(); + } + ; + }; + r.run(); + } + + void innerFunc2(Foo f) { + Runnable r = + new Runnable() { + public void run() { + Foo g = makeFoo(); + g.a(); + } + ; + }; + r.run(); + } + + void innerfunc3() { + + Foo f = makeFoo(); + f.a(); + Function<@MustCall Foo, @CalledMethods("a") @MustCall Foo> innerfunc = + st -> { + // :: error: required.method.not.called + Foo fn1 = new Foo(); + Foo fn2 = makeFoo(); + fn2.a(); + // The need for this cast is undesirable, but is a consequence of our approach + // to generic types. In this case, this cast is clearly safe (a() has already + // been called, so the obligation is satisfied on the returned value, as + // intended). + // :: warning: cast.unsafe + return ((@MustCall Foo) fn2); + }; + + innerfunc.apply(f); + } + + void ifElse(boolean b) { + if (b) { + Foo f1 = new Foo(); + f1.a(); + } else { + // :: error: required.method.not.called + Foo f2 = new Foo(); + } + } + + Foo ifElseWithReturnExit(boolean b, boolean c) { + // :: error: required.method.not.called + Foo f1 = makeFoo(); + // :: error: required.method.not.called + Foo f3 = new Foo(); + // :: error: required.method.not.called + Foo f4 = new Foo(); + + if (b) { + // :: error: required.method.not.called + Foo f2 = new Foo(); + if (c) { + f4.a(); + } else { + f4.b(); + } + return f1; + } else { + // :: error: required.method.not.called + Foo f2 = new Foo(); + f2 = new Foo(); + f2.a(); + } + return f3; + } + + void ifElseWithDeclaration(boolean b) { + Foo f1; + Foo f2; + if (b) { + f1 = new Foo(); + f1.a(); + } else { + // :: error: required.method.not.called + f2 = new Foo(); + } + } + + void ifElseWithInitialization(boolean b) { + // :: error: required.method.not.called + Foo f2 = new Foo(); + Foo f11 = null; + if (b) { + f11 = makeFoo(); + f11.a(); + } else { + // :: error: required.method.not.called + f2 = new Foo(); + } + } + + void ifWithInitialization(boolean b) { + // :: error: required.method.not.called + Foo f1 = new Foo(); + // :: error: required.method.not.called + Foo f2 = new Foo(); + if (b) { + f1.a(); + } + } + + void variableGoesOutOfScope(boolean b) { + if (b) { + Foo f1 = new Foo(); + f1.a(); + } + } + + void ifWithNullInitialization(boolean b) { + Foo f1 = null; + Foo f2 = null; + if (b) { + f1 = new Foo(); + f1.a(); + } else { + // :: error: required.method.not.called + f2 = new Foo(); + } + } + + void variableInitializedWithNull() { + Foo f = null; + } + + void testLoop() { + Foo f = null; + while (true) { // :: error: required.method.not.called - Foo g = new Foo(); - } - ; - }; - r.run(); - } - - void innerFunc2(Foo f) { - Runnable r = - new Runnable() { - public void run() { - Foo g = makeFoo(); - g.a(); - } - ; - }; - r.run(); - } - - void innerfunc3() { - - Foo f = makeFoo(); - f.a(); - Function<@MustCall Foo, @CalledMethods("a") @MustCall Foo> innerfunc = - st -> { - // :: error: required.method.not.called - Foo fn1 = new Foo(); - Foo fn2 = makeFoo(); - fn2.a(); - // The need for this cast is undesirable, but is a consequence of our approach - // to generic types. In this case, this cast is clearly safe (a() has already - // been called, so the obligation is satisfied on the returned value, as - // intended). - // :: warning: cast.unsafe - return ((@MustCall Foo) fn2); - }; - - innerfunc.apply(f); - } - - void ifElse(boolean b) { - if (b) { - Foo f1 = new Foo(); - f1.a(); - } else { - // :: error: required.method.not.called - Foo f2 = new Foo(); - } - } - - Foo ifElseWithReturnExit(boolean b, boolean c) { - // :: error: required.method.not.called - Foo f1 = makeFoo(); - // :: error: required.method.not.called - Foo f3 = new Foo(); - // :: error: required.method.not.called - Foo f4 = new Foo(); - - if (b) { - // :: error: required.method.not.called - Foo f2 = new Foo(); - if (c) { - f4.a(); - } else { - f4.b(); - } - return f1; - } else { - // :: error: required.method.not.called - Foo f2 = new Foo(); - f2 = new Foo(); - f2.a(); - } - return f3; - } - - void ifElseWithDeclaration(boolean b) { - Foo f1; - Foo f2; - if (b) { - f1 = new Foo(); - f1.a(); - } else { - // :: error: required.method.not.called - f2 = new Foo(); - } - } - - void ifElseWithInitialization(boolean b) { - // :: error: required.method.not.called - Foo f2 = new Foo(); - Foo f11 = null; - if (b) { - f11 = makeFoo(); - f11.a(); - } else { - // :: error: required.method.not.called - f2 = new Foo(); - } - } - - void ifWithInitialization(boolean b) { - // :: error: required.method.not.called - Foo f1 = new Foo(); - // :: error: required.method.not.called - Foo f2 = new Foo(); - if (b) { - f1.a(); - } - } - - void variableGoesOutOfScope(boolean b) { - if (b) { - Foo f1 = new Foo(); - f1.a(); - } - } - - void ifWithNullInitialization(boolean b) { - Foo f1 = null; - Foo f2 = null; - if (b) { - f1 = new Foo(); - f1.a(); - } else { - // :: error: required.method.not.called - f2 = new Foo(); - } - } - - void variableInitializedWithNull() { - Foo f = null; - } - - void testLoop() { - Foo f = null; - while (true) { - // :: error: required.method.not.called - f = new Foo(); - } - } - - void overWrittingVarInLoop() { - // :: error: required.method.not.called - Foo f = new Foo(); - while (true) { - // :: error: required.method.not.called - f = new Foo(); - } - } - - void loopWithNestedBranches(boolean b) { - Foo frodo = null; - while (true) { - if (b) { + f = new Foo(); + } + } + + void overWrittingVarInLoop() { // :: error: required.method.not.called - frodo = new Foo(); - } else { - // this is a known false positive, due to lack of path sensitivity in the - // Called Methods Checker + Foo f = new Foo(); + while (true) { + // :: error: required.method.not.called + f = new Foo(); + } + } + + void loopWithNestedBranches(boolean b) { + Foo frodo = null; + while (true) { + if (b) { + // :: error: required.method.not.called + frodo = new Foo(); + } else { + // this is a known false positive, due to lack of path sensitivity in the + // Called Methods Checker + // :: error: required.method.not.called + frodo = new Foo(); + frodo.a(); + } + } + } + + void replaceVarWithNull(boolean b, boolean c) { // :: error: required.method.not.called - frodo = new Foo(); - frodo.a(); - } - } - } - - void replaceVarWithNull(boolean b, boolean c) { - // :: error: required.method.not.called - Foo f = new Foo(); - if (b) { - f = null; - } else if (c) { - f = null; - } else { - - } - } - - void ownershipTransfer() { - Foo f1 = new Foo(); - Foo f2 = f1; - Foo f3 = f2.b(); - f3.a(); - } - - void ownershipTransfer2() { - Foo f1 = null; - Foo f2 = f1; - } - - void testECM() { - Foo f = new Foo(); - callA(f); - } - - void testFinallyBlock(boolean b) { - Foo f = null; - try { - f = new Foo(); - if (true) { - throw new IOException(); - } - } catch (IOException e) { - - } finally { - f.a(); - } - } - - void testSubFoo() { - // :: error: required.method.not.called - Foo f = new SubFoo(); - } - - void testSubFoo2() { - // :: error: required.method.not.called - SubFoo f = new SubFoo(); - } + Foo f = new Foo(); + if (b) { + f = null; + } else if (c) { + f = null; + } else { + + } + } + + void ownershipTransfer() { + Foo f1 = new Foo(); + Foo f2 = f1; + Foo f3 = f2.b(); + f3.a(); + } + + void ownershipTransfer2() { + Foo f1 = null; + Foo f2 = f1; + } + + void testECM() { + Foo f = new Foo(); + callA(f); + } + + void testFinallyBlock(boolean b) { + Foo f = null; + try { + f = new Foo(); + if (true) { + throw new IOException(); + } + } catch (IOException e) { + + } finally { + f.a(); + } + } + + void testSubFoo() { + // :: error: required.method.not.called + Foo f = new SubFoo(); + } + + void testSubFoo2() { + // :: error: required.method.not.called + SubFoo f = new SubFoo(); + } } diff --git a/checker/tests/resourceleak/ACSocketTest.java b/checker/tests/resourceleak/ACSocketTest.java index 9686a4a9a15..ca7cb03e350 100644 --- a/checker/tests/resourceleak/ACSocketTest.java +++ b/checker/tests/resourceleak/ACSocketTest.java @@ -1,3 +1,7 @@ +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; +import org.checkerframework.common.returnsreceiver.qual.*; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -6,439 +10,444 @@ import java.nio.channels.*; import java.util.*; import java.util.concurrent.atomic.AtomicReference; + import javax.net.ssl.*; -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; -import org.checkerframework.common.returnsreceiver.qual.*; public class ACSocketTest { - @Owning - Socket makeSocket(String address, int port) { - - try { - Socket socket = new Socket(address, port); - return socket; - } catch (IOException i) { - return null; - } - } - - void basicTest(String address, int port) { - try { - // :: error: required.method.not.called - Socket socket2 = new Socket(address, port); - Socket specialSocket = new Socket(address, port); - specialSocket.close(); - } catch (IOException i) { - } - } - - void tryWithResourcesTest(String address, int port) throws IOException { - try (Socket s = new Socket(address, port)) {} - } - - void callMakeSocketAndClose(String address, int port) { - Socket socket = makeSocket(address, port); - try { - socket.close(); - } catch (IOException i) { - } - } - - void callMakeSocket(String address, int port) { - // :: error: required.method.not.called - Socket socket = makeSocket(address, port); - } - - void ifElseWithDeclaration(String address, int port, boolean b) { - Socket s1; - Socket s2; - try { - if (b) { - s1 = new Socket(address, port); - s1.close(); - } else { + @Owning + Socket makeSocket(String address, int port) { + + try { + Socket socket = new Socket(address, port); + return socket; + } catch (IOException i) { + return null; + } + } + + void basicTest(String address, int port) { + try { + // :: error: required.method.not.called + Socket socket2 = new Socket(address, port); + Socket specialSocket = new Socket(address, port); + specialSocket.close(); + } catch (IOException i) { + } + } + + void tryWithResourcesTest(String address, int port) throws IOException { + try (Socket s = new Socket(address, port)) {} + } + + void callMakeSocketAndClose(String address, int port) { + Socket socket = makeSocket(address, port); + try { + socket.close(); + } catch (IOException i) { + } + } + + void callMakeSocket(String address, int port) { // :: error: required.method.not.called - s2 = new Socket(address, port + 1); - } - } catch (IOException i) { + Socket socket = makeSocket(address, port); + } + void ifElseWithDeclaration(String address, int port, boolean b) { + Socket s1; + Socket s2; + try { + if (b) { + s1 = new Socket(address, port); + s1.close(); + } else { + // :: error: required.method.not.called + s2 = new Socket(address, port + 1); + } + } catch (IOException i) { + + } } - } - void testLoop(String address, int port) { - Socket s = null; - while (true) { - try { - s = new Socket(address, port); - s.close(); - } catch (IOException e) { + void testLoop(String address, int port) { + Socket s = null; + while (true) { + try { + s = new Socket(address, port); + s.close(); + } catch (IOException e) { - } + } + } } - } - void overWrittingVarInLoop(String address, int port) { - // :: error: required.method.not.called - Socket s = makeSocket(address, port); - while (true) { - try { + void overWrittingVarInLoop(String address, int port) { // :: error: required.method.not.called - s = new Socket(address, port); - } catch (IOException e) { + Socket s = makeSocket(address, port); + while (true) { + try { + // :: error: required.method.not.called + s = new Socket(address, port); + } catch (IOException e) { + + } + } + } - } + void loopWithNestedBranches(String address, int port, boolean b) { + Socket s = null; + while (true) { + if (b) { + // :: error: required.method.not.called + s = makeSocket(address, port); + } else { + // :: error: required.method.not.called + s = makeSocket(address, port); + } + } } - } - void loopWithNestedBranches(String address, int port, boolean b) { - Socket s = null; - while (true) { - if (b) { + void replaceVarWithNull(String address, int port, boolean b, boolean c) { + Socket s; + try { + // :: error: required.method.not.called + s = new Socket(address, port); + } catch (IOException e) { + + } + if (b) { + s = null; + } else if (c) { + s = null; + } else { + + } + } + + void ownershipTransfer(String address, int port) { + Socket s1 = null; + try { + // :: error: required.method.not.called + s1 = new Socket(address, port); + } catch (IOException e) { + + } + // It is equally correct to report an error here. + Socket s2 = s1; + if (true) { + closeSocket(s2); + } + } + + void test(String address, int port) { + try { + // :: error: required.method.not.called + Socket socket = new Socket(address, 80); + PrintStream out = new PrintStream(socket.getOutputStream()); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected Socket sock; + + // This type.argument error is undesirable, but is a necessary consquence of our approach to + // handling generics in the Must Call Checker, which prevents containers from having + // @MustCall("close") type arguments without errors (in exchange for avoiding many false + // positives on containers that do not have must-call obligations on their component types). + // :: error: type.argument.type.incompatible + void connectToLeader(AtomicReference socket) throws IOException { // :: error: required.method.not.called - s = makeSocket(address, port); - } else { + if (socket.get() == null) { + throw new IOException("Failed connect to "); + } else { + // :: error: required.method.not.called + sock = socket.get(); + } + } + + Socket createSocket(boolean b, String address, int port) throws IOException { + Socket sock; + if (b) { + // :: error: required.method.not.called + sock = new Socket(address, port); + } else { + // :: error: required.method.not.called + sock = new Socket(address, port); + } + + sock.setSoTimeout(10000); + closeSocket(sock); + return sock; + } + + // @EnsuresCalledMethodsIf(expression = "#1", methods = {"close"}, result = true) + // void closeSocket(Socket sock) { + //// if (sock == null) { + //// return; + //// } + // + // try { + // sock.close(); + // } catch (IOException ie) { + // + // } + // } + + public static void ruok(String host, int port) { + Socket s = null; + try { + s = new Socket(host, port); + } catch (IOException e) { + + } finally { + + try { + s.close(); + } catch (IOException e) { + + } + } + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeSocket(Socket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { + + } + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeServerSocket(ServerSocket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { + + } + } + + void useCloseSocket(String address, int port) throws IOException { + Socket sock = new Socket(address, port); + Socket s = getSocket(sock); + closeSocket(sock); + } + + void setSockOpts(Socket sock) throws SocketException { + sock.setTcpNoDelay(true); + sock.setKeepAlive(true); + sock.setSoTimeout(1000); + } + + void initiateConnection( + SocketAddress endpoint, int timeout, SSLContext context, final Long sid) { + Socket sock = null; + try { + sock = context.getSocketFactory().createSocket(); + setSockOpts(sock); + sock.connect(endpoint, timeout); + if (sock instanceof SSLSocket) { + SSLSocket sslSock = (SSLSocket) sock; + sslSock.startHandshake(); + } + } catch (ClassCastException e) { + closeSocket(sock); + return; + } catch (IOException e) { + closeSocket(sock); + return; + } + + try { + startConnection(sock); + } catch (IOException e) { + closeSocket(sock); + } + } + + private boolean startConnection(@Owning Socket s) throws IOException { + closeSocket(s); + return true; + } + + private boolean startConnection(@Owning SSLSocket s) throws IOException { + closeSocket(s); + return true; + } + + @MustCall({"close"}) class PrependableSocket extends Socket { + + public PrependableSocket(SocketImpl base) throws IOException { + super(base); + } + } + + void makePrependableSocket() throws IOException { // :: error: required.method.not.called - s = makeSocket(address, port); - } - } - } - - void replaceVarWithNull(String address, int port, boolean b, boolean c) { - Socket s; - try { - // :: error: required.method.not.called - s = new Socket(address, port); - } catch (IOException e) { - - } - if (b) { - s = null; - } else if (c) { - s = null; - } else { - - } - } - - void ownershipTransfer(String address, int port) { - Socket s1 = null; - try { - // :: error: required.method.not.called - s1 = new Socket(address, port); - } catch (IOException e) { - - } - // It is equally correct to report an error here. - Socket s2 = s1; - if (true) { - closeSocket(s2); - } - } - - void test(String address, int port) { - try { - // :: error: required.method.not.called - Socket socket = new Socket(address, 80); - PrintStream out = new PrintStream(socket.getOutputStream()); - BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - in.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - protected Socket sock; - - // This type.argument error is undesirable, but is a necessary consquence of our approach to - // handling generics in the Must Call Checker, which prevents containers from having - // @MustCall("close") type arguments without errors (in exchange for avoiding many false - // positives on containers that do not have must-call obligations on their component types). - // :: error: type.argument.type.incompatible - void connectToLeader(AtomicReference socket) throws IOException { - // :: error: required.method.not.called - if (socket.get() == null) { - throw new IOException("Failed connect to "); - } else { - // :: error: required.method.not.called - sock = socket.get(); - } - } - - Socket createSocket(boolean b, String address, int port) throws IOException { - Socket sock; - if (b) { - // :: error: required.method.not.called - sock = new Socket(address, port); - } else { - // :: error: required.method.not.called - sock = new Socket(address, port); - } - - sock.setSoTimeout(10000); - closeSocket(sock); - return sock; - } - - // @EnsuresCalledMethodsIf(expression = "#1", methods = {"close"}, result = true) - // void closeSocket(Socket sock) { - //// if (sock == null) { - //// return; - //// } - // - // try { - // sock.close(); - // } catch (IOException ie) { - // - // } - // } - - public static void ruok(String host, int port) { - Socket s = null; - try { - s = new Socket(host, port); - } catch (IOException e) { - - } finally { - - try { - s.close(); - } catch (IOException e) { - - } - } - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeSocket(Socket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { - - } - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeServerSocket(ServerSocket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { - - } - } - - void useCloseSocket(String address, int port) throws IOException { - Socket sock = new Socket(address, port); - Socket s = getSocket(sock); - closeSocket(sock); - } - - void setSockOpts(Socket sock) throws SocketException { - sock.setTcpNoDelay(true); - sock.setKeepAlive(true); - sock.setSoTimeout(1000); - } - - void initiateConnection(SocketAddress endpoint, int timeout, SSLContext context, final Long sid) { - Socket sock = null; - try { - sock = context.getSocketFactory().createSocket(); - setSockOpts(sock); - sock.connect(endpoint, timeout); - if (sock instanceof SSLSocket) { - SSLSocket sslSock = (SSLSocket) sock; - sslSock.startHandshake(); - } - } catch (ClassCastException e) { - closeSocket(sock); - return; - } catch (IOException e) { - closeSocket(sock); - return; - } - - try { - startConnection(sock); - } catch (IOException e) { - closeSocket(sock); - } - } - - private boolean startConnection(@Owning Socket s) throws IOException { - closeSocket(s); - return true; - } - - private boolean startConnection(@Owning SSLSocket s) throws IOException { - closeSocket(s); - return true; - } - - @MustCall({"close"}) class PrependableSocket extends Socket { - - public PrependableSocket(SocketImpl base) throws IOException { - super(base); - } - } - - void makePrependableSocket() throws IOException { - // :: error: required.method.not.called - final PrependableSocket prependableSocket = new PrependableSocket(null); - } - - // private void acceptConnections() { - // int numRetries = 0; - // Socket client = null; - // - // while ((!shutdown) && (portBindMaxRetry == 0 || numRetries < portBindMaxRetry)) { - // try { - // serverSocket = createNewServerSocket(); - // LOG.info("{} is accepting connections now, my election bind port: {}", - // QuorumCnxManager.this.mySid, address.toString()); - // while (!shutdown) { - // try { - // client = serverSocket.accept(); - // setSockOpts(client); - // LOG.info("Received connection request from {}", - // client.getRemoteSocketAddress()); - // // Receive and handle the connection request - // // asynchronously if the quorum sasl authentication is - // // enabled. This is required because sasl server - // // authentication process may take few seconds to finish, - // // this may delay next peer connection requests. - // if (quorumSaslAuthEnabled) { - // receiveConnectionAsync(client); - // } else { - // receiveConnection(client); - // } - // numRetries = 0; - // } catch (SocketTimeoutException e) { - // LOG.warn("The socket is listening for the election accepted " - // + "and it timed out unexpectedly, but will retry." - // + "see ZOOKEEPER-2836"); - // } - // } - // } catch (IOException e) { - // if (shutdown) { - // break; - // } - // - // LOG.error("Exception while listening", e); - // - // if (e instanceof SocketException) { - // socketException.set(true); - // } - // - // numRetries++; - // try { - // close(); - // Thread.sleep(1000); - // } catch (IOException ie) { - // LOG.error("Error closing server socket", ie); - // } catch (InterruptedException ie) { - // LOG.error("Interrupted while sleeping. Ignoring exception", ie); - // } - // closeSocket(client); - // } - // } - // if (!shutdown) { - // LOG.error( - // "Leaving listener thread for address {} after {} errors. Use {} property - // to - // increase retry count.", - // formatInetAddr(address), - // numRetries, - // ELECTION_PORT_BIND_RETRY); - // } - // } - - void createNewServerSocket(InetSocketAddress address, boolean b, boolean c) throws IOException { - ServerSocket socket; - - if (b) { - socket = new ServerSocket(); - } else if (c) { - socket = new ServerSocket(); - } else { - socket = new ServerSocket(); - } - - socket.setReuseAddress(true); - socket.bind(address); - closeServerSocket(socket); - } - - @Owning - public SSLServerSocket createSSLServerSocket(SSLContext sslContext) throws IOException { - SSLServerSocket sslServerSocket = - (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); - return configureSSLServerSocket(sslServerSocket); - } - - private SSLServerSocket nonOwningSSField; - - void assignToNonOwningViaCast(SSLContext sslContext) throws IOException { - nonOwningSSField = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); - } - - private SSLServerSocket configureSSLServerSocket(@Owning SSLServerSocket socket) { - return socket; - } - - public SSLSocket createSSLSocket( - @Owning Socket socket, byte[] pushbackBytes, SSLContext sslContext) throws IOException { - SSLSocket sslSocket; - if (pushbackBytes != null && pushbackBytes.length > 0) { - sslSocket = - (SSLSocket) - sslContext.getSocketFactory().createSocket(socket, null, socket.getPort(), true); - } else { - sslSocket = - (SSLSocket) - sslContext.getSocketFactory().createSocket(socket, null, socket.getPort(), true); - } - return configureSSLSocket(sslSocket, false); - } - - private SSLSocket configureSSLSocket(@Owning SSLSocket socket, boolean isClientSocket) { - SSLParameters sslParameters = socket.getSSLParameters(); - // configureSslParameters(sslParameters, isClientSocket); - socket.setSSLParameters(sslParameters); - socket.setUseClientMode(isClientSocket); - return socket; - } - - private void updateSocketAddresses(SelectionKey sockKey) { - // no error here as SelectionKey.channel()'s return is @NotOwning - Socket socket = ((SocketChannel) sockKey.channel()).socket(); - SocketAddress localSocketAddress = socket.getLocalSocketAddress(); - SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); - } - - private void recieverParameterWithCasting(@Owning SelectableChannel channel1) throws IOException { - try { - ((SocketChannel) channel1).socket(); - } finally { - channel1.close(); - } - } - - @NotOwning - Socket getSocket(Socket s) { - return s; - } - - private ServerSocket testMCAParamInReturn() throws IOException { - ServerSocketChannel chan = ServerSocketChannel.open(); - return chan.socket(); - } - - private void testMCAParamInReturn2() throws IOException { - ServerSocket chan = ServerSocketChannel.open().socket(); - } + final PrependableSocket prependableSocket = new PrependableSocket(null); + } + + // private void acceptConnections() { + // int numRetries = 0; + // Socket client = null; + // + // while ((!shutdown) && (portBindMaxRetry == 0 || numRetries < portBindMaxRetry)) { + // try { + // serverSocket = createNewServerSocket(); + // LOG.info("{} is accepting connections now, my election bind port: {}", + // QuorumCnxManager.this.mySid, address.toString()); + // while (!shutdown) { + // try { + // client = serverSocket.accept(); + // setSockOpts(client); + // LOG.info("Received connection request from {}", + // client.getRemoteSocketAddress()); + // // Receive and handle the connection request + // // asynchronously if the quorum sasl authentication is + // // enabled. This is required because sasl server + // // authentication process may take few seconds to finish, + // // this may delay next peer connection requests. + // if (quorumSaslAuthEnabled) { + // receiveConnectionAsync(client); + // } else { + // receiveConnection(client); + // } + // numRetries = 0; + // } catch (SocketTimeoutException e) { + // LOG.warn("The socket is listening for the election accepted " + // + "and it timed out unexpectedly, but will retry." + // + "see ZOOKEEPER-2836"); + // } + // } + // } catch (IOException e) { + // if (shutdown) { + // break; + // } + // + // LOG.error("Exception while listening", e); + // + // if (e instanceof SocketException) { + // socketException.set(true); + // } + // + // numRetries++; + // try { + // close(); + // Thread.sleep(1000); + // } catch (IOException ie) { + // LOG.error("Error closing server socket", ie); + // } catch (InterruptedException ie) { + // LOG.error("Interrupted while sleeping. Ignoring exception", ie); + // } + // closeSocket(client); + // } + // } + // if (!shutdown) { + // LOG.error( + // "Leaving listener thread for address {} after {} errors. Use {} property + // to + // increase retry count.", + // formatInetAddr(address), + // numRetries, + // ELECTION_PORT_BIND_RETRY); + // } + // } + + void createNewServerSocket(InetSocketAddress address, boolean b, boolean c) throws IOException { + ServerSocket socket; + + if (b) { + socket = new ServerSocket(); + } else if (c) { + socket = new ServerSocket(); + } else { + socket = new ServerSocket(); + } + + socket.setReuseAddress(true); + socket.bind(address); + closeServerSocket(socket); + } + + @Owning + public SSLServerSocket createSSLServerSocket(SSLContext sslContext) throws IOException { + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); + return configureSSLServerSocket(sslServerSocket); + } + + private SSLServerSocket nonOwningSSField; + + void assignToNonOwningViaCast(SSLContext sslContext) throws IOException { + nonOwningSSField = + (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(); + } + + private SSLServerSocket configureSSLServerSocket(@Owning SSLServerSocket socket) { + return socket; + } + + public SSLSocket createSSLSocket( + @Owning Socket socket, byte[] pushbackBytes, SSLContext sslContext) throws IOException { + SSLSocket sslSocket; + if (pushbackBytes != null && pushbackBytes.length > 0) { + sslSocket = + (SSLSocket) + sslContext + .getSocketFactory() + .createSocket(socket, null, socket.getPort(), true); + } else { + sslSocket = + (SSLSocket) + sslContext + .getSocketFactory() + .createSocket(socket, null, socket.getPort(), true); + } + return configureSSLSocket(sslSocket, false); + } + + private SSLSocket configureSSLSocket(@Owning SSLSocket socket, boolean isClientSocket) { + SSLParameters sslParameters = socket.getSSLParameters(); + // configureSslParameters(sslParameters, isClientSocket); + socket.setSSLParameters(sslParameters); + socket.setUseClientMode(isClientSocket); + return socket; + } + + private void updateSocketAddresses(SelectionKey sockKey) { + // no error here as SelectionKey.channel()'s return is @NotOwning + Socket socket = ((SocketChannel) sockKey.channel()).socket(); + SocketAddress localSocketAddress = socket.getLocalSocketAddress(); + SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); + } + + private void recieverParameterWithCasting(@Owning SelectableChannel channel1) + throws IOException { + try { + ((SocketChannel) channel1).socket(); + } finally { + channel1.close(); + } + } + + @NotOwning + Socket getSocket(Socket s) { + return s; + } + + private ServerSocket testMCAParamInReturn() throws IOException { + ServerSocketChannel chan = ServerSocketChannel.open(); + return chan.socket(); + } + + private void testMCAParamInReturn2() throws IOException { + ServerSocket chan = ServerSocketChannel.open().socket(); + } } diff --git a/checker/tests/resourceleak/AccumulationValueFieldTest.java b/checker/tests/resourceleak/AccumulationValueFieldTest.java index 51afe3b63b4..e145aa32426 100644 --- a/checker/tests/resourceleak/AccumulationValueFieldTest.java +++ b/checker/tests/resourceleak/AccumulationValueFieldTest.java @@ -5,36 +5,36 @@ public class AccumulationValueFieldTest { - @InheritableMustCall({"a"}) - class MCAB { - void a() {} + @InheritableMustCall({"a"}) + class MCAB { + void a() {} - void b() {} - } - - @InheritableMustCall({"a"}) - class FieldTest { - - @Owning - @MustCall({"a"}) T m = null; - - FieldTest(@Owning @MustCall({"a"}) T mcab) { - m = mcab; - } - - @RequiresCalledMethods( - value = {"this.m"}, - methods = {"a"}) - @CreatesMustCallFor("this") - void overwriteMCorrect(@Owning @MustCall({"a"}) T mcab) { - this.m = mcab; + void b() {} } - @EnsuresCalledMethods( - value = {"this.m"}, - methods = {"a"}) - void a() { - m.a(); + @InheritableMustCall({"a"}) + class FieldTest { + + @Owning + @MustCall({"a"}) T m = null; + + FieldTest(@Owning @MustCall({"a"}) T mcab) { + m = mcab; + } + + @RequiresCalledMethods( + value = {"this.m"}, + methods = {"a"}) + @CreatesMustCallFor("this") + void overwriteMCorrect(@Owning @MustCall({"a"}) T mcab) { + this.m = mcab; + } + + @EnsuresCalledMethods( + value = {"this.m"}, + methods = {"a"}) + void a() { + m.a(); + } } - } } diff --git a/checker/tests/resourceleak/AccumulationValueTest.java b/checker/tests/resourceleak/AccumulationValueTest.java index 373db0ee398..ddd99fdf11d 100644 --- a/checker/tests/resourceleak/AccumulationValueTest.java +++ b/checker/tests/resourceleak/AccumulationValueTest.java @@ -5,93 +5,93 @@ public class AccumulationValueTest { - @InheritableMustCall({"a", "b"}) - class MCAB { - void a() {} + @InheritableMustCall({"a", "b"}) + class MCAB { + void a() {} - void b() {} + void b() {} - void c() {} - } + void c() {} + } - void simple1(@Owning @MustCall({"a", "b"}) T mcab) { - // test that an accumulation value can accumulate more than one item - mcab.a(); - mcab.b(); - } + void simple1(@Owning @MustCall({"a", "b"}) T mcab) { + // test that an accumulation value can accumulate more than one item + mcab.a(); + mcab.b(); + } - // :: error: required.method.not.called - void simple2(@Owning @MustCall({"a", "b"}) T mcab) { - // test that the RLC handles missing call to a() - mcab.b(); - } + // :: error: required.method.not.called + void simple2(@Owning @MustCall({"a", "b"}) T mcab) { + // test that the RLC handles missing call to a() + mcab.b(); + } - void simple3(@Owning @MustCall({"a", "b"}) T mcab) { - // test that an accumulation value can accumulate extra items without issue (this tests - // mostSpecific) - mcab.a(); - mcab.b(); - mcab.c(); - } + void simple3(@Owning @MustCall({"a", "b"}) T mcab) { + // test that an accumulation value can accumulate extra items without issue (this tests + // mostSpecific) + mcab.a(); + mcab.b(); + mcab.c(); + } - // :: error: required.method.not.called - void lub1(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join - if (b) { - mcab.a(); + // :: error: required.method.not.called + void lub1(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join + if (b) { + mcab.a(); + } + mcab.b(); } - mcab.b(); - } - void lub2(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join - if (b) mcab.a(); - else mcab.a(); - mcab.b(); - } + void lub2(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join + if (b) mcab.a(); + else mcab.a(); + mcab.b(); + } - // :: error: required.method.not.called - void lub3(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join if both are non-empty but non-intersecting - if (b) { - mcab.a(); - } else { - mcab.b(); + // :: error: required.method.not.called + void lub3(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join if both are non-empty but non-intersecting + if (b) { + mcab.a(); + } else { + mcab.b(); + } } - } - // :: error: required.method.not.called - void lub4(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting - if (b) { - mcab.a(); - mcab.c(); - } else { - mcab.a(); - mcab.b(); + // :: error: required.method.not.called + void lub4(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting + if (b) { + mcab.a(); + mcab.c(); + } else { + mcab.a(); + mcab.b(); + } } - } - void lub5(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { - // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting - if (b) { - mcab.a(); - mcab.b(); - mcab.c(); - } else { - mcab.a(); - mcab.b(); + void lub5(@Owning @MustCall({"a", "b"}) T mcab, boolean b) { + // tests lubbing two AccumulationValue at a join if both are non-empty but intersecting + if (b) { + mcab.a(); + mcab.b(); + mcab.c(); + } else { + mcab.a(); + mcab.b(); + } } - } - // These two paired methods show what happens when the @MustCall type is "too small": - // errors at call sites. - void wrongMCAnno(@Owning @MustCall({"a"}) T mcab) { - mcab.a(); - } + // These two paired methods show what happens when the @MustCall type is "too small": + // errors at call sites. + void wrongMCAnno(@Owning @MustCall({"a"}) T mcab) { + mcab.a(); + } - void wrongMCAnnoUse(@Owning MCAB mcab) { - // :: error: argument.type.incompatible - wrongMCAnno(mcab); - } + void wrongMCAnnoUse(@Owning MCAB mcab) { + // :: error: argument.type.incompatible + wrongMCAnno(mcab); + } } diff --git a/checker/tests/resourceleak/BindChannel.java b/checker/tests/resourceleak/BindChannel.java index a43e1cb008c..5db5525fabc 100644 --- a/checker/tests/resourceleak/BindChannel.java +++ b/checker/tests/resourceleak/BindChannel.java @@ -5,36 +5,36 @@ import java.nio.channels.*; class BindChannel { - static void test(InetSocketAddress addr, boolean b) { - try { - // This channel is bound - so even with unconnected socket support, we need to - // treat either this channel or the .socket() expression as must-close. - // - // Even though there's now a temporary in the Must Call Checker for the value that - // has the reset method (bind) called on it below, we can't successfully translate - // the reset expression to that temporary, since all we have is a string (from the - // reset annotation) and so we have to go through the type factory's parsing facility, - // which doesn't know about the temporaries and so doesn't return them. We're therefore - // limited to issuing the reset.not.owning error below, - // instead of the preferable required.method.not.called error on this line - as in - // the method below, which extracts the socket into a local variable, which can be - // parsed as an CO target. - ServerSocketChannel httpChannel = ServerSocketChannel.open(); - // :: error: reset.not.owning - httpChannel.socket().bind(addr); - } catch (IOException io) { + static void test(InetSocketAddress addr, boolean b) { + try { + // This channel is bound - so even with unconnected socket support, we need to + // treat either this channel or the .socket() expression as must-close. + // + // Even though there's now a temporary in the Must Call Checker for the value that + // has the reset method (bind) called on it below, we can't successfully translate + // the reset expression to that temporary, since all we have is a string (from the + // reset annotation) and so we have to go through the type factory's parsing facility, + // which doesn't know about the temporaries and so doesn't return them. We're therefore + // limited to issuing the reset.not.owning error below, + // instead of the preferable required.method.not.called error on this line - as in + // the method below, which extracts the socket into a local variable, which can be + // parsed as an CO target. + ServerSocketChannel httpChannel = ServerSocketChannel.open(); + // :: error: reset.not.owning + httpChannel.socket().bind(addr); + } catch (IOException io) { + } } - } - static void test_lv(InetSocketAddress addr, boolean b) { - try { - ServerSocketChannel httpChannel = ServerSocketChannel.open(); - // :: error: required.method.not.called - ServerSocket httpSock = httpChannel.socket(); - httpSock.bind(addr); - } catch (IOException io) { + static void test_lv(InetSocketAddress addr, boolean b) { + try { + ServerSocketChannel httpChannel = ServerSocketChannel.open(); + // :: error: required.method.not.called + ServerSocket httpSock = httpChannel.socket(); + httpSock.bind(addr); + } catch (IOException io) { + } } - } } diff --git a/checker/tests/resourceleak/COAnonymousClass.java b/checker/tests/resourceleak/COAnonymousClass.java index 0171ff81c10..2efbccf4a23 100644 --- a/checker/tests/resourceleak/COAnonymousClass.java +++ b/checker/tests/resourceleak/COAnonymousClass.java @@ -3,58 +3,58 @@ import org.checkerframework.checker.mustcall.qual.*; class COAnonymousClass { - @InheritableMustCall("foo") - static class Foo { - - void foo() {} - - @CreatesMustCallFor("this") - void resetFoo() {} - - void other() { - - Runnable r = - new Runnable() { - @Override - @CreatesMustCallFor("Foo.this") - // :: error: creates.mustcall.for.invalid.target - // :: error: creates.mustcall.for.override.invalid - public void run() { - // [The following explanation is incorrect. The problem is a bug in - // creating implicit "this" expressions.] - // Ideally, we would not issue the following error. However, the Checker - // Framework's JavaExpression support - // (https://eisop.github.io/cf/manual/#java-expressions-as-arguments) - // treats all versions of "this" (including "Foo.this") as referring to - // the object that directly contains the annotation, so we treat this - // call to resetFoo as not permitted. - // :: error: (reset.not.owning) - resetFoo(); - } - }; - call_run(r); + @InheritableMustCall("foo") + static class Foo { + + void foo() {} + + @CreatesMustCallFor("this") + void resetFoo() {} + + void other() { + + Runnable r = + new Runnable() { + @Override + @CreatesMustCallFor("Foo.this") + // :: error: creates.mustcall.for.invalid.target + // :: error: creates.mustcall.for.override.invalid + public void run() { + // [The following explanation is incorrect. The problem is a bug in + // creating implicit "this" expressions.] + // Ideally, we would not issue the following error. However, the Checker + // Framework's JavaExpression support + // (https://eisop.github.io/cf/manual/#java-expressions-as-arguments) + // treats all versions of "this" (including "Foo.this") as referring to + // the object that directly contains the annotation, so we treat this + // call to resetFoo as not permitted. + // :: error: (reset.not.owning) + resetFoo(); + } + }; + call_run(r); + } + + void other2() { + + Runnable r = + new Runnable() { + @Override + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + // :: error: creates.mustcall.for.override.invalid + public void run() { + // This error definitely must be issued, since Foo.this != this. + // :: error: reset.not.owning + resetFoo(); + } + }; + call_run(r); + } + + // If this call to run() were permitted with no errors, this would be unsound. + void call_run(Runnable r) { + r.run(); + } } - - void other2() { - - Runnable r = - new Runnable() { - @Override - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - // :: error: creates.mustcall.for.override.invalid - public void run() { - // This error definitely must be issued, since Foo.this != this. - // :: error: reset.not.owning - resetFoo(); - } - }; - call_run(r); - } - - // If this call to run() were permitted with no errors, this would be unsound. - void call_run(Runnable r) { - r.run(); - } - } } diff --git a/checker/tests/resourceleak/COInSubtype.java b/checker/tests/resourceleak/COInSubtype.java index 3f67ca3769a..13ff2d5b52f 100644 --- a/checker/tests/resourceleak/COInSubtype.java +++ b/checker/tests/resourceleak/COInSubtype.java @@ -5,25 +5,25 @@ import org.checkerframework.checker.mustcall.qual.*; class COInSubtype { - static class Foo { + static class Foo { - void foo() {} + void foo() {} - // This is not supported, even though a sub-class may have must-call obligations. - // This pattern is not used in realistic code, and supporting it hurts checker performance. - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - void resetFoo() {} - } + // This is not supported, even though a sub-class may have must-call obligations. + // This pattern is not used in realistic code, and supporting it hurts checker performance. + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + void resetFoo() {} + } - @InheritableMustCall("a") - static class Bar extends Foo { - void a() {} - } + @InheritableMustCall("a") + static class Bar extends Foo { + void a() {} + } - static void test() { - // :: error: required.method.not.called - @MustCall("a") Foo f = new Bar(); - f.resetFoo(); - } + static void test() { + // :: error: required.method.not.called + @MustCall("a") Foo f = new Bar(); + f.resetFoo(); + } } diff --git a/checker/tests/resourceleak/CheckFields.java b/checker/tests/resourceleak/CheckFields.java index c135a60a7a0..62e1872d99b 100644 --- a/checker/tests/resourceleak/CheckFields.java +++ b/checker/tests/resourceleak/CheckFields.java @@ -4,168 +4,168 @@ class CheckFields { - @InheritableMustCall("a") - static class Foo { - void a() {} - - void c() {} - } - - Foo makeFoo() { - return new Foo(); - } - - @InheritableMustCall("b") - static class FooField { - private final @Owning Foo finalOwningFoo; - // :: error: required.method.not.called - private final @Owning Foo finalOwningFooWrong; - private final Foo finalNotOwningFoo; - private @Owning Foo owningFoo; - private @Owning @MustCall({}) Foo owningEmptyMustCallFoo; - private Foo notOwningFoo; - - public FooField() { - this.finalOwningFoo = new Foo(); - this.finalOwningFooWrong = new Foo(); - // :: error: required.method.not.called - this.finalNotOwningFoo = new Foo(); - } + @InheritableMustCall("a") + static class Foo { + void a() {} - @CreatesMustCallFor - void assingToOwningFieldWrong() { - Foo f = new Foo(); - // :: error: required.method.not.called - this.owningFoo = f; + void c() {} } - @CreatesMustCallFor - void assignToOwningFieldWrong2() { - // :: error: required.method.not.called - this.owningFoo = new Foo(); + Foo makeFoo() { + return new Foo(); } - @CreatesMustCallFor - void assingToOwningField() { - // this is a safe re-assignment. - if (this.owningFoo == null) { - Foo f = new Foo(); - this.owningFoo = f; - } + @InheritableMustCall("b") + static class FooField { + private final @Owning Foo finalOwningFoo; + // :: error: required.method.not.called + private final @Owning Foo finalOwningFooWrong; + private final Foo finalNotOwningFoo; + private @Owning Foo owningFoo; + private @Owning @MustCall({}) Foo owningEmptyMustCallFoo; + private Foo notOwningFoo; + + public FooField() { + this.finalOwningFoo = new Foo(); + this.finalOwningFooWrong = new Foo(); + // :: error: required.method.not.called + this.finalNotOwningFoo = new Foo(); + } + + @CreatesMustCallFor + void assingToOwningFieldWrong() { + Foo f = new Foo(); + // :: error: required.method.not.called + this.owningFoo = f; + } + + @CreatesMustCallFor + void assignToOwningFieldWrong2() { + // :: error: required.method.not.called + this.owningFoo = new Foo(); + } + + @CreatesMustCallFor + void assingToOwningField() { + // this is a safe re-assignment. + if (this.owningFoo == null) { + Foo f = new Foo(); + this.owningFoo = f; + } + } + + void assingToFinalNotOwningField() { + // :: error: required.method.not.called + Foo f = new Foo(); + this.notOwningFoo = f; + } + + Foo getOwningFoo() { + return this.owningFoo; + } + + @EnsuresCalledMethods( + value = {"this.finalOwningFoo", "this.owningFoo"}, + methods = {"a"}) + void b() { + this.finalOwningFoo.a(); + this.finalOwningFoo.c(); + this.owningFoo.a(); + } } - void assingToFinalNotOwningField() { - // :: error: required.method.not.called - Foo f = new Foo(); - this.notOwningFoo = f; + void testField() { + FooField fooField = new FooField(); + fooField.b(); } - Foo getOwningFoo() { - return this.owningFoo; + void testAccessField() { + FooField fooField = new FooField(); + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + fooField.b(); } - @EnsuresCalledMethods( - value = {"this.finalOwningFoo", "this.owningFoo"}, - methods = {"a"}) - void b() { - this.finalOwningFoo.a(); - this.finalOwningFoo.c(); - this.owningFoo.a(); + void testAccessField2() { + FooField fooField = new FooField(); + if (fooField.owningFoo == null) { + fooField.owningFoo = new Foo(); + } + fooField.b(); } - } - - void testField() { - FooField fooField = new FooField(); - fooField.b(); - } - - void testAccessField() { - FooField fooField = new FooField(); - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - fooField.b(); - } - - void testAccessField2() { - FooField fooField = new FooField(); - if (fooField.owningFoo == null) { - fooField.owningFoo = new Foo(); + + void testAccessFieldWrong() { + // :: error: required.method.not.called + FooField fooField = new FooField(); + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + // :: error: required.method.not.called + fooField.notOwningFoo = new Foo(); } - fooField.b(); - } - - void testAccessFieldWrong() { - // :: error: required.method.not.called - FooField fooField = new FooField(); - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - // :: error: required.method.not.called - fooField.notOwningFoo = new Foo(); - } - - @CreatesMustCallFor("#1") - void testAccessField_param(FooField fooField) { - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - fooField.b(); - } - - // :: error: missing.creates.mustcall.for - void testAccessField_param_no_co(FooField fooField) { - // :: error: required.method.not.called - fooField.owningFoo = new Foo(); - fooField.b(); - } - - static class NestedWrong { - - // Non-final owning fields also require the surrounding class to have an appropriate MC - // annotation. - // :: error: required.method.not.called - @Owning Foo foo; - - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } + + @CreatesMustCallFor("#1") + void testAccessField_param(FooField fooField) { + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + fooField.b(); } - } - - @InheritableMustCall("f") - static class NestedWrong2 { - // Non-final owning fields also require the surrounding class to have an appropriate MC - // annotation. - // :: error: required.method.not.called - @Owning Foo foo; - - @CreatesMustCallFor("this") - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } + + // :: error: missing.creates.mustcall.for + void testAccessField_param_no_co(FooField fooField) { + // :: error: required.method.not.called + fooField.owningFoo = new Foo(); + fooField.b(); } - void f() {} - } + static class NestedWrong { + + // Non-final owning fields also require the surrounding class to have an appropriate MC + // annotation. + // :: error: required.method.not.called + @Owning Foo foo; - @InheritableMustCall("f") - static class NestedRight { - // Non-final owning fields also require the surrounding class to have an appropriate MC - // annotation. - @Owning Foo foo; + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } + } + } - @CreatesMustCallFor("this") - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } + @InheritableMustCall("f") + static class NestedWrong2 { + // Non-final owning fields also require the surrounding class to have an appropriate MC + // annotation. + // :: error: required.method.not.called + @Owning Foo foo; + + @CreatesMustCallFor("this") + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } + } + + void f() {} } - @EnsuresCalledMethods(value = "this.foo", methods = "a") - void f() { - this.foo.a(); + @InheritableMustCall("f") + static class NestedRight { + // Non-final owning fields also require the surrounding class to have an appropriate MC + // annotation. + @Owning Foo foo; + + @CreatesMustCallFor("this") + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } + } + + @EnsuresCalledMethods(value = "this.foo", methods = "a") + void f() { + this.foo.a(); + } } - } } diff --git a/checker/tests/resourceleak/CloseSuper.java b/checker/tests/resourceleak/CloseSuper.java index b37fc0b5d91..3636436a79d 100644 --- a/checker/tests/resourceleak/CloseSuper.java +++ b/checker/tests/resourceleak/CloseSuper.java @@ -1,39 +1,40 @@ // Test case for https://github.com/typetools/checker-framework/issues/6204 -import java.io.Closeable; -import java.io.IOException; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.Owning; +import java.io.Closeable; +import java.io.IOException; + public class CloseSuper { - public static class A implements Closeable { - private final @Owning Closeable resource; + public static class A implements Closeable { + private final @Owning Closeable resource; - public A(@Owning Closeable resource) { - this.resource = resource; - } + public A(@Owning Closeable resource) { + this.resource = resource; + } - @Override - @EnsuresCalledMethods( - value = "resource", - methods = {"close"}) - public void close() throws IOException { - resource.close(); + @Override + @EnsuresCalledMethods( + value = "resource", + methods = {"close"}) + public void close() throws IOException { + resource.close(); + } } - } - public static class B extends A { - public B(@Owning Closeable resource) { - super(resource); - } + public static class B extends A { + public B(@Owning Closeable resource) { + super(resource); + } - @Override - @EnsuresCalledMethods( - value = "resource", - methods = {"close"}) - public void close() throws IOException { - super.close(); + @Override + @EnsuresCalledMethods( + value = "resource", + methods = {"close"}) + public void close() throws IOException { + super.close(); + } } - } } diff --git a/checker/tests/resourceleak/CloseableAndMore.java b/checker/tests/resourceleak/CloseableAndMore.java index 838f8cd0a39..7237d45ab3c 100644 --- a/checker/tests/resourceleak/CloseableAndMore.java +++ b/checker/tests/resourceleak/CloseableAndMore.java @@ -1,30 +1,31 @@ // A test that when a class implements autocloseable and has another must-call obligation, // errors are still issued about the other obligation even when it used as a resource variable. -import java.io.IOException; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; +import java.io.IOException; + @SuppressWarnings( - "declaration.inconsistent.with.implements.clause") // stronger @InheritableMustCall + "declaration.inconsistent.with.implements.clause") // stronger @InheritableMustCall @InheritableMustCall({"close", "foo"}) public class CloseableAndMore implements AutoCloseable { - void foo() {} + void foo() {} - @Override - public void close() throws IOException {} + @Override + public void close() throws IOException {} - public static void test_bad() { - // :: error: required.method.not.called - try (CloseableAndMore c = new CloseableAndMore()) { - // empty body - } catch (Exception e) { + public static void test_bad() { + // :: error: required.method.not.called + try (CloseableAndMore c = new CloseableAndMore()) { + // empty body + } catch (Exception e) { + } } - } - public static void test_good() { - try (CloseableAndMore c = new CloseableAndMore()) { - c.foo(); - } catch (Exception e) { + public static void test_good() { + try (CloseableAndMore c = new CloseableAndMore()) { + c.foo(); + } catch (Exception e) { + } } - } } diff --git a/checker/tests/resourceleak/CommonModuleCrash.java b/checker/tests/resourceleak/CommonModuleCrash.java index 87b3a92bb87..cf3eb5703cb 100644 --- a/checker/tests/resourceleak/CommonModuleCrash.java +++ b/checker/tests/resourceleak/CommonModuleCrash.java @@ -1,11 +1,11 @@ import java.net.*; class CommonModuleCrash { - Socket bar = new Socket(); + Socket bar = new Socket(); - static void baz(Socket s) {} + static void baz(Socket s) {} - static { - baz(new Socket()); - } + static { + baz(new Socket()); + } } diff --git a/checker/tests/resourceleak/ConnectingServerSockets.java b/checker/tests/resourceleak/ConnectingServerSockets.java index c09b8a9845e..1856d26b31d 100644 --- a/checker/tests/resourceleak/ConnectingServerSockets.java +++ b/checker/tests/resourceleak/ConnectingServerSockets.java @@ -1,46 +1,47 @@ // a set of test cases that demonstrate that errors are actually insued in appropriate // places when ServerSockets are connected -import java.net.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.*; + class ConnectingServerSockets { - static void simple_ss_test(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - ServerSocket s = new ServerSocket(); - s.bind(sa); - } - - static void simple_ss_test2(SocketAddress sa) throws Exception { - ServerSocket s = new ServerSocket(); - // s.bind(sa); - } - - static void simple_ss_test4(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - ServerSocket s = new ServerSocket(); - s.bind(sa, to); - } - - static @MustCall({}) ServerSocket makeUnconnected() throws Exception { - return new ServerSocket(); - } - - static void simple_ss_test5(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - ServerSocket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ss_test6(SocketAddress sa) throws Exception { - ServerSocket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ss_test8(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - ServerSocket s = makeUnconnected(); - s.bind(sa, to); - } + static void simple_ss_test(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + ServerSocket s = new ServerSocket(); + s.bind(sa); + } + + static void simple_ss_test2(SocketAddress sa) throws Exception { + ServerSocket s = new ServerSocket(); + // s.bind(sa); + } + + static void simple_ss_test4(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + ServerSocket s = new ServerSocket(); + s.bind(sa, to); + } + + static @MustCall({}) ServerSocket makeUnconnected() throws Exception { + return new ServerSocket(); + } + + static void simple_ss_test5(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + ServerSocket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ss_test6(SocketAddress sa) throws Exception { + ServerSocket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ss_test8(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + ServerSocket s = makeUnconnected(); + s.bind(sa, to); + } } diff --git a/checker/tests/resourceleak/ConnectingSockets.java b/checker/tests/resourceleak/ConnectingSockets.java index b433015a3b1..d91c6b04a25 100644 --- a/checker/tests/resourceleak/ConnectingSockets.java +++ b/checker/tests/resourceleak/ConnectingSockets.java @@ -1,58 +1,59 @@ // a set of test cases that demonstrate that errors are actually insued in appropriate // places when Sockets are connected -import java.net.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.*; + class ConnectingSockets { - static void simple_ns_test(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.bind(sa); - } - - static void simple_ns_test2(SocketAddress sa) throws Exception { - Socket s = new Socket(); - // s.bind(sa); - } - - static void simple_ns_test3(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.connect(sa); - } - - static void simple_ns_test4(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.connect(sa, to); - } - - static @MustCall({}) Socket makeUnconnected() throws Exception { - return new Socket(); - } - - static void simple_ns_test5(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = makeUnconnected(); - s.bind(sa); - } - - static void simple_ns_test6(SocketAddress sa) throws Exception { - Socket s = makeUnconnected(); - // s.bind(sa); - } - - static void simple_ns_test7(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = makeUnconnected(); - s.connect(sa); - } - - static void simple_ns_test8(SocketAddress sa, int to) throws Exception { - // :: error: required.method.not.called - Socket s = makeUnconnected(); - s.connect(sa, to); - } + static void simple_ns_test(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.bind(sa); + } + + static void simple_ns_test2(SocketAddress sa) throws Exception { + Socket s = new Socket(); + // s.bind(sa); + } + + static void simple_ns_test3(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.connect(sa); + } + + static void simple_ns_test4(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.connect(sa, to); + } + + static @MustCall({}) Socket makeUnconnected() throws Exception { + return new Socket(); + } + + static void simple_ns_test5(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = makeUnconnected(); + s.bind(sa); + } + + static void simple_ns_test6(SocketAddress sa) throws Exception { + Socket s = makeUnconnected(); + // s.bind(sa); + } + + static void simple_ns_test7(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = makeUnconnected(); + s.connect(sa); + } + + static void simple_ns_test8(SocketAddress sa, int to) throws Exception { + // :: error: required.method.not.called + Socket s = makeUnconnected(); + s.connect(sa, to); + } } diff --git a/checker/tests/resourceleak/ConnectingSockets2.java b/checker/tests/resourceleak/ConnectingSockets2.java index 73e04e1f786..529af682426 100644 --- a/checker/tests/resourceleak/ConnectingSockets2.java +++ b/checker/tests/resourceleak/ConnectingSockets2.java @@ -5,12 +5,12 @@ class ConnectingSockets2 { - void run(InetSocketAddress isa) { - try (Socket serverSocket = new Socket()) { - serverSocket.close(); - serverSocket.connect(isa); - } catch (IOException e) { - // do nothing + void run(InetSocketAddress isa) { + try (Socket serverSocket = new Socket()) { + serverSocket.close(); + serverSocket.connect(isa); + } catch (IOException e) { + // do nothing + } } - } } diff --git a/checker/tests/resourceleak/ConstructorAddsMustCall.java b/checker/tests/resourceleak/ConstructorAddsMustCall.java index bd0f14ef2c8..319139d2e3a 100644 --- a/checker/tests/resourceleak/ConstructorAddsMustCall.java +++ b/checker/tests/resourceleak/ConstructorAddsMustCall.java @@ -4,19 +4,19 @@ import org.checkerframework.checker.mustcall.qual.MustCall; public class ConstructorAddsMustCall { - static class Foo { - void a() {} + static class Foo { + void a() {} - Foo() {} + Foo() {} - @MustCall("a") Foo(String s) {} - } + @MustCall("a") Foo(String s) {} + } - static void useFoo() { - // no obligation for this one - Foo f1 = new Foo(); - // obligation for this one - // :: error: required.method.not.called - Foo f2 = new Foo("hi"); - } + static void useFoo() { + // no obligation for this one + Foo f1 = new Foo(); + // obligation for this one + // :: error: required.method.not.called + Foo f2 = new Foo("hi"); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForIndirect.java b/checker/tests/resourceleak/CreatesMustCallForIndirect.java index 123f89975f2..c941f6694b4 100644 --- a/checker/tests/resourceleak/CreatesMustCallForIndirect.java +++ b/checker/tests/resourceleak/CreatesMustCallForIndirect.java @@ -6,58 +6,58 @@ @InheritableMustCall("a") class CreatesMustCallForIndirect { - @CreatesMustCallFor - void reset() {} + @CreatesMustCallFor + void reset() {} + + void a() {} + + static @MustCall({}) CreatesMustCallForSimple makeNoMC() { + return null; + } + + public static void resetIndirect_no_anno(CreatesMustCallForIndirect r) { + // :: error: reset.not.owning + r.reset(); + } + + @CreatesMustCallFor("#1") + public static void resetIndirect_anno(CreatesMustCallForIndirect r) { + r.reset(); + } + + public static void reset_local() { + // :: error: required.method.not.called + CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); + r.reset(); + } + + public static void reset_local2() { + CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); + r.reset(); + r.a(); + } + + public static void reset_local3() { + // :: error: required.method.not.called + CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); + // Ideally, we'd issue a reset.not.owning error on the next line instead, but not being able + // to parse the case and requiring it to be in a local var is okay too. + // :: error: createsmustcallfor.target.unparseable + ((CreatesMustCallForIndirect) r).reset(); + } - void a() {} - - static @MustCall({}) CreatesMustCallForSimple makeNoMC() { - return null; - } - - public static void resetIndirect_no_anno(CreatesMustCallForIndirect r) { - // :: error: reset.not.owning - r.reset(); - } - - @CreatesMustCallFor("#1") - public static void resetIndirect_anno(CreatesMustCallForIndirect r) { - r.reset(); - } - - public static void reset_local() { - // :: error: required.method.not.called - CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); - r.reset(); - } - - public static void reset_local2() { - CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); - r.reset(); - r.a(); - } - - public static void reset_local3() { // :: error: required.method.not.called - CreatesMustCallForIndirect r = new CreatesMustCallForIndirect(); - // Ideally, we'd issue a reset.not.owning error on the next line instead, but not being able - // to parse the case and requiring it to be in a local var is okay too. - // :: error: createsmustcallfor.target.unparseable - ((CreatesMustCallForIndirect) r).reset(); - } - - // :: error: required.method.not.called - public static void test(@Owning CreatesMustCallForIndirect r) { - resetIndirect_anno(r); - } - - public static void test2(CreatesMustCallForIndirect r) { - // :: error: reset.not.owning - resetIndirect_anno(r); - } - - public static void test3(@Owning CreatesMustCallForIndirect r) { - resetIndirect_anno(r); - r.a(); - } + public static void test(@Owning CreatesMustCallForIndirect r) { + resetIndirect_anno(r); + } + + public static void test2(CreatesMustCallForIndirect r) { + // :: error: reset.not.owning + resetIndirect_anno(r); + } + + public static void test3(@Owning CreatesMustCallForIndirect r) { + resetIndirect_anno(r); + r.a(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForInnerClass.java b/checker/tests/resourceleak/CreatesMustCallForInnerClass.java index 91df837f901..f230f4dc739 100644 --- a/checker/tests/resourceleak/CreatesMustCallForInnerClass.java +++ b/checker/tests/resourceleak/CreatesMustCallForInnerClass.java @@ -3,31 +3,31 @@ import org.checkerframework.checker.mustcall.qual.*; class CreatesMustCallForInnerClass { - @InheritableMustCall("foo") - static class Foo { + @InheritableMustCall("foo") + static class Foo { - void foo() {} + void foo() {} - @CreatesMustCallFor("this") - void resetFoo() {} + @CreatesMustCallFor("this") + void resetFoo() {} - /** non-static inner class */ - class Bar { - @CreatesMustCallFor - // :: error: creates.mustcall.for.invalid.target - void bar() { - // :: error: reset.not.owning - resetFoo(); - } - } + /** non-static inner class */ + class Bar { + @CreatesMustCallFor + // :: error: creates.mustcall.for.invalid.target + void bar() { + // :: error: reset.not.owning + resetFoo(); + } + } - void callBar() { - Bar b = new Bar(); - // If this call to bar() were permitted with no errors, this would be unsound. - // b is in fact an owning pointer, but we don't track it as such because - // Bar objects cannot have must-call obligations created for them. - // :: error: reset.not.owning - b.bar(); + void callBar() { + Bar b = new Bar(); + // If this call to bar() were permitted with no errors, this would be unsound. + // b is in fact an owning pointer, but we don't track it as such because + // Bar objects cannot have must-call obligations created for them. + // :: error: reset.not.owning + b.bar(); + } } - } } diff --git a/checker/tests/resourceleak/CreatesMustCallForOverride.java b/checker/tests/resourceleak/CreatesMustCallForOverride.java index d9145f2b4f5..698b7feca4c 100644 --- a/checker/tests/resourceleak/CreatesMustCallForOverride.java +++ b/checker/tests/resourceleak/CreatesMustCallForOverride.java @@ -5,28 +5,28 @@ @InheritableMustCall("a") class CreatesMustCallForOverride { - @CreatesMustCallFor - @Override - // :: error: creates.mustcall.for.override.invalid - public String toString() { - return "this method could re-assign a field or do something else it shouldn't"; - } + @CreatesMustCallFor + @Override + // :: error: creates.mustcall.for.override.invalid + public String toString() { + return "this method could re-assign a field or do something else it shouldn't"; + } - public void a() {} + public void a() {} - public static void test_no_cast() { - // :: error: required.method.not.called - CreatesMustCallForOverride co = new CreatesMustCallForOverride(); - co.a(); - co.toString(); - } + public static void test_no_cast() { + // :: error: required.method.not.called + CreatesMustCallForOverride co = new CreatesMustCallForOverride(); + co.a(); + co.toString(); + } - public static void test_cast() { - // it would be ideal if the checker issued an error directly here, but the best we can do is - // issue the error above when the offending version of toString() is defined - CreatesMustCallForOverride co = new CreatesMustCallForOverride(); - co.a(); - Object o = co; - o.toString(); - } + public static void test_cast() { + // it would be ideal if the checker issued an error directly here, but the best we can do is + // issue the error above when the offending version of toString() is defined + CreatesMustCallForOverride co = new CreatesMustCallForOverride(); + co.a(); + Object o = co; + o.toString(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForOverride2.java b/checker/tests/resourceleak/CreatesMustCallForOverride2.java index 94e47c7adfa..28d3d359687 100644 --- a/checker/tests/resourceleak/CreatesMustCallForOverride2.java +++ b/checker/tests/resourceleak/CreatesMustCallForOverride2.java @@ -6,171 +6,171 @@ class CreatesMustCallForOverride2 { - @InheritableMustCall("a") - static class Foo { - - @CreatesMustCallFor - public void b() {} - - public void a() {} - } - - static class Bar extends Foo { - - @Override - @CreatesMustCallFor - public void b() {} - } - - static class Baz extends Foo {} - - static class Qux extends Foo { - @Override - public void b() {} - } - - static class Razz extends Foo { - - public @Owning Foo myFoo; - - @Override - @EnsuresCalledMethods(value = "this.myFoo", methods = "a") - public void a() { - super.a(); - myFoo.a(); - } - - // this version isn't permitted, since it adds a new obligation - @Override - @CreatesMustCallFor("this.myFoo") - // :: error: creates.mustcall.for.override.invalid - public void b() {} - } - - static class Thud extends Foo { - - public @Owning Foo myFoo; - - @Override - @EnsuresCalledMethods(value = "this.myFoo", methods = "a") - public void a() { - super.a(); - myFoo.a(); - } - - // this method isn't permitted, since it's also adding a new obligation - @Override - @CreatesMustCallFor("this.myFoo") - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.override.invalid - public void b() {} - } - - static class Thudless extends Thud { - // this method override is also NOT permitted, because the @CreatesMustCallFor("this.myFoo") - // annotation from Thud is inherited! - @Override - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.override.invalid - public void b() {} - } - - static void test1() { - // :: error: required.method.not.called - Foo foo = new Foo(); - foo.a(); - foo.b(); - } - - static void test2() { - // :: error: required.method.not.called - Foo foo = new Bar(); - foo.a(); - foo.b(); - } - - static void test3() { - // :: error: required.method.not.called - Foo foo = new Baz(); - foo.a(); - foo.b(); - } - - static void test4() { - // :: error: required.method.not.called - Foo foo = new Qux(); - foo.a(); - foo.b(); - } - - static void test5() { - // :: error: required.method.not.called - Bar foo = new Bar(); - foo.a(); - foo.b(); - } - - static void test6() { - // :: error: required.method.not.called - Baz foo = new Baz(); - foo.a(); - foo.b(); - } - - static void test7() { - // :: error: required.method.not.called - Qux foo = new Qux(); - foo.a(); - foo.b(); - } - - static void test8() { - // :: error: required.method.not.called - Foo foo = new Razz(); - foo.a(); - foo.b(); - } - - static void test9() { - // No error is issued here, because Razz#b is *only* @CreatesMustCallFor("this.myFoo"), not - // @CreatesMustCallFor("this"). An error is issued at the declaration of Razz#b instead. - Razz foo = new Razz(); - foo.a(); - foo.b(); - } - - static void test10() { - // :: error: required.method.not.called - Foo foo = new Thud(); - foo.a(); - foo.b(); - } - - static void test11() { - // :: error: required.method.not.called - Thud foo = new Thud(); - foo.a(); - foo.b(); - } - - static void test12() { - // :: error: required.method.not.called - Foo foo = new Thudless(); - foo.a(); - foo.b(); - } - - static void test13() { - // :: error: required.method.not.called - Thud foo = new Thudless(); - foo.a(); - foo.b(); - } - - static void test14() { - // :: error: required.method.not.called - Thudless foo = new Thudless(); - foo.a(); - foo.b(); - } + @InheritableMustCall("a") + static class Foo { + + @CreatesMustCallFor + public void b() {} + + public void a() {} + } + + static class Bar extends Foo { + + @Override + @CreatesMustCallFor + public void b() {} + } + + static class Baz extends Foo {} + + static class Qux extends Foo { + @Override + public void b() {} + } + + static class Razz extends Foo { + + public @Owning Foo myFoo; + + @Override + @EnsuresCalledMethods(value = "this.myFoo", methods = "a") + public void a() { + super.a(); + myFoo.a(); + } + + // this version isn't permitted, since it adds a new obligation + @Override + @CreatesMustCallFor("this.myFoo") + // :: error: creates.mustcall.for.override.invalid + public void b() {} + } + + static class Thud extends Foo { + + public @Owning Foo myFoo; + + @Override + @EnsuresCalledMethods(value = "this.myFoo", methods = "a") + public void a() { + super.a(); + myFoo.a(); + } + + // this method isn't permitted, since it's also adding a new obligation + @Override + @CreatesMustCallFor("this.myFoo") + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.override.invalid + public void b() {} + } + + static class Thudless extends Thud { + // this method override is also NOT permitted, because the @CreatesMustCallFor("this.myFoo") + // annotation from Thud is inherited! + @Override + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.override.invalid + public void b() {} + } + + static void test1() { + // :: error: required.method.not.called + Foo foo = new Foo(); + foo.a(); + foo.b(); + } + + static void test2() { + // :: error: required.method.not.called + Foo foo = new Bar(); + foo.a(); + foo.b(); + } + + static void test3() { + // :: error: required.method.not.called + Foo foo = new Baz(); + foo.a(); + foo.b(); + } + + static void test4() { + // :: error: required.method.not.called + Foo foo = new Qux(); + foo.a(); + foo.b(); + } + + static void test5() { + // :: error: required.method.not.called + Bar foo = new Bar(); + foo.a(); + foo.b(); + } + + static void test6() { + // :: error: required.method.not.called + Baz foo = new Baz(); + foo.a(); + foo.b(); + } + + static void test7() { + // :: error: required.method.not.called + Qux foo = new Qux(); + foo.a(); + foo.b(); + } + + static void test8() { + // :: error: required.method.not.called + Foo foo = new Razz(); + foo.a(); + foo.b(); + } + + static void test9() { + // No error is issued here, because Razz#b is *only* @CreatesMustCallFor("this.myFoo"), not + // @CreatesMustCallFor("this"). An error is issued at the declaration of Razz#b instead. + Razz foo = new Razz(); + foo.a(); + foo.b(); + } + + static void test10() { + // :: error: required.method.not.called + Foo foo = new Thud(); + foo.a(); + foo.b(); + } + + static void test11() { + // :: error: required.method.not.called + Thud foo = new Thud(); + foo.a(); + foo.b(); + } + + static void test12() { + // :: error: required.method.not.called + Foo foo = new Thudless(); + foo.a(); + foo.b(); + } + + static void test13() { + // :: error: required.method.not.called + Thud foo = new Thudless(); + foo.a(); + foo.b(); + } + + static void test14() { + // :: error: required.method.not.called + Thudless foo = new Thudless(); + foo.a(); + foo.b(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForRepeat.java b/checker/tests/resourceleak/CreatesMustCallForRepeat.java index 27fb36d3dde..d1c9a849f61 100644 --- a/checker/tests/resourceleak/CreatesMustCallForRepeat.java +++ b/checker/tests/resourceleak/CreatesMustCallForRepeat.java @@ -6,66 +6,66 @@ @InheritableMustCall("a") class CreatesMustCallForRepeat { - @CreatesMustCallFor("this") - @CreatesMustCallFor("#1") - void reset(CreatesMustCallForRepeat r) {} + @CreatesMustCallFor("this") + @CreatesMustCallFor("#1") + void reset(CreatesMustCallForRepeat r) {} - void a() {} + void a() {} - static @MustCall({}) CreatesMustCallForRepeat makeNoMC() { - return null; - } + static @MustCall({}) CreatesMustCallForRepeat makeNoMC() { + return null; + } - static void test1() { - // :: error: required.method.not.called - CreatesMustCallForRepeat cos1 = makeNoMC(); - // :: error: required.method.not.called - CreatesMustCallForRepeat cos2 = makeNoMC(); - @MustCall({}) CreatesMustCallForRepeat a = cos2; - @MustCall({}) CreatesMustCallForRepeat a2 = cos2; - cos2.a(); - cos1.reset(cos2); - // :: error: assignment.type.incompatible - @CalledMethods({"reset"}) CreatesMustCallForRepeat b = cos1; - @CalledMethods({}) CreatesMustCallForRepeat c = cos1; - @CalledMethods({}) CreatesMustCallForRepeat d = cos2; - // :: error: assignment.type.incompatible - @CalledMethods({"a"}) CreatesMustCallForRepeat e = cos2; - } + static void test1() { + // :: error: required.method.not.called + CreatesMustCallForRepeat cos1 = makeNoMC(); + // :: error: required.method.not.called + CreatesMustCallForRepeat cos2 = makeNoMC(); + @MustCall({}) CreatesMustCallForRepeat a = cos2; + @MustCall({}) CreatesMustCallForRepeat a2 = cos2; + cos2.a(); + cos1.reset(cos2); + // :: error: assignment.type.incompatible + @CalledMethods({"reset"}) CreatesMustCallForRepeat b = cos1; + @CalledMethods({}) CreatesMustCallForRepeat c = cos1; + @CalledMethods({}) CreatesMustCallForRepeat d = cos2; + // :: error: assignment.type.incompatible + @CalledMethods({"a"}) CreatesMustCallForRepeat e = cos2; + } - static void test3() { - // :: error: required.method.not.called - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - // :: error: required.method.not.called - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - } + static void test3() { + // :: error: required.method.not.called + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + // :: error: required.method.not.called + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + } - static void test4() { - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - // :: error: required.method.not.called - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - cos.a(); - } + static void test4() { + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + // :: error: required.method.not.called + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + cos.a(); + } - static void test5() { - // :: error: required.method.not.called - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - cos2.a(); - } + static void test5() { + // :: error: required.method.not.called + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + cos2.a(); + } - static void test6() { - CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); - CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); - cos.a(); - cos.reset(cos2); - cos2.a(); - cos.a(); - } + static void test6() { + CreatesMustCallForRepeat cos = new CreatesMustCallForRepeat(); + CreatesMustCallForRepeat cos2 = new CreatesMustCallForRepeat(); + cos.a(); + cos.reset(cos2); + cos2.a(); + cos.a(); + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForSimple.java b/checker/tests/resourceleak/CreatesMustCallForSimple.java index dfa47b7e82a..4f5829dd5c0 100644 --- a/checker/tests/resourceleak/CreatesMustCallForSimple.java +++ b/checker/tests/resourceleak/CreatesMustCallForSimple.java @@ -6,72 +6,72 @@ @InheritableMustCall("a") class CreatesMustCallForSimple { - @CreatesMustCallFor - void reset() {} + @CreatesMustCallFor + void reset() {} - @CreatesMustCallFor("this") - void resetThis() {} + @CreatesMustCallFor("this") + void resetThis() {} - void a() {} + void a() {} - static @MustCall({}) CreatesMustCallForSimple makeNoMC() { - return null; - } + static @MustCall({}) CreatesMustCallForSimple makeNoMC() { + return null; + } - static void test1() { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.reset(); - // :: error: assignment.type.incompatible - @CalledMethods({"reset"}) CreatesMustCallForSimple b = cos; - @CalledMethods({}) CreatesMustCallForSimple c = cos; - } + static void test1() { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.reset(); + // :: error: assignment.type.incompatible + @CalledMethods({"reset"}) CreatesMustCallForSimple b = cos; + @CalledMethods({}) CreatesMustCallForSimple c = cos; + } - static void test2() { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimple a = cos; - cos.resetThis(); - // :: error: assignment.type.incompatible - @CalledMethods({"resetThis"}) CreatesMustCallForSimple b = cos; - @CalledMethods({}) CreatesMustCallForSimple c = cos; - } + static void test2() { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimple a = cos; + cos.resetThis(); + // :: error: assignment.type.incompatible + @CalledMethods({"resetThis"}) CreatesMustCallForSimple b = cos; + @CalledMethods({}) CreatesMustCallForSimple c = cos; + } - static void test3() { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.a(); - cos.resetThis(); - } + static void test3() { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.a(); + cos.resetThis(); + } - static void test4() { - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.a(); - cos.resetThis(); - cos.a(); - } + static void test4() { + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.a(); + cos.resetThis(); + cos.a(); + } - static void test5() { - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.resetThis(); - cos.a(); - } + static void test5() { + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.resetThis(); + cos.a(); + } - static void test6(boolean b) { - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - if (b) { - cos.resetThis(); + static void test6(boolean b) { + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + if (b) { + cos.resetThis(); + } + cos.a(); } - cos.a(); - } - static void test7(boolean b) { - // :: error: required.method.not.called - CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); - cos.a(); - if (b) { - cos.resetThis(); + static void test7(boolean b) { + // :: error: required.method.not.called + CreatesMustCallForSimple cos = new CreatesMustCallForSimple(); + cos.a(); + if (b) { + cos.resetThis(); + } } - } } diff --git a/checker/tests/resourceleak/CreatesMustCallForSimpler.java b/checker/tests/resourceleak/CreatesMustCallForSimpler.java index 0789ebf3dba..c5b2a6a8ed6 100644 --- a/checker/tests/resourceleak/CreatesMustCallForSimpler.java +++ b/checker/tests/resourceleak/CreatesMustCallForSimpler.java @@ -6,25 +6,25 @@ @InheritableMustCall("a") class CreatesMustCallForSimpler { - @CreatesMustCallFor - void reset() {} + @CreatesMustCallFor + void reset() {} - @CreatesMustCallFor("this") - void resetThis() {} + @CreatesMustCallFor("this") + void resetThis() {} - void a() {} + void a() {} - static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { - return null; - } + static @MustCall({}) CreatesMustCallForSimpler makeNoMC() { + return null; + } - static void test1() { - // :: error: required.method.not.called - CreatesMustCallForSimpler cos = makeNoMC(); - @MustCall({}) CreatesMustCallForSimpler a = cos; - cos.reset(); - // :: error: assignment.type.incompatible - @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; - @CalledMethods({}) CreatesMustCallForSimpler c = cos; - } + static void test1() { + // :: error: required.method.not.called + CreatesMustCallForSimpler cos = makeNoMC(); + @MustCall({}) CreatesMustCallForSimpler a = cos; + cos.reset(); + // :: error: assignment.type.incompatible + @CalledMethods({"reset"}) CreatesMustCallForSimpler b = cos; + @CalledMethods({}) CreatesMustCallForSimpler c = cos; + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForTargets.java b/checker/tests/resourceleak/CreatesMustCallForTargets.java index 1389505f1d7..e809b865878 100644 --- a/checker/tests/resourceleak/CreatesMustCallForTargets.java +++ b/checker/tests/resourceleak/CreatesMustCallForTargets.java @@ -1,85 +1,90 @@ // A test that errors are correctly issued when re-assignments don't match the // create obligation annotation on a method. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + @InheritableMustCall("a") class CreatesMustCallForTargets { - @Owning InputStream is1; + @Owning InputStream is1; - @CreatesMustCallFor - // :: error: createsmustcallfor.target.unparseable - // :: error: incompatible.creates.mustcall.for - static void resetObj1(CreatesMustCallForTargets r) throws Exception { - if (r.is1 == null) { - r.is1 = new FileInputStream("foo.txt"); + @CreatesMustCallFor + // :: error: createsmustcallfor.target.unparseable + // :: error: incompatible.creates.mustcall.for + static void resetObj1(CreatesMustCallForTargets r) throws Exception { + if (r.is1 == null) { + r.is1 = new FileInputStream("foo.txt"); + } } - } - @CreatesMustCallFor("#2") - // :: error: incompatible.creates.mustcall.for - static void resetObj2(CreatesMustCallForTargets r, CreatesMustCallForTargets other) - throws Exception { - if (r.is1 == null) { - r.is1 = new FileInputStream("foo.txt"); + @CreatesMustCallFor("#2") + // :: error: incompatible.creates.mustcall.for + static void resetObj2(CreatesMustCallForTargets r, CreatesMustCallForTargets other) + throws Exception { + if (r.is1 == null) { + r.is1 = new FileInputStream("foo.txt"); + } } - } - @CreatesMustCallFor("#1") - static void resetObj3(CreatesMustCallForTargets r, CreatesMustCallForTargets other) - throws Exception { - if (r.is1 == null) { - r.is1 = new FileInputStream("foo.txt"); + @CreatesMustCallFor("#1") + static void resetObj3(CreatesMustCallForTargets r, CreatesMustCallForTargets other) + throws Exception { + if (r.is1 == null) { + r.is1 = new FileInputStream("foo.txt"); + } } - } - @CreatesMustCallFor - void resetObj4(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { - if (is1 == null) { - is1 = new FileInputStream("foo.txt"); + @CreatesMustCallFor + void resetObj4(CreatesMustCallForTargets this, CreatesMustCallForTargets other) + throws Exception { + if (is1 == null) { + is1 = new FileInputStream("foo.txt"); + } } - } - @CreatesMustCallFor - // :: error: incompatible.creates.mustcall.for - void resetObj5(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { - if (other.is1 == null) { - other.is1 = new FileInputStream("foo.txt"); + @CreatesMustCallFor + // :: error: incompatible.creates.mustcall.for + void resetObj5(CreatesMustCallForTargets this, CreatesMustCallForTargets other) + throws Exception { + if (other.is1 == null) { + other.is1 = new FileInputStream("foo.txt"); + } } - } - @CreatesMustCallFor("#2") - // :: error: createsmustcallfor.target.unparseable - // :: error: incompatible.creates.mustcall.for - void resetObj6(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { - if (other.is1 == null) { - other.is1 = new FileInputStream("foo.txt"); + @CreatesMustCallFor("#2") + // :: error: createsmustcallfor.target.unparseable + // :: error: incompatible.creates.mustcall.for + void resetObj6(CreatesMustCallForTargets this, CreatesMustCallForTargets other) + throws Exception { + if (other.is1 == null) { + other.is1 = new FileInputStream("foo.txt"); + } } - } - @CreatesMustCallFor("#1") - void resetObj7(CreatesMustCallForTargets this, CreatesMustCallForTargets other) throws Exception { - if (other.is1 == null) { - other.is1 = new FileInputStream("foo.txt"); + @CreatesMustCallFor("#1") + void resetObj7(CreatesMustCallForTargets this, CreatesMustCallForTargets other) + throws Exception { + if (other.is1 == null) { + other.is1 = new FileInputStream("foo.txt"); + } } - } - @EnsuresCalledMethods(value = "this.is1", methods = "close") - void a() throws Exception { - is1.close(); - } + @EnsuresCalledMethods(value = "this.is1", methods = "close") + void a() throws Exception { + is1.close(); + } - @CreatesMustCallFor("#1") - // :: error: creates.mustcall.for.invalid.target - static void testBadCreates(Object o) {} + @CreatesMustCallFor("#1") + // :: error: creates.mustcall.for.invalid.target + static void testBadCreates(Object o) {} - static class BadCreatesField { - @Owning Object o; + static class BadCreatesField { + @Owning Object o; - @CreatesMustCallFor("this.o") - // :: error: creates.mustcall.for.invalid.target - void badCreatesOnField() {} - } + @CreatesMustCallFor("this.o") + // :: error: creates.mustcall.for.invalid.target + void badCreatesOnField() {} + } } diff --git a/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java b/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java index c9827c4c47e..80c3025af5b 100644 --- a/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java +++ b/checker/tests/resourceleak/CreatesMustCallForTwoAliases.java @@ -3,50 +3,50 @@ import org.checkerframework.checker.mustcall.qual.*; public class CreatesMustCallForTwoAliases { - @InheritableMustCall("a") - static class Foo { + @InheritableMustCall("a") + static class Foo { - @SuppressWarnings("mustcall") - @MustCall() Foo() { - // unconnected socket like + @SuppressWarnings("mustcall") + @MustCall() Foo() { + // unconnected socket like + } + + @CreatesMustCallFor("this") + void reset() {} + + void a() {} + } + + public static void test1() { + Foo a = new Foo(); + // :: error: required.method.not.called + Foo b = a; + b.reset(); + } + + @CreatesMustCallFor("#1") + public static void sneakyReset(Foo f) { + f.reset(); } - @CreatesMustCallFor("this") - void reset() {} - - void a() {} - } - - public static void test1() { - Foo a = new Foo(); - // :: error: required.method.not.called - Foo b = a; - b.reset(); - } - - @CreatesMustCallFor("#1") - public static void sneakyReset(Foo f) { - f.reset(); - } - - public static void test2() { - Foo a = new Foo(); - // :: error: required.method.not.called - Foo b = a; - sneakyReset(b); - } - - public static void test3(Foo b) { - Foo a = new Foo(); - // :: error: required.method.not.called - b = a; - sneakyReset(b); - } - - public static void test4(Foo b) { - // :: error: required.method.not.called - Foo a = new Foo(); - b = a; - sneakyReset(a); - } + public static void test2() { + Foo a = new Foo(); + // :: error: required.method.not.called + Foo b = a; + sneakyReset(b); + } + + public static void test3(Foo b) { + Foo a = new Foo(); + // :: error: required.method.not.called + b = a; + sneakyReset(b); + } + + public static void test4(Foo b) { + // :: error: required.method.not.called + Foo a = new Foo(); + b = a; + sneakyReset(a); + } } diff --git a/checker/tests/resourceleak/DifferentSWKeys.java b/checker/tests/resourceleak/DifferentSWKeys.java index 3ff3b4d8ca8..4bb74911abe 100644 --- a/checker/tests/resourceleak/DifferentSWKeys.java +++ b/checker/tests/resourceleak/DifferentSWKeys.java @@ -6,22 +6,22 @@ @SuppressWarnings("required.method.not.called") class DifferentSWKeys { - void test(@Owning @MustCall("foo") Object obj) { - // :: warning: unneeded.suppression - @SuppressWarnings("mustcall") - @MustCall("foo") Object bar = obj; - } + void test(@Owning @MustCall("foo") Object obj) { + // :: warning: unneeded.suppression + @SuppressWarnings("mustcall") + @MustCall("foo") Object bar = obj; + } - void test2(@Owning @MustCall("foo") Object obj) { - // actually needed suppression - @SuppressWarnings("mustcall") - @MustCall({}) Object bar = obj; - } + void test2(@Owning @MustCall("foo") Object obj) { + // actually needed suppression + @SuppressWarnings("mustcall") + @MustCall({}) Object bar = obj; + } - void test3(@Owning @MustCall("foo") Object obj) { - // test that the option-specific suppression key doesn't work - @SuppressWarnings("mustcallnocreatesmustcallfor") - // :: error: assignment.type.incompatible - @MustCall({}) Object bar = obj; - } + void test3(@Owning @MustCall("foo") Object obj) { + // test that the option-specific suppression key doesn't work + @SuppressWarnings("mustcallnocreatesmustcallfor") + // :: error: assignment.type.incompatible + @MustCall({}) Object bar = obj; + } } diff --git a/checker/tests/resourceleak/DoubleIf.java b/checker/tests/resourceleak/DoubleIf.java index a0c41d30e24..7c4163d09f3 100644 --- a/checker/tests/resourceleak/DoubleIf.java +++ b/checker/tests/resourceleak/DoubleIf.java @@ -3,52 +3,53 @@ // Adding an @CalledMethods annotation, like in parse4, also // makes this code verifiable... -import java.io.*; import org.checkerframework.checker.calledmethods.qual.CalledMethods; +import java.io.*; + class DoubleIf { - String fn; + String fn; - public void parse(boolean b, boolean c) throws Exception { - if (c) { - FileInputStream fis1 = new FileInputStream(fn); - try { - } finally { - fis1.close(); - } - if (b) {} + public void parse(boolean b, boolean c) throws Exception { + if (c) { + FileInputStream fis1 = new FileInputStream(fn); + try { + } finally { + fis1.close(); + } + if (b) {} + } } - } - - public void parse2(boolean c) throws Exception { - if (c) { - FileInputStream fis2 = new FileInputStream(fn); - try { - } finally { - fis2.close(); - } + + public void parse2(boolean c) throws Exception { + if (c) { + FileInputStream fis2 = new FileInputStream(fn); + try { + } finally { + fis2.close(); + } + } } - } - public void parse3(boolean b) throws Exception { - FileInputStream fis3 = new FileInputStream(fn); - try { - } finally { - fis3.close(); + public void parse3(boolean b) throws Exception { + FileInputStream fis3 = new FileInputStream(fn); + try { + } finally { + fis3.close(); + } + if (b) {} } - if (b) {} - } - - public void parse4(boolean b, boolean c) throws Exception { - if (c) { - FileInputStream fis4 = new FileInputStream(fn); - try { - } finally { - fis4.close(); - } - if (b) {} - @CalledMethods("close") FileInputStream fis24 = fis4; + + public void parse4(boolean b, boolean c) throws Exception { + if (c) { + FileInputStream fis4 = new FileInputStream(fn); + try { + } finally { + fis4.close(); + } + if (b) {} + @CalledMethods("close") FileInputStream fis24 = fis4; + } } - } } diff --git a/checker/tests/resourceleak/DuplicateError.java b/checker/tests/resourceleak/DuplicateError.java index 0bc42ead16d..6fb0b88d92f 100644 --- a/checker/tests/resourceleak/DuplicateError.java +++ b/checker/tests/resourceleak/DuplicateError.java @@ -1,21 +1,22 @@ -import java.util.List; -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.UnknownKeyFor; +import java.util.List; +import java.util.function.Function; + public class DuplicateError { - void m(List values) { - @SuppressWarnings("lambda.param.type.incompatible") - List stringVals = DECollectionsPlume.mapList((Object o) -> (String) o, values); - } + void m(List values) { + @SuppressWarnings("lambda.param.type.incompatible") + List stringVals = DECollectionsPlume.mapList((Object o) -> (String) o, values); + } } class DECollectionsPlume { - public static < - @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, - @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> - List mapList(Function f, Iterable iterable) { - return null; - } + public static < + @KeyForBottom FROM extends @Nullable @UnknownKeyFor Object, + @KeyForBottom TO extends @Nullable @UnknownKeyFor Object> + List mapList(Function f, Iterable iterable) { + return null; + } } diff --git a/checker/tests/resourceleak/Enclosing.java b/checker/tests/resourceleak/Enclosing.java index b9953331fb0..eccdb4ff03b 100644 --- a/checker/tests/resourceleak/Enclosing.java +++ b/checker/tests/resourceleak/Enclosing.java @@ -3,21 +3,21 @@ import org.checkerframework.checker.mustcall.qual.*; class Enclosing { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - static class Nested { - // :: error: required.method.not.called - @Owning Foo foo; + static class Nested { + // :: error: required.method.not.called + @Owning Foo foo; - @CreatesMustCallFor("this") - // :: error: creates.mustcall.for.invalid.target - void initFoo() { - if (this.foo == null) { - this.foo = new Foo(); - } + @CreatesMustCallFor("this") + // :: error: creates.mustcall.for.invalid.target + void initFoo() { + if (this.foo == null) { + this.foo = new Foo(); + } + } } - } } diff --git a/checker/tests/resourceleak/EnhancedFor.java b/checker/tests/resourceleak/EnhancedFor.java index c4d706a7ea1..d8e8f84f222 100644 --- a/checker/tests/resourceleak/EnhancedFor.java +++ b/checker/tests/resourceleak/EnhancedFor.java @@ -1,66 +1,67 @@ // Based on some false positives I found in ZK. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.IOException; import java.net.Socket; import java.util.List; -import org.checkerframework.checker.mustcall.qual.*; class EnhancedFor { - void test(List<@MustCall Socket> list) { - for (Socket s : list) { - try { - s.close(); - } catch (IOException i) { - } + void test(List<@MustCall Socket> list) { + for (Socket s : list) { + try { + s.close(); + } catch (IOException i) { + } + } } - } - void test2(List<@MustCall Socket> list) { - for (int i = 0; i < list.size(); i++) { - Socket s = list.get(i); - try { - s.close(); - } catch (IOException io) { - } + void test2(List<@MustCall Socket> list) { + for (int i = 0; i < list.size(); i++) { + Socket s = list.get(i); + try { + s.close(); + } catch (IOException io) { + } + } } - } - // :: error: (type.argument.type.incompatible) - void test3(List list) { - // This error is issued because `s` is a local variable, and - // the foreach loop under the hood assigns the result of a call - // to Iterator#next into it (which is owning by default, because it's - // a method return type). Both this error and the type.argument error - // above can be suppressed by writing @MustCall on the Socket type, as in - // test4 below (but note that this will make call sites difficult to verify). - // :: error: (required.method.not.called) - for (Socket s : list) {} - } + // :: error: (type.argument.type.incompatible) + void test3(List list) { + // This error is issued because `s` is a local variable, and + // the foreach loop under the hood assigns the result of a call + // to Iterator#next into it (which is owning by default, because it's + // a method return type). Both this error and the type.argument error + // above can be suppressed by writing @MustCall on the Socket type, as in + // test4 below (but note that this will make call sites difficult to verify). + // :: error: (required.method.not.called) + for (Socket s : list) {} + } - void test4(List<@MustCall Socket> list) { - for (Socket s : list) {} - } + void test4(List<@MustCall Socket> list) { + for (Socket s : list) {} + } - void test5(List list) { - for (Socket s : list) {} - } + void test5(List list) { + for (Socket s : list) {} + } - void test6(List list) { - for (Socket s : list) { - try { - s.close(); - } catch (IOException i) { - } + void test6(List list) { + for (Socket s : list) { + try { + s.close(); + } catch (IOException i) { + } + } } - } - void test7(List list) { - for (int i = 0; i < list.size(); i++) { - Socket s = list.get(i); - try { - s.close(); - } catch (IOException io) { - } + void test7(List list) { + for (int i = 0; i < list.size(); i++) { + Socket s = list.get(i); + try { + s.close(); + } catch (IOException io) { + } + } } - } } diff --git a/checker/tests/resourceleak/FileDescriptorTest.java b/checker/tests/resourceleak/FileDescriptorTest.java index 5301ba0899f..50aad87d5f2 100644 --- a/checker/tests/resourceleak/FileDescriptorTest.java +++ b/checker/tests/resourceleak/FileDescriptorTest.java @@ -1,57 +1,58 @@ +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.io.IOException; import java.net.*; -import org.checkerframework.checker.mustcall.qual.*; public abstract class FileDescriptorTest { - // This is the original test case. It fails because `in.close()` might throw an exception, which - // is - // not caught; therefore, file might still be open. - public static void readPropertiesFile(File from) throws IOException { - // This is a false positive. - // :: error: required.method.not.called - RandomAccessFile file = new RandomAccessFile(from, "rws"); - FileInputStream in = null; - try { - in = new FileInputStream(file.getFD()); - file.seek(0); - } finally { - if (in != null) { - in.close(); - } - file.close(); + // This is the original test case. It fails because `in.close()` might throw an exception, which + // is + // not caught; therefore, file might still be open. + public static void readPropertiesFile(File from) throws IOException { + // This is a false positive. + // :: error: required.method.not.called + RandomAccessFile file = new RandomAccessFile(from, "rws"); + FileInputStream in = null; + try { + in = new FileInputStream(file.getFD()); + file.seek(0); + } finally { + if (in != null) { + in.close(); + } + file.close(); + } } - } - abstract Socket createSocket(); + abstract Socket createSocket(); - // This is a similar test to the above, but without using the indirection through getFD(). - // This test case demonstrates that the problem is not related to getFD(). - // This warning is a false positive, and should be resolved at the same time as the warning - // above. - public void sameScenario_noFD() throws IOException { - // :: error: (required.method.not.called) - Socket sock = createSocket(); - InputStream in = null; - try { - in = sock.getInputStream(); - } finally { - if (in != null) { - in.close(); - } - sock.close(); + // This is a similar test to the above, but without using the indirection through getFD(). + // This test case demonstrates that the problem is not related to getFD(). + // This warning is a false positive, and should be resolved at the same time as the warning + // above. + public void sameScenario_noFD() throws IOException { + // :: error: (required.method.not.called) + Socket sock = createSocket(); + InputStream in = null; + try { + in = sock.getInputStream(); + } finally { + if (in != null) { + in.close(); + } + sock.close(); + } } - } - // This version, written by Narges, does not issue a false positive. - public static void readPropertiesFile_noFP(File from) throws IOException { - RandomAccessFile file = new RandomAccessFile(from, "rws"); - FileInputStream in = null; - try { - in = new FileInputStream(file.getFD()); - in.close(); - } catch (IOException e) { - file.close(); + // This version, written by Narges, does not issue a false positive. + public static void readPropertiesFile_noFP(File from) throws IOException { + RandomAccessFile file = new RandomAccessFile(from, "rws"); + FileInputStream in = null; + try { + in = new FileInputStream(file.getFD()); + in.close(); + } catch (IOException e) { + file.close(); + } } - } } diff --git a/checker/tests/resourceleak/FilesTest.java b/checker/tests/resourceleak/FilesTest.java index 755f249514a..cdcd41c87bb 100644 --- a/checker/tests/resourceleak/FilesTest.java +++ b/checker/tests/resourceleak/FilesTest.java @@ -5,14 +5,14 @@ public class FilesTest { - void bad(Path p) throws IOException { - // :: error: (required.method.not.called) - Stream s = Files.list(p); - } + void bad(Path p) throws IOException { + // :: error: (required.method.not.called) + Stream s = Files.list(p); + } - void good(Path p) throws IOException { - try (Stream s = Files.list(p)) { - // empty body + void good(Path p) throws IOException { + try (Stream s = Files.list(p)) { + // empty body + } } - } } diff --git a/checker/tests/resourceleak/GetChannelOnLocks.java b/checker/tests/resourceleak/GetChannelOnLocks.java index 50141c09bbb..aafa376a502 100644 --- a/checker/tests/resourceleak/GetChannelOnLocks.java +++ b/checker/tests/resourceleak/GetChannelOnLocks.java @@ -5,35 +5,36 @@ import java.nio.channels.FileLock; class GetChannelOnLocks { - public boolean isLockSupported(FileLock lock, FileLock lock1, FileLock lock2) throws IOException { - FileLock firstLock = null; - FileLock secondLock = null; - try { - firstLock = lock1; - if (firstLock == null) { - return true; - } - secondLock = lock2; - if (secondLock == null) { - return true; - } - } finally { - if (firstLock != null && firstLock != lock) { - firstLock.release(); - firstLock.channel().close(); - } - if (secondLock != null) { - secondLock.release(); - secondLock.channel().close(); - } + public boolean isLockSupported(FileLock lock, FileLock lock1, FileLock lock2) + throws IOException { + FileLock firstLock = null; + FileLock secondLock = null; + try { + firstLock = lock1; + if (firstLock == null) { + return true; + } + secondLock = lock2; + if (secondLock == null) { + return true; + } + } finally { + if (firstLock != null && firstLock != lock) { + firstLock.release(); + firstLock.channel().close(); + } + if (secondLock != null) { + secondLock.release(); + secondLock.channel().close(); + } + } + return false; } - return false; - } - public void isLockSupported2(FileLock lock, FileLock firstLock) throws IOException { - if (firstLock != null && firstLock != lock) { - firstLock.release(); - firstLock.channel().close(); + public void isLockSupported2(FileLock lock, FileLock firstLock) throws IOException { + if (firstLock != null && firstLock != lock) { + firstLock.release(); + firstLock.channel().close(); + } } - } } diff --git a/checker/tests/resourceleak/HBaseReport1.java b/checker/tests/resourceleak/HBaseReport1.java index 2206658f64c..d08960e8de9 100644 --- a/checker/tests/resourceleak/HBaseReport1.java +++ b/checker/tests/resourceleak/HBaseReport1.java @@ -8,29 +8,29 @@ class HBaseReport1 { - public static void test(String fileName) { - FileWriter fstream; - try { - // :: error: required.method.not.called - fstream = new FileWriter(fileName); - } catch (IOException e) { - return; - } + public static void test(String fileName) { + FileWriter fstream; + try { + // :: error: required.method.not.called + fstream = new FileWriter(fileName); + } catch (IOException e) { + return; + } - BufferedWriter out = new BufferedWriter(fstream); + BufferedWriter out = new BufferedWriter(fstream); - try { - try { - out.write(fileName + "\n"); - } finally { try { - out.close(); - } finally { - fstream.close(); - } - } - } catch (IOException e) { + try { + out.write(fileName + "\n"); + } finally { + try { + out.close(); + } finally { + fstream.close(); + } + } + } catch (IOException e) { + } } - } } diff --git a/checker/tests/resourceleak/HDFSReport.java b/checker/tests/resourceleak/HDFSReport.java index 78014a1b3ab..f141eef2190 100644 --- a/checker/tests/resourceleak/HDFSReport.java +++ b/checker/tests/resourceleak/HDFSReport.java @@ -1,24 +1,24 @@ class Handler extends Thread { - boolean running; + boolean running; - static class Call { - boolean isResponseDeferred() { - return true; + static class Call { + boolean isResponseDeferred() { + return true; + } } - } - @Override - public void run() { - while (running) { - Call call = null; - try { - if (running) { - continue; + @Override + public void run() { + while (running) { + Call call = null; + try { + if (running) { + continue; + } + } catch (Exception e) { + } finally { + String s = call.isResponseDeferred() ? ", deferred" : ""; + } } - } catch (Exception e) { - } finally { - String s = call.isResponseDeferred() ? ", deferred" : ""; - } } - } } diff --git a/checker/tests/resourceleak/HDFSReport2.java b/checker/tests/resourceleak/HDFSReport2.java index 03773b9676b..d5cea231798 100644 --- a/checker/tests/resourceleak/HDFSReport2.java +++ b/checker/tests/resourceleak/HDFSReport2.java @@ -2,21 +2,21 @@ class RamDiskAsyncLazyPersistService { - public interface FsVolumeReference extends Closeable {} + public interface FsVolumeReference extends Closeable {} - class ReplicaLazyPersistTask implements Runnable { - private final FsVolumeReference targetVolume; + class ReplicaLazyPersistTask implements Runnable { + private final FsVolumeReference targetVolume; - ReplicaLazyPersistTask(FsVolumeReference targetVolume) { - this.targetVolume = targetVolume; - } + ReplicaLazyPersistTask(FsVolumeReference targetVolume) { + this.targetVolume = targetVolume; + } - @Override - public void run() { - try (FsVolumeReference ref = this.targetVolume) { + @Override + public void run() { + try (FsVolumeReference ref = this.targetVolume) { - } catch (Exception e) { - } + } catch (Exception e) { + } + } } - } } diff --git a/checker/tests/resourceleak/HdfsReport3.java b/checker/tests/resourceleak/HdfsReport3.java index dbb13767e9c..358550eb5fb 100644 --- a/checker/tests/resourceleak/HdfsReport3.java +++ b/checker/tests/resourceleak/HdfsReport3.java @@ -1,26 +1,28 @@ // Based on a false positive in hdfs +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; +import org.checkerframework.common.returnsreceiver.qual.*; + import java.io.*; import java.net.*; import java.nio.*; import java.nio.file.*; import java.security.*; import java.util.*; + import javax.net.ssl.*; -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; -import org.checkerframework.common.returnsreceiver.qual.*; class HdfsReport3 { - private StringBuffer nonObligationTest(int id) { - final StringWriter out = new StringWriter(); - dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(), id); - return out.getBuffer(); - } + private StringBuffer nonObligationTest(int id) { + final StringWriter out = new StringWriter(); + dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(), id); + return out.getBuffer(); + } - public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, int snapshotId) {} + public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, int snapshotId) {} - // StringBuilder doesn't implement closeable - private final StringBuilder sb = new StringBuilder(); - private final Formatter formatter = new Formatter(sb); + // StringBuilder doesn't implement closeable + private final StringBuilder sb = new StringBuilder(); + private final Formatter formatter = new Formatter(sb); } diff --git a/checker/tests/resourceleak/IOUtilsTest.java b/checker/tests/resourceleak/IOUtilsTest.java index 936c1d2df20..eaa1721c8fa 100644 --- a/checker/tests/resourceleak/IOUtilsTest.java +++ b/checker/tests/resourceleak/IOUtilsTest.java @@ -1,16 +1,17 @@ -import java.io.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class IOUtilsTest { - static void test1(@Owning InputStream inputStream) { - org.apache.commons.io.IOUtils.closeQuietly(inputStream); - } + static void test1(@Owning InputStream inputStream) { + org.apache.commons.io.IOUtils.closeQuietly(inputStream); + } - static void test2(@Owning InputStream inputStream) throws IOException { - try { - InputStream other = org.apache.commons.io.IOUtils.toBufferedInputStream(inputStream); - } finally { - inputStream.close(); + static void test2(@Owning InputStream inputStream) throws IOException { + try { + InputStream other = org.apache.commons.io.IOUtils.toBufferedInputStream(inputStream); + } finally { + inputStream.close(); + } } - } } diff --git a/checker/tests/resourceleak/IgnoredExceptionECM.java b/checker/tests/resourceleak/IgnoredExceptionECM.java index d214a1355f8..4aabe7988ac 100644 --- a/checker/tests/resourceleak/IgnoredExceptionECM.java +++ b/checker/tests/resourceleak/IgnoredExceptionECM.java @@ -7,14 +7,14 @@ @InheritableMustCall("foo") class IgnoredExceptionECM { - @Owning - @MustCall("toString") Object obj; + @Owning + @MustCall("toString") Object obj; - @EnsuresCalledMethods(value = "this.obj", methods = "toString") - void foo() { - // This line will produce an exception, - // which the RLC should ignore and verify the method. - int y = 5 / 0; - this.obj.toString(); - } + @EnsuresCalledMethods(value = "this.obj", methods = "toString") + void foo() { + // This line will produce an exception, + // which the RLC should ignore and verify the method. + int y = 5 / 0; + this.obj.toString(); + } } diff --git a/checker/tests/resourceleak/IndexMode.java b/checker/tests/resourceleak/IndexMode.java index 47740afb349..d5235c3539b 100644 --- a/checker/tests/resourceleak/IndexMode.java +++ b/checker/tests/resourceleak/IndexMode.java @@ -1,83 +1,84 @@ // A test for a new false positive issued in release 3.36.0 but not 3.35.0. // Reported as part of https://github.com/typetools/checker-framework/issues/6077. +import org.checkerframework.checker.mustcall.qual.MustCall; + import java.io.InputStream; import java.util.Map; -import org.checkerframework.checker.mustcall.qual.MustCall; public class IndexMode { - public static Object getMode(Map indexOptions) { - // Try-catch is needed, otherwise no FP. - try { - String literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { + public static Object getMode(Map indexOptions) { + // Try-catch is needed, otherwise no FP. + try { + String literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { + } + + // Actual return type rather than void is needed, otherwise no FP. + return null; } - // Actual return type rather than void is needed, otherwise no FP. - return null; - } + // This copy of getMode() adds an explicit `@MustCall` annotation to the String. + public static Object getMode2(Map indexOptions) { + try { + // TODO: a required.method.not.called error should be issued on this line, but currently + // it is not. The reason is an interaction between type variable defaulting, + // local dataflow, and the rules that the RLC uses for choosing a variable's must-call + // obligations: local inference defaults literalOption to @MustCallUnknown (i.e., the + // top MustCall type) even though the RHS expression's type is @MustCall("hashCode"). + // Then, the rule for obligations says that if a variable has the top must-call type, + // use the type's default must-call type instead. For String, this is @MustCall({}), + // so no error is issued. This rule is important to avoid false positives in realistic + // code (such as the first getMode() method in this class). + String literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { + } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String. - public static Object getMode2(Map indexOptions) { - try { - // TODO: a required.method.not.called error should be issued on this line, but currently - // it is not. The reason is an interaction between type variable defaulting, - // local dataflow, and the rules that the RLC uses for choosing a variable's must-call - // obligations: local inference defaults literalOption to @MustCallUnknown (i.e., the - // top MustCall type) even though the RHS expression's type is @MustCall("hashCode"). - // Then, the rule for obligations says that if a variable has the top must-call type, - // use the type's default must-call type instead. For String, this is @MustCall({}), - // so no error is issued. This rule is important to avoid false positives in realistic - // code (such as the first getMode() method in this class). - String literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { + return null; } - return null; - } + // This copy of getMode() adds an explicit `@MustCall` annotation to the String and to + // the local variable. This version currently works as expected, unlike getMode2(). + public static Object getMode2a(Map indexOptions) { + try { + // :: error: required.method.not.called + @MustCall("hashCode") String literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { + } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String and to - // the local variable. This version currently works as expected, unlike getMode2(). - public static Object getMode2a(Map indexOptions) { - try { - // :: error: required.method.not.called - @MustCall("hashCode") String literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { + return null; } - return null; - } + // This copy of getMode() adds an explicit `@MustCall` annotation to the String and removes + // the try-catch. + public static Object getMode3(Map indexOptions) { + // :: error: required.method.not.called + String literalOption = indexOptions.get("is_literal"); + return null; + } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String and removes - // the try-catch. - public static Object getMode3(Map indexOptions) { - // :: error: required.method.not.called - String literalOption = indexOptions.get("is_literal"); - return null; - } + // This copy of getMode() adds an explicit `@MustCall` annotation to the String, removes + // the try-catch, and makes the return type void. + public static void getMode4(Map indexOptions) { + // :: error: required.method.not.called + String literalOption = indexOptions.get("is_literal"); + } - // This copy of getMode() adds an explicit `@MustCall` annotation to the String, removes - // the try-catch, and makes the return type void. - public static void getMode4(Map indexOptions) { - // :: error: required.method.not.called - String literalOption = indexOptions.get("is_literal"); - } + // This copy of getMode() removes the try-catch and makes the return type void. + public static void getMode5(Map indexOptions) { + String literalOption = indexOptions.get("is_literal"); + } - // This copy of getMode() removes the try-catch and makes the return type void. - public static void getMode5(Map indexOptions) { - String literalOption = indexOptions.get("is_literal"); - } + // This variant uses an InputStream (which has a MustCall type by default) as the + // value type in the map. + // :: error: type.argument.type.incompatible + public static Object getModeIS(Map indexOptions) { + try { + // :: error: required.method.not.called + InputStream literalOption = indexOptions.get("is_literal"); + } catch (Exception e) { + } - // This variant uses an InputStream (which has a MustCall type by default) as the - // value type in the map. - // :: error: type.argument.type.incompatible - public static Object getModeIS(Map indexOptions) { - try { - // :: error: required.method.not.called - InputStream literalOption = indexOptions.get("is_literal"); - } catch (Exception e) { + return null; } - - return null; - } } diff --git a/checker/tests/resourceleak/InheritanceStream.java b/checker/tests/resourceleak/InheritanceStream.java index 36f85881836..893319b7dfe 100644 --- a/checker/tests/resourceleak/InheritanceStream.java +++ b/checker/tests/resourceleak/InheritanceStream.java @@ -4,19 +4,19 @@ import java.io.*; class InheritanceStream { - void testBAIS(byte[] buf) { - new ByteArrayInputStream(buf); - } + void testBAIS(byte[] buf) { + new ByteArrayInputStream(buf); + } - void testBAOS() { - new ByteArrayOutputStream(); - } + void testBAOS() { + new ByteArrayOutputStream(); + } - void testSR(String buf) { - new StringReader(buf); - } + void testSR(String buf) { + new StringReader(buf); + } - void testSW() { - new StringWriter(); - } + void testSW() { + new StringWriter(); + } } diff --git a/checker/tests/resourceleak/InitializationAfterSuperTest.java b/checker/tests/resourceleak/InitializationAfterSuperTest.java index bee8853a49a..c723c79e3e3 100644 --- a/checker/tests/resourceleak/InitializationAfterSuperTest.java +++ b/checker/tests/resourceleak/InitializationAfterSuperTest.java @@ -2,25 +2,26 @@ // @skip-test until the bug is fixed -import java.io.IOException; -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.Owning; +import java.io.IOException; +import java.net.Socket; + @InheritableMustCall("close") public class InitializationAfterSuperTest implements AutoCloseable { - @Owning Socket mySocket; + @Owning Socket mySocket; - public InitializationAfterSuperTest(@Owning Socket mySocket) { - super(); - this.mySocket = mySocket; - } + public InitializationAfterSuperTest(@Owning Socket mySocket) { + super(); + this.mySocket = mySocket; + } - @EnsuresCalledMethods(value = "mySocket", methods = "close") - @Override - public void close() throws IOException { - mySocket.close(); - } + @EnsuresCalledMethods(value = "mySocket", methods = "close") + @Override + public void close() throws IOException { + mySocket.close(); + } } diff --git a/checker/tests/resourceleak/InputOutputStreams.java b/checker/tests/resourceleak/InputOutputStreams.java index cc9b3fc87c7..9d45ae2f9d5 100644 --- a/checker/tests/resourceleak/InputOutputStreams.java +++ b/checker/tests/resourceleak/InputOutputStreams.java @@ -1,187 +1,188 @@ // A MustCallAlias test wrt sockets. Also coincidentally tests that MCA sets can be larger than two. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.io.IOException; import java.net.*; -import org.checkerframework.checker.mustcall.qual.*; abstract class InputOutputStreams { - abstract Socket newSocket(); + abstract Socket newSocket(); - void test_close_sock(@Owning Socket sock) throws IOException { - try { - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - } catch (IOException e) { + void test_close_sock(@Owning Socket sock) throws IOException { + try { + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + } catch (IOException e) { - } finally { - sock.close(); - } - } - - // getInputStream()/getOutputStream() can throw IOException in three different scenarios: - // 1) The underlying socket is already closed - // 2) The underlying socket is not connected - // 3) The underlysing socket input is shutdown - // In the first case our checker always reports a false positive but for the second case - // and third case our checker has to verify that close is called on the underlying resource. - // So, because sock.getInputStream() can throw IOException, "is" can be null, then sock will - // remain open. So, it's a true positive warning. - // :: error: required.method.not.called - void test_close_is(@Owning Socket sock) throws IOException { - InputStream is = null; - OutputStream os = null; - try { - is = sock.getInputStream(); - os = sock.getOutputStream(); - } catch (IOException e) { - - } finally { - is.close(); - } - } - - // NOTE (2023/12/8): Previously the RLC reported required.method.not.called - // on this method. However, the code is actually safe because we are not - // obligated to close @Owning parameters when a method throws an exception - // (see https://github.com/typetools/checker-framework/pull/6241). - void test_close_os_old(@Owning Socket sock) throws IOException { - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - os.close(); - } - - void test_close_os() throws IOException { - // :: error: (required.method.not.called) - Socket sock = newSocket(); - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - os.close(); - } - - // :: error: required.method.not.called - void test_close_os2(@Owning Socket sock) throws IOException { - OutputStream os = null; - InputStream is = null; - try { - is = sock.getInputStream(); - } catch (IOException e) { - try { - os = sock.getOutputStream(); - } catch (IOException ee) { - } - } finally { - os.close(); - } - } - - // NOTE (2023/12/8): Previously the RLC reported required.method.not.called - // on this method. However, the code is actually safe because we are not - // obligated to close @Owning parameters when a method throws an exception - // (see https://github.com/typetools/checker-framework/pull/6241). - void test_close_os3_old(@Owning Socket sock) throws IOException { - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { + } finally { + sock.close(); + } } - try { - os = sock.getOutputStream(); - } finally { - os.close(); + + // getInputStream()/getOutputStream() can throw IOException in three different scenarios: + // 1) The underlying socket is already closed + // 2) The underlying socket is not connected + // 3) The underlysing socket input is shutdown + // In the first case our checker always reports a false positive but for the second case + // and third case our checker has to verify that close is called on the underlying resource. + // So, because sock.getInputStream() can throw IOException, "is" can be null, then sock will + // remain open. So, it's a true positive warning. + // :: error: required.method.not.called + void test_close_is(@Owning Socket sock) throws IOException { + InputStream is = null; + OutputStream os = null; + try { + is = sock.getInputStream(); + os = sock.getOutputStream(); + } catch (IOException e) { + + } finally { + is.close(); + } } - } - - void test_close_os3() throws IOException { - // :: error: (required.method.not.called) - Socket sock = newSocket(); - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { + + // NOTE (2023/12/8): Previously the RLC reported required.method.not.called + // on this method. However, the code is actually safe because we are not + // obligated to close @Owning parameters when a method throws an exception + // (see https://github.com/typetools/checker-framework/pull/6241). + void test_close_os_old(@Owning Socket sock) throws IOException { + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + os.close(); } - try { - os = sock.getOutputStream(); - } finally { - os.close(); + + void test_close_os() throws IOException { + // :: error: (required.method.not.called) + Socket sock = newSocket(); + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + os.close(); } - } - - // NOTE (2023/12/8): Previously the RLC reported required.method.not.called - // on this method. However, the code is actually safe because we are not - // obligated to close @Owning parameters when a method throws an exception - // (see https://github.com/typetools/checker-framework/pull/6241). - void test_close_os4_old(@Owning Socket sock) throws IOException { - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { + + // :: error: required.method.not.called + void test_close_os2(@Owning Socket sock) throws IOException { + OutputStream os = null; + InputStream is = null; + try { + is = sock.getInputStream(); + } catch (IOException e) { + try { + os = sock.getOutputStream(); + } catch (IOException ee) { + } + } finally { + os.close(); + } } - try { - os = sock.getOutputStream(); - } finally { - if (os != null) { - os.close(); - } else { - sock.close(); - } + + // NOTE (2023/12/8): Previously the RLC reported required.method.not.called + // on this method. However, the code is actually safe because we are not + // obligated to close @Owning parameters when a method throws an exception + // (see https://github.com/typetools/checker-framework/pull/6241). + void test_close_os3_old(@Owning Socket sock) throws IOException { + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { + } + try { + os = sock.getOutputStream(); + } finally { + os.close(); + } } - } - - // TODO this case requires more general tracking of additional boolean conditions - // If getOutputStream() throws an IOException, then the os variable remains definitely null. - // When our worklist analysis gets to the finally block along the corresponding CFG edge, - // it does not know that os is definitely null, and it is only tracking sock as a name for - // the resource. So, it analyzes a path through the "then" branch of the conditional, - // where sock.close() is not invoked, and reports an error. - void test_close_os4() throws IOException { - // :: error: (required.method.not.called) - Socket sock = newSocket(); - OutputStream os = null; - try { - InputStream is = sock.getInputStream(); - } catch (IOException e) { + + void test_close_os3() throws IOException { + // :: error: (required.method.not.called) + Socket sock = newSocket(); + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { + } + try { + os = sock.getOutputStream(); + } finally { + os.close(); + } } - try { - os = sock.getOutputStream(); - } finally { - if (os != null) { - os.close(); - } else { - sock.close(); - } + + // NOTE (2023/12/8): Previously the RLC reported required.method.not.called + // on this method. However, the code is actually safe because we are not + // obligated to close @Owning parameters when a method throws an exception + // (see https://github.com/typetools/checker-framework/pull/6241). + void test_close_os4_old(@Owning Socket sock) throws IOException { + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { + } + try { + os = sock.getOutputStream(); + } finally { + if (os != null) { + os.close(); + } else { + sock.close(); + } + } } - } - - // :: error: required.method.not.called - void test_close_buff(@Owning Socket sock) throws IOException { - BufferedOutputStream buff = null; - try { - InputStream is = sock.getInputStream(); - OutputStream os = sock.getOutputStream(); - buff = new BufferedOutputStream(os); - } catch (IOException e) { - - } finally { - buff.close(); + + // TODO this case requires more general tracking of additional boolean conditions + // If getOutputStream() throws an IOException, then the os variable remains definitely null. + // When our worklist analysis gets to the finally block along the corresponding CFG edge, + // it does not know that os is definitely null, and it is only tracking sock as a name for + // the resource. So, it analyzes a path through the "then" branch of the conditional, + // where sock.close() is not invoked, and reports an error. + void test_close_os4() throws IOException { + // :: error: (required.method.not.called) + Socket sock = newSocket(); + OutputStream os = null; + try { + InputStream is = sock.getInputStream(); + } catch (IOException e) { + } + try { + os = sock.getOutputStream(); + } finally { + if (os != null) { + os.close(); + } else { + sock.close(); + } + } } - } - - void test_write(String host, int port) { - Socket sock = null; - try { - sock = new Socket(host, port); - sock.getOutputStream().write("isro".getBytes()); - } catch (Exception e) { - System.err.println("write failed: " + e); - } finally { - if (sock != null) { + + // :: error: required.method.not.called + void test_close_buff(@Owning Socket sock) throws IOException { + BufferedOutputStream buff = null; try { - sock.close(); + InputStream is = sock.getInputStream(); + OutputStream os = sock.getOutputStream(); + buff = new BufferedOutputStream(os); } catch (IOException e) { - System.err.println("couldn't close socket!"); + + } finally { + buff.close(); + } + } + + void test_write(String host, int port) { + Socket sock = null; + try { + sock = new Socket(host, port); + sock.getOutputStream().write("isro".getBytes()); + } catch (Exception e) { + System.err.println("write failed: " + e); + } finally { + if (sock != null) { + try { + sock.close(); + } catch (IOException e) { + System.err.println("couldn't close socket!"); + } + } } - } } - } } diff --git a/checker/tests/resourceleak/InstanceInitializer.java b/checker/tests/resourceleak/InstanceInitializer.java index 74eb6e61ada..4bb2a951ed4 100644 --- a/checker/tests/resourceleak/InstanceInitializer.java +++ b/checker/tests/resourceleak/InstanceInitializer.java @@ -2,55 +2,56 @@ // In the resourceleak-permitinitializationleak/ directory, it's a test that the // checker is unsound with the -ApermitInitializationLeak command-line argument. -import java.net.Socket; import org.checkerframework.checker.mustcall.qual.*; +import java.net.Socket; + class InstanceInitializer { - // :: error: required.method.not.called - private @Owning Socket s; - - private final int DEFAULT_PORT = 5; - private final String DEFAULT_ADDR = "localhost"; - - { - try { - // This assignment is OK, because it's the first assignment. - // However, the Resource Leak Checker issues a false positive warning. - // :: error: required.method.not.called - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { + // :: error: required.method.not.called + private @Owning Socket s; + + private final int DEFAULT_PORT = 5; + private final String DEFAULT_ADDR = "localhost"; + + { + try { + // This assignment is OK, because it's the first assignment. + // However, the Resource Leak Checker issues a false positive warning. + // :: error: required.method.not.called + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } } - } - - { - try { - // This assignment is not OK, because it's a reassignment without satisfying the - // mustcall obligations of the previous value of `s`. - // :: error: required.method.not.called - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { + + { + try { + // This assignment is not OK, because it's a reassignment without satisfying the + // mustcall obligations of the previous value of `s`. + // :: error: required.method.not.called + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } } - } - { - try { - // :: error: required.method.not.called - Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { + { + try { + // :: error: required.method.not.called + Socket s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } } - } - { - Socket s1 = null; - try { - s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } catch (Exception e) { + { + Socket s1 = null; + try { + s1 = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } catch (Exception e) { + } + s1.close(); } - s1.close(); - } - public InstanceInitializer() throws Exception { - // :: error: required.method.not.called - s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); - } + public InstanceInitializer() throws Exception { + // :: error: required.method.not.called + s = new Socket(DEFAULT_ADDR, DEFAULT_PORT); + } } diff --git a/checker/tests/resourceleak/IsClosed.java b/checker/tests/resourceleak/IsClosed.java index 44150381f30..207027f646e 100644 --- a/checker/tests/resourceleak/IsClosed.java +++ b/checker/tests/resourceleak/IsClosed.java @@ -1,28 +1,29 @@ // A test that the EnsuresCalledMethodsIf annotations in // the Socket stub files are respected. +import org.checkerframework.checker.mustcall.qual.Owning; + import java.io.*; import java.net.*; -import org.checkerframework.checker.mustcall.qual.Owning; class IsClosed { - void test_socket(@Owning Socket sock) { - if (!sock.isClosed()) { - try { - sock.close(); - } catch (IOException io) { + void test_socket(@Owning Socket sock) { + if (!sock.isClosed()) { + try { + sock.close(); + } catch (IOException io) { - } + } + } } - } - void test_server_socket(@Owning ServerSocket sock) { - if (!sock.isClosed()) { - try { - sock.close(); - } catch (IOException io) { + void test_server_socket(@Owning ServerSocket sock) { + if (!sock.isClosed()) { + try { + sock.close(); + } catch (IOException io) { - } + } + } } - } } diff --git a/checker/tests/resourceleak/Issue4815.java b/checker/tests/resourceleak/Issue4815.java index ec9178ad1e5..212d7cdeb82 100644 --- a/checker/tests/resourceleak/Issue4815.java +++ b/checker/tests/resourceleak/Issue4815.java @@ -1,17 +1,18 @@ // Test case for https://tinyurl.com/cfissue/4815 -import java.util.List; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; +import java.util.List; + public class Issue4815 { - public void initialize( - List list, @Owning @MustCall("initialize") T object) { - object.initialize(); - list.add(object); - } + public void initialize( + List list, @Owning @MustCall("initialize") T object) { + object.initialize(); + list.add(object); + } - private static class Component { - void initialize() {} - } + private static class Component { + void initialize() {} + } } diff --git a/checker/tests/resourceleak/Issue6030.java b/checker/tests/resourceleak/Issue6030.java index abb621a5cfd..7ae668c4442 100644 --- a/checker/tests/resourceleak/Issue6030.java +++ b/checker/tests/resourceleak/Issue6030.java @@ -1,29 +1,30 @@ // Test case for https://github.com/typetools/checker-framework/issues/6030 -import java.util.*; import org.checkerframework.checker.mustcall.qual.Owning; +import java.util.*; + public class Issue6030 { - interface CloseableIterator extends Iterator, java.io.Closeable {} + interface CloseableIterator extends Iterator, java.io.Closeable {} - static class MyScanner> implements CloseableIterator { - @Owning I iterator; + static class MyScanner> implements CloseableIterator { + @Owning I iterator; - // :: error: missing.creates.mustcall.for - public boolean hasNext() { - if (iterator == null) iterator = createIterator(); - return iterator.hasNext(); - } + // :: error: missing.creates.mustcall.for + public boolean hasNext() { + if (iterator == null) iterator = createIterator(); + return iterator.hasNext(); + } - public T next() { - return null; - } + public T next() { + return null; + } - private I createIterator() { - return null; - } + private I createIterator() { + return null; + } - public void close() {} - } + public void close() {} + } } diff --git a/checker/tests/resourceleak/JavaEETest.java b/checker/tests/resourceleak/JavaEETest.java index bb1f7431b49..b4fc459de04 100644 --- a/checker/tests/resourceleak/JavaEETest.java +++ b/checker/tests/resourceleak/JavaEETest.java @@ -1,7 +1,7 @@ // Test for https://github.com/typetools/checker-framework/issues/5472 class JavaEETest { - static void foo(javax.servlet.ServletResponse s) throws java.io.IOException { - s.getWriter(); - } + static void foo(javax.servlet.ServletResponse s) throws java.io.IOException { + s.getWriter(); + } } diff --git a/checker/tests/resourceleak/LemmaStack.java b/checker/tests/resourceleak/LemmaStack.java index b8348656899..2aaeae52e77 100644 --- a/checker/tests/resourceleak/LemmaStack.java +++ b/checker/tests/resourceleak/LemmaStack.java @@ -9,40 +9,41 @@ // ^ // 1 error -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.UncheckedIOException; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; + @MustCall("close") public class LemmaStack implements Closeable { - private @Owning @MustCall("close") PrintWriter session; + private @Owning @MustCall("close") PrintWriter session; - @CreatesMustCallFor("this") - @EnsuresNonNull("session") - private void startProver() { - try { - if (session != null) { - session.close(); - } - session = new PrintWriter("filename.txt"); - } catch (IOException e) { - throw new UncheckedIOException(e); + @CreatesMustCallFor("this") + @EnsuresNonNull("session") + private void startProver() { + try { + if (session != null) { + session.close(); + } + session = new PrintWriter("filename.txt"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - } - public LemmaStack() { - startProver(); - } + public LemmaStack() { + startProver(); + } - @EnsuresCalledMethods(value = "session", methods = "close") - @Override - public void close(LemmaStack this) { - session.close(); - } + @EnsuresCalledMethods(value = "session", methods = "close") + @Override + public void close(LemmaStack this) { + session.close(); + } } diff --git a/checker/tests/resourceleak/LhsArrayCast.java b/checker/tests/resourceleak/LhsArrayCast.java index e324f9f185b..452fe70d1e2 100644 --- a/checker/tests/resourceleak/LhsArrayCast.java +++ b/checker/tests/resourceleak/LhsArrayCast.java @@ -1,8 +1,8 @@ class LhsArrayCast { - void populateWithSamples(Object[] currentSample) { - int j = 22; - int k = 42; - ((String[]) currentSample[j])[k] = "hello"; - } + void populateWithSamples(Object[] currentSample) { + int j = 22; + int k = 42; + ((String[]) currentSample[j])[k] = "hello"; + } } diff --git a/checker/tests/resourceleak/LineNumberReaderTest.java b/checker/tests/resourceleak/LineNumberReaderTest.java index 73813de5b3f..011c0df7daf 100644 --- a/checker/tests/resourceleak/LineNumberReaderTest.java +++ b/checker/tests/resourceleak/LineNumberReaderTest.java @@ -1,37 +1,38 @@ -import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import java.io.*; + class LineNumberReaderTest implements Closeable { - private final @Owning LineNumberReader reader; + private final @Owning LineNumberReader reader; - LineNumberReaderTest(File toRead) { - LineNumberReader reader = null; - try { - reader = new LineNumberReader(new FileReader(toRead)); - this.reader = reader; - advance(); - } catch (IOException e) { - if (reader != null) { + LineNumberReaderTest(File toRead) { + LineNumberReader reader = null; try { - reader.close(); - } catch (Exception exceptionOnClose) { - e.addSuppressed(exceptionOnClose); + reader = new LineNumberReader(new FileReader(toRead)); + this.reader = reader; + advance(); + } catch (IOException e) { + if (reader != null) { + try { + reader.close(); + } catch (Exception exceptionOnClose) { + e.addSuppressed(exceptionOnClose); + } + } + throw new RuntimeException(e); } - } - throw new RuntimeException(e); } - } - @RequiresNonNull("reader") - protected void advance(@UnknownInitialization LineNumberReaderTest this) throws IOException {} + @RequiresNonNull("reader") + protected void advance(@UnknownInitialization LineNumberReaderTest this) throws IOException {} - @Override - @EnsuresCalledMethods(value = "reader", methods = "close") - public void close() throws IOException { - reader.close(); - } + @Override + @EnsuresCalledMethods(value = "reader", methods = "close") + public void close() throws IOException { + reader.close(); + } } diff --git a/checker/tests/resourceleak/MCANotOwningField.java b/checker/tests/resourceleak/MCANotOwningField.java index 25aa4087117..5015283c5a1 100644 --- a/checker/tests/resourceleak/MCANotOwningField.java +++ b/checker/tests/resourceleak/MCANotOwningField.java @@ -5,13 +5,13 @@ class MCANotOwningField { - final Socket s; + final Socket s; - MCANotOwningField(Socket s) throws Exception { - this.s = s; - } + MCANotOwningField(Socket s) throws Exception { + this.s = s; + } - void simple() throws Exception { - s.getInputStream(); - } + void simple() throws Exception { + s.getInputStream(); + } } diff --git a/checker/tests/resourceleak/MCAOwningField.java b/checker/tests/resourceleak/MCAOwningField.java index ae5be0d4196..4173742c8cf 100644 --- a/checker/tests/resourceleak/MCAOwningField.java +++ b/checker/tests/resourceleak/MCAOwningField.java @@ -1,25 +1,26 @@ // A test case for a common pattern in Zookeeper: something is must-call-alias // with an owning field, and therefore a false positive was issued. -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.Socket; + @InheritableMustCall("stop") class MCAOwningField { - @Owning final Socket s; + @Owning final Socket s; - MCAOwningField() throws Exception { - s = new Socket(); - } + MCAOwningField() throws Exception { + s = new Socket(); + } - void simple() throws Exception { - s.getInputStream(); - } + void simple() throws Exception { + s.getInputStream(); + } - @EnsuresCalledMethods(value = "s", methods = "close") - void stop() throws Exception { - s.close(); - } + @EnsuresCalledMethods(value = "s", methods = "close") + void stop() throws Exception { + s.close(); + } } diff --git a/checker/tests/resourceleak/MCAWithThis.java b/checker/tests/resourceleak/MCAWithThis.java index d9451bb2700..ae76db6fbb2 100644 --- a/checker/tests/resourceleak/MCAWithThis.java +++ b/checker/tests/resourceleak/MCAWithThis.java @@ -4,15 +4,15 @@ import java.net.Socket; class MCAWithThis extends Socket { - public MCAWithThis() { - super(); - } + public MCAWithThis() { + super(); + } - public void test() throws Exception { - this.getInputStream(); - } + public void test() throws Exception { + this.getInputStream(); + } - public void test2() throws Exception { - getInputStream(); - } + public void test2() throws Exception { + getInputStream(); + } } diff --git a/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java b/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java index ac3547dac63..f15c12984c0 100644 --- a/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java +++ b/checker/tests/resourceleak/ManualMustCallEmptyOnConstructor.java @@ -1,25 +1,26 @@ // test for https://github.com/kelloggm/object-construction-checker/issues/326 -import java.io.InputStream; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; +import java.io.InputStream; + class ManualMustCallEmptyOnConstructor { - // Test that writing @MustCall({}) on a constructor results in an error - @InheritableMustCall("a") - static class Foo { - final @Owning InputStream is; + // Test that writing @MustCall({}) on a constructor results in an error + @InheritableMustCall("a") + static class Foo { + final @Owning InputStream is; - // :: error: inconsistent.constructor.type - @MustCall({}) Foo(@Owning InputStream is) { - this.is = is; - } + // :: error: inconsistent.constructor.type + @MustCall({}) Foo(@Owning InputStream is) { + this.is = is; + } - @EnsuresCalledMethods(value = "this.is", methods = "close") - void a() throws Exception { - is.close(); + @EnsuresCalledMethods(value = "this.is", methods = "close") + void a() throws Exception { + is.close(); + } } - } } diff --git a/checker/tests/resourceleak/MultipleIdenticalReturns.java b/checker/tests/resourceleak/MultipleIdenticalReturns.java index f25c0a5ee5c..ba24c6fde73 100644 --- a/checker/tests/resourceleak/MultipleIdenticalReturns.java +++ b/checker/tests/resourceleak/MultipleIdenticalReturns.java @@ -6,32 +6,32 @@ * a method */ class MultipleIdenticalReturns { - static class Repro { - private java.lang.ClassLoader loader; + static class Repro { + private java.lang.ClassLoader loader; - public Object loadClass(final String className) throws ClassNotFoundException { - final String classFile = className.replace('.', '/'); - Object RC = null; - if (RC != null) { - return RC; - } - try (InputStream is = loader.getResourceAsStream(classFile + ".class")) { - // no warning here, since parse() is invoked in parser - ClassParser parser = new ClassParser(); - RC = parser.parse(); - return RC; - } catch (final IOException e) { - throw new ClassNotFoundException(className + " not found: " + e, e); - } + public Object loadClass(final String className) throws ClassNotFoundException { + final String classFile = className.replace('.', '/'); + Object RC = null; + if (RC != null) { + return RC; + } + try (InputStream is = loader.getResourceAsStream(classFile + ".class")) { + // no warning here, since parse() is invoked in parser + ClassParser parser = new ClassParser(); + RC = parser.parse(); + return RC; + } catch (final IOException e) { + throw new ClassNotFoundException(className + " not found: " + e, e); + } + } } - } - @org.checkerframework.checker.mustcall.qual.InheritableMustCall("parse") - static class ClassParser { - public ClassParser() {} + @org.checkerframework.checker.mustcall.qual.InheritableMustCall("parse") + static class ClassParser { + public ClassParser() {} - public Object parse() { - return null; + public Object parse() { + return null; + } } - } } diff --git a/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java b/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java index 794113d2f70..7a7ee24c056 100644 --- a/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java +++ b/checker/tests/resourceleak/MultipleMethodParamsMustCallAliasTest.java @@ -1,91 +1,92 @@ -import java.io.*; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; +import java.io.*; +import java.net.*; + class MultipleMethodParamsMustCallAliasTest { - void testMultiMethodParamsCorrect1(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + void testMultiMethodParamsCorrect1(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - r.close(); - } + r.close(); + } - void testMultiMethodParamsCorrect2(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + void testMultiMethodParamsCorrect2(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - try { - in1.close(); - } catch (IOException e) { - } finally { - in2.close(); + try { + in1.close(); + } catch (IOException e) { + } finally { + in2.close(); + } } - } - void testMultiMethodParamsCorrect3(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + void testMultiMethodParamsCorrect3(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - try { - in1.close(); - } finally { - in2.close(); + try { + in1.close(); + } finally { + in2.close(); + } } - } - // :: error: required.method.not.called - void testMultiMethodParamsWrong1(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + // :: error: required.method.not.called + void testMultiMethodParamsWrong1(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - in1.close(); - } + in1.close(); + } - // :: error: required.method.not.called - void testMultiMethodParamsWrong2(@Owning InputStream in1, @Owning InputStream in2) - throws IOException { + // :: error: required.method.not.called + void testMultiMethodParamsWrong2(@Owning InputStream in1, @Owning InputStream in2) + throws IOException { - ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); + ReplicaInputStreams r = new ReplicaInputStreams(in1, in2); - in2.close(); - } + in2.close(); + } - // :: error: required.method.not.called - void testMultiMethodParamsWrong3(@Owning InputStream in1) throws IOException { // :: error: required.method.not.called - Socket socket = new Socket("address", 12); - ReplicaInputStreams r = new ReplicaInputStreams(in1, socket.getInputStream()); - } - - class ReplicaInputStreams implements Closeable { - - private final @Owning InputStream in1; - private final @Owning InputStream in2; - - public @MustCallAlias ReplicaInputStreams( - // This class is unsafe: calling close on i1 doesn't result in calling close on i2, - // so this MustCallAlias relationship shouldn't be verified. - // :: error: mustcallalias.out.of.scope - @MustCallAlias InputStream i1, @MustCallAlias InputStream i2) { - this.in1 = i1; - this.in2 = i2; + void testMultiMethodParamsWrong3(@Owning InputStream in1) throws IOException { + // :: error: required.method.not.called + Socket socket = new Socket("address", 12); + ReplicaInputStreams r = new ReplicaInputStreams(in1, socket.getInputStream()); } - @Override - @EnsuresCalledMethods( - value = {"this.in1", "this.in2"}, - methods = {"close"}) - // :: error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - in1.close(); - in2.close(); + class ReplicaInputStreams implements Closeable { + + private final @Owning InputStream in1; + private final @Owning InputStream in2; + + public @MustCallAlias ReplicaInputStreams( + // This class is unsafe: calling close on i1 doesn't result in calling close on i2, + // so this MustCallAlias relationship shouldn't be verified. + // :: error: mustcallalias.out.of.scope + @MustCallAlias InputStream i1, @MustCallAlias InputStream i2) { + this.in1 = i1; + this.in2 = i2; + } + + @Override + @EnsuresCalledMethods( + value = {"this.in1", "this.in2"}, + methods = {"close"}) + // :: error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + in1.close(); + in2.close(); + } } - } } diff --git a/checker/tests/resourceleak/MultipleOwnedResources.java b/checker/tests/resourceleak/MultipleOwnedResources.java index 5e34f57b02c..5ae8e56825b 100644 --- a/checker/tests/resourceleak/MultipleOwnedResources.java +++ b/checker/tests/resourceleak/MultipleOwnedResources.java @@ -1,28 +1,29 @@ // Test case for https://github.com/typetools/checker-framework/issues/5911 -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MultipleOwnedResources implements Closeable { - private final @Owning Closeable r1; - private final @Owning Closeable r2; + private final @Owning Closeable r1; + private final @Owning Closeable r2; - public MultipleOwnedResources(@Owning Closeable r1, @Owning Closeable r2) { - this.r1 = r1; - this.r2 = r2; - } + public MultipleOwnedResources(@Owning Closeable r1, @Owning Closeable r2) { + this.r1 = r1; + this.r2 = r2; + } - @Override - @EnsuresCalledMethods( - value = {"r1", "r2"}, - methods = {"close"}) - public void close() throws IOException { - try { - r1.close(); - } finally { - r2.close(); + @Override + @EnsuresCalledMethods( + value = {"r1", "r2"}, + methods = {"close"}) + public void close() throws IOException { + try { + r1.close(); + } finally { + r2.close(); + } } - } } diff --git a/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java b/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java index 5a656764a6d..1478d1f4676 100644 --- a/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java +++ b/checker/tests/resourceleak/MultipleOwnedResourcesOfDifferentTypes.java @@ -5,40 +5,40 @@ class MultipleOwnedResourcesOfDifferentTypes { - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - @InheritableMustCall("b") - static class Bar { - void b() {} - } - - @InheritableMustCall("finalizer") - static class OwningField { - - private final @Owning Foo owningFoo; - - private final @Owning Bar owningBar; + @InheritableMustCall("a") + static class Foo { + void a() {} + } - public OwningField() { - this.owningFoo = new Foo(); - this.owningBar = new Bar(); + @InheritableMustCall("b") + static class Bar { + void b() {} } - @EnsuresCalledMethods( - value = {"owningBar"}, - methods = {"b"}) - @EnsuresCalledMethods( - value = {"this.owningFoo"}, - methods = {"a"}) - void finalizer() { - try { - this.owningFoo.a(); - } finally { - this.owningBar.b(); - } + @InheritableMustCall("finalizer") + static class OwningField { + + private final @Owning Foo owningFoo; + + private final @Owning Bar owningBar; + + public OwningField() { + this.owningFoo = new Foo(); + this.owningBar = new Bar(); + } + + @EnsuresCalledMethods( + value = {"owningBar"}, + methods = {"b"}) + @EnsuresCalledMethods( + value = {"this.owningFoo"}, + methods = {"a"}) + void finalizer() { + try { + this.owningFoo.a(); + } finally { + this.owningBar.b(); + } + } } - } } diff --git a/checker/tests/resourceleak/MultipleReturnStmts.java b/checker/tests/resourceleak/MultipleReturnStmts.java index 5fda1f4eff6..7764d948658 100644 --- a/checker/tests/resourceleak/MultipleReturnStmts.java +++ b/checker/tests/resourceleak/MultipleReturnStmts.java @@ -1,24 +1,25 @@ -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.checker.nullness.qual.*; +import java.io.*; + abstract class MultipleReturnStmts { - abstract @Nullable Closeable alloc(); + abstract @Nullable Closeable alloc(); - abstract boolean arbitraryChoice(); + abstract boolean arbitraryChoice(); - void method() throws IOException { + void method() throws IOException { - if (arbitraryChoice()) { - return; - } + if (arbitraryChoice()) { + return; + } - Closeable r1 = alloc(); - if (r1 == null) { - return; + Closeable r1 = alloc(); + if (r1 == null) { + return; + } + r1.close(); } - r1.close(); - } } diff --git a/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java b/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java index 9c3650f5a4a..4e61e6c94fa 100644 --- a/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java +++ b/checker/tests/resourceleak/MustCallAliasDifferentMethodNames.java @@ -9,38 +9,38 @@ class MustCallAliasDifferentMethodNames { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + @InheritableMustCall("b") + static class FooField { + private final @Owning Foo finalOwningFoo; - @InheritableMustCall("b") - static class FooField { - private final @Owning Foo finalOwningFoo; + public @MustCallAlias FooField(@MustCallAlias Foo f) { + this.finalOwningFoo = f; + } + + @EnsuresCalledMethods( + value = {"this.finalOwningFoo"}, + methods = {"a"}) + void b() { + this.finalOwningFoo.a(); + } + } - public @MustCallAlias FooField(@MustCallAlias Foo f) { - this.finalOwningFoo = f; + void testField1() { + Foo f = new Foo(); + FooField fooFieldWrapper = new FooField(f); + // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation + fooFieldWrapper.b(); } - @EnsuresCalledMethods( - value = {"this.finalOwningFoo"}, - methods = {"a"}) - void b() { - this.finalOwningFoo.a(); + void testField2() { + Foo f = new Foo(); + FooField fooFieldWrapper = new FooField(f); + // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation + f.a(); } - } - - void testField1() { - Foo f = new Foo(); - FooField fooFieldWrapper = new FooField(f); - // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation - fooFieldWrapper.b(); - } - - void testField2() { - Foo f = new Foo(); - FooField fooFieldWrapper = new FooField(f); - // Either calling f.a() or fooFieldWrapper.b() satisfies the obligation - f.a(); - } } diff --git a/checker/tests/resourceleak/MustCallAliasExamples.java b/checker/tests/resourceleak/MustCallAliasExamples.java index e45513be55d..d21d5908c6e 100644 --- a/checker/tests/resourceleak/MustCallAliasExamples.java +++ b/checker/tests/resourceleak/MustCallAliasExamples.java @@ -1,53 +1,54 @@ // Simple tests of @MustCallAlias functionality on wrapper streams. +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.io.IOException; import java.net.*; -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; class MustCallAliasExamples { - void test_two_locals(String address) { - Socket socket = null; - try { - socket = new Socket(address, 8000); - DataInputStream d = new DataInputStream(socket.getInputStream()); - } catch (IOException e) { + void test_two_locals(String address) { + Socket socket = null; + try { + socket = new Socket(address, 8000); + DataInputStream d = new DataInputStream(socket.getInputStream()); + } catch (IOException e) { + + } finally { + closeSocket(socket); + } + } + + void test_close_wrapper(@Owning InputStream b) throws IOException { + DataInputStream d = new DataInputStream(b); + d.close(); + } + + void test_close_nonwrapper(@Owning InputStream b) throws IOException { + DataInputStream d = new DataInputStream(b); + b.close(); + } - } finally { - closeSocket(socket); + // :: error: required.method.not.called + void test_no_close(@Owning InputStream b) { + DataInputStream d = new DataInputStream(b); } - } - - void test_close_wrapper(@Owning InputStream b) throws IOException { - DataInputStream d = new DataInputStream(b); - d.close(); - } - - void test_close_nonwrapper(@Owning InputStream b) throws IOException { - DataInputStream d = new DataInputStream(b); - b.close(); - } - - // :: error: required.method.not.called - void test_no_close(@Owning InputStream b) { - DataInputStream d = new DataInputStream(b); - } - - // :: error: required.method.not.called - void test_no_assign(@Owning InputStream b) { - new DataInputStream(new BufferedInputStream(b)); - } - - @EnsuresCalledMethods(value = "#1", methods = "close") - void closeSocket(Socket sock) { - try { - if (sock != null) { - sock.close(); - } - } catch (IOException e) { + // :: error: required.method.not.called + void test_no_assign(@Owning InputStream b) { + new DataInputStream(new BufferedInputStream(b)); + } + + @EnsuresCalledMethods(value = "#1", methods = "close") + void closeSocket(Socket sock) { + try { + if (sock != null) { + sock.close(); + } + } catch (IOException e) { + + } } - } } diff --git a/checker/tests/resourceleak/MustCallAliasImpl.java b/checker/tests/resourceleak/MustCallAliasImpl.java index 66b6794c089..91f17a4b915 100644 --- a/checker/tests/resourceleak/MustCallAliasImpl.java +++ b/checker/tests/resourceleak/MustCallAliasImpl.java @@ -1,23 +1,24 @@ // A simple test that the extra obligations that MustCallAlias imposes are // respected. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class MustCallAliasImpl implements Closeable { - final @Owning Closeable foo; + final @Owning Closeable foo; - public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { - this.foo = foo; - } + public @MustCallAlias MustCallAliasImpl(@MustCallAlias Closeable foo) { + this.foo = foo; + } - @Override - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) - public void close() throws IOException { - this.foo.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"close"}) + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasImplWrong1.java b/checker/tests/resourceleak/MustCallAliasImplWrong1.java index 912cc7b0b7b..7d07c69e6ba 100644 --- a/checker/tests/resourceleak/MustCallAliasImplWrong1.java +++ b/checker/tests/resourceleak/MustCallAliasImplWrong1.java @@ -2,24 +2,25 @@ // respected. This version gets it wrong by not assigning the MCA param // to a field. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class MustCallAliasImplWrong1 implements Closeable { - final @Owning Closeable foo; + final @Owning Closeable foo; - // :: error: mustcallalias.out.of.scope - public @MustCallAlias MustCallAliasImplWrong1(@MustCallAlias Closeable foo) { - this.foo = null; - } + // :: error: mustcallalias.out.of.scope + public @MustCallAlias MustCallAliasImplWrong1(@MustCallAlias Closeable foo) { + this.foo = null; + } - @Override - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) - public void close() throws IOException { - this.foo.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"close"}) + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasImplWrong2.java b/checker/tests/resourceleak/MustCallAliasImplWrong2.java index a5943151058..e031e88eeb2 100644 --- a/checker/tests/resourceleak/MustCallAliasImplWrong2.java +++ b/checker/tests/resourceleak/MustCallAliasImplWrong2.java @@ -2,23 +2,24 @@ // respected. This version gets it wrong by assigning the MCA param to a non-owning // field. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class MustCallAliasImplWrong2 implements Closeable { - final /*@Owning*/ Closeable foo; + final /*@Owning*/ Closeable foo; - // :: error: (mustcallalias.out.of.scope) - public @MustCallAlias MustCallAliasImplWrong2(@MustCallAlias Closeable foo) { - // The following error isn't really desirable, but occurs because the special case - // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields - // is not triggered. - // :: error: (assignment.type.incompatible) - this.foo = foo; - } + // :: error: (mustcallalias.out.of.scope) + public @MustCallAlias MustCallAliasImplWrong2(@MustCallAlias Closeable foo) { + // The following error isn't really desirable, but occurs because the special case + // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields + // is not triggered. + // :: error: (assignment.type.incompatible) + this.foo = foo; + } - @Override - public void close() {} + @Override + public void close() {} } diff --git a/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java b/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java index 07f964c9438..18701862772 100644 --- a/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java +++ b/checker/tests/resourceleak/MustCallAliasInitializeWithOwningParameter.java @@ -1,32 +1,33 @@ // Test that methods like `allocateAndInitializeService` are accepted by // the resource leak checker. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class MustCallAliasInitializeWithOwningParameter { - public Service allocateAndInitializeService(@Owning Closeable resource) throws IOException { - Service service = new Service(resource); - service.initialize(); - return service; - } + public Service allocateAndInitializeService(@Owning Closeable resource) throws IOException { + Service service = new Service(resource); + service.initialize(); + return service; + } - private static class Service implements Closeable { + private static class Service implements Closeable { - private final @Owning Closeable wrappedResource; + private final @Owning Closeable wrappedResource; - public @MustCallAlias Service(@MustCallAlias Closeable resource) { - this.wrappedResource = resource; - } + public @MustCallAlias Service(@MustCallAlias Closeable resource) { + this.wrappedResource = resource; + } - public void initialize() throws IOException {} + public void initialize() throws IOException {} - @Override - @EnsuresCalledMethods(value = "wrappedResource", methods = "close") - public void close() throws IOException { - wrappedResource.close(); + @Override + @EnsuresCalledMethods(value = "wrappedResource", methods = "close") + public void close() throws IOException { + wrappedResource.close(); + } } - } } diff --git a/checker/tests/resourceleak/MustCallAliasLayeredStreams.java b/checker/tests/resourceleak/MustCallAliasLayeredStreams.java index 2129a1206a6..29303050e63 100644 --- a/checker/tests/resourceleak/MustCallAliasLayeredStreams.java +++ b/checker/tests/resourceleak/MustCallAliasLayeredStreams.java @@ -1,22 +1,23 @@ // Test case based on an MCA situation in Zookeeper. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasLayeredStreams { - InputStream cache; + InputStream cache; - public InputStream createInputStream(String filename) throws FileNotFoundException { - if (cache == null) { - // The real version of this uses a mix of JDK and custom streams, so it makes more - // sense... - // TODO we shouldn't report a warning here and the code is okay because the cache is - // non-owning, and the caller of createInputStream is the owner of all of these streams. - cache = - new DataInputStream( - // :: error: required.method.not.called - new BufferedInputStream(new FileInputStream(new File(filename)))); + public InputStream createInputStream(String filename) throws FileNotFoundException { + if (cache == null) { + // The real version of this uses a mix of JDK and custom streams, so it makes more + // sense... + // TODO we shouldn't report a warning here and the code is okay because the cache is + // non-owning, and the caller of createInputStream is the owner of all of these streams. + cache = + new DataInputStream( + // :: error: required.method.not.called + new BufferedInputStream(new FileInputStream(new File(filename)))); + } + return cache; } - return cache; - } } diff --git a/checker/tests/resourceleak/MustCallAliasLocal.java b/checker/tests/resourceleak/MustCallAliasLocal.java index 7e6afcfb511..86b60579bdd 100644 --- a/checker/tests/resourceleak/MustCallAliasLocal.java +++ b/checker/tests/resourceleak/MustCallAliasLocal.java @@ -1,26 +1,27 @@ // Test case for a set of false positives caused by the Must Call Checker's handling // of assigning @MustCallAlias parameters to @Owning fields as a special case. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class MustCallAliasLocal implements Closeable { - final @Owning Closeable foo; + final @Owning Closeable foo; - public @MustCallAlias MustCallAliasLocal(@MustCallAlias Closeable foo) { - Closeable local = foo; - // The error on the following line is a false positive: - // :: error: (assignment.type.incompatible) - this.foo = local; - } + public @MustCallAlias MustCallAliasLocal(@MustCallAlias Closeable foo) { + Closeable local = foo; + // The error on the following line is a false positive: + // :: error: (assignment.type.incompatible) + this.foo = local; + } - @Override - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) - public void close() throws IOException { - this.foo.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"close"}) + public void close() throws IOException { + this.foo.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java b/checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java index 1c2570e6b4d..1aeb666d6d6 100644 --- a/checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java +++ b/checker/tests/resourceleak/MustCallAliasNoAnnoOnConstructor.java @@ -1,24 +1,25 @@ // Test case for https://github.com/typetools/checker-framework/issues/6376 -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + @InheritableMustCall("close") public class MustCallAliasNoAnnoOnConstructor { - final @Owning InputStream is; + final @Owning InputStream is; - // :: warning: (mustcallalias.method.return.and.param) - @MustCallAlias MustCallAliasNoAnnoOnConstructor(InputStream p, boolean b) throws Exception { - if (b) { - throw new Exception("an exception!"); + // :: warning: (mustcallalias.method.return.and.param) + @MustCallAlias MustCallAliasNoAnnoOnConstructor(InputStream p, boolean b) throws Exception { + if (b) { + throw new Exception("an exception!"); + } + this.is = p; } - this.is = p; - } - @EnsuresCalledMethods(value = "this.is", methods = "close") - public void close() throws IOException { - this.is.close(); - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasNormalExit.java b/checker/tests/resourceleak/MustCallAliasNormalExit.java index c79bcf60b0a..d84e08474aa 100644 --- a/checker/tests/resourceleak/MustCallAliasNormalExit.java +++ b/checker/tests/resourceleak/MustCallAliasNormalExit.java @@ -1,81 +1,82 @@ // Test case for https://github.com/typetools/checker-framework/issues/5597 -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + @InheritableMustCall("close") public class MustCallAliasNormalExit { - final @Owning InputStream is; + final @Owning InputStream is; - @MustCallAlias MustCallAliasNormalExit(@MustCallAlias InputStream p, boolean b) throws Exception { - if (b) { - throw new Exception("an exception!"); + @MustCallAlias MustCallAliasNormalExit(@MustCallAlias InputStream p, boolean b) throws Exception { + if (b) { + throw new Exception("an exception!"); + } + this.is = p; } - this.is = p; - } - @EnsuresCalledMethods(value = "this.is", methods = "close") - public void close() throws IOException { - this.is.close(); - } + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } - public static @MustCallAlias MustCallAliasNormalExit mcaneFactory(@MustCallAlias InputStream is) - throws Exception { - return new MustCallAliasNormalExit(is, false); - } + public static @MustCallAlias MustCallAliasNormalExit mcaneFactory(@MustCallAlias InputStream is) + throws Exception { + return new MustCallAliasNormalExit(is, false); + } - // :: error: required.method.not.called - public static void testUse1(@Owning InputStream inputStream) throws IOException { - MustCallAliasNormalExit mcane = null; - try { - mcane = new MustCallAliasNormalExit(inputStream, true); // at run time, this WILL throw - } catch (Exception e) { - // At run time would fail (NPE), but for illustrative purposes this is fine. - // This absolutely must not cause inputStream to be considered closed because of the - // MustCallAlias - // relationship. - mcane.close(); + // :: error: required.method.not.called + public static void testUse1(@Owning InputStream inputStream) throws IOException { + MustCallAliasNormalExit mcane = null; + try { + mcane = new MustCallAliasNormalExit(inputStream, true); // at run time, this WILL throw + } catch (Exception e) { + // At run time would fail (NPE), but for illustrative purposes this is fine. + // This absolutely must not cause inputStream to be considered closed because of the + // MustCallAlias + // relationship. + mcane.close(); + } } - } - // :: error: required.method.not.called - public static void testUse2(@Owning InputStream inputStream) throws IOException { - MustCallAliasNormalExit mcane = null; - try { - mcane = mcaneFactory(inputStream); - } catch (Exception e) { - mcane.close(); + // :: error: required.method.not.called + public static void testUse2(@Owning InputStream inputStream) throws IOException { + MustCallAliasNormalExit mcane = null; + try { + mcane = mcaneFactory(inputStream); + } catch (Exception e) { + mcane.close(); + } } - } - public static void testUse3(@Owning InputStream inputStream) throws Exception { - // If mcaneFactory throws, then inputStream goes out of scope w/o being closed. But, - // @Owning only requires that the resource be closed at normal exit, so this is OK. - MustCallAliasNormalExit mcane = mcaneFactory(inputStream); - mcane.close(); - } + public static void testUse3(@Owning InputStream inputStream) throws Exception { + // If mcaneFactory throws, then inputStream goes out of scope w/o being closed. But, + // @Owning only requires that the resource be closed at normal exit, so this is OK. + MustCallAliasNormalExit mcane = mcaneFactory(inputStream); + mcane.close(); + } - // TODO: this is a false positive due to imprecision in our analysis. At the program point - // before the call to mcane.close(), the inferred @CalledMethods type of inputStream is - // @CalledMethods(""), due to the control-flow merge. Further, this program point may be - // reached with mcane _not_ being a resource alias of inputStream, if mcaneFactory throws an - // exception. In this scenario, the analysis reasons that the exit may be reached without - // close() being called on inputStream. But, if mcane is not a resource alias of inputStream, - // then the inputStream.close() call in the catch block must have executed, so this is a false - // positive. Removing this false positive would require greater path sensitivity in the - // consistency analyzer, by tracking both resource aliases and @CalledMethods types for each - // path in an Obligation. See https://github.com/typetools/checker-framework/issues/5658 . - // :: error: required.method.not.called - public static void testUse4(@Owning InputStream inputStream) throws Exception { - MustCallAliasNormalExit mcane = null; - try { - mcane = mcaneFactory(inputStream); - } catch (Exception e) { - // this makes it safe - inputStream.close(); + // TODO: this is a false positive due to imprecision in our analysis. At the program point + // before the call to mcane.close(), the inferred @CalledMethods type of inputStream is + // @CalledMethods(""), due to the control-flow merge. Further, this program point may be + // reached with mcane _not_ being a resource alias of inputStream, if mcaneFactory throws an + // exception. In this scenario, the analysis reasons that the exit may be reached without + // close() being called on inputStream. But, if mcane is not a resource alias of inputStream, + // then the inputStream.close() call in the catch block must have executed, so this is a false + // positive. Removing this false positive would require greater path sensitivity in the + // consistency analyzer, by tracking both resource aliases and @CalledMethods types for each + // path in an Obligation. See https://github.com/typetools/checker-framework/issues/5658 . + // :: error: required.method.not.called + public static void testUse4(@Owning InputStream inputStream) throws Exception { + MustCallAliasNormalExit mcane = null; + try { + mcane = mcaneFactory(inputStream); + } catch (Exception e) { + // this makes it safe + inputStream.close(); + } + mcane.close(); } - mcane.close(); - } } diff --git a/checker/tests/resourceleak/MustCallAliasNotThis.java b/checker/tests/resourceleak/MustCallAliasNotThis.java index bbc3373e14b..aabdf3722cc 100644 --- a/checker/tests/resourceleak/MustCallAliasNotThis.java +++ b/checker/tests/resourceleak/MustCallAliasNotThis.java @@ -1,55 +1,56 @@ // A test case with examples of code that shouldn't pass the checker where an @MustCallAlias // parameter is passed to an owning field, but not an owning field of this. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public class MustCallAliasNotThis implements Closeable { - @Owning Closeable foo; - - // Both of these constructors are wrong: the first assigns to the owning field of another - // object of the same class, but not the "this" object; the second assigns to another - // class' owning field entirely. Both of these assignments would require - // @CreatesMustCallFor annotations to verify, so it's okay that the @MustCallAlias - // annotations are verified here (because it is impossible to write the required - // @CreatesMustCallFor annotations, since they can only be written on methods, not on - // constructors). If we ever permit @CreatesMustCallFor annotations on constructors, this - // test should be revisited: it might be necessary to make a corresponding change in the - // rules for verifying @MustCallAlias. - - // :: error: missing.creates.mustcall.for - public @MustCallAlias MustCallAliasNotThis( - @MustCallAlias Closeable foo, MustCallAliasNotThis other) throws IOException { - other.close(); - other.foo = foo; - } - - // :: error: missing.creates.mustcall.for - public @MustCallAlias MustCallAliasNotThis(@MustCallAlias Closeable foo, Bar other) - throws IOException { - other.close(); - other.baz = foo; - } - - @Override - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"close"}) - public void close() throws IOException { - this.foo.close(); - } - - class Bar implements Closeable { - @Owning Closeable baz; + @Owning Closeable foo; + + // Both of these constructors are wrong: the first assigns to the owning field of another + // object of the same class, but not the "this" object; the second assigns to another + // class' owning field entirely. Both of these assignments would require + // @CreatesMustCallFor annotations to verify, so it's okay that the @MustCallAlias + // annotations are verified here (because it is impossible to write the required + // @CreatesMustCallFor annotations, since they can only be written on methods, not on + // constructors). If we ever permit @CreatesMustCallFor annotations on constructors, this + // test should be revisited: it might be necessary to make a corresponding change in the + // rules for verifying @MustCallAlias. + + // :: error: missing.creates.mustcall.for + public @MustCallAlias MustCallAliasNotThis( + @MustCallAlias Closeable foo, MustCallAliasNotThis other) throws IOException { + other.close(); + other.foo = foo; + } + + // :: error: missing.creates.mustcall.for + public @MustCallAlias MustCallAliasNotThis(@MustCallAlias Closeable foo, Bar other) + throws IOException { + other.close(); + other.baz = foo; + } @Override @EnsuresCalledMethods( - value = {"this.baz"}, - methods = {"close"}) + value = {"this.foo"}, + methods = {"close"}) public void close() throws IOException { - this.baz.close(); + this.foo.close(); + } + + class Bar implements Closeable { + @Owning Closeable baz; + + @Override + @EnsuresCalledMethods( + value = {"this.baz"}, + methods = {"close"}) + public void close() throws IOException { + this.baz.close(); + } } - } } diff --git a/checker/tests/resourceleak/MustCallAliasNullConstructor.java b/checker/tests/resourceleak/MustCallAliasNullConstructor.java index ccaac0400da..d9add720829 100644 --- a/checker/tests/resourceleak/MustCallAliasNullConstructor.java +++ b/checker/tests/resourceleak/MustCallAliasNullConstructor.java @@ -1,26 +1,27 @@ // test for https://github.com/typetools/checker-framework/issues/5777 +import org.checkerframework.checker.calledmethods.qual.*; +import org.checkerframework.checker.mustcall.qual.*; + import java.io.Closeable; import java.io.IOException; import java.net.Socket; -import org.checkerframework.checker.calledmethods.qual.*; -import org.checkerframework.checker.mustcall.qual.*; public class MustCallAliasNullConstructor implements Closeable { - @Owning Socket socket; + @Owning Socket socket; - @MustCallAlias MustCallAliasNullConstructor(@MustCallAlias Socket s) throws IOException { - if (this.socket != null) { - this.socket.close(); + @MustCallAlias MustCallAliasNullConstructor(@MustCallAlias Socket s) throws IOException { + if (this.socket != null) { + this.socket.close(); + } + this.socket = s; + // :: error: required.method.not.called + this.socket = null; } - this.socket = s; - // :: error: required.method.not.called - this.socket = null; - } - @Override - @EnsuresCalledMethods(value = "this.socket", methods = "close") - public void close() throws IOException { - this.socket.close(); - } + @Override + @EnsuresCalledMethods(value = "this.socket", methods = "close") + public void close() throws IOException { + this.socket.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasOwningField.java b/checker/tests/resourceleak/MustCallAliasOwningField.java index 1c7ace724d1..95509805295 100644 --- a/checker/tests/resourceleak/MustCallAliasOwningField.java +++ b/checker/tests/resourceleak/MustCallAliasOwningField.java @@ -1,29 +1,30 @@ // Based on a MustCallAlias scenario in Zookeeper. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + public @InheritableMustCall("shutdown") class MustCallAliasOwningField { - private final @Owning BufferedInputStream input; + private final @Owning BufferedInputStream input; - public MustCallAliasOwningField(@Owning BufferedInputStream input, boolean b) { - this.input = input; - if (b) { - DataInputStream d = new DataInputStream(input); - authenticate(d); + public MustCallAliasOwningField(@Owning BufferedInputStream input, boolean b) { + this.input = input; + if (b) { + DataInputStream d = new DataInputStream(input); + authenticate(d); + } } - } - @EnsuresCalledMethods(value = "this.input", methods = "close") - public void shutdown() throws IOException { - input.close(); - } + @EnsuresCalledMethods(value = "this.input", methods = "close") + public void shutdown() throws IOException { + input.close(); + } - public static void authenticate(InputStream is) {} + public static void authenticate(InputStream is) {} - public void wrapField() { - DataInputStream dis = new DataInputStream(input); - } + public void wrapField() { + DataInputStream dis = new DataInputStream(input); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthrough.java b/checker/tests/resourceleak/MustCallAliasPassthrough.java index 86a58ca7ccc..d3dd4bcbef8 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthrough.java +++ b/checker/tests/resourceleak/MustCallAliasPassthrough.java @@ -1,12 +1,13 @@ // A test that a class can extend another class with an MCA constructor, // and have its own constructor be MCA as well. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthrough extends FilterInputStream { - @MustCallAlias MustCallAliasPassthrough(@MustCallAlias InputStream is) { - super(is); - } + @MustCallAlias MustCallAliasPassthrough(@MustCallAlias InputStream is) { + super(is); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughChain.java b/checker/tests/resourceleak/MustCallAliasPassthroughChain.java index c75c3d05a80..4bebae6aa29 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughChain.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughChain.java @@ -2,52 +2,53 @@ // chain // leads to errors. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughChain { - static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { - return is; - } - - static @MustCallAlias InputStream chain1(@MustCallAlias InputStream is) { - return withMCA(is); - } - - static @MustCallAlias InputStream chain2(@MustCallAlias InputStream is) { - InputStream s = withMCA(is); - return s; - } - - static @MustCallAlias InputStream chain3(@MustCallAlias InputStream is) { - return withMCA(chain1(is)); - } - - static @MustCallAlias InputStream chain4(@MustCallAlias InputStream is) { - return withMCA(chain1(chain3(is))); - } - - static @MustCallAlias InputStream chain5(@MustCallAlias InputStream is) { - InputStream s = withMCA(chain1(is)); - return s; - } - - // :: error: mustcallalias.out.of.scope - static @MustCallAlias InputStream chain_bad1(@MustCallAlias InputStream is) { - InputStream s = withMCA(chain1(is)); - return null; - } - - // :: error: mustcallalias.out.of.scope - static @MustCallAlias InputStream chain_bad2(@MustCallAlias InputStream is) { - withMCA(chain1(is)); - return null; - } - - // :: error: mustcallalias.out.of.scope - static @MustCallAlias InputStream chain_bad3(@MustCallAlias InputStream is, boolean b) { - return b ? null : withMCA(chain1(is)); - } + static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { + return is; + } + + static @MustCallAlias InputStream chain1(@MustCallAlias InputStream is) { + return withMCA(is); + } + + static @MustCallAlias InputStream chain2(@MustCallAlias InputStream is) { + InputStream s = withMCA(is); + return s; + } + + static @MustCallAlias InputStream chain3(@MustCallAlias InputStream is) { + return withMCA(chain1(is)); + } + + static @MustCallAlias InputStream chain4(@MustCallAlias InputStream is) { + return withMCA(chain1(chain3(is))); + } + + static @MustCallAlias InputStream chain5(@MustCallAlias InputStream is) { + InputStream s = withMCA(chain1(is)); + return s; + } + + // :: error: mustcallalias.out.of.scope + static @MustCallAlias InputStream chain_bad1(@MustCallAlias InputStream is) { + InputStream s = withMCA(chain1(is)); + return null; + } + + // :: error: mustcallalias.out.of.scope + static @MustCallAlias InputStream chain_bad2(@MustCallAlias InputStream is) { + withMCA(chain1(is)); + return null; + } + + // :: error: mustcallalias.out.of.scope + static @MustCallAlias InputStream chain_bad3(@MustCallAlias InputStream is, boolean b) { + return b ? null : withMCA(chain1(is)); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java b/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java index 67424865bea..f51266173cf 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughLocal.java @@ -1,22 +1,23 @@ // A test that passing a local to an MCA super constructor is allowed. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughLocal extends FilterInputStream { - MustCallAliasPassthroughLocal(File f) throws Exception { - // This is safe - this MCA constructor of FilterInputStream means that the result of this - // constructor - i.e. the caller - is taking ownership of this newly-created output stream. - super(new FileInputStream(f)); - } + MustCallAliasPassthroughLocal(File f) throws Exception { + // This is safe - this MCA constructor of FilterInputStream means that the result of this + // constructor - i.e. the caller - is taking ownership of this newly-created output stream. + super(new FileInputStream(f)); + } - static void test(File f) throws Exception { - // :: error: required.method.not.called - new MustCallAliasPassthroughLocal(f); - } + static void test(File f) throws Exception { + // :: error: required.method.not.called + new MustCallAliasPassthroughLocal(f); + } - static void test_ok(File f) throws Exception { - new MustCallAliasPassthroughLocal(f).close(); - } + static void test_ok(File f) throws Exception { + new MustCallAliasPassthroughLocal(f).close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughThis.java b/checker/tests/resourceleak/MustCallAliasPassthroughThis.java index decc01adf57..3ecb02e1d11 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughThis.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughThis.java @@ -1,15 +1,16 @@ // A test that a class can have multiple MCA constructors. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughThis extends FilterInputStream { - @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is) { - super(is); - } + @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is) { + super(is); + } - @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is, int x) { - this(is); - } + @MustCallAlias MustCallAliasPassthroughThis(@MustCallAlias InputStream is, int x) { + this(is); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java index c3a4a05f117..063f8a9b427 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong1.java @@ -2,13 +2,14 @@ // and have its own constructor be MCA as well. // This version just throws away the input rather than passing it to the super constructor. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughWrong1 extends FilterInputStream { - // :: error: mustcallalias.out.of.scope - @MustCallAlias MustCallAliasPassthroughWrong1(@MustCallAlias InputStream is) { - super(null); - } + // :: error: mustcallalias.out.of.scope + @MustCallAlias MustCallAliasPassthroughWrong1(@MustCallAlias InputStream is) { + super(null); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java index fd7bbb7e09b..f5a8741d06e 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong2.java @@ -5,22 +5,23 @@ // MCA annotation on the return type is super misleading and will lead to FPs. It would be better // to annotate code like this with @Owning on the constructor. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughWrong2 extends FilterInputStream { - // :: error: mustcallalias.out.of.scope - @MustCallAlias MustCallAliasPassthroughWrong2(@MustCallAlias InputStream is) throws Exception { - super(null); - // The following error isn't really desirable, but occurs because the special case - // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields - // is not triggered, and @MustCallAlias is treated as @PolyMustCall otherwise. - // :: error: (argument.type.incompatible) - closeIS(is); - } + // :: error: mustcallalias.out.of.scope + @MustCallAlias MustCallAliasPassthroughWrong2(@MustCallAlias InputStream is) throws Exception { + super(null); + // The following error isn't really desirable, but occurs because the special case + // in the Must Call Checker for assigning @MustCallAlias parameters to @Owning fields + // is not triggered, and @MustCallAlias is treated as @PolyMustCall otherwise. + // :: error: (argument.type.incompatible) + closeIS(is); + } - void closeIS(@Owning InputStream is) throws Exception { - is.close(); - } + void closeIS(@Owning InputStream is) throws Exception { + is.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java index 9d1bafe4dd6..47a5fc942a8 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong3.java @@ -1,29 +1,30 @@ // This is a test for what happens when there's a missing MCA return type. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughWrong3 { - // :: warning: (mustcallalias.method.return.and.param) - static InputStream missingMCA(@MustCallAlias InputStream is) { - // :: error: (return.type.incompatible) - return is; - } + // :: warning: (mustcallalias.method.return.and.param) + static InputStream missingMCA(@MustCallAlias InputStream is) { + // :: error: (return.type.incompatible) + return is; + } - static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { - return is; - } + static @MustCallAlias InputStream withMCA(@MustCallAlias InputStream is) { + return is; + } - // :: error: (required.method.not.called) - void use_bad(@Owning InputStream is) throws Exception { - InputStream is2 = missingMCA(is); - is2.close(); - } + // :: error: (required.method.not.called) + void use_bad(@Owning InputStream is) throws Exception { + InputStream is2 = missingMCA(is); + is2.close(); + } - void use_good(@Owning InputStream is) throws Exception { - InputStream is2 = withMCA(is); - is2.close(); - } + void use_good(@Owning InputStream is) throws Exception { + InputStream is2 = withMCA(is); + is2.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java b/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java index 08f0b882ebc..1be0c7cfda3 100644 --- a/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java +++ b/checker/tests/resourceleak/MustCallAliasPassthroughWrong4.java @@ -4,14 +4,15 @@ // test for it. Issuing an error here is appropriate, because no aliasing relationship // actually exists after the constructor returns. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class MustCallAliasPassthroughWrong4 extends FilterInputStream { - // :: error: mustcallalias.out.of.scope - @MustCallAlias MustCallAliasPassthroughWrong4(@MustCallAlias InputStream is) throws Exception { - super(null); - is.close(); - } + // :: error: mustcallalias.out.of.scope + @MustCallAlias MustCallAliasPassthroughWrong4(@MustCallAlias InputStream is) throws Exception { + super(null); + is.close(); + } } diff --git a/checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java b/checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java index df7329b5910..a07cf094aa4 100644 --- a/checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java +++ b/checker/tests/resourceleak/MustCallAliasReturnAndParamSimple.java @@ -1,59 +1,60 @@ // Test case for https://github.com/typetools/checker-framework/issues/6376 -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + @InheritableMustCall("close") public class MustCallAliasReturnAndParamSimple { - final @Owning InputStream is; - - // Should have no error here. - @MustCallAlias MustCallAliasReturnAndParamSimple someFluentMethod( - @MustCallAlias MustCallAliasReturnAndParamSimple this) { - return this; - } - - // :: warning: (mustcallalias.method.return.and.param) - @MustCallAlias MustCallAliasReturnAndParamSimple someIncorrectlyAnnotatedFluentMethod( - MustCallAliasReturnAndParamSimple this) { - return this; - } - - @MustCallAlias MustCallAliasReturnAndParamSimple(@MustCallAlias InputStream p, boolean b) throws Exception { - if (b) { - throw new Exception("an exception!"); - } - this.is = p; - } - - @EnsuresCalledMethods(value = "this.is", methods = "close") - public void close() throws IOException { - this.is.close(); - } - - // No errors or warnings here, properly annotated. - public static @MustCallAlias MustCallAliasReturnAndParamSimple mcaneFactory( - @MustCallAlias InputStream is) throws Exception { - return new MustCallAliasReturnAndParamSimple(is, false); - } - - // :: warning: (mustcallalias.method.return.and.param) - public static MustCallAliasReturnAndParamSimple mcaneFactory2(@MustCallAlias InputStream is) - throws Exception { - // :: error: (return) - return new MustCallAliasReturnAndParamSimple(is, false); - } - - // :: warning: (mustcallalias.method.return.and.param) - public static @MustCallAlias MustCallAliasReturnAndParamSimple mcaneFactory3(InputStream is) - throws Exception { - return new MustCallAliasReturnAndParamSimple(is, false); - } - - // No warning here; neither the return type or parameter is annotated with @MustCallAlias - public static MustCallAliasReturnAndParamSimple mcaneFactory4(InputStream is) throws Exception { - return new MustCallAliasReturnAndParamSimple(is, false); - } + final @Owning InputStream is; + + // Should have no error here. + @MustCallAlias MustCallAliasReturnAndParamSimple someFluentMethod( + @MustCallAlias MustCallAliasReturnAndParamSimple this) { + return this; + } + + // :: warning: (mustcallalias.method.return.and.param) + @MustCallAlias MustCallAliasReturnAndParamSimple someIncorrectlyAnnotatedFluentMethod( + MustCallAliasReturnAndParamSimple this) { + return this; + } + + @MustCallAlias MustCallAliasReturnAndParamSimple(@MustCallAlias InputStream p, boolean b) throws Exception { + if (b) { + throw new Exception("an exception!"); + } + this.is = p; + } + + @EnsuresCalledMethods(value = "this.is", methods = "close") + public void close() throws IOException { + this.is.close(); + } + + // No errors or warnings here, properly annotated. + public static @MustCallAlias MustCallAliasReturnAndParamSimple mcaneFactory( + @MustCallAlias InputStream is) throws Exception { + return new MustCallAliasReturnAndParamSimple(is, false); + } + + // :: warning: (mustcallalias.method.return.and.param) + public static MustCallAliasReturnAndParamSimple mcaneFactory2(@MustCallAlias InputStream is) + throws Exception { + // :: error: (return) + return new MustCallAliasReturnAndParamSimple(is, false); + } + + // :: warning: (mustcallalias.method.return.and.param) + public static @MustCallAlias MustCallAliasReturnAndParamSimple mcaneFactory3(InputStream is) + throws Exception { + return new MustCallAliasReturnAndParamSimple(is, false); + } + + // No warning here; neither the return type or parameter is annotated with @MustCallAlias + public static MustCallAliasReturnAndParamSimple mcaneFactory4(InputStream is) throws Exception { + return new MustCallAliasReturnAndParamSimple(is, false); + } } diff --git a/checker/tests/resourceleak/MustCallAliasSocketException.java b/checker/tests/resourceleak/MustCallAliasSocketException.java index 56db01e82c5..ef5dffc989a 100644 --- a/checker/tests/resourceleak/MustCallAliasSocketException.java +++ b/checker/tests/resourceleak/MustCallAliasSocketException.java @@ -5,24 +5,24 @@ class MustCallAliasSocketException { - public boolean quorumRequireSasl; + public boolean quorumRequireSasl; - // This socket isn't owning, so we shouldn't warn on it. - public void authenticate(Socket sock, String hostName) throws IOException { - if (!quorumRequireSasl) { - // I kept this block in the test case because it demonstrates - // that sock is definitely non-owning. - System.out.println("Skipping SASL authentication as"); - return; + // This socket isn't owning, so we shouldn't warn on it. + public void authenticate(Socket sock, String hostName) throws IOException { + if (!quorumRequireSasl) { + // I kept this block in the test case because it demonstrates + // that sock is definitely non-owning. + System.out.println("Skipping SASL authentication as"); + return; + } + try { + DataOutputStream dout = new DataOutputStream(sock.getOutputStream()); + // Before MCA was implemented, the call to getInputStream() below triggered + // a false positive warning that dout had not been closed. + DataInputStream din = new DataInputStream(sock.getInputStream()); + // ~30 lines omitted... + } finally { + // do some other things that are definitely not closing sock + } } - try { - DataOutputStream dout = new DataOutputStream(sock.getOutputStream()); - // Before MCA was implemented, the call to getInputStream() below triggered - // a false positive warning that dout had not been closed. - DataInputStream din = new DataInputStream(sock.getInputStream()); - // ~30 lines omitted... - } finally { - // do some other things that are definitely not closing sock - } - } } diff --git a/checker/tests/resourceleak/MustCallAliasSubstitution.java b/checker/tests/resourceleak/MustCallAliasSubstitution.java index 5e27e189334..2b1d08ec6e2 100644 --- a/checker/tests/resourceleak/MustCallAliasSubstitution.java +++ b/checker/tests/resourceleak/MustCallAliasSubstitution.java @@ -2,24 +2,25 @@ // substituting a fresh object is not counted as an alias, for the purpose of MustCallAlias // verification. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.net.Socket; -import org.checkerframework.checker.mustcall.qual.*; class MustCallAliasSubstitution { - // :: error: mustcallalias.out.of.scope - static @MustCallAlias Closeable example(@MustCallAlias Closeable p) throws IOException { - p.close(); - return new Socket("localhost", 5000); - } + // :: error: mustcallalias.out.of.scope + static @MustCallAlias Closeable example(@MustCallAlias Closeable p) throws IOException { + p.close(); + return new Socket("localhost", 5000); + } - // This method demonstrates how a false negative could occur, if no error was issued - // on example(). - void use(Closeable c) throws IOException { - // s never gets closed, but the checker permits this code, because it believes - // that s and c are aliased. - Closeable s = example(c); - c.close(); - } + // This method demonstrates how a false negative could occur, if no error was issued + // on example(). + void use(Closeable c) throws IOException { + // s never gets closed, but the checker permits this code, because it believes + // that s and c are aliased. + Closeable s = example(c); + c.close(); + } } diff --git a/checker/tests/resourceleak/MustCallNullStore.java b/checker/tests/resourceleak/MustCallNullStore.java index 0b88c11696d..07551a3c19d 100644 --- a/checker/tests/resourceleak/MustCallNullStore.java +++ b/checker/tests/resourceleak/MustCallNullStore.java @@ -3,18 +3,18 @@ // input exposing a bug where store after the return is null class MustCallNullStore { - int inflateDirect(ByteBuffer src, ByteBuffer dst) throws java.io.IOException { + int inflateDirect(ByteBuffer src, ByteBuffer dst) throws java.io.IOException { - ByteBuffer presliced = dst; - if (dst.position() > 0) { - dst = dst.slice(); - } + ByteBuffer presliced = dst; + if (dst.position() > 0) { + dst = dst.slice(); + } - int n = 0; - try { - presliced.position(presliced.position() + n); - } finally { + int n = 0; + try { + presliced.position(presliced.position() + n); + } finally { + } + return n; } - return n; - } } diff --git a/checker/tests/resourceleak/MustCloseIntoObject.java b/checker/tests/resourceleak/MustCloseIntoObject.java index b82c5143c15..200568364a5 100644 --- a/checker/tests/resourceleak/MustCloseIntoObject.java +++ b/checker/tests/resourceleak/MustCloseIntoObject.java @@ -4,8 +4,8 @@ import java.net.Socket; class MustCloseIntoObject { - void test() throws Exception { - // :: error: required.method.not.called - Object o = new Socket("", 0); - } + void test() throws Exception { + // :: error: required.method.not.called + Object o = new Socket("", 0); + } } diff --git a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java index 244ff3ffde2..bf2dea2d533 100644 --- a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java +++ b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull.java @@ -1,88 +1,89 @@ // A test that must-call close errors are not issued when overwriting a field // if the field is definitely null. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import java.io.*; + @InheritableMustCall("close") class NonFinalFieldOnlyOverwrittenIfNull { - @Owning @MonotonicNonNull InputStream is; + @Owning @MonotonicNonNull InputStream is; - @CreatesMustCallFor - void set(String fn) throws FileNotFoundException { - if (is == null) { - is = new FileInputStream(fn); + @CreatesMustCallFor + void set(String fn) throws FileNotFoundException { + if (is == null) { + is = new FileInputStream(fn); + } } - } - @CreatesMustCallFor - void set_after_close(String fn, boolean b) throws IOException { - if (b) { - is.close(); - is = new FileInputStream(fn); + @CreatesMustCallFor + void set_after_close(String fn, boolean b) throws IOException { + if (b) { + is.close(); + is = new FileInputStream(fn); + } } - } - @CreatesMustCallFor - void set_error(String fn, boolean b) throws FileNotFoundException { - if (b) { - // :: error: required.method.not.called - is = new FileInputStream(fn); + @CreatesMustCallFor + void set_error(String fn, boolean b) throws FileNotFoundException { + if (b) { + // :: error: required.method.not.called + is = new FileInputStream(fn); + } } - } - // These three methods are copies of the three above, without the appropriate annotation. - // :: error: missing.creates.mustcall.for - void set2(String fn) throws FileNotFoundException { - if (is == null) { - is = new FileInputStream(fn); + // These three methods are copies of the three above, without the appropriate annotation. + // :: error: missing.creates.mustcall.for + void set2(String fn) throws FileNotFoundException { + if (is == null) { + is = new FileInputStream(fn); + } } - } - // :: error: missing.creates.mustcall.for - void set_after_close2(String fn, boolean b) throws IOException { - if (b) { - is.close(); - is = new FileInputStream(fn); + // :: error: missing.creates.mustcall.for + void set_after_close2(String fn, boolean b) throws IOException { + if (b) { + is.close(); + is = new FileInputStream(fn); + } } - } - // :: error: missing.creates.mustcall.for - void set_error2(String fn, boolean b) throws FileNotFoundException { - if (b) { - // :: error: required.method.not.called - is = new FileInputStream(fn); + // :: error: missing.creates.mustcall.for + void set_error2(String fn, boolean b) throws FileNotFoundException { + if (b) { + // :: error: required.method.not.called + is = new FileInputStream(fn); + } } - } - /* This version of close() doesn't verify, because in the `catch` block - `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker - @EnsuresCalledMethods(value="this.is", methods="close") - void close_real() { - if (is != null) { - try { - is.close(); - } catch (Exception ie) { + /* This version of close() doesn't verify, because in the `catch` block + `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker + @EnsuresCalledMethods(value="this.is", methods="close") + void close_real() { + if (is != null) { + try { + is.close(); + } catch (Exception ie) { - } - } - } */ + } + } + } */ - @EnsuresCalledMethods(value = "this.is", methods = "close") - void close() throws Exception { - if (is != null) { - is.close(); + @EnsuresCalledMethods(value = "this.is", methods = "close") + void close() throws Exception { + if (is != null) { + is.close(); + } } - } - public static void test_leak() throws Exception { - // :: error: required.method.not.called - NonFinalFieldOnlyOverwrittenIfNull n = new NonFinalFieldOnlyOverwrittenIfNull(); - n.close(); - n.set_after_close("bar.txt", true); - } + public static void test_leak() throws Exception { + // :: error: required.method.not.called + NonFinalFieldOnlyOverwrittenIfNull n = new NonFinalFieldOnlyOverwrittenIfNull(); + n.close(); + n.set_after_close("bar.txt", true); + } } diff --git a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java index 182fa5c8e59..c06aeba8a9c 100644 --- a/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java +++ b/checker/tests/resourceleak/NonFinalFieldOnlyOverwrittenIfNull2.java @@ -1,67 +1,68 @@ // Another test that must-call close errors are not issued when overwriting a field // if the field is definitely null. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.InheritableMustCall; import org.checkerframework.checker.mustcall.qual.Owning; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import java.io.*; + @InheritableMustCall("close") class NonFinalFieldOnlyOverwrittenIfNull2 { - @Owning @MonotonicNonNull InputStream is; + @Owning @MonotonicNonNull InputStream is; - @CreatesMustCallFor - void set(String fn) throws FileNotFoundException { - if (is == null) { - is = new FileInputStream(fn); + @CreatesMustCallFor + void set(String fn) throws FileNotFoundException { + if (is == null) { + is = new FileInputStream(fn); + } } - } - @CreatesMustCallFor - void set_after_close(String fn, boolean b) throws IOException { - if (b) { - is.close(); - is = new FileInputStream(fn); + @CreatesMustCallFor + void set_after_close(String fn, boolean b) throws IOException { + if (b) { + is.close(); + is = new FileInputStream(fn); + } } - } - @CreatesMustCallFor - void set_error(String fn, boolean b) throws FileNotFoundException { - if (b) { - // :: error: required.method.not.called - is = new FileInputStream(fn); + @CreatesMustCallFor + void set_error(String fn, boolean b) throws FileNotFoundException { + if (b) { + // :: error: required.method.not.called + is = new FileInputStream(fn); + } } - } - /* This version of close() doesn't verify, because in the `catch` block - `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker - @EnsuresCalledMethods(value="this.is", methods="close") - void close_real() { - if (is != null) { - try { - is.close(); - } catch (Exception ie) { + /* This version of close() doesn't verify, because in the `catch` block + `is` isn't @CalledMethods("close"). TODO: investigate that in the CM checker + @EnsuresCalledMethods(value="this.is", methods="close") + void close_real() { + if (is != null) { + try { + is.close(); + } catch (Exception ie) { - } - } - } */ + } + } + } */ - @EnsuresCalledMethods(value = "this.is", methods = "close") - @CreatesMustCallFor - void close() throws Exception { - if (is != null) { - is.close(); - is = null; + @EnsuresCalledMethods(value = "this.is", methods = "close") + @CreatesMustCallFor + void close() throws Exception { + if (is != null) { + is.close(); + is = null; + } } - } - public static void test_leak() throws Exception { - // :: error: required.method.not.called - NonFinalFieldOnlyOverwrittenIfNull2 n = new NonFinalFieldOnlyOverwrittenIfNull2(); - n.set("foo.txt"); - n.close(); - n.set("bar.txt"); - } + public static void test_leak() throws Exception { + // :: error: required.method.not.called + NonFinalFieldOnlyOverwrittenIfNull2 n = new NonFinalFieldOnlyOverwrittenIfNull2(); + n.set("foo.txt"); + n.close(); + n.set("bar.txt"); + } } diff --git a/checker/tests/resourceleak/OptionalSocket.java b/checker/tests/resourceleak/OptionalSocket.java index f1153eeca00..60f726e8c7f 100644 --- a/checker/tests/resourceleak/OptionalSocket.java +++ b/checker/tests/resourceleak/OptionalSocket.java @@ -4,22 +4,23 @@ // which case it's fine with me to skip this test for now. - Martin // @skip-test +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.net.*; import java.util.*; -import org.checkerframework.checker.mustcall.qual.*; class OptionalSocket { - void test_close_get_null(@Owning Optional sock) throws IOException { - // TODO can't pass this - if (sock.get() != null) { - // TODO can't pass this - sock.get().close(); + void test_close_get_null(@Owning Optional sock) throws IOException { + // TODO can't pass this + if (sock.get() != null) { + // TODO can't pass this + sock.get().close(); + } } - } - void test_close_get(@Owning Optional sock) throws IOException { - // TODO can't pass this - sock.get().close(); - } + void test_close_get(@Owning Optional sock) throws IOException { + // TODO can't pass this + sock.get().close(); + } } diff --git a/checker/tests/resourceleak/OwnershipTransferAtReassignment.java b/checker/tests/resourceleak/OwnershipTransferAtReassignment.java index 53582ab46a0..85ddab1d576 100644 --- a/checker/tests/resourceleak/OwnershipTransferAtReassignment.java +++ b/checker/tests/resourceleak/OwnershipTransferAtReassignment.java @@ -9,30 +9,30 @@ @InheritableMustCall("disconnect") public class OwnershipTransferAtReassignment { - private @Owning @Nullable Node head = null; + private @Owning @Nullable Node head = null; - @CreatesMustCallFor("this") - public boolean add() { - head = new Node(head); - return true; - } + @CreatesMustCallFor("this") + public boolean add() { + head = new Node(head); + return true; + } - @EnsuresCalledMethods(value = "this.head", methods = "disconnect") - public void disconnect() { - head.disconnect(); - } + @EnsuresCalledMethods(value = "this.head", methods = "disconnect") + public void disconnect() { + head.disconnect(); + } - @InheritableMustCall("disconnect") - private static class Node { - @Owning private final @Nullable Node next; + @InheritableMustCall("disconnect") + private static class Node { + @Owning private final @Nullable Node next; - public Node(@Owning @Nullable Node next) { - this.next = next; - } + public Node(@Owning @Nullable Node next) { + this.next = next; + } - @EnsuresCalledMethods(value = "this.next", methods = "disconnect") - public void disconnect() { - next.disconnect(); + @EnsuresCalledMethods(value = "this.next", methods = "disconnect") + public void disconnect() { + next.disconnect(); + } } - } } diff --git a/checker/tests/resourceleak/OwnershipWithExceptions.java b/checker/tests/resourceleak/OwnershipWithExceptions.java index 39e794c34e4..f9403669387 100644 --- a/checker/tests/resourceleak/OwnershipWithExceptions.java +++ b/checker/tests/resourceleak/OwnershipWithExceptions.java @@ -1,265 +1,266 @@ // Test case for https://github.com/typetools/checker-framework/issues/6179 // (and other rules regarding @Owning and exceptions) -import java.io.Closeable; -import java.io.IOException; -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.dataflow.qual.SideEffectFree; +import java.io.Closeable; +import java.io.IOException; +import java.net.Socket; + abstract class OwnershipWithExceptions { - static class ManualExample1 { - void example(String myHost, int myPort) throws IOException { - Socket s = new Socket(myHost, myPort); - closeSocket(s); - } + static class ManualExample1 { + void example(String myHost, int myPort) throws IOException { + Socket s = new Socket(myHost, myPort); + closeSocket(s); + } - void closeSocket(@Owning @MustCall("close") Socket t) { - try { - t.close(); - } catch (IOException e) { - e.printStackTrace(); - } + void closeSocket(@Owning @MustCall("close") Socket t) { + try { + t.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } - } - static class ManualExample2 { - void example(String myHost, int myPort) throws Exception { - // Error: `s` is not closed on all paths - // ::error: (required.method.not.called) - Socket s = new Socket(myHost, myPort); + static class ManualExample2 { + void example(String myHost, int myPort) throws Exception { + // Error: `s` is not closed on all paths + // ::error: (required.method.not.called) + Socket s = new Socket(myHost, myPort); - // `closeSocket` does not have to close `s` when it throws IOException. - // Instead, this method has to catch the exception and close `s`. - closeSocket(s); - } + // `closeSocket` does not have to close `s` when it throws IOException. + // Instead, this method has to catch the exception and close `s`. + closeSocket(s); + } - void closeSocket(@Owning Socket t) throws IOException { - throw new IOException(); + void closeSocket(@Owning Socket t) throws IOException { + throw new IOException(); + } } - } - abstract @Owning Closeable alloc(); + abstract @Owning Closeable alloc(); - @SideEffectFree - abstract boolean arbitraryChoice(); + @SideEffectFree + abstract boolean arbitraryChoice(); - abstract void transfer(@Owning Closeable resource) throws IOException; + abstract void transfer(@Owning Closeable resource) throws IOException; - void transferAndPropagateException(@Owning Closeable resource) throws IOException { - transfer(resource); - } - - void transferHasNoObligationsOnException(@Owning Closeable resource) throws IOException { - throw new IOException(); - } - - // :: error: (required.method.not.called) - void transferAndIgnoreExceptionWithoutClosing(@Owning Closeable zzz) { - try { - transfer(zzz); - } catch (IOException ignored) { - } - } - - boolean transferAndIgnoreExceptionCorrectly(@Owning Closeable resource) { - try { - transfer(resource); - return true; - } catch (Exception e) { - try { - resource.close(); - } catch (Exception other) { - } - return false; - } - } - - // Passing an argument as an @Owning parameter does not transfer ownership if - // the called method throws. So, this is not correct: if transfer(resource) - // throws an exception, it leaks the resource. - void noExceptionHandling() throws IOException { - // ::error: (required.method.not.called) - Closeable resource = alloc(); - // ::error: (assignment.type.incompatible) - @CalledMethods("close") Closeable a = resource; - transfer(resource); - // ::error: (assignment.type.incompatible) - @CalledMethods("close") Closeable b = resource; - } - - class FinalOwnedField implements Closeable { - - final @Owning Closeable resource; - - FinalOwnedField() throws IOException { - // Field assignments in constructors are special. When the constructor - // exits by exception, the field becomes permanently inaccessible, and - // therefore the allocated resource is leaked. - // :: error: (required.method.not.called) - resource = alloc(); - if (arbitraryChoice()) { - throw new IOException(); - } + void transferAndPropagateException(@Owning Closeable resource) throws IOException { + transfer(resource); } - FinalOwnedField(@Owning Closeable resource) throws IOException { - // Although, when the resource was passed by a caller, then we can be - // more relaxed. On exception, ownership remains with the caller. - this.resource = resource; - if (arbitraryChoice()) { + void transferHasNoObligationsOnException(@Owning Closeable resource) throws IOException { throw new IOException(); - } } - FinalOwnedField(@Owning Closeable resource, boolean arg) throws IOException { - // Same as the previous constructor, but in the other order. - if (arbitraryChoice()) { - throw new IOException(); - } - this.resource = resource; - } - - FinalOwnedField(int ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 1). - resource = alloc(); - try { - if (arbitraryChoice()) { - throw new IOException(); + // :: error: (required.method.not.called) + void transferAndIgnoreExceptionWithoutClosing(@Owning Closeable zzz) { + try { + transfer(zzz); + } catch (IOException ignored) { } - } catch (Exception e) { - resource.close(); - throw e; - } } - FinalOwnedField(float ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 2). - Closeable r = alloc(); - resource = r; - try { - if (arbitraryChoice()) { - throw new IOException(); + boolean transferAndIgnoreExceptionCorrectly(@Owning Closeable resource) { + try { + transfer(resource); + return true; + } catch (Exception e) { + try { + resource.close(); + } catch (Exception other) { + } + return false; } - } catch (Exception e) { - r.close(); - throw e; - } } - // Not allowed: destructors have to close @Owning fields even on exception. - @Override - @EnsuresCalledMethods( - value = "this.resource", - methods = {"close"}) - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - throw new IOException(); + // Passing an argument as an @Owning parameter does not transfer ownership if + // the called method throws. So, this is not correct: if transfer(resource) + // throws an exception, it leaks the resource. + void noExceptionHandling() throws IOException { + // ::error: (required.method.not.called) + Closeable resource = alloc(); + // ::error: (assignment.type.incompatible) + @CalledMethods("close") Closeable a = resource; + transfer(resource); + // ::error: (assignment.type.incompatible) + @CalledMethods("close") Closeable b = resource; } - } - // Classes with >1 owned field are treated slightly differently - // (see ./TwoOwningMCATest.java) - class TwoOwnedFields implements Closeable { + class FinalOwnedField implements Closeable { - final @Owning Closeable unused = null; + final @Owning Closeable resource; - final @Owning Closeable resource; + FinalOwnedField() throws IOException { + // Field assignments in constructors are special. When the constructor + // exits by exception, the field becomes permanently inaccessible, and + // therefore the allocated resource is leaked. + // :: error: (required.method.not.called) + resource = alloc(); + if (arbitraryChoice()) { + throw new IOException(); + } + } - TwoOwnedFields() throws IOException { - // Field assignments in constructors are special. When the constructor - // exits by exception, the field becomes permanently inaccessible, and - // therefore the allocated resource is leaked. - // :: error: (required.method.not.called) - resource = alloc(); - if (arbitraryChoice()) { - throw new IOException(); - } - } + FinalOwnedField(@Owning Closeable resource) throws IOException { + // Although, when the resource was passed by a caller, then we can be + // more relaxed. On exception, ownership remains with the caller. + this.resource = resource; + if (arbitraryChoice()) { + throw new IOException(); + } + } - TwoOwnedFields(@Owning Closeable resource) throws IOException { - // Although, when the resource was passed by a caller, then we can be - // more relaxed. On exception, ownership remains with the caller. - this.resource = resource; - if (arbitraryChoice()) { - throw new IOException(); - } - } + FinalOwnedField(@Owning Closeable resource, boolean arg) throws IOException { + // Same as the previous constructor, but in the other order. + if (arbitraryChoice()) { + throw new IOException(); + } + this.resource = resource; + } - TwoOwnedFields(@Owning Closeable resource, boolean arg) throws IOException { - // Same as the previous constructor, but in the other order. - if (arbitraryChoice()) { - throw new IOException(); - } - this.resource = resource; - } + FinalOwnedField(int ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 1). + resource = alloc(); + try { + if (arbitraryChoice()) { + throw new IOException(); + } + } catch (Exception e) { + resource.close(); + throw e; + } + } - TwoOwnedFields(int ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 1). - resource = alloc(); - try { - if (arbitraryChoice()) { - throw new IOException(); + FinalOwnedField(float ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 2). + Closeable r = alloc(); + resource = r; + try { + if (arbitraryChoice()) { + throw new IOException(); + } + } catch (Exception e) { + r.close(); + throw e; + } } - } catch (Exception e) { - resource.close(); - throw e; - } - } - TwoOwnedFields(float ignored) throws IOException { - // Same as the 0-argument constructor, but handled correctly (algorithm 2). - Closeable r = alloc(); - resource = r; - try { - if (arbitraryChoice()) { - throw new IOException(); + // Not allowed: destructors have to close @Owning fields even on exception. + @Override + @EnsuresCalledMethods( + value = "this.resource", + methods = {"close"}) + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + throw new IOException(); } - } catch (Exception e) { - r.close(); - throw e; - } } - // Not allowed: destructors have to close @Owning fields even on exception. - // In this case, the exception from `unused.close()` can prematurely stop the method. - @Override - @EnsuresCalledMethods( - value = {"this.resource", "this.unused"}, - methods = {"close"}) - // ::error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - if (unused != null) unused.close(); - if (resource != null) resource.close(); - } - } - - class MutableOwnedField implements Closeable { - - @Owning Closeable resource; - - @RequiresCalledMethods( - value = "this.resource", - methods = {"close"}) - @CreatesMustCallFor("this") - void realloc() throws IOException { - // Unlike in a constructor, field assignments in normal methods are not - // leaked when the method exits with an exception, since the reciever - // is still accessible to the caller. - resource = alloc(); - if (arbitraryChoice()) { - throw new IOException(); - } + // Classes with >1 owned field are treated slightly differently + // (see ./TwoOwningMCATest.java) + class TwoOwnedFields implements Closeable { + + final @Owning Closeable unused = null; + + final @Owning Closeable resource; + + TwoOwnedFields() throws IOException { + // Field assignments in constructors are special. When the constructor + // exits by exception, the field becomes permanently inaccessible, and + // therefore the allocated resource is leaked. + // :: error: (required.method.not.called) + resource = alloc(); + if (arbitraryChoice()) { + throw new IOException(); + } + } + + TwoOwnedFields(@Owning Closeable resource) throws IOException { + // Although, when the resource was passed by a caller, then we can be + // more relaxed. On exception, ownership remains with the caller. + this.resource = resource; + if (arbitraryChoice()) { + throw new IOException(); + } + } + + TwoOwnedFields(@Owning Closeable resource, boolean arg) throws IOException { + // Same as the previous constructor, but in the other order. + if (arbitraryChoice()) { + throw new IOException(); + } + this.resource = resource; + } + + TwoOwnedFields(int ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 1). + resource = alloc(); + try { + if (arbitraryChoice()) { + throw new IOException(); + } + } catch (Exception e) { + resource.close(); + throw e; + } + } + + TwoOwnedFields(float ignored) throws IOException { + // Same as the 0-argument constructor, but handled correctly (algorithm 2). + Closeable r = alloc(); + resource = r; + try { + if (arbitraryChoice()) { + throw new IOException(); + } + } catch (Exception e) { + r.close(); + throw e; + } + } + + // Not allowed: destructors have to close @Owning fields even on exception. + // In this case, the exception from `unused.close()` can prematurely stop the method. + @Override + @EnsuresCalledMethods( + value = {"this.resource", "this.unused"}, + methods = {"close"}) + // ::error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + if (unused != null) unused.close(); + if (resource != null) resource.close(); + } } - @Override - @EnsuresCalledMethods( - value = "this.resource", - methods = {"close"}) - public void close() throws IOException { - resource.close(); + class MutableOwnedField implements Closeable { + + @Owning Closeable resource; + + @RequiresCalledMethods( + value = "this.resource", + methods = {"close"}) + @CreatesMustCallFor("this") + void realloc() throws IOException { + // Unlike in a constructor, field assignments in normal methods are not + // leaked when the method exits with an exception, since the reciever + // is still accessible to the caller. + resource = alloc(); + if (arbitraryChoice()) { + throw new IOException(); + } + } + + @Override + @EnsuresCalledMethods( + value = "this.resource", + methods = {"close"}) + public void close() throws IOException { + resource.close(); + } } - } } diff --git a/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java b/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java index e9da8f2f516..c5066ff802a 100644 --- a/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java +++ b/checker/tests/resourceleak/OwningAndEnsuresCalledMethodsOnException.java @@ -1,71 +1,72 @@ // Test case for a Resource Leak manual example that involves interaction // between @Owning and @EnsuresCalledMethodsOnException. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class OwningAndEnsuresCalledMethodsOnException implements Closeable { - private final @Owning Closeable resource; + private final @Owning Closeable resource; - // Good constructor, as illustrated in the manual. - @EnsuresCalledMethodsOnException(value = "#1", methods = "close") - public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource) throws IOException { - this.resource = resource; - try { - initialize(resource); - } catch (Exception e) { - resource.close(); - throw e; + // Good constructor, as illustrated in the manual. + @EnsuresCalledMethodsOnException(value = "#1", methods = "close") + public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource) throws IOException { + this.resource = resource; + try { + initialize(resource); + } catch (Exception e) { + resource.close(); + throw e; + } } - } - // Alternative constructor with a weaker contract (specifically, the default - // contract for @Owning: only takes ownership on normal return) - public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource, int ignored) - throws IOException { - this.resource = resource; - initialize(resource); - } + // Alternative constructor with a weaker contract (specifically, the default + // contract for @Owning: only takes ownership on normal return) + public OwningAndEnsuresCalledMethodsOnException(@Owning Closeable resource, int ignored) + throws IOException { + this.resource = resource; + initialize(resource); + } - public OwningAndEnsuresCalledMethodsOnException() throws IOException { - // OK: the good delegate constructor will either take ownership or close the argument - // This will issue a false positive warning due to - // https://github.com/typetools/checker-framework/issues/6270 - // ::error: (required.method.not.called) - this(new Resource()); - } + public OwningAndEnsuresCalledMethodsOnException() throws IOException { + // OK: the good delegate constructor will either take ownership or close the argument + // This will issue a false positive warning due to + // https://github.com/typetools/checker-framework/issues/6270 + // ::error: (required.method.not.called) + this(new Resource()); + } - public OwningAndEnsuresCalledMethodsOnException(int x) throws IOException { - // WRONG: the bad delegate constructor does not close the argument on exception - // ::error: (required.method.not.called) - this(new Resource(), x); - } + public OwningAndEnsuresCalledMethodsOnException(int x) throws IOException { + // WRONG: the bad delegate constructor does not close the argument on exception + // ::error: (required.method.not.called) + this(new Resource(), x); + } - static void exampleUseInNormalMethod1() throws IOException { - // OK: the constructor will either take ownership or close the argument - // This will issue a false positive warning due to - // https://github.com/typetools/checker-framework/issues/6270 - // ::error: (required.method.not.called) - new OwningAndEnsuresCalledMethodsOnException(new Resource()); - } + static void exampleUseInNormalMethod1() throws IOException { + // OK: the constructor will either take ownership or close the argument + // This will issue a false positive warning due to + // https://github.com/typetools/checker-framework/issues/6270 + // ::error: (required.method.not.called) + new OwningAndEnsuresCalledMethodsOnException(new Resource()); + } - static void exampleUseInNormalMethod2() throws IOException { - // WRONG: the bad constructor does not close the argument on exception - // ::error: (required.method.not.called) - new OwningAndEnsuresCalledMethodsOnException(new Resource(), 0); - } + static void exampleUseInNormalMethod2() throws IOException { + // WRONG: the bad constructor does not close the argument on exception + // ::error: (required.method.not.called) + new OwningAndEnsuresCalledMethodsOnException(new Resource(), 0); + } - static void initialize(Closeable resource) throws IOException {} + static void initialize(Closeable resource) throws IOException {} - @EnsuresCalledMethods(value = "resource", methods = "close") - public void close() throws IOException { - resource.close(); - } + @EnsuresCalledMethods(value = "resource", methods = "close") + public void close() throws IOException { + resource.close(); + } - private static class Resource implements Closeable { - @Override - public void close() throws IOException {} - } + private static class Resource implements Closeable { + @Override + public void close() throws IOException {} + } } diff --git a/checker/tests/resourceleak/OwningEnsuresCalledMethods.java b/checker/tests/resourceleak/OwningEnsuresCalledMethods.java index b17dcf5643e..bac8ab3a404 100644 --- a/checker/tests/resourceleak/OwningEnsuresCalledMethods.java +++ b/checker/tests/resourceleak/OwningEnsuresCalledMethods.java @@ -11,26 +11,27 @@ // must-call methods at some point in the future through a different alias; it // does not promise to call those methods before returning. -import java.io.*; -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.net.Socket; + @InheritableMustCall("dispose") public class OwningEnsuresCalledMethods { - @Owning Socket con; + @Owning Socket con; - @EnsuresCalledMethods(value = "this.con", methods = "close") - // ::error: (contracts.postcondition.not.satisfied) - void dispose() { - closeCon(con); - } + @EnsuresCalledMethods(value = "this.con", methods = "close") + // ::error: (contracts.postcondition.not.satisfied) + void dispose() { + closeCon(con); + } - static void closeCon(@Owning Socket con) { - try { - con.close(); - } catch (IOException e) { + static void closeCon(@Owning Socket con) { + try { + con.close(); + } catch (IOException e) { + } } - } } diff --git a/checker/tests/resourceleak/OwningFieldStringComparison.java b/checker/tests/resourceleak/OwningFieldStringComparison.java index c89fe62351f..3865402b1f6 100644 --- a/checker/tests/resourceleak/OwningFieldStringComparison.java +++ b/checker/tests/resourceleak/OwningFieldStringComparison.java @@ -1,32 +1,33 @@ // Test case for https://github.com/typetools/checker-framework/issues/6276 -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.Socket; + @InheritableMustCall("a") public class OwningFieldStringComparison { - // :: error: required.method.not.called - @Owning Socket s; + // :: error: required.method.not.called + @Owning Socket s; - // important to the bug: the name of this field must contain - // the name of the owning socket - /* @NotOwning */ Socket s2; + // important to the bug: the name of this field must contain + // the name of the owning socket + /* @NotOwning */ Socket s2; - // Note this "destructor" closes the wrong socket - @EnsuresCalledMethods(value = "this.s2", methods = "close") - public void a() { - try { - this.s2.close(); - } catch (Exception e) { + // Note this "destructor" closes the wrong socket + @EnsuresCalledMethods(value = "this.s2", methods = "close") + public void a() { + try { + this.s2.close(); + } catch (Exception e) { - } finally { - try { - this.s2.close(); - } catch (Exception e) { + } finally { + try { + this.s2.close(); + } catch (Exception e) { - } + } + } } - } } diff --git a/checker/tests/resourceleak/OwningMCU.java b/checker/tests/resourceleak/OwningMCU.java index 482eda06fd9..226ce1ae6a4 100644 --- a/checker/tests/resourceleak/OwningMCU.java +++ b/checker/tests/resourceleak/OwningMCU.java @@ -6,10 +6,10 @@ public class OwningMCU { - @Owning @MustCallUnknown Object foo; + @Owning @MustCallUnknown Object foo; - // :: error: missing.creates.mustcall.for - void test() { - foo = new Object(); - } + // :: error: missing.creates.mustcall.for + void test() { + foo = new Object(); + } } diff --git a/checker/tests/resourceleak/OwningOverride.java b/checker/tests/resourceleak/OwningOverride.java index ee12fd76d48..b5bb749c757 100644 --- a/checker/tests/resourceleak/OwningOverride.java +++ b/checker/tests/resourceleak/OwningOverride.java @@ -1,28 +1,29 @@ // Test case for https://github.com/typetools/checker-framework/issues/5722 +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.net.*; -import org.checkerframework.checker.mustcall.qual.*; public class OwningOverride { - abstract static class A { - public abstract void closeStream(@Owning InputStream s) throws IOException; - } + abstract static class A { + public abstract void closeStream(@Owning InputStream s) throws IOException; + } - static class B extends A { - @Override - // :: error: owning.override.param - public void closeStream(InputStream s) {} - } + static class B extends A { + @Override + // :: error: owning.override.param + public void closeStream(InputStream s) {} + } - static void main(String[] args) throws IOException { - // no resource leak reported for x - InputStream x = new FileInputStream("foo.txt"); - A a = new B(); - try { - a.closeStream(x); - } catch (Exception e) { - x.close(); + static void main(String[] args) throws IOException { + // no resource leak reported for x + InputStream x = new FileInputStream("foo.txt"); + A a = new B(); + try { + a.closeStream(x); + } catch (Exception e) { + x.close(); + } } - } } diff --git a/checker/tests/resourceleak/OwningOverride2.java b/checker/tests/resourceleak/OwningOverride2.java index 26ab41a831a..ea4ce962ee5 100644 --- a/checker/tests/resourceleak/OwningOverride2.java +++ b/checker/tests/resourceleak/OwningOverride2.java @@ -2,20 +2,21 @@ // https://github.com/typetools/checker-framework/issues/5722 // Test that an owning return cannot override a non-owning return +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.net.*; -import org.checkerframework.checker.mustcall.qual.*; public class OwningOverride2 { - abstract static class A { - public abstract @NotOwning Socket get(); - } + abstract static class A { + public abstract @NotOwning Socket get(); + } - static class B extends A { - @Override - // :: error: owning.override.return - public Socket get() { - return null; + static class B extends A { + @Override + // :: error: owning.override.return + public Socket get() { + return null; + } } - } } diff --git a/checker/tests/resourceleak/PaperExample.java b/checker/tests/resourceleak/PaperExample.java index 2d337ee3646..0838504d7ac 100644 --- a/checker/tests/resourceleak/PaperExample.java +++ b/checker/tests/resourceleak/PaperExample.java @@ -1,16 +1,16 @@ import java.net.Socket; class PaperExample { - void test(String myHost, int myPort) throws Exception { - Socket s = null; - try { - s = new Socket(myHost, myPort); /* 1 */ - } catch (Exception e) { - } finally { - if (s != null) { - /* 2 */ - s.close(); - } + void test(String myHost, int myPort) throws Exception { + Socket s = null; + try { + s = new Socket(myHost, myPort); /* 1 */ + } catch (Exception e) { + } finally { + if (s != null) { + /* 2 */ + s.close(); + } + } } - } } diff --git a/checker/tests/resourceleak/PrimitiveCast.java b/checker/tests/resourceleak/PrimitiveCast.java index 7f5ec7bcd3d..b28d7375ea1 100644 --- a/checker/tests/resourceleak/PrimitiveCast.java +++ b/checker/tests/resourceleak/PrimitiveCast.java @@ -1,37 +1,38 @@ -import java.util.List; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.MustCallUnknown; +import java.util.List; + public class PrimitiveCast { - char foo(List values) { - for (Object o : values) { - return (char) o; + char foo(List values) { + for (Object o : values) { + return (char) o; + } + return 'A'; } - return 'A'; - } - char toChar1(@MustCall("hashCode") Character c) { - return (char) c; - } + char toChar1(@MustCall("hashCode") Character c) { + return (char) c; + } - char toChar2(@MustCallUnknown Character c) { - return (char) c; - } + char toChar2(@MustCallUnknown Character c) { + return (char) c; + } - char toChar3(@MustCall Character c) { - return (char) c; - } + char toChar3(@MustCall Character c) { + return (char) c; + } - @MustCall("hashCode") Character toCharacter1(char c) { - return (char) c; - } + @MustCall("hashCode") Character toCharacter1(char c) { + return (char) c; + } - @MustCallUnknown Character toCharacter2(char c) { - return (char) c; - } + @MustCallUnknown Character toCharacter2(char c) { + return (char) c; + } - @MustCall Character toCharacter3(char c) { - return (char) c; - } + @MustCall Character toCharacter3(char c) { + return (char) c; + } } diff --git a/checker/tests/resourceleak/ReassignmentWithMCA.java b/checker/tests/resourceleak/ReassignmentWithMCA.java index 5d0c4dfb50b..360839acbc7 100644 --- a/checker/tests/resourceleak/ReassignmentWithMCA.java +++ b/checker/tests/resourceleak/ReassignmentWithMCA.java @@ -1,49 +1,50 @@ -import java.io.*; -import java.security.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.security.*; + public class ReassignmentWithMCA { - void testReassignment(File newFile, MessageDigest digester) throws IOException { - FileOutputStream fout = new FileOutputStream(newFile); - DigestOutputStream fos = new DigestOutputStream(fout, digester); - DataOutputStream out = new DataOutputStream(fos); - try { - out = new DataOutputStream(new BufferedOutputStream(fos)); - fout.getChannel(); - } finally { - out.close(); + void testReassignment(File newFile, MessageDigest digester) throws IOException { + FileOutputStream fout = new FileOutputStream(newFile); + DigestOutputStream fos = new DigestOutputStream(fout, digester); + DataOutputStream out = new DataOutputStream(fos); + try { + out = new DataOutputStream(new BufferedOutputStream(fos)); + fout.getChannel(); + } finally { + out.close(); + } } - } - void testReassignmentWithoutMCA( - @Owning FileOutputStream fout1, @Owning FileOutputStream fout2, MessageDigest digester) - throws IOException { - DigestOutputStream fos1 = new DigestOutputStream(fout1, digester); - DataOutputStream out = new DataOutputStream(fos1); - try { - DigestOutputStream fos2 = new DigestOutputStream(fout2, digester); - out = new DataOutputStream(new BufferedOutputStream(fos2)); - fout1.getChannel(); - } finally { - callClose(fout1); - callClose(fout2); + void testReassignmentWithoutMCA( + @Owning FileOutputStream fout1, @Owning FileOutputStream fout2, MessageDigest digester) + throws IOException { + DigestOutputStream fos1 = new DigestOutputStream(fout1, digester); + DataOutputStream out = new DataOutputStream(fos1); + try { + DigestOutputStream fos2 = new DigestOutputStream(fout2, digester); + out = new DataOutputStream(new BufferedOutputStream(fos2)); + fout1.getChannel(); + } finally { + callClose(fout1); + callClose(fout2); + } } - } - void testReassignmentSetSizeOne(@Owning FilterOutputStream out) throws IOException { - out = new DataOutputStream(out); - out.close(); - } + void testReassignmentSetSizeOne(@Owning FilterOutputStream out) throws IOException { + out = new DataOutputStream(out); + out.close(); + } - @EnsuresCalledMethods(value = "#1", methods = "close") - void callClose(Closeable c) { - try { - if (c != null) { - c.close(); - } - } catch (IOException e) { + @EnsuresCalledMethods(value = "#1", methods = "close") + void callClose(Closeable c) { + try { + if (c != null) { + c.close(); + } + } catch (IOException e) { + } } - } } diff --git a/checker/tests/resourceleak/ReplicaInputStreams.java b/checker/tests/resourceleak/ReplicaInputStreams.java index d0b030752f4..c72b00c5243 100644 --- a/checker/tests/resourceleak/ReplicaInputStreams.java +++ b/checker/tests/resourceleak/ReplicaInputStreams.java @@ -1,28 +1,29 @@ // A test case for https://github.com/typetools/checker-framework/issues/4838. +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.mustcall.qual.Owning; + import java.io.Closeable; import java.io.IOException; import java.io.InputStream; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.mustcall.qual.Owning; class ReplicaInputStreams implements Closeable { - private final @Owning InputStream in1; - private final @Owning InputStream in2; + private final @Owning InputStream in1; + private final @Owning InputStream in2; - public ReplicaInputStreams(@Owning InputStream i1, @Owning InputStream i2) { - this.in1 = i1; - this.in2 = i2; - } + public ReplicaInputStreams(@Owning InputStream i1, @Owning InputStream i2) { + this.in1 = i1; + this.in2 = i2; + } - @Override - @EnsuresCalledMethods( - value = {"this.in1", "this.in2"}, - methods = {"close"}) - // :: error: (contracts.exceptional.postcondition.not.satisfied) - public void close() throws IOException { - in1.close(); - in2.close(); - } + @Override + @EnsuresCalledMethods( + value = {"this.in1", "this.in2"}, + methods = {"close"}) + // :: error: (contracts.exceptional.postcondition.not.satisfied) + public void close() throws IOException { + in1.close(); + in2.close(); + } } diff --git a/checker/tests/resourceleak/ReplicaInputStreams2.java b/checker/tests/resourceleak/ReplicaInputStreams2.java index c5df98e1ba0..089f4eaec83 100644 --- a/checker/tests/resourceleak/ReplicaInputStreams2.java +++ b/checker/tests/resourceleak/ReplicaInputStreams2.java @@ -1,31 +1,32 @@ // A test case for https://github.com/typetools/checker-framework/issues/4838. // This variant uses a try-finally in the destructor, so it is correct. +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.mustcall.qual.Owning; + import java.io.Closeable; import java.io.IOException; import java.io.InputStream; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.mustcall.qual.Owning; class ReplicaInputStreams2 implements Closeable { - private final @Owning InputStream in1; - private final @Owning InputStream in2; + private final @Owning InputStream in1; + private final @Owning InputStream in2; - public ReplicaInputStreams2(@Owning InputStream i1, @Owning InputStream i2) { - this.in1 = i1; - this.in2 = i2; - } + public ReplicaInputStreams2(@Owning InputStream i1, @Owning InputStream i2) { + this.in1 = i1; + this.in2 = i2; + } - @Override - @EnsuresCalledMethods( - value = {"this.in1", "this.in2"}, - methods = {"close"}) - public void close() throws IOException { - try { - in1.close(); - } finally { - in2.close(); + @Override + @EnsuresCalledMethods( + value = {"this.in1", "this.in2"}, + methods = {"close"}) + public void close() throws IOException { + try { + in1.close(); + } finally { + in2.close(); + } } - } } diff --git a/checker/tests/resourceleak/RequiresCalledMethodsTest.java b/checker/tests/resourceleak/RequiresCalledMethodsTest.java index 4cbb9a6b6b6..3178e6a2329 100644 --- a/checker/tests/resourceleak/RequiresCalledMethodsTest.java +++ b/checker/tests/resourceleak/RequiresCalledMethodsTest.java @@ -1,53 +1,54 @@ -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; -public class RequiresCalledMethodsTest { - - @InheritableMustCall("a") - static class Foo { - void a() {} - - void c() {} - } - - @InheritableMustCall("releaseFoo") - static class FooField { - private @Owning Foo foo = null; - - @RequiresCalledMethods( - value = {"this.foo"}, - methods = {"a"}) - @CreatesMustCallFor("this") - void overwriteFooCorrect() { - this.foo = new Foo(); - } +import java.io.*; - @CreatesMustCallFor("this") - void overwriteFooWrong() { - // :: error: required.method.not.called - this.foo = new Foo(); - } +public class RequiresCalledMethodsTest { - @CreatesMustCallFor("this") - void overwriteFooWithoutReleasing() { - // :: error: contracts.precondition.not.satisfied - overwriteFooCorrect(); - } + @InheritableMustCall("a") + static class Foo { + void a() {} - void releaseThenOverwriteFoo() { - releaseFoo(); - // :: error: reset.not.owning - overwriteFooCorrect(); + void c() {} } - @EnsuresCalledMethods( - value = {"this.foo"}, - methods = {"a"}) - void releaseFoo() { - if (this.foo != null) { - foo.a(); - } + @InheritableMustCall("releaseFoo") + static class FooField { + private @Owning Foo foo = null; + + @RequiresCalledMethods( + value = {"this.foo"}, + methods = {"a"}) + @CreatesMustCallFor("this") + void overwriteFooCorrect() { + this.foo = new Foo(); + } + + @CreatesMustCallFor("this") + void overwriteFooWrong() { + // :: error: required.method.not.called + this.foo = new Foo(); + } + + @CreatesMustCallFor("this") + void overwriteFooWithoutReleasing() { + // :: error: contracts.precondition.not.satisfied + overwriteFooCorrect(); + } + + void releaseThenOverwriteFoo() { + releaseFoo(); + // :: error: reset.not.owning + overwriteFooCorrect(); + } + + @EnsuresCalledMethods( + value = {"this.foo"}, + methods = {"a"}) + void releaseFoo() { + if (this.foo != null) { + foo.a(); + } + } } - } } diff --git a/checker/tests/resourceleak/ReturnOwningObject.java b/checker/tests/resourceleak/ReturnOwningObject.java index 822c0cd5d0a..20d3fa208fa 100644 --- a/checker/tests/resourceleak/ReturnOwningObject.java +++ b/checker/tests/resourceleak/ReturnOwningObject.java @@ -5,19 +5,19 @@ import org.checkerframework.checker.mustcall.qual.MustCall; public class ReturnOwningObject { - @InheritableMustCall("a") - static class Foo { - void a() {} - } + @InheritableMustCall("a") + static class Foo { + void a() {} + } - // This is unsatisfiable without a cast, but - // for soundness the RLC still needs to track it. - public static @MustCall("a") Object getFoo() { - return new Foo(); - } + // This is unsatisfiable without a cast, but + // for soundness the RLC still needs to track it. + public static @MustCall("a") Object getFoo() { + return new Foo(); + } - public static void useGetFoo() { - // :: error: required.method.not.called - Object obj = getFoo(); - } + public static void useGetFoo() { + // :: error: required.method.not.called + Object obj = getFoo(); + } } diff --git a/checker/tests/resourceleak/RlcThisTest.java b/checker/tests/resourceleak/RlcThisTest.java index dc41cb142dc..113b02daa5b 100644 --- a/checker/tests/resourceleak/RlcThisTest.java +++ b/checker/tests/resourceleak/RlcThisTest.java @@ -6,59 +6,59 @@ class RlcThisTest { - @InheritableMustCall("a") - private class Foo { + @InheritableMustCall("a") + private class Foo { - void a() {} + void a() {} - @This Foo b1(Foo this) { - return this; - } + @This Foo b1(Foo this) { + return this; + } - @MustCallAlias Foo b2(@MustCallAlias Foo this) { - return this; + @MustCallAlias Foo b2(@MustCallAlias Foo this) { + return this; + } } - } - void test1() { - Foo f = new Foo(); - f.b1(); - f.a(); - } + void test1() { + Foo f = new Foo(); + f.b1(); + f.a(); + } - void test2() { - Foo f = new Foo(); - f.b2(); - f.a(); - } + void test2() { + Foo f = new Foo(); + f.b2(); + f.a(); + } - void test3() { - Foo f = new Foo(); - Foo ff = f.b1(); - ff.a(); - } + void test3() { + Foo f = new Foo(); + Foo ff = f.b1(); + ff.a(); + } - void test4() { - Foo f = new Foo(); - Foo ff = f.b2(); - ff.a(); - } + void test4() { + Foo f = new Foo(); + Foo ff = f.b2(); + ff.a(); + } - void testA() { - Foo f = new Foo(); - f.b1(); // RLC reports a FP at this line - f.a(); + void testA() { + Foo f = new Foo(); + f.b1(); // RLC reports a FP at this line + f.a(); - f = new Foo(); - f.a(); - } + f = new Foo(); + f.a(); + } - void testB() { - Foo f = new Foo(); - f.b2(); // RLC reports a FP at this line - f.a(); + void testB() { + Foo f = new Foo(); + f.b2(); // RLC reports a FP at this line + f.a(); - f = new Foo(); - f.a(); - } + f = new Foo(); + f.a(); + } } diff --git a/checker/tests/resourceleak/SSLSocketFactoryTest.java b/checker/tests/resourceleak/SSLSocketFactoryTest.java index 40570ada578..c7108731abd 100644 --- a/checker/tests/resourceleak/SSLSocketFactoryTest.java +++ b/checker/tests/resourceleak/SSLSocketFactoryTest.java @@ -1,17 +1,21 @@ // A test for a bug that came up while porting the Resource Leak Checker into the // Checker Framework proper. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.IOException; import java.net.Socket; + import javax.net.ssl.*; -import org.checkerframework.checker.mustcall.qual.*; class SSLSocketFactoryTest { - public SSLSocket createSSLSocket(@Owning Socket socket, SSLContext sslContext) - throws IOException { - SSLSocket sslSocket = - (SSLSocket) - sslContext.getSocketFactory().createSocket(socket, null, socket.getPort(), true); - return sslSocket; - } + public SSLSocket createSSLSocket(@Owning Socket socket, SSLContext sslContext) + throws IOException { + SSLSocket sslSocket = + (SSLSocket) + sslContext + .getSocketFactory() + .createSocket(socket, null, socket.getPort(), true); + return sslSocket; + } } diff --git a/checker/tests/resourceleak/SelfAssign.java b/checker/tests/resourceleak/SelfAssign.java index d658faf67c7..d3c9fa5205b 100644 --- a/checker/tests/resourceleak/SelfAssign.java +++ b/checker/tests/resourceleak/SelfAssign.java @@ -1,36 +1,39 @@ // test assignments of the same variable to itself -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + class SelfAssign { - static void test0() throws IOException { - InputStream selfAssignIn0 = new FileInputStream("file.txt"); - try { - selfAssignIn0 = selfAssignIn0; - } finally { - selfAssignIn0.close(); + static void test0() throws IOException { + InputStream selfAssignIn0 = new FileInputStream("file.txt"); + try { + selfAssignIn0 = selfAssignIn0; + } finally { + selfAssignIn0.close(); + } } - } - static void test1(boolean b) throws IOException { - InputStream selfAssignIn = new FileInputStream("file.txt"); - try { - selfAssignIn = - selfAssignIn.markSupported() ? selfAssignIn : new BufferedInputStream(selfAssignIn); - } finally { - selfAssignIn.close(); + static void test1(boolean b) throws IOException { + InputStream selfAssignIn = new FileInputStream("file.txt"); + try { + selfAssignIn = + selfAssignIn.markSupported() + ? selfAssignIn + : new BufferedInputStream(selfAssignIn); + } finally { + selfAssignIn.close(); + } } - } - static void test2(boolean b) throws IOException { - InputStream in = new FileInputStream("file.txt"); - try { - in = new BufferedInputStream(in); - } finally { - in.close(); + static void test2(boolean b) throws IOException { + InputStream in = new FileInputStream("file.txt"); + try { + in = new BufferedInputStream(in); + } finally { + in.close(); + } } - } } diff --git a/checker/tests/resourceleak/SimpleSocketExample.java b/checker/tests/resourceleak/SimpleSocketExample.java index e3e09484305..b5297624659 100644 --- a/checker/tests/resourceleak/SimpleSocketExample.java +++ b/checker/tests/resourceleak/SimpleSocketExample.java @@ -4,14 +4,14 @@ import java.net.Socket; class SimpleSocketExample { - void basicTest(String address, int port) { - try { - // :: error: required.method.not.called - Socket socket2 = new Socket(address, port); - Socket specialSocket = new Socket(address, port); - specialSocket.close(); - } catch (IOException i) { + void basicTest(String address, int port) { + try { + // :: error: required.method.not.called + Socket socket2 = new Socket(address, port); + Socket specialSocket = new Socket(address, port); + specialSocket.close(); + } catch (IOException i) { + } } - } } diff --git a/checker/tests/resourceleak/SneakyDestructor.java b/checker/tests/resourceleak/SneakyDestructor.java index 586646ae600..5437af3d1af 100644 --- a/checker/tests/resourceleak/SneakyDestructor.java +++ b/checker/tests/resourceleak/SneakyDestructor.java @@ -3,23 +3,24 @@ // is not fooled by a must-call method that closes the owned field // of another instance of the class. -import java.io.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; + @InheritableMustCall("close") public class SneakyDestructor { - // :: error: required.method.not.called - private final @Owning Closeable resource; + // :: error: required.method.not.called + private final @Owning Closeable resource; - public SneakyDestructor(Closeable r) { - this.resource = r; - } + public SneakyDestructor(Closeable r) { + this.resource = r; + } - // ... + // ... - @EnsuresCalledMethods(value = "#1.resource", methods = "close") - public void close(SneakyDestructor other) throws IOException { - other.resource.close(); - } + @EnsuresCalledMethods(value = "#1.resource", methods = "close") + public void close(SneakyDestructor other) throws IOException { + other.resource.close(); + } } diff --git a/checker/tests/resourceleak/SneakyDrop.java b/checker/tests/resourceleak/SneakyDrop.java index a4e793757e7..8d53db16e18 100644 --- a/checker/tests/resourceleak/SneakyDrop.java +++ b/checker/tests/resourceleak/SneakyDrop.java @@ -3,63 +3,63 @@ import org.checkerframework.checker.mustcall.qual.*; class Resource implements java.io.Closeable { - @Override - public void close() {} + @Override + public void close() {} } public class SneakyDrop { - public static void sneakyDrop(@Owning T value) {} + public static void sneakyDrop(@Owning T value) {} - public static void main(String[] args) throws Exception { - Resource x = new Resource(); - // :: error: (type.arguments.not.inferred) - sneakyDrop(x); - } + public static void main(String[] args) throws Exception { + Resource x = new Resource(); + // :: error: (type.arguments.not.inferred) + sneakyDrop(x); + } - // :: error: (required.method.not.called) - public static void sneakyDrop2(@Owning @MustCall("close") T value) {} + // :: error: (required.method.not.called) + public static void sneakyDrop2(@Owning @MustCall("close") T value) {} - public static void main2(String[] args) throws Exception { - Resource x = new Resource(); - sneakyDrop2(x); - } + public static void main2(String[] args) throws Exception { + Resource x = new Resource(); + sneakyDrop2(x); + } - // :: error: (required.method.not.called) - public static void sneakyDrop3(@Owning T value) {} + // :: error: (required.method.not.called) + public static void sneakyDrop3(@Owning T value) {} - public static void main3(String[] args) throws Exception { - Resource x = new Resource(); - sneakyDrop3(x); - } + public static void main3(String[] args) throws Exception { + Resource x = new Resource(); + sneakyDrop3(x); + } - public static void sneakyDrop4(@Owning T value) {} + public static void sneakyDrop4(@Owning T value) {} - public static void main4(String[] args) throws Exception { - Resource x = new Resource(); - // :: error: (type.arguments.not.inferred) - sneakyDrop4(x); - } + public static void main4(String[] args) throws Exception { + Resource x = new Resource(); + // :: error: (type.arguments.not.inferred) + sneakyDrop4(x); + } - // :: error: (required.method.not.called) - public static void sneakyDrop5(@Owning T value) {} + // :: error: (required.method.not.called) + public static void sneakyDrop5(@Owning T value) {} - public static void main5(String[] args) throws Exception { - Resource x = new Resource(); - sneakyDrop5(x); - } + public static void main5(String[] args) throws Exception { + Resource x = new Resource(); + sneakyDrop5(x); + } - public static void sneakyDropCorrect( - @Owning @MustCall("close") T value) throws Exception { - value.close(); - } + public static void sneakyDropCorrect( + @Owning @MustCall("close") T value) throws Exception { + value.close(); + } - public static void main6(String[] args) throws Exception { - Resource x = new Resource(); - try { - sneakyDropCorrect(x); - } catch (Exception e) { - x.close(); + public static void main6(String[] args) throws Exception { + Resource x = new Resource(); + try { + sneakyDropCorrect(x); + } catch (Exception e) { + x.close(); + } } - } } diff --git a/checker/tests/resourceleak/SocketContainer.java b/checker/tests/resourceleak/SocketContainer.java index d431d665271..302e597a325 100644 --- a/checker/tests/resourceleak/SocketContainer.java +++ b/checker/tests/resourceleak/SocketContainer.java @@ -2,30 +2,31 @@ // This test exists to check that we gracefully handle assignments 1) // in the constructor and 2) to null. -import java.io.*; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.net.*; + @InheritableMustCall("close") class SocketContainer { - @Owning Socket sock; + @Owning Socket sock; - public SocketContainer(String host, int port) throws Exception { - // It should be okay to assign to uninitialized owning fields in the constructor. - // But it isn't! Why? - // :: error: required.method.not.called - sock = new Socket(host, port); - // It's definitely not okay to do it twice! - // :: error: required.method.not.called - sock = new Socket(host, port); - } + public SocketContainer(String host, int port) throws Exception { + // It should be okay to assign to uninitialized owning fields in the constructor. + // But it isn't! Why? + // :: error: required.method.not.called + sock = new Socket(host, port); + // It's definitely not okay to do it twice! + // :: error: required.method.not.called + sock = new Socket(host, port); + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - // It's okay to assign a field to null after its obligations have been fulfilled, - // without inducing a reset. - sock = null; - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + // It's okay to assign a field to null after its obligations have been fulfilled, + // without inducing a reset. + sock = null; + } } diff --git a/checker/tests/resourceleak/SocketContainer2.java b/checker/tests/resourceleak/SocketContainer2.java index 3d039c8e953..fc4dfd7a180 100644 --- a/checker/tests/resourceleak/SocketContainer2.java +++ b/checker/tests/resourceleak/SocketContainer2.java @@ -1,24 +1,25 @@ // A simple class that has a Socket as an owning field. // This test exists to check that we gracefully handle assignments to it. -import java.io.*; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.net.*; + @InheritableMustCall("close") class SocketContainer2 { - @Owning Socket sock = new Socket(); + @Owning Socket sock = new Socket(); - public SocketContainer2(String host, int port) throws Exception { - // This assignment is safe, because the only possible value of sock here is the unconnected - // socket in the field initializer. - sock = new Socket(host, port); - } + public SocketContainer2(String host, int port) throws Exception { + // This assignment is safe, because the only possible value of sock here is the unconnected + // socket in the field initializer. + sock = new Socket(host, port); + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + } } diff --git a/checker/tests/resourceleak/SocketContainer3.java b/checker/tests/resourceleak/SocketContainer3.java index 0dec9beec1b..bcb212ebc5e 100644 --- a/checker/tests/resourceleak/SocketContainer3.java +++ b/checker/tests/resourceleak/SocketContainer3.java @@ -1,21 +1,22 @@ // A simple class that has a Socket as an owning field. // This test exists to check that we gracefully handle assignments. -import java.io.*; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.net.*; + @InheritableMustCall("close") class SocketContainer3 { - @Owning Socket sock = null; + @Owning Socket sock = null; - public SocketContainer3(String host, int port) throws Exception { - sock = new Socket(host, port); - } + public SocketContainer3(String host, int port) throws Exception { + sock = new Socket(host, port); + } - @EnsuresCalledMethods(value = "this.sock", methods = "close") - public void close() throws IOException { - sock.close(); - } + @EnsuresCalledMethods(value = "this.sock", methods = "close") + public void close() throws IOException { + sock.close(); + } } diff --git a/checker/tests/resourceleak/SocketField.java b/checker/tests/resourceleak/SocketField.java index 198890ed708..29a505f109f 100644 --- a/checker/tests/resourceleak/SocketField.java +++ b/checker/tests/resourceleak/SocketField.java @@ -1,66 +1,67 @@ // test case for https://github.com/kelloggm/object-construction-checker/issues/381 -import java.io.IOException; -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; import org.checkerframework.dataflow.qual.Pure; +import java.io.IOException; +import java.net.Socket; + @InheritableMustCall("closeSocket") class SocketField { - protected @Owning Socket socket = null; + protected @Owning Socket socket = null; - @CreatesMustCallFor("this") - protected void setupConnection(javax.net.SocketFactory socketFactory) throws IOException { - // This is the original test case. Before this issue was fixed, an error was issued on the - // second line. - this.socket.close(); - this.socket = socketFactory.createSocket(); - } + @CreatesMustCallFor("this") + protected void setupConnection(javax.net.SocketFactory socketFactory) throws IOException { + // This is the original test case. Before this issue was fixed, an error was issued on the + // second line. + this.socket.close(); + this.socket = socketFactory.createSocket(); + } - @CreatesMustCallFor("this") - protected void setupConnectionWithLocal(javax.net.SocketFactory socketFactory) - throws IOException { - // This is the original test case, modified to include an assignment to a local that - // demonstrates that - // the correct value was in the store at some point. - this.socket.close(); - @CalledMethods("close") Socket s = this.socket; - this.socket = socketFactory.createSocket(); - } + @CreatesMustCallFor("this") + protected void setupConnectionWithLocal(javax.net.SocketFactory socketFactory) + throws IOException { + // This is the original test case, modified to include an assignment to a local that + // demonstrates that + // the correct value was in the store at some point. + this.socket.close(); + @CalledMethods("close") Socket s = this.socket; + this.socket = socketFactory.createSocket(); + } - @CreatesMustCallFor("this") - protected void setupConnectionWithConstructor(javax.net.SocketFactory socketFactory) - throws IOException { - // This is the original test case, modified to replace the call to createSocket() with a new - // Socket() call. - // This version succeeded, even before the bug was fixed. - this.socket.close(); - this.socket = new Socket(); - } + @CreatesMustCallFor("this") + protected void setupConnectionWithConstructor(javax.net.SocketFactory socketFactory) + throws IOException { + // This is the original test case, modified to replace the call to createSocket() with a new + // Socket() call. + // This version succeeded, even before the bug was fixed. + this.socket.close(); + this.socket = new Socket(); + } - @CreatesMustCallFor("this") - protected void setupConnection2(javax.net.SocketFactory socketFactory) throws IOException { - this.socket.close(); - // This version succeeds, because getSocket is @Pure, so no side-effects can occur. - this.socket = getSocket(socketFactory); - } + @CreatesMustCallFor("this") + protected void setupConnection2(javax.net.SocketFactory socketFactory) throws IOException { + this.socket.close(); + // This version succeeds, because getSocket is @Pure, so no side-effects can occur. + this.socket = getSocket(socketFactory); + } - @CreatesMustCallFor("this") - protected void setupConnection3(javax.net.SocketFactory socketFactory) throws IOException { - // This version demonstrates a work-around. - Socket s = socketFactory.createSocket(); - this.socket.close(); - this.socket = s; - } + @CreatesMustCallFor("this") + protected void setupConnection3(javax.net.SocketFactory socketFactory) throws IOException { + // This version demonstrates a work-around. + Socket s = socketFactory.createSocket(); + this.socket.close(); + this.socket = s; + } - @Pure - private Socket getSocket(javax.net.SocketFactory socketFactory) throws IOException { - return socketFactory.createSocket(); - } + @Pure + private Socket getSocket(javax.net.SocketFactory socketFactory) throws IOException { + return socketFactory.createSocket(); + } - @EnsuresCalledMethods(value = "this.socket", methods = "close") - private void closeSocket() throws IOException { - this.socket.close(); - } + @EnsuresCalledMethods(value = "this.socket", methods = "close") + private void closeSocket() throws IOException { + this.socket.close(); + } } diff --git a/checker/tests/resourceleak/SocketIntoList.java b/checker/tests/resourceleak/SocketIntoList.java index 57603d7cbe1..d56e87fa044 100644 --- a/checker/tests/resourceleak/SocketIntoList.java +++ b/checker/tests/resourceleak/SocketIntoList.java @@ -2,44 +2,45 @@ // a socket with an obligation is stored into a List (which cannot be // owning). +import org.checkerframework.checker.mustcall.qual.*; + import java.net.*; import java.util.List; -import org.checkerframework.checker.mustcall.qual.*; public class SocketIntoList { - public void test1(List<@MustCall({}) Socket> l) { - // s is unconnected, so no error is expected when it's stored into a list - Socket s = new Socket(); - l.add(s); - } + public void test1(List<@MustCall({}) Socket> l) { + // s is unconnected, so no error is expected when it's stored into a list + Socket s = new Socket(); + l.add(s); + } - // :: error: type.argument.type.incompatible - public void test2(List l) { - // s is unconnected, so no error is expected when it's stored into the list. - // But, if the list is unannotated, we do get an error at its declaration site - // (as expected, due to #5912). - Socket s = new Socket(); - l.add(s); - } + // :: error: type.argument.type.incompatible + public void test2(List l) { + // s is unconnected, so no error is expected when it's stored into the list. + // But, if the list is unannotated, we do get an error at its declaration site + // (as expected, due to #5912). + Socket s = new Socket(); + l.add(s); + } - public void test3(List<@MustCall({}) Socket> l) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - s.bind(new InetSocketAddress("192.168.0.1", 0)); - l.add(s); - } + public void test3(List<@MustCall({}) Socket> l) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + s.bind(new InetSocketAddress("192.168.0.1", 0)); + l.add(s); + } - // This input list might have been produced by e.g., test1() - public void test4(List<@MustCall({}) Socket> l) throws Exception { - // :: error: required.method.not.called - Socket s = l.get(0); - s.bind(new InetSocketAddress("192.168.0.1", 0)); - } + // This input list might have been produced by e.g., test1() + public void test4(List<@MustCall({}) Socket> l) throws Exception { + // :: error: required.method.not.called + Socket s = l.get(0); + s.bind(new InetSocketAddress("192.168.0.1", 0)); + } - // This input list might have been produced by e.g., test1() - public void test5(List<@MustCall({}) Socket> l) throws Exception { - // No error is expected here, because the socket should be @MustCall({}) when - // it is retrieved from the list. (Equivalently, the list is not owning). - Socket s = l.get(0); - } + // This input list might have been produced by e.g., test1() + public void test5(List<@MustCall({}) Socket> l) throws Exception { + // No error is expected here, because the socket should be @MustCall({}) when + // it is retrieved from the list. (Equivalently, the list is not owning). + Socket s = l.get(0); + } } diff --git a/checker/tests/resourceleak/SocketNullOverwrite.java b/checker/tests/resourceleak/SocketNullOverwrite.java index 2b6a3af93ea..72b6e1b93cf 100644 --- a/checker/tests/resourceleak/SocketNullOverwrite.java +++ b/checker/tests/resourceleak/SocketNullOverwrite.java @@ -4,13 +4,13 @@ import java.net.Socket; class SocketNullOverwrite { - void replaceVarWithNull(String address, int port) { - try { - // :: error: required.method.not.called - Socket s = new Socket(address, port); - s = null; - } catch (IOException e) { + void replaceVarWithNull(String address, int port) { + try { + // :: error: required.method.not.called + Socket s = new Socket(address, port); + s = null; + } catch (IOException e) { + } } - } } diff --git a/checker/tests/resourceleak/SparkSessionCFGCrash.java b/checker/tests/resourceleak/SparkSessionCFGCrash.java index 971b86e4dcc..cca1510a451 100644 --- a/checker/tests/resourceleak/SparkSessionCFGCrash.java +++ b/checker/tests/resourceleak/SparkSessionCFGCrash.java @@ -4,7 +4,7 @@ public class SparkSessionCFGCrash { - private void run() { - try (SparkSession session = SparkSession.builder().getOrCreate()) {} - } + private void run() { + try (SparkSession session = SparkSession.builder().getOrCreate()) {} + } } diff --git a/checker/tests/resourceleak/StaticOwningField.java b/checker/tests/resourceleak/StaticOwningField.java index 101664cab72..2da76b120cb 100644 --- a/checker/tests/resourceleak/StaticOwningField.java +++ b/checker/tests/resourceleak/StaticOwningField.java @@ -1,66 +1,67 @@ -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintStream; import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; -@MustCall("close") class StaticOwningField implements Closeable { +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; - // Instance field +@MustCall("close") class StaticOwningField implements Closeable { - private @Owning @MustCall("close") PrintStream ps_instance; + // Instance field - @CreatesMustCallFor("this") - void m_instance() throws IOException { - ps_instance.close(); - ps_instance = new PrintStream("filename.txt"); - } + private @Owning @MustCall("close") PrintStream ps_instance; - @EnsuresCalledMethods(value = "ps_instance", methods = "close") - @Override - public void close() { - ps_instance.close(); - } + @CreatesMustCallFor("this") + void m_instance() throws IOException { + ps_instance.close(); + ps_instance = new PrintStream("filename.txt"); + } - // Static field + @EnsuresCalledMethods(value = "ps_instance", methods = "close") + @Override + public void close() { + ps_instance.close(); + } - // :: error: (required.method.not.called) - private static @Owning @MustCall("close") PrintStream ps_static; + // Static field - // :: error: (missing.creates.mustcall.for) - static void m_static() throws IOException { - ps_static.close(); - ps_static = new PrintStream("filename.txt"); - } + // :: error: (required.method.not.called) + private static @Owning @MustCall("close") PrintStream ps_static; - // :: error: (required.method.not.called) - private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = - newPrintStreamWithoutExceptions(); + // :: error: (missing.creates.mustcall.for) + static void m_static() throws IOException { + ps_static.close(); + ps_static = new PrintStream("filename.txt"); + } - // :: error: (required.method.not.called) - private static @Owning @MustCall("close") PrintStream ps_static_initialized2; + // :: error: (required.method.not.called) + private static @Owning @MustCall("close") PrintStream ps_static_initialized1 = + newPrintStreamWithoutExceptions(); - static { // :: error: (required.method.not.called) - ps_static_initialized2 = newPrintStreamWithoutExceptions(); - } + private static @Owning @MustCall("close") PrintStream ps_static_initialized2; + + static { + // :: error: (required.method.not.called) + ps_static_initialized2 = newPrintStreamWithoutExceptions(); + } - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = - newPrintStreamWithoutExceptions(); + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized1 = + newPrintStreamWithoutExceptions(); - private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; + private static final @Owning @MustCall("close") PrintStream ps_static_final_initialized2; - static { - ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); - } + static { + ps_static_final_initialized2 = newPrintStreamWithoutExceptions(); + } - public static PrintStream newPrintStreamWithoutExceptions() { - try { - return new PrintStream("filename.txt"); - } catch (Exception e) { - throw new Error(e); + public static PrintStream newPrintStreamWithoutExceptions() { + try { + return new PrintStream("filename.txt"); + } catch (Exception e) { + throw new Error(e); + } } - } } diff --git a/checker/tests/resourceleak/StaticOwningFieldOtherClass.java b/checker/tests/resourceleak/StaticOwningFieldOtherClass.java index d4ff42d3ad7..1999a06499a 100644 --- a/checker/tests/resourceleak/StaticOwningFieldOtherClass.java +++ b/checker/tests/resourceleak/StaticOwningFieldOtherClass.java @@ -1,23 +1,24 @@ +import org.checkerframework.checker.mustcall.qual.Owning; + import java.io.FileWriter; import java.io.IOException; import java.io.UncheckedIOException; -import org.checkerframework.checker.mustcall.qual.Owning; public class StaticOwningFieldOtherClass {} abstract class HasStaticOwningField { - // :: error: (required.method.not.called) - public static @Owning FileWriter log = null; + // :: error: (required.method.not.called) + public static @Owning FileWriter log = null; } class TestUtils { - // :: error: (missing.creates.mustcall.for) - public static void setLog(String filename) { - try { - // :: error: (required.method.not.called) - HasStaticOwningField.log = new FileWriter(filename); - } catch (IOException ioe) { - throw new UncheckedIOException("Cannot write file " + filename, ioe); + // :: error: (missing.creates.mustcall.for) + public static void setLog(String filename) { + try { + // :: error: (required.method.not.called) + HasStaticOwningField.log = new FileWriter(filename); + } catch (IOException ioe) { + throw new UncheckedIOException("Cannot write file " + filename, ioe); + } } - } } diff --git a/checker/tests/resourceleak/StringConcatenation.java b/checker/tests/resourceleak/StringConcatenation.java index fc27e816cae..017a8b105b7 100644 --- a/checker/tests/resourceleak/StringConcatenation.java +++ b/checker/tests/resourceleak/StringConcatenation.java @@ -1,17 +1,17 @@ public class StringConcatenation { - public final V1 first; - public final V2 second; + public final V1 first; + public final V2 second; - private StringConcatenation(V1 v1, V2 v2) { - this.first = v1; - this.second = v2; - } + private StringConcatenation(V1 v1, V2 v2) { + this.first = v1; + this.second = v2; + } - public static StringConcatenation of(V1 v1, V2 v2) { - return new StringConcatenation<>(v1, v2); - } + public static StringConcatenation of(V1 v1, V2 v2) { + return new StringConcatenation<>(v1, v2); + } - public String toString() { - return "StringConcatenation(" + first + ", " + second + ")"; - } + public String toString() { + return "StringConcatenation(" + first + ", " + second + ")"; + } } diff --git a/checker/tests/resourceleak/StringFromObject.java b/checker/tests/resourceleak/StringFromObject.java index bccdc042c19..0d108848d24 100644 --- a/checker/tests/resourceleak/StringFromObject.java +++ b/checker/tests/resourceleak/StringFromObject.java @@ -6,15 +6,15 @@ import java.util.Map.Entry; class StringFromObject { - boolean test(Map map) { - boolean isHierarchical = false; - for (Entry entry : map.entrySet()) { - String key = entry.getKey().toString().trim(); - if (key.startsWith("group") || key.startsWith("weight")) { - isHierarchical = true; - break; - } + boolean test(Map map) { + boolean isHierarchical = false; + for (Entry entry : map.entrySet()) { + String key = entry.getKey().toString().trim(); + if (key.startsWith("group") || key.startsWith("weight")) { + isHierarchical = true; + break; + } + } + return isHierarchical; } - return isHierarchical; - } } diff --git a/checker/tests/resourceleak/TernaryExpressions.java b/checker/tests/resourceleak/TernaryExpressions.java index c9656429d41..710e18ced66 100644 --- a/checker/tests/resourceleak/TernaryExpressions.java +++ b/checker/tests/resourceleak/TernaryExpressions.java @@ -4,118 +4,118 @@ class TernaryExpressions { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; + @This Foo b() { + return this; + } + + void c(@CalledMethods("a") Foo this) {} } - void c(@CalledMethods("a") Foo this) {} - } + Foo makeFoo() { + return new Foo(); + } - Foo makeFoo() { - return new Foo(); - } + static void takeOwnership(@Owning Foo foo) { + foo.a(); + } - static void takeOwnership(@Owning Foo foo) { - foo.a(); - } + /** cases where ternary expressions are assigned to a variable */ + void testTernaryAssigned(boolean b) { + Foo ternary1 = b ? new Foo() : makeFoo(); + ternary1.a(); - /** cases where ternary expressions are assigned to a variable */ - void testTernaryAssigned(boolean b) { - Foo ternary1 = b ? new Foo() : makeFoo(); - ternary1.a(); + // :: error: required.method.not.called + Foo ternary2 = b ? new Foo() : makeFoo(); - // :: error: required.method.not.called - Foo ternary2 = b ? new Foo() : makeFoo(); + // :: error: required.method.not.called + Foo x = new Foo(); + Foo ternary3 = b ? new Foo() : x; + ternary3.a(); - // :: error: required.method.not.called - Foo x = new Foo(); - Foo ternary3 = b ? new Foo() : x; - ternary3.a(); + Foo y = new Foo(); + Foo ternary4 = b ? y : y; + ternary4.a(); - Foo y = new Foo(); - Foo ternary4 = b ? y : y; - ternary4.a(); + takeOwnership(b ? new Foo() : makeFoo()); - takeOwnership(b ? new Foo() : makeFoo()); + // :: error: required.method.not.called + Foo x2 = new Foo(); + takeOwnership(b ? x2 : null); - // :: error: required.method.not.called - Foo x2 = new Foo(); - takeOwnership(b ? x2 : null); + int i = 10; + Foo ternaryInLoop = null; + while (i > 0) { + // :: error: required.method.not.called + ternaryInLoop = b ? null : new Foo(); + i--; + } + ternaryInLoop.a(); - int i = 10; - Foo ternaryInLoop = null; - while (i > 0) { - // :: error: required.method.not.called - ternaryInLoop = b ? null : new Foo(); - i--; - } - ternaryInLoop.a(); - - (b ? new Foo() : makeFoo()).a(); - } - - /** - * tests where ternary and cast expressions (possibly nested) may or may not be assigned to a - * variable - */ - void testTernaryCastUnassigned(boolean b) { - // :: error: required.method.not.called - if ((b ? new Foo() : null) != null) { - b = !b; + (b ? new Foo() : makeFoo()).a(); } - // :: error: required.method.not.called - if ((b ? makeFoo() : null) != null) { - b = !b; + /** + * tests where ternary and cast expressions (possibly nested) may or may not be assigned to a + * variable + */ + void testTernaryCastUnassigned(boolean b) { + // :: error: required.method.not.called + if ((b ? new Foo() : null) != null) { + b = !b; + } + + // :: error: required.method.not.called + if ((b ? makeFoo() : null) != null) { + b = !b; + } + + Foo x = new Foo(); + if ((b ? x : null) != null) { + b = !b; + } + x.a(); + + // :: error: required.method.not.called + if (((Foo) new Foo()) != null) { + b = !b; + } + + // double cast; no error + Foo doubleCast = (Foo) ((Foo) makeFoo()); + doubleCast.a(); + + // nesting casts and ternary expressions; no error + Foo deepNesting = (b ? (!b ? makeFoo() : (Foo) makeFoo()) : ((Foo) new Foo())); + deepNesting.a(); } - Foo x = new Foo(); - if ((b ? x : null) != null) { - b = !b; + @Owning + Foo testTernaryReturnOk(boolean b) { + return b ? new Foo() : makeFoo(); } - x.a(); - // :: error: required.method.not.called - if (((Foo) new Foo()) != null) { - b = !b; + @Owning + Foo testTernaryReturnBad(boolean b) { + // :: error: required.method.not.called + Foo x = new Foo(); + return b ? x : makeFoo(); } - // double cast; no error - Foo doubleCast = (Foo) ((Foo) makeFoo()); - doubleCast.a(); - - // nesting casts and ternary expressions; no error - Foo deepNesting = (b ? (!b ? makeFoo() : (Foo) makeFoo()) : ((Foo) new Foo())); - deepNesting.a(); - } - - @Owning - Foo testTernaryReturnOk(boolean b) { - return b ? new Foo() : makeFoo(); - } - - @Owning - Foo testTernaryReturnBad(boolean b) { - // :: error: required.method.not.called - Foo x = new Foo(); - return b ? x : makeFoo(); - } - - @InheritableMustCall("toString") - static class Sub1 extends Object {} - - @InheritableMustCall("clone") - static class Sub2 extends Object {} - - static void testTernarySubtyping(boolean b) { - // :: error: required.method.not.called - Object toStringAndClone = b ? new Sub1() : new Sub2(); - // at this point, for soundness, we should be responsible for calling both toString and - // clone on obj... - toStringAndClone.toString(); - } + @InheritableMustCall("toString") + static class Sub1 extends Object {} + + @InheritableMustCall("clone") + static class Sub2 extends Object {} + + static void testTernarySubtyping(boolean b) { + // :: error: required.method.not.called + Object toStringAndClone = b ? new Sub1() : new Sub2(); + // at this point, for soundness, we should be responsible for calling both toString and + // clone on obj... + toStringAndClone.toString(); + } } diff --git a/checker/tests/resourceleak/TryWithResourcesDeclaration.java b/checker/tests/resourceleak/TryWithResourcesDeclaration.java index b25e22eaaf9..ebf7baf54b6 100644 --- a/checker/tests/resourceleak/TryWithResourcesDeclaration.java +++ b/checker/tests/resourceleak/TryWithResourcesDeclaration.java @@ -6,38 +6,38 @@ import java.util.*; class TryWithResourcesDeclaration { - static void test(String address, int port) { - try (Socket socket = new Socket(address, port)) { + static void test(String address, int port) { + try (Socket socket = new Socket(address, port)) { - } catch (Exception e) { + } catch (Exception e) { + } } - } - public boolean isPreUpgradableLayout(File oldF) throws IOException { + public boolean isPreUpgradableLayout(File oldF) throws IOException { - if (!oldF.exists()) { - return false; + if (!oldF.exists()) { + return false; + } + // check the layout version inside the storage file + // Lock and Read old storage file + try (RandomAccessFile oldFile = new RandomAccessFile(oldF, "rws"); + FileLock oldLock = oldFile.getChannel().tryLock()) { + if (null == oldLock) { + throw new OverlappingFileLockException(); + } + oldFile.seek(0); + int oldVersion = oldFile.readInt(); + return false; + } } - // check the layout version inside the storage file - // Lock and Read old storage file - try (RandomAccessFile oldFile = new RandomAccessFile(oldF, "rws"); - FileLock oldLock = oldFile.getChannel().tryLock()) { - if (null == oldLock) { - throw new OverlappingFileLockException(); - } - oldFile.seek(0); - int oldVersion = oldFile.readInt(); - return false; - } - } - public void testNestedTryWithResourcesDecls(Properties prop, ClassLoader cl, String propfile) - throws Exception { - try (InputStream in = cl.getResourceAsStream(propfile)) { - try (InputStream fis = new FileInputStream(propfile)) { - prop.load(fis); - } + public void testNestedTryWithResourcesDecls(Properties prop, ClassLoader cl, String propfile) + throws Exception { + try (InputStream in = cl.getResourceAsStream(propfile)) { + try (InputStream fis = new FileInputStream(propfile)) { + prop.load(fis); + } + } } - } } diff --git a/checker/tests/resourceleak/TryWithResourcesFP.java b/checker/tests/resourceleak/TryWithResourcesFP.java index 69f584449df..b49932f95f0 100644 --- a/checker/tests/resourceleak/TryWithResourcesFP.java +++ b/checker/tests/resourceleak/TryWithResourcesFP.java @@ -1,27 +1,29 @@ // Based on a false positive reported on the BibTeX project +import org.plumelib.util.EntryReader; +import org.plumelib.util.UtilPlume; + import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import org.plumelib.util.EntryReader; -import org.plumelib.util.UtilPlume; @SuppressWarnings("deprecation") public final class TryWithResourcesFP { - public static void main(String[] args) { - for (String filename : args) { - File inFile = new File(filename); - File outFile = new File(inFile.getName()); // in current directory - // Delete the file to work around a bug. Files.newBufferedWriter (which is called by - // UtilPlume.bufferedFileWriter) seems to have a bug where it does not correctly - // truncate the file first. If the target file already exists, then characters beyond - // what is written remain in the file. - outFile.delete(); - try (PrintWriter out = new PrintWriter(UtilPlume.bufferedFileWriter(outFile.toString())); - EntryReader er = new EntryReader(filename)) { - } catch (IOException e) { + public static void main(String[] args) { + for (String filename : args) { + File inFile = new File(filename); + File outFile = new File(inFile.getName()); // in current directory + // Delete the file to work around a bug. Files.newBufferedWriter (which is called by + // UtilPlume.bufferedFileWriter) seems to have a bug where it does not correctly + // truncate the file first. If the target file already exists, then characters beyond + // what is written remain in the file. + outFile.delete(); + try (PrintWriter out = + new PrintWriter(UtilPlume.bufferedFileWriter(outFile.toString())); + EntryReader er = new EntryReader(filename)) { + } catch (IOException e) { - } + } + } } - } } diff --git a/checker/tests/resourceleak/TryWithResourcesMultiResources.java b/checker/tests/resourceleak/TryWithResourcesMultiResources.java index c2ada57e2f0..de39bfc0399 100644 --- a/checker/tests/resourceleak/TryWithResourcesMultiResources.java +++ b/checker/tests/resourceleak/TryWithResourcesMultiResources.java @@ -1,42 +1,43 @@ -import java.io.IOException; -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.IOException; +import java.net.Socket; + public class TryWithResourcesMultiResources { - class OuterResource implements java.io.Closeable { - private final @Owning Socket socket; + class OuterResource implements java.io.Closeable { + private final @Owning Socket socket; - public @MustCallAlias OuterResource(@MustCallAlias Socket sock) throws IOException { - this.socket = sock; - } + public @MustCallAlias OuterResource(@MustCallAlias Socket sock) throws IOException { + this.socket = sock; + } - @Override - @EnsuresCalledMethods( - value = {"this.socket"}, - methods = {"close"}) - public void close() throws IOException { - this.socket.close(); + @Override + @EnsuresCalledMethods( + value = {"this.socket"}, + methods = {"close"}) + public void close() throws IOException { + this.socket.close(); + } } - } - // If "new OuterResource" throws an exception, then the socket won't be released. - public void multiResourcesWrong(String address, int port) { - // :: error: required.method.not.called - try (OuterResource outer = new OuterResource(new Socket(address, port))) { + // If "new OuterResource" throws an exception, then the socket won't be released. + public void multiResourcesWrong(String address, int port) { + // :: error: required.method.not.called + try (OuterResource outer = new OuterResource(new Socket(address, port))) { - } catch (Exception e) { + } catch (Exception e) { + } } - } - public void multiResourcesCorrect(String address, int port) { - try (Socket s = new Socket(address, port); - OuterResource outer = new OuterResource(s)) { + public void multiResourcesCorrect(String address, int port) { + try (Socket s = new Socket(address, port); + OuterResource outer = new OuterResource(s)) { - } catch (Exception e) { + } catch (Exception e) { + } } - } } diff --git a/checker/tests/resourceleak/TryWithResourcesVariable.java b/checker/tests/resourceleak/TryWithResourcesVariable.java index 7642e22fcd6..34568261fba 100644 --- a/checker/tests/resourceleak/TryWithResourcesVariable.java +++ b/checker/tests/resourceleak/TryWithResourcesVariable.java @@ -2,125 +2,126 @@ // @below-java9-jdk-skip-test -import java.io.*; -import java.net.*; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.*; +import java.net.*; + class TryWithResourcesVariable { - static void test1() throws Exception { - Socket socket = new Socket("127.0.0.1", 5050); - try (socket) { + static void test1() throws Exception { + Socket socket = new Socket("127.0.0.1", 5050); + try (socket) { - } catch (Exception e) { + } catch (Exception e) { + } } - } - static void test2(@Owning Socket socket) { - try (socket) { + static void test2(@Owning Socket socket) { + try (socket) { - } catch (Exception e) { + } catch (Exception e) { + } } - } - static void test3(InetSocketAddress isa) { - Socket socket = new Socket(); - try (socket) { - socket.connect(isa); - } catch (Exception e) { + static void test3(InetSocketAddress isa) { + Socket socket = new Socket(); + try (socket) { + socket.connect(isa); + } catch (Exception e) { + } } - } - - // :: error: (required.method.not.called) - static void test4(@Owning InputStream i1, @Owning InputStream i2) { - try { - try (i2) {} - // This will not run if i2.close() throws an IOException - i1.close(); - } catch (Exception e) { + // :: error: (required.method.not.called) + static void test4(@Owning InputStream i1, @Owning InputStream i2) { + try { + try (i2) {} + // This will not run if i2.close() throws an IOException + i1.close(); + } catch (Exception e) { + + } } - } - static void test4Fixed(@Owning InputStream i1, @Owning InputStream i2) throws IOException { - try { - try (i2) {} - } catch (Exception e) { - } - i1.close(); - } - - @InheritableMustCall("disposer") - static class FinalResourceField { - final @Owning Socket socketField; - - FinalResourceField() { - try { - socketField = new Socket("127.0.0.1", 5050); - } catch (Exception e) { - throw new RuntimeException(e); - } + static void test4Fixed(@Owning InputStream i1, @Owning InputStream i2) throws IOException { + try { + try (i2) {} + } catch (Exception e) { + } + i1.close(); } - @EnsuresCalledMethods(value = "this.socketField", methods = "close") - void disposer() { - try (socketField) { + @InheritableMustCall("disposer") + static class FinalResourceField { + final @Owning Socket socketField; - } catch (Exception e) { + FinalResourceField() { + try { + socketField = new Socket("127.0.0.1", 5050); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - } + @EnsuresCalledMethods(value = "this.socketField", methods = "close") + void disposer() { + try (socketField) { + + } catch (Exception e) { + + } + } } - } - static void closeFinalFieldUnsupported() throws Exception { - // This is a false positive (i.e., there is no resource leak), but our checker reports a - // warning since it does not support this coding pattern. - // :: error: (required.method.not.called) - FinalResourceField finalResourceField = new FinalResourceField(); - try (finalResourceField.socketField) {} - } + static void closeFinalFieldUnsupported() throws Exception { + // This is a false positive (i.e., there is no resource leak), but our checker reports a + // warning since it does not support this coding pattern. + // :: error: (required.method.not.called) + FinalResourceField finalResourceField = new FinalResourceField(); + try (finalResourceField.socketField) {} + } - @InheritableMustCall("disposer") - static class FinalResourceFieldWrapper { + @InheritableMustCall("disposer") + static class FinalResourceFieldWrapper { - final @Owning FinalResourceField frField = new FinalResourceField(); + final @Owning FinalResourceField frField = new FinalResourceField(); - @EnsuresCalledMethods(value = "this.frField", methods = "disposer") - void disposer() { - this.frField.disposer(); + @EnsuresCalledMethods(value = "this.frField", methods = "disposer") + void disposer() { + this.frField.disposer(); + } } - } - static void closeWrapperUnsupported() throws Exception { - // This is a false positive (i.e., there is no resource leak), but our checker reports a - // warning since it does not support this coding pattern. - // :: error: (required.method.not.called) - FinalResourceFieldWrapper finalResourceFieldWrapper = new FinalResourceFieldWrapper(); - try (finalResourceFieldWrapper.frField.socketField) {} - } - - @InheritableMustCall("disposer") - static class TwoFinalResourceFields { - final @Owning Socket socketField1; - final @Owning Socket socketField2; - - TwoFinalResourceFields(@Owning Socket socket1, @Owning Socket socket2) { - socketField1 = socket1; - socketField2 = socket2; + static void closeWrapperUnsupported() throws Exception { + // This is a false positive (i.e., there is no resource leak), but our checker reports a + // warning since it does not support this coding pattern. + // :: error: (required.method.not.called) + FinalResourceFieldWrapper finalResourceFieldWrapper = new FinalResourceFieldWrapper(); + try (finalResourceFieldWrapper.frField.socketField) {} } - @EnsuresCalledMethods(value = "this.socketField1", methods = "close") - @EnsuresCalledMethods(value = "this.socketField2", methods = "close") - void disposer() { - try (socketField1; - socketField2) { + @InheritableMustCall("disposer") + static class TwoFinalResourceFields { + final @Owning Socket socketField1; + final @Owning Socket socketField2; + + TwoFinalResourceFields(@Owning Socket socket1, @Owning Socket socket2) { + socketField1 = socket1; + socketField2 = socket2; + } + + @EnsuresCalledMethods(value = "this.socketField1", methods = "close") + @EnsuresCalledMethods(value = "this.socketField2", methods = "close") + void disposer() { + try (socketField1; + socketField2) { - } catch (Exception e) { + } catch (Exception e) { - } + } + } } - } } diff --git a/checker/tests/resourceleak/TwoConstructorsCloseable.java b/checker/tests/resourceleak/TwoConstructorsCloseable.java index 282ce8d1a1e..47a20424728 100644 --- a/checker/tests/resourceleak/TwoConstructorsCloseable.java +++ b/checker/tests/resourceleak/TwoConstructorsCloseable.java @@ -3,17 +3,17 @@ import java.io.Closeable; public class TwoConstructorsCloseable implements Closeable { - public TwoConstructorsCloseable(Object obj) {} + public TwoConstructorsCloseable(Object obj) {} - public TwoConstructorsCloseable() { - this(null); - } + public TwoConstructorsCloseable() { + this(null); + } - public void close() {} + public void close() {} - class Derivative extends TwoConstructorsCloseable { - Derivative() { - super(null); + class Derivative extends TwoConstructorsCloseable { + Derivative() { + super(null); + } } - } } diff --git a/checker/tests/resourceleak/TwoOwningMCATest.java b/checker/tests/resourceleak/TwoOwningMCATest.java index 6167a508f6c..5166c73253d 100644 --- a/checker/tests/resourceleak/TwoOwningMCATest.java +++ b/checker/tests/resourceleak/TwoOwningMCATest.java @@ -6,33 +6,33 @@ @InheritableMustCall({"finish1", "finish2"}) class TwoOwningMCATest { - @Owning private final Foo f1 = new Foo(); - - @Owning private final Foo f2; - - @MustCallAlias - // :: error: mustcallalias.out.of.scope - TwoOwningMCATest(@MustCallAlias Foo g) { - this.f2 = g; - } - - @EnsuresCalledMethods(value = "this.f1", methods = "a") - void finish1() { - this.f1.a(); - } - - @EnsuresCalledMethods(value = "this.f2", methods = "a") - void finish2() { - this.f2.a(); - } - - @InheritableMustCall("a") - static class Foo { - void a() {} - } - - public static void test(Foo f) { - TwoOwningMCATest t = new TwoOwningMCATest(f); - f.a(); - } + @Owning private final Foo f1 = new Foo(); + + @Owning private final Foo f2; + + @MustCallAlias + // :: error: mustcallalias.out.of.scope + TwoOwningMCATest(@MustCallAlias Foo g) { + this.f2 = g; + } + + @EnsuresCalledMethods(value = "this.f1", methods = "a") + void finish1() { + this.f1.a(); + } + + @EnsuresCalledMethods(value = "this.f2", methods = "a") + void finish2() { + this.f2.a(); + } + + @InheritableMustCall("a") + static class Foo { + void a() {} + } + + public static void test(Foo f) { + TwoOwningMCATest t = new TwoOwningMCATest(f); + f.a(); + } } diff --git a/checker/tests/resourceleak/TwoResourcesECM.java b/checker/tests/resourceleak/TwoResourcesECM.java index e048b813ea8..aea95247566 100644 --- a/checker/tests/resourceleak/TwoResourcesECM.java +++ b/checker/tests/resourceleak/TwoResourcesECM.java @@ -3,39 +3,40 @@ // This test that shows that no unsoundess occurs when a single close() method is responsible // for closing two resources. -import java.io.IOException; -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.io.IOException; +import java.net.Socket; + @InheritableMustCall("dispose") class TwoResourcesECM { - @Owning Socket s1, s2; + @Owning Socket s1, s2; - // The contracts.postcondition error below is thrown because s1 is not final, - // and therefore might theoretically be side-effected by the call to s2.close() - // even on the non-exceptional path. See ReplicaInputStreams.java for a variant - // of this test where such an error is not issued. Because this method can leak - // along both regular and exceptional exits, both errors are issued. - // - // The contracts.exceptional.postcondition.not.satisfied error is thrown because destructors - // have to close their resources even on exception. If s1.close() throws an exception, then - // s2.close() will not be called. - @EnsuresCalledMethods( - value = {"this.s1", "this.s2"}, - methods = {"close"}) - // :: error: (contracts.postcondition.not.satisfied) - // :: error: (contracts.exceptional.postcondition.not.satisfied) - public void dispose() throws IOException { - s1.close(); - s2.close(); - } + // The contracts.postcondition error below is thrown because s1 is not final, + // and therefore might theoretically be side-effected by the call to s2.close() + // even on the non-exceptional path. See ReplicaInputStreams.java for a variant + // of this test where such an error is not issued. Because this method can leak + // along both regular and exceptional exits, both errors are issued. + // + // The contracts.exceptional.postcondition.not.satisfied error is thrown because destructors + // have to close their resources even on exception. If s1.close() throws an exception, then + // s2.close() will not be called. + @EnsuresCalledMethods( + value = {"this.s1", "this.s2"}, + methods = {"close"}) + // :: error: (contracts.postcondition.not.satisfied) + // :: error: (contracts.exceptional.postcondition.not.satisfied) + public void dispose() throws IOException { + s1.close(); + s2.close(); + } - static void test1(TwoResourcesECM obj) { - try { - obj.dispose(); - } catch (IOException ioe) { + static void test1(TwoResourcesECM obj) { + try { + obj.dispose(); + } catch (IOException ioe) { + } } - } } diff --git a/checker/tests/resourceleak/TwoSocketContainer.java b/checker/tests/resourceleak/TwoSocketContainer.java index 0b21e58d130..f290e89aaf0 100644 --- a/checker/tests/resourceleak/TwoSocketContainer.java +++ b/checker/tests/resourceleak/TwoSocketContainer.java @@ -1,37 +1,38 @@ // A test that a class with two owned sockets cannot be @MustCallAliased with both of them. -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.Socket; + @InheritableMustCall({"close1", "close2"}) public class TwoSocketContainer { - @Owning private final Socket s1, s2; + @Owning private final Socket s1, s2; - // :: error: mustcallalias.out.of.scope - public @MustCallAlias TwoSocketContainer(@MustCallAlias Socket s1, @MustCallAlias Socket s2) { - this.s1 = s1; - this.s2 = s2; - } + // :: error: mustcallalias.out.of.scope + public @MustCallAlias TwoSocketContainer(@MustCallAlias Socket s1, @MustCallAlias Socket s2) { + this.s1 = s1; + this.s2 = s2; + } - @EnsuresCalledMethods( - value = "this.s1", - methods = {"close"}) - public void close1() throws java.io.IOException { - s1.close(); - } + @EnsuresCalledMethods( + value = "this.s1", + methods = {"close"}) + public void close1() throws java.io.IOException { + s1.close(); + } - @EnsuresCalledMethods( - value = "this.s2", - methods = {"close"}) - public void close2() throws java.io.IOException { - s2.close(); - } + @EnsuresCalledMethods( + value = "this.s2", + methods = {"close"}) + public void close2() throws java.io.IOException { + s2.close(); + } - // The following error should be thrown about at least sock2 - // :: error: required.method.not.called - public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { - TwoSocketContainer tsc = new TwoSocketContainer(sock1, sock2); - sock1.close(); - } + // The following error should be thrown about at least sock2 + // :: error: required.method.not.called + public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { + TwoSocketContainer tsc = new TwoSocketContainer(sock1, sock2); + sock1.close(); + } } diff --git a/checker/tests/resourceleak/TwoSocketContainerSafe.java b/checker/tests/resourceleak/TwoSocketContainerSafe.java index f7cea334fe4..6e1482a1e76 100644 --- a/checker/tests/resourceleak/TwoSocketContainerSafe.java +++ b/checker/tests/resourceleak/TwoSocketContainerSafe.java @@ -1,42 +1,43 @@ // This is the safe version of TwoSocketContainer.java. -import java.net.Socket; import org.checkerframework.checker.calledmethods.qual.*; import org.checkerframework.checker.mustcall.qual.*; +import java.net.Socket; + @InheritableMustCall({"close1", "close2"}) public class TwoSocketContainerSafe { - @Owning private final Socket s1, s2; + @Owning private final Socket s1, s2; - public TwoSocketContainerSafe(@Owning Socket s1, @Owning Socket s2) { - this.s1 = s1; - this.s2 = s2; - } + public TwoSocketContainerSafe(@Owning Socket s1, @Owning Socket s2) { + this.s1 = s1; + this.s2 = s2; + } - @EnsuresCalledMethods( - value = "this.s1", - methods = {"close"}) - public void close1() throws java.io.IOException { - s1.close(); - } + @EnsuresCalledMethods( + value = "this.s1", + methods = {"close"}) + public void close1() throws java.io.IOException { + s1.close(); + } - @EnsuresCalledMethods( - value = "this.s2", - methods = {"close"}) - public void close2() throws java.io.IOException { - s2.close(); - } + @EnsuresCalledMethods( + value = "this.s2", + methods = {"close"}) + public void close2() throws java.io.IOException { + s2.close(); + } - public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { - TwoSocketContainerSafe tsc = new TwoSocketContainerSafe(sock1, sock2); - try { - tsc.close1(); - } catch (Exception io) { - } finally { - try { - tsc.close2(); - } catch (Exception io2) { - } + public static void test(@Owning Socket sock1, @Owning Socket sock2) throws java.io.IOException { + TwoSocketContainerSafe tsc = new TwoSocketContainerSafe(sock1, sock2); + try { + tsc.close1(); + } catch (Exception io) { + } finally { + try { + tsc.close2(); + } catch (Exception io2) { + } + } } - } } diff --git a/checker/tests/resourceleak/TypeProcessError.java b/checker/tests/resourceleak/TypeProcessError.java index b10816c1f88..4db149eee1c 100644 --- a/checker/tests/resourceleak/TypeProcessError.java +++ b/checker/tests/resourceleak/TypeProcessError.java @@ -1,32 +1,33 @@ -import java.io.IOException; -import java.io.PrintStream; import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.mustcall.qual.Owning; +import java.io.IOException; +import java.io.PrintStream; + public class TypeProcessError { - @SuppressWarnings("required.method.not.called") - @Owning - @MustCall("close") PrintStream ps_instance; + @SuppressWarnings("required.method.not.called") + @Owning + @MustCall("close") PrintStream ps_instance; - @SuppressWarnings("required.method.not.called") - private static @Owning @MustCall("close") PrintStream ps_static; + @SuppressWarnings("required.method.not.called") + private static @Owning @MustCall("close") PrintStream ps_static; - @SuppressWarnings("missing.creates.mustcall.for") - static void m_static() throws IOException { - ps_static.close(); - ps_static = new PrintStream("filename.txt"); - } + @SuppressWarnings("missing.creates.mustcall.for") + static void m_static() throws IOException { + ps_static.close(); + ps_static = new PrintStream("filename.txt"); + } } class TypeProcessError2 extends TypeProcessError { - @SuppressWarnings("required.method.not.called") - @Owning - @MustCall("close") PrintStream ps_instance; + @SuppressWarnings("required.method.not.called") + @Owning + @MustCall("close") PrintStream ps_instance; - @SuppressWarnings("missing.creates.mustcall.for") - void m() throws IOException { - super.ps_instance.close(); - super.ps_instance = new PrintStream("filename.txt"); - } + @SuppressWarnings("missing.creates.mustcall.for") + void m() throws IOException { + super.ps_instance.close(); + super.ps_instance = new PrintStream("filename.txt"); + } } diff --git a/checker/tests/resourceleak/TypevarDefault.java b/checker/tests/resourceleak/TypevarDefault.java index e3911ee8dc4..e6641b69a3a 100644 --- a/checker/tests/resourceleak/TypevarDefault.java +++ b/checker/tests/resourceleak/TypevarDefault.java @@ -3,11 +3,11 @@ import org.checkerframework.checker.mustcall.qual.*; class IATF< - Value extends CFAV, - Store extends IS, - Transfer extends IT, - Flow extends CFAA> - extends GATF {} + Value extends CFAV, + Store extends IS, + Transfer extends IT, + Flow extends CFAA> + extends GATF {} class CFAV> {} @@ -22,27 +22,27 @@ class CFAT, S extends CFAS, T extends CFAT> {} class CFAS, S extends CFAS> {} class GATF< - Value extends CFAV, - Store extends CFAS, - TransferFunction extends CFAT, - FlowAnalysis extends CFAA> { - - public @MustCall({}) Store getRegularExitStore() { - return null; - } + Value extends CFAV, + Store extends CFAS, + TransferFunction extends CFAT, + FlowAnalysis extends CFAA> { + + public @MustCall({}) Store getRegularExitStore() { + return null; + } } class BTV> {} class IV< - Factory extends IATF, - Value extends CFAV, - Store extends IS> - extends BTV { + Factory extends IATF, + Value extends CFAV, + Store extends IS> + extends BTV { - Factory atypefactory; + Factory atypefactory; - public void test() { - Store store = this.atypefactory.getRegularExitStore(); - } + public void test() { + Store store = this.atypefactory.getRegularExitStore(); + } } diff --git a/checker/tests/resourceleak/TypevarSimple.java b/checker/tests/resourceleak/TypevarSimple.java index 5376d73c2b8..639ec7a80cb 100644 --- a/checker/tests/resourceleak/TypevarSimple.java +++ b/checker/tests/resourceleak/TypevarSimple.java @@ -4,8 +4,8 @@ import org.checkerframework.checker.mustcall.qual.*; public class TypevarSimple { - public static void sneakyDropCorrect( - @Owning @MustCall("close") T value1) throws Exception { - value1.close(); - } + public static void sneakyDropCorrect( + @Owning @MustCall("close") T value1) throws Exception { + value1.close(); + } } diff --git a/checker/tests/resourceleak/UnconnectedSocketAlias.java b/checker/tests/resourceleak/UnconnectedSocketAlias.java index 55bd1e96897..7e1bc0b1bb3 100644 --- a/checker/tests/resourceleak/UnconnectedSocketAlias.java +++ b/checker/tests/resourceleak/UnconnectedSocketAlias.java @@ -4,11 +4,11 @@ import java.net.*; class UnconnectedSocketAlias { - void test(SocketAddress sa) throws Exception { - // :: error: required.method.not.called - Socket s = new Socket(); - Socket t = s; - t.close(); - s.connect(sa); - } + void test(SocketAddress sa) throws Exception { + // :: error: required.method.not.called + Socket s = new Socket(); + Socket t = s; + t.close(); + s.connect(sa); + } } diff --git a/checker/tests/resourceleak/WrapperStream.java b/checker/tests/resourceleak/WrapperStream.java index 79f187c5193..ad3c9e90908 100644 --- a/checker/tests/resourceleak/WrapperStream.java +++ b/checker/tests/resourceleak/WrapperStream.java @@ -4,7 +4,7 @@ import java.io.*; class WrapperStream { - void test(byte[] buf) { - InputStream is = new ByteArrayInputStream(buf); - } + void test(byte[] buf) { + InputStream is = new ByteArrayInputStream(buf); + } } diff --git a/checker/tests/resourceleak/WrapperStreamPoly.java b/checker/tests/resourceleak/WrapperStreamPoly.java index 8a50fdf0917..7a3eafbd897 100644 --- a/checker/tests/resourceleak/WrapperStreamPoly.java +++ b/checker/tests/resourceleak/WrapperStreamPoly.java @@ -2,17 +2,18 @@ // "polymorphic" streams like DataInputStream and DataOutputStream are treated as // their constituent stream. -import java.io.*; import org.checkerframework.checker.mustcall.qual.Owning; +import java.io.*; + class WrapperStreamPoly { - void test_no_close_needed(@Owning ByteArrayInputStream b) { - // b doesn't need to be closed, so neither does this stream. - DataInputStream d = new DataInputStream(b); - } + void test_no_close_needed(@Owning ByteArrayInputStream b) { + // b doesn't need to be closed, so neither does this stream. + DataInputStream d = new DataInputStream(b); + } - // :: error: required.method.not.called - void test_close_needed(@Owning InputStream b) { - DataInputStream d = new DataInputStream(b); - } + // :: error: required.method.not.called + void test_close_needed(@Owning InputStream b) { + DataInputStream d = new DataInputStream(b); + } } diff --git a/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java b/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java index 187ca36ff15..0cb36deee57 100644 --- a/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java +++ b/checker/tests/resourceleak/ZookeeperByteBufferInputStream.java @@ -5,58 +5,59 @@ // regression // test that at least one error is still issued. +import org.checkerframework.checker.mustcall.qual.MustCall; + import java.io.*; import java.nio.ByteBuffer; -import org.checkerframework.checker.mustcall.qual.MustCall; @MustCall({}) // :: error: inconsistent.mustcall.subtype public class ZookeeperByteBufferInputStream extends InputStream { - ByteBuffer bb; + ByteBuffer bb; - // :: error: super.invocation.invalid - public ZookeeperByteBufferInputStream(ByteBuffer bb) { - this.bb = bb; - } + // :: error: super.invocation.invalid + public ZookeeperByteBufferInputStream(ByteBuffer bb) { + this.bb = bb; + } - @Override - public int read() throws IOException { - if (bb.remaining() == 0) { - return -1; + @Override + public int read() throws IOException { + if (bb.remaining() == 0) { + return -1; + } + return bb.get() & 0xff; } - return bb.get() & 0xff; - } - - @Override - public int available() throws IOException { - return bb.remaining(); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (bb.remaining() == 0) { - return -1; + + @Override + public int available() throws IOException { + return bb.remaining(); } - if (len > bb.remaining()) { - len = bb.remaining(); + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (bb.remaining() == 0) { + return -1; + } + if (len > bb.remaining()) { + len = bb.remaining(); + } + bb.get(b, off, len); + return len; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); } - bb.get(b, off, len); - return len; - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public long skip(long n) throws IOException { - if (n < 0L) { - return 0; + + @Override + public long skip(long n) throws IOException { + if (n < 0L) { + return 0; + } + n = Math.min(n, bb.remaining()); + bb.position(bb.position() + (int) n); + return n; } - n = Math.min(n, bb.remaining()); - bb.position(bb.position() + (int) n); - return n; - } } diff --git a/checker/tests/resourceleak/ZookeeperReport1.java b/checker/tests/resourceleak/ZookeeperReport1.java index f00cad7ff77..a38b2be1a21 100644 --- a/checker/tests/resourceleak/ZookeeperReport1.java +++ b/checker/tests/resourceleak/ZookeeperReport1.java @@ -1,71 +1,72 @@ // Based on a Zookeeper false positive that requires unconnected socket support. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; -import org.checkerframework.checker.mustcall.qual.*; class ZookeeperReport1 { - static int tickTime, initLimit; + static int tickTime, initLimit; - protected static @MustCall({}) Socket createSocket() throws IOException { - Socket sock; - sock = new Socket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall({}) Socket createSocket() throws IOException { + Socket sock; + sock = new Socket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - protected static @MustCall({}) Socket createSocket2() throws IOException { - Socket sock; - sock = createCustomSocket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall({}) Socket createSocket2() throws IOException { + Socket sock; + sock = createCustomSocket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - // This is the full version of case 1. - protected static @MustCall({}) Socket createSocket3(boolean b) throws IOException { - Socket sock; - if (b) { - sock = createCustomSocket(); - } else { - sock = new Socket(); + // This is the full version of case 1. + protected static @MustCall({}) Socket createSocket3(boolean b) throws IOException { + Socket sock; + if (b) { + sock = createCustomSocket(); + } else { + sock = new Socket(); + } + sock.setSoTimeout(tickTime * initLimit); + return sock; } - sock.setSoTimeout(tickTime * initLimit); - return sock; - } - private static @MustCall({}) Socket createCustomSocket() { - return new Socket(); - } + private static @MustCall({}) Socket createCustomSocket() { + return new Socket(); + } - static void use1() throws IOException { - Socket s = createSocket(); - } + static void use1() throws IOException { + Socket s = createSocket(); + } - static void use2(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket(); - s.connect(endpoint); - } + static void use2(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket(); + s.connect(endpoint); + } - static void use3() throws IOException { - Socket s = createSocket2(); - } + static void use3() throws IOException { + Socket s = createSocket2(); + } - static void use4(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket2(); - s.connect(endpoint); - } + static void use4(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket2(); + s.connect(endpoint); + } - static void use5(boolean b) throws IOException { - Socket s = createSocket3(b); - } + static void use5(boolean b) throws IOException { + Socket s = createSocket3(b); + } - static void use6(SocketAddress endpoint, boolean b) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket3(b); - s.connect(endpoint); - } + static void use6(SocketAddress endpoint, boolean b) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket3(b); + s.connect(endpoint); + } } diff --git a/checker/tests/resourceleak/ZookeeperReport1a.java b/checker/tests/resourceleak/ZookeeperReport1a.java index a286418ca9d..5462d47ad3b 100644 --- a/checker/tests/resourceleak/ZookeeperReport1a.java +++ b/checker/tests/resourceleak/ZookeeperReport1a.java @@ -1,72 +1,73 @@ // Based on a Zookeeper false positive that requires unconnected socket support. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; -import org.checkerframework.checker.mustcall.qual.*; // Like ZookeeperReport1, but using "@MustCall()" instead of "@MustCall({})" class ZookeeperReport1a { - static int tickTime, initLimit; + static int tickTime, initLimit; - protected static @MustCall() Socket createSocket() throws IOException { - Socket sock; - sock = new Socket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall() Socket createSocket() throws IOException { + Socket sock; + sock = new Socket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - protected static @MustCall() Socket createSocket2() throws IOException { - Socket sock; - sock = createCustomSocket(); - sock.setSoTimeout(tickTime * initLimit); - return sock; - } + protected static @MustCall() Socket createSocket2() throws IOException { + Socket sock; + sock = createCustomSocket(); + sock.setSoTimeout(tickTime * initLimit); + return sock; + } - // This is the full version of case 1. - protected static @MustCall() Socket createSocket3(boolean b) throws IOException { - Socket sock; - if (b) { - sock = createCustomSocket(); - } else { - sock = new Socket(); + // This is the full version of case 1. + protected static @MustCall() Socket createSocket3(boolean b) throws IOException { + Socket sock; + if (b) { + sock = createCustomSocket(); + } else { + sock = new Socket(); + } + sock.setSoTimeout(tickTime * initLimit); + return sock; } - sock.setSoTimeout(tickTime * initLimit); - return sock; - } - private static @MustCall() Socket createCustomSocket() { - return new Socket(); - } + private static @MustCall() Socket createCustomSocket() { + return new Socket(); + } - static void use1() throws IOException { - Socket s = createSocket(); - } + static void use1() throws IOException { + Socket s = createSocket(); + } - static void use2(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket(); - s.connect(endpoint); - } + static void use2(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket(); + s.connect(endpoint); + } - static void use3() throws IOException { - Socket s = createSocket2(); - } + static void use3() throws IOException { + Socket s = createSocket2(); + } - static void use4(SocketAddress endpoint) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket2(); - s.connect(endpoint); - } + static void use4(SocketAddress endpoint) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket2(); + s.connect(endpoint); + } - static void use5(boolean b) throws IOException { - Socket s = createSocket3(b); - } + static void use5(boolean b) throws IOException { + Socket s = createSocket3(b); + } - static void use6(SocketAddress endpoint, boolean b) throws IOException { - // :: error: required.method.not.called - Socket s = createSocket3(b); - s.connect(endpoint); - } + static void use6(SocketAddress endpoint, boolean b) throws IOException { + // :: error: required.method.not.called + Socket s = createSocket3(b); + s.connect(endpoint); + } } diff --git a/checker/tests/resourceleak/ZookeeperReport3.java b/checker/tests/resourceleak/ZookeeperReport3.java index 43907dcc88f..099519e95fd 100644 --- a/checker/tests/resourceleak/ZookeeperReport3.java +++ b/checker/tests/resourceleak/ZookeeperReport3.java @@ -3,70 +3,71 @@ // ("createNewServerSocket"). This version of the test is incomplete: the real version // of this test also requires support for Optional. See ZookeeperTest3WithOptional.java. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.net.*; -import org.checkerframework.checker.mustcall.qual.*; class ZookeeperReport3 { - // This is a simpler version of case 3. - ServerSocket createServerSocket_easy( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - serverSocket = new ServerSocket(); - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return serverSocket; - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); + // This is a simpler version of case 3. + ServerSocket createServerSocket_easy( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + serverSocket = new ServerSocket(); + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return serverSocket; + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); + } + return null; } - return null; - } - ServerSocket createServerSocket( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - if (portUnification || sslQuorum) { - serverSocket = new UnifiedServerSocket(portUnification); - } else { - serverSocket = new ServerSocket(); - } - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return serverSocket; - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); + ServerSocket createServerSocket( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + if (portUnification || sslQuorum) { + serverSocket = new UnifiedServerSocket(portUnification); + } else { + serverSocket = new ServerSocket(); + } + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return serverSocket; + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); + } + return null; } - return null; - } - private ServerSocket createNewServerSocket( - SocketAddress address, boolean portUnification, boolean sslQuorum) throws IOException { - ServerSocket socket; + private ServerSocket createNewServerSocket( + SocketAddress address, boolean portUnification, boolean sslQuorum) throws IOException { + ServerSocket socket; - if (portUnification) { - System.out.println("Creating TLS-enabled quorum server socket"); - socket = new UnifiedServerSocket(true); - } else if (sslQuorum) { - System.out.println("Creating TLS-only quorum server socket"); - socket = new UnifiedServerSocket(false); - } else { - socket = new ServerSocket(); - } + if (portUnification) { + System.out.println("Creating TLS-enabled quorum server socket"); + socket = new UnifiedServerSocket(true); + } else if (sslQuorum) { + System.out.println("Creating TLS-only quorum server socket"); + socket = new UnifiedServerSocket(false); + } else { + socket = new ServerSocket(); + } - socket.setReuseAddress(true); - socket.bind(address); + socket.setReuseAddress(true); + socket.bind(address); - return socket; - } + return socket; + } - class UnifiedServerSocket extends ServerSocket { - // A human has to verify that this constructor actually does produce an unconnected socket. - @SuppressWarnings("inconsistent.constructor.type") - public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { - super(); + class UnifiedServerSocket extends ServerSocket { + // A human has to verify that this constructor actually does produce an unconnected socket. + @SuppressWarnings("inconsistent.constructor.type") + public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { + super(); + } } - } } diff --git a/checker/tests/resourceleak/ZookeeperReport3WithOptional.java b/checker/tests/resourceleak/ZookeeperReport3WithOptional.java index d21f746cce0..7a2fa20ca70 100644 --- a/checker/tests/resourceleak/ZookeeperReport3WithOptional.java +++ b/checker/tests/resourceleak/ZookeeperReport3WithOptional.java @@ -3,50 +3,51 @@ // @skip-test until Optional is supported. For now, users should use null instead. +import org.checkerframework.checker.mustcall.qual.*; + import java.io.*; import java.net.*; import java.util.Optional; -import org.checkerframework.checker.mustcall.qual.*; class ZookeeperReport3WithOptional { - // This is a simpler version of case 3. - Optional createServerSocket_easy( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - serverSocket = new ServerSocket(); - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return Optional.of(serverSocket); - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); + // This is a simpler version of case 3. + Optional createServerSocket_easy( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + serverSocket = new ServerSocket(); + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return Optional.of(serverSocket); + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); + } + return Optional.empty(); } - return Optional.empty(); - } - Optional createServerSocket( - InetSocketAddress address, boolean portUnification, boolean sslQuorum) { - ServerSocket serverSocket; - try { - if (portUnification || sslQuorum) { - serverSocket = new UnifiedServerSocket(portUnification); - } else { - serverSocket = new ServerSocket(); - } - serverSocket.setReuseAddress(true); - serverSocket.bind(address); - return Optional.of(serverSocket); - } catch (IOException e) { - System.err.println("Couldn't bind to " + address.toString() + e); + Optional createServerSocket( + InetSocketAddress address, boolean portUnification, boolean sslQuorum) { + ServerSocket serverSocket; + try { + if (portUnification || sslQuorum) { + serverSocket = new UnifiedServerSocket(portUnification); + } else { + serverSocket = new ServerSocket(); + } + serverSocket.setReuseAddress(true); + serverSocket.bind(address); + return Optional.of(serverSocket); + } catch (IOException e) { + System.err.println("Couldn't bind to " + address.toString() + e); + } + return Optional.empty(); } - return Optional.empty(); - } - class UnifiedServerSocket extends ServerSocket { - // A human has to verify that this constructor actually does produce an unconnected socket. - public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { - super(); + class UnifiedServerSocket extends ServerSocket { + // A human has to verify that this constructor actually does produce an unconnected socket. + public @MustCall({}) UnifiedServerSocket(boolean b) throws IOException { + super(); + } } - } } diff --git a/checker/tests/resourceleak/ZookeeperReport6.java b/checker/tests/resourceleak/ZookeeperReport6.java index 1f0a0d0c0dd..3adef15563d 100644 --- a/checker/tests/resourceleak/ZookeeperReport6.java +++ b/checker/tests/resourceleak/ZookeeperReport6.java @@ -4,12 +4,12 @@ import java.nio.channels.SocketChannel; class ZookeeperReport6 { - SocketChannel createSock() throws IOException { - SocketChannel sock; - sock = SocketChannel.open(); - sock.configureBlocking(false); - sock.socket().setSoLinger(false, -1); - sock.socket().setTcpNoDelay(true); - return sock; - } + SocketChannel createSock() throws IOException { + SocketChannel sock; + sock = SocketChannel.open(); + sock.configureBlocking(false); + sock.socket().setSoLinger(false, -1); + sock.socket().setTcpNoDelay(true); + return sock; + } } diff --git a/checker/tests/resourceleak/ZookeeperTernaryCrash.java b/checker/tests/resourceleak/ZookeeperTernaryCrash.java index 3c795c56a74..62df18e405d 100644 --- a/checker/tests/resourceleak/ZookeeperTernaryCrash.java +++ b/checker/tests/resourceleak/ZookeeperTernaryCrash.java @@ -1,74 +1,76 @@ +import org.checkerframework.checker.mustcall.qual.MustCall; + import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.*; -import org.checkerframework.checker.mustcall.qual.MustCall; final class ZookeeperTernaryCrash { - private static List getSubjectAltNames(final X509Certificate cert) { - try { - final Collection> entries = cert.getSubjectAlternativeNames(); - if (entries == null) { - return Collections.emptyList(); - } - final List result = new ArrayList(); - for (List entry : entries) { - // the need to add this annotation is annoying, but it's better than the - // alternative, which would be to prevent boxed primitives from having must-call - // types at all. - final Integer type = entry.size() >= 2 ? (@MustCall({}) Integer) entry.get(0) : null; - if (type != null) { - if (type == SubjectName.DNS || type == SubjectName.IP) { - final Object o = entry.get(1); - if (o instanceof String) { - result.add(new SubjectName((String) o, type)); - } else if (o instanceof byte[]) { - // TODO ASN.1 DER encoded form + private static List getSubjectAltNames(final X509Certificate cert) { + try { + final Collection> entries = cert.getSubjectAlternativeNames(); + if (entries == null) { + return Collections.emptyList(); + } + final List result = new ArrayList(); + for (List entry : entries) { + // the need to add this annotation is annoying, but it's better than the + // alternative, which would be to prevent boxed primitives from having must-call + // types at all. + final Integer type = + entry.size() >= 2 ? (@MustCall({}) Integer) entry.get(0) : null; + if (type != null) { + if (type == SubjectName.DNS || type == SubjectName.IP) { + final Object o = entry.get(1); + if (o instanceof String) { + result.add(new SubjectName((String) o, type)); + } else if (o instanceof byte[]) { + // TODO ASN.1 DER encoded form + } + } + } } - } + return result; + } catch (final CertificateParsingException ignore) { + return Collections.emptyList(); } - } - return result; - } catch (final CertificateParsingException ignore) { - return Collections.emptyList(); } - } - private static final class SubjectName { + private static final class SubjectName { - static final int DNS = 2; - static final int IP = 7; + static final int DNS = 2; + static final int IP = 7; - private final String value; - private final int type; + private final String value; + private final int type; - static SubjectName IP(final String value) { - return new SubjectName(value, IP); - } + static SubjectName IP(final String value) { + return new SubjectName(value, IP); + } - static SubjectName DNS(final String value) { - return new SubjectName(value, DNS); - } + static SubjectName DNS(final String value) { + return new SubjectName(value, DNS); + } - SubjectName(final String value, final int type) { - if (type != DNS && type != IP) { - throw new IllegalArgumentException("Invalid type: " + type); - } - this.value = Objects.requireNonNull(value); - this.type = type; - } + SubjectName(final String value, final int type) { + if (type != DNS && type != IP) { + throw new IllegalArgumentException("Invalid type: " + type); + } + this.value = Objects.requireNonNull(value); + this.type = type; + } - public int getType() { - return type; - } + public int getType() { + return type; + } - public String getValue() { - return value; - } + public String getValue() { + return value; + } - @Override - public String toString() { - return value; + @Override + public String toString() { + return value; + } } - } } diff --git a/checker/tests/resourceleak/java17/SwitchExpressions.java b/checker/tests/resourceleak/java17/SwitchExpressions.java index e5b64ff974a..f5a4521578f 100644 --- a/checker/tests/resourceleak/java17/SwitchExpressions.java +++ b/checker/tests/resourceleak/java17/SwitchExpressions.java @@ -5,181 +5,181 @@ class SwitchExpressions { - @InheritableMustCall("a") - class Foo { - void a() {} + @InheritableMustCall("a") + class Foo { + void a() {} - @This Foo b() { - return this; - } + @This Foo b() { + return this; + } - void c(@CalledMethods("a") Foo this) {} - } + void c(@CalledMethods("a") Foo this) {} + } - Foo makeFoo() { - return new Foo(); - } + Foo makeFoo() { + return new Foo(); + } - static void takeOwnership(@Owning Foo foo) { - foo.a(); - } + static void takeOwnership(@Owning Foo foo) { + foo.a(); + } - /** cases where switch expressions are assigned to a variable */ - void testSwitchAssigned(int i) { - Foo switch1 = - switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }; - switch1.a(); + /** cases where switch expressions are assigned to a variable */ + void testSwitchAssigned(int i) { + Foo switch1 = + switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }; + switch1.a(); + + // :: error: required.method.not.called + Foo switch2 = + switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }; + + // :: error: required.method.not.called + Foo x = new Foo(); + Foo switch3 = + switch (i) { + case 3 -> new Foo(); + default -> x; + }; + switch3.a(); + + Foo y = new Foo(); + Foo switch4 = + switch (i) { + case 3 -> y; + default -> y; + }; + switch4.a(); + + takeOwnership( + switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }); + + // :: error: required.method.not.called + Foo x2 = new Foo(); + takeOwnership( + switch (i) { + case 3 -> x2; + default -> null; + }); + + int j = 10; + Foo switchInLoop = null; + while (j > 0) { + // :: error: required.method.not.called + switchInLoop = + switch (i) { + case 3 -> null; + default -> new Foo(); + }; + j--; + } + switchInLoop.a(); - // :: error: required.method.not.called - Foo switch2 = - switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }; + (switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }) + .a(); + } - // :: error: required.method.not.called - Foo x = new Foo(); - Foo switch3 = - switch (i) { - case 3 -> new Foo(); - default -> x; - }; - switch3.a(); + /** + * tests where switch and cast expressions (possibly nested) may or may not be assigned to a + * variable + */ + void testSwitchCastUnassigned(int i) { + // :: error: required.method.not.called + if ((switch (i) { + case 3 -> new Foo(); + default -> null; + }) + != null) { + i = -i; + } - Foo y = new Foo(); - Foo switch4 = - switch (i) { - case 3 -> y; - default -> y; - }; - switch4.a(); - - takeOwnership( - switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }); - - // :: error: required.method.not.called - Foo x2 = new Foo(); - takeOwnership( - switch (i) { - case 3 -> x2; - default -> null; - }); - - int j = 10; - Foo switchInLoop = null; - while (j > 0) { - // :: error: required.method.not.called - switchInLoop = - switch (i) { - case 3 -> null; - default -> new Foo(); - }; - j--; - } - switchInLoop.a(); - - (switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }) - .a(); - } - - /** - * tests where switch and cast expressions (possibly nested) may or may not be assigned to a - * variable - */ - void testSwitchCastUnassigned(int i) { - // :: error: required.method.not.called - if ((switch (i) { - case 3 -> new Foo(); - default -> null; - }) - != null) { - i = -i; - } + // :: error: required.method.not.called + if (switch (i) { + case 3 -> makeFoo(); + default -> null; + } + != null) { + i = -i; + } - // :: error: required.method.not.called - if (switch (i) { - case 3 -> makeFoo(); - default -> null; + Foo x = new Foo(); + if (switch (i) { + case 3 -> x; + default -> null; + } + != null) { + i = -i; } - != null) { - i = -i; - } + x.a(); - Foo x = new Foo(); - if (switch (i) { - case 3 -> x; - default -> null; + // :: error: required.method.not.called + if (((Foo) new Foo()) != null) { + i = -i; } - != null) { - i = -i; - } - x.a(); - // :: error: required.method.not.called - if (((Foo) new Foo()) != null) { - i = -i; + // double cast; no error + Foo doubleCast = (Foo) ((Foo) makeFoo()); + doubleCast.a(); + + // nesting casts and switch expressions; no error + Foo deepNesting = + (switch (i) { + case 3 -> + (switch (-i) { + case -3 -> makeFoo(); + default -> (Foo) makeFoo(); + }); + default -> ((Foo) new Foo()); + }); + deepNesting.a(); } - // double cast; no error - Foo doubleCast = (Foo) ((Foo) makeFoo()); - doubleCast.a(); + @Owning + Foo testSwitchReturnOk(int i) { + return switch (i) { + case 3 -> new Foo(); + default -> makeFoo(); + }; + } - // nesting casts and switch expressions; no error - Foo deepNesting = - (switch (i) { - case 3 -> - (switch (-i) { - case -3 -> makeFoo(); - default -> (Foo) makeFoo(); - }); - default -> ((Foo) new Foo()); - }); - deepNesting.a(); - } - - @Owning - Foo testSwitchReturnOk(int i) { - return switch (i) { - case 3 -> new Foo(); - default -> makeFoo(); - }; - } - - @Owning - Foo testSwitchReturnBad(int i) { - // :: error: required.method.not.called - Foo x = new Foo(); - return switch (i) { - case 3 -> x; - default -> makeFoo(); - }; - } - - @InheritableMustCall("toString") - static class Sub1 extends Object {} - - @InheritableMustCall("clone") - static class Sub2 extends Object {} - - static void testSwitchSubtyping(int i) { - // :: error: required.method.not.called - Object toStringAndClone = - switch (i) { - case 3 -> new Sub1(); - default -> new Sub2(); + @Owning + Foo testSwitchReturnBad(int i) { + // :: error: required.method.not.called + Foo x = new Foo(); + return switch (i) { + case 3 -> x; + default -> makeFoo(); }; - // at this point, for soundness, we should be responsible for calling both toString and - // clone on - // obj... - toStringAndClone.toString(); - } + } + + @InheritableMustCall("toString") + static class Sub1 extends Object {} + + @InheritableMustCall("clone") + static class Sub2 extends Object {} + + static void testSwitchSubtyping(int i) { + // :: error: required.method.not.called + Object toStringAndClone = + switch (i) { + case 3 -> new Sub1(); + default -> new Sub2(); + }; + // at this point, for soundness, we should be responsible for calling both toString and + // clone on + // obj... + toStringAndClone.toString(); + } } diff --git a/checker/tests/signature/ArraysAsList.java b/checker/tests/signature/ArraysAsList.java index b8ae2754534..cd20e5d5cfa 100644 --- a/checker/tests/signature/ArraysAsList.java +++ b/checker/tests/signature/ArraysAsList.java @@ -1,10 +1,11 @@ +import org.checkerframework.checker.signature.qual.*; + import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.signature.qual.*; public class ArraysAsList { - List m() { - return Arrays.asList("id", "department_id", "permission_id", "expected_connection_time"); - } + List m() { + return Arrays.asList("id", "department_id", "permission_id", "expected_connection_time"); + } } diff --git a/checker/tests/signature/CanonicalNameNonEmptyTest.java b/checker/tests/signature/CanonicalNameNonEmptyTest.java index bd2a9ea2dd4..fd7a3eac882 100644 --- a/checker/tests/signature/CanonicalNameNonEmptyTest.java +++ b/checker/tests/signature/CanonicalNameNonEmptyTest.java @@ -2,28 +2,28 @@ public class CanonicalNameNonEmptyTest { - @CanonicalName String nonEmpty1(@CanonicalNameOrEmpty String s) { - if (s.isEmpty()) { - return null; - } else { - return s; + @CanonicalName String nonEmpty1(@CanonicalNameOrEmpty String s) { + if (s.isEmpty()) { + return null; + } else { + return s; + } } - } - @CanonicalName String nonEmpty2(@CanonicalNameOrEmpty String s) { - if (!s.isEmpty()) { - return s; - } else { - return null; + @CanonicalName String nonEmpty2(@CanonicalNameOrEmpty String s) { + if (!s.isEmpty()) { + return s; + } else { + return null; + } } - } - @CanonicalName String nonEmpty3(@FullyQualifiedName String s) { - if (s.isEmpty()) { - return null; - } else { - // :: error: (return.type.incompatible) - return s; + @CanonicalName String nonEmpty3(@FullyQualifiedName String s) { + if (s.isEmpty()) { + return null; + } else { + // :: error: (return.type.incompatible) + return s; + } } - } } diff --git a/checker/tests/signature/ClassGetNameBinaryName.java b/checker/tests/signature/ClassGetNameBinaryName.java index ccb38588842..544031cef8f 100644 --- a/checker/tests/signature/ClassGetNameBinaryName.java +++ b/checker/tests/signature/ClassGetNameBinaryName.java @@ -4,117 +4,117 @@ public class ClassGetNameBinaryName { - static class Nested {} + static class Nested {} - class Inner {} + class Inner {} - class TestGetName { + class TestGetName { - @DotSeparatedIdentifiers String s1 = ClassGetNameBinaryName.class.getName(); + @DotSeparatedIdentifiers String s1 = ClassGetNameBinaryName.class.getName(); - @DotSeparatedIdentifiers String s2a = Integer.class.getName(); + @DotSeparatedIdentifiers String s2a = Integer.class.getName(); - @DotSeparatedIdentifiers String s2b = java.lang.Integer.class.getName(); + @DotSeparatedIdentifiers String s2b = java.lang.Integer.class.getName(); - @DotSeparatedIdentifiers String s4a = Boolean.class.getName(); + @DotSeparatedIdentifiers String s4a = Boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String s4b = Boolean.class.getName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String s4b = Boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s12 = Nested.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s12 = Nested.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s13 = Inner.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s13 = Inner.class.getName(); - /// Primitive types + /// Primitive types - @PrimitiveType String prim1 = int.class.getName(); + @PrimitiveType String prim1 = int.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String prim2 = int.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String prim2 = int.class.getName(); - @PrimitiveType String prim3 = boolean.class.getName(); + @PrimitiveType String prim3 = boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String prim4 = boolean.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String prim4 = boolean.class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String prim5 = void.class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String prim5 = void.class.getName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String prim6 = void.class.getName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String prim6 = void.class.getName(); - /// Arrays + /// Arrays - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s6 = int[].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s6 = int[].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s7 = int[][].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s7 = int[][].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s8 = boolean[].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s8 = boolean[].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s9 = Integer[].class.getName(); + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s9 = Integer[].class.getName(); - // :: error: (assignment.type.incompatible) - @DotSeparatedIdentifiers String s10 = Boolean[].class.getName(); - } + // :: error: (assignment.type.incompatible) + @DotSeparatedIdentifiers String s10 = Boolean[].class.getName(); + } - class TestGetCanonicalName { + class TestGetCanonicalName { - @CanonicalNameAndBinaryName String s1 = ClassGetNameBinaryName.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s1 = ClassGetNameBinaryName.class.getCanonicalName(); - @CanonicalNameAndBinaryName String s2a = Integer.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s2a = Integer.class.getCanonicalName(); - @CanonicalNameAndBinaryName String s2b = java.lang.Integer.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s2b = java.lang.Integer.class.getCanonicalName(); - @CanonicalNameAndBinaryName String s4a = Boolean.class.getCanonicalName(); + @CanonicalNameAndBinaryName String s4a = Boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String s4b = Boolean.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String s4b = Boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s12 = Nested.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s12 = Nested.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s13 = Inner.class.getName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s13 = Inner.class.getName(); - /// Primitive types + /// Primitive types - @PrimitiveType String prim1 = int.class.getCanonicalName(); + @PrimitiveType String prim1 = int.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String prim2 = int.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String prim2 = int.class.getCanonicalName(); - @PrimitiveType String prim3 = boolean.class.getCanonicalName(); + @PrimitiveType String prim3 = boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String prim4 = boolean.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String prim4 = boolean.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String prim5 = void.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String prim5 = void.class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @PrimitiveType String prim6 = void.class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @PrimitiveType String prim6 = void.class.getCanonicalName(); - /// Arrays + /// Arrays - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s6 = int[].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s6 = int[].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s7 = int[][].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s7 = int[][].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s8 = boolean[].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s8 = boolean[].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s9 = Integer[].class.getCanonicalName(); + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s9 = Integer[].class.getCanonicalName(); - // :: error: (assignment.type.incompatible) - @CanonicalNameAndBinaryName String s10 = Boolean[].class.getCanonicalName(); - } + // :: error: (assignment.type.incompatible) + @CanonicalNameAndBinaryName String s10 = Boolean[].class.getCanonicalName(); + } } diff --git a/checker/tests/signature/Conversion.java b/checker/tests/signature/Conversion.java index 57d3e1db7fb..e3be2665134 100644 --- a/checker/tests/signature/Conversion.java +++ b/checker/tests/signature/Conversion.java @@ -2,103 +2,103 @@ public class Conversion { - class CharChar { - @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { - return bn.replace('.', '/'); + class CharChar { + @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { + return bn.replace('.', '/'); + } + + @BinaryName String internalFormToBinaryName(@InternalForm String iform) { + return iform.replace('/', '.'); + } + + @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace('/', '.'); + } + + @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace(':', '/'); + } + + @InternalForm String binaryNameToInternalFormWRONG3(String bn) { + // :: error: (return.type.incompatible) + return bn.replace('.', '/'); + } + + @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace('.', '/'); + } + + @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace('/', ':'); + } + + @BinaryName String internalFormToBinaryNameWRONG3(String iform) { + // :: error: (return.type.incompatible) + return iform.replace('/', '.'); + } + + @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace('$', '.'); + } + + @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace('$', '.'); + } + } + + class CharSequenceCharSequence { + @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { + return bn.replace(".", "/"); + } + + @BinaryName String internalFormToBinaryName(@InternalForm String iform) { + return iform.replace("/", "."); + } + + @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace("/", "."); + } + + @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace(":", "/"); + } + + @InternalForm String binaryNameToInternalFormWRONG3(String bn) { + // :: error: (return.type.incompatible) + return bn.replace(".", "/"); + } + + @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace(".", "/"); + } + + @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { + // :: error: (return.type.incompatible) + return iform.replace("/", ":"); + } + + @BinaryName String internalFormToBinaryNameWRONG3(String iform) { + // :: error: (return.type.incompatible) + return iform.replace("/", "."); + } + + @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace("$", "."); + } + + @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { + // :: error: (return.type.incompatible) + return bn.replace("$", "."); + } } - - @BinaryName String internalFormToBinaryName(@InternalForm String iform) { - return iform.replace('/', '.'); - } - - @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace('/', '.'); - } - - @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace(':', '/'); - } - - @InternalForm String binaryNameToInternalFormWRONG3(String bn) { - // :: error: (return.type.incompatible) - return bn.replace('.', '/'); - } - - @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace('.', '/'); - } - - @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace('/', ':'); - } - - @BinaryName String internalFormToBinaryNameWRONG3(String iform) { - // :: error: (return.type.incompatible) - return iform.replace('/', '.'); - } - - @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace('$', '.'); - } - - @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace('$', '.'); - } - } - - class CharSequenceCharSequence { - @InternalForm String binaryNameToInternalForm(@BinaryName String bn) { - return bn.replace(".", "/"); - } - - @BinaryName String internalFormToBinaryName(@InternalForm String iform) { - return iform.replace("/", "."); - } - - @InternalForm String binaryNameToInternalFormWRONG1(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace("/", "."); - } - - @InternalForm String binaryNameToInternalFormWRONG2(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace(":", "/"); - } - - @InternalForm String binaryNameToInternalFormWRONG3(String bn) { - // :: error: (return.type.incompatible) - return bn.replace(".", "/"); - } - - @BinaryName String internalFormToBinaryNameWRONG1(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace(".", "/"); - } - - @BinaryName String internalFormToBinaryNameWRONG2(@InternalForm String iform) { - // :: error: (return.type.incompatible) - return iform.replace("/", ":"); - } - - @BinaryName String internalFormToBinaryNameWRONG3(String iform) { - // :: error: (return.type.incompatible) - return iform.replace("/", "."); - } - - @DotSeparatedIdentifiers String binaryNameToDotSeparatedIdentifiers(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace("$", "."); - } - - @FullyQualifiedName String binaryNameToFullyQualifiedName(@BinaryName String bn) { - // :: error: (return.type.incompatible) - return bn.replace("$", "."); - } - } } diff --git a/checker/tests/signature/DiamondTest.java b/checker/tests/signature/DiamondTest.java index e23d7900d75..04402b728fd 100644 --- a/checker/tests/signature/DiamondTest.java +++ b/checker/tests/signature/DiamondTest.java @@ -1,9 +1,10 @@ -import java.util.ArrayList; import org.checkerframework.checker.signature.qual.*; +import java.util.ArrayList; + public class DiamondTest { - void m() { - ArrayList list = new ArrayList<>(); - } + void m() { + ArrayList list = new ArrayList<>(); + } } diff --git a/checker/tests/signature/FakeOverridePoly.java b/checker/tests/signature/FakeOverridePoly.java index a4e6978bf5e..b46a947e734 100644 --- a/checker/tests/signature/FakeOverridePoly.java +++ b/checker/tests/signature/FakeOverridePoly.java @@ -1,11 +1,12 @@ // @skip-test until fake overrides affect formal parameter types as well as return types -import javax.lang.model.element.Name; import org.checkerframework.checker.signature.qual.CanonicalName; +import javax.lang.model.element.Name; + public class FakeOverridePoly { - void m(@CanonicalName Name n) { - @CanonicalName String s = n.toString(); - } + void m(@CanonicalName Name n) { + @CanonicalName String s = n.toString(); + } } diff --git a/checker/tests/signature/PolySignatureTest.java b/checker/tests/signature/PolySignatureTest.java index de87622966c..356118c0fc8 100644 --- a/checker/tests/signature/PolySignatureTest.java +++ b/checker/tests/signature/PolySignatureTest.java @@ -2,12 +2,12 @@ public class PolySignatureTest { - @PolySignature String polyMethod(@PolySignature String arg) { - return arg; - } + @PolySignature String polyMethod(@PolySignature String arg) { + return arg; + } - void m(@ClassGetName String s) { - @ClassGetName String s1 = polyMethod(s); - @ClassGetName String s2 = s.intern(); - } + void m(@ClassGetName String s) { + @ClassGetName String s1 = polyMethod(s); + @ClassGetName String s2 = s.intern(); + } } diff --git a/checker/tests/signature/PolySignatureTest2.java b/checker/tests/signature/PolySignatureTest2.java index 56507d195d6..2dc859add40 100644 --- a/checker/tests/signature/PolySignatureTest2.java +++ b/checker/tests/signature/PolySignatureTest2.java @@ -1,18 +1,19 @@ // Test for stub files and https://tinyurl.com/cfissue/658 . // Commented in part because that issue is not yet fixed. +import org.checkerframework.checker.signature.qual.*; + import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; -import org.checkerframework.checker.signature.qual.*; public class PolySignatureTest2 { - @CanonicalNameOrEmpty Name m1(TypeElement e) { - return e.getQualifiedName(); - } + @CanonicalNameOrEmpty Name m1(TypeElement e) { + return e.getQualifiedName(); + } - @DotSeparatedIdentifiers String m2(@DotSeparatedIdentifiers Name n) { - // :: error: (return.type.incompatible) - return n.toString(); - } + @DotSeparatedIdentifiers String m2(@DotSeparatedIdentifiers Name n) { + // :: error: (return.type.incompatible) + return n.toString(); + } } diff --git a/checker/tests/signature/RefinedReturnTest.java b/checker/tests/signature/RefinedReturnTest.java index 1628b070c78..1c59ac15a6d 100644 --- a/checker/tests/signature/RefinedReturnTest.java +++ b/checker/tests/signature/RefinedReturnTest.java @@ -5,20 +5,20 @@ public class RefinedReturnTest { - public class Super { - public @FullyQualifiedName String aString() { - return "java.lang.Integer[][]"; + public class Super { + public @FullyQualifiedName String aString() { + return "java.lang.Integer[][]"; + } } - } - public class Sub extends Super { - @Override - public @ArrayWithoutPackage String aString() { - return "Integer[]"; + public class Sub extends Super { + @Override + public @ArrayWithoutPackage String aString() { + return "Integer[]"; + } } - } - void m() { - @ArrayWithoutPackage String s = new Sub().aString(); - } + void m() { + @ArrayWithoutPackage String s = new Sub().aString(); + } } diff --git a/checker/tests/signature/SignatureConcatenation.java b/checker/tests/signature/SignatureConcatenation.java index 901c7adb7d2..e55b4da9023 100644 --- a/checker/tests/signature/SignatureConcatenation.java +++ b/checker/tests/signature/SignatureConcatenation.java @@ -2,8 +2,8 @@ public class SignatureConcatenation { - @ClassGetSimpleName String m(@ClassGetSimpleName String arg1, @ClassGetSimpleName String arg2) { - // :: error: (return.type.incompatible) - return arg1 + arg2; - } + @ClassGetSimpleName String m(@ClassGetSimpleName String arg1, @ClassGetSimpleName String arg2) { + // :: error: (return.type.incompatible) + return arg1 + arg2; + } } diff --git a/checker/tests/signature/SignatureLiteralTest.java b/checker/tests/signature/SignatureLiteralTest.java index 11739b5f5b6..66cf1d73ad8 100644 --- a/checker/tests/signature/SignatureLiteralTest.java +++ b/checker/tests/signature/SignatureLiteralTest.java @@ -2,6 +2,6 @@ public class SignatureLiteralTest { - protected static final @FullyQualifiedName String FORMAT_NAME = - "org.checkerframework.checker.formatter.qual.Format"; + protected static final @FullyQualifiedName String FORMAT_NAME = + "org.checkerframework.checker.formatter.qual.Format"; } diff --git a/checker/tests/signature/SignatureTypeFactoryTest.java b/checker/tests/signature/SignatureTypeFactoryTest.java index 2a62fda0921..017fa38a4de 100644 --- a/checker/tests/signature/SignatureTypeFactoryTest.java +++ b/checker/tests/signature/SignatureTypeFactoryTest.java @@ -2,879 +2,879 @@ public class SignatureTypeFactoryTest { - // The hierarchy of type representations contains: - // - // SignatureUnknown.class, - // - // FullyQualifiedName.class, - // ClassGetName.class, - // FieldDescriptor.class, - // InternalForm.class, - // ClassGetSimpleName.class, - // FqBinaryName.class, - // - // BinaryName.class, - // FieldDescriptorWithoutPackage.class, - // - // ArrayWithoutPackage.class, - // DotSeparatedIdentifiers.class, - // BinaryNameWithoutPackage.class, - // - // Identifier.class, - // - // FieldDescriptorForPrimitive.class - // - // SignatureBottom.class - // - // There are also signature representations, which are not handled yet. - - void m() { - - String s1 = "a"; - String s2 = "a.b"; - String s3 = "a.b$c"; - String s4 = "B"; - String s5 = "[B"; - String s6 = "Ljava/lang/String;"; - String s7 = "Ljava/lang/String"; - // TODO: Should be @MethodDescriptor - String s8 = "foo()V"; - String s9 = "java.lang.annotation.Retention"; - String s10 = "dummy"; - String s11 = null; - String s12 = "a.b$c[][]"; - String s13 = "a.b.c[][]"; - String s14 = "[[Ljava/lang/String;"; - String s15 = ""; - String s16 = "[]"; - String s17 = "[][]"; - String s18 = "null"; - String s19 = "abstract"; - String s20 = "float"; - String s21 = "float "; - String s22 = " Foo"; - - // All the examples from the manual - String t13 = "int"; - String t14 = "int[][]"; - String t1 = "I"; - String t12 = "[[I"; - - String t5 = "MyClass"; - String t2 = "LMyClass;"; - String t6 = "MyClass[]"; - String t7 = "[LMyClass;"; - - String t29 = ""; - String t33 = "[]"; - - String t15 = "java.lang.Integer"; - String t16 = "java.lang.Integer[]"; - String t22 = "java/lang/Integer"; - String t23 = "java/lang/Integer[]"; - String t3 = "Ljava/lang/Integer;"; - String t8 = "[Ljava.lang.Integer;"; - String t9 = "[Ljava/lang/Integer;"; - - String t24 = "pakkage/Outer$Inner"; - String t25 = "pakkage/Outer$Inner[]"; - - String t28 = "pakkage/Outer$22"; - String t27 = "Lpakkage/Outer$22;"; - String t26 = "pakkage.Outer$22"; - String t32 = "pakkage/Outer$22[]"; - String t30 = "pakkage.Outer$22[]"; - String t31 = "[Lpakkage.Outer$22;"; - - String t34 = "org.plumelib.reflection.TestReflectionPlume$Inner.InnerInner"; - String t17 = "pakkage.Outer.Inner"; - String t18 = "pakkage.Outer.Inner[]"; - String t19 = "pakkage.Outer$Inner"; - String t21 = "pakkage.Outer$Inner[]"; - String t20 = "Lpakkage.Outer$Inner;"; - String t10 = "[Lpakkage.Outer$Inner;"; - String t4 = "Lpakkage/Outer$Inner;"; - String t11 = "[Lpakkage/Outer$Inner;"; - - String us; // @SignatureUnknown - @FullyQualifiedName String fqn; - @ClassGetName String cgn; - @FieldDescriptor String fd; - @InternalForm String iform; - @ClassGetSimpleName String sn; - @FqBinaryName String fbn; - @BinaryName String bn; - // not public, so a user can't write it. - // @SignatureBottom String sb; - - us = s1; - fqn = s1; - cgn = s1; - // :: error: (assignment.type.incompatible) - fd = s1; - iform = s1; - sn = s1; - bn = s1; - fbn = s1; - - us = s2; - fqn = s2; - cgn = s2; - // :: error: (assignment.type.incompatible) - fd = s2; - // :: error: (assignment.type.incompatible) - iform = s2; - // :: error: (assignment.type.incompatible) - sn = s2; - bn = s2; - fbn = s2; - - us = s3; - fqn = s3; - cgn = s3; - // :: error: (assignment.type.incompatible) - fd = s3; - // :: error: (assignment.type.incompatible) - iform = s3; - // :: error: (assignment.type.incompatible) - sn = s3; - bn = s3; - fbn = s3; - - us = s4; - fqn = s4; - cgn = s4; - fd = s4; - iform = s4; - sn = s4; - bn = s4; - fbn = s4; - - us = s5; - // :: error: (assignment.type.incompatible) - fqn = s5; - cgn = s5; - fd = s5; - // :: error: (assignment.type.incompatible) - iform = s5; - // :: error: (assignment.type.incompatible) - sn = s5; - // :: error: (assignment.type.incompatible) - bn = s5; - // :: error: (assignment.type.incompatible) - fbn = s5; - - us = s6; - // :: error: (assignment.type.incompatible) - fqn = s6; - // :: error: (assignment.type.incompatible) - cgn = s6; - fd = s6; - // :: error: (assignment.type.incompatible) - iform = s6; - // :: error: (assignment.type.incompatible) - sn = s6; - // :: error: (assignment.type.incompatible) - bn = s6; - // :: error: (assignment.type.incompatible) - fbn = s6; - - us = s7; - // :: error: (assignment.type.incompatible) - fqn = s7; - // :: error: (assignment.type.incompatible) - cgn = s7; - // :: error: (assignment.type.incompatible) - fd = s7; - iform = s7; - // :: error: (assignment.type.incompatible) - sn = s7; - // :: error: (assignment.type.incompatible) - bn = s7; - // :: error: (assignment.type.incompatible) - fbn = s7; - - us = s8; - // :: error: (assignment.type.incompatible) - fqn = s8; - // :: error: (assignment.type.incompatible) - cgn = s8; - // :: error: (assignment.type.incompatible) - fd = s8; - // :: error: (assignment.type.incompatible) - iform = s8; - // :: error: (assignment.type.incompatible) - sn = s8; - // :: error: (assignment.type.incompatible) - bn = s8; - // :: error: (assignment.type.incompatible) - fbn = s8; - - us = s9; - fqn = s9; - cgn = s9; - // :: error: (assignment.type.incompatible) - fd = s9; - // :: error: (assignment.type.incompatible) - iform = s9; - // :: error: (assignment.type.incompatible) - sn = s9; - bn = s9; - fbn = s9; - - us = s10; - fqn = s10; - cgn = s10; - // :: error: (assignment.type.incompatible) - fd = s10; - iform = s10; - sn = s10; - bn = s10; - fbn = s10; - - us = s11; - fqn = s11; - cgn = s11; - fd = s11; - iform = s11; - sn = s11; - bn = s11; - fbn = s11; - - us = s12; - fqn = s12; - // :: error: (assignment.type.incompatible) - cgn = s12; - // :: error: (assignment.type.incompatible) - fd = s12; - // :: error: (assignment.type.incompatible) - iform = s12; - // :: error: (assignment.type.incompatible) - sn = s12; - // :: error: (assignment.type.incompatible) - bn = s12; - fbn = s12; - - us = s13; - fqn = s13; - // :: error: (assignment.type.incompatible) - cgn = s13; - // :: error: (assignment.type.incompatible) - fd = s13; - // :: error: (assignment.type.incompatible) - iform = s13; - // :: error: (assignment.type.incompatible) - sn = s13; - // :: error: (assignment.type.incompatible) - bn = s13; - fbn = s13; - - us = s14; - // :: error: (assignment.type.incompatible) - fqn = s14; - // :: error: (assignment.type.incompatible) - cgn = s14; - fd = s14; - // :: error: (assignment.type.incompatible) - iform = s14; - // :: error: (assignment.type.incompatible) - sn = s14; - // :: error: (assignment.type.incompatible) - bn = s14; - // :: error: (assignment.type.incompatible) - fbn = s14; - - us = s15; - // :: error: (assignment.type.incompatible) - fqn = s15; - // :: error: (assignment.type.incompatible) - cgn = s15; - // :: error: (assignment.type.incompatible) - fd = s15; - // :: error: (assignment.type.incompatible) - iform = s15; - sn = s15; - // :: error: (assignment.type.incompatible) - bn = s15; - // :: error: (assignment.type.incompatible) - fbn = s15; - - us = s16; - // :: error: (assignment.type.incompatible) - fqn = s16; - // :: error: (assignment.type.incompatible) - cgn = s16; - // :: error: (assignment.type.incompatible) - fd = s16; - // :: error: (assignment.type.incompatible) - iform = s16; - sn = s16; - // :: error: (assignment.type.incompatible) - bn = s16; - // :: error: (assignment.type.incompatible) - fbn = s16; - - us = s17; - // :: error: (assignment.type.incompatible) - fqn = s17; - // :: error: (assignment.type.incompatible) - cgn = s17; - // :: error: (assignment.type.incompatible) - fd = s17; - // :: error: (assignment.type.incompatible) - iform = s17; - sn = s17; - // :: error: (assignment.type.incompatible) - bn = s17; - // :: error: (assignment.type.incompatible) - fbn = s17; - - us = s18; - // :: error: (assignment.type.incompatible) - fqn = s18; - // :: error: (assignment.type.incompatible) - cgn = s18; - // :: error: (assignment.type.incompatible) - fd = s18; - // :: error: (assignment.type.incompatible) - iform = s18; - // :: error: (assignment.type.incompatible) - sn = s18; - // :: error: (assignment.type.incompatible) - bn = s18; - // :: error: (assignment.type.incompatible) - fbn = s18; - - us = s19; - // :: error: (assignment.type.incompatible) - fqn = s19; - // :: error: (assignment.type.incompatible) - cgn = s19; - // :: error: (assignment.type.incompatible) - fd = s19; - // :: error: (assignment.type.incompatible) - iform = s19; - // :: error: (assignment.type.incompatible) - sn = s19; - // :: error: (assignment.type.incompatible) - bn = s19; - // :: error: (assignment.type.incompatible) - fbn = s19; - - us = s20; - fqn = s20; - cgn = s20; - // :: error: (assignment.type.incompatible) - fd = s20; - // :: error: (assignment.type.incompatible) - iform = s20; - sn = s20; - // :: error: (assignment.type.incompatible) - bn = s20; - fbn = s20; - - us = s21; - // :: error: (assignment.type.incompatible) - fqn = s21; - // :: error: (assignment.type.incompatible) - cgn = s21; - // :: error: (assignment.type.incompatible) - fd = s21; - // :: error: (assignment.type.incompatible) - iform = s21; - // :: error: (assignment.type.incompatible) - sn = s21; - // :: error: (assignment.type.incompatible) - bn = s21; - // :: error: (assignment.type.incompatible) - fbn = s21; - - us = s22; - // :: error: (assignment.type.incompatible) - fqn = s22; - // :: error: (assignment.type.incompatible) - cgn = s22; - // :: error: (assignment.type.incompatible) - fd = s22; - // :: error: (assignment.type.incompatible) - iform = s22; - // :: error: (assignment.type.incompatible) - sn = s22; - // :: error: (assignment.type.incompatible) - bn = s22; - // :: error: (assignment.type.incompatible) - fbn = s22; - - // Examples from the manual start here - - us = t13; - fqn = t13; - cgn = t13; - // :: error: (assignment.type.incompatible) - fd = t13; - // :: error: (assignment.type.incompatible) - iform = t13; - sn = t13; - // :: error: (assignment.type.incompatible) - bn = t13; - fbn = t13; - - us = t14; - fqn = t14; - // :: error: (assignment.type.incompatible) - cgn = t14; - // :: error: (assignment.type.incompatible) - fd = t14; - // :: error: (assignment.type.incompatible) - iform = t14; - sn = t14; - // :: error: (assignment.type.incompatible) - bn = t14; // t14 is int[][] - - us = t1; - fqn = t1; - cgn = t1; - fd = t1; - iform = t1; - sn = t1; - bn = t1; - fbn = t1; - - us = t12; - // :: error: (assignment.type.incompatible) - fqn = t12; - cgn = t12; - fd = t12; - // :: error: (assignment.type.incompatible) - iform = t12; - // :: error: (assignment.type.incompatible) - sn = t12; - // :: error: (assignment.type.incompatible) - bn = t12; - // :: error: (assignment.type.incompatible) - fbn = t12; - - us = t5; - fqn = t5; - cgn = t5; - // :: error: (assignment.type.incompatible) - fd = t5; - iform = t5; - sn = t5; - bn = t5; - fbn = t5; - - us = t2; - // :: error: (assignment.type.incompatible) - fqn = t2; - // :: error: (assignment.type.incompatible) - cgn = t2; - fd = t2; - // :: error: (assignment.type.incompatible) - iform = t2; - // :: error: (assignment.type.incompatible) - sn = t2; - // :: error: (assignment.type.incompatible) - bn = t2; - // :: error: (assignment.type.incompatible) - fbn = t2; - - us = t6; - fqn = t6; - // :: error: (assignment.type.incompatible) - cgn = t6; - // :: error: (assignment.type.incompatible) - fd = t6; - // :: error: (assignment.type.incompatible) - iform = t6; - sn = t6; - // :: error: (assignment.type.incompatible) - bn = t6; - fbn = t6; - - us = t7; - // :: error: (assignment.type.incompatible) - fqn = t7; - cgn = t7; - fd = t7; - // :: error: (assignment.type.incompatible) - iform = t7; - // :: error: (assignment.type.incompatible) - sn = t7; - // :: error: (assignment.type.incompatible) - bn = t7; - // :: error: (assignment.type.incompatible) - fbn = t7; - - us = t29; - // :: error: (assignment.type.incompatible) - fqn = t29; - // :: error: (assignment.type.incompatible) - cgn = t29; - // :: error: (assignment.type.incompatible) - fd = t29; - // :: error: (assignment.type.incompatible) - iform = t29; - sn = t29; - // :: error: (assignment.type.incompatible) - bn = t29; - // :: error: (assignment.type.incompatible) - fbn = t29; - - us = t33; - // :: error: (assignment.type.incompatible) - fqn = t33; - // :: error: (assignment.type.incompatible) - cgn = t33; - // :: error: (assignment.type.incompatible) - fd = t33; - // :: error: (assignment.type.incompatible) - iform = t33; - sn = t33; - // :: error: (assignment.type.incompatible) - bn = t33; - // :: error: (assignment.type.incompatible) - fbn = t33; - - us = t15; - fqn = t15; - cgn = t15; - // :: error: (assignment.type.incompatible) - fd = t15; - // :: error: (assignment.type.incompatible) - iform = t15; - // :: error: (assignment.type.incompatible) - sn = t15; - bn = t15; - fbn = t15; - - us = t16; - fqn = t16; - // :: error: (assignment.type.incompatible) - cgn = t16; - // :: error: (assignment.type.incompatible) - fd = t16; - // :: error: (assignment.type.incompatible) - iform = t16; - // :: error: (assignment.type.incompatible) - sn = t16; - // :: error: (assignment.type.incompatible) - bn = t16; // t16 is java.lang.Integer[] - - us = t22; - // :: error: (assignment.type.incompatible) - fqn = t22; - // :: error: (assignment.type.incompatible) - cgn = t22; - // :: error: (assignment.type.incompatible) - fd = t22; - iform = t22; - // :: error: (assignment.type.incompatible) - sn = t22; - // :: error: (assignment.type.incompatible) - bn = t22; - // :: error: (assignment.type.incompatible) - fbn = t22; - - us = t23; - // :: error: (assignment.type.incompatible) - fqn = t23; - // :: error: (assignment.type.incompatible) - cgn = t23; - // :: error: (assignment.type.incompatible) - fd = t23; - // :: error: (assignment.type.incompatible) - iform = t23; // t23 is java/lang/Integer[] - // :: error: (assignment.type.incompatible) - sn = t23; - // :: error: (assignment.type.incompatible) - bn = t23; - // :: error: (assignment.type.incompatible) - fbn = t23; - - us = t3; - // :: error: (assignment.type.incompatible) - fqn = t3; - // :: error: (assignment.type.incompatible) - cgn = t3; - fd = t3; - // :: error: (assignment.type.incompatible) - iform = t3; - // :: error: (assignment.type.incompatible) - sn = t3; - // :: error: (assignment.type.incompatible) - bn = t3; - // :: error: (assignment.type.incompatible) - fbn = t3; - - us = t8; - // :: error: (assignment.type.incompatible) - fqn = t8; - cgn = t8; - // :: error: (assignment.type.incompatible) - fd = t8; - // :: error: (assignment.type.incompatible) - iform = t8; - // :: error: (assignment.type.incompatible) - sn = t8; - // :: error: (assignment.type.incompatible) - bn = t8; - // :: error: (assignment.type.incompatible) - fbn = t8; - - us = t9; - // :: error: (assignment.type.incompatible) - fqn = t9; - // :: error: (assignment.type.incompatible) - cgn = t9; - fd = t9; - // :: error: (assignment.type.incompatible) - iform = t9; - // :: error: (assignment.type.incompatible) - sn = t9; - // :: error: (assignment.type.incompatible) - bn = t9; - // :: error: (assignment.type.incompatible) - fbn = t9; - - us = t24; - // :: error: (assignment.type.incompatible) - fqn = t24; - // :: error: (assignment.type.incompatible) - cgn = t24; - // :: error: (assignment.type.incompatible) - fd = t24; - iform = t24; - // :: error: (assignment.type.incompatible) - sn = t24; - // :: error: (assignment.type.incompatible) - bn = t24; - // :: error: (assignment.type.incompatible) - fbn = t24; - - us = t25; - // :: error: (assignment.type.incompatible) - fqn = t25; - // :: error: (assignment.type.incompatible) - cgn = t25; - // :: error: (assignment.type.incompatible) - fd = t25; - // :: error: (assignment.type.incompatible) - iform = t25; // rhs is pakkage/Outer$Inner[] - // :: error: (assignment.type.incompatible) - sn = t25; - // :: error: (assignment.type.incompatible) - bn = t25; - // :: error: (assignment.type.incompatible) - fbn = t25; - - us = t28; - // :: error: (assignment.type.incompatible) - fqn = t28; - // :: error: (assignment.type.incompatible) - cgn = t28; - // :: error: (assignment.type.incompatible) - fd = t28; - iform = t28; - // :: error: (assignment.type.incompatible) - sn = t28; - // :: error: (assignment.type.incompatible) - bn = t28; - // :: error: (assignment.type.incompatible) - fbn = t28; - - us = t27; - // :: error: (assignment.type.incompatible) - fqn = t27; - // :: error: (assignment.type.incompatible) - cgn = t27; - fd = t27; - // :: error: (assignment.type.incompatible) - iform = t27; - // :: error: (assignment.type.incompatible) - sn = t27; - // :: error: (assignment.type.incompatible) - bn = t27; - // :: error: (assignment.type.incompatible) - fbn = t27; - - us = t26; - fqn = t26; - cgn = t26; - // :: error: (assignment.type.incompatible) - fd = t26; - // :: error: (assignment.type.incompatible) - iform = t26; - // :: error: (assignment.type.incompatible) - sn = t26; - bn = t26; - fbn = t26; - - us = t32; - // :: error: (assignment.type.incompatible) - fqn = t32; - // :: error: (assignment.type.incompatible) - cgn = t32; - // :: error: (assignment.type.incompatible) - fd = t32; - // :: error: (assignment.type.incompatible) - iform = t32; // t32 is array - // :: error: (assignment.type.incompatible) - sn = t32; - // :: error: (assignment.type.incompatible) - bn = t32; - // :: error: (assignment.type.incompatible) - fbn = t32; - - us = t30; - fqn = t30; - // :: error: (assignment.type.incompatible) - cgn = t30; - // :: error: (assignment.type.incompatible) - fd = t30; - // :: error: (assignment.type.incompatible) - iform = t30; - // :: error: (assignment.type.incompatible) - sn = t30; - // :: error: (assignment.type.incompatible) - bn = t30; // rhs is array - - us = t31; - // :: error: (assignment.type.incompatible) - fqn = t31; - cgn = t31; - // :: error: (assignment.type.incompatible) - fd = t31; - // :: error: (assignment.type.incompatible) - iform = t31; - // :: error: (assignment.type.incompatible) - sn = t31; - // :: error: (assignment.type.incompatible) - bn = t31; - // :: error: (assignment.type.incompatible) - fbn = t31; - - us = t34; - fqn = t34; - cgn = t34; - // :: error: (assignment.type.incompatible) - fd = t34; - // :: error: (assignment.type.incompatible) - iform = t34; - // :: error: (assignment.type.incompatible) - sn = t34; - bn = t34; - fbn = t34; - - us = t17; - fqn = t17; - cgn = t17; - // :: error: (assignment.type.incompatible) - fd = t17; - // :: error: (assignment.type.incompatible) - iform = t17; - // :: error: (assignment.type.incompatible) - sn = t17; - bn = t17; - fbn = t17; - - us = t18; - fqn = t18; - // :: error: (assignment.type.incompatible) - cgn = t18; - // :: error: (assignment.type.incompatible) - fd = t18; - // :: error: (assignment.type.incompatible) - iform = t18; - // :: error: (assignment.type.incompatible) - sn = t18; - // :: error: (assignment.type.incompatible) - bn = t18; // t18 is pakkage.Outer.Inner[] - - us = t19; - fqn = t19; - cgn = t19; - // :: error: (assignment.type.incompatible) - fd = t19; - // :: error: (assignment.type.incompatible) - iform = t19; - // :: error: (assignment.type.incompatible) - sn = t19; - bn = t19; - fbn = t19; - - us = t21; - fqn = t21; - // :: error: (assignment.type.incompatible) - cgn = t21; - // :: error: (assignment.type.incompatible) - fd = t21; - // :: error: (assignment.type.incompatible) - iform = t21; - // :: error: (assignment.type.incompatible) - sn = t21; - // :: error: (assignment.type.incompatible) - bn = t21; // t21 is pakkage.Outer$Inner[] - - us = t20; - // :: error: (assignment.type.incompatible) - fqn = t20; - // :: error: (assignment.type.incompatible) - cgn = t20; - // :: error: (assignment.type.incompatible) - fd = t20; - // :: error: (assignment.type.incompatible) - iform = t20; - // :: error: (assignment.type.incompatible) - sn = t20; - // :: error: (assignment.type.incompatible) - bn = t20; - // :: error: (assignment.type.incompatible) - fbn = t20; - - us = t10; - // :: error: (assignment.type.incompatible) - fqn = t10; - cgn = t10; - // :: error: (assignment.type.incompatible) - fd = t10; - // :: error: (assignment.type.incompatible) - iform = t10; - // :: error: (assignment.type.incompatible) - sn = t10; - // :: error: (assignment.type.incompatible) - bn = t10; - // :: error: (assignment.type.incompatible) - fbn = t10; - - us = t4; - // :: error: (assignment.type.incompatible) - fqn = t4; - // :: error: (assignment.type.incompatible) - cgn = t4; - fd = t4; - // :: error: (assignment.type.incompatible) - iform = t4; - // :: error: (assignment.type.incompatible) - sn = t4; - // :: error: (assignment.type.incompatible) - bn = t4; - // :: error: (assignment.type.incompatible) - fbn = t4; - - us = t11; - // :: error: (assignment.type.incompatible) - fqn = t11; - // :: error: (assignment.type.incompatible) - cgn = t11; - fd = t11; - // :: error: (assignment.type.incompatible) - iform = t11; - // :: error: (assignment.type.incompatible) - sn = t11; - // :: error: (assignment.type.incompatible) - bn = t11; - // :: error: (assignment.type.incompatible) - fbn = t11; - } + // The hierarchy of type representations contains: + // + // SignatureUnknown.class, + // + // FullyQualifiedName.class, + // ClassGetName.class, + // FieldDescriptor.class, + // InternalForm.class, + // ClassGetSimpleName.class, + // FqBinaryName.class, + // + // BinaryName.class, + // FieldDescriptorWithoutPackage.class, + // + // ArrayWithoutPackage.class, + // DotSeparatedIdentifiers.class, + // BinaryNameWithoutPackage.class, + // + // Identifier.class, + // + // FieldDescriptorForPrimitive.class + // + // SignatureBottom.class + // + // There are also signature representations, which are not handled yet. + + void m() { + + String s1 = "a"; + String s2 = "a.b"; + String s3 = "a.b$c"; + String s4 = "B"; + String s5 = "[B"; + String s6 = "Ljava/lang/String;"; + String s7 = "Ljava/lang/String"; + // TODO: Should be @MethodDescriptor + String s8 = "foo()V"; + String s9 = "java.lang.annotation.Retention"; + String s10 = "dummy"; + String s11 = null; + String s12 = "a.b$c[][]"; + String s13 = "a.b.c[][]"; + String s14 = "[[Ljava/lang/String;"; + String s15 = ""; + String s16 = "[]"; + String s17 = "[][]"; + String s18 = "null"; + String s19 = "abstract"; + String s20 = "float"; + String s21 = "float "; + String s22 = " Foo"; + + // All the examples from the manual + String t13 = "int"; + String t14 = "int[][]"; + String t1 = "I"; + String t12 = "[[I"; + + String t5 = "MyClass"; + String t2 = "LMyClass;"; + String t6 = "MyClass[]"; + String t7 = "[LMyClass;"; + + String t29 = ""; + String t33 = "[]"; + + String t15 = "java.lang.Integer"; + String t16 = "java.lang.Integer[]"; + String t22 = "java/lang/Integer"; + String t23 = "java/lang/Integer[]"; + String t3 = "Ljava/lang/Integer;"; + String t8 = "[Ljava.lang.Integer;"; + String t9 = "[Ljava/lang/Integer;"; + + String t24 = "pakkage/Outer$Inner"; + String t25 = "pakkage/Outer$Inner[]"; + + String t28 = "pakkage/Outer$22"; + String t27 = "Lpakkage/Outer$22;"; + String t26 = "pakkage.Outer$22"; + String t32 = "pakkage/Outer$22[]"; + String t30 = "pakkage.Outer$22[]"; + String t31 = "[Lpakkage.Outer$22;"; + + String t34 = "org.plumelib.reflection.TestReflectionPlume$Inner.InnerInner"; + String t17 = "pakkage.Outer.Inner"; + String t18 = "pakkage.Outer.Inner[]"; + String t19 = "pakkage.Outer$Inner"; + String t21 = "pakkage.Outer$Inner[]"; + String t20 = "Lpakkage.Outer$Inner;"; + String t10 = "[Lpakkage.Outer$Inner;"; + String t4 = "Lpakkage/Outer$Inner;"; + String t11 = "[Lpakkage/Outer$Inner;"; + + String us; // @SignatureUnknown + @FullyQualifiedName String fqn; + @ClassGetName String cgn; + @FieldDescriptor String fd; + @InternalForm String iform; + @ClassGetSimpleName String sn; + @FqBinaryName String fbn; + @BinaryName String bn; + // not public, so a user can't write it. + // @SignatureBottom String sb; + + us = s1; + fqn = s1; + cgn = s1; + // :: error: (assignment.type.incompatible) + fd = s1; + iform = s1; + sn = s1; + bn = s1; + fbn = s1; + + us = s2; + fqn = s2; + cgn = s2; + // :: error: (assignment.type.incompatible) + fd = s2; + // :: error: (assignment.type.incompatible) + iform = s2; + // :: error: (assignment.type.incompatible) + sn = s2; + bn = s2; + fbn = s2; + + us = s3; + fqn = s3; + cgn = s3; + // :: error: (assignment.type.incompatible) + fd = s3; + // :: error: (assignment.type.incompatible) + iform = s3; + // :: error: (assignment.type.incompatible) + sn = s3; + bn = s3; + fbn = s3; + + us = s4; + fqn = s4; + cgn = s4; + fd = s4; + iform = s4; + sn = s4; + bn = s4; + fbn = s4; + + us = s5; + // :: error: (assignment.type.incompatible) + fqn = s5; + cgn = s5; + fd = s5; + // :: error: (assignment.type.incompatible) + iform = s5; + // :: error: (assignment.type.incompatible) + sn = s5; + // :: error: (assignment.type.incompatible) + bn = s5; + // :: error: (assignment.type.incompatible) + fbn = s5; + + us = s6; + // :: error: (assignment.type.incompatible) + fqn = s6; + // :: error: (assignment.type.incompatible) + cgn = s6; + fd = s6; + // :: error: (assignment.type.incompatible) + iform = s6; + // :: error: (assignment.type.incompatible) + sn = s6; + // :: error: (assignment.type.incompatible) + bn = s6; + // :: error: (assignment.type.incompatible) + fbn = s6; + + us = s7; + // :: error: (assignment.type.incompatible) + fqn = s7; + // :: error: (assignment.type.incompatible) + cgn = s7; + // :: error: (assignment.type.incompatible) + fd = s7; + iform = s7; + // :: error: (assignment.type.incompatible) + sn = s7; + // :: error: (assignment.type.incompatible) + bn = s7; + // :: error: (assignment.type.incompatible) + fbn = s7; + + us = s8; + // :: error: (assignment.type.incompatible) + fqn = s8; + // :: error: (assignment.type.incompatible) + cgn = s8; + // :: error: (assignment.type.incompatible) + fd = s8; + // :: error: (assignment.type.incompatible) + iform = s8; + // :: error: (assignment.type.incompatible) + sn = s8; + // :: error: (assignment.type.incompatible) + bn = s8; + // :: error: (assignment.type.incompatible) + fbn = s8; + + us = s9; + fqn = s9; + cgn = s9; + // :: error: (assignment.type.incompatible) + fd = s9; + // :: error: (assignment.type.incompatible) + iform = s9; + // :: error: (assignment.type.incompatible) + sn = s9; + bn = s9; + fbn = s9; + + us = s10; + fqn = s10; + cgn = s10; + // :: error: (assignment.type.incompatible) + fd = s10; + iform = s10; + sn = s10; + bn = s10; + fbn = s10; + + us = s11; + fqn = s11; + cgn = s11; + fd = s11; + iform = s11; + sn = s11; + bn = s11; + fbn = s11; + + us = s12; + fqn = s12; + // :: error: (assignment.type.incompatible) + cgn = s12; + // :: error: (assignment.type.incompatible) + fd = s12; + // :: error: (assignment.type.incompatible) + iform = s12; + // :: error: (assignment.type.incompatible) + sn = s12; + // :: error: (assignment.type.incompatible) + bn = s12; + fbn = s12; + + us = s13; + fqn = s13; + // :: error: (assignment.type.incompatible) + cgn = s13; + // :: error: (assignment.type.incompatible) + fd = s13; + // :: error: (assignment.type.incompatible) + iform = s13; + // :: error: (assignment.type.incompatible) + sn = s13; + // :: error: (assignment.type.incompatible) + bn = s13; + fbn = s13; + + us = s14; + // :: error: (assignment.type.incompatible) + fqn = s14; + // :: error: (assignment.type.incompatible) + cgn = s14; + fd = s14; + // :: error: (assignment.type.incompatible) + iform = s14; + // :: error: (assignment.type.incompatible) + sn = s14; + // :: error: (assignment.type.incompatible) + bn = s14; + // :: error: (assignment.type.incompatible) + fbn = s14; + + us = s15; + // :: error: (assignment.type.incompatible) + fqn = s15; + // :: error: (assignment.type.incompatible) + cgn = s15; + // :: error: (assignment.type.incompatible) + fd = s15; + // :: error: (assignment.type.incompatible) + iform = s15; + sn = s15; + // :: error: (assignment.type.incompatible) + bn = s15; + // :: error: (assignment.type.incompatible) + fbn = s15; + + us = s16; + // :: error: (assignment.type.incompatible) + fqn = s16; + // :: error: (assignment.type.incompatible) + cgn = s16; + // :: error: (assignment.type.incompatible) + fd = s16; + // :: error: (assignment.type.incompatible) + iform = s16; + sn = s16; + // :: error: (assignment.type.incompatible) + bn = s16; + // :: error: (assignment.type.incompatible) + fbn = s16; + + us = s17; + // :: error: (assignment.type.incompatible) + fqn = s17; + // :: error: (assignment.type.incompatible) + cgn = s17; + // :: error: (assignment.type.incompatible) + fd = s17; + // :: error: (assignment.type.incompatible) + iform = s17; + sn = s17; + // :: error: (assignment.type.incompatible) + bn = s17; + // :: error: (assignment.type.incompatible) + fbn = s17; + + us = s18; + // :: error: (assignment.type.incompatible) + fqn = s18; + // :: error: (assignment.type.incompatible) + cgn = s18; + // :: error: (assignment.type.incompatible) + fd = s18; + // :: error: (assignment.type.incompatible) + iform = s18; + // :: error: (assignment.type.incompatible) + sn = s18; + // :: error: (assignment.type.incompatible) + bn = s18; + // :: error: (assignment.type.incompatible) + fbn = s18; + + us = s19; + // :: error: (assignment.type.incompatible) + fqn = s19; + // :: error: (assignment.type.incompatible) + cgn = s19; + // :: error: (assignment.type.incompatible) + fd = s19; + // :: error: (assignment.type.incompatible) + iform = s19; + // :: error: (assignment.type.incompatible) + sn = s19; + // :: error: (assignment.type.incompatible) + bn = s19; + // :: error: (assignment.type.incompatible) + fbn = s19; + + us = s20; + fqn = s20; + cgn = s20; + // :: error: (assignment.type.incompatible) + fd = s20; + // :: error: (assignment.type.incompatible) + iform = s20; + sn = s20; + // :: error: (assignment.type.incompatible) + bn = s20; + fbn = s20; + + us = s21; + // :: error: (assignment.type.incompatible) + fqn = s21; + // :: error: (assignment.type.incompatible) + cgn = s21; + // :: error: (assignment.type.incompatible) + fd = s21; + // :: error: (assignment.type.incompatible) + iform = s21; + // :: error: (assignment.type.incompatible) + sn = s21; + // :: error: (assignment.type.incompatible) + bn = s21; + // :: error: (assignment.type.incompatible) + fbn = s21; + + us = s22; + // :: error: (assignment.type.incompatible) + fqn = s22; + // :: error: (assignment.type.incompatible) + cgn = s22; + // :: error: (assignment.type.incompatible) + fd = s22; + // :: error: (assignment.type.incompatible) + iform = s22; + // :: error: (assignment.type.incompatible) + sn = s22; + // :: error: (assignment.type.incompatible) + bn = s22; + // :: error: (assignment.type.incompatible) + fbn = s22; + + // Examples from the manual start here + + us = t13; + fqn = t13; + cgn = t13; + // :: error: (assignment.type.incompatible) + fd = t13; + // :: error: (assignment.type.incompatible) + iform = t13; + sn = t13; + // :: error: (assignment.type.incompatible) + bn = t13; + fbn = t13; + + us = t14; + fqn = t14; + // :: error: (assignment.type.incompatible) + cgn = t14; + // :: error: (assignment.type.incompatible) + fd = t14; + // :: error: (assignment.type.incompatible) + iform = t14; + sn = t14; + // :: error: (assignment.type.incompatible) + bn = t14; // t14 is int[][] + + us = t1; + fqn = t1; + cgn = t1; + fd = t1; + iform = t1; + sn = t1; + bn = t1; + fbn = t1; + + us = t12; + // :: error: (assignment.type.incompatible) + fqn = t12; + cgn = t12; + fd = t12; + // :: error: (assignment.type.incompatible) + iform = t12; + // :: error: (assignment.type.incompatible) + sn = t12; + // :: error: (assignment.type.incompatible) + bn = t12; + // :: error: (assignment.type.incompatible) + fbn = t12; + + us = t5; + fqn = t5; + cgn = t5; + // :: error: (assignment.type.incompatible) + fd = t5; + iform = t5; + sn = t5; + bn = t5; + fbn = t5; + + us = t2; + // :: error: (assignment.type.incompatible) + fqn = t2; + // :: error: (assignment.type.incompatible) + cgn = t2; + fd = t2; + // :: error: (assignment.type.incompatible) + iform = t2; + // :: error: (assignment.type.incompatible) + sn = t2; + // :: error: (assignment.type.incompatible) + bn = t2; + // :: error: (assignment.type.incompatible) + fbn = t2; + + us = t6; + fqn = t6; + // :: error: (assignment.type.incompatible) + cgn = t6; + // :: error: (assignment.type.incompatible) + fd = t6; + // :: error: (assignment.type.incompatible) + iform = t6; + sn = t6; + // :: error: (assignment.type.incompatible) + bn = t6; + fbn = t6; + + us = t7; + // :: error: (assignment.type.incompatible) + fqn = t7; + cgn = t7; + fd = t7; + // :: error: (assignment.type.incompatible) + iform = t7; + // :: error: (assignment.type.incompatible) + sn = t7; + // :: error: (assignment.type.incompatible) + bn = t7; + // :: error: (assignment.type.incompatible) + fbn = t7; + + us = t29; + // :: error: (assignment.type.incompatible) + fqn = t29; + // :: error: (assignment.type.incompatible) + cgn = t29; + // :: error: (assignment.type.incompatible) + fd = t29; + // :: error: (assignment.type.incompatible) + iform = t29; + sn = t29; + // :: error: (assignment.type.incompatible) + bn = t29; + // :: error: (assignment.type.incompatible) + fbn = t29; + + us = t33; + // :: error: (assignment.type.incompatible) + fqn = t33; + // :: error: (assignment.type.incompatible) + cgn = t33; + // :: error: (assignment.type.incompatible) + fd = t33; + // :: error: (assignment.type.incompatible) + iform = t33; + sn = t33; + // :: error: (assignment.type.incompatible) + bn = t33; + // :: error: (assignment.type.incompatible) + fbn = t33; + + us = t15; + fqn = t15; + cgn = t15; + // :: error: (assignment.type.incompatible) + fd = t15; + // :: error: (assignment.type.incompatible) + iform = t15; + // :: error: (assignment.type.incompatible) + sn = t15; + bn = t15; + fbn = t15; + + us = t16; + fqn = t16; + // :: error: (assignment.type.incompatible) + cgn = t16; + // :: error: (assignment.type.incompatible) + fd = t16; + // :: error: (assignment.type.incompatible) + iform = t16; + // :: error: (assignment.type.incompatible) + sn = t16; + // :: error: (assignment.type.incompatible) + bn = t16; // t16 is java.lang.Integer[] + + us = t22; + // :: error: (assignment.type.incompatible) + fqn = t22; + // :: error: (assignment.type.incompatible) + cgn = t22; + // :: error: (assignment.type.incompatible) + fd = t22; + iform = t22; + // :: error: (assignment.type.incompatible) + sn = t22; + // :: error: (assignment.type.incompatible) + bn = t22; + // :: error: (assignment.type.incompatible) + fbn = t22; + + us = t23; + // :: error: (assignment.type.incompatible) + fqn = t23; + // :: error: (assignment.type.incompatible) + cgn = t23; + // :: error: (assignment.type.incompatible) + fd = t23; + // :: error: (assignment.type.incompatible) + iform = t23; // t23 is java/lang/Integer[] + // :: error: (assignment.type.incompatible) + sn = t23; + // :: error: (assignment.type.incompatible) + bn = t23; + // :: error: (assignment.type.incompatible) + fbn = t23; + + us = t3; + // :: error: (assignment.type.incompatible) + fqn = t3; + // :: error: (assignment.type.incompatible) + cgn = t3; + fd = t3; + // :: error: (assignment.type.incompatible) + iform = t3; + // :: error: (assignment.type.incompatible) + sn = t3; + // :: error: (assignment.type.incompatible) + bn = t3; + // :: error: (assignment.type.incompatible) + fbn = t3; + + us = t8; + // :: error: (assignment.type.incompatible) + fqn = t8; + cgn = t8; + // :: error: (assignment.type.incompatible) + fd = t8; + // :: error: (assignment.type.incompatible) + iform = t8; + // :: error: (assignment.type.incompatible) + sn = t8; + // :: error: (assignment.type.incompatible) + bn = t8; + // :: error: (assignment.type.incompatible) + fbn = t8; + + us = t9; + // :: error: (assignment.type.incompatible) + fqn = t9; + // :: error: (assignment.type.incompatible) + cgn = t9; + fd = t9; + // :: error: (assignment.type.incompatible) + iform = t9; + // :: error: (assignment.type.incompatible) + sn = t9; + // :: error: (assignment.type.incompatible) + bn = t9; + // :: error: (assignment.type.incompatible) + fbn = t9; + + us = t24; + // :: error: (assignment.type.incompatible) + fqn = t24; + // :: error: (assignment.type.incompatible) + cgn = t24; + // :: error: (assignment.type.incompatible) + fd = t24; + iform = t24; + // :: error: (assignment.type.incompatible) + sn = t24; + // :: error: (assignment.type.incompatible) + bn = t24; + // :: error: (assignment.type.incompatible) + fbn = t24; + + us = t25; + // :: error: (assignment.type.incompatible) + fqn = t25; + // :: error: (assignment.type.incompatible) + cgn = t25; + // :: error: (assignment.type.incompatible) + fd = t25; + // :: error: (assignment.type.incompatible) + iform = t25; // rhs is pakkage/Outer$Inner[] + // :: error: (assignment.type.incompatible) + sn = t25; + // :: error: (assignment.type.incompatible) + bn = t25; + // :: error: (assignment.type.incompatible) + fbn = t25; + + us = t28; + // :: error: (assignment.type.incompatible) + fqn = t28; + // :: error: (assignment.type.incompatible) + cgn = t28; + // :: error: (assignment.type.incompatible) + fd = t28; + iform = t28; + // :: error: (assignment.type.incompatible) + sn = t28; + // :: error: (assignment.type.incompatible) + bn = t28; + // :: error: (assignment.type.incompatible) + fbn = t28; + + us = t27; + // :: error: (assignment.type.incompatible) + fqn = t27; + // :: error: (assignment.type.incompatible) + cgn = t27; + fd = t27; + // :: error: (assignment.type.incompatible) + iform = t27; + // :: error: (assignment.type.incompatible) + sn = t27; + // :: error: (assignment.type.incompatible) + bn = t27; + // :: error: (assignment.type.incompatible) + fbn = t27; + + us = t26; + fqn = t26; + cgn = t26; + // :: error: (assignment.type.incompatible) + fd = t26; + // :: error: (assignment.type.incompatible) + iform = t26; + // :: error: (assignment.type.incompatible) + sn = t26; + bn = t26; + fbn = t26; + + us = t32; + // :: error: (assignment.type.incompatible) + fqn = t32; + // :: error: (assignment.type.incompatible) + cgn = t32; + // :: error: (assignment.type.incompatible) + fd = t32; + // :: error: (assignment.type.incompatible) + iform = t32; // t32 is array + // :: error: (assignment.type.incompatible) + sn = t32; + // :: error: (assignment.type.incompatible) + bn = t32; + // :: error: (assignment.type.incompatible) + fbn = t32; + + us = t30; + fqn = t30; + // :: error: (assignment.type.incompatible) + cgn = t30; + // :: error: (assignment.type.incompatible) + fd = t30; + // :: error: (assignment.type.incompatible) + iform = t30; + // :: error: (assignment.type.incompatible) + sn = t30; + // :: error: (assignment.type.incompatible) + bn = t30; // rhs is array + + us = t31; + // :: error: (assignment.type.incompatible) + fqn = t31; + cgn = t31; + // :: error: (assignment.type.incompatible) + fd = t31; + // :: error: (assignment.type.incompatible) + iform = t31; + // :: error: (assignment.type.incompatible) + sn = t31; + // :: error: (assignment.type.incompatible) + bn = t31; + // :: error: (assignment.type.incompatible) + fbn = t31; + + us = t34; + fqn = t34; + cgn = t34; + // :: error: (assignment.type.incompatible) + fd = t34; + // :: error: (assignment.type.incompatible) + iform = t34; + // :: error: (assignment.type.incompatible) + sn = t34; + bn = t34; + fbn = t34; + + us = t17; + fqn = t17; + cgn = t17; + // :: error: (assignment.type.incompatible) + fd = t17; + // :: error: (assignment.type.incompatible) + iform = t17; + // :: error: (assignment.type.incompatible) + sn = t17; + bn = t17; + fbn = t17; + + us = t18; + fqn = t18; + // :: error: (assignment.type.incompatible) + cgn = t18; + // :: error: (assignment.type.incompatible) + fd = t18; + // :: error: (assignment.type.incompatible) + iform = t18; + // :: error: (assignment.type.incompatible) + sn = t18; + // :: error: (assignment.type.incompatible) + bn = t18; // t18 is pakkage.Outer.Inner[] + + us = t19; + fqn = t19; + cgn = t19; + // :: error: (assignment.type.incompatible) + fd = t19; + // :: error: (assignment.type.incompatible) + iform = t19; + // :: error: (assignment.type.incompatible) + sn = t19; + bn = t19; + fbn = t19; + + us = t21; + fqn = t21; + // :: error: (assignment.type.incompatible) + cgn = t21; + // :: error: (assignment.type.incompatible) + fd = t21; + // :: error: (assignment.type.incompatible) + iform = t21; + // :: error: (assignment.type.incompatible) + sn = t21; + // :: error: (assignment.type.incompatible) + bn = t21; // t21 is pakkage.Outer$Inner[] + + us = t20; + // :: error: (assignment.type.incompatible) + fqn = t20; + // :: error: (assignment.type.incompatible) + cgn = t20; + // :: error: (assignment.type.incompatible) + fd = t20; + // :: error: (assignment.type.incompatible) + iform = t20; + // :: error: (assignment.type.incompatible) + sn = t20; + // :: error: (assignment.type.incompatible) + bn = t20; + // :: error: (assignment.type.incompatible) + fbn = t20; + + us = t10; + // :: error: (assignment.type.incompatible) + fqn = t10; + cgn = t10; + // :: error: (assignment.type.incompatible) + fd = t10; + // :: error: (assignment.type.incompatible) + iform = t10; + // :: error: (assignment.type.incompatible) + sn = t10; + // :: error: (assignment.type.incompatible) + bn = t10; + // :: error: (assignment.type.incompatible) + fbn = t10; + + us = t4; + // :: error: (assignment.type.incompatible) + fqn = t4; + // :: error: (assignment.type.incompatible) + cgn = t4; + fd = t4; + // :: error: (assignment.type.incompatible) + iform = t4; + // :: error: (assignment.type.incompatible) + sn = t4; + // :: error: (assignment.type.incompatible) + bn = t4; + // :: error: (assignment.type.incompatible) + fbn = t4; + + us = t11; + // :: error: (assignment.type.incompatible) + fqn = t11; + // :: error: (assignment.type.incompatible) + cgn = t11; + fd = t11; + // :: error: (assignment.type.incompatible) + iform = t11; + // :: error: (assignment.type.incompatible) + sn = t11; + // :: error: (assignment.type.incompatible) + bn = t11; + // :: error: (assignment.type.incompatible) + fbn = t11; + } } diff --git a/checker/tests/signature/StubLibraryTest.java b/checker/tests/signature/StubLibraryTest.java index 18a0716ac33..2d3439ac9c5 100644 --- a/checker/tests/signature/StubLibraryTest.java +++ b/checker/tests/signature/StubLibraryTest.java @@ -5,13 +5,13 @@ public class StubLibraryTest { - void testJdk() { - @ClassGetName String s3 = String.class.getName(); - } + void testJdk() { + @ClassGetName String s3 = String.class.getName(); + } - // void testBcel(ClassGen cg) { - // @ClassGetName String cgn = cg.getClassName(); - // @BinaryName String bn = cg.getClassName(); - // } + // void testBcel(ClassGen cg) { + // @ClassGetName String cgn = cg.getClassName(); + // @BinaryName String bn = cg.getClassName(); + // } } diff --git a/checker/tests/signedness-initialized-fields/SignednessFields.java b/checker/tests/signedness-initialized-fields/SignednessFields.java index a21fc5791f0..4113c0b3d9d 100644 --- a/checker/tests/signedness-initialized-fields/SignednessFields.java +++ b/checker/tests/signedness-initialized-fields/SignednessFields.java @@ -2,9 +2,9 @@ public class SignednessFields { - int intField; - @Signed int signedField; - double doubleField; - Object o; - boolean b; + int intField; + @Signed int signedField; + double doubleField; + Object o; + boolean b; } diff --git a/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java b/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java index 4c5168fe8fc..4d1dcba7dca 100644 --- a/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java +++ b/checker/tests/signedness-unchecked-defaults/TestUncheckedByteCode.java @@ -2,20 +2,20 @@ import org.checkerframework.framework.testchecker.lib.UncheckedByteCode; public class TestUncheckedByteCode { - @UnknownSignedness Object field; + @UnknownSignedness Object field; - void test(UncheckedByteCode param, Integer i) { - field = param.getCT(); - // :: error: (argument.type.incompatible) - field = param.getInt(1); - // Signedness Checker doesn't default boxed primitives correctly. - // https://github.com/typetools/checker-framework/issues/797 - // :: error: (argument.type.incompatible) - field = param.getInteger(i); - // :: error: (argument.type.incompatible) - field = param.getObject(new Object()); - // :: error: (argument.type.incompatible) - field = param.getString("hello"); - field = param.identity("hello"); - } + void test(UncheckedByteCode param, Integer i) { + field = param.getCT(); + // :: error: (argument.type.incompatible) + field = param.getInt(1); + // Signedness Checker doesn't default boxed primitives correctly. + // https://github.com/typetools/checker-framework/issues/797 + // :: error: (argument.type.incompatible) + field = param.getInteger(i); + // :: error: (argument.type.incompatible) + field = param.getObject(new Object()); + // :: error: (argument.type.incompatible) + field = param.getString("hello"); + field = param.identity("hello"); + } } diff --git a/checker/tests/signedness/AdditionWithChar.java b/checker/tests/signedness/AdditionWithChar.java index 8066d42ec34..92bcd8b3eba 100644 --- a/checker/tests/signedness/AdditionWithChar.java +++ b/checker/tests/signedness/AdditionWithChar.java @@ -1,7 +1,7 @@ public class AdditionWithChar { - int i2; + int i2; - void additionWithChar(int i1, char c) { - i2 = i1 + c; - } + void additionWithChar(int i1, char c) { + i2 = i1 + c; + } } diff --git a/checker/tests/signedness/AnnoBeforeModifier.java b/checker/tests/signedness/AnnoBeforeModifier.java index 2e4177fe605..26e74f47f62 100644 --- a/checker/tests/signedness/AnnoBeforeModifier.java +++ b/checker/tests/signedness/AnnoBeforeModifier.java @@ -3,98 +3,98 @@ public class AnnoBeforeModifier { - // :: warning: (type.anno.before.modifier) - @Unsigned public int i = 0; + // :: warning: (type.anno.before.modifier) + @Unsigned public int i = 0; - public @Unsigned int j = 0; + public @Unsigned int j = 0; - // :: warning: (type.anno.before.modifier) - public @Unsigned final int k = 0; + // :: warning: (type.anno.before.modifier) + public @Unsigned final int k = 0; - @SuppressWarnings("foobar") - @Unsigned public int l = 0; + @SuppressWarnings("foobar") + @Unsigned public int l = 0; - public @SuppressWarnings("foobar") @Unsigned int m = 0; + public @SuppressWarnings("foobar") @Unsigned int m = 0; - @SuppressWarnings("foobar") - @Unsigned public int n = 0; + @SuppressWarnings("foobar") + @Unsigned public int n = 0; - // TODO: :: warning: (type.anno.before.modifier) - public @SuppressWarnings("foobar") @Unsigned final int o = 0; + // TODO: :: warning: (type.anno.before.modifier) + public @SuppressWarnings("foobar") @Unsigned final int o = 0; - // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) - public @Unsigned @SuppressWarnings("foobar") final int p = 0; + // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) + public @Unsigned @SuppressWarnings("foobar") final int p = 0; - public @SuppressWarnings("foobar") final @Unsigned int q = 0; + public @SuppressWarnings("foobar") final @Unsigned int q = 0; - @SuppressWarnings("foobar") - public int r = 0; + @SuppressWarnings("foobar") + public int r = 0; - public @SuppressWarnings("foobar") int s = 0; + public @SuppressWarnings("foobar") int s = 0; - public @SuppressWarnings("foobar") final int t = 0; + public @SuppressWarnings("foobar") final int t = 0; - // :: warning: (type.anno.before.modifier) - @Unsigned public int iMethod() { - return 0; - } + // :: warning: (type.anno.before.modifier) + @Unsigned public int iMethod() { + return 0; + } - public @Unsigned int jMethod() { - return 0; - } + public @Unsigned int jMethod() { + return 0; + } - // :: warning: (type.anno.before.modifier) - public @Unsigned final int kMethod() { - return 0; - } + // :: warning: (type.anno.before.modifier) + public @Unsigned final int kMethod() { + return 0; + } - @SuppressWarnings("foobar") - @Unsigned public int lMethod() { - return 0; - } + @SuppressWarnings("foobar") + @Unsigned public int lMethod() { + return 0; + } - public @SuppressWarnings("foobar") @Unsigned int mMethod() { - return 0; - } + public @SuppressWarnings("foobar") @Unsigned int mMethod() { + return 0; + } - @SuppressWarnings("foobar") - @Unsigned public int nMethod() { - return 0; - } + @SuppressWarnings("foobar") + @Unsigned public int nMethod() { + return 0; + } - // TODO: :: warning: (type.anno.before.modifier) - public @SuppressWarnings("foobar") @Unsigned final int oMethod() { - return 0; - } + // TODO: :: warning: (type.anno.before.modifier) + public @SuppressWarnings("foobar") @Unsigned final int oMethod() { + return 0; + } - // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) - public @Unsigned @SuppressWarnings("foobar") final int pMethod() { - return 0; - } + // :: warning: (type.anno.before.decl.anno) :: warning: (type.anno.before.modifier) + public @Unsigned @SuppressWarnings("foobar") final int pMethod() { + return 0; + } - public @SuppressWarnings("foobar") final @Unsigned int qMethod() { - return 0; - } + public @SuppressWarnings("foobar") final @Unsigned int qMethod() { + return 0; + } - @SuppressWarnings("foobar") - public int rMethod() { - return 0; - } + @SuppressWarnings("foobar") + public int rMethod() { + return 0; + } - public @SuppressWarnings("foobar") int sMethod() { - return 0; - } + public @SuppressWarnings("foobar") int sMethod() { + return 0; + } - public @SuppressWarnings("foobar") final int tMethod() { - return 0; - } + public @SuppressWarnings("foobar") final int tMethod() { + return 0; + } - // Use @NonNull rather than a signedness annotation to avoid errors. - @NonNull public enum MyEnum { - @NonNull CONSTANT - } + // Use @NonNull rather than a signedness annotation to avoid errors. + @NonNull public enum MyEnum { + @NonNull CONSTANT + } - interface MyInterface { - @NonNull String myMethod(); - } + interface MyInterface { + @NonNull String myMethod(); + } } diff --git a/checker/tests/signedness/Arrays.java b/checker/tests/signedness/Arrays.java index 3d839d06627..c60c1966c6e 100644 --- a/checker/tests/signedness/Arrays.java +++ b/checker/tests/signedness/Arrays.java @@ -1,5 +1,5 @@ public class Arrays { - void test() { - Object[] os = new Double[234]; - } + void test() { + Object[] os = new Double[234]; + } } diff --git a/checker/tests/signedness/BinaryOperations.java b/checker/tests/signedness/BinaryOperations.java index 06064a79bac..47bc19c3e65 100644 --- a/checker/tests/signedness/BinaryOperations.java +++ b/checker/tests/signedness/BinaryOperations.java @@ -2,161 +2,161 @@ public class BinaryOperations { - public void DivModTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void DivModTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @Unsigned int unsignedresult; - @UnknownSignedness int unknownresult; + @Unsigned int unsignedresult; + @UnknownSignedness int unknownresult; - // :: error: (operation.unsignedrhs) - unknownresult = unknown / unsigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown / unsigned; - // :: error: (operation.unsignedlhs) - unknownresult = unsigned / unknown; + // :: error: (operation.unsignedlhs) + unknownresult = unsigned / unknown; - // :: error: (operation.unsignedlhs) - unsignedresult = unsigned / constant; + // :: error: (operation.unsignedlhs) + unsignedresult = unsigned / constant; - // :: error: (operation.unsignedrhs) - unsignedresult = constant / unsigned; + // :: error: (operation.unsignedrhs) + unsignedresult = constant / unsigned; - // :: error: (operation.unsignedrhs) - unknownresult = unknown / polysigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown / polysigned; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned / unknown; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned / unknown; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned / constant; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned / constant; - // :: error: (operation.unsignedrhs) - unknownresult = constant / polysigned; + // :: error: (operation.unsignedrhs) + unknownresult = constant / polysigned; - // :: error: (operation.unsignedrhs) - unknownresult = unknown % unsigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown % unsigned; - // :: error: (operation.unsignedlhs) - unknownresult = unsigned % unknown; + // :: error: (operation.unsignedlhs) + unknownresult = unsigned % unknown; - // :: error: (operation.unsignedrhs) - unknownresult = unknown % polysigned; + // :: error: (operation.unsignedrhs) + unknownresult = unknown % polysigned; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned % unknown; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned % unknown; - // :: error: (operation.unsignedlhs) - unsignedresult = unsigned % constant; + // :: error: (operation.unsignedlhs) + unsignedresult = unsigned % constant; - // :: error: (operation.unsignedrhs) - unsignedresult = constant % unsigned; + // :: error: (operation.unsignedrhs) + unsignedresult = constant % unsigned; - // :: error: (operation.unsignedlhs) - unknownresult = polysigned % constant; + // :: error: (operation.unsignedlhs) + unknownresult = polysigned % constant; - // :: error: (operation.unsignedrhs) - unknownresult = constant % polysigned; - } + // :: error: (operation.unsignedrhs) + unknownresult = constant % polysigned; + } - public void SignedRightShiftTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void SignedRightShiftTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @Unsigned int unsignedresult; - @PolySigned int polysignedresult; - @UnknownSignedness int unknownresult; - int result; + @Unsigned int unsignedresult; + @PolySigned int polysignedresult; + @UnknownSignedness int unknownresult; + int result; - // :: error: (shift.signed) - unsignedresult = unsigned >> constant; + // :: error: (shift.signed) + unsignedresult = unsigned >> constant; - result = constant >> unsigned; + result = constant >> unsigned; - // :: error: (shift.signed) - polysignedresult = polysigned >> constant; + // :: error: (shift.signed) + polysignedresult = polysigned >> constant; - result = constant >> polysigned; + result = constant >> polysigned; - // :: error: (shift.signed) - unsignedresult = unsigned >> unknown; + // :: error: (shift.signed) + unsignedresult = unsigned >> unknown; - unknownresult = unknown >> unsigned; + unknownresult = unknown >> unsigned; - // :: error: (shift.signed) - polysignedresult = polysigned >> unknown; + // :: error: (shift.signed) + polysignedresult = polysigned >> unknown; - unknownresult = unknown >> polysigned; - } + unknownresult = unknown >> polysigned; + } - public void UnsignedRightShiftTest( - @Signed int signed, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void UnsignedRightShiftTest( + @Signed int signed, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @PolySigned int polysignedresult; - @UnknownSignedness int unknownresult; - int result; + @PolySigned int polysignedresult; + @UnknownSignedness int unknownresult; + int result; - // :: error: (shift.unsigned) - result = signed >>> constant; + // :: error: (shift.unsigned) + result = signed >>> constant; - result = constant >>> signed; + result = constant >>> signed; - // :: error: (shift.unsigned) - result = signed >>> unknown; + // :: error: (shift.unsigned) + result = signed >>> unknown; - unknownresult = unknown >>> signed; + unknownresult = unknown >>> signed; - // :: error: (shift.unsigned) - polysignedresult = polysigned >>> constant; + // :: error: (shift.unsigned) + polysignedresult = polysigned >>> constant; - result = constant >>> polysigned; + result = constant >>> polysigned; - // :: error: (shift.unsigned) - polysignedresult = polysigned >>> unknown; + // :: error: (shift.unsigned) + polysignedresult = polysigned >>> unknown; - unknownresult = unknown >>> polysigned; - } + unknownresult = unknown >>> polysigned; + } - public void LeftShiftTest( - @Signed int signed, - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void LeftShiftTest( + @Signed int signed, + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - @PolySigned int polysignedresult; - @UnknownSignedness int unknownresult; - @Unsigned int unsignedresult; - int result; + @PolySigned int polysignedresult; + @UnknownSignedness int unknownresult; + @Unsigned int unsignedresult; + int result; - result = signed << constant; + result = signed << constant; - result = constant << signed; + result = constant << signed; - result = signed << unknown; + result = signed << unknown; - unknownresult = unknown << signed; + unknownresult = unknown << signed; - unsignedresult = unsigned << constant; + unsignedresult = unsigned << constant; - result = constant << unsigned; + result = constant << unsigned; - unsignedresult = unsigned << unknown; + unsignedresult = unsigned << unknown; - unknownresult = unknown << unsigned; + unknownresult = unknown << unsigned; - polysignedresult = polysigned << constant; + polysignedresult = polysigned << constant; - result = constant << polysigned; + result = constant << polysigned; - polysignedresult = polysigned << unknown; + polysignedresult = polysigned << unknown; - unknownresult = unknown << polysigned; - } + unknownresult = unknown << polysigned; + } } diff --git a/checker/tests/signedness/BooleansTest.java b/checker/tests/signedness/BooleansTest.java index 16815a6ef35..46ab2c34c96 100644 --- a/checker/tests/signedness/BooleansTest.java +++ b/checker/tests/signedness/BooleansTest.java @@ -2,24 +2,24 @@ public final class BooleansTest { - private static int indexOf(boolean[] array, boolean target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } + private static int indexOf(boolean[] array, boolean target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; } - return -1; - } - static class BooleanArrayAsList { - boolean[] array = new boolean[] {}; + static class BooleanArrayAsList { + boolean[] array = new boolean[] {}; - int start = 0; - int end = 0; + int start = 0; + int end = 0; - public boolean contains(@UnknownSignedness Object target) { - return (target instanceof Boolean) - && BooleansTest.indexOf(array, (Boolean) target, start, end) != -1; + public boolean contains(@UnknownSignedness Object target) { + return (target instanceof Boolean) + && BooleansTest.indexOf(array, (Boolean) target, start, end) != -1; + } } - } } diff --git a/checker/tests/signedness/BoxedPrimitives.java b/checker/tests/signedness/BoxedPrimitives.java index 661f522ebad..a182725f607 100644 --- a/checker/tests/signedness/BoxedPrimitives.java +++ b/checker/tests/signedness/BoxedPrimitives.java @@ -1,83 +1,84 @@ -import java.util.LinkedList; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.Unsigned; +import java.util.LinkedList; + public class BoxedPrimitives { - @Signed int si; - @Unsigned int ui; + @Signed int si; + @Unsigned int ui; - @Signed Integer sbi; - @Unsigned Integer ubi; + @Signed Integer sbi; + @Unsigned Integer ubi; - void argSigned(@Signed int x) { - si = x; - sbi = x; - // :: error: (assignment.type.incompatible) - ui = x; - // :: error: (assignment.type.incompatible) - ubi = x; - } + void argSigned(@Signed int x) { + si = x; + sbi = x; + // :: error: (assignment.type.incompatible) + ui = x; + // :: error: (assignment.type.incompatible) + ubi = x; + } - void argUnsigned(@Unsigned int x) { - // :: error: (assignment.type.incompatible) - si = x; - // :: error: (assignment.type.incompatible) - sbi = x; - ui = x; - ubi = x; - } + void argUnsigned(@Unsigned int x) { + // :: error: (assignment.type.incompatible) + si = x; + // :: error: (assignment.type.incompatible) + sbi = x; + ui = x; + ubi = x; + } - void argSignedBoxed(@Signed Integer x) { - si = x; - sbi = x; - // :: error: (assignment.type.incompatible) - ui = x; - // :: error: (assignment.type.incompatible) - ubi = x; - } + void argSignedBoxed(@Signed Integer x) { + si = x; + sbi = x; + // :: error: (assignment.type.incompatible) + ui = x; + // :: error: (assignment.type.incompatible) + ubi = x; + } - void argUnsignedBoxed(@Unsigned Integer x) { - // :: error: (assignment.type.incompatible) - si = x; - // :: error: (assignment.type.incompatible) - sbi = x; - ui = x; - ubi = x; - } + void argUnsignedBoxed(@Unsigned Integer x) { + // :: error: (assignment.type.incompatible) + si = x; + // :: error: (assignment.type.incompatible) + sbi = x; + ui = x; + ubi = x; + } - void client() { - argSigned(si); - argSignedBoxed(si); - argSigned(sbi); - argSignedBoxed(sbi); - // :: error: (argument.type.incompatible) - argUnsigned(si); - // :: error: (argument.type.incompatible) - argUnsignedBoxed(si); - // :: error: (argument.type.incompatible) - argUnsigned(sbi); - // :: error: (argument.type.incompatible) - argUnsignedBoxed(sbi); - // :: error: (argument.type.incompatible) - argSigned(ui); - // :: error: (argument.type.incompatible) - argSignedBoxed(ui); - // :: error: (argument.type.incompatible) - argSigned(ubi); - // :: error: (argument.type.incompatible) - argSignedBoxed(ubi); - argUnsigned(ui); - argUnsignedBoxed(ui); - argUnsigned(ubi); - argUnsignedBoxed(ubi); - } + void client() { + argSigned(si); + argSignedBoxed(si); + argSigned(sbi); + argSignedBoxed(sbi); + // :: error: (argument.type.incompatible) + argUnsigned(si); + // :: error: (argument.type.incompatible) + argUnsignedBoxed(si); + // :: error: (argument.type.incompatible) + argUnsigned(sbi); + // :: error: (argument.type.incompatible) + argUnsignedBoxed(sbi); + // :: error: (argument.type.incompatible) + argSigned(ui); + // :: error: (argument.type.incompatible) + argSignedBoxed(ui); + // :: error: (argument.type.incompatible) + argSigned(ubi); + // :: error: (argument.type.incompatible) + argSignedBoxed(ubi); + argUnsigned(ui); + argUnsignedBoxed(ui); + argUnsigned(ubi); + argUnsignedBoxed(ubi); + } - public LinkedList commands; + public LinkedList commands; - void forLoop() { - for (Integer ix : this.commands) { - argSigned(ix); + void forLoop() { + for (Integer ix : this.commands) { + argSigned(ix); + } } - } } diff --git a/checker/tests/signedness/Cast.java b/checker/tests/signedness/Cast.java index 51b43bd9443..40122c62fc7 100644 --- a/checker/tests/signedness/Cast.java +++ b/checker/tests/signedness/Cast.java @@ -2,21 +2,21 @@ public class Cast { - static final Object object = 1; + static final Object object = 1; - void client() { - objectiveParameter(object); - } + void client() { + objectiveParameter(object); + } - void objectiveParameter(Object object) { - integralParameter((Integer) object); - } + void objectiveParameter(Object object) { + integralParameter((Integer) object); + } - // This passes when object is initialized within objectiveArgument(). - void objectiveArgument() { - Object object = -3; - integralParameter((Integer) object); - } + // This passes when object is initialized within objectiveArgument(). + void objectiveArgument() { + Object object = -3; + integralParameter((Integer) object); + } - void integralParameter(int x) {} + void integralParameter(int x) {} } diff --git a/checker/tests/signedness/CastedShifts.java b/checker/tests/signedness/CastedShifts.java index d82b9f048d8..a703a9e0c5a 100644 --- a/checker/tests/signedness/CastedShifts.java +++ b/checker/tests/signedness/CastedShifts.java @@ -2,374 +2,374 @@ public class CastedShifts { - public void CastedIntShifts(@Unsigned int unsigned, @Signed int signed) { - // Cast to byte. - @UnknownSignedness byte byteRes; - - // Shifting right by 23, the introduced bits are cast away - byteRes = (@Unsigned byte) (unsigned >>> 23); - byteRes = (@Unsigned byte) (unsigned >> 23); - byteRes = (@Signed byte) (signed >>> 23); - byteRes = (@Signed byte) (signed >> 23); - byteRes = (byte) (signed >> 23); - - // Shifting right by 24, the introduced bits are still cast away. - byteRes = (@Unsigned byte) (unsigned >>> 24); - byteRes = (@Unsigned byte) (unsigned >> 24); - byteRes = (@Signed byte) (signed >>> 24); - byteRes = (@Signed byte) (signed >> 24); - - // Shifting right by 25, now the MSB matters. - byteRes = (@Unsigned byte) (unsigned >>> 25); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) (unsigned >> 25); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) (signed >>> 25); - byteRes = (@Signed byte) (signed >> 25); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) (unsigned >>> 0); - byteRes = (@Unsigned byte) (unsigned >> 0); - byteRes = (@Signed byte) (signed >>> 0); - byteRes = (@Signed byte) (signed >> 0); - - // Cast to short. - @UnknownSignedness short shortRes; - - // Shifting right by 15, the introduced bits are cast away - shortRes = (@Unsigned short) (unsigned >>> 15); - shortRes = (@Unsigned short) (unsigned >> 15); - shortRes = (@Signed short) (signed >>> 15); - shortRes = (@Signed short) (signed >> 15); - - // Shifting right by 16, the introduced bits are still cast away. - shortRes = (@Unsigned short) (unsigned >>> 16); - shortRes = (@Unsigned short) (unsigned >> 16); - shortRes = (@Signed short) (signed >>> 16); - shortRes = (@Signed short) (signed >> 16); - - // Shifting right by 17, now the MSB matters. - shortRes = (@Unsigned short) (unsigned >>> 17); - - // :: error: (shift.signed) - shortRes = (@Unsigned short) (unsigned >> 17); - - // :: error: (shift.unsigned) - shortRes = (@Signed short) (signed >>> 17); - shortRes = (@Signed short) (signed >> 17); - - // Shifting right by zero should behave as assignment - shortRes = (@Unsigned short) (unsigned >>> 0); - shortRes = (@Unsigned short) (unsigned >> 0); - shortRes = (@Signed short) (signed >>> 0); - shortRes = (@Signed short) (signed >> 0); - - // Cast to int. - @UnknownSignedness int intRes; - - // Now shift signedness matters again - intRes = (@Unsigned int) (unsigned >>> 1); - - // :: error: (shift.signed) - intRes = (@Unsigned int) (unsigned >> 1); - - // :: error: (shift.unsigned) - intRes = (@Signed int) (signed >>> 1); - intRes = (@Signed int) (signed >> 1); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) (unsigned >>> 0); - intRes = (@Unsigned int) (unsigned >> 0); - intRes = (@Signed int) (signed >>> 0); - intRes = (@Signed int) (signed >> 0); - - // Cast to long. - @UnknownSignedness long longRes; - - // Now shift signedness matters again - longRes = (@Unsigned long) (unsigned >>> 1); - - // :: error: (shift.signed) - longRes = (@Unsigned long) (unsigned >> 1); - - // :: error: (shift.unsigned) - longRes = (@Signed long) (signed >>> 1); - longRes = (@Signed long) (signed >> 1); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) (unsigned >>> 0); - longRes = (@Unsigned long) (unsigned >> 0); - longRes = (@Signed long) (signed >>> 0); - longRes = (@Signed long) (signed >> 0); - - // Tests with double parenthesis (only byte and int) - - // Cast to byte. - // Shifting right by 23, the introduced bits are cast away - byteRes = (@Unsigned byte) ((unsigned >>> 23)); - byteRes = (@Unsigned byte) ((unsigned >> 23)); - byteRes = (@Signed byte) ((signed >>> 23)); - byteRes = (@Signed byte) ((signed >> 23)); - - // Shifting right by 24, the introduced bits are still cast away. - byteRes = (@Unsigned byte) ((unsigned >>> 24)); - byteRes = (@Unsigned byte) ((unsigned >> 24)); - byteRes = (@Signed byte) ((signed >>> 24)); - byteRes = (@Signed byte) ((signed >> 24)); - - // Shifting right by 25, now the MSB matters. - byteRes = (@Unsigned byte) ((unsigned >>> 25)); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) ((unsigned >> 25)); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) ((signed >>> 25)); - byteRes = (@Signed byte) ((signed >> 25)); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) ((unsigned >>> 0)); - byteRes = (@Unsigned byte) ((unsigned >> 0)); - byteRes = (@Signed byte) ((signed >>> 0)); - byteRes = (@Signed byte) ((signed >> 0)); - - // Cast to int. - // Now shift signedness matters again - intRes = (@Unsigned int) ((unsigned >>> 1)); - - // :: error: (shift.signed) - intRes = (@Unsigned int) ((unsigned >> 1)); - - // :: error: (shift.unsigned) - intRes = (@Signed int) ((signed >>> 1)); - intRes = (@Signed int) ((signed >> 1)); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) ((unsigned >>> 0)); - intRes = (@Unsigned int) ((unsigned >> 0)); - intRes = (@Signed int) ((signed >>> 0)); - intRes = (@Signed int) ((signed >> 0)); - - // Test outside Java Specification shift ranges - // Cast to int. - // Now shift signedness matters again - intRes = (@Unsigned int) ((unsigned >>> 33)); - - // :: error: (shift.signed) - intRes = (@Unsigned int) ((unsigned >> 33)); - - // :: error: (shift.unsigned) - intRes = (@Signed int) ((signed >>> 33)); - intRes = (@Signed int) ((signed >> 33)); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) ((unsigned >>> 32)); - intRes = (@Unsigned int) ((unsigned >> 32)); - intRes = (@Signed int) ((signed >>> 32)); - intRes = (@Signed int) ((signed >> 32)); - } - - public void CastedLongShifts(@Unsigned long unsigned, @Signed long signed) { - // Cast to byte. - @UnknownSignedness byte byteRes; - - // Shifting right by 55, the introduced bits are cast away - byteRes = (@Unsigned byte) (unsigned >>> 55); - byteRes = (@Unsigned byte) (unsigned >> 55); - byteRes = (@Signed byte) (signed >>> 55); - byteRes = (@Signed byte) (signed >> 55); - - // Shifting right by 56, the introduced bits are still cast away. - byteRes = (@Unsigned byte) (unsigned >>> 56); - byteRes = (@Unsigned byte) (unsigned >> 56); - byteRes = (@Signed byte) (signed >>> 56); - byteRes = (@Signed byte) (signed >> 56); - - // Shifting right by 57, now the MSB matters. - byteRes = (@Unsigned byte) (unsigned >>> 57); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) (unsigned >> 57); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) (signed >>> 57); - byteRes = (@Signed byte) (signed >> 57); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) (unsigned >>> 0); - byteRes = (@Unsigned byte) (unsigned >> 0); - byteRes = (@Signed byte) (signed >>> 0); - byteRes = (@Signed byte) (signed >> 0); - - // Cast to char. - char charRes; - - // Shifting right by 55, the introduced bits are cast away - charRes = (char) (unsigned >>> 55); - charRes = (char) (unsigned >> 55); - - // Shifting right by 56, the introduced bits are still cast away. - charRes = (char) (unsigned >>> 56); - charRes = (char) (unsigned >> 56); - - // Shifting right by 57, now the MSB matters. - charRes = (char) (unsigned >>> 57); - - // :: error: (shift.signed) - charRes = (char) (unsigned >> 57); - - // Shifting right by zero should behave as assignment - charRes = (char) (unsigned >>> 0); - charRes = (char) (unsigned >> 0); - - // Cast to short. - @UnknownSignedness short shortRes; - - // Shifting right by 47, the introduced bits are cast away - shortRes = (@Unsigned short) (unsigned >>> 47); - shortRes = (@Unsigned short) (unsigned >> 47); - shortRes = (@Signed short) (signed >>> 47); - shortRes = (@Signed short) (signed >> 47); - - // Shifting right by 48, the introduced bits are still cast away. - shortRes = (@Unsigned short) (unsigned >>> 48); - shortRes = (@Unsigned short) (unsigned >> 48); - shortRes = (@Signed short) (signed >>> 48); - shortRes = (@Signed short) (signed >> 48); - - // Shifting right by 49, now the MSB matters. - shortRes = (@Unsigned short) (unsigned >>> 49); - - // :: error: (shift.signed) - shortRes = (@Unsigned short) (unsigned >> 49); - - // :: error: (shift.unsigned) - shortRes = (@Signed short) (signed >>> 49); - shortRes = (@Signed short) (signed >> 49); - - // Shifting right by zero should behave as assignment - shortRes = (@Unsigned short) (unsigned >>> 0); - shortRes = (@Unsigned short) (unsigned >> 0); - shortRes = (@Signed short) (signed >>> 0); - shortRes = (@Signed short) (signed >> 0); - - // Cast to int. - @UnknownSignedness int intRes; - - // Shifting right by 31, the introduced bits are cast away - intRes = (@Unsigned int) (unsigned >>> 31); - intRes = (@Unsigned int) (unsigned >> 31); - intRes = (@Signed int) (signed >>> 31); - intRes = (@Signed int) (signed >> 31); - - // Shifting right by 32, the introduced bits are still cast away. - intRes = (@Unsigned int) (unsigned >>> 32); - intRes = (@Unsigned int) (unsigned >> 32); - intRes = (@Signed int) (signed >>> 32); - intRes = (@Signed int) (signed >> 32); - - // Shifting right by 33, now the MSB matters. - intRes = (@Unsigned int) (unsigned >>> 33); - - // :: error: (shift.signed) - intRes = (@Unsigned int) (unsigned >> 33); - - // :: error: (shift.unsigned) - intRes = (@Signed int) (signed >>> 33); - intRes = (@Signed int) (signed >> 33); - - // Shifting right by zero should behave as assignment - intRes = (@Unsigned int) (unsigned >>> 0); - intRes = (@Unsigned int) (unsigned >> 0); - intRes = (@Signed int) (signed >>> 0); - intRes = (@Signed int) (signed >> 0); - - // Cast to long. - @UnknownSignedness long longRes; - - // Now shift signedness matters again - longRes = (@Unsigned long) (unsigned >>> 1); - - // :: error: (shift.signed) - longRes = (@Unsigned long) (unsigned >> 1); - - // :: error: (shift.unsigned) - longRes = (@Signed long) (signed >>> 1); - longRes = (@Signed long) (signed >> 1); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) (unsigned >>> 0); - longRes = (@Unsigned long) (unsigned >> 0); - longRes = (@Signed long) (signed >>> 0); - longRes = (@Signed long) (signed >> 0); - - // Tests with double parenthesis (only byte and long) - - // Cast to byte. - // Shifting right by 55, the introduced bits are cast away - byteRes = (@Unsigned byte) ((unsigned >>> 55)); - byteRes = (@Unsigned byte) ((unsigned >> 55)); - byteRes = (@Signed byte) ((signed >>> 55)); - byteRes = (@Signed byte) ((signed >> 55)); - - // Shifting right by 56, the introduced bits are still cast away. - byteRes = (@Unsigned byte) ((unsigned >>> 56)); - byteRes = (@Unsigned byte) ((unsigned >> 56)); - byteRes = (@Signed byte) ((signed >>> 56)); - byteRes = (@Signed byte) ((signed >> 56)); - - // Shifting right by 9, now the MSB matters. - byteRes = (@Unsigned byte) ((unsigned >>> 57)); - - // :: error: (shift.signed) - byteRes = (@Unsigned byte) ((unsigned >> 57)); - - // :: error: (shift.unsigned) - byteRes = (@Signed byte) ((signed >>> 57)); - byteRes = (@Signed byte) ((signed >> 57)); - - // Shifting right by zero should behave as assignment - byteRes = (@Unsigned byte) ((unsigned >>> 0)); - byteRes = (@Unsigned byte) ((unsigned >> 0)); - byteRes = (@Signed byte) ((signed >>> 0)); - byteRes = (@Signed byte) ((signed >> 0)); - - // Cast to long. - // Now shift signedness matters again - longRes = (@Unsigned long) ((unsigned >>> 1)); - - // :: error: (shift.signed) - longRes = (@Unsigned long) ((unsigned >> 1)); - - // :: error: (shift.unsigned) - longRes = (@Signed long) ((signed >>> 1)); - longRes = (@Signed long) ((signed >> 1)); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) ((unsigned >>> 0)); - longRes = (@Unsigned long) ((unsigned >> 0)); - longRes = (@Signed long) ((signed >>> 0)); - longRes = (@Signed long) ((signed >> 0)); - - // Test outside Java Specification shift ranges - // Cast to long. - // Now shift signedness matters again - longRes = (@Unsigned long) ((unsigned >>> 65)); - - // :: error: (shift.signed) - longRes = (@Unsigned long) ((unsigned >> 65)); - - // :: error: (shift.unsigned) - longRes = (@Signed long) ((signed >>> 65)); - longRes = (@Signed long) ((signed >> 65)); - - // Shifting right by zero should behave as assignment - longRes = (@Unsigned long) ((unsigned >>> 64)); - longRes = (@Unsigned long) ((unsigned >> 64)); - longRes = (@Signed long) ((signed >>> 64)); - longRes = (@Signed long) ((signed >> 64)); - longRes = (long) ((signed >> 64)); - } + public void CastedIntShifts(@Unsigned int unsigned, @Signed int signed) { + // Cast to byte. + @UnknownSignedness byte byteRes; + + // Shifting right by 23, the introduced bits are cast away + byteRes = (@Unsigned byte) (unsigned >>> 23); + byteRes = (@Unsigned byte) (unsigned >> 23); + byteRes = (@Signed byte) (signed >>> 23); + byteRes = (@Signed byte) (signed >> 23); + byteRes = (byte) (signed >> 23); + + // Shifting right by 24, the introduced bits are still cast away. + byteRes = (@Unsigned byte) (unsigned >>> 24); + byteRes = (@Unsigned byte) (unsigned >> 24); + byteRes = (@Signed byte) (signed >>> 24); + byteRes = (@Signed byte) (signed >> 24); + + // Shifting right by 25, now the MSB matters. + byteRes = (@Unsigned byte) (unsigned >>> 25); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) (unsigned >> 25); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) (signed >>> 25); + byteRes = (@Signed byte) (signed >> 25); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) (unsigned >>> 0); + byteRes = (@Unsigned byte) (unsigned >> 0); + byteRes = (@Signed byte) (signed >>> 0); + byteRes = (@Signed byte) (signed >> 0); + + // Cast to short. + @UnknownSignedness short shortRes; + + // Shifting right by 15, the introduced bits are cast away + shortRes = (@Unsigned short) (unsigned >>> 15); + shortRes = (@Unsigned short) (unsigned >> 15); + shortRes = (@Signed short) (signed >>> 15); + shortRes = (@Signed short) (signed >> 15); + + // Shifting right by 16, the introduced bits are still cast away. + shortRes = (@Unsigned short) (unsigned >>> 16); + shortRes = (@Unsigned short) (unsigned >> 16); + shortRes = (@Signed short) (signed >>> 16); + shortRes = (@Signed short) (signed >> 16); + + // Shifting right by 17, now the MSB matters. + shortRes = (@Unsigned short) (unsigned >>> 17); + + // :: error: (shift.signed) + shortRes = (@Unsigned short) (unsigned >> 17); + + // :: error: (shift.unsigned) + shortRes = (@Signed short) (signed >>> 17); + shortRes = (@Signed short) (signed >> 17); + + // Shifting right by zero should behave as assignment + shortRes = (@Unsigned short) (unsigned >>> 0); + shortRes = (@Unsigned short) (unsigned >> 0); + shortRes = (@Signed short) (signed >>> 0); + shortRes = (@Signed short) (signed >> 0); + + // Cast to int. + @UnknownSignedness int intRes; + + // Now shift signedness matters again + intRes = (@Unsigned int) (unsigned >>> 1); + + // :: error: (shift.signed) + intRes = (@Unsigned int) (unsigned >> 1); + + // :: error: (shift.unsigned) + intRes = (@Signed int) (signed >>> 1); + intRes = (@Signed int) (signed >> 1); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) (unsigned >>> 0); + intRes = (@Unsigned int) (unsigned >> 0); + intRes = (@Signed int) (signed >>> 0); + intRes = (@Signed int) (signed >> 0); + + // Cast to long. + @UnknownSignedness long longRes; + + // Now shift signedness matters again + longRes = (@Unsigned long) (unsigned >>> 1); + + // :: error: (shift.signed) + longRes = (@Unsigned long) (unsigned >> 1); + + // :: error: (shift.unsigned) + longRes = (@Signed long) (signed >>> 1); + longRes = (@Signed long) (signed >> 1); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) (unsigned >>> 0); + longRes = (@Unsigned long) (unsigned >> 0); + longRes = (@Signed long) (signed >>> 0); + longRes = (@Signed long) (signed >> 0); + + // Tests with double parenthesis (only byte and int) + + // Cast to byte. + // Shifting right by 23, the introduced bits are cast away + byteRes = (@Unsigned byte) ((unsigned >>> 23)); + byteRes = (@Unsigned byte) ((unsigned >> 23)); + byteRes = (@Signed byte) ((signed >>> 23)); + byteRes = (@Signed byte) ((signed >> 23)); + + // Shifting right by 24, the introduced bits are still cast away. + byteRes = (@Unsigned byte) ((unsigned >>> 24)); + byteRes = (@Unsigned byte) ((unsigned >> 24)); + byteRes = (@Signed byte) ((signed >>> 24)); + byteRes = (@Signed byte) ((signed >> 24)); + + // Shifting right by 25, now the MSB matters. + byteRes = (@Unsigned byte) ((unsigned >>> 25)); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) ((unsigned >> 25)); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) ((signed >>> 25)); + byteRes = (@Signed byte) ((signed >> 25)); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) ((unsigned >>> 0)); + byteRes = (@Unsigned byte) ((unsigned >> 0)); + byteRes = (@Signed byte) ((signed >>> 0)); + byteRes = (@Signed byte) ((signed >> 0)); + + // Cast to int. + // Now shift signedness matters again + intRes = (@Unsigned int) ((unsigned >>> 1)); + + // :: error: (shift.signed) + intRes = (@Unsigned int) ((unsigned >> 1)); + + // :: error: (shift.unsigned) + intRes = (@Signed int) ((signed >>> 1)); + intRes = (@Signed int) ((signed >> 1)); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) ((unsigned >>> 0)); + intRes = (@Unsigned int) ((unsigned >> 0)); + intRes = (@Signed int) ((signed >>> 0)); + intRes = (@Signed int) ((signed >> 0)); + + // Test outside Java Specification shift ranges + // Cast to int. + // Now shift signedness matters again + intRes = (@Unsigned int) ((unsigned >>> 33)); + + // :: error: (shift.signed) + intRes = (@Unsigned int) ((unsigned >> 33)); + + // :: error: (shift.unsigned) + intRes = (@Signed int) ((signed >>> 33)); + intRes = (@Signed int) ((signed >> 33)); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) ((unsigned >>> 32)); + intRes = (@Unsigned int) ((unsigned >> 32)); + intRes = (@Signed int) ((signed >>> 32)); + intRes = (@Signed int) ((signed >> 32)); + } + + public void CastedLongShifts(@Unsigned long unsigned, @Signed long signed) { + // Cast to byte. + @UnknownSignedness byte byteRes; + + // Shifting right by 55, the introduced bits are cast away + byteRes = (@Unsigned byte) (unsigned >>> 55); + byteRes = (@Unsigned byte) (unsigned >> 55); + byteRes = (@Signed byte) (signed >>> 55); + byteRes = (@Signed byte) (signed >> 55); + + // Shifting right by 56, the introduced bits are still cast away. + byteRes = (@Unsigned byte) (unsigned >>> 56); + byteRes = (@Unsigned byte) (unsigned >> 56); + byteRes = (@Signed byte) (signed >>> 56); + byteRes = (@Signed byte) (signed >> 56); + + // Shifting right by 57, now the MSB matters. + byteRes = (@Unsigned byte) (unsigned >>> 57); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) (unsigned >> 57); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) (signed >>> 57); + byteRes = (@Signed byte) (signed >> 57); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) (unsigned >>> 0); + byteRes = (@Unsigned byte) (unsigned >> 0); + byteRes = (@Signed byte) (signed >>> 0); + byteRes = (@Signed byte) (signed >> 0); + + // Cast to char. + char charRes; + + // Shifting right by 55, the introduced bits are cast away + charRes = (char) (unsigned >>> 55); + charRes = (char) (unsigned >> 55); + + // Shifting right by 56, the introduced bits are still cast away. + charRes = (char) (unsigned >>> 56); + charRes = (char) (unsigned >> 56); + + // Shifting right by 57, now the MSB matters. + charRes = (char) (unsigned >>> 57); + + // :: error: (shift.signed) + charRes = (char) (unsigned >> 57); + + // Shifting right by zero should behave as assignment + charRes = (char) (unsigned >>> 0); + charRes = (char) (unsigned >> 0); + + // Cast to short. + @UnknownSignedness short shortRes; + + // Shifting right by 47, the introduced bits are cast away + shortRes = (@Unsigned short) (unsigned >>> 47); + shortRes = (@Unsigned short) (unsigned >> 47); + shortRes = (@Signed short) (signed >>> 47); + shortRes = (@Signed short) (signed >> 47); + + // Shifting right by 48, the introduced bits are still cast away. + shortRes = (@Unsigned short) (unsigned >>> 48); + shortRes = (@Unsigned short) (unsigned >> 48); + shortRes = (@Signed short) (signed >>> 48); + shortRes = (@Signed short) (signed >> 48); + + // Shifting right by 49, now the MSB matters. + shortRes = (@Unsigned short) (unsigned >>> 49); + + // :: error: (shift.signed) + shortRes = (@Unsigned short) (unsigned >> 49); + + // :: error: (shift.unsigned) + shortRes = (@Signed short) (signed >>> 49); + shortRes = (@Signed short) (signed >> 49); + + // Shifting right by zero should behave as assignment + shortRes = (@Unsigned short) (unsigned >>> 0); + shortRes = (@Unsigned short) (unsigned >> 0); + shortRes = (@Signed short) (signed >>> 0); + shortRes = (@Signed short) (signed >> 0); + + // Cast to int. + @UnknownSignedness int intRes; + + // Shifting right by 31, the introduced bits are cast away + intRes = (@Unsigned int) (unsigned >>> 31); + intRes = (@Unsigned int) (unsigned >> 31); + intRes = (@Signed int) (signed >>> 31); + intRes = (@Signed int) (signed >> 31); + + // Shifting right by 32, the introduced bits are still cast away. + intRes = (@Unsigned int) (unsigned >>> 32); + intRes = (@Unsigned int) (unsigned >> 32); + intRes = (@Signed int) (signed >>> 32); + intRes = (@Signed int) (signed >> 32); + + // Shifting right by 33, now the MSB matters. + intRes = (@Unsigned int) (unsigned >>> 33); + + // :: error: (shift.signed) + intRes = (@Unsigned int) (unsigned >> 33); + + // :: error: (shift.unsigned) + intRes = (@Signed int) (signed >>> 33); + intRes = (@Signed int) (signed >> 33); + + // Shifting right by zero should behave as assignment + intRes = (@Unsigned int) (unsigned >>> 0); + intRes = (@Unsigned int) (unsigned >> 0); + intRes = (@Signed int) (signed >>> 0); + intRes = (@Signed int) (signed >> 0); + + // Cast to long. + @UnknownSignedness long longRes; + + // Now shift signedness matters again + longRes = (@Unsigned long) (unsigned >>> 1); + + // :: error: (shift.signed) + longRes = (@Unsigned long) (unsigned >> 1); + + // :: error: (shift.unsigned) + longRes = (@Signed long) (signed >>> 1); + longRes = (@Signed long) (signed >> 1); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) (unsigned >>> 0); + longRes = (@Unsigned long) (unsigned >> 0); + longRes = (@Signed long) (signed >>> 0); + longRes = (@Signed long) (signed >> 0); + + // Tests with double parenthesis (only byte and long) + + // Cast to byte. + // Shifting right by 55, the introduced bits are cast away + byteRes = (@Unsigned byte) ((unsigned >>> 55)); + byteRes = (@Unsigned byte) ((unsigned >> 55)); + byteRes = (@Signed byte) ((signed >>> 55)); + byteRes = (@Signed byte) ((signed >> 55)); + + // Shifting right by 56, the introduced bits are still cast away. + byteRes = (@Unsigned byte) ((unsigned >>> 56)); + byteRes = (@Unsigned byte) ((unsigned >> 56)); + byteRes = (@Signed byte) ((signed >>> 56)); + byteRes = (@Signed byte) ((signed >> 56)); + + // Shifting right by 9, now the MSB matters. + byteRes = (@Unsigned byte) ((unsigned >>> 57)); + + // :: error: (shift.signed) + byteRes = (@Unsigned byte) ((unsigned >> 57)); + + // :: error: (shift.unsigned) + byteRes = (@Signed byte) ((signed >>> 57)); + byteRes = (@Signed byte) ((signed >> 57)); + + // Shifting right by zero should behave as assignment + byteRes = (@Unsigned byte) ((unsigned >>> 0)); + byteRes = (@Unsigned byte) ((unsigned >> 0)); + byteRes = (@Signed byte) ((signed >>> 0)); + byteRes = (@Signed byte) ((signed >> 0)); + + // Cast to long. + // Now shift signedness matters again + longRes = (@Unsigned long) ((unsigned >>> 1)); + + // :: error: (shift.signed) + longRes = (@Unsigned long) ((unsigned >> 1)); + + // :: error: (shift.unsigned) + longRes = (@Signed long) ((signed >>> 1)); + longRes = (@Signed long) ((signed >> 1)); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) ((unsigned >>> 0)); + longRes = (@Unsigned long) ((unsigned >> 0)); + longRes = (@Signed long) ((signed >>> 0)); + longRes = (@Signed long) ((signed >> 0)); + + // Test outside Java Specification shift ranges + // Cast to long. + // Now shift signedness matters again + longRes = (@Unsigned long) ((unsigned >>> 65)); + + // :: error: (shift.signed) + longRes = (@Unsigned long) ((unsigned >> 65)); + + // :: error: (shift.unsigned) + longRes = (@Signed long) ((signed >>> 65)); + longRes = (@Signed long) ((signed >> 65)); + + // Shifting right by zero should behave as assignment + longRes = (@Unsigned long) ((unsigned >>> 64)); + longRes = (@Unsigned long) ((unsigned >> 64)); + longRes = (@Signed long) ((signed >>> 64)); + longRes = (@Signed long) ((signed >> 64)); + longRes = (long) ((signed >> 64)); + } } diff --git a/checker/tests/signedness/CharCast.java b/checker/tests/signedness/CharCast.java index 5d17303ff68..3d83353bfbc 100644 --- a/checker/tests/signedness/CharCast.java +++ b/checker/tests/signedness/CharCast.java @@ -2,28 +2,28 @@ public class CharCast { - void m(@SignedPositive int i) { - char c = (char) i; - } + void m(@SignedPositive int i) { + char c = (char) i; + } - void m1(short s) { - int x = s; - char c = (char) x; - } + void m1(short s) { + int x = s; + char c = (char) x; + } - void m2(int i) { - int x = (short) i; - char c = (char) x; - } + void m2(int i) { + int x = (short) i; + char c = (char) x; + } - void m3() { - int x = (short) 1; - char c = (char) x; - } + void m3() { + int x = (short) 1; + char c = (char) x; + } - void m4() { - short x = 1; - int y = x; - char c = (char) y; - } + void m4() { + short x = 1; + int y = x; + char c = (char) y; + } } diff --git a/checker/tests/signedness/CharCastedToInt.java b/checker/tests/signedness/CharCastedToInt.java index 25f99f10c68..25cc6924c23 100644 --- a/checker/tests/signedness/CharCastedToInt.java +++ b/checker/tests/signedness/CharCastedToInt.java @@ -1,8 +1,8 @@ public class CharCastedToInt { - int charCastToInt(char c) { - intParameter((int) c); - return (int) c; - } + int charCastToInt(char c) { + intParameter((int) c); + return (int) c; + } - void intParameter(int x) {} + void intParameter(int x) {} } diff --git a/checker/tests/signedness/CharComparisons.java b/checker/tests/signedness/CharComparisons.java index 860a5026b7f..c44551f233a 100644 --- a/checker/tests/signedness/CharComparisons.java +++ b/checker/tests/signedness/CharComparisons.java @@ -1,30 +1,30 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class CharComparisons { - char c; - @Unsigned byte b; + char c; + @Unsigned byte b; - void unsignedComparison(char c, @Unsigned byte b) { - // :: error: (comparison.unsignedrhs) - boolean res = c > b; - // :: error: (comparison.unsignedrhs) - res = c >= b; - // :: error: (comparison.unsignedrhs) - res = c < b; - // :: error: (comparison.unsignedrhs) - res = c <= b; - res = c == b; - } + void unsignedComparison(char c, @Unsigned byte b) { + // :: error: (comparison.unsignedrhs) + boolean res = c > b; + // :: error: (comparison.unsignedrhs) + res = c >= b; + // :: error: (comparison.unsignedrhs) + res = c < b; + // :: error: (comparison.unsignedrhs) + res = c <= b; + res = c == b; + } - void unsignedComparisonFields() { - // :: error: (comparison.unsignedrhs) - boolean res = this.c > this.b; - // :: error: (comparison.unsignedrhs) - res = this.c >= this.b; - // :: error: (comparison.unsignedrhs) - res = this.c < this.b; - // :: error: (comparison.unsignedrhs) - res = this.c <= this.b; - res = this.c == this.b; - } + void unsignedComparisonFields() { + // :: error: (comparison.unsignedrhs) + boolean res = this.c > this.b; + // :: error: (comparison.unsignedrhs) + res = this.c >= this.b; + // :: error: (comparison.unsignedrhs) + res = this.c < this.b; + // :: error: (comparison.unsignedrhs) + res = this.c <= this.b; + res = this.c == this.b; + } } diff --git a/checker/tests/signedness/CharSignedObject.java b/checker/tests/signedness/CharSignedObject.java index 2d0ecbc9bbc..e4fd6e79bfc 100644 --- a/checker/tests/signedness/CharSignedObject.java +++ b/checker/tests/signedness/CharSignedObject.java @@ -1,5 +1,5 @@ public final class CharSignedObject { - void m(int ttype) { - System.out.printf(" bad ttype %c%n", (char) ttype); - } + void m(int ttype) { + System.out.printf(" bad ttype %c%n", (char) ttype); + } } diff --git a/checker/tests/signedness/CharToFloat.java b/checker/tests/signedness/CharToFloat.java index 6a2a6b3bc7d..1caaea9f8f0 100644 --- a/checker/tests/signedness/CharToFloat.java +++ b/checker/tests/signedness/CharToFloat.java @@ -1,17 +1,17 @@ // Test case for issue #3711: https://github.com/typetools/checker-framework/issues/3711 public class CharToFloat { - void castCharacter(Object o) { - floatParameter((Character) o); - doubleParameter((Character) o); - } + void castCharacter(Object o) { + floatParameter((Character) o); + doubleParameter((Character) o); + } - void passCharacter(Character c) { - floatParameter(c); - doubleParameter(c); - } + void passCharacter(Character c) { + floatParameter(c); + doubleParameter(c); + } - void floatParameter(float f) {} + void floatParameter(float f) {} - void doubleParameter(double d) {} + void doubleParameter(double d) {} } diff --git a/checker/tests/signedness/CombinationIterator.java b/checker/tests/signedness/CombinationIterator.java index 0e5e411dafb..d37c8f8e2d1 100644 --- a/checker/tests/signedness/CombinationIterator.java +++ b/checker/tests/signedness/CombinationIterator.java @@ -4,18 +4,18 @@ import java.util.List; public class CombinationIterator implements Iterator> { - public CombinationIterator(Collection> collectionsOfCandidates) { - ArrayList> listOfCollectionsOfCanditates = - new ArrayList<>(collectionsOfCandidates); - } + public CombinationIterator(Collection> collectionsOfCandidates) { + ArrayList> listOfCollectionsOfCanditates = + new ArrayList<>(collectionsOfCandidates); + } - @Override - public boolean hasNext() { - return false; - } + @Override + public boolean hasNext() { + return false; + } - @Override - public List next() { - return null; - } + @Override + public List next() { + return null; + } } diff --git a/checker/tests/signedness/CompareChars.java b/checker/tests/signedness/CompareChars.java index 1ca055906fc..bbacec7b5bf 100644 --- a/checker/tests/signedness/CompareChars.java +++ b/checker/tests/signedness/CompareChars.java @@ -2,16 +2,16 @@ // https://github.com/typetools/checker-framework/issues/3669 public class CompareChars { - void compareUnsignedChars(char c2) { - char c1 = 'a'; - boolean res = c1 > c2; - res = c1 >= c2; - res = c1 < c2; - res = c1 <= c2; - } + void compareUnsignedChars(char c2) { + char c1 = 'a'; + boolean res = c1 > c2; + res = c1 >= c2; + res = c1 < c2; + res = c1 <= c2; + } - // Test case for issue #5166: https://tinyurl.com/cfissue/5166 - private static boolean isWhitespace(char c) { - return c <= '\u0020'; - } + // Test case for issue #5166: https://tinyurl.com/cfissue/5166 + private static boolean isWhitespace(char c) { + return c <= '\u0020'; + } } diff --git a/checker/tests/signedness/Comparisons.java b/checker/tests/signedness/Comparisons.java index 680c003cdcf..0baeee52f90 100644 --- a/checker/tests/signedness/Comparisons.java +++ b/checker/tests/signedness/Comparisons.java @@ -2,74 +2,74 @@ public class Comparisons { - public void ComparisonTest( - @Unsigned int unsigned, @PolySigned int polysigned, @UnknownSignedness int unknown) { + public void ComparisonTest( + @Unsigned int unsigned, @PolySigned int polysigned, @UnknownSignedness int unknown) { - boolean testRes; + boolean testRes; - // :: error: (comparison.unsignedlhs) - testRes = unsigned < unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned < unknown; - // :: error: (comparison.unsignedlhs) - testRes = polysigned < unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned < unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown < unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown < unsigned; - // :: error: (comparison.unsignedrhs) - testRes = unknown < polysigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown < polysigned; - // :: error: (comparison.unsignedlhs) - testRes = unsigned <= unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned <= unknown; - // :: error: (comparison.unsignedlhs) - testRes = polysigned <= unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned <= unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown <= unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown <= unsigned; - // :: error: (comparison.unsignedrhs) - testRes = unknown <= polysigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown <= polysigned; - // :: error: (comparison.unsignedlhs) - testRes = unsigned > unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned > unknown; - // :: error: (comparison.unsignedlhs) - testRes = polysigned > unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned > unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown > unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown > unsigned; - // :: error: (comparison.unsignedrhs) - testRes = unknown > polysigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown > polysigned; - // :: error: (comparison.unsignedlhs) - testRes = unsigned >= unknown; + // :: error: (comparison.unsignedlhs) + testRes = unsigned >= unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown >= unsigned; + // :: error: (comparison.unsignedrhs) + testRes = unknown >= unsigned; - // :: error: (comparison.unsignedlhs) - testRes = polysigned >= unknown; + // :: error: (comparison.unsignedlhs) + testRes = polysigned >= unknown; - // :: error: (comparison.unsignedrhs) - testRes = unknown >= polysigned; - } + // :: error: (comparison.unsignedrhs) + testRes = unknown >= polysigned; + } - public void EqualsTest(@Unsigned int unsigned, @Signed int signed) { + public void EqualsTest(@Unsigned int unsigned, @Signed int signed) { - boolean testRes; + boolean testRes; - // :: error: (comparison.mixed.unsignedlhs) - testRes = unsigned == signed; + // :: error: (comparison.mixed.unsignedlhs) + testRes = unsigned == signed; - // :: error: (comparison.mixed.unsignedrhs) - testRes = signed == unsigned; + // :: error: (comparison.mixed.unsignedrhs) + testRes = signed == unsigned; - // :: error: (comparison.mixed.unsignedlhs) - testRes = unsigned != signed; + // :: error: (comparison.mixed.unsignedlhs) + testRes = unsigned != signed; - // :: error: (comparison.mixed.unsignedrhs) - testRes = signed != unsigned; - } + // :: error: (comparison.mixed.unsignedrhs) + testRes = signed != unsigned; + } } diff --git a/checker/tests/signedness/CompoundAssignmentsSignedness.java b/checker/tests/signedness/CompoundAssignmentsSignedness.java index a1efead0989..62dccb004a1 100644 --- a/checker/tests/signedness/CompoundAssignmentsSignedness.java +++ b/checker/tests/signedness/CompoundAssignmentsSignedness.java @@ -2,165 +2,165 @@ public class CompoundAssignmentsSignedness { - public void DivModTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void DivModTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - // :: error: (compound.assignment.unsigned.expression) - unknown /= unsigned; + // :: error: (compound.assignment.unsigned.expression) + unknown /= unsigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - unsigned /= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + unsigned /= unknown; - // :: error: (compound.assignment.unsigned.variable) - unsigned /= constant; + // :: error: (compound.assignment.unsigned.variable) + unsigned /= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant /= unsigned; + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant /= unsigned; - // :: error: (compound.assignment.unsigned.expression) - unknown /= polysigned; + // :: error: (compound.assignment.unsigned.expression) + unknown /= polysigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned /= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned /= unknown; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned /= constant; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned /= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant /= polysigned; + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant /= polysigned; - // :: error: (compound.assignment.unsigned.expression) - unknown %= unsigned; + // :: error: (compound.assignment.unsigned.expression) + unknown %= unsigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - unsigned %= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + unsigned %= unknown; - // :: error: (compound.assignment.unsigned.expression) - unknown %= polysigned; + // :: error: (compound.assignment.unsigned.expression) + unknown %= polysigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned %= unknown; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned %= unknown; - // :: error: (compound.assignment.unsigned.variable) - unsigned %= constant; + // :: error: (compound.assignment.unsigned.variable) + unsigned %= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant %= unsigned; + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant %= unsigned; - // :: error: (compound.assignment.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - polysigned %= constant; + // :: error: (compound.assignment.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + polysigned %= constant; - // :: error: (compound.assignment.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - constant %= polysigned; - } + // :: error: (compound.assignment.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + constant %= polysigned; + } - public void SignedRightShiftTest( - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void SignedRightShiftTest( + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - // :: error: (compound.assignment.shift.signed) - unsigned >>= constant; + // :: error: (compound.assignment.shift.signed) + unsigned >>= constant; - constant >>= unsigned; + constant >>= unsigned; - // :: error: (compound.assignment.shift.signed) - polysigned >>= constant; + // :: error: (compound.assignment.shift.signed) + polysigned >>= constant; - constant >>= polysigned; + constant >>= polysigned; - // :: error: (compound.assignment.shift.signed) - unsigned >>= unknown; + // :: error: (compound.assignment.shift.signed) + unsigned >>= unknown; - unknown >>= unsigned; + unknown >>= unsigned; - // :: error: (compound.assignment.shift.signed) - polysigned >>= unknown; + // :: error: (compound.assignment.shift.signed) + polysigned >>= unknown; - unknown >>= polysigned; - } + unknown >>= polysigned; + } - public void UnsignedRightShiftTest( - @Signed int signed, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void UnsignedRightShiftTest( + @Signed int signed, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - // :: error: (compound.assignment.shift.unsigned) - signed >>>= constant; + // :: error: (compound.assignment.shift.unsigned) + signed >>>= constant; - constant >>>= signed; + constant >>>= signed; - // :: error: (compound.assignment.shift.unsigned) - signed >>>= unknown; + // :: error: (compound.assignment.shift.unsigned) + signed >>>= unknown; - unknown >>>= signed; + unknown >>>= signed; - // :: error: (compound.assignment.shift.unsigned) - polysigned >>>= constant; + // :: error: (compound.assignment.shift.unsigned) + polysigned >>>= constant; - constant >>>= polysigned; + constant >>>= polysigned; - // :: error: (compound.assignment.shift.unsigned) - polysigned >>>= unknown; + // :: error: (compound.assignment.shift.unsigned) + polysigned >>>= unknown; - unknown >>>= polysigned; - } + unknown >>>= polysigned; + } - public void LeftShiftTest( - @Signed int signed, - @Unsigned int unsigned, - @PolySigned int polysigned, - @UnknownSignedness int unknown, - @SignednessGlb int constant) { + public void LeftShiftTest( + @Signed int signed, + @Unsigned int unsigned, + @PolySigned int polysigned, + @UnknownSignedness int unknown, + @SignednessGlb int constant) { - signed <<= constant; + signed <<= constant; - constant <<= signed; + constant <<= signed; - signed <<= unknown; + signed <<= unknown; - unknown <<= signed; + unknown <<= signed; - unsigned <<= constant; + unsigned <<= constant; - constant <<= unsigned; + constant <<= unsigned; - unsigned <<= unknown; + unsigned <<= unknown; - unknown <<= unsigned; + unknown <<= unsigned; - polysigned <<= constant; + polysigned <<= constant; - constant <<= polysigned; + constant <<= polysigned; - polysigned <<= unknown; + polysigned <<= unknown; - unknown <<= polysigned; - } + unknown <<= polysigned; + } - public void mixedTest(@Unsigned int unsigned, @Signed int signed) { + public void mixedTest(@Unsigned int unsigned, @Signed int signed) { - // :: error: (compound.assignment.mixed.unsigned.variable) - // :: error: (compound.assignment.type.incompatible) - unsigned += signed; + // :: error: (compound.assignment.mixed.unsigned.variable) + // :: error: (compound.assignment.type.incompatible) + unsigned += signed; - // :: error: (compound.assignment.mixed.unsigned.expression) - // :: error: (compound.assignment.type.incompatible) - signed += unsigned; - } + // :: error: (compound.assignment.mixed.unsigned.expression) + // :: error: (compound.assignment.type.incompatible) + signed += unsigned; + } } diff --git a/checker/tests/signedness/CompoundAssignmentsSignedness2.java b/checker/tests/signedness/CompoundAssignmentsSignedness2.java index cb49f0af424..1710a351631 100644 --- a/checker/tests/signedness/CompoundAssignmentsSignedness2.java +++ b/checker/tests/signedness/CompoundAssignmentsSignedness2.java @@ -1,63 +1,63 @@ // Test case for issue #3709: https://github.com/typetools/checker-framework/issues/3709 public class CompoundAssignmentsSignedness2 { - void additionWithCompoundAssignment(char c, int i1) { - i1 += c; - } + void additionWithCompoundAssignment(char c, int i1) { + i1 += c; + } - void additionWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 + c); - } + void additionWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 + c); + } - void additionWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 + c; - } + void additionWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 + c; + } - void subtractionWithCompoundAssignment(char c, int i1) { - i1 -= c; - } + void subtractionWithCompoundAssignment(char c, int i1) { + i1 -= c; + } - void subtractionWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 - c); - } + void subtractionWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 - c); + } - void subtractionWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 - c; - } + void subtractionWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 - c; + } - void multiplicationWithCompoundAssignment(char c, int i1) { - i1 *= c; - } + void multiplicationWithCompoundAssignment(char c, int i1) { + i1 *= c; + } - void multiplicationWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 * c); - } + void multiplicationWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 * c); + } - void multiplicationWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 * c; - } + void multiplicationWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 * c; + } - void divisionWithCompoundAssignment(char c, int i1) { - i1 /= c; - } + void divisionWithCompoundAssignment(char c, int i1) { + i1 /= c; + } - void divisionWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 / c); - } + void divisionWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 / c); + } - void divisionWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 / c; - } + void divisionWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 / c; + } - void modulusWithCompoundAssignment(char c, int i1) { - i1 %= c; - } + void modulusWithCompoundAssignment(char c, int i1) { + i1 %= c; + } - void modulusWithoutCompoundAssignment1(char c, int i1) { - i1 = (int) (i1 % c); - } + void modulusWithoutCompoundAssignment1(char c, int i1) { + i1 = (int) (i1 % c); + } - void modulusWithoutCompoundAssignment2(char c, int i1) { - i1 = i1 % c; - } + void modulusWithoutCompoundAssignment2(char c, int i1) { + i1 = i1 % c; + } } diff --git a/checker/tests/signedness/ConstantTests.java b/checker/tests/signedness/ConstantTests.java index 55b50a13acb..313ec42050a 100644 --- a/checker/tests/signedness/ConstantTests.java +++ b/checker/tests/signedness/ConstantTests.java @@ -5,29 +5,29 @@ public class ConstantTests { - @Unsigned int uint_negative_one = (@Unsigned int) -1; + @Unsigned int uint_negative_one = (@Unsigned int) -1; - @Unsigned int u1lit = 0xFFFFFFFE; // unsigned: 2^32 - 2, signed: -2 + @Unsigned int u1lit = 0xFFFFFFFE; // unsigned: 2^32 - 2, signed: -2 - void m() { + void m() { - int s = -2 / -1; + int s = -2 / -1; - @Unsigned int u = -1 / -2; + @Unsigned int u = -1 / -2; - int a = -1; - int b = -2; - int c = a / b; + int a = -1; + int b = -2; + int c = a / b; - @UnknownSignedness int x = 0xFFFFFFFE / 2; + @UnknownSignedness int x = 0xFFFFFFFE / 2; - int s1 = 0xFFFFFFFE; - @UnknownSignedness int y = s1 / 2; + int s1 = 0xFFFFFFFE; + @UnknownSignedness int y = s1 / 2; - // :: error: (operation.unsignedlhs) - @UnknownSignedness int z = (uint_negative_one) / -2; + // :: error: (operation.unsignedlhs) + @UnknownSignedness int z = (uint_negative_one) / -2; - // :: error: (operation.unsignedlhs) - @UnknownSignedness int w = u1lit / 2; - } + // :: error: (operation.unsignedlhs) + @UnknownSignedness int w = u1lit / 2; + } } diff --git a/checker/tests/signedness/DefaultsSignedness.java b/checker/tests/signedness/DefaultsSignedness.java index 34228e34f16..8b19c1d08d1 100644 --- a/checker/tests/signedness/DefaultsSignedness.java +++ b/checker/tests/signedness/DefaultsSignedness.java @@ -2,180 +2,180 @@ public class DefaultsSignedness { - public void ConstantTest() { + public void ConstantTest() { - // Test bytes with literal values - @SignednessGlb byte conByte; - @SignednessBottom byte botByte; + // Test bytes with literal values + @SignednessGlb byte conByte; + @SignednessBottom byte botByte; - byte testByte = 0; + byte testByte = 0; - conByte = testByte; + conByte = testByte; - // :: error: (assignment.type.incompatible) - botByte = testByte; + // :: error: (assignment.type.incompatible) + botByte = testByte; - // Test shorts with literal values - @SignednessGlb short conShort; - @SignednessBottom short botShort; + // Test shorts with literal values + @SignednessGlb short conShort; + @SignednessBottom short botShort; - short testShort = 128; + short testShort = 128; - conShort = testShort; + conShort = testShort; - // :: error: (assignment.type.incompatible) - botShort = testShort; + // :: error: (assignment.type.incompatible) + botShort = testShort; - // Test ints with literal values - @SignednessGlb int conInt; - @SignednessBottom int botInt; + // Test ints with literal values + @SignednessGlb int conInt; + @SignednessBottom int botInt; - int testInt = 32768; + int testInt = 32768; - conInt = testInt; + conInt = testInt; - // :: error: (assignment.type.incompatible) - botInt = testInt; + // :: error: (assignment.type.incompatible) + botInt = testInt; - // Test longs with literal values - @SignednessGlb long conLong; - @SignednessBottom long botLong; + // Test longs with literal values + @SignednessGlb long conLong; + @SignednessBottom long botLong; - long testLong = 2147483648L; + long testLong = 2147483648L; - conLong = testLong; + conLong = testLong; - // :: error: (assignment.type.incompatible) - botLong = testLong; - } + // :: error: (assignment.type.incompatible) + botLong = testLong; + } - public void SignedTest( - byte testByte, - short testShort, - int testInt, - long testLong, - float testFloat, - double testDouble, - char testChar, - boolean testBool, - Byte testBoxedByte, - Short testBoxedShort, - Integer testBoxedInteger, - Long testBoxedLong) { + public void SignedTest( + byte testByte, + short testShort, + int testInt, + long testLong, + float testFloat, + double testDouble, + char testChar, + boolean testBool, + Byte testBoxedByte, + Short testBoxedShort, + Integer testBoxedInteger, + Long testBoxedLong) { - // Test bytes - @Signed byte sinByte; - @SignednessGlb byte conByte; + // Test bytes + @Signed byte sinByte; + @SignednessGlb byte conByte; - sinByte = testByte; + sinByte = testByte; - // :: error: (assignment.type.incompatible) - conByte = testByte; + // :: error: (assignment.type.incompatible) + conByte = testByte; - // Test shorts - @Signed short sinShort; - @SignednessGlb short conShort; + // Test shorts + @Signed short sinShort; + @SignednessGlb short conShort; - sinShort = testShort; + sinShort = testShort; - // :: error: (assignment.type.incompatible) - conShort = testShort; + // :: error: (assignment.type.incompatible) + conShort = testShort; - // Test ints - @Signed int sinInt; - @SignednessGlb int conInt; + // Test ints + @Signed int sinInt; + @SignednessGlb int conInt; - sinInt = testInt; + sinInt = testInt; - // :: error: (assignment.type.incompatible) - conInt = testInt; + // :: error: (assignment.type.incompatible) + conInt = testInt; - // Test longs - @Signed long sinLong; - @SignednessGlb long conLong; + // Test longs + @Signed long sinLong; + @SignednessGlb long conLong; - sinLong = testLong; + sinLong = testLong; - // :: error: (assignment.type.incompatible) - conLong = testLong; + // :: error: (assignment.type.incompatible) + conLong = testLong; - // Test floats - // :: error: (anno.on.irrelevant) - @Signed float sinFloat; + // Test floats + // :: error: (anno.on.irrelevant) + @Signed float sinFloat; - sinFloat = testFloat; + sinFloat = testFloat; - // Test doubles - // :: error: (anno.on.irrelevant) - @Signed double sinDouble; + // Test doubles + // :: error: (anno.on.irrelevant) + @Signed double sinDouble; - sinDouble = testDouble; + sinDouble = testDouble; - /* - // Test boxed bytes - @Signed Byte sinBoxedByte; - @SignednessGlb Byte conBoxedByte; + /* + // Test boxed bytes + @Signed Byte sinBoxedByte; + @SignednessGlb Byte conBoxedByte; - sinBoxedByte = testBoxedByte; + sinBoxedByte = testBoxedByte; - //// :: error: (assignment.type.incompatible) - conBoxedByte = testBoxedByte; + //// :: error: (assignment.type.incompatible) + conBoxedByte = testBoxedByte; - // Test boxed shorts - @Signed Short sinBoxedShort; - @SignednessGlb Short conBoxedShort; + // Test boxed shorts + @Signed Short sinBoxedShort; + @SignednessGlb Short conBoxedShort; - sinBoxedShort = testBoxedShort; + sinBoxedShort = testBoxedShort; - //// :: error: (assignment.type.incompatible) - conBoxedShort = testBoxedShort; + //// :: error: (assignment.type.incompatible) + conBoxedShort = testBoxedShort; - // Test boxed Integers - @Signed Integer sinBoxedInteger; - @SignednessGlb Integer conBoxedInteger; + // Test boxed Integers + @Signed Integer sinBoxedInteger; + @SignednessGlb Integer conBoxedInteger; - sinBoxedInteger = testBoxedInteger; + sinBoxedInteger = testBoxedInteger; - //// :: error: (assignment.type.incompatible) - conBoxedInteger = testBoxedInteger; + //// :: error: (assignment.type.incompatible) + conBoxedInteger = testBoxedInteger; - // Test boxed Longs - @Signed Long sinBoxedLong; - @SignednessGlb Long conBoxedLong; + // Test boxed Longs + @Signed Long sinBoxedLong; + @SignednessGlb Long conBoxedLong; - sinBoxedLong = testBoxedLong; + sinBoxedLong = testBoxedLong; - //// :: error: (assignment.type.incompatible) - conBoxedLong = testBoxedLong; - */ - } + //// :: error: (assignment.type.incompatible) + conBoxedLong = testBoxedLong; + */ + } - public void SignednessBottom() { + public void SignednessBottom() { - @SignednessBottom Object botObj; + @SignednessBottom Object botObj; - Object testObj = null; + Object testObj = null; - botObj = testObj; - } + botObj = testObj; + } - public void UnknownSignedness(Object testObj, @Unsigned int unsigned, @Signed int signed) { + public void UnknownSignedness(Object testObj, @Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness Object unkObj; - @Unsigned Object unsinObj; + @UnknownSignedness Object unkObj; + @Unsigned Object unsinObj; - unkObj = testObj; + unkObj = testObj; - // :: error: (assignment.type.incompatible) - unsinObj = testObj; - } + // :: error: (assignment.type.incompatible) + unsinObj = testObj; + } - public void booleanProblem(@Unsigned int unsigned, @Signed int signed) { - boolean testBool = unsigned == 1 || signed > 1; - } + public void booleanProblem(@Unsigned int unsigned, @Signed int signed) { + boolean testBool = unsigned == 1 || signed > 1; + } - void method(Object[] obj_tags, int field_num) { - Object o = new DefaultsSignedness(); - obj_tags[field_num] = o; - } + void method(Object[] obj_tags, int field_num) { + Object o = new DefaultsSignedness(); + obj_tags[field_num] = o; + } } diff --git a/checker/tests/signedness/Desugar.java b/checker/tests/signedness/Desugar.java index 4f6719c4949..b7315e47c17 100644 --- a/checker/tests/signedness/Desugar.java +++ b/checker/tests/signedness/Desugar.java @@ -1,31 +1,32 @@ -import java.util.Map; import org.checkerframework.checker.signedness.qual.PolySigned; import org.checkerframework.checker.signedness.qual.Signed; +import java.util.Map; + public class Desugar { - void test(int x) { - int i = getI(); - Integer box = i; - @Signed Integer boxy = box; - @Signed Integer box2 = method(box); - } + void test(int x) { + int i = getI(); + Integer box = i; + @Signed Integer boxy = box; + @Signed Integer box2 = method(box); + } - @PolySigned Integer method(@PolySigned Integer i) { - return i; - } + @PolySigned Integer method(@PolySigned Integer i) { + return i; + } - @Signed int getI() { - return 0; - } + @Signed int getI() { + return 0; + } - void test2(Map nonceMap, String nextInvo) { - int invoNonce = calcNonce(nextInvo); - Integer key = invoNonce; - String enterInvo = nonceMap.get(key); - } + void test2(Map nonceMap, String nextInvo) { + int invoNonce = calcNonce(nextInvo); + Integer key = invoNonce; + String enterInvo = nonceMap.get(key); + } - private @Signed int calcNonce(String invocation) { - return 0; - } + private @Signed int calcNonce(String invocation) { + return 0; + } } diff --git a/checker/tests/signedness/IrrelevantAnnotationsTest.java b/checker/tests/signedness/IrrelevantAnnotationsTest.java index b165a50223c..bd701eb0400 100644 --- a/checker/tests/signedness/IrrelevantAnnotationsTest.java +++ b/checker/tests/signedness/IrrelevantAnnotationsTest.java @@ -3,13 +3,13 @@ public final class IrrelevantAnnotationsTest { - // :: error: (anno.on.irrelevant) - @Signed Boolean b1; + // :: error: (anno.on.irrelevant) + @Signed Boolean b1; - // :: error: (anno.on.irrelevant) - @Unsigned Boolean b2; + // :: error: (anno.on.irrelevant) + @Unsigned Boolean b2; - @Signed Object o1; + @Signed Object o1; - @Unsigned Object o2; + @Unsigned Object o2; } diff --git a/checker/tests/signedness/Issue2482.java b/checker/tests/signedness/Issue2482.java index d2bdc72d5bc..8872f3c91e3 100644 --- a/checker/tests/signedness/Issue2482.java +++ b/checker/tests/signedness/Issue2482.java @@ -1,68 +1,68 @@ public class Issue2482 { - void regularAssignment(byte[] b, int c) { - int a = b.length; - a = a + c; - } + void regularAssignment(byte[] b, int c) { + int a = b.length; + a = a + c; + } - void compoundAssignment(byte[] b, int c) { - int a = b.length; - a += c; - } + void compoundAssignment(byte[] b, int c) { + int a = b.length; + a += c; + } - void stringLenAdd(String s, int a) { - int len = s.length(); - len += a; - } + void stringLenAdd(String s, int a) { + int len = s.length(); + len += a; + } - void stringLenSub(String s, int a) { - int len = s.length(); - len -= a; - } + void stringLenSub(String s, int a) { + int len = s.length(); + len -= a; + } - void stringLenDiv(String s, int a) { - int len = s.length(); - len /= a; - } + void stringLenDiv(String s, int a) { + int len = s.length(); + len /= a; + } - void stringLenMul(String s, int a) { - int len = s.length(); - len *= a; - } + void stringLenMul(String s, int a) { + int len = s.length(); + len *= a; + } - void arrayLenAdd(byte[] b, int a) { - int len = b.length; - len += a; - } + void arrayLenAdd(byte[] b, int a) { + int len = b.length; + len += a; + } - void arrayLenSub(byte[] b, int a) { - int len = b.length; - len -= a; - } + void arrayLenSub(byte[] b, int a) { + int len = b.length; + len -= a; + } - void arrayLenDiv(byte[] b, int a) { - int len = b.length; - len /= a; - } + void arrayLenDiv(byte[] b, int a) { + int len = b.length; + len /= a; + } - void arrayLenMul(byte[] b, int a) { - int len = b.length; - len *= a; - } + void arrayLenMul(byte[] b, int a) { + int len = b.length; + len *= a; + } - void m3(int a) { + void m3(int a) { - int len = -1; // Negative - int len2 = 1; // Positive + int len = -1; // Negative + int len2 = 1; // Positive - len += a; - len -= a; - len /= a; - len *= a; + len += a; + len -= a; + len /= a; + len *= a; - len2 += a; - len2 -= a; - len2 /= a; - len2 *= a; - } + len2 += a; + len2 -= a; + len2 /= a; + len2 *= a; + } } diff --git a/checker/tests/signedness/Issue2483.java b/checker/tests/signedness/Issue2483.java index 916ea70f6a8..a0abf1dbd29 100644 --- a/checker/tests/signedness/Issue2483.java +++ b/checker/tests/signedness/Issue2483.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.signedness.qual.*; public class Issue2483 { - void foo(String a, byte[] b) { - @Unsigned int len = a.length(); - @Unsigned int len2 = b.length; - } + void foo(String a, byte[] b) { + @Unsigned int len = a.length(); + @Unsigned int len2 = b.length; + } } diff --git a/checker/tests/signedness/Issue2534.java b/checker/tests/signedness/Issue2534.java index 9f1de834be6..742f7535bc6 100644 --- a/checker/tests/signedness/Issue2534.java +++ b/checker/tests/signedness/Issue2534.java @@ -3,25 +3,25 @@ public class Issue2534 { - @IntRange(from = 0, to = Integer.MAX_VALUE) int field = 3; + @IntRange(from = 0, to = Integer.MAX_VALUE) int field = 3; - @IntRange(from = 0, to = Integer.MAX_VALUE) int qwe() { - return 3; - } + @IntRange(from = 0, to = Integer.MAX_VALUE) int qwe() { + return 3; + } - void m1() { - @Unsigned int c = qwe(); - } + void m1() { + @Unsigned int c = qwe(); + } - void m2() { - @Unsigned int c = field; - } + void m2() { + @Unsigned int c = field; + } - void m3() { - @Unsigned int c = this.field; - } + void m3() { + @Unsigned int c = this.field; + } - void m4(@IntRange(from = 0, to = Integer.MAX_VALUE) int array[]) { - @Unsigned int c = array[0]; - } + void m4(@IntRange(from = 0, to = Integer.MAX_VALUE) int array[]) { + @Unsigned int c = array[0]; + } } diff --git a/checker/tests/signedness/Issue2543.java b/checker/tests/signedness/Issue2543.java index d85e678097f..704ca5b6b2d 100644 --- a/checker/tests/signedness/Issue2543.java +++ b/checker/tests/signedness/Issue2543.java @@ -5,40 +5,41 @@ public class Issue2543 { - public static @PolySigned int rotateRightPart1(@PolySigned int i, int distance) { - // :: error: (shift.unsigned) - return i >>> distance; - } - - public static @PolySigned int rotateRightPart2(@PolySigned int i, int distance) { - return i << -distance; - } - - public static @PolySigned int rotateRight(@PolySigned int i, int distance) { - // :: error: (shift.unsigned) - return (i >>> distance) | (i << -distance); - } - - public static @Signed int rotateRightSignedPart1(@Signed int i, int distance) { - // :: error: (shift.unsigned) - return i >>> distance; - } - - public static @Signed int rotateRightSignedPart2(@Signed int i, int distance) { - return i << -distance; - } - - public static @Signed int rotateRightSigned(@Signed int i, int distance) { - // :: error: (shift.unsigned) - return (i >>> distance) | (i << -distance); - } - - public static @Unsigned int rotateRightUnsigned(@Unsigned int i, int distance) { - return (i >>> distance) | (i << -distance); - } - - public static @Unsigned int rotateRightUnknownSignedness(@UnknownSignedness int i, int distance) { - // :: error: (return.type.incompatible) - return (i >>> distance) | (i << -distance); - } + public static @PolySigned int rotateRightPart1(@PolySigned int i, int distance) { + // :: error: (shift.unsigned) + return i >>> distance; + } + + public static @PolySigned int rotateRightPart2(@PolySigned int i, int distance) { + return i << -distance; + } + + public static @PolySigned int rotateRight(@PolySigned int i, int distance) { + // :: error: (shift.unsigned) + return (i >>> distance) | (i << -distance); + } + + public static @Signed int rotateRightSignedPart1(@Signed int i, int distance) { + // :: error: (shift.unsigned) + return i >>> distance; + } + + public static @Signed int rotateRightSignedPart2(@Signed int i, int distance) { + return i << -distance; + } + + public static @Signed int rotateRightSigned(@Signed int i, int distance) { + // :: error: (shift.unsigned) + return (i >>> distance) | (i << -distance); + } + + public static @Unsigned int rotateRightUnsigned(@Unsigned int i, int distance) { + return (i >>> distance) | (i << -distance); + } + + public static @Unsigned int rotateRightUnknownSignedness( + @UnknownSignedness int i, int distance) { + // :: error: (return.type.incompatible) + return (i >>> distance) | (i << -distance); + } } diff --git a/checker/tests/signedness/Issue3710.java b/checker/tests/signedness/Issue3710.java index 4293121fd8c..138c32c95db 100644 --- a/checker/tests/signedness/Issue3710.java +++ b/checker/tests/signedness/Issue3710.java @@ -1,21 +1,21 @@ // Test case for issue #3711: https://github.com/typetools/checker-framework/issues/3710 public class Issue3710 { - int returnIntWithLocalVariable(char c) { - int i = c; - return i; - } + int returnIntWithLocalVariable(char c) { + int i = c; + return i; + } - long returnLongWithLocalVariable(char c) { - long l = c; - return l; - } + long returnLongWithLocalVariable(char c) { + long l = c; + return l; + } - int returnIntWithoutLocalVariable(char c) { - return c; - } + int returnIntWithoutLocalVariable(char c) { + return c; + } - long returnLongWithoutLocalVariable(char c) { - return c; - } + long returnLongWithoutLocalVariable(char c) { + return c; + } } diff --git a/checker/tests/signedness/Issue5256.java b/checker/tests/signedness/Issue5256.java index f598ccade7f..280115c1a44 100644 --- a/checker/tests/signedness/Issue5256.java +++ b/checker/tests/signedness/Issue5256.java @@ -1,16 +1,16 @@ final class Issue5256 { - char c; + char c; - public int foo1() { - int x = 1; - x = x + (int) c; - return x; - } + public int foo1() { + int x = 1; + x = x + (int) c; + return x; + } - public int foo2() { - char c = 65535; - int x = 1; - x = x + (int) c; - return x; - } + public int foo2() { + char c = 65535; + int x = 1; + x = x + (int) c; + return x; + } } diff --git a/checker/tests/signedness/JdkConstantsTest.java b/checker/tests/signedness/JdkConstantsTest.java index 27cb52cf368..2321d2ca4fb 100644 --- a/checker/tests/signedness/JdkConstantsTest.java +++ b/checker/tests/signedness/JdkConstantsTest.java @@ -2,13 +2,13 @@ public class JdkConstantsTest { - static @PolySigned int integerMinValue(@PolySigned int value) { - // :: error: (return.type.incompatible) - return Integer.MIN_VALUE; - } + static @PolySigned int integerMinValue(@PolySigned int value) { + // :: error: (return.type.incompatible) + return Integer.MIN_VALUE; + } - static @PolySigned int flip(@PolySigned int value) { - // :: error: (return.type.incompatible) - return value ^ Integer.MIN_VALUE; - } + static @PolySigned int flip(@PolySigned int value) { + // :: error: (return.type.incompatible) + return value ^ Integer.MIN_VALUE; + } } diff --git a/checker/tests/signedness/LiteralCast.java b/checker/tests/signedness/LiteralCast.java index f4372da8709..c1ec2552c68 100644 --- a/checker/tests/signedness/LiteralCast.java +++ b/checker/tests/signedness/LiteralCast.java @@ -1,72 +1,73 @@ -import java.util.Arrays; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.Unsigned; import org.checkerframework.checker.units.qual.m; +import java.util.Arrays; + public class LiteralCast { - @Unsigned int u; - @Signed int s; + @Unsigned int u; + @Signed int s; - void m() { - testCompile(2); - // manifest literals are treated as @SignednessGlb - testCompile(-2); - // :: error: (argument.type.incompatible) - testCompile((@Signed int) 2); - testCompile((@Unsigned int) 2); - testCompile((int) 2); - testCompile((@m int) 2); + void m() { + testCompile(2); + // manifest literals are treated as @SignednessGlb + testCompile(-2); + // :: error: (argument.type.incompatible) + testCompile((@Signed int) 2); + testCompile((@Unsigned int) 2); + testCompile((int) 2); + testCompile((@m int) 2); - requireSigned((@Signed int) 2); - // :: error: (argument.type.incompatible) - requireSigned((@Unsigned int) 2); - requireSigned((int) 2); - requireSigned((@m int) 2); - // :: warning: (cast.unsafe) - requireSigned((@Signed int) u); - // :: error: (argument.type.incompatible) - requireSigned((@Unsigned int) u); - // :: error: (argument.type.incompatible) - requireSigned((int) u); - // :: error: (argument.type.incompatible) - requireSigned((@m int) u); - requireSigned((@Signed int) s); - // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) - requireSigned((@Unsigned int) s); - requireSigned((int) s); - requireSigned((@m int) s); + requireSigned((@Signed int) 2); + // :: error: (argument.type.incompatible) + requireSigned((@Unsigned int) 2); + requireSigned((int) 2); + requireSigned((@m int) 2); + // :: warning: (cast.unsafe) + requireSigned((@Signed int) u); + // :: error: (argument.type.incompatible) + requireSigned((@Unsigned int) u); + // :: error: (argument.type.incompatible) + requireSigned((int) u); + // :: error: (argument.type.incompatible) + requireSigned((@m int) u); + requireSigned((@Signed int) s); + // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) + requireSigned((@Unsigned int) s); + requireSigned((int) s); + requireSigned((@m int) s); - // :: error: (argument.type.incompatible) - requireUnsigned((@Signed int) 2); - requireUnsigned((@Unsigned int) 2); - requireUnsigned((int) 2); - requireUnsigned((@m int) 2); - // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) - requireUnsigned((@Signed int) u); - requireUnsigned((@Unsigned int) u); - requireUnsigned((int) u); - requireUnsigned((@m int) u); - // :: error: (argument.type.incompatible) - requireUnsigned((@Signed int) s); - // :: warning: (cast.unsafe) - requireUnsigned((@Unsigned int) s); - // :: error: (argument.type.incompatible) - requireUnsigned((int) s); - // :: error: (argument.type.incompatible) - requireUnsigned((@m int) s); - } + // :: error: (argument.type.incompatible) + requireUnsigned((@Signed int) 2); + requireUnsigned((@Unsigned int) 2); + requireUnsigned((int) 2); + requireUnsigned((@m int) 2); + // :: error: (argument.type.incompatible) :: warning: (cast.unsafe) + requireUnsigned((@Signed int) u); + requireUnsigned((@Unsigned int) u); + requireUnsigned((int) u); + requireUnsigned((@m int) u); + // :: error: (argument.type.incompatible) + requireUnsigned((@Signed int) s); + // :: warning: (cast.unsafe) + requireUnsigned((@Unsigned int) s); + // :: error: (argument.type.incompatible) + requireUnsigned((int) s); + // :: error: (argument.type.incompatible) + requireUnsigned((@m int) s); + } - void requireSigned(@Signed int arg) {} + void requireSigned(@Signed int arg) {} - void requireUnsigned(@Unsigned int arg) {} + void requireUnsigned(@Unsigned int arg) {} - public static void testCompile(@Unsigned int x) { - @Unsigned int[] arr = {1, 2, 3, 4, 5, 56}; + public static void testCompile(@Unsigned int x) { + @Unsigned int[] arr = {1, 2, 3, 4, 5, 56}; - Arrays.fill(arr, x); - Arrays.fill(arr, (@Unsigned int) Integer.valueOf(-2)); - Arrays.fill(arr, Integer.valueOf(-2)); - Arrays.fill(arr, (@Unsigned int) Integer.valueOf(2)); - } + Arrays.fill(arr, x); + Arrays.fill(arr, (@Unsigned int) Integer.valueOf(-2)); + Arrays.fill(arr, Integer.valueOf(-2)); + Arrays.fill(arr, (@Unsigned int) Integer.valueOf(2)); + } } diff --git a/checker/tests/signedness/LocalVarDefaults.java b/checker/tests/signedness/LocalVarDefaults.java index a42237b57b9..60efe1b1081 100644 --- a/checker/tests/signedness/LocalVarDefaults.java +++ b/checker/tests/signedness/LocalVarDefaults.java @@ -3,25 +3,25 @@ public class LocalVarDefaults { - void methodInt(@Unsigned int unsignedInt, @Signed int signedInt) { - int local = unsignedInt; - int local2 = signedInt; - } + void methodInt(@Unsigned int unsignedInt, @Signed int signedInt) { + int local = unsignedInt; + int local2 = signedInt; + } - // :: error: (anno.on.irrelevant) - void methodDouble(@Unsigned double unsigned, @Signed double signed) { - double local = unsigned; - double local2 = signed; - } + // :: error: (anno.on.irrelevant) + void methodDouble(@Unsigned double unsigned, @Signed double signed) { + double local = unsigned; + double local2 = signed; + } - void methodInteger(@Unsigned Integer unsignedInt, @Signed Integer signedInt) { - Integer local = unsignedInt; - Integer local2 = signedInt; - } + void methodInteger(@Unsigned Integer unsignedInt, @Signed Integer signedInt) { + Integer local = unsignedInt; + Integer local2 = signedInt; + } - // :: error: (anno.on.irrelevant) - void methodDoubleWrapper(@Unsigned Double unsigned, @Signed Double signed) { - Double local = unsigned; - Double local2 = signed; - } + // :: error: (anno.on.irrelevant) + void methodDoubleWrapper(@Unsigned Double unsigned, @Signed Double signed) { + Double local = unsigned; + Double local2 = signed; + } } diff --git a/checker/tests/signedness/LowerUpperBound.java b/checker/tests/signedness/LowerUpperBound.java index a005aa268de..33e52cb3a3a 100644 --- a/checker/tests/signedness/LowerUpperBound.java +++ b/checker/tests/signedness/LowerUpperBound.java @@ -2,36 +2,36 @@ public class LowerUpperBound { - public void LowerUpperBoundTest( - @UnknownSignedness int unknown, - @Unsigned int unsigned, - @Signed int signed, - @SignednessGlb int constant) { + public void LowerUpperBoundTest( + @UnknownSignedness int unknown, + @Unsigned int unsigned, + @Signed int signed, + @SignednessGlb int constant) { - @UnknownSignedness int unkTest; - @Unsigned int unsTest; - @Signed int sinTest; - @SignednessGlb int conTest; - @SignednessBottom int botTest; + @UnknownSignedness int unkTest; + @Unsigned int unsTest; + @Signed int sinTest; + @SignednessGlb int conTest; + @SignednessBottom int botTest; - unkTest = unknown + unknown; + unkTest = unknown + unknown; - // :: error: (assignment.type.incompatible) - sinTest = unknown + unknown; + // :: error: (assignment.type.incompatible) + sinTest = unknown + unknown; - unkTest = unknown + signed; + unkTest = unknown + signed; - // :: error: (assignment.type.incompatible) - sinTest = unknown + signed; + // :: error: (assignment.type.incompatible) + sinTest = unknown + signed; - sinTest = signed + signed; + sinTest = signed + signed; - // :: error: (assignment.type.incompatible) - conTest = signed + signed; + // :: error: (assignment.type.incompatible) + conTest = signed + signed; - sinTest = signed + constant; + sinTest = signed + constant; - // :: error: (assignment.type.incompatible) - conTest = signed + constant; - } + // :: error: (assignment.type.incompatible) + conTest = signed + constant; + } } diff --git a/checker/tests/signedness/MaskedShifts.java b/checker/tests/signedness/MaskedShifts.java index 2bedaa100b0..e4609faaf9f 100644 --- a/checker/tests/signedness/MaskedShifts.java +++ b/checker/tests/signedness/MaskedShifts.java @@ -2,364 +2,364 @@ public class MaskedShifts { - public void MaskedAndShifts(@Unsigned int unsigned, @Signed int signed) { + public void MaskedAndShifts(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // Use mask that renders the 9 MSB_s irrelevant. + // Use mask that renders the 9 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are masked away - testRes = (unsigned >>> 8) & 0x7FFFFF; - testRes = (unsigned >> 8) & 0x7FFFFF; - testRes = (signed >>> 8) & 0x7FFFFF; - testRes = (signed >> 8) & 0x7FFFFF; + // Shifting right by 8, the introduced bits are masked away + testRes = (unsigned >>> 8) & 0x7FFFFF; + testRes = (unsigned >> 8) & 0x7FFFFF; + testRes = (signed >>> 8) & 0x7FFFFF; + testRes = (signed >> 8) & 0x7FFFFF; - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = (unsigned >>> 8) & 0xFFFFFF; - testRes = (unsigned >> 8) & 0xFFFFFF; - testRes = (signed >>> 8) & 0xFFFFFF; - testRes = (signed >> 8) & 0xFFFFFF; + // Shifting right by 8, the introduced bits are still masked away. + testRes = (unsigned >>> 8) & 0xFFFFFF; + testRes = (unsigned >> 8) & 0xFFFFFF; + testRes = (signed >>> 8) & 0xFFFFFF; + testRes = (signed >> 8) & 0xFFFFFF; - // Use mask that renders the 7 MSB_s irrelevant + // Use mask that renders the 7 MSB_s irrelevant - // Now the right-most introduced bit matters - testRes = (unsigned >>> 8) & 0x1FFFFFF; + // Now the right-most introduced bit matters + testRes = (unsigned >>> 8) & 0x1FFFFFF; - // :: error: (shift.signed) - testRes = (unsigned >> 8) & 0x1FFFFFF; + // :: error: (shift.signed) + testRes = (unsigned >> 8) & 0x1FFFFFF; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) & 0x1FFFFFF; - testRes = (signed >> 8) & 0x1FFFFFF; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) & 0x1FFFFFF; + testRes = (signed >> 8) & 0x1FFFFFF; - // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s - // irrelevant. + // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s + // irrelevant. - // Now the left-most introduced bit matters - testRes = (unsigned >>> 8) & 0x90FFFFFF; + // Now the left-most introduced bit matters + testRes = (unsigned >>> 8) & 0x90FFFFFF; - // :: error: (shift.signed) - testRes = (unsigned >> 8) & 0x90FFFFFF; + // :: error: (shift.signed) + testRes = (unsigned >> 8) & 0x90FFFFFF; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) & 0x90FFFFFF; - testRes = (signed >> 8) & 0x90FFFFFF; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) & 0x90FFFFFF; + testRes = (signed >> 8) & 0x90FFFFFF; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = (unsigned >>> 8) & 0xFFFFFFFF; + testRes = (unsigned >>> 8) & 0xFFFFFFFF; - // :: error: (shift.signed) - testRes = (unsigned >> 8) & 0xFFFFFFFF; + // :: error: (shift.signed) + testRes = (unsigned >> 8) & 0xFFFFFFFF; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) & 0xFFFFFFFF; - testRes = (signed >> 8) & 0xFFFFFFFF; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) & 0xFFFFFFFF; + testRes = (signed >> 8) & 0xFFFFFFFF; - // Tests with no parenthesis (only 8 and 0 MSB_s) + // Tests with no parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 & 0xFFFFFF; - testRes = unsigned >> 8 & 0xFFFFFF; - testRes = signed >>> 8 & 0xFFFFFF; - testRes = signed >> 8 & 0xFFFFFF; + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 & 0xFFFFFF; + testRes = unsigned >> 8 & 0xFFFFFF; + testRes = signed >>> 8 & 0xFFFFFF; + testRes = signed >> 8 & 0xFFFFFF; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 & 0xFFFFFFFF; + testRes = unsigned >>> 8 & 0xFFFFFFFF; - // :: error: (shift.signed) - testRes = unsigned >> 8 & 0xFFFFFFFF; + // :: error: (shift.signed) + testRes = unsigned >> 8 & 0xFFFFFFFF; - // :: error: (shift.unsigned) - testRes = signed >>> 8 & 0xFFFFFFFF; - testRes = signed >> 8 & 0xFFFFFFFF; + // :: error: (shift.unsigned) + testRes = signed >>> 8 & 0xFFFFFFFF; + testRes = signed >> 8 & 0xFFFFFFFF; - // Tests with double parenthesis (only 8 and 0 MSB_s) + // Tests with double parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = ((unsigned >>> 8)) & 0xFFFFFF; - testRes = ((unsigned >> 8)) & 0xFFFFFF; - testRes = ((signed >>> 8)) & 0xFFFFFF; - testRes = ((signed >> 8)) & 0xFFFFFF; + // Shifting right by 8, the introduced bits are still masked away. + testRes = ((unsigned >>> 8)) & 0xFFFFFF; + testRes = ((unsigned >> 8)) & 0xFFFFFF; + testRes = ((signed >>> 8)) & 0xFFFFFF; + testRes = ((signed >> 8)) & 0xFFFFFF; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = ((unsigned >>> 8)) & 0xFFFFFFFF; + testRes = ((unsigned >>> 8)) & 0xFFFFFFFF; - // :: error: (shift.signed) - testRes = ((unsigned >> 8)) & 0xFFFFFFFF; + // :: error: (shift.signed) + testRes = ((unsigned >> 8)) & 0xFFFFFFFF; - // :: error: (shift.unsigned) - testRes = ((signed >>> 8)) & 0xFFFFFFFF; - testRes = ((signed >> 8)) & 0xFFFFFFFF; + // :: error: (shift.unsigned) + testRes = ((signed >>> 8)) & 0xFFFFFFFF; + testRes = ((signed >> 8)) & 0xFFFFFFFF; - // Tests shift on right (only 8 and 0 MSB_s) + // Tests shift on right (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = 0xFFFFFF & (unsigned >>> 8); - testRes = 0xFFFFFF & (unsigned >> 8); - testRes = 0xFFFFFF & (signed >>> 8); - testRes = 0xFFFFFF & (signed >> 8); + // Shifting right by 8, the introduced bits are still masked away. + testRes = 0xFFFFFF & (unsigned >>> 8); + testRes = 0xFFFFFF & (unsigned >> 8); + testRes = 0xFFFFFF & (signed >>> 8); + testRes = 0xFFFFFF & (signed >> 8); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = 0xFFFFFFFF & (unsigned >>> 8); + testRes = 0xFFFFFFFF & (unsigned >>> 8); - // :: error: (shift.signed) - testRes = 0xFFFFFFFF & (unsigned >> 8); + // :: error: (shift.signed) + testRes = 0xFFFFFFFF & (unsigned >> 8); - // :: error: (shift.unsigned) - testRes = 0xFFFFFFFF & (signed >>> 8); - testRes = 0xFFFFFFFF & (signed >> 8); + // :: error: (shift.unsigned) + testRes = 0xFFFFFFFF & (signed >>> 8); + testRes = 0xFFFFFFFF & (signed >> 8); - // Tests shift on right (only 8 and 0 MSB_s), with no parenthesis on right + // Tests shift on right (only 8 and 0 MSB_s), with no parenthesis on right - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = 0xFFFFFF & unsigned >>> 8; - testRes = 0xFFFFFF & unsigned >> 8; - testRes = 0xFFFFFF & signed >>> 8; - testRes = 0xFFFFFF & signed >> 8; + // Shifting right by 8, the introduced bits are still masked away. + testRes = 0xFFFFFF & unsigned >>> 8; + testRes = 0xFFFFFF & unsigned >> 8; + testRes = 0xFFFFFF & signed >>> 8; + testRes = 0xFFFFFF & signed >> 8; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = 0xFFFFFFFF & unsigned >>> 8; + testRes = 0xFFFFFFFF & unsigned >>> 8; - // :: error: (shift.signed) - testRes = 0xFFFFFFFF & unsigned >> 8; + // :: error: (shift.signed) + testRes = 0xFFFFFFFF & unsigned >> 8; - // :: error: (shift.unsigned) - testRes = 0xFFFFFFFF & signed >>> 8; - testRes = 0xFFFFFFFF & signed >> 8; + // :: error: (shift.unsigned) + testRes = 0xFFFFFFFF & signed >>> 8; + testRes = 0xFFFFFFFF & signed >> 8; - // Tests with parenthesis on mask (only 8 and 0 MSB_s) + // Tests with parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 & (0xFFFFFF); - testRes = unsigned >> 8 & (0xFFFFFF); - testRes = signed >>> 8 & (0xFFFFFF); - testRes = signed >> 8 & (0xFFFFFF); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 & (0xFFFFFF); + testRes = unsigned >> 8 & (0xFFFFFF); + testRes = signed >>> 8 & (0xFFFFFF); + testRes = signed >> 8 & (0xFFFFFF); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 & (0xFFFFFFFF); + testRes = unsigned >>> 8 & (0xFFFFFFFF); - // :: error: (shift.signed) - testRes = unsigned >> 8 & (0xFFFFFFFF); + // :: error: (shift.signed) + testRes = unsigned >> 8 & (0xFFFFFFFF); - // :: error: (shift.unsigned) - testRes = signed >>> 8 & (0xFFFFFFFF); - testRes = signed >> 8 & (0xFFFFFFFF); + // :: error: (shift.unsigned) + testRes = signed >>> 8 & (0xFFFFFFFF); + testRes = signed >> 8 & (0xFFFFFFFF); - // Tests with double parenthesis on mask (only 8 and 0 MSB_s) + // Tests with double parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 & ((0xFFFFFF)); - testRes = unsigned >> 8 & ((0xFFFFFF)); - testRes = signed >>> 8 & ((0xFFFFFF)); - testRes = signed >> 8 & ((0xFFFFFF)); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 & ((0xFFFFFF)); + testRes = unsigned >> 8 & ((0xFFFFFF)); + testRes = signed >>> 8 & ((0xFFFFFF)); + testRes = signed >> 8 & ((0xFFFFFF)); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 & ((0xFFFFFFFF)); + testRes = unsigned >>> 8 & ((0xFFFFFFFF)); - // :: error: (shift.signed) - testRes = unsigned >> 8 & ((0xFFFFFFFF)); + // :: error: (shift.signed) + testRes = unsigned >> 8 & ((0xFFFFFFFF)); - // :: error: (shift.unsigned) - testRes = signed >>> 8 & ((0xFFFFFFFF)); - testRes = signed >> 8 & ((0xFFFFFFFF)); - } + // :: error: (shift.unsigned) + testRes = signed >>> 8 & ((0xFFFFFFFF)); + testRes = signed >> 8 & ((0xFFFFFFFF)); + } - public void MaskedOrShifts(@Unsigned int unsigned, @Signed int signed) { + public void MaskedOrShifts(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // Use mask that renders the 9 MSB_s irrelevant. + // Use mask that renders the 9 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are masked away. - testRes = (unsigned >>> 8) | 0xFF800000; - testRes = (unsigned >> 8) | 0xFF800000; - testRes = (signed >>> 8) | 0xFF800000; - testRes = (signed >> 8) | 0xFF800000; + // Shifting right by 8, the introduced bits are masked away. + testRes = (unsigned >>> 8) | 0xFF800000; + testRes = (unsigned >> 8) | 0xFF800000; + testRes = (signed >>> 8) | 0xFF800000; + testRes = (signed >> 8) | 0xFF800000; - // Use mask that render ths 8 MSB_s irrelevant. + // Use mask that render ths 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = (unsigned >>> 8) | 0xFF000000; - testRes = (unsigned >> 8) | 0xFF000000; - testRes = (signed >>> 8) | 0xFF000000; - testRes = (signed >> 8) | 0xFF000000; + // Shifting right by 8, the introduced bits are still masked away. + testRes = (unsigned >>> 8) | 0xFF000000; + testRes = (unsigned >> 8) | 0xFF000000; + testRes = (signed >>> 8) | 0xFF000000; + testRes = (signed >> 8) | 0xFF000000; - // Use mask that renders the 7 MSB_s irrelevant. + // Use mask that renders the 7 MSB_s irrelevant. - // The right-most introduced bit now matters. - testRes = (unsigned >>> 8) | 0xFE000000; + // The right-most introduced bit now matters. + testRes = (unsigned >>> 8) | 0xFE000000; - // :: error: (shift.signed) - testRes = (unsigned >> 8) | 0xFE000000; + // :: error: (shift.signed) + testRes = (unsigned >> 8) | 0xFE000000; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) | 0xFE000000; - testRes = (signed >> 8) | 0xFE000000; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) | 0xFE000000; + testRes = (signed >> 8) | 0xFE000000; - // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s - // irrelevant. + // Use mask that doesn't render the MSB irrelevant, but does render the next 7 MSB_s + // irrelevant. - // Now the left-most introduced bit matters - testRes = (unsigned >>> 8) | 0x8F000000; + // Now the left-most introduced bit matters + testRes = (unsigned >>> 8) | 0x8F000000; - // :: error: (shift.signed) - testRes = (unsigned >> 8) | 0x8F000000; + // :: error: (shift.signed) + testRes = (unsigned >> 8) | 0x8F000000; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) | 0x8F000000; - testRes = (signed >> 8) | 0x8F000000; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) | 0x8F000000; + testRes = (signed >> 8) | 0x8F000000; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = (unsigned >>> 8) | 0x0; + testRes = (unsigned >>> 8) | 0x0; - // :: error: (shift.signed) - testRes = (unsigned >> 8) | 0x0; + // :: error: (shift.signed) + testRes = (unsigned >> 8) | 0x0; - // :: error: (shift.unsigned) - testRes = (signed >>> 8) | 0x0; - testRes = (signed >> 8) | 0x0; + // :: error: (shift.unsigned) + testRes = (signed >>> 8) | 0x0; + testRes = (signed >> 8) | 0x0; - // Tests with no parenthesis (only 8 and 0 MSB_s) + // Tests with no parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 | 0xFF000000; - testRes = unsigned >> 8 | 0xFF000000; - testRes = signed >>> 8 | 0xFF000000; - testRes = signed >> 8 | 0xFF000000; + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 | 0xFF000000; + testRes = unsigned >> 8 | 0xFF000000; + testRes = signed >>> 8 | 0xFF000000; + testRes = signed >> 8 | 0xFF000000; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 | 0x0; + testRes = unsigned >>> 8 | 0x0; - // :: error: (shift.signed) - testRes = unsigned >> 8 | 0x0; + // :: error: (shift.signed) + testRes = unsigned >> 8 | 0x0; - // :: error: (shift.unsigned) - testRes = signed >>> 8 | 0x0; - testRes = signed >> 8 | 0x0; + // :: error: (shift.unsigned) + testRes = signed >>> 8 | 0x0; + testRes = signed >> 8 | 0x0; - // Tests with double parenthesis (only 8 and 0 MSB_s) + // Tests with double parenthesis (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = ((unsigned >>> 8)) | 0xFF000000; - testRes = ((unsigned >> 8)) | 0xFF000000; - testRes = ((signed >>> 8)) | 0xFF000000; - testRes = ((signed >> 8)) | 0xFF000000; + // Shifting right by 8, the introduced bits are still masked away. + testRes = ((unsigned >>> 8)) | 0xFF000000; + testRes = ((unsigned >> 8)) | 0xFF000000; + testRes = ((signed >>> 8)) | 0xFF000000; + testRes = ((signed >> 8)) | 0xFF000000; - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = ((unsigned >>> 8)) | 0x0; + testRes = ((unsigned >>> 8)) | 0x0; - // :: error: (shift.signed) - testRes = ((unsigned >> 8)) | 0x0; + // :: error: (shift.signed) + testRes = ((unsigned >> 8)) | 0x0; - // :: error: (shift.unsigned) - testRes = ((signed >>> 8)) | 0x0; - testRes = ((signed >> 8)) | 0x0; + // :: error: (shift.unsigned) + testRes = ((signed >>> 8)) | 0x0; + testRes = ((signed >> 8)) | 0x0; - // Tests shift on right (only 8 and 0 MSB_s) + // Tests shift on right (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = 0xFF000000 | (unsigned >>> 8); - testRes = 0xFF000000 | (unsigned >> 8); - testRes = 0xFF000000 | (signed >>> 8); - testRes = 0xFF000000 | (signed >> 8); + // Shifting right by 8, the introduced bits are still masked away. + testRes = 0xFF000000 | (unsigned >>> 8); + testRes = 0xFF000000 | (unsigned >> 8); + testRes = 0xFF000000 | (signed >>> 8); + testRes = 0xFF000000 | (signed >> 8); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = 0x0 | (unsigned >>> 8); + testRes = 0x0 | (unsigned >>> 8); - // :: error: (shift.signed) - testRes = 0x0 | (unsigned >> 8); + // :: error: (shift.signed) + testRes = 0x0 | (unsigned >> 8); - // :: error: (shift.unsigned) - testRes = 0x0 | (signed >>> 8); - testRes = 0x0 | (signed >> 8); + // :: error: (shift.unsigned) + testRes = 0x0 | (signed >>> 8); + testRes = 0x0 | (signed >> 8); - // Tests with parenthesis on mask (only 8 and 0 MSB_s) + // Tests with parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 | (0xFF000000); - testRes = unsigned >> 8 | (0xFF000000); - testRes = signed >>> 8 | (0xFF000000); - testRes = signed >> 8 | (0xFF000000); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 | (0xFF000000); + testRes = unsigned >> 8 | (0xFF000000); + testRes = signed >>> 8 | (0xFF000000); + testRes = signed >> 8 | (0xFF000000); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 | (0x0); + testRes = unsigned >>> 8 | (0x0); - // :: error: (shift.signed) - testRes = unsigned >> 8 | (0x0); + // :: error: (shift.signed) + testRes = unsigned >> 8 | (0x0); - // :: error: (shift.unsigned) - testRes = signed >>> 8 | (0x0); - testRes = signed >> 8 | (0x0); + // :: error: (shift.unsigned) + testRes = signed >>> 8 | (0x0); + testRes = signed >> 8 | (0x0); - // Tests with double parenthesis on mask (only 8 and 0 MSB_s) + // Tests with double parenthesis on mask (only 8 and 0 MSB_s) - // Use mask that renders the 8 MSB_s irrelevant. + // Use mask that renders the 8 MSB_s irrelevant. - // Shifting right by 8, the introduced bits are still masked away. - testRes = unsigned >>> 8 | ((0xFF000000)); - testRes = unsigned >> 8 | ((0xFF000000)); - testRes = signed >>> 8 | ((0xFF000000)); - testRes = signed >> 8 | ((0xFF000000)); + // Shifting right by 8, the introduced bits are still masked away. + testRes = unsigned >>> 8 | ((0xFF000000)); + testRes = unsigned >> 8 | ((0xFF000000)); + testRes = signed >>> 8 | ((0xFF000000)); + testRes = signed >> 8 | ((0xFF000000)); - // Use mask that doesn't render any bits irrelevant + // Use mask that doesn't render any bits irrelevant - testRes = unsigned >>> 8 | ((0x0)); + testRes = unsigned >>> 8 | ((0x0)); - // :: error: (shift.signed) - testRes = unsigned >> 8 | ((0x0)); + // :: error: (shift.signed) + testRes = unsigned >> 8 | ((0x0)); - // :: error: (shift.unsigned) - testRes = signed >>> 8 | ((0x0)); - testRes = signed >> 8 | ((0x0)); - } + // :: error: (shift.unsigned) + testRes = signed >>> 8 | ((0x0)); + testRes = signed >> 8 | ((0x0)); + } - public void ZeroShiftTests(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + public void ZeroShiftTests(@Unsigned int unsigned, @Signed int signed) { + @UnknownSignedness int testRes; - // Tests shift by zero followed by "and" mask - testRes = (unsigned >>> 0) & 0xFFFFFFFF; - testRes = (unsigned >> 0) & 0xFFFFFFFF; - testRes = (signed >>> 0) & 0xFFFFFFFF; - testRes = (signed >> 0) & 0xFFFFFFFF; + // Tests shift by zero followed by "and" mask + testRes = (unsigned >>> 0) & 0xFFFFFFFF; + testRes = (unsigned >> 0) & 0xFFFFFFFF; + testRes = (signed >>> 0) & 0xFFFFFFFF; + testRes = (signed >> 0) & 0xFFFFFFFF; - // Tests shift by zero followed by "or" mask - testRes = (unsigned >>> 0) | 0x0; - testRes = (unsigned >> 0) | 0x0; - testRes = (signed >>> 0) | 0x0; - testRes = (signed >> 0) | 0x0; - } + // Tests shift by zero followed by "or" mask + testRes = (unsigned >>> 0) | 0x0; + testRes = (unsigned >> 0) | 0x0; + testRes = (signed >>> 0) | 0x0; + testRes = (signed >> 0) | 0x0; + } } diff --git a/checker/tests/signedness/ObjectCasts.java b/checker/tests/signedness/ObjectCasts.java index d59dff45944..9537a197421 100644 --- a/checker/tests/signedness/ObjectCasts.java +++ b/checker/tests/signedness/ObjectCasts.java @@ -5,103 +5,103 @@ public class ObjectCasts { - Integer castObjectToInteger1(Object o) { - return (Integer) o; - } - - Integer castObjectToInteger2(@Unsigned Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - Integer castObjectToInteger3(@Signed Object o) { - return (Integer) o; - } - - @Signed Integer castObjectToInteger4(Object o) { - return (Integer) o; - } - - @Signed Integer castObjectToInteger5(@Unsigned Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - @Signed Integer castObjectToInteger6(@Signed Object o) { - return (Integer) o; - } - - @Unsigned Integer castObjectToInteger7(Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - @Unsigned Integer castObjectToInteger8(@Unsigned Object o) { - return (Integer) o; - } - - @Unsigned Integer castObjectToInteger9(@Signed Object o) { - // :: error: (return.type.incompatible) - return (Integer) o; - } - - Object castIntegerToObject1(Integer o) { - return (Object) o; - } - - Object castIntegerToObject2(@Unsigned Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - Object castIntegerToObject3(@Signed Integer o) { - return (Object) o; - } - - @Signed Object castIntegerToObject4(Integer o) { - return (Object) o; - } - - @Signed Object castIntegerToObject5(@Unsigned Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - @Signed Object castIntegerToObject6(@Signed Integer o) { - return (Object) o; - } - - @Unsigned Object castIntegerToObject7(Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - @Unsigned Object castIntegerToObject8(@Unsigned Integer o) { - return (Object) o; - } - - @Unsigned Object castIntegerToObject9(@Signed Integer o) { - // :: error: (return.type.incompatible) - return (Object) o; - } - - void castObjectToBoxedVariants() { - byte b1 = 1; - short s1 = 1; - int i1 = 1; - long l1 = 1; - Object[] obj = new Object[] {b1, s1, i1, l1}; - byteParameter((Byte) obj[0]); - shortParameter((Short) obj[1]); - integralParameter((Integer) obj[2]); - longParameter((Long) obj[3]); - } - - void byteParameter(byte b) {} - - void shortParameter(short s) {} - - void integralParameter(int i) {} - - void longParameter(long l) {} + Integer castObjectToInteger1(Object o) { + return (Integer) o; + } + + Integer castObjectToInteger2(@Unsigned Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + Integer castObjectToInteger3(@Signed Object o) { + return (Integer) o; + } + + @Signed Integer castObjectToInteger4(Object o) { + return (Integer) o; + } + + @Signed Integer castObjectToInteger5(@Unsigned Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + @Signed Integer castObjectToInteger6(@Signed Object o) { + return (Integer) o; + } + + @Unsigned Integer castObjectToInteger7(Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + @Unsigned Integer castObjectToInteger8(@Unsigned Object o) { + return (Integer) o; + } + + @Unsigned Integer castObjectToInteger9(@Signed Object o) { + // :: error: (return.type.incompatible) + return (Integer) o; + } + + Object castIntegerToObject1(Integer o) { + return (Object) o; + } + + Object castIntegerToObject2(@Unsigned Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + Object castIntegerToObject3(@Signed Integer o) { + return (Object) o; + } + + @Signed Object castIntegerToObject4(Integer o) { + return (Object) o; + } + + @Signed Object castIntegerToObject5(@Unsigned Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + @Signed Object castIntegerToObject6(@Signed Integer o) { + return (Object) o; + } + + @Unsigned Object castIntegerToObject7(Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + @Unsigned Object castIntegerToObject8(@Unsigned Integer o) { + return (Object) o; + } + + @Unsigned Object castIntegerToObject9(@Signed Integer o) { + // :: error: (return.type.incompatible) + return (Object) o; + } + + void castObjectToBoxedVariants() { + byte b1 = 1; + short s1 = 1; + int i1 = 1; + long l1 = 1; + Object[] obj = new Object[] {b1, s1, i1, l1}; + byteParameter((Byte) obj[0]); + shortParameter((Short) obj[1]); + integralParameter((Integer) obj[2]); + longParameter((Long) obj[3]); + } + + void byteParameter(byte b) {} + + void shortParameter(short s) {} + + void integralParameter(int i) {} + + void longParameter(long l) {} } diff --git a/checker/tests/signedness/Operations.java b/checker/tests/signedness/Operations.java index 8dd068dff58..d5311b6dd95 100644 --- a/checker/tests/signedness/Operations.java +++ b/checker/tests/signedness/Operations.java @@ -2,77 +2,77 @@ public class Operations { - public void DivModTest(@Unsigned int unsigned) { + public void DivModTest(@Unsigned int unsigned) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (operation.unsignedlhs) - testRes = unsigned / 1; + // :: error: (operation.unsignedlhs) + testRes = unsigned / 1; - // :: error: (operation.unsignedrhs) - testRes = 1 / unsigned; + // :: error: (operation.unsignedrhs) + testRes = 1 / unsigned; - // :: error: (operation.unsignedlhs) - testRes = unsigned % 1; + // :: error: (operation.unsignedlhs) + testRes = unsigned % 1; - // :: error: (operation.unsignedrhs) - testRes = 1 % unsigned; - } + // :: error: (operation.unsignedrhs) + testRes = 1 % unsigned; + } - public void SignedRightShiftTest(@Unsigned int unsigned) { + public void SignedRightShiftTest(@Unsigned int unsigned) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (shift.signed) - testRes = unsigned >> 1; - } + // :: error: (shift.signed) + testRes = unsigned >> 1; + } - public void UnsignedRightShiftTest(@Signed int signed) { + public void UnsignedRightShiftTest(@Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (shift.unsigned) - testRes = signed >>> 1; - } + // :: error: (shift.unsigned) + testRes = signed >>> 1; + } - public void BinaryOperationTest(@Unsigned int unsigned, @Signed int signed) { + public void BinaryOperationTest(@Unsigned int unsigned, @Signed int signed) { - @UnknownSignedness int testRes; + @UnknownSignedness int testRes; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned * signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned * signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed * unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed * unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned + signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned + signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed + unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed + unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned - signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned - signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed - unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed - unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned & signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned & signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed & unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed & unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned ^ signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned ^ signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed ^ unsigned; + // :: error: (operation.mixed.unsignedrhs) + testRes = signed ^ unsigned; - // :: error: (operation.mixed.unsignedlhs) - testRes = unsigned | signed; + // :: error: (operation.mixed.unsignedlhs) + testRes = unsigned | signed; - // :: error: (operation.mixed.unsignedrhs) - testRes = signed | unsigned; - } + // :: error: (operation.mixed.unsignedrhs) + testRes = signed | unsigned; + } } diff --git a/checker/tests/signedness/PolymorphicReturnType.java b/checker/tests/signedness/PolymorphicReturnType.java index 93ba4c48ceb..b513a150fab 100644 --- a/checker/tests/signedness/PolymorphicReturnType.java +++ b/checker/tests/signedness/PolymorphicReturnType.java @@ -5,8 +5,8 @@ public class PolymorphicReturnType { - public @PolySigned byte get() { - // :: error: (return.type.incompatible) - return 0; - } + public @PolySigned byte get() { + // :: error: (return.type.incompatible) + return 0; + } } diff --git a/checker/tests/signedness/PrimitiveCasts.java b/checker/tests/signedness/PrimitiveCasts.java index 0b62c101d5b..515fc2589ba 100644 --- a/checker/tests/signedness/PrimitiveCasts.java +++ b/checker/tests/signedness/PrimitiveCasts.java @@ -2,39 +2,39 @@ public class PrimitiveCasts { - void shortToChar1(short s) { - char c = (char) s; - } - - // These are Java errors. - // void shortToChar2(short s) { - // char c = s; - // } - // char shortToChar3(short s) { - // return s; - // } - - void intToDouble1(@Unsigned int ui) { - double d = (double) ui; - } - - void intToDouble2(@Unsigned int ui) { - double d = ui; - } - - double intToDouble3(@Unsigned int ui) { - return ui; - } - - void shortToDouble1(@Unsigned short ui) { - double d = (double) ui; - } - - void shortToDouble2(@Unsigned short ui) { - double d = ui; - } - - double shortToDouble3(@Unsigned short ui) { - return ui; - } + void shortToChar1(short s) { + char c = (char) s; + } + + // These are Java errors. + // void shortToChar2(short s) { + // char c = s; + // } + // char shortToChar3(short s) { + // return s; + // } + + void intToDouble1(@Unsigned int ui) { + double d = (double) ui; + } + + void intToDouble2(@Unsigned int ui) { + double d = ui; + } + + double intToDouble3(@Unsigned int ui) { + return ui; + } + + void shortToDouble1(@Unsigned short ui) { + double d = (double) ui; + } + + void shortToDouble2(@Unsigned short ui) { + double d = ui; + } + + double shortToDouble3(@Unsigned short ui) { + return ui; + } } diff --git a/checker/tests/signedness/RestrictedPolymorphism.java b/checker/tests/signedness/RestrictedPolymorphism.java index c86293fe684..7658afc4f36 100644 --- a/checker/tests/signedness/RestrictedPolymorphism.java +++ b/checker/tests/signedness/RestrictedPolymorphism.java @@ -4,17 +4,17 @@ public class RestrictedPolymorphism { - @Signed Number sn; - @Unsigned Number un; + @Signed Number sn; + @Unsigned Number un; - public void foo(@PolySigned Object a, @PolySigned Object b) {} + public void foo(@PolySigned Object a, @PolySigned Object b) {} - void client() { - foo(sn, sn); - // :: error: (argument.type.incompatible) - foo(sn, un); - // :: error: (argument.type.incompatible) - foo(un, sn); - foo(un, un); - } + void client() { + foo(sn, sn); + // :: error: (argument.type.incompatible) + foo(sn, un); + // :: error: (argument.type.incompatible) + foo(un, sn); + foo(un, un); + } } diff --git a/checker/tests/signedness/ShiftAndMask.java b/checker/tests/signedness/ShiftAndMask.java index 95a7e1067ae..f287dfafca7 100644 --- a/checker/tests/signedness/ShiftAndMask.java +++ b/checker/tests/signedness/ShiftAndMask.java @@ -1,7 +1,7 @@ public class ShiftAndMask { - void m(long longValue) { - byte b1 = (byte) ((longValue >>> 32) & 0xFF); - byte b2 = (byte) ((longValue >>> 40) & 0xFF); - } + void m(long longValue) { + byte b1 = (byte) ((longValue >>> 32) & 0xFF); + byte b2 = (byte) ((longValue >>> 40) & 0xFF); + } } diff --git a/checker/tests/signedness/ShiftPropogation.java b/checker/tests/signedness/ShiftPropogation.java index f84ea5a42da..dbaf3866ed4 100644 --- a/checker/tests/signedness/ShiftPropogation.java +++ b/checker/tests/signedness/ShiftPropogation.java @@ -2,29 +2,29 @@ public class ShiftPropogation { - public void ShiftOperationTests(@Unsigned int unsigned, @Signed int signed) { - @Unsigned int uur = unsigned >>> unsigned; - @Unsigned int usr = unsigned >>> signed; + public void ShiftOperationTests(@Unsigned int unsigned, @Signed int signed) { + @Unsigned int uur = unsigned >>> unsigned; + @Unsigned int usr = unsigned >>> signed; - @Signed int sur = signed >> unsigned; - @Signed int ssr = signed >> signed; + @Signed int sur = signed >> unsigned; + @Signed int ssr = signed >> signed; - @Unsigned int uul = unsigned << unsigned; - @Unsigned int usl = unsigned << signed; - @Signed int sul = signed << unsigned; - @Signed int ssl = signed << signed; - } + @Unsigned int uul = unsigned << unsigned; + @Unsigned int usl = unsigned << signed; + @Signed int sul = signed << unsigned; + @Signed int ssl = signed << signed; + } - public void ShiftAssignmentTests(@Unsigned int unsigned, @Signed int signed) { - @Unsigned int uur = unsigned >>>= unsigned; - @Unsigned int usr = unsigned >>>= signed; + public void ShiftAssignmentTests(@Unsigned int unsigned, @Signed int signed) { + @Unsigned int uur = unsigned >>>= unsigned; + @Unsigned int usr = unsigned >>>= signed; - @Signed int sur = signed >>= unsigned; - @Signed int ssr = signed >>= signed; + @Signed int sur = signed >>= unsigned; + @Signed int ssr = signed >>= signed; - @Unsigned int uul = unsigned <<= unsigned; - @Unsigned int usl = unsigned <<= signed; - @Signed int sul = signed <<= unsigned; - @Signed int ssl = signed <<= signed; - } + @Unsigned int uul = unsigned <<= unsigned; + @Unsigned int usl = unsigned <<= signed; + @Signed int sul = signed <<= unsigned; + @Signed int ssl = signed <<= signed; + } } diff --git a/checker/tests/signedness/SignednessAnnotationError.java b/checker/tests/signedness/SignednessAnnotationError.java index 6a0b4a09d17..6ce74cfd5ac 100644 --- a/checker/tests/signedness/SignednessAnnotationError.java +++ b/checker/tests/signedness/SignednessAnnotationError.java @@ -1,10 +1,11 @@ +import org.checkerframework.checker.nullness.qual.KeyFor; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.KeyFor; public class SignednessAnnotationError { - void test() { - List<@KeyFor("hello") String> s = new ArrayList<>(); - @KeyFor("hell") Object o = new Object(); - } + void test() { + List<@KeyFor("hello") String> s = new ArrayList<>(); + @KeyFor("hell") Object o = new Object(); + } } diff --git a/checker/tests/signedness/SignednessAssignments.java b/checker/tests/signedness/SignednessAssignments.java index e8c726b8301..73d32d18cd7 100644 --- a/checker/tests/signedness/SignednessAssignments.java +++ b/checker/tests/signedness/SignednessAssignments.java @@ -4,133 +4,133 @@ public class SignednessAssignments { - @Signed byte sb; - @Unsigned byte ub; - @Signed Byte sB; - @Unsigned Byte uB; - - @Signed short ss; - @Unsigned short us; - @Signed Short sS; - @Unsigned Short uS; - - @Signed int si; - @Unsigned int ui; - @Signed Integer sI; - @Unsigned Integer uI; - - @Signed long sl; - @Unsigned long ul; - @Signed Long sL; - @Unsigned Long uL; - - void assignmentsByte() { - @Signed byte i1 = sb; - @Unsigned byte i2 = ub; - @Signed byte i3 = sB; - @Unsigned byte i4 = uB; - - @Signed Byte i91 = sb; - @Unsigned Byte i92 = ub; - @Signed Byte i93 = sB; - @Unsigned Byte i94 = uB; - } - - void assignmentsShort() { - // :: error: (assignment.type.incompatible) - @SignedPositive short i1 = sb; - // :: error: (assignment.type.incompatible) - @SignedPositive short i2 = ub; - // :: error: (assignment.type.incompatible) - @SignedPositive short i3 = sB; - // :: error: (assignment.type.incompatible) - @SignedPositive short i4 = uB; - - @Signed short i9 = ss; - @Unsigned short i10 = us; - @Signed short i11 = sS; - @Unsigned short i12 = uS; - - @Signed Short i91 = ss; - @Unsigned Short i92 = us; - @Signed Short i93 = sS; - @Unsigned Short i94 = uS; - } - - void assignmentsChar() { - // These are commented out because they are Java errors. - // @Unsigned char i2 = ub; - // @Unsigned char i4 = uB; - // @Unsigned char i10 = us; - // @Unsigned char i12 = uS; - } - - void assignmentsInt() { - // :: error: (assignment.type.incompatible) - @SignedPositive int i1 = sb; - // :: error: (assignment.type.incompatible) - @SignedPositive int i2 = ub; - // :: error: (assignment.type.incompatible) - @SignedPositive int i3 = sB; - // :: error: (assignment.type.incompatible) - @SignedPositive int i4 = uB; - - // :: error: (assignment.type.incompatible) - @SignedPositive int i9 = ss; - // :: error: (assignment.type.incompatible) - @SignedPositive int i10 = us; - // :: error: (assignment.type.incompatible) - @SignedPositive int i11 = sS; - // :: error: (assignment.type.incompatible) - @SignedPositive int i12 = uS; - - @Signed int i13 = si; - @Unsigned int i14 = ui; - @Signed int i15 = sI; - @Unsigned int i16 = uI; - - @Signed Integer i91 = si; - @Unsigned Integer i92 = ui; - @Signed Integer i93 = sI; - @Unsigned Integer i94 = uI; - } - - void assignmentsLong() { - // :: error: (assignment.type.incompatible) - @SignedPositive long i1 = sb; - // :: error: (assignment.type.incompatible) - @SignedPositive long i2 = ub; - // :: error: (assignment.type.incompatible) - @SignedPositive long i3 = sB; - // :: error: (assignment.type.incompatible) - @SignedPositive long i4 = uB; - - // :: error: (assignment.type.incompatible) - @SignedPositive long i9 = ss; - // :: error: (assignment.type.incompatible) - @SignedPositive long i10 = us; - // :: error: (assignment.type.incompatible) - @SignedPositive long i11 = sS; - // :: error: (assignment.type.incompatible) - @SignedPositive long i12 = uS; - - // :: error: (assignment.type.incompatible) - @SignedPositive long i13 = si; - // :: error: (assignment.type.incompatible) - @SignedPositive long i14 = ui; - // :: error: (assignment.type.incompatible) - @SignedPositive long i15 = sI; - // :: error: (assignment.type.incompatible) - @SignedPositive long i16 = uI; - - @Signed long i17 = sl; - @Unsigned long i18 = ul; - @Signed long i19 = sL; - @Unsigned long i20 = uL; - - @Signed Long i91 = sl; - @Unsigned Long i92 = ul; - @Signed Long i93 = sL; - @Unsigned Long i94 = uL; - } + @Signed byte sb; + @Unsigned byte ub; + @Signed Byte sB; + @Unsigned Byte uB; + + @Signed short ss; + @Unsigned short us; + @Signed Short sS; + @Unsigned Short uS; + + @Signed int si; + @Unsigned int ui; + @Signed Integer sI; + @Unsigned Integer uI; + + @Signed long sl; + @Unsigned long ul; + @Signed Long sL; + @Unsigned Long uL; + + void assignmentsByte() { + @Signed byte i1 = sb; + @Unsigned byte i2 = ub; + @Signed byte i3 = sB; + @Unsigned byte i4 = uB; + + @Signed Byte i91 = sb; + @Unsigned Byte i92 = ub; + @Signed Byte i93 = sB; + @Unsigned Byte i94 = uB; + } + + void assignmentsShort() { + // :: error: (assignment.type.incompatible) + @SignedPositive short i1 = sb; + // :: error: (assignment.type.incompatible) + @SignedPositive short i2 = ub; + // :: error: (assignment.type.incompatible) + @SignedPositive short i3 = sB; + // :: error: (assignment.type.incompatible) + @SignedPositive short i4 = uB; + + @Signed short i9 = ss; + @Unsigned short i10 = us; + @Signed short i11 = sS; + @Unsigned short i12 = uS; + + @Signed Short i91 = ss; + @Unsigned Short i92 = us; + @Signed Short i93 = sS; + @Unsigned Short i94 = uS; + } + + void assignmentsChar() { + // These are commented out because they are Java errors. + // @Unsigned char i2 = ub; + // @Unsigned char i4 = uB; + // @Unsigned char i10 = us; + // @Unsigned char i12 = uS; + } + + void assignmentsInt() { + // :: error: (assignment.type.incompatible) + @SignedPositive int i1 = sb; + // :: error: (assignment.type.incompatible) + @SignedPositive int i2 = ub; + // :: error: (assignment.type.incompatible) + @SignedPositive int i3 = sB; + // :: error: (assignment.type.incompatible) + @SignedPositive int i4 = uB; + + // :: error: (assignment.type.incompatible) + @SignedPositive int i9 = ss; + // :: error: (assignment.type.incompatible) + @SignedPositive int i10 = us; + // :: error: (assignment.type.incompatible) + @SignedPositive int i11 = sS; + // :: error: (assignment.type.incompatible) + @SignedPositive int i12 = uS; + + @Signed int i13 = si; + @Unsigned int i14 = ui; + @Signed int i15 = sI; + @Unsigned int i16 = uI; + + @Signed Integer i91 = si; + @Unsigned Integer i92 = ui; + @Signed Integer i93 = sI; + @Unsigned Integer i94 = uI; + } + + void assignmentsLong() { + // :: error: (assignment.type.incompatible) + @SignedPositive long i1 = sb; + // :: error: (assignment.type.incompatible) + @SignedPositive long i2 = ub; + // :: error: (assignment.type.incompatible) + @SignedPositive long i3 = sB; + // :: error: (assignment.type.incompatible) + @SignedPositive long i4 = uB; + + // :: error: (assignment.type.incompatible) + @SignedPositive long i9 = ss; + // :: error: (assignment.type.incompatible) + @SignedPositive long i10 = us; + // :: error: (assignment.type.incompatible) + @SignedPositive long i11 = sS; + // :: error: (assignment.type.incompatible) + @SignedPositive long i12 = uS; + + // :: error: (assignment.type.incompatible) + @SignedPositive long i13 = si; + // :: error: (assignment.type.incompatible) + @SignedPositive long i14 = ui; + // :: error: (assignment.type.incompatible) + @SignedPositive long i15 = sI; + // :: error: (assignment.type.incompatible) + @SignedPositive long i16 = uI; + + @Signed long i17 = sl; + @Unsigned long i18 = ul; + @Signed long i19 = sL; + @Unsigned long i20 = uL; + + @Signed Long i91 = sl; + @Unsigned Long i92 = ul; + @Signed Long i93 = sL; + @Unsigned Long i94 = uL; + } } diff --git a/checker/tests/signedness/SignednessCast.java b/checker/tests/signedness/SignednessCast.java index f3b23993760..36f2aef1f5e 100644 --- a/checker/tests/signedness/SignednessCast.java +++ b/checker/tests/signedness/SignednessCast.java @@ -2,38 +2,38 @@ @SuppressWarnings("deprecation") // newInstance is deprecated. public class SignednessCast { - static class Instruction {} + static class Instruction {} - public Instruction createCast(String name) { - Instruction i = null; - try { - i = (Instruction) java.lang.Class.forName(name).newInstance(); - } catch (final Exception e) { - throw new IllegalArgumentException("Could not find instruction: " + name, e); + public Instruction createCast(String name) { + Instruction i = null; + try { + i = (Instruction) java.lang.Class.forName(name).newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException("Could not find instruction: " + name, e); + } + return i; } - return i; - } - static class SerializableInstruction implements Serializable {} + static class SerializableInstruction implements Serializable {} - SerializableInstruction other(String name) { - SerializableInstruction i = null; - try { - i = (SerializableInstruction) java.lang.Class.forName(name).newInstance(); - } catch (final Exception e) { - throw new IllegalArgumentException("Could not find instruction: " + name, e); + SerializableInstruction other(String name) { + SerializableInstruction i = null; + try { + i = (SerializableInstruction) java.lang.Class.forName(name).newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException("Could not find instruction: " + name, e); + } + return i; } - return i; - } - Serializable maybeNumber(String name) { - Serializable i = null; - try { - i = (Serializable) java.lang.Class.forName(name).newInstance(); - } catch (final Exception e) { - throw new IllegalArgumentException("Could not find instruction: " + name, e); + Serializable maybeNumber(String name) { + Serializable i = null; + try { + i = (Serializable) java.lang.Class.forName(name).newInstance(); + } catch (final Exception e) { + throw new IllegalArgumentException("Could not find instruction: " + name, e); + } + // :: error: (return.type.incompatible) + return i; } - // :: error: (return.type.incompatible) - return i; - } } diff --git a/checker/tests/signedness/SignednessEquals.java b/checker/tests/signedness/SignednessEquals.java index 5709ac70f6e..fc34b7ed3bf 100644 --- a/checker/tests/signedness/SignednessEquals.java +++ b/checker/tests/signedness/SignednessEquals.java @@ -1,96 +1,97 @@ -import java.util.Objects; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.Unsigned; +import java.util.Objects; + public class SignednessEquals { - @Signed Object so; - @Unsigned Object uo; - - @Signed Number sn; - @Unsigned Number un; - - @Signed byte sb; - @Unsigned byte ub; - @Signed Byte sB; - @Unsigned Byte uB; - - char uc; - Character uC; - - @Signed short ss; - @Unsigned short us; - @Signed Short sS; - @Unsigned Short uS; - - @Signed int si; - @Unsigned int ui; - @Signed Integer sI; - @Unsigned Integer uI; - - @Signed long sl; - @Unsigned long ul; - @Signed Long sL; - @Unsigned Long uL; - - void nonIntegralEquality() { - so.equals(sn); - // :: error: (comparison.mixed.unsignedrhs) - so.equals(un); - // :: error: (comparison.mixed.unsignedlhs) - uo.equals(sn); - uo.equals(un); - - Objects.equals(so, sn); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(so, un); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uo, sn); - Objects.equals(uo, un); - - sI.equals(sn); - // :: error: (comparison.mixed.unsignedrhs) - sI.equals(un); - // :: error: (comparison.mixed.unsignedlhs) - uI.equals(sn); - uI.equals(un); - - Objects.equals(sI, sn); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(sI, un); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uI, sn); - Objects.equals(uI, un); - } - - void integralEquality() { - - so.equals(sS); - // :: error: (comparison.mixed.unsignedrhs) - so.equals(uS); - // :: error: (comparison.mixed.unsignedlhs) - uo.equals(sS); - uo.equals(uS); - - Objects.equals(so, sS); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(so, uS); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uo, sS); - Objects.equals(uo, uS); - - sB.equals(sS); - // :: error: (comparison.mixed.unsignedrhs) - sB.equals(uS); - // :: error: (comparison.mixed.unsignedlhs) - uB.equals(sS); - uB.equals(uS); - - Objects.equals(sB, sS); - // :: error: (comparison.mixed.unsignedrhs) - Objects.equals(sB, uS); - // :: error: (comparison.mixed.unsignedlhs) - Objects.equals(uB, sS); - Objects.equals(uB, uS); - } + @Signed Object so; + @Unsigned Object uo; + + @Signed Number sn; + @Unsigned Number un; + + @Signed byte sb; + @Unsigned byte ub; + @Signed Byte sB; + @Unsigned Byte uB; + + char uc; + Character uC; + + @Signed short ss; + @Unsigned short us; + @Signed Short sS; + @Unsigned Short uS; + + @Signed int si; + @Unsigned int ui; + @Signed Integer sI; + @Unsigned Integer uI; + + @Signed long sl; + @Unsigned long ul; + @Signed Long sL; + @Unsigned Long uL; + + void nonIntegralEquality() { + so.equals(sn); + // :: error: (comparison.mixed.unsignedrhs) + so.equals(un); + // :: error: (comparison.mixed.unsignedlhs) + uo.equals(sn); + uo.equals(un); + + Objects.equals(so, sn); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(so, un); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uo, sn); + Objects.equals(uo, un); + + sI.equals(sn); + // :: error: (comparison.mixed.unsignedrhs) + sI.equals(un); + // :: error: (comparison.mixed.unsignedlhs) + uI.equals(sn); + uI.equals(un); + + Objects.equals(sI, sn); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(sI, un); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uI, sn); + Objects.equals(uI, un); + } + + void integralEquality() { + + so.equals(sS); + // :: error: (comparison.mixed.unsignedrhs) + so.equals(uS); + // :: error: (comparison.mixed.unsignedlhs) + uo.equals(sS); + uo.equals(uS); + + Objects.equals(so, sS); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(so, uS); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uo, sS); + Objects.equals(uo, uS); + + sB.equals(sS); + // :: error: (comparison.mixed.unsignedrhs) + sB.equals(uS); + // :: error: (comparison.mixed.unsignedlhs) + uB.equals(sS); + uB.equals(uS); + + Objects.equals(sB, sS); + // :: error: (comparison.mixed.unsignedrhs) + Objects.equals(sB, uS); + // :: error: (comparison.mixed.unsignedlhs) + Objects.equals(uB, sS); + Objects.equals(uB, uS); + } } diff --git a/checker/tests/signedness/SignednessManualExample.java b/checker/tests/signedness/SignednessManualExample.java index 6f45522c494..e713a07d5b7 100644 --- a/checker/tests/signedness/SignednessManualExample.java +++ b/checker/tests/signedness/SignednessManualExample.java @@ -4,40 +4,17 @@ public class SignednessManualExample { - int s1 = -2; - int s2 = -1; - - @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2 - @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1 - - void m() { - int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 - // :: error: (operation.unsignedlhs) - int x = u1 / u2; // ERROR: result is 2, which is incorrect for (2^32 - 2) / (2^32 - 1) - } - - int s3 = -1; - int s4 = 5; - - @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1 - @Unsigned int u4 = 5; - - void m2() { - int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 - // :: error: (operation.unsignedlhs) - int z = u3 % u4; // ERROR: result is -1, which is incorrect for (2^32 - 1) % 5 = 2 - } - - void useLocalVariables() { - int s1 = -2; int s2 = -1; @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2 @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1 - int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 - int x = u1 / u2; // OK; computation over constants, interpreted as signed; result is signed + void m() { + int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 + // :: error: (operation.unsignedlhs) + int x = u1 / u2; // ERROR: result is 2, which is incorrect for (2^32 - 2) / (2^32 - 1) + } int s3 = -1; int s4 = 5; @@ -45,7 +22,30 @@ void useLocalVariables() { @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1 @Unsigned int u4 = 5; - int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 - int z = u3 % u4; // OK; computation over constants, interpreted as signed; result is signed - } + void m2() { + int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 + // :: error: (operation.unsignedlhs) + int z = u3 % u4; // ERROR: result is -1, which is incorrect for (2^32 - 1) % 5 = 2 + } + + void useLocalVariables() { + + int s1 = -2; + int s2 = -1; + + @Unsigned int u1 = 2147483646; // unsigned: 2^32 - 2, signed: -2 + @Unsigned int u2 = 2147483647; // unsigned: 2^32 - 1, signed: -1 + + int w = s1 / s2; // OK: result is 2, which is correct for -2 / -1 + int x = u1 / u2; // OK; computation over constants, interpreted as signed; result is signed + + int s3 = -1; + int s4 = 5; + + @Unsigned int u3 = 2147483647; // unsigned: 2^32 - 1, signed: -1 + @Unsigned int u4 = 5; + + int y = s3 % s4; // OK: result is -1, which is correct for -1 % 5 + int z = u3 % u4; // OK; computation over constants, interpreted as signed; result is signed + } } diff --git a/checker/tests/signedness/SignednessNumberCasts.java b/checker/tests/signedness/SignednessNumberCasts.java index ff8229f7158..5fc3bf3d600 100644 --- a/checker/tests/signedness/SignednessNumberCasts.java +++ b/checker/tests/signedness/SignednessNumberCasts.java @@ -1,17 +1,17 @@ import org.checkerframework.checker.signedness.qual.Signed; public class SignednessNumberCasts { - Double d2; + Double d2; - void test(MyClass o, MyClass signed) { - // :: error: (assignment.type.incompatible) - @Signed int i = (Integer) o.get(); - @Signed int i2 = (Integer) signed.get(); - Double d = (Double) o.get(); - d2 = (Double) signed.get(); - } + void test(MyClass o, MyClass signed) { + // :: error: (assignment.type.incompatible) + @Signed int i = (Integer) o.get(); + @Signed int i2 = (Integer) signed.get(); + Double d = (Double) o.get(); + d2 = (Double) signed.get(); + } - static interface MyClass { - T get(); - } + static interface MyClass { + T get(); + } } diff --git a/checker/tests/signedness/SignednessRangeTest.java b/checker/tests/signedness/SignednessRangeTest.java index 03206984730..eab0658df22 100644 --- a/checker/tests/signedness/SignednessRangeTest.java +++ b/checker/tests/signedness/SignednessRangeTest.java @@ -2,9 +2,9 @@ class SignednessRangeTest { - private static final @Unsigned int UINT8_MAX = 255; - private static final @Unsigned int UINT16_MAX = 65_535; - private static final @Unsigned byte SOFT_MAX = (@Unsigned byte) UINT8_MAX; - private static final @Unsigned byte SOFT_MAX_2 = (@Unsigned byte) 255; - private static final @Unsigned short DISTANCE_MAX = (@Unsigned short) UINT16_MAX; + private static final @Unsigned int UINT8_MAX = 255; + private static final @Unsigned int UINT16_MAX = 65_535; + private static final @Unsigned byte SOFT_MAX = (@Unsigned byte) UINT8_MAX; + private static final @Unsigned byte SOFT_MAX_2 = (@Unsigned byte) 255; + private static final @Unsigned short DISTANCE_MAX = (@Unsigned short) UINT16_MAX; } diff --git a/checker/tests/signedness/StringConcat.java b/checker/tests/signedness/StringConcat.java index c9b6d684735..bc13a9af2ab 100644 --- a/checker/tests/signedness/StringConcat.java +++ b/checker/tests/signedness/StringConcat.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class StringConcat { - public String doConcat(@Unsigned int i, String s) { - // :: error: (unsigned.concat) - return s + i; - } + public String doConcat(@Unsigned int i, String s) { + // :: error: (unsigned.concat) + return s + i; + } } diff --git a/checker/tests/signedness/TestPrintln.java b/checker/tests/signedness/TestPrintln.java index c2882e11dda..3f36f969f0f 100644 --- a/checker/tests/signedness/TestPrintln.java +++ b/checker/tests/signedness/TestPrintln.java @@ -4,16 +4,16 @@ import org.checkerframework.checker.signedness.qual.*; public class TestPrintln { - public static void main(String[] args) { - // The first call produces the intended result, but the next two do not. + public static void main(String[] args) { + // The first call produces the intended result, but the next two do not. - @Unsigned int a = Integer.parseUnsignedInt("2147483647"); - System.out.println(a); - @Unsigned int b = Integer.parseUnsignedInt("2147483648"); - // :: error: (argument.type.incompatible) - System.out.println(b); - @Unsigned int c = Integer.parseUnsignedInt("4000000000"); - // :: error: (argument.type.incompatible) - System.out.println(c); - } + @Unsigned int a = Integer.parseUnsignedInt("2147483647"); + System.out.println(a); + @Unsigned int b = Integer.parseUnsignedInt("2147483648"); + // :: error: (argument.type.incompatible) + System.out.println(b); + @Unsigned int c = Integer.parseUnsignedInt("4000000000"); + // :: error: (argument.type.incompatible) + System.out.println(c); + } } diff --git a/checker/tests/signedness/ToHexString.java b/checker/tests/signedness/ToHexString.java index 128e141f4f3..8ef65748442 100644 --- a/checker/tests/signedness/ToHexString.java +++ b/checker/tests/signedness/ToHexString.java @@ -2,15 +2,15 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class ToHexString { - void toHexString(int x) { - Integer.toHexString(x); - } + void toHexString(int x) { + Integer.toHexString(x); + } - void toHexStringU(@Unsigned int x) { - Integer.toHexString(x); - } + void toHexStringU(@Unsigned int x) { + Integer.toHexString(x); + } - void toHexStringS(@Signed int x) { - Integer.toHexString(x); - } + void toHexStringS(@Signed int x) { + Integer.toHexString(x); + } } diff --git a/checker/tests/signedness/UnsignedConcat.java b/checker/tests/signedness/UnsignedConcat.java index 454c27e2403..df21c73bb97 100644 --- a/checker/tests/signedness/UnsignedConcat.java +++ b/checker/tests/signedness/UnsignedConcat.java @@ -3,55 +3,55 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class UnsignedConcat { - @UnknownSignedness int unknownInt = -3; - @Unsigned short unsignedShort = -2; - @Unsigned int unsignedInt = -2; - @Signed short signedShort = -2; - @Signed int signedInt = -2; + @UnknownSignedness int unknownInt = -3; + @Unsigned short unsignedShort = -2; + @Unsigned int unsignedInt = -2; + @Signed short signedShort = -2; + @Signed int signedInt = -2; - void test1(char c, Character charObj) { - // :: error: (unsigned.concat) - String s1 = "" + unsignedShort; - // :: error: (unsigned.concat) - String s2 = "" + unsignedInt; - // :: error: (unsigned.concat) - String s1b = unsignedShort + ""; - // :: error: (unsigned.concat) - String s2b = "" + unsignedInt + ""; - String s3 = "" + signedShort; - String s4 = "" + signedInt; - // :: error: (unsigned.concat) - String s5 = "" + unknownInt; - String s6 = "" + -1; + void test1(char c, Character charObj) { + // :: error: (unsigned.concat) + String s1 = "" + unsignedShort; + // :: error: (unsigned.concat) + String s2 = "" + unsignedInt; + // :: error: (unsigned.concat) + String s1b = unsignedShort + ""; + // :: error: (unsigned.concat) + String s2b = "" + unsignedInt + ""; + String s3 = "" + signedShort; + String s4 = "" + signedInt; + // :: error: (unsigned.concat) + String s5 = "" + unknownInt; + String s6 = "" + -1; - String s7 = "" + c; - String s8 = "" + charObj; - } + String s7 = "" + c; + String s8 = "" + charObj; + } - void test2(String s, char c, Character charObj) { - // :: error: (unsigned.concat) - s += unsignedShort; - // :: error: (unsigned.concat) - s += +unsignedInt; - s += "" + signedShort; - s += signedInt; - // :: error: (unsigned.concat) - s += unknownInt; - s += 9; - s += c; - s += charObj; - } + void test2(String s, char c, Character charObj) { + // :: error: (unsigned.concat) + s += unsignedShort; + // :: error: (unsigned.concat) + s += +unsignedInt; + s += "" + signedShort; + s += signedInt; + // :: error: (unsigned.concat) + s += unknownInt; + s += 9; + s += c; + s += charObj; + } - void test3() { - String a = "World"; - String s = "Hi " + (int) a.charAt(0); - } + void test3() { + String a = "World"; + String s = "Hi " + (int) a.charAt(0); + } - void test4(String s) { - Class sc = null; - if (s != null) { - sc = s.getClass(); + void test4(String s) { + Class sc = null; + if (s != null) { + sc = s.getClass(); + } + System.out.println("In: " + sc); } - System.out.println("In: " + sc); - } } diff --git a/checker/tests/signedness/UnsignedConcat2.java b/checker/tests/signedness/UnsignedConcat2.java index 78f62093510..19e8d5f6769 100644 --- a/checker/tests/signedness/UnsignedConcat2.java +++ b/checker/tests/signedness/UnsignedConcat2.java @@ -1,22 +1,23 @@ -import java.util.Set; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Set; + public class UnsignedConcat2 { - protected @Nullable T value; + protected @Nullable T value; - protected Set set; + protected Set set; - @Override - public String toString() { - switch (set.size()) { - case 0: - return "[]"; - case 1: - return "[" + value + "]"; - default: - return set.toString(); + @Override + public String toString() { + switch (set.size()) { + case 0: + return "[]"; + case 1: + return "[" + value + "]"; + default: + return set.toString(); + } } - } } diff --git a/checker/tests/signedness/UnsignedRightShiftTest.java b/checker/tests/signedness/UnsignedRightShiftTest.java index 414f7aaf0eb..cc51fd23df5 100644 --- a/checker/tests/signedness/UnsignedRightShiftTest.java +++ b/checker/tests/signedness/UnsignedRightShiftTest.java @@ -5,46 +5,46 @@ import org.checkerframework.checker.signedness.qual.Unsigned; public class UnsignedRightShiftTest { - int length; - - void unsignedRightShiftWithLiteral() { - int length = Integer.MAX_VALUE; - byte b = (byte) (length >>> 24); - } - - void unsignedRightShiftWithParameter(int length) { - byte b1 = (byte) (length >>> 24); - byte b2 = (@Signed byte) (length >>> 24); - byte b3 = (@Unsigned byte) (length >>> 24); - } - - void unsignedRightShiftWithField() { - byte b = (byte) (this.length >>> 24); - } - - void unsignedRightShiftComplex() { - int length = return12(); - byte[] byteArray = new byte[4]; - byteArray[0] = (byte) (length >>> 24); - byteArray[1] = (byte) (length >>> 16); - byteArray[2] = (byte) (length >>> 8); - byteArray[3] = (byte) length; - } - - void testWrite64(long x) { - write32((int) (x >>> 32)); - } - - void testWrite64() { - long myLong = Long.MAX_VALUE; - int z = (int) (myLong >>> 32); - int myInt = 2; - short w = (short) (myInt >>> 16); - } - - int return12() { - return 12; - } - - void write32(int x) {} + int length; + + void unsignedRightShiftWithLiteral() { + int length = Integer.MAX_VALUE; + byte b = (byte) (length >>> 24); + } + + void unsignedRightShiftWithParameter(int length) { + byte b1 = (byte) (length >>> 24); + byte b2 = (@Signed byte) (length >>> 24); + byte b3 = (@Unsigned byte) (length >>> 24); + } + + void unsignedRightShiftWithField() { + byte b = (byte) (this.length >>> 24); + } + + void unsignedRightShiftComplex() { + int length = return12(); + byte[] byteArray = new byte[4]; + byteArray[0] = (byte) (length >>> 24); + byteArray[1] = (byte) (length >>> 16); + byteArray[2] = (byte) (length >>> 8); + byteArray[3] = (byte) length; + } + + void testWrite64(long x) { + write32((int) (x >>> 32)); + } + + void testWrite64() { + long myLong = Long.MAX_VALUE; + int z = (int) (myLong >>> 32); + int myInt = 2; + short w = (short) (myInt >>> 16); + } + + int return12() { + return 12; + } + + void write32(int x) {} } diff --git a/checker/tests/signedness/Utils.java b/checker/tests/signedness/Utils.java index d6135b9ee0a..304ff37486d 100644 --- a/checker/tests/signedness/Utils.java +++ b/checker/tests/signedness/Utils.java @@ -1,166 +1,170 @@ -import java.nio.ByteBuffer; import org.checkerframework.checker.signedness.qual.*; import org.checkerframework.checker.signedness.util.SignednessUtil; +import java.nio.ByteBuffer; + public class Utils { - public void getTests( - @Unsigned int uint, - @Signed int sint, - @Unsigned short ushort, - @Signed short sshort, - @Unsigned byte ubyte, - @Signed byte sbyte, - @Unsigned byte[] ubyteArr, - @Signed byte[] sbyteArr, - ByteBuffer b) { + public void getTests( + @Unsigned int uint, + @Signed int sint, + @Unsigned short ushort, + @Signed short sshort, + @Unsigned byte ubyte, + @Signed byte sbyte, + @Unsigned byte[] ubyteArr, + @Signed byte[] sbyteArr, + ByteBuffer b) { - // :: error: (assignment.type.incompatible) - sint = SignednessUtil.getUnsignedInt(b); + // :: error: (assignment.type.incompatible) + sint = SignednessUtil.getUnsignedInt(b); - uint = SignednessUtil.getUnsignedInt(b); + uint = SignednessUtil.getUnsignedInt(b); - // :: error: (assignment.type.incompatible) - sshort = SignednessUtil.getUnsignedShort(b); + // :: error: (assignment.type.incompatible) + sshort = SignednessUtil.getUnsignedShort(b); - ushort = SignednessUtil.getUnsignedShort(b); + ushort = SignednessUtil.getUnsignedShort(b); - // :: error: (assignment.type.incompatible) - sbyte = SignednessUtil.getUnsigned(b); + // :: error: (assignment.type.incompatible) + sbyte = SignednessUtil.getUnsigned(b); - ubyte = SignednessUtil.getUnsigned(b); + ubyte = SignednessUtil.getUnsigned(b); - // :: error: (argument.type.incompatible) - SignednessUtil.getUnsigned(b, sbyteArr); + // :: error: (argument.type.incompatible) + SignednessUtil.getUnsigned(b, sbyteArr); - SignednessUtil.getUnsigned(b, ubyteArr); - } + SignednessUtil.getUnsigned(b, ubyteArr); + } - public void compTests( - @Unsigned long ulong, - @Signed long slong, - @Unsigned int uint, - @Signed int sint, - @Unsigned short ushort, - @Signed short sshort, - @Unsigned byte ubyte, - @Signed byte sbyte) { + public void compTests( + @Unsigned long ulong, + @Signed long slong, + @Unsigned int uint, + @Signed int sint, + @Unsigned short ushort, + @Signed short sshort, + @Unsigned byte ubyte, + @Signed byte sbyte) { - int res; + int res; - // :: error: (argument.type.incompatible) - res = Long.compareUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + res = Long.compareUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - res = Long.compareUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + res = Long.compareUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - res = Long.compareUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + res = Long.compareUnsigned(ulong, slong); - res = Long.compareUnsigned(ulong, ulong); + res = Long.compareUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - res = Integer.compareUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + res = Integer.compareUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - res = Integer.compareUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + res = Integer.compareUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - res = Integer.compareUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + res = Integer.compareUnsigned(uint, sint); - res = Integer.compareUnsigned(uint, uint); + res = Integer.compareUnsigned(uint, uint); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sshort, sshort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sshort, sshort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sshort, ushort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sshort, ushort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(ushort, sshort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(ushort, sshort); - res = SignednessUtil.compareUnsigned(ushort, ushort); + res = SignednessUtil.compareUnsigned(ushort, ushort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sbyte, sbyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sbyte, sbyte); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(sbyte, ubyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(sbyte, ubyte); - // :: error: (argument.type.incompatible) - res = SignednessUtil.compareUnsigned(ubyte, sbyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.compareUnsigned(ubyte, sbyte); - res = SignednessUtil.compareUnsigned(ubyte, ubyte); - } + res = SignednessUtil.compareUnsigned(ubyte, ubyte); + } - public void stringTests( - @Unsigned long ulong, - @Signed long slong, - @Unsigned int uint, - @Signed int sint, - @Unsigned short ushort, - @Signed short sshort, - @Unsigned byte ubyte, - @Signed byte sbyte) { + public void stringTests( + @Unsigned long ulong, + @Signed long slong, + @Unsigned int uint, + @Signed int sint, + @Unsigned short ushort, + @Signed short sshort, + @Unsigned byte ubyte, + @Signed byte sbyte) { - String res; + String res; - // :: error: (argument.type.incompatible) - res = Long.toUnsignedString(slong); + // :: error: (argument.type.incompatible) + res = Long.toUnsignedString(slong); - res = Long.toUnsignedString(ulong); + res = Long.toUnsignedString(ulong); - // :: error: (argument.type.incompatible) - res = Long.toUnsignedString(slong, 10); + // :: error: (argument.type.incompatible) + res = Long.toUnsignedString(slong, 10); - res = Long.toUnsignedString(ulong, 10); + res = Long.toUnsignedString(ulong, 10); - // :: error: (argument.type.incompatible) - res = Integer.toUnsignedString(sint); + // :: error: (argument.type.incompatible) + res = Integer.toUnsignedString(sint); - res = Integer.toUnsignedString(uint); + res = Integer.toUnsignedString(uint); - // :: error: (argument.type.incompatible) - res = Integer.toUnsignedString(sint, 10); + // :: error: (argument.type.incompatible) + res = Integer.toUnsignedString(sint, 10); - res = Integer.toUnsignedString(uint, 10); + res = Integer.toUnsignedString(uint, 10); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sshort); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sshort); - res = SignednessUtil.toUnsignedString(ushort); + res = SignednessUtil.toUnsignedString(ushort); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sshort, 10); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sshort, 10); - res = SignednessUtil.toUnsignedString(ushort, 10); + res = SignednessUtil.toUnsignedString(ushort, 10); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sbyte); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sbyte); - res = SignednessUtil.toUnsignedString(ubyte); + res = SignednessUtil.toUnsignedString(ubyte); - // :: error: (argument.type.incompatible) - res = SignednessUtil.toUnsignedString(sbyte, 10); + // :: error: (argument.type.incompatible) + res = SignednessUtil.toUnsignedString(sbyte, 10); - res = SignednessUtil.toUnsignedString(ubyte, 10); - } + res = SignednessUtil.toUnsignedString(ubyte, 10); + } - public void floatingPointConversionTests( - @Unsigned long ulong, @Unsigned int uint, @Unsigned short ushort, @Unsigned byte ubyte) { + public void floatingPointConversionTests( + @Unsigned long ulong, + @Unsigned int uint, + @Unsigned short ushort, + @Unsigned byte ubyte) { - float resFloat; + float resFloat; - resFloat = SignednessUtil.toFloat(ubyte); - resFloat = SignednessUtil.toFloat(ushort); - resFloat = SignednessUtil.toFloat(uint); - resFloat = SignednessUtil.toFloat(ulong); + resFloat = SignednessUtil.toFloat(ubyte); + resFloat = SignednessUtil.toFloat(ushort); + resFloat = SignednessUtil.toFloat(uint); + resFloat = SignednessUtil.toFloat(ulong); - double resDouble; + double resDouble; - resDouble = SignednessUtil.toDouble(ubyte); - resDouble = SignednessUtil.toDouble(ushort); - resDouble = SignednessUtil.toDouble(uint); - resDouble = SignednessUtil.toDouble(ulong); - } + resDouble = SignednessUtil.toDouble(ubyte); + resDouble = SignednessUtil.toDouble(ushort); + resDouble = SignednessUtil.toDouble(uint); + resDouble = SignednessUtil.toDouble(ulong); + } } diff --git a/checker/tests/signedness/UtilsJava8.java b/checker/tests/signedness/UtilsJava8.java index 94a62a81b4b..1421299ea9f 100644 --- a/checker/tests/signedness/UtilsJava8.java +++ b/checker/tests/signedness/UtilsJava8.java @@ -4,138 +4,138 @@ // Test Java 8 unsigned utils public class UtilsJava8 { - public void annotatedJDKTests( - @Unsigned long ulong, - @Signed long slong, - @Unsigned int uint, - @Signed int sint, - char[] buf, - String s) { + public void annotatedJDKTests( + @Unsigned long ulong, + @Signed long slong, + @Unsigned int uint, + @Signed int sint, + char[] buf, + String s) { - String resString; - int resInt; - long resLong; + String resString; + int resInt; + long resLong; - // :: error: (argument.type.incompatible) - resString = Long.toUnsignedString(slong, 10); + // :: error: (argument.type.incompatible) + resString = Long.toUnsignedString(slong, 10); - resString = Long.toUnsignedString(ulong, 10); + resString = Long.toUnsignedString(ulong, 10); - // :: error: (argument.type.incompatible) - resString = Long.toUnsignedString(slong); + // :: error: (argument.type.incompatible) + resString = Long.toUnsignedString(slong); - resString = Long.toUnsignedString(ulong); + resString = Long.toUnsignedString(ulong); - // :: error: (assignment.type.incompatible) - slong = Long.parseUnsignedLong(s, 10); + // :: error: (assignment.type.incompatible) + slong = Long.parseUnsignedLong(s, 10); - ulong = Long.parseUnsignedLong(s, 10); + ulong = Long.parseUnsignedLong(s, 10); - // :: error: (assignment.type.incompatible) - slong = Long.parseUnsignedLong(s); + // :: error: (assignment.type.incompatible) + slong = Long.parseUnsignedLong(s); - ulong = Long.parseUnsignedLong(s); + ulong = Long.parseUnsignedLong(s); - // :: error: (argument.type.incompatible) - resInt = Long.compareUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + resInt = Long.compareUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - resInt = Long.compareUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + resInt = Long.compareUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - resInt = Long.compareUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + resInt = Long.compareUnsigned(ulong, slong); - resInt = Long.compareUnsigned(ulong, ulong); + resInt = Long.compareUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.divideUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.divideUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - ulong = Long.divideUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + ulong = Long.divideUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.divideUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.divideUnsigned(ulong, slong); - // :: error: (assignment.type.incompatible) - slong = Long.divideUnsigned(ulong, ulong); + // :: error: (assignment.type.incompatible) + slong = Long.divideUnsigned(ulong, ulong); - ulong = Long.divideUnsigned(ulong, ulong); + ulong = Long.divideUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.remainderUnsigned(slong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.remainderUnsigned(slong, slong); - // :: error: (argument.type.incompatible) - ulong = Long.remainderUnsigned(slong, ulong); + // :: error: (argument.type.incompatible) + ulong = Long.remainderUnsigned(slong, ulong); - // :: error: (argument.type.incompatible) - ulong = Long.remainderUnsigned(ulong, slong); + // :: error: (argument.type.incompatible) + ulong = Long.remainderUnsigned(ulong, slong); - // :: error: (assignment.type.incompatible) - slong = Long.remainderUnsigned(ulong, ulong); + // :: error: (assignment.type.incompatible) + slong = Long.remainderUnsigned(ulong, ulong); - ulong = Long.remainderUnsigned(ulong, ulong); + ulong = Long.remainderUnsigned(ulong, ulong); - // :: error: (argument.type.incompatible) - resString = Integer.toUnsignedString(sint, 10); + // :: error: (argument.type.incompatible) + resString = Integer.toUnsignedString(sint, 10); - resString = Integer.toUnsignedString(uint, 10); + resString = Integer.toUnsignedString(uint, 10); - // :: error: (argument.type.incompatible) - resString = Integer.toUnsignedString(sint); + // :: error: (argument.type.incompatible) + resString = Integer.toUnsignedString(sint); - resString = Integer.toUnsignedString(uint); + resString = Integer.toUnsignedString(uint); - // :: error: (assignment.type.incompatible) - sint = Integer.parseUnsignedInt(s, 10); + // :: error: (assignment.type.incompatible) + sint = Integer.parseUnsignedInt(s, 10); - uint = Integer.parseUnsignedInt(s, 10); + uint = Integer.parseUnsignedInt(s, 10); - // :: error: (assignment.type.incompatible) - sint = Integer.parseUnsignedInt(s); + // :: error: (assignment.type.incompatible) + sint = Integer.parseUnsignedInt(s); - uint = Integer.parseUnsignedInt(s); + uint = Integer.parseUnsignedInt(s); - // :: error: (argument.type.incompatible) - resInt = Integer.compareUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + resInt = Integer.compareUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - resInt = Integer.compareUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + resInt = Integer.compareUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - resInt = Integer.compareUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + resInt = Integer.compareUnsigned(uint, sint); - resInt = Integer.compareUnsigned(uint, uint); + resInt = Integer.compareUnsigned(uint, uint); - resLong = Integer.toUnsignedLong(sint); + resLong = Integer.toUnsignedLong(sint); - ulong = Integer.toUnsignedLong(uint); + ulong = Integer.toUnsignedLong(uint); - // :: error: (argument.type.incompatible) - uint = Integer.divideUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.divideUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - uint = Integer.divideUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + uint = Integer.divideUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - uint = Integer.divideUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.divideUnsigned(uint, sint); - // :: error: (assignment.type.incompatible) - sint = Integer.divideUnsigned(uint, uint); + // :: error: (assignment.type.incompatible) + sint = Integer.divideUnsigned(uint, uint); - uint = Integer.divideUnsigned(uint, uint); + uint = Integer.divideUnsigned(uint, uint); - // :: error: (argument.type.incompatible) - uint = Integer.remainderUnsigned(sint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.remainderUnsigned(sint, sint); - // :: error: (argument.type.incompatible) - uint = Integer.remainderUnsigned(sint, uint); + // :: error: (argument.type.incompatible) + uint = Integer.remainderUnsigned(sint, uint); - // :: error: (argument.type.incompatible) - uint = Integer.remainderUnsigned(uint, sint); + // :: error: (argument.type.incompatible) + uint = Integer.remainderUnsigned(uint, sint); - // :: error: (assignment.type.incompatible) - sint = Integer.remainderUnsigned(uint, uint); + // :: error: (assignment.type.incompatible) + sint = Integer.remainderUnsigned(uint, uint); - uint = Integer.remainderUnsigned(uint, uint); - } + uint = Integer.remainderUnsigned(uint, uint); + } } diff --git a/checker/tests/signedness/ValueIntegration.java b/checker/tests/signedness/ValueIntegration.java index 48c3c647891..ae4ecc860d7 100644 --- a/checker/tests/signedness/ValueIntegration.java +++ b/checker/tests/signedness/ValueIntegration.java @@ -7,491 +7,491 @@ import org.checkerframework.common.value.qual.IntVal; public class ValueIntegration { - public void ByteValRules( - @IntVal({0, 127}) byte c, - @IntVal({128, 255}) byte upure, - @IntVal({0, 128}) byte umixed, // 128 is another way to write -128 - @IntVal({-128, -1}) byte spure, - @IntVal({-1, 127}) byte smixed, - @IntVal({-128, 0, 128}) byte bmixed) { - @Signed byte stest; - @SignednessGlb byte gtest; - @SignedPositive byte ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - // Character and char are always @Unsigned, never @Signed. - /* - public void CharValRules( - @IntVal({0, 127}) char c, - @IntVal({128, 255}) char upure, - @IntVal({0, 128}) char umixed, - @IntVal({-128, -1}) char spure, - @IntVal({-1, 127}) char smixed, - @IntVal({-128, 0, 128}) char bmixed) { - @Signed char stest; - @SignednessGlb char gtest; - @SignedPositive char ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // XX error: (assignment.type.incompatible) - gtest = upure; - // XX error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // XX error: (assignment.type.incompatible) - gtest = umixed; - // XX error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // XX error: (assignment.type.incompatible) - gtest = spure; - // XX error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // XX error: (assignment.type.incompatible) - gtest = smixed; - // XX error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // XX error: (assignment.type.incompatible) - gtest = bmixed; - // XX error: (assignment.type.incompatible) - ptest = bmixed; - } - */ - - public void ShortValRules( - @IntVal({0, 32767}) short c, - @IntVal({32768, 65535}) short upure, - @IntVal({0, 32768}) short umixed, - @IntVal({-32768, -1}) short spure, - @IntVal({-1, 32767}) short smixed, - @IntVal({-32768, 0, 32768}) short bmixed) { - @Signed short stest; - @SignednessGlb short gtest; - @SignedPositive short ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void IntValRules( - @IntVal({0, 2147483647}) int c, - @IntVal({2147483648L, 4294967295L}) int upure, - @IntVal({0, 2147483648L}) int umixed, - @IntVal({-2147483648, -1}) int spure, - @IntVal({-1, 2147483647}) int smixed, - @IntVal({-2147483648, 0, 2147483648L}) int bmixed) { - @Signed int stest; - @SignednessGlb int gtest; - @SignedPositive int ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void LongValRules( - @IntVal({0, Long.MAX_VALUE}) long c, - @IntVal({Long.MIN_VALUE, -1}) long spure, - @IntVal({-1, Long.MAX_VALUE}) long smixed, - @IntVal({Long.MIN_VALUE, 0, Long.MAX_VALUE}) long bmixed) { - @Signed long stest; - @SignednessGlb long gtest; - @SignedPositive long ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void ByteRangeRules( - @IntRange(from = 0, to = 127) byte c, - @NonNegative byte nnc, - @Positive byte pc, - @IntRange(from = 128, to = 255) byte upure, - @IntRange(from = 0, to = 128) byte umixed, - @IntRange(from = -128, to = -1) byte spure, - @IntRange(from = -1, to = 127) byte smixed, - @IntRange(from = -128, to = 128) byte bmixed) { - @Signed byte stest; - @SignednessGlb byte gtest; - @SignedPositive byte ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - // Character and char are always @Unsigned, never @Signed. - /* - public void CharRangeRules( - @IntRange(from = 0, to = 127) char c, - @NonNegative char nnc, - @Positive char pc, - @IntRange(from = 128, to = 255) char upure, - @IntRange(from = 0, to = 128) char umixed, - @IntRange(from = -128, to = -1) char spure, - @IntRange(from = -1, to = 127) char smixed, - @IntRange(from = -128, to = 128) char bmixed) { - @Signed char stest; - @SignednessGlb char gtest; - @SignedPositive char ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // XX error: (assignment.type.incompatible) - gtest = upure; - // XX error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // XX error: (assignment.type.incompatible) - gtest = umixed; - // XX error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // XX error: (assignment.type.incompatible) - gtest = spure; - // XX error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // XX error: (assignment.type.incompatible) - gtest = smixed; - // XX error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // XX error: (assignment.type.incompatible) - gtest = bmixed; - // XX error: (assignment.type.incompatible) - ptest = bmixed; - } - */ - - public void ShortRangeRules( - @IntRange(from = 0, to = 32767) short c, - @NonNegative short nnc, - @Positive short pc, - @IntRange(from = 32768, to = 65535) short upure, - @IntRange(from = 0, to = 32768) short umixed, - @IntRange(from = -32768, to = -1) short spure, - @IntRange(from = -1, to = 32767) short smixed, - @IntRange(from = -32768, to = 32768) short bmixed) { - @Signed short stest; - @SignednessGlb short gtest; - @SignedPositive short ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void IntRangeRules( - @IntRange(from = 0, to = 2147483647) int c, - @NonNegative int nnc, - @Positive int pc, - @IntRange(from = 2147483648L, to = 4294967295L) int upure, - @IntRange(from = 0, to = 2147483648L) int umixed, - @IntRange(from = -2147483648, to = -1) int spure, - @IntRange(from = -1, to = 2147483647) int smixed, - @IntRange(from = -2147483648, to = 2147483648L) int bmixed) { - @Signed int stest; - @SignednessGlb int gtest; - @SignedPositive int ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = upure; - // :: error: (assignment.type.incompatible) - gtest = upure; - // :: error: (assignment.type.incompatible) - ptest = upure; - - stest = umixed; - // :: error: (assignment.type.incompatible) - gtest = umixed; - // :: error: (assignment.type.incompatible) - ptest = umixed; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } - - public void LongRangeRules( - @IntRange(from = 0, to = Long.MAX_VALUE) long c, - @NonNegative long nnc, - @Positive long pc, - @IntRange(from = Long.MIN_VALUE, to = -1) long spure, - @IntRange(from = -1, to = Long.MAX_VALUE) long smixed, - @IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long bmixed) { - @Signed long stest; - @SignednessGlb long gtest; - @SignedPositive long ptest; - - stest = c; - gtest = c; - ptest = c; - - stest = nnc; - gtest = nnc; - ptest = nnc; - - stest = pc; - gtest = pc; - ptest = pc; - - stest = spure; - // :: error: (assignment.type.incompatible) - gtest = spure; - // :: error: (assignment.type.incompatible) - ptest = spure; - - stest = smixed; - // :: error: (assignment.type.incompatible) - gtest = smixed; - // :: error: (assignment.type.incompatible) - ptest = smixed; - - stest = bmixed; - // :: error: (assignment.type.incompatible) - gtest = bmixed; - // :: error: (assignment.type.incompatible) - ptest = bmixed; - } + public void ByteValRules( + @IntVal({0, 127}) byte c, + @IntVal({128, 255}) byte upure, + @IntVal({0, 128}) byte umixed, // 128 is another way to write -128 + @IntVal({-128, -1}) byte spure, + @IntVal({-1, 127}) byte smixed, + @IntVal({-128, 0, 128}) byte bmixed) { + @Signed byte stest; + @SignednessGlb byte gtest; + @SignedPositive byte ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + // Character and char are always @Unsigned, never @Signed. + /* + public void CharValRules( + @IntVal({0, 127}) char c, + @IntVal({128, 255}) char upure, + @IntVal({0, 128}) char umixed, + @IntVal({-128, -1}) char spure, + @IntVal({-1, 127}) char smixed, + @IntVal({-128, 0, 128}) char bmixed) { + @Signed char stest; + @SignednessGlb char gtest; + @SignedPositive char ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // XX error: (assignment.type.incompatible) + gtest = upure; + // XX error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // XX error: (assignment.type.incompatible) + gtest = umixed; + // XX error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // XX error: (assignment.type.incompatible) + gtest = spure; + // XX error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // XX error: (assignment.type.incompatible) + gtest = smixed; + // XX error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // XX error: (assignment.type.incompatible) + gtest = bmixed; + // XX error: (assignment.type.incompatible) + ptest = bmixed; + } + */ + + public void ShortValRules( + @IntVal({0, 32767}) short c, + @IntVal({32768, 65535}) short upure, + @IntVal({0, 32768}) short umixed, + @IntVal({-32768, -1}) short spure, + @IntVal({-1, 32767}) short smixed, + @IntVal({-32768, 0, 32768}) short bmixed) { + @Signed short stest; + @SignednessGlb short gtest; + @SignedPositive short ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void IntValRules( + @IntVal({0, 2147483647}) int c, + @IntVal({2147483648L, 4294967295L}) int upure, + @IntVal({0, 2147483648L}) int umixed, + @IntVal({-2147483648, -1}) int spure, + @IntVal({-1, 2147483647}) int smixed, + @IntVal({-2147483648, 0, 2147483648L}) int bmixed) { + @Signed int stest; + @SignednessGlb int gtest; + @SignedPositive int ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void LongValRules( + @IntVal({0, Long.MAX_VALUE}) long c, + @IntVal({Long.MIN_VALUE, -1}) long spure, + @IntVal({-1, Long.MAX_VALUE}) long smixed, + @IntVal({Long.MIN_VALUE, 0, Long.MAX_VALUE}) long bmixed) { + @Signed long stest; + @SignednessGlb long gtest; + @SignedPositive long ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void ByteRangeRules( + @IntRange(from = 0, to = 127) byte c, + @NonNegative byte nnc, + @Positive byte pc, + @IntRange(from = 128, to = 255) byte upure, + @IntRange(from = 0, to = 128) byte umixed, + @IntRange(from = -128, to = -1) byte spure, + @IntRange(from = -1, to = 127) byte smixed, + @IntRange(from = -128, to = 128) byte bmixed) { + @Signed byte stest; + @SignednessGlb byte gtest; + @SignedPositive byte ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + // Character and char are always @Unsigned, never @Signed. + /* + public void CharRangeRules( + @IntRange(from = 0, to = 127) char c, + @NonNegative char nnc, + @Positive char pc, + @IntRange(from = 128, to = 255) char upure, + @IntRange(from = 0, to = 128) char umixed, + @IntRange(from = -128, to = -1) char spure, + @IntRange(from = -1, to = 127) char smixed, + @IntRange(from = -128, to = 128) char bmixed) { + @Signed char stest; + @SignednessGlb char gtest; + @SignedPositive char ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // XX error: (assignment.type.incompatible) + gtest = upure; + // XX error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // XX error: (assignment.type.incompatible) + gtest = umixed; + // XX error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // XX error: (assignment.type.incompatible) + gtest = spure; + // XX error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // XX error: (assignment.type.incompatible) + gtest = smixed; + // XX error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // XX error: (assignment.type.incompatible) + gtest = bmixed; + // XX error: (assignment.type.incompatible) + ptest = bmixed; + } + */ + + public void ShortRangeRules( + @IntRange(from = 0, to = 32767) short c, + @NonNegative short nnc, + @Positive short pc, + @IntRange(from = 32768, to = 65535) short upure, + @IntRange(from = 0, to = 32768) short umixed, + @IntRange(from = -32768, to = -1) short spure, + @IntRange(from = -1, to = 32767) short smixed, + @IntRange(from = -32768, to = 32768) short bmixed) { + @Signed short stest; + @SignednessGlb short gtest; + @SignedPositive short ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void IntRangeRules( + @IntRange(from = 0, to = 2147483647) int c, + @NonNegative int nnc, + @Positive int pc, + @IntRange(from = 2147483648L, to = 4294967295L) int upure, + @IntRange(from = 0, to = 2147483648L) int umixed, + @IntRange(from = -2147483648, to = -1) int spure, + @IntRange(from = -1, to = 2147483647) int smixed, + @IntRange(from = -2147483648, to = 2147483648L) int bmixed) { + @Signed int stest; + @SignednessGlb int gtest; + @SignedPositive int ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = upure; + // :: error: (assignment.type.incompatible) + gtest = upure; + // :: error: (assignment.type.incompatible) + ptest = upure; + + stest = umixed; + // :: error: (assignment.type.incompatible) + gtest = umixed; + // :: error: (assignment.type.incompatible) + ptest = umixed; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } + + public void LongRangeRules( + @IntRange(from = 0, to = Long.MAX_VALUE) long c, + @NonNegative long nnc, + @Positive long pc, + @IntRange(from = Long.MIN_VALUE, to = -1) long spure, + @IntRange(from = -1, to = Long.MAX_VALUE) long smixed, + @IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long bmixed) { + @Signed long stest; + @SignednessGlb long gtest; + @SignedPositive long ptest; + + stest = c; + gtest = c; + ptest = c; + + stest = nnc; + gtest = nnc; + ptest = nnc; + + stest = pc; + gtest = pc; + ptest = pc; + + stest = spure; + // :: error: (assignment.type.incompatible) + gtest = spure; + // :: error: (assignment.type.incompatible) + ptest = spure; + + stest = smixed; + // :: error: (assignment.type.incompatible) + gtest = smixed; + // :: error: (assignment.type.incompatible) + ptest = smixed; + + stest = bmixed; + // :: error: (assignment.type.incompatible) + gtest = bmixed; + // :: error: (assignment.type.incompatible) + ptest = bmixed; + } } diff --git a/checker/tests/signedness/WideningConversion.java b/checker/tests/signedness/WideningConversion.java index a43a650a366..10e0dc177ae 100644 --- a/checker/tests/signedness/WideningConversion.java +++ b/checker/tests/signedness/WideningConversion.java @@ -3,98 +3,98 @@ public class WideningConversion { - char c1; - char c2; - int i1; - int i2; - @Signed int si1; - @Signed int si2; - @Unsigned int ui1; - @Unsigned int ui2; - @Unsigned short us1; - @Unsigned short us2; + char c1; + char c2; + int i1; + int i2; + @Signed int si1; + @Signed int si2; + @Unsigned int ui1; + @Unsigned int ui2; + @Unsigned short us1; + @Unsigned short us2; - void compare() { - boolean b; - b = c1 > c2; - b = c1 > i2; - b = i1 > c2; - b = i1 > i2; - } + void compare() { + boolean b; + b = c1 > c2; + b = c1 > i2; + b = i1 > c2; + b = i1 > i2; + } - void plus() { - // Not just "int si" because it's defaulted to TOP so every assignment would work. - @Signed int si; - si = c1 + c2; - si = c1 + i2; - si = i1 + c2; - si = i1 + i2; + void plus() { + // Not just "int si" because it's defaulted to TOP so every assignment would work. + @Signed int si; + si = c1 + c2; + si = c1 + i2; + si = i1 + c2; + si = i1 + i2; - si = c1 + c2; - si = c1 + si2; - si = si1 + c2; - si = si1 + si2; + si = c1 + c2; + si = c1 + si2; + si = si1 + c2; + si = si1 + si2; - si = c1 + c2; - // :: error: (assignment.type.incompatible) - si = c1 + ui2; - // :: error: (assignment.type.incompatible) - si = ui1 + c2; - // :: error: (assignment.type.incompatible) - si = ui1 + ui2; + si = c1 + c2; + // :: error: (assignment.type.incompatible) + si = c1 + ui2; + // :: error: (assignment.type.incompatible) + si = ui1 + c2; + // :: error: (assignment.type.incompatible) + si = ui1 + ui2; - @Unsigned int ui; - ui = c1 + c2; - // :: error: (assignment.type.incompatible) - ui = c1 + i2; - // :: error: (assignment.type.incompatible) - ui = i1 + c2; - // :: error: (assignment.type.incompatible) - ui = i1 + i2; + @Unsigned int ui; + ui = c1 + c2; + // :: error: (assignment.type.incompatible) + ui = c1 + i2; + // :: error: (assignment.type.incompatible) + ui = i1 + c2; + // :: error: (assignment.type.incompatible) + ui = i1 + i2; - ui = c1 + c2; - // :: error: (assignment.type.incompatible) - ui = c1 + si2; - // :: error: (assignment.type.incompatible) - ui = si1 + c2; - // :: error: (assignment.type.incompatible) - ui = si1 + si2; + ui = c1 + c2; + // :: error: (assignment.type.incompatible) + ui = c1 + si2; + // :: error: (assignment.type.incompatible) + ui = si1 + c2; + // :: error: (assignment.type.incompatible) + ui = si1 + si2; - ui = c1 + c2; - ui = c1 + ui2; - ui = ui1 + c2; - ui = ui1 + ui2; + ui = c1 + c2; + ui = c1 + ui2; + ui = ui1 + c2; + ui = ui1 + ui2; - // All of these are illegal in Java, without an explicit cast. - // char c; - // c = c1 + c2; - // c = c1 + i2; - // c = i1 + c2; - // c = i1 + i2; + // All of these are illegal in Java, without an explicit cast. + // char c; + // c = c1 + c2; + // c = c1 + i2; + // c = i1 + c2; + // c = i1 + i2; - char c; - c = (char) (c1 + c2); - c = (char) (c1 + i2); - c = (char) (i1 + c2); - c = (char) (i1 + i2); + char c; + c = (char) (c1 + c2); + c = (char) (c1 + i2); + c = (char) (i1 + c2); + c = (char) (i1 + i2); - c = (char) (c1 + c2); - c = (char) (c1 + si2); - c = (char) (si1 + c2); - c = (char) (si1 + si2); + c = (char) (c1 + c2); + c = (char) (c1 + si2); + c = (char) (si1 + c2); + c = (char) (si1 + si2); - c = (char) (c1 + c2); - c = (char) (c1 + ui2); - c = (char) (ui1 + c2); - c = (char) (ui1 + ui2); - } + c = (char) (c1 + c2); + c = (char) (c1 + ui2); + c = (char) (ui1 + c2); + c = (char) (ui1 + ui2); + } - void to_string() { - // :: error: (unsigned.concat) - String s1 = "" + us1; - // :: error: (argument.type.incompatible) - String s2 = String.valueOf(us2); - // :: error: (argument.type.incompatible) - String s3 = Short.toString(us1); - } + void to_string() { + // :: error: (unsigned.concat) + String s1 = "" + us1; + // :: error: (argument.type.incompatible) + String s2 = String.valueOf(us2); + // :: error: (argument.type.incompatible) + String s3 = Short.toString(us1); + } } diff --git a/checker/tests/signedness/WideningFloat.java b/checker/tests/signedness/WideningFloat.java index cbdeb73a17d..11a4c575520 100644 --- a/checker/tests/signedness/WideningFloat.java +++ b/checker/tests/signedness/WideningFloat.java @@ -2,21 +2,21 @@ public class WideningFloat { - void floatArg(float x) {} + void floatArg(float x) {} - void m(Object arg) { - floatArg((Byte) arg); - } + void m(Object arg) { + floatArg((Byte) arg); + } - void m2(@UnknownSignedness Byte arg) { - floatArg(arg); - } + void m2(@UnknownSignedness Byte arg) { + floatArg(arg); + } - void m3(@UnknownSignedness Byte arg) { - float f = arg; - } + void m3(@UnknownSignedness Byte arg) { + float f = arg; + } - void m3(@UnknownSignedness byte arg) { - float f = arg; - } + void m3(@UnknownSignedness byte arg) { + float f = arg; + } } diff --git a/checker/tests/signedness/WideningInitialization.java b/checker/tests/signedness/WideningInitialization.java index 469ca80996c..3422848d812 100644 --- a/checker/tests/signedness/WideningInitialization.java +++ b/checker/tests/signedness/WideningInitialization.java @@ -1,34 +1,34 @@ import org.checkerframework.checker.signedness.qual.Signed; public class WideningInitialization { - public int findLineNr(int pc, char startPC, char lineNr) { - int ln = 0; - for (int i = 0; i < 3; i++) { - if (startPC <= pc) { - ln = lineNr; - } else { + public int findLineNr(int pc, char startPC, char lineNr) { + int ln = 0; + for (int i = 0; i < 3; i++) { + if (startPC <= pc) { + ln = lineNr; + } else { + return ln; + } + } return ln; - } } - return ln; - } - public int findLineNr2(char lineNr) { - int ln = 0; - ln = lineNr; - return ln; - } + public int findLineNr2(char lineNr) { + int ln = 0; + ln = lineNr; + return ln; + } - public void findLineNr3a(char lineNr) { - @Signed int ln = lineNr; - } + public void findLineNr3a(char lineNr) { + @Signed int ln = lineNr; + } - public int findLineNr3b(char lineNr) { - int ln = lineNr; - return ln; - } + public int findLineNr3b(char lineNr) { + int ln = lineNr; + return ln; + } - public int findLineNr4(char lineNr) { - return lineNr; - } + public int findLineNr4(char lineNr) { + return lineNr; + } } diff --git a/checker/tests/signedness/java17/Issue6100.java b/checker/tests/signedness/java17/Issue6100.java index 6fa84d0270b..eaa84fb7cbe 100644 --- a/checker/tests/signedness/java17/Issue6100.java +++ b/checker/tests/signedness/java17/Issue6100.java @@ -1,17 +1,18 @@ // @below-java17-jdk-skip-test // @infer-jaifs-skip-test The AFU's JAIF reading/writing libraries don't support records. -import java.util.List; import org.checkerframework.checker.index.qual.NonNegative; +import java.util.List; + public record Issue6100(List<@NonNegative Integer> bar) { - public Issue6100 { - List<@NonNegative Integer> b = bar; - // :: error: (assignment.type.incompatible) - List b2 = bar; - if (bar.size() < 0) { - throw new IllegalArgumentException(); + public Issue6100 { + List<@NonNegative Integer> b = bar; + // :: error: (assignment.type.incompatible) + List b2 = bar; + if (bar.size() < 0) { + throw new IllegalArgumentException(); + } } - } } diff --git a/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java b/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java index a60b6569f52..d2bec564ad8 100644 --- a/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java +++ b/checker/tests/stubparser-nullness/MultidimentionalArrayAnnotationTest.java @@ -20,197 +20,197 @@ */ public class MultidimentionalArrayAnnotationTest { - int numb = 1; - - // Declared 8 3-dimentional variables. - Object @Nullable [] @Nullable [] @Nullable [] obj1 = new Object[numb][numb][numb]; - Object @NonNull [] @Nullable [] @Nullable [] obj2 = new Object[numb][numb][numb]; - Object @Nullable [] @NonNull [] @Nullable [] obj3 = new Object[numb][numb][numb]; - Object @Nullable [] @Nullable [] @NonNull [] obj4 = new Object[numb][numb][numb]; - Object @NonNull [] @NonNull [] @Nullable [] obj5 = new Object[numb][numb][numb]; - Object @NonNull [] @Nullable [] @NonNull [] obj6 = new Object[numb][numb][numb]; - Object @Nullable [] @NonNull [] @NonNull [] obj7 = new Object[numb][numb][numb]; - Object @NonNull [] @NonNull [] @NonNull [] obj8 = new Object[numb][numb][numb]; - - /* - * Call to method 1 that returns Object @NonNull [] @NonNull [] @NonNull []. - * Errors are not expected. - */ - void callTomethod1() { - obj1 = method1(); - obj2 = method1(); - obj3 = method1(); - obj4 = method1(); - obj5 = method1(); - obj6 = method1(); - obj7 = method1(); - obj8 = method1(); - } - - /* - * Call to method 2 that returns Object @Nullable [] @NonNull [] @NonNull []. - */ - void callTomethod2() { - obj1 = method2(); - // :: error: (assignment.type.incompatible) - obj2 = method2(); - obj3 = method2(); - obj4 = method2(); - // :: error: (assignment.type.incompatible) - obj5 = method2(); - // :: error: (assignment.type.incompatible) - obj6 = method2(); - obj7 = method2(); - // :: error: (assignment.type.incompatible) - obj8 = method2(); - } - - /* - * Call to method 3 that returns Object @NonNull [] @Nullable [] @NonNull []. - */ - void callTomethod3() { - obj1 = method3(); - obj2 = method3(); - // :: error: (assignment.type.incompatible) - obj3 = method3(); - obj4 = method3(); - // :: error: (assignment.type.incompatible) - obj5 = method3(); - obj6 = method3(); - // :: error: (assignment.type.incompatible) - obj7 = method3(); - // :: error: (assignment.type.incompatible) - obj8 = method3(); - } - - /* - * Call to method 4 that returns Object @NonNull [] @NonNull [] @Nullable []. - */ - void callTomethod4() { - obj1 = method4(); - obj2 = method4(); - obj3 = method4(); - // :: error: (assignment.type.incompatible) - obj4 = method4(); - obj5 = method4(); - // :: error: (assignment.type.incompatible) - obj6 = method4(); - // :: error: (assignment.type.incompatible) - obj7 = method4(); - // :: error: (assignment.type.incompatible) - obj8 = method4(); - } - - /* - * Call to method 5 that returns Object @Nullable [] @Nullable [] @NonNull []. - */ - void callTomethod5() { - obj1 = method5(); - // :: error: (assignment.type.incompatible) - obj2 = method5(); - // :: error: (assignment.type.incompatible) - obj3 = method5(); - obj4 = method5(); - // :: error: (assignment.type.incompatible) - obj5 = method5(); - // :: error: (assignment.type.incompatible) - obj6 = method5(); - // :: error: (assignment.type.incompatible) - obj7 = method5(); - // :: error: (assignment.type.incompatible) - obj8 = method5(); - } - - /* - * Call to method 6 that returns Object @Nullable [] @NonNull [] @Nullable []. - */ - void callTomethod6() { - obj1 = method6(); - // :: error: (assignment.type.incompatible) - obj2 = method6(); - obj3 = method6(); - // :: error: (assignment.type.incompatible) - obj4 = method6(); - // :: error: (assignment.type.incompatible) - obj5 = method6(); - // :: error: (assignment.type.incompatible) - obj6 = method6(); - // :: error: (assignment.type.incompatible) - obj7 = method6(); - // :: error: (assignment.type.incompatible) - obj8 = method6(); - } - - /* - * Call to method 7 that returns Object @NonNull [] @Nullable [] @Nullable []. - */ - void callTomethod7() { - obj1 = method7(); - obj2 = method7(); - // :: error: (assignment.type.incompatible) - obj3 = method7(); - // :: error: (assignment.type.incompatible) - obj4 = method7(); - // :: error: (assignment.type.incompatible) - obj5 = method7(); - // :: error: (assignment.type.incompatible) - obj6 = method7(); - // :: error: (assignment.type.incompatible) - obj7 = method7(); - // :: error: (assignment.type.incompatible) - obj8 = method7(); - } - - /* - * Call to method 8 that returns Object @Nullable [] @Nullable [] @Nullable []. - */ - void callTomethod8() { - obj1 = method8(); - // :: error: (assignment.type.incompatible) - obj2 = method8(); - // :: error: (assignment.type.incompatible) - obj3 = method8(); - // :: error: (assignment.type.incompatible) - obj4 = method8(); - // :: error: (assignment.type.incompatible) - obj5 = method8(); - // :: error: (assignment.type.incompatible) - obj6 = method8(); - // :: error: (assignment.type.incompatible) - obj7 = method8(); - // :: error: (assignment.type.incompatible) - obj8 = method8(); - } - - Object[][][] method1() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [][][] method2() { - return new Object[numb][numb][numb]; - } - - Object[] @Nullable [][] method3() { - return new Object[numb][numb][numb]; - } - - Object[][] @Nullable [] method4() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [] @Nullable [][] method5() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [][] @Nullable [] method6() { - return new Object[numb][numb][numb]; - } - - Object[] @Nullable [] @Nullable [] method7() { - return new Object[numb][numb][numb]; - } - - Object @Nullable [] @Nullable [] @Nullable [] method8() { - return new Object[numb][numb][numb]; - } + int numb = 1; + + // Declared 8 3-dimentional variables. + Object @Nullable [] @Nullable [] @Nullable [] obj1 = new Object[numb][numb][numb]; + Object @NonNull [] @Nullable [] @Nullable [] obj2 = new Object[numb][numb][numb]; + Object @Nullable [] @NonNull [] @Nullable [] obj3 = new Object[numb][numb][numb]; + Object @Nullable [] @Nullable [] @NonNull [] obj4 = new Object[numb][numb][numb]; + Object @NonNull [] @NonNull [] @Nullable [] obj5 = new Object[numb][numb][numb]; + Object @NonNull [] @Nullable [] @NonNull [] obj6 = new Object[numb][numb][numb]; + Object @Nullable [] @NonNull [] @NonNull [] obj7 = new Object[numb][numb][numb]; + Object @NonNull [] @NonNull [] @NonNull [] obj8 = new Object[numb][numb][numb]; + + /* + * Call to method 1 that returns Object @NonNull [] @NonNull [] @NonNull []. + * Errors are not expected. + */ + void callTomethod1() { + obj1 = method1(); + obj2 = method1(); + obj3 = method1(); + obj4 = method1(); + obj5 = method1(); + obj6 = method1(); + obj7 = method1(); + obj8 = method1(); + } + + /* + * Call to method 2 that returns Object @Nullable [] @NonNull [] @NonNull []. + */ + void callTomethod2() { + obj1 = method2(); + // :: error: (assignment.type.incompatible) + obj2 = method2(); + obj3 = method2(); + obj4 = method2(); + // :: error: (assignment.type.incompatible) + obj5 = method2(); + // :: error: (assignment.type.incompatible) + obj6 = method2(); + obj7 = method2(); + // :: error: (assignment.type.incompatible) + obj8 = method2(); + } + + /* + * Call to method 3 that returns Object @NonNull [] @Nullable [] @NonNull []. + */ + void callTomethod3() { + obj1 = method3(); + obj2 = method3(); + // :: error: (assignment.type.incompatible) + obj3 = method3(); + obj4 = method3(); + // :: error: (assignment.type.incompatible) + obj5 = method3(); + obj6 = method3(); + // :: error: (assignment.type.incompatible) + obj7 = method3(); + // :: error: (assignment.type.incompatible) + obj8 = method3(); + } + + /* + * Call to method 4 that returns Object @NonNull [] @NonNull [] @Nullable []. + */ + void callTomethod4() { + obj1 = method4(); + obj2 = method4(); + obj3 = method4(); + // :: error: (assignment.type.incompatible) + obj4 = method4(); + obj5 = method4(); + // :: error: (assignment.type.incompatible) + obj6 = method4(); + // :: error: (assignment.type.incompatible) + obj7 = method4(); + // :: error: (assignment.type.incompatible) + obj8 = method4(); + } + + /* + * Call to method 5 that returns Object @Nullable [] @Nullable [] @NonNull []. + */ + void callTomethod5() { + obj1 = method5(); + // :: error: (assignment.type.incompatible) + obj2 = method5(); + // :: error: (assignment.type.incompatible) + obj3 = method5(); + obj4 = method5(); + // :: error: (assignment.type.incompatible) + obj5 = method5(); + // :: error: (assignment.type.incompatible) + obj6 = method5(); + // :: error: (assignment.type.incompatible) + obj7 = method5(); + // :: error: (assignment.type.incompatible) + obj8 = method5(); + } + + /* + * Call to method 6 that returns Object @Nullable [] @NonNull [] @Nullable []. + */ + void callTomethod6() { + obj1 = method6(); + // :: error: (assignment.type.incompatible) + obj2 = method6(); + obj3 = method6(); + // :: error: (assignment.type.incompatible) + obj4 = method6(); + // :: error: (assignment.type.incompatible) + obj5 = method6(); + // :: error: (assignment.type.incompatible) + obj6 = method6(); + // :: error: (assignment.type.incompatible) + obj7 = method6(); + // :: error: (assignment.type.incompatible) + obj8 = method6(); + } + + /* + * Call to method 7 that returns Object @NonNull [] @Nullable [] @Nullable []. + */ + void callTomethod7() { + obj1 = method7(); + obj2 = method7(); + // :: error: (assignment.type.incompatible) + obj3 = method7(); + // :: error: (assignment.type.incompatible) + obj4 = method7(); + // :: error: (assignment.type.incompatible) + obj5 = method7(); + // :: error: (assignment.type.incompatible) + obj6 = method7(); + // :: error: (assignment.type.incompatible) + obj7 = method7(); + // :: error: (assignment.type.incompatible) + obj8 = method7(); + } + + /* + * Call to method 8 that returns Object @Nullable [] @Nullable [] @Nullable []. + */ + void callTomethod8() { + obj1 = method8(); + // :: error: (assignment.type.incompatible) + obj2 = method8(); + // :: error: (assignment.type.incompatible) + obj3 = method8(); + // :: error: (assignment.type.incompatible) + obj4 = method8(); + // :: error: (assignment.type.incompatible) + obj5 = method8(); + // :: error: (assignment.type.incompatible) + obj6 = method8(); + // :: error: (assignment.type.incompatible) + obj7 = method8(); + // :: error: (assignment.type.incompatible) + obj8 = method8(); + } + + Object[][][] method1() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [][][] method2() { + return new Object[numb][numb][numb]; + } + + Object[] @Nullable [][] method3() { + return new Object[numb][numb][numb]; + } + + Object[][] @Nullable [] method4() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [] @Nullable [][] method5() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [][] @Nullable [] method6() { + return new Object[numb][numb][numb]; + } + + Object[] @Nullable [] @Nullable [] method7() { + return new Object[numb][numb][numb]; + } + + Object @Nullable [] @Nullable [] @Nullable [] method8() { + return new Object[numb][numb][numb]; + } } diff --git a/checker/tests/stubparser-nullness/NoExplicitAnnotations.java b/checker/tests/stubparser-nullness/NoExplicitAnnotations.java index 5aa61cf314f..82e72708ca5 100644 --- a/checker/tests/stubparser-nullness/NoExplicitAnnotations.java +++ b/checker/tests/stubparser-nullness/NoExplicitAnnotations.java @@ -4,61 +4,61 @@ public class NoExplicitAnnotations {} class NoExplicitAnnotationsSuper { - @Nullable String method1() { - return helper(); - } + @Nullable String method1() { + return helper(); + } - @Nullable String method2() { - return helper(); - } + @Nullable String method2() { + return helper(); + } - @Nullable String method3() { - return helper(); - } + @Nullable String method3() { + return helper(); + } - @Nullable String helper() { - return null; - } + @Nullable String helper() { + return null; + } } class NoExplicitAnnotationsSub1 extends NoExplicitAnnotationsSuper { - @Override - String helper() { - return "hello"; - } + @Override + String helper() { + return "hello"; + } } class NoExplicitAnnotationsSub2 extends NoExplicitAnnotationsSuper { - @Override - String helper() { - return "hello"; - } + @Override + String helper() { + return "hello"; + } } class NoExplicitAnnotationsSub3 extends NoExplicitAnnotationsSuper { - @Override - String helper() { - return "hello"; - } + @Override + String helper() { + return "hello"; + } } class NoExplicitAnnotationsUse { - @Nullable String nble = null; - @NonNull String nn = "hello"; + @Nullable String nble = null; + @NonNull String nn = "hello"; - void use( - NoExplicitAnnotationsSub1 sub1, - NoExplicitAnnotationsSub2 sub2, - NoExplicitAnnotationsSub3 sub3) { - nble = sub1.method1(); - nn = sub1.method1(); - nble = sub2.method2(); - nn = sub2.method2(); - nble = sub3.method3(); - // :: error: (assignment.type.incompatible) - nn = sub3.method3(); + void use( + NoExplicitAnnotationsSub1 sub1, + NoExplicitAnnotationsSub2 sub2, + NoExplicitAnnotationsSub3 sub3) { + nble = sub1.method1(); + nn = sub1.method1(); + nble = sub2.method2(); + nn = sub2.method2(); + nble = sub3.method3(); + // :: error: (assignment.type.incompatible) + nn = sub3.method3(); - // :: error: (assignment.type.incompatible) - nn = nble; - } + // :: error: (assignment.type.incompatible) + nn = nble; + } } diff --git a/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java b/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java index ce0c9783a7f..fc027aa2567 100644 --- a/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java +++ b/checker/tests/stubparser-nullness/VarargConstructorParameterAnnotationTest.java @@ -5,21 +5,21 @@ */ public class VarargConstructorParameterAnnotationTest { - public void strArraysNonNull(@NonNull String[] parameter) { - new ProcessBuilder(parameter); - } + public void strArraysNonNull(@NonNull String[] parameter) { + new ProcessBuilder(parameter); + } - public void strArraysNullable(@Nullable String[] parameter) { - // :: error: (argument.type.incompatible) - new ProcessBuilder(parameter); - } + public void strArraysNullable(@Nullable String[] parameter) { + // :: error: (argument.type.incompatible) + new ProcessBuilder(parameter); + } - public void strVarargNonNull(@NonNull String... parameter) { - new ProcessBuilder(parameter); - } + public void strVarargNonNull(@NonNull String... parameter) { + new ProcessBuilder(parameter); + } - public void strVarargNullable(@Nullable String... parameter) { - // :: error: (argument.type.incompatible) - new ProcessBuilder(parameter); - } + public void strVarargNullable(@Nullable String... parameter) { + // :: error: (argument.type.incompatible) + new ProcessBuilder(parameter); + } } diff --git a/checker/tests/stubparser-records/PairRecord.java b/checker/tests/stubparser-records/PairRecord.java index eb7fbf66386..0b868db501e 100644 --- a/checker/tests/stubparser-records/PairRecord.java +++ b/checker/tests/stubparser-records/PairRecord.java @@ -1,6 +1,6 @@ record PairRecord(String key, Object value) { - PairRecord(String val) { - this("", val); - } + PairRecord(String val) { + this("", val); + } } diff --git a/checker/tests/stubparser-records/RecordStubbed.java b/checker/tests/stubparser-records/RecordStubbed.java index fc48281915e..e04bc66620f 100644 --- a/checker/tests/stubparser-records/RecordStubbed.java +++ b/checker/tests/stubparser-records/RecordStubbed.java @@ -6,7 +6,7 @@ * record but nullable in constructor and accessor via stubs */ public record RecordStubbed(@Nullable String nxx, String nsxx, Integer xnn) { - RecordStubbed(Integer a, String b, String c) { - this(c, b, a); - } + RecordStubbed(Integer a, String b, String c) { + this(c, b, a); + } } diff --git a/checker/tests/stubparser-records/RecordUsage.java b/checker/tests/stubparser-records/RecordUsage.java index 88258035fbd..228959e2660 100644 --- a/checker/tests/stubparser-records/RecordUsage.java +++ b/checker/tests/stubparser-records/RecordUsage.java @@ -1,24 +1,24 @@ import org.checkerframework.checker.nullness.qual.NonNull; class PairUsage { - public void makePairs() { - PairRecord a = new PairRecord("key", "value"); - PairRecord b = new PairRecord(null); - // :: error: (assignment.type.incompatible) - @NonNull Object o = a.value(); - PairRecord p = new PairRecord("key", null); - } + public void makePairs() { + PairRecord a = new PairRecord("key", "value"); + PairRecord b = new PairRecord(null); + // :: error: (assignment.type.incompatible) + @NonNull Object o = a.value(); + PairRecord p = new PairRecord("key", null); + } - public void makeStubbed() { - RecordStubbed r = new RecordStubbed("a", "b", 7); - RecordStubbed r1 = new RecordStubbed("a", "b", null); - // :: error: (argument.type.incompatible) - RecordStubbed r2 = new RecordStubbed((String) null, "b", null); - // :: error: (argument.type.incompatible) - RecordStubbed r3 = new RecordStubbed("a", null, null); - @NonNull Object o = r.nxx(); - @NonNull Object o2 = r.nsxx(); - // :: error: (assignment.type.incompatible) - @NonNull Object o3 = r.xnn(); - } + public void makeStubbed() { + RecordStubbed r = new RecordStubbed("a", "b", 7); + RecordStubbed r1 = new RecordStubbed("a", "b", null); + // :: error: (argument.type.incompatible) + RecordStubbed r2 = new RecordStubbed((String) null, "b", null); + // :: error: (argument.type.incompatible) + RecordStubbed r3 = new RecordStubbed("a", null, null); + @NonNull Object o = r.nxx(); + @NonNull Object o2 = r.nsxx(); + // :: error: (assignment.type.incompatible) + @NonNull Object o3 = r.xnn(); + } } diff --git a/checker/tests/stubparser-tainting/FakeOverrideRSuper.java b/checker/tests/stubparser-tainting/FakeOverrideRSuper.java index 6af907de5b6..25a990c38a2 100644 --- a/checker/tests/stubparser-tainting/FakeOverrideRSuper.java +++ b/checker/tests/stubparser-tainting/FakeOverrideRSuper.java @@ -6,27 +6,27 @@ @SuppressWarnings("tainting") public class FakeOverrideRSuper { - public @Tainted int returnsTaintedInt() { - return 0; - } + public @Tainted int returnsTaintedInt() { + return 0; + } - public @Untainted int returnsUntaintedInt() { - return 0; - } + public @Untainted int returnsUntaintedInt() { + return 0; + } - public @Tainted int returnsTaintedIntWithFakeOverride() { - return 0; - } + public @Tainted int returnsTaintedIntWithFakeOverride() { + return 0; + } - public @Untainted int returnsUntaintedIntWithFakeOverride() { - return 0; - } + public @Untainted int returnsUntaintedIntWithFakeOverride() { + return 0; + } - public @Untainted int returnsUntaintedIntWithFakeOverride2() { - return 0; - } + public @Untainted int returnsUntaintedIntWithFakeOverride2() { + return 0; + } - public @PolyTainted int returnsPolyTaintedIntWithFakeOverride() { - return 0; - } + public @PolyTainted int returnsPolyTaintedIntWithFakeOverride() { + return 0; + } } diff --git a/checker/tests/stubparser-tainting/FakeOverrideReturn.java b/checker/tests/stubparser-tainting/FakeOverrideReturn.java index 951b71efda6..5af14e890e4 100644 --- a/checker/tests/stubparser-tainting/FakeOverrideReturn.java +++ b/checker/tests/stubparser-tainting/FakeOverrideReturn.java @@ -4,56 +4,56 @@ public class FakeOverrideReturn { - @Tainted int tf; - - @Untainted int uf; - - void m(@Tainted int t, @Untainted int u) { - - FakeOverrideRSuper sup = new FakeOverrideRSuper(); - FakeOverrideRMid mid = new FakeOverrideRMid(); - FakeOverrideRSub sub = new FakeOverrideRSub(); - - tf = sup.returnsTaintedInt(); - tf = mid.returnsTaintedInt(); - tf = sub.returnsTaintedInt(); - // :: error: (assignment.type.incompatible) - uf = sup.returnsTaintedInt(); - // :: error: (assignment.type.incompatible) - uf = mid.returnsTaintedInt(); - // :: error: (assignment.type.incompatible) - uf = sub.returnsTaintedInt(); - - tf = sup.returnsUntaintedInt(); - tf = mid.returnsUntaintedInt(); - tf = sub.returnsUntaintedInt(); - uf = sup.returnsUntaintedInt(); - uf = mid.returnsUntaintedInt(); - uf = sub.returnsUntaintedInt(); - - tf = sup.returnsTaintedIntWithFakeOverride(); - tf = mid.returnsTaintedIntWithFakeOverride(); - tf = sub.returnsTaintedIntWithFakeOverride(); - // :: error: (assignment.type.incompatible) - uf = sup.returnsTaintedIntWithFakeOverride(); - uf = mid.returnsTaintedIntWithFakeOverride(); - uf = sub.returnsTaintedIntWithFakeOverride(); - - tf = sup.returnsUntaintedIntWithFakeOverride(); - tf = mid.returnsUntaintedIntWithFakeOverride(); - tf = sub.returnsUntaintedIntWithFakeOverride(); - uf = sup.returnsUntaintedIntWithFakeOverride(); - // :: error: (assignment.type.incompatible) - uf = mid.returnsUntaintedIntWithFakeOverride(); - // :: error: (assignment.type.incompatible) - uf = sub.returnsUntaintedIntWithFakeOverride(); - } - - void poly() { - FakeOverrideRSuper sup = new FakeOverrideRSuper(); - FakeOverrideRMid mid = new FakeOverrideRMid(); - - @Untainted int j = mid.returnsUntaintedIntWithFakeOverride2(); - @Untainted int k = sup.returnsPolyTaintedIntWithFakeOverride(); - } + @Tainted int tf; + + @Untainted int uf; + + void m(@Tainted int t, @Untainted int u) { + + FakeOverrideRSuper sup = new FakeOverrideRSuper(); + FakeOverrideRMid mid = new FakeOverrideRMid(); + FakeOverrideRSub sub = new FakeOverrideRSub(); + + tf = sup.returnsTaintedInt(); + tf = mid.returnsTaintedInt(); + tf = sub.returnsTaintedInt(); + // :: error: (assignment.type.incompatible) + uf = sup.returnsTaintedInt(); + // :: error: (assignment.type.incompatible) + uf = mid.returnsTaintedInt(); + // :: error: (assignment.type.incompatible) + uf = sub.returnsTaintedInt(); + + tf = sup.returnsUntaintedInt(); + tf = mid.returnsUntaintedInt(); + tf = sub.returnsUntaintedInt(); + uf = sup.returnsUntaintedInt(); + uf = mid.returnsUntaintedInt(); + uf = sub.returnsUntaintedInt(); + + tf = sup.returnsTaintedIntWithFakeOverride(); + tf = mid.returnsTaintedIntWithFakeOverride(); + tf = sub.returnsTaintedIntWithFakeOverride(); + // :: error: (assignment.type.incompatible) + uf = sup.returnsTaintedIntWithFakeOverride(); + uf = mid.returnsTaintedIntWithFakeOverride(); + uf = sub.returnsTaintedIntWithFakeOverride(); + + tf = sup.returnsUntaintedIntWithFakeOverride(); + tf = mid.returnsUntaintedIntWithFakeOverride(); + tf = sub.returnsUntaintedIntWithFakeOverride(); + uf = sup.returnsUntaintedIntWithFakeOverride(); + // :: error: (assignment.type.incompatible) + uf = mid.returnsUntaintedIntWithFakeOverride(); + // :: error: (assignment.type.incompatible) + uf = sub.returnsUntaintedIntWithFakeOverride(); + } + + void poly() { + FakeOverrideRSuper sup = new FakeOverrideRSuper(); + FakeOverrideRMid mid = new FakeOverrideRMid(); + + @Untainted int j = mid.returnsUntaintedIntWithFakeOverride2(); + @Untainted int k = sup.returnsPolyTaintedIntWithFakeOverride(); + } } diff --git a/checker/tests/stubparser-tainting/TypeParamWithInner.java b/checker/tests/stubparser-tainting/TypeParamWithInner.java index e491bc0cb59..6a96162f7ed 100644 --- a/checker/tests/stubparser-tainting/TypeParamWithInner.java +++ b/checker/tests/stubparser-tainting/TypeParamWithInner.java @@ -3,9 +3,9 @@ // T extends @Untainted String in stub file. Tests bug where the presence of an inner class causes // annotations on type variables to be forgotten. public class TypeParamWithInner { - public class Inner {} + public class Inner {} - public void requiresUntainted(T param) { - @Untainted String s = param; - } + public void requiresUntainted(T param) { + @Untainted String s = param; + } } diff --git a/checker/tests/tainting/AnonymousProblem.java b/checker/tests/tainting/AnonymousProblem.java index a1b78a76c6a..dac7b2d4a78 100644 --- a/checker/tests/tainting/AnonymousProblem.java +++ b/checker/tests/tainting/AnonymousProblem.java @@ -1,5 +1,5 @@ import java.nio.file.SimpleFileVisitor; public class AnonymousProblem { - SimpleFileVisitor s = new SimpleFileVisitor() {}; + SimpleFileVisitor s = new SimpleFileVisitor() {}; } diff --git a/checker/tests/tainting/Buffer.java b/checker/tests/tainting/Buffer.java index 79acdebdbe5..c3d2b1bc534 100644 --- a/checker/tests/tainting/Buffer.java +++ b/checker/tests/tainting/Buffer.java @@ -1,83 +1,84 @@ -import java.util.ArrayList; -import java.util.List; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.framework.qual.HasQualifierParameter; +import java.util.ArrayList; +import java.util.List; + @HasQualifierParameter(Tainted.class) public class Buffer { - final List<@PolyTainted String> list = new ArrayList<>(); - @PolyTainted String someString = ""; - // :: error: (invalid.polymorphic.qualifier.use) - static @PolyTainted Object staticField; - - public @PolyTainted Buffer() {} + final List<@PolyTainted String> list = new ArrayList<>(); + @PolyTainted String someString = ""; + // :: error: (invalid.polymorphic.qualifier.use) + static @PolyTainted Object staticField; - public @Untainted Buffer(@Tainted String s) { - // :: error: (assignment.type.incompatible) - this.someString = s; - } + public @PolyTainted Buffer() {} - public @PolyTainted Buffer(@PolyTainted Buffer copy) {} + public @Untainted Buffer(@Tainted String s) { + // :: error: (assignment.type.incompatible) + this.someString = s; + } - public @PolyTainted Buffer append(@PolyTainted Buffer this, @PolyTainted String s) { - list.add(s); - someString = s; - return this; - } + public @PolyTainted Buffer(@PolyTainted Buffer copy) {} - public @PolyTainted String prettyPrint(@PolyTainted Buffer this) { - String prettyString = ""; - for (String s : list) { - prettyString += s + " ~~ "; + public @PolyTainted Buffer append(@PolyTainted Buffer this, @PolyTainted String s) { + list.add(s); + someString = s; + return this; } - return prettyString; - } - - public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { - // :: error: (argument.type.incompatible) - list.add(s); - // :: error: (assignment.type.incompatible) - someString = s; - return s; - } - static class Use { - void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { - buffer.list.add(untainted); - buffer.someString = untainted; - buffer.append(untainted); + public @PolyTainted String prettyPrint(@PolyTainted Buffer this) { + String prettyString = ""; + for (String s : list) { + prettyString += s + " ~~ "; + } + return prettyString; } - void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { - // :: error: (argument.type.incompatible) - buffer.list.add(tainted); - // :: error: (assignment.type.incompatible) - buffer.someString = tainted; - // :: error: (argument.type.incompatible) - buffer.append(tainted); + public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { + // :: error: (argument.type.incompatible) + list.add(s); + // :: error: (assignment.type.incompatible) + someString = s; + return s; } - void casts(@Untainted Object untainted, @Tainted Object tainted) { - @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Untainted Buffer b2 = (@Untainted Buffer) tainted; + static class Use { + void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { + buffer.list.add(untainted); + buffer.someString = untainted; + buffer.append(untainted); + } - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error + void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { + // :: error: (argument.type.incompatible) + buffer.list.add(tainted); + // :: error: (assignment.type.incompatible) + buffer.someString = tainted; + // :: error: (argument.type.incompatible) + buffer.append(tainted); + } - @Untainted Buffer b5 = (Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b6 = (Buffer) tainted; - } + void casts(@Untainted Object untainted, @Tainted Object tainted) { + @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Untainted Buffer b2 = (@Untainted Buffer) tainted; + + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error + + @Untainted Buffer b5 = (Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b6 = (Buffer) tainted; + } - void creation() { - @Untainted Buffer b1 = new @Untainted Buffer(); - @Tainted Buffer b2 = new @Tainted Buffer(); - @PolyTainted Buffer b3 = new @PolyTainted Buffer(); + void creation() { + @Untainted Buffer b1 = new @Untainted Buffer(); + @Tainted Buffer b2 = new @Tainted Buffer(); + @PolyTainted Buffer b3 = new @PolyTainted Buffer(); + } } - } } diff --git a/checker/tests/tainting/CaptureSubtype.java b/checker/tests/tainting/CaptureSubtype.java index 10c729097cf..086ace7e52c 100644 --- a/checker/tests/tainting/CaptureSubtype.java +++ b/checker/tests/tainting/CaptureSubtype.java @@ -3,14 +3,15 @@ public class CaptureSubtype { - class MyGeneric {} + class MyGeneric {} - class SubGeneric extends MyGeneric {} + class SubGeneric extends MyGeneric {} - class UseMyGeneric { - SubGeneric wildcardUnbounded = new SubGeneric<@Untainted Number>(); + class UseMyGeneric { + SubGeneric wildcardUnbounded = + new SubGeneric<@Untainted Number>(); - MyGeneric wildcardOutsideUB = wildcardUnbounded; - MyGeneric wildcardInsideUB2 = wildcardUnbounded; - } + MyGeneric wildcardOutsideUB = wildcardUnbounded; + MyGeneric wildcardInsideUB2 = wildcardUnbounded; + } } diff --git a/checker/tests/tainting/CaptureSubtype2.java b/checker/tests/tainting/CaptureSubtype2.java index 95785eef19e..e8080626ee2 100644 --- a/checker/tests/tainting/CaptureSubtype2.java +++ b/checker/tests/tainting/CaptureSubtype2.java @@ -1,25 +1,28 @@ -import java.util.function.Function; import org.checkerframework.checker.tainting.qual.Untainted; +import java.util.function.Function; + public class CaptureSubtype2 { - interface FFunction extends Function {} + interface FFunction extends Function {} - interface DInterface {} + interface DInterface {} - interface MInterface

          {} + interface MInterface

          {} - interface QInterface, V extends MInterface

          , P> {} + interface QInterface, V extends MInterface

          , P> {} - FFunction> r; + FFunction> r; - CaptureSubtype2( - FFunction< - String, - QInterface< - ? extends MInterface, ? extends MInterface, DInterface>> - r) { - // :: error: (assignment.type.incompatible) - this.r = r; - } + CaptureSubtype2( + FFunction< + String, + QInterface< + ? extends MInterface, + ? extends MInterface, + DInterface>> + r) { + // :: error: (assignment.type.incompatible) + this.r = r; + } } diff --git a/checker/tests/tainting/Casts.java b/checker/tests/tainting/Casts.java index 8fd5c998ad0..2713c0c67bc 100644 --- a/checker/tests/tainting/Casts.java +++ b/checker/tests/tainting/Casts.java @@ -3,68 +3,68 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class Casts { - @HasQualifierParameter(Tainted.class) - static class Buffer {} + @HasQualifierParameter(Tainted.class) + static class Buffer {} - @HasQualifierParameter(Tainted.class) - static class MyBuffer extends Buffer {} + @HasQualifierParameter(Tainted.class) + static class MyBuffer extends Buffer {} - void test( - @Tainted Buffer taintedBuf, - @Untainted Buffer untaintedBuf, - @Tainted Object taintedObj, - @Untainted Object untaintedObj) { - @Tainted Object o = (@Tainted Object) taintedBuf; - o = (@Tainted Object) untaintedObj; - o = (@Tainted Object) untaintedBuf; + void test( + @Tainted Buffer taintedBuf, + @Untainted Buffer untaintedBuf, + @Tainted Object taintedObj, + @Untainted Object untaintedObj) { + @Tainted Object o = (@Tainted Object) taintedBuf; + o = (@Tainted Object) untaintedObj; + o = (@Tainted Object) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) taintedObj; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedObj; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) taintedObj; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedObj; - // :: warning: (cast.unsafe) - o = (@Untainted Object) taintedObj; - // :: warning: (cast.unsafe) - o = (@Untainted Object) taintedBuf; - o = (@Untainted Object) untaintedBuf; + // :: warning: (cast.unsafe) + o = (@Untainted Object) taintedObj; + // :: warning: (cast.unsafe) + o = (@Untainted Object) taintedBuf; + o = (@Untainted Object) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedObj; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedBuf; - o = (@Untainted Buffer) untaintedObj; - } + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedObj; + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedBuf; + o = (@Untainted Buffer) untaintedObj; + } - void test2( - @Tainted Buffer taintedBuf, - @Untainted Buffer untaintedBuf, - @Tainted MyBuffer taintedMyBuf, - @Untainted MyBuffer untaintedMyBuff) { - @Tainted Object o = (@Tainted Buffer) taintedMyBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted Buffer) untaintedMyBuff; + void test2( + @Tainted Buffer taintedBuf, + @Untainted Buffer untaintedBuf, + @Tainted MyBuffer taintedMyBuf, + @Untainted MyBuffer untaintedMyBuff) { + @Tainted Object o = (@Tainted Buffer) taintedMyBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted Buffer) untaintedMyBuff; - o = (@Tainted MyBuffer) taintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted MyBuffer) untaintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Tainted MyBuffer) untaintedMyBuff; + o = (@Tainted MyBuffer) taintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted MyBuffer) untaintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Tainted MyBuffer) untaintedMyBuff; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedMyBuf; - // :: error: (invariant.cast.unsafe) - o = (@Untainted Buffer) taintedMyBuf; - o = (@Untainted Buffer) untaintedMyBuff; + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedMyBuf; + // :: error: (invariant.cast.unsafe) + o = (@Untainted Buffer) taintedMyBuf; + o = (@Untainted Buffer) untaintedMyBuff; - // :: error: (invariant.cast.unsafe) - o = (@Untainted MyBuffer) taintedBuf; - // :: error: (invariant.cast.unsafe) - o = (@Untainted MyBuffer) taintedMyBuf; - o = (@Untainted MyBuffer) untaintedMyBuff; - } + // :: error: (invariant.cast.unsafe) + o = (@Untainted MyBuffer) taintedBuf; + // :: error: (invariant.cast.unsafe) + o = (@Untainted MyBuffer) taintedMyBuf; + o = (@Untainted MyBuffer) untaintedMyBuff; + } } diff --git a/checker/tests/tainting/ClassQPTypeVarTest.java b/checker/tests/tainting/ClassQPTypeVarTest.java index e0a75b0a496..7cb25e86be5 100644 --- a/checker/tests/tainting/ClassQPTypeVarTest.java +++ b/checker/tests/tainting/ClassQPTypeVarTest.java @@ -4,34 +4,34 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class ClassQPTypeVarTest { - @HasQualifierParameter(Tainted.class) - interface Buffer { - void append(@PolyTainted String s); - } + @HasQualifierParameter(Tainted.class) + interface Buffer { + void append(@PolyTainted String s); + } - @Tainted T cast(T param) { - return param; - } + @Tainted T cast(T param) { + return param; + } - void bug(@Untainted Buffer b, @Tainted String s) { - // :: error: (argument.type.incompatible) - b.append(s); - // :: error: (type.argument.invalid.hasqualparam) - cast(b).append(s); - } + void bug(@Untainted Buffer b, @Tainted String s) { + // :: error: (argument.type.incompatible) + b.append(s); + // :: error: (type.argument.invalid.hasqualparam) + cast(b).append(s); + } - @Tainted T castBuffer(T param) { - return param; - } + @Tainted T castBuffer(T param) { + return param; + } - T identity(T param) { - @Tainted Buffer b = param; - return param; // ok - } + T identity(T param) { + @Tainted Buffer b = param; + return param; // ok + } - void use(@Untainted Buffer ub, @Tainted Buffer tb) { - // :: error: (type.arguments.not.inferred) - identity(ub); - identity(tb); // ok - } + void use(@Untainted Buffer ub, @Tainted Buffer tb) { + // :: error: (type.arguments.not.inferred) + identity(ub); + identity(tb); // ok + } } diff --git a/checker/tests/tainting/EnumTypeArgs.java b/checker/tests/tainting/EnumTypeArgs.java index bd1c46a7cd1..9742e75a806 100644 --- a/checker/tests/tainting/EnumTypeArgs.java +++ b/checker/tests/tainting/EnumTypeArgs.java @@ -3,12 +3,12 @@ public class EnumTypeArgs { - enum MyEnum { - CONST1, - CONST2, - } + enum MyEnum { + CONST1, + CONST2, + } - void method(@Untainted MyEnum e1, @Tainted MyEnum e2) { - e1.compareTo(e2); - } + void method(@Untainted MyEnum e1, @Tainted MyEnum e2) { + e1.compareTo(e2); + } } diff --git a/checker/tests/tainting/ExtendHasQual.java b/checker/tests/tainting/ExtendHasQual.java index 4ff22baf860..7f719449eea 100644 --- a/checker/tests/tainting/ExtendHasQual.java +++ b/checker/tests/tainting/ExtendHasQual.java @@ -4,38 +4,38 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class ExtendHasQual { - static class Super { - @SuppressWarnings("super.invocation.invalid") - @Untainted Super() {} - } + static class Super { + @SuppressWarnings("super.invocation.invalid") + @Untainted Super() {} + } - @HasQualifierParameter(Tainted.class) - static class Buffer extends Super {} + @HasQualifierParameter(Tainted.class) + static class Buffer extends Super {} - static class MyBuffer1 extends Buffer {} + static class MyBuffer1 extends Buffer {} - @HasQualifierParameter(Tainted.class) - static class MyBuffer2 extends Buffer {} + @HasQualifierParameter(Tainted.class) + static class MyBuffer2 extends Buffer {} - @HasQualifierParameter(Nullable.class) - // :: error: (invalid.qual.param) - static class MyBuffer3 extends Buffer {} + @HasQualifierParameter(Nullable.class) + // :: error: (invalid.qual.param) + static class MyBuffer3 extends Buffer {} - @HasQualifierParameter({Tainted.class, Nullable.class}) - static class MyBuffer4 extends Buffer {} + @HasQualifierParameter({Tainted.class, Nullable.class}) + static class MyBuffer4 extends Buffer {} - @HasQualifierParameter(Tainted.class) - interface BufferInterface {} + @HasQualifierParameter(Tainted.class) + interface BufferInterface {} - static class ImplementsBufferInterface1 implements BufferInterface {} + static class ImplementsBufferInterface1 implements BufferInterface {} - @HasQualifierParameter(Tainted.class) - static class ImplementsBufferInterface2 implements BufferInterface {} + @HasQualifierParameter(Tainted.class) + static class ImplementsBufferInterface2 implements BufferInterface {} - static class Both1 extends Buffer implements BufferInterface {} + static class Both1 extends Buffer implements BufferInterface {} - @HasQualifierParameter(Tainted.class) - static class Both2 extends Buffer implements BufferInterface {} + @HasQualifierParameter(Tainted.class) + static class Both2 extends Buffer implements BufferInterface {} - static class Both3 extends Super implements BufferInterface {} + static class Both3 extends Super implements BufferInterface {} } diff --git a/checker/tests/tainting/ExtendsAndAnnotation.java b/checker/tests/tainting/ExtendsAndAnnotation.java index f49bfe90d10..f569bff3c1d 100644 --- a/checker/tests/tainting/ExtendsAndAnnotation.java +++ b/checker/tests/tainting/ExtendsAndAnnotation.java @@ -6,14 +6,14 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class ExtendsAndAnnotation extends @Tainted Object { - void test(@Untainted ExtendsAndAnnotation c) { - // :: warning: (cast.unsafe.constructor.invocation) - Object o = new @Untainted ExtendsAndAnnotation(); - o = new @Tainted ExtendsAndAnnotation(); - } + void test(@Untainted ExtendsAndAnnotation c) { + // :: warning: (cast.unsafe.constructor.invocation) + Object o = new @Untainted ExtendsAndAnnotation(); + o = new @Tainted ExtendsAndAnnotation(); + } - @HasQualifierParameter(Tainted.class) - // :: error: (invalid.polymorphic.qualifier) - // :: error: (declaration.inconsistent.with.extends.clause) - static class Banana extends @PolyTainted Object {} + @HasQualifierParameter(Tainted.class) + // :: error: (invalid.polymorphic.qualifier) + // :: error: (declaration.inconsistent.with.extends.clause) + static class Banana extends @PolyTainted Object {} } diff --git a/checker/tests/tainting/GenericsEnclosing.java b/checker/tests/tainting/GenericsEnclosing.java index dc06dbc6250..45e1773a7b1 100644 --- a/checker/tests/tainting/GenericsEnclosing.java +++ b/checker/tests/tainting/GenericsEnclosing.java @@ -7,21 +7,21 @@ *

          Also see all-systems/GenericsEnclosing for the type-system independent test. */ class MyG { - X f; + X f; - void m(X p) {} + void m(X p) {} } class ExtMyG extends MyG<@Untainted Object> { - class EInner1 { - class EInner2 { - void bar() { - // :: error: (assignment.type.incompatible) - f = 1; - m("test"); - // :: error: (argument.type.incompatible) - m(1); - } + class EInner1 { + class EInner2 { + void bar() { + // :: error: (assignment.type.incompatible) + f = 1; + m("test"); + // :: error: (argument.type.incompatible) + m(1); + } + } } - } } diff --git a/checker/tests/tainting/HasQualParamDefaults.java b/checker/tests/tainting/HasQualParamDefaults.java index 6a6b7d8e48e..d62dd15d03c 100644 --- a/checker/tests/tainting/HasQualParamDefaults.java +++ b/checker/tests/tainting/HasQualParamDefaults.java @@ -1,156 +1,157 @@ -import java.util.ArrayList; -import java.util.List; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.framework.qual.HasQualifierParameter; -public class HasQualParamDefaults { - @HasQualifierParameter(Tainted.class) - public class Buffer { - final List<@PolyTainted String> list = new ArrayList<>(); - @PolyTainted String someString = ""; - - public Buffer() {} - - public @Untainted Buffer(@Tainted String s) { - // :: error: (assignment.type.incompatible) - this.someString = s; - } - - public Buffer(Buffer copy) { - this.list.addAll(copy.list); - this.someString = copy.someString; - } - - public Buffer append(@PolyTainted String s) { - list.add(s); - someString = s; - return this; - } - - public @PolyTainted String prettyPrint() { - String prettyString = list.get(1); - for (@PolyTainted String s : list) { - prettyString += s + " ~~ "; - } - return prettyString; - } - - public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { - // :: error: (argument.type.incompatible) - list.add(s); - // :: error: (assignment.type.incompatible) - someString = s; - return s; - } - - void initializeLocalTainted(@Tainted Buffer b) { - Buffer local = b; - @Tainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Untainted Buffer copy2 = local; - } - - void initializeLocalUntainted(@Untainted Buffer b) { - Buffer local = b; - @Untainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Tainted Buffer copy2 = local; - } - - void initializeLocalPolyTainted(@PolyTainted Buffer b) { - Buffer local = b; - @PolyTainted Buffer copy = local; - } - - void noInitializer(@Untainted Buffer b) { - Buffer local; - // :: error: (assignment.type.incompatible) - local = b; - } - } - - class Use { - void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { - buffer.list.add(untainted); - buffer.someString = untainted; - buffer.append(untainted); - } - - void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { - // :: error: (argument.type.incompatible) - buffer.list.add(tainted); - // :: error: (assignment.type.incompatible) - buffer.someString = tainted; - // :: error: (argument.type.incompatible) - buffer.append(tainted); - } - - void casts(@Untainted Object untainted, @Tainted Object tainted) { - @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Untainted Buffer b2 = (@Untainted Buffer) tainted; - - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error - - @Untainted Buffer b5 = (Buffer) untainted; // ok - // :: error: (invariant.cast.unsafe) - @Tainted Buffer b6 = (Buffer) tainted; - } - - void creation() { - @Untainted Buffer b1 = new @Untainted Buffer(); - @Tainted Buffer b2 = new @Tainted Buffer(); - @PolyTainted Buffer b3 = new @PolyTainted Buffer(); - } - } - - // For classes with @HasQualifierParameter, different defaulting rules are applied on that type - // inside the class body and outside the class body, so local variables need to be tested - // outside the class as well. - class LocalVars { - void initializeLocalTainted(@Tainted Buffer b) { - Buffer local = b; - @Tainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Untainted Buffer copy2 = local; - } - - void initializeLocalUntainted(@Untainted Buffer b) { - Buffer local = b; - @Untainted Buffer copy1 = local; - // :: error: (assignment.type.incompatible) - @Tainted Buffer copy2 = local; - } - - void initializeLocalPolyTainted(@PolyTainted Buffer b) { - Buffer local = b; - @PolyTainted Buffer copy = local; - } - - void noInitializer(@Untainted Buffer b) { - Buffer local; - // :: error: (assignment.type.incompatible) - local = b; - } - - // These next two cases test circular dependencies. Calculating the type of a local variable - // looks at the type of initializer, but if the type of the initializer depends on the type - // of the variable, then infinite recursion could occur. - - void testTypeVariableInference() { - GenericWithQualParam set = new GenericWithQualParam<>(); - } - - void testVariableInOwnInitializer() { - Buffer b = (b = null); - } - } +import java.util.ArrayList; +import java.util.List; - @HasQualifierParameter(Tainted.class) - static class GenericWithQualParam {} +public class HasQualParamDefaults { + @HasQualifierParameter(Tainted.class) + public class Buffer { + final List<@PolyTainted String> list = new ArrayList<>(); + @PolyTainted String someString = ""; + + public Buffer() {} + + public @Untainted Buffer(@Tainted String s) { + // :: error: (assignment.type.incompatible) + this.someString = s; + } + + public Buffer(Buffer copy) { + this.list.addAll(copy.list); + this.someString = copy.someString; + } + + public Buffer append(@PolyTainted String s) { + list.add(s); + someString = s; + return this; + } + + public @PolyTainted String prettyPrint() { + String prettyString = list.get(1); + for (@PolyTainted String s : list) { + prettyString += s + " ~~ "; + } + return prettyString; + } + + public @PolyTainted String unTaintedOnly(@Untainted Buffer this, @PolyTainted String s) { + // :: error: (argument.type.incompatible) + list.add(s); + // :: error: (assignment.type.incompatible) + someString = s; + return s; + } + + void initializeLocalTainted(@Tainted Buffer b) { + Buffer local = b; + @Tainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Untainted Buffer copy2 = local; + } + + void initializeLocalUntainted(@Untainted Buffer b) { + Buffer local = b; + @Untainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Tainted Buffer copy2 = local; + } + + void initializeLocalPolyTainted(@PolyTainted Buffer b) { + Buffer local = b; + @PolyTainted Buffer copy = local; + } + + void noInitializer(@Untainted Buffer b) { + Buffer local; + // :: error: (assignment.type.incompatible) + local = b; + } + } + + class Use { + void passingUses(@Untainted String untainted, @Untainted Buffer buffer) { + buffer.list.add(untainted); + buffer.someString = untainted; + buffer.append(untainted); + } + + void failingUses(@Tainted String tainted, @Untainted Buffer buffer) { + // :: error: (argument.type.incompatible) + buffer.list.add(tainted); + // :: error: (assignment.type.incompatible) + buffer.someString = tainted; + // :: error: (argument.type.incompatible) + buffer.append(tainted); + } + + void casts(@Untainted Object untainted, @Tainted Object tainted) { + @Untainted Buffer b1 = (@Untainted Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Untainted Buffer b2 = (@Untainted Buffer) tainted; + + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b3 = (@Tainted Buffer) untainted; // error + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b4 = (@Tainted Buffer) tainted; // error + + @Untainted Buffer b5 = (Buffer) untainted; // ok + // :: error: (invariant.cast.unsafe) + @Tainted Buffer b6 = (Buffer) tainted; + } + + void creation() { + @Untainted Buffer b1 = new @Untainted Buffer(); + @Tainted Buffer b2 = new @Tainted Buffer(); + @PolyTainted Buffer b3 = new @PolyTainted Buffer(); + } + } + + // For classes with @HasQualifierParameter, different defaulting rules are applied on that type + // inside the class body and outside the class body, so local variables need to be tested + // outside the class as well. + class LocalVars { + void initializeLocalTainted(@Tainted Buffer b) { + Buffer local = b; + @Tainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Untainted Buffer copy2 = local; + } + + void initializeLocalUntainted(@Untainted Buffer b) { + Buffer local = b; + @Untainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Tainted Buffer copy2 = local; + } + + void initializeLocalPolyTainted(@PolyTainted Buffer b) { + Buffer local = b; + @PolyTainted Buffer copy = local; + } + + void noInitializer(@Untainted Buffer b) { + Buffer local; + // :: error: (assignment.type.incompatible) + local = b; + } + + // These next two cases test circular dependencies. Calculating the type of a local variable + // looks at the type of initializer, but if the type of the initializer depends on the type + // of the variable, then infinite recursion could occur. + + void testTypeVariableInference() { + GenericWithQualParam set = new GenericWithQualParam<>(); + } + + void testVariableInOwnInitializer() { + Buffer b = (b = null); + } + } + + @HasQualifierParameter(Tainted.class) + static class GenericWithQualParam {} } diff --git a/checker/tests/tainting/HasQualifierParameterIsNonTop.java b/checker/tests/tainting/HasQualifierParameterIsNonTop.java index c95da332ecd..1e95afc087d 100644 --- a/checker/tests/tainting/HasQualifierParameterIsNonTop.java +++ b/checker/tests/tainting/HasQualifierParameterIsNonTop.java @@ -4,5 +4,5 @@ @HasQualifierParameter(Untainted.class) // :: error: (invalid.qual.param) class HasQualifierParameterIsNonTop { - @PolyTainted String input; + @PolyTainted String input; } diff --git a/checker/tests/tainting/InheritQualifierParameter.java b/checker/tests/tainting/InheritQualifierParameter.java index 5d77712a21c..3da5f7da47f 100644 --- a/checker/tests/tainting/InheritQualifierParameter.java +++ b/checker/tests/tainting/InheritQualifierParameter.java @@ -7,10 +7,10 @@ public class InheritQualifierParameter {} class SubHasQualifierParameter extends InheritQualifierParameter { - void test(@Untainted SubHasQualifierParameter arg) { - // :: error: (assignment.type.incompatible) - @Tainted SubHasQualifierParameter local = arg; - } + void test(@Untainted SubHasQualifierParameter arg) { + // :: error: (assignment.type.incompatible) + @Tainted SubHasQualifierParameter local = arg; + } } @NoQualifierParameter(Tainted.class) @@ -21,9 +21,9 @@ class SubHasQualifierParameter1 extends InheritQualifierParameter {} class InheritNoQualifierParameter {} class SubNoQualifierParameter extends InheritNoQualifierParameter { - void test(@Untainted SubNoQualifierParameter arg) { - @Tainted SubNoQualifierParameter local = arg; - } + void test(@Untainted SubNoQualifierParameter arg) { + @Tainted SubNoQualifierParameter local = arg; + } } @HasQualifierParameter(Tainted.class) diff --git a/checker/tests/tainting/InitializerDataflow.java b/checker/tests/tainting/InitializerDataflow.java index 7e3afe0b760..73d1f484e98 100644 --- a/checker/tests/tainting/InitializerDataflow.java +++ b/checker/tests/tainting/InitializerDataflow.java @@ -4,20 +4,20 @@ import org.checkerframework.framework.qual.HasQualifierParameter; public class InitializerDataflow { - @HasQualifierParameter(Tainted.class) - static class Buffer {} + @HasQualifierParameter(Tainted.class) + static class Buffer {} - @PolyTainted Buffer id(@PolyTainted String s) { - return null; - } + @PolyTainted Buffer id(@PolyTainted String s) { + return null; + } - void methodBuffer(@Untainted String s) { - Buffer b1 = id(s); + void methodBuffer(@Untainted String s) { + Buffer b1 = id(s); - String local = s; - Buffer b2 = id(local); + String local = s; + Buffer b2 = id(local); - @Untainted String local2 = s; - Buffer b3 = id(local2); - } + @Untainted String local2 = s; + Buffer b3 = id(local2); + } } diff --git a/checker/tests/tainting/InnerHasQualifierParameter.java b/checker/tests/tainting/InnerHasQualifierParameter.java index 34af014eb14..17b241271fe 100644 --- a/checker/tests/tainting/InnerHasQualifierParameter.java +++ b/checker/tests/tainting/InnerHasQualifierParameter.java @@ -4,15 +4,15 @@ @HasQualifierParameter(Tainted.class) public class InnerHasQualifierParameter { - @HasQualifierParameter(Tainted.class) - interface TestInterface { - public void testMethod(); - } + @HasQualifierParameter(Tainted.class) + interface TestInterface { + public void testMethod(); + } - public void test() { - TestInterface test = - new TestInterface() { - public void testMethod() {} - }; - } + public void test() { + TestInterface test = + new TestInterface() { + public void testMethod() {} + }; + } } diff --git a/checker/tests/tainting/Issue1111.java b/checker/tests/tainting/Issue1111.java index 7937c049e43..d881fece8a4 100644 --- a/checker/tests/tainting/Issue1111.java +++ b/checker/tests/tainting/Issue1111.java @@ -2,25 +2,26 @@ // https://github.com/typetools/checker-framework/issues/1111 // Additional test case in framework/tests/all-systems/Issue1111.java -import java.util.List; import org.checkerframework.checker.tainting.qual.Untainted; +import java.util.List; + public class Issue1111 { - void foo(Box box, List list) { - // :: error: (type.arguments.not.inferred) - bar(box, list); - } + void foo(Box box, List list) { + // :: error: (type.arguments.not.inferred) + bar(box, list); + } - void foo2(Box<@Untainted ? super Integer> box, List list) { - // :: error: (type.arguments.not.inferred) - bar(box, list); - } + void foo2(Box<@Untainted ? super Integer> box, List list) { + // :: error: (type.arguments.not.inferred) + bar(box, list); + } - void foo3(Box<@Untainted ? super Integer> box, List<@Untainted Integer> list) { - bar(box, list); - } + void foo3(Box<@Untainted ? super Integer> box, List<@Untainted Integer> list) { + bar(box, list); + } - void bar(Box box, Iterable list) {} + void bar(Box box, Iterable list) {} - class Box {} + class Box {} } diff --git a/checker/tests/tainting/Issue1705.java b/checker/tests/tainting/Issue1705.java index 351fbc31a75..871b788feb6 100644 --- a/checker/tests/tainting/Issue1705.java +++ b/checker/tests/tainting/Issue1705.java @@ -1,26 +1,27 @@ // Test case for Issue 1705 // https://github.com/typetools/checker-framework/issues/1705 -import java.util.function.Function; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Untainted; +import java.util.function.Function; + public class Issue1705 { - static class MySecondClass { - @PolyTainted MySecondClass doOnComplete(@PolyTainted MySecondClass this) { - throw new RuntimeException(); + static class MySecondClass { + @PolyTainted MySecondClass doOnComplete(@PolyTainted MySecondClass this) { + throw new RuntimeException(); + } } - } - @PolyTainted R to(@PolyTainted Issue1705 this, Function arg0) { - throw new RuntimeException(); - } + @PolyTainted R to(@PolyTainted Issue1705 this, Function arg0) { + throw new RuntimeException(); + } - static Function> empty() { - throw new RuntimeException(); - } + static Function> empty() { + throw new RuntimeException(); + } - void test(@Untainted Issue1705 a) { - @Untainted Object z = a.to(empty()).doOnComplete(); - } + void test(@Untainted Issue1705 a) { + @Untainted Object z = a.to(empty()).doOnComplete(); + } } diff --git a/checker/tests/tainting/Issue1942.java b/checker/tests/tainting/Issue1942.java index 8b874798554..b9087f418f5 100644 --- a/checker/tests/tainting/Issue1942.java +++ b/checker/tests/tainting/Issue1942.java @@ -1,18 +1,19 @@ -import java.util.List; import org.checkerframework.checker.tainting.qual.Untainted; +import java.util.List; + public class Issue1942 { - public interface LoadableExpression {} + public interface LoadableExpression {} - abstract static class OperatorSection> { - abstract A makeExpression(List<@Untainted A> expressions); - } + abstract static class OperatorSection> { + abstract A makeExpression(List<@Untainted A> expressions); + } - static class BinaryOperatorSection> extends OperatorSection { - @Override - // Override used to fail. - B makeExpression(List<@Untainted B> expressions) { - throw new RuntimeException(""); + static class BinaryOperatorSection> extends OperatorSection { + @Override + // Override used to fail. + B makeExpression(List<@Untainted B> expressions) { + throw new RuntimeException(""); + } } - } } diff --git a/checker/tests/tainting/Issue2107.java b/checker/tests/tainting/Issue2107.java index 6dc0f1f14dc..e2436174643 100644 --- a/checker/tests/tainting/Issue2107.java +++ b/checker/tests/tainting/Issue2107.java @@ -2,13 +2,13 @@ public abstract class Issue2107 { - abstract @PolyTainted int method(@PolyTainted Issue2107 this); + abstract @PolyTainted int method(@PolyTainted Issue2107 this); - @PolyTainted int method2(@PolyTainted Issue2107 this) { - return this.method(); - } + @PolyTainted int method2(@PolyTainted Issue2107 this) { + return this.method(); + } - @PolyTainted int method3(@PolyTainted Issue2107 this) { - return method(); - } + @PolyTainted int method3(@PolyTainted Issue2107 this) { + return method(); + } } diff --git a/checker/tests/tainting/Issue2156.java b/checker/tests/tainting/Issue2156.java index 3a09e369863..2372d3b14c6 100644 --- a/checker/tests/tainting/Issue2156.java +++ b/checker/tests/tainting/Issue2156.java @@ -7,16 +7,16 @@ import org.checkerframework.checker.tainting.qual.Untainted; enum SampleEnum { - @Untainted FIRST, - @Tainted SECOND; + @Untainted FIRST, + @Tainted SECOND; } public class Issue2156 { - void test() { - requireUntainted(SampleEnum.FIRST); - // :: error: (assignment.type.incompatible) - requireUntainted(SampleEnum.SECOND); - } + void test() { + requireUntainted(SampleEnum.FIRST); + // :: error: (assignment.type.incompatible) + requireUntainted(SampleEnum.SECOND); + } - void requireUntainted(@Untainted SampleEnum sEnum) {} + void requireUntainted(@Untainted SampleEnum sEnum) {} } diff --git a/checker/tests/tainting/Issue2159.java b/checker/tests/tainting/Issue2159.java index 1d569780f2a..f5fa4202554 100644 --- a/checker/tests/tainting/Issue2159.java +++ b/checker/tests/tainting/Issue2159.java @@ -3,29 +3,29 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue2159 { - @Tainted Issue2159() {} + @Tainted Issue2159() {} - static class MyClass extends Issue2159 { - MyClass() {} + static class MyClass extends Issue2159 { + MyClass() {} - // :: error: (super.invocation.invalid) - @PolyTainted MyClass(@PolyTainted Object x) {} + // :: error: (super.invocation.invalid) + @PolyTainted MyClass(@PolyTainted Object x) {} - void testPolyTaintedLocal( - @PolyTainted Object input, @Untainted Object untainted, @Tainted Object tainted) { - // :: warning: (cast.unsafe) - @PolyTainted Object local = (@PolyTainted MyClass) new MyClass(); - // :: warning: (cast.unsafe.constructor.invocation) - @PolyTainted Object local1 = new @PolyTainted MyClass(); - // :: warning: (cast.unsafe.constructor.invocation) - @Untainted Object local2 = new @Untainted MyClass(); + void testPolyTaintedLocal( + @PolyTainted Object input, @Untainted Object untainted, @Tainted Object tainted) { + // :: warning: (cast.unsafe) + @PolyTainted Object local = (@PolyTainted MyClass) new MyClass(); + // :: warning: (cast.unsafe.constructor.invocation) + @PolyTainted Object local1 = new @PolyTainted MyClass(); + // :: warning: (cast.unsafe.constructor.invocation) + @Untainted Object local2 = new @Untainted MyClass(); - @PolyTainted Object local3 = new @PolyTainted MyClass(input); - // :: warning: (cast.unsafe.constructor.invocation) - @Untainted Object local4 = new @Untainted MyClass(input); - // :: warning: (cast.unsafe.constructor.invocation) - @PolyTainted Object local5 = new @PolyTainted MyClass(tainted); - @Untainted Object local6 = new @Untainted MyClass(untainted); + @PolyTainted Object local3 = new @PolyTainted MyClass(input); + // :: warning: (cast.unsafe.constructor.invocation) + @Untainted Object local4 = new @Untainted MyClass(input); + // :: warning: (cast.unsafe.constructor.invocation) + @PolyTainted Object local5 = new @PolyTainted MyClass(tainted); + @Untainted Object local6 = new @Untainted MyClass(untainted); + } } - } } diff --git a/checker/tests/tainting/Issue2243.java b/checker/tests/tainting/Issue2243.java index c4a185399c8..2a993a705da 100644 --- a/checker/tests/tainting/Issue2243.java +++ b/checker/tests/tainting/Issue2243.java @@ -13,12 +13,12 @@ class ExtendsSubTypingExplicit2243 extends @Untainted X2243 {} class X2243 {} class MyClass2243 { - @Tainted MyClass2243() {} + @Tainted MyClass2243() {} } @Untainted class Y2243 extends MyClass2243 { - // :: error: (super.invocation.invalid) - @Untainted Y2243() {} + // :: error: (super.invocation.invalid) + @Untainted Y2243() {} } @Untainted interface SuperClass2243 {} @@ -27,6 +27,6 @@ class MyClass2243 { @Tainted class Z2243 implements SuperClass2243 {} class Issue2243Test { - @Untainted ExtendsSubTypingExplicit2243 field; - @Tainted ExtendsSubTypingExplicit2243 field2; + @Untainted ExtendsSubTypingExplicit2243 field; + @Tainted ExtendsSubTypingExplicit2243 field2; } diff --git a/checker/tests/tainting/Issue2330.java b/checker/tests/tainting/Issue2330.java index c796c81998e..eab2937f0c9 100644 --- a/checker/tests/tainting/Issue2330.java +++ b/checker/tests/tainting/Issue2330.java @@ -3,16 +3,16 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue2330 { - // Checker can't verify that this creates an untainted Issue2330 - @SuppressWarnings("tainting") - public @Untainted Issue2330(@PolyTainted int i) {} + // Checker can't verify that this creates an untainted Issue2330 + @SuppressWarnings("tainting") + public @Untainted Issue2330(@PolyTainted int i) {} - // Checker can't verify that this creates an untainted Issue2330 - @SuppressWarnings("tainting") - public @Untainted Issue2330() {} + // Checker can't verify that this creates an untainted Issue2330 + @SuppressWarnings("tainting") + public @Untainted Issue2330() {} - public static void f(@PolyTainted int i) { - new @Untainted Issue2330<@PolyTainted Integer>(i); - new @Untainted Issue2330<@PolyTainted Integer>(); - } + public static void f(@PolyTainted int i) { + new @Untainted Issue2330<@PolyTainted Integer>(i); + new @Untainted Issue2330<@PolyTainted Integer>(); + } } diff --git a/checker/tests/tainting/Issue3033.java b/checker/tests/tainting/Issue3033.java index fe3b4ff7800..7abe60e7bda 100644 --- a/checker/tests/tainting/Issue3033.java +++ b/checker/tests/tainting/Issue3033.java @@ -3,18 +3,18 @@ public class Issue3033 { - void main() { - @Tainted String a = getTainted(); - // :: warning: (instanceof.unsafe) - if (a instanceof @Untainted String) { - // `a` is now refined to @Untainted String - isUntainted(a); + void main() { + @Tainted String a = getTainted(); + // :: warning: (instanceof.unsafe) + if (a instanceof @Untainted String) { + // `a` is now refined to @Untainted String + isUntainted(a); + } } - } - static void isUntainted(@Untainted String a) {} + static void isUntainted(@Untainted String a) {} - static @Tainted String getTainted() { - return "hi"; - } + static @Tainted String getTainted() { + return "hi"; + } } diff --git a/checker/tests/tainting/Issue3036Tainting.java b/checker/tests/tainting/Issue3036Tainting.java index 1e56f3f0809..2f3b8590d2a 100644 --- a/checker/tests/tainting/Issue3036Tainting.java +++ b/checker/tests/tainting/Issue3036Tainting.java @@ -1,3 +1,5 @@ +import org.checkerframework.checker.tainting.qual.Tainted; + import java.io.Serializable; import java.util.HashMap; import java.util.List; @@ -6,44 +8,43 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.checkerframework.checker.tainting.qual.Tainted; public class Issue3036Tainting { - public Set getDsData() { - throw new RuntimeException(); - } - - public static class MyInnerClass { - public int getKeyTag() { - return 5; + public Set getDsData() { + throw new RuntimeException(); } - public String getDigest() { - return ""; + public static class MyInnerClass { + public int getKeyTag() { + return 5; + } + + public String getDigest() { + return ""; + } } - } - - private void write(Stream stream) { - Function> mapper = - dsData1 -> - ImmutableMap.of( - "keyTag", dsData1.getKeyTag(), - "digest", dsData1.getDigest()); - - List> dsData = - getDsData().stream() - .map( + + private void write(Stream stream) { + Function> mapper = dsData1 -> - ImmutableMap.<@Tainted String, Object>of( - "keyTag", dsData1.getKeyTag(), - "digest", dsData1.getDigest())) - .collect(Collectors.toList()); - } - - public static class ImmutableMap extends HashMap { - public static ImmutableMap of(K k1, V v1, K k2, V v2) { - throw new RuntimeException(); + ImmutableMap.of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest()); + + List> dsData = + getDsData().stream() + .map( + dsData1 -> + ImmutableMap.<@Tainted String, Object>of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest())) + .collect(Collectors.toList()); + } + + public static class ImmutableMap extends HashMap { + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + throw new RuntimeException(); + } } - } } diff --git a/checker/tests/tainting/Issue352.java b/checker/tests/tainting/Issue352.java index 5132e2c375c..73c42874f86 100644 --- a/checker/tests/tainting/Issue352.java +++ b/checker/tests/tainting/Issue352.java @@ -4,9 +4,9 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue352 { - class Nested { - @Untainted Issue352 context(@Untainted Issue352.@Untainted Nested this) { - return Issue352.this; + class Nested { + @Untainted Issue352 context(@Untainted Issue352.@Untainted Nested this) { + return Issue352.this; + } } - } } diff --git a/checker/tests/tainting/Issue3561.java b/checker/tests/tainting/Issue3561.java index ca5fde378cd..2e4c44c9923 100644 --- a/checker/tests/tainting/Issue3561.java +++ b/checker/tests/tainting/Issue3561.java @@ -1,16 +1,16 @@ import org.checkerframework.checker.tainting.qual.*; public class Issue3561 { - void outerMethod(@Untainted Issue3561 this) {} + void outerMethod(@Untainted Issue3561 this) {} - class Inner { - void innerMethod(@Untainted Issue3561.@Untainted Inner this) { - Issue3561.this.outerMethod(); - } + class Inner { + void innerMethod(@Untainted Issue3561.@Untainted Inner this) { + Issue3561.this.outerMethod(); + } - void innerMethod2(@Tainted Issue3561.@Untainted Inner this) { - // :: error: (method.invocation.invalid) - Issue3561.this.outerMethod(); + void innerMethod2(@Tainted Issue3561.@Untainted Inner this) { + // :: error: (method.invocation.invalid) + Issue3561.this.outerMethod(); + } } - } } diff --git a/checker/tests/tainting/Issue3562.java b/checker/tests/tainting/Issue3562.java index 1b067b7fda3..2600b9649b2 100644 --- a/checker/tests/tainting/Issue3562.java +++ b/checker/tests/tainting/Issue3562.java @@ -1,8 +1,8 @@ import org.checkerframework.checker.tainting.qual.*; public class Issue3562 { - // This used to issue type.invalid.conflicting.annos - @Tainted Issue3562.@Untainted Inner field; + // This used to issue type.invalid.conflicting.annos + @Tainted Issue3562.@Untainted Inner field; - class Inner {} + class Inner {} } diff --git a/checker/tests/tainting/Issue3776.java b/checker/tests/tainting/Issue3776.java index 3044f1d6537..302a09588a0 100644 --- a/checker/tests/tainting/Issue3776.java +++ b/checker/tests/tainting/Issue3776.java @@ -2,44 +2,44 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class Issue3776 { - class MyInnerClass { - public MyInnerClass() {} + class MyInnerClass { + public MyInnerClass() {} - public MyInnerClass(@Untainted String s) {} + public MyInnerClass(@Untainted String s) {} - public MyInnerClass(int... i) {} - } + public MyInnerClass(int... i) {} + } - static class MyClass { - public MyClass() {} + static class MyClass { + public MyClass() {} - public MyClass(@Untainted String s) {} + public MyClass(@Untainted String s) {} - public MyClass(int... i) {} - } + public MyClass(int... i) {} + } - void test(Issue3776 outer, @Tainted String tainted) { - new MyInnerClass("1") {}; - this.new MyInnerClass("2") {}; - new MyClass() {}; - // :: error: (argument.type.incompatible) - new MyClass(tainted) {}; - new MyClass(1, 2, 3) {}; - new MyClass(1) {}; - new MyInnerClass() {}; - // :: error: (argument.type.incompatible) - new MyInnerClass(tainted) {}; - new MyInnerClass(1) {}; - new MyInnerClass(1, 2, 3) {}; - this.new MyInnerClass() {}; - // :: error: (argument.type.incompatible) - this.new MyInnerClass(tainted) {}; - this.new MyInnerClass(1) {}; - this.new MyInnerClass(1, 2, 3) {}; - outer.new MyInnerClass() {}; - // :: error: (argument.type.incompatible) - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(1) {}; - outer.new MyInnerClass(1, 2, 3) {}; - } + void test(Issue3776 outer, @Tainted String tainted) { + new MyInnerClass("1") {}; + this.new MyInnerClass("2") {}; + new MyClass() {}; + // :: error: (argument.type.incompatible) + new MyClass(tainted) {}; + new MyClass(1, 2, 3) {}; + new MyClass(1) {}; + new MyInnerClass() {}; + // :: error: (argument.type.incompatible) + new MyInnerClass(tainted) {}; + new MyInnerClass(1) {}; + new MyInnerClass(1, 2, 3) {}; + this.new MyInnerClass() {}; + // :: error: (argument.type.incompatible) + this.new MyInnerClass(tainted) {}; + this.new MyInnerClass(1) {}; + this.new MyInnerClass(1, 2, 3) {}; + outer.new MyInnerClass() {}; + // :: error: (argument.type.incompatible) + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(1) {}; + outer.new MyInnerClass(1, 2, 3) {}; + } } diff --git a/checker/tests/tainting/Issue4170.java b/checker/tests/tainting/Issue4170.java index a53508fad78..0ce21cbefbb 100644 --- a/checker/tests/tainting/Issue4170.java +++ b/checker/tests/tainting/Issue4170.java @@ -1,58 +1,59 @@ // @below-java10-jdk-skip-test -import java.util.ArrayList; -import java.util.List; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; +import java.util.ArrayList; +import java.util.List; + public class Issue4170 { - public void method1() { - var list = new ArrayList<@Untainted String>(); - ArrayList<@Untainted String> list2 = list; - // :: error: (assignment.type.incompatible) - ArrayList list3 = new ArrayList<@Untainted String>(); - ArrayList<@Tainted String> list4 = list3; - var stream = list.stream(); - } - - public void method2() { - var list = new ArrayList(); - var stream = list.stream(); - } - - public void method3() { - var list = new ArrayList<@Tainted String>(); - var stream = list.stream(); - } - - public ArrayList<@Untainted String> method4() { - var list = new ArrayList<@Untainted String>(); - return list; - } - - public ArrayList<@Tainted String> method5() { - var list = new ArrayList<@Untainted String>(); - // :: error: (return.type.incompatible) - return list; - } - - public ArrayList<@Untainted String> method6() { - var list = new ArrayList(); - // :: error: (return.type.incompatible) - return list; - } - - public void method7() { - var list = new ArrayList<@Untainted String>(); - method8(list); - } - - public void method8(ArrayList<@Untainted String> data) {} - - public void method9(List<@Tainted String> taintedlist, List<@Untainted String> untaintedList) { - var list1 = taintedlist; - List<@Tainted String> l = list1; - // :: error: (assignment.type.incompatible) - list1 = untaintedList; - } + public void method1() { + var list = new ArrayList<@Untainted String>(); + ArrayList<@Untainted String> list2 = list; + // :: error: (assignment.type.incompatible) + ArrayList list3 = new ArrayList<@Untainted String>(); + ArrayList<@Tainted String> list4 = list3; + var stream = list.stream(); + } + + public void method2() { + var list = new ArrayList(); + var stream = list.stream(); + } + + public void method3() { + var list = new ArrayList<@Tainted String>(); + var stream = list.stream(); + } + + public ArrayList<@Untainted String> method4() { + var list = new ArrayList<@Untainted String>(); + return list; + } + + public ArrayList<@Tainted String> method5() { + var list = new ArrayList<@Untainted String>(); + // :: error: (return.type.incompatible) + return list; + } + + public ArrayList<@Untainted String> method6() { + var list = new ArrayList(); + // :: error: (return.type.incompatible) + return list; + } + + public void method7() { + var list = new ArrayList<@Untainted String>(); + method8(list); + } + + public void method8(ArrayList<@Untainted String> data) {} + + public void method9(List<@Tainted String> taintedlist, List<@Untainted String> untaintedList) { + var list1 = taintedlist; + List<@Tainted String> l = list1; + // :: error: (assignment.type.incompatible) + list1 = untaintedList; + } } diff --git a/checker/tests/tainting/Issue5435.java b/checker/tests/tainting/Issue5435.java index 9b3196e590b..61140363fc0 100644 --- a/checker/tests/tainting/Issue5435.java +++ b/checker/tests/tainting/Issue5435.java @@ -1,7 +1,7 @@ class Issue5435 { - public @interface A1 {} + public @interface A1 {} - public @interface A2 { - A1[] m() default {@A1()}; - } + public @interface A2 { + A1[] m() default {@A1()}; + } } diff --git a/checker/tests/tainting/Issue6110.java b/checker/tests/tainting/Issue6110.java index c2dda0fb061..3ec39e5ddef 100644 --- a/checker/tests/tainting/Issue6110.java +++ b/checker/tests/tainting/Issue6110.java @@ -1,22 +1,23 @@ -import java.util.EnumSet; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; +import java.util.EnumSet; + class Issue6110 { - enum TestEnum { - ONE, - @Untainted TWO - } + enum TestEnum { + ONE, + @Untainted TWO + } - static void test(Enum<@Untainted TestEnum> o) { + static void test(Enum<@Untainted TestEnum> o) { - @Tainted TestEnum e = TestEnum.ONE; - o.compareTo(TestEnum.ONE); - o.compareTo(TestEnum.TWO); + @Tainted TestEnum e = TestEnum.ONE; + o.compareTo(TestEnum.ONE); + o.compareTo(TestEnum.TWO); - EnumSet<@Tainted TestEnum> s1 = EnumSet.of(TestEnum.ONE); - // :: error: (type.arguments.not.inferred) - EnumSet<@Untainted TestEnum> s2 = EnumSet.of(TestEnum.ONE); - EnumSet<@Untainted TestEnum> s3 = EnumSet.of(TestEnum.TWO); - } + EnumSet<@Tainted TestEnum> s1 = EnumSet.of(TestEnum.ONE); + // :: error: (type.arguments.not.inferred) + EnumSet<@Untainted TestEnum> s2 = EnumSet.of(TestEnum.ONE); + EnumSet<@Untainted TestEnum> s3 = EnumSet.of(TestEnum.TWO); + } } diff --git a/checker/tests/tainting/Issue6113.java b/checker/tests/tainting/Issue6113.java index e25789af7cf..f05089095bc 100644 --- a/checker/tests/tainting/Issue6113.java +++ b/checker/tests/tainting/Issue6113.java @@ -3,8 +3,8 @@ import java.util.List; class Issue6113 { - public void bar(List list) { - Collection c = - list != null ? list : Collections.emptyList(); // reported error here - } + public void bar(List list) { + Collection c = + list != null ? list : Collections.emptyList(); // reported error here + } } diff --git a/checker/tests/tainting/Issue6116.java b/checker/tests/tainting/Issue6116.java index 4579ea0860d..c4367118cca 100644 --- a/checker/tests/tainting/Issue6116.java +++ b/checker/tests/tainting/Issue6116.java @@ -2,13 +2,13 @@ import java.util.function.Consumer; class Issue6116 { - private final Iterator it; + private final Iterator it; - public Issue6116(Iterator iterator) { - this.it = iterator; - } + public Issue6116(Iterator iterator) { + this.it = iterator; + } - public void test(Consumer action) { - it.forEachRemaining(action); - } + public void test(Consumer action) { + it.forEachRemaining(action); + } } diff --git a/checker/tests/tainting/LambdaParameterDefaulting.java b/checker/tests/tainting/LambdaParameterDefaulting.java index 90706dada23..f8dd5a1d146 100644 --- a/checker/tests/tainting/LambdaParameterDefaulting.java +++ b/checker/tests/tainting/LambdaParameterDefaulting.java @@ -1,37 +1,38 @@ -import java.util.function.Function; import org.checkerframework.checker.tainting.qual.Untainted; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; +import java.util.function.Function; + public class LambdaParameterDefaulting { - @DefaultQualifier(locations = TypeUseLocation.PARAMETER, value = Untainted.class) - void method() { - // :: error: (lambda.param.type.incompatible) - Function function = (String s) -> untainted(s); - Function<@Untainted String, String> function2 = (String s) -> untainted(s); - Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); + @DefaultQualifier(locations = TypeUseLocation.PARAMETER, value = Untainted.class) + void method() { + // :: error: (lambda.param.type.incompatible) + Function function = (String s) -> untainted(s); + Function<@Untainted String, String> function2 = (String s) -> untainted(s); + Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function function4 = s -> untainted(s); - Function<@Untainted String, String> function5 = s -> untainted(s); - Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); - } + // :: error: (argument.type.incompatible) + Function function4 = s -> untainted(s); + Function<@Untainted String, String> function5 = s -> untainted(s); + Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); + } - void method2() { - // :: error: (argument.type.incompatible) - Function function = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function<@Untainted String, String> function2 = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); + void method2() { + // :: error: (argument.type.incompatible) + Function function = (String s) -> untainted(s); + // :: error: (argument.type.incompatible) + Function<@Untainted String, String> function2 = (String s) -> untainted(s); + // :: error: (argument.type.incompatible) + Function<@Untainted String, @Untainted String> function3 = (String s) -> untainted(s); - // :: error: (argument.type.incompatible) - Function function4 = s -> untainted(s); - Function<@Untainted String, String> function5 = s -> untainted(s); - Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); - } + // :: error: (argument.type.incompatible) + Function function4 = s -> untainted(s); + Function<@Untainted String, String> function5 = s -> untainted(s); + Function<@Untainted String, @Untainted String> function6 = s -> untainted(s); + } - @Untainted String untainted(@Untainted String s) { - return s; - } + @Untainted String untainted(@Untainted String s) { + return s; + } } diff --git a/checker/tests/tainting/MemberReferenceInference.java b/checker/tests/tainting/MemberReferenceInference.java index 367826da91d..e45c3b414a3 100644 --- a/checker/tests/tainting/MemberReferenceInference.java +++ b/checker/tests/tainting/MemberReferenceInference.java @@ -1,35 +1,36 @@ +import org.checkerframework.checker.tainting.qual.Tainted; +import org.checkerframework.checker.tainting.qual.Untainted; + import java.math.BigDecimal; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; -import org.checkerframework.checker.tainting.qual.Tainted; -import org.checkerframework.checker.tainting.qual.Untainted; public class MemberReferenceInference { - void clever2( - Stream> taintedStream, - Stream> untaintedStream) { - // :: error: (type.arguments.not.inferred) - Stream<@Untainted BigDecimal> s = taintedStream.map(Optional::get); - Stream<@Untainted BigDecimal> s2 = untaintedStream.map(Optional::get); - Stream<@Tainted BigDecimal> s3 = taintedStream.map(Optional::get); - Stream<@Tainted BigDecimal> s4 = untaintedStream.map(Optional::get); - } + void clever2( + Stream> taintedStream, + Stream> untaintedStream) { + // :: error: (type.arguments.not.inferred) + Stream<@Untainted BigDecimal> s = taintedStream.map(Optional::get); + Stream<@Untainted BigDecimal> s2 = untaintedStream.map(Optional::get); + Stream<@Tainted BigDecimal> s3 = taintedStream.map(Optional::get); + Stream<@Tainted BigDecimal> s4 = untaintedStream.map(Optional::get); + } - interface MyClass { - String getName(); - } + interface MyClass { + String getName(); + } - void method( - MyClass clazz, - Map, @Untainted String> annotationClassNames) { - // :: error: (type.arguments.not.inferred) - String canonicalName = annotationClassNames.computeIfAbsent(clazz, MyClass::getName); - } + void method( + MyClass clazz, + Map, @Untainted String> annotationClassNames) { + // :: error: (type.arguments.not.inferred) + String canonicalName = annotationClassNames.computeIfAbsent(clazz, MyClass::getName); + } - void method2( - MyClass clazz, - Map, String> annotationClassNames) { - String canonicalName = annotationClassNames.computeIfAbsent(clazz, MyClass::getName); - } + void method2( + MyClass clazz, + Map, String> annotationClassNames) { + String canonicalName = annotationClassNames.computeIfAbsent(clazz, MyClass::getName); + } } diff --git a/checker/tests/tainting/NestedAnonymous.java b/checker/tests/tainting/NestedAnonymous.java index 150181ea776..4fe15a787e2 100644 --- a/checker/tests/tainting/NestedAnonymous.java +++ b/checker/tests/tainting/NestedAnonymous.java @@ -2,13 +2,13 @@ import org.checkerframework.checker.tainting.qual.Untainted; class NestedAnonymous { - Object o1 = new @Tainted Object() {}; - Object o2 = new Outer.@Tainted Inner() {}; + Object o1 = new @Tainted Object() {}; + Object o2 = new Outer.@Tainted Inner() {}; - // :: error: (assignment.type.incompatible) - Outer.@Untainted Inner unt = new Outer.@Tainted Inner() {}; + // :: error: (assignment.type.incompatible) + Outer.@Untainted Inner unt = new Outer.@Tainted Inner() {}; - static class Outer { - static class Inner {} - } + static class Outer { + static class Inner {} + } } diff --git a/checker/tests/tainting/NestedTypeConstructor.java b/checker/tests/tainting/NestedTypeConstructor.java index 641d40ac3bf..ae6b50ba7cb 100644 --- a/checker/tests/tainting/NestedTypeConstructor.java +++ b/checker/tests/tainting/NestedTypeConstructor.java @@ -4,7 +4,7 @@ // https://github.com/typetools/checker-framework/issues/275 // Not tainting-specific, but a convenient location. public class NestedTypeConstructor { - class Inner { - @Tainted Inner() {} - } + class Inner { + @Tainted Inner() {} + } } diff --git a/checker/tests/tainting/ObjectCreation.java b/checker/tests/tainting/ObjectCreation.java index 09e5a43bfbd..35b607ad9b7 100644 --- a/checker/tests/tainting/ObjectCreation.java +++ b/checker/tests/tainting/ObjectCreation.java @@ -5,47 +5,47 @@ public class ObjectCreation { - @HasQualifierParameter(Tainted.class) - static class Buffer { // Which constructors and super calls are legal? - @PolyTainted Buffer() { - super(); // ok this creates an @Untainted object - } + @HasQualifierParameter(Tainted.class) + static class Buffer { // Which constructors and super calls are legal? + @PolyTainted Buffer() { + super(); // ok this creates an @Untainted object + } - @Untainted Buffer(int p) { - super(); // ok, super is untainted and creating an untainted buffer - } + @Untainted Buffer(int p) { + super(); // ok, super is untainted and creating an untainted buffer + } - @Tainted Buffer(String s) { - super(); // ok, super is not @HasQualifierParameter @Tainted Object >: the type of - // super. + @Tainted Buffer(String s) { + super(); // ok, super is not @HasQualifierParameter @Tainted Object >: the type of + // super. + } } - } - @HasQualifierParameter(Tainted.class) - static class MyBuffer extends Buffer { - @PolyTainted MyBuffer() { - super(); // ok, if super is @PolyTainted. - } + @HasQualifierParameter(Tainted.class) + static class MyBuffer extends Buffer { + @PolyTainted MyBuffer() { + super(); // ok, if super is @PolyTainted. + } - @Untainted MyBuffer(int p) { - super(p); - } + @Untainted MyBuffer(int p) { + super(p); + } - @Tainted MyBuffer(String s) { - super(s); - } + @Tainted MyBuffer(String s) { + super(s); + } - @PolyTainted MyBuffer(Object o) { - // :: error: (super.invocation.invalid) - super(""); - } + @PolyTainted MyBuffer(Object o) { + // :: error: (super.invocation.invalid) + super(""); + } - @Untainted MyBuffer(Object o, int p) { - super(); - } + @Untainted MyBuffer(Object o, int p) { + super(); + } - @Tainted MyBuffer(Object o, String s) { - super(); + @Tainted MyBuffer(Object o, String s) { + super(); + } } - } } diff --git a/checker/tests/tainting/PolyClassDecl.java b/checker/tests/tainting/PolyClassDecl.java index 02ffbdc0d90..1cae9303f72 100644 --- a/checker/tests/tainting/PolyClassDecl.java +++ b/checker/tests/tainting/PolyClassDecl.java @@ -1,33 +1,34 @@ +import org.checkerframework.checker.tainting.qual.PolyTainted; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.tainting.qual.PolyTainted; public class PolyClassDecl { - // :: error: (invalid.polymorphic.qualifier) - @PolyTainted static class Class1 {} + // :: error: (invalid.polymorphic.qualifier) + @PolyTainted static class Class1 {} - // :: error: (invalid.polymorphic.qualifier) - static class Class2<@PolyTainted T> {} + // :: error: (invalid.polymorphic.qualifier) + static class Class2<@PolyTainted T> {} - // :: error: (invalid.polymorphic.qualifier) - abstract static class Class3> {} + // :: error: (invalid.polymorphic.qualifier) + abstract static class Class3> {} - // :: error: (invalid.polymorphic.qualifier) - interface Class4 extends List<@PolyTainted String> {} + // :: error: (invalid.polymorphic.qualifier) + interface Class4 extends List<@PolyTainted String> {} - // :: error: (invalid.polymorphic.qualifier) - // :: error: (declaration.inconsistent.with.implements.clause) - interface Class5 extends @PolyTainted List {} + // :: error: (invalid.polymorphic.qualifier) + // :: error: (declaration.inconsistent.with.implements.clause) + interface Class5 extends @PolyTainted List {} - // :: error: (invalid.polymorphic.qualifier) - abstract static class Class6 implements List<@PolyTainted String> {} + // :: error: (invalid.polymorphic.qualifier) + abstract static class Class6 implements List<@PolyTainted String> {} - void method() { - ArrayList<@PolyTainted String> s = new ArrayList<@PolyTainted String>() {}; - } + void method() { + ArrayList<@PolyTainted String> s = new ArrayList<@PolyTainted String>() {}; + } - // :: error: (invalid.polymorphic.qualifier) - <@PolyTainted T> T identity(T arg) { - return arg; - } + // :: error: (invalid.polymorphic.qualifier) + <@PolyTainted T> T identity(T arg) { + return arg; + } } diff --git a/checker/tests/tainting/PolyConstructor.java b/checker/tests/tainting/PolyConstructor.java index 1358636c448..8db3a14990a 100644 --- a/checker/tests/tainting/PolyConstructor.java +++ b/checker/tests/tainting/PolyConstructor.java @@ -6,22 +6,22 @@ @HasQualifierParameter(Tainted.class) public class PolyConstructor { - @PolyTainted PolyConstructor() {} + @PolyTainted PolyConstructor() {} - @PolyTainted PolyConstructor(@PolyTainted Object o) {} + @PolyTainted PolyConstructor(@PolyTainted Object o) {} - static void uses(@Tainted Object tainted, @Untainted Object untainted) { - @Untainted PolyConstructor o1 = new @Untainted PolyConstructor(); - @Tainted PolyConstructor o2 = new @Tainted PolyConstructor(); - @PolyTainted PolyConstructor o3 = new @PolyTainted PolyConstructor(); + static void uses(@Tainted Object tainted, @Untainted Object untainted) { + @Untainted PolyConstructor o1 = new @Untainted PolyConstructor(); + @Tainted PolyConstructor o2 = new @Tainted PolyConstructor(); + @PolyTainted PolyConstructor o3 = new @PolyTainted PolyConstructor(); - // :: error: (assignment.type.incompatible) - @Untainted PolyConstructor o4 = new @Tainted PolyConstructor(untainted); - @Untainted PolyConstructor o5 = new PolyConstructor(untainted); + // :: error: (assignment.type.incompatible) + @Untainted PolyConstructor o4 = new @Tainted PolyConstructor(untainted); + @Untainted PolyConstructor o5 = new PolyConstructor(untainted); - // This currently isn't supported, but could be in the future. - @Untainted PolyConstructor o6 = new PolyConstructor(); - // :: error: (assignment.type.incompatible) - @Tainted PolyConstructor o7 = new PolyConstructor(); - } + // This currently isn't supported, but could be in the future. + @Untainted PolyConstructor o6 = new PolyConstructor(); + // :: error: (assignment.type.incompatible) + @Tainted PolyConstructor o7 = new PolyConstructor(); + } } diff --git a/checker/tests/tainting/PolyReceivers.java b/checker/tests/tainting/PolyReceivers.java index 162daca45d5..f96b3ade677 100644 --- a/checker/tests/tainting/PolyReceivers.java +++ b/checker/tests/tainting/PolyReceivers.java @@ -2,42 +2,44 @@ public class PolyReceivers { - static class MyClass { - public void start(@PolyTainted MyClass this) {} - } + static class MyClass { + public void start(@PolyTainted MyClass this) {} + } - PolyReceivers(int i, Runnable... runnables) {} + PolyReceivers(int i, Runnable... runnables) {} - PolyReceivers(Consumer consumer) { - consumer.consume("hello"); // Use lambda as a constructor argument - } + PolyReceivers(Consumer consumer) { + consumer.consume("hello"); // Use lambda as a constructor argument + } - interface Top { - public void consume(String s); - } + interface Top { + public void consume(String s); + } - interface Sub extends Top { - public default void otherMethod() {} - } + interface Sub extends Top { + public default void otherMethod() {} + } - interface Consumer { - void consume(T t); - } + interface Consumer { + void consume(T t); + } - void varargs(Runnable... runnables) {} + void varargs(Runnable... runnables) {} - public static void consumeStr(String str) {} + public static void consumeStr(String str) {} - public static void consumeStr2(String str) {} + public static void consumeStr2(String str) {} - > void context(E e, Sub s) { - new PolyReceivers(PolyReceivers::consumeStr); + > void context(E e, Sub s) { + new PolyReceivers(PolyReceivers::consumeStr); - Consumer cs1 = (false) ? PolyReceivers::consumeStr2 : PolyReceivers::consumeStr; - Consumer cs2 = (false) ? e : PolyReceivers::consumeStr; - Top t = (false) ? s : PolyReceivers::consumeStr; + Consumer cs1 = (false) ? PolyReceivers::consumeStr2 : PolyReceivers::consumeStr; + Consumer cs2 = (false) ? e : PolyReceivers::consumeStr; + Top t = (false) ? s : PolyReceivers::consumeStr; - new PolyReceivers(42, new MyClass()::start); // Use lambda as a constructor argument - varargs(new MyClass()::start, new MyClass()::start); // Use lambda in a var arg list of method - } + new PolyReceivers(42, new MyClass()::start); // Use lambda as a constructor argument + varargs( + new MyClass()::start, + new MyClass()::start); // Use lambda in a var arg list of method + } } diff --git a/checker/tests/tainting/PolyReturn.java b/checker/tests/tainting/PolyReturn.java index 592ceb3a39b..761b73857cc 100644 --- a/checker/tests/tainting/PolyReturn.java +++ b/checker/tests/tainting/PolyReturn.java @@ -3,25 +3,25 @@ import org.checkerframework.checker.tainting.qual.Untainted; @SuppressWarnings({ - "inconsistent.constructor.type", - "super.invocation.invalid" + "inconsistent.constructor.type", + "super.invocation.invalid" }) // ignore these warnings public class PolyReturn { - @PolyTainted PolyReturn() {} + @PolyTainted PolyReturn() {} - @PolyTainted PolyReturn method() { - return new PolyReturn(); - } + @PolyTainted PolyReturn method() { + return new PolyReturn(); + } - void use() { - @Untainted PolyReturn untainted = new PolyReturn(); - @Untainted PolyReturn untainted2 = new @Untainted PolyReturn(); + void use() { + @Untainted PolyReturn untainted = new PolyReturn(); + @Untainted PolyReturn untainted2 = new @Untainted PolyReturn(); - @Untainted PolyReturn untainted3 = method(); + @Untainted PolyReturn untainted3 = method(); - @Tainted PolyReturn tainted = new PolyReturn(); - @Tainted PolyReturn tainted2 = new @Tainted PolyReturn(); + @Tainted PolyReturn tainted = new PolyReturn(); + @Tainted PolyReturn tainted2 = new @Tainted PolyReturn(); - @Tainted PolyReturn tainted3 = method(); - } + @Tainted PolyReturn tainted3 = method(); + } } diff --git a/checker/tests/tainting/PolyVarArgs.java b/checker/tests/tainting/PolyVarArgs.java index de2848d06f2..19de69b4496 100644 --- a/checker/tests/tainting/PolyVarArgs.java +++ b/checker/tests/tainting/PolyVarArgs.java @@ -2,50 +2,50 @@ class PolyVarArgs { - void testVarArgsNoFormals() { - @Tainted String tainted = varArgsNoFormals(); - @Untainted String untainted = varArgsNoFormals("a"); - @Untainted String untainted2 = varArgsNoFormals("b", "c"); - } - - void testVarArgsNoFormalsInvalid() { - // :: error: (assignment) - @Untainted String tainted = varArgsNoFormals(); - } - - void testVarArgsWithFormals() { - @Tainted String tainted = varArgsWithFormals(1); - @Untainted String untainted = varArgsWithFormals(1, "a"); - @Untainted String untainted2 = varArgsWithFormals(1, "a", "b"); - } - - void testVarArgsWithFormalsInvalid() { - // :: error: (assignment) - @Untainted String tainted = varArgsWithFormals(1); - } - - void testVarArgsWithPolyFormals() { - @Tainted String tainted = varArgsWithPolyFormals(1); - - // :: warning: (cast.unsafe) - @Untainted int safeInt = (@Untainted int) 1; - @Untainted String untainted = varArgsWithPolyFormals(safeInt, "a"); - } - - void testVarArgsWithPolyFormalsInvalid() { - // :: error: (assignment) - @Untainted String tainted = varArgsWithPolyFormals(1); - } - - @PolyTainted String varArgsNoFormals(@PolyTainted String... s) { - throw new Error(); - } - - @PolyTainted String varArgsWithFormals(int a, @PolyTainted String... s) { - throw new Error(); - } - - @PolyTainted String varArgsWithPolyFormals(@PolyTainted int a, @PolyTainted String... s) { - throw new Error(); - } + void testVarArgsNoFormals() { + @Tainted String tainted = varArgsNoFormals(); + @Untainted String untainted = varArgsNoFormals("a"); + @Untainted String untainted2 = varArgsNoFormals("b", "c"); + } + + void testVarArgsNoFormalsInvalid() { + // :: error: (assignment) + @Untainted String tainted = varArgsNoFormals(); + } + + void testVarArgsWithFormals() { + @Tainted String tainted = varArgsWithFormals(1); + @Untainted String untainted = varArgsWithFormals(1, "a"); + @Untainted String untainted2 = varArgsWithFormals(1, "a", "b"); + } + + void testVarArgsWithFormalsInvalid() { + // :: error: (assignment) + @Untainted String tainted = varArgsWithFormals(1); + } + + void testVarArgsWithPolyFormals() { + @Tainted String tainted = varArgsWithPolyFormals(1); + + // :: warning: (cast.unsafe) + @Untainted int safeInt = (@Untainted int) 1; + @Untainted String untainted = varArgsWithPolyFormals(safeInt, "a"); + } + + void testVarArgsWithPolyFormalsInvalid() { + // :: error: (assignment) + @Untainted String tainted = varArgsWithPolyFormals(1); + } + + @PolyTainted String varArgsNoFormals(@PolyTainted String... s) { + throw new Error(); + } + + @PolyTainted String varArgsWithFormals(int a, @PolyTainted String... s) { + throw new Error(); + } + + @PolyTainted String varArgsWithPolyFormals(@PolyTainted int a, @PolyTainted String... s) { + throw new Error(); + } } diff --git a/checker/tests/tainting/Refine.java b/checker/tests/tainting/Refine.java index b0262615b06..7fde0d6d991 100644 --- a/checker/tests/tainting/Refine.java +++ b/checker/tests/tainting/Refine.java @@ -4,29 +4,29 @@ @HasQualifierParameter(Tainted.class) public class Refine { - void method(@Tainted Refine tainted, @Untainted Refine untainted) { - // :: error: (assignment.type.incompatible) - @Tainted Refine local = untainted; - // :: error: (assignment.type.incompatible) - @Untainted Refine untaintedLocal = local; - @Untainted Refine untaintedLocal2 = untaintedLocal; - } + void method(@Tainted Refine tainted, @Untainted Refine untainted) { + // :: error: (assignment.type.incompatible) + @Tainted Refine local = untainted; + // :: error: (assignment.type.incompatible) + @Untainted Refine untaintedLocal = local; + @Untainted Refine untaintedLocal2 = untaintedLocal; + } - void methodNull() { - @Tainted Refine local = null; - @Untainted Refine untaintedLocal = local; - } + void methodNull() { + @Tainted Refine local = null; + @Untainted Refine untaintedLocal = local; + } - public static class SuperClass { - @Untainted SuperClass() {} - } + public static class SuperClass { + @Untainted SuperClass() {} + } - @HasQualifierParameter(Tainted.class) - public static class SubClass extends SuperClass {} + @HasQualifierParameter(Tainted.class) + public static class SubClass extends SuperClass {} - static void method2(@Untainted SubClass subClass) { - @Untainted SuperClass untainted1 = subClass; - @Tainted SuperClass superClass = subClass; - @Untainted SuperClass untainted2 = superClass; - } + static void method2(@Untainted SubClass subClass) { + @Untainted SuperClass untainted1 = subClass; + @Tainted SuperClass superClass = subClass; + @Untainted SuperClass untainted2 = superClass; + } } diff --git a/checker/tests/tainting/SameTypeBounds.java b/checker/tests/tainting/SameTypeBounds.java index 92a710cbbe7..67c05f7703b 100644 --- a/checker/tests/tainting/SameTypeBounds.java +++ b/checker/tests/tainting/SameTypeBounds.java @@ -4,43 +4,43 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class SameTypeBounds { - static class MyGen {} + static class MyGen {} - void test1(MyGen p) { - // The upper and lower bound must have the same annotation because the bounds are collasped - // during capture conversion. - // :: error: (type.invalid.super.wildcard) - MyGen o = p; - // :: error: (assignment.type.incompatible) - p = o; - } + void test1(MyGen p) { + // The upper and lower bound must have the same annotation because the bounds are collasped + // during capture conversion. + // :: error: (type.invalid.super.wildcard) + MyGen o = p; + // :: error: (assignment.type.incompatible) + p = o; + } - void test2(MyGen p) { - // :: error: (assignment.type.incompatible) - MyGen<@Untainted ? super @Untainted Object> o = p; - // :: error: (assignment.type.incompatible) - p = o; - } + void test2(MyGen p) { + // :: error: (assignment.type.incompatible) + MyGen<@Untainted ? super @Untainted Object> o = p; + // :: error: (assignment.type.incompatible) + p = o; + } - void test3(MyGen<@Untainted Object> p) { - // :: error: (assignment.type.incompatible) - MyGen o = p; - // :: error: (assignment.type.incompatible) - p = o; - } + void test3(MyGen<@Untainted Object> p) { + // :: error: (assignment.type.incompatible) + MyGen o = p; + // :: error: (assignment.type.incompatible) + p = o; + } - static class MyClass {} + static class MyClass {} - static class MySubClass extends MyClass {} + static class MySubClass extends MyClass {} - class Gen {} + class Gen {} - // :: error: (type.invalid.super.wildcard) - void test3(Gen p, Gen p2) { // :: error: (type.invalid.super.wildcard) - Gen o = p; - o = p2; - // :: error: (assignment.type.incompatible) - p = p2; - } + void test3(Gen p, Gen p2) { + // :: error: (type.invalid.super.wildcard) + Gen o = p; + o = p2; + // :: error: (assignment.type.incompatible) + p = p2; + } } diff --git a/checker/tests/tainting/SimplePrims.java b/checker/tests/tainting/SimplePrims.java index 91bdefc4cdb..b784473601f 100644 --- a/checker/tests/tainting/SimplePrims.java +++ b/checker/tests/tainting/SimplePrims.java @@ -2,49 +2,49 @@ public class SimplePrims { - void execute(@Untainted int s) {} - - void tainted(int s) {} - - void intLiteral() { - // :: error: (argument.type.incompatible) - execute(5); - tainted(6); - } - - void intRef(int ref) { - // :: error: (argument.type.incompatible) - execute(ref); - tainted(ref); - } - - void untaintedRef(@Untainted int ref) { - execute(ref); - tainted(ref); - } - - void concatenation(@Untainted int s1, int s2) { - execute(s1 + s1); - execute(s1 += s1); - // :: error: (argument.type.incompatible) - execute(s1 + 3); - - // :: error: (argument.type.incompatible) - execute(s1 + s2); - - // :: error: (argument.type.incompatible) - execute(s2 + s1); - // :: error: (argument.type.incompatible) - execute(s2 + 4); - // :: error: (argument.type.incompatible) - execute(s2 + s2); - - tainted(s1 + s1); - tainted(s1 + 7); - tainted(s1 + s2); - - tainted(s2 + s1); - tainted(s2 + 8); - tainted(s2 + s2); - } + void execute(@Untainted int s) {} + + void tainted(int s) {} + + void intLiteral() { + // :: error: (argument.type.incompatible) + execute(5); + tainted(6); + } + + void intRef(int ref) { + // :: error: (argument.type.incompatible) + execute(ref); + tainted(ref); + } + + void untaintedRef(@Untainted int ref) { + execute(ref); + tainted(ref); + } + + void concatenation(@Untainted int s1, int s2) { + execute(s1 + s1); + execute(s1 += s1); + // :: error: (argument.type.incompatible) + execute(s1 + 3); + + // :: error: (argument.type.incompatible) + execute(s1 + s2); + + // :: error: (argument.type.incompatible) + execute(s2 + s1); + // :: error: (argument.type.incompatible) + execute(s2 + 4); + // :: error: (argument.type.incompatible) + execute(s2 + s2); + + tainted(s1 + s1); + tainted(s1 + 7); + tainted(s1 + s2); + + tainted(s2 + s1); + tainted(s2 + 8); + tainted(s2 + s2); + } } diff --git a/checker/tests/tainting/SimpleTainting.java b/checker/tests/tainting/SimpleTainting.java index 8dd46a6837c..498b9c11933 100644 --- a/checker/tests/tainting/SimpleTainting.java +++ b/checker/tests/tainting/SimpleTainting.java @@ -2,46 +2,46 @@ public class SimpleTainting { - void execute(@Untainted String s) {} - - void tainted(String s) {} - - void stringLiteral() { - execute("ldskjfldj"); - tainted("lksjdflkjdf"); - } - - void stringRef(String ref) { - // :: error: (argument.type.incompatible) - execute(ref); // error - tainted(ref); - } - - void untaintedRef(@Untainted String ref) { - execute(ref); - tainted(ref); - } - - void concatenation(@Untainted String s1, String s2) { - execute(s1 + s1); - execute(s1 += s1); - execute(s1 + "m"); - // :: error: (argument.type.incompatible) - execute(s1 + s2); // error - - // :: error: (argument.type.incompatible) - execute(s2 + s1); // error - // :: error: (argument.type.incompatible) - execute(s2 + "m"); // error - // :: error: (argument.type.incompatible) - execute(s2 + s2); // error - - tainted(s1 + s1); - tainted(s1 + "m"); - tainted(s1 + s2); - - tainted(s2 + s1); - tainted(s2 + "m"); - tainted(s2 + s2); - } + void execute(@Untainted String s) {} + + void tainted(String s) {} + + void stringLiteral() { + execute("ldskjfldj"); + tainted("lksjdflkjdf"); + } + + void stringRef(String ref) { + // :: error: (argument.type.incompatible) + execute(ref); // error + tainted(ref); + } + + void untaintedRef(@Untainted String ref) { + execute(ref); + tainted(ref); + } + + void concatenation(@Untainted String s1, String s2) { + execute(s1 + s1); + execute(s1 += s1); + execute(s1 + "m"); + // :: error: (argument.type.incompatible) + execute(s1 + s2); // error + + // :: error: (argument.type.incompatible) + execute(s2 + s1); // error + // :: error: (argument.type.incompatible) + execute(s2 + "m"); // error + // :: error: (argument.type.incompatible) + execute(s2 + s2); // error + + tainted(s1 + s1); + tainted(s1 + "m"); + tainted(s1 + s2); + + tainted(s2 + s1); + tainted(s2 + "m"); + tainted(s2 + s2); + } } diff --git a/checker/tests/tainting/SubClassHasQP.java b/checker/tests/tainting/SubClassHasQP.java index a91dbc02d00..8a7783c6096 100644 --- a/checker/tests/tainting/SubClassHasQP.java +++ b/checker/tests/tainting/SubClassHasQP.java @@ -5,45 +5,45 @@ // @skip-test https://github.com/typetools/checker-framework/issues/3400 public class SubClassHasQP { - @HasQualifierParameter(Tainted.class) - static class Buffer { - void append(@PolyTainted Buffer this, @PolyTainted String s) {} - - void append2(@PolyTainted Buffer this, @PolyTainted String s) {} - } - - @HasQualifierParameter(Tainted.class) - static @Untainted class UntaintedBuffer extends @Untainted Buffer { - @Override - // :: error: (type.invalid.annotations.on.use) - void append(@Tainted UntaintedBuffer this, @Tainted String s) {} - - @Override - void append2(@Untainted UntaintedBuffer this, @Untainted String s) {} - } - - @HasQualifierParameter(Tainted.class) - static @Tainted class TaintedBuffer extends @Tainted Buffer { - @Override - void append(@Tainted TaintedBuffer this, @Tainted String s) {} // legal override - - @Override - void append2(@Untainted TaintedBuffer this, String s) { - @Untainted Buffer that = this; + @HasQualifierParameter(Tainted.class) + static class Buffer { + void append(@PolyTainted Buffer this, @PolyTainted String s) {} + + void append2(@PolyTainted Buffer this, @PolyTainted String s) {} + } + + @HasQualifierParameter(Tainted.class) + static @Untainted class UntaintedBuffer extends @Untainted Buffer { + @Override + // :: error: (type.invalid.annotations.on.use) + void append(@Tainted UntaintedBuffer this, @Tainted String s) {} + + @Override + void append2(@Untainted UntaintedBuffer this, @Untainted String s) {} + } + + @HasQualifierParameter(Tainted.class) + static @Tainted class TaintedBuffer extends @Tainted Buffer { + @Override + void append(@Tainted TaintedBuffer this, @Tainted String s) {} // legal override + + @Override + void append2(@Untainted TaintedBuffer this, String s) { + @Untainted Buffer that = this; + } + } + + @HasQualifierParameter(Tainted.class) + // :: error: (super.invocation.invalid) + static class MyTaintedBuffer extends TaintedBuffer { + @Override + // :: error: (override.receiver.invalid) + void append(MyTaintedBuffer this, String s) {} // legal override + } + + @HasQualifierParameter(Tainted.class) + @Tainted class MyTaintedBuffer2 extends TaintedBuffer { + @Override + void append(@Tainted MyTaintedBuffer2 this, String s) {} // legal override } - } - - @HasQualifierParameter(Tainted.class) - // :: error: (super.invocation.invalid) - static class MyTaintedBuffer extends TaintedBuffer { - @Override - // :: error: (override.receiver.invalid) - void append(MyTaintedBuffer this, String s) {} // legal override - } - - @HasQualifierParameter(Tainted.class) - @Tainted class MyTaintedBuffer2 extends TaintedBuffer { - @Override - void append(@Tainted MyTaintedBuffer2 this, String s) {} // legal override - } } diff --git a/checker/tests/tainting/SubtypingConstraint.java b/checker/tests/tainting/SubtypingConstraint.java index 2a3eed2cfd0..ba82de76774 100644 --- a/checker/tests/tainting/SubtypingConstraint.java +++ b/checker/tests/tainting/SubtypingConstraint.java @@ -1,17 +1,18 @@ +import org.checkerframework.checker.tainting.qual.Untainted; + import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.tainting.qual.Untainted; public class SubtypingConstraint { - void test() { - Object[] o = new Object[0]; - // :: error: (type.arguments.not.inferred) - asList(0, 0, "", Arrays.asList(o)); - } + void test() { + Object[] o = new Object[0]; + // :: error: (type.arguments.not.inferred) + asList(0, 0, "", Arrays.asList(o)); + } - @SafeVarargs - @SuppressWarnings("varargs") - public static List asList(T... a) { - throw new RuntimeException(""); - } + @SafeVarargs + @SuppressWarnings("varargs") + public static List asList(T... a) { + throw new RuntimeException(""); + } } diff --git a/checker/tests/tainting/TaintedIntersections.java b/checker/tests/tainting/TaintedIntersections.java index abafc9ec8ef..52510441e1c 100644 --- a/checker/tests/tainting/TaintedIntersections.java +++ b/checker/tests/tainting/TaintedIntersections.java @@ -2,48 +2,48 @@ import org.checkerframework.checker.tainting.qual.Untainted; public class TaintedIntersections { - interface MyInterface {} + interface MyInterface {} - void test1() { - // null is @Untainted - @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) null; - // :: warning: (explicit.annotation.ignored) - @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) null; - // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) - @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) null; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) null; - } + void test1() { + // null is @Untainted + @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) null; + // :: warning: (explicit.annotation.ignored) + @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) null; + // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) + @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) null; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) null; + } - void test2() { - // null is @Untainted - @Untainted Object o1 = (@Untainted Object & MyInterface) null; - @Untainted Object o3 = (Object & @Untainted MyInterface) null; - // :: error: (assignment.type.incompatible) - @Untainted Object o2 = (Object & @Tainted MyInterface) null; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & MyInterface) null; - } + void test2() { + // null is @Untainted + @Untainted Object o1 = (@Untainted Object & MyInterface) null; + @Untainted Object o3 = (Object & @Untainted MyInterface) null; + // :: error: (assignment.type.incompatible) + @Untainted Object o2 = (Object & @Tainted MyInterface) null; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & MyInterface) null; + } - void test3(@Tainted MyInterface i) { - // :: warning: (cast.unsafe) - @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) i; - // :: warning: (explicit.annotation.ignored) :: warning: (cast.unsafe) - @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) i; - // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) - @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) i; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) i; - } + void test3(@Tainted MyInterface i) { + // :: warning: (cast.unsafe) + @Untainted Object o1 = (@Untainted Object & @Untainted MyInterface) i; + // :: warning: (explicit.annotation.ignored) :: warning: (cast.unsafe) + @Untainted Object o2 = (@Untainted Object & @Tainted MyInterface) i; + // :: error: (assignment.type.incompatible) :: warning: (explicit.annotation.ignored) + @Untainted Object o3 = (@Tainted Object & @Untainted MyInterface) i; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & @Tainted MyInterface) i; + } - void test4(@Tainted MyInterface i) { - // :: warning: (cast.unsafe) - @Untainted Object o1 = (@Untainted Object & MyInterface) i; - // :: warning: (cast.unsafe) - @Untainted Object o3 = (Object & @Untainted MyInterface) i; - // :: error: (assignment.type.incompatible) - @Untainted Object o2 = (Object & @Tainted MyInterface) i; - // :: error: (assignment.type.incompatible) - @Untainted Object o4 = (@Tainted Object & MyInterface) i; - } + void test4(@Tainted MyInterface i) { + // :: warning: (cast.unsafe) + @Untainted Object o1 = (@Untainted Object & MyInterface) i; + // :: warning: (cast.unsafe) + @Untainted Object o3 = (Object & @Untainted MyInterface) i; + // :: error: (assignment.type.incompatible) + @Untainted Object o2 = (Object & @Tainted MyInterface) i; + // :: error: (assignment.type.incompatible) + @Untainted Object o4 = (@Tainted Object & MyInterface) i; + } } diff --git a/checker/tests/tainting/TaintingDiamondInference.java b/checker/tests/tainting/TaintingDiamondInference.java index a232435d8d0..59ece7471a2 100644 --- a/checker/tests/tainting/TaintingDiamondInference.java +++ b/checker/tests/tainting/TaintingDiamondInference.java @@ -1,17 +1,18 @@ // Test case for issue #660: https://github.com/typetools/checker-framework/issues/660 +import org.checkerframework.checker.tainting.qual.Untainted; + import java.util.Set; import java.util.TreeSet; -import org.checkerframework.checker.tainting.qual.Untainted; public class TaintingDiamondInference { - private @Untainted Set<@Untainted String> s; + private @Untainted Set<@Untainted String> s; - public TaintingDiamondInference() { - // :: warning: (cast.unsafe.constructor.invocation) - s = new @Untainted TreeSet<>(); - // :: warning: (cast.unsafe.constructor.invocation) - s = new @Untainted TreeSet<@Untainted String>(); - } + public TaintingDiamondInference() { + // :: warning: (cast.unsafe.constructor.invocation) + s = new @Untainted TreeSet<>(); + // :: warning: (cast.unsafe.constructor.invocation) + s = new @Untainted TreeSet<@Untainted String>(); + } } diff --git a/checker/tests/tainting/TaintingIssue6025.java b/checker/tests/tainting/TaintingIssue6025.java index a2a5a418da1..e0b8c2fa1d7 100644 --- a/checker/tests/tainting/TaintingIssue6025.java +++ b/checker/tests/tainting/TaintingIssue6025.java @@ -2,19 +2,19 @@ public class TaintingIssue6025 { - public interface A {} + public interface A {} - private static final class B {} + private static final class B {} - public interface C, T2 extends A> {} + public interface C, T2 extends A> {} - private final B, ? extends A>> one = new B<>(); - private final B, ? extends A>> two = new B<>(); + private final B, ? extends A>> one = new B<>(); + private final B, ? extends A>> two = new B<>(); - void f(boolean b) { - // :: error: (assignment.type.incompatible) - B> three1 = one; - // :: error: (assignment.type.incompatible) - B> three = b ? two : one; - } + void f(boolean b) { + // :: error: (assignment.type.incompatible) + B> three1 = one; + // :: error: (assignment.type.incompatible) + B> three = b ? two : one; + } } diff --git a/checker/tests/tainting/TaintingIssue6060.java b/checker/tests/tainting/TaintingIssue6060.java index 59032163572..e2e6e6a8769 100644 --- a/checker/tests/tainting/TaintingIssue6060.java +++ b/checker/tests/tainting/TaintingIssue6060.java @@ -1,25 +1,26 @@ +import org.checkerframework.checker.tainting.qual.Untainted; + import java.util.Spliterator; import java.util.function.Consumer; -import org.checkerframework.checker.tainting.qual.Untainted; public interface TaintingIssue6060 extends Iterable<@Untainted R> { - default Spliterator<@Untainted R> spliterator() { - return Iterable.super.spliterator(); - } + default Spliterator<@Untainted R> spliterator() { + return Iterable.super.spliterator(); + } - default Spliterator spliterator2() { - // :: error: (return.type.incompatible) - return Iterable.super.spliterator(); - } + default Spliterator spliterator2() { + // :: error: (return.type.incompatible) + return Iterable.super.spliterator(); + } - default Spliterator spliterator3() { - // :: error: (return.type.incompatible) - return this.spliterator(); - } + default Spliterator spliterator3() { + // :: error: (return.type.incompatible) + return this.spliterator(); + } - // :: error: (override.param.invalid) - default void forEach(Consumer action) { - Iterable.super.forEach(action); - } + // :: error: (override.param.invalid) + default void forEach(Consumer action) { + Iterable.super.forEach(action); + } } diff --git a/checker/tests/tainting/TaintingPolyFields.java b/checker/tests/tainting/TaintingPolyFields.java index 208c10db9af..52fd672239f 100644 --- a/checker/tests/tainting/TaintingPolyFields.java +++ b/checker/tests/tainting/TaintingPolyFields.java @@ -1,49 +1,50 @@ -import java.util.List; import org.checkerframework.checker.tainting.qual.PolyTainted; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; +import java.util.List; + public class TaintingPolyFields { - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted Integer x; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted List<@PolyTainted String> lst; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted String @PolyTainted [] str; - // :: error: (invalid.polymorphic.qualifier.use) - List<@PolyTainted String> lst1; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted String[] str1; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted List lst2; - // :: error: (invalid.polymorphic.qualifier.use) - String @PolyTainted [] str2; - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted int z; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted Integer x; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted List<@PolyTainted String> lst; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted String @PolyTainted [] str; + // :: error: (invalid.polymorphic.qualifier.use) + List<@PolyTainted String> lst1; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted String[] str1; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted List lst2; + // :: error: (invalid.polymorphic.qualifier.use) + String @PolyTainted [] str2; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted int z; - // Access of poly fields outside of the declaring class. - static void test() { - @Tainted TaintingPolyFields obj = new @Tainted TaintingPolyFields(); - // :: error: (assignment.type.incompatible) - @Untainted Integer myX = obj.x; - // :: error: (assignment.type.incompatible) - @Untainted List<@Untainted String> myLst = obj.lst; - // :: error: (assignment.type.incompatible) - @Untainted String @Untainted [] myStr = obj.str; + // Access of poly fields outside of the declaring class. + static void test() { + @Tainted TaintingPolyFields obj = new @Tainted TaintingPolyFields(); + // :: error: (assignment.type.incompatible) + @Untainted Integer myX = obj.x; + // :: error: (assignment.type.incompatible) + @Untainted List<@Untainted String> myLst = obj.lst; + // :: error: (assignment.type.incompatible) + @Untainted String @Untainted [] myStr = obj.str; - // :: warning: (cast.unsafe.constructor.invocation) - @Untainted TaintingPolyFields obj1 = new @Untainted TaintingPolyFields(); - @Untainted Integer myX1 = obj1.x; - TaintingPolyFields obj2 = new TaintingPolyFields(); - // :: error: (assignment.type.incompatible) - @Untainted List<@Untainted String> myLst2 = obj2.lst; - } + // :: warning: (cast.unsafe.constructor.invocation) + @Untainted TaintingPolyFields obj1 = new @Untainted TaintingPolyFields(); + @Untainted Integer myX1 = obj1.x; + TaintingPolyFields obj2 = new TaintingPolyFields(); + // :: error: (assignment.type.incompatible) + @Untainted List<@Untainted String> myLst2 = obj2.lst; + } - static void polyTest(@PolyTainted TaintingPolyFields o) { - @PolyTainted Integer f = o.x; - } + static void polyTest(@PolyTainted TaintingPolyFields o) { + @PolyTainted Integer f = o.x; + } } class TypeParam { - T field; + T field; } diff --git a/checker/tests/tainting/TaintingPrimitiveTarget.java b/checker/tests/tainting/TaintingPrimitiveTarget.java index eef0ccba062..f81c54e38fe 100644 --- a/checker/tests/tainting/TaintingPrimitiveTarget.java +++ b/checker/tests/tainting/TaintingPrimitiveTarget.java @@ -1,17 +1,18 @@ +import org.checkerframework.checker.tainting.qual.Untainted; + import java.util.Collections; import java.util.List; -import org.checkerframework.checker.tainting.qual.Untainted; public class TaintingPrimitiveTarget { - void method(List list) { - long l = Collections.min(list); - // :: error: (assignment) - // :: error: (type.arguments.not.inferred) - @Untainted long l2 = Collections.min(list); - } + void method(List list) { + long l = Collections.min(list); + // :: error: (assignment) + // :: error: (type.arguments.not.inferred) + @Untainted long l2 = Collections.min(list); + } - void method2(List<@Untainted Integer> list) { - long l = Collections.min(list); - @Untainted long l2 = Collections.min(list); - } + void method2(List<@Untainted Integer> list) { + long l = Collections.min(list); + @Untainted long l2 = Collections.min(list); + } } diff --git a/checker/tests/tainting/TestFieldPolymorphism.java b/checker/tests/tainting/TestFieldPolymorphism.java index 7864f7ff953..78d6cdea920 100644 --- a/checker/tests/tainting/TestFieldPolymorphism.java +++ b/checker/tests/tainting/TestFieldPolymorphism.java @@ -5,54 +5,54 @@ @HasQualifierParameter(Tainted.class) public class TestFieldPolymorphism { - @PolyTainted String field; - - @PolyTainted TestFieldPolymorphism(@PolyTainted String s) { - this.field = s; - } - - @PolyTainted TestFieldPolymorphism testConstructor(@PolyTainted String s) { - return new TestFieldPolymorphism(s); - } - - void testSetter1(@PolyTainted TestFieldPolymorphism this, @PolyTainted String s) { - this.field = s; - } - - void testSetter2(@PolyTainted TestFieldPolymorphism this, @Untainted String s) { - this.field = s; - } - - void testSetter3(@PolyTainted TestFieldPolymorphism this, @Tainted String s) { - // :: error: (assignment.type.incompatible) - this.field = s; - } - - @PolyTainted String testGetter1(@PolyTainted TestFieldPolymorphism this) { - return this.field; - } - - @Untainted String testGetter2(@PolyTainted TestFieldPolymorphism this) { - // :: error: (return.type.incompatible) - return this.field; - } - - static @Untainted String testInstantiateUntaintedGetter(@Untainted TestFieldPolymorphism c) { - return c.field; - } - - static void testInstantiateUntaintedSetter( - @Untainted TestFieldPolymorphism c, @Tainted String s) { - // :: error: (assignment.type.incompatible) - c.field = s; - } - - static @Untainted String testInstantiateTaintedGetter(@Tainted TestFieldPolymorphism c) { - // :: error: (return.type.incompatible) - return c.field; - } - - static void testInstantiateTaintedSetter(@Tainted TestFieldPolymorphism c, @Tainted String s) { - c.field = s; - } + @PolyTainted String field; + + @PolyTainted TestFieldPolymorphism(@PolyTainted String s) { + this.field = s; + } + + @PolyTainted TestFieldPolymorphism testConstructor(@PolyTainted String s) { + return new TestFieldPolymorphism(s); + } + + void testSetter1(@PolyTainted TestFieldPolymorphism this, @PolyTainted String s) { + this.field = s; + } + + void testSetter2(@PolyTainted TestFieldPolymorphism this, @Untainted String s) { + this.field = s; + } + + void testSetter3(@PolyTainted TestFieldPolymorphism this, @Tainted String s) { + // :: error: (assignment.type.incompatible) + this.field = s; + } + + @PolyTainted String testGetter1(@PolyTainted TestFieldPolymorphism this) { + return this.field; + } + + @Untainted String testGetter2(@PolyTainted TestFieldPolymorphism this) { + // :: error: (return.type.incompatible) + return this.field; + } + + static @Untainted String testInstantiateUntaintedGetter(@Untainted TestFieldPolymorphism c) { + return c.field; + } + + static void testInstantiateUntaintedSetter( + @Untainted TestFieldPolymorphism c, @Tainted String s) { + // :: error: (assignment.type.incompatible) + c.field = s; + } + + static @Untainted String testInstantiateTaintedGetter(@Tainted TestFieldPolymorphism c) { + // :: error: (return.type.incompatible) + return c.field; + } + + static void testInstantiateTaintedSetter(@Tainted TestFieldPolymorphism c, @Tainted String s) { + c.field = s; + } } diff --git a/checker/tests/tainting/TestNoQualifierParameterConflicting.java b/checker/tests/tainting/TestNoQualifierParameterConflicting.java index 0e8b2e41fbb..250380a2938 100644 --- a/checker/tests/tainting/TestNoQualifierParameterConflicting.java +++ b/checker/tests/tainting/TestNoQualifierParameterConflicting.java @@ -7,10 +7,10 @@ // :: error: (conflicting.qual.param) public class TestNoQualifierParameterConflicting { - @HasQualifierParameter(Tainted.class) - static class Super {} + @HasQualifierParameter(Tainted.class) + static class Super {} - @NoQualifierParameter(Tainted.class) - // :: error: (conflicting.qual.param) - static class Sup extends Super {} + @NoQualifierParameter(Tainted.class) + // :: error: (conflicting.qual.param) + static class Sup extends Super {} } diff --git a/checker/tests/tainting/TypeInvalid.java b/checker/tests/tainting/TypeInvalid.java index 274db2ca051..65b4623a35f 100644 --- a/checker/tests/tainting/TypeInvalid.java +++ b/checker/tests/tainting/TypeInvalid.java @@ -9,53 +9,53 @@ import org.checkerframework.checker.tainting.qual.Untainted; abstract class TypeInvalid { - // :: error: (type.invalid.conflicting.annos) - static @Untainted @Tainted class Inner {} - - // Duplication forbidden - // :: error: (type.invalid.conflicting.annos) - void bad(@Tainted @Untainted TypeInvalid c) { - // :: error: (type.invalid.conflicting.annos) - Object o = new @Tainted @Untainted Object(); - // :: error: (type.invalid.conflicting.annos) - o = new @Tainted @Untainted Object(); - // :: error: (type.invalid.conflicting.annos) - o = o.equals(new @Tainted @Untainted Object()); // :: error: (type.invalid.conflicting.annos) - o = (Object) new @Tainted @Untainted Object(); - // :: error: (type.invalid.conflicting.annos) - o = (@Tainted @Untainted TypeInvalid) o; - // :: error: (type.invalid.conflicting.annos) - o = (new @Tainted @Untainted Object()) instanceof Object; + static @Untainted @Tainted class Inner {} + + // Duplication forbidden // :: error: (type.invalid.conflicting.annos) - // :: warning: (instanceof.unsafe) - o = o instanceof @Tainted @Untainted TypeInvalid; - } + void bad(@Tainted @Untainted TypeInvalid c) { + // :: error: (type.invalid.conflicting.annos) + Object o = new @Tainted @Untainted Object(); + // :: error: (type.invalid.conflicting.annos) + o = new @Tainted @Untainted Object(); + // :: error: (type.invalid.conflicting.annos) + o = o.equals(new @Tainted @Untainted Object()); + // :: error: (type.invalid.conflicting.annos) + o = (Object) new @Tainted @Untainted Object(); + // :: error: (type.invalid.conflicting.annos) + o = (@Tainted @Untainted TypeInvalid) o; + // :: error: (type.invalid.conflicting.annos) + o = (new @Tainted @Untainted Object()) instanceof Object; + // :: error: (type.invalid.conflicting.annos) + // :: warning: (instanceof.unsafe) + o = o instanceof @Tainted @Untainted TypeInvalid; + } - // :: error: (type.invalid.conflicting.annos) - @Tainted @Untainted Object bar() { - return null; - } + // :: error: (type.invalid.conflicting.annos) + @Tainted @Untainted Object bar() { + return null; + } - // :: error: (type.invalid.conflicting.annos) - abstract @Tainted @Untainted Object absbar(); + // :: error: (type.invalid.conflicting.annos) + abstract @Tainted @Untainted Object absbar(); - void voidmethod() {} + void voidmethod() {} - TypeInvalid() {} + TypeInvalid() {} - // :: error: (type.invalid.conflicting.annos) - @Tainted @Untainted TypeInvalid(int p) {} + // :: error: (type.invalid.conflicting.annos) + @Tainted @Untainted TypeInvalid(int p) {} - // :: error: (type.invalid.conflicting.annos) - void recv(@Tainted @Untainted TypeInvalid this) {} + // :: error: (type.invalid.conflicting.annos) + void recv(@Tainted @Untainted TypeInvalid this) {} - // :: error: (type.invalid.conflicting.annos) - @Tainted @Untainted Object field; + // :: error: (type.invalid.conflicting.annos) + @Tainted @Untainted Object field; - // TODO: Note the error marker positions for the errors on fields - // and method return types. Maybe these should be improved. + // TODO: Note the error marker positions for the errors on fields + // and method return types. Maybe these should be improved. - // :: error: (type.invalid.conflicting.annos) - void athro() throws @Tainted @Untainted Exception {} + // :: error: (type.invalid.conflicting.annos) + void athro() throws @Tainted @Untainted Exception {} } diff --git a/checker/tests/tainting/WildcardArrayBound.java b/checker/tests/tainting/WildcardArrayBound.java index b8c7da7fc26..d17e55d9555 100644 --- a/checker/tests/tainting/WildcardArrayBound.java +++ b/checker/tests/tainting/WildcardArrayBound.java @@ -1,39 +1,40 @@ package wildcards; -import java.io.Serializable; import org.checkerframework.checker.tainting.qual.Tainted; import org.checkerframework.checker.tainting.qual.Untainted; +import java.io.Serializable; + public class WildcardArrayBound { - interface MyInterface {} - - abstract static class Other { - void use1( - Other x, - Other<@Tainted MyInterface @Untainted []> y) { - Other z = y; - // :: error: (assignment.type.incompatible) - x = y; - } + interface MyInterface {} - void use( - Other x, - Other<@Tainted MyInterface @Untainted []> y) { - // :: error: (assignment.type.incompatible) - x = y; - @Untainted Serializable s = x.getU(); - @Untainted MyInterface @Untainted [] sw = x.getU(); - } + abstract static class Other { + void use1( + Other x, + Other<@Tainted MyInterface @Untainted []> y) { + Other z = y; + // :: error: (assignment.type.incompatible) + x = y; + } - abstract U getU(); - } + void use( + Other x, + Other<@Tainted MyInterface @Untainted []> y) { + // :: error: (assignment.type.incompatible) + x = y; + @Untainted Serializable s = x.getU(); + @Untainted MyInterface @Untainted [] sw = x.getU(); + } - abstract static class Another { - void use(Another x) { - Serializable s = x.getU(); - MyInterface[] sw = x.getU(); + abstract U getU(); } - abstract U getU(); - } + abstract static class Another { + void use(Another x) { + Serializable s = x.getU(); + MyInterface[] sw = x.getU(); + } + + abstract U getU(); + } } diff --git a/checker/tests/tainting/WildcardMethodArgument.java b/checker/tests/tainting/WildcardMethodArgument.java index d9ad256d297..1403ab76e4a 100644 --- a/checker/tests/tainting/WildcardMethodArgument.java +++ b/checker/tests/tainting/WildcardMethodArgument.java @@ -1,13 +1,14 @@ import com.sun.source.tree.ExpressionTree; + import java.util.ArrayList; import java.util.List; public class WildcardMethodArgument { - abstract static class MyClass { - abstract List getArguments(); - } + abstract static class MyClass { + abstract List getArguments(); + } - void method(MyClass myClass) { - List javacArgs = new ArrayList<>(myClass.getArguments()); - } + void method(MyClass myClass) { + List javacArgs = new ArrayList<>(myClass.getArguments()); + } } diff --git a/checker/tests/tainting/java17/TaintingBindingVariable.java b/checker/tests/tainting/java17/TaintingBindingVariable.java index edb634a7f8f..0cea4fb27f1 100644 --- a/checker/tests/tainting/java17/TaintingBindingVariable.java +++ b/checker/tests/tainting/java17/TaintingBindingVariable.java @@ -3,30 +3,30 @@ public class TaintingBindingVariable { - void bar(@Untainted Object o) { - if (o instanceof @Untainted String s) { - @Untainted String f = s; + void bar(@Untainted Object o) { + if (o instanceof @Untainted String s) { + @Untainted String f = s; + } + if (o instanceof String s) { + @Untainted String f2 = s; + } } - if (o instanceof String s) { - @Untainted String f2 = s; - } - } - void bar2(Object o) { - // :: warning: (instanceof.pattern.unsafe) - if (o instanceof @Untainted String s) { - @Untainted String f = s; - } - if (o instanceof String s) { - // :: error: (assignment.type.incompatible) - @Untainted String f2 = s; + void bar2(Object o) { + // :: warning: (instanceof.pattern.unsafe) + if (o instanceof @Untainted String s) { + @Untainted String f = s; + } + if (o instanceof String s) { + // :: error: (assignment.type.incompatible) + @Untainted String f2 = s; + } } - } - void bar3(Object o, boolean b) { - // :: warning: (instanceof.pattern.unsafe) - if (b && o instanceof @Untainted String s) { - @Untainted String f = s; + void bar3(Object o, boolean b) { + // :: warning: (instanceof.pattern.unsafe) + if (b && o instanceof @Untainted String s) { + @Untainted String f = s; + } } - } } diff --git a/checker/tests/tainting/withdefault/NoQualifierTest.java b/checker/tests/tainting/withdefault/NoQualifierTest.java index 409daf01a26..2ca037e1e88 100644 --- a/checker/tests/tainting/withdefault/NoQualifierTest.java +++ b/checker/tests/tainting/withdefault/NoQualifierTest.java @@ -6,6 +6,6 @@ @NoQualifierParameter(Tainted.class) public class NoQualifierTest { - // :: error: (invalid.polymorphic.qualifier.use) - @PolyTainted int field; + // :: error: (invalid.polymorphic.qualifier.use) + @PolyTainted int field; } diff --git a/checker/tests/tainting/withdefault/WithDefault.java b/checker/tests/tainting/withdefault/WithDefault.java index 2e10f8df626..59e1ee33cbd 100644 --- a/checker/tests/tainting/withdefault/WithDefault.java +++ b/checker/tests/tainting/withdefault/WithDefault.java @@ -3,5 +3,5 @@ import org.checkerframework.checker.tainting.qual.PolyTainted; public class WithDefault { - @PolyTainted int field; + @PolyTainted int field; } diff --git a/checker/tests/units/Addition.java b/checker/tests/units/Addition.java index 244e8736a2d..557911d16a4 100644 --- a/checker/tests/units/Addition.java +++ b/checker/tests/units/Addition.java @@ -36,391 +36,391 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Addition { - // Addition is legal when the operands have the same units. - void good() { - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - @A int sAmpere = aAmpere + bAmpere; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - @cd int sCandela = aCandela + bCandela; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - @C int sCelsius = aCelsius + bCelsius; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - @g int sGram = aGram + bGram; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - @h int sHour = aHour + bHour; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - @K int sKelvin = aKelvin + bKelvin; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - @kg int sKilogram = aKilogram + bKilogram; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - @km int sKilometer = aKilometer + bKilometer; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - @km2 int sSquareKilometer = aSquareKilometer + bSquareKilometer; - - // Cubic kilometer - @km3 int aCubicKilometer = 5 * UnitsTools.km3; - @km3 int bCubicKilometer = 5 * UnitsTools.km3; - @km3 int sCubicKilometer = aCubicKilometer + bCubicKilometer; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int sKilometerPerHour = aKilometerPerHour + bKilometerPerHour; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - @m int sMeter = aMeter + bMeter; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - @m2 int sSquareMeter = aSquareMeter + bSquareMeter; - - // Cubic meter - @m3 int aCubicMeter = 5 * UnitsTools.m3; - @m3 int bCubicMeter = 5 * UnitsTools.m3; - @m3 int sCubicMeter = aCubicMeter + bCubicMeter; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int sMeterPerSecond = aMeterPerSecond + bMeterPerSecond; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeterPerSecondSquare; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - @min int sMinute = aMinute + bMinute; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - @mm int sMillimeter = aMillimeter + bMillimeter; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int sSquareMillimeter = aSquareMillimeter + bSquareMillimeter; - - // Cubic millimeter - @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int sCubicMillimeter = aCubicMillimeter + bCubicMillimeter; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - @mol int sMole = aMole + bMole; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - @N int sNewton = aNewton + bNewton; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - @kN int sKilonewton = aKilonewton + bKilonewton; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - @s int sSecond = aSecond + bSecond; - } - - // Addition is illegal when the operands have different units or one is unqualified. In these - // tests, we cycle between the result and the first or second operand having an incorrect type. - void bad() { - // Dimensions - // Acceleration - @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; - @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; - - // Area - @Area int aArea = 5 * UnitsTools.km2; - @Area int bArea = 5 * UnitsTools.mm2; - - // Current - @Current int aCurrent = 5 * UnitsTools.A; - @Current int bCurrent = 5 * UnitsTools.A; - - // Force - @Force int aForce = 5 * UnitsTools.N; - @Force int bForce = 5 * UnitsTools.N; - - // Length - @Length int aLength = 5 * UnitsTools.m; - @Length int bLength = 5 * UnitsTools.mm; - - // Luminance - @Luminance int aLuminance = 5 * UnitsTools.cd; - @Luminance int bLuminance = 5 * UnitsTools.cd; - - // Mass - @Mass int aMass = 5 * UnitsTools.kg; - @Mass int bMass = 5 * UnitsTools.g; - - // Substance - @Substance int aSubstance = 5 * UnitsTools.mol; - @Substance int bSubstance = 5 * UnitsTools.mol; - - // Temperature - @Temperature int aTemperature = 5 * UnitsTools.K; - @Temperature int bTemperature = 5 * UnitsTools.K; - - // Time - @Time int aTime = 5 * UnitsTools.min; - @Time int bTime = 5 * UnitsTools.h; - - // Dimensions - // Acceleration - // :: error: (assignment.type.incompatible) - @Acceleration int sAcceleration = aAcceleration + bMass; - - // Area - // :: error: (assignment.type.incompatible) - @Luminance int sLuminance = aArea + bArea; - - // Current - // :: error: (assignment.type.incompatible) - @Current int sCurrent = aMass + bCurrent; - - // Length - // :: error: (assignment.type.incompatible) - @Length int sLength = aLength + bSubstance; - - // Luminance - // :: error: (assignment.type.incompatible) - @Temperature int sTemperature = aLuminance + bLuminance; - - // Mass - // :: error: (assignment.type.incompatible) - @Mass int sMass = aTemperature + bMass; - - // Substance - // :: error: (assignment.type.incompatible) - @Substance int sSubstance = aSubstance + bCurrent; - - // Temperature - // :: error: (assignment.type.incompatible) - @Area int sArea = aTemperature + bTemperature; - - // Time - // :: error: (assignment.type.incompatible) - @Time int sTime = aArea + bTime; - - // Force - // :: error: (assignment.type.incompatible) - sMass = aForce + bForce; - - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - - // Metric Ton - @t int aMetricTon = 5 * UnitsTools.t; - @t int bMetricTon = 5 * UnitsTools.t; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - - // Units - // Amperes - // :: error: (assignment.type.incompatible) - @g int sGram = aAmpere + bAmpere; - - // Candela - // :: error: (assignment.type.incompatible) - @cd int sCandela = aTemperature + bCandela; - - // Celsius - // :: error: (assignment.type.incompatible) - @C int sCelsius = aCelsius + bMillimeter; - - // Gram - // :: error: (assignment.type.incompatible) - @kg int sKilogram = aGram + bGram; - - // Hour - // :: error: (assignment.type.incompatible) - @h int sHour = aSquareMeter + bHour; - - // Kelvin - // :: error: (assignment.type.incompatible) - @K int sKelvin = aKelvin + bSecond; - - // Kilogram - // :: error: (assignment.type.incompatible) - @kmPERh int sKilometerPerHour = aKilogram + bKilogram; - - // Kilometer - // :: error: (assignment.type.incompatible) - @km int sKilometer = aCandela + bKilometer; - - // Square kilometer - // :: error: (assignment.type.incompatible) - @km2 int sSquareKilometer = aSquareKilometer + bAmpere; - - // Kilometer per hour - // :: error: (assignment.type.incompatible) - @mPERs int sMeterPerSecond = aKilometerPerHour + bKilometerPerHour; - - // Meter - // :: error: (assignment.type.incompatible) - @m int sMeter = aHour + bMeter; - - // Square meter - // :: error: (assignment.type.incompatible) - @m2 int sSquareMeter = aSquareMeter + bGram; - - // Meter per second - // :: error: (assignment.type.incompatible) - @mm2 int sSquareMillimeter = aMeterPerSecond + bMeterPerSecond; - - // Meter per second square - // :: error: (assignment.type.incompatible) - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeter; - - // Minute - // :: error: (assignment.type.incompatible) - @min int sMinute = aMole + bMinute; - - // Millimeter - // :: error: (assignment.type.incompatible) - @mm int sMillimeter = aMillimeter + bHour; - - // Square millimeter - // :: error: (assignment.type.incompatible) - @A int sAmpere = aSquareMillimeter + bSquareMillimeter; - - // Mole - // :: error: (assignment.type.incompatible) - @mol int sMole = aCandela + bMole; - - // Second - // :: error: (assignment.type.incompatible) - @s int sSecond = aSecond + bSquareKilometer; - - // Newton - // :: error: (assignment.type.incompatible) - sKilogram = aNewton + bNewton; - - // Kilonewton - // :: error: (assignment.type.incompatible) - @kN int sKilonewton = aKilonewton + bNewton; - - // Metric Ton - // :: error: (assignment.type.incompatible) - @N int sNewton = aNewton + bMetricTon; - } + // Addition is legal when the operands have the same units. + void good() { + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + @A int sAmpere = aAmpere + bAmpere; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + @cd int sCandela = aCandela + bCandela; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + @C int sCelsius = aCelsius + bCelsius; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + @g int sGram = aGram + bGram; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + @h int sHour = aHour + bHour; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + @K int sKelvin = aKelvin + bKelvin; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + @kg int sKilogram = aKilogram + bKilogram; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + @km int sKilometer = aKilometer + bKilometer; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + @km2 int sSquareKilometer = aSquareKilometer + bSquareKilometer; + + // Cubic kilometer + @km3 int aCubicKilometer = 5 * UnitsTools.km3; + @km3 int bCubicKilometer = 5 * UnitsTools.km3; + @km3 int sCubicKilometer = aCubicKilometer + bCubicKilometer; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int sKilometerPerHour = aKilometerPerHour + bKilometerPerHour; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + @m int sMeter = aMeter + bMeter; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + @m2 int sSquareMeter = aSquareMeter + bSquareMeter; + + // Cubic meter + @m3 int aCubicMeter = 5 * UnitsTools.m3; + @m3 int bCubicMeter = 5 * UnitsTools.m3; + @m3 int sCubicMeter = aCubicMeter + bCubicMeter; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int sMeterPerSecond = aMeterPerSecond + bMeterPerSecond; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeterPerSecondSquare; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + @min int sMinute = aMinute + bMinute; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + @mm int sMillimeter = aMillimeter + bMillimeter; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int sSquareMillimeter = aSquareMillimeter + bSquareMillimeter; + + // Cubic millimeter + @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int sCubicMillimeter = aCubicMillimeter + bCubicMillimeter; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + @mol int sMole = aMole + bMole; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + @N int sNewton = aNewton + bNewton; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + @kN int sKilonewton = aKilonewton + bKilonewton; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + @s int sSecond = aSecond + bSecond; + } + + // Addition is illegal when the operands have different units or one is unqualified. In these + // tests, we cycle between the result and the first or second operand having an incorrect type. + void bad() { + // Dimensions + // Acceleration + @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; + @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; + + // Area + @Area int aArea = 5 * UnitsTools.km2; + @Area int bArea = 5 * UnitsTools.mm2; + + // Current + @Current int aCurrent = 5 * UnitsTools.A; + @Current int bCurrent = 5 * UnitsTools.A; + + // Force + @Force int aForce = 5 * UnitsTools.N; + @Force int bForce = 5 * UnitsTools.N; + + // Length + @Length int aLength = 5 * UnitsTools.m; + @Length int bLength = 5 * UnitsTools.mm; + + // Luminance + @Luminance int aLuminance = 5 * UnitsTools.cd; + @Luminance int bLuminance = 5 * UnitsTools.cd; + + // Mass + @Mass int aMass = 5 * UnitsTools.kg; + @Mass int bMass = 5 * UnitsTools.g; + + // Substance + @Substance int aSubstance = 5 * UnitsTools.mol; + @Substance int bSubstance = 5 * UnitsTools.mol; + + // Temperature + @Temperature int aTemperature = 5 * UnitsTools.K; + @Temperature int bTemperature = 5 * UnitsTools.K; + + // Time + @Time int aTime = 5 * UnitsTools.min; + @Time int bTime = 5 * UnitsTools.h; + + // Dimensions + // Acceleration + // :: error: (assignment.type.incompatible) + @Acceleration int sAcceleration = aAcceleration + bMass; + + // Area + // :: error: (assignment.type.incompatible) + @Luminance int sLuminance = aArea + bArea; + + // Current + // :: error: (assignment.type.incompatible) + @Current int sCurrent = aMass + bCurrent; + + // Length + // :: error: (assignment.type.incompatible) + @Length int sLength = aLength + bSubstance; + + // Luminance + // :: error: (assignment.type.incompatible) + @Temperature int sTemperature = aLuminance + bLuminance; + + // Mass + // :: error: (assignment.type.incompatible) + @Mass int sMass = aTemperature + bMass; + + // Substance + // :: error: (assignment.type.incompatible) + @Substance int sSubstance = aSubstance + bCurrent; + + // Temperature + // :: error: (assignment.type.incompatible) + @Area int sArea = aTemperature + bTemperature; + + // Time + // :: error: (assignment.type.incompatible) + @Time int sTime = aArea + bTime; + + // Force + // :: error: (assignment.type.incompatible) + sMass = aForce + bForce; + + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + + // Metric Ton + @t int aMetricTon = 5 * UnitsTools.t; + @t int bMetricTon = 5 * UnitsTools.t; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + + // Units + // Amperes + // :: error: (assignment.type.incompatible) + @g int sGram = aAmpere + bAmpere; + + // Candela + // :: error: (assignment.type.incompatible) + @cd int sCandela = aTemperature + bCandela; + + // Celsius + // :: error: (assignment.type.incompatible) + @C int sCelsius = aCelsius + bMillimeter; + + // Gram + // :: error: (assignment.type.incompatible) + @kg int sKilogram = aGram + bGram; + + // Hour + // :: error: (assignment.type.incompatible) + @h int sHour = aSquareMeter + bHour; + + // Kelvin + // :: error: (assignment.type.incompatible) + @K int sKelvin = aKelvin + bSecond; + + // Kilogram + // :: error: (assignment.type.incompatible) + @kmPERh int sKilometerPerHour = aKilogram + bKilogram; + + // Kilometer + // :: error: (assignment.type.incompatible) + @km int sKilometer = aCandela + bKilometer; + + // Square kilometer + // :: error: (assignment.type.incompatible) + @km2 int sSquareKilometer = aSquareKilometer + bAmpere; + + // Kilometer per hour + // :: error: (assignment.type.incompatible) + @mPERs int sMeterPerSecond = aKilometerPerHour + bKilometerPerHour; + + // Meter + // :: error: (assignment.type.incompatible) + @m int sMeter = aHour + bMeter; + + // Square meter + // :: error: (assignment.type.incompatible) + @m2 int sSquareMeter = aSquareMeter + bGram; + + // Meter per second + // :: error: (assignment.type.incompatible) + @mm2 int sSquareMillimeter = aMeterPerSecond + bMeterPerSecond; + + // Meter per second square + // :: error: (assignment.type.incompatible) + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare + bMeter; + + // Minute + // :: error: (assignment.type.incompatible) + @min int sMinute = aMole + bMinute; + + // Millimeter + // :: error: (assignment.type.incompatible) + @mm int sMillimeter = aMillimeter + bHour; + + // Square millimeter + // :: error: (assignment.type.incompatible) + @A int sAmpere = aSquareMillimeter + bSquareMillimeter; + + // Mole + // :: error: (assignment.type.incompatible) + @mol int sMole = aCandela + bMole; + + // Second + // :: error: (assignment.type.incompatible) + @s int sSecond = aSecond + bSquareKilometer; + + // Newton + // :: error: (assignment.type.incompatible) + sKilogram = aNewton + bNewton; + + // Kilonewton + // :: error: (assignment.type.incompatible) + @kN int sKilonewton = aKilonewton + bNewton; + + // Metric Ton + // :: error: (assignment.type.incompatible) + @N int sNewton = aNewton + bMetricTon; + } } diff --git a/checker/tests/units/BasicUnits.java b/checker/tests/units/BasicUnits.java index 16a42e9ba23..68167f36ebb 100644 --- a/checker/tests/units/BasicUnits.java +++ b/checker/tests/units/BasicUnits.java @@ -20,130 +20,130 @@ public class BasicUnits { - void demo() { - // :: error: (assignment.type.incompatible) - @m int merr = 5; + void demo() { + // :: error: (assignment.type.incompatible) + @m int merr = 5; - @m int m = 5 * UnitsTools.m; - @s int s = 9 * UnitsTools.s; + @m int m = 5 * UnitsTools.m; + @s int s = 9 * UnitsTools.s; - // :: error: (assignment.type.incompatible) - @km int kmerr = 10; - @km int km = 10 * UnitsTools.km; + // :: error: (assignment.type.incompatible) + @km int kmerr = 10; + @km int km = 10 * UnitsTools.km; - // this is allowed, unqualified is a supertype of all units - int bad = m / s; + // this is allowed, unqualified is a supertype of all units + int bad = m / s; - @mPERs int good = m / s; + @mPERs int good = m / s; - // :: error: (assignment.type.incompatible) - @mPERs int b1 = s / m; + // :: error: (assignment.type.incompatible) + @mPERs int b1 = s / m; - // :: error: (assignment.type.incompatible) - @mPERs int b2 = m * s; + // :: error: (assignment.type.incompatible) + @mPERs int b2 = m * s; - @mPERs2 int goodaccel = m / s / s; + @mPERs2 int goodaccel = m / s / s; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel1 = s / m / s; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel1 = s / m / s; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel2 = s / s / m; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel2 = s / s / m; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel3 = s * s / m; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel3 = s * s / m; - // :: error: (assignment.type.incompatible) - @mPERs2 int badaccel4 = m * s * s; + // :: error: (assignment.type.incompatible) + @mPERs2 int badaccel4 = m * s * s; - @Area int ae = m * m; - @m2 int gae = m * m; + @Area int ae = m * m; + @m2 int gae = m * m; - // :: error: (assignment.type.incompatible) - @Area int bae = m * m * m; + // :: error: (assignment.type.incompatible) + @Area int bae = m * m * m; - // :: error: (assignment.type.incompatible) - @km2 int bae1 = m * m; + // :: error: (assignment.type.incompatible) + @km2 int bae1 = m * m; - @Volume int vol = m * m * m; - @m3 int gvol = m * m * m; + @Volume int vol = m * m * m; + @m3 int gvol = m * m * m; - // :: error: (assignment.type.incompatible) - @Volume int bvol = m * m * m * m; + // :: error: (assignment.type.incompatible) + @Volume int bvol = m * m * m * m; - // :: error: (assignment.type.incompatible) - @km3 int bvol1 = m * m * m; + // :: error: (assignment.type.incompatible) + @km3 int bvol1 = m * m * m; - @radians double rad = 20.0d * UnitsTools.rad; - @degrees double deg = 30.0d * UnitsTools.deg; + @radians double rad = 20.0d * UnitsTools.rad; + @degrees double deg = 30.0d * UnitsTools.deg; - @degrees double rToD1 = UnitsTools.toDegrees(rad); - // :: error: (argument.type.incompatible) - @degrees double rToD2 = UnitsTools.toDegrees(deg); - // :: error: (assignment.type.incompatible) - @radians double rToD3 = UnitsTools.toDegrees(rad); + @degrees double rToD1 = UnitsTools.toDegrees(rad); + // :: error: (argument.type.incompatible) + @degrees double rToD2 = UnitsTools.toDegrees(deg); + // :: error: (assignment.type.incompatible) + @radians double rToD3 = UnitsTools.toDegrees(rad); - @radians double dToR1 = UnitsTools.toRadians(deg); - // :: error: (argument.type.incompatible) - @radians double rToR2 = UnitsTools.toRadians(rad); - // :: error: (assignment.type.incompatible) - @degrees double rToR3 = UnitsTools.toRadians(deg); + @radians double dToR1 = UnitsTools.toRadians(deg); + // :: error: (argument.type.incompatible) + @radians double rToR2 = UnitsTools.toRadians(rad); + // :: error: (assignment.type.incompatible) + @degrees double rToR3 = UnitsTools.toRadians(deg); - // speed conversion - @mPERs int mPs = 30 * UnitsTools.mPERs; - @kmPERh int kmPhr = 20 * UnitsTools.kmPERh; + // speed conversion + @mPERs int mPs = 30 * UnitsTools.mPERs; + @kmPERh int kmPhr = 20 * UnitsTools.kmPERh; - @kmPERh int kmPhrRes = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); - @mPERs int mPsRes = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); + @kmPERh int kmPhrRes = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); + @mPERs int mPsRes = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); - // :: error: (assignment.type.incompatible) - @mPERs int mPsResBad = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); - // :: error: (assignment.type.incompatible) - @kmPERh int kmPhrResBad = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); + // :: error: (assignment.type.incompatible) + @mPERs int mPsResBad = (int) UnitsTools.fromMeterPerSecondToKiloMeterPerHour(mPs); + // :: error: (assignment.type.incompatible) + @kmPERh int kmPhrResBad = (int) UnitsTools.fromKiloMeterPerHourToMeterPerSecond(kmPhr); - // speeds - @km int kilometers = 10 * UnitsTools.km; - @h int hours = UnitsTools.h; - @kmPERh int speed = kilometers / hours; + // speeds + @km int kilometers = 10 * UnitsTools.km; + @h int hours = UnitsTools.h; + @kmPERh int speed = kilometers / hours; - // Addition/substraction only accepts another @kmPERh value - // :: error: (assignment.type.incompatible) - speed = speed + 5; - // :: error: (compound.assignment.type.incompatible) - speed += 5; + // Addition/substraction only accepts another @kmPERh value + // :: error: (assignment.type.incompatible) + speed = speed + 5; + // :: error: (compound.assignment.type.incompatible) + speed += 5; - speed += speed; - speed = (speed += speed); + speed += speed; + speed = (speed += speed); - // Multiplication/division with an unqualified type is allowed - speed = kilometers / hours * 2; - speed /= 2; + // Multiplication/division with an unqualified type is allowed + speed = kilometers / hours * 2; + speed /= 2; - speed = (speed /= 2); + speed = (speed /= 2); - @kg int kiloGrams = 1000 * UnitsTools.kg; - @t int metricTons = UnitsTools.fromKiloGramToMetricTon(kiloGrams); - kiloGrams = UnitsTools.fromMetricTonToKiloGram(metricTons); - } + @kg int kiloGrams = 1000 * UnitsTools.kg; + @t int metricTons = UnitsTools.fromKiloGramToMetricTon(kiloGrams); + kiloGrams = UnitsTools.fromMetricTonToKiloGram(metricTons); + } - void prefixOutputTest() { - @m int x = 5 * UnitsTools.m; - @m(Prefix.kilo) int y = 2 * UnitsTools.km; - @m(Prefix.one) int z = 3 * UnitsTools.m; - @km int y2 = 3 * UnitsTools.km; + void prefixOutputTest() { + @m int x = 5 * UnitsTools.m; + @m(Prefix.kilo) int y = 2 * UnitsTools.km; + @m(Prefix.one) int z = 3 * UnitsTools.m; + @km int y2 = 3 * UnitsTools.km; - // :: error: (assignment.type.incompatible) - y2 = z; - // :: error: (assignment.type.incompatible) - y2 = x; - // :: error: (assignment.type.incompatible) - y = z; - // :: error: (assignment.type.incompatible) - y = x; + // :: error: (assignment.type.incompatible) + y2 = z; + // :: error: (assignment.type.incompatible) + y2 = x; + // :: error: (assignment.type.incompatible) + y = z; + // :: error: (assignment.type.incompatible) + y = x; - // :: error: (assignment.type.incompatible) - y2 = x * x; - // :: error: (assignment.type.incompatible) - y2 = z * z; - } + // :: error: (assignment.type.incompatible) + y2 = x * x; + // :: error: (assignment.type.incompatible) + y2 = z * z; + } } diff --git a/checker/tests/units/Consistency.java b/checker/tests/units/Consistency.java index 727d39bfe7d..3a42759a77b 100644 --- a/checker/tests/units/Consistency.java +++ b/checker/tests/units/Consistency.java @@ -14,34 +14,34 @@ */ public class Consistency { - @UnitsSame({0, 1}) - @UnitsProduct({0, 1, -1}) - @Area int calcArea(@Length int width, @Length int height) { - return width * height; - } + @UnitsSame({0, 1}) + @UnitsProduct({0, 1, -1}) + @Area int calcArea(@Length int width, @Length int height) { + return width * height; + } - void use() { - @m int m1, m2; - m1 = UnitsTools.toMeter(5); - m2 = UnitsTools.toMeter(51); + void use() { + @m int m1, m2; + m1 = UnitsTools.toMeter(5); + m2 = UnitsTools.toMeter(51); - @km int km1, km2; - km1 = UnitsTools.toMeter(5); - km2 = UnitsTools.toMeter(5); + @km int km1, km2; + km1 = UnitsTools.toMeter(5); + km2 = UnitsTools.toMeter(5); - @m2 int msq; - @km2 int kmsq; + @m2 int msq; + @km2 int kmsq; - // good - msq = calcArea(m1, m2); + // good + msq = calcArea(m1, m2); - // :: bad args - msq = calcArea(m1, km2); + // :: bad args + msq = calcArea(m1, km2); - // :: bad return - kmsq = calcArea(m1, m2); + // :: bad return + kmsq = calcArea(m1, m2); - // good - kmsq = calcArea(km1, km2); - } + // good + kmsq = calcArea(km1, km2); + } } diff --git a/checker/tests/units/Division.java b/checker/tests/units/Division.java index 2e2d37b0c33..bc0ff74bbf5 100644 --- a/checker/tests/units/Division.java +++ b/checker/tests/units/Division.java @@ -19,127 +19,127 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Division { - void d() { - // Basic division of same units, no units constraint on x - @m int am = 6 * UnitsTools.m, bm = 3 * UnitsTools.m; - int x = am / bm; - - // :: error: (assignment.type.incompatible) - @m int bad = am / bm; - - // Division removes the unit. - // As unqualified would be a supertype, we add another multiplication - // to make sure the result of the division is unqualified. - @s int div = (am / UnitsTools.m) * UnitsTools.s; - - // units setup - @m int m = 2 * UnitsTools.m; - @mm int mm = 8 * UnitsTools.mm; - @km int km = 4 * UnitsTools.km; - @s int s = 3 * UnitsTools.s; - @h int h = 5 * UnitsTools.h; - @m2 int m2 = 25 * UnitsTools.m2; - @km2 int km2 = 9 * UnitsTools.km2; - @mm2 int mm2 = 16 * UnitsTools.mm2; - @mPERs int mPERs = 20 * UnitsTools.mPERs; - @kmPERh int kmPERh = 2 * UnitsTools.kmPERh; - @mPERs2 int mPERs2 = 30 * UnitsTools.mPERs2; - @m3 int m3 = 125 * UnitsTools.m3; - @km3 int km3 = 27 * UnitsTools.km3; - @mm3 int mm3 = 64 * UnitsTools.mm3; - @kg int kg = 11 * UnitsTools.kg; - @t int t = 19 * UnitsTools.t; - @N int N = 7 * UnitsTools.N; - @kN int kN = 13 * UnitsTools.kN; - - // m / s = mPERs - @mPERs int velocitym = m / s; - // :: error: (assignment.type.incompatible) - velocitym = m / h; - - // km / h = kmPERh - @kmPERh int velocitykm = km / h; - // :: error: (assignment.type.incompatible) - velocitykm = km / s; - - // m2 / m = m - @m int distancem = m2 / m; - // :: error: (assignment.type.incompatible) - distancem = m2 / km; - - // km2 / km = km - @km int distancekm = km2 / km; - // :: error: (assignment.type.incompatible) - distancekm = km2 / m; - - // mm2 / mm = mm - @mm int distancemm = mm2 / mm; - // :: error: (assignment.type.incompatible) - distancemm = km2 / mm; - - // m3 / m2 = m - distancem = m3 / m2; - // :: error: (assignment.type.incompatible) - distancem = m3 / km2; - - // km3 / km2 = km - distancekm = km3 / km2; - // :: error: (assignment.type.incompatible) - distancekm = km3 / m2; - - // mm3 / mm2 = mm - distancemm = mm3 / mm2; - // :: error: (assignment.type.incompatible) - distancemm = km3 / mm2; - - // m / mPERs = s - @s int times = m / mPERs; - // :: error: (assignment.type.incompatible) - times = km / mPERs; - - // km / kmPERh = h - @h int timeh = km / kmPERh; - // :: error: (assignment.type.incompatible) - timeh = m / kmPERh; - - // mPERs / s = mPERs2 - @mPERs2 int accel1 = mPERs / s; - // :: error: (assignment.type.incompatible) - accel1 = kmPERh / s; - - // mPERs / mPERs2 = s - @s int times2 = mPERs / mPERs2; - // :: error: (assignment.type.incompatible) - times2 = kmPERh / mPERs2; - - // mPERs2 = N / kg - @mPERs2 int accel2 = N / kg; - // :: error: (assignment.type.incompatible) - accel2 = N / km; - - // mPERs2 = kN / t - @mPERs2 int accel3 = kN / t; - // :: error: (assignment.type.incompatible) - accel3 = N / t; - - // kg = N / mPERs2 - @kg int mass = N / mPERs2; - // :: error: (assignment.type.incompatible) - mass = s / mPERs2; - - // t = kN / mPERs2 - @t int mass2 = kN / mPERs2; - // :: error: (assignment.type.incompatible) - mass2 = N / mPERs2; - } - - void SpeedOfSoundTests() { - @mPERs double speedOfSound = (340.29 * UnitsTools.m) / (UnitsTools.s); - - @s double tenSeconds = 10.0 * UnitsTools.s; - @m double soundIn10Seconds = speedOfSound * tenSeconds; - - @m double length = 100.0 * UnitsTools.m; - @s double soundNeedTimeForLength = length / speedOfSound; - } + void d() { + // Basic division of same units, no units constraint on x + @m int am = 6 * UnitsTools.m, bm = 3 * UnitsTools.m; + int x = am / bm; + + // :: error: (assignment.type.incompatible) + @m int bad = am / bm; + + // Division removes the unit. + // As unqualified would be a supertype, we add another multiplication + // to make sure the result of the division is unqualified. + @s int div = (am / UnitsTools.m) * UnitsTools.s; + + // units setup + @m int m = 2 * UnitsTools.m; + @mm int mm = 8 * UnitsTools.mm; + @km int km = 4 * UnitsTools.km; + @s int s = 3 * UnitsTools.s; + @h int h = 5 * UnitsTools.h; + @m2 int m2 = 25 * UnitsTools.m2; + @km2 int km2 = 9 * UnitsTools.km2; + @mm2 int mm2 = 16 * UnitsTools.mm2; + @mPERs int mPERs = 20 * UnitsTools.mPERs; + @kmPERh int kmPERh = 2 * UnitsTools.kmPERh; + @mPERs2 int mPERs2 = 30 * UnitsTools.mPERs2; + @m3 int m3 = 125 * UnitsTools.m3; + @km3 int km3 = 27 * UnitsTools.km3; + @mm3 int mm3 = 64 * UnitsTools.mm3; + @kg int kg = 11 * UnitsTools.kg; + @t int t = 19 * UnitsTools.t; + @N int N = 7 * UnitsTools.N; + @kN int kN = 13 * UnitsTools.kN; + + // m / s = mPERs + @mPERs int velocitym = m / s; + // :: error: (assignment.type.incompatible) + velocitym = m / h; + + // km / h = kmPERh + @kmPERh int velocitykm = km / h; + // :: error: (assignment.type.incompatible) + velocitykm = km / s; + + // m2 / m = m + @m int distancem = m2 / m; + // :: error: (assignment.type.incompatible) + distancem = m2 / km; + + // km2 / km = km + @km int distancekm = km2 / km; + // :: error: (assignment.type.incompatible) + distancekm = km2 / m; + + // mm2 / mm = mm + @mm int distancemm = mm2 / mm; + // :: error: (assignment.type.incompatible) + distancemm = km2 / mm; + + // m3 / m2 = m + distancem = m3 / m2; + // :: error: (assignment.type.incompatible) + distancem = m3 / km2; + + // km3 / km2 = km + distancekm = km3 / km2; + // :: error: (assignment.type.incompatible) + distancekm = km3 / m2; + + // mm3 / mm2 = mm + distancemm = mm3 / mm2; + // :: error: (assignment.type.incompatible) + distancemm = km3 / mm2; + + // m / mPERs = s + @s int times = m / mPERs; + // :: error: (assignment.type.incompatible) + times = km / mPERs; + + // km / kmPERh = h + @h int timeh = km / kmPERh; + // :: error: (assignment.type.incompatible) + timeh = m / kmPERh; + + // mPERs / s = mPERs2 + @mPERs2 int accel1 = mPERs / s; + // :: error: (assignment.type.incompatible) + accel1 = kmPERh / s; + + // mPERs / mPERs2 = s + @s int times2 = mPERs / mPERs2; + // :: error: (assignment.type.incompatible) + times2 = kmPERh / mPERs2; + + // mPERs2 = N / kg + @mPERs2 int accel2 = N / kg; + // :: error: (assignment.type.incompatible) + accel2 = N / km; + + // mPERs2 = kN / t + @mPERs2 int accel3 = kN / t; + // :: error: (assignment.type.incompatible) + accel3 = N / t; + + // kg = N / mPERs2 + @kg int mass = N / mPERs2; + // :: error: (assignment.type.incompatible) + mass = s / mPERs2; + + // t = kN / mPERs2 + @t int mass2 = kN / mPERs2; + // :: error: (assignment.type.incompatible) + mass2 = N / mPERs2; + } + + void SpeedOfSoundTests() { + @mPERs double speedOfSound = (340.29 * UnitsTools.m) / (UnitsTools.s); + + @s double tenSeconds = 10.0 * UnitsTools.s; + @m double soundIn10Seconds = speedOfSound * tenSeconds; + + @m double length = 100.0 * UnitsTools.m; + @s double soundNeedTimeForLength = length / speedOfSound; + } } diff --git a/checker/tests/units/Issue4549.java b/checker/tests/units/Issue4549.java index 5df72565720..41c0fd9c082 100644 --- a/checker/tests/units/Issue4549.java +++ b/checker/tests/units/Issue4549.java @@ -2,10 +2,10 @@ import java.util.Map; class Issue4549 { - private Map map = new HashMap<>(); + private Map map = new HashMap<>(); - void testMethod(String s, int i) { - Long l = map.get(s); - l += i; - } + void testMethod(String s, int i) { + Long l = map.get(s); + l += i; + } } diff --git a/checker/tests/units/Manual.java b/checker/tests/units/Manual.java index 608b7833ac8..d4df3842e11 100644 --- a/checker/tests/units/Manual.java +++ b/checker/tests/units/Manual.java @@ -6,9 +6,9 @@ // Include all the examples from the manual here, // to ensure they work as expected. public class Manual { - void demo1() { - @m int meters = 5 * UnitsTools.m; - @s int secs = 2 * UnitsTools.s; - @mPERs int speed = meters / secs; - } + void demo1() { + @m int meters = 5 * UnitsTools.m; + @s int secs = 2 * UnitsTools.s; + @mPERs int speed = meters / secs; + } } diff --git a/checker/tests/units/Multiples.java b/checker/tests/units/Multiples.java index 0a21f98885b..250e5b3f1ba 100644 --- a/checker/tests/units/Multiples.java +++ b/checker/tests/units/Multiples.java @@ -21,187 +21,187 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Multiples { - void m() { - // Prefix assignment tests - // kg - @kg int kg = 5 * UnitsTools.kg; - @g(Prefix.kilo) int alsokg = kg; - // :: error: (assignment.type.incompatible) - @g(Prefix.giga) int notkg = kg; - // :: error: (assignment.type.incompatible) - kg = notkg; - kg = alsokg; - - // g - @g int g = 5 * UnitsTools.g; - @g(Prefix.one) int alsog = g; - // :: error: (assignment.type.incompatible) - @g(Prefix.milli) int notg = g; - // :: error: (assignment.type.incompatible) - notg = g; - g = alsog; - - // m - @m int m = 5 * UnitsTools.m; - @m(Prefix.one) int alsom = m; - // :: error: (assignment.type.incompatible) - @m(Prefix.giga) int notm = m; - // :: error: (assignment.type.incompatible) - m = notm; - m = alsom; - - // km - @km int km = 5 * UnitsTools.km; - @m(Prefix.kilo) int alsokm = km; - // :: error: (assignment.type.incompatible) - @m(Prefix.giga) int notkm = km; - // :: error: (assignment.type.incompatible) - km = notkm; - km = alsokm; - - // mm - @mm int mm = 5 * UnitsTools.mm; - @m(Prefix.milli) int alsomm = mm; - // :: error: (assignment.type.incompatible) - @m(Prefix.giga) int notmm = mm; - // :: error: (assignment.type.incompatible) - mm = notmm; - mm = alsomm; - - // N - @N int N = 5 * UnitsTools.N; - @N(Prefix.one) int alsoN = N; - @N(Prefix.giga) - // :: error: (assignment.type.incompatible) - int notN = N; - // :: error: (assignment.type.incompatible) - N = notN; - N = alsoN; - - // kN - @kN int kN = 5 * UnitsTools.kN; - @N(Prefix.kilo) int alsokN = kN; - @N(Prefix.giga) - // :: error: (assignment.type.incompatible) - int notkN = kN; - // :: error: (assignment.type.incompatible) - kN = notkN; - kN = alsokN; - - // s - @s int s = 5 * UnitsTools.s; - - // h - @h int h = 5 * UnitsTools.h; - - // m * m = m2 - @m2 int area = m * m; - // :: error: (assignment.type.incompatible) - @km2 int areambad1 = m * m; - // :: error: (assignment.type.incompatible) - @mm2 int areambad2 = m * m; - - // km * km = km2 - @km2 int karea = km * km; - // :: error: (assignment.type.incompatible) - @m2 int areakmbad1 = km * km; - // :: error: (assignment.type.incompatible) - @mm2 int areakmbad2 = km * km; - - // mm * mm = mm2 - @mm2 int marea = mm * mm; - // :: error: (assignment.type.incompatible) - @m2 int areammbad1 = mm * mm; - // :: error: (assignment.type.incompatible) - @km2 int areammbad2 = mm * mm; - - // m * m2 = m3 - @m3 int volume = m * area; - // :: error: (assignment.type.incompatible) - @km3 int volumembad1 = m * area; - // :: error: (assignment.type.incompatible) - @mm3 int volumembad2 = m * area; - - // km * km2 = km3 - @km3 int kvolume = km * karea; - // :: error: (assignment.type.incompatible) - @m3 int volumekmbad1 = km * karea; - // :: error: (assignment.type.incompatible) - @mm3 int volumekmbad2 = km * karea; - - // mm * mm2 = mm3 - @mm3 int mvolume = mm * marea; - // :: error: (assignment.type.incompatible) - @m3 int volumemmbad1 = mm * marea; - // :: error: (assignment.type.incompatible) - @km3 int volumemmbad2 = mm * marea; - - // m2 * m = m3 - volume = area * m; - // :: error: (assignment.type.incompatible) - volumembad1 = area * m; - // :: error: (assignment.type.incompatible) - volumembad2 = area * m; - - // km2 * km = km3 - kvolume = karea * km; - // :: error: (assignment.type.incompatible) - volumekmbad1 = karea * km; - // :: error: (assignment.type.incompatible) - volumekmbad2 = karea * km; - - // mm2 * mm = mm3 - mvolume = marea * mm; - // :: error: (assignment.type.incompatible) - volumemmbad1 = marea * mm; - // :: error: (assignment.type.incompatible) - volumemmbad2 = marea * mm; - - // s * mPERs = m - @mPERs int speedm = 10 * UnitsTools.mPERs; - @m int lengthm = s * speedm; - lengthm = speedm * s; - // :: error: (assignment.type.incompatible) - @km int lengthmbad1 = s * speedm; - // :: error: (assignment.type.incompatible) - @mm int lengthmbad2 = s * speedm; - - // s * mPERs2 = mPERs - @mPERs2 int accelm = 20 * UnitsTools.mPERs2; - @mPERs int speedm2 = s * accelm; - speedm2 = accelm * s; - // :: error: (assignment.type.incompatible) - @kmPERh int speedm2bad1 = s * accelm; - - // h * kmPERh = km - @kmPERh int speedkm = 30 * UnitsTools.kmPERh; - @km int lengthkm = h * speedkm; - lengthkm = speedkm * h; - // :: error: (assignment.type.incompatible) - @m int lengthkmbad1 = h * speedkm; - // :: error: (assignment.type.incompatible) - @mm int lengthkmbad2 = h * speedkm; - - // kg * mPERs2 = N - @kg int mass = 40 * UnitsTools.kg; - @mPERs2 int accel = 50 * UnitsTools.mPERs2; - @N int force = mass * accel; - - // mPERs2 * kg = N - @N int alsoforce = accel * mass; - - @t int massMetricTons = 50 * UnitsTools.t; - @kN int forceKiloNewtons = massMetricTons * accel; - forceKiloNewtons = accel * massMetricTons; - - // s * s * mPERs2 = m - // TODO: fix checker so it is insensitive to order of operations as long as final results' - // unit makes sense. - // Currently due to left associativity, and the lack of an s2 annotation, this tries to - // evaluate (s * s) * mPERs2 which causes the type assignment incompatible error. - // :: error: (assignment.type.incompatible) - @m int distance = s * s * accelm; - // if we bracket for order of operations, it works fine - distance = s * (s * accelm); - } + void m() { + // Prefix assignment tests + // kg + @kg int kg = 5 * UnitsTools.kg; + @g(Prefix.kilo) int alsokg = kg; + // :: error: (assignment.type.incompatible) + @g(Prefix.giga) int notkg = kg; + // :: error: (assignment.type.incompatible) + kg = notkg; + kg = alsokg; + + // g + @g int g = 5 * UnitsTools.g; + @g(Prefix.one) int alsog = g; + // :: error: (assignment.type.incompatible) + @g(Prefix.milli) int notg = g; + // :: error: (assignment.type.incompatible) + notg = g; + g = alsog; + + // m + @m int m = 5 * UnitsTools.m; + @m(Prefix.one) int alsom = m; + // :: error: (assignment.type.incompatible) + @m(Prefix.giga) int notm = m; + // :: error: (assignment.type.incompatible) + m = notm; + m = alsom; + + // km + @km int km = 5 * UnitsTools.km; + @m(Prefix.kilo) int alsokm = km; + // :: error: (assignment.type.incompatible) + @m(Prefix.giga) int notkm = km; + // :: error: (assignment.type.incompatible) + km = notkm; + km = alsokm; + + // mm + @mm int mm = 5 * UnitsTools.mm; + @m(Prefix.milli) int alsomm = mm; + // :: error: (assignment.type.incompatible) + @m(Prefix.giga) int notmm = mm; + // :: error: (assignment.type.incompatible) + mm = notmm; + mm = alsomm; + + // N + @N int N = 5 * UnitsTools.N; + @N(Prefix.one) int alsoN = N; + @N(Prefix.giga) + // :: error: (assignment.type.incompatible) + int notN = N; + // :: error: (assignment.type.incompatible) + N = notN; + N = alsoN; + + // kN + @kN int kN = 5 * UnitsTools.kN; + @N(Prefix.kilo) int alsokN = kN; + @N(Prefix.giga) + // :: error: (assignment.type.incompatible) + int notkN = kN; + // :: error: (assignment.type.incompatible) + kN = notkN; + kN = alsokN; + + // s + @s int s = 5 * UnitsTools.s; + + // h + @h int h = 5 * UnitsTools.h; + + // m * m = m2 + @m2 int area = m * m; + // :: error: (assignment.type.incompatible) + @km2 int areambad1 = m * m; + // :: error: (assignment.type.incompatible) + @mm2 int areambad2 = m * m; + + // km * km = km2 + @km2 int karea = km * km; + // :: error: (assignment.type.incompatible) + @m2 int areakmbad1 = km * km; + // :: error: (assignment.type.incompatible) + @mm2 int areakmbad2 = km * km; + + // mm * mm = mm2 + @mm2 int marea = mm * mm; + // :: error: (assignment.type.incompatible) + @m2 int areammbad1 = mm * mm; + // :: error: (assignment.type.incompatible) + @km2 int areammbad2 = mm * mm; + + // m * m2 = m3 + @m3 int volume = m * area; + // :: error: (assignment.type.incompatible) + @km3 int volumembad1 = m * area; + // :: error: (assignment.type.incompatible) + @mm3 int volumembad2 = m * area; + + // km * km2 = km3 + @km3 int kvolume = km * karea; + // :: error: (assignment.type.incompatible) + @m3 int volumekmbad1 = km * karea; + // :: error: (assignment.type.incompatible) + @mm3 int volumekmbad2 = km * karea; + + // mm * mm2 = mm3 + @mm3 int mvolume = mm * marea; + // :: error: (assignment.type.incompatible) + @m3 int volumemmbad1 = mm * marea; + // :: error: (assignment.type.incompatible) + @km3 int volumemmbad2 = mm * marea; + + // m2 * m = m3 + volume = area * m; + // :: error: (assignment.type.incompatible) + volumembad1 = area * m; + // :: error: (assignment.type.incompatible) + volumembad2 = area * m; + + // km2 * km = km3 + kvolume = karea * km; + // :: error: (assignment.type.incompatible) + volumekmbad1 = karea * km; + // :: error: (assignment.type.incompatible) + volumekmbad2 = karea * km; + + // mm2 * mm = mm3 + mvolume = marea * mm; + // :: error: (assignment.type.incompatible) + volumemmbad1 = marea * mm; + // :: error: (assignment.type.incompatible) + volumemmbad2 = marea * mm; + + // s * mPERs = m + @mPERs int speedm = 10 * UnitsTools.mPERs; + @m int lengthm = s * speedm; + lengthm = speedm * s; + // :: error: (assignment.type.incompatible) + @km int lengthmbad1 = s * speedm; + // :: error: (assignment.type.incompatible) + @mm int lengthmbad2 = s * speedm; + + // s * mPERs2 = mPERs + @mPERs2 int accelm = 20 * UnitsTools.mPERs2; + @mPERs int speedm2 = s * accelm; + speedm2 = accelm * s; + // :: error: (assignment.type.incompatible) + @kmPERh int speedm2bad1 = s * accelm; + + // h * kmPERh = km + @kmPERh int speedkm = 30 * UnitsTools.kmPERh; + @km int lengthkm = h * speedkm; + lengthkm = speedkm * h; + // :: error: (assignment.type.incompatible) + @m int lengthkmbad1 = h * speedkm; + // :: error: (assignment.type.incompatible) + @mm int lengthkmbad2 = h * speedkm; + + // kg * mPERs2 = N + @kg int mass = 40 * UnitsTools.kg; + @mPERs2 int accel = 50 * UnitsTools.mPERs2; + @N int force = mass * accel; + + // mPERs2 * kg = N + @N int alsoforce = accel * mass; + + @t int massMetricTons = 50 * UnitsTools.t; + @kN int forceKiloNewtons = massMetricTons * accel; + forceKiloNewtons = accel * massMetricTons; + + // s * s * mPERs2 = m + // TODO: fix checker so it is insensitive to order of operations as long as final results' + // unit makes sense. + // Currently due to left associativity, and the lack of an s2 annotation, this tries to + // evaluate (s * s) * mPERs2 which causes the type assignment incompatible error. + // :: error: (assignment.type.incompatible) + @m int distance = s * s * accelm; + // if we bracket for order of operations, it works fine + distance = s * (s * accelm); + } } diff --git a/checker/tests/units/PolyUnitTest.java b/checker/tests/units/PolyUnitTest.java index ec44c892189..df848df97ba 100644 --- a/checker/tests/units/PolyUnitTest.java +++ b/checker/tests/units/PolyUnitTest.java @@ -5,18 +5,18 @@ public class PolyUnitTest { - @PolyUnit int triplePolyUnit(@PolyUnit int amount) { - return 3 * amount; - } + @PolyUnit int triplePolyUnit(@PolyUnit int amount) { + return 3 * amount; + } - void testPolyUnit() { - @m int m1 = 7 * UnitsTools.m; - @m int m2 = triplePolyUnit(m1); + void testPolyUnit() { + @m int m1 = 7 * UnitsTools.m; + @m int m2 = triplePolyUnit(m1); - @s int sec1 = 7 * UnitsTools.s; - @s int sec2 = triplePolyUnit(sec1); + @s int sec1 = 7 * UnitsTools.s; + @s int sec2 = triplePolyUnit(sec1); - // :: error: (assignment.type.incompatible) - @s int sec3 = triplePolyUnit(m1); - } + // :: error: (assignment.type.incompatible) + @s int sec3 = triplePolyUnit(m1); + } } diff --git a/checker/tests/units/SubtractionUnits.java b/checker/tests/units/SubtractionUnits.java index 63f4217d28f..9201cd5b745 100644 --- a/checker/tests/units/SubtractionUnits.java +++ b/checker/tests/units/SubtractionUnits.java @@ -37,419 +37,419 @@ import org.checkerframework.checker.units.util.UnitsTools; public class SubtractionUnits { - // Subtraction is legal when the operands have the same units. - void good() { - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - @A int sAmpere = aAmpere - bAmpere; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - @cd int sCandela = aCandela - bCandela; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - @C int sCelsius = aCelsius - bCelsius; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - @g int sGram = aGram - bGram; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - @h int sHour = aHour - bHour; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - @K int sKelvin = aKelvin - bKelvin; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - @kN int sKiloewton = aKilonewton - bKilonewton; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - @kg int sKilogram = aKilogram - bKilogram; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - @km int sKilometer = aKilometer - bKilometer; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - @km2 int sSquareKilometer = aSquareKilometer - bSquareKilometer; - - // Cubic kilometer - @km3 int aCubicKilometer = 5 * UnitsTools.km3; - @km3 int bCubicKilometer = 5 * UnitsTools.km3; - @km3 int sCubicKilometer = aCubicKilometer - bCubicKilometer; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int sKilometerPerHour = aKilometerPerHour - bKilometerPerHour; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - @m int sMeter = aMeter - bMeter; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - @m2 int sSquareMeter = aSquareMeter - bSquareMeter; - - // Cubic meter - @m3 int aCubicMeter = 5 * UnitsTools.m3; - @m3 int bCubicMeter = 5 * UnitsTools.m3; - @m3 int sCubicMeter = aCubicMeter - bCubicMeter; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int sMeterPerSecond = aMeterPerSecond - bMeterPerSecond; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeterPerSecondSquare; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - @min int sMinute = aMinute - bMinute; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - @mm int sMillimeter = aMillimeter - bMillimeter; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int sSquareMillimeter = aSquareMillimeter - bSquareMillimeter; - - // Cubic millimeter - @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int sCubicMillimeter = aCubicMillimeter - bCubicMillimeter; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - @mol int sMole = aMole - bMole; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - @N int sNewton = aNewton - bNewton; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - @s int sSecond = aSecond - bSecond; - } - - // Subtraction is illegal when the operands have different units or one is unqualified. In - // these tests, we cycle between the result and the first or second operand having an incorrect - // type. - void bad() { - // Dimensions - // Acceleration - @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; - @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; - - // Area - @Area int aArea = 5 * UnitsTools.km2; - @Area int bArea = 5 * UnitsTools.mm2; - - // Current - @Current int aCurrent = 5 * UnitsTools.A; - @Current int bCurrent = 5 * UnitsTools.A; - - // Force - @Force int aForce = 5 * UnitsTools.N; - @Force int bForce = 5 * UnitsTools.N; - - // Length - @Length int aLength = 5 * UnitsTools.m; - @Length int bLength = 5 * UnitsTools.mm; - - // Luminance - @Luminance int aLuminance = 5 * UnitsTools.cd; - @Luminance int bLuminance = 5 * UnitsTools.cd; - - // Mass - @Mass int aMass = 5 * UnitsTools.kg; - @Mass int bMass = 5 * UnitsTools.g; - - // Substance - @Substance int aSubstance = 5 * UnitsTools.mol; - @Substance int bSubstance = 5 * UnitsTools.mol; - - // Temperature - @Temperature int aTemperature = 5 * UnitsTools.K; - @Temperature int bTemperature = 5 * UnitsTools.K; - - // Time - @Time int aTime = 5 * UnitsTools.min; - @Time int bTime = 5 * UnitsTools.h; - - // Volume - @Volume int aVolume = 5 * UnitsTools.m3; - @Volume int bVolume = 5 * UnitsTools.km3; - - // Dimensions - // :: error: (assignment.type.incompatible) - @Acceleration int sAcceleration = aAcceleration - bMass; - - // Area - // :: error: (assignment.type.incompatible) - @Luminance int sLuminance = aArea - bArea; - - // Current - // :: error: (assignment.type.incompatible) - @Current int sCurrent = aMass - bCurrent; - - // Length - // :: error: (assignment.type.incompatible) - @Length int sLength = aLength - bSubstance; - - // Luminance - // :: error: (assignment.type.incompatible) - @Temperature int sTemperature = aLuminance - bLuminance; - - // Mass - // :: error: (assignment.type.incompatible) - @Mass int sMass = aTemperature - bMass; - - // Substance - // :: error: (assignment.type.incompatible) - @Substance int sSubstance = aSubstance - bCurrent; - - // Temperature - // :: error: (assignment.type.incompatible) - @Area int sArea = aTemperature - bTemperature; - - // Time - // :: error: (assignment.type.incompatible) - @Time int sTime = aArea - bTime; - - // Volume - // :: error: (assignment.type.incompatible) - @Volume int sVolume = aVolume - bArea; - - // Force - // :: error: (assignment.type.incompatible) - sMass = aForce - bForce; - - // Units - // Amperes - @A int aAmpere = 5 * UnitsTools.A; - @A int bAmpere = 5 * UnitsTools.A; - - // Candela - @cd int aCandela = 5 * UnitsTools.cd; - @cd int bCandela = 5 * UnitsTools.cd; - - // Celsius - @C int aCelsius = 5 * UnitsTools.C; - @C int bCelsius = 5 * UnitsTools.C; - - // Gram - @g int aGram = 5 * UnitsTools.g; - @g int bGram = 5 * UnitsTools.g; - - // Hour - @h int aHour = 5 * UnitsTools.h; - @h int bHour = 5 * UnitsTools.h; - - // Kelvin - @K int aKelvin = 5 * UnitsTools.K; - @K int bKelvin = 5 * UnitsTools.K; - - // Kilonewton - @kN int aKilonewton = 5 * UnitsTools.kN; - @kN int bKilonewton = 5 * UnitsTools.kN; - - // Kilogram - @kg int aKilogram = 5 * UnitsTools.kg; - @kg int bKilogram = 5 * UnitsTools.kg; - - // Kilometer - @km int aKilometer = 5 * UnitsTools.km; - @km int bKilometer = 5 * UnitsTools.km; - - // Square kilometer - @km2 int aSquareKilometer = 5 * UnitsTools.km2; - @km2 int bSquareKilometer = 5 * UnitsTools.km2; - - // Cubic kilometer - @km3 int aCubicKilometer = 5 * UnitsTools.km3; - @km3 int bCubicKilometer = 5 * UnitsTools.km3; - - // Kilometer per hour - @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; - @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; - - // Meter - @m int aMeter = 5 * UnitsTools.m; - @m int bMeter = 5 * UnitsTools.m; - - // Square meter - @m2 int aSquareMeter = 5 * UnitsTools.m2; - @m2 int bSquareMeter = 5 * UnitsTools.m2; - - // Cubic meter - @m3 int aCubicMeter = 5 * UnitsTools.m3; - @m3 int bCubicMeter = 5 * UnitsTools.m3; - - // Meter per second - @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; - @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; - - // Meter per second square - @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; - - // Minute - @min int aMinute = 5 * UnitsTools.min; - @min int bMinute = 5 * UnitsTools.min; - - // Millimeter - @mm int aMillimeter = 5 * UnitsTools.mm; - @mm int bMillimeter = 5 * UnitsTools.mm; - - // Square millimeter - @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; - @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; - - // Cubic millimeter - @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; - @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; - - // Mole - @mol int aMole = 5 * UnitsTools.mol; - @mol int bMole = 5 * UnitsTools.mol; - - // Second - @s int aSecond = 5 * UnitsTools.s; - @s int bSecond = 5 * UnitsTools.s; - - // Metric Tons - @t int aMetricTon = 5 * UnitsTools.t; - @t int bMetricTon = 5 * UnitsTools.t; - - // Newton - @N int aNewton = 5 * UnitsTools.N; - @N int bNewton = 5 * UnitsTools.N; - - // Units - // Amperes - // :: error: (assignment.type.incompatible) - @g int sGram = aAmpere - bAmpere; - - // Candela - // :: error: (assignment.type.incompatible) - @cd int sCandela = aTemperature - bCandela; - - // Celsius - // :: error: (assignment.type.incompatible) - @C int sCelsius = aCelsius - bMillimeter; - - // Gram - // :: error: (assignment.type.incompatible) - @kg int sKilogram = aGram - bGram; - - // Hour - // :: error: (assignment.type.incompatible) - @h int sHour = aSquareMeter - bHour; - - // Kelvin - // :: error: (assignment.type.incompatible) - @K int sKelvin = aKelvin - bSecond; - - // Kilogram - // :: error: (assignment.type.incompatible) - @kmPERh int sKilometerPerHour = aKilogram - bKilogram; - - // Kilometer - // :: error: (assignment.type.incompatible) - @km int sKilometer = aCandela - bKilometer; - - // Square kilometer - // :: error: (assignment.type.incompatible) - @km2 int sSquareKilometer = aSquareKilometer - bAmpere; - - // Cubic kilometer - // :: error: (assignment.type.incompatible) - @km3 int sCubicKilometer = aCubicKilometer - bAmpere; - - // Kilometer per hour - // :: error: (assignment.type.incompatible) - @mPERs int sMeterPerSecond = aKilometerPerHour - bKilometerPerHour; - - // Meter - // :: error: (assignment.type.incompatible) - @m int sMeter = aHour - bMeter; - - // Square meter - // :: error: (assignment.type.incompatible) - @m2 int sSquareMeter = aSquareMeter - bGram; - - // Cubic meter - // :: error: (assignment.type.incompatible) - @m3 int sCubicMeter = aCubicMeter - bGram; - - // Meter per second - // :: error: (assignment.type.incompatible) - @mm2 int sSquareMillimeter = aMeterPerSecond - bMeterPerSecond; - - // Meter per second square - // :: error: (assignment.type.incompatible) - @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeter; - - // Minute - // :: error: (assignment.type.incompatible) - @min int sMinute = aMole - bMinute; - - // Millimeter - // :: error: (assignment.type.incompatible) - @mm int sMillimeter = aMillimeter - bHour; - - // Square millimeter - // :: error: (assignment.type.incompatible) - @A int sAmpere = aSquareMillimeter - bSquareMillimeter; - - // Mole - // :: error: (assignment.type.incompatible) - @mol int sMole = aCandela - bMole; - - // Second - // :: error: (assignment.type.incompatible) - @s int sSecond = aSecond - bSquareKilometer; - - // Newton - // :: error: (assignment.type.incompatible) - sKilogram = aNewton - bNewton; - - // Kilonewton - // :: error: (assignment.type.incompatible) - @kN int sKilonewton = aKilonewton - bNewton; - - // Metric Ton - // :: error: (assignment.type.incompatible) - @N int sNewton = aNewton - bMetricTon; - } + // Subtraction is legal when the operands have the same units. + void good() { + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + @A int sAmpere = aAmpere - bAmpere; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + @cd int sCandela = aCandela - bCandela; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + @C int sCelsius = aCelsius - bCelsius; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + @g int sGram = aGram - bGram; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + @h int sHour = aHour - bHour; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + @K int sKelvin = aKelvin - bKelvin; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + @kN int sKiloewton = aKilonewton - bKilonewton; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + @kg int sKilogram = aKilogram - bKilogram; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + @km int sKilometer = aKilometer - bKilometer; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + @km2 int sSquareKilometer = aSquareKilometer - bSquareKilometer; + + // Cubic kilometer + @km3 int aCubicKilometer = 5 * UnitsTools.km3; + @km3 int bCubicKilometer = 5 * UnitsTools.km3; + @km3 int sCubicKilometer = aCubicKilometer - bCubicKilometer; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int sKilometerPerHour = aKilometerPerHour - bKilometerPerHour; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + @m int sMeter = aMeter - bMeter; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + @m2 int sSquareMeter = aSquareMeter - bSquareMeter; + + // Cubic meter + @m3 int aCubicMeter = 5 * UnitsTools.m3; + @m3 int bCubicMeter = 5 * UnitsTools.m3; + @m3 int sCubicMeter = aCubicMeter - bCubicMeter; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int sMeterPerSecond = aMeterPerSecond - bMeterPerSecond; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeterPerSecondSquare; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + @min int sMinute = aMinute - bMinute; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + @mm int sMillimeter = aMillimeter - bMillimeter; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int sSquareMillimeter = aSquareMillimeter - bSquareMillimeter; + + // Cubic millimeter + @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int sCubicMillimeter = aCubicMillimeter - bCubicMillimeter; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + @mol int sMole = aMole - bMole; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + @N int sNewton = aNewton - bNewton; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + @s int sSecond = aSecond - bSecond; + } + + // Subtraction is illegal when the operands have different units or one is unqualified. In + // these tests, we cycle between the result and the first or second operand having an incorrect + // type. + void bad() { + // Dimensions + // Acceleration + @Acceleration int aAcceleration = 5 * UnitsTools.mPERs2; + @Acceleration int bAcceleration = 5 * UnitsTools.mPERs2; + + // Area + @Area int aArea = 5 * UnitsTools.km2; + @Area int bArea = 5 * UnitsTools.mm2; + + // Current + @Current int aCurrent = 5 * UnitsTools.A; + @Current int bCurrent = 5 * UnitsTools.A; + + // Force + @Force int aForce = 5 * UnitsTools.N; + @Force int bForce = 5 * UnitsTools.N; + + // Length + @Length int aLength = 5 * UnitsTools.m; + @Length int bLength = 5 * UnitsTools.mm; + + // Luminance + @Luminance int aLuminance = 5 * UnitsTools.cd; + @Luminance int bLuminance = 5 * UnitsTools.cd; + + // Mass + @Mass int aMass = 5 * UnitsTools.kg; + @Mass int bMass = 5 * UnitsTools.g; + + // Substance + @Substance int aSubstance = 5 * UnitsTools.mol; + @Substance int bSubstance = 5 * UnitsTools.mol; + + // Temperature + @Temperature int aTemperature = 5 * UnitsTools.K; + @Temperature int bTemperature = 5 * UnitsTools.K; + + // Time + @Time int aTime = 5 * UnitsTools.min; + @Time int bTime = 5 * UnitsTools.h; + + // Volume + @Volume int aVolume = 5 * UnitsTools.m3; + @Volume int bVolume = 5 * UnitsTools.km3; + + // Dimensions + // :: error: (assignment.type.incompatible) + @Acceleration int sAcceleration = aAcceleration - bMass; + + // Area + // :: error: (assignment.type.incompatible) + @Luminance int sLuminance = aArea - bArea; + + // Current + // :: error: (assignment.type.incompatible) + @Current int sCurrent = aMass - bCurrent; + + // Length + // :: error: (assignment.type.incompatible) + @Length int sLength = aLength - bSubstance; + + // Luminance + // :: error: (assignment.type.incompatible) + @Temperature int sTemperature = aLuminance - bLuminance; + + // Mass + // :: error: (assignment.type.incompatible) + @Mass int sMass = aTemperature - bMass; + + // Substance + // :: error: (assignment.type.incompatible) + @Substance int sSubstance = aSubstance - bCurrent; + + // Temperature + // :: error: (assignment.type.incompatible) + @Area int sArea = aTemperature - bTemperature; + + // Time + // :: error: (assignment.type.incompatible) + @Time int sTime = aArea - bTime; + + // Volume + // :: error: (assignment.type.incompatible) + @Volume int sVolume = aVolume - bArea; + + // Force + // :: error: (assignment.type.incompatible) + sMass = aForce - bForce; + + // Units + // Amperes + @A int aAmpere = 5 * UnitsTools.A; + @A int bAmpere = 5 * UnitsTools.A; + + // Candela + @cd int aCandela = 5 * UnitsTools.cd; + @cd int bCandela = 5 * UnitsTools.cd; + + // Celsius + @C int aCelsius = 5 * UnitsTools.C; + @C int bCelsius = 5 * UnitsTools.C; + + // Gram + @g int aGram = 5 * UnitsTools.g; + @g int bGram = 5 * UnitsTools.g; + + // Hour + @h int aHour = 5 * UnitsTools.h; + @h int bHour = 5 * UnitsTools.h; + + // Kelvin + @K int aKelvin = 5 * UnitsTools.K; + @K int bKelvin = 5 * UnitsTools.K; + + // Kilonewton + @kN int aKilonewton = 5 * UnitsTools.kN; + @kN int bKilonewton = 5 * UnitsTools.kN; + + // Kilogram + @kg int aKilogram = 5 * UnitsTools.kg; + @kg int bKilogram = 5 * UnitsTools.kg; + + // Kilometer + @km int aKilometer = 5 * UnitsTools.km; + @km int bKilometer = 5 * UnitsTools.km; + + // Square kilometer + @km2 int aSquareKilometer = 5 * UnitsTools.km2; + @km2 int bSquareKilometer = 5 * UnitsTools.km2; + + // Cubic kilometer + @km3 int aCubicKilometer = 5 * UnitsTools.km3; + @km3 int bCubicKilometer = 5 * UnitsTools.km3; + + // Kilometer per hour + @kmPERh int aKilometerPerHour = 5 * UnitsTools.kmPERh; + @kmPERh int bKilometerPerHour = 5 * UnitsTools.kmPERh; + + // Meter + @m int aMeter = 5 * UnitsTools.m; + @m int bMeter = 5 * UnitsTools.m; + + // Square meter + @m2 int aSquareMeter = 5 * UnitsTools.m2; + @m2 int bSquareMeter = 5 * UnitsTools.m2; + + // Cubic meter + @m3 int aCubicMeter = 5 * UnitsTools.m3; + @m3 int bCubicMeter = 5 * UnitsTools.m3; + + // Meter per second + @mPERs int aMeterPerSecond = 5 * UnitsTools.mPERs; + @mPERs int bMeterPerSecond = 5 * UnitsTools.mPERs; + + // Meter per second square + @mPERs2 int aMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + @mPERs2 int bMeterPerSecondSquare = 5 * UnitsTools.mPERs2; + + // Minute + @min int aMinute = 5 * UnitsTools.min; + @min int bMinute = 5 * UnitsTools.min; + + // Millimeter + @mm int aMillimeter = 5 * UnitsTools.mm; + @mm int bMillimeter = 5 * UnitsTools.mm; + + // Square millimeter + @mm2 int aSquareMillimeter = 5 * UnitsTools.mm2; + @mm2 int bSquareMillimeter = 5 * UnitsTools.mm2; + + // Cubic millimeter + @mm3 int aCubicMillimeter = 5 * UnitsTools.mm3; + @mm3 int bCubicMillimeter = 5 * UnitsTools.mm3; + + // Mole + @mol int aMole = 5 * UnitsTools.mol; + @mol int bMole = 5 * UnitsTools.mol; + + // Second + @s int aSecond = 5 * UnitsTools.s; + @s int bSecond = 5 * UnitsTools.s; + + // Metric Tons + @t int aMetricTon = 5 * UnitsTools.t; + @t int bMetricTon = 5 * UnitsTools.t; + + // Newton + @N int aNewton = 5 * UnitsTools.N; + @N int bNewton = 5 * UnitsTools.N; + + // Units + // Amperes + // :: error: (assignment.type.incompatible) + @g int sGram = aAmpere - bAmpere; + + // Candela + // :: error: (assignment.type.incompatible) + @cd int sCandela = aTemperature - bCandela; + + // Celsius + // :: error: (assignment.type.incompatible) + @C int sCelsius = aCelsius - bMillimeter; + + // Gram + // :: error: (assignment.type.incompatible) + @kg int sKilogram = aGram - bGram; + + // Hour + // :: error: (assignment.type.incompatible) + @h int sHour = aSquareMeter - bHour; + + // Kelvin + // :: error: (assignment.type.incompatible) + @K int sKelvin = aKelvin - bSecond; + + // Kilogram + // :: error: (assignment.type.incompatible) + @kmPERh int sKilometerPerHour = aKilogram - bKilogram; + + // Kilometer + // :: error: (assignment.type.incompatible) + @km int sKilometer = aCandela - bKilometer; + + // Square kilometer + // :: error: (assignment.type.incompatible) + @km2 int sSquareKilometer = aSquareKilometer - bAmpere; + + // Cubic kilometer + // :: error: (assignment.type.incompatible) + @km3 int sCubicKilometer = aCubicKilometer - bAmpere; + + // Kilometer per hour + // :: error: (assignment.type.incompatible) + @mPERs int sMeterPerSecond = aKilometerPerHour - bKilometerPerHour; + + // Meter + // :: error: (assignment.type.incompatible) + @m int sMeter = aHour - bMeter; + + // Square meter + // :: error: (assignment.type.incompatible) + @m2 int sSquareMeter = aSquareMeter - bGram; + + // Cubic meter + // :: error: (assignment.type.incompatible) + @m3 int sCubicMeter = aCubicMeter - bGram; + + // Meter per second + // :: error: (assignment.type.incompatible) + @mm2 int sSquareMillimeter = aMeterPerSecond - bMeterPerSecond; + + // Meter per second square + // :: error: (assignment.type.incompatible) + @mPERs2 int sMeterPerSecondSquare = aMeterPerSecondSquare - bMeter; + + // Minute + // :: error: (assignment.type.incompatible) + @min int sMinute = aMole - bMinute; + + // Millimeter + // :: error: (assignment.type.incompatible) + @mm int sMillimeter = aMillimeter - bHour; + + // Square millimeter + // :: error: (assignment.type.incompatible) + @A int sAmpere = aSquareMillimeter - bSquareMillimeter; + + // Mole + // :: error: (assignment.type.incompatible) + @mol int sMole = aCandela - bMole; + + // Second + // :: error: (assignment.type.incompatible) + @s int sSecond = aSecond - bSquareKilometer; + + // Newton + // :: error: (assignment.type.incompatible) + sKilogram = aNewton - bNewton; + + // Kilonewton + // :: error: (assignment.type.incompatible) + @kN int sKilonewton = aKilonewton - bNewton; + + // Metric Ton + // :: error: (assignment.type.incompatible) + @N int sNewton = aNewton - bMetricTon; + } } diff --git a/checker/tests/units/TypeVarsArrays.java b/checker/tests/units/TypeVarsArrays.java index 629d2eb50c6..b12054f2803 100644 --- a/checker/tests/units/TypeVarsArrays.java +++ b/checker/tests/units/TypeVarsArrays.java @@ -1,8 +1,8 @@ public class TypeVarsArrays { - private T[] array; + private T[] array; - public void triggerBug(int index, T val) { - array[index] = val; - array[index] = null; - } + public void triggerBug(int index, T val) { + array[index] = val; + array[index] = null; + } } diff --git a/checker/tests/units/Units.java b/checker/tests/units/Units.java index 712c3159188..3244e266138 100644 --- a/checker/tests/units/Units.java +++ b/checker/tests/units/Units.java @@ -5,12 +5,12 @@ import org.checkerframework.checker.units.util.UnitsTools; public class Units { - @m int m1 = 5 * UnitsTools.m; + @m int m1 = 5 * UnitsTools.m; - // The advantage of using the multiplication with a unit is that also double, float, etc. are - // easily handled and we don't need to end a huge number of methods to UnitsTools. - @m double dm = 9.34d * UnitsTools.m; + // The advantage of using the multiplication with a unit is that also double, float, etc. are + // easily handled and we don't need to end a huge number of methods to UnitsTools. + @m double dm = 9.34d * UnitsTools.m; - // With a static import: - @s float time = 5.32f * s; + // With a static import: + @s float time = 5.32f * s; } diff --git a/checker/tests/units/UnqualTest.java b/checker/tests/units/UnqualTest.java index c300a014ef4..3e7b0704ecd 100644 --- a/checker/tests/units/UnqualTest.java +++ b/checker/tests/units/UnqualTest.java @@ -1,9 +1,9 @@ import org.checkerframework.checker.units.qual.kg; public class UnqualTest { - // :: error: (assignment.type.incompatible) - @kg int kg = 5; - int nonkg = kg; - // :: error: (assignment.type.incompatible) - @kg int alsokg = nonkg; + // :: error: (assignment.type.incompatible) + @kg int kg = 5; + int nonkg = kg; + // :: error: (assignment.type.incompatible) + @kg int alsokg = nonkg; } diff --git a/checker/tests/value-index-interaction/MethodOverrides.java b/checker/tests/value-index-interaction/MethodOverrides.java index 80f834592ae..c3419416921 100644 --- a/checker/tests/value-index-interaction/MethodOverrides.java +++ b/checker/tests/value-index-interaction/MethodOverrides.java @@ -7,13 +7,13 @@ import org.checkerframework.checker.index.qual.GTENegativeOne; public class MethodOverrides { - @GTENegativeOne int read() { - return -1; - } + @GTENegativeOne int read() { + return -1; + } } class MethodOverrides2 extends MethodOverrides { - int read() { - return -1; - } + int read() { + return -1; + } } diff --git a/checker/tests/value-index-interaction/MethodOverrides3.java b/checker/tests/value-index-interaction/MethodOverrides3.java index 5cc8792f192..f0a8ecfd5bb 100644 --- a/checker/tests/value-index-interaction/MethodOverrides3.java +++ b/checker/tests/value-index-interaction/MethodOverrides3.java @@ -1,17 +1,18 @@ // This class should not issues any errors, since these annotations are identical to the ones // on java.io.PrintWriter in the Index JDK. +import org.checkerframework.checker.index.qual.IndexFor; +import org.checkerframework.checker.index.qual.IndexOrHigh; + import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; -import org.checkerframework.checker.index.qual.IndexFor; -import org.checkerframework.checker.index.qual.IndexOrHigh; public class MethodOverrides3 extends PrintWriter { - public MethodOverrides3(File file) throws FileNotFoundException { - super(file); - } + public MethodOverrides3(File file) throws FileNotFoundException { + super(file); + } - @Override - public void write(char[] buf, @IndexFor("#1") int off, @IndexOrHigh("#1") int len) {} + @Override + public void write(char[] buf, @IndexFor("#1") int off, @IndexOrHigh("#1") int len) {} } diff --git a/checker/tests/value-index-interaction/MinLenFromPositive.java b/checker/tests/value-index-interaction/MinLenFromPositive.java index 6eb4beecb11..f14a9d556eb 100644 --- a/checker/tests/value-index-interaction/MinLenFromPositive.java +++ b/checker/tests/value-index-interaction/MinLenFromPositive.java @@ -5,52 +5,52 @@ public class MinLenFromPositive { - public @Positive int x = 0; - - void testField() { - this.x = -1; - @IntRange(from = 1) int f = this.x; - int @MinLen(1) [] y = new int[x]; - } - - void testArray(@Positive int @ArrayLen(1) [] x) { - int @MinLen(1) [] array = new int[x[0]]; - } - - void useTestArray(int @ArrayLen(1) [] x, int[] y) { - testArray(x); - // :: error: (argument.type.incompatible) - testArray(y); - } - - void test(@Positive int x) { - @IntRange(from = 1) int z = x; - @Positive int q = x; - @Positive int a = -1; - int @MinLen(1) [] array = new int[a]; - } - - // Ensure that just running the value checker doesn't result in an LHS warning. - void foo2(int x) { - test(x); - } - - @Positive int id(@Positive int x) { - return -1; - } - - @Positive int plus(@Positive int x, @Positive int y) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int z = x + y; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int q = x + y; - - return x + y; - } - - // Ensure that LHS warnings aren't issued even for arrays of Positives - @Positive int[] array_test() { - int[] a = {-1, 2, 3}; - return a; - } + public @Positive int x = 0; + + void testField() { + this.x = -1; + @IntRange(from = 1) int f = this.x; + int @MinLen(1) [] y = new int[x]; + } + + void testArray(@Positive int @ArrayLen(1) [] x) { + int @MinLen(1) [] array = new int[x[0]]; + } + + void useTestArray(int @ArrayLen(1) [] x, int[] y) { + testArray(x); + // :: error: (argument.type.incompatible) + testArray(y); + } + + void test(@Positive int x) { + @IntRange(from = 1) int z = x; + @Positive int q = x; + @Positive int a = -1; + int @MinLen(1) [] array = new int[a]; + } + + // Ensure that just running the value checker doesn't result in an LHS warning. + void foo2(int x) { + test(x); + } + + @Positive int id(@Positive int x) { + return -1; + } + + @Positive int plus(@Positive int x, @Positive int y) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int z = x + y; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int q = x + y; + + return x + y; + } + + // Ensure that LHS warnings aren't issued even for arrays of Positives + @Positive int[] array_test() { + int[] a = {-1, 2, 3}; + return a; + } } diff --git a/checker/tests/value-index-interaction/OverrideIntVal.java b/checker/tests/value-index-interaction/OverrideIntVal.java index 8e3e37ac9ad..8baffc1b5d2 100644 --- a/checker/tests/value-index-interaction/OverrideIntVal.java +++ b/checker/tests/value-index-interaction/OverrideIntVal.java @@ -4,61 +4,61 @@ public class OverrideIntVal { - @NonNegative int foo(@IntVal(0) int zero) { - return zero; - } + @NonNegative int foo(@IntVal(0) int zero) { + return zero; + } - @NonNegative int bar(@BottomVal int bottom) { - return bottom; - } + @NonNegative int bar(@BottomVal int bottom) { + return bottom; + } - @NonNegative int m() { - return 0; - } + @NonNegative int m() { + return 0; + } - @IntVal({0, 1, 2, 3}) int m2() { - return 0; - } + @IntVal({0, 1, 2, 3}) int m2() { + return 0; + } - @GTENegativeOne int n() { - return -1; - } + @GTENegativeOne int n() { + return -1; + } - @Positive int p() { - return 1; - } + @Positive int p() { + return 1; + } } class OverrideIntValSub extends OverrideIntVal { - @Override - @IntVal(0) int m() { - return 0; - } + @Override + @IntVal(0) int m() { + return 0; + } - @Override - @IntVal(0) int m2() { - return 0; - } + @Override + @IntVal(0) int m2() { + return 0; + } - @Override - @IntVal(0) int n() { - return 0; - } + @Override + @IntVal(0) int n() { + return 0; + } - @Override - @IntVal(2) int p() { - return 2; - } + @Override + @IntVal(2) int p() { + return 2; + } } class OverrideIntValBottom extends OverrideIntVal { - @Override - @BottomVal int m() { - throw new Error("never returns normally"); - } + @Override + @BottomVal int m() { + throw new Error("never returns normally"); + } - @Override - @BottomVal int m2() { - throw new Error("never returns normally"); - } + @Override + @BottomVal int m2() { + throw new Error("never returns normally"); + } } diff --git a/dataflow/build.gradle b/dataflow/build.gradle index 9b3d3350927..9d69b832316 100644 --- a/dataflow/build.gradle +++ b/dataflow/build.gradle @@ -1,30 +1,30 @@ plugins { - id 'java-library' - id 'base' + id 'java-library' + id 'base' } dependencies { - api project(':javacutil') - api project(':checker-qual') - - // Node implements org.plumelib.util.UniqueId, so this dependency must be "api". - api "org.plumelib:plume-util:${versions.plumeUtil}" - // plume-util has an `implementation` dependency on hashmap-util. - // That follows Gradle's rules, but Gradle's rules are not entirely correct: - // https://github.com/gradle/gradle/issues/30054 - // To build with JDK 22+, we need to redeclare that dependency here. - implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" - - // External dependencies: - // If you add an external dependency, you must shadow its packages both in the dataflow-shaded - // artifact (see shadowJar block below) and also in checker.jar (see the comment in - // ../build.gradle in the createDataflowShaded task). + api project(':javacutil') + api project(':checker-qual') + + // Node implements org.plumelib.util.UniqueId, so this dependency must be "api". + api "org.plumelib:plume-util:${versions.plumeUtil}" + // plume-util has an `implementation` dependency on hashmap-util. + // That follows Gradle's rules, but Gradle's rules are not entirely correct: + // https://github.com/gradle/gradle/issues/30054 + // To build with JDK 22+, we need to redeclare that dependency here. + implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" + + // External dependencies: + // If you add an external dependency, you must shadow its packages both in the dataflow-shaded + // artifact (see shadowJar block below) and also in checker.jar (see the comment in + // ../build.gradle in the createDataflowShaded task). } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } // Shadowing Test Sources and Dependencies @@ -37,30 +37,30 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar * @return */ def createDataflowShaded(shadedPkgName) { - tasks.create(name: "dataflow${shadedPkgName}Jar", type: ShadowJar, dependsOn: compileJava, group: 'Build') { - description = "Builds dataflow-${shadedPkgName}.jar." - includeEmptyDirs = false - base { - archivesName = "dataflow-${shadedPkgName}" - } - // Without this line, the Maven artifact will have the classifier "all". - archiveClassifier.set('') + tasks.create(name: "dataflow${shadedPkgName}Jar", type: ShadowJar, dependsOn: compileJava, group: 'Build') { + description = "Builds dataflow-${shadedPkgName}.jar." + includeEmptyDirs = false + base { + archivesName = "dataflow-${shadedPkgName}" + } + // Without this line, the Maven artifact will have the classifier "all". + archiveClassifier.set('') - from shadowJar.source - configurations = shadowJar.configurations + from shadowJar.source + configurations = shadowJar.configurations - destinationDirectory = file("${buildDir}/shadow/dataflow${shadedPkgName}") + destinationDirectory = file("${buildDir}/shadow/dataflow${shadedPkgName}") - relocate('org.checkerframework', "org.checkerframework.${shadedPkgName}") { - // Shade all Checker Framework packages, except for the dataflow qualifiers. - exclude 'org.checkerframework.dataflow.qual.*' - } + relocate('org.checkerframework', "org.checkerframework.${shadedPkgName}") { + // Shade all Checker Framework packages, except for the dataflow qualifiers. + exclude 'org.checkerframework.dataflow.qual.*' + } - // Relocate external dependencies - relocate 'org.plumelib', "org.checkerframework.${shadedPkgName}.org.plumelib" - relocate 'com.google', "org.checkerframework.${shadedPkgName}.com.google" - } + // Relocate external dependencies + relocate 'org.plumelib', "org.checkerframework.${shadedPkgName}.org.plumelib" + relocate 'com.google', "org.checkerframework.${shadedPkgName}.com.google" + } } // Creates a new shaded dataflow artifact. To add a new one, add a new method call below, and add @@ -70,28 +70,28 @@ createDataflowShaded('nullaway') createDataflowShaded('errorprone') task manual(group: 'Documentation') { - description = 'Build the manual' + description = 'Build the manual' - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - doLast { - injected.execOps.exec { - workingDir = file('manual') - commandLine 'make' + doLast { + injected.execOps.exec { + workingDir = file('manual') + commandLine 'make' + } } - } } tasks.withType(Test) { - // Disable the gradle generated test task as the dataflow framework does not use JUnit for testing. If it were kept enabled (which is the default), gradlew would produce a deprecation warning. - // The `allDataflowTests` task dependency is still enabled. - enabled = false - dependsOn('allDataflowTests') + // Disable the gradle generated test task as the dataflow framework does not use JUnit for testing. If it were kept enabled (which is the default), gradlew would produce a deprecation warning. + // The `allDataflowTests` task dependency is still enabled. + enabled = false + dependsOn('allDataflowTests') } tasks.create(name: 'allDataflowTests', group: 'Verification') { - description = 'Run all dataflow analysis tests' - // 'allDataflowTests' is automatically populated by testDataflowAnalysis(). + description = 'Run all dataflow analysis tests' + // 'allDataflowTests' is automatically populated by testDataflowAnalysis(). } /** @@ -109,65 +109,65 @@ tasks.create(name: 'allDataflowTests', group: 'Verification') { * @return */ def testDataflowAnalysis(taskName, dirName, className, diff) { - tasks.create(name: taskName, dependsOn: [assemble, compileTestJava], group: 'Verification') { - description = "Run the ${dirName} dataflow framework test." - - def injected = project.objects.newInstance(InjectedExecOps) - - if (diff) { - inputs.file("tests/${dirName}/Expected.txt") - } - inputs.file("tests/${dirName}/Test.java") + tasks.create(name: taskName, dependsOn: [assemble, compileTestJava], group: 'Verification') { + description = "Run the ${dirName} dataflow framework test." - outputs.file("tests/${dirName}/Out.txt") - outputs.file("tests/${dirName}/Test.class") + def injected = project.objects.newInstance(InjectedExecOps) - delete("tests/${dirName}/Out.txt") - delete("tests/${dirName}/Test.class") - doLast { - injected.execOps.javaexec { - workingDir = file("tests/${dirName}") - if (!JavaVersion.current().java9Compatible) { - jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() - } - else if (JavaVersion.current() > JavaVersion.VERSION_11) { - jvmArgs += [ - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-opens', - 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', - ] + if (diff) { + inputs.file("tests/${dirName}/Expected.txt") } - - classpath = sourceSets.test.runtimeClasspath - classpath += sourceSets.test.output - mainClass = "${className}" - } - if (diff) { - injected.execOps.exec { - workingDir = file("tests/${dirName}") - executable 'diff' - args = [ - '-u', - 'Expected.txt', - 'Out.txt' - ] + inputs.file("tests/${dirName}/Test.java") + + outputs.file("tests/${dirName}/Out.txt") + outputs.file("tests/${dirName}/Test.class") + + delete("tests/${dirName}/Out.txt") + delete("tests/${dirName}/Test.class") + doLast { + injected.execOps.javaexec { + workingDir = file("tests/${dirName}") + if (!JavaVersion.current().java9Compatible) { + jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}".toString() + } + else if (JavaVersion.current() > JavaVersion.VERSION_11) { + jvmArgs += [ + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-opens', + 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + ] + } + + classpath = sourceSets.test.runtimeClasspath + classpath += sourceSets.test.output + mainClass = "${className}" + } + if (diff) { + injected.execOps.exec { + workingDir = file("tests/${dirName}") + executable 'diff' + args = [ + '-u', + 'Expected.txt', + 'Out.txt' + ] + } + } } - } } - } - allDataflowTests.dependsOn << taskName + allDataflowTests.dependsOn << taskName } // When adding a new test case, don't forget to add the temporary files to `../.gitignore`. @@ -182,20 +182,20 @@ apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading the dataflow artifacts to Maven repositories. */ final dataflowPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - // Information that is in all pom files is configured in checker-framework/gradle-mvn-push.gradle. - publication.pom { - name = 'Dataflow' - description = 'Dataflow is a dataflow framework based on the javac compiler.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + publication.from components.java + // Information that is in all pom files is configured in checker-framework/gradle-mvn-push.gradle. + publication.pom { + name = 'Dataflow' + description = 'Dataflow is a dataflow framework based on the javac compiler.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } + } } - } } /** @@ -203,63 +203,63 @@ final dataflowPom(publication) { * @param shadedPkgName the name of the shaded package to use; also used as part of the artifact name: "dataflow-${shadePkgName}" */ final dataflowShadedPom(MavenPublication publication, String shadedPkgName) { - sharedPublicationConfiguration(publication) - - publication.artifactId = "dataflow-${shadedPkgName}" - publication.pom { - name = "Dataflow (${shadedPkgName})" - description = "dataflow-${shadedPkgName} is a dataflow framework based on the javac compiler.\n" + - '\n' + - 'It differs from the org.checkerframework:dataflow artifact in two ways.\n' + - "First, the packages in this artifact have been renamed to org.checkerframework.${shadedPkgName}.*.\n" + - 'Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + + publication.artifactId = "dataflow-${shadedPkgName}" + publication.pom { + name = "Dataflow (${shadedPkgName})" + description = "dataflow-${shadedPkgName} is a dataflow framework based on the javac compiler.\n" + + '\n' + + 'It differs from the org.checkerframework:dataflow artifact in two ways.\n' + + "First, the packages in this artifact have been renamed to org.checkerframework.${shadedPkgName}.*.\n" + + 'Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } + } } - } } publishing { - publications { - dataflow(MavenPublication) { - dataflowPom it - } + publications { + dataflow(MavenPublication) { + dataflowPom it + } - dataflowShaded(MavenPublication) { - dataflowShadedPom(it, 'shaded') - artifact project.tasks.getByName('dataflowshadedJar').archiveFile - artifact sourcesJar - artifact javadocJar - } + dataflowShaded(MavenPublication) { + dataflowShadedPom(it, 'shaded') + artifact project.tasks.getByName('dataflowshadedJar').archiveFile + artifact sourcesJar + artifact javadocJar + } - dataflowShadednullaway(MavenPublication) { - dataflowShadedPom(it, 'nullaway') + dataflowShadednullaway(MavenPublication) { + dataflowShadedPom(it, 'nullaway') - artifact project.tasks.getByName('dataflownullawayJar').archiveFile - artifact sourcesJar - artifact javadocJar - } + artifact project.tasks.getByName('dataflownullawayJar').archiveFile + artifact sourcesJar + artifact javadocJar + } - dataflowShadederrorprone(MavenPublication) { - dataflowShadedPom(it, 'errorprone') + dataflowShadederrorprone(MavenPublication) { + dataflowShadedPom(it, 'errorprone') - artifact project.tasks.getByName('dataflowerrorproneJar').archiveFile - artifact sourcesJar - artifact javadocJar + artifact project.tasks.getByName('dataflowerrorproneJar').archiveFile + artifact sourcesJar + artifact javadocJar + } } - } } signing { - sign publishing.publications.dataflow - sign publishing.publications.dataflowShaded - sign publishing.publications.dataflowShadednullaway - sign publishing.publications.dataflowShadederrorprone + sign publishing.publications.dataflow + sign publishing.publications.dataflowShaded + sign publishing.publications.dataflowShadednullaway + sign publishing.publications.dataflowShadederrorprone } publishDataflowPublicationToMavenRepository.dependsOn(signDataflowShadedPublication) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java index 6fdb944d180..854a9eed27b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java @@ -3,15 +3,7 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; -import java.util.Comparator; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.PriorityQueue; -import java.util.Set; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; @@ -29,6 +21,17 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; +import java.util.Comparator; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; + +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + /** * Implementation of common features for {@link BackwardAnalysisImpl} and {@link * ForwardAnalysisImpl}. @@ -38,529 +41,534 @@ * @param the transfer function type that is used to approximated runtime behavior */ public abstract class AbstractAnalysis< - V extends AbstractValue, S extends Store, T extends TransferFunction> - implements Analysis { - - /** The direction of this analysis. */ - protected final Direction direction; - - /** Is the analysis currently running? */ - protected boolean isRunning = false; - - /** The transfer function for regular nodes. */ - // TODO: make final. Currently, the transferFunction has a reference to the analysis, so it - // can't be created until the Analysis is initialized. - protected @MonotonicNonNull T transferFunction; - - /** The current control flow graph to perform the analysis on. */ - protected @MonotonicNonNull ControlFlowGraph cfg; - - /** - * The transfer inputs of every basic block; assumed to be 'no information' if not present. The - * inputs are before blocks in forward analysis, and are after blocks in backward analysis. - */ - protected final IdentityHashMap> inputs = new IdentityHashMap<>(); - - /** The worklist used for the fix-point iteration. */ - protected final Worklist worklist; - - /** Abstract values of nodes. */ - protected final IdentityHashMap nodeValues = new IdentityHashMap<>(); - - /** Map from (effectively final) local variable elements to their abstract value. */ - protected final HashMap finalLocalValues = new HashMap<>(); - - /** - * The node that is currently handled in the analysis (if it is running). The following invariant - * holds: - * - *
          -   *   !isRunning ⇒ (currentNode == null)
          -   * 
          - */ - // currentNode == null when isRunning is true. - // See https://github.com/typetools/checker-framework/issues/4115 - protected @InternedDistinct @Nullable Node currentNode; - - /** - * The tree that is currently being looked at. The transfer function can set this tree to make - * sure that calls to {@code getValue} will not return information for this given tree. - */ - protected @InternedDistinct @Nullable Tree currentTree; - - /** The current transfer input when the analysis is running. */ - protected @Nullable TransferInput currentInput; - - /** - * Returns the tree that is currently being looked at. The transfer function can set this tree to - * make sure that calls to {@code getValue} will not return information for this given tree. - * - * @return the tree that is currently being looked at - */ - public @Nullable Tree getCurrentTree() { - return currentTree; - } - - /** - * Set the tree that is currently being looked at. - * - * @param currentTree the tree that should be currently looked at - */ - public void setCurrentTree(@FindDistinct Tree currentTree) { - this.currentTree = currentTree; - } - - /** - * Set the node that is currently being looked at. - * - * @param currentNode the node that should be currently looked at - */ - protected void setCurrentNode(@FindDistinct @Nullable Node currentNode) { - this.currentNode = currentNode; - } - - /** - * Implementation of common features for {@link BackwardAnalysisImpl} and {@link - * ForwardAnalysisImpl}. - * - * @param direction direction of the analysis - */ - protected AbstractAnalysis(Direction direction) { - this.direction = direction; - this.worklist = new Worklist(this.direction); - } - - /** Initialize the transfer inputs of every basic block before performing the analysis. */ - @RequiresNonNull("cfg") - protected abstract void initInitialInputs(); - - /** - * Propagate the stores in {@code currentInput} to the next block in the direction of analysis, - * according to the {@code flowRule}. - * - * @param nextBlock the target block to propagate the stores to - * @param node the node of the target block - * @param currentInput the current transfer input - * @param flowRule the flow rule being used - * @param addToWorklistAgain whether the block should be added to {@link #worklist} again - */ - protected abstract void propagateStoresTo( - Block nextBlock, - Node node, - TransferInput currentInput, - Store.FlowRule flowRule, - boolean addToWorklistAgain); - - @Override - public boolean isRunning() { - return isRunning; - } - - @Override - public Direction getDirection() { - return this.direction; - } - - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public AnalysisResult getResult() { - if (isRunning) { - throw new BugInCF( - "AbstractAnalysis::getResult() shouldn't be called when the analysis is" + " running."); + V extends AbstractValue, S extends Store, T extends TransferFunction> + implements Analysis { + + /** The direction of this analysis. */ + protected final Direction direction; + + /** Is the analysis currently running? */ + protected boolean isRunning = false; + + /** The transfer function for regular nodes. */ + // TODO: make final. Currently, the transferFunction has a reference to the analysis, so it + // can't be created until the Analysis is initialized. + protected @MonotonicNonNull T transferFunction; + + /** The current control flow graph to perform the analysis on. */ + protected @MonotonicNonNull ControlFlowGraph cfg; + + /** + * The transfer inputs of every basic block; assumed to be 'no information' if not present. The + * inputs are before blocks in forward analysis, and are after blocks in backward analysis. + */ + protected final IdentityHashMap> inputs = new IdentityHashMap<>(); + + /** The worklist used for the fix-point iteration. */ + protected final Worklist worklist; + + /** Abstract values of nodes. */ + protected final IdentityHashMap nodeValues = new IdentityHashMap<>(); + + /** Map from (effectively final) local variable elements to their abstract value. */ + protected final HashMap finalLocalValues = new HashMap<>(); + + /** + * The node that is currently handled in the analysis (if it is running). The following + * invariant holds: + * + *
          +     *   !isRunning ⇒ (currentNode == null)
          +     * 
          + */ + // currentNode == null when isRunning is true. + // See https://github.com/typetools/checker-framework/issues/4115 + protected @InternedDistinct @Nullable Node currentNode; + + /** + * The tree that is currently being looked at. The transfer function can set this tree to make + * sure that calls to {@code getValue} will not return information for this given tree. + */ + protected @InternedDistinct @Nullable Tree currentTree; + + /** The current transfer input when the analysis is running. */ + protected @Nullable TransferInput currentInput; + + /** + * Returns the tree that is currently being looked at. The transfer function can set this tree + * to make sure that calls to {@code getValue} will not return information for this given tree. + * + * @return the tree that is currently being looked at + */ + public @Nullable Tree getCurrentTree() { + return currentTree; } - return new AnalysisResult<>( - nodeValues, inputs, cfg.getTreeLookup(), cfg.getPostfixNodeLookup(), finalLocalValues); - } - - @Override - public @Nullable T getTransferFunction() { - return transferFunction; - } - - @Override - public @Nullable V getValue(Node n) { - if (isRunning) { - // we don't have a org.checkerframework.dataflow fact about the current node yet - if (currentNode == null - || currentNode == n - || (currentTree != null && currentTree == n.getTree())) { - return null; - } - // check that 'n' is a subnode of 'currentNode'. Check immediate operands - // first for efficiency. - assert !n.isLValue() : "Did not expect an lvalue, but got " + n; - if (!currentNode.getOperands().contains(n) - && !currentNode.getTransitiveOperands().contains(n)) { - return null; - } - // fall through when the current node is not 'n', and 'n' is not a subnode. + + /** + * Set the tree that is currently being looked at. + * + * @param currentTree the tree that should be currently looked at + */ + public void setCurrentTree(@FindDistinct Tree currentTree) { + this.currentTree = currentTree; } - return nodeValues.get(n); - } - - /** - * Returns all current node values. - * - * @return {@link #nodeValues} - */ - public IdentityHashMap getNodeValues() { - return nodeValues; - } - - /** - * Set all current node values to the given map. - * - * @param in the current node values - */ - /*package-private*/ void setNodeValues(IdentityHashMap in) { - assert !isRunning; - nodeValues.clear(); - nodeValues.putAll(in); - } - - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public @Nullable S getRegularExitStore() { - SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); - if (inputs.containsKey(regularExitBlock)) { - return inputs.get(regularExitBlock).getRegularStore(); - } else { - return null; + + /** + * Set the node that is currently being looked at. + * + * @param currentNode the node that should be currently looked at + */ + protected void setCurrentNode(@FindDistinct @Nullable Node currentNode) { + this.currentNode = currentNode; } - } - - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public @Nullable S getExceptionalExitStore() { - SpecialBlock exceptionalExitBlock = cfg.getExceptionalExitBlock(); - if (inputs.containsKey(exceptionalExitBlock)) { - S exceptionalExitStore = inputs.get(exceptionalExitBlock).getRegularStore(); - return exceptionalExitStore; - } else { - return null; + + /** + * Implementation of common features for {@link BackwardAnalysisImpl} and {@link + * ForwardAnalysisImpl}. + * + * @param direction direction of the analysis + */ + protected AbstractAnalysis(Direction direction) { + this.direction = direction; + this.worklist = new Worklist(this.direction); } - } - - /** - * Get the set of {@link Node}s for a given {@link Tree}. Returns null for trees that don't - * produce a value. - * - * @param t the given tree - * @return the set of corresponding nodes to the given tree - */ - public @Nullable Set getNodesForTree(Tree t) { - if (cfg == null) { - return null; + + /** Initialize the transfer inputs of every basic block before performing the analysis. */ + @RequiresNonNull("cfg") + protected abstract void initInitialInputs(); + + /** + * Propagate the stores in {@code currentInput} to the next block in the direction of analysis, + * according to the {@code flowRule}. + * + * @param nextBlock the target block to propagate the stores to + * @param node the node of the target block + * @param currentInput the current transfer input + * @param flowRule the flow rule being used + * @param addToWorklistAgain whether the block should be added to {@link #worklist} again + */ + protected abstract void propagateStoresTo( + Block nextBlock, + Node node, + TransferInput currentInput, + Store.FlowRule flowRule, + boolean addToWorklistAgain); + + @Override + public boolean isRunning() { + return isRunning; } - return cfg.getNodesCorrespondingToTree(t); - } - - @Override - public @Nullable V getValue(Tree t) { - // Dataflow is analyzing the tree, so no value is available. - if (t == currentTree || cfg == null) { - return null; + + @Override + public Direction getDirection() { + return this.direction; } - V result = getValue(getNodesForTree(t)); - if (result == null) { - result = getValue(cfg.getTreeLookup().get(t)); + + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public AnalysisResult getResult() { + if (isRunning) { + throw new BugInCF( + "AbstractAnalysis::getResult() shouldn't be called when the analysis is" + + " running."); + } + return new AnalysisResult<>( + nodeValues, + inputs, + cfg.getTreeLookup(), + cfg.getPostfixNodeLookup(), + finalLocalValues); } - return result; - } - - /** - * Returns the least upper bound of the values of {@code nodes}. - * - * @param nodes a set of nodes - * @return the least upper bound of the values of {@code nodes} - */ - private @Nullable V getValue(@Nullable Set nodes) { - if (nodes == null) { - return null; + + @Override + public @Nullable T getTransferFunction() { + return transferFunction; } - V merged = null; - for (Node aNode : nodes) { - if (aNode.isLValue()) { - return null; - } - V v = getValue(aNode); - if (merged == null) { - merged = v; - } else if (v != null) { - merged = merged.leastUpperBound(v); - } + @Override + public @Nullable V getValue(Node n) { + if (isRunning) { + // we don't have a org.checkerframework.dataflow fact about the current node yet + if (currentNode == null + || currentNode == n + || (currentTree != null && currentTree == n.getTree())) { + return null; + } + // check that 'n' is a subnode of 'currentNode'. Check immediate operands + // first for efficiency. + assert !n.isLValue() : "Did not expect an lvalue, but got " + n; + if (!currentNode.getOperands().contains(n) + && !currentNode.getTransitiveOperands().contains(n)) { + return null; + } + // fall through when the current node is not 'n', and 'n' is not a subnode. + } + return nodeValues.get(n); } - return merged; - } - - /** - * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps to a {@link - * Node} in the CFG or {@code null} otherwise. - * - * @param t the given tree - * @return the contained method tree of the given tree - */ - public @Nullable MethodTree getContainingMethod(Tree t) { - if (cfg == null) { - return null; + /** + * Returns all current node values. + * + * @return {@link #nodeValues} + */ + public IdentityHashMap getNodeValues() { + return nodeValues; } - return cfg.getContainingMethod(t); - } - - /** - * Get the {@link ClassTree} of the current CFG if the argument {@link Tree} maps to a {@link - * Node} in the CFG or {@code null} otherwise. - * - * @param t the given tree - * @return the contained class tree of the given tree - */ - public @Nullable ClassTree getContainingClass(Tree t) { - if (cfg == null) { - return null; + + /** + * Set all current node values to the given map. + * + * @param in the current node values + */ + /*package-private*/ void setNodeValues(IdentityHashMap in) { + assert !isRunning; + nodeValues.clear(); + nodeValues.putAll(in); } - return cfg.getContainingClass(t); - } - - /** - * Call the transfer function for node {@code node}, and set that node as current node first. This - * method requires a {@code transferInput} that the method can modify. - * - * @param node the given node - * @param transferInput the transfer input - * @return the output of the transfer function - */ - protected TransferResult callTransferFunction( - Node node, TransferInput transferInput) { - assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; - if (node.isLValue()) { - // TODO: should the default behavior return a regular transfer result, a conditional - // transfer result (depending on store.containsTwoStores()), or is the following - // correct? - return new RegularTransferResult<>(null, transferInput.getRegularStore()); + + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public @Nullable S getRegularExitStore() { + SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); + if (inputs.containsKey(regularExitBlock)) { + return inputs.get(regularExitBlock).getRegularStore(); + } else { + return null; + } } - transferInput.node = node; - setCurrentNode(node); - TransferResult transferResult = node.accept(transferFunction, transferInput); - setCurrentNode(null); - if (node instanceof AssignmentNode) { - // store the flow-refined value effectively for final local variables - AssignmentNode assignment = (AssignmentNode) node; - Node lhst = assignment.getTarget(); - if (lhst instanceof LocalVariableNode) { - LocalVariableNode lhs = (LocalVariableNode) lhst; - VariableElement elem = lhs.getElement(); - if (ElementUtils.isEffectivelyFinal(elem)) { - V resval = transferResult.getResultValue(); - if (resval != null) { - finalLocalValues.put(elem, resval); - } + + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public @Nullable S getExceptionalExitStore() { + SpecialBlock exceptionalExitBlock = cfg.getExceptionalExitBlock(); + if (inputs.containsKey(exceptionalExitBlock)) { + S exceptionalExitStore = inputs.get(exceptionalExitBlock).getRegularStore(); + return exceptionalExitStore; + } else { + return null; } - } } - return transferResult; - } - - /** - * Initialize the analysis with a new control flow graph. - * - * @param cfg the control flow graph to use - */ - protected final void init(ControlFlowGraph cfg) { - initFields(cfg); - initInitialInputs(); - } - - /** - * Should exceptional control flow for a particular exception type be ignored? - * - *

          The default implementation always returns {@code false}. Subclasses should override the - * method to implement a different policy. - * - * @param exceptionType the exception type - * @return {@code true} if exceptional control flow due to {@code exceptionType} should be - * ignored, {@code false} otherwise - */ - protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { - return false; - } - - /** - * Initialize fields of this object based on a given control flow graph. Sub-class may override - * this method to initialize customized fields. - * - * @param cfg a given control flow graph - */ - @EnsuresNonNull("this.cfg") - protected void initFields(ControlFlowGraph cfg) { - inputs.clear(); - nodeValues.clear(); - finalLocalValues.clear(); - this.cfg = cfg; - } - - /** - * Updates the value of node {@code node} to the value of the {@code transferResult}. Returns true - * if the node's value changed, or a store was updated. - * - * @param node the node to update - * @param transferResult the transfer result being updated - * @return true if the node's value changed, or a store was updated - */ - protected boolean updateNodeValues(Node node, TransferResult transferResult) { - V newVal = transferResult.getResultValue(); - boolean nodeValueChanged = false; - if (newVal != null) { - V oldVal = nodeValues.get(node); - nodeValues.put(node, newVal); - nodeValueChanged = !Objects.equals(oldVal, newVal); + + /** + * Get the set of {@link Node}s for a given {@link Tree}. Returns null for trees that don't + * produce a value. + * + * @param t the given tree + * @return the set of corresponding nodes to the given tree + */ + public @Nullable Set getNodesForTree(Tree t) { + if (cfg == null) { + return null; + } + return cfg.getNodesCorrespondingToTree(t); } - return nodeValueChanged || transferResult.storeChanged(); - } - - /** - * Read the store for a particular basic block from a map of stores (or {@code null} if none - * exists yet). - * - * @param stores a map of stores - * @param b the target block - * @param method return type should be a subtype of {@link Store} - * @return the store for the target block - */ - protected static @Nullable S readFromStore(Map stores, Block b) { - return stores.get(b); - } - - /** - * Add a basic block to {@link #worklist}. If {@code b} is already present, the method does - * nothing. - * - * @param b the block to add to {@link #worklist} - */ - protected void addToWorklist(Block b) { - // TODO: use a more efficient way to check if b is already present - if (!worklist.contains(b)) { - worklist.add(b); + + @Override + public @Nullable V getValue(Tree t) { + // Dataflow is analyzing the tree, so no value is available. + if (t == currentTree || cfg == null) { + return null; + } + V result = getValue(getNodesForTree(t)); + if (result == null) { + result = getValue(cfg.getTreeLookup().get(t)); + } + return result; } - } - /** - * A worklist is a priority queue of blocks in which the order is given by depth-first ordering to - * place non-loop predecessors ahead of successors. - */ - protected static class Worklist { + /** + * Returns the least upper bound of the values of {@code nodes}. + * + * @param nodes a set of nodes + * @return the least upper bound of the values of {@code nodes} + */ + private @Nullable V getValue(@Nullable Set nodes) { + if (nodes == null) { + return null; + } + + V merged = null; + for (Node aNode : nodes) { + if (aNode.isLValue()) { + return null; + } + V v = getValue(aNode); + if (merged == null) { + merged = v; + } else if (v != null) { + merged = merged.leastUpperBound(v); + } + } - /** Map all blocks in the CFG to their depth-first order. */ - protected final IdentityHashMap depthFirstOrder = new IdentityHashMap<>(); + return merged; + } /** - * Comparators to allow priority queue to order blocks by their depth-first order, using by - * forward analysis. + * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps to a {@link + * Node} in the CFG or {@code null} otherwise. + * + * @param t the given tree + * @return the contained method tree of the given tree */ - public class ForwardDFOComparator implements Comparator { - @SuppressWarnings("nullness:unboxing.of.nullable") - @Override - public int compare(Block b1, Block b2) { - return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); - } + public @Nullable MethodTree getContainingMethod(Tree t) { + if (cfg == null) { + return null; + } + return cfg.getContainingMethod(t); } /** - * Comparators to allow priority queue to order blocks by their depth-first order, using by - * backward analysis. + * Get the {@link ClassTree} of the current CFG if the argument {@link Tree} maps to a {@link + * Node} in the CFG or {@code null} otherwise. + * + * @param t the given tree + * @return the contained class tree of the given tree */ - public class BackwardDFOComparator implements Comparator { - @SuppressWarnings("nullness:unboxing.of.nullable") - @Override - public int compare(Block b1, Block b2) { - return depthFirstOrder.get(b2) - depthFirstOrder.get(b1); - } + public @Nullable ClassTree getContainingClass(Tree t) { + if (cfg == null) { + return null; + } + return cfg.getContainingClass(t); } - /** The backing priority queue. */ - protected final PriorityQueue queue; + /** + * Call the transfer function for node {@code node}, and set that node as current node first. + * This method requires a {@code transferInput} that the method can modify. + * + * @param node the given node + * @param transferInput the transfer input + * @return the output of the transfer function + */ + protected TransferResult callTransferFunction( + Node node, TransferInput transferInput) { + assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; + if (node.isLValue()) { + // TODO: should the default behavior return a regular transfer result, a conditional + // transfer result (depending on store.containsTwoStores()), or is the following + // correct? + return new RegularTransferResult<>(null, transferInput.getRegularStore()); + } + transferInput.node = node; + setCurrentNode(node); + TransferResult transferResult = node.accept(transferFunction, transferInput); + setCurrentNode(null); + if (node instanceof AssignmentNode) { + // store the flow-refined value effectively for final local variables + AssignmentNode assignment = (AssignmentNode) node; + Node lhst = assignment.getTarget(); + if (lhst instanceof LocalVariableNode) { + LocalVariableNode lhs = (LocalVariableNode) lhst; + VariableElement elem = lhs.getElement(); + if (ElementUtils.isEffectivelyFinal(elem)) { + V resval = transferResult.getResultValue(); + if (resval != null) { + finalLocalValues.put(elem, resval); + } + } + } + } + return transferResult; + } /** - * Create a Worklist. + * Initialize the analysis with a new control flow graph. * - * @param direction the direction (forward or backward) + * @param cfg the control flow graph to use */ - public Worklist(Direction direction) { - if (direction == Direction.FORWARD) { - queue = new PriorityQueue<>(new ForwardDFOComparator()); - } else if (direction == Direction.BACKWARD) { - queue = new PriorityQueue<>(new BackwardDFOComparator()); - } else { - throw new BugInCF("Unexpected Direction meet: " + direction.name()); - } + protected final void init(ControlFlowGraph cfg) { + initFields(cfg); + initInitialInputs(); } /** - * Process the control flow graph, add the blocks to {@link #depthFirstOrder}. + * Should exceptional control flow for a particular exception type be ignored? + * + *

          The default implementation always returns {@code false}. Subclasses should override the + * method to implement a different policy. * - * @param cfg the control flow graph to process + * @param exceptionType the exception type + * @return {@code true} if exceptional control flow due to {@code exceptionType} should be + * ignored, {@code false} otherwise */ - public void process(ControlFlowGraph cfg) { - depthFirstOrder.clear(); - int count = 1; - for (Block b : cfg.getDepthFirstOrderedBlocks()) { - depthFirstOrder.put(b, count++); - } - - queue.clear(); + protected boolean isIgnoredExceptionType(TypeMirror exceptionType) { + return false; } /** - * See {@link PriorityQueue#isEmpty}. + * Initialize fields of this object based on a given control flow graph. Sub-class may override + * this method to initialize customized fields. * - * @see PriorityQueue#isEmpty - * @return true if {@link #queue} is empty else false + * @param cfg a given control flow graph */ - @Pure - @EnsuresNonNullIf(result = false, expression = "poll()") - @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") // forwarded - public boolean isEmpty() { - return queue.isEmpty(); + @EnsuresNonNull("this.cfg") + protected void initFields(ControlFlowGraph cfg) { + inputs.clear(); + nodeValues.clear(); + finalLocalValues.clear(); + this.cfg = cfg; } /** - * Check if {@link #queue} contains the block which is passed as the argument. + * Updates the value of node {@code node} to the value of the {@code transferResult}. Returns + * true if the node's value changed, or a store was updated. * - * @param block the given block to check - * @return true if {@link #queue} contains the given block + * @param node the node to update + * @param transferResult the transfer result being updated + * @return true if the node's value changed, or a store was updated */ - public boolean contains(Block block) { - return queue.contains(block); + protected boolean updateNodeValues(Node node, TransferResult transferResult) { + V newVal = transferResult.getResultValue(); + boolean nodeValueChanged = false; + if (newVal != null) { + V oldVal = nodeValues.get(node); + nodeValues.put(node, newVal); + nodeValueChanged = !Objects.equals(oldVal, newVal); + } + return nodeValueChanged || transferResult.storeChanged(); } /** - * Add the given block to {@link #queue}. Adds unconditionally: does not check containment - * first. + * Read the store for a particular basic block from a map of stores (or {@code null} if none + * exists yet). * - * @param block the block to add to {@link #queue} + * @param stores a map of stores + * @param b the target block + * @param method return type should be a subtype of {@link Store} + * @return the store for the target block */ - public void add(Block block) { - queue.add(block); + protected static @Nullable S readFromStore(Map stores, Block b) { + return stores.get(b); } /** - * See {@link PriorityQueue#poll}. + * Add a basic block to {@link #worklist}. If {@code b} is already present, the method does + * nothing. * - * @see PriorityQueue#poll - * @return the head of {@link #queue} + * @param b the block to add to {@link #worklist} */ - @Pure - public @Nullable Block poll() { - return queue.poll(); + protected void addToWorklist(Block b) { + // TODO: use a more efficient way to check if b is already present + if (!worklist.contains(b)) { + worklist.add(b); + } } - @Override - public String toString() { - return "Worklist(" + queue + ")"; + /** + * A worklist is a priority queue of blocks in which the order is given by depth-first ordering + * to place non-loop predecessors ahead of successors. + */ + protected static class Worklist { + + /** Map all blocks in the CFG to their depth-first order. */ + protected final IdentityHashMap depthFirstOrder = new IdentityHashMap<>(); + + /** + * Comparators to allow priority queue to order blocks by their depth-first order, using by + * forward analysis. + */ + public class ForwardDFOComparator implements Comparator { + @SuppressWarnings("nullness:unboxing.of.nullable") + @Override + public int compare(Block b1, Block b2) { + return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); + } + } + + /** + * Comparators to allow priority queue to order blocks by their depth-first order, using by + * backward analysis. + */ + public class BackwardDFOComparator implements Comparator { + @SuppressWarnings("nullness:unboxing.of.nullable") + @Override + public int compare(Block b1, Block b2) { + return depthFirstOrder.get(b2) - depthFirstOrder.get(b1); + } + } + + /** The backing priority queue. */ + protected final PriorityQueue queue; + + /** + * Create a Worklist. + * + * @param direction the direction (forward or backward) + */ + public Worklist(Direction direction) { + if (direction == Direction.FORWARD) { + queue = new PriorityQueue<>(new ForwardDFOComparator()); + } else if (direction == Direction.BACKWARD) { + queue = new PriorityQueue<>(new BackwardDFOComparator()); + } else { + throw new BugInCF("Unexpected Direction meet: " + direction.name()); + } + } + + /** + * Process the control flow graph, add the blocks to {@link #depthFirstOrder}. + * + * @param cfg the control flow graph to process + */ + public void process(ControlFlowGraph cfg) { + depthFirstOrder.clear(); + int count = 1; + for (Block b : cfg.getDepthFirstOrderedBlocks()) { + depthFirstOrder.put(b, count++); + } + + queue.clear(); + } + + /** + * See {@link PriorityQueue#isEmpty}. + * + * @see PriorityQueue#isEmpty + * @return true if {@link #queue} is empty else false + */ + @Pure + @EnsuresNonNullIf(result = false, expression = "poll()") + @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") // forwarded + public boolean isEmpty() { + return queue.isEmpty(); + } + + /** + * Check if {@link #queue} contains the block which is passed as the argument. + * + * @param block the given block to check + * @return true if {@link #queue} contains the given block + */ + public boolean contains(Block block) { + return queue.contains(block); + } + + /** + * Add the given block to {@link #queue}. Adds unconditionally: does not check containment + * first. + * + * @param block the block to add to {@link #queue} + */ + public void add(Block block) { + queue.add(block); + } + + /** + * See {@link PriorityQueue#poll}. + * + * @see PriorityQueue#poll + * @return the head of {@link #queue} + */ + @Pure + public @Nullable Block poll() { + return queue.poll(); + } + + @Override + public String toString() { + return "Worklist(" + queue + ")"; + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java index e503db4ee0a..f226a8feb6e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractValue.java @@ -3,22 +3,22 @@ /** An abstract value used in the org.checkerframework.dataflow analysis. */ public interface AbstractValue> { - /** - * Compute the least upper bound of two values. - * - *

          Important: This method must fulfill the following contract: - * - *

            - *
          • Does not change {@code this}. - *
          • Does not change {@code other}. - *
          • Returns a fresh object which is not aliased yet. - *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
          • Is commutative. - *
          - * - * @param other the other value - * @return the least upper bound of the two values - */ - V leastUpperBound(V other); + /** + * Compute the least upper bound of two values. + * + *

          Important: This method must fulfill the following contract: + * + *

            + *
          • Does not change {@code this}. + *
          • Does not change {@code other}. + *
          • Returns a fresh object which is not aliased yet. + *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
          • Is commutative. + *
          + * + * @param other the other value + * @return the least upper bound of the two values + */ + V leastUpperBound(V other); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java index 30e9cfbf798..9ec9af400dd 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java @@ -1,13 +1,15 @@ package org.checkerframework.dataflow.analysis; import com.sun.source.tree.Tree; -import java.util.IdentityHashMap; -import java.util.Map; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.block.Block; import org.checkerframework.dataflow.cfg.node.Node; +import java.util.IdentityHashMap; +import java.util.Map; + /** * This interface defines a dataflow analysis, given a control flow graph and a transfer function. A * dataflow analysis has a direction, either forward or backward. The direction of corresponding @@ -19,136 +21,138 @@ * @param the transfer function type that is used to approximated runtime behavior */ public interface Analysis< - V extends AbstractValue, S extends Store, T extends TransferFunction> { - - /** The direction of an analysis instance. */ - enum Direction { - /** The forward direction. */ - FORWARD, - /** The backward direction. */ - BACKWARD - } - - /** - * In calls to {@code Analysis#runAnalysisFor}, whether to return the store before or after the - * given node. - */ - enum BeforeOrAfter { - /** Return the pre-store. */ - BEFORE, - /** Return the post-store. */ - AFTER - } - - /** - * Get the direction of this analysis. - * - * @return the direction of this analysis - */ - Direction getDirection(); - - /** - * Is the analysis currently running? - * - * @return true if the analysis is running currently, else false - */ - boolean isRunning(); - - /** - * Perform the actual analysis. - * - * @param cfg the control flow graph - */ - void performAnalysis(ControlFlowGraph cfg); - - /** - * Perform the actual analysis on one block. - * - * @param b the block to analyze - */ - void performAnalysisBlock(Block b); - - /** - * Runs the analysis again within the block of {@code node} and returns the store at the location - * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node} - * {@code node} is returned. Otherwise, the store immediately after {@code node} is returned. If - * {@code analysisCaches} is not null, this method uses a cache. {@code analysisCaches} is a map - * of a block of node to the cached analysis result. If the cache for {@code transferInput} is not - * in {@code analysisCaches}, this method creates new cache and stores it in {@code - * analysisCaches}. The cache is a map of nodes to the analysis results of the nodes. - * - * @param node the node to analyze - * @param preOrPost which store to return: the store immediately before {@code node} or the store - * after {@code node} - * @param blockTransferInput the transfer input of the block of this node - * @param nodeValues abstract values of nodes - * @param analysisCaches caches of analysis results - * @return the store before or after {@code node} (depends on the value of {@code before}) after - * running the analysis - */ - S runAnalysisFor( - Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput blockTransferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches); - - /** - * The result of running the analysis. This is only available once the analysis finished running. - * - * @return the result of running the analysis - */ - AnalysisResult getResult(); - - /** - * Get the transfer function of this analysis. - * - * @return the transfer function of this analysis - */ - @Nullable T getTransferFunction(); - - /** - * Get the transfer input of a given {@link Block} b. - * - * @param b a given Block - * @return the transfer input of this Block - */ - @Nullable TransferInput getInput(Block b); - - /** - * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param n n a node - * @return the abstract value for node {@code n}, or {@code null} if no information is available - */ - @Nullable V getValue(Node n); - - /** - * Return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param t the given tree - * @return the abstract value for the given tree - */ - @Nullable V getValue(Tree t); - - /** - * Returns the regular exit store, or {@code null}, if there is no such store (because the method - * cannot exit through the regular exit block). - * - * @return the regular exit store, or {@code null}, if there is no such store (because the method - * cannot exit through the regular exit block) - */ - @Nullable S getRegularExitStore(); - - /** - * Returns the exceptional exit store. - * - * @return the exceptional exit store - */ - @Nullable S getExceptionalExitStore(); + V extends AbstractValue, S extends Store, T extends TransferFunction> { + + /** The direction of an analysis instance. */ + enum Direction { + /** The forward direction. */ + FORWARD, + /** The backward direction. */ + BACKWARD + } + + /** + * In calls to {@code Analysis#runAnalysisFor}, whether to return the store before or after the + * given node. + */ + enum BeforeOrAfter { + /** Return the pre-store. */ + BEFORE, + /** Return the post-store. */ + AFTER + } + + /** + * Get the direction of this analysis. + * + * @return the direction of this analysis + */ + Direction getDirection(); + + /** + * Is the analysis currently running? + * + * @return true if the analysis is running currently, else false + */ + boolean isRunning(); + + /** + * Perform the actual analysis. + * + * @param cfg the control flow graph + */ + void performAnalysis(ControlFlowGraph cfg); + + /** + * Perform the actual analysis on one block. + * + * @param b the block to analyze + */ + void performAnalysisBlock(Block b); + + /** + * Runs the analysis again within the block of {@code node} and returns the store at the + * location of {@code node}. If {@code before} is true, then the store immediately before the + * {@link Node} {@code node} is returned. Otherwise, the store immediately after {@code node} is + * returned. If {@code analysisCaches} is not null, this method uses a cache. {@code + * analysisCaches} is a map of a block of node to the cached analysis result. If the cache for + * {@code transferInput} is not in {@code analysisCaches}, this method creates new cache and + * stores it in {@code analysisCaches}. The cache is a map of nodes to the analysis results of + * the nodes. + * + * @param node the node to analyze + * @param preOrPost which store to return: the store immediately before {@code node} or the + * store after {@code node} + * @param blockTransferInput the transfer input of the block of this node + * @param nodeValues abstract values of nodes + * @param analysisCaches caches of analysis results + * @return the store before or after {@code node} (depends on the value of {@code before}) after + * running the analysis + */ + S runAnalysisFor( + Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput blockTransferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches); + + /** + * The result of running the analysis. This is only available once the analysis finished + * running. + * + * @return the result of running the analysis + */ + AnalysisResult getResult(); + + /** + * Get the transfer function of this analysis. + * + * @return the transfer function of this analysis + */ + @Nullable T getTransferFunction(); + + /** + * Get the transfer input of a given {@link Block} b. + * + * @param b a given Block + * @return the transfer input of this Block + */ + @Nullable TransferInput getInput(Block b); + + /** + * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param n n a node + * @return the abstract value for node {@code n}, or {@code null} if no information is available + */ + @Nullable V getValue(Node n); + + /** + * Return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param t the given tree + * @return the abstract value for the given tree + */ + @Nullable V getValue(Tree t); + + /** + * Returns the regular exit store, or {@code null}, if there is no such store (because the + * method cannot exit through the regular exit block). + * + * @return the regular exit store, or {@code null}, if there is no such store (because the + * method cannot exit through the regular exit block) + */ + @Nullable S getRegularExitStore(); + + /** + * Returns the exceptional exit store. + * + * @return the exceptional exit store + */ + @Nullable S getExceptionalExitStore(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java index 77ea632a04c..c66379b2b01 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java @@ -3,14 +3,7 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicLong; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.block.Block; @@ -20,6 +13,16 @@ import org.plumelib.util.UniqueId; import org.plumelib.util.UnmodifiableIdentityHashMap; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; + +import javax.lang.model.element.VariableElement; + /** * An {@link AnalysisResult} represents the result of a org.checkerframework.dataflow analysis by * providing the abstract values given a node or a tree. Note that it does not keep track of custom @@ -30,492 +33,511 @@ */ public class AnalysisResult, S extends Store> implements UniqueId { - /** - * For efficiency, certain maps stored in the result are only copied lazily, when they need to be - * mutated. This flag tracks if the copying has occurred. - */ - private boolean mapsCopied = false; - - /** Abstract values of nodes. */ - protected IdentityHashMap nodeValues; - - /** - * Map from AST {@link Tree}s to sets of {@link Node}s. - * - *

          Some of those Nodes might not be keys in {@link #nodeValues}. One reason is that the Node is - * unreachable in the control flow graph, so dataflow never gave it a value. - */ - protected IdentityHashMap> treeLookup; - - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic - * tree that is {@code v + 1} or {@code v - 1}. - */ - protected IdentityHashMap postfixLookup; - - /** Map from (effectively final) local variable elements to their abstract value. */ - protected final Map finalLocalValues; - - /** The stores before every method call. */ - protected final IdentityHashMap> stores; - - /** - * Caches of the analysis results for each input for the block of the node and each node. - * - * @see #runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, IdentityHashMap, Map) - */ - protected final Map, IdentityHashMap>> - analysisCaches; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid(@UnknownInitialization AnalysisResult this) { - return uid; - } - - /** - * Initialize with given mappings. - * - * @param nodeValues {@link #nodeValues} - * @param stores {@link #stores} - * @param treeLookup {@link #treeLookup} - * @param postfixLookup {@link #postfixLookup} - * @param finalLocalValues {@link #finalLocalValues} - * @param analysisCaches {@link #analysisCaches} - */ - protected AnalysisResult( - IdentityHashMap nodeValues, - IdentityHashMap> stores, - IdentityHashMap> treeLookup, - IdentityHashMap postfixLookup, - Map finalLocalValues, - Map, IdentityHashMap>> analysisCaches) { - this.nodeValues = UnmodifiableIdentityHashMap.wrap(nodeValues); - this.treeLookup = UnmodifiableIdentityHashMap.wrap(treeLookup); - this.postfixLookup = UnmodifiableIdentityHashMap.wrap(postfixLookup); - // TODO: why are stores and finalLocalValues captured? - this.stores = stores; - this.finalLocalValues = finalLocalValues; - this.analysisCaches = analysisCaches; - } - - /** - * Initialize with given mappings and empty cache. - * - * @param nodeValues {@link #nodeValues} - * @param stores {@link #stores} - * @param treeLookup {@link #treeLookup} - * @param postfixLookup {@link #postfixLookup} - * @param finalLocalValues {@link #finalLocalValues} - */ - public AnalysisResult( - IdentityHashMap nodeValues, - IdentityHashMap> stores, - IdentityHashMap> treeLookup, - IdentityHashMap postfixLookup, - Map finalLocalValues) { - this(nodeValues, stores, treeLookup, postfixLookup, finalLocalValues, new IdentityHashMap<>()); - } - - /** - * Initialize empty result with specified cache. - * - * @param analysisCaches {@link #analysisCaches} - */ - public AnalysisResult( - Map, IdentityHashMap>> analysisCaches) { - this( - new IdentityHashMap<>(), - new IdentityHashMap<>(), - new IdentityHashMap<>(), - new IdentityHashMap<>(), - new HashMap<>(), - analysisCaches); - } - - /** - * Combine with another analysis result. - * - * @param other an analysis result to combine with this - */ - public void combine(AnalysisResult other) { - copyMapsIfNeeded(); - nodeValues.putAll(other.nodeValues); - mergeTreeLookup(treeLookup, other.treeLookup); - postfixLookup.putAll(other.postfixLookup); - stores.putAll(other.stores); - finalLocalValues.putAll(other.finalLocalValues); - } - - /** Make copies of certain internal IdentityHashMaps, if they have not been copied already. */ - private void copyMapsIfNeeded() { - if (!mapsCopied) { - nodeValues = new IdentityHashMap<>(nodeValues); - treeLookup = new IdentityHashMap<>(treeLookup); - postfixLookup = new IdentityHashMap<>(postfixLookup); - mapsCopied = true; + /** + * For efficiency, certain maps stored in the result are only copied lazily, when they need to + * be mutated. This flag tracks if the copying has occurred. + */ + private boolean mapsCopied = false; + + /** Abstract values of nodes. */ + protected IdentityHashMap nodeValues; + + /** + * Map from AST {@link Tree}s to sets of {@link Node}s. + * + *

          Some of those Nodes might not be keys in {@link #nodeValues}. One reason is that the Node + * is unreachable in the control flow graph, so dataflow never gave it a value. + */ + protected IdentityHashMap> treeLookup; + + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the + * synthetic tree that is {@code v + 1} or {@code v - 1}. + */ + protected IdentityHashMap postfixLookup; + + /** Map from (effectively final) local variable elements to their abstract value. */ + protected final Map finalLocalValues; + + /** The stores before every method call. */ + protected final IdentityHashMap> stores; + + /** + * Caches of the analysis results for each input for the block of the node and each node. + * + * @see #runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, IdentityHashMap, Map) + */ + protected final Map, IdentityHashMap>> + analysisCaches; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid(@UnknownInitialization AnalysisResult this) { + return uid; + } + + /** + * Initialize with given mappings. + * + * @param nodeValues {@link #nodeValues} + * @param stores {@link #stores} + * @param treeLookup {@link #treeLookup} + * @param postfixLookup {@link #postfixLookup} + * @param finalLocalValues {@link #finalLocalValues} + * @param analysisCaches {@link #analysisCaches} + */ + protected AnalysisResult( + IdentityHashMap nodeValues, + IdentityHashMap> stores, + IdentityHashMap> treeLookup, + IdentityHashMap postfixLookup, + Map finalLocalValues, + Map, IdentityHashMap>> analysisCaches) { + this.nodeValues = UnmodifiableIdentityHashMap.wrap(nodeValues); + this.treeLookup = UnmodifiableIdentityHashMap.wrap(treeLookup); + this.postfixLookup = UnmodifiableIdentityHashMap.wrap(postfixLookup); + // TODO: why are stores and finalLocalValues captured? + this.stores = stores; + this.finalLocalValues = finalLocalValues; + this.analysisCaches = analysisCaches; + } + + /** + * Initialize with given mappings and empty cache. + * + * @param nodeValues {@link #nodeValues} + * @param stores {@link #stores} + * @param treeLookup {@link #treeLookup} + * @param postfixLookup {@link #postfixLookup} + * @param finalLocalValues {@link #finalLocalValues} + */ + public AnalysisResult( + IdentityHashMap nodeValues, + IdentityHashMap> stores, + IdentityHashMap> treeLookup, + IdentityHashMap postfixLookup, + Map finalLocalValues) { + this( + nodeValues, + stores, + treeLookup, + postfixLookup, + finalLocalValues, + new IdentityHashMap<>()); } - } - - /** - * Merge all entries from otherTreeLookup into treeLookup. Merge sets if already present. - * - * @param treeLookup a map from abstract syntax trees to sets of nodes - * @param otherTreeLookup another treeLookup that will be merged into {@code treeLookup} - */ - private static void mergeTreeLookup( - IdentityHashMap> treeLookup, - IdentityHashMap> otherTreeLookup) { - for (Map.Entry> entry : otherTreeLookup.entrySet()) { - Set hit = treeLookup.get(entry.getKey()); - if (hit == null) { - treeLookup.put(entry.getKey(), entry.getValue()); - } else { - hit.addAll(entry.getValue()); - } + + /** + * Initialize empty result with specified cache. + * + * @param analysisCaches {@link #analysisCaches} + */ + public AnalysisResult( + Map, IdentityHashMap>> analysisCaches) { + this( + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), + new HashMap<>(), + analysisCaches); + } + + /** + * Combine with another analysis result. + * + * @param other an analysis result to combine with this + */ + public void combine(AnalysisResult other) { + copyMapsIfNeeded(); + nodeValues.putAll(other.nodeValues); + mergeTreeLookup(treeLookup, other.treeLookup); + postfixLookup.putAll(other.postfixLookup); + stores.putAll(other.stores); + finalLocalValues.putAll(other.finalLocalValues); + } + + /** Make copies of certain internal IdentityHashMaps, if they have not been copied already. */ + private void copyMapsIfNeeded() { + if (!mapsCopied) { + nodeValues = new IdentityHashMap<>(nodeValues); + treeLookup = new IdentityHashMap<>(treeLookup); + postfixLookup = new IdentityHashMap<>(postfixLookup); + mapsCopied = true; + } + } + + /** + * Merge all entries from otherTreeLookup into treeLookup. Merge sets if already present. + * + * @param treeLookup a map from abstract syntax trees to sets of nodes + * @param otherTreeLookup another treeLookup that will be merged into {@code treeLookup} + */ + private static void mergeTreeLookup( + IdentityHashMap> treeLookup, + IdentityHashMap> otherTreeLookup) { + for (Map.Entry> entry : otherTreeLookup.entrySet()) { + Set hit = treeLookup.get(entry.getKey()); + if (hit == null) { + treeLookup.put(entry.getKey(), entry.getValue()); + } else { + hit.addAll(entry.getValue()); + } + } } - } - - /** - * Returns the value of effectively final local variables. - * - * @return the value of effectively final local variables - */ - public Map getFinalLocalValues() { - return finalLocalValues; - } - - /** - * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param n a node - * @return the abstract value for {@link Node} {@code n}, or {@code null} if no information is - * available - */ - public @Nullable V getValue(Node n) { - return nodeValues.get(n); - } - - /** - * Returns the abstract value for {@link Tree} {@code t}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param t a tree - * @return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is - * available - */ - public @Nullable V getValue(Tree t) { - Set nodes = treeLookup.get(t); - - if (nodes == null) { - return null; + + /** + * Returns the value of effectively final local variables. + * + * @return the value of effectively final local variables + */ + public Map getFinalLocalValues() { + return finalLocalValues; } - V merged = null; - for (Node aNode : nodes) { - V a = getValue(aNode); - if (merged == null) { - merged = a; - } else if (a != null) { - merged = merged.leastUpperBound(a); - } + + /** + * Returns the abstract value for {@link Node} {@code n}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param n a node + * @return the abstract value for {@link Node} {@code n}, or {@code null} if no information is + * available + */ + public @Nullable V getValue(Node n) { + return nodeValues.get(n); } - return merged; - } - - /** - * Returns the {@code Node}s corresponding to a particular {@code Tree}. Multiple {@code Node}s - * can correspond to a single {@code Tree} because of several reasons: - * - *

            - *
          1. In a lambda expression such as {@code () -> 5} the {@code 5} is both an {@code - * IntegerLiteralNode} and a {@code LambdaResultExpressionNode}. - *
          2. Widening and narrowing primitive conversions can result in {@code WideningConversionNode} - * and {@code NarrowingConversionNode}. - *
          3. Automatic String conversion can result in a {@code StringConversionNode}. - *
          4. Trees for {@code finally} blocks are cloned to achieve a precise CFG. Any {@code Tree} - * within a finally block can have multiple corresponding {@code Node}s attached to them. - *
          - * - * Callers of this method should always iterate through the returned set, possibly ignoring all - * {@code Node}s they are not interested in. - * - * @param tree a tree - * @return the set of {@link Node}s for a given {@link Tree} - */ - public @Nullable Set getNodesForTree(Tree tree) { - return treeLookup.get(tree); - } - - /** - * Returns the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment or - * decrement tree. - * - * @param postfixTree a postfix increment or decrement tree - * @return the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment or - * decrement tree - */ - public BinaryTree getPostfixBinaryTree(UnaryTree postfixTree) { - if (!postfixLookup.containsKey(postfixTree)) { - throw new BugInCF(postfixTree + " is not in postfixLookup"); + + /** + * Returns the abstract value for {@link Tree} {@code t}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param t a tree + * @return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is + * available + */ + public @Nullable V getValue(Tree t) { + Set nodes = treeLookup.get(t); + + if (nodes == null) { + return null; + } + V merged = null; + for (Node aNode : nodes) { + V a = getValue(aNode); + if (merged == null) { + merged = a; + } else if (a != null) { + merged = merged.leastUpperBound(a); + } + } + return merged; } - return postfixLookup.get(postfixTree); - } - - /** - * Returns the store immediately before a given {@link Tree}. - * - * @param tree a tree - * @return the store immediately before a given {@link Tree} - */ - public @Nullable S getStoreBefore(Tree tree) { - Set nodes = getNodesForTree(tree); - if (nodes == null) { - return null; + + /** + * Returns the {@code Node}s corresponding to a particular {@code Tree}. Multiple {@code Node}s + * can correspond to a single {@code Tree} because of several reasons: + * + *
            + *
          1. In a lambda expression such as {@code () -> 5} the {@code 5} is both an {@code + * IntegerLiteralNode} and a {@code LambdaResultExpressionNode}. + *
          2. Widening and narrowing primitive conversions can result in {@code + * WideningConversionNode} and {@code NarrowingConversionNode}. + *
          3. Automatic String conversion can result in a {@code StringConversionNode}. + *
          4. Trees for {@code finally} blocks are cloned to achieve a precise CFG. Any {@code Tree} + * within a finally block can have multiple corresponding {@code Node}s attached to them. + *
          + * + * Callers of this method should always iterate through the returned set, possibly ignoring all + * {@code Node}s they are not interested in. + * + * @param tree a tree + * @return the set of {@link Node}s for a given {@link Tree} + */ + public @Nullable Set getNodesForTree(Tree tree) { + return treeLookup.get(tree); } - S merged = null; - for (Node node : nodes) { - S s = getStoreBefore(node); - if (merged == null) { - merged = s; - } else if (s != null) { - merged = merged.leastUpperBound(s); - } + + /** + * Returns the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment + * or decrement tree. + * + * @param postfixTree a postfix increment or decrement tree + * @return the synthetic {@code v + 1} or {@code v - 1} corresponding to the postfix increment + * or decrement tree + */ + public BinaryTree getPostfixBinaryTree(UnaryTree postfixTree) { + if (!postfixLookup.containsKey(postfixTree)) { + throw new BugInCF(postfixTree + " is not in postfixLookup"); + } + return postfixLookup.get(postfixTree); } - return merged; - } - - /** - * Returns the store immediately before a given {@link Node}. - * - * @param node a node - * @return the store immediately before a given {@link Node} - */ - public @Nullable S getStoreBefore(Node node) { - return runAnalysisFor(node, Analysis.BeforeOrAfter.BEFORE); - } - - /** - * Returns the regular store immediately before a given {@link Block}. - * - * @param block a block - * @return the store right before the given block - */ - public S getStoreBefore(Block block) { - TransferInput transferInput = stores.get(block); - assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null"; - Analysis analysis = transferInput.analysis; - switch (analysis.getDirection()) { - case FORWARD: - return transferInput.getRegularStore(); - case BACKWARD: - List nodes = block.getNodes(); - if (nodes.isEmpty()) { - // This block doesn't contain any node, return the store in the transfer input. - return transferInput.getRegularStore(); - } else { - Node firstNode = nodes.get(0); - return analysis.runAnalysisFor( - firstNode, Analysis.BeforeOrAfter.BEFORE, transferInput, nodeValues, analysisCaches); + + /** + * Returns the store immediately before a given {@link Tree}. + * + * @param tree a tree + * @return the store immediately before a given {@link Tree} + */ + public @Nullable S getStoreBefore(Tree tree) { + Set nodes = getNodesForTree(tree); + if (nodes == null) { + return null; } - default: - throw new BugInCF("Unknown direction: " + analysis.getDirection()); + S merged = null; + for (Node node : nodes) { + S s = getStoreBefore(node); + if (merged == null) { + merged = s; + } else if (s != null) { + merged = merged.leastUpperBound(s); + } + } + return merged; + } + + /** + * Returns the store immediately before a given {@link Node}. + * + * @param node a node + * @return the store immediately before a given {@link Node} + */ + public @Nullable S getStoreBefore(Node node) { + return runAnalysisFor(node, Analysis.BeforeOrAfter.BEFORE); } - } - - /** - * Returns the regular store immediately after a given block. - * - * @param block a block - * @return the store after the given block - */ - public S getStoreAfter(Block block) { - TransferInput transferInput = stores.get(block); - assert transferInput != null : "@AssumeAssertion(nullness): transferInput should be non-null"; - Analysis analysis = transferInput.analysis; - switch (analysis.getDirection()) { - case FORWARD: - Node lastNode = block.getLastNode(); - if (lastNode == null) { - // This block doesn't contain any node, return the store in the transfer input. - return transferInput.getRegularStore(); - } else { - return analysis.runAnalysisFor( - lastNode, Analysis.BeforeOrAfter.AFTER, transferInput, nodeValues, analysisCaches); + + /** + * Returns the regular store immediately before a given {@link Block}. + * + * @param block a block + * @return the store right before the given block + */ + public S getStoreBefore(Block block) { + TransferInput transferInput = stores.get(block); + assert transferInput != null + : "@AssumeAssertion(nullness): transferInput should be non-null"; + Analysis analysis = transferInput.analysis; + switch (analysis.getDirection()) { + case FORWARD: + return transferInput.getRegularStore(); + case BACKWARD: + List nodes = block.getNodes(); + if (nodes.isEmpty()) { + // This block doesn't contain any node, return the store in the transfer input. + return transferInput.getRegularStore(); + } else { + Node firstNode = nodes.get(0); + return analysis.runAnalysisFor( + firstNode, + Analysis.BeforeOrAfter.BEFORE, + transferInput, + nodeValues, + analysisCaches); + } + default: + throw new BugInCF("Unknown direction: " + analysis.getDirection()); } - case BACKWARD: - return transferInput.getRegularStore(); - default: - throw new BugInCF("Unknown direction: " + analysis.getDirection()); } - } - - /** - * Returns the store immediately after a given {@link Tree}. - * - * @param tree a tree - * @return the store immediately after a given {@link Tree} - */ - public @Nullable S getStoreAfter(Tree tree) { - Set nodes = getNodesForTree(tree); - if (nodes == null) { - return null; + + /** + * Returns the regular store immediately after a given block. + * + * @param block a block + * @return the store after the given block + */ + public S getStoreAfter(Block block) { + TransferInput transferInput = stores.get(block); + assert transferInput != null + : "@AssumeAssertion(nullness): transferInput should be non-null"; + Analysis analysis = transferInput.analysis; + switch (analysis.getDirection()) { + case FORWARD: + Node lastNode = block.getLastNode(); + if (lastNode == null) { + // This block doesn't contain any node, return the store in the transfer input. + return transferInput.getRegularStore(); + } else { + return analysis.runAnalysisFor( + lastNode, + Analysis.BeforeOrAfter.AFTER, + transferInput, + nodeValues, + analysisCaches); + } + case BACKWARD: + return transferInput.getRegularStore(); + default: + throw new BugInCF("Unknown direction: " + analysis.getDirection()); + } } - S merged = null; - for (Node node : nodes) { - S s = getStoreAfter(node); - if (merged == null) { - merged = s; - } else if (s != null) { - merged = merged.leastUpperBound(s); - } + + /** + * Returns the store immediately after a given {@link Tree}. + * + * @param tree a tree + * @return the store immediately after a given {@link Tree} + */ + public @Nullable S getStoreAfter(Tree tree) { + Set nodes = getNodesForTree(tree); + if (nodes == null) { + return null; + } + S merged = null; + for (Node node : nodes) { + S s = getStoreAfter(node); + if (merged == null) { + merged = s; + } else if (s != null) { + merged = merged.leastUpperBound(s); + } + } + return merged; } - return merged; - } - - /** - * Returns the store immediately after a given {@link Node}. - * - * @param node a node - * @return the store immediately after a given {@link Node} - */ - public @Nullable S getStoreAfter(Node node) { - return runAnalysisFor(node, Analysis.BeforeOrAfter.AFTER); - } - - /** - * Runs the analysis again within the block of {@code node} and returns the store at the location - * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node} - * {@code node} is returned. Otherwise, the store after {@code node} is returned. - * - *

          If the given {@link Node} cannot be reached (in the control flow graph), then {@code null} - * is returned. - * - * @param node the node to analyze - * @param preOrPost which store to return: the store immediately before {@code node} or the store - * after {@code node} - * @return the store before or after {@code node} (depends on the value of {@code before}) after - * running the analysis - */ - protected @Nullable S runAnalysisFor(Node node, Analysis.BeforeOrAfter preOrPost) { - // block is null if node is a formal parameter of a method, or is a field access thereof - Block block = node.getBlock(); - assert block != null : "@AssumeAssertion(nullness): null block for node " + node; - TransferInput transferInput = stores.get(block); - if (transferInput == null) { - return null; + + /** + * Returns the store immediately after a given {@link Node}. + * + * @param node a node + * @return the store immediately after a given {@link Node} + */ + public @Nullable S getStoreAfter(Node node) { + return runAnalysisFor(node, Analysis.BeforeOrAfter.AFTER); } - // Calling Analysis.runAnalysisFor() may mutate the internal nodeValues map inside an - // AbstractAnalysis object, and by default the AnalysisResult constructor just wraps this - // map without copying it. So here the AnalysisResult maps must be copied, to preserve - // them. - // TODO: Wouldn't it be safer to do at the beginning of the called method? - copyMapsIfNeeded(); - return runAnalysisFor(node, preOrPost, transferInput, nodeValues, analysisCaches); - } - - /** - * Runs the analysis again within the block of {@code node} and returns the store at the location - * of {@code node}. If {@code before} is true, then the store immediately before the {@link Node} - * {@code node} is returned. Otherwise, the store immediately after {@code node} is returned. If - * {@code analysisCaches} is not null, this method uses a cache. {@code analysisCaches} is a map - * of a block of node to the cached analysis result. If the cache for {@code transferInput} is not - * in {@code analysisCaches}, this method creates new cache and stores it in {@code - * analysisCaches}. The cache is a map of nodes to the analysis results of the nodes. - * - * @param the abstract value type to be tracked by the analysis - * @param the store type used in the analysis - * @param node the node to analyze - * @param preOrPost which store to return: the store immediately before {@code node} or the store - * after {@code node} - * @param transferInput a transfer input - * @param nodeValues {@link #nodeValues} - * @param analysisCaches {@link #analysisCaches} - * @return the store before or after {@code node} (depends on the value of {@code before}) after - * running the analysis - */ - public static , S extends Store> S runAnalysisFor( - Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput transferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches) { - if (transferInput.analysis == null) { - throw new BugInCF("Analysis in transferInput cannot be null."); + + /** + * Runs the analysis again within the block of {@code node} and returns the store at the + * location of {@code node}. If {@code before} is true, then the store immediately before the + * {@link Node} {@code node} is returned. Otherwise, the store after {@code node} is returned. + * + *

          If the given {@link Node} cannot be reached (in the control flow graph), then {@code null} + * is returned. + * + * @param node the node to analyze + * @param preOrPost which store to return: the store immediately before {@code node} or the + * store after {@code node} + * @return the store before or after {@code node} (depends on the value of {@code before}) after + * running the analysis + */ + protected @Nullable S runAnalysisFor(Node node, Analysis.BeforeOrAfter preOrPost) { + // block is null if node is a formal parameter of a method, or is a field access thereof + Block block = node.getBlock(); + assert block != null : "@AssumeAssertion(nullness): null block for node " + node; + TransferInput transferInput = stores.get(block); + if (transferInput == null) { + return null; + } + // Calling Analysis.runAnalysisFor() may mutate the internal nodeValues map inside an + // AbstractAnalysis object, and by default the AnalysisResult constructor just wraps this + // map without copying it. So here the AnalysisResult maps must be copied, to preserve + // them. + // TODO: Wouldn't it be safer to do at the beginning of the called method? + copyMapsIfNeeded(); + return runAnalysisFor(node, preOrPost, transferInput, nodeValues, analysisCaches); } - return transferInput.analysis.runAnalysisFor( - node, preOrPost, transferInput, nodeValues, analysisCaches); - } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public String toStringDebug() { - StringJoiner result = - new StringJoiner( - String.format("%n "), String.format("AnalysisResult{%n "), String.format("%n}")); - result.add("nodeValues = " + nodeValuesToString(nodeValues)); - result.add("treeLookup = " + treeLookupToString(treeLookup)); - result.add("postfixLookup = " + postfixLookup); - result.add("finalLocalValues = " + finalLocalValues); - result.add("stores = " + stores); - result.add("analysisCaches = " + analysisCaches); - return result.toString(); - } - - /** - * Returns a verbose string representation, useful for debugging. The map has the same type as the - * {@code nodeValues} field. - * - * @param the type of values in the map - * @param nodeValues a map to format - * @return a printed representation of the given map - */ - public static String nodeValuesToString(Map nodeValues) { - if (nodeValues.isEmpty()) { - return "{}"; + + /** + * Runs the analysis again within the block of {@code node} and returns the store at the + * location of {@code node}. If {@code before} is true, then the store immediately before the + * {@link Node} {@code node} is returned. Otherwise, the store immediately after {@code node} is + * returned. If {@code analysisCaches} is not null, this method uses a cache. {@code + * analysisCaches} is a map of a block of node to the cached analysis result. If the cache for + * {@code transferInput} is not in {@code analysisCaches}, this method creates new cache and + * stores it in {@code analysisCaches}. The cache is a map of nodes to the analysis results of + * the nodes. + * + * @param the abstract value type to be tracked by the analysis + * @param the store type used in the analysis + * @param node the node to analyze + * @param preOrPost which store to return: the store immediately before {@code node} or the + * store after {@code node} + * @param transferInput a transfer input + * @param nodeValues {@link #nodeValues} + * @param analysisCaches {@link #analysisCaches} + * @return the store before or after {@code node} (depends on the value of {@code before}) after + * running the analysis + */ + public static , S extends Store> S runAnalysisFor( + Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput transferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches) { + if (transferInput.analysis == null) { + throw new BugInCF("Analysis in transferInput cannot be null."); + } + return transferInput.analysis.runAnalysisFor( + node, preOrPost, transferInput, nodeValues, analysisCaches); } - StringJoiner result = new StringJoiner(String.format("%n ")); - result.add("{"); - for (Map.Entry entry : nodeValues.entrySet()) { - Node key = entry.getKey(); - result.add(String.format("%s => %s", key.toStringDebug(), entry.getValue())); + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public String toStringDebug() { + StringJoiner result = + new StringJoiner( + String.format("%n "), + String.format("AnalysisResult{%n "), + String.format("%n}")); + result.add("nodeValues = " + nodeValuesToString(nodeValues)); + result.add("treeLookup = " + treeLookupToString(treeLookup)); + result.add("postfixLookup = " + postfixLookup); + result.add("finalLocalValues = " + finalLocalValues); + result.add("stores = " + stores); + result.add("analysisCaches = " + analysisCaches); + return result.toString(); } - result.add("}"); - return result.toString(); - } - - /** - * Returns a verbose string representation of a map, useful for debugging. The map has the same - * type as the {@code treeLookup} field. - * - * @param treeLookup a map to format - * @return a printed representation of the given map - */ - public static String treeLookupToString(Map> treeLookup) { - if (treeLookup.isEmpty()) { - return "{}"; + + /** + * Returns a verbose string representation, useful for debugging. The map has the same type as + * the {@code nodeValues} field. + * + * @param the type of values in the map + * @param nodeValues a map to format + * @return a printed representation of the given map + */ + public static String nodeValuesToString(Map nodeValues) { + if (nodeValues.isEmpty()) { + return "{}"; + } + StringJoiner result = new StringJoiner(String.format("%n ")); + result.add("{"); + for (Map.Entry entry : nodeValues.entrySet()) { + Node key = entry.getKey(); + result.add(String.format("%s => %s", key.toStringDebug(), entry.getValue())); + } + result.add("}"); + return result.toString(); } - StringJoiner result = new StringJoiner(String.format("%n ")); - result.add("{"); - for (Map.Entry> entry : treeLookup.entrySet()) { - Tree key = entry.getKey(); - result.add( - TreeUtils.toStringTruncated(key, 65) - + " => " - + Node.nodeCollectionToString(entry.getValue())); + + /** + * Returns a verbose string representation of a map, useful for debugging. The map has the same + * type as the {@code treeLookup} field. + * + * @param treeLookup a map to format + * @return a printed representation of the given map + */ + public static String treeLookupToString(Map> treeLookup) { + if (treeLookup.isEmpty()) { + return "{}"; + } + StringJoiner result = new StringJoiner(String.format("%n ")); + result.add("{"); + for (Map.Entry> entry : treeLookup.entrySet()) { + Tree key = entry.getKey(); + result.add( + TreeUtils.toStringTruncated(key, 65) + + " => " + + Node.nodeCollectionToString(entry.getValue())); + } + result.add("}"); + return result.toString(); } - result.add("}"); - return result.toString(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java index 91c768b4e2e..c59110d61f4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysis.java @@ -11,14 +11,17 @@ * @param the backward transfer function type that is used to approximate runtime behavior */ public interface BackwardAnalysis< - V extends AbstractValue, S extends Store, T extends BackwardTransferFunction> - extends Analysis { + V extends AbstractValue, + S extends Store, + T extends BackwardTransferFunction> + extends Analysis { - /** - * Get the output store at the entry block of a given control flow graph. For a backward analysis, - * the output store contains the analyzed flow information from the exit block to the entry block. - * - * @return the output store at the entry block of a given control flow graph - */ - @Nullable S getEntryStore(); + /** + * Get the output store at the entry block of a given control flow graph. For a backward + * analysis, the output store contains the analyzed flow information from the exit block to the + * entry block. + * + * @return the output store at the entry block of a given control flow graph + */ + @Nullable S getEntryStore(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java index 7b0eec9c58a..f6e63262977 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java @@ -1,11 +1,5 @@ package org.checkerframework.dataflow.analysis; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -22,6 +16,14 @@ import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.checkerframework.javacutil.BugInCF; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + /** * An implementation of a backward analysis to solve a org.checkerframework.dataflow problem given a * control flow graph and a backward transfer function. @@ -31,378 +33,391 @@ * @param the transfer function type that is used to approximate runtime behavior */ public class BackwardAnalysisImpl< - V extends AbstractValue, S extends Store, T extends BackwardTransferFunction> - extends AbstractAnalysis implements BackwardAnalysis { + V extends AbstractValue, + S extends Store, + T extends BackwardTransferFunction> + extends AbstractAnalysis implements BackwardAnalysis { - // TODO: Add widening support like what the forward analysis does. + // TODO: Add widening support like what the forward analysis does. - /** Out stores after every basic block (assumed to be 'no information' if not present). */ - protected final IdentityHashMap outStores = new IdentityHashMap<>(); + /** Out stores after every basic block (assumed to be 'no information' if not present). */ + protected final IdentityHashMap outStores = new IdentityHashMap<>(); - /** - * Exception store of an exception block, propagated by exceptional successors of its exception - * block, and merged with the normal {@link TransferResult}. - */ - protected final IdentityHashMap exceptionStores = new IdentityHashMap<>(); + /** + * Exception store of an exception block, propagated by exceptional successors of its exception + * block, and merged with the normal {@link TransferResult}. + */ + protected final IdentityHashMap exceptionStores = new IdentityHashMap<>(); - /** The store right before the entry block. */ - protected @Nullable S storeAtEntry = null; + /** The store right before the entry block. */ + protected @Nullable S storeAtEntry = null; - // `@code`, not `@link`, because dataflow module doesn't depend on framework module. - /** - * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a - * control flow graph. When using this constructor, the transfer function is set later by the - * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. - */ - public BackwardAnalysisImpl() { - super(Direction.BACKWARD); - } - - /** - * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a - * control flow graph given a transfer function. - * - * @param transferFunction the transfer function - */ - public BackwardAnalysisImpl(T transferFunction) { - this(); - this.transferFunction = transferFunction; - } - - @Override - public void performAnalysis(ControlFlowGraph cfg) { - if (isRunning) { - throw new BugInCF("performAnalysis() shouldn't be called when the analysis is running."); + // `@code`, not `@link`, because dataflow module doesn't depend on framework module. + /** + * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a + * control flow graph. When using this constructor, the transfer function is set later by the + * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. + */ + public BackwardAnalysisImpl() { + super(Direction.BACKWARD); } - isRunning = true; - try { - init(cfg); - while (!worklist.isEmpty()) { - Block b = worklist.poll(); - performAnalysisBlock(b); - } - } finally { - assert isRunning; - // In case performAnalysisBlock crashed, reset isRunning to false. - isRunning = false; + + /** + * Construct an object that can perform a org.checkerframework.dataflow backward analysis over a + * control flow graph given a transfer function. + * + * @param transferFunction the transfer function + */ + public BackwardAnalysisImpl(T transferFunction) { + this(); + this.transferFunction = transferFunction; } - } - @Override - public void performAnalysisBlock(Block b) { - switch (b.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rb = (RegularBlock) b; - TransferInput inputAfter = getInput(rb); - assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputAfter.copy(); - Node firstNode = null; - boolean addToWorklistAgain = false; - List nodeList = rb.getNodes(); - ListIterator reverseIter = nodeList.listIterator(nodeList.size()); - while (reverseIter.hasPrevious()) { - Node node = reverseIter.previous(); - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - TransferResult transferResult = callTransferFunction(node, currentInput); - addToWorklistAgain |= updateNodeValues(node, transferResult); - currentInput = new TransferInput<>(node, this, transferResult); - firstNode = node; - } - // Propagate store to predecessors - for (Block pred : rb.getPredecessors()) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - propagateStoresTo( - pred, firstNode, currentInput, FlowRule.EACH_TO_EACH, addToWorklistAgain); - } - break; - } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) b; - TransferInput inputAfter = getInput(eb); - assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputAfter.copy(); - Node node = eb.getNode(); - TransferResult transferResult = callTransferFunction(node, currentInput); - boolean addToWorklistAgain = updateNodeValues(node, transferResult); - // Merge transferResult with exceptionStore if there exists one - S exceptionStore = exceptionStores.get(eb); - S mergedStore = - exceptionStore != null - ? transferResult.getRegularStore().leastUpperBound(exceptionStore) - : transferResult.getRegularStore(); - for (Block pred : eb.getPredecessors()) { - addStoreAfter(pred, node, mergedStore, addToWorklistAgain); - } - break; - } - case CONDITIONAL_BLOCK: - { - ConditionalBlock cb = (ConditionalBlock) b; - TransferInput inputAfter = getInput(cb); - assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; - TransferInput input = inputAfter.copy(); - for (Block pred : cb.getPredecessors()) { - propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); - } - break; + @Override + public void performAnalysis(ControlFlowGraph cfg) { + if (isRunning) { + throw new BugInCF( + "performAnalysis() shouldn't be called when the analysis is running."); } - case SPECIAL_BLOCK: - { - // Special basic blocks are empty and cannot throw exceptions, - // thus there is no need to perform any analysis. - SpecialBlock sb = (SpecialBlock) b; - SpecialBlockType sType = sb.getSpecialType(); - if (sType == SpecialBlockType.ENTRY) { - // storage the store at entry - storeAtEntry = outStores.get(sb); - } else { - assert sType == SpecialBlockType.EXIT || sType == SpecialBlockType.EXCEPTIONAL_EXIT; - TransferInput input = getInput(sb); - assert input != null : "@AssumeAssertion(nullness): invariant"; - for (Block pred : sb.getPredecessors()) { - propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); + isRunning = true; + try { + init(cfg); + while (!worklist.isEmpty()) { + Block b = worklist.poll(); + performAnalysisBlock(b); } - } - break; + } finally { + assert isRunning; + // In case performAnalysisBlock crashed, reset isRunning to false. + isRunning = false; } - default: - throw new BugInCF("Unexpected block type: " + b.getType()); } - } - @Override - public @Nullable TransferInput getInput(Block b) { - return inputs.get(b); - } - - @Override - public @Nullable S getEntryStore() { - return storeAtEntry; - } - - @Override - protected void initFields(ControlFlowGraph cfg) { - super.initFields(cfg); - outStores.clear(); - exceptionStores.clear(); - // storeAtEntry is null before analysis begin - storeAtEntry = null; - } - - @Override - @RequiresNonNull("cfg") - protected void initInitialInputs() { - worklist.process(cfg); - SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); - SpecialBlock exceptionExitBlock = cfg.getExceptionalExitBlock(); - if (worklist.depthFirstOrder.get(regularExitBlock) == null - && worklist.depthFirstOrder.get(exceptionExitBlock) == null) { - throw new BugInCF( - "regularExitBlock and exceptionExitBlock should never both be null at the same" - + " time."); - } - UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); - List returnNodes = cfg.getReturnNodes(); - assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; - S normalInitialStore = transferFunction.initialNormalExitStore(underlyingAST, returnNodes); - S exceptionalInitialStore = transferFunction.initialExceptionalExitStore(underlyingAST); - // If regularExitBlock or exceptionExitBlock is reachable in the control flow graph, then - // initialize it as a start point of the analysis. - if (worklist.depthFirstOrder.get(regularExitBlock) != null) { - worklist.add(regularExitBlock); - inputs.put(regularExitBlock, new TransferInput<>(null, this, normalInitialStore)); - outStores.put(regularExitBlock, normalInitialStore); + @Override + public void performAnalysisBlock(Block b) { + switch (b.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rb = (RegularBlock) b; + TransferInput inputAfter = getInput(rb); + assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputAfter.copy(); + Node firstNode = null; + boolean addToWorklistAgain = false; + List nodeList = rb.getNodes(); + ListIterator reverseIter = nodeList.listIterator(nodeList.size()); + while (reverseIter.hasPrevious()) { + Node node = reverseIter.previous(); + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + TransferResult transferResult = + callTransferFunction(node, currentInput); + addToWorklistAgain |= updateNodeValues(node, transferResult); + currentInput = new TransferInput<>(node, this, transferResult); + firstNode = node; + } + // Propagate store to predecessors + for (Block pred : rb.getPredecessors()) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + propagateStoresTo( + pred, + firstNode, + currentInput, + FlowRule.EACH_TO_EACH, + addToWorklistAgain); + } + break; + } + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) b; + TransferInput inputAfter = getInput(eb); + assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputAfter.copy(); + Node node = eb.getNode(); + TransferResult transferResult = callTransferFunction(node, currentInput); + boolean addToWorklistAgain = updateNodeValues(node, transferResult); + // Merge transferResult with exceptionStore if there exists one + S exceptionStore = exceptionStores.get(eb); + S mergedStore = + exceptionStore != null + ? transferResult + .getRegularStore() + .leastUpperBound(exceptionStore) + : transferResult.getRegularStore(); + for (Block pred : eb.getPredecessors()) { + addStoreAfter(pred, node, mergedStore, addToWorklistAgain); + } + break; + } + case CONDITIONAL_BLOCK: + { + ConditionalBlock cb = (ConditionalBlock) b; + TransferInput inputAfter = getInput(cb); + assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; + TransferInput input = inputAfter.copy(); + for (Block pred : cb.getPredecessors()) { + propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); + } + break; + } + case SPECIAL_BLOCK: + { + // Special basic blocks are empty and cannot throw exceptions, + // thus there is no need to perform any analysis. + SpecialBlock sb = (SpecialBlock) b; + SpecialBlockType sType = sb.getSpecialType(); + if (sType == SpecialBlockType.ENTRY) { + // storage the store at entry + storeAtEntry = outStores.get(sb); + } else { + assert sType == SpecialBlockType.EXIT + || sType == SpecialBlockType.EXCEPTIONAL_EXIT; + TransferInput input = getInput(sb); + assert input != null : "@AssumeAssertion(nullness): invariant"; + for (Block pred : sb.getPredecessors()) { + propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); + } + } + break; + } + default: + throw new BugInCF("Unexpected block type: " + b.getType()); + } } - if (worklist.depthFirstOrder.get(exceptionExitBlock) != null) { - worklist.add(exceptionExitBlock); - inputs.put(exceptionExitBlock, new TransferInput<>(null, this, exceptionalInitialStore)); - outStores.put(exceptionExitBlock, exceptionalInitialStore); + + @Override + public @Nullable TransferInput getInput(Block b) { + return inputs.get(b); } - if (worklist.isEmpty()) { - throw new BugInCF("The worklist needs at least one exit block as starting point."); + + @Override + public @Nullable S getEntryStore() { + return storeAtEntry; } - if (inputs.isEmpty() || outStores.isEmpty()) { - throw new BugInCF("At least one input and one output store are required."); + + @Override + protected void initFields(ControlFlowGraph cfg) { + super.initFields(cfg); + outStores.clear(); + exceptionStores.clear(); + // storeAtEntry is null before analysis begin + storeAtEntry = null; } - } - @Override - protected void propagateStoresTo( - Block pred, - @Nullable Node node, - TransferInput currentInput, - FlowRule flowRule, - boolean addToWorklistAgain) { - if (flowRule != FlowRule.EACH_TO_EACH) { - throw new BugInCF( - "Backward analysis always propagates EACH to EACH, because there is no control" - + " flow."); + @Override + @RequiresNonNull("cfg") + protected void initInitialInputs() { + worklist.process(cfg); + SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); + SpecialBlock exceptionExitBlock = cfg.getExceptionalExitBlock(); + if (worklist.depthFirstOrder.get(regularExitBlock) == null + && worklist.depthFirstOrder.get(exceptionExitBlock) == null) { + throw new BugInCF( + "regularExitBlock and exceptionExitBlock should never both be null at the same" + + " time."); + } + UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); + List returnNodes = cfg.getReturnNodes(); + assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; + S normalInitialStore = transferFunction.initialNormalExitStore(underlyingAST, returnNodes); + S exceptionalInitialStore = transferFunction.initialExceptionalExitStore(underlyingAST); + // If regularExitBlock or exceptionExitBlock is reachable in the control flow graph, then + // initialize it as a start point of the analysis. + if (worklist.depthFirstOrder.get(regularExitBlock) != null) { + worklist.add(regularExitBlock); + inputs.put(regularExitBlock, new TransferInput<>(null, this, normalInitialStore)); + outStores.put(regularExitBlock, normalInitialStore); + } + if (worklist.depthFirstOrder.get(exceptionExitBlock) != null) { + worklist.add(exceptionExitBlock); + inputs.put( + exceptionExitBlock, new TransferInput<>(null, this, exceptionalInitialStore)); + outStores.put(exceptionExitBlock, exceptionalInitialStore); + } + if (worklist.isEmpty()) { + throw new BugInCF("The worklist needs at least one exit block as starting point."); + } + if (inputs.isEmpty() || outStores.isEmpty()) { + throw new BugInCF("At least one input and one output store are required."); + } } - addStoreAfter(pred, node, currentInput.getRegularStore(), addToWorklistAgain); - } + @Override + protected void propagateStoresTo( + Block pred, + @Nullable Node node, + TransferInput currentInput, + FlowRule flowRule, + boolean addToWorklistAgain) { + if (flowRule != FlowRule.EACH_TO_EACH) { + throw new BugInCF( + "Backward analysis always propagates EACH to EACH, because there is no control" + + " flow."); + } - /** - * Add a store after the basic block {@code pred} by merging with the existing stores for that - * location. - * - * @param pred the basic block - * @param node the node of the basic block {@code b} - * @param s the store being added - * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code - * Worklist} - */ - protected void addStoreAfter(Block pred, @Nullable Node node, S s, boolean addBlockToWorklist) { - // If the block pred is an exception block, decide whether the block of passing node is an - // exceptional successor of the block pred - TypeMirror excSuccType = getSuccExceptionType(pred, node); - if (excSuccType != null) { - if (isIgnoredExceptionType(excSuccType)) { - return; - } - // If the block of passing node is an exceptional successor of Block pred, propagate - // store to the exceptionStores. Currently it doesn't track the label of an - // exceptional edge from exception block to its exceptional successors in backward - // direction. Instead, all exception stores of exceptional successors of an - // exception block will merge to one exception store at the exception block - ExceptionBlock ebPred = (ExceptionBlock) pred; - S exceptionStore = exceptionStores.get(ebPred); - S newExceptionStore = (exceptionStore != null) ? exceptionStore.leastUpperBound(s) : s; - if (!newExceptionStore.equals(exceptionStore)) { - exceptionStores.put(ebPred, newExceptionStore); - inputs.put(ebPred, new TransferInput(node, this, newExceptionStore)); - addBlockToWorklist = true; - } - } else { - S predOutStore = getStoreAfter(pred); - S newPredOutStore = (predOutStore != null) ? predOutStore.leastUpperBound(s) : s; - if (!newPredOutStore.equals(predOutStore)) { - outStores.put(pred, newPredOutStore); - inputs.put(pred, new TransferInput<>(node, this, newPredOutStore)); - addBlockToWorklist = true; - } - } - if (addBlockToWorklist) { - addToWorklist(pred); + addStoreAfter(pred, node, currentInput.getRegularStore(), addToWorklistAgain); } - } - /** - * Checks if the block for a node is an exceptional successor of a predecessor block, and if so, - * returns the exception type for the control-flow edge. - * - * @param pred the predecessor block - * @param node the successor node - * @return the exception type leading to a control flow edge from {@code pred} to the block for - * {@code node}, if it exists; {@code null} otherwise - */ - @SuppressWarnings("interning:not.interned") // Block equality - private @Nullable TypeMirror getSuccExceptionType(Block pred, @Nullable Node node) { - if (!(pred instanceof ExceptionBlock) || node == null) { - return null; - } - Block block = node.getBlock(); - if (block == null) { - return null; - } - Map> exceptionalSuccessors = - ((ExceptionBlock) pred).getExceptionalSuccessors(); - for (Map.Entry> excTypeEntry : exceptionalSuccessors.entrySet()) { - for (Block excSuccBlock : excTypeEntry.getValue()) { - if (excSuccBlock == block) { - return excTypeEntry.getKey(); + /** + * Add a store after the basic block {@code pred} by merging with the existing stores for that + * location. + * + * @param pred the basic block + * @param node the node of the basic block {@code b} + * @param s the store being added + * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code + * Worklist} + */ + protected void addStoreAfter(Block pred, @Nullable Node node, S s, boolean addBlockToWorklist) { + // If the block pred is an exception block, decide whether the block of passing node is an + // exceptional successor of the block pred + TypeMirror excSuccType = getSuccExceptionType(pred, node); + if (excSuccType != null) { + if (isIgnoredExceptionType(excSuccType)) { + return; + } + // If the block of passing node is an exceptional successor of Block pred, propagate + // store to the exceptionStores. Currently it doesn't track the label of an + // exceptional edge from exception block to its exceptional successors in backward + // direction. Instead, all exception stores of exceptional successors of an + // exception block will merge to one exception store at the exception block + ExceptionBlock ebPred = (ExceptionBlock) pred; + S exceptionStore = exceptionStores.get(ebPred); + S newExceptionStore = (exceptionStore != null) ? exceptionStore.leastUpperBound(s) : s; + if (!newExceptionStore.equals(exceptionStore)) { + exceptionStores.put(ebPred, newExceptionStore); + inputs.put(ebPred, new TransferInput(node, this, newExceptionStore)); + addBlockToWorklist = true; + } + } else { + S predOutStore = getStoreAfter(pred); + S newPredOutStore = (predOutStore != null) ? predOutStore.leastUpperBound(s) : s; + if (!newPredOutStore.equals(predOutStore)) { + outStores.put(pred, newPredOutStore); + inputs.put(pred, new TransferInput<>(node, this, newPredOutStore)); + addBlockToWorklist = true; + } + } + if (addBlockToWorklist) { + addToWorklist(pred); } - } } - return null; - } - /** - * Returns the store corresponding to the location right after the basic block {@code b}. - * - * @param b the given block - * @return the store right after the given block - */ - protected @Nullable S getStoreAfter(Block b) { - return readFromStore(outStores, b); - } + /** + * Checks if the block for a node is an exceptional successor of a predecessor block, and if so, + * returns the exception type for the control-flow edge. + * + * @param pred the predecessor block + * @param node the successor node + * @return the exception type leading to a control flow edge from {@code pred} to the block for + * {@code node}, if it exists; {@code null} otherwise + */ + @SuppressWarnings("interning:not.interned") // Block equality + private @Nullable TypeMirror getSuccExceptionType(Block pred, @Nullable Node node) { + if (!(pred instanceof ExceptionBlock) || node == null) { + return null; + } + Block block = node.getBlock(); + if (block == null) { + return null; + } + Map> exceptionalSuccessors = + ((ExceptionBlock) pred).getExceptionalSuccessors(); + for (Map.Entry> excTypeEntry : exceptionalSuccessors.entrySet()) { + for (Block excSuccBlock : excTypeEntry.getValue()) { + if (excSuccBlock == block) { + return excTypeEntry.getKey(); + } + } + } + return null; + } - @Override - public S runAnalysisFor( - @FindDistinct Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput blockTransferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches) { - Block block = node.getBlock(); - assert block != null : "@AssumeAssertion(nullness): invariant"; - Node oldCurrentNode = currentNode; - if (isRunning) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - return currentInput.getRegularStore(); + /** + * Returns the store corresponding to the location right after the basic block {@code b}. + * + * @param b the given block + * @return the store right after the given block + */ + protected @Nullable S getStoreAfter(Block b) { + return readFromStore(outStores, b); } - isRunning = true; - try { - switch (block.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rBlock = (RegularBlock) block; - // Apply transfer function to contents until we found the node we are - // looking for. - TransferInput store = blockTransferInput; - List nodeList = rBlock.getNodes(); - ListIterator reverseIter = nodeList.listIterator(nodeList.size()); - while (reverseIter.hasPrevious()) { - Node n = reverseIter.previous(); - setCurrentNode(n); - if (n == node && preOrPost == Analysis.BeforeOrAfter.AFTER) { - return store.getRegularStore(); - } - // Copy the store to avoid changing other blocks' transfer inputs in - // {@link #inputs} - TransferResult transferResult = callTransferFunction(n, store.copy()); - if (n == node) { - return transferResult.getRegularStore(); - } - store = new TransferInput<>(n, this, transferResult); - } - throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); - } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) block; - if (eb.getNode() != node) { - throw new BugInCF( - "Node should be equal to eb.getNode(). But get: node: " - + node - + "\teb.getNode(): " - + eb.getNode()); - } - if (preOrPost == Analysis.BeforeOrAfter.AFTER) { - return blockTransferInput.getRegularStore(); + + @Override + public S runAnalysisFor( + @FindDistinct Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput blockTransferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches) { + Block block = node.getBlock(); + assert block != null : "@AssumeAssertion(nullness): invariant"; + Node oldCurrentNode = currentNode; + if (isRunning) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + return currentInput.getRegularStore(); + } + isRunning = true; + try { + switch (block.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rBlock = (RegularBlock) block; + // Apply transfer function to contents until we found the node we are + // looking for. + TransferInput store = blockTransferInput; + List nodeList = rBlock.getNodes(); + ListIterator reverseIter = nodeList.listIterator(nodeList.size()); + while (reverseIter.hasPrevious()) { + Node n = reverseIter.previous(); + setCurrentNode(n); + if (n == node && preOrPost == Analysis.BeforeOrAfter.AFTER) { + return store.getRegularStore(); + } + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} + TransferResult transferResult = + callTransferFunction(n, store.copy()); + if (n == node) { + return transferResult.getRegularStore(); + } + store = new TransferInput<>(n, this, transferResult); + } + throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); + } + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) block; + if (eb.getNode() != node) { + throw new BugInCF( + "Node should be equal to eb.getNode(). But get: node: " + + node + + "\teb.getNode(): " + + eb.getNode()); + } + if (preOrPost == Analysis.BeforeOrAfter.AFTER) { + return blockTransferInput.getRegularStore(); + } + setCurrentNode(node); + // Copy the store to avoid changing other blocks' transfer inputs in {@link + // #inputs} + TransferResult transferResult = + callTransferFunction(node, blockTransferInput.copy()); + // Merge transfer result with the exception store of this exceptional block + S exceptionStore = exceptionStores.get(eb); + return exceptionStore == null + ? transferResult.getRegularStore() + : transferResult.getRegularStore().leastUpperBound(exceptionStore); + } + default: + // Only regular blocks and exceptional blocks can hold nodes. + throw new BugInCF("Unexpected block type: " + block.getType()); } - setCurrentNode(node); - // Copy the store to avoid changing other blocks' transfer inputs in {@link - // #inputs} - TransferResult transferResult = - callTransferFunction(node, blockTransferInput.copy()); - // Merge transfer result with the exception store of this exceptional block - S exceptionStore = exceptionStores.get(eb); - return exceptionStore == null - ? transferResult.getRegularStore() - : transferResult.getRegularStore().leastUpperBound(exceptionStore); - } - default: - // Only regular blocks and exceptional blocks can hold nodes. - throw new BugInCF("Unexpected block type: " + block.getType()); - } - } finally { - setCurrentNode(oldCurrentNode); - isRunning = false; + } finally { + setCurrentNode(oldCurrentNode); + isRunning = false; + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java index 28c0bfa9c8c..e1c3e070172 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardTransferFunction.java @@ -1,10 +1,11 @@ package org.checkerframework.dataflow.analysis; -import java.util.List; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.checkerframework.dataflow.qual.SideEffectFree; +import java.util.List; + /** * Interface of a backward transfer function for the abstract interpretation used for the backward * flow analysis. @@ -17,26 +18,26 @@ * @param the store type used in the analysis */ public interface BackwardTransferFunction, S extends Store> - extends TransferFunction { + extends TransferFunction { - /** - * Returns the initial store that should be used at the normal exit block. - * - * @param underlyingAST the underlying AST of the given control flow graph - * @param returnNodes the return nodes of the given control flow graph (an empty list if the - * underlying AST is not a method) - * @return the initial store that should be used at the normal exit block - */ - @SideEffectFree - S initialNormalExitStore(UnderlyingAST underlyingAST, List returnNodes); + /** + * Returns the initial store that should be used at the normal exit block. + * + * @param underlyingAST the underlying AST of the given control flow graph + * @param returnNodes the return nodes of the given control flow graph (an empty list if the + * underlying AST is not a method) + * @return the initial store that should be used at the normal exit block + */ + @SideEffectFree + S initialNormalExitStore(UnderlyingAST underlyingAST, List returnNodes); - /** - * Returns the initial store that should be used at the exceptional exit block or given the - * underlying AST of a control flow graph. - * - * @param underlyingAST the underlying AST of the given control flow graph - * @return the initial store that should be used at the exceptional exit block - */ - @SideEffectFree - S initialExceptionalExitStore(UnderlyingAST underlyingAST); + /** + * Returns the initial store that should be used at the exceptional exit block or given the + * underlying AST of a control flow graph. + * + * @param underlyingAST the underlying AST of the given control flow graph + * @return the initial store that should be used at the exceptional exit block + */ + @SideEffectFree + S initialExceptionalExitStore(UnderlyingAST underlyingAST); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java index 59ba8fe6683..2cda1f9b1d8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java @@ -1,10 +1,12 @@ package org.checkerframework.dataflow.analysis; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.StringsPlume; + import java.util.Map; import java.util.StringJoiner; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.StringsPlume; /** * Implementation of a {@link TransferResult} with two non-exceptional stores. The 'then' store @@ -17,138 +19,139 @@ * @param the store type used in the analysis */ public class ConditionalTransferResult, S extends Store> - extends TransferResult { - - /** Whether the store changed. */ - private final boolean storeChanged; - - /** The 'then' result store. */ - protected final S thenStore; - - /** The 'else' result store. */ - protected final S elseStore; - - /** - * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, - * using {@code null} for {@link #exceptionalStores}. - * - *

          Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no - * special handling is necessary and the store before the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. - * - *

          Aliasing: {@code thenStore} and {@code elseStore} are not allowed to be used - * anywhere outside of this class (including use through aliases). Complete control over the - * objects is transferred to this class. - * - * @param value the abstract value produced by the transfer function - * @param thenStore 'then' result store - * @param elseStore 'else' result store - * @param storeChanged whether the store changed - * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) - */ - public ConditionalTransferResult( - @Nullable V value, S thenStore, S elseStore, boolean storeChanged) { - this(value, thenStore, elseStore, null, storeChanged); - } - - /** - * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, - * using {@code false} for whether the store changed and {@code null} for {@link - * #exceptionalStores}. - * - * @param value the abstract value produced by the transfer function - * @param thenStore {@link #thenStore} - * @param elseStore {@link #elseStore} - * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) - */ - public ConditionalTransferResult(@Nullable V value, S thenStore, S elseStore) { - this(value, thenStore, elseStore, false); - } - - /** - * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, - * using {@code false} for the {@code storeChanged} formal parameter. - * - * @param value the abstract value produced by the transfer function - * @param thenStore {@link #thenStore} - * @param elseStore {@link #elseStore} - * @param exceptionalStores {@link #exceptionalStores} - * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) - */ - public ConditionalTransferResult( - V value, S thenStore, S elseStore, @Nullable Map exceptionalStores) { - this(value, thenStore, elseStore, exceptionalStores, false); - } - - /** - * Create a {@code ConditionalTransferResult} with {@code thenStore} as the resulting store if the - * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} evaluates to {@code true} and - * {@code elseStore} otherwise. - * - *

          Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding store - * in {@code exceptionalStores} is used. If no exception is found in {@code exceptionalStores}, - * then it is assumed that no special handling is necessary and the store before the corresponding - * {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. - * - *

          Aliasing: {@code thenStore}, {@code elseStore}, and any store in {@code - * exceptionalStores} are not allowed to be used anywhere outside of this class (including use - * through aliases). Complete control over the objects is transferred to this class. - * - * @param value the abstract value produced by the transfer function - * @param thenStore {@link #thenStore} - * @param elseStore {@link #elseStore} - * @param exceptionalStores {@link #exceptionalStores} - * @param storeChanged whether the store changed; see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - */ - public ConditionalTransferResult( - @Nullable V value, - S thenStore, - S elseStore, - @Nullable Map exceptionalStores, - boolean storeChanged) { - super(value, exceptionalStores); - this.thenStore = thenStore; - this.elseStore = elseStore; - this.storeChanged = storeChanged; - } - - /** The regular result store. */ - @Override - public S getRegularStore() { - return thenStore.leastUpperBound(elseStore); - } - - @Override - public S getThenStore() { - return thenStore; - } - - @Override - public S getElseStore() { - return elseStore; - } - - @Override - public boolean containsTwoStores() { - return true; - } - - @Override - public String toString() { - StringJoiner result = new StringJoiner(System.lineSeparator()); - result.add("RegularTransferResult("); - result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); - result.add(" thenStore = " + StringsPlume.indentLinesExceptFirst(2, thenStore)); - result.add(" elseStore = " + StringsPlume.indentLinesExceptFirst(2, elseStore)); - result.add(")"); - return result.toString(); - } - - @Override - public boolean storeChanged() { - return storeChanged; - } + extends TransferResult { + + /** Whether the store changed. */ + private final boolean storeChanged; + + /** The 'then' result store. */ + protected final S thenStore; + + /** The 'else' result store. */ + protected final S elseStore; + + /** + * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, + * using {@code null} for {@link #exceptionalStores}. + * + *

          Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no + * special handling is necessary and the store before the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + *

          Aliasing: {@code thenStore} and {@code elseStore} are not allowed to be used + * anywhere outside of this class (including use through aliases). Complete control over the + * objects is transferred to this class. + * + * @param value the abstract value produced by the transfer function + * @param thenStore 'then' result store + * @param elseStore 'else' result store + * @param storeChanged whether the store changed + * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) + */ + public ConditionalTransferResult( + @Nullable V value, S thenStore, S elseStore, boolean storeChanged) { + this(value, thenStore, elseStore, null, storeChanged); + } + + /** + * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, + * using {@code false} for whether the store changed and {@code null} for {@link + * #exceptionalStores}. + * + * @param value the abstract value produced by the transfer function + * @param thenStore {@link #thenStore} + * @param elseStore {@link #elseStore} + * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) + */ + public ConditionalTransferResult(@Nullable V value, S thenStore, S elseStore) { + this(value, thenStore, elseStore, false); + } + + /** + * Create a new {@link #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean)}, + * using {@code false} for the {@code storeChanged} formal parameter. + * + * @param value the abstract value produced by the transfer function + * @param thenStore {@link #thenStore} + * @param elseStore {@link #elseStore} + * @param exceptionalStores {@link #exceptionalStores} + * @see #ConditionalTransferResult(AbstractValue, Store, Store, Map, boolean) + */ + public ConditionalTransferResult( + V value, S thenStore, S elseStore, @Nullable Map exceptionalStores) { + this(value, thenStore, elseStore, exceptionalStores, false); + } + + /** + * Create a {@code ConditionalTransferResult} with {@code thenStore} as the resulting store if + * the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} evaluates to {@code + * true} and {@code elseStore} otherwise. + * + *

          Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding + * store in {@code exceptionalStores} is used. If no exception is found in {@code + * exceptionalStores}, then it is assumed that no special handling is necessary and the store + * before the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed + * along any exceptional edge. + * + *

          Aliasing: {@code thenStore}, {@code elseStore}, and any store in {@code + * exceptionalStores} are not allowed to be used anywhere outside of this class (including use + * through aliases). Complete control over the objects is transferred to this class. + * + * @param value the abstract value produced by the transfer function + * @param thenStore {@link #thenStore} + * @param elseStore {@link #elseStore} + * @param exceptionalStores {@link #exceptionalStores} + * @param storeChanged whether the store changed; see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + */ + public ConditionalTransferResult( + @Nullable V value, + S thenStore, + S elseStore, + @Nullable Map exceptionalStores, + boolean storeChanged) { + super(value, exceptionalStores); + this.thenStore = thenStore; + this.elseStore = elseStore; + this.storeChanged = storeChanged; + } + + /** The regular result store. */ + @Override + public S getRegularStore() { + return thenStore.leastUpperBound(elseStore); + } + + @Override + public S getThenStore() { + return thenStore; + } + + @Override + public S getElseStore() { + return elseStore; + } + + @Override + public boolean containsTwoStores() { + return true; + } + + @Override + public String toString() { + StringJoiner result = new StringJoiner(System.lineSeparator()); + result.add("RegularTransferResult("); + result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); + result.add(" thenStore = " + StringsPlume.indentLinesExceptFirst(2, thenStore)); + result.add(" elseStore = " + StringsPlume.indentLinesExceptFirst(2, elseStore)); + result.add(")"); + return result.toString(); + } + + @Override + public boolean storeChanged() { + return storeChanged; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java index 68d81e27f2d..9e60cc62565 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysis.java @@ -1,10 +1,11 @@ package org.checkerframework.dataflow.analysis; -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.plumelib.util.IPair; +import java.util.List; + /** * This interface defines a forward analysis, given a control flow graph and a forward transfer * function. @@ -14,15 +15,17 @@ * @param the forward transfer function type that is used to approximated runtime behavior */ public interface ForwardAnalysis< - V extends AbstractValue, S extends Store, T extends ForwardTransferFunction> - extends Analysis { + V extends AbstractValue, + S extends Store, + T extends ForwardTransferFunction> + extends Analysis { - /** - * Get stores at return statements. These stores are transfer results at return node. Thus for a - * forward analysis, these stores contain the analyzed flow information from entry nodes to return - * nodes. - * - * @return the transfer results for each return node in the CFG - */ - List>> getReturnStatementStores(); + /** + * Get stores at return statements. These stores are transfer results at return node. Thus for a + * forward analysis, these stores contain the analyzed flow information from entry nodes to + * return nodes. + * + * @return the transfer results for each return node in the CFG + */ + List>> getReturnStatementStores(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java index 4c939757a52..a3fba8c5864 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java @@ -2,12 +2,7 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MethodTree; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -28,6 +23,14 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + /** * An implementation of a forward analysis to solve a org.checkerframework.dataflow problem given a * control flow graph and a forward transfer function. @@ -37,522 +40,564 @@ * @param the transfer function type that is used to approximate runtime behavior */ public class ForwardAnalysisImpl< - V extends AbstractValue, S extends Store, T extends ForwardTransferFunction> - extends AbstractAnalysis implements ForwardAnalysis { - - /** - * Number of times each block has been analyzed since the last time widening was applied. Null if - * maxCountBeforeWidening is -1, which implies widening isn't used for this analysis. - */ - protected final @Nullable IdentityHashMap blockCount; - - /** - * Number of times a block can be analyzed before widening. -1 implies that widening shouldn't be - * used. - */ - protected final int maxCountBeforeWidening; + V extends AbstractValue, + S extends Store, + T extends ForwardTransferFunction> + extends AbstractAnalysis implements ForwardAnalysis { - /** Then stores before every basic block (assumed to be 'no information' if not present). */ - protected final IdentityHashMap thenStores; + /** + * Number of times each block has been analyzed since the last time widening was applied. Null + * if maxCountBeforeWidening is -1, which implies widening isn't used for this analysis. + */ + protected final @Nullable IdentityHashMap blockCount; - /** Else stores before every basic block (assumed to be 'no information' if not present). */ - protected final IdentityHashMap elseStores; + /** + * Number of times a block can be analyzed before widening. -1 implies that widening shouldn't + * be used. + */ + protected final int maxCountBeforeWidening; - /** The stores after every return statement. */ - protected final IdentityHashMap> storesAtReturnStatements; + /** Then stores before every basic block (assumed to be 'no information' if not present). */ + protected final IdentityHashMap thenStores; - // `@code`, not `@link`, because dataflow module doesn't depend on framework module. - /** - * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a - * control flow graph. When using this constructor, the transfer function is set later by the - * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. - * - * @param maxCountBeforeWidening number of times a block can be analyzed before widening - */ - public ForwardAnalysisImpl(int maxCountBeforeWidening) { - super(Direction.FORWARD); - this.maxCountBeforeWidening = maxCountBeforeWidening; - this.blockCount = maxCountBeforeWidening == -1 ? null : new IdentityHashMap<>(); - this.thenStores = new IdentityHashMap<>(); - this.elseStores = new IdentityHashMap<>(); - this.storesAtReturnStatements = new IdentityHashMap<>(); - } + /** Else stores before every basic block (assumed to be 'no information' if not present). */ + protected final IdentityHashMap elseStores; - /** - * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a - * control flow graph given a transfer function. - * - * @param transferFunction the transfer function - */ - public ForwardAnalysisImpl(T transferFunction) { - this(-1); - this.transferFunction = transferFunction; - } + /** The stores after every return statement. */ + protected final IdentityHashMap> storesAtReturnStatements; - @Override - public void performAnalysis(ControlFlowGraph cfg) { - if (isRunning) { - throw new BugInCF( - "ForwardAnalysisImpl::performAnalysis() shouldn't be called when the analysis" - + " is running."); + // `@code`, not `@link`, because dataflow module doesn't depend on framework module. + /** + * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a + * control flow graph. When using this constructor, the transfer function is set later by the + * subclass, e.g., {@code org.checkerframework.framework.flow.CFAbstractAnalysis}. + * + * @param maxCountBeforeWidening number of times a block can be analyzed before widening + */ + public ForwardAnalysisImpl(int maxCountBeforeWidening) { + super(Direction.FORWARD); + this.maxCountBeforeWidening = maxCountBeforeWidening; + this.blockCount = maxCountBeforeWidening == -1 ? null : new IdentityHashMap<>(); + this.thenStores = new IdentityHashMap<>(); + this.elseStores = new IdentityHashMap<>(); + this.storesAtReturnStatements = new IdentityHashMap<>(); } - isRunning = true; - try { - init(cfg); - while (!worklist.isEmpty()) { - Block b = worklist.poll(); - performAnalysisBlock(b); - } - } finally { - assert isRunning; - // In case performAnalysisBlock crashed, reset isRunning to false. - isRunning = false; + /** + * Construct an object that can perform a org.checkerframework.dataflow forward analysis over a + * control flow graph given a transfer function. + * + * @param transferFunction the transfer function + */ + public ForwardAnalysisImpl(T transferFunction) { + this(-1); + this.transferFunction = transferFunction; } - } - @Override - public void performAnalysisBlock(Block b) { - switch (b.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rb = (RegularBlock) b; - // Apply transfer function to contents - TransferInput inputBefore = getInputBefore(rb); - assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputBefore.copy(); - Node lastNode = null; - boolean addToWorklistAgain = false; - for (Node n : rb.getNodes()) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - TransferResult transferResult = callTransferFunction(n, currentInput); - addToWorklistAgain |= updateNodeValues(n, transferResult); - currentInput = new TransferInput<>(n, this, transferResult); - lastNode = n; - } - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - // Loop will run at least once, making transferResult non-null - // Propagate store to successors - Block succ = rb.getSuccessor(); - assert succ != null - : "@AssumeAssertion(nullness): regular basic block without" - + " non-exceptional successor unexpected"; - propagateStoresTo(succ, lastNode, currentInput, rb.getFlowRule(), addToWorklistAgain); - break; + @Override + public void performAnalysis(ControlFlowGraph cfg) { + if (isRunning) { + throw new BugInCF( + "ForwardAnalysisImpl::performAnalysis() shouldn't be called when the analysis" + + " is running."); } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) b; - // Apply transfer function to content - TransferInput inputBefore = getInputBefore(eb); - assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; - currentInput = inputBefore.copy(); - Node node = eb.getNode(); - TransferResult transferResult = callTransferFunction(node, currentInput); - boolean addToWorklistAgain = updateNodeValues(node, transferResult); - // Propagate store to successor - Block succ = eb.getSuccessor(); - if (succ != null) { - currentInput = new TransferInput<>(node, this, transferResult); - propagateStoresTo(succ, node, currentInput, eb.getFlowRule(), addToWorklistAgain); - } - // Propagate store to exceptional successors - for (Map.Entry> e : eb.getExceptionalSuccessors().entrySet()) { - TypeMirror cause = e.getKey(); - if (isIgnoredExceptionType(cause)) { - continue; - } - S exceptionalStore = transferResult.getExceptionalStore(cause); - if (exceptionalStore != null) { - for (Block exceptionSucc : e.getValue()) { - addStoreBefore( - exceptionSucc, node, exceptionalStore, Store.Kind.BOTH, addToWorklistAgain); - } - } else { - for (Block exceptionSucc : e.getValue()) { - addStoreBefore( - exceptionSucc, - node, - inputBefore.copy().getRegularStore(), - Store.Kind.BOTH, - addToWorklistAgain); - } + isRunning = true; + + try { + init(cfg); + while (!worklist.isEmpty()) { + Block b = worklist.poll(); + performAnalysisBlock(b); } - } - break; + } finally { + assert isRunning; + // In case performAnalysisBlock crashed, reset isRunning to false. + isRunning = false; } - case CONDITIONAL_BLOCK: - { - ConditionalBlock cb = (ConditionalBlock) b; - // Get store before - TransferInput inputBefore = getInputBefore(cb); - assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; - TransferInput input = inputBefore.copy(); - // Propagate store to successor - Block thenSucc = cb.getThenSuccessor(); - Block elseSucc = cb.getElseSuccessor(); - propagateStoresTo(thenSucc, null, input, cb.getThenFlowRule(), false); - propagateStoresTo(elseSucc, null, input, cb.getElseFlowRule(), false); - break; - } - case SPECIAL_BLOCK: - { - // Special basic blocks are empty and cannot throw exceptions, - // thus there is no need to perform any analysis. - SpecialBlock sb = (SpecialBlock) b; - Block succ = sb.getSuccessor(); - if (succ != null) { - TransferInput input = getInputBefore(b); - assert input != null : "@AssumeAssertion(nullness): invariant"; - propagateStoresTo(succ, null, input, sb.getFlowRule(), false); - } - break; + } + + @Override + public void performAnalysisBlock(Block b) { + switch (b.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rb = (RegularBlock) b; + // Apply transfer function to contents + TransferInput inputBefore = getInputBefore(rb); + assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputBefore.copy(); + Node lastNode = null; + boolean addToWorklistAgain = false; + for (Node n : rb.getNodes()) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + TransferResult transferResult = callTransferFunction(n, currentInput); + addToWorklistAgain |= updateNodeValues(n, transferResult); + currentInput = new TransferInput<>(n, this, transferResult); + lastNode = n; + } + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + // Loop will run at least once, making transferResult non-null + // Propagate store to successors + Block succ = rb.getSuccessor(); + assert succ != null + : "@AssumeAssertion(nullness): regular basic block without" + + " non-exceptional successor unexpected"; + propagateStoresTo( + succ, lastNode, currentInput, rb.getFlowRule(), addToWorklistAgain); + break; + } + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) b; + // Apply transfer function to content + TransferInput inputBefore = getInputBefore(eb); + assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; + currentInput = inputBefore.copy(); + Node node = eb.getNode(); + TransferResult transferResult = callTransferFunction(node, currentInput); + boolean addToWorklistAgain = updateNodeValues(node, transferResult); + // Propagate store to successor + Block succ = eb.getSuccessor(); + if (succ != null) { + currentInput = new TransferInput<>(node, this, transferResult); + propagateStoresTo( + succ, node, currentInput, eb.getFlowRule(), addToWorklistAgain); + } + // Propagate store to exceptional successors + for (Map.Entry> e : + eb.getExceptionalSuccessors().entrySet()) { + TypeMirror cause = e.getKey(); + if (isIgnoredExceptionType(cause)) { + continue; + } + S exceptionalStore = transferResult.getExceptionalStore(cause); + if (exceptionalStore != null) { + for (Block exceptionSucc : e.getValue()) { + addStoreBefore( + exceptionSucc, + node, + exceptionalStore, + Store.Kind.BOTH, + addToWorklistAgain); + } + } else { + for (Block exceptionSucc : e.getValue()) { + addStoreBefore( + exceptionSucc, + node, + inputBefore.copy().getRegularStore(), + Store.Kind.BOTH, + addToWorklistAgain); + } + } + } + break; + } + case CONDITIONAL_BLOCK: + { + ConditionalBlock cb = (ConditionalBlock) b; + // Get store before + TransferInput inputBefore = getInputBefore(cb); + assert inputBefore != null : "@AssumeAssertion(nullness): invariant"; + TransferInput input = inputBefore.copy(); + // Propagate store to successor + Block thenSucc = cb.getThenSuccessor(); + Block elseSucc = cb.getElseSuccessor(); + propagateStoresTo(thenSucc, null, input, cb.getThenFlowRule(), false); + propagateStoresTo(elseSucc, null, input, cb.getElseFlowRule(), false); + break; + } + case SPECIAL_BLOCK: + { + // Special basic blocks are empty and cannot throw exceptions, + // thus there is no need to perform any analysis. + SpecialBlock sb = (SpecialBlock) b; + Block succ = sb.getSuccessor(); + if (succ != null) { + TransferInput input = getInputBefore(b); + assert input != null : "@AssumeAssertion(nullness): invariant"; + propagateStoresTo(succ, null, input, sb.getFlowRule(), false); + } + break; + } + default: + throw new BugInCF("Unexpected block type: " + b.getType()); } - default: - throw new BugInCF("Unexpected block type: " + b.getType()); } - } - @Override - public @Nullable TransferInput getInput(Block b) { - return getInputBefore(b); - } + @Override + public @Nullable TransferInput getInput(Block b) { + return getInputBefore(b); + } - @Override - @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field - @RequiresNonNull("cfg") - public List>> getReturnStatementStores() { - return CollectionsPlume.>>mapList( - returnNode -> IPair.of(returnNode, storesAtReturnStatements.get(returnNode)), - cfg.getReturnNodes()); - } + @Override + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field + @RequiresNonNull("cfg") + public List>> getReturnStatementStores() { + return CollectionsPlume + .>>mapList( + returnNode -> + IPair.of(returnNode, storesAtReturnStatements.get(returnNode)), + cfg.getReturnNodes()); + } - @Override - public S runAnalysisFor( - @FindDistinct Node node, - Analysis.BeforeOrAfter preOrPost, - TransferInput blockTransferInput, - IdentityHashMap nodeValues, - @Nullable Map, IdentityHashMap>> - analysisCaches) { - Block block = node.getBlock(); - assert block != null : "@AssumeAssertion(nullness): invariant"; - Node oldCurrentNode = currentNode; + @Override + public S runAnalysisFor( + @FindDistinct Node node, + Analysis.BeforeOrAfter preOrPost, + TransferInput blockTransferInput, + IdentityHashMap nodeValues, + @Nullable Map, IdentityHashMap>> + analysisCaches) { + Block block = node.getBlock(); + assert block != null : "@AssumeAssertion(nullness): invariant"; + Node oldCurrentNode = currentNode; - // Prepare cache - IdentityHashMap> cache; - if (analysisCaches != null) { - cache = analysisCaches.computeIfAbsent(blockTransferInput, __ -> new IdentityHashMap<>()); - } else { - cache = null; - } + // Prepare cache + IdentityHashMap> cache; + if (analysisCaches != null) { + cache = + analysisCaches.computeIfAbsent( + blockTransferInput, __ -> new IdentityHashMap<>()); + } else { + cache = null; + } - if (isRunning) { - assert currentInput != null : "@AssumeAssertion(nullness): invariant"; - return currentInput.getRegularStore(); - } - setNodeValues(nodeValues); - isRunning = true; - try { - switch (block.getType()) { - case REGULAR_BLOCK: - { - RegularBlock rb = (RegularBlock) block; - // Apply transfer function to contents until we found the node we are - // looking for. - TransferInput store = blockTransferInput; - TransferResult transferResult; - for (Node n : rb.getNodes()) { - setCurrentNode(n); - if (n == node && preOrPost == Analysis.BeforeOrAfter.BEFORE) { - return store.getRegularStore(); - } - if (cache != null && cache.containsKey(n)) { - transferResult = cache.get(n); - } else { - // Copy the store to avoid changing other blocks' transfer inputs in - // {@link #inputs} - transferResult = callTransferFunction(n, store.copy()); - if (cache != null) { - cache.put(n, transferResult); - } - } - if (n == node) { - return transferResult.getRegularStore(); - } - store = new TransferInput<>(n, this, transferResult); - } - throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); - } - case EXCEPTION_BLOCK: - { - ExceptionBlock eb = (ExceptionBlock) block; - // Apply the transfer function to content - if (eb.getNode() != node) { - throw new BugInCF( - "Node should be equal to eb.getNode(). But get: node: " - + node - + "\teb.getNode(): " - + eb.getNode()); - } - if (preOrPost == Analysis.BeforeOrAfter.BEFORE) { - return blockTransferInput.getRegularStore(); - } - setCurrentNode(node); - // Copy the store to avoid changing other blocks' transfer inputs in {@link - // #inputs} - TransferResult transferResult; - if (cache != null && cache.containsKey(node)) { - transferResult = cache.get(node); - } else { - // Copy the store to avoid changing other blocks' transfer inputs in - // {@link #inputs} - transferResult = callTransferFunction(node, blockTransferInput.copy()); - if (cache != null) { - cache.put(node, transferResult); - } + if (isRunning) { + assert currentInput != null : "@AssumeAssertion(nullness): invariant"; + return currentInput.getRegularStore(); + } + setNodeValues(nodeValues); + isRunning = true; + try { + switch (block.getType()) { + case REGULAR_BLOCK: + { + RegularBlock rb = (RegularBlock) block; + // Apply transfer function to contents until we found the node we are + // looking for. + TransferInput store = blockTransferInput; + TransferResult transferResult; + for (Node n : rb.getNodes()) { + setCurrentNode(n); + if (n == node && preOrPost == Analysis.BeforeOrAfter.BEFORE) { + return store.getRegularStore(); + } + if (cache != null && cache.containsKey(n)) { + transferResult = cache.get(n); + } else { + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} + transferResult = callTransferFunction(n, store.copy()); + if (cache != null) { + cache.put(n, transferResult); + } + } + if (n == node) { + return transferResult.getRegularStore(); + } + store = new TransferInput<>(n, this, transferResult); + } + throw new BugInCF("node %s is not in node.getBlock()=%s", node, block); + } + case EXCEPTION_BLOCK: + { + ExceptionBlock eb = (ExceptionBlock) block; + // Apply the transfer function to content + if (eb.getNode() != node) { + throw new BugInCF( + "Node should be equal to eb.getNode(). But get: node: " + + node + + "\teb.getNode(): " + + eb.getNode()); + } + if (preOrPost == Analysis.BeforeOrAfter.BEFORE) { + return blockTransferInput.getRegularStore(); + } + setCurrentNode(node); + // Copy the store to avoid changing other blocks' transfer inputs in {@link + // #inputs} + TransferResult transferResult; + if (cache != null && cache.containsKey(node)) { + transferResult = cache.get(node); + } else { + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} + transferResult = callTransferFunction(node, blockTransferInput.copy()); + if (cache != null) { + cache.put(node, transferResult); + } + } + return transferResult.getRegularStore(); + } + default: + // Only regular blocks and exceptional blocks can hold nodes. + throw new BugInCF("Unexpected block type: " + block.getType()); } - return transferResult.getRegularStore(); - } - default: - // Only regular blocks and exceptional blocks can hold nodes. - throw new BugInCF("Unexpected block type: " + block.getType()); - } - } finally { - setCurrentNode(oldCurrentNode); - isRunning = false; + } finally { + setCurrentNode(oldCurrentNode); + isRunning = false; + } } - } - @Override - protected void initFields(ControlFlowGraph cfg) { - thenStores.clear(); - elseStores.clear(); - if (blockCount != null) { - blockCount.clear(); + @Override + protected void initFields(ControlFlowGraph cfg) { + thenStores.clear(); + elseStores.clear(); + if (blockCount != null) { + blockCount.clear(); + } + storesAtReturnStatements.clear(); + super.initFields(cfg); } - storesAtReturnStatements.clear(); - super.initFields(cfg); - } - @Override - @RequiresNonNull("cfg") - protected void initInitialInputs() { - worklist.process(cfg); - Block entry = cfg.getEntryBlock(); - worklist.add(entry); - UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); - List parameters = getParameters(underlyingAST); - assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; - S initialStore = transferFunction.initialStore(underlyingAST, parameters); - thenStores.put(entry, initialStore); - elseStores.put(entry, initialStore); - inputs.put(entry, new TransferInput<>(null, this, initialStore)); - } + @Override + @RequiresNonNull("cfg") + protected void initInitialInputs() { + worklist.process(cfg); + Block entry = cfg.getEntryBlock(); + worklist.add(entry); + UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); + List parameters = getParameters(underlyingAST); + assert transferFunction != null : "@AssumeAssertion(nullness): invariant"; + S initialStore = transferFunction.initialStore(underlyingAST, parameters); + thenStores.put(entry, initialStore); + elseStores.put(entry, initialStore); + inputs.put(entry, new TransferInput<>(null, this, initialStore)); + } - /** - * Returns the formal parameters for a method. - * - * @param underlyingAST the AST for the method - * @return the formal parameters for the method - */ - @SideEffectFree - private List getParameters(UnderlyingAST underlyingAST) { - switch (underlyingAST.getKind()) { - case METHOD: - MethodTree tree = ((CFGMethod) underlyingAST).getMethod(); - // TODO: document that LocalVariableNode has no block that it belongs to - return CollectionsPlume.mapList(LocalVariableNode::new, tree.getParameters()); - case LAMBDA: - LambdaExpressionTree lambda = ((CFGLambda) underlyingAST).getLambdaTree(); - // TODO: document that LocalVariableNode has no block that it belongs to - return CollectionsPlume.mapList(LocalVariableNode::new, lambda.getParameters()); - default: - return Collections.emptyList(); + /** + * Returns the formal parameters for a method. + * + * @param underlyingAST the AST for the method + * @return the formal parameters for the method + */ + @SideEffectFree + private List getParameters(UnderlyingAST underlyingAST) { + switch (underlyingAST.getKind()) { + case METHOD: + MethodTree tree = ((CFGMethod) underlyingAST).getMethod(); + // TODO: document that LocalVariableNode has no block that it belongs to + return CollectionsPlume.mapList(LocalVariableNode::new, tree.getParameters()); + case LAMBDA: + LambdaExpressionTree lambda = ((CFGLambda) underlyingAST).getLambdaTree(); + // TODO: document that LocalVariableNode has no block that it belongs to + return CollectionsPlume.mapList(LocalVariableNode::new, lambda.getParameters()); + default: + return Collections.emptyList(); + } } - } - @Override - protected TransferResult callTransferFunction(Node node, TransferInput input) { - TransferResult transferResult = super.callTransferFunction(node, input); + @Override + protected TransferResult callTransferFunction(Node node, TransferInput input) { + TransferResult transferResult = super.callTransferFunction(node, input); - if (node instanceof ReturnNode) { - // Save a copy of the store to later check if some property holds at a given return - // statement. - storesAtReturnStatements.put((ReturnNode) node, transferResult); + if (node instanceof ReturnNode) { + // Save a copy of the store to later check if some property holds at a given return + // statement. + storesAtReturnStatements.put((ReturnNode) node, transferResult); + } + return transferResult; } - return transferResult; - } - @Override - protected void propagateStoresTo( - Block succ, - @Nullable Node node, - TransferInput currentInput, - Store.FlowRule flowRule, - boolean addToWorklistAgain) { - switch (flowRule) { - case EACH_TO_EACH: - if (currentInput.containsTwoStores()) { - addStoreBefore( - succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain); - addStoreBefore( - succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain); - } else { - addStoreBefore( - succ, node, currentInput.getRegularStore(), Store.Kind.BOTH, addToWorklistAgain); + @Override + protected void propagateStoresTo( + Block succ, + @Nullable Node node, + TransferInput currentInput, + Store.FlowRule flowRule, + boolean addToWorklistAgain) { + switch (flowRule) { + case EACH_TO_EACH: + if (currentInput.containsTwoStores()) { + addStoreBefore( + succ, + node, + currentInput.getThenStore(), + Store.Kind.THEN, + addToWorklistAgain); + addStoreBefore( + succ, + node, + currentInput.getElseStore(), + Store.Kind.ELSE, + addToWorklistAgain); + } else { + addStoreBefore( + succ, + node, + currentInput.getRegularStore(), + Store.Kind.BOTH, + addToWorklistAgain); + } + break; + case THEN_TO_BOTH: + addStoreBefore( + succ, + node, + currentInput.getThenStore(), + Store.Kind.BOTH, + addToWorklistAgain); + break; + case ELSE_TO_BOTH: + addStoreBefore( + succ, + node, + currentInput.getElseStore(), + Store.Kind.BOTH, + addToWorklistAgain); + break; + case THEN_TO_THEN: + addStoreBefore( + succ, + node, + currentInput.getThenStore(), + Store.Kind.THEN, + addToWorklistAgain); + break; + case ELSE_TO_ELSE: + addStoreBefore( + succ, + node, + currentInput.getElseStore(), + Store.Kind.ELSE, + addToWorklistAgain); + break; } - break; - case THEN_TO_BOTH: - addStoreBefore( - succ, node, currentInput.getThenStore(), Store.Kind.BOTH, addToWorklistAgain); - break; - case ELSE_TO_BOTH: - addStoreBefore( - succ, node, currentInput.getElseStore(), Store.Kind.BOTH, addToWorklistAgain); - break; - case THEN_TO_THEN: - addStoreBefore( - succ, node, currentInput.getThenStore(), Store.Kind.THEN, addToWorklistAgain); - break; - case ELSE_TO_ELSE: - addStoreBefore( - succ, node, currentInput.getElseStore(), Store.Kind.ELSE, addToWorklistAgain); - break; } - } - /** - * Add a store before the basic block {@code b} by merging with the existing stores for that - * location. - * - * @param b a basic block - * @param node the node of the basic block {@code b} - * @param s the store being added - * @param kind the kind of store {@code s} - * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code - * Worklist} - */ - protected void addStoreBefore( - Block b, @Nullable Node node, S s, Store.Kind kind, boolean addBlockToWorklist) { - S thenStore = getStoreBefore(b, Store.Kind.THEN); - S elseStore = getStoreBefore(b, Store.Kind.ELSE); - boolean shouldWiden = false; - if (blockCount != null) { - Integer count = blockCount.getOrDefault(b, 0); - shouldWiden = count >= maxCountBeforeWidening; - if (shouldWiden) { - blockCount.put(b, 0); - } else { - blockCount.put(b, count + 1); - } - } - switch (kind) { - case THEN: - { - // Update the then store - S newThenStore = mergeStores(s, thenStore, shouldWiden); - if (!newThenStore.equals(thenStore)) { - thenStores.put(b, newThenStore); - if (elseStore != null) { - inputs.put(b, new TransferInput<>(node, this, newThenStore, elseStore)); - addBlockToWorklist = true; + /** + * Add a store before the basic block {@code b} by merging with the existing stores for that + * location. + * + * @param b a basic block + * @param node the node of the basic block {@code b} + * @param s the store being added + * @param kind the kind of store {@code s} + * @param addBlockToWorklist whether the basic block {@code b} should be added back to {@code + * Worklist} + */ + protected void addStoreBefore( + Block b, @Nullable Node node, S s, Store.Kind kind, boolean addBlockToWorklist) { + S thenStore = getStoreBefore(b, Store.Kind.THEN); + S elseStore = getStoreBefore(b, Store.Kind.ELSE); + boolean shouldWiden = false; + if (blockCount != null) { + Integer count = blockCount.getOrDefault(b, 0); + shouldWiden = count >= maxCountBeforeWidening; + if (shouldWiden) { + blockCount.put(b, 0); + } else { + blockCount.put(b, count + 1); } - } - break; } - case ELSE: - { - // Update the else store - S newElseStore = mergeStores(s, elseStore, shouldWiden); - if (!newElseStore.equals(elseStore)) { - elseStores.put(b, newElseStore); - if (thenStore != null) { - inputs.put(b, new TransferInput<>(node, this, thenStore, newElseStore)); - addBlockToWorklist = true; - } - } - break; + switch (kind) { + case THEN: + { + // Update the then store + S newThenStore = mergeStores(s, thenStore, shouldWiden); + if (!newThenStore.equals(thenStore)) { + thenStores.put(b, newThenStore); + if (elseStore != null) { + inputs.put(b, new TransferInput<>(node, this, newThenStore, elseStore)); + addBlockToWorklist = true; + } + } + break; + } + case ELSE: + { + // Update the else store + S newElseStore = mergeStores(s, elseStore, shouldWiden); + if (!newElseStore.equals(elseStore)) { + elseStores.put(b, newElseStore); + if (thenStore != null) { + inputs.put(b, new TransferInput<>(node, this, thenStore, newElseStore)); + addBlockToWorklist = true; + } + } + break; + } + case BOTH: + @SuppressWarnings("interning:not.interned") + boolean sameStore = (thenStore == elseStore); + if (sameStore) { + // Currently there is only one regular store + S newStore = mergeStores(s, thenStore, shouldWiden); + if (!newStore.equals(thenStore)) { + thenStores.put(b, newStore); + elseStores.put(b, newStore); + inputs.put(b, new TransferInput<>(node, this, newStore)); + addBlockToWorklist = true; + } + } else { + boolean storeChanged = false; + S newThenStore = mergeStores(s, thenStore, shouldWiden); + if (!newThenStore.equals(thenStore)) { + thenStores.put(b, newThenStore); + storeChanged = true; + } + S newElseStore = mergeStores(s, elseStore, shouldWiden); + if (!newElseStore.equals(elseStore)) { + elseStores.put(b, newElseStore); + storeChanged = true; + } + if (storeChanged) { + inputs.put(b, new TransferInput<>(node, this, newThenStore, newElseStore)); + addBlockToWorklist = true; + } + } } - case BOTH: - @SuppressWarnings("interning:not.interned") - boolean sameStore = (thenStore == elseStore); - if (sameStore) { - // Currently there is only one regular store - S newStore = mergeStores(s, thenStore, shouldWiden); - if (!newStore.equals(thenStore)) { - thenStores.put(b, newStore); - elseStores.put(b, newStore); - inputs.put(b, new TransferInput<>(node, this, newStore)); - addBlockToWorklist = true; - } - } else { - boolean storeChanged = false; - S newThenStore = mergeStores(s, thenStore, shouldWiden); - if (!newThenStore.equals(thenStore)) { - thenStores.put(b, newThenStore); - storeChanged = true; - } - S newElseStore = mergeStores(s, elseStore, shouldWiden); - if (!newElseStore.equals(elseStore)) { - elseStores.put(b, newElseStore); - storeChanged = true; - } - if (storeChanged) { - inputs.put(b, new TransferInput<>(node, this, newThenStore, newElseStore)); - addBlockToWorklist = true; - } + if (addBlockToWorklist) { + addToWorklist(b); } } - if (addBlockToWorklist) { - addToWorklist(b); - } - } - /** - * Merge two stores, possibly widening the result. - * - * @param newStore the new Store - * @param previousStore the previous Store - * @param shouldWiden should widen or not - * @return the merged Store - */ - private S mergeStores(S newStore, @Nullable S previousStore, boolean shouldWiden) { - if (previousStore == null) { - return newStore; - } else if (shouldWiden) { - return newStore.widenedUpperBound(previousStore); - } else { - return newStore.leastUpperBound(previousStore); + /** + * Merge two stores, possibly widening the result. + * + * @param newStore the new Store + * @param previousStore the previous Store + * @param shouldWiden should widen or not + * @return the merged Store + */ + private S mergeStores(S newStore, @Nullable S previousStore, boolean shouldWiden) { + if (previousStore == null) { + return newStore; + } else if (shouldWiden) { + return newStore.widenedUpperBound(previousStore); + } else { + return newStore.leastUpperBound(previousStore); + } } - } - /** - * Return the store corresponding to the location right before the basic block {@code b}. - * - * @param b a block - * @param kind the kind of store which will be returned - * @return the store corresponding to the location right before the basic block {@code b} - */ - protected @Nullable S getStoreBefore(Block b, Store.Kind kind) { - switch (kind) { - case THEN: - return readFromStore(thenStores, b); - case ELSE: - return readFromStore(elseStores, b); - default: - throw new BugInCF("Unexpected Store.Kind: " + kind); + /** + * Return the store corresponding to the location right before the basic block {@code b}. + * + * @param b a block + * @param kind the kind of store which will be returned + * @return the store corresponding to the location right before the basic block {@code b} + */ + protected @Nullable S getStoreBefore(Block b, Store.Kind kind) { + switch (kind) { + case THEN: + return readFromStore(thenStores, b); + case ELSE: + return readFromStore(elseStores, b); + default: + throw new BugInCF("Unexpected Store.Kind: " + kind); + } } - } - /** - * Returns the transfer input corresponding to the location right before the basic block {@code - * b}. - * - * @param b a block - * @return the transfer input corresponding to the location right before the basic block {@code b} - */ - protected @Nullable TransferInput getInputBefore(Block b) { - return inputs.get(b); - } + /** + * Returns the transfer input corresponding to the location right before the basic block {@code + * b}. + * + * @param b a block + * @return the transfer input corresponding to the location right before the basic block {@code + * b} + */ + protected @Nullable TransferInput getInputBefore(Block b) { + return inputs.get(b); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java index 3c5f8e58515..8856e8bc416 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardTransferFunction.java @@ -1,9 +1,10 @@ package org.checkerframework.dataflow.analysis; -import java.util.List; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; +import java.util.List; + /** * Interface of a forward transfer function for the abstract interpretation used for the forward * flow analysis. @@ -16,14 +17,14 @@ * @param the store type used in the analysis */ public interface ForwardTransferFunction, S extends Store> - extends TransferFunction { + extends TransferFunction { - /** - * Returns the initial store to be used by the org.checkerframework.dataflow analysis. - * - * @param underlyingAST an abstract syntax tree - * @param parameters a list of local variable nodes representing formal parameters (if any) - * @return the initial store - */ - S initialStore(UnderlyingAST underlyingAST, List parameters); + /** + * Returns the initial store to be used by the org.checkerframework.dataflow analysis. + * + * @param underlyingAST an abstract syntax tree + * @param parameters a list of local variable nodes representing formal parameters (if any) + * @return the initial store + */ + S initialStore(UnderlyingAST underlyingAST, List parameters); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java index 6bf69b6dcaf..1885f0d9451 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java @@ -1,10 +1,12 @@ package org.checkerframework.dataflow.analysis; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.StringsPlume; + import java.util.Map; import java.util.StringJoiner; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.StringsPlume; /** * Implementation of a {@link TransferResult} with just one non-exceptional store. The result of @@ -14,141 +16,144 @@ * @param the store type used in the analysis */ public class RegularTransferResult, S extends Store> - extends TransferResult { - - /** The regular result store. */ - protected final S store; - - /** - * Whether the store changed; see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - */ - private final boolean storeChanged; - - /** - * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code - * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores}. - * - *

          Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no - * special handling is necessary and the store before the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. - * - *

          Aliasing: {@code resultStore} is not allowed to be used anywhere outside of this - * class (including use through aliases). Complete control over the object is transferred to this - * class. - * - * @param value the abstract value produced by the transfer function - * @param resultStore regular result store - * @param storeChanged whether the store changed; see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} - * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) - */ - public RegularTransferResult(@Nullable V value, S resultStore, boolean storeChanged) { - this(value, resultStore, null, storeChanged); - } - - /** - * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code - * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores} and - * {@code false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - * - * @param value the abstract value produced by the transfer function - * @param resultStore regular result store - * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) - */ - public RegularTransferResult(@Nullable V value, S resultStore) { - this(value, resultStore, false); - } - - /** - * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code - * false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. - * - * @param value the abstract value produced by the transfer function - * @param resultStore the regular result store - * @param exceptionalStores the stores in case the basic block throws an exception, or null if the - * basic block does not throw any exceptions - * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) - */ - public RegularTransferResult( - @Nullable V value, S resultStore, @Nullable Map exceptionalStores) { - this(value, resultStore, exceptionalStores, false); - } - - /** - * Create a {@code TransferResult} with {@code resultStore} as the resulting store. If the - * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then - * {@code resultStore} is used for both the 'then' and 'else' edge. - * - *

          Exceptions: If the corresponding {@link - * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding store - * in {@code exceptionalStores} is used. If no exception is found in {@code exceptionalStores}, - * then it is assumed that no special handling is necessary and the store before the corresponding - * {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. - * - *

          Aliasing: {@code resultStore} and any store in {@code exceptionalStores} are not - * allowed to be used anywhere outside of this class (including use through aliases). Complete - * control over the objects is transferred to this class. - * - * @param value the abstract value produced by the transfer function - * @param resultStore the regular result store - * @param exceptionalStores the stores in case the basic block throws an exception, or null if the - * basic block does not throw any exceptions - * @param storeChanged see {@link - * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} - */ - public RegularTransferResult( - @Nullable V value, - S resultStore, - @Nullable Map exceptionalStores, - boolean storeChanged) { - super(value, exceptionalStores); - this.store = resultStore; - this.storeChanged = storeChanged; - } - - /** The regular result store. */ - @Override - public S getRegularStore() { - return store; - } - - @Override - public S getThenStore() { - return store; - } - - @Override - public S getElseStore() { - // copy the store such that it is the same as the result of getThenStore - // (that is, identical according to equals), but two different objects. - return store.copy(); - } - - @Override - public boolean containsTwoStores() { - return false; - } - - @Override - public String toString() { - StringJoiner result = new StringJoiner(System.lineSeparator()); - result.add("RegularTransferResult("); - result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); - // "toString().trim()" works around bug where toString ends with a newline. - result.add( - " store = " + StringsPlume.indentLinesExceptFirst(2, store.toString().trim()) + ")"); - return result.toString(); - } - - /** - * See {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged()}. - * - * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged() - */ - @Override - public boolean storeChanged() { - return storeChanged; - } + extends TransferResult { + + /** The regular result store. */ + protected final S store; + + /** + * Whether the store changed; see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + */ + private final boolean storeChanged; + + /** + * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code + * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores}. + * + *

          Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no + * special handling is necessary and the store before the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + *

          Aliasing: {@code resultStore} is not allowed to be used anywhere outside of this + * class (including use through aliases). Complete control over the object is transferred to + * this class. + * + * @param value the abstract value produced by the transfer function + * @param resultStore regular result store + * @param storeChanged whether the store changed; see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} + * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) + */ + public RegularTransferResult(@Nullable V value, S resultStore, boolean storeChanged) { + this(value, resultStore, null, storeChanged); + } + + /** + * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code + * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores} and + * {@code false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + * + * @param value the abstract value produced by the transfer function + * @param resultStore regular result store + * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) + */ + public RegularTransferResult(@Nullable V value, S resultStore) { + this(value, resultStore, false); + } + + /** + * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code + * false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + * + * @param value the abstract value produced by the transfer function + * @param resultStore the regular result store + * @param exceptionalStores the stores in case the basic block throws an exception, or null if + * the basic block does not throw any exceptions + * @see #RegularTransferResult(AbstractValue, Store, Map, boolean) + */ + public RegularTransferResult( + @Nullable V value, S resultStore, @Nullable Map exceptionalStores) { + this(value, resultStore, exceptionalStores, false); + } + + /** + * Create a {@code TransferResult} with {@code resultStore} as the resulting store. If the + * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then + * {@code resultStore} is used for both the 'then' and 'else' edge. + * + *

          Exceptions: If the corresponding {@link + * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding + * store in {@code exceptionalStores} is used. If no exception is found in {@code + * exceptionalStores}, then it is assumed that no special handling is necessary and the store + * before the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed + * along any exceptional edge. + * + *

          Aliasing: {@code resultStore} and any store in {@code exceptionalStores} are not + * allowed to be used anywhere outside of this class (including use through aliases). Complete + * control over the objects is transferred to this class. + * + * @param value the abstract value produced by the transfer function + * @param resultStore the regular result store + * @param exceptionalStores the stores in case the basic block throws an exception, or null if + * the basic block does not throw any exceptions + * @param storeChanged see {@link + * org.checkerframework.dataflow.analysis.TransferResult#storeChanged} + */ + public RegularTransferResult( + @Nullable V value, + S resultStore, + @Nullable Map exceptionalStores, + boolean storeChanged) { + super(value, exceptionalStores); + this.store = resultStore; + this.storeChanged = storeChanged; + } + + /** The regular result store. */ + @Override + public S getRegularStore() { + return store; + } + + @Override + public S getThenStore() { + return store; + } + + @Override + public S getElseStore() { + // copy the store such that it is the same as the result of getThenStore + // (that is, identical according to equals), but two different objects. + return store.copy(); + } + + @Override + public boolean containsTwoStores() { + return false; + } + + @Override + public String toString() { + StringJoiner result = new StringJoiner(System.lineSeparator()); + result.add("RegularTransferResult("); + result.add(" resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue)); + // "toString().trim()" works around bug where toString ends with a newline. + result.add( + " store = " + + StringsPlume.indentLinesExceptFirst(2, store.toString().trim()) + + ")"); + return result.toString(); + } + + /** + * See {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged()}. + * + * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged() + */ + @Override + public boolean storeChanged() { + return storeChanged; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java index 0588bb2c12a..cbf403ece59 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Store.java @@ -14,101 +14,102 @@ */ public interface Store> { - // We maintain a then store and an else store before each basic block. - // When they are identical (by reference equality), they can be treated - // as a regular unconditional store. - // Once we have some information for both the then and else store, we - // create a TransferInput for the block and allow it to be analyzed. - public enum Kind { - THEN, - ELSE, - BOTH - } + // We maintain a then store and an else store before each basic block. + // When they are identical (by reference equality), they can be treated + // as a regular unconditional store. + // Once we have some information for both the then and else store, we + // create a TransferInput for the block and allow it to be analyzed. + public enum Kind { + THEN, + ELSE, + BOTH + } + + /** A flow rule describes how stores flow along one edge between basic blocks. */ + public enum FlowRule { + /** + * The normal case: then store flows to the then store, and else store flows to the else + * store. + */ + EACH_TO_EACH, + /** Then store flows to both then and else of successor. */ + THEN_TO_BOTH, + /** Else store flows to both then and else of successor. */ + ELSE_TO_BOTH, + /** Then store flows to the then of successor. Else store is ignored. */ + THEN_TO_THEN, + /** Else store flows to the else of successor. Then store is ignored. */ + ELSE_TO_ELSE, + } - /** A flow rule describes how stores flow along one edge between basic blocks. */ - public enum FlowRule { /** - * The normal case: then store flows to the then store, and else store flows to the else store. + * Returns an exact copy of this store. + * + * @return an exact copy of this store */ - EACH_TO_EACH, - /** Then store flows to both then and else of successor. */ - THEN_TO_BOTH, - /** Else store flows to both then and else of successor. */ - ELSE_TO_BOTH, - /** Then store flows to the then of successor. Else store is ignored. */ - THEN_TO_THEN, - /** Else store flows to the else of successor. Then store is ignored. */ - ELSE_TO_ELSE, - } - - /** - * Returns an exact copy of this store. - * - * @return an exact copy of this store - */ - S copy(); + S copy(); - /** - * Compute the least upper bound of two stores. - * - *

          Important: This method must fulfill the following contract: - * - *

            - *
          • Does not change {@code this}. - *
          • Does not change {@code other}. - *
          • Returns a fresh object which is not aliased yet. - *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
          • Is commutative. - *
          - */ - S leastUpperBound(S other); + /** + * Compute the least upper bound of two stores. + * + *

          Important: This method must fulfill the following contract: + * + *

            + *
          • Does not change {@code this}. + *
          • Does not change {@code other}. + *
          • Returns a fresh object which is not aliased yet. + *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
          • Is commutative. + *
          + */ + S leastUpperBound(S other); - /** - * Compute an upper bound of two stores that is wider than the least upper bound of the two - * stores. Used to jump to a higher abstraction to allow faster termination of the fixed point - * computations in {@link Analysis}. {@code previous} must be the previous store. - * - *

          A particular analysis might not require widening and should implement this method by calling - * leastUpperBound. - * - *

          Important: This method must fulfill the following contract: - * - *

            - *
          • Does not change {@code this}. - *
          • Does not change {@code previous}. - *
          • Returns a fresh object which is not aliased yet. - *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
          • Is commutative. - *
          - * - * @param previous must be the previous store - */ - S widenedUpperBound(S previous); + /** + * Compute an upper bound of two stores that is wider than the least upper bound of the two + * stores. Used to jump to a higher abstraction to allow faster termination of the fixed point + * computations in {@link Analysis}. {@code previous} must be the previous store. + * + *

          A particular analysis might not require widening and should implement this method by + * calling leastUpperBound. + * + *

          Important: This method must fulfill the following contract: + * + *

            + *
          • Does not change {@code this}. + *
          • Does not change {@code previous}. + *
          • Returns a fresh object which is not aliased yet. + *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
          • Is commutative. + *
          + * + * @param previous must be the previous store + */ + S widenedUpperBound(S previous); - /** - * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., - * returns {@code true} if not enough information is available to determine aliasing). - */ - boolean canAlias(JavaExpression a, JavaExpression b); + /** + * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., + * returns {@code true} if not enough information is available to determine aliasing). + */ + boolean canAlias(JavaExpression a, JavaExpression b); - /** - * Delegate visualization responsibility to a visualizer. - * - * @param viz the visualizer to visualize this store - * @return the String representation of this store - */ - String visualize(CFGVisualizer viz); + /** + * Delegate visualization responsibility to a visualizer. + * + * @param viz the visualizer to visualize this store + * @return the String representation of this store + */ + String visualize(CFGVisualizer viz); - // I'm not sure why this is necessary, but without it `Store.equals()` is treated as requiring a - // @NonNull argument. TODO: fix. - /** - * Returns true if this is equal to the given argument. - * - * @param o the object to compare against this - * @return true if this is equal to the given argument - */ - @Override - boolean equals(@Nullable Object o); + // I'm not sure why this is necessary, but without it `Store.equals()` is treated as requiring a + // @NonNull argument. TODO: fix. + /** + * Returns true if this is equal to the given argument. + * + * @param o the object to compare against this + * @return true if this is equal to the given argument + */ + @Override + boolean equals(@Nullable Object o); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java index 0469edee0e6..2634333a45f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferFunction.java @@ -29,4 +29,4 @@ * @param the store type used in the analysis */ public interface TransferFunction, S extends Store> - extends NodeVisitor, TransferInput> {} + extends NodeVisitor, TransferInput> {} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java index ced4f8be570..0dfe3f22cc9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java @@ -1,13 +1,14 @@ package org.checkerframework.dataflow.analysis; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.plumelib.util.StringsPlume; import org.plumelib.util.UniqueId; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + /** * {@code TransferInput} is used as the input type of the individual transfer functions of a {@link * ForwardTransferFunction} or a {@link BackwardTransferFunction}. It also contains a reference to @@ -21,294 +22,297 @@ */ public class TransferInput, S extends Store> implements UniqueId { - /** The corresponding node. */ - // TODO: explain when the node is changed. - protected @Nullable Node node; - - /** - * The regular result store (or {@code null} if none is present, because {@link #thenStore} and - * {@link #elseStore} are set). The following invariant is maintained: - * - *
          
          -   * store == null ⇔ thenStore != null && elseStore != null
          -   * 
          - */ - protected final @Nullable S store; + /** The corresponding node. */ + // TODO: explain when the node is changed. + protected @Nullable Node node; - /** - * The 'then' result store (or {@code null} if none is present). See invariant at {@link #store}. - */ - protected final @Nullable S thenStore; + /** + * The regular result store (or {@code null} if none is present, because {@link #thenStore} and + * {@link #elseStore} are set). The following invariant is maintained: + * + *
          
          +     * store == null ⇔ thenStore != null && elseStore != null
          +     * 
          + */ + protected final @Nullable S store; - /** - * The 'else' result store (or {@code null} if none is present). See invariant at {@link #store}. - */ - protected final @Nullable S elseStore; + /** + * The 'then' result store (or {@code null} if none is present). See invariant at {@link + * #store}. + */ + protected final @Nullable S thenStore; - /** The corresponding analysis class to get intermediate flow results. */ - protected final Analysis analysis; + /** + * The 'else' result store (or {@code null} if none is present). See invariant at {@link + * #store}. + */ + protected final @Nullable S elseStore; - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); + /** The corresponding analysis class to get intermediate flow results. */ + protected final Analysis analysis; - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); - @Override - public long getUid(@UnknownInitialization TransferInput this) { - return uid; - } + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); - /** - * Private helper constructor; all TransferInput construction bottoms out here. - * - * @param node the corresponding node - * @param store the regular result store, or {@code null} if none is present - * @param thenStore the 'then' result store, or {@code null} if none is present - * @param elseStore the 'else' result store, or {@code null} if none is present - * @param analysis analysis the corresponding analysis class to get intermediate flow results - */ - private TransferInput( - @Nullable Node node, - @Nullable S store, - @Nullable S thenStore, - @Nullable S elseStore, - Analysis analysis) { - if (store == null) { - assert thenStore != null && elseStore != null; - } else { - assert thenStore == null && elseStore == null; + @Override + public long getUid(@UnknownInitialization TransferInput this) { + return uid; } - this.node = node; - this.store = store; - this.thenStore = thenStore; - this.elseStore = elseStore; - this.analysis = analysis; - } - /** - * Create a {@link TransferInput}, given a {@link TransferResult} and a node-value mapping. - * - *

          Aliasing: The stores returned by any methods of {@code to} will be stored - * internally and are not allowed to be used elsewhere. Full control of them is transferred to - * this object. - * - *

          The node-value mapping {@code nodeValues} is provided by the analysis and is only read from - * within this {@link TransferInput}. - * - * @param n {@link #node} - * @param analysis {@link #analysis} - * @param to a transfer result - */ - public TransferInput(Node n, Analysis analysis, TransferResult to) { - this( - n, - to.containsTwoStores() ? null : to.getRegularStore(), - to.containsTwoStores() ? to.getThenStore() : null, - to.containsTwoStores() ? to.getElseStore() : null, - analysis); - } + /** + * Private helper constructor; all TransferInput construction bottoms out here. + * + * @param node the corresponding node + * @param store the regular result store, or {@code null} if none is present + * @param thenStore the 'then' result store, or {@code null} if none is present + * @param elseStore the 'else' result store, or {@code null} if none is present + * @param analysis analysis the corresponding analysis class to get intermediate flow results + */ + private TransferInput( + @Nullable Node node, + @Nullable S store, + @Nullable S thenStore, + @Nullable S elseStore, + Analysis analysis) { + if (store == null) { + assert thenStore != null && elseStore != null; + } else { + assert thenStore == null && elseStore == null; + } + this.node = node; + this.store = store; + this.thenStore = thenStore; + this.elseStore = elseStore; + this.analysis = analysis; + } - /** - * Create a {@link TransferInput}, given a store and a node-value mapping. - * - *

          Aliasing: The store {@code s} will be stored internally and is not allowed to be - * used elsewhere. Full control over {@code s} is transferred to this object. - * - *

          The node-value mapping {@code nodeValues} is provided by the analysis and is only read from - * within this {@link TransferInput}. - * - * @param n {@link #node} - * @param analysis {@link #analysis} - * @param s {@link #store} - */ - public TransferInput(@Nullable Node n, Analysis analysis, S s) { - this(n, s, null, null, analysis); - } + /** + * Create a {@link TransferInput}, given a {@link TransferResult} and a node-value mapping. + * + *

          Aliasing: The stores returned by any methods of {@code to} will be stored + * internally and are not allowed to be used elsewhere. Full control of them is transferred to + * this object. + * + *

          The node-value mapping {@code nodeValues} is provided by the analysis and is only read + * from within this {@link TransferInput}. + * + * @param n {@link #node} + * @param analysis {@link #analysis} + * @param to a transfer result + */ + public TransferInput(Node n, Analysis analysis, TransferResult to) { + this( + n, + to.containsTwoStores() ? null : to.getRegularStore(), + to.containsTwoStores() ? to.getThenStore() : null, + to.containsTwoStores() ? to.getElseStore() : null, + analysis); + } - /** - * Create a {@link TransferInput}, given two stores and a node-value mapping. - * - *

          Aliasing: The two stores {@code s1} and {@code s2} will be stored internally and - * are not allowed to be used elsewhere. Full control of them is transferred to this object. - * - * @param n a node - * @param analysis {@link #analysis} - * @param s1 {@link #thenStore} - * @param s2 {@link #elseStore} - */ - public TransferInput(@Nullable Node n, Analysis analysis, S s1, S s2) { - this(n, null, s1, s2, analysis); - } + /** + * Create a {@link TransferInput}, given a store and a node-value mapping. + * + *

          Aliasing: The store {@code s} will be stored internally and is not allowed to be + * used elsewhere. Full control over {@code s} is transferred to this object. + * + *

          The node-value mapping {@code nodeValues} is provided by the analysis and is only read + * from within this {@link TransferInput}. + * + * @param n {@link #node} + * @param analysis {@link #analysis} + * @param s {@link #store} + */ + public TransferInput(@Nullable Node n, Analysis analysis, S s) { + this(n, s, null, null, analysis); + } - /** - * Copy constructor. - * - * @param from a {@link TransferInput} to copy - */ - @SuppressWarnings("nullness:dereference.of.nullable") // object invariant: store vs thenStore - protected TransferInput(TransferInput from) { - this( - from.node, - from.store == null ? null : from.store.copy(), - from.store == null ? from.thenStore.copy() : null, - from.store == null ? from.elseStore.copy() : null, - from.analysis); - } + /** + * Create a {@link TransferInput}, given two stores and a node-value mapping. + * + *

          Aliasing: The two stores {@code s1} and {@code s2} will be stored internally and + * are not allowed to be used elsewhere. Full control of them is transferred to this object. + * + * @param n a node + * @param analysis {@link #analysis} + * @param s1 {@link #thenStore} + * @param s2 {@link #elseStore} + */ + public TransferInput(@Nullable Node n, Analysis analysis, S s1, S s2) { + this(n, null, s1, s2, analysis); + } - /** - * Returns the {@link Node} for this {@link TransferInput}. - * - * @return the {@link Node} for this {@link TransferInput} - */ - public @Nullable Node getNode() { - return node; - } + /** + * Copy constructor. + * + * @param from a {@link TransferInput} to copy + */ + @SuppressWarnings("nullness:dereference.of.nullable") // object invariant: store vs thenStore + protected TransferInput(TransferInput from) { + this( + from.node, + from.store == null ? null : from.store.copy(), + from.store == null ? from.thenStore.copy() : null, + from.store == null ? from.elseStore.copy() : null, + from.analysis); + } - /** - * Returns the abstract value of node {@code n}, which is required to be a 'sub-node' (that is, a - * direct or indirect child) of the node this transfer input is associated with. Furthermore, - * {@code n} cannot be a l-value node. Returns {@code null} if no value is available. - * - * @param n a node - * @return the abstract value of node {@code n}, or {@code null} if no value is available - */ - public @Nullable V getValueOfSubNode(Node n) { - return analysis.getValue(n); - } + /** + * Returns the {@link Node} for this {@link TransferInput}. + * + * @return the {@link Node} for this {@link TransferInput} + */ + public @Nullable Node getNode() { + return node; + } - /** - * Returns the regular result store produced if no exception is thrown by the {@link Node} - * corresponding to this transfer function result. - * - * @return the regular result store produced if no exception is thrown by the {@link Node} - * corresponding to this transfer function result - */ - public S getRegularStore() { - if (store == null) { - assert thenStore != null && elseStore != null : "@AssumeAssertion(nullness): invariant"; - return thenStore.leastUpperBound(elseStore); - } else { - return store; + /** + * Returns the abstract value of node {@code n}, which is required to be a 'sub-node' (that is, + * a direct or indirect child) of the node this transfer input is associated with. Furthermore, + * {@code n} cannot be a l-value node. Returns {@code null} if no value is available. + * + * @param n a node + * @return the abstract value of node {@code n}, or {@code null} if no value is available + */ + public @Nullable V getValueOfSubNode(Node n) { + return analysis.getValue(n); } - } - /** - * Returns the result store produced if the {@link Node} this result belongs to evaluates to - * {@code true}. - * - * @return the result store produced if the {@link Node} this result belongs to evaluates to - * {@code true} - */ - public S getThenStore() { - if (store == null) { - assert thenStore != null : "@AssumeAssertion(nullness): invariant"; - return thenStore; + /** + * Returns the regular result store produced if no exception is thrown by the {@link Node} + * corresponding to this transfer function result. + * + * @return the regular result store produced if no exception is thrown by the {@link Node} + * corresponding to this transfer function result + */ + public S getRegularStore() { + if (store == null) { + assert thenStore != null && elseStore != null : "@AssumeAssertion(nullness): invariant"; + return thenStore.leastUpperBound(elseStore); + } else { + return store; + } } - return store; - } - /** - * Returns the result store produced if the {@link Node} this result belongs to evaluates to - * {@code false}. - * - * @return the result store produced if the {@link Node} this result belongs to evaluates to - * {@code false} - */ - public S getElseStore() { - if (store == null) { - assert elseStore != null : "@AssumeAssertion(nullness): invariant"; - return elseStore; + /** + * Returns the result store produced if the {@link Node} this result belongs to evaluates to + * {@code true}. + * + * @return the result store produced if the {@link Node} this result belongs to evaluates to + * {@code true} + */ + public S getThenStore() { + if (store == null) { + assert thenStore != null : "@AssumeAssertion(nullness): invariant"; + return thenStore; + } + return store; } - // copy the store such that it is the same as the result of getThenStore - // (that is, identical according to equals), but two different objects. - return store.copy(); - } - /** - * Returns {@code true} if and only if this transfer input contains two stores that are - * potentially not equal. Note that the result {@code true} does not imply that {@code - * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates that - * {@code getThenStore} or {@code getElseStore} can be used to give more precise results. - * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, - * {@code getThenStore}, and {@code getElseStore} return equivalent stores. - * - * @return {@code true} if and only if this transfer input contains two stores that are - * potentially not equal - */ - public boolean containsTwoStores() { - return store == null; - } + /** + * Returns the result store produced if the {@link Node} this result belongs to evaluates to + * {@code false}. + * + * @return the result store produced if the {@link Node} this result belongs to evaluates to + * {@code false} + */ + public S getElseStore() { + if (store == null) { + assert elseStore != null : "@AssumeAssertion(nullness): invariant"; + return elseStore; + } + // copy the store such that it is the same as the result of getThenStore + // (that is, identical according to equals), but two different objects. + return store.copy(); + } - /** - * Returns an exact copy of this store. - * - * @return an exact copy of this store - */ - public TransferInput copy() { - return new TransferInput<>(this); - } + /** + * Returns {@code true} if and only if this transfer input contains two stores that are + * potentially not equal. Note that the result {@code true} does not imply that {@code + * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates + * that {@code getThenStore} or {@code getElseStore} can be used to give more precise results. + * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, + * {@code getThenStore}, and {@code getElseStore} return equivalent stores. + * + * @return {@code true} if and only if this transfer input contains two stores that are + * potentially not equal + */ + public boolean containsTwoStores() { + return store == null; + } - /** - * Compute the least upper bound of two stores. - * - *

          Important: This method must fulfill the same contract as {@code leastUpperBound} of - * {@link Store}. - * - * @param other a transfer input - * @return the least upper bound of this and {@code other} - */ - public TransferInput leastUpperBound(TransferInput other) { - if (store == null) { - S newThenStore = getThenStore().leastUpperBound(other.getThenStore()); - S newElseStore = getElseStore().leastUpperBound(other.getElseStore()); - return new TransferInput<>(node, analysis, newThenStore, newElseStore); - } else { - if (other.store == null) { - // make sure we do not lose precision and keep two stores if at - // least one of the two TransferInput's has two stores. - return other.leastUpperBound(this); - } - return new TransferInput<>(node, analysis, store.leastUpperBound(other.getRegularStore())); + /** + * Returns an exact copy of this store. + * + * @return an exact copy of this store + */ + public TransferInput copy() { + return new TransferInput<>(this); } - } - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof TransferInput) { - @SuppressWarnings("unchecked") - TransferInput other = (TransferInput) o; - if (containsTwoStores()) { - if (other.containsTwoStores()) { - return getThenStore().equals(other.getThenStore()) - && getElseStore().equals(other.getElseStore()); + /** + * Compute the least upper bound of two stores. + * + *

          Important: This method must fulfill the same contract as {@code leastUpperBound} + * of {@link Store}. + * + * @param other a transfer input + * @return the least upper bound of this and {@code other} + */ + public TransferInput leastUpperBound(TransferInput other) { + if (store == null) { + S newThenStore = getThenStore().leastUpperBound(other.getThenStore()); + S newElseStore = getElseStore().leastUpperBound(other.getElseStore()); + return new TransferInput<>(node, analysis, newThenStore, newElseStore); + } else { + if (other.store == null) { + // make sure we do not lose precision and keep two stores if at + // least one of the two TransferInput's has two stores. + return other.leastUpperBound(this); + } + return new TransferInput<>( + node, analysis, store.leastUpperBound(other.getRegularStore())); } - } else { - if (!other.containsTwoStores()) { - return getRegularStore().equals(other.getRegularStore()); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof TransferInput) { + @SuppressWarnings("unchecked") + TransferInput other = (TransferInput) o; + if (containsTwoStores()) { + if (other.containsTwoStores()) { + return getThenStore().equals(other.getThenStore()) + && getElseStore().equals(other.getElseStore()); + } + } else { + if (!other.containsTwoStores()) { + return getRegularStore().equals(other.getRegularStore()); + } + } } - } + return false; } - return false; - } - @Override - public int hashCode() { - return Objects.hash(this.analysis, this.node, this.store, this.thenStore, this.elseStore); - } + @Override + public int hashCode() { + return Objects.hash(this.analysis, this.node, this.store, this.thenStore, this.elseStore); + } - @Override - public String toString() { - if (store == null) { - return "[then=" - + StringsPlume.indentLinesExceptFirst(2, thenStore) - + "," - + System.lineSeparator() - + " else=" - + StringsPlume.indentLinesExceptFirst(2, elseStore) - + "]"; - } else { - return "[" + store + "]"; + @Override + public String toString() { + if (store == null) { + return "[then=" + + StringsPlume.indentLinesExceptFirst(2, thenStore) + + "," + + System.lineSeparator() + + " else=" + + StringsPlume.indentLinesExceptFirst(2, elseStore) + + "]"; + } else { + return "[" + store + "]"; + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java index dab3046ae5e..938a01a43ad 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java @@ -1,8 +1,10 @@ package org.checkerframework.dataflow.analysis; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Map; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; /** * {@code TransferResult} is used as the result type of the individual transfer functions of a @@ -18,126 +20,127 @@ */ public abstract class TransferResult, S extends Store> { - /** - * The abstract value of the {@link org.checkerframework.dataflow.cfg.node.Node} associated with - * this {@link TransferResult}, or {@code null} if no value has been produced. - * - *

          Is set by {@link #setResultValue}. - */ - protected @Nullable V resultValue; - - /** - * The stores in case the basic block throws an exception (or {@code null} if the corresponding - * {@link org.checkerframework.dataflow.cfg.node.Node} does not throw any exceptions). Does not - * necessarily contain a store for every exception, in which case the in-store will be used. - */ - protected final @Nullable Map exceptionalStores; - - /** - * Create a new TransferResult, given {@link #resultValue} and {@link #exceptionalStores}. - * - * @param resultValue the abstract value of the {@link - * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} - * @param exceptionalStores the stores in case the basic block throws an exception (or {@code - * null} if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} does not - * throw any exceptions) - */ - protected TransferResult( - @Nullable V resultValue, @Nullable Map exceptionalStores) { - this.resultValue = resultValue; - this.exceptionalStores = exceptionalStores; - } - - /** - * Returns the abstract value produced by the transfer function, {@code null} otherwise. - * - * @return the abstract value produced by the transfer function, {@code null} otherwise - */ - public @Nullable V getResultValue() { - return resultValue; - } - - /** - * Set the value of {@link #resultValue}. - * - * @param resultValue the abstract value of the {@link - * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} - */ - public void setResultValue(V resultValue) { - this.resultValue = resultValue; - } - - /** - * Returns the regular result store produced if no exception is thrown by the {@link - * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result. - * - * @return the regular result store produced if no exception is thrown by the {@link - * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result - */ - public abstract S getRegularStore(); - - /** - * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code true}. - * - * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code true} - */ - public abstract S getThenStore(); - - /** - * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code false}. - * - * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} - * this result belongs to evaluates to {@code false} - */ - public abstract S getElseStore(); - - /** - * Returns the store that flows along the outgoing exceptional edge labeled with {@code exception} - * (or {@code null} if no special handling is required for exceptional edges). - * - * @param exception an exception type - * @return the store that flows along the outgoing exceptional edge labeled with {@code exception} - * (or {@code null} if no special handling is required for exceptional edges) - */ - public @Nullable S getExceptionalStore(TypeMirror exception) { - if (exceptionalStores == null) { - return null; + /** + * The abstract value of the {@link org.checkerframework.dataflow.cfg.node.Node} associated with + * this {@link TransferResult}, or {@code null} if no value has been produced. + * + *

          Is set by {@link #setResultValue}. + */ + protected @Nullable V resultValue; + + /** + * The stores in case the basic block throws an exception (or {@code null} if the corresponding + * {@link org.checkerframework.dataflow.cfg.node.Node} does not throw any exceptions). Does not + * necessarily contain a store for every exception, in which case the in-store will be used. + */ + protected final @Nullable Map exceptionalStores; + + /** + * Create a new TransferResult, given {@link #resultValue} and {@link #exceptionalStores}. + * + * @param resultValue the abstract value of the {@link + * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} + * @param exceptionalStores the stores in case the basic block throws an exception (or {@code + * null} if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} does not + * throw any exceptions) + */ + protected TransferResult( + @Nullable V resultValue, @Nullable Map exceptionalStores) { + this.resultValue = resultValue; + this.exceptionalStores = exceptionalStores; + } + + /** + * Returns the abstract value produced by the transfer function, {@code null} otherwise. + * + * @return the abstract value produced by the transfer function, {@code null} otherwise + */ + public @Nullable V getResultValue() { + return resultValue; + } + + /** + * Set the value of {@link #resultValue}. + * + * @param resultValue the abstract value of the {@link + * org.checkerframework.dataflow.cfg.node.Node} associated with this {@link TransferResult} + */ + public void setResultValue(V resultValue) { + this.resultValue = resultValue; } - return exceptionalStores.get(exception); - } - - /** - * Returns a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise. - * - * @return a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise - * @see TransferResult#getExceptionalStore(TypeMirror) - */ - public @Nullable Map getExceptionalStores() { - return exceptionalStores; - } - - /** - * Returns {@code true} if and only if this transfer result contains two stores that are - * potentially not equal. Note that the result {@code true} does not imply that {@code - * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates that - * {@code getThenStore} or {@code getElseStore} can be used to give more precise results. - * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, - * {@code getThenStore}, and {@code getElseStore} return equivalent stores. - * - * @return {@code true} if and only if this transfer result contains two stores that are - * potentially not equal - */ - public abstract boolean containsTwoStores(); - - /** - * Returns {@code true} if and only if the transfer function returning this transfer result - * changed the regularStore, elseStore, or thenStore. - * - * @return {@code true} if and only if the transfer function returning this transfer result - * changed the regularStore, elseStore, or thenStore - */ - public abstract boolean storeChanged(); + + /** + * Returns the regular result store produced if no exception is thrown by the {@link + * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result. + * + * @return the regular result store produced if no exception is thrown by the {@link + * org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function + * result + */ + public abstract S getRegularStore(); + + /** + * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code true}. + * + * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code true} + */ + public abstract S getThenStore(); + + /** + * Returns the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code false}. + * + * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} + * this result belongs to evaluates to {@code false} + */ + public abstract S getElseStore(); + + /** + * Returns the store that flows along the outgoing exceptional edge labeled with {@code + * exception} (or {@code null} if no special handling is required for exceptional edges). + * + * @param exception an exception type + * @return the store that flows along the outgoing exceptional edge labeled with {@code + * exception} (or {@code null} if no special handling is required for exceptional edges) + */ + public @Nullable S getExceptionalStore(TypeMirror exception) { + if (exceptionalStores == null) { + return null; + } + return exceptionalStores.get(exception); + } + + /** + * Returns a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise. + * + * @return a Map of {@link TypeMirror} to {@link Store}, {@code null} otherwise + * @see TransferResult#getExceptionalStore(TypeMirror) + */ + public @Nullable Map getExceptionalStores() { + return exceptionalStores; + } + + /** + * Returns {@code true} if and only if this transfer result contains two stores that are + * potentially not equal. Note that the result {@code true} does not imply that {@code + * getRegularStore} cannot be called (or vice versa for {@code false}). Rather, it indicates + * that {@code getThenStore} or {@code getElseStore} can be used to give more precise results. + * Otherwise, if the result is {@code false}, then all three methods {@code getRegularStore}, + * {@code getThenStore}, and {@code getElseStore} return equivalent stores. + * + * @return {@code true} if and only if this transfer result contains two stores that are + * potentially not equal + */ + public abstract boolean containsTwoStores(); + + /** + * Returns {@code true} if and only if the transfer function returning this transfer result + * changed the regularStore, elseStore, or thenStore. + * + * @return {@code true} if and only if the transfer function returning this transfer result + * changed the regularStore, elseStore, or thenStore + */ + public abstract boolean storeChanged(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java index 9c5bc2e2b0d..ff6088f5500 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/UnusedAbstractValue.java @@ -11,13 +11,13 @@ */ public final class UnusedAbstractValue implements AbstractValue { - /** This class cannot be instantiated */ - private UnusedAbstractValue() { - throw new AssertionError("Class UnusedAbstractValue cannot be instantiated."); - } + /** This class cannot be instantiated */ + private UnusedAbstractValue() { + throw new AssertionError("Class UnusedAbstractValue cannot be instantiated."); + } - @Override - public UnusedAbstractValue leastUpperBound(UnusedAbstractValue other) { - throw new BugInCF("UnusedAbstractValue.leastUpperBound was called!"); - } + @Override + public UnusedAbstractValue leastUpperBound(UnusedAbstractValue other) { + throw new BugInCF("UnusedAbstractValue.leastUpperBound was called!"); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java index 314b062d560..d66c72fb29b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprStore.java @@ -1,8 +1,5 @@ package org.checkerframework.dataflow.busyexpr; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.BinaryOperationNode; @@ -11,132 +8,136 @@ import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.javacutil.BugInCF; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + /** A busy expression store contains a set of busy expressions represented by nodes. */ public class BusyExprStore implements Store { - /** A set of busy expression abstract values. */ - private final Set busyExprValueSet; - - /** - * Create a new BusyExprStore. - * - * @param busyExprValueSet a set of busy expression abstract values. The parameter is captured and - * the caller should not retain an alias. - */ - public BusyExprStore(Set busyExprValueSet) { - this.busyExprValueSet = busyExprValueSet; - } - - /** Create a new BusyExprStore. */ - public BusyExprStore() { - busyExprValueSet = new LinkedHashSet<>(); - } - - /** - * Kill expressions if they contain variable var. - * - * @param var a variable - */ - public void killBusyExpr(Node var) { - busyExprValueSet.removeIf( - busyExprValue -> exprContainsVariable(busyExprValue.busyExpression, var)); - } - - /** - * Return true if the expression contains variable var. Note that {@code .equals} is used in the - * return statement to verify value equality, as the statement decides whether the two nodes have - * the same value, not represent the same CFG node. - * - * @param expr the expression checked - * @param var the variable - * @return true if the expression contains the variable - */ - public boolean exprContainsVariable(Node expr, Node var) { - if (expr instanceof BinaryOperationNode) { - BinaryOperationNode binaryNode = (BinaryOperationNode) expr; - return exprContainsVariable(binaryNode.getLeftOperand(), var) - || exprContainsVariable(binaryNode.getRightOperand(), var); + /** A set of busy expression abstract values. */ + private final Set busyExprValueSet; + + /** + * Create a new BusyExprStore. + * + * @param busyExprValueSet a set of busy expression abstract values. The parameter is captured + * and the caller should not retain an alias. + */ + public BusyExprStore(Set busyExprValueSet) { + this.busyExprValueSet = busyExprValueSet; } - return expr.equals(var); - } - - /** - * Add busy expression e to busy expression value set. - * - * @param e the busy expression to be added - */ - public void putBusyExpr(BusyExprValue e) { - busyExprValueSet.add(e); - } - - /** - * Add expressions to the store, add sub-expressions to the store recursively - * - * @param e the expression to be added - */ - public void addUseInExpression(Node e) { - if (e instanceof BinaryOperationNode) { - BinaryOperationNode binaryNode = (BinaryOperationNode) e; - putBusyExpr(new BusyExprValue(binaryNode)); - // recursively add expressions - addUseInExpression(binaryNode.getLeftOperand()); - addUseInExpression(binaryNode.getRightOperand()); + /** Create a new BusyExprStore. */ + public BusyExprStore() { + busyExprValueSet = new LinkedHashSet<>(); } - } - - @Override - public BusyExprStore copy() { - return new BusyExprStore(new LinkedHashSet<>(busyExprValueSet)); - } - - @Override - public BusyExprStore leastUpperBound(BusyExprStore other) { - Set busyExprValueSetLub = new LinkedHashSet<>(this.busyExprValueSet); - busyExprValueSetLub.retainAll(other.busyExprValueSet); - - return new BusyExprStore(busyExprValueSetLub); - } - - @Override - public BusyExprStore widenedUpperBound(BusyExprStore previous) { - throw new BugInCF("BusyExprStore.widenedUpperBound was called!"); - } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; - } - - @Override - public String visualize(CFGVisualizer viz) { - String key = "busy expressions"; - if (busyExprValueSet.isEmpty()) { - return viz.visualizeStoreKeyVal(key, "none"); + + /** + * Kill expressions if they contain variable var. + * + * @param var a variable + */ + public void killBusyExpr(Node var) { + busyExprValueSet.removeIf( + busyExprValue -> exprContainsVariable(busyExprValue.busyExpression, var)); } - StringJoiner sjStoreVal = new StringJoiner(", "); - for (BusyExprValue busyExprValue : busyExprValueSet) { - sjStoreVal.add(busyExprValue.toString()); + + /** + * Return true if the expression contains variable var. Note that {@code .equals} is used in the + * return statement to verify value equality, as the statement decides whether the two nodes + * have the same value, not represent the same CFG node. + * + * @param expr the expression checked + * @param var the variable + * @return true if the expression contains the variable + */ + public boolean exprContainsVariable(Node expr, Node var) { + if (expr instanceof BinaryOperationNode) { + BinaryOperationNode binaryNode = (BinaryOperationNode) expr; + return exprContainsVariable(binaryNode.getLeftOperand(), var) + || exprContainsVariable(binaryNode.getRightOperand(), var); + } + + return expr.equals(var); + } + + /** + * Add busy expression e to busy expression value set. + * + * @param e the busy expression to be added + */ + public void putBusyExpr(BusyExprValue e) { + busyExprValueSet.add(e); + } + + /** + * Add expressions to the store, add sub-expressions to the store recursively + * + * @param e the expression to be added + */ + public void addUseInExpression(Node e) { + if (e instanceof BinaryOperationNode) { + BinaryOperationNode binaryNode = (BinaryOperationNode) e; + putBusyExpr(new BusyExprValue(binaryNode)); + // recursively add expressions + addUseInExpression(binaryNode.getLeftOperand()); + addUseInExpression(binaryNode.getRightOperand()); + } + } + + @Override + public BusyExprStore copy() { + return new BusyExprStore(new LinkedHashSet<>(busyExprValueSet)); } - return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); - } - - @Override - public String toString() { - return busyExprValueSet.toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BusyExprStore)) { - return false; + + @Override + public BusyExprStore leastUpperBound(BusyExprStore other) { + Set busyExprValueSetLub = new LinkedHashSet<>(this.busyExprValueSet); + busyExprValueSetLub.retainAll(other.busyExprValueSet); + + return new BusyExprStore(busyExprValueSetLub); + } + + @Override + public BusyExprStore widenedUpperBound(BusyExprStore previous) { + throw new BugInCF("BusyExprStore.widenedUpperBound was called!"); + } + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; + } + + @Override + public String visualize(CFGVisualizer viz) { + String key = "busy expressions"; + if (busyExprValueSet.isEmpty()) { + return viz.visualizeStoreKeyVal(key, "none"); + } + StringJoiner sjStoreVal = new StringJoiner(", "); + for (BusyExprValue busyExprValue : busyExprValueSet) { + sjStoreVal.add(busyExprValue.toString()); + } + return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); + } + + @Override + public String toString() { + return busyExprValueSet.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BusyExprStore)) { + return false; + } + BusyExprStore other = (BusyExprStore) obj; + return other.busyExprValueSet.equals(this.busyExprValueSet); + } + + @Override + public int hashCode() { + return this.busyExprValueSet.hashCode(); } - BusyExprStore other = (BusyExprStore) obj; - return other.busyExprValueSet.equals(this.busyExprValueSet); - } - - @Override - public int hashCode() { - return this.busyExprValueSet.hashCode(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java index 45c0e30ccfb..ab9259c6184 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.dataflow.busyexpr; -import java.util.List; import org.checkerframework.dataflow.analysis.BackwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -14,76 +13,80 @@ import org.checkerframework.dataflow.cfg.node.ObjectCreationNode; import org.checkerframework.dataflow.cfg.node.ReturnNode; +import java.util.List; + /** A busy expression transfer function */ public class BusyExprTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements BackwardTransferFunction { + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements BackwardTransferFunction { - @Override - public BusyExprStore initialNormalExitStore( - UnderlyingAST underlyingAST, List returnNodes) { - return new BusyExprStore(); - } + @Override + public BusyExprStore initialNormalExitStore( + UnderlyingAST underlyingAST, List returnNodes) { + return new BusyExprStore(); + } - @Override - public BusyExprStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { - return new BusyExprStore(); - } + @Override + public BusyExprStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { + return new BusyExprStore(); + } - @Override - public RegularTransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } + @Override + public RegularTransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } - @Override - public RegularTransferResult visitAssignment( - AssignmentNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitAssignment(n, p); - BusyExprStore store = transferResult.getRegularStore(); - store.killBusyExpr(n.getTarget()); - store.addUseInExpression(n.getExpression()); - return transferResult; - } + @Override + public RegularTransferResult visitAssignment( + AssignmentNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitAssignment(n, p); + BusyExprStore store = transferResult.getRegularStore(); + store.killBusyExpr(n.getTarget()); + store.addUseInExpression(n.getExpression()); + return transferResult; + } - @Override - public RegularTransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitMethodInvocation(n, p); - BusyExprStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); + @Override + public RegularTransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitMethodInvocation(n, p); + BusyExprStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); + } + return transferResult; } - return transferResult; - } - @Override - public RegularTransferResult visitObjectCreation( - ObjectCreationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitObjectCreation(n, p); - BusyExprStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); + @Override + public RegularTransferResult visitObjectCreation( + ObjectCreationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitObjectCreation(n, p); + BusyExprStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); + } + return transferResult; } - return transferResult; - } - @Override - public RegularTransferResult visitReturn( - ReturnNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitReturn(n, p); - Node result = n.getResult(); - if (result != null) { - BusyExprStore store = transferResult.getRegularStore(); - store.addUseInExpression(result); + @Override + public RegularTransferResult visitReturn( + ReturnNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitReturn(n, p); + Node result = n.getResult(); + if (result != null) { + BusyExprStore store = transferResult.getRegularStore(); + store.addUseInExpression(result); + } + return transferResult; } - return transferResult; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java index 7d035ac5d48..206e7ba0b8b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/busyexpr/BusyExprValue.java @@ -12,38 +12,38 @@ */ public class BusyExprValue { - /** - * A busy expression is represented by a node, which can be a {@link - * org.checkerframework.dataflow.cfg.node.BinaryOperationNode} - */ - protected final BinaryOperationNode busyExpression; + /** + * A busy expression is represented by a node, which can be a {@link + * org.checkerframework.dataflow.cfg.node.BinaryOperationNode} + */ + protected final BinaryOperationNode busyExpression; - /** - * Create a new busy expression. - * - * @param n a node - */ - public BusyExprValue(BinaryOperationNode n) { - this.busyExpression = n; - } + /** + * Create a new busy expression. + * + * @param n a node + */ + public BusyExprValue(BinaryOperationNode n) { + this.busyExpression = n; + } - @Override - public String toString() { - return this.busyExpression.toString(); - } + @Override + public String toString() { + return this.busyExpression.toString(); + } - @Override - public int hashCode() { - return this.busyExpression.hashCode(); - } + @Override + public int hashCode() { + return this.busyExpression.hashCode(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BusyExprValue)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BusyExprValue)) { + return false; + } + BusyExprValue other = (BusyExprValue) obj; + // Use `equals` to check equality rather than using `==`. + return this.busyExpression.equals(other.busyExpression); } - BusyExprValue other = (BusyExprValue) obj; - // Use `equals` to check equality rather than using `==`. - return this.busyExpression.equals(other.busyExpression); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java index 4b2115bb807..a35ebe8eb97 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGProcessor.java @@ -5,10 +5,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.util.TreePathScanner; import com.sun.tools.javac.util.Log; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; + import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -17,6 +14,11 @@ import org.checkerframework.javacutil.BasicTypeProcessor; import org.checkerframework.javacutil.TreeUtils; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + /** * Generate the control flow graph of a given method in a given class. See {@link * org.checkerframework.dataflow.cfg.visualize.CFGVisualizeLauncher} for example usage. @@ -24,174 +26,176 @@ @SupportedAnnotationTypes("*") public class CFGProcessor extends BasicTypeProcessor { - /** - * Qualified name of a specified class which includes a specified method to generate the CFG for. - */ - private final String className; - - /** Name of a specified method to generate the CFG for. */ - private final String methodName; - - /** AST for source file. */ - private @Nullable CompilationUnitTree rootTree; - - /** AST node for the specified class. */ - private @Nullable ClassTree classTree; - - /** AST node for the specified method. */ - private @Nullable MethodTree methodTree; - - /** Result of CFG process; is set by {@link #typeProcessingOver}. */ - private @MonotonicNonNull CFGProcessResult result = null; - - /** - * Create a CFG processor. - * - * @param className the qualified name of class which includes the specified method to generate - * the CFG for - * @param methodName the name of the method to generate the CFG for - */ - public CFGProcessor(String className, String methodName) { - this.className = className; - this.methodName = methodName; - } - - /** - * Get the CFG process result. - * - * @return result of cfg process - */ - public final @Nullable CFGProcessResult getCFGProcessResult() { - return result; - } - - @Override - public void typeProcessingOver() { - if (rootTree == null) { - result = new CFGProcessResult("Root tree is null."); - } else if (classTree == null) { - result = new CFGProcessResult("Method tree is null."); - } else if (methodTree == null) { - result = new CFGProcessResult("Class tree is null."); - } else { - Log log = getCompilerLog(); - if (log.nerrors > 0) { - result = new CFGProcessResult("Compilation issued an error."); - } else { - ControlFlowGraph cfg = CFGBuilder.build(rootTree, methodTree, classTree, processingEnv); - result = new CFGProcessResult(cfg); - } - } - super.typeProcessingOver(); - } - - @Override - protected TreePathScanner createTreePathScanner(CompilationUnitTree root) { - rootTree = root; - return new TreePathScanner() { - @Override - public Void visitClass(ClassTree tree, Void p) { - TypeElement el = TreeUtils.elementFromDeclaration(tree); - if (el.getSimpleName().contentEquals(className)) { - classTree = tree; - } - return super.visitClass(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement el = TreeUtils.elementFromDeclaration(tree); - if (el.getSimpleName().contentEquals(methodName)) { - methodTree = tree; - // Stop execution by throwing an exception. This makes sure that compilation - // does not proceed, and thus the AST is not modified by further phases of the - // compilation (and we save the work to do the compilation). - throw new RuntimeException(); - } - return null; - } - }; - } + /** + * Qualified name of a specified class which includes a specified method to generate the CFG + * for. + */ + private final String className; + + /** Name of a specified method to generate the CFG for. */ + private final String methodName; - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } + /** AST for source file. */ + private @Nullable CompilationUnitTree rootTree; - /** The result of the CFG process, contains the control flow graph when successful. */ - public static class CFGProcessResult { - /** Control flow graph. */ - private final @Nullable ControlFlowGraph controlFlowGraph; + /** AST node for the specified class. */ + private @Nullable ClassTree classTree; - /** Did the CFG process succeed? */ - private final boolean isSuccess; + /** AST node for the specified method. */ + private @Nullable MethodTree methodTree; - /** Error message (when the CFG process failed). */ - private final @Nullable String errMsg; + /** Result of CFG process; is set by {@link #typeProcessingOver}. */ + private @MonotonicNonNull CFGProcessResult result = null; /** - * Create the result of the CFG process. Only called if the CFG was built successfully. + * Create a CFG processor. * - * @param cfg control flow graph + * @param className the qualified name of class which includes the specified method to generate + * the CFG for + * @param methodName the name of the method to generate the CFG for */ - /*package-private*/ CFGProcessResult(ControlFlowGraph cfg) { - this(cfg, true, null); + public CFGProcessor(String className, String methodName) { + this.className = className; + this.methodName = methodName; } /** - * Create the result of the CFG process. Only called if the CFG was not built successfully. + * Get the CFG process result. * - * @param errMsg the error message + * @return result of cfg process */ - /*package-private*/ CFGProcessResult(String errMsg) { - this(null, false, errMsg); + public final @Nullable CFGProcessResult getCFGProcessResult() { + return result; } - /** - * Create the result of CFG process. - * - * @param cfg the control flow graph - * @param isSuccess did the CFG process succeed? - * @param errMsg error message (when the CFG process failed) - */ - private CFGProcessResult( - @Nullable ControlFlowGraph cfg, boolean isSuccess, @Nullable String errMsg) { - this.controlFlowGraph = cfg; - this.isSuccess = isSuccess; - this.errMsg = errMsg; + @Override + public void typeProcessingOver() { + if (rootTree == null) { + result = new CFGProcessResult("Root tree is null."); + } else if (classTree == null) { + result = new CFGProcessResult("Method tree is null."); + } else if (methodTree == null) { + result = new CFGProcessResult("Class tree is null."); + } else { + Log log = getCompilerLog(); + if (log.nerrors > 0) { + result = new CFGProcessResult("Compilation issued an error."); + } else { + ControlFlowGraph cfg = + CFGBuilder.build(rootTree, methodTree, classTree, processingEnv); + result = new CFGProcessResult(cfg); + } + } + super.typeProcessingOver(); } - /** - * Check if the CFG process succeeded. - * - * @return true if the CFG process succeeded - */ - @Pure - @EnsuresNonNullIf(expression = "getCFG()", result = true) - @EnsuresNonNullIf(expression = "getErrMsg()", result = false) - @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") - public boolean isSuccess() { - return isSuccess; + @Override + protected TreePathScanner createTreePathScanner(CompilationUnitTree root) { + rootTree = root; + return new TreePathScanner() { + @Override + public Void visitClass(ClassTree tree, Void p) { + TypeElement el = TreeUtils.elementFromDeclaration(tree); + if (el.getSimpleName().contentEquals(className)) { + classTree = tree; + } + return super.visitClass(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement el = TreeUtils.elementFromDeclaration(tree); + if (el.getSimpleName().contentEquals(methodName)) { + methodTree = tree; + // Stop execution by throwing an exception. This makes sure that compilation + // does not proceed, and thus the AST is not modified by further phases of the + // compilation (and we save the work to do the compilation). + throw new RuntimeException(); + } + return null; + } + }; } - /** - * Returns the generated control flow graph. - * - * @return the generated control flow graph - */ - @Pure - public @Nullable ControlFlowGraph getCFG() { - return controlFlowGraph; + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); } - /** - * Returns the error message. - * - * @return the error message - */ - @Pure - public @Nullable String getErrMsg() { - return errMsg; + /** The result of the CFG process, contains the control flow graph when successful. */ + public static class CFGProcessResult { + /** Control flow graph. */ + private final @Nullable ControlFlowGraph controlFlowGraph; + + /** Did the CFG process succeed? */ + private final boolean isSuccess; + + /** Error message (when the CFG process failed). */ + private final @Nullable String errMsg; + + /** + * Create the result of the CFG process. Only called if the CFG was built successfully. + * + * @param cfg control flow graph + */ + /*package-private*/ CFGProcessResult(ControlFlowGraph cfg) { + this(cfg, true, null); + } + + /** + * Create the result of the CFG process. Only called if the CFG was not built successfully. + * + * @param errMsg the error message + */ + /*package-private*/ CFGProcessResult(String errMsg) { + this(null, false, errMsg); + } + + /** + * Create the result of CFG process. + * + * @param cfg the control flow graph + * @param isSuccess did the CFG process succeed? + * @param errMsg error message (when the CFG process failed) + */ + private CFGProcessResult( + @Nullable ControlFlowGraph cfg, boolean isSuccess, @Nullable String errMsg) { + this.controlFlowGraph = cfg; + this.isSuccess = isSuccess; + this.errMsg = errMsg; + } + + /** + * Check if the CFG process succeeded. + * + * @return true if the CFG process succeeded + */ + @Pure + @EnsuresNonNullIf(expression = "getCFG()", result = true) + @EnsuresNonNullIf(expression = "getErrMsg()", result = false) + @SuppressWarnings("nullness:contracts.conditional.postcondition.not.satisfied") + public boolean isSuccess() { + return isSuccess; + } + + /** + * Returns the generated control flow graph. + * + * @return the generated control flow graph + */ + @Pure + public @Nullable ControlFlowGraph getCFG() { + return controlFlowGraph; + } + + /** + * Returns the error message. + * + * @return the error message + */ + @Pure + public @Nullable String getErrMsg() { + return errMsg; + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java index d122d1a7dc7..e6175f08460 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java @@ -6,22 +6,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AnalysisResult; @@ -40,6 +25,24 @@ import org.plumelib.util.UniqueId; import org.plumelib.util.UnmodifiableIdentityHashMap; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +import javax.lang.model.type.TypeMirror; + /** * A control flow graph (CFG for short) of a single method. * @@ -50,434 +53,436 @@ */ public class ControlFlowGraph implements UniqueId { - /** The entry block of the control flow graph. */ - protected final SpecialBlock entryBlock; - - /** The regular exit block of the control flow graph. */ - protected final SpecialBlock regularExitBlock; - - /** The exceptional exit block of the control flow graph. */ - protected final SpecialBlock exceptionalExitBlock; - - /** The AST this CFG corresponds to. */ - public final UnderlyingAST underlyingAST; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid(@UnknownInitialization ControlFlowGraph this) { - return uid; - } - - /** - * Maps from AST {@link Tree}s to sets of {@link Node}s. - * - *

            - *
          • Most Trees that produce a value will have at least one corresponding Node. - *
          • Trees that undergo conversions, such as boxing or unboxing, can map to two distinct - * Nodes. The Node for the pre-conversion value is stored in {@link #treeLookup}, while the - * Node for the post-conversion value is stored in {@link #convertedTreeLookup}. - *
          - * - * Some of the mapped-to nodes (in both {@link #treeLookup} and {@link #convertedTreeLookup}) do - * not appear in {@link #getAllNodes} because their blocks are not reachable in the control flow - * graph. Dataflow will not compute abstract values for these nodes. - */ - protected final IdentityHashMap> treeLookup; - - /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ - protected final IdentityHashMap> convertedTreeLookup; - - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic - * tree that is {@code v + 1} or {@code v - 1}. - */ - protected final IdentityHashMap postfixNodeLookup; - - /** - * All return nodes (if any) encountered. Only includes return statements that actually return - * something - */ - protected final List returnNodes; - - /** - * Class declarations that have been encountered when building the control-flow graph for a - * method. - */ - protected final List declaredClasses; - - /** - * Lambdas encountered when building the control-flow graph for a method, variable initializer, or - * initializer. - */ - protected final List declaredLambdas; - - public ControlFlowGraph( - SpecialBlock entryBlock, - SpecialBlockImpl regularExitBlock, - SpecialBlockImpl exceptionalExitBlock, - UnderlyingAST underlyingAST, - IdentityHashMap> treeLookup, - IdentityHashMap> convertedTreeLookup, - IdentityHashMap postfixNodeLookup, - List returnNodes, - List declaredClasses, - List declaredLambdas) { - super(); - this.entryBlock = entryBlock; - this.underlyingAST = underlyingAST; - this.treeLookup = treeLookup; - this.postfixNodeLookup = postfixNodeLookup; - this.convertedTreeLookup = convertedTreeLookup; - this.regularExitBlock = regularExitBlock; - this.exceptionalExitBlock = exceptionalExitBlock; - this.returnNodes = returnNodes; - this.declaredClasses = declaredClasses; - this.declaredLambdas = declaredLambdas; - } - - /** - * Verify that this is a complete and well-formed CFG, i.e. that all internal invariants hold. - * - * @throws IllegalStateException if some internal invariant is violated - */ - public void checkInvariants() { - // TODO: this is a big data structure with many more invariants... - for (Block b : getAllBlocks()) { - - // Each node in the block should have this block as its parent. - for (Node n : b.getNodes()) { - if (!Objects.equals(n.getBlock(), b)) { - throw new IllegalStateException( - "Node " - + n - + " in block " - + b - + " incorrectly believes it belongs to " - + n.getBlock()); - } - } - - // Each successor should have this block in its predecessors. - for (Block succ : b.getSuccessors()) { - if (!succ.getPredecessors().contains(b)) { - throw new IllegalStateException( - "Block " - + b - + " has successor " - + succ - + " but does not appear in that successor's predecessors"); + /** The entry block of the control flow graph. */ + protected final SpecialBlock entryBlock; + + /** The regular exit block of the control flow graph. */ + protected final SpecialBlock regularExitBlock; + + /** The exceptional exit block of the control flow graph. */ + protected final SpecialBlock exceptionalExitBlock; + + /** The AST this CFG corresponds to. */ + public final UnderlyingAST underlyingAST; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid(@UnknownInitialization ControlFlowGraph this) { + return uid; + } + + /** + * Maps from AST {@link Tree}s to sets of {@link Node}s. + * + *
            + *
          • Most Trees that produce a value will have at least one corresponding Node. + *
          • Trees that undergo conversions, such as boxing or unboxing, can map to two distinct + * Nodes. The Node for the pre-conversion value is stored in {@link #treeLookup}, while + * the Node for the post-conversion value is stored in {@link #convertedTreeLookup}. + *
          + * + * Some of the mapped-to nodes (in both {@link #treeLookup} and {@link #convertedTreeLookup}) do + * not appear in {@link #getAllNodes} because their blocks are not reachable in the control flow + * graph. Dataflow will not compute abstract values for these nodes. + */ + protected final IdentityHashMap> treeLookup; + + /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ + protected final IdentityHashMap> convertedTreeLookup; + + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the + * synthetic tree that is {@code v + 1} or {@code v - 1}. + */ + protected final IdentityHashMap postfixNodeLookup; + + /** + * All return nodes (if any) encountered. Only includes return statements that actually return + * something + */ + protected final List returnNodes; + + /** + * Class declarations that have been encountered when building the control-flow graph for a + * method. + */ + protected final List declaredClasses; + + /** + * Lambdas encountered when building the control-flow graph for a method, variable initializer, + * or initializer. + */ + protected final List declaredLambdas; + + public ControlFlowGraph( + SpecialBlock entryBlock, + SpecialBlockImpl regularExitBlock, + SpecialBlockImpl exceptionalExitBlock, + UnderlyingAST underlyingAST, + IdentityHashMap> treeLookup, + IdentityHashMap> convertedTreeLookup, + IdentityHashMap postfixNodeLookup, + List returnNodes, + List declaredClasses, + List declaredLambdas) { + super(); + this.entryBlock = entryBlock; + this.underlyingAST = underlyingAST; + this.treeLookup = treeLookup; + this.postfixNodeLookup = postfixNodeLookup; + this.convertedTreeLookup = convertedTreeLookup; + this.regularExitBlock = regularExitBlock; + this.exceptionalExitBlock = exceptionalExitBlock; + this.returnNodes = returnNodes; + this.declaredClasses = declaredClasses; + this.declaredLambdas = declaredLambdas; + } + + /** + * Verify that this is a complete and well-formed CFG, i.e. that all internal invariants hold. + * + * @throws IllegalStateException if some internal invariant is violated + */ + public void checkInvariants() { + // TODO: this is a big data structure with many more invariants... + for (Block b : getAllBlocks()) { + + // Each node in the block should have this block as its parent. + for (Node n : b.getNodes()) { + if (!Objects.equals(n.getBlock(), b)) { + throw new IllegalStateException( + "Node " + + n + + " in block " + + b + + " incorrectly believes it belongs to " + + n.getBlock()); + } + } + + // Each successor should have this block in its predecessors. + for (Block succ : b.getSuccessors()) { + if (!succ.getPredecessors().contains(b)) { + throw new IllegalStateException( + "Block " + + b + + " has successor " + + succ + + " but does not appear in that successor's predecessors"); + } + } + + // Each predecessor should have this block in its successors. + for (Block pred : b.getPredecessors()) { + if (!pred.getSuccessors().contains(b)) { + throw new IllegalStateException( + "Block " + + b + + " has predecessor " + + pred + + " but does not appear in that predecessor's successors"); + } + } } - } - - // Each predecessor should have this block in its successors. - for (Block pred : b.getPredecessors()) { - if (!pred.getSuccessors().contains(b)) { - throw new IllegalStateException( - "Block " - + b - + " has predecessor " - + pred - + " but does not appear in that predecessor's successors"); + } + + /** + * Returns the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for + * trees that don't produce a value. + * + * @param t a tree + * @return the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for + * trees that don't produce a value + */ + public @Nullable Set getNodesCorrespondingToTree(Tree t) { + if (convertedTreeLookup.containsKey(t)) { + return convertedTreeLookup.get(t); + } else { + return treeLookup.get(t); } - } } - } - - /** - * Returns the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for - * trees that don't produce a value. - * - * @param t a tree - * @return the set of {@link Node}s to which the {@link Tree} {@code t} corresponds, or null for - * trees that don't produce a value - */ - public @Nullable Set getNodesCorrespondingToTree(Tree t) { - if (convertedTreeLookup.containsKey(t)) { - return convertedTreeLookup.get(t); - } else { - return treeLookup.get(t); + + /** + * Returns the entry block of the control flow graph. + * + * @return the entry block of the control flow graph + */ + public SpecialBlock getEntryBlock() { + return entryBlock; } - } - - /** - * Returns the entry block of the control flow graph. - * - * @return the entry block of the control flow graph - */ - public SpecialBlock getEntryBlock() { - return entryBlock; - } - - public List getReturnNodes() { - return returnNodes; - } - - public SpecialBlock getRegularExitBlock() { - return regularExitBlock; - } - - public SpecialBlock getExceptionalExitBlock() { - return exceptionalExitBlock; - } - - /** - * Returns the AST this CFG corresponds to. - * - * @return the AST this CFG corresponds to - */ - public UnderlyingAST getUnderlyingAST() { - return underlyingAST; - } - - /** - * Returns the set of all basic blocks in this control flow graph. - * - * @return the set of all basic blocks in this control flow graph - */ - public Set getAllBlocks( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { - Set visited = new LinkedHashSet<>(); - // worklist is always a subset of visited; any block in worklist is also in visited. - Queue worklist = new ArrayDeque<>(); - Block cur = entryBlock; - visited.add(entryBlock); - - // traverse the whole control flow graph - while (true) { - if (cur == null) { - break; - } - - for (Block b : cur.getSuccessors()) { - if (visited.add(b)) { - worklist.add(b); - } - } - cur = worklist.poll(); + public List getReturnNodes() { + return returnNodes; } - return visited; - } - - /** - * Returns all nodes in this control flow graph. - * - * @return all nodes in this control flow graph - */ - public List getAllNodes( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { - List result = new ArrayList<>(); - for (Block b : getAllBlocks()) { - result.addAll(b.getNodes()); + public SpecialBlock getRegularExitBlock() { + return regularExitBlock; } - return result; - } - - /** - * Returns the set of all basic blocks in this control flow graph, except those that are - * only reachable via an exception whose type is ignored by parameter {@code - * shouldIgnoreException}. - * - * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be - * ignored - * @return the set of all basic blocks in this control flow graph, except those that are - * only reachable via an exception whose type is ignored by {@code shouldIgnoreException} - */ - public Set getAllBlocks( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, - Function shouldIgnoreException) { - // This is the return value of the method. - Set visited = new LinkedHashSet<>(); - // `worklist` is always a subset of `visited`; any block in `worklist` is also in `visited`. - Queue worklist = new ArrayDeque<>(); - Block cur = entryBlock; - visited.add(entryBlock); - - // Traverse the whole control flow graph. - while (cur != null) { - if (cur instanceof ExceptionBlock) { - for (Map.Entry> entry : - ((ExceptionBlock) cur).getExceptionalSuccessors().entrySet()) { - if (!shouldIgnoreException.apply(entry.getKey())) { - for (Block b : entry.getValue()) { - if (visited.add(b)) { - worklist.add(b); - } + + public SpecialBlock getExceptionalExitBlock() { + return exceptionalExitBlock; + } + + /** + * Returns the AST this CFG corresponds to. + * + * @return the AST this CFG corresponds to + */ + public UnderlyingAST getUnderlyingAST() { + return underlyingAST; + } + + /** + * Returns the set of all basic blocks in this control flow graph. + * + * @return the set of all basic blocks in this control flow graph + */ + public Set getAllBlocks( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { + Set visited = new LinkedHashSet<>(); + // worklist is always a subset of visited; any block in worklist is also in visited. + Queue worklist = new ArrayDeque<>(); + Block cur = entryBlock; + visited.add(entryBlock); + + // traverse the whole control flow graph + while (true) { + if (cur == null) { + break; } - } + + for (Block b : cur.getSuccessors()) { + if (visited.add(b)) { + worklist.add(b); + } + } + + cur = worklist.poll(); + } + + return visited; + } + + /** + * Returns all nodes in this control flow graph. + * + * @return all nodes in this control flow graph + */ + public List getAllNodes( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this) { + List result = new ArrayList<>(); + for (Block b : getAllBlocks()) { + result.addAll(b.getNodes()); } - Block b = ((SingleSuccessorBlockImpl) cur).getSuccessor(); - if (b != null && visited.add(b)) { - worklist.add(b); + return result; + } + + /** + * Returns the set of all basic blocks in this control flow graph, except those that are + * only reachable via an exception whose type is ignored by parameter {@code + * shouldIgnoreException}. + * + * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be + * ignored + * @return the set of all basic blocks in this control flow graph, except those that are + * only reachable via an exception whose type is ignored by {@code shouldIgnoreException} + */ + public Set getAllBlocks( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, + Function shouldIgnoreException) { + // This is the return value of the method. + Set visited = new LinkedHashSet<>(); + // `worklist` is always a subset of `visited`; any block in `worklist` is also in `visited`. + Queue worklist = new ArrayDeque<>(); + Block cur = entryBlock; + visited.add(entryBlock); + + // Traverse the whole control flow graph. + while (cur != null) { + if (cur instanceof ExceptionBlock) { + for (Map.Entry> entry : + ((ExceptionBlock) cur).getExceptionalSuccessors().entrySet()) { + if (!shouldIgnoreException.apply(entry.getKey())) { + for (Block b : entry.getValue()) { + if (visited.add(b)) { + worklist.add(b); + } + } + } + } + Block b = ((SingleSuccessorBlockImpl) cur).getSuccessor(); + if (b != null && visited.add(b)) { + worklist.add(b); + } + + } else { + for (Block b : cur.getSuccessors()) { + if (visited.add(b)) { + worklist.add(b); + } + } + } + cur = worklist.poll(); } - } else { - for (Block b : cur.getSuccessors()) { - if (visited.add(b)) { - worklist.add(b); - } + return visited; + } + + /** + * Returns the list of all nodes in this control flow graph, except those that are only + * reachable via an exception whose type is ignored by parameter {@code shouldIgnoreException}. + * + * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be + * ignored + * @return the list of all nodes in this control flow graph, except those that are only + * reachable via an exception whose type is ignored by {@code shouldIgnoreException} + */ + public List getAllNodes( + @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, + Function shouldIgnoreException) { + List result = new ArrayList<>(); + getAllBlocks(shouldIgnoreException).forEach(b -> result.addAll(b.getNodes())); + return result; + } + + /** + * Returns all basic blocks in this control flow graph, in reversed depth-first postorder. + * Blocks may appear more than once in the sequence. + * + * @return the list of all basic block in this control flow graph in reversed depth-first + * postorder sequence + */ + public List getDepthFirstOrderedBlocks() { + List dfsOrderResult = new ArrayList<>(); + Set visited = new HashSet<>(); + // worklist can contain values that are not yet in visited. + Deque worklist = new ArrayDeque<>(); + worklist.add(entryBlock); + while (!worklist.isEmpty()) { + Block cur = worklist.getLast(); + if (visited.contains(cur)) { + dfsOrderResult.add(cur); + worklist.removeLast(); + } else { + visited.add(cur); + + for (Block b : cur.getSuccessors()) { + if (!visited.contains(b)) { + worklist.add(b); + } + } + } } - } - cur = worklist.poll(); + + Collections.reverse(dfsOrderResult); + return dfsOrderResult; + } + + /** + * Returns an unmodifiable view of the tree-lookup map. Ignores convertedTreeLookup, though + * {@link #getNodesCorrespondingToTree} uses that field. + * + * @return the unmodifiable tree-lookup map + */ + public UnmodifiableIdentityHashMap> getTreeLookup() { + return UnmodifiableIdentityHashMap.wrap(treeLookup); + } + + /** + * Returns an unmodifiable view of the lookup-map of the binary tree for a postfix expression. + * + * @return the unmodifiable lookup-map of the binary tree for a postfix expression + */ + public UnmodifiableIdentityHashMap getPostfixNodeLookup() { + return UnmodifiableIdentityHashMap.wrap(postfixNodeLookup); } - return visited; - } - - /** - * Returns the list of all nodes in this control flow graph, except those that are only - * reachable via an exception whose type is ignored by parameter {@code shouldIgnoreException}. - * - * @param shouldIgnoreException returns true if it is passed a {@code TypeMirror} that should be - * ignored - * @return the list of all nodes in this control flow graph, except those that are only - * reachable via an exception whose type is ignored by {@code shouldIgnoreException} - */ - public List getAllNodes( - @UnknownInitialization(ControlFlowGraph.class) ControlFlowGraph this, - Function shouldIgnoreException) { - List result = new ArrayList<>(); - getAllBlocks(shouldIgnoreException).forEach(b -> result.addAll(b.getNodes())); - return result; - } - - /** - * Returns all basic blocks in this control flow graph, in reversed depth-first postorder. Blocks - * may appear more than once in the sequence. - * - * @return the list of all basic block in this control flow graph in reversed depth-first - * postorder sequence - */ - public List getDepthFirstOrderedBlocks() { - List dfsOrderResult = new ArrayList<>(); - Set visited = new HashSet<>(); - // worklist can contain values that are not yet in visited. - Deque worklist = new ArrayDeque<>(); - worklist.add(entryBlock); - while (!worklist.isEmpty()) { - Block cur = worklist.getLast(); - if (visited.contains(cur)) { - dfsOrderResult.add(cur); - worklist.removeLast(); - } else { - visited.add(cur); - - for (Block b : cur.getSuccessors()) { - if (!visited.contains(b)) { - worklist.add(b); - } + /** + * Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in + * the CFG, or null otherwise. + * + * @param t a tree that might correspond to a node in the CFG + * @return the method that contains {@code t}'s Node, or null + */ + public @Nullable MethodTree getContainingMethod(Tree t) { + if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; + return cfgMethod.getMethod(); } - } + return null; } - Collections.reverse(dfsOrderResult); - return dfsOrderResult; - } - - /** - * Returns an unmodifiable view of the tree-lookup map. Ignores convertedTreeLookup, though {@link - * #getNodesCorrespondingToTree} uses that field. - * - * @return the unmodifiable tree-lookup map - */ - public UnmodifiableIdentityHashMap> getTreeLookup() { - return UnmodifiableIdentityHashMap.wrap(treeLookup); - } - - /** - * Returns an unmodifiable view of the lookup-map of the binary tree for a postfix expression. - * - * @return the unmodifiable lookup-map of the binary tree for a postfix expression - */ - public UnmodifiableIdentityHashMap getPostfixNodeLookup() { - return UnmodifiableIdentityHashMap.wrap(postfixNodeLookup); - } - - /** - * Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in - * the CFG, or null otherwise. - * - * @param t a tree that might correspond to a node in the CFG - * @return the method that contains {@code t}'s Node, or null - */ - public @Nullable MethodTree getContainingMethod(Tree t) { - if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { - UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; - return cfgMethod.getMethod(); + /** + * Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in + * the CFG, or null otherwise. + * + * @param t a tree that might be within a class + * @return the class that contains the given tree, or null + */ + public @Nullable ClassTree getContainingClass(Tree t) { + if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; + return cfgMethod.getClassTree(); + } + return null; } - return null; - } - - /** - * Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps to a {@link Node} in the - * CFG, or null otherwise. - * - * @param t a tree that might be within a class - * @return the class that contains the given tree, or null - */ - public @Nullable ClassTree getContainingClass(Tree t) { - if (treeLookup.containsKey(t) && underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { - UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; - return cfgMethod.getClassTree(); + + public List getDeclaredClasses() { + return declaredClasses; } - return null; - } - - public List getDeclaredClasses() { - return declaredClasses; - } - - public List getDeclaredLambdas() { - return declaredLambdas; - } - - @Override - public String toString() { - CFGVisualizer viz = new StringCFGVisualizer<>(); - viz.init(Collections.singletonMap("verbose", true)); - Map res = viz.visualize(this, this.getEntryBlock(), null); - viz.shutdown(); - if (res == null) { - return "unvisualizable " + getClass().getCanonicalName(); + + public List getDeclaredLambdas() { + return declaredLambdas; } - String stringGraph = (String) res.get("stringGraph"); - return stringGraph == null ? "unvisualizable " + getClass().getCanonicalName() : stringGraph; - } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public String toStringDebug() { - String className = this.getClass().getSimpleName(); - if (className.equals("ControlFlowGraph") && this.getClass() != ControlFlowGraph.class) { - className = this.getClass().getCanonicalName(); + + @Override + public String toString() { + CFGVisualizer viz = new StringCFGVisualizer<>(); + viz.init(Collections.singletonMap("verbose", true)); + Map res = viz.visualize(this, this.getEntryBlock(), null); + viz.shutdown(); + if (res == null) { + return "unvisualizable " + getClass().getCanonicalName(); + } + String stringGraph = (String) res.get("stringGraph"); + return stringGraph == null + ? "unvisualizable " + getClass().getCanonicalName() + : stringGraph; } - StringJoiner result = new StringJoiner(String.format("%n ")); - result.add(className + " #" + getUid() + " {"); - result.add("entryBlock=" + entryBlock); - result.add("regularExitBlock=" + regularExitBlock); - result.add("exceptionalExitBlock=" + exceptionalExitBlock); - String astString = underlyingAST.toString().replaceAll("\\s", " "); - if (astString.length() > 65) { - astString = "\"" + astString.substring(0, 60) + "\""; + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public String toStringDebug() { + String className = this.getClass().getSimpleName(); + if (className.equals("ControlFlowGraph") && this.getClass() != ControlFlowGraph.class) { + className = this.getClass().getCanonicalName(); + } + + StringJoiner result = new StringJoiner(String.format("%n ")); + result.add(className + " #" + getUid() + " {"); + result.add("entryBlock=" + entryBlock); + result.add("regularExitBlock=" + regularExitBlock); + result.add("exceptionalExitBlock=" + exceptionalExitBlock); + String astString = underlyingAST.toString().replaceAll("\\s", " "); + if (astString.length() > 65) { + astString = "\"" + astString.substring(0, 60) + "\""; + } + result.add("underlyingAST=" + underlyingAST); + result.add("treeLookup=" + AnalysisResult.treeLookupToString(treeLookup)); + result.add("convertedTreeLookup=" + AnalysisResult.treeLookupToString(convertedTreeLookup)); + result.add("postfixLookup=" + postfixNodeLookup); + result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); + result.add("declaredClasses=" + declaredClasses); + result.add("declaredLambdas=" + declaredLambdas); + result.add("}"); + return result.toString(); } - result.add("underlyingAST=" + underlyingAST); - result.add("treeLookup=" + AnalysisResult.treeLookupToString(treeLookup)); - result.add("convertedTreeLookup=" + AnalysisResult.treeLookupToString(convertedTreeLookup)); - result.add("postfixLookup=" + postfixNodeLookup); - result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); - result.add("declaredClasses=" + declaredClasses); - result.add("declaredLambdas=" + declaredLambdas); - result.add("}"); - return result.toString(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java index 09f41866e10..d08e7174eb7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java @@ -4,242 +4,247 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; -import java.util.concurrent.atomic.AtomicLong; + import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.plumelib.util.StringsPlume; import org.plumelib.util.UniqueId; +import java.util.concurrent.atomic.AtomicLong; + /** * Represents an abstract syntax tree of type {@link Tree} that underlies a given control flow * graph. */ public abstract class UnderlyingAST implements UniqueId { - /** The kinds of underlying ASTs. */ - public enum Kind { - /** The underlying code is a whole method. */ - METHOD, - /** The underlying code is a lambda expression. */ - LAMBDA, - - /** The underlying code is an arbitrary Java statement or expression. */ - ARBITRARY_CODE, - } - - /** The kind of the underlying AST. */ - protected final Kind kind; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid(@UnknownInitialization UnderlyingAST this) { - return uid; - } - - /** - * Creates an UnderlyingAST. - * - * @param kind the kind of the AST - */ - protected UnderlyingAST(Kind kind) { - this.kind = kind; - } - - /** - * Returns the code that corresponds to the CFG. For a method or lamdda, this returns the body. - * For other constructs, it returns the tree itself (a statement or expression). - * - * @return the code that corresponds to the CFG - */ - public abstract Tree getCode(); - - public Kind getKind() { - return kind; - } - - /** If the underlying AST is a method. */ - public static class CFGMethod extends UnderlyingAST { - - /** The method declaration. */ - protected final MethodTree method; - - /** The class tree this method belongs to. */ - protected final ClassTree classTree; - - public CFGMethod(MethodTree method, ClassTree classTree) { - super(Kind.METHOD); - this.method = method; - this.classTree = classTree; - } - - @Override - public Tree getCode() { - return method.getBody(); - } - - public MethodTree getMethod() { - return method; - } - - /** - * Returns the name of the method. - * - * @return the name of the method - */ - public String getMethodName() { - return method.getName().toString(); - } - - /** - * Returns the class tree this method belongs to. - * - * @return the class tree this method belongs to - */ - public ClassTree getClassTree() { - return classTree; - } - - /** - * Returns the simple name of the enclosing class. - * - * @return the simple name of the enclosing class - */ - public String getSimpleClassName() { - return classTree.getSimpleName().toString(); - } + /** The kinds of underlying ASTs. */ + public enum Kind { + /** The underlying code is a whole method. */ + METHOD, + /** The underlying code is a lambda expression. */ + LAMBDA, - @Override - public String toString() { - return StringsPlume.joinLines("CFGMethod(", method, ")"); + /** The underlying code is an arbitrary Java statement or expression. */ + ARBITRARY_CODE, } - } - - /** If the underlying AST is a lambda. */ - public static class CFGLambda extends UnderlyingAST { - - /** The lambda expression. */ - private final LambdaExpressionTree lambda; - /** The enclosing class of the lambda. */ - private final ClassTree classTree; + /** The kind of the underlying AST. */ + protected final Kind kind; - /** The enclosing method of the lambda. */ - private final @Nullable MethodTree enclosingMethod; + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); - /** - * Create a new CFGLambda. - * - * @param lambda the lambda expression - * @param classTree the enclosing class of the lambda - * @param enclosingMethod the enclosing method of the lambda - */ - public CFGLambda( - LambdaExpressionTree lambda, ClassTree classTree, @Nullable MethodTree enclosingMethod) { - super(Kind.LAMBDA); - this.lambda = lambda; - this.enclosingMethod = enclosingMethod; - this.classTree = classTree; - } + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); @Override - public Tree getCode() { - return lambda.getBody(); + public long getUid(@UnknownInitialization UnderlyingAST this) { + return uid; } /** - * Returns the lambda expression tree. + * Creates an UnderlyingAST. * - * @return the lambda expression tree + * @param kind the kind of the AST */ - public LambdaExpressionTree getLambdaTree() { - return lambda; + protected UnderlyingAST(Kind kind) { + this.kind = kind; } /** - * Returns the enclosing class of the lambda. + * Returns the code that corresponds to the CFG. For a method or lamdda, this returns the body. + * For other constructs, it returns the tree itself (a statement or expression). * - * @return the enclosing class of the lambda + * @return the code that corresponds to the CFG */ - public ClassTree getClassTree() { - return classTree; + public abstract Tree getCode(); + + public Kind getKind() { + return kind; + } + + /** If the underlying AST is a method. */ + public static class CFGMethod extends UnderlyingAST { + + /** The method declaration. */ + protected final MethodTree method; + + /** The class tree this method belongs to. */ + protected final ClassTree classTree; + + public CFGMethod(MethodTree method, ClassTree classTree) { + super(Kind.METHOD); + this.method = method; + this.classTree = classTree; + } + + @Override + public Tree getCode() { + return method.getBody(); + } + + public MethodTree getMethod() { + return method; + } + + /** + * Returns the name of the method. + * + * @return the name of the method + */ + public String getMethodName() { + return method.getName().toString(); + } + + /** + * Returns the class tree this method belongs to. + * + * @return the class tree this method belongs to + */ + public ClassTree getClassTree() { + return classTree; + } + + /** + * Returns the simple name of the enclosing class. + * + * @return the simple name of the enclosing class + */ + public String getSimpleClassName() { + return classTree.getSimpleName().toString(); + } + + @Override + public String toString() { + return StringsPlume.joinLines("CFGMethod(", method, ")"); + } + } + + /** If the underlying AST is a lambda. */ + public static class CFGLambda extends UnderlyingAST { + + /** The lambda expression. */ + private final LambdaExpressionTree lambda; + + /** The enclosing class of the lambda. */ + private final ClassTree classTree; + + /** The enclosing method of the lambda. */ + private final @Nullable MethodTree enclosingMethod; + + /** + * Create a new CFGLambda. + * + * @param lambda the lambda expression + * @param classTree the enclosing class of the lambda + * @param enclosingMethod the enclosing method of the lambda + */ + public CFGLambda( + LambdaExpressionTree lambda, + ClassTree classTree, + @Nullable MethodTree enclosingMethod) { + super(Kind.LAMBDA); + this.lambda = lambda; + this.enclosingMethod = enclosingMethod; + this.classTree = classTree; + } + + @Override + public Tree getCode() { + return lambda.getBody(); + } + + /** + * Returns the lambda expression tree. + * + * @return the lambda expression tree + */ + public LambdaExpressionTree getLambdaTree() { + return lambda; + } + + /** + * Returns the enclosing class of the lambda. + * + * @return the enclosing class of the lambda + */ + public ClassTree getClassTree() { + return classTree; + } + + /** + * Returns the simple name of the enclosing class. + * + * @return the simple name of the enclosing class + */ + public String getSimpleClassName() { + return classTree.getSimpleName().toString(); + } + + /** + * Returns the enclosing method of the lambda. + * + * @return the enclosing method of the lambda, or {@code null} if there is no enclosing + * method + */ + public @Nullable MethodTree getEnclosingMethod() { + return enclosingMethod; + } + + /** + * Returns the name of the enclosing method of the lambda. + * + * @return the name of the enclosing method of the lambda, or {@code null} if there is no + * enclosing method + */ + public @Nullable String getEnclosingMethodName() { + return enclosingMethod == null ? null : enclosingMethod.getName().toString(); + } + + @Override + public String toString() { + return StringsPlume.joinLines("CFGLambda(", lambda, ")"); + } } /** - * Returns the simple name of the enclosing class. - * - * @return the simple name of the enclosing class - */ - public String getSimpleClassName() { - return classTree.getSimpleName().toString(); - } - - /** - * Returns the enclosing method of the lambda. - * - * @return the enclosing method of the lambda, or {@code null} if there is no enclosing method - */ - public @Nullable MethodTree getEnclosingMethod() { - return enclosingMethod; - } - - /** - * Returns the name of the enclosing method of the lambda. - * - * @return the name of the enclosing method of the lambda, or {@code null} if there is no - * enclosing method - */ - public @Nullable String getEnclosingMethodName() { - return enclosingMethod == null ? null : enclosingMethod.getName().toString(); - } - - @Override - public String toString() { - return StringsPlume.joinLines("CFGLambda(", lambda, ")"); - } - } - - /** - * If the underlying AST is a statement or expression. This is for field definitions (with - * initializers) and initializer blocks. - */ - public static class CFGStatement extends UnderlyingAST { - - protected final Tree code; - - /** The class tree this method belongs to. */ - protected final ClassTree classTree; - - public CFGStatement(Tree code, ClassTree classTree) { - super(Kind.ARBITRARY_CODE); - this.code = code; - this.classTree = classTree; - } - - @Override - public Tree getCode() { - return code; - } - - public ClassTree getClassTree() { - return classTree; - } - - /** - * Returns the simple name of the enclosing class. - * - * @return the simple name of the enclosing class + * If the underlying AST is a statement or expression. This is for field definitions (with + * initializers) and initializer blocks. */ - public String getSimpleClassName() { - return classTree.getSimpleName().toString(); - } - - @Override - public String toString() { - return StringsPlume.joinLines("CFGStatement(", code, ")"); + public static class CFGStatement extends UnderlyingAST { + + protected final Tree code; + + /** The class tree this method belongs to. */ + protected final ClassTree classTree; + + public CFGStatement(Tree code, ClassTree classTree) { + super(Kind.ARBITRARY_CODE); + this.code = code; + this.classTree = classTree; + } + + @Override + public Tree getCode() { + return code; + } + + public ClassTree getClassTree() { + return classTree; + } + + /** + * Returns the simple name of the enclosing class. + * + * @return the simple name of the enclosing class + */ + public String getSimpleClassName() { + return classTree.getSimpleName().toString(); + } + + @Override + public String toString() { + return StringsPlume.joinLines("CFGStatement(", code, ")"); + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java index ed18d829acf..007f48fe755 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java @@ -1,70 +1,71 @@ package org.checkerframework.dataflow.cfg.block; -import java.util.List; -import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.qual.Pure; import org.plumelib.util.UniqueId; +import java.util.List; +import java.util.Set; + /** Represents a basic block in a control flow graph. */ public interface Block extends UniqueId { - /** The types of basic blocks. */ - enum BlockType { + /** The types of basic blocks. */ + enum BlockType { - /** A regular basic block. */ - REGULAR_BLOCK, + /** A regular basic block. */ + REGULAR_BLOCK, - /** A conditional basic block. */ - CONDITIONAL_BLOCK, + /** A conditional basic block. */ + CONDITIONAL_BLOCK, - /** A special basic block. */ - SPECIAL_BLOCK, + /** A special basic block. */ + SPECIAL_BLOCK, - /** A basic block that can throw an exception. */ - EXCEPTION_BLOCK, - } + /** A basic block that can throw an exception. */ + EXCEPTION_BLOCK, + } - /** - * Returns the type of this basic block. - * - * @return the type of this basic block - */ - BlockType getType(); + /** + * Returns the type of this basic block. + * + * @return the type of this basic block + */ + BlockType getType(); - /** - * Returns the predecessors of this basic block. - * - * @return the predecessors of this basic block - */ - Set getPredecessors(); + /** + * Returns the predecessors of this basic block. + * + * @return the predecessors of this basic block + */ + Set getPredecessors(); - /** - * Returns the successors of this basic block. - * - * @return the successors of this basic block - */ - Set getSuccessors(); + /** + * Returns the successors of this basic block. + * + * @return the successors of this basic block + */ + Set getSuccessors(); - /** - * Returns the nodes contained within this basic block. The list may be empty. - * - *

          The following invariant holds. - * - *

          -   * forall n in getNodes() :: n.getBlock() == this
          -   * 
          - * - * @return the nodes contained within this basic block - */ - @Pure - List getNodes(); + /** + * Returns the nodes contained within this basic block. The list may be empty. + * + *

          The following invariant holds. + * + *

          +     * forall n in getNodes() :: n.getBlock() == this
          +     * 
          + * + * @return the nodes contained within this basic block + */ + @Pure + List getNodes(); - /** - * Returns the last node of this block, or null if none. - * - * @return the last node of this block or {@code null} - */ - @Nullable Node getLastNode(); + /** + * Returns the last node of this block, or null if none. + * + * @return the last node of this block or {@code null} + */ + @Nullable Node getLastNode(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java index 8c2fadfeaab..3e007e49f01 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java @@ -1,58 +1,59 @@ package org.checkerframework.dataflow.cfg.block; -import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.plumelib.util.ArraySet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + /** Base class of the {@link Block} implementation hierarchy. */ public abstract class BlockImpl implements Block { - /** The type of this basic block. */ - protected final BlockType type; - - /** The set of predecessors. */ - protected final Set predecessors; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid(@UnknownInitialization BlockImpl this) { - return uid; - } - - /** - * Create a new BlockImpl. - * - * @param type the type of this basic block - */ - protected BlockImpl(BlockType type) { - this.type = type; - // Most blocks have few predecessors. - this.predecessors = new ArraySet<>(2); - } - - @Override - public BlockType getType() { - return type; - } - - @Override - public Set getPredecessors() { - // Not "Collections.unmodifiableSet(predecessors)" which has nondeterministic iteration - // order. - return new ArraySet<>(predecessors); - } - - public void addPredecessor(BlockImpl pred) { - predecessors.add(pred); - } - - public void removePredecessor(BlockImpl pred) { - predecessors.remove(pred); - } + /** The type of this basic block. */ + protected final BlockType type; + + /** The set of predecessors. */ + protected final Set predecessors; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid(@UnknownInitialization BlockImpl this) { + return uid; + } + + /** + * Create a new BlockImpl. + * + * @param type the type of this basic block + */ + protected BlockImpl(BlockType type) { + this.type = type; + // Most blocks have few predecessors. + this.predecessors = new ArraySet<>(2); + } + + @Override + public BlockType getType() { + return type; + } + + @Override + public Set getPredecessors() { + // Not "Collections.unmodifiableSet(predecessors)" which has nondeterministic iteration + // order. + return new ArraySet<>(predecessors); + } + + public void addPredecessor(BlockImpl pred) { + predecessors.add(pred); + } + + public void removePredecessor(BlockImpl pred) { + predecessors.remove(pred); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java index 6ee0d627905..334b8f1ffce 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java @@ -9,45 +9,45 @@ /** Represents a conditional basic block. */ public interface ConditionalBlock extends Block { - /** - * Returns the entry block of the then branch. - * - * @return the entry block of the then branch - */ - Block getThenSuccessor(); - - /** - * Returns the entry block of the else branch. - * - * @return the entry block of the else branch - */ - Block getElseSuccessor(); - - /** - * Returns the flow rule for information flowing from this block to its then successor. - * - * @return the flow rule for information flowing from this block to its then successor - */ - FlowRule getThenFlowRule(); - - /** - * Returns the flow rule for information flowing from this block to its else successor. - * - * @return the flow rule for information flowing from this block to its else successor - */ - FlowRule getElseFlowRule(); - - /** - * Set the flow rule for information flowing from this block to its then successor. - * - * @param rule the new flow rule for information flowing from this block to its then successor - */ - void setThenFlowRule(FlowRule rule); - - /** - * Set the flow rule for information flowing from this block to its else successor. - * - * @param rule the new flow rule for information flowing from this block to its else successor - */ - void setElseFlowRule(FlowRule rule); + /** + * Returns the entry block of the then branch. + * + * @return the entry block of the then branch + */ + Block getThenSuccessor(); + + /** + * Returns the entry block of the else branch. + * + * @return the entry block of the else branch + */ + Block getElseSuccessor(); + + /** + * Returns the flow rule for information flowing from this block to its then successor. + * + * @return the flow rule for information flowing from this block to its then successor + */ + FlowRule getThenFlowRule(); + + /** + * Returns the flow rule for information flowing from this block to its else successor. + * + * @return the flow rule for information flowing from this block to its else successor + */ + FlowRule getElseFlowRule(); + + /** + * Set the flow rule for information flowing from this block to its then successor. + * + * @param rule the new flow rule for information flowing from this block to its then successor + */ + void setThenFlowRule(FlowRule rule); + + /** + * Set the flow rule for information flowing from this block to its else successor. + * + * @param rule the new flow rule for information flowing from this block to its else successor + */ + void setElseFlowRule(FlowRule rule); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java index f7edfa36698..7e0c08f1af1 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java @@ -1,116 +1,119 @@ package org.checkerframework.dataflow.cfg.block; -import java.util.Collections; -import java.util.List; -import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store.FlowRule; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.ArraySet; +import java.util.Collections; +import java.util.List; +import java.util.Set; + /** Implementation of a conditional basic block. */ public class ConditionalBlockImpl extends BlockImpl implements ConditionalBlock { - /** Successor of the then branch. */ - protected @Nullable BlockImpl thenSuccessor; - - /** Successor of the else branch. */ - protected @Nullable BlockImpl elseSuccessor; - - /** - * The initial value says that the THEN store before a conditional block flows to BOTH of the - * stores of the then successor. - */ - protected FlowRule thenFlowRule = FlowRule.THEN_TO_BOTH; - - /** - * The initial value says that the ELSE store before a conditional block flows to BOTH of the - * stores of the else successor. - */ - protected FlowRule elseFlowRule = FlowRule.ELSE_TO_BOTH; - - /** - * Initialize an empty conditional basic block to be filled with contents and linked to other - * basic blocks later. - */ - public ConditionalBlockImpl() { - super(BlockType.CONDITIONAL_BLOCK); - } - - /** Set the then branch successor. */ - public void setThenSuccessor(BlockImpl b) { - thenSuccessor = b; - b.addPredecessor(this); - } - - /** Set the else branch successor. */ - public void setElseSuccessor(BlockImpl b) { - elseSuccessor = b; - b.addPredecessor(this); - } - - @Override - public Block getThenSuccessor() { - if (thenSuccessor == null) { - throw new BugInCF("Requested thenSuccessor for conditional block before initialization"); + /** Successor of the then branch. */ + protected @Nullable BlockImpl thenSuccessor; + + /** Successor of the else branch. */ + protected @Nullable BlockImpl elseSuccessor; + + /** + * The initial value says that the THEN store before a conditional block flows to BOTH of the + * stores of the then successor. + */ + protected FlowRule thenFlowRule = FlowRule.THEN_TO_BOTH; + + /** + * The initial value says that the ELSE store before a conditional block flows to BOTH of the + * stores of the else successor. + */ + protected FlowRule elseFlowRule = FlowRule.ELSE_TO_BOTH; + + /** + * Initialize an empty conditional basic block to be filled with contents and linked to other + * basic blocks later. + */ + public ConditionalBlockImpl() { + super(BlockType.CONDITIONAL_BLOCK); + } + + /** Set the then branch successor. */ + public void setThenSuccessor(BlockImpl b) { + thenSuccessor = b; + b.addPredecessor(this); + } + + /** Set the else branch successor. */ + public void setElseSuccessor(BlockImpl b) { + elseSuccessor = b; + b.addPredecessor(this); + } + + @Override + public Block getThenSuccessor() { + if (thenSuccessor == null) { + throw new BugInCF( + "Requested thenSuccessor for conditional block before initialization"); + } + return thenSuccessor; + } + + @Override + public Block getElseSuccessor() { + if (elseSuccessor == null) { + throw new BugInCF( + "Requested elseSuccessor for conditional block before initialization"); + } + return elseSuccessor; + } + + @Override + public Set getSuccessors() { + Set result = new ArraySet<>(2); + result.add(getThenSuccessor()); + result.add(getElseSuccessor()); + return result; + } + + @Override + public FlowRule getThenFlowRule() { + return thenFlowRule; + } + + @Override + public FlowRule getElseFlowRule() { + return elseFlowRule; + } + + @Override + public void setThenFlowRule(FlowRule rule) { + thenFlowRule = rule; + } + + @Override + public void setElseFlowRule(FlowRule rule) { + elseFlowRule = rule; + } + + /** + * {@inheritDoc} + * + *

          This implementation returns an empty list. + */ + @Override + public List getNodes() { + return Collections.emptyList(); + } + + @Override + public @Nullable Node getLastNode() { + return null; } - return thenSuccessor; - } - @Override - public Block getElseSuccessor() { - if (elseSuccessor == null) { - throw new BugInCF("Requested elseSuccessor for conditional block before initialization"); + @Override + public String toString() { + return "ConditionalBlock()"; } - return elseSuccessor; - } - - @Override - public Set getSuccessors() { - Set result = new ArraySet<>(2); - result.add(getThenSuccessor()); - result.add(getElseSuccessor()); - return result; - } - - @Override - public FlowRule getThenFlowRule() { - return thenFlowRule; - } - - @Override - public FlowRule getElseFlowRule() { - return elseFlowRule; - } - - @Override - public void setThenFlowRule(FlowRule rule) { - thenFlowRule = rule; - } - - @Override - public void setElseFlowRule(FlowRule rule) { - elseFlowRule = rule; - } - - /** - * {@inheritDoc} - * - *

          This implementation returns an empty list. - */ - @Override - public List getNodes() { - return Collections.emptyList(); - } - - @Override - public @Nullable Node getLastNode() { - return null; - } - - @Override - public String toString() { - return "ConditionalBlock()"; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java index cf9b3f3b4f4..83b028db751 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java @@ -1,10 +1,12 @@ package org.checkerframework.dataflow.cfg.block; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.qual.Pure; + import java.util.Map; import java.util.Set; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.dataflow.cfg.node.Node; -import org.checkerframework.dataflow.qual.Pure; /** * Represents a basic block that contains exactly one {@link Node} which can throw an exception. @@ -18,19 +20,19 @@ */ public interface ExceptionBlock extends SingleSuccessorBlock { - /** - * Returns the node of this block. - * - * @return the node of this block - */ - @Pure - Node getNode(); + /** + * Returns the node of this block. + * + * @return the node of this block + */ + @Pure + Node getNode(); - /** - * Returns the list of exceptional successor blocks as an unmodifiable map. - * - * @return the list of exceptional successor blocks as an unmodifiable map - */ - @Pure - Map> getExceptionalSuccessors(); + /** + * Returns the list of exceptional successor blocks as an unmodifiable map. + * + * @return the list of exceptional successor blocks as an unmodifiable map + */ + @Pure + Map> getExceptionalSuccessors(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java index 1657661dec5..7d1efd8aff3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java @@ -1,10 +1,5 @@ package org.checkerframework.dataflow.cfg.block; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.javacutil.BugInCF; @@ -12,81 +7,88 @@ import org.plumelib.util.ArraySet; import org.plumelib.util.CollectionsPlume; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + /** Implementation of {@link ExceptionBlock}. */ public class ExceptionBlockImpl extends SingleSuccessorBlockImpl implements ExceptionBlock { - /** The node of this block. */ - protected @Nullable Node node; + /** The node of this block. */ + protected @Nullable Node node; - /** Set of exceptional successors. */ - protected final Map> exceptionalSuccessors; + /** Set of exceptional successors. */ + protected final Map> exceptionalSuccessors; - /** Create an empty exceptional block. */ - public ExceptionBlockImpl() { - super(BlockType.EXCEPTION_BLOCK); - exceptionalSuccessors = new ArrayMap<>(2); - } + /** Create an empty exceptional block. */ + public ExceptionBlockImpl() { + super(BlockType.EXCEPTION_BLOCK); + exceptionalSuccessors = new ArrayMap<>(2); + } - /** Set the node. */ - public void setNode(Node c) { - node = c; - c.setBlock(this); - } + /** Set the node. */ + public void setNode(Node c) { + node = c; + c.setBlock(this); + } - @Override - public Node getNode() { - if (node == null) { - throw new BugInCF("Requested node for exception block before initialization"); + @Override + public Node getNode() { + if (node == null) { + throw new BugInCF("Requested node for exception block before initialization"); + } + return node; } - return node; - } - /** - * {@inheritDoc} - * - *

          This implementation returns a singleton list. - */ - @Override - public List getNodes() { - return Collections.singletonList(getNode()); - } + /** + * {@inheritDoc} + * + *

          This implementation returns a singleton list. + */ + @Override + public List getNodes() { + return Collections.singletonList(getNode()); + } - @Override - public @Nullable Node getLastNode() { - return getNode(); - } + @Override + public @Nullable Node getLastNode() { + return getNode(); + } - /** - * Add an exceptional successor. - * - * @param b the successor - * @param cause the exception type that leads to the given block - */ - public void addExceptionalSuccessor(BlockImpl b, TypeMirror cause) { - Set blocks = exceptionalSuccessors.computeIfAbsent(cause, __ -> new ArraySet<>(2)); - blocks.add(b); - b.addPredecessor(this); - } + /** + * Add an exceptional successor. + * + * @param b the successor + * @param cause the exception type that leads to the given block + */ + public void addExceptionalSuccessor(BlockImpl b, TypeMirror cause) { + Set blocks = exceptionalSuccessors.computeIfAbsent(cause, __ -> new ArraySet<>(2)); + blocks.add(b); + b.addPredecessor(this); + } - @Override - public Map> getExceptionalSuccessors() { - if (exceptionalSuccessors == null) { - return Collections.emptyMap(); + @Override + public Map> getExceptionalSuccessors() { + if (exceptionalSuccessors == null) { + return Collections.emptyMap(); + } + return Collections.unmodifiableMap(exceptionalSuccessors); } - return Collections.unmodifiableMap(exceptionalSuccessors); - } - @Override - public Set getSuccessors() { - Set result = new ArraySet<>(super.getSuccessors()); - for (Set blocks : getExceptionalSuccessors().values()) { - CollectionsPlume.adjoinAll(result, blocks); + @Override + public Set getSuccessors() { + Set result = new ArraySet<>(super.getSuccessors()); + for (Set blocks : getExceptionalSuccessors().values()) { + CollectionsPlume.adjoinAll(result, blocks); + } + return result; } - return result; - } - @Override - public String toString() { - return "ExceptionBlock(" + node + ")"; - } + @Override + public String toString() { + return "ExceptionBlock(" + node + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java index 3883c45ab3e..91648ac8456 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java @@ -6,15 +6,15 @@ /** A regular basic block that contains a sequence of {@link Node}s. */ public interface RegularBlock extends SingleSuccessorBlock { - /** - * Returns the regular successor block. - * - * @return the regular successor block - */ - @Pure - @Nullable Block getRegularSuccessor(); + /** + * Returns the regular successor block. + * + * @return the regular successor block + */ + @Pure + @Nullable Block getRegularSuccessor(); - /** Is this block empty (i.e., does it not contain any contents). */ - @Pure - boolean isEmpty(); + /** Is this block empty (i.e., does it not contain any contents). */ + @Pure + boolean isEmpty(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java index 27e8c8ba087..1197ac77301 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java @@ -1,66 +1,67 @@ package org.checkerframework.dataflow.cfg.block; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.cfg.node.Node; + import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.cfg.node.Node; /** Implementation of a regular basic block. */ public class RegularBlockImpl extends SingleSuccessorBlockImpl implements RegularBlock { - /** Internal representation of the contents. */ - protected final List contents; + /** Internal representation of the contents. */ + protected final List contents; - /** - * Initialize an empty basic block to be filled with contents and linked to other basic blocks - * later. - */ - public RegularBlockImpl() { - super(BlockType.REGULAR_BLOCK); - contents = new ArrayList<>(); - } + /** + * Initialize an empty basic block to be filled with contents and linked to other basic blocks + * later. + */ + public RegularBlockImpl() { + super(BlockType.REGULAR_BLOCK); + contents = new ArrayList<>(); + } - /** Add a node to the contents of this basic block. */ - public void addNode(Node t) { - contents.add(t); - t.setBlock(this); - } + /** Add a node to the contents of this basic block. */ + public void addNode(Node t) { + contents.add(t); + t.setBlock(this); + } - /** Add multiple nodes to the contents of this basic block. */ - public void addNodes(List ts) { - for (Node t : ts) { - addNode(t); + /** Add multiple nodes to the contents of this basic block. */ + public void addNodes(List ts) { + for (Node t : ts) { + addNode(t); + } } - } - /** - * {@inheritDoc} - * - *

          This implementation returns an non-empty list. - */ - @Override - public List getNodes() { - return Collections.unmodifiableList(contents); - } + /** + * {@inheritDoc} + * + *

          This implementation returns an non-empty list. + */ + @Override + public List getNodes() { + return Collections.unmodifiableList(contents); + } - @Override - public @Nullable Node getLastNode() { - return contents.get(contents.size() - 1); - } + @Override + public @Nullable Node getLastNode() { + return contents.get(contents.size() - 1); + } - @Override - public @Nullable BlockImpl getRegularSuccessor() { - return successor; - } + @Override + public @Nullable BlockImpl getRegularSuccessor() { + return successor; + } - @Override - public String toString() { - return "RegularBlock(" + contents + ")"; - } + @Override + public String toString() { + return "RegularBlock(" + contents + ")"; + } - @Override - public boolean isEmpty() { - return contents.isEmpty(); - } + @Override + public boolean isEmpty() { + return contents.isEmpty(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java index 057a78b949e..b7dd3f23df3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java @@ -7,28 +7,28 @@ /** A basic block that has exactly one non-exceptional successor. */ public interface SingleSuccessorBlock extends Block { - /** - * Returns the non-exceptional successor block, or {@code null} if there is no non-exceptional - * successor. - * - * @return the non-exceptional successor block, or {@code null} if there is no non-exceptional - * successor - */ - @Pure - @Nullable Block getSuccessor(); + /** + * Returns the non-exceptional successor block, or {@code null} if there is no non-exceptional + * successor. + * + * @return the non-exceptional successor block, or {@code null} if there is no non-exceptional + * successor + */ + @Pure + @Nullable Block getSuccessor(); - /** - * Returns the flow rule for information flowing from this block to its successor. - * - * @return the flow rule for information flowing from this block to its successor - */ - @Pure - FlowRule getFlowRule(); + /** + * Returns the flow rule for information flowing from this block to its successor. + * + * @return the flow rule for information flowing from this block to its successor + */ + @Pure + FlowRule getFlowRule(); - /** - * Set the flow rule for information flowing from this block to its successor. - * - * @param rule the new flow rule for information flowing from this block to its successor - */ - void setFlowRule(FlowRule rule); + /** + * Set the flow rule for information flowing from this block to its successor. + * + * @param rule the new flow rule for information flowing from this block to its successor + */ + void setFlowRule(FlowRule rule); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java index 996f92deea8..429a17c14ce 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java @@ -1,69 +1,70 @@ package org.checkerframework.dataflow.cfg.block; -import java.util.Collections; -import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store.FlowRule; +import java.util.Collections; +import java.util.Set; + /** * A basic block that has at most one successor. SpecialBlockImpl extends this, but exit blocks have * no successor. */ public abstract class SingleSuccessorBlockImpl extends BlockImpl implements SingleSuccessorBlock { - /** - * Internal representation of the successor. - * - *

          Is set by {@link #setSuccessor}. - */ - protected @Nullable BlockImpl successor; + /** + * Internal representation of the successor. + * + *

          Is set by {@link #setSuccessor}. + */ + protected @Nullable BlockImpl successor; - /** - * The initial value for the rule below says that EACH store at the end of a single successor - * block flows to the corresponding store of the successor. - */ - protected FlowRule flowRule = FlowRule.EACH_TO_EACH; + /** + * The initial value for the rule below says that EACH store at the end of a single successor + * block flows to the corresponding store of the successor. + */ + protected FlowRule flowRule = FlowRule.EACH_TO_EACH; - /** - * Creates a new SingleSuccessorBlock. - * - * @param type the type of this basic block - */ - protected SingleSuccessorBlockImpl(BlockType type) { - super(type); - } + /** + * Creates a new SingleSuccessorBlock. + * + * @param type the type of this basic block + */ + protected SingleSuccessorBlockImpl(BlockType type) { + super(type); + } - @Override - public @Nullable Block getSuccessor() { - return successor; - } + @Override + public @Nullable Block getSuccessor() { + return successor; + } - @Override - public Set getSuccessors() { - if (successor == null) { - return Collections.emptySet(); - } else { - return Collections.singleton(successor); + @Override + public Set getSuccessors() { + if (successor == null) { + return Collections.emptySet(); + } else { + return Collections.singleton(successor); + } } - } - /** - * Set a basic block as the successor of this block. - * - * @param successor the block that will be the successor of this - */ - public void setSuccessor(BlockImpl successor) { - this.successor = successor; - successor.addPredecessor(this); - } + /** + * Set a basic block as the successor of this block. + * + * @param successor the block that will be the successor of this + */ + public void setSuccessor(BlockImpl successor) { + this.successor = successor; + successor.addPredecessor(this); + } - @Override - public FlowRule getFlowRule() { - return flowRule; - } + @Override + public FlowRule getFlowRule() { + return flowRule; + } - @Override - public void setFlowRule(FlowRule rule) { - flowRule = rule; - } + @Override + public void setFlowRule(FlowRule rule) { + flowRule = rule; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java index ed564ec8116..e3d26d7797b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java @@ -11,23 +11,23 @@ */ public interface SpecialBlock extends SingleSuccessorBlock { - /** The types of special basic blocks. */ - public enum SpecialBlockType { + /** The types of special basic blocks. */ + public enum SpecialBlockType { - /** The entry block of a method. */ - ENTRY, + /** The entry block of a method. */ + ENTRY, - /** The exit block of a method. */ - EXIT, + /** The exit block of a method. */ + EXIT, - /** A special exit block of a method for exceptional termination. */ - EXCEPTIONAL_EXIT, - } + /** A special exit block of a method for exceptional termination. */ + EXCEPTIONAL_EXIT, + } - /** - * Returns the type of this special basic block. - * - * @return the type of this special basic block - */ - SpecialBlockType getSpecialType(); + /** + * Returns the type of this special basic block. + * + * @return the type of this special basic block + */ + SpecialBlockType getSpecialType(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java index 9a3bf14cd68..cd461998c3e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java @@ -1,43 +1,44 @@ package org.checkerframework.dataflow.cfg.block; -import java.util.Collections; -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; +import java.util.Collections; +import java.util.List; + /** The implementation of a {@link SpecialBlock}. */ public class SpecialBlockImpl extends SingleSuccessorBlockImpl implements SpecialBlock { - /** The type of this special basic block. */ - protected final SpecialBlockType specialType; - - public SpecialBlockImpl(SpecialBlockType type) { - super(BlockType.SPECIAL_BLOCK); - this.specialType = type; - } - - @Override - public SpecialBlockType getSpecialType() { - return specialType; - } - - /** - * {@inheritDoc} - * - *

          This implementation returns an empty list. - */ - @Override - public List getNodes() { - return Collections.emptyList(); - } - - @Override - public @Nullable Node getLastNode() { - return null; - } - - @Override - public String toString() { - return "SpecialBlock(" + specialType + ")"; - } + /** The type of this special basic block. */ + protected final SpecialBlockType specialType; + + public SpecialBlockImpl(SpecialBlockType type) { + super(BlockType.SPECIAL_BLOCK); + this.specialType = type; + } + + @Override + public SpecialBlockType getSpecialType() { + return specialType; + } + + /** + * {@inheritDoc} + * + *

          This implementation returns an empty list. + */ + @Override + public List getNodes() { + return Collections.emptyList(); + } + + @Override + public @Nullable Node getLastNode() { + return null; + } + + @Override + public String toString() { + return "SpecialBlock(" + specialType + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java index f258b08eac4..57b3043c0b4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGBuilder.java @@ -4,12 +4,7 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; import com.sun.source.util.TreePath; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod; @@ -22,6 +17,14 @@ import org.checkerframework.javacutil.BasicAnnotationProvider; import org.checkerframework.javacutil.trees.TreeBuilder; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeMirror; + /** * Builds the control flow graph of some Java code (either a method, or an arbitrary statement). * @@ -46,129 +49,140 @@ */ public abstract class CFGBuilder { - /** Creates a CFGBuilder. */ - protected CFGBuilder() {} + /** Creates a CFGBuilder. */ + protected CFGBuilder() {} - /** - * Build the control flow graph of some code. - * - * @param root the compilation unit - * @param underlyingAST the AST that underlies the control frow graph - * @param assumeAssertionsDisabled can assertions be assumed to be disabled? - * @param assumeAssertionsEnabled can assertions be assumed to be enabled? - * @param env annotation processing environment containing type utilities - * @return a control flow graph - */ - public static ControlFlowGraph build( - CompilationUnitTree root, - UnderlyingAST underlyingAST, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - TreeBuilder builder = new TreeBuilder(env); - AnnotationProvider annotationProvider = new BasicAnnotationProvider(); - PhaseOneResult phase1result = - new CFGTranslationPhaseOne( - builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env) - .process(root, underlyingAST); - ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); - ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); - return phase3result; - } + /** + * Build the control flow graph of some code. + * + * @param root the compilation unit + * @param underlyingAST the AST that underlies the control frow graph + * @param assumeAssertionsDisabled can assertions be assumed to be disabled? + * @param assumeAssertionsEnabled can assertions be assumed to be enabled? + * @param env annotation processing environment containing type utilities + * @return a control flow graph + */ + public static ControlFlowGraph build( + CompilationUnitTree root, + UnderlyingAST underlyingAST, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + TreeBuilder builder = new TreeBuilder(env); + AnnotationProvider annotationProvider = new BasicAnnotationProvider(); + PhaseOneResult phase1result = + new CFGTranslationPhaseOne( + builder, + annotationProvider, + assumeAssertionsEnabled, + assumeAssertionsDisabled, + env) + .process(root, underlyingAST); + ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); + return phase3result; + } - /** - * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the - * TreePath to the body of that code. - */ - public static ControlFlowGraph build( - TreePath bodyPath, - UnderlyingAST underlyingAST, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - TreeBuilder builder = new TreeBuilder(env); - AnnotationProvider annotationProvider = new BasicAnnotationProvider(); - PhaseOneResult phase1result = - new CFGTranslationPhaseOne( - builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env) - .process(bodyPath, underlyingAST); - ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); - ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); - return phase3result; - } + /** + * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the + * TreePath to the body of that code. + */ + public static ControlFlowGraph build( + TreePath bodyPath, + UnderlyingAST underlyingAST, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + TreeBuilder builder = new TreeBuilder(env); + AnnotationProvider annotationProvider = new BasicAnnotationProvider(); + PhaseOneResult phase1result = + new CFGTranslationPhaseOne( + builder, + annotationProvider, + assumeAssertionsEnabled, + assumeAssertionsDisabled, + env) + .process(bodyPath, underlyingAST); + ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); + return phase3result; + } - /** Build the control flow graph of some code. */ - public static ControlFlowGraph build( - CompilationUnitTree root, UnderlyingAST underlyingAST, ProcessingEnvironment env) { - return build(root, underlyingAST, false, false, env); - } + /** Build the control flow graph of some code. */ + public static ControlFlowGraph build( + CompilationUnitTree root, UnderlyingAST underlyingAST, ProcessingEnvironment env) { + return build(root, underlyingAST, false, false, env); + } - /** Build the control flow graph of a method. */ - public static ControlFlowGraph build( - CompilationUnitTree root, MethodTree tree, ClassTree classTree, ProcessingEnvironment env) { - UnderlyingAST underlyingAST = new CFGMethod(tree, classTree); - return build(root, underlyingAST, false, false, env); - } + /** Build the control flow graph of a method. */ + public static ControlFlowGraph build( + CompilationUnitTree root, + MethodTree tree, + ClassTree classTree, + ProcessingEnvironment env) { + UnderlyingAST underlyingAST = new CFGMethod(tree, classTree); + return build(root, underlyingAST, false, false, env); + } - /** - * Return a printed representation of a collection of extended nodes. - * - * @param nodes a collection of extended nodes to format - * @return a printed representation of the given collection - */ - public static String extendedNodeCollectionToStringDebug( - Collection nodes) { - StringJoiner result = new StringJoiner(", ", "[", "]"); - for (ExtendedNode n : nodes) { - result.add(n.toStringDebug()); + /** + * Return a printed representation of a collection of extended nodes. + * + * @param nodes a collection of extended nodes to format + * @return a printed representation of the given collection + */ + public static String extendedNodeCollectionToStringDebug( + Collection nodes) { + StringJoiner result = new StringJoiner(", ", "[", "]"); + for (ExtendedNode n : nodes) { + result.add(n.toStringDebug()); + } + return result.toString(); } - return result.toString(); - } - /* --------------------------------------------------------- */ - /* Utility routines for debugging CFG building */ - /* --------------------------------------------------------- */ + /* --------------------------------------------------------- */ + /* Utility routines for debugging CFG building */ + /* --------------------------------------------------------- */ - /** - * Print a set of {@link Block}s and the edges between them. This is useful for examining the - * results of phase two. - * - * @param blocks the blocks to print - */ - protected static void printBlocks(Set blocks) { - for (Block b : blocks) { - System.out.print(b.getUid() + ": " + b); - switch (b.getType()) { - case REGULAR_BLOCK: - case SPECIAL_BLOCK: - { - Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); - System.out.println(" -> " + (succ != null ? succ.getUid() : "||")); - break; - } - case EXCEPTION_BLOCK: - { - Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); - System.out.print(" -> " + (succ != null ? succ.getUid() : "||") + " {"); - for (Map.Entry> entry : - ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) { - System.out.print(entry.getKey() + " : " + entry.getValue() + ", "); + /** + * Print a set of {@link Block}s and the edges between them. This is useful for examining the + * results of phase two. + * + * @param blocks the blocks to print + */ + protected static void printBlocks(Set blocks) { + for (Block b : blocks) { + System.out.print(b.getUid() + ": " + b); + switch (b.getType()) { + case REGULAR_BLOCK: + case SPECIAL_BLOCK: + { + Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); + System.out.println(" -> " + (succ != null ? succ.getUid() : "||")); + break; + } + case EXCEPTION_BLOCK: + { + Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); + System.out.print(" -> " + (succ != null ? succ.getUid() : "||") + " {"); + for (Map.Entry> entry : + ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) { + System.out.print(entry.getKey() + " : " + entry.getValue() + ", "); + } + System.out.println("}"); + break; + } + case CONDITIONAL_BLOCK: + { + Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor(); + Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor(); + System.out.println( + " -> T " + + (tSucc != null ? tSucc.getUid() : "||") + + " F " + + (eSucc != null ? eSucc.getUid() : "||")); + break; + } } - System.out.println("}"); - break; - } - case CONDITIONAL_BLOCK: - { - Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor(); - Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor(); - System.out.println( - " -> T " - + (tSucc != null ? tSucc.getUid() : "||") - + " F " - + (eSucc != null ? eSucc.getUid() : "||")); - break; - } - } + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index 2ce2b83d53c..419fbd69100 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -58,32 +58,7 @@ import com.sun.source.util.TreeScanner; import com.sun.source.util.Trees; import com.sun.tools.javac.code.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.ReferenceType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; + import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store.FlowRule; @@ -187,6 +162,34 @@ import org.plumelib.util.IPair; import org.plumelib.util.IdentityArraySet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.ReferenceType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + /** * Class that performs phase one of the translation process. It generates the following information: * @@ -212,4268 +215,4385 @@ @SuppressWarnings("nullness") // TODO public class CFGTranslationPhaseOne extends TreeScanner { - /** Path to the tree currently being scanned. */ - private TreePath path; - - /** Annotation processing environment. */ - protected final ProcessingEnvironment env; - - /** Element utilities. */ - protected final Elements elements; - - /** Type utilities. */ - protected final Types types; - - /** Tree utilities. */ - protected final Trees trees; - - /** TreeBuilder instance. */ - protected final TreeBuilder treeBuilder; - - /** The annotation provider, e.g., a type factory. */ - protected final AnnotationProvider annotationProvider; - - /** Can assertions be assumed to be disabled? */ - protected final boolean assumeAssertionsDisabled; - - /** Can assertions be assumed to be enabled? */ - protected final boolean assumeAssertionsEnabled; - - /* --------------------------------------------------------- */ - /* Extended Node Types and Labels */ - /* --------------------------------------------------------- */ - - /** Special label to identify the regular exit. */ - private final Label regularExitLabel; + /** Path to the tree currently being scanned. */ + private TreePath path; - /** Special label to identify the exceptional exit. */ - private final Label exceptionalExitLabel; + /** Annotation processing environment. */ + protected final ProcessingEnvironment env; - /** - * Current {@link LabelCell} to which a return statement should jump, or null if there is no valid - * destination. - */ - private @Nullable LabelCell returnTargetLC; + /** Element utilities. */ + protected final Elements elements; - /** - * Current {@link LabelCell} to which a break statement with no label should jump, or null if - * there is no valid destination. - */ - private @Nullable LabelCell breakTargetLC; + /** Type utilities. */ + protected final Types types; - /** - * Map from AST label Names to CFG {@link Label}s for breaks. Each labeled statement creates two - * CFG {@link Label}s, one for break and one for continue. - */ - private Map breakLabels; + /** Tree utilities. */ + protected final Trees trees; - /** - * Current {@link LabelCell} to which a continue statement with no label should jump, or null if - * there is no valid destination. - */ - private @Nullable LabelCell continueTargetLC; + /** TreeBuilder instance. */ + protected final TreeBuilder treeBuilder; - /** - * Map from AST label Names to CFG {@link Label}s for continues. Each labeled statement creates - * two CFG {@link Label}s, one for break and one for continue. - */ - private Map continueLabels; + /** The annotation provider, e.g., a type factory. */ + protected final AnnotationProvider annotationProvider; - /** Nested scopes of try-catch blocks in force at the current program point. */ - private final TryStack tryStack; + /** Can assertions be assumed to be disabled? */ + protected final boolean assumeAssertionsDisabled; - /** SwitchBuilder for the current switch. Used to match yield statements to enclosing switches. */ - private SwitchBuilder switchBuilder; + /** Can assertions be assumed to be enabled? */ + protected final boolean assumeAssertionsEnabled; - /** - * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will - * have at least one corresponding Node. Trees that undergo conversions, such as boxing or - * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in the - * treeToCfgNodes, while the Node for the post-conversion value is stored in the - * treeToConvertedCfgNodes. - */ - private final IdentityHashMap> treeToCfgNodes; - - /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ - private final IdentityHashMap> treeToConvertedCfgNodes; - - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic - * tree that is {@code v + 1} or {@code v - 1}. - */ - private final IdentityHashMap postfixTreeToCfgNodes; - - /** The list of extended nodes. */ - private final ArrayList nodeList; - - /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ - private final Map bindings; - - /** The set of leaders (represented as indices into {@code nodeList}). */ - private final Set leaders; - - /** - * All return nodes (if any) encountered. Only includes return statements that actually return - * something. - */ - private final List returnNodes; - - /** - * Class declarations that have been encountered when building the control-flow graph for a - * method. - */ - private final List declaredClasses; - - /** - * Lambdas encountered when building the control-flow graph for a method, variable initializer, or - * initializer. - */ - private final List declaredLambdas; - - /** The ArithmeticException type. */ - protected final TypeMirror arithmeticExceptionType; - - /** The ArrayIndexOutOfBoundsException type. */ - protected final TypeMirror arrayIndexOutOfBoundsExceptionType; - - /** The AssertionError type. */ - protected final TypeMirror assertionErrorType; - - /** The ClassCastException type . */ - protected final TypeMirror classCastExceptionType; - - /** The Iterable type (erased). */ - protected final TypeMirror iterableType; - - /** The NegativeArraySizeException type. */ - protected final TypeMirror negativeArraySizeExceptionType; - - /** The NullPointerException type . */ - protected final TypeMirror nullPointerExceptionType; - - /** The OutOfMemoryError type. */ - protected final @Nullable TypeMirror outOfMemoryErrorType; - - /** The ClassCircularityError type. */ - protected final @Nullable TypeMirror classCircularityErrorType; - - /** The ClassFormatErrorType type. */ - protected final @Nullable TypeMirror classFormatErrorType; - - /** The NoClassDefFoundError type. */ - protected final @Nullable TypeMirror noClassDefFoundErrorType; - - /** The String type. */ - protected final TypeMirror stringType; - - /** The Throwable type. */ - protected final TypeMirror throwableType; - - /** - * Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code - * RuntimeException} and {@code Error}. - */ - protected final Set uncheckedExceptionTypes; - - /** - * Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the - * contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes - * from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions". - */ - protected final Set newArrayExceptionTypes; - - /** - * Creates {@link CFGTranslationPhaseOne}. - * - * @param treeBuilder builder for new AST nodes - * @param annotationProvider extracts annotations from AST nodes - * @param assumeAssertionsDisabled can assertions be assumed to be disabled? - * @param assumeAssertionsEnabled can assertions be assumed to be enabled? - * @param env annotation processing environment containing type utilities - */ - public CFGTranslationPhaseOne( - TreeBuilder treeBuilder, - AnnotationProvider annotationProvider, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - this.env = env; - this.treeBuilder = treeBuilder; - this.annotationProvider = annotationProvider; - - assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); - this.assumeAssertionsEnabled = assumeAssertionsEnabled; - this.assumeAssertionsDisabled = assumeAssertionsDisabled; - - elements = env.getElementUtils(); - types = env.getTypeUtils(); - trees = Trees.instance(env); - - // initialize lists and maps - treeToCfgNodes = new IdentityHashMap<>(); - treeToConvertedCfgNodes = new IdentityHashMap<>(); - postfixTreeToCfgNodes = new IdentityHashMap<>(); - nodeList = new ArrayList<>(); - bindings = new HashMap<>(); - leaders = new HashSet<>(); - - regularExitLabel = new Label(); - exceptionalExitLabel = new Label(); - tryStack = new TryStack(exceptionalExitLabel); - returnTargetLC = new LabelCell(regularExitLabel); - breakLabels = new HashMap<>(2); - continueLabels = new HashMap<>(2); - returnNodes = new ArrayList<>(); - declaredClasses = new ArrayList<>(); - declaredLambdas = new ArrayList<>(); - - arithmeticExceptionType = getTypeMirror(ArithmeticException.class); - arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class); - assertionErrorType = getTypeMirror(AssertionError.class); - classCastExceptionType = getTypeMirror(ClassCastException.class); - iterableType = types.erasure(getTypeMirror(Iterable.class)); - negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class); - nullPointerExceptionType = getTypeMirror(NullPointerException.class); - outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class); - classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class); - classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class); - noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class); - stringType = getTypeMirror(String.class); - throwableType = getTypeMirror(Throwable.class); - uncheckedExceptionTypes = new ArraySet<>(2); - uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class)); - uncheckedExceptionTypes.add(getTypeMirror(Error.class)); - newArrayExceptionTypes = new ArraySet<>(2); - newArrayExceptionTypes.add(negativeArraySizeExceptionType); - if (outOfMemoryErrorType != null) { - newArrayExceptionTypes.add(outOfMemoryErrorType); - } - } - - /** - * Performs the actual work of phase one: processing a single body (of a method, lambda, top-level - * block, etc.). - * - * @param bodyPath path to the body of the underlying AST's method - * @param underlyingAST the AST for which the CFG is to be built - * @return the result of phase one - */ - public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) { - - // Set class variables - this.path = bodyPath; - - // Traverse AST of the method body. - try { // "finally" clause is "this.path = null" - Node finalNode = scan(path.getLeaf(), null); - - // If we are building the CFG for a lambda with a single expression as the body, then - // add an extra node for the result of that lambda. - if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { - LambdaExpressionTree lambdaTree = ((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree(); - if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) { - Node resultNode = - new LambdaResultExpressionNode((ExpressionTree) lambdaTree.getBody(), finalNode); - extendWithNode(resultNode); - } - } - - // Add marker to indicate that the next block will be the exit block. - // Note: if there is a return statement earlier in the method (which is always the case - // for non-void methods), then this is not strictly necessary. However, it is also not a - // problem, as it will just generate a degenerate control graph case that will be - // removed in a later phase. - nodeList.add(new UnconditionalJump(regularExitLabel)); - - return new PhaseOneResult( - underlyingAST, - treeToCfgNodes, - treeToConvertedCfgNodes, - postfixTreeToCfgNodes, - nodeList, - bindings, - leaders, - returnNodes, - regularExitLabel, - exceptionalExitLabel, - declaredClasses, - declaredLambdas, - types); - } finally { - this.path = null; - } - } - - /** - * Process a single body within {@code root}. This method does not process the entire given - * CompilationUnitTree. Rather, it processes one body (of a method/lambda/etc.) within it, which - * corresponds to {@code underlyingAST}. - * - * @param root the compilation unit - * @param underlyingAST the AST corresponding to the body to process - * @return a PhaseOneResult - */ - public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) { - // TODO: Isn't this costly? Is there no cache we can reuse? - TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); - assert bodyPath != null; - return process(bodyPath, underlyingAST); - } - - /** - * Perform any actions required when CFG translation creates a new Tree that is not part of the - * original AST. - * - * @param tree the newly created Tree - */ - public void handleArtificialTree(Tree tree) {} - - /** - * Returns the current path for the tree currently being scanned. - * - * @return the current path - */ - public TreePath getCurrentPath() { - return path; - } - - // TODO: remove method and instead use JCP to add version-specific methods. - // Switch expressions first appeared in 12, standard in 14, so don't use 17. - // TODO: look into changing back to TreePathScanner and removing path/getCurrentPath. - @Override - public Node scan(Tree tree, Void p) { - if (tree == null) { - return null; - } - - TreePath prev = path; - @SuppressWarnings("interning:not.interned") // Looking for exact match. - boolean treeIsLeaf = path.getLeaf() != tree; - if (treeIsLeaf) { - path = new TreePath(path, tree); - } - try { - // TODO: use JCP to add version-specific behavior - if (SystemUtil.jreVersion >= 14) { - // Must use String comparison to support compiling on JDK 11 and earlier. - // Features added between JDK 12 and JDK 17 inclusive. - switch (tree.getKind().name()) { - case "BINDING_PATTERN": - return visitBindingPattern17(path.getLeaf(), p); - case "SWITCH_EXPRESSION": - return visitSwitchExpression17(tree, p); - case "YIELD": - return visitYield17(tree, p); - case "DECONSTRUCTION_PATTERN": - return visitDeconstructionPattern21(tree, p); - default: - // fall through to generic behavior - } - } - - return tree.accept(this, p); - } finally { - path = prev; - } - } - - /** - * Visit a SwitchExpressionTree. - * - * @param yieldTree a YieldTree, typed as Tree to be backward-compatible - * @param p parameter - * @return the result of visiting the switch expression tree - */ - public Node visitYield17(Tree yieldTree, Void p) { - ExpressionTree resultExpression = YieldUtils.getValue(yieldTree); - switchBuilder.buildSwitchExpressionResult(resultExpression); - return null; - } - - /** - * Visit a SwitchExpressionTree - * - * @param switchExpressionTree a SwitchExpressionTree, typed as Tree to be backward-compatible - * @param p parameter - * @return the result of visiting the switch expression tree - */ - public Node visitSwitchExpression17(Tree switchExpressionTree, Void p) { - SwitchBuilder oldSwitchBuilder = switchBuilder; - switchBuilder = new SwitchBuilder(switchExpressionTree); - Node result = switchBuilder.build(); - switchBuilder = oldSwitchBuilder; - return result; - } - - /** - * Visit a BindingPatternTree - * - * @param bindingPatternTree a BindingPatternTree, typed as Tree to be backward-compatible - * @param p parameter - * @return the result of visiting the binding pattern tree - */ - public Node visitBindingPattern17(Tree bindingPatternTree, Void p) { - ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); - TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass); - Node receiver = new ImplicitThisNode(classElem.asType()); - VariableTree varTree = BindingPatternUtils.getVariable(bindingPatternTree); - VariableDeclarationNode variableDeclarationNode = new VariableDeclarationNode(varTree); - extendWithNode(variableDeclarationNode); - LocalVariableNode varNode = new LocalVariableNode(varTree, receiver); - extendWithNode(varNode); - return varNode; - } - - /** - * Visit a DeconstructionPatternTree. - * - * @param deconstructionPatternTree a DeconstructionPatternTree, typed as Tree so the Checker - * Framework compiles under JDK 20 and earlier - * @param p an unused parameter - * @return the result of visiting the tree - */ - public Node visitDeconstructionPattern21(Tree deconstructionPatternTree, Void p) { - List nestedPatternTrees = - DeconstructionPatternUtils.getNestedPatterns(deconstructionPatternTree); - List nestedPatterns = new ArrayList<>(nestedPatternTrees.size()); - for (Tree pattern : nestedPatternTrees) { - nestedPatterns.add(scan(pattern, p)); - } - - DeconstructorPatternNode dcpN = - new DeconstructorPatternNode( - TreeUtils.typeOf(deconstructionPatternTree), deconstructionPatternTree, nestedPatterns); - extendWithNode(dcpN); - return dcpN; - } - - /* --------------------------------------------------------- */ - /* Nodes and Labels Management */ - /* --------------------------------------------------------- */ - - /** - * Add a node to the lookup map if it not already present. - * - * @param node the node to add to the lookup map - */ - protected void addToLookupMap(Node node) { - Tree tree = node.getTree(); - if (tree == null) { - return; - } - Set existing = treeToCfgNodes.get(tree); - if (existing == null) { - Set newSet = new IdentityArraySet(1); - newSet.add(node); - treeToCfgNodes.put(tree, newSet); - } else { - existing.add(node); - } + /* --------------------------------------------------------- */ + /* Extended Node Types and Labels */ + /* --------------------------------------------------------- */ - Tree enclosingParens = parenMapping.get(tree); - while (enclosingParens != null) { - Set exp = - treeToCfgNodes.computeIfAbsent(enclosingParens, k -> new IdentityArraySet<>(1)); - // `node` could already be in set `exp`, but it's probably as fast to just add again - exp.add(node); - enclosingParens = parenMapping.get(enclosingParens); - } - } - - /** - * Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree - * should already be in the pre-conversion lookup map. This method is used to update the Tree-Node - * mapping with conversion nodes. - * - * @param node the node to add to the lookup map - */ - protected void addToConvertedLookupMap(Node node) { - Tree tree = node.getTree(); - addToConvertedLookupMap(tree, node); - } - - /** - * Add a node in the post-conversion lookup map. The tree argument should already be in the - * pre-conversion lookup map. This method is used to update the Tree-Node mapping with conversion - * nodes. - * - * @param tree the tree used as a key in the map - * @param node the node to add to the lookup map - */ - protected void addToConvertedLookupMap(Tree tree, Node node) { - assert tree != null; - assert treeToCfgNodes.containsKey(tree); - Set existing = treeToConvertedCfgNodes.get(tree); - if (existing == null) { - Set newSet = new IdentityArraySet<>(1); - newSet.add(node); - treeToConvertedCfgNodes.put(tree, newSet); - } else { - existing.add(node); - } - } - - /** - * Extend the list of extended nodes with a node. - * - * @param node the node to add - */ - protected void extendWithNode(Node node) { - addToLookupMap(node); - extendWithExtendedNode(new NodeHolder(node)); - } - - /** - * Extend the list of extended nodes with a node, where {@code node} might throw the exception - * {@code cause}. - * - * @param node the node to add - * @param cause an exception that the node might throw - * @return the node holder - */ - protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) { - addToLookupMap(node); - return extendWithNodeWithExceptions(node, Collections.singleton(cause)); - } - - /** - * Extend the list of extended nodes with a node, where {@code node} might throw any of the - * exceptions in {@code causes}. - * - * @param node the node to add - * @param causes the set of exceptions that the node might throw - * @return the node holder - */ - protected NodeWithExceptionsHolder extendWithNodeWithExceptions( - Node node, Set causes) { - addToLookupMap(node); - Map> exceptions = new ArrayMap<>(causes.size()); - for (TypeMirror cause : causes) { - exceptions.put(cause, tryStack.possibleLabels(cause)); - } - NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); - extendWithExtendedNode(exNode); - return exNode; - } - - /** - * Extend a list of extended nodes with a ClassName node. - * - *

          Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw one - * of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1). - * - * @param node the ClassName node to add - * @return the node holder - */ - protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) { - Set thrownSet = new ArraySet<>(4); - if (classCircularityErrorType != null) { - thrownSet.add(classCircularityErrorType); - } - if (classFormatErrorType != null) { - thrownSet.add(classFormatErrorType); - } - if (noClassDefFoundErrorType != null) { - thrownSet.add(noClassDefFoundErrorType); - } - if (outOfMemoryErrorType != null) { - thrownSet.add(outOfMemoryErrorType); - } + /** Special label to identify the regular exit. */ + private final Label regularExitLabel; - return extendWithNodeWithExceptions(node, thrownSet); - } - - /** - * Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list if - * {@code pred} is not present. - * - * @param node the node to add - * @param pred the desired predecessor of node - * @return the node holder - */ - protected T insertNodeAfter(T node, Node pred) { - addToLookupMap(node); - insertExtendedNodeAfter(new NodeHolder(node), pred); - return node; - } - - /** - * Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in - * the list of extended nodes, or append to the list if {@code pred} is not present. - * - * @param node the node to add - * @param causes the set of exceptions that the node might throw - * @param pred the desired predecessor of node - * @return the node holder - */ - protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter( - Node node, Set causes, Node pred) { - addToLookupMap(node); - Map> exceptions = new ArrayMap<>(causes.size()); - for (TypeMirror cause : causes) { - exceptions.put(cause, tryStack.possibleLabels(cause)); - } - NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); - insertExtendedNodeAfter(exNode, pred); - return exNode; - } - - /** - * Extend the list of extended nodes with an extended node. - * - * @param n the extended node - */ - protected void extendWithExtendedNode(ExtendedNode n) { - nodeList.add(n); - } - - /** - * Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code n} - * if {@code pred} is not present. - * - * @param n the extended node - * @param pred the desired predecessor - */ - @SuppressWarnings("ModifyCollectionInEnhancedForLoop") - protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) { - int index = -1; - for (int i = 0; i < nodeList.size(); i++) { - ExtendedNode inList = nodeList.get(i); - if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) { - if (inList.getNode() == pred) { - index = i; - break; - } - } - } - if (index != -1) { - nodeList.add(index + 1, n); - // update bindings - for (Map.Entry e : bindings.entrySet()) { - if (e.getValue() >= index + 1) { - bindings.put(e.getKey(), e.getValue() + 1); - } - } - // update leaders - Set oldLeaders = new HashSet<>(leaders); - leaders.clear(); - for (Integer l : oldLeaders) { - if (l >= index + 1) { - leaders.add(l + 1); - } else { - leaders.add(l); - } - } - } else { - nodeList.add(n); - } - } - - /** - * Add the label {@code l} to the extended node that will be placed next in the sequence. - * - * @param l the node to add to the forthcoming extended node - */ - protected void addLabelForNextNode(Label l) { - if (bindings.containsKey(l)) { - throw new BugInCF("bindings already contains key %s: %s", l, bindings); - } - leaders.add(nodeList.size()); - bindings.put(l, nodeList.size()); - } - - /* --------------------------------------------------------- */ - /* Utility Methods */ - /* --------------------------------------------------------- */ - - /** The UID for the next unique name. */ - protected long uid = 0; - - /** - * Returns a unique name starting with {@code prefix}. - * - * @param prefix the prefix of the unique name - * @return a unique name starting with {@code prefix} - */ - protected String uniqueName(String prefix) { - return prefix + "#num" + uid++; - } - - /** - * If the input node is an unboxed primitive type, insert a call to the appropriate valueOf - * method, otherwise leave it alone. - * - * @param node in input node - * @return a Node representing the boxed version of the input, which may simply be the input node - */ - protected Node box(Node node) { - // For boxing conversion, see JLS 5.1.7 - if (TypesUtils.isPrimitive(node.getType())) { - PrimitiveType primitive = types.getPrimitiveType(node.getType().getKind()); - TypeMirror boxedType = types.getDeclaredType(types.boxedClass(primitive)); - - TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); - IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement); - handleArtificialTree(classTree); - // No need to handle possible errors from evaluating a class literal here - // since this is synthetic code that can't fail. - ClassNameNode className = new ClassNameNode(classTree); - className.setInSource(false); - insertNodeAfter(className, node); - - MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree); - handleArtificialTree(valueOfSelect); - MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className); - valueOfAccess.setInSource(false); - insertNodeAfter(valueOfAccess, className); - - MethodInvocationTree valueOfCall = - treeBuilder.buildMethodInvocation(valueOfSelect, (ExpressionTree) node.getTree()); - handleArtificialTree(valueOfCall); - Node boxed = - new MethodInvocationNode( - valueOfCall, valueOfAccess, Collections.singletonList(node), getCurrentPath()); - boxed.setInSource(false); - // Add Throwable to account for unchecked exceptions - addToConvertedLookupMap(node.getTree(), boxed); - insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess); - return boxed; - } else { - return node; - } - } - - /** - * If the input node is a boxed type, unbox it, otherwise leave it alone. - * - * @param node in input node - * @return a Node representing the unboxed version of the input, which may simply be the input - * node - */ - protected Node unbox(Node node) { - if (TypesUtils.isBoxedPrimitive(node.getType())) { - - MemberSelectTree primValueSelect = treeBuilder.buildPrimValueMethodAccess(node.getTree()); - handleArtificialTree(primValueSelect); - MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node); - primValueAccess.setInSource(false); - // Method access may throw NullPointerException - insertNodeWithExceptionsAfter( - primValueAccess, Collections.singleton(nullPointerExceptionType), node); - - MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect); - handleArtificialTree(primValueCall); - Node unboxed = - new MethodInvocationNode( - primValueCall, primValueAccess, Collections.emptyList(), getCurrentPath()); - unboxed.setInSource(false); - - // Add Throwable to account for unchecked exceptions - addToConvertedLookupMap(node.getTree(), unboxed); - insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess); - return unboxed; - } else { - return node; - } - } - - private TreeInfo getTreeInfo(Tree tree) { - TypeMirror type = TreeUtils.typeOf(tree); - boolean boxed = TypesUtils.isBoxedPrimitive(type); - TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; - - boolean bool = TypesUtils.isBooleanType(type); - boolean numeric = TypesUtils.isNumeric(unboxedType); - - return new TreeInfo() { - @Override - public boolean isNumeric() { - return numeric; - } - - @Override - public boolean isBoxed() { - return boxed; - } - - @Override - public boolean isBoolean() { - return bool; - } - - @Override - public TypeMirror unboxedType() { - return unboxedType; - } - }; - } - - /** - * Returns the unboxed tree if necessary, as described in JLS 5.1.8. - * - * @return the unboxed tree if necessary, as described in JLS 5.1.8 - */ - private Node unboxAsNeeded(Node node, boolean boxed) { - return boxed ? unbox(node) : node; - } - - /** - * Convert the input node to String type, if it isn't already. - * - * @param node an input node - * @return a Node with the value promoted to String, which may be the input node - */ - protected Node stringConversion(Node node) { - // For string conversion, see JLS 5.1.11 - if (!TypesUtils.isString(node.getType())) { - Node converted = new StringConversionNode(node.getTree(), node, stringType); - addToConvertedLookupMap(converted); - insertNodeAfter(converted, node); - return converted; - } else { - return node; - } - } - - /** - * Perform unary numeric promotion on the input node. - * - * @param node a node producing a value of numeric primitive or boxed type - * @return a Node with the value promoted to the int, long, float, or double; may return be the - * input node - */ - protected Node unaryNumericPromotion(Node node) { - // For unary numeric promotion, see JLS 5.6.1 - node = unbox(node); - - switch (node.getType().getKind()) { - case BYTE: - case CHAR: - case SHORT: - { - TypeMirror intType = types.getPrimitiveType(TypeKind.INT); - Node widened = new WideningConversionNode(node.getTree(), node, intType); - addToConvertedLookupMap(widened); - insertNodeAfter(widened, node); - return widened; - } - default: - // Nothing to do. - break; - } + /** Special label to identify the exceptional exit. */ + private final Label exceptionalExitLabel; - return node; - } + /** + * Current {@link LabelCell} to which a return statement should jump, or null if there is no + * valid destination. + */ + private @Nullable LabelCell returnTargetLC; - /** - * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and false - * otherwise. - */ - protected boolean isNumericOrBoxed(TypeMirror type) { - if (TypesUtils.isBoxedPrimitive(type)) { - type = types.unboxedType(type); - } - return TypesUtils.isNumeric(type); - } - - /** - * Compute the type to which two numeric types must be promoted before performing a binary numeric - * operation on them. The input types must both be numeric and the output type is primitive. - * - * @param left the type of the left operand - * @param right the type of the right operand - * @return a TypeMirror representing the binary numeric promoted type - */ - protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { - if (TypesUtils.isBoxedPrimitive(left)) { - left = types.unboxedType(left); - } - if (TypesUtils.isBoxedPrimitive(right)) { - right = types.unboxedType(right); - } - TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right); - return types.getPrimitiveType(promotedTypeKind); - } - - /** - * Perform binary numeric promotion on the input node to make it match the expression type. - * - * @param node a node producing a value of numeric primitive or boxed type - * @param exprType the type to promote the value to - * @return a Node with the value promoted to the exprType, which may be the input node - */ - protected Node binaryNumericPromotion(Node node, TypeMirror exprType) { - // For binary numeric promotion, see JLS 5.6.2 - node = unbox(node); - - if (!types.isSameType(node.getType(), exprType)) { - Node widened = new WideningConversionNode(node.getTree(), node, exprType); - addToConvertedLookupMap(widened); - insertNodeAfter(widened, node); - return widened; - } else { - return node; - } - } - - /** - * Perform widening primitive conversion on the input node to make it match the destination type. - * - * @param node a node producing a value of numeric primitive type - * @param destType the type to widen the value to - * @return a Node with the value widened to the exprType, which may be the input node - */ - protected Node widen(Node node, TypeMirror destType) { - // For widening conversion, see JLS 5.1.2 - assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) - : "widening must be applied to primitive types"; - if (types.isSubtype(node.getType(), destType) && !types.isSameType(node.getType(), destType)) { - Node widened = new WideningConversionNode(node.getTree(), node, destType); - addToConvertedLookupMap(widened); - insertNodeAfter(widened, node); - return widened; - } else { - return node; - } - } - - /** - * Perform narrowing conversion on the input node to make it match the destination type. - * - * @param node a node producing a value of numeric primitive type - * @param destType the type to narrow the value to - * @return a Node with the value narrowed to the exprType, which may be the input node - */ - protected Node narrow(Node node, TypeMirror destType) { - // For narrowing conversion, see JLS 5.1.3 - assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) - : "narrowing must be applied to primitive types"; - if (types.isSubtype(destType, node.getType()) && !types.isSameType(destType, node.getType())) { - Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType); - addToConvertedLookupMap(narrowed); - insertNodeAfter(narrowed, node); - return narrowed; - } else { - return node; - } - } - - /** - * Perform narrowing conversion and optionally boxing conversion on the input node to make it - * match the destination type. - * - * @param node a node producing a value of numeric primitive type - * @param destType the type to narrow the value to (possibly boxed) - * @return a Node with the value narrowed and boxed to the destType, which may be the input node - */ - protected Node narrowAndBox(Node node, TypeMirror destType) { - if (TypesUtils.isBoxedPrimitive(destType)) { - return box(narrow(node, types.unboxedType(destType))); - } else { - return narrow(node, destType); - } - } - - /** - * Return whether a conversion from the type of the node to varType requires narrowing. - * - * @param varType the type of a variable (or general LHS) to be converted to - * @param node a node whose value is being converted - * @return whether this conversion requires narrowing to succeed - */ - protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { - // Narrowing is restricted to cases where the left hand side is byte, char, short or Byte, - // Char, Short and the right hand side is a constant. - TypeMirror unboxedVarType = - TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType; - TypeKind unboxedVarKind = unboxedVarType.getKind(); - boolean isLeftNarrowableTo = - unboxedVarKind == TypeKind.BYTE - || unboxedVarKind == TypeKind.SHORT - || unboxedVarKind == TypeKind.CHAR; - boolean isRightConstant = node instanceof ValueLiteralNode; - return isLeftNarrowableTo && isRightConstant; - } - - /** - * Assignment conversion and method invocation conversion are almost identical, except that - * assignment conversion allows narrowing. We factor out the common logic here. - * - * @param node a Node producing a value - * @param varType the type of a variable - * @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not - * (for method invocation conversion) - * @return a Node with the value converted to the type of the variable, which may be the input - * node itself - */ - protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) { - // For assignment conversion, see JLS 5.2 - // For method invocation conversion, see JLS 5.3 - - // Check for identical types or "identity conversion" - TypeMirror nodeType = node.getType(); - boolean isSameType = types.isSameType(nodeType, varType); - if (isSameType) { - return node; - } + /** + * Current {@link LabelCell} to which a break statement with no label should jump, or null if + * there is no valid destination. + */ + private @Nullable LabelCell breakTargetLC; - boolean isRightNumeric = TypesUtils.isNumeric(nodeType); - boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); - boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); - boolean isRightReference = nodeType instanceof ReferenceType; - boolean isLeftNumeric = TypesUtils.isNumeric(varType); - boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); - // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType); - boolean isLeftReference = varType instanceof ReferenceType; - boolean isSubtype = types.isSubtype(nodeType, varType); - - if (isRightNumeric && isLeftNumeric && isSubtype) { - node = widen(node, varType); - } else if (isRightReference && isLeftReference && isSubtype) { - // widening reference conversion is a no-op, but if it - // applies, then later conversions do not. - } else if (isRightPrimitive && isLeftReference) { - if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { - node = narrowAndBox(node, varType); - } else { - node = box(node); - } - } else if (isRightBoxed && isLeftPrimitive) { - node = unbox(node); - nodeType = node.getType(); - - if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) { - node = widen(node, varType); - } - } else if (isRightPrimitive && isLeftPrimitive) { - if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { - node = narrow(node, varType); - } - } - // `node` might have been re-assigned; if `nodeType` is needed, set it again. - // nodeType = node.getType(); - - // TODO: if checkers need to know about null references of - // a particular type, add logic for them here. - - return node; - } - - /** - * Perform assignment conversion so that it can be assigned to a variable of the given type. - * - * @param node a Node producing a value - * @param varType the type of a variable - * @return a Node with the value converted to the type of the variable, which may be the input - * node itself - */ - protected Node assignConvert(Node node, TypeMirror varType) { - return commonConvert(node, varType, true); - } - - /** - * Perform method invocation conversion so that the node can be passed as a formal parameter of - * the given type. - * - * @param node a Node producing a value - * @param formalType the type of a formal parameter - * @return a Node with the value converted to the type of the formal, which may be the input node - * itself - */ - protected Node methodInvocationConvert(Node node, TypeMirror formalType) { - return commonConvert(node, formalType, false); - } - - /** - * Given a method element, its type at the call site, and a list of argument expressions, return a - * list of {@link Node}s representing the arguments converted for a call of the method. This - * method applies to both method invocations and constructor calls. The argument of newClassTree - * is null when we visit {@link MethodInvocationTree}, and is non-null when we visit {@link - * NewClassTree}. - * - * @param tree the invocation tree for the call - * @param executable an ExecutableElement representing a method/constructor to be called - * @param executableType an ExecutableType representing the type of the method/constructor call; - * the type must be viewpoint-adapted to the call - * @param actualExprs a List of argument expressions to a call - * @param newClassTree the NewClassTree if the method is the invocation of a constructor - * @return a List of {@link Node}s representing arguments after conversions required by a call to - * this method - */ - protected List convertCallArguments( - ExpressionTree tree, - ExecutableElement executable, - ExecutableType executableType, - List actualExprs, - @Nullable NewClassTree newClassTree) { - // NOTE: It is important to convert one method argument before generating CFG nodes for the - // next argument, since label binding expects nodes to be generated in execution order. - // Therefore, this method first determines which conversions need to be applied and then - // iterates over the actual arguments. - List formals = executableType.getParameterTypes(); - int numFormals = formals.size(); - - ArrayList convertedNodes = new ArrayList<>(numFormals); - AssertMethodTuple assertMethodTuple = getAssertMethodTuple(executable); - - int numActuals = actualExprs.size(); - if (executable.isVarArgs()) { - // Create a new array argument if the actuals outnumber the formals, or if the last - // actual is not assignable to the last formal. - int lastArgIndex = numFormals - 1; - TypeMirror lastParamType = formals.get(lastArgIndex); - if (numActuals == numFormals - && types.isAssignable(TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) { - // Normal call with no array creation, apply method - // invocation conversion to all arguments. - for (int i = 0; i < numActuals; i++) { - Node actualVal = scan(actualExprs.get(i), null); - if (i == assertMethodTuple.booleanParam) { - treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); - } - if (actualVal == null) { - throw new BugInCF( - "CFGBuilder: scan returned null for %s [%s]", - actualExprs.get(i), actualExprs.get(i).getClass()); - } - convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); - } - } else { - assert lastParamType instanceof ArrayType : "variable argument formal must be an array"; - // Handle anonymous constructors with an explicit enclosing expression. - // There is a mismatch between the number of parameters and arguments - // when the following conditions are met: - // 1. Java version >= 11, - // 2. the method is an anonymous constructor, - // 3. the executable element has varargs, - // 4. the constructor is invoked with an explicit enclosing expression. - // In this case, the parameters have an enclosing expression as its first parameter, - // while the arguments do not have such element. Hence, decrease the lastArgIndex - // to organize the arguments from the correct index later. - if (SystemUtil.jreVersion >= 11 - && newClassTree != null - && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( - executable, newClassTree)) { - lastArgIndex--; - } + /** + * Map from AST label Names to CFG {@link Label}s for breaks. Each labeled statement creates two + * CFG {@link Label}s, one for break and one for continue. + */ + private Map breakLabels; - // Apply method invocation conversion to lastArgIndex arguments and use the - // remaining ones to initialize an array. - for (int i = 0; i < lastArgIndex; i++) { - Node actualVal = scan(actualExprs.get(i), null); - if (i == assertMethodTuple.booleanParam) { - treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); - } - convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); - } + /** + * Current {@link LabelCell} to which a continue statement with no label should jump, or null if + * there is no valid destination. + */ + private @Nullable LabelCell continueTargetLC; - TypeMirror elemType = ((ArrayType) lastParamType).getComponentType(); + /** + * Map from AST label Names to CFG {@link Label}s for continues. Each labeled statement creates + * two CFG {@link Label}s, one for break and one for continue. + */ + private Map continueLabels; - List inits = new ArrayList<>(numActuals - lastArgIndex); - List initializers = new ArrayList<>(numActuals - lastArgIndex); - for (int i = lastArgIndex; i < numActuals; i++) { - inits.add(actualExprs.get(i)); - Node actualVal = scan(actualExprs.get(i), null); - initializers.add(assignConvert(actualVal, elemType)); - } + /** Nested scopes of try-catch blocks in force at the current program point. */ + private final TryStack tryStack; - NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits); - handleArtificialTree(wrappedVarargs); - - Node lastArgument = - new ArrayCreationNode( - wrappedVarargs, - lastParamType, - /* dimensions= */ Collections.emptyList(), - initializers); - extendWithNode(lastArgument); - - convertedNodes.add(lastArgument); - } - } else { - for (int i = 0; i < numActuals; i++) { - Node actualVal = scan(actualExprs.get(i), null); - if (i == assertMethodTuple.booleanParam) { - treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); - } - convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); - } - } + /** + * SwitchBuilder for the current switch. Used to match yield statements to enclosing switches. + */ + private SwitchBuilder switchBuilder; - return convertedNodes; - } - - /** - * Returns the AssertMethodTuple for {@code method}. If {@code method} is not an assert method, - * then {@link AssertMethodTuple#NONE} is returned. - * - * @param method a method element that might be an assert method - * @return the AssertMethodTuple for {@code method} - */ - protected AssertMethodTuple getAssertMethodTuple(ExecutableElement method) { - AnnotationMirror assertMethodAnno = - annotationProvider.getDeclAnnotation(method, AssertMethod.class); - if (assertMethodAnno == null) { - return AssertMethodTuple.NONE; - } + /** + * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will + * have at least one corresponding Node. Trees that undergo conversions, such as boxing or + * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in + * the treeToCfgNodes, while the Node for the post-conversion value is stored in the + * treeToConvertedCfgNodes. + */ + private final IdentityHashMap> treeToCfgNodes; - // Dataflow does not require checker-qual.jar to be on the users classpath, so - // AnnotationUtils.getElementValue(...) cannot be used. + /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ + private final IdentityHashMap> treeToConvertedCfgNodes; - int booleanParam = - AnnotationUtils.getElementValueNotOnClasspath( - assertMethodAnno, "parameter", Integer.class, 1) - - 1; + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the + * synthetic tree that is {@code v + 1} or {@code v - 1}. + */ + private final IdentityHashMap postfixTreeToCfgNodes; - TypeMirror exceptionType = - AnnotationUtils.getElementValueNotOnClasspath( - assertMethodAnno, "value", Type.ClassType.class, (Type.ClassType) assertionErrorType); - boolean isAssertFalse = - AnnotationUtils.getElementValueNotOnClasspath( - assertMethodAnno, "isAssertFalse", Boolean.class, false); - return new AssertMethodTuple(booleanParam, exceptionType, isAssertFalse); - } + /** The list of extended nodes. */ + private final ArrayList nodeList; - /** Holds the elements of an {@link AssertMethod} annotation. */ - protected static class AssertMethodTuple { + /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ + private final Map bindings; - /** A tuple representing the lack of an {@link AssertMethodTuple}. */ - protected static final AssertMethodTuple NONE = new AssertMethodTuple(-1, null, false); + /** The set of leaders (represented as indices into {@code nodeList}). */ + private final Set leaders; /** - * 0-based index of the parameter of the expression that is tested by the assert method. (Or -1 - * if this isn't an assert method.) + * All return nodes (if any) encountered. Only includes return statements that actually return + * something. */ - public final int booleanParam; - - /** The type of the exception thrown by the assert method. */ - public final TypeMirror exceptionType; - - /** Is this an assert false method? */ - public final boolean isAssertFalse; + private final List returnNodes; /** - * Creates an AssertMethodTuple. - * - * @param booleanParam 0-based index of the parameter of the expression that is tested by the - * assert method - * @param exceptionType the type of the exception thrown by the assert method - * @param isAssertFalse is this an assert false method + * Class declarations that have been encountered when building the control-flow graph for a + * method. */ - public AssertMethodTuple(int booleanParam, TypeMirror exceptionType, boolean isAssertFalse) { - this.booleanParam = booleanParam; - this.exceptionType = exceptionType; - this.isAssertFalse = isAssertFalse; - } - } - - /** - * Convert an operand of a conditional expression to the type of the whole expression. - * - * @param node a node occurring as the second or third operand of a conditional expression - * @param destType the type to promote the value to - * @return a Node with the value promoted to the destType, which may be the input node - */ - protected Node conditionalExprPromotion(Node node, TypeMirror destType) { - // For rules on converting operands of conditional expressions, - // JLS 15.25 - TypeMirror nodeType = node.getType(); - - // If the operand is already the same type as the whole - // expression, then do nothing. - if (types.isSameType(nodeType, destType)) { - return node; - } + private final List declaredClasses; - // If the operand is a primitive and the whole expression is - // boxed, then apply boxing. - if (TypesUtils.isPrimitive(nodeType) && TypesUtils.isBoxedPrimitive(destType)) { - return box(node); - } + /** + * Lambdas encountered when building the control-flow graph for a method, variable initializer, + * or initializer. + */ + private final List declaredLambdas; - // If the operand is byte or Byte and the whole expression is - // short, then convert to short. - boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType); - TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType; - TypeMirror unboxedDestType = - TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType; - if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) { - if (unboxedNodeType.getKind() == TypeKind.BYTE && destType.getKind() == TypeKind.SHORT) { - if (isBoxedPrimitive) { - node = unbox(node); - } - return widen(node, destType); - } - - // If the operand is Byte, Short or Character and the whole expression - // is the unboxed version of it, then apply unboxing. - TypeKind destKind = destType.getKind(); - if (destKind == TypeKind.BYTE || destKind == TypeKind.CHAR || destKind == TypeKind.SHORT) { - if (isBoxedPrimitive) { - return unbox(node); - } else if (nodeType.getKind() == TypeKind.INT) { - return narrow(node, destType); - } - } + /** The ArithmeticException type. */ + protected final TypeMirror arithmeticExceptionType; - return binaryNumericPromotion(node, destType); - } + /** The ArrayIndexOutOfBoundsException type. */ + protected final TypeMirror arrayIndexOutOfBoundsExceptionType; - // For the final case in JLS 15.25, apply boxing but not lub. - if (TypesUtils.isPrimitive(nodeType) - && (destType.getKind() == TypeKind.DECLARED - || destType.getKind() == TypeKind.UNION - || destType.getKind() == TypeKind.INTERSECTION)) { - return box(node); - } + /** The AssertionError type. */ + protected final TypeMirror assertionErrorType; - return node; - } - - /** - * Returns the label {@link Name} of the leaf in the argument path, or null if the leaf is not a - * labeled statement. - */ - protected @Nullable Name getLabel(TreePath path) { - if (path.getParentPath() != null) { - Tree parent = path.getParentPath().getLeaf(); - if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) { - return ((LabeledStatementTree) parent).getLabel(); - } - } - return null; - } - - /* --------------------------------------------------------- */ - /* Visitor Methods */ - /* --------------------------------------------------------- */ - - @Override - public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) { - return scan(tree.getUnderlyingType(), p); - } - - @Override - public Node visitAnnotation(AnnotationTree tree, Void p) { - throw new BugInCF("AnnotationTree is unexpected in AST to CFG translation"); - } - - @Override - public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { - - // see JLS 15.12.4 - - // First, compute the receiver, if any (15.12.4.1). - // Second, evaluate the actual arguments, left to right and possibly some arguments are - // stored into an array for varargs calls (15.12.4.2). - // Third, test the receiver, if any, for nullness (15.12.4.4). - // Fourth, convert the arguments to the type of the formal parameters (15.12.4.5). - // Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5). - ExecutableElement method = TreeUtils.elementFromUse(tree); - if (method == null) { - // The method wasn't found, e.g. because of a compilation error. - return null; - } + /** The ClassCastException type . */ + protected final TypeMirror classCastExceptionType; - ExpressionTree methodSelect = tree.getMethodSelect(); - assert TreeUtils.isMethodAccess(methodSelect) - : "Expected a method access, but got: " + methodSelect; + /** The Iterable type (erased). */ + protected final TypeMirror iterableType; - List actualExprs = tree.getArguments(); + /** The NegativeArraySizeException type. */ + protected final TypeMirror negativeArraySizeExceptionType; - // Look up method to invoke and possibly throw NullPointerException - Node receiver = getReceiver(methodSelect); + /** The NullPointerException type . */ + protected final TypeMirror nullPointerExceptionType; - MethodAccessNode target = new MethodAccessNode(methodSelect, method, receiver); + /** The OutOfMemoryError type. */ + protected final @Nullable TypeMirror outOfMemoryErrorType; - if (ElementUtils.isStatic(method) || receiver instanceof ThisNode) { - // No NullPointerException can be thrown, use normal node - extendWithNode(target); - } else { - extendWithNodeWithException(target, nullPointerExceptionType); - } + /** The ClassCircularityError type. */ + protected final @Nullable TypeMirror classCircularityErrorType; - List arguments; - if (TreeUtils.isEnumSuperCall(tree)) { - // Don't convert arguments for enum super calls. The AST contains no actual arguments, - // while the method element expects two arguments, leading to an exception in - // convertCallArguments. - // Since no actual arguments are present in the AST that is being checked, it shouldn't - // cause any harm to omit the conversions. - // See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate. - arguments = Collections.emptyList(); - } else { - arguments = - convertCallArguments(tree, method, TreeUtils.typeFromUse(tree), actualExprs, null); - } + /** The ClassFormatErrorType type. */ + protected final @Nullable TypeMirror classFormatErrorType; - // TODO: lock the receiver for synchronized methods + /** The NoClassDefFoundError type. */ + protected final @Nullable TypeMirror noClassDefFoundErrorType; - MethodInvocationNode node = new MethodInvocationNode(tree, target, arguments, getCurrentPath()); + /** The String type. */ + protected final TypeMirror stringType; - ExtendedNode extendedNode = extendWithMethodInvocationNode(method, node); + /** The Throwable type. */ + protected final TypeMirror throwableType; - /* Check for the TerminatesExecution annotation. */ - boolean terminatesExecution = - annotationProvider.getDeclAnnotation(method, TerminatesExecution.class) != null; - if (terminatesExecution) { - extendedNode.setTerminatesExecution(true); - } - return node; - } - - /** - * Extends the CFG with a MethodInvocationNode, accounting for potential exceptions thrown by the - * invocation. - * - * @param method the invoked method - * @param node the invocation - * @return an ExtendedNode representing the invocation and its possible thrown exceptions - */ - private ExtendedNode extendWithMethodInvocationNode( - ExecutableElement method, MethodInvocationNode node) { - List thrownTypes = method.getThrownTypes(); - Set thrownSet = - new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size()); - // Add exceptions explicitly mentioned in the throws clause. - thrownSet.addAll(thrownTypes); - // Add types to account for unchecked exceptions - thrownSet.addAll(uncheckedExceptionTypes); - - return extendWithNodeWithExceptions(node, thrownSet); - } - - @Override - public Node visitAssert(AssertTree tree, Void p) { - - // see JLS 14.10 - - // If assertions are enabled, then we can just translate the assertion. - if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) { - translateAssertWithAssertionsEnabled(tree); - return null; - } + /** + * Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code + * RuntimeException} and {@code Error}. + */ + protected final Set uncheckedExceptionTypes; - // If assertions are disabled, then nothing is executed. - if (assumeAssertionsDisabled) { - return null; - } + /** + * Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the + * contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes + * from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions". + */ + protected final Set newArrayExceptionTypes; - // Otherwise, we don't know if assertions are enabled, so we use a - // variable "ea" and case-split on it. One branch does execute the - // assertion, while the other assumes assertions are disabled. - VariableTree ea = getAssertionsEnabledVariable(); - - // all necessary labels - Label assertionEnabled = new Label(); - Label assertionDisabled = new Label(); - - extendWithNode(new LocalVariableNode(ea)); - extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled)); - - // 'then' branch (i.e. check the assertion) - addLabelForNextNode(assertionEnabled); - - translateAssertWithAssertionsEnabled(tree); - - // 'else' branch - addLabelForNextNode(assertionDisabled); - - return null; - } - - /** - * Should assertions be assumed to be executed for a given {@link AssertTree}? False by default. - */ - protected boolean assumeAssertionsEnabledFor(AssertTree tree) { - return false; - } - - /** The {@link VariableTree} that indicates whether assertions are enabled or not. */ - protected VariableTree ea = null; - - /** Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not. */ - protected VariableTree getAssertionsEnabledVariable() { - if (ea == null) { - String name = uniqueName("assertionsEnabled"); - Element owner = TreePathUtil.findNearestEnclosingElement(getCurrentPath()); - ExpressionTree initializer = null; - ea = - treeBuilder.buildVariableDecl( - types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer); - handleArtificialTree(ea); - } - return ea; - } - - /** - * Translates an assertion statement to the correct CFG nodes. The translation assumes that - * assertions are enabled. - */ - protected void translateAssertWithAssertionsEnabled(AssertTree tree) { - - // all necessary labels - Label assertEnd = new Label(); - Label elseEntry = new Label(); - - // basic block for the condition - Node condition = unbox(scan(tree.getCondition(), null)); - ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry); - extendWithExtendedNode(cjump); - - // else branch - Node detail = null; - addLabelForNextNode(elseEntry); - if (tree.getDetail() != null) { - detail = scan(tree.getDetail(), null); - } - AssertionErrorNode assertNode = - new AssertionErrorNode(tree, condition, detail, assertionErrorType); - extendWithNode(assertNode); - NodeWithExceptionsHolder exNode = - extendWithNodeWithException( - new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType); - exNode.setTerminatesExecution(true); - - // then branch (nothing happens) - addLabelForNextNode(assertEnd); - } - - /** - * Translates a method marked as {@link AssertMethod} into CFG nodes corresponding to an {@code - * assert} statement. - * - * @param tree the method invocation tree for a method marked as {@link AssertMethod} - * @param assertMethodTuple the assert method tuple for the method - * @param condition the boolean expression node for the argument that the method tests - */ - protected void treatMethodAsAssert( - MethodInvocationTree tree, AssertMethodTuple assertMethodTuple, Node condition) { - // all necessary labels - Label thenLabel = new Label(); - Label elseLabel = new Label(); - ConditionalJump cjump = new ConditionalJump(thenLabel, elseLabel); - extendWithExtendedNode(cjump); - - addLabelForNextNode(assertMethodTuple.isAssertFalse ? thenLabel : elseLabel); - AssertionErrorNode assertNode = - new AssertionErrorNode(tree, condition, null, assertMethodTuple.exceptionType); - extendWithNode(assertNode); - NodeWithExceptionsHolder exNode = - extendWithNodeWithException( - new ThrowNode(null, assertNode, env.getTypeUtils()), assertMethodTuple.exceptionType); - exNode.setTerminatesExecution(true); - - addLabelForNextNode(assertMethodTuple.isAssertFalse ? elseLabel : thenLabel); - } - - @Override - public Node visitAssignment(AssignmentTree tree, Void p) { - - // see JLS 15.26.1 - - AssignmentNode assignmentNode; - ExpressionTree variable = tree.getVariable(); - TypeMirror varType = TreeUtils.typeOf(variable); - - // case 1: lhs is field access - if (TreeUtils.isFieldAccess(variable)) { - // visit receiver - Node receiver = getReceiver(variable); - - // visit expression - Node expression = scan(tree.getExpression(), p); - expression = assignConvert(expression, varType); - - // visit field access (throws null-pointer exception) - FieldAccessNode target = new FieldAccessNode(variable, receiver); - target.setLValue(); - - Element element = TreeUtils.elementFromUse(variable); - if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) { - // No NullPointerException can be thrown, use normal node - extendWithNode(target); - } else { - extendWithNodeWithException(target, nullPointerExceptionType); - } - - // add assignment node - assignmentNode = new AssignmentNode(tree, target, expression); - extendWithNode(assignmentNode); + /** + * Creates {@link CFGTranslationPhaseOne}. + * + * @param treeBuilder builder for new AST nodes + * @param annotationProvider extracts annotations from AST nodes + * @param assumeAssertionsDisabled can assertions be assumed to be disabled? + * @param assumeAssertionsEnabled can assertions be assumed to be enabled? + * @param env annotation processing environment containing type utilities + */ + public CFGTranslationPhaseOne( + TreeBuilder treeBuilder, + AnnotationProvider annotationProvider, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + this.env = env; + this.treeBuilder = treeBuilder; + this.annotationProvider = annotationProvider; + + assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); + this.assumeAssertionsEnabled = assumeAssertionsEnabled; + this.assumeAssertionsDisabled = assumeAssertionsDisabled; + + elements = env.getElementUtils(); + types = env.getTypeUtils(); + trees = Trees.instance(env); + + // initialize lists and maps + treeToCfgNodes = new IdentityHashMap<>(); + treeToConvertedCfgNodes = new IdentityHashMap<>(); + postfixTreeToCfgNodes = new IdentityHashMap<>(); + nodeList = new ArrayList<>(); + bindings = new HashMap<>(); + leaders = new HashSet<>(); + + regularExitLabel = new Label(); + exceptionalExitLabel = new Label(); + tryStack = new TryStack(exceptionalExitLabel); + returnTargetLC = new LabelCell(regularExitLabel); + breakLabels = new HashMap<>(2); + continueLabels = new HashMap<>(2); + returnNodes = new ArrayList<>(); + declaredClasses = new ArrayList<>(); + declaredLambdas = new ArrayList<>(); + + arithmeticExceptionType = getTypeMirror(ArithmeticException.class); + arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class); + assertionErrorType = getTypeMirror(AssertionError.class); + classCastExceptionType = getTypeMirror(ClassCastException.class); + iterableType = types.erasure(getTypeMirror(Iterable.class)); + negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class); + nullPointerExceptionType = getTypeMirror(NullPointerException.class); + outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class); + classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class); + classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class); + noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class); + stringType = getTypeMirror(String.class); + throwableType = getTypeMirror(Throwable.class); + uncheckedExceptionTypes = new ArraySet<>(2); + uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class)); + uncheckedExceptionTypes.add(getTypeMirror(Error.class)); + newArrayExceptionTypes = new ArraySet<>(2); + newArrayExceptionTypes.add(negativeArraySizeExceptionType); + if (outOfMemoryErrorType != null) { + newArrayExceptionTypes.add(outOfMemoryErrorType); + } } - // case 2: lhs is not a field access - else { - Node target = scan(variable, p); - target.setLValue(); + /** + * Performs the actual work of phase one: processing a single body (of a method, lambda, + * top-level block, etc.). + * + * @param bodyPath path to the body of the underlying AST's method + * @param underlyingAST the AST for which the CFG is to be built + * @return the result of phase one + */ + public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) { + + // Set class variables + this.path = bodyPath; + + // Traverse AST of the method body. + try { // "finally" clause is "this.path = null" + Node finalNode = scan(path.getLeaf(), null); + + // If we are building the CFG for a lambda with a single expression as the body, then + // add an extra node for the result of that lambda. + if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { + LambdaExpressionTree lambdaTree = + ((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree(); + if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) { + Node resultNode = + new LambdaResultExpressionNode( + (ExpressionTree) lambdaTree.getBody(), finalNode); + extendWithNode(resultNode); + } + } - assignmentNode = translateAssignment(tree, target, tree.getExpression()); + // Add marker to indicate that the next block will be the exit block. + // Note: if there is a return statement earlier in the method (which is always the case + // for non-void methods), then this is not strictly necessary. However, it is also not a + // problem, as it will just generate a degenerate control graph case that will be + // removed in a later phase. + nodeList.add(new UnconditionalJump(regularExitLabel)); + + return new PhaseOneResult( + underlyingAST, + treeToCfgNodes, + treeToConvertedCfgNodes, + postfixTreeToCfgNodes, + nodeList, + bindings, + leaders, + returnNodes, + regularExitLabel, + exceptionalExitLabel, + declaredClasses, + declaredLambdas, + types); + } finally { + this.path = null; + } } - return assignmentNode; - } - - /** Translate an assignment. */ - protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) { - Node expression = scan(rhs, null); - return translateAssignment(tree, target, expression); - } - - /** Translate an assignment where the RHS has already been scanned. */ - protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) { - assert tree instanceof AssignmentTree || tree instanceof VariableTree; - target.setLValue(); - expression = assignConvert(expression, target.getType()); - AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression); - extendWithNode(assignmentNode); - return assignmentNode; - } - - /** - * Note 1: Requires {@code tree} to be a field or method access tree. - * - *

          Note 2: Visits the receiver and adds all necessary blocks to the CFG. - * - * @param tree the field or method access tree containing the receiver: one of - * MethodInvocationTree, AssignmentTree, or IdentifierTree - * @return the receiver of the field or method access - */ - private Node getReceiver(ExpressionTree tree) { - assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree); - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - // `tree` has an explicit receiver. - MemberSelectTree mtree = (MemberSelectTree) tree; - return scan(mtree.getExpression(), null); + /** + * Process a single body within {@code root}. This method does not process the entire given + * CompilationUnitTree. Rather, it processes one body (of a method/lambda/etc.) within it, which + * corresponds to {@code underlyingAST}. + * + * @param root the compilation unit + * @param underlyingAST the AST corresponding to the body to process + * @return a PhaseOneResult + */ + public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) { + // TODO: Isn't this costly? Is there no cache we can reuse? + TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); + assert bodyPath != null; + return process(bodyPath, underlyingAST); } - // Access through an implicit receiver - Element ele = TreeUtils.elementFromUse(tree); - TypeElement declClassElem = ElementUtils.enclosingTypeElement(ele); - TypeMirror declClassType = ElementUtils.getType(declClassElem); - - if (ElementUtils.isStatic(ele)) { - ClassNameNode node = new ClassNameNode(declClassType, declClassElem); - extendWithClassNameNode(node); - return node; - } + /** + * Perform any actions required when CFG translation creates a new Tree that is not part of the + * original AST. + * + * @param tree the newly created Tree + */ + public void handleArtificialTree(Tree tree) {} - // Access through an implicit `this` - TreePath enclClassPath = TreePathUtil.pathTillClass(getCurrentPath()); - ClassTree enclClassTree = (ClassTree) enclClassPath.getLeaf(); - TypeElement enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); - TypeMirror enclClassType = enclClassElem.asType(); - while (!TypesUtils.isErasedSubtype(enclClassType, declClassType, types)) { - enclClassPath = TreePathUtil.pathTillClass(enclClassPath.getParentPath()); - if (enclClassPath == null) { - enclClassType = declClassType; - break; - } - enclClassTree = (ClassTree) enclClassPath.getLeaf(); - enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); - enclClassType = enclClassElem.asType(); - } - Node node = new ImplicitThisNode(enclClassType); - extendWithNode(node); - return node; - } - - /** - * Map an operation with assignment to the corresponding operation without assignment. - * - * @param kind a Tree.Kind representing an operation with assignment - * @return the Tree.Kind for the same operation without assignment - */ - protected Tree.Kind withoutAssignment(Tree.Kind kind) { - switch (kind) { - case DIVIDE_ASSIGNMENT: - return Tree.Kind.DIVIDE; - case MULTIPLY_ASSIGNMENT: - return Tree.Kind.MULTIPLY; - case REMAINDER_ASSIGNMENT: - return Tree.Kind.REMAINDER; - case MINUS_ASSIGNMENT: - return Tree.Kind.MINUS; - case PLUS_ASSIGNMENT: - return Tree.Kind.PLUS; - case LEFT_SHIFT_ASSIGNMENT: - return Tree.Kind.LEFT_SHIFT; - case RIGHT_SHIFT_ASSIGNMENT: - return Tree.Kind.RIGHT_SHIFT; - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - return Tree.Kind.UNSIGNED_RIGHT_SHIFT; - case AND_ASSIGNMENT: - return Tree.Kind.AND; - case OR_ASSIGNMENT: - return Tree.Kind.OR; - case XOR_ASSIGNMENT: - return Tree.Kind.XOR; - default: - return Tree.Kind.ERRONEOUS; + /** + * Returns the current path for the tree currently being scanned. + * + * @return the current path + */ + public TreePath getCurrentPath() { + return path; } - } - - @Override - public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - // According the JLS 15.26.2, E1 op= E2 is equivalent to - // E1 = (T) ((E1) op (E2)), where T is the type of E1, - // except that E1 is evaluated only once. - // - - Tree.Kind kind = tree.getKind(); - switch (kind) { - case DIVIDE_ASSIGNMENT: - case MULTIPLY_ASSIGNMENT: - case REMAINDER_ASSIGNMENT: - { - // see JLS 15.17 and 15.26.2 - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror exprType = TreeUtils.typeOf(tree); - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); - value = binaryNumericPromotion(value, promotedType); - - BinaryTree operTree = - treeBuilder.buildBinary( - promotedType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) { - operNode = new NumericalMultiplicationNode(operTree, targetRHS, value); - } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) { - if (TypesUtils.isIntegralPrimitive(exprType)) { - operNode = new IntegerDivisionNode(operTree, targetRHS, value); - - extendWithNodeWithException(operNode, arithmeticExceptionType); - } else { - operNode = new FloatingDivisionNode(operTree, targetRHS, value); - } - } else { - assert kind == Tree.Kind.REMAINDER_ASSIGNMENT; - if (TypesUtils.isIntegralPrimitive(exprType)) { - operNode = new IntegerRemainderNode(operTree, targetRHS, value); - - extendWithNodeWithException(operNode, arithmeticExceptionType); - } else { - operNode = new FloatingRemainderNode(operTree, targetRHS, value); - } - } - extendWithNode(operNode); - - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); - - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; - } - - case MINUS_ASSIGNMENT: - case PLUS_ASSIGNMENT: - { - // see JLS 15.18 and 15.26.2 - - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); - - if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { - assert (kind == Tree.Kind.PLUS_ASSIGNMENT); - Node targetRHS = stringConversion(targetLHS); - value = stringConversion(value); - BinaryTree operTree = - treeBuilder.buildBinary( - leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); - handleArtificialTree(operTree); - Node operNode = new StringConcatenateNode(operTree, targetRHS, value); - extendWithNode(operNode); - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, operNode); - extendWithNode(assignNode); - return assignNode; - } else { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); - value = binaryNumericPromotion(value, promotedType); - - BinaryTree operTree = - treeBuilder.buildBinary( - promotedType, - withoutAssignment(kind), - tree.getVariable(), - tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.PLUS_ASSIGNMENT) { - operNode = new NumericalAdditionNode(operTree, targetRHS, value); - } else { - assert kind == Tree.Kind.MINUS_ASSIGNMENT; - operNode = new NumericalSubtractionNode(operTree, targetRHS, value); - } - extendWithNode(operNode); - - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); - - // Map the compound assignment tree to an assignment node, which - // will have the correct type. - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; - } - } - case LEFT_SHIFT_ASSIGNMENT: - case RIGHT_SHIFT_ASSIGNMENT: - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - { - // see JLS 15.19 and 15.26.2 - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - - Node targetRHS = unaryNumericPromotion(targetLHS); - value = unaryNumericPromotion(value); - - BinaryTree operTree = - treeBuilder.buildBinary( - leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) { - operNode = new LeftShiftNode(operTree, targetRHS, value); - } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) { - operNode = new SignedRightShiftNode(operTree, targetRHS, value); - } else { - assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; - operNode = new UnsignedRightShiftNode(operTree, targetRHS, value); - } - extendWithNode(operNode); - - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); - - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; + // TODO: remove method and instead use JCP to add version-specific methods. + // Switch expressions first appeared in 12, standard in 14, so don't use 17. + // TODO: look into changing back to TreePathScanner and removing path/getCurrentPath. + @Override + public Node scan(Tree tree, Void p) { + if (tree == null) { + return null; } - case AND_ASSIGNMENT: - case OR_ASSIGNMENT: - case XOR_ASSIGNMENT: - // see JLS 15.22 - Node targetLHS = scan(tree.getVariable(), p); - Node value = scan(tree.getExpression(), p); - - TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); - TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); - - Node targetRHS = null; - if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - targetRHS = binaryNumericPromotion(targetLHS, promotedType); - value = binaryNumericPromotion(value, promotedType); - } else if (TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType)) { - targetRHS = unbox(targetLHS); - value = unbox(value); - } else { - throw new BugInCF("Both arguments to logical operation must be numeric or boolean"); + TreePath prev = path; + @SuppressWarnings("interning:not.interned") // Looking for exact match. + boolean treeIsLeaf = path.getLeaf() != tree; + if (treeIsLeaf) { + path = new TreePath(path, tree); } + try { + // TODO: use JCP to add version-specific behavior + if (SystemUtil.jreVersion >= 14) { + // Must use String comparison to support compiling on JDK 11 and earlier. + // Features added between JDK 12 and JDK 17 inclusive. + switch (tree.getKind().name()) { + case "BINDING_PATTERN": + return visitBindingPattern17(path.getLeaf(), p); + case "SWITCH_EXPRESSION": + return visitSwitchExpression17(tree, p); + case "YIELD": + return visitYield17(tree, p); + case "DECONSTRUCTION_PATTERN": + return visitDeconstructionPattern21(tree, p); + default: + // fall through to generic behavior + } + } - BinaryTree operTree = - treeBuilder.buildBinary( - leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); - handleArtificialTree(operTree); - Node operNode; - if (kind == Tree.Kind.AND_ASSIGNMENT) { - operNode = new BitwiseAndNode(operTree, targetRHS, value); - } else if (kind == Tree.Kind.OR_ASSIGNMENT) { - operNode = new BitwiseOrNode(operTree, targetRHS, value); - } else { - assert kind == Tree.Kind.XOR_ASSIGNMENT; - operNode = new BitwiseXorNode(operTree, targetRHS, value); + return tree.accept(this, p); + } finally { + path = prev; } - extendWithNode(operNode); + } - TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); - TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); - handleArtificialTree(castTree); - TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); - castNode.setInSource(false); - extendWithNode(castNode); + /** + * Visit a SwitchExpressionTree. + * + * @param yieldTree a YieldTree, typed as Tree to be backward-compatible + * @param p parameter + * @return the result of visiting the switch expression tree + */ + public Node visitYield17(Tree yieldTree, Void p) { + ExpressionTree resultExpression = YieldUtils.getValue(yieldTree); + switchBuilder.buildSwitchExpressionResult(resultExpression); + return null; + } - AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); - extendWithNode(assignNode); - return assignNode; - default: - throw new BugInCF("unexpected compound assignment type"); + /** + * Visit a SwitchExpressionTree + * + * @param switchExpressionTree a SwitchExpressionTree, typed as Tree to be backward-compatible + * @param p parameter + * @return the result of visiting the switch expression tree + */ + public Node visitSwitchExpression17(Tree switchExpressionTree, Void p) { + SwitchBuilder oldSwitchBuilder = switchBuilder; + switchBuilder = new SwitchBuilder(switchExpressionTree); + Node result = switchBuilder.build(); + switchBuilder = oldSwitchBuilder; + return result; } - } - - @Override - public Node visitBinary(BinaryTree tree, Void p) { - // Note that for binary operations it is important to perform any required promotion on the - // left operand before generating any Nodes for the right operand, because labels must be - // inserted AFTER ALL preceding Nodes and BEFORE ALL following Nodes. - Node r = null; - Tree leftTree = tree.getLeftOperand(); - Tree rightTree = tree.getRightOperand(); - - Tree.Kind kind = tree.getKind(); - switch (kind) { - case DIVIDE: - case MULTIPLY: - case REMAINDER: - { - // see JLS 15.17 - - TypeMirror exprType = TreeUtils.typeOf(tree); - TypeMirror leftType = TreeUtils.typeOf(leftTree); - TypeMirror rightType = TreeUtils.typeOf(rightTree); - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - - Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); - Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); - - if (kind == Tree.Kind.MULTIPLY) { - r = new NumericalMultiplicationNode(tree, left, right); - } else if (kind == Tree.Kind.DIVIDE) { - if (TypesUtils.isIntegralPrimitive(exprType)) { - r = new IntegerDivisionNode(tree, left, right); - - extendWithNodeWithException(r, arithmeticExceptionType); - } else { - r = new FloatingDivisionNode(tree, left, right); - } - } else { - assert kind == Tree.Kind.REMAINDER; - if (TypesUtils.isIntegralPrimitive(exprType)) { - r = new IntegerRemainderNode(tree, left, right); - extendWithNodeWithException(r, arithmeticExceptionType); - } else { - r = new FloatingRemainderNode(tree, left, right); - } - } - break; - } + /** + * Visit a BindingPatternTree + * + * @param bindingPatternTree a BindingPatternTree, typed as Tree to be backward-compatible + * @param p parameter + * @return the result of visiting the binding pattern tree + */ + public Node visitBindingPattern17(Tree bindingPatternTree, Void p) { + ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); + TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass); + Node receiver = new ImplicitThisNode(classElem.asType()); + VariableTree varTree = BindingPatternUtils.getVariable(bindingPatternTree); + VariableDeclarationNode variableDeclarationNode = new VariableDeclarationNode(varTree); + extendWithNode(variableDeclarationNode); + LocalVariableNode varNode = new LocalVariableNode(varTree, receiver); + extendWithNode(varNode); + return varNode; + } - case MINUS: - case PLUS: - { - // see JLS 15.18 - - // TypeMirror exprType = InternalUtils.typeOf(tree); - TypeMirror leftType = TreeUtils.typeOf(leftTree); - TypeMirror rightType = TreeUtils.typeOf(rightTree); - - if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { - assert (kind == Tree.Kind.PLUS); - Node left = stringConversion(scan(leftTree, p)); - Node right = stringConversion(scan(rightTree, p)); - r = new StringConcatenateNode(tree, left, right); - } else { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); - Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); - - // TODO: Decide whether to deal with floating-point value - // set conversion. - if (kind == Tree.Kind.PLUS) { - r = new NumericalAdditionNode(tree, left, right); - } else { - assert kind == Tree.Kind.MINUS; - r = new NumericalSubtractionNode(tree, left, right); - } - } - break; + /** + * Visit a DeconstructionPatternTree. + * + * @param deconstructionPatternTree a DeconstructionPatternTree, typed as Tree so the Checker + * Framework compiles under JDK 20 and earlier + * @param p an unused parameter + * @return the result of visiting the tree + */ + public Node visitDeconstructionPattern21(Tree deconstructionPatternTree, Void p) { + List nestedPatternTrees = + DeconstructionPatternUtils.getNestedPatterns(deconstructionPatternTree); + List nestedPatterns = new ArrayList<>(nestedPatternTrees.size()); + for (Tree pattern : nestedPatternTrees) { + nestedPatterns.add(scan(pattern, p)); } - case LEFT_SHIFT: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - { - // see JLS 15.19 - - Node left = unaryNumericPromotion(scan(leftTree, p)); - Node right = unaryNumericPromotion(scan(rightTree, p)); - - if (kind == Tree.Kind.LEFT_SHIFT) { - r = new LeftShiftNode(tree, left, right); - } else if (kind == Tree.Kind.RIGHT_SHIFT) { - r = new SignedRightShiftNode(tree, left, right); - } else { - assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT; - r = new UnsignedRightShiftNode(tree, left, right); - } - break; - } + DeconstructorPatternNode dcpN = + new DeconstructorPatternNode( + TreeUtils.typeOf(deconstructionPatternTree), + deconstructionPatternTree, + nestedPatterns); + extendWithNode(dcpN); + return dcpN; + } - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case LESS_THAN: - case LESS_THAN_EQUAL: - { - // see JLS 15.20.1 - TypeMirror leftType = TreeUtils.typeOf(leftTree); - if (TypesUtils.isBoxedPrimitive(leftType)) { - leftType = types.unboxedType(leftType); - } - - TypeMirror rightType = TreeUtils.typeOf(rightTree); - if (TypesUtils.isBoxedPrimitive(rightType)) { - rightType = types.unboxedType(rightType); - } - - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); - Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); - - Node node; - if (kind == Tree.Kind.GREATER_THAN) { - node = new GreaterThanNode(tree, left, right); - } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) { - node = new GreaterThanOrEqualNode(tree, left, right); - } else if (kind == Tree.Kind.LESS_THAN) { - node = new LessThanNode(tree, left, right); - } else { - assert kind == Tree.Kind.LESS_THAN_EQUAL; - node = new LessThanOrEqualNode(tree, left, right); - } - - extendWithNode(node); - - return node; - } + /* --------------------------------------------------------- */ + /* Nodes and Labels Management */ + /* --------------------------------------------------------- */ - case EQUAL_TO: - case NOT_EQUAL_TO: - { - // see JLS 15.21 - TreeInfo leftInfo = getTreeInfo(leftTree); - TreeInfo rightInfo = getTreeInfo(rightTree); - Node left = scan(leftTree, p); - Node right = scan(rightTree, p); - - if (leftInfo.isNumeric() - && rightInfo.isNumeric() - && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { - // JLS 15.21.1 numerical equality - TypeMirror promotedType = - binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType()); - left = binaryNumericPromotion(left, promotedType); - right = binaryNumericPromotion(right, promotedType); - } else if (leftInfo.isBoolean() - && rightInfo.isBoolean() - && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { - // JSL 15.21.2 boolean equality - left = unboxAsNeeded(left, leftInfo.isBoxed()); - right = unboxAsNeeded(right, rightInfo.isBoxed()); - } - - Node node; - if (kind == Tree.Kind.EQUAL_TO) { - node = new EqualToNode(tree, left, right); - } else { - assert kind == Tree.Kind.NOT_EQUAL_TO; - node = new NotEqualNode(tree, left, right); - } - extendWithNode(node); - - return node; + /** + * Add a node to the lookup map if it not already present. + * + * @param node the node to add to the lookup map + */ + protected void addToLookupMap(Node node) { + Tree tree = node.getTree(); + if (tree == null) { + return; } - - case AND: - case OR: - case XOR: - { - // see JLS 15.22 - TypeMirror leftType = TreeUtils.typeOf(leftTree); - TypeMirror rightType = TreeUtils.typeOf(rightTree); - boolean isBooleanOp = - TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType); - - Node left; - Node right; - - if (isBooleanOp) { - left = unbox(scan(leftTree, p)); - right = unbox(scan(rightTree, p)); - } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { - TypeMirror promotedType = binaryPromotedType(leftType, rightType); - left = binaryNumericPromotion(scan(leftTree, p), promotedType); - right = binaryNumericPromotion(scan(rightTree, p), promotedType); - } else { - left = unbox(scan(leftTree, p)); - right = unbox(scan(rightTree, p)); - } - - Node node; - if (kind == Tree.Kind.AND) { - node = new BitwiseAndNode(tree, left, right); - } else if (kind == Tree.Kind.OR) { - node = new BitwiseOrNode(tree, left, right); - } else { - assert kind == Tree.Kind.XOR; - node = new BitwiseXorNode(tree, left, right); - } - - extendWithNode(node); - - return node; + Set existing = treeToCfgNodes.get(tree); + if (existing == null) { + Set newSet = new IdentityArraySet(1); + newSet.add(node); + treeToCfgNodes.put(tree, newSet); + } else { + existing.add(node); } - case CONDITIONAL_AND: - case CONDITIONAL_OR: - { - // see JLS 15.23 and 15.24 - - // all necessary labels - Label rightStartLabel = new Label(); - Label shortCircuitLabel = new Label(); - - // left-hand side - Node left = scan(leftTree, p); - - ConditionalJump cjump; - if (kind == Tree.Kind.CONDITIONAL_AND) { - cjump = new ConditionalJump(rightStartLabel, shortCircuitLabel); - cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE); - } else { - cjump = new ConditionalJump(shortCircuitLabel, rightStartLabel); - cjump.setTrueFlowRule(FlowRule.THEN_TO_THEN); - } - extendWithExtendedNode(cjump); - - // right-hand side - addLabelForNextNode(rightStartLabel); - Node right = scan(rightTree, p); - - // conditional expression itself - addLabelForNextNode(shortCircuitLabel); - Node node; - if (kind == Tree.Kind.CONDITIONAL_AND) { - node = new ConditionalAndNode(tree, left, right); - } else { - node = new ConditionalOrNode(tree, left, right); - } - extendWithNode(node); - return node; + Tree enclosingParens = parenMapping.get(tree); + while (enclosingParens != null) { + Set exp = + treeToCfgNodes.computeIfAbsent(enclosingParens, k -> new IdentityArraySet<>(1)); + // `node` could already be in set `exp`, but it's probably as fast to just add again + exp.add(node); + enclosingParens = parenMapping.get(enclosingParens); } - default: - throw new BugInCF("unexpected binary tree: " + kind); - } - assert r != null : "unexpected binary tree"; - extendWithNode(r); - return r; - } - - @Override - public Node visitBlock(BlockTree tree, Void p) { - for (StatementTree n : tree.getStatements()) { - scan(n, null); - } - return null; - } - - @Override - public Node visitBreak(BreakTree tree, Void p) { - Name label = tree.getLabel(); - if (label == null) { - assert breakTargetLC != null : "no target for break statement"; - - extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); - } else { - assert breakLabels.containsKey(label); - - extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label))); } - return null; - } - - // This visits a switch statement. - // Switch expressions are visited by visitSwitchExpression17. - @Override - public Node visitSwitch(SwitchTree tree, Void p) { - SwitchBuilder builder = new SwitchBuilder(tree); - builder.build(); - return null; - } - - /** - * Helper class for handling switch statements and switch expressions, including all their - * substatements such as case labels. - */ - private class SwitchBuilder { - /** - * The tree for the switch statement or switch expression. Its type may be {@link SwitchTree} - * (for a switch statement) or {@code SwitchExpressionTree}. + * Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree + * should already be in the pre-conversion lookup map. This method is used to update the + * Tree-Node mapping with conversion nodes. + * + * @param node the node to add to the lookup map */ - private final Tree switchTree; - - /** The case trees of {@code switchTree} */ - private final List caseTrees; + protected void addToConvertedLookupMap(Node node) { + Tree tree = node.getTree(); + addToConvertedLookupMap(tree, node); + } /** - * The Tree for the selector expression. + * Add a node in the post-conversion lookup map. The tree argument should already be in the + * pre-conversion lookup map. This method is used to update the Tree-Node mapping with + * conversion nodes. * - *

          -     *   switch ( selector expression ) { ... }
          -     * 
          + * @param tree the tree used as a key in the map + * @param node the node to add to the lookup map */ - private final ExpressionTree selectorExprTree; - - /** The labels for the case bodies. */ - private final Label[] caseBodyLabels; + protected void addToConvertedLookupMap(Tree tree, Node node) { + assert tree != null; + assert treeToCfgNodes.containsKey(tree); + Set existing = treeToConvertedCfgNodes.get(tree); + if (existing == null) { + Set newSet = new IdentityArraySet<>(1); + newSet.add(node); + treeToConvertedCfgNodes.put(tree, newSet); + } else { + existing.add(node); + } + } /** - * The Node for the assignment of the switch selector expression to a synthetic local variable. + * Extend the list of extended nodes with a node. + * + * @param node the node to add */ - private AssignmentNode selectorExprAssignment; + protected void extendWithNode(Node node) { + addToLookupMap(node); + extendWithExtendedNode(new NodeHolder(node)); + } /** - * If {@link #switchTree} is a switch expression, then this is a result variable: the synthetic - * variable that all results of {@code #switchTree} are assigned to. Otherwise, this is null. + * Extend the list of extended nodes with a node, where {@code node} might throw the exception + * {@code cause}. + * + * @param node the node to add + * @param cause an exception that the node might throw + * @return the node holder */ - private @Nullable VariableTree switchExprVarTree; + protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) { + addToLookupMap(node); + return extendWithNodeWithExceptions(node, Collections.singleton(cause)); + } /** - * Construct a SwitchBuilder. + * Extend the list of extended nodes with a node, where {@code node} might throw any of the + * exceptions in {@code causes}. * - * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} + * @param node the node to add + * @param causes the set of exceptions that the node might throw + * @return the node holder */ - private SwitchBuilder(Tree switchTree) { - this.switchTree = switchTree; - if (TreeUtils.isSwitchStatement(switchTree)) { - SwitchTree switchStatementTree = (SwitchTree) switchTree; - this.caseTrees = switchStatementTree.getCases(); - this.selectorExprTree = switchStatementTree.getExpression(); - } else { - this.caseTrees = SwitchExpressionUtils.getCases(switchTree); - this.selectorExprTree = SwitchExpressionUtils.getExpression(switchTree); - } - // "+ 1" for the default case. If the switch has an explicit default case, then - // the last element of the array is never used. - this.caseBodyLabels = new Label[caseTrees.size() + 1]; + protected NodeWithExceptionsHolder extendWithNodeWithExceptions( + Node node, Set causes) { + addToLookupMap(node); + Map> exceptions = new ArrayMap<>(causes.size()); + for (TypeMirror cause : causes) { + exceptions.put(cause, tryStack.possibleLabels(cause)); + } + NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); + extendWithExtendedNode(exNode); + return exNode; } /** - * Build up the CFG for the switchTree. + * Extend a list of extended nodes with a ClassName node. * - * @return if the switch is a switch expression, then a {@link SwitchExpressionNode}; otherwise, - * null + *

          Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw + * one of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1). + * + * @param node the ClassName node to add + * @return the node holder */ - public @Nullable SwitchExpressionNode build() { - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(new Label()); - int numCases = caseTrees.size(); - - for (int i = 0; i < numCases; ++i) { - caseBodyLabels[i] = new Label(); - } - caseBodyLabels[numCases] = breakTargetLC.peekLabel(); + protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) { + Set thrownSet = new ArraySet<>(4); + if (classCircularityErrorType != null) { + thrownSet.add(classCircularityErrorType); + } + if (classFormatErrorType != null) { + thrownSet.add(classFormatErrorType); + } + if (noClassDefFoundErrorType != null) { + thrownSet.add(noClassDefFoundErrorType); + } + if (outOfMemoryErrorType != null) { + thrownSet.add(outOfMemoryErrorType); + } + + return extendWithNodeWithExceptions(node, thrownSet); + } + + /** + * Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list + * if {@code pred} is not present. + * + * @param node the node to add + * @param pred the desired predecessor of node + * @return the node holder + */ + protected T insertNodeAfter(T node, Node pred) { + addToLookupMap(node); + insertExtendedNodeAfter(new NodeHolder(node), pred); + return node; + } + + /** + * Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in + * the list of extended nodes, or append to the list if {@code pred} is not present. + * + * @param node the node to add + * @param causes the set of exceptions that the node might throw + * @param pred the desired predecessor of node + * @return the node holder + */ + protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter( + Node node, Set causes, Node pred) { + addToLookupMap(node); + Map> exceptions = new ArrayMap<>(causes.size()); + for (TypeMirror cause : causes) { + exceptions.put(cause, tryStack.possibleLabels(cause)); + } + NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); + insertExtendedNodeAfter(exNode, pred); + return exNode; + } + + /** + * Extend the list of extended nodes with an extended node. + * + * @param n the extended node + */ + protected void extendWithExtendedNode(ExtendedNode n) { + nodeList.add(n); + } + + /** + * Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code + * n} if {@code pred} is not present. + * + * @param n the extended node + * @param pred the desired predecessor + */ + @SuppressWarnings("ModifyCollectionInEnhancedForLoop") + protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) { + int index = -1; + for (int i = 0; i < nodeList.size(); i++) { + ExtendedNode inList = nodeList.get(i); + if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) { + if (inList.getNode() == pred) { + index = i; + break; + } + } + } + if (index != -1) { + nodeList.add(index + 1, n); + // update bindings + for (Map.Entry e : bindings.entrySet()) { + if (e.getValue() >= index + 1) { + bindings.put(e.getKey(), e.getValue() + 1); + } + } + // update leaders + Set oldLeaders = new HashSet<>(leaders); + leaders.clear(); + for (Integer l : oldLeaders) { + if (l >= index + 1) { + leaders.add(l + 1); + } else { + leaders.add(l); + } + } + } else { + nodeList.add(n); + } + } + + /** + * Add the label {@code l} to the extended node that will be placed next in the sequence. + * + * @param l the node to add to the forthcoming extended node + */ + protected void addLabelForNextNode(Label l) { + if (bindings.containsKey(l)) { + throw new BugInCF("bindings already contains key %s: %s", l, bindings); + } + leaders.add(nodeList.size()); + bindings.put(l, nodeList.size()); + } + + /* --------------------------------------------------------- */ + /* Utility Methods */ + /* --------------------------------------------------------- */ + + /** The UID for the next unique name. */ + protected long uid = 0; + + /** + * Returns a unique name starting with {@code prefix}. + * + * @param prefix the prefix of the unique name + * @return a unique name starting with {@code prefix} + */ + protected String uniqueName(String prefix) { + return prefix + "#num" + uid++; + } + + /** + * If the input node is an unboxed primitive type, insert a call to the appropriate valueOf + * method, otherwise leave it alone. + * + * @param node in input node + * @return a Node representing the boxed version of the input, which may simply be the input + * node + */ + protected Node box(Node node) { + // For boxing conversion, see JLS 5.1.7 + if (TypesUtils.isPrimitive(node.getType())) { + PrimitiveType primitive = types.getPrimitiveType(node.getType().getKind()); + TypeMirror boxedType = types.getDeclaredType(types.boxedClass(primitive)); + + TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); + IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement); + handleArtificialTree(classTree); + // No need to handle possible errors from evaluating a class literal here + // since this is synthetic code that can't fail. + ClassNameNode className = new ClassNameNode(classTree); + className.setInSource(false); + insertNodeAfter(className, node); + + MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree); + handleArtificialTree(valueOfSelect); + MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className); + valueOfAccess.setInSource(false); + insertNodeAfter(valueOfAccess, className); + + MethodInvocationTree valueOfCall = + treeBuilder.buildMethodInvocation( + valueOfSelect, (ExpressionTree) node.getTree()); + handleArtificialTree(valueOfCall); + Node boxed = + new MethodInvocationNode( + valueOfCall, + valueOfAccess, + Collections.singletonList(node), + getCurrentPath()); + boxed.setInSource(false); + // Add Throwable to account for unchecked exceptions + addToConvertedLookupMap(node.getTree(), boxed); + insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess); + return boxed; + } else { + return node; + } + } + + /** + * If the input node is a boxed type, unbox it, otherwise leave it alone. + * + * @param node in input node + * @return a Node representing the unboxed version of the input, which may simply be the input + * node + */ + protected Node unbox(Node node) { + if (TypesUtils.isBoxedPrimitive(node.getType())) { + + MemberSelectTree primValueSelect = + treeBuilder.buildPrimValueMethodAccess(node.getTree()); + handleArtificialTree(primValueSelect); + MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node); + primValueAccess.setInSource(false); + // Method access may throw NullPointerException + insertNodeWithExceptionsAfter( + primValueAccess, Collections.singleton(nullPointerExceptionType), node); + + MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect); + handleArtificialTree(primValueCall); + Node unboxed = + new MethodInvocationNode( + primValueCall, + primValueAccess, + Collections.emptyList(), + getCurrentPath()); + unboxed.setInSource(false); + + // Add Throwable to account for unchecked exceptions + addToConvertedLookupMap(node.getTree(), unboxed); + insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess); + return unboxed; + } else { + return node; + } + } + + private TreeInfo getTreeInfo(Tree tree) { + TypeMirror type = TreeUtils.typeOf(tree); + boolean boxed = TypesUtils.isBoxedPrimitive(type); + TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; + + boolean bool = TypesUtils.isBooleanType(type); + boolean numeric = TypesUtils.isNumeric(unboxedType); + + return new TreeInfo() { + @Override + public boolean isNumeric() { + return numeric; + } + + @Override + public boolean isBoxed() { + return boxed; + } + + @Override + public boolean isBoolean() { + return bool; + } + + @Override + public TypeMirror unboxedType() { + return unboxedType; + } + }; + } + + /** + * Returns the unboxed tree if necessary, as described in JLS 5.1.8. + * + * @return the unboxed tree if necessary, as described in JLS 5.1.8 + */ + private Node unboxAsNeeded(Node node, boolean boxed) { + return boxed ? unbox(node) : node; + } + + /** + * Convert the input node to String type, if it isn't already. + * + * @param node an input node + * @return a Node with the value promoted to String, which may be the input node + */ + protected Node stringConversion(Node node) { + // For string conversion, see JLS 5.1.11 + if (!TypesUtils.isString(node.getType())) { + Node converted = new StringConversionNode(node.getTree(), node, stringType); + addToConvertedLookupMap(converted); + insertNodeAfter(converted, node); + return converted; + } else { + return node; + } + } + + /** + * Perform unary numeric promotion on the input node. + * + * @param node a node producing a value of numeric primitive or boxed type + * @return a Node with the value promoted to the int, long, float, or double; may return be the + * input node + */ + protected Node unaryNumericPromotion(Node node) { + // For unary numeric promotion, see JLS 5.6.1 + node = unbox(node); + + switch (node.getType().getKind()) { + case BYTE: + case CHAR: + case SHORT: + { + TypeMirror intType = types.getPrimitiveType(TypeKind.INT); + Node widened = new WideningConversionNode(node.getTree(), node, intType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } + default: + // Nothing to do. + break; + } + + return node; + } + + /** + * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and + * false otherwise. + */ + protected boolean isNumericOrBoxed(TypeMirror type) { + if (TypesUtils.isBoxedPrimitive(type)) { + type = types.unboxedType(type); + } + return TypesUtils.isNumeric(type); + } + + /** + * Compute the type to which two numeric types must be promoted before performing a binary + * numeric operation on them. The input types must both be numeric and the output type is + * primitive. + * + * @param left the type of the left operand + * @param right the type of the right operand + * @return a TypeMirror representing the binary numeric promoted type + */ + protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { + if (TypesUtils.isBoxedPrimitive(left)) { + left = types.unboxedType(left); + } + if (TypesUtils.isBoxedPrimitive(right)) { + right = types.unboxedType(right); + } + TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right); + return types.getPrimitiveType(promotedTypeKind); + } + + /** + * Perform binary numeric promotion on the input node to make it match the expression type. + * + * @param node a node producing a value of numeric primitive or boxed type + * @param exprType the type to promote the value to + * @return a Node with the value promoted to the exprType, which may be the input node + */ + protected Node binaryNumericPromotion(Node node, TypeMirror exprType) { + // For binary numeric promotion, see JLS 5.6.2 + node = unbox(node); + + if (!types.isSameType(node.getType(), exprType)) { + Node widened = new WideningConversionNode(node.getTree(), node, exprType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } else { + return node; + } + } + + /** + * Perform widening primitive conversion on the input node to make it match the destination + * type. + * + * @param node a node producing a value of numeric primitive type + * @param destType the type to widen the value to + * @return a Node with the value widened to the exprType, which may be the input node + */ + protected Node widen(Node node, TypeMirror destType) { + // For widening conversion, see JLS 5.1.2 + assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) + : "widening must be applied to primitive types"; + if (types.isSubtype(node.getType(), destType) + && !types.isSameType(node.getType(), destType)) { + Node widened = new WideningConversionNode(node.getTree(), node, destType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } else { + return node; + } + } + + /** + * Perform narrowing conversion on the input node to make it match the destination type. + * + * @param node a node producing a value of numeric primitive type + * @param destType the type to narrow the value to + * @return a Node with the value narrowed to the exprType, which may be the input node + */ + protected Node narrow(Node node, TypeMirror destType) { + // For narrowing conversion, see JLS 5.1.3 + assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) + : "narrowing must be applied to primitive types"; + if (types.isSubtype(destType, node.getType()) + && !types.isSameType(destType, node.getType())) { + Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType); + addToConvertedLookupMap(narrowed); + insertNodeAfter(narrowed, node); + return narrowed; + } else { + return node; + } + } + + /** + * Perform narrowing conversion and optionally boxing conversion on the input node to make it + * match the destination type. + * + * @param node a node producing a value of numeric primitive type + * @param destType the type to narrow the value to (possibly boxed) + * @return a Node with the value narrowed and boxed to the destType, which may be the input node + */ + protected Node narrowAndBox(Node node, TypeMirror destType) { + if (TypesUtils.isBoxedPrimitive(destType)) { + return box(narrow(node, types.unboxedType(destType))); + } else { + return narrow(node, destType); + } + } + + /** + * Return whether a conversion from the type of the node to varType requires narrowing. + * + * @param varType the type of a variable (or general LHS) to be converted to + * @param node a node whose value is being converted + * @return whether this conversion requires narrowing to succeed + */ + protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { + // Narrowing is restricted to cases where the left hand side is byte, char, short or Byte, + // Char, Short and the right hand side is a constant. + TypeMirror unboxedVarType = + TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType; + TypeKind unboxedVarKind = unboxedVarType.getKind(); + boolean isLeftNarrowableTo = + unboxedVarKind == TypeKind.BYTE + || unboxedVarKind == TypeKind.SHORT + || unboxedVarKind == TypeKind.CHAR; + boolean isRightConstant = node instanceof ValueLiteralNode; + return isLeftNarrowableTo && isRightConstant; + } + + /** + * Assignment conversion and method invocation conversion are almost identical, except that + * assignment conversion allows narrowing. We factor out the common logic here. + * + * @param node a Node producing a value + * @param varType the type of a variable + * @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not + * (for method invocation conversion) + * @return a Node with the value converted to the type of the variable, which may be the input + * node itself + */ + protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) { + // For assignment conversion, see JLS 5.2 + // For method invocation conversion, see JLS 5.3 + + // Check for identical types or "identity conversion" + TypeMirror nodeType = node.getType(); + boolean isSameType = types.isSameType(nodeType, varType); + if (isSameType) { + return node; + } + + boolean isRightNumeric = TypesUtils.isNumeric(nodeType); + boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); + boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); + boolean isRightReference = nodeType instanceof ReferenceType; + boolean isLeftNumeric = TypesUtils.isNumeric(varType); + boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); + // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType); + boolean isLeftReference = varType instanceof ReferenceType; + boolean isSubtype = types.isSubtype(nodeType, varType); + + if (isRightNumeric && isLeftNumeric && isSubtype) { + node = widen(node, varType); + } else if (isRightReference && isLeftReference && isSubtype) { + // widening reference conversion is a no-op, but if it + // applies, then later conversions do not. + } else if (isRightPrimitive && isLeftReference) { + if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { + node = narrowAndBox(node, varType); + } else { + node = box(node); + } + } else if (isRightBoxed && isLeftPrimitive) { + node = unbox(node); + nodeType = node.getType(); + + if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) { + node = widen(node, varType); + } + } else if (isRightPrimitive && isLeftPrimitive) { + if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { + node = narrow(node, varType); + } + } + // `node` might have been re-assigned; if `nodeType` is needed, set it again. + // nodeType = node.getType(); + + // TODO: if checkers need to know about null references of + // a particular type, add logic for them here. + + return node; + } + + /** + * Perform assignment conversion so that it can be assigned to a variable of the given type. + * + * @param node a Node producing a value + * @param varType the type of a variable + * @return a Node with the value converted to the type of the variable, which may be the input + * node itself + */ + protected Node assignConvert(Node node, TypeMirror varType) { + return commonConvert(node, varType, true); + } + + /** + * Perform method invocation conversion so that the node can be passed as a formal parameter of + * the given type. + * + * @param node a Node producing a value + * @param formalType the type of a formal parameter + * @return a Node with the value converted to the type of the formal, which may be the input + * node itself + */ + protected Node methodInvocationConvert(Node node, TypeMirror formalType) { + return commonConvert(node, formalType, false); + } + + /** + * Given a method element, its type at the call site, and a list of argument expressions, return + * a list of {@link Node}s representing the arguments converted for a call of the method. This + * method applies to both method invocations and constructor calls. The argument of newClassTree + * is null when we visit {@link MethodInvocationTree}, and is non-null when we visit {@link + * NewClassTree}. + * + * @param tree the invocation tree for the call + * @param executable an ExecutableElement representing a method/constructor to be called + * @param executableType an ExecutableType representing the type of the method/constructor call; + * the type must be viewpoint-adapted to the call + * @param actualExprs a List of argument expressions to a call + * @param newClassTree the NewClassTree if the method is the invocation of a constructor + * @return a List of {@link Node}s representing arguments after conversions required by a call + * to this method + */ + protected List convertCallArguments( + ExpressionTree tree, + ExecutableElement executable, + ExecutableType executableType, + List actualExprs, + @Nullable NewClassTree newClassTree) { + // NOTE: It is important to convert one method argument before generating CFG nodes for the + // next argument, since label binding expects nodes to be generated in execution order. + // Therefore, this method first determines which conversions need to be applied and then + // iterates over the actual arguments. + List formals = executableType.getParameterTypes(); + int numFormals = formals.size(); + + ArrayList convertedNodes = new ArrayList<>(numFormals); + AssertMethodTuple assertMethodTuple = getAssertMethodTuple(executable); + + int numActuals = actualExprs.size(); + if (executable.isVarArgs()) { + // Create a new array argument if the actuals outnumber the formals, or if the last + // actual is not assignable to the last formal. + int lastArgIndex = numFormals - 1; + TypeMirror lastParamType = formals.get(lastArgIndex); + if (numActuals == numFormals + && types.isAssignable( + TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) { + // Normal call with no array creation, apply method + // invocation conversion to all arguments. + for (int i = 0; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + if (i == assertMethodTuple.booleanParam) { + treatMethodAsAssert( + (MethodInvocationTree) tree, assertMethodTuple, actualVal); + } + if (actualVal == null) { + throw new BugInCF( + "CFGBuilder: scan returned null for %s [%s]", + actualExprs.get(i), actualExprs.get(i).getClass()); + } + convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); + } + } else { + assert lastParamType instanceof ArrayType + : "variable argument formal must be an array"; + // Handle anonymous constructors with an explicit enclosing expression. + // There is a mismatch between the number of parameters and arguments + // when the following conditions are met: + // 1. Java version >= 11, + // 2. the method is an anonymous constructor, + // 3. the executable element has varargs, + // 4. the constructor is invoked with an explicit enclosing expression. + // In this case, the parameters have an enclosing expression as its first parameter, + // while the arguments do not have such element. Hence, decrease the lastArgIndex + // to organize the arguments from the correct index later. + if (SystemUtil.jreVersion >= 11 + && newClassTree != null + && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( + executable, newClassTree)) { + lastArgIndex--; + } + + // Apply method invocation conversion to lastArgIndex arguments and use the + // remaining ones to initialize an array. + for (int i = 0; i < lastArgIndex; i++) { + Node actualVal = scan(actualExprs.get(i), null); + if (i == assertMethodTuple.booleanParam) { + treatMethodAsAssert( + (MethodInvocationTree) tree, assertMethodTuple, actualVal); + } + convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); + } + + TypeMirror elemType = ((ArrayType) lastParamType).getComponentType(); + + List inits = new ArrayList<>(numActuals - lastArgIndex); + List initializers = new ArrayList<>(numActuals - lastArgIndex); + for (int i = lastArgIndex; i < numActuals; i++) { + inits.add(actualExprs.get(i)); + Node actualVal = scan(actualExprs.get(i), null); + initializers.add(assignConvert(actualVal, elemType)); + } + + NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits); + handleArtificialTree(wrappedVarargs); + + Node lastArgument = + new ArrayCreationNode( + wrappedVarargs, + lastParamType, + /* dimensions= */ Collections.emptyList(), + initializers); + extendWithNode(lastArgument); + + convertedNodes.add(lastArgument); + } + } else { + for (int i = 0; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + if (i == assertMethodTuple.booleanParam) { + treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); + } + convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); + } + } + + return convertedNodes; + } + + /** + * Returns the AssertMethodTuple for {@code method}. If {@code method} is not an assert method, + * then {@link AssertMethodTuple#NONE} is returned. + * + * @param method a method element that might be an assert method + * @return the AssertMethodTuple for {@code method} + */ + protected AssertMethodTuple getAssertMethodTuple(ExecutableElement method) { + AnnotationMirror assertMethodAnno = + annotationProvider.getDeclAnnotation(method, AssertMethod.class); + if (assertMethodAnno == null) { + return AssertMethodTuple.NONE; + } + + // Dataflow does not require checker-qual.jar to be on the users classpath, so + // AnnotationUtils.getElementValue(...) cannot be used. + + int booleanParam = + AnnotationUtils.getElementValueNotOnClasspath( + assertMethodAnno, "parameter", Integer.class, 1) + - 1; + + TypeMirror exceptionType = + AnnotationUtils.getElementValueNotOnClasspath( + assertMethodAnno, + "value", + Type.ClassType.class, + (Type.ClassType) assertionErrorType); + boolean isAssertFalse = + AnnotationUtils.getElementValueNotOnClasspath( + assertMethodAnno, "isAssertFalse", Boolean.class, false); + return new AssertMethodTuple(booleanParam, exceptionType, isAssertFalse); + } + + /** Holds the elements of an {@link AssertMethod} annotation. */ + protected static class AssertMethodTuple { + + /** A tuple representing the lack of an {@link AssertMethodTuple}. */ + protected static final AssertMethodTuple NONE = new AssertMethodTuple(-1, null, false); + + /** + * 0-based index of the parameter of the expression that is tested by the assert method. (Or + * -1 if this isn't an assert method.) + */ + public final int booleanParam; + + /** The type of the exception thrown by the assert method. */ + public final TypeMirror exceptionType; + + /** Is this an assert false method? */ + public final boolean isAssertFalse; + + /** + * Creates an AssertMethodTuple. + * + * @param booleanParam 0-based index of the parameter of the expression that is tested by + * the assert method + * @param exceptionType the type of the exception thrown by the assert method + * @param isAssertFalse is this an assert false method + */ + public AssertMethodTuple( + int booleanParam, TypeMirror exceptionType, boolean isAssertFalse) { + this.booleanParam = booleanParam; + this.exceptionType = exceptionType; + this.isAssertFalse = isAssertFalse; + } + } + + /** + * Convert an operand of a conditional expression to the type of the whole expression. + * + * @param node a node occurring as the second or third operand of a conditional expression + * @param destType the type to promote the value to + * @return a Node with the value promoted to the destType, which may be the input node + */ + protected Node conditionalExprPromotion(Node node, TypeMirror destType) { + // For rules on converting operands of conditional expressions, + // JLS 15.25 + TypeMirror nodeType = node.getType(); + + // If the operand is already the same type as the whole + // expression, then do nothing. + if (types.isSameType(nodeType, destType)) { + return node; + } + + // If the operand is a primitive and the whole expression is + // boxed, then apply boxing. + if (TypesUtils.isPrimitive(nodeType) && TypesUtils.isBoxedPrimitive(destType)) { + return box(node); + } + + // If the operand is byte or Byte and the whole expression is + // short, then convert to short. + boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType); + TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType; + TypeMirror unboxedDestType = + TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType; + if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) { + if (unboxedNodeType.getKind() == TypeKind.BYTE + && destType.getKind() == TypeKind.SHORT) { + if (isBoxedPrimitive) { + node = unbox(node); + } + return widen(node, destType); + } + + // If the operand is Byte, Short or Character and the whole expression + // is the unboxed version of it, then apply unboxing. + TypeKind destKind = destType.getKind(); + if (destKind == TypeKind.BYTE + || destKind == TypeKind.CHAR + || destKind == TypeKind.SHORT) { + if (isBoxedPrimitive) { + return unbox(node); + } else if (nodeType.getKind() == TypeKind.INT) { + return narrow(node, destType); + } + } + + return binaryNumericPromotion(node, destType); + } + + // For the final case in JLS 15.25, apply boxing but not lub. + if (TypesUtils.isPrimitive(nodeType) + && (destType.getKind() == TypeKind.DECLARED + || destType.getKind() == TypeKind.UNION + || destType.getKind() == TypeKind.INTERSECTION)) { + return box(node); + } + + return node; + } + + /** + * Returns the label {@link Name} of the leaf in the argument path, or null if the leaf is not a + * labeled statement. + */ + protected @Nullable Name getLabel(TreePath path) { + if (path.getParentPath() != null) { + Tree parent = path.getParentPath().getLeaf(); + if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) { + return ((LabeledStatementTree) parent).getLabel(); + } + } + return null; + } + + /* --------------------------------------------------------- */ + /* Visitor Methods */ + /* --------------------------------------------------------- */ + + @Override + public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) { + return scan(tree.getUnderlyingType(), p); + } + + @Override + public Node visitAnnotation(AnnotationTree tree, Void p) { + throw new BugInCF("AnnotationTree is unexpected in AST to CFG translation"); + } + + @Override + public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { + + // see JLS 15.12.4 + + // First, compute the receiver, if any (15.12.4.1). + // Second, evaluate the actual arguments, left to right and possibly some arguments are + // stored into an array for varargs calls (15.12.4.2). + // Third, test the receiver, if any, for nullness (15.12.4.4). + // Fourth, convert the arguments to the type of the formal parameters (15.12.4.5). + // Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5). + ExecutableElement method = TreeUtils.elementFromUse(tree); + if (method == null) { + // The method wasn't found, e.g. because of a compilation error. + return null; + } + + ExpressionTree methodSelect = tree.getMethodSelect(); + assert TreeUtils.isMethodAccess(methodSelect) + : "Expected a method access, but got: " + methodSelect; + + List actualExprs = tree.getArguments(); + + // Look up method to invoke and possibly throw NullPointerException + Node receiver = getReceiver(methodSelect); + + MethodAccessNode target = new MethodAccessNode(methodSelect, method, receiver); + + if (ElementUtils.isStatic(method) || receiver instanceof ThisNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(target); + } else { + extendWithNodeWithException(target, nullPointerExceptionType); + } + + List arguments; + if (TreeUtils.isEnumSuperCall(tree)) { + // Don't convert arguments for enum super calls. The AST contains no actual arguments, + // while the method element expects two arguments, leading to an exception in + // convertCallArguments. + // Since no actual arguments are present in the AST that is being checked, it shouldn't + // cause any harm to omit the conversions. + // See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate. + arguments = Collections.emptyList(); + } else { + arguments = + convertCallArguments( + tree, method, TreeUtils.typeFromUse(tree), actualExprs, null); + } + + // TODO: lock the receiver for synchronized methods + + MethodInvocationNode node = + new MethodInvocationNode(tree, target, arguments, getCurrentPath()); + + ExtendedNode extendedNode = extendWithMethodInvocationNode(method, node); + + /* Check for the TerminatesExecution annotation. */ + boolean terminatesExecution = + annotationProvider.getDeclAnnotation(method, TerminatesExecution.class) != null; + if (terminatesExecution) { + extendedNode.setTerminatesExecution(true); + } + return node; + } + + /** + * Extends the CFG with a MethodInvocationNode, accounting for potential exceptions thrown by + * the invocation. + * + * @param method the invoked method + * @param node the invocation + * @return an ExtendedNode representing the invocation and its possible thrown exceptions + */ + private ExtendedNode extendWithMethodInvocationNode( + ExecutableElement method, MethodInvocationNode node) { + List thrownTypes = method.getThrownTypes(); + Set thrownSet = + new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size()); + // Add exceptions explicitly mentioned in the throws clause. + thrownSet.addAll(thrownTypes); + // Add types to account for unchecked exceptions + thrownSet.addAll(uncheckedExceptionTypes); + + return extendWithNodeWithExceptions(node, thrownSet); + } + + @Override + public Node visitAssert(AssertTree tree, Void p) { + + // see JLS 14.10 + + // If assertions are enabled, then we can just translate the assertion. + if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) { + translateAssertWithAssertionsEnabled(tree); + return null; + } + + // If assertions are disabled, then nothing is executed. + if (assumeAssertionsDisabled) { + return null; + } + + // Otherwise, we don't know if assertions are enabled, so we use a + // variable "ea" and case-split on it. One branch does execute the + // assertion, while the other assumes assertions are disabled. + VariableTree ea = getAssertionsEnabledVariable(); + + // all necessary labels + Label assertionEnabled = new Label(); + Label assertionDisabled = new Label(); + + extendWithNode(new LocalVariableNode(ea)); + extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled)); + + // 'then' branch (i.e. check the assertion) + addLabelForNextNode(assertionEnabled); + + translateAssertWithAssertionsEnabled(tree); + + // 'else' branch + addLabelForNextNode(assertionDisabled); + + return null; + } + + /** + * Should assertions be assumed to be executed for a given {@link AssertTree}? False by default. + */ + protected boolean assumeAssertionsEnabledFor(AssertTree tree) { + return false; + } + + /** The {@link VariableTree} that indicates whether assertions are enabled or not. */ + protected VariableTree ea = null; + + /** + * Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not. + */ + protected VariableTree getAssertionsEnabledVariable() { + if (ea == null) { + String name = uniqueName("assertionsEnabled"); + Element owner = TreePathUtil.findNearestEnclosingElement(getCurrentPath()); + ExpressionTree initializer = null; + ea = + treeBuilder.buildVariableDecl( + types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer); + handleArtificialTree(ea); + } + return ea; + } + + /** + * Translates an assertion statement to the correct CFG nodes. The translation assumes that + * assertions are enabled. + */ + protected void translateAssertWithAssertionsEnabled(AssertTree tree) { + + // all necessary labels + Label assertEnd = new Label(); + Label elseEntry = new Label(); + + // basic block for the condition + Node condition = unbox(scan(tree.getCondition(), null)); + ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry); + extendWithExtendedNode(cjump); + + // else branch + Node detail = null; + addLabelForNextNode(elseEntry); + if (tree.getDetail() != null) { + detail = scan(tree.getDetail(), null); + } + AssertionErrorNode assertNode = + new AssertionErrorNode(tree, condition, detail, assertionErrorType); + extendWithNode(assertNode); + NodeWithExceptionsHolder exNode = + extendWithNodeWithException( + new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType); + exNode.setTerminatesExecution(true); + + // then branch (nothing happens) + addLabelForNextNode(assertEnd); + } + + /** + * Translates a method marked as {@link AssertMethod} into CFG nodes corresponding to an {@code + * assert} statement. + * + * @param tree the method invocation tree for a method marked as {@link AssertMethod} + * @param assertMethodTuple the assert method tuple for the method + * @param condition the boolean expression node for the argument that the method tests + */ + protected void treatMethodAsAssert( + MethodInvocationTree tree, AssertMethodTuple assertMethodTuple, Node condition) { + // all necessary labels + Label thenLabel = new Label(); + Label elseLabel = new Label(); + ConditionalJump cjump = new ConditionalJump(thenLabel, elseLabel); + extendWithExtendedNode(cjump); + + addLabelForNextNode(assertMethodTuple.isAssertFalse ? thenLabel : elseLabel); + AssertionErrorNode assertNode = + new AssertionErrorNode(tree, condition, null, assertMethodTuple.exceptionType); + extendWithNode(assertNode); + NodeWithExceptionsHolder exNode = + extendWithNodeWithException( + new ThrowNode(null, assertNode, env.getTypeUtils()), + assertMethodTuple.exceptionType); + exNode.setTerminatesExecution(true); + + addLabelForNextNode(assertMethodTuple.isAssertFalse ? elseLabel : thenLabel); + } + + @Override + public Node visitAssignment(AssignmentTree tree, Void p) { + + // see JLS 15.26.1 + + AssignmentNode assignmentNode; + ExpressionTree variable = tree.getVariable(); + TypeMirror varType = TreeUtils.typeOf(variable); - buildSelector(); + // case 1: lhs is field access + if (TreeUtils.isFieldAccess(variable)) { + // visit receiver + Node receiver = getReceiver(variable); + + // visit expression + Node expression = scan(tree.getExpression(), p); + expression = assignConvert(expression, varType); + + // visit field access (throws null-pointer exception) + FieldAccessNode target = new FieldAccessNode(variable, receiver); + target.setLValue(); + + Element element = TreeUtils.elementFromUse(variable); + if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(target); + } else { + extendWithNodeWithException(target, nullPointerExceptionType); + } + + // add assignment node + assignmentNode = new AssignmentNode(tree, target, expression); + extendWithNode(assignmentNode); + } + + // case 2: lhs is not a field access + else { + Node target = scan(variable, p); + target.setLValue(); + + assignmentNode = translateAssignment(tree, target, tree.getExpression()); + } + + return assignmentNode; + } + + /** Translate an assignment. */ + protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) { + Node expression = scan(rhs, null); + return translateAssignment(tree, target, expression); + } + + /** Translate an assignment where the RHS has already been scanned. */ + protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) { + assert tree instanceof AssignmentTree || tree instanceof VariableTree; + target.setLValue(); + expression = assignConvert(expression, target.getType()); + AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression); + extendWithNode(assignmentNode); + return assignmentNode; + } + + /** + * Note 1: Requires {@code tree} to be a field or method access tree. + * + *

          Note 2: Visits the receiver and adds all necessary blocks to the CFG. + * + * @param tree the field or method access tree containing the receiver: one of + * MethodInvocationTree, AssignmentTree, or IdentifierTree + * @return the receiver of the field or method access + */ + private Node getReceiver(ExpressionTree tree) { + assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree); + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + // `tree` has an explicit receiver. + MemberSelectTree mtree = (MemberSelectTree) tree; + return scan(mtree.getExpression(), null); + } + + // Access through an implicit receiver + Element ele = TreeUtils.elementFromUse(tree); + TypeElement declClassElem = ElementUtils.enclosingTypeElement(ele); + TypeMirror declClassType = ElementUtils.getType(declClassElem); + + if (ElementUtils.isStatic(ele)) { + ClassNameNode node = new ClassNameNode(declClassType, declClassElem); + extendWithClassNameNode(node); + return node; + } + + // Access through an implicit `this` + TreePath enclClassPath = TreePathUtil.pathTillClass(getCurrentPath()); + ClassTree enclClassTree = (ClassTree) enclClassPath.getLeaf(); + TypeElement enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); + TypeMirror enclClassType = enclClassElem.asType(); + while (!TypesUtils.isErasedSubtype(enclClassType, declClassType, types)) { + enclClassPath = TreePathUtil.pathTillClass(enclClassPath.getParentPath()); + if (enclClassPath == null) { + enclClassType = declClassType; + break; + } + enclClassTree = (ClassTree) enclClassPath.getLeaf(); + enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); + enclClassType = enclClassElem.asType(); + } + Node node = new ImplicitThisNode(enclClassType); + extendWithNode(node); + return node; + } + + /** + * Map an operation with assignment to the corresponding operation without assignment. + * + * @param kind a Tree.Kind representing an operation with assignment + * @return the Tree.Kind for the same operation without assignment + */ + protected Tree.Kind withoutAssignment(Tree.Kind kind) { + switch (kind) { + case DIVIDE_ASSIGNMENT: + return Tree.Kind.DIVIDE; + case MULTIPLY_ASSIGNMENT: + return Tree.Kind.MULTIPLY; + case REMAINDER_ASSIGNMENT: + return Tree.Kind.REMAINDER; + case MINUS_ASSIGNMENT: + return Tree.Kind.MINUS; + case PLUS_ASSIGNMENT: + return Tree.Kind.PLUS; + case LEFT_SHIFT_ASSIGNMENT: + return Tree.Kind.LEFT_SHIFT; + case RIGHT_SHIFT_ASSIGNMENT: + return Tree.Kind.RIGHT_SHIFT; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + return Tree.Kind.UNSIGNED_RIGHT_SHIFT; + case AND_ASSIGNMENT: + return Tree.Kind.AND; + case OR_ASSIGNMENT: + return Tree.Kind.OR; + case XOR_ASSIGNMENT: + return Tree.Kind.XOR; + default: + return Tree.Kind.ERRONEOUS; + } + } + + @Override + public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + // According the JLS 15.26.2, E1 op= E2 is equivalent to + // E1 = (T) ((E1) op (E2)), where T is the type of E1, + // except that E1 is evaluated only once. + // + + Tree.Kind kind = tree.getKind(); + switch (kind) { + case DIVIDE_ASSIGNMENT: + case MULTIPLY_ASSIGNMENT: + case REMAINDER_ASSIGNMENT: + { + // see JLS 15.17 and 15.26.2 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror exprType = TreeUtils.typeOf(tree); + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + + BinaryTree operTree = + treeBuilder.buildBinary( + promotedType, + withoutAssignment(kind), + tree.getVariable(), + tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) { + operNode = new NumericalMultiplicationNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) { + if (TypesUtils.isIntegralPrimitive(exprType)) { + operNode = new IntegerDivisionNode(operTree, targetRHS, value); + + extendWithNodeWithException(operNode, arithmeticExceptionType); + } else { + operNode = new FloatingDivisionNode(operTree, targetRHS, value); + } + } else { + assert kind == Tree.Kind.REMAINDER_ASSIGNMENT; + if (TypesUtils.isIntegralPrimitive(exprType)) { + operNode = new IntegerRemainderNode(operTree, targetRHS, value); + + extendWithNodeWithException(operNode, arithmeticExceptionType); + } else { + operNode = new FloatingRemainderNode(operTree, targetRHS, value); + } + } + extendWithNode(operNode); + + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + } + + case MINUS_ASSIGNMENT: + case PLUS_ASSIGNMENT: + { + // see JLS 15.18 and 15.26.2 + + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); + + if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { + assert (kind == Tree.Kind.PLUS_ASSIGNMENT); + Node targetRHS = stringConversion(targetLHS); + value = stringConversion(value); + BinaryTree operTree = + treeBuilder.buildBinary( + leftType, + withoutAssignment(kind), + tree.getVariable(), + tree.getExpression()); + handleArtificialTree(operTree); + Node operNode = new StringConcatenateNode(operTree, targetRHS, value); + extendWithNode(operNode); + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, operNode); + extendWithNode(assignNode); + return assignNode; + } else { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + + BinaryTree operTree = + treeBuilder.buildBinary( + promotedType, + withoutAssignment(kind), + tree.getVariable(), + tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.PLUS_ASSIGNMENT) { + operNode = new NumericalAdditionNode(operTree, targetRHS, value); + } else { + assert kind == Tree.Kind.MINUS_ASSIGNMENT; + operNode = new NumericalSubtractionNode(operTree, targetRHS, value); + } + extendWithNode(operNode); + + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = + new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + // Map the compound assignment tree to an assignment node, which + // will have the correct type. + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + } + } + + case LEFT_SHIFT_ASSIGNMENT: + case RIGHT_SHIFT_ASSIGNMENT: + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + { + // see JLS 15.19 and 15.26.2 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + + Node targetRHS = unaryNumericPromotion(targetLHS); + value = unaryNumericPromotion(value); + + BinaryTree operTree = + treeBuilder.buildBinary( + leftType, + withoutAssignment(kind), + tree.getVariable(), + tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) { + operNode = new LeftShiftNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) { + operNode = new SignedRightShiftNode(operTree, targetRHS, value); + } else { + assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; + operNode = new UnsignedRightShiftNode(operTree, targetRHS, value); + } + extendWithNode(operNode); + + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + } + + case AND_ASSIGNMENT: + case OR_ASSIGNMENT: + case XOR_ASSIGNMENT: + // see JLS 15.22 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); + TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); + + Node targetRHS = null; + if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + } else if (TypesUtils.isBooleanType(leftType) + && TypesUtils.isBooleanType(rightType)) { + targetRHS = unbox(targetLHS); + value = unbox(value); + } else { + throw new BugInCF( + "Both arguments to logical operation must be numeric or boolean"); + } + + BinaryTree operTree = + treeBuilder.buildBinary( + leftType, + withoutAssignment(kind), + tree.getVariable(), + tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.AND_ASSIGNMENT) { + operNode = new BitwiseAndNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.OR_ASSIGNMENT) { + operNode = new BitwiseOrNode(operTree, targetRHS, value); + } else { + assert kind == Tree.Kind.XOR_ASSIGNMENT; + operNode = new BitwiseXorNode(operTree, targetRHS, value); + } + extendWithNode(operNode); + + TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); + TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + default: + throw new BugInCF("unexpected compound assignment type"); + } + } + + @Override + public Node visitBinary(BinaryTree tree, Void p) { + // Note that for binary operations it is important to perform any required promotion on the + // left operand before generating any Nodes for the right operand, because labels must be + // inserted AFTER ALL preceding Nodes and BEFORE ALL following Nodes. + Node r = null; + Tree leftTree = tree.getLeftOperand(); + Tree rightTree = tree.getRightOperand(); + + Tree.Kind kind = tree.getKind(); + switch (kind) { + case DIVIDE: + case MULTIPLY: + case REMAINDER: + { + // see JLS 15.17 + + TypeMirror exprType = TreeUtils.typeOf(tree); + TypeMirror leftType = TreeUtils.typeOf(leftTree); + TypeMirror rightType = TreeUtils.typeOf(rightTree); + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + if (kind == Tree.Kind.MULTIPLY) { + r = new NumericalMultiplicationNode(tree, left, right); + } else if (kind == Tree.Kind.DIVIDE) { + if (TypesUtils.isIntegralPrimitive(exprType)) { + r = new IntegerDivisionNode(tree, left, right); + + extendWithNodeWithException(r, arithmeticExceptionType); + } else { + r = new FloatingDivisionNode(tree, left, right); + } + } else { + assert kind == Tree.Kind.REMAINDER; + if (TypesUtils.isIntegralPrimitive(exprType)) { + r = new IntegerRemainderNode(tree, left, right); + + extendWithNodeWithException(r, arithmeticExceptionType); + } else { + r = new FloatingRemainderNode(tree, left, right); + } + } + break; + } + + case MINUS: + case PLUS: + { + // see JLS 15.18 + + // TypeMirror exprType = InternalUtils.typeOf(tree); + TypeMirror leftType = TreeUtils.typeOf(leftTree); + TypeMirror rightType = TreeUtils.typeOf(rightTree); + + if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { + assert (kind == Tree.Kind.PLUS); + Node left = stringConversion(scan(leftTree, p)); + Node right = stringConversion(scan(rightTree, p)); + r = new StringConcatenateNode(tree, left, right); + } else { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + // TODO: Decide whether to deal with floating-point value + // set conversion. + if (kind == Tree.Kind.PLUS) { + r = new NumericalAdditionNode(tree, left, right); + } else { + assert kind == Tree.Kind.MINUS; + r = new NumericalSubtractionNode(tree, left, right); + } + } + break; + } + + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + { + // see JLS 15.19 + + Node left = unaryNumericPromotion(scan(leftTree, p)); + Node right = unaryNumericPromotion(scan(rightTree, p)); + + if (kind == Tree.Kind.LEFT_SHIFT) { + r = new LeftShiftNode(tree, left, right); + } else if (kind == Tree.Kind.RIGHT_SHIFT) { + r = new SignedRightShiftNode(tree, left, right); + } else { + assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT; + r = new UnsignedRightShiftNode(tree, left, right); + } + break; + } + + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case LESS_THAN: + case LESS_THAN_EQUAL: + { + // see JLS 15.20.1 + TypeMirror leftType = TreeUtils.typeOf(leftTree); + if (TypesUtils.isBoxedPrimitive(leftType)) { + leftType = types.unboxedType(leftType); + } + + TypeMirror rightType = TreeUtils.typeOf(rightTree); + if (TypesUtils.isBoxedPrimitive(rightType)) { + rightType = types.unboxedType(rightType); + } + + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + Node node; + if (kind == Tree.Kind.GREATER_THAN) { + node = new GreaterThanNode(tree, left, right); + } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) { + node = new GreaterThanOrEqualNode(tree, left, right); + } else if (kind == Tree.Kind.LESS_THAN) { + node = new LessThanNode(tree, left, right); + } else { + assert kind == Tree.Kind.LESS_THAN_EQUAL; + node = new LessThanOrEqualNode(tree, left, right); + } + + extendWithNode(node); + + return node; + } + + case EQUAL_TO: + case NOT_EQUAL_TO: + { + // see JLS 15.21 + TreeInfo leftInfo = getTreeInfo(leftTree); + TreeInfo rightInfo = getTreeInfo(rightTree); + Node left = scan(leftTree, p); + Node right = scan(rightTree, p); + + if (leftInfo.isNumeric() + && rightInfo.isNumeric() + && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + // JLS 15.21.1 numerical equality + TypeMirror promotedType = + binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType()); + left = binaryNumericPromotion(left, promotedType); + right = binaryNumericPromotion(right, promotedType); + } else if (leftInfo.isBoolean() + && rightInfo.isBoolean() + && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + // JSL 15.21.2 boolean equality + left = unboxAsNeeded(left, leftInfo.isBoxed()); + right = unboxAsNeeded(right, rightInfo.isBoxed()); + } + + Node node; + if (kind == Tree.Kind.EQUAL_TO) { + node = new EqualToNode(tree, left, right); + } else { + assert kind == Tree.Kind.NOT_EQUAL_TO; + node = new NotEqualNode(tree, left, right); + } + extendWithNode(node); + + return node; + } + + case AND: + case OR: + case XOR: + { + // see JLS 15.22 + TypeMirror leftType = TreeUtils.typeOf(leftTree); + TypeMirror rightType = TreeUtils.typeOf(rightTree); + boolean isBooleanOp = + TypesUtils.isBooleanType(leftType) + && TypesUtils.isBooleanType(rightType); + + Node left; + Node right; + + if (isBooleanOp) { + left = unbox(scan(leftTree, p)); + right = unbox(scan(rightTree, p)); + } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + left = binaryNumericPromotion(scan(leftTree, p), promotedType); + right = binaryNumericPromotion(scan(rightTree, p), promotedType); + } else { + left = unbox(scan(leftTree, p)); + right = unbox(scan(rightTree, p)); + } + + Node node; + if (kind == Tree.Kind.AND) { + node = new BitwiseAndNode(tree, left, right); + } else if (kind == Tree.Kind.OR) { + node = new BitwiseOrNode(tree, left, right); + } else { + assert kind == Tree.Kind.XOR; + node = new BitwiseXorNode(tree, left, right); + } + + extendWithNode(node); + + return node; + } + + case CONDITIONAL_AND: + case CONDITIONAL_OR: + { + // see JLS 15.23 and 15.24 + + // all necessary labels + Label rightStartLabel = new Label(); + Label shortCircuitLabel = new Label(); + + // left-hand side + Node left = scan(leftTree, p); + + ConditionalJump cjump; + if (kind == Tree.Kind.CONDITIONAL_AND) { + cjump = new ConditionalJump(rightStartLabel, shortCircuitLabel); + cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE); + } else { + cjump = new ConditionalJump(shortCircuitLabel, rightStartLabel); + cjump.setTrueFlowRule(FlowRule.THEN_TO_THEN); + } + extendWithExtendedNode(cjump); + + // right-hand side + addLabelForNextNode(rightStartLabel); + Node right = scan(rightTree, p); + + // conditional expression itself + addLabelForNextNode(shortCircuitLabel); + Node node; + if (kind == Tree.Kind.CONDITIONAL_AND) { + node = new ConditionalAndNode(tree, left, right); + } else { + node = new ConditionalOrNode(tree, left, right); + } + extendWithNode(node); + return node; + } + default: + throw new BugInCF("unexpected binary tree: " + kind); + } + assert r != null : "unexpected binary tree"; + extendWithNode(r); + return r; + } + + @Override + public Node visitBlock(BlockTree tree, Void p) { + for (StatementTree n : tree.getStatements()) { + scan(n, null); + } + return null; + } + + @Override + public Node visitBreak(BreakTree tree, Void p) { + Name label = tree.getLabel(); + if (label == null) { + assert breakTargetLC != null : "no target for break statement"; + + extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); + } else { + assert breakLabels.containsKey(label); + + extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label))); + } + + return null; + } + + // This visits a switch statement. + // Switch expressions are visited by visitSwitchExpression17. + @Override + public Node visitSwitch(SwitchTree tree, Void p) { + SwitchBuilder builder = new SwitchBuilder(tree); + builder.build(); + return null; + } + + /** + * Helper class for handling switch statements and switch expressions, including all their + * substatements such as case labels. + */ + private class SwitchBuilder { + + /** + * The tree for the switch statement or switch expression. Its type may be {@link + * SwitchTree} (for a switch statement) or {@code SwitchExpressionTree}. + */ + private final Tree switchTree; + + /** The case trees of {@code switchTree} */ + private final List caseTrees; + + /** + * The Tree for the selector expression. + * + *

          +         *   switch ( selector expression ) { ... }
          +         * 
          + */ + private final ExpressionTree selectorExprTree; + + /** The labels for the case bodies. */ + private final Label[] caseBodyLabels; + + /** + * The Node for the assignment of the switch selector expression to a synthetic local + * variable. + */ + private AssignmentNode selectorExprAssignment; + + /** + * If {@link #switchTree} is a switch expression, then this is a result variable: the + * synthetic variable that all results of {@code #switchTree} are assigned to. Otherwise, + * this is null. + */ + private @Nullable VariableTree switchExprVarTree; + + /** + * Construct a SwitchBuilder. + * + * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} + */ + private SwitchBuilder(Tree switchTree) { + this.switchTree = switchTree; + if (TreeUtils.isSwitchStatement(switchTree)) { + SwitchTree switchStatementTree = (SwitchTree) switchTree; + this.caseTrees = switchStatementTree.getCases(); + this.selectorExprTree = switchStatementTree.getExpression(); + } else { + this.caseTrees = SwitchExpressionUtils.getCases(switchTree); + this.selectorExprTree = SwitchExpressionUtils.getExpression(switchTree); + } + // "+ 1" for the default case. If the switch has an explicit default case, then + // the last element of the array is never used. + this.caseBodyLabels = new Label[caseTrees.size() + 1]; + } + + /** + * Build up the CFG for the switchTree. + * + * @return if the switch is a switch expression, then a {@link SwitchExpressionNode}; + * otherwise, null + */ + public @Nullable SwitchExpressionNode build() { + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(new Label()); + int numCases = caseTrees.size(); + + for (int i = 0; i < numCases; ++i) { + caseBodyLabels[i] = new Label(); + } + caseBodyLabels[numCases] = breakTargetLC.peekLabel(); + + buildSelector(); + + buildSwitchExpressionVar(); + + if (TreeUtils.isSwitchStatement(switchTree)) { + // It's a switch statement, not a switch expression. + extendWithNode( + new MarkerNode( + switchTree, + "start of switch statement #" + TreeUtils.treeUids.get(switchTree), + env.getTypeUtils())); + } + + // JLS 14.11.2 + // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 + // states "For compatibility reasons, switch statements that are not enhanced switch + // statements are not required to be exhaustive". + // Switch expressions and enhanced switch statements are exhaustive. + boolean switchExprOrEnhanced = + !TreeUtils.isSwitchStatement(switchTree) + || TreeUtils.isEnhancedSwitchStatement((SwitchTree) switchTree); + // Build CFG for the cases. + int defaultIndex = -1; + for (int i = 0; i < numCases; ++i) { + CaseTree caseTree = caseTrees.get(i); + if (CaseUtils.isDefaultCaseTree(caseTree)) { + // Per the Java Language Specification, the checks of all cases must happen + // before the default case, no matter where `default:` is written. Therefore, + // build the default case last. + defaultIndex = i; + } else if (i == numCases - 1 && defaultIndex == -1) { + // This is the last case, and there is no default case. + // Switch expressions and enhanced switch statements are exhaustive. + buildCase(caseTree, i, switchExprOrEnhanced); + } else { + buildCase(caseTree, i, false); + } + } + + if (defaultIndex != -1) { + // The checks of all cases must happen before the default case, therefore we build + // the default case last. + // Fallthrough is still handled correctly with the caseBodyLabels. + buildCase(caseTrees.get(defaultIndex), defaultIndex, false); + } + + addLabelForNextNode(breakTargetLC.peekLabel()); + breakTargetLC = oldBreakTargetLC; + if (TreeUtils.isSwitchStatement(switchTree)) { + // It's a switch statement, not a switch expression. + extendWithNode( + new MarkerNode( + switchTree, + "end of switch statement #" + TreeUtils.treeUids.get(switchTree), + env.getTypeUtils())); + } + + if (!TreeUtils.isSwitchStatement(switchTree)) { + // It's a switch expression, not a switch statement. + IdentifierTree switchExprVarUseTree = + treeBuilder.buildVariableUse(switchExprVarTree); + handleArtificialTree(switchExprVarUseTree); + + LocalVariableNode switchExprVarUseNode = + new LocalVariableNode(switchExprVarUseTree); + switchExprVarUseNode.setInSource(false); + extendWithNode(switchExprVarUseNode); + SwitchExpressionNode switchExpressionNode = + new SwitchExpressionNode( + TreeUtils.typeOf(switchTree), switchTree, switchExprVarUseNode); + extendWithNode(switchExpressionNode); + return switchExpressionNode; + } else { + return null; + } + } + + /** + * Builds the CFG for the selector expression. It also creates a synthetic variable and + * assigns the selector expression to the variable. This assignment node is stored in {@link + * #selectorExprAssignment}. It can later be used to refine the selector expression in case + * bodies. + */ + private void buildSelector() { + // Create a synthetic variable to which the switch selector expression will be assigned + TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree); + VariableTree selectorVarTree = + treeBuilder.buildVariableDecl( + selectorExprType, + uniqueName("switch"), + TreePathUtil.findNearestEnclosingElement(getCurrentPath()), + null); + handleArtificialTree(selectorVarTree); + + VariableDeclarationNode selectorVarNode = new VariableDeclarationNode(selectorVarTree); + selectorVarNode.setInSource(false); + extendWithNode(selectorVarNode); + + IdentifierTree selectorVarUseTree = treeBuilder.buildVariableUse(selectorVarTree); + handleArtificialTree(selectorVarUseTree); + + LocalVariableNode selectorVarUseNode = new LocalVariableNode(selectorVarUseTree); + selectorVarUseNode.setInSource(false); + extendWithNode(selectorVarUseNode); + + Node selectorExprNode = unbox(scan(selectorExprTree, null)); + + AssignmentTree assign = + treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree); + handleArtificialTree(assign); + + selectorExprAssignment = + new AssignmentNode(assign, selectorVarUseNode, selectorExprNode); + selectorExprAssignment.setInSource(false); + extendWithNode(selectorExprAssignment); + } + + /** + * If {@link #switchTree} is a switch expression tree, this method creates a synthetic + * variable whose value is the value of the switch expression. + */ + private void buildSwitchExpressionVar() { + if (TreeUtils.isSwitchStatement(switchTree)) { + // A switch statement does not have a value, so do nothing. + return; + } + TypeMirror switchExprType = TreeUtils.typeOf(switchTree); + switchExprVarTree = + treeBuilder.buildVariableDecl( + switchExprType, + uniqueName("switchExpr"), + TreePathUtil.findNearestEnclosingElement(getCurrentPath()), + null); + handleArtificialTree(switchExprVarTree); + + VariableDeclarationNode switchExprVarNode = + new VariableDeclarationNode(switchExprVarTree); + switchExprVarNode.setInSource(false); + extendWithNode(switchExprVarNode); + } + + /** + * Build the CFG for the given case tree. + * + * @param caseTree a case tree whose CFG to build + * @param index the index of the case tree in {@link #caseBodyLabels} + * @param isLastCaseOfExhaustive true if this is the last case of an exhaustive switch + * statement, with no fallthrough to it. In other words, no test of the labels is + * necessary. + */ + private void buildCase(CaseTree caseTree, int index, boolean isLastCaseOfExhaustive) { + boolean isDefaultCase = CaseUtils.isDefaultCaseTree(caseTree); + // If true, no test of labels is necessary. + // Unfortunately, if isLastCaseOfExhaustive==TRUE, no flow-sensitive refinement occurs + // within the body of the CaseNode. In the future, that can be performed, but it + // requires addition of InfeasibleExitBlock, a new SpecialBlock in the CFG. + boolean isTerminalCase = isDefaultCase || isLastCaseOfExhaustive; + + Label thisBodyLabel = caseBodyLabels[index]; + Label nextBodyLabel = caseBodyLabels[index + 1]; + // `nextCaseLabel` is not used if isTerminalCase==FALSE. + Label nextCaseLabel = new Label(); + + // Handle the case expressions + if (!isTerminalCase) { + // A case expression exists, and it needs to be tested. + ArrayList exprs = new ArrayList<>(); + for (Tree exprTree : CaseUtils.getLabels(caseTree)) { + exprs.add(scan(exprTree, null)); + } + + ExpressionTree guardTree = CaseUtils.getGuard(caseTree); + Node guard = (guardTree == null) ? null : scan(guardTree, null); + + CaseNode test = + new CaseNode( + caseTree, selectorExprAssignment, exprs, guard, env.getTypeUtils()); + extendWithNode(test); + extendWithExtendedNode(new ConditionalJump(thisBodyLabel, nextCaseLabel)); + } + + // Handle the case body + addLabelForNextNode(thisBodyLabel); + if (caseTree.getStatements() != null) { + // This is a switch labeled statement group. + // A "switch labeled statement group" is a "case L:" label along with its code. + // The code either ends with a "yield" statement, or it falls through. + for (StatementTree stmt : caseTree.getStatements()) { + scan(stmt, null); + } + // Handle possible fallthrough by adding jump to next body. + if (!isTerminalCase) { + extendWithExtendedNode(new UnconditionalJump(nextBodyLabel)); + } + } else { + // This is either the default case or a switch labeled rule (which appears in a + // switch expression). + // A "switch labeled rule" is a "case L ->" label along with its code. + Tree bodyTree = CaseUtils.getBody(caseTree); + if (!TreeUtils.isSwitchStatement(switchTree) + && bodyTree instanceof ExpressionTree) { + buildSwitchExpressionResult((ExpressionTree) bodyTree); + } else { + scan(bodyTree, null); + // Switch rules never fall through so add jump to the break target. + assert breakTargetLC != null : "no target for case statement"; + extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); + } + } - buildSwitchExpressionVar(); + if (!isTerminalCase) { + addLabelForNextNode(nextCaseLabel); + } + } - if (TreeUtils.isSwitchStatement(switchTree)) { - // It's a switch statement, not a switch expression. - extendWithNode( - new MarkerNode( - switchTree, - "start of switch statement #" + TreeUtils.treeUids.get(switchTree), - env.getTypeUtils())); - } - - // JLS 14.11.2 - // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 - // states "For compatibility reasons, switch statements that are not enhanced switch - // statements are not required to be exhaustive". - // Switch expressions and enhanced switch statements are exhaustive. - boolean switchExprOrEnhanced = - !TreeUtils.isSwitchStatement(switchTree) - || TreeUtils.isEnhancedSwitchStatement((SwitchTree) switchTree); - // Build CFG for the cases. - int defaultIndex = -1; - for (int i = 0; i < numCases; ++i) { - CaseTree caseTree = caseTrees.get(i); - if (CaseUtils.isDefaultCaseTree(caseTree)) { - // Per the Java Language Specification, the checks of all cases must happen - // before the default case, no matter where `default:` is written. Therefore, - // build the default case last. - defaultIndex = i; - } else if (i == numCases - 1 && defaultIndex == -1) { - // This is the last case, and there is no default case. - // Switch expressions and enhanced switch statements are exhaustive. - buildCase(caseTree, i, switchExprOrEnhanced); - } else { - buildCase(caseTree, i, false); + /** + * Does the following for the result expression of a switch expression, {@code + * resultExpression}: + * + *
            + *
          1. Builds the CFG for the switch expression result. + *
          2. Creates an assignment node for the assignment of {@code resultExpression} to {@code + * switchExprVarTree}. + *
          3. Adds an unconditional jump to {@link #breakTargetLC} (the end of the switch + * expression). + *
          + * + * @param resultExpression the result of a switch expression; either from a yield or an + * expression in a case rule + */ + /*package-private*/ void buildSwitchExpressionResult(ExpressionTree resultExpression) { + IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); + handleArtificialTree(switchExprVarUseTree); + + LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); + switchExprVarUseNode.setInSource(false); + extendWithNode(switchExprVarUseNode); + + Node resultExprNode = scan(resultExpression, null); + + AssignmentTree assign = + treeBuilder.buildAssignment(switchExprVarUseTree, resultExpression); + handleArtificialTree(assign); + + AssignmentNode assignmentNode = + new AssignmentNode(assign, switchExprVarUseNode, resultExprNode); + assignmentNode.setInSource(false); + extendWithNode(assignmentNode); + // Switch rules never fall through so add jump to the break target. + assert breakTargetLC != null : "no target for case statement"; + extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); } - } - - if (defaultIndex != -1) { - // The checks of all cases must happen before the default case, therefore we build - // the default case last. - // Fallthrough is still handled correctly with the caseBodyLabels. - buildCase(caseTrees.get(defaultIndex), defaultIndex, false); - } - - addLabelForNextNode(breakTargetLC.peekLabel()); - breakTargetLC = oldBreakTargetLC; - if (TreeUtils.isSwitchStatement(switchTree)) { - // It's a switch statement, not a switch expression. - extendWithNode( - new MarkerNode( - switchTree, - "end of switch statement #" + TreeUtils.treeUids.get(switchTree), - env.getTypeUtils())); - } - - if (!TreeUtils.isSwitchStatement(switchTree)) { - // It's a switch expression, not a switch statement. - IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); - handleArtificialTree(switchExprVarUseTree); - - LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); - switchExprVarUseNode.setInSource(false); - extendWithNode(switchExprVarUseNode); - SwitchExpressionNode switchExpressionNode = - new SwitchExpressionNode( - TreeUtils.typeOf(switchTree), switchTree, switchExprVarUseNode); - extendWithNode(switchExpressionNode); - return switchExpressionNode; - } else { + } + + @Override + public Node visitCase(CaseTree tree, Void p) { + // This assertion assumes that `case` appears only within a switch statement, + throw new AssertionError("case visitor is implemented in SwitchBuilder"); + } + + @Override + public Node visitCatch(CatchTree tree, Void p) { + scan(tree.getParameter(), p); + scan(tree.getBlock(), p); return null; - } } - /** - * Builds the CFG for the selector expression. It also creates a synthetic variable and assigns - * the selector expression to the variable. This assignment node is stored in {@link - * #selectorExprAssignment}. It can later be used to refine the selector expression in case - * bodies. - */ - private void buildSelector() { - // Create a synthetic variable to which the switch selector expression will be assigned - TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree); - VariableTree selectorVarTree = - treeBuilder.buildVariableDecl( - selectorExprType, - uniqueName("switch"), - TreePathUtil.findNearestEnclosingElement(getCurrentPath()), - null); - handleArtificialTree(selectorVarTree); - - VariableDeclarationNode selectorVarNode = new VariableDeclarationNode(selectorVarTree); - selectorVarNode.setInSource(false); - extendWithNode(selectorVarNode); - - IdentifierTree selectorVarUseTree = treeBuilder.buildVariableUse(selectorVarTree); - handleArtificialTree(selectorVarUseTree); - - LocalVariableNode selectorVarUseNode = new LocalVariableNode(selectorVarUseTree); - selectorVarUseNode.setInSource(false); - extendWithNode(selectorVarUseNode); - - Node selectorExprNode = unbox(scan(selectorExprTree, null)); - - AssignmentTree assign = treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree); - handleArtificialTree(assign); - - selectorExprAssignment = new AssignmentNode(assign, selectorVarUseNode, selectorExprNode); - selectorExprAssignment.setInSource(false); - extendWithNode(selectorExprAssignment); + // This is not invoked for top-level classes. Maybe it is, for classes defined within method + // bodies. + @Override + public Node visitClass(ClassTree tree, Void p) { + declaredClasses.add(tree); + Node classbody = new ClassDeclarationNode(tree); + extendWithNode(classbody); + return classbody; + } + + @Override + public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + // see JLS 15.25 + TypeMirror exprType = TreeUtils.typeOf(tree); + + Label trueStart = new Label(); + Label falseStart = new Label(); + Label merge = new Label(); + + // create a synthetic variable for the value of the conditional expression + VariableTree condExprVarTree = + treeBuilder.buildVariableDecl( + exprType, + uniqueName("condExpr"), + TreePathUtil.findNearestEnclosingElement(getCurrentPath()), + null); + handleArtificialTree(condExprVarTree); + VariableDeclarationNode condExprVarNode = new VariableDeclarationNode(condExprVarTree); + condExprVarNode.setInSource(false); + extendWithNode(condExprVarNode); + + Node condition = unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(trueStart, falseStart); + extendWithExtendedNode(cjump); + + addLabelForNextNode(trueStart); + ExpressionTree trueExprTree = tree.getTrueExpression(); + Node trueExprNode = scan(trueExprTree, p); + trueExprNode = conditionalExprPromotion(trueExprNode, exprType); + extendWithAssignmentForConditionalExpr(condExprVarTree, trueExprTree, trueExprNode); + extendWithExtendedNode(new UnconditionalJump(merge)); + + addLabelForNextNode(falseStart); + ExpressionTree falseExprTree = tree.getFalseExpression(); + Node falseExprNode = scan(falseExprTree, p); + falseExprNode = conditionalExprPromotion(falseExprNode, exprType); + extendWithAssignmentForConditionalExpr(condExprVarTree, falseExprTree, falseExprNode); + extendWithExtendedNode(new UnconditionalJump(merge)); + + addLabelForNextNode(merge); + IPair treeAndLocalVarNode = + buildVarUseNode(condExprVarTree); + Node node = + new TernaryExpressionNode( + tree, condition, trueExprNode, falseExprNode, treeAndLocalVarNode.second); + extendWithNode(node); + + return node; } /** - * If {@link #switchTree} is a switch expression tree, this method creates a synthetic variable - * whose value is the value of the switch expression. + * Extend the CFG with an assignment for either the true or false case of a conditional + * expression, assigning the value of the expression for the case to the synthetic variable for + * the conditional expression + * + * @param condExprVarTree tree for synthetic variable for conditional expression + * @param caseExprTree expression tree for the case + * @param caseExprNode node for the case */ - private void buildSwitchExpressionVar() { - if (TreeUtils.isSwitchStatement(switchTree)) { - // A switch statement does not have a value, so do nothing. - return; - } - TypeMirror switchExprType = TreeUtils.typeOf(switchTree); - switchExprVarTree = - treeBuilder.buildVariableDecl( - switchExprType, - uniqueName("switchExpr"), - TreePathUtil.findNearestEnclosingElement(getCurrentPath()), - null); - handleArtificialTree(switchExprVarTree); - - VariableDeclarationNode switchExprVarNode = new VariableDeclarationNode(switchExprVarTree); - switchExprVarNode.setInSource(false); - extendWithNode(switchExprVarNode); + private void extendWithAssignmentForConditionalExpr( + VariableTree condExprVarTree, ExpressionTree caseExprTree, Node caseExprNode) { + IPair treeAndLocalVarNode = + buildVarUseNode(condExprVarTree); + + AssignmentTree assign = + treeBuilder.buildAssignment(treeAndLocalVarNode.first, caseExprTree); + handleArtificialTree(assign); + + // Build a "synthetic" assignment node, allowing special handling in transfer functions + AssignmentNode assignmentNode = + new AssignmentNode(assign, treeAndLocalVarNode.second, caseExprNode, true); + assignmentNode.setInSource(false); + extendWithNode(assignmentNode); } /** - * Build the CFG for the given case tree. + * Build a pair of {@link IdentifierTree} and {@link LocalVariableNode} to represent a use of + * some variable. Does not add the node to the CFG. * - * @param caseTree a case tree whose CFG to build - * @param index the index of the case tree in {@link #caseBodyLabels} - * @param isLastCaseOfExhaustive true if this is the last case of an exhaustive switch - * statement, with no fallthrough to it. In other words, no test of the labels is necessary. + * @param varTree tree for the variable + * @return a pair whose first element is the synthetic {@link IdentifierTree} for the use, and + * whose second element is the {@link LocalVariableNode} representing the use */ - private void buildCase(CaseTree caseTree, int index, boolean isLastCaseOfExhaustive) { - boolean isDefaultCase = CaseUtils.isDefaultCaseTree(caseTree); - // If true, no test of labels is necessary. - // Unfortunately, if isLastCaseOfExhaustive==TRUE, no flow-sensitive refinement occurs - // within the body of the CaseNode. In the future, that can be performed, but it - // requires addition of InfeasibleExitBlock, a new SpecialBlock in the CFG. - boolean isTerminalCase = isDefaultCase || isLastCaseOfExhaustive; - - Label thisBodyLabel = caseBodyLabels[index]; - Label nextBodyLabel = caseBodyLabels[index + 1]; - // `nextCaseLabel` is not used if isTerminalCase==FALSE. - Label nextCaseLabel = new Label(); - - // Handle the case expressions - if (!isTerminalCase) { - // A case expression exists, and it needs to be tested. - ArrayList exprs = new ArrayList<>(); - for (Tree exprTree : CaseUtils.getLabels(caseTree)) { - exprs.add(scan(exprTree, null)); - } + private IPair buildVarUseNode(VariableTree varTree) { + IdentifierTree condExprVarUseTree = treeBuilder.buildVariableUse(varTree); + handleArtificialTree(condExprVarUseTree); + LocalVariableNode condExprVarUseNode = new LocalVariableNode(condExprVarUseTree); + condExprVarUseNode.setInSource(false); + // Do not actually add the node to the CFG. + return IPair.of(condExprVarUseTree, condExprVarUseNode); + } - ExpressionTree guardTree = CaseUtils.getGuard(caseTree); - Node guard = (guardTree == null) ? null : scan(guardTree, null); - - CaseNode test = - new CaseNode(caseTree, selectorExprAssignment, exprs, guard, env.getTypeUtils()); - extendWithNode(test); - extendWithExtendedNode(new ConditionalJump(thisBodyLabel, nextCaseLabel)); - } - - // Handle the case body - addLabelForNextNode(thisBodyLabel); - if (caseTree.getStatements() != null) { - // This is a switch labeled statement group. - // A "switch labeled statement group" is a "case L:" label along with its code. - // The code either ends with a "yield" statement, or it falls through. - for (StatementTree stmt : caseTree.getStatements()) { - scan(stmt, null); - } - // Handle possible fallthrough by adding jump to next body. - if (!isTerminalCase) { - extendWithExtendedNode(new UnconditionalJump(nextBodyLabel)); + @Override + public Node visitContinue(ContinueTree tree, Void p) { + Name label = tree.getLabel(); + if (label == null) { + assert continueTargetLC != null : "no target for continue statement"; + + extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); + } else { + assert continueLabels.containsKey(label); + + extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label))); } - } else { - // This is either the default case or a switch labeled rule (which appears in a - // switch expression). - // A "switch labeled rule" is a "case L ->" label along with its code. - Tree bodyTree = CaseUtils.getBody(caseTree); - if (!TreeUtils.isSwitchStatement(switchTree) && bodyTree instanceof ExpressionTree) { - buildSwitchExpressionResult((ExpressionTree) bodyTree); + + return null; + } + + @Override + public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + Name parentLabel = getLabel(getCurrentPath()); + + Label loopEntry = new Label(); + Label loopExit = new Label(); + + // If the loop is a labeled statement, then its continue target is identical for continues + // with no label and continues with the loop's label. + Label conditionStart; + if (parentLabel != null) { + conditionStart = continueLabels.get(parentLabel); } else { - scan(bodyTree, null); - // Switch rules never fall through so add jump to the break target. - assert breakTargetLC != null : "no target for case statement"; - extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); + conditionStart = new Label(); } - } - if (!isTerminalCase) { - addLabelForNextNode(nextCaseLabel); - } + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(loopExit); + + LabelCell oldContinueTargetLC = continueTargetLC; + continueTargetLC = new LabelCell(conditionStart); + + // Loop body + addLabelForNextNode(loopEntry); + assert tree.getStatement() != null; + scan(tree.getStatement(), p); + + // Condition + addLabelForNextNode(conditionStart); + assert tree.getCondition() != null; + unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); + extendWithExtendedNode(cjump); + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetLC = oldBreakTargetLC; + continueTargetLC = oldContinueTargetLC; + + return null; } - /** - * Does the following for the result expression of a switch expression, {@code - * resultExpression}: - * - *
            - *
          1. Builds the CFG for the switch expression result. - *
          2. Creates an assignment node for the assignment of {@code resultExpression} to {@code - * switchExprVarTree}. - *
          3. Adds an unconditional jump to {@link #breakTargetLC} (the end of the switch - * expression). - *
          - * - * @param resultExpression the result of a switch expression; either from a yield or an - * expression in a case rule - */ - /*package-private*/ void buildSwitchExpressionResult(ExpressionTree resultExpression) { - IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); - handleArtificialTree(switchExprVarUseTree); - - LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); - switchExprVarUseNode.setInSource(false); - extendWithNode(switchExprVarUseNode); - - Node resultExprNode = scan(resultExpression, null); - - AssignmentTree assign = treeBuilder.buildAssignment(switchExprVarUseTree, resultExpression); - handleArtificialTree(assign); - - AssignmentNode assignmentNode = - new AssignmentNode(assign, switchExprVarUseNode, resultExprNode); - assignmentNode.setInSource(false); - extendWithNode(assignmentNode); - // Switch rules never fall through so add jump to the break target. - assert breakTargetLC != null : "no target for case statement"; - extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); + @Override + public Node visitErroneous(ErroneousTree tree, Void p) { + throw new BugInCF("ErroneousTree is unexpected in AST to CFG translation: " + tree); } - } - - @Override - public Node visitCase(CaseTree tree, Void p) { - // This assertion assumes that `case` appears only within a switch statement, - throw new AssertionError("case visitor is implemented in SwitchBuilder"); - } - - @Override - public Node visitCatch(CatchTree tree, Void p) { - scan(tree.getParameter(), p); - scan(tree.getBlock(), p); - return null; - } - - // This is not invoked for top-level classes. Maybe it is, for classes defined within method - // bodies. - @Override - public Node visitClass(ClassTree tree, Void p) { - declaredClasses.add(tree); - Node classbody = new ClassDeclarationNode(tree); - extendWithNode(classbody); - return classbody; - } - - @Override - public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - // see JLS 15.25 - TypeMirror exprType = TreeUtils.typeOf(tree); - - Label trueStart = new Label(); - Label falseStart = new Label(); - Label merge = new Label(); - - // create a synthetic variable for the value of the conditional expression - VariableTree condExprVarTree = - treeBuilder.buildVariableDecl( - exprType, - uniqueName("condExpr"), - TreePathUtil.findNearestEnclosingElement(getCurrentPath()), - null); - handleArtificialTree(condExprVarTree); - VariableDeclarationNode condExprVarNode = new VariableDeclarationNode(condExprVarTree); - condExprVarNode.setInSource(false); - extendWithNode(condExprVarNode); - - Node condition = unbox(scan(tree.getCondition(), p)); - ConditionalJump cjump = new ConditionalJump(trueStart, falseStart); - extendWithExtendedNode(cjump); - - addLabelForNextNode(trueStart); - ExpressionTree trueExprTree = tree.getTrueExpression(); - Node trueExprNode = scan(trueExprTree, p); - trueExprNode = conditionalExprPromotion(trueExprNode, exprType); - extendWithAssignmentForConditionalExpr(condExprVarTree, trueExprTree, trueExprNode); - extendWithExtendedNode(new UnconditionalJump(merge)); - - addLabelForNextNode(falseStart); - ExpressionTree falseExprTree = tree.getFalseExpression(); - Node falseExprNode = scan(falseExprTree, p); - falseExprNode = conditionalExprPromotion(falseExprNode, exprType); - extendWithAssignmentForConditionalExpr(condExprVarTree, falseExprTree, falseExprNode); - extendWithExtendedNode(new UnconditionalJump(merge)); - - addLabelForNextNode(merge); - IPair treeAndLocalVarNode = buildVarUseNode(condExprVarTree); - Node node = - new TernaryExpressionNode( - tree, condition, trueExprNode, falseExprNode, treeAndLocalVarNode.second); - extendWithNode(node); - - return node; - } - - /** - * Extend the CFG with an assignment for either the true or false case of a conditional - * expression, assigning the value of the expression for the case to the synthetic variable for - * the conditional expression - * - * @param condExprVarTree tree for synthetic variable for conditional expression - * @param caseExprTree expression tree for the case - * @param caseExprNode node for the case - */ - private void extendWithAssignmentForConditionalExpr( - VariableTree condExprVarTree, ExpressionTree caseExprTree, Node caseExprNode) { - IPair treeAndLocalVarNode = buildVarUseNode(condExprVarTree); - - AssignmentTree assign = treeBuilder.buildAssignment(treeAndLocalVarNode.first, caseExprTree); - handleArtificialTree(assign); - - // Build a "synthetic" assignment node, allowing special handling in transfer functions - AssignmentNode assignmentNode = - new AssignmentNode(assign, treeAndLocalVarNode.second, caseExprNode, true); - assignmentNode.setInSource(false); - extendWithNode(assignmentNode); - } - - /** - * Build a pair of {@link IdentifierTree} and {@link LocalVariableNode} to represent a use of some - * variable. Does not add the node to the CFG. - * - * @param varTree tree for the variable - * @return a pair whose first element is the synthetic {@link IdentifierTree} for the use, and - * whose second element is the {@link LocalVariableNode} representing the use - */ - private IPair buildVarUseNode(VariableTree varTree) { - IdentifierTree condExprVarUseTree = treeBuilder.buildVariableUse(varTree); - handleArtificialTree(condExprVarUseTree); - LocalVariableNode condExprVarUseNode = new LocalVariableNode(condExprVarUseTree); - condExprVarUseNode.setInSource(false); - // Do not actually add the node to the CFG. - return IPair.of(condExprVarUseTree, condExprVarUseNode); - } - - @Override - public Node visitContinue(ContinueTree tree, Void p) { - Name label = tree.getLabel(); - if (label == null) { - assert continueTargetLC != null : "no target for continue statement"; - - extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); - } else { - assert continueLabels.containsKey(label); - - extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label))); + + @Override + public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) { + ExpressionTree exprTree = tree.getExpression(); + scan(exprTree, p); + extendWithNode(new ExpressionStatementNode(exprTree)); + return null; } - return null; - } + @Override + public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + // see JLS 14.14.2 + Name parentLabel = getLabel(getCurrentPath()); + + Label conditionStart = new Label(); + Label loopEntry = new Label(); + Label loopExit = new Label(); - @Override - public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - Name parentLabel = getLabel(getCurrentPath()); + // If the loop is a labeled statement, then its continue target is identical for continues + // with no label and continues with the loop's label. + Label updateStart; + if (parentLabel != null) { + updateStart = continueLabels.get(parentLabel); + } else { + updateStart = new Label(); + } - Label loopEntry = new Label(); - Label loopExit = new Label(); + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(loopExit); - // If the loop is a labeled statement, then its continue target is identical for continues - // with no label and continues with the loop's label. - Label conditionStart; - if (parentLabel != null) { - conditionStart = continueLabels.get(parentLabel); - } else { - conditionStart = new Label(); - } + LabelCell oldContinueTargetLC = continueTargetLC; + continueTargetLC = new LabelCell(updateStart); - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(loopExit); - - LabelCell oldContinueTargetLC = continueTargetLC; - continueTargetLC = new LabelCell(conditionStart); - - // Loop body - addLabelForNextNode(loopEntry); - assert tree.getStatement() != null; - scan(tree.getStatement(), p); - - // Condition - addLabelForNextNode(conditionStart); - assert tree.getCondition() != null; - unbox(scan(tree.getCondition(), p)); - ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); - extendWithExtendedNode(cjump); - - // Loop exit - addLabelForNextNode(loopExit); - - breakTargetLC = oldBreakTargetLC; - continueTargetLC = oldContinueTargetLC; - - return null; - } - - @Override - public Node visitErroneous(ErroneousTree tree, Void p) { - throw new BugInCF("ErroneousTree is unexpected in AST to CFG translation: " + tree); - } - - @Override - public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) { - ExpressionTree exprTree = tree.getExpression(); - scan(exprTree, p); - extendWithNode(new ExpressionStatementNode(exprTree)); - return null; - } - - @Override - public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - // see JLS 14.14.2 - Name parentLabel = getLabel(getCurrentPath()); - - Label conditionStart = new Label(); - Label loopEntry = new Label(); - Label loopExit = new Label(); - - // If the loop is a labeled statement, then its continue target is identical for continues - // with no label and continues with the loop's label. - Label updateStart; - if (parentLabel != null) { - updateStart = continueLabels.get(parentLabel); - } else { - updateStart = new Label(); - } + // Distinguish loops over Iterables from loops over arrays. - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(loopExit); + VariableTree variable = tree.getVariable(); + VariableElement variableElement = TreeUtils.elementFromDeclaration(variable); + ExpressionTree expression = tree.getExpression(); + StatementTree statement = tree.getStatement(); - LabelCell oldContinueTargetLC = continueTargetLC; - continueTargetLC = new LabelCell(updateStart); + TypeMirror exprType = TreeUtils.typeOf(expression); + + if (types.isSubtype(exprType, iterableType)) { + // Take the upper bound of a type variable or wildcard + exprType = TypesUtils.upperBound(exprType); + + assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType"; + DeclaredType declaredExprType = (DeclaredType) exprType; + declaredExprType.getTypeArguments(); + + MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression); + handleArtificialTree(iteratorSelect); + + MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect); + handleArtificialTree(iteratorCall); + + VariableTree iteratorVariable = + createEnhancedForLoopIteratorVariable(iteratorCall, variableElement); + handleArtificialTree(iteratorVariable); + + VariableDeclarationNode iteratorVariableDecl = + new VariableDeclarationNode(iteratorVariable); + iteratorVariableDecl.setInSource(false); + + extendWithNode(iteratorVariableDecl); + + Node expressionNode = scan(expression, p); + + MethodAccessNode iteratorAccessNode = + new MethodAccessNode(iteratorSelect, expressionNode); + iteratorAccessNode.setInSource(false); + extendWithNode(iteratorAccessNode); + MethodInvocationNode iteratorCallNode = + new MethodInvocationNode( + iteratorCall, + iteratorAccessNode, + Collections.emptyList(), + getCurrentPath()); + iteratorCallNode.setInSource(false); + extendWithNode(iteratorCallNode); + + translateAssignment( + iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode); + + // Test the loop ending condition + addLabelForNextNode(conditionStart); + IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable); + handleArtificialTree(iteratorUse1); + + LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1); + iteratorReceiverNode.setInSource(false); + extendWithNode(iteratorReceiverNode); + + MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1); + handleArtificialTree(hasNextSelect); + + MethodAccessNode hasNextAccessNode = + new MethodAccessNode(hasNextSelect, iteratorReceiverNode); + hasNextAccessNode.setInSource(false); + extendWithNode(hasNextAccessNode); + + MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect); + handleArtificialTree(hasNextCall); + + MethodInvocationNode hasNextCallNode = + new MethodInvocationNode( + hasNextCall, + hasNextAccessNode, + Collections.emptyList(), + getCurrentPath()); + hasNextCallNode.setInSource(false); + extendWithNode(hasNextCallNode); + extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); + + // Loop body, starting with declaration of the loop iteration variable + addLabelForNextNode(loopEntry); + extendWithNode(new VariableDeclarationNode(variable)); + + IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable); + handleArtificialTree(iteratorUse2); + + LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2); + iteratorReceiverNode2.setInSource(false); + extendWithNode(iteratorReceiverNode2); + + MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2); + handleArtificialTree(nextSelect); + + MethodAccessNode nextAccessNode = + new MethodAccessNode(nextSelect, iteratorReceiverNode2); + nextAccessNode.setInSource(false); + extendWithNode(nextAccessNode); + + MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect); + handleArtificialTree(nextCall); + + MethodInvocationNode nextCallNode = + new MethodInvocationNode( + nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath()); + // If the type of iteratorVariable is a capture, its type tree may be missing + // annotations, so save the expression in the node so that the full type can be + // found later. + nextCallNode.setIterableExpression(expression); + nextCallNode.setInSource(false); + extendWithNode(nextCallNode); + + AssignmentNode assignNode = + translateAssignment(variable, new LocalVariableNode(variable), nextCall); + // translateAssignment() scans variable and creates new nodes, so set the expression + // there, too. + ((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression); + + assert statement != null; + scan(statement, p); + + // Loop back edge + addLabelForNextNode(updateStart); + extendWithExtendedNode(new UnconditionalJump(conditionStart)); - // Distinguish loops over Iterables from loops over arrays. + } else { + // TODO: Shift any labels after the initialization of the + // temporary array variable. + + VariableTree arrayVariable = + createEnhancedForLoopArrayVariable(expression, variableElement); + handleArtificialTree(arrayVariable); + + VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable); + arrayVariableNode.setInSource(false); + extendWithNode(arrayVariableNode); + Node expressionNode = scan(expression, p); + + translateAssignment( + arrayVariable, new LocalVariableNode(arrayVariable), expressionNode); + + // Declare and initialize the loop index variable + TypeMirror intType = types.getPrimitiveType(TypeKind.INT); + + LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0)); + handleArtificialTree(zero); + + VariableTree indexVariable = + treeBuilder.buildVariableDecl( + intType, + uniqueName("index"), + variableElement.getEnclosingElement(), + zero); + handleArtificialTree(indexVariable); + VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable); + indexVariableNode.setInSource(false); + extendWithNode(indexVariableNode); + IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero); + extendWithNode(zeroNode); + + translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode); + + // Compare index to array length + addLabelForNextNode(conditionStart); + IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse1); + LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1); + indexNode1.setInSource(false); + extendWithNode(indexNode1); + + IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable); + handleArtificialTree(arrayUse1); + LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1); + extendWithNode(arrayNode1); + + MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1); + handleArtificialTree(lengthSelect); + FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1); + lengthAccessNode.setInSource(false); + extendWithNode(lengthAccessNode); + + BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect); + handleArtificialTree(lessThan); + + LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode); + lessThanNode.setInSource(false); + extendWithNode(lessThanNode); + extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); + + // Loop body, starting with declaration of the loop iteration variable + addLabelForNextNode(loopEntry); + extendWithNode(new VariableDeclarationNode(variable)); + + IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable); + handleArtificialTree(arrayUse2); + LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2); + arrayNode2.setInSource(false); + extendWithNode(arrayNode2); + + IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse2); + LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2); + indexNode2.setInSource(false); + extendWithNode(indexNode2); + + ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2); + handleArtificialTree(arrayAccess); + ArrayAccessNode arrayAccessNode = + new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2); + arrayAccessNode.setArrayExpression(expression); + arrayAccessNode.setInSource(false); + extendWithNode(arrayAccessNode); + AssignmentNode arrayAccessAssignNode = + translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode); + extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType); + // translateAssignment() scans variable and creates new nodes, so set the expression + // there, too. + Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression(); + if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) { + ((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression); + } else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) { + // If the array component type is a primitive, there may be a boxing or unboxing + // conversion. Treat that as an iterator. + MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr; + boxingNode.setIterableExpression(expression); + } + + assert statement != null; + scan(statement, p); + + // Loop back edge + addLabelForNextNode(updateStart); + + IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse3); + LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3); + indexNode3.setInSource(false); + extendWithNode(indexNode3); + + LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); + handleArtificialTree(oneTree); + Node one = new IntegerLiteralNode(oneTree); + one.setInSource(false); + extendWithNode(one); + + BinaryTree addOneTree = + treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree); + handleArtificialTree(addOneTree); + Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one); + addOneNode.setInSource(false); + extendWithNode(addOneNode); + + AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree); + handleArtificialTree(assignTree); + Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode); + assignNode.setInSource(false); + extendWithNode(assignNode); + + extendWithExtendedNode(new UnconditionalJump(conditionStart)); + } + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetLC = oldBreakTargetLC; + continueTargetLC = oldContinueTargetLC; - VariableTree variable = tree.getVariable(); - VariableElement variableElement = TreeUtils.elementFromDeclaration(variable); - ExpressionTree expression = tree.getExpression(); - StatementTree statement = tree.getStatement(); + return null; + } - TypeMirror exprType = TreeUtils.typeOf(expression); + protected VariableTree createEnhancedForLoopIteratorVariable( + MethodInvocationTree iteratorCall, VariableElement variableElement) { + TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall); - if (types.isSubtype(exprType, iterableType)) { - // Take the upper bound of a type variable or wildcard - exprType = TypesUtils.upperBound(exprType); + // Declare and initialize a new, unique iterator variable + VariableTree iteratorVariable = + treeBuilder.buildVariableDecl( + iteratorType, // annotatedIteratorTypeTree, + uniqueName("iter"), + variableElement.getEnclosingElement(), + iteratorCall); + return iteratorVariable; + } - assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType"; - DeclaredType declaredExprType = (DeclaredType) exprType; - declaredExprType.getTypeArguments(); + protected VariableTree createEnhancedForLoopArrayVariable( + ExpressionTree expression, VariableElement variableElement) { + TypeMirror arrayType = TreeUtils.typeOf(expression); - MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression); - handleArtificialTree(iteratorSelect); + // Declare and initialize a temporary array variable + VariableTree arrayVariable = + treeBuilder.buildVariableDecl( + arrayType, + uniqueName("array"), + variableElement.getEnclosingElement(), + expression); + return arrayVariable; + } - MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect); - handleArtificialTree(iteratorCall); + @Override + public Node visitForLoop(ForLoopTree tree, Void p) { + Name parentLabel = getLabel(getCurrentPath()); - VariableTree iteratorVariable = - createEnhancedForLoopIteratorVariable(iteratorCall, variableElement); - handleArtificialTree(iteratorVariable); + Label conditionStart = new Label(); + Label loopEntry = new Label(); + Label loopExit = new Label(); - VariableDeclarationNode iteratorVariableDecl = new VariableDeclarationNode(iteratorVariable); - iteratorVariableDecl.setInSource(false); + // If the loop is a labeled statement, then its continue target is identical for continues + // with no label and continues with the loop's label. + Label updateStart; + if (parentLabel != null) { + updateStart = continueLabels.get(parentLabel); + } else { + updateStart = new Label(); + } - extendWithNode(iteratorVariableDecl); + LabelCell oldBreakTargetLC = breakTargetLC; + breakTargetLC = new LabelCell(loopExit); - Node expressionNode = scan(expression, p); + LabelCell oldContinueTargetLC = continueTargetLC; + continueTargetLC = new LabelCell(updateStart); - MethodAccessNode iteratorAccessNode = new MethodAccessNode(iteratorSelect, expressionNode); - iteratorAccessNode.setInSource(false); - extendWithNode(iteratorAccessNode); - MethodInvocationNode iteratorCallNode = - new MethodInvocationNode( - iteratorCall, iteratorAccessNode, Collections.emptyList(), getCurrentPath()); - iteratorCallNode.setInSource(false); - extendWithNode(iteratorCallNode); + // Initializer + for (StatementTree init : tree.getInitializer()) { + scan(init, p); + } - translateAssignment( - iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode); + // Condition + addLabelForNextNode(conditionStart); + if (tree.getCondition() != null) { + unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); + extendWithExtendedNode(cjump); + } - // Test the loop ending condition - addLabelForNextNode(conditionStart); - IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable); - handleArtificialTree(iteratorUse1); + // Loop body + addLabelForNextNode(loopEntry); + assert tree.getStatement() != null; + scan(tree.getStatement(), p); - LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1); - iteratorReceiverNode.setInSource(false); - extendWithNode(iteratorReceiverNode); + // Update + addLabelForNextNode(updateStart); + for (ExpressionStatementTree update : tree.getUpdate()) { + scan(update, p); + } - MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1); - handleArtificialTree(hasNextSelect); + extendWithExtendedNode(new UnconditionalJump(conditionStart)); - MethodAccessNode hasNextAccessNode = - new MethodAccessNode(hasNextSelect, iteratorReceiverNode); - hasNextAccessNode.setInSource(false); - extendWithNode(hasNextAccessNode); + // Loop exit + addLabelForNextNode(loopExit); - MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect); - handleArtificialTree(hasNextCall); - - MethodInvocationNode hasNextCallNode = - new MethodInvocationNode( - hasNextCall, hasNextAccessNode, Collections.emptyList(), getCurrentPath()); - hasNextCallNode.setInSource(false); - extendWithNode(hasNextCallNode); - extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); - - // Loop body, starting with declaration of the loop iteration variable - addLabelForNextNode(loopEntry); - extendWithNode(new VariableDeclarationNode(variable)); - - IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable); - handleArtificialTree(iteratorUse2); + breakTargetLC = oldBreakTargetLC; + continueTargetLC = oldContinueTargetLC; - LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2); - iteratorReceiverNode2.setInSource(false); - extendWithNode(iteratorReceiverNode2); - - MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2); - handleArtificialTree(nextSelect); - - MethodAccessNode nextAccessNode = new MethodAccessNode(nextSelect, iteratorReceiverNode2); - nextAccessNode.setInSource(false); - extendWithNode(nextAccessNode); - - MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect); - handleArtificialTree(nextCall); - - MethodInvocationNode nextCallNode = - new MethodInvocationNode( - nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath()); - // If the type of iteratorVariable is a capture, its type tree may be missing - // annotations, so save the expression in the node so that the full type can be - // found later. - nextCallNode.setIterableExpression(expression); - nextCallNode.setInSource(false); - extendWithNode(nextCallNode); - - AssignmentNode assignNode = - translateAssignment(variable, new LocalVariableNode(variable), nextCall); - // translateAssignment() scans variable and creates new nodes, so set the expression - // there, too. - ((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression); - - assert statement != null; - scan(statement, p); - - // Loop back edge - addLabelForNextNode(updateStart); - extendWithExtendedNode(new UnconditionalJump(conditionStart)); - - } else { - // TODO: Shift any labels after the initialization of the - // temporary array variable. - - VariableTree arrayVariable = createEnhancedForLoopArrayVariable(expression, variableElement); - handleArtificialTree(arrayVariable); - - VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable); - arrayVariableNode.setInSource(false); - extendWithNode(arrayVariableNode); - Node expressionNode = scan(expression, p); - - translateAssignment(arrayVariable, new LocalVariableNode(arrayVariable), expressionNode); - - // Declare and initialize the loop index variable - TypeMirror intType = types.getPrimitiveType(TypeKind.INT); - - LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0)); - handleArtificialTree(zero); - - VariableTree indexVariable = - treeBuilder.buildVariableDecl( - intType, uniqueName("index"), variableElement.getEnclosingElement(), zero); - handleArtificialTree(indexVariable); - VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable); - indexVariableNode.setInSource(false); - extendWithNode(indexVariableNode); - IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero); - extendWithNode(zeroNode); - - translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode); - - // Compare index to array length - addLabelForNextNode(conditionStart); - IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable); - handleArtificialTree(indexUse1); - LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1); - indexNode1.setInSource(false); - extendWithNode(indexNode1); - - IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable); - handleArtificialTree(arrayUse1); - LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1); - extendWithNode(arrayNode1); - - MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1); - handleArtificialTree(lengthSelect); - FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1); - lengthAccessNode.setInSource(false); - extendWithNode(lengthAccessNode); - - BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect); - handleArtificialTree(lessThan); - - LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode); - lessThanNode.setInSource(false); - extendWithNode(lessThanNode); - extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); - - // Loop body, starting with declaration of the loop iteration variable - addLabelForNextNode(loopEntry); - extendWithNode(new VariableDeclarationNode(variable)); - - IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable); - handleArtificialTree(arrayUse2); - LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2); - arrayNode2.setInSource(false); - extendWithNode(arrayNode2); - - IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable); - handleArtificialTree(indexUse2); - LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2); - indexNode2.setInSource(false); - extendWithNode(indexNode2); - - ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2); - handleArtificialTree(arrayAccess); - ArrayAccessNode arrayAccessNode = new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2); - arrayAccessNode.setArrayExpression(expression); - arrayAccessNode.setInSource(false); - extendWithNode(arrayAccessNode); - AssignmentNode arrayAccessAssignNode = - translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode); - extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType); - // translateAssignment() scans variable and creates new nodes, so set the expression - // there, too. - Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression(); - if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) { - ((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression); - } else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) { - // If the array component type is a primitive, there may be a boxing or unboxing - // conversion. Treat that as an iterator. - MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr; - boxingNode.setIterableExpression(expression); - } - - assert statement != null; - scan(statement, p); - - // Loop back edge - addLabelForNextNode(updateStart); - - IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable); - handleArtificialTree(indexUse3); - LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3); - indexNode3.setInSource(false); - extendWithNode(indexNode3); - - LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); - handleArtificialTree(oneTree); - Node one = new IntegerLiteralNode(oneTree); - one.setInSource(false); - extendWithNode(one); - - BinaryTree addOneTree = treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree); - handleArtificialTree(addOneTree); - Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one); - addOneNode.setInSource(false); - extendWithNode(addOneNode); - - AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree); - handleArtificialTree(assignTree); - Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode); - assignNode.setInSource(false); - extendWithNode(assignNode); - - extendWithExtendedNode(new UnconditionalJump(conditionStart)); + return null; } - // Loop exit - addLabelForNextNode(loopExit); - - breakTargetLC = oldBreakTargetLC; - continueTargetLC = oldContinueTargetLC; - - return null; - } - - protected VariableTree createEnhancedForLoopIteratorVariable( - MethodInvocationTree iteratorCall, VariableElement variableElement) { - TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall); - - // Declare and initialize a new, unique iterator variable - VariableTree iteratorVariable = - treeBuilder.buildVariableDecl( - iteratorType, // annotatedIteratorTypeTree, - uniqueName("iter"), - variableElement.getEnclosingElement(), - iteratorCall); - return iteratorVariable; - } - - protected VariableTree createEnhancedForLoopArrayVariable( - ExpressionTree expression, VariableElement variableElement) { - TypeMirror arrayType = TreeUtils.typeOf(expression); - - // Declare and initialize a temporary array variable - VariableTree arrayVariable = - treeBuilder.buildVariableDecl( - arrayType, uniqueName("array"), variableElement.getEnclosingElement(), expression); - return arrayVariable; - } - - @Override - public Node visitForLoop(ForLoopTree tree, Void p) { - Name parentLabel = getLabel(getCurrentPath()); - - Label conditionStart = new Label(); - Label loopEntry = new Label(); - Label loopExit = new Label(); - - // If the loop is a labeled statement, then its continue target is identical for continues - // with no label and continues with the loop's label. - Label updateStart; - if (parentLabel != null) { - updateStart = continueLabels.get(parentLabel); - } else { - updateStart = new Label(); + @Override + public Node visitIdentifier(IdentifierTree tree, Void p) { + Node node; + if (TreeUtils.isFieldAccess(tree)) { + Node receiver = getReceiver(tree); + node = new FieldAccessNode(tree, receiver); + } else { + Element element = TreeUtils.elementFromUse(tree); + switch (element.getKind()) { + case FIELD: + // Note that "this"/"super" is a field, but not a field access. + if (element.getSimpleName().contentEquals("this")) { + node = new ExplicitThisNode(tree); + } else { + node = new SuperNode(tree); + } + break; + case EXCEPTION_PARAMETER: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case PARAMETER: + node = new LocalVariableNode(tree); + break; + case PACKAGE: + node = new PackageNameNode(tree); + break; + default: + if (ElementUtils.isTypeDeclaration(element)) { + node = new ClassNameNode(tree); + break; + } else if (ElementUtils.isBindingVariable(element)) { + // Note: BINDING_VARIABLE should be added as a direct case above when + // instanceof pattern matching and Java15 are supported. + node = new LocalVariableNode(tree); + break; + } + throw new BugInCF("bad element kind " + element.getKind()); + } + } + if (node instanceof ClassNameNode) { + extendWithClassNameNode((ClassNameNode) node); + } else { + extendWithNode(node); + } + return node; } - LabelCell oldBreakTargetLC = breakTargetLC; - breakTargetLC = new LabelCell(loopExit); + @Override + public Node visitIf(IfTree tree, Void p) { + // all necessary labels + Label thenEntry = new Label(); + Label elseEntry = new Label(); + Label endIf = new Label(); + + // basic block for the condition + unbox(scan(tree.getCondition(), p)); + + ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry); + extendWithExtendedNode(cjump); + + // then branch + addLabelForNextNode(thenEntry); + StatementTree thenStatement = tree.getThenStatement(); + scan(thenStatement, p); + extendWithExtendedNode(new UnconditionalJump(endIf)); + + // else branch + addLabelForNextNode(elseEntry); + StatementTree elseStatement = tree.getElseStatement(); + if (elseStatement != null) { + scan(elseStatement, p); + } - LabelCell oldContinueTargetLC = continueTargetLC; - continueTargetLC = new LabelCell(updateStart); + // label the end of the if statement + addLabelForNextNode(endIf); - // Initializer - for (StatementTree init : tree.getInitializer()) { - scan(init, p); + return null; } - // Condition - addLabelForNextNode(conditionStart); - if (tree.getCondition() != null) { - unbox(scan(tree.getCondition(), p)); - ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); - extendWithExtendedNode(cjump); + @Override + public Node visitImport(ImportTree tree, Void p) { + throw new BugInCF("ImportTree is unexpected in AST to CFG translation: " + tree); } - // Loop body - addLabelForNextNode(loopEntry); - assert tree.getStatement() != null; - scan(tree.getStatement(), p); - - // Update - addLabelForNextNode(updateStart); - for (ExpressionStatementTree update : tree.getUpdate()) { - scan(update, p); + @Override + public Node visitArrayAccess(ArrayAccessTree tree, Void p) { + Node array = scan(tree.getExpression(), p); + Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); + Node arrayAccess = new ArrayAccessNode(tree, array, index); + extendWithNode(arrayAccess); + extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType); + extendWithNodeWithException(arrayAccess, nullPointerExceptionType); + return arrayAccess; } - extendWithExtendedNode(new UnconditionalJump(conditionStart)); - - // Loop exit - addLabelForNextNode(loopExit); - - breakTargetLC = oldBreakTargetLC; - continueTargetLC = oldContinueTargetLC; - - return null; - } - - @Override - public Node visitIdentifier(IdentifierTree tree, Void p) { - Node node; - if (TreeUtils.isFieldAccess(tree)) { - Node receiver = getReceiver(tree); - node = new FieldAccessNode(tree, receiver); - } else { - Element element = TreeUtils.elementFromUse(tree); - switch (element.getKind()) { - case FIELD: - // Note that "this"/"super" is a field, but not a field access. - if (element.getSimpleName().contentEquals("this")) { - node = new ExplicitThisNode(tree); - } else { - node = new SuperNode(tree); - } - break; - case EXCEPTION_PARAMETER: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case PARAMETER: - node = new LocalVariableNode(tree); - break; - case PACKAGE: - node = new PackageNameNode(tree); - break; - default: - if (ElementUtils.isTypeDeclaration(element)) { - node = new ClassNameNode(tree); - break; - } else if (ElementUtils.isBindingVariable(element)) { - // Note: BINDING_VARIABLE should be added as a direct case above when - // instanceof pattern matching and Java15 are supported. - node = new LocalVariableNode(tree); - break; - } - throw new BugInCF("bad element kind " + element.getKind()); - } + @Override + public Node visitLabeledStatement(LabeledStatementTree tree, Void p) { + // This method can set the break target after generating all Nodes in the contained + // statement, but it can't set the continue target, which may be in the middle of a + // sequence of nodes. Labeled loops must look up and use the continue Labels. + Name labelName = tree.getLabel(); + + Label breakLabel = new Label(labelName + "_break"); + Label continueLabel = new Label(labelName + "_continue"); + + breakLabels.put(labelName, breakLabel); + continueLabels.put(labelName, continueLabel); + + scan(tree.getStatement(), p); + + addLabelForNextNode(breakLabel); + + breakLabels.remove(labelName); + continueLabels.remove(labelName); + + return null; } - if (node instanceof ClassNameNode) { - extendWithClassNameNode((ClassNameNode) node); - } else { - extendWithNode(node); + + @Override + public Node visitLiteral(LiteralTree tree, Void p) { + Node r = null; + switch (tree.getKind()) { + case BOOLEAN_LITERAL: + r = new BooleanLiteralNode(tree); + break; + case CHAR_LITERAL: + r = new CharacterLiteralNode(tree); + break; + case DOUBLE_LITERAL: + r = new DoubleLiteralNode(tree); + break; + case FLOAT_LITERAL: + r = new FloatLiteralNode(tree); + break; + case INT_LITERAL: + r = new IntegerLiteralNode(tree); + break; + case LONG_LITERAL: + r = new LongLiteralNode(tree); + break; + case NULL_LITERAL: + r = new NullLiteralNode(tree); + break; + case STRING_LITERAL: + r = new StringLiteralNode(tree); + break; + default: + throw new BugInCF("unexpected literal tree: " + tree); + } + assert r != null : "unexpected literal tree"; + extendWithNode(r); + return r; } - return node; - } - - @Override - public Node visitIf(IfTree tree, Void p) { - // all necessary labels - Label thenEntry = new Label(); - Label elseEntry = new Label(); - Label endIf = new Label(); - - // basic block for the condition - unbox(scan(tree.getCondition(), p)); - - ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry); - extendWithExtendedNode(cjump); - - // then branch - addLabelForNextNode(thenEntry); - StatementTree thenStatement = tree.getThenStatement(); - scan(thenStatement, p); - extendWithExtendedNode(new UnconditionalJump(endIf)); - - // else branch - addLabelForNextNode(elseEntry); - StatementTree elseStatement = tree.getElseStatement(); - if (elseStatement != null) { - scan(elseStatement, p); + + @Override + public Node visitMethod(MethodTree tree, Void p) { + throw new BugInCF("MethodTree is unexpected in AST to CFG translation"); } - // label the end of the if statement - addLabelForNextNode(endIf); - - return null; - } - - @Override - public Node visitImport(ImportTree tree, Void p) { - throw new BugInCF("ImportTree is unexpected in AST to CFG translation: " + tree); - } - - @Override - public Node visitArrayAccess(ArrayAccessTree tree, Void p) { - Node array = scan(tree.getExpression(), p); - Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); - Node arrayAccess = new ArrayAccessNode(tree, array, index); - extendWithNode(arrayAccess); - extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType); - extendWithNodeWithException(arrayAccess, nullPointerExceptionType); - return arrayAccess; - } - - @Override - public Node visitLabeledStatement(LabeledStatementTree tree, Void p) { - // This method can set the break target after generating all Nodes in the contained - // statement, but it can't set the continue target, which may be in the middle of a - // sequence of nodes. Labeled loops must look up and use the continue Labels. - Name labelName = tree.getLabel(); - - Label breakLabel = new Label(labelName + "_break"); - Label continueLabel = new Label(labelName + "_continue"); - - breakLabels.put(labelName, breakLabel); - continueLabels.put(labelName, continueLabel); - - scan(tree.getStatement(), p); - - addLabelForNextNode(breakLabel); - - breakLabels.remove(labelName); - continueLabels.remove(labelName); - - return null; - } - - @Override - public Node visitLiteral(LiteralTree tree, Void p) { - Node r = null; - switch (tree.getKind()) { - case BOOLEAN_LITERAL: - r = new BooleanLiteralNode(tree); - break; - case CHAR_LITERAL: - r = new CharacterLiteralNode(tree); - break; - case DOUBLE_LITERAL: - r = new DoubleLiteralNode(tree); - break; - case FLOAT_LITERAL: - r = new FloatLiteralNode(tree); - break; - case INT_LITERAL: - r = new IntegerLiteralNode(tree); - break; - case LONG_LITERAL: - r = new LongLiteralNode(tree); - break; - case NULL_LITERAL: - r = new NullLiteralNode(tree); - break; - case STRING_LITERAL: - r = new StringLiteralNode(tree); - break; - default: - throw new BugInCF("unexpected literal tree: " + tree); + @Override + public Node visitModifiers(ModifiersTree tree, Void p) { + throw new BugInCF("ModifiersTree is unexpected in AST to CFG translation"); } - assert r != null : "unexpected literal tree"; - extendWithNode(r); - return r; - } - - @Override - public Node visitMethod(MethodTree tree, Void p) { - throw new BugInCF("MethodTree is unexpected in AST to CFG translation"); - } - - @Override - public Node visitModifiers(ModifiersTree tree, Void p) { - throw new BugInCF("ModifiersTree is unexpected in AST to CFG translation"); - } - - @Override - public Node visitNewArray(NewArrayTree tree, Void p) { - // see JLS 15.10 - - ArrayType type = (ArrayType) TreeUtils.typeOf(tree); - TypeMirror elemType = type.getComponentType(); - - List dimensions = tree.getDimensions(); - List initializers = tree.getInitializers(); - assert dimensions != null; - - List dimensionNodes = - CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions); - - List initializerNodes; - if (initializers == null) { - initializerNodes = Collections.emptyList(); - } else { - initializerNodes = - CollectionsPlume.mapList(init -> assignConvert(scan(init, p), elemType), initializers); + + @Override + public Node visitNewArray(NewArrayTree tree, Void p) { + // see JLS 15.10 + + ArrayType type = (ArrayType) TreeUtils.typeOf(tree); + TypeMirror elemType = type.getComponentType(); + + List dimensions = tree.getDimensions(); + List initializers = tree.getInitializers(); + assert dimensions != null; + + List dimensionNodes = + CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions); + + List initializerNodes; + if (initializers == null) { + initializerNodes = Collections.emptyList(); + } else { + initializerNodes = + CollectionsPlume.mapList( + init -> assignConvert(scan(init, p), elemType), initializers); + } + + Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes); + + extendWithNodeWithExceptions(node, newArrayExceptionTypes); + return node; } - Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes); - - extendWithNodeWithExceptions(node, newArrayExceptionTypes); - return node; - } - - @Override - public Node visitNewClass(NewClassTree tree, Void p) { - // see JLS 15.9 - - DeclaredType classType = (DeclaredType) TreeUtils.typeOf(tree); - TypeMirror enclosingType = classType.getEnclosingType(); - Tree enclosingExpr = tree.getEnclosingExpression(); - Node enclosingExprNode; - if (enclosingExpr != null) { - enclosingExprNode = scan(enclosingExpr, p); - } else if (enclosingType.getKind() == TypeKind.DECLARED) { - // This is an inner class (instance nested class). - // As there is no explicit enclosing expression, create a node for the implicit this - // argument. - enclosingExprNode = new ImplicitThisNode(enclosingType); - extendWithNode(enclosingExprNode); - } else { - // For static nested classes, the kind would be Typekind.None. - - enclosingExprNode = null; + @Override + public Node visitNewClass(NewClassTree tree, Void p) { + // see JLS 15.9 + + DeclaredType classType = (DeclaredType) TreeUtils.typeOf(tree); + TypeMirror enclosingType = classType.getEnclosingType(); + Tree enclosingExpr = tree.getEnclosingExpression(); + Node enclosingExprNode; + if (enclosingExpr != null) { + enclosingExprNode = scan(enclosingExpr, p); + } else if (enclosingType.getKind() == TypeKind.DECLARED) { + // This is an inner class (instance nested class). + // As there is no explicit enclosing expression, create a node for the implicit this + // argument. + enclosingExprNode = new ImplicitThisNode(enclosingType); + extendWithNode(enclosingExprNode); + } else { + // For static nested classes, the kind would be Typekind.None. + + enclosingExprNode = null; + } + + // Convert constructor arguments + ExecutableElement constructor = TreeUtils.elementFromUse(tree); + + List actualExprs = tree.getArguments(); + + List arguments = + convertCallArguments( + tree, constructor, TreeUtils.typeFromUse(tree), actualExprs, tree); + + // TODO: for anonymous classes, don't use the identifier alone. + // See https://github.com/typetools/checker-framework/issues/890 . + Node constructorNode = scan(tree.getIdentifier(), p); + + // Handle anonymous classes in visitClass. + // Note that getClassBody() and therefore classbody can be null. + ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p); + + Node node = + new ObjectCreationNode( + tree, enclosingExprNode, constructorNode, arguments, classbody); + List thrownTypes = constructor.getThrownTypes(); + Set thrownSet = + ArraySet.newArraySetOrLinkedHashSet( + thrownTypes.size() + uncheckedExceptionTypes.size()); + // Add exceptions explicitly mentioned in the throws clause. + thrownSet.addAll(thrownTypes); + // Add types to account for unchecked exceptions + thrownSet.addAll(uncheckedExceptionTypes); + + extendWithNodeWithExceptions(node, thrownSet); + + return node; } - // Convert constructor arguments - ExecutableElement constructor = TreeUtils.elementFromUse(tree); - - List actualExprs = tree.getArguments(); - - List arguments = - convertCallArguments(tree, constructor, TreeUtils.typeFromUse(tree), actualExprs, tree); - - // TODO: for anonymous classes, don't use the identifier alone. - // See https://github.com/typetools/checker-framework/issues/890 . - Node constructorNode = scan(tree.getIdentifier(), p); - - // Handle anonymous classes in visitClass. - // Note that getClassBody() and therefore classbody can be null. - ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p); - - Node node = - new ObjectCreationNode(tree, enclosingExprNode, constructorNode, arguments, classbody); - List thrownTypes = constructor.getThrownTypes(); - Set thrownSet = - ArraySet.newArraySetOrLinkedHashSet(thrownTypes.size() + uncheckedExceptionTypes.size()); - // Add exceptions explicitly mentioned in the throws clause. - thrownSet.addAll(thrownTypes); - // Add types to account for unchecked exceptions - thrownSet.addAll(uncheckedExceptionTypes); - - extendWithNodeWithExceptions(node, thrownSet); - - return node; - } - - /** - * Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists. - * - *

          This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a - * {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This - * map is necessary because dataflow does not create a {@code Node} for a {@code - * ParenthesizedTree}. - */ - private final Map parenMapping = new HashMap<>(); - - @Override - public Node visitParenthesized(ParenthesizedTree tree, Void p) { - parenMapping.put(tree.getExpression(), tree); - return scan(tree.getExpression(), p); - } - - @Override - public Node visitReturn(ReturnTree tree, Void p) { - ExpressionTree ret = tree.getExpression(); - // TODO: also have a return-node if nothing is returned - ReturnNode result = null; - if (ret != null) { - Node node = scan(ret, p); - result = new ReturnNode(tree, node, env.getTypeUtils()); - returnNodes.add(result); - extendWithNode(result); + /** + * Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists. + * + *

          This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a + * {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This + * map is necessary because dataflow does not create a {@code Node} for a {@code + * ParenthesizedTree}. + */ + private final Map parenMapping = new HashMap<>(); + + @Override + public Node visitParenthesized(ParenthesizedTree tree, Void p) { + parenMapping.put(tree.getExpression(), tree); + return scan(tree.getExpression(), p); } - extendWithExtendedNode(new UnconditionalJump(this.returnTargetLC.accessLabel())); + @Override + public Node visitReturn(ReturnTree tree, Void p) { + ExpressionTree ret = tree.getExpression(); + // TODO: also have a return-node if nothing is returned + ReturnNode result = null; + if (ret != null) { + Node node = scan(ret, p); + result = new ReturnNode(tree, node, env.getTypeUtils()); + returnNodes.add(result); + extendWithNode(result); + } - return result; - } + extendWithExtendedNode(new UnconditionalJump(this.returnTargetLC.accessLabel())); - @Override - public Node visitMemberSelect(MemberSelectTree tree, Void p) { - Node expr = scan(tree.getExpression(), p); - if (!TreeUtils.isFieldAccess(tree)) { - // Could be a selector of a class or package - Element element = TreeUtils.elementFromUse(tree); - if (ElementUtils.isTypeElement(element)) { - ClassNameNode result = new ClassNameNode(tree, expr); - extendWithClassNameNode(result); - return result; - } else if (element.getKind() == ElementKind.PACKAGE) { - Node result = new PackageNameNode(tree, (PackageNameNode) expr); - extendWithNode(result); return result; - } else { - throw new BugInCF("Unexpected element kind: " + element.getKind()); - } } - Node node = new FieldAccessNode(tree, expr); + @Override + public Node visitMemberSelect(MemberSelectTree tree, Void p) { + Node expr = scan(tree.getExpression(), p); + if (!TreeUtils.isFieldAccess(tree)) { + // Could be a selector of a class or package + Element element = TreeUtils.elementFromUse(tree); + if (ElementUtils.isTypeElement(element)) { + ClassNameNode result = new ClassNameNode(tree, expr); + extendWithClassNameNode(result); + return result; + } else if (element.getKind() == ElementKind.PACKAGE) { + Node result = new PackageNameNode(tree, (PackageNameNode) expr); + extendWithNode(result); + return result; + } else { + throw new BugInCF("Unexpected element kind: " + element.getKind()); + } + } + + Node node = new FieldAccessNode(tree, expr); + + Element element = TreeUtils.elementFromUse(tree); + if (ElementUtils.isStatic(element) || expr instanceof ThisNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(node); + } else { + extendWithNodeWithException(node, nullPointerExceptionType); + } + + return node; + } + + @Override + public Node visitEmptyStatement(EmptyStatementTree tree, Void p) { + return null; + } + + @Override + public Node visitSynchronized(SynchronizedTree tree, Void p) { + // see JLS 14.19 - Element element = TreeUtils.elementFromUse(tree); - if (ElementUtils.isStatic(element) || expr instanceof ThisNode) { - // No NullPointerException can be thrown, use normal node - extendWithNode(node); - } else { - extendWithNodeWithException(node, nullPointerExceptionType); + Node synchronizedExpr = scan(tree.getExpression(), p); + SynchronizedNode synchronizedStartNode = + new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils()); + extendWithNode(synchronizedStartNode); + scan(tree.getBlock(), p); + SynchronizedNode synchronizedEndNode = + new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils()); + extendWithNode(synchronizedEndNode); + + return null; + } + + @Override + public Node visitThrow(ThrowTree tree, Void p) { + Node expression = scan(tree.getExpression(), p); + TypeMirror exception = expression.getType(); + ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils()); + NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception); + exNode.setTerminatesExecution(true); + return throwsNode; } - return node; - } - - @Override - public Node visitEmptyStatement(EmptyStatementTree tree, Void p) { - return null; - } - - @Override - public Node visitSynchronized(SynchronizedTree tree, Void p) { - // see JLS 14.19 - - Node synchronizedExpr = scan(tree.getExpression(), p); - SynchronizedNode synchronizedStartNode = - new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils()); - extendWithNode(synchronizedStartNode); - scan(tree.getBlock(), p); - SynchronizedNode synchronizedEndNode = - new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils()); - extendWithNode(synchronizedEndNode); - - return null; - } - - @Override - public Node visitThrow(ThrowTree tree, Void p) { - Node expression = scan(tree.getExpression(), p); - TypeMirror exception = expression.getType(); - ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils()); - NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception); - exNode.setTerminatesExecution(true); - return throwsNode; - } - - @Override - public Node visitCompilationUnit(CompilationUnitTree tree, Void p) { - throw new BugInCF("CompilationUnitTree is unexpected in AST to CFG translation"); - } - - /** - * Return the first argument if it is non-null, otherwise return the second argument. Throws an - * exception if both arguments are null. - * - * @param the type of the arguments - * @param first a reference - * @param second a reference - * @return the first argument that is non-null - */ - private static A firstNonNull(A first, A second) { - if (first != null) { - return first; - } else if (second != null) { - return second; - } else { - throw new NullPointerException(); + @Override + public Node visitCompilationUnit(CompilationUnitTree tree, Void p) { + throw new BugInCF("CompilationUnitTree is unexpected in AST to CFG translation"); } - } - @Override - public Node visitTry(TryTree tree, Void p) { - List catches = tree.getCatches(); - BlockTree finallyBlock = tree.getFinallyBlock(); + /** + * Return the first argument if it is non-null, otherwise return the second argument. Throws an + * exception if both arguments are null. + * + * @param the type of the arguments + * @param first a reference + * @param second a reference + * @return the first argument that is non-null + */ + private static A firstNonNull(A first, A second) { + if (first != null) { + return first; + } else if (second != null) { + return second; + } else { + throw new NullPointerException(); + } + } - extendWithNode( - new MarkerNode( - tree, "start of try statement #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); + @Override + public Node visitTry(TryTree tree, Void p) { + List catches = tree.getCatches(); + BlockTree finallyBlock = tree.getFinallyBlock(); - List> catchLabels = - CollectionsPlume.mapList( - (CatchTree c) -> IPair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label()), - catches); + extendWithNode( + new MarkerNode( + tree, + "start of try statement #" + TreeUtils.treeUids.get(tree), + env.getTypeUtils())); - // Store return/break/continue labels, just in case we need them for a finally block. - LabelCell oldReturnTargetLC = returnTargetLC; - LabelCell oldBreakTargetLC = breakTargetLC; - Map oldBreakLabels = breakLabels; - LabelCell oldContinueTargetLC = continueTargetLC; - Map oldContinueLabels = continueLabels; + List> catchLabels = + CollectionsPlume.mapList( + (CatchTree c) -> + IPair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label()), + catches); - Label finallyLabel = null; - Label exceptionalFinallyLabel = null; + // Store return/break/continue labels, just in case we need them for a finally block. + LabelCell oldReturnTargetLC = returnTargetLC; + LabelCell oldBreakTargetLC = breakTargetLC; + Map oldBreakLabels = breakLabels; + LabelCell oldContinueTargetLC = continueTargetLC; + Map oldContinueLabels = continueLabels; - if (finallyBlock != null) { - finallyLabel = new Label(); + Label finallyLabel = null; + Label exceptionalFinallyLabel = null; - exceptionalFinallyLabel = new Label(); - tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); + if (finallyBlock != null) { + finallyLabel = new Label(); - returnTargetLC = new LabelCell(); + exceptionalFinallyLabel = new Label(); + tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); - breakTargetLC = new LabelCell(); - breakLabels = new TryFinallyScopeMap(); + returnTargetLC = new LabelCell(); - continueTargetLC = new LabelCell(); - continueLabels = new TryFinallyScopeMap(); - } + breakTargetLC = new LabelCell(); + breakLabels = new TryFinallyScopeMap(); - Label doneLabel = new Label(); + continueTargetLC = new LabelCell(); + continueLabels = new TryFinallyScopeMap(); + } - tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); + Label doneLabel = new Label(); - extendWithNode( - new MarkerNode( - tree, "start of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); + tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); - handleTryResourcesAndBlock(tree, p, tree.getResources()); + extendWithNode( + new MarkerNode( + tree, + "start of try block #" + TreeUtils.treeUids.get(tree), + env.getTypeUtils())); - extendWithNode( - new MarkerNode( - tree, "end of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); + handleTryResourcesAndBlock(tree, p, tree.getResources()); - extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); + extendWithNode( + new MarkerNode( + tree, + "end of try block #" + TreeUtils.treeUids.get(tree), + env.getTypeUtils())); + + extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); + + // This pops the try-catch frame + tryStack.popFrame(); + + int catchIndex = 0; + for (CatchTree c : catches) { + addLabelForNextNode(catchLabels.get(catchIndex).second); + TypeMirror catchType = TreeUtils.typeOf(c.getParameter().getType()); + extendWithNode(new CatchMarkerNode(tree, "start", catchType, env.getTypeUtils())); + scan(c, p); + extendWithNode(new CatchMarkerNode(tree, "end", catchType, env.getTypeUtils())); + + catchIndex++; + extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); + } - // This pops the try-catch frame - tryStack.popFrame(); + if (finallyLabel != null) { + handleFinally( + tree, + doneLabel, + finallyLabel, + exceptionalFinallyLabel, + () -> scan(finallyBlock, p), + oldReturnTargetLC, + oldBreakTargetLC, + oldBreakLabels, + oldContinueTargetLC, + oldContinueLabels); + } - int catchIndex = 0; - for (CatchTree c : catches) { - addLabelForNextNode(catchLabels.get(catchIndex).second); - TypeMirror catchType = TreeUtils.typeOf(c.getParameter().getType()); - extendWithNode(new CatchMarkerNode(tree, "start", catchType, env.getTypeUtils())); - scan(c, p); - extendWithNode(new CatchMarkerNode(tree, "end", catchType, env.getTypeUtils())); + addLabelForNextNode(doneLabel); - catchIndex++; - extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); + return null; } - if (finallyLabel != null) { - handleFinally( - tree, - doneLabel, - finallyLabel, - exceptionalFinallyLabel, - () -> scan(finallyBlock, p), - oldReturnTargetLC, - oldBreakTargetLC, - oldBreakLabels, - oldContinueTargetLC, - oldContinueLabels); - } + /** + * A recursive helper method to handle the resource declarations (if any) in a {@link TryTree} + * and its main block. If the {@code resources} list is empty, the method scans the main block + * of the try statement and returns. Otherwise, the first resource declaration in {@code + * resources} is desugared, following the logic in JLS 14.20.3.1. A resource declaration + * r is desugared by adding the nodes for r itself to the CFG, followed by a + * synthetic nested {@code try} block and {@code finally} block. The synthetic {@code try} block + * contains any remaining resource declarations and the original try block (handled via + * recursion). The synthetic {@code finally} block contains a call to {@code close} for + * r, guaranteeing that on every path through the CFG, r is closed. + * + * @param tryTree the original try tree (with 0 or more resources) from the AST + * @param p the value to pass to calls to {@code scan} + * @param resources the remaining resource declarations to handle + */ + private void handleTryResourcesAndBlock( + TryTree tryTree, Void p, List resources) { + if (resources.isEmpty()) { + // Either `tryTree` was not a try-with-resources, or this method was called + // recursively and all the resources have been handled. Just scan the main try block. + scan(tryTree.getBlock(), p); + return; + } + + // Handle the first resource declaration in the list. The rest will be handled by a + // recursive call. + Tree resourceDeclarationTree = resources.get(0); - addLabelForNextNode(doneLabel); - - return null; - } - - /** - * A recursive helper method to handle the resource declarations (if any) in a {@link TryTree} and - * its main block. If the {@code resources} list is empty, the method scans the main block of the - * try statement and returns. Otherwise, the first resource declaration in {@code resources} is - * desugared, following the logic in JLS 14.20.3.1. A resource declaration r is desugared - * by adding the nodes for r itself to the CFG, followed by a synthetic nested {@code try} - * block and {@code finally} block. The synthetic {@code try} block contains any remaining - * resource declarations and the original try block (handled via recursion). The synthetic {@code - * finally} block contains a call to {@code close} for r, guaranteeing that on every path - * through the CFG, r is closed. - * - * @param tryTree the original try tree (with 0 or more resources) from the AST - * @param p the value to pass to calls to {@code scan} - * @param resources the remaining resource declarations to handle - */ - private void handleTryResourcesAndBlock(TryTree tryTree, Void p, List resources) { - if (resources.isEmpty()) { - // Either `tryTree` was not a try-with-resources, or this method was called - // recursively and all the resources have been handled. Just scan the main try block. - scan(tryTree.getBlock(), p); - return; + extendWithNode( + new MarkerNode( + resourceDeclarationTree, + "start of try for resource #" + + TreeUtils.treeUids.get(resourceDeclarationTree), + env.getTypeUtils())); + + // Store return/break/continue labels. Generating a synthetic finally block for closing the + // resource requires creating fresh return/break/continue labels and then restoring the old + // labels afterward. + LabelCell oldReturnTargetLC = returnTargetLC; + LabelCell oldBreakTargetLC = breakTargetLC; + Map oldBreakLabels = breakLabels; + LabelCell oldContinueTargetLC = continueTargetLC; + Map oldContinueLabels = continueLabels; + + // Add nodes for the resource declaration to the CFG. NOTE: it is critical to add these + // nodes *before* pushing a TryFinallyFrame for the finally block that will close the + // resource. If any exception occurs due to code within the resource declaration, the + // corresponding variable or field is *not* automatically closed (as it was never + // assigned a value). + Node resourceCloseNode = scan(resourceDeclarationTree, p); + + // Now, set things up for our synthetic finally block that closes the resource. + Label doneLabel = new Label(); + Label finallyLabel = new Label(); + + Label exceptionalFinallyLabel = new Label(); + tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); + + returnTargetLC = new LabelCell(); + + breakTargetLC = new LabelCell(); + breakLabels = new TryFinallyScopeMap(); + + continueTargetLC = new LabelCell(); + continueLabels = new TryFinallyScopeMap(); + + extendWithNode( + new MarkerNode( + resourceDeclarationTree, + "start of try block for resource #" + + TreeUtils.treeUids.get(resourceDeclarationTree), + env.getTypeUtils())); + // Recursively handle any remaining resource declarations and the main block of the try + handleTryResourcesAndBlock(tryTree, p, resources.subList(1, resources.size())); + extendWithNode( + new MarkerNode( + resourceDeclarationTree, + "end of try block for resource #" + + TreeUtils.treeUids.get(resourceDeclarationTree), + env.getTypeUtils())); + + extendWithExtendedNode(new UnconditionalJump(finallyLabel)); + + // Generate the finally block that closes the resource + handleFinally( + resourceDeclarationTree, + doneLabel, + finallyLabel, + exceptionalFinallyLabel, + () -> addCloseCallForResource(resourceDeclarationTree, resourceCloseNode), + oldReturnTargetLC, + oldBreakTargetLC, + oldBreakLabels, + oldContinueTargetLC, + oldContinueLabels); + + addLabelForNextNode(doneLabel); } - // Handle the first resource declaration in the list. The rest will be handled by a - // recursive call. - Tree resourceDeclarationTree = resources.get(0); - - extendWithNode( - new MarkerNode( - resourceDeclarationTree, - "start of try for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), - env.getTypeUtils())); - - // Store return/break/continue labels. Generating a synthetic finally block for closing the - // resource requires creating fresh return/break/continue labels and then restoring the old - // labels afterward. - LabelCell oldReturnTargetLC = returnTargetLC; - LabelCell oldBreakTargetLC = breakTargetLC; - Map oldBreakLabels = breakLabels; - LabelCell oldContinueTargetLC = continueTargetLC; - Map oldContinueLabels = continueLabels; - - // Add nodes for the resource declaration to the CFG. NOTE: it is critical to add these - // nodes *before* pushing a TryFinallyFrame for the finally block that will close the - // resource. If any exception occurs due to code within the resource declaration, the - // corresponding variable or field is *not* automatically closed (as it was never - // assigned a value). - Node resourceCloseNode = scan(resourceDeclarationTree, p); - - // Now, set things up for our synthetic finally block that closes the resource. - Label doneLabel = new Label(); - Label finallyLabel = new Label(); - - Label exceptionalFinallyLabel = new Label(); - tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); - - returnTargetLC = new LabelCell(); - - breakTargetLC = new LabelCell(); - breakLabels = new TryFinallyScopeMap(); - - continueTargetLC = new LabelCell(); - continueLabels = new TryFinallyScopeMap(); - - extendWithNode( - new MarkerNode( - resourceDeclarationTree, - "start of try block for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), - env.getTypeUtils())); - // Recursively handle any remaining resource declarations and the main block of the try - handleTryResourcesAndBlock(tryTree, p, resources.subList(1, resources.size())); - extendWithNode( - new MarkerNode( - resourceDeclarationTree, - "end of try block for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), - env.getTypeUtils())); - - extendWithExtendedNode(new UnconditionalJump(finallyLabel)); - - // Generate the finally block that closes the resource - handleFinally( - resourceDeclarationTree, - doneLabel, - finallyLabel, - exceptionalFinallyLabel, - () -> addCloseCallForResource(resourceDeclarationTree, resourceCloseNode), - oldReturnTargetLC, - oldBreakTargetLC, - oldBreakLabels, - oldContinueTargetLC, - oldContinueLabels); - - addLabelForNextNode(doneLabel); - } - - /** - * Adds a synthetic {@code close} call to the CFG to close some resource variable declared or used - * in a try-with-resources. - * - * @param resourceDeclarationTree the resource declaration - * @param resourceToCloseNode node represented the variable or field on which {@code close} should - * be invoked - */ - private void addCloseCallForResource(Tree resourceDeclarationTree, Node resourceToCloseNode) { - Tree receiverTree = resourceDeclarationTree; - if (receiverTree instanceof VariableTree) { - receiverTree = treeBuilder.buildVariableUse((VariableTree) receiverTree); - handleArtificialTree(receiverTree); + /** + * Adds a synthetic {@code close} call to the CFG to close some resource variable declared or + * used in a try-with-resources. + * + * @param resourceDeclarationTree the resource declaration + * @param resourceToCloseNode node represented the variable or field on which {@code close} + * should be invoked + */ + private void addCloseCallForResource(Tree resourceDeclarationTree, Node resourceToCloseNode) { + Tree receiverTree = resourceDeclarationTree; + if (receiverTree instanceof VariableTree) { + receiverTree = treeBuilder.buildVariableUse((VariableTree) receiverTree); + handleArtificialTree(receiverTree); + } + + MemberSelectTree closeSelect = + treeBuilder.buildCloseMethodAccess((ExpressionTree) receiverTree); + handleArtificialTree(closeSelect); + + MethodInvocationTree closeCall = treeBuilder.buildMethodInvocation(closeSelect); + handleArtificialTree(closeCall); + + Node receiverNode = resourceToCloseNode; + if (receiverNode instanceof AssignmentNode) { + // variable declaration; use the LHS + receiverNode = ((AssignmentNode) resourceToCloseNode).getTarget(); + } + // TODO do we need to insert some kind of node representing a use of receiverNode + // (which can be either a LocalVariableNode or a FieldAccessNode)? + MethodAccessNode closeAccessNode = new MethodAccessNode(closeSelect, receiverNode); + closeAccessNode.setInSource(false); + extendWithNode(closeAccessNode); + MethodInvocationNode closeCallNode = + new MethodInvocationNode( + closeCall, closeAccessNode, Collections.emptyList(), getCurrentPath()); + closeCallNode.setInSource(false); + extendWithMethodInvocationNode(TreeUtils.elementFromUse(closeCall), closeCallNode); } - MemberSelectTree closeSelect = - treeBuilder.buildCloseMethodAccess((ExpressionTree) receiverTree); - handleArtificialTree(closeSelect); + /** + * Shared logic for CFG generation for a finally block. The block may correspond to a {@link + * TryTree} originally in the source code, or it may be a synthetic finally block used to model + * closing of a resource due to try-with-resources. + * + * @param markerTree tree to reference when creating {@link MarkerNode}s for the finally block + * @param doneLabel label for the normal successor of the try block (no exceptions, returns, + * breaks, or continues) + * @param finallyLabel label for the entry of the finally block for the normal case + * @param exceptionalFinallyLabel label for entry of the finally block for when the try block + * throws an exception + * @param finallyBlockCFGGenerator generates CFG nodes and edges for the finally block + * @param oldReturnTargetLC old return target label cell, which gets restored to {@link + * #returnTargetLC} while handling the finally block + * @param oldBreakTargetLC old break target label cell, which gets restored to {@link + * #breakTargetLC} while handling the finally block + * @param oldBreakLabels old break labels, which get restored to {@link #breakLabels} while + * handling the finally block + * @param oldContinueTargetLC old continue target label cell, which gets restored to {@link + * #continueTargetLC} while handling the finally block + * @param oldContinueLabels old continue labels, which get restored to {@link #continueLabels} + * while handling the finally block + */ + private void handleFinally( + Tree markerTree, + Label doneLabel, + Label finallyLabel, + Label exceptionalFinallyLabel, + Runnable finallyBlockCFGGenerator, + LabelCell oldReturnTargetLC, + LabelCell oldBreakTargetLC, + Map oldBreakLabels, + LabelCell oldContinueTargetLC, + Map oldContinueLabels) { + // Reset values before analyzing the finally block! + + tryStack.popFrame(); + + { // Scan 'finallyBlock' for only 'finallyLabel' (a successful path) + addLabelForNextNode(finallyLabel); + extendWithNode( + new MarkerNode( + markerTree, + "start of finally block #" + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + finallyBlockCFGGenerator.run(); + extendWithNode( + new MarkerNode( + markerTree, + "end of finally block #" + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + extendWithExtendedNode(new UnconditionalJump(doneLabel)); + } + + if (hasExceptionalPath(exceptionalFinallyLabel)) { + // If an exceptional path exists, scan 'finallyBlock' for 'exceptionalFinallyLabel', + // and scan copied 'finallyBlock' for 'finallyLabel' (a successful path). If there + // is no successful path, it will be removed in later phase. + // TODO: Don't we need a separate finally block for each kind of exception? + addLabelForNextNode(exceptionalFinallyLabel); + extendWithNode( + new MarkerNode( + markerTree, + "start of finally block for Throwable #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + + finallyBlockCFGGenerator.run(); + + NodeWithExceptionsHolder throwing = + extendWithNodeWithException( + new MarkerNode( + markerTree, + "end of finally block for Throwable #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils()), + throwableType); + + throwing.setTerminatesExecution(true); + } + + if (returnTargetLC.wasAccessed()) { + addLabelForNextNode(returnTargetLC.peekLabel()); + returnTargetLC = oldReturnTargetLC; + + extendWithNode( + new MarkerNode( + markerTree, + "start of finally block for return #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + finallyBlockCFGGenerator.run(); + extendWithNode( + new MarkerNode( + markerTree, + "end of finally block for return #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + extendWithExtendedNode(new UnconditionalJump(returnTargetLC.accessLabel())); + } else { + returnTargetLC = oldReturnTargetLC; + } + + if (breakTargetLC.wasAccessed()) { + addLabelForNextNode(breakTargetLC.peekLabel()); + breakTargetLC = oldBreakTargetLC; + + extendWithNode( + new MarkerNode( + markerTree, + "start of finally block for break #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + finallyBlockCFGGenerator.run(); + extendWithNode( + new MarkerNode( + markerTree, + "end of finally block for break #" + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); + } else { + breakTargetLC = oldBreakTargetLC; + } + + Map accessedBreakLabels = + ((TryFinallyScopeMap) breakLabels).getAccessedNames(); + if (!accessedBreakLabels.isEmpty()) { + breakLabels = oldBreakLabels; + + for (Map.Entry access : accessedBreakLabels.entrySet()) { + addLabelForNextNode(access.getValue()); + extendWithNode( + new MarkerNode( + markerTree, + "start of finally block for break label " + + access.getKey() + + " #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + finallyBlockCFGGenerator.run(); + extendWithNode( + new MarkerNode( + markerTree, + "end of finally block for break label " + + access.getKey() + + " #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + extendWithExtendedNode(new UnconditionalJump(breakLabels.get(access.getKey()))); + } + } else { + breakLabels = oldBreakLabels; + } - MethodInvocationTree closeCall = treeBuilder.buildMethodInvocation(closeSelect); - handleArtificialTree(closeCall); + if (continueTargetLC.wasAccessed()) { + addLabelForNextNode(continueTargetLC.peekLabel()); + continueTargetLC = oldContinueTargetLC; + + extendWithNode( + new MarkerNode( + markerTree, + "start of finally block for continue #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + finallyBlockCFGGenerator.run(); + extendWithNode( + new MarkerNode( + markerTree, + "end of finally block for continue #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); + } else { + continueTargetLC = oldContinueTargetLC; + } - Node receiverNode = resourceToCloseNode; - if (receiverNode instanceof AssignmentNode) { - // variable declaration; use the LHS - receiverNode = ((AssignmentNode) resourceToCloseNode).getTarget(); + Map accessedContinueLabels = + ((TryFinallyScopeMap) continueLabels).getAccessedNames(); + if (!accessedContinueLabels.isEmpty()) { + continueLabels = oldContinueLabels; + + for (Map.Entry access : accessedContinueLabels.entrySet()) { + addLabelForNextNode(access.getValue()); + extendWithNode( + new MarkerNode( + markerTree, + "start of finally block for continue label " + + access.getKey() + + " #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + finallyBlockCFGGenerator.run(); + extendWithNode( + new MarkerNode( + markerTree, + "end of finally block for continue label " + + access.getKey() + + " #" + + TreeUtils.treeUids.get(markerTree), + env.getTypeUtils())); + extendWithExtendedNode(new UnconditionalJump(continueLabels.get(access.getKey()))); + } + } else { + continueLabels = oldContinueLabels; + } } - // TODO do we need to insert some kind of node representing a use of receiverNode - // (which can be either a LocalVariableNode or a FieldAccessNode)? - MethodAccessNode closeAccessNode = new MethodAccessNode(closeSelect, receiverNode); - closeAccessNode.setInSource(false); - extendWithNode(closeAccessNode); - MethodInvocationNode closeCallNode = - new MethodInvocationNode( - closeCall, closeAccessNode, Collections.emptyList(), getCurrentPath()); - closeCallNode.setInSource(false); - extendWithMethodInvocationNode(TreeUtils.elementFromUse(closeCall), closeCallNode); - } - - /** - * Shared logic for CFG generation for a finally block. The block may correspond to a {@link - * TryTree} originally in the source code, or it may be a synthetic finally block used to model - * closing of a resource due to try-with-resources. - * - * @param markerTree tree to reference when creating {@link MarkerNode}s for the finally block - * @param doneLabel label for the normal successor of the try block (no exceptions, returns, - * breaks, or continues) - * @param finallyLabel label for the entry of the finally block for the normal case - * @param exceptionalFinallyLabel label for entry of the finally block for when the try block - * throws an exception - * @param finallyBlockCFGGenerator generates CFG nodes and edges for the finally block - * @param oldReturnTargetLC old return target label cell, which gets restored to {@link - * #returnTargetLC} while handling the finally block - * @param oldBreakTargetLC old break target label cell, which gets restored to {@link - * #breakTargetLC} while handling the finally block - * @param oldBreakLabels old break labels, which get restored to {@link #breakLabels} while - * handling the finally block - * @param oldContinueTargetLC old continue target label cell, which gets restored to {@link - * #continueTargetLC} while handling the finally block - * @param oldContinueLabels old continue labels, which get restored to {@link #continueLabels} - * while handling the finally block - */ - private void handleFinally( - Tree markerTree, - Label doneLabel, - Label finallyLabel, - Label exceptionalFinallyLabel, - Runnable finallyBlockCFGGenerator, - LabelCell oldReturnTargetLC, - LabelCell oldBreakTargetLC, - Map oldBreakLabels, - LabelCell oldContinueTargetLC, - Map oldContinueLabels) { - // Reset values before analyzing the finally block! - - tryStack.popFrame(); - - { // Scan 'finallyBlock' for only 'finallyLabel' (a successful path) - addLabelForNextNode(finallyLabel); - extendWithNode( - new MarkerNode( - markerTree, - "start of finally block #" + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - finallyBlockCFGGenerator.run(); - extendWithNode( - new MarkerNode( - markerTree, - "end of finally block #" + TreeUtils.treeUids.get(markerTree), - env.getTypeUtils())); - extendWithExtendedNode(new UnconditionalJump(doneLabel)); + + /** + * Returns whether an exceptional node for {@code target} exists in {@link #nodeList} or not. + * + * @param target label for exception + * @return true when an exceptional node for {@code target} exists in {@link #nodeList} + */ + private boolean hasExceptionalPath(Label target) { + for (ExtendedNode node : nodeList) { + if (node instanceof NodeWithExceptionsHolder) { + NodeWithExceptionsHolder exceptionalNode = (NodeWithExceptionsHolder) node; + for (Set

          This can be used to handle system types that are not present. For example, in Java code that - * is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are - * emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present. - * - * @param clazz a class, which must have a canonical name - * @return the TypeMirror for the class, or {@code null} if the type is not present - */ - protected @Nullable TypeMirror maybeGetTypeMirror(Class clazz) { - String name = clazz.getCanonicalName(); - assert name != null : clazz + " does not have a canonical name"; - TypeElement element = elements.getTypeElement(name); - if (element == null) { - return null; + /** + * Returns the TypeMirror for the given class, or {@code null} if the type is not present. + * + *

          This can be used to handle system types that are not present. For example, in Java code + * that is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are + * emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present. + * + * @param clazz a class, which must have a canonical name + * @return the TypeMirror for the class, or {@code null} if the type is not present + */ + protected @Nullable TypeMirror maybeGetTypeMirror(Class clazz) { + String name = clazz.getCanonicalName(); + assert name != null : clazz + " does not have a canonical name"; + TypeElement element = elements.getTypeElement(name); + if (element == null) { + return null; + } + return element.asType(); } - return element.asType(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java index 9d75d9e9742..8c8a8299417 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java @@ -1,10 +1,5 @@ package org.checkerframework.dataflow.cfg.builder; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import javax.lang.model.type.TypeMirror; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.block.Block; import org.checkerframework.dataflow.cfg.block.Block.BlockType; @@ -15,6 +10,13 @@ import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl; import org.checkerframework.javacutil.BugInCF; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + /** * Class that performs phase three of the translation process. In particular, the following * degenerate cases of basic blocks are removed: @@ -36,345 +38,347 @@ */ public class CFGTranslationPhaseThree { - /** A simple wrapper object that holds a basic block and allows to set one of its successors. */ - protected interface PredecessorHolder { - void setSuccessor(BlockImpl b); + /** A simple wrapper object that holds a basic block and allows to set one of its successors. */ + protected interface PredecessorHolder { + void setSuccessor(BlockImpl b); - BlockImpl getBlock(); - } + BlockImpl getBlock(); + } - /** - * Perform phase three on the control flow graph {@code cfg}. - * - * @param cfg the control flow graph. Ownership is transfered to this method and the caller is not - * allowed to read or modify {@code cfg} after the call to {@code process} any more. - * @return the resulting control flow graph - */ - @SuppressWarnings("nullness") // TODO: successors - public static ControlFlowGraph process(ControlFlowGraph cfg) { - Set worklist = cfg.getAllBlocks(); + /** + * Perform phase three on the control flow graph {@code cfg}. + * + * @param cfg the control flow graph. Ownership is transfered to this method and the caller is + * not allowed to read or modify {@code cfg} after the call to {@code process} any more. + * @return the resulting control flow graph + */ + @SuppressWarnings("nullness") // TODO: successors + public static ControlFlowGraph process(ControlFlowGraph cfg) { + Set worklist = cfg.getAllBlocks(); - // note: this method has to be careful when relinking basic blocks - // to not forget to adjust the predecessors, too + // note: this method has to be careful when relinking basic blocks + // to not forget to adjust the predecessors, too - // fix predecessor lists by removing any unreachable predecessors - for (Block c : worklist) { - BlockImpl cur = (BlockImpl) c; - for (Block pred : cur.getPredecessors()) { - if (!worklist.contains(pred)) { - cur.removePredecessor((BlockImpl) pred); + // fix predecessor lists by removing any unreachable predecessors + for (Block c : worklist) { + BlockImpl cur = (BlockImpl) c; + for (Block pred : cur.getPredecessors()) { + if (!worklist.contains(pred)) { + cur.removePredecessor((BlockImpl) pred); + } + } } - } - } - // remove empty blocks - Set dontVisit = new HashSet<>(); - for (Block cur : worklist) { - if (dontVisit.contains(cur)) { - continue; - } + // remove empty blocks + Set dontVisit = new HashSet<>(); + for (Block cur : worklist) { + if (dontVisit.contains(cur)) { + continue; + } - if (cur.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl b = (RegularBlockImpl) cur; - if (b.isEmpty()) { - Set emptyBlocks = new HashSet<>(); - Set predecessors = new LinkedHashSet<>(); - BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, emptyBlocks, predecessors); - for (RegularBlockImpl e : emptyBlocks) { - succ.removePredecessor(e); - dontVisit.add(e); - } - for (PredecessorHolder p : predecessors) { - BlockImpl block = p.getBlock(); - dontVisit.add(block); - succ.removePredecessor(block); - p.setSuccessor(succ); - } + if (cur.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl b = (RegularBlockImpl) cur; + if (b.isEmpty()) { + Set emptyBlocks = new HashSet<>(); + Set predecessors = new LinkedHashSet<>(); + BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, emptyBlocks, predecessors); + for (RegularBlockImpl e : emptyBlocks) { + succ.removePredecessor(e); + dontVisit.add(e); + } + for (PredecessorHolder p : predecessors) { + BlockImpl block = p.getBlock(); + dontVisit.add(block); + succ.removePredecessor(block); + p.setSuccessor(succ); + } + } + } } - } - } - // remove useless conditional blocks - /* Issue 3267 revealed that this is a dangerous optimization: - it merges a block that evaluates one condition onto an unrelated following block, - which can also be a condition. The then/else stores from the first block are still - set, leading to incorrect results for the then/else stores in the following block. - The correct result would be to merge the then/else stores from the previous block. - However, as this is late in the CFG construction, I didn't see how to add e.g. a - dummy variable declaration node in a dummy regular block, which would cause a merge. - So for now, let's not perform this optimization. - It would be interesting to know how large the impact of this optimization is. + // remove useless conditional blocks + /* Issue 3267 revealed that this is a dangerous optimization: + it merges a block that evaluates one condition onto an unrelated following block, + which can also be a condition. The then/else stores from the first block are still + set, leading to incorrect results for the then/else stores in the following block. + The correct result would be to merge the then/else stores from the previous block. + However, as this is late in the CFG construction, I didn't see how to add e.g. a + dummy variable declaration node in a dummy regular block, which would cause a merge. + So for now, let's not perform this optimization. + It would be interesting to know how large the impact of this optimization is. - worklist = cfg.getAllBlocks(); - for (Block c : worklist) { - BlockImpl cur = (BlockImpl) c; + worklist = cfg.getAllBlocks(); + for (Block c : worklist) { + BlockImpl cur = (BlockImpl) c; - if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { - ConditionalBlockImpl cb = (ConditionalBlockImpl) cur; - assert cb.getPredecessors().size() == 1; - if (cb.getThenSuccessor() == cb.getElseSuccessor()) { - BlockImpl pred = cb.getPredecessors().iterator().next(); - PredecessorHolder predecessorHolder = getPredecessorHolder(pred, cb); - BlockImpl succ = (BlockImpl) cb.getThenSuccessor(); - succ.removePredecessor(cb); - predecessorHolder.setSuccessor(succ); + if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { + ConditionalBlockImpl cb = (ConditionalBlockImpl) cur; + assert cb.getPredecessors().size() == 1; + if (cb.getThenSuccessor() == cb.getElseSuccessor()) { + BlockImpl pred = cb.getPredecessors().iterator().next(); + PredecessorHolder predecessorHolder = getPredecessorHolder(pred, cb); + BlockImpl succ = (BlockImpl) cb.getThenSuccessor(); + succ.removePredecessor(cb); + predecessorHolder.setSuccessor(succ); + } } } - } - */ - - mergeConsecutiveBlocks(cfg); - return cfg; - } + */ - /** - * Simplify the CFG by merging consecutive single-successor blocks. - * - * @param cfg the control flow graph - */ - @SuppressWarnings({ - "interning:not.interned", // CFG node comparisons - "nullness" // TODO: successors - }) - protected static void mergeConsecutiveBlocks(ControlFlowGraph cfg) { - Set worklist = cfg.getAllBlocks(); + mergeConsecutiveBlocks(cfg); + return cfg; + } - // This transformation removes blocks from the CFG. If those blocks appear in `worklist` - // then we might visit a block AFTER it has been removed and its nodes have been moved - // somewhere else. When this happens the correct behavior is to just skip the removed - // block; to do so, we need to remember which blocks have been removed. - Set removedBlocks = new HashSet<>(); + /** + * Simplify the CFG by merging consecutive single-successor blocks. + * + * @param cfg the control flow graph + */ + @SuppressWarnings({ + "interning:not.interned", // CFG node comparisons + "nullness" // TODO: successors + }) + protected static void mergeConsecutiveBlocks(ControlFlowGraph cfg) { + Set worklist = cfg.getAllBlocks(); - for (Block cur : worklist) { - // Skip this block if it was already merged into another. - if (removedBlocks.contains(cur)) { - continue; - } + // This transformation removes blocks from the CFG. If those blocks appear in `worklist` + // then we might visit a block AFTER it has been removed and its nodes have been moved + // somewhere else. When this happens the correct behavior is to just skip the removed + // block; to do so, we need to remember which blocks have been removed. + Set removedBlocks = new HashSet<>(); - // There may be many blocks to merge in series. - // - // ... \ /> ... - // ... --> cur -> b2 -> b3 -> ... - // ... / \> ... - // - // This loop merges the successor into `cur` until it can't do so anymore. - boolean didMerge; - do { - didMerge = false; - if (cur.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl b = (RegularBlockImpl) cur; - Block succ = b.getRegularSuccessor(); - if (succ.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl rs = (RegularBlockImpl) succ; - if (rs.getRegularSuccessor() == rs) { - // Do not attempt to merge a block with a self edge (which would - // infinite-loop if it were run), as it leads to non-termination - // in the merging algorithm. - break; - } - if (rs.getPredecessors().size() == 1) { - b.setSuccessor(rs.getRegularSuccessor()); - b.addNodes(rs.getNodes()); - rs.getRegularSuccessor().removePredecessor(rs); - removedBlocks.add(rs); - didMerge = true; + for (Block cur : worklist) { + // Skip this block if it was already merged into another. + if (removedBlocks.contains(cur)) { + continue; } - } + + // There may be many blocks to merge in series. + // + // ... \ /> ... + // ... --> cur -> b2 -> b3 -> ... + // ... / \> ... + // + // This loop merges the successor into `cur` until it can't do so anymore. + boolean didMerge; + do { + didMerge = false; + if (cur.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl b = (RegularBlockImpl) cur; + Block succ = b.getRegularSuccessor(); + if (succ.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl rs = (RegularBlockImpl) succ; + if (rs.getRegularSuccessor() == rs) { + // Do not attempt to merge a block with a self edge (which would + // infinite-loop if it were run), as it leads to non-termination + // in the merging algorithm. + break; + } + if (rs.getPredecessors().size() == 1) { + b.setSuccessor(rs.getRegularSuccessor()); + b.addNodes(rs.getNodes()); + rs.getRegularSuccessor().removePredecessor(rs); + removedBlocks.add(rs); + didMerge = true; + } + } + } + } while (didMerge); } - } while (didMerge); } - } - /** - * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} - * and going both forward and backwards. Furthermore, compute the predecessors of these empty - * blocks ({@code predecessors} ), and their single successor (return value). - * - * @param start the starting point of the search (an empty, regular basic block) - * @param emptyBlocks a set to be filled by this method with all empty basic blocks found - * (including {@code start}). - * @param predecessors a set to be filled by this method with all predecessors - * @return the single successor of the set of the empty basic blocks - */ - @SuppressWarnings({ - "interning:not.interned", // CFG node comparisons - "nullness" // successors - }) - protected static BlockImpl computeNeighborhoodOfEmptyBlock( - RegularBlockImpl start, - Set emptyBlocks, - Set predecessors) { + /** + * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} + * and going both forward and backwards. Furthermore, compute the predecessors of these empty + * blocks ({@code predecessors} ), and their single successor (return value). + * + * @param start the starting point of the search (an empty, regular basic block) + * @param emptyBlocks a set to be filled by this method with all empty basic blocks found + * (including {@code start}). + * @param predecessors a set to be filled by this method with all predecessors + * @return the single successor of the set of the empty basic blocks + */ + @SuppressWarnings({ + "interning:not.interned", // CFG node comparisons + "nullness" // successors + }) + protected static BlockImpl computeNeighborhoodOfEmptyBlock( + RegularBlockImpl start, + Set emptyBlocks, + Set predecessors) { - // get empty neighborhood that come before 'start' - computeNeighborhoodOfEmptyBlockBackwards(start, emptyBlocks, predecessors); + // get empty neighborhood that come before 'start' + computeNeighborhoodOfEmptyBlockBackwards(start, emptyBlocks, predecessors); - // go forward - BlockImpl succ = (BlockImpl) start.getSuccessor(); - while (succ.getType() == BlockType.REGULAR_BLOCK) { - RegularBlockImpl cur = (RegularBlockImpl) succ; - if (cur.isEmpty()) { - computeNeighborhoodOfEmptyBlockBackwards(cur, emptyBlocks, predecessors); - assert emptyBlocks.contains(cur) : "cur ought to be in emptyBlocks"; - succ = (BlockImpl) cur.getSuccessor(); - if (succ == cur) { - // An infinite loop, making exit block unreachable - break; + // go forward + BlockImpl succ = (BlockImpl) start.getSuccessor(); + while (succ.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl cur = (RegularBlockImpl) succ; + if (cur.isEmpty()) { + computeNeighborhoodOfEmptyBlockBackwards(cur, emptyBlocks, predecessors); + assert emptyBlocks.contains(cur) : "cur ought to be in emptyBlocks"; + succ = (BlockImpl) cur.getSuccessor(); + if (succ == cur) { + // An infinite loop, making exit block unreachable + break; + } + } else { + break; + } } - } else { - break; - } + return succ; } - return succ; - } - /** - * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} - * and looking only backwards in the control flow graph. Furthermore, compute the predecessors of - * these empty blocks ({@code predecessors}). - * - * @param start the starting point of the search (an empty, regular basic block) - * @param emptyBlocks a set to be filled by this method with all empty basic blocks found - * (including {@code start}). - * @param predecessors a set to be filled by this method with all predecessors - */ - protected static void computeNeighborhoodOfEmptyBlockBackwards( - RegularBlockImpl start, - Set emptyBlocks, - Set predecessors) { + /** + * Compute the set of empty regular basic blocks {@code emptyBlocks}, starting at {@code start} + * and looking only backwards in the control flow graph. Furthermore, compute the predecessors + * of these empty blocks ({@code predecessors}). + * + * @param start the starting point of the search (an empty, regular basic block) + * @param emptyBlocks a set to be filled by this method with all empty basic blocks found + * (including {@code start}). + * @param predecessors a set to be filled by this method with all predecessors + */ + protected static void computeNeighborhoodOfEmptyBlockBackwards( + RegularBlockImpl start, + Set emptyBlocks, + Set predecessors) { - RegularBlockImpl cur = start; - emptyBlocks.add(cur); - for (Block p : cur.getPredecessors()) { - BlockImpl pred = (BlockImpl) p; - switch (pred.getType()) { - case SPECIAL_BLOCK: - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - break; - case CONDITIONAL_BLOCK: - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - break; - case EXCEPTION_BLOCK: - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - break; - case REGULAR_BLOCK: - RegularBlockImpl r = (RegularBlockImpl) pred; - if (r.isEmpty()) { - // recursively look backwards - if (!emptyBlocks.contains(r)) { - computeNeighborhoodOfEmptyBlockBackwards(r, emptyBlocks, predecessors); + RegularBlockImpl cur = start; + emptyBlocks.add(cur); + for (Block p : cur.getPredecessors()) { + BlockImpl pred = (BlockImpl) p; + switch (pred.getType()) { + case SPECIAL_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case CONDITIONAL_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case EXCEPTION_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case REGULAR_BLOCK: + RegularBlockImpl r = (RegularBlockImpl) pred; + if (r.isEmpty()) { + // recursively look backwards + if (!emptyBlocks.contains(r)) { + computeNeighborhoodOfEmptyBlockBackwards(r, emptyBlocks, predecessors); + } + } else { + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + } + break; } - } else { - // add pred correctly to predecessor list - predecessors.add(getPredecessorHolder(pred, cur)); - } - break; - } + } } - } - /** - * Return a predecessor holder that can be used to set the successor of {@code pred} in the place - * where previously the edge pointed to {@code cur}. Additionally, the predecessor holder also - * takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} predecessors). - * - * @param pred a block whose successor should be set - * @param cur the previous successor of {@code pred} - * @return a predecessor holder to set the successor of {@code pred} - */ - @SuppressWarnings("interning:not.interned") // AST node comparisons - protected static PredecessorHolder getPredecessorHolder(BlockImpl pred, BlockImpl cur) { - switch (pred.getType()) { - case SPECIAL_BLOCK: - SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred; - return singleSuccessorHolder(s, cur); - case CONDITIONAL_BLOCK: - // add pred correctly to predecessor list - ConditionalBlockImpl c = (ConditionalBlockImpl) pred; - if (c.getThenSuccessor() == cur) { - return new PredecessorHolder() { - @Override - public void setSuccessor(BlockImpl b) { - c.setThenSuccessor(b); - cur.removePredecessor(pred); - } + /** + * Return a predecessor holder that can be used to set the successor of {@code pred} in the + * place where previously the edge pointed to {@code cur}. Additionally, the predecessor holder + * also takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} + * predecessors). + * + * @param pred a block whose successor should be set + * @param cur the previous successor of {@code pred} + * @return a predecessor holder to set the successor of {@code pred} + */ + @SuppressWarnings("interning:not.interned") // AST node comparisons + protected static PredecessorHolder getPredecessorHolder(BlockImpl pred, BlockImpl cur) { + switch (pred.getType()) { + case SPECIAL_BLOCK: + SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred; + return singleSuccessorHolder(s, cur); + case CONDITIONAL_BLOCK: + // add pred correctly to predecessor list + ConditionalBlockImpl c = (ConditionalBlockImpl) pred; + if (c.getThenSuccessor() == cur) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + c.setThenSuccessor(b); + cur.removePredecessor(pred); + } - @Override - public BlockImpl getBlock() { - return c; - } - }; - } else { - assert c.getElseSuccessor() == cur; - return new PredecessorHolder() { - @Override - public void setSuccessor(BlockImpl b) { - c.setElseSuccessor(b); - cur.removePredecessor(pred); - } + @Override + public BlockImpl getBlock() { + return c; + } + }; + } else { + assert c.getElseSuccessor() == cur; + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + c.setElseSuccessor(b); + cur.removePredecessor(pred); + } - @Override - public BlockImpl getBlock() { - return c; - } - }; - } - case EXCEPTION_BLOCK: - // add pred correctly to predecessor list - ExceptionBlockImpl e = (ExceptionBlockImpl) pred; - if (e.getSuccessor() == cur) { - return singleSuccessorHolder(e, cur); - } else { - @SuppressWarnings("keyfor:assignment.type.incompatible") // ignore keyfor type - Set>> entrySet = e.getExceptionalSuccessors().entrySet(); - for (Map.Entry> entry : entrySet) { - if (entry.getValue().contains(cur)) { - return new PredecessorHolder() { - @Override - public void setSuccessor(BlockImpl b) { - e.addExceptionalSuccessor(b, entry.getKey()); - cur.removePredecessor(pred); + @Override + public BlockImpl getBlock() { + return c; + } + }; } + case EXCEPTION_BLOCK: + // add pred correctly to predecessor list + ExceptionBlockImpl e = (ExceptionBlockImpl) pred; + if (e.getSuccessor() == cur) { + return singleSuccessorHolder(e, cur); + } else { + @SuppressWarnings("keyfor:assignment.type.incompatible") // ignore keyfor type + Set>> entrySet = + e.getExceptionalSuccessors().entrySet(); + for (Map.Entry> entry : entrySet) { + if (entry.getValue().contains(cur)) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + e.addExceptionalSuccessor(b, entry.getKey()); + cur.removePredecessor(pred); + } - @Override - public BlockImpl getBlock() { - return e; + @Override + public BlockImpl getBlock() { + return e; + } + }; + } + } } - }; - } - } + throw new BugInCF("Unreachable"); + case REGULAR_BLOCK: + RegularBlockImpl r = (RegularBlockImpl) pred; + return singleSuccessorHolder(r, cur); + default: + throw new BugInCF("Unexpected block type " + pred.getType()); } - throw new BugInCF("Unreachable"); - case REGULAR_BLOCK: - RegularBlockImpl r = (RegularBlockImpl) pred; - return singleSuccessorHolder(r, cur); - default: - throw new BugInCF("Unexpected block type " + pred.getType()); } - } - /** - * Returns a {@link PredecessorHolder} that sets the successor of a single successor block {@code - * s}. - * - * @return a {@link PredecessorHolder} that sets the successor of a single successor block {@code - * s} - */ - protected static PredecessorHolder singleSuccessorHolder( - SingleSuccessorBlockImpl s, BlockImpl old) { - return new PredecessorHolder() { - @Override - public void setSuccessor(BlockImpl b) { - s.setSuccessor(b); - old.removePredecessor(s); - } + /** + * Returns a {@link PredecessorHolder} that sets the successor of a single successor block + * {@code s}. + * + * @return a {@link PredecessorHolder} that sets the successor of a single successor block + * {@code s} + */ + protected static PredecessorHolder singleSuccessorHolder( + SingleSuccessorBlockImpl s, BlockImpl old) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + s.setSuccessor(b); + old.removePredecessor(s); + } - @Override - public BlockImpl getBlock() { - return s; - } - }; - } + @Override + public BlockImpl getBlock() { + return s; + } + }; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java index faba847b1d5..a1818b19101 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseTwo.java @@ -1,10 +1,5 @@ package org.checkerframework.dataflow.cfg.builder; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.block.BlockImpl; @@ -19,215 +14,223 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.ArraySet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + /** Class that performs phase two of the translation process. */ @SuppressWarnings("nullness") // TODO public class CFGTranslationPhaseTwo { - private CFGTranslationPhaseTwo() {} - - /** - * Perform phase two of the translation. - * - * @param in the result of phase one - * @return a control flow graph that might still contain degenerate basic blocks (such as empty - * regular basic blocks or conditional blocks with the same block as 'then' and 'else' - * successor) - */ - @SuppressWarnings("interning:not.interned") // AST node comparisons - public static ControlFlowGraph process(PhaseOneResult in) { - - Map bindings = in.bindings; - List nodeList = in.nodeList; - // A leader is an extended node which will give rise to a basic block in phase two. - Set leaders = in.leaders; - - assert !in.nodeList.isEmpty(); - - // exit blocks - SpecialBlockImpl regularExitBlock = new SpecialBlockImpl(SpecialBlockType.EXIT); - SpecialBlockImpl exceptionalExitBlock = new SpecialBlockImpl(SpecialBlockType.EXCEPTIONAL_EXIT); - - // record missing edges that will be added later - Set missingEdges = new ArraySet<>(1); - - // missing exceptional edges - Set missingExceptionalEdges = new LinkedHashSet<>(); - - // create start block - SpecialBlockImpl startBlock = new SpecialBlockImpl(SpecialBlockType.ENTRY); - missingEdges.add(new MissingEdge(startBlock, 0)); - - // Loop through all 'leaders' (while dynamically detecting the leaders). - @NonNull RegularBlockImpl block = new RegularBlockImpl(); // block being processed/built - int i = 0; - for (ExtendedNode node : nodeList) { - switch (node.getType()) { - case NODE: - if (leaders.contains(i)) { - RegularBlockImpl b = new RegularBlockImpl(); - block.setSuccessor(b); - block = b; - } - block.addNode(node.getNode()); - node.setBlock(block); - - // does this node end the execution (modeled as an edge to - // the exceptional exit block) - boolean terminatesExecution = node.getTerminatesExecution(); - if (terminatesExecution) { - block.setSuccessor(exceptionalExitBlock); - block = new RegularBlockImpl(); - } - break; - case CONDITIONAL_JUMP: - { - ConditionalJump cj = (ConditionalJump) node; - // Exception nodes may fall through to conditional jumps, so we set the - // block which is required for the insertion of missing edges. - node.setBlock(block); - assert block != null; - ConditionalBlockImpl cb = new ConditionalBlockImpl(); - if (cj.getTrueFlowRule() != null) { - cb.setThenFlowRule(cj.getTrueFlowRule()); + private CFGTranslationPhaseTwo() {} + + /** + * Perform phase two of the translation. + * + * @param in the result of phase one + * @return a control flow graph that might still contain degenerate basic blocks (such as empty + * regular basic blocks or conditional blocks with the same block as 'then' and 'else' + * successor) + */ + @SuppressWarnings("interning:not.interned") // AST node comparisons + public static ControlFlowGraph process(PhaseOneResult in) { + + Map bindings = in.bindings; + List nodeList = in.nodeList; + // A leader is an extended node which will give rise to a basic block in phase two. + Set leaders = in.leaders; + + assert !in.nodeList.isEmpty(); + + // exit blocks + SpecialBlockImpl regularExitBlock = new SpecialBlockImpl(SpecialBlockType.EXIT); + SpecialBlockImpl exceptionalExitBlock = + new SpecialBlockImpl(SpecialBlockType.EXCEPTIONAL_EXIT); + + // record missing edges that will be added later + Set missingEdges = new ArraySet<>(1); + + // missing exceptional edges + Set missingExceptionalEdges = new LinkedHashSet<>(); + + // create start block + SpecialBlockImpl startBlock = new SpecialBlockImpl(SpecialBlockType.ENTRY); + missingEdges.add(new MissingEdge(startBlock, 0)); + + // Loop through all 'leaders' (while dynamically detecting the leaders). + @NonNull RegularBlockImpl block = new RegularBlockImpl(); // block being processed/built + int i = 0; + for (ExtendedNode node : nodeList) { + switch (node.getType()) { + case NODE: + if (leaders.contains(i)) { + RegularBlockImpl b = new RegularBlockImpl(); + block.setSuccessor(b); + block = b; + } + block.addNode(node.getNode()); + node.setBlock(block); + + // does this node end the execution (modeled as an edge to + // the exceptional exit block) + boolean terminatesExecution = node.getTerminatesExecution(); + if (terminatesExecution) { + block.setSuccessor(exceptionalExitBlock); + block = new RegularBlockImpl(); + } + break; + case CONDITIONAL_JUMP: + { + ConditionalJump cj = (ConditionalJump) node; + // Exception nodes may fall through to conditional jumps, so we set the + // block which is required for the insertion of missing edges. + node.setBlock(block); + assert block != null; + ConditionalBlockImpl cb = new ConditionalBlockImpl(); + if (cj.getTrueFlowRule() != null) { + cb.setThenFlowRule(cj.getTrueFlowRule()); + } + if (cj.getFalseFlowRule() != null) { + cb.setElseFlowRule(cj.getFalseFlowRule()); + } + block.setSuccessor(cb); + block = new RegularBlockImpl(); + + // use two anonymous SingleSuccessorBlockImpl that set the + // 'then' and 'else' successor of the conditional block + Label thenLabel = cj.getThenLabel(); + Label elseLabel = cj.getElseLabel(); + Integer target = bindings.get(thenLabel); + assert target != null; + missingEdges.add( + new MissingEdge( + new RegularBlockImpl() { + @Override + public void setSuccessor(BlockImpl successor) { + cb.setThenSuccessor(successor); + } + }, + target)); + target = bindings.get(elseLabel); + if (target == null) { + throw new BugInCF( + String.format( + "in conditional jump %s, no binding for elseLabel %s: %s", + cj, elseLabel, bindings)); + } + missingEdges.add( + new MissingEdge( + new RegularBlockImpl() { + @Override + public void setSuccessor(BlockImpl successor) { + cb.setElseSuccessor(successor); + } + }, + target)); + break; + } + case UNCONDITIONAL_JUMP: + UnconditionalJump uj = (UnconditionalJump) node; + if (leaders.contains(i)) { + RegularBlockImpl b = new RegularBlockImpl(); + block.setSuccessor(b); + block = b; + } + node.setBlock(block); + if (node.getLabel() == in.regularExitLabel) { + block.setSuccessor(regularExitBlock); + block.setFlowRule(uj.getFlowRule()); + } else if (node.getLabel() == in.exceptionalExitLabel) { + block.setSuccessor(exceptionalExitBlock); + block.setFlowRule(uj.getFlowRule()); + } else { + int target = bindings.get(node.getLabel()); + missingEdges.add(new MissingEdge(block, target, uj.getFlowRule())); + } + block = new RegularBlockImpl(); + break; + case EXCEPTION_NODE: + NodeWithExceptionsHolder en = (NodeWithExceptionsHolder) node; + // create new exception block and link with previous block + ExceptionBlockImpl e = new ExceptionBlockImpl(); + Node nn = en.getNode(); + e.setNode(nn); + node.setBlock(e); + block.setSuccessor(e); + block = new RegularBlockImpl(); + + // Ensure linking between e and next block (normal edge). + // Note: do not link to the next block for throw statements (these throw + // exceptions for sure). + if (!node.getTerminatesExecution()) { + missingEdges.add(new MissingEdge(e, i + 1)); + } + + // exceptional edges + for (Map.Entry> entry : en.getExceptions().entrySet()) { + TypeMirror cause = entry.getKey(); + for (Label label : entry.getValue()) { + Integer target = bindings.get(label); + // TODO: `target` is sometimes null; is this a problem? + // assert target != null; + missingExceptionalEdges.add(new MissingEdge(e, target, cause)); + } + } + break; } - if (cj.getFalseFlowRule() != null) { - cb.setElseFlowRule(cj.getFalseFlowRule()); - } - block.setSuccessor(cb); - block = new RegularBlockImpl(); - - // use two anonymous SingleSuccessorBlockImpl that set the - // 'then' and 'else' successor of the conditional block - Label thenLabel = cj.getThenLabel(); - Label elseLabel = cj.getElseLabel(); - Integer target = bindings.get(thenLabel); - assert target != null; - missingEdges.add( - new MissingEdge( - new RegularBlockImpl() { - @Override - public void setSuccessor(BlockImpl successor) { - cb.setThenSuccessor(successor); - } - }, - target)); - target = bindings.get(elseLabel); - if (target == null) { - throw new BugInCF( - String.format( - "in conditional jump %s, no binding for elseLabel %s: %s", - cj, elseLabel, bindings)); - } - missingEdges.add( - new MissingEdge( - new RegularBlockImpl() { - @Override - public void setSuccessor(BlockImpl successor) { - cb.setElseSuccessor(successor); - } - }, - target)); - break; - } - case UNCONDITIONAL_JUMP: - UnconditionalJump uj = (UnconditionalJump) node; - if (leaders.contains(i)) { - RegularBlockImpl b = new RegularBlockImpl(); - block.setSuccessor(b); - block = b; - } - node.setBlock(block); - if (node.getLabel() == in.regularExitLabel) { - block.setSuccessor(regularExitBlock); - block.setFlowRule(uj.getFlowRule()); - } else if (node.getLabel() == in.exceptionalExitLabel) { - block.setSuccessor(exceptionalExitBlock); - block.setFlowRule(uj.getFlowRule()); - } else { - int target = bindings.get(node.getLabel()); - missingEdges.add(new MissingEdge(block, target, uj.getFlowRule())); - } - block = new RegularBlockImpl(); - break; - case EXCEPTION_NODE: - NodeWithExceptionsHolder en = (NodeWithExceptionsHolder) node; - // create new exception block and link with previous block - ExceptionBlockImpl e = new ExceptionBlockImpl(); - Node nn = en.getNode(); - e.setNode(nn); - node.setBlock(e); - block.setSuccessor(e); - block = new RegularBlockImpl(); - - // Ensure linking between e and next block (normal edge). - // Note: do not link to the next block for throw statements (these throw - // exceptions for sure). - if (!node.getTerminatesExecution()) { - missingEdges.add(new MissingEdge(e, i + 1)); - } - - // exceptional edges - for (Map.Entry> entry : en.getExceptions().entrySet()) { - TypeMirror cause = entry.getKey(); - for (Label label : entry.getValue()) { - Integer target = bindings.get(label); - // TODO: `target` is sometimes null; is this a problem? - // assert target != null; - missingExceptionalEdges.add(new MissingEdge(e, target, cause)); - } - } - break; - } - i++; - } + i++; + } - // add missing edges - for (MissingEdge p : missingEdges) { - Integer index = p.index; - assert index != null : "CFGBuilder: problem in CFG construction " + p.source; - ExtendedNode extendedNode = nodeList.get(index); - BlockImpl target = extendedNode.getBlock(); - SingleSuccessorBlockImpl source = p.source; - source.setSuccessor(target); - if (p.flowRule != null) { - source.setFlowRule(p.flowRule); - } - } + // add missing edges + for (MissingEdge p : missingEdges) { + Integer index = p.index; + assert index != null : "CFGBuilder: problem in CFG construction " + p.source; + ExtendedNode extendedNode = nodeList.get(index); + BlockImpl target = extendedNode.getBlock(); + SingleSuccessorBlockImpl source = p.source; + source.setSuccessor(target); + if (p.flowRule != null) { + source.setFlowRule(p.flowRule); + } + } - // add missing exceptional edges - for (MissingEdge p : missingExceptionalEdges) { - Integer index = p.index; - TypeMirror cause = p.cause; - ExceptionBlockImpl source = (ExceptionBlockImpl) p.source; - if (index == null) { - // edge to exceptional exit - source.addExceptionalSuccessor(exceptionalExitBlock, cause); - } else { - // edge to specific target - ExtendedNode extendedNode = nodeList.get(index); - BlockImpl target = extendedNode.getBlock(); - List targetNodes = target.getNodes(); - Node firstNode = targetNodes.isEmpty() ? null : targetNodes.get(0); - if (firstNode instanceof CatchMarkerNode) { - TypeMirror catchType = ((CatchMarkerNode) firstNode).getCatchType(); - if (in.types.isSubtype(catchType, cause)) { - cause = catchType; - } + // add missing exceptional edges + for (MissingEdge p : missingExceptionalEdges) { + Integer index = p.index; + TypeMirror cause = p.cause; + ExceptionBlockImpl source = (ExceptionBlockImpl) p.source; + if (index == null) { + // edge to exceptional exit + source.addExceptionalSuccessor(exceptionalExitBlock, cause); + } else { + // edge to specific target + ExtendedNode extendedNode = nodeList.get(index); + BlockImpl target = extendedNode.getBlock(); + List targetNodes = target.getNodes(); + Node firstNode = targetNodes.isEmpty() ? null : targetNodes.get(0); + if (firstNode instanceof CatchMarkerNode) { + TypeMirror catchType = ((CatchMarkerNode) firstNode).getCatchType(); + if (in.types.isSubtype(catchType, cause)) { + cause = catchType; + } + } + source.addExceptionalSuccessor(target, cause); + } } - source.addExceptionalSuccessor(target, cause); - } - } - return new ControlFlowGraph( - startBlock, - regularExitBlock, - exceptionalExitBlock, - in.underlyingAST, - in.treeToCfgNodes, - in.treeToConvertedCfgNodes, - in.postfixTreeToCfgNodes, - in.returnNodes, - in.declaredClasses, - in.declaredLambdas); - } + return new ControlFlowGraph( + startBlock, + regularExitBlock, + exceptionalExitBlock, + in.underlyingAST, + in.treeToCfgNodes, + in.treeToConvertedCfgNodes, + in.postfixTreeToCfgNodes, + in.returnNodes, + in.declaredClasses, + in.declaredLambdas); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java index 5323f2c2f6e..f7b152f89a9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ConditionalJump.java @@ -14,89 +14,89 @@ @SuppressWarnings("nullness") // TODO public class ConditionalJump extends ExtendedNode { - /** The true successor label. */ - protected final Label trueSucc; + /** The true successor label. */ + protected final Label trueSucc; - /** The false successor label. */ - protected final Label falseSucc; + /** The false successor label. */ + protected final Label falseSucc; - /** The true branch flow rule. */ - protected FlowRule trueFlowRule; + /** The true branch flow rule. */ + protected FlowRule trueFlowRule; - /** The false branch flow rule. */ - protected FlowRule falseFlowRule; + /** The false branch flow rule. */ + protected FlowRule falseFlowRule; - /** - * Construct a ConditionalJump. - * - * @param trueSucc true successor label - * @param falseSucc false successor label - */ - public ConditionalJump(Label trueSucc, Label falseSucc) { - super(ExtendedNodeType.CONDITIONAL_JUMP); - assert trueSucc != null; - this.trueSucc = trueSucc; - assert falseSucc != null; - this.falseSucc = falseSucc; - } + /** + * Construct a ConditionalJump. + * + * @param trueSucc true successor label + * @param falseSucc false successor label + */ + public ConditionalJump(Label trueSucc, Label falseSucc) { + super(ExtendedNodeType.CONDITIONAL_JUMP); + assert trueSucc != null; + this.trueSucc = trueSucc; + assert falseSucc != null; + this.falseSucc = falseSucc; + } - public Label getThenLabel() { - return trueSucc; - } + public Label getThenLabel() { + return trueSucc; + } - public Label getElseLabel() { - return falseSucc; - } + public Label getElseLabel() { + return falseSucc; + } - /** - * Returns the true branch flow rule. - * - * @return the true branch flow rule - */ - public FlowRule getTrueFlowRule() { - return trueFlowRule; - } + /** + * Returns the true branch flow rule. + * + * @return the true branch flow rule + */ + public FlowRule getTrueFlowRule() { + return trueFlowRule; + } - /** - * Returns the false branch flow rule. - * - * @return the false branch flow rule - */ - public FlowRule getFalseFlowRule() { - return falseFlowRule; - } + /** + * Returns the false branch flow rule. + * + * @return the false branch flow rule + */ + public FlowRule getFalseFlowRule() { + return falseFlowRule; + } - /** - * Sets the true branch flow rule. - * - * @param rule the new true branch flow rule - */ - public void setTrueFlowRule(FlowRule rule) { - trueFlowRule = rule; - } + /** + * Sets the true branch flow rule. + * + * @param rule the new true branch flow rule + */ + public void setTrueFlowRule(FlowRule rule) { + trueFlowRule = rule; + } - /** - * Sets the false branch flow rule. - * - * @param rule the new false branch flow rule - */ - public void setFalseFlowRule(FlowRule rule) { - falseFlowRule = rule; - } + /** + * Sets the false branch flow rule. + * + * @param rule the new false branch flow rule + */ + public void setFalseFlowRule(FlowRule rule) { + falseFlowRule = rule; + } - /** - * Produce a string representation. - * - * @return a string representation - * @see org.checkerframework.dataflow.cfg.builder.PhaseOneResult#nodeToString - */ - @Override - public String toString() { - return "TwoTargetConditionalJump(" + getThenLabel() + ", " + getElseLabel() + ")"; - } + /** + * Produce a string representation. + * + * @return a string representation + * @see org.checkerframework.dataflow.cfg.builder.PhaseOneResult#nodeToString + */ + @Override + public String toString() { + return "TwoTargetConditionalJump(" + getThenLabel() + ", " + getElseLabel() + ")"; + } - @Override - public String toStringDebug() { - return toString(); - } + @Override + public String toStringDebug() { + return toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java index f72d4af9b84..1c1c40f6e69 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/ExtendedNode.java @@ -25,83 +25,83 @@ @SuppressWarnings("nullness") // TODO public abstract class ExtendedNode { - /** The basic block this extended node belongs to (as determined in phase two). */ - protected BlockImpl block; - - /** Type of this node. */ - protected final ExtendedNodeType type; - - /** Does this node terminate the execution? (e.g., "System.exit()") */ - protected boolean terminatesExecution = false; - - /** - * Create a new ExtendedNode. - * - * @param type the type of this node - */ - protected ExtendedNode(ExtendedNodeType type) { - this.type = type; - } - - /** Extended node types (description see above). */ - public enum ExtendedNodeType { - NODE, - EXCEPTION_NODE, - UNCONDITIONAL_JUMP, - CONDITIONAL_JUMP - } - - public ExtendedNodeType getType() { - return type; - } - - public boolean getTerminatesExecution() { - return terminatesExecution; - } - - public void setTerminatesExecution(boolean terminatesExecution) { - this.terminatesExecution = terminatesExecution; - } - - /** - * Returns the node contained in this extended node (only applicable if the type is {@code NODE} - * or {@code EXCEPTION_NODE}). - * - * @return the node contained in this extended node (only applicable if the type is {@code NODE} - * or {@code EXCEPTION_NODE}) - */ - public Node getNode() { - throw new BugInCF("Do not call"); - } - - /** - * Returns the label associated with this extended node (only applicable if type is {@link - * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}). - * - * @return the label associated with this extended node (only applicable if type is {@link - * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}) - */ - public Label getLabel() { - throw new BugInCF("Do not call"); - } - - public BlockImpl getBlock() { - return block; - } - - public void setBlock(BlockImpl b) { - this.block = b; - } - - @Override - public String toString() { - throw new BugInCF("DO NOT CALL ExtendedNode.toString(). Write your own."); - } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public abstract String toStringDebug(); + /** The basic block this extended node belongs to (as determined in phase two). */ + protected BlockImpl block; + + /** Type of this node. */ + protected final ExtendedNodeType type; + + /** Does this node terminate the execution? (e.g., "System.exit()") */ + protected boolean terminatesExecution = false; + + /** + * Create a new ExtendedNode. + * + * @param type the type of this node + */ + protected ExtendedNode(ExtendedNodeType type) { + this.type = type; + } + + /** Extended node types (description see above). */ + public enum ExtendedNodeType { + NODE, + EXCEPTION_NODE, + UNCONDITIONAL_JUMP, + CONDITIONAL_JUMP + } + + public ExtendedNodeType getType() { + return type; + } + + public boolean getTerminatesExecution() { + return terminatesExecution; + } + + public void setTerminatesExecution(boolean terminatesExecution) { + this.terminatesExecution = terminatesExecution; + } + + /** + * Returns the node contained in this extended node (only applicable if the type is {@code NODE} + * or {@code EXCEPTION_NODE}). + * + * @return the node contained in this extended node (only applicable if the type is {@code NODE} + * or {@code EXCEPTION_NODE}) + */ + public Node getNode() { + throw new BugInCF("Do not call"); + } + + /** + * Returns the label associated with this extended node (only applicable if type is {@link + * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}). + * + * @return the label associated with this extended node (only applicable if type is {@link + * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}) + */ + public Label getLabel() { + throw new BugInCF("Do not call"); + } + + public BlockImpl getBlock() { + return block; + } + + public void setBlock(BlockImpl b) { + this.block = b; + } + + @Override + public String toString() { + throw new BugInCF("DO NOT CALL ExtendedNode.toString(). Write your own."); + } + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public abstract String toStringDebug(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java index 22dacf0030a..3ce9e3dcda3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/Label.java @@ -10,30 +10,30 @@ */ public class Label { - /** Unique id counter that incremented in {@code #uniqueName}. */ - private static int uid = 0; + /** Unique id counter that incremented in {@code #uniqueName}. */ + private static int uid = 0; - protected final String name; + protected final String name; - public Label(String name) { - this.name = name; - } + public Label(String name) { + this.name = name; + } - public Label() { - this.name = uniqueName(); - } + public Label() { + this.name = uniqueName(); + } - @Override - public String toString() { - return name; - } + @Override + public String toString() { + return name; + } - /** - * Return a new unique label name that cannot be confused with a Java source code label. - * - * @return a new unique label name - */ - private static String uniqueName() { - return "%L" + uid++; - } + /** + * Return a new unique label name that cannot be confused with a Java source code label. + * + * @return a new unique label name + */ + private static String uniqueName() { + return "%L" + uid++; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java index 90730482fe4..a948fcac774 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/LabelCell.java @@ -5,44 +5,44 @@ /** Storage cell for a single Label, with tracking whether it was accessed. */ /*package-private*/ class LabelCell { - /** The label. If it is null, then it will be lazily set if {@link #accessLabel} is called. */ - private @MonotonicNonNull Label label; - - /** True if the label has been accessed. */ - private boolean accessed; - - /** Create a LabelCell with no label; the label will be lazily created if needed. */ - protected LabelCell() { - this.accessed = false; - } - - /** - * Create a LabelCell with the given label. - * - * @param label the label - */ - protected LabelCell(Label label) { - assert label != null; - this.label = label; - this.accessed = false; - } - - public Label accessLabel() { - if (label == null) { - label = new Label(); + /** The label. If it is null, then it will be lazily set if {@link #accessLabel} is called. */ + private @MonotonicNonNull Label label; + + /** True if the label has been accessed. */ + private boolean accessed; + + /** Create a LabelCell with no label; the label will be lazily created if needed. */ + protected LabelCell() { + this.accessed = false; + } + + /** + * Create a LabelCell with the given label. + * + * @param label the label + */ + protected LabelCell(Label label) { + assert label != null; + this.label = label; + this.accessed = false; } - accessed = true; - return label; - } - public Label peekLabel() { - if (label == null) { - throw new BugInCF("called peekLabel prematurely"); + public Label accessLabel() { + if (label == null) { + label = new Label(); + } + accessed = true; + return label; } - return label; - } - public boolean wasAccessed() { - return accessed; - } + public Label peekLabel() { + if (label == null) { + throw new BugInCF("called peekLabel prematurely"); + } + return label; + } + + public boolean wasAccessed() { + return accessed; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java index 2dfbcb58ffb..a49169ca59f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/MissingEdge.java @@ -1,83 +1,84 @@ package org.checkerframework.dataflow.cfg.builder; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store.FlowRule; import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl; +import javax.lang.model.type.TypeMirror; + /* --------------------------------------------------------- */ /* Phase Two */ /* --------------------------------------------------------- */ /** Represents a missing edge that will be added later. */ /*package-private*/ class MissingEdge { - /** The source of the edge. */ - /*package-private*/ final SingleSuccessorBlockImpl source; + /** The source of the edge. */ + /*package-private*/ final SingleSuccessorBlockImpl source; - /** The index (target?) of the edge. Null means go to exceptional exit. */ - /*package-private*/ final @Nullable Integer index; + /** The index (target?) of the edge. Null means go to exceptional exit. */ + /*package-private*/ final @Nullable Integer index; - /** The cause exception type, for an exceptional edge; otherwise null. */ - /*package-private*/ final @Nullable TypeMirror cause; + /** The cause exception type, for an exceptional edge; otherwise null. */ + /*package-private*/ final @Nullable TypeMirror cause; - /** The flow rule for this edge. */ - /*package-private*/ final @Nullable FlowRule flowRule; + /** The flow rule for this edge. */ + /*package-private*/ final @Nullable FlowRule flowRule; - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge - */ - public MissingEdge(SingleSuccessorBlockImpl source, int index) { - this(source, index, null, FlowRule.EACH_TO_EACH); - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge + */ + public MissingEdge(SingleSuccessorBlockImpl source, int index) { + this(source, index, null, FlowRule.EACH_TO_EACH); + } - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge - * @param flowRule the flow rule for this edge - */ - public MissingEdge(SingleSuccessorBlockImpl source, int index, FlowRule flowRule) { - this(source, index, null, flowRule); - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge + * @param flowRule the flow rule for this edge + */ + public MissingEdge(SingleSuccessorBlockImpl source, int index, FlowRule flowRule) { + this(source, index, null, flowRule); + } - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge; null means go to exceptional exit - * @param cause the cause exception type, for an exceptional edge; otherwise null - */ - public MissingEdge( - SingleSuccessorBlockImpl source, @Nullable Integer index, @Nullable TypeMirror cause) { - this(source, index, cause, FlowRule.EACH_TO_EACH); - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge; null means go to exceptional exit + * @param cause the cause exception type, for an exceptional edge; otherwise null + */ + public MissingEdge( + SingleSuccessorBlockImpl source, @Nullable Integer index, @Nullable TypeMirror cause) { + this(source, index, cause, FlowRule.EACH_TO_EACH); + } - /** - * Create a new MissingEdge. - * - * @param source the source of the edge - * @param index the index (target?) of the edge; null means go to exceptional exit - * @param cause the cause exception type, for an exceptional edge; otherwise null - * @param flowRule the flow rule for this edge - */ - public MissingEdge( - SingleSuccessorBlockImpl source, - @Nullable Integer index, - @Nullable TypeMirror cause, - FlowRule flowRule) { - assert (index != null) || (cause != null); - this.source = source; - this.index = index; - this.cause = cause; - this.flowRule = flowRule; - } + /** + * Create a new MissingEdge. + * + * @param source the source of the edge + * @param index the index (target?) of the edge; null means go to exceptional exit + * @param cause the cause exception type, for an exceptional edge; otherwise null + * @param flowRule the flow rule for this edge + */ + public MissingEdge( + SingleSuccessorBlockImpl source, + @Nullable Integer index, + @Nullable TypeMirror cause, + FlowRule flowRule) { + assert (index != null) || (cause != null); + this.source = source; + this.index = index; + this.cause = cause; + this.flowRule = flowRule; + } - @Override - public String toString() { - return "MissingEdge(" + source + ", " + index + ", " + cause + ")"; - } + @Override + public String toString() { + return "MissingEdge(" + source + ", " + index + ", " + cause + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java index da7b7de241a..33dc0e97689 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeHolder.java @@ -6,31 +6,31 @@ /** An extended node of type {@code NODE}. */ /*package-private*/ class NodeHolder extends ExtendedNode { - /** The node to hold. */ - protected final Node node; + /** The node to hold. */ + protected final Node node; - /** - * Construct a NodeHolder for the given Node. - * - * @param node the node to hold - */ - public NodeHolder(Node node) { - super(ExtendedNodeType.NODE); - this.node = node; - } + /** + * Construct a NodeHolder for the given Node. + * + * @param node the node to hold + */ + public NodeHolder(Node node) { + super(ExtendedNodeType.NODE); + this.node = node; + } - @Override - public Node getNode() { - return node; - } + @Override + public Node getNode() { + return node; + } - @Override - public String toString() { - return "NodeHolder(" + node + ")"; - } + @Override + public String toString() { + return "NodeHolder(" + node + ")"; + } - @Override - public String toStringDebug() { - return "NodeHolder(" + node.toStringDebug() + ")"; - } + @Override + public String toStringDebug() { + return "NodeHolder(" + node.toStringDebug() + ")"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java index e3f8f7ab406..c499126d428 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/NodeWithExceptionsHolder.java @@ -1,68 +1,70 @@ package org.checkerframework.dataflow.cfg.builder; +import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; +import org.checkerframework.dataflow.cfg.node.Node; + import java.util.Map; import java.util.Set; import java.util.StringJoiner; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; -import org.checkerframework.dataflow.cfg.node.Node; /** An extended node of type {@code EXCEPTION_NODE}. */ /*package-private*/ class NodeWithExceptionsHolder extends ExtendedNode { - /** The node to hold. */ - protected final Node node; + /** The node to hold. */ + protected final Node node; - /** - * Map from exception type to labels of successors that may be reached as a result of that - * exception. - * - *

          This map's keys are the exception types that a Java expression or statement is declared to - * throw -- say, in the {@code throws} clause of the declaration of a method being called. The - * expression might be within a {@code try} statement with {@code catch} blocks that are different - * (either finer-grained or coarser). - */ - protected final Map> exceptions; + /** + * Map from exception type to labels of successors that may be reached as a result of that + * exception. + * + *

          This map's keys are the exception types that a Java expression or statement is declared to + * throw -- say, in the {@code throws} clause of the declaration of a method being called. The + * expression might be within a {@code try} statement with {@code catch} blocks that are + * different (either finer-grained or coarser). + */ + protected final Map> exceptions; - /** - * Construct a NodeWithExceptionsHolder for the given node and exceptions. - * - * @param node the node to hold - * @param exceptions the exceptions to hold - */ - public NodeWithExceptionsHolder(Node node, Map> exceptions) { - super(ExtendedNodeType.EXCEPTION_NODE); - this.node = node; - this.exceptions = exceptions; - } + /** + * Construct a NodeWithExceptionsHolder for the given node and exceptions. + * + * @param node the node to hold + * @param exceptions the exceptions to hold + */ + public NodeWithExceptionsHolder(Node node, Map> exceptions) { + super(ExtendedNodeType.EXCEPTION_NODE); + this.node = node; + this.exceptions = exceptions; + } - /** - * Get the exceptions for the node. - * - * @return exceptions for the node - */ - public Map> getExceptions() { - return exceptions; - } + /** + * Get the exceptions for the node. + * + * @return exceptions for the node + */ + public Map> getExceptions() { + return exceptions; + } - @Override - public Node getNode() { - return node; - } + @Override + public Node getNode() { + return node; + } - @Override - public String toString() { - return "NodeWithExceptionsHolder(" + node + ")"; - } + @Override + public String toString() { + return "NodeWithExceptionsHolder(" + node + ")"; + } - @Override - public String toStringDebug() { - StringJoiner sj = new StringJoiner(String.format("%n ")); - sj.add("NodeWithExceptionsHolder(" + node.toStringDebug() + ") {"); - for (Map.Entry> entry : exceptions.entrySet()) { - sj.add(entry.getKey() + " => " + entry.getValue()); + @Override + public String toStringDebug() { + StringJoiner sj = new StringJoiner(String.format("%n ")); + sj.add("NodeWithExceptionsHolder(" + node.toStringDebug() + ") {"); + for (Map.Entry> entry : exceptions.entrySet()) { + sj.add(entry.getKey() + " => " + entry.getValue()); + } + sj.add("}"); + return sj.toString(); } - sj.add("}"); - return sj.toString(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java index 87e2a738bad..e9a4537ac6d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/PhaseOneResult.java @@ -5,197 +5,204 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; + +import org.checkerframework.dataflow.cfg.UnderlyingAST; +import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.cfg.node.ReturnNode; + import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringJoiner; + import javax.lang.model.util.Types; -import org.checkerframework.dataflow.cfg.UnderlyingAST; -import org.checkerframework.dataflow.cfg.builder.ExtendedNode.ExtendedNodeType; -import org.checkerframework.dataflow.cfg.node.Node; -import org.checkerframework.dataflow.cfg.node.ReturnNode; /** A wrapper object to pass around the result of phase one. */ public class PhaseOneResult { - /** AST for which the CFG is to be built. */ - /*package-private*/ final UnderlyingAST underlyingAST; - - /** - * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will - * have at least one corresponding Node. Trees that undergo conversions, such as boxing or - * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in the - * treeToCfgNodes, while the Node for the post-conversion value is stored in the - * treeToConvertedCfgNodes. - */ - /*package-private*/ final IdentityHashMap> treeToCfgNodes; - - /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ - /*package-private*/ final IdentityHashMap> treeToConvertedCfgNodes; - - /** - * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the synthetic - * tree that is {@code v + 1} or {@code v - 1}. - */ - /*package-private*/ final IdentityHashMap postfixTreeToCfgNodes; - - /** The list of extended nodes. */ - /*package-private*/ final List nodeList; - - /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ - /*package-private*/ final Map bindings; - - /** The set of leaders (represented as indices into {@code nodeList}). */ - /*package-private*/ final Set leaders; - - /** - * All return nodes (if any) encountered. Only includes return statements that actually return - * something. - */ - /*package-private*/ final List returnNodes; - - /** Special label to identify the regular exit. */ - /*package-private*/ final Label regularExitLabel; - - /** Special label to identify the exceptional exit. */ - /*package-private*/ final Label exceptionalExitLabel; - - /** - * Class declarations that have been encountered when building the control-flow graph for a - * method. - */ - /*package-private*/ final List declaredClasses; - - /** - * Lambdas encountered when building the control-flow graph for a method, variable initializer, or - * initializer. - */ - /*package-private*/ final List declaredLambdas; - - /** The javac type utilities. */ - /*package-private*/ final Types types; - - /** - * Create a PhaseOneResult with the given data. - * - * @param underlyingAST the underlying AST - * @param treeToCfgNodes the tree to nodes mapping - * @param treeToConvertedCfgNodes the tree to converted nodes mapping - * @param postfixTreeToCfgNodes the postfix tree to nodes mapping - * @param nodeList the list of nodes - * @param bindings the label bindings - * @param leaders the leaders - * @param returnNodes the return nodes - * @param regularExitLabel the regular exit labels - * @param exceptionalExitLabel the exceptional exit labels - * @param declaredClasses the declared classes - * @param declaredLambdas the declared lambdas - * @param types the javac type utilities - */ - public PhaseOneResult( - UnderlyingAST underlyingAST, - IdentityHashMap> treeToCfgNodes, - IdentityHashMap> treeToConvertedCfgNodes, - IdentityHashMap postfixTreeToCfgNodes, - List nodeList, - Map bindings, - Set leaders, - List returnNodes, - Label regularExitLabel, - Label exceptionalExitLabel, - List declaredClasses, - List declaredLambdas, - Types types) { - this.underlyingAST = underlyingAST; - this.treeToCfgNodes = treeToCfgNodes; - this.treeToConvertedCfgNodes = treeToConvertedCfgNodes; - this.postfixTreeToCfgNodes = postfixTreeToCfgNodes; - this.nodeList = nodeList; - this.bindings = bindings; - this.leaders = leaders; - this.returnNodes = returnNodes; - this.regularExitLabel = regularExitLabel; - this.exceptionalExitLabel = exceptionalExitLabel; - this.declaredClasses = declaredClasses; - this.declaredLambdas = declaredLambdas; - this.types = types; - } - - @Override - public String toString() { - StringJoiner sj = new StringJoiner(System.lineSeparator()); - for (ExtendedNode n : nodeList) { - sj.add(nodeToString(n)); + /** AST for which the CFG is to be built. */ + /*package-private*/ final UnderlyingAST underlyingAST; + + /** + * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will + * have at least one corresponding Node. Trees that undergo conversions, such as boxing or + * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in + * the treeToCfgNodes, while the Node for the post-conversion value is stored in the + * treeToConvertedCfgNodes. + */ + /*package-private*/ final IdentityHashMap> treeToCfgNodes; + + /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ + /*package-private*/ final IdentityHashMap> treeToConvertedCfgNodes; + + /** + * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the + * synthetic tree that is {@code v + 1} or {@code v - 1}. + */ + /*package-private*/ final IdentityHashMap postfixTreeToCfgNodes; + + /** The list of extended nodes. */ + /*package-private*/ final List nodeList; + + /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ + /*package-private*/ final Map bindings; + + /** The set of leaders (represented as indices into {@code nodeList}). */ + /*package-private*/ final Set leaders; + + /** + * All return nodes (if any) encountered. Only includes return statements that actually return + * something. + */ + /*package-private*/ final List returnNodes; + + /** Special label to identify the regular exit. */ + /*package-private*/ final Label regularExitLabel; + + /** Special label to identify the exceptional exit. */ + /*package-private*/ final Label exceptionalExitLabel; + + /** + * Class declarations that have been encountered when building the control-flow graph for a + * method. + */ + /*package-private*/ final List declaredClasses; + + /** + * Lambdas encountered when building the control-flow graph for a method, variable initializer, + * or initializer. + */ + /*package-private*/ final List declaredLambdas; + + /** The javac type utilities. */ + /*package-private*/ final Types types; + + /** + * Create a PhaseOneResult with the given data. + * + * @param underlyingAST the underlying AST + * @param treeToCfgNodes the tree to nodes mapping + * @param treeToConvertedCfgNodes the tree to converted nodes mapping + * @param postfixTreeToCfgNodes the postfix tree to nodes mapping + * @param nodeList the list of nodes + * @param bindings the label bindings + * @param leaders the leaders + * @param returnNodes the return nodes + * @param regularExitLabel the regular exit labels + * @param exceptionalExitLabel the exceptional exit labels + * @param declaredClasses the declared classes + * @param declaredLambdas the declared lambdas + * @param types the javac type utilities + */ + public PhaseOneResult( + UnderlyingAST underlyingAST, + IdentityHashMap> treeToCfgNodes, + IdentityHashMap> treeToConvertedCfgNodes, + IdentityHashMap postfixTreeToCfgNodes, + List nodeList, + Map bindings, + Set leaders, + List returnNodes, + Label regularExitLabel, + Label exceptionalExitLabel, + List declaredClasses, + List declaredLambdas, + Types types) { + this.underlyingAST = underlyingAST; + this.treeToCfgNodes = treeToCfgNodes; + this.treeToConvertedCfgNodes = treeToConvertedCfgNodes; + this.postfixTreeToCfgNodes = postfixTreeToCfgNodes; + this.nodeList = nodeList; + this.bindings = bindings; + this.leaders = leaders; + this.returnNodes = returnNodes; + this.regularExitLabel = regularExitLabel; + this.exceptionalExitLabel = exceptionalExitLabel; + this.declaredClasses = declaredClasses; + this.declaredLambdas = declaredLambdas; + this.types = types; + } + + @Override + public String toString() { + StringJoiner sj = new StringJoiner(System.lineSeparator()); + for (ExtendedNode n : nodeList) { + sj.add(nodeToString(n)); + } + return sj.toString(); } - return sj.toString(); - } - - protected String nodeToString(ExtendedNode n) { - if (n.getType() == ExtendedNodeType.CONDITIONAL_JUMP) { - ConditionalJump t = (ConditionalJump) n; - return "TwoTargetConditionalJump(" - + resolveLabel(t.getThenLabel()) - + ", " - + resolveLabel(t.getElseLabel()) - + ")"; - } else if (n.getType() == ExtendedNodeType.UNCONDITIONAL_JUMP) { - return "UnconditionalJump(" + resolveLabel(n.getLabel()) + ")"; - } else { - return n.toString(); + + protected String nodeToString(ExtendedNode n) { + if (n.getType() == ExtendedNodeType.CONDITIONAL_JUMP) { + ConditionalJump t = (ConditionalJump) n; + return "TwoTargetConditionalJump(" + + resolveLabel(t.getThenLabel()) + + ", " + + resolveLabel(t.getElseLabel()) + + ")"; + } else if (n.getType() == ExtendedNodeType.UNCONDITIONAL_JUMP) { + return "UnconditionalJump(" + resolveLabel(n.getLabel()) + ")"; + } else { + return n.toString(); + } } - } - private String resolveLabel(Label label) { - Integer index = bindings.get(label); - if (index == null) { - return "unbound label: " + label; + private String resolveLabel(Label label) { + Integer index = bindings.get(label); + if (index == null) { + return "unbound label: " + label; + } + return nodeToString(nodeList.get(index)); } - return nodeToString(nodeList.get(index)); - } - - /** - * Returns a representation of a map, one entry per line. - * - * @param the key type of the map - * @param the value type of the map - * @param map a map - * @return a representation of a map, one entry per line - */ - private String mapToString(Map map) { - if (map.isEmpty()) { - return "{}"; + + /** + * Returns a representation of a map, one entry per line. + * + * @param the key type of the map + * @param the value type of the map + * @param map a map + * @return a representation of a map, one entry per line + */ + private String mapToString(Map map) { + if (map.isEmpty()) { + return "{}"; + } + StringJoiner result = + new StringJoiner( + String.format("%n "), + String.format("{%n "), + String.format("%n }")); + for (Map.Entry entry : map.entrySet()) { + result.add(entry.getKey() + " => " + entry.getValue()); + } + return result.toString(); } - StringJoiner result = - new StringJoiner( - String.format("%n "), String.format("{%n "), String.format("%n }")); - for (Map.Entry entry : map.entrySet()) { - result.add(entry.getKey() + " => " + entry.getValue()); + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a string representation of this + */ + public String toStringDebug() { + StringJoiner result = + new StringJoiner( + String.format("%n "), + String.format("PhaseOneResult{%n "), + String.format("%n }")); + result.add("treeToCfgNodes=" + mapToString(treeToCfgNodes)); + result.add("treeToConvertedCfgNodes=" + mapToString(treeToConvertedCfgNodes)); + result.add("postfixTreeToCfgNodes=" + mapToString(postfixTreeToCfgNodes)); + result.add("underlyingAST=" + underlyingAST); + result.add("bindings=" + bindings); + result.add("nodeList=" + CFGBuilder.extendedNodeCollectionToStringDebug(nodeList)); + result.add("leaders=" + leaders); + result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); + result.add("regularExitLabel=" + regularExitLabel); + result.add("exceptionalExitLabel=" + exceptionalExitLabel); + result.add("declaredClasses=" + declaredClasses); + result.add("declaredLambdas=" + declaredLambdas); + return result.toString(); } - return result.toString(); - } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a string representation of this - */ - public String toStringDebug() { - StringJoiner result = - new StringJoiner( - String.format("%n "), String.format("PhaseOneResult{%n "), String.format("%n }")); - result.add("treeToCfgNodes=" + mapToString(treeToCfgNodes)); - result.add("treeToConvertedCfgNodes=" + mapToString(treeToConvertedCfgNodes)); - result.add("postfixTreeToCfgNodes=" + mapToString(postfixTreeToCfgNodes)); - result.add("underlyingAST=" + underlyingAST); - result.add("bindings=" + bindings); - result.add("nodeList=" + CFGBuilder.extendedNodeCollectionToStringDebug(nodeList)); - result.add("leaders=" + leaders); - result.add("returnNodes=" + Node.nodeCollectionToString(returnNodes)); - result.add("regularExitLabel=" + regularExitLabel); - result.add("exceptionalExitLabel=" + exceptionalExitLabel); - result.add("declaredClasses=" + declaredClasses); - result.add("declaredLambdas=" + declaredLambdas); - return result.toString(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java index 32953cdb80f..e4a0df09690 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java @@ -4,31 +4,31 @@ /** A tuple with 4 named elements. */ /*package-private*/ interface TreeInfo { - /** - * Returns true if this is boxed. - * - * @return true if this is boxed - */ - boolean isBoxed(); + /** + * Returns true if this is boxed. + * + * @return true if this is boxed + */ + boolean isBoxed(); - /** - * Returns true if this is numeric. - * - * @return true if this is numeric - */ - boolean isNumeric(); + /** + * Returns true if this is numeric. + * + * @return true if this is numeric + */ + boolean isNumeric(); - /** - * Returns true if this is boolean. - * - * @return true if this is boolean - */ - boolean isBoolean(); + /** + * Returns true if this is boolean. + * + * @return true if this is boolean + */ + boolean isBoolean(); - /** - * Returns the unboxed type that this wraps. - * - * @return the unboxed type that this wraps - */ - TypeMirror unboxedType(); + /** + * Returns the unboxed type that this wraps. + * + * @return the unboxed type that this wraps + */ + TypeMirror unboxedType(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java index 1151c5830d5..d390653a789 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TryCatchFrame.java @@ -1,118 +1,121 @@ package org.checkerframework.dataflow.cfg.builder; +import org.plumelib.util.IPair; + import java.util.List; import java.util.Set; import java.util.StringJoiner; + import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.UnionType; import javax.lang.model.util.Types; -import org.plumelib.util.IPair; /** * A TryCatchFrame contains an ordered list of catch labels that apply to exceptions with specific * types. */ /*package-private*/ class TryCatchFrame implements TryFrame { - /** The Types utilities. */ - protected final Types types; - - /** An ordered list of pairs because catch blocks are ordered. */ - protected final List> catchLabels; + /** The Types utilities. */ + protected final Types types; - /** - * Construct a TryCatchFrame. - * - * @param types the Types utilities - * @param catchLabels the catch labels - */ - public TryCatchFrame(Types types, List> catchLabels) { - this.types = types; - this.catchLabels = catchLabels; - } + /** An ordered list of pairs because catch blocks are ordered. */ + protected final List> catchLabels; - @Override - public String toString() { - if (this.catchLabels.isEmpty()) { - return "TryCatchFrame: no catch labels."; - } else { - StringJoiner sb = new StringJoiner(System.lineSeparator(), "TryCatchFrame: ", ""); - for (IPair ptml : this.catchLabels) { - sb.add(ptml.first.toString() + " -> " + ptml.second.toString()); - } - return sb.toString(); + /** + * Construct a TryCatchFrame. + * + * @param types the Types utilities + * @param catchLabels the catch labels + */ + public TryCatchFrame(Types types, List> catchLabels) { + this.types = types; + this.catchLabels = catchLabels; } - } - - /** - * Given a type of thrown exception, add the set of possible control flow successor {@link Label}s - * to the argument set. Return true if the exception is known to be caught by one of those labels - * and false if it may propagate still further. - */ - @Override - public boolean possibleLabels(TypeMirror thrown, Set

          Is set by {@link #setArrayExpression}. - */ - protected @Nullable ExpressionTree arrayExpression; - - /** - * Create an ArrayAccessNode. - * - * @param t tree for the array access - * @param array the node for the array expression being accessed - * @param index the node for the index used to access the array - */ - public ArrayAccessNode(ArrayAccessTree t, Node array, Node index) { - super(TreeUtils.typeOf(t)); - this.tree = t; - this.array = array; - this.index = index; - } - - /** - * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then return - * the expression in the for loop, e.g., {@code arr} in {@code for(Object o: arr}. Otherwise, - * return null. - * - * @return the array expression, or null if this is not an array desugared from an enhanced for - * loop - */ - public @Nullable ExpressionTree getArrayExpression() { - return arrayExpression; - } - - /** - * Set the array expression from a for loop. - * - * @param arrayExpression array expression - * @see #getArrayExpression() - */ - public void setArrayExpression(@Nullable ExpressionTree arrayExpression) { - this.arrayExpression = arrayExpression; - } - - /** - * Get the node that represents the array expression being accessed. - * - * @return the array expression node - */ - public Node getArray() { - return array; - } - - public Node getIndex() { - return index; - } - - @Override - public ArrayAccessTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitArrayAccess(this, p); - } - - @Override - public String toString() { - String base = getArray().toString() + "[" + getIndex() + "]"; - return base; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayAccessNode)) { - return false; + /** The corresponding ArrayAccessTree. */ + protected final ArrayAccessTree tree; + + /** The array expression being accessed. */ + protected final Node array; + + /** The index expresssion used to access the array. */ + protected final Node index; + + /** + * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then the + * {@code arrayExpression} field is the expression in the for loop, e.g., {@code arr} in {@code + * for(Object o: arr}. + * + *

          Is set by {@link #setArrayExpression}. + */ + protected @Nullable ExpressionTree arrayExpression; + + /** + * Create an ArrayAccessNode. + * + * @param t tree for the array access + * @param array the node for the array expression being accessed + * @param index the node for the index used to access the array + */ + public ArrayAccessNode(ArrayAccessTree t, Node array, Node index) { + super(TreeUtils.typeOf(t)); + this.tree = t; + this.array = array; + this.index = index; + } + + /** + * If this ArrayAccessNode is a node for an array desugared from an enhanced for loop, then + * return the expression in the for loop, e.g., {@code arr} in {@code for(Object o: arr}. + * Otherwise, return null. + * + * @return the array expression, or null if this is not an array desugared from an enhanced for + * loop + */ + public @Nullable ExpressionTree getArrayExpression() { + return arrayExpression; + } + + /** + * Set the array expression from a for loop. + * + * @param arrayExpression array expression + * @see #getArrayExpression() + */ + public void setArrayExpression(@Nullable ExpressionTree arrayExpression) { + this.arrayExpression = arrayExpression; + } + + /** + * Get the node that represents the array expression being accessed. + * + * @return the array expression node + */ + public Node getArray() { + return array; + } + + public Node getIndex() { + return index; + } + + @Override + public ArrayAccessTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitArrayAccess(this, p); + } + + @Override + public String toString() { + String base = getArray().toString() + "[" + getIndex() + "]"; + return base; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayAccessNode)) { + return false; + } + ArrayAccessNode other = (ArrayAccessNode) obj; + return getArray().equals(other.getArray()) && getIndex().equals(other.getIndex()); + } + + @Override + public int hashCode() { + return Objects.hash(getArray(), getIndex()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getArray(), getIndex()); } - ArrayAccessNode other = (ArrayAccessNode) obj; - return getArray().equals(other.getArray()) && getIndex().equals(other.getIndex()); - } - - @Override - public int hashCode() { - return Objects.hash(getArray(), getIndex()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getArray(), getIndex()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java index 35684ab0e38..9e4d33e905b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java @@ -2,14 +2,17 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.plumelib.util.StringsPlume; + import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.plumelib.util.StringsPlume; /** * A node for new array creation. @@ -21,94 +24,94 @@ */ public class ArrayCreationNode extends Node { - /** The tree is null when an array is created for variable arity method calls. */ - protected final @Nullable NewArrayTree tree; - - /** - * The length of this list is the number of dimensions in the array. Each element is the size of - * the given dimension. It can be empty if initializers is non-empty, as in {@code new SomeType[] - * = { expr1, expr2, ... }}. - */ - protected final List dimensions; - - protected final List initializers; - - public ArrayCreationNode( - @Nullable NewArrayTree tree, - TypeMirror type, - List dimensions, - List initializers) { - super(type); - this.tree = tree; - this.dimensions = dimensions; - this.initializers = initializers; - } - - public List getDimensions() { - return dimensions; - } - - public Node getDimension(int i) { - return dimensions.get(i); - } - - public List getInitializers() { - return initializers; - } - - public Node getInitializer(int i) { - return initializers.get(i); - } - - @Override - public @Nullable Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitArrayCreation(this, p); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("new " + type); - if (!dimensions.isEmpty()) { - sb.append(" ("); - sb.append(StringsPlume.join(", ", dimensions)); - sb.append(")"); + /** The tree is null when an array is created for variable arity method calls. */ + protected final @Nullable NewArrayTree tree; + + /** + * The length of this list is the number of dimensions in the array. Each element is the size of + * the given dimension. It can be empty if initializers is non-empty, as in {@code new + * SomeType[] = { expr1, expr2, ... }}. + */ + protected final List dimensions; + + protected final List initializers; + + public ArrayCreationNode( + @Nullable NewArrayTree tree, + TypeMirror type, + List dimensions, + List initializers) { + super(type); + this.tree = tree; + this.dimensions = dimensions; + this.initializers = initializers; + } + + public List getDimensions() { + return dimensions; } - if (!initializers.isEmpty()) { - sb.append(" = {"); - sb.append(StringsPlume.join(", ", initializers)); - sb.append("}"); + + public Node getDimension(int i) { + return dimensions.get(i); + } + + public List getInitializers() { + return initializers; + } + + public Node getInitializer(int i) { + return initializers.get(i); + } + + @Override + public @Nullable Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitArrayCreation(this, p); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("new " + type); + if (!dimensions.isEmpty()) { + sb.append(" ("); + sb.append(StringsPlume.join(", ", dimensions)); + sb.append(")"); + } + if (!initializers.isEmpty()) { + sb.append(" = {"); + sb.append(StringsPlume.join(", ", initializers)); + sb.append("}"); + } + return sb.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayCreationNode)) { + return false; + } + ArrayCreationNode other = (ArrayCreationNode) obj; + + return getDimensions().equals(other.getDimensions()) + && getInitializers().equals(other.getInitializers()); + } + + @Override + public int hashCode() { + return Objects.hash(dimensions, initializers); } - return sb.toString(); - } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayCreationNode)) { - return false; + @Override + @SideEffectFree + public Collection getOperands() { + ArrayList list = new ArrayList<>(dimensions.size() + initializers.size()); + list.addAll(dimensions); + list.addAll(initializers); + return list; } - ArrayCreationNode other = (ArrayCreationNode) obj; - - return getDimensions().equals(other.getDimensions()) - && getInitializers().equals(other.getInitializers()); - } - - @Override - public int hashCode() { - return Objects.hash(dimensions, initializers); - } - - @Override - @SideEffectFree - public Collection getOperands() { - ArrayList list = new ArrayList<>(dimensions.size() + initializers.size()); - list.addAll(dimensions); - list.addAll(initializers); - return list; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java index 8d2bbd4bb99..088c84fbbaa 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java @@ -2,13 +2,16 @@ import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; /** * A node representing an array type used in an expression such as a field access. @@ -17,49 +20,49 @@ */ public class ArrayTypeNode extends Node { - protected final ArrayTypeTree tree; + protected final ArrayTypeTree tree; - /** For Types.isSameType. */ - protected final Types types; + /** For Types.isSameType. */ + protected final Types types; - public ArrayTypeNode(ArrayTypeTree tree, Types types) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.types = types; - } + public ArrayTypeNode(ArrayTypeTree tree, Types types) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.types = types; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitArrayType(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitArrayType(this, p); + } - @Override - public String toString() { - return tree.toString(); - } + @Override + public String toString() { + return tree.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayTypeNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayTypeNode)) { + return false; + } + ArrayTypeNode other = (ArrayTypeNode) obj; + return types.isSameType(getType(), other.getType()); } - ArrayTypeNode other = (ArrayTypeNode) obj; - return types.isSameType(getType(), other.getType()); - } - @Override - public int hashCode() { - return Objects.hash(getType()); - } + @Override + public int hashCode() { + return Objects.hash(getType()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java index 27431a9bad0..de2af1c76e4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java @@ -1,14 +1,17 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for the {@link AssertionError} when an assertion fails or when a method call marked {@link @@ -20,88 +23,88 @@ */ public class AssertionErrorNode extends Node { - /** Tree for the assert statement or assert method. */ - protected final Tree tree; - - /** The condition that if it is false, the assertion exception is thrown. */ - protected final Node condition; - - /** The node for the expression after {@code :} in the assert statement, or null. */ - protected final @Nullable Node detail; - - /** - * Creates an AssertionErrorNode. - * - * @param tree tree for the assert statement or assert method - * @param condition the node of the condition when if false the assertion exception is thrown - * @param detail node for the expression after {@code :} in the assert statement, or null - * @param type the type of the exception thrown - */ - public AssertionErrorNode(Tree tree, Node condition, @Nullable Node detail, TypeMirror type) { - // TODO: Find out the correct "type" for statements. - // Is it TypeKind.NONE? - super(type); - this.tree = tree; - this.condition = condition; - this.detail = detail; - } - - /** - * The node of the condition that if it is false, the assertion exception is thrown. - * - * @return the node of the condition that if it is false, the assertion exception is thrown - */ - @Pure - public Node getCondition() { - return condition; - } - - /** - * The node for the expression after {@code :} in the assert statement, or null. - * - * @return node for the expression after {@code :} in the assert statement, or null - */ - @Pure - public @Nullable Node getDetail() { - return detail; - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitAssertionError(this, p); - } - - @Override - public String toString() { - return "AssertionError(" + getDetail() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof AssertionErrorNode)) { - return false; + /** Tree for the assert statement or assert method. */ + protected final Tree tree; + + /** The condition that if it is false, the assertion exception is thrown. */ + protected final Node condition; + + /** The node for the expression after {@code :} in the assert statement, or null. */ + protected final @Nullable Node detail; + + /** + * Creates an AssertionErrorNode. + * + * @param tree tree for the assert statement or assert method + * @param condition the node of the condition when if false the assertion exception is thrown + * @param detail node for the expression after {@code :} in the assert statement, or null + * @param type the type of the exception thrown + */ + public AssertionErrorNode(Tree tree, Node condition, @Nullable Node detail, TypeMirror type) { + // TODO: Find out the correct "type" for statements. + // Is it TypeKind.NONE? + super(type); + this.tree = tree; + this.condition = condition; + this.detail = detail; + } + + /** + * The node of the condition that if it is false, the assertion exception is thrown. + * + * @return the node of the condition that if it is false, the assertion exception is thrown + */ + @Pure + public Node getCondition() { + return condition; } - AssertionErrorNode other = (AssertionErrorNode) obj; - return Objects.equals(getCondition(), other.getCondition()) - && Objects.equals(getDetail(), other.getDetail()); - } - - @Override - public int hashCode() { - return Objects.hash(getCondition(), getDetail()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - if (getDetail() == null) { - return Collections.singleton(getCondition()); + + /** + * The node for the expression after {@code :} in the assert statement, or null. + * + * @return node for the expression after {@code :} in the assert statement, or null + */ + @Pure + public @Nullable Node getDetail() { + return detail; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitAssertionError(this, p); + } + + @Override + public String toString() { + return "AssertionError(" + getDetail() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof AssertionErrorNode)) { + return false; + } + AssertionErrorNode other = (AssertionErrorNode) obj; + return Objects.equals(getCondition(), other.getCondition()) + && Objects.equals(getDetail(), other.getDetail()); + } + + @Override + public int hashCode() { + return Objects.hash(getCondition(), getDetail()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + if (getDetail() == null) { + return Collections.singleton(getCondition()); + } + return Arrays.asList(getCondition(), getDetail()); } - return Arrays.asList(getCondition(), getDetail()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java index 63e9813e596..6cd525436df 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java @@ -5,14 +5,16 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; -import java.util.Arrays; -import java.util.Collection; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; + /** * A node for an assignment: * @@ -35,119 +37,120 @@ */ public class AssignmentNode extends Node { - /** The underlying assignment tree. */ - protected final Tree tree; - - /** The node for the LHS of the assignment tree. */ - protected final Node lhs; - - /** The node for the RHS of the assignment tree. */ - protected final Node rhs; - - /** Whether the assignment node is synthetic */ - protected final boolean synthetic; - - /** - * Create a (non-synthetic) AssignmentNode. - * - * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} - * @param target the lhs of {@code tree} - * @param expression the rhs of {@code tree} - */ - public AssignmentNode(Tree tree, Node target, Node expression) { - this(tree, target, expression, false); - } - - /** - * Create an AssignmentNode. - * - * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} - * @param target the lhs of {@code tree} - * @param expression the rhs of {@code tree} - * @param synthetic whether the assignment node is synthetic - */ - public AssignmentNode(Tree tree, Node target, Node expression, boolean synthetic) { - super(TreeUtils.typeOf(tree)); - assert tree instanceof AssignmentTree - || tree instanceof VariableTree - || tree instanceof CompoundAssignmentTree - || tree instanceof UnaryTree; - assert target instanceof FieldAccessNode - || target instanceof LocalVariableNode - || target instanceof ArrayAccessNode; - this.tree = tree; - this.lhs = target; - this.rhs = expression; - this.synthetic = synthetic; - } - - /** - * Returns the left-hand-side of the assignment. - * - * @return the left-hand-side of the assignment - */ - @Pure - public Node getTarget() { - return lhs; - } - - /** - * Returns the right-hand-side of the assignment. - * - * @return the right-hand-side of the assignment - */ - @Pure - public Node getExpression() { - return rhs; - } - - @Override - @Pure - public Tree getTree() { - return tree; - } - - /** - * Check if the assignment node is synthetic, e.g. the synthetic assignment in a ternary - * expression. - * - * @return true if the assignment node is synthetic - */ - @Pure - public boolean isSynthetic() { - return synthetic; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitAssignment(this, p); - } - - @Override - @Pure - public String toString() { - return getTarget() + " = " + getExpression() + (synthetic ? " (synthetic)" : ""); - } - - @Override - @Pure - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof AssignmentNode)) { - return false; + /** The underlying assignment tree. */ + protected final Tree tree; + + /** The node for the LHS of the assignment tree. */ + protected final Node lhs; + + /** The node for the RHS of the assignment tree. */ + protected final Node rhs; + + /** Whether the assignment node is synthetic */ + protected final boolean synthetic; + + /** + * Create a (non-synthetic) AssignmentNode. + * + * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} + * @param target the lhs of {@code tree} + * @param expression the rhs of {@code tree} + */ + public AssignmentNode(Tree tree, Node target, Node expression) { + this(tree, target, expression, false); + } + + /** + * Create an AssignmentNode. + * + * @param tree the {@code AssignmentTree} corresponding to the {@code AssignmentNode} + * @param target the lhs of {@code tree} + * @param expression the rhs of {@code tree} + * @param synthetic whether the assignment node is synthetic + */ + public AssignmentNode(Tree tree, Node target, Node expression, boolean synthetic) { + super(TreeUtils.typeOf(tree)); + assert tree instanceof AssignmentTree + || tree instanceof VariableTree + || tree instanceof CompoundAssignmentTree + || tree instanceof UnaryTree; + assert target instanceof FieldAccessNode + || target instanceof LocalVariableNode + || target instanceof ArrayAccessNode; + this.tree = tree; + this.lhs = target; + this.rhs = expression; + this.synthetic = synthetic; + } + + /** + * Returns the left-hand-side of the assignment. + * + * @return the left-hand-side of the assignment + */ + @Pure + public Node getTarget() { + return lhs; + } + + /** + * Returns the right-hand-side of the assignment. + * + * @return the right-hand-side of the assignment + */ + @Pure + public Node getExpression() { + return rhs; + } + + @Override + @Pure + public Tree getTree() { + return tree; + } + + /** + * Check if the assignment node is synthetic, e.g. the synthetic assignment in a ternary + * expression. + * + * @return true if the assignment node is synthetic + */ + @Pure + public boolean isSynthetic() { + return synthetic; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitAssignment(this, p); + } + + @Override + @Pure + public String toString() { + return getTarget() + " = " + getExpression() + (synthetic ? " (synthetic)" : ""); + } + + @Override + @Pure + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof AssignmentNode)) { + return false; + } + AssignmentNode other = (AssignmentNode) obj; + return getTarget().equals(other.getTarget()) + && getExpression().equals(other.getExpression()); + } + + @Override + @Pure + public int hashCode() { + return Objects.hash(getTarget(), getExpression()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getTarget(), getExpression()); } - AssignmentNode other = (AssignmentNode) obj; - return getTarget().equals(other.getTarget()) && getExpression().equals(other.getExpression()); - } - - @Override - @Pure - public int hashCode() { - return Objects.hash(getTarget(), getExpression()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getTarget(), getExpression()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java index 8cadd084d8c..d5ad0c2d3a7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BinaryOperationNode.java @@ -1,11 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.BinaryTree; -import java.util.Arrays; -import java.util.Collection; + import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Arrays; +import java.util.Collection; + /** * A node for a binary expression. * @@ -17,33 +19,33 @@ */ public abstract class BinaryOperationNode extends Node { - protected final BinaryTree tree; - protected final Node left; - protected final Node right; - - protected BinaryOperationNode(BinaryTree tree, Node left, Node right) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.left = left; - this.right = right; - } - - public Node getLeftOperand() { - return left; - } - - public Node getRightOperand() { - return right; - } - - @Override - public BinaryTree getTree() { - return tree; - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getLeftOperand(), getRightOperand()); - } + protected final BinaryTree tree; + protected final Node left; + protected final Node right; + + protected BinaryOperationNode(BinaryTree tree, Node left, Node right) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public BinaryTree getTree() { + return tree; + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getLeftOperand(), getRightOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java index 3a0af45cf0e..35818d89410 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the bitwise or logical (single bit) and operation: * @@ -14,40 +16,40 @@ */ public class BitwiseAndNode extends BinaryOperationNode { - /** - * Constructs a {@link BitwiseAndNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public BitwiseAndNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.AND; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseAnd(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " & " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseAndNode)) { - return false; + /** + * Constructs a {@link BitwiseAndNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public BitwiseAndNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.AND; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseAnd(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " & " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseAndNode)) { + return false; + } + BitwiseAndNode other = (BitwiseAndNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - BitwiseAndNode other = (BitwiseAndNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java index 56c56f6a579..527827f373f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the bitwise complement operation: * @@ -14,38 +16,38 @@ */ public class BitwiseComplementNode extends UnaryOperationNode { - /** - * Constructs a {@link BitwiseComplementNode}. - * - * @param tree the tree of the bitwise complement - * @param operand the operand of the bitwise complement - */ - public BitwiseComplementNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseComplement(this, p); - } - - @Override - public String toString() { - return "(~ " + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseComplementNode)) { - return false; + /** + * Constructs a {@link BitwiseComplementNode}. + * + * @param tree the tree of the bitwise complement + * @param operand the operand of the bitwise complement + */ + public BitwiseComplementNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.BITWISE_COMPLEMENT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseComplement(this, p); + } + + @Override + public String toString() { + return "(~ " + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseComplementNode)) { + return false; + } + BitwiseComplementNode other = (BitwiseComplementNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(BitwiseComplementNode.class, getOperand()); } - BitwiseComplementNode other = (BitwiseComplementNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(BitwiseComplementNode.class, getOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java index ad70c3ce32f..d85a3522ecc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the bitwise or logical (single bit) or operation: * @@ -14,40 +16,40 @@ */ public class BitwiseOrNode extends BinaryOperationNode { - /** - * Constructs a {@link BitwiseOrNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public BitwiseOrNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.OR; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseOr(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " | " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseOrNode)) { - return false; + /** + * Constructs a {@link BitwiseOrNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public BitwiseOrNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.OR; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseOr(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " | " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseOrNode)) { + return false; + } + BitwiseOrNode other = (BitwiseOrNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - BitwiseOrNode other = (BitwiseOrNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java index bd9206f42f5..847e0aaac69 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the bitwise or logical (single bit) xor operation: * @@ -14,40 +16,40 @@ */ public class BitwiseXorNode extends BinaryOperationNode { - /** - * Constructs a {@link BitwiseXorNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public BitwiseXorNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.XOR; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBitwiseXor(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " ^ " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof BitwiseXorNode)) { - return false; + /** + * Constructs a {@link BitwiseXorNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public BitwiseXorNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.XOR; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBitwiseXor(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " ^ " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof BitwiseXorNode)) { + return false; + } + BitwiseXorNode other = (BitwiseXorNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - BitwiseXorNode other = (BitwiseXorNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java index 9a812a343cd..35edef75a06 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,33 +15,33 @@ */ public class BooleanLiteralNode extends ValueLiteralNode { - /** - * Create a new BooleanLiteralNode. - * - * @param t the tree for the literal value - */ - public BooleanLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.BOOLEAN_LITERAL; - } + /** + * Create a new BooleanLiteralNode. + * + * @param t the tree for the literal value + */ + public BooleanLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.BOOLEAN_LITERAL; + } - @Override - public Boolean getValue() { - return (Boolean) tree.getValue(); - } + @Override + public Boolean getValue() { + return (Boolean) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitBooleanLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitBooleanLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a BooleanLiteralNode - if (!(obj instanceof BooleanLiteralNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a BooleanLiteralNode + if (!(obj instanceof BooleanLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java index e3027fa44b9..540d37684ec 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CaseNode.java @@ -1,15 +1,18 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.CaseTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.plumelib.util.StringsPlume; + import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; + import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.plumelib.util.StringsPlume; /** * A node for a case in a switch statement. Although a case has no abstract value, it can imply @@ -21,112 +24,112 @@ */ public class CaseNode extends Node { - /** The tree for this node. */ - protected final CaseTree tree; - - /** - * The Node for the assignment of the switch selector expression to a synthetic local variable. - */ - protected final AssignmentNode selectorExprAssignment; - - /** - * The case expressions to match the switch expression against: the operands of (possibly - * multiple) case labels. - */ - protected final List caseExprs; - - /** The guard (the expression in the {@code when} clause) for this case. */ - protected final @Nullable Node guard; - - /** - * Create a new CaseNode. - * - * @param tree the tree for this node - * @param selectorExprAssignment the Node for the assignment of the switch selector expression to - * a synthetic local variable - * @param caseExprs the case expression(s) to match the switch expression against - * @param guard the guard expression or null - * @param types a factory of utility methods for operating on types - */ - public CaseNode( - CaseTree tree, - AssignmentNode selectorExprAssignment, - List caseExprs, - @Nullable Node guard, - Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.selectorExprAssignment = selectorExprAssignment; - this.caseExprs = caseExprs; - this.guard = guard; - } - - /** - * The Node for the assignment of the switch selector expression to a synthetic local variable. - * This is used to refine the type of the switch selector expression in a case block. - * - * @return the assignment of the switch selector expression to a synthetic local variable - */ - public AssignmentNode getSwitchOperand() { - return selectorExprAssignment; - } - - /** - * Gets the nodes corresponding to the case expressions. There can be multiple expressions since - * Java 12. - * - * @return the nodes corresponding to the (potentially multiple) case expressions - */ - public List getCaseOperands() { - return caseExprs; - } - - /** - * Gets the node for the guard (the expression in the {@code when} clause). - * - * @return the node for the guard - */ - public @Nullable Node getGuard() { - return guard; - } - - @Override - public CaseTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitCase(this, p); - } - - @Override - public String toString() { - return "case " + StringsPlume.join(", ", getCaseOperands()) + ":"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof CaseNode)) { - return false; + /** The tree for this node. */ + protected final CaseTree tree; + + /** + * The Node for the assignment of the switch selector expression to a synthetic local variable. + */ + protected final AssignmentNode selectorExprAssignment; + + /** + * The case expressions to match the switch expression against: the operands of (possibly + * multiple) case labels. + */ + protected final List caseExprs; + + /** The guard (the expression in the {@code when} clause) for this case. */ + protected final @Nullable Node guard; + + /** + * Create a new CaseNode. + * + * @param tree the tree for this node + * @param selectorExprAssignment the Node for the assignment of the switch selector expression + * to a synthetic local variable + * @param caseExprs the case expression(s) to match the switch expression against + * @param guard the guard expression or null + * @param types a factory of utility methods for operating on types + */ + public CaseNode( + CaseTree tree, + AssignmentNode selectorExprAssignment, + List caseExprs, + @Nullable Node guard, + Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.selectorExprAssignment = selectorExprAssignment; + this.caseExprs = caseExprs; + this.guard = guard; + } + + /** + * The Node for the assignment of the switch selector expression to a synthetic local variable. + * This is used to refine the type of the switch selector expression in a case block. + * + * @return the assignment of the switch selector expression to a synthetic local variable + */ + public AssignmentNode getSwitchOperand() { + return selectorExprAssignment; + } + + /** + * Gets the nodes corresponding to the case expressions. There can be multiple expressions since + * Java 12. + * + * @return the nodes corresponding to the (potentially multiple) case expressions + */ + public List getCaseOperands() { + return caseExprs; + } + + /** + * Gets the node for the guard (the expression in the {@code when} clause). + * + * @return the node for the guard + */ + public @Nullable Node getGuard() { + return guard; + } + + @Override + public CaseTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitCase(this, p); + } + + @Override + public String toString() { + return "case " + StringsPlume.join(", ", getCaseOperands()) + ":"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CaseNode)) { + return false; + } + CaseNode other = (CaseNode) obj; + return getSwitchOperand().equals(other.getSwitchOperand()) + && getCaseOperands().equals(other.getCaseOperands()); + } + + @Override + public int hashCode() { + return Objects.hash(getSwitchOperand(), getCaseOperands()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + List caseOperands = getCaseOperands(); + ArrayList operands = new ArrayList<>(caseOperands.size() + 1); + operands.add(getSwitchOperand()); + operands.addAll(caseOperands); + return operands; } - CaseNode other = (CaseNode) obj; - return getSwitchOperand().equals(other.getSwitchOperand()) - && getCaseOperands().equals(other.getCaseOperands()); - } - - @Override - public int hashCode() { - return Objects.hash(getSwitchOperand(), getCaseOperands()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - List caseOperands = getCaseOperands(); - ArrayList operands = new ArrayList<>(caseOperands.size() + 1); - operands.add(getSwitchOperand()); - operands.addAll(caseOperands); - return operands; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java index 0b7e8e85eef..95d42cbe51d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CatchMarkerNode.java @@ -1,64 +1,67 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; -import java.util.Objects; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.Objects; + +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + /** A CatchMarkerNode is a marker node for the beginning or end of a catch block. */ public class CatchMarkerNode extends MarkerNode { - /** The type of the exception parameter. */ - private final TypeMirror catchType; + /** The type of the exception parameter. */ + private final TypeMirror catchType; - /** The type utilities. */ - private final Types types; + /** The type utilities. */ + private final Types types; - /** - * Creates a new CatchMarkerNode. - * - * @param tree the tree - * @param startOrEnd {@code "start"} or {@code "end"} - * @param catchType the type of the exception parameter - * @param types the type utilities - */ - public CatchMarkerNode( - @Nullable Tree tree, String startOrEnd, TypeMirror catchType, Types types) { - super( - tree, - startOrEnd - + " of catch block for " - + TypesUtils.simpleTypeName(catchType) - + " #" - + (tree == null ? "null" : TreeUtils.treeUids.get(tree)), - types); - this.catchType = catchType; - this.types = types; - } + /** + * Creates a new CatchMarkerNode. + * + * @param tree the tree + * @param startOrEnd {@code "start"} or {@code "end"} + * @param catchType the type of the exception parameter + * @param types the type utilities + */ + public CatchMarkerNode( + @Nullable Tree tree, String startOrEnd, TypeMirror catchType, Types types) { + super( + tree, + startOrEnd + + " of catch block for " + + TypesUtils.simpleTypeName(catchType) + + " #" + + (tree == null ? "null" : TreeUtils.treeUids.get(tree)), + types); + this.catchType = catchType; + this.types = types; + } - /** - * Returns the type of the exception parameter. - * - * @return the type of the exception parameter - */ - public TypeMirror getCatchType() { - return catchType; - } + /** + * Returns the type of the exception parameter. + * + * @return the type of the exception parameter + */ + public TypeMirror getCatchType() { + return catchType; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof CatchMarkerNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CatchMarkerNode)) { + return false; + } + CatchMarkerNode other = (CatchMarkerNode) obj; + return types.isSameType(getCatchType(), other.getCatchType()) && super.equals(other); } - CatchMarkerNode other = (CatchMarkerNode) obj; - return types.isSameType(getCatchType(), other.getCatchType()) && super.equals(other); - } - @Override - public int hashCode() { - return Objects.hash(tree, getMessage(), catchType); - } + @Override + public int hashCode() { + return Objects.hash(tree, getMessage(), catchType); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java index 868b5cf843c..91c27e85ebb 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -15,33 +16,33 @@ */ public class CharacterLiteralNode extends ValueLiteralNode { - /** - * Create a new CharacterLiteralNode. - * - * @param t the character literal - */ - public CharacterLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.CHAR_LITERAL; - } + /** + * Create a new CharacterLiteralNode. + * + * @param t the character literal + */ + public CharacterLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.CHAR_LITERAL; + } - @Override - public Character getValue() { - return (Character) tree.getValue(); - } + @Override + public Character getValue() { + return (Character) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitCharacterLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitCharacterLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a CharacterLiteralNode - if (!(obj instanceof CharacterLiteralNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a CharacterLiteralNode + if (!(obj instanceof CharacterLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java index 0aa5e75b932..4a526b42e1d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassDeclarationNode.java @@ -1,13 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ClassTree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + /** * A node representing a class declaration that occurs within a method, for example, an anonymous * class declaration. In contrast to a top-level class declaration, such a declaration has an @@ -15,49 +17,49 @@ */ public class ClassDeclarationNode extends Node { - protected final ClassTree tree; - - public ClassDeclarationNode(ClassTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } + protected final ClassTree tree; - @Override - public ClassTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitClassDeclaration(this, p); - } + public ClassDeclarationNode(ClassTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - @Override - public String toString() { - return tree.toString(); - } + @Override + public ClassTree getTree() { + return tree; + } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitClassDeclaration(this, p); } - if (o == null || getClass() != o.getClass()) { - return false; + + @Override + public String toString() { + return tree.toString(); } - ClassDeclarationNode that = (ClassDeclarationNode) o; - return Objects.equals(tree, that.tree); - } + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - @Override - public int hashCode() { - return Objects.hash(tree); - } + ClassDeclarationNode that = (ClassDeclarationNode) o; + return Objects.equals(tree, that.tree); + } + + @Override + public int hashCode() { + return Objects.hash(tree); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java index ebad7f80fc4..b34299862fc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java @@ -4,16 +4,19 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; /** * A node representing a class name used in an expression such as a static method invocation. @@ -22,103 +25,103 @@ */ public class ClassNameNode extends Node { - /** The tree for this node. */ - protected final @Nullable Tree tree; - - /** The class named by this node. Either a TypeElement or a TypeParameterElement. */ - protected final Element element; - - /** The parent name, if any. */ - protected final @Nullable Node parent; - - public ClassNameNode(IdentifierTree tree) { - super(TreeUtils.typeOf(tree)); - assert tree.getKind() == Tree.Kind.IDENTIFIER; - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - Element element = TreeUtils.elementFromUse(tree); - assert element instanceof TypeElement || element instanceof TypeParameterElement - : "@AssumeAssertion(nullness)"; - this.element = element; - this.parent = null; - } - - /** - * Create a new ClassNameNode. - * - * @param tree the class tree for this node - */ - public ClassNameNode(ClassTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.element = TreeUtils.elementFromDeclaration(tree); - this.parent = null; - } - - public ClassNameNode(MemberSelectTree tree, Node parent) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - Element element = TreeUtils.elementFromUse(tree); - assert element instanceof TypeElement || element instanceof TypeParameterElement - : "@AssumeAssertion(nullness)"; - this.element = element; - this.parent = parent; - } - - public ClassNameNode(TypeMirror type, Element element) { - super(type); - this.tree = null; - this.element = element; - assert element instanceof TypeElement || element instanceof TypeParameterElement; - this.parent = null; - } - - public Element getElement() { - return element; - } - - /** The parent node of the current node. */ - public @Nullable Node getParent() { - return parent; - } - - @Override - public @Nullable Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitClassName(this, p); - } - - @Override - public String toString() { - return getElement().getSimpleName().toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ClassNameNode)) { - return false; + /** The tree for this node. */ + protected final @Nullable Tree tree; + + /** The class named by this node. Either a TypeElement or a TypeParameterElement. */ + protected final Element element; + + /** The parent name, if any. */ + protected final @Nullable Node parent; + + public ClassNameNode(IdentifierTree tree) { + super(TreeUtils.typeOf(tree)); + assert tree.getKind() == Tree.Kind.IDENTIFIER; + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + Element element = TreeUtils.elementFromUse(tree); + assert element instanceof TypeElement || element instanceof TypeParameterElement + : "@AssumeAssertion(nullness)"; + this.element = element; + this.parent = null; + } + + /** + * Create a new ClassNameNode. + * + * @param tree the class tree for this node + */ + public ClassNameNode(ClassTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.element = TreeUtils.elementFromDeclaration(tree); + this.parent = null; + } + + public ClassNameNode(MemberSelectTree tree, Node parent) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + Element element = TreeUtils.elementFromUse(tree); + assert element instanceof TypeElement || element instanceof TypeParameterElement + : "@AssumeAssertion(nullness)"; + this.element = element; + this.parent = parent; + } + + public ClassNameNode(TypeMirror type, Element element) { + super(type); + this.tree = null; + this.element = element; + assert element instanceof TypeElement || element instanceof TypeParameterElement; + this.parent = null; } - ClassNameNode other = (ClassNameNode) obj; - return Objects.equals(getParent(), other.getParent()) - && getElement().equals(other.getElement()); - } - - @Override - public int hashCode() { - return Objects.hash(getElement(), getParent()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - if (parent == null) { - return Collections.emptyList(); + + public Element getElement() { + return element; + } + + /** The parent node of the current node. */ + public @Nullable Node getParent() { + return parent; + } + + @Override + public @Nullable Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitClassName(this, p); + } + + @Override + public String toString() { + return getElement().getSimpleName().toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ClassNameNode)) { + return false; + } + ClassNameNode other = (ClassNameNode) obj; + return Objects.equals(getParent(), other.getParent()) + && getElement().equals(other.getElement()); + } + + @Override + public int hashCode() { + return Objects.hash(getElement(), getParent()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + if (parent == null) { + return Collections.emptyList(); + } + return Collections.singleton(parent); } - return Collections.singleton(parent); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java index 2a5f1ef2e4a..d87073ca647 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for a conditional and expression: * @@ -14,40 +16,40 @@ */ public class ConditionalAndNode extends BinaryOperationNode { - /** - * Create a new ConditionalAndNode. - * - * @param tree the conditional-and tree for this node - * @param left the first argument - * @param right the second argument - */ - public ConditionalAndNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.CONDITIONAL_AND; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitConditionalAnd(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " && " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ConditionalAndNode)) { - return false; + /** + * Create a new ConditionalAndNode. + * + * @param tree the conditional-and tree for this node + * @param left the first argument + * @param right the second argument + */ + public ConditionalAndNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.CONDITIONAL_AND; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitConditionalAnd(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " && " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ConditionalAndNode)) { + return false; + } + ConditionalAndNode other = (ConditionalAndNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - ConditionalAndNode other = (ConditionalAndNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java index 07629bbfe39..f6940487813 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for a conditional not expression: * @@ -14,38 +16,38 @@ */ public class ConditionalNotNode extends UnaryOperationNode { - /** - * Create a new ConditionalNotNode. - * - * @param tree the logical-complement tree for this node - * @param operand the boolean expression being negated - */ - public ConditionalNotNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.LOGICAL_COMPLEMENT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitConditionalNot(this, p); - } - - @Override - public String toString() { - return "(!" + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ConditionalNotNode)) { - return false; + /** + * Create a new ConditionalNotNode. + * + * @param tree the logical-complement tree for this node + * @param operand the boolean expression being negated + */ + public ConditionalNotNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.LOGICAL_COMPLEMENT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitConditionalNot(this, p); + } + + @Override + public String toString() { + return "(!" + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ConditionalNotNode)) { + return false; + } + ConditionalNotNode other = (ConditionalNotNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(ConditionalNotNode.class, getOperand()); } - ConditionalNotNode other = (ConditionalNotNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(ConditionalNotNode.class, getOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java index 793264a6e87..38704da607c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for a conditional or expression: * @@ -14,40 +16,40 @@ */ public class ConditionalOrNode extends BinaryOperationNode { - /** - * Create a new ConditionalOrNode. - * - * @param tree the conditional-or tree for this node - * @param left the first argument - * @param right the second argument - */ - public ConditionalOrNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.CONDITIONAL_OR; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitConditionalOr(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " || " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ConditionalOrNode)) { - return false; + /** + * Create a new ConditionalOrNode. + * + * @param tree the conditional-or tree for this node + * @param left the first argument + * @param right the second argument + */ + public ConditionalOrNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.CONDITIONAL_OR; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitConditionalOr(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " || " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ConditionalOrNode)) { + return false; + } + ConditionalOrNode other = (ConditionalOrNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - ConditionalOrNode other = (ConditionalOrNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java index d8bfaf04110..33ae66cc71e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DeconstructorPatternNode.java @@ -1,94 +1,98 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; /** A node for a deconstrutor pattern. */ public class DeconstructorPatternNode extends Node { - /** - * The {@code DeconstructorPatternTree}, declared as {@link Tree} to permit this file to compile - * under JDK 20 and earlier. - */ - protected final Tree deconstructorPattern; + /** + * The {@code DeconstructorPatternTree}, declared as {@link Tree} to permit this file to compile + * under JDK 20 and earlier. + */ + protected final Tree deconstructorPattern; - /** A list of nested pattern nodes. */ - protected final List nestedPatterns; + /** A list of nested pattern nodes. */ + protected final List nestedPatterns; - /** - * Creates a {@code DeconstructorPatternNode}. - * - * @param type the type of the node - * @param deconstructorPattern the {@code DeconstructorPatternTree} - * @param nestedPatterns a list of nested pattern nodes - */ - public DeconstructorPatternNode( - TypeMirror type, Tree deconstructorPattern, List nestedPatterns) { - super(type); - this.deconstructorPattern = deconstructorPattern; - this.nestedPatterns = nestedPatterns; - } + /** + * Creates a {@code DeconstructorPatternNode}. + * + * @param type the type of the node + * @param deconstructorPattern the {@code DeconstructorPatternTree} + * @param nestedPatterns a list of nested pattern nodes + */ + public DeconstructorPatternNode( + TypeMirror type, Tree deconstructorPattern, List nestedPatterns) { + super(type); + this.deconstructorPattern = deconstructorPattern; + this.nestedPatterns = nestedPatterns; + } - @Override - @Pure - public @Nullable Tree getTree() { - return deconstructorPattern; - } + @Override + @Pure + public @Nullable Tree getTree() { + return deconstructorPattern; + } - /** - * Returns the nested patterns. - * - * @return the nested patterns - */ - @Pure - public List getNestedPatterns() { - return nestedPatterns; - } + /** + * Returns the nested patterns. + * + * @return the nested patterns + */ + @Pure + public List getNestedPatterns() { + return nestedPatterns; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitDeconstructorPattern(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitDeconstructorPattern(this, p); + } - @Override - @Pure - public Collection getOperands() { - return nestedPatterns; - } + @Override + @Pure + public Collection getOperands() { + return nestedPatterns; + } - /** - * A list of nested binding variables. This is lazily initialized and should only be accessed by - * {@link #getBindingVariables()}. - */ - protected @MonotonicNonNull List bindingVariables = null; + /** + * A list of nested binding variables. This is lazily initialized and should only be accessed by + * {@link #getBindingVariables()}. + */ + protected @MonotonicNonNull List bindingVariables = null; - /** - * Return all the binding variables in this pattern. - * - * @return all the binding variables in this pattern - */ - public List getBindingVariables() { - if (bindingVariables == null) { - if (nestedPatterns.isEmpty()) { - bindingVariables = Collections.emptyList(); - } else { - bindingVariables = new ArrayList<>(nestedPatterns.size()); - for (Node patternNode : nestedPatterns) { - if (patternNode instanceof LocalVariableNode) { - bindingVariables.add((LocalVariableNode) patternNode); - } else { - bindingVariables.addAll(((DeconstructorPatternNode) patternNode).getBindingVariables()); - } + /** + * Return all the binding variables in this pattern. + * + * @return all the binding variables in this pattern + */ + public List getBindingVariables() { + if (bindingVariables == null) { + if (nestedPatterns.isEmpty()) { + bindingVariables = Collections.emptyList(); + } else { + bindingVariables = new ArrayList<>(nestedPatterns.size()); + for (Node patternNode : nestedPatterns) { + if (patternNode instanceof LocalVariableNode) { + bindingVariables.add((LocalVariableNode) patternNode); + } else { + bindingVariables.addAll( + ((DeconstructorPatternNode) patternNode).getBindingVariables()); + } + } + bindingVariables = Collections.unmodifiableList(bindingVariables); + } } - bindingVariables = Collections.unmodifiableList(bindingVariables); - } + return bindingVariables; } - return bindingVariables; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java index 7b014a27533..4907b234f40 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,33 +15,33 @@ */ public class DoubleLiteralNode extends ValueLiteralNode { - /** - * Create a new DoubleLiteralNode. - * - * @param t the tree for the literal value - */ - public DoubleLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.DOUBLE_LITERAL; - } + /** + * Create a new DoubleLiteralNode. + * + * @param t the tree for the literal value + */ + public DoubleLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.DOUBLE_LITERAL; + } - @Override - public Double getValue() { - return (Double) tree.getValue(); - } + @Override + public Double getValue() { + return (Double) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitDoubleLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitDoubleLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a DoubleLiteralNode - if (!(obj instanceof DoubleLiteralNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a DoubleLiteralNode + if (!(obj instanceof DoubleLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java index 0dab020afae..389da3c4fdd 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for an equality check: * @@ -14,40 +16,40 @@ */ public class EqualToNode extends BinaryOperationNode { - /** - * Create a new EqualToNode object. - * - * @param tree the tree for this node - * @param left the first argument - * @param right the second argument - */ - public EqualToNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.EQUAL_TO; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitEqualTo(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " == " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof EqualToNode)) { - return false; + /** + * Create a new EqualToNode object. + * + * @param tree the tree for this node + * @param left the first argument + * @param right the second argument + */ + public EqualToNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.EQUAL_TO; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitEqualTo(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " == " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof EqualToNode)) { + return false; + } + EqualToNode other = (EqualToNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - EqualToNode other = (EqualToNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java index dba86498d56..1176d970713 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExplicitThisNode.java @@ -1,6 +1,7 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.IdentifierTree; + import org.checkerframework.javacutil.TreeUtils; /** @@ -12,26 +13,26 @@ */ public class ExplicitThisNode extends ThisNode { - protected final IdentifierTree tree; + protected final IdentifierTree tree; - public ExplicitThisNode(IdentifierTree t) { - super(TreeUtils.typeOf(t)); - assert t.getName().contentEquals("this"); - tree = t; - } + public ExplicitThisNode(IdentifierTree t) { + super(TreeUtils.typeOf(t)); + assert t.getName().contentEquals("this"); + tree = t; + } - @Override - public IdentifierTree getTree() { - return tree; - } + @Override + public IdentifierTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitExplicitThis(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitExplicitThis(this, p); + } - @Override - public String toString() { - return "this"; - } + @Override + public String toString() { + return "this"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java index 14da7926d0d..60c12afeb70 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ExpressionStatementNode.java @@ -2,11 +2,13 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.TreeUtils; + import java.util.Collection; import java.util.Collections; import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.TreeUtils; /** * A node for an expression that is used as a statement. @@ -20,46 +22,46 @@ * node. */ public class ExpressionStatementNode extends Node { - /** The expression constituting this ExpressionStatementNode. */ - protected final ExpressionTree tree; + /** The expression constituting this ExpressionStatementNode. */ + protected final ExpressionTree tree; - /** - * Construct a ExpressionStatementNode. - * - * @param t the expression constituting this ExpressionStatementNode - */ - public ExpressionStatementNode(ExpressionTree t) { - super(TreeUtils.typeOf(t)); - tree = t; - } + /** + * Construct a ExpressionStatementNode. + * + * @param t the expression constituting this ExpressionStatementNode + */ + public ExpressionStatementNode(ExpressionTree t) { + super(TreeUtils.typeOf(t)); + tree = t; + } - @Override - public @Nullable Tree getTree() { - return null; - } + @Override + public @Nullable Tree getTree() { + return null; + } - @Override - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + public Collection getOperands() { + return Collections.emptyList(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitExpressionStatement(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitExpressionStatement(this, p); + } - @Override - public String toString() { - return "expression statement " + tree.toString(); - } + @Override + public String toString() { + return "expression statement " + tree.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - return this == obj; - } + @Override + public boolean equals(@Nullable Object obj) { + return this == obj; + } - @Override - public int hashCode() { - return Objects.hash(toString()); - } + @Override + public int hashCode() { + return Objects.hash(toString()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java index 7ad59de9eef..1cf3af49fe3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java @@ -3,16 +3,19 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +import javax.lang.model.element.VariableElement; + /** * A node for a field access, including a method accesses: * @@ -21,105 +24,106 @@ * */ public class FieldAccessNode extends Node { - /** The tree of the field access. */ - protected final Tree tree; - - /** The element of the accessed field. */ - protected final VariableElement element; - - /** The name of the accessed field. */ - protected final String field; - - /** The receiver node of the field access. */ - protected final Node receiver; - - /** - * Creates a new FieldAccessNode. - * - * @param tree the tree from which to create a FieldAccessNode - * @param receiver the receiver for the resulting FieldAccessNode - */ - public FieldAccessNode(Tree tree, Node receiver) { - super(TreeUtils.typeOf(tree)); - assert TreeUtils.isFieldAccess(tree); - this.tree = tree; - this.receiver = receiver; - this.field = TreeUtils.getFieldName(tree); - - if (tree instanceof MemberSelectTree) { - MemberSelectTree mstree = (MemberSelectTree) tree; - assert TreeUtils.isUseOfElement(mstree) : "@AssumeAssertion(nullness): tree kind"; - this.element = TreeUtils.variableElementFromUse(mstree); - } else if (tree instanceof IdentifierTree) { - IdentifierTree itree = (IdentifierTree) tree; - assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; - this.element = TreeUtils.variableElementFromUse(itree); - } else { - throw new BugInCF("unexpected tree %s [%s]", tree, tree.getClass()); + /** The tree of the field access. */ + protected final Tree tree; + + /** The element of the accessed field. */ + protected final VariableElement element; + + /** The name of the accessed field. */ + protected final String field; + + /** The receiver node of the field access. */ + protected final Node receiver; + + /** + * Creates a new FieldAccessNode. + * + * @param tree the tree from which to create a FieldAccessNode + * @param receiver the receiver for the resulting FieldAccessNode + */ + public FieldAccessNode(Tree tree, Node receiver) { + super(TreeUtils.typeOf(tree)); + assert TreeUtils.isFieldAccess(tree); + this.tree = tree; + this.receiver = receiver; + this.field = TreeUtils.getFieldName(tree); + + if (tree instanceof MemberSelectTree) { + MemberSelectTree mstree = (MemberSelectTree) tree; + assert TreeUtils.isUseOfElement(mstree) : "@AssumeAssertion(nullness): tree kind"; + this.element = TreeUtils.variableElementFromUse(mstree); + } else if (tree instanceof IdentifierTree) { + IdentifierTree itree = (IdentifierTree) tree; + assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; + this.element = TreeUtils.variableElementFromUse(itree); + } else { + throw new BugInCF("unexpected tree %s [%s]", tree, tree.getClass()); + } + } + + public FieldAccessNode(Tree tree, VariableElement element, Node receiver) { + super(element.asType()); + this.tree = tree; + this.element = element; + this.receiver = receiver; + this.field = element.getSimpleName().toString(); + } + + public VariableElement getElement() { + return element; + } + + public Node getReceiver() { + return receiver; + } + + public String getFieldName() { + return field; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFieldAccess(this, p); + } + + @Override + public String toString() { + return getReceiver() + "." + field; + } + + /** + * Determine whether the field is static or not. + * + * @return whether the field is static or not + */ + public boolean isStatic() { + return ElementUtils.isStatic(getElement()); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FieldAccessNode)) { + return false; + } + FieldAccessNode other = (FieldAccessNode) obj; + return getReceiver().equals(other.getReceiver()) + && getFieldName().equals(other.getFieldName()); + } + + @Override + public int hashCode() { + return Objects.hash(getReceiver(), getFieldName()); } - } - - public FieldAccessNode(Tree tree, VariableElement element, Node receiver) { - super(element.asType()); - this.tree = tree; - this.element = element; - this.receiver = receiver; - this.field = element.getSimpleName().toString(); - } - - public VariableElement getElement() { - return element; - } - - public Node getReceiver() { - return receiver; - } - - public String getFieldName() { - return field; - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFieldAccess(this, p); - } - - @Override - public String toString() { - return getReceiver() + "." + field; - } - - /** - * Determine whether the field is static or not. - * - * @return whether the field is static or not - */ - public boolean isStatic() { - return ElementUtils.isStatic(getElement()); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FieldAccessNode)) { - return false; + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(receiver); } - FieldAccessNode other = (FieldAccessNode) obj; - return getReceiver().equals(other.getReceiver()) && getFieldName().equals(other.getFieldName()); - } - - @Override - public int hashCode() { - return Objects.hash(getReceiver(), getFieldName()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(receiver); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java index 6f41a1c12ab..a7ad2b4b44c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,33 +15,33 @@ */ public class FloatLiteralNode extends ValueLiteralNode { - /** - * Create a new FloatLiteralNode. - * - * @param t the tree for the literal value - */ - public FloatLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.FLOAT_LITERAL; - } + /** + * Create a new FloatLiteralNode. + * + * @param t the tree for the literal value + */ + public FloatLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.FLOAT_LITERAL; + } - @Override - public Float getValue() { - return (Float) tree.getValue(); - } + @Override + public Float getValue() { + return (Float) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFloatLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFloatLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a FloatLiteralNode - if (!(obj instanceof FloatLiteralNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a FloatLiteralNode + if (!(obj instanceof FloatLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java index 12e91bbdcfa..a9fcd2748ef 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the floating-point division: * @@ -14,40 +16,40 @@ */ public class FloatingDivisionNode extends BinaryOperationNode { - /** - * Constructs a {@link FloatingDivisionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public FloatingDivisionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.DIVIDE; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFloatingDivision(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FloatingDivisionNode)) { - return false; + /** + * Constructs a {@link FloatingDivisionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public FloatingDivisionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.DIVIDE; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFloatingDivision(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FloatingDivisionNode)) { + return false; + } + FloatingDivisionNode other = (FloatingDivisionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - FloatingDivisionNode other = (FloatingDivisionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java index 134d18caa54..fb3f25eca42 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the floating-point remainder: * @@ -14,40 +16,40 @@ */ public class FloatingRemainderNode extends BinaryOperationNode { - /** - * Constructs a {@link FloatingRemainderNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public FloatingRemainderNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.REMAINDER; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitFloatingRemainder(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FloatingRemainderNode)) { - return false; + /** + * Constructs a {@link FloatingRemainderNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public FloatingRemainderNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.REMAINDER; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitFloatingRemainder(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FloatingRemainderNode)) { + return false; + } + FloatingRemainderNode other = (FloatingRemainderNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - FloatingRemainderNode other = (FloatingRemainderNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java index 830728c8d71..8e5ce2340ed 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java @@ -3,14 +3,16 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MemberReferenceTree; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + /** * A node for member references and lambdas. * @@ -31,62 +33,62 @@ */ public class FunctionalInterfaceNode extends Node { - protected final Tree tree; - - public FunctionalInterfaceNode(MemberReferenceTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } + protected final Tree tree; - public FunctionalInterfaceNode(LambdaExpressionTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } - - @Override - public Tree getTree() { - return tree; - } + public FunctionalInterfaceNode(MemberReferenceTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMemberReference(this, p); - } + public FunctionalInterfaceNode(LambdaExpressionTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - @Override - public String toString() { - if (tree instanceof LambdaExpressionTree) { - return "FunctionalInterfaceNode:" + ((LambdaExpressionTree) tree).getBodyKind(); - } else if (tree instanceof MemberReferenceTree) { - return "FunctionalInterfaceNode:" + ((MemberReferenceTree) tree).getName(); - } else { - // This should never happen. - throw new BugInCF("Invalid tree in FunctionalInterfaceNode"); + @Override + public Tree getTree() { + return tree; } - } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMemberReference(this, p); } - if (o == null || getClass() != o.getClass()) { - return false; + + @Override + public String toString() { + if (tree instanceof LambdaExpressionTree) { + return "FunctionalInterfaceNode:" + ((LambdaExpressionTree) tree).getBodyKind(); + } else if (tree instanceof MemberReferenceTree) { + return "FunctionalInterfaceNode:" + ((MemberReferenceTree) tree).getName(); + } else { + // This should never happen. + throw new BugInCF("Invalid tree in FunctionalInterfaceNode"); + } } - FunctionalInterfaceNode that = (FunctionalInterfaceNode) o; + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - return tree != null ? tree.equals(that.tree) : that.tree == null; - } + FunctionalInterfaceNode that = (FunctionalInterfaceNode) o; - @Override - public int hashCode() { - return Objects.hash(tree); - } + return tree != null ? tree.equals(that.tree) : that.tree == null; + } + + @Override + public int hashCode() { + return Objects.hash(tree); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java index 771d2992d20..5679d665fa4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the greater than comparison: * @@ -14,40 +16,40 @@ */ public class GreaterThanNode extends BinaryOperationNode { - /** - * Constructs a {@link GreaterThanNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public GreaterThanNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.GREATER_THAN; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitGreaterThan(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " > " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof GreaterThanNode)) { - return false; + /** + * Constructs a {@link GreaterThanNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public GreaterThanNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.GREATER_THAN; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitGreaterThan(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " > " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof GreaterThanNode)) { + return false; + } + GreaterThanNode other = (GreaterThanNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - GreaterThanNode other = (GreaterThanNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java index f6ecab0ab9d..3f76c748a5c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the greater than or equal comparison: * @@ -14,40 +16,40 @@ */ public class GreaterThanOrEqualNode extends BinaryOperationNode { - /** - * Constructs a {@link GreaterThanOrEqualNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public GreaterThanOrEqualNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.GREATER_THAN_EQUAL; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitGreaterThanOrEqual(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " >= " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof GreaterThanOrEqualNode)) { - return false; + /** + * Constructs a {@link GreaterThanOrEqualNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public GreaterThanOrEqualNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.GREATER_THAN_EQUAL; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitGreaterThanOrEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >= " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof GreaterThanOrEqualNode)) { + return false; + } + GreaterThanOrEqualNode other = (GreaterThanOrEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - GreaterThanOrEqualNode other = (GreaterThanOrEqualNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java index 751b50a8126..09b555d316c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ImplicitThisNode.java @@ -1,31 +1,33 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; +import javax.lang.model.type.TypeMirror; + /** A node to model the implicit {@code this}, e.g., in a field access. */ public class ImplicitThisNode extends ThisNode { - public ImplicitThisNode(TypeMirror type) { - super(type); - } - - @Override - public @Nullable Tree getTree() { - return null; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitImplicitThis(this, p); - } - - // In an inner class context, an implicit this may need to be represented as "Outer.this" rather - // than just as "this". This is context-dependent, and toString doesn't know if it is being - // used in an inner class context. - @Override - public String toString() { - return "(this)"; - } + public ImplicitThisNode(TypeMirror type) { + super(type); + } + + @Override + public @Nullable Tree getTree() { + return null; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitImplicitThis(this, p); + } + + // In an inner class context, an implicit this may need to be represented as "Outer.this" rather + // than just as "this". This is context-dependent, and toString doesn't know if it is being + // used in an inner class context. + @Override + public String toString() { + return "(this)"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java index 9df3aac02b8..c72bb91886b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java @@ -1,17 +1,20 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.InstanceOfTree; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TypesUtils; + import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; + import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TypesUtils; /** * A node for the instanceof operator: @@ -20,156 +23,156 @@ */ public class InstanceOfNode extends Node { - /** The value being tested. */ - protected final Node operand; - - /** The reference type being tested against. */ - protected final TypeMirror refType; - - /** The tree associated with this node. */ - protected final InstanceOfTree tree; - - /** The node of the pattern if one exists. */ - protected final @Nullable Node patternNode; - - /** For Types.isSameType. */ - protected final Types types; - - /** - * Create an InstanceOfNode. - * - * @param tree instanceof tree - * @param operand the expression in the instanceof tree - * @param refType the type in the instanceof - * @param types types util - */ - public InstanceOfNode(InstanceOfTree tree, Node operand, TypeMirror refType, Types types) { - this(tree, operand, null, refType, types); - } - - /** - * Create an InstanceOfNode. - * - * @param tree instanceof tree - * @param operand the expression in the instanceof tree - * @param patternNode the pattern node or null if there is none - * @param refType the type in the instanceof - * @param types types util - */ - public InstanceOfNode( - InstanceOfTree tree, - Node operand, - @Nullable Node patternNode, - TypeMirror refType, - Types types) { - super(types.getPrimitiveType(TypeKind.BOOLEAN)); - this.tree = tree; - this.operand = operand; - this.refType = refType; - this.types = types; - this.patternNode = patternNode; - } - - public Node getOperand() { - return operand; - } - - /** - * Returns the binding variable for this instanceof, or null if one does not exist. - * - * @return the binding variable for this instanceof, or null if one does not exist - * @deprecated Use {@link #getPatternNode()} or {@link #getBindingVariables()} instead. - */ - @Deprecated // 2023-09-24 - public @Nullable LocalVariableNode getBindingVariable() { - if (patternNode instanceof LocalVariableNode) { - return (LocalVariableNode) patternNode; + /** The value being tested. */ + protected final Node operand; + + /** The reference type being tested against. */ + protected final TypeMirror refType; + + /** The tree associated with this node. */ + protected final InstanceOfTree tree; + + /** The node of the pattern if one exists. */ + protected final @Nullable Node patternNode; + + /** For Types.isSameType. */ + protected final Types types; + + /** + * Create an InstanceOfNode. + * + * @param tree instanceof tree + * @param operand the expression in the instanceof tree + * @param refType the type in the instanceof + * @param types types util + */ + public InstanceOfNode(InstanceOfTree tree, Node operand, TypeMirror refType, Types types) { + this(tree, operand, null, refType, types); + } + + /** + * Create an InstanceOfNode. + * + * @param tree instanceof tree + * @param operand the expression in the instanceof tree + * @param patternNode the pattern node or null if there is none + * @param refType the type in the instanceof + * @param types types util + */ + public InstanceOfNode( + InstanceOfTree tree, + Node operand, + @Nullable Node patternNode, + TypeMirror refType, + Types types) { + super(types.getPrimitiveType(TypeKind.BOOLEAN)); + this.tree = tree; + this.operand = operand; + this.refType = refType; + this.types = types; + this.patternNode = patternNode; + } + + public Node getOperand() { + return operand; + } + + /** + * Returns the binding variable for this instanceof, or null if one does not exist. + * + * @return the binding variable for this instanceof, or null if one does not exist + * @deprecated Use {@link #getPatternNode()} or {@link #getBindingVariables()} instead. + */ + @Deprecated // 2023-09-24 + public @Nullable LocalVariableNode getBindingVariable() { + if (patternNode instanceof LocalVariableNode) { + return (LocalVariableNode) patternNode; + } + return null; + } + + /** + * A list of all binding variables in this instanceof. This is lazily initialized, use {@link + * #getBindingVariables()}. + */ + protected @MonotonicNonNull List bindingVariables = null; + + /** + * Return all the binding variables in this instanceof. + * + * @return all the binding variables in this instanceof + */ + public List getBindingVariables() { + if (bindingVariables == null) { + if (patternNode instanceof DeconstructorPatternNode) { + bindingVariables = ((DeconstructorPatternNode) patternNode).getBindingVariables(); + } else if (patternNode instanceof LocalVariableNode) { + bindingVariables = Collections.singletonList((LocalVariableNode) patternNode); + } else { + bindingVariables = Collections.emptyList(); + } + } + return bindingVariables; } - return null; - } - - /** - * A list of all binding variables in this instanceof. This is lazily initialized, use {@link - * #getBindingVariables()}. - */ - protected @MonotonicNonNull List bindingVariables = null; - - /** - * Return all the binding variables in this instanceof. - * - * @return all the binding variables in this instanceof - */ - public List getBindingVariables() { - if (bindingVariables == null) { - if (patternNode instanceof DeconstructorPatternNode) { - bindingVariables = ((DeconstructorPatternNode) patternNode).getBindingVariables(); - } else if (patternNode instanceof LocalVariableNode) { - bindingVariables = Collections.singletonList((LocalVariableNode) patternNode); - } else { - bindingVariables = Collections.emptyList(); - } + + /** + * Returns the pattern for this instanceof, or null if one does not exist. + * + * @return the pattern for this instanceof, or null if one does not exist + */ + public @Nullable Node getPatternNode() { + return patternNode; + } + + /** + * The reference type being tested against. + * + * @return the reference type + */ + public TypeMirror getRefType() { + return refType; + } + + @Override + public InstanceOfTree getTree() { + return tree; } - return bindingVariables; - } - - /** - * Returns the pattern for this instanceof, or null if one does not exist. - * - * @return the pattern for this instanceof, or null if one does not exist - */ - public @Nullable Node getPatternNode() { - return patternNode; - } - - /** - * The reference type being tested against. - * - * @return the reference type - */ - public TypeMirror getRefType() { - return refType; - } - - @Override - public InstanceOfTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitInstanceOf(this, p); - } - - @Override - public String toString() { - return "(" - + getOperand() - + " instanceof " - + TypesUtils.simpleTypeName(getRefType()) - + (patternNode == null ? "" : " " + getPatternNode()) - + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof InstanceOfNode)) { - return false; + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitInstanceOf(this, p); + } + + @Override + public String toString() { + return "(" + + getOperand() + + " instanceof " + + TypesUtils.simpleTypeName(getRefType()) + + (patternNode == null ? "" : " " + getPatternNode()) + + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof InstanceOfNode)) { + return false; + } + InstanceOfNode other = (InstanceOfNode) obj; + // TODO: TypeMirror.equals may be too restrictive. + // Check whether Types.isSameType is the better comparison. + return getOperand().equals(other.getOperand()) + && types.isSameType(getRefType(), other.getRefType()); + } + + @Override + public int hashCode() { + return Objects.hash(InstanceOfNode.class, getOperand()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); } - InstanceOfNode other = (InstanceOfNode) obj; - // TODO: TypeMirror.equals may be too restrictive. - // Check whether Types.isSameType is the better comparison. - return getOperand().equals(other.getOperand()) - && types.isSameType(getRefType(), other.getRefType()); - } - - @Override - public int hashCode() { - return Objects.hash(InstanceOfNode.class, getOperand()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java index ff6e5bd23e3..8307fc95edc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the integer division: * @@ -14,40 +16,40 @@ */ public class IntegerDivisionNode extends BinaryOperationNode { - /** - * Constructs an {@link IntegerDivisionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public IntegerDivisionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.DIVIDE; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitIntegerDivision(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof IntegerDivisionNode)) { - return false; + /** + * Constructs an {@link IntegerDivisionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public IntegerDivisionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.DIVIDE; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitIntegerDivision(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof IntegerDivisionNode)) { + return false; + } + IntegerDivisionNode other = (IntegerDivisionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - IntegerDivisionNode other = (IntegerDivisionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java index 209852218ee..b7c05eaa9d2 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -13,33 +14,33 @@ */ public class IntegerLiteralNode extends ValueLiteralNode { - /** - * Create a new IntegerLiteralNode. - * - * @param t the tree for the literal value - */ - public IntegerLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.INT_LITERAL; - } + /** + * Create a new IntegerLiteralNode. + * + * @param t the tree for the literal value + */ + public IntegerLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.INT_LITERAL; + } - @Override - public Integer getValue() { - return (Integer) tree.getValue(); - } + @Override + public Integer getValue() { + return (Integer) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitIntegerLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitIntegerLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a IntegerLiteralNode - if (!(obj instanceof IntegerLiteralNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a IntegerLiteralNode + if (!(obj instanceof IntegerLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java index 6a0a1b3f0f0..d80fc1339a2 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the integer remainder: * @@ -14,40 +16,40 @@ */ public class IntegerRemainderNode extends BinaryOperationNode { - /** - * Constructs an {@link IntegerRemainderNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public IntegerRemainderNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.REMAINDER; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitIntegerRemainder(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof IntegerRemainderNode)) { - return false; + /** + * Constructs an {@link IntegerRemainderNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public IntegerRemainderNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.REMAINDER; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitIntegerRemainder(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof IntegerRemainderNode)) { + return false; + } + IntegerRemainderNode other = (IntegerRemainderNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - IntegerRemainderNode other = (IntegerRemainderNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java index 48369f80034..27beb525617 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LambdaResultExpressionNode.java @@ -1,87 +1,89 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ExpressionTree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + /** A node for the single expression body of a single-expression lambda. */ public class LambdaResultExpressionNode extends Node { - /** Tree for the lambda expression body. */ - protected final ExpressionTree tree; + /** Tree for the lambda expression body. */ + protected final ExpressionTree tree; - /** Final CFG node corresponding to the lambda expression body. */ - protected final Node result; + /** Final CFG node corresponding to the lambda expression body. */ + protected final Node result; - /** - * Creates a LambdaResultExpressionNode. - * - * @param t tree for the lambda expression body - * @param result final CFG node corresponding to the lambda expression body - */ - public LambdaResultExpressionNode(ExpressionTree t, Node result) { - super(TreeUtils.typeOf(t)); - this.result = result; - tree = t; - } + /** + * Creates a LambdaResultExpressionNode. + * + * @param t tree for the lambda expression body + * @param result final CFG node corresponding to the lambda expression body + */ + public LambdaResultExpressionNode(ExpressionTree t, Node result) { + super(TreeUtils.typeOf(t)); + this.result = result; + tree = t; + } - /** - * Returns the final node of the CFG corresponding to the lambda expression body (see {@link - * #getTree()}). - * - * @return the final node of the CFG corresponding to the lambda expression body - */ - public Node getResult() { - return result; - } + /** + * Returns the final node of the CFG corresponding to the lambda expression body (see {@link + * #getTree()}). + * + * @return the final node of the CFG corresponding to the lambda expression body + */ + public Node getResult() { + return result; + } - /** - * Returns the {@link ExpressionTree} corresponding to the body of a lambda expression with an - * expression body (e.g. X for ({@code o -> X}) where X is an expression and not a {...} block). - * - * @return the {@link ExpressionTree} corresponding to the body of a lambda expression with an - * expression body - */ - @Override - public ExpressionTree getTree() { - return tree; - } + /** + * Returns the {@link ExpressionTree} corresponding to the body of a lambda expression with an + * expression body (e.g. X for ({@code o -> X}) where X is an expression and not a {...} block). + * + * @return the {@link ExpressionTree} corresponding to the body of a lambda expression with an + * expression body + */ + @Override + public ExpressionTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLambdaResultExpression(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLambdaResultExpression(this, p); + } - @Override - public String toString() { - return "-> " + result; - } + @Override + public String toString() { + return "-> " + result; + } - @Override - public boolean equals(@Nullable Object obj) { - // No need to compare tree, since in a well-formed LambdaResultExpressionNode, result will - // be the same only when tree is the same (this is similar to ReturnNode). - if (!(obj instanceof LambdaResultExpressionNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // No need to compare tree, since in a well-formed LambdaResultExpressionNode, result will + // be the same only when tree is the same (this is similar to ReturnNode). + if (!(obj instanceof LambdaResultExpressionNode)) { + return false; + } + LambdaResultExpressionNode other = (LambdaResultExpressionNode) obj; + return Objects.equals(result, other.result); } - LambdaResultExpressionNode other = (LambdaResultExpressionNode) obj; - return Objects.equals(result, other.result); - } - @Override - public int hashCode() { - // No need to incorporate tree, since in a well-formed LambdaResultExpressionNode, result - // will be the same only when tree is the same (this is similar to ReturnNode). - return Objects.hash(LambdaResultExpressionNode.class, result); - } + @Override + public int hashCode() { + // No need to incorporate tree, since in a well-formed LambdaResultExpressionNode, result + // will be the same only when tree is the same (this is similar to ReturnNode). + return Objects.hash(LambdaResultExpressionNode.class, result); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(result); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(result); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java index b6b38e85062..8ab462127e3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for bitwise left shift operations: * @@ -14,40 +16,40 @@ */ public class LeftShiftNode extends BinaryOperationNode { - /** - * Constructs a {@link LeftShiftNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public LeftShiftNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.LEFT_SHIFT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLeftShift(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " << " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LeftShiftNode)) { - return false; + /** + * Constructs a {@link LeftShiftNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public LeftShiftNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.LEFT_SHIFT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLeftShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " << " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LeftShiftNode)) { + return false; + } + LeftShiftNode other = (LeftShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - LeftShiftNode other = (LeftShiftNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java index 8b5595938f3..5237ee99d2a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the less than comparison: * @@ -16,40 +18,40 @@ */ public class LessThanNode extends BinaryOperationNode { - /** - * Constructs a {@link LessThanNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public LessThanNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.LESS_THAN; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLessThan(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " < " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LessThanNode)) { - return false; + /** + * Constructs a {@link LessThanNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public LessThanNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.LESS_THAN; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLessThan(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " < " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LessThanNode)) { + return false; + } + LessThanNode other = (LessThanNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - LessThanNode other = (LessThanNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java index 5f13d004455..cb29c4adbcf 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the less than or equal comparison: * @@ -14,40 +16,40 @@ */ public class LessThanOrEqualNode extends BinaryOperationNode { - /** - * Constructs a {@link LessThanOrEqualNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public LessThanOrEqualNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.LESS_THAN_EQUAL; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLessThanOrEqual(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " <= " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LessThanOrEqualNode)) { - return false; + /** + * Constructs a {@link LessThanOrEqualNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public LessThanOrEqualNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.LESS_THAN_EQUAL; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLessThanOrEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " <= " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LessThanOrEqualNode)) { + return false; + } + LessThanOrEqualNode other = (LessThanOrEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - LessThanOrEqualNode other = (LessThanOrEqualNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java index ca171272bed..6d477b7962a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java @@ -3,13 +3,16 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.element.VariableElement; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; /** * A node for a local variable or a parameter: @@ -24,99 +27,99 @@ // TODO: don't use for parameters, as they don't have a tree public class LocalVariableNode extends Node { - /** The tree for the local variable. */ - protected final Tree tree; - - /** The receiver node for the local variable, {@code null} otherwise. */ - protected final @Nullable Node receiver; - - /** - * Create a new local variable node for the given tree. - * - * @param tree the tree for the local variable: a VariableTree or an IdentifierTree - */ - public LocalVariableNode(Tree tree) { - this(tree, null); - } - - /** - * Create a new local variable node for the given tree and receiver. - * - * @param tree the tree for the local variable: a VariableTree or an IdentifierTree - * @param receiver the receiver for the local variable, or null if none - */ - public LocalVariableNode(Tree tree, @Nullable Node receiver) { - super(TreeUtils.typeOf(tree)); - // IdentifierTree for normal uses of the local variable or parameter, - // and VariableTree for declarations or the translation of an initializer block - assert tree != null; - assert tree instanceof IdentifierTree || tree instanceof VariableTree; - this.tree = tree; - this.receiver = receiver; - } - - /** - * Returns the element associated with this local variable. - * - * @return the element associated with this local variable - */ - public VariableElement getElement() { - VariableElement el; - if (tree instanceof IdentifierTree) { - IdentifierTree itree = (IdentifierTree) tree; - assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; - el = TreeUtils.variableElementFromUse(itree); - } else { - assert tree instanceof VariableTree; - el = TreeUtils.elementFromDeclaration((VariableTree) tree); + /** The tree for the local variable. */ + protected final Tree tree; + + /** The receiver node for the local variable, {@code null} otherwise. */ + protected final @Nullable Node receiver; + + /** + * Create a new local variable node for the given tree. + * + * @param tree the tree for the local variable: a VariableTree or an IdentifierTree + */ + public LocalVariableNode(Tree tree) { + this(tree, null); } - return el; - } - /** The receiver node for the local variable, {@code null} otherwise. */ - public @Nullable Node getReceiver() { - return receiver; - } + /** + * Create a new local variable node for the given tree and receiver. + * + * @param tree the tree for the local variable: a VariableTree or an IdentifierTree + * @param receiver the receiver for the local variable, or null if none + */ + public LocalVariableNode(Tree tree, @Nullable Node receiver) { + super(TreeUtils.typeOf(tree)); + // IdentifierTree for normal uses of the local variable or parameter, + // and VariableTree for declarations or the translation of an initializer block + assert tree != null; + assert tree instanceof IdentifierTree || tree instanceof VariableTree; + this.tree = tree; + this.receiver = receiver; + } + + /** + * Returns the element associated with this local variable. + * + * @return the element associated with this local variable + */ + public VariableElement getElement() { + VariableElement el; + if (tree instanceof IdentifierTree) { + IdentifierTree itree = (IdentifierTree) tree; + assert TreeUtils.isUseOfElement(itree) : "@AssumeAssertion(nullness): tree kind"; + el = TreeUtils.variableElementFromUse(itree); + } else { + assert tree instanceof VariableTree; + el = TreeUtils.elementFromDeclaration((VariableTree) tree); + } + return el; + } - public String getName() { - if (tree instanceof IdentifierTree) { - return ((IdentifierTree) tree).getName().toString(); + /** The receiver node for the local variable, {@code null} otherwise. */ + public @Nullable Node getReceiver() { + return receiver; } - return ((VariableTree) tree).getName().toString(); - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLocalVariable(this, p); - } - - @Override - public String toString() { - return getName().toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LocalVariableNode)) { - return false; + + public String getName() { + if (tree instanceof IdentifierTree) { + return ((IdentifierTree) tree).getName().toString(); + } + return ((VariableTree) tree).getName().toString(); + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLocalVariable(this, p); + } + + @Override + public String toString() { + return getName().toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LocalVariableNode)) { + return false; + } + LocalVariableNode other = (LocalVariableNode) obj; + return getName().equals(other.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(getName()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); } - LocalVariableNode other = (LocalVariableNode) obj; - return getName().equals(other.getName()); - } - - @Override - public int hashCode() { - return Objects.hash(getName()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java index 14818335eb8..081633ead10 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -14,33 +15,33 @@ */ public class LongLiteralNode extends ValueLiteralNode { - /** - * Create a new LongLiteralNode. - * - * @param t the tree for the literal value - */ - public LongLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.LONG_LITERAL; - } + /** + * Create a new LongLiteralNode. + * + * @param t the tree for the literal value + */ + public LongLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.LONG_LITERAL; + } - @Override - public Long getValue() { - return (Long) tree.getValue(); - } + @Override + public Long getValue() { + return (Long) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitLongLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitLongLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a LongLiteralNode - if (!(obj instanceof LongLiteralNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a LongLiteralNode + if (!(obj instanceof LongLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java index 06a4fc8b0e3..658551a458e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * MarkerNodes are no-op Nodes used for debugging information. They can hold a Tree and a message, @@ -17,55 +20,56 @@ */ public class MarkerNode extends Node { - protected final @Nullable Tree tree; - protected final String message; + protected final @Nullable Tree tree; + protected final String message; - public MarkerNode(@Nullable Tree tree, String message, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.message = message; - } + public MarkerNode(@Nullable Tree tree, String message, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.message = message; + } - public String getMessage() { - return message; - } + public String getMessage() { + return message; + } - @Override - public @Nullable Tree getTree() { - return tree; - } + @Override + public @Nullable Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMarker(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMarker(this, p); + } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("marker ("); - sb.append(message); - sb.append(")"); - return sb.toString(); - } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("marker ("); + sb.append(message); + sb.append(")"); + return sb.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof MarkerNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof MarkerNode)) { + return false; + } + MarkerNode other = (MarkerNode) obj; + return Objects.equals(getTree(), other.getTree()) + && getMessage().equals(other.getMessage()); } - MarkerNode other = (MarkerNode) obj; - return Objects.equals(getTree(), other.getTree()) && getMessage().equals(other.getMessage()); - } - @Override - public int hashCode() { - return Objects.hash(tree, getMessage()); - } + @Override + public int hashCode() { + return Objects.hash(tree, getMessage()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java index 276db20f5af..b35eabe3d04 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java @@ -2,15 +2,18 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +import javax.lang.model.element.ExecutableElement; + /** * A node for a method access, including a receiver: * @@ -19,90 +22,90 @@ * */ public class MethodAccessNode extends Node { - /** The tree of the method access. */ - protected final ExpressionTree tree; - - /** The element of the accessed method. */ - protected final ExecutableElement method; - - /** The receiver node of the method access. */ - protected final Node receiver; - - /** - * Create a new MethodAccessNode. - * - * @param tree the expression that is a method access - * @param receiver the receiver - */ - public MethodAccessNode(ExpressionTree tree, Node receiver) { - this(tree, (ExecutableElement) TreeUtils.elementFromUse(tree), receiver); - } - - /** - * Create a new MethodAccessNode. - * - * @param tree the expression that is a method access - * @param method the element for the method - * @param receiver the receiver - */ - public MethodAccessNode(ExpressionTree tree, ExecutableElement method, Node receiver) { - super(TreeUtils.typeOf(tree)); - assert TreeUtils.isMethodAccess(tree); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - this.method = method; - this.receiver = receiver; - } - - public ExecutableElement getMethod() { - return method; - } - - public Node getReceiver() { - return receiver; - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMethodAccess(this, p); - } - - @Override - public String toString() { - return getReceiver() + "." + method.getSimpleName(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof MethodAccessNode)) { - return false; + /** The tree of the method access. */ + protected final ExpressionTree tree; + + /** The element of the accessed method. */ + protected final ExecutableElement method; + + /** The receiver node of the method access. */ + protected final Node receiver; + + /** + * Create a new MethodAccessNode. + * + * @param tree the expression that is a method access + * @param receiver the receiver + */ + public MethodAccessNode(ExpressionTree tree, Node receiver) { + this(tree, (ExecutableElement) TreeUtils.elementFromUse(tree), receiver); + } + + /** + * Create a new MethodAccessNode. + * + * @param tree the expression that is a method access + * @param method the element for the method + * @param receiver the receiver + */ + public MethodAccessNode(ExpressionTree tree, ExecutableElement method, Node receiver) { + super(TreeUtils.typeOf(tree)); + assert TreeUtils.isMethodAccess(tree); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + this.method = method; + this.receiver = receiver; + } + + public ExecutableElement getMethod() { + return method; + } + + public Node getReceiver() { + return receiver; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMethodAccess(this, p); + } + + @Override + public String toString() { + return getReceiver() + "." + method.getSimpleName(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof MethodAccessNode)) { + return false; + } + MethodAccessNode other = (MethodAccessNode) obj; + return getReceiver().equals(other.getReceiver()) && getMethod().equals(other.getMethod()); + } + + @Override + public int hashCode() { + return Objects.hash(getReceiver(), getMethod()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(receiver); + } + + /** + * Determine whether the method is static or not. + * + * @return whether the method is static or not + */ + public boolean isStatic() { + return ElementUtils.isStatic(getMethod()); } - MethodAccessNode other = (MethodAccessNode) obj; - return getReceiver().equals(other.getReceiver()) && getMethod().equals(other.getMethod()); - } - - @Override - public int hashCode() { - return Objects.hash(getReceiver(), getMethod()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(receiver); - } - - /** - * Determine whether the method is static or not. - * - * @return whether the method is static or not - */ - public boolean isStatic() { - return ElementUtils.isStatic(getMethod()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java index 212a0860db3..5c444b2e4ec 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java @@ -4,15 +4,17 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; +import org.plumelib.util.StringsPlume; + import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; -import org.plumelib.util.StringsPlume; /** * A node for method invocation. @@ -26,128 +28,128 @@ */ public class MethodInvocationNode extends Node { - /** The tree for the method invocation. */ - protected final @Nullable MethodInvocationTree tree; - - /** - * The MethodAccessNode for the method being invoked. Includes the receiver if any. For a static - * method, the receiver may be a class name. - */ - protected final MethodAccessNode target; - - /** The arguments of the method invocation. */ - protected final List arguments; - - /** The tree path to the method invocation. */ - protected final TreePath treePath; - - /** - * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an - * enhanced for loop, then the {@code iterExpression} field is the expression in the for loop, - * e.g., {@code iter} in {@code for(Object o: iter}. - * - *

          Is set by {@link #setIterableExpression}. - */ - protected @Nullable ExpressionTree iterableExpression; - - /** - * Create a MethodInvocationNode. - * - * @param tree for the method invocation - * @param target the MethodAccessNode for the method being invoked - * @param arguments arguments of the method invocation - * @param treePath path to the method invocation - */ - public MethodInvocationNode( - @Nullable MethodInvocationTree tree, - MethodAccessNode target, - List arguments, - TreePath treePath) { - super(tree != null ? TreeUtils.typeOf(tree) : target.getMethod().getReturnType()); - this.tree = tree; - this.target = target; - this.arguments = arguments; - this.treePath = treePath; - } - - public MethodInvocationNode(MethodAccessNode target, List arguments, TreePath treePath) { - this(null, target, arguments, treePath); - } - - public MethodAccessNode getTarget() { - return target; - } - - public List getArguments() { - return arguments; - } - - public Node getArgument(int i) { - return arguments.get(i); - } - - public TreePath getTreePath() { - return treePath; - } - - /** - * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an - * enhanced for loop, then return the expression in the for loop, e.g., {@code iter} in {@code - * for(Object o: iter}. Otherwise, return null. - * - * @return the iter expression, or null if this is not a {@link Iterator#next()} from an enhanced - * for loop - */ - public @Nullable ExpressionTree getIterableExpression() { - return iterableExpression; - } - - /** - * Set the iterable expression from a for loop. - * - * @param iterableExpression iterable expression - * @see #getIterableExpression() - */ - public void setIterableExpression(@Nullable ExpressionTree iterableExpression) { - this.iterableExpression = iterableExpression; - } - - @Override - public @Nullable MethodInvocationTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitMethodInvocation(this, p); - } - - @Override - public String toString() { - return target + "(" + StringsPlume.join(", ", arguments) + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof MethodInvocationNode)) { - return false; + /** The tree for the method invocation. */ + protected final @Nullable MethodInvocationTree tree; + + /** + * The MethodAccessNode for the method being invoked. Includes the receiver if any. For a static + * method, the receiver may be a class name. + */ + protected final MethodAccessNode target; + + /** The arguments of the method invocation. */ + protected final List arguments; + + /** The tree path to the method invocation. */ + protected final TreePath treePath; + + /** + * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an + * enhanced for loop, then the {@code iterExpression} field is the expression in the for loop, + * e.g., {@code iter} in {@code for(Object o: iter}. + * + *

          Is set by {@link #setIterableExpression}. + */ + protected @Nullable ExpressionTree iterableExpression; + + /** + * Create a MethodInvocationNode. + * + * @param tree for the method invocation + * @param target the MethodAccessNode for the method being invoked + * @param arguments arguments of the method invocation + * @param treePath path to the method invocation + */ + public MethodInvocationNode( + @Nullable MethodInvocationTree tree, + MethodAccessNode target, + List arguments, + TreePath treePath) { + super(tree != null ? TreeUtils.typeOf(tree) : target.getMethod().getReturnType()); + this.tree = tree; + this.target = target; + this.arguments = arguments; + this.treePath = treePath; + } + + public MethodInvocationNode(MethodAccessNode target, List arguments, TreePath treePath) { + this(null, target, arguments, treePath); + } + + public MethodAccessNode getTarget() { + return target; + } + + public List getArguments() { + return arguments; + } + + public Node getArgument(int i) { + return arguments.get(i); + } + + public TreePath getTreePath() { + return treePath; + } + + /** + * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an + * enhanced for loop, then return the expression in the for loop, e.g., {@code iter} in {@code + * for(Object o: iter}. Otherwise, return null. + * + * @return the iter expression, or null if this is not a {@link Iterator#next()} from an + * enhanced for loop + */ + public @Nullable ExpressionTree getIterableExpression() { + return iterableExpression; + } + + /** + * Set the iterable expression from a for loop. + * + * @param iterableExpression iterable expression + * @see #getIterableExpression() + */ + public void setIterableExpression(@Nullable ExpressionTree iterableExpression) { + this.iterableExpression = iterableExpression; + } + + @Override + public @Nullable MethodInvocationTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitMethodInvocation(this, p); + } + + @Override + public String toString() { + return target + "(" + StringsPlume.join(", ", arguments) + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof MethodInvocationNode)) { + return false; + } + MethodInvocationNode other = (MethodInvocationNode) obj; + + return getTarget().equals(other.getTarget()) && getArguments().equals(other.getArguments()); + } + + @Override + public int hashCode() { + return Objects.hash(target, arguments); + } + + @Override + @SideEffectFree + public Collection getOperands() { + List list = new ArrayList<>(1 + arguments.size()); + list.add(target); + list.addAll(arguments); + return list; } - MethodInvocationNode other = (MethodInvocationNode) obj; - - return getTarget().equals(other.getTarget()) && getArguments().equals(other.getArguments()); - } - - @Override - public int hashCode() { - return Objects.hash(target, arguments); - } - - @Override - @SideEffectFree - public Collection getOperands() { - List list = new ArrayList<>(1 + arguments.size()); - list.add(target); - list.addAll(arguments); - return list; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java index 68e601e45a7..9f187c33426 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TypesUtils; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TypesUtils; /** * A node for the narrowing primitive conversion operation. See JLS 5.1.3 for the definition of @@ -19,53 +22,53 @@ */ public class NarrowingConversionNode extends Node { - protected final Tree tree; - protected final Node operand; + protected final Tree tree; + protected final Node operand; - public NarrowingConversionNode(Tree tree, Node operand, TypeMirror type) { - super(type); - assert TypesUtils.isPrimitive(type) : "non-primitive type in narrowing conversion"; - this.tree = tree; - this.operand = operand; - } + public NarrowingConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + assert TypesUtils.isPrimitive(type) : "non-primitive type in narrowing conversion"; + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNarrowingConversion(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNarrowingConversion(this, p); + } - @Override - public String toString() { - return "NarrowingConversion(" + getOperand() + ", " + type + ")"; - } + @Override + public String toString() { + return "NarrowingConversion(" + getOperand() + ", " + type + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NarrowingConversionNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NarrowingConversionNode)) { + return false; + } + NarrowingConversionNode other = (NarrowingConversionNode) obj; + return getOperand().equals(other.getOperand()) + && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); } - NarrowingConversionNode other = (NarrowingConversionNode) obj; - return getOperand().equals(other.getOperand()) - && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); - } - @Override - public int hashCode() { - return Objects.hash(NarrowingConversionNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(NarrowingConversionNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java index d249fa59344..0fdd5dfbe34 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/Node.java @@ -1,11 +1,7 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicLong; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.block.Block; @@ -14,6 +10,13 @@ import org.checkerframework.dataflow.qual.SideEffectFree; import org.plumelib.util.UniqueId; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; + +import javax.lang.model.type.TypeMirror; + /** * A node in the abstract representation used for Java code inside a basic block. * @@ -35,179 +38,179 @@ */ public abstract class Node implements UniqueId { - /** - * The basic block this node belongs to. If null, this object represents a method formal - * parameter. - * - *

          Is set by {@link #setBlock}. - */ - protected @Nullable Block block; - - /** - * Is this node an l-value? - * - *

          Is set by {@link #setLValue}. - */ - protected boolean lvalue = false; - - /** - * Does this node represent a tree that appears in the source code (true) or one that the CFG - * builder added while desugaring (false). - * - *

          Is set by {@link #setInSource}. - */ - protected boolean inSource = true; - - /** - * The type of this node. For {@link Node}s with {@link Tree}s, this type is the type of the - * {@link Tree}. Otherwise, it is the type is set by the {@link CFGBuilder}. - */ - protected final TypeMirror type; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - @Pure - public long getUid(@UnknownInitialization Node this) { - return uid; - } - - /** - * Creates a new Node. - * - * @param type the type of the node - */ - protected Node(TypeMirror type) { - assert type != null; - this.type = type; - } - - /** - * Returns the basic block this node belongs to (or {@code null} if it represents the parameter of - * a method). - * - * @return the basic block this node belongs to (or {@code null} if it represents the parameter of - * a method) - */ - @Pure - public @Nullable Block getBlock() { - return block; - } - - /** Set the basic block this node belongs to. */ - public void setBlock(Block b) { - block = b; - } - - /** - * Returns the {@link Tree} in the abstract syntax tree, or {@code null} if no corresponding tree - * exists. For instance, this is the case for an {@link ImplicitThisNode}. - * - * @return the corresponding {@link Tree} or {@code null} - */ - @Pure - public abstract @Nullable Tree getTree(); - - /** - * Returns a {@link TypeMirror} representing the type of a {@link Node}. A {@link Node} will - * always have a type even when it has no {@link Tree}. - * - * @return a {@link TypeMirror} representing the type of this {@link Node} - */ - @Pure - public TypeMirror getType() { - return type; - } - - /** - * Accept method of the visitor pattern. - * - * @param result type of the operation - * @param

          parameter type - * @param visitor the visitor to be applied to this node - * @param p the parameter for this operation - */ - public abstract R accept(NodeVisitor visitor, P p); - - /** Is the node an lvalue or not? */ - @Pure - public boolean isLValue() { - return lvalue; - } - - /** Make this node an l-value. */ - public void setLValue() { - lvalue = true; - } - - /** - * Return whether this node represents a tree that appears in the source code (true) or one that - * the CFG or builder added while desugaring (false). - * - * @return whether this node represents a tree that appears in the source code - */ - @Pure - public boolean getInSource() { - return inSource; - } - - public void setInSource(boolean inSrc) { - inSource = inSrc; - } - - /** - * Returns a collection containing all of the operand {@link Node}s of this {@link Node}. - * - * @return a collection containing all of the operand {@link Node}s of this {@link Node} - */ - @SideEffectFree - public abstract Collection getOperands(); - - /** - * Returns a collection containing all of the operand {@link Node}s of this {@link Node}, as well - * as (transitively) the operands of its operands. - * - * @return a collection containing all of the operand {@link Node}s of this {@link Node}, as well - * as (transitively) the operands of its operands - */ - @Pure - public Collection getTransitiveOperands() { - ArrayDeque operands = new ArrayDeque<>(getOperands()); - ArrayDeque transitiveOperands = new ArrayDeque<>(operands.size()); - while (!operands.isEmpty()) { - Node next = operands.removeFirst(); - operands.addAll(next.getOperands()); - transitiveOperands.add(next); + /** + * The basic block this node belongs to. If null, this object represents a method formal + * parameter. + * + *

          Is set by {@link #setBlock}. + */ + protected @Nullable Block block; + + /** + * Is this node an l-value? + * + *

          Is set by {@link #setLValue}. + */ + protected boolean lvalue = false; + + /** + * Does this node represent a tree that appears in the source code (true) or one that the CFG + * builder added while desugaring (false). + * + *

          Is set by {@link #setInSource}. + */ + protected boolean inSource = true; + + /** + * The type of this node. For {@link Node}s with {@link Tree}s, this type is the type of the + * {@link Tree}. Otherwise, it is the type is set by the {@link CFGBuilder}. + */ + protected final TypeMirror type; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + @Pure + public long getUid(@UnknownInitialization Node this) { + return uid; + } + + /** + * Creates a new Node. + * + * @param type the type of the node + */ + protected Node(TypeMirror type) { + assert type != null; + this.type = type; + } + + /** + * Returns the basic block this node belongs to (or {@code null} if it represents the parameter + * of a method). + * + * @return the basic block this node belongs to (or {@code null} if it represents the parameter + * of a method) + */ + @Pure + public @Nullable Block getBlock() { + return block; } - return transitiveOperands; - } - - /** - * Returns a verbose string representation of this, useful for debugging. - * - * @return a printed representation of this - */ - @Pure - public String toStringDebug() { - return String.format("%s [%s]", this, this.getClassAndUid()); - } - - /** - * Returns a verbose string representation of a collection of nodes, useful for debugging.. - * - * @param nodes a collection of nodes to format - * @return a printed representation of the given collection - */ - @Pure - public static String nodeCollectionToString(Collection nodes) { - StringJoiner result = new StringJoiner(", ", "[", "]"); - for (Node n : nodes) { - result.add(n.toStringDebug()); + + /** Set the basic block this node belongs to. */ + public void setBlock(Block b) { + block = b; + } + + /** + * Returns the {@link Tree} in the abstract syntax tree, or {@code null} if no corresponding + * tree exists. For instance, this is the case for an {@link ImplicitThisNode}. + * + * @return the corresponding {@link Tree} or {@code null} + */ + @Pure + public abstract @Nullable Tree getTree(); + + /** + * Returns a {@link TypeMirror} representing the type of a {@link Node}. A {@link Node} will + * always have a type even when it has no {@link Tree}. + * + * @return a {@link TypeMirror} representing the type of this {@link Node} + */ + @Pure + public TypeMirror getType() { + return type; + } + + /** + * Accept method of the visitor pattern. + * + * @param result type of the operation + * @param

          parameter type + * @param visitor the visitor to be applied to this node + * @param p the parameter for this operation + */ + public abstract R accept(NodeVisitor visitor, P p); + + /** Is the node an lvalue or not? */ + @Pure + public boolean isLValue() { + return lvalue; + } + + /** Make this node an l-value. */ + public void setLValue() { + lvalue = true; + } + + /** + * Return whether this node represents a tree that appears in the source code (true) or one that + * the CFG or builder added while desugaring (false). + * + * @return whether this node represents a tree that appears in the source code + */ + @Pure + public boolean getInSource() { + return inSource; + } + + public void setInSource(boolean inSrc) { + inSource = inSrc; + } + + /** + * Returns a collection containing all of the operand {@link Node}s of this {@link Node}. + * + * @return a collection containing all of the operand {@link Node}s of this {@link Node} + */ + @SideEffectFree + public abstract Collection getOperands(); + + /** + * Returns a collection containing all of the operand {@link Node}s of this {@link Node}, as + * well as (transitively) the operands of its operands. + * + * @return a collection containing all of the operand {@link Node}s of this {@link Node}, as + * well as (transitively) the operands of its operands + */ + @Pure + public Collection getTransitiveOperands() { + ArrayDeque operands = new ArrayDeque<>(getOperands()); + ArrayDeque transitiveOperands = new ArrayDeque<>(operands.size()); + while (!operands.isEmpty()) { + Node next = operands.removeFirst(); + operands.addAll(next.getOperands()); + transitiveOperands.add(next); + } + return transitiveOperands; + } + + /** + * Returns a verbose string representation of this, useful for debugging. + * + * @return a printed representation of this + */ + @Pure + public String toStringDebug() { + return String.format("%s [%s]", this, this.getClassAndUid()); + } + + /** + * Returns a verbose string representation of a collection of nodes, useful for debugging.. + * + * @param nodes a collection of nodes to format + * @return a printed representation of the given collection + */ + @Pure + public static String nodeCollectionToString(Collection nodes) { + StringJoiner result = new StringJoiner(", ", "[", "]"); + for (Node n : nodes) { + result.add(n.toStringDebug()); + } + return result.toString(); } - return result.toString(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java index ad6be81daa9..20211a9619b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java @@ -9,180 +9,180 @@ * parameter. */ public interface NodeVisitor { - // Literals - R visitShortLiteral(ShortLiteralNode n, P p); + // Literals + R visitShortLiteral(ShortLiteralNode n, P p); - R visitIntegerLiteral(IntegerLiteralNode n, P p); + R visitIntegerLiteral(IntegerLiteralNode n, P p); - R visitLongLiteral(LongLiteralNode n, P p); + R visitLongLiteral(LongLiteralNode n, P p); - R visitFloatLiteral(FloatLiteralNode n, P p); + R visitFloatLiteral(FloatLiteralNode n, P p); - R visitDoubleLiteral(DoubleLiteralNode n, P p); + R visitDoubleLiteral(DoubleLiteralNode n, P p); - R visitBooleanLiteral(BooleanLiteralNode n, P p); + R visitBooleanLiteral(BooleanLiteralNode n, P p); - R visitCharacterLiteral(CharacterLiteralNode n, P p); + R visitCharacterLiteral(CharacterLiteralNode n, P p); - R visitStringLiteral(StringLiteralNode n, P p); + R visitStringLiteral(StringLiteralNode n, P p); - R visitNullLiteral(NullLiteralNode n, P p); + R visitNullLiteral(NullLiteralNode n, P p); - // Unary operations - R visitNumericalMinus(NumericalMinusNode n, P p); + // Unary operations + R visitNumericalMinus(NumericalMinusNode n, P p); - R visitNumericalPlus(NumericalPlusNode n, P p); + R visitNumericalPlus(NumericalPlusNode n, P p); - R visitBitwiseComplement(BitwiseComplementNode n, P p); + R visitBitwiseComplement(BitwiseComplementNode n, P p); - R visitNullChk(NullChkNode n, P p); + R visitNullChk(NullChkNode n, P p); - // Binary operations - R visitStringConcatenate(StringConcatenateNode n, P p); + // Binary operations + R visitStringConcatenate(StringConcatenateNode n, P p); - R visitNumericalAddition(NumericalAdditionNode n, P p); + R visitNumericalAddition(NumericalAdditionNode n, P p); - R visitNumericalSubtraction(NumericalSubtractionNode n, P p); + R visitNumericalSubtraction(NumericalSubtractionNode n, P p); - R visitNumericalMultiplication(NumericalMultiplicationNode n, P p); + R visitNumericalMultiplication(NumericalMultiplicationNode n, P p); - R visitIntegerDivision(IntegerDivisionNode n, P p); + R visitIntegerDivision(IntegerDivisionNode n, P p); - R visitFloatingDivision(FloatingDivisionNode n, P p); + R visitFloatingDivision(FloatingDivisionNode n, P p); - R visitIntegerRemainder(IntegerRemainderNode n, P p); + R visitIntegerRemainder(IntegerRemainderNode n, P p); - R visitFloatingRemainder(FloatingRemainderNode n, P p); + R visitFloatingRemainder(FloatingRemainderNode n, P p); - R visitLeftShift(LeftShiftNode n, P p); + R visitLeftShift(LeftShiftNode n, P p); - R visitSignedRightShift(SignedRightShiftNode n, P p); + R visitSignedRightShift(SignedRightShiftNode n, P p); - R visitUnsignedRightShift(UnsignedRightShiftNode n, P p); + R visitUnsignedRightShift(UnsignedRightShiftNode n, P p); - R visitBitwiseAnd(BitwiseAndNode n, P p); + R visitBitwiseAnd(BitwiseAndNode n, P p); - R visitBitwiseOr(BitwiseOrNode n, P p); + R visitBitwiseOr(BitwiseOrNode n, P p); - R visitBitwiseXor(BitwiseXorNode n, P p); + R visitBitwiseXor(BitwiseXorNode n, P p); - // Comparison operations - R visitLessThan(LessThanNode n, P p); + // Comparison operations + R visitLessThan(LessThanNode n, P p); - R visitLessThanOrEqual(LessThanOrEqualNode n, P p); + R visitLessThanOrEqual(LessThanOrEqualNode n, P p); - R visitGreaterThan(GreaterThanNode n, P p); + R visitGreaterThan(GreaterThanNode n, P p); - R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p); + R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p); - R visitEqualTo(EqualToNode n, P p); + R visitEqualTo(EqualToNode n, P p); - R visitNotEqual(NotEqualNode n, P p); + R visitNotEqual(NotEqualNode n, P p); - // Conditional operations - R visitConditionalAnd(ConditionalAndNode n, P p); + // Conditional operations + R visitConditionalAnd(ConditionalAndNode n, P p); - R visitConditionalOr(ConditionalOrNode n, P p); + R visitConditionalOr(ConditionalOrNode n, P p); - R visitConditionalNot(ConditionalNotNode n, P p); + R visitConditionalNot(ConditionalNotNode n, P p); - R visitTernaryExpression(TernaryExpressionNode n, P p); + R visitTernaryExpression(TernaryExpressionNode n, P p); - R visitSwitchExpressionNode(SwitchExpressionNode n, P p); + R visitSwitchExpressionNode(SwitchExpressionNode n, P p); - R visitAssignment(AssignmentNode n, P p); + R visitAssignment(AssignmentNode n, P p); - R visitLocalVariable(LocalVariableNode n, P p); + R visitLocalVariable(LocalVariableNode n, P p); - R visitVariableDeclaration(VariableDeclarationNode n, P p); + R visitVariableDeclaration(VariableDeclarationNode n, P p); - R visitFieldAccess(FieldAccessNode n, P p); + R visitFieldAccess(FieldAccessNode n, P p); - R visitMethodAccess(MethodAccessNode n, P p); + R visitMethodAccess(MethodAccessNode n, P p); - R visitArrayAccess(ArrayAccessNode n, P p); + R visitArrayAccess(ArrayAccessNode n, P p); - R visitImplicitThis(ImplicitThisNode n, P p); + R visitImplicitThis(ImplicitThisNode n, P p); - R visitExplicitThis(ExplicitThisNode n, P p); + R visitExplicitThis(ExplicitThisNode n, P p); - R visitSuper(SuperNode n, P p); + R visitSuper(SuperNode n, P p); - R visitReturn(ReturnNode n, P p); + R visitReturn(ReturnNode n, P p); - R visitLambdaResultExpression(LambdaResultExpressionNode n, P p); + R visitLambdaResultExpression(LambdaResultExpressionNode n, P p); - R visitStringConversion(StringConversionNode n, P p); + R visitStringConversion(StringConversionNode n, P p); - R visitWideningConversion(WideningConversionNode n, P p); + R visitWideningConversion(WideningConversionNode n, P p); - R visitNarrowingConversion(NarrowingConversionNode n, P p); + R visitNarrowingConversion(NarrowingConversionNode n, P p); - R visitInstanceOf(InstanceOfNode n, P p); + R visitInstanceOf(InstanceOfNode n, P p); - R visitTypeCast(TypeCastNode n, P p); + R visitTypeCast(TypeCastNode n, P p); - // Blocks + // Blocks - R visitSynchronized(SynchronizedNode n, P p); + R visitSynchronized(SynchronizedNode n, P p); - // Statements - R visitAssertionError(AssertionErrorNode n, P p); + // Statements + R visitAssertionError(AssertionErrorNode n, P p); - R visitThrow(ThrowNode n, P p); + R visitThrow(ThrowNode n, P p); - // Cases - R visitCase(CaseNode n, P p); + // Cases + R visitCase(CaseNode n, P p); - // Method and constructor invocations - R visitMethodInvocation(MethodInvocationNode n, P p); + // Method and constructor invocations + R visitMethodInvocation(MethodInvocationNode n, P p); - R visitObjectCreation(ObjectCreationNode n, P p); + R visitObjectCreation(ObjectCreationNode n, P p); - R visitMemberReference(FunctionalInterfaceNode n, P p); + R visitMemberReference(FunctionalInterfaceNode n, P p); - R visitArrayCreation(ArrayCreationNode n, P p); + R visitArrayCreation(ArrayCreationNode n, P p); - // Type, package and class names - R visitArrayType(ArrayTypeNode n, P p); + // Type, package and class names + R visitArrayType(ArrayTypeNode n, P p); - R visitPrimitiveType(PrimitiveTypeNode n, P p); + R visitPrimitiveType(PrimitiveTypeNode n, P p); - R visitClassName(ClassNameNode n, P p); + R visitClassName(ClassNameNode n, P p); - R visitPackageName(PackageNameNode n, P p); + R visitPackageName(PackageNameNode n, P p); - // Parameterized types - R visitParameterizedType(ParameterizedTypeNode n, P p); + // Parameterized types + R visitParameterizedType(ParameterizedTypeNode n, P p); - // Marker nodes - R visitMarker(MarkerNode n, P p); + // Marker nodes + R visitMarker(MarkerNode n, P p); - /** - * Visits an anonymous/inner/nested class declaration within a method. - * - * @param classDeclarationNode the {@link ClassDeclarationNode} to be visited - * @param p the argument for the operation implemented by this visitor - * @return the return value of the operation implemented by this visitor - */ - R visitClassDeclaration(ClassDeclarationNode classDeclarationNode, P p); + /** + * Visits an anonymous/inner/nested class declaration within a method. + * + * @param classDeclarationNode the {@link ClassDeclarationNode} to be visited + * @param p the argument for the operation implemented by this visitor + * @return the return value of the operation implemented by this visitor + */ + R visitClassDeclaration(ClassDeclarationNode classDeclarationNode, P p); - /** - * Visits an expression that is used as a statement. This node is a marker after the expression - * node(s). - * - * @param n the {@link ExpressionStatementNode} to be visited - * @param p the argument for the operation implemented by this visitor - * @return the return value of the operation implemented by this visitor - */ - R visitExpressionStatement(ExpressionStatementNode n, P p); + /** + * Visits an expression that is used as a statement. This node is a marker after the expression + * node(s). + * + * @param n the {@link ExpressionStatementNode} to be visited + * @param p the argument for the operation implemented by this visitor + * @return the return value of the operation implemented by this visitor + */ + R visitExpressionStatement(ExpressionStatementNode n, P p); - /** - * Visits a deconstructor pattern node. - * - * @param n the {@link DeconstructorPatternNode} to be visited - * @param p the argument for the operation implemented by this visitor - * @return the return value of the operation implemented by this visitor - */ - R visitDeconstructorPattern(DeconstructorPatternNode n, P p); + /** + * Visits a deconstructor pattern node. + * + * @param n the {@link DeconstructorPatternNode} to be visited + * @param p the argument for the operation implemented by this visitor + * @return the return value of the operation implemented by this visitor + */ + R visitDeconstructorPattern(DeconstructorPatternNode n, P p); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java index ee0548c27aa..b7c2f5ed0ec 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the not equal comparison: * @@ -14,40 +16,40 @@ */ public class NotEqualNode extends BinaryOperationNode { - /** - * Constructs a {@link NotEqualNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NotEqualNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.NOT_EQUAL_TO; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNotEqual(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " != " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NotEqualNode)) { - return false; + /** + * Constructs a {@link NotEqualNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NotEqualNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.NOT_EQUAL_TO; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNotEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " != " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NotEqualNode)) { + return false; + } + NotEqualNode other = (NotEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - NotEqualNode other = (NotEqualNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java index 0a2dc570bc0..386bc977fd0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java @@ -1,13 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + /** * A node for the unary 'nullchk' operation (generated by the Java compiler): * @@ -16,61 +18,61 @@ * */ public class NullChkNode extends Node { - /** The entire tree of the null check */ - protected final Tree tree; + /** The entire tree of the null check */ + protected final Tree tree; - /** The operand of the null check */ - protected final Node operand; + /** The operand of the null check */ + protected final Node operand; - /** - * Constructs a {@link NullChkNode}. - * - * @param tree the nullchk tree - * @param operand the operand of the null check - */ - public NullChkNode(Tree tree, Node operand) { - super(TreeUtils.typeOf(tree)); - assert tree.getKind() == Tree.Kind.OTHER; - this.tree = tree; - this.operand = operand; - } + /** + * Constructs a {@link NullChkNode}. + * + * @param tree the nullchk tree + * @param operand the operand of the null check + */ + public NullChkNode(Tree tree, Node operand) { + super(TreeUtils.typeOf(tree)); + assert tree.getKind() == Tree.Kind.OTHER; + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNullChk(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNullChk(this, p); + } - @Override - public String toString() { - return "(+ " + getOperand() + ")"; - } + @Override + public String toString() { + return "(+ " + getOperand() + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalPlusNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalPlusNode)) { + return false; + } + NumericalPlusNode other = (NumericalPlusNode) obj; + return getOperand().equals(other.getOperand()); } - NumericalPlusNode other = (NumericalPlusNode) obj; - return getOperand().equals(other.getOperand()); - } - @Override - public int hashCode() { - return Objects.hash(NullChkNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(NullChkNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java index f0866c47d77..574e1fbe8dd 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -13,35 +14,35 @@ */ public class NullLiteralNode extends ValueLiteralNode { - /** - * Create a new NullLiteralNode. - * - * @param t the tree for the literal value - */ - public NullLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.NULL_LITERAL; - } - - @Override - public Void getValue() { - return (Void) tree.getValue(); - } + /** + * Create a new NullLiteralNode. + * + * @param t the tree for the literal value + */ + public NullLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.NULL_LITERAL; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNullLiteral(this, p); - } + @Override + public Void getValue() { + return (Void) tree.getValue(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNullLiteral(this, p); } - if (!(obj instanceof NullLiteralNode)) { - return false; + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof NullLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java index 6ca458abf79..fd26ab1c3cc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the numerical addition: * @@ -14,40 +16,40 @@ */ public class NumericalAdditionNode extends BinaryOperationNode { - /** - * Constructs a {@link NumericalAdditionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NumericalAdditionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.PLUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalAddition(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalAdditionNode)) { - return false; + /** + * Constructs a {@link NumericalAdditionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NumericalAdditionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.PLUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalAddition(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalAdditionNode)) { + return false; + } + NumericalAdditionNode other = (NumericalAdditionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - NumericalAdditionNode other = (NumericalAdditionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java index c5b2e5619f4..0a7a02f5949 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the unary minus operation: * @@ -14,38 +16,38 @@ */ public class NumericalMinusNode extends UnaryOperationNode { - /** - * Constructs a {@link NumericalMinusNode}. - * - * @param tree the unary tree - * @param operand the operand of the unary minus - */ - public NumericalMinusNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.UNARY_MINUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalMinus(this, p); - } - - @Override - public String toString() { - return "(- " + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalMinusNode)) { - return false; + /** + * Constructs a {@link NumericalMinusNode}. + * + * @param tree the unary tree + * @param operand the operand of the unary minus + */ + public NumericalMinusNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.UNARY_MINUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalMinus(this, p); + } + + @Override + public String toString() { + return "(- " + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalMinusNode)) { + return false; + } + NumericalMinusNode other = (NumericalMinusNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(NumericalMinusNode.class, getOperand()); } - NumericalMinusNode other = (NumericalMinusNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(NumericalMinusNode.class, getOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java index 3dabcaf1dd1..a1abe1c8403 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the numerical multiplication: * @@ -14,40 +16,40 @@ */ public class NumericalMultiplicationNode extends BinaryOperationNode { - /** - * Constructs a {@link NumericalMultiplicationNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NumericalMultiplicationNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.MULTIPLY; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalMultiplication(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " * " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalMultiplicationNode)) { - return false; + /** + * Constructs a {@link NumericalMultiplicationNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NumericalMultiplicationNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.MULTIPLY; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalMultiplication(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " * " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalMultiplicationNode)) { + return false; + } + NumericalMultiplicationNode other = (NumericalMultiplicationNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - NumericalMultiplicationNode other = (NumericalMultiplicationNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java index 2f6d2daa8de..6f611d97cbe 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the unary plus operation: * @@ -14,38 +16,38 @@ */ public class NumericalPlusNode extends UnaryOperationNode { - /** - * Constructs a {@link NumericalPlusNode}. - * - * @param tree the tree of the whole operation - * @param operand the operand of the operation - */ - public NumericalPlusNode(UnaryTree tree, Node operand) { - super(tree, operand); - assert tree.getKind() == Tree.Kind.UNARY_PLUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalPlus(this, p); - } - - @Override - public String toString() { - return "(+ " + getOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalPlusNode)) { - return false; + /** + * Constructs a {@link NumericalPlusNode}. + * + * @param tree the tree of the whole operation + * @param operand the operand of the operation + */ + public NumericalPlusNode(UnaryTree tree, Node operand) { + super(tree, operand); + assert tree.getKind() == Tree.Kind.UNARY_PLUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalPlus(this, p); + } + + @Override + public String toString() { + return "(+ " + getOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalPlusNode)) { + return false; + } + NumericalPlusNode other = (NumericalPlusNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(NumericalPlusNode.class, getOperand()); } - NumericalPlusNode other = (NumericalPlusNode) obj; - return getOperand().equals(other.getOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(NumericalPlusNode.class, getOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java index 5d2576bb7b6..af72519ffe7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for the numerical subtraction: * @@ -14,40 +16,40 @@ */ public class NumericalSubtractionNode extends BinaryOperationNode { - /** - * Constructs a {@link NumericalSubtractionNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public NumericalSubtractionNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.MINUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitNumericalSubtraction(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " - " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof NumericalSubtractionNode)) { - return false; + /** + * Constructs a {@link NumericalSubtractionNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public NumericalSubtractionNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.MINUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitNumericalSubtraction(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " - " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof NumericalSubtractionNode)) { + return false; + } + NumericalSubtractionNode other = (NumericalSubtractionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - NumericalSubtractionNode other = (NumericalSubtractionNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java index 7d7374d6ed6..a14856f89d0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java @@ -1,16 +1,18 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.NewClassTree; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.StringsPlume; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + /** * A node for a new object creation. * @@ -31,182 +33,183 @@ */ public class ObjectCreationNode extends Node { - /** The tree for the object creation. */ - protected final NewClassTree tree; - - /** The enclosing expression of the object creation or null. */ - protected final @Nullable Node enclosingExpression; - - /** - * The type to instantiate node of the object creation. A non-generic typeToInstantiate node will - * refer to a {@link ClassNameNode}, while a generic typeToInstantiate node will refer to a {@link - * ParameterizedTypeNode}. - */ - protected final Node typeToInstantiate; - - /** The arguments of the object creation. */ - protected final List arguments; - - /** Class body for anonymous classes, otherwise null. */ - protected final @Nullable ClassDeclarationNode classbody; - - /** - * Constructs a {@link ObjectCreationNode}. - * - * @param tree the NewClassTree - * @param enclosingExpr the enclosing expression Node if it exists, or null - * @param typeToInstantiate the typeToInstantiate node - * @param arguments the passed arguments - * @param classbody the ClassDeclarationNode - */ - public ObjectCreationNode( - NewClassTree tree, - @Nullable Node enclosingExpr, - Node typeToInstantiate, - List arguments, - @Nullable ClassDeclarationNode classbody) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.enclosingExpression = enclosingExpr; - this.typeToInstantiate = typeToInstantiate; - this.arguments = arguments; - this.classbody = classbody; - } - - /** - * Returns the constructor node. - * - * @return the constructor node - * @deprecated use {@link #getTypeToInstantiate()} - */ - @Pure - @Deprecated // 2023-06-06 - public Node getConstructor() { - return typeToInstantiate; - } - - /** - * Returns the typeToInstantiate node. A non-generic typeToInstantiate node can refer to a {@link - * ClassNameNode}, while a generic typeToInstantiate node can refer to a {@link - * ParameterizedTypeNode}. - * - * @return the typeToInstantiate node - */ - @Pure - public Node getTypeToInstantiate() { - return typeToInstantiate; - } - - /** - * Returns the explicit arguments to the object creation. - * - * @return the arguments - */ - @Pure - public List getArguments() { - return arguments; - } - - /** - * Returns the i-th explicit argument to the object creation. - * - * @param i the index of the argument - * @return the argument - */ - @Pure - public Node getArgument(int i) { - return arguments.get(i); - } - - /** - * Returns the enclosing expression node, which only exists if it is an inner class instantiation. - * - * @return the enclosing type expression node - */ - @Pure - public @Nullable Node getEnclosingExpression() { - return enclosingExpression; - } - - /** - * Returns the classbody. - * - * @return the classbody - */ - @Pure - public @Nullable Node getClassBody() { - return classbody; - } - - @Override - @Pure - public NewClassTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitObjectCreation(this, p); - } - - @Override - @SideEffectFree - public String toString() { - StringBuilder sb = new StringBuilder(); - if (enclosingExpression != null) { - sb.append(enclosingExpression + "."); + /** The tree for the object creation. */ + protected final NewClassTree tree; + + /** The enclosing expression of the object creation or null. */ + protected final @Nullable Node enclosingExpression; + + /** + * The type to instantiate node of the object creation. A non-generic typeToInstantiate node + * will refer to a {@link ClassNameNode}, while a generic typeToInstantiate node will refer to a + * {@link ParameterizedTypeNode}. + */ + protected final Node typeToInstantiate; + + /** The arguments of the object creation. */ + protected final List arguments; + + /** Class body for anonymous classes, otherwise null. */ + protected final @Nullable ClassDeclarationNode classbody; + + /** + * Constructs a {@link ObjectCreationNode}. + * + * @param tree the NewClassTree + * @param enclosingExpr the enclosing expression Node if it exists, or null + * @param typeToInstantiate the typeToInstantiate node + * @param arguments the passed arguments + * @param classbody the ClassDeclarationNode + */ + public ObjectCreationNode( + NewClassTree tree, + @Nullable Node enclosingExpr, + Node typeToInstantiate, + List arguments, + @Nullable ClassDeclarationNode classbody) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.enclosingExpression = enclosingExpr; + this.typeToInstantiate = typeToInstantiate; + this.arguments = arguments; + this.classbody = classbody; + } + + /** + * Returns the constructor node. + * + * @return the constructor node + * @deprecated use {@link #getTypeToInstantiate()} + */ + @Pure + @Deprecated // 2023-06-06 + public Node getConstructor() { + return typeToInstantiate; + } + + /** + * Returns the typeToInstantiate node. A non-generic typeToInstantiate node can refer to a + * {@link ClassNameNode}, while a generic typeToInstantiate node can refer to a {@link + * ParameterizedTypeNode}. + * + * @return the typeToInstantiate node + */ + @Pure + public Node getTypeToInstantiate() { + return typeToInstantiate; } - sb.append("new "); - if (!tree.getTypeArguments().isEmpty()) { - sb.append("<"); - sb.append(StringsPlume.join(", ", tree.getTypeArguments())); - sb.append(">"); + + /** + * Returns the explicit arguments to the object creation. + * + * @return the arguments + */ + @Pure + public List getArguments() { + return arguments; + } + + /** + * Returns the i-th explicit argument to the object creation. + * + * @param i the index of the argument + * @return the argument + */ + @Pure + public Node getArgument(int i) { + return arguments.get(i); + } + + /** + * Returns the enclosing expression node, which only exists if it is an inner class + * instantiation. + * + * @return the enclosing type expression node + */ + @Pure + public @Nullable Node getEnclosingExpression() { + return enclosingExpression; + } + + /** + * Returns the classbody. + * + * @return the classbody + */ + @Pure + public @Nullable Node getClassBody() { + return classbody; } - sb.append(typeToInstantiate + "("); - sb.append(StringsPlume.join(", ", arguments)); - sb.append(")"); - if (classbody != null) { - // TODO: maybe this can be done nicer... - sb.append(" "); - sb.append(classbody.toString()); + + @Override + @Pure + public NewClassTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitObjectCreation(this, p); } - return sb.toString(); - } - - @Override - @Pure - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ObjectCreationNode)) { - return false; + + @Override + @SideEffectFree + public String toString() { + StringBuilder sb = new StringBuilder(); + if (enclosingExpression != null) { + sb.append(enclosingExpression + "."); + } + sb.append("new "); + if (!tree.getTypeArguments().isEmpty()) { + sb.append("<"); + sb.append(StringsPlume.join(", ", tree.getTypeArguments())); + sb.append(">"); + } + sb.append(typeToInstantiate + "("); + sb.append(StringsPlume.join(", ", arguments)); + sb.append(")"); + if (classbody != null) { + // TODO: maybe this can be done nicer... + sb.append(" "); + sb.append(classbody.toString()); + } + return sb.toString(); } - ObjectCreationNode other = (ObjectCreationNode) obj; - // TODO: See issue 470. - if (typeToInstantiate == null && other.getTypeToInstantiate() != null) { - return false; + + @Override + @Pure + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ObjectCreationNode)) { + return false; + } + ObjectCreationNode other = (ObjectCreationNode) obj; + // TODO: See issue 470. + if (typeToInstantiate == null && other.getTypeToInstantiate() != null) { + return false; + } + + return getTypeToInstantiate().equals(other.getTypeToInstantiate()) + && getArguments().equals(other.getArguments()) + && (getEnclosingExpression() == null + ? null == other.getEnclosingExpression() + : getEnclosingExpression().equals(other.getEnclosingExpression())); + } + + @Override + @SideEffectFree + public int hashCode() { + return Objects.hash(enclosingExpression, typeToInstantiate, arguments); } - return getTypeToInstantiate().equals(other.getTypeToInstantiate()) - && getArguments().equals(other.getArguments()) - && (getEnclosingExpression() == null - ? null == other.getEnclosingExpression() - : getEnclosingExpression().equals(other.getEnclosingExpression())); - } - - @Override - @SideEffectFree - public int hashCode() { - return Objects.hash(enclosingExpression, typeToInstantiate, arguments); - } - - @Override - @SideEffectFree - public Collection getOperands() { - ArrayList list = new ArrayList<>(2 + arguments.size()); - if (enclosingExpression != null) { - list.add(enclosingExpression); + @Override + @SideEffectFree + public Collection getOperands() { + ArrayList list = new ArrayList<>(2 + arguments.size()); + if (enclosingExpression != null) { + list.add(enclosingExpression); + } + list.add(typeToInstantiate); + list.addAll(arguments); + return list; } - list.add(typeToInstantiate); - list.addAll(arguments); - return list; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java index 5874000a36a..6f8347a72cb 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java @@ -3,15 +3,18 @@ import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import javax.lang.model.element.PackageElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +import javax.lang.model.element.PackageElement; + /** * A node representing a package name used in an expression such as a constructor invocation. * @@ -21,89 +24,89 @@ */ public class PackageNameNode extends Node { - /** The package name, which is an IdentifierTree or a MemberSelectTree. */ - protected final Tree tree; + /** The package name, which is an IdentifierTree or a MemberSelectTree. */ + protected final Tree tree; + + /** The package named by this node. */ + protected final PackageElement element; + + /** The parent name, if any. */ + protected final @Nullable PackageNameNode parent; - /** The package named by this node. */ - protected final PackageElement element; + public PackageNameNode(IdentifierTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); + if (element == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + } + this.element = element; + this.parent = null; + } - /** The parent name, if any. */ - protected final @Nullable PackageNameNode parent; + public PackageNameNode(MemberSelectTree tree, PackageNameNode parent) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; + PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); + if (element == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + } + this.element = element; + this.parent = parent; + } - public PackageNameNode(IdentifierTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); - if (element == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + /** + * Returns the element for this package. + * + * @return the element for this package + */ + public PackageElement getElement() { + return element; } - this.element = element; - this.parent = null; - } - - public PackageNameNode(MemberSelectTree tree, PackageNameNode parent) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - assert TreeUtils.isUseOfElement(tree) : "@AssumeAssertion(nullness): tree kind"; - PackageElement element = (PackageElement) TreeUtils.elementFromUse(tree); - if (element == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + + /** The package name node for the parent package, {@code null} otherwise. */ + public @Nullable PackageNameNode getParent() { + return parent; } - this.element = element; - this.parent = parent; - } - - /** - * Returns the element for this package. - * - * @return the element for this package - */ - public PackageElement getElement() { - return element; - } - - /** The package name node for the parent package, {@code null} otherwise. */ - public @Nullable PackageNameNode getParent() { - return parent; - } - - @Override - public Tree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitPackageName(this, p); - } - - @Override - public String toString() { - return getElement().getSimpleName().toString(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof PackageNameNode)) { - return false; + + @Override + public Tree getTree() { + return tree; } - PackageNameNode other = (PackageNameNode) obj; - return Objects.equals(getParent(), other.getParent()) - && getElement().equals(other.getElement()); - } - - @Override - public int hashCode() { - return Objects.hash(getElement(), getParent()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - if (parent == null) { - return Collections.emptyList(); + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitPackageName(this, p); + } + + @Override + public String toString() { + return getElement().getSimpleName().toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof PackageNameNode)) { + return false; + } + PackageNameNode other = (PackageNameNode) obj; + return Objects.equals(getParent(), other.getParent()) + && getElement().equals(other.getElement()); + } + + @Override + public int hashCode() { + return Objects.hash(getElement(), getParent()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + if (parent == null) { + return Collections.emptyList(); + } + return Collections.singleton((Node) parent); } - return Collections.singleton((Node) parent); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java index 82cd3e23d46..b5931f6de7f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java @@ -1,13 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ParameterizedTypeTree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + /** * A node for a parameterized type occurring in an expression: * @@ -21,45 +23,45 @@ */ public class ParameterizedTypeNode extends Node { - protected final ParameterizedTypeTree tree; + protected final ParameterizedTypeTree tree; - public ParameterizedTypeNode(ParameterizedTypeTree t) { - super(TreeUtils.typeOf(t)); - tree = t; - } + public ParameterizedTypeNode(ParameterizedTypeTree t) { + super(TreeUtils.typeOf(t)); + tree = t; + } - @Override - public ParameterizedTypeTree getTree() { - return tree; - } + @Override + public ParameterizedTypeTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitParameterizedType(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitParameterizedType(this, p); + } - @Override - public String toString() { - return getTree().toString(); - } + @Override + public String toString() { + return getTree().toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ParameterizedTypeNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ParameterizedTypeNode)) { + return false; + } + ParameterizedTypeNode other = (ParameterizedTypeNode) obj; + return getTree().equals(other.getTree()); } - ParameterizedTypeNode other = (ParameterizedTypeNode) obj; - return getTree().equals(other.getTree()); - } - @Override - public int hashCode() { - return Objects.hash(getTree()); - } + @Override + public int hashCode() { + return Objects.hash(getTree()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java index bcb24e91a57..a69081a99ab 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.PrimitiveTypeTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TreeUtils; /** * A node representing a primitive type used in an expression such as a field access. @@ -16,49 +19,49 @@ */ public class PrimitiveTypeNode extends Node { - protected final PrimitiveTypeTree tree; + protected final PrimitiveTypeTree tree; - /** For Types.isSameType. */ - protected final Types types; + /** For Types.isSameType. */ + protected final Types types; - public PrimitiveTypeNode(PrimitiveTypeTree tree, Types types) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.types = types; - } + public PrimitiveTypeNode(PrimitiveTypeTree tree, Types types) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.types = types; + } - @Override - public PrimitiveTypeTree getTree() { - return tree; - } + @Override + public PrimitiveTypeTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitPrimitiveType(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitPrimitiveType(this, p); + } - @Override - public String toString() { - return tree.toString(); - } + @Override + public String toString() { + return tree.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof PrimitiveTypeNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof PrimitiveTypeNode)) { + return false; + } + PrimitiveTypeNode other = (PrimitiveTypeNode) obj; + return types.isSameType(getType(), other.getType()); } - PrimitiveTypeNode other = (PrimitiveTypeNode) obj; - return types.isSameType(getType(), other.getType()); - } - @Override - public int hashCode() { - return Objects.hash(getType()); - } + @Override + public int hashCode() { + return Objects.hash(getType()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java index cfea031f9af..435a6ccc452 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ReturnTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for a return statement: @@ -21,73 +24,73 @@ */ public class ReturnNode extends Node { - /** The return tree. */ - protected final ReturnTree returnTree; + /** The return tree. */ + protected final ReturnTree returnTree; - /** The node of the returned expression. */ - protected final @Nullable Node result; + /** The node of the returned expression. */ + protected final @Nullable Node result; - /** - * Creates a node for the given return statement. - * - * @param returnTree return tree - * @param result the returned expression - * @param types types util - */ - public ReturnNode(ReturnTree returnTree, @Nullable Node result, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.result = result; - this.returnTree = returnTree; - } + /** + * Creates a node for the given return statement. + * + * @param returnTree return tree + * @param result the returned expression + * @param types types util + */ + public ReturnNode(ReturnTree returnTree, @Nullable Node result, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.result = result; + this.returnTree = returnTree; + } - /** - * The result of the return node, {@code null} otherwise. - * - * @return the result of the return node, {@code null} otherwise - */ - public @Nullable Node getResult() { - return result; - } + /** + * The result of the return node, {@code null} otherwise. + * + * @return the result of the return node, {@code null} otherwise + */ + public @Nullable Node getResult() { + return result; + } - @Override - public ReturnTree getTree() { - return returnTree; - } + @Override + public ReturnTree getTree() { + return returnTree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitReturn(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitReturn(this, p); + } - @Override - public String toString() { - if (result != null) { - return "return " + result; + @Override + public String toString() { + if (result != null) { + return "return " + result; + } + return "return"; } - return "return"; - } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ReturnNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ReturnNode)) { + return false; + } + ReturnNode other = (ReturnNode) obj; + return Objects.equals(result, other.result); } - ReturnNode other = (ReturnNode) obj; - return Objects.equals(result, other.result); - } - @Override - public int hashCode() { - return Objects.hash(ReturnNode.class, result); - } + @Override + public int hashCode() { + return Objects.hash(ReturnNode.class, result); + } - @Override - @SideEffectFree - public Collection getOperands() { - if (result == null) { - return Collections.emptyList(); - } else { - return Collections.singletonList(result); + @Override + @SideEffectFree + public Collection getOperands() { + if (result == null) { + return Collections.emptyList(); + } else { + return Collections.singletonList(result); + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java index f504e8b1321..8da1646c07a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -18,33 +19,33 @@ // TODO: If we use explicit NarrowingConversionNodes, do we need ShortLiteralNodes too? public class ShortLiteralNode extends ValueLiteralNode { - /** - * Create a new ShortLiteralNode. - * - * @param t the tree for the literal value - */ - public ShortLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.INT_LITERAL; - } + /** + * Create a new ShortLiteralNode. + * + * @param t the tree for the literal value + */ + public ShortLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.INT_LITERAL; + } - @Override - public Short getValue() { - return (Short) tree.getValue(); - } + @Override + public Short getValue() { + return (Short) tree.getValue(); + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitShortLiteral(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitShortLiteral(this, p); + } - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a ShortLiteralNode - if (!(obj instanceof ShortLiteralNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a ShortLiteralNode + if (!(obj instanceof ShortLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); } - // super method compares values - return super.equals(obj); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java index 849b7fafbfe..22710411850 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for bitwise right shift operations with sign extension: * @@ -14,40 +16,40 @@ */ public class SignedRightShiftNode extends BinaryOperationNode { - /** - * Constructs a {@link SignedRightShiftNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public SignedRightShiftNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.RIGHT_SHIFT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSignedRightShift(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " >> " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof SignedRightShiftNode)) { - return false; + /** + * Constructs a {@link SignedRightShiftNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public SignedRightShiftNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.RIGHT_SHIFT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSignedRightShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >> " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof SignedRightShiftNode)) { + return false; + } + SignedRightShiftNode other = (SignedRightShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - SignedRightShiftNode other = (SignedRightShiftNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java index c7d0b2d0fb6..c6615ad5605 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for string concatenation: * @@ -14,40 +16,40 @@ */ public class StringConcatenateNode extends BinaryOperationNode { - /** - * Constructs a {@link StringConcatenateNode}. - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public StringConcatenateNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.PLUS; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitStringConcatenate(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof StringConcatenateNode)) { - return false; + /** + * Constructs a {@link StringConcatenateNode}. + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public StringConcatenateNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.PLUS; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitStringConcatenate(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof StringConcatenateNode)) { + return false; + } + StringConcatenateNode other = (StringConcatenateNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - StringConcatenateNode other = (StringConcatenateNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java index 850edc43fc1..4d55bb0171a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java @@ -1,12 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for the string conversion operation. See JLS 5.1.11 for the definition of string @@ -22,55 +25,55 @@ */ public class StringConversionNode extends Node { - protected final Tree tree; - protected final Node operand; + protected final Tree tree; + protected final Node operand; - // TODO: The type of a string conversion should be a final - // TypeMirror representing java.lang.String. Currently we require - // the caller to pass in a TypeMirror instead of creating one - // through the javax.lang.model.type.Types interface. - public StringConversionNode(Tree tree, Node operand, TypeMirror type) { - super(type); - this.tree = tree; - this.operand = operand; - } + // TODO: The type of a string conversion should be a final + // TypeMirror representing java.lang.String. Currently we require + // the caller to pass in a TypeMirror instead of creating one + // through the javax.lang.model.type.Types interface. + public StringConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitStringConversion(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitStringConversion(this, p); + } - @Override - public String toString() { - return "StringConversion(" + getOperand() + ")"; - } + @Override + public String toString() { + return "StringConversion(" + getOperand() + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof StringConversionNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof StringConversionNode)) { + return false; + } + StringConversionNode other = (StringConversionNode) obj; + return getOperand().equals(other.getOperand()); } - StringConversionNode other = (StringConversionNode) obj; - return getOperand().equals(other.getOperand()); - } - @Override - public int hashCode() { - return Objects.hash(StringConversionNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(StringConversionNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java index a44c39eaed3..7ba89c61b74 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java @@ -2,6 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -13,38 +14,38 @@ */ public class StringLiteralNode extends ValueLiteralNode { - /** - * Create a new StringLiteralNode. - * - * @param t the tree for the literal value - */ - public StringLiteralNode(LiteralTree t) { - super(t); - assert t.getKind() == Tree.Kind.STRING_LITERAL; - } - - @Override - public String getValue() { - return (String) tree.getValue(); - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitStringLiteral(this, p); - } - - @Override - public boolean equals(@Nullable Object obj) { - // test that obj is a StringLiteralNode - if (!(obj instanceof StringLiteralNode)) { - return false; + /** + * Create a new StringLiteralNode. + * + * @param t the tree for the literal value + */ + public StringLiteralNode(LiteralTree t) { + super(t); + assert t.getKind() == Tree.Kind.STRING_LITERAL; + } + + @Override + public String getValue() { + return (String) tree.getValue(); + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitStringLiteral(this, p); + } + + @Override + public boolean equals(@Nullable Object obj) { + // test that obj is a StringLiteralNode + if (!(obj instanceof StringLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public String toString() { + return "\"" + super.toString() + "\""; } - // super method compares values - return super.equals(obj); - } - - @Override - public String toString() { - return "\"" + super.toString() + "\""; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java index c1fbf572286..05ef67c0eb4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SuperNode.java @@ -1,12 +1,14 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.IdentifierTree; -import java.util.Collection; -import java.util.Collections; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; + /** * A node for a reference to 'super'. * @@ -16,42 +18,42 @@ */ public class SuperNode extends Node { - protected final IdentifierTree tree; - - public SuperNode(IdentifierTree t) { - super(TreeUtils.typeOf(t)); - assert t.getName().contentEquals("super"); - tree = t; - } - - @Override - public IdentifierTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSuper(this, p); - } - - @Override - public String toString() { - return "super"; - } - - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof SuperNode; - } - - @Override - public int hashCode() { - return 109801370; // Objects.hash("super"); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + protected final IdentifierTree tree; + + public SuperNode(IdentifierTree t) { + super(TreeUtils.typeOf(t)); + assert t.getName().contentEquals("super"); + tree = t; + } + + @Override + public IdentifierTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSuper(this, p); + } + + @Override + public String toString() { + return "super"; + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof SuperNode; + } + + @Override + public int hashCode() { + return 109801370; // Objects.hash("super"); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java index 725079c820e..ef2a1dea1b9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SwitchExpressionNode.java @@ -1,101 +1,104 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.SystemUtil; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +import javax.lang.model.type.TypeMirror; + /** A node for a switch expression. */ public class SwitchExpressionNode extends Node { - /** The {@code SwitchExpressionTree} corresponding to this node. */ - private final Tree switchExpressionTree; - - /** - * This is a variable created by dataflow to which each result expression of the switch expression - * is assigned. Its value should be used for the value of the switch expression. - */ - private final LocalVariableNode switchExpressionVar; - - /** - * Creates a new SwitchExpressionNode. - * - * @param type the type of the node - * @param switchExpressionTree the {@code SwitchExpressionTree} for this node - * @param switchExpressionVar a variable created by dataflow to which each result expression of - * the switch expression is assigned. Its value should be used for the value of the switch - * expression - */ - public SwitchExpressionNode( - TypeMirror type, Tree switchExpressionTree, LocalVariableNode switchExpressionVar) { - super(type); - - // TODO: use JCP to add version-specific behavior - if (SystemUtil.jreVersion < 14 - || !switchExpressionTree.getKind().name().equals("SWITCH_EXPRESSION")) { - throw new BugInCF( - "switchExpressionTree is not a SwitchExpressionTree found tree with kind %s" - + " instead.", - switchExpressionTree.getKind()); + /** The {@code SwitchExpressionTree} corresponding to this node. */ + private final Tree switchExpressionTree; + + /** + * This is a variable created by dataflow to which each result expression of the switch + * expression is assigned. Its value should be used for the value of the switch expression. + */ + private final LocalVariableNode switchExpressionVar; + + /** + * Creates a new SwitchExpressionNode. + * + * @param type the type of the node + * @param switchExpressionTree the {@code SwitchExpressionTree} for this node + * @param switchExpressionVar a variable created by dataflow to which each result expression of + * the switch expression is assigned. Its value should be used for the value of the switch + * expression + */ + public SwitchExpressionNode( + TypeMirror type, Tree switchExpressionTree, LocalVariableNode switchExpressionVar) { + super(type); + + // TODO: use JCP to add version-specific behavior + if (SystemUtil.jreVersion < 14 + || !switchExpressionTree.getKind().name().equals("SWITCH_EXPRESSION")) { + throw new BugInCF( + "switchExpressionTree is not a SwitchExpressionTree found tree with kind %s" + + " instead.", + switchExpressionTree.getKind()); + } + + this.switchExpressionTree = switchExpressionTree; + this.switchExpressionVar = switchExpressionVar; + } + + @Override + public Tree getTree() { + return switchExpressionTree; + } + + /** + * This is a variable created by dataflow to which each result expression of the switch + * expression is assigned. Its value should be used for the value of the switch expression. + * + * @return the variable for this switch expression + */ + public LocalVariableNode getSwitchExpressionVar() { + return switchExpressionVar; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSwitchExpressionNode(this, p); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singleton(switchExpressionVar); + } + + @Override + public String toString() { + return "SwitchExpressionNode{" + + "switchExpressionTree=" + + switchExpressionTree + + ", switchExpressionVar=" + + switchExpressionVar + + '}'; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof SwitchExpressionNode)) { + return false; + } + SwitchExpressionNode other = (SwitchExpressionNode) obj; + return getTree().equals(other.getTree()) + && getSwitchExpressionVar().equals(other.getSwitchExpressionVar()); } - this.switchExpressionTree = switchExpressionTree; - this.switchExpressionVar = switchExpressionVar; - } - - @Override - public Tree getTree() { - return switchExpressionTree; - } - - /** - * This is a variable created by dataflow to which each result expression of the switch expression - * is assigned. Its value should be used for the value of the switch expression. - * - * @return the variable for this switch expression - */ - public LocalVariableNode getSwitchExpressionVar() { - return switchExpressionVar; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSwitchExpressionNode(this, p); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singleton(switchExpressionVar); - } - - @Override - public String toString() { - return "SwitchExpressionNode{" - + "switchExpressionTree=" - + switchExpressionTree - + ", switchExpressionVar=" - + switchExpressionVar - + '}'; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof SwitchExpressionNode)) { - return false; + @Override + public int hashCode() { + return Objects.hash(getTree(), getSwitchExpressionVar()); } - SwitchExpressionNode other = (SwitchExpressionNode) obj; - return getTree().equals(other.getTree()) - && getSwitchExpressionVar().equals(other.getSwitchExpressionVar()); - } - - @Override - public int hashCode() { - return Objects.hash(getTree(), getSwitchExpressionVar()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java index be52c5d9647..fd636d47d65 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.SynchronizedTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * This represents the start and end of a synchronized code block. If startOfBlock == true it is the @@ -16,64 +19,64 @@ */ public class SynchronizedNode extends Node { - protected final SynchronizedTree tree; - protected final Node expression; - protected final boolean startOfBlock; + protected final SynchronizedTree tree; + protected final Node expression; + protected final boolean startOfBlock; - public SynchronizedNode( - SynchronizedTree tree, Node expression, boolean startOfBlock, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.expression = expression; - this.startOfBlock = startOfBlock; - } + public SynchronizedNode( + SynchronizedTree tree, Node expression, boolean startOfBlock, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.expression = expression; + this.startOfBlock = startOfBlock; + } - @Override - public SynchronizedTree getTree() { - return tree; - } + @Override + public SynchronizedTree getTree() { + return tree; + } - public Node getExpression() { - return expression; - } + public Node getExpression() { + return expression; + } - public boolean getIsStartOfBlock() { - return startOfBlock; - } + public boolean getIsStartOfBlock() { + return startOfBlock; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitSynchronized(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitSynchronized(this, p); + } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("synchronized ("); - sb.append(expression); - sb.append(")"); - return sb.toString(); - } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("synchronized ("); + sb.append(expression); + sb.append(")"); + return sb.toString(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof SynchronizedNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof SynchronizedNode)) { + return false; + } + SynchronizedNode other = (SynchronizedNode) obj; + return Objects.equals(getTree(), other.getTree()) + && getExpression().equals(other.getExpression()) + && startOfBlock == other.startOfBlock; } - SynchronizedNode other = (SynchronizedNode) obj; - return Objects.equals(getTree(), other.getTree()) - && getExpression().equals(other.getExpression()) - && startOfBlock == other.startOfBlock; - } - @Override - public int hashCode() { - return Objects.hash(tree, startOfBlock, getExpression()); - } + @Override + public int hashCode() { + return Objects.hash(tree, startOfBlock, getExpression()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java index 2150640b98a..8301cfd9460 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java @@ -1,13 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ConditionalExpressionTree; -import java.util.Arrays; -import java.util.Collection; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; + /** * A node for a conditional expression: * @@ -17,120 +19,126 @@ */ public class TernaryExpressionNode extends Node { - /** The {@code ConditionalExpressionTree} corresponding to this node */ - protected final ConditionalExpressionTree tree; - - /** Node representing the condition checked by the expression */ - protected final Node condition; - - /** Node representing the "then" case of the expression */ - protected final Node thenOperand; - - /** Node representing the "else" case of the expression */ - protected final Node elseOperand; - - /** - * This is a variable created by dataflow to which each case expression of the ternary expression - * is assigned. Its value should be used for the value of the switch expression. - */ - private final LocalVariableNode ternaryExpressionVar; - - /** - * Creates a new TernaryExpressionNode. - * - * @param tree the {@code ConditionalExpressionTree} for the node - * @param condition node representing the condition checked by the expression - * @param thenOperand node representing the "then" case of the expression - * @param elseOperand node representing the "else" case of the expression - * @param ternaryExpressionVar a variable created by dataflow to which each case expression of the - * ternary expression is assigned. Its value should be used for the value of the switch - * expression. - */ - public TernaryExpressionNode( - ConditionalExpressionTree tree, - Node condition, - Node thenOperand, - Node elseOperand, - LocalVariableNode ternaryExpressionVar) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.condition = condition; - this.thenOperand = thenOperand; - this.elseOperand = elseOperand; - this.ternaryExpressionVar = ternaryExpressionVar; - } - - /** - * Gets the node representing the conditional operand for this node - * - * @return the condition operand node - */ - public Node getConditionOperand() { - return condition; - } - - /** - * Gets the node representing the "then" operand for this node - * - * @return the "then" operand node - */ - public Node getThenOperand() { - return thenOperand; - } - - /** - * Gets the node representing the "else" operand for this node - * - * @return the "else" operand node - */ - public Node getElseOperand() { - return elseOperand; - } - - /** - * This is a variable created by dataflow to which each case expression of the ternary expression - * is assigned. Its value should be used for the value of the switch expression. - * - * @return the variable for this ternary expression - */ - public LocalVariableNode getTernaryExpressionVar() { - return ternaryExpressionVar; - } - - @Override - public ConditionalExpressionTree getTree() { - return tree; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitTernaryExpression(this, p); - } - - @Override - public String toString() { - return "(" + getConditionOperand() + " ? " + getThenOperand() + " : " + getElseOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof TernaryExpressionNode)) { - return false; + /** The {@code ConditionalExpressionTree} corresponding to this node */ + protected final ConditionalExpressionTree tree; + + /** Node representing the condition checked by the expression */ + protected final Node condition; + + /** Node representing the "then" case of the expression */ + protected final Node thenOperand; + + /** Node representing the "else" case of the expression */ + protected final Node elseOperand; + + /** + * This is a variable created by dataflow to which each case expression of the ternary + * expression is assigned. Its value should be used for the value of the switch expression. + */ + private final LocalVariableNode ternaryExpressionVar; + + /** + * Creates a new TernaryExpressionNode. + * + * @param tree the {@code ConditionalExpressionTree} for the node + * @param condition node representing the condition checked by the expression + * @param thenOperand node representing the "then" case of the expression + * @param elseOperand node representing the "else" case of the expression + * @param ternaryExpressionVar a variable created by dataflow to which each case expression of + * the ternary expression is assigned. Its value should be used for the value of the switch + * expression. + */ + public TernaryExpressionNode( + ConditionalExpressionTree tree, + Node condition, + Node thenOperand, + Node elseOperand, + LocalVariableNode ternaryExpressionVar) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.condition = condition; + this.thenOperand = thenOperand; + this.elseOperand = elseOperand; + this.ternaryExpressionVar = ternaryExpressionVar; + } + + /** + * Gets the node representing the conditional operand for this node + * + * @return the condition operand node + */ + public Node getConditionOperand() { + return condition; + } + + /** + * Gets the node representing the "then" operand for this node + * + * @return the "then" operand node + */ + public Node getThenOperand() { + return thenOperand; + } + + /** + * Gets the node representing the "else" operand for this node + * + * @return the "else" operand node + */ + public Node getElseOperand() { + return elseOperand; + } + + /** + * This is a variable created by dataflow to which each case expression of the ternary + * expression is assigned. Its value should be used for the value of the switch expression. + * + * @return the variable for this ternary expression + */ + public LocalVariableNode getTernaryExpressionVar() { + return ternaryExpressionVar; + } + + @Override + public ConditionalExpressionTree getTree() { + return tree; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitTernaryExpression(this, p); + } + + @Override + public String toString() { + return "(" + + getConditionOperand() + + " ? " + + getThenOperand() + + " : " + + getElseOperand() + + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TernaryExpressionNode)) { + return false; + } + TernaryExpressionNode other = (TernaryExpressionNode) obj; + return getConditionOperand().equals(other.getConditionOperand()) + && getThenOperand().equals(other.getThenOperand()) + && getElseOperand().equals(other.getElseOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getConditionOperand(), getThenOperand(), getElseOperand()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Arrays.asList(getConditionOperand(), getThenOperand(), getElseOperand()); } - TernaryExpressionNode other = (TernaryExpressionNode) obj; - return getConditionOperand().equals(other.getConditionOperand()) - && getThenOperand().equals(other.getThenOperand()) - && getElseOperand().equals(other.getElseOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getConditionOperand(), getThenOperand(), getElseOperand()); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Arrays.asList(getConditionOperand(), getThenOperand(), getElseOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java index 307d0cee743..e7d5f3c589b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThisNode.java @@ -1,10 +1,12 @@ package org.checkerframework.dataflow.cfg.node; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; import java.util.Collections; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for a reference to 'this', either implicit or explicit. @@ -15,23 +17,23 @@ */ public abstract class ThisNode extends Node { - protected ThisNode(TypeMirror type) { - super(type); - } - - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ThisNode; - } - - @Override - public int hashCode() { - return 3559101; // Objects.hash("this"); - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + protected ThisNode(TypeMirror type) { + super(type); + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ThisNode; + } + + @Override + public int hashCode() { + return 3559101; // Objects.hash("this"); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java index a80adae19ee..3adc6307d06 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.ThrowTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for exception throws: @@ -18,51 +21,51 @@ */ public class ThrowNode extends Node { - protected final ThrowTree tree; - protected final Node expression; + protected final ThrowTree tree; + protected final Node expression; - public ThrowNode(ThrowTree tree, Node expression, Types types) { - super(types.getNoType(TypeKind.NONE)); - this.tree = tree; - this.expression = expression; - } + public ThrowNode(ThrowTree tree, Node expression, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.expression = expression; + } - public Node getExpression() { - return expression; - } + public Node getExpression() { + return expression; + } - @Override - public ThrowTree getTree() { - return tree; - } + @Override + public ThrowTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitThrow(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitThrow(this, p); + } - @Override - public String toString() { - return "throw " + expression; - } + @Override + public String toString() { + return "throw " + expression; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ThrowNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ThrowNode)) { + return false; + } + ThrowNode other = (ThrowNode) obj; + return getExpression().equals(other.getExpression()); } - ThrowNode other = (ThrowNode) obj; - return getExpression().equals(other.getExpression()); - } - @Override - public int hashCode() { - return Objects.hash(ThrowNode.class, expression); - } + @Override + public int hashCode() { + return Objects.hash(ThrowNode.class, expression); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(expression); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(expression); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java index dfad710c0dc..55e35949827 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.TypeCastTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * A node for the cast operator: @@ -16,55 +19,56 @@ */ public class TypeCastNode extends Node { - protected final TypeCastTree tree; - protected final Node operand; + protected final TypeCastTree tree; + protected final Node operand; - /** For Types.isSameType. */ - protected final Types types; + /** For Types.isSameType. */ + protected final Types types; - public TypeCastNode(TypeCastTree tree, Node operand, TypeMirror type, Types types) { - super(type); - this.tree = tree; - this.operand = operand; - this.types = types; - } + public TypeCastNode(TypeCastTree tree, Node operand, TypeMirror type, Types types) { + super(type); + this.tree = tree; + this.operand = operand; + this.types = types; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public TypeCastTree getTree() { - return tree; - } + @Override + public TypeCastTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitTypeCast(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitTypeCast(this, p); + } - @Override - public String toString() { - return "(" + getType() + ")" + getOperand(); - } + @Override + public String toString() { + return "(" + getType() + ")" + getOperand(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof TypeCastNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TypeCastNode)) { + return false; + } + TypeCastNode other = (TypeCastNode) obj; + return getOperand().equals(other.getOperand()) + && types.isSameType(getType(), other.getType()); } - TypeCastNode other = (TypeCastNode) obj; - return getOperand().equals(other.getOperand()) && types.isSameType(getType(), other.getType()); - } - @Override - public int hashCode() { - return Objects.hash(getType(), getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(getType(), getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java index bfcdb161d70..37921a06531 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnaryOperationNode.java @@ -1,11 +1,13 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.UnaryTree; -import java.util.Collection; -import java.util.Collections; + import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; + /** * A node for a postfix or an unary expression. * @@ -19,27 +21,27 @@ */ public abstract class UnaryOperationNode extends Node { - protected final UnaryTree tree; - protected final Node operand; - - protected UnaryOperationNode(UnaryTree tree, Node operand) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - this.operand = operand; - } - - public Node getOperand() { - return this.operand; - } - - @Override - public UnaryTree getTree() { - return tree; - } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + protected final UnaryTree tree; + protected final Node operand; + + protected UnaryOperationNode(UnaryTree tree, Node operand) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return this.operand; + } + + @Override + public UnaryTree getTree() { + return tree; + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java index a87002eb16b..661de7e83a8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java @@ -2,9 +2,11 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.Tree; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** * A node for bitwise right shift operations with zero extension: * @@ -14,40 +16,40 @@ */ public class UnsignedRightShiftNode extends BinaryOperationNode { - /** - * Constructs an {@link UnsignedRightShiftNode} - * - * @param tree the binary tree - * @param left the left operand - * @param right the right operand - */ - public UnsignedRightShiftNode(BinaryTree tree, Node left, Node right) { - super(tree, left, right); - assert tree.getKind() == Tree.Kind.UNSIGNED_RIGHT_SHIFT; - } - - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitUnsignedRightShift(this, p); - } - - @Override - public String toString() { - return "(" + getLeftOperand() + " >>> " + getRightOperand() + ")"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof UnsignedRightShiftNode)) { - return false; + /** + * Constructs an {@link UnsignedRightShiftNode} + * + * @param tree the binary tree + * @param left the left operand + * @param right the right operand + */ + public UnsignedRightShiftNode(BinaryTree tree, Node left, Node right) { + super(tree, left, right); + assert tree.getKind() == Tree.Kind.UNSIGNED_RIGHT_SHIFT; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitUnsignedRightShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >>> " + getRightOperand() + ")"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof UnsignedRightShiftNode)) { + return false; + } + UnsignedRightShiftNode other = (UnsignedRightShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return Objects.hash(getLeftOperand(), getRightOperand()); } - UnsignedRightShiftNode other = (UnsignedRightShiftNode) obj; - return getLeftOperand().equals(other.getLeftOperand()) - && getRightOperand().equals(other.getRightOperand()); - } - - @Override - public int hashCode() { - return Objects.hash(getLeftOperand(), getRightOperand()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java index f8570e8aed4..8a461a45264 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java @@ -1,13 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.LiteralTree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + /** * A node for a literals that have some form of value: * @@ -24,54 +26,54 @@ */ public abstract class ValueLiteralNode extends Node { - /** The tree for the value literal. */ - protected final LiteralTree tree; - - /** - * Returns the value of the literal, null for the null literal. - * - * @return the value of the literal, null for the null literal - */ - public abstract @Nullable Object getValue(); + /** The tree for the value literal. */ + protected final LiteralTree tree; - protected ValueLiteralNode(LiteralTree tree) { - super(TreeUtils.typeOf(tree)); - this.tree = tree; - } + /** + * Returns the value of the literal, null for the null literal. + * + * @return the value of the literal, null for the null literal + */ + public abstract @Nullable Object getValue(); - @Override - public LiteralTree getTree() { - return tree; - } + protected ValueLiteralNode(LiteralTree tree) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + } - @Override - public String toString() { - return String.valueOf(getValue()); - } + @Override + public LiteralTree getTree() { + return tree; + } - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; + @Override + public String toString() { + return String.valueOf(getValue()); } - if (!(obj instanceof ValueLiteralNode)) { - return false; + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ValueLiteralNode)) { + return false; + } + ValueLiteralNode other = (ValueLiteralNode) obj; + Object val = getValue(); + Object otherVal = other.getValue(); + return Objects.equals(val, otherVal); } - ValueLiteralNode other = (ValueLiteralNode) obj; - Object val = getValue(); - Object otherVal = other.getValue(); - return Objects.equals(val, otherVal); - } - @Override - public int hashCode() { - // value might be null - return Objects.hash(this.getClass(), getValue()); - } + @Override + public int hashCode() { + // value might be null + return Objects.hash(this.getClass(), getValue()); + } - @Override - @SideEffectFree - public final Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public final Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java index 8b39801d6a5..3fe7294cd57 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java @@ -1,13 +1,15 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.VariableTree; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + /** * A node for a variable declaration, including local variables and fields: * @@ -20,53 +22,53 @@ */ public class VariableDeclarationNode extends Node { - protected final VariableTree tree; - protected final String name; + protected final VariableTree tree; + protected final String name; - // TODO: make modifier accessible + // TODO: make modifier accessible - public VariableDeclarationNode(VariableTree t) { - super(TreeUtils.typeOf(t)); - tree = t; - name = tree.getName().toString(); - } + public VariableDeclarationNode(VariableTree t) { + super(TreeUtils.typeOf(t)); + tree = t; + name = tree.getName().toString(); + } - public String getName() { - return name; - } + public String getName() { + return name; + } - @Override - public VariableTree getTree() { - return tree; - } + @Override + public VariableTree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitVariableDeclaration(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitVariableDeclaration(this, p); + } - @Override - public String toString() { - return name; - } + @Override + public String toString() { + return name; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof VariableDeclarationNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof VariableDeclarationNode)) { + return false; + } + VariableDeclarationNode other = (VariableDeclarationNode) obj; + return getName().equals(other.getName()); } - VariableDeclarationNode other = (VariableDeclarationNode) obj; - return getName().equals(other.getName()); - } - @Override - public int hashCode() { - return Objects.hash(getName()); - } + @Override + public int hashCode() { + return Objects.hash(getName()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.emptyList(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java index 566afde3e47..07e5e031c70 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.cfg.node; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TypesUtils; + import java.util.Collection; import java.util.Collections; import java.util.Objects; + import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.TypesUtils; /** * A node for the widening primitive conversion operation. See JLS 5.1.2 for the definition of @@ -19,53 +22,53 @@ */ public class WideningConversionNode extends Node { - protected final Tree tree; - protected final Node operand; + protected final Tree tree; + protected final Node operand; - public WideningConversionNode(Tree tree, Node operand, TypeMirror type) { - super(type); - assert TypesUtils.isPrimitive(type) : "non-primitive type in widening conversion"; - this.tree = tree; - this.operand = operand; - } + public WideningConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + assert TypesUtils.isPrimitive(type) : "non-primitive type in widening conversion"; + this.tree = tree; + this.operand = operand; + } - public Node getOperand() { - return operand; - } + public Node getOperand() { + return operand; + } - @Override - public Tree getTree() { - return tree; - } + @Override + public Tree getTree() { + return tree; + } - @Override - public R accept(NodeVisitor visitor, P p) { - return visitor.visitWideningConversion(this, p); - } + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitWideningConversion(this, p); + } - @Override - public String toString() { - return "WideningConversion(" + getOperand() + ", " + type + ")"; - } + @Override + public String toString() { + return "WideningConversion(" + getOperand() + ", " + type + ")"; + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof WideningConversionNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof WideningConversionNode)) { + return false; + } + WideningConversionNode other = (WideningConversionNode) obj; + return getOperand().equals(other.getOperand()) + && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); } - WideningConversionNode other = (WideningConversionNode) obj; - return getOperand().equals(other.getOperand()) - && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); - } - @Override - public int hashCode() { - return Objects.hash(WideningConversionNode.class, getOperand()); - } + @Override + public int hashCode() { + return Objects.hash(WideningConversionNode.class, getOperand()); + } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.singletonList(getOperand()); - } + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singletonList(getOperand()); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java index 7139214675a..005a4c60d5e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/BusyExpressionPlayground.java @@ -14,25 +14,25 @@ */ public class BusyExpressionPlayground { - /** Class cannot be instantiated. */ - private BusyExpressionPlayground() { - throw new AssertionError("Class BusyExpressionPlayground cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private BusyExpressionPlayground() { + throw new AssertionError("Class BusyExpressionPlayground cannot be instantiated."); + } - /** - * Run busy expression analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run busy expression analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // Run the analysis and create a PDF file - BusyExprTransfer transfer = new BusyExprTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); - } + // Run the analysis and create a PDF file + BusyExprTransfer transfer = new BusyExprTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java index fddf9760e9b..9a901bf5fd0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java @@ -11,25 +11,25 @@ /** The playground for constant propagation analysis. */ public class ConstantPropagationPlayground { - /** Class cannot be instantiated. */ - private ConstantPropagationPlayground() { - throw new AssertionError("Class ConstantPropagationPlayground cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private ConstantPropagationPlayground() { + throw new AssertionError("Class ConstantPropagationPlayground cannot be instantiated."); + } - /** - * Run constant propagation analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run constant propagation analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // run the analysis and create a PDF file - ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); - } + // run the analysis and create a PDF file + ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java index 10946fcae33..80682f13291 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/LiveVariablePlayground.java @@ -11,25 +11,25 @@ /** The playground of live variable analysis. */ public class LiveVariablePlayground { - /** Do not instantiate. */ - private LiveVariablePlayground() { - throw new Error("do not instantiate"); - } + /** Do not instantiate. */ + private LiveVariablePlayground() { + throw new Error("do not instantiate"); + } - /** - * Run live variable analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run live variable analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // Run the analysis and create a PDF file - LiveVarTransfer transfer = new LiveVarTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); - } + // Run the analysis and create a PDF file + LiveVarTransfer transfer = new LiveVarTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, backwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java index de2672f8543..04e52030dc5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/playground/ReachingDefinitionPlayground.java @@ -14,25 +14,25 @@ */ public class ReachingDefinitionPlayground { - /** Class cannot be instantiated. */ - private ReachingDefinitionPlayground() { - throw new AssertionError("Class ReachingDefinitionPlayground cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private ReachingDefinitionPlayground() { + throw new AssertionError("Class ReachingDefinitionPlayground cannot be instantiated."); + } - /** - * Run reaching definition analysis on a file. - * - * @param args command-line arguments - */ - public static void main(String[] args) { + /** + * Run reaching definition analysis on a file. + * + * @param args command-line arguments + */ + public static void main(String[] args) { - // Parse the arguments. - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + // Parse the arguments. + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - // Run the analysis and create a PDF file - ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); - } + // Run the analysis and create a PDF file + ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.performAnalysis(config, forwardAnalysis); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java index 244a2288855..e17c25409c0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java @@ -1,15 +1,5 @@ package org.checkerframework.dataflow.cfg.visualize; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.StringJoiner; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; @@ -29,6 +19,18 @@ import org.plumelib.util.StringsPlume; import org.plumelib.util.UniqueId; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.StringJoiner; + +import javax.lang.model.type.TypeMirror; + /** * This abstract class makes implementing a {@link CFGVisualizer} easier. Some of the methods in * {@link CFGVisualizer} are already implemented in this abstract class, but can be overridden if @@ -41,431 +43,438 @@ * @see StringCFGVisualizer */ public abstract class AbstractCFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> - implements CFGVisualizer { - - /** - * If {@code true}, {@link CFGVisualizer} returns more detailed information. - * - *

          Initialized in {@link #init(Map)}. - */ - protected boolean verbose; - - /** The line separator. */ - protected static final String lineSeparator = System.lineSeparator(); - - /** The indentation for elements of the store. */ - protected static final String storeEntryIndent = " "; - - @Override - public void init(Map args) { - this.verbose = toBoolean(args.get("verbose")); - } - - /** - * Convert the value to boolean, by parsing a string or casting any other value. null converts to - * false. - * - * @param o an object to convert to boolean - * @return {@code o} converted to boolean - */ - private static boolean toBoolean(@Nullable Object o) { - if (o == null) { - return false; + V extends AbstractValue, S extends Store, T extends TransferFunction> + implements CFGVisualizer { + + /** + * If {@code true}, {@link CFGVisualizer} returns more detailed information. + * + *

          Initialized in {@link #init(Map)}. + */ + protected boolean verbose; + + /** The line separator. */ + protected static final String lineSeparator = System.lineSeparator(); + + /** The indentation for elements of the store. */ + protected static final String storeEntryIndent = " "; + + @Override + public void init(Map args) { + this.verbose = toBoolean(args.get("verbose")); } - if (o instanceof String) { - return Boolean.parseBoolean((String) o); + + /** + * Convert the value to boolean, by parsing a string or casting any other value. null converts + * to false. + * + * @param o an object to convert to boolean + * @return {@code o} converted to boolean + */ + private static boolean toBoolean(@Nullable Object o) { + if (o == null) { + return false; + } + if (o instanceof String) { + return Boolean.parseBoolean((String) o); + } + return (boolean) o; } - return (boolean) o; - } - - /** - * Visualize a control flow graph. - * - * @param cfg the current control flow graph - * @param entry the entry block of the control flow graph - * @param analysis the current analysis - * @return the representation of the control flow graph - */ - protected String visualizeGraph( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - return visualizeGraphHeader() - + visualizeGraphWithoutHeaderAndFooter(cfg, entry, analysis) - + visualizeGraphFooter(); - } - - /** - * Helper method to visualize a control flow graph, without outputting a header or footer. - * - * @param cfg the control flow graph - * @param entry the entry block of the control flow graph - * @param analysis the current analysis - * @return the String representation of the control flow graph - */ - protected String visualizeGraphWithoutHeaderAndFooter( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - Set visited = new LinkedHashSet<>(); - StringBuilder sbGraph = new StringBuilder(); - Queue workList = new ArrayDeque<>(); - Block cur = entry; - visited.add(entry); - while (cur != null) { - handleSuccessorsHelper(cur, visited, workList, sbGraph); - cur = workList.poll(); + + /** + * Visualize a control flow graph. + * + * @param cfg the current control flow graph + * @param entry the entry block of the control flow graph + * @param analysis the current analysis + * @return the representation of the control flow graph + */ + protected String visualizeGraph( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + return visualizeGraphHeader() + + visualizeGraphWithoutHeaderAndFooter(cfg, entry, analysis) + + visualizeGraphFooter(); } - sbGraph.append(lineSeparator); - sbGraph.append(visualizeNodes(visited, cfg, analysis)); - return sbGraph.toString(); - } - - /** - * Outputs, to sbGraph, a visualization of a block's edges, but not the block itself. (The block - * itself is output elsewhere.) Also adds the successors of the block to the work list and the - * visited blocks list. - * - * @param cur the current block - * @param visited the set of blocks that have already been visited or are in the work list; side - * effected by this method - * @param workList the queue of blocks to be processed; side effected by this method - * @param sbGraph the {@link StringBuilder} to store the graph; side effected by this method - */ - protected void handleSuccessorsHelper( - Block cur, Set visited, Queue workList, StringBuilder sbGraph) { - if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { - ConditionalBlock ccur = ((ConditionalBlock) cur); - Block thenSuccessor = ccur.getThenSuccessor(); - sbGraph.append( - visualizeEdge(ccur.getUid(), thenSuccessor.getUid(), ccur.getThenFlowRule().toString())); - sbGraph.append(lineSeparator); - addBlock(thenSuccessor, visited, workList); - Block elseSuccessor = ccur.getElseSuccessor(); - sbGraph.append( - visualizeEdge(ccur.getUid(), elseSuccessor.getUid(), ccur.getElseFlowRule().toString())); - sbGraph.append(lineSeparator); - addBlock(elseSuccessor, visited, workList); - } else { - SingleSuccessorBlock sscur = (SingleSuccessorBlock) cur; - Block succ = sscur.getSuccessor(); - if (succ != null) { - sbGraph.append(visualizeEdge(cur.getUid(), succ.getUid(), sscur.getFlowRule().name())); + + /** + * Helper method to visualize a control flow graph, without outputting a header or footer. + * + * @param cfg the control flow graph + * @param entry the entry block of the control flow graph + * @param analysis the current analysis + * @return the String representation of the control flow graph + */ + protected String visualizeGraphWithoutHeaderAndFooter( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + Set visited = new LinkedHashSet<>(); + StringBuilder sbGraph = new StringBuilder(); + Queue workList = new ArrayDeque<>(); + Block cur = entry; + visited.add(entry); + while (cur != null) { + handleSuccessorsHelper(cur, visited, workList, sbGraph); + cur = workList.poll(); + } sbGraph.append(lineSeparator); - addBlock(succ, visited, workList); - } + sbGraph.append(visualizeNodes(visited, cfg, analysis)); + return sbGraph.toString(); } - if (cur.getType() == Block.BlockType.EXCEPTION_BLOCK) { - ExceptionBlock ecur = (ExceptionBlock) cur; - for (Map.Entry> e : ecur.getExceptionalSuccessors().entrySet()) { - TypeMirror cause = e.getKey(); - String exception = cause.toString(); - if (exception.startsWith("java.lang.")) { - exception = exception.replace("java.lang.", ""); + + /** + * Outputs, to sbGraph, a visualization of a block's edges, but not the block itself. (The block + * itself is output elsewhere.) Also adds the successors of the block to the work list and the + * visited blocks list. + * + * @param cur the current block + * @param visited the set of blocks that have already been visited or are in the work list; side + * effected by this method + * @param workList the queue of blocks to be processed; side effected by this method + * @param sbGraph the {@link StringBuilder} to store the graph; side effected by this method + */ + protected void handleSuccessorsHelper( + Block cur, Set visited, Queue workList, StringBuilder sbGraph) { + if (cur.getType() == Block.BlockType.CONDITIONAL_BLOCK) { + ConditionalBlock ccur = ((ConditionalBlock) cur); + Block thenSuccessor = ccur.getThenSuccessor(); + sbGraph.append( + visualizeEdge( + ccur.getUid(), + thenSuccessor.getUid(), + ccur.getThenFlowRule().toString())); + sbGraph.append(lineSeparator); + addBlock(thenSuccessor, visited, workList); + Block elseSuccessor = ccur.getElseSuccessor(); + sbGraph.append( + visualizeEdge( + ccur.getUid(), + elseSuccessor.getUid(), + ccur.getElseFlowRule().toString())); + sbGraph.append(lineSeparator); + addBlock(elseSuccessor, visited, workList); + } else { + SingleSuccessorBlock sscur = (SingleSuccessorBlock) cur; + Block succ = sscur.getSuccessor(); + if (succ != null) { + sbGraph.append( + visualizeEdge(cur.getUid(), succ.getUid(), sscur.getFlowRule().name())); + sbGraph.append(lineSeparator); + addBlock(succ, visited, workList); + } } - for (Block b : e.getValue()) { - sbGraph.append(visualizeEdge(cur.getUid(), b.getUid(), exception)); - sbGraph.append(lineSeparator); - addBlock(b, visited, workList); + if (cur.getType() == Block.BlockType.EXCEPTION_BLOCK) { + ExceptionBlock ecur = (ExceptionBlock) cur; + for (Map.Entry> e : ecur.getExceptionalSuccessors().entrySet()) { + TypeMirror cause = e.getKey(); + String exception = cause.toString(); + if (exception.startsWith("java.lang.")) { + exception = exception.replace("java.lang.", ""); + } + for (Block b : e.getValue()) { + sbGraph.append(visualizeEdge(cur.getUid(), b.getUid(), exception)); + sbGraph.append(lineSeparator); + addBlock(b, visited, workList); + } + } } - } } - } - - /** - * Checks whether a block exists in the visited blocks list, and, if not, adds it to the visited - * blocks list and the work list. - * - * @param b the block to check - * @param visited the set of blocks that have already been visited or are in the work list - * @param workList the queue of blocks to be processed - */ - protected void addBlock(Block b, Set visited, Queue workList) { - if (visited.add(b)) { - workList.add(b); - } - } - - /** - * Helper method to visualize a block. - * - *

          NOTE: The output ends with a separator, only if an "after" store is visualized. The client - * {@link #visualizeBlock} should correct this if needed. - * - * @param bb the block - * @param analysis the current analysis - * @param separator the line separator. Examples: "\\l" for left justification in {@link - * DOTCFGVisualizer} (this is really a terminator, not a separator), "\n" to add a new line in - * {@link StringCFGVisualizer} - * @return the String representation of the block - */ - protected String visualizeBlockWithSeparator( - Block bb, @Nullable Analysis analysis, String separator) { - StringBuilder sbBlock = new StringBuilder(); - String contents = loopOverBlockContents(bb, analysis, separator); - if (!contents.isEmpty()) { - sbBlock.append(contents); + + /** + * Checks whether a block exists in the visited blocks list, and, if not, adds it to the visited + * blocks list and the work list. + * + * @param b the block to check + * @param visited the set of blocks that have already been visited or are in the work list + * @param workList the queue of blocks to be processed + */ + protected void addBlock(Block b, Set visited, Queue workList) { + if (visited.add(b)) { + workList.add(b); + } } - if (sbBlock.length() == 0) { - // Nothing got appended; use default text for empty block - if (bb.getType() == Block.BlockType.SPECIAL_BLOCK) { - sbBlock.append(visualizeSpecialBlock((SpecialBlock) bb)); - } else if (bb.getType() == Block.BlockType.CONDITIONAL_BLOCK) { - sbBlock.append(visualizeConditionalBlock((ConditionalBlock) bb)); - } else { - sbBlock.append(""); - } + + /** + * Helper method to visualize a block. + * + *

          NOTE: The output ends with a separator, only if an "after" store is visualized. The client + * {@link #visualizeBlock} should correct this if needed. + * + * @param bb the block + * @param analysis the current analysis + * @param separator the line separator. Examples: "\\l" for left justification in {@link + * DOTCFGVisualizer} (this is really a terminator, not a separator), "\n" to add a new line + * in {@link StringCFGVisualizer} + * @return the String representation of the block + */ + protected String visualizeBlockWithSeparator( + Block bb, @Nullable Analysis analysis, String separator) { + StringBuilder sbBlock = new StringBuilder(); + String contents = loopOverBlockContents(bb, analysis, separator); + if (!contents.isEmpty()) { + sbBlock.append(contents); + } + if (sbBlock.length() == 0) { + // Nothing got appended; use default text for empty block + if (bb.getType() == Block.BlockType.SPECIAL_BLOCK) { + sbBlock.append(visualizeSpecialBlock((SpecialBlock) bb)); + } else if (bb.getType() == Block.BlockType.CONDITIONAL_BLOCK) { + sbBlock.append(visualizeConditionalBlock((ConditionalBlock) bb)); + } else { + sbBlock.append(""); + } + } + + // Visualize transfer input if necessary. + if (analysis != null) { + sbBlock.insert(0, visualizeBlockTransferInputBefore(bb, analysis) + separator); + if (verbose) { + Node lastNode = bb.getLastNode(); + if (lastNode != null) { + if (!sbBlock.toString().endsWith(separator)) { + sbBlock.append(separator); + } + sbBlock.append(visualizeBlockTransferInputAfter(bb, analysis) + separator); + } + } + } + return sbBlock.toString(); } - // Visualize transfer input if necessary. - if (analysis != null) { - sbBlock.insert(0, visualizeBlockTransferInputBefore(bb, analysis) + separator); - if (verbose) { - Node lastNode = bb.getLastNode(); - if (lastNode != null) { - if (!sbBlock.toString().endsWith(separator)) { - sbBlock.append(separator); - } - sbBlock.append(visualizeBlockTransferInputAfter(bb, analysis) + separator); + /** + * Iterates over the block content and visualizes all the nodes in it. + * + * @param bb the block + * @param analysis the current analysis + * @param separator the separator between the nodes of the block + * @return the String representation of the contents of the block + */ + protected String loopOverBlockContents( + Block bb, @Nullable Analysis analysis, String separator) { + + List contents = addBlockContent(bb); + StringJoiner sjBlockContents = new StringJoiner(separator); + for (Node t : contents) { + sjBlockContents.add(visualizeBlockNode(t, analysis)); } - } + return sjBlockContents.toString(); } - return sbBlock.toString(); - } - - /** - * Iterates over the block content and visualizes all the nodes in it. - * - * @param bb the block - * @param analysis the current analysis - * @param separator the separator between the nodes of the block - * @return the String representation of the contents of the block - */ - protected String loopOverBlockContents( - Block bb, @Nullable Analysis analysis, String separator) { - - List contents = addBlockContent(bb); - StringJoiner sjBlockContents = new StringJoiner(separator); - for (Node t : contents) { - sjBlockContents.add(visualizeBlockNode(t, analysis)); + + /** + * Returns the contents of the block. + * + * @param bb the block + * @return the contents of the block, as a list of nodes + */ + protected List addBlockContent(Block bb) { + return bb.getNodes(); } - return sjBlockContents.toString(); - } - - /** - * Returns the contents of the block. - * - * @param bb the block - * @return the contents of the block, as a list of nodes - */ - protected List addBlockContent(Block bb) { - return bb.getNodes(); - } - - /** - * Format the given object as a String suitable for the output format, i.e. with format-specific - * characters escaped. - * - * @param obj an object - * @return the formatted String from the given object - */ - protected abstract String format(Object obj); - - @Override - public String visualizeBlockNode(Node t, @Nullable Analysis analysis) { - StringBuilder sbBlockNode = new StringBuilder(); - sbBlockNode.append(format(t)).append(" [ ").append(getNodeSimpleName(t)).append(" ]"); - if (analysis != null) { - V value = analysis.getValue(t); - if (value != null) { - sbBlockNode.append(" > ").append(format(value)); - } + + /** + * Format the given object as a String suitable for the output format, i.e. with format-specific + * characters escaped. + * + * @param obj an object + * @return the formatted String from the given object + */ + protected abstract String format(Object obj); + + @Override + public String visualizeBlockNode(Node t, @Nullable Analysis analysis) { + StringBuilder sbBlockNode = new StringBuilder(); + sbBlockNode.append(format(t)).append(" [ ").append(getNodeSimpleName(t)).append(" ]"); + if (analysis != null) { + V value = analysis.getValue(t); + if (value != null) { + sbBlockNode.append(" > ").append(format(value)); + } + } + return sbBlockNode.toString(); } - return sbBlockNode.toString(); - } - - /** Whether to visualize before or after a block. */ - protected enum VisualizeWhere { - /** Visualize before the block. */ - BEFORE, - /** Visualize after the block. */ - AFTER - } - - /** - * Visualize the transfer input before or after the given block. - * - * @param where either BEFORE or AFTER - * @param bb a block - * @param analysis the current analysis - * @param separator the line separator. Examples: "\\l" for left justification in {@link - * DOTCFGVisualizer} (which is actually a line TERMINATOR, not a separator!), "\n" to add a - * new line in {@link StringCFGVisualizer} - * @return the visualization of the transfer input before or after the given block - */ - protected String visualizeBlockTransferInputHelper( - VisualizeWhere where, Block bb, Analysis analysis, String separator) { - if (analysis == null) { - throw new BugInCF( - "analysis must be non-null when visualizing the transfer input of a block."); + + /** Whether to visualize before or after a block. */ + protected enum VisualizeWhere { + /** Visualize before the block. */ + BEFORE, + /** Visualize after the block. */ + AFTER } - Direction analysisDirection = analysis.getDirection(); - - S regularStore; - S thenStore = null; - S elseStore = null; - boolean isTwoStores = false; - - UniqueId storesFrom; - - if (analysisDirection == Direction.FORWARD && where == VisualizeWhere.AFTER) { - regularStore = analysis.getResult().getStoreAfter(bb); - storesFrom = analysis.getResult(); - } else if (analysisDirection == Direction.BACKWARD && where == VisualizeWhere.BEFORE) { - regularStore = analysis.getResult().getStoreBefore(bb); - storesFrom = analysis.getResult(); - } else { - TransferInput input = analysis.getInput(bb); - // Per the documentation of AbstractAnalysis#inputs, null means no information. - if (input == null) { - regularStore = null; - storesFrom = null; - } else { - storesFrom = input; - isTwoStores = input.containsTwoStores(); - regularStore = input.getRegularStore(); - thenStore = input.getThenStore(); - elseStore = input.getElseStore(); - } + /** + * Visualize the transfer input before or after the given block. + * + * @param where either BEFORE or AFTER + * @param bb a block + * @param analysis the current analysis + * @param separator the line separator. Examples: "\\l" for left justification in {@link + * DOTCFGVisualizer} (which is actually a line TERMINATOR, not a separator!), "\n" to add a + * new line in {@link StringCFGVisualizer} + * @return the visualization of the transfer input before or after the given block + */ + protected String visualizeBlockTransferInputHelper( + VisualizeWhere where, Block bb, Analysis analysis, String separator) { + if (analysis == null) { + throw new BugInCF( + "analysis must be non-null when visualizing the transfer input of a block."); + } + + Direction analysisDirection = analysis.getDirection(); + + S regularStore; + S thenStore = null; + S elseStore = null; + boolean isTwoStores = false; + + UniqueId storesFrom; + + if (analysisDirection == Direction.FORWARD && where == VisualizeWhere.AFTER) { + regularStore = analysis.getResult().getStoreAfter(bb); + storesFrom = analysis.getResult(); + } else if (analysisDirection == Direction.BACKWARD && where == VisualizeWhere.BEFORE) { + regularStore = analysis.getResult().getStoreBefore(bb); + storesFrom = analysis.getResult(); + } else { + TransferInput input = analysis.getInput(bb); + // Per the documentation of AbstractAnalysis#inputs, null means no information. + if (input == null) { + regularStore = null; + storesFrom = null; + } else { + storesFrom = input; + isTwoStores = input.containsTwoStores(); + regularStore = input.getRegularStore(); + thenStore = input.getThenStore(); + elseStore = input.getElseStore(); + } + } + + StringBuilder sbStore = new StringBuilder(); + if (verbose) { + sbStore.append((storesFrom == null ? "null" : storesFrom.getClassAndUid()) + separator); + } + sbStore.append(where == VisualizeWhere.BEFORE ? "Before: " : "After: "); + + if (regularStore == null) { + sbStore.append("()"); + } else if (!isTwoStores) { + sbStore.append(visualizeStore(regularStore)); + } else { + assert thenStore != null : "@AssumeAssertion(nullness): invariant"; + assert elseStore != null : "@AssumeAssertion(nullness): invariant"; + sbStore.append("then="); + sbStore.append(visualizeStore(thenStore)); + sbStore.append(","); + sbStore.append(separator); + sbStore.append("else="); + sbStore.append(visualizeStore(elseStore)); + } + if (where == VisualizeWhere.BEFORE) { + sbStore.append(separator + "~~~~~~~~~"); + } else { + sbStore.insert(0, "~~~~~~~~~" + separator); + } + return sbStore.toString(); } - StringBuilder sbStore = new StringBuilder(); - if (verbose) { - sbStore.append((storesFrom == null ? "null" : storesFrom.getClassAndUid()) + separator); + /** + * Visualize a special block. + * + * @param sbb the special block + * @return the String representation of the special block + */ + protected String visualizeSpecialBlockHelper(SpecialBlock sbb) { + switch (sbb.getSpecialType()) { + case ENTRY: + return ""; + case EXIT: + return ""; + case EXCEPTIONAL_EXIT: + return ""; + default: + throw new BugInCF("Unrecognized special block type: " + sbb.getType()); + } } - sbStore.append(where == VisualizeWhere.BEFORE ? "Before: " : "After: "); - - if (regularStore == null) { - sbStore.append("()"); - } else if (!isTwoStores) { - sbStore.append(visualizeStore(regularStore)); - } else { - assert thenStore != null : "@AssumeAssertion(nullness): invariant"; - assert elseStore != null : "@AssumeAssertion(nullness): invariant"; - sbStore.append("then="); - sbStore.append(visualizeStore(thenStore)); - sbStore.append(","); - sbStore.append(separator); - sbStore.append("else="); - sbStore.append(visualizeStore(elseStore)); + + /** + * Generate the order of processing blocks. Because a block may appear more than once in {@link + * ControlFlowGraph#getDepthFirstOrderedBlocks()}, the orders of each block are stored in a + * separate array list. + * + * @param cfg the current control flow graph + * @return an IdentityHashMap that maps from blocks to their orders + */ + protected IdentityHashMap> getProcessOrder(ControlFlowGraph cfg) { + IdentityHashMap> depthFirstOrder = new IdentityHashMap<>(); + int count = 1; + for (Block b : cfg.getDepthFirstOrderedBlocks()) { + depthFirstOrder.computeIfAbsent(b, k -> new ArrayList<>()); + @SuppressWarnings( + "nullness:assignment.type.incompatible") // computeIfAbsent's function doesn't + // return null + @NonNull List blockIds = depthFirstOrder.get(b); + blockIds.add(count++); + } + return depthFirstOrder; } - if (where == VisualizeWhere.BEFORE) { - sbStore.append(separator + "~~~~~~~~~"); - } else { - sbStore.insert(0, "~~~~~~~~~" + separator); + + @Override + public String visualizeStore(S store) { + return store.visualize(this); } - return sbStore.toString(); - } - - /** - * Visualize a special block. - * - * @param sbb the special block - * @return the String representation of the special block - */ - protected String visualizeSpecialBlockHelper(SpecialBlock sbb) { - switch (sbb.getSpecialType()) { - case ENTRY: - return ""; - case EXIT: - return ""; - case EXCEPTIONAL_EXIT: - return ""; - default: - throw new BugInCF("Unrecognized special block type: " + sbb.getType()); + + /** + * Generate the String representation of the nodes of a control flow graph. + * + * @param blocks the set of all the blocks in a control flow graph + * @param cfg the control flow graph + * @param analysis the current analysis + * @return the String representation of the nodes + */ + protected abstract String visualizeNodes( + Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis); + + /** + * Generate the String representation of an edge. + * + * @param sId a representation of the current block, such as its ID + * @param eId a representation of the successor block, such as its ID + * @param flowRule the content of the edge + * @return the String representation of the edge + */ + protected abstract String visualizeEdge(Object sId, Object eId, String flowRule); + + /** + * Return the header of the generated graph. + * + * @return the String representation of the header of the control flow graph + */ + protected abstract String visualizeGraphHeader(); + + /** + * Return the footer of the generated graph. + * + * @return the String representation of the footer of the control flow graph + */ + protected abstract String visualizeGraphFooter(); + + /** + * Given a list of process orders (integers), returns a string representation. + * + *

          Examples: "Process order: 23", "Process order: 23,25". + * + * @param order a list of process orders + * @return a String representation of the given process orders + */ + protected String getProcessOrderSimpleString(List order) { + return "Process order: " + StringsPlume.join(",", order); } - } - - /** - * Generate the order of processing blocks. Because a block may appear more than once in {@link - * ControlFlowGraph#getDepthFirstOrderedBlocks()}, the orders of each block are stored in a - * separate array list. - * - * @param cfg the current control flow graph - * @return an IdentityHashMap that maps from blocks to their orders - */ - protected IdentityHashMap> getProcessOrder(ControlFlowGraph cfg) { - IdentityHashMap> depthFirstOrder = new IdentityHashMap<>(); - int count = 1; - for (Block b : cfg.getDepthFirstOrderedBlocks()) { - depthFirstOrder.computeIfAbsent(b, k -> new ArrayList<>()); - @SuppressWarnings( - "nullness:assignment.type.incompatible") // computeIfAbsent's function doesn't - // return null - @NonNull List blockIds = depthFirstOrder.get(b); - blockIds.add(count++); + + /** + * Get the simple name of a node. + * + * @param t a node + * @return the node's simple name, without "Node" + */ + protected String getNodeSimpleName(Node t) { + String name = t.getClass().getSimpleName(); + return name.replace("Node", ""); } - return depthFirstOrder; - } - - @Override - public String visualizeStore(S store) { - return store.visualize(this); - } - - /** - * Generate the String representation of the nodes of a control flow graph. - * - * @param blocks the set of all the blocks in a control flow graph - * @param cfg the control flow graph - * @param analysis the current analysis - * @return the String representation of the nodes - */ - protected abstract String visualizeNodes( - Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis); - - /** - * Generate the String representation of an edge. - * - * @param sId a representation of the current block, such as its ID - * @param eId a representation of the successor block, such as its ID - * @param flowRule the content of the edge - * @return the String representation of the edge - */ - protected abstract String visualizeEdge(Object sId, Object eId, String flowRule); - - /** - * Return the header of the generated graph. - * - * @return the String representation of the header of the control flow graph - */ - protected abstract String visualizeGraphHeader(); - - /** - * Return the footer of the generated graph. - * - * @return the String representation of the footer of the control flow graph - */ - protected abstract String visualizeGraphFooter(); - - /** - * Given a list of process orders (integers), returns a string representation. - * - *

          Examples: "Process order: 23", "Process order: 23,25". - * - * @param order a list of process orders - * @return a String representation of the given process orders - */ - protected String getProcessOrderSimpleString(List order) { - return "Process order: " + StringsPlume.join(",", order); - } - - /** - * Get the simple name of a node. - * - * @param t a node - * @return the node's simple name, without "Node" - */ - protected String getNodeSimpleName(Node t) { - String name = t.getClass().getSimpleName(); - return name.replace("Node", ""); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java index 0e7a3e01009..300176143d5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java @@ -5,15 +5,7 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Options; -import java.io.FileWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Map; -import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; + import org.checkerframework.checker.mustcall.qual.MustCall; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; @@ -25,6 +17,17 @@ import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.plumelib.util.ArrayMap; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; + +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; + /** * Launcher to generate the DOT or String representation of the control flow graph of a given method * in a given class. @@ -36,322 +39,335 @@ */ public final class CFGVisualizeLauncher { - /** Class cannot be instantiated. */ - private CFGVisualizeLauncher() { - throw new AssertionError("Class CFGVisualizeLauncher cannot be instantiated."); - } + /** Class cannot be instantiated. */ + private CFGVisualizeLauncher() { + throw new AssertionError("Class CFGVisualizeLauncher cannot be instantiated."); + } - /** - * The main entry point of CFGVisualizeLauncher. - * - * @param args command-line arguments - */ - public static void main(String[] args) { - CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); + /** + * The main entry point of CFGVisualizeLauncher. + * + * @param args command-line arguments + */ + public static void main(String[] args) { + CFGVisualizeOptions config = CFGVisualizeOptions.parseArgs(args); - performAnalysis(config, null); - } + performAnalysis(config, null); + } - /** - * Generate a visualization of the CFG of a method, with an optional analysis. - * - * @param the abstract value type of the analysis - * @param the store type of the analysis - * @param the transfer function type of the analysis - * @param config CFGVisualizeOptions that includes input file, output directory, method name, and - * class name - * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is - * to be performed) - */ - public static , S extends Store, T extends TransferFunction> - void performAnalysis(CFGVisualizeOptions config, @Nullable Analysis analysis) { - if (!config.isString()) { - if (analysis == null) { - generateDOTofCFGWithoutAnalysis( - config.getInputFile(), - config.getOutputDirectory(), - config.getMethodName(), - config.getClassName(), - config.isPDF(), - config.isVerbose()); - } else { - generateDOTofCFG( - config.getInputFile(), - config.getOutputDirectory(), - config.getMethodName(), - config.getClassName(), - config.isPDF(), - config.isVerbose(), - analysis); - } - } else { - if (analysis == null) { - String stringGraph = - generateStringOfCFGWithoutAnalysis( - config.getInputFile(), - config.getMethodName(), - config.getClassName(), - config.isVerbose()); - System.out.println(stringGraph); - } else { - Map res = - generateStringOfCFG( - config.getInputFile(), - config.getMethodName(), - config.getClassName(), - config.isVerbose(), - analysis); - if (res != null) { - String stringGraph = (String) res.get("stringGraph"); - if (stringGraph == null) { - System.err.println( - "Unexpected output from generating string control flow graph, shouldn't be" - + " null. Result map: " - + res); - return; - } - System.out.println(stringGraph); + /** + * Generate a visualization of the CFG of a method, with an optional analysis. + * + * @param the abstract value type of the analysis + * @param the store type of the analysis + * @param the transfer function type of the analysis + * @param config CFGVisualizeOptions that includes input file, output directory, method name, + * and class name + * @param analysis analysis to perform before the visualization (or {@code null} if no analysis + * is to be performed) + */ + public static , S extends Store, T extends TransferFunction> + void performAnalysis(CFGVisualizeOptions config, @Nullable Analysis analysis) { + if (!config.isString()) { + if (analysis == null) { + generateDOTofCFGWithoutAnalysis( + config.getInputFile(), + config.getOutputDirectory(), + config.getMethodName(), + config.getClassName(), + config.isPDF(), + config.isVerbose()); + } else { + generateDOTofCFG( + config.getInputFile(), + config.getOutputDirectory(), + config.getMethodName(), + config.getClassName(), + config.isPDF(), + config.isVerbose(), + analysis); + } } else { - System.err.println( - "Unexpected output from generating string control flow graph, shouldn't be" - + " null."); + if (analysis == null) { + String stringGraph = + generateStringOfCFGWithoutAnalysis( + config.getInputFile(), + config.getMethodName(), + config.getClassName(), + config.isVerbose()); + System.out.println(stringGraph); + } else { + Map res = + generateStringOfCFG( + config.getInputFile(), + config.getMethodName(), + config.getClassName(), + config.isVerbose(), + analysis); + if (res != null) { + String stringGraph = (String) res.get("stringGraph"); + if (stringGraph == null) { + System.err.println( + "Unexpected output from generating string control flow graph, shouldn't be" + + " null. Result map: " + + res); + return; + } + System.out.println(stringGraph); + } else { + System.err.println( + "Unexpected output from generating string control flow graph, shouldn't be" + + " null."); + } + } } - } } - } - - /** - * Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis. - * - * @param inputFile a Java source file, used as input - * @param outputDir output directory - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param pdf also generate a PDF - * @param verbose show verbose information in CFG - */ - private static void generateDOTofCFGWithoutAnalysis( - String inputFile, - String outputDir, - String method, - String clas, - boolean pdf, - boolean verbose) { - generateDOTofCFG(inputFile, outputDir, method, clas, pdf, verbose, null); - } - /** - * Generate the String representation of the CFG for a method, only. Does no dataflow analysis. - * - * @param inputFile a Java source file, used as input - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param verbose show verbose information in CFG - * @return the String representation of the CFG - */ - private static String generateStringOfCFGWithoutAnalysis( - String inputFile, String method, String clas, boolean verbose) { - Map res = generateStringOfCFG(inputFile, method, clas, verbose, null); - if (res != null) { - String stringGraph = (String) res.get("stringGraph"); - if (stringGraph == null) { - return "Unexpected output from generating string control flow graph, shouldn't be" - + " null."; - } - return stringGraph; - } else { - return "Unexpected output from generating string control flow graph, shouldn't be" + " null."; + /** + * Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis. + * + * @param inputFile a Java source file, used as input + * @param outputDir output directory + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param pdf also generate a PDF + * @param verbose show verbose information in CFG + */ + private static void generateDOTofCFGWithoutAnalysis( + String inputFile, + String outputDir, + String method, + String clas, + boolean pdf, + boolean verbose) { + generateDOTofCFG(inputFile, outputDir, method, clas, pdf, verbose, null); } - } - /** - * Generate the DOT representation of the CFG for a method. - * - * @param the abstract value type to be tracked by the analysis - * @param the store type used in the analysis - * @param the transfer function type that is used to approximated runtime behavior - * @param inputFile a Java source file, used as input - * @param outputDir source output directory - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param pdf also generate a PDF - * @param verbose show verbose information in CFG - * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is - * to be performed) - */ - private static , S extends Store, T extends TransferFunction> - void generateDOTofCFG( - String inputFile, - String outputDir, - String method, - String clas, - boolean pdf, - boolean verbose, - @Nullable Analysis analysis) { - ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); - if (analysis != null) { - analysis.performAnalysis(cfg); + /** + * Generate the String representation of the CFG for a method, only. Does no dataflow analysis. + * + * @param inputFile a Java source file, used as input + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param verbose show verbose information in CFG + * @return the String representation of the CFG + */ + private static String generateStringOfCFGWithoutAnalysis( + String inputFile, String method, String clas, boolean verbose) { + Map res = generateStringOfCFG(inputFile, method, clas, verbose, null); + if (res != null) { + String stringGraph = (String) res.get("stringGraph"); + if (stringGraph == null) { + return "Unexpected output from generating string control flow graph, shouldn't be" + + " null."; + } + return stringGraph; + } else { + return "Unexpected output from generating string control flow graph, shouldn't be" + + " null."; + } } - Map args = new ArrayMap<>(2); - args.put("outdir", outputDir); - args.put("verbose", verbose); + /** + * Generate the DOT representation of the CFG for a method. + * + * @param the abstract value type to be tracked by the analysis + * @param the store type used in the analysis + * @param the transfer function type that is used to approximated runtime behavior + * @param inputFile a Java source file, used as input + * @param outputDir source output directory + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param pdf also generate a PDF + * @param verbose show verbose information in CFG + * @param analysis analysis to perform before the visualization (or {@code null} if no analysis + * is to be performed) + */ + private static < + V extends AbstractValue, + S extends Store, + T extends TransferFunction> + void generateDOTofCFG( + String inputFile, + String outputDir, + String method, + String clas, + boolean pdf, + boolean verbose, + @Nullable Analysis analysis) { + ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); + if (analysis != null) { + analysis.performAnalysis(cfg); + } + + Map args = new ArrayMap<>(2); + args.put("outdir", outputDir); + args.put("verbose", verbose); - CFGVisualizer viz = new DOTCFGVisualizer<>(); - viz.init(args); - Map res = viz.visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); - viz.shutdown(); + CFGVisualizer viz = new DOTCFGVisualizer<>(); + viz.init(args); + Map res = viz.visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); + viz.shutdown(); - if (pdf && res != null) { - assert res.get("dotFileName") != null : "@AssumeAssertion(nullness): specification"; - producePDF((String) res.get("dotFileName")); + if (pdf && res != null) { + assert res.get("dotFileName") != null : "@AssumeAssertion(nullness): specification"; + producePDF((String) res.get("dotFileName")); + } } - } - - /** - * Generate the control flow graph of a method in a class. - * - * @param file a Java source file, used as input - * @param clas name of the class which includes the method to generate the CFG for - * @param method name of the method to generate the CFG for - * @return control flow graph of the specified method - */ - public static ControlFlowGraph generateMethodCFG(String file, String clas, String method) { - CFGProcessor cfgProcessor = new CFGProcessor(clas, method); - Context context = new Context(); - Options.instance(context).put("compilePolicy", "ATTR_ONLY"); - JavaCompiler javac = new JavaCompiler(context); + /** + * Generate the control flow graph of a method in a class. + * + * @param file a Java source file, used as input + * @param clas name of the class which includes the method to generate the CFG for + * @param method name of the method to generate the CFG for + * @return control flow graph of the specified method + */ + public static ControlFlowGraph generateMethodCFG(String file, String clas, String method) { + CFGProcessor cfgProcessor = new CFGProcessor(clas, method); - JavaFileObject l; - try (@SuppressWarnings("mustcall:type.arguments.not.inferred" // Context isn't annotated for - // the Must Call Checker. - ) - JavacFileManager fileManager = (JavacFileManager) context.get(JavaFileManager.class)) { - l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next(); - } catch (IOException e) { - throw new Error(e); - } + Context context = new Context(); + Options.instance(context).put("compilePolicy", "ATTR_ONLY"); + JavaCompiler javac = new JavaCompiler(context); - PrintStream err = System.err; - try { - // Redirect syserr to nothing (and prevent the compiler from issuing - // warnings about our exception). - @SuppressWarnings({ - "builder:required.method.not.called", - "mustcall:assignment" - }) // Won't be needed in JDK 11+ with use of "OutputStream.nullOutputStream()". - @MustCall() OutputStream nullOS = - // In JDK 11+, this can be just "OutputStream.nullOutputStream()". - new OutputStream() { - @Override - public void write(int b) throws IOException {} - }; - System.setErr(new PrintStream(nullOS)); - javac.compile(List.of(l), List.of(clas), List.of(cfgProcessor), List.nil()); - } catch (Throwable e) { - // ok - } finally { - System.setErr(err); - } + JavaFileObject l; + try (@SuppressWarnings("mustcall:type.arguments.not.inferred" // Context isn't annotated for + // the Must Call Checker. + ) + JavacFileManager fileManager = + (JavacFileManager) context.get(JavaFileManager.class)) { + l = fileManager.getJavaFileObjectsFromStrings(List.of(file)).iterator().next(); + } catch (IOException e) { + throw new Error(e); + } - CFGProcessResult res = cfgProcessor.getCFGProcessResult(); + PrintStream err = System.err; + try { + // Redirect syserr to nothing (and prevent the compiler from issuing + // warnings about our exception). + @SuppressWarnings({ + "builder:required.method.not.called", + "mustcall:assignment" + }) // Won't be needed in JDK 11+ with use of "OutputStream.nullOutputStream()". + @MustCall() OutputStream nullOS = + // In JDK 11+, this can be just "OutputStream.nullOutputStream()". + new OutputStream() { + @Override + public void write(int b) throws IOException {} + }; + System.setErr(new PrintStream(nullOS)); + javac.compile(List.of(l), List.of(clas), List.of(cfgProcessor), List.nil()); + } catch (Throwable e) { + // ok + } finally { + System.setErr(err); + } - if (res == null) { - printError( - "internal error in type processor! method typeProcessOver() doesn't get" + " called."); - System.exit(1); - } + CFGProcessResult res = cfgProcessor.getCFGProcessResult(); - if (!res.isSuccess()) { - printError(res.getErrMsg()); - System.exit(1); - } + if (res == null) { + printError( + "internal error in type processor! method typeProcessOver() doesn't get" + + " called."); + System.exit(1); + } - return res.getCFG(); - } + if (!res.isSuccess()) { + printError(res.getErrMsg()); + System.exit(1); + } - /** - * Write generated String representation of the CFG for a method to a file. - * - * @param inputFile a Java source file, used as input - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param outputFile source output file - * @param analysis instance of forward or backward analysis from specific dataflow test case - */ - @SuppressWarnings("CatchAndPrintStackTrace") // we want to use e.printStackTrace here. - public static void writeStringOfCFG( - String inputFile, String method, String clas, String outputFile, Analysis analysis) { - Map res = generateStringOfCFG(inputFile, method, clas, true, analysis); - try (FileWriter out = new FileWriter(outputFile, StandardCharsets.UTF_8)) { - if (res != null && res.get("stringGraph") != null) { - out.write(res.get("stringGraph").toString()); - } - out.write(System.lineSeparator()); - } catch (IOException e) { - e.printStackTrace(); + return res.getCFG(); } - } - /** - * Invoke "dot" command to generate a PDF. - * - * @param file name of the dot file - */ - private static void producePDF(String file) { - try { - String command = "dot -Tpdf \"" + file + "\" -o \"" + file + ".pdf\""; - Process child = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command}); - child.waitFor(); - } catch (InterruptedException | IOException e) { - e.printStackTrace(); - System.exit(1); + /** + * Write generated String representation of the CFG for a method to a file. + * + * @param inputFile a Java source file, used as input + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param outputFile source output file + * @param analysis instance of forward or backward analysis from specific dataflow test case + */ + @SuppressWarnings("CatchAndPrintStackTrace") // we want to use e.printStackTrace here. + public static void writeStringOfCFG( + String inputFile, + String method, + String clas, + String outputFile, + Analysis analysis) { + Map res = generateStringOfCFG(inputFile, method, clas, true, analysis); + try (FileWriter out = new FileWriter(outputFile, StandardCharsets.UTF_8)) { + if (res != null && res.get("stringGraph") != null) { + out.write(res.get("stringGraph").toString()); + } + out.write(System.lineSeparator()); + } catch (IOException e) { + e.printStackTrace(); + } } - } - /** - * Generate the String representation of the CFG for a method. - * - * @param the abstract value type to be tracked by the analysis - * @param the store type used in the analysis - * @param the transfer function type that is used to approximated runtime behavior - * @param inputFile a Java source file, used as input - * @param method name of the method to generate the CFG for - * @param clas name of the class which includes the method to generate the CFG for - * @param verbose show verbose information in CFG - * @param analysis analysis to perform before the visualization (or {@code null} if no analysis is - * to be performed) - * @return a map which includes a key "stringGraph" and the String representation of CFG as the - * value - */ - private static , S extends Store, T extends TransferFunction> - @Nullable Map generateStringOfCFG( - String inputFile, - String method, - String clas, - boolean verbose, - @Nullable Analysis analysis) { - ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); - if (analysis != null) { - analysis.performAnalysis(cfg); + /** + * Invoke "dot" command to generate a PDF. + * + * @param file name of the dot file + */ + private static void producePDF(String file) { + try { + String command = "dot -Tpdf \"" + file + "\" -o \"" + file + ".pdf\""; + Process child = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", command}); + child.waitFor(); + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + System.exit(1); + } } - Map args = Collections.singletonMap("verbose", verbose); + /** + * Generate the String representation of the CFG for a method. + * + * @param the abstract value type to be tracked by the analysis + * @param the store type used in the analysis + * @param the transfer function type that is used to approximated runtime behavior + * @param inputFile a Java source file, used as input + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param verbose show verbose information in CFG + * @param analysis analysis to perform before the visualization (or {@code null} if no analysis + * is to be performed) + * @return a map which includes a key "stringGraph" and the String representation of CFG as the + * value + */ + private static < + V extends AbstractValue, + S extends Store, + T extends TransferFunction> + @Nullable Map generateStringOfCFG( + String inputFile, + String method, + String clas, + boolean verbose, + @Nullable Analysis analysis) { + ControlFlowGraph cfg = generateMethodCFG(inputFile, clas, method); + if (analysis != null) { + analysis.performAnalysis(cfg); + } - CFGVisualizer viz = new StringCFGVisualizer<>(); - viz.init(args); - Map res = viz.visualize(cfg, cfg.getEntryBlock(), analysis); - viz.shutdown(); - return res; - } + Map args = Collections.singletonMap("verbose", verbose); - /** - * Print error message. - * - * @param string error message - */ - private static void printError(@Nullable String string) { - System.err.println("ERROR: " + string); - } + CFGVisualizer viz = new StringCFGVisualizer<>(); + viz.init(args); + Map res = viz.visualize(cfg, cfg.getEntryBlock(), analysis); + viz.shutdown(); + return res; + } + + /** + * Print error message. + * + * @param string error message + */ + private static void printError(@Nullable String string) { + System.err.println("ERROR: " + string); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java index 1a563a31aee..39ba302c5ad 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java @@ -1,8 +1,9 @@ package org.checkerframework.dataflow.cfg.visualize; -import java.io.File; import org.checkerframework.checker.nullness.qual.Nullable; +import java.io.File; + /** * Options for running analysis on files. * @@ -13,241 +14,242 @@ */ public class CFGVisualizeOptions { - /** Default method name. */ - private static final String DEFAULT_METHOD = "test"; - - /** Default class name. */ - private static final String DEFAULT_CLASS = "Test"; - - /** Default output directory. */ - private static final String DEFAULT_OUTPUT_DIR = "."; - - /** The input file. */ - private final String input; - - /** The output directory. */ - private final String output; - - /** The method name. */ - private final String method; - - /** The class name. */ - private final String clas; - - /** True if the PDF should be generated. */ - private final boolean pdf; - - /** True if the verbose output should be generated. */ - private final boolean verbose; - - /** True if the string representation should be generated. */ - private final boolean string; - - /** - * Private constructor. - * - *

          This constructor is private to ensure that the object is only created by calling {@link - * #parseArgs(String[])}. - * - * @param input the input file - * @param output the output directory - * @param method the method name - * @param clas the class name - * @param pdf true if the PDF should be generated - * @param verbose true if the verbose output should be generated - * @param string true if the string representation should be generated - */ - private CFGVisualizeOptions( - String input, - String output, - String method, - String clas, - boolean pdf, - boolean verbose, - boolean string) { - this.input = input; - this.output = output; - this.method = method; - this.clas = clas; - this.pdf = pdf; - this.verbose = verbose; - this.string = string; - } - - /** - * Parse the command line arguments. - * - *

          This method calls System.exit(1) if there are no arguments or if the input file cannot be - * read. - * - * @param args command-line arguments, see {@link #printUsage()} - * @return CFGVisualizeOptions object containing the parsed options - */ - public static CFGVisualizeOptions parseArgs(String[] args) { - if (args.length == 0) { - printUsage(); - System.exit(1); + /** Default method name. */ + private static final String DEFAULT_METHOD = "test"; + + /** Default class name. */ + private static final String DEFAULT_CLASS = "Test"; + + /** Default output directory. */ + private static final String DEFAULT_OUTPUT_DIR = "."; + + /** The input file. */ + private final String input; + + /** The output directory. */ + private final String output; + + /** The method name. */ + private final String method; + + /** The class name. */ + private final String clas; + + /** True if the PDF should be generated. */ + private final boolean pdf; + + /** True if the verbose output should be generated. */ + private final boolean verbose; + + /** True if the string representation should be generated. */ + private final boolean string; + + /** + * Private constructor. + * + *

          This constructor is private to ensure that the object is only created by calling {@link + * #parseArgs(String[])}. + * + * @param input the input file + * @param output the output directory + * @param method the method name + * @param clas the class name + * @param pdf true if the PDF should be generated + * @param verbose true if the verbose output should be generated + * @param string true if the string representation should be generated + */ + private CFGVisualizeOptions( + String input, + String output, + String method, + String clas, + boolean pdf, + boolean verbose, + boolean string) { + this.input = input; + this.output = output; + this.method = method; + this.clas = clas; + this.pdf = pdf; + this.verbose = verbose; + this.string = string; } - String input = args[0]; - File file = new File(input); - if (!file.canRead()) { - printError("Cannot read input file: " + file.getAbsolutePath()); - printUsage(); - System.exit(1); + + /** + * Parse the command line arguments. + * + *

          This method calls System.exit(1) if there are no arguments or if the input file cannot be + * read. + * + * @param args command-line arguments, see {@link #printUsage()} + * @return CFGVisualizeOptions object containing the parsed options + */ + public static CFGVisualizeOptions parseArgs(String[] args) { + if (args.length == 0) { + printUsage(); + System.exit(1); + } + String input = args[0]; + File file = new File(input); + if (!file.canRead()) { + printError("Cannot read input file: " + file.getAbsolutePath()); + printUsage(); + System.exit(1); + } + + String method = DEFAULT_METHOD; + String clas = DEFAULT_CLASS; + String output = DEFAULT_OUTPUT_DIR; + boolean pdf = false; + boolean error = false; + boolean verbose = false; + boolean string = false; + + for (int i = 1; i < args.length; i++) { + switch (args[i]) { + case "--outputdir": + if (i >= args.length - 1) { + printError("Did not find after --outputdir."); + continue; + } + i++; + output = args[i]; + break; + case "--pdf": + pdf = true; + break; + case "--method": + if (i >= args.length - 1) { + printError("Did not find after --method."); + continue; + } + i++; + method = args[i]; + break; + case "--class": + if (i >= args.length - 1) { + printError("Did not find after --class."); + continue; + } + i++; + clas = args[i]; + break; + case "--verbose": + verbose = true; + break; + case "--string": + string = true; + break; + default: + printError("Unknown command line argument: " + args[i]); + error = true; + break; + } + } + + if (error) { + System.exit(1); + } + + return new CFGVisualizeOptions(input, output, method, clas, pdf, verbose, string); } - String method = DEFAULT_METHOD; - String clas = DEFAULT_CLASS; - String output = DEFAULT_OUTPUT_DIR; - boolean pdf = false; - boolean error = false; - boolean verbose = false; - boolean string = false; - - for (int i = 1; i < args.length; i++) { - switch (args[i]) { - case "--outputdir": - if (i >= args.length - 1) { - printError("Did not find after --outputdir."); - continue; - } - i++; - output = args[i]; - break; - case "--pdf": - pdf = true; - break; - case "--method": - if (i >= args.length - 1) { - printError("Did not find after --method."); - continue; - } - i++; - method = args[i]; - break; - case "--class": - if (i >= args.length - 1) { - printError("Did not find after --class."); - continue; - } - i++; - clas = args[i]; - break; - case "--verbose": - verbose = true; - break; - case "--string": - string = true; - break; - default: - printError("Unknown command line argument: " + args[i]); - error = true; - break; - } + /** + * Getter for the input file. + * + * @return the input file + */ + public String getInputFile() { + return input; } - if (error) { - System.exit(1); + /** + * Getter for the output directory. + * + * @return the output directory + */ + public String getOutputDirectory() { + return output; } - return new CFGVisualizeOptions(input, output, method, clas, pdf, verbose, string); - } - - /** - * Getter for the input file. - * - * @return the input file - */ - public String getInputFile() { - return input; - } - - /** - * Getter for the output directory. - * - * @return the output directory - */ - public String getOutputDirectory() { - return output; - } - - /** - * Getter for the method name. - * - * @return the method name - */ - public String getMethodName() { - return method; - } - - /** - * Getter for the class name. - * - * @return the class name - */ - public String getClassName() { - return clas; - } - - /** - * Getter for the PDF flag. - * - * @return true if the PDF should be generated - */ - public boolean isPDF() { - return pdf; - } - - /** - * Getter for the verbose flag. - * - * @return true if the verbose output should be generated - */ - public boolean isVerbose() { - return verbose; - } - - /** - * Getter for the string flag. - * - * @return true if the string representation should be generated - */ - public boolean isString() { - return string; - } - - /** - * Print usage information. - * - *

          Sends the usage information to System.out. - */ - private static void printUsage() { - System.out.println( - "Generate the control flow graph of a Java method, represented as a DOT or String" - + " graph."); - System.out.println( - "Parameters: [--outputdir ] [--method ] [--class" - + " ] [--pdf] [--verbose] [--string]"); - System.out.println( - " --outputdir: The output directory for the generated files (defaults to '.')."); - System.out.println(" --method: The method to generate the CFG for (defaults to 'test')."); - System.out.println( - " --class: The class in which to find the method (defaults to 'Test')."); - System.out.println(" --pdf: Also generate the PDF by invoking 'dot'."); - System.out.println(" --verbose: Show the verbose output (defaults to 'false')."); - System.out.println( - " --string: Print the string representation of the control flow graph" - + " (defaults to 'false')."); - } - - /** - * Print error message. - * - *

          Sends the error message to System.err. - * - * @param string error message - */ - private static void printError(@Nullable String string) { - System.err.println("ERROR: " + string); - } + /** + * Getter for the method name. + * + * @return the method name + */ + public String getMethodName() { + return method; + } + + /** + * Getter for the class name. + * + * @return the class name + */ + public String getClassName() { + return clas; + } + + /** + * Getter for the PDF flag. + * + * @return true if the PDF should be generated + */ + public boolean isPDF() { + return pdf; + } + + /** + * Getter for the verbose flag. + * + * @return true if the verbose output should be generated + */ + public boolean isVerbose() { + return verbose; + } + + /** + * Getter for the string flag. + * + * @return true if the string representation should be generated + */ + public boolean isString() { + return string; + } + + /** + * Print usage information. + * + *

          Sends the usage information to System.out. + */ + private static void printUsage() { + System.out.println( + "Generate the control flow graph of a Java method, represented as a DOT or String" + + " graph."); + System.out.println( + "Parameters: [--outputdir ] [--method ] [--class" + + " ] [--pdf] [--verbose] [--string]"); + System.out.println( + " --outputdir: The output directory for the generated files (defaults to '.')."); + System.out.println( + " --method: The method to generate the CFG for (defaults to 'test')."); + System.out.println( + " --class: The class in which to find the method (defaults to 'Test')."); + System.out.println(" --pdf: Also generate the PDF by invoking 'dot'."); + System.out.println(" --verbose: Show the verbose output (defaults to 'false')."); + System.out.println( + " --string: Print the string representation of the control flow graph" + + " (defaults to 'false')."); + } + + /** + * Print error message. + * + *

          Sends the error message to System.err. + * + * @param string error message + */ + private static void printError(@Nullable String string) { + System.err.println("ERROR: " + string); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java index 8ca4378715b..d23a6eafbb9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizer.java @@ -1,6 +1,5 @@ package org.checkerframework.dataflow.cfg.visualize; -import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; import org.checkerframework.dataflow.analysis.Analysis; @@ -17,6 +16,8 @@ import org.checkerframework.dataflow.expression.LocalVariable; import org.checkerframework.dataflow.expression.MethodCall; +import java.util.Map; + /** * Perform some visualization on a control flow graph. The particular operations depend on the * implementation. @@ -26,196 +27,196 @@ * @param the transfer function type that is used to approximate runtime behavior */ public interface CFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> { - /** - * Initialization method guaranteed to be called once before the first invocation of {@link - * #visualize} or {@link #visualizeWithAction}. - * - * @param args implementation-dependent options - */ - void init(Map args); - - /** - * Returns the separator for lines within a node's representation. - * - * @return the separator for lines within a node's representation - */ - public abstract String getSeparator(); - - /** - * Creates a visualization representing the control flow graph starting at {@code entry}. The keys - * and values in the returned map are implementation dependent. The method should not perform any - * actions. - * - *

          An invocation {@code visualize(cfg, entry, null);} does not output stores at the beginning - * of basic blocks. - * - * @param cfg the CFG to visualize - * @param entry the entry node of the control flow graph to be represented - * @param analysis an analysis containing information about the program represented by the CFG. - * The information includes {@link Store}s that are valid at the beginning of basic blocks - * reachable from {@code entry} and per-node information for value producing {@link Node}s. - * Can also be {@code null} to indicate that this information should not be output. - * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a String - * representation of the CFG ({@link StringCFGVisualizer}) - * @see #visualizeWithAction(ControlFlowGraph, Block, Analysis) - */ - @Nullable Map visualize( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); - - /** - * Output a visualization representing the control flow graph starting at {@code entry}. The keys - * and values in the returned map are implementation dependent. The concrete actions are - * implementation dependent, and can include outputting information and producing files. - * - *

          An invocation {@code visualizeWithAction(cfg, entry, null);} does not output stores at the - * beginning of basic blocks. - * - * @param cfg the CFG to visualize - * @param entry the entry node of the control flow graph to be represented - * @param analysis an analysis containing information about the program represented by the CFG. - * The information includes {@link Store}s that are valid at the beginning of basic blocks - * reachable from {@code entry} and per-node information for value producing {@link Node}s. - * Can also be {@code null} to indicate that this information should not be output. - * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a String - * representation of the CFG ({@link StringCFGVisualizer}) - * @see #visualize(ControlFlowGraph, Block, Analysis) - */ - @Nullable Map visualizeWithAction( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); - - /** - * Delegate the visualization responsibility to the passed {@link Store} instance, which will call - * back to this visualizer instance for sub-components. - * - * @param store the store to visualize - * @return the String representation of the given store - */ - String visualizeStore(S store); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize a local variable. - * - * @param localVar the local variable - * @param value the value of the local variable - * @return the String representation of the local variable - */ - String visualizeStoreLocalVar(LocalVariable localVar, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of the current - * object {@code this} in this Store. - * - * @param value the value of the current object {@code this} - * @return the String representation of {@code this} - */ - String visualizeStoreThisVal(V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one field - * collected by this Store. - * - * @param fieldAccess the field - * @param value the value of the field - * @return the String representation of the field - */ - String visualizeStoreFieldVal(FieldAccess fieldAccess, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one array - * collected by this Store. - * - * @param arrayValue the array - * @param value the value of the array - * @return the String representation of the array - */ - String visualizeStoreArrayVal(ArrayAccess arrayValue, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of pure method - * calls collected by this Store. - * - * @param methodCall the pure method call - * @param value the value of the pure method call - * @return the String representation of the pure method call - */ - String visualizeStoreMethodVals(MethodCall methodCall, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of class names - * collected by this Store. - * - * @param className the class name - * @param value the value of the class name - * @return the String representation of the class name - */ - String visualizeStoreClassVals(ClassName className, V value); - - /** - * Called by {@code CFAbstractStore#internalVisualize()} to visualize the specific information - * collected according to the specific kind of Store. Currently, these Stores call this method: - * {@code LockStore}, {@code NullnessStore}, and {@code InitializationStore} to visualize - * additional information. - * - * @param keyName the name of the specific information to be visualized - * @param value the value of the specific information to be visualized - * @return the String representation of the specific information - */ - String visualizeStoreKeyVal(String keyName, Object value); - - /** - * Visualize a block based on the analysis. - * - * @param bb the block - * @param analysis the current analysis - * @return the String representation of the given block - */ - String visualizeBlock(Block bb, @Nullable Analysis analysis); - - /** - * Visualize a SpecialBlock. - * - * @param sbb the special block - * @return the String representation of the type of the special block {@code sbb}: entry, exit, or - * exceptional-exit - */ - String visualizeSpecialBlock(SpecialBlock sbb); - - /** - * Visualize a ConditionalBlock. - * - * @param cbb the conditional block - * @return the String representation of the conditional block - */ - String visualizeConditionalBlock(ConditionalBlock cbb); - - /** - * Visualize the transferInput before a Block based on the analysis. - * - * @param bb the block - * @param analysis the current analysis - * @return the String representation of the transferInput before the given block - */ - String visualizeBlockTransferInputBefore(Block bb, Analysis analysis); - - /** - * Visualize the transferInput after a Block based on the analysis. - * - * @param bb the block - * @param analysis the current analysis - * @return the String representation of the transferInput after the given block - */ - String visualizeBlockTransferInputAfter(Block bb, Analysis analysis); - - /** - * Visualize a Node based on the analysis. - * - * @param t the node - * @param analysis the current analysis - * @return the String representation of the given node - */ - String visualizeBlockNode(Node t, @Nullable Analysis analysis); - - /** Shutdown method called once from the shutdown hook of the {@code BaseTypeChecker}. */ - void shutdown(); + V extends AbstractValue, S extends Store, T extends TransferFunction> { + /** + * Initialization method guaranteed to be called once before the first invocation of {@link + * #visualize} or {@link #visualizeWithAction}. + * + * @param args implementation-dependent options + */ + void init(Map args); + + /** + * Returns the separator for lines within a node's representation. + * + * @return the separator for lines within a node's representation + */ + public abstract String getSeparator(); + + /** + * Creates a visualization representing the control flow graph starting at {@code entry}. The + * keys and values in the returned map are implementation dependent. The method should not + * perform any actions. + * + *

          An invocation {@code visualize(cfg, entry, null);} does not output stores at the beginning + * of basic blocks. + * + * @param cfg the CFG to visualize + * @param entry the entry node of the control flow graph to be represented + * @param analysis an analysis containing information about the program represented by the CFG. + * The information includes {@link Store}s that are valid at the beginning of basic blocks + * reachable from {@code entry} and per-node information for value producing {@link Node}s. + * Can also be {@code null} to indicate that this information should not be output. + * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a + * String representation of the CFG ({@link StringCFGVisualizer}) + * @see #visualizeWithAction(ControlFlowGraph, Block, Analysis) + */ + @Nullable Map visualize( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); + + /** + * Output a visualization representing the control flow graph starting at {@code entry}. The + * keys and values in the returned map are implementation dependent. The concrete actions are + * implementation dependent, and can include outputting information and producing files. + * + *

          An invocation {@code visualizeWithAction(cfg, entry, null);} does not output stores at the + * beginning of basic blocks. + * + * @param cfg the CFG to visualize + * @param entry the entry node of the control flow graph to be represented + * @param analysis an analysis containing information about the program represented by the CFG. + * The information includes {@link Store}s that are valid at the beginning of basic blocks + * reachable from {@code entry} and per-node information for value producing {@link Node}s. + * Can also be {@code null} to indicate that this information should not be output. + * @return visualization results, e.g. generated file names ({@link DOTCFGVisualizer}) or a + * String representation of the CFG ({@link StringCFGVisualizer}) + * @see #visualize(ControlFlowGraph, Block, Analysis) + */ + @Nullable Map visualizeWithAction( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis); + + /** + * Delegate the visualization responsibility to the passed {@link Store} instance, which will + * call back to this visualizer instance for sub-components. + * + * @param store the store to visualize + * @return the String representation of the given store + */ + String visualizeStore(S store); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize a local variable. + * + * @param localVar the local variable + * @param value the value of the local variable + * @return the String representation of the local variable + */ + String visualizeStoreLocalVar(LocalVariable localVar, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of the current + * object {@code this} in this Store. + * + * @param value the value of the current object {@code this} + * @return the String representation of {@code this} + */ + String visualizeStoreThisVal(V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one field + * collected by this Store. + * + * @param fieldAccess the field + * @param value the value of the field + * @return the String representation of the field + */ + String visualizeStoreFieldVal(FieldAccess fieldAccess, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of one array + * collected by this Store. + * + * @param arrayValue the array + * @param value the value of the array + * @return the String representation of the array + */ + String visualizeStoreArrayVal(ArrayAccess arrayValue, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of pure method + * calls collected by this Store. + * + * @param methodCall the pure method call + * @param value the value of the pure method call + * @return the String representation of the pure method call + */ + String visualizeStoreMethodVals(MethodCall methodCall, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the value of class names + * collected by this Store. + * + * @param className the class name + * @param value the value of the class name + * @return the String representation of the class name + */ + String visualizeStoreClassVals(ClassName className, V value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize the specific information + * collected according to the specific kind of Store. Currently, these Stores call this method: + * {@code LockStore}, {@code NullnessStore}, and {@code InitializationStore} to visualize + * additional information. + * + * @param keyName the name of the specific information to be visualized + * @param value the value of the specific information to be visualized + * @return the String representation of the specific information + */ + String visualizeStoreKeyVal(String keyName, Object value); + + /** + * Visualize a block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + * @return the String representation of the given block + */ + String visualizeBlock(Block bb, @Nullable Analysis analysis); + + /** + * Visualize a SpecialBlock. + * + * @param sbb the special block + * @return the String representation of the type of the special block {@code sbb}: entry, exit, + * or exceptional-exit + */ + String visualizeSpecialBlock(SpecialBlock sbb); + + /** + * Visualize a ConditionalBlock. + * + * @param cbb the conditional block + * @return the String representation of the conditional block + */ + String visualizeConditionalBlock(ConditionalBlock cbb); + + /** + * Visualize the transferInput before a Block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + * @return the String representation of the transferInput before the given block + */ + String visualizeBlockTransferInputBefore(Block bb, Analysis analysis); + + /** + * Visualize the transferInput after a Block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + * @return the String representation of the transferInput after the given block + */ + String visualizeBlockTransferInputAfter(Block bb, Analysis analysis); + + /** + * Visualize a Node based on the analysis. + * + * @param t the node + * @param analysis the current analysis + * @return the String representation of the given node + */ + String visualizeBlockNode(Node t, @Nullable Analysis analysis); + + /** Shutdown method called once from the shutdown hook of the {@code BaseTypeChecker}. */ + void shutdown(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java index f23b5114d8a..3a7e5da4a84 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java @@ -3,20 +3,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.tree.JCTree; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; + import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -43,337 +30,356 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.UserError; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + /** Generate a graph description in the DOT language of a control graph. */ public class DOTCFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> - extends AbstractCFGVisualizer { - - /** The output directory. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method - protected String outDir; - - /** The (optional) checker name. Used as a part of the name of the output dot file. */ - protected @Nullable String checkerName; - - /** Mapping from class/method representation to generated dot file. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method - protected Map generated; - - /** Terminator for lines that are left-justified. */ - protected static final String leftJustifiedTerminator = "\\l"; - - @Override - @SuppressWarnings("nullness") // assume arguments are set correctly - public void init(Map args) { - super.init(args); - this.outDir = (String) args.get("outdir"); - if (this.outDir == null) { - throw new BugInCF( - "outDir should never be null," - + " provide it in args when calling DOTCFGVisualizer.init(args)."); + V extends AbstractValue, S extends Store, T extends TransferFunction> + extends AbstractCFGVisualizer { + + /** The output directory. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method + protected String outDir; + + /** The (optional) checker name. Used as a part of the name of the output dot file. */ + protected @Nullable String checkerName; + + /** Mapping from class/method representation to generated dot file. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // uses init method + protected Map generated; + + /** Terminator for lines that are left-justified. */ + protected static final String leftJustifiedTerminator = "\\l"; + + @Override + @SuppressWarnings("nullness") // assume arguments are set correctly + public void init(Map args) { + super.init(args); + this.outDir = (String) args.get("outdir"); + if (this.outDir == null) { + throw new BugInCF( + "outDir should never be null," + + " provide it in args when calling DOTCFGVisualizer.init(args)."); + } + this.checkerName = (String) args.get("checkerName"); + this.generated = new HashMap<>(); + } + + @Override + public String getSeparator() { + return leftJustifiedTerminator; } - this.checkerName = (String) args.get("checkerName"); - this.generated = new HashMap<>(); - } - - @Override - public String getSeparator() { - return leftJustifiedTerminator; - } - - @Override - public Map visualize( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - String dotGraph = visualizeGraph(cfg, entry, analysis); - - Map vis = new HashMap<>(2); - vis.put("dotGraph", dotGraph); - return vis; - } - - @Override - public Map visualizeWithAction( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - Map vis = visualize(cfg, entry, analysis); - String dotGraph = (String) vis.get("dotGraph"); - if (dotGraph == null) { - throw new BugInCF("dotGraph key missing in visualize result!"); + + @Override + public Map visualize( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + String dotGraph = visualizeGraph(cfg, entry, analysis); + + Map vis = new HashMap<>(2); + vis.put("dotGraph", dotGraph); + return vis; } - String dotFileName = dotOutputFileName(cfg.underlyingAST); - try (BufferedWriter out = - new BufferedWriter(new FileWriter(dotFileName, StandardCharsets.UTF_8))) { - out.write(dotGraph); - } catch (IOException e) { - throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e); + @Override + public Map visualizeWithAction( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + Map vis = visualize(cfg, entry, analysis); + String dotGraph = (String) vis.get("dotGraph"); + if (dotGraph == null) { + throw new BugInCF("dotGraph key missing in visualize result!"); + } + String dotFileName = dotOutputFileName(cfg.underlyingAST); + + try (BufferedWriter out = + new BufferedWriter(new FileWriter(dotFileName, StandardCharsets.UTF_8))) { + out.write(dotGraph); + } catch (IOException e) { + throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e); + } + vis.put("dotFileName", dotFileName); + return vis; } - vis.put("dotFileName", dotFileName); - return vis; - } - - @SuppressWarnings("keyfor:enhancedfor.type.incompatible") - @Override - public String visualizeNodes( - Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { - - StringBuilder sbDotNodes = new StringBuilder(); - - IdentityHashMap> processOrder = getProcessOrder(cfg); - - // Definition of all nodes including their labels. - for (@KeyFor("processOrder") Block v : blocks) { - sbDotNodes.append(" ").append(v.getUid()).append(" ["); - if (v.getType() == BlockType.CONDITIONAL_BLOCK) { - sbDotNodes.append("shape=polygon sides=8 "); - } else if (v.getType() == BlockType.SPECIAL_BLOCK) { - sbDotNodes.append("shape=oval "); - } else { - sbDotNodes.append("shape=rectangle "); - } - sbDotNodes.append("label=\""); - if (verbose) { - sbDotNodes.append(getProcessOrderSimpleString(processOrder.get(v))).append(getSeparator()); - } - String strBlock = visualizeBlock(v, analysis); - if (strBlock.length() == 0) { - if (v.getType() == BlockType.CONDITIONAL_BLOCK) { - // The footer of the conditional block. - sbDotNodes.append("\"];"); + + @SuppressWarnings("keyfor:enhancedfor.type.incompatible") + @Override + public String visualizeNodes( + Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { + + StringBuilder sbDotNodes = new StringBuilder(); + + IdentityHashMap> processOrder = getProcessOrder(cfg); + + // Definition of all nodes including their labels. + for (@KeyFor("processOrder") Block v : blocks) { + sbDotNodes.append(" ").append(v.getUid()).append(" ["); + if (v.getType() == BlockType.CONDITIONAL_BLOCK) { + sbDotNodes.append("shape=polygon sides=8 "); + } else if (v.getType() == BlockType.SPECIAL_BLOCK) { + sbDotNodes.append("shape=oval "); + } else { + sbDotNodes.append("shape=rectangle "); + } + sbDotNodes.append("label=\""); + if (verbose) { + sbDotNodes + .append(getProcessOrderSimpleString(processOrder.get(v))) + .append(getSeparator()); + } + String strBlock = visualizeBlock(v, analysis); + if (strBlock.length() == 0) { + if (v.getType() == BlockType.CONDITIONAL_BLOCK) { + // The footer of the conditional block. + sbDotNodes.append("\"];"); + } else { + // The footer of the block which has no content and is not a special or + // conditional block. + sbDotNodes.append("?? empty ??\"];"); + } + } else { + sbDotNodes.append(strBlock).append("\"];"); + } + sbDotNodes.append(System.lineSeparator()); + } + return sbDotNodes.toString(); + } + + @Override + protected String visualizeEdge(Object sId, Object eId, String flowRule) { + return " " + format(sId) + " -> " + format(eId) + " [label=\"" + flowRule + "\"];"; + } + + @Override + public String visualizeBlock(Block bb, @Nullable Analysis analysis) { + return super.visualizeBlockWithSeparator(bb, analysis, getSeparator()); + } + + @Override + public String visualizeSpecialBlock(SpecialBlock sbb) { + return super.visualizeSpecialBlockHelper(sbb); + } + + @Override + public String visualizeConditionalBlock(ConditionalBlock cbb) { + // No extra content in DOT output. + return ""; + } + + @Override + public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.BEFORE, bb, analysis, getSeparator()); + } + + @Override + public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.AFTER, bb, analysis, getSeparator()); + } + + /** + * Create a dot file and return its name. + * + * @param ast an abstract syntax tree + * @return the file name used for DOT output + */ + protected String dotOutputFileName(UnderlyingAST ast) { + StringBuilder srcLoc = new StringBuilder(); + StringBuilder outFile = new StringBuilder(); + + if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { + CFGStatement cfgStatement = (CFGStatement) ast; + String clsName = cfgStatement.getSimpleClassName(); + outFile.append(clsName); + outFile.append("-initializer-"); + outFile.append(ast.getUid()); + + srcLoc.append("<"); + srcLoc.append(clsName); + srcLoc.append("::initializer::"); + srcLoc.append(((JCTree) cfgStatement.getCode()).pos); + srcLoc.append(">"); + } else if (ast.getKind() == UnderlyingAST.Kind.METHOD) { + CFGMethod cfgMethod = (CFGMethod) ast; + String clsName = cfgMethod.getSimpleClassName(); + String methodName = cfgMethod.getMethodName(); + StringJoiner params = new StringJoiner(","); + for (VariableTree tree : cfgMethod.getMethod().getParameters()) { + params.add(tree.getType().toString()); + } + outFile.append(clsName); + outFile.append("-"); + outFile.append(methodName); + if (params.length() != 0) { + outFile.append("-"); + outFile.append(params); + } + + srcLoc.append("<"); + srcLoc.append(clsName); + srcLoc.append("::"); + srcLoc.append(methodName); + srcLoc.append("("); + srcLoc.append(params); + srcLoc.append(")::"); + srcLoc.append(((JCTree) cfgMethod.getMethod()).pos); + srcLoc.append(">"); + } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { + CFGLambda cfgLambda = (CFGLambda) ast; + String clsName = cfgLambda.getSimpleClassName(); + String enclosingMethodName = cfgLambda.getEnclosingMethodName(); + long uid = TreeUtils.treeUids.get(cfgLambda.getCode()); + outFile.append(clsName); + outFile.append("-"); + if (enclosingMethodName != null) { + outFile.append(enclosingMethodName); + outFile.append("-"); + } + outFile.append(uid); + + srcLoc.append("<"); + srcLoc.append(clsName); + if (enclosingMethodName != null) { + srcLoc.append("::"); + srcLoc.append(enclosingMethodName); + srcLoc.append("("); + @SuppressWarnings( + "nullness") // enclosingMethodName != null => getEnclosingMethod() != null + @NonNull MethodTree method = cfgLambda.getEnclosingMethod(); + srcLoc.append(method.getParameters()); + srcLoc.append(")"); + } + srcLoc.append("::"); + srcLoc.append(((JCTree) cfgLambda.getCode()).pos); + srcLoc.append(">"); } else { - // The footer of the block which has no content and is not a special or - // conditional block. - sbDotNodes.append("?? empty ??\"];"); + throw new BugInCF("Unexpected AST kind: " + ast.getKind() + " value: " + ast); + } + if (checkerName != null && !checkerName.isEmpty()) { + outFile.append('-'); + outFile.append(checkerName); } - } else { - sbDotNodes.append(strBlock).append("\"];"); - } - sbDotNodes.append(System.lineSeparator()); + outFile.append(".dot"); + + // make path safe for Linux + if (outFile.length() > 255) { + outFile.setLength(255); + } + // make path safe for Windows + String outFileBaseName = outFile.toString().replace("<", "_").replace(">", ""); + String outFileName = outDir + "/" + outFileBaseName; + + generated.put(srcLoc.toString(), outFileName); + + return outFileName; } - return sbDotNodes.toString(); - } - - @Override - protected String visualizeEdge(Object sId, Object eId, String flowRule) { - return " " + format(sId) + " -> " + format(eId) + " [label=\"" + flowRule + "\"];"; - } - - @Override - public String visualizeBlock(Block bb, @Nullable Analysis analysis) { - return super.visualizeBlockWithSeparator(bb, analysis, getSeparator()); - } - - @Override - public String visualizeSpecialBlock(SpecialBlock sbb) { - return super.visualizeSpecialBlockHelper(sbb); - } - - @Override - public String visualizeConditionalBlock(ConditionalBlock cbb) { - // No extra content in DOT output. - return ""; - } - - @Override - public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.BEFORE, bb, analysis, getSeparator()); - } - - @Override - public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.AFTER, bb, analysis, getSeparator()); - } - - /** - * Create a dot file and return its name. - * - * @param ast an abstract syntax tree - * @return the file name used for DOT output - */ - protected String dotOutputFileName(UnderlyingAST ast) { - StringBuilder srcLoc = new StringBuilder(); - StringBuilder outFile = new StringBuilder(); - - if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { - CFGStatement cfgStatement = (CFGStatement) ast; - String clsName = cfgStatement.getSimpleClassName(); - outFile.append(clsName); - outFile.append("-initializer-"); - outFile.append(ast.getUid()); - - srcLoc.append("<"); - srcLoc.append(clsName); - srcLoc.append("::initializer::"); - srcLoc.append(((JCTree) cfgStatement.getCode()).pos); - srcLoc.append(">"); - } else if (ast.getKind() == UnderlyingAST.Kind.METHOD) { - CFGMethod cfgMethod = (CFGMethod) ast; - String clsName = cfgMethod.getSimpleClassName(); - String methodName = cfgMethod.getMethodName(); - StringJoiner params = new StringJoiner(","); - for (VariableTree tree : cfgMethod.getMethod().getParameters()) { - params.add(tree.getType().toString()); - } - outFile.append(clsName); - outFile.append("-"); - outFile.append(methodName); - if (params.length() != 0) { - outFile.append("-"); - outFile.append(params); - } - - srcLoc.append("<"); - srcLoc.append(clsName); - srcLoc.append("::"); - srcLoc.append(methodName); - srcLoc.append("("); - srcLoc.append(params); - srcLoc.append(")::"); - srcLoc.append(((JCTree) cfgMethod.getMethod()).pos); - srcLoc.append(">"); - } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { - CFGLambda cfgLambda = (CFGLambda) ast; - String clsName = cfgLambda.getSimpleClassName(); - String enclosingMethodName = cfgLambda.getEnclosingMethodName(); - long uid = TreeUtils.treeUids.get(cfgLambda.getCode()); - outFile.append(clsName); - outFile.append("-"); - if (enclosingMethodName != null) { - outFile.append(enclosingMethodName); - outFile.append("-"); - } - outFile.append(uid); - - srcLoc.append("<"); - srcLoc.append(clsName); - if (enclosingMethodName != null) { - srcLoc.append("::"); - srcLoc.append(enclosingMethodName); - srcLoc.append("("); - @SuppressWarnings("nullness") // enclosingMethodName != null => getEnclosingMethod() != null - @NonNull MethodTree method = cfgLambda.getEnclosingMethod(); - srcLoc.append(method.getParameters()); - srcLoc.append(")"); - } - srcLoc.append("::"); - srcLoc.append(((JCTree) cfgLambda.getCode()).pos); - srcLoc.append(">"); - } else { - throw new BugInCF("Unexpected AST kind: " + ast.getKind() + " value: " + ast); + + @Override + protected String format(Object obj) { + return escapeString(obj); } - if (checkerName != null && !checkerName.isEmpty()) { - outFile.append('-'); - outFile.append(checkerName); + + @Override + public String visualizeStoreThisVal(V value) { + return storeEntryIndent + "this > " + escapeString(value); + } + + @Override + public String visualizeStoreLocalVar(LocalVariable localVar, V value) { + return storeEntryIndent + localVar + " > " + escapeString(value); + } + + @Override + public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { + return storeEntryIndent + fieldAccess + " > " + escapeString(value); } - outFile.append(".dot"); - // make path safe for Linux - if (outFile.length() > 255) { - outFile.setLength(255); + @Override + public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { + return storeEntryIndent + arrayValue + " > " + escapeString(value); } - // make path safe for Windows - String outFileBaseName = outFile.toString().replace("<", "_").replace(">", ""); - String outFileName = outDir + "/" + outFileBaseName; - - generated.put(srcLoc.toString(), outFileName); - - return outFileName; - } - - @Override - protected String format(Object obj) { - return escapeString(obj); - } - - @Override - public String visualizeStoreThisVal(V value) { - return storeEntryIndent + "this > " + escapeString(value); - } - - @Override - public String visualizeStoreLocalVar(LocalVariable localVar, V value) { - return storeEntryIndent + localVar + " > " + escapeString(value); - } - - @Override - public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { - return storeEntryIndent + fieldAccess + " > " + escapeString(value); - } - - @Override - public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { - return storeEntryIndent + arrayValue + " > " + escapeString(value); - } - - @Override - public String visualizeStoreMethodVals(MethodCall methodCall, V value) { - return storeEntryIndent + escapeString(methodCall) + " > " + escapeString(value); - } - - @Override - public String visualizeStoreClassVals(ClassName className, V value) { - return storeEntryIndent + className + " > " + escapeString(value); - } - - @Override - public String visualizeStoreKeyVal(String keyName, Object value) { - return storeEntryIndent + keyName + " = " + value; - } - - /** - * Escape the input String. - * - * @param str the string to be escaped - * @return the escaped version of the string - */ - private static String escapeString(String str) { - return str.replace("\"", "\\\"").replace("\r", "\\\\r").replace("\n", "\\\\n"); - } - - /** - * Escape the double quotes from the string representation of the given object. - * - * @param obj an object - * @return an escaped version of the string representation of the object - */ - private static String escapeString(Object obj) { - return escapeString(String.valueOf(obj)); - } - - /** - * Write a file {@code methods.txt} that contains a mapping from source code location to generated - * dot file. - */ - @Override - public void shutdown() { - // Open for append, in case of multiple sub-checkers. - try (Writer fstream = - Files.newBufferedWriter( - Paths.get(outDir + "/methods.txt"), - StandardCharsets.UTF_8, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND); - BufferedWriter out = new BufferedWriter(fstream)) { - for (Map.Entry kv : generated.entrySet()) { - out.write(kv.getKey()); - out.append("\t"); - out.write(kv.getValue()); - out.append(lineSeparator); - } - } catch (IOException e) { - throw new UserError( - "Error creating methods.txt file in: " + outDir + "; ensure the path is valid", e); + + @Override + public String visualizeStoreMethodVals(MethodCall methodCall, V value) { + return storeEntryIndent + escapeString(methodCall) + " > " + escapeString(value); } - } - @Override - protected String visualizeGraphHeader() { - return "digraph {" + lineSeparator; - } + @Override + public String visualizeStoreClassVals(ClassName className, V value) { + return storeEntryIndent + className + " > " + escapeString(value); + } - @Override - protected String visualizeGraphFooter() { - return "}"; - } + @Override + public String visualizeStoreKeyVal(String keyName, Object value) { + return storeEntryIndent + keyName + " = " + value; + } + + /** + * Escape the input String. + * + * @param str the string to be escaped + * @return the escaped version of the string + */ + private static String escapeString(String str) { + return str.replace("\"", "\\\"").replace("\r", "\\\\r").replace("\n", "\\\\n"); + } + + /** + * Escape the double quotes from the string representation of the given object. + * + * @param obj an object + * @return an escaped version of the string representation of the object + */ + private static String escapeString(Object obj) { + return escapeString(String.valueOf(obj)); + } + + /** + * Write a file {@code methods.txt} that contains a mapping from source code location to + * generated dot file. + */ + @Override + public void shutdown() { + // Open for append, in case of multiple sub-checkers. + try (Writer fstream = + Files.newBufferedWriter( + Paths.get(outDir + "/methods.txt"), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + BufferedWriter out = new BufferedWriter(fstream)) { + for (Map.Entry kv : generated.entrySet()) { + out.write(kv.getKey()); + out.append("\t"); + out.write(kv.getValue()); + out.append(lineSeparator); + } + } catch (IOException e) { + throw new UserError( + "Error creating methods.txt file in: " + outDir + "; ensure the path is valid", + e); + } + } + + @Override + protected String visualizeGraphHeader() { + return "digraph {" + lineSeparator; + } + + @Override + protected String visualizeGraphFooter() { + return "}"; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java index 46891f908ed..d6450d71299 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java @@ -1,12 +1,5 @@ package org.checkerframework.dataflow.cfg.visualize; -import java.io.PrintStream; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; @@ -24,172 +17,180 @@ import org.checkerframework.dataflow.expression.LocalVariable; import org.checkerframework.dataflow.expression.MethodCall; +import java.io.PrintStream; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + /** Generate the String representation of a control flow graph. */ public class StringCFGVisualizer< - V extends AbstractValue, S extends Store, T extends TransferFunction> - extends AbstractCFGVisualizer { - - /** Stream to output String representation to. */ - protected PrintStream out; - - /** Create a StringCFGVisualizer. */ - public StringCFGVisualizer() { - out = System.out; - } - - @Override - public void init(Map args) { - super.init(args); - PrintStream argout = (PrintStream) args.get("output"); - if (argout != null) { - out = argout; - } - } - - @Override - public String getSeparator() { - return "\n"; - } - - @Override - public Map visualize( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - String stringGraph = visualizeGraph(cfg, entry, analysis); - return Collections.singletonMap("stringGraph", stringGraph); - } - - @Override - public Map visualizeWithAction( - ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { - Map vis = visualize(cfg, entry, analysis); - String stringGraph = (String) vis.get("stringGraph"); - out.println(stringGraph); - return vis; - } - - @SuppressWarnings("keyfor:enhancedfor.type.incompatible") - @Override - public String visualizeNodes( - Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { - StringJoiner sjStringNodes = new StringJoiner(lineSeparator); - IdentityHashMap> processOrder = getProcessOrder(cfg); - - // Generate all the Nodes. - for (@KeyFor("processOrder") Block v : blocks) { - sjStringNodes.add(v.getUid() + ":"); - if (verbose) { - sjStringNodes.add(getProcessOrderSimpleString(processOrder.get(v))); - } - sjStringNodes.add(visualizeBlock(v, analysis)); - sjStringNodes.add(""); - } - - return sjStringNodes.toString().trim(); - } - - @Override - protected String visualizeEdge(Object sId, Object eId, String flowRule) { - if (this.verbose) { - return sId + " -> " + eId + " " + flowRule; - } - return sId + " -> " + eId; - } - - @Override - public String visualizeBlock(Block bb, @Nullable Analysis analysis) { - return super.visualizeBlockWithSeparator(bb, analysis, lineSeparator).trim(); - } - - @Override - public String visualizeSpecialBlock(SpecialBlock sbb) { - return super.visualizeSpecialBlockHelper(sbb); - } - - @Override - public String visualizeConditionalBlock(ConditionalBlock cbb) { - return "ConditionalBlock: then: " - + cbb.getThenSuccessor().getUid() - + ", else: " - + cbb.getElseSuccessor().getUid(); - } - - @Override - public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.BEFORE, bb, analysis, lineSeparator); - } - - @Override - public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { - return super.visualizeBlockTransferInputHelper( - VisualizeWhere.AFTER, bb, analysis, lineSeparator); - } - - @Override - protected String format(Object obj) { - return obj.toString(); - } - - @Override - public String visualizeStoreThisVal(V value) { - return storeEntryIndent + "this > " + value; - } - - @Override - public String visualizeStoreLocalVar(LocalVariable localVar, V value) { - return storeEntryIndent + localVar + " > " + value; - } - - @Override - public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { - return storeEntryIndent + fieldAccess + " > " + value; - } - - @Override - public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { - return storeEntryIndent + arrayValue + " > " + value; - } - - @Override - public String visualizeStoreMethodVals(MethodCall methodCall, V value) { - return storeEntryIndent + methodCall + " > " + value; - } - - @Override - public String visualizeStoreClassVals(ClassName className, V value) { - return storeEntryIndent + className + " > " + value; - } - - @Override - public String visualizeStoreKeyVal(String keyName, Object value) { - return storeEntryIndent + keyName + " = " + value; - } - - /** - * {@inheritDoc} - * - *

          StringCFGVisualizer does not write into file, so left intentionally blank. - */ - @Override - public void shutdown() {} - - /** - * {@inheritDoc} - * - *

          StringCFGVisualizer does not need a specific header, so just return an empty string. - */ - @Override - protected String visualizeGraphHeader() { - return ""; - } - - /** - * {@inheritDoc} - * - *

          StringCFGVisualizer does not need a specific footer, so just return an empty string. - */ - @Override - protected String visualizeGraphFooter() { - return ""; - } + V extends AbstractValue, S extends Store, T extends TransferFunction> + extends AbstractCFGVisualizer { + + /** Stream to output String representation to. */ + protected PrintStream out; + + /** Create a StringCFGVisualizer. */ + public StringCFGVisualizer() { + out = System.out; + } + + @Override + public void init(Map args) { + super.init(args); + PrintStream argout = (PrintStream) args.get("output"); + if (argout != null) { + out = argout; + } + } + + @Override + public String getSeparator() { + return "\n"; + } + + @Override + public Map visualize( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + String stringGraph = visualizeGraph(cfg, entry, analysis); + return Collections.singletonMap("stringGraph", stringGraph); + } + + @Override + public Map visualizeWithAction( + ControlFlowGraph cfg, Block entry, @Nullable Analysis analysis) { + Map vis = visualize(cfg, entry, analysis); + String stringGraph = (String) vis.get("stringGraph"); + out.println(stringGraph); + return vis; + } + + @SuppressWarnings("keyfor:enhancedfor.type.incompatible") + @Override + public String visualizeNodes( + Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { + StringJoiner sjStringNodes = new StringJoiner(lineSeparator); + IdentityHashMap> processOrder = getProcessOrder(cfg); + + // Generate all the Nodes. + for (@KeyFor("processOrder") Block v : blocks) { + sjStringNodes.add(v.getUid() + ":"); + if (verbose) { + sjStringNodes.add(getProcessOrderSimpleString(processOrder.get(v))); + } + sjStringNodes.add(visualizeBlock(v, analysis)); + sjStringNodes.add(""); + } + + return sjStringNodes.toString().trim(); + } + + @Override + protected String visualizeEdge(Object sId, Object eId, String flowRule) { + if (this.verbose) { + return sId + " -> " + eId + " " + flowRule; + } + return sId + " -> " + eId; + } + + @Override + public String visualizeBlock(Block bb, @Nullable Analysis analysis) { + return super.visualizeBlockWithSeparator(bb, analysis, lineSeparator).trim(); + } + + @Override + public String visualizeSpecialBlock(SpecialBlock sbb) { + return super.visualizeSpecialBlockHelper(sbb); + } + + @Override + public String visualizeConditionalBlock(ConditionalBlock cbb) { + return "ConditionalBlock: then: " + + cbb.getThenSuccessor().getUid() + + ", else: " + + cbb.getElseSuccessor().getUid(); + } + + @Override + public String visualizeBlockTransferInputBefore(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.BEFORE, bb, analysis, lineSeparator); + } + + @Override + public String visualizeBlockTransferInputAfter(Block bb, Analysis analysis) { + return super.visualizeBlockTransferInputHelper( + VisualizeWhere.AFTER, bb, analysis, lineSeparator); + } + + @Override + protected String format(Object obj) { + return obj.toString(); + } + + @Override + public String visualizeStoreThisVal(V value) { + return storeEntryIndent + "this > " + value; + } + + @Override + public String visualizeStoreLocalVar(LocalVariable localVar, V value) { + return storeEntryIndent + localVar + " > " + value; + } + + @Override + public String visualizeStoreFieldVal(FieldAccess fieldAccess, V value) { + return storeEntryIndent + fieldAccess + " > " + value; + } + + @Override + public String visualizeStoreArrayVal(ArrayAccess arrayValue, V value) { + return storeEntryIndent + arrayValue + " > " + value; + } + + @Override + public String visualizeStoreMethodVals(MethodCall methodCall, V value) { + return storeEntryIndent + methodCall + " > " + value; + } + + @Override + public String visualizeStoreClassVals(ClassName className, V value) { + return storeEntryIndent + className + " > " + value; + } + + @Override + public String visualizeStoreKeyVal(String keyName, Object value) { + return storeEntryIndent + keyName + " = " + value; + } + + /** + * {@inheritDoc} + * + *

          StringCFGVisualizer does not write into file, so left intentionally blank. + */ + @Override + public void shutdown() {} + + /** + * {@inheritDoc} + * + *

          StringCFGVisualizer does not need a specific header, so just return an empty string. + */ + @Override + protected String visualizeGraphHeader() { + return ""; + } + + /** + * {@inheritDoc} + * + *

          StringCFGVisualizer does not need a specific footer, so just return an empty string. + */ + @Override + protected String visualizeGraphFooter() { + return ""; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java index b44f11c9818..02b5e969ec9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java @@ -1,125 +1,126 @@ package org.checkerframework.dataflow.constantpropagation; -import java.util.Objects; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; import org.checkerframework.javacutil.BugInCF; +import java.util.Objects; + public class Constant implements AbstractValue { - /** What kind of abstract value is this? */ - protected final Type type; - - /** The value of this abstract value (or null). */ - protected @Nullable Integer value; - - public enum Type { - CONSTANT, - TOP, - BOTTOM, - } - - /** Create a constant for {@code type}. */ - public Constant(Type type) { - assert type != Type.CONSTANT; - this.type = type; - } - - /** Create a constant for {@code value}. */ - public Constant(Integer value) { - this.type = Type.CONSTANT; - this.value = value; - } - - /** - * Returns whether or not the constant is TOP. - * - * @return whether or not the constant is TOP - */ - public boolean isTop() { - return type == Type.TOP; - } - - /** - * Returns whether or not the constant is BOTTOM. - * - * @return whether or not the constant is BOTTOM - */ - public boolean isBottom() { - return type == Type.BOTTOM; - } - - /** - * Returns whether or not the constant is CONSTANT. - * - * @return whether or not the constant is CONSTANT - */ - @EnsuresNonNullIf(result = true, expression = "value") - public boolean isConstant() { - return type == Type.CONSTANT && value != null; - } - - /** - * Returns the value. - * - * @return the value - */ - public Integer getValue() { - assert isConstant() : "@AssumeAssertion(nullness): inspection"; - return value; - } - - public Constant copy() { - if (isConstant()) { - return new Constant(value); + /** What kind of abstract value is this? */ + protected final Type type; + + /** The value of this abstract value (or null). */ + protected @Nullable Integer value; + + public enum Type { + CONSTANT, + TOP, + BOTTOM, } - return new Constant(type); - } - @Override - public Constant leastUpperBound(Constant other) { - if (other.isBottom()) { - return this.copy(); + /** Create a constant for {@code type}. */ + public Constant(Type type) { + assert type != Type.CONSTANT; + this.type = type; } - if (this.isBottom()) { - return other.copy(); + + /** Create a constant for {@code value}. */ + public Constant(Integer value) { + this.type = Type.CONSTANT; + this.value = value; } - if (other.isTop() || this.isTop()) { - return new Constant(Type.TOP); + + /** + * Returns whether or not the constant is TOP. + * + * @return whether or not the constant is TOP + */ + public boolean isTop() { + return type == Type.TOP; } - if (other.getValue().equals(getValue())) { - return this.copy(); + + /** + * Returns whether or not the constant is BOTTOM. + * + * @return whether or not the constant is BOTTOM + */ + public boolean isBottom() { + return type == Type.BOTTOM; + } + + /** + * Returns whether or not the constant is CONSTANT. + * + * @return whether or not the constant is CONSTANT + */ + @EnsuresNonNullIf(result = true, expression = "value") + public boolean isConstant() { + return type == Type.CONSTANT && value != null; } - return new Constant(Type.TOP); - } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof Constant)) { - return false; + /** + * Returns the value. + * + * @return the value + */ + public Integer getValue() { + assert isConstant() : "@AssumeAssertion(nullness): inspection"; + return value; } - Constant other = (Constant) obj; - return type == other.type && Objects.equals(value, other.value); - } - - @Override - public int hashCode() { - return Objects.hash(type, value); - } - - @Override - public String toString() { - switch (type) { - case TOP: - return "T"; - case BOTTOM: - return "-"; - case CONSTANT: - assert isConstant() : "@AssumeAssertion(nullness)"; - return value.toString(); - default: - throw new BugInCF("Unexpected type: " + type); + + public Constant copy() { + if (isConstant()) { + return new Constant(value); + } + return new Constant(type); + } + + @Override + public Constant leastUpperBound(Constant other) { + if (other.isBottom()) { + return this.copy(); + } + if (this.isBottom()) { + return other.copy(); + } + if (other.isTop() || this.isTop()) { + return new Constant(Type.TOP); + } + if (other.getValue().equals(getValue())) { + return this.copy(); + } + return new Constant(Type.TOP); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof Constant)) { + return false; + } + Constant other = (Constant) obj; + return type == other.type && Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(type, value); + } + + @Override + public String toString() { + switch (type) { + case TOP: + return "T"; + case BOTTOM: + return "-"; + case CONSTANT: + assert isConstant() : "@AssumeAssertion(nullness)"; + return value.toString(); + default: + throw new BugInCF("Unexpected type: " + type); + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java index f4c1efef9c8..79189d6c5fc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java @@ -1,7 +1,5 @@ package org.checkerframework.dataflow.constantpropagation; -import java.util.LinkedHashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode; @@ -12,156 +10,159 @@ import org.plumelib.util.ArrayMap; import org.plumelib.util.CollectionsPlume; +import java.util.LinkedHashMap; +import java.util.Map; + /** A store that records information about constant values. */ public class ConstantPropagationStore implements Store { - /** Information about variables gathered so far. */ - private final Map contents; + /** Information about variables gathered so far. */ + private final Map contents; - /** Creates a new ConstantPropagationStore. */ - public ConstantPropagationStore() { - contents = new LinkedHashMap<>(); - } + /** Creates a new ConstantPropagationStore. */ + public ConstantPropagationStore() { + contents = new LinkedHashMap<>(); + } - protected ConstantPropagationStore(Map contents) { - this.contents = contents; - } + protected ConstantPropagationStore(Map contents) { + this.contents = contents; + } - public Constant getInformation(Node n) { - if (contents.containsKey(n)) { - return contents.get(n); + public Constant getInformation(Node n) { + if (contents.containsKey(n)) { + return contents.get(n); + } + return new Constant(Constant.Type.TOP); } - return new Constant(Constant.Type.TOP); - } - - public void mergeInformation(Node n, Constant val) { - Constant value; - if (contents.containsKey(n)) { - value = val.leastUpperBound(contents.get(n)); - } else { - value = val; + + public void mergeInformation(Node n, Constant val) { + Constant value; + if (contents.containsKey(n)) { + value = val.leastUpperBound(contents.get(n)); + } else { + value = val; + } + // TODO: remove (only two nodes supported atm) + assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; + contents.put(n, value); } - // TODO: remove (only two nodes supported atm) - assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; - contents.put(n, value); - } - - public void setInformation(Node n, Constant val) { - // TODO: remove (only two nodes supported atm) - assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; - contents.put(n, val); - } - - @Override - public ConstantPropagationStore copy() { - return new ConstantPropagationStore(new LinkedHashMap<>(contents)); - } - - @Override - public ConstantPropagationStore leastUpperBound(ConstantPropagationStore other) { - Map newContents = - ArrayMap.newArrayMapOrLinkedHashMap(contents.size() + other.contents.size()); - - // go through all of the information of the other class - for (Map.Entry e : other.contents.entrySet()) { - Node n = e.getKey(); - Constant otherVal = e.getValue(); - if (contents.containsKey(n)) { - // merge if both contain information about a variable - newContents.put(n, otherVal.leastUpperBound(contents.get(n))); - } else { - // add new information - newContents.put(n, otherVal); - } + + public void setInformation(Node n, Constant val) { + // TODO: remove (only two nodes supported atm) + assert n instanceof IntegerLiteralNode || n instanceof LocalVariableNode; + contents.put(n, val); } - for (Map.Entry e : contents.entrySet()) { - Node n = e.getKey(); - Constant thisVal = e.getValue(); - if (!other.contents.containsKey(n)) { - // add new information - newContents.put(n, thisVal); - } + @Override + public ConstantPropagationStore copy() { + return new ConstantPropagationStore(new LinkedHashMap<>(contents)); } - return new ConstantPropagationStore(newContents); - } + @Override + public ConstantPropagationStore leastUpperBound(ConstantPropagationStore other) { + Map newContents = + ArrayMap.newArrayMapOrLinkedHashMap(contents.size() + other.contents.size()); + + // go through all of the information of the other class + for (Map.Entry e : other.contents.entrySet()) { + Node n = e.getKey(); + Constant otherVal = e.getValue(); + if (contents.containsKey(n)) { + // merge if both contain information about a variable + newContents.put(n, otherVal.leastUpperBound(contents.get(n))); + } else { + // add new information + newContents.put(n, otherVal); + } + } - @Override - public ConstantPropagationStore widenedUpperBound(ConstantPropagationStore previous) { - return leastUpperBound(previous); - } + for (Map.Entry e : contents.entrySet()) { + Node n = e.getKey(); + Constant thisVal = e.getValue(); + if (!other.contents.containsKey(n)) { + // add new information + newContents.put(n, thisVal); + } + } - @Override - public boolean equals(@Nullable Object o) { - if (o == null) { - return false; + return new ConstantPropagationStore(newContents); } - if (!(o instanceof ConstantPropagationStore)) { - return false; + + @Override + public ConstantPropagationStore widenedUpperBound(ConstantPropagationStore previous) { + return leastUpperBound(previous); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == null) { + return false; + } + if (!(o instanceof ConstantPropagationStore)) { + return false; + } + ConstantPropagationStore other = (ConstantPropagationStore) o; + // go through all of the information of the other object + for (Map.Entry e : other.contents.entrySet()) { + Node n = e.getKey(); + Constant otherVal = e.getValue(); + if (otherVal.isBottom()) { + continue; // no information + } + if (contents.containsKey(n)) { + if (!otherVal.equals(contents.get(n))) { + return false; + } + } else { + return false; + } + } + // go through all of the information of the this object + for (Map.Entry e : contents.entrySet()) { + Node n = e.getKey(); + Constant thisVal = e.getValue(); + if (thisVal.isBottom()) { + continue; // no information + } + if (!other.contents.containsKey(n)) { + return false; + } + } + return true; } - ConstantPropagationStore other = (ConstantPropagationStore) o; - // go through all of the information of the other object - for (Map.Entry e : other.contents.entrySet()) { - Node n = e.getKey(); - Constant otherVal = e.getValue(); - if (otherVal.isBottom()) { - continue; // no information - } - if (contents.containsKey(n)) { - if (!otherVal.equals(contents.get(n))) { - return false; + + @Override + public int hashCode() { + int s = 0; + for (Map.Entry e : contents.entrySet()) { + if (!e.getValue().isBottom()) { + s += e.hashCode(); + } } - } else { - return false; - } + return s; } - // go through all of the information of the this object - for (Map.Entry e : contents.entrySet()) { - Node n = e.getKey(); - Constant thisVal = e.getValue(); - if (thisVal.isBottom()) { - continue; // no information - } - if (!other.contents.containsKey(n)) { - return false; - } + + @Override + public String toString() { + // Only output local variable information. + // This output is very terse, so a CFG containing it fits well in the manual. + Map contentsLocalVars = + new LinkedHashMap<>(CollectionsPlume.mapCapacity(contents)); + for (Map.Entry e : contents.entrySet()) { + if (e.getKey() instanceof LocalVariableNode) { + contentsLocalVars.put(e.getKey(), e.getValue()); + } + } + return contentsLocalVars.toString(); } - return true; - } - - @Override - public int hashCode() { - int s = 0; - for (Map.Entry e : contents.entrySet()) { - if (!e.getValue().isBottom()) { - s += e.hashCode(); - } + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; } - return s; - } - - @Override - public String toString() { - // Only output local variable information. - // This output is very terse, so a CFG containing it fits well in the manual. - Map contentsLocalVars = - new LinkedHashMap<>(CollectionsPlume.mapCapacity(contents)); - for (Map.Entry e : contents.entrySet()) { - if (e.getKey() instanceof LocalVariableNode) { - contentsLocalVars.put(e.getKey(), e.getValue()); - } + + @Override + public String visualize(CFGVisualizer viz) { + return viz.visualizeStoreKeyVal("constant propagation", toString()); } - return contentsLocalVars.toString(); - } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; - } - - @Override - public String visualize(CFGVisualizer viz) { - return viz.visualizeStoreKeyVal("constant propagation", toString()); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java index 0a850eda4de..2d75ee65bbb 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.dataflow.constantpropagation; -import java.util.List; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.ForwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; @@ -14,72 +13,74 @@ import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.dataflow.cfg.node.Node; +import java.util.List; + public class ConstantPropagationTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements ForwardTransferFunction { + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements ForwardTransferFunction { - @Override - public ConstantPropagationStore initialStore( - UnderlyingAST underlyingAST, List parameters) { - ConstantPropagationStore store = new ConstantPropagationStore(); - return store; - } + @Override + public ConstantPropagationStore initialStore( + UnderlyingAST underlyingAST, List parameters) { + ConstantPropagationStore store = new ConstantPropagationStore(); + return store; + } - @Override - public TransferResult visitLocalVariable( - LocalVariableNode node, TransferInput before) { - ConstantPropagationStore store = before.getRegularStore(); - Constant value = store.getInformation(node); - return new RegularTransferResult<>(value, store); - } + @Override + public TransferResult visitLocalVariable( + LocalVariableNode node, TransferInput before) { + ConstantPropagationStore store = before.getRegularStore(); + Constant value = store.getInformation(node); + return new RegularTransferResult<>(value, store); + } - @Override - public TransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } + @Override + public TransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } - @Override - public TransferResult visitAssignment( - AssignmentNode n, TransferInput pi) { - ConstantPropagationStore p = pi.getRegularStore(); - Node target = n.getTarget(); - Constant info = null; - if (target instanceof LocalVariableNode) { - LocalVariableNode t = (LocalVariableNode) target; - info = p.getInformation(n.getExpression()); - p.setInformation(t, info); + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput pi) { + ConstantPropagationStore p = pi.getRegularStore(); + Node target = n.getTarget(); + Constant info = null; + if (target instanceof LocalVariableNode) { + LocalVariableNode t = (LocalVariableNode) target; + info = p.getInformation(n.getExpression()); + p.setInformation(t, info); + } + return new RegularTransferResult<>(info, p); } - return new RegularTransferResult<>(info, p); - } - @Override - public TransferResult visitIntegerLiteral( - IntegerLiteralNode n, TransferInput pi) { - ConstantPropagationStore p = pi.getRegularStore(); - Constant c = new Constant(n.getValue()); - p.setInformation(n, c); - return new RegularTransferResult<>(c, p); - } + @Override + public TransferResult visitIntegerLiteral( + IntegerLiteralNode n, TransferInput pi) { + ConstantPropagationStore p = pi.getRegularStore(); + Constant c = new Constant(n.getValue()); + p.setInformation(n, c); + return new RegularTransferResult<>(c, p); + } - @Override - public TransferResult visitEqualTo( - EqualToNode n, TransferInput pi) { - ConstantPropagationStore p = pi.getRegularStore(); - ConstantPropagationStore old = p.copy(); - Node left = n.getLeftOperand(); - Node right = n.getRightOperand(); - process(p, left, right); - process(p, right, left); - return new ConditionalTransferResult<>(null, p, old); - } + @Override + public TransferResult visitEqualTo( + EqualToNode n, TransferInput pi) { + ConstantPropagationStore p = pi.getRegularStore(); + ConstantPropagationStore old = p.copy(); + Node left = n.getLeftOperand(); + Node right = n.getRightOperand(); + process(p, left, right); + process(p, right, left); + return new ConditionalTransferResult<>(null, p, old); + } - protected void process(ConstantPropagationStore p, Node a, Node b) { - Constant val = p.getInformation(a); - if (b instanceof LocalVariableNode && val.isConstant()) { - p.setInformation(b, val); + protected void process(ConstantPropagationStore p, Node a, Node b) { + Constant val = p.getInformation(a); + if (b instanceof LocalVariableNode && val.isConstant()) { + p.setInformation(b, val); + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java index cff72a52c44..ab245472636 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayAccess.java @@ -1,122 +1,124 @@ package org.checkerframework.dataflow.expression; -import java.util.Objects; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.javacutil.AnnotationProvider; +import java.util.Objects; + +import javax.lang.model.type.TypeMirror; + /** An array access. */ public class ArrayAccess extends JavaExpression { - /** The array being accessed. */ - protected final JavaExpression array; - - /** The index; an expression of type int. */ - protected final JavaExpression index; - - /** - * Create a new ArrayAccess. - * - * @param type the type of the array access - * @param array the array being accessed - * @param index the index; an expression of type int - */ - public ArrayAccess(TypeMirror type, JavaExpression array, JavaExpression index) { - super(type); - this.array = array; - this.index = index; - } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; + /** The array being accessed. */ + protected final JavaExpression array; + + /** The index; an expression of type int. */ + protected final JavaExpression index; + + /** + * Create a new ArrayAccess. + * + * @param type the type of the array access + * @param array the array being accessed + * @param index the index; an expression of type int + */ + public ArrayAccess(TypeMirror type, JavaExpression array, JavaExpression index) { + super(type); + this.array = array; + this.index = index; + } + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; + } + if (array.containsOfClass(clazz)) { + return true; + } + return index.containsOfClass(clazz); + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return array.isDeterministic(provider) && index.isDeterministic(provider); } - if (array.containsOfClass(clazz)) { - return true; + + /** + * Returns the array being accessed. + * + * @return the array being accessed + */ + public JavaExpression getArray() { + return array; } - return index.containsOfClass(clazz); - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return array.isDeterministic(provider) && index.isDeterministic(provider); - } - - /** - * Returns the array being accessed. - * - * @return the array being accessed - */ - public JavaExpression getArray() { - return array; - } - - public JavaExpression getIndex() { - return index; - } - - @Override - public boolean isUnassignableByOtherCode() { - return false; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return false; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof ArrayAccess)) { - return false; + + public JavaExpression getIndex() { + return index; + } + + @Override + public boolean isUnassignableByOtherCode() { + return false; } - ArrayAccess other = (ArrayAccess) je; - return array.syntacticEquals(other.array) && index.syntacticEquals(other.index); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) - || array.containsSyntacticEqualJavaExpression(other) - || index.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - if (array.containsModifiableAliasOf(store, other)) { - return true; + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof ArrayAccess)) { + return false; + } + ArrayAccess other = (ArrayAccess) je; + return array.syntacticEquals(other.array) && index.syntacticEquals(other.index); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) + || array.containsSyntacticEqualJavaExpression(other) + || index.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + if (array.containsModifiableAliasOf(store, other)) { + return true; + } + return index.containsModifiableAliasOf(store, other); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayAccess)) { + return false; + } + ArrayAccess other = (ArrayAccess) obj; + return array.equals(other.array) && index.equals(other.index); + } + + @Override + public int hashCode() { + return Objects.hash(array, index); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(array.toString()); + result.append("["); + result.append(index.toString()); + result.append("]"); + return result.toString(); } - return index.containsModifiableAliasOf(store, other); - } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayAccess)) { - return false; + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitArrayAccess(this, p); } - ArrayAccess other = (ArrayAccess) obj; - return array.equals(other.array) && index.equals(other.index); - } - - @Override - public int hashCode() { - return Objects.hash(array, index); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append(array.toString()); - result.append("["); - result.append(index.toString()); - result.append("]"); - return result.toString(); - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitArrayAccess(this, p); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java index 6d359535eaa..bda0d3a406f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java @@ -1,161 +1,164 @@ package org.checkerframework.dataflow.expression; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.StringsPlume; + import java.util.List; import java.util.Objects; + import javax.lang.model.type.ArrayType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.StringsPlume; /** JavaExpression for array creations. {@code new String[]()}. */ public class ArrayCreation extends JavaExpression { - /** - * List of dimensions expressions. A {code null} element means that there is no dimension - * expression for the given array level. - */ - protected final List<@Nullable JavaExpression> dimensions; - - /** List of initializers. */ - protected final List initializers; - - /** - * Creates an ArrayCreation object. - * - * @param type array type - * @param dimensions list of dimension expressions; a {@code null} element means that there is no - * dimension expression for the given array level - * @param initializers list of initializer expressions - */ - public ArrayCreation( - TypeMirror type, - List<@Nullable JavaExpression> dimensions, - List initializers) { - super(type); - assert type.getKind() == TypeKind.ARRAY; - this.dimensions = dimensions; - this.initializers = initializers; - } - - /** - * Returns a list representing the dimensions of this array creation. A {code null} element means - * that there is no dimension expression for the given array level. - * - * @return a list representing the dimensions of this array creation - */ - public List<@Nullable JavaExpression> getDimensions() { - return dimensions; - } - - public List getInitializers() { - return initializers; - } - - @Override - public boolean containsOfClass(Class clazz) { - for (JavaExpression n : dimensions) { - if (n != null && n.getClass() == clazz) { - return true; - } + /** + * List of dimensions expressions. A {code null} element means that there is no dimension + * expression for the given array level. + */ + protected final List<@Nullable JavaExpression> dimensions; + + /** List of initializers. */ + protected final List initializers; + + /** + * Creates an ArrayCreation object. + * + * @param type array type + * @param dimensions list of dimension expressions; a {@code null} element means that there is + * no dimension expression for the given array level + * @param initializers list of initializer expressions + */ + public ArrayCreation( + TypeMirror type, + List<@Nullable JavaExpression> dimensions, + List initializers) { + super(type); + assert type.getKind() == TypeKind.ARRAY; + this.dimensions = dimensions; + this.initializers = initializers; + } + + /** + * Returns a list representing the dimensions of this array creation. A {code null} element + * means that there is no dimension expression for the given array level. + * + * @return a list representing the dimensions of this array creation + */ + public List<@Nullable JavaExpression> getDimensions() { + return dimensions; } - for (JavaExpression n : initializers) { - if (n.getClass() == clazz) { - return true; - } + + public List getInitializers() { + return initializers; } - return false; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return listIsDeterministic(dimensions, provider) && listIsDeterministic(initializers, provider); - } - - @Override - public boolean isUnassignableByOtherCode() { - return false; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return false; - } - - @Override - public int hashCode() { - return Objects.hash(dimensions, initializers, getType().toString()); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ArrayCreation)) { - return false; + + @Override + public boolean containsOfClass(Class clazz) { + for (JavaExpression n : dimensions) { + if (n != null && n.getClass() == clazz) { + return true; + } + } + for (JavaExpression n : initializers) { + if (n.getClass() == clazz) { + return true; + } + } + return false; } - ArrayCreation other = (ArrayCreation) obj; - return this.dimensions.equals(other.getDimensions()) - && this.initializers.equals(other.getInitializers()) - // It might be better to use Types.isSameType(getType(), other.getType()), but I - // don't have a Types object. - && getType().toString().equals(other.getType().toString()); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof ArrayCreation)) { - return false; + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return listIsDeterministic(dimensions, provider) + && listIsDeterministic(initializers, provider); } - ArrayCreation other = (ArrayCreation) je; - return JavaExpression.syntacticEqualsList(this.dimensions, other.dimensions) - && JavaExpression.syntacticEqualsList(this.initializers, other.initializers) - && getType().toString().equals(other.getType().toString()); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) - || JavaExpression.listContainsSyntacticEqualJavaExpression(dimensions, other) - || JavaExpression.listContainsSyntacticEqualJavaExpression(initializers, other); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - if (dimensions.isEmpty()) { - sb.append("new " + type); - } else { - sb.append("new " + TypesUtils.getInnermostComponentType((ArrayType) type)); - for (JavaExpression dim : dimensions) { - sb.append("["); - sb.append(dim == null ? "" : dim); - sb.append("]"); - } + + @Override + public boolean isUnassignableByOtherCode() { + return false; } - if (!initializers.isEmpty()) { - sb.append(" {"); - sb.append(StringsPlume.join(", ", initializers)); - sb.append("}"); + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public int hashCode() { + return Objects.hash(dimensions, initializers, getType().toString()); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ArrayCreation)) { + return false; + } + ArrayCreation other = (ArrayCreation) obj; + return this.dimensions.equals(other.getDimensions()) + && this.initializers.equals(other.getInitializers()) + // It might be better to use Types.isSameType(getType(), other.getType()), but I + // don't have a Types object. + && getType().toString().equals(other.getType().toString()); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof ArrayCreation)) { + return false; + } + ArrayCreation other = (ArrayCreation) je; + return JavaExpression.syntacticEqualsList(this.dimensions, other.dimensions) + && JavaExpression.syntacticEqualsList(this.initializers, other.initializers) + && getType().toString().equals(other.getType().toString()); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) + || JavaExpression.listContainsSyntacticEqualJavaExpression(dimensions, other) + || JavaExpression.listContainsSyntacticEqualJavaExpression(initializers, other); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (dimensions.isEmpty()) { + sb.append("new " + type); + } else { + sb.append("new " + TypesUtils.getInnermostComponentType((ArrayType) type)); + for (JavaExpression dim : dimensions) { + sb.append("["); + sb.append(dim == null ? "" : dim); + sb.append("]"); + } + } + if (!initializers.isEmpty()) { + sb.append(" {"); + sb.append(StringsPlume.join(", ", initializers)); + sb.append("}"); + } + return sb.toString(); + } + + @Override + public String toStringDebug() { + return "\"" + + super.toStringDebug() + + "\"" + + " type=" + + type + + " dimensions=" + + dimensions + + " initializers=" + + initializers; + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitArrayCreation(this, p); } - return sb.toString(); - } - - @Override - public String toStringDebug() { - return "\"" - + super.toStringDebug() - + "\"" - + " type=" - + type - + " dimensions=" - + dimensions - + " initializers=" - + initializers; - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitArrayCreation(this, p); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java index 1b2511d48c7..50b9018ba8d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/BinaryOperation.java @@ -1,228 +1,235 @@ package org.checkerframework.dataflow.expression; import com.sun.source.tree.Tree; -import java.util.Objects; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.BinaryOperationNode; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; +import java.util.Objects; + +import javax.lang.model.type.TypeMirror; + /** JavaExpression for binary operations. */ public class BinaryOperation extends JavaExpression { - /** The binary operation kind. */ - protected final Tree.Kind operationKind; - - /** The left operand. */ - protected final JavaExpression left; - - /** The right operand. */ - protected final JavaExpression right; - - /** - * Create a binary operation. - * - * @param type the result type - * @param operationKind the operator - * @param left the left operand - * @param right the right operand - */ - public BinaryOperation( - TypeMirror type, Tree.Kind operationKind, JavaExpression left, JavaExpression right) { - super(type); - this.operationKind = operationKind; - this.left = left; - this.right = right; - } - - /** - * Create a binary operation. - * - * @param node the binary operation node - * @param left the left operand - * @param right the right operand - */ - public BinaryOperation(BinaryOperationNode node, JavaExpression left, JavaExpression right) { - this(node.getType(), node.getTree().getKind(), left, right); - } - - /** - * Returns the operator of this binary operation. - * - * @return the binary operation kind - */ - public Tree.Kind getOperationKind() { - return operationKind; - } - - /** - * Returns the left operand of this binary operation. - * - * @return the left operand - */ - public JavaExpression getLeft() { - return left; - } - - /** - * Returns the right operand of this binary operation. - * - * @return the right operand - */ - public JavaExpression getRight() { - return right; - } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; - } - return left.containsOfClass(clazz) || right.containsOfClass(clazz); - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return left.isDeterministic(provider) && right.isDeterministic(provider); - } - - @Override - public boolean isUnassignableByOtherCode() { - return left.isUnassignableByOtherCode() && right.isUnassignableByOtherCode(); - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return left.isUnmodifiableByOtherCode() && right.isUnmodifiableByOtherCode(); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof BinaryOperation)) { - return false; - } - BinaryOperation other = (BinaryOperation) je; - return operationKind == other.getOperationKind() - && left.syntacticEquals(other.left) - && right.syntacticEquals(other.right); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other) - || left.containsSyntacticEqualJavaExpression(other) - || right.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return left.containsModifiableAliasOf(store, other) - || right.containsModifiableAliasOf(store, other); - } - - @Override - public int hashCode() { - return Objects.hash(operationKind, left, right); - } - - @Override - public boolean equals(@Nullable Object other) { - if (!(other instanceof BinaryOperation)) { - return false; - } - BinaryOperation biOp = (BinaryOperation) other; - if (!(operationKind == biOp.getOperationKind())) { - return false; - } - if (isCommutative()) { - return (left.equals(biOp.left) && right.equals(biOp.right)) - || (left.equals(biOp.right) && right.equals(biOp.left)); - } - return left.equals(biOp.left) && right.equals(biOp.right); - } - - /** - * Returns true if the binary operation is commutative, e.g., x + y == y + x. - * - * @return true if the binary operation is commutative - */ - private boolean isCommutative() { - switch (operationKind) { - case PLUS: - case MULTIPLY: - case AND: - case OR: - case XOR: - case EQUAL_TO: - case NOT_EQUAL_TO: - case CONDITIONAL_AND: - case CONDITIONAL_OR: - return true; - default: - return false; - } - } - - @Override - public String toString() { - return left.toString() + " " + operationKindToString(operationKind) + " " + right.toString(); - } - - /** - * Return the Java source code representation of the given operation. - * - * @param operationKind an unary operation kind - * @return the Java source code representation of the given operation - */ - private String operationKindToString(Tree.Kind operationKind) { - switch (operationKind) { - case CONDITIONAL_AND: - return "&&"; - case AND: - return "&"; - case OR: - return "|"; - case DIVIDE: - return "/"; - case EQUAL_TO: - return "=="; - case GREATER_THAN: - return ">"; - case GREATER_THAN_EQUAL: - return ">="; - case LEFT_SHIFT: - return "<<"; - case LESS_THAN: - return "<"; - case LESS_THAN_EQUAL: - return "<="; - case MINUS: - return "-"; - case MULTIPLY: - return "*"; - case NOT_EQUAL_TO: - return "!="; - case CONDITIONAL_OR: - return "||"; - case PLUS: - return "+"; - case REMAINDER: - return "%"; - case RIGHT_SHIFT: - return ">>"; - case UNSIGNED_RIGHT_SHIFT: - return ">>>"; - case XOR: - return "^"; - default: - throw new BugInCF("unhandled " + operationKind); - } - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitBinaryOperation(this, p); - } + /** The binary operation kind. */ + protected final Tree.Kind operationKind; + + /** The left operand. */ + protected final JavaExpression left; + + /** The right operand. */ + protected final JavaExpression right; + + /** + * Create a binary operation. + * + * @param type the result type + * @param operationKind the operator + * @param left the left operand + * @param right the right operand + */ + public BinaryOperation( + TypeMirror type, Tree.Kind operationKind, JavaExpression left, JavaExpression right) { + super(type); + this.operationKind = operationKind; + this.left = left; + this.right = right; + } + + /** + * Create a binary operation. + * + * @param node the binary operation node + * @param left the left operand + * @param right the right operand + */ + public BinaryOperation(BinaryOperationNode node, JavaExpression left, JavaExpression right) { + this(node.getType(), node.getTree().getKind(), left, right); + } + + /** + * Returns the operator of this binary operation. + * + * @return the binary operation kind + */ + public Tree.Kind getOperationKind() { + return operationKind; + } + + /** + * Returns the left operand of this binary operation. + * + * @return the left operand + */ + public JavaExpression getLeft() { + return left; + } + + /** + * Returns the right operand of this binary operation. + * + * @return the right operand + */ + public JavaExpression getRight() { + return right; + } + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; + } + return left.containsOfClass(clazz) || right.containsOfClass(clazz); + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return left.isDeterministic(provider) && right.isDeterministic(provider); + } + + @Override + public boolean isUnassignableByOtherCode() { + return left.isUnassignableByOtherCode() && right.isUnassignableByOtherCode(); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return left.isUnmodifiableByOtherCode() && right.isUnmodifiableByOtherCode(); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof BinaryOperation)) { + return false; + } + BinaryOperation other = (BinaryOperation) je; + return operationKind == other.getOperationKind() + && left.syntacticEquals(other.left) + && right.syntacticEquals(other.right); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other) + || left.containsSyntacticEqualJavaExpression(other) + || right.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return left.containsModifiableAliasOf(store, other) + || right.containsModifiableAliasOf(store, other); + } + + @Override + public int hashCode() { + return Objects.hash(operationKind, left, right); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof BinaryOperation)) { + return false; + } + BinaryOperation biOp = (BinaryOperation) other; + if (!(operationKind == biOp.getOperationKind())) { + return false; + } + if (isCommutative()) { + return (left.equals(biOp.left) && right.equals(biOp.right)) + || (left.equals(biOp.right) && right.equals(biOp.left)); + } + return left.equals(biOp.left) && right.equals(biOp.right); + } + + /** + * Returns true if the binary operation is commutative, e.g., x + y == y + x. + * + * @return true if the binary operation is commutative + */ + private boolean isCommutative() { + switch (operationKind) { + case PLUS: + case MULTIPLY: + case AND: + case OR: + case XOR: + case EQUAL_TO: + case NOT_EQUAL_TO: + case CONDITIONAL_AND: + case CONDITIONAL_OR: + return true; + default: + return false; + } + } + + @Override + public String toString() { + return left.toString() + + " " + + operationKindToString(operationKind) + + " " + + right.toString(); + } + + /** + * Return the Java source code representation of the given operation. + * + * @param operationKind an unary operation kind + * @return the Java source code representation of the given operation + */ + private String operationKindToString(Tree.Kind operationKind) { + switch (operationKind) { + case CONDITIONAL_AND: + return "&&"; + case AND: + return "&"; + case OR: + return "|"; + case DIVIDE: + return "/"; + case EQUAL_TO: + return "=="; + case GREATER_THAN: + return ">"; + case GREATER_THAN_EQUAL: + return ">="; + case LEFT_SHIFT: + return "<<"; + case LESS_THAN: + return "<"; + case LESS_THAN_EQUAL: + return "<="; + case MINUS: + return "-"; + case MULTIPLY: + return "*"; + case NOT_EQUAL_TO: + return "!="; + case CONDITIONAL_OR: + return "||"; + case PLUS: + return "+"; + case REMAINDER: + return "%"; + case RIGHT_SHIFT: + return ">>"; + case UNSIGNED_RIGHT_SHIFT: + return ">>>"; + case XOR: + return "^"; + default: + throw new BugInCF("unhandled " + operationKind); + } + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitBinaryOperation(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java index 9e15dbdb84b..708bdaa6ee4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ClassName.java @@ -1,96 +1,98 @@ package org.checkerframework.dataflow.expression; -import java.util.Objects; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.javacutil.AnnotationProvider; +import java.util.Objects; + +import javax.lang.model.type.TypeMirror; + /** * A ClassName represents either a class literal or the occurrence of a class as part of a static * field access or static method invocation. */ public class ClassName extends JavaExpression { - /** The string representation of the raw type of this. */ - private final String typeString; - - /** - * Creates a new ClassName object for the given type. - * - * @param type the type for the new ClassName. If it will represent a class literal, the type is - * declared primitive, void, or array of one of them. If it represents part of a static field - * access or static method invocation, the type is declared, type variable, or array - * (including array of primitive). - */ - public ClassName(TypeMirror type) { - super(type); - String typeString = type.toString(); - if (typeString.endsWith(">")) { - typeString = typeString.substring(0, typeString.indexOf("<")); + /** The string representation of the raw type of this. */ + private final String typeString; + + /** + * Creates a new ClassName object for the given type. + * + * @param type the type for the new ClassName. If it will represent a class literal, the type is + * declared primitive, void, or array of one of them. If it represents part of a static + * field access or static method invocation, the type is declared, type variable, or array + * (including array of primitive). + */ + public ClassName(TypeMirror type) { + super(type); + String typeString = type.toString(); + if (typeString.endsWith(">")) { + typeString = typeString.substring(0, typeString.indexOf("<")); + } + this.typeString = typeString; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ClassName)) { + return false; + } + ClassName other = (ClassName) obj; + return typeString.equals(other.typeString); + } + + @Override + public int hashCode() { + return Objects.hash(typeString); + } + + @Override + public String toString() { + return typeString + ".class"; } - this.typeString = typeString; - } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ClassName)) { - return false; + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; } - ClassName other = (ClassName) obj; - return typeString.equals(other.typeString); - } - - @Override - public int hashCode() { - return Objects.hash(typeString); - } - - @Override - public String toString() { - return typeString + ".class"; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return true; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof ClassName)) { - return false; + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof ClassName)) { + return false; + } + ClassName other = (ClassName) je; + return typeString.equals(other.typeString); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return false; // not modifiable + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitClassName(this, p); } - ClassName other = (ClassName) je; - return typeString.equals(other.typeString); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return false; // not modifiable - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitClassName(this, p); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java index e1bb0559fbe..9af3750d15f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FieldAccess.java @@ -1,9 +1,7 @@ package org.checkerframework.dataflow.expression; import com.sun.tools.javac.code.Symbol; -import java.util.Objects; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.FieldAccessNode; @@ -12,164 +10,169 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.Objects; + +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + /** * A FieldAccess represents a field access. It does not represent a class literal such as {@code * SomeClass.class} or {@code int[].class}. */ public class FieldAccess extends JavaExpression { - /** The receiver of the field access. */ - protected final JavaExpression receiver; - - /** The field being accessed. */ - protected final VariableElement field; - - /** - * Returns the receiver. - * - * @return the receiver - */ - public JavaExpression getReceiver() { - return receiver; - } - - /** - * Returns the field. - * - * @return the field - */ - public VariableElement getField() { - return field; - } - - /** - * Create a {@code FieldAccess}. - * - * @param receiver receiver of the field access - * @param node the FieldAccessNode - */ - public FieldAccess(JavaExpression receiver, FieldAccessNode node) { - this(receiver, node.getType(), node.getElement()); - } - - /** - * Create a {@code FieldAccess}. - * - * @param receiver receiver of the field access - * @param fieldElement element of the field - */ - public FieldAccess(JavaExpression receiver, VariableElement fieldElement) { - this(receiver, fieldElement.asType(), fieldElement); - } - - /** - * Create a {@code FieldAccess}. - * - * @param receiver receiver of the field access - * @param type type of the field - * @param fieldElement element of the field - */ - public FieldAccess(JavaExpression receiver, TypeMirror type, VariableElement fieldElement) { - super(type); - this.receiver = receiver; - this.field = fieldElement; - String fieldName = fieldElement.toString(); - if (fieldName.equals("class") || fieldName.equals("this")) { - BugInCF e = - new BugInCF( - String.format( - "bad field name \"%s\" in new FieldAccess(%s, %s, %s)%n", - fieldName, receiver, type, fieldElement)); - e.printStackTrace(System.out); - e.printStackTrace(System.err); - throw e; - } - } - - public boolean isFinal() { - return ElementUtils.isFinal(field); - } - - public boolean isStatic() { - return ElementUtils.isStatic(field); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FieldAccess)) { - return false; - } - FieldAccess fa = (FieldAccess) obj; - return fa.getField().equals(getField()) && fa.getReceiver().equals(getReceiver()); - } - - @Override - public int hashCode() { - return Objects.hash(getField(), getReceiver()); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof FieldAccess)) { - return false; - } - FieldAccess other = (FieldAccess) je; - return this.receiver.syntacticEquals(other.receiver) && this.field.equals(other.field); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) || receiver.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return super.containsModifiableAliasOf(store, other) - || receiver.containsModifiableAliasOf(store, other); - } - - @Override - public String toString() { - if (receiver instanceof ClassName) { - return receiver.getType() + "." + field; - } else { - return receiver + "." + field; - } - } - - @Override - public String toStringDebug() { - return String.format( - "FieldAccess(type=%s, receiver=%s, field=%s [%s] [%s] owner=%s)", - type, - receiver.toStringDebug(), - field, - field.getClass().getSimpleName(), - System.identityHashCode(field), - ((Symbol) field).owner); - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz || receiver.containsOfClass(clazz); - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return receiver.isDeterministic(provider); - } - - @Override - public boolean isUnassignableByOtherCode() { - return isFinal() && getReceiver().isUnassignableByOtherCode(); - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return isUnassignableByOtherCode() && TypesUtils.isImmutableTypeInJdk(getReceiver().type); - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitFieldAccess(this, p); - } + /** The receiver of the field access. */ + protected final JavaExpression receiver; + + /** The field being accessed. */ + protected final VariableElement field; + + /** + * Returns the receiver. + * + * @return the receiver + */ + public JavaExpression getReceiver() { + return receiver; + } + + /** + * Returns the field. + * + * @return the field + */ + public VariableElement getField() { + return field; + } + + /** + * Create a {@code FieldAccess}. + * + * @param receiver receiver of the field access + * @param node the FieldAccessNode + */ + public FieldAccess(JavaExpression receiver, FieldAccessNode node) { + this(receiver, node.getType(), node.getElement()); + } + + /** + * Create a {@code FieldAccess}. + * + * @param receiver receiver of the field access + * @param fieldElement element of the field + */ + public FieldAccess(JavaExpression receiver, VariableElement fieldElement) { + this(receiver, fieldElement.asType(), fieldElement); + } + + /** + * Create a {@code FieldAccess}. + * + * @param receiver receiver of the field access + * @param type type of the field + * @param fieldElement element of the field + */ + public FieldAccess(JavaExpression receiver, TypeMirror type, VariableElement fieldElement) { + super(type); + this.receiver = receiver; + this.field = fieldElement; + String fieldName = fieldElement.toString(); + if (fieldName.equals("class") || fieldName.equals("this")) { + BugInCF e = + new BugInCF( + String.format( + "bad field name \"%s\" in new FieldAccess(%s, %s, %s)%n", + fieldName, receiver, type, fieldElement)); + e.printStackTrace(System.out); + e.printStackTrace(System.err); + throw e; + } + } + + public boolean isFinal() { + return ElementUtils.isFinal(field); + } + + public boolean isStatic() { + return ElementUtils.isStatic(field); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FieldAccess)) { + return false; + } + FieldAccess fa = (FieldAccess) obj; + return fa.getField().equals(getField()) && fa.getReceiver().equals(getReceiver()); + } + + @Override + public int hashCode() { + return Objects.hash(getField(), getReceiver()); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof FieldAccess)) { + return false; + } + FieldAccess other = (FieldAccess) je; + return this.receiver.syntacticEquals(other.receiver) && this.field.equals(other.field); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) || receiver.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return super.containsModifiableAliasOf(store, other) + || receiver.containsModifiableAliasOf(store, other); + } + + @Override + public String toString() { + if (receiver instanceof ClassName) { + return receiver.getType() + "." + field; + } else { + return receiver + "." + field; + } + } + + @Override + public String toStringDebug() { + return String.format( + "FieldAccess(type=%s, receiver=%s, field=%s [%s] [%s] owner=%s)", + type, + receiver.toStringDebug(), + field, + field.getClass().getSimpleName(), + System.identityHashCode(field), + ((Symbol) field).owner); + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz || receiver.containsOfClass(clazz); + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return receiver.isDeterministic(provider); + } + + @Override + public boolean isUnassignableByOtherCode() { + return isFinal() && getReceiver().isUnassignableByOtherCode(); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return isUnassignableByOtherCode() && TypesUtils.isImmutableTypeInJdk(getReceiver().type); + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitFieldAccess(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java index 6a058504bca..1b5694925b3 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/FormalParameter.java @@ -1,13 +1,16 @@ package org.checkerframework.dataflow.expression; import com.sun.tools.javac.code.Symbol.VarSymbol; -import java.util.Objects; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypeAnnotationUtils; +import java.util.Objects; + +import javax.lang.model.element.VariableElement; + /** * A formal parameter, represented by its 1-based index. * @@ -15,113 +18,113 @@ */ public class FormalParameter extends JavaExpression { - /** The 1-based index. */ - protected final int index; - - /** The element for this formal parameter. */ - protected final VariableElement element; - - /** - * Creates a FormalParameter. - * - * @param index the 1-based index - * @param element the element for the formal parameter - */ - public FormalParameter(int index, VariableElement element) { - super(ElementUtils.getType(element)); - this.index = index; - this.element = element; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof FormalParameter)) { - return false; + /** The 1-based index. */ + protected final int index; + + /** The element for this formal parameter. */ + protected final VariableElement element; + + /** + * Creates a FormalParameter. + * + * @param index the 1-based index + * @param element the element for the formal parameter + */ + public FormalParameter(int index, VariableElement element) { + super(ElementUtils.getType(element)); + this.index = index; + this.element = element; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof FormalParameter)) { + return false; + } + + FormalParameter other = (FormalParameter) obj; + return this.index == other.index && LocalVariable.sameElement(this.element, other.element); + } + + /** + * Returns the 1-based index of this formal parameter. + * + * @return the 1-based index of this formal parameter + */ + public int getIndex() { + return index; + } + + /** + * Returns the element for this variable. + * + * @return the element for this variable + */ + public VariableElement getElement() { + return element; + } + + @Override + public int hashCode() { + VarSymbol vs = (VarSymbol) element; + return Objects.hash( + index, + vs.name.toString(), + TypeAnnotationUtils.unannotatedType(vs.type).toString(), + vs.owner.toString()); + } + + @Override + public String toString() { + return "#" + index; + } + + @Override + public String toStringDebug() { + return super.toStringDebug() + + " [element=" + + element + + ", owner=" + + ((VarSymbol) element).owner + + "]"; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof FormalParameter)) { + return false; + } + FormalParameter other = (FormalParameter) je; + return index == other.index; + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other); + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; } - FormalParameter other = (FormalParameter) obj; - return this.index == other.index && LocalVariable.sameElement(this.element, other.element); - } - - /** - * Returns the 1-based index of this formal parameter. - * - * @return the 1-based index of this formal parameter - */ - public int getIndex() { - return index; - } - - /** - * Returns the element for this variable. - * - * @return the element for this variable - */ - public VariableElement getElement() { - return element; - } - - @Override - public int hashCode() { - VarSymbol vs = (VarSymbol) element; - return Objects.hash( - index, - vs.name.toString(), - TypeAnnotationUtils.unannotatedType(vs.type).toString(), - vs.owner.toString()); - } - - @Override - public String toString() { - return "#" + index; - } - - @Override - public String toStringDebug() { - return super.toStringDebug() - + " [element=" - + element - + ", owner=" - + ((VarSymbol) element).owner - + "]"; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof FormalParameter)) { - return false; + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitFormalParameter(this, p); } - FormalParameter other = (FormalParameter) je; - return index == other.index; - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other); - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return true; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitFormalParameter(this, p); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java index d62910f7ce8..d68840d9085 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java @@ -14,16 +14,7 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -52,6 +43,18 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + // The Lock Checker also supports "" as a JavaExpression, but that is implemented in the Lock // Checker. // There are no special subclasses (AST nodes) for "". @@ -72,748 +75,760 @@ * Java expressions supported by the Checker Framework */ public abstract class JavaExpression { - /** The type of this expression. */ - protected final TypeMirror type; - - /** - * Create a JavaExpression. - * - * @param type the type of the expression - */ - protected JavaExpression(TypeMirror type) { - assert type != null; - this.type = type; - } - - public TypeMirror getType() { - return type; - } - - @Pure - public abstract boolean containsOfClass(Class clazz); - - @Pure - public boolean containsUnknown() { - return containsOfClass(Unknown.class); - } - - /** - * Returns true if the expression is deterministic. - * - * @param provider an annotation provider (a type factory) - * @return true if this expression is deterministic - */ - @Pure - public abstract boolean isDeterministic(AnnotationProvider provider); - - /** - * Returns true if all the expressions in the list are deterministic. - * - * @param list the list whose elements to test - * @param provider an annotation provider (a type factory) - * @return true if all the expressions in the list are deterministic - */ - @Pure - public static boolean listIsDeterministic( - List list, AnnotationProvider provider) { - return list.stream().allMatch(je -> je == null || je.isDeterministic(provider)); - } - - /** - * Returns true if and only if the value this expression stands for cannot be changed (with - * respect to ==) by a method call. This is the case for local variables, the self reference, - * final field accesses whose receiver is {@link #isUnassignableByOtherCode}, and operations whose - * operands are all {@link #isUnmodifiableByOtherCode}. - * - * @see #isUnmodifiableByOtherCode - */ - @Pure - public abstract boolean isUnassignableByOtherCode(); - - /** - * Returns true if and only if the value this expression stands for cannot be changed by a method - * call, including changes to any of its fields. - * - *

          Approximately, this returns true if the expression is {@link #isUnassignableByOtherCode} and - * its type is immutable. - * - * @see #isUnassignableByOtherCode - */ - @Pure - public abstract boolean isUnmodifiableByOtherCode(); - - /** - * Returns true if and only if the two Java expressions are syntactically identical. - * - *

          This exists for use by {@link #containsSyntacticEqualJavaExpression}. - * - * @param je the other Java expression to compare to this one - * @return true if and only if the two Java expressions are syntactically identical - */ - @EqualsMethod - @Pure - public abstract boolean syntacticEquals(JavaExpression je); - - /** - * Returns true if the corresponding list elements satisfy {@link #syntacticEquals}. - * - * @param lst1 the first list to compare - * @param lst2 the second list to compare - * @return true if the corresponding list elements satisfy {@link #syntacticEquals} - */ - @Pure - public static boolean syntacticEqualsList( - List lst1, - List lst2) { - if (lst1.size() != lst2.size()) { - return false; + /** The type of this expression. */ + protected final TypeMirror type; + + /** + * Create a JavaExpression. + * + * @param type the type of the expression + */ + protected JavaExpression(TypeMirror type) { + assert type != null; + this.type = type; } - for (int i = 0; i < lst1.size(); i++) { - JavaExpression dim1 = lst1.get(i); - JavaExpression dim2 = lst2.get(i); - if (dim1 == null && dim2 == null) { - // Continue to next index. - } else if (dim1 == null || dim2 == null) { - return false; - } else { - if (!dim1.syntacticEquals(dim2)) { - return false; - } - } + + public TypeMirror getType() { + return type; } - return true; - } - - /** - * Returns true if and only if this contains a JavaExpression that is syntactically equal to - * {@code other}. - * - * @param other the JavaExpression to search for - * @return true if and only if this contains a JavaExpression that is syntactically equal to - * {@code other} - */ - @Pure - public abstract boolean containsSyntacticEqualJavaExpression(JavaExpression other); - - /** - * Returns true if the given list contains a JavaExpression that is syntactically equal to {@code - * other}. - * - * @param list the list in which to search for a match - * @param other the JavaExpression to search for - * @return true if and only if the list contains a JavaExpression that is syntactically equal to - * {@code other} - */ - @Pure - public static boolean listContainsSyntacticEqualJavaExpression( - List list, JavaExpression other) { - return list.stream() - .anyMatch(je -> je != null && je.containsSyntacticEqualJavaExpression(other)); - } - - /** - * Returns true if and only if {@code other} appears anywhere in this or an expression appears in - * this such that {@code other} might alias this expression, and that expression is modifiable. - * - *

          This is always true, except for cases where the Java type information prevents aliasing and - * none of the subexpressions can alias 'other'. - */ - @Pure - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return this.equals(other) || store.canAlias(this, other); - } - - /** - * Format this verbosely, for debugging. - * - * @return a verbose string representation of this - */ - @Pure - public String toStringDebug() { - return String.format("%s(%s): %s", getClass().getSimpleName(), type, toString()); - } - - /// - /// Static methods - /// - - /** - * Returns the Java expression for a {@link FieldAccessNode}. The result may contain {@link - * Unknown} as receiver. - * - * @param node the FieldAccessNode to convert to a JavaExpression - * @return the {@link FieldAccess} or {@link ClassName} that corresponds to {@code node} - */ - public static JavaExpression fromNodeFieldAccess(FieldAccessNode node) { - Node receiverNode = node.getReceiver(); - String fieldName = node.getFieldName(); - if (fieldName.equals("this")) { - // The CFG represents "className.this" as a FieldAccessNode, but it isn't a field - // access. - return new ThisReference(receiverNode.getType()); - } else if (fieldName.equals("class")) { - // The CFG represents "className.class" as a FieldAccessNode; bit it is a class literal. - return new ClassName(receiverNode.getType()); + + @Pure + public abstract boolean containsOfClass(Class clazz); + + @Pure + public boolean containsUnknown() { + return containsOfClass(Unknown.class); } - JavaExpression receiver; - if (node.isStatic()) { - receiver = new ClassName(receiverNode.getType()); - } else { - receiver = fromNode(receiverNode); + + /** + * Returns true if the expression is deterministic. + * + * @param provider an annotation provider (a type factory) + * @return true if this expression is deterministic + */ + @Pure + public abstract boolean isDeterministic(AnnotationProvider provider); + + /** + * Returns true if all the expressions in the list are deterministic. + * + * @param list the list whose elements to test + * @param provider an annotation provider (a type factory) + * @return true if all the expressions in the list are deterministic + */ + @Pure + public static boolean listIsDeterministic( + List list, AnnotationProvider provider) { + return list.stream().allMatch(je -> je == null || je.isDeterministic(provider)); } - return new FieldAccess(receiver, node); - } - - /** - * Returns the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. The - * result may contain {@link Unknown} as receiver. - * - * @param node the ArrayAccessNode to convert to a JavaExpression - * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. Can - * contain {@link Unknown} as receiver. - */ - public static ArrayAccess fromArrayAccess(ArrayAccessNode node) { - JavaExpression array = fromNode(node.getArray()); - JavaExpression index = fromNode(node.getIndex()); - return new ArrayAccess(node.getType(), array, index); - } - - /** - * We ignore operations such as widening and narrowing when computing the internal representation. - * - * @param receiverNode a node to convert to a JavaExpression - * @return the internal representation of the given node. Might contain {@link Unknown}. - */ - public static JavaExpression fromNode(Node receiverNode) { - JavaExpression result = null; - if (receiverNode instanceof FieldAccessNode) { - result = fromNodeFieldAccess((FieldAccessNode) receiverNode); - } else if (receiverNode instanceof ThisNode) { - result = new ThisReference(receiverNode.getType()); - } else if (receiverNode instanceof SuperNode) { - result = new ThisReference(receiverNode.getType()); - } else if (receiverNode instanceof LocalVariableNode) { - LocalVariableNode lv = (LocalVariableNode) receiverNode; - result = new LocalVariable(lv); - } else if (receiverNode instanceof ArrayAccessNode) { - ArrayAccessNode a = (ArrayAccessNode) receiverNode; - result = fromArrayAccess(a); - } else if (receiverNode instanceof StringConversionNode) { - // ignore string conversion - return fromNode(((StringConversionNode) receiverNode).getOperand()); - } else if (receiverNode instanceof WideningConversionNode) { - // ignore widening - return fromNode(((WideningConversionNode) receiverNode).getOperand()); - } else if (receiverNode instanceof NarrowingConversionNode) { - // ignore narrowing - return fromNode(((NarrowingConversionNode) receiverNode).getOperand()); - } else if (receiverNode instanceof UnaryOperationNode) { - UnaryOperationNode uopn = (UnaryOperationNode) receiverNode; - return new UnaryOperation(uopn, fromNode(uopn.getOperand())); - } else if (receiverNode instanceof BinaryOperationNode) { - BinaryOperationNode bopn = (BinaryOperationNode) receiverNode; - return new BinaryOperation( - bopn, fromNode(bopn.getLeftOperand()), fromNode(bopn.getRightOperand())); - } else if (receiverNode instanceof ClassNameNode) { - ClassNameNode cn = (ClassNameNode) receiverNode; - result = new ClassName(cn.getType()); - } else if (receiverNode instanceof ValueLiteralNode) { - ValueLiteralNode vn = (ValueLiteralNode) receiverNode; - result = new ValueLiteral(vn.getType(), vn); - } else if (receiverNode instanceof ArrayCreationNode) { - ArrayCreationNode an = (ArrayCreationNode) receiverNode; - List<@Nullable JavaExpression> dimensions = - CollectionsPlume.mapList(JavaExpression::fromNode, an.getDimensions()); - List initializers = - CollectionsPlume.mapList(JavaExpression::fromNode, an.getInitializers()); - result = new ArrayCreation(an.getType(), dimensions, initializers); - } else if (receiverNode instanceof MethodInvocationNode) { - MethodInvocationNode mn = (MethodInvocationNode) receiverNode; - MethodInvocationTree t = mn.getTree(); - if (t == null) { - throw new BugInCF("Unexpected null tree for node: " + mn); - } - assert TreeUtils.isUseOfElement(t) : "@AssumeAssertion(nullness): tree kind"; - ExecutableElement invokedMethod = TreeUtils.elementFromUse(t); - - // Note that the method might be nondeterministic. - List parameters = - CollectionsPlume.mapList(JavaExpression::fromNode, mn.getArguments()); - JavaExpression methodReceiver; - if (ElementUtils.isStatic(invokedMethod)) { - methodReceiver = new ClassName(mn.getTarget().getReceiver().getType()); - } else { - methodReceiver = fromNode(mn.getTarget().getReceiver()); - } - result = new MethodCall(mn.getType(), invokedMethod, methodReceiver, parameters); + + /** + * Returns true if and only if the value this expression stands for cannot be changed (with + * respect to ==) by a method call. This is the case for local variables, the self reference, + * final field accesses whose receiver is {@link #isUnassignableByOtherCode}, and operations + * whose operands are all {@link #isUnmodifiableByOtherCode}. + * + * @see #isUnmodifiableByOtherCode + */ + @Pure + public abstract boolean isUnassignableByOtherCode(); + + /** + * Returns true if and only if the value this expression stands for cannot be changed by a + * method call, including changes to any of its fields. + * + *

          Approximately, this returns true if the expression is {@link #isUnassignableByOtherCode} + * and its type is immutable. + * + * @see #isUnassignableByOtherCode + */ + @Pure + public abstract boolean isUnmodifiableByOtherCode(); + + /** + * Returns true if and only if the two Java expressions are syntactically identical. + * + *

          This exists for use by {@link #containsSyntacticEqualJavaExpression}. + * + * @param je the other Java expression to compare to this one + * @return true if and only if the two Java expressions are syntactically identical + */ + @EqualsMethod + @Pure + public abstract boolean syntacticEquals(JavaExpression je); + + /** + * Returns true if the corresponding list elements satisfy {@link #syntacticEquals}. + * + * @param lst1 the first list to compare + * @param lst2 the second list to compare + * @return true if the corresponding list elements satisfy {@link #syntacticEquals} + */ + @Pure + public static boolean syntacticEqualsList( + List lst1, + List lst2) { + if (lst1.size() != lst2.size()) { + return false; + } + for (int i = 0; i < lst1.size(); i++) { + JavaExpression dim1 = lst1.get(i); + JavaExpression dim2 = lst2.get(i); + if (dim1 == null && dim2 == null) { + // Continue to next index. + } else if (dim1 == null || dim2 == null) { + return false; + } else { + if (!dim1.syntacticEquals(dim2)) { + return false; + } + } + } + return true; } - if (result == null) { - result = new Unknown(receiverNode); + /** + * Returns true if and only if this contains a JavaExpression that is syntactically equal to + * {@code other}. + * + * @param other the JavaExpression to search for + * @return true if and only if this contains a JavaExpression that is syntactically equal to + * {@code other} + */ + @Pure + public abstract boolean containsSyntacticEqualJavaExpression(JavaExpression other); + + /** + * Returns true if the given list contains a JavaExpression that is syntactically equal to + * {@code other}. + * + * @param list the list in which to search for a match + * @param other the JavaExpression to search for + * @return true if and only if the list contains a JavaExpression that is syntactically equal to + * {@code other} + */ + @Pure + public static boolean listContainsSyntacticEqualJavaExpression( + List list, JavaExpression other) { + return list.stream() + .anyMatch(je -> je != null && je.containsSyntacticEqualJavaExpression(other)); } - return result; - } - - /** - * Converts a javac {@link ExpressionTree} to a CF JavaExpression. The result might contain {@link - * Unknown}. - * - *

          We ignore operations such as widening and narrowing when computing the JavaExpression. - * - * @param tree a javac tree - * @return a JavaExpression for the given javac tree - */ - public static JavaExpression fromTree(ExpressionTree tree) { - JavaExpression result; - switch (tree.getKind()) { - case ARRAY_ACCESS: - ArrayAccessTree a = (ArrayAccessTree) tree; - JavaExpression arrayAccessExpression = fromTree(a.getExpression()); - JavaExpression index = fromTree(a.getIndex()); - result = new ArrayAccess(TreeUtils.typeOf(a), arrayAccessExpression, index); - break; - - case BOOLEAN_LITERAL: - case CHAR_LITERAL: - case DOUBLE_LITERAL: - case FLOAT_LITERAL: - case INT_LITERAL: - case LONG_LITERAL: - case NULL_LITERAL: - case STRING_LITERAL: - LiteralTree vn = (LiteralTree) tree; - result = new ValueLiteral(TreeUtils.typeOf(tree), vn.getValue()); - break; - - case NEW_ARRAY: - NewArrayTree newArrayTree = (NewArrayTree) tree; - List<@Nullable JavaExpression> dimensions; - if (newArrayTree.getDimensions() == null) { - dimensions = Collections.emptyList(); - } else { - dimensions = new ArrayList<>(newArrayTree.getDimensions().size()); - for (ExpressionTree dimension : newArrayTree.getDimensions()) { - dimensions.add(fromTree(dimension)); - } + + /** + * Returns true if and only if {@code other} appears anywhere in this or an expression appears + * in this such that {@code other} might alias this expression, and that expression is + * modifiable. + * + *

          This is always true, except for cases where the Java type information prevents aliasing + * and none of the subexpressions can alias 'other'. + */ + @Pure + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return this.equals(other) || store.canAlias(this, other); + } + + /** + * Format this verbosely, for debugging. + * + * @return a verbose string representation of this + */ + @Pure + public String toStringDebug() { + return String.format("%s(%s): %s", getClass().getSimpleName(), type, toString()); + } + + /// + /// Static methods + /// + + /** + * Returns the Java expression for a {@link FieldAccessNode}. The result may contain {@link + * Unknown} as receiver. + * + * @param node the FieldAccessNode to convert to a JavaExpression + * @return the {@link FieldAccess} or {@link ClassName} that corresponds to {@code node} + */ + public static JavaExpression fromNodeFieldAccess(FieldAccessNode node) { + Node receiverNode = node.getReceiver(); + String fieldName = node.getFieldName(); + if (fieldName.equals("this")) { + // The CFG represents "className.this" as a FieldAccessNode, but it isn't a field + // access. + return new ThisReference(receiverNode.getType()); + } else if (fieldName.equals("class")) { + // The CFG represents "className.class" as a FieldAccessNode; bit it is a class literal. + return new ClassName(receiverNode.getType()); } - List initializers; - if (newArrayTree.getInitializers() == null) { - initializers = Collections.emptyList(); + JavaExpression receiver; + if (node.isStatic()) { + receiver = new ClassName(receiverNode.getType()); } else { - initializers = new ArrayList<>(newArrayTree.getInitializers().size()); - for (ExpressionTree initializer : newArrayTree.getInitializers()) { - initializers.add(fromTree(initializer)); - } + receiver = fromNode(receiverNode); } + return new FieldAccess(receiver, node); + } - result = new ArrayCreation(TreeUtils.typeOf(tree), dimensions, initializers); - break; - - case METHOD_INVOCATION: - MethodInvocationTree mn = (MethodInvocationTree) tree; - assert TreeUtils.isUseOfElement(mn) : "@AssumeAssertion(nullness): tree kind"; - ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn); - - // Note that the method might be nondeterministic. - List parameters = - CollectionsPlume.mapList(JavaExpression::fromTree, mn.getArguments()); - JavaExpression methodReceiver; - if (ElementUtils.isStatic(invokedMethod)) { - @SuppressWarnings("nullness:assignment" // enclosingTypeElement(ExecutableElement): - // @NonNull - ) - @NonNull TypeElement methodType = ElementUtils.enclosingTypeElement(invokedMethod); - methodReceiver = new ClassName(methodType.asType()); - } else { - methodReceiver = getReceiver(mn); - } - TypeMirror resultType = TreeUtils.typeOf(mn); - result = new MethodCall(resultType, invokedMethod, methodReceiver, parameters); - break; - - case MEMBER_SELECT: - result = fromMemberSelect((MemberSelectTree) tree); - break; - - case IDENTIFIER: - IdentifierTree identifierTree = (IdentifierTree) tree; - TypeMirror typeOfId = TreeUtils.typeOf(identifierTree); - Name identifierName = identifierTree.getName(); - if (identifierName.contentEquals("this") || identifierName.contentEquals("super")) { - result = new ThisReference(typeOfId); - break; + /** + * Returns the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. + * The result may contain {@link Unknown} as receiver. + * + * @param node the ArrayAccessNode to convert to a JavaExpression + * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. + * Can contain {@link Unknown} as receiver. + */ + public static ArrayAccess fromArrayAccess(ArrayAccessNode node) { + JavaExpression array = fromNode(node.getArray()); + JavaExpression index = fromNode(node.getIndex()); + return new ArrayAccess(node.getType(), array, index); + } + + /** + * We ignore operations such as widening and narrowing when computing the internal + * representation. + * + * @param receiverNode a node to convert to a JavaExpression + * @return the internal representation of the given node. Might contain {@link Unknown}. + */ + public static JavaExpression fromNode(Node receiverNode) { + JavaExpression result = null; + if (receiverNode instanceof FieldAccessNode) { + result = fromNodeFieldAccess((FieldAccessNode) receiverNode); + } else if (receiverNode instanceof ThisNode) { + result = new ThisReference(receiverNode.getType()); + } else if (receiverNode instanceof SuperNode) { + result = new ThisReference(receiverNode.getType()); + } else if (receiverNode instanceof LocalVariableNode) { + LocalVariableNode lv = (LocalVariableNode) receiverNode; + result = new LocalVariable(lv); + } else if (receiverNode instanceof ArrayAccessNode) { + ArrayAccessNode a = (ArrayAccessNode) receiverNode; + result = fromArrayAccess(a); + } else if (receiverNode instanceof StringConversionNode) { + // ignore string conversion + return fromNode(((StringConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof WideningConversionNode) { + // ignore widening + return fromNode(((WideningConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof NarrowingConversionNode) { + // ignore narrowing + return fromNode(((NarrowingConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof UnaryOperationNode) { + UnaryOperationNode uopn = (UnaryOperationNode) receiverNode; + return new UnaryOperation(uopn, fromNode(uopn.getOperand())); + } else if (receiverNode instanceof BinaryOperationNode) { + BinaryOperationNode bopn = (BinaryOperationNode) receiverNode; + return new BinaryOperation( + bopn, fromNode(bopn.getLeftOperand()), fromNode(bopn.getRightOperand())); + } else if (receiverNode instanceof ClassNameNode) { + ClassNameNode cn = (ClassNameNode) receiverNode; + result = new ClassName(cn.getType()); + } else if (receiverNode instanceof ValueLiteralNode) { + ValueLiteralNode vn = (ValueLiteralNode) receiverNode; + result = new ValueLiteral(vn.getType(), vn); + } else if (receiverNode instanceof ArrayCreationNode) { + ArrayCreationNode an = (ArrayCreationNode) receiverNode; + List<@Nullable JavaExpression> dimensions = + CollectionsPlume.mapList(JavaExpression::fromNode, an.getDimensions()); + List initializers = + CollectionsPlume.mapList(JavaExpression::fromNode, an.getInitializers()); + result = new ArrayCreation(an.getType(), dimensions, initializers); + } else if (receiverNode instanceof MethodInvocationNode) { + MethodInvocationNode mn = (MethodInvocationNode) receiverNode; + MethodInvocationTree t = mn.getTree(); + if (t == null) { + throw new BugInCF("Unexpected null tree for node: " + mn); + } + assert TreeUtils.isUseOfElement(t) : "@AssumeAssertion(nullness): tree kind"; + ExecutableElement invokedMethod = TreeUtils.elementFromUse(t); + + // Note that the method might be nondeterministic. + List parameters = + CollectionsPlume.mapList(JavaExpression::fromNode, mn.getArguments()); + JavaExpression methodReceiver; + if (ElementUtils.isStatic(invokedMethod)) { + methodReceiver = new ClassName(mn.getTarget().getReceiver().getType()); + } else { + methodReceiver = fromNode(mn.getTarget().getReceiver()); + } + result = new MethodCall(mn.getType(), invokedMethod, methodReceiver, parameters); } - assert TreeUtils.isUseOfElement(identifierTree) : "@AssumeAssertion(nullness): tree kind"; - Element ele = TreeUtils.elementFromUse(identifierTree); - if (ele == null) { - result = null; - } else if (ElementUtils.isTypeElement(ele)) { - result = new ClassName(ele.asType()); - } else { - result = fromVariableElement(typeOfId, (VariableElement) ele, identifierTree); + + if (result == null) { + result = new Unknown(receiverNode); } - break; - - case UNARY_PLUS: - return fromTree(((UnaryTree) tree).getExpression()); - case BITWISE_COMPLEMENT: - case LOGICAL_COMPLEMENT: - case POSTFIX_DECREMENT: - case POSTFIX_INCREMENT: - case PREFIX_DECREMENT: - case PREFIX_INCREMENT: - case UNARY_MINUS: - JavaExpression operand = fromTree(((UnaryTree) tree).getExpression()); - return new UnaryOperation(TreeUtils.typeOf(tree), tree.getKind(), operand); - - case CONDITIONAL_AND: - case CONDITIONAL_OR: - case DIVIDE: - case EQUAL_TO: - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case LEFT_SHIFT: - case LESS_THAN: - case LESS_THAN_EQUAL: - case MINUS: - case MULTIPLY: - case NOT_EQUAL_TO: - case OR: - case PLUS: - case REMAINDER: - case RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT: - case XOR: - BinaryTree binaryTree = (BinaryTree) tree; - JavaExpression left = fromTree(binaryTree.getLeftOperand()); - JavaExpression right = fromTree(binaryTree.getRightOperand()); - return new BinaryOperation(TreeUtils.typeOf(tree), tree.getKind(), left, right); - - default: - result = null; + return result; } - if (result == null) { - result = new Unknown(tree); + /** + * Converts a javac {@link ExpressionTree} to a CF JavaExpression. The result might contain + * {@link Unknown}. + * + *

          We ignore operations such as widening and narrowing when computing the JavaExpression. + * + * @param tree a javac tree + * @return a JavaExpression for the given javac tree + */ + public static JavaExpression fromTree(ExpressionTree tree) { + JavaExpression result; + switch (tree.getKind()) { + case ARRAY_ACCESS: + ArrayAccessTree a = (ArrayAccessTree) tree; + JavaExpression arrayAccessExpression = fromTree(a.getExpression()); + JavaExpression index = fromTree(a.getIndex()); + result = new ArrayAccess(TreeUtils.typeOf(a), arrayAccessExpression, index); + break; + + case BOOLEAN_LITERAL: + case CHAR_LITERAL: + case DOUBLE_LITERAL: + case FLOAT_LITERAL: + case INT_LITERAL: + case LONG_LITERAL: + case NULL_LITERAL: + case STRING_LITERAL: + LiteralTree vn = (LiteralTree) tree; + result = new ValueLiteral(TreeUtils.typeOf(tree), vn.getValue()); + break; + + case NEW_ARRAY: + NewArrayTree newArrayTree = (NewArrayTree) tree; + List<@Nullable JavaExpression> dimensions; + if (newArrayTree.getDimensions() == null) { + dimensions = Collections.emptyList(); + } else { + dimensions = new ArrayList<>(newArrayTree.getDimensions().size()); + for (ExpressionTree dimension : newArrayTree.getDimensions()) { + dimensions.add(fromTree(dimension)); + } + } + List initializers; + if (newArrayTree.getInitializers() == null) { + initializers = Collections.emptyList(); + } else { + initializers = new ArrayList<>(newArrayTree.getInitializers().size()); + for (ExpressionTree initializer : newArrayTree.getInitializers()) { + initializers.add(fromTree(initializer)); + } + } + + result = new ArrayCreation(TreeUtils.typeOf(tree), dimensions, initializers); + break; + + case METHOD_INVOCATION: + MethodInvocationTree mn = (MethodInvocationTree) tree; + assert TreeUtils.isUseOfElement(mn) : "@AssumeAssertion(nullness): tree kind"; + ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn); + + // Note that the method might be nondeterministic. + List parameters = + CollectionsPlume.mapList(JavaExpression::fromTree, mn.getArguments()); + JavaExpression methodReceiver; + if (ElementUtils.isStatic(invokedMethod)) { + @SuppressWarnings( + "nullness:assignment" // enclosingTypeElement(ExecutableElement): + // @NonNull + ) + @NonNull TypeElement methodType = + ElementUtils.enclosingTypeElement(invokedMethod); + methodReceiver = new ClassName(methodType.asType()); + } else { + methodReceiver = getReceiver(mn); + } + TypeMirror resultType = TreeUtils.typeOf(mn); + result = new MethodCall(resultType, invokedMethod, methodReceiver, parameters); + break; + + case MEMBER_SELECT: + result = fromMemberSelect((MemberSelectTree) tree); + break; + + case IDENTIFIER: + IdentifierTree identifierTree = (IdentifierTree) tree; + TypeMirror typeOfId = TreeUtils.typeOf(identifierTree); + Name identifierName = identifierTree.getName(); + if (identifierName.contentEquals("this") || identifierName.contentEquals("super")) { + result = new ThisReference(typeOfId); + break; + } + assert TreeUtils.isUseOfElement(identifierTree) + : "@AssumeAssertion(nullness): tree kind"; + Element ele = TreeUtils.elementFromUse(identifierTree); + if (ele == null) { + result = null; + } else if (ElementUtils.isTypeElement(ele)) { + result = new ClassName(ele.asType()); + } else { + result = fromVariableElement(typeOfId, (VariableElement) ele, identifierTree); + } + break; + + case UNARY_PLUS: + return fromTree(((UnaryTree) tree).getExpression()); + case BITWISE_COMPLEMENT: + case LOGICAL_COMPLEMENT: + case POSTFIX_DECREMENT: + case POSTFIX_INCREMENT: + case PREFIX_DECREMENT: + case PREFIX_INCREMENT: + case UNARY_MINUS: + JavaExpression operand = fromTree(((UnaryTree) tree).getExpression()); + return new UnaryOperation(TreeUtils.typeOf(tree), tree.getKind(), operand); + + case CONDITIONAL_AND: + case CONDITIONAL_OR: + case DIVIDE: + case EQUAL_TO: + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case LEFT_SHIFT: + case LESS_THAN: + case LESS_THAN_EQUAL: + case MINUS: + case MULTIPLY: + case NOT_EQUAL_TO: + case OR: + case PLUS: + case REMAINDER: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + case XOR: + BinaryTree binaryTree = (BinaryTree) tree; + JavaExpression left = fromTree(binaryTree.getLeftOperand()); + JavaExpression right = fromTree(binaryTree.getRightOperand()); + return new BinaryOperation(TreeUtils.typeOf(tree), tree.getKind(), left, right); + + default: + result = null; + } + + if (result == null) { + result = new Unknown(tree); + } + return result; } - return result; - } - - /** - * Returns the Java expression corresponding to the given variable tree {@code tree}. - * - * @param tree a variable tree - * @return a JavaExpression for {@code tree} - */ - public static JavaExpression fromVariableTree(VariableTree tree) { - return fromVariableElement( - TreeUtils.typeOf(tree), TreeUtils.elementFromDeclaration(tree), tree); - } - - /** - * Returns the Java expression corresponding to the given variable element {@code ele}. - * - * @param typeOfEle the type of {@code ele} - * @param ele element whose JavaExpression is returned - * @param tree the tree for the variable - * @return the Java expression corresponding to the given variable element {@code ele} - */ - private static JavaExpression fromVariableElement( - TypeMirror typeOfEle, @Nullable VariableElement ele, Tree tree) { - if (ele == null) { - return new Unknown(tree); + + /** + * Returns the Java expression corresponding to the given variable tree {@code tree}. + * + * @param tree a variable tree + * @return a JavaExpression for {@code tree} + */ + public static JavaExpression fromVariableTree(VariableTree tree) { + return fromVariableElement( + TreeUtils.typeOf(tree), TreeUtils.elementFromDeclaration(tree), tree); } - switch (ele.getKind()) { - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - case PARAMETER: - return new LocalVariable(ele); - case FIELD: - case ENUM_CONSTANT: - // Implicit access expression, such as "this" or a class name - JavaExpression fieldAccessExpression; - @SuppressWarnings("nullness:dereference.of.nullable") // a field has enclosing class - TypeMirror enclosingTypeElement = ElementUtils.enclosingTypeElement(ele).asType(); - if (ElementUtils.isStatic(ele)) { - fieldAccessExpression = new ClassName(enclosingTypeElement); - } else { - fieldAccessExpression = new ThisReference(enclosingTypeElement); + + /** + * Returns the Java expression corresponding to the given variable element {@code ele}. + * + * @param typeOfEle the type of {@code ele} + * @param ele element whose JavaExpression is returned + * @param tree the tree for the variable + * @return the Java expression corresponding to the given variable element {@code ele} + */ + private static JavaExpression fromVariableElement( + TypeMirror typeOfEle, @Nullable VariableElement ele, Tree tree) { + if (ele == null) { + return new Unknown(tree); } - return new FieldAccess(fieldAccessExpression, typeOfEle, ele); - default: - if (ElementUtils.isBindingVariable(ele)) { - return new LocalVariable(ele); + switch (ele.getKind()) { + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + case PARAMETER: + return new LocalVariable(ele); + case FIELD: + case ENUM_CONSTANT: + // Implicit access expression, such as "this" or a class name + JavaExpression fieldAccessExpression; + @SuppressWarnings("nullness:dereference.of.nullable") // a field has enclosing class + TypeMirror enclosingTypeElement = ElementUtils.enclosingTypeElement(ele).asType(); + if (ElementUtils.isStatic(ele)) { + fieldAccessExpression = new ClassName(enclosingTypeElement); + } else { + fieldAccessExpression = new ThisReference(enclosingTypeElement); + } + return new FieldAccess(fieldAccessExpression, typeOfEle, ele); + default: + if (ElementUtils.isBindingVariable(ele)) { + return new LocalVariable(ele); + } + throw new BugInCF( + "Unexpected kind of VariableTree: kind: %s element: %s", + ele.getKind(), ele); } - throw new BugInCF( - "Unexpected kind of VariableTree: kind: %s element: %s", ele.getKind(), ele); } - } - - /** - * Creates a JavaExpression from the {@code memberSelectTree}. - * - * @param memberSelectTree tree - * @return a JavaExpression for {@code memberSelectTree} - */ - private static JavaExpression fromMemberSelect(MemberSelectTree memberSelectTree) { - TypeMirror expressionType = TreeUtils.typeOf(memberSelectTree.getExpression()); - if (TreeUtils.isClassLiteral(memberSelectTree)) { - // the identifier is "class" - return new ClassName(expressionType); + + /** + * Creates a JavaExpression from the {@code memberSelectTree}. + * + * @param memberSelectTree tree + * @return a JavaExpression for {@code memberSelectTree} + */ + private static JavaExpression fromMemberSelect(MemberSelectTree memberSelectTree) { + TypeMirror expressionType = TreeUtils.typeOf(memberSelectTree.getExpression()); + if (TreeUtils.isClassLiteral(memberSelectTree)) { + // the identifier is "class" + return new ClassName(expressionType); + } + if (TreeUtils.isExplicitThisDereference(memberSelectTree)) { + // the identifier is "class" + return new ThisReference(expressionType); + } + + assert TreeUtils.isUseOfElement(memberSelectTree) : "@AssumeAssertion(nullness): tree kind"; + Element ele = TreeUtils.elementFromUse(memberSelectTree); + if (ElementUtils.isTypeElement(ele)) { + // o instanceof MyClass.InnerClass + // o instanceof MyClass.InnerInterface + TypeMirror selectType = TreeUtils.typeOf(memberSelectTree); + return new ClassName(selectType); + } + switch (ele.getKind()) { + case METHOD: + case CONSTRUCTOR: + return fromTree(memberSelectTree.getExpression()); + case ENUM_CONSTANT: + case FIELD: + TypeMirror fieldType = TreeUtils.typeOf(memberSelectTree); + JavaExpression je = fromTree(memberSelectTree.getExpression()); + return new FieldAccess(je, fieldType, (VariableElement) ele); + default: + throw new BugInCF("Unexpected element kind: %s element: %s", ele.getKind(), ele); + } } - if (TreeUtils.isExplicitThisDereference(memberSelectTree)) { - // the identifier is "class" - return new ThisReference(expressionType); + + /** + * Returns the parameters of {@code methodEle} as {@link LocalVariable}s. + * + * @param methodEle the method element + * @return list of parameters as {@link LocalVariable}s + */ + public static List getParametersAsLocalVariables(ExecutableElement methodEle) { + return CollectionsPlume.mapList(LocalVariable::new, methodEle.getParameters()); } - assert TreeUtils.isUseOfElement(memberSelectTree) : "@AssumeAssertion(nullness): tree kind"; - Element ele = TreeUtils.elementFromUse(memberSelectTree); - if (ElementUtils.isTypeElement(ele)) { - // o instanceof MyClass.InnerClass - // o instanceof MyClass.InnerInterface - TypeMirror selectType = TreeUtils.typeOf(memberSelectTree); - return new ClassName(selectType); + /** + * Returns the parameters of {@code methodEle} as {@link FormalParameter}s. + * + * @param methodEle the method element + * @return list of parameters as {@link FormalParameter}s + */ + public static List getFormalParameters(ExecutableElement methodEle) { + List parameters = new ArrayList<>(methodEle.getParameters().size()); + int oneBasedIndex = 1; + for (VariableElement variableElement : methodEle.getParameters()) { + parameters.add(new FormalParameter(oneBasedIndex, variableElement)); + oneBasedIndex++; + } + return parameters; } - switch (ele.getKind()) { - case METHOD: - case CONSTRUCTOR: - return fromTree(memberSelectTree.getExpression()); - case ENUM_CONSTANT: - case FIELD: - TypeMirror fieldType = TreeUtils.typeOf(memberSelectTree); - JavaExpression je = fromTree(memberSelectTree.getExpression()); - return new FieldAccess(je, fieldType, (VariableElement) ele); - default: - throw new BugInCF("Unexpected element kind: %s element: %s", ele.getKind(), ele); + + /// + /// Obtaining the receiver + /// + + /** + * Returns the receiver of the given invocation. + * + * @param accessTree a method or constructor invocation + * @return the receiver of the given invocation + */ + public static JavaExpression getReceiver(ExpressionTree accessTree) { + // TODO: Handle field accesses too? + assert accessTree instanceof MethodInvocationTree || accessTree instanceof NewClassTree; + ExpressionTree receiverTree = TreeUtils.getReceiverTree(accessTree); + if (receiverTree != null) { + return fromTree(receiverTree); + } else { + Element ele = TreeUtils.elementFromUse(accessTree); + if (ele == null) { + throw new BugInCF("TreeUtils.elementFromUse(" + accessTree + ") => null"); + } + return getImplicitReceiver(ele); + } } - } - - /** - * Returns the parameters of {@code methodEle} as {@link LocalVariable}s. - * - * @param methodEle the method element - * @return list of parameters as {@link LocalVariable}s - */ - public static List getParametersAsLocalVariables(ExecutableElement methodEle) { - return CollectionsPlume.mapList(LocalVariable::new, methodEle.getParameters()); - } - - /** - * Returns the parameters of {@code methodEle} as {@link FormalParameter}s. - * - * @param methodEle the method element - * @return list of parameters as {@link FormalParameter}s - */ - public static List getFormalParameters(ExecutableElement methodEle) { - List parameters = new ArrayList<>(methodEle.getParameters().size()); - int oneBasedIndex = 1; - for (VariableElement variableElement : methodEle.getParameters()) { - parameters.add(new FormalParameter(oneBasedIndex, variableElement)); - oneBasedIndex++; + + /** + * Returns the implicit receiver of ele. + * + *

          Returns either a new ClassName or a new ThisReference depending on whether ele is static + * or not. The passed element must be a field, method, or class. + * + *

          When this returns a ThisReference, its type is the class that declares {@code ele}, which + * is not necessarily the type of {@code this} at the invocation site. + * + * @param ele a field, method, or class + * @return either a new ClassName or a new ThisReference depending on whether ele is static or + * not + */ + public static JavaExpression getImplicitReceiver(Element ele) { + TypeElement enclosingTypeElement = ElementUtils.enclosingTypeElement(ele); + if (enclosingTypeElement == null) { + throw new BugInCF("getImplicitReceiver's arg has no enclosing type: " + ele); + } + TypeMirror enclosingType = enclosingTypeElement.asType(); + if (ElementUtils.isStatic(ele)) { + return new ClassName(enclosingType); + } else { + return new ThisReference(enclosingType); + } } - return parameters; - } - - /// - /// Obtaining the receiver - /// - - /** - * Returns the receiver of the given invocation. - * - * @param accessTree a method or constructor invocation - * @return the receiver of the given invocation - */ - public static JavaExpression getReceiver(ExpressionTree accessTree) { - // TODO: Handle field accesses too? - assert accessTree instanceof MethodInvocationTree || accessTree instanceof NewClassTree; - ExpressionTree receiverTree = TreeUtils.getReceiverTree(accessTree); - if (receiverTree != null) { - return fromTree(receiverTree); - } else { - Element ele = TreeUtils.elementFromUse(accessTree); - if (ele == null) { - throw new BugInCF("TreeUtils.elementFromUse(" + accessTree + ") => null"); - } - return getImplicitReceiver(ele); + + /** + * Returns either a new ClassName or ThisReference JavaExpression object for the enclosingType. + * + *

          The Tree should be an expression or a statement that does not have a receiver or an + * implicit receiver. For example, a local variable declaration. + * + * @param path a tree path + * @param enclosingType type of the enclosing type + * @return a new {@link ClassName} or {@link ThisReference} that is a JavaExpression object for + * the enclosingType + */ + public static JavaExpression getPseudoReceiver(TreePath path, TypeMirror enclosingType) { + if (TreePathUtil.isTreeInStaticScope(path)) { + return new ClassName(enclosingType); + } else { + return new ThisReference(enclosingType); + } } - } - - /** - * Returns the implicit receiver of ele. - * - *

          Returns either a new ClassName or a new ThisReference depending on whether ele is static or - * not. The passed element must be a field, method, or class. - * - *

          When this returns a ThisReference, its type is the class that declares {@code ele}, which is - * not necessarily the type of {@code this} at the invocation site. - * - * @param ele a field, method, or class - * @return either a new ClassName or a new ThisReference depending on whether ele is static or not - */ - public static JavaExpression getImplicitReceiver(Element ele) { - TypeElement enclosingTypeElement = ElementUtils.enclosingTypeElement(ele); - if (enclosingTypeElement == null) { - throw new BugInCF("getImplicitReceiver's arg has no enclosing type: " + ele); + + /** + * Accept method of the visitor pattern. + * + * @param visitor the visitor to be applied to this JavaExpression + * @param p the parameter for this operation + * @param result type of the operation + * @param

          parameter type + * @return the result of visiting this + */ + public abstract R accept(JavaExpressionVisitor visitor, P p); + + /** + * Viewpoint-adapts {@code this} to a field access with receiver {@code receiver}. + * + * @param receiver receiver of the field access + * @return viewpoint-adapted version of this + */ + public JavaExpression atFieldAccess(JavaExpression receiver) { + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiver); } - TypeMirror enclosingType = enclosingTypeElement.asType(); - if (ElementUtils.isStatic(ele)) { - return new ClassName(enclosingType); - } else { - return new ThisReference(enclosingType); + + /** + * Viewpoint-adapts {@code this} to the {@code methodTree} by converting any {@code + * FormalParameter} into {@code LocalVariable}s. + * + * @param methodTree method declaration tree + * @return viewpoint-adapted version of this + */ + public final JavaExpression atMethodBody(MethodTree methodTree) { + List parametersJe = + CollectionsPlume.mapList( + (VariableTree param) -> + new LocalVariable(TreeUtils.elementFromDeclaration(param)), + methodTree.getParameters()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, parametersJe); } - } - - /** - * Returns either a new ClassName or ThisReference JavaExpression object for the enclosingType. - * - *

          The Tree should be an expression or a statement that does not have a receiver or an implicit - * receiver. For example, a local variable declaration. - * - * @param path a tree path - * @param enclosingType type of the enclosing type - * @return a new {@link ClassName} or {@link ThisReference} that is a JavaExpression object for - * the enclosingType - */ - public static JavaExpression getPseudoReceiver(TreePath path, TypeMirror enclosingType) { - if (TreePathUtil.isTreeInStaticScope(path)) { - return new ClassName(enclosingType); - } else { - return new ThisReference(enclosingType); + + /** + * Viewpoint-adapts {@code this} to the {@code methodInvocationTree}. + * + * @param methodInvocationTree method invocation + * @return viewpoint-adapted version of this + */ + public final JavaExpression atMethodInvocation(MethodInvocationTree methodInvocationTree) { + JavaExpression receiverJe = JavaExpression.getReceiver(methodInvocationTree); + List argumentsJe = + argumentTreesToJavaExpressions( + TreeUtils.elementFromUse(methodInvocationTree), + methodInvocationTree.getArguments()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); } - } - - /** - * Accept method of the visitor pattern. - * - * @param visitor the visitor to be applied to this JavaExpression - * @param p the parameter for this operation - * @param result type of the operation - * @param

          parameter type - * @return the result of visiting this - */ - public abstract R accept(JavaExpressionVisitor visitor, P p); - - /** - * Viewpoint-adapts {@code this} to a field access with receiver {@code receiver}. - * - * @param receiver receiver of the field access - * @return viewpoint-adapted version of this - */ - public JavaExpression atFieldAccess(JavaExpression receiver) { - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiver); - } - - /** - * Viewpoint-adapts {@code this} to the {@code methodTree} by converting any {@code - * FormalParameter} into {@code LocalVariable}s. - * - * @param methodTree method declaration tree - * @return viewpoint-adapted version of this - */ - public final JavaExpression atMethodBody(MethodTree methodTree) { - List parametersJe = - CollectionsPlume.mapList( - (VariableTree param) -> new LocalVariable(TreeUtils.elementFromDeclaration(param)), - methodTree.getParameters()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, parametersJe); - } - - /** - * Viewpoint-adapts {@code this} to the {@code methodInvocationTree}. - * - * @param methodInvocationTree method invocation - * @return viewpoint-adapted version of this - */ - public final JavaExpression atMethodInvocation(MethodInvocationTree methodInvocationTree) { - JavaExpression receiverJe = JavaExpression.getReceiver(methodInvocationTree); - List argumentsJe = - argumentTreesToJavaExpressions( - TreeUtils.elementFromUse(methodInvocationTree), methodInvocationTree.getArguments()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); - } - - /** - * Viewpoint-adapts {@code this} to the {@code invocationNode}. - * - * @param invocationNode method invocation - * @return viewpoint-adapted version of this - */ - public final JavaExpression atMethodInvocation(MethodInvocationNode invocationNode) { - JavaExpression receiverJe = JavaExpression.fromNode(invocationNode.getTarget().getReceiver()); - List argumentsJe = - CollectionsPlume.mapList(JavaExpression::fromNode, invocationNode.getArguments()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); - } - - /** - * Viewpoint-adapts {@code this} to the {@code newClassTree}. - * - * @param newClassTree constructor invocation - * @return viewpoint-adapted version of this - */ - public JavaExpression atConstructorInvocation(NewClassTree newClassTree) { - JavaExpression receiverJe = JavaExpression.getReceiver(newClassTree); - List argumentsJe = - argumentTreesToJavaExpressions( - TreeUtils.elementFromUse(newClassTree), newClassTree.getArguments()); - return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); - } - - /** - * Converts method or constructor arguments from Trees to JavaExpressions, accounting for varargs. - * - * @param method the method or constructor being invoked - * @param argTrees the arguments to the method or constructor - * @return the arguments, as JavaExpressions - */ - private static List argumentTreesToJavaExpressions( - ExecutableElement method, List argTrees) { - if (isVarArgsInvocation(method, argTrees)) { - List result = new ArrayList<>(method.getParameters().size()); - for (int i = 0; i < method.getParameters().size() - 1; i++) { - result.add(JavaExpression.fromTree(argTrees.get(i))); - } - - List varargArgs = - new ArrayList<>(argTrees.size() - method.getParameters().size() + 1); - for (int i = method.getParameters().size() - 1; i < argTrees.size(); i++) { - varargArgs.add(JavaExpression.fromTree(argTrees.get(i))); - } - Element varargsElement = method.getParameters().get(method.getParameters().size() - 1); - TypeMirror tm = ElementUtils.getType(varargsElement); - result.add(new ArrayCreation(tm, Collections.emptyList(), varargArgs)); - - return result; + + /** + * Viewpoint-adapts {@code this} to the {@code invocationNode}. + * + * @param invocationNode method invocation + * @return viewpoint-adapted version of this + */ + public final JavaExpression atMethodInvocation(MethodInvocationNode invocationNode) { + JavaExpression receiverJe = + JavaExpression.fromNode(invocationNode.getTarget().getReceiver()); + List argumentsJe = + CollectionsPlume.mapList(JavaExpression::fromNode, invocationNode.getArguments()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); } - return CollectionsPlume.mapList(JavaExpression::fromTree, argTrees); - } - - /** - * Returns true if method is a varargs method or constructor and its varargs arguments are not - * passed in an array. - * - * @param method the method or constructor - * @param args the arguments at the call site - * @return true if method is a varargs method and its varargs arguments are not passed in an array - */ - private static boolean isVarArgsInvocation( - ExecutableElement method, List args) { - if (!method.isVarArgs()) { - return false; + /** + * Viewpoint-adapts {@code this} to the {@code newClassTree}. + * + * @param newClassTree constructor invocation + * @return viewpoint-adapted version of this + */ + public JavaExpression atConstructorInvocation(NewClassTree newClassTree) { + JavaExpression receiverJe = JavaExpression.getReceiver(newClassTree); + List argumentsJe = + argumentTreesToJavaExpressions( + TreeUtils.elementFromUse(newClassTree), newClassTree.getArguments()); + return ViewpointAdaptJavaExpression.viewpointAdapt(this, receiverJe, argumentsJe); } - if (method.getParameters().size() != args.size()) { - return true; + + /** + * Converts method or constructor arguments from Trees to JavaExpressions, accounting for + * varargs. + * + * @param method the method or constructor being invoked + * @param argTrees the arguments to the method or constructor + * @return the arguments, as JavaExpressions + */ + private static List argumentTreesToJavaExpressions( + ExecutableElement method, List argTrees) { + if (isVarArgsInvocation(method, argTrees)) { + List result = new ArrayList<>(method.getParameters().size()); + for (int i = 0; i < method.getParameters().size() - 1; i++) { + result.add(JavaExpression.fromTree(argTrees.get(i))); + } + + List varargArgs = + new ArrayList<>(argTrees.size() - method.getParameters().size() + 1); + for (int i = method.getParameters().size() - 1; i < argTrees.size(); i++) { + varargArgs.add(JavaExpression.fromTree(argTrees.get(i))); + } + Element varargsElement = method.getParameters().get(method.getParameters().size() - 1); + TypeMirror tm = ElementUtils.getType(varargsElement); + result.add(new ArrayCreation(tm, Collections.emptyList(), varargArgs)); + + return result; + } + + return CollectionsPlume.mapList(JavaExpression::fromTree, argTrees); } - TypeMirror lastArgType = TreeUtils.typeOf(args.get(args.size() - 1)); - if (lastArgType.getKind() != TypeKind.ARRAY) { - return true; + + /** + * Returns true if method is a varargs method or constructor and its varargs arguments are not + * passed in an array. + * + * @param method the method or constructor + * @param args the arguments at the call site + * @return true if method is a varargs method and its varargs arguments are not passed in an + * array + */ + private static boolean isVarArgsInvocation( + ExecutableElement method, List args) { + if (!method.isVarArgs()) { + return false; + } + if (method.getParameters().size() != args.size()) { + return true; + } + TypeMirror lastArgType = TreeUtils.typeOf(args.get(args.size() - 1)); + if (lastArgType.getKind() != TypeKind.ARRAY) { + return true; + } + List paramElts = method.getParameters(); + VariableElement lastParamElt = paramElts.get(paramElts.size() - 1); + return TypesUtils.getArrayDepth(ElementUtils.getType(lastParamElt)) + != TypesUtils.getArrayDepth(lastArgType); } - List paramElts = method.getParameters(); - VariableElement lastParamElt = paramElts.get(paramElts.size() - 1); - return TypesUtils.getArrayDepth(ElementUtils.getType(lastParamElt)) - != TypesUtils.getArrayDepth(lastArgType); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java index 1b811ec4443..0e2db76615c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionConverter.java @@ -1,10 +1,11 @@ package org.checkerframework.dataflow.expression; -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.plumelib.util.CollectionsPlume; +import java.util.List; + /** * This class calls {@link #convert(JavaExpression)} on each subexpression of the {@link * JavaExpression} and returns a new {@code JavaExpression} built from the result of calling {@code @@ -17,103 +18,104 @@ */ public abstract class JavaExpressionConverter extends JavaExpressionVisitor { - /** - * Converts {@code javaExpr} and returns the resulting {@code JavaExpression}. - * - * @param javaExpr the expression to convert - * @return the converted expression - */ - public JavaExpression convert(JavaExpression javaExpr) { - return super.visit(javaExpr, null); - } - - /** - * Converts all the expressions in {@code list} and returns the resulting list. - * - * @param list the list of expressions to convert - * @return the list of converted expressions - */ - public List<@PolyNull JavaExpression> convert(List<@PolyNull JavaExpression> list) { - return CollectionsPlume.mapList( - expression -> { - // Can't use a ternary operator because of: - // https://github.com/typetools/checker-framework/issues/1170 - if (expression == null) { - return null; - } - return convert(expression); - }, - list); - } - - @Override - protected JavaExpression visitArrayAccess(ArrayAccess arrayAccessExpr, Void unused) { - JavaExpression array = convert(arrayAccessExpr.getArray()); - JavaExpression index = convert(arrayAccessExpr.getIndex()); - return new ArrayAccess(arrayAccessExpr.type, array, index); - } - - @Override - protected JavaExpression visitArrayCreation(ArrayCreation arrayCreationExpr, Void unused) { - List<@Nullable JavaExpression> dims = convert(arrayCreationExpr.getDimensions()); - List inits = convert(arrayCreationExpr.getInitializers()); - return new ArrayCreation(arrayCreationExpr.getType(), dims, inits); - } - - @Override - protected JavaExpression visitBinaryOperation(BinaryOperation binaryOpExpr, Void unused) { - JavaExpression left = convert(binaryOpExpr.getLeft()); - JavaExpression right = convert(binaryOpExpr.getRight()); - return new BinaryOperation( - binaryOpExpr.getType(), binaryOpExpr.getOperationKind(), left, right); - } - - @Override - protected JavaExpression visitClassName(ClassName classNameExpr, Void unused) { - return classNameExpr; - } - - @Override - protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { - JavaExpression receiver = convert(fieldAccessExpr.getReceiver()); - return new FieldAccess(receiver, fieldAccessExpr.getType(), fieldAccessExpr.getField()); - } - - @Override - protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { - return parameterExpr; - } - - @Override - protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { - return localVarExpr; - } - - @Override - protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { - JavaExpression receiver = convert(methodCallExpr.getReceiver()); - List args = convert(methodCallExpr.getArguments()); - return new MethodCall(methodCallExpr.getType(), methodCallExpr.getElement(), receiver, args); - } - - @Override - protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { - return thisExpr; - } - - @Override - protected JavaExpression visitUnaryOperation(UnaryOperation unaryOpExpr, Void unused) { - JavaExpression operand = convert(unaryOpExpr.getOperand()); - return new UnaryOperation(unaryOpExpr.getType(), unaryOpExpr.getOperationKind(), operand); - } - - @Override - protected JavaExpression visitUnknown(Unknown unknownExpr, Void unused) { - return unknownExpr; - } - - @Override - protected JavaExpression visitValueLiteral(ValueLiteral literalExpr, Void unused) { - return literalExpr; - } + /** + * Converts {@code javaExpr} and returns the resulting {@code JavaExpression}. + * + * @param javaExpr the expression to convert + * @return the converted expression + */ + public JavaExpression convert(JavaExpression javaExpr) { + return super.visit(javaExpr, null); + } + + /** + * Converts all the expressions in {@code list} and returns the resulting list. + * + * @param list the list of expressions to convert + * @return the list of converted expressions + */ + public List<@PolyNull JavaExpression> convert(List<@PolyNull JavaExpression> list) { + return CollectionsPlume.mapList( + expression -> { + // Can't use a ternary operator because of: + // https://github.com/typetools/checker-framework/issues/1170 + if (expression == null) { + return null; + } + return convert(expression); + }, + list); + } + + @Override + protected JavaExpression visitArrayAccess(ArrayAccess arrayAccessExpr, Void unused) { + JavaExpression array = convert(arrayAccessExpr.getArray()); + JavaExpression index = convert(arrayAccessExpr.getIndex()); + return new ArrayAccess(arrayAccessExpr.type, array, index); + } + + @Override + protected JavaExpression visitArrayCreation(ArrayCreation arrayCreationExpr, Void unused) { + List<@Nullable JavaExpression> dims = convert(arrayCreationExpr.getDimensions()); + List inits = convert(arrayCreationExpr.getInitializers()); + return new ArrayCreation(arrayCreationExpr.getType(), dims, inits); + } + + @Override + protected JavaExpression visitBinaryOperation(BinaryOperation binaryOpExpr, Void unused) { + JavaExpression left = convert(binaryOpExpr.getLeft()); + JavaExpression right = convert(binaryOpExpr.getRight()); + return new BinaryOperation( + binaryOpExpr.getType(), binaryOpExpr.getOperationKind(), left, right); + } + + @Override + protected JavaExpression visitClassName(ClassName classNameExpr, Void unused) { + return classNameExpr; + } + + @Override + protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { + JavaExpression receiver = convert(fieldAccessExpr.getReceiver()); + return new FieldAccess(receiver, fieldAccessExpr.getType(), fieldAccessExpr.getField()); + } + + @Override + protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { + return parameterExpr; + } + + @Override + protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { + return localVarExpr; + } + + @Override + protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { + JavaExpression receiver = convert(methodCallExpr.getReceiver()); + List args = convert(methodCallExpr.getArguments()); + return new MethodCall( + methodCallExpr.getType(), methodCallExpr.getElement(), receiver, args); + } + + @Override + protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { + return thisExpr; + } + + @Override + protected JavaExpression visitUnaryOperation(UnaryOperation unaryOpExpr, Void unused) { + JavaExpression operand = convert(unaryOpExpr.getOperand()); + return new UnaryOperation(unaryOpExpr.getType(), unaryOpExpr.getOperationKind(), operand); + } + + @Override + protected JavaExpression visitUnknown(Unknown unknownExpr, Void unused) { + return unknownExpr; + } + + @Override + protected JavaExpression visitValueLiteral(ValueLiteral literalExpr, Void unused) { + return literalExpr; + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java index e282ce5a11d..11414a13bba 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionScanner.java @@ -1,8 +1,9 @@ package org.checkerframework.dataflow.expression; -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + /** * A simple scanner for {@link JavaExpression}. * @@ -10,97 +11,97 @@ */ public abstract class JavaExpressionScanner

          extends JavaExpressionVisitor { - /** - * Scans the JavaExpression. - * - * @param javaExpression the expression to scan - * @param p parameter to pass - */ - public void scan(JavaExpression javaExpression, P p) { - visit(javaExpression, p); - } - - /** - * Scans each JavaExpression in {@code expressions}. - * - * @param expressions a list of JavaExpressions to scan - * @param p pameter to pass - */ - public void scan(List expressions, P p) { - for (JavaExpression expression : expressions) { - if (expression != null) { - visit(expression, p); - } + /** + * Scans the JavaExpression. + * + * @param javaExpression the expression to scan + * @param p parameter to pass + */ + public void scan(JavaExpression javaExpression, P p) { + visit(javaExpression, p); + } + + /** + * Scans each JavaExpression in {@code expressions}. + * + * @param expressions a list of JavaExpressions to scan + * @param p pameter to pass + */ + public void scan(List expressions, P p) { + for (JavaExpression expression : expressions) { + if (expression != null) { + visit(expression, p); + } + } + } + + @Override + protected Void visitArrayAccess(ArrayAccess arrayAccessExpr, P p) { + visit(arrayAccessExpr.getArray(), p); + visit(arrayAccessExpr.getIndex(), p); + return null; + } + + @Override + protected Void visitArrayCreation(ArrayCreation arrayCreationExpr, P p) { + scan(arrayCreationExpr.getDimensions(), p); + scan(arrayCreationExpr.getInitializers(), p); + return null; + } + + @Override + protected Void visitBinaryOperation(BinaryOperation binaryOpExpr, P p) { + visit(binaryOpExpr.getLeft(), p); + visit(binaryOpExpr.getRight(), p); + return null; + } + + @Override + protected Void visitClassName(ClassName classNameExpr, P p) { + return null; + } + + @Override + protected Void visitFormalParameter(FormalParameter parameterExpr, P p) { + return null; + } + + @Override + protected Void visitFieldAccess(FieldAccess fieldAccessExpr, P p) { + visit(fieldAccessExpr.getReceiver(), p); + return null; + } + + @Override + protected Void visitLocalVariable(LocalVariable localVarExpr, P p) { + return null; + } + + @Override + protected Void visitMethodCall(MethodCall methodCallExpr, P p) { + visit(methodCallExpr.getReceiver(), p); + scan(methodCallExpr.getArguments(), p); + return null; + } + + @Override + protected Void visitThisReference(ThisReference thisExpr, P p) { + return null; + } + + @Override + protected Void visitUnaryOperation(UnaryOperation unaryOpExpr, P p) { + visit(unaryOpExpr.getOperand(), p); + return null; + } + + @Override + protected Void visitUnknown(Unknown unknownExpr, P p) { + return null; + } + + @Override + protected Void visitValueLiteral(ValueLiteral literalExpr, P p) { + return null; } - } - - @Override - protected Void visitArrayAccess(ArrayAccess arrayAccessExpr, P p) { - visit(arrayAccessExpr.getArray(), p); - visit(arrayAccessExpr.getIndex(), p); - return null; - } - - @Override - protected Void visitArrayCreation(ArrayCreation arrayCreationExpr, P p) { - scan(arrayCreationExpr.getDimensions(), p); - scan(arrayCreationExpr.getInitializers(), p); - return null; - } - - @Override - protected Void visitBinaryOperation(BinaryOperation binaryOpExpr, P p) { - visit(binaryOpExpr.getLeft(), p); - visit(binaryOpExpr.getRight(), p); - return null; - } - - @Override - protected Void visitClassName(ClassName classNameExpr, P p) { - return null; - } - - @Override - protected Void visitFormalParameter(FormalParameter parameterExpr, P p) { - return null; - } - - @Override - protected Void visitFieldAccess(FieldAccess fieldAccessExpr, P p) { - visit(fieldAccessExpr.getReceiver(), p); - return null; - } - - @Override - protected Void visitLocalVariable(LocalVariable localVarExpr, P p) { - return null; - } - - @Override - protected Void visitMethodCall(MethodCall methodCallExpr, P p) { - visit(methodCallExpr.getReceiver(), p); - scan(methodCallExpr.getArguments(), p); - return null; - } - - @Override - protected Void visitThisReference(ThisReference thisExpr, P p) { - return null; - } - - @Override - protected Void visitUnaryOperation(UnaryOperation unaryOpExpr, P p) { - visit(unaryOpExpr.getOperand(), p); - return null; - } - - @Override - protected Void visitUnknown(Unknown unknownExpr, P p) { - return null; - } - - @Override - protected Void visitValueLiteral(ValueLiteral literalExpr, P p) { - return null; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java index 1511dea1277..024ec632ce1 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpressionVisitor.java @@ -8,122 +8,122 @@ */ public abstract class JavaExpressionVisitor { - /** - * Visits the given {@code javaExpr}. - * - * @param javaExpr the expression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the expression - */ - public R visit(JavaExpression javaExpr, P p) { - return javaExpr.accept(this, p); - } + /** + * Visits the given {@code javaExpr}. + * + * @param javaExpr the expression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the expression + */ + public R visit(JavaExpression javaExpr, P p) { + return javaExpr.accept(this, p); + } - /** - * Visit an {@link ArrayAccess}. - * - * @param arrayAccessExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code arrayAccessExpr} - */ - protected abstract R visitArrayAccess(ArrayAccess arrayAccessExpr, P p); + /** + * Visit an {@link ArrayAccess}. + * + * @param arrayAccessExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code arrayAccessExpr} + */ + protected abstract R visitArrayAccess(ArrayAccess arrayAccessExpr, P p); - /** - * Visit an {@link ArrayCreation}. - * - * @param arrayCreationExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code arrayCreationExpr} - */ - protected abstract R visitArrayCreation(ArrayCreation arrayCreationExpr, P p); + /** + * Visit an {@link ArrayCreation}. + * + * @param arrayCreationExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code arrayCreationExpr} + */ + protected abstract R visitArrayCreation(ArrayCreation arrayCreationExpr, P p); - /** - * Visit a {@link BinaryOperation}. - * - * @param binaryOpExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code binaryOpExpr} - */ - protected abstract R visitBinaryOperation(BinaryOperation binaryOpExpr, P p); + /** + * Visit a {@link BinaryOperation}. + * + * @param binaryOpExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code binaryOpExpr} + */ + protected abstract R visitBinaryOperation(BinaryOperation binaryOpExpr, P p); - /** - * Visit a {@link ClassName}. - * - * @param classNameExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code classNameExpr} - */ - protected abstract R visitClassName(ClassName classNameExpr, P p); + /** + * Visit a {@link ClassName}. + * + * @param classNameExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code classNameExpr} + */ + protected abstract R visitClassName(ClassName classNameExpr, P p); - /** - * Visit a {@link FieldAccess}. - * - * @param fieldAccessExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code fieldAccessExpr} - */ - protected abstract R visitFieldAccess(FieldAccess fieldAccessExpr, P p); + /** + * Visit a {@link FieldAccess}. + * + * @param fieldAccessExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code fieldAccessExpr} + */ + protected abstract R visitFieldAccess(FieldAccess fieldAccessExpr, P p); - /** - * Visit a {@link FormalParameter}. - * - * @param parameterExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code parameterExpr} - */ - protected abstract R visitFormalParameter(FormalParameter parameterExpr, P p); + /** + * Visit a {@link FormalParameter}. + * + * @param parameterExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code parameterExpr} + */ + protected abstract R visitFormalParameter(FormalParameter parameterExpr, P p); - /** - * Visit a {@link LocalVariable}. - * - * @param localVarExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code localVarExpr} - */ - protected abstract R visitLocalVariable(LocalVariable localVarExpr, P p); + /** + * Visit a {@link LocalVariable}. + * + * @param localVarExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code localVarExpr} + */ + protected abstract R visitLocalVariable(LocalVariable localVarExpr, P p); - /** - * Visit a {@link MethodCall}. - * - * @param methodCallExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code methodCallExpr} - */ - protected abstract R visitMethodCall(MethodCall methodCallExpr, P p); + /** + * Visit a {@link MethodCall}. + * + * @param methodCallExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code methodCallExpr} + */ + protected abstract R visitMethodCall(MethodCall methodCallExpr, P p); - /** - * Visit a {@link ThisReference}. - * - * @param thisExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code thisExpr} - */ - protected abstract R visitThisReference(ThisReference thisExpr, P p); + /** + * Visit a {@link ThisReference}. + * + * @param thisExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code thisExpr} + */ + protected abstract R visitThisReference(ThisReference thisExpr, P p); - /** - * Visit an {@link UnaryOperation}. - * - * @param unaryOpExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code unaryOpExpr} - */ - protected abstract R visitUnaryOperation(UnaryOperation unaryOpExpr, P p); + /** + * Visit an {@link UnaryOperation}. + * + * @param unaryOpExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code unaryOpExpr} + */ + protected abstract R visitUnaryOperation(UnaryOperation unaryOpExpr, P p); - /** - * Visit an {@link Unknown}. - * - * @param unknownExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code unknownExpr} - */ - protected abstract R visitUnknown(Unknown unknownExpr, P p); + /** + * Visit an {@link Unknown}. + * + * @param unknownExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code unknownExpr} + */ + protected abstract R visitUnknown(Unknown unknownExpr, P p); - /** - * Visit a {@link ValueLiteral}. - * - * @param literalExpr the JavaExpression to visit - * @param p the parameter to pass to the visit method - * @return the result of visiting the {@code literalExpr} - */ - protected abstract R visitValueLiteral(ValueLiteral literalExpr, P p); + /** + * Visit a {@link ValueLiteral}. + * + * @param literalExpr the JavaExpression to visit + * @param p the parameter to pass to the visit method + * @return the result of visiting the {@code literalExpr} + */ + protected abstract R visitValueLiteral(ValueLiteral literalExpr, P p); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java index 632d6c21bd3..0930d6bedc2 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/LocalVariable.java @@ -1,14 +1,17 @@ package org.checkerframework.dataflow.expression; import com.sun.tools.javac.code.Symbol.VarSymbol; -import java.util.Objects; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.Objects; + +import javax.lang.model.element.VariableElement; + /** * A local variable. * @@ -16,118 +19,118 @@ * FormalParameter} represents a formal parameter expressed using the "#2" notation. */ public class LocalVariable extends JavaExpression { - /** The element for this local variable. */ - protected final VariableElement element; - - /** - * Creates a new LocalVariable. - * - * @param localVar a CFG local variable - */ - public LocalVariable(LocalVariableNode localVar) { - super(localVar.getType()); - this.element = localVar.getElement(); - } - - /** - * Creates a new LocalVariable. - * - * @param element the element for the local variable - */ - public LocalVariable(VariableElement element) { - super(ElementUtils.getType(element)); - this.element = element; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LocalVariable)) { - return false; + /** The element for this local variable. */ + protected final VariableElement element; + + /** + * Creates a new LocalVariable. + * + * @param localVar a CFG local variable + */ + public LocalVariable(LocalVariableNode localVar) { + super(localVar.getType()); + this.element = localVar.getElement(); + } + + /** + * Creates a new LocalVariable. + * + * @param element the element for the local variable + */ + public LocalVariable(VariableElement element) { + super(ElementUtils.getType(element)); + this.element = element; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LocalVariable)) { + return false; + } + LocalVariable other = (LocalVariable) obj; + + return sameElement(element, other.element); + } + + /** + * Returns true if the two elements are the same. + * + * @param element1 the first element to compare + * @param element2 the second element to compare + * @return true if the two elements are the same + */ + protected static boolean sameElement(VariableElement element1, VariableElement element2) { + VarSymbol vs1 = (VarSymbol) element1; + VarSymbol vs2 = (VarSymbol) element2; + // If a LocalVariable is created via JavaExpressionParseUtil#parse, then `vs1.equals(vs2)` + // will not return true even if the elements represent the same local variable. + // The owner of a lambda parameter is the enclosing method, so a local variable and a lambda + // parameter might have the same name and the same owner. Use pos to differentiate this + // case. + return vs1.pos == vs2.pos && vs1.name == vs2.name && vs1.owner.equals(vs2.owner); + } + + /** + * Returns the element for this variable. + * + * @return the element for this variable + */ + public VariableElement getElement() { + return element; + } + + @Override + public int hashCode() { + VarSymbol vs = (VarSymbol) element; + return Objects.hash(vs.pos, vs.name, vs.owner); } - LocalVariable other = (LocalVariable) obj; - - return sameElement(element, other.element); - } - - /** - * Returns true if the two elements are the same. - * - * @param element1 the first element to compare - * @param element2 the second element to compare - * @return true if the two elements are the same - */ - protected static boolean sameElement(VariableElement element1, VariableElement element2) { - VarSymbol vs1 = (VarSymbol) element1; - VarSymbol vs2 = (VarSymbol) element2; - // If a LocalVariable is created via JavaExpressionParseUtil#parse, then `vs1.equals(vs2)` - // will not return true even if the elements represent the same local variable. - // The owner of a lambda parameter is the enclosing method, so a local variable and a lambda - // parameter might have the same name and the same owner. Use pos to differentiate this - // case. - return vs1.pos == vs2.pos && vs1.name == vs2.name && vs1.owner.equals(vs2.owner); - } - - /** - * Returns the element for this variable. - * - * @return the element for this variable - */ - public VariableElement getElement() { - return element; - } - - @Override - public int hashCode() { - VarSymbol vs = (VarSymbol) element; - return Objects.hash(vs.pos, vs.name, vs.owner); - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public String toStringDebug() { - return super.toStringDebug() + " [owner=" + ((VarSymbol) element).owner + "]"; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof LocalVariable)) { - return false; + + @Override + public String toString() { + return element.toString(); + } + + @Override + public String toStringDebug() { + return super.toStringDebug() + " [owner=" + ((VarSymbol) element).owner + "]"; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof LocalVariable)) { + return false; + } + LocalVariable other = (LocalVariable) je; + return this.equals(other); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other); + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return TypesUtils.isImmutableTypeInJdk(((VarSymbol) element).type); + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitLocalVariable(this, p); } - LocalVariable other = (LocalVariable) je; - return this.equals(other); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other); - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return TypesUtils.isImmutableTypeInJdk(((VarSymbol) element).type); - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitLocalVariable(this, p); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java index 7d42cad17ad..4c50db53d22 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/MethodCall.java @@ -1,189 +1,191 @@ package org.checkerframework.dataflow.expression; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.util.PurityUtils; +import org.checkerframework.javacutil.AnnotationProvider; + import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.StringJoiner; + import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.analysis.Store; -import org.checkerframework.dataflow.util.PurityUtils; -import org.checkerframework.javacutil.AnnotationProvider; /** A call to a @Deterministic method. */ public class MethodCall extends JavaExpression { - /** The method being called. */ - protected final ExecutableElement method; - - /** The receiver argument. */ - protected final JavaExpression receiver; - - /** The arguments. */ - protected final List arguments; - - /** - * Creates a new MethodCall. - * - * @param type the type of the method call - * @param method the method being called - * @param receiver the receiver argument - * @param arguments the arguments - */ - public MethodCall( - TypeMirror type, - ExecutableElement method, - JavaExpression receiver, - List arguments) { - super(type); - this.receiver = receiver; - this.arguments = arguments; - this.method = method; - } - - /** - * Returns the ExecutableElement for the method call. - * - * @return the ExecutableElement for the method call - */ - public ExecutableElement getElement() { - return method; - } - - /** - * Returns the method call receiver (for inspection only - do not modify). - * - * @return the method call receiver (for inspection only - do not modify) - */ - public JavaExpression getReceiver() { - return receiver; - } - - /** - * Returns the method call arguments (for inspection only - do not modify any of the arguments). - * - * @return the method call arguments (for inspection only - do not modify any of the arguments) - */ - public List getArguments() { - return Collections.unmodifiableList(arguments); - } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; + /** The method being called. */ + protected final ExecutableElement method; + + /** The receiver argument. */ + protected final JavaExpression receiver; + + /** The arguments. */ + protected final List arguments; + + /** + * Creates a new MethodCall. + * + * @param type the type of the method call + * @param method the method being called + * @param receiver the receiver argument + * @param arguments the arguments + */ + public MethodCall( + TypeMirror type, + ExecutableElement method, + JavaExpression receiver, + List arguments) { + super(type); + this.receiver = receiver; + this.arguments = arguments; + this.method = method; } - if (receiver.containsOfClass(clazz)) { - return true; + + /** + * Returns the ExecutableElement for the method call. + * + * @return the ExecutableElement for the method call + */ + public ExecutableElement getElement() { + return method; } - for (JavaExpression p : arguments) { - if (p.containsOfClass(clazz)) { - return true; - } + + /** + * Returns the method call receiver (for inspection only - do not modify). + * + * @return the method call receiver (for inspection only - do not modify) + */ + public JavaExpression getReceiver() { + return receiver; } - return false; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return (PurityUtils.isDeterministic(provider, method) || provider.isDeterministic(method)) - && listIsDeterministic(arguments, provider); - } - - @Override - public boolean isUnassignableByOtherCode() { - // There is no need to check that the method is deterministic, because a MethodCall is - // only created for deterministic methods. - return receiver.isUnmodifiableByOtherCode() - && arguments.stream().allMatch(JavaExpression::isUnmodifiableByOtherCode); - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return isUnassignableByOtherCode(); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof MethodCall)) { - return false; + + /** + * Returns the method call arguments (for inspection only - do not modify any of the arguments). + * + * @return the method call arguments (for inspection only - do not modify any of the arguments) + */ + public List getArguments() { + return Collections.unmodifiableList(arguments); } - MethodCall other = (MethodCall) je; - return method.equals(other.method) - && this.receiver.syntacticEquals(other.receiver) - && JavaExpression.syntacticEqualsList(this.arguments, other.arguments); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return syntacticEquals(other) - || receiver.containsSyntacticEqualJavaExpression(other) - || JavaExpression.listContainsSyntacticEqualJavaExpression(arguments, other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - if (receiver.containsModifiableAliasOf(store, other)) { - return true; + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; + } + if (receiver.containsOfClass(clazz)) { + return true; + } + for (JavaExpression p : arguments) { + if (p.containsOfClass(clazz)) { + return true; + } + } + return false; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return (PurityUtils.isDeterministic(provider, method) || provider.isDeterministic(method)) + && listIsDeterministic(arguments, provider); } - for (JavaExpression p : arguments) { - if (p.containsModifiableAliasOf(store, other)) { - return true; - } + + @Override + public boolean isUnassignableByOtherCode() { + // There is no need to check that the method is deterministic, because a MethodCall is + // only created for deterministic methods. + return receiver.isUnmodifiableByOtherCode() + && arguments.stream().allMatch(JavaExpression::isUnmodifiableByOtherCode); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return isUnassignableByOtherCode(); } - return false; // the method call itself is not modifiable - } - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof MethodCall)) { + return false; + } + MethodCall other = (MethodCall) je; + return method.equals(other.method) + && this.receiver.syntacticEquals(other.receiver) + && JavaExpression.syntacticEqualsList(this.arguments, other.arguments); } - if (!(obj instanceof MethodCall)) { - return false; + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return syntacticEquals(other) + || receiver.containsSyntacticEqualJavaExpression(other) + || JavaExpression.listContainsSyntacticEqualJavaExpression(arguments, other); } - if (method.getKind() == ElementKind.CONSTRUCTOR) { - // No two constructor instances are equal. - return false; + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + if (receiver.containsModifiableAliasOf(store, other)) { + return true; + } + for (JavaExpression p : arguments) { + if (p.containsModifiableAliasOf(store, other)) { + return true; + } + } + return false; // the method call itself is not modifiable } - MethodCall other = (MethodCall) obj; - return method.equals(other.method) - && receiver.equals(other.receiver) - && arguments.equals(other.arguments); - } - - @Override - public int hashCode() { - if (method.getKind() == ElementKind.CONSTRUCTOR) { - // No two constructor instances have the same hashcode. - return System.identityHashCode(this); + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MethodCall)) { + return false; + } + if (method.getKind() == ElementKind.CONSTRUCTOR) { + // No two constructor instances are equal. + return false; + } + MethodCall other = (MethodCall) obj; + return method.equals(other.method) + && receiver.equals(other.receiver) + && arguments.equals(other.arguments); } - return Objects.hash(method, receiver, arguments); - } - - @Override - public String toString() { - StringBuilder preParen = new StringBuilder(); - if (receiver instanceof ClassName) { - preParen.append(receiver.getType()); - } else { - preParen.append(receiver); + + @Override + public int hashCode() { + if (method.getKind() == ElementKind.CONSTRUCTOR) { + // No two constructor instances have the same hashcode. + return System.identityHashCode(this); + } + return Objects.hash(method, receiver, arguments); } - preParen.append("."); - String methodName = method.getSimpleName().toString(); - preParen.append(methodName); - preParen.append("("); - StringJoiner result = new StringJoiner(", ", preParen, ")"); - for (JavaExpression argument : arguments) { - result.add(argument.toString()); + + @Override + public String toString() { + StringBuilder preParen = new StringBuilder(); + if (receiver instanceof ClassName) { + preParen.append(receiver.getType()); + } else { + preParen.append(receiver); + } + preParen.append("."); + String methodName = method.getSimpleName().toString(); + preParen.append(methodName); + preParen.append("("); + StringJoiner result = new StringJoiner(", ", preParen, ")"); + for (JavaExpression argument : arguments) { + result.add(argument.toString()); + } + return result.toString(); } - return result.toString(); - } - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitMethodCall(this, p); - } + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitMethodCall(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java index dfb2374c058..4ca54f224c0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ThisReference.java @@ -1,74 +1,75 @@ package org.checkerframework.dataflow.expression; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.TypesUtils; +import javax.lang.model.type.TypeMirror; + /** A use of {@code this}. */ public class ThisReference extends JavaExpression { - /** - * Create a new ThisReference. - * - * @param type the type of the {@code this} reference - */ - public ThisReference(TypeMirror type) { - super(type); - } + /** + * Create a new ThisReference. + * + * @param type the type of the {@code this} reference + */ + public ThisReference(TypeMirror type) { + super(type); + } - @Override - public boolean equals(@Nullable Object obj) { - return obj instanceof ThisReference; - } + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof ThisReference; + } - @Override - public int hashCode() { - return 0; - } + @Override + public int hashCode() { + return 0; + } - @Override - public String toString() { - return "this"; - } + @Override + public String toString() { + return "this"; + } - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } - @Override - public boolean isUnassignableByOtherCode() { - return true; - } + @Override + public boolean isUnassignableByOtherCode() { + return true; + } - @Override - public boolean isUnmodifiableByOtherCode() { - return TypesUtils.isImmutableTypeInJdk(type); - } + @Override + public boolean isUnmodifiableByOtherCode() { + return TypesUtils.isImmutableTypeInJdk(type); + } - @Override - public boolean syntacticEquals(JavaExpression je) { - return je instanceof ThisReference; - } + @Override + public boolean syntacticEquals(JavaExpression je) { + return je instanceof ThisReference; + } - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return false; // 'this' is not modifiable - } + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return false; // 'this' is not modifiable + } - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitThisReference(this, p); - } + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitThisReference(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java index 7365833b77c..7b61243c2b2 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/UnaryOperation.java @@ -1,147 +1,150 @@ package org.checkerframework.dataflow.expression; import com.sun.source.tree.Tree; -import java.util.Objects; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.UnaryOperationNode; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; +import java.util.Objects; + +import javax.lang.model.type.TypeMirror; + /** JavaExpression for unary operations. */ public class UnaryOperation extends JavaExpression { - /** The unary operation kind. */ - protected final Tree.Kind operationKind; - - /** The operand. */ - protected final JavaExpression operand; - - /** - * Create a unary operation. - * - * @param type the type of the result - * @param operationKind the operator - * @param operand the operand - */ - public UnaryOperation(TypeMirror type, Tree.Kind operationKind, JavaExpression operand) { - super(operand.type); - this.operationKind = operationKind; - this.operand = operand; - } - - /** - * Create a unary operation. - * - * @param node the unary operation node - * @param operand the operand - */ - public UnaryOperation(UnaryOperationNode node, JavaExpression operand) { - this(node.getType(), node.getTree().getKind(), operand); - } - - /** - * Returns the operator of this unary operation. - * - * @return the unary operation kind - */ - public Tree.Kind getOperationKind() { - return operationKind; - } - - /** - * Returns the operand of this unary operation. - * - * @return the operand - */ - public JavaExpression getOperand() { - return operand; - } - - @Override - public boolean containsOfClass(Class clazz) { - if (getClass() == clazz) { - return true; + /** The unary operation kind. */ + protected final Tree.Kind operationKind; + + /** The operand. */ + protected final JavaExpression operand; + + /** + * Create a unary operation. + * + * @param type the type of the result + * @param operationKind the operator + * @param operand the operand + */ + public UnaryOperation(TypeMirror type, Tree.Kind operationKind, JavaExpression operand) { + super(operand.type); + this.operationKind = operationKind; + this.operand = operand; + } + + /** + * Create a unary operation. + * + * @param node the unary operation node + * @param operand the operand + */ + public UnaryOperation(UnaryOperationNode node, JavaExpression operand) { + this(node.getType(), node.getTree().getKind(), operand); + } + + /** + * Returns the operator of this unary operation. + * + * @return the unary operation kind + */ + public Tree.Kind getOperationKind() { + return operationKind; + } + + /** + * Returns the operand of this unary operation. + * + * @return the operand + */ + public JavaExpression getOperand() { + return operand; } - return operand.containsOfClass(clazz); - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return operand.isDeterministic(provider); - } - - @Override - public boolean isUnassignableByOtherCode() { - return operand.isUnassignableByOtherCode(); - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return operand.isUnmodifiableByOtherCode(); - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - if (!(je instanceof UnaryOperation)) { - return false; + + @Override + public boolean containsOfClass(Class clazz) { + if (getClass() == clazz) { + return true; + } + return operand.containsOfClass(clazz); } - UnaryOperation other = (UnaryOperation) je; - return operationKind == other.getOperationKind() && operand.syntacticEquals(other.operand); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other) || operand.containsSyntacticEqualJavaExpression(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return operand.containsModifiableAliasOf(store, other); - } - - @Override - public int hashCode() { - return Objects.hash(operationKind, operand); - } - - @Override - public boolean equals(@Nullable Object other) { - if (!(other instanceof UnaryOperation)) { - return false; + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return operand.isDeterministic(provider); } - UnaryOperation unOp = (UnaryOperation) other; - return operationKind == unOp.getOperationKind() && operand.equals(unOp.operand); - } - - @Override - public String toString() { - String operandString = operand.toString(); - switch (operationKind) { - case BITWISE_COMPLEMENT: - return "~" + operandString; - case LOGICAL_COMPLEMENT: - return "!" + operandString; - case POSTFIX_DECREMENT: - return operandString + "--"; - case POSTFIX_INCREMENT: - return operandString + "++"; - case PREFIX_DECREMENT: - return "--" + operandString; - case PREFIX_INCREMENT: - return "++" + operandString; - case UNARY_MINUS: - return "-" + operandString; - case UNARY_PLUS: - return "+" + operandString; - default: - throw new BugInCF("Unrecognized unary operation kind " + operationKind); + + @Override + public boolean isUnassignableByOtherCode() { + return operand.isUnassignableByOtherCode(); } - } - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitUnaryOperation(this, p); - } + @Override + public boolean isUnmodifiableByOtherCode() { + return operand.isUnmodifiableByOtherCode(); + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + if (!(je instanceof UnaryOperation)) { + return false; + } + UnaryOperation other = (UnaryOperation) je; + return operationKind == other.getOperationKind() && operand.syntacticEquals(other.operand); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other) || operand.containsSyntacticEqualJavaExpression(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return operand.containsModifiableAliasOf(store, other); + } + + @Override + public int hashCode() { + return Objects.hash(operationKind, operand); + } + + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof UnaryOperation)) { + return false; + } + UnaryOperation unOp = (UnaryOperation) other; + return operationKind == unOp.getOperationKind() && operand.equals(unOp.operand); + } + + @Override + public String toString() { + String operandString = operand.toString(); + switch (operationKind) { + case BITWISE_COMPLEMENT: + return "~" + operandString; + case LOGICAL_COMPLEMENT: + return "!" + operandString; + case POSTFIX_DECREMENT: + return operandString + "--"; + case POSTFIX_INCREMENT: + return operandString + "++"; + case PREFIX_DECREMENT: + return "--" + operandString; + case PREFIX_INCREMENT: + return "++" + operandString; + case UNARY_MINUS: + return "-" + operandString; + case UNARY_PLUS: + return "+" + operandString; + default: + throw new BugInCF("Unrecognized unary operation kind " + operationKind); + } + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitUnaryOperation(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java index 309c1c25683..50962765e26 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/Unknown.java @@ -1,7 +1,7 @@ package org.checkerframework.dataflow.expression; import com.sun.source.tree.Tree; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.interning.qual.UsesObjectEquals; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; @@ -9,105 +9,107 @@ import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.type.TypeMirror; + /** Stands for any expression that the Dataflow Framework lacks explicit support for. */ @UsesObjectEquals public class Unknown extends JavaExpression { - /** String representation of the expression that has no corresponding {@code JavaExpression}. */ - private final String originalExpression; - - /** - * Create a new Unknown JavaExpression. - * - * @param type the Java type of this - */ - public Unknown(TypeMirror type) { - this(type, "?"); - } - - /** - * Create a new Unknown JavaExpression. - * - * @param type the Java type of this - * @param originalExpression a String representation of the expression that has no corresponding - * {@code JavaExpression} - */ - public Unknown(TypeMirror type, String originalExpression) { - super(type); - this.originalExpression = originalExpression; - } - - /** - * Create a new Unknown JavaExpression. - * - * @param tree a tree that does not have a corresponding {@code JavaExpression} - */ - public Unknown(Tree tree) { - this(TreeUtils.typeOf(tree), TreeUtils.toStringTruncated(tree, 40)); - } - - /** - * Create a new Unknown JavaExpression. - * - * @param node a node that does not have a corresponding {@code JavaExpression} - */ - public Unknown(Node node) { - this(node.getType(), node.toString()); - } - - @Override - public boolean equals(@Nullable Object obj) { - return obj == this; - } - - // Overridden to avoid an error "overrides equals, but does not override hashCode" - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public String toString() { - return originalExpression; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return false; - } - - @Override - public boolean isUnassignableByOtherCode() { - return false; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return false; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - return this == je; - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return true; - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitUnknown(this, p); - } + /** String representation of the expression that has no corresponding {@code JavaExpression}. */ + private final String originalExpression; + + /** + * Create a new Unknown JavaExpression. + * + * @param type the Java type of this + */ + public Unknown(TypeMirror type) { + this(type, "?"); + } + + /** + * Create a new Unknown JavaExpression. + * + * @param type the Java type of this + * @param originalExpression a String representation of the expression that has no corresponding + * {@code JavaExpression} + */ + public Unknown(TypeMirror type, String originalExpression) { + super(type); + this.originalExpression = originalExpression; + } + + /** + * Create a new Unknown JavaExpression. + * + * @param tree a tree that does not have a corresponding {@code JavaExpression} + */ + public Unknown(Tree tree) { + this(TreeUtils.typeOf(tree), TreeUtils.toStringTruncated(tree, 40)); + } + + /** + * Create a new Unknown JavaExpression. + * + * @param node a node that does not have a corresponding {@code JavaExpression} + */ + public Unknown(Node node) { + this(node.getType(), node.toString()); + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj == this; + } + + // Overridden to avoid an error "overrides equals, but does not override hashCode" + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return originalExpression; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return false; + } + + @Override + public boolean isUnassignableByOtherCode() { + return false; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + return this == je; + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return true; + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitUnknown(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java index 02940123b8b..65c90bdb4ad 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java @@ -1,9 +1,5 @@ package org.checkerframework.dataflow.expression; -import java.math.BigInteger; -import java.util.Objects; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.ValueLiteralNode; @@ -11,162 +7,168 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; +import java.math.BigInteger; +import java.util.Objects; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** JavaExpression for literals. */ public class ValueLiteral extends JavaExpression { - /** The value of the literal. */ - protected final @Nullable Object value; - - /** The negative of Long.MIN_VALUE, which does not fit in a long. */ - private static final BigInteger NEGATIVE_LONG_MIN_VALUE = new BigInteger("9223372036854775808"); - - /** - * Creates a ValueLiteral from the node with the given type. - * - * @param type type of the literal - * @param node the literal represents by this {@link - * org.checkerframework.dataflow.expression.ValueLiteral} - */ - public ValueLiteral(TypeMirror type, ValueLiteralNode node) { - super(type); - value = node.getValue(); - } - - /** - * Creates a ValueLiteral where the value is {@code value} that has the given type. - * - * @param type type of the literal - * @param value the literal value - */ - public ValueLiteral(TypeMirror type, @Nullable Object value) { - super(type); - this.value = value; - } - - /** - * Returns the negation of this literal. Throws an exception if negation is not possible. - * - * @return the negation of this literal - */ - public ValueLiteral negate() { - if (TypesUtils.isIntegralPrimitive(type)) { - if (value == null) { - throw new BugInCF("null value of integral type " + type); - } - return new ValueLiteral(type, negateBoxedPrimitive(value)); - } - throw new BugInCF(String.format("cannot negate: %s type=%s", this, type)); - } - - /** - * Negate a boxed primitive. - * - * @param o a boxed primitive - * @return a boxed primitive that is the negation of the argument - */ - private Object negateBoxedPrimitive(Object o) { - if (value instanceof Byte) { - return (byte) -(Byte) value; - } - if (value instanceof Short) { - return (short) -(Short) value; - } - if (value instanceof Integer) { - return -(Integer) value; - } - if (value instanceof Long) { - return -(Long) value; - } - if (value instanceof Float) { - return -(Float) value; - } - if (value instanceof Double) { - return -(Double) value; - } - if (value instanceof BigInteger) { - assert value.equals(NEGATIVE_LONG_MIN_VALUE); - return Long.MIN_VALUE; - } - throw new BugInCF("Cannot be negated: " + o + " " + o.getClass()); - } - - /** - * Returns the value of this literal. - * - * @return the value of this literal - */ - public @Nullable Object getValue() { - return value; - } - - @Override - public boolean containsOfClass(Class clazz) { - return getClass() == clazz; - } - - @Override - public boolean isDeterministic(AnnotationProvider provider) { - return true; - } - - @Override - public boolean isUnassignableByOtherCode() { - return true; - } - - @Override - public boolean isUnmodifiableByOtherCode() { - return true; - } - - @Override - public boolean syntacticEquals(JavaExpression je) { - return this.equals(je); - } - - @Override - public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { - return this.syntacticEquals(other); - } - - @Override - public boolean containsModifiableAliasOf(Store store, JavaExpression other) { - return false; // not modifiable - } - - /// java.lang.Object methods - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ValueLiteral)) { - return false; - } - ValueLiteral other = (ValueLiteral) obj; - // TODO: Can this string comparison be cleaned up? - // Cannot use Types.isSameType(type, other.type) because we don't have a Types object. - return type.toString().equals(other.type.toString()) && Objects.equals(value, other.value); - } - - @Override - public String toString() { - if (TypesUtils.isString(type)) { - return "\"" + value + "\""; - } else if (type.getKind() == TypeKind.LONG) { - assert value != null : "@AssumeAssertion(nullness): invariant"; - return value.toString() + "L"; - } else if (type.getKind() == TypeKind.CHAR) { - return "\'" + value + "\'"; - } - return value == null ? "null" : value.toString(); - } - - @Override - public int hashCode() { - return Objects.hash(value, type.toString()); - } - - @Override - public R accept(JavaExpressionVisitor visitor, P p) { - return visitor.visitValueLiteral(this, p); - } + /** The value of the literal. */ + protected final @Nullable Object value; + + /** The negative of Long.MIN_VALUE, which does not fit in a long. */ + private static final BigInteger NEGATIVE_LONG_MIN_VALUE = new BigInteger("9223372036854775808"); + + /** + * Creates a ValueLiteral from the node with the given type. + * + * @param type type of the literal + * @param node the literal represents by this {@link + * org.checkerframework.dataflow.expression.ValueLiteral} + */ + public ValueLiteral(TypeMirror type, ValueLiteralNode node) { + super(type); + value = node.getValue(); + } + + /** + * Creates a ValueLiteral where the value is {@code value} that has the given type. + * + * @param type type of the literal + * @param value the literal value + */ + public ValueLiteral(TypeMirror type, @Nullable Object value) { + super(type); + this.value = value; + } + + /** + * Returns the negation of this literal. Throws an exception if negation is not possible. + * + * @return the negation of this literal + */ + public ValueLiteral negate() { + if (TypesUtils.isIntegralPrimitive(type)) { + if (value == null) { + throw new BugInCF("null value of integral type " + type); + } + return new ValueLiteral(type, negateBoxedPrimitive(value)); + } + throw new BugInCF(String.format("cannot negate: %s type=%s", this, type)); + } + + /** + * Negate a boxed primitive. + * + * @param o a boxed primitive + * @return a boxed primitive that is the negation of the argument + */ + private Object negateBoxedPrimitive(Object o) { + if (value instanceof Byte) { + return (byte) -(Byte) value; + } + if (value instanceof Short) { + return (short) -(Short) value; + } + if (value instanceof Integer) { + return -(Integer) value; + } + if (value instanceof Long) { + return -(Long) value; + } + if (value instanceof Float) { + return -(Float) value; + } + if (value instanceof Double) { + return -(Double) value; + } + if (value instanceof BigInteger) { + assert value.equals(NEGATIVE_LONG_MIN_VALUE); + return Long.MIN_VALUE; + } + throw new BugInCF("Cannot be negated: " + o + " " + o.getClass()); + } + + /** + * Returns the value of this literal. + * + * @return the value of this literal + */ + public @Nullable Object getValue() { + return value; + } + + @Override + public boolean containsOfClass(Class clazz) { + return getClass() == clazz; + } + + @Override + public boolean isDeterministic(AnnotationProvider provider) { + return true; + } + + @Override + public boolean isUnassignableByOtherCode() { + return true; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean syntacticEquals(JavaExpression je) { + return this.equals(je); + } + + @Override + public boolean containsSyntacticEqualJavaExpression(JavaExpression other) { + return this.syntacticEquals(other); + } + + @Override + public boolean containsModifiableAliasOf(Store store, JavaExpression other) { + return false; // not modifiable + } + + /// java.lang.Object methods + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ValueLiteral)) { + return false; + } + ValueLiteral other = (ValueLiteral) obj; + // TODO: Can this string comparison be cleaned up? + // Cannot use Types.isSameType(type, other.type) because we don't have a Types object. + return type.toString().equals(other.type.toString()) && Objects.equals(value, other.value); + } + + @Override + public String toString() { + if (TypesUtils.isString(type)) { + return "\"" + value + "\""; + } else if (type.getKind() == TypeKind.LONG) { + assert value != null : "@AssumeAssertion(nullness): invariant"; + return value.toString() + "L"; + } else if (type.getKind() == TypeKind.CHAR) { + return "\'" + value + "\'"; + } + return value == null ? "null" : value.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(value, type.toString()); + } + + @Override + public R accept(JavaExpressionVisitor visitor, P p) { + return visitor.visitValueLiteral(this, p); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java index 422a70ea847..26f129e5948 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/ViewpointAdaptJavaExpression.java @@ -1,103 +1,104 @@ package org.checkerframework.dataflow.expression; -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + /** * This class has methods to viewpoint-adapt {@link JavaExpression} by replacing {@link * ThisReference} and {@link FormalParameter} expressions with the given {@link JavaExpression}s. */ public class ViewpointAdaptJavaExpression extends JavaExpressionConverter { - // Public static methods + // Public static methods - /** - * Replace {@link FormalParameter}s by {@code args} in {@code javaExpr}. ({@link ThisReference}s - * are not converted.) - * - * @param javaExpr the expression to viewpoint-adapt - * @param args the expressions that replace {@link FormalParameter}s; if null, {@link - * FormalParameter}s are not replaced - * @return the viewpoint-adapted expression - */ - public static JavaExpression viewpointAdapt( - JavaExpression javaExpr, @Nullable List args) { - return viewpointAdapt(javaExpr, null, args); - } + /** + * Replace {@link FormalParameter}s by {@code args} in {@code javaExpr}. ({@link ThisReference}s + * are not converted.) + * + * @param javaExpr the expression to viewpoint-adapt + * @param args the expressions that replace {@link FormalParameter}s; if null, {@link + * FormalParameter}s are not replaced + * @return the viewpoint-adapted expression + */ + public static JavaExpression viewpointAdapt( + JavaExpression javaExpr, @Nullable List args) { + return viewpointAdapt(javaExpr, null, args); + } - /** - * Replace {@link ThisReference} with {@code thisReference} in {@code javaExpr}. ({@link - * FormalParameter} are not replaced. - * - * @param javaExpr the expression to viewpoint-adapt - * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if - * null, {@link ThisReference}s are not replaced - * @return the viewpoint-adapted expression - */ - public static JavaExpression viewpointAdapt( - JavaExpression javaExpr, @Nullable JavaExpression thisReference) { - return viewpointAdapt(javaExpr, thisReference, null); - } + /** + * Replace {@link ThisReference} with {@code thisReference} in {@code javaExpr}. ({@link + * FormalParameter} are not replaced. + * + * @param javaExpr the expression to viewpoint-adapt + * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if + * null, {@link ThisReference}s are not replaced + * @return the viewpoint-adapted expression + */ + public static JavaExpression viewpointAdapt( + JavaExpression javaExpr, @Nullable JavaExpression thisReference) { + return viewpointAdapt(javaExpr, thisReference, null); + } - /** - * Replace {@link FormalParameter}s with {@code args} and {@link ThisReference} with {@code - * thisReference} in {@code javaExpr}. - * - * @param javaExpr the expression to viewpoint-adapt - * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if - * null, {@link ThisReference}s are not replaced - * @param args the expressions that replaces {@link FormalParameter}s; if null, {@link - * FormalParameter}s are not replaced - * @return the viewpoint-adapted expression - */ - public static JavaExpression viewpointAdapt( - JavaExpression javaExpr, - @Nullable JavaExpression thisReference, - @Nullable List args) { - return new ViewpointAdaptJavaExpression(thisReference, args).convert(javaExpr); - } + /** + * Replace {@link FormalParameter}s with {@code args} and {@link ThisReference} with {@code + * thisReference} in {@code javaExpr}. + * + * @param javaExpr the expression to viewpoint-adapt + * @param thisReference the expression that replaces occurrences of {@link ThisReference}; if + * null, {@link ThisReference}s are not replaced + * @param args the expressions that replaces {@link FormalParameter}s; if null, {@link + * FormalParameter}s are not replaced + * @return the viewpoint-adapted expression + */ + public static JavaExpression viewpointAdapt( + JavaExpression javaExpr, + @Nullable JavaExpression thisReference, + @Nullable List args) { + return new ViewpointAdaptJavaExpression(thisReference, args).convert(javaExpr); + } - // Fields + // Fields - /** List of arguments used to replace occurrences {@link FormalParameter}s. */ - private final @Nullable List args; + /** List of arguments used to replace occurrences {@link FormalParameter}s. */ + private final @Nullable List args; - /** The expression to replace occurrences of {@link ThisReference}s. */ - private final @Nullable JavaExpression thisReference; + /** The expression to replace occurrences of {@link ThisReference}s. */ + private final @Nullable JavaExpression thisReference; - // Instance methods + // Instance methods - /** - * Creates a {@link JavaExpressionConverter} that viewpoint-adapts using the given {@code - * thisReference} and {@code args}. - * - * @param thisReference the expression that replaces occurrences of {@link ThisReference}; {@code - * null} means don't replace - * @param args list of arguments that replaces occurrences {@link FormalParameter}s; {@code null} - * means don't replace - */ - private ViewpointAdaptJavaExpression( - @Nullable JavaExpression thisReference, @Nullable List args) { - this.args = args; - this.thisReference = thisReference; - } + /** + * Creates a {@link JavaExpressionConverter} that viewpoint-adapts using the given {@code + * thisReference} and {@code args}. + * + * @param thisReference the expression that replaces occurrences of {@link ThisReference}; + * {@code null} means don't replace + * @param args list of arguments that replaces occurrences {@link FormalParameter}s; {@code + * null} means don't replace + */ + private ViewpointAdaptJavaExpression( + @Nullable JavaExpression thisReference, @Nullable List args) { + this.args = args; + this.thisReference = thisReference; + } - @Override - protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { - if (thisReference != null) { - return thisReference; + @Override + protected JavaExpression visitThisReference(ThisReference thisExpr, Void unused) { + if (thisReference != null) { + return thisReference; + } + return super.visitThisReference(thisExpr, unused); } - return super.visitThisReference(thisExpr, unused); - } - @Override - protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { - if (args != null) { - int index = parameterExpr.getIndex() - 1; - if (index < args.size()) { - return args.get(index); - } + @Override + protected JavaExpression visitFormalParameter(FormalParameter parameterExpr, Void unused) { + if (args != null) { + int index = parameterExpr.getIndex() - 1; + if (index < args.size()) { + return args.get(index); + } + } + return super.visitFormalParameter(parameterExpr, unused); } - return super.visitFormalParameter(parameterExpr, unused); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java index 42b28360e11..c40558fa312 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarNode.java @@ -14,40 +14,40 @@ */ public class LiveVarNode { - /** - * A live variable is represented by a node, which can be a {@link - * org.checkerframework.dataflow.cfg.node.LocalVariableNode} or {@link - * org.checkerframework.dataflow.cfg.node.FieldAccessNode}. - */ - protected final Node liveVariable; + /** + * A live variable is represented by a node, which can be a {@link + * org.checkerframework.dataflow.cfg.node.LocalVariableNode} or {@link + * org.checkerframework.dataflow.cfg.node.FieldAccessNode}. + */ + protected final Node liveVariable; - /** - * Create a new live variable. - * - * @param n a node - */ - public LiveVarNode(Node n) { - assert n instanceof FieldAccessNode || n instanceof LocalVariableNode; - this.liveVariable = n; - } + /** + * Create a new live variable. + * + * @param n a node + */ + public LiveVarNode(Node n) { + assert n instanceof FieldAccessNode || n instanceof LocalVariableNode; + this.liveVariable = n; + } - @Override - public int hashCode() { - return this.liveVariable.hashCode(); - } + @Override + public int hashCode() { + return this.liveVariable.hashCode(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LiveVarNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LiveVarNode)) { + return false; + } + LiveVarNode other = (LiveVarNode) obj; + // We use `.equals` instead of `==` here to compare value equality. + return this.liveVariable.equals(other.liveVariable); } - LiveVarNode other = (LiveVarNode) obj; - // We use `.equals` instead of `==` here to compare value equality. - return this.liveVariable.equals(other.liveVariable); - } - @Override - public String toString() { - return this.liveVariable.toString(); - } + @Override + public String toString() { + return this.liveVariable.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java index 5c9de01162f..a85f111c0ec 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarStore.java @@ -1,8 +1,5 @@ package org.checkerframework.dataflow.livevariable; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.BinaryOperationNode; @@ -18,131 +15,135 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.ArraySet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + /** A live variable store contains a set of live variables represented by nodes. */ public class LiveVarStore implements Store { - /** The set of live variables in this store */ - private final Set liveVarNodeSet; - - /** Create a new LiveVarStore. */ - public LiveVarStore() { - liveVarNodeSet = new LinkedHashSet<>(); - } - - /** - * Create a new LiveVarStore. - * - * @param liveVarNodeSet the set of live variable nodes. The parameter is captured and the caller - * should not retain an alias. - */ - public LiveVarStore(Set liveVarNodeSet) { - this.liveVarNodeSet = liveVarNodeSet; - } - - /** - * Add the information of a live variable into the live variable set. - * - * @param variable a live variable - */ - public void putLiveVar(LiveVarNode variable) { - liveVarNodeSet.add(variable); - } - - /** - * Remove the information of a live variable from the live variable set. - * - * @param variable a live variable - */ - public void killLiveVar(LiveVarNode variable) { - liveVarNodeSet.remove(variable); - } - - /** - * Add the information of live variables in an expression to the live variable set. - * - * @param expression a node - */ - public void addUseInExpression(Node expression) { - // TODO Do we need a AbstractNodeScanner to do the following job? - if (expression instanceof LocalVariableNode || expression instanceof FieldAccessNode) { - LiveVarNode liveVarValue = new LiveVarNode(expression); - putLiveVar(liveVarValue); - } else if (expression instanceof UnaryOperationNode) { - UnaryOperationNode unaryNode = (UnaryOperationNode) expression; - addUseInExpression(unaryNode.getOperand()); - } else if (expression instanceof TernaryExpressionNode) { - TernaryExpressionNode ternaryNode = (TernaryExpressionNode) expression; - addUseInExpression(ternaryNode.getConditionOperand()); - addUseInExpression(ternaryNode.getThenOperand()); - addUseInExpression(ternaryNode.getElseOperand()); - } else if (expression instanceof TypeCastNode) { - TypeCastNode typeCastNode = (TypeCastNode) expression; - addUseInExpression(typeCastNode.getOperand()); - } else if (expression instanceof InstanceOfNode) { - InstanceOfNode instanceOfNode = (InstanceOfNode) expression; - addUseInExpression(instanceOfNode.getOperand()); - } else if (expression instanceof BinaryOperationNode) { - BinaryOperationNode binaryNode = (BinaryOperationNode) expression; - addUseInExpression(binaryNode.getLeftOperand()); - addUseInExpression(binaryNode.getRightOperand()); + /** The set of live variables in this store */ + private final Set liveVarNodeSet; + + /** Create a new LiveVarStore. */ + public LiveVarStore() { + liveVarNodeSet = new LinkedHashSet<>(); + } + + /** + * Create a new LiveVarStore. + * + * @param liveVarNodeSet the set of live variable nodes. The parameter is captured and the + * caller should not retain an alias. + */ + public LiveVarStore(Set liveVarNodeSet) { + this.liveVarNodeSet = liveVarNodeSet; } - } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof LiveVarStore)) { - return false; + /** + * Add the information of a live variable into the live variable set. + * + * @param variable a live variable + */ + public void putLiveVar(LiveVarNode variable) { + liveVarNodeSet.add(variable); } - LiveVarStore other = (LiveVarStore) obj; - return other.liveVarNodeSet.equals(this.liveVarNodeSet); - } - - @Override - public int hashCode() { - return this.liveVarNodeSet.hashCode(); - } - - @Override - public LiveVarStore copy() { - return new LiveVarStore(new LinkedHashSet<>(liveVarNodeSet)); - } - - @Override - public LiveVarStore leastUpperBound(LiveVarStore other) { - Set liveVarNodeSetLub = - ArraySet.newArraySetOrLinkedHashSet( - this.liveVarNodeSet.size() + other.liveVarNodeSet.size()); - liveVarNodeSetLub.addAll(this.liveVarNodeSet); - liveVarNodeSetLub.addAll(other.liveVarNodeSet); - return new LiveVarStore(liveVarNodeSetLub); - } - - /** It should not be called since it is not used by the backward analysis. */ - @Override - public LiveVarStore widenedUpperBound(LiveVarStore previous) { - throw new BugInCF("wub of LiveVarStore get called!"); - } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; - } - - @Override - public String visualize(CFGVisualizer viz) { - String key = "live variables"; - if (liveVarNodeSet.isEmpty()) { - return viz.visualizeStoreKeyVal(key, "none"); + + /** + * Remove the information of a live variable from the live variable set. + * + * @param variable a live variable + */ + public void killLiveVar(LiveVarNode variable) { + liveVarNodeSet.remove(variable); } - StringJoiner sjStoreVal = new StringJoiner(", "); - for (LiveVarNode liveVar : liveVarNodeSet) { - sjStoreVal.add(liveVar.toString()); + + /** + * Add the information of live variables in an expression to the live variable set. + * + * @param expression a node + */ + public void addUseInExpression(Node expression) { + // TODO Do we need a AbstractNodeScanner to do the following job? + if (expression instanceof LocalVariableNode || expression instanceof FieldAccessNode) { + LiveVarNode liveVarValue = new LiveVarNode(expression); + putLiveVar(liveVarValue); + } else if (expression instanceof UnaryOperationNode) { + UnaryOperationNode unaryNode = (UnaryOperationNode) expression; + addUseInExpression(unaryNode.getOperand()); + } else if (expression instanceof TernaryExpressionNode) { + TernaryExpressionNode ternaryNode = (TernaryExpressionNode) expression; + addUseInExpression(ternaryNode.getConditionOperand()); + addUseInExpression(ternaryNode.getThenOperand()); + addUseInExpression(ternaryNode.getElseOperand()); + } else if (expression instanceof TypeCastNode) { + TypeCastNode typeCastNode = (TypeCastNode) expression; + addUseInExpression(typeCastNode.getOperand()); + } else if (expression instanceof InstanceOfNode) { + InstanceOfNode instanceOfNode = (InstanceOfNode) expression; + addUseInExpression(instanceOfNode.getOperand()); + } else if (expression instanceof BinaryOperationNode) { + BinaryOperationNode binaryNode = (BinaryOperationNode) expression; + addUseInExpression(binaryNode.getLeftOperand()); + addUseInExpression(binaryNode.getRightOperand()); + } + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LiveVarStore)) { + return false; + } + LiveVarStore other = (LiveVarStore) obj; + return other.liveVarNodeSet.equals(this.liveVarNodeSet); } - return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); - } - @Override - public String toString() { - return liveVarNodeSet.toString(); - } + @Override + public int hashCode() { + return this.liveVarNodeSet.hashCode(); + } + + @Override + public LiveVarStore copy() { + return new LiveVarStore(new LinkedHashSet<>(liveVarNodeSet)); + } + + @Override + public LiveVarStore leastUpperBound(LiveVarStore other) { + Set liveVarNodeSetLub = + ArraySet.newArraySetOrLinkedHashSet( + this.liveVarNodeSet.size() + other.liveVarNodeSet.size()); + liveVarNodeSetLub.addAll(this.liveVarNodeSet); + liveVarNodeSetLub.addAll(other.liveVarNodeSet); + return new LiveVarStore(liveVarNodeSetLub); + } + + /** It should not be called since it is not used by the backward analysis. */ + @Override + public LiveVarStore widenedUpperBound(LiveVarStore previous) { + throw new BugInCF("wub of LiveVarStore get called!"); + } + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; + } + + @Override + public String visualize(CFGVisualizer viz) { + String key = "live variables"; + if (liveVarNodeSet.isEmpty()) { + return viz.visualizeStoreKeyVal(key, "none"); + } + StringJoiner sjStoreVal = new StringJoiner(", "); + for (LiveVarNode liveVar : liveVarNodeSet) { + sjStoreVal.add(liveVar.toString()); + } + return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); + } + + @Override + public String toString() { + return liveVarNodeSet.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java index 4bee886ffb9..dcc9557fda5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/livevariable/LiveVarTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.dataflow.livevariable; -import java.util.List; import org.checkerframework.dataflow.analysis.BackwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; @@ -15,90 +14,95 @@ import org.checkerframework.dataflow.cfg.node.ReturnNode; import org.checkerframework.dataflow.qual.SideEffectFree; +import java.util.List; + /** A live variable transfer function. */ public class LiveVarTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements BackwardTransferFunction { + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements BackwardTransferFunction { - /** Creates a new LiveVarTransfer. */ - public LiveVarTransfer() {} + /** Creates a new LiveVarTransfer. */ + public LiveVarTransfer() {} - @Override - @SideEffectFree - public LiveVarStore initialNormalExitStore( - UnderlyingAST underlyingAST, List returnNodes) { - return new LiveVarStore(); - } + @Override + @SideEffectFree + public LiveVarStore initialNormalExitStore( + UnderlyingAST underlyingAST, List returnNodes) { + return new LiveVarStore(); + } - @Override - public LiveVarStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { - return new LiveVarStore(); - } + @Override + public LiveVarStore initialExceptionalExitStore(UnderlyingAST underlyingAST) { + return new LiveVarStore(); + } - @Override - public RegularTransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } + @Override + public RegularTransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } - @Override - public RegularTransferResult visitAssignment( - AssignmentNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitAssignment(n, p); - processLiveVarInAssignment(n.getTarget(), n.getExpression(), transferResult.getRegularStore()); - return transferResult; - } + @Override + public RegularTransferResult visitAssignment( + AssignmentNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitAssignment(n, p); + processLiveVarInAssignment( + n.getTarget(), n.getExpression(), transferResult.getRegularStore()); + return transferResult; + } - @Override - public RegularTransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitMethodInvocation(n, p); - LiveVarStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); + @Override + public RegularTransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitMethodInvocation(n, p); + LiveVarStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); + } + return transferResult; } - return transferResult; - } - @Override - public RegularTransferResult visitObjectCreation( - ObjectCreationNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitObjectCreation(n, p); - LiveVarStore store = transferResult.getRegularStore(); - for (Node arg : n.getArguments()) { - store.addUseInExpression(arg); + @Override + public RegularTransferResult visitObjectCreation( + ObjectCreationNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitObjectCreation(n, p); + LiveVarStore store = transferResult.getRegularStore(); + for (Node arg : n.getArguments()) { + store.addUseInExpression(arg); + } + return transferResult; } - return transferResult; - } - @Override - public RegularTransferResult visitReturn( - ReturnNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) super.visitReturn(n, p); - Node result = n.getResult(); - if (result != null) { - LiveVarStore store = transferResult.getRegularStore(); - store.addUseInExpression(result); + @Override + public RegularTransferResult visitReturn( + ReturnNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) super.visitReturn(n, p); + Node result = n.getResult(); + if (result != null) { + LiveVarStore store = transferResult.getRegularStore(); + store.addUseInExpression(result); + } + return transferResult; } - return transferResult; - } - /** - * Update the information of live variables from an assignment statement. - * - * @param variable the variable that should be killed - * @param expression the expression in which the variables should be added - * @param store the live variable store - */ - private void processLiveVarInAssignment(Node variable, Node expression, LiveVarStore store) { - store.killLiveVar(new LiveVarNode(variable)); - store.addUseInExpression(expression); - } + /** + * Update the information of live variables from an assignment statement. + * + * @param variable the variable that should be killed + * @param expression the expression in which the variables should be added + * @param store the live variable store + */ + private void processLiveVarInAssignment(Node variable, Node expression, LiveVarStore store) { + store.killLiveVar(new LiveVarNode(variable)); + store.addUseInExpression(expression); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java index 0b9ec2cb19b..4488f47ef4f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionNode.java @@ -12,38 +12,38 @@ */ public class ReachingDefinitionNode { - /** - * A reaching definition is represented by a node, which can only be a {@link - * org.checkerframework.dataflow.cfg.node.AssignmentNode}. - */ - protected final AssignmentNode def; + /** + * A reaching definition is represented by a node, which can only be a {@link + * org.checkerframework.dataflow.cfg.node.AssignmentNode}. + */ + protected final AssignmentNode def; - /** - * Create a new reaching definition. - * - * @param n an assignment node - */ - public ReachingDefinitionNode(AssignmentNode n) { - this.def = n; - } + /** + * Create a new reaching definition. + * + * @param n an assignment node + */ + public ReachingDefinitionNode(AssignmentNode n) { + this.def = n; + } - @Override - public int hashCode() { - return this.def.hashCode(); - } + @Override + public int hashCode() { + return this.def.hashCode(); + } - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ReachingDefinitionNode)) { - return false; + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ReachingDefinitionNode)) { + return false; + } + ReachingDefinitionNode other = (ReachingDefinitionNode) obj; + // We use `.equals` instead of `==` here to compare value equality. + return this.def.equals(other.def); } - ReachingDefinitionNode other = (ReachingDefinitionNode) obj; - // We use `.equals` instead of `==` here to compare value equality. - return this.def.equals(other.def); - } - @Override - public String toString() { - return this.def.toString(); - } + @Override + public String toString() { + return this.def.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java index 865e8657033..d56d7fc46db 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionStore.java @@ -1,9 +1,5 @@ package org.checkerframework.dataflow.reachingdef; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.Node; @@ -11,111 +7,116 @@ import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.javacutil.BugInCF; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + /** * A reaching definition store contains a set of reaching definitions represented by * ReachingDefinitionNode */ public class ReachingDefinitionStore implements Store { - /** The set of reaching definitions in this store */ - private final Set reachingDefSet; - - /** Create a new ReachDefinitionStore. */ - public ReachingDefinitionStore() { - reachingDefSet = new LinkedHashSet<>(); - } - - /** - * Create a new ReachDefinitionStore. - * - * @param reachingDefSet a set of reaching definition nodes. The parameter is captured and the - * caller should not retain an alias. - */ - public ReachingDefinitionStore(Set reachingDefSet) { - this.reachingDefSet = reachingDefSet; - } - - /** - * Remove the information of a reaching definition from the reaching definition set. - * - * @param defTarget target of a reaching definition - */ - public void killDef(Node defTarget) { - Iterator it = reachingDefSet.iterator(); - while (it.hasNext()) { - // We use `.equals` instead of `==` here to compare value equality - // rather than reference equality, because if two left-hand side node - // have same values, we need to kill the old one and replace with the - // new one. - ReachingDefinitionNode generatedDefNode = it.next(); - if (generatedDefNode.def.getTarget().equals(defTarget)) { - it.remove(); - } + /** The set of reaching definitions in this store */ + private final Set reachingDefSet; + + /** Create a new ReachDefinitionStore. */ + public ReachingDefinitionStore() { + reachingDefSet = new LinkedHashSet<>(); + } + + /** + * Create a new ReachDefinitionStore. + * + * @param reachingDefSet a set of reaching definition nodes. The parameter is captured and the + * caller should not retain an alias. + */ + public ReachingDefinitionStore(Set reachingDefSet) { + this.reachingDefSet = reachingDefSet; + } + + /** + * Remove the information of a reaching definition from the reaching definition set. + * + * @param defTarget target of a reaching definition + */ + public void killDef(Node defTarget) { + Iterator it = reachingDefSet.iterator(); + while (it.hasNext()) { + // We use `.equals` instead of `==` here to compare value equality + // rather than reference equality, because if two left-hand side node + // have same values, we need to kill the old one and replace with the + // new one. + ReachingDefinitionNode generatedDefNode = it.next(); + if (generatedDefNode.def.getTarget().equals(defTarget)) { + it.remove(); + } + } } - } - - /** - * Add a reaching definition to the reaching definition set. - * - * @param def a reaching definition - */ - public void putDef(ReachingDefinitionNode def) { - reachingDefSet.add(def); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof ReachingDefinitionStore)) { - return false; + + /** + * Add a reaching definition to the reaching definition set. + * + * @param def a reaching definition + */ + public void putDef(ReachingDefinitionNode def) { + reachingDefSet.add(def); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ReachingDefinitionStore)) { + return false; + } + ReachingDefinitionStore other = (ReachingDefinitionStore) obj; + return other.reachingDefSet.equals(this.reachingDefSet); + } + + @Override + public int hashCode() { + return this.reachingDefSet.hashCode(); + } + + @Override + public ReachingDefinitionStore copy() { + return new ReachingDefinitionStore(new LinkedHashSet<>(reachingDefSet)); } - ReachingDefinitionStore other = (ReachingDefinitionStore) obj; - return other.reachingDefSet.equals(this.reachingDefSet); - } - - @Override - public int hashCode() { - return this.reachingDefSet.hashCode(); - } - - @Override - public ReachingDefinitionStore copy() { - return new ReachingDefinitionStore(new LinkedHashSet<>(reachingDefSet)); - } - - @Override - public ReachingDefinitionStore leastUpperBound(ReachingDefinitionStore other) { - LinkedHashSet reachingDefSetLub = - new LinkedHashSet<>(this.reachingDefSet.size() + other.reachingDefSet.size()); - reachingDefSetLub.addAll(this.reachingDefSet); - reachingDefSetLub.addAll(other.reachingDefSet); - return new ReachingDefinitionStore(reachingDefSetLub); - } - - @Override - public ReachingDefinitionStore widenedUpperBound(ReachingDefinitionStore previous) { - throw new BugInCF("ReachingDefinitionStore.widenedUpperBound was called!"); - } - - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - return true; - } - - @Override - public String visualize(CFGVisualizer viz) { - String key = "reaching definitions"; - if (reachingDefSet.isEmpty()) { - return viz.visualizeStoreKeyVal(key, "none"); + + @Override + public ReachingDefinitionStore leastUpperBound(ReachingDefinitionStore other) { + LinkedHashSet reachingDefSetLub = + new LinkedHashSet<>(this.reachingDefSet.size() + other.reachingDefSet.size()); + reachingDefSetLub.addAll(this.reachingDefSet); + reachingDefSetLub.addAll(other.reachingDefSet); + return new ReachingDefinitionStore(reachingDefSetLub); + } + + @Override + public ReachingDefinitionStore widenedUpperBound(ReachingDefinitionStore previous) { + throw new BugInCF("ReachingDefinitionStore.widenedUpperBound was called!"); } - StringJoiner sjStoreVal = new StringJoiner(", ", "{ ", " }"); - for (ReachingDefinitionNode reachDefNode : reachingDefSet) { - sjStoreVal.add(reachDefNode.toString()); + + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + return true; } - return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); - } - @Override - public String toString() { - return "ReachingDefinitionStore: " + reachingDefSet.toString(); - } + @Override + public String visualize(CFGVisualizer viz) { + String key = "reaching definitions"; + if (reachingDefSet.isEmpty()) { + return viz.visualizeStoreKeyVal(key, "none"); + } + StringJoiner sjStoreVal = new StringJoiner(", ", "{ ", " }"); + for (ReachingDefinitionNode reachDefNode : reachingDefSet) { + sjStoreVal.add(reachDefNode.toString()); + } + return viz.visualizeStoreKeyVal(key, sjStoreVal.toString()); + } + + @Override + public String toString() { + return "ReachingDefinitionStore: " + reachingDefSet.toString(); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java index d4ae76c7554..2ed3b9e0b4f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/reachingdef/ReachingDefinitionTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.dataflow.reachingdef; -import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.ForwardTransferFunction; import org.checkerframework.dataflow.analysis.RegularTransferResult; @@ -13,50 +12,52 @@ import org.checkerframework.dataflow.cfg.node.LocalVariableNode; import org.checkerframework.dataflow.cfg.node.Node; +import java.util.List; + /** * The reaching definition transfer function. The transfer function processes the * ReachingDefinitionNode in ReachingDefinitionStore, killing the node with same LHS and putting new * generated node into the store. See dataflow manual for more details. */ public class ReachingDefinitionTransfer - extends AbstractNodeVisitor< - TransferResult, - TransferInput> - implements ForwardTransferFunction { - - /** Create a new ReachingDefinitionTransfer. */ - public ReachingDefinitionTransfer() {} - - @Override - public ReachingDefinitionStore initialStore( - UnderlyingAST underlyingAST, @Nullable List parameters) { - return new ReachingDefinitionStore(); - } - - @Override - public RegularTransferResult visitNode( - Node n, TransferInput p) { - return new RegularTransferResult<>(null, p.getRegularStore()); - } - - @Override - public RegularTransferResult visitAssignment( - AssignmentNode n, TransferInput p) { - RegularTransferResult transferResult = - (RegularTransferResult) - super.visitAssignment(n, p); - processDefinition(n, transferResult.getRegularStore()); - return transferResult; - } - - /** - * Update a reaching definition node in the store from an assignment statement. - * - * @param def the definition that should be put into the store - * @param store the reaching definition store - */ - private void processDefinition(AssignmentNode def, ReachingDefinitionStore store) { - store.killDef(def.getTarget()); - store.putDef(new ReachingDefinitionNode(def)); - } + extends AbstractNodeVisitor< + TransferResult, + TransferInput> + implements ForwardTransferFunction { + + /** Create a new ReachingDefinitionTransfer. */ + public ReachingDefinitionTransfer() {} + + @Override + public ReachingDefinitionStore initialStore( + UnderlyingAST underlyingAST, @Nullable List parameters) { + return new ReachingDefinitionStore(); + } + + @Override + public RegularTransferResult visitNode( + Node n, TransferInput p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } + + @Override + public RegularTransferResult visitAssignment( + AssignmentNode n, TransferInput p) { + RegularTransferResult transferResult = + (RegularTransferResult) + super.visitAssignment(n, p); + processDefinition(n, transferResult.getRegularStore()); + return transferResult; + } + + /** + * Update a reaching definition node in the store from an assignment statement. + * + * @param def the definition that should be put into the store + * @param store the reaching definition store + */ + private void processDefinition(AssignmentNode def, ReachingDefinitionStore store) { + store.killDef(def.getTarget()); + store.putDef(new ReachingDefinitionNode(def)); + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java index 7dbf822d065..81ca9f9970d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/NodeUtils.java @@ -3,9 +3,7 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; + import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode; import org.checkerframework.dataflow.cfg.node.ConditionalNotNode; import org.checkerframework.dataflow.cfg.node.ConditionalOrNode; @@ -16,90 +14,94 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypesUtils; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; + /** A utility class to operate on a given {@link Node}. */ public class NodeUtils { - /** - * Returns true iff {@code node} corresponds to a boolean typed expression (either the primitive - * type {@code boolean}, or class type {@link java.lang.Boolean}). - * - * @return true iff {@code node} corresponds to a boolean typed expression (either the primitive - * type {@code boolean}, or class type {@link java.lang.Boolean}) - */ - public static boolean isBooleanTypeNode(Node node) { + /** + * Returns true iff {@code node} corresponds to a boolean typed expression (either the primitive + * type {@code boolean}, or class type {@link java.lang.Boolean}). + * + * @return true iff {@code node} corresponds to a boolean typed expression (either the primitive + * type {@code boolean}, or class type {@link java.lang.Boolean}) + */ + public static boolean isBooleanTypeNode(Node node) { - if (node instanceof ConditionalOrNode) { - return true; - } + if (node instanceof ConditionalOrNode) { + return true; + } - // not all nodes have an associated tree, but those are all not of a boolean type. - Tree tree = node.getTree(); - if (tree == null) { - return false; - } + // not all nodes have an associated tree, but those are all not of a boolean type. + Tree tree = node.getTree(); + if (tree == null) { + return false; + } - Type type = ((JCTree) tree).type; - if (TypesUtils.isBooleanType(type)) { - return true; - } + Type type = ((JCTree) tree).type; + if (TypesUtils.isBooleanType(type)) { + return true; + } - return false; - } + return false; + } - /** - * Returns true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's - * length. - * - * @return true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's - * length - */ - public static boolean isArrayLengthFieldAccess(Node node) { - if (!(node instanceof FieldAccessNode)) { - return false; + /** + * Returns true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's + * length. + * + * @return true iff {@code node} is a {@link FieldAccessNode} that is an access to an array's + * length + */ + public static boolean isArrayLengthFieldAccess(Node node) { + if (!(node instanceof FieldAccessNode)) { + return false; + } + FieldAccessNode fieldAccess = (FieldAccessNode) node; + return fieldAccess.getFieldName().equals("length") + && fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY; } - FieldAccessNode fieldAccess = (FieldAccessNode) node; - return fieldAccess.getFieldName().equals("length") - && fieldAccess.getReceiver().getType().getKind() == TypeKind.ARRAY; - } - /** Returns true iff {@code node} is an invocation of the given method. */ - public static boolean isMethodInvocation( - Node node, ExecutableElement method, ProcessingEnvironment env) { - if (!(node instanceof MethodInvocationNode)) { - return false; + /** Returns true iff {@code node} is an invocation of the given method. */ + public static boolean isMethodInvocation( + Node node, ExecutableElement method, ProcessingEnvironment env) { + if (!(node instanceof MethodInvocationNode)) { + return false; + } + ExecutableElement invoked = ((MethodInvocationNode) node).getTarget().getMethod(); + return ElementUtils.isMethod(invoked, method, env); } - ExecutableElement invoked = ((MethodInvocationNode) node).getTarget().getMethod(); - return ElementUtils.isMethod(invoked, method, env); - } - /** - * Returns true if the given node statically evaluates to {@code value} and has no side effects. - * - * @param n a node - * @param value the boolean value that the node is tested against - * @return true if the node is equivalent to a literal with value {@code value} - */ - public static boolean isConstantBoolean(Node n, boolean value) { - if (n instanceof BooleanLiteralNode) { - return ((BooleanLiteralNode) n).getValue() == value; - } else if (n instanceof ConditionalNotNode) { - return isConstantBoolean(((ConditionalNotNode) n).getOperand(), !value); - } else { - return false; + /** + * Returns true if the given node statically evaluates to {@code value} and has no side effects. + * + * @param n a node + * @param value the boolean value that the node is tested against + * @return true if the node is equivalent to a literal with value {@code value} + */ + public static boolean isConstantBoolean(Node n, boolean value) { + if (n instanceof BooleanLiteralNode) { + return ((BooleanLiteralNode) n).getValue() == value; + } else if (n instanceof ConditionalNotNode) { + return isConstantBoolean(((ConditionalNotNode) n).getOperand(), !value); + } else { + return false; + } } - } - /** - * Remove any {@link TypeCastNode}s wrapping a node, returning the operand nested within the type - * casts. - * - * @param node a node - * @return node, but with any surrounding typecasts removed - */ - public static Node removeCasts(Node node) { - while (node instanceof TypeCastNode) { - node = ((TypeCastNode) node).getOperand(); + /** + * Remove any {@link TypeCastNode}s wrapping a node, returning the operand nested within the + * type casts. + * + * @param node a node + * @return node, but with any surrounding typecasts removed + */ + public static Node removeCasts(Node node) { + while (node instanceof TypeCastNode) { + node = ((TypeCastNode) node).getOperand(); + } + return node; } - return node; - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java index e2b3f0d6517..9933fa0e7f7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java @@ -13,12 +13,7 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; @@ -28,6 +23,14 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + /** * A visitor that determines the purity (as defined by {@link * org.checkerframework.dataflow.qual.SideEffectFree}, {@link @@ -41,375 +44,376 @@ */ public class PurityChecker { - /** - * Compute whether the given statement is side-effect-free, deterministic, or both. Returns a - * result that can be queried. - * - * @param statement the statement to check - * @param annoProvider the annotation provider - * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree - * @param assumeDeterministic true if all methods should be assumed to be @Deterministic - * @param assumePureGetters true if all getter methods should be assumed to be @Pure - * @return information about whether the given statement is side-effect-free, deterministic, or - * both - */ - public static PurityResult checkPurity( - TreePath statement, - AnnotationProvider annoProvider, - boolean assumeSideEffectFree, - boolean assumeDeterministic, - boolean assumePureGetters) { - PurityCheckerHelper helper = - new PurityCheckerHelper( - annoProvider, assumeSideEffectFree, assumeDeterministic, assumePureGetters); - helper.scan(statement, null); - return helper.purityResult; - } - - /** - * Result of the {@link PurityChecker}. Can be queried regarding whether a given tree was - * side-effect-free, deterministic, or both; also gives reasons if the answer is "no". - */ - public static class PurityResult { - - /** Reasons that the referenced method is not side-effect-free. */ - protected final List> notSEFreeReasons = new ArrayList<>(1); - - /** Reasons that the referenced method is not deterministic. */ - protected final List> notDetReasons = new ArrayList<>(1); - - /** Reasons that the referenced method is not side-effect-free and deterministic. */ - protected final List> notBothReasons = new ArrayList<>(1); - - /** - * Contains all the varieties of purity that the expression has. Starts out with all varieties, - * and elements are removed from it as violations are found. - */ - protected EnumSet kinds = EnumSet.allOf(Pure.Kind.class); - - /** - * Return the kinds of purity that the method has. - * - * @return the kinds of purity that the method has - */ - public EnumSet getKinds() { - return kinds; - } - - /** - * Is the method pure w.r.t. a given set of kinds? - * - * @param otherKinds the varieties of purity to check - * @return true if the method is pure with respect to all the given kinds - */ - public boolean isPure(EnumSet otherKinds) { - return kinds.containsAll(otherKinds); - } - /** - * Get the reasons why the method is not side-effect-free. + * Compute whether the given statement is side-effect-free, deterministic, or both. Returns a + * result that can be queried. * - * @return the reasons why the method is not side-effect-free - */ - public List> getNotSEFreeReasons() { - return notSEFreeReasons; - } - - /** - * Add a reason why the method is not side-effect-free. - * - * @param t a tree - * @param msgId why the tree is not side-effect-free - */ - public void addNotSEFreeReason(Tree t, String msgId) { - notSEFreeReasons.add(IPair.of(t, msgId)); - kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); - } - - /** - * Get the reasons why the method is not deterministic. - * - * @return the reasons why the method is not deterministic - */ - public List> getNotDetReasons() { - return notDetReasons; - } - - /** - * Add a reason why the method is not deterministic. - * - * @param t a tree - * @param msgId why the tree is not deterministic + * @param statement the statement to check + * @param annoProvider the annotation provider + * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree + * @param assumeDeterministic true if all methods should be assumed to be @Deterministic + * @param assumePureGetters true if all getter methods should be assumed to be @Pure + * @return information about whether the given statement is side-effect-free, deterministic, or + * both */ - public void addNotDetReason(Tree t, String msgId) { - notDetReasons.add(IPair.of(t, msgId)); - kinds.remove(Pure.Kind.DETERMINISTIC); + public static PurityResult checkPurity( + TreePath statement, + AnnotationProvider annoProvider, + boolean assumeSideEffectFree, + boolean assumeDeterministic, + boolean assumePureGetters) { + PurityCheckerHelper helper = + new PurityCheckerHelper( + annoProvider, assumeSideEffectFree, assumeDeterministic, assumePureGetters); + helper.scan(statement, null); + return helper.purityResult; } /** - * Get the reasons why the method is not both side-effect-free and deterministic. - * - * @return the reasons why the method is not both side-effect-free and deterministic + * Result of the {@link PurityChecker}. Can be queried regarding whether a given tree was + * side-effect-free, deterministic, or both; also gives reasons if the answer is "no". */ - public List> getNotBothReasons() { - return notBothReasons; - } + public static class PurityResult { + + /** Reasons that the referenced method is not side-effect-free. */ + protected final List> notSEFreeReasons = new ArrayList<>(1); + + /** Reasons that the referenced method is not deterministic. */ + protected final List> notDetReasons = new ArrayList<>(1); + + /** Reasons that the referenced method is not side-effect-free and deterministic. */ + protected final List> notBothReasons = new ArrayList<>(1); + + /** + * Contains all the varieties of purity that the expression has. Starts out with all + * varieties, and elements are removed from it as violations are found. + */ + protected EnumSet kinds = EnumSet.allOf(Pure.Kind.class); + + /** + * Return the kinds of purity that the method has. + * + * @return the kinds of purity that the method has + */ + public EnumSet getKinds() { + return kinds; + } - /** - * Add a reason why the method is not both side-effect-free and deterministic. - * - * @param t tree - * @param msgId why the tree is not deterministic and side-effect-free - */ - public void addNotBothReason(Tree t, String msgId) { - notBothReasons.add(IPair.of(t, msgId)); - kinds.remove(Pure.Kind.DETERMINISTIC); - kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); - } + /** + * Is the method pure w.r.t. a given set of kinds? + * + * @param otherKinds the varieties of purity to check + * @return true if the method is pure with respect to all the given kinds + */ + public boolean isPure(EnumSet otherKinds) { + return kinds.containsAll(otherKinds); + } - @Override - public String toString() { - return String.join( - System.lineSeparator(), - "PurityResult{", - " notSEF: " + notSEFreeReasons, - " notDet: " + notDetReasons, - " notBoth: " + notBothReasons, - "}"); - } - } + /** + * Get the reasons why the method is not side-effect-free. + * + * @return the reasons why the method is not side-effect-free + */ + public List> getNotSEFreeReasons() { + return notSEFreeReasons; + } - // TODO: It would be possible to improve efficiency by visiting fewer nodes. This would require - // overriding more visit* methods. I'm not sure whether such an optimization would be worth it. + /** + * Add a reason why the method is not side-effect-free. + * + * @param t a tree + * @param msgId why the tree is not side-effect-free + */ + public void addNotSEFreeReason(Tree t, String msgId) { + notSEFreeReasons.add(IPair.of(t, msgId)); + kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); + } - /** - * Helper class to keep {@link PurityChecker}'s interface clean. - * - *

          The scanner is run on a single statement, not on a class or method. - */ - protected static class PurityCheckerHelper extends TreePathScanner { + /** + * Get the reasons why the method is not deterministic. + * + * @return the reasons why the method is not deterministic + */ + public List> getNotDetReasons() { + return notDetReasons; + } - /** The purity result. */ - PurityResult purityResult = new PurityResult(); + /** + * Add a reason why the method is not deterministic. + * + * @param t a tree + * @param msgId why the tree is not deterministic + */ + public void addNotDetReason(Tree t, String msgId) { + notDetReasons.add(IPair.of(t, msgId)); + kinds.remove(Pure.Kind.DETERMINISTIC); + } - /** The annotation provider (typically an AnnotatedTypeFactory). */ - protected final AnnotationProvider annoProvider; + /** + * Get the reasons why the method is not both side-effect-free and deterministic. + * + * @return the reasons why the method is not both side-effect-free and deterministic + */ + public List> getNotBothReasons() { + return notBothReasons; + } - /** - * True if all methods should be assumed to be @SideEffectFree, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeSideEffectFree; + /** + * Add a reason why the method is not both side-effect-free and deterministic. + * + * @param t tree + * @param msgId why the tree is not deterministic and side-effect-free + */ + public void addNotBothReason(Tree t, String msgId) { + notBothReasons.add(IPair.of(t, msgId)); + kinds.remove(Pure.Kind.DETERMINISTIC); + kinds.remove(Pure.Kind.SIDE_EFFECT_FREE); + } - /** - * True if all methods should be assumed to be @Deterministic, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeDeterministic; + @Override + public String toString() { + return String.join( + System.lineSeparator(), + "PurityResult{", + " notSEF: " + notSEFreeReasons, + " notDet: " + notDetReasons, + " notBoth: " + notBothReasons, + "}"); + } + } - /** - * True if all getter methods should be assumed to be @SideEffectFree and @Deterministic, for - * the purposes of org.checkerframework.dataflow analysis. - */ - private final boolean assumePureGetters; + // TODO: It would be possible to improve efficiency by visiting fewer nodes. This would require + // overriding more visit* methods. I'm not sure whether such an optimization would be worth it. /** - * Create a PurityCheckerHelper. + * Helper class to keep {@link PurityChecker}'s interface clean. * - * @param annoProvider the annotation provider - * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree - * @param assumeDeterministic true if all methods should be assumed to be @Deterministic - * @param assumePureGetters true if getter methods should be assumed to be @Pure + *

          The scanner is run on a single statement, not on a class or method. */ - public PurityCheckerHelper( - AnnotationProvider annoProvider, - boolean assumeSideEffectFree, - boolean assumeDeterministic, - boolean assumePureGetters) { - this.annoProvider = annoProvider; - this.assumeSideEffectFree = assumeSideEffectFree; - this.assumeDeterministic = assumeDeterministic; - this.assumePureGetters = assumePureGetters; - } + protected static class PurityCheckerHelper extends TreePathScanner { + + /** The purity result. */ + PurityResult purityResult = new PurityResult(); + + /** The annotation provider (typically an AnnotatedTypeFactory). */ + protected final AnnotationProvider annoProvider; + + /** + * True if all methods should be assumed to be @SideEffectFree, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeSideEffectFree; + + /** + * True if all methods should be assumed to be @Deterministic, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeDeterministic; + + /** + * True if all getter methods should be assumed to be @SideEffectFree and @Deterministic, + * for the purposes of org.checkerframework.dataflow analysis. + */ + private final boolean assumePureGetters; + + /** + * Create a PurityCheckerHelper. + * + * @param annoProvider the annotation provider + * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree + * @param assumeDeterministic true if all methods should be assumed to be @Deterministic + * @param assumePureGetters true if getter methods should be assumed to be @Pure + */ + public PurityCheckerHelper( + AnnotationProvider annoProvider, + boolean assumeSideEffectFree, + boolean assumeDeterministic, + boolean assumePureGetters) { + this.annoProvider = annoProvider; + this.assumeSideEffectFree = assumeSideEffectFree; + this.assumeDeterministic = assumeDeterministic; + this.assumePureGetters = assumePureGetters; + } - @Override - public Void visitCatch(CatchTree tree, Void ignore) { - purityResult.addNotDetReason(tree, "catch"); - return super.visitCatch(tree, ignore); - } + @Override + public Void visitCatch(CatchTree tree, Void ignore) { + purityResult.addNotDetReason(tree, "catch"); + return super.visitCatch(tree, ignore); + } - /** Represents a method that is both deterministic and side-effect free. */ - private static final EnumSet detAndSeFree = - EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void ignore) { - ExecutableElement elt = TreeUtils.elementFromUse(tree); - if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) { - purityResult.addNotBothReason(tree, "call"); - } else { - EnumSet purityKinds = - ((assumeDeterministic && assumeSideEffectFree) - || (assumePureGetters && ElementUtils.isGetter(elt))) - // Avoid computation if not necessary - ? detAndSeFree - : PurityUtils.getPurityKinds(annoProvider, elt); - boolean det = assumeDeterministic || purityKinds.contains(Pure.Kind.DETERMINISTIC); - boolean seFree = assumeSideEffectFree || purityKinds.contains(Pure.Kind.SIDE_EFFECT_FREE); - if (!det && !seFree) { - purityResult.addNotBothReason(tree, "call"); - } else if (!det) { - purityResult.addNotDetReason(tree, "call"); - } else if (!seFree) { - purityResult.addNotSEFreeReason(tree, "call"); + /** Represents a method that is both deterministic and side-effect free. */ + private static final EnumSet detAndSeFree = + EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void ignore) { + ExecutableElement elt = TreeUtils.elementFromUse(tree); + if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) { + purityResult.addNotBothReason(tree, "call"); + } else { + EnumSet purityKinds = + ((assumeDeterministic && assumeSideEffectFree) + || (assumePureGetters && ElementUtils.isGetter(elt))) + // Avoid computation if not necessary + ? detAndSeFree + : PurityUtils.getPurityKinds(annoProvider, elt); + boolean det = assumeDeterministic || purityKinds.contains(Pure.Kind.DETERMINISTIC); + boolean seFree = + assumeSideEffectFree || purityKinds.contains(Pure.Kind.SIDE_EFFECT_FREE); + if (!det && !seFree) { + purityResult.addNotBothReason(tree, "call"); + } else if (!det) { + purityResult.addNotDetReason(tree, "call"); + } else if (!seFree) { + purityResult.addNotSEFreeReason(tree, "call"); + } + } + return super.visitMethodInvocation(tree, ignore); } - } - return super.visitMethodInvocation(tree, ignore); - } - @Override - public Void visitNewClass(NewClassTree tree, Void ignore) { - // Ordinarily, "new MyClass()" is forbidden. It is permitted, however, when it is the - // expression in "throw EXPR;". (In the future, more expressions could be permitted.) - // - // The expression in "throw EXPR;" is allowed to be non-@Deterministic, so long as it is - // not within a catch block that could catch an exception that the statement throws. - // For example, EXPR can be object creation (a "new" expression) or can call a - // non-deterministic method. - // - // Coarse rule (currently implemented): - // * permit only "throw new SomeExpression(args)", where the constructor is - // @SideEffectFree and the args are pure, and forbid all enclosing try statements - // that have a catch clause. - // More precise rule: - // * permit other non-deterministic expresssions within throw (at which time move this - // logic to visitThrow()). - // * the only bad try statements are those with a catch block that is: - // * unchecked exceptions - // * checked = Exception or lower, but excluding RuntimeException and its - // subclasses - // * super- or sub-classes of the type of _expr_ - // * if _expr_ is exactly "new SomeException", this can be changed to just - // "superclasses of SomeException". - // * super- or sub-classes of exceptions declared to be thrown by any component of - // _expr_. - // * need to check every containing try statement, not just the nearest enclosing - // one. - - // Object creation is usually prohibited, but permit "throw new SomeException();" if it - // is not contained within any try statement that has a catch clause. (There is no need - // to check the latter condition, because the Purity Checker forbids all catch - // statements.) - Tree parent = getCurrentPath().getParentPath().getLeaf(); - boolean okThrowDeterministic = parent.getKind() == Tree.Kind.THROW; - - ExecutableElement ctorElement = TreeUtils.elementFromUse(tree); - boolean deterministic = - assumeDeterministic - || okThrowDeterministic - // No need to check assumePureGetters because a constructor is never a - // getter. - || PurityUtils.isDeterministic(annoProvider, ctorElement); - boolean sideEffectFree = - assumeSideEffectFree || PurityUtils.isSideEffectFree(annoProvider, ctorElement); - // This does not use "addNotBothReason" because the reasons are different: one is - // because the constructor is called at all, and the other is because the constuctor is - // not side-effect-free. - if (!deterministic) { - purityResult.addNotDetReason(tree, "object.creation"); - } - if (!sideEffectFree) { - purityResult.addNotSEFreeReason(tree, "call"); - } - - // TODO: if okThrowDeterministic, permit arguments to the newClass to be - // non-deterministic (don't add those to purityResult), but still don't permit them to - // have side effects. This should probably wait until a rewrite of the Purity Checker. - return super.visitNewClass(tree, ignore); - } + @Override + public Void visitNewClass(NewClassTree tree, Void ignore) { + // Ordinarily, "new MyClass()" is forbidden. It is permitted, however, when it is the + // expression in "throw EXPR;". (In the future, more expressions could be permitted.) + // + // The expression in "throw EXPR;" is allowed to be non-@Deterministic, so long as it is + // not within a catch block that could catch an exception that the statement throws. + // For example, EXPR can be object creation (a "new" expression) or can call a + // non-deterministic method. + // + // Coarse rule (currently implemented): + // * permit only "throw new SomeExpression(args)", where the constructor is + // @SideEffectFree and the args are pure, and forbid all enclosing try statements + // that have a catch clause. + // More precise rule: + // * permit other non-deterministic expresssions within throw (at which time move this + // logic to visitThrow()). + // * the only bad try statements are those with a catch block that is: + // * unchecked exceptions + // * checked = Exception or lower, but excluding RuntimeException and its + // subclasses + // * super- or sub-classes of the type of _expr_ + // * if _expr_ is exactly "new SomeException", this can be changed to just + // "superclasses of SomeException". + // * super- or sub-classes of exceptions declared to be thrown by any component of + // _expr_. + // * need to check every containing try statement, not just the nearest enclosing + // one. + + // Object creation is usually prohibited, but permit "throw new SomeException();" if it + // is not contained within any try statement that has a catch clause. (There is no need + // to check the latter condition, because the Purity Checker forbids all catch + // statements.) + Tree parent = getCurrentPath().getParentPath().getLeaf(); + boolean okThrowDeterministic = parent.getKind() == Tree.Kind.THROW; + + ExecutableElement ctorElement = TreeUtils.elementFromUse(tree); + boolean deterministic = + assumeDeterministic + || okThrowDeterministic + // No need to check assumePureGetters because a constructor is never a + // getter. + || PurityUtils.isDeterministic(annoProvider, ctorElement); + boolean sideEffectFree = + assumeSideEffectFree || PurityUtils.isSideEffectFree(annoProvider, ctorElement); + // This does not use "addNotBothReason" because the reasons are different: one is + // because the constructor is called at all, and the other is because the constuctor is + // not side-effect-free. + if (!deterministic) { + purityResult.addNotDetReason(tree, "object.creation"); + } + if (!sideEffectFree) { + purityResult.addNotSEFreeReason(tree, "call"); + } + + // TODO: if okThrowDeterministic, permit arguments to the newClass to be + // non-deterministic (don't add those to purityResult), but still don't permit them to + // have side effects. This should probably wait until a rewrite of the Purity Checker. + return super.visitNewClass(tree, ignore); + } - @Override - public Void visitAssignment(AssignmentTree tree, Void ignore) { - ExpressionTree variable = tree.getVariable(); - assignmentCheck(variable); - return super.visitAssignment(tree, ignore); - } + @Override + public Void visitAssignment(AssignmentTree tree, Void ignore) { + ExpressionTree variable = tree.getVariable(); + assignmentCheck(variable); + return super.visitAssignment(tree, ignore); + } - @Override - public Void visitUnary(UnaryTree tree, Void ignore) { - switch (tree.getKind()) { - case POSTFIX_DECREMENT: - case POSTFIX_INCREMENT: - case PREFIX_DECREMENT: - case PREFIX_INCREMENT: - ExpressionTree expression = tree.getExpression(); - assignmentCheck(expression); - break; - default: - // Nothing to do - break; - } - return super.visitUnary(tree, ignore); - } + @Override + public Void visitUnary(UnaryTree tree, Void ignore) { + switch (tree.getKind()) { + case POSTFIX_DECREMENT: + case POSTFIX_INCREMENT: + case PREFIX_DECREMENT: + case PREFIX_INCREMENT: + ExpressionTree expression = tree.getExpression(); + assignmentCheck(expression); + break; + default: + // Nothing to do + break; + } + return super.visitUnary(tree, ignore); + } - /** - * Check whether {@code variable} is permitted on the left-hand-side of an assignment. - * - * @param variable the lhs to check - */ - protected void assignmentCheck(ExpressionTree variable) { - variable = TreeUtils.withoutParens(variable); - VariableElement fieldElt = TreeUtils.asFieldAccess(variable); - if (fieldElt != null - && isFieldInCurrentClass(fieldElt) - && TreePathUtil.inConstructor(getCurrentPath())) { - // assigning a field in a constructor - // TODO: add a check for ArrayAccessTree too. - return; - } - if (TreeUtils.isFieldAccess(variable)) { - // lhs is a field access - purityResult.addNotBothReason(variable, "assign.field"); - } else if (variable instanceof ArrayAccessTree) { - // lhs is array access - purityResult.addNotBothReason(variable, "assign.array"); - } else { - // lhs is a local variable - assert isLocalVariable(variable); - } - } + /** + * Check whether {@code variable} is permitted on the left-hand-side of an assignment. + * + * @param variable the lhs to check + */ + protected void assignmentCheck(ExpressionTree variable) { + variable = TreeUtils.withoutParens(variable); + VariableElement fieldElt = TreeUtils.asFieldAccess(variable); + if (fieldElt != null + && isFieldInCurrentClass(fieldElt) + && TreePathUtil.inConstructor(getCurrentPath())) { + // assigning a field in a constructor + // TODO: add a check for ArrayAccessTree too. + return; + } + if (TreeUtils.isFieldAccess(variable)) { + // lhs is a field access + purityResult.addNotBothReason(variable, "assign.field"); + } else if (variable instanceof ArrayAccessTree) { + // lhs is array access + purityResult.addNotBothReason(variable, "assign.array"); + } else { + // lhs is a local variable + assert isLocalVariable(variable); + } + } - /** - * Returns true if the given field is defined by the current class. - * - * @param fieldElt a field - * @return true if the given field is defined by the current class - */ - private boolean isFieldInCurrentClass(VariableElement fieldElt) { - ClassTree currentTypeTree = TreePathUtil.enclosingClass(getCurrentPath()); - assert currentTypeTree != null : "@AssumeAssertion(nullness)"; - TypeElement currentType = TreeUtils.elementFromDeclaration(currentTypeTree); - assert currentType != null : "@AssumeAssertion(nullness)"; - TypeElement definesField = ElementUtils.enclosingTypeElement(fieldElt); - assert definesField != null : "@AssumeAssertion(nullness)"; - return currentType.equals(definesField); - } + /** + * Returns true if the given field is defined by the current class. + * + * @param fieldElt a field + * @return true if the given field is defined by the current class + */ + private boolean isFieldInCurrentClass(VariableElement fieldElt) { + ClassTree currentTypeTree = TreePathUtil.enclosingClass(getCurrentPath()); + assert currentTypeTree != null : "@AssumeAssertion(nullness)"; + TypeElement currentType = TreeUtils.elementFromDeclaration(currentTypeTree); + assert currentType != null : "@AssumeAssertion(nullness)"; + TypeElement definesField = ElementUtils.enclosingTypeElement(fieldElt); + assert definesField != null : "@AssumeAssertion(nullness)"; + return currentType.equals(definesField); + } - /** - * Checks if the argument is a local variable. - * - * @param variable the tree to check - * @return true if the argument is a local variable - */ - protected boolean isLocalVariable(ExpressionTree variable) { - return variable instanceof IdentifierTree && !TreeUtils.isFieldAccess(variable); - } + /** + * Checks if the argument is a local variable. + * + * @param variable the tree to check + * @return true if the argument is a local variable + */ + protected boolean isLocalVariable(ExpressionTree variable) { + return variable instanceof IdentifierTree && !TreeUtils.isFieldAccess(variable); + } - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void ignore) { - ExpressionTree variable = tree.getVariable(); - assignmentCheck(variable); - return super.visitCompoundAssignment(tree, ignore); + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void ignore) { + ExpressionTree variable = tree.getVariable(); + assignmentCheck(variable); + return super.visitCompoundAssignment(tree, ignore); + } } - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index 9af408c0bd0..ce61c39c76c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -1,9 +1,7 @@ package org.checkerframework.dataflow.util; import com.sun.source.tree.MethodTree; -import java.util.EnumSet; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; @@ -11,6 +9,11 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.EnumSet; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + /** * A utility class for working with the {@link SideEffectFree}, {@link Deterministic}, and {@link * Pure} annotations. @@ -21,125 +24,126 @@ */ public class PurityUtils { - /** Do not instantiate. */ - private PurityUtils() { - throw new Error("Do not instantiate PurityUtils."); - } - - /** Represents a method that is both deterministic and side-effect free. */ - private static final EnumSet detAndSeFree = - EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); - - /** - * Does the method {@code methodTree} have any purity annotation? - * - * @param provider how to get annotations - * @param methodTree a method to test - * @return whether the method has any purity annotations - */ - public static boolean hasPurityAnnotation(AnnotationProvider provider, MethodTree methodTree) { - return !getPurityKinds(provider, methodTree).isEmpty(); - } - - /** - * Does the method {@code methodElement} have any purity annotation? - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return whether the method has any purity annotations - */ - public static boolean hasPurityAnnotation( - AnnotationProvider provider, ExecutableElement methodElement) { - return !getPurityKinds(provider, methodElement).isEmpty(); - } - - /** - * Is the method {@code methodTree} deterministic? - * - * @param provider how to get annotations - * @param methodTree a method to test - * @return whether the method is deterministic - */ - public static boolean isDeterministic(AnnotationProvider provider, MethodTree methodTree) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); - return isDeterministic(provider, methodElement); - } - - /** - * Is the method {@code methodElement} deterministic? - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return whether the method is deterministic - */ - public static boolean isDeterministic( - AnnotationProvider provider, ExecutableElement methodElement) { - EnumSet kinds = getPurityKinds(provider, methodElement); - return kinds.contains(Pure.Kind.DETERMINISTIC); - } - - /** - * Is the method {@code methodElement} side-effect-free? - * - *

          This method does not use, and has different semantics than, {@link - * AnnotationProvider#isSideEffectFree}. This method is concerned only with standard purity - * annotations. - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return whether the method is side-effect-free - */ - public static boolean isSideEffectFree( - AnnotationProvider provider, ExecutableElement methodElement) { - EnumSet kinds = getPurityKinds(provider, methodElement); - return kinds.contains(Pure.Kind.SIDE_EFFECT_FREE); - } - - /** - * Returns the purity annotations on the method {@code methodTree}. - * - * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and - * {@link AnnotationProvider#isDeterministic} methods are not used. - * @param methodTree a method to test - * @return the types of purity of the method {@code methodTree} - */ - public static EnumSet getPurityKinds( - AnnotationProvider provider, MethodTree methodTree) { - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); - return getPurityKinds(provider, methodElement); - } - - /** - * Returns the purity annotations on the method {@code methodElement}. - * - * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and - * {@link AnnotationProvider#isDeterministic} methods are not used. - * @param methodElement a method to test - * @return the types of purity of the method {@code methodElement} - */ - public static EnumSet getPurityKinds( - AnnotationProvider provider, ExecutableElement methodElement) { - // Special case for record accessors - if (ElementUtils.isRecordAccessor(methodElement) - && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { - return detAndSeFree; + /** Do not instantiate. */ + private PurityUtils() { + throw new Error("Do not instantiate PurityUtils."); } - AnnotationMirror pureAnnotation = provider.getDeclAnnotation(methodElement, Pure.class); - AnnotationMirror sefAnnotation = - provider.getDeclAnnotation(methodElement, SideEffectFree.class); - AnnotationMirror detAnnotation = provider.getDeclAnnotation(methodElement, Deterministic.class); + /** Represents a method that is both deterministic and side-effect free. */ + private static final EnumSet detAndSeFree = + EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); - if (pureAnnotation != null) { - return detAndSeFree; + /** + * Does the method {@code methodTree} have any purity annotation? + * + * @param provider how to get annotations + * @param methodTree a method to test + * @return whether the method has any purity annotations + */ + public static boolean hasPurityAnnotation(AnnotationProvider provider, MethodTree methodTree) { + return !getPurityKinds(provider, methodTree).isEmpty(); } - EnumSet result = EnumSet.noneOf(Pure.Kind.class); - if (sefAnnotation != null) { - result.add(Pure.Kind.SIDE_EFFECT_FREE); + + /** + * Does the method {@code methodElement} have any purity annotation? + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return whether the method has any purity annotations + */ + public static boolean hasPurityAnnotation( + AnnotationProvider provider, ExecutableElement methodElement) { + return !getPurityKinds(provider, methodElement).isEmpty(); } - if (detAnnotation != null) { - result.add(Pure.Kind.DETERMINISTIC); + + /** + * Is the method {@code methodTree} deterministic? + * + * @param provider how to get annotations + * @param methodTree a method to test + * @return whether the method is deterministic + */ + public static boolean isDeterministic(AnnotationProvider provider, MethodTree methodTree) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); + return isDeterministic(provider, methodElement); + } + + /** + * Is the method {@code methodElement} deterministic? + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return whether the method is deterministic + */ + public static boolean isDeterministic( + AnnotationProvider provider, ExecutableElement methodElement) { + EnumSet kinds = getPurityKinds(provider, methodElement); + return kinds.contains(Pure.Kind.DETERMINISTIC); + } + + /** + * Is the method {@code methodElement} side-effect-free? + * + *

          This method does not use, and has different semantics than, {@link + * AnnotationProvider#isSideEffectFree}. This method is concerned only with standard purity + * annotations. + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return whether the method is side-effect-free + */ + public static boolean isSideEffectFree( + AnnotationProvider provider, ExecutableElement methodElement) { + EnumSet kinds = getPurityKinds(provider, methodElement); + return kinds.contains(Pure.Kind.SIDE_EFFECT_FREE); + } + + /** + * Returns the purity annotations on the method {@code methodTree}. + * + * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and + * {@link AnnotationProvider#isDeterministic} methods are not used. + * @param methodTree a method to test + * @return the types of purity of the method {@code methodTree} + */ + public static EnumSet getPurityKinds( + AnnotationProvider provider, MethodTree methodTree) { + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodTree); + return getPurityKinds(provider, methodElement); + } + + /** + * Returns the purity annotations on the method {@code methodElement}. + * + * @param provider how to get annotations. Its {@link AnnotationProvider#isSideEffectFree} and + * {@link AnnotationProvider#isDeterministic} methods are not used. + * @param methodElement a method to test + * @return the types of purity of the method {@code methodElement} + */ + public static EnumSet getPurityKinds( + AnnotationProvider provider, ExecutableElement methodElement) { + // Special case for record accessors + if (ElementUtils.isRecordAccessor(methodElement) + && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { + return detAndSeFree; + } + + AnnotationMirror pureAnnotation = provider.getDeclAnnotation(methodElement, Pure.class); + AnnotationMirror sefAnnotation = + provider.getDeclAnnotation(methodElement, SideEffectFree.class); + AnnotationMirror detAnnotation = + provider.getDeclAnnotation(methodElement, Deterministic.class); + + if (pureAnnotation != null) { + return detAndSeFree; + } + EnumSet result = EnumSet.noneOf(Pure.Kind.class); + if (sefAnnotation != null) { + result.add(Pure.Kind.SIDE_EFFECT_FREE); + } + if (detAnnotation != null) { + result.add(Pure.Kind.DETERMINISTIC); + } + return result; } - return result; - } } diff --git a/dataflow/src/test/java/busyexpr/BusyExpression.java b/dataflow/src/test/java/busyexpr/BusyExpression.java index 619ec2dd1ad..00587aec969 100644 --- a/dataflow/src/test/java/busyexpr/BusyExpression.java +++ b/dataflow/src/test/java/busyexpr/BusyExpression.java @@ -9,21 +9,22 @@ /** Used in busyExpressionTest Gradle task to test the BusyExpression analysis. */ public class BusyExpression { - /** - * The main method expects to be run in dataflow/tests/busy-expression directory. - * - * @param args not used - */ - public static void main(String[] args) { + /** + * The main method expects to be run in dataflow/tests/busy-expression directory. + * + * @param args not used + */ + public static void main(String[] args) { - String inputFile = "Test.java"; // input file name; - String method = "test"; - String clazz = "Test"; - String outputFile = "Out.txt"; + String inputFile = "Test.java"; // input file name; + String method = "test"; + String clazz = "Test"; + String outputFile = "Out.txt"; - BusyExprTransfer transfer = new BusyExprTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clazz, outputFile, backwardAnalysis); - } + BusyExprTransfer transfer = new BusyExprTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG( + inputFile, method, clazz, outputFile, backwardAnalysis); + } } diff --git a/dataflow/src/test/java/cfgconstruction/CFGConstruction.java b/dataflow/src/test/java/cfgconstruction/CFGConstruction.java index ac598e6f86c..e59df6a1339 100644 --- a/dataflow/src/test/java/cfgconstruction/CFGConstruction.java +++ b/dataflow/src/test/java/cfgconstruction/CFGConstruction.java @@ -5,12 +5,12 @@ public class CFGConstruction { - public static void main(String[] args) { - String inputFile = "Test.java"; - String clazz = "Test"; - String method = "manyNestedTryFinallyBlocks"; + public static void main(String[] args) { + String inputFile = "Test.java"; + String clazz = "Test"; + String method = "manyNestedTryFinallyBlocks"; - ControlFlowGraph cfg = CFGVisualizeLauncher.generateMethodCFG(inputFile, clazz, method); - cfg.checkInvariants(); - } + ControlFlowGraph cfg = CFGVisualizeLauncher.generateMethodCFG(inputFile, clazz, method); + cfg.checkInvariants(); + } } diff --git a/dataflow/src/test/java/constantpropagation/ConstantPropagation.java b/dataflow/src/test/java/constantpropagation/ConstantPropagation.java index 02e8fc25aab..a83e8c37474 100644 --- a/dataflow/src/test/java/constantpropagation/ConstantPropagation.java +++ b/dataflow/src/test/java/constantpropagation/ConstantPropagation.java @@ -9,21 +9,21 @@ public class ConstantPropagation { - /** - * The main method expects to be run in dataflow/tests/constant-propagation directory. - * - * @param args not used - */ - public static void main(String[] args) { + /** + * The main method expects to be run in dataflow/tests/constant-propagation directory. + * + * @param args not used + */ + public static void main(String[] args) { - String inputFile = "Test.java"; - String method = "test"; - String clas = "Test"; - String outputFile = "Out.txt"; + String inputFile = "Test.java"; + String method = "test"; + String clas = "Test"; + String outputFile = "Out.txt"; - ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); - } + ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); + } } diff --git a/dataflow/src/test/java/livevar/LiveVariable.java b/dataflow/src/test/java/livevar/LiveVariable.java index 2364829557c..e30939fa213 100644 --- a/dataflow/src/test/java/livevar/LiveVariable.java +++ b/dataflow/src/test/java/livevar/LiveVariable.java @@ -10,20 +10,21 @@ /** Used in liveVariableTest Gradle task to test the LiveVariable analysis. */ public class LiveVariable { - /** - * The main method expects to be run in dataflow/tests/live-variable directory. - * - * @param args not used - */ - public static void main(String[] args) { - String inputFile = "Test.java"; - String method = "test"; - String clas = "Test"; - String outputFile = "Out.txt"; + /** + * The main method expects to be run in dataflow/tests/live-variable directory. + * + * @param args not used + */ + public static void main(String[] args) { + String inputFile = "Test.java"; + String method = "test"; + String clas = "Test"; + String outputFile = "Out.txt"; - LiveVarTransfer transfer = new LiveVarTransfer(); - BackwardAnalysis backwardAnalysis = - new BackwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, backwardAnalysis); - } + LiveVarTransfer transfer = new LiveVarTransfer(); + BackwardAnalysis backwardAnalysis = + new BackwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG( + inputFile, method, clas, outputFile, backwardAnalysis); + } } diff --git a/dataflow/src/test/java/reachingdef/ReachingDefinition.java b/dataflow/src/test/java/reachingdef/ReachingDefinition.java index 23a30c41771..d55ce717c64 100644 --- a/dataflow/src/test/java/reachingdef/ReachingDefinition.java +++ b/dataflow/src/test/java/reachingdef/ReachingDefinition.java @@ -10,21 +10,21 @@ /** Used in reachingDefinitionsTest Gradle task to test the ReachingDefinition analysis. */ public class ReachingDefinition { - /** - * The main method expects to be run in dataflow/tests/reaching-definitions directory. - * - * @param args not used - */ - public static void main(String[] args) { + /** + * The main method expects to be run in dataflow/tests/reaching-definitions directory. + * + * @param args not used + */ + public static void main(String[] args) { - String inputFile = "Test.java"; - String method = "test"; - String clas = "Test"; - String outputFile = "Out.txt"; + String inputFile = "Test.java"; + String method = "test"; + String clas = "Test"; + String outputFile = "Out.txt"; - ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); - ForwardAnalysis - forwardAnalysis = new ForwardAnalysisImpl<>(transfer); - CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); - } + ReachingDefinitionTransfer transfer = new ReachingDefinitionTransfer(); + ForwardAnalysis + forwardAnalysis = new ForwardAnalysisImpl<>(transfer); + CFGVisualizeLauncher.writeStringOfCFG(inputFile, method, clas, outputFile, forwardAnalysis); + } } diff --git a/dataflow/tests/busyexpr/Test.java b/dataflow/tests/busyexpr/Test.java index 55dab9065a0..4e93b45962a 100644 --- a/dataflow/tests/busyexpr/Test.java +++ b/dataflow/tests/busyexpr/Test.java @@ -1,25 +1,25 @@ class Test { - Test(int x) {} + Test(int x) {} - public int test(int m) { - int a = 2, b = 3, x = 1, y; - if (a != b) { - x = b >> a; - new Test(a - b); // test object creation - y = a + b; - } else { - y = b >> a; - a = 0; - test(a - b); // test method invocation - } + public int test(int m) { + int a = 2, b = 3, x = 1, y; + if (a != b) { + x = b >> a; + new Test(a - b); // test object creation + y = a + b; + } else { + y = b >> a; + a = 0; + test(a - b); // test method invocation + } - // test exceptional exit block - int d; - try { - d = y / x; - } catch (ArithmeticException e) { - d = 10000000; + // test exceptional exit block + int d; + try { + d = y / x; + } catch (ArithmeticException e) { + d = 10000000; + } + return d; } - return d; - } } diff --git a/dataflow/tests/cfgconstruction/Test.java b/dataflow/tests/cfgconstruction/Test.java index 1d94da0c189..998fa64de4d 100644 --- a/dataflow/tests/cfgconstruction/Test.java +++ b/dataflow/tests/cfgconstruction/Test.java @@ -1,26 +1,26 @@ public class Test { - public void manyNestedTryFinallyBlocks() { - try { - System.out.println("!"); - } finally { - try { - System.out.println("!"); - } finally { + public void manyNestedTryFinallyBlocks() { try { - System.out.println("!"); - } finally { - try { System.out.println("!"); - } finally { + } finally { try { - System.out.println("!"); + System.out.println("!"); } finally { - System.out.println("!"); + try { + System.out.println("!"); + } finally { + try { + System.out.println("!"); + } finally { + try { + System.out.println("!"); + } finally { + System.out.println("!"); + } + } + } } - } } - } } - } } diff --git a/dataflow/tests/constant-propagation/Test.java b/dataflow/tests/constant-propagation/Test.java index 51e1159db35..62cb612e315 100644 --- a/dataflow/tests/constant-propagation/Test.java +++ b/dataflow/tests/constant-propagation/Test.java @@ -1,11 +1,11 @@ public class Test { - public int test() { - int a = 0, b; - if (a > 5) { - b = a; - } else { - b = 4; + public int test() { + int a = 0, b; + if (a > 5) { + b = a; + } else { + b = 4; + } + return b; } - return b; - } } diff --git a/dataflow/tests/issue3447/Test.java b/dataflow/tests/issue3447/Test.java index 2ff93c9aeda..563d01a1d62 100644 --- a/dataflow/tests/issue3447/Test.java +++ b/dataflow/tests/issue3447/Test.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/3447 public class Test { - public void test() throws Exception { - try { - int[] myNumbers = {1}; - System.out.println(myNumbers[1]); - } catch (Exception e) { + public void test() throws Exception { + try { + int[] myNumbers = {1}; + System.out.println(myNumbers[1]); + } catch (Exception e) { + } } - } } diff --git a/dataflow/tests/live-variable/Test.java b/dataflow/tests/live-variable/Test.java index 0248330e8c5..db2c8e4b9f3 100644 --- a/dataflow/tests/live-variable/Test.java +++ b/dataflow/tests/live-variable/Test.java @@ -1,19 +1,19 @@ // This file may not be renamed; it has to have the same filename as ../issue3447/Test.java . public class Test { - public int test() { - int a = 1, b = 2, c = 3; - if (a > 0) { - int d = a + c; - } else { - int e = a + b; + public int test() { + int a = 1, b = 2, c = 3; + if (a > 0) { + int d = a + c; + } else { + int e = a + b; + } + int f; + b = 0; + try { + f = 1 / a; + } catch (ArithmeticException e) { + f = b; + } + return a; } - int f; - b = 0; - try { - f = 1 / a; - } catch (ArithmeticException e) { - f = b; - } - return a; - } } diff --git a/dataflow/tests/reachingdef/Test.java b/dataflow/tests/reachingdef/Test.java index d0e145c8f5f..ea71b2fc492 100644 --- a/dataflow/tests/reachingdef/Test.java +++ b/dataflow/tests/reachingdef/Test.java @@ -1,15 +1,15 @@ public class Test { - public int test() { - int a = 1, b = 2, c = 3; - String x = "a", y = "b"; - if (a > 0) { - int d = a + c; - } else { - int e = a + b; + public int test() { + int a = 1, b = 2, c = 3; + String x = "a", y = "b"; + if (a > 0) { + int d = a + c; + } else { + int e = a + b; + } + b = 0; + a = b; + x += y; + return a; } - b = 0; - a = b; - x += y; - return a; - } } diff --git a/docs/build.gradle b/docs/build.gradle index 0dbb6292d3f..ecb64aac695 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -1,17 +1,17 @@ // Enable execOperations.javaexec interface InjectedExecOps { - @Inject //@javax.inject.Inject - ExecOperations getExecOps() + @Inject //@javax.inject.Inject + ExecOperations getExecOps() } clean { - def injected = project.objects.newInstance(InjectedExecOps) + def injected = project.objects.newInstance(InjectedExecOps) - delete('api/') - doLast { - injected.execOps.exec { - workingDir = file('examples') - commandLine 'make', 'clean' + delete('api/') + doLast { + injected.execOps.exec { + workingDir = file('examples') + commandLine 'make', 'clean' + } } - } } diff --git a/docs/examples/BazelExample/BazelExample.java b/docs/examples/BazelExample/BazelExample.java index 5206e41fa11..5e94c8b0e9d 100644 --- a/docs/examples/BazelExample/BazelExample.java +++ b/docs/examples/BazelExample/BazelExample.java @@ -1,9 +1,10 @@ -import java.util.HashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.HashMap; +import java.util.Map; + /** * If you run: * @@ -15,16 +16,16 @@ */ public class BazelExample { - public static @Nullable Object nullable = null; - public Map map = new HashMap<>(); + public static @Nullable Object nullable = null; + public Map map = new HashMap<>(); - public static void main(String[] args) { - System.out.println("Hello World!"); + public static void main(String[] args) { + System.out.println("Hello World!"); - @NonNull Object nn = null; // error on this line - System.out.println(nn.hashCode()); // NPE - } + @NonNull Object nn = null; // error on this line + System.out.println(nn.hashCode()); // NPE + } - // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. - void mapTest(@KeyFor("map") Object k) {} + // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. + void mapTest(@KeyFor("map") Object k) {} } diff --git a/docs/examples/InterningExample.java b/docs/examples/InterningExample.java index 64d73628859..5122db82dc3 100644 --- a/docs/examples/InterningExample.java +++ b/docs/examples/InterningExample.java @@ -9,16 +9,16 @@ */ public class InterningExample { - public void example() { + public void example() { - // These type annotations are redundant -- the Interning Checker will - // infer them, but they are written here in the example for emhpasis. - // In general, you do not have to annotate local variables. - @Interned String foo = "foo"; - @Interned String bar = "bar"; + // These type annotations are redundant -- the Interning Checker will + // infer them, but they are written here in the example for emhpasis. + // In general, you do not have to annotate local variables. + @Interned String foo = "foo"; + @Interned String bar = "bar"; - if (foo == bar) { - System.out.println("foo == bar"); + if (foo == bar) { + System.out.println("foo == bar"); + } } - } } diff --git a/docs/examples/InterningExampleWithWarnings.java b/docs/examples/InterningExampleWithWarnings.java index 5e68e1f73b3..174f6559ed5 100644 --- a/docs/examples/InterningExampleWithWarnings.java +++ b/docs/examples/InterningExampleWithWarnings.java @@ -10,16 +10,16 @@ */ public class InterningExampleWithWarnings { - public void example() { + public void example() { - // This type annotation is redundant -- the Interning Checker will - // infer it, but it is written here in the example for emhpasis. - // In general, you do not have to annotate local variables. - @Interned String foo = "foo"; - String bar = new String("bar"); + // This type annotation is redundant -- the Interning Checker will + // infer it, but it is written here in the example for emhpasis. + // In general, you do not have to annotate local variables. + @Interned String foo = "foo"; + String bar = new String("bar"); - if (foo == bar) { - System.out.println("foo == bar"); + if (foo == bar) { + System.out.println("foo == bar"); + } } - } } diff --git a/docs/examples/LockExample.java b/docs/examples/LockExample.java index a74f4e13a95..a5765e27295 100644 --- a/docs/examples/LockExample.java +++ b/docs/examples/LockExample.java @@ -1,70 +1,70 @@ import org.checkerframework.checker.lock.qual.*; class BankAccount { - int balance; + int balance; - void withdraw(@GuardSatisfied BankAccount this, int amount) { - this.balance = this.balance - amount; - } + void withdraw(@GuardSatisfied BankAccount this, int amount) { + this.balance = this.balance - amount; + } - void deposit(@GuardedBy("") BankAccount this, int amount) { - synchronized (this) { - this.balance = this.balance + amount; + void deposit(@GuardedBy("") BankAccount this, int amount) { + synchronized (this) { + this.balance = this.balance + amount; + } } - } } public class LockExample { - final @GuardedBy("") BankAccount myAccount; + final @GuardedBy("") BankAccount myAccount; - LockExample(@GuardedBy("") BankAccount in) { - this.myAccount = in; - } + LockExample(@GuardedBy("") BankAccount in) { + this.myAccount = in; + } - void demo1() { - myAccount.withdraw(100); // error! + void demo1() { + myAccount.withdraw(100); // error! - synchronized (myAccount) { - myAccount.withdraw(100); // OK + synchronized (myAccount) { + myAccount.withdraw(100); // OK + } } - } - @Holding("myAccount") - void demo1b() { - myAccount.withdraw(100); // OK - } + @Holding("myAccount") + void demo1b() { + myAccount.withdraw(100); // OK + } - void demo1c() { - demo1b(); // error! + void demo1c() { + demo1b(); // error! - synchronized (myAccount) { - demo1b(); + synchronized (myAccount) { + demo1b(); + } } - } - void demo2() { - myAccount.deposit(500); // OK - } + void demo2() { + myAccount.deposit(500); // OK + } - void demo3(Object someotherlock, @GuardedBy("someotherlock") BankAccount otherAccount) { - otherAccount.deposit(500); // error! - } + void demo3(Object someotherlock, @GuardedBy("someotherlock") BankAccount otherAccount) { + otherAccount.deposit(500); // error! + } - void demo3b(Object someotherlock, @GuardedBy("#1") BankAccount otherAccount) { - synchronized (someotherlock) { - otherAccount.deposit(500); // error! + void demo3b(Object someotherlock, @GuardedBy("#1") BankAccount otherAccount) { + synchronized (someotherlock) { + otherAccount.deposit(500); // error! + } } - } - void demo4() { - BankAccount spouseAccount = myAccount; // OK - spouseAccount.deposit(500); // OK + void demo4() { + BankAccount spouseAccount = myAccount; // OK + spouseAccount.deposit(500); // OK - synchronized (myAccount) { - spouseAccount.withdraw(100); // error! - } - synchronized (spouseAccount) { - spouseAccount.withdraw(200); // OK + synchronized (myAccount) { + spouseAccount.withdraw(100); // error! + } + synchronized (spouseAccount) { + spouseAccount.withdraw(200); // OK + } } - } } diff --git a/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java b/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java index e7c50e7787d..257e6136434 100644 --- a/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java +++ b/docs/examples/MavenExample-framework-all/src/main/java/org/checkerframework/example/MavenExample.java @@ -14,14 +14,14 @@ */ public class MavenExample { - public static @IntVal(5) int five = 5; + public static @IntVal(5) int five = 5; - public static void main(final String[] args) { - System.out.println("Hello World!"); + public static void main(final String[] args) { + System.out.println("Hello World!"); - StrBuilder stb = new StrBuilder(); + StrBuilder stb = new StrBuilder(); - @IntVal(55) int l = five; // error on this line - System.out.println(l); - } + @IntVal(55) int l = five; // error on this line + System.out.println(l); + } } diff --git a/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java b/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java index e5bc843ebde..37ce755b12f 100644 --- a/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java +++ b/docs/examples/MavenExample/src/main/java/org/checkerframework/example/MavenExample.java @@ -1,12 +1,13 @@ package org.checkerframework.example; -import java.util.HashMap; -import java.util.Map; import org.apache.commons.lang3.text.StrBuilder; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.HashMap; +import java.util.Map; + /** * If you run: * @@ -18,18 +19,18 @@ */ public class MavenExample { - public static @Nullable Object nullable = null; - public Map map = new HashMap<>(); + public static @Nullable Object nullable = null; + public Map map = new HashMap<>(); - public static void main(String[] args) { - System.out.println("Hello World!"); + public static void main(String[] args) { + System.out.println("Hello World!"); - StrBuilder stb = new StrBuilder(); + StrBuilder stb = new StrBuilder(); - @NonNull Object nn = nullable; // error on this line - System.out.println(nn); - } + @NonNull Object nn = nullable; // error on this line + System.out.println(nn); + } - // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. - void mapTest(@KeyFor("map") Object k) {} + // Test for -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED. + void mapTest(@KeyFor("map") Object k) {} } diff --git a/docs/examples/NullnessExample.java b/docs/examples/NullnessExample.java index 961695419cc..16891bacc40 100644 --- a/docs/examples/NullnessExample.java +++ b/docs/examples/NullnessExample.java @@ -1,6 +1,7 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.LinkedList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; /** * This class illustrates use of nullness type annotations. The class doesn't do anything -- it is @@ -12,27 +13,27 @@ */ public class NullnessExample { - public void example() { + public void example() { - // In general, you do not have to annotate local variables, because the - // Nullness Checker infers such annotations. It is written here in the - // example for emhpasis. - @NonNull String foo = "foo"; - @NonNull String bar = "bar"; + // In general, you do not have to annotate local variables, because the + // Nullness Checker infers such annotations. It is written here in the + // example for emhpasis. + @NonNull String foo = "foo"; + @NonNull String bar = "bar"; - foo = bar; - bar = foo; - } + foo = bar; + bar = foo; + } - public @NonNull String exampleGenerics() { + public @NonNull String exampleGenerics() { - List<@NonNull String> foo = new LinkedList<@NonNull String>(); - List<@NonNull String> bar = foo; + List<@NonNull String> foo = new LinkedList<@NonNull String>(); + List<@NonNull String> bar = foo; - @NonNull String quux = "quux"; - foo.add(quux); - foo.add("quux"); - @NonNull String baz = foo.get(0); - return baz; - } + @NonNull String quux = "quux"; + foo.add(quux); + foo.add("quux"); + @NonNull String baz = foo.get(0); + return baz; + } } diff --git a/docs/examples/NullnessExampleWithWarnings.java b/docs/examples/NullnessExampleWithWarnings.java index b0c684ce885..be28113d0a2 100644 --- a/docs/examples/NullnessExampleWithWarnings.java +++ b/docs/examples/NullnessExampleWithWarnings.java @@ -1,6 +1,7 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.LinkedList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; /** * This class illustrates use of nullness type annotations. The class doesn't do anything -- it is @@ -12,27 +13,27 @@ */ public class NullnessExampleWithWarnings { - public void example() { + public void example() { - // In general, you do not have to annotate local variables, because the - // Nullness Checker infers such annotations. It is written here in the - // example for emhpasis. - @NonNull String foo = "foo"; - String bar = null; + // In general, you do not have to annotate local variables, because the + // Nullness Checker infers such annotations. It is written here in the + // example for emhpasis. + @NonNull String foo = "foo"; + String bar = null; - foo = bar; - bar = foo; - } + foo = bar; + bar = foo; + } - public String exampleGenerics() { + public String exampleGenerics() { - List<@NonNull String> foo = new LinkedList<@NonNull String>(); - List bar = foo; + List<@NonNull String> foo = new LinkedList<@NonNull String>(); + List bar = foo; - String quux = null; - foo.add(quux); - foo.add("quux"); - @NonNull String baz = foo.get(0); - return baz; - } + String quux = null; + foo.add(quux); + foo.add("quux"); + @NonNull String baz = foo.get(0); + return baz; + } } diff --git a/docs/examples/NullnessReleaseTests.java b/docs/examples/NullnessReleaseTests.java index 848d640ecc1..b745fd4fe26 100644 --- a/docs/examples/NullnessReleaseTests.java +++ b/docs/examples/NullnessReleaseTests.java @@ -1,6 +1,7 @@ +import org.checkerframework.checker.nullness.qual.*; + import java.util.LinkedList; import java.util.List; -import org.checkerframework.checker.nullness.qual.*; /** * This class is based on NullnessExample. This version contains additional tests to ensure that a @@ -8,26 +9,26 @@ */ public class NullnessReleaseTests { - public void example() { - @NonNull String foo = "foo"; - @NonNull String bar = "bar"; + public void example() { + @NonNull String foo = "foo"; + @NonNull String bar = "bar"; - foo = bar; - bar = foo; - } + foo = bar; + bar = foo; + } - public @NonNull String exampleGenerics() { - List<@NonNull String> foo = new LinkedList<@NonNull String>(); - List<@NonNull String> bar = foo; + public @NonNull String exampleGenerics() { + List<@NonNull String> foo = new LinkedList<@NonNull String>(); + List<@NonNull String> bar = foo; - @NonNull String quux = "quux"; - foo.add(quux); - foo.add("quux"); - @NonNull String baz = foo.get(0); - return baz; - } + @NonNull String quux = "quux"; + foo.add(quux); + foo.add("quux"); + @NonNull String baz = foo.get(0); + return baz; + } - // For some reason this class causes an exception if the Checker - // Framework is compiled with JDK 7 and then executed on JDK 6. - class TestException extends Exception {} + // For some reason this class causes an exception if the Checker + // Framework is compiled with JDK 7 and then executed on JDK 6. + class TestException extends Exception {} } diff --git a/docs/examples/errorprone/build.gradle b/docs/examples/errorprone/build.gradle index d342cd80cf8..e1916676847 100644 --- a/docs/examples/errorprone/build.gradle +++ b/docs/examples/errorprone/build.gradle @@ -3,60 +3,60 @@ /// plugins { - id 'java' - id 'net.ltgt.errorprone' version '4.1.0' - // Checker Framework pluggable type-checking - id 'org.checkerframework' version '0.6.48' apply false + id 'java' + id 'net.ltgt.errorprone' version '4.1.0' + // Checker Framework pluggable type-checking + id 'org.checkerframework' version '0.6.48' apply false } ext { - versions = [ - eisopVersion: '3.42.0-eisop5', - ] + versions = [ + eisopVersion: '3.42.0-eisop5', + ] } apply plugin: 'org.checkerframework' if (false) { - def cfHome = "${projectDir}/../../.." - dependencies { - compileOnly files(cfHome + '/checker/dist/checker-qual.jar') - testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') - checkerFramework files(cfHome + '/checker/dist/checker.jar') - } + def cfHome = "${projectDir}/../../.." + dependencies { + compileOnly files(cfHome + '/checker/dist/checker-qual.jar') + testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') + checkerFramework files(cfHome + '/checker/dist/checker.jar') + } } else { - dependencies { - compileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" - testCompileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" - checkerFramework "io.github.eisop:checker-qual:${versions.eisopVersion}" - checkerFramework "io.github.eisop:checker:${versions.eisopVersion}" - } + dependencies { + compileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" + testCompileOnly "io.github.eisop:checker-qual:${versions.eisopVersion}" + checkerFramework "io.github.eisop:checker-qual:${versions.eisopVersion}" + checkerFramework "io.github.eisop:checker:${versions.eisopVersion}" + } } dependencies { - // Must use at least version 2.4.0 of Error Prone. - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - errorprone 'com.google.errorprone:error_prone_core:2.10.0' - } else if (JavaVersion.current() < JavaVersion.VERSION_17) { - errorprone 'com.google.errorprone:error_prone_core:2.31.0' - } else { - errorprone 'com.google.errorprone:error_prone_core:2.36.0' - } + // Must use at least version 2.4.0 of Error Prone. + if (JavaVersion.current() == JavaVersion.VERSION_1_8) { + errorprone 'com.google.errorprone:error_prone_core:2.10.0' + } else if (JavaVersion.current() < JavaVersion.VERSION_17) { + errorprone 'com.google.errorprone:error_prone_core:2.31.0' + } else { + errorprone 'com.google.errorprone:error_prone_core:2.36.0' + } } repositories { - mavenCentral() + mavenCentral() } checkerFramework { - checkers = [ - 'org.checkerframework.checker.nullness.NullnessChecker', - ] - extraJavacArgs = ['-Aversion'] + checkers = [ + 'org.checkerframework.checker.nullness.NullnessChecker', + ] + extraJavacArgs = ['-Aversion'] } compileJava { - // A checker will only run if Error Prone does not issue any warnings. So - // convert the expected error to a warning to test that both Error Prone - // and the Nullness Checker run. - options.errorprone.warn('CollectionIncompatibleType') + // A checker will only run if Error Prone does not issue any warnings. So + // convert the expected error to a warning to test that both Error Prone + // and the Nullness Checker run. + options.errorprone.warn('CollectionIncompatibleType') } diff --git a/docs/examples/errorprone/src/main/java/com/example/Demo.java b/docs/examples/errorprone/src/main/java/com/example/Demo.java index 85806b3908c..cde00ff5489 100644 --- a/docs/examples/errorprone/src/main/java/com/example/Demo.java +++ b/docs/examples/errorprone/src/main/java/com/example/Demo.java @@ -3,8 +3,8 @@ import java.util.Set; public class Demo { - void demo(Set s, short i) { - s.remove(i - 1); // Error Prone error - s.add(null); // Nullness Checker error - } + void demo(Set s, short i) { + s.remove(i - 1); // Error Prone error + s.add(null); // Nullness Checker error + } } diff --git a/docs/examples/fenum-extension/FenumDemo.java b/docs/examples/fenum-extension/FenumDemo.java index d3113930126..027a807f948 100644 --- a/docs/examples/fenum-extension/FenumDemo.java +++ b/docs/examples/fenum-extension/FenumDemo.java @@ -1,82 +1,83 @@ import org.checkerframework.checker.fenum.qual.Fenum; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.framework.qual.DefaultQualifier; + import qual.MyFenum; @SuppressWarnings("fenum:assignment.type.incompatible") // initialization of fake enums class TestStatic { - public static final @Fenum("A") int ACONST1 = 1; - public static final @Fenum("A") int ACONST2 = 2; + public static final @Fenum("A") int ACONST1 = 1; + public static final @Fenum("A") int ACONST2 = 2; - public static final @Fenum("B") int BCONST1 = 4; - public static final @Fenum("B") int BCONST2 = 5; + public static final @Fenum("B") int BCONST1 = 4; + public static final @Fenum("B") int BCONST2 = 5; - public static final @MyFenum int CCONST1 = 5; - public static final @MyFenum int CCONST2 = 6; + public static final @MyFenum int CCONST1 = 5; + public static final @MyFenum int CCONST2 = 6; } public class FenumDemo { - @Fenum("A") int state1 = TestStatic.ACONST1; // ok - - @Fenum("B") int state2 = TestStatic.ACONST1; // Incompatible fenums forbidden! - - @MyFenum int state3 = TestStatic.CCONST1; // ok + @Fenum("A") int state1 = TestStatic.ACONST1; // ok - short state4 = TestStatic.CCONST1; // ok, @MyFenum applies to short by default - @NonNegative short state5 = TestStatic.CCONST1; // ok, @MyFenum also applies to - // @NonNegative short by default (see issue #333) + @Fenum("B") int state2 = TestStatic.ACONST1; // Incompatible fenums forbidden! - int state6 = TestStatic.BCONST1; // Incompatible fenums forbidden! - @NonNegative int state7 = TestStatic.BCONST1; // Incompatible fenums forbidden! + @MyFenum int state3 = TestStatic.CCONST1; // ok - void fenumArg(@Fenum("A") int p) {} + short state4 = TestStatic.CCONST1; // ok, @MyFenum applies to short by default + @NonNegative short state5 = TestStatic.CCONST1; // ok, @MyFenum also applies to + // @NonNegative short by default (see issue #333) - void myFenumArg(@MyFenum int p) {} + int state6 = TestStatic.BCONST1; // Incompatible fenums forbidden! + @NonNegative int state7 = TestStatic.BCONST1; // Incompatible fenums forbidden! - void foo() { - state1 = 4; // Direct use of value forbidden! - state1 = TestStatic.BCONST1; // Incompatible fenums forbidden! - state1 = TestStatic.ACONST2; // ok + void fenumArg(@Fenum("A") int p) {} - fenumArg(5); // Direct use of value forbidden! - fenumArg(TestStatic.BCONST1); // Incompatible fenums forbidden! - fenumArg(TestStatic.ACONST1); // ok + void myFenumArg(@MyFenum int p) {} - state3 = 8; - state3 = TestStatic.ACONST2; // Incompatible fenums forbidden! - state3 = TestStatic.CCONST2; // ok + void foo() { + state1 = 4; // Direct use of value forbidden! + state1 = TestStatic.BCONST1; // Incompatible fenums forbidden! + state1 = TestStatic.ACONST2; // ok - myFenumArg(8); // Direct use of value forbidden! - myFenumArg(TestStatic.BCONST2); // Incompatible fenums forbidden! - myFenumArg(TestStatic.CCONST1); // ok - } + fenumArg(5); // Direct use of value forbidden! + fenumArg(TestStatic.BCONST1); // Incompatible fenums forbidden! + fenumArg(TestStatic.ACONST1); // ok - @DefaultQualifier(MyFenum.class) - void bar() { - int int0 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method - @NonNegative int int1 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method - } + state3 = 8; + state3 = TestStatic.ACONST2; // Incompatible fenums forbidden! + state3 = TestStatic.CCONST2; // ok - void comparisons() { - if (TestStatic.ACONST1 < TestStatic.ACONST2) { - // ok + myFenumArg(8); // Direct use of value forbidden! + myFenumArg(TestStatic.BCONST2); // Incompatible fenums forbidden! + myFenumArg(TestStatic.CCONST1); // ok } - if (TestStatic.CCONST1 > TestStatic.CCONST2) { - // ok + + @DefaultQualifier(MyFenum.class) + void bar() { + int int0 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method + @NonNegative int int1 = TestStatic.CCONST1; // ok, @MyFenum applies by default in this method } - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 < TestStatic.BCONST2) {} - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 == TestStatic.BCONST2) {} - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 >= TestStatic.CCONST2) {} - - // :: error: (binary.type.incompatible) - if (TestStatic.ACONST1 < 5) {} - // :: error: (binary.type.incompatible) - if (TestStatic.BCONST1 > 5) {} - // :: error: (binary.type.incompatible) - if (TestStatic.CCONST1 == 5) {} - } + void comparisons() { + if (TestStatic.ACONST1 < TestStatic.ACONST2) { + // ok + } + if (TestStatic.CCONST1 > TestStatic.CCONST2) { + // ok + } + + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 < TestStatic.BCONST2) {} + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 == TestStatic.BCONST2) {} + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 >= TestStatic.CCONST2) {} + + // :: error: (binary.type.incompatible) + if (TestStatic.ACONST1 < 5) {} + // :: error: (binary.type.incompatible) + if (TestStatic.BCONST1 > 5) {} + // :: error: (binary.type.incompatible) + if (TestStatic.CCONST1 == 5) {} + } } diff --git a/docs/examples/fenum-extension/qual/MyFenum.java b/docs/examples/fenum-extension/qual/MyFenum.java index d32a024db7b..2c7df239c40 100644 --- a/docs/examples/fenum-extension/qual/MyFenum.java +++ b/docs/examples/fenum-extension/qual/MyFenum.java @@ -1,13 +1,14 @@ package qual; +import org.checkerframework.checker.fenum.qual.FenumTop; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.fenum.qual.FenumTop; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/docs/examples/lombok/build.gradle b/docs/examples/lombok/build.gradle index a439fd08b64..410b0c88be5 100644 --- a/docs/examples/lombok/build.gradle +++ b/docs/examples/lombok/build.gradle @@ -3,32 +3,32 @@ /// plugins { - id 'java' - id 'io.freefair.lombok' version '8.12' - // Checker Framework pluggable type-checking - id 'org.checkerframework' version '0.6.48' + id 'java' + id 'io.freefair.lombok' version '8.12' + // Checker Framework pluggable type-checking + id 'org.checkerframework' version '0.6.48' } lombok { - version = "1.18.30" + version = "1.18.30" } apply plugin: 'org.checkerframework' def cfHome = "${projectDir}/../../.." dependencies { - compileOnly files(cfHome + '/checker/dist/checker-qual.jar') - testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') - checkerFramework files(cfHome + '/checker/dist/checker.jar') + compileOnly files(cfHome + '/checker/dist/checker-qual.jar') + testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') + checkerFramework files(cfHome + '/checker/dist/checker.jar') } repositories { - mavenCentral() + mavenCentral() } checkerFramework { - checkers = [ - 'org.checkerframework.checker.nullness.NullnessChecker', - ] - extraJavacArgs = ['-Aversion'] + checkers = [ + 'org.checkerframework.checker.nullness.NullnessChecker', + ] + extraJavacArgs = ['-Aversion'] } diff --git a/docs/examples/lombok/src/main/java/lib/Foo.java b/docs/examples/lombok/src/main/java/lib/Foo.java index bc81d1b10cf..e9fd6dc0eae 100644 --- a/docs/examples/lombok/src/main/java/lib/Foo.java +++ b/docs/examples/lombok/src/main/java/lib/Foo.java @@ -1,15 +1,16 @@ package lib; import lombok.Builder; + import org.checkerframework.checker.nullness.qual.Nullable; @Builder public class Foo { - private @Nullable Integer x; - private Integer y; + private @Nullable Integer x; + private Integer y; - void demo() { - x = null; // ok - y = null; // error - } + void demo() { + x = null; // ok + y = null; // error + } } diff --git a/docs/examples/lombok/src/main/java/use/User.java b/docs/examples/lombok/src/main/java/use/User.java index 741ecf16d38..64f218ddc32 100644 --- a/docs/examples/lombok/src/main/java/use/User.java +++ b/docs/examples/lombok/src/main/java/use/User.java @@ -3,10 +3,10 @@ import lib.Foo; public class User { - Foo demo() { - return Foo.builder() - .x(null) // ok - .y(null) // error - .build(); - } + Foo demo() { + return Foo.builder() + .x(null) // ok + .y(null) // error + .build(); + } } diff --git a/docs/examples/subtyping-extension/Demo.java b/docs/examples/subtyping-extension/Demo.java index 35994c9b640..4cc3a80805a 100644 --- a/docs/examples/subtyping-extension/Demo.java +++ b/docs/examples/subtyping-extension/Demo.java @@ -1,38 +1,39 @@ import java.util.LinkedList; import java.util.List; + import qual.Encrypted; abstract class EncryptionDemo { - public @Encrypted String encrypt(String text) { - byte[] b = text.getBytes(); - for (int i = 0; i < b.length; b[i++]++) { - // side effect is in increment expression of for loop + public @Encrypted String encrypt(String text) { + byte[] b = text.getBytes(); + for (int i = 0; i < b.length; b[i++]++) { + // side effect is in increment expression of for loop + } + // :: warning: (cast.unsafe) + return (@Encrypted String) new String(b); } - // :: warning: (cast.unsafe) - return (@Encrypted String) new String(b); - } - // Only send encrypted data! - abstract void sendOverTheInternet(@Encrypted String msg); + // Only send encrypted data! + abstract void sendOverTheInternet(@Encrypted String msg); - void sendText() { - @Encrypted String s = encrypt("foo"); // valid - sendOverTheInternet(s); // valid + void sendText() { + @Encrypted String s = encrypt("foo"); // valid + sendOverTheInternet(s); // valid - String t = encrypt("bar"); // valid (subtype) - sendOverTheInternet(t); // valid (flow) + String t = encrypt("bar"); // valid (subtype) + sendOverTheInternet(t); // valid (flow) - List<@Encrypted String> lst = new LinkedList<@Encrypted String>(); - lst.add(s); - lst.add(t); + List<@Encrypted String> lst = new LinkedList<@Encrypted String>(); + lst.add(s); + lst.add(t); - for (String str : lst) // valid - sendOverTheInternet(str); - } + for (String str : lst) // valid + sendOverTheInternet(str); + } - void sendPassword() { - String password = "unencrypted"; - sendOverTheInternet(password); // invalid - } + void sendPassword() { + String password = "unencrypted"; + sendOverTheInternet(password); // invalid + } } diff --git a/docs/examples/subtyping-extension/qual/Encrypted.java b/docs/examples/subtyping-extension/qual/Encrypted.java index 5f781b68405..21617420927 100644 --- a/docs/examples/subtyping-extension/qual/Encrypted.java +++ b/docs/examples/subtyping-extension/qual/Encrypted.java @@ -1,13 +1,14 @@ package qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** Denotes that the representation of an object is encrypted. */ @Documented diff --git a/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java b/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java index b5ce5cf00a2..13cc4770a86 100644 --- a/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java +++ b/docs/examples/subtyping-extension/qual/PossiblyUnencrypted.java @@ -1,12 +1,13 @@ package qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** Denotes that the representation of an object might not be encrypted. */ @Documented diff --git a/docs/examples/units-extension/UnitsExtensionDemo.java b/docs/examples/units-extension/UnitsExtensionDemo.java index 10de7a1f464..3c78eef43fb 100644 --- a/docs/examples/units-extension/UnitsExtensionDemo.java +++ b/docs/examples/units-extension/UnitsExtensionDemo.java @@ -1,69 +1,70 @@ import org.checkerframework.checker.units.qual.Prefix; import org.checkerframework.checker.units.qual.s; import org.checkerframework.checker.units.util.UnitsTools; + import qual.Frequency; import qual.Hz; import qual.kHz; public class UnitsExtensionDemo { - @Hz int frq; + @Hz int frq; - void bad() { - // Error! Unqualified value assigned to a @Hz value. - // :: error: (assignment.type.incompatible) - frq = 5; + void bad() { + // Error! Unqualified value assigned to a @Hz value. + // :: error: (assignment.type.incompatible) + frq = 5; - // suppress all warnings issued by the units checker for the d1 assignment statement - @SuppressWarnings("units") - @Hz int d1 = 9; + // suppress all warnings issued by the units checker for the d1 assignment statement + @SuppressWarnings("units") + @Hz int d1 = 9; - // specifically suppress warnings related to any frequency units for the d2 assigment - // statement - @SuppressWarnings("frequency") - @Hz int d2 = 10; - } + // specifically suppress warnings related to any frequency units for the d2 assigment + // statement + @SuppressWarnings("frequency") + @Hz int d2 = 10; + } - // specifically suppresses warnings for the hz annotation for the toHz method - @SuppressWarnings("hz") - static @Hz int toHz(int hz) { - return hz; - } + // specifically suppresses warnings for the hz annotation for the toHz method + @SuppressWarnings("hz") + static @Hz int toHz(int hz) { + return hz; + } - void good() { - frq = toHz(9); + void good() { + frq = toHz(9); - @s double time = 5 * UnitsTools.s; - @Hz double freq2 = 20 / time; - } + @s double time = 5 * UnitsTools.s; + @Hz double freq2 = 20 / time; + } - void auto(@s int time) { - // The @Hz annotation is automatically added to the result - // of the division, because we provide class FrequencyRelations. - frq = 99 / time; - } + void auto(@s int time) { + // The @Hz annotation is automatically added to the result + // of the division, because we provide class FrequencyRelations. + frq = 99 / time; + } - public static void main(String[] args) { - @Hz int hertz = toHz(20); - @s int seconds = 5 * UnitsTools.s; + public static void main(String[] args) { + @Hz int hertz = toHz(20); + @s int seconds = 5 * UnitsTools.s; - @SuppressWarnings("units") - @s(Prefix.milli) int millisec = 10; + @SuppressWarnings("units") + @s(Prefix.milli) int millisec = 10; - @SuppressWarnings("hz") - @kHz int kilohertz = 30; + @SuppressWarnings("hz") + @kHz int kilohertz = 30; - @Hz int resultHz = hertz + 20 / seconds; - System.out.println(resultHz); + @Hz int resultHz = hertz + 20 / seconds; + System.out.println(resultHz); - @kHz int resultkHz = kilohertz + 50 / millisec; - System.out.println(resultkHz); + @kHz int resultkHz = kilohertz + 50 / millisec; + System.out.println(resultkHz); - // this demonstrates the type hierarchy resolution: the common supertype of Hz and kHz is - // Frequency, so this statement will pass - @Frequency int okTernaryAssign = seconds > 10 ? hertz : kilohertz; + // this demonstrates the type hierarchy resolution: the common supertype of Hz and kHz is + // Frequency, so this statement will pass + @Frequency int okTernaryAssign = seconds > 10 ? hertz : kilohertz; - // on the other hand, this statement expects the right hand side to be a Hz, so it will fail - // :: error: (assignment.type.incompatible) - @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz; - } + // on the other hand, this statement expects the right hand side to be a Hz, so it will fail + // :: error: (assignment.type.incompatible) + @Hz int badTernaryAssign = seconds > 10 ? hertz : kilohertz; + } } diff --git a/docs/examples/units-extension/qual/Frequency.java b/docs/examples/units-extension/qual/Frequency.java index 793acd1994f..1ab6b088943 100644 --- a/docs/examples/units-extension/qual/Frequency.java +++ b/docs/examples/units-extension/qual/Frequency.java @@ -1,13 +1,14 @@ package qual; +import org.checkerframework.checker.units.qual.UnitsRelations; +import org.checkerframework.checker.units.qual.UnknownUnits; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.units.qual.UnitsRelations; -import org.checkerframework.checker.units.qual.UnknownUnits; -import org.checkerframework.framework.qual.SubtypeOf; /** * Units of frequency, such as hertz (@{@link Hz}). diff --git a/docs/examples/units-extension/qual/FrequencyRelations.java b/docs/examples/units-extension/qual/FrequencyRelations.java index 9f60012b708..5717d8110d7 100644 --- a/docs/examples/units-extension/qual/FrequencyRelations.java +++ b/docs/examples/units-extension/qual/FrequencyRelations.java @@ -1,8 +1,5 @@ package qual; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.units.UnitsRelations; import org.checkerframework.checker.units.UnitsRelationsTools; @@ -10,47 +7,53 @@ import org.checkerframework.checker.units.qual.s; import org.checkerframework.framework.type.AnnotatedTypeMirror; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** Relations among units of frequency. */ public class FrequencyRelations implements UnitsRelations { - protected AnnotationMirror hertz, kilohertz, second, millisecond; - protected Elements elements; - - public UnitsRelations init(ProcessingEnvironment env) { - elements = env.getElementUtils(); - - // create Annotation Mirrors, each representing a particular Unit's Annotation - hertz = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, Hz.class); - kilohertz = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, Hz.class, Prefix.kilo); - second = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); - millisecond = UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, s.class, Prefix.milli); - - return this; - } - - /** No multiplications yield Hertz. */ - public @Nullable AnnotationMirror multiplication( - AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - // return null so the default units relations can process multiplcations of other units - return null; - } - - /** - * Division of a scalar by seconds yields Hertz. Division of a scalar by milliseconds yields - * Kilohertz. Other divisions yield an unannotated value. - */ - public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { - if (UnitsRelationsTools.hasNoUnits(lht)) { - // scalar / millisecond => kilohertz - if (UnitsRelationsTools.hasSpecificUnit(rht, millisecond)) { - return kilohertz; - } - // scalar / second => hertz - else if (UnitsRelationsTools.hasSpecificUnit(rht, second)) { - return hertz; - } + protected AnnotationMirror hertz, kilohertz, second, millisecond; + protected Elements elements; + + public UnitsRelations init(ProcessingEnvironment env) { + elements = env.getElementUtils(); + + // create Annotation Mirrors, each representing a particular Unit's Annotation + hertz = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, Hz.class); + kilohertz = + UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, Hz.class, Prefix.kilo); + second = UnitsRelationsTools.buildAnnoMirrorWithDefaultPrefix(env, s.class); + millisecond = + UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(env, s.class, Prefix.milli); + + return this; } - return null; - } + /** No multiplications yield Hertz. */ + public @Nullable AnnotationMirror multiplication( + AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + // return null so the default units relations can process multiplcations of other units + return null; + } + + /** + * Division of a scalar by seconds yields Hertz. Division of a scalar by milliseconds yields + * Kilohertz. Other divisions yield an unannotated value. + */ + public @Nullable AnnotationMirror division(AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) { + if (UnitsRelationsTools.hasNoUnits(lht)) { + // scalar / millisecond => kilohertz + if (UnitsRelationsTools.hasSpecificUnit(rht, millisecond)) { + return kilohertz; + } + // scalar / second => hertz + else if (UnitsRelationsTools.hasSpecificUnit(rht, second)) { + return hertz; + } + } + + return null; + } } diff --git a/docs/examples/units-extension/qual/Hz.java b/docs/examples/units-extension/qual/Hz.java index 055407831ca..34f549eb92b 100644 --- a/docs/examples/units-extension/qual/Hz.java +++ b/docs/examples/units-extension/qual/Hz.java @@ -1,13 +1,14 @@ package qual; +import org.checkerframework.checker.units.qual.Prefix; +import org.checkerframework.checker.units.qual.UnitsRelations; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.units.qual.Prefix; -import org.checkerframework.checker.units.qual.UnitsRelations; -import org.checkerframework.framework.qual.SubtypeOf; /** * Hertz (Hz), a unit of frequency. @@ -20,5 +21,5 @@ @SubtypeOf(Frequency.class) @UnitsRelations(FrequencyRelations.class) public @interface Hz { - Prefix value() default Prefix.one; + Prefix value() default Prefix.one; } diff --git a/docs/examples/units-extension/qual/kHz.java b/docs/examples/units-extension/qual/kHz.java index 48ddab5c665..7ddfd8b8d9e 100644 --- a/docs/examples/units-extension/qual/kHz.java +++ b/docs/examples/units-extension/qual/kHz.java @@ -1,14 +1,15 @@ package qual; +import org.checkerframework.checker.units.qual.Prefix; +import org.checkerframework.checker.units.qual.UnitsMultiple; +import org.checkerframework.checker.units.qual.UnitsRelations; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.checker.units.qual.Prefix; -import org.checkerframework.checker.units.qual.UnitsMultiple; -import org.checkerframework.checker.units.qual.UnitsRelations; -import org.checkerframework.framework.qual.SubtypeOf; /** * Kilohertz (kHz), a unit of frequency, and an alias of @Hz(Prefix.kilo). diff --git a/docs/tutorial/src/NullnessExample.java b/docs/tutorial/src/NullnessExample.java index 909308a9ea1..d5a29a6c2e3 100644 --- a/docs/tutorial/src/NullnessExample.java +++ b/docs/tutorial/src/NullnessExample.java @@ -1,10 +1,10 @@ public class NullnessExample { - public static void main(String[] args) { - Object myObject = null; + public static void main(String[] args) { + Object myObject = null; - if (args.length > 2) { - myObject = new Object(); + if (args.length > 2) { + myObject = new Object(); + } + System.out.println(myObject.toString()); } - System.out.println(myObject.toString()); - } } diff --git a/docs/tutorial/src/RegexExample.java b/docs/tutorial/src/RegexExample.java index 824eaf00a6a..72c7a17de1d 100644 --- a/docs/tutorial/src/RegexExample.java +++ b/docs/tutorial/src/RegexExample.java @@ -6,17 +6,17 @@ * text, from the string, that matches the first capturing group in the regular expression. */ public class RegexExample { - public static void main(String[] args) { - String regex = args[0]; - String content = args[1]; + public static void main(String[] args) { + String regex = args[0]; + String content = args[1]; - Pattern pat = Pattern.compile(regex); - Matcher mat = pat.matcher(content); + Pattern pat = Pattern.compile(regex); + Matcher mat = pat.matcher(content); - if (mat.matches()) { - System.out.println("Group 1: " + mat.group(1)); - } else { - System.out.println("No match!"); + if (mat.matches()) { + System.out.println("Group 1: " + mat.group(1)); + } else { + System.out.println("No match!"); + } } - } } diff --git a/docs/tutorial/src/encrypted/EncryptionDemo.java b/docs/tutorial/src/encrypted/EncryptionDemo.java index 38feb8096c5..cd34d8a8bba 100644 --- a/docs/tutorial/src/encrypted/EncryptionDemo.java +++ b/docs/tutorial/src/encrypted/EncryptionDemo.java @@ -3,32 +3,32 @@ import myqual.Encrypted; public class EncryptionDemo { - private final int OFFSET = 13; + private final int OFFSET = 13; - public @Encrypted String encrypt(String text) { - @Encrypted String encryptedText = ""; - for (char character : text.toCharArray()) { - encryptedText += encryptCharacter(character); + public @Encrypted String encrypt(String text) { + @Encrypted String encryptedText = ""; + for (char character : text.toCharArray()) { + encryptedText += encryptCharacter(character); + } + return encryptedText; } - return encryptedText; - } - private @Encrypted char encryptCharacter(char character) { - @Encrypted int encryptInt = (character + OFFSET) % Character.MAX_VALUE; - return (@Encrypted char) encryptInt; - } + private @Encrypted char encryptCharacter(char character) { + @Encrypted int encryptInt = (character + OFFSET) % Character.MAX_VALUE; + return (@Encrypted char) encryptInt; + } - // Only send encrypted data! - public void sendOverInternet(@Encrypted String msg) { - // ... - } + // Only send encrypted data! + public void sendOverInternet(@Encrypted String msg) { + // ... + } - public void sendPassword() { - String password = getUserPassword(); - sendOverInternet(password); - } + public void sendPassword() { + String password = getUserPassword(); + sendOverInternet(password); + } - private String getUserPassword() { - return "!@#$Really Good Password**"; - } + private String getUserPassword() { + return "!@#$Really Good Password**"; + } } diff --git a/docs/tutorial/src/myqual/Encrypted.java b/docs/tutorial/src/myqual/Encrypted.java index 2a5ff0c0758..ef7f561377f 100644 --- a/docs/tutorial/src/myqual/Encrypted.java +++ b/docs/tutorial/src/myqual/Encrypted.java @@ -1,10 +1,11 @@ package myqual; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; /** Denotes that the representation of an object is encrypted. */ @Documented diff --git a/docs/tutorial/src/myqual/PolyEncrypted.java b/docs/tutorial/src/myqual/PolyEncrypted.java index 5930cffdfe3..0ecff27c024 100644 --- a/docs/tutorial/src/myqual/PolyEncrypted.java +++ b/docs/tutorial/src/myqual/PolyEncrypted.java @@ -1,9 +1,10 @@ package myqual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** Denotes that the representation of an object is encrypted. */ @Documented diff --git a/docs/tutorial/src/myqual/PossiblyUnencrypted.java b/docs/tutorial/src/myqual/PossiblyUnencrypted.java index 33f1022aad2..e64f5fd9434 100644 --- a/docs/tutorial/src/myqual/PossiblyUnencrypted.java +++ b/docs/tutorial/src/myqual/PossiblyUnencrypted.java @@ -1,10 +1,11 @@ package myqual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** Denotes that the representation of an object might not be encrypted. */ @Documented diff --git a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java index e1251bc8023..10fce17677d 100644 --- a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java +++ b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java @@ -1,10 +1,15 @@ package net.eyde.personalblog.service; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.checkerframework.checker.tainting.qual.Untainted; + import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.Properties; + import net.eyde.personalblog.beans.BlogProperty; import net.eyde.personalblog.beans.Comment; import net.eyde.personalblog.beans.Post; @@ -12,9 +17,6 @@ import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.cfg.Configuration; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.checkerframework.checker.tainting.qual.Untainted; /** * @author NEyde @@ -23,157 +25,157 @@ *

          When a user selects a month, they will get all the posts for the month. */ public class PersonalBlogService { - // Installation State - public static final String INSTALLATION_STATE = "installation_state"; - public static final String STATE_UNDEFINED = "undefined"; - public static final String STATE_NO_HIBERNATE_FILE = "no_hibernate_file"; - public static final String STATE_DATABASE_OFF = "database_off"; - public static final String STATE_HIBERNATE_FILE_INVALID = "hibernate_file_invalid"; - public static final String STATE_TABLES_NOT_CREATED = "tables_not_created_yet"; - public static final String STATE_MISSING_PROPERTIES = "missing_properties"; - public static final String STATE_OK = "ok"; - private static Log log = LogFactory.getLog(PersonalBlogService.class); - private static PersonalBlogService service = null; - - // Property Name Constants - public static final String WEBLOG_TITLE = "weblog.title"; - public static final String WEBLOG_DESCRIPTION = "weblog.description"; - public static final String WEBLOG_PICTURE = "weblog.ownerpicture"; - public static final String WEBLOG_OWNER_NICK_NAME = "weblog.ownernickname"; - public static final String WEBLOG_URL = "weblog.url"; - public static final String WEBLOG_OWNER = "weblog.owner"; - public static final String WEBLOG_EMAIL = "weblog.email"; - public static final String LINK_POST = "links.post"; - public static final String EMOTICON_VALUES = "emoticon.values"; - public static final String EMOTICON_IMAGES = "emoticon.images"; - public static final String LOGON_ID = "logon.id"; - public static final String LOGON_PASSWORD = "logon.password"; - public static final String EDITOR = "weblog.editor"; - public static final String EMAIL_HOST = "mail.smtp.host"; - public static final String EMAIL_TRANSPORT = "mail.transport"; - public static final String EMAIL_USERNAME = "mail.username"; - public static final String EMAIL_PASSWORD = "mail.password"; - public static final String CATEGORY_TITLES = "category.titles"; - public static final String CATEGORY_VALUES = "category.values"; - public static final String CATEGORY_IMAGES = "category.images"; - Configuration cfg; - SessionFactory sf; - - int adjustHours; - PropertyManager pm; - CacheManager cache; - - // is really necessary when you are going to format it? - Locale myLocale = Locale.US; - String dburl; - String dbuser; - String dbpassword; - SimpleDateFormat qf = new SimpleDateFormat("yyyy-MM-dd", myLocale); - SimpleDateFormat monthNav = new SimpleDateFormat("yyyyMM", myLocale); - - /** Constructor for PersonalBlogService. */ - protected PersonalBlogService(Properties conn) throws InitializationException { - log.debug("initialization - constructor"); - - try { - cfg = - new Configuration() - .addClass(Post.class) - .addClass(Comment.class) - .addClass(Referrer.class) - .addClass(BlogProperty.class); - - if (conn != null) { - cfg.setProperties(conn); - pm = new PropertyManager(conn); - } else { - pm = new PropertyManager(); - } - - // I want to take it out of here, for these - sf = cfg.buildSessionFactory(); - } catch (Exception e) { - log.error("Error initializing PersonalBlog Service", e); - - throw new InitializationException(e); + // Installation State + public static final String INSTALLATION_STATE = "installation_state"; + public static final String STATE_UNDEFINED = "undefined"; + public static final String STATE_NO_HIBERNATE_FILE = "no_hibernate_file"; + public static final String STATE_DATABASE_OFF = "database_off"; + public static final String STATE_HIBERNATE_FILE_INVALID = "hibernate_file_invalid"; + public static final String STATE_TABLES_NOT_CREATED = "tables_not_created_yet"; + public static final String STATE_MISSING_PROPERTIES = "missing_properties"; + public static final String STATE_OK = "ok"; + private static Log log = LogFactory.getLog(PersonalBlogService.class); + private static PersonalBlogService service = null; + + // Property Name Constants + public static final String WEBLOG_TITLE = "weblog.title"; + public static final String WEBLOG_DESCRIPTION = "weblog.description"; + public static final String WEBLOG_PICTURE = "weblog.ownerpicture"; + public static final String WEBLOG_OWNER_NICK_NAME = "weblog.ownernickname"; + public static final String WEBLOG_URL = "weblog.url"; + public static final String WEBLOG_OWNER = "weblog.owner"; + public static final String WEBLOG_EMAIL = "weblog.email"; + public static final String LINK_POST = "links.post"; + public static final String EMOTICON_VALUES = "emoticon.values"; + public static final String EMOTICON_IMAGES = "emoticon.images"; + public static final String LOGON_ID = "logon.id"; + public static final String LOGON_PASSWORD = "logon.password"; + public static final String EDITOR = "weblog.editor"; + public static final String EMAIL_HOST = "mail.smtp.host"; + public static final String EMAIL_TRANSPORT = "mail.transport"; + public static final String EMAIL_USERNAME = "mail.username"; + public static final String EMAIL_PASSWORD = "mail.password"; + public static final String CATEGORY_TITLES = "category.titles"; + public static final String CATEGORY_VALUES = "category.values"; + public static final String CATEGORY_IMAGES = "category.images"; + Configuration cfg; + SessionFactory sf; + + int adjustHours; + PropertyManager pm; + CacheManager cache; + + // is really necessary when you are going to format it? + Locale myLocale = Locale.US; + String dburl; + String dbuser; + String dbpassword; + SimpleDateFormat qf = new SimpleDateFormat("yyyy-MM-dd", myLocale); + SimpleDateFormat monthNav = new SimpleDateFormat("yyyyMM", myLocale); + + /** Constructor for PersonalBlogService. */ + protected PersonalBlogService(Properties conn) throws InitializationException { + log.debug("initialization - constructor"); + + try { + cfg = + new Configuration() + .addClass(Post.class) + .addClass(Comment.class) + .addClass(Referrer.class) + .addClass(BlogProperty.class); + + if (conn != null) { + cfg.setProperties(conn); + pm = new PropertyManager(conn); + } else { + pm = new PropertyManager(); + } + + // I want to take it out of here, for these + sf = cfg.buildSessionFactory(); + } catch (Exception e) { + log.error("Error initializing PersonalBlog Service", e); + + throw new InitializationException(e); + } } - } - - /** Singleton getInstance method */ - public static PersonalBlogService getInstance() throws ServiceException { - if (service == null) { - try { - log.debug("Initializing PersonalBlog Service (WITHOUT CONNECTION PARMS)"); - service = new PersonalBlogService(null); - } catch (ServiceException e) { - log.error("Error getting instance of PersonalBlog Service", e); - - throw e; - } + + /** Singleton getInstance method */ + public static PersonalBlogService getInstance() throws ServiceException { + if (service == null) { + try { + log.debug("Initializing PersonalBlog Service (WITHOUT CONNECTION PARMS)"); + service = new PersonalBlogService(null); + } catch (ServiceException e) { + log.error("Error getting instance of PersonalBlog Service", e); + + throw e; + } + } + + return service; + } + + public static PersonalBlogService getInstance(Properties conn) throws ServiceException { + if (service == null) { + try { + log.debug("Initializing PersonalBlog Service (WITH CONNECTION PARMS)"); + service = new PersonalBlogService(conn); + } catch (Exception e) { + log.error("Error getting instance of PersonalBlog Service", e); + } + } + + return service; } - return service; - } - - public static PersonalBlogService getInstance(Properties conn) throws ServiceException { - if (service == null) { - try { - log.debug("Initializing PersonalBlog Service (WITH CONNECTION PARMS)"); - service = new PersonalBlogService(conn); - } catch (Exception e) { - log.error("Error getting instance of PersonalBlog Service", e); - } + /* + * This method will return the most recent posts for today's date. This method + * will return a maximum of 25 total posts or three days worth of posts. + * + */ + public List getPosts() throws ServiceException { + List posts = null; + + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MONTH, -1); + @SuppressWarnings("tainting") + String startdate = (@Untainted String) qf.format(cal.getTime()); + + posts = + executeQuery( + "from post in class net.eyde.personalblog.beans.Post " + + "where post.created > '" + + startdate + + "' order by post.created desc"); + + return posts; + } + + public List getPostsByCategory(String category) throws ServiceException { + List posts = null; + + posts = + executeQuery( + "from post in class net.eyde.personalblog.beans.Post " + + "where post.category like '%" + + category + + "%' order by post.created desc"); + + return posts; } - return service; - } - - /* - * This method will return the most recent posts for today's date. This method - * will return a maximum of 25 total posts or three days worth of posts. - * - */ - public List getPosts() throws ServiceException { - List posts = null; - - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.MONTH, -1); - @SuppressWarnings("tainting") - String startdate = (@Untainted String) qf.format(cal.getTime()); - - posts = - executeQuery( - "from post in class net.eyde.personalblog.beans.Post " - + "where post.created > '" - + startdate - + "' order by post.created desc"); - - return posts; - } - - public List getPostsByCategory(String category) throws ServiceException { - List posts = null; - - posts = - executeQuery( - "from post in class net.eyde.personalblog.beans.Post " - + "where post.category like '%" - + category - + "%' order by post.created desc"); - - return posts; - } - - private List executeQuery(@Untainted String query) { - try { - Session session = sf.openSession(); - @SuppressWarnings({"unchecked"}) - List lst = (List) session.find(query); - session.close(); - return lst; - } catch (Exception e) { - log.error("Error while importing data", e); - return null; + private List executeQuery(@Untainted String query) { + try { + Session session = sf.openSession(); + @SuppressWarnings({"unchecked"}) + List lst = (List) session.find(query); + session.close(); + return lst; + } catch (Exception e) { + log.error("Error while importing data", e); + return null; + } } - } } diff --git a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java index 6d22737279d..07b7384fc9b 100644 --- a/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java +++ b/docs/tutorial/src/personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java @@ -1,9 +1,5 @@ package net.eyde.personalblog.struts.action; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import net.eyde.personalblog.service.PersonalBlogService; -import net.eyde.personalblog.service.ServiceException; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; @@ -12,6 +8,12 @@ import org.apache.struts.action.ActionMessages; import org.checkerframework.checker.tainting.qual.Untainted; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import net.eyde.personalblog.service.PersonalBlogService; +import net.eyde.personalblog.service.ServiceException; + /** * Description of the Class * @@ -19,79 +21,79 @@ * @created September 17, 2002 */ public final class ReadAction extends BlogGeneralAction { - /** - * Process the specified HTTP request, and create the corresponding HTTP response (or forward to - * another web component that will create it). Return an ActionForward instance describing where - * and how control should be forwarded, or null if the response has already been completed. - * - * @param mapping The ActionMapping used to select this instance - * @param request The HTTP request we are processing - * @param response The HTTP response we are creating - * @param form Description of the Parameter - * @return Description of the Return Value - * @exception IOException if an input/output error occurs - * @exception ServletException if a servlet exception occurs - */ - @Override - public ActionForward executeSub( - ActionMapping mapping, - ActionForm form, - HttpServletRequest request, - HttpServletResponse response) - throws Exception { - ActionErrors errors = new ActionErrors(); - String forward = "readposts"; + /** + * Process the specified HTTP request, and create the corresponding HTTP response (or forward to + * another web component that will create it). Return an ActionForward instance describing where + * and how control should be forwarded, or null if the response has already been completed. + * + * @param mapping The ActionMapping used to select this instance + * @param request The HTTP request we are processing + * @param response The HTTP response we are creating + * @param form Description of the Parameter + * @return Description of the Return Value + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet exception occurs + */ + @Override + public ActionForward executeSub( + ActionMapping mapping, + ActionForm form, + HttpServletRequest request, + HttpServletResponse response) + throws Exception { + ActionErrors errors = new ActionErrors(); + String forward = "readposts"; - // Get request parameters - String reqCategory = cleanNull(request.getParameter("cat")); + // Get request parameters + String reqCategory = cleanNull(request.getParameter("cat")); - // Get instance of PersonalBlog Service - PersonalBlogService pblog = PersonalBlogService.getInstance(); + // Get instance of PersonalBlog Service + PersonalBlogService pblog = PersonalBlogService.getInstance(); - // Set Request Parameters - // Depending on the parameters, call the appropriate method - try { - if (!reqCategory.equals("")) { - request.setAttribute("posts", pblog.getPostsByCategory(reqCategory)); - } else { - request.setAttribute("posts", pblog.getPosts()); - } + // Set Request Parameters + // Depending on the parameters, call the appropriate method + try { + if (!reqCategory.equals("")) { + request.setAttribute("posts", pblog.getPostsByCategory(reqCategory)); + } else { + request.setAttribute("posts", pblog.getPosts()); + } - } catch (ServiceException e) { - ActionMessages messages = new ActionMessages(); - ActionMessage message = new ActionMessage("exception.postdoesnotexist"); - messages.add(ActionMessages.GLOBAL_MESSAGE, message); + } catch (ServiceException e) { + ActionMessages messages = new ActionMessages(); + ActionMessage message = new ActionMessage("exception.postdoesnotexist"); + messages.add(ActionMessages.GLOBAL_MESSAGE, message); - errors.add(messages); - e.printStackTrace(); - } + errors.add(messages); + e.printStackTrace(); + } - if (!errors.isEmpty()) { - saveErrors(request, errors); - } + if (!errors.isEmpty()) { + saveErrors(request, errors); + } - return (mapping.findForward(forward)); - } + return (mapping.findForward(forward)); + } - /** - * Validates userInput: verifies that it cannot be used for an attack. - * - *

          A string is valid if it contains only letters, digits, and whitespace. - * - * @param userInput user input to be validated - * @return the input if it is valid - * @throws IllegalArgumentException if userInput is not valid - */ - @Untainted String validate(String userInput) { - for (int i = 0; i < userInput.length(); ++i) { - char ch = userInput.charAt(i); - if (!Character.isLetter(ch) && !Character.isDigit(ch) && !Character.isWhitespace(ch)) - throw new IllegalArgumentException("Illegal user input"); + /** + * Validates userInput: verifies that it cannot be used for an attack. + * + *

          A string is valid if it contains only letters, digits, and whitespace. + * + * @param userInput user input to be validated + * @return the input if it is valid + * @throws IllegalArgumentException if userInput is not valid + */ + @Untainted String validate(String userInput) { + for (int i = 0; i < userInput.length(); ++i) { + char ch = userInput.charAt(i); + if (!Character.isLetter(ch) && !Character.isDigit(ch) && !Character.isWhitespace(ch)) + throw new IllegalArgumentException("Illegal user input"); + } + @SuppressWarnings("tainting") + @Untainted String result = userInput; + return result; } - @SuppressWarnings("tainting") - @Untainted String result = userInput; - return result; - } } /* To fix the bug, replace line 48 by: diff --git a/framework-test/build.gradle b/framework-test/build.gradle index 840b91759ac..888b92a2b46 100644 --- a/framework-test/build.gradle +++ b/framework-test/build.gradle @@ -1,22 +1,22 @@ import org.gradle.internal.jvm.Jvm sourceSets { - taglet - tagletJdk8 + taglet + tagletJdk8 } dependencies { - implementation "junit:junit:${versions.junit}" - implementation project(':javacutil') - implementation project(':checker-qual') + implementation "junit:junit:${versions.junit}" + implementation project(':javacutil') + implementation project(':checker-qual') - implementation "org.plumelib:plume-util:${versions.plumeUtil}" + implementation "org.plumelib:plume-util:${versions.plumeUtil}" - if (Jvm.current().toolsJar) { - tagletJdk8Implementation files(Jvm.current().toolsJar) - } + if (Jvm.current().toolsJar) { + tagletJdk8Implementation files(Jvm.current().toolsJar) + } - testImplementation project(':framework') + testImplementation project(':framework') } jar.archiveBaseName = 'framework-test' @@ -25,30 +25,30 @@ apply from: rootProject.file('gradle-mvn-push.gradle') /** Adds information to the publication for uploading to Maven repositories. */ final frameworkTest(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - publication.pom { - name = 'Checker Framework Testing Library' - description = 'framework-test contains utility classes for testing type-checkers\n' + - 'that are built on the Checker Framework.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + publication.from components.java + publication.pom { + name = 'Checker Framework Testing Library' + description = 'framework-test contains utility classes for testing type-checkers\n' + + 'that are built on the Checker Framework.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } + } } - } } publishing { - publications { - frameworkTest(MavenPublication) { - frameworkTest it + publications { + frameworkTest(MavenPublication) { + frameworkTest it + } } - } } signing { - sign publishing.publications.frameworkTest + sign publishing.publications.frameworkTest } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java index 943d39f2421..d40376aec3f 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/AinferGeneratePerDirectoryTest.java @@ -2,6 +2,7 @@ import java.io.File; import java.util.List; + import javax.annotation.processing.AbstractProcessor; /** @@ -12,29 +13,29 @@ * after those inferences are taken into account. */ public abstract class AinferGeneratePerDirectoryTest extends CheckerFrameworkWPIPerDirectoryTest { - /** - * Creates a new checker test. Use this constructor when creating a generation test. - * - *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected AinferGeneratePerDirectoryTest( - List testFiles, - Class checker, - String testDir, - String... checkerOptions) { - super(testFiles, checker, testDir, checkerOptions); - // Do not typecheck the file all-systems/java8/memberref/Purity.java: it contains - // an expected error that will be issued as a warning, instead (because of -Awarns) if - // the test is executed by this test runner. - // Since it is part of the all-systems tests, it cannot be changed (that would break other - // checkers). Instead, a copy of the file with the expected warning (rather than error) - // has been added to the ainfer non-annotated suite. - doNotTypecheck("all-systems/java8/memberref/Purity.java"); - } + /** + * Creates a new checker test. Use this constructor when creating a generation test. + * + *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected AinferGeneratePerDirectoryTest( + List testFiles, + Class checker, + String testDir, + String... checkerOptions) { + super(testFiles, checker, testDir, checkerOptions); + // Do not typecheck the file all-systems/java8/memberref/Purity.java: it contains + // an expected error that will be issued as a warning, instead (because of -Awarns) if + // the test is executed by this test runner. + // Since it is part of the all-systems tests, it cannot be changed (that would break other + // checkers). Instead, a copy of the file with the expected warning (rather than error) + // has been added to the ainfer non-annotated suite. + doNotTypecheck("all-systems/java8/memberref/Purity.java"); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java index da221d9fca7..458a8976fac 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/AinferValidatePerDirectoryTest.java @@ -1,5 +1,7 @@ package org.checkerframework.framework.test; +import org.checkerframework.common.value.qual.StringVal; + import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -8,8 +10,8 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; + import javax.annotation.processing.AbstractProcessor; -import org.checkerframework.common.value.qual.StringVal; /** * A specialized variant of {@link CheckerFrameworkPerDirectoryTest} for testing the Whole Program @@ -20,155 +22,159 @@ */ public class AinferValidatePerDirectoryTest extends CheckerFrameworkWPIPerDirectoryTest { - /** The class of the corresponding generation test. */ - private final Class generationTest; - - /** - * The short name of the checker, as used in the naming conventions for files, e.g. "index" for - * the Index Checker or "testchecker" for the AInferTestChecker. - */ - private final String CHECKER_SHORT_NAME; + /** The class of the corresponding generation test. */ + private final Class generationTest; - /** - * The number of letters in ".java", used when stripping off the extension from the name of a - * file. - */ - private static final int DOT_JAVA_LETTER_COUNT = ".java".length(); + /** + * The short name of the checker, as used in the naming conventions for files, e.g. "index" for + * the Index Checker or "testchecker" for the AInferTestChecker. + */ + private final String CHECKER_SHORT_NAME; - /** - * Creates a new checker test. Use this constructor when creating a validation test. - * - *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @param testDir the path to the directory of test inputs - * @param generationTest the class of the test that must run before this test, if this is the - * second of a pair of tests - * @param checkerOptions options to pass to the compiler when running tests - */ - protected AinferValidatePerDirectoryTest( - List testFiles, - Class checker, - String checkerShortName, - String testDir, - Class generationTest, - String... checkerOptions) { - super(testFiles, checker, testDir, checkerOptions); - this.generationTest = generationTest; - this.CHECKER_SHORT_NAME = checkerShortName; - } + /** + * The number of letters in ".java", used when stripping off the extension from the name of a + * file. + */ + private static final int DOT_JAVA_LETTER_COUNT = ".java".length(); - /** - * The directory where inference output files are found. - * - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @return the (relative) directory in which the inference output files can be found - */ - private static String getInferenceBaseDir(String checkerShortName) { - return "tests/ainfer-" + checkerShortName + "/inference-output/"; - } + /** + * Creates a new checker test. Use this constructor when creating a validation test. + * + *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @param testDir the path to the directory of test inputs + * @param generationTest the class of the test that must run before this test, if this is the + * second of a pair of tests + * @param checkerOptions options to pass to the compiler when running tests + */ + protected AinferValidatePerDirectoryTest( + List testFiles, + Class checker, + String checkerShortName, + String testDir, + Class generationTest, + String... checkerOptions) { + super(testFiles, checker, testDir, checkerOptions); + this.generationTest = generationTest; + this.CHECKER_SHORT_NAME = checkerShortName; + } - /** - * Computes the -Aajava argument that corresponds to the test files. This method is necessary - * because the framework issues a warning if a .ajava file with no corresponding source file is - * specified. - * - *

          Assumes that ajava files will be in the {@code #getInferenceBaseDir(String)} directory. - * - * @param sourceFiles the list of source files - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @return the appropriate -Aajava argument - */ - protected static String ajavaArgFromFiles(List sourceFiles, String checkerShortName) { - return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".ajava"); - } + /** + * The directory where inference output files are found. + * + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @return the (relative) directory in which the inference output files can be found + */ + private static String getInferenceBaseDir(String checkerShortName) { + return "tests/ainfer-" + checkerShortName + "/inference-output/"; + } - /** - * Computes the -Astubs argument that corresponds to the test files. This method is necessary - * because the framework issues a warning if a .astub file with no corresponding source file is - * specified. - * - *

          Assumes that astub files will be in the {@code #getInferenceBaseDir(String)} directory. - * - * @param sourceFiles the list of source files - * @param checkerShortName the short name of the checker, as used in the naming conventions for - * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker - * @return the appropriate -Astubs argument - */ - protected static String astubsArgFromFiles(List sourceFiles, String checkerShortName) { - return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".astub"); - } + /** + * Computes the -Aajava argument that corresponds to the test files. This method is necessary + * because the framework issues a warning if a .ajava file with no corresponding source file is + * specified. + * + *

          Assumes that ajava files will be in the {@code #getInferenceBaseDir(String)} directory. + * + * @param sourceFiles the list of source files + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @return the appropriate -Aajava argument + */ + protected static String ajavaArgFromFiles(List sourceFiles, String checkerShortName) { + return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".ajava"); + } - /** - * Computes the list of ajava or stub files that correspond to the test files. This method is - * necessary because the framework issues a warning if a .ajava file or a stub file with no - * corresponding source file is specified. - * - *

          Assumes that ajava/astub files will be in the {@code #getInferenceBaseDir(String)} - * directory. - * - * @param sourceFiles the list of source files - * @param inferenceBaseDir the base directory in which inference output is placed - * @param extension the extension to use: either .astub or .ajava - * @return the appropriate {@code -Aajava} or {@code -Astubs} argument - */ - private static String annotationArgFromFiles( - List sourceFiles, - String inferenceBaseDir, - @StringVal({".astub", ".ajava"}) String extension) { - String checkerArg = extension.equals(".astub") ? "-Astubs=" : "-Aajava="; - return checkerArg - + sourceFiles.stream() - .map(f -> annotationFilenameFromSourceFile(f, inferenceBaseDir, extension)) - .filter(s -> !s.isEmpty()) - .collect(Collectors.joining(":")); - } + /** + * Computes the -Astubs argument that corresponds to the test files. This method is necessary + * because the framework issues a warning if a .astub file with no corresponding source file is + * specified. + * + *

          Assumes that astub files will be in the {@code #getInferenceBaseDir(String)} directory. + * + * @param sourceFiles the list of source files + * @param checkerShortName the short name of the checker, as used in the naming conventions for + * files, e.g. "index" for the Index Checker or "testchecker" for the AInferTestChecker + * @return the appropriate -Astubs argument + */ + protected static String astubsArgFromFiles(List sourceFiles, String checkerShortName) { + return annotationArgFromFiles(sourceFiles, getInferenceBaseDir(checkerShortName), ".astub"); + } - /** - * Generates the correct argument to the {@code -Aajava} or {@code -Astubs} CF option - * corresponding to the source file {@code sourceFile} and the WPI output type. - * - * @param sourceFile a java source file - * @param inferenceBaseDir the base directory in which inference output is placed - * @param extension the extension to use: either .astub or .ajava - * @return the ajava argument for the corresponding ajava files, if there are any - */ - private static String annotationFilenameFromSourceFile( - File sourceFile, String inferenceBaseDir, @StringVal({".astub", ".ajava"}) String extension) { - String fileBaseName = - sourceFile.getName().substring(0, sourceFile.getName().length() - DOT_JAVA_LETTER_COUNT); - StringBuilder sb = new StringBuilder(); - // Find all the annotation files associated with this class name. This approach is necessary - // because (1) some tests are in packages, which will be included in the annotation file - // names, and (2) separate astub files are generated for inner classes. - try (DirectoryStream dirStream = - Files.newDirectoryStream( - Paths.get(inferenceBaseDir), "*" + fileBaseName + "{-,$}*" + extension)) { - dirStream.forEach(f -> sb.append(f).append(":")); - } catch (IOException ignored) { - // Ignore errors. + /** + * Computes the list of ajava or stub files that correspond to the test files. This method is + * necessary because the framework issues a warning if a .ajava file or a stub file with no + * corresponding source file is specified. + * + *

          Assumes that ajava/astub files will be in the {@code #getInferenceBaseDir(String)} + * directory. + * + * @param sourceFiles the list of source files + * @param inferenceBaseDir the base directory in which inference output is placed + * @param extension the extension to use: either .astub or .ajava + * @return the appropriate {@code -Aajava} or {@code -Astubs} argument + */ + private static String annotationArgFromFiles( + List sourceFiles, + String inferenceBaseDir, + @StringVal({".astub", ".ajava"}) String extension) { + String checkerArg = extension.equals(".astub") ? "-Astubs=" : "-Aajava="; + return checkerArg + + sourceFiles.stream() + .map(f -> annotationFilenameFromSourceFile(f, inferenceBaseDir, extension)) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining(":")); } - // remove the last ":" - if (sb.length() > 0) { - sb.deleteCharAt(sb.length() - 1); + + /** + * Generates the correct argument to the {@code -Aajava} or {@code -Astubs} CF option + * corresponding to the source file {@code sourceFile} and the WPI output type. + * + * @param sourceFile a java source file + * @param inferenceBaseDir the base directory in which inference output is placed + * @param extension the extension to use: either .astub or .ajava + * @return the ajava argument for the corresponding ajava files, if there are any + */ + private static String annotationFilenameFromSourceFile( + File sourceFile, + String inferenceBaseDir, + @StringVal({".astub", ".ajava"}) String extension) { + String fileBaseName = + sourceFile + .getName() + .substring(0, sourceFile.getName().length() - DOT_JAVA_LETTER_COUNT); + StringBuilder sb = new StringBuilder(); + // Find all the annotation files associated with this class name. This approach is necessary + // because (1) some tests are in packages, which will be included in the annotation file + // names, and (2) separate astub files are generated for inner classes. + try (DirectoryStream dirStream = + Files.newDirectoryStream( + Paths.get(inferenceBaseDir), "*" + fileBaseName + "{-,$}*" + extension)) { + dirStream.forEach(f -> sb.append(f).append(":")); + } catch (IOException ignored) { + // Ignore errors. + } + // remove the last ":" + if (sb.length() > 0) { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); } - return sb.toString(); - } - @Override - public void run() { - // Only run if annotated files have been created. - // See ainferTest task. - if (generationTest != null - && !new File("tests/ainfer-" + CHECKER_SHORT_NAME + "/annotated/").exists()) { - throw new RuntimeException(generationTest + " must be run before this test."); + @Override + public void run() { + // Only run if annotated files have been created. + // See ainferTest task. + if (generationTest != null + && !new File("tests/ainfer-" + CHECKER_SHORT_NAME + "/annotated/").exists()) { + throw new RuntimeException(generationTest + " must be run before this test."); + } + super.run(); } - super.run(); - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java index c7726b42978..338347700c3 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java @@ -1,14 +1,16 @@ package org.checkerframework.framework.test; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; + import javax.annotation.processing.AbstractProcessor; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.junit.Test; -import org.junit.runner.RunWith; /** * Compiles all test files in a test directory together. Use {@link CheckerFrameworkPerFileTest} to @@ -46,146 +48,147 @@ @RunWith(PerDirectorySuite.class) public abstract class CheckerFrameworkPerDirectoryTest extends CheckerFrameworkRootedTest { - /** The files containing test code, which will be type-checked. */ - protected final List testFiles; + /** The files containing test code, which will be type-checked. */ + protected final List testFiles; - /** The binary names of the checkers to run. */ - protected final List<@BinaryName String> checkerNames; + /** The binary names of the checkers to run. */ + protected final List<@BinaryName String> checkerNames; - /** - * The path, relative to the test root directory (see {@link - * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. - */ - protected final String testDir; + /** + * The path, relative to the test root directory (see {@link + * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. + */ + protected final String testDir; - /** Extra options to pass to javac when running the checker. */ - protected final List checkerOptions; + /** Extra options to pass to javac when running the checker. */ + protected final List checkerOptions; - /** Extra entries for the classpath. */ - protected final List classpathExtra; + /** Extra entries for the classpath. */ + protected final List classpathExtra; - /** - * Creates a new checker test. - * - *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkPerDirectoryTest( - List testFiles, - Class checker, - String testDir, - String... checkerOptions) { - this(testFiles, checker, testDir, Collections.emptyList(), checkerOptions); - } + /** + * Creates a new checker test. + * + *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkPerDirectoryTest( + List testFiles, + Class checker, + String testDir, + String... checkerOptions) { + this(testFiles, checker, testDir, Collections.emptyList(), checkerOptions); + } - /** - * Creates a new checker test. - * - *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param classpathExtra extra entries for the classpath, relative to a directory such as - * checker-framework/checker - * @param checkerOptions options to pass to the compiler when running tests - */ - @SuppressWarnings( - "signature:cast.unsafe" // for non-array non-primitive class, getName(): @BinaryName - ) - protected CheckerFrameworkPerDirectoryTest( - List testFiles, - Class checker, - String testDir, - List classpathExtra, - String... checkerOptions) { - this( - testFiles, - Collections.singletonList((@BinaryName String) checker.getName()), - testDir, - classpathExtra, - checkerOptions); - } + /** + * Creates a new checker test. + * + *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param classpathExtra extra entries for the classpath, relative to a directory such as + * checker-framework/checker + * @param checkerOptions options to pass to the compiler when running tests + */ + @SuppressWarnings( + "signature:cast.unsafe" // for non-array non-primitive class, getName(): @BinaryName + ) + protected CheckerFrameworkPerDirectoryTest( + List testFiles, + Class checker, + String testDir, + List classpathExtra, + String... checkerOptions) { + this( + testFiles, + Collections.singletonList((@BinaryName String) checker.getName()), + testDir, + classpathExtra, + checkerOptions); + } - /** - * Creates a new checker test. - * - *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checkerNames the binary names of the checkers to run - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param classpathExtra extra entries for the classpath, relative to a directory such as - * checker-framework/checker - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkPerDirectoryTest( - List testFiles, - List<@BinaryName String> checkerNames, - String testDir, - List classpathExtra, - String... checkerOptions) { - super(); - this.testFiles = testFiles; - this.checkerNames = checkerNames; - this.testDir = testDir; - this.classpathExtra = classpathExtra; - this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions)); - this.checkerOptions.add("-AajavaChecks"); - this.checkerOptions.add("-AconvertTypeArgInferenceCrashToWarning=false"); - } + /** + * Creates a new checker test. + * + *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checkerNames the binary names of the checkers to run + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param classpathExtra extra entries for the classpath, relative to a directory such as + * checker-framework/checker + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkPerDirectoryTest( + List testFiles, + List<@BinaryName String> checkerNames, + String testDir, + List classpathExtra, + String... checkerOptions) { + super(); + this.testFiles = testFiles; + this.checkerNames = checkerNames; + this.testDir = testDir; + this.classpathExtra = classpathExtra; + this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions)); + this.checkerOptions.add("-AajavaChecks"); + this.checkerOptions.add("-AconvertTypeArgInferenceCrashToWarning=false"); + } - /** Run the tests. */ - @Test - public void run() { - if (testFiles.isEmpty()) { - return; + /** Run the tests. */ + @Test + public void run() { + if (testFiles.isEmpty()) { + return; + } + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + new File(resolveTestDirectory(), testDir).getPath(), + testFiles, + classpathExtra, + checkerNames, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); + checkResult(adjustedTestResult); } - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - new File(resolveTestDirectory(), testDir).getPath(), - testFiles, - classpathExtra, - checkerNames, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - TypecheckResult adjustedTestResult = adjustTypecheckResult(testResult); - checkResult(adjustedTestResult); - } - /** - * This method is called before issuing assertions about a TypecheckResult. Subclasses can - * override it to customize behavior. - * - * @param testResult a test result to possibly change - * @return a TypecheckResult to use instead, which may be the unmodified argument - */ - public TypecheckResult adjustTypecheckResult(TypecheckResult testResult) { - return testResult; - } + /** + * This method is called before issuing assertions about a TypecheckResult. Subclasses can + * override it to customize behavior. + * + * @param testResult a test result to possibly change + * @return a TypecheckResult to use instead, which may be the unmodified argument + */ + public TypecheckResult adjustTypecheckResult(TypecheckResult testResult) { + return testResult; + } - /** - * Override this method if you would like to supply a checker command-line option that depends on - * the Java files passed to the test. Those files are available in field {@link #testFiles}. - * - *

          If you want to specify the same command-line option for all tests of a particular checker, - * then pass it to the {@link #CheckerFrameworkPerDirectoryTest} constructor. - * - * @param previousOptions the options specified in the constructor of the test previousOptions is - * unmodifiable - * @return a new list of options or the original passed through - */ - public List customizeOptions(List previousOptions) { - return previousOptions; - } + /** + * Override this method if you would like to supply a checker command-line option that depends + * on the Java files passed to the test. Those files are available in field {@link #testFiles}. + * + *

          If you want to specify the same command-line option for all tests of a particular checker, + * then pass it to the {@link #CheckerFrameworkPerDirectoryTest} constructor. + * + * @param previousOptions the options specified in the constructor of the test previousOptions + * is unmodifiable + * @return a new list of options or the original passed through + */ + public List customizeOptions(List previousOptions) { + return previousOptions; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java index 101ca081a55..de1f9c7fb10 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerFileTest.java @@ -1,13 +1,15 @@ package org.checkerframework.framework.test; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; + import javax.annotation.processing.AbstractProcessor; -import org.junit.Test; -import org.junit.runner.RunWith; /** * Compiles all test files individually. Use {@link CheckerFrameworkPerDirectoryTest} to compile all @@ -47,71 +49,72 @@ @RunWith(PerFileSuite.class) public abstract class CheckerFrameworkPerFileTest extends CheckerFrameworkRootedTest { - /** The file containing test code, which will be type-checked. */ - protected final File testFile; + /** The file containing test code, which will be type-checked. */ + protected final File testFile; - /** The checker to use for tests. */ - protected final Class checker; + /** The checker to use for tests. */ + protected final Class checker; - /** - * The path, relative to the test root directory (see {@link - * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. - */ - protected final String testDir; + /** + * The path, relative to the test root directory (see {@link + * CheckerFrameworkRootedTest#resolveTestDirectory()}), to the directory containing test inputs. + */ + protected final String testDir; - /** Extra options to pass to javac when running the checker. */ - protected final List checkerOptions; + /** Extra options to pass to javac when running the checker. */ + protected final List checkerOptions; - /** - * Creates a new checker test. - * - *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFile the file containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path, relative to currentDir/tests, to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkPerFileTest( - File testFile, - Class checker, - String testDir, - String... checkerOptions) { - super(); - this.testFile = testFile; - this.checker = checker; - this.testDir = testDir; - this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions)); - } + /** + * Creates a new checker test. + * + *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFile the file containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path, relative to currentDir/tests, to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkPerFileTest( + File testFile, + Class checker, + String testDir, + String... checkerOptions) { + super(); + this.testFile = testFile; + this.checker = checker; + this.testDir = testDir; + this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions)); + } - @Test - public void run() { - boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); - List customizedOptions = customizeOptions(Collections.unmodifiableList(checkerOptions)); - TestConfiguration config = - TestConfigurationBuilder.buildDefaultConfiguration( - new File(resolveTestDirectory(), testDir).getPath(), - testFile, - checker, - customizedOptions, - shouldEmitDebugInfo); - TypecheckResult testResult = new TypecheckExecutor().runTest(config); - checkResult(testResult); - } + @Test + public void run() { + boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo(); + List customizedOptions = + customizeOptions(Collections.unmodifiableList(checkerOptions)); + TestConfiguration config = + TestConfigurationBuilder.buildDefaultConfiguration( + new File(resolveTestDirectory(), testDir).getPath(), + testFile, + checker, + customizedOptions, + shouldEmitDebugInfo); + TypecheckResult testResult = new TypecheckExecutor().runTest(config); + checkResult(testResult); + } - /** - * Override this method if you would like to supply a checker command-line option that depends on - * the Java file passed to the test. That file name is available in field {@link #testFile}. - * - *

          If you want to specify the same command-line option for all tests of a particular checker, - * then pass it to the {@link CheckerFrameworkPerFileTest} constructor. - * - * @param previousOptions the options specified in the constructor of the test previousOptions is - * unmodifiable - * @return a new list of options or the original passed through - */ - public List customizeOptions(List previousOptions) { - return previousOptions; - } + /** + * Override this method if you would like to supply a checker command-line option that depends + * on the Java file passed to the test. That file name is available in field {@link #testFile}. + * + *

          If you want to specify the same command-line option for all tests of a particular checker, + * then pass it to the {@link CheckerFrameworkPerFileTest} constructor. + * + * @param previousOptions the options specified in the constructor of the test previousOptions + * is unmodifiable + * @return a new list of options or the original passed through + */ + public List customizeOptions(List previousOptions) { + return previousOptions; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java index ce02cbed2cb..8d2f358a21f 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkRootedTest.java @@ -5,29 +5,29 @@ /** Encapsulates the directory root to search within for test files to compile. */ abstract class CheckerFrameworkRootedTest { - /** Constructs a test that will assert that it can resolve its test root directory. */ - public CheckerFrameworkRootedTest() {} + /** Constructs a test that will assert that it can resolve its test root directory. */ + public CheckerFrameworkRootedTest() {} - /** - * Resolves the test root directory from the optional {@link TestRootDirectory} annotation or - * falls back to the default of {@code currentDir/tests}. - * - * @return the resolved directory - */ - protected File resolveTestDirectory() { - TestRootDirectory annotation = getClass().getAnnotation(TestRootDirectory.class); - if (annotation != null) { - return new File(annotation.value()); + /** + * Resolves the test root directory from the optional {@link TestRootDirectory} annotation or + * falls back to the default of {@code currentDir/tests}. + * + * @return the resolved directory + */ + protected File resolveTestDirectory() { + TestRootDirectory annotation = getClass().getAnnotation(TestRootDirectory.class); + if (annotation != null) { + return new File(annotation.value()); + } + return new File("tests"); } - return new File("tests"); - } - /** - * Check that the {@link TypecheckResult} did not fail. - * - * @param typecheckResult result to check - */ - public void checkResult(TypecheckResult typecheckResult) { - TestUtilities.assertTestDidNotFail(typecheckResult); - } + /** + * Check that the {@link TypecheckResult} did not fail. + * + * @param typecheckResult result to check + */ + public void checkResult(TypecheckResult typecheckResult) { + TestUtilities.assertTestDidNotFail(typecheckResult); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java index 4f6dedbc2bd..ee115fe991d 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java @@ -1,14 +1,16 @@ package org.checkerframework.framework.test; +import org.checkerframework.checker.initialization.qual.UnderInitialization; +import org.junit.Assert; + import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Scanner; + import javax.annotation.processing.AbstractProcessor; -import org.checkerframework.checker.initialization.qual.UnderInitialization; -import org.junit.Assert; /** * A specialized variant of {@link CheckerFrameworkPerDirectoryTest} for testing the Whole Program @@ -20,100 +22,100 @@ */ public abstract class CheckerFrameworkWPIPerDirectoryTest extends CheckerFrameworkPerDirectoryTest { - /** - * Creates a new checker test. Use this constructor when creating a generation test. - * - *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected CheckerFrameworkWPIPerDirectoryTest( - List testFiles, - Class checker, - String testDir, - String... checkerOptions) { - super(testFiles, checker, testDir, checkerOptions); + /** + * Creates a new checker test. Use this constructor when creating a generation test. + * + *

          {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, + * Iterable, Iterable, List, boolean)} adds additional checker options. + * + * @param testFiles the files containing test code, which will be type-checked + * @param checker the class for the checker to use + * @param testDir the path to the directory of test inputs + * @param checkerOptions options to pass to the compiler when running tests + */ + protected CheckerFrameworkWPIPerDirectoryTest( + List testFiles, + Class checker, + String testDir, + String... checkerOptions) { + super(testFiles, checker, testDir, checkerOptions); - String skipComment; - if (this.checkerOptions.contains("-Ainfer=ajava")) { - skipComment = "@infer-ajava-skip-test"; - } else if (this.checkerOptions.contains("-Ainfer=jaifs")) { - skipComment = "@infer-jaifs-skip-test"; - } else if (this.checkerOptions.contains("-Ainfer=stubs")) { - skipComment = "@infer-stubs-skip-test"; - } else { - skipComment = null; - } - if (skipComment != null) { - List removeFiles = new ArrayList<>(); - for (File testFile : testFiles) { - if (hasSkipComment(testFile, skipComment)) { - removeFiles.add(testFile); + String skipComment; + if (this.checkerOptions.contains("-Ainfer=ajava")) { + skipComment = "@infer-ajava-skip-test"; + } else if (this.checkerOptions.contains("-Ainfer=jaifs")) { + skipComment = "@infer-jaifs-skip-test"; + } else if (this.checkerOptions.contains("-Ainfer=stubs")) { + skipComment = "@infer-stubs-skip-test"; + } else { + skipComment = null; + } + if (skipComment != null) { + List removeFiles = new ArrayList<>(); + for (File testFile : testFiles) { + if (hasSkipComment(testFile, skipComment)) { + removeFiles.add(testFile); + } + } + this.testFiles.removeAll(removeFiles); } - } - this.testFiles.removeAll(removeFiles); } - } - /** - * Do not typecheck any file ending with the given String. A subclass of - * CheckerFrameworkWPIPerDirectoryTest uses this routine to avoid typechecking files in the - * all-systems test suite that are problematic for one typechecker. For example, this routine is - * useful when running the all-systems tests using WPI, because some all-systems tests have - * expected errors that become warnings during a WPI run (because of {@code -Awarns}) and so must - * be excluded. - * - *

          This code takes advantage of the mutability of the {@link #testFiles} field. - * - * @param endswith a string that the absolute path of the target file that should not be - * typechecked ends with. Usually, this takes the form "all-systems/ProblematicFile.java". - */ - protected void doNotTypecheck( - @UnderInitialization(CheckerFrameworkPerDirectoryTest.class) CheckerFrameworkWPIPerDirectoryTest this, - String endswith) { - int removeIndex = -1; - for (int i = 0; i < testFiles.size(); i++) { - File f = testFiles.get(i); - if (f.getAbsolutePath().endsWith(endswith)) { + /** + * Do not typecheck any file ending with the given String. A subclass of + * CheckerFrameworkWPIPerDirectoryTest uses this routine to avoid typechecking files in the + * all-systems test suite that are problematic for one typechecker. For example, this routine is + * useful when running the all-systems tests using WPI, because some all-systems tests have + * expected errors that become warnings during a WPI run (because of {@code -Awarns}) and so + * must be excluded. + * + *

          This code takes advantage of the mutability of the {@link #testFiles} field. + * + * @param endswith a string that the absolute path of the target file that should not be + * typechecked ends with. Usually, this takes the form "all-systems/ProblematicFile.java". + */ + protected void doNotTypecheck( + @UnderInitialization(CheckerFrameworkPerDirectoryTest.class) CheckerFrameworkWPIPerDirectoryTest this, + String endswith) { + int removeIndex = -1; + for (int i = 0; i < testFiles.size(); i++) { + File f = testFiles.get(i); + if (f.getAbsolutePath().endsWith(endswith)) { + if (removeIndex != -1) { + Assert.fail( + "When attempting to exclude a file, found more than one match in the" + + " test suite. Check the test code and use a more-specific removal" + + " key. Attempting to exclude: " + + endswith); + } + removeIndex = i; + } + } + // This test code can run for every subdirectory of the all-systems tests, so there is no + // guarantee that the file to be excluded will be found. if (removeIndex != -1) { - Assert.fail( - "When attempting to exclude a file, found more than one match in the" - + " test suite. Check the test code and use a more-specific removal" - + " key. Attempting to exclude: " - + endswith); + testFiles.remove(removeIndex); } - removeIndex = i; - } - } - // This test code can run for every subdirectory of the all-systems tests, so there is no - // guarantee that the file to be excluded will be found. - if (removeIndex != -1) { - testFiles.remove(removeIndex); } - } - /** - * Whether {@code file} contains {@code skipComment}. - * - * @param file a java test file - * @param skipComment a comment that indicates that a test should be skipped - * @return whether {@code file} contains {@code skipComment} - */ - public static boolean hasSkipComment(File file, String skipComment) { - try (Scanner in = new Scanner(file, StandardCharsets.UTF_8)) { - while (in.hasNext()) { - String nextLine = in.nextLine(); - if (nextLine.contains(skipComment)) { - return true; + /** + * Whether {@code file} contains {@code skipComment}. + * + * @param file a java test file + * @param skipComment a comment that indicates that a test should be skipped + * @return whether {@code file} contains {@code skipComment} + */ + public static boolean hasSkipComment(File file, String skipComment) { + try (Scanner in = new Scanner(file, StandardCharsets.UTF_8)) { + while (in.hasNext()) { + String nextLine = in.nextLine(); + if (nextLine.contains(skipComment)) { + return true; + } + } + } catch (IOException e) { + throw new RuntimeException(e); } - } - } catch (IOException e) { - throw new RuntimeException(e); + return false; } - return false; - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java index dd01819da95..b6048cecf54 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CompilationResult.java @@ -2,60 +2,61 @@ import java.util.Collections; import java.util.List; + import javax.tools.Diagnostic; import javax.tools.JavaFileObject; /** CompilationResult represents the output of the compiler after it is run. */ public class CompilationResult { - private final boolean compiledWithoutError; - private final String javacOutput; - private final Iterable javaFileObjects; - private final List> diagnostics; - - CompilationResult( - boolean compiledWithoutError, - String javacOutput, - Iterable javaFileObjects, - List> diagnostics) { - this.compiledWithoutError = compiledWithoutError; - this.javacOutput = javacOutput; - this.javaFileObjects = javaFileObjects; - this.diagnostics = Collections.unmodifiableList(diagnostics); - } - - /** - * Returns whether or not compilation succeeded without errors or exceptions. - * - * @return whether or not compilation succeeded without errors or exceptions - */ - public boolean compiledWithoutError() { - return compiledWithoutError; - } - - /** - * Returns all of the output from the compiler. - * - * @return all of the output from the compiler - */ - public String getJavacOutput() { - return javacOutput; - } - - /** - * Returns the list of Java files passed to the compiler. - * - * @return the list of Java files passed to the compiler - */ - public Iterable getJavaFileObjects() { - return javaFileObjects; - } - - /** - * Returns the diagnostics reported by the compiler. - * - * @return the diagnostics reported by the compiler - */ - public List> getDiagnostics() { - return diagnostics; - } + private final boolean compiledWithoutError; + private final String javacOutput; + private final Iterable javaFileObjects; + private final List> diagnostics; + + CompilationResult( + boolean compiledWithoutError, + String javacOutput, + Iterable javaFileObjects, + List> diagnostics) { + this.compiledWithoutError = compiledWithoutError; + this.javacOutput = javacOutput; + this.javaFileObjects = javaFileObjects; + this.diagnostics = Collections.unmodifiableList(diagnostics); + } + + /** + * Returns whether or not compilation succeeded without errors or exceptions. + * + * @return whether or not compilation succeeded without errors or exceptions + */ + public boolean compiledWithoutError() { + return compiledWithoutError; + } + + /** + * Returns all of the output from the compiler. + * + * @return all of the output from the compiler + */ + public String getJavacOutput() { + return javacOutput; + } + + /** + * Returns the list of Java files passed to the compiler. + * + * @return the list of Java files passed to the compiler + */ + public Iterable getJavaFileObjects() { + return javaFileObjects; + } + + /** + * Returns the diagnostics reported by the compiler. + * + * @return the diagnostics reported by the compiler + */ + public List> getDiagnostics() { + return diagnostics; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java index 6df18767861..3105b42e217 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java @@ -1,14 +1,15 @@ package org.checkerframework.framework.test; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.plumelib.util.StringsPlume; + import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.plumelib.util.StringsPlume; /** * Represents all of the information needed to execute the Javac compiler for a given set of test @@ -16,98 +17,98 @@ */ public class ImmutableTestConfiguration implements TestConfiguration { - /** - * Options that should be passed to the compiler. This a {@code Map(optionName => - * optionArgumentIfAny)}. E.g., - * - *

          {@code
          -   * Map(
          -   *     "-AprintAllQualifiers" => null
          -   *     "-classpath" => "myDir1:myDir2"
          -   * )
          -   * }
          - */ - private final Map options; - - /** - * These files contain diagnostics that should be returned by Javac. If this list is empty, the - * diagnostics are instead read from comments in the Java file itself - */ - private final List diagnosticFiles; - - /** - * The source files to compile. If the file is expected to emit errors on compilation, the file - * should contain expected error diagnostics OR should have a companion file with the same - * path/name but with the extension .out instead of .java if they - */ - private final List testSourceFiles; - - /** A list of AnnotationProcessors (usually checkers) to pass to the compiler for this test. */ - private final List<@BinaryName String> processors; - - /** The value of system property "emit.test.debug". */ - private final boolean shouldEmitDebugInfo; - - /** - * Create a new ImmutableTestConfiguration. - * - * @param diagnosticFiles files containing diagnostics that should be returned by javac - * @param testSourceFiles the source files to compile - * @param processors the annotation processors (usually checkers) to run - * @param options options that should be passed to the compiler - * @param shouldEmitDebugInfo the value of system property "emit.test.debug" - */ - public ImmutableTestConfiguration( - List diagnosticFiles, - List testSourceFiles, - List<@BinaryName String> processors, - Map options, - boolean shouldEmitDebugInfo) { - this.diagnosticFiles = Collections.unmodifiableList(diagnosticFiles); - this.testSourceFiles = Collections.unmodifiableList(new ArrayList<>(testSourceFiles)); - this.processors = new ArrayList<>(processors); - this.options = - Collections.unmodifiableMap(new LinkedHashMap(options)); - this.shouldEmitDebugInfo = shouldEmitDebugInfo; - } - - @Override - public List getTestSourceFiles() { - return testSourceFiles; - } - - @Override - public List getDiagnosticFiles() { - return diagnosticFiles; - } - - @Override - public List<@BinaryName String> getProcessors() { - return processors; - } - - @Override - public Map getOptions() { - return options; - } - - @Override - public List getFlatOptions() { - return TestUtilities.optionMapToList(options); - } - - @Override - public boolean shouldEmitDebugInfo() { - return shouldEmitDebugInfo; - } - - @Override - public String toString() { - return StringsPlume.joinLines( - "TestConfigurationBuilder:", - "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), - "processors=" + String.join(", ", processors), - "options=" + String.join(", ", getFlatOptions()), - "shouldEmitDebugInfo=" + shouldEmitDebugInfo); - } + /** + * Options that should be passed to the compiler. This a {@code Map(optionName => + * optionArgumentIfAny)}. E.g., + * + *
          {@code
          +     * Map(
          +     *     "-AprintAllQualifiers" => null
          +     *     "-classpath" => "myDir1:myDir2"
          +     * )
          +     * }
          + */ + private final Map options; + + /** + * These files contain diagnostics that should be returned by Javac. If this list is empty, the + * diagnostics are instead read from comments in the Java file itself + */ + private final List diagnosticFiles; + + /** + * The source files to compile. If the file is expected to emit errors on compilation, the file + * should contain expected error diagnostics OR should have a companion file with the same + * path/name but with the extension .out instead of .java if they + */ + private final List testSourceFiles; + + /** A list of AnnotationProcessors (usually checkers) to pass to the compiler for this test. */ + private final List<@BinaryName String> processors; + + /** The value of system property "emit.test.debug". */ + private final boolean shouldEmitDebugInfo; + + /** + * Create a new ImmutableTestConfiguration. + * + * @param diagnosticFiles files containing diagnostics that should be returned by javac + * @param testSourceFiles the source files to compile + * @param processors the annotation processors (usually checkers) to run + * @param options options that should be passed to the compiler + * @param shouldEmitDebugInfo the value of system property "emit.test.debug" + */ + public ImmutableTestConfiguration( + List diagnosticFiles, + List testSourceFiles, + List<@BinaryName String> processors, + Map options, + boolean shouldEmitDebugInfo) { + this.diagnosticFiles = Collections.unmodifiableList(diagnosticFiles); + this.testSourceFiles = Collections.unmodifiableList(new ArrayList<>(testSourceFiles)); + this.processors = new ArrayList<>(processors); + this.options = + Collections.unmodifiableMap(new LinkedHashMap(options)); + this.shouldEmitDebugInfo = shouldEmitDebugInfo; + } + + @Override + public List getTestSourceFiles() { + return testSourceFiles; + } + + @Override + public List getDiagnosticFiles() { + return diagnosticFiles; + } + + @Override + public List<@BinaryName String> getProcessors() { + return processors; + } + + @Override + public Map getOptions() { + return options; + } + + @Override + public List getFlatOptions() { + return TestUtilities.optionMapToList(options); + } + + @Override + public boolean shouldEmitDebugInfo() { + return shouldEmitDebugInfo; + } + + @Override + public String toString() { + return StringsPlume.joinLines( + "TestConfigurationBuilder:", + "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), + "processors=" + String.join(", ", processors), + "options=" + String.join(", ", getFlatOptions()), + "shouldEmitDebugInfo=" + shouldEmitDebugInfo); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java index 753d06c338a..25c518e28e9 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java @@ -1,5 +1,15 @@ package org.checkerframework.framework.test; +import org.checkerframework.javacutil.BugInCF; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + import java.io.File; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -10,15 +20,6 @@ import java.util.Collections; import java.util.List; import java.util.StringJoiner; -import org.checkerframework.javacutil.BugInCF; -import org.junit.runner.Runner; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.Parameterized.Parameters; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; // TODO: large parts of this file are the same as PerFileSuite.java. // Reduce duplication by moving common parts to an abstract class. @@ -35,155 +36,156 @@ */ public class PerDirectorySuite extends RootedSuite { - /** Name */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public @interface Name {} - - private final ArrayList runners = new ArrayList<>(); - - @Override - protected List getChildren() { - return runners; - } - - /** - * Only called reflectively. Do not use programmatically. - * - * @param klass the class whose tests to run - */ - @SuppressWarnings("nullness") // JUnit needs to be annotated - public PerDirectorySuite(Class klass) throws Throwable { - super(klass, Collections.emptyList()); - TestClass testClass = getTestClass(); - Class javaTestClass = testClass.getJavaClass(); - List> parametersList = getParametersList(testClass); - - for (List parameters : parametersList) { - runners.add(new PerParameterSetTestRunner(javaTestClass, parameters)); - } - } + /** Name */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Name {} - /** Returns a list of one-element arrays, each containing a Java File. */ - @SuppressWarnings("nullness") // JUnit needs to be annotated - private List> getParametersList(TestClass klass) throws Throwable { - FrameworkMethod method = getParametersMethod(klass); + private final ArrayList runners = new ArrayList<>(); - // We must have a method getTestDirs which returns String[], - // or getParametersMethod would fail. - if (method == null) { - throw new BugInCF("no method annotated with @Parameters"); - } - if (!method.getReturnType().isArray()) { - throw new BugInCF( - "@Parameters annotation on method that does not return an array: " + method); + @Override + protected List getChildren() { + return runners; } - String[] dirs = (String[]) method.invokeExplosively(null); - return TestUtilities.findJavaFilesPerDirectory(resolveTestDirectory(), dirs); - } - - /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ - private FrameworkMethod getParametersMethod(TestClass testClass) { - List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); - if (parameterMethods.size() != 1) { - // Construct error message - - String methods; - if (parameterMethods.isEmpty()) { - methods = "[No methods specified]"; - } else { - StringJoiner sj = new StringJoiner(", "); - for (FrameworkMethod method : parameterMethods) { - sj.add(method.getName()); + + /** + * Only called reflectively. Do not use programmatically. + * + * @param klass the class whose tests to run + */ + @SuppressWarnings("nullness") // JUnit needs to be annotated + public PerDirectorySuite(Class klass) throws Throwable { + super(klass, Collections.emptyList()); + TestClass testClass = getTestClass(); + Class javaTestClass = testClass.getJavaClass(); + List> parametersList = getParametersList(testClass); + + for (List parameters : parametersList) { + runners.add(new PerParameterSetTestRunner(javaTestClass, parameters)); } - methods = sj.toString(); - } - - throw new BugInCF( - "Exactly one of the following methods should be declared:%n%s%n" - + "testClass=%s%n" - + "parameterMethods=%s", - requiredFormsMessage, testClass.getName(), methods); } - FrameworkMethod method = parameterMethods.get(0); + /** Returns a list of one-element arrays, each containing a Java File. */ + @SuppressWarnings("nullness") // JUnit needs to be annotated + private List> getParametersList(TestClass klass) throws Throwable { + FrameworkMethod method = getParametersMethod(klass); - Class returnType = method.getReturnType(); - String methodName = method.getName(); - switch (methodName) { - case "getTestDirs": - if (!(returnType.isArray() && returnType.getComponentType() == String.class)) { - throw new RuntimeException("getTestDirs should return String[], found " + returnType); + // We must have a method getTestDirs which returns String[], + // or getParametersMethod would fail. + if (method == null) { + throw new BugInCF("no method annotated with @Parameters"); } - break; - - default: - throw new RuntimeException( - requiredFormsMessage - + "%n" - + "testClass=" - + testClass.getName() - + "%n" - + "parameterMethods=" - + method); - } - - int modifiers = method.getMethod().getModifiers(); - if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { - throw new RuntimeException( - "Parameter method (" + method.getName() + ") must be public and static"); + if (!method.getReturnType().isArray()) { + throw new BugInCF( + "@Parameters annotation on method that does not return an array: " + method); + } + String[] dirs = (String[]) method.invokeExplosively(null); + return TestUtilities.findJavaFilesPerDirectory(resolveTestDirectory(), dirs); } - return method; - } + /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ + private FrameworkMethod getParametersMethod(TestClass testClass) { + List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); + if (parameterMethods.size() != 1) { + // Construct error message + + String methods; + if (parameterMethods.isEmpty()) { + methods = "[No methods specified]"; + } else { + StringJoiner sj = new StringJoiner(", "); + for (FrameworkMethod method : parameterMethods) { + sj.add(method.getName()); + } + methods = sj.toString(); + } + + throw new BugInCF( + "Exactly one of the following methods should be declared:%n%s%n" + + "testClass=%s%n" + + "parameterMethods=%s", + requiredFormsMessage, testClass.getName(), methods); + } - /** The message about the required getTestDirs method. */ - private static final String requiredFormsMessage = - "Parameter method must have the following form:" - + System.lineSeparator() - + "@Parameters String[] getTestDirs()"; + FrameworkMethod method = parameterMethods.get(0); + + Class returnType = method.getReturnType(); + String methodName = method.getName(); + switch (methodName) { + case "getTestDirs": + if (!(returnType.isArray() && returnType.getComponentType() == String.class)) { + throw new RuntimeException( + "getTestDirs should return String[], found " + returnType); + } + break; + + default: + throw new RuntimeException( + requiredFormsMessage + + "%n" + + "testClass=" + + testClass.getName() + + "%n" + + "parameterMethods=" + + method); + } - /** Runs the test class for the set of javaFiles passed in the constructor. */ - private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { - private final List javaFiles; + int modifiers = method.getMethod().getModifiers(); + if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { + throw new RuntimeException( + "Parameter method (" + method.getName() + ") must be public and static"); + } - PerParameterSetTestRunner(Class type, List javaFiles) throws InitializationError { - super(type); - this.javaFiles = javaFiles; + return method; } - @Override - public Object createTest() throws Exception { - Object[] arguments = Collections.singleton(javaFiles).toArray(); - return getTestClass().getOnlyConstructor().newInstance(arguments); - } + /** The message about the required getTestDirs method. */ + private static final String requiredFormsMessage = + "Parameter method must have the following form:" + + System.lineSeparator() + + "@Parameters String[] getTestDirs()"; - String testCaseName() { - File file = javaFiles.get(0).getParentFile(); - if (file == null) { - throw new Error("root was passed? " + javaFiles.get(0)); - } - return file.getPath(); - } + /** Runs the test class for the set of javaFiles passed in the constructor. */ + private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { + private final List javaFiles; - @Override - protected String getName() { - return String.format("[%s]", testCaseName()); - } + PerParameterSetTestRunner(Class type, List javaFiles) throws InitializationError { + super(type); + this.javaFiles = javaFiles; + } - @Override - protected String testName(FrameworkMethod method) { - return String.format("%s[%s]", method.getName(), testCaseName()); - } + @Override + public Object createTest() throws Exception { + Object[] arguments = Collections.singleton(javaFiles).toArray(); + return getTestClass().getOnlyConstructor().newInstance(arguments); + } - @Override - protected void validateZeroArgConstructor(List errors) { - // constructor should have args. - } + String testCaseName() { + File file = javaFiles.get(0).getParentFile(); + if (file == null) { + throw new Error("root was passed? " + javaFiles.get(0)); + } + return file.getPath(); + } - @Override - protected Statement classBlock(RunNotifier notifier) { - return childrenInvoker(notifier); + @Override + protected String getName() { + return String.format("[%s]", testCaseName()); + } + + @Override + protected String testName(FrameworkMethod method) { + return String.format("%s[%s]", method.getName(), testCaseName()); + } + + @Override + protected void validateZeroArgConstructor(List errors) { + // constructor should have args. + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); + } } - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java index 84db8ceb825..7466ff2cc27 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java @@ -1,15 +1,5 @@ package org.checkerframework.framework.test; -import java.io.File; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.StringJoiner; import org.checkerframework.javacutil.BugInCF; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; @@ -21,6 +11,17 @@ import org.junit.runners.model.TestClass; import org.plumelib.util.CollectionsPlume; +import java.io.File; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; + // TODO: large parts of this file are the same as PerDirectorySuite.java. // Reduce duplication by moving common parts to an abstract class. /** @@ -36,186 +37,189 @@ */ public class PerFileSuite extends RootedSuite { - /** Name */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public @interface Name {} - - private final ArrayList runners = new ArrayList<>(); - - @Override - protected List getChildren() { - return runners; - } - - /** - * Only called reflectively. Do not use programmatically. - * - * @param klass the class whose tests to run - */ - @SuppressWarnings("nullness") // JUnit needs to be annotated - public PerFileSuite(Class klass) throws Throwable { - super(klass, Collections.emptyList()); - TestClass testClass = getTestClass(); - Class javaTestClass = testClass.getJavaClass(); - List parametersList = getParametersList(testClass); - - for (Object[] parameters : parametersList) { - runners.add(new PerParameterSetTestRunner(javaTestClass, resolveTestDirectory(), parameters)); - } - } - - /** Returns a list of one-element arrays, each containing a Java File. */ - @SuppressWarnings({ - "unchecked", - "nullness" // JUnit needs to be annotated - }) - private List getParametersList(TestClass klass) throws Throwable { - FrameworkMethod method = getParametersMethod(klass); - - List javaFiles; - // We will have either a method getTestDirs which returns String [] or getTestFiles - // which returns List or getParametersMethod would fail - if (method == null) { - throw new BugInCF("no method annotated with @Parameters"); - } - if (method.getReturnType().isArray()) { - String[] dirs = (String[]) method.invokeExplosively(null); - javaFiles = TestUtilities.findRelativeNestedJavaFiles(resolveTestDirectory(), dirs); - } else { - javaFiles = (List) method.invokeExplosively(null); - } - - List argumentLists = - CollectionsPlume.mapList((File javaFile) -> new Object[] {javaFile}, javaFiles); - - return argumentLists; - } - - /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ - private FrameworkMethod getParametersMethod(TestClass testClass) { - List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); - if (parameterMethods.size() != 1) { - // Construct error message - - String methods; - if (parameterMethods.isEmpty()) { - methods = "[No methods specified]"; - } else { - StringJoiner sj = new StringJoiner(", "); - for (FrameworkMethod method : parameterMethods) { - sj.add(method.getName()); - } - methods = sj.toString(); - } + /** Name */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Name {} - throw new BugInCF(requiredFormsMessage, testClass.getName(), methods); - } // else + private final ArrayList runners = new ArrayList<>(); - FrameworkMethod method = parameterMethods.get(0); + @Override + protected List getChildren() { + return runners; + } - Class returnType = method.getReturnType(); - String methodName = method.getName(); - switch (methodName) { - case "getTestDirs": - if (returnType.isArray()) { - if (returnType.getComponentType() != String.class) { - throw new RuntimeException( - "Component type of getTestDirs must be java.lang.String, found " - + returnType.getComponentType().getCanonicalName()); - } + /** + * Only called reflectively. Do not use programmatically. + * + * @param klass the class whose tests to run + */ + @SuppressWarnings("nullness") // JUnit needs to be annotated + public PerFileSuite(Class klass) throws Throwable { + super(klass, Collections.emptyList()); + TestClass testClass = getTestClass(); + Class javaTestClass = testClass.getJavaClass(); + List parametersList = getParametersList(testClass); + + for (Object[] parameters : parametersList) { + runners.add( + new PerParameterSetTestRunner( + javaTestClass, resolveTestDirectory(), parameters)); } - break; + } - case "getTestFiles": - // We'll force people to return a List for now but enforcing exactly List or a - // subtype thereof is not easy. - if (!List.class.getCanonicalName().equals(returnType.getCanonicalName())) { - throw new RuntimeException("getTestFiles must return a List, found " + returnType); + /** Returns a list of one-element arrays, each containing a Java File. */ + @SuppressWarnings({ + "unchecked", + "nullness" // JUnit needs to be annotated + }) + private List getParametersList(TestClass klass) throws Throwable { + FrameworkMethod method = getParametersMethod(klass); + + List javaFiles; + // We will have either a method getTestDirs which returns String [] or getTestFiles + // which returns List or getParametersMethod would fail + if (method == null) { + throw new BugInCF("no method annotated with @Parameters"); + } + if (method.getReturnType().isArray()) { + String[] dirs = (String[]) method.invokeExplosively(null); + javaFiles = TestUtilities.findRelativeNestedJavaFiles(resolveTestDirectory(), dirs); + } else { + javaFiles = (List) method.invokeExplosively(null); } - break; - default: - throw new BugInCF(requiredFormsMessage, testClass.getName(), method); - } + List argumentLists = + CollectionsPlume.mapList((File javaFile) -> new Object[] {javaFile}, javaFiles); - int modifiers = method.getMethod().getModifiers(); - if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { - throw new RuntimeException( - "Parameter method (" + method.getName() + ") must be public and static"); + return argumentLists; } - return method; - } - - /** The message about the required getTestDirs or getTestFiles method. */ - private static final String requiredFormsMessage = - "Parameter method must have one of the following two forms:%n" - + "@Parameters String [] getTestDirs()%n" - + "@Parameters List getTestFiles()%n" - + "testClass=%s%n" - + "parameterMethods=%s"; - - /** Runs the test class for the set of parameters passed in the constructor. */ - private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { - /** A directory prefix to remove from the test name. */ - private final File directoryPrefix; + /** Returns method annotated @Parameters, typically the getTestDirs or getTestFiles method. */ + private FrameworkMethod getParametersMethod(TestClass testClass) { + List parameterMethods = testClass.getAnnotatedMethods(Parameters.class); + if (parameterMethods.size() != 1) { + // Construct error message + + String methods; + if (parameterMethods.isEmpty()) { + methods = "[No methods specified]"; + } else { + StringJoiner sj = new StringJoiner(", "); + for (FrameworkMethod method : parameterMethods) { + sj.add(method.getName()); + } + methods = sj.toString(); + } + + throw new BugInCF(requiredFormsMessage, testClass.getName(), methods); + } // else + + FrameworkMethod method = parameterMethods.get(0); + + Class returnType = method.getReturnType(); + String methodName = method.getName(); + switch (methodName) { + case "getTestDirs": + if (returnType.isArray()) { + if (returnType.getComponentType() != String.class) { + throw new RuntimeException( + "Component type of getTestDirs must be java.lang.String, found " + + returnType.getComponentType().getCanonicalName()); + } + } + break; + + case "getTestFiles": + // We'll force people to return a List for now but enforcing exactly List or a + // subtype thereof is not easy. + if (!List.class.getCanonicalName().equals(returnType.getCanonicalName())) { + throw new RuntimeException( + "getTestFiles must return a List, found " + returnType); + } + break; + + default: + throw new BugInCF(requiredFormsMessage, testClass.getName(), method); + } - /** The parameters, java source file(s) under {@link #directoryPrefix}. */ - private final Object[] parameters; + int modifiers = method.getMethod().getModifiers(); + if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { + throw new RuntimeException( + "Parameter method (" + method.getName() + ") must be public and static"); + } - /** - * Creates a PerParameterSetTestRunner for the test class {@code type}. - * - * @param type the test class - * @param directoryPrefix directory prefix to remove from the test name - * @param parameters java source file(s) under {@link #directoryPrefix} - * @throws InitializationError if the test class is malformed - */ - PerParameterSetTestRunner(Class type, File directoryPrefix, Object[] parameters) - throws InitializationError { - super(type); - this.directoryPrefix = directoryPrefix; - this.parameters = parameters; + return method; } - @Override - public Object createTest() throws Exception { - return getTestClass().getOnlyConstructor().newInstance(parameters); - } + /** The message about the required getTestDirs or getTestFiles method. */ + private static final String requiredFormsMessage = + "Parameter method must have one of the following two forms:%n" + + "@Parameters String [] getTestDirs()%n" + + "@Parameters List getTestFiles()%n" + + "testClass=%s%n" + + "parameterMethods=%s"; + + /** Runs the test class for the set of parameters passed in the constructor. */ + private static class PerParameterSetTestRunner extends BlockJUnit4ClassRunner { + /** A directory prefix to remove from the test name. */ + private final File directoryPrefix; + + /** The parameters, java source file(s) under {@link #directoryPrefix}. */ + private final Object[] parameters; + + /** + * Creates a PerParameterSetTestRunner for the test class {@code type}. + * + * @param type the test class + * @param directoryPrefix directory prefix to remove from the test name + * @param parameters java source file(s) under {@link #directoryPrefix} + * @throws InitializationError if the test class is malformed + */ + PerParameterSetTestRunner(Class type, File directoryPrefix, Object[] parameters) + throws InitializationError { + super(type); + this.directoryPrefix = directoryPrefix; + this.parameters = parameters; + } - /** - * Returns the test case's name. - * - * @return the test case's name - */ - String testCaseName() { - File file = (File) parameters[0]; - String name = - file.getPath() - .replace(".java", "") - .replace(directoryPrefix.getPath() + File.separator, ""); - return name; - } + @Override + public Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(parameters); + } - @Override - protected String getName() { - return String.format("[%s]", testCaseName()); - } + /** + * Returns the test case's name. + * + * @return the test case's name + */ + String testCaseName() { + File file = (File) parameters[0]; + String name = + file.getPath() + .replace(".java", "") + .replace(directoryPrefix.getPath() + File.separator, ""); + return name; + } - @Override - protected String testName(FrameworkMethod method) { - return String.format("%s[%s]", method.getName(), testCaseName()); - } + @Override + protected String getName() { + return String.format("[%s]", testCaseName()); + } - @Override - protected void validateZeroArgConstructor(List errors) { - // constructor should have args. - } + @Override + protected String testName(FrameworkMethod method) { + return String.format("%s[%s]", method.getName(), testCaseName()); + } - @Override - protected Statement classBlock(RunNotifier notifier) { - return childrenInvoker(notifier); + @Override + protected void validateZeroArgConstructor(List errors) { + // constructor should have args. + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); + } } - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java index 6fffafc2871..37e2be289ed 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/RootedSuite.java @@ -1,38 +1,40 @@ package org.checkerframework.framework.test; -import java.io.File; -import java.util.List; import org.junit.runner.Runner; import org.junit.runners.Suite; import org.junit.runners.model.InitializationError; +import java.io.File; +import java.util.List; + /** * Encapsulates the directory root to search within for test files to parameterise the test with. */ abstract class RootedSuite extends Suite { - /** - * Called by this class and subclasses once the runners making up the suite have been determined. - * - * @param klass root of the suite - * @param runners for each class in the suite, a {@link Runner} - * @throws InitializationError malformed test suite - */ - public RootedSuite(Class klass, List runners) throws InitializationError { - super(klass, runners); - } + /** + * Called by this class and subclasses once the runners making up the suite have been + * determined. + * + * @param klass root of the suite + * @param runners for each class in the suite, a {@link Runner} + * @throws InitializationError malformed test suite + */ + public RootedSuite(Class klass, List runners) throws InitializationError { + super(klass, runners); + } - /** - * Resolves the directory specified by {@link TestRootDirectory} or defaults to {@code - * currentDir/tests}. - * - * @return the resolved directory - */ - protected final File resolveTestDirectory() { - TestRootDirectory annotation = getTestClass().getAnnotation(TestRootDirectory.class); - if (annotation != null) { - return new File(annotation.value()); + /** + * Resolves the directory specified by {@link TestRootDirectory} or defaults to {@code + * currentDir/tests}. + * + * @return the resolved directory + */ + protected final File resolveTestDirectory() { + TestRootDirectory annotation = getTestClass().getAnnotation(TestRootDirectory.class); + if (annotation != null) { + return new File(annotation.value()); + } + return new File("tests"); } - return new File("tests"); - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java index 9ba608db3bb..58e6d9b540d 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.test; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.io.File; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; /** * SimpleOptionMap is a very basic Option container. The keys of the Option container are the set of @@ -24,135 +25,135 @@ * needed to make this class usable from the command line. */ public class SimpleOptionMap { - /** A Map from optionName to arg, where arg is null if the option doesn't require any args. */ - private final Map options = new LinkedHashMap<>(); - - /** - * Clears the current set of options and copies the input options to this map. - * - * @param options the new options to use for this object - */ - public void setOptions(Map options) { - this.options.clear(); - this.options.putAll(options); - } - - /** - * A method to easily add Strings to an option that takes a filepath as an argument. - * - * @param key an option with an argument of the form "arg1[path-separator]arg2..." e.g., "-cp - * myDir:myDir2:myDir3" - * @param toAppend a string to append onto the path or, if the path is null/empty, the argument to - * the option indicated by key - */ - public void addToPathOption(String key, String toAppend) { - if (toAppend == null) { - throw new IllegalArgumentException("Null string appended to sourcePath."); + /** A Map from optionName to arg, where arg is null if the option doesn't require any args. */ + private final Map options = new LinkedHashMap<>(); + + /** + * Clears the current set of options and copies the input options to this map. + * + * @param options the new options to use for this object + */ + public void setOptions(Map options) { + this.options.clear(); + this.options.putAll(options); + } + + /** + * A method to easily add Strings to an option that takes a filepath as an argument. + * + * @param key an option with an argument of the form "arg1[path-separator]arg2..." e.g., "-cp + * myDir:myDir2:myDir3" + * @param toAppend a string to append onto the path or, if the path is null/empty, the argument + * to the option indicated by key + */ + public void addToPathOption(String key, String toAppend) { + if (toAppend == null) { + throw new IllegalArgumentException("Null string appended to sourcePath."); + } + + String path = options.get(key); + + if (toAppend.startsWith(File.pathSeparator)) { + if (path == null || path.isEmpty()) { + path = toAppend.substring(1, toAppend.length()); + } else { + path += toAppend; + } + } else { + if (path == null || path.isEmpty()) { + path = toAppend; + } else { + path += File.pathSeparator + toAppend; + } + } + + addOption(key, path); + } + + /** + * Adds an option that takes no argument. + * + * @param option the no-argument option to add to this object + */ + public void addOption(String option) { + this.options.put(option, null); } - String path = options.get(key); - - if (toAppend.startsWith(File.pathSeparator)) { - if (path == null || path.isEmpty()) { - path = toAppend.substring(1, toAppend.length()); - } else { - path += toAppend; - } - } else { - if (path == null || path.isEmpty()) { - path = toAppend; - } else { - path += File.pathSeparator + toAppend; - } + /** + * Adds an option that takes an argument. + * + * @param option the option to add to this object + * @param value the argument to the option + */ + public void addOption(String option, String value) { + this.options.put(option, value); } - addOption(key, path); - } - - /** - * Adds an option that takes no argument. - * - * @param option the no-argument option to add to this object - */ - public void addOption(String option) { - this.options.put(option, null); - } - - /** - * Adds an option that takes an argument. - * - * @param option the option to add to this object - * @param value the argument to the option - */ - public void addOption(String option, String value) { - this.options.put(option, value); - } - - /** - * Adds the option only if value is a non-null, non-empty String. - * - * @param option the option to add to this object - * @param value the argument to the option (or null) - */ - public void addOptionIfValueNonEmpty(String option, @Nullable String value) { - if (value != null && !value.isEmpty()) { - addOption(option, value); + /** + * Adds the option only if value is a non-null, non-empty String. + * + * @param option the option to add to this object + * @param value the argument to the option (or null) + */ + public void addOptionIfValueNonEmpty(String option, @Nullable String value) { + if (value != null && !value.isEmpty()) { + addOption(option, value); + } } - } - - /** - * Adds all of the options in the given map to this one. - * - * @param options the options to add to this object - */ - public void addOptions(Map options) { - this.options.putAll(options); - } - - public void addOptions(Iterable newOptions) { - Iterator optIter = newOptions.iterator(); - while (optIter.hasNext()) { - String opt = optIter.next(); - if (this.options.get(opt) != null) { - if (!optIter.hasNext()) { - throw new RuntimeException( - "Expected a value for option: " - + opt - + " in option list: " - + String.join(", ", newOptions)); + + /** + * Adds all of the options in the given map to this one. + * + * @param options the options to add to this object + */ + public void addOptions(Map options) { + this.options.putAll(options); + } + + public void addOptions(Iterable newOptions) { + Iterator optIter = newOptions.iterator(); + while (optIter.hasNext()) { + String opt = optIter.next(); + if (this.options.get(opt) != null) { + if (!optIter.hasNext()) { + throw new RuntimeException( + "Expected a value for option: " + + opt + + " in option list: " + + String.join(", ", newOptions)); + } + this.options.put(opt, optIter.next()); + + } else { + this.options.put(opt, null); + } } - this.options.put(opt, optIter.next()); + } + + /** + * Removes the specified option. + * + * @param option the option to be removed from this object + */ + public void removeOption(String option) { + this.options.remove(option); + } + + /** + * Returns the map that backs this SimpleOptionMap. + * + * @return the options in this object + */ + public Map getOptions() { + return options; + } - } else { - this.options.put(opt, null); - } + /** + * Creates a "flat" list representation of these options. + * + * @return a list of the string representations of the options in this object + */ + public List getOptionsAsList() { + return TestUtilities.optionMapToList(options); } - } - - /** - * Removes the specified option. - * - * @param option the option to be removed from this object - */ - public void removeOption(String option) { - this.options.remove(option); - } - - /** - * Returns the map that backs this SimpleOptionMap. - * - * @return the options in this object - */ - public Map getOptions() { - return options; - } - - /** - * Creates a "flat" list representation of these options. - * - * @return a list of the string representations of the options in this object - */ - public List getOptionsAsList() { - return TestUtilities.optionMapToList(options); - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java index a35dba5f29f..9107215b629 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java @@ -1,84 +1,85 @@ package org.checkerframework.framework.test; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; + import java.io.File; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; /** A configuration for running CheckerFrameworkTests or running the TypecheckExecutor. */ public interface TestConfiguration { - /** - * Returns a list of source files a CheckerFrameworkPerDirectoryTest should be run over. These - * source files will be passed to Javac when the test is run. These are NOT JUnit tests. - * - * @return a list of source files a CheckerFrameworkPerDirectoryTest should be run over - */ - List getTestSourceFiles(); + /** + * Returns a list of source files a CheckerFrameworkPerDirectoryTest should be run over. These + * source files will be passed to Javac when the test is run. These are NOT JUnit tests. + * + * @return a list of source files a CheckerFrameworkPerDirectoryTest should be run over + */ + List getTestSourceFiles(); - /** - * Diagnostic files consist of a set of lines that enumerate expected error/warning diagnostics. - * The lines are of the form: - * - *
          fileName:lineNumber: diagnostKind: (messageKey)
          - * - * e.g., - * - *
          MethodInvocation.java:17: error: (method.invocation.invalid)
          - * - * If getDiagnosticFiles does NOT return an empty list, then the only diagnostics expected by the - * TestExecutor will be the ones found in these files. If it does return an empty list, then the - * only diagnostics expected will be the ones found in comments in the input test files. - * - *

          It is preferred that users write the errors in the test files and not in diagnostic files. - * - * @return a List of diagnostic files containing the error/warning messages expected to be output - * when Javac is run on the files returned by getTestSourceFiles. Return an empty list if - * these messages were specified within the source files. - */ - List getDiagnosticFiles(); + /** + * Diagnostic files consist of a set of lines that enumerate expected error/warning diagnostics. + * The lines are of the form: + * + *

          fileName:lineNumber: diagnostKind: (messageKey)
          + * + * e.g., + * + *
          MethodInvocation.java:17: error: (method.invocation.invalid)
          + * + * If getDiagnosticFiles does NOT return an empty list, then the only diagnostics expected by + * the TestExecutor will be the ones found in these files. If it does return an empty list, then + * the only diagnostics expected will be the ones found in comments in the input test files. + * + *

          It is preferred that users write the errors in the test files and not in diagnostic files. + * + * @return a List of diagnostic files containing the error/warning messages expected to be + * output when Javac is run on the files returned by getTestSourceFiles. Return an empty + * list if these messages were specified within the source files. + */ + List getDiagnosticFiles(); - /** - * Returns a list of annotation processors (Checkers) passed to the Javac compiler. - * - * @return a list of annotation processors (Checkers) passed to the Javac compiler - */ - List<@BinaryName String> getProcessors(); + /** + * Returns a list of annotation processors (Checkers) passed to the Javac compiler. + * + * @return a list of annotation processors (Checkers) passed to the Javac compiler + */ + List<@BinaryName String> getProcessors(); - /** - * Some Javac command line arguments require arguments themselves (e.g. {@code -classpath} takes a - * path) getOptions returns a {@code Map(optionName => optionArgumentIfAny)}. If an option does - * not take an argument, pass null as the value. - * - *

          E.g., - * - *

          {@code
          -   * Map(
          -   *     "-AprintAllQualifiers" => null
          -   *     "-classpath" => "myDir1:myDir2"
          -   * )
          -   * }
          - * - * @return a Map representing all command-line options to Javac other than source files and - * processors - */ - Map getOptions(); + /** + * Some Javac command line arguments require arguments themselves (e.g. {@code -classpath} takes + * a path) getOptions returns a {@code Map(optionName => optionArgumentIfAny)}. If an option + * does not take an argument, pass null as the value. + * + *

          E.g., + * + *

          {@code
          +     * Map(
          +     *     "-AprintAllQualifiers" => null
          +     *     "-classpath" => "myDir1:myDir2"
          +     * )
          +     * }
          + * + * @return a Map representing all command-line options to Javac other than source files and + * processors + */ + Map getOptions(); - /** - * Returns the map returned by {@link #getOptions}, flattened into a list. The entries will be - * added as followed: List(key1, value1, key2, value2, ..., keyN, valueN). If a value is NULL, - * then it will not appear in the list. - * - * @return the map returned {@link #getOptions}, but flattened into a list - */ - List getFlatOptions(); + /** + * Returns the map returned by {@link #getOptions}, flattened into a list. The entries will be + * added as followed: List(key1, value1, key2, value2, ..., keyN, valueN). If a value is NULL, + * then it will not appear in the list. + * + * @return the map returned {@link #getOptions}, but flattened into a list + */ + List getFlatOptions(); - /** - * Returns true if the TypecheckExecutor should emit debug information on system out, false - * otherwise. - * - * @return true if the TypecheckExecutor should emit debug information on system out, false - * otherwise - */ - boolean shouldEmitDebugInfo(); + /** + * Returns true if the TypecheckExecutor should emit debug information on system out, false + * otherwise. + * + * @return true if the TypecheckExecutor should emit debug information on system out, false + * otherwise + */ + boolean shouldEmitDebugInfo(); } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java index c4877ea5f1d..d3573c720b6 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java @@ -1,5 +1,12 @@ package org.checkerframework.framework.test; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.javacutil.BugInCF; +import org.plumelib.util.StringsPlume; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -9,12 +16,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.javacutil.BugInCF; -import org.plumelib.util.StringsPlume; /** * Used to create an instance of TestConfiguration. TestConfigurationBuilder is fluent: it returns @@ -27,589 +28,590 @@ */ public class TestConfigurationBuilder { - // Presented first are static helper methods that reduce configuration building to a method - // call. - // However, if you need more complex configuration or custom configuration, use the - // constructors provided below. - - /** - * This creates a builder for the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the directory - * of Checker's tests - * @param outputClassDirectory the directory to place classes compiled for testing - * @param classPath the classpath to use for compilation - * @param testSourceFiles the Java files that compose the test - * @param processors the checkers or other annotation processors to run over the testSourceFiles - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return the builder that will create an immutable test configuration - */ - public static TestConfigurationBuilder getDefaultConfigurationBuilder( - String testSourcePath, - File outputClassDirectory, - String classPath, - Iterable testSourceFiles, - Iterable<@BinaryName String> processors, - List options, - boolean shouldEmitDebugInfo) { - - TestConfigurationBuilder configBuilder = - new TestConfigurationBuilder() - .setShouldEmitDebugInfo(shouldEmitDebugInfo) - .addProcessors(processors) - .addOption("-Xmaxerrs", "9999") - .addOption("-Xmaxwarns", "9999") - .addOption("-g") - .addOption("-Xlint:unchecked") - .addOption("-Xlint:deprecation") - .addOption("-XDrawDiagnostics") // use short javac diagnostics - .addOption("-ApermitMissingJdk") - .addOption("-AnoJreVersionCheck"); - - // -Anomsgtext is needed to ensure expected errors can be matched, which is the - // right thing for most test cases. - // Note that this will be removed if -Adetailedmsgtext is added to the configuration. - // Check `TestConfigurationBuilder#removeConflicts()` for more details. - configBuilder.addOption("-Anomsgtext"); - - // TODO: decide whether this would be useful - // configBuilder.addOption("-AajavaChecks"); - - if (outputClassDirectory != null) { - configBuilder.addOption("-d", outputClassDirectory.getAbsolutePath()); - } - - configBuilder - .addOptionIfValueNonEmpty("-sourcepath", testSourcePath) - .addOption("-implicit:class") - .addOption("-classpath", classPath); - - configBuilder.addOptions(options); - - configBuilder.addSourceFiles(testSourceFiles); - return configBuilder; - } - - /** - * This is the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the directory - * of Checker's tests - * @param testFile a single test Java file to compile - * @param processor a single checker to include in the processors field - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return a TestConfiguration with input parameters added plus the normal default options, - * compiler, and file manager used by Checker Framework tests - */ - @SuppressWarnings( - "signature:cast.unsafe" // for non-array non-primitive class, getName(): @BinaryName - ) - public static TestConfiguration buildDefaultConfiguration( - String testSourcePath, - File testFile, - Class processor, - List options, - boolean shouldEmitDebugInfo) { - return buildDefaultConfiguration( - testSourcePath, - Arrays.asList(testFile), - Collections.emptyList(), - Arrays.asList((@BinaryName String) processor.getName()), - options, - shouldEmitDebugInfo); - } - - /** - * This is the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the directory - * of Checker's tests - * @param testSourceFiles the Java files that compose the test - * @param processors the checkers or other annotation processors to run over the testSourceFiles - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return a TestConfiguration with input parameters added plus the normal default options, - * compiler, and file manager used by Checker Framework tests - */ - public static TestConfiguration buildDefaultConfiguration( - String testSourcePath, - Iterable testSourceFiles, - Iterable<@BinaryName String> processors, - List options, - boolean shouldEmitDebugInfo) { - return buildDefaultConfiguration( - testSourcePath, - testSourceFiles, - Collections.emptyList(), - processors, - options, - shouldEmitDebugInfo); - } - - /** - * This is the default configuration used by Checker Framework JUnit tests. - * - * @param testSourcePath the path to the Checker test file sources, usually this is the directory - * of Checker's tests - * @param testSourceFiles the Java files that compose the test - * @param classpathExtra extra entries for the classpath, needed to compile the source files - * @param processors the checkers or other annotation processors to run over the testSourceFiles - * @param options the options to the compiler/processors - * @param shouldEmitDebugInfo whether or not debug information should be emitted - * @return a TestConfiguration with input parameters added plus the normal default options, - * compiler, and file manager used by Checker Framework tests - */ - public static TestConfiguration buildDefaultConfiguration( - String testSourcePath, - Iterable testSourceFiles, - Collection classpathExtra, - Iterable<@BinaryName String> processors, - List options, - boolean shouldEmitDebugInfo) { - - String classPath = getDefaultClassPath(); - if (!classpathExtra.isEmpty()) { - classPath += - System.getProperty("path.separator") - + String.join(System.getProperty("path.separator"), classpathExtra); - } - - File outputDir = getOutputDirFromProperty(); - - TestConfigurationBuilder builder = - getDefaultConfigurationBuilder( - testSourcePath, - outputDir, - classPath, - testSourceFiles, - processors, - options, - shouldEmitDebugInfo); - return builder.validateThenBuild(true); - } - - /** The list of files that contain Java diagnostics to compare against. */ - private List diagnosticFiles; - - /** The set of Java files to test against. */ - private List testSourceFiles; - - /** The set of Checker Framework processors to test with. */ - private final Set<@BinaryName String> processors; - - /** The set of options to the Javac command line used to run the test. */ - private final SimpleOptionMap options; - - /** Should the Javac options be output before running the test. */ - private boolean shouldEmitDebugInfo; - - /** - * Note: There are static helper methods named buildConfiguration and buildConfigurationBuilder - * that can be used to create the most common types of configurations - */ - public TestConfigurationBuilder() { - diagnosticFiles = new ArrayList<>(); - testSourceFiles = new ArrayList<>(); - processors = new LinkedHashSet<>(); - options = new SimpleOptionMap(); - shouldEmitDebugInfo = false; - } - - /** - * Create a builder that has all of the options in initialConfig. - * - * @param initialConfig initial configuration for the newly-created builder - */ - public TestConfigurationBuilder(TestConfiguration initialConfig) { - this.diagnosticFiles = new ArrayList<>(initialConfig.getDiagnosticFiles()); - this.testSourceFiles = new ArrayList<>(initialConfig.getTestSourceFiles()); - this.processors = new LinkedHashSet<>(initialConfig.getProcessors()); - this.options = new SimpleOptionMap(); - this.addOptions(initialConfig.getOptions()); - - this.shouldEmitDebugInfo = initialConfig.shouldEmitDebugInfo(); - } - - /** - * Ensures that the minimum requirements for running a test are met. These requirements are: - * - *
            - *
          • There is at least one source file - *
          • There is at least one processor (if requireProcessors has been set to true) - *
          • There is an output directory specified for class files - *
          • There is no {@code -processor} option in the optionMap (it should be added by - * addProcessor instead) - *
          • There is no option with prefix "-J-" in the optionMap - *
          - * - * @param requireProcessors whether or not to require that there is at least one processor - * @return a list of errors found while validating this configuration - */ - public List validate(boolean requireProcessors) { - List errors = new ArrayList<>(); - if (testSourceFiles == null || !testSourceFiles.iterator().hasNext()) { - errors.add("No source files specified!"); - } - - if (requireProcessors && !processors.iterator().hasNext()) { - errors.add("No processors were specified!"); - } - - Map optionMap = options.getOptions(); - if (!optionMap.containsKey("-d") || optionMap.get("-d") == null) { - errors.add("No output directory was specified."); - } - - if (optionMap.containsKey("-processor")) { - errors.add("Processors should not be added to the options list"); - } - - StringBuilder jvmOptionKeys = new StringBuilder(); - for (String optionKey : optionMap.keySet()) { - if (optionKey.startsWith("-J-")) { - jvmOptionKeys.append(optionKey).append('\n'); - } - } - if (jvmOptionKeys.length() > 0) { - errors.add( - "The following JVM options have no effects in a configuration.\n" - + jvmOptionKeys - + "If needed, please add them to your build file instead."); - } - - return errors; - } - - /** Ensures there are no options conflicting with each other. */ - protected void removeConflicts() { - final Map optionMap = options.getOptions(); - if (optionMap.containsKey("-Adetailedmsgtext")) { - // If `detailedmsgtext` is specified, remove `nomsgtext`. - options.removeOption("-Anomsgtext"); - } - } - - /** - * Adds the given path option to {@code this}. - * - * @param key the key to add - * @param toAppend the path to append - * @return the current object {@code this} - */ - public TestConfigurationBuilder adddToPathOption(String key, String toAppend) { - this.options.addToPathOption(key, toAppend); - return this; - } - - /** - * Adds the given diagnostics file to {@code this}. - * - * @param diagnostics the diagnostics file to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addDiagnosticFile(File diagnostics) { - this.diagnosticFiles.add(diagnostics); - return this; - } - - /** - * Adds the given diagnostics files to {@code this}. - * - * @param diagnostics diagnostics files to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addDiagnosticFiles(Iterable diagnostics) { - this.diagnosticFiles = catListAndIterable(diagnosticFiles, diagnostics); - return this; - } - - /** - * Sets the diagnostics files of {@code this}. - * - * @param diagnosticFiles diagnostics files to set on {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder setDiagnosticFiles(List diagnosticFiles) { - this.diagnosticFiles = new ArrayList<>(diagnosticFiles); - return this; - } - - /** - * Adds the given source file to {@code this}. - * - * @param sourceFile source file to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addSourceFile(File sourceFile) { - this.testSourceFiles.add(sourceFile); - return this; - } - - /** - * Adds the given source files to {@code this}. - * - * @param sourceFiles source files to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addSourceFiles(Iterable sourceFiles) { - this.testSourceFiles = catListAndIterable(testSourceFiles, sourceFiles); - return this; - } - - /** - * Sets the source files of {@code this}. - * - * @param sourceFiles source files to set on {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder setSourceFiles(List sourceFiles) { - this.testSourceFiles = new ArrayList<>(sourceFiles); - return this; - } - - /** - * Sets the given options on {@code this}. - * - * @param options options to set on {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder setOptions(Map options) { - this.options.setOptions(options); - return this; - } - - /** - * Adds the given option to {@code this}. - * - * @param option option to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOption(String option) { - this.options.addOption(option); - return this; - } - - /** - * Adds the given option and value to {@code this}. - * - * @param option option to add to {@code this} - * @param value value to add - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOption(String option, String value) { - this.options.addOption(option, value); - return this; - } - - /** - * Adds the given option to {@code this} if the value is non-empty. - * - * @param option option to add to {@code this} - * @param value value to add, iff it is non-empty - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOptionIfValueNonEmpty(String option, String value) { - if (value != null && !value.isEmpty()) { - return addOption(option, value); - } - - return this; - } - - /** - * Adds the given options to {@code this}. - * - * @param options options to add to {@code this} - * @return the current object {@code this} - */ - @SuppressWarnings("initialization:return.type.incompatible") // need @PolyInitialized annotation - @RequiresNonNull("this.options") - public TestConfigurationBuilder addOptions( - @UnknownInitialization(TestConfigurationBuilder.class) TestConfigurationBuilder this, - Map options) { - this.options.addOptions(options); - return this; - } - - /** - * Adds the given options to {@code this}. - * - * @param newOptions options to add to {@code this} - * @return the current object {@code this} - */ - public TestConfigurationBuilder addOptions(Iterable newOptions) { - this.options.addOptions(newOptions); - return this; - } - - /** - * Set the processors. - * - * @param processors the processors to run - * @return this - */ - public TestConfigurationBuilder setProcessors(Iterable<@BinaryName String> processors) { - this.processors.clear(); - for (String proc : processors) { - this.processors.add(proc); - } - return this; - } - - /** - * Add a processor. - * - * @param processor a processor to run - * @return this - */ - public TestConfigurationBuilder addProcessor(@BinaryName String processor) { - this.processors.add(processor); - return this; - } - - /** - * Add processors. - * - * @param processors processors to run - * @return this - */ - public TestConfigurationBuilder addProcessors(Iterable<@BinaryName String> processors) { - for (String processor : processors) { - this.processors.add(processor); - } - - return this; - } - - /** - * Sets {@code this} to output debug info. - * - * @return the current object {@code this} - */ - public TestConfigurationBuilder emitDebugInfo() { - this.shouldEmitDebugInfo = true; - return this; - } - - /** - * Sets {@code this} to not output debug info. - * - * @return the current object {@code this} - */ - public TestConfigurationBuilder dontEmitDebugInfo() { - this.shouldEmitDebugInfo = false; - return this; - } - - /** - * Sets {@code this} to output debug info depending on the parameter. - * - * @param shouldEmitDebugInfo whether to emit debug info - * @return the current object {@code this} - */ - public TestConfigurationBuilder setShouldEmitDebugInfo(boolean shouldEmitDebugInfo) { - this.shouldEmitDebugInfo = shouldEmitDebugInfo; - return this; - } - - /** - * Creates a TestConfiguration using the settings in this builder. The settings are NOT validated - * first. - * - * @return a TestConfiguration using the settings in this builder - */ - public TestConfiguration build() { - return new ImmutableTestConfiguration( - diagnosticFiles, - testSourceFiles, - new ArrayList<>(processors), - options.getOptions(), - shouldEmitDebugInfo); - } - - /** - * Creates a TestConfiguration using the settings in this builder. The settings are first - * validated and a runtime exception is thrown if any errors are found - * - * @param requireProcessors whether or not there should be at least 1 processor specified, see - * method validate - * @return a TestConfiguration using the settings in this builder - */ - public TestConfiguration validateThenBuild(boolean requireProcessors) { - removeConflicts(); - List errors = validate(requireProcessors); - if (errors.isEmpty()) { - return build(); - } - - throw new BugInCF( - "Attempted to build invalid test configuration:%n" + "Errors:%n%s%n%s%n", - String.join("%n", errors), this); - } - - /** - * Returns the set of Javac options as a flat list. - * - * @return the set of Javac options as a flat list - */ - public List flatOptions() { - return options.getOptionsAsList(); - } - - @Override - public String toString() { - return StringsPlume.joinLines( - "TestConfigurationBuilder:", - "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), - "processors=" + String.join(", ", processors), - "options=" + String.join(", ", options.getOptionsAsList()), - "shouldEmitDebugInfo=" + shouldEmitDebugInfo); - } - - /** - * Returns a list that first has the items from parameter list then the items from iterable. - * - * @param the type of the elements in the resulting list - * @param list a list - * @param iterable an iterable - * @return a list that first has the items from parameter list then the items from iterable - */ - private static List catListAndIterable( - List list, Iterable iterable) { - List newList = new ArrayList<>(list); - - for (T iterObject : iterable) { - newList.add(iterObject); - } - - return newList; - } - - /** The output directory for tests. */ - public static final String TESTS_OUTPUTDIR = "tests.outputDir"; - - /** - * Determine the output directory from the {@code tests.outputDir} property. - * - * @return the output directory - */ - public static File getOutputDirFromProperty() { - return new File( - System.getProperty( - "tests.outputDir", - "tests" + File.separator + "build" + File.separator + "testclasses")); - } - - /** - * Determine the default classpath from the {@code tests.classpath} property. - * - * @return the default classpath - */ - public static String getDefaultClassPath() { - String classpath = System.getProperty("tests.classpath", "tests" + File.separator + "build"); - String globalclasspath = System.getProperty("java.class.path", ""); - return classpath + File.pathSeparator + globalclasspath; - } + // Presented first are static helper methods that reduce configuration building to a method + // call. + // However, if you need more complex configuration or custom configuration, use the + // constructors provided below. + + /** + * This creates a builder for the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the + * directory of Checker's tests + * @param outputClassDirectory the directory to place classes compiled for testing + * @param classPath the classpath to use for compilation + * @param testSourceFiles the Java files that compose the test + * @param processors the checkers or other annotation processors to run over the testSourceFiles + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return the builder that will create an immutable test configuration + */ + public static TestConfigurationBuilder getDefaultConfigurationBuilder( + String testSourcePath, + File outputClassDirectory, + String classPath, + Iterable testSourceFiles, + Iterable<@BinaryName String> processors, + List options, + boolean shouldEmitDebugInfo) { + + TestConfigurationBuilder configBuilder = + new TestConfigurationBuilder() + .setShouldEmitDebugInfo(shouldEmitDebugInfo) + .addProcessors(processors) + .addOption("-Xmaxerrs", "9999") + .addOption("-Xmaxwarns", "9999") + .addOption("-g") + .addOption("-Xlint:unchecked") + .addOption("-Xlint:deprecation") + .addOption("-XDrawDiagnostics") // use short javac diagnostics + .addOption("-ApermitMissingJdk") + .addOption("-AnoJreVersionCheck"); + + // -Anomsgtext is needed to ensure expected errors can be matched, which is the + // right thing for most test cases. + // Note that this will be removed if -Adetailedmsgtext is added to the configuration. + // Check `TestConfigurationBuilder#removeConflicts()` for more details. + configBuilder.addOption("-Anomsgtext"); + + // TODO: decide whether this would be useful + // configBuilder.addOption("-AajavaChecks"); + + if (outputClassDirectory != null) { + configBuilder.addOption("-d", outputClassDirectory.getAbsolutePath()); + } + + configBuilder + .addOptionIfValueNonEmpty("-sourcepath", testSourcePath) + .addOption("-implicit:class") + .addOption("-classpath", classPath); + + configBuilder.addOptions(options); + + configBuilder.addSourceFiles(testSourceFiles); + return configBuilder; + } + + /** + * This is the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the + * directory of Checker's tests + * @param testFile a single test Java file to compile + * @param processor a single checker to include in the processors field + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return a TestConfiguration with input parameters added plus the normal default options, + * compiler, and file manager used by Checker Framework tests + */ + @SuppressWarnings( + "signature:cast.unsafe" // for non-array non-primitive class, getName(): @BinaryName + ) + public static TestConfiguration buildDefaultConfiguration( + String testSourcePath, + File testFile, + Class processor, + List options, + boolean shouldEmitDebugInfo) { + return buildDefaultConfiguration( + testSourcePath, + Arrays.asList(testFile), + Collections.emptyList(), + Arrays.asList((@BinaryName String) processor.getName()), + options, + shouldEmitDebugInfo); + } + + /** + * This is the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the + * directory of Checker's tests + * @param testSourceFiles the Java files that compose the test + * @param processors the checkers or other annotation processors to run over the testSourceFiles + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return a TestConfiguration with input parameters added plus the normal default options, + * compiler, and file manager used by Checker Framework tests + */ + public static TestConfiguration buildDefaultConfiguration( + String testSourcePath, + Iterable testSourceFiles, + Iterable<@BinaryName String> processors, + List options, + boolean shouldEmitDebugInfo) { + return buildDefaultConfiguration( + testSourcePath, + testSourceFiles, + Collections.emptyList(), + processors, + options, + shouldEmitDebugInfo); + } + + /** + * This is the default configuration used by Checker Framework JUnit tests. + * + * @param testSourcePath the path to the Checker test file sources, usually this is the + * directory of Checker's tests + * @param testSourceFiles the Java files that compose the test + * @param classpathExtra extra entries for the classpath, needed to compile the source files + * @param processors the checkers or other annotation processors to run over the testSourceFiles + * @param options the options to the compiler/processors + * @param shouldEmitDebugInfo whether or not debug information should be emitted + * @return a TestConfiguration with input parameters added plus the normal default options, + * compiler, and file manager used by Checker Framework tests + */ + public static TestConfiguration buildDefaultConfiguration( + String testSourcePath, + Iterable testSourceFiles, + Collection classpathExtra, + Iterable<@BinaryName String> processors, + List options, + boolean shouldEmitDebugInfo) { + + String classPath = getDefaultClassPath(); + if (!classpathExtra.isEmpty()) { + classPath += + System.getProperty("path.separator") + + String.join(System.getProperty("path.separator"), classpathExtra); + } + + File outputDir = getOutputDirFromProperty(); + + TestConfigurationBuilder builder = + getDefaultConfigurationBuilder( + testSourcePath, + outputDir, + classPath, + testSourceFiles, + processors, + options, + shouldEmitDebugInfo); + return builder.validateThenBuild(true); + } + + /** The list of files that contain Java diagnostics to compare against. */ + private List diagnosticFiles; + + /** The set of Java files to test against. */ + private List testSourceFiles; + + /** The set of Checker Framework processors to test with. */ + private final Set<@BinaryName String> processors; + + /** The set of options to the Javac command line used to run the test. */ + private final SimpleOptionMap options; + + /** Should the Javac options be output before running the test. */ + private boolean shouldEmitDebugInfo; + + /** + * Note: There are static helper methods named buildConfiguration and buildConfigurationBuilder + * that can be used to create the most common types of configurations + */ + public TestConfigurationBuilder() { + diagnosticFiles = new ArrayList<>(); + testSourceFiles = new ArrayList<>(); + processors = new LinkedHashSet<>(); + options = new SimpleOptionMap(); + shouldEmitDebugInfo = false; + } + + /** + * Create a builder that has all of the options in initialConfig. + * + * @param initialConfig initial configuration for the newly-created builder + */ + public TestConfigurationBuilder(TestConfiguration initialConfig) { + this.diagnosticFiles = new ArrayList<>(initialConfig.getDiagnosticFiles()); + this.testSourceFiles = new ArrayList<>(initialConfig.getTestSourceFiles()); + this.processors = new LinkedHashSet<>(initialConfig.getProcessors()); + this.options = new SimpleOptionMap(); + this.addOptions(initialConfig.getOptions()); + + this.shouldEmitDebugInfo = initialConfig.shouldEmitDebugInfo(); + } + + /** + * Ensures that the minimum requirements for running a test are met. These requirements are: + * + *
            + *
          • There is at least one source file + *
          • There is at least one processor (if requireProcessors has been set to true) + *
          • There is an output directory specified for class files + *
          • There is no {@code -processor} option in the optionMap (it should be added by + * addProcessor instead) + *
          • There is no option with prefix "-J-" in the optionMap + *
          + * + * @param requireProcessors whether or not to require that there is at least one processor + * @return a list of errors found while validating this configuration + */ + public List validate(boolean requireProcessors) { + List errors = new ArrayList<>(); + if (testSourceFiles == null || !testSourceFiles.iterator().hasNext()) { + errors.add("No source files specified!"); + } + + if (requireProcessors && !processors.iterator().hasNext()) { + errors.add("No processors were specified!"); + } + + Map optionMap = options.getOptions(); + if (!optionMap.containsKey("-d") || optionMap.get("-d") == null) { + errors.add("No output directory was specified."); + } + + if (optionMap.containsKey("-processor")) { + errors.add("Processors should not be added to the options list"); + } + + StringBuilder jvmOptionKeys = new StringBuilder(); + for (String optionKey : optionMap.keySet()) { + if (optionKey.startsWith("-J-")) { + jvmOptionKeys.append(optionKey).append('\n'); + } + } + if (jvmOptionKeys.length() > 0) { + errors.add( + "The following JVM options have no effects in a configuration.\n" + + jvmOptionKeys + + "If needed, please add them to your build file instead."); + } + + return errors; + } + + /** Ensures there are no options conflicting with each other. */ + protected void removeConflicts() { + final Map optionMap = options.getOptions(); + if (optionMap.containsKey("-Adetailedmsgtext")) { + // If `detailedmsgtext` is specified, remove `nomsgtext`. + options.removeOption("-Anomsgtext"); + } + } + + /** + * Adds the given path option to {@code this}. + * + * @param key the key to add + * @param toAppend the path to append + * @return the current object {@code this} + */ + public TestConfigurationBuilder adddToPathOption(String key, String toAppend) { + this.options.addToPathOption(key, toAppend); + return this; + } + + /** + * Adds the given diagnostics file to {@code this}. + * + * @param diagnostics the diagnostics file to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addDiagnosticFile(File diagnostics) { + this.diagnosticFiles.add(diagnostics); + return this; + } + + /** + * Adds the given diagnostics files to {@code this}. + * + * @param diagnostics diagnostics files to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addDiagnosticFiles(Iterable diagnostics) { + this.diagnosticFiles = catListAndIterable(diagnosticFiles, diagnostics); + return this; + } + + /** + * Sets the diagnostics files of {@code this}. + * + * @param diagnosticFiles diagnostics files to set on {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder setDiagnosticFiles(List diagnosticFiles) { + this.diagnosticFiles = new ArrayList<>(diagnosticFiles); + return this; + } + + /** + * Adds the given source file to {@code this}. + * + * @param sourceFile source file to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addSourceFile(File sourceFile) { + this.testSourceFiles.add(sourceFile); + return this; + } + + /** + * Adds the given source files to {@code this}. + * + * @param sourceFiles source files to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addSourceFiles(Iterable sourceFiles) { + this.testSourceFiles = catListAndIterable(testSourceFiles, sourceFiles); + return this; + } + + /** + * Sets the source files of {@code this}. + * + * @param sourceFiles source files to set on {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder setSourceFiles(List sourceFiles) { + this.testSourceFiles = new ArrayList<>(sourceFiles); + return this; + } + + /** + * Sets the given options on {@code this}. + * + * @param options options to set on {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder setOptions(Map options) { + this.options.setOptions(options); + return this; + } + + /** + * Adds the given option to {@code this}. + * + * @param option option to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOption(String option) { + this.options.addOption(option); + return this; + } + + /** + * Adds the given option and value to {@code this}. + * + * @param option option to add to {@code this} + * @param value value to add + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOption(String option, String value) { + this.options.addOption(option, value); + return this; + } + + /** + * Adds the given option to {@code this} if the value is non-empty. + * + * @param option option to add to {@code this} + * @param value value to add, iff it is non-empty + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOptionIfValueNonEmpty(String option, String value) { + if (value != null && !value.isEmpty()) { + return addOption(option, value); + } + + return this; + } + + /** + * Adds the given options to {@code this}. + * + * @param options options to add to {@code this} + * @return the current object {@code this} + */ + @SuppressWarnings("initialization:return.type.incompatible") // need @PolyInitialized annotation + @RequiresNonNull("this.options") + public TestConfigurationBuilder addOptions( + @UnknownInitialization(TestConfigurationBuilder.class) TestConfigurationBuilder this, + Map options) { + this.options.addOptions(options); + return this; + } + + /** + * Adds the given options to {@code this}. + * + * @param newOptions options to add to {@code this} + * @return the current object {@code this} + */ + public TestConfigurationBuilder addOptions(Iterable newOptions) { + this.options.addOptions(newOptions); + return this; + } + + /** + * Set the processors. + * + * @param processors the processors to run + * @return this + */ + public TestConfigurationBuilder setProcessors(Iterable<@BinaryName String> processors) { + this.processors.clear(); + for (String proc : processors) { + this.processors.add(proc); + } + return this; + } + + /** + * Add a processor. + * + * @param processor a processor to run + * @return this + */ + public TestConfigurationBuilder addProcessor(@BinaryName String processor) { + this.processors.add(processor); + return this; + } + + /** + * Add processors. + * + * @param processors processors to run + * @return this + */ + public TestConfigurationBuilder addProcessors(Iterable<@BinaryName String> processors) { + for (String processor : processors) { + this.processors.add(processor); + } + + return this; + } + + /** + * Sets {@code this} to output debug info. + * + * @return the current object {@code this} + */ + public TestConfigurationBuilder emitDebugInfo() { + this.shouldEmitDebugInfo = true; + return this; + } + + /** + * Sets {@code this} to not output debug info. + * + * @return the current object {@code this} + */ + public TestConfigurationBuilder dontEmitDebugInfo() { + this.shouldEmitDebugInfo = false; + return this; + } + + /** + * Sets {@code this} to output debug info depending on the parameter. + * + * @param shouldEmitDebugInfo whether to emit debug info + * @return the current object {@code this} + */ + public TestConfigurationBuilder setShouldEmitDebugInfo(boolean shouldEmitDebugInfo) { + this.shouldEmitDebugInfo = shouldEmitDebugInfo; + return this; + } + + /** + * Creates a TestConfiguration using the settings in this builder. The settings are NOT + * validated first. + * + * @return a TestConfiguration using the settings in this builder + */ + public TestConfiguration build() { + return new ImmutableTestConfiguration( + diagnosticFiles, + testSourceFiles, + new ArrayList<>(processors), + options.getOptions(), + shouldEmitDebugInfo); + } + + /** + * Creates a TestConfiguration using the settings in this builder. The settings are first + * validated and a runtime exception is thrown if any errors are found + * + * @param requireProcessors whether or not there should be at least 1 processor specified, see + * method validate + * @return a TestConfiguration using the settings in this builder + */ + public TestConfiguration validateThenBuild(boolean requireProcessors) { + removeConflicts(); + List errors = validate(requireProcessors); + if (errors.isEmpty()) { + return build(); + } + + throw new BugInCF( + "Attempted to build invalid test configuration:%n" + "Errors:%n%s%n%s%n", + String.join("%n", errors), this); + } + + /** + * Returns the set of Javac options as a flat list. + * + * @return the set of Javac options as a flat list + */ + public List flatOptions() { + return options.getOptionsAsList(); + } + + @Override + public String toString() { + return StringsPlume.joinLines( + "TestConfigurationBuilder:", + "testSourceFiles=" + StringsPlume.join(" ", testSourceFiles), + "processors=" + String.join(", ", processors), + "options=" + String.join(", ", options.getOptionsAsList()), + "shouldEmitDebugInfo=" + shouldEmitDebugInfo); + } + + /** + * Returns a list that first has the items from parameter list then the items from iterable. + * + * @param the type of the elements in the resulting list + * @param list a list + * @param iterable an iterable + * @return a list that first has the items from parameter list then the items from iterable + */ + private static List catListAndIterable( + List list, Iterable iterable) { + List newList = new ArrayList<>(list); + + for (T iterObject : iterable) { + newList.add(iterObject); + } + + return newList; + } + + /** The output directory for tests. */ + public static final String TESTS_OUTPUTDIR = "tests.outputDir"; + + /** + * Determine the output directory from the {@code tests.outputDir} property. + * + * @return the output directory + */ + public static File getOutputDirFromProperty() { + return new File( + System.getProperty( + "tests.outputDir", + "tests" + File.separator + "build" + File.separator + "testclasses")); + } + + /** + * Determine the default classpath from the {@code tests.classpath} property. + * + * @return the default classpath + */ + public static String getDefaultClassPath() { + String classpath = + System.getProperty("tests.classpath", "tests" + File.separator + "build"); + String globalclasspath = System.getProperty("java.class.path", ""); + return classpath + File.pathSeparator + globalclasspath; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java index 9203aee1f8e..b288bc7ac2b 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestRootDirectory.java @@ -11,10 +11,10 @@ @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface TestRootDirectory { - /** - * Path, relative to the current/module directory, within which to search for test sources. - * - * @return tests root directory - */ - String value(); + /** + * Path, relative to the current/module directory, within which to search for test sources. + * + * @return tests root directory + */ + String value(); } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java index c7117a6e3da..e73257f3783 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java @@ -1,5 +1,14 @@ package org.checkerframework.framework.test; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.SystemUtil; +import org.junit.Assert; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; +import org.plumelib.util.SystemPlume; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -20,572 +29,571 @@ import java.util.Scanner; import java.util.Set; import java.util.StringJoiner; + import javax.tools.Diagnostic; import javax.tools.JavaFileObject; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.SystemUtil; -import org.junit.Assert; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; -import org.plumelib.util.SystemPlume; /** Utilities for testing. */ public class TestUtilities { - /** True if the JVM is version 9 or above. */ - public static final boolean IS_AT_LEAST_9_JVM = SystemUtil.jreVersion >= 9; - - /** True if the JVM is version 10 or above. */ - public static final boolean IS_AT_LEAST_10_JVM = SystemUtil.jreVersion >= 10; - - /** True if the JVM is version 11 or above. */ - public static final boolean IS_AT_LEAST_11_JVM = SystemUtil.jreVersion >= 11; - - /** True if the JVM is version 11 or lower. */ - public static final boolean IS_AT_MOST_11_JVM = SystemUtil.jreVersion <= 11; - - /** True if the JVM is version 14 or above. */ - public static final boolean IS_AT_LEAST_14_JVM = SystemUtil.jreVersion >= 14; - - /** True if the JVM is version 14 or lower. */ - public static final boolean IS_AT_MOST_14_JVM = SystemUtil.jreVersion <= 14; - - /** True if the JVM is version 16 or above. */ - public static final boolean IS_AT_LEAST_16_JVM = SystemUtil.jreVersion >= 16; - - /** True if the JVM is version 16 or lower. */ - public static final boolean IS_AT_MOST_16_JVM = SystemUtil.jreVersion <= 16; - - /** True if the JVM is version 17 or above. */ - public static final boolean IS_AT_LEAST_17_JVM = SystemUtil.jreVersion >= 17; - - /** True if the JVM is version 17 or lower. */ - public static final boolean IS_AT_MOST_17_JVM = SystemUtil.jreVersion <= 17; - - /** True if the JVM is version 18 or above. */ - public static final boolean IS_AT_LEAST_18_JVM = SystemUtil.jreVersion >= 18; - - /** True if the JVM is version 18 or lower. */ - public static final boolean IS_AT_MOST_18_JVM = SystemUtil.jreVersion <= 18; - - /** True if the JVM is version 21 or above. */ - public static final boolean IS_AT_LEAST_21_JVM = SystemUtil.jreVersion >= 21; - - /** - * Find test java sources within currentDir/tests. - * - * @param dirNames subdirectories of currentDir/tests - * @return found files - */ - public static List findNestedJavaTestFiles(String... dirNames) { - return findRelativeNestedJavaFiles(new File("tests"), dirNames); - } - - /** - * Find test java sources within {@code parent}. - * - * @param parent directory to search within - * @param dirNames subdirectories of {@code parent} - * @return found files - */ - public static List findRelativeNestedJavaFiles(String parent, String... dirNames) { - return findRelativeNestedJavaFiles(new File(parent), dirNames); - } - - /** - * Find test java sources within {@code parent}. - * - * @param parent directory to search within - * @param dirNames subdirectories of {@code parent} - * @return found files - */ - public static List findRelativeNestedJavaFiles(File parent, String... dirNames) { - File[] dirs = new File[dirNames.length]; - - int i = 0; - for (String dirName : dirNames) { - dirs[i] = new File(parent, dirName); - ++i; + /** True if the JVM is version 9 or above. */ + public static final boolean IS_AT_LEAST_9_JVM = SystemUtil.jreVersion >= 9; + + /** True if the JVM is version 10 or above. */ + public static final boolean IS_AT_LEAST_10_JVM = SystemUtil.jreVersion >= 10; + + /** True if the JVM is version 11 or above. */ + public static final boolean IS_AT_LEAST_11_JVM = SystemUtil.jreVersion >= 11; + + /** True if the JVM is version 11 or lower. */ + public static final boolean IS_AT_MOST_11_JVM = SystemUtil.jreVersion <= 11; + + /** True if the JVM is version 14 or above. */ + public static final boolean IS_AT_LEAST_14_JVM = SystemUtil.jreVersion >= 14; + + /** True if the JVM is version 14 or lower. */ + public static final boolean IS_AT_MOST_14_JVM = SystemUtil.jreVersion <= 14; + + /** True if the JVM is version 16 or above. */ + public static final boolean IS_AT_LEAST_16_JVM = SystemUtil.jreVersion >= 16; + + /** True if the JVM is version 16 or lower. */ + public static final boolean IS_AT_MOST_16_JVM = SystemUtil.jreVersion <= 16; + + /** True if the JVM is version 17 or above. */ + public static final boolean IS_AT_LEAST_17_JVM = SystemUtil.jreVersion >= 17; + + /** True if the JVM is version 17 or lower. */ + public static final boolean IS_AT_MOST_17_JVM = SystemUtil.jreVersion <= 17; + + /** True if the JVM is version 18 or above. */ + public static final boolean IS_AT_LEAST_18_JVM = SystemUtil.jreVersion >= 18; + + /** True if the JVM is version 18 or lower. */ + public static final boolean IS_AT_MOST_18_JVM = SystemUtil.jreVersion <= 18; + + /** True if the JVM is version 21 or above. */ + public static final boolean IS_AT_LEAST_21_JVM = SystemUtil.jreVersion >= 21; + + /** + * Find test java sources within currentDir/tests. + * + * @param dirNames subdirectories of currentDir/tests + * @return found files + */ + public static List findNestedJavaTestFiles(String... dirNames) { + return findRelativeNestedJavaFiles(new File("tests"), dirNames); } - return getJavaFilesAsArgumentList(dirs); - } - - /** - * Returns a list where each item is a list of Java files, excluding any skip tests, for each - * directory given by dirName and also a list for any subdirectory. - * - * @param parent parent directory of the dirNames directories - * @param dirNames names of directories to search - * @return list where each item is a list of Java test files grouped by directory - */ - public static List> findJavaFilesPerDirectory(File parent, String... dirNames) { - if (!parent.exists()) { - throw new BugInCF( - "test parent directory does not exist: %s %s", parent, parent.getAbsoluteFile()); + /** + * Find test java sources within {@code parent}. + * + * @param parent directory to search within + * @param dirNames subdirectories of {@code parent} + * @return found files + */ + public static List findRelativeNestedJavaFiles(String parent, String... dirNames) { + return findRelativeNestedJavaFiles(new File(parent), dirNames); } - if (!parent.isDirectory()) { - throw new BugInCF( - "test parent directory is not a directory: %s %s", parent, parent.getAbsoluteFile()); + + /** + * Find test java sources within {@code parent}. + * + * @param parent directory to search within + * @param dirNames subdirectories of {@code parent} + * @return found files + */ + public static List findRelativeNestedJavaFiles(File parent, String... dirNames) { + File[] dirs = new File[dirNames.length]; + + int i = 0; + for (String dirName : dirNames) { + dirs[i] = new File(parent, dirName); + ++i; + } + + return getJavaFilesAsArgumentList(dirs); } - List> filesPerDirectory = new ArrayList<>(); - - for (String dirName : dirNames) { - File dir = new File(parent, dirName).toPath().toAbsolutePath().normalize().toFile(); - if (dir.isDirectory()) { - filesPerDirectory.addAll(findJavaTestFilesInDirectory(dir)); - } else { - // `dir` is not an existing directory. - // If delombok does not yet work on a given JDK, this directory does not exist. - if (dir.getName().contains("delomboked")) { - continue; + + /** + * Returns a list where each item is a list of Java files, excluding any skip tests, for each + * directory given by dirName and also a list for any subdirectory. + * + * @param parent parent directory of the dirNames directories + * @param dirNames names of directories to search + * @return list where each item is a list of Java test files grouped by directory + */ + public static List> findJavaFilesPerDirectory(File parent, String... dirNames) { + if (!parent.exists()) { + throw new BugInCF( + "test parent directory does not exist: %s %s", + parent, parent.getAbsoluteFile()); } - // For "ainfer-*" tests, their sources do not necessarily - // exist yet but will be created by a test that runs earlier than they do. - if (dir.getName().equals("annotated") - && dir.getParentFile() != null - && dir.getParentFile().getName().startsWith("ainfer-")) { - continue; + if (!parent.isDirectory()) { + throw new BugInCF( + "test parent directory is not a directory: %s %s", + parent, parent.getAbsoluteFile()); } - // When this reaches a sym-linked dir like all-system, Windows needs to explicitly - // read the content recorded in this file, which is the path to the real dir. - // Without this check Windows will treat the file as a meaningless one and skip it. - if (dir.isFile()) { - File p = dir; - try (BufferedReader br = Files.newBufferedReader(dir.toPath(), StandardCharsets.UTF_8)) { - String allSystemPath = br.readLine(); - if (allSystemPath == null) { - throw new BugInCF("test directory does not exist: %s", dir); + List> filesPerDirectory = new ArrayList<>(); + + for (String dirName : dirNames) { + File dir = new File(parent, dirName).toPath().toAbsolutePath().normalize().toFile(); + if (dir.isDirectory()) { + filesPerDirectory.addAll(findJavaTestFilesInDirectory(dir)); + } else { + // `dir` is not an existing directory. + // If delombok does not yet work on a given JDK, this directory does not exist. + if (dir.getName().contains("delomboked")) { + continue; + } + // For "ainfer-*" tests, their sources do not necessarily + // exist yet but will be created by a test that runs earlier than they do. + if (dir.getName().equals("annotated") + && dir.getParentFile() != null + && dir.getParentFile().getName().startsWith("ainfer-")) { + continue; + } + // When this reaches a sym-linked dir like all-system, Windows needs to explicitly + // read the content recorded in this file, which is the path to the real dir. + // Without this check Windows will treat the file as a meaningless one and skip it. + if (dir.isFile()) { + File p = dir; + try (BufferedReader br = + Files.newBufferedReader(dir.toPath(), StandardCharsets.UTF_8)) { + String allSystemPath = br.readLine(); + if (allSystemPath == null) { + throw new BugInCF("test directory does not exist: %s", dir); + } + p = + new File(parent, allSystemPath.replace("/", File.separator)) + .toPath() + .toAbsolutePath() + .normalize() + .toFile(); + + } catch (IOException e) { + throw new BugInCF("file is not readable: %s", dir); + } + filesPerDirectory.addAll(findJavaTestFilesInDirectory(p)); + continue; + } + + throw new BugInCF("test directory does not exist: %s", dir); } - p = - new File(parent, allSystemPath.replace("/", File.separator)) - .toPath() - .toAbsolutePath() - .normalize() - .toFile(); - - } catch (IOException e) { - throw new BugInCF("file is not readable: %s", dir); - } - filesPerDirectory.addAll(findJavaTestFilesInDirectory(p)); - continue; } - throw new BugInCF("test directory does not exist: %s", dir); - } + return filesPerDirectory; } - return filesPerDirectory; - } - - /** - * Returns a list where each item is a list of Java files, excluding any skip tests. There is one - * list for {@code dir}, and one list for each subdirectory of {@code dir}. - * - * @param dir directory in which to search for Java files - * @return a list of list of Java test files - */ - private static List> findJavaTestFilesInDirectory(File dir) { - List> fileGroupedByDirectory = new ArrayList<>(); - List filesInDir = new ArrayList<>(); - - fileGroupedByDirectory.add(filesInDir); - String[] dirContents = dir.list(); - if (dirContents == null) { - throw new Error("Not a directory: " + dir); - } - Arrays.sort(dirContents); - for (String fileName : dirContents) { - File file = new File(dir, fileName); - if (file.isDirectory()) { - fileGroupedByDirectory.addAll(findJavaTestFilesInDirectory(file)); - } else if (isJavaTestFile(file)) { - filesInDir.add(file); - } - } - if (filesInDir.isEmpty()) { - fileGroupedByDirectory.remove(filesInDir); + /** + * Returns a list where each item is a list of Java files, excluding any skip tests. There is + * one list for {@code dir}, and one list for each subdirectory of {@code dir}. + * + * @param dir directory in which to search for Java files + * @return a list of list of Java test files + */ + private static List> findJavaTestFilesInDirectory(File dir) { + List> fileGroupedByDirectory = new ArrayList<>(); + List filesInDir = new ArrayList<>(); + + fileGroupedByDirectory.add(filesInDir); + String[] dirContents = dir.list(); + if (dirContents == null) { + throw new Error("Not a directory: " + dir); + } + Arrays.sort(dirContents); + for (String fileName : dirContents) { + File file = new File(dir, fileName); + if (file.isDirectory()) { + fileGroupedByDirectory.addAll(findJavaTestFilesInDirectory(file)); + } else if (isJavaTestFile(file)) { + filesInDir.add(file); + } + } + if (filesInDir.isEmpty()) { + fileGroupedByDirectory.remove(filesInDir); + } + return fileGroupedByDirectory; } - return fileGroupedByDirectory; - } - - /** - * Prepends a file to the beginning of each filename. - * - * @param parent a file to prepend to each filename - * @param fileNames file names - * @return the file names, each with {@code parent} prepended - */ - public static List findFilesInParent(File parent, String... fileNames) { - return CollectionsPlume.mapList( - (String fileName) -> new Object[] {new File(parent, fileName)}, fileNames); - } - - /** - * Traverses the directories listed looking for Java test files. - * - * @param dirs directories in which to search for Java test files - * @return a list of Java test files found in the directories - */ - public static List getJavaFilesAsArgumentList(File... dirs) { - List arguments = new ArrayList<>(); - for (File dir : dirs) { - arguments.addAll(deeplyEnclosedJavaTestFiles(dir)); + + /** + * Prepends a file to the beginning of each filename. + * + * @param parent a file to prepend to each filename + * @param fileNames file names + * @return the file names, each with {@code parent} prepended + */ + public static List findFilesInParent(File parent, String... fileNames) { + return CollectionsPlume.mapList( + (String fileName) -> new Object[] {new File(parent, fileName)}, fileNames); } - return arguments; - } - - /** - * Returns all the Java files that are descendants of the given directory. - * - * @param directory a directory - * @return all the Java files that are descendants of the given directory - */ - public static List deeplyEnclosedJavaTestFiles(File directory) { - if (!directory.exists()) { - throw new IllegalArgumentException( - "directory does not exist: " + directory + " " + directory.getAbsolutePath()); + + /** + * Traverses the directories listed looking for Java test files. + * + * @param dirs directories in which to search for Java test files + * @return a list of Java test files found in the directories + */ + public static List getJavaFilesAsArgumentList(File... dirs) { + List arguments = new ArrayList<>(); + for (File dir : dirs) { + arguments.addAll(deeplyEnclosedJavaTestFiles(dir)); + } + return arguments; } - if (!directory.isDirectory()) { - throw new IllegalArgumentException("found file instead of directory: " + directory); + + /** + * Returns all the Java files that are descendants of the given directory. + * + * @param directory a directory + * @return all the Java files that are descendants of the given directory + */ + public static List deeplyEnclosedJavaTestFiles(File directory) { + if (!directory.exists()) { + throw new IllegalArgumentException( + "directory does not exist: " + directory + " " + directory.getAbsolutePath()); + } + if (!directory.isDirectory()) { + throw new IllegalArgumentException("found file instead of directory: " + directory); + } + + List javaFiles = new ArrayList<>(); + + @SuppressWarnings("nullness") // checked above that it's a directory + File @NonNull [] in = directory.listFiles(); + Arrays.sort(in, Comparator.comparing(File::getName)); + for (File file : in) { + if (file.isDirectory()) { + javaFiles.addAll(deeplyEnclosedJavaTestFiles(file)); + } else if (isJavaTestFile(file)) { + javaFiles.add(file); + } + } + + return javaFiles; } - List javaFiles = new ArrayList<>(); - - @SuppressWarnings("nullness") // checked above that it's a directory - File @NonNull [] in = directory.listFiles(); - Arrays.sort(in, Comparator.comparing(File::getName)); - for (File file : in) { - if (file.isDirectory()) { - javaFiles.addAll(deeplyEnclosedJavaTestFiles(file)); - } else if (isJavaTestFile(file)) { - javaFiles.add(file); - } + public static boolean isJavaFile(File file) { + return file.isFile() && file.getName().endsWith(".java"); } - return javaFiles; - } + public static boolean isJavaTestFile(File file) { + if (!isJavaFile(file)) { + return false; + } - public static boolean isJavaFile(File file) { - return file.isFile() && file.getName().endsWith(".java"); - } + try (Scanner in = new Scanner(file, StandardCharsets.UTF_8)) { + while (in.hasNext()) { + String nextLine = in.nextLine(); + if (nextLine.contains("@skip-test") + || (!IS_AT_LEAST_9_JVM && nextLine.contains("@below-java9-jdk-skip-test")) + || (!IS_AT_LEAST_10_JVM && nextLine.contains("@below-java10-jdk-skip-test")) + || (!IS_AT_LEAST_11_JVM && nextLine.contains("@below-java11-jdk-skip-test")) + || (!IS_AT_MOST_11_JVM && nextLine.contains("@above-java11-jdk-skip-test")) + || (!IS_AT_LEAST_14_JVM && nextLine.contains("@below-java14-jdk-skip-test")) + || (!IS_AT_MOST_14_JVM && nextLine.contains("@above-java14-jdk-skip-test")) + || (!IS_AT_LEAST_16_JVM && nextLine.contains("@below-java16-jdk-skip-test")) + || (!IS_AT_MOST_16_JVM && nextLine.contains("@above-java16-jdk-skip-test")) + || (!IS_AT_LEAST_17_JVM && nextLine.contains("@below-java17-jdk-skip-test")) + || (!IS_AT_MOST_17_JVM && nextLine.contains("@above-java17-jdk-skip-test")) + || (!IS_AT_LEAST_18_JVM && nextLine.contains("@below-java18-jdk-skip-test")) + || (!IS_AT_MOST_18_JVM && nextLine.contains("@above-java18-jdk-skip-test")) + || (!IS_AT_LEAST_21_JVM + && nextLine.contains("@below-java21-jdk-skip-test"))) { + return false; + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } - public static boolean isJavaTestFile(File file) { - if (!isJavaFile(file)) { - return false; + return true; } - try (Scanner in = new Scanner(file, StandardCharsets.UTF_8)) { - while (in.hasNext()) { - String nextLine = in.nextLine(); - if (nextLine.contains("@skip-test") - || (!IS_AT_LEAST_9_JVM && nextLine.contains("@below-java9-jdk-skip-test")) - || (!IS_AT_LEAST_10_JVM && nextLine.contains("@below-java10-jdk-skip-test")) - || (!IS_AT_LEAST_11_JVM && nextLine.contains("@below-java11-jdk-skip-test")) - || (!IS_AT_MOST_11_JVM && nextLine.contains("@above-java11-jdk-skip-test")) - || (!IS_AT_LEAST_14_JVM && nextLine.contains("@below-java14-jdk-skip-test")) - || (!IS_AT_MOST_14_JVM && nextLine.contains("@above-java14-jdk-skip-test")) - || (!IS_AT_LEAST_16_JVM && nextLine.contains("@below-java16-jdk-skip-test")) - || (!IS_AT_MOST_16_JVM && nextLine.contains("@above-java16-jdk-skip-test")) - || (!IS_AT_LEAST_17_JVM && nextLine.contains("@below-java17-jdk-skip-test")) - || (!IS_AT_MOST_17_JVM && nextLine.contains("@above-java17-jdk-skip-test")) - || (!IS_AT_LEAST_18_JVM && nextLine.contains("@below-java18-jdk-skip-test")) - || (!IS_AT_MOST_18_JVM && nextLine.contains("@above-java18-jdk-skip-test")) - || (!IS_AT_LEAST_21_JVM && nextLine.contains("@below-java21-jdk-skip-test"))) { - return false; + /** + * Convert a compiler diagnostic to a string. + * + * @param diagnostic the compiler diagnostic + * @param usingAnomsgtxt whether message text should be excluded or not + * @return the compiler message string or null for certain lint warnings + */ + public static @Nullable String diagnosticToString( + Diagnostic diagnostic, boolean usingAnomsgtxt) { + String result = diagnostic.toString().trim(); + + // suppress Xlint warnings + if (result.contains("uses unchecked or unsafe operations.") + || result.contains("Recompile with -Xlint:unchecked for details.") + || result.endsWith(" declares unsafe vararg methods.") + || result.contains("Recompile with -Xlint:varargs for details.")) { + return null; } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return true; - } - - /** - * Convert a compiler diagnostic to a string. - * - * @param diagnostic the compiler diagnostic - * @param usingAnomsgtxt whether message text should be excluded or not - * @return the compiler message string or null for certain lint warnings - */ - public static @Nullable String diagnosticToString( - Diagnostic diagnostic, boolean usingAnomsgtxt) { - String result = diagnostic.toString().trim(); - - // suppress Xlint warnings - if (result.contains("uses unchecked or unsafe operations.") - || result.contains("Recompile with -Xlint:unchecked for details.") - || result.endsWith(" declares unsafe vararg methods.") - || result.contains("Recompile with -Xlint:varargs for details.")) { - return null; + if (usingAnomsgtxt) { + // Lines with "unexpected Throwable" are stack traces + // and should be printed in full. + if (!result.contains("unexpected Throwable")) { + String firstLine; + int lineSepPos = result.indexOf(System.lineSeparator()); + if (lineSepPos != -1) { + firstLine = result.substring(0, lineSepPos); + } else { + firstLine = result; + } + int javaPos = firstLine.indexOf(".java:"); + if (javaPos != -1) { + firstLine = firstLine.substring(javaPos + 5).trim(); + } + result = firstLine; + } + } + + return result; } - if (usingAnomsgtxt) { - // Lines with "unexpected Throwable" are stack traces - // and should be printed in full. - if (!result.contains("unexpected Throwable")) { - String firstLine; - int lineSepPos = result.indexOf(System.lineSeparator()); - if (lineSepPos != -1) { - firstLine = result.substring(0, lineSepPos); - } else { - firstLine = result; + public static Set diagnosticsToStrings( + Iterable> actualDiagnostics, + boolean usingAnomsgtxt) { + Set actualDiagnosticsStr = new LinkedHashSet<>(); + for (Diagnostic diagnostic : actualDiagnostics) { + String diagnosticStr = TestUtilities.diagnosticToString(diagnostic, usingAnomsgtxt); + if (diagnosticStr != null) { + actualDiagnosticsStr.add(diagnosticStr); + } } - int javaPos = firstLine.indexOf(".java:"); - if (javaPos != -1) { - firstLine = firstLine.substring(javaPos + 5).trim(); + + return actualDiagnosticsStr; + } + + /** + * Return the file absolute pathnames, separated by commas. + * + * @param javaFiles a list of Java files + * @return the file absolute pathnames, separated by commas + */ + public static String summarizeSourceFiles(List javaFiles) { + StringJoiner sj = new StringJoiner(", "); + for (File file : javaFiles) { + sj.add(file.getAbsolutePath()); } - result = firstLine; - } + return sj.toString(); } - return result; - } - - public static Set diagnosticsToStrings( - Iterable> actualDiagnostics, boolean usingAnomsgtxt) { - Set actualDiagnosticsStr = new LinkedHashSet<>(); - for (Diagnostic diagnostic : actualDiagnostics) { - String diagnosticStr = TestUtilities.diagnosticToString(diagnostic, usingAnomsgtxt); - if (diagnosticStr != null) { - actualDiagnosticsStr.add(diagnosticStr); - } + public static File getTestFile(String fileRelativeToTestsDir) { + return new File("tests", fileRelativeToTestsDir); } - return actualDiagnosticsStr; - } - - /** - * Return the file absolute pathnames, separated by commas. - * - * @param javaFiles a list of Java files - * @return the file absolute pathnames, separated by commas - */ - public static String summarizeSourceFiles(List javaFiles) { - StringJoiner sj = new StringJoiner(", "); - for (File file : javaFiles) { - sj.add(file.getAbsolutePath()); + public static File findComparisonFile(File testFile) { + File comparisonFile = + new File(testFile.getParent(), testFile.getName().replace(".java", ".out")); + return comparisonFile; } - return sj.toString(); - } - - public static File getTestFile(String fileRelativeToTestsDir) { - return new File("tests", fileRelativeToTestsDir); - } - - public static File findComparisonFile(File testFile) { - File comparisonFile = - new File(testFile.getParent(), testFile.getName().replace(".java", ".out")); - return comparisonFile; - } - - /** - * Given an option map, return a list of option names. - * - * @param options an option map - * @return return a list of option names - */ - public static List optionMapToList(Map options) { - List optionList = new ArrayList<>(options.size() * 2); - - for (Map.Entry optEntry : options.entrySet()) { - optionList.add(optEntry.getKey()); - - if (optEntry.getValue() != null) { - optionList.add(optEntry.getValue()); - } + + /** + * Given an option map, return a list of option names. + * + * @param options an option map + * @return return a list of option names + */ + public static List optionMapToList(Map options) { + List optionList = new ArrayList<>(options.size() * 2); + + for (Map.Entry optEntry : options.entrySet()) { + optionList.add(optEntry.getKey()); + + if (optEntry.getValue() != null) { + optionList.add(optEntry.getValue()); + } + } + + return optionList; } - return optionList; - } - - /** - * Write all the lines in the given Iterable to the given File. - * - * @param file where to write the lines - * @param lines what lines to write - */ - public static void writeLines(File file, Iterable lines) { - try (BufferedWriter bw = - Files.newBufferedWriter( - file.toPath(), - StandardCharsets.UTF_8, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND)) { - Iterator iter = lines.iterator(); - while (iter.hasNext()) { - Object next = iter.next(); - if (next == null) { - bw.write(""); - } else { - bw.write(next.toString()); + /** + * Write all the lines in the given Iterable to the given File. + * + * @param file where to write the lines + * @param lines what lines to write + */ + public static void writeLines(File file, Iterable lines) { + try (BufferedWriter bw = + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND)) { + Iterator iter = lines.iterator(); + while (iter.hasNext()) { + Object next = iter.next(); + if (next == null) { + bw.write(""); + } else { + bw.write(next.toString()); + } + bw.newLine(); + } + bw.flush(); + } catch (IOException io) { + throw new RuntimeException(io); + } + } + + public static void writeDiagnostics( + File file, + File testFile, + List expected, + List actual, + List unexpected, + List missing, + boolean usingNoMsgText, + boolean testFailed) { + try (PrintWriter pw = + new PrintWriter( + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND))) { + pw.println("File: " + testFile.getAbsolutePath()); + pw.println("TestFailed: " + testFailed); + pw.println("Using nomsgtxt: " + usingNoMsgText); + pw.println("#Missing: " + missing.size() + " #Unexpected: " + unexpected.size()); + + pw.println("Expected:"); + pw.println(StringsPlume.joinLines(expected)); + pw.println(); + + pw.println("Actual:"); + pw.println(StringsPlume.joinLines(actual)); + pw.println(); + + pw.println("Missing:"); + pw.println(StringsPlume.joinLines(missing)); + pw.println(); + + pw.println("Unexpected:"); + pw.println(StringsPlume.joinLines(unexpected)); + pw.println(); + + pw.println(); + pw.println(); + pw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); } - bw.newLine(); - } - bw.flush(); - } catch (IOException io) { - throw new RuntimeException(io); } - } - - public static void writeDiagnostics( - File file, - File testFile, - List expected, - List actual, - List unexpected, - List missing, - boolean usingNoMsgText, - boolean testFailed) { - try (PrintWriter pw = - new PrintWriter( - Files.newBufferedWriter( - file.toPath(), - StandardCharsets.UTF_8, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND))) { - pw.println("File: " + testFile.getAbsolutePath()); - pw.println("TestFailed: " + testFailed); - pw.println("Using nomsgtxt: " + usingNoMsgText); - pw.println("#Missing: " + missing.size() + " #Unexpected: " + unexpected.size()); - - pw.println("Expected:"); - pw.println(StringsPlume.joinLines(expected)); - pw.println(); - - pw.println("Actual:"); - pw.println(StringsPlume.joinLines(actual)); - pw.println(); - - pw.println("Missing:"); - pw.println(StringsPlume.joinLines(missing)); - pw.println(); - - pw.println("Unexpected:"); - pw.println(StringsPlume.joinLines(unexpected)); - pw.println(); - - pw.println(); - pw.println(); - pw.flush(); - } catch (IOException e) { - throw new RuntimeException(e); + + /** + * Append a test configuration to the end of a file. + * + * @param file the file to write to + * @param config the configuration to append to the end of the file + */ + public static void writeTestConfiguration(File file, TestConfiguration config) { + try (BufferedWriter bw = + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND)) { + bw.write(config.toString()); + bw.newLine(); + bw.newLine(); + bw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void writeJavacArguments( + File file, + Iterable files, + Iterable options, + Iterable processors) { + try (PrintWriter pw = + new PrintWriter( + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND))) { + pw.println("Files:"); + for (JavaFileObject f : files) { + pw.println(" " + f.getName()); + } + pw.println(); + + pw.println("Options:"); + for (String o : options) { + pw.println(" " + o); + } + pw.println(); + + pw.println("Processors:"); + for (String p : processors) { + pw.println(" " + p); + } + pw.println(); + pw.println(); + + pw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } } - } - - /** - * Append a test configuration to the end of a file. - * - * @param file the file to write to - * @param config the configuration to append to the end of the file - */ - public static void writeTestConfiguration(File file, TestConfiguration config) { - try (BufferedWriter bw = - Files.newBufferedWriter( - file.toPath(), - StandardCharsets.UTF_8, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND)) { - bw.write(config.toString()); - bw.newLine(); - bw.newLine(); - bw.flush(); - } catch (IOException e) { - throw new RuntimeException(e); + + /** + * If the given TypecheckResult has unexpected or missing diagnostics, fail the running JUnit + * test. + * + * @param testResult the result of type-checking + */ + public static void assertTestDidNotFail(TypecheckResult testResult) { + if (testResult.didTestFail()) { + if (getShouldEmitDebugInfo()) { + System.out.println("---------------- start of javac ouput ----------------"); + System.out.println(testResult.getCompilationResult().getJavacOutput()); + System.out.println("---------------- end of javac ouput ----------------"); + } else { + System.out.println( + "To see the javac command line and output, run with: -Pemit.test.debug"); + } + Assert.fail(testResult.summarize()); + } } - } - - public static void writeJavacArguments( - File file, - Iterable files, - Iterable options, - Iterable processors) { - try (PrintWriter pw = - new PrintWriter( - Files.newBufferedWriter( - file.toPath(), - StandardCharsets.UTF_8, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND))) { - pw.println("Files:"); - for (JavaFileObject f : files) { - pw.println(" " + f.getName()); - } - pw.println(); - - pw.println("Options:"); - for (String o : options) { - pw.println(" " + o); - } - pw.println(); - - pw.println("Processors:"); - for (String p : processors) { - pw.println(" " + p); - } - pw.println(); - pw.println(); - - pw.flush(); - } catch (IOException e) { - throw new RuntimeException(e); + + /** + * Create the directory (and its parents) if it does not exist. + * + * @param dir the directory to create + */ + public static void ensureDirectoryExists(String dir) { + try { + Files.createDirectories(Paths.get(dir)); + } catch (FileAlreadyExistsException e) { + // directory already exists + } catch (IOException e) { + throw new RuntimeException("Could not make directory: " + dir + ": " + e.getMessage()); + } } - } - - /** - * If the given TypecheckResult has unexpected or missing diagnostics, fail the running JUnit - * test. - * - * @param testResult the result of type-checking - */ - public static void assertTestDidNotFail(TypecheckResult testResult) { - if (testResult.didTestFail()) { - if (getShouldEmitDebugInfo()) { - System.out.println("---------------- start of javac ouput ----------------"); - System.out.println(testResult.getCompilationResult().getJavacOutput()); - System.out.println("---------------- end of javac ouput ----------------"); - } else { - System.out.println("To see the javac command line and output, run with: -Pemit.test.debug"); - } - Assert.fail(testResult.summarize()); + + /** + * Returns the value of system property "emit.test.debug". + * + * @return the value of system property "emit.test.debug" + */ + public static boolean getShouldEmitDebugInfo() { + return SystemPlume.getBooleanSystemProperty("emit.test.debug"); } - } - - /** - * Create the directory (and its parents) if it does not exist. - * - * @param dir the directory to create - */ - public static void ensureDirectoryExists(String dir) { - try { - Files.createDirectories(Paths.get(dir)); - } catch (FileAlreadyExistsException e) { - // directory already exists - } catch (IOException e) { - throw new RuntimeException("Could not make directory: " + dir + ": " + e.getMessage()); + + /** + * Adapt a string that uses Unix file and path separators to use the correct operating system + * separator. + * + * @param input a path with Unix file and path separators + * @return a path with the correct operating system separator + */ + public static String adapt(String input) { + return input.replace("/", File.separator).replace(":", File.pathSeparator); } - } - - /** - * Returns the value of system property "emit.test.debug". - * - * @return the value of system property "emit.test.debug" - */ - public static boolean getShouldEmitDebugInfo() { - return SystemPlume.getBooleanSystemProperty("emit.test.debug"); - } - - /** - * Adapt a string that uses Unix file and path separators to use the correct operating system - * separator. - * - * @param input a path with Unix file and path separators - * @return a path with the correct operating system separator - */ - public static String adapt(String input) { - return input.replace("/", File.separator).replace(":", File.pathSeparator); - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java index d8fff5c6663..01a3786ee0d 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java @@ -1,129 +1,144 @@ package org.checkerframework.framework.test; +import org.checkerframework.framework.test.diagnostics.JavaDiagnosticReader; +import org.checkerframework.framework.test.diagnostics.TestDiagnostic; +import org.plumelib.util.StringsPlume; + import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; + import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; -import org.checkerframework.framework.test.diagnostics.JavaDiagnosticReader; -import org.checkerframework.framework.test.diagnostics.TestDiagnostic; -import org.plumelib.util.StringsPlume; /** Used by the Checker Framework test suite to run the framework and generate a test result. */ public class TypecheckExecutor { - /** Creates a new TypecheckExecutor. */ - public TypecheckExecutor() {} - - /** - * Runs a typechecking test using the given configuration and returns the test result. - * - * @param configuration the test configuration - * @return the test result - */ - public TypecheckResult runTest(TestConfiguration configuration) { - try { - CompilationResult result = compile(configuration); - return interpretResults(configuration, result); - } catch (OutOfMemoryError e) { - String message = - String.format( - "Max memory = %d, total memory = %d, free memory = %d.", - Runtime.getRuntime().maxMemory(), - Runtime.getRuntime().totalMemory(), - Runtime.getRuntime().freeMemory()); - System.out.println(message); - System.err.println(message); - throw new Error(message, e); - } - } - - /** - * Using the settings from the input configuration, compile all source files in the configuration, - * and return the result in a CompilationResult. - */ - public CompilationResult compile(TestConfiguration configuration) { - String dOption = configuration.getOptions().get("-d"); - if (dOption == null) { - throw new Error("-d not supplied"); + /** Creates a new TypecheckExecutor. */ + public TypecheckExecutor() {} + + /** + * Runs a typechecking test using the given configuration and returns the test result. + * + * @param configuration the test configuration + * @return the test result + */ + public TypecheckResult runTest(TestConfiguration configuration) { + try { + CompilationResult result = compile(configuration); + return interpretResults(configuration, result); + } catch (OutOfMemoryError e) { + String message = + String.format( + "Max memory = %d, total memory = %d, free memory = %d.", + Runtime.getRuntime().maxMemory(), + Runtime.getRuntime().totalMemory(), + Runtime.getRuntime().freeMemory()); + System.out.println(message); + System.err.println(message); + throw new Error(message, e); + } } - TestUtilities.ensureDirectoryExists(dOption); - - StringWriter javacOutput = new StringWriter(); - DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { - Iterable javaFiles = - fileManager.getJavaFileObjects(configuration.getTestSourceFiles().toArray(new File[] {})); - - // Even though the method compiler.getTask takes a list of processors, it fails if - // processors are passed this way with the message: - // error: Class names, 'org.checkerframework.checker.interning.InterningChecker', are - // only accepted if annotation processing is explicitly requested - // Therefore, we now add them to the beginning of the options list. - List options = new ArrayList<>(); - options.add("-processor"); - options.add(String.join(",", configuration.getProcessors())); - options.addAll(configuration.getFlatOptions()); - - if (configuration.shouldEmitDebugInfo()) { - System.out.println("Running test using the following invocation:"); - System.out.println( - "javac " - + String.join(" ", options) - + " " - + StringsPlume.join(" ", configuration.getTestSourceFiles())); - } - - JavaCompiler.CompilationTask task = - compiler.getTask( - javacOutput, fileManager, diagnostics, options, new ArrayList(), javaFiles); - - /* - * In Eclipse, std out and std err for multiple tests appear as one - * long stream. When selecting a specific failed test, one sees the - * expected/unexpected messages, but not the std out/err messages from - * that particular test. Can we improve this somehow? - */ - Boolean compiledWithoutError = task.call(); - javacOutput.flush(); - return new CompilationResult( - compiledWithoutError, javacOutput.toString(), javaFiles, diagnostics.getDiagnostics()); - } catch (IOException e) { - throw new Error(e); + + /** + * Using the settings from the input configuration, compile all source files in the + * configuration, and return the result in a CompilationResult. + */ + public CompilationResult compile(TestConfiguration configuration) { + String dOption = configuration.getOptions().get("-d"); + if (dOption == null) { + throw new Error("-d not supplied"); + } + TestUtilities.ensureDirectoryExists(dOption); + + StringWriter javacOutput = new StringWriter(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fileManager = + compiler.getStandardFileManager(null, null, null)) { + Iterable javaFiles = + fileManager.getJavaFileObjects( + configuration.getTestSourceFiles().toArray(new File[] {})); + + // Even though the method compiler.getTask takes a list of processors, it fails if + // processors are passed this way with the message: + // error: Class names, 'org.checkerframework.checker.interning.InterningChecker', are + // only accepted if annotation processing is explicitly requested + // Therefore, we now add them to the beginning of the options list. + List options = new ArrayList<>(); + options.add("-processor"); + options.add(String.join(",", configuration.getProcessors())); + options.addAll(configuration.getFlatOptions()); + + if (configuration.shouldEmitDebugInfo()) { + System.out.println("Running test using the following invocation:"); + System.out.println( + "javac " + + String.join(" ", options) + + " " + + StringsPlume.join(" ", configuration.getTestSourceFiles())); + } + + JavaCompiler.CompilationTask task = + compiler.getTask( + javacOutput, + fileManager, + diagnostics, + options, + new ArrayList(), + javaFiles); + + /* + * In Eclipse, std out and std err for multiple tests appear as one + * long stream. When selecting a specific failed test, one sees the + * expected/unexpected messages, but not the std out/err messages from + * that particular test. Can we improve this somehow? + */ + Boolean compiledWithoutError = task.call(); + javacOutput.flush(); + return new CompilationResult( + compiledWithoutError, + javacOutput.toString(), + javaFiles, + diagnostics.getDiagnostics()); + } catch (IOException e) { + throw new Error(e); + } } - } - - /** - * Reads the expected diagnostics for the given configuration and creates a TypecheckResult which - * contains all of the missing and expected diagnostics. - */ - public TypecheckResult interpretResults( - TestConfiguration config, CompilationResult compilationResult) { - List expectedDiagnostics = readDiagnostics(config, compilationResult); - return TypecheckResult.fromCompilationResults(config, compilationResult, expectedDiagnostics); - } - - /** - * A subclass can override this to filter out errors or add new expected errors. This method is - * called immediately before results are checked. - */ - protected List readDiagnostics( - TestConfiguration config, CompilationResult compilationResult) { - List expectedDiagnostics; - if (config.getDiagnosticFiles() == null || config.getDiagnosticFiles().isEmpty()) { - expectedDiagnostics = - JavaDiagnosticReader.readJavaSourceFiles(compilationResult.getJavaFileObjects()); - } else { - expectedDiagnostics = JavaDiagnosticReader.readDiagnosticFiles(config.getDiagnosticFiles()); + + /** + * Reads the expected diagnostics for the given configuration and creates a TypecheckResult + * which contains all of the missing and expected diagnostics. + */ + public TypecheckResult interpretResults( + TestConfiguration config, CompilationResult compilationResult) { + List expectedDiagnostics = readDiagnostics(config, compilationResult); + return TypecheckResult.fromCompilationResults( + config, compilationResult, expectedDiagnostics); } - return expectedDiagnostics; - } + /** + * A subclass can override this to filter out errors or add new expected errors. This method is + * called immediately before results are checked. + */ + protected List readDiagnostics( + TestConfiguration config, CompilationResult compilationResult) { + List expectedDiagnostics; + if (config.getDiagnosticFiles() == null || config.getDiagnosticFiles().isEmpty()) { + expectedDiagnostics = + JavaDiagnosticReader.readJavaSourceFiles( + compilationResult.getJavaFileObjects()); + } else { + expectedDiagnostics = + JavaDiagnosticReader.readDiagnosticFiles(config.getDiagnosticFiles()); + } + + return expectedDiagnostics; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java index 1b4cd2c16e6..d9fe028896c 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java @@ -1,157 +1,161 @@ package org.checkerframework.framework.test; +import org.checkerframework.framework.test.diagnostics.TestDiagnostic; +import org.checkerframework.framework.test.diagnostics.TestDiagnosticUtils; +import org.plumelib.util.StringsPlume; + import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.StringJoiner; + import javax.tools.Diagnostic; import javax.tools.JavaFileObject; -import org.checkerframework.framework.test.diagnostics.TestDiagnostic; -import org.checkerframework.framework.test.diagnostics.TestDiagnosticUtils; -import org.plumelib.util.StringsPlume; /** * Represents the test results from typechecking one or more Java files using the given * TestConfiguration. */ public class TypecheckResult { - private final TestConfiguration configuration; - private final CompilationResult compilationResult; - private final List expectedDiagnostics; - - private final List missingDiagnostics; - private final List unexpectedDiagnostics; - - protected TypecheckResult( - TestConfiguration configuration, - CompilationResult compilationResult, - List expectedDiagnostics, - List missingDiagnostics, - List unexpectedDiagnostics) { - this.configuration = configuration; - this.compilationResult = compilationResult; - this.expectedDiagnostics = expectedDiagnostics; - this.missingDiagnostics = missingDiagnostics; - this.unexpectedDiagnostics = unexpectedDiagnostics; - } - - public TestConfiguration getConfiguration() { - return configuration; - } - - public CompilationResult getCompilationResult() { - return compilationResult; - } - - public List> getActualDiagnostics() { - return compilationResult.getDiagnostics(); - } - - public List getExpectedDiagnostics() { - return expectedDiagnostics; - } - - public boolean didTestFail() { - return !unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty(); - } - - public List getMissingDiagnostics() { - return missingDiagnostics; - } - - public List getUnexpectedDiagnostics() { - return unexpectedDiagnostics; - } - - public List getErrorHeaders() { - List errorHeaders = new ArrayList<>(); - - // none of these should be true if the test didn't fail - if (didTestFail()) { - if (compilationResult.compiledWithoutError() && !expectedDiagnostics.isEmpty()) { - errorHeaders.add("The test run was expected to issue errors/warnings, but it did not."); - - } else if (!compilationResult.compiledWithoutError() && expectedDiagnostics.isEmpty()) { - errorHeaders.add("The test run was not expected to issue errors/warnings, but it did."); - } - - if (!unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty()) { - int numExpected = expectedDiagnostics.size(); - int numFound = numExpected - missingDiagnostics.size(); - errorHeaders.add( - numFound - + " out of " - + StringsPlume.nplural(numExpected, "expected diagnostic") - + " " - + (numFound == 1 ? "was" : "were") - + " found."); - } + private final TestConfiguration configuration; + private final CompilationResult compilationResult; + private final List expectedDiagnostics; + + private final List missingDiagnostics; + private final List unexpectedDiagnostics; + + protected TypecheckResult( + TestConfiguration configuration, + CompilationResult compilationResult, + List expectedDiagnostics, + List missingDiagnostics, + List unexpectedDiagnostics) { + this.configuration = configuration; + this.compilationResult = compilationResult; + this.expectedDiagnostics = expectedDiagnostics; + this.missingDiagnostics = missingDiagnostics; + this.unexpectedDiagnostics = unexpectedDiagnostics; + } + + public TestConfiguration getConfiguration() { + return configuration; } - return errorHeaders; - } - - /** - * Summarize unexpected and missing diagnostics. - * - * @return summary of failures - */ - public String summarize() { - if (!didTestFail()) { - return ""; + public CompilationResult getCompilationResult() { + return compilationResult; } - StringJoiner summaryBuilder = new StringJoiner(System.lineSeparator()); - summaryBuilder.add(StringsPlume.joinLines(getErrorHeaders())); - - if (!unexpectedDiagnostics.isEmpty()) { - int numUnexpected = unexpectedDiagnostics.size(); - if (numUnexpected == 1) { - summaryBuilder.add("1 unexpected diagnostic was found:"); - } else { - summaryBuilder.add(numUnexpected + " unexpected diagnostics were found:"); - } - - for (TestDiagnostic unexpected : unexpectedDiagnostics) { - summaryBuilder.add(" " + unexpected.toString()); - } + + public List> getActualDiagnostics() { + return compilationResult.getDiagnostics(); } - if (!missingDiagnostics.isEmpty()) { - int numMissing = missingDiagnostics.size(); - summaryBuilder.add( - StringsPlume.nplural(numMissing, "expected diagnostic") - + " " - + (numMissing == 1 ? "was" : "were") - + " not found:"); - - for (TestDiagnostic missing : missingDiagnostics) { - summaryBuilder.add(" " + missing.toString()); - } + public List getExpectedDiagnostics() { + return expectedDiagnostics; + } + + public boolean didTestFail() { + return !unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty(); + } + + public List getMissingDiagnostics() { + return missingDiagnostics; + } + + public List getUnexpectedDiagnostics() { + return unexpectedDiagnostics; + } + + public List getErrorHeaders() { + List errorHeaders = new ArrayList<>(); + + // none of these should be true if the test didn't fail + if (didTestFail()) { + if (compilationResult.compiledWithoutError() && !expectedDiagnostics.isEmpty()) { + errorHeaders.add( + "The test run was expected to issue errors/warnings, but it did not."); + + } else if (!compilationResult.compiledWithoutError() && expectedDiagnostics.isEmpty()) { + errorHeaders.add( + "The test run was not expected to issue errors/warnings, but it did."); + } + + if (!unexpectedDiagnostics.isEmpty() || !missingDiagnostics.isEmpty()) { + int numExpected = expectedDiagnostics.size(); + int numFound = numExpected - missingDiagnostics.size(); + errorHeaders.add( + numFound + + " out of " + + StringsPlume.nplural(numExpected, "expected diagnostic") + + " " + + (numFound == 1 ? "was" : "were") + + " found."); + } + } + + return errorHeaders; + } + + /** + * Summarize unexpected and missing diagnostics. + * + * @return summary of failures + */ + public String summarize() { + if (!didTestFail()) { + return ""; + } + StringJoiner summaryBuilder = new StringJoiner(System.lineSeparator()); + summaryBuilder.add(StringsPlume.joinLines(getErrorHeaders())); + + if (!unexpectedDiagnostics.isEmpty()) { + int numUnexpected = unexpectedDiagnostics.size(); + if (numUnexpected == 1) { + summaryBuilder.add("1 unexpected diagnostic was found:"); + } else { + summaryBuilder.add(numUnexpected + " unexpected diagnostics were found:"); + } + + for (TestDiagnostic unexpected : unexpectedDiagnostics) { + summaryBuilder.add(" " + unexpected.toString()); + } + } + + if (!missingDiagnostics.isEmpty()) { + int numMissing = missingDiagnostics.size(); + summaryBuilder.add( + StringsPlume.nplural(numMissing, "expected diagnostic") + + " " + + (numMissing == 1 ? "was" : "were") + + " not found:"); + + for (TestDiagnostic missing : missingDiagnostics) { + summaryBuilder.add(" " + missing.toString()); + } + } + return summaryBuilder.toString(); + } + + public static TypecheckResult fromCompilationResults( + TestConfiguration configuration, + CompilationResult result, + List expectedDiagnostics) { + + Set actualDiagnostics = + TestDiagnosticUtils.fromJavaxToolsDiagnosticList(result.getDiagnostics()); + + Set unexpectedDiagnostics = new LinkedHashSet<>(); + unexpectedDiagnostics.addAll(actualDiagnostics); + unexpectedDiagnostics.removeAll(expectedDiagnostics); + + List missingDiagnostics = new ArrayList<>(expectedDiagnostics); + missingDiagnostics.removeAll(actualDiagnostics); + + return new TypecheckResult( + configuration, + result, + expectedDiagnostics, + missingDiagnostics, + new ArrayList<>(unexpectedDiagnostics)); } - return summaryBuilder.toString(); - } - - public static TypecheckResult fromCompilationResults( - TestConfiguration configuration, - CompilationResult result, - List expectedDiagnostics) { - - Set actualDiagnostics = - TestDiagnosticUtils.fromJavaxToolsDiagnosticList(result.getDiagnostics()); - - Set unexpectedDiagnostics = new LinkedHashSet<>(); - unexpectedDiagnostics.addAll(actualDiagnostics); - unexpectedDiagnostics.removeAll(expectedDiagnostics); - - List missingDiagnostics = new ArrayList<>(expectedDiagnostics); - missingDiagnostics.removeAll(actualDiagnostics); - - return new TypecheckResult( - configuration, - result, - expectedDiagnostics, - missingDiagnostics, - new ArrayList<>(unexpectedDiagnostics)); - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java index 3edadf3d72d..9acd3405fda 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.test.diagnostics; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.StringJoiner; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents a detailed error/warning message reported by the Checker Framework when the {@code @@ -14,119 +15,120 @@ */ public class DetailedTestDiagnostic extends TestDiagnostic { - /** Additional tokens that are part of the diagnostic message. */ - protected final List additionalTokens; - - /** The start position of the diagnostic in the source file. */ - protected final long startPosition; - - /** The end position of the diagnostic in the source file. */ - protected final long endPosition; - - /** - * Create a new instance. - * - * @param file the file in which the diagnostic occurred - * @param lineNo the line number in the file at which the diagnostic occurred - * @param kind the kind of diagnostic (error or warning) - * @param messageKey a message key that usually appears between parentheses in diagnostic messages - * @param additionalTokens additional tokens that are part of the diagnostic message - * @param startPosition the start position of the diagnostic in the source file - * @param endPosition the end position of the diagnostic in the source file - * @param readableMessage a human-readable message describing the diagnostic - * @param isFixable whether the diagnostic is fixable - */ - public DetailedTestDiagnostic( - Path file, - long lineNo, - DiagnosticKind kind, - String messageKey, - List additionalTokens, - long startPosition, - long endPosition, - String readableMessage, - boolean isFixable) { - super(file, lineNo, kind, messageKey, readableMessage, isFixable); - - this.additionalTokens = additionalTokens; - this.startPosition = startPosition; - this.endPosition = endPosition; - } - - /** - * The additional tokens that are part of the diagnostic message. - * - * @return the additional tokens - */ - public List getAdditionalTokens() { - return additionalTokens; - } - - /** - * The start position of the diagnostic in the source file. - * - * @return the start position - */ - public long getStartPosition() { - return startPosition; - } - - /** - * The end position of the diagnostic in the source file. - * - * @return the end position - */ - public long getEndPosition() { - return endPosition; - } - - /** - * Equality is compared without isFixable and messageKeyParens. - * - * @return true if this and otherObj are equal according to additionalTokens, startPosition, - * endPosition, and equality of the superclass. - */ - @Override - public boolean equals(@Nullable Object otherObj) { - if (!(otherObj instanceof DetailedTestDiagnostic)) { - return false; + /** Additional tokens that are part of the diagnostic message. */ + protected final List additionalTokens; + + /** The start position of the diagnostic in the source file. */ + protected final long startPosition; + + /** The end position of the diagnostic in the source file. */ + protected final long endPosition; + + /** + * Create a new instance. + * + * @param file the file in which the diagnostic occurred + * @param lineNo the line number in the file at which the diagnostic occurred + * @param kind the kind of diagnostic (error or warning) + * @param messageKey a message key that usually appears between parentheses in diagnostic + * messages + * @param additionalTokens additional tokens that are part of the diagnostic message + * @param startPosition the start position of the diagnostic in the source file + * @param endPosition the end position of the diagnostic in the source file + * @param readableMessage a human-readable message describing the diagnostic + * @param isFixable whether the diagnostic is fixable + */ + public DetailedTestDiagnostic( + Path file, + long lineNo, + DiagnosticKind kind, + String messageKey, + List additionalTokens, + long startPosition, + long endPosition, + String readableMessage, + boolean isFixable) { + super(file, lineNo, kind, messageKey, readableMessage, isFixable); + + this.additionalTokens = additionalTokens; + this.startPosition = startPosition; + this.endPosition = endPosition; } - DetailedTestDiagnostic other = (DetailedTestDiagnostic) otherObj; - return super.equals(other) - && additionalTokens.equals(other.additionalTokens) - && startPosition == other.startPosition - && endPosition == other.endPosition; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), additionalTokens, startPosition, endPosition); - } - - /** - * Returns a representation of this diagnostic as if it appeared as a detailed message. - * - * @return a representation of this diagnostic as if it appeared as a detailed message - * @see - * org.checkerframework.framework.source.SourceChecker#detailedMsgTextPrefix(Object,String,Object[]) - */ - @Override - public String toString() { - // Keep in sync with SourceChecker.DETAILS_SEPARATOR. - StringJoiner sj = new StringJoiner(" $$ "); - - sj.add("(" + messageKey + ")"); - if (additionalTokens != null) { - sj.add(Integer.toString(additionalTokens.size())); - for (String token : additionalTokens) { - sj.add(token); - } - } else { - sj.add("0"); + + /** + * The additional tokens that are part of the diagnostic message. + * + * @return the additional tokens + */ + public List getAdditionalTokens() { + return additionalTokens; + } + + /** + * The start position of the diagnostic in the source file. + * + * @return the start position + */ + public long getStartPosition() { + return startPosition; } - sj.add(String.format("( %d, %d )", startPosition, endPosition)); - sj.add(getMessage()); - return sj.toString(); - } + /** + * The end position of the diagnostic in the source file. + * + * @return the end position + */ + public long getEndPosition() { + return endPosition; + } + + /** + * Equality is compared without isFixable and messageKeyParens. + * + * @return true if this and otherObj are equal according to additionalTokens, startPosition, + * endPosition, and equality of the superclass. + */ + @Override + public boolean equals(@Nullable Object otherObj) { + if (!(otherObj instanceof DetailedTestDiagnostic)) { + return false; + } + DetailedTestDiagnostic other = (DetailedTestDiagnostic) otherObj; + return super.equals(other) + && additionalTokens.equals(other.additionalTokens) + && startPosition == other.startPosition + && endPosition == other.endPosition; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), additionalTokens, startPosition, endPosition); + } + + /** + * Returns a representation of this diagnostic as if it appeared as a detailed message. + * + * @return a representation of this diagnostic as if it appeared as a detailed message + * @see + * org.checkerframework.framework.source.SourceChecker#detailedMsgTextPrefix(Object,String,Object[]) + */ + @Override + public String toString() { + // Keep in sync with SourceChecker.DETAILS_SEPARATOR. + StringJoiner sj = new StringJoiner(" $$ "); + + sj.add("(" + messageKey + ")"); + if (additionalTokens != null) { + sj.add(Integer.toString(additionalTokens.size())); + for (String token : additionalTokens) { + sj.add(token); + } + } else { + sj.add("0"); + } + + sj.add(String.format("( %d, %d )", startPosition, endPosition)); + sj.add(getMessage()); + return sj.toString(); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java index e6ef75348d5..2e914f98a26 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java @@ -1,37 +1,40 @@ package org.checkerframework.framework.test.diagnostics; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.LinkedHashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; /** The kinds of errors that can be encountered during typechecking. */ public enum DiagnosticKind { - /** A warning. */ - Warning("warning"), - /** An error. */ - Error("error"), - /** A note. */ - Note("Note"), - /** Something else. */ - Other("other"); - - /** How this DiagnosticKind appears in error messages or source code. */ - public final String parseString; - - DiagnosticKind(String parseString) { - this.parseString = parseString; - } - - private static final Map stringToCategory = new LinkedHashMap<>(); - - static { - for (DiagnosticKind cat : values()) { - stringToCategory.put(cat.parseString, cat); + /** A warning. */ + Warning("warning"), + /** An error. */ + Error("error"), + /** A note. */ + Note("Note"), + /** Something else. */ + Other("other"); + + /** How this DiagnosticKind appears in error messages or source code. */ + public final String parseString; + + DiagnosticKind(String parseString) { + this.parseString = parseString; } - } - /** Convert a string as it would appear in error messages or source code into a DiagnosticKind. */ - public static @Nullable DiagnosticKind fromParseString(String parseStr) { - return stringToCategory.get(parseStr); - } + private static final Map stringToCategory = new LinkedHashMap<>(); + + static { + for (DiagnosticKind cat : values()) { + stringToCategory.put(cat.parseString, cat); + } + } + + /** + * Convert a string as it would appear in error messages or source code into a DiagnosticKind. + */ + public static @Nullable DiagnosticKind fromParseString(String parseStr) { + return stringToCategory.get(parseStr); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java index 75efeb437d7..3dfc4e162a9 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java @@ -1,5 +1,13 @@ package org.checkerframework.framework.test.diagnostics; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.index.qual.GTENegativeOne; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.mustcall.qual.Owning; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.dataflow.qual.Pure; + import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -10,14 +18,8 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; + import javax.tools.JavaFileObject; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.index.qual.GTENegativeOne; -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.checkerframework.checker.mustcall.qual.Owning; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.checkerframework.dataflow.qual.Pure; /** * This class reads expected javac diagnostics from a single file. Its implementation is as an @@ -27,255 +29,262 @@ */ public class JavaDiagnosticReader implements Iterator, Closeable { - /// - /// This class begins with the public static methods that clients use to read diagnostics. - /// + /// + /// This class begins with the public static methods that clients use to read diagnostics. + /// - /** - * Returns all the diagnostics in any of the Java source files. - * - * @param files the Java files to read; each is a File or a JavaFileObject - * @return the TestDiagnostics from the input file - */ - // The argument has type Iterable because Java cannot resolve the overload - // of two versions that take Iterable and Iterable. - public static List readJavaSourceFiles(Iterable files) { - List result = new ArrayList<>(); - for (Object file : files) { - if (file instanceof JavaFileObject) { - try (JavaDiagnosticReader reader = - new JavaDiagnosticReader( - (JavaFileObject) file, TestDiagnosticUtils::fromJavaSourceLine)) { - readDiagnostics(result, reader); - } - } else if (file instanceof File) { - try (JavaDiagnosticReader reader = - new JavaDiagnosticReader((File) file, TestDiagnosticUtils::fromJavaSourceLine)) { - readDiagnostics(result, reader); + /** + * Returns all the diagnostics in any of the Java source files. + * + * @param files the Java files to read; each is a File or a JavaFileObject + * @return the TestDiagnostics from the input file + */ + // The argument has type Iterable because Java cannot resolve the overload + // of two versions that take Iterable and Iterable. + public static List readJavaSourceFiles(Iterable files) { + List result = new ArrayList<>(); + for (Object file : files) { + if (file instanceof JavaFileObject) { + try (JavaDiagnosticReader reader = + new JavaDiagnosticReader( + (JavaFileObject) file, TestDiagnosticUtils::fromJavaSourceLine)) { + readDiagnostics(result, reader); + } + } else if (file instanceof File) { + try (JavaDiagnosticReader reader = + new JavaDiagnosticReader( + (File) file, TestDiagnosticUtils::fromJavaSourceLine)) { + readDiagnostics(result, reader); + } + } else { + throw new IllegalArgumentException( + String.format( + "Elements of argument should be File or JavaFileObject, not %s: %s", + file.getClass(), file)); + } } - } else { - throw new IllegalArgumentException( - String.format( - "Elements of argument should be File or JavaFileObject, not %s: %s", - file.getClass(), file)); - } + return result; } - return result; - } - /** - * Reads diagnostics line-by-line from the input diagnostic files. - * - * @param files a set of diagnostic files - * @return the TestDiagnosticLines from the input files - */ - public static List readDiagnosticFiles(Iterable files) { - List result = new ArrayList<>(); - for (File file : files) { - try (JavaDiagnosticReader reader = - new JavaDiagnosticReader( - file, - (filename, line, lineNumber) -> TestDiagnosticUtils.fromDiagnosticFileLine(line))) { - readDiagnostics(result, reader); - } + /** + * Reads diagnostics line-by-line from the input diagnostic files. + * + * @param files a set of diagnostic files + * @return the TestDiagnosticLines from the input files + */ + public static List readDiagnosticFiles(Iterable files) { + List result = new ArrayList<>(); + for (File file : files) { + try (JavaDiagnosticReader reader = + new JavaDiagnosticReader( + file, + (filename, line, lineNumber) -> + TestDiagnosticUtils.fromDiagnosticFileLine(line))) { + readDiagnostics(result, reader); + } + } + return result; } - return result; - } - - /// - /// End of public static methods, start of private static methods. - /// - /** - * Reads all the diagnostics in the file. - * - * @param list where to put the diagnostics - * @param reader the file (Java or Diagnostics format) to read - */ - private static void readDiagnostics(List list, JavaDiagnosticReader reader) { - diagnosticLinesToDiagnostics(list, readDiagnosticLines(reader)); - } + /// + /// End of public static methods, start of private static methods. + /// - /** - * Reads the entire input file using the given codec and returns the resulting lines, filtering - * out empty ones produced by JavaDiagnosticReader. - * - * @param reader the file (Java or Diagnostics format) to read - * @return the List of TestDiagnosticLines from the input file - */ - private static List readDiagnosticLines(JavaDiagnosticReader reader) { - List diagnosticLines = new ArrayList<>(); - while (reader.hasNext()) { - TestDiagnosticLine line = reader.next(); - // A JavaDiagnosticReader can return a lot of empty diagnostics. Filter them out. - if (line.hasDiagnostics()) { - diagnosticLines.add(line); - } + /** + * Reads all the diagnostics in the file. + * + * @param list where to put the diagnostics + * @param reader the file (Java or Diagnostics format) to read + */ + private static void readDiagnostics(List list, JavaDiagnosticReader reader) { + diagnosticLinesToDiagnostics(list, readDiagnosticLines(reader)); } - return diagnosticLines; - } - /** - * Converts a list of TestDiagnosticLine into a list of TestDiagnostic. - * - * @param list where to put the result TestDiagnostics - * @param lines the TestDiagnosticLines - */ - private static void diagnosticLinesToDiagnostics( - List list, List lines) { - for (TestDiagnosticLine line : lines) { - list.addAll(line.getDiagnostics()); + /** + * Reads the entire input file using the given codec and returns the resulting lines, filtering + * out empty ones produced by JavaDiagnosticReader. + * + * @param reader the file (Java or Diagnostics format) to read + * @return the List of TestDiagnosticLines from the input file + */ + private static List readDiagnosticLines(JavaDiagnosticReader reader) { + List diagnosticLines = new ArrayList<>(); + while (reader.hasNext()) { + TestDiagnosticLine line = reader.next(); + // A JavaDiagnosticReader can return a lot of empty diagnostics. Filter them out. + if (line.hasDiagnostics()) { + diagnosticLines.add(line); + } + } + return diagnosticLines; } - } - /** - * StringToTestDiagnosticLine converts a line of a file into a TestDiagnosticLine. There are - * currently two possible formats: one for Java source code, and one for Diagnostic files. - * - *

          No classes implement this interface. The methods TestDiagnosticUtils.fromJavaSourceLine and - * TestDiagnosticUtils.fromDiagnosticFileLine instantiate the method. - */ - private interface StringToTestDiagnosticLine { + /** + * Converts a list of TestDiagnosticLine into a list of TestDiagnostic. + * + * @param list where to put the result TestDiagnostics + * @param lines the TestDiagnosticLines + */ + private static void diagnosticLinesToDiagnostics( + List list, List lines) { + for (TestDiagnosticLine line : lines) { + list.addAll(line.getDiagnostics()); + } + } /** - * Converts the specified line of the file into a {@link TestDiagnosticLine}. + * StringToTestDiagnosticLine converts a line of a file into a TestDiagnosticLine. There are + * currently two possible formats: one for Java source code, and one for Diagnostic files. * - * @param filename name of the file - * @param line the text of the line to convert to a TestDiagnosticLine - * @param lineNumber the line number of the line - * @return TestDiagnosticLine corresponding to {@code line} + *

          No classes implement this interface. The methods TestDiagnosticUtils.fromJavaSourceLine + * and TestDiagnosticUtils.fromDiagnosticFileLine instantiate the method. */ - TestDiagnosticLine createTestDiagnosticLine(String filename, String line, long lineNumber); - } + private interface StringToTestDiagnosticLine { + + /** + * Converts the specified line of the file into a {@link TestDiagnosticLine}. + * + * @param filename name of the file + * @param line the text of the line to convert to a TestDiagnosticLine + * @param lineNumber the line number of the line + * @return TestDiagnosticLine corresponding to {@code line} + */ + TestDiagnosticLine createTestDiagnosticLine(String filename, String line, long lineNumber); + } - /// - /// End of static methods, start of per-instance state. - /// + /// + /// End of static methods, start of per-instance state. + /// - /** Converts a file line into a TestDiagnosticLine. */ - private final StringToTestDiagnosticLine codec; + /** Converts a file line into a TestDiagnosticLine. */ + private final StringToTestDiagnosticLine codec; - /** The file name. */ - private final String filename; + /** The file name. */ + private final String filename; - /** The reader for the file. */ - private final @Owning LineNumberReader reader; + /** The reader for the file. */ + private final @Owning LineNumberReader reader; - /** The next line to be read, or null. */ - private @Nullable String nextLine = null; + /** The next line to be read, or null. */ + private @Nullable String nextLine = null; - /** The line number of the next line to be read, or -1. */ - private @GTENegativeOne int nextLineNumber = -1; + /** The line number of the next line to be read, or -1. */ + private @GTENegativeOne int nextLineNumber = -1; - /** - * Creates a JavaDiagnosticReader. - * - * @param toRead the file to read - * @param codec converts a file line into a TestDiagnosticLine - */ - private JavaDiagnosticReader(File toRead, StringToTestDiagnosticLine codec) { - this.codec = codec; - this.filename = toRead.getName(); - LineNumberReader reader = null; - try { - reader = - new LineNumberReader(Files.newBufferedReader(toRead.toPath(), StandardCharsets.UTF_8)); - this.reader = reader; - advance(); - } catch (IOException e) { - if (reader != null) { + /** + * Creates a JavaDiagnosticReader. + * + * @param toRead the file to read + * @param codec converts a file line into a TestDiagnosticLine + */ + private JavaDiagnosticReader(File toRead, StringToTestDiagnosticLine codec) { + this.codec = codec; + this.filename = toRead.getName(); + LineNumberReader reader = null; try { - reader.close(); - } catch (Exception exceptionOnClose) { - e.addSuppressed(exceptionOnClose); + reader = + new LineNumberReader( + Files.newBufferedReader(toRead.toPath(), StandardCharsets.UTF_8)); + this.reader = reader; + advance(); + } catch (IOException e) { + if (reader != null) { + try { + reader.close(); + } catch (Exception exceptionOnClose) { + e.addSuppressed(exceptionOnClose); + } + } + throw new RuntimeException(e); } - } - throw new RuntimeException(e); } - } - /** - * Creates a JavaDiagnosticReader. - * - * @param toReadFileObject the file to read - * @param codec converts a file line into a TestDiagnosticLine - */ - private JavaDiagnosticReader(JavaFileObject toReadFileObject, StringToTestDiagnosticLine codec) { - this.codec = codec; - this.filename = new File(toReadFileObject.getName()).getName(); - LineNumberReader reader = null; - try { - reader = new LineNumberReader(toReadFileObject.openReader(true)); - this.reader = reader; - advance(); - } catch (IOException e) { - if (reader != null) { + /** + * Creates a JavaDiagnosticReader. + * + * @param toReadFileObject the file to read + * @param codec converts a file line into a TestDiagnosticLine + */ + private JavaDiagnosticReader( + JavaFileObject toReadFileObject, StringToTestDiagnosticLine codec) { + this.codec = codec; + this.filename = new File(toReadFileObject.getName()).getName(); + LineNumberReader reader = null; try { - reader.close(); - } catch (Exception exceptionOnClose) { - e.addSuppressed(exceptionOnClose); + reader = new LineNumberReader(toReadFileObject.openReader(true)); + this.reader = reader; + advance(); + } catch (IOException e) { + if (reader != null) { + try { + reader.close(); + } catch (Exception exceptionOnClose) { + e.addSuppressed(exceptionOnClose); + } + } + throw new RuntimeException(e); } - } - throw new RuntimeException(e); } - } - - @Override - @Pure - public boolean hasNext() { - return nextLine != null; - } - @Override - public void remove() { - throw new UnsupportedOperationException( - "Cannot remove elements using JavaDiagnosticFileReader."); - } + @Override + @Pure + public boolean hasNext() { + return nextLine != null; + } - @Override - public TestDiagnosticLine next() { - if (nextLine == null) { - throw new NoSuchElementException(); + @Override + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove elements using JavaDiagnosticFileReader."); } - String currentLine = nextLine; - int currentLineNumber = nextLineNumber; + @Override + public TestDiagnosticLine next() { + if (nextLine == null) { + throw new NoSuchElementException(); + } - try { - advance(); + String currentLine = nextLine; + int currentLineNumber = nextLineNumber; - currentLine = TestDiagnosticUtils.handleEndOfLineJavaDiagnostic(currentLine); + try { + advance(); - if (TestDiagnosticUtils.isJavaDiagnosticLineStart(currentLine)) { - while (TestDiagnosticUtils.isJavaDiagnosticLineContinuation(nextLine)) { - currentLine = currentLine.trim() + " " + TestDiagnosticUtils.continuationPart(nextLine); - currentLineNumber = nextLineNumber; - advance(); + currentLine = TestDiagnosticUtils.handleEndOfLineJavaDiagnostic(currentLine); + + if (TestDiagnosticUtils.isJavaDiagnosticLineStart(currentLine)) { + while (TestDiagnosticUtils.isJavaDiagnosticLineContinuation(nextLine)) { + currentLine = + currentLine.trim() + + " " + + TestDiagnosticUtils.continuationPart(nextLine); + currentLineNumber = nextLineNumber; + advance(); + } + } + } catch (IOException e) { + throw new RuntimeException(e); } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return codec.createTestDiagnosticLine(filename, currentLine, currentLineNumber); - } + return codec.createTestDiagnosticLine(filename, currentLine, currentLineNumber); + } - @RequiresNonNull("reader") - protected void advance(@UnknownInitialization JavaDiagnosticReader this) throws IOException { - nextLine = reader.readLine(); - nextLineNumber = reader.getLineNumber(); - if (nextLine == null) { - reader.close(); + @RequiresNonNull("reader") + protected void advance(@UnknownInitialization JavaDiagnosticReader this) throws IOException { + nextLine = reader.readLine(); + nextLineNumber = reader.getLineNumber(); + if (nextLine == null) { + reader.close(); + } } - } - @Override - @EnsuresCalledMethods(value = "reader", methods = "close") - public void close() { - try { - reader.close(); - } catch (IOException e) { - throw new Error(e); + @Override + @EnsuresCalledMethods(value = "reader", methods = "close") + public void close() { + try { + reader.close(); + } catch (IOException e) { + throw new Error(e); + } } - } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java index b7432202a18..9e961dbe8ab 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.test.diagnostics; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.nio.file.Path; import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents an expected error/warning message in a Java test file or an error/warning reported by @@ -16,243 +17,247 @@ */ public class TestDiagnostic { - /** The path to the test file. */ - protected final Path file; + /** The path to the test file. */ + protected final Path file; - /** The base file name of the test file. */ - protected final String filename; + /** The base file name of the test file. */ + protected final String filename; - /** The line number of the diagnostic output. */ - protected final long lineNumber; + /** The line number of the diagnostic output. */ + protected final long lineNumber; - /** The diagnostic kind of the output. */ - protected final DiagnosticKind kind; + /** The diagnostic kind of the output. */ + protected final DiagnosticKind kind; - /** The full diagnostic message. */ - protected final String message; + /** The full diagnostic message. */ + protected final String message; - /** - * The message key that usually appears between parentheses in diagnostic messages. Parentheses - * are removed and field messageKeyParens indicates whether they were present. - */ - protected final String messageKey; + /** + * The message key that usually appears between parentheses in diagnostic messages. Parentheses + * are removed and field messageKeyParens indicates whether they were present. + */ + protected final String messageKey; - /** Whether the message key had parentheses around it. */ - protected final boolean messageKeyParens; + /** Whether the message key had parentheses around it. */ + protected final boolean messageKeyParens; - /** Whether this diagnostic should no longer be reported after whole program inference. */ - protected final boolean isFixable; + /** Whether this diagnostic should no longer be reported after whole program inference. */ + protected final boolean isFixable; - /** - * Basic constructor that sets the immutable fields of this diagnostic. - * - * @param file the path to the test file - * @param lineNumber the line number of the diagnostic output - * @param kind the diagnostic kind of the output - * @param messageKey the message key - * @param message the full diagnostic message - * @param isFixable whether WPI can fix the test - */ - public TestDiagnostic( - Path file, - long lineNumber, - DiagnosticKind kind, - String messageKey, - String message, - boolean isFixable) { - this.file = file; - this.filename = file.getFileName() != null ? file.getFileName().toString() : file.toString(); - this.lineNumber = lineNumber; - this.kind = kind; - this.message = message; - this.isFixable = isFixable; + /** + * Basic constructor that sets the immutable fields of this diagnostic. + * + * @param file the path to the test file + * @param lineNumber the line number of the diagnostic output + * @param kind the diagnostic kind of the output + * @param messageKey the message key + * @param message the full diagnostic message + * @param isFixable whether WPI can fix the test + */ + public TestDiagnostic( + Path file, + long lineNumber, + DiagnosticKind kind, + String messageKey, + String message, + boolean isFixable) { + this.file = file; + this.filename = + file.getFileName() != null ? file.getFileName().toString() : file.toString(); + this.lineNumber = lineNumber; + this.kind = kind; + this.message = message; + this.isFixable = isFixable; - // Keep in sync with code below. - int open = messageKey.indexOf("("); - int close = messageKey.indexOf(")"); - if (open == 0 && close > open) { - this.messageKey = messageKey.substring(open + 1, close).trim(); - this.messageKeyParens = true; - } else { - this.messageKey = messageKey; - this.messageKeyParens = false; + // Keep in sync with code below. + int open = messageKey.indexOf("("); + int close = messageKey.indexOf(")"); + if (open == 0 && close > open) { + this.messageKey = messageKey.substring(open + 1, close).trim(); + this.messageKeyParens = true; + } else { + this.messageKey = messageKey; + this.messageKeyParens = false; + } } - } - /** - * Basic constructor that sets the immutable fields of this diagnostic. - * - * @param file the path to the test file - * @param lineNumber the line number of the diagnostic output - * @param kind the diagnostic kind of the output - * @param message the full diagnostic message - * @param isFixable whether WPI can fix the test - */ - public TestDiagnostic( - Path file, long lineNumber, DiagnosticKind kind, String message, boolean isFixable) { - this.file = file; - this.filename = file.getFileName() != null ? file.getFileName().toString() : file.toString(); - this.lineNumber = lineNumber; - this.kind = kind; - this.message = message; - this.isFixable = isFixable; + /** + * Basic constructor that sets the immutable fields of this diagnostic. + * + * @param file the path to the test file + * @param lineNumber the line number of the diagnostic output + * @param kind the diagnostic kind of the output + * @param message the full diagnostic message + * @param isFixable whether WPI can fix the test + */ + public TestDiagnostic( + Path file, long lineNumber, DiagnosticKind kind, String message, boolean isFixable) { + this.file = file; + this.filename = + file.getFileName() != null ? file.getFileName().toString() : file.toString(); + this.lineNumber = lineNumber; + this.kind = kind; + this.message = message; + this.isFixable = isFixable; - if (keepFullMessage(message)) { - this.messageKey = message; - this.messageKeyParens = false; - } else { - String firstline; - // There might be a mismatch between the System.lineSeparator() and the diagnostic - // message, so manually check both options. - int lineSepPos = this.message.indexOf("\r\n"); - if (lineSepPos == -1) { - lineSepPos = this.message.indexOf("\n"); - } - if (lineSepPos != -1) { - firstline = this.message.substring(0, lineSepPos).trim(); - } else { - firstline = this.message; - } + if (keepFullMessage(message)) { + this.messageKey = message; + this.messageKeyParens = false; + } else { + String firstline; + // There might be a mismatch between the System.lineSeparator() and the diagnostic + // message, so manually check both options. + int lineSepPos = this.message.indexOf("\r\n"); + if (lineSepPos == -1) { + lineSepPos = this.message.indexOf("\n"); + } + if (lineSepPos != -1) { + firstline = this.message.substring(0, lineSepPos).trim(); + } else { + firstline = this.message; + } - // Keep in sync with code above. - int open = firstline.indexOf("("); - int close = firstline.indexOf(")"); - if (open == 0 && close > open) { - this.messageKey = firstline.substring(open + 1, close).trim(); - this.messageKeyParens = true; - } else { - this.messageKey = firstline; - this.messageKeyParens = false; - } + // Keep in sync with code above. + int open = firstline.indexOf("("); + int close = firstline.indexOf(")"); + if (open == 0 && close > open) { + this.messageKey = firstline.substring(open + 1, close).trim(); + this.messageKeyParens = true; + } else { + this.messageKey = firstline; + this.messageKeyParens = false; + } + } } - } - - /** - * Determine whether the full diagnostic message should be used as message key. This is useful to - * ensure e.g. stack traces are fully shown. - * - * @param message the full message - * @return whether the full diagnostic message should be used - */ - public static boolean keepFullMessage(String message) { - return message.contains("unexpected Throwable") - || message.contains("Compilation unit") - || message.contains("OutOfMemoryError"); - } - /** - * The path to the test file. - * - * @return the path to the test file - */ - public Path getFile() { - return file; - } + /** + * Determine whether the full diagnostic message should be used as message key. This is useful + * to ensure e.g. stack traces are fully shown. + * + * @param message the full message + * @return whether the full diagnostic message should be used + */ + public static boolean keepFullMessage(String message) { + return message.contains("unexpected Throwable") + || message.contains("Compilation unit") + || message.contains("OutOfMemoryError"); + } - /** - * The base file name of the test file. - * - * @return the base file name of the test file - */ - public String getFilename() { - return filename; - } + /** + * The path to the test file. + * + * @return the path to the test file + */ + public Path getFile() { + return file; + } - /** - * The line number of the diagnostic output. - * - * @return the line number of the diagnostic output - */ - public long getLineNumber() { - return lineNumber; - } + /** + * The base file name of the test file. + * + * @return the base file name of the test file + */ + public String getFilename() { + return filename; + } - /** - * The diagnostic kind of the output. - * - * @return the diagnostic kind of the output - */ - public DiagnosticKind getKind() { - return kind; - } + /** + * The line number of the diagnostic output. + * + * @return the line number of the diagnostic output + */ + public long getLineNumber() { + return lineNumber; + } - /** - * The message key, without surrounding parentheses. - * - * @return the message key - */ - public String getMessageKey() { - return messageKey; - } + /** + * The diagnostic kind of the output. + * + * @return the diagnostic kind of the output + */ + public DiagnosticKind getKind() { + return kind; + } - /** - * The full diagnostic message. - * - * @return the full diagnostic message - */ - public String getMessage() { - return message; - } + /** + * The message key, without surrounding parentheses. + * + * @return the message key + */ + public String getMessageKey() { + return messageKey; + } - /** - * Whether WPI can fix the test. - * - * @return whether WPI can fix the test - */ - public boolean isFixable() { - return isFixable; - } + /** + * The full diagnostic message. + * + * @return the full diagnostic message + */ + public String getMessage() { + return message; + } - /** - * Equality is compared based the file name, not the full path, on the messageKey, not the full - * message, and without considering isFixable and messageKeyParens. - * - * @return true if this and otherObj are equal according to file, lineNumber, kind, and messageKey - */ - @Override - public boolean equals(@Nullable Object otherObj) { - if (otherObj == null || otherObj.getClass() != TestDiagnostic.class) { - return false; + /** + * Whether WPI can fix the test. + * + * @return whether WPI can fix the test + */ + public boolean isFixable() { + return isFixable; } - TestDiagnostic other = (TestDiagnostic) otherObj; - return other.filename.equals(this.filename) - && other.lineNumber == lineNumber - && other.kind == this.kind - && other.messageKey.equals(this.messageKey); - } + /** + * Equality is compared based the file name, not the full path, on the messageKey, not the full + * message, and without considering isFixable and messageKeyParens. + * + * @return true if this and otherObj are equal according to file, lineNumber, kind, and + * messageKey + */ + @Override + public boolean equals(@Nullable Object otherObj) { + if (otherObj == null || otherObj.getClass() != TestDiagnostic.class) { + return false; + } - @Override - public int hashCode() { - // Only filename, not file, and only messageKey, not message, not isFixable, not - // messageKeyParens. - return Objects.hash(filename, lineNumber, kind, messageKey); - } + TestDiagnostic other = (TestDiagnostic) otherObj; + return other.filename.equals(this.filename) + && other.lineNumber == lineNumber + && other.kind == this.kind + && other.messageKey.equals(this.messageKey); + } - /** - * Returns a representation of this diagnostic as if it appeared in a diagnostics file. This uses - * only the base file name, not the full path, and only the message key, not the full message. - * Field {@link #messageKeyParens} influences whether the message key is output in parentheses. - * - * @return a representation of this diagnostic as if it appeared in a diagnostics file - */ - @Override - public String toString() { - if (messageKeyParens) { - return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + messageKey + ")"; - } else { - return filename + ":" + lineNumber + ": " + kind.parseString + ": " + messageKey; + @Override + public int hashCode() { + // Only filename, not file, and only messageKey, not message, not isFixable, not + // messageKeyParens. + return Objects.hash(filename, lineNumber, kind, messageKey); } - } - /** - * Returns the internal representation of this, formatted. - * - * @return the internal representation of this, formatted - */ - public String repr() { - return String.format( - "[TestDiagnostic: file=%s, lineNumber=%d, kind=%s, message=%s]", - file, lineNumber, kind, message); - } + /** + * Returns a representation of this diagnostic as if it appeared in a diagnostics file. This + * uses only the base file name, not the full path, and only the message key, not the full + * message. Field {@link #messageKeyParens} influences whether the message key is output in + * parentheses. + * + * @return a representation of this diagnostic as if it appeared in a diagnostics file + */ + @Override + public String toString() { + if (messageKeyParens) { + return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + messageKey + ")"; + } else { + return filename + ":" + lineNumber + ": " + kind.parseString + ": " + messageKey; + } + } + + /** + * Returns the internal representation of this, formatted. + * + * @return the internal representation of this, formatted + */ + public String repr() { + return String.format( + "[TestDiagnostic: file=%s, lineNumber=%d, kind=%s, message=%s]", + file, lineNumber, kind, message); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java index 7c12461db61..dee08978bbb 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticLine.java @@ -4,36 +4,39 @@ /** Represents a list of TestDiagnostics, which was read from a one line of a file. */ public class TestDiagnosticLine { - private final String filename; - private final long lineNumber; - private final String originalLine; - private final List diagnostics; - - public TestDiagnosticLine( - String filename, long lineNumber, String originalLine, List diagnostics) { - this.filename = filename; - this.lineNumber = lineNumber; - this.originalLine = originalLine; - this.diagnostics = diagnostics; - } - - public String getFilename() { - return filename; - } - - public boolean hasDiagnostics() { - return !diagnostics.isEmpty(); - } - - public long getLineNumber() { - return lineNumber; - } - - public String getOriginalLine() { - return originalLine; - } - - public List getDiagnostics() { - return diagnostics; - } + private final String filename; + private final long lineNumber; + private final String originalLine; + private final List diagnostics; + + public TestDiagnosticLine( + String filename, + long lineNumber, + String originalLine, + List diagnostics) { + this.filename = filename; + this.lineNumber = lineNumber; + this.originalLine = originalLine; + this.diagnostics = diagnostics; + } + + public String getFilename() { + return filename; + } + + public boolean hasDiagnostics() { + return !diagnostics.isEmpty(); + } + + public long getLineNumber() { + return lineNumber; + } + + public String getOriginalLine() { + return originalLine; + } + + public List getDiagnostics() { + return diagnostics; + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java index a132563cb44..6a476746fc0 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java @@ -1,5 +1,10 @@ package org.checkerframework.framework.test.diagnostics; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.IPair; + import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; @@ -9,433 +14,437 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; + import javax.tools.Diagnostic; import javax.tools.JavaFileObject; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.IPair; /** A set of utilities and factory methods useful for working with TestDiagnostics. */ public class TestDiagnosticUtils { - /** How the diagnostics appear in Java source files. */ - public static final String DIAGNOSTIC_IN_JAVA_REGEX = - "\\s*(?error|fixable-error|warning|fixable-warning|Note|other):\\s*(?[\\s\\S]*)"; - - /** Pattern compiled from {@link #DIAGNOSTIC_IN_JAVA_REGEX}. */ - public static final Pattern DIAGNOSTIC_IN_JAVA_PATTERN = - Pattern.compile(DIAGNOSTIC_IN_JAVA_REGEX); - - /** How the diagnostic warnings appear in Java source files. */ - public static final String DIAGNOSTIC_WARNING_IN_JAVA_REGEX = - "\\s*warning:\\s*(?[\\s\\S]*)"; - - /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_IN_JAVA_REGEX}. */ - public static final Pattern DIAGNOSTIC_WARNING_IN_JAVA_PATTERN = - Pattern.compile(DIAGNOSTIC_WARNING_IN_JAVA_REGEX); - - /** How the diagnostics appear in javax tools diagnostics from the compiler. */ - public static final String DIAGNOSTIC_REGEX = - "(?:(?\\d+):)?" + DIAGNOSTIC_IN_JAVA_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_REGEX}. */ - public static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile(DIAGNOSTIC_REGEX); - - /** How the diagnostic warnings appear in javax tools diagnostics from the compiler. */ - public static final String DIAGNOSTIC_WARNING_REGEX = - "(?:(?\\d+):)?" + DIAGNOSTIC_WARNING_IN_JAVA_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_REGEX}. */ - public static final Pattern DIAGNOSTIC_WARNING_PATTERN = - Pattern.compile(DIAGNOSTIC_WARNING_REGEX); - - /** How the diagnostics appear in diagnostic files (.out). */ - public static final String DIAGNOSTIC_FILE_REGEX = ".+\\.java" + DIAGNOSTIC_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_FILE_REGEX}. */ - public static final Pattern DIAGNOSTIC_FILE_PATTERN = Pattern.compile(DIAGNOSTIC_FILE_REGEX); - - /** How the diagnostic warnings appear in diagnostic files (.out). */ - public static final String DIAGNOSTIC_FILE_WARNING_REGEX = ".+\\.java" + DIAGNOSTIC_WARNING_REGEX; - - /** Pattern compiled from {@link #DIAGNOSTIC_FILE_WARNING_REGEX}. */ - public static final Pattern DIAGNOSTIC_FILE_WARNING_PATTERN = - Pattern.compile(DIAGNOSTIC_FILE_WARNING_REGEX); - - /** - * Instantiate the diagnostic based on a string that would appear in diagnostic files (i.e. files - * that only contain line after line of expected diagnostics). - * - * @param stringFromDiagnosticFile a single diagnostic string to parse - * @return a new TestDiagnostic - */ - public static TestDiagnostic fromDiagnosticFileString(String stringFromDiagnosticFile) { - return fromPatternMatching( - DIAGNOSTIC_FILE_PATTERN, - DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, - // Important to use "" to make input of expected warnings easy. - Paths.get(""), - null, - stringFromDiagnosticFile); - } - - /** - * Instantiate the diagnostic from a string that would appear in a Java file, e.g.: "error: - * (message)" - * - * @param filename the file containing the diagnostic (and the error) - * @param lineNumber the line number of the line immediately below the diagnostic comment in the - * Java file - * @param stringFromJavaFile the string containing the diagnostic - * @return a new TestDiagnostic - */ - public static TestDiagnostic fromJavaFileComment( - String filename, long lineNumber, String stringFromJavaFile) { - return fromPatternMatching( - DIAGNOSTIC_IN_JAVA_PATTERN, - DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, - Paths.get(filename), - lineNumber, - stringFromJavaFile); - } - - /** - * Instantiate a diagnostic from output produced by the Java compiler. The resulting diagnostic is - * never fixable and always has parentheses. - * - * @param diagnosticString the compiler diagnostics string - * @return the corresponding test diagnostic - */ - public static TestDiagnostic fromJavaxToolsDiagnostic(String diagnosticString) { - // It would be nice not to parse this from the diagnostic string. - // However, diagnostic.toString() may contain "[unchecked]" even though getMessage() does - // not. - // Since we want to match the error messages reported by javac exactly, we must parse. - // diagnostic.getCode() returns "compiler.warn.prob.found.req" for "[unchecked]" messages, - // but not clear how to map from one to the other. - IPair trimmed = formatJavaxToolString(diagnosticString); - return fromPatternMatching( - DIAGNOSTIC_PATTERN, DIAGNOSTIC_WARNING_PATTERN, trimmed.second, null, trimmed.first); - } - - /** - * Instantiate the diagnostic via pattern-matching against patterns. - * - * @param diagnosticPattern a pattern that matches any diagnostic - * @param warningPattern a pattern that matches a warning diagnostic - * @param file the test file - * @param lineNumber the line number - * @param diagnosticString the string to parse - * @return a diagnostic parsed from the given string - */ - @SuppressWarnings("nullness") // TODO: regular expression group access - protected static TestDiagnostic fromPatternMatching( - Pattern diagnosticPattern, - Pattern warningPattern, - Path file, - @Nullable Long lineNumber, - String diagnosticString) { - final DiagnosticKind kind; - final String message; - final boolean isFixable; - long lineNo = -1; - - if (lineNumber != null) { - lineNo = lineNumber; + /** How the diagnostics appear in Java source files. */ + public static final String DIAGNOSTIC_IN_JAVA_REGEX = + "\\s*(?error|fixable-error|warning|fixable-warning|Note|other):\\s*(?[\\s\\S]*)"; + + /** Pattern compiled from {@link #DIAGNOSTIC_IN_JAVA_REGEX}. */ + public static final Pattern DIAGNOSTIC_IN_JAVA_PATTERN = + Pattern.compile(DIAGNOSTIC_IN_JAVA_REGEX); + + /** How the diagnostic warnings appear in Java source files. */ + public static final String DIAGNOSTIC_WARNING_IN_JAVA_REGEX = + "\\s*warning:\\s*(?[\\s\\S]*)"; + + /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_IN_JAVA_REGEX}. */ + public static final Pattern DIAGNOSTIC_WARNING_IN_JAVA_PATTERN = + Pattern.compile(DIAGNOSTIC_WARNING_IN_JAVA_REGEX); + + /** How the diagnostics appear in javax tools diagnostics from the compiler. */ + public static final String DIAGNOSTIC_REGEX = + "(?:(?\\d+):)?" + DIAGNOSTIC_IN_JAVA_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_REGEX}. */ + public static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile(DIAGNOSTIC_REGEX); + + /** How the diagnostic warnings appear in javax tools diagnostics from the compiler. */ + public static final String DIAGNOSTIC_WARNING_REGEX = + "(?:(?\\d+):)?" + DIAGNOSTIC_WARNING_IN_JAVA_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_REGEX}. */ + public static final Pattern DIAGNOSTIC_WARNING_PATTERN = + Pattern.compile(DIAGNOSTIC_WARNING_REGEX); + + /** How the diagnostics appear in diagnostic files (.out). */ + public static final String DIAGNOSTIC_FILE_REGEX = ".+\\.java" + DIAGNOSTIC_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_FILE_REGEX}. */ + public static final Pattern DIAGNOSTIC_FILE_PATTERN = Pattern.compile(DIAGNOSTIC_FILE_REGEX); + + /** How the diagnostic warnings appear in diagnostic files (.out). */ + public static final String DIAGNOSTIC_FILE_WARNING_REGEX = + ".+\\.java" + DIAGNOSTIC_WARNING_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_FILE_WARNING_REGEX}. */ + public static final Pattern DIAGNOSTIC_FILE_WARNING_PATTERN = + Pattern.compile(DIAGNOSTIC_FILE_WARNING_REGEX); + + /** + * Instantiate the diagnostic based on a string that would appear in diagnostic files (i.e. + * files that only contain line after line of expected diagnostics). + * + * @param stringFromDiagnosticFile a single diagnostic string to parse + * @return a new TestDiagnostic + */ + public static TestDiagnostic fromDiagnosticFileString(String stringFromDiagnosticFile) { + return fromPatternMatching( + DIAGNOSTIC_FILE_PATTERN, + DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, + // Important to use "" to make input of expected warnings easy. + Paths.get(""), + null, + stringFromDiagnosticFile); } - Matcher diagnosticMatcher = diagnosticPattern.matcher(diagnosticString); - if (diagnosticMatcher.matches()) { - IPair categoryToFixable = - parseCategoryString(diagnosticMatcher.group("kind")); - kind = categoryToFixable.first; - isFixable = categoryToFixable.second; - message = diagnosticMatcher.group("message").trim(); - if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { - lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); - } - } else { - Matcher warningMatcher = warningPattern.matcher(diagnosticString); - if (warningMatcher.matches()) { - kind = DiagnosticKind.Warning; - isFixable = false; - message = warningMatcher.group("message").trim(); - if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { - lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); - } - } else if (diagnosticString.startsWith("warning:")) { - kind = DiagnosticKind.Warning; - isFixable = false; - message = diagnosticString.substring("warning:".length()).trim(); + /** + * Instantiate the diagnostic from a string that would appear in a Java file, e.g.: "error: + * (message)" + * + * @param filename the file containing the diagnostic (and the error) + * @param lineNumber the line number of the line immediately below the diagnostic comment in the + * Java file + * @param stringFromJavaFile the string containing the diagnostic + * @return a new TestDiagnostic + */ + public static TestDiagnostic fromJavaFileComment( + String filename, long lineNumber, String stringFromJavaFile) { + return fromPatternMatching( + DIAGNOSTIC_IN_JAVA_PATTERN, + DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, + Paths.get(filename), + lineNumber, + stringFromJavaFile); + } + + /** + * Instantiate a diagnostic from output produced by the Java compiler. The resulting diagnostic + * is never fixable and always has parentheses. + * + * @param diagnosticString the compiler diagnostics string + * @return the corresponding test diagnostic + */ + public static TestDiagnostic fromJavaxToolsDiagnostic(String diagnosticString) { + // It would be nice not to parse this from the diagnostic string. + // However, diagnostic.toString() may contain "[unchecked]" even though getMessage() does + // not. + // Since we want to match the error messages reported by javac exactly, we must parse. + // diagnostic.getCode() returns "compiler.warn.prob.found.req" for "[unchecked]" messages, + // but not clear how to map from one to the other. + IPair trimmed = formatJavaxToolString(diagnosticString); + return fromPatternMatching( + DIAGNOSTIC_PATTERN, + DIAGNOSTIC_WARNING_PATTERN, + trimmed.second, + null, + trimmed.first); + } + + /** + * Instantiate the diagnostic via pattern-matching against patterns. + * + * @param diagnosticPattern a pattern that matches any diagnostic + * @param warningPattern a pattern that matches a warning diagnostic + * @param file the test file + * @param lineNumber the line number + * @param diagnosticString the string to parse + * @return a diagnostic parsed from the given string + */ + @SuppressWarnings("nullness") // TODO: regular expression group access + protected static TestDiagnostic fromPatternMatching( + Pattern diagnosticPattern, + Pattern warningPattern, + Path file, + @Nullable Long lineNumber, + String diagnosticString) { + final DiagnosticKind kind; + final String message; + final boolean isFixable; + long lineNo = -1; + if (lineNumber != null) { - lineNo = lineNumber; + lineNo = lineNumber; + } + + Matcher diagnosticMatcher = diagnosticPattern.matcher(diagnosticString); + if (diagnosticMatcher.matches()) { + IPair categoryToFixable = + parseCategoryString(diagnosticMatcher.group("kind")); + kind = categoryToFixable.first; + isFixable = categoryToFixable.second; + message = diagnosticMatcher.group("message").trim(); + if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { + lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); + } } else { - lineNo = 0; + Matcher warningMatcher = warningPattern.matcher(diagnosticString); + if (warningMatcher.matches()) { + kind = DiagnosticKind.Warning; + isFixable = false; + message = warningMatcher.group("message").trim(); + if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { + lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); + } + } else if (diagnosticString.startsWith("warning:")) { + kind = DiagnosticKind.Warning; + isFixable = false; + message = diagnosticString.substring("warning:".length()).trim(); + if (lineNumber != null) { + lineNo = lineNumber; + } else { + lineNo = 0; + } + } else { + kind = DiagnosticKind.Other; + isFixable = false; + message = diagnosticString; + // this should only happen if we are parsing a Java Diagnostic from the compiler + // that we did do not handle + if (lineNumber == null) { + lineNo = -1; + } + } } - } else { - kind = DiagnosticKind.Other; - isFixable = false; - message = diagnosticString; - // this should only happen if we are parsing a Java Diagnostic from the compiler - // that we did do not handle - if (lineNumber == null) { - lineNo = -1; + + // Check if the message matches detailed message format. + // Trim the message to remove leading/trailing whitespace. + // Keep separator in sync with SourceChecker.DETAILS_SEPARATOR. + String[] diagnosticStrings = + Arrays.stream(message.split(" \\$\\$ ")).map(String::trim).toArray(String[]::new); + if (diagnosticStrings.length > 1) { + // See SourceChecker.detailedMsgTextPrefix. + // The parts of the detailed message are: + + // (1) message key; + String messageKey = diagnosticStrings[0]; + + // (2) number of additional tokens, and those tokens; this depends on the error message, + // and an example is the found and expected types; + int numAdditionalTokens = Integer.parseInt(diagnosticStrings[1]); + int lastAdditionalToken = 2 + numAdditionalTokens; + List additionalTokens = + Arrays.asList(diagnosticStrings).subList(2, lastAdditionalToken); + + // (3) the diagnostic position, given by the format (startPosition, endPosition); + String pairParens = diagnosticStrings[lastAdditionalToken]; + // remove the leading and trailing parentheses and spaces + String pair = pairParens.substring(2, pairParens.length() - 2); + String[] diagPositionString = pair.split(", "); + long startPosition = Long.parseLong(diagPositionString[0]); + long endPosition = Long.parseLong(diagPositionString[1]); + + // (4) the human-readable diagnostic message. + String readableMessage = diagnosticStrings[lastAdditionalToken + 1]; + + return new DetailedTestDiagnostic( + file, + lineNo, + kind, + messageKey, + additionalTokens, + startPosition, + endPosition, + readableMessage, + isFixable); } - } - } - // Check if the message matches detailed message format. - // Trim the message to remove leading/trailing whitespace. - // Keep separator in sync with SourceChecker.DETAILS_SEPARATOR. - String[] diagnosticStrings = - Arrays.stream(message.split(" \\$\\$ ")).map(String::trim).toArray(String[]::new); - if (diagnosticStrings.length > 1) { - // See SourceChecker.detailedMsgTextPrefix. - // The parts of the detailed message are: - - // (1) message key; - String messageKey = diagnosticStrings[0]; - - // (2) number of additional tokens, and those tokens; this depends on the error message, - // and an example is the found and expected types; - int numAdditionalTokens = Integer.parseInt(diagnosticStrings[1]); - int lastAdditionalToken = 2 + numAdditionalTokens; - List additionalTokens = - Arrays.asList(diagnosticStrings).subList(2, lastAdditionalToken); - - // (3) the diagnostic position, given by the format (startPosition, endPosition); - String pairParens = diagnosticStrings[lastAdditionalToken]; - // remove the leading and trailing parentheses and spaces - String pair = pairParens.substring(2, pairParens.length() - 2); - String[] diagPositionString = pair.split(", "); - long startPosition = Long.parseLong(diagPositionString[0]); - long endPosition = Long.parseLong(diagPositionString[1]); - - // (4) the human-readable diagnostic message. - String readableMessage = diagnosticStrings[lastAdditionalToken + 1]; - - return new DetailedTestDiagnostic( - file, - lineNo, - kind, - messageKey, - additionalTokens, - startPosition, - endPosition, - readableMessage, - isFixable); + return new TestDiagnostic(file, lineNo, kind, message, isFixable); } - return new TestDiagnostic(file, lineNo, kind, message, isFixable); - } - - /** - * Given a javax diagnostic, return a pair of (trimmed, file), where "trimmed" is the message - * without the leading filename and the file path. As an example: "foo/bar/Baz.java:49: My error - * message" is turned into {@code IPair.of(":49: My error message", Path("foo/bar/Baz.java"))}. If - * the file path cannot be determined, it uses {@code ""}. This is necessary to make writing the - * expected warnings easy. - * - * @param original a javax diagnostic - * @return the diagnostic, split into message and file - */ - public static IPair formatJavaxToolString(String original) { - String firstline; - // In TestDiagnostic we manually check for "\r\n" and "\n". Here, we only use - // `firstline` to find the file name. Using the system line separator is not - // problem here, it seems. - int lineSepPos = original.indexOf(System.lineSeparator()); - if (lineSepPos != -1) { - firstline = original.substring(0, lineSepPos); - } else { - firstline = original; + /** + * Given a javax diagnostic, return a pair of (trimmed, file), where "trimmed" is the message + * without the leading filename and the file path. As an example: "foo/bar/Baz.java:49: My error + * message" is turned into {@code IPair.of(":49: My error message", Path("foo/bar/Baz.java"))}. + * If the file path cannot be determined, it uses {@code ""}. This is necessary to make writing + * the expected warnings easy. + * + * @param original a javax diagnostic + * @return the diagnostic, split into message and file + */ + public static IPair formatJavaxToolString(String original) { + String firstline; + // In TestDiagnostic we manually check for "\r\n" and "\n". Here, we only use + // `firstline` to find the file name. Using the system line separator is not + // problem here, it seems. + int lineSepPos = original.indexOf(System.lineSeparator()); + if (lineSepPos != -1) { + firstline = original.substring(0, lineSepPos); + } else { + firstline = original; + } + + String trimmed; + Path file; + int extensionPos = firstline.indexOf(".java:"); + if (extensionPos != -1) { + file = Paths.get(firstline.substring(0, extensionPos + 5).trim()); + trimmed = original.substring(extensionPos + 5).trim(); + } else { + // Important to use "" to make input of expected warnings easy. + // For an example, see file + // ./checker/tests/nullness-stubfile/NullnessStubfileMerge.java + file = Paths.get(""); + trimmed = original; + } + + return IPair.of(trimmed, file); } - String trimmed; - Path file; - int extensionPos = firstline.indexOf(".java:"); - if (extensionPos != -1) { - file = Paths.get(firstline.substring(0, extensionPos + 5).trim()); - trimmed = original.substring(extensionPos + 5).trim(); - } else { - // Important to use "" to make input of expected warnings easy. - // For an example, see file - // ./checker/tests/nullness-stubfile/NullnessStubfileMerge.java - file = Paths.get(""); - trimmed = original; + /** + * Given a category string that may be prepended with "fixable-", return the category enum that + * corresponds with the category and whether or not it is a isFixable error. + * + * @param category a category string + * @return the corresponding diagnostic kind and whether it is fixable + */ + private static IPair parseCategoryString(String category) { + String fixable = "fixable-"; + boolean isFixable = category.startsWith(fixable); + if (isFixable) { + category = category.substring(fixable.length()); + } + DiagnosticKind categoryEnum = DiagnosticKind.fromParseString(category); + if (categoryEnum == null) { + throw new Error("Unparsable category: " + category); + } + + return IPair.of(categoryEnum, isFixable); } - return IPair.of(trimmed, file); - } - - /** - * Given a category string that may be prepended with "fixable-", return the category enum that - * corresponds with the category and whether or not it is a isFixable error. - * - * @param category a category string - * @return the corresponding diagnostic kind and whether it is fixable - */ - private static IPair parseCategoryString(String category) { - String fixable = "fixable-"; - boolean isFixable = category.startsWith(fixable); - if (isFixable) { - category = category.substring(fixable.length()); + /** + * Return true if this line in a Java file indicates an expected diagnostic that might be + * continued on the next line. + * + * @param originalLine the input line + * @return whether the diagnostic might be continued on the next line + */ + public static boolean isJavaDiagnosticLineStart(String originalLine) { + String trimmedLine = originalLine.trim(); + return trimmedLine.startsWith("// ::") || trimmedLine.startsWith("// warning:"); } - DiagnosticKind categoryEnum = DiagnosticKind.fromParseString(category); - if (categoryEnum == null) { - throw new Error("Unparsable category: " + category); + + /** + * Convert an end-of-line diagnostic message to a beginning-of-line one. Returns the argument + * unchanged if it does not contain an end-of-line diagnostic message. + * + *

          Most diagnostics in Java files start at the beginning of a line. Occasionally, javac + * issues a warning about implicit code, such as an implicit constructor, on the line + * immediately after a curly brace. The only place to put the expected diagnostic + * message is on the line with the curly brace. + * + *

          This implementation replaces "{ // ::" by "// ::", converting the end-of-line diagnostic + * message to a beginning-of-line one that the rest of the code can handle. It is rather + * specific (to avoid false positive matches, such as when "// ::" is commented out in source + * code). It could be extended in the future if such an extension is necessary. + */ + public static String handleEndOfLineJavaDiagnostic(String originalLine) { + int curlyIndex = originalLine.indexOf("{ // ::"); + if (curlyIndex == -1) { + return originalLine; + } else { + return originalLine.substring(curlyIndex + 2); + } } - return IPair.of(categoryEnum, isFixable); - } - - /** - * Return true if this line in a Java file indicates an expected diagnostic that might be - * continued on the next line. - * - * @param originalLine the input line - * @return whether the diagnostic might be continued on the next line - */ - public static boolean isJavaDiagnosticLineStart(String originalLine) { - String trimmedLine = originalLine.trim(); - return trimmedLine.startsWith("// ::") || trimmedLine.startsWith("// warning:"); - } - - /** - * Convert an end-of-line diagnostic message to a beginning-of-line one. Returns the argument - * unchanged if it does not contain an end-of-line diagnostic message. - * - *

          Most diagnostics in Java files start at the beginning of a line. Occasionally, javac issues - * a warning about implicit code, such as an implicit constructor, on the line immediately - * after a curly brace. The only place to put the expected diagnostic message is on the line - * with the curly brace. - * - *

          This implementation replaces "{ // ::" by "// ::", converting the end-of-line diagnostic - * message to a beginning-of-line one that the rest of the code can handle. It is rather specific - * (to avoid false positive matches, such as when "// ::" is commented out in source code). It - * could be extended in the future if such an extension is necessary. - */ - public static String handleEndOfLineJavaDiagnostic(String originalLine) { - int curlyIndex = originalLine.indexOf("{ // ::"); - if (curlyIndex == -1) { - return originalLine; - } else { - return originalLine.substring(curlyIndex + 2); + /** Return true if this line in a Java file continues an expected diagnostic. */ + @EnsuresNonNullIf(result = true, expression = "#1") + public static boolean isJavaDiagnosticLineContinuation(@Nullable String originalLine) { + if (originalLine == null) { + return false; + } + String trimmedLine = originalLine.trim(); + // Unlike with errors, there is no logic elsewhere for splitting multiple "warning:"s. So, + // avoid concatenating them. Also, each one must begin a line. They are allowed to wrap to + // the next line, though. + return trimmedLine.startsWith("// ") && !trimmedLine.startsWith("// warning:"); } - } - /** Return true if this line in a Java file continues an expected diagnostic. */ - @EnsuresNonNullIf(result = true, expression = "#1") - public static boolean isJavaDiagnosticLineContinuation(@Nullable String originalLine) { - if (originalLine == null) { - return false; + /** + * Return the continuation part. The argument is such that {@link + * #isJavaDiagnosticLineContinuation} returns true. + */ + public static String continuationPart(String originalLine) { + return originalLine.trim().substring(2).trim(); } - String trimmedLine = originalLine.trim(); - // Unlike with errors, there is no logic elsewhere for splitting multiple "warning:"s. So, - // avoid concatenating them. Also, each one must begin a line. They are allowed to wrap to - // the next line, though. - return trimmedLine.startsWith("// ") && !trimmedLine.startsWith("// warning:"); - } - - /** - * Return the continuation part. The argument is such that {@link - * #isJavaDiagnosticLineContinuation} returns true. - */ - public static String continuationPart(String originalLine) { - return originalLine.trim().substring(2).trim(); - } - - /** - * Convert a line in a Java source file to a TestDiagnosticLine. - * - *

          The input {@code line} is possibly the concatenation of multiple source lines, if the - * diagnostic was split across lines in the source code. - */ - public static TestDiagnosticLine fromJavaSourceLine( - String filename, String line, long lineNumber) { - String trimmedLine = line.trim(); - long errorLine = lineNumber + 1; - - if (trimmedLine.startsWith("// ::")) { - String restOfLine = trimmedLine.substring(5); // drop the "// ::" - String[] diagnosticStrs = restOfLine.split("::"); - List diagnostics = - CollectionsPlume.mapList( - (String diagnostic) -> fromJavaFileComment(filename, errorLine, diagnostic), - diagnosticStrs); - return new TestDiagnosticLine( - filename, errorLine, line, Collections.unmodifiableList(diagnostics)); - } else if (trimmedLine.startsWith("// warning:")) { - // This special diagnostic does not expect a line number nor a file name - String diagnosticString = trimmedLine.substring(2); - TestDiagnostic diagnostic = fromJavaFileComment("", -1, diagnosticString); - return new TestDiagnosticLine("", -1, line, Collections.singletonList(diagnostic)); - } else if (trimmedLine.startsWith("//::")) { - TestDiagnostic diagnostic = - new TestDiagnostic( - Paths.get(filename), - lineNumber, - DiagnosticKind.Error, - "Use \"// ::\", not \"//::\"", - false); - return new TestDiagnosticLine( - filename, lineNumber, line, Collections.singletonList(diagnostic)); - } else { - // It's a bit gross to create empty diagnostics (returning null might be more - // efficient), but they will be filtered out later. - return new TestDiagnosticLine(filename, errorLine, line, Collections.emptyList()); + + /** + * Convert a line in a Java source file to a TestDiagnosticLine. + * + *

          The input {@code line} is possibly the concatenation of multiple source lines, if the + * diagnostic was split across lines in the source code. + */ + public static TestDiagnosticLine fromJavaSourceLine( + String filename, String line, long lineNumber) { + String trimmedLine = line.trim(); + long errorLine = lineNumber + 1; + + if (trimmedLine.startsWith("// ::")) { + String restOfLine = trimmedLine.substring(5); // drop the "// ::" + String[] diagnosticStrs = restOfLine.split("::"); + List diagnostics = + CollectionsPlume.mapList( + (String diagnostic) -> + fromJavaFileComment(filename, errorLine, diagnostic), + diagnosticStrs); + return new TestDiagnosticLine( + filename, errorLine, line, Collections.unmodifiableList(diagnostics)); + } else if (trimmedLine.startsWith("// warning:")) { + // This special diagnostic does not expect a line number nor a file name + String diagnosticString = trimmedLine.substring(2); + TestDiagnostic diagnostic = fromJavaFileComment("", -1, diagnosticString); + return new TestDiagnosticLine("", -1, line, Collections.singletonList(diagnostic)); + } else if (trimmedLine.startsWith("//::")) { + TestDiagnostic diagnostic = + new TestDiagnostic( + Paths.get(filename), + lineNumber, + DiagnosticKind.Error, + "Use \"// ::\", not \"//::\"", + false); + return new TestDiagnosticLine( + filename, lineNumber, line, Collections.singletonList(diagnostic)); + } else { + // It's a bit gross to create empty diagnostics (returning null might be more + // efficient), but they will be filtered out later. + return new TestDiagnosticLine(filename, errorLine, line, Collections.emptyList()); + } } - } - /** Convert a line in a DiagnosticFile to a TestDiagnosticLine. */ - public static TestDiagnosticLine fromDiagnosticFileLine(String diagnosticLine) { - String trimmedLine = diagnosticLine.trim(); - if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) { - return new TestDiagnosticLine("", -1, diagnosticLine, Collections.emptyList()); + /** Convert a line in a DiagnosticFile to a TestDiagnosticLine. */ + public static TestDiagnosticLine fromDiagnosticFileLine(String diagnosticLine) { + String trimmedLine = diagnosticLine.trim(); + if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) { + return new TestDiagnosticLine("", -1, diagnosticLine, Collections.emptyList()); + } + + TestDiagnostic diagnostic = fromDiagnosticFileString(diagnosticLine); + return new TestDiagnosticLine( + "", diagnostic.getLineNumber(), diagnosticLine, Arrays.asList(diagnostic)); } - TestDiagnostic diagnostic = fromDiagnosticFileString(diagnosticLine); - return new TestDiagnosticLine( - "", diagnostic.getLineNumber(), diagnosticLine, Arrays.asList(diagnostic)); - } - - /** - * Convert a list of compiler diagnostics into test diagnostics. - * - * @param javaxDiagnostics the list of compiler diagnostics - * @return the corresponding test diagnostics - */ - public static Set fromJavaxToolsDiagnosticList( - List> javaxDiagnostics) { - Set diagnostics = new LinkedHashSet<>(javaxDiagnostics.size()); - - for (Diagnostic diagnostic : javaxDiagnostics) { - // See fromJavaxToolsDiagnostic as to why we use diagnostic.toString rather - // than convert from the diagnostic itself - String diagnosticString = diagnostic.toString(); - - // suppress Xlint warnings - if (diagnosticString.contains("uses unchecked or unsafe operations.") - || diagnosticString.contains("Recompile with -Xlint:unchecked for details.") - || diagnosticString.endsWith(" declares unsafe vararg methods.") - || diagnosticString.contains("Recompile with -Xlint:varargs for details.")) { - continue; - } - - diagnostics.add(fromJavaxToolsDiagnostic(diagnosticString)); + /** + * Convert a list of compiler diagnostics into test diagnostics. + * + * @param javaxDiagnostics the list of compiler diagnostics + * @return the corresponding test diagnostics + */ + public static Set fromJavaxToolsDiagnosticList( + List> javaxDiagnostics) { + Set diagnostics = new LinkedHashSet<>(javaxDiagnostics.size()); + + for (Diagnostic diagnostic : javaxDiagnostics) { + // See fromJavaxToolsDiagnostic as to why we use diagnostic.toString rather + // than convert from the diagnostic itself + String diagnosticString = diagnostic.toString(); + + // suppress Xlint warnings + if (diagnosticString.contains("uses unchecked or unsafe operations.") + || diagnosticString.contains("Recompile with -Xlint:unchecked for details.") + || diagnosticString.endsWith(" declares unsafe vararg methods.") + || diagnosticString.contains("Recompile with -Xlint:varargs for details.")) { + continue; + } + + diagnostics.add(fromJavaxToolsDiagnostic(diagnosticString)); + } + + return diagnostics; } - return diagnostics; - } - - /** - * Converts the given diagnostics to strings (as they would appear in a source file individually). - * - * @param diagnostics a list of diagnostics - * @return a list of the diagnastics as they would appear in a source file - */ - public static List diagnosticsToString(List diagnostics) { - return CollectionsPlume.mapList(TestDiagnostic::toString, diagnostics); - } + /** + * Converts the given diagnostics to strings (as they would appear in a source file + * individually). + * + * @param diagnostics a list of diagnostics + * @return a list of the diagnastics as they would appear in a source file + */ + public static List diagnosticsToString(List diagnostics) { + return CollectionsPlume.mapList(TestDiagnostic::toString, diagnostics); + } } diff --git a/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java b/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java index eb89f02a97d..b5b31bd34cb 100644 --- a/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java +++ b/framework-test/src/taglet/java/org/checkerframework/taglet/ManualTaglet.java @@ -8,12 +8,15 @@ import com.sun.source.doctree.UnknownBlockTagTree; import com.sun.source.doctree.UnknownInlineTagTree; import com.sun.source.util.SimpleDocTreeVisitor; + +import jdk.javadoc.doclet.Taglet; + import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.StringJoiner; + import javax.lang.model.element.Element; -import jdk.javadoc.doclet.Taglet; /** * A taglet for processing the {@code @checker_framework.manual} javadoc block tag, which inserts @@ -30,96 +33,97 @@ */ public class ManualTaglet implements Taglet { - private static final String NAME = "checker_framework.manual"; - - @Override - public String getName() { - return NAME; - } - - private static final EnumSet allowedSet = EnumSet.allOf(Location.class); - - @Override - public Set getAllowedLocations() { - return allowedSet; - } - - @Override - public boolean isInlineTag() { - return false; - } - - /** - * Formats a link, given an array of tokens. - * - * @param parts the array of tokens - * @return a link to the manual top-level if the array size is one, or a link to a part of the - * manual if it's larger than one - */ - private String formatLink(String[] parts) { - String anchor, text; - if (parts.length < 2) { - anchor = ""; - text = "Checker Framework"; - } else { - anchor = parts[0]; - text = parts[1]; + private static final String NAME = "checker_framework.manual"; + + @Override + public String getName() { + return NAME; } - return String.format("%s", anchor, text); - } - - /** - * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag - * content. - * - * @param text the tag content - * @return the formatted tag - */ - private String formatHeader(String text) { - return String.format("

          See the Checker Framework Manual:
          %s
          ", text); - } - - @Override - public String toString(List tags, Element element) { - if (tags.isEmpty()) { - return ""; + + private static final EnumSet allowedSet = EnumSet.allOf(Location.class); + + @Override + public Set getAllowedLocations() { + return allowedSet; } - StringJoiner sb = new StringJoiner(", "); - for (DocTree t : tags) { - String text = getText(t); - String[] split = text.split(" ", 2); - sb.add(formatLink(split)); + + @Override + public boolean isInlineTag() { + return false; } - return formatHeader(sb.toString()); - } - - static String getText(DocTree dt) { - return new SimpleDocTreeVisitor() { - @Override - public String visitUnknownBlockTag(UnknownBlockTagTree tree, Void p) { - for (DocTree dt : tree.getContent()) { - return dt.accept(this, null); + + /** + * Formats a link, given an array of tokens. + * + * @param parts the array of tokens + * @return a link to the manual top-level if the array size is one, or a link to a part of the + * manual if it's larger than one + */ + private String formatLink(String[] parts) { + String anchor, text; + if (parts.length < 2) { + anchor = ""; + text = "Checker Framework"; + } else { + anchor = parts[0]; + text = parts[1]; } - return ""; - } + return String.format( + "%s", anchor, text); + } + + /** + * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag + * content. + * + * @param text the tag content + * @return the formatted tag + */ + private String formatHeader(String text) { + return String.format("
          See the Checker Framework Manual:
          %s
          ", text); + } - @Override - public String visitUnknownInlineTag(UnknownInlineTagTree tree, Void p) { - for (DocTree dt : tree.getContent()) { - return dt.accept(this, null); + @Override + public String toString(List tags, Element element) { + if (tags.isEmpty()) { + return ""; } - return ""; - } - - @Override - public String visitText(TextTree tree, Void p) { - return tree.getBody(); - } - - @Override - protected String defaultAction(DocTree tree, Void p) { - return ""; - } - }.visit(dt, null); - } + StringJoiner sb = new StringJoiner(", "); + for (DocTree t : tags) { + String text = getText(t); + String[] split = text.split(" ", 2); + sb.add(formatLink(split)); + } + return formatHeader(sb.toString()); + } + + static String getText(DocTree dt) { + return new SimpleDocTreeVisitor() { + @Override + public String visitUnknownBlockTag(UnknownBlockTagTree tree, Void p) { + for (DocTree dt : tree.getContent()) { + return dt.accept(this, null); + } + return ""; + } + + @Override + public String visitUnknownInlineTag(UnknownInlineTagTree tree, Void p) { + for (DocTree dt : tree.getContent()) { + return dt.accept(this, null); + } + return ""; + } + + @Override + public String visitText(TextTree tree, Void p) { + return tree.getBody(); + } + + @Override + protected String defaultAction(DocTree tree, Void p) { + return ""; + } + }.visit(dt, null); + } } diff --git a/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java b/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java index 4252da48efa..726e2b1c2db 100644 --- a/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java +++ b/framework-test/src/tagletJdk8/java/org/checkerframework/taglet/ManualTaglet.java @@ -5,6 +5,7 @@ import com.sun.javadoc.Tag; import com.sun.tools.doclets.Taglet; + import java.util.Map; import java.util.StringJoiner; @@ -23,103 +24,104 @@ */ public class ManualTaglet implements Taglet { - @Override - public String getName() { - return "checker_framework.manual"; - } - - @Override - public boolean inConstructor() { - return true; - } - - @Override - public boolean inField() { - return true; - } - - @Override - public boolean inMethod() { - return true; - } - - @Override - public boolean inOverview() { - return true; - } - - @Override - public boolean inPackage() { - return true; - } - - @Override - public boolean inType() { - return true; - } - - @Override - public boolean isInlineTag() { - return false; - } - - /** - * Formats a link, given an array of tokens. - * - * @param parts the array of tokens - * @return a link to the manual top-level if the array size is one, or a link to a part of the - * manual if it's larger than one - */ - private String formatLink(String[] parts) { - String anchor, text; - if (parts.length < 2) { - anchor = ""; - text = "Checker Framework"; - } else { - anchor = parts[0]; - text = parts[1]; + @Override + public String getName() { + return "checker_framework.manual"; + } + + @Override + public boolean inConstructor() { + return true; + } + + @Override + public boolean inField() { + return true; + } + + @Override + public boolean inMethod() { + return true; + } + + @Override + public boolean inOverview() { + return true; + } + + @Override + public boolean inPackage() { + return true; } - return String.format("%s", anchor, text); - } - - /** - * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag - * content. - * - * @param text the tag content - * @return the formatted tag - */ - private String formatHeader(String text) { - return String.format("
          See the Checker Framework Manual:
          %s
          ", text); - } - - @Override - public String toString(Tag tag) { - String[] split = tag.text().split(" ", 2); - return formatHeader(formatLink(split)); - } - - @Override - public String toString(Tag[] tags) { - if (tags.length == 0) { - return ""; + + @Override + public boolean inType() { + return true; + } + + @Override + public boolean isInlineTag() { + return false; } - StringJoiner sb = new StringJoiner(", "); - for (Tag t : tags) { - String text = t.text(); - String[] split = text.split(" ", 2); - sb.add(formatLink(split)); + + /** + * Formats a link, given an array of tokens. + * + * @param parts the array of tokens + * @return a link to the manual top-level if the array size is one, or a link to a part of the + * manual if it's larger than one + */ + private String formatLink(String[] parts) { + String anchor, text; + if (parts.length < 2) { + anchor = ""; + text = "Checker Framework"; + } else { + anchor = parts[0]; + text = parts[1]; + } + return String.format( + "%s", anchor, text); + } + + /** + * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the tag + * content. + * + * @param text the tag content + * @return the formatted tag + */ + private String formatHeader(String text) { + return String.format("
          See the Checker Framework Manual:
          %s
          ", text); } - return formatHeader(sb.toString()); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - public static void register(Map tagletMap) { - ManualTaglet tag = new ManualTaglet(); - Taglet t = (Taglet) tagletMap.get(tag.getName()); - if (t != null) { - tagletMap.remove(tag.getName()); + + @Override + public String toString(Tag tag) { + String[] split = tag.text().split(" ", 2); + return formatHeader(formatLink(split)); + } + + @Override + public String toString(Tag[] tags) { + if (tags.length == 0) { + return ""; + } + StringJoiner sb = new StringJoiner(", "); + for (Tag t : tags) { + String text = t.text(); + String[] split = text.split(" ", 2); + sb.add(formatLink(split)); + } + return formatHeader(sb.toString()); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void register(Map tagletMap) { + ManualTaglet tag = new ManualTaglet(); + Taglet t = (Taglet) tagletMap.get(tag.getName()); + if (t != null) { + tagletMap.remove(tag.getName()); + } + tagletMap.put(tag.getName(), tag); } - tagletMap.put(tag.getName(), tag); - } } diff --git a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java index 3907ac964eb..0c6fd0a7778 100644 --- a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java +++ b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerDirTest.java @@ -1,7 +1,5 @@ package org.checkerframework.framework.test.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestRootDirectory; @@ -11,53 +9,60 @@ import org.hamcrest.MatcherAssert; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests the explicit tests root configuration. */ @TestRootDirectory("tests-alt") public class AlternateTestRootPerDirTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AlternateTestRootPerDirTest(List testFiles) { - super(testFiles, ValueChecker.class, ""); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AlternateTestRootPerDirTest(List testFiles) { + super(testFiles, ValueChecker.class, ""); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"alt-dir-a", "alt-dir-b"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"alt-dir-a", "alt-dir-b"}; + } - @Override - public void checkResult(TypecheckResult typecheckResult) { - super.checkResult(typecheckResult); - MatcherAssert.assertThat( - "test check result has exactly one expected diagnostic", - typecheckResult.getExpectedDiagnostics().size(), - CoreMatchers.describedAs( - "singleton collection %0", - CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has the expected diagnostic of one of the test files", - typecheckResult.getExpectedDiagnostics(), - CoreMatchers.either( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))) - .or( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125B.java", 5, "error: (assignment.type.incompatible)")))); - MatcherAssert.assertThat( - "test check result has exactly zero unexpected diagnostics", - typecheckResult.getUnexpectedDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has exactly zero missing diagnostics", - typecheckResult.getMissingDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); - } + @Override + public void checkResult(TypecheckResult typecheckResult) { + super.checkResult(typecheckResult); + MatcherAssert.assertThat( + "test check result has exactly one expected diagnostic", + typecheckResult.getExpectedDiagnostics().size(), + CoreMatchers.describedAs( + "singleton collection %0", + CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has the expected diagnostic of one of the test files", + typecheckResult.getExpectedDiagnostics(), + CoreMatchers.either( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125A.java", + 5, + "error: (assignment.type.incompatible)"))) + .or( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125B.java", + 5, + "error: (assignment.type.incompatible)")))); + MatcherAssert.assertThat( + "test check result has exactly zero unexpected diagnostics", + typecheckResult.getUnexpectedDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has exactly zero missing diagnostics", + typecheckResult.getMissingDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); + } } diff --git a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java index 228b1af810d..26c45913faa 100644 --- a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java +++ b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithDirsTest.java @@ -1,6 +1,5 @@ package org.checkerframework.framework.test.test.junit; -import java.io.File; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.checkerframework.framework.test.TestRootDirectory; @@ -10,53 +9,59 @@ import org.hamcrest.MatcherAssert; import org.junit.runners.Parameterized.Parameters; +import java.io.File; + /** Tests the explicit tests root configuration. */ @TestRootDirectory("tests-alt") public class AlternateTestRootPerFileWithDirsTest extends CheckerFrameworkPerFileTest { - /** - * @param testFile the files containing test code, which will be type-checked - */ - public AlternateTestRootPerFileWithDirsTest(File testFile) { - super(testFile, ValueChecker.class, ""); - } + /** + * @param testFile the files containing test code, which will be type-checked + */ + public AlternateTestRootPerFileWithDirsTest(File testFile) { + super(testFile, ValueChecker.class, ""); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"alt-dir-a", "alt-dir-b"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"alt-dir-a", "alt-dir-b"}; + } - @Override - public void checkResult(TypecheckResult typecheckResult) { - super.checkResult(typecheckResult); - MatcherAssert.assertThat( - "test check result has exactly one expected diagnostic", - typecheckResult.getExpectedDiagnostics().size(), - CoreMatchers.describedAs( - "singleton collection %0", - CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has the expected diagnostic of one of the test files", - typecheckResult.getExpectedDiagnostics(), - CoreMatchers.either( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))) - .or( - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125B.java", 5, "error: (assignment.type.incompatible)")))); - MatcherAssert.assertThat( - "test check result has exactly zero unexpected diagnostics", - typecheckResult.getUnexpectedDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has exactly zero missing diagnostics", - typecheckResult.getMissingDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); - } + @Override + public void checkResult(TypecheckResult typecheckResult) { + super.checkResult(typecheckResult); + MatcherAssert.assertThat( + "test check result has exactly one expected diagnostic", + typecheckResult.getExpectedDiagnostics().size(), + CoreMatchers.describedAs( + "singleton collection %0", + CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has the expected diagnostic of one of the test files", + typecheckResult.getExpectedDiagnostics(), + CoreMatchers.either( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125A.java", + 5, + "error: (assignment.type.incompatible)"))) + .or( + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125B.java", + 5, + "error: (assignment.type.incompatible)")))); + MatcherAssert.assertThat( + "test check result has exactly zero unexpected diagnostics", + typecheckResult.getUnexpectedDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has exactly zero missing diagnostics", + typecheckResult.getMissingDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); + } } diff --git a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java index ee8659ecf67..c85dc0bcd54 100644 --- a/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java +++ b/framework-test/src/test/java/org/checkerframework/framework/test/test/junit/AlternateTestRootPerFileWithFilesTest.java @@ -1,7 +1,5 @@ package org.checkerframework.framework.test.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.checkerframework.framework.test.TestRootDirectory; @@ -12,48 +10,51 @@ import org.hamcrest.MatcherAssert; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests the explicit tests root configuration. */ @TestRootDirectory("tests-alt") public class AlternateTestRootPerFileWithFilesTest extends CheckerFrameworkPerFileTest { - /** - * @param testFile the files containing test code, which will be type-checked - */ - public AlternateTestRootPerFileWithFilesTest(File testFile) { - super(testFile, ValueChecker.class, ""); - } + /** + * @param testFile the files containing test code, which will be type-checked + */ + public AlternateTestRootPerFileWithFilesTest(File testFile) { + super(testFile, ValueChecker.class, ""); + } - @Parameters - public static List getTestFiles() { - return TestUtilities.findRelativeNestedJavaFiles("tests-alt", "alt-dir-a"); - } + @Parameters + public static List getTestFiles() { + return TestUtilities.findRelativeNestedJavaFiles("tests-alt", "alt-dir-a"); + } - @Override - public void checkResult(TypecheckResult typecheckResult) { - super.checkResult(typecheckResult); - MatcherAssert.assertThat( - "test check result has exactly one expected diagnostic", - typecheckResult.getExpectedDiagnostics().size(), - CoreMatchers.describedAs( - "singleton collection %0", - CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has the expected diagnostic of the test file", - typecheckResult.getExpectedDiagnostics(), - CoreMatchers.hasItem( - TestDiagnosticUtils.fromJavaFileComment( - "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))); - MatcherAssert.assertThat( - "test check result has exactly zero unexpected diagnostics", - typecheckResult.getUnexpectedDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); - MatcherAssert.assertThat( - "test check result has exactly zero missing diagnostics", - typecheckResult.getMissingDiagnostics().size(), - CoreMatchers.describedAs( - "zero length collection %0", - CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); - } + @Override + public void checkResult(TypecheckResult typecheckResult) { + super.checkResult(typecheckResult); + MatcherAssert.assertThat( + "test check result has exactly one expected diagnostic", + typecheckResult.getExpectedDiagnostics().size(), + CoreMatchers.describedAs( + "singleton collection %0", + CoreMatchers.is(1), typecheckResult.getExpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has the expected diagnostic of the test file", + typecheckResult.getExpectedDiagnostics(), + CoreMatchers.hasItem( + TestDiagnosticUtils.fromJavaFileComment( + "Issue6125A.java", 5, "error: (assignment.type.incompatible)"))); + MatcherAssert.assertThat( + "test check result has exactly zero unexpected diagnostics", + typecheckResult.getUnexpectedDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getUnexpectedDiagnostics())); + MatcherAssert.assertThat( + "test check result has exactly zero missing diagnostics", + typecheckResult.getMissingDiagnostics().size(), + CoreMatchers.describedAs( + "zero length collection %0", + CoreMatchers.is(0), typecheckResult.getMissingDiagnostics())); + } } diff --git a/framework-test/tests-alt/alt-dir-a/Issue6125A.java b/framework-test/tests-alt/alt-dir-a/Issue6125A.java index 5e063add1b0..b1df9696a06 100644 --- a/framework-test/tests-alt/alt-dir-a/Issue6125A.java +++ b/framework-test/tests-alt/alt-dir-a/Issue6125A.java @@ -1,6 +1,6 @@ import org.checkerframework.common.value.qual.StringVal; public class Issue6125A { - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "goodbye"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "goodbye"; } diff --git a/framework-test/tests-alt/alt-dir-b/Issue6125B.java b/framework-test/tests-alt/alt-dir-b/Issue6125B.java index c2f3e0f2a65..0a7aa611b5b 100644 --- a/framework-test/tests-alt/alt-dir-b/Issue6125B.java +++ b/framework-test/tests-alt/alt-dir-b/Issue6125B.java @@ -1,6 +1,6 @@ import org.checkerframework.common.value.qual.StringVal; public class Issue6125B { - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "world"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "world"; } diff --git a/framework/build.gradle b/framework/build.gradle index c1128b2b3ca..a5882cf5af0 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -1,177 +1,177 @@ plugins { - id 'java-library' + id 'java-library' } ext { - annotatedJdkHome = '../../jdk' + annotatedJdkHome = '../../jdk' } sourceSets { - main { - java { - // NO-AFU - exclude '**/org/checkerframework/common/wholeprograminference/**' - exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' - exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' - } + main { + java { + // NO-AFU + exclude '**/org/checkerframework/common/wholeprograminference/**' + exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' + exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' + } - resources { - // Stub files, message.properties, etc. - srcDirs += [ - 'src/main/java', - "${buildDir}/generated/resources" - ] - - // NO-AFU - exclude '**/org/checkerframework/common/wholeprograminference/**' - exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' - exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' + resources { + // Stub files, message.properties, etc. + srcDirs += [ + 'src/main/java', + "${buildDir}/generated/resources" + ] + + // NO-AFU + exclude '**/org/checkerframework/common/wholeprograminference/**' + exclude '**/org/checkerframework/framework/stub/AddAnnotatedFor.java' + exclude '**/org/checkerframework/framework/stub/ToIndexFileConverter.java' + } } - } - testannotations { - java { - srcDirs = ['src/testannotations/java'] + testannotations { + java { + srcDirs = ['src/testannotations/java'] + } } - } } sourcesJar { - // The resources duplicate content from the src directory. - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + // The resources duplicate content from the src directory. + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } jar { - // The resources duplicate content from the src directory. - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + // The resources duplicate content from the src directory. + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } configurations { - implementation.extendsFrom(annotatedGuava) + implementation.extendsFrom(annotatedGuava) } dependencies { - api project(':javacutil') - api project(':dataflow') - // At the moement, there are no differences between eisop and typetools stubparsers. - api 'org.checkerframework:stubparser:3.25.10' - - // NO-AFU - /* - // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. - // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite - api('org.checkerframework:annotation-file-utilities:*') { - exclude group: 'com.google.errorprone', module: 'javac' - } - */ - - api project(':checker-qual') - - // External dependencies: - // If you add an external dependency, you must shadow its packages. - // See the comment in ../build.gradle in the shadowJar block. - implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" - implementation "org.plumelib:plume-util:${versions.plumeUtil}" - implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" - // Add this dependency if needed to debug classpath issues. - // implementation 'io.github.classgraph:classgraph:4.8.172' - - testImplementation "junit:junit:${versions.junit}" - testImplementation project(':framework-test') - testImplementation sourceSets.testannotations.output - - // AutoValue support in Returns Receiver Checker - testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" - testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" - - // Lombok support in Returns Receiver Checker - testImplementation "org.projectlombok:lombok:${versions.lombok}" + api project(':javacutil') + api project(':dataflow') + // At the moement, there are no differences between eisop and typetools stubparsers. + api 'org.checkerframework:stubparser:3.25.10' + + // NO-AFU + /* + // AFU is an "includedBuild" imported in checker-framework/settings.gradle, so the version number doesn't matter. + // https://docs.gradle.org/current/userguide/composite_builds.html#settings_defined_composite + api('org.checkerframework:annotation-file-utilities:*') { + exclude group: 'com.google.errorprone', module: 'javac' + } + */ + + api project(':checker-qual') + + // External dependencies: + // If you add an external dependency, you must shadow its packages. + // See the comment in ../build.gradle in the shadowJar block. + implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" + implementation "org.plumelib:plume-util:${versions.plumeUtil}" + implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" + // Add this dependency if needed to debug classpath issues. + // implementation 'io.github.classgraph:classgraph:4.8.172' + + testImplementation "junit:junit:${versions.junit}" + testImplementation project(':framework-test') + testImplementation sourceSets.testannotations.output + + // AutoValue support in Returns Receiver Checker + testImplementation "com.google.auto.value:auto-value-annotations:${versions.autoValue}" + testImplementation "com.google.auto.value:auto-value:${versions.autoValue}" + + // Lombok support in Returns Receiver Checker + testImplementation "org.projectlombok:lombok:${versions.lombok}" } // Enable exec/javaexec interface InjectedExecOps { - @Inject - ExecOperations getExecOps() + @Inject + ExecOperations getExecOps() } task cloneAnnotatedJdk() { - description = 'Obtain or update the annotated JDK.' - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - if (file(annotatedJdkHome).exists()) { - injected.execOps.exec { - workingDir file(annotatedJdkHome) - executable 'git' - args = ['pull', '-q'] - ignoreExitValue = true - } - } else { - println "Cloning annotated JDK repository in ${annotatedJdkHome}/../" - injected.execOps.exec { - workingDir file("${annotatedJdkHome}/../") - executable 'git' - args = [ - 'clone', - '-q', - '--filter=blob:none', - 'https://github.com/eisop/jdk.git', - 'jdk' - ] - } + description = 'Obtain or update the annotated JDK.' + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + if (file(annotatedJdkHome).exists()) { + injected.execOps.exec { + workingDir file(annotatedJdkHome) + executable 'git' + args = ['pull', '-q'] + ignoreExitValue = true + } + } else { + println "Cloning annotated JDK repository in ${annotatedJdkHome}/../" + injected.execOps.exec { + workingDir file("${annotatedJdkHome}/../") + executable 'git' + args = [ + 'clone', + '-q', + '--filter=blob:none', + 'https://github.com/eisop/jdk.git', + 'jdk' + ] + } + } } - } } task copyAndMinimizeAnnotatedJdkFiles(dependsOn: cloneAnnotatedJdk, group: 'Build') { - dependsOn ':framework:compileJava' - // we need the next two dependencies because we run JavaStubifier using this project's runtimeClasspath, - // which refers to the jars for these other projects - dependsOn ':javacutil:jar' - dependsOn ':dataflow:jar' - def inputDir = "${annotatedJdkHome}/src" - def outputDir = "${buildDir}/generated/resources/annotated-jdk/" - - description = "Copy annotated JDK files to ${outputDir}. Removes private and package-private methods, method bodies, comments, etc. from the annotated JDK" - - inputs.dir file(inputDir) - outputs.dir file(outputDir) - - def injected = project.objects.newInstance(InjectedExecOps) - - doLast { - FileTree tree = fileTree(dir: inputDir) - NavigableSet annotatedForFiles = new TreeSet<>(); - tree.visit { FileVisitDetails fvd -> - if (!fvd.file.isDirectory() && fvd.file.name.matches('.*\\.java') - && !fvd.file.path.contains('org/checkerframework')) { - fvd.getFile().readLines().any { line -> - if (line.contains('@AnnotatedFor') || line.contains('org.checkerframework') || line.contains('org.jspecify')) { - annotatedForFiles.add(fvd.file.absolutePath) - return true; - } + dependsOn ':framework:compileJava' + // we need the next two dependencies because we run JavaStubifier using this project's runtimeClasspath, + // which refers to the jars for these other projects + dependsOn ':javacutil:jar' + dependsOn ':dataflow:jar' + def inputDir = "${annotatedJdkHome}/src" + def outputDir = "${buildDir}/generated/resources/annotated-jdk/" + + description = "Copy annotated JDK files to ${outputDir}. Removes private and package-private methods, method bodies, comments, etc. from the annotated JDK" + + inputs.dir file(inputDir) + outputs.dir file(outputDir) + + def injected = project.objects.newInstance(InjectedExecOps) + + doLast { + FileTree tree = fileTree(dir: inputDir) + NavigableSet annotatedForFiles = new TreeSet<>(); + tree.visit { FileVisitDetails fvd -> + if (!fvd.file.isDirectory() && fvd.file.name.matches('.*\\.java') + && !fvd.file.path.contains('org/checkerframework')) { + fvd.getFile().readLines().any { line -> + if (line.contains('@AnnotatedFor') || line.contains('org.checkerframework') || line.contains('org.jspecify')) { + annotatedForFiles.add(fvd.file.absolutePath) + return true; + } + } + } } - } - } - String absolutejdkHome = file(annotatedJdkHome).absolutePath - int jdkDirStringSize = absolutejdkHome.size() - copy { - from(annotatedJdkHome) - into(outputDir) - for (String filename : annotatedForFiles) { - include filename.substring(jdkDirStringSize) - } - } - injected.execOps.javaexec { - classpath = sourceSets.main.runtimeClasspath - standardOutput = System.out - errorOutput = System.err + String absolutejdkHome = file(annotatedJdkHome).absolutePath + int jdkDirStringSize = absolutejdkHome.size() + copy { + from(annotatedJdkHome) + into(outputDir) + for (String filename : annotatedForFiles) { + include filename.substring(jdkDirStringSize) + } + } + injected.execOps.javaexec { + classpath = sourceSets.main.runtimeClasspath + standardOutput = System.out + errorOutput = System.err - mainClass = 'org.checkerframework.framework.stub.JavaStubifier' - args outputDir + mainClass = 'org.checkerframework.framework.stub.JavaStubifier' + args outputDir + } } - } } sourcesJar.dependsOn(copyAndMinimizeAnnotatedJdkFiles) @@ -179,108 +179,108 @@ sourcesJar.dependsOn(copyAndMinimizeAnnotatedJdkFiles) processResources.dependsOn(copyAndMinimizeAnnotatedJdkFiles) task allSourcesJar(type: Jar, group: 'Build') { - description = 'Creates a sources jar that includes sources for all Checker Framework classes in framework.jar' - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'framework-source.jar' - from (project(':framework').sourceSets.main.java, - project(':dataflow').sourceSets.main.allJava, - project(':javacutil').sourceSets.main.allJava) + description = 'Creates a sources jar that includes sources for all Checker Framework classes in framework.jar' + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'framework-source.jar' + from (project(':framework').sourceSets.main.java, + project(':dataflow').sourceSets.main.allJava, + project(':javacutil').sourceSets.main.allJava) } task allJavadocJar(type: Jar, group: 'Build') { - description = 'Creates javadoc jar include Javadoc for all of the framework' - dependsOn (project(':framework').tasks.javadoc, - project(':dataflow').tasks.javadoc, - project(':javacutil').tasks.javadoc) - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'framework-javadoc.jar' - from (project(':framework').tasks.javadoc.destinationDir, - project(':dataflow').tasks.javadoc.destinationDir, - project(':javacutil').tasks.javadoc.destinationDir) + description = 'Creates javadoc jar include Javadoc for all of the framework' + dependsOn (project(':framework').tasks.javadoc, + project(':dataflow').tasks.javadoc, + project(':javacutil').tasks.javadoc) + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'framework-javadoc.jar' + from (project(':framework').tasks.javadoc.destinationDir, + project(':dataflow').tasks.javadoc.destinationDir, + project(':javacutil').tasks.javadoc.destinationDir) } shadowJar { - description = 'Creates the "fat" framework.jar in dist' - destinationDirectory = file("${projectDir}/dist") - archiveFileName = 'framework.jar' - manifest { - attributes('Automatic-Module-Name': 'org.checkerframework.framework') - } - - minimize() + description = 'Creates the "fat" framework.jar in dist' + destinationDirectory = file("${projectDir}/dist") + archiveFileName = 'framework.jar' + manifest { + attributes('Automatic-Module-Name': 'org.checkerframework.framework') + } + + minimize() } createCheckTypeTask(project.name, 'CompilerMessages', - 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') + 'org.checkerframework.checker.compilermsgs.CompilerMessagesChecker') checkCompilerMessages { - options.compilerArgs += [ - '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath - ] + options.compilerArgs += [ + '-Apropfiles=' + sourceSets.main.resources.filter { file -> file.name.equals('messages.properties') }.asPath + ] } task loaderTests(dependsOn: 'shadowJar', group: 'Verification') { - description = 'Run tests for the annotation class loader' - dependsOn(compileTestJava) - - def injected = project.objects.newInstance(InjectedExecOps) - - // TODO: this dependency on checker is a bit ugly. - dependsOn project(':checker-qual').tasks.jar - dependsOn project(':checker').tasks.assemble - doLast { - injected.execOps.exec { - workingDir = file('tests/annotationclassloader') - commandLine 'make', 'all' + description = 'Run tests for the annotation class loader' + dependsOn(compileTestJava) + + def injected = project.objects.newInstance(InjectedExecOps) + + // TODO: this dependency on checker is a bit ugly. + dependsOn project(':checker-qual').tasks.jar + dependsOn project(':checker').tasks.assemble + doLast { + injected.execOps.exec { + workingDir = file('tests/annotationclassloader') + commandLine 'make', 'all' + } } - } } clean { - def injected = project.objects.newInstance(InjectedExecOps) - - delete('tests/returnsreceiverdelomboked') - delete('dist') - delete('tests/build') - doLast { - injected.execOps.exec { - workingDir = file('tests/annotationclassloader') - commandLine 'make', 'clean' + def injected = project.objects.newInstance(InjectedExecOps) + + delete('tests/returnsreceiverdelomboked') + delete('dist') + delete('tests/build') + doLast { + injected.execOps.exec { + workingDir = file('tests/annotationclassloader') + commandLine 'make', 'clean' + } } - } } task delombok { - description = 'Delomboks the source code tree in tests/returnsreceiverlombok' + description = 'Delomboks the source code tree in tests/returnsreceiverlombok' - def srcDelomboked = 'tests/returnsreceiverdelomboked' - def srcJava = 'tests/returnsreceiverlombok' + def srcDelomboked = 'tests/returnsreceiverdelomboked' + def srcJava = 'tests/returnsreceiverlombok' - inputs.files file(srcJava) - outputs.dir file(srcDelomboked) + inputs.files file(srcJava) + outputs.dir file(srcDelomboked) - // This dependency is required to ensure the checker-qual jar exists, - // to prevent lombok from emitting "cannot find symbol" errors for @This - // annotations in the test input code. - dependsOn project(':checker-qual').tasks.jar + // This dependency is required to ensure the checker-qual jar exists, + // to prevent lombok from emitting "cannot find symbol" errors for @This + // annotations in the test input code. + dependsOn project(':checker-qual').tasks.jar - doLast { - if(!skipDelombok) { - def collection = files(configurations.testCompileClasspath) - ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', - classpath: collection.asPath) - ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) + doLast { + if(!skipDelombok) { + def collection = files(configurations.testCompileClasspath) + ant.taskdef(name: 'delombok', classname: 'lombok.delombok.ant.Tasks$Delombok', + classpath: collection.asPath) + ant.delombok(from: srcJava, to: srcDelomboked, classpath: collection.asPath) + } } - } } if (skipDelombok) { - delombok.enabled = false - test { - exclude '**/ReturnsReceiverLombokTest.java' - } + delombok.enabled = false + test { + exclude '**/ReturnsReceiverLombokTest.java' + } } else { - tasks.test.dependsOn('delombok') + tasks.test.dependsOn('delombok') } diff --git a/framework/jtreg/DOTCFGVisualizerForVarargsTest.java b/framework/jtreg/DOTCFGVisualizerForVarargsTest.java index 845284f95bc..1f57315e077 100644 --- a/framework/jtreg/DOTCFGVisualizerForVarargsTest.java +++ b/framework/jtreg/DOTCFGVisualizerForVarargsTest.java @@ -7,15 +7,15 @@ public class DOTCFGVisualizerForVarargsTest { - public DOTCFGVisualizerForVarargsTest(Object... objs) {} + public DOTCFGVisualizerForVarargsTest(Object... objs) {} - public static void method(Object... objs) {} + public static void method(Object... objs) {} - public void call() { - new DOTCFGVisualizerForVarargsTest(); - new DOTCFGVisualizerForVarargsTest(1, 2); + public void call() { + new DOTCFGVisualizerForVarargsTest(); + new DOTCFGVisualizerForVarargsTest(1, 2); - method(); - method("", null); - } + method(); + method("", null); + } } diff --git a/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java b/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java index 01c980edc22..230348c6a09 100644 --- a/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java +++ b/framework/jtreg/StubParserEnum/AnnotationFileParserEnumTest.java @@ -9,43 +9,44 @@ import static java.util.concurrent.TimeUnit.*; -import java.util.concurrent.TimeUnit; import org.checkerframework.common.util.report.qual.*; +import java.util.concurrent.TimeUnit; + public class AnnotationFileParserEnumTest { - @SuppressWarnings("report") - enum MyTimeUnit { - NANOSECONDS, - MICROSECONDS, - @ReportReadWrite - MILLISECONDS, - @ReportReadWrite - SECONDS; - - @ReportCall - long toMicros(long d) { - return d; + @SuppressWarnings("report") + enum MyTimeUnit { + NANOSECONDS, + MICROSECONDS, + @ReportReadWrite + MILLISECONDS, + @ReportReadWrite + SECONDS; + + @ReportCall + long toMicros(long d) { + return d; + } + } + + void readFromEnumInSource() { + MyTimeUnit u1 = MyTimeUnit.SECONDS; + MyTimeUnit u2 = MyTimeUnit.MILLISECONDS; + MyTimeUnit u3 = MyTimeUnit.MICROSECONDS; + MyTimeUnit u4 = MyTimeUnit.NANOSECONDS; + long sUS = MyTimeUnit.SECONDS.toMicros(10); + } + + void readFromEnumInStub() { + TimeUnit u1 = TimeUnit.SECONDS; + TimeUnit u2 = MILLISECONDS; + TimeUnit u3 = TimeUnit.MICROSECONDS; + TimeUnit u4 = NANOSECONDS; + long sUS = TimeUnit.SECONDS.toMicros(10); + long sNS = SECONDS.toNanos(10); + long msMS = TimeUnit.MILLISECONDS.toMillis(10); + long msUS = TimeUnit.MILLISECONDS.toMicros(10); + long msNS = MILLISECONDS.toNanos(10); } - } - - void readFromEnumInSource() { - MyTimeUnit u1 = MyTimeUnit.SECONDS; - MyTimeUnit u2 = MyTimeUnit.MILLISECONDS; - MyTimeUnit u3 = MyTimeUnit.MICROSECONDS; - MyTimeUnit u4 = MyTimeUnit.NANOSECONDS; - long sUS = MyTimeUnit.SECONDS.toMicros(10); - } - - void readFromEnumInStub() { - TimeUnit u1 = TimeUnit.SECONDS; - TimeUnit u2 = MILLISECONDS; - TimeUnit u3 = TimeUnit.MICROSECONDS; - TimeUnit u4 = NANOSECONDS; - long sUS = TimeUnit.SECONDS.toMicros(10); - long sNS = SECONDS.toNanos(10); - long msMS = TimeUnit.MILLISECONDS.toMillis(10); - long msUS = TimeUnit.MILLISECONDS.toMicros(10); - long msNS = MILLISECONDS.toNanos(10); - } } diff --git a/framework/jtreg/issue845/checker/qual/NotInPackageTop.java b/framework/jtreg/issue845/checker/qual/NotInPackageTop.java index 54254994a65..7b4f8a9eb0c 100644 --- a/framework/jtreg/issue845/checker/qual/NotInPackageTop.java +++ b/framework/jtreg/issue845/checker/qual/NotInPackageTop.java @@ -1,10 +1,11 @@ package qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) @DefaultQualifierInHierarchy diff --git a/framework/jtreg/variablenamedefault/lib/Test.java b/framework/jtreg/variablenamedefault/lib/Test.java index 68143680a5f..a676fe9bc37 100644 --- a/framework/jtreg/variablenamedefault/lib/Test.java +++ b/framework/jtreg/variablenamedefault/lib/Test.java @@ -1,5 +1,5 @@ package lib; public class Test { - public static void method(int middle, int notmiddle) {} + public static void method(int middle, int notmiddle) {} } diff --git a/framework/jtreg/variablenamedefault/use/UseTest.java b/framework/jtreg/variablenamedefault/use/UseTest.java index 1a2fb727896..c3e0eecf43a 100644 --- a/framework/jtreg/variablenamedefault/use/UseTest.java +++ b/framework/jtreg/variablenamedefault/use/UseTest.java @@ -1,10 +1,11 @@ package use; import lib.Test; + import org.checkerframework.framework.testchecker.variablenamedefault.quals.*; public class UseTest { - void testParameters(@VariableNameDefaultTop int t) { - Test.method(t, t); - } + void testParameters(@VariableNameDefaultTop int t) { + Test.method(t, t); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java index 90df4549afe..daa1705630a 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnalysis.java @@ -1,45 +1,46 @@ package org.checkerframework.common.accumulation; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.type.TypeMirror; + /** * This class only contains boilerplate code to permit AccumulationValue's accumulatedValues * functionality to interact with the rest of an accumulation type system. */ public class AccumulationAnalysis - extends CFAbstractAnalysis { + extends CFAbstractAnalysis { - /** - * Constructs an AccumulationAnalysis. - * - * @param checker the checker - * @param factory the type factory - */ - public AccumulationAnalysis(BaseTypeChecker checker, AccumulationAnnotatedTypeFactory factory) { - super(checker, factory); - } + /** + * Constructs an AccumulationAnalysis. + * + * @param checker the checker + * @param factory the type factory + */ + public AccumulationAnalysis(BaseTypeChecker checker, AccumulationAnnotatedTypeFactory factory) { + super(checker, factory); + } - @Override - public AccumulationStore createEmptyStore(boolean sequentialSemantics) { - return new AccumulationStore(this, sequentialSemantics); - } + @Override + public AccumulationStore createEmptyStore(boolean sequentialSemantics) { + return new AccumulationStore(this, sequentialSemantics); + } - @Override - public AccumulationStore createCopiedStore(AccumulationStore accumulationStore) { - return new AccumulationStore(accumulationStore); - } + @Override + public AccumulationStore createCopiedStore(AccumulationStore accumulationStore) { + return new AccumulationStore(accumulationStore); + } - @Override - public @Nullable AccumulationValue createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType) { - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; + @Override + public @Nullable AccumulationValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; + } + return new AccumulationValue(this, annotations, underlyingType); } - return new AccumulationValue(this, annotations, underlyingType); - } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java index 90c24749222..d1bd9234816 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java @@ -8,16 +8,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.accumulation.AccumulationChecker.AliasAnalysis; @@ -40,6 +31,18 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.util.CollectionsPlume; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; + /** * An annotated type factory for an accumulation checker. * @@ -48,655 +51,666 @@ * #postInit()}. */ public abstract class AccumulationAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory< - AccumulationValue, AccumulationStore, AccumulationTransfer, AccumulationAnalysis> { - - /** The typechecker associated with this factory. */ - public final AccumulationChecker accumulationChecker; - - /** - * The canonical top annotation for this accumulation checker: an instance of the accumulator - * annotation with no arguments. - */ - public final AnnotationMirror top; - - /** The canonical bottom annotation for this accumulation checker. */ - public final AnnotationMirror bottom; - - /** - * The annotation that accumulates things in this accumulation checker. Must be an annotation with - * exactly one field named "value" whose type is a String array. - */ - private final Class accumulator; - - /** - * The predicate annotation for this accumulation analysis, or null if predicates are not - * supported. A predicate annotation must have a single element named "value" of type String. - */ - private final @MonotonicNonNull Class predicate; - - /** - * Create an annotated type factory for an accumulation checker. - * - * @param checker the checker - * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single - * argument named "value" whose type is a String array. - * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code accumulator}. - * The bottom type should be an annotation with no arguments. - * @param predicate the predicate annotation. Either null (if predicates are not supported), or an - * annotation with a single element named "value" whose type is a String. - */ - protected AccumulationAnnotatedTypeFactory( - BaseTypeChecker checker, - Class accumulator, - Class bottom, - @Nullable Class predicate) { - super(checker); - if (!(checker instanceof AccumulationChecker)) { - throw new TypeSystemError( - "AccumulationAnnotatedTypeFactory cannot be used with a checker " - + "class that is not a subtype of AccumulationChecker. Found class: " - + checker.getClass()); - } - this.accumulationChecker = (AccumulationChecker) checker; + extends GenericAnnotatedTypeFactory< + AccumulationValue, AccumulationStore, AccumulationTransfer, AccumulationAnalysis> { - this.accumulator = accumulator; - // Check that the requirements of the accumulator are met. - Method[] accDeclaredMethods = accumulator.getDeclaredMethods(); - if (accDeclaredMethods.length != 1) { - rejectMalformedAccumulator("have exactly one element"); - } + /** The typechecker associated with this factory. */ + public final AccumulationChecker accumulationChecker; + + /** + * The canonical top annotation for this accumulation checker: an instance of the accumulator + * annotation with no arguments. + */ + public final AnnotationMirror top; + + /** The canonical bottom annotation for this accumulation checker. */ + public final AnnotationMirror bottom; + + /** + * The annotation that accumulates things in this accumulation checker. Must be an annotation + * with exactly one field named "value" whose type is a String array. + */ + private final Class accumulator; + + /** + * The predicate annotation for this accumulation analysis, or null if predicates are not + * supported. A predicate annotation must have a single element named "value" of type String. + */ + private final @MonotonicNonNull Class predicate; - Method accValue = accDeclaredMethods[0]; - if (accValue.getName() != "value") { // interned - rejectMalformedAccumulator("name its element \"value\""); + /** + * Create an annotated type factory for an accumulation checker. + * + * @param checker the checker + * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single + * argument named "value" whose type is a String array. + * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code + * accumulator}. The bottom type should be an annotation with no arguments. + * @param predicate the predicate annotation. Either null (if predicates are not supported), or + * an annotation with a single element named "value" whose type is a String. + */ + protected AccumulationAnnotatedTypeFactory( + BaseTypeChecker checker, + Class accumulator, + Class bottom, + @Nullable Class predicate) { + super(checker); + if (!(checker instanceof AccumulationChecker)) { + throw new TypeSystemError( + "AccumulationAnnotatedTypeFactory cannot be used with a checker " + + "class that is not a subtype of AccumulationChecker. Found class: " + + checker.getClass()); + } + this.accumulationChecker = (AccumulationChecker) checker; + + this.accumulator = accumulator; + // Check that the requirements of the accumulator are met. + Method[] accDeclaredMethods = accumulator.getDeclaredMethods(); + if (accDeclaredMethods.length != 1) { + rejectMalformedAccumulator("have exactly one element"); + } + + Method accValue = accDeclaredMethods[0]; + if (accValue.getName() != "value") { // interned + rejectMalformedAccumulator("name its element \"value\""); + } + if (!accValue.getReturnType().isInstance(new String[0])) { + rejectMalformedAccumulator("have an element of type String[]"); + } + if (accValue.getDefaultValue() == null + || ((String[]) accValue.getDefaultValue()).length != 0) { + rejectMalformedAccumulator("have the empty String array {} as its default value"); + } + + this.predicate = predicate; + // If there is a predicate annotation, check that its requirements are met. + if (predicate != null) { + Method[] predDeclaredMethods = predicate.getDeclaredMethods(); + if (predDeclaredMethods.length != 1) { + rejectMalformedPredicate("have exactly one element"); + } + Method predValue = predDeclaredMethods[0]; + if (predValue.getName() != "value") { // interned + rejectMalformedPredicate("name its element \"value\""); + } + if (!predValue.getReturnType().isInstance("")) { + rejectMalformedPredicate("have an element of type String"); + } + } + + this.bottom = AnnotationBuilder.fromClass(elements, bottom); + this.top = createAccumulatorAnnotation(Collections.emptyList()); + + // Every subclass must call postInit! This does not do so. } - if (!accValue.getReturnType().isInstance(new String[0])) { - rejectMalformedAccumulator("have an element of type String[]"); + + /** + * Create an annotated type factory for an accumulation checker. + * + * @param checker the checker + * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single + * argument named "value" whose type is a String array. + * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code + * accumulator}. The bottom type should be an annotation with no arguments. + */ + protected AccumulationAnnotatedTypeFactory( + BaseTypeChecker checker, + Class accumulator, + Class bottom) { + this(checker, accumulator, bottom, null); } - if (accValue.getDefaultValue() == null || ((String[]) accValue.getDefaultValue()).length != 0) { - rejectMalformedAccumulator("have the empty String array {} as its default value"); + + /** + * Common error message for malformed accumulator annotation. + * + * @param missing what is missing from the accumulator, suitable for use in this string to + * replace $MISSING$: "The accumulator annotation Foo must $MISSING$." + */ + private void rejectMalformedAccumulator(String missing) { + rejectMalformedAnno("accumulator", accumulator, missing); } - this.predicate = predicate; - // If there is a predicate annotation, check that its requirements are met. - if (predicate != null) { - Method[] predDeclaredMethods = predicate.getDeclaredMethods(); - if (predDeclaredMethods.length != 1) { - rejectMalformedPredicate("have exactly one element"); - } - Method predValue = predDeclaredMethods[0]; - if (predValue.getName() != "value") { // interned - rejectMalformedPredicate("name its element \"value\""); - } - if (!predValue.getReturnType().isInstance("")) { - rejectMalformedPredicate("have an element of type String"); - } + /** + * Common error message for malformed predicate annotation. + * + * @param missing what is missing from the predicate, suitable for use in this string to replace + * $MISSING$: "The predicate annotation Foo must $MISSING$." + */ + private void rejectMalformedPredicate(String missing) { + rejectMalformedAnno("predicate", predicate, missing); } - this.bottom = AnnotationBuilder.fromClass(elements, bottom); - this.top = createAccumulatorAnnotation(Collections.emptyList()); - - // Every subclass must call postInit! This does not do so. - } - - /** - * Create an annotated type factory for an accumulation checker. - * - * @param checker the checker - * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single - * argument named "value" whose type is a String array. - * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code accumulator}. - * The bottom type should be an annotation with no arguments. - */ - protected AccumulationAnnotatedTypeFactory( - BaseTypeChecker checker, - Class accumulator, - Class bottom) { - this(checker, accumulator, bottom, null); - } - - /** - * Common error message for malformed accumulator annotation. - * - * @param missing what is missing from the accumulator, suitable for use in this string to replace - * $MISSING$: "The accumulator annotation Foo must $MISSING$." - */ - private void rejectMalformedAccumulator(String missing) { - rejectMalformedAnno("accumulator", accumulator, missing); - } - - /** - * Common error message for malformed predicate annotation. - * - * @param missing what is missing from the predicate, suitable for use in this string to replace - * $MISSING$: "The predicate annotation Foo must $MISSING$." - */ - private void rejectMalformedPredicate(String missing) { - rejectMalformedAnno("predicate", predicate, missing); - } - - /** - * Common error message implementation. Call rejectMalformedAccumulator or - * rejectMalformedPredicate as appropriate, rather than this method directly. - * - * @param annoTypeName the display name for the type of malformed annotation, such as - * "accumulator" - * @param anno the malformed annotation - * @param missing what is missing from the annotation, suitable for use in this string to replace - * $MISSING$: "The accumulator annotation Foo must $MISSING$." - */ - private void rejectMalformedAnno( - String annoTypeName, Class anno, String missing) { - throw new BugInCF("The " + annoTypeName + " annotation " + anno + " must " + missing + "."); - } - - /** - * Creates a new instance of the accumulator annotation that contains the elements of {@code - * values}. - * - * @param values the arguments to the annotation. The values can contain duplicates and can be in - * any order. - * @return an annotation mirror representing the accumulator annotation with {@code values}'s - * arguments; this is top if {@code values} is empty - */ - public AnnotationMirror createAccumulatorAnnotation(List values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); - builder.setValue("value", CollectionsPlume.withoutDuplicatesSorted(values)); - return builder.build(); - } - - /** - * Creates a new instance of the accumulator annotation that contains exactly one value. - * - * @param value the argument to the annotation - * @return an annotation mirror representing the accumulator annotation with {@code value} as its - * argument - */ - public AnnotationMirror createAccumulatorAnnotation(String value) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); - builder.setValue("value", Collections.singletonList(value)); - return builder.build(); - } - - /** - * Returns true if the return type of the given method invocation tree has an @This annotation - * from the Returns Receiver Checker. - * - * @param tree a method invocation tree - * @return true if the method being invoked returns its receiver - */ - public boolean returnsThis(MethodInvocationTree tree) { - if (!accumulationChecker.isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { - return false; + /** + * Common error message implementation. Call rejectMalformedAccumulator or + * rejectMalformedPredicate as appropriate, rather than this method directly. + * + * @param annoTypeName the display name for the type of malformed annotation, such as + * "accumulator" + * @param anno the malformed annotation + * @param missing what is missing from the annotation, suitable for use in this string to + * replace $MISSING$: "The accumulator annotation Foo must $MISSING$." + */ + private void rejectMalformedAnno( + String annoTypeName, Class anno, String missing) { + throw new BugInCF("The " + annoTypeName + " annotation " + anno + " must " + missing + "."); } - // Must call `getTypeFactoryOfSubchecker` each time, not store and reuse. - ReturnsReceiverAnnotatedTypeFactory rrATF = - getTypeFactoryOfSubchecker(ReturnsReceiverChecker.class); - ExecutableElement methodEle = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType methodAtm = rrATF.getAnnotatedType(methodEle); - AnnotatedTypeMirror rrType = methodAtm.getReturnType(); - return rrType != null && rrType.hasAnnotation(This.class); - } - - /** - * Is the given annotation an accumulator annotation? Returns false if the argument is {@link - * #bottom}. - * - * @param anm an annotation mirror - * @return true if the annotation mirror is an instance of this factory's accumulator annotation - */ - public boolean isAccumulatorAnnotation(AnnotationMirror anm) { - return areSameByClass(anm, accumulator); - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(super.createTreeAnnotator(), new AccumulationTreeAnnotator(this)); - } - - /** - * This tree annotator implements the following rule(s): - * - *
          - *
          RRA - *
          If a method returns its receiver, and the receiver has an accumulation type, then the - * default type of the method's return value is the type of the receiver. - *
          - */ - protected class AccumulationTreeAnnotator extends TreeAnnotator { /** - * Creates an instance of this tree annotator for the given type factory. + * Creates a new instance of the accumulator annotation that contains the elements of {@code + * values}. * - * @param factory the type factory + * @param values the arguments to the annotation. The values can contain duplicates and can be + * in any order. + * @return an annotation mirror representing the accumulator annotation with {@code values}'s + * arguments; this is top if {@code values} is empty */ - public AccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { - super(factory); + public AnnotationMirror createAccumulatorAnnotation(List values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); + builder.setValue("value", CollectionsPlume.withoutDuplicatesSorted(values)); + return builder.build(); } /** - * Implements rule RRA. + * Creates a new instance of the accumulator annotation that contains exactly one value. * - *

          This implementation propagates types from the receiver to the return value, without - * change. Subclasses may override this method to also add additional properties, as - * appropriate. + * @param value the argument to the annotation + * @return an annotation mirror representing the accumulator annotation with {@code value} as + * its argument + */ + public AnnotationMirror createAccumulatorAnnotation(String value) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, accumulator); + builder.setValue("value", Collections.singletonList(value)); + return builder.build(); + } + + /** + * Returns true if the return type of the given method invocation tree has an @This annotation + * from the Returns Receiver Checker. * * @param tree a method invocation tree - * @param type the type of {@code tree} (i.e. the return type of the invoked method). Is - * (possibly) side-effected by this method. - * @return nothing, works by side-effect on {@code type} + * @return true if the method being invoked returns its receiver */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (returnsThis(tree)) { - // There is a @This annotation on the return type of the invoked method. - ExpressionTree receiverTree = TreeUtils.getReceiverTree(tree.getMethodSelect()); - AnnotationMirror returnAnno = type.getAnnotationInHierarchy(top); - AnnotationMirror glbAnno; - if (receiverTree == null) { - glbAnno = returnAnno; - } else { - AnnotatedTypeMirror receiverType = getAnnotatedType(receiverTree); - // The current type of the receiver, or top if none exists. - AnnotationMirror receiverAnno = receiverType.getAnnotationInHierarchy(top); - glbAnno = - qualHierarchy.greatestLowerBoundShallow( - returnAnno, - type.getUnderlyingType(), - receiverAnno, - receiverType.getUnderlyingType()); + public boolean returnsThis(MethodInvocationTree tree) { + if (!accumulationChecker.isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { + return false; } + // Must call `getTypeFactoryOfSubchecker` each time, not store and reuse. + ReturnsReceiverAnnotatedTypeFactory rrATF = + getTypeFactoryOfSubchecker(ReturnsReceiverChecker.class); + ExecutableElement methodEle = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType methodAtm = rrATF.getAnnotatedType(methodEle); + AnnotatedTypeMirror rrType = methodAtm.getReturnType(); + return rrType != null && rrType.hasAnnotation(This.class); + } - type.replaceAnnotation(glbAnno); - } - return super.visitMethodInvocation(tree, type); + /** + * Is the given annotation an accumulator annotation? Returns false if the argument is {@link + * #bottom}. + * + * @param anm an annotation mirror + * @return true if the annotation mirror is an instance of this factory's accumulator annotation + */ + public boolean isAccumulatorAnnotation(AnnotationMirror anm) { + return areSameByClass(anm, accumulator); } - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new AccumulationQualifierHierarchy(this.getSupportedTypeQualifiers(), this.elements); - } - - /** - * Returns all the values that anno has accumulated. - * - * @param anno an accumulator annotation; must not be bottom - * @return the list of values the annotation has accumulated; it is a new list, so it is safe for - * clients to side-effect - */ - public List getAccumulatedValues(AnnotationMirror anno) { - if (!isAccumulatorAnnotation(anno)) { - throw new BugInCF(anno + " isn't an accumulator annotation"); + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new AccumulationTreeAnnotator(this)); + } + + /** + * This tree annotator implements the following rule(s): + * + *

          + *
          RRA + *
          If a method returns its receiver, and the receiver has an accumulation type, then the + * default type of the method's return value is the type of the receiver. + *
          + */ + protected class AccumulationTreeAnnotator extends TreeAnnotator { + + /** + * Creates an instance of this tree annotator for the given type factory. + * + * @param factory the type factory + */ + public AccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { + super(factory); + } + + /** + * Implements rule RRA. + * + *

          This implementation propagates types from the receiver to the return value, without + * change. Subclasses may override this method to also add additional properties, as + * appropriate. + * + * @param tree a method invocation tree + * @param type the type of {@code tree} (i.e. the return type of the invoked method). Is + * (possibly) side-effected by this method. + * @return nothing, works by side-effect on {@code type} + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (returnsThis(tree)) { + // There is a @This annotation on the return type of the invoked method. + ExpressionTree receiverTree = TreeUtils.getReceiverTree(tree.getMethodSelect()); + AnnotationMirror returnAnno = type.getAnnotationInHierarchy(top); + AnnotationMirror glbAnno; + if (receiverTree == null) { + glbAnno = returnAnno; + } else { + AnnotatedTypeMirror receiverType = getAnnotatedType(receiverTree); + // The current type of the receiver, or top if none exists. + AnnotationMirror receiverAnno = receiverType.getAnnotationInHierarchy(top); + glbAnno = + qualHierarchy.greatestLowerBoundShallow( + returnAnno, + type.getUnderlyingType(), + receiverAnno, + receiverType.getUnderlyingType()); + } + + type.replaceAnnotation(glbAnno); + } + return super.visitMethodInvocation(tree, type); + } } - List values = - AnnotationUtils.getElementValueArrayOrNull(anno, "value", String.class, false); - if (values == null) { - return Collections.emptyList(); - } else { - return values; + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new AccumulationQualifierHierarchy(this.getSupportedTypeQualifiers(), this.elements); } - } - - /** - * Returns the accumulated values on the given (expression, usually) tree. This differs from - * calling {@link #getAnnotatedType(Tree)}, because this version takes into account accumulated - * methods that are stored on the value. This is useful when dealing with accumulated facts on - * variables whose types are type variables (because type variable types cannot be refined - * directly, due to the quirks of subtyping between type variables and its interactions with the - * qualified type system). - * - *

          The returned collection may be either a list or a set. - * - * @param tree a tree - * @return the accumulated values for the given tree, including those stored on the value - */ - public Collection getAccumulatedValues(Tree tree) { - AnnotatedTypeMirror type = getAnnotatedType(tree); - AnnotationMirror anno = type.getAnnotationInHierarchy(top); - if (anno != null && isAccumulatorAnnotation(anno)) { - return getAccumulatedValues(anno); - } else if (anno == null) { - // Handle type variables and wildcards. - AccumulationValue inferredValue = getInferredValueFor(tree); - if (inferredValue != null) { - Set accumulatedValues = inferredValue.getAccumulatedValues(); - if (accumulatedValues != null) { - return accumulatedValues; + + /** + * Returns all the values that anno has accumulated. + * + * @param anno an accumulator annotation; must not be bottom + * @return the list of values the annotation has accumulated; it is a new list, so it is safe + * for clients to side-effect + */ + public List getAccumulatedValues(AnnotationMirror anno) { + if (!isAccumulatorAnnotation(anno)) { + throw new BugInCF(anno + " isn't an accumulator annotation"); + } + List values = + AnnotationUtils.getElementValueArrayOrNull(anno, "value", String.class, false); + if (values == null) { + return Collections.emptyList(); + } else { + return values; } - } } - return Collections.emptyList(); - } - - /** - * All accumulation analyses share a similar type hierarchy. This class implements the subtyping, - * LUB, and GLB for that hierarchy. The lattice looks like: - * - *

          -   *       acc()
          -   *      /   \
          -   * acc(x)   acc(y) ...
          -   *      \   /
          -   *     acc(x,y) ...
          -   *        |
          -   *      bottom
          -   * 
          - * - * Predicate subtyping is defined as follows: - * - *
            - *
          • An accumulator is a subtype of a predicate if substitution from the accumulator to the - * predicate makes the predicate true. For example, {@code Acc(A)} is a subtype of {@code - * AccPred("A || B")}, because when A is replaced with {@code true} and B is replaced with - * {@code false}, the resulting boolean formula evaluates to true. - *
          • A predicate P is a subtype of an accumulator iff after converting the accumulator into a - * predicate representing the conjunction of its elements, P is a subtype of that predicate - * according to the rule for subtyping between two predicates defined below. - *
          • A predicate P is a subtype of another predicate Q iff P and Q are equal. An extension - * point ({@link #isPredicateSubtype(String, String)}) is provided to allow more complex - * subtyping behavior between predicates. (The "correct" subtyping rule is that P is a - * subtype of Q iff P implies Q. That rule would require an SMT solver in the general case, - * which is undesirable because it would require an external dependency. A user can override - * {@link #isPredicateSubtype(String, String)} if they require more precise subtyping; the - * check described here is overly conservative (and therefore sound), but not very precise.) - *
          - */ - protected class AccumulationQualifierHierarchy extends ElementQualifierHierarchy { /** - * Creates a ElementQualifierHierarchy from the given classes. + * Returns the accumulated values on the given (expression, usually) tree. This differs from + * calling {@link #getAnnotatedType(Tree)}, because this version takes into account accumulated + * methods that are stored on the value. This is useful when dealing with accumulated facts on + * variables whose types are type variables (because type variable types cannot be refined + * directly, due to the quirks of subtyping between type variables and its interactions with the + * qualified type system). * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils + *

          The returned collection may be either a list or a set. + * + * @param tree a tree + * @return the accumulated values for the given tree, including those stored on the value */ - protected AccumulationQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, AccumulationAnnotatedTypeFactory.this); + public Collection getAccumulatedValues(Tree tree) { + AnnotatedTypeMirror type = getAnnotatedType(tree); + AnnotationMirror anno = type.getAnnotationInHierarchy(top); + if (anno != null && isAccumulatorAnnotation(anno)) { + return getAccumulatedValues(anno); + } else if (anno == null) { + // Handle type variables and wildcards. + AccumulationValue inferredValue = getInferredValueFor(tree); + if (inferredValue != null) { + Set accumulatedValues = inferredValue.getAccumulatedValues(); + if (accumulatedValues != null) { + return accumulatedValues; + } + } + } + return Collections.emptyList(); } /** - * GLB in this type system is set union of the arguments of the two annotations, unless one of - * them is bottom, in which case the result is also bottom. + * All accumulation analyses share a similar type hierarchy. This class implements the + * subtyping, LUB, and GLB for that hierarchy. The lattice looks like: + * + *

          +     *       acc()
          +     *      /   \
          +     * acc(x)   acc(y) ...
          +     *      \   /
          +     *     acc(x,y) ...
          +     *        |
          +     *      bottom
          +     * 
          + * + * Predicate subtyping is defined as follows: + * + *
            + *
          • An accumulator is a subtype of a predicate if substitution from the accumulator to the + * predicate makes the predicate true. For example, {@code Acc(A)} is a subtype of {@code + * AccPred("A || B")}, because when A is replaced with {@code true} and B is replaced with + * {@code false}, the resulting boolean formula evaluates to true. + *
          • A predicate P is a subtype of an accumulator iff after converting the accumulator into + * a predicate representing the conjunction of its elements, P is a subtype of that + * predicate according to the rule for subtyping between two predicates defined below. + *
          • A predicate P is a subtype of another predicate Q iff P and Q are equal. An extension + * point ({@link #isPredicateSubtype(String, String)}) is provided to allow more complex + * subtyping behavior between predicates. (The "correct" subtyping rule is that P is a + * subtype of Q iff P implies Q. That rule would require an SMT solver in the general + * case, which is undesirable because it would require an external dependency. A user can + * override {@link #isPredicateSubtype(String, String)} if they require more precise + * subtyping; the check described here is overly conservative (and therefore sound), but + * not very precise.) + *
          */ - @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, bottom) || AnnotationUtils.areSame(a2, bottom)) { - return bottom; - } - - if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { - return a1; - } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { - return bottom; - } - - // If either is a predicate, then both should be converted to predicates and and-ed. - if (isPredicate(a1) || isPredicate(a2)) { - String a1Pred = convertToPredicate(a1); - String a2Pred = convertToPredicate(a2); - // check for top - if (a1Pred.isEmpty()) { - return a2; - } else if (a2Pred.isEmpty()) { - return a1; - } else { - return createPredicateAnnotation("(" + a1Pred + ") && (" + a2Pred + ")"); + protected class AccumulationQualifierHierarchy extends ElementQualifierHierarchy { + + /** + * Creates a ElementQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + protected AccumulationQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, AccumulationAnnotatedTypeFactory.this); + } + + /** + * GLB in this type system is set union of the arguments of the two annotations, unless one + * of them is bottom, in which case the result is also bottom. + */ + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, bottom) || AnnotationUtils.areSame(a2, bottom)) { + return bottom; + } + + if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { + return a1; + } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { + return bottom; + } + + // If either is a predicate, then both should be converted to predicates and and-ed. + if (isPredicate(a1) || isPredicate(a2)) { + String a1Pred = convertToPredicate(a1); + String a2Pred = convertToPredicate(a2); + // check for top + if (a1Pred.isEmpty()) { + return a2; + } else if (a2Pred.isEmpty()) { + return a1; + } else { + return createPredicateAnnotation("(" + a1Pred + ") && (" + a2Pred + ")"); + } + } + + List a1Val = getAccumulatedValues(a1); + List a2Val = getAccumulatedValues(a2); + // Avoid creating new annotation objects in the common case. + if (a1Val.containsAll(a2Val)) { + return a1; + } + if (a2Val.containsAll(a1Val)) { + return a2; + } + a1Val.addAll(a2Val); // union + return createAccumulatorAnnotation(a1Val); + } + + /** + * LUB in this type system is set intersection of the arguments of the two annotations, + * unless one of them is bottom, in which case the result is the other annotation. + */ + @Override + public AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (AnnotationUtils.areSame(a1, bottom)) { + return a2; + } else if (AnnotationUtils.areSame(a2, bottom)) { + return a1; + } + + if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { + return a1; + } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { + return top; + } + + // If either is a predicate, then both should be converted to predicates and or-ed. + if (isPredicate(a1) || isPredicate(a2)) { + String a1Pred = convertToPredicate(a1); + String a2Pred = convertToPredicate(a2); + // check for top + if (a1Pred.isEmpty()) { + return a1; + } else if (a2Pred.isEmpty()) { + return a2; + } else { + return createPredicateAnnotation("(" + a1Pred + ") || (" + a2Pred + ")"); + } + } + + List a1Val = getAccumulatedValues(a1); + List a2Val = getAccumulatedValues(a2); + // Avoid creating new annotation objects in the common case. + if (a1Val.containsAll(a2Val)) { + return a2; + } + if (a2Val.containsAll(a1Val)) { + return a1; + } + a1Val.retainAll(a2Val); // intersection + return createAccumulatorAnnotation(a1Val); + } + + /** + * {@inheritDoc} + * + *

          isSubtype in this type system is subset. + */ + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (AnnotationUtils.areSame(subAnno, bottom)) { + return true; + } else if (AnnotationUtils.areSame(superAnno, bottom)) { + return false; + } + + if (isPolymorphicQualifier(subAnno)) { + if (isPolymorphicQualifier(superAnno)) { + return true; + } else { + // Use this slightly more expensive conversion here because this is a rare code + // path and it's simpler to read than checking for both predicate and + // non-predicate forms of top. + return "".equals(convertToPredicate(superAnno)); + } + } else if (isPolymorphicQualifier(superAnno)) { + // Polymorphic annotations are only a supertype of other polymorphic annotations and + // the bottom type, both of which have already been checked above. + return false; + } + + if (isPredicate(subAnno)) { + return isPredicateSubtype( + convertToPredicate(subAnno), convertToPredicate(superAnno)); + } else if (isPredicate(superAnno)) { + return evaluatePredicate(subAnno, convertToPredicate(superAnno)); + } + + List subVal = getAccumulatedValues(subAnno); + List superVal = getAccumulatedValues(superAnno); + return subVal.containsAll(superVal); } - } - - List a1Val = getAccumulatedValues(a1); - List a2Val = getAccumulatedValues(a2); - // Avoid creating new annotation objects in the common case. - if (a1Val.containsAll(a2Val)) { - return a1; - } - if (a2Val.containsAll(a1Val)) { - return a2; - } - a1Val.addAll(a2Val); // union - return createAccumulatorAnnotation(a1Val); } /** - * LUB in this type system is set intersection of the arguments of the two annotations, unless - * one of them is bottom, in which case the result is the other annotation. + * Extension point for subtyping behavior between predicates. This implementation conservatively + * returns true only if the predicates are equal, or if the prospective supertype (q) is + * equivalent to top (that is, the empty string). + * + * @param p a predicate + * @param q another predicate + * @return true if p is a subtype of q + */ + protected boolean isPredicateSubtype(String p, String q) { + return "".equals(q) || p.equals(q); + } + + /** + * Evaluates whether the accumulator annotation {@code subAnno} makes the predicate {@code pred} + * true. + * + * @param subAnno an accumulator annotation + * @param pred a predicate + * @return whether the accumulator annotation satisfies the predicate */ - @Override - public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (AnnotationUtils.areSame(a1, bottom)) { - return a2; - } else if (AnnotationUtils.areSame(a2, bottom)) { - return a1; - } - - if (isPolymorphicQualifier(a1) && isPolymorphicQualifier(a2)) { - return a1; - } else if (isPolymorphicQualifier(a1) || isPolymorphicQualifier(a2)) { - return top; - } - - // If either is a predicate, then both should be converted to predicates and or-ed. - if (isPredicate(a1) || isPredicate(a2)) { - String a1Pred = convertToPredicate(a1); - String a2Pred = convertToPredicate(a2); - // check for top - if (a1Pred.isEmpty()) { - return a1; - } else if (a2Pred.isEmpty()) { - return a2; - } else { - return createPredicateAnnotation("(" + a1Pred + ") || (" + a2Pred + ")"); + protected boolean evaluatePredicate(AnnotationMirror subAnno, String pred) { + if (!isAccumulatorAnnotation(subAnno)) { + throw new BugInCF( + "tried to evaluate a predicate using an annotation that wasn't an accumulator: " + + subAnno); } - } - - List a1Val = getAccumulatedValues(a1); - List a2Val = getAccumulatedValues(a2); - // Avoid creating new annotation objects in the common case. - if (a1Val.containsAll(a2Val)) { - return a2; - } - if (a2Val.containsAll(a1Val)) { - return a1; - } - a1Val.retainAll(a2Val); // intersection - return createAccumulatorAnnotation(a1Val); + List trueVariables = getAccumulatedValues(subAnno); + return evaluatePredicate(trueVariables, pred); } /** - * {@inheritDoc} + * Checks that the given annotation either: + * + *

            + *
          • does not contain a predicate, or + *
          • contains a parse-able predicate + *
          + * + * Used by the visitor to throw "predicate.invalid" errors; thus must be package-private. * - *

          isSubtype in this type system is subset. + * @param anm any annotation supported by this checker + * @return null if there is nothing wrong with the annotation, or an error message indicating + * the problem if it has an invalid predicate */ - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (AnnotationUtils.areSame(subAnno, bottom)) { - return true; - } else if (AnnotationUtils.areSame(superAnno, bottom)) { - return false; - } - - if (isPolymorphicQualifier(subAnno)) { - if (isPolymorphicQualifier(superAnno)) { - return true; - } else { - // Use this slightly more expensive conversion here because this is a rare code - // path and it's simpler to read than checking for both predicate and - // non-predicate forms of top. - return "".equals(convertToPredicate(superAnno)); + /*package-private*/ @Nullable String isValidPredicate(AnnotationMirror anm) { + String pred = convertToPredicate(anm); + try { + evaluatePredicate(Collections.emptyList(), pred); + } catch (UserError ue) { + return ue.getLocalizedMessage(); } - } else if (isPolymorphicQualifier(superAnno)) { - // Polymorphic annotations are only a supertype of other polymorphic annotations and - // the bottom type, both of which have already been checked above. - return false; - } - - if (isPredicate(subAnno)) { - return isPredicateSubtype(convertToPredicate(subAnno), convertToPredicate(superAnno)); - } else if (isPredicate(superAnno)) { - return evaluatePredicate(subAnno, convertToPredicate(superAnno)); - } - - List subVal = getAccumulatedValues(subAnno); - List superVal = getAccumulatedValues(superAnno); - return subVal.containsAll(superVal); + return null; } - } - - /** - * Extension point for subtyping behavior between predicates. This implementation conservatively - * returns true only if the predicates are equal, or if the prospective supertype (q) is - * equivalent to top (that is, the empty string). - * - * @param p a predicate - * @param q another predicate - * @return true if p is a subtype of q - */ - protected boolean isPredicateSubtype(String p, String q) { - return "".equals(q) || p.equals(q); - } - - /** - * Evaluates whether the accumulator annotation {@code subAnno} makes the predicate {@code pred} - * true. - * - * @param subAnno an accumulator annotation - * @param pred a predicate - * @return whether the accumulator annotation satisfies the predicate - */ - protected boolean evaluatePredicate(AnnotationMirror subAnno, String pred) { - if (!isAccumulatorAnnotation(subAnno)) { - throw new BugInCF( - "tried to evaluate a predicate using an annotation that wasn't an accumulator: " - + subAnno); + + /** + * Evaluates whether treating the variables in {@code trueVariables} as {@code true} literals + * (and all other names as {@code false} literals) makes the predicate {@code pred} evaluate to + * true. + * + * @param trueVariables a list of names that should be replaced with {@code true} + * @param pred a predicate + * @return whether the true variables satisfy the predicate + */ + protected boolean evaluatePredicate(List trueVariables, String pred) { + Expression expression; + try { + expression = StaticJavaParser.parseExpression(pred); + } catch (ParseProblemException p) { + throw new UserError("unparsable predicate: " + pred + ". Parse exception: " + p); + } + return evaluateBooleanExpression(expression, trueVariables); } - List trueVariables = getAccumulatedValues(subAnno); - return evaluatePredicate(trueVariables, pred); - } - - /** - * Checks that the given annotation either: - * - *

            - *
          • does not contain a predicate, or - *
          • contains a parse-able predicate - *
          - * - * Used by the visitor to throw "predicate.invalid" errors; thus must be package-private. - * - * @param anm any annotation supported by this checker - * @return null if there is nothing wrong with the annotation, or an error message indicating the - * problem if it has an invalid predicate - */ - /*package-private*/ @Nullable String isValidPredicate(AnnotationMirror anm) { - String pred = convertToPredicate(anm); - try { - evaluatePredicate(Collections.emptyList(), pred); - } catch (UserError ue) { - return ue.getLocalizedMessage(); + + /** + * Evaluates a boolean expression, in JavaParser format, that contains only and, or, + * parentheses, logical complement, and boolean literal nodes. + * + * @param expression a JavaParser boolean expression + * @param trueVariables the names of the variables that should be considered "true"; all other + * literals are considered "false" + * @return the result of evaluating the expression + */ + private boolean evaluateBooleanExpression(Expression expression, List trueVariables) { + if (expression.isNameExpr()) { + return trueVariables.contains(expression.asNameExpr().getNameAsString()); + } else if (expression.isBinaryExpr()) { + if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.OR) { + return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) + || evaluateBooleanExpression( + expression.asBinaryExpr().getRight(), trueVariables); + } else if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.AND) { + return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) + && evaluateBooleanExpression( + expression.asBinaryExpr().getRight(), trueVariables); + } + } else if (expression.isEnclosedExpr()) { + return evaluateBooleanExpression(expression.asEnclosedExpr().getInner(), trueVariables); + } else if (expression.isUnaryExpr()) { + if (expression.asUnaryExpr().getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return !evaluateBooleanExpression( + expression.asUnaryExpr().getExpression(), trueVariables); + } + } + // This could be a BugInCF if there is a bug in the code above. + throw new UserError( + "encountered an unexpected type of expression in a " + + "predicate expression: " + + expression + + " was of type " + + expression.getClass()); } - return null; - } - - /** - * Evaluates whether treating the variables in {@code trueVariables} as {@code true} literals (and - * all other names as {@code false} literals) makes the predicate {@code pred} evaluate to true. - * - * @param trueVariables a list of names that should be replaced with {@code true} - * @param pred a predicate - * @return whether the true variables satisfy the predicate - */ - protected boolean evaluatePredicate(List trueVariables, String pred) { - Expression expression; - try { - expression = StaticJavaParser.parseExpression(pred); - } catch (ParseProblemException p) { - throw new UserError("unparsable predicate: " + pred + ". Parse exception: " + p); + + /** + * Creates a new predicate annotation from the given string. + * + * @param p a valid predicate + * @return an annotation representing that predicate + */ + protected AnnotationMirror createPredicateAnnotation(String p) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, predicate); + builder.setValue("value", p); + return builder.build(); } - return evaluateBooleanExpression(expression, trueVariables); - } - - /** - * Evaluates a boolean expression, in JavaParser format, that contains only and, or, parentheses, - * logical complement, and boolean literal nodes. - * - * @param expression a JavaParser boolean expression - * @param trueVariables the names of the variables that should be considered "true"; all other - * literals are considered "false" - * @return the result of evaluating the expression - */ - private boolean evaluateBooleanExpression(Expression expression, List trueVariables) { - if (expression.isNameExpr()) { - return trueVariables.contains(expression.asNameExpr().getNameAsString()); - } else if (expression.isBinaryExpr()) { - if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.OR) { - return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) - || evaluateBooleanExpression(expression.asBinaryExpr().getRight(), trueVariables); - } else if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.AND) { - return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) - && evaluateBooleanExpression(expression.asBinaryExpr().getRight(), trueVariables); - } - } else if (expression.isEnclosedExpr()) { - return evaluateBooleanExpression(expression.asEnclosedExpr().getInner(), trueVariables); - } else if (expression.isUnaryExpr()) { - if (expression.asUnaryExpr().getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return !evaluateBooleanExpression(expression.asUnaryExpr().getExpression(), trueVariables); - } + + /** + * Converts the given annotation mirror to a predicate. + * + * @param anno an annotation + * @return the predicate, as a String, that is equivalent to that annotation. May return the + * empty string. + */ + protected String convertToPredicate(AnnotationMirror anno) { + if (AnnotationUtils.areSame(anno, bottom)) { + return "false"; + } else if (isPredicate(anno)) { + String result = + AnnotationUtils.getElementValueOrNull(anno, "value", String.class, false); + return result == null ? "" : result; + } else if (isAccumulatorAnnotation(anno)) { + List values = getAccumulatedValues(anno); + StringJoiner sj = new StringJoiner(" && "); + for (String value : values) { + sj.add(value); + } + return sj.toString(); + } else { + throw new BugInCF("annotation is not bottom, a predicate, or an accumulator: " + anno); + } } - // This could be a BugInCF if there is a bug in the code above. - throw new UserError( - "encountered an unexpected type of expression in a " - + "predicate expression: " - + expression - + " was of type " - + expression.getClass()); - } - - /** - * Creates a new predicate annotation from the given string. - * - * @param p a valid predicate - * @return an annotation representing that predicate - */ - protected AnnotationMirror createPredicateAnnotation(String p) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, predicate); - builder.setValue("value", p); - return builder.build(); - } - - /** - * Converts the given annotation mirror to a predicate. - * - * @param anno an annotation - * @return the predicate, as a String, that is equivalent to that annotation. May return the empty - * string. - */ - protected String convertToPredicate(AnnotationMirror anno) { - if (AnnotationUtils.areSame(anno, bottom)) { - return "false"; - } else if (isPredicate(anno)) { - String result = AnnotationUtils.getElementValueOrNull(anno, "value", String.class, false); - return result == null ? "" : result; - } else if (isAccumulatorAnnotation(anno)) { - List values = getAccumulatedValues(anno); - StringJoiner sj = new StringJoiner(" && "); - for (String value : values) { - sj.add(value); - } - return sj.toString(); - } else { - throw new BugInCF("annotation is not bottom, a predicate, or an accumulator: " + anno); + + /** + * Returns true if anno is a predicate annotation. + * + * @param anno an annotation + * @return true if anno is a predicate annotation + */ + protected boolean isPredicate(AnnotationMirror anno) { + return predicate != null && areSameByClass(anno, predicate); } - } - - /** - * Returns true if anno is a predicate annotation. - * - * @param anno an annotation - * @return true if anno is a predicate annotation - */ - protected boolean isPredicate(AnnotationMirror anno) { - return predicate != null && areSameByClass(anno, predicate); - } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java index 2d9716a5405..990cabb5700 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java @@ -1,11 +1,12 @@ package org.checkerframework.common.accumulation; -import java.util.EnumSet; -import java.util.Set; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; +import java.util.EnumSet; +import java.util.Set; + /** * An accumulation checker is one that accumulates some property: method calls, map keys, etc. * @@ -23,54 +24,54 @@ */ public abstract class AccumulationChecker extends BaseTypeChecker { - /** Set of alias analyses that are enabled in this particular accumulation checker. */ - private final EnumSet aliasAnalyses; + /** Set of alias analyses that are enabled in this particular accumulation checker. */ + private final EnumSet aliasAnalyses; - /** Constructs a new AccumulationChecker. */ - protected AccumulationChecker() { - super(); - this.aliasAnalyses = createAliasAnalyses(); - } + /** Constructs a new AccumulationChecker. */ + protected AccumulationChecker() { + super(); + this.aliasAnalyses = createAliasAnalyses(); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - Set> checkers = super.getImmediateSubcheckerClasses(); - if (isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { - checkers.add(ReturnsReceiverChecker.class); + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + if (isEnabled(AliasAnalysis.RETURNS_RECEIVER)) { + checkers.add(ReturnsReceiverChecker.class); + } + return checkers; } - return checkers; - } - /** - * The alias analyses that an accumulation checker can support. To add support for a new alias - * analysis, add a new item to this enum and then implement any functionality of the checker - * behind a call to {@link #isEnabled(AliasAnalysis)}. - */ - public enum AliasAnalysis { /** - * An alias analysis that detects methods that always return their own receiver (i.e. whose - * return value and receiver are aliases). + * The alias analyses that an accumulation checker can support. To add support for a new alias + * analysis, add a new item to this enum and then implement any functionality of the checker + * behind a call to {@link #isEnabled(AliasAnalysis)}. */ - RETURNS_RECEIVER - } + public enum AliasAnalysis { + /** + * An alias analysis that detects methods that always return their own receiver (i.e. whose + * return value and receiver are aliases). + */ + RETURNS_RECEIVER + } - /** - * Get the alias analyses that this checker should employ. - * - * @return the alias analyses - */ - protected EnumSet createAliasAnalyses( - @UnderInitialization AccumulationChecker this) { - return EnumSet.of(AliasAnalysis.RETURNS_RECEIVER); - } + /** + * Get the alias analyses that this checker should employ. + * + * @return the alias analyses + */ + protected EnumSet createAliasAnalyses( + @UnderInitialization AccumulationChecker this) { + return EnumSet.of(AliasAnalysis.RETURNS_RECEIVER); + } - /** - * Check whether the given alias analysis is enabled by this particular accumulation checker. - * - * @param aliasAnalysis the analysis to check - * @return true iff the analysis is enabled - */ - public boolean isEnabled(AliasAnalysis aliasAnalysis) { - return aliasAnalyses.contains(aliasAnalysis); - } + /** + * Check whether the given alias analysis is enabled by this particular accumulation checker. + * + * @param aliasAnalysis the analysis to check + * @return true iff the analysis is enabled + */ + public boolean isEnabled(AliasAnalysis aliasAnalysis) { + return aliasAnalyses.contains(aliasAnalysis); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java index f05d0169718..eb2aff97ea1 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationStore.java @@ -6,25 +6,25 @@ /** This class is boilerplate, to enable the logic in AccumulationValue. */ public class AccumulationStore extends CFAbstractStore { - /** - * Constructor matching super. - * - * @param analysis the analysis - * @param sequentialSemantics whether to use sequential semantics (true) or concurrent semantics - * (false) - */ - protected AccumulationStore( - CFAbstractAnalysis analysis, - boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - } + /** + * Constructor matching super. + * + * @param analysis the analysis + * @param sequentialSemantics whether to use sequential semantics (true) or concurrent semantics + * (false) + */ + protected AccumulationStore( + CFAbstractAnalysis analysis, + boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } - /** - * Constructor matching super's copy constructor. - * - * @param other another store - */ - protected AccumulationStore(CFAbstractStore other) { - super(other); - } + /** + * Constructor matching super's copy constructor. + * + * @param other another store + */ + protected AccumulationStore(CFAbstractStore other) { + super(other); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java index 00967e564f0..3a7a884370f 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java @@ -2,11 +2,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; @@ -16,6 +12,13 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.plumelib.util.CollectionsPlume; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; + /** * The default transfer function for an accumulation checker. * @@ -23,112 +26,115 @@ * method to add a string to the estimate at a particular program point. */ public class AccumulationTransfer - extends CFAbstractTransfer { + extends CFAbstractTransfer { - /** The type factory. */ - protected final AccumulationAnnotatedTypeFactory atypeFactory; + /** The type factory. */ + protected final AccumulationAnnotatedTypeFactory atypeFactory; - /** - * Build a new AccumulationTransfer for the given analysis. - * - * @param analysis the analysis - */ - public AccumulationTransfer(AccumulationAnalysis analysis) { - super(analysis); - atypeFactory = (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); - } - - /** - * Updates the estimate of how many things {@code node} has accumulated. - * - *

          If the node is an invocation of a method that returns its receiver, then its receiver's type - * will also be updated. In a chain of method calls, this process will continue backward as long - * as each receiver is itself a receiver-returning method invocation. - * - *

          For example, suppose {@code node} is the expression {@code a.b().c()}, the new value (added - * by the accumulation analysis because of the {@code .c()} call) is "foo", and b and c return - * their receiver. This method will directly update the estimate of {@code a.b().c()} to include - * "foo". In addition, the estimates for the expressions {@code a.b()} and {@code a} would have - * their estimates updated to include "foo", because c and b (respectively) return their - * receivers. Note that due to what kind of values can be held in the store, this information is - * lost outside the method chain. That is, the returns-receiver propagated information is lost - * outside the expression in which the returns-receiver method invocations are nested. - * - *

          As a concrete example, consider the Called Methods accumulation checker: if {@code build} - * requires a, b, and c to be called, then {@code foo.a().b().c().build();} will typecheck (they - * are in one fluent method chain), but {@code foo.a().b().c(); foo.build();} will not -- the - * store does not keep the information that a, b, and c have been called outside the chain. {@code - * foo}'s type will be {@code CalledMethods("a")}, because only {@code a()} was called directly on - * {@code foo}. For such code to typecheck, the Called Methods accumulation checker uses an - * additional rule: the return type of a receiver-returning method {@code rr()} is {@code - * CalledMethods("rr")}. This rule is implemented directly in the {@link - * org.checkerframework.framework.type.treeannotator.TreeAnnotator} subclass defined in the Called - * Methods type factory. - * - * @param node the node whose estimate should be expanded - * @param result the transfer result containing the store to be modified - * @param values the new accumulation values - */ - public void accumulate( - Node node, TransferResult result, String... values) { - List valuesAsList = Arrays.asList(values); - JavaExpression target = JavaExpression.fromNode(node); - if (CFAbstractStore.canInsertJavaExpression(target)) { - if (result.containsTwoStores()) { - updateValueAndInsertIntoStore(result.getThenStore(), target, valuesAsList); - updateValueAndInsertIntoStore(result.getElseStore(), target, valuesAsList); - } else { - updateValueAndInsertIntoStore(result.getRegularStore(), target, valuesAsList); - } + /** + * Build a new AccumulationTransfer for the given analysis. + * + * @param analysis the analysis + */ + public AccumulationTransfer(AccumulationAnalysis analysis) { + super(analysis); + atypeFactory = (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); } - Tree tree = node.getTree(); - if (tree != null && tree.getKind() == Tree.Kind.METHOD_INVOCATION) { - Node receiver = ((MethodInvocationNode) node).getTarget().getReceiver(); - if (receiver != null && atypeFactory.returnsThis((MethodInvocationTree) tree)) { - accumulate(receiver, result, values); - } + /** + * Updates the estimate of how many things {@code node} has accumulated. + * + *

          If the node is an invocation of a method that returns its receiver, then its receiver's + * type will also be updated. In a chain of method calls, this process will continue backward as + * long as each receiver is itself a receiver-returning method invocation. + * + *

          For example, suppose {@code node} is the expression {@code a.b().c()}, the new value + * (added by the accumulation analysis because of the {@code .c()} call) is "foo", and b and c + * return their receiver. This method will directly update the estimate of {@code a.b().c()} to + * include "foo". In addition, the estimates for the expressions {@code a.b()} and {@code a} + * would have their estimates updated to include "foo", because c and b (respectively) return + * their receivers. Note that due to what kind of values can be held in the store, this + * information is lost outside the method chain. That is, the returns-receiver propagated + * information is lost outside the expression in which the returns-receiver method invocations + * are nested. + * + *

          As a concrete example, consider the Called Methods accumulation checker: if {@code build} + * requires a, b, and c to be called, then {@code foo.a().b().c().build();} will typecheck (they + * are in one fluent method chain), but {@code foo.a().b().c(); foo.build();} will not -- the + * store does not keep the information that a, b, and c have been called outside the chain. + * {@code foo}'s type will be {@code CalledMethods("a")}, because only {@code a()} was called + * directly on {@code foo}. For such code to typecheck, the Called Methods accumulation checker + * uses an additional rule: the return type of a receiver-returning method {@code rr()} is + * {@code CalledMethods("rr")}. This rule is implemented directly in the {@link + * org.checkerframework.framework.type.treeannotator.TreeAnnotator} subclass defined in the + * Called Methods type factory. + * + * @param node the node whose estimate should be expanded + * @param result the transfer result containing the store to be modified + * @param values the new accumulation values + */ + public void accumulate( + Node node, + TransferResult result, + String... values) { + List valuesAsList = Arrays.asList(values); + JavaExpression target = JavaExpression.fromNode(node); + if (CFAbstractStore.canInsertJavaExpression(target)) { + if (result.containsTwoStores()) { + updateValueAndInsertIntoStore(result.getThenStore(), target, valuesAsList); + updateValueAndInsertIntoStore(result.getElseStore(), target, valuesAsList); + } else { + updateValueAndInsertIntoStore(result.getRegularStore(), target, valuesAsList); + } + } + + Tree tree = node.getTree(); + if (tree != null && tree.getKind() == Tree.Kind.METHOD_INVOCATION) { + Node receiver = ((MethodInvocationNode) node).getTarget().getReceiver(); + if (receiver != null && atypeFactory.returnsThis((MethodInvocationTree) tree)) { + accumulate(receiver, result, values); + } + } } - } - /** - * Updates {@code target} in {@code store} so that {@code store}'s estimate includes the - * newly-accumulated values in {@code values}. If dataflow has already recorded information about - * the target, this method fetches it and integrates it into the list of values in the new - * annotation. - * - * @param store a store - * @param target an insertable JavaExpression ({@code canInsertJavaExpression(target)} should have - * returned true) - * @param values a list of newly-accumulated values - */ - private void updateValueAndInsertIntoStore( - AccumulationStore store, JavaExpression target, List values) { - // Make a modifiable copy of the list. - List valuesAsList = new ArrayList<>(values); - AccumulationValue flowValue = store.getValue(target); - if (flowValue != null) { - Set accumulatedValues = flowValue.getAccumulatedValues(); - if (accumulatedValues != null) { - valuesAsList = CollectionsPlume.concatenate(valuesAsList, accumulatedValues); - } else { - AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); - assert flowAnnos.size() <= 1; - for (AnnotationMirror anno : flowAnnos) { - if (atypeFactory.isAccumulatorAnnotation(anno)) { - List oldFlowValues = atypeFactory.getAccumulatedValues(anno); - if (!oldFlowValues.isEmpty()) { - // valuesAsList cannot have its length changed -- it is backed by an - // array -- but if oldFlowValues is not empty, it is a new, modifiable - // list. - oldFlowValues.addAll(valuesAsList); - valuesAsList = oldFlowValues; + /** + * Updates {@code target} in {@code store} so that {@code store}'s estimate includes the + * newly-accumulated values in {@code values}. If dataflow has already recorded information + * about the target, this method fetches it and integrates it into the list of values in the new + * annotation. + * + * @param store a store + * @param target an insertable JavaExpression ({@code canInsertJavaExpression(target)} should + * have returned true) + * @param values a list of newly-accumulated values + */ + private void updateValueAndInsertIntoStore( + AccumulationStore store, JavaExpression target, List values) { + // Make a modifiable copy of the list. + List valuesAsList = new ArrayList<>(values); + AccumulationValue flowValue = store.getValue(target); + if (flowValue != null) { + Set accumulatedValues = flowValue.getAccumulatedValues(); + if (accumulatedValues != null) { + valuesAsList = CollectionsPlume.concatenate(valuesAsList, accumulatedValues); + } else { + AnnotationMirrorSet flowAnnos = flowValue.getAnnotations(); + assert flowAnnos.size() <= 1; + for (AnnotationMirror anno : flowAnnos) { + if (atypeFactory.isAccumulatorAnnotation(anno)) { + List oldFlowValues = atypeFactory.getAccumulatedValues(anno); + if (!oldFlowValues.isEmpty()) { + // valuesAsList cannot have its length changed -- it is backed by an + // array -- but if oldFlowValues is not empty, it is a new, modifiable + // list. + oldFlowValues.addAll(valuesAsList); + valuesAsList = oldFlowValues; + } + } + } } - } } - } + AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); + store.insertValue(target, newAnno); } - AnnotationMirror newAnno = atypeFactory.createAccumulatorAnnotation(valuesAsList); - store.insertValue(target, newAnno); - } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java index 12d51b25cff..43af2a3d2b0 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationValue.java @@ -1,10 +1,5 @@ package org.checkerframework.common.accumulation; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.Node; @@ -12,6 +7,13 @@ import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.javacutil.AnnotationMirrorSet; +import java.util.HashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * AccumulationValue holds additional information about accumulated facts ("values", not to be * confused with "Value" in the name of this class) that cannot be stored in the accumulation type, @@ -33,130 +35,132 @@ */ public class AccumulationValue extends CFAbstractValue { - /** - * If the underlying type is a type variable or a wildcard, then this is a set of accumulated - * values. Otherwise, it is null. - */ - private @Nullable Set accumulatedValues = null; + /** + * If the underlying type is a type variable or a wildcard, then this is a set of accumulated + * values. Otherwise, it is null. + */ + private @Nullable Set accumulatedValues = null; - /** - * Creates a new CFAbstractValue. - * - * @param analysis the analysis class this value belongs to - * @param annotations the annotations in this abstract value - * @param underlyingType the underlying (Java) type in this abstract value - */ - protected AccumulationValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - if (underlyingType.getKind() == TypeKind.TYPEVAR - || underlyingType.getKind() == TypeKind.WILDCARD) { - AccumulationAnnotatedTypeFactory typeFactory = - (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); - AnnotationMirror accumulator = null; - for (AnnotationMirror anm : annotations) { - if (typeFactory.isAccumulatorAnnotation(anm)) { - accumulator = anm; - break; + /** + * Creates a new CFAbstractValue. + * + * @param analysis the analysis class this value belongs to + * @param annotations the annotations in this abstract value + * @param underlyingType the underlying (Java) type in this abstract value + */ + protected AccumulationValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + if (underlyingType.getKind() == TypeKind.TYPEVAR + || underlyingType.getKind() == TypeKind.WILDCARD) { + AccumulationAnnotatedTypeFactory typeFactory = + (AccumulationAnnotatedTypeFactory) analysis.getTypeFactory(); + AnnotationMirror accumulator = null; + for (AnnotationMirror anm : annotations) { + if (typeFactory.isAccumulatorAnnotation(anm)) { + accumulator = anm; + break; + } + } + if (accumulator != null) { + accumulatedValues = new HashSet<>(typeFactory.getAccumulatedValues(accumulator)); + } } - } - if (accumulator != null) { - accumulatedValues = new HashSet<>(typeFactory.getAccumulatedValues(accumulator)); - } } - } - /** - * If the underlying type is a type variable or a wildcard, then this is a set of accumulated - * values. Otherwise, it is null. - * - * @return the set (this is not a copy of the set, but an alias) - */ - public @Nullable Set getAccumulatedValues() { - return accumulatedValues; - } + /** + * If the underlying type is a type variable or a wildcard, then this is a set of accumulated + * values. Otherwise, it is null. + * + * @return the set (this is not a copy of the set, but an alias) + */ + public @Nullable Set getAccumulatedValues() { + return accumulatedValues; + } - @Override - protected AccumulationValue upperBound( - @Nullable AccumulationValue other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { - AccumulationValue lub = super.upperBound(other, upperBoundTypeMirror, shouldWiden); + @Override + protected AccumulationValue upperBound( + @Nullable AccumulationValue other, + TypeMirror upperBoundTypeMirror, + boolean shouldWiden) { + AccumulationValue lub = super.upperBound(other, upperBoundTypeMirror, shouldWiden); - if (other == null || other.accumulatedValues == null || this.accumulatedValues == null) { - return lub; - } - // Lub the accumulatedValues by intersecting the lists as if they were sets. - lub.accumulatedValues = new HashSet<>(this.accumulatedValues.size()); - lub.accumulatedValues.addAll(this.accumulatedValues); - lub.accumulatedValues.retainAll(other.accumulatedValues); - if (lub.accumulatedValues.isEmpty()) { - lub.accumulatedValues = null; + if (other == null || other.accumulatedValues == null || this.accumulatedValues == null) { + return lub; + } + // Lub the accumulatedValues by intersecting the lists as if they were sets. + lub.accumulatedValues = new HashSet<>(this.accumulatedValues.size()); + lub.accumulatedValues.addAll(this.accumulatedValues); + lub.accumulatedValues.retainAll(other.accumulatedValues); + if (lub.accumulatedValues.isEmpty()) { + lub.accumulatedValues = null; + } + return lub; } - return lub; - } - @Override - public @Nullable AccumulationValue mostSpecific( - AccumulationValue other, AccumulationValue backup) { - if (other == null) { - return this; - } - AccumulationValue mostSpecific = super.mostSpecific(other, backup); - if (mostSpecific != null) { - mostSpecific.addAccumulatedValues(this.accumulatedValues); - mostSpecific.addAccumulatedValues(other.accumulatedValues); - return mostSpecific; - } + @Override + public @Nullable AccumulationValue mostSpecific( + AccumulationValue other, AccumulationValue backup) { + if (other == null) { + return this; + } + AccumulationValue mostSpecific = super.mostSpecific(other, backup); + if (mostSpecific != null) { + mostSpecific.addAccumulatedValues(this.accumulatedValues); + mostSpecific.addAccumulatedValues(other.accumulatedValues); + return mostSpecific; + } - // mostSpecific is null if the two types are not comparable. This is normally - // because one of this or other is a type variable and annotations is empty, but the - // other annotations are not empty. In this case, copy the accumulatedValues to the - // value with no annotations and return it as most specific. - if (other.getAnnotations().isEmpty()) { - mostSpecific = - new AccumulationValue( - analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); - mostSpecific.addAccumulatedValues(this.accumulatedValues); - mostSpecific.addAccumulatedValues(other.accumulatedValues); - return mostSpecific; - } else if (this.getAnnotations().isEmpty()) { - mostSpecific = - new AccumulationValue( - analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); - mostSpecific.addAccumulatedValues(this.accumulatedValues); - mostSpecific.addAccumulatedValues(other.accumulatedValues); - return mostSpecific; + // mostSpecific is null if the two types are not comparable. This is normally + // because one of this or other is a type variable and annotations is empty, but the + // other annotations are not empty. In this case, copy the accumulatedValues to the + // value with no annotations and return it as most specific. + if (other.getAnnotations().isEmpty()) { + mostSpecific = + new AccumulationValue( + analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); + mostSpecific.addAccumulatedValues(this.accumulatedValues); + mostSpecific.addAccumulatedValues(other.accumulatedValues); + return mostSpecific; + } else if (this.getAnnotations().isEmpty()) { + mostSpecific = + new AccumulationValue( + analysis, AnnotationMirrorSet.emptySet(), other.getUnderlyingType()); + mostSpecific.addAccumulatedValues(this.accumulatedValues); + mostSpecific.addAccumulatedValues(other.accumulatedValues); + return mostSpecific; + } + return null; } - return null; - } - /** - * Merges its argument into the {@link #accumulatedValues} field of this. - * - * @param newAccumulatedValues a new list of accumulated values - */ - private void addAccumulatedValues(Set newAccumulatedValues) { - if (newAccumulatedValues == null || newAccumulatedValues.isEmpty()) { - return; - } - if (accumulatedValues == null) { - accumulatedValues = new HashSet<>(newAccumulatedValues.size()); + /** + * Merges its argument into the {@link #accumulatedValues} field of this. + * + * @param newAccumulatedValues a new list of accumulated values + */ + private void addAccumulatedValues(Set newAccumulatedValues) { + if (newAccumulatedValues == null || newAccumulatedValues.isEmpty()) { + return; + } + if (accumulatedValues == null) { + accumulatedValues = new HashSet<>(newAccumulatedValues.size()); + } + accumulatedValues.addAll(newAccumulatedValues); } - accumulatedValues.addAll(newAccumulatedValues); - } - @Override - public String toString() { - String superToString = super.toString(); - // remove last '}' - superToString = superToString.substring(0, superToString.length() - 1); - return superToString - + ", " - + "[" - + (accumulatedValues == null - ? "null" - : String.join(", ", accumulatedValues.toArray(new String[0]))) - + "] }"; - } + @Override + public String toString() { + String superToString = super.toString(); + // remove last '}' + superToString = superToString.substring(0, superToString.length() - 1); + return superToString + + ", " + + "[" + + (accumulatedValues == null + ? "null" + : String.join(", ", accumulatedValues.toArray(new String[0]))) + + "] }"; + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java index 3cf870a4536..8f4b7e9c3cd 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java @@ -1,36 +1,38 @@ package org.checkerframework.common.accumulation; import com.sun.source.tree.AnnotationTree; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; + /** * The visitor for an accumulation checker. Issues predicate.invalid errors if the user writes an * invalid predicate. */ public class AccumulationVisitor extends BaseTypeVisitor { - /** - * Constructor matching super. - * - * @param checker the checker - */ - public AccumulationVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Constructor matching super. + * + * @param checker the checker + */ + public AccumulationVisitor(BaseTypeChecker checker) { + super(checker); + } - /** Checks each predicate annotation to make sure the predicate is well-formed. */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - if (atypeFactory.isPredicate(anno)) { - String errorMessage = atypeFactory.isValidPredicate(anno); - if (errorMessage != null) { - checker.reportError(tree, "predicate.invalid", errorMessage); - } + /** Checks each predicate annotation to make sure the predicate is well-formed. */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + if (atypeFactory.isPredicate(anno)) { + String errorMessage = atypeFactory.isValidPredicate(anno); + if (errorMessage != null) { + checker.reportError(tree, "predicate.invalid", errorMessage); + } + } + return super.visitAnnotation(tree, p); } - return super.visitAnnotation(tree, p); - } } diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java index 9b24346f1fb..8627c37c0c8 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingAnnotatedTypeFactory.java @@ -1,11 +1,7 @@ package org.checkerframework.common.aliasing; import com.sun.source.tree.NewArrayTree; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; + import org.checkerframework.common.aliasing.qual.LeakedToResult; import org.checkerframework.common.aliasing.qual.MaybeAliased; import org.checkerframework.common.aliasing.qual.MaybeLeaked; @@ -24,108 +20,115 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationBuilder; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** Annotated type factory for the Aliasing Checker. */ public class AliasingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** Aliasing annotations. */ - /** The @{@link MaybeAliased} annotation. */ - protected final AnnotationMirror MAYBE_ALIASED = - AnnotationBuilder.fromClass(elements, MaybeAliased.class); + /** Aliasing annotations. */ + /** The @{@link MaybeAliased} annotation. */ + protected final AnnotationMirror MAYBE_ALIASED = + AnnotationBuilder.fromClass(elements, MaybeAliased.class); - /** The @{@link NonLeaked} annotation. */ - protected final AnnotationMirror NON_LEAKED = - AnnotationBuilder.fromClass(elements, NonLeaked.class); + /** The @{@link NonLeaked} annotation. */ + protected final AnnotationMirror NON_LEAKED = + AnnotationBuilder.fromClass(elements, NonLeaked.class); - /** The @{@link Unique} annotation. */ - protected final AnnotationMirror UNIQUE = AnnotationBuilder.fromClass(elements, Unique.class); + /** The @{@link Unique} annotation. */ + protected final AnnotationMirror UNIQUE = AnnotationBuilder.fromClass(elements, Unique.class); - /** The @{@link MaybeLeaked} annotation. */ - protected final AnnotationMirror MAYBE_LEAKED = - AnnotationBuilder.fromClass(elements, MaybeLeaked.class); + /** The @{@link MaybeLeaked} annotation. */ + protected final AnnotationMirror MAYBE_LEAKED = + AnnotationBuilder.fromClass(elements, MaybeLeaked.class); - /** Create the type factory. */ - public AliasingAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - if (this.getClass() == AliasingAnnotatedTypeFactory.class) { - this.postInit(); + /** Create the type factory. */ + public AliasingAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + if (this.getClass() == AliasingAnnotatedTypeFactory.class) { + this.postInit(); + } } - } - - // @NonLeaked and @LeakedToResult are type qualifiers because of a checker framework limitation - // (Issue 383). Once the stub parser gets updated to read non-type-qualifiers annotations on - // stub files, this annotation won't be a type qualifier anymore. - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(MaybeLeaked.class); - } - - @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - CFTransfer ret = new AliasingTransfer(analysis); - return ret; - } - - protected class AliasingTreeAnnotator extends TreeAnnotator { - - public AliasingTreeAnnotator(AliasingAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + + // @NonLeaked and @LeakedToResult are type qualifiers because of a checker framework limitation + // (Issue 383). Once the stub parser gets updated to read non-type-qualifiers annotations on + // stub files, this annotation won't be a type qualifier anymore. + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(MaybeLeaked.class); } @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - type.replaceAnnotation(UNIQUE); - return super.visitNewArray(tree, type); + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + CFTransfer ret = new AliasingTransfer(analysis); + return ret; } - } - - @Override - protected ListTreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new AliasingTreeAnnotator(this), super.createTreeAnnotator()); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new AliasingQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - - /** AliasingQualifierHierarchy. */ - protected class AliasingQualifierHierarchy extends NoElementQualifierHierarchy { - - /** - * Create AliasingQualifierHierarchy. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - */ - protected AliasingQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, AliasingAnnotatedTypeFactory.this); + + protected class AliasingTreeAnnotator extends TreeAnnotator { + + public AliasingTreeAnnotator(AliasingAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + type.replaceAnnotation(UNIQUE); + return super.visitNewArray(tree, type); + } } - /** - * Returns true is {@code anno} is annotation in the Leaked hierarchy. - * - * @param anno an annotation - * @return true is {@code anno} is annotation in the Leaked hierarchy - */ - private boolean isLeakedQualifier(AnnotationMirror anno) { - return areSameByClass(anno, MaybeLeaked.class) - || areSameByClass(anno, NonLeaked.class) - || areSameByClass(anno, LeakedToResult.class); + @Override + protected ListTreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new AliasingTreeAnnotator(this), super.createTreeAnnotator()); } @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (isLeakedQualifier(superAnno) && isLeakedQualifier(subAnno)) { - // @LeakedToResult and @NonLeaked were supposed to be non-type-qualifiers - // annotations. - // Currently the stub parser does not support non-type-qualifier annotations on - // receiver parameters (Issue 383), therefore these annotations are implemented as - // type qualifiers but the warnings related to the hierarchy are ignored. - return true; - } - return super.isSubtypeQualifiers(subAnno, superAnno); + protected QualifierHierarchy createQualifierHierarchy() { + return new AliasingQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); + } + + /** AliasingQualifierHierarchy. */ + protected class AliasingQualifierHierarchy extends NoElementQualifierHierarchy { + + /** + * Create AliasingQualifierHierarchy. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + protected AliasingQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, AliasingAnnotatedTypeFactory.this); + } + + /** + * Returns true is {@code anno} is annotation in the Leaked hierarchy. + * + * @param anno an annotation + * @return true is {@code anno} is annotation in the Leaked hierarchy + */ + private boolean isLeakedQualifier(AnnotationMirror anno) { + return areSameByClass(anno, MaybeLeaked.class) + || areSameByClass(anno, NonLeaked.class) + || areSameByClass(anno, LeakedToResult.class); + } + + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (isLeakedQualifier(superAnno) && isLeakedQualifier(subAnno)) { + // @LeakedToResult and @NonLeaked were supposed to be non-type-qualifiers + // annotations. + // Currently the stub parser does not support non-type-qualifier annotations on + // receiver parameters (Issue 383), therefore these annotations are implemented as + // type qualifiers but the warnings related to the hierarchy are ignored. + return true; + } + return super.isSubtypeQualifiers(subAnno, superAnno); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java index bd0aa7c43e0..a2e721206de 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingTransfer.java @@ -3,9 +3,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.common.aliasing.qual.LeakedToResult; import org.checkerframework.common.aliasing.qual.NonLeaked; import org.checkerframework.common.aliasing.qual.Unique; @@ -27,6 +25,11 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.javacutil.TreeUtils; +import java.util.List; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; + /** * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may lose * its type refinement, before the LHS is type-refined. @@ -44,136 +47,136 @@ */ public class AliasingTransfer extends CFTransfer { - /** The annotated type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * Create a new AliasingTransfer. - * - * @param analysis the CFAbstractAnalysis - */ - public AliasingTransfer(CFAbstractAnalysis analysis) { - super(analysis); - atypeFactory = analysis.getTypeFactory(); - } - - /** - * Case 1: For every assignment, the LHS is refined if the RHS has type {@literal @}Unique and is - * a method invocation or a new class instance. - */ - @Override - public TransferResult visitAssignment( - AssignmentNode n, TransferInput in) { - Node rhs = n.getExpression(); - Tree treeRhs = rhs.getTree(); - AnnotatedTypeMirror rhsType = atypeFactory.getAnnotatedType(treeRhs); - - if (rhsType.hasAnnotation(Unique.class) - && (rhs instanceof MethodInvocationNode || rhs instanceof ObjectCreationNode)) { - return super.visitAssignment(n, in); // Do normal refinement. - } - // Widen the type of the rhs if the RHS's declared type wasn't @Unique. - JavaExpression rhsExpr = JavaExpression.fromNode(rhs); - in.getRegularStore().clearValue(rhsExpr); - return new RegularTransferResult<>(null, in.getRegularStore()); - } - - /** - * Handling pseudo-assignments. Called by {@code CFAbstractTransfer.visitMethodInvocation()}. - * - *

          Case 2: Given a method call, traverses all formal parameters of the method declaration, and - * if it doesn't have the {@literal @}NonLeaked or {@literal @}LeakedToResult annotations, we - * remove the node of the respective argument in the method call from the store. If parameter has - * {@literal @}LeakedToResult, {@code visitMethodInvocation()} handles it. - */ - @Override - protected void processPostconditions( - Node n, CFStore store, ExecutableElement executableElement, ExpressionTree tree) { - // TODO: Process ObjectCreationNode here after fixing issue: - // https://github.com/eisop/checker-framework/issues/400 - if (!(n instanceof MethodInvocationNode)) { - return; - } - super.processPostconditions(n, store, executableElement, tree); - if (TreeUtils.isEnumSuperCall((MethodInvocationTree) n.getTree())) { - // Skipping the init() method for enums. - return; + /** The annotated type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * Create a new AliasingTransfer. + * + * @param analysis the CFAbstractAnalysis + */ + public AliasingTransfer(CFAbstractAnalysis analysis) { + super(analysis); + atypeFactory = analysis.getTypeFactory(); } - List args = ((MethodInvocationNode) n).getArguments(); - List params = executableElement.getParameters(); - assert (args.size() == params.size()) - : "Number of arguments in " - + "the method call " - + n - + " is different from the" - + " number of parameters for the method declaration: " - + executableElement.getSimpleName(); - - AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(executableElement); - List paramTypes = annotatedType.getParameterTypes(); - for (int i = 0; i < args.size(); i++) { - Node arg = args.get(i); - AnnotatedTypeMirror paramType = paramTypes.get(i); - if (!paramType.hasAnnotation(NonLeaked.class) - && !paramType.hasAnnotation(LeakedToResult.class)) { - store.clearValue(JavaExpression.fromNode(arg)); - } + + /** + * Case 1: For every assignment, the LHS is refined if the RHS has type {@literal @}Unique and + * is a method invocation or a new class instance. + */ + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput in) { + Node rhs = n.getExpression(); + Tree treeRhs = rhs.getTree(); + AnnotatedTypeMirror rhsType = atypeFactory.getAnnotatedType(treeRhs); + + if (rhsType.hasAnnotation(Unique.class) + && (rhs instanceof MethodInvocationNode || rhs instanceof ObjectCreationNode)) { + return super.visitAssignment(n, in); // Do normal refinement. + } + // Widen the type of the rhs if the RHS's declared type wasn't @Unique. + JavaExpression rhsExpr = JavaExpression.fromNode(rhs); + in.getRegularStore().clearValue(rhsExpr); + return new RegularTransferResult<>(null, in.getRegularStore()); } - // Now, doing the same as above for the receiver parameter - Node receiver = ((MethodInvocationNode) n).getTarget().getReceiver(); - AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); - if (receiverType != null - && !receiverType.hasAnnotation(LeakedToResult.class) - && !receiverType.hasAnnotation(NonLeaked.class)) { - store.clearValue(JavaExpression.fromNode(receiver)); + /** + * Handling pseudo-assignments. Called by {@code CFAbstractTransfer.visitMethodInvocation()}. + * + *

          Case 2: Given a method call, traverses all formal parameters of the method declaration, + * and if it doesn't have the {@literal @}NonLeaked or {@literal @}LeakedToResult annotations, + * we remove the node of the respective argument in the method call from the store. If parameter + * has {@literal @}LeakedToResult, {@code visitMethodInvocation()} handles it. + */ + @Override + protected void processPostconditions( + Node n, CFStore store, ExecutableElement executableElement, ExpressionTree tree) { + // TODO: Process ObjectCreationNode here after fixing issue: + // https://github.com/eisop/checker-framework/issues/400 + if (!(n instanceof MethodInvocationNode)) { + return; + } + super.processPostconditions(n, store, executableElement, tree); + if (TreeUtils.isEnumSuperCall((MethodInvocationTree) n.getTree())) { + // Skipping the init() method for enums. + return; + } + List args = ((MethodInvocationNode) n).getArguments(); + List params = executableElement.getParameters(); + assert (args.size() == params.size()) + : "Number of arguments in " + + "the method call " + + n + + " is different from the" + + " number of parameters for the method declaration: " + + executableElement.getSimpleName(); + + AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(executableElement); + List paramTypes = annotatedType.getParameterTypes(); + for (int i = 0; i < args.size(); i++) { + Node arg = args.get(i); + AnnotatedTypeMirror paramType = paramTypes.get(i); + if (!paramType.hasAnnotation(NonLeaked.class) + && !paramType.hasAnnotation(LeakedToResult.class)) { + store.clearValue(JavaExpression.fromNode(arg)); + } + } + + // Now, doing the same as above for the receiver parameter + Node receiver = ((MethodInvocationNode) n).getTarget().getReceiver(); + AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); + if (receiverType != null + && !receiverType.hasAnnotation(LeakedToResult.class) + && !receiverType.hasAnnotation(NonLeaked.class)) { + store.clearValue(JavaExpression.fromNode(receiver)); + } } - } - - /** - * Case 3: Given a method invocation expression, if the parent of the expression is not a - * statement, check if there are any arguments of the method call annotated as - * {@literal @}LeakedToResult and remove it from the store, since it might be leaked. - */ - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { - Tree parent = n.getTreePath().getParentPath().getLeaf(); - boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; - - if (!parentIsStatement) { - - ExecutableElement methodElement = TreeUtils.elementFromUse(n.getTree()); - List args = n.getArguments(); - List params = methodElement.getParameters(); - assert (args.size() == params.size()) - : "Number of arguments in " - + "the method call " - + n - + " is different from the" - + " number of parameters for the method declaration: " - + methodElement.getSimpleName(); - CFStore store = in.getRegularStore(); - - for (int i = 0; i < args.size(); i++) { - Node arg = args.get(i); - VariableElement param = params.get(i); - if (atypeFactory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class)) { - // If argument can leak to result, and parent is not a - // single statement, remove that node from store. - store.clearValue(JavaExpression.fromNode(arg)); + + /** + * Case 3: Given a method invocation expression, if the parent of the expression is not a + * statement, check if there are any arguments of the method call annotated as + * {@literal @}LeakedToResult and remove it from the store, since it might be leaked. + */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + Tree parent = n.getTreePath().getParentPath().getLeaf(); + boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; + + if (!parentIsStatement) { + + ExecutableElement methodElement = TreeUtils.elementFromUse(n.getTree()); + List args = n.getArguments(); + List params = methodElement.getParameters(); + assert (args.size() == params.size()) + : "Number of arguments in " + + "the method call " + + n + + " is different from the" + + " number of parameters for the method declaration: " + + methodElement.getSimpleName(); + CFStore store = in.getRegularStore(); + + for (int i = 0; i < args.size(); i++) { + Node arg = args.get(i); + VariableElement param = params.get(i); + if (atypeFactory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class)) { + // If argument can leak to result, and parent is not a + // single statement, remove that node from store. + store.clearValue(JavaExpression.fromNode(arg)); + } + } + + // Now, doing the same as above for the receiver parameter + Node receiver = n.getTarget().getReceiver(); + AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement); + AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); + if (receiverType != null && receiverType.hasAnnotation(LeakedToResult.class)) { + store.clearValue(JavaExpression.fromNode(receiver)); + } } - } - - // Now, doing the same as above for the receiver parameter - Node receiver = n.getTarget().getReceiver(); - AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement); - AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); - if (receiverType != null && receiverType.hasAnnotation(LeakedToResult.class)) { - store.clearValue(JavaExpression.fromNode(receiver)); - } + // If parent is a statement, processPostconditions will handle the pseudo-assignments. + return super.visitMethodInvocation(n, in); } - // If parent is a statement, processPostconditions will handle the pseudo-assignments. - return super.visitMethodInvocation(n, in); - } } diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java index 5a01d3d1b5e..60d31dc5756 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java @@ -8,10 +8,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.List; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.common.aliasing.qual.LeakedToResult; @@ -27,6 +24,12 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; + /** * This visitor ensures that every constructor whose result is annotated as {@literal @}Unique does * not leak aliases. @@ -44,308 +47,312 @@ */ public class AliasingVisitor extends BaseTypeVisitor { - public AliasingVisitor(BaseTypeChecker checker) { - super(checker); - } + public AliasingVisitor(BaseTypeChecker checker) { + super(checker); + } - /** - * Checks that if a method call is being invoked inside a constructor with result type - * {@literal @}Unique, it must not leak the "this" reference. There are 3 ways to make sure that - * this is not happening: - * - *

            - *
          1. {@code this} is not an argument of the method call. - *
          2. {@code this} is an argument of the method call, but the respective parameter is annotated - * as {@literal @}NonLeaked. - *
          3. {@code this} is an argument of the method call, but the respective parameter is annotated - * as {@literal @}LeakedToResult AND the result of the method call is not being stored (the - * method call is a statement). - *
          - * - * The private method {@code isUniqueCheck} handles cases 2 and 3. - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - // The check only needs to be done for constructors with result type - // @Unique. We also want to avoid visiting the method. - if (isInUniqueConstructor()) { - if (TreeUtils.isSuperConstructorCall(tree)) { - // Check if a call to super() might create an alias: that - // happens when the parent's respective constructor is not @Unique. - AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(tree); - if (!superResult.hasAnnotation(Unique.class)) { - checker.reportError(tree, "unique.leaked"); - } - } else { - // TODO: Currently the type of "this" doesn't always return the type of the - // constructor result, therefore we need this "else" block. Once constructors are - // implemented correctly we could remove that code below, since the type of "this" - // in a @Unique constructor will be @Unique. - Tree parent = getCurrentPath().getParentPath().getLeaf(); - boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; - ExecutableElement methodElement = TreeUtils.elementFromUse(tree); - List params = methodElement.getParameters(); - List args = tree.getArguments(); - assert (args.size() == params.size()) - : "Number of arguments in" - + " the method call " - + tree - + " is different from the " - + "number of parameters for the method declaration: " - + methodElement.getSimpleName(); - for (int i = 0; i < args.size(); i++) { - // Here we are traversing the arguments of the method call. - // For every argument we check if it is a reference to "this". - if (TreeUtils.isExplicitThisDereference(args.get(i))) { - // If it is a reference to "this", there is still hope that - // it is not being leaked (2. and 3. from the javadoc). - VariableElement param = params.get(i); - boolean hasNonLeaked = - atypeFactory.getAnnotatedType(param).hasAnnotation(NonLeaked.class); - boolean hasLeakedToResult = - atypeFactory.getAnnotatedType(param).hasAnnotation(LeakedToResult.class); - isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); - } else { - // Not possible to leak reference here (case 1. from the javadoc). - } - } + /** + * Checks that if a method call is being invoked inside a constructor with result type + * {@literal @}Unique, it must not leak the "this" reference. There are 3 ways to make sure that + * this is not happening: + * + *
            + *
          1. {@code this} is not an argument of the method call. + *
          2. {@code this} is an argument of the method call, but the respective parameter is + * annotated as {@literal @}NonLeaked. + *
          3. {@code this} is an argument of the method call, but the respective parameter is + * annotated as {@literal @}LeakedToResult AND the result of the method call is not being + * stored (the method call is a statement). + *
          + * + * The private method {@code isUniqueCheck} handles cases 2 and 3. + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + // The check only needs to be done for constructors with result type + // @Unique. We also want to avoid visiting the method. + if (isInUniqueConstructor()) { + if (TreeUtils.isSuperConstructorCall(tree)) { + // Check if a call to super() might create an alias: that + // happens when the parent's respective constructor is not @Unique. + AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(tree); + if (!superResult.hasAnnotation(Unique.class)) { + checker.reportError(tree, "unique.leaked"); + } + } else { + // TODO: Currently the type of "this" doesn't always return the type of the + // constructor result, therefore we need this "else" block. Once constructors are + // implemented correctly we could remove that code below, since the type of "this" + // in a @Unique constructor will be @Unique. + Tree parent = getCurrentPath().getParentPath().getLeaf(); + boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; + ExecutableElement methodElement = TreeUtils.elementFromUse(tree); + List params = methodElement.getParameters(); + List args = tree.getArguments(); + assert (args.size() == params.size()) + : "Number of arguments in" + + " the method call " + + tree + + " is different from the " + + "number of parameters for the method declaration: " + + methodElement.getSimpleName(); + for (int i = 0; i < args.size(); i++) { + // Here we are traversing the arguments of the method call. + // For every argument we check if it is a reference to "this". + if (TreeUtils.isExplicitThisDereference(args.get(i))) { + // If it is a reference to "this", there is still hope that + // it is not being leaked (2. and 3. from the javadoc). + VariableElement param = params.get(i); + boolean hasNonLeaked = + atypeFactory.getAnnotatedType(param).hasAnnotation(NonLeaked.class); + boolean hasLeakedToResult = + atypeFactory + .getAnnotatedType(param) + .hasAnnotation(LeakedToResult.class); + isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); + } else { + // Not possible to leak reference here (case 1. from the javadoc). + } + } - // Now, doing the same as above for the receiver parameter - AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement); - AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); - if (receiverType != null) { - boolean hasNonLeaked = receiverType.hasAnnotation(NonLeaked.class); - boolean hasLeakedToResult = receiverType.hasAnnotation(LeakedToResult.class); - isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); + // Now, doing the same as above for the receiver parameter + AnnotatedExecutableType annotatedType = + atypeFactory.getAnnotatedType(methodElement); + AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); + if (receiverType != null) { + boolean hasNonLeaked = receiverType.hasAnnotation(NonLeaked.class); + boolean hasLeakedToResult = receiverType.hasAnnotation(LeakedToResult.class); + isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); + } + } } - } + return super.visitMethodInvocation(tree, p); } - return super.visitMethodInvocation(tree, p); - } - /** - * Possibly issue a "unique.leaked" error. - * - * @param tree a method invocation - * @param parentIsStatement is the parent of {@code tree} a statement? - * @param hasNonLeaked does the receiver have a {@code @NonLeaked} annotation? - * @param hasLeakedToResult does the receiver have a {@code @LeakedToResult} annotation? - */ - private void isUniqueCheck( - MethodInvocationTree tree, - boolean parentIsStatement, - boolean hasNonLeaked, - boolean hasLeakedToResult) { - if (hasNonLeaked || (hasLeakedToResult && parentIsStatement)) { - // Not leaked according to cases 2. and 3. from the javadoc of - // visitMethodInvocation. - } else { - // May be leaked, raise warning. - checker.reportError(tree, "unique.leaked"); + /** + * Possibly issue a "unique.leaked" error. + * + * @param tree a method invocation + * @param parentIsStatement is the parent of {@code tree} a statement? + * @param hasNonLeaked does the receiver have a {@code @NonLeaked} annotation? + * @param hasLeakedToResult does the receiver have a {@code @LeakedToResult} annotation? + */ + private void isUniqueCheck( + MethodInvocationTree tree, + boolean parentIsStatement, + boolean hasNonLeaked, + boolean hasLeakedToResult) { + if (hasNonLeaked || (hasLeakedToResult && parentIsStatement)) { + // Not leaked according to cases 2. and 3. from the javadoc of + // visitMethodInvocation. + } else { + // May be leaked, raise warning. + checker.reportError(tree, "unique.leaked"); + } } - } - // TODO: Merge that code in commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree - // valueExp, String errorKey, boolean isLocalVariableAssignment), because the method below - // isn't called for pseudo-assignments, but the mentioned one is. The issue of copy-pasting the - // code from this method to the other one is that a declaration such as: List<@Unique Object> - // will raise a unique.leaked warning, as there is a pseudo-assignment from @Unique to a - // @MaybeAliased object, if the @Unique annotation is not in the stubfile. TODO: Change the - // documentation in BaseTypeVisitor to point out that this isn't called for pseudo-assignments. - @Override - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); - if (isInUniqueConstructor() && TreeUtils.isExplicitThisDereference(valueExp)) { - // If an assignment occurs inside a constructor with result type @Unique, it will - // invalidate the @Unique property by using the "this" reference. - checker.reportError(valueExp, "unique.leaked"); - result = false; - } else if (canBeLeaked(valueExp)) { - checker.reportError(valueExp, "unique.leaked"); - result = false; + // TODO: Merge that code in commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree + // valueExp, String errorKey, boolean isLocalVariableAssignment), because the method below + // isn't called for pseudo-assignments, but the mentioned one is. The issue of copy-pasting the + // code from this method to the other one is that a declaration such as: List<@Unique Object> + // will raise a unique.leaked warning, as there is a pseudo-assignment from @Unique to a + // @MaybeAliased object, if the @Unique annotation is not in the stubfile. TODO: Change the + // documentation in BaseTypeVisitor to point out that this isn't called for pseudo-assignments. + @Override + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); + if (isInUniqueConstructor() && TreeUtils.isExplicitThisDereference(valueExp)) { + // If an assignment occurs inside a constructor with result type @Unique, it will + // invalidate the @Unique property by using the "this" reference. + checker.reportError(valueExp, "unique.leaked"); + result = false; + } else if (canBeLeaked(valueExp)) { + checker.reportError(valueExp, "unique.leaked"); + result = false; + } + return result; } - return result; - } - @Override - @FormatMethod - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - boolean result = - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + @Override + @FormatMethod + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + boolean result = + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - // If we are visiting a pseudo-assignment, visitorLeafKind is either - // Tree.Kind.NEW_CLASS or Tree.Kind.METHOD_INVOCATION. - TreePath path = getCurrentPath(); - if (path == null) { - return result; - } - Tree.Kind visitorLeafKind = path.getLeaf().getKind(); + // If we are visiting a pseudo-assignment, visitorLeafKind is either + // Tree.Kind.NEW_CLASS or Tree.Kind.METHOD_INVOCATION. + TreePath path = getCurrentPath(); + if (path == null) { + return result; + } + Tree.Kind visitorLeafKind = path.getLeaf().getKind(); - if (visitorLeafKind == Tree.Kind.NEW_CLASS || visitorLeafKind == Tree.Kind.METHOD_INVOCATION) { - // Handling pseudo-assignments - if (canBeLeaked(valueTree)) { - Tree.Kind parentKind = getCurrentPath().getParentPath().getLeaf().getKind(); + if (visitorLeafKind == Tree.Kind.NEW_CLASS + || visitorLeafKind == Tree.Kind.METHOD_INVOCATION) { + // Handling pseudo-assignments + if (canBeLeaked(valueTree)) { + Tree.Kind parentKind = getCurrentPath().getParentPath().getLeaf().getKind(); - if (!varType.hasAnnotation(NonLeaked.class) - && !(varType.hasAnnotation(LeakedToResult.class) - && parentKind == Tree.Kind.EXPRESSION_STATEMENT)) { - checker.reportError(valueTree, "unique.leaked"); - result = false; + if (!varType.hasAnnotation(NonLeaked.class) + && !(varType.hasAnnotation(LeakedToResult.class) + && parentKind == Tree.Kind.EXPRESSION_STATEMENT)) { + checker.reportError(valueTree, "unique.leaked"); + result = false; + } + } } - } + return result; } - return result; - } - @Override - public Void visitThrow(ThrowTree tree, Void p) { - // throw is also an escape mechanism. If an expression of type - // @Unique is thrown, it is not @Unique anymore. - ExpressionTree exp = tree.getExpression(); - if (canBeLeaked(exp)) { - checker.reportError(exp, "unique.leaked"); + @Override + public Void visitThrow(ThrowTree tree, Void p) { + // throw is also an escape mechanism. If an expression of type + // @Unique is thrown, it is not @Unique anymore. + ExpressionTree exp = tree.getExpression(); + if (canBeLeaked(exp)) { + checker.reportError(exp, "unique.leaked"); + } + return super.visitThrow(tree, p); } - return super.visitThrow(tree, p); - } - @Override - public Void visitVariable(VariableTree tree, Void p) { - // Component types are not allowed to have the @Unique annotation. - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(tree); - VariableElement elt = TreeUtils.elementFromDeclaration(tree); - if (elt.getKind().isField() && varType.hasExplicitAnnotation(Unique.class)) { - checker.reportError(tree, "unique.location.forbidden"); - } else if (tree.getType() != null) { - // VariableTree#getType returns null for binding variables from a - // DeconstructionPatternTree. - if (tree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { - AnnotatedArrayType arrayType = (AnnotatedArrayType) varType; - if (arrayType.getComponentType().hasAnnotation(Unique.class)) { - checker.reportError(tree, "unique.location.forbidden"); - } - } else if (tree.getType().getKind() == Tree.Kind.PARAMETERIZED_TYPE) { - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) varType; - for (AnnotatedTypeMirror atm : declaredType.getTypeArguments()) { - if (atm.hasAnnotation(Unique.class)) { + @Override + public Void visitVariable(VariableTree tree, Void p) { + // Component types are not allowed to have the @Unique annotation. + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(tree); + VariableElement elt = TreeUtils.elementFromDeclaration(tree); + if (elt.getKind().isField() && varType.hasExplicitAnnotation(Unique.class)) { checker.reportError(tree, "unique.location.forbidden"); - } + } else if (tree.getType() != null) { + // VariableTree#getType returns null for binding variables from a + // DeconstructionPatternTree. + if (tree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { + AnnotatedArrayType arrayType = (AnnotatedArrayType) varType; + if (arrayType.getComponentType().hasAnnotation(Unique.class)) { + checker.reportError(tree, "unique.location.forbidden"); + } + } else if (tree.getType().getKind() == Tree.Kind.PARAMETERIZED_TYPE) { + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) varType; + for (AnnotatedTypeMirror atm : declaredType.getTypeArguments()) { + if (atm.hasAnnotation(Unique.class)) { + checker.reportError(tree, "unique.location.forbidden"); + } + } + } } - } + return super.visitVariable(tree, p); } - return super.visitVariable(tree, p); - } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - List initializers = tree.getInitializers(); - if (initializers != null && !initializers.isEmpty()) { - for (ExpressionTree exp : initializers) { - if (canBeLeaked(exp)) { - checker.reportError(exp, "unique.leaked"); + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + List initializers = tree.getInitializers(); + if (initializers != null && !initializers.isEmpty()) { + for (ExpressionTree exp : initializers) { + if (canBeLeaked(exp)) { + checker.reportError(exp, "unique.leaked"); + } + } } - } + return super.visitNewArray(tree, p); } - return super.visitNewArray(tree, p); - } - @Override - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - // @Unique is verified, so don't check this. - AnnotatedTypeMirror returnType = constructorType.getReturnType(); - if (returnType.hasAnnotation(atypeFactory.UNIQUE)) { - return; - } - - // Don't issue warnings about @LeakedToResult or (implicit) @MaybeLeaked on constructor - // results. - if (!returnType.hasAnnotation(atypeFactory.NON_LEAKED)) { - // TODO: the visitor should not change qualifiers. - // Possible problem from aliasing of `returnType`, but all tests pass. - returnType.replaceAnnotation(atypeFactory.NON_LEAKED); - } + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // @Unique is verified, so don't check this. + AnnotatedTypeMirror returnType = constructorType.getReturnType(); + if (returnType.hasAnnotation(atypeFactory.UNIQUE)) { + return; + } - super.checkConstructorResult(constructorType, constructorElement); - } + // Don't issue warnings about @LeakedToResult or (implicit) @MaybeLeaked on constructor + // results. + if (!returnType.hasAnnotation(atypeFactory.NON_LEAKED)) { + // TODO: the visitor should not change qualifiers. + // Possible problem from aliasing of `returnType`, but all tests pass. + returnType.replaceAnnotation(atypeFactory.NON_LEAKED); + } - @Override - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { - if (isInUniqueConstructor()) { - // Check if a call to super() might create an alias: that - // happens when the parent's respective constructor is not @Unique. - AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(superCall); - if (!superResult.hasAnnotation(Unique.class)) { - checker.reportError(superCall, "unique.leaked"); - } + super.checkConstructorResult(constructorType, constructorElement); } - } - /** - * Returns true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new - * class expression. It checks whether the tree expression is unique by either checking for an - * explicit annotation or checking whether the class of the tree expression {@code exp} has type - * {@code @Unique} - * - * @param exp the Tree to check - * @return true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new - * class expression - */ - private boolean canBeLeaked(Tree exp) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(exp); - boolean isMethodInvocation = exp.getKind() == Tree.Kind.METHOD_INVOCATION; - boolean isNewClass = exp.getKind() == Tree.Kind.NEW_CLASS; - boolean isUniqueType = isUniqueClass(type) || type.hasExplicitAnnotation(Unique.class); - return isUniqueType && !isMethodInvocation && !isNewClass; - } - - /** - * Return true if the class declaration for annotated type {@code type} has annotation - * {@code @Unique}. - * - * @param type the annotated type whose class must be checked - * @return boolean true if class is unique and false otherwise - */ - private boolean isUniqueClass(AnnotatedTypeMirror type) { - Element el = types.asElement(type.getUnderlyingType()); - if (el == null) { - return false; + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + if (isInUniqueConstructor()) { + // Check if a call to super() might create an alias: that + // happens when the parent's respective constructor is not @Unique. + AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(superCall); + if (!superResult.hasAnnotation(Unique.class)) { + checker.reportError(superCall, "unique.leaked"); + } + } } - AnnotationMirrorSet annoMirrors = atypeFactory.getDeclAnnotations(el); - if (annoMirrors == null) { - return false; + + /** + * Returns true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new + * class expression. It checks whether the tree expression is unique by either checking for an + * explicit annotation or checking whether the class of the tree expression {@code exp} has type + * {@code @Unique} + * + * @param exp the Tree to check + * @return true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new + * class expression + */ + private boolean canBeLeaked(Tree exp) { + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(exp); + boolean isMethodInvocation = exp.getKind() == Tree.Kind.METHOD_INVOCATION; + boolean isNewClass = exp.getKind() == Tree.Kind.NEW_CLASS; + boolean isUniqueType = isUniqueClass(type) || type.hasExplicitAnnotation(Unique.class); + return isUniqueType && !isMethodInvocation && !isNewClass; } - if (atypeFactory.containsSameByClass(annoMirrors, Unique.class)) { - return true; + + /** + * Return true if the class declaration for annotated type {@code type} has annotation + * {@code @Unique}. + * + * @param type the annotated type whose class must be checked + * @return boolean true if class is unique and false otherwise + */ + private boolean isUniqueClass(AnnotatedTypeMirror type) { + Element el = types.asElement(type.getUnderlyingType()); + if (el == null) { + return false; + } + AnnotationMirrorSet annoMirrors = atypeFactory.getDeclAnnotations(el); + if (annoMirrors == null) { + return false; + } + if (atypeFactory.containsSameByClass(annoMirrors, Unique.class)) { + return true; + } + return false; } - return false; - } - /** - * Returns true if the enclosing method is a constructor whose return type is annotated as - * {@code @Unique}. - * - * @return true if the enclosing method is a constructor whose return type is annotated as - * {@code @Unique} - */ - private boolean isInUniqueConstructor() { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - if (enclosingMethod == null) { - return false; // No enclosing method. + /** + * Returns true if the enclosing method is a constructor whose return type is annotated as + * {@code @Unique}. + * + * @return true if the enclosing method is a constructor whose return type is annotated as + * {@code @Unique} + */ + private boolean isInUniqueConstructor() { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + if (enclosingMethod == null) { + return false; // No enclosing method. + } + return TreeUtils.isConstructor(enclosingMethod) + && atypeFactory + .getAnnotatedType(enclosingMethod) + .getReturnType() + .hasAnnotation(Unique.class); } - return TreeUtils.isConstructor(enclosingMethod) - && atypeFactory - .getAnnotatedType(enclosingMethod) - .getReturnType() - .hasAnnotation(Unique.class); - } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java index 29b9bcfe6fc..b027f0bd004 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseAnnotatedTypeFactory.java @@ -11,23 +11,23 @@ * analysis as provided by {@link CFAnalysis}. */ public class BaseAnnotatedTypeFactory - extends GenericAnnotatedTypeFactory { + extends GenericAnnotatedTypeFactory { - public BaseAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { - super(checker, useFlow); + public BaseAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { + super(checker, useFlow); - // Every subclass must call postInit! - if (this.getClass() == BaseAnnotatedTypeFactory.class) { - this.postInit(); + // Every subclass must call postInit! + if (this.getClass() == BaseAnnotatedTypeFactory.class) { + this.postInit(); + } } - } - public BaseAnnotatedTypeFactory(BaseTypeChecker checker) { - this(checker, flowByDefault); - } + public BaseAnnotatedTypeFactory(BaseTypeChecker checker) { + this(checker, flowByDefault); + } - @Override - protected CFAnalysis createFlowAnalysis() { - return new CFAnalysis(checker, this); - } + @Override + protected CFAnalysis createFlowAnalysis() { + return new CFAnalysis(checker, this); + } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index ed2626a0b1e..edb17ea0796 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -7,24 +7,7 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; + import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -48,6 +31,26 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + /** * An abstract {@link SourceChecker} that provides a simple {@link * org.checkerframework.framework.source.SourceVisitor} implementation that type-checks assignments, @@ -90,825 +93,834 @@ */ public abstract class BaseTypeChecker extends SourceChecker { - @Override - public void initChecker() { - // initialize all checkers and share options as necessary - for (BaseTypeChecker checker : getSubcheckers()) { - // We need to add all options that are activated for the set of subcheckers to - // the individual checkers. - checker.addOptions(super.getOptions()); - // Each checker should "support" all possible lint options - otherwise - // subchecker A would complain about a lint option for subchecker B. - checker.setSupportedLintOptions(this.getSupportedLintOptions()); - - // initChecker validates the passed options, so call it after setting supported options - // and lints. - checker.initChecker(); - } - - if (!getSubcheckers().isEmpty()) { - messageStore = new TreeSet<>(this::compareCheckerMessages); - } - - super.initChecker(); - - warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); - } - - /** - * The full list of subcheckers that need to be run prior to this one, in the order they need to - * be run in. This list will only be non-empty for the one checker that runs all other - * subcheckers. Do not read this field directly. Instead, retrieve it via {@link #getSubcheckers}. - * - *

          If the list still null when {@link #getSubcheckers} is called, then {@code getSubcheckers()} - * will call {@link #instantiateSubcheckers}. However, if the current object was itself - * instantiated by a prior call to instantiateSubcheckers, this field will have been initialized - * to an empty list before {@code getSubcheckers()} is called, thereby ensuring that this list is - * non-empty only for one checker. - */ - private @MonotonicNonNull List subcheckers = null; - - /** - * The list of subcheckers that are direct dependencies of this checker. This list will be - * non-empty for any checker that has at least one subchecker. - * - *

          Does not need to be initialized to null or an empty list because it is always initialized - * via calls to {@link #instantiateSubcheckers}. - */ - // Set to non-null when subcheckers is. - private @MonotonicNonNull List immediateSubcheckers = null; - - /** Supported options for this checker. */ - private @MonotonicNonNull Set supportedOptions = null; - - /** Options passed to this checker. */ - private @MonotonicNonNull Map options = null; - - /** - * The list of suppress warnings prefixes supported by this checker or any of its subcheckers - * (including indirect subcheckers). Do not access this field directly; instead, use {@link - * #getSuppressWarningsPrefixesOfSubcheckers}. - */ - private @MonotonicNonNull Collection suppressWarningsPrefixesOfSubcheckers = null; - - /** True if -AwarnUnneededSuppressions was supplied on the command line. */ - // Not final because it is set in `init()`. - private boolean warnUnneededSuppressions; - - @Override - protected void setRoot(CompilationUnitTree newRoot) { - super.setRoot(newRoot); - if (parentChecker == null) { - // Only clear the path cache if this is the main checker. - treePathCacher.clear(); - } - } - - /** - * Returns the set of subchecker classes on which this checker depends. Returns an empty set if - * this checker does not depend on any others. - * - *

          Subclasses should override this method to specify subcheckers. If they do so, they should - * call the super implementation of this method and add dependencies to the returned set so that - * checkers required for reflection resolution are included if reflection resolution is requested. - * - *

          Each subchecker of this checker may also depend on other checkers. If this checker and one - * of its subcheckers both depend on a third checker, that checker will only be instantiated once. - * - *

          Though each checker is run on a whole compilation unit before the next checker is run, error - * and warning messages are collected and sorted based on the location in the source file before - * being printed. (See {@link #printOrStoreMessage(Diagnostic.Kind, String, Tree, - * CompilationUnitTree)}.) - * - *

          WARNING: Circular dependencies are not supported nor do checkers verify that their - * dependencies are not circular. Make sure no circular dependencies are created when overriding - * this method. (In other words, if checker A depends on checker B, checker B cannot depend on - * checker A.) - * - *

          This method is protected so it can be overridden, but it should only be called internally by - * the BaseTypeChecker. - * - *

          The BaseTypeChecker will not modify the set returned by this method, but clients that - * override the method do modify the set. - * - * @return the subchecker classes on which this checker depends; will be modified by callees in - * overriding methods - */ - // This is never looked up in, but it is iterated over (and added to, which does a lookup). - protected Set> getImmediateSubcheckerClasses() { - // This must return a modifiable set because clients modify it. - // Most checkers have 1 or fewer subcheckers. - // Use a LinkedHashSet for deterministic ordering. - LinkedHashSet> result = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); - if (shouldResolveReflection()) { - result.add(MethodValChecker.class); - } - return result; - } - - /** - * Returns whether or not reflection should be resolved. - * - * @return true if reflection should be resolved - */ - public boolean shouldResolveReflection() { - return hasOptionNoSubcheckers("resolveReflection"); - } - - /** An array containing just {@code BaseTypeChecker.class}. */ - private static final Class[] baseTypeCheckerClassArray = - new Class[] {BaseTypeChecker.class}; - - /** - * Returns the appropriate visitor that type-checks the compilation unit according to the type - * system rules. - * - *

          This implementation uses the checker naming convention to create the appropriate visitor. If - * no visitor is found, it returns an instance of {@link BaseTypeVisitor}. It reflectively invokes - * the constructor that accepts this checker and the compilation unit tree (in that order) as - * arguments. - * - *

          Subclasses have to override this method to create the appropriate visitor if they do not - * follow the checker naming convention. - * - * @return the type-checking visitor - */ - @Override - protected BaseTypeVisitor createSourceVisitor() { - // Try to reflectively load the visitor. - Class checkerClass = this.getClass(); - Object[] thisArray = new Object[] {this}; - while (checkerClass != BaseTypeChecker.class) { - BaseTypeVisitor result = - invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"), - baseTypeCheckerClassArray, - thisArray); - if (result != null) { + @Override + public void initChecker() { + // initialize all checkers and share options as necessary + for (BaseTypeChecker checker : getSubcheckers()) { + // We need to add all options that are activated for the set of subcheckers to + // the individual checkers. + checker.addOptions(super.getOptions()); + // Each checker should "support" all possible lint options - otherwise + // subchecker A would complain about a lint option for subchecker B. + checker.setSupportedLintOptions(this.getSupportedLintOptions()); + + // initChecker validates the passed options, so call it after setting supported options + // and lints. + checker.initChecker(); + } + + if (!getSubcheckers().isEmpty()) { + messageStore = new TreeSet<>(this::compareCheckerMessages); + } + + super.initChecker(); + + warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); + } + + /** + * The full list of subcheckers that need to be run prior to this one, in the order they need to + * be run in. This list will only be non-empty for the one checker that runs all other + * subcheckers. Do not read this field directly. Instead, retrieve it via {@link + * #getSubcheckers}. + * + *

          If the list still null when {@link #getSubcheckers} is called, then {@code + * getSubcheckers()} will call {@link #instantiateSubcheckers}. However, if the current object + * was itself instantiated by a prior call to instantiateSubcheckers, this field will have been + * initialized to an empty list before {@code getSubcheckers()} is called, thereby ensuring that + * this list is non-empty only for one checker. + */ + private @MonotonicNonNull List subcheckers = null; + + /** + * The list of subcheckers that are direct dependencies of this checker. This list will be + * non-empty for any checker that has at least one subchecker. + * + *

          Does not need to be initialized to null or an empty list because it is always initialized + * via calls to {@link #instantiateSubcheckers}. + */ + // Set to non-null when subcheckers is. + private @MonotonicNonNull List immediateSubcheckers = null; + + /** Supported options for this checker. */ + private @MonotonicNonNull Set supportedOptions = null; + + /** Options passed to this checker. */ + private @MonotonicNonNull Map options = null; + + /** + * The list of suppress warnings prefixes supported by this checker or any of its subcheckers + * (including indirect subcheckers). Do not access this field directly; instead, use {@link + * #getSuppressWarningsPrefixesOfSubcheckers}. + */ + private @MonotonicNonNull Collection suppressWarningsPrefixesOfSubcheckers = null; + + /** True if -AwarnUnneededSuppressions was supplied on the command line. */ + // Not final because it is set in `init()`. + private boolean warnUnneededSuppressions; + + @Override + protected void setRoot(CompilationUnitTree newRoot) { + super.setRoot(newRoot); + if (parentChecker == null) { + // Only clear the path cache if this is the main checker. + treePathCacher.clear(); + } + } + + /** + * Returns the set of subchecker classes on which this checker depends. Returns an empty set if + * this checker does not depend on any others. + * + *

          Subclasses should override this method to specify subcheckers. If they do so, they should + * call the super implementation of this method and add dependencies to the returned set so that + * checkers required for reflection resolution are included if reflection resolution is + * requested. + * + *

          Each subchecker of this checker may also depend on other checkers. If this checker and one + * of its subcheckers both depend on a third checker, that checker will only be instantiated + * once. + * + *

          Though each checker is run on a whole compilation unit before the next checker is run, + * error and warning messages are collected and sorted based on the location in the source file + * before being printed. (See {@link #printOrStoreMessage(Diagnostic.Kind, String, Tree, + * CompilationUnitTree)}.) + * + *

          WARNING: Circular dependencies are not supported nor do checkers verify that their + * dependencies are not circular. Make sure no circular dependencies are created when overriding + * this method. (In other words, if checker A depends on checker B, checker B cannot depend on + * checker A.) + * + *

          This method is protected so it can be overridden, but it should only be called internally + * by the BaseTypeChecker. + * + *

          The BaseTypeChecker will not modify the set returned by this method, but clients that + * override the method do modify the set. + * + * @return the subchecker classes on which this checker depends; will be modified by callees in + * overriding methods + */ + // This is never looked up in, but it is iterated over (and added to, which does a lookup). + protected Set> getImmediateSubcheckerClasses() { + // This must return a modifiable set because clients modify it. + // Most checkers have 1 or fewer subcheckers. + // Use a LinkedHashSet for deterministic ordering. + LinkedHashSet> result = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); + if (shouldResolveReflection()) { + result.add(MethodValChecker.class); + } return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If a visitor couldn't be loaded reflectively, return the default. - return new BaseTypeVisitor(this); - } - - /** - * A public variant of {@link #createSourceVisitor}. Only use this if you know what you are doing. - * - * @return the type-checking visitor - */ - public BaseTypeVisitor createSourceVisitorPublic() { - return createSourceVisitor(); - } - - /** - * Returns the name of a class related to a given one, by replacing "Checker" or "Subchecker" by - * {@code replacement}. - * - * @param checkerClass the checker class - * @param replacement the string to replace "Checker" or "Subchecker" by - * @return the name of the related class - */ - @SuppressWarnings("signature") // string manipulation of @ClassGetName string - public static @ClassGetName String getRelatedClassName( - Class checkerClass, String replacement) { - return checkerClass - .getName() - .replace("Checker", replacement) - .replace("Subchecker", replacement); - } - - // ********************************************************************** - // Misc. methods - // ********************************************************************** - - /** Specify supported lint options for all type-checkers. */ - @Override - public Set getSupportedLintOptions() { - Set lintSet = new HashSet<>(super.getSupportedLintOptions()); - lintSet.add("cast"); - lintSet.add("cast:redundant"); - lintSet.add("cast:unsafe"); - - for (BaseTypeChecker checker : getSubcheckers()) { - lintSet.addAll(checker.getSupportedLintOptions()); - } - - return Collections.unmodifiableSet(lintSet); - } - - /** - * Invokes the constructor belonging to the class named by {@code name} having the given parameter - * types on the given arguments. Returns {@code null} if the class cannot be found. Otherwise, - * throws an exception if there is trouble with the constructor invocation. - * - * @param the type to which the constructor belongs - * @param name the name of the class to which the constructor belongs - * @param paramTypes the types of the constructor's parameters - * @param args the arguments on which to invoke the constructor - * @return the result of the constructor invocation on {@code args}, or null if the class does not - * exist - */ - @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse - public static @Nullable T invokeConstructorFor( - @ClassGetName String name, Class[] paramTypes, Object[] args) { - - // Load the class. - Class cls = null; - try { - cls = (Class) Class.forName(name); - } catch (Exception e) { - // no class is found, simply return null - return null; - } - - assert cls != null : "reflectively loading " + name + " failed"; - - // Invoke the constructor. - try { - Constructor ctor = cls.getConstructor(paramTypes); - return ctor.newInstance(args); - } catch (Throwable t) { - if (t instanceof InvocationTargetException) { - Throwable err = t.getCause(); - if (err instanceof UserError || err instanceof TypeSystemError) { - // Don't add more information about the constructor invocation. - throw (RuntimeException) err; - } - } else if (t instanceof NoSuchMethodException) { - // Note: it's possible that NoSuchMethodException was caused by - // `ctor.newInstance(args)`, if the constructor itself uses reflection. - // But this case is unlikely. - throw new TypeSystemError( - "Could not find constructor %s(%s)", name, StringsPlume.join(", ", paramTypes)); - } - - Throwable cause; - String causeMessage; - if (t instanceof InvocationTargetException) { - cause = t.getCause(); - if (cause == null || cause.getMessage() == null) { - causeMessage = t.getMessage(); - } else if (t.getMessage() == null) { - causeMessage = cause.getMessage(); + } + + /** + * Returns whether or not reflection should be resolved. + * + * @return true if reflection should be resolved + */ + public boolean shouldResolveReflection() { + return hasOptionNoSubcheckers("resolveReflection"); + } + + /** An array containing just {@code BaseTypeChecker.class}. */ + private static final Class[] baseTypeCheckerClassArray = + new Class[] {BaseTypeChecker.class}; + + /** + * Returns the appropriate visitor that type-checks the compilation unit according to the type + * system rules. + * + *

          This implementation uses the checker naming convention to create the appropriate visitor. + * If no visitor is found, it returns an instance of {@link BaseTypeVisitor}. It reflectively + * invokes the constructor that accepts this checker and the compilation unit tree (in that + * order) as arguments. + * + *

          Subclasses have to override this method to create the appropriate visitor if they do not + * follow the checker naming convention. + * + * @return the type-checking visitor + */ + @Override + protected BaseTypeVisitor createSourceVisitor() { + // Try to reflectively load the visitor. + Class checkerClass = this.getClass(); + Object[] thisArray = new Object[] {this}; + while (checkerClass != BaseTypeChecker.class) { + BaseTypeVisitor result = + invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"), + baseTypeCheckerClassArray, + thisArray); + if (result != null) { + return result; + } + checkerClass = checkerClass.getSuperclass(); + } + + // If a visitor couldn't be loaded reflectively, return the default. + return new BaseTypeVisitor(this); + } + + /** + * A public variant of {@link #createSourceVisitor}. Only use this if you know what you are + * doing. + * + * @return the type-checking visitor + */ + public BaseTypeVisitor createSourceVisitorPublic() { + return createSourceVisitor(); + } + + /** + * Returns the name of a class related to a given one, by replacing "Checker" or "Subchecker" by + * {@code replacement}. + * + * @param checkerClass the checker class + * @param replacement the string to replace "Checker" or "Subchecker" by + * @return the name of the related class + */ + @SuppressWarnings("signature") // string manipulation of @ClassGetName string + public static @ClassGetName String getRelatedClassName( + Class checkerClass, String replacement) { + return checkerClass + .getName() + .replace("Checker", replacement) + .replace("Subchecker", replacement); + } + + // ********************************************************************** + // Misc. methods + // ********************************************************************** + + /** Specify supported lint options for all type-checkers. */ + @Override + public Set getSupportedLintOptions() { + Set lintSet = new HashSet<>(super.getSupportedLintOptions()); + lintSet.add("cast"); + lintSet.add("cast:redundant"); + lintSet.add("cast:unsafe"); + + for (BaseTypeChecker checker : getSubcheckers()) { + lintSet.addAll(checker.getSupportedLintOptions()); + } + + return Collections.unmodifiableSet(lintSet); + } + + /** + * Invokes the constructor belonging to the class named by {@code name} having the given + * parameter types on the given arguments. Returns {@code null} if the class cannot be found. + * Otherwise, throws an exception if there is trouble with the constructor invocation. + * + * @param the type to which the constructor belongs + * @param name the name of the class to which the constructor belongs + * @param paramTypes the types of the constructor's parameters + * @param args the arguments on which to invoke the constructor + * @return the result of the constructor invocation on {@code args}, or null if the class does + * not exist + */ + @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse + public static @Nullable T invokeConstructorFor( + @ClassGetName String name, Class[] paramTypes, Object[] args) { + + // Load the class. + Class cls = null; + try { + cls = (Class) Class.forName(name); + } catch (Exception e) { + // no class is found, simply return null + return null; + } + + assert cls != null : "reflectively loading " + name + " failed"; + + // Invoke the constructor. + try { + Constructor ctor = cls.getConstructor(paramTypes); + return ctor.newInstance(args); + } catch (Throwable t) { + if (t instanceof InvocationTargetException) { + Throwable err = t.getCause(); + if (err instanceof UserError || err instanceof TypeSystemError) { + // Don't add more information about the constructor invocation. + throw (RuntimeException) err; + } + } else if (t instanceof NoSuchMethodException) { + // Note: it's possible that NoSuchMethodException was caused by + // `ctor.newInstance(args)`, if the constructor itself uses reflection. + // But this case is unlikely. + throw new TypeSystemError( + "Could not find constructor %s(%s)", + name, StringsPlume.join(", ", paramTypes)); + } + + Throwable cause; + String causeMessage; + if (t instanceof InvocationTargetException) { + cause = t.getCause(); + if (cause == null || cause.getMessage() == null) { + causeMessage = t.getMessage(); + } else if (t.getMessage() == null) { + causeMessage = cause.getMessage(); + } else { + causeMessage = t.getMessage() + ": " + cause.getMessage(); + } + } else { + cause = t; + causeMessage = (cause == null) ? "null" : cause.getMessage(); + } + throw new BugInCF( + cause, + "Error when invoking constructor %s(%s) on args %s; cause: %s", + name, + StringsPlume.join(", ", paramTypes), + Arrays.toString(args), + causeMessage); + } + } + + @Override + public BaseTypeVisitor getVisitor() { + return (BaseTypeVisitor) super.getVisitor(); + } + + /** + * Return the type factory associated with this checker. + * + * @return the type factory associated with this checker + */ + public GenericAnnotatedTypeFactory getTypeFactory() { + BaseTypeVisitor visitor = getVisitor(); + // Avoid NPE if this method is called during initialization. + if (visitor == null) { + throw new TypeSystemError("Called getTypeFactory() before initialization was complete"); + } + return visitor.getTypeFactory(); + } + + @Override + public AnnotationProvider getAnnotationProvider() { + return getTypeFactory(); + } + + /** + * Returns the requested subchecker. A checker of a given class can only be run once, so this + * returns the only such checker, or null if none was found. The caller must know the exact + * checker class to request. + * + * @param the class of the subchecker to return + * @param checkerClass the class of the subchecker to return + * @return the requested subchecker or null if not found + */ + @SuppressWarnings("unchecked") + public @Nullable T getSubchecker(Class checkerClass) { + for (BaseTypeChecker checker : immediateSubcheckers) { + if (checker.getClass() == checkerClass) { + return (T) checker; + } + } + + return null; + } + + /** + * Returns the type factory used by a subchecker. Returns null if no matching subchecker was + * found or if the type factory is null. The caller must know the exact checker class to + * request. + * + *

          Because the visitor state is copied, call this method each time a subfactory is needed + * rather than store the returned subfactory in a field. + * + * @param subCheckerClass the class of the subchecker + * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} + * @return the type factory of the requested subchecker or null if not found + */ + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public > + @Nullable T getTypeFactoryOfSubcheckerOrNull( + Class subCheckerClass) { + return getTypeFactory().getTypeFactoryOfSubcheckerOrNull(subCheckerClass); + } + + /** + * Returns the unmodifiable list of immediate subcheckers of this checker. + * + *

          Performs a depth first search for all checkers this checker depends on. The depth first + * search ensures that the collection has the correct order the checkers need to be run in. + * + *

          Modifies the alreadyInitializedSubcheckerMap map by adding all recursively newly + * instantiated subcheckers' class objects and instances. It is necessary to use a map that + * preserves the order in which entries were inserted, such as LinkedHashMap or ArrayMap. + * + * @param alreadyInitializedSubcheckerMap subcheckers that have already been instantiated. Is + * modified by this method. + * @return the unmodifiable list of immediate subcheckers of this checker + */ + private List instantiateSubcheckers( + Map, BaseTypeChecker> + alreadyInitializedSubcheckerMap) { + Set> classesOfImmediateSubcheckers = + getImmediateSubcheckerClasses(); + if (classesOfImmediateSubcheckers.isEmpty()) { + return Collections.emptyList(); + } + + ArrayList immediateSubcheckers = + new ArrayList<>(classesOfImmediateSubcheckers.size()); + + for (Class subcheckerClass : classesOfImmediateSubcheckers) { + BaseTypeChecker subchecker = alreadyInitializedSubcheckerMap.get(subcheckerClass); + if (subchecker != null) { + // Add the already initialized subchecker to the list of immediate subcheckers so + // that this checker can refer to it. + immediateSubcheckers.add(subchecker); + continue; + } + + BaseTypeChecker instance; + try { + instance = subcheckerClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new BugInCF("Could not create an instance of " + subcheckerClass, e); + } + + instance.setProcessingEnvironment(this.processingEnv); + instance.treePathCacher = this.getTreePathCacher(); + // Prevent the new checker from storing non-immediate subcheckers + instance.subcheckers = Collections.emptyList(); + immediateSubcheckers.add(instance); + instance.immediateSubcheckers = + instance.instantiateSubcheckers(alreadyInitializedSubcheckerMap); + instance.setParentChecker(this); + alreadyInitializedSubcheckerMap.put(subcheckerClass, instance); + } + + return Collections.unmodifiableList(immediateSubcheckers); + } + + /** + * Get the list of all subcheckers (if any). via the instantiateSubcheckers method. This list is + * only non-empty for the one checker that runs all other subcheckers. These are recursively + * instantiated via instantiateSubcheckers the first time the method is called if subcheckers is + * null. Assumes all checkers run on the same thread. + * + * @return the list of all subcheckers (if any) + */ + public List getSubcheckers() { + if (subcheckers == null) { + // Instantiate the checkers this one depends on, if any. + Map, BaseTypeChecker> checkerMap = + new ArrayMap, BaseTypeChecker>(2); + + immediateSubcheckers = instantiateSubcheckers(checkerMap); + + subcheckers = Collections.unmodifiableList(new ArrayList<>(checkerMap.values())); + } + + return subcheckers; + } + + // AbstractTypeProcessor delegation + @Override + public void typeProcess(TypeElement element, TreePath tree) { + if (!getSubcheckers().isEmpty()) { + // TODO: I expected this to only be necessary if (parentChecker == null). + // However, the NestedAggregateChecker fails otherwise. + messageStore.clear(); + } + + // Errors (or other messages) issued via + // SourceChecker#message(Diagnostic.Kind, Object, String, Object...) + // are stored in messageStore until all checkers have processed this compilation unit. + // All other messages are printed immediately. This includes errors issued because the + // checker threw an exception. + + // In order to run the next checker on this compilation unit even if the previous issued + // errors, the next checker's errsOnLastExit needs to include all errors issued by previous + // checkers. + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Log log = Log.instance(context); + + int nerrorsOfAllPreviousCheckers = this.errsOnLastExit; + for (BaseTypeChecker subchecker : getSubcheckers()) { + subchecker.errsOnLastExit = nerrorsOfAllPreviousCheckers; + subchecker.messageStore = messageStore; + int errorsBeforeTypeChecking = log.nerrors; + + subchecker.typeProcess(element, tree); + + int errorsAfterTypeChecking = log.nerrors; + nerrorsOfAllPreviousCheckers += errorsAfterTypeChecking - errorsBeforeTypeChecking; + } + + this.errsOnLastExit = nerrorsOfAllPreviousCheckers; + super.typeProcess(element, tree); + + if (!getSubcheckers().isEmpty()) { + printStoredMessages(tree.getCompilationUnit()); + // Update errsOnLastExit to reflect the errors issued. + this.errsOnLastExit = log.nerrors; + } + } + + /** + * Like {@link SourceChecker#getSuppressWarningsPrefixes()}, but includes all prefixes supported + * by this checker or any of its subcheckers. Does not guarantee that the result is in any + * particular order. The result is immutable. + * + * @return the suppress warnings prefixes supported by this checker or any of its subcheckers + */ + public Collection getSuppressWarningsPrefixesOfSubcheckers() { + if (this.suppressWarningsPrefixesOfSubcheckers == null) { + Collection prefixes = getSuppressWarningsPrefixes(); + for (BaseTypeChecker subchecker : getSubcheckers()) { + prefixes.addAll(subchecker.getSuppressWarningsPrefixes()); + } + this.suppressWarningsPrefixesOfSubcheckers = ImmutableSet.copyOf(prefixes); + } + return this.suppressWarningsPrefixesOfSubcheckers; + } + + /** A cache for {@link #getUltimateParentChecker}. */ + private @MonotonicNonNull BaseTypeChecker ultimateParentChecker; + + /** + * Finds the ultimate parent checker of this checker. The ultimate parent checker is the checker + * that the user actually requested, i.e. the one with no parent. The ultimate parent might be + * this checker itself. + * + * @return the first checker in the parent checker chain with no parent checker of its own, + * i.e., the ultimate parent checker + */ + public BaseTypeChecker getUltimateParentChecker() { + if (ultimateParentChecker == null) { + ultimateParentChecker = this; + while (ultimateParentChecker.getParentChecker() instanceof BaseTypeChecker) { + ultimateParentChecker = (BaseTypeChecker) ultimateParentChecker.getParentChecker(); + } + } + + return ultimateParentChecker; + } + + /** + * {@inheritDoc} + * + *

          This implementation collects needed warning suppressions for all subcheckers. + */ + @Override + protected void warnUnneededSuppressions() { + if (parentChecker != null) { + return; + } + + if (!warnUnneededSuppressions) { + return; + } + Set elementsWithSuppressedWarnings = + new HashSet<>(this.elementsWithSuppressedWarnings); + this.elementsWithSuppressedWarnings.clear(); + + Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); + Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); + for (BaseTypeChecker subChecker : subcheckers) { + elementsWithSuppressedWarnings.addAll(subChecker.elementsWithSuppressedWarnings); + subChecker.elementsWithSuppressedWarnings.clear(); + prefixes.addAll(subChecker.getSuppressWarningsPrefixes()); + errorKeys.addAll(subChecker.messagesProperties.stringPropertyNames()); + subChecker.getVisitor().treesWithSuppressWarnings.clear(); + } + warnUnneededSuppressions(elementsWithSuppressedWarnings, prefixes, errorKeys); + + getVisitor().treesWithSuppressWarnings.clear(); + } + + /** + * Stores all messages issued by this checker and its subcheckers for the current compilation + * unit. The messages are printed after all checkers have processed the current compilation + * unit. The purpose is to sort messages, grouping together all messages about a particular line + * of code. + * + *

          If this checker has no subcheckers and is not a subchecker for any other checker, then + * messageStore is null and messages will be printed as they are issued by this checker. + */ + private @MonotonicNonNull TreeSet messageStore; + + /** + * If this is a compound checker or a subchecker of a compound checker, then the message is + * stored until all messages from all checkers for the compilation unit are issued. + * + *

          Otherwise, it prints the message. + */ + @Override + protected void printOrStoreMessage( + Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { + assert this.currentRoot == root; + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + if (messageStore == null) { + super.printOrStoreMessage(kind, message, source, root, trace); } else { - causeMessage = t.getMessage() + ": " + cause.getMessage(); - } - } else { - cause = t; - causeMessage = (cause == null) ? "null" : cause.getMessage(); - } - throw new BugInCF( - cause, - "Error when invoking constructor %s(%s) on args %s; cause: %s", - name, - StringsPlume.join(", ", paramTypes), - Arrays.toString(args), - causeMessage); - } - } - - @Override - public BaseTypeVisitor getVisitor() { - return (BaseTypeVisitor) super.getVisitor(); - } - - /** - * Return the type factory associated with this checker. - * - * @return the type factory associated with this checker - */ - public GenericAnnotatedTypeFactory getTypeFactory() { - BaseTypeVisitor visitor = getVisitor(); - // Avoid NPE if this method is called during initialization. - if (visitor == null) { - throw new TypeSystemError("Called getTypeFactory() before initialization was complete"); - } - return visitor.getTypeFactory(); - } - - @Override - public AnnotationProvider getAnnotationProvider() { - return getTypeFactory(); - } - - /** - * Returns the requested subchecker. A checker of a given class can only be run once, so this - * returns the only such checker, or null if none was found. The caller must know the exact - * checker class to request. - * - * @param the class of the subchecker to return - * @param checkerClass the class of the subchecker to return - * @return the requested subchecker or null if not found - */ - @SuppressWarnings("unchecked") - public @Nullable T getSubchecker(Class checkerClass) { - for (BaseTypeChecker checker : immediateSubcheckers) { - if (checker.getClass() == checkerClass) { - return (T) checker; - } - } - - return null; - } - - /** - * Returns the type factory used by a subchecker. Returns null if no matching subchecker was found - * or if the type factory is null. The caller must know the exact checker class to request. - * - *

          Because the visitor state is copied, call this method each time a subfactory is needed - * rather than store the returned subfactory in a field. - * - * @param subCheckerClass the class of the subchecker - * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} - * @return the type factory of the requested subchecker or null if not found - */ - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public > - @Nullable T getTypeFactoryOfSubcheckerOrNull( - Class subCheckerClass) { - return getTypeFactory().getTypeFactoryOfSubcheckerOrNull(subCheckerClass); - } - - /** - * Returns the unmodifiable list of immediate subcheckers of this checker. - * - *

          Performs a depth first search for all checkers this checker depends on. The depth first - * search ensures that the collection has the correct order the checkers need to be run in. - * - *

          Modifies the alreadyInitializedSubcheckerMap map by adding all recursively newly - * instantiated subcheckers' class objects and instances. It is necessary to use a map that - * preserves the order in which entries were inserted, such as LinkedHashMap or ArrayMap. - * - * @param alreadyInitializedSubcheckerMap subcheckers that have already been instantiated. Is - * modified by this method. - * @return the unmodifiable list of immediate subcheckers of this checker - */ - private List instantiateSubcheckers( - Map, BaseTypeChecker> alreadyInitializedSubcheckerMap) { - Set> classesOfImmediateSubcheckers = - getImmediateSubcheckerClasses(); - if (classesOfImmediateSubcheckers.isEmpty()) { - return Collections.emptyList(); - } - - ArrayList immediateSubcheckers = - new ArrayList<>(classesOfImmediateSubcheckers.size()); - - for (Class subcheckerClass : classesOfImmediateSubcheckers) { - BaseTypeChecker subchecker = alreadyInitializedSubcheckerMap.get(subcheckerClass); - if (subchecker != null) { - // Add the already initialized subchecker to the list of immediate subcheckers so - // that this checker can refer to it. - immediateSubcheckers.add(subchecker); - continue; - } - - BaseTypeChecker instance; - try { - instance = subcheckerClass.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new BugInCF("Could not create an instance of " + subcheckerClass, e); - } - - instance.setProcessingEnvironment(this.processingEnv); - instance.treePathCacher = this.getTreePathCacher(); - // Prevent the new checker from storing non-immediate subcheckers - instance.subcheckers = Collections.emptyList(); - immediateSubcheckers.add(instance); - instance.immediateSubcheckers = - instance.instantiateSubcheckers(alreadyInitializedSubcheckerMap); - instance.setParentChecker(this); - alreadyInitializedSubcheckerMap.put(subcheckerClass, instance); - } - - return Collections.unmodifiableList(immediateSubcheckers); - } - - /** - * Get the list of all subcheckers (if any). via the instantiateSubcheckers method. This list is - * only non-empty for the one checker that runs all other subcheckers. These are recursively - * instantiated via instantiateSubcheckers the first time the method is called if subcheckers is - * null. Assumes all checkers run on the same thread. - * - * @return the list of all subcheckers (if any) - */ - public List getSubcheckers() { - if (subcheckers == null) { - // Instantiate the checkers this one depends on, if any. - Map, BaseTypeChecker> checkerMap = - new ArrayMap, BaseTypeChecker>(2); - - immediateSubcheckers = instantiateSubcheckers(checkerMap); - - subcheckers = Collections.unmodifiableList(new ArrayList<>(checkerMap.values())); - } - - return subcheckers; - } - - // AbstractTypeProcessor delegation - @Override - public void typeProcess(TypeElement element, TreePath tree) { - if (!getSubcheckers().isEmpty()) { - // TODO: I expected this to only be necessary if (parentChecker == null). - // However, the NestedAggregateChecker fails otherwise. - messageStore.clear(); - } - - // Errors (or other messages) issued via - // SourceChecker#message(Diagnostic.Kind, Object, String, Object...) - // are stored in messageStore until all checkers have processed this compilation unit. - // All other messages are printed immediately. This includes errors issued because the - // checker threw an exception. - - // In order to run the next checker on this compilation unit even if the previous issued - // errors, the next checker's errsOnLastExit needs to include all errors issued by previous - // checkers. - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Log log = Log.instance(context); - - int nerrorsOfAllPreviousCheckers = this.errsOnLastExit; - for (BaseTypeChecker subchecker : getSubcheckers()) { - subchecker.errsOnLastExit = nerrorsOfAllPreviousCheckers; - subchecker.messageStore = messageStore; - int errorsBeforeTypeChecking = log.nerrors; - - subchecker.typeProcess(element, tree); - - int errorsAfterTypeChecking = log.nerrors; - nerrorsOfAllPreviousCheckers += errorsAfterTypeChecking - errorsBeforeTypeChecking; - } - - this.errsOnLastExit = nerrorsOfAllPreviousCheckers; - super.typeProcess(element, tree); - - if (!getSubcheckers().isEmpty()) { - printStoredMessages(tree.getCompilationUnit()); - // Update errsOnLastExit to reflect the errors issued. - this.errsOnLastExit = log.nerrors; - } - } - - /** - * Like {@link SourceChecker#getSuppressWarningsPrefixes()}, but includes all prefixes supported - * by this checker or any of its subcheckers. Does not guarantee that the result is in any - * particular order. The result is immutable. - * - * @return the suppress warnings prefixes supported by this checker or any of its subcheckers - */ - public Collection getSuppressWarningsPrefixesOfSubcheckers() { - if (this.suppressWarningsPrefixesOfSubcheckers == null) { - Collection prefixes = getSuppressWarningsPrefixes(); - for (BaseTypeChecker subchecker : getSubcheckers()) { - prefixes.addAll(subchecker.getSuppressWarningsPrefixes()); - } - this.suppressWarningsPrefixesOfSubcheckers = ImmutableSet.copyOf(prefixes); - } - return this.suppressWarningsPrefixesOfSubcheckers; - } - - /** A cache for {@link #getUltimateParentChecker}. */ - private @MonotonicNonNull BaseTypeChecker ultimateParentChecker; - - /** - * Finds the ultimate parent checker of this checker. The ultimate parent checker is the checker - * that the user actually requested, i.e. the one with no parent. The ultimate parent might be - * this checker itself. - * - * @return the first checker in the parent checker chain with no parent checker of its own, i.e., - * the ultimate parent checker - */ - public BaseTypeChecker getUltimateParentChecker() { - if (ultimateParentChecker == null) { - ultimateParentChecker = this; - while (ultimateParentChecker.getParentChecker() instanceof BaseTypeChecker) { - ultimateParentChecker = (BaseTypeChecker) ultimateParentChecker.getParentChecker(); - } - } - - return ultimateParentChecker; - } - - /** - * {@inheritDoc} - * - *

          This implementation collects needed warning suppressions for all subcheckers. - */ - @Override - protected void warnUnneededSuppressions() { - if (parentChecker != null) { - return; - } - - if (!warnUnneededSuppressions) { - return; - } - Set elementsWithSuppressedWarnings = - new HashSet<>(this.elementsWithSuppressedWarnings); - this.elementsWithSuppressedWarnings.clear(); - - Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); - Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); - for (BaseTypeChecker subChecker : subcheckers) { - elementsWithSuppressedWarnings.addAll(subChecker.elementsWithSuppressedWarnings); - subChecker.elementsWithSuppressedWarnings.clear(); - prefixes.addAll(subChecker.getSuppressWarningsPrefixes()); - errorKeys.addAll(subChecker.messagesProperties.stringPropertyNames()); - subChecker.getVisitor().treesWithSuppressWarnings.clear(); - } - warnUnneededSuppressions(elementsWithSuppressedWarnings, prefixes, errorKeys); - - getVisitor().treesWithSuppressWarnings.clear(); - } - - /** - * Stores all messages issued by this checker and its subcheckers for the current compilation - * unit. The messages are printed after all checkers have processed the current compilation unit. - * The purpose is to sort messages, grouping together all messages about a particular line of - * code. - * - *

          If this checker has no subcheckers and is not a subchecker for any other checker, then - * messageStore is null and messages will be printed as they are issued by this checker. - */ - private @MonotonicNonNull TreeSet messageStore; - - /** - * If this is a compound checker or a subchecker of a compound checker, then the message is stored - * until all messages from all checkers for the compilation unit are issued. - * - *

          Otherwise, it prints the message. - */ - @Override - protected void printOrStoreMessage( - Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { - assert this.currentRoot == root; - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - if (messageStore == null) { - super.printOrStoreMessage(kind, message, source, root, trace); - } else { - CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this, trace); - messageStore.add(checkerMessage); - } - } - - /** - * Prints error messages for this checker and all subcheckers such that the errors are ordered by - * line and column number and then by checker. (See {@link #compareCheckerMessages} for more - * precise order.) - * - * @param unit current compilation unit - */ - private void printStoredMessages(CompilationUnitTree unit) { - for (CheckerMessage msg : messageStore) { - super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit, msg.trace); - } - } - - /** Represents a message (e.g., an error message) issued by a checker. */ - private static class CheckerMessage { - /** The severity of the message. */ - final Diagnostic.Kind kind; - - /** The message itself. */ - final String message; - - /** The source code that the message is about. */ - final @InternedDistinct Tree source; - - /** Stores the stack trace when the message is created. */ - final StackTraceElement[] trace; + CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this, trace); + messageStore.add(checkerMessage); + } + } /** - * The checker that issued this message. The compound checker that depends on this checker uses - * this to sort the messages. + * Prints error messages for this checker and all subcheckers such that the errors are ordered + * by line and column number and then by checker. (See {@link #compareCheckerMessages} for more + * precise order.) + * + * @param unit current compilation unit */ - final @InternedDistinct BaseTypeChecker checker; + private void printStoredMessages(CompilationUnitTree unit) { + for (CheckerMessage msg : messageStore) { + super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit, msg.trace); + } + } + + /** Represents a message (e.g., an error message) issued by a checker. */ + private static class CheckerMessage { + /** The severity of the message. */ + final Diagnostic.Kind kind; + + /** The message itself. */ + final String message; + + /** The source code that the message is about. */ + final @InternedDistinct Tree source; + + /** Stores the stack trace when the message is created. */ + final StackTraceElement[] trace; + + /** + * The checker that issued this message. The compound checker that depends on this checker + * uses this to sort the messages. + */ + final @InternedDistinct BaseTypeChecker checker; + + /** + * Create a new CheckerMessage. + * + * @param kind kind of diagnostic, for example, error or warning + * @param message error message that needs to be printed + * @param source tree element causing the error + * @param checker the type-checker in use + * @param trace the stack trace when the message is created + */ + private CheckerMessage( + Diagnostic.Kind kind, + String message, + @FindDistinct Tree source, + @FindDistinct BaseTypeChecker checker, + StackTraceElement[] trace) { + this.kind = kind; + this.message = message; + this.source = source; + this.checker = checker; + this.trace = trace; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CheckerMessage that = (CheckerMessage) o; + return this.kind == that.kind + && this.message.equals(that.message) + && this.source == that.source + && this.checker == that.checker; + } + + @Override + public int hashCode() { + return Objects.hash(kind, message, source, checker); + } + + @Override + public String toString() { + return "CheckerMessage{" + + "kind=" + + kind + + ", checker=" + + checker.getClass().getSimpleName() + + ", message='" + + message + + '\'' + + ", source=" + + source + + '}'; + } + } /** - * Create a new CheckerMessage. + * Compares two {@link CheckerMessage}s. Compares first by position at which the error will be + * printed, then by kind of message, then by the message string, and finally by the order in + * which the checkers run. * - * @param kind kind of diagnostic, for example, error or warning - * @param message error message that needs to be printed - * @param source tree element causing the error - * @param checker the type-checker in use - * @param trace the stack trace when the message is created + * @param o1 the first CheckerMessage + * @param o2 the second CheckerMessage + * @return a negative integer, zero, or a positive integer if the first CheckerMessage is less + * than, equal to, or greater than the second */ - private CheckerMessage( - Diagnostic.Kind kind, - String message, - @FindDistinct Tree source, - @FindDistinct BaseTypeChecker checker, - StackTraceElement[] trace) { - this.kind = kind; - this.message = message; - this.source = source; - this.checker = checker; - this.trace = trace; + private int compareCheckerMessages(CheckerMessage o1, CheckerMessage o2) { + int byPos = InternalUtils.compareDiagnosticPosition(o1.source, o2.source); + if (byPos != 0) { + return byPos; + } + + int kind = o1.kind.compareTo(o2.kind); + if (kind != 0) { + return kind; + } + + int msgcmp = o1.message.compareTo(o2.message); + if (msgcmp == 0) { + // If the two messages are identical so far, it doesn't matter + // from which checker they came. + return 0; + } + + // Sort by order in which the checkers are run. (All the subcheckers, + // followed by the checker.) + List subcheckers = BaseTypeChecker.this.getSubcheckers(); + int o1Index = subcheckers.indexOf(o1.checker); + int o2Index = subcheckers.indexOf(o2.checker); + if (o1Index == -1) { + o1Index = subcheckers.size(); + } + if (o2Index == -1) { + o2Index = subcheckers.size(); + } + int checkercmp = Integer.compare(o1Index, o2Index); + if (checkercmp == 0) { + // If the two messages are from the same checker, sort by message. + return msgcmp; + } else { + return checkercmp; + } } @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + public void typeProcessingOver() { + for (BaseTypeChecker checker : getSubcheckers()) { + checker.typeProcessingOver(); + } + + super.typeProcessingOver(); + } + + @Override + public Set getSupportedOptions() { + if (supportedOptions == null) { + Set options = new HashSet<>(); + options.addAll(super.getSupportedOptions()); + + for (BaseTypeChecker checker : getSubcheckers()) { + options.addAll(checker.getSupportedOptions()); + } + + options.addAll( + expandCFOptions( + Arrays.asList(this.getClass()), options.toArray(new String[0]))); - CheckerMessage that = (CheckerMessage) o; - return this.kind == that.kind - && this.message.equals(that.message) - && this.source == that.source - && this.checker == that.checker; + supportedOptions = Collections.unmodifiableSet(options); + } + return supportedOptions; + } + + @Override + public Map getOptions() { + if (this.options == null) { + Map options = new HashMap<>(super.getOptions()); + + for (BaseTypeChecker checker : getSubcheckers()) { + options.putAll(checker.getOptions()); + } + this.options = Collections.unmodifiableMap(options); + } + + return this.options; + } + + /** + * Like {@link #getOptions}, but only includes options provided to this checker. Does not + * include those passed to subcheckers. + * + * @return the active options for this checker, not including those passed to subcheckers + */ + public Map getOptionsNoSubcheckers() { + return super.getOptions(); + } + + /** + * Like {@link #hasOption}, but checks whether the given option is provided to this checker. + * Does not consider those passed to subcheckers. + * + * @param name the name of the option to check + * @return true if the option name was provided to this checker, false otherwise + */ + public final boolean hasOptionNoSubcheckers(String name) { + return getOptionsNoSubcheckers().containsKey(name); + } + + /** + * Return a list of additional stub files to be treated as if they had been written in a + * {@code @StubFiles} annotation. + * + * @return stub files to be treated as if they had been written in a {@code @StubFiles} + * annotation + */ + public List getExtraStubFiles() { + return Collections.emptyList(); } @Override - public int hashCode() { - return Objects.hash(kind, message, source, checker); + protected Object processErrorMessageArg(Object arg) { + if (arg instanceof Collection) { + Collection carg = (Collection) arg; + return CollectionsPlume.mapList(this::processErrorMessageArg, carg); + } else if (arg instanceof AnnotationMirror && getTypeFactory() != null) { + return getTypeFactory() + .getAnnotationFormatter() + .formatAnnotationMirror((AnnotationMirror) arg); + } else { + return super.processErrorMessageArg(arg); + } } @Override - public String toString() { - return "CheckerMessage{" - + "kind=" - + kind - + ", checker=" - + checker.getClass().getSimpleName() - + ", message='" - + message - + '\'' - + ", source=" - + source - + '}'; - } - } - - /** - * Compares two {@link CheckerMessage}s. Compares first by position at which the error will be - * printed, then by kind of message, then by the message string, and finally by the order in which - * the checkers run. - * - * @param o1 the first CheckerMessage - * @param o2 the second CheckerMessage - * @return a negative integer, zero, or a positive integer if the first CheckerMessage is less - * than, equal to, or greater than the second - */ - private int compareCheckerMessages(CheckerMessage o1, CheckerMessage o2) { - int byPos = InternalUtils.compareDiagnosticPosition(o1.source, o2.source); - if (byPos != 0) { - return byPos; - } - - int kind = o1.kind.compareTo(o2.kind); - if (kind != 0) { - return kind; - } - - int msgcmp = o1.message.compareTo(o2.message); - if (msgcmp == 0) { - // If the two messages are identical so far, it doesn't matter - // from which checker they came. - return 0; - } - - // Sort by order in which the checkers are run. (All the subcheckers, - // followed by the checker.) - List subcheckers = BaseTypeChecker.this.getSubcheckers(); - int o1Index = subcheckers.indexOf(o1.checker); - int o2Index = subcheckers.indexOf(o2.checker); - if (o1Index == -1) { - o1Index = subcheckers.size(); - } - if (o2Index == -1) { - o2Index = subcheckers.size(); - } - int checkercmp = Integer.compare(o1Index, o2Index); - if (checkercmp == 0) { - // If the two messages are from the same checker, sort by message. - return msgcmp; - } else { - return checkercmp; - } - } - - @Override - public void typeProcessingOver() { - for (BaseTypeChecker checker : getSubcheckers()) { - checker.typeProcessingOver(); - } - - super.typeProcessingOver(); - } - - @Override - public Set getSupportedOptions() { - if (supportedOptions == null) { - Set options = new HashSet<>(); - options.addAll(super.getSupportedOptions()); - - for (BaseTypeChecker checker : getSubcheckers()) { - options.addAll(checker.getSupportedOptions()); - } - - options.addAll( - expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0]))); - - supportedOptions = Collections.unmodifiableSet(options); - } - return supportedOptions; - } - - @Override - public Map getOptions() { - if (this.options == null) { - Map options = new HashMap<>(super.getOptions()); - - for (BaseTypeChecker checker : getSubcheckers()) { - options.putAll(checker.getOptions()); - } - this.options = Collections.unmodifiableMap(options); - } - - return this.options; - } - - /** - * Like {@link #getOptions}, but only includes options provided to this checker. Does not include - * those passed to subcheckers. - * - * @return the active options for this checker, not including those passed to subcheckers - */ - public Map getOptionsNoSubcheckers() { - return super.getOptions(); - } - - /** - * Like {@link #hasOption}, but checks whether the given option is provided to this checker. Does - * not consider those passed to subcheckers. - * - * @param name the name of the option to check - * @return true if the option name was provided to this checker, false otherwise - */ - public final boolean hasOptionNoSubcheckers(String name) { - return getOptionsNoSubcheckers().containsKey(name); - } - - /** - * Return a list of additional stub files to be treated as if they had been written in a - * {@code @StubFiles} annotation. - * - * @return stub files to be treated as if they had been written in a {@code @StubFiles} annotation - */ - public List getExtraStubFiles() { - return Collections.emptyList(); - } - - @Override - protected Object processErrorMessageArg(Object arg) { - if (arg instanceof Collection) { - Collection carg = (Collection) arg; - return CollectionsPlume.mapList(this::processErrorMessageArg, carg); - } else if (arg instanceof AnnotationMirror && getTypeFactory() != null) { - return getTypeFactory() - .getAnnotationFormatter() - .formatAnnotationMirror((AnnotationMirror) arg); - } else { - return super.processErrorMessageArg(arg); - } - } - - @Override - protected boolean shouldAddShutdownHook() { - if (super.shouldAddShutdownHook() || getTypeFactory().getCFGVisualizer() != null) { - return true; - } - for (BaseTypeChecker checker : getSubcheckers()) { - if (checker.getTypeFactory().getCFGVisualizer() != null) { - return true; - } - } - return false; - } - - @Override - protected void shutdownHook() { - super.shutdownHook(); - - CFGVisualizer viz = getTypeFactory().getCFGVisualizer(); - if (viz != null) { - viz.shutdown(); - } - - for (BaseTypeChecker checker : getSubcheckers()) { - viz = checker.getTypeFactory().getCFGVisualizer(); - if (viz != null) { - viz.shutdown(); - } - } - } + protected boolean shouldAddShutdownHook() { + if (super.shouldAddShutdownHook() || getTypeFactory().getCFGVisualizer() != null) { + return true; + } + for (BaseTypeChecker checker : getSubcheckers()) { + if (checker.getTypeFactory().getCFGVisualizer() != null) { + return true; + } + } + return false; + } + + @Override + protected void shutdownHook() { + super.shutdownHook(); + + CFGVisualizer viz = getTypeFactory().getCFGVisualizer(); + if (viz != null) { + viz.shutdown(); + } + + for (BaseTypeChecker checker : getSubcheckers()) { + viz = checker.getTypeFactory().getCFGVisualizer(); + if (viz != null) { + viz.shutdown(); + } + } + } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java index b9efcb937f6..9a362af237f 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -10,15 +10,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.TypeUseLocation; @@ -45,6 +37,17 @@ import org.plumelib.util.ArrayMap; import org.plumelib.util.IPair; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; + /** * A visitor to validate the types in a tree. * @@ -60,711 +63,725 @@ * BaseTypeVisitor#visitVariable}. */ public class BaseTypeValidator extends AnnotatedTypeScanner implements TypeValidator { - /** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */ - protected boolean isValid = true; - - /** Should the primary annotation on the top level type be checked? */ - protected boolean checkTopLevelDeclaredOrPrimitiveType = true; - - /** BaseTypeChecker. */ - protected final BaseTypeChecker checker; - - /** BaseTypeVisitor. */ - protected final BaseTypeVisitor visitor; - - /** AnnotatedTypeFactory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The qualifer hierarchy. */ - protected final QualifierHierarchy qualHierarchy; - - // TODO: clean up coupling between components - public BaseTypeValidator( - BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { - this.checker = checker; - this.visitor = visitor; - this.atypeFactory = atypeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - } - - /** - * Validate the type against the given tree. This method both issues error messages and also - * returns a boolean value. - * - *

          This is the entry point to the type validator. Neither this method nor visit should be - * called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}. - * - *

          This method is only called on top-level types, but it validates the entire type including - * components of a compound type. Subclasses should override this only if there is special-case - * behavior that should be performed only on top-level types. - * - * @param type the type to validate - * @param tree the tree from which the type originated. If the tree is a method tree, {@code type} - * is its return type. If the tree is a variable tree, {@code type} is the variable's type. - * @return true if the type is valid - */ - @Override - public boolean isValid(AnnotatedTypeMirror type, Tree tree) { - List diagMessages = isValidStructurally(type); - if (!diagMessages.isEmpty()) { - for (DiagMessage d : diagMessages) { - checker.report(tree, d); - } - return false; - } - this.isValid = true; - this.checkTopLevelDeclaredOrPrimitiveType = - shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); - visit(type, tree); - return this.isValid; - } - - /** - * Should the top-level declared or primitive type be checked? - * - *

          If {@code type} is not a declared or primitive type, then this method returns true. - * - *

          Top-level type is not checked if tree is a local variable or an expression tree. - * - * @param type the AnnotatedTypeMirror being validated - * @param tree a Tree whose type is {@code type} - * @return whether or not the top-level type should be checked, if {@code type} is a declared or - * primitive type. - */ - protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( - AnnotatedTypeMirror type, Tree tree) { - if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) { - return true; - } - return !TreeUtils.isLocalVariable(tree) - && (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree)); - } - - /** - * Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list - * of failures. If successful, returns an empty list. The method will never return failures for a - * valid type, but might not catch all invalid types. - * - *

          This method ensures that the type is structurally or lexically well-formed, but it does not - * check whether the annotations are semantically sensible. Subclasses should generally override - * visit methods such as {@link #visitDeclared} rather than this method. - * - *

          Currently, this implementation checks the following (subclasses can extend this behavior): - * - *

            - *
          1. There should not be multiple annotations from the same qualifier hierarchy. - *
          2. There should not be more annotations than the width of the QualifierHierarchy. - *
          3. If the type is not a type variable, then the number of annotations should be the same as - * the width of the QualifierHierarchy. - *
          4. These properties should also hold recursively for component types of arrays and for - * bounds of type variables and wildcards. - *
          - * - * This does not test whether the Java type is relevant, because by the time this method is - * called, the type includes some non-programmer-written annotations. - * - * @param type the type to test - * @return list of reasons the type is invalid, or empty list if the type is valid - */ - protected List isValidStructurally(AnnotatedTypeMirror type) { - SimpleAnnotatedTypeScanner, Void> scanner = - new SimpleAnnotatedTypeScanner<>( - (atm, p) -> isTopLevelValidType(atm), DiagMessage::mergeLists, Collections.emptyList()); - return scanner.visit(type, null); - } - - /** - * Checks every property listed in {@link #isValidStructurally}, but only for the top level type. - * If successful, returns an empty list. If not successful, returns diagnostics. - * - * @param type the type to be checked - * @return the diagnostics indicating failure, or an empty list if successful - */ - // This method returns a singleton or empyty list. Its return type is List rather than - // DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects - // a list. - protected List isTopLevelValidType(AnnotatedTypeMirror type) { - // multiple annotations from the same hierarchy - AnnotationMirrorSet annotations = type.getAnnotations(); - AnnotationMirrorSet seenTops = new AnnotationMirrorSet(); - for (AnnotationMirror anno : annotations) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); - if (AnnotationUtils.containsSame(seenTops, top)) { - return Collections.singletonList( - DiagMessage.error("type.invalid.conflicting.annos", annotations, type)); - } - seenTops.add(top); - } + /** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */ + protected boolean isValid = true; + + /** Should the primary annotation on the top level type be checked? */ + protected boolean checkTopLevelDeclaredOrPrimitiveType = true; - boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type); + /** BaseTypeChecker. */ + protected final BaseTypeChecker checker; - // wrong number of annotations - if (!canHaveEmptyAnnotationSet && seenTops.size() < qualHierarchy.getWidth()) { - return Collections.singletonList( - DiagMessage.error("type.invalid.too.few.annotations", annotations, type)); + /** BaseTypeVisitor. */ + protected final BaseTypeVisitor visitor; + + /** AnnotatedTypeFactory. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** The qualifer hierarchy. */ + protected final QualifierHierarchy qualHierarchy; + + // TODO: clean up coupling between components + public BaseTypeValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + this.checker = checker; + this.visitor = visitor; + this.atypeFactory = atypeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); } - // success - return Collections.emptyList(); - } - - protected void reportValidityResult( - @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { - checker.reportError(p, errorType, type.getAnnotations(), type.toString()); - isValid = false; - } - - /** - * Like {@link #reportValidityResult}, but the type is printed in the error message without - * annotations. This method would print "annotation @NonNull is not permitted on type int", - * whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on - * type @NonNull int". In addition, when the underlying type is a compound type such as - * {@code @Bad List}, the erased type will be used, i.e., "{@code List}" will print - * instead of "{@code @Bad List}". - */ - protected void reportValidityResultOnUnannotatedType( - @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { - TypeMirror underlying = - TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType()); - checker.reportError(p, errorType, type.getAnnotations(), underlying.toString()); - isValid = false; - } - - /** - * Most errors reported by this class are of the form type.invalid. This method reports when the - * bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective - * annotations on the upper bound are supertypes of those on the lower bounds for all hierarchies. - * To ensure that this subtlety is not lost on users, we report "bound.type.incompatible" and - * print the bounds along with the invalid type rather than a "type.invalid". - * - * @param type the type with invalid bounds - * @param tree where to report the error - */ - protected void reportInvalidBounds(AnnotatedTypeMirror type, Tree tree) { - final String label; - final AnnotatedTypeMirror upperBound; - final AnnotatedTypeMirror lowerBound; - - switch (type.getKind()) { - case TYPEVAR: - label = "type parameter"; - upperBound = ((AnnotatedTypeVariable) type).getUpperBound(); - lowerBound = ((AnnotatedTypeVariable) type).getLowerBound(); - break; - - case WILDCARD: - label = "wildcard"; - upperBound = ((AnnotatedWildcardType) type).getExtendsBound(); - lowerBound = ((AnnotatedWildcardType) type).getSuperBound(); - break; - - default: - throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree); + /** + * Validate the type against the given tree. This method both issues error messages and also + * returns a boolean value. + * + *

          This is the entry point to the type validator. Neither this method nor visit should be + * called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}. + * + *

          This method is only called on top-level types, but it validates the entire type including + * components of a compound type. Subclasses should override this only if there is special-case + * behavior that should be performed only on top-level types. + * + * @param type the type to validate + * @param tree the tree from which the type originated. If the tree is a method tree, {@code + * type} is its return type. If the tree is a variable tree, {@code type} is the variable's + * type. + * @return true if the type is valid + */ + @Override + public boolean isValid(AnnotatedTypeMirror type, Tree tree) { + List diagMessages = isValidStructurally(type); + if (!diagMessages.isEmpty()) { + for (DiagMessage d : diagMessages) { + checker.report(tree, d); + } + return false; + } + this.isValid = true; + this.checkTopLevelDeclaredOrPrimitiveType = + shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); + visit(type, tree); + return this.isValid; } - checker.reportError( - tree, - "bound.type.incompatible", - label, - type.toString(), - upperBound.toString(true), - lowerBound.toString(true)); - isValid = false; - } - - protected void reportInvalidType(AnnotatedTypeMirror type, Tree p) { - reportValidityResult("type.invalid", type, p); - } - - /** - * Report an "annotations.on.use" error for the given type and tree. - * - * @param type the type with invalid annotations - * @param p the tree where to report the error - */ - protected void reportInvalidAnnotationsOnUse(AnnotatedTypeMirror type, Tree p) { - reportValidityResultOnUnannotatedType("type.invalid.annotations.on.use", type, p); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + /** + * Should the top-level declared or primitive type be checked? + * + *

          If {@code type} is not a declared or primitive type, then this method returns true. + * + *

          Top-level type is not checked if tree is a local variable or an expression tree. + * + * @param type the AnnotatedTypeMirror being validated + * @param tree a Tree whose type is {@code type} + * @return whether or not the top-level type should be checked, if {@code type} is a declared or + * primitive type. + */ + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( + AnnotatedTypeMirror type, Tree tree) { + if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) { + return true; + } + return !TreeUtils.isLocalVariable(tree) + && (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree)); } - boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement()); + /** + * Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list + * of failures. If successful, returns an empty list. The method will never return failures for + * a valid type, but might not catch all invalid types. + * + *

          This method ensures that the type is structurally or lexically well-formed, but it does + * not check whether the annotations are semantically sensible. Subclasses should generally + * override visit methods such as {@link #visitDeclared} rather than this method. + * + *

          Currently, this implementation checks the following (subclasses can extend this behavior): + * + *

            + *
          1. There should not be multiple annotations from the same qualifier hierarchy. + *
          2. There should not be more annotations than the width of the QualifierHierarchy. + *
          3. If the type is not a type variable, then the number of annotations should be the same + * as the width of the QualifierHierarchy. + *
          4. These properties should also hold recursively for component types of arrays and for + * bounds of type variables and wildcards. + *
          + * + * This does not test whether the Java type is relevant, because by the time this method is + * called, the type includes some non-programmer-written annotations. + * + * @param type the type to test + * @return list of reasons the type is invalid, or empty list if the type is valid + */ + protected List isValidStructurally(AnnotatedTypeMirror type) { + SimpleAnnotatedTypeScanner, Void> scanner = + new SimpleAnnotatedTypeScanner<>( + (atm, p) -> isTopLevelValidType(atm), + DiagMessage::mergeLists, + Collections.emptyList()); + return scanner.visit(type, null); + } - if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) { - // Ensure that type use is a subtype of the element type - // isValidUse determines the erasure of the types. + /** + * Checks every property listed in {@link #isValidStructurally}, but only for the top level + * type. If successful, returns an empty list. If not successful, returns diagnostics. + * + * @param type the type to be checked + * @return the diagnostics indicating failure, or an empty list if successful + */ + // This method returns a singleton or empyty list. Its return type is List rather than + // DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects + // a list. + protected List isTopLevelValidType(AnnotatedTypeMirror type) { + // multiple annotations from the same hierarchy + AnnotationMirrorSet annotations = type.getAnnotations(); + AnnotationMirrorSet seenTops = new AnnotationMirrorSet(); + for (AnnotationMirror anno : annotations) { + AnnotationMirror top = qualHierarchy.getTopAnnotation(anno); + if (AnnotationUtils.containsSame(seenTops, top)) { + return Collections.singletonList( + DiagMessage.error("type.invalid.conflicting.annos", annotations, type)); + } + seenTops.add(top); + } - AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type); - AnnotatedDeclaredType elemType = type.deepCopy(); - elemType.clearAnnotations(); - elemType.addAnnotations(bounds); + // wrong number of annotations + if (!canHaveEmptyAnnotationSet && seenTops.size() < qualHierarchy.getWidth()) { + return Collections.singletonList( + DiagMessage.error("type.invalid.too.few.annotations", annotations, type)); + } - if (!visitor.isValidUse(elemType, type, tree)) { - reportInvalidAnnotationsOnUse(type, tree); - } + // success + return Collections.emptyList(); } - // Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called, - // the type isn't the top level, so always do the check. - checkTopLevelDeclaredOrPrimitiveType = true; - - if (TreeUtils.isClassTree(tree)) { - visitedNodes.put(type, null); - visitClassTypeParameters(type, (ClassTree) tree); - return null; + + protected void reportValidityResult( + @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { + checker.reportError(p, errorType, type.getAnnotations(), type.toString()); + isValid = false; } - /* - * Try to reconstruct the ParameterizedTypeTree from the given tree. - * TODO: there has to be a nicer way to do this... + /** + * Like {@link #reportValidityResult}, but the type is printed in the error message without + * annotations. This method would print "annotation @NonNull is not permitted on type int", + * whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on + * type @NonNull int". In addition, when the underlying type is a compound type such as + * {@code @Bad List}, the erased type will be used, i.e., "{@code List}" will print + * instead of "{@code @Bad List}". */ - IPair p = - extractParameterizedTypeTree(tree, type); - ParameterizedTypeTree typeArgTree = p.first; - type = p.second; - - if (typeArgTree == null) { - return super.visitDeclared(type, tree); - } // else - - // We put this here because we don't want to put it in visitedNodes before calling - // super (in the else branch) because that would cause the super implementation - // to detect that we've already visited type and to immediately return. - visitedNodes.put(type, null); - - // We have a ParameterizedTypeTree -> visit it. - - visitParameterizedType(type, typeArgTree); - - /* - * Instead of calling super with the unchanged "tree", adapt the - * second argument to be the corresponding type argument tree. This - * ensures that the first and second parameter to this method always - * correspond. visitDeclared is the only method that had this - * problem. + protected void reportValidityResultOnUnannotatedType( + @CompilerMessageKey String errorType, AnnotatedTypeMirror type, Tree p) { + TypeMirror underlying = + TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType()); + checker.reportError(p, errorType, type.getAnnotations(), underlying.toString()); + isValid = false; + } + + /** + * Most errors reported by this class are of the form type.invalid. This method reports when the + * bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective + * annotations on the upper bound are supertypes of those on the lower bounds for all + * hierarchies. To ensure that this subtlety is not lost on users, we report + * "bound.type.incompatible" and print the bounds along with the invalid type rather than a + * "type.invalid". + * + * @param type the type with invalid bounds + * @param tree where to report the error */ - List tatypes = type.getTypeArguments(); + protected void reportInvalidBounds(AnnotatedTypeMirror type, Tree tree) { + final String label; + final AnnotatedTypeMirror upperBound; + final AnnotatedTypeMirror lowerBound; + + switch (type.getKind()) { + case TYPEVAR: + label = "type parameter"; + upperBound = ((AnnotatedTypeVariable) type).getUpperBound(); + lowerBound = ((AnnotatedTypeVariable) type).getLowerBound(); + break; + + case WILDCARD: + label = "wildcard"; + upperBound = ((AnnotatedWildcardType) type).getExtendsBound(); + lowerBound = ((AnnotatedWildcardType) type).getSuperBound(); + break; + + default: + throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree); + } - if (tatypes == null) { - return null; + checker.reportError( + tree, + "bound.type.incompatible", + label, + type.toString(), + upperBound.toString(true), + lowerBound.toString(true)); + isValid = false; } - // May be zero for a "diamond" (inferred type args in constructor invocation). - int numTypeArgs = typeArgTree.getTypeArguments().size(); - if (numTypeArgs != 0) { - // TODO: this should be an equality, but in the past it failed with: - // daikon/Debug.java; message: size mismatch for type arguments: - // @NonNull Object and Class - // but I didn't manage to reduce it to a test case. - assert tatypes.size() <= numTypeArgs || skipChecks - : "size mismatch for type arguments: " + type + " and " + typeArgTree; - - for (int i = 0; i < tatypes.size(); ++i) { - scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i)); - } + protected void reportInvalidType(AnnotatedTypeMirror type, Tree p) { + reportValidityResult("type.invalid", type, p); } - // Don't call the super version, because it creates a mismatch - // between the first and second parameters. - // return super.visitDeclared(type, tree); - - return null; - } - - /** - * Visits the type parameters of a class tree. - * - * @param type type of {@code tree} - * @param tree a class tree - */ - protected void visitClassTypeParameters(AnnotatedDeclaredType type, ClassTree tree) { - for (int i = 0, size = type.getTypeArguments().size(); i < size; i++) { - AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type.getTypeArguments().get(i); - TypeParameterTree typeParameterTree = tree.getTypeParameters().get(i); - scan(typeParameter, typeParameterTree); - } - } - - /** - * Visits type parameter bounds. - * - * @param typeParameter type of {@code typeParameterTree} - * @param typeParameterTree a type parameter tree - */ - protected void visitTypeParameterBounds( - AnnotatedTypeVariable typeParameter, TypeParameterTree typeParameterTree) { - List boundTrees = typeParameterTree.getBounds(); - if (boundTrees.size() == 1) { - scan(typeParameter.getUpperBound(), boundTrees.get(0)); - } else if (boundTrees.size() == 0) { - // The upper bound is implicitly Object - scan(typeParameter.getUpperBound(), typeParameterTree); - } else { - AnnotatedIntersectionType intersectionType = - (AnnotatedIntersectionType) typeParameter.getUpperBound(); - for (int j = 0; j < intersectionType.getBounds().size(); j++) { - scan(intersectionType.getBounds().get(j), boundTrees.get(j)); - } + /** + * Report an "annotations.on.use" error for the given type and tree. + * + * @param type the type with invalid annotations + * @param p the tree where to report the error + */ + protected void reportInvalidAnnotationsOnUse(AnnotatedTypeMirror type, Tree p) { + reportValidityResultOnUnannotatedType("type.invalid.annotations.on.use", type, p); } - } - - /** - * If {@code tree} has a {@link ParameterizedTypeTree}, then the tree and its type is returned. - * Otherwise null and {@code type} are returned. - * - * @param tree tree to search - * @param type type to return if no {@code ParameterizedTypeTree} is found - * @return if {@code tree} has a {@code ParameterizedTypeTree}, then returns the tree and its - * type. Otherwise, returns null and {@code type}. - */ - private IPair<@Nullable ParameterizedTypeTree, AnnotatedDeclaredType> - extractParameterizedTypeTree(Tree tree, AnnotatedDeclaredType type) { - ParameterizedTypeTree typeargtree = null; - - switch (tree.getKind()) { - case VARIABLE: - Tree lt = ((VariableTree) tree).getType(); - if (lt instanceof ParameterizedTypeTree) { - typeargtree = (ParameterizedTypeTree) lt; - } else { - // System.out.println("Found a: " + lt); + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - break; - case PARAMETERIZED_TYPE: - typeargtree = (ParameterizedTypeTree) tree; - break; - case NEW_CLASS: - NewClassTree nct = (NewClassTree) tree; - ExpressionTree nctid = nct.getIdentifier(); - if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) { - typeargtree = (ParameterizedTypeTree) nctid; - /* - * This is quite tricky... for anonymous class instantiations, - * the type at this point has no type arguments. By doing the - * following, we get the type arguments again. - */ - type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree); + + boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement()); + + if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) { + // Ensure that type use is a subtype of the element type + // isValidUse determines the erasure of the types. + + AnnotationMirrorSet bounds = + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + + AnnotatedDeclaredType elemType = type.deepCopy(); + elemType.clearAnnotations(); + elemType.addAnnotations(bounds); + + if (!visitor.isValidUse(elemType, type, tree)) { + reportInvalidAnnotationsOnUse(type, tree); + } } - break; - case ANNOTATED_TYPE: - AnnotatedTypeTree tr = (AnnotatedTypeTree) tree; - ExpressionTree undtr = tr.getUnderlyingType(); - if (undtr instanceof ParameterizedTypeTree) { - typeargtree = (ParameterizedTypeTree) undtr; - } else if (undtr instanceof IdentifierTree) { - // @Something D -> Nothing to do - } else { - // TODO: add more test cases to ensure that nested types are - // handled correctly, - // e.g. @Nullable() List<@Nullable Object>[][] - IPair p = - extractParameterizedTypeTree(undtr, type); - typeargtree = p.first; - type = p.second; + // Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called, + // the type isn't the top level, so always do the check. + checkTopLevelDeclaredOrPrimitiveType = true; + + if (TreeUtils.isClassTree(tree)) { + visitedNodes.put(type, null); + visitClassTypeParameters(type, (ClassTree) tree); + return null; } - break; - case IDENTIFIER: - case ARRAY_TYPE: - case NEW_ARRAY: - case MEMBER_SELECT: - case UNBOUNDED_WILDCARD: - case EXTENDS_WILDCARD: - case SUPER_WILDCARD: - case TYPE_PARAMETER: - // Nothing to do. - break; - case METHOD: - // If a MethodTree is passed, it's just the return type that is validated. - // See BaseTypeVisitor#validateTypeOf. - MethodTree methodTree = (MethodTree) tree; - if (methodTree.getReturnType() instanceof ParameterizedTypeTree) { - typeargtree = (ParameterizedTypeTree) methodTree.getReturnType(); + + /* + * Try to reconstruct the ParameterizedTypeTree from the given tree. + * TODO: there has to be a nicer way to do this... + */ + IPair p = + extractParameterizedTypeTree(tree, type); + ParameterizedTypeTree typeArgTree = p.first; + type = p.second; + + if (typeArgTree == null) { + return super.visitDeclared(type, tree); + } // else + + // We put this here because we don't want to put it in visitedNodes before calling + // super (in the else branch) because that would cause the super implementation + // to detect that we've already visited type and to immediately return. + visitedNodes.put(type, null); + + // We have a ParameterizedTypeTree -> visit it. + + visitParameterizedType(type, typeArgTree); + + /* + * Instead of calling super with the unchanged "tree", adapt the + * second argument to be the corresponding type argument tree. This + * ensures that the first and second parameter to this method always + * correspond. visitDeclared is the only method that had this + * problem. + */ + List tatypes = type.getTypeArguments(); + + if (tatypes == null) { + return null; } - break; - default: - // The parameterized type is the result of some expression tree. - // No need to do anything further. - break; - } - return IPair.of(typeargtree, type); - } + // May be zero for a "diamond" (inferred type args in constructor invocation). + int numTypeArgs = typeArgTree.getTypeArguments().size(); + if (numTypeArgs != 0) { + // TODO: this should be an equality, but in the past it failed with: + // daikon/Debug.java; message: size mismatch for type arguments: + // @NonNull Object and Class + // but I didn't manage to reduce it to a test case. + assert tatypes.size() <= numTypeArgs || skipChecks + : "size mismatch for type arguments: " + type + " and " + typeArgTree; + + for (int i = 0; i < tatypes.size(); ++i) { + scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i)); + } + } - @Override - @SuppressWarnings( - "signature:argument.type.incompatible") // PrimitiveType.toString(): @PrimitiveType - public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { - if (!checkTopLevelDeclaredOrPrimitiveType - || checker.shouldSkipUses(type.getUnderlyingType().toString())) { - return super.visitPrimitive(type, tree); - } + // Don't call the super version, because it creates a mismatch + // between the first and second parameters. + // return super.visitDeclared(type, tree); - if (!visitor.isValidUse(type, tree)) { - reportInvalidAnnotationsOnUse(type, tree); + return null; } - return super.visitPrimitive(type, tree); - } - - @Override - public Void visitArray(AnnotatedArrayType type, Tree tree) { - // TODO: is there already or add a helper method - // to determine the non-array component type - AnnotatedTypeMirror comp = type; - do { - comp = ((AnnotatedArrayType) comp).getComponentType(); - } while (comp.getKind() == TypeKind.ARRAY); - - if (comp.getKind() == TypeKind.DECLARED - && checker.shouldSkipUses(((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) { - return super.visitArray(type, tree); + /** + * Visits the type parameters of a class tree. + * + * @param type type of {@code tree} + * @param tree a class tree + */ + protected void visitClassTypeParameters(AnnotatedDeclaredType type, ClassTree tree) { + for (int i = 0, size = type.getTypeArguments().size(); i < size; i++) { + AnnotatedTypeVariable typeParameter = + (AnnotatedTypeVariable) type.getTypeArguments().get(i); + TypeParameterTree typeParameterTree = tree.getTypeParameters().get(i); + scan(typeParameter, typeParameterTree); + } } - if (!visitor.isValidUse(type, tree)) { - reportInvalidAnnotationsOnUse(type, tree); + /** + * Visits type parameter bounds. + * + * @param typeParameter type of {@code typeParameterTree} + * @param typeParameterTree a type parameter tree + */ + protected void visitTypeParameterBounds( + AnnotatedTypeVariable typeParameter, TypeParameterTree typeParameterTree) { + List boundTrees = typeParameterTree.getBounds(); + if (boundTrees.size() == 1) { + scan(typeParameter.getUpperBound(), boundTrees.get(0)); + } else if (boundTrees.size() == 0) { + // The upper bound is implicitly Object + scan(typeParameter.getUpperBound(), typeParameterTree); + } else { + AnnotatedIntersectionType intersectionType = + (AnnotatedIntersectionType) typeParameter.getUpperBound(); + for (int j = 0; j < intersectionType.getBounds().size(); j++) { + scan(intersectionType.getBounds().get(j), boundTrees.get(j)); + } + } } - return super.visitArray(type, tree); - } - - /** - * Checks that the annotations on the type arguments supplied to a type or a method invocation are - * within the bounds of the type variables as declared, and issues the - * "type.argument.type.incompatible" error if they are not. - * - * @param type the type to check - * @param tree the type's tree - */ - protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) { - // System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s%n", type, - // tree); - - if (TreeUtils.isDiamondTree(tree)) { - return null; - } + /** + * If {@code tree} has a {@link ParameterizedTypeTree}, then the tree and its type is returned. + * Otherwise null and {@code type} are returned. + * + * @param tree tree to search + * @param type type to return if no {@code ParameterizedTypeTree} is found + * @return if {@code tree} has a {@code ParameterizedTypeTree}, then returns the tree and its + * type. Otherwise, returns null and {@code type}. + */ + private IPair<@Nullable ParameterizedTypeTree, AnnotatedDeclaredType> + extractParameterizedTypeTree(Tree tree, AnnotatedDeclaredType type) { + ParameterizedTypeTree typeargtree = null; + + switch (tree.getKind()) { + case VARIABLE: + Tree lt = ((VariableTree) tree).getType(); + if (lt instanceof ParameterizedTypeTree) { + typeargtree = (ParameterizedTypeTree) lt; + } else { + // System.out.println("Found a: " + lt); + } + break; + case PARAMETERIZED_TYPE: + typeargtree = (ParameterizedTypeTree) tree; + break; + case NEW_CLASS: + NewClassTree nct = (NewClassTree) tree; + ExpressionTree nctid = nct.getIdentifier(); + if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) { + typeargtree = (ParameterizedTypeTree) nctid; + /* + * This is quite tricky... for anonymous class instantiations, + * the type at this point has no type arguments. By doing the + * following, we get the type arguments again. + */ + type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree); + } + break; + case ANNOTATED_TYPE: + AnnotatedTypeTree tr = (AnnotatedTypeTree) tree; + ExpressionTree undtr = tr.getUnderlyingType(); + if (undtr instanceof ParameterizedTypeTree) { + typeargtree = (ParameterizedTypeTree) undtr; + } else if (undtr instanceof IdentifierTree) { + // @Something D -> Nothing to do + } else { + // TODO: add more test cases to ensure that nested types are + // handled correctly, + // e.g. @Nullable() List<@Nullable Object>[][] + IPair p = + extractParameterizedTypeTree(undtr, type); + typeargtree = p.first; + type = p.second; + } + break; + case IDENTIFIER: + case ARRAY_TYPE: + case NEW_ARRAY: + case MEMBER_SELECT: + case UNBOUNDED_WILDCARD: + case EXTENDS_WILDCARD: + case SUPER_WILDCARD: + case TYPE_PARAMETER: + // Nothing to do. + break; + case METHOD: + // If a MethodTree is passed, it's just the return type that is validated. + // See BaseTypeVisitor#validateTypeOf. + MethodTree methodTree = (MethodTree) tree; + if (methodTree.getReturnType() instanceof ParameterizedTypeTree) { + typeargtree = (ParameterizedTypeTree) methodTree.getReturnType(); + } + break; + default: + // The parameterized type is the result of some expression tree. + // No need to do anything further. + break; + } - TypeElement element = (TypeElement) type.getUnderlyingType().asElement(); - if (checker.shouldSkipUses(element)) { - return null; + return IPair.of(typeargtree, type); } - AnnotatedDeclaredType capturedType = - (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type); - List bounds = - atypeFactory.typeVariablesFromUse(capturedType, element); + @Override + @SuppressWarnings( + "signature:argument.type.incompatible") // PrimitiveType.toString(): @PrimitiveType + public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { + if (!checkTopLevelDeclaredOrPrimitiveType + || checker.shouldSkipUses(type.getUnderlyingType().toString())) { + return super.visitPrimitive(type, tree); + } - visitor.checkTypeArguments( - tree, - bounds, - capturedType.getTypeArguments(), - tree.getTypeArguments(), - element.getSimpleName(), - element.getTypeParameters()); + if (!visitor.isValidUse(type, tree)) { + reportInvalidAnnotationsOnUse(type, tree); + } - @SuppressWarnings( - "interning:not.interned") // applyCaptureConversion returns the passed type if type - // does not have wildcards. - boolean hasCapturedTypeVariables = capturedType != type; - if (!hasCapturedTypeVariables) { - return null; + return super.visitPrimitive(type, tree); } - // Check that the extends bound of the captured type variable is a subtype of the - // extends bound of the wildcard. - int numTypeArgs = capturedType.getTypeArguments().size(); - // First create a mapping from captured type variable to its wildcard. - Map typeVarToWildcard = - ArrayMap.newArrayMapOrHashMap(numTypeArgs); - for (int i = 0; i < numTypeArgs; i++) { - AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); - if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType()) - && type.getTypeArguments().get(i).getKind() == TypeKind.WILDCARD) { - AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i); - typeVarToWildcard.put(capturedTypeVar.getUnderlyingType(), wildcard); - } + @Override + public Void visitArray(AnnotatedArrayType type, Tree tree) { + // TODO: is there already or add a helper method + // to determine the non-array component type + AnnotatedTypeMirror comp = type; + do { + comp = ((AnnotatedArrayType) comp).getComponentType(); + } while (comp.getKind() == TypeKind.ARRAY); + + if (comp.getKind() == TypeKind.DECLARED + && checker.shouldSkipUses( + ((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) { + return super.visitArray(type, tree); + } + + if (!visitor.isValidUse(type, tree)) { + reportInvalidAnnotationsOnUse(type, tree); + } + + return super.visitArray(type, tree); } - for (int i = 0; i < numTypeArgs; i++) { - if (type.getTypeArguments().get(i).getKind() != TypeKind.WILDCARD) { - continue; - } - AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i); - if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())) { - AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; - // Substitute the captured type variables with their wildcards. Without - // this, the isSubtype check crashes because wildcards aren't comparable - // with type variables. - AnnotatedTypeMirror captureTypeVarUB = - atypeFactory - .getTypeVarSubstitutor() - .substituteWithoutCopyingTypeArguments( - typeVarToWildcard, capturedTypeVar.getUpperBound()); - if (!atypeFactory - .getTypeHierarchy() - .isSubtype(captureTypeVarUB, wildcard.getExtendsBound())) { - // For most captured type variables, this will trivially hold, as capturing - // incorporated the extends bound of the wildcard into the upper bound of the - // type variable. - // This will fail if the bound and the wildcard have generic types and there is - // no appropriate GLB. - // This issues an error for types that cannot be satisfied, because the two - // bounds have contradictory requirements. - checker.reportError( - tree.getTypeArguments().get(i), - "type.argument.type.incompatible", - element.getTypeParameters().get(i), - element.getSimpleName(), - wildcard.getExtendsBound(), - capturedTypeVar.getUpperBound()); + /** + * Checks that the annotations on the type arguments supplied to a type or a method invocation + * are within the bounds of the type variables as declared, and issues the + * "type.argument.type.incompatible" error if they are not. + * + * @param type the type to check + * @param tree the type's tree + */ + protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) { + // System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s%n", type, + // tree); + + if (TreeUtils.isDiamondTree(tree)) { + return null; } - } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { - // If the super bound of the wildcard is the same as the upper bound of the - // type parameter, then javac uses the bound rather than creating a fresh - // type variable. - // (See https://bugs.openjdk.org/browse/JDK-8054309.) - // In this case, the Checker Framework uses the annotations on the super - // bound of the wildcard and ignores the annotations on the extends bound. - // For example, Set<@1 ? super @2 Object> will collapse into Set<@2 Object>. - // So, issue a warning if the annotations on the extends bound are not the - // same as the annotations on the super bound. - if (!(atypeFactory - .getTypeHierarchy() - .isSubtypeShallowEffective(wildcard.getSuperBound(), wildcard.getExtendsBound()) - && atypeFactory - .getTypeHierarchy() - .isSubtypeShallowEffective(wildcard.getExtendsBound(), wildcard.getSuperBound()))) { - checker.reportError( - tree.getTypeArguments().get(i), - "type.invalid.super.wildcard", - wildcard.getExtendsBound(), - wildcard.getSuperBound()); + + TypeElement element = (TypeElement) type.getUnderlyingType().asElement(); + if (checker.shouldSkipUses(element)) { + return null; } - } - } - return null; - } + AnnotatedDeclaredType capturedType = + (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type); + List bounds = + atypeFactory.typeVariablesFromUse(capturedType, element); + + visitor.checkTypeArguments( + tree, + bounds, + capturedType.getTypeArguments(), + tree.getTypeArguments(), + element.getSimpleName(), + element.getTypeParameters()); + + @SuppressWarnings( + "interning:not.interned") // applyCaptureConversion returns the passed type if type + // does not have wildcards. + boolean hasCapturedTypeVariables = capturedType != type; + if (!hasCapturedTypeVariables) { + return null; + } - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } + // Check that the extends bound of the captured type variable is a subtype of the + // extends bound of the wildcard. + int numTypeArgs = capturedType.getTypeArguments().size(); + // First create a mapping from captured type variable to its wildcard. + Map typeVarToWildcard = + ArrayMap.newArrayMapOrHashMap(numTypeArgs); + for (int i = 0; i < numTypeArgs; i++) { + AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); + if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType()) + && type.getTypeArguments().get(i).getKind() == TypeKind.WILDCARD) { + AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; + AnnotatedWildcardType wildcard = + (AnnotatedWildcardType) type.getTypeArguments().get(i); + typeVarToWildcard.put(capturedTypeVar.getUnderlyingType(), wildcard); + } + } - if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) { - reportInvalidBounds(type, tree); - } - AnnotatedTypeVariable useOfTypeVar = type.asUse(); - if (tree instanceof TypeParameterTree) { - TypeParameterTree typeParameterTree = (TypeParameterTree) tree; - visitedNodes.put(useOfTypeVar, defaultResult); - visitTypeParameterBounds(useOfTypeVar, typeParameterTree); - visitedNodes.put(useOfTypeVar, defaultResult); - return null; - } - return super.visitTypeVariable(useOfTypeVar, tree); - } + for (int i = 0; i < numTypeArgs; i++) { + if (type.getTypeArguments().get(i).getKind() != TypeKind.WILDCARD) { + continue; + } + AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i); + AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i); + if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())) { + AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg; + // Substitute the captured type variables with their wildcards. Without + // this, the isSubtype check crashes because wildcards aren't comparable + // with type variables. + AnnotatedTypeMirror captureTypeVarUB = + atypeFactory + .getTypeVarSubstitutor() + .substituteWithoutCopyingTypeArguments( + typeVarToWildcard, capturedTypeVar.getUpperBound()); + if (!atypeFactory + .getTypeHierarchy() + .isSubtype(captureTypeVarUB, wildcard.getExtendsBound())) { + // For most captured type variables, this will trivially hold, as capturing + // incorporated the extends bound of the wildcard into the upper bound of the + // type variable. + // This will fail if the bound and the wildcard have generic types and there is + // no appropriate GLB. + // This issues an error for types that cannot be satisfied, because the two + // bounds have contradictory requirements. + checker.reportError( + tree.getTypeArguments().get(i), + "type.argument.type.incompatible", + element.getTypeParameters().get(i), + element.getSimpleName(), + wildcard.getExtendsBound(), + capturedTypeVar.getUpperBound()); + } + } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { + // If the super bound of the wildcard is the same as the upper bound of the + // type parameter, then javac uses the bound rather than creating a fresh + // type variable. + // (See https://bugs.openjdk.org/browse/JDK-8054309.) + // In this case, the Checker Framework uses the annotations on the super + // bound of the wildcard and ignores the annotations on the extends bound. + // For example, Set<@1 ? super @2 Object> will collapse into Set<@2 Object>. + // So, issue a warning if the annotations on the extends bound are not the + // same as the annotations on the super bound. + if (!(atypeFactory + .getTypeHierarchy() + .isSubtypeShallowEffective( + wildcard.getSuperBound(), wildcard.getExtendsBound()) + && atypeFactory + .getTypeHierarchy() + .isSubtypeShallowEffective( + wildcard.getExtendsBound(), wildcard.getSuperBound()))) { + checker.reportError( + tree.getTypeArguments().get(i), + "type.invalid.super.wildcard", + wildcard.getExtendsBound(), + wildcard.getSuperBound()); + } + } + } - @Override - public Void visitWildcard(AnnotatedWildcardType type, Tree tree) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + return null; } - if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) { - reportInvalidBounds(type, tree); - } + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } - validateWildCardTargetLocation(type, tree); - return super.visitWildcard(type, tree); - } - - /** - * Returns true if the effective annotations on the upperBound are above (or equal to) those on - * the lowerBound. - * - * @param upperBound the upper bound to check - * @param lowerBound the lower bound to check - * @return true if the effective annotations on the upperBound are above (or equal to) those on - * the lowerBound - */ - public boolean areBoundsValid(AnnotatedTypeMirror upperBound, AnnotatedTypeMirror lowerBound) { - AnnotationMirrorSet upperBoundAnnos = - AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, upperBound); - AnnotationMirrorSet lowerBoundAnnos = - AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, lowerBound); - - if (upperBoundAnnos.size() == lowerBoundAnnos.size()) { - return atypeFactory.getTypeHierarchy().isSubtypeShallowEffective(lowerBound, upperBound); - } else { - // When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will - // be reported as invalid. Therefore, we do not do any other comparisons nor do we - // report a bound. - return true; + if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) { + reportInvalidBounds(type, tree); + } + AnnotatedTypeVariable useOfTypeVar = type.asUse(); + if (tree instanceof TypeParameterTree) { + TypeParameterTree typeParameterTree = (TypeParameterTree) tree; + visitedNodes.put(useOfTypeVar, defaultResult); + visitTypeParameterBounds(useOfTypeVar, typeParameterTree); + visitedNodes.put(useOfTypeVar, defaultResult); + return null; + } + return super.visitTypeVariable(useOfTypeVar, tree); } - } - - /** - * Validate if qualifiers on wildcard are permitted by {@link - * org.checkerframework.framework.qual.TargetLocations}. Report an error if the actual use of this - * annotation is not listed in the declared TypeUseLocations in this meta-annotation. - * - * @param type the type to check - * @param tree the tree of this type - */ - protected void validateWildCardTargetLocation(AnnotatedWildcardType type, Tree tree) { - if (visitor.ignoreTargetLocations) { - return; + + @Override + public Void visitWildcard(AnnotatedWildcardType type, Tree tree) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + + if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) { + reportInvalidBounds(type, tree); + } + + validateWildCardTargetLocation(type, tree); + return super.visitWildcard(type, tree); } - for (AnnotationMirror am : type.getSuperBound().getAnnotations()) { - List locations = - visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means - // that the qualifier can be written on any type use. - // Otherwise, for a valid use of qualifier on the super bound, that qualifier must - // declare one of these four type-use locations in the @TargetLocations meta-annotation. - List lowerLocations = - Arrays.asList( - TypeUseLocation.ALL, - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.IMPLICIT_LOWER_BOUND, - TypeUseLocation.EXPLICIT_LOWER_BOUND); - if (locations == null || locations.stream().anyMatch(lowerLocations::contains)) { - continue; - } - - checker.reportError( - tree, - "type.invalid.annotations.on.location", - type.getSuperBound().getAnnotations().toString(), - "SUPER_WILDCARD"); + /** + * Returns true if the effective annotations on the upperBound are above (or equal to) those on + * the lowerBound. + * + * @param upperBound the upper bound to check + * @param lowerBound the lower bound to check + * @return true if the effective annotations on the upperBound are above (or equal to) those on + * the lowerBound + */ + public boolean areBoundsValid(AnnotatedTypeMirror upperBound, AnnotatedTypeMirror lowerBound) { + AnnotationMirrorSet upperBoundAnnos = + AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, upperBound); + AnnotationMirrorSet lowerBoundAnnos = + AnnotatedTypes.findEffectiveAnnotations(qualHierarchy, lowerBound); + + if (upperBoundAnnos.size() == lowerBoundAnnos.size()) { + return atypeFactory + .getTypeHierarchy() + .isSubtypeShallowEffective(lowerBound, upperBound); + } else { + // When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will + // be reported as invalid. Therefore, we do not do any other comparisons nor do we + // report a bound. + return true; + } } - for (AnnotationMirror am : type.getExtendsBound().getAnnotations()) { - List locations = - visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - List upperLocations = - Arrays.asList( - TypeUseLocation.ALL, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.IMPLICIT_UPPER_BOUND, - TypeUseLocation.EXPLICIT_UPPER_BOUND); - if (locations == null || locations.stream().anyMatch(upperLocations::contains)) { - continue; - } - - checker.reportError( - tree, - "type.invalid.annotations.on.location", - type.getExtendsBound().getAnnotations().toString(), - "EXTENDS_WILDCARD"); + /** + * Validate if qualifiers on wildcard are permitted by {@link + * org.checkerframework.framework.qual.TargetLocations}. Report an error if the actual use of + * this annotation is not listed in the declared TypeUseLocations in this meta-annotation. + * + * @param type the type to check + * @param tree the tree of this type + */ + protected void validateWildCardTargetLocation(AnnotatedWildcardType type, Tree tree) { + if (visitor.ignoreTargetLocations) { + return; + } + + for (AnnotationMirror am : type.getSuperBound().getAnnotations()) { + List locations = + visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means + // that the qualifier can be written on any type use. + // Otherwise, for a valid use of qualifier on the super bound, that qualifier must + // declare one of these four type-use locations in the @TargetLocations meta-annotation. + List lowerLocations = + Arrays.asList( + TypeUseLocation.ALL, + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.IMPLICIT_LOWER_BOUND, + TypeUseLocation.EXPLICIT_LOWER_BOUND); + if (locations == null || locations.stream().anyMatch(lowerLocations::contains)) { + continue; + } + + checker.reportError( + tree, + "type.invalid.annotations.on.location", + type.getSuperBound().getAnnotations().toString(), + "SUPER_WILDCARD"); + } + + for (AnnotationMirror am : type.getExtendsBound().getAnnotations()) { + List locations = + visitor.qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + List upperLocations = + Arrays.asList( + TypeUseLocation.ALL, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.IMPLICIT_UPPER_BOUND, + TypeUseLocation.EXPLICIT_UPPER_BOUND); + if (locations == null || locations.stream().anyMatch(upperLocations::contains)) { + continue; + } + + checker.reportError( + tree, + "type.invalid.annotations.on.location", + type.getExtendsBound().getAnnotations().toString(), + "EXTENDS_WILDCARD"); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 436b818b2d1..207a8069379 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -44,36 +44,7 @@ import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.TreeInfo; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.Vector; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -153,6 +124,38 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.Vector; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; + /* NO-AFU import org.checkerframework.common.wholeprograminference.WholeProgramInference; */ @@ -192,4862 +195,5014 @@ * @see AnnotatedTypeFactory */ public class BaseTypeVisitor> - extends SourceVisitor { + extends SourceVisitor { - /** The {@link BaseTypeChecker} for error reporting. */ - protected final BaseTypeChecker checker; + /** The {@link BaseTypeChecker} for error reporting. */ + protected final BaseTypeChecker checker; - /** The factory to use for obtaining "parsed" version of annotations. */ - protected final Factory atypeFactory; + /** The factory to use for obtaining "parsed" version of annotations. */ + protected final Factory atypeFactory; - /** The qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; + /** The qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; - /** The Annotated Type Hierarchy. */ - protected final TypeHierarchy typeHierarchy; + /** The Annotated Type Hierarchy. */ + protected final TypeHierarchy typeHierarchy; - /** For obtaining line numbers in {@code -Ashowchecks} debugging output. */ - protected final SourcePositions positions; + /** For obtaining line numbers in {@code -Ashowchecks} debugging output. */ + protected final SourcePositions positions; - /** The element for java.util.Vector#copyInto. */ - private final ExecutableElement vectorCopyInto; + /** The element for java.util.Vector#copyInto. */ + private final ExecutableElement vectorCopyInto; - /** The element for java.util.function.Function#apply. */ - private final ExecutableElement functionApply; + /** The element for java.util.function.Function#apply. */ + private final ExecutableElement functionApply; - /** The type of java.util.Vector. */ - private final AnnotatedDeclaredType vectorType; + /** The type of java.util.Vector. */ + private final AnnotatedDeclaredType vectorType; - /** The @java.lang.annotation.Target annotation. */ - protected final AnnotationMirror TARGET = - AnnotationBuilder.fromClass( - elements, - java.lang.annotation.Target.class, - AnnotationBuilder.elementNamesValues("value", new ElementType[0])); - - /** The @{@link Deterministic} annotation. */ - protected final AnnotationMirror DETERMINISTIC = - AnnotationBuilder.fromClass(elements, Deterministic.class); - - /** The @{@link SideEffectFree} annotation. */ - protected final AnnotationMirror SIDE_EFFECT_FREE = - AnnotationBuilder.fromClass(elements, SideEffectFree.class); - - /** The @{@link Pure} annotation. */ - protected final AnnotationMirror PURE = AnnotationBuilder.fromClass(elements, Pure.class); - - /** The @{@link Impure} annotation. */ - protected final AnnotationMirror IMPURE = AnnotationBuilder.fromClass(elements, Impure.class); - - /** The {@code value} element/field of the @java.lang.annotation.Target annotation. */ - protected final ExecutableElement targetValueElement; - - /** The {@code when} element/field of the @Unused annotation. */ - protected final ExecutableElement unusedWhenElement; - - /** True if "-Ashowchecks" was passed on the command line. */ - protected final boolean showchecks; - - /* NO-AFU True if "-Ainfer" was passed on the command line. */ - /* NO-AFU - private final boolean infer; - */ - - /** True if "-AsuggestPureMethods" or "-Ainfer" was passed on the command line. */ - private final boolean suggestPureMethods; - - /** - * True if "-AcheckPurityAnnotations" or "-AsuggestPureMethods" or "-Ainfer" was passed on the - * command line. - */ - private final boolean checkPurityAnnotations; - - /** True if "-AajavaChecks" was passed on the command line. */ - private final boolean ajavaChecks; - - /** True if "-AassumeSideEffectFree" or "-AassumePure" was passed on the command line. */ - private final boolean assumeSideEffectFree; - - /** True if "-AassumeDeterministic" or "-AassumePure" was passed on the command line. */ - private final boolean assumeDeterministic; - - /** True if "-AassumePureGetters" was passed on the command line. */ - public final boolean assumePureGetters; - - /** True if "-AcheckCastElementType" was passed on the command line. */ - private final boolean checkCastElementType; - - /** True if "-AwarnRedundantAnnotations" was passed on the command line. */ - private final boolean warnRedundantAnnotations; - - /** True if "-AignoreTargetLocations" was passed on the command line. */ - protected final boolean ignoreTargetLocations; - - /** True if "-AcheckEnclosingExpr" was passed on the command line. */ - private final boolean checkEnclosingExpr; - - /** The tree of the enclosing method that is currently being visited, if any. */ - protected @Nullable MethodTree methodTree = null; - - /** - * Map from String (canonical name of the qualifier) to its type-use locations declared in {@link - * org.checkerframework.framework.qual.TargetLocations}. - */ - protected final Map<@CanonicalName String, List> qualAllowedLocations; - - /** - * Constructor for creating a BaseTypeVisitor. - * - * @param checker the type-checker associated with this visitor (for callbacks to {@link - * TypeHierarchy#isSubtype}) - */ - public BaseTypeVisitor(BaseTypeChecker checker) { - this(checker, null); - } - - /** - * Constructor for creating a BaseTypeVisitor. - * - * @param checker the type-checker associated with this visitor - * @param typeFactory the type factory, or null. If null, this calls {@link #createTypeFactory}. - */ - protected BaseTypeVisitor(BaseTypeChecker checker, @Nullable Factory typeFactory) { - super(checker); - - this.checker = checker; - this.atypeFactory = typeFactory == null ? createTypeFactory() : typeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - this.typeHierarchy = atypeFactory.getTypeHierarchy(); - this.positions = trees.getSourcePositions(); - this.typeValidator = createTypeValidator(); - ProcessingEnvironment env = checker.getProcessingEnvironment(); - this.vectorCopyInto = TreeUtils.getMethod("java.util.Vector", "copyInto", 1, env); - this.functionApply = TreeUtils.getMethod("java.util.function.Function", "apply", 1, env); - this.vectorType = - atypeFactory.fromElement(elements.getTypeElement(Vector.class.getCanonicalName())); - targetValueElement = TreeUtils.getMethod(Target.class, "value", 0, env); - unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, env); - showchecks = checker.hasOption("showchecks"); - /* NO-AFU - infer = checker.hasOption("infer"); - */ - suggestPureMethods = checker.hasOption("suggestPureMethods"); // NO-AFU || infer; - checkPurityAnnotations = checker.hasOption("checkPurityAnnotations") || suggestPureMethods; - warnRedundantAnnotations = checker.hasOption("warnRedundantAnnotations"); - ignoreTargetLocations = checker.hasOption("ignoreTargetLocations"); - qualAllowedLocations = createQualAllowedLocations(); - - checkEnclosingExpr = checker.hasOption("checkEnclosingExpr"); - ajavaChecks = checker.hasOption("ajavaChecks"); - assumeSideEffectFree = - checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); - assumeDeterministic = - checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); - assumePureGetters = checker.hasOption("assumePureGetters"); - checkCastElementType = checker.hasOption("checkCastElementType"); - } - - /** An array containing just {@code BaseTypeChecker.class}. */ - private static final Class[] baseTypeCheckerClassArray = - new Class[] {BaseTypeChecker.class}; - - /** - * Constructs an instance of the appropriate type factory for the implemented type system. - * - *

          The default implementation uses the checker naming convention to create the appropriate type - * factory. If no factory is found, it returns {@link BaseAnnotatedTypeFactory}. It reflectively - * invokes the constructor that accepts this checker and compilation unit tree (in that order) as - * arguments. - * - *

          Subclasses have to override this method to create the appropriate visitor if they do not - * follow the checker naming convention. - * - * @return the appropriate type factory - */ - @SuppressWarnings({ - "unchecked", // unchecked cast to type variable - }) - protected Factory createTypeFactory() { - // Try to reflectively load the type factory. - Class checkerClass = checker.getClass(); - Object[] checkerArray = new Object[] {checker}; - while (checkerClass != BaseTypeChecker.class) { - AnnotatedTypeFactory result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "AnnotatedTypeFactory"), - baseTypeCheckerClassArray, - checkerArray); - if (result != null) { - return (Factory) result; - } - checkerClass = checkerClass.getSuperclass(); - } - try { - return (Factory) new BaseAnnotatedTypeFactory(checker); - } catch (Throwable t) { - throw new BugInCF( - "Unexpected " - + t.getClass().getSimpleName() - + " when invoking BaseAnnotatedTypeFactory for checker " - + checker.getClass().getSimpleName(), - t); - } - } - - public final Factory getTypeFactory() { - return atypeFactory; - } - - /** - * A public variant of {@link #createTypeFactory}. Only use this if you know what you are doing. - * - * @return the appropriate type factory - */ - public Factory createTypeFactoryPublic() { - return createTypeFactory(); - } - - // ********************************************************************** - // Responsible for updating the factory for the location (for performance) - // ********************************************************************** - - @Override - public void setRoot(CompilationUnitTree root) { - atypeFactory.setRoot(root); - super.setRoot(root); - testJointJavacJavaParserVisitor(); - testAnnotationInsertion(); - } - - @Override - public Void scan(@Nullable Tree tree, Void p) { - if (tree == null) { - return null; - } - if (getCurrentPath() != null) { - this.atypeFactory.setVisitorTreePath(new TreePath(getCurrentPath(), tree)); - } - // TODO: use JCP to add version-specific behavior - if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { - visitSwitchExpression17(tree); - return null; - } - return super.scan(tree, p); - } - - /** - * Test {@link org.checkerframework.framework.ajava.JointJavacJavaParserVisitor} if the checker - * has the "ajavaChecks" option. - * - *

          Parse the current source file with JavaParser and check that the AST can be matched with the - * Tree produced by javac. Crash if not. - * - *

          Subclasses may override this method to disable the test if even the option is provided. - */ - protected void testJointJavacJavaParserVisitor() { - if (root == null - || !ajavaChecks - // TODO: Make annotation insertion work for Java 21. - || root.getSourceFile().toUri().toString().contains("java21")) { - return; - } + /** The @java.lang.annotation.Target annotation. */ + protected final AnnotationMirror TARGET = + AnnotationBuilder.fromClass( + elements, + java.lang.annotation.Target.class, + AnnotationBuilder.elementNamesValues("value", new ElementType[0])); - Map treePairs = new HashMap<>(); - try (InputStream reader = root.getSourceFile().openInputStream()) { - CompilationUnit javaParserRoot = JavaParserUtil.parseCompilationUnit(reader); - JavaParserUtil.concatenateAddedStringLiterals(javaParserRoot); - new JointVisitorWithDefaultAction() { - @Override - public void defaultJointAction( - Tree javacTree, com.github.javaparser.ast.Node javaParserNode) { - treePairs.put(javacTree, javaParserNode); - } - }.visitCompilationUnit(root, javaParserRoot); - ExpectedTreesVisitor expectedTreesVisitor = new ExpectedTreesVisitor(); - expectedTreesVisitor.visitCompilationUnit(root, null); - for (Tree expected : expectedTreesVisitor.getTrees()) { - if (!treePairs.containsKey(expected)) { - throw new BugInCF( - "Javac tree not matched to JavaParser node: %s [%s @ %d], in file: %s", - expected, - expected.getClass(), - positions.getStartPosition(root, expected), - root.getSourceFile().getName()); - } - } - } catch (IOException e) { - throw new BugInCF("Error reading Java source file", e); - } - } - - /** - * Tests {@link org.checkerframework.framework.ajava.InsertAjavaAnnotations} if the checker has - * the "ajavaChecks" option. - * - *

            - *
          1. Parses the current file with JavaParser. - *
          2. Removes all annotations. - *
          3. Reinserts the annotations. - *
          4. Throws an exception if the ASTs are not the same. - *
          - * - *

          Subclasses may override this method to disable the test even if the option is provided. - */ - protected void testAnnotationInsertion() { - if (root == null - || !ajavaChecks - // TODO: Make annotation insertion work for Java 21. - || root.getSourceFile().toUri().toString().contains("java21")) { - return; - } + /** The @{@link Deterministic} annotation. */ + protected final AnnotationMirror DETERMINISTIC = + AnnotationBuilder.fromClass(elements, Deterministic.class); - CompilationUnit originalAst; - try (InputStream originalInputStream = root.getSourceFile().openInputStream()) { - originalAst = JavaParserUtil.parseCompilationUnit(originalInputStream); - } catch (IOException e) { - throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); - } + /** The @{@link SideEffectFree} annotation. */ + protected final AnnotationMirror SIDE_EFFECT_FREE = + AnnotationBuilder.fromClass(elements, SideEffectFree.class); - CompilationUnit astWithoutAnnotations = originalAst.clone(); - JavaParserUtil.clearAnnotations(astWithoutAnnotations); - String withoutAnnotations = new DefaultPrettyPrinter().print(astWithoutAnnotations); - - String withAnnotations; - try (InputStream annotationInputStream = root.getSourceFile().openInputStream()) { - // This check only runs on files from the Checker Framework test suite, which should all - // use UNIX line separators. Using System.lineSeparator instead of "\n" could cause the - // test to fail on Mac or Windows. - withAnnotations = - new InsertAjavaAnnotations(elements) - .insertAnnotations(annotationInputStream, withoutAnnotations, "\n"); - } catch (IOException e) { - throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); - } + /** The @{@link Pure} annotation. */ + protected final AnnotationMirror PURE = AnnotationBuilder.fromClass(elements, Pure.class); - CompilationUnit modifiedAst = null; - try { - modifiedAst = JavaParserUtil.parseCompilationUnit(withAnnotations); - } catch (ParseProblemException e) { - throw new BugInCF("Failed to parse code after annotation insertion:\n" + withAnnotations, e); - } + /** The @{@link Impure} annotation. */ + protected final AnnotationMirror IMPURE = AnnotationBuilder.fromClass(elements, Impure.class); - AnnotationEqualityVisitor visitor = new AnnotationEqualityVisitor(); - originalAst.accept(visitor, modifiedAst); - if (!visitor.getAnnotationsMatch()) { - throw new BugInCF( - String.join( - System.lineSeparator(), - "Sanity check of erasing then reinserting annotations produced a" + " different AST.", - "File: " + root.getSourceFile(), - "Node class: " + visitor.getMismatchedNode1().getClass().getSimpleName(), - "Original node: " + oneLine(visitor.getMismatchedNode1()), - "Node with annotations re-inserted: " + oneLine(visitor.getMismatchedNode2()), - "Original annotations: " + visitor.getMismatchedNode1().getAnnotations(), - "Re-inserted annotations: " + visitor.getMismatchedNode2().getAnnotations(), - "Original AST:", - originalAst.toString(), - "Ast with annotations re-inserted: " + modifiedAst)); - } - } - - /** - * Replace newlines in the printed representation by spaces. - * - * @param arg an object to format - * @return the object's toString representation, on one line - */ - private String oneLine(Object arg) { - return arg.toString().replace(System.lineSeparator(), " "); - } - - /** - * Type-check classTree and skips classes specified by the skipDef option. Subclasses should - * override {@link #processClassTree(ClassTree)} instead of this method. - * - * @param classTree class to check - * @param p null - * @return null - */ - @Override - public final Void visitClass(ClassTree classTree, Void p) { - if (checker.shouldSkipDefs(classTree) || checker.shouldSkipFiles(classTree)) { - // Not "return super.visitClass(classTree, p);" because that would recursively call - // visitors on subtrees; we want to skip the class entirely. - return null; - } - atypeFactory.preProcessClassTree(classTree); + /** The {@code value} element/field of the @java.lang.annotation.Target annotation. */ + protected final ExecutableElement targetValueElement; - TreePath preTreePath = atypeFactory.getVisitorTreePath(); - MethodTree preMT = methodTree; + /** The {@code when} element/field of the @Unused annotation. */ + protected final ExecutableElement unusedWhenElement; - // Don't use atypeFactory.getPath, because that depends on the visitor path. - atypeFactory.setVisitorTreePath(TreePath.getPath(root, classTree)); - methodTree = null; + /** True if "-Ashowchecks" was passed on the command line. */ + protected final boolean showchecks; - try { - processClassTree(classTree); - atypeFactory.postProcessClassTree(classTree); - } finally { - atypeFactory.setVisitorTreePath(preTreePath); - methodTree = preMT; - } - return null; - } - - /** - * Type-check classTree. Subclasses should override this method instead of {@link - * #visitClass(ClassTree, Void)}. - * - * @param classTree class to check - */ - public void processClassTree(ClassTree classTree) { - checkFieldInvariantDeclarations(classTree); - if (!TreeUtils.hasExplicitConstructor(classTree)) { - checkDefaultConstructor(classTree); - } + /* NO-AFU True if "-Ainfer" was passed on the command line. */ + /* NO-AFU + private final boolean infer; + */ - AnnotatedDeclaredType classType = atypeFactory.getAnnotatedType(classTree); - atypeFactory.getDependentTypesHelper().checkClassForErrorExpressions(classTree, classType); - validateType(classTree, classType); + /** True if "-AsuggestPureMethods" or "-Ainfer" was passed on the command line. */ + private final boolean suggestPureMethods; - Tree ext = classTree.getExtendsClause(); - if (ext != null) { - AnnotatedTypeMirror superClass = atypeFactory.getTypeOfExtendsImplements(ext); - validateType(ext, superClass); - } + /** + * True if "-AcheckPurityAnnotations" or "-AsuggestPureMethods" or "-Ainfer" was passed on the + * command line. + */ + private final boolean checkPurityAnnotations; - List impls = classTree.getImplementsClause(); - if (impls != null) { - for (Tree im : impls) { - AnnotatedTypeMirror superInterface = atypeFactory.getTypeOfExtendsImplements(im); - validateType(im, superInterface); - } - } + /** True if "-AajavaChecks" was passed on the command line. */ + private final boolean ajavaChecks; - warnInvalidPolymorphicQualifier(classTree); + /** True if "-AassumeSideEffectFree" or "-AassumePure" was passed on the command line. */ + private final boolean assumeSideEffectFree; - checkExtendsAndImplements(classTree); + /** True if "-AassumeDeterministic" or "-AassumePure" was passed on the command line. */ + private final boolean assumeDeterministic; - checkQualifierParameter(classTree); + /** True if "-AassumePureGetters" was passed on the command line. */ + public final boolean assumePureGetters; - super.visitClass(classTree, null); - } + /** True if "-AcheckCastElementType" was passed on the command line. */ + private final boolean checkCastElementType; - /** - * A TreeScanner that issues an "invalid.polymorphic.qualifier" error for each {@link - * AnnotationTree} that is a polymorphic qualifier. The second parameter is added to the error - * message and should explain the location. - */ - private final TreeScanner polyTreeScanner = - new TreeScanner() { - @Override - public Void visitAnnotation(AnnotationTree annoTree, String location) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(annoTree); - if (atypeFactory.isSupportedQualifier(anno) - && qualHierarchy.isPolymorphicQualifier(anno)) { - checker.reportError(annoTree, "invalid.polymorphic.qualifier", anno, location); - } - return super.visitAnnotation(annoTree, location); - } - }; - - /** - * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the - * class declaration. - * - * @param classTree the class to check - */ - protected void warnInvalidPolymorphicQualifier(ClassTree classTree) { - if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { - // Anonymous class can have polymorphic annotations, so don't check them. - return; - } - classTree.getModifiers().accept(polyTreeScanner, "in a class declaration"); - if (classTree.getExtendsClause() != null) { - classTree.getExtendsClause().accept(polyTreeScanner, "in a class declaration"); - } - for (Tree tree : classTree.getImplementsClause()) { - tree.accept(polyTreeScanner, "in a class declaration"); - } - for (Tree tree : classTree.getTypeParameters()) { - tree.accept(polyTreeScanner, "in a class declaration"); - } - } - - /** - * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the - * type parameters declaration. - * - * @param typeParameterTrees the type parameters to check - */ - protected void warnInvalidPolymorphicQualifier( - List typeParameterTrees) { - for (Tree tree : typeParameterTrees) { - tree.accept(polyTreeScanner, "in a type parameter"); - } - } - - /** - * Issues an error if {@code classTree} has polymorphic fields but is not annotated with - * {@code @HasQualifierParameter}. Always issue a warning if the type of a static field is - * annotated with a polymorphic qualifier. - * - *

          Issues an error if {@code classTree} extends or implements a class/interface that has a - * qualifier parameter, but this class does not. - * - * @param classTree the ClassTree to check for polymorphic fields - */ - protected void checkQualifierParameter(ClassTree classTree) { - // Set of polymorphic qualifiers for hierarchies that do not have a qualifier parameter and - // therefore cannot appear on a field. - AnnotationMirrorSet illegalOnFieldsPolyQual = new AnnotationMirrorSet(); - // Set of polymorphic annotations for all hierarchies - AnnotationMirrorSet polys = new AnnotationMirrorSet(); - TypeElement classElement = TreeUtils.elementFromDeclaration(classTree); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); - if (poly != null) { - polys.add(poly); - } - // else { - // If there is no polymorphic qualifier in the hierarchy, it could still have a - // @HasQualifierParameter that must be checked. - // } - - if (!atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) - && atypeFactory.getDeclAnnotation(classElement, HasQualifierParameter.class) != null) { - // The argument to a @HasQualifierParameter annotation must be the top type in the - // type system. - checker.reportError(classTree, "invalid.qual.param", top); - break; - } + /** True if "-AwarnRedundantAnnotations" was passed on the command line. */ + private final boolean warnRedundantAnnotations; - if (atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) - && atypeFactory.hasExplicitNoQualifierParameterInHierarchy(classElement, top)) { - checker.reportError(classTree, "conflicting.qual.param", top); - } + /** True if "-AignoreTargetLocations" was passed on the command line. */ + protected final boolean ignoreTargetLocations; - if (atypeFactory.hasQualifierParameterInHierarchy(classElement, top)) { - continue; - } + /** True if "-AcheckEnclosingExpr" was passed on the command line. */ + private final boolean checkEnclosingExpr; - if (poly != null) { - illegalOnFieldsPolyQual.add(poly); - } - Element extendsEle = TypesUtils.getTypeElement(classElement.getSuperclass()); - if (extendsEle != null && atypeFactory.hasQualifierParameterInHierarchy(extendsEle, top)) { - checker.reportError(classTree, "missing.has.qual.param", top); - } else { - for (TypeMirror interfaceType : classElement.getInterfaces()) { - Element interfaceEle = TypesUtils.getTypeElement(interfaceType); - if (atypeFactory.hasQualifierParameterInHierarchy(interfaceEle, top)) { - checker.reportError(classTree, "missing.has.qual.param", top); - break; // only issue error once - } - } - } - } + /** The tree of the enclosing method that is currently being visited, if any. */ + protected @Nullable MethodTree methodTree = null; - for (Tree mem : classTree.getMembers()) { - if (mem.getKind() == Tree.Kind.VARIABLE) { - AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(mem); - List hasInvalidPoly; - if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration((VariableTree) mem))) { - // A polymorphic qualifier is not allowed on a static field even if the class - // has a qualifier parameter. - hasInvalidPoly = hasInvalidPolyScanner.visit(fieldType, polys); - } else { - hasInvalidPoly = hasInvalidPolyScanner.visit(fieldType, illegalOnFieldsPolyQual); - } - for (DiagMessage dm : hasInvalidPoly) { - checker.report(mem, dm); - } - } - } - } - - /** - * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use - * of one of the polymorphic qualifiers. - */ - private final HasInvalidPolyScanner hasInvalidPolyScanner = new HasInvalidPolyScanner(); - - /** - * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use - * of one of the polymorphic qualifiers. - */ - static class HasInvalidPolyScanner - extends SimpleAnnotatedTypeScanner, AnnotationMirrorSet> { - - /** Create HasInvalidPolyScanner. */ - private HasInvalidPolyScanner() { - super(DiagMessage::mergeLists, Collections.emptyList()); + /** + * Map from String (canonical name of the qualifier) to its type-use locations declared in + * {@link org.checkerframework.framework.qual.TargetLocations}. + */ + protected final Map<@CanonicalName String, List> qualAllowedLocations; + + /** + * Constructor for creating a BaseTypeVisitor. + * + * @param checker the type-checker associated with this visitor (for callbacks to {@link + * TypeHierarchy#isSubtype}) + */ + public BaseTypeVisitor(BaseTypeChecker checker) { + this(checker, null); } - @Override - protected List defaultAction(AnnotatedTypeMirror type, AnnotationMirrorSet polys) { - if (type == null) { - return Collections.emptyList(); - } + /** + * Constructor for creating a BaseTypeVisitor. + * + * @param checker the type-checker associated with this visitor + * @param typeFactory the type factory, or null. If null, this calls {@link #createTypeFactory}. + */ + protected BaseTypeVisitor(BaseTypeChecker checker, @Nullable Factory typeFactory) { + super(checker); + + this.checker = checker; + this.atypeFactory = typeFactory == null ? createTypeFactory() : typeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + this.typeHierarchy = atypeFactory.getTypeHierarchy(); + this.positions = trees.getSourcePositions(); + this.typeValidator = createTypeValidator(); + ProcessingEnvironment env = checker.getProcessingEnvironment(); + this.vectorCopyInto = TreeUtils.getMethod("java.util.Vector", "copyInto", 1, env); + this.functionApply = TreeUtils.getMethod("java.util.function.Function", "apply", 1, env); + this.vectorType = + atypeFactory.fromElement(elements.getTypeElement(Vector.class.getCanonicalName())); + targetValueElement = TreeUtils.getMethod(Target.class, "value", 0, env); + unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, env); + showchecks = checker.hasOption("showchecks"); + /* NO-AFU + infer = checker.hasOption("infer"); + */ + suggestPureMethods = checker.hasOption("suggestPureMethods"); // NO-AFU || infer; + checkPurityAnnotations = checker.hasOption("checkPurityAnnotations") || suggestPureMethods; + warnRedundantAnnotations = checker.hasOption("warnRedundantAnnotations"); + ignoreTargetLocations = checker.hasOption("ignoreTargetLocations"); + qualAllowedLocations = createQualAllowedLocations(); + + checkEnclosingExpr = checker.hasOption("checkEnclosingExpr"); + ajavaChecks = checker.hasOption("ajavaChecks"); + assumeSideEffectFree = + checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); + assumeDeterministic = + checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); + assumePureGetters = checker.hasOption("assumePureGetters"); + checkCastElementType = checker.hasOption("checkCastElementType"); + } + + /** An array containing just {@code BaseTypeChecker.class}. */ + private static final Class[] baseTypeCheckerClassArray = + new Class[] {BaseTypeChecker.class}; - for (AnnotationMirror poly : polys) { - if (type.hasAnnotation(poly)) { - return Collections.singletonList( - DiagMessage.error("invalid.polymorphic.qualifier.use", poly)); + /** + * Constructs an instance of the appropriate type factory for the implemented type system. + * + *

          The default implementation uses the checker naming convention to create the appropriate + * type factory. If no factory is found, it returns {@link BaseAnnotatedTypeFactory}. It + * reflectively invokes the constructor that accepts this checker and compilation unit tree (in + * that order) as arguments. + * + *

          Subclasses have to override this method to create the appropriate visitor if they do not + * follow the checker naming convention. + * + * @return the appropriate type factory + */ + @SuppressWarnings({ + "unchecked", // unchecked cast to type variable + }) + protected Factory createTypeFactory() { + // Try to reflectively load the type factory. + Class checkerClass = checker.getClass(); + Object[] checkerArray = new Object[] {checker}; + while (checkerClass != BaseTypeChecker.class) { + AnnotatedTypeFactory result = + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName( + checkerClass, "AnnotatedTypeFactory"), + baseTypeCheckerClassArray, + checkerArray); + if (result != null) { + return (Factory) result; + } + checkerClass = checkerClass.getSuperclass(); + } + try { + return (Factory) new BaseAnnotatedTypeFactory(checker); + } catch (Throwable t) { + throw new BugInCF( + "Unexpected " + + t.getClass().getSimpleName() + + " when invoking BaseAnnotatedTypeFactory for checker " + + checker.getClass().getSimpleName(), + t); } - } - return Collections.emptyList(); - } - } - - /** - * In {@code @A class X extends @B Y implements @C Z {}}, enforce that {@code @A} must be a - * subtype of {@code @B} and {@code @C}. - * - *

          Also validate the types of the extends and implements clauses. - * - * @param classTree class tree to check - */ - protected void checkExtendsAndImplements(ClassTree classTree) { - if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { - // Don't check extends clause on anonymous classes. - return; - } - if (classTree.getExtendsClause() == null && classTree.getImplementsClause().isEmpty()) { - // Nothing to do - return; } - TypeMirror classType = TreeUtils.typeOf(classTree); - AnnotationMirrorSet classBounds = atypeFactory.getTypeDeclarationBounds(classType); - // If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A. - // classTree.getExtendsClause() is null when there is no explicitly-written extends clause, - // as in "class X {}". This is equivalent to writing "class X extends @Top Object {}", so - // there is no need to do any subtype checking. - if (classTree.getExtendsClause() != null) { - Tree boundClause = classTree.getExtendsClause(); - checkExtendsOrImplements(boundClause, classBounds, classType, true); - } - // Do the same check as above for implements clauses. - for (Tree boundClause : classTree.getImplementsClause()) { - checkExtendsOrImplements(boundClause, classBounds, classType, false); - } - } - - /** - * Helper for {@link #checkExtendsAndImplements} that checks one extends or implements clause. - * - * @param boundClause an extends or implements clause - * @param classBounds the type declarations bounds to check for consistency with {@code - * boundClause} - * @param classType the type being declared - * @param isExtends true for an extends clause, false for an implements clause - */ - protected void checkExtendsOrImplements( - Tree boundClause, AnnotationMirrorSet classBounds, TypeMirror classType, boolean isExtends) { - AnnotatedTypeMirror boundType = atypeFactory.getTypeOfExtendsImplements(boundClause); - TypeMirror boundTM = boundType.getUnderlyingType(); - for (AnnotationMirror classAnno : classBounds) { - AnnotationMirror boundAnno = boundType.getAnnotationInHierarchy(classAnno); - if (!qualHierarchy.isSubtypeShallow(classAnno, classType, boundAnno, boundTM)) { - checker.reportError( - boundClause, - (isExtends - ? "declaration.inconsistent.with.extends.clause" - : "declaration.inconsistent.with.implements.clause"), - classAnno, - boundAnno); - } + public final Factory getTypeFactory() { + return atypeFactory; } - } - - /** - * Check that the field invariant declaration annotations meet the following requirements: - * - *

            - * - *
          1. If the superclass of {@code classTree} has a field invariant, then the field - * invariant for {@code classTree} must include all the fields in the superclass invariant - * and those fields' annotations must be a subtype (or equal) to the annotations for those - * fields in the superclass. - *
          2. The fields in the invariant must be a.) final and b.) declared in a superclass - * of {@code classTree}. - *
          3. The qualifier for each field must be a subtype of the annotation on the - * declaration of that field. - *
          4. The field invariant has an equal number of fields and qualifiers, or it has one - * qualifier and at least one field. - *
          - * - * @param classTree class that might have a field invariant - * @checker_framework.manual #field-invariants Field invariants - */ - protected void checkFieldInvariantDeclarations(ClassTree classTree) { - TypeElement elt = TreeUtils.elementFromDeclaration(classTree); - FieldInvariants invariants = atypeFactory.getFieldInvariants(elt); - if (invariants == null) { - // No invariants to check - return; + + /** + * A public variant of {@link #createTypeFactory}. Only use this if you know what you are doing. + * + * @return the appropriate type factory + */ + public Factory createTypeFactoryPublic() { + return createTypeFactory(); } - // Where to issue an error, if any. - Tree errorTree = - atypeFactory.getFieldInvariantAnnotationTree(classTree.getModifiers().getAnnotations()); - if (errorTree == null) { - // If the annotation was inherited, then there is no annotation tree, so issue the - // error on the class. - errorTree = classTree; + // ********************************************************************** + // Responsible for updating the factory for the location (for performance) + // ********************************************************************** + + @Override + public void setRoot(CompilationUnitTree root) { + atypeFactory.setRoot(root); + super.setRoot(root); + testJointJavacJavaParserVisitor(); + testAnnotationInsertion(); } - // Checks #4 (see method Javadoc) - if (!invariants.isWellFormed()) { - checker.reportError(errorTree, "field.invariant.not.wellformed"); - return; + @Override + public Void scan(@Nullable Tree tree, Void p) { + if (tree == null) { + return null; + } + if (getCurrentPath() != null) { + this.atypeFactory.setVisitorTreePath(new TreePath(getCurrentPath(), tree)); + } + // TODO: use JCP to add version-specific behavior + if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { + visitSwitchExpression17(tree); + return null; + } + return super.scan(tree, p); } - TypeMirror superClass = elt.getSuperclass(); - List fieldsNotFound = new ArrayList<>(invariants.getFields()); - Set fieldElts = - ElementUtils.findFieldsInTypeOrSuperType(superClass, fieldsNotFound); + /** + * Test {@link org.checkerframework.framework.ajava.JointJavacJavaParserVisitor} if the checker + * has the "ajavaChecks" option. + * + *

          Parse the current source file with JavaParser and check that the AST can be matched with + * the Tree produced by javac. Crash if not. + * + *

          Subclasses may override this method to disable the test if even the option is provided. + */ + protected void testJointJavacJavaParserVisitor() { + if (root == null + || !ajavaChecks + // TODO: Make annotation insertion work for Java 21. + || root.getSourceFile().toUri().toString().contains("java21")) { + return; + } - // Checks that fields are declared in super class. (#2b) - if (!fieldsNotFound.isEmpty()) { - String notFoundString = String.join(", ", fieldsNotFound); - checker.reportError(errorTree, "field.invariant.not.found", notFoundString); + Map treePairs = new HashMap<>(); + try (InputStream reader = root.getSourceFile().openInputStream()) { + CompilationUnit javaParserRoot = JavaParserUtil.parseCompilationUnit(reader); + JavaParserUtil.concatenateAddedStringLiterals(javaParserRoot); + new JointVisitorWithDefaultAction() { + @Override + public void defaultJointAction( + Tree javacTree, com.github.javaparser.ast.Node javaParserNode) { + treePairs.put(javacTree, javaParserNode); + } + }.visitCompilationUnit(root, javaParserRoot); + ExpectedTreesVisitor expectedTreesVisitor = new ExpectedTreesVisitor(); + expectedTreesVisitor.visitCompilationUnit(root, null); + for (Tree expected : expectedTreesVisitor.getTrees()) { + if (!treePairs.containsKey(expected)) { + throw new BugInCF( + "Javac tree not matched to JavaParser node: %s [%s @ %d], in file: %s", + expected, + expected.getClass(), + positions.getStartPosition(root, expected), + root.getSourceFile().getName()); + } + } + } catch (IOException e) { + throw new BugInCF("Error reading Java source file", e); + } } - FieldInvariants superInvar = - atypeFactory.getFieldInvariants(TypesUtils.getTypeElement(superClass)); - if (superInvar != null) { - // Checks #3 (see method Javadoc) - DiagMessage superError = invariants.isStrongerThan(superInvar); - if (superError != null) { - checker.report(errorTree, superError); - } - } + /** + * Tests {@link org.checkerframework.framework.ajava.InsertAjavaAnnotations} if the checker has + * the "ajavaChecks" option. + * + *

            + *
          1. Parses the current file with JavaParser. + *
          2. Removes all annotations. + *
          3. Reinserts the annotations. + *
          4. Throws an exception if the ASTs are not the same. + *
          + * + *

          Subclasses may override this method to disable the test even if the option is provided. + */ + protected void testAnnotationInsertion() { + if (root == null + || !ajavaChecks + // TODO: Make annotation insertion work for Java 21. + || root.getSourceFile().toUri().toString().contains("java21")) { + return; + } - List notFinal = new ArrayList<>(fieldElts.size()); - for (VariableElement field : fieldElts) { - String fieldName = field.getSimpleName().toString(); - if (!ElementUtils.isFinal(field)) { - notFinal.add(fieldName); - } - AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(field); + CompilationUnit originalAst; + try (InputStream originalInputStream = root.getSourceFile().openInputStream()) { + originalAst = JavaParserUtil.parseCompilationUnit(originalInputStream); + } catch (IOException e) { + throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); + } - List annos = invariants.getQualifiersFor(field.getSimpleName()); - for (AnnotationMirror invariantAnno : annos) { - AnnotationMirror declaredAnno = fieldType.getEffectiveAnnotationInHierarchy(invariantAnno); - if (declaredAnno == null) { - // invariant anno isn't in this hierarchy - continue; + CompilationUnit astWithoutAnnotations = originalAst.clone(); + JavaParserUtil.clearAnnotations(astWithoutAnnotations); + String withoutAnnotations = new DefaultPrettyPrinter().print(astWithoutAnnotations); + + String withAnnotations; + try (InputStream annotationInputStream = root.getSourceFile().openInputStream()) { + // This check only runs on files from the Checker Framework test suite, which should all + // use UNIX line separators. Using System.lineSeparator instead of "\n" could cause the + // test to fail on Mac or Windows. + withAnnotations = + new InsertAjavaAnnotations(elements) + .insertAnnotations(annotationInputStream, withoutAnnotations, "\n"); + } catch (IOException e) { + throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e); } - if (!typeHierarchy.isSubtypeShallowEffective(invariantAnno, fieldType)) { - // Checks #3 - checker.reportError( - errorTree, "field.invariant.not.subtype", fieldName, invariantAnno, declaredAnno); + CompilationUnit modifiedAst = null; + try { + modifiedAst = JavaParserUtil.parseCompilationUnit(withAnnotations); + } catch (ParseProblemException e) { + throw new BugInCF( + "Failed to parse code after annotation insertion:\n" + withAnnotations, e); } - } - } - // Checks #2a - if (!notFinal.isEmpty()) { - String notFinalString = String.join(", ", notFinal); - checker.reportError(errorTree, "field.invariant.not.final", notFinalString); + AnnotationEqualityVisitor visitor = new AnnotationEqualityVisitor(); + originalAst.accept(visitor, modifiedAst); + if (!visitor.getAnnotationsMatch()) { + throw new BugInCF( + String.join( + System.lineSeparator(), + "Sanity check of erasing then reinserting annotations produced a" + + " different AST.", + "File: " + root.getSourceFile(), + "Node class: " + + visitor.getMismatchedNode1().getClass().getSimpleName(), + "Original node: " + oneLine(visitor.getMismatchedNode1()), + "Node with annotations re-inserted: " + + oneLine(visitor.getMismatchedNode2()), + "Original annotations: " + + visitor.getMismatchedNode1().getAnnotations(), + "Re-inserted annotations: " + + visitor.getMismatchedNode2().getAnnotations(), + "Original AST:", + originalAst.toString(), + "Ast with annotations re-inserted: " + modifiedAst)); + } } - } - - /** - * Check the default constructor. - * - * @param tree a class declaration - */ - protected void checkDefaultConstructor(ClassTree tree) {} - - /** - * Checks that the method obeys override and subtype rules to all overridden methods. (Uses the - * pseudo-assignment logic to do so.) - * - *

          The override rule specifies that a method, m1, may override a method m2 only if: - * - *

            - *
          • m1 return type is a subtype of m2 - *
          • m1 receiver type is a supertype of m2 - *
          • m1 parameters are supertypes of corresponding m2 parameters - *
          - * - * Also, it issues a "missing.this" error for static method annotated receivers. - */ - @Override - public Void visitMethod(MethodTree tree, Void p) { - // We copy the result from getAnnotatedType to ensure that circular types (e.g. K extends - // Comparable) are represented by circular AnnotatedTypeMirrors, which avoids problems - // with later checks. - // TODO: Find a cleaner way to ensure circular AnnotatedTypeMirrors. - AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); - MethodTree preMT = methodTree; - methodTree = tree; - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); - - warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); - - if (tree.getReturnType() != null) { - visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getReturnType()); - warnRedundantAnnotations(tree.getReturnType(), methodType.getReturnType()); - } else if (TreeUtils.isConstructor(tree)) { - maybeReportAnnoOnIrrelevant( - tree.getModifiers(), - methodType.getReturnType().getUnderlyingType(), - tree.getModifiers().getAnnotations()); + + /** + * Replace newlines in the printed representation by spaces. + * + * @param arg an object to format + * @return the object's toString representation, on one line + */ + private String oneLine(Object arg) { + return arg.toString().replace(System.lineSeparator(), " "); } - try { - if (TreeUtils.isAnonymousConstructor(tree)) { - // We shouldn't dig deeper - return null; - } + /** + * Type-check classTree and skips classes specified by the skipDef option. Subclasses should + * override {@link #processClassTree(ClassTree)} instead of this method. + * + * @param classTree class to check + * @param p null + * @return null + */ + @Override + public final Void visitClass(ClassTree classTree, Void p) { + if (checker.shouldSkipDefs(classTree) || checker.shouldSkipFiles(classTree)) { + // Not "return super.visitClass(classTree, p);" because that would recursively call + // visitors on subtrees; we want to skip the class entirely. + return null; + } + atypeFactory.preProcessClassTree(classTree); - if (TreeUtils.isConstructor(tree)) { - checkConstructorResult(methodType, methodElement); - } + TreePath preTreePath = atypeFactory.getVisitorTreePath(); + MethodTree preMT = methodTree; - checkPurityAnnotations(tree); + // Don't use atypeFactory.getPath, because that depends on the visitor path. + atypeFactory.setVisitorTreePath(TreePath.getPath(root, classTree)); + methodTree = null; + + try { + processClassTree(classTree); + atypeFactory.postProcessClassTree(classTree); + } finally { + atypeFactory.setVisitorTreePath(preTreePath); + methodTree = preMT; + } + return null; + } - // Passing the whole method/constructor validates the return type - validateTypeOf(tree); + /** + * Type-check classTree. Subclasses should override this method instead of {@link + * #visitClass(ClassTree, Void)}. + * + * @param classTree class to check + */ + public void processClassTree(ClassTree classTree) { + checkFieldInvariantDeclarations(classTree); + if (!TreeUtils.hasExplicitConstructor(classTree)) { + checkDefaultConstructor(classTree); + } - // Validate types in throws clauses - for (ExpressionTree thr : tree.getThrows()) { - validateTypeOf(thr); - } + AnnotatedDeclaredType classType = atypeFactory.getAnnotatedType(classTree); + atypeFactory.getDependentTypesHelper().checkClassForErrorExpressions(classTree, classType); + validateType(classTree, classType); - atypeFactory.getDependentTypesHelper().checkMethodForErrorExpressions(tree, methodType); - - // Check method overrides - AnnotatedDeclaredType enclosingType = - (AnnotatedDeclaredType) - atypeFactory.getAnnotatedType(methodElement.getEnclosingElement()); - - // Find which methods this method overrides - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedDeclaredType overriddenType = pair.getKey(); - ExecutableElement overriddenMethodElt = pair.getValue(); - AnnotatedExecutableType overriddenMethodType = - AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, overriddenMethodElt); - if (!checkOverride(tree, enclosingType, overriddenMethodType, overriddenType)) { - // Stop at the first mismatch; this makes a difference only if - // -Awarns is passed, in which case multiple warnings might be raised on - // the same method, not adding any value. See Issue 373. - break; + Tree ext = classTree.getExtendsClause(); + if (ext != null) { + AnnotatedTypeMirror superClass = atypeFactory.getTypeOfExtendsImplements(ext); + validateType(ext, superClass); } - } - // Check well-formedness of pre/postcondition - boolean abstractMethod = - methodElement.getModifiers().contains(Modifier.ABSTRACT) - || methodElement.getModifiers().contains(Modifier.NATIVE); - - List formalParamNames = - CollectionsPlume.mapList( - (VariableTree param) -> param.getName().toString(), tree.getParameters()); - checkContractsAtMethodDeclaration(tree, methodElement, formalParamNames, abstractMethod); - /* NO-AFU - // Infer postconditions - if (shouldPerformContractInference()) { - assert ElementUtils.isElementFromSourceCode(methodElement); - - // TODO: Infer conditional postconditions too. - CFAbstractStore store = atypeFactory.getRegularExitStore(tree); - // The store is null if the method has no normal exit, for example if its body is a - // throw statement. - if (store != null) { - atypeFactory - .getWholeProgramInference() - .updateContracts(Analysis.BeforeOrAfter.AFTER, methodElement, store); + List impls = classTree.getImplementsClause(); + if (impls != null) { + for (Tree im : impls) { + AnnotatedTypeMirror superInterface = atypeFactory.getTypeOfExtendsImplements(im); + validateType(im, superInterface); + } } - } - */ - warnInvalidPolymorphicQualifier(tree.getTypeParameters()); + warnInvalidPolymorphicQualifier(classTree); - return super.visitMethod(tree, p); - } finally { - methodTree = preMT; - } - } - - /* NO-AFU - * Should Whole Program Inference attempt to infer contract annotations? Typically, the answer is - * "yes" whenever WPI is enabled, but this method exists to allow subclasses to customize that - * behavior. - * - * @return true if contract inference should be performed, false if it should be disabled (even - * when WPI is enabled) - */ - /* NO-AFU - protected boolean shouldPerformContractInference() { - return atypeFactory.getWholeProgramInference() != null; - } - */ - - /** - * Check method purity if needed. Note that overriding rules are checked as part of {@link - * #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType)}. - * - * @param tree the method tree to check - */ - protected void checkPurityAnnotations(MethodTree tree) { - if (!checkPurityAnnotations) { - return; - } + checkExtendsAndImplements(classTree); - if (!suggestPureMethods && !PurityUtils.hasPurityAnnotation(atypeFactory, tree)) { - // There is nothing to check. - return; - } + checkQualifierParameter(classTree); - // `body` is lazily assigned. - TreePath body = null; - boolean bodyAssigned = false; - - if (suggestPureMethods || PurityUtils.hasPurityAnnotation(atypeFactory, tree)) { - // check "no" purity - EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, tree); - // @Deterministic makes no sense for a void method or constructor - boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); - if (isDeterministic) { - if (TreeUtils.isConstructor(tree)) { - checker.reportWarning(tree, "purity.deterministic.constructor"); - } else if (TreeUtils.isVoidReturn(tree)) { - checker.reportWarning(tree, "purity.deterministic.void.method"); - } - } + super.visitClass(classTree, null); + } - body = atypeFactory.getPath(tree.getBody()); - bodyAssigned = true; - PurityResult r; - if (body == null) { - r = new PurityResult(); - } else { - r = - PurityChecker.checkPurity( - body, atypeFactory, assumeSideEffectFree, assumeDeterministic, assumePureGetters); - } - if (!r.isPure(kinds)) { - reportPurityErrors(r, tree, kinds); - } + /** + * A TreeScanner that issues an "invalid.polymorphic.qualifier" error for each {@link + * AnnotationTree} that is a polymorphic qualifier. The second parameter is added to the error + * message and should explain the location. + */ + private final TreeScanner polyTreeScanner = + new TreeScanner() { + @Override + public Void visitAnnotation(AnnotationTree annoTree, String location) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(annoTree); + if (atypeFactory.isSupportedQualifier(anno) + && qualHierarchy.isPolymorphicQualifier(anno)) { + checker.reportError( + annoTree, "invalid.polymorphic.qualifier", anno, location); + } + return super.visitAnnotation(annoTree, location); + } + }; - if (suggestPureMethods && !TreeUtils.isSynthetic(tree)) { - // Issue a warning if the method is pure, but not annotated as such. - EnumSet additionalKinds = r.getKinds().clone(); - /* NO-AFU - if (!infer) {*/ - // During WPI, propagate all purity kinds, even those that are already - // present (because they were inferred in a previous WPI round). - additionalKinds.removeAll(kinds); - // NO-AFU } - if (TreeUtils.isConstructor(tree) || TreeUtils.isVoidReturn(tree)) { - additionalKinds.remove(Pure.Kind.DETERMINISTIC); + /** + * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on + * the class declaration. + * + * @param classTree the class to check + */ + protected void warnInvalidPolymorphicQualifier(ClassTree classTree) { + if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { + // Anonymous class can have polymorphic annotations, so don't check them. + return; } - /* NO-AFU - if (infer) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); - inferPurityAnno(additionalKinds, wpi, methodElt); - // The purity of overridden methods is impacted by the purity of this method. If - // a superclass method is pure, but an implementation in a subclass is not, WPI - // ought to treat **neither** as pure. The purity kind of the superclass method - // is the LUB of its own purity and the purity of all the methods that override - // it. Logically, this rule is the same as the WPI rule for overrides, but - // purity isn't a type system and therefore must be special-cased. - Set overriddenMethods = - ElementUtils.getOverriddenMethods(methodElt, types); - for (ExecutableElement overriddenElt : overriddenMethods) { - inferPurityAnno(additionalKinds, wpi, overriddenElt); - } - } else */ - if (additionalKinds.isEmpty()) { - // No need to suggest @Impure, since it is equivalent to no annotation. - } else if (additionalKinds.size() == 2) { - checker.reportWarning(tree, "purity.more.pure", tree.getName()); - } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - checker.reportWarning(tree, "purity.more.sideeffectfree", tree.getName()); - } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { - checker.reportWarning(tree, "purity.more.deterministic", tree.getName()); - } else { - throw new BugInCF("Unexpected purity kind in " + additionalKinds); + classTree.getModifiers().accept(polyTreeScanner, "in a class declaration"); + if (classTree.getExtendsClause() != null) { + classTree.getExtendsClause().accept(polyTreeScanner, "in a class declaration"); } - } - } - - // There will be code here that *may* use `body` (and may set `body` before using it). - // The below is just a placeholder so `bodyAssigned` is not a dead variable. - // ... - if (!bodyAssigned) { - body = atypeFactory.getPath(tree.getBody()); - bodyAssigned = true; - } - // ... - } - - /* NO-AFU - * Infer a purity annotation for {@code elt} by converting {@code kinds} into a method annotation. - * - *

          This method delegates to {@code WholeProgramInference.addMethodDeclarationAnnotation}, which - * special-cases purity annotations: that method lubs a purity argument with whatever purity - * annotation is already present on {@code elt}. - * - * @param kinds the set of purity kinds to use to infer the annotation - * @param wpi the whole program inference instance to use to do the inferring - * @param elt the element whose purity is being inferred - * - private void inferPurityAnno( - EnumSet kinds, WholeProgramInference wpi, ExecutableElement elt) { - if (kinds.size() == 2) { - wpi.addMethodDeclarationAnnotation(elt, PURE, true); - } else if (kinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - wpi.addMethodDeclarationAnnotation(elt, SIDE_EFFECT_FREE, true); - } else if (kinds.contains(Pure.Kind.DETERMINISTIC)) { - wpi.addMethodDeclarationAnnotation(elt, DETERMINISTIC, true); - } else { - assert kinds.isEmpty(); - wpi.addMethodDeclarationAnnotation(elt, IMPURE, true); - } - } - */ - - /** - * Issue a warning if the result type of the constructor declaration is not top. If it is a - * supertype of the class, then a type.invalid.conflicting.annos error will also be issued by - * {@link - * #isValidUse(AnnotatedTypeMirror.AnnotatedDeclaredType,AnnotatedTypeMirror.AnnotatedDeclaredType,Tree)}. - * - * @param constructorType the AnnotatedExecutableType for the constructor - * @param constructorElement the element that declares the constructor - */ - protected void checkConstructorResult( - AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { - AnnotatedTypeMirror returnType = constructorType.getReturnType(); - AnnotationMirrorSet constructorAnnotations = returnType.getAnnotations(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - - for (AnnotationMirror top : tops) { - AnnotationMirror constructorAnno = - qualHierarchy.findAnnotationInHierarchy(constructorAnnotations, top); - if (!AnnotationUtils.areSame(top, constructorAnno)) { - checker.reportWarning( - constructorElement, "inconsistent.constructor.type", constructorAnno, top); - } - } - } - - /** - * Reports errors found during purity checking. - * - * @param result whether the method is deterministic and/or side-effect-free - * @param tree the method - * @param expectedKinds the expected purity for the method - */ - protected void reportPurityErrors( - PurityResult result, MethodTree tree, EnumSet expectedKinds) { - assert !result.isPure(expectedKinds); - EnumSet violations = EnumSet.copyOf(expectedKinds); - violations.removeAll(result.getKinds()); - if (violations.contains(Pure.Kind.DETERMINISTIC) - || violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - String msgKeyPrefix; - if (!violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - msgKeyPrefix = "purity.not.deterministic."; - } else if (!violations.contains(Pure.Kind.DETERMINISTIC)) { - msgKeyPrefix = "purity.not.sideeffectfree."; - } else { - msgKeyPrefix = "purity.not.deterministic.not.sideeffectfree."; - } - for (IPair r : result.getNotBothReasons()) { - reportPurityError(msgKeyPrefix, r); - } - if (violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - for (IPair r : result.getNotSEFreeReasons()) { - reportPurityError("purity.not.sideeffectfree.", r); + for (Tree tree : classTree.getImplementsClause()) { + tree.accept(polyTreeScanner, "in a class declaration"); } - } - if (violations.contains(Pure.Kind.DETERMINISTIC)) { - for (IPair r : result.getNotDetReasons()) { - reportPurityError("purity.not.deterministic.", r); + for (Tree tree : classTree.getTypeParameters()) { + tree.accept(polyTreeScanner, "in a class declaration"); } - } - } - } - - /** - * Reports a single purity error. - * - * @param msgKeyPrefix the prefix of the message key to use when reporting - * @param r the result to report - */ - private void reportPurityError(String msgKeyPrefix, IPair r) { - String reason = r.second; - @SuppressWarnings("compilermessages") - @CompilerMessageKey String msgKey = msgKeyPrefix + reason; - if (reason.equals("call")) { - if (r.first.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree mitree = (MethodInvocationTree) r.first; - checker.reportError(r.first, msgKey, mitree.getMethodSelect()); - } else { - NewClassTree nctree = (NewClassTree) r.first; - checker.reportError(r.first, msgKey, nctree.getIdentifier()); - } - } else { - checker.reportError(r.first, msgKey); } - } - - /** - * Check the contracts written on a method declaration. Ensures that the postconditions hold on - * exit, and that the contracts are well-formed. - * - * @param methodTree the method declaration - * @param methodElement the method element - * @param formalParamNames the formal parameter names - * @param abstractMethod whether the method is abstract - */ - private void checkContractsAtMethodDeclaration( - MethodTree methodTree, - ExecutableElement methodElement, - List formalParamNames, - boolean abstractMethod) { - Set contracts = atypeFactory.getContractsFromMethod().getContracts(methodElement); - - if (contracts.isEmpty()) { - return; - } - StringToJavaExpression stringToJavaExpr = - stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, methodTree, checker); - for (Contract contract : contracts) { - String expressionString = contract.expressionString; - AnnotationMirror annotation = - contract.viewpointAdaptDependentTypeAnnotation( - atypeFactory, stringToJavaExpr, methodTree); - - JavaExpression exprJe; - try { - exprJe = StringToJavaExpression.atMethodBody(expressionString, methodTree, checker); - } catch (JavaExpressionParseException e) { - DiagMessage diagMessage = e.getDiagMessage(); - if (diagMessage.getMessageKey().equals("flowexpr.parse.error")) { - String s = - String.format( - "'%s' in the %s %s on the declaration of method '%s': ", - expressionString, - contract.kind.errorKey, - contract.contractAnnotation.getAnnotationType().asElement().getSimpleName(), - methodTree.getName().toString()); - checker.reportError(methodTree, "flowexpr.parse.error", s + diagMessage.getArgs()[0]); - } else { - checker.report(methodTree, e.getDiagMessage()); - } - continue; - } - if (!CFAbstractStore.canInsertJavaExpression(exprJe)) { - checker.reportError(methodTree, "flowexpr.parse.error", expressionString); - continue; - } - if (!abstractMethod && contract.kind != Contract.Kind.PRECONDITION) { - // Check the contract, which is a postcondition. - // Preconditions are checked at method invocations, not declarations. - - switch (contract.kind) { - case POSTCONDITION: - checkPostcondition(methodTree, annotation, exprJe); - break; - case CONDITIONALPOSTCONDITION: - checkConditionalPostcondition( - methodTree, annotation, exprJe, ((ConditionalPostcondition) contract).resultValue); - break; - default: - throw new BugInCF("Impossible: " + contract.kind); - } - } - - if (formalParamNames != null && formalParamNames.contains(expressionString)) { - String locationOfExpression = - contract.kind.errorKey - + " " - + contract.contractAnnotation.getAnnotationType().asElement().getSimpleName() - + " on the declaration"; - checker.reportWarning( - methodTree, - "expression.parameter.name.shadows.field", - locationOfExpression, - methodTree.getName().toString(), - expressionString, - expressionString, - formalParamNames.indexOf(expressionString) + 1); - } - checkParametersAreEffectivelyFinal(methodTree, exprJe); - } - } - - /** - * Scans a {@link JavaExpression} and adds all the parameters in the {@code JavaExpression} to the - * passed set. - */ - private final JavaExpressionScanner> findParameters = - new JavaExpressionScanner>() { - @Override - protected Void visitLocalVariable(LocalVariable localVarExpr, Set parameters) { - if (localVarExpr.getElement().getKind() == ElementKind.PARAMETER) { - parameters.add(localVarExpr.getElement()); - } - return super.visitLocalVariable(localVarExpr, parameters); - } - }; - - /** - * Check that the parameters used in {@code javaExpression} are effectively final for method - * {@code method}. - * - * @param methodDeclTree a method declaration - * @param javaExpression a Java expression - */ - private void checkParametersAreEffectivelyFinal( - MethodTree methodDeclTree, JavaExpression javaExpression) { - // check that all parameters used in the expression are - // effectively final, so that they cannot be modified - Set parameters = new ArraySet<>(2); - findParameters.scan(javaExpression, parameters); - for (Element parameter : parameters) { - if (!ElementUtils.isEffectivelyFinal(parameter)) { - checker.reportError( - methodDeclTree, - "flowexpr.parameter.not.final", - parameter.getSimpleName(), - javaExpression); - } - } - } - - /** - * Check that the expression's type is annotated with {@code annotation} at the regular exit - * store. - * - * @param methodTree declaration of the method - * @param annotation expression's type must have this annotation - * @param expression the expression that must have an annotation - */ - protected void checkPostcondition( - MethodTree methodTree, AnnotationMirror annotation, JavaExpression expression) { - CFAbstractStore exitStore = atypeFactory.getRegularExitStore(methodTree); - if (exitStore == null) { - // If there is no regular exitStore, then the method cannot reach the regular exit and - // there is no need to check anything. - } else { - CFAbstractValue value = exitStore.getValue(expression); - AnnotationMirror inferredAnno = null; - if (value != null) { - AnnotationMirrorSet annos = value.getAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); - } - if (!checkContract(expression, annotation, inferredAnno, exitStore)) { - checker.reportError( - methodTree, - "contracts.postcondition.not.satisfied", - methodTree.getName(), - contractExpressionAndType(expression.toString(), inferredAnno), - contractExpressionAndType(expression.toString(), annotation)); - } - } - } - - /** - * Returns a string representation of an expression and type qualifier. - * - * @param expression a Java expression - * @param qualifier the expression's type, or null if no information is available - * @return a string representation of the expression and type qualifier - */ - protected String contractExpressionAndType( - String expression, @Nullable AnnotationMirror qualifier) { - if (qualifier == null) { - return "no information about " + expression; - } else { - return expression - + " is " - + atypeFactory.getAnnotationFormatter().formatAnnotationMirror(qualifier); - } - } - - /** - * Check that the expression's type is annotated with {@code annotation} at every regular exit - * that returns {@code result}. - * - * @param methodTree tree of method with the postcondition - * @param annotation expression's type must have this annotation - * @param expression the expression that the postcondition concerns - * @param result result for which the postcondition is valid - */ - protected void checkConditionalPostcondition( - MethodTree methodTree, - AnnotationMirror annotation, - JavaExpression expression, - boolean result) { - boolean booleanReturnType = - TypesUtils.isBooleanType(TreeUtils.typeOf(methodTree.getReturnType())); - if (!booleanReturnType) { - checker.reportError(methodTree, "contracts.conditional.postcondition.invalid.returntype"); - // No reason to go ahead with further checking. The - // annotation is invalid. - return; + /** + * Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on + * the type parameters declaration. + * + * @param typeParameterTrees the type parameters to check + */ + protected void warnInvalidPolymorphicQualifier( + List typeParameterTrees) { + for (Tree tree : typeParameterTrees) { + tree.accept(polyTreeScanner, "in a type parameter"); + } } - for (IPair pair : atypeFactory.getReturnStatementStores(methodTree)) { - ReturnNode returnStmt = pair.first; - - Node retValNode = returnStmt.getResult(); - Boolean retVal = - retValNode instanceof BooleanLiteralNode - ? ((BooleanLiteralNode) retValNode).getValue() - : null; - - TransferResult transferResult = (TransferResult) pair.second; - if (transferResult == null) { - // Unreachable return statements have no stores, but there is no need to check them. - continue; - } - CFAbstractStore exitStore = - (CFAbstractStore) - (result ? transferResult.getThenStore() : transferResult.getElseStore()); - CFAbstractValue value = exitStore.getValue(expression); - - // don't check if return statement certainly does not match 'result'. at the moment, - // this means the result is a boolean literal - if (!(retVal == null || retVal == result)) { - continue; - } - AnnotationMirror inferredAnno = null; - if (value != null) { - AnnotationMirrorSet annos = value.getAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); - } + /** + * Issues an error if {@code classTree} has polymorphic fields but is not annotated with + * {@code @HasQualifierParameter}. Always issue a warning if the type of a static field is + * annotated with a polymorphic qualifier. + * + *

          Issues an error if {@code classTree} extends or implements a class/interface that has a + * qualifier parameter, but this class does not. + * + * @param classTree the ClassTree to check for polymorphic fields + */ + protected void checkQualifierParameter(ClassTree classTree) { + // Set of polymorphic qualifiers for hierarchies that do not have a qualifier parameter and + // therefore cannot appear on a field. + AnnotationMirrorSet illegalOnFieldsPolyQual = new AnnotationMirrorSet(); + // Set of polymorphic annotations for all hierarchies + AnnotationMirrorSet polys = new AnnotationMirrorSet(); + TypeElement classElement = TreeUtils.elementFromDeclaration(classTree); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); + if (poly != null) { + polys.add(poly); + } + // else { + // If there is no polymorphic qualifier in the hierarchy, it could still have a + // @HasQualifierParameter that must be checked. + // } + + if (!atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) + && atypeFactory.getDeclAnnotation(classElement, HasQualifierParameter.class) + != null) { + // The argument to a @HasQualifierParameter annotation must be the top type in the + // type system. + checker.reportError(classTree, "invalid.qual.param", top); + break; + } + + if (atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) + && atypeFactory.hasExplicitNoQualifierParameterInHierarchy(classElement, top)) { + checker.reportError(classTree, "conflicting.qual.param", top); + } + + if (atypeFactory.hasQualifierParameterInHierarchy(classElement, top)) { + continue; + } + + if (poly != null) { + illegalOnFieldsPolyQual.add(poly); + } + Element extendsEle = TypesUtils.getTypeElement(classElement.getSuperclass()); + if (extendsEle != null + && atypeFactory.hasQualifierParameterInHierarchy(extendsEle, top)) { + checker.reportError(classTree, "missing.has.qual.param", top); + } else { + for (TypeMirror interfaceType : classElement.getInterfaces()) { + Element interfaceEle = TypesUtils.getTypeElement(interfaceType); + if (atypeFactory.hasQualifierParameterInHierarchy(interfaceEle, top)) { + checker.reportError(classTree, "missing.has.qual.param", top); + break; // only issue error once + } + } + } + } + + for (Tree mem : classTree.getMembers()) { + if (mem.getKind() == Tree.Kind.VARIABLE) { + AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(mem); + List hasInvalidPoly; + if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration((VariableTree) mem))) { + // A polymorphic qualifier is not allowed on a static field even if the class + // has a qualifier parameter. + hasInvalidPoly = hasInvalidPolyScanner.visit(fieldType, polys); + } else { + hasInvalidPoly = + hasInvalidPolyScanner.visit(fieldType, illegalOnFieldsPolyQual); + } + for (DiagMessage dm : hasInvalidPoly) { + checker.report(mem, dm); + } + } + } + } + + /** + * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a + * use of one of the polymorphic qualifiers. + */ + private final HasInvalidPolyScanner hasInvalidPolyScanner = new HasInvalidPolyScanner(); + + /** + * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a + * use of one of the polymorphic qualifiers. + */ + static class HasInvalidPolyScanner + extends SimpleAnnotatedTypeScanner, AnnotationMirrorSet> { + + /** Create HasInvalidPolyScanner. */ + private HasInvalidPolyScanner() { + super(DiagMessage::mergeLists, Collections.emptyList()); + } + + @Override + protected List defaultAction( + AnnotatedTypeMirror type, AnnotationMirrorSet polys) { + if (type == null) { + return Collections.emptyList(); + } + + for (AnnotationMirror poly : polys) { + if (type.hasAnnotation(poly)) { + return Collections.singletonList( + DiagMessage.error("invalid.polymorphic.qualifier.use", poly)); + } + } + return Collections.emptyList(); + } + } + + /** + * In {@code @A class X extends @B Y implements @C Z {}}, enforce that {@code @A} must be a + * subtype of {@code @B} and {@code @C}. + * + *

          Also validate the types of the extends and implements clauses. + * + * @param classTree class tree to check + */ + protected void checkExtendsAndImplements(ClassTree classTree) { + if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { + // Don't check extends clause on anonymous classes. + return; + } + if (classTree.getExtendsClause() == null && classTree.getImplementsClause().isEmpty()) { + // Nothing to do + return; + } + + TypeMirror classType = TreeUtils.typeOf(classTree); + AnnotationMirrorSet classBounds = atypeFactory.getTypeDeclarationBounds(classType); + // If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A. + // classTree.getExtendsClause() is null when there is no explicitly-written extends clause, + // as in "class X {}". This is equivalent to writing "class X extends @Top Object {}", so + // there is no need to do any subtype checking. + if (classTree.getExtendsClause() != null) { + Tree boundClause = classTree.getExtendsClause(); + checkExtendsOrImplements(boundClause, classBounds, classType, true); + } + // Do the same check as above for implements clauses. + for (Tree boundClause : classTree.getImplementsClause()) { + checkExtendsOrImplements(boundClause, classBounds, classType, false); + } + } + + /** + * Helper for {@link #checkExtendsAndImplements} that checks one extends or implements clause. + * + * @param boundClause an extends or implements clause + * @param classBounds the type declarations bounds to check for consistency with {@code + * boundClause} + * @param classType the type being declared + * @param isExtends true for an extends clause, false for an implements clause + */ + protected void checkExtendsOrImplements( + Tree boundClause, + AnnotationMirrorSet classBounds, + TypeMirror classType, + boolean isExtends) { + AnnotatedTypeMirror boundType = atypeFactory.getTypeOfExtendsImplements(boundClause); + TypeMirror boundTM = boundType.getUnderlyingType(); + for (AnnotationMirror classAnno : classBounds) { + AnnotationMirror boundAnno = boundType.getAnnotationInHierarchy(classAnno); + if (!qualHierarchy.isSubtypeShallow(classAnno, classType, boundAnno, boundTM)) { + checker.reportError( + boundClause, + (isExtends + ? "declaration.inconsistent.with.extends.clause" + : "declaration.inconsistent.with.implements.clause"), + classAnno, + boundAnno); + } + } + } + + /** + * Check that the field invariant declaration annotations meet the following requirements: + * + *

            + * + *
          1. If the superclass of {@code classTree} has a field invariant, then the field + * invariant for {@code classTree} must include all the fields in the superclass invariant + * and those fields' annotations must be a subtype (or equal) to the annotations for those + * fields in the superclass. + *
          2. The fields in the invariant must be a.) final and b.) declared in a + * superclass of {@code classTree}. + *
          3. The qualifier for each field must be a subtype of the annotation on the + * declaration of that field. + *
          4. The field invariant has an equal number of fields and qualifiers, or it has + * one qualifier and at least one field. + *
          + * + * @param classTree class that might have a field invariant + * @checker_framework.manual #field-invariants Field invariants + */ + protected void checkFieldInvariantDeclarations(ClassTree classTree) { + TypeElement elt = TreeUtils.elementFromDeclaration(classTree); + FieldInvariants invariants = atypeFactory.getFieldInvariants(elt); + if (invariants == null) { + // No invariants to check + return; + } + + // Where to issue an error, if any. + Tree errorTree = + atypeFactory.getFieldInvariantAnnotationTree( + classTree.getModifiers().getAnnotations()); + if (errorTree == null) { + // If the annotation was inherited, then there is no annotation tree, so issue the + // error on the class. + errorTree = classTree; + } + + // Checks #4 (see method Javadoc) + if (!invariants.isWellFormed()) { + checker.reportError(errorTree, "field.invariant.not.wellformed"); + return; + } + + TypeMirror superClass = elt.getSuperclass(); + List fieldsNotFound = new ArrayList<>(invariants.getFields()); + Set fieldElts = + ElementUtils.findFieldsInTypeOrSuperType(superClass, fieldsNotFound); + + // Checks that fields are declared in super class. (#2b) + if (!fieldsNotFound.isEmpty()) { + String notFoundString = String.join(", ", fieldsNotFound); + checker.reportError(errorTree, "field.invariant.not.found", notFoundString); + } + + FieldInvariants superInvar = + atypeFactory.getFieldInvariants(TypesUtils.getTypeElement(superClass)); + if (superInvar != null) { + // Checks #3 (see method Javadoc) + DiagMessage superError = invariants.isStrongerThan(superInvar); + if (superError != null) { + checker.report(errorTree, superError); + } + } + + List notFinal = new ArrayList<>(fieldElts.size()); + for (VariableElement field : fieldElts) { + String fieldName = field.getSimpleName().toString(); + if (!ElementUtils.isFinal(field)) { + notFinal.add(fieldName); + } + AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(field); + + List annos = invariants.getQualifiersFor(field.getSimpleName()); + for (AnnotationMirror invariantAnno : annos) { + AnnotationMirror declaredAnno = + fieldType.getEffectiveAnnotationInHierarchy(invariantAnno); + if (declaredAnno == null) { + // invariant anno isn't in this hierarchy + continue; + } + + if (!typeHierarchy.isSubtypeShallowEffective(invariantAnno, fieldType)) { + // Checks #3 + checker.reportError( + errorTree, + "field.invariant.not.subtype", + fieldName, + invariantAnno, + declaredAnno); + } + } + } + + // Checks #2a + if (!notFinal.isEmpty()) { + String notFinalString = String.join(", ", notFinal); + checker.reportError(errorTree, "field.invariant.not.final", notFinalString); + } + } + + /** + * Check the default constructor. + * + * @param tree a class declaration + */ + protected void checkDefaultConstructor(ClassTree tree) {} + + /** + * Checks that the method obeys override and subtype rules to all overridden methods. (Uses the + * pseudo-assignment logic to do so.) + * + *

          The override rule specifies that a method, m1, may override a method m2 only if: + * + *

            + *
          • m1 return type is a subtype of m2 + *
          • m1 receiver type is a supertype of m2 + *
          • m1 parameters are supertypes of corresponding m2 parameters + *
          + * + * Also, it issues a "missing.this" error for static method annotated receivers. + */ + @Override + public Void visitMethod(MethodTree tree, Void p) { + // We copy the result from getAnnotatedType to ensure that circular types (e.g. K extends + // Comparable) are represented by circular AnnotatedTypeMirrors, which avoids problems + // with later checks. + // TODO: Find a cleaner way to ensure circular AnnotatedTypeMirrors. + AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(tree).deepCopy(); + MethodTree preMT = methodTree; + methodTree = tree; + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree); + + warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); + + if (tree.getReturnType() != null) { + visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getReturnType()); + warnRedundantAnnotations(tree.getReturnType(), methodType.getReturnType()); + } else if (TreeUtils.isConstructor(tree)) { + maybeReportAnnoOnIrrelevant( + tree.getModifiers(), + methodType.getReturnType().getUnderlyingType(), + tree.getModifiers().getAnnotations()); + } + + try { + if (TreeUtils.isAnonymousConstructor(tree)) { + // We shouldn't dig deeper + return null; + } + + if (TreeUtils.isConstructor(tree)) { + checkConstructorResult(methodType, methodElement); + } + + checkPurityAnnotations(tree); + + // Passing the whole method/constructor validates the return type + validateTypeOf(tree); + + // Validate types in throws clauses + for (ExpressionTree thr : tree.getThrows()) { + validateTypeOf(thr); + } + + atypeFactory.getDependentTypesHelper().checkMethodForErrorExpressions(tree, methodType); + + // Check method overrides + AnnotatedDeclaredType enclosingType = + (AnnotatedDeclaredType) + atypeFactory.getAnnotatedType(methodElement.getEnclosingElement()); + + // Find which methods this method overrides + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedDeclaredType overriddenType = pair.getKey(); + ExecutableElement overriddenMethodElt = pair.getValue(); + AnnotatedExecutableType overriddenMethodType = + AnnotatedTypes.asMemberOf( + types, atypeFactory, overriddenType, overriddenMethodElt); + if (!checkOverride(tree, enclosingType, overriddenMethodType, overriddenType)) { + // Stop at the first mismatch; this makes a difference only if + // -Awarns is passed, in which case multiple warnings might be raised on + // the same method, not adding any value. See Issue 373. + break; + } + } + + // Check well-formedness of pre/postcondition + boolean abstractMethod = + methodElement.getModifiers().contains(Modifier.ABSTRACT) + || methodElement.getModifiers().contains(Modifier.NATIVE); + + List formalParamNames = + CollectionsPlume.mapList( + (VariableTree param) -> param.getName().toString(), + tree.getParameters()); + checkContractsAtMethodDeclaration( + tree, methodElement, formalParamNames, abstractMethod); + /* NO-AFU + // Infer postconditions + if (shouldPerformContractInference()) { + assert ElementUtils.isElementFromSourceCode(methodElement); + + // TODO: Infer conditional postconditions too. + CFAbstractStore store = atypeFactory.getRegularExitStore(tree); + // The store is null if the method has no normal exit, for example if its body is a + // throw statement. + if (store != null) { + atypeFactory + .getWholeProgramInference() + .updateContracts(Analysis.BeforeOrAfter.AFTER, methodElement, store); + } + } + */ + + warnInvalidPolymorphicQualifier(tree.getTypeParameters()); + + return super.visitMethod(tree, p); + } finally { + methodTree = preMT; + } + } + + /* NO-AFU + * Should Whole Program Inference attempt to infer contract annotations? Typically, the answer is + * "yes" whenever WPI is enabled, but this method exists to allow subclasses to customize that + * behavior. + * + * @return true if contract inference should be performed, false if it should be disabled (even + * when WPI is enabled) + */ + /* NO-AFU + protected boolean shouldPerformContractInference() { + return atypeFactory.getWholeProgramInference() != null; + } + */ + + /** + * Check method purity if needed. Note that overriding rules are checked as part of {@link + * #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType)}. + * + * @param tree the method tree to check + */ + protected void checkPurityAnnotations(MethodTree tree) { + if (!checkPurityAnnotations) { + return; + } + + if (!suggestPureMethods && !PurityUtils.hasPurityAnnotation(atypeFactory, tree)) { + // There is nothing to check. + return; + } + + // `body` is lazily assigned. + TreePath body = null; + boolean bodyAssigned = false; + + if (suggestPureMethods || PurityUtils.hasPurityAnnotation(atypeFactory, tree)) { + // check "no" purity + EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, tree); + // @Deterministic makes no sense for a void method or constructor + boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); + if (isDeterministic) { + if (TreeUtils.isConstructor(tree)) { + checker.reportWarning(tree, "purity.deterministic.constructor"); + } else if (TreeUtils.isVoidReturn(tree)) { + checker.reportWarning(tree, "purity.deterministic.void.method"); + } + } + + body = atypeFactory.getPath(tree.getBody()); + bodyAssigned = true; + PurityResult r; + if (body == null) { + r = new PurityResult(); + } else { + r = + PurityChecker.checkPurity( + body, + atypeFactory, + assumeSideEffectFree, + assumeDeterministic, + assumePureGetters); + } + if (!r.isPure(kinds)) { + reportPurityErrors(r, tree, kinds); + } + + if (suggestPureMethods && !TreeUtils.isSynthetic(tree)) { + // Issue a warning if the method is pure, but not annotated as such. + EnumSet additionalKinds = r.getKinds().clone(); + /* NO-AFU + if (!infer) {*/ + // During WPI, propagate all purity kinds, even those that are already + // present (because they were inferred in a previous WPI round). + additionalKinds.removeAll(kinds); + // NO-AFU } + if (TreeUtils.isConstructor(tree) || TreeUtils.isVoidReturn(tree)) { + additionalKinds.remove(Pure.Kind.DETERMINISTIC); + } + /* NO-AFU + if (infer) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); + inferPurityAnno(additionalKinds, wpi, methodElt); + // The purity of overridden methods is impacted by the purity of this method. If + // a superclass method is pure, but an implementation in a subclass is not, WPI + // ought to treat **neither** as pure. The purity kind of the superclass method + // is the LUB of its own purity and the purity of all the methods that override + // it. Logically, this rule is the same as the WPI rule for overrides, but + // purity isn't a type system and therefore must be special-cased. + Set overriddenMethods = + ElementUtils.getOverriddenMethods(methodElt, types); + for (ExecutableElement overriddenElt : overriddenMethods) { + inferPurityAnno(additionalKinds, wpi, overriddenElt); + } + } else */ + if (additionalKinds.isEmpty()) { + // No need to suggest @Impure, since it is equivalent to no annotation. + } else if (additionalKinds.size() == 2) { + checker.reportWarning(tree, "purity.more.pure", tree.getName()); + } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + checker.reportWarning(tree, "purity.more.sideeffectfree", tree.getName()); + } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { + checker.reportWarning(tree, "purity.more.deterministic", tree.getName()); + } else { + throw new BugInCF("Unexpected purity kind in " + additionalKinds); + } + } + } + + // There will be code here that *may* use `body` (and may set `body` before using it). + // The below is just a placeholder so `bodyAssigned` is not a dead variable. + // ... + if (!bodyAssigned) { + body = atypeFactory.getPath(tree.getBody()); + bodyAssigned = true; + } + // ... + } + + /* NO-AFU + * Infer a purity annotation for {@code elt} by converting {@code kinds} into a method annotation. + * + *

          This method delegates to {@code WholeProgramInference.addMethodDeclarationAnnotation}, which + * special-cases purity annotations: that method lubs a purity argument with whatever purity + * annotation is already present on {@code elt}. + * + * @param kinds the set of purity kinds to use to infer the annotation + * @param wpi the whole program inference instance to use to do the inferring + * @param elt the element whose purity is being inferred + * + private void inferPurityAnno( + EnumSet kinds, WholeProgramInference wpi, ExecutableElement elt) { + if (kinds.size() == 2) { + wpi.addMethodDeclarationAnnotation(elt, PURE, true); + } else if (kinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + wpi.addMethodDeclarationAnnotation(elt, SIDE_EFFECT_FREE, true); + } else if (kinds.contains(Pure.Kind.DETERMINISTIC)) { + wpi.addMethodDeclarationAnnotation(elt, DETERMINISTIC, true); + } else { + assert kinds.isEmpty(); + wpi.addMethodDeclarationAnnotation(elt, IMPURE, true); + } + } + */ + + /** + * Issue a warning if the result type of the constructor declaration is not top. If it is a + * supertype of the class, then a type.invalid.conflicting.annos error will also be issued by + * {@link + * #isValidUse(AnnotatedTypeMirror.AnnotatedDeclaredType,AnnotatedTypeMirror.AnnotatedDeclaredType,Tree)}. + * + * @param constructorType the AnnotatedExecutableType for the constructor + * @param constructorElement the element that declares the constructor + */ + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + AnnotatedTypeMirror returnType = constructorType.getReturnType(); + AnnotationMirrorSet constructorAnnotations = returnType.getAnnotations(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + + for (AnnotationMirror top : tops) { + AnnotationMirror constructorAnno = + qualHierarchy.findAnnotationInHierarchy(constructorAnnotations, top); + if (!AnnotationUtils.areSame(top, constructorAnno)) { + checker.reportWarning( + constructorElement, "inconsistent.constructor.type", constructorAnno, top); + } + } + } + + /** + * Reports errors found during purity checking. + * + * @param result whether the method is deterministic and/or side-effect-free + * @param tree the method + * @param expectedKinds the expected purity for the method + */ + protected void reportPurityErrors( + PurityResult result, MethodTree tree, EnumSet expectedKinds) { + assert !result.isPure(expectedKinds); + EnumSet violations = EnumSet.copyOf(expectedKinds); + violations.removeAll(result.getKinds()); + if (violations.contains(Pure.Kind.DETERMINISTIC) + || violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + String msgKeyPrefix; + if (!violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + msgKeyPrefix = "purity.not.deterministic."; + } else if (!violations.contains(Pure.Kind.DETERMINISTIC)) { + msgKeyPrefix = "purity.not.sideeffectfree."; + } else { + msgKeyPrefix = "purity.not.deterministic.not.sideeffectfree."; + } + for (IPair r : result.getNotBothReasons()) { + reportPurityError(msgKeyPrefix, r); + } + if (violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + for (IPair r : result.getNotSEFreeReasons()) { + reportPurityError("purity.not.sideeffectfree.", r); + } + } + if (violations.contains(Pure.Kind.DETERMINISTIC)) { + for (IPair r : result.getNotDetReasons()) { + reportPurityError("purity.not.deterministic.", r); + } + } + } + } + + /** + * Reports a single purity error. + * + * @param msgKeyPrefix the prefix of the message key to use when reporting + * @param r the result to report + */ + private void reportPurityError(String msgKeyPrefix, IPair r) { + String reason = r.second; + @SuppressWarnings("compilermessages") + @CompilerMessageKey String msgKey = msgKeyPrefix + reason; + if (reason.equals("call")) { + if (r.first.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree mitree = (MethodInvocationTree) r.first; + checker.reportError(r.first, msgKey, mitree.getMethodSelect()); + } else { + NewClassTree nctree = (NewClassTree) r.first; + checker.reportError(r.first, msgKey, nctree.getIdentifier()); + } + } else { + checker.reportError(r.first, msgKey); + } + } + + /** + * Check the contracts written on a method declaration. Ensures that the postconditions hold on + * exit, and that the contracts are well-formed. + * + * @param methodTree the method declaration + * @param methodElement the method element + * @param formalParamNames the formal parameter names + * @param abstractMethod whether the method is abstract + */ + private void checkContractsAtMethodDeclaration( + MethodTree methodTree, + ExecutableElement methodElement, + List formalParamNames, + boolean abstractMethod) { + Set contracts = atypeFactory.getContractsFromMethod().getContracts(methodElement); + + if (contracts.isEmpty()) { + return; + } + StringToJavaExpression stringToJavaExpr = + stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, methodTree, checker); + for (Contract contract : contracts) { + String expressionString = contract.expressionString; + AnnotationMirror annotation = + contract.viewpointAdaptDependentTypeAnnotation( + atypeFactory, stringToJavaExpr, methodTree); + + JavaExpression exprJe; + try { + exprJe = StringToJavaExpression.atMethodBody(expressionString, methodTree, checker); + } catch (JavaExpressionParseException e) { + DiagMessage diagMessage = e.getDiagMessage(); + if (diagMessage.getMessageKey().equals("flowexpr.parse.error")) { + String s = + String.format( + "'%s' in the %s %s on the declaration of method '%s': ", + expressionString, + contract.kind.errorKey, + contract.contractAnnotation + .getAnnotationType() + .asElement() + .getSimpleName(), + methodTree.getName().toString()); + checker.reportError( + methodTree, "flowexpr.parse.error", s + diagMessage.getArgs()[0]); + } else { + checker.report(methodTree, e.getDiagMessage()); + } + continue; + } + if (!CFAbstractStore.canInsertJavaExpression(exprJe)) { + checker.reportError(methodTree, "flowexpr.parse.error", expressionString); + continue; + } + if (!abstractMethod && contract.kind != Contract.Kind.PRECONDITION) { + // Check the contract, which is a postcondition. + // Preconditions are checked at method invocations, not declarations. + + switch (contract.kind) { + case POSTCONDITION: + checkPostcondition(methodTree, annotation, exprJe); + break; + case CONDITIONALPOSTCONDITION: + checkConditionalPostcondition( + methodTree, + annotation, + exprJe, + ((ConditionalPostcondition) contract).resultValue); + break; + default: + throw new BugInCF("Impossible: " + contract.kind); + } + } + + if (formalParamNames != null && formalParamNames.contains(expressionString)) { + String locationOfExpression = + contract.kind.errorKey + + " " + + contract.contractAnnotation + .getAnnotationType() + .asElement() + .getSimpleName() + + " on the declaration"; + checker.reportWarning( + methodTree, + "expression.parameter.name.shadows.field", + locationOfExpression, + methodTree.getName().toString(), + expressionString, + expressionString, + formalParamNames.indexOf(expressionString) + 1); + } + + checkParametersAreEffectivelyFinal(methodTree, exprJe); + } + } + + /** + * Scans a {@link JavaExpression} and adds all the parameters in the {@code JavaExpression} to + * the passed set. + */ + private final JavaExpressionScanner> findParameters = + new JavaExpressionScanner>() { + @Override + protected Void visitLocalVariable( + LocalVariable localVarExpr, Set parameters) { + if (localVarExpr.getElement().getKind() == ElementKind.PARAMETER) { + parameters.add(localVarExpr.getElement()); + } + return super.visitLocalVariable(localVarExpr, parameters); + } + }; + + /** + * Check that the parameters used in {@code javaExpression} are effectively final for method + * {@code method}. + * + * @param methodDeclTree a method declaration + * @param javaExpression a Java expression + */ + private void checkParametersAreEffectivelyFinal( + MethodTree methodDeclTree, JavaExpression javaExpression) { + // check that all parameters used in the expression are + // effectively final, so that they cannot be modified + Set parameters = new ArraySet<>(2); + findParameters.scan(javaExpression, parameters); + for (Element parameter : parameters) { + if (!ElementUtils.isEffectivelyFinal(parameter)) { + checker.reportError( + methodDeclTree, + "flowexpr.parameter.not.final", + parameter.getSimpleName(), + javaExpression); + } + } + } + + /** + * Check that the expression's type is annotated with {@code annotation} at the regular exit + * store. + * + * @param methodTree declaration of the method + * @param annotation expression's type must have this annotation + * @param expression the expression that must have an annotation + */ + protected void checkPostcondition( + MethodTree methodTree, AnnotationMirror annotation, JavaExpression expression) { + CFAbstractStore exitStore = atypeFactory.getRegularExitStore(methodTree); + if (exitStore == null) { + // If there is no regular exitStore, then the method cannot reach the regular exit and + // there is no need to check anything. + } else { + CFAbstractValue value = exitStore.getValue(expression); + AnnotationMirror inferredAnno = null; + if (value != null) { + AnnotationMirrorSet annos = value.getAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); + } + if (!checkContract(expression, annotation, inferredAnno, exitStore)) { + checker.reportError( + methodTree, + "contracts.postcondition.not.satisfied", + methodTree.getName(), + contractExpressionAndType(expression.toString(), inferredAnno), + contractExpressionAndType(expression.toString(), annotation)); + } + } + } + + /** + * Returns a string representation of an expression and type qualifier. + * + * @param expression a Java expression + * @param qualifier the expression's type, or null if no information is available + * @return a string representation of the expression and type qualifier + */ + protected String contractExpressionAndType( + String expression, @Nullable AnnotationMirror qualifier) { + if (qualifier == null) { + return "no information about " + expression; + } else { + return expression + + " is " + + atypeFactory.getAnnotationFormatter().formatAnnotationMirror(qualifier); + } + } + + /** + * Check that the expression's type is annotated with {@code annotation} at every regular exit + * that returns {@code result}. + * + * @param methodTree tree of method with the postcondition + * @param annotation expression's type must have this annotation + * @param expression the expression that the postcondition concerns + * @param result result for which the postcondition is valid + */ + protected void checkConditionalPostcondition( + MethodTree methodTree, + AnnotationMirror annotation, + JavaExpression expression, + boolean result) { + boolean booleanReturnType = + TypesUtils.isBooleanType(TreeUtils.typeOf(methodTree.getReturnType())); + if (!booleanReturnType) { + checker.reportError( + methodTree, "contracts.conditional.postcondition.invalid.returntype"); + // No reason to go ahead with further checking. The + // annotation is invalid. + return; + } + + for (IPair pair : atypeFactory.getReturnStatementStores(methodTree)) { + ReturnNode returnStmt = pair.first; + + Node retValNode = returnStmt.getResult(); + Boolean retVal = + retValNode instanceof BooleanLiteralNode + ? ((BooleanLiteralNode) retValNode).getValue() + : null; + + TransferResult transferResult = (TransferResult) pair.second; + if (transferResult == null) { + // Unreachable return statements have no stores, but there is no need to check them. + continue; + } + CFAbstractStore exitStore = + (CFAbstractStore) + (result + ? transferResult.getThenStore() + : transferResult.getElseStore()); + CFAbstractValue value = exitStore.getValue(expression); + + // don't check if return statement certainly does not match 'result'. at the moment, + // this means the result is a boolean literal + if (!(retVal == null || retVal == result)) { + continue; + } + AnnotationMirror inferredAnno = null; + if (value != null) { + AnnotationMirrorSet annos = value.getAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, annotation); + } + + if (!checkContract(expression, annotation, inferredAnno, exitStore)) { + checker.reportError( + returnStmt.getTree(), + "contracts.conditional.postcondition.not.satisfied", + methodTree.getName(), + result, + contractExpressionAndType(expression.toString(), inferredAnno), + contractExpressionAndType(expression.toString(), annotation)); + } + } + } + + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void p) { + if (tree.getBounds().size() > 1) { + // The upper bound of the type parameter is an intersection + AnnotatedTypeVariable type = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tree); + AnnotatedIntersectionType intersection = + (AnnotatedIntersectionType) type.getUpperBound(); + checkExplicitAnnotationsOnIntersectionBounds(intersection, tree.getBounds()); + } + validateTypeOf(tree); + + return super.visitTypeParameter(tree, p); + } + + /** + * Issues "explicit.annotation.ignored" warning if any explicit annotation on an intersection + * bound is not the same as the primary annotation of the given intersection type. + * + * @param intersection type to use + * @param boundTrees trees of {@code intersection} bounds + */ + protected void checkExplicitAnnotationsOnIntersectionBounds( + AnnotatedIntersectionType intersection, List boundTrees) { + for (Tree boundTree : boundTrees) { + if (boundTree.getKind() != Tree.Kind.ANNOTATED_TYPE) { + continue; + } + List explictAnnos = + TreeUtils.annotationsFromTree((AnnotatedTypeTree) boundTree); + for (AnnotationMirror explictAnno : explictAnnos) { + if (atypeFactory.isSupportedQualifier(explictAnno)) { + AnnotationMirror anno = intersection.getAnnotationInHierarchy(explictAnno); + if (!AnnotationUtils.areSame(anno, explictAnno)) { + checker.reportWarning( + boundTree, + "explicit.annotation.ignored", + explictAnno, + anno, + explictAnno, + anno); + } + } + } + } + } + + // ********************************************************************** + // Assignment checkers and pseudo-assignments + // ********************************************************************** + + @Override + public Void visitVariable(VariableTree tree, Void p) { + warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); + + // VariableTree#getType returns null for binding variables from a DeconstructionPatternTree. + if (tree.getType() != null) { + visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getType()); + } + + AnnotatedTypeMirror variableType = atypeFactory.getAnnotatedTypeLhs(tree); + + atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(variableType, tree); + Element varElt = TreeUtils.elementFromDeclaration(tree); + if (varElt.getKind() == ElementKind.ENUM_CONSTANT) { + commonAssignmentCheck( + tree, tree.getInitializer(), "enum.declaration.type.incompatible"); + } else if (tree.getInitializer() != null) { + if (!TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { + // If there is no assignment in this variable declaration or it is declared using + // `var`, skip it. + // For a `var` declaration, TypeFromMemberVisitor#visitVariable already uses the + // type of the initializer for the variable type, so it would be redundant to check + // for compatibility here. + commonAssignmentCheck(tree, tree.getInitializer(), "assignment.type.incompatible"); + } + } else { + // commonAssignmentCheck validates the type of `tree`, + // so only validate if commonAssignmentCheck wasn't called + validateTypeOf(tree); + } + validateVariablesTargetLocation(tree, variableType); + warnRedundantAnnotations(tree, variableType); + return super.visitVariable(tree, p); + } + + /** + * Validate if the annotations on the VariableTree are at the right locations, which is + * specified by the meta-annotation @TargetLocations. The difference of this method between + * {@link BaseTypeVisitor#validateTargetLocation(Tree, AnnotatedTypeMirror, TypeUseLocation)} is + * that this one is only used in {@link BaseTypeVisitor#visitVariable(VariableTree, Void)} + * + * @param tree annotations on this VariableTree will be validated + * @param type the type of the tree + */ + protected void validateVariablesTargetLocation(Tree tree, AnnotatedTypeMirror type) { + if (ignoreTargetLocations) { + return; + } + Element element = TreeUtils.elementFromTree(tree); + + if (element != null) { + ElementKind elemKind = element.getKind(); + // TypeUseLocation.java doesn't have ENUM type use location right now. + for (AnnotationMirror am : type.getAnnotations()) { + List locations = + qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + if (locations == null || locations.contains(TypeUseLocation.ALL)) { + continue; + } + boolean issueError = true; + switch (elemKind) { + case LOCAL_VARIABLE: + if (locations.contains(TypeUseLocation.LOCAL_VARIABLE)) issueError = false; + break; + case EXCEPTION_PARAMETER: + if (locations.contains(TypeUseLocation.EXCEPTION_PARAMETER)) + issueError = false; + break; + case PARAMETER: + if (((VariableTree) tree).getName().contentEquals("this")) { + if (locations.contains(TypeUseLocation.RECEIVER)) { + issueError = false; + } + } else { + if (locations.contains(TypeUseLocation.PARAMETER)) { + issueError = false; + } + } + break; + case RESOURCE_VARIABLE: + if (locations.contains(TypeUseLocation.RESOURCE_VARIABLE)) { + issueError = false; + } + break; + case FIELD: + if (locations.contains(TypeUseLocation.FIELD)) { + issueError = false; + } + break; + case ENUM_CONSTANT: + if (locations.contains(TypeUseLocation.FIELD) + || locations.contains(TypeUseLocation.CONSTRUCTOR_RESULT)) { + issueError = false; + } + break; + default: + throw new BugInCF("Location not matched"); + } + if (issueError) { + checker.reportError( + tree, + "type.invalid.annotations.on.location", + am.toString(), + element.getKind().name()); + } + } + } + } + + /** + * Validate if the annotations on the tree are at the right locations, which are specified by + * the meta-annotation @TargetLocations. + * + * @param tree annotations on this VariableTree will be validated + * @param type the type of the tree + * @param required if all of the TypeUseLocations in {@code required} are not present in the + * specification of the annotation (@TargetLocations), issue an error. + */ + protected void validateTargetLocation( + Tree tree, AnnotatedTypeMirror type, TypeUseLocation required) { + if (ignoreTargetLocations) { + return; + } + for (AnnotationMirror am : type.getAnnotations()) { + List locations = + qualAllowedLocations.get(AnnotationUtils.annotationName(am)); + if (locations == null || locations.contains(TypeUseLocation.ALL)) { + continue; + } + boolean issueError = !locations.contains(required); + + if (issueError) { + checker.reportError( + tree, + "type.invalid.annotations.on.location", + am.toString(), + required.toString()); + } + } + } + + /** + * Create a new map, which is used for declared type-use locations lookup. + * + * @return a new mapping from strings of qualifier names to their declared type-use locations. + */ + protected Map<@CanonicalName String, List> createQualAllowedLocations() { + HashMap<@CanonicalName String, List> qualAllowedLocations = + new HashMap<>(); + for (String qual : atypeFactory.getSupportedTypeQualifierNames()) { + Element elem = elements.getTypeElement(qual); + TargetLocations tls = elem.getAnnotation(TargetLocations.class); + // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means that + // the qualifier can be written on any type use. + if (tls == null) { + qualAllowedLocations.put(qual, null); + continue; + } + List locations = Arrays.asList(tls.value()); + qualAllowedLocations.put(qual, locations); + } + return qualAllowedLocations; + } + + /** + * Issues a "redundant.anno" warning if the annotation written on the type is the same as the + * default annotation for this type and location. + * + * @param tree an AST node + * @param type get the explicit annotation on this type and compare it with the default one for + * this type and location. + */ + protected void warnRedundantAnnotations(Tree tree, AnnotatedTypeMirror type) { + // Type variable uses don't have default annotations. So, any explicit annotation is not + // redundant. + if (!warnRedundantAnnotations || (type.getKind() == TypeKind.TYPEVAR)) { + return; + } + AnnotationMirrorSet explicitAnnos = type.getExplicitAnnotations(); + if (explicitAnnos.isEmpty()) { + return; + } + if (tree == null) { + throw new BugInCF("unexpected null tree argument!"); + } + + AnnotatedTypeMirror defaultType = atypeFactory.getDefaultAnnotations(tree, type); + for (AnnotationMirror explicitAnno : explicitAnnos) { + AnnotationMirror defaultAM = defaultType.getAnnotationInHierarchy(explicitAnno); + if (AnnotationUtils.areSame(defaultAM, explicitAnno)) { + checker.reportWarning(tree, "redundant.anno", defaultAM); + } + } + } + + /** + * Warn if a type annotation is written before a modifier such as "public" or before a + * declaration annotation. + * + * @param tree a VariableTree or a MethodTree + * @param modifiersTree the modifiers sub-tree of tree + */ + private void warnAboutTypeAnnotationsTooEarly(Tree tree, ModifiersTree modifiersTree) { + + // Don't issue warnings about compiler-inserted modifiers. + // This simple code completely igonores enum constants and try-with-resources declarations. + // It could be made to catch some user errors in those locations, but it doesn't seem worth + // the effort to do so. + if (tree.getKind() == Tree.Kind.VARIABLE) { + ElementKind varKind = TreeUtils.elementFromDeclaration((VariableTree) tree).getKind(); + switch (varKind) { + case ENUM_CONSTANT: + // Enum constants are "public static final" by default, so the annotation always + // appears to be before "public". + return; + case RESOURCE_VARIABLE: + // Try-with-resources variables are "final" by default, so the annotation always + // appears to be before "final". + return; + default: + if (TreeUtils.isAutoGeneratedRecordMember(tree)) { + // Annotations can appear on record fields before the class body, so don't + // issue a warning about those. + return; + } + // Nothing to do + } + } + + Set modifierSet = modifiersTree.getFlags(); + List annotations = modifiersTree.getAnnotations(); + + if (annotations.isEmpty()) { + return; + } + + // Warn about type annotations written before modifiers such as "public". javac retains no + // information about modifier locations. So, this is a very partial check: Issue a warning + // if a type annotation is at the very beginning of the VariableTree, and a modifier follows + // it. + + // Check if a type annotation precedes a declaration annotation. + int lastDeclAnnoIndex = -1; + for (int i = annotations.size() - 1; i > 0; i--) { // no need to check index 0 + if (!isTypeAnnotation(annotations.get(i))) { + lastDeclAnnoIndex = i; + break; + } + } + if (lastDeclAnnoIndex != -1) { + // Usually, there are few bad invariant annotations. + List badTypeAnnos = new ArrayList<>(2); + for (int i = 0; i < lastDeclAnnoIndex; i++) { + AnnotationTree anno = annotations.get(i); + if (isTypeAnnotation(anno)) { + badTypeAnnos.add(anno); + } + } + if (!badTypeAnnos.isEmpty()) { + checker.reportWarning( + tree, + "type.anno.before.decl.anno", + badTypeAnnos, + annotations.get(lastDeclAnnoIndex)); + } + } + + // Determine the length of the text that ought to precede the first type annotation. + // If the type annotation appears before that text could appear, then warn that a + // modifier appears after the type annotation. + // TODO: in the future, account for the lengths of declaration annotations. Length of + // toString of the annotation isn't useful, as it might be different length than original + // input. Can use JCTree.getEndPosition(EndPosTable) and + // com.sun.tools.javac.tree.EndPosTable, but it requires -Xjcov. + AnnotationTree firstAnno = annotations.get(0); + if (!modifierSet.isEmpty() && isTypeAnnotation(firstAnno)) { + int precedingTextLength = 0; + for (Modifier m : modifierSet) { + precedingTextLength += m.toString().length() + 1; // +1 for the space + } + int annoStartPos = ((JCTree) firstAnno).getStartPosition(); + int varStartPos = ((JCTree) tree).getStartPosition(); + if (annoStartPos < varStartPos + precedingTextLength) { + checker.reportWarning(tree, "type.anno.before.modifier", firstAnno, modifierSet); + } + } + } + + /** + * Return true if the given annotation is a type annotation: that is, its definition is + * meta-annotated with {@code @Target({TYPE_USE,....})}. + */ + private boolean isTypeAnnotation(AnnotationTree anno) { + Tree annoType = anno.getAnnotationType(); + ClassSymbol annoSymbol; + switch (annoType.getKind()) { + case IDENTIFIER: + annoSymbol = (ClassSymbol) ((JCIdent) annoType).sym; + break; + case MEMBER_SELECT: + annoSymbol = (ClassSymbol) ((JCFieldAccess) annoType).sym; + break; + default: + throw new BugInCF("Unhandled kind: " + annoType.getKind() + " for " + anno); + } + for (AnnotationMirror metaAnno : annoSymbol.getAnnotationMirrors()) { + if (AnnotationUtils.areSameByName(metaAnno, TARGET)) { + AnnotationValue av = metaAnno.getElementValues().get(targetValueElement); + return AnnotationUtils.annotationValueContainsToString(av, "TYPE_USE"); + } + } + + return false; + } + + /** + * Performs two checks: subtyping and assignability checks, using {@link + * #commonAssignmentCheck(Tree, ExpressionTree, String, Object[])}. + * + *

          If the subtype check fails, it issues an "assignment.type.incompatible" error. + */ + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + commonAssignmentCheck( + tree.getVariable(), tree.getExpression(), "assignment.type.incompatible"); + return super.visitAssignment(tree, p); + } + + /** + * Performs a subtype check, to test whether the tree expression iterable type is a subtype of + * the variable type in the enhanced for loop. + * + *

          If the subtype check fails, it issues a "enhancedfor.type.incompatible" error. + */ + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(tree.getVariable()); + AnnotatedTypeMirror iteratedType = + atypeFactory.getIterableElementType(tree.getExpression()); + boolean valid = validateTypeOf(tree.getVariable()); + if (valid) { + commonAssignmentCheck( + var, iteratedType, tree.getExpression(), "enhancedfor.type.incompatible"); + } + return super.visitEnhancedForLoop(tree, p); + } + + /** + * Performs a method invocation check. + * + *

          An invocation of a method, m, on the receiver, r is valid only if: + * + *

            + *
          • passed arguments are subtypes of corresponding m parameters + *
          • r is a subtype of m receiver type + *
          • if m is generic, passed type arguments are subtypes of m type variables + *
          + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + + // Skip calls to the Enum constructor (they're generated by javac and + // hard to check), also see CFGBuilder.visitMethodInvocation. + if (TreeUtils.elementFromUse(tree) == null || TreeUtils.isEnumSuperCall(tree)) { + return super.visitMethodInvocation(tree, p); + } + + if (shouldSkipUses(tree)) { + return super.visitMethodInvocation(tree, p); + } + ParameterizedExecutableType preInference = + atypeFactory.methodFromUseWithoutTypeArgInference(tree); + if (!preInference.executableType.getElement().getTypeParameters().isEmpty() + && preInference.typeArgs.isEmpty()) { + if (!checkTypeArgumentInference(tree, preInference.executableType)) { + return null; + } + } + ParameterizedExecutableType mType = atypeFactory.methodFromUse(tree); + AnnotatedExecutableType invokedMethod = mType.executableType; + List typeargs = mType.typeArgs; + + List paramBounds = + CollectionsPlume.mapList( + AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables()); + + ExecutableElement method = invokedMethod.getElement(); + CharSequence methodName = ElementUtils.getSimpleDescription(method); + checkTypeArguments( + tree, + paramBounds, + typeargs, + tree.getTypeArguments(), + methodName, + invokedMethod.getTypeVariables()); + List params = + AnnotatedTypes.adaptParameters( + atypeFactory, invokedMethod, tree.getArguments(), null); + checkArguments(params, tree.getArguments(), methodName, method.getParameters()); + checkVarargs(invokedMethod, tree); + + if (ElementUtils.isMethod( + invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) { + typeCheckVectorCopyIntoArgument(tree, params); + } + + ExecutableElement invokedMethodElement = invokedMethod.getElement(); + if (!ElementUtils.isStatic(invokedMethodElement) + && !TreeUtils.isSuperConstructorCall(tree)) { + checkMethodInvocability(invokedMethod, tree); + } + + // check precondition annotations + checkPreconditions( + tree, atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement)); + + if (TreeUtils.isSuperConstructorCall(tree)) { + checkSuperConstructorCall(tree); + } else if (TreeUtils.isThisConstructorCall(tree)) { + checkThisConstructorCall(tree); + } + + // Do not call super, as that would observe the arguments without + // a set assignment context. + scan(tree.getMethodSelect(), p); + return null; // super.visitMethodInvocation(tree, p); + } + + /** + * Reports a "type.arguments.not.inferred" error if type argument inference fails and returns + * false if inference fails. + * + * @param tree a tree that requires type argument inference + * @param methodType the type of the method before type argument substitution + * @return whether type argument inference succeeds + */ + private boolean checkTypeArgumentInference( + ExpressionTree tree, AnnotatedExecutableType methodType) { + InferenceResult args = + atypeFactory + .getTypeArgumentInference() + .inferTypeArgs(atypeFactory, tree, methodType); + if (args != null && !args.inferenceFailed()) { + return true; + } + checker.reportError( + tree, + "type.arguments.not.inferred", + ElementUtils.getSimpleDescription(methodType.getElement()), + args == null ? "" : args.getErrorMsg()); + return false; + } + + /** + * Checks that the following rule is satisfied: The type on a constructor declaration must be a + * supertype of the return type of "this()" invocation within that constructor. + * + *

          Subclasses can override this method to change the behavior for just "this" constructor + * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to + * change the behavior for "this" and "super" constructor calls. + * + * @param thisCall the AST node for the constructor call + */ + protected void checkThisConstructorCall(MethodInvocationTree thisCall) { + checkThisOrSuperConstructorCall(thisCall, "this.invocation.invalid"); + } + + /** + * Checks that the following rule is satisfied: The type on a constructor declaration must be a + * supertype of the return type of "super()" invocation within that constructor. + * + *

          Subclasses can override this method to change the behavior for just "super" constructor + * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to + * change the behavior for "this" and "super" constructor calls. + * + * @param superCall the AST node for the super constructor call + */ + protected void checkSuperConstructorCall(MethodInvocationTree superCall) { + checkThisOrSuperConstructorCall(superCall, "super.invocation.invalid"); + } + + /** + * Checks that the following rule is satisfied: The type on a constructor declaration must be a + * supertype of the return type of "this()" or "super()" invocation within that constructor. + * + * @param call the AST node for the constructor call + * @param errorKey the error message key to use if the check fails + */ + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree call, @CompilerMessageKey String errorKey) { + TreePath path = atypeFactory.getPath(call); + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path); + AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(call); + AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); + AnnotatedTypeMirror returnType = constructorType.getReturnType(); + AnnotationMirrorSet topAnnotations = qualHierarchy.getTopAnnotations(); + for (AnnotationMirror topAnno : topAnnotations) { + if (!typeHierarchy.isSubtypeShallowEffective(superType, returnType, topAnno)) { + AnnotationMirror superAnno = superType.getAnnotationInHierarchy(topAnno); + AnnotationMirror constructorReturnAnno = + returnType.getAnnotationInHierarchy(topAnno); + checker.reportError(call, errorKey, constructorReturnAnno, call, superAnno); + } + } + } + + /** + * If the given invocation is a varargs invocation, check that the array type of actual varargs + * is a subtype of the corresponding formal parameter; issues "argument.invalid" error if not. + * + *

          The caller must type-check for each element in varargs before or after calling this + * method. + * + * @see #checkArguments + * @param invokedMethod the method type to be invoked + * @param tree method or constructor invocation tree + */ + protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) { + if (!TreeUtils.isVarArgs(tree)) { + // If not a varargs invocation, type checking is already done in checkArguments. + return; + } + + // This is the varags type, an array. + AnnotatedArrayType lastParamAnnotatedType = invokedMethod.getVarargType(); + + AnnotatedTypeMirror wrappedVarargsType = atypeFactory.getAnnotatedTypeVarargsArray(tree); + + // When dataflow analysis is not enabled, it will be null and we can suppose there is no + // annotation to be checked for generated varargs array. + if (wrappedVarargsType == null) { + return; + } + + // The component type of wrappedVarargsType might not be a subtype of the component type of + // lastParamAnnotatedType due to the difference of type inference between for an expression + // and an invoked method element. We can consider that the component type of actual is same + // with formal one because type checking for elements will be done in checkArguments. This + // is also needed to avoid duplicating error message caused by elements in varargs. + if (wrappedVarargsType.getKind() == TypeKind.ARRAY) { + ((AnnotatedArrayType) wrappedVarargsType) + .setComponentType(lastParamAnnotatedType.getComponentType()); + } + + commonAssignmentCheck( + lastParamAnnotatedType, wrappedVarargsType, tree, "varargs.type.incompatible"); + } + + /** + * Checks that all the given {@code preconditions} hold true immediately prior to the method + * invocation or variable access at {@code tree}. + * + * @param tree the method invocation; immediately prior to it, the preconditions must hold true + * @param preconditions the preconditions to be checked + */ + protected void checkPreconditions(MethodInvocationTree tree, Set preconditions) { + // This check is needed for the GUI effects and Units Checkers tests to pass. + // TODO: Remove this check and investigate the root cause. + if (preconditions.isEmpty()) { + return; + } + + StringToJavaExpression stringToJavaExpr = + stringExpr -> StringToJavaExpression.atMethodInvocation(stringExpr, tree, checker); + for (Contract c : preconditions) { + Precondition p = (Precondition) c; + String expressionString = p.expressionString; + AnnotationMirror anno = + c.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, tree); + JavaExpression exprJe; + try { + exprJe = StringToJavaExpression.atMethodInvocation(expressionString, tree, checker); + } catch (JavaExpressionParseException e) { + // report errors here + checker.report(tree, e.getDiagMessage()); + return; + } + + CFAbstractStore store = atypeFactory.getStoreBefore(tree); + + AnnotationMirrorSet annos = + atypeFactory.getAnnotatedTypeBefore(exprJe, tree).getAnnotations(); + + AnnotationMirror inferredAnno = + qualHierarchy.findAnnotationInSameHierarchy(annos, anno); + + // If the expression is "this", then get the type of the method receiver. + // TODO: There are other expressions that can be converted to trees, "#1" for + // example. + if (expressionString.equals("this") + && qualHierarchy.getTopAnnotations().contains(inferredAnno)) { + AnnotatedTypeMirror atype = atypeFactory.getReceiverType(tree); + if (atype != null) { + annos = atype.getEffectiveAnnotations(); + inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, anno); + } + } + + if (!checkContract(exprJe, anno, inferredAnno, store)) { + if (exprJe != null) { + expressionString = exprJe.toString(); + } + checker.reportError( + tree, + "contracts.precondition.not.satisfied", + tree.getMethodSelect().toString(), + contractExpressionAndType(expressionString, inferredAnno), + contractExpressionAndType(expressionString, anno)); + } + } + } + + /** + * Returns true if and only if {@code inferredAnnotation} is valid for a given expression to + * match the {@code necessaryAnnotation}. + * + *

          By default, {@code inferredAnnotation} must be a subtype of {@code necessaryAnnotation}, + * but subclasses might override this behavior. + */ + protected boolean checkContract( + JavaExpression expr, + AnnotationMirror necessaryAnnotation, + AnnotationMirror inferredAnnotation, + CFAbstractStore store) { + if (inferredAnnotation == null) { + return false; + } + TypeMirror exprTM = expr.getType(); + return qualHierarchy.isSubtypeShallow(inferredAnnotation, necessaryAnnotation, exprTM); + } + + /** + * Type checks the method arguments of {@code Vector.copyInto()}. + * + *

          The Checker Framework special-cases the method invocation, as its type safety cannot be + * expressed by Java's type system. + * + *

          For a Vector {@code v} of type {@code Vector}, the method invocation {@code + * v.copyInto(arr)} is type-safe iff {@code arr} is an array of type {@code T[]}, where {@code + * T} is a subtype of {@code E}. + * + *

          In other words, this method checks that the type argument of the receiver method is a + * subtype of the component type of the passed array argument. + * + * @param tree a method invocation of {@code Vector.copyInto()} + * @param params the types of the parameters of {@code Vectory.copyInto()} + */ + protected void typeCheckVectorCopyIntoArgument( + MethodInvocationTree tree, List params) { + assert params.size() == 1 + : "invalid no. of parameters " + params + " found for method invocation " + tree; + assert tree.getArguments().size() == 1 + : "invalid no. of arguments in method invocation " + tree; + + AnnotatedTypeMirror passed = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedArrayType passedAsArray = (AnnotatedArrayType) passed; + + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + AnnotatedDeclaredType receiverAsVector = + AnnotatedTypes.asSuper(atypeFactory, receiver, vectorType); + if (receiverAsVector.getTypeArguments().isEmpty()) { + return; + } - if (!checkContract(expression, annotation, inferredAnno, exitStore)) { - checker.reportError( - returnStmt.getTree(), - "contracts.conditional.postcondition.not.satisfied", - methodTree.getName(), - result, - contractExpressionAndType(expression.toString(), inferredAnno), - contractExpressionAndType(expression.toString(), annotation)); - } + AnnotatedTypeMirror argComponent = passedAsArray.getComponentType(); + AnnotatedTypeMirror vectorTypeArg = receiverAsVector.getTypeArguments().get(0); + Tree errorLocation = tree.getArguments().get(0); + if (TypesUtils.isErasedSubtype( + vectorTypeArg.getUnderlyingType(), argComponent.getUnderlyingType(), types)) { + commonAssignmentCheck( + argComponent, + vectorTypeArg, + errorLocation, + "vector.copyinto.type.incompatible"); + } else { + checker.reportError( + errorLocation, + "vector.copyinto.type.incompatible", + vectorTypeArg, + argComponent); + } } - } - - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void p) { - if (tree.getBounds().size() > 1) { - // The upper bound of the type parameter is an intersection - AnnotatedTypeVariable type = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tree); - AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type.getUpperBound(); - checkExplicitAnnotationsOnIntersectionBounds(intersection, tree.getBounds()); + + /** + * Performs a new class invocation check. + * + *

          An invocation of a constructor, c, is valid only if: + * + *

            + *
          • passed arguments are subtypes of corresponding c parameters + *
          • if c is generic, passed type arguments are subtypes of c type variables + *
          + */ + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + if (checker.shouldSkipUses(TreeUtils.elementFromUse(tree))) { + return super.visitNewClass(tree, p); + } + + ParameterizedExecutableType preInference = + atypeFactory.constructorFromUseWithoutTypeArgInference(tree); + if (!preInference.executableType.getElement().getTypeParameters().isEmpty() + || TreeUtils.isDiamondTree(tree)) { + if (!checkTypeArgumentInference(tree, preInference.executableType)) { + return null; + } + } + + ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(tree); + AnnotatedExecutableType constructorType = fromUse.executableType; + List typeargs = fromUse.typeArgs; + + // Type check inner class enclosing expr type + checkEnclosingExpr(tree, constructorType); + List passedArguments = tree.getArguments(); + List params = constructorType.getParameterTypes(); + + ExecutableElement constructor = constructorType.getElement(); + CharSequence constructorName = ElementUtils.getSimpleDescription(constructor); + + checkArguments(params, passedArguments, constructorName, constructor.getParameters()); + checkVarargs(constructorType, tree); + + List paramBounds = + CollectionsPlume.mapList( + AnnotatedTypeVariable::getBounds, constructorType.getTypeVariables()); + + checkTypeArguments( + tree, + paramBounds, + typeargs, + tree.getTypeArguments(), + constructorName, + constructor.getTypeParameters()); + + boolean valid = validateTypeOf(tree); + + if (valid) { + AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(tree); + atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(dt, tree); + checkConstructorInvocation(dt, constructorType, tree); + } + // Do not call super, as that would observe the arguments without + // a set assignment context. + scan(tree.getEnclosingExpression(), p); + scan(tree.getIdentifier(), p); + scan(tree.getClassBody(), p); + + return null; } - validateTypeOf(tree); - - return super.visitTypeParameter(tree, p); - } - - /** - * Issues "explicit.annotation.ignored" warning if any explicit annotation on an intersection - * bound is not the same as the primary annotation of the given intersection type. - * - * @param intersection type to use - * @param boundTrees trees of {@code intersection} bounds - */ - protected void checkExplicitAnnotationsOnIntersectionBounds( - AnnotatedIntersectionType intersection, List boundTrees) { - for (Tree boundTree : boundTrees) { - if (boundTree.getKind() != Tree.Kind.ANNOTATED_TYPE) { - continue; - } - List explictAnnos = - TreeUtils.annotationsFromTree((AnnotatedTypeTree) boundTree); - for (AnnotationMirror explictAnno : explictAnnos) { - if (atypeFactory.isSupportedQualifier(explictAnno)) { - AnnotationMirror anno = intersection.getAnnotationInHierarchy(explictAnno); - if (!AnnotationUtils.areSame(anno, explictAnno)) { - checker.reportWarning( - boundTree, "explicit.annotation.ignored", explictAnno, anno, explictAnno, anno); - } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + + AnnotatedExecutableType functionType = atypeFactory.getFunctionTypeFromTree(tree); + + if (tree.getBody().getKind() != Tree.Kind.BLOCK) { + // Check return type for single statement returns here. + AnnotatedTypeMirror ret = functionType.getReturnType(); + if (ret.getKind() != TypeKind.VOID) { + commonAssignmentCheck( + ret, (ExpressionTree) tree.getBody(), "return.type.incompatible"); + } } - } + + // Check parameters + for (int i = 0; i < functionType.getParameterTypes().size(); ++i) { + AnnotatedTypeMirror lambdaParameter = + atypeFactory.getAnnotatedType(tree.getParameters().get(i)); + commonAssignmentCheck( + lambdaParameter, + functionType.getParameterTypes().get(i), + tree.getParameters().get(i), + "lambda.param.type.incompatible", + i); + } + + // TODO: Postconditions? + // https://github.com/typetools/checker-framework/issues/801 + + return super.visitLambdaExpression(tree, p); + } + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void p) { + this.checkMethodReferenceAsOverride(tree, p); + return super.visitMemberReference(tree, p); } - } - // ********************************************************************** - // Assignment checkers and pseudo-assignments - // ********************************************************************** + /** A set containing {@code Tree.Kind.METHOD} and {@code Tree.Kind.LAMBDA_EXPRESSION}. */ + private final ArraySet methodAndLambdaExpression = + new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); + + /** + * Checks that the type of the return expression is a subtype of the enclosing method required + * return type. If not, it issues a "return.type.incompatible" error. + */ + @Override + public Void visitReturn(ReturnTree tree, Void p) { + // Don't try to check return expressions for void methods. + if (tree.getExpression() == null) { + return super.visitReturn(tree, p); + } + + Tree enclosing = TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); - @Override - public Void visitVariable(VariableTree tree, Void p) { - warnAboutTypeAnnotationsTooEarly(tree, tree.getModifiers()); + AnnotatedTypeMirror ret = null; + if (enclosing.getKind() == Tree.Kind.METHOD) { + + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + boolean valid = validateTypeOf(enclosing); + if (valid) { + ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); + } + } else { + AnnotatedExecutableType result = + atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); + ret = result.getReturnType(); + } - // VariableTree#getType returns null for binding variables from a DeconstructionPatternTree. - if (tree.getType() != null) { - visitAnnotatedType(tree.getModifiers().getAnnotations(), tree.getType()); + if (ret != null) { + commonAssignmentCheck(ret, tree.getExpression(), "return.type.incompatible"); + } + return super.visitReturn(tree, p); } - AnnotatedTypeMirror variableType = atypeFactory.getAnnotatedTypeLhs(tree); - - atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(variableType, tree); - Element varElt = TreeUtils.elementFromDeclaration(tree); - if (varElt.getKind() == ElementKind.ENUM_CONSTANT) { - commonAssignmentCheck(tree, tree.getInitializer(), "enum.declaration.type.incompatible"); - } else if (tree.getInitializer() != null) { - if (!TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { - // If there is no assignment in this variable declaration or it is declared using - // `var`, skip it. - // For a `var` declaration, TypeFromMemberVisitor#visitVariable already uses the - // type of the initializer for the variable type, so it would be redundant to check - // for compatibility here. - commonAssignmentCheck(tree, tree.getInitializer(), "assignment.type.incompatible"); - } - } else { - // commonAssignmentCheck validates the type of `tree`, - // so only validate if commonAssignmentCheck wasn't called - validateTypeOf(tree); + /** + * Ensure that the annotation arguments comply to their declarations. This needs some special + * casing, as annotation arguments form special trees. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + List args = tree.getArguments(); + if (args.isEmpty()) { + // Nothing to do if there are no annotation arguments. + return null; + } + + TypeElement annoType = (TypeElement) TreeInfo.symbol((JCTree) tree.getAnnotationType()); + + Name annoName = annoType.getQualifiedName(); + if (annoName.contentEquals(DefaultQualifier.class.getName()) + || annoName.contentEquals(SuppressWarnings.class.getName())) { + // Skip these two annotations, as we don't care about the arguments to them. + return null; + } + + List methods = ElementFilter.methodsIn(annoType.getEnclosedElements()); + // Mapping from argument simple name to its annotated type. + Map annoTypes = ArrayMap.newArrayMapOrHashMap(methods.size()); + for (ExecutableElement meth : methods) { + AnnotatedExecutableType exeatm = atypeFactory.getAnnotatedType(meth); + AnnotatedTypeMirror retty = exeatm.getReturnType(); + annoTypes.put(meth.getSimpleName().toString(), retty); + } + + for (ExpressionTree arg : args) { + if (!(arg instanceof AssignmentTree)) { + // TODO: when can this happen? + continue; + } + + AssignmentTree at = (AssignmentTree) arg; + // Ensure that we never ask for the annotated type of an annotation, because + // we don't have a type for annotations. + if (at.getExpression().getKind() == Tree.Kind.ANNOTATION) { + visitAnnotation((AnnotationTree) at.getExpression(), p); + continue; + } + if (at.getExpression().getKind() == Tree.Kind.NEW_ARRAY) { + NewArrayTree nat = (NewArrayTree) at.getExpression(); + boolean isAnno = false; + for (ExpressionTree init : nat.getInitializers()) { + if (init.getKind() == Tree.Kind.ANNOTATION) { + visitAnnotation((AnnotationTree) init, p); + isAnno = true; + } + } + if (isAnno) { + continue; + } + } + + AnnotatedTypeMirror expected = annoTypes.get(at.getVariable().toString()); + AnnotatedTypeMirror actual = atypeFactory.getAnnotatedType(at.getExpression()); + if (expected.getKind() != TypeKind.ARRAY) { + // Expected is not an array -> direct comparison. + commonAssignmentCheck( + expected, actual, at.getExpression(), "annotation.type.incompatible"); + } else if (actual.getKind() == TypeKind.ARRAY) { + // Both actual and expected are arrays. + commonAssignmentCheck( + expected, actual, at.getExpression(), "annotation.type.incompatible"); + } else { + // The declaration is an array type, but just a single element is given. + commonAssignmentCheck( + ((AnnotatedArrayType) expected).getComponentType(), + actual, + at.getExpression(), + "annotation.type.incompatible"); + } + } + return null; + } + + /** + * If the computation of the type of the ConditionalExpressionTree in + * org.checkerframework.framework.type.TypeFromTree.TypeFromExpression.visitConditionalExpression(ConditionalExpressionTree, + * AnnotatedTypeFactory) is correct, the following checks are redundant. However, let's add + * another failsafe guard and do the checks. + */ + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + if (TreeUtils.isPolyExpression(tree)) { + // From the JLS: + // A poly reference conditional expression is compatible with a target type T if its + // second and third operand expressions are compatible with T. In the Checker + // Framework this check happens in #commonAssignmentCheck. + return super.visitConditionalExpression(tree, p); + } + + AnnotatedTypeMirror cond = atypeFactory.getAnnotatedType(tree); + this.commonAssignmentCheck(cond, tree.getTrueExpression(), "conditional.type.incompatible"); + this.commonAssignmentCheck( + cond, tree.getFalseExpression(), "conditional.type.incompatible"); + return super.visitConditionalExpression(tree, p); } - validateVariablesTargetLocation(tree, variableType); - warnRedundantAnnotations(tree, variableType); - return super.visitVariable(tree, p); - } - - /** - * Validate if the annotations on the VariableTree are at the right locations, which is specified - * by the meta-annotation @TargetLocations. The difference of this method between {@link - * BaseTypeVisitor#validateTargetLocation(Tree, AnnotatedTypeMirror, TypeUseLocation)} is that - * this one is only used in {@link BaseTypeVisitor#visitVariable(VariableTree, Void)} - * - * @param tree annotations on this VariableTree will be validated - * @param type the type of the tree - */ - protected void validateVariablesTargetLocation(Tree tree, AnnotatedTypeMirror type) { - if (ignoreTargetLocations) { - return; + + /** + * This method validates the type of the switch expression. It issues an error if the type of a + * value that the switch expression can result is not a subtype of the switch type. + * + *

          If a subclass overrides this method, it must call {@code super.scan(switchExpressionTree, + * null)} so that the blocks and statements in the cases are checked. + * + * @param switchExpressionTree a {@code SwitchExpressionTree} + */ + public void visitSwitchExpression17(Tree switchExpressionTree) { + boolean valid = validateTypeOf(switchExpressionTree); + if (valid) { + AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(switchExpressionTree); + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree valueTree, Void unused) -> { + BaseTypeVisitor.this.commonAssignmentCheck( + switchType, + valueTree, + "switch.expression.type.incompatible"); + return null; + }, + (r1, r2) -> null); + + scanner.scanSwitchExpression(switchExpressionTree, null); + } + super.scan(switchExpressionTree, null); } - Element element = TreeUtils.elementFromTree(tree); - - if (element != null) { - ElementKind elemKind = element.getKind(); - // TypeUseLocation.java doesn't have ENUM type use location right now. - for (AnnotationMirror am : type.getAnnotations()) { - List locations = - qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - if (locations == null || locations.contains(TypeUseLocation.ALL)) { - continue; - } - boolean issueError = true; - switch (elemKind) { - case LOCAL_VARIABLE: - if (locations.contains(TypeUseLocation.LOCAL_VARIABLE)) issueError = false; - break; - case EXCEPTION_PARAMETER: - if (locations.contains(TypeUseLocation.EXCEPTION_PARAMETER)) issueError = false; - break; - case PARAMETER: - if (((VariableTree) tree).getName().contentEquals("this")) { - if (locations.contains(TypeUseLocation.RECEIVER)) { - issueError = false; - } + + // ********************************************************************** + // Check for illegal re-assignment + // ********************************************************************** + + /** Performs assignability check. */ + @Override + public Void visitUnary(UnaryTree tree, Void p) { + Tree.Kind treeKind = tree.getKind(); + if (treeKind == Tree.Kind.PREFIX_DECREMENT + || treeKind == Tree.Kind.PREFIX_INCREMENT + || treeKind == Tree.Kind.POSTFIX_DECREMENT + || treeKind == Tree.Kind.POSTFIX_INCREMENT) { + // Check the assignment that occurs at the increment/decrement. i.e.: + // exp = exp + 1 or exp = exp - 1 + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(tree.getExpression()); + AnnotatedTypeMirror valueType; + if (treeKind == Tree.Kind.POSTFIX_DECREMENT + || treeKind == Tree.Kind.POSTFIX_INCREMENT) { + // For postfixed increments or decrements, the type of the tree the type of the + // expression before 1 is added or subtracted. So, use a special method to get the + // type after 1 has been added or subtracted. + valueType = atypeFactory.getAnnotatedTypeRhsUnaryAssign(tree); } else { - if (locations.contains(TypeUseLocation.PARAMETER)) { - issueError = false; - } + // For prefixed increments or decrements, the type of the tree the type of the + // expression after 1 is added or subtracted. So, its type can be found using the + // usual method. + valueType = atypeFactory.getAnnotatedType(tree); } - break; - case RESOURCE_VARIABLE: - if (locations.contains(TypeUseLocation.RESOURCE_VARIABLE)) { - issueError = false; + String errorKey = + (treeKind == Tree.Kind.PREFIX_INCREMENT + || treeKind == Tree.Kind.POSTFIX_INCREMENT) + ? "unary.increment.type.incompatible" + : "unary.decrement.type.incompatible"; + commonAssignmentCheck(varType, valueType, tree, errorKey); + } + return super.visitUnary(tree, p); + } + + /** Performs assignability check. */ + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + // If tree is the tree representing the compounds assignment s += expr, + // Then this method should check whether s + expr can be assigned to s, + // but the "s + expr" tree does not exist. So instead, check that + // s += expr can be assigned to s. + commonAssignmentCheck(tree.getVariable(), tree, "compound.assignment.type.incompatible"); + return super.visitCompoundAssignment(tree, p); + } + + // ********************************************************************** + // Check for invalid types inserted by the user + // ********************************************************************** + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + boolean valid = validateTypeOf(tree); + + if (valid && tree.getType() != null) { + AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(tree); + atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(arrayType, tree); + if (tree.getInitializers() != null) { + checkArrayInitialization(arrayType.getComponentType(), tree.getInitializers()); } - break; - case FIELD: - if (locations.contains(TypeUseLocation.FIELD)) { - issueError = false; + } + + return super.visitNewArray(tree, p); + } + + /** + * If the lint option "cast:redundant" is set, this method issues a warning if the cast is + * redundant. + */ + protected void checkTypecastRedundancy(TypeCastTree typeCastTree) { + if (!checker.getLintOption("cast:redundant", false)) { + return; + } + + AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); + + if (castType.equals(exprType)) { + checker.reportWarning(typeCastTree, "cast.redundant", castType); + } + } + + /** + * Issues a warning if the given explicitly-written typecast is unsafe. Does nothing if the lint + * option "cast:unsafe" is not set. Only primary qualifiers are checked unless the command line + * option "checkCastElementType" is supplied. + * + * @param typeCastTree an explicitly-written typecast + */ + protected void checkTypecastSafety(TypeCastTree typeCastTree) { + if (!checker.getLintOption("cast:unsafe", true)) { + return; + } + AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); + boolean reported = false; + for (AnnotationMirror top : atypeFactory.getQualifierParameterHierarchies(castType)) { + if (!isTypeCastSafeInvariant(castType, exprType, top)) { + checker.reportError( + typeCastTree, + "invariant.cast.unsafe", + exprType.toString(true), + castType.toString(true)); } - break; - case ENUM_CONSTANT: - if (locations.contains(TypeUseLocation.FIELD) - || locations.contains(TypeUseLocation.CONSTRUCTOR_RESULT)) { - issueError = false; + reported = true; // don't issue cast unsafe warning. + } + + // Don't call TypeHierarchy#isSubtype(exprType, castType) because the underlying Java types + // will not be in the correct subtyping relationship if this is a downcast. + if (!reported && !isTypeCastSafe(castType, exprType)) { + checker.reportWarning( + typeCastTree, "cast.unsafe", exprType.toString(true), castType.toString(true)); + } + } + + /** + * Returns true if the cast is safe. + * + *

          Only primary qualifiers are checked unless the command line option "checkCastElementType" + * is supplied. + * + * @param castType annotated type of the cast + * @param exprType annotated type of the casted expression + * @return true if the type cast is safe, false otherwise + */ + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + TypeKind castTypeKind = castType.getKind(); + if (castTypeKind == TypeKind.DECLARED) { + // Don't issue an error if the annotations are equivalent to the qualifier upper bound + // of the type. + AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType; + AnnotationMirrorSet bounds = + atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType()); + + if (AnnotationUtils.areSame(castDeclared.getAnnotations(), bounds)) { + return true; } - break; - default: - throw new BugInCF("Location not matched"); } - if (issueError) { - checker.reportError( - tree, - "type.invalid.annotations.on.location", - am.toString(), - element.getKind().name()); + + AnnotationMirrorSet castAnnos; + AnnotatedTypeMirror newCastType; + TypeMirror newCastTM; + if (!checkCastElementType) { + // checkCastElementType option wasn't specified, so only check effective annotations. + castAnnos = castType.getEffectiveAnnotations(); + newCastType = castType; + newCastTM = newCastType.getUnderlyingType(); + } else { + if (castTypeKind == TypeKind.TYPEVAR) { + newCastType = ((AnnotatedTypeVariable) castType).getUpperBound(); + } else { + newCastType = castType; + } + newCastTM = newCastType.getUnderlyingType(); + AnnotatedTypeMirror newExprType; + if (exprType.getKind() == TypeKind.TYPEVAR) { + newExprType = ((AnnotatedTypeVariable) exprType).getUpperBound(); + } else { + newExprType = exprType; + } + TypeMirror newExprTM = newExprType.getUnderlyingType(); + + if (!typeHierarchy.isSubtype(newExprType, newCastType)) { + return false; + } + if (newCastType.getKind() == TypeKind.ARRAY + && newExprType.getKind() != TypeKind.ARRAY) { + // Always warn if the cast contains an array, but the expression + // doesn't, as in "(Object[]) o" where o is of type Object + return false; + } else if (newCastType.getKind() == TypeKind.DECLARED + && newExprType.getKind() == TypeKind.DECLARED) { + int castSize = ((AnnotatedDeclaredType) newCastType).getTypeArguments().size(); + int exprSize = ((AnnotatedDeclaredType) newExprType).getTypeArguments().size(); + + if (castSize != exprSize) { + // Always warn if the cast and expression contain a different number of type + // arguments, e.g. to catch a cast from "Object" to "List<@NonNull Object>". + // TODO: the same number of arguments actually doesn't guarantee anything. + return false; + } + } else if (castTypeKind == TypeKind.TYPEVAR && exprType.getKind() == TypeKind.TYPEVAR) { + // If both the cast type and the casted expression are type variables, then check + // the bounds. + AnnotationMirrorSet lowerBoundAnnotationsCast = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); + AnnotationMirrorSet lowerBoundAnnotationsExpr = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, exprType); + return qualHierarchy.isSubtypeShallow( + lowerBoundAnnotationsExpr, + newExprTM, + lowerBoundAnnotationsCast, + newCastTM) + && typeHierarchy.isSubtypeShallowEffective(exprType, castType); + } + if (castTypeKind == TypeKind.TYPEVAR) { + // If the cast type is a type var, but the expression is not, then check that the + // type of the expression is a subtype of the lower bound. + castAnnos = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); + } else { + castAnnos = castType.getAnnotations(); + } } - } + + AnnotatedTypeMirror exprTypeWidened = atypeFactory.getWidenedType(exprType, castType); + return qualHierarchy.isSubtypeShallow( + exprTypeWidened.getEffectiveAnnotations(), + exprTypeWidened.getUnderlyingType(), + castAnnos, + newCastTM); } - } - - /** - * Validate if the annotations on the tree are at the right locations, which are specified by the - * meta-annotation @TargetLocations. - * - * @param tree annotations on this VariableTree will be validated - * @param type the type of the tree - * @param required if all of the TypeUseLocations in {@code required} are not present in the - * specification of the annotation (@TargetLocations), issue an error. - */ - protected void validateTargetLocation( - Tree tree, AnnotatedTypeMirror type, TypeUseLocation required) { - if (ignoreTargetLocations) { - return; + + /** + * Return whether casting the {@code exprType} to {@code castType}, a type with a qualifier + * parameter, is legal. + * + *

          If {@code exprType} has qualifier parameter, the cast is legal if the qualifiers are + * invariant. Otherwise, the cast is legal is if the qualifier on both types is bottom. + * + * @param castType a type with a qualifier parameter + * @param exprType type of the expressions that is cast which may or may not have a qualifier + * parameter + * @param top the top qualifier of the hierarchy to check + * @return whether casting the {@code exprType} to {@code castType}, a type with a qualifier + * parameter, is legal. + */ + private boolean isTypeCastSafeInvariant( + AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType, AnnotationMirror top) { + if (!isTypeCastSafe(castType, exprType)) { + return false; + } + + if (atypeFactory.hasQualifierParameterInHierarchy(exprType, top)) { + // The isTypeCastSafe call above checked that the exprType is a subtype of castType, + // so just check the reverse to check that the qualifiers are equivalent. + return typeHierarchy.isSubtypeShallowEffective(castType, exprType, top); + } + AnnotationMirror castTypeAnno = castType.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror exprTypeAnno = exprType.getEffectiveAnnotationInHierarchy(top); + // Otherwise the cast is unsafe, unless the qualifiers on both cast and expr are bottom. + AnnotationMirror bottom = qualHierarchy.getBottomAnnotation(top); + return AnnotationUtils.areSame(castTypeAnno, bottom) + && AnnotationUtils.areSame(exprTypeAnno, bottom); } - for (AnnotationMirror am : type.getAnnotations()) { - List locations = - qualAllowedLocations.get(AnnotationUtils.annotationName(am)); - if (locations == null || locations.contains(TypeUseLocation.ALL)) { - continue; - } - boolean issueError = !locations.contains(required); - if (issueError) { - checker.reportError( - tree, "type.invalid.annotations.on.location", am.toString(), required.toString()); - } + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + // validate "tree" instead of "tree.getType()" to prevent duplicate errors. + boolean valid = validateTypeOf(tree) && validateTypeOf(tree.getExpression()); + if (valid) { + checkTypecastSafety(tree); + checkTypecastRedundancy(tree); + } + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + + if (atypeFactory.getDependentTypesHelper().hasDependentAnnotations()) { + atypeFactory + .getDependentTypesHelper() + .checkTypeForErrorExpressions(type, tree.getType()); + } + + if (tree.getType().getKind() == Tree.Kind.INTERSECTION_TYPE) { + AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type; + checkExplicitAnnotationsOnIntersectionBounds( + intersection, ((IntersectionTypeTree) tree.getType()).getBounds()); + } + return super.visitTypeCast(tree, p); } - } - - /** - * Create a new map, which is used for declared type-use locations lookup. - * - * @return a new mapping from strings of qualifier names to their declared type-use locations. - */ - protected Map<@CanonicalName String, List> createQualAllowedLocations() { - HashMap<@CanonicalName String, List> qualAllowedLocations = new HashMap<>(); - for (String qual : atypeFactory.getSupportedTypeQualifierNames()) { - Element elem = elements.getTypeElement(qual); - TargetLocations tls = elem.getAnnotation(TargetLocations.class); - // @Target({ElementType.TYPE_USE})} together with no @TargetLocations(...) means that - // the qualifier can be written on any type use. - if (tls == null) { - qualAllowedLocations.put(qual, null); - continue; - } - List locations = Arrays.asList(tls.value()); - qualAllowedLocations.put(qual, locations); + + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + // The "reference type" is the type after "instanceof". + Tree patternTree = InstanceOfUtils.getPattern(tree); + if (patternTree != null) { + if (TreeUtils.isBindingPatternTree(patternTree)) { + VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); + validateTypeOf(variableTree); + if (variableTree.getModifiers() != null) { + AnnotatedTypeMirror variableType = atypeFactory.getAnnotatedType(variableTree); + AnnotatedTypeMirror expType = + atypeFactory.getAnnotatedType(tree.getExpression()); + if (!isTypeCastSafe(variableType, expType)) { + checker.reportWarning( + tree, "instanceof.pattern.unsafe", expType, variableTree); + } + } + } else { + // TODO: implement deconstructed patterns. + } + } else { + Tree refTypeTree = tree.getType(); + validateTypeOf(refTypeTree); + if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeMirror refType = atypeFactory.getAnnotatedType(refTypeTree); + AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(tree.getExpression()); + if (typeHierarchy.isSubtype(refType, expType) + && !refType.getAnnotations().equals(expType.getAnnotations())) { + checker.reportWarning(tree, "instanceof.unsafe", expType, refType); + } + } + } + + return super.visitInstanceOf(tree, p); } - return qualAllowedLocations; - } - - /** - * Issues a "redundant.anno" warning if the annotation written on the type is the same as the - * default annotation for this type and location. - * - * @param tree an AST node - * @param type get the explicit annotation on this type and compare it with the default one for - * this type and location. - */ - protected void warnRedundantAnnotations(Tree tree, AnnotatedTypeMirror type) { - // Type variable uses don't have default annotations. So, any explicit annotation is not - // redundant. - if (!warnRedundantAnnotations || (type.getKind() == TypeKind.TYPEVAR)) { - return; + + /** + * Checks the type of the exception parameter. Subclasses should override {@link + * #checkExceptionParameter} rather than this method to change the behavior of this check. + */ + @Override + public Void visitCatch(CatchTree tree, Void p) { + checkExceptionParameter(tree); + return super.visitCatch(tree, p); } - AnnotationMirrorSet explicitAnnos = type.getExplicitAnnotations(); - if (explicitAnnos.isEmpty()) { - return; + + /** + * Checks the type of a thrown exception. Subclasses should override + * checkThrownExpression(ThrowTree tree) rather than this method to change the behavior of this + * check. + */ + @Override + public Void visitThrow(ThrowTree tree, Void p) { + checkThrownExpression(tree); + return super.visitThrow(tree, p); } - if (tree == null) { - throw new BugInCF("unexpected null tree argument!"); + + /** + * Rather than overriding this method, clients should often override {@link + * #visitAnnotatedType(List,Tree)}. That method also handles the case of annotations at the + * beginning of a variable or method declaration. javac parses all those annotations as being on + * the variable or method declaration, even though the ones that are type annotations logically + * belong to the variable type or method return type. + */ + @Override + public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { + visitAnnotatedType(null, tree); + return super.visitAnnotatedType(tree, p); } - AnnotatedTypeMirror defaultType = atypeFactory.getDefaultAnnotations(tree, type); - for (AnnotationMirror explicitAnno : explicitAnnos) { - AnnotationMirror defaultAM = defaultType.getAnnotationInHierarchy(explicitAnno); - if (AnnotationUtils.areSame(defaultAM, explicitAnno)) { - checker.reportWarning(tree, "redundant.anno", defaultAM); - } + /** + * Checks an annotated type. Invoked by {@link #visitAnnotatedType(AnnotatedTypeTree, Void)}, + * {@link #visitVariable}, and {@link #visitMethod}. Exists to prevent code duplication among + * the three. Checking in {@code visitVariable} and {@code visitMethod} is needed because there + * isn't an AnnotatedTypeTree within a variable declaration or for a method return type -- all + * the annotations are attached to the VariableTree or MethodTree, respectively. + * + * @param annoTrees annotations written before a variable/method declaration, if this type is + * from one; null otherwise. This might contain type annotations that the Java parser + * attached to the declaration rather than to the type. + * @param typeTree the type that any type annotations in annoTrees apply to + */ + public void visitAnnotatedType( + @Nullable List annoTrees, Tree typeTree) { + warnAboutIrrelevantJavaTypes(annoTrees, typeTree); } - } - - /** - * Warn if a type annotation is written before a modifier such as "public" or before a declaration - * annotation. - * - * @param tree a VariableTree or a MethodTree - * @param modifiersTree the modifiers sub-tree of tree - */ - private void warnAboutTypeAnnotationsTooEarly(Tree tree, ModifiersTree modifiersTree) { - - // Don't issue warnings about compiler-inserted modifiers. - // This simple code completely igonores enum constants and try-with-resources declarations. - // It could be made to catch some user errors in those locations, but it doesn't seem worth - // the effort to do so. - if (tree.getKind() == Tree.Kind.VARIABLE) { - ElementKind varKind = TreeUtils.elementFromDeclaration((VariableTree) tree).getKind(); - switch (varKind) { - case ENUM_CONSTANT: - // Enum constants are "public static final" by default, so the annotation always - // appears to be before "public". - return; - case RESOURCE_VARIABLE: - // Try-with-resources variables are "final" by default, so the annotation always - // appears to be before "final". - return; - default: - if (TreeUtils.isAutoGeneratedRecordMember(tree)) { - // Annotations can appear on record fields before the class body, so don't - // issue a warning about those. + + /** + * Warns if a type annotation is written on a Java type that is not listed in + * the @RelevantJavaTypes annotation. + * + * @param annoTrees annotations written before a variable/method declaration, if this type is + * from one; null otherwise. This might contain type annotations that the Java parser + * attached to the declaration rather than to the type. + * @param typeTree the type that any type annotations in annoTrees apply to + */ + public void warnAboutIrrelevantJavaTypes( + @Nullable List annoTrees, Tree typeTree) { + if (!shouldWarnAboutIrrelevantJavaTypes()) { return; - } - // Nothing to do - } - } + } - Set modifierSet = modifiersTree.getFlags(); - List annotations = modifiersTree.getAnnotations(); + Tree t = typeTree; + while (true) { + switch (t.getKind()) { + + // Recurse for compound types whose top level is not at the far left. + case ARRAY_TYPE: + t = ((ArrayTypeTree) t).getType(); + continue; + case MEMBER_SELECT: + t = ((MemberSelectTree) t).getExpression(); + continue; + case PARAMETERIZED_TYPE: + t = ((ParameterizedTypeTree) t).getType(); + continue; + + // Base cases + case PRIMITIVE_TYPE: + case IDENTIFIER: + maybeReportAnnoOnIrrelevant(t, TreeUtils.typeOf(t), annoTrees); + return; + case ANNOTATED_TYPE: + AnnotatedTypeTree at = (AnnotatedTypeTree) t; + ExpressionTree underlying = at.getUnderlyingType(); + maybeReportAnnoOnIrrelevant( + t, TreeUtils.typeOf(underlying), at.getAnnotations()); + return; + + default: + return; + } + } + } - if (annotations.isEmpty()) { - return; + /** + * If the given Java basetype is not relevant, report an "anno.on.irrelevant" if it is + * annotated. This method does not necessarily issue an error, but it might. + * + * @param errorLocation where to repor the error + * @param type the Java basetype + * @param annos the annotation on the type + */ + private void maybeReportAnnoOnIrrelevant( + Tree errorLocation, TypeMirror type, List annos) { + List supportedAnnoTrees = supportedAnnoTrees(annos); + if (!supportedAnnoTrees.isEmpty() && !atypeFactory.isRelevant(type)) { + String extraInfo = atypeFactory.irrelevantExtraMessage(); + checker.reportError(errorLocation, "anno.on.irrelevant", annos, type, extraInfo); + } } - // Warn about type annotations written before modifiers such as "public". javac retains no - // information about modifier locations. So, this is a very partial check: Issue a warning - // if a type annotation is at the very beginning of the VariableTree, and a modifier follows - // it. - - // Check if a type annotation precedes a declaration annotation. - int lastDeclAnnoIndex = -1; - for (int i = annotations.size() - 1; i > 0; i--) { // no need to check index 0 - if (!isTypeAnnotation(annotations.get(i))) { - lastDeclAnnoIndex = i; - break; - } + /** + * Returns true if the checker should issue warnings about irrelevant java types. + * + * @return true if the checker should issue warnings about irrelevant java types + */ + protected boolean shouldWarnAboutIrrelevantJavaTypes() { + return atypeFactory.relevantJavaTypes != null; } - if (lastDeclAnnoIndex != -1) { - // Usually, there are few bad invariant annotations. - List badTypeAnnos = new ArrayList<>(2); - for (int i = 0; i < lastDeclAnnoIndex; i++) { - AnnotationTree anno = annotations.get(i); - if (isTypeAnnotation(anno)) { - badTypeAnnos.add(anno); + + /** + * Returns a new list containing only the supported annotations from its argument -- that is, + * those that are part of the current type system. + * + *

          This method ignores aliases of supported annotations that are declaration annotations, + * because they may apply to inner types. + * + * @param annoTrees annotation trees + * @return a new list containing only the supported annotations from its argument + */ + private List supportedAnnoTrees(List annoTrees) { + List result = new ArrayList<>(1); + for (AnnotationTree at : annoTrees) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(at); + if (AnnotationUtils.isTypeUseAnnotation(anno) + && atypeFactory.isSupportedQualifier(anno)) { + result.add(at); + } } - } - if (!badTypeAnnos.isEmpty()) { - checker.reportWarning( - tree, "type.anno.before.decl.anno", badTypeAnnos, annotations.get(lastDeclAnnoIndex)); - } + return result; } - // Determine the length of the text that ought to precede the first type annotation. - // If the type annotation appears before that text could appear, then warn that a - // modifier appears after the type annotation. - // TODO: in the future, account for the lengths of declaration annotations. Length of - // toString of the annotation isn't useful, as it might be different length than original - // input. Can use JCTree.getEndPosition(EndPosTable) and - // com.sun.tools.javac.tree.EndPosTable, but it requires -Xjcov. - AnnotationTree firstAnno = annotations.get(0); - if (!modifierSet.isEmpty() && isTypeAnnotation(firstAnno)) { - int precedingTextLength = 0; - for (Modifier m : modifierSet) { - precedingTextLength += m.toString().length() + 1; // +1 for the space - } - int annoStartPos = ((JCTree) firstAnno).getStartPosition(); - int varStartPos = ((JCTree) tree).getStartPosition(); - if (annoStartPos < varStartPos + precedingTextLength) { - checker.reportWarning(tree, "type.anno.before.modifier", firstAnno, modifierSet); - } - } - } - - /** - * Return true if the given annotation is a type annotation: that is, its definition is - * meta-annotated with {@code @Target({TYPE_USE,....})}. - */ - private boolean isTypeAnnotation(AnnotationTree anno) { - Tree annoType = anno.getAnnotationType(); - ClassSymbol annoSymbol; - switch (annoType.getKind()) { - case IDENTIFIER: - annoSymbol = (ClassSymbol) ((JCIdent) annoType).sym; - break; - case MEMBER_SELECT: - annoSymbol = (ClassSymbol) ((JCFieldAccess) annoType).sym; - break; - default: - throw new BugInCF("Unhandled kind: " + annoType.getKind() + " for " + anno); + // ********************************************************************** + // Helper methods to provide a single overriding point + // ********************************************************************** + + /** + * Cache to avoid calling {@link #getExceptionParameterLowerBoundAnnotations} more than once. + */ + private @MonotonicNonNull AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCache; + + /** + * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. The same + * as {@link #getExceptionParameterLowerBoundAnnotations}, but uses a cache. + * + * @return a set of AnnotationMirrors that is a lower bound for exception parameters + */ + private AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCached() { + if (getExceptionParameterLowerBoundAnnotationsCache == null) { + getExceptionParameterLowerBoundAnnotationsCache = + getExceptionParameterLowerBoundAnnotations(); + } + return getExceptionParameterLowerBoundAnnotationsCache; } - for (AnnotationMirror metaAnno : annoSymbol.getAnnotationMirrors()) { - if (AnnotationUtils.areSameByName(metaAnno, TARGET)) { - AnnotationValue av = metaAnno.getElementValues().get(targetValueElement); - return AnnotationUtils.annotationValueContainsToString(av, "TYPE_USE"); - } + + /** + * Issue error if the exception parameter is not a supertype of the annotation specified by + * {@link #getExceptionParameterLowerBoundAnnotations()}, which is top by default. + * + *

          Subclasses may override this method to change the behavior of this check. Subclasses + * wishing to enforce that exception parameter be annotated with other annotations can just + * override {@link #getExceptionParameterLowerBoundAnnotations()}. + * + * @param tree a CatchTree to check + */ + protected void checkExceptionParameter(CatchTree tree) { + AnnotationMirrorSet requiredAnnotations = + getExceptionParameterLowerBoundAnnotationsCached(); + VariableTree excParamTree = tree.getParameter(); + AnnotatedTypeMirror excParamType = atypeFactory.getAnnotatedType(excParamTree); + + for (AnnotationMirror required : requiredAnnotations) { + AnnotationMirror found = excParamType.getAnnotationInHierarchy(required); + assert found != null; + if (!typeHierarchy.isSubtypeShallowEffective(required, excParamType)) { + checker.reportError(excParamTree, "exception.parameter.invalid", found, required); + } + + if (excParamType.getKind() == TypeKind.UNION) { + AnnotatedUnionType aut = (AnnotatedUnionType) excParamType; + for (AnnotatedTypeMirror alternativeType : aut.getAlternatives()) { + if (!typeHierarchy.isSubtypeShallowEffective(required, alternativeType)) { + AnnotationMirror alternativeAnno = + alternativeType.getAnnotationInHierarchy(required); + checker.reportError( + excParamTree, + "exception.parameter.invalid", + alternativeAnno, + required); + } + } + } + } } - return false; - } - - /** - * Performs two checks: subtyping and assignability checks, using {@link - * #commonAssignmentCheck(Tree, ExpressionTree, String, Object[])}. - * - *

          If the subtype check fails, it issues an "assignment.type.incompatible" error. - */ - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - commonAssignmentCheck(tree.getVariable(), tree.getExpression(), "assignment.type.incompatible"); - return super.visitAssignment(tree, p); - } - - /** - * Performs a subtype check, to test whether the tree expression iterable type is a subtype of the - * variable type in the enhanced for loop. - * - *

          If the subtype check fails, it issues a "enhancedfor.type.incompatible" error. - */ - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(tree.getVariable()); - AnnotatedTypeMirror iteratedType = atypeFactory.getIterableElementType(tree.getExpression()); - boolean valid = validateTypeOf(tree.getVariable()); - if (valid) { - commonAssignmentCheck( - var, iteratedType, tree.getExpression(), "enhancedfor.type.incompatible"); + /** + * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. + * + *

          This implementation returns top; subclasses can change this behavior. + * + *

          Note: by default this method is called by {@link #getThrowUpperBoundAnnotations()}, so + * that this annotation is enforced. + * + * @return set of annotation mirrors, one per hierarchy, that form a lower bound of annotations + * that can be written on an exception parameter + */ + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + return qualHierarchy.getTopAnnotations(); } - return super.visitEnhancedForLoop(tree, p); - } - - /** - * Performs a method invocation check. - * - *

          An invocation of a method, m, on the receiver, r is valid only if: - * - *

            - *
          • passed arguments are subtypes of corresponding m parameters - *
          • r is a subtype of m receiver type - *
          • if m is generic, passed type arguments are subtypes of m type variables - *
          - */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - - // Skip calls to the Enum constructor (they're generated by javac and - // hard to check), also see CFGBuilder.visitMethodInvocation. - if (TreeUtils.elementFromUse(tree) == null || TreeUtils.isEnumSuperCall(tree)) { - return super.visitMethodInvocation(tree, p); + + /** + * Checks the type of the thrown expression. + * + *

          By default, this method checks that the thrown expression is a subtype of top. + * + *

          Issue error if the thrown expression is not a sub type of the annotation given by {@link + * #getThrowUpperBoundAnnotations()}, the same as {@link + * #getExceptionParameterLowerBoundAnnotations()} by default. + * + *

          Subclasses may override this method to change the behavior of this check. Subclasses + * wishing to enforce that the thrown expression be a subtype of a type besides {@link + * #getExceptionParameterLowerBoundAnnotations}, should override {@link + * #getThrowUpperBoundAnnotations()}. + * + * @param tree a ThrowTree to check + */ + protected void checkThrownExpression(ThrowTree tree) { + AnnotatedTypeMirror throwType = atypeFactory.getAnnotatedType(tree.getExpression()); + TypeMirror throwTM = throwType.getUnderlyingType(); + Set required = getThrowUpperBoundAnnotations(); + switch (throwType.getKind()) { + case NULL: + case DECLARED: + case TYPEVAR: + case WILDCARD: + if (!typeHierarchy.isSubtypeShallowEffective(throwType, required)) { + AnnotationMirrorSet found = throwType.getEffectiveAnnotations(); + checker.reportError( + tree.getExpression(), "throw.type.invalid", found, required); + } + break; + + case UNION: + AnnotatedUnionType unionType = (AnnotatedUnionType) throwType; + AnnotationMirrorSet foundPrimary = unionType.getAnnotations(); + if (!qualHierarchy.isSubtypeShallow(foundPrimary, required, throwTM)) { + checker.reportError( + tree.getExpression(), "throw.type.invalid", foundPrimary, required); + } + for (AnnotatedTypeMirror altern : unionType.getAlternatives()) { + TypeMirror alternTM = altern.getUnderlyingType(); + if (!qualHierarchy.isSubtypeShallow( + altern.getAnnotations(), required, alternTM)) { + checker.reportError( + tree.getExpression(), + "throw.type.invalid", + altern.getAnnotations(), + required); + } + } + break; + default: + throw new BugInCF("Unexpected throw expression type: " + throwType.getKind()); + } } - if (shouldSkipUses(tree)) { - return super.visitMethodInvocation(tree, p); - } - ParameterizedExecutableType preInference = - atypeFactory.methodFromUseWithoutTypeArgInference(tree); - if (!preInference.executableType.getElement().getTypeParameters().isEmpty() - && preInference.typeArgs.isEmpty()) { - if (!checkTypeArgumentInference(tree, preInference.executableType)) { - return null; - } - } - ParameterizedExecutableType mType = atypeFactory.methodFromUse(tree); - AnnotatedExecutableType invokedMethod = mType.executableType; - List typeargs = mType.typeArgs; - - List paramBounds = - CollectionsPlume.mapList( - AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables()); - - ExecutableElement method = invokedMethod.getElement(); - CharSequence methodName = ElementUtils.getSimpleDescription(method); - checkTypeArguments( - tree, - paramBounds, - typeargs, - tree.getTypeArguments(), - methodName, - invokedMethod.getTypeVariables()); - List params = - AnnotatedTypes.adaptParameters(atypeFactory, invokedMethod, tree.getArguments(), null); - checkArguments(params, tree.getArguments(), methodName, method.getParameters()); - checkVarargs(invokedMethod, tree); - - if (ElementUtils.isMethod( - invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) { - typeCheckVectorCopyIntoArgument(tree, params); + /** + * Returns a set of AnnotationMirrors that is a upper bound for thrown exceptions. + * + *

          Note: by default this method is returns by getExceptionParameterLowerBoundAnnotations(), + * so that this annotation is enforced. + * + *

          (Default is top) + * + * @return set of annotation mirrors, one per hierarchy, that form an upper bound of thrown + * expressions + */ + protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { + return getExceptionParameterLowerBoundAnnotations(); } - ExecutableElement invokedMethodElement = invokedMethod.getElement(); - if (!ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperConstructorCall(tree)) { - checkMethodInvocability(invokedMethod, tree); - } + /** + * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and + * emits an error message (through the compiler's messaging interface) if it is not valid. + * + * @param varTree the AST node for the lvalue (usually a variable) + * @param valueExpTree the AST node for the rvalue (the new value) + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean commonAssignmentCheck( + Tree varTree, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + if (valueExpTree.getKind() == Kind.CONDITIONAL_EXPRESSION) { + ConditionalExpressionTree condExprTree = (ConditionalExpressionTree) valueExpTree; + boolean trueResult = + commonAssignmentCheck(varTree, condExprTree.getTrueExpression(), "assignment"); + boolean falseResult = + commonAssignmentCheck(varTree, condExprTree.getFalseExpression(), "assignment"); + return trueResult && falseResult; + } - // check precondition annotations - checkPreconditions( - tree, atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement)); + AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(varTree); + assert varType != null : "no variable found for tree: " + varTree; + + if (!validateType(varTree, varType)) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because validateType() returned false", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return true; + } - if (TreeUtils.isSuperConstructorCall(tree)) { - checkSuperConstructorCall(tree); - } else if (TreeUtils.isThisConstructorCall(tree)) { - checkThisConstructorCall(tree); + return commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); } - // Do not call super, as that would observe the arguments without - // a set assignment context. - scan(tree.getMethodSelect(), p); - return null; // super.visitMethodInvocation(tree, p); - } - - /** - * Reports a "type.arguments.not.inferred" error if type argument inference fails and returns - * false if inference fails. - * - * @param tree a tree that requires type argument inference - * @param methodType the type of the method before type argument substitution - * @return whether type argument inference succeeds - */ - private boolean checkTypeArgumentInference( - ExpressionTree tree, AnnotatedExecutableType methodType) { - InferenceResult args = - atypeFactory.getTypeArgumentInference().inferTypeArgs(atypeFactory, tree, methodType); - if (args != null && !args.inferenceFailed()) { - return true; - } - checker.reportError( - tree, - "type.arguments.not.inferred", - ElementUtils.getSimpleDescription(methodType.getElement()), - args == null ? "" : args.getErrorMsg()); - return false; - } - - /** - * Checks that the following rule is satisfied: The type on a constructor declaration must be a - * supertype of the return type of "this()" invocation within that constructor. - * - *

          Subclasses can override this method to change the behavior for just "this" constructor - * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to - * change the behavior for "this" and "super" constructor calls. - * - * @param thisCall the AST node for the constructor call - */ - protected void checkThisConstructorCall(MethodInvocationTree thisCall) { - checkThisOrSuperConstructorCall(thisCall, "this.invocation.invalid"); - } - - /** - * Checks that the following rule is satisfied: The type on a constructor declaration must be a - * supertype of the return type of "super()" invocation within that constructor. - * - *

          Subclasses can override this method to change the behavior for just "super" constructor - * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to - * change the behavior for "this" and "super" constructor calls. - * - * @param superCall the AST node for the super constructor call - */ - protected void checkSuperConstructorCall(MethodInvocationTree superCall) { - checkThisOrSuperConstructorCall(superCall, "super.invocation.invalid"); - } - - /** - * Checks that the following rule is satisfied: The type on a constructor declaration must be a - * supertype of the return type of "this()" or "super()" invocation within that constructor. - * - * @param call the AST node for the constructor call - * @param errorKey the error message key to use if the check fails - */ - protected void checkThisOrSuperConstructorCall( - MethodInvocationTree call, @CompilerMessageKey String errorKey) { - TreePath path = atypeFactory.getPath(call); - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path); - AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(call); - AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); - AnnotatedTypeMirror returnType = constructorType.getReturnType(); - AnnotationMirrorSet topAnnotations = qualHierarchy.getTopAnnotations(); - for (AnnotationMirror topAnno : topAnnotations) { - if (!typeHierarchy.isSubtypeShallowEffective(superType, returnType, topAnno)) { - AnnotationMirror superAnno = superType.getAnnotationInHierarchy(topAnno); - AnnotationMirror constructorReturnAnno = returnType.getAnnotationInHierarchy(topAnno); - checker.reportError(call, errorKey, constructorReturnAnno, call, superAnno); - } - } - } - - /** - * If the given invocation is a varargs invocation, check that the array type of actual varargs is - * a subtype of the corresponding formal parameter; issues "argument.invalid" error if not. - * - *

          The caller must type-check for each element in varargs before or after calling this method. - * - * @see #checkArguments - * @param invokedMethod the method type to be invoked - * @param tree method or constructor invocation tree - */ - protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) { - if (!TreeUtils.isVarArgs(tree)) { - // If not a varargs invocation, type checking is already done in checkArguments. - return; + /** + * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and + * emits an error message (through the compiler's messaging interface) if it is not valid. + * + * @param varType the annotated type for the lvalue (usually a variable) + * @param valueExpTree the AST node for the rvalue (the new value) + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + if (shouldSkipUses(valueExpTree)) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because shouldSkipUses() returned true", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return true; + } + if (valueExpTree.getKind() == Tree.Kind.MEMBER_REFERENCE + || valueExpTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + // Member references and lambda expressions are type checked separately + // and do not need to be checked again as arguments. + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because member reference and lambda expression are type checked separately", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return true; + } + boolean result = true; + if (varType.getKind() == TypeKind.ARRAY + && valueExpTree instanceof NewArrayTree + && ((NewArrayTree) valueExpTree).getType() == null) { + AnnotatedTypeMirror compType = ((AnnotatedArrayType) varType).getComponentType(); + NewArrayTree arrayTree = (NewArrayTree) valueExpTree; + assert arrayTree.getInitializers() != null + : "array initializers are not expected to be null in: " + valueExpTree; + result = checkArrayInitialization(compType, arrayTree.getInitializers()) && result; + } + if (!validateTypeOf(valueExpTree)) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "skipping test whether actual is a subtype of expected" + + " because validateType() returned false", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + varType.getKind(), + varType.toString()); + } + return result; + } + AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExpTree); + assert valueType != null : "null type for expression: " + valueExpTree; + result = + commonAssignmentCheck(varType, valueType, valueExpTree, errorKey, extraArgs) + && result; + return result; } - // This is the varags type, an array. - AnnotatedArrayType lastParamAnnotatedType = invokedMethod.getVarargType(); - - AnnotatedTypeMirror wrappedVarargsType = atypeFactory.getAnnotatedTypeVarargsArray(tree); + /** + * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and + * emits an error message (through the compiler's messaging interface) if it is not valid. + * + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueExpTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + commonAssignmentCheckStartDiagnostic(varType, valueType, valueExpTree); + + AnnotatedTypeMirror widenedValueType = atypeFactory.getWidenedType(valueType, varType); + boolean result = typeHierarchy.isSubtype(widenedValueType, varType); + + // TODO: integrate with subtype test. + if (result) { + for (Class mono : + atypeFactory.getSupportedMonotonicTypeQualifiers()) { + if (valueType.hasAnnotation(mono) && varType.hasAnnotation(mono)) { + checker.reportError( + valueExpTree, + "monotonic.type.incompatible", + mono.getSimpleName(), + mono.getSimpleName(), + valueType.toString()); + result = false; + } + } + } else { + // `result` is false. + // Use an error key only if it's overridden by a checker. + reportCommonAssignmentError( + varType, widenedValueType, valueExpTree, errorKey, extraArgs); + } - // When dataflow analysis is not enabled, it will be null and we can suppose there is no - // annotation to be checked for generated varargs array. - if (wrappedVarargsType == null) { - return; - } + commonAssignmentCheckEndDiagnostic(result, null, varType, valueType, valueExpTree); - // The component type of wrappedVarargsType might not be a subtype of the component type of - // lastParamAnnotatedType due to the difference of type inference between for an expression - // and an invoked method element. We can consider that the component type of actual is same - // with formal one because type checking for elements will be done in checkArguments. This - // is also needed to avoid duplicating error message caused by elements in varargs. - if (wrappedVarargsType.getKind() == TypeKind.ARRAY) { - ((AnnotatedArrayType) wrappedVarargsType) - .setComponentType(lastParamAnnotatedType.getComponentType()); + return result; } - commonAssignmentCheck( - lastParamAnnotatedType, wrappedVarargsType, tree, "varargs.type.incompatible"); - } - - /** - * Checks that all the given {@code preconditions} hold true immediately prior to the method - * invocation or variable access at {@code tree}. - * - * @param tree the method invocation; immediately prior to it, the preconditions must hold true - * @param preconditions the preconditions to be checked - */ - protected void checkPreconditions(MethodInvocationTree tree, Set preconditions) { - // This check is needed for the GUI effects and Units Checkers tests to pass. - // TODO: Remove this check and investigate the root cause. - if (preconditions.isEmpty()) { - return; + /** + * Report a common assignment error. Allows checkers to change how the message is output. + * + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueTree the location to use when reporting the error message + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + */ + protected void reportCommonAssignmentError( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + FoundRequired pair = FoundRequired.of(valueType, varType); + String valueTypeString = pair.found; + String varTypeString = pair.required; + checker.reportError( + valueTree, + errorKey, + ArraysPlume.concatenate(extraArgs, valueTypeString, varTypeString)); } - StringToJavaExpression stringToJavaExpr = - stringExpr -> StringToJavaExpression.atMethodInvocation(stringExpr, tree, checker); - for (Contract c : preconditions) { - Precondition p = (Precondition) c; - String expressionString = p.expressionString; - AnnotationMirror anno = - c.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, tree); - JavaExpression exprJe; - try { - exprJe = StringToJavaExpression.atMethodInvocation(expressionString, tree, checker); - } catch (JavaExpressionParseException e) { - // report errors here - checker.report(tree, e.getDiagMessage()); - return; - } - - CFAbstractStore store = atypeFactory.getStoreBefore(tree); - - AnnotationMirrorSet annos = - atypeFactory.getAnnotatedTypeBefore(exprJe, tree).getAnnotations(); - - AnnotationMirror inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, anno); - - // If the expression is "this", then get the type of the method receiver. - // TODO: There are other expressions that can be converted to trees, "#1" for - // example. - if (expressionString.equals("this") - && qualHierarchy.getTopAnnotations().contains(inferredAnno)) { - AnnotatedTypeMirror atype = atypeFactory.getReceiverType(tree); - if (atype != null) { - annos = atype.getEffectiveAnnotations(); - inferredAnno = qualHierarchy.findAnnotationInSameHierarchy(annos, anno); + /** + * Prints a diagnostic about entering {@code commonAssignmentCheck()}, if the showchecks option + * was set. + * + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + */ + protected final void commonAssignmentCheckStartDiagnostic( + AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueExpTree) { + if (showchecks) { + System.out.printf( + "%s %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", + this.getClass().getSimpleName(), + "about to test whether actual is a subtype of expected", + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + valueType.getKind(), + valueType.toString(), + varType.getKind(), + varType.toString()); } - } + } - if (!checkContract(exprJe, anno, inferredAnno, store)) { - if (exprJe != null) { - expressionString = exprJe.toString(); + /** + * Prints a diagnostic about exiting {@code commonAssignmentCheck()}, if the showchecks option + * was set. + * + * @param success whether the check succeeded or failed + * @param extraMessage information about why the result is what it is; may be null + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + */ + protected final void commonAssignmentCheckEndDiagnostic( + boolean success, + @Nullable String extraMessage, + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueExpTree) { + if (showchecks) { + commonAssignmentCheckEndDiagnostic( + (success + ? "success: actual is subtype of expected" + : "FAILURE: actual is not subtype of expected") + + (extraMessage == null ? "" : " because " + extraMessage), + varType, + valueType, + valueExpTree); } - checker.reportError( - tree, - "contracts.precondition.not.satisfied", - tree.getMethodSelect().toString(), - contractExpressionAndType(expressionString, inferredAnno), - contractExpressionAndType(expressionString, anno)); - } - } - } - - /** - * Returns true if and only if {@code inferredAnnotation} is valid for a given expression to match - * the {@code necessaryAnnotation}. - * - *

          By default, {@code inferredAnnotation} must be a subtype of {@code necessaryAnnotation}, but - * subclasses might override this behavior. - */ - protected boolean checkContract( - JavaExpression expr, - AnnotationMirror necessaryAnnotation, - AnnotationMirror inferredAnnotation, - CFAbstractStore store) { - if (inferredAnnotation == null) { - return false; - } - TypeMirror exprTM = expr.getType(); - return qualHierarchy.isSubtypeShallow(inferredAnnotation, necessaryAnnotation, exprTM); - } - - /** - * Type checks the method arguments of {@code Vector.copyInto()}. - * - *

          The Checker Framework special-cases the method invocation, as its type safety cannot be - * expressed by Java's type system. - * - *

          For a Vector {@code v} of type {@code Vector}, the method invocation {@code - * v.copyInto(arr)} is type-safe iff {@code arr} is an array of type {@code T[]}, where {@code T} - * is a subtype of {@code E}. - * - *

          In other words, this method checks that the type argument of the receiver method is a - * subtype of the component type of the passed array argument. - * - * @param tree a method invocation of {@code Vector.copyInto()} - * @param params the types of the parameters of {@code Vectory.copyInto()} - */ - protected void typeCheckVectorCopyIntoArgument( - MethodInvocationTree tree, List params) { - assert params.size() == 1 - : "invalid no. of parameters " + params + " found for method invocation " + tree; - assert tree.getArguments().size() == 1 - : "invalid no. of arguments in method invocation " + tree; - - AnnotatedTypeMirror passed = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedArrayType passedAsArray = (AnnotatedArrayType) passed; - - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - AnnotatedDeclaredType receiverAsVector = - AnnotatedTypes.asSuper(atypeFactory, receiver, vectorType); - if (receiverAsVector.getTypeArguments().isEmpty()) { - return; } - AnnotatedTypeMirror argComponent = passedAsArray.getComponentType(); - AnnotatedTypeMirror vectorTypeArg = receiverAsVector.getTypeArguments().get(0); - Tree errorLocation = tree.getArguments().get(0); - if (TypesUtils.isErasedSubtype( - vectorTypeArg.getUnderlyingType(), argComponent.getUnderlyingType(), types)) { - commonAssignmentCheck( - argComponent, vectorTypeArg, errorLocation, "vector.copyinto.type.incompatible"); - } else { - checker.reportError( - errorLocation, "vector.copyinto.type.incompatible", vectorTypeArg, argComponent); - } - } - - /** - * Performs a new class invocation check. - * - *

          An invocation of a constructor, c, is valid only if: - * - *

            - *
          • passed arguments are subtypes of corresponding c parameters - *
          • if c is generic, passed type arguments are subtypes of c type variables - *
          - */ - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - if (checker.shouldSkipUses(TreeUtils.elementFromUse(tree))) { - return super.visitNewClass(tree, p); + /** + * Helper method for printing a diagnostic about exiting {@code commonAssignmentCheck()}, if the + * showchecks option was set. + * + *

          Most clients should call {@link #commonAssignmentCheckEndDiagnostic(boolean, String, + * AnnotatedTypeMirror, AnnotatedTypeMirror, Tree)}. The purpose of this method is to permit + * customizing the message that is printed. + * + * @param message the result, plus information about why the result is what it is + * @param varType the annotated type of the variable + * @param valueType the annotated type of the value + * @param valueExpTree the location to use when reporting the error message + */ + protected final void commonAssignmentCheckEndDiagnostic( + String message, + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueExpTree) { + if (showchecks) { + System.out.printf( + " %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", + message, + fileAndLineNumber(valueExpTree), + valueExpTree.getKind(), + valueExpTree, + valueType.getKind(), + valueType.toString(), + varType.getKind(), + varType.toString()); + } } - ParameterizedExecutableType preInference = - atypeFactory.constructorFromUseWithoutTypeArgInference(tree); - if (!preInference.executableType.getElement().getTypeParameters().isEmpty() - || TreeUtils.isDiamondTree(tree)) { - if (!checkTypeArgumentInference(tree, preInference.executableType)) { - return null; - } + /** + * Returns "filename:linenumber:columnnumber" for the given tree. For brevity, the filename is + * given as a simple name, without any directory components. If the line and column numbers are + * unknown, they are omitted. + * + * @param tree a tree + * @return the location of the given tree in source code + */ + private String fileAndLineNumber(Tree tree) { + StringBuilder result = new StringBuilder(); + result.append(Paths.get(root.getSourceFile().getName()).getFileName().toString()); + long valuePos = positions.getStartPosition(root, tree); + LineMap lineMap = root.getLineMap(); + if (valuePos != -1 && lineMap != null) { + result.append(":"); + result.append(lineMap.getLineNumber(valuePos)); + result.append(":"); + result.append(lineMap.getColumnNumber(valuePos)); + } + return result.toString(); } - ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(tree); - AnnotatedExecutableType constructorType = fromUse.executableType; - List typeargs = fromUse.typeArgs; - - // Type check inner class enclosing expr type - checkEnclosingExpr(tree, constructorType); - List passedArguments = tree.getArguments(); - List params = constructorType.getParameterTypes(); - - ExecutableElement constructor = constructorType.getElement(); - CharSequence constructorName = ElementUtils.getSimpleDescription(constructor); - - checkArguments(params, passedArguments, constructorName, constructor.getParameters()); - checkVarargs(constructorType, tree); - - List paramBounds = - CollectionsPlume.mapList( - AnnotatedTypeVariable::getBounds, constructorType.getTypeVariables()); - - checkTypeArguments( - tree, - paramBounds, - typeargs, - tree.getTypeArguments(), - constructorName, - constructor.getTypeParameters()); - - boolean valid = validateTypeOf(tree); + /** + * Class that creates string representations of {@link AnnotatedTypeMirror}s which are only + * verbose if required to differentiate the two types. + */ + protected static class FoundRequired { - if (valid) { - AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(tree); - atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(dt, tree); - checkConstructorInvocation(dt, constructorType, tree); - } - // Do not call super, as that would observe the arguments without - // a set assignment context. - scan(tree.getEnclosingExpression(), p); - scan(tree.getIdentifier(), p); - scan(tree.getClassBody(), p); + /** The found type's string representation. */ + public final String found; - return null; - } + /** The required type's string representation. */ + public final String required; - @Override - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { + if (shouldPrintVerbose(found, required)) { + this.found = found.toString(true); + this.required = required.toString(true); + } else { + this.found = found.toString(); + this.required = required.toString(); + } + } - AnnotatedExecutableType functionType = atypeFactory.getFunctionTypeFromTree(tree); + /** Create a FoundRequired for a type and bounds. */ + private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { + if (shouldPrintVerbose(found, required)) { + this.found = found.toString(true); + this.required = required.toString(true); + } else { + this.found = found.toString(); + this.required = required.toString(); + } + } - if (tree.getBody().getKind() != Tree.Kind.BLOCK) { - // Check return type for single statement returns here. - AnnotatedTypeMirror ret = functionType.getReturnType(); - if (ret.getKind() != TypeKind.VOID) { - commonAssignmentCheck(ret, (ExpressionTree) tree.getBody(), "return.type.incompatible"); - } - } + /** + * Creates string representations of {@link AnnotatedTypeMirror}s which are only verbose if + * required to differentiate the two types. + * + * @param found the found annotation + * @param required the required annotation + * @return a string representation of the two annotations + */ + public static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { + return new FoundRequired(found, required); + } - // Check parameters - for (int i = 0; i < functionType.getParameterTypes().size(); ++i) { - AnnotatedTypeMirror lambdaParameter = - atypeFactory.getAnnotatedType(tree.getParameters().get(i)); - commonAssignmentCheck( - lambdaParameter, - functionType.getParameterTypes().get(i), - tree.getParameters().get(i), - "lambda.param.type.incompatible", - i); + /** + * Creates string representations of {@link AnnotatedTypeMirror} and {@link + * AnnotatedTypeParameterBounds}s which are only verbose if required to differentiate the + * two types. + * + * @param found the found annotation + * @param required the required annotation + * @return a string representation of the two annotations + */ + public static FoundRequired of( + AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { + return new FoundRequired(found, required); + } } - // TODO: Postconditions? - // https://github.com/typetools/checker-framework/issues/801 - - return super.visitLambdaExpression(tree, p); - } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void p) { - this.checkMethodReferenceAsOverride(tree, p); - return super.visitMemberReference(tree, p); - } - - /** A set containing {@code Tree.Kind.METHOD} and {@code Tree.Kind.LAMBDA_EXPRESSION}. */ - private final ArraySet methodAndLambdaExpression = - new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); - - /** - * Checks that the type of the return expression is a subtype of the enclosing method required - * return type. If not, it issues a "return.type.incompatible" error. - */ - @Override - public Void visitReturn(ReturnTree tree, Void p) { - // Don't try to check return expressions for void methods. - if (tree.getExpression() == null) { - return super.visitReturn(tree, p); + /** + * Return whether or not the verbose toString should be used when printing the two annotated + * types. + * + * @param atm1 the first AnnotatedTypeMirror + * @param atm2 the second AnnotatedTypeMirror + * @return true iff neither argument contains "@", or there are two annotated types (in either + * ATM) such that their toStrings are the same but their verbose toStrings differ + */ + private static boolean shouldPrintVerbose(AnnotatedTypeMirror atm1, AnnotatedTypeMirror atm2) { + if (!atm1.toString().contains("@") && !atm2.toString().contains("@")) { + return true; + } + return containsSameToString(atm1, atm2); } - Tree enclosing = TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); - - AnnotatedTypeMirror ret = null; - if (enclosing.getKind() == Tree.Kind.METHOD) { - - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - boolean valid = validateTypeOf(enclosing); - if (valid) { - ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); - } - } else { - AnnotatedExecutableType result = - atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); - ret = result.getReturnType(); + /** + * Return whether or not the verbose toString should be used when printing the annotated type + * and the bounds it is not within. + * + * @param atm the type + * @param bounds the bounds + * @return true iff bounds does not contain "@", or there are two annotated types (in either + * argument) such that their toStrings are the same but their verbose toStrings differ + */ + private static boolean shouldPrintVerbose( + AnnotatedTypeMirror atm, AnnotatedTypeParameterBounds bounds) { + if (!atm.toString().contains("@") && !bounds.toString().contains("@")) { + return true; + } + return containsSameToString(atm, bounds.getUpperBound(), bounds.getLowerBound()); } - if (ret != null) { - commonAssignmentCheck(ret, tree.getExpression(), "return.type.incompatible"); - } - return super.visitReturn(tree, p); - } - - /** - * Ensure that the annotation arguments comply to their declarations. This needs some special - * casing, as annotation arguments form special trees. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - List args = tree.getArguments(); - if (args.isEmpty()) { - // Nothing to do if there are no annotation arguments. - return null; - } + /** + * A scanner that indicates whether any (component) types have the same toString but different + * verbose toString. If so, the Checker Framework prints types verbosely. + */ + private static final SimpleAnnotatedTypeScanner> + checkContainsSameToString = + new SimpleAnnotatedTypeScanner<>( + (AnnotatedTypeMirror type, Map map) -> { + if (type == null) { + return false; + } + String simple = type.toString(); + String verbose = map.get(simple); + if (verbose == null) { + map.put(simple, type.toString(true)); + return false; + } else { + return !verbose.equals(type.toString(true)); + } + }, + Boolean::logicalOr, + false); - TypeElement annoType = (TypeElement) TreeInfo.symbol((JCTree) tree.getAnnotationType()); + /** + * Return true iff there are two annotated types (anywhere in any ATM) such that their toStrings + * are the same but their verbose toStrings differ. If so, the Checker Framework prints types + * verbosely. + * + * @param atms annotated type mirrors to compare + * @return true iff there are two annotated types (anywhere in any ATM) such that their + * toStrings are the same but their verbose toStrings differ + */ + private static boolean containsSameToString(AnnotatedTypeMirror... atms) { + Map simpleToVerbose = new HashMap<>(); + for (AnnotatedTypeMirror atm : atms) { + if (checkContainsSameToString.visit(atm, simpleToVerbose)) { + return true; + } + } - Name annoName = annoType.getQualifiedName(); - if (annoName.contentEquals(DefaultQualifier.class.getName()) - || annoName.contentEquals(SuppressWarnings.class.getName())) { - // Skip these two annotations, as we don't care about the arguments to them. - return null; + return false; } - List methods = ElementFilter.methodsIn(annoType.getEnclosedElements()); - // Mapping from argument simple name to its annotated type. - Map annoTypes = ArrayMap.newArrayMapOrHashMap(methods.size()); - for (ExecutableElement meth : methods) { - AnnotatedExecutableType exeatm = atypeFactory.getAnnotatedType(meth); - AnnotatedTypeMirror retty = exeatm.getReturnType(); - annoTypes.put(meth.getSimpleName().toString(), retty); + /** + * Checks that the array initializers are consistent with the array type. + * + * @param type the array elemen type + * @param initializers the initializers + * @return true if the check succeeds, false if an error message was issued + */ + protected boolean checkArrayInitialization( + AnnotatedTypeMirror type, List initializers) { + // TODO: set assignment context like for method arguments? + // Also in AbstractFlow. + boolean result = true; + for (ExpressionTree init : initializers) { + result = + commonAssignmentCheck(type, init, "array.initializer.type.incompatible") + && result; + } + return result; } - for (ExpressionTree arg : args) { - if (!(arg instanceof AssignmentTree)) { - // TODO: when can this happen? - continue; - } - - AssignmentTree at = (AssignmentTree) arg; - // Ensure that we never ask for the annotated type of an annotation, because - // we don't have a type for annotations. - if (at.getExpression().getKind() == Tree.Kind.ANNOTATION) { - visitAnnotation((AnnotationTree) at.getExpression(), p); - continue; - } - if (at.getExpression().getKind() == Tree.Kind.NEW_ARRAY) { - NewArrayTree nat = (NewArrayTree) at.getExpression(); - boolean isAnno = false; - for (ExpressionTree init : nat.getInitializers()) { - if (init.getKind() == Tree.Kind.ANNOTATION) { - visitAnnotation((AnnotationTree) init, p); - isAnno = true; - } - } - if (isAnno) { - continue; + /** + * Checks that the annotations on the type arguments supplied to a type or a method invocation + * are within the bounds of the type variables as declared, and issues the + * "type.argument.type.incompatible" error if they are not. + * + * @param toptree the tree for error reporting, only used for inferred type arguments + * @param paramBounds the bounds of the type parameters from a class or method declaration + * @param typeargs the type arguments from the type or method invocation + * @param typeargTrees the type arguments as trees, used for error reporting + */ + protected void checkTypeArguments( + Tree toptree, + List paramBounds, + List typeargs, + List typeargTrees, + CharSequence typeOrMethodName, + List paramNames) { + + // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s%n", + // toptree, paramBounds, typeargs, typeargTrees); + + // If there are no type variables, do nothing. + if (paramBounds.isEmpty()) { + return; } - } - AnnotatedTypeMirror expected = annoTypes.get(at.getVariable().toString()); - AnnotatedTypeMirror actual = atypeFactory.getAnnotatedType(at.getExpression()); - if (expected.getKind() != TypeKind.ARRAY) { - // Expected is not an array -> direct comparison. - commonAssignmentCheck(expected, actual, at.getExpression(), "annotation.type.incompatible"); - } else if (actual.getKind() == TypeKind.ARRAY) { - // Both actual and expected are arrays. - commonAssignmentCheck(expected, actual, at.getExpression(), "annotation.type.incompatible"); - } else { - // The declaration is an array type, but just a single element is given. - commonAssignmentCheck( - ((AnnotatedArrayType) expected).getComponentType(), - actual, - at.getExpression(), - "annotation.type.incompatible"); - } - } - return null; - } - - /** - * If the computation of the type of the ConditionalExpressionTree in - * org.checkerframework.framework.type.TypeFromTree.TypeFromExpression.visitConditionalExpression(ConditionalExpressionTree, - * AnnotatedTypeFactory) is correct, the following checks are redundant. However, let's add - * another failsafe guard and do the checks. - */ - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - if (TreeUtils.isPolyExpression(tree)) { - // From the JLS: - // A poly reference conditional expression is compatible with a target type T if its - // second and third operand expressions are compatible with T. In the Checker - // Framework this check happens in #commonAssignmentCheck. - return super.visitConditionalExpression(tree, p); - } + int size = paramBounds.size(); + assert size == typeargs.size() + : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " + + typeargs + + " and type parameter bounds" + + paramBounds; - AnnotatedTypeMirror cond = atypeFactory.getAnnotatedType(tree); - this.commonAssignmentCheck(cond, tree.getTrueExpression(), "conditional.type.incompatible"); - this.commonAssignmentCheck(cond, tree.getFalseExpression(), "conditional.type.incompatible"); - return super.visitConditionalExpression(tree, p); - } - - /** - * This method validates the type of the switch expression. It issues an error if the type of a - * value that the switch expression can result is not a subtype of the switch type. - * - *

          If a subclass overrides this method, it must call {@code super.scan(switchExpressionTree, - * null)} so that the blocks and statements in the cases are checked. - * - * @param switchExpressionTree a {@code SwitchExpressionTree} - */ - public void visitSwitchExpression17(Tree switchExpressionTree) { - boolean valid = validateTypeOf(switchExpressionTree); - if (valid) { - AnnotatedTypeMirror switchType = atypeFactory.getAnnotatedType(switchExpressionTree); - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (ExpressionTree valueTree, Void unused) -> { - BaseTypeVisitor.this.commonAssignmentCheck( - switchType, valueTree, "switch.expression.type.incompatible"); - return null; - }, - (r1, r2) -> null); + for (int i = 0; i < size; i++) { - scanner.scanSwitchExpression(switchExpressionTree, null); - } - super.scan(switchExpressionTree, null); - } - - // ********************************************************************** - // Check for illegal re-assignment - // ********************************************************************** - - /** Performs assignability check. */ - @Override - public Void visitUnary(UnaryTree tree, Void p) { - Tree.Kind treeKind = tree.getKind(); - if (treeKind == Tree.Kind.PREFIX_DECREMENT - || treeKind == Tree.Kind.PREFIX_INCREMENT - || treeKind == Tree.Kind.POSTFIX_DECREMENT - || treeKind == Tree.Kind.POSTFIX_INCREMENT) { - // Check the assignment that occurs at the increment/decrement. i.e.: - // exp = exp + 1 or exp = exp - 1 - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(tree.getExpression()); - AnnotatedTypeMirror valueType; - if (treeKind == Tree.Kind.POSTFIX_DECREMENT || treeKind == Tree.Kind.POSTFIX_INCREMENT) { - // For postfixed increments or decrements, the type of the tree the type of the - // expression before 1 is added or subtracted. So, use a special method to get the - // type after 1 has been added or subtracted. - valueType = atypeFactory.getAnnotatedTypeRhsUnaryAssign(tree); - } else { - // For prefixed increments or decrements, the type of the tree the type of the - // expression after 1 is added or subtracted. So, its type can be found using the - // usual method. - valueType = atypeFactory.getAnnotatedType(tree); - } - String errorKey = - (treeKind == Tree.Kind.PREFIX_INCREMENT || treeKind == Tree.Kind.POSTFIX_INCREMENT) - ? "unary.increment.type.incompatible" - : "unary.decrement.type.incompatible"; - commonAssignmentCheck(varType, valueType, tree, errorKey); - } - return super.visitUnary(tree, p); - } - - /** Performs assignability check. */ - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - // If tree is the tree representing the compounds assignment s += expr, - // Then this method should check whether s + expr can be assigned to s, - // but the "s + expr" tree does not exist. So instead, check that - // s += expr can be assigned to s. - commonAssignmentCheck(tree.getVariable(), tree, "compound.assignment.type.incompatible"); - return super.visitCompoundAssignment(tree, p); - } - - // ********************************************************************** - // Check for invalid types inserted by the user - // ********************************************************************** - - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - boolean valid = validateTypeOf(tree); - - if (valid && tree.getType() != null) { - AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(tree); - atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(arrayType, tree); - if (tree.getInitializers() != null) { - checkArrayInitialization(arrayType.getComponentType(), tree.getInitializers()); - } - } + AnnotatedTypeParameterBounds bounds = paramBounds.get(i); + AnnotatedTypeMirror typeArg = typeargs.get(i); - return super.visitNewArray(tree, p); - } + if (atypeFactory.ignoreRawTypeArguments + && AnnotatedTypes.isTypeArgOfRawType(bounds.getUpperBound())) { + continue; + } - /** - * If the lint option "cast:redundant" is set, this method issues a warning if the cast is - * redundant. - */ - protected void checkTypecastRedundancy(TypeCastTree typeCastTree) { - if (!checker.getLintOption("cast:redundant", false)) { - return; - } + AnnotatedTypeMirror paramUpperBound = bounds.getUpperBound(); - AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); + Tree reportErrorToTree; + if (typeargTrees == null || typeargTrees.isEmpty()) { + // The type arguments were inferred, report the error on the method invocation. + reportErrorToTree = toptree; + } else { + reportErrorToTree = typeargTrees.get(i); + } - if (castType.equals(exprType)) { - checker.reportWarning(typeCastTree, "cast.redundant", castType); - } - } - - /** - * Issues a warning if the given explicitly-written typecast is unsafe. Does nothing if the lint - * option "cast:unsafe" is not set. Only primary qualifiers are checked unless the command line - * option "checkCastElementType" is supplied. - * - * @param typeCastTree an explicitly-written typecast - */ - protected void checkTypecastSafety(TypeCastTree typeCastTree) { - if (!checker.getLintOption("cast:unsafe", true)) { - return; - } - AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree); - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression()); - boolean reported = false; - for (AnnotationMirror top : atypeFactory.getQualifierParameterHierarchies(castType)) { - if (!isTypeCastSafeInvariant(castType, exprType, top)) { - checker.reportError( - typeCastTree, - "invariant.cast.unsafe", - exprType.toString(true), - castType.toString(true)); - } - reported = true; // don't issue cast unsafe warning. + checkHasQualifierParameterAsTypeArgument(typeArg, paramUpperBound, toptree); + commonAssignmentCheck( + paramUpperBound, + typeArg, + reportErrorToTree, + "type.argument.type.incompatible", + paramNames.get(i), + typeOrMethodName); + + if (!typeHierarchy.isSubtype(bounds.getLowerBound(), typeArg)) { + FoundRequired fr = FoundRequired.of(typeArg, bounds); + checker.reportError( + reportErrorToTree, + "type.argument.type.incompatible", + paramNames.get(i), + typeOrMethodName, + fr.found, + fr.required); + } + } } - // Don't call TypeHierarchy#isSubtype(exprType, castType) because the underlying Java types - // will not be in the correct subtyping relationship if this is a downcast. - if (!reported && !isTypeCastSafe(castType, exprType)) { - checker.reportWarning( - typeCastTree, "cast.unsafe", exprType.toString(true), castType.toString(true)); - } - } - - /** - * Returns true if the cast is safe. - * - *

          Only primary qualifiers are checked unless the command line option "checkCastElementType" is - * supplied. - * - * @param castType annotated type of the cast - * @param exprType annotated type of the casted expression - * @return true if the type cast is safe, false otherwise - */ - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - TypeKind castTypeKind = castType.getKind(); - if (castTypeKind == TypeKind.DECLARED) { - // Don't issue an error if the annotations are equivalent to the qualifier upper bound - // of the type. - AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType; - AnnotationMirrorSet bounds = - atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType()); - - if (AnnotationUtils.areSame(castDeclared.getAnnotations(), bounds)) { - return true; - } + /** + * Reports an error if the type argument has a qualifier parameter and the type parameter upper + * bound does not have a qualifier parameter. + * + * @param typeArgument type argument + * @param typeParameterUpperBound upper bound of the type parameter + * @param reportError where to report the error + */ + private void checkHasQualifierParameterAsTypeArgument( + AnnotatedTypeMirror typeArgument, + AnnotatedTypeMirror typeParameterUpperBound, + Tree reportError) { + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (atypeFactory.hasQualifierParameterInHierarchy(typeArgument, top) + && !getTypeFactory() + .hasQualifierParameterInHierarchy(typeParameterUpperBound, top)) { + checker.reportError(reportError, "type.argument.invalid.hasqualparam", top); + } + } } - AnnotationMirrorSet castAnnos; - AnnotatedTypeMirror newCastType; - TypeMirror newCastTM; - if (!checkCastElementType) { - // checkCastElementType option wasn't specified, so only check effective annotations. - castAnnos = castType.getEffectiveAnnotations(); - newCastType = castType; - newCastTM = newCastType.getUnderlyingType(); - } else { - if (castTypeKind == TypeKind.TYPEVAR) { - newCastType = ((AnnotatedTypeVariable) castType).getUpperBound(); - } else { - newCastType = castType; - } - newCastTM = newCastType.getUnderlyingType(); - AnnotatedTypeMirror newExprType; - if (exprType.getKind() == TypeKind.TYPEVAR) { - newExprType = ((AnnotatedTypeVariable) exprType).getUpperBound(); - } else { - newExprType = exprType; - } - TypeMirror newExprTM = newExprType.getUnderlyingType(); - - if (!typeHierarchy.isSubtype(newExprType, newCastType)) { - return false; - } - if (newCastType.getKind() == TypeKind.ARRAY && newExprType.getKind() != TypeKind.ARRAY) { - // Always warn if the cast contains an array, but the expression - // doesn't, as in "(Object[]) o" where o is of type Object + /** + * Indicates whether to skip subtype checks on the receiver when checking method invocability. A + * visitor may, for example, allow a method to be invoked even if the receivers are siblings in + * a hierarchy, provided that some other condition (implemented by the visitor) is satisfied. + * + * @param tree the method invocation tree + * @param methodDefinitionReceiver the ATM of the receiver of the method definition + * @param methodCallReceiver the ATM of the receiver of the method call + * @return whether to skip subtype checks on the receiver + */ + protected boolean skipReceiverSubtypeCheck( + MethodInvocationTree tree, + AnnotatedTypeMirror methodDefinitionReceiver, + AnnotatedTypeMirror methodCallReceiver) { return false; - } else if (newCastType.getKind() == TypeKind.DECLARED - && newExprType.getKind() == TypeKind.DECLARED) { - int castSize = ((AnnotatedDeclaredType) newCastType).getTypeArguments().size(); - int exprSize = ((AnnotatedDeclaredType) newExprType).getTypeArguments().size(); - - if (castSize != exprSize) { - // Always warn if the cast and expression contain a different number of type - // arguments, e.g. to catch a cast from "Object" to "List<@NonNull Object>". - // TODO: the same number of arguments actually doesn't guarantee anything. - return false; - } - } else if (castTypeKind == TypeKind.TYPEVAR && exprType.getKind() == TypeKind.TYPEVAR) { - // If both the cast type and the casted expression are type variables, then check - // the bounds. - AnnotationMirrorSet lowerBoundAnnotationsCast = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); - AnnotationMirrorSet lowerBoundAnnotationsExpr = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, exprType); - return qualHierarchy.isSubtypeShallow( - lowerBoundAnnotationsExpr, newExprTM, lowerBoundAnnotationsCast, newCastTM) - && typeHierarchy.isSubtypeShallowEffective(exprType, castType); - } - if (castTypeKind == TypeKind.TYPEVAR) { - // If the cast type is a type var, but the expression is not, then check that the - // type of the expression is a subtype of the lower bound. - castAnnos = AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, castType); - } else { - castAnnos = castType.getAnnotations(); - } } - AnnotatedTypeMirror exprTypeWidened = atypeFactory.getWidenedType(exprType, castType); - return qualHierarchy.isSubtypeShallow( - exprTypeWidened.getEffectiveAnnotations(), - exprTypeWidened.getUnderlyingType(), - castAnnos, - newCastTM); - } - - /** - * Return whether casting the {@code exprType} to {@code castType}, a type with a qualifier - * parameter, is legal. - * - *

          If {@code exprType} has qualifier parameter, the cast is legal if the qualifiers are - * invariant. Otherwise, the cast is legal is if the qualifier on both types is bottom. - * - * @param castType a type with a qualifier parameter - * @param exprType type of the expressions that is cast which may or may not have a qualifier - * parameter - * @param top the top qualifier of the hierarchy to check - * @return whether casting the {@code exprType} to {@code castType}, a type with a qualifier - * parameter, is legal. - */ - private boolean isTypeCastSafeInvariant( - AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType, AnnotationMirror top) { - if (!isTypeCastSafe(castType, exprType)) { - return false; - } + /** + * Tests whether the method can be invoked using the receiver of the 'tree' method invocation, + * and issues a "method.invocation.invalid" if the invocation is invalid. + * + *

          This implementation tests whether the receiver in the method invocation is a subtype of + * the method receiver type. This behavior can be specialized by overriding + * skipReceiverSubtypeCheck. + * + * @param method the type of the invoked method + * @param tree the method invocation tree + */ + protected void checkMethodInvocability( + AnnotatedExecutableType method, MethodInvocationTree tree) { + if (method.getReceiverType() == null) { + // Static methods don't have a receiver to check. + return; + } + if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) { + // TODO: Explicit "this()" calls of constructors have an implicit passed + // from the enclosing constructor. We must not use the self type, but + // instead should find a way to determine the receiver of the enclosing constructor. + // rcv = + // ((AnnotatedExecutableType)atypeFactory.getAnnotatedType(atypeFactory.getEnclosingMethod(tree))).getReceiverType(); + return; + } - if (atypeFactory.hasQualifierParameterInHierarchy(exprType, top)) { - // The isTypeCastSafe call above checked that the exprType is a subtype of castType, - // so just check the reverse to check that the qualifiers are equivalent. - return typeHierarchy.isSubtypeShallowEffective(castType, exprType, top); - } - AnnotationMirror castTypeAnno = castType.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror exprTypeAnno = exprType.getEffectiveAnnotationInHierarchy(top); - // Otherwise the cast is unsafe, unless the qualifiers on both cast and expr are bottom. - AnnotationMirror bottom = qualHierarchy.getBottomAnnotation(top); - return AnnotationUtils.areSame(castTypeAnno, bottom) - && AnnotationUtils.areSame(exprTypeAnno, bottom); - } - - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - // validate "tree" instead of "tree.getType()" to prevent duplicate errors. - boolean valid = validateTypeOf(tree) && validateTypeOf(tree.getExpression()); - if (valid) { - checkTypecastSafety(tree); - checkTypecastRedundancy(tree); - } - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); + AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); + AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); - if (atypeFactory.getDependentTypesHelper().hasDependentAnnotations()) { - atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(type, tree.getType()); - } + treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); - if (tree.getType().getKind() == Tree.Kind.INTERSECTION_TYPE) { - AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type; - checkExplicitAnnotationsOnIntersectionBounds( - intersection, ((IntersectionTypeTree) tree.getType()).getBounds()); - } - return super.visitTypeCast(tree, p); - } - - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - // The "reference type" is the type after "instanceof". - Tree patternTree = InstanceOfUtils.getPattern(tree); - if (patternTree != null) { - if (TreeUtils.isBindingPatternTree(patternTree)) { - VariableTree variableTree = BindingPatternUtils.getVariable(patternTree); - validateTypeOf(variableTree); - if (variableTree.getModifiers() != null) { - AnnotatedTypeMirror variableType = atypeFactory.getAnnotatedType(variableTree); - AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(tree.getExpression()); - if (!isTypeCastSafe(variableType, expType)) { - checker.reportWarning(tree, "instanceof.pattern.unsafe", expType, variableTree); - } - } - } else { - // TODO: implement deconstructed patterns. - } - } else { - Tree refTypeTree = tree.getType(); - validateTypeOf(refTypeTree); - if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeMirror refType = atypeFactory.getAnnotatedType(refTypeTree); - AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(tree.getExpression()); - if (typeHierarchy.isSubtype(refType, expType) - && !refType.getAnnotations().equals(expType.getAnnotations())) { - checker.reportWarning(tree, "instanceof.unsafe", expType, refType); + if (!skipReceiverSubtypeCheck(tree, methodReceiver, rcv)) { + // The diagnostic can be a bit misleading because the check is of the receiver but + // `tree` is the entire method invocation (where the receiver might be implicit). + commonAssignmentCheckStartDiagnostic(methodReceiver, treeReceiver, tree); + boolean success = typeHierarchy.isSubtype(treeReceiver, methodReceiver); + commonAssignmentCheckEndDiagnostic(success, null, methodReceiver, treeReceiver, tree); + if (!success) { + reportMethodInvocabilityError(tree, treeReceiver, methodReceiver); + } } - } - } - - return super.visitInstanceOf(tree, p); - } - - /** - * Checks the type of the exception parameter. Subclasses should override {@link - * #checkExceptionParameter} rather than this method to change the behavior of this check. - */ - @Override - public Void visitCatch(CatchTree tree, Void p) { - checkExceptionParameter(tree); - return super.visitCatch(tree, p); - } - - /** - * Checks the type of a thrown exception. Subclasses should override - * checkThrownExpression(ThrowTree tree) rather than this method to change the behavior of this - * check. - */ - @Override - public Void visitThrow(ThrowTree tree, Void p) { - checkThrownExpression(tree); - return super.visitThrow(tree, p); - } - - /** - * Rather than overriding this method, clients should often override {@link - * #visitAnnotatedType(List,Tree)}. That method also handles the case of annotations at the - * beginning of a variable or method declaration. javac parses all those annotations as being on - * the variable or method declaration, even though the ones that are type annotations logically - * belong to the variable type or method return type. - */ - @Override - public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { - visitAnnotatedType(null, tree); - return super.visitAnnotatedType(tree, p); - } - - /** - * Checks an annotated type. Invoked by {@link #visitAnnotatedType(AnnotatedTypeTree, Void)}, - * {@link #visitVariable}, and {@link #visitMethod}. Exists to prevent code duplication among the - * three. Checking in {@code visitVariable} and {@code visitMethod} is needed because there isn't - * an AnnotatedTypeTree within a variable declaration or for a method return type -- all the - * annotations are attached to the VariableTree or MethodTree, respectively. - * - * @param annoTrees annotations written before a variable/method declaration, if this type is from - * one; null otherwise. This might contain type annotations that the Java parser attached to - * the declaration rather than to the type. - * @param typeTree the type that any type annotations in annoTrees apply to - */ - public void visitAnnotatedType( - @Nullable List annoTrees, Tree typeTree) { - warnAboutIrrelevantJavaTypes(annoTrees, typeTree); - } - - /** - * Warns if a type annotation is written on a Java type that is not listed in - * the @RelevantJavaTypes annotation. - * - * @param annoTrees annotations written before a variable/method declaration, if this type is from - * one; null otherwise. This might contain type annotations that the Java parser attached to - * the declaration rather than to the type. - * @param typeTree the type that any type annotations in annoTrees apply to - */ - public void warnAboutIrrelevantJavaTypes( - @Nullable List annoTrees, Tree typeTree) { - if (!shouldWarnAboutIrrelevantJavaTypes()) { - return; } - Tree t = typeTree; - while (true) { - switch (t.getKind()) { - - // Recurse for compound types whose top level is not at the far left. - case ARRAY_TYPE: - t = ((ArrayTypeTree) t).getType(); - continue; - case MEMBER_SELECT: - t = ((MemberSelectTree) t).getExpression(); - continue; - case PARAMETERIZED_TYPE: - t = ((ParameterizedTypeTree) t).getType(); - continue; - - // Base cases - case PRIMITIVE_TYPE: - case IDENTIFIER: - maybeReportAnnoOnIrrelevant(t, TreeUtils.typeOf(t), annoTrees); - return; - case ANNOTATED_TYPE: - AnnotatedTypeTree at = (AnnotatedTypeTree) t; - ExpressionTree underlying = at.getUnderlyingType(); - maybeReportAnnoOnIrrelevant(t, TreeUtils.typeOf(underlying), at.getAnnotations()); - return; - - default: - return; - } - } - } - - /** - * If the given Java basetype is not relevant, report an "anno.on.irrelevant" if it is annotated. - * This method does not necessarily issue an error, but it might. - * - * @param errorLocation where to repor the error - * @param type the Java basetype - * @param annos the annotation on the type - */ - private void maybeReportAnnoOnIrrelevant( - Tree errorLocation, TypeMirror type, List annos) { - List supportedAnnoTrees = supportedAnnoTrees(annos); - if (!supportedAnnoTrees.isEmpty() && !atypeFactory.isRelevant(type)) { - String extraInfo = atypeFactory.irrelevantExtraMessage(); - checker.reportError(errorLocation, "anno.on.irrelevant", annos, type, extraInfo); - } - } - - /** - * Returns true if the checker should issue warnings about irrelevant java types. - * - * @return true if the checker should issue warnings about irrelevant java types - */ - protected boolean shouldWarnAboutIrrelevantJavaTypes() { - return atypeFactory.relevantJavaTypes != null; - } - - /** - * Returns a new list containing only the supported annotations from its argument -- that is, - * those that are part of the current type system. - * - *

          This method ignores aliases of supported annotations that are declaration annotations, - * because they may apply to inner types. - * - * @param annoTrees annotation trees - * @return a new list containing only the supported annotations from its argument - */ - private List supportedAnnoTrees(List annoTrees) { - List result = new ArrayList<>(1); - for (AnnotationTree at : annoTrees) { - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(at); - if (AnnotationUtils.isTypeUseAnnotation(anno) && atypeFactory.isSupportedQualifier(anno)) { - result.add(at); - } - } - return result; - } - - // ********************************************************************** - // Helper methods to provide a single overriding point - // ********************************************************************** - - /** Cache to avoid calling {@link #getExceptionParameterLowerBoundAnnotations} more than once. */ - private @MonotonicNonNull AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCache; - - /** - * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. The same as - * {@link #getExceptionParameterLowerBoundAnnotations}, but uses a cache. - * - * @return a set of AnnotationMirrors that is a lower bound for exception parameters - */ - private AnnotationMirrorSet getExceptionParameterLowerBoundAnnotationsCached() { - if (getExceptionParameterLowerBoundAnnotationsCache == null) { - getExceptionParameterLowerBoundAnnotationsCache = - getExceptionParameterLowerBoundAnnotations(); + /** + * Report a method invocability error. Allows checkers to change how the message is output. + * + * @param tree the AST node at which to report the error + * @param found the actual type of the receiver + * @param expected the expected type of the receiver + */ + protected void reportMethodInvocabilityError( + MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { + checker.reportError( + tree, + "method.invocation.invalid", + TreeUtils.elementFromUse(tree), + found.toString(), + expected.toString()); } - return getExceptionParameterLowerBoundAnnotationsCache; - } - - /** - * Issue error if the exception parameter is not a supertype of the annotation specified by {@link - * #getExceptionParameterLowerBoundAnnotations()}, which is top by default. - * - *

          Subclasses may override this method to change the behavior of this check. Subclasses wishing - * to enforce that exception parameter be annotated with other annotations can just override - * {@link #getExceptionParameterLowerBoundAnnotations()}. - * - * @param tree a CatchTree to check - */ - protected void checkExceptionParameter(CatchTree tree) { - AnnotationMirrorSet requiredAnnotations = getExceptionParameterLowerBoundAnnotationsCached(); - VariableTree excParamTree = tree.getParameter(); - AnnotatedTypeMirror excParamType = atypeFactory.getAnnotatedType(excParamTree); - - for (AnnotationMirror required : requiredAnnotations) { - AnnotationMirror found = excParamType.getAnnotationInHierarchy(required); - assert found != null; - if (!typeHierarchy.isSubtypeShallowEffective(required, excParamType)) { - checker.reportError(excParamTree, "exception.parameter.invalid", found, required); - } - if (excParamType.getKind() == TypeKind.UNION) { - AnnotatedUnionType aut = (AnnotatedUnionType) excParamType; - for (AnnotatedTypeMirror alternativeType : aut.getAlternatives()) { - if (!typeHierarchy.isSubtypeShallowEffective(required, alternativeType)) { - AnnotationMirror alternativeAnno = alternativeType.getAnnotationInHierarchy(required); - checker.reportError( - excParamTree, "exception.parameter.invalid", alternativeAnno, required); - } + /** + * Check that the (explicit) annotations on a new class tree are comparable to the result type + * of the constructor. Issue an error if not. + * + *

          Issue a warning if the annotations on the constructor invocation is a subtype of the + * constructor result type. This is equivalent to down-casting. + * + *

          For type checking of the enclosing expression of inner type instantiations, see {@link + * #checkEnclosingExpr(NewClassTree, AnnotatedTypeMirror.AnnotatedExecutableType)} + * + * @param invocation the AnnotatedDeclaredType of the constructor invocation + * @param constructor the AnnotatedExecutableType of the constructor declaration + * @param newClassTree the NewClassTree + */ + protected void checkConstructorInvocation( + AnnotatedDeclaredType invocation, + AnnotatedExecutableType constructor, + NewClassTree newClassTree) { + // Only check the primary annotations, the type arguments are checked elsewhere. + AnnotationMirrorSet explicitAnnos = atypeFactory.getExplicitNewClassAnnos(newClassTree); + if (explicitAnnos.isEmpty()) { + return; } - } - } - } - - /** - * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. - * - *

          This implementation returns top; subclasses can change this behavior. - * - *

          Note: by default this method is called by {@link #getThrowUpperBoundAnnotations()}, so that - * this annotation is enforced. - * - * @return set of annotation mirrors, one per hierarchy, that form a lower bound of annotations - * that can be written on an exception parameter - */ - protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { - return qualHierarchy.getTopAnnotations(); - } - - /** - * Checks the type of the thrown expression. - * - *

          By default, this method checks that the thrown expression is a subtype of top. - * - *

          Issue error if the thrown expression is not a sub type of the annotation given by {@link - * #getThrowUpperBoundAnnotations()}, the same as {@link - * #getExceptionParameterLowerBoundAnnotations()} by default. - * - *

          Subclasses may override this method to change the behavior of this check. Subclasses wishing - * to enforce that the thrown expression be a subtype of a type besides {@link - * #getExceptionParameterLowerBoundAnnotations}, should override {@link - * #getThrowUpperBoundAnnotations()}. - * - * @param tree a ThrowTree to check - */ - protected void checkThrownExpression(ThrowTree tree) { - AnnotatedTypeMirror throwType = atypeFactory.getAnnotatedType(tree.getExpression()); - TypeMirror throwTM = throwType.getUnderlyingType(); - Set required = getThrowUpperBoundAnnotations(); - switch (throwType.getKind()) { - case NULL: - case DECLARED: - case TYPEVAR: - case WILDCARD: - if (!typeHierarchy.isSubtypeShallowEffective(throwType, required)) { - AnnotationMirrorSet found = throwType.getEffectiveAnnotations(); - checker.reportError(tree.getExpression(), "throw.type.invalid", found, required); - } - break; - - case UNION: - AnnotatedUnionType unionType = (AnnotatedUnionType) throwType; - AnnotationMirrorSet foundPrimary = unionType.getAnnotations(); - if (!qualHierarchy.isSubtypeShallow(foundPrimary, required, throwTM)) { - checker.reportError(tree.getExpression(), "throw.type.invalid", foundPrimary, required); - } - for (AnnotatedTypeMirror altern : unionType.getAlternatives()) { - TypeMirror alternTM = altern.getUnderlyingType(); - if (!qualHierarchy.isSubtypeShallow(altern.getAnnotations(), required, alternTM)) { - checker.reportError( - tree.getExpression(), "throw.type.invalid", altern.getAnnotations(), required); - } + for (AnnotationMirror explicit : explicitAnnos) { + // The return type of the constructor (resultAnnos) must be comparable to the + // annotations on the constructor invocation (explicitAnnos). + boolean resultIsSubtypeOfExplicit = + typeHierarchy.isSubtypeShallowEffective(constructor.getReturnType(), explicit); + if (!(typeHierarchy.isSubtypeShallowEffective(explicit, constructor.getReturnType()) + || resultIsSubtypeOfExplicit)) { + AnnotationMirror resultAnno = + constructor.getReturnType().getAnnotationInHierarchy(explicit); + checker.reportError( + newClassTree, + "constructor.invocation.invalid", + constructor.toString(), + explicit, + resultAnno); + return; + } else if (!resultIsSubtypeOfExplicit) { + AnnotationMirror resultAnno = + constructor.getReturnType().getAnnotationInHierarchy(explicit); + // Issue a warning if the annotations on the constructor invocation is a subtype of + // the constructor result type. This is equivalent to down-casting. + checker.reportWarning( + newClassTree, "cast.unsafe.constructor.invocation", resultAnno, explicit); + return; + } } - break; - default: - throw new BugInCF("Unexpected throw expression type: " + throwType.getKind()); - } - } - - /** - * Returns a set of AnnotationMirrors that is a upper bound for thrown exceptions. - * - *

          Note: by default this method is returns by getExceptionParameterLowerBoundAnnotations(), so - * that this annotation is enforced. - * - *

          (Default is top) - * - * @return set of annotation mirrors, one per hierarchy, that form an upper bound of thrown - * expressions - */ - protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { - return getExceptionParameterLowerBoundAnnotations(); - } - - /** - * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and - * emits an error message (through the compiler's messaging interface) if it is not valid. - * - * @param varTree the AST node for the lvalue (usually a variable) - * @param valueExpTree the AST node for the rvalue (the new value) - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean commonAssignmentCheck( - Tree varTree, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - if (valueExpTree.getKind() == Kind.CONDITIONAL_EXPRESSION) { - ConditionalExpressionTree condExprTree = (ConditionalExpressionTree) valueExpTree; - boolean trueResult = - commonAssignmentCheck(varTree, condExprTree.getTrueExpression(), "assignment"); - boolean falseResult = - commonAssignmentCheck(varTree, condExprTree.getFalseExpression(), "assignment"); - return trueResult && falseResult; } - AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(varTree); - assert varType != null : "no variable found for tree: " + varTree; - - if (!validateType(varTree, varType)) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because validateType() returned false", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return true; + /** + * Helper method to type check the enclosing expression of an inner class instantiation. + * + * @param node the NewClassTree + * @param constructorType the annotatedExecutableType of the constructor declaration + */ + protected void checkEnclosingExpr(NewClassTree node, AnnotatedExecutableType constructorType) { + if (!checkEnclosingExpr) { + return; + } + AnnotatedTypeMirror formalReceiverType = constructorType.getReceiverType(); + if (formalReceiverType != null) { + AnnotatedTypeMirror passedReceiverType = atypeFactory.getReceiverType(node); + commonAssignmentCheck( + formalReceiverType, + passedReceiverType, + node, + "enclosingexpr.type.incompatible"); + } } - return commonAssignmentCheck(varType, valueExpTree, errorKey, extraArgs); - } - - /** - * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and - * emits an error message (through the compiler's messaging interface) if it is not valid. - * - * @param varType the annotated type for the lvalue (usually a variable) - * @param valueExpTree the AST node for the rvalue (the new value) - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - if (shouldSkipUses(valueExpTree)) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because shouldSkipUses() returned true", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return true; - } - if (valueExpTree.getKind() == Tree.Kind.MEMBER_REFERENCE - || valueExpTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - // Member references and lambda expressions are type checked separately - // and do not need to be checked again as arguments. - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because member reference and lambda expression are type checked separately", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return true; - } - boolean result = true; - if (varType.getKind() == TypeKind.ARRAY - && valueExpTree instanceof NewArrayTree - && ((NewArrayTree) valueExpTree).getType() == null) { - AnnotatedTypeMirror compType = ((AnnotatedArrayType) varType).getComponentType(); - NewArrayTree arrayTree = (NewArrayTree) valueExpTree; - assert arrayTree.getInitializers() != null - : "array initializers are not expected to be null in: " + valueExpTree; - result = checkArrayInitialization(compType, arrayTree.getInitializers()) && result; - } - if (!validateTypeOf(valueExpTree)) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "skipping test whether actual is a subtype of expected" - + " because validateType() returned false", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - varType.getKind(), - varType.toString()); - } - return result; - } - AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExpTree); - assert valueType != null : "null type for expression: " + valueExpTree; - result = commonAssignmentCheck(varType, valueType, valueExpTree, errorKey, extraArgs) && result; - return result; - } - - /** - * Checks the validity of an assignment (or pseudo-assignment) from a value to a variable and - * emits an error message (through the compiler's messaging interface) if it is not valid. - * - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueExpTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - commonAssignmentCheckStartDiagnostic(varType, valueType, valueExpTree); - - AnnotatedTypeMirror widenedValueType = atypeFactory.getWidenedType(valueType, varType); - boolean result = typeHierarchy.isSubtype(widenedValueType, varType); - - // TODO: integrate with subtype test. - if (result) { - for (Class mono : atypeFactory.getSupportedMonotonicTypeQualifiers()) { - if (valueType.hasAnnotation(mono) && varType.hasAnnotation(mono)) { - checker.reportError( - valueExpTree, - "monotonic.type.incompatible", - mono.getSimpleName(), - mono.getSimpleName(), - valueType.toString()); - result = false; + /** + * A helper method to check that each passed argument is a subtype of the corresponding required + * argument. Issues an "argument.invalid" error for each passed argument that not a subtype of + * the required one. + * + *

          Note this method requires the lists to have the same length, as it does not handle cases + * like var args. + * + * @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree) + * @param requiredTypes the required types. This may differ from the formal parameter types, + * because it replaces a varargs parameter by multiple parameters with the vararg's element + * type. + * @param passedArgs the expressions passed to the corresponding types + * @param executableName the name of the method or constructor being called + * @param paramNames the names of the callee's formal parameters + */ + protected void checkArguments( + List requiredTypes, + List passedArgs, + CharSequence executableName, + List paramNames) { + int size = requiredTypes.size(); + assert size == passedArgs.size() + : "size mismatch between required args (" + + requiredTypes + + ") and passed args (" + + passedArgs + + ")"; + int maxParamNamesIndex = paramNames.size() - 1; + // Rather weak assertion, due to how varargs parameters are treated. + assert size >= maxParamNamesIndex + : String.format( + "mismatched lengths %d %d %d checkArguments(%s, %s, %s, %s)", + size, + passedArgs.size(), + paramNames.size(), + listToString(requiredTypes), + listToString(passedArgs), + executableName, + listToString(paramNames)); + + for (int i = 0; i < size; ++i) { + AnnotatedTypeMirror requiredType = requiredTypes.get(i); + ExpressionTree passedArg = passedArgs.get(i); + Object paramName = paramNames.get(Math.min(i, maxParamNamesIndex)); + commonAssignmentCheck( + requiredType, + passedArg, + "argument.type.incompatible", + // TODO: for expanded varargs parameters, maybe adjust the name + paramName, + executableName); + scan(passedArg, null); } - } - } else { - // `result` is false. - // Use an error key only if it's overridden by a checker. - reportCommonAssignmentError(varType, widenedValueType, valueExpTree, errorKey, extraArgs); } - commonAssignmentCheckEndDiagnostic(result, null, varType, valueType, valueExpTree); - - return result; - } - - /** - * Report a common assignment error. Allows checkers to change how the message is output. - * - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueTree the location to use when reporting the error message - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - */ - protected void reportCommonAssignmentError( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - FoundRequired pair = FoundRequired.of(valueType, varType); - String valueTypeString = pair.found; - String varTypeString = pair.required; - checker.reportError( - valueTree, errorKey, ArraysPlume.concatenate(extraArgs, valueTypeString, varTypeString)); - } - - /** - * Prints a diagnostic about entering {@code commonAssignmentCheck()}, if the showchecks option - * was set. - * - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - */ - protected final void commonAssignmentCheckStartDiagnostic( - AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueExpTree) { - if (showchecks) { - System.out.printf( - "%s %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", - this.getClass().getSimpleName(), - "about to test whether actual is a subtype of expected", - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - valueType.getKind(), - valueType.toString(), - varType.getKind(), - varType.toString()); - } - } - - /** - * Prints a diagnostic about exiting {@code commonAssignmentCheck()}, if the showchecks option was - * set. - * - * @param success whether the check succeeded or failed - * @param extraMessage information about why the result is what it is; may be null - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - */ - protected final void commonAssignmentCheckEndDiagnostic( - boolean success, - @Nullable String extraMessage, - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueExpTree) { - if (showchecks) { - commonAssignmentCheckEndDiagnostic( - (success - ? "success: actual is subtype of expected" - : "FAILURE: actual is not subtype of expected") - + (extraMessage == null ? "" : " because " + extraMessage), - varType, - valueType, - valueExpTree); - } - } - - /** - * Helper method for printing a diagnostic about exiting {@code commonAssignmentCheck()}, if the - * showchecks option was set. - * - *

          Most clients should call {@link #commonAssignmentCheckEndDiagnostic(boolean, String, - * AnnotatedTypeMirror, AnnotatedTypeMirror, Tree)}. The purpose of this method is to permit - * customizing the message that is printed. - * - * @param message the result, plus information about why the result is what it is - * @param varType the annotated type of the variable - * @param valueType the annotated type of the value - * @param valueExpTree the location to use when reporting the error message - */ - protected final void commonAssignmentCheckEndDiagnostic( - String message, - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueExpTree) { - if (showchecks) { - System.out.printf( - " %s (at %s): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n", - message, - fileAndLineNumber(valueExpTree), - valueExpTree.getKind(), - valueExpTree, - valueType.getKind(), - valueType.toString(), - varType.getKind(), - varType.toString()); - } - } - - /** - * Returns "filename:linenumber:columnnumber" for the given tree. For brevity, the filename is - * given as a simple name, without any directory components. If the line and column numbers are - * unknown, they are omitted. - * - * @param tree a tree - * @return the location of the given tree in source code - */ - private String fileAndLineNumber(Tree tree) { - StringBuilder result = new StringBuilder(); - result.append(Paths.get(root.getSourceFile().getName()).getFileName().toString()); - long valuePos = positions.getStartPosition(root, tree); - LineMap lineMap = root.getLineMap(); - if (valuePos != -1 && lineMap != null) { - result.append(":"); - result.append(lineMap.getLineNumber(valuePos)); - result.append(":"); - result.append(lineMap.getColumnNumber(valuePos)); + // com.sun.tools.javac.util.List has a toString that does not include surrounding "[...]", + // making it hard to interpret in messages. + /** + * Produce a printed representation of a list, in the standard format with surrounding "[...]". + * + * @param lst a list to format + * @return the printed representation of the list + */ + private String listToString(List lst) { + StringJoiner result = new StringJoiner(",", "[", "]"); + for (Object elt : lst) { + result.add(elt.toString()); + } + return result.toString(); } - return result.toString(); - } - - /** - * Class that creates string representations of {@link AnnotatedTypeMirror}s which are only - * verbose if required to differentiate the two types. - */ - protected static class FoundRequired { - /** The found type's string representation. */ - public final String found; - - /** The required type's string representation. */ - public final String required; + /** + * Returns true if both types are type variables and outer contains inner. Outer contains inner + * implies: {@literal inner.upperBound <: outer.upperBound outer.lowerBound <: + * inner.lowerBound}. + * + * @return true if both types are type variables and outer contains inner + */ + protected boolean testTypevarContainment(AnnotatedTypeMirror inner, AnnotatedTypeMirror outer) { + if (inner.getKind() == TypeKind.TYPEVAR && outer.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable innerAtv = (AnnotatedTypeVariable) inner; + AnnotatedTypeVariable outerAtv = (AnnotatedTypeVariable) outer; + + if (AnnotatedTypes.areCorrespondingTypeVariables(elements, innerAtv, outerAtv)) { + return typeHierarchy.isSubtype(innerAtv.getUpperBound(), outerAtv.getUpperBound()) + && typeHierarchy.isSubtype( + outerAtv.getLowerBound(), innerAtv.getLowerBound()); + } + } - private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { - if (shouldPrintVerbose(found, required)) { - this.found = found.toString(true); - this.required = required.toString(true); - } else { - this.found = found.toString(); - this.required = required.toString(); - } + return false; } - /** Create a FoundRequired for a type and bounds. */ - private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { - if (shouldPrintVerbose(found, required)) { - this.found = found.toString(true); - this.required = required.toString(true); - } else { - this.found = found.toString(); - this.required = required.toString(); - } + /** + * Create an OverrideChecker. + * + *

          This exists so that subclasses can subclass OverrideChecker and use their subclass instead + * of using OverrideChecker itself. + * + * @param overriderTree the AST node of the overriding method or method reference + * @param overriderMethodType the type of the overriding method + * @param overriderType the type enclosing the overrider method, usually an + * AnnotatedDeclaredType; for Method References may be something else + * @param overriderReturnType the return type of the overriding method + * @param overriddenMethodType the type of the overridden method + * @param overriddenType the declared type enclosing the overridden method + * @param overriddenReturnType the return type of the overridden method + * @return an OverrideChecker + */ + protected OverrideChecker createOverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overriderMethodType, + AnnotatedTypeMirror overriderType, + AnnotatedTypeMirror overriderReturnType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + return new OverrideChecker( + overriderTree, + overriderMethodType, + overriderType, + overriderReturnType, + overriddenMethodType, + overriddenType, + overriddenReturnType); } /** - * Creates string representations of {@link AnnotatedTypeMirror}s which are only verbose if - * required to differentiate the two types. + * Type checks that a method may override another method. Uses an OverrideChecker subclass as + * created by {@link #createOverrideChecker}. This version of the method uses the annotated type + * factory to get the annotated type of the overriding method, and does NOT expose that type. * - * @param found the found annotation - * @param required the required annotation - * @return a string representation of the two annotations + * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, + * AnnotatedTypeMirror.AnnotatedDeclaredType) + * @param overriderTree declaration tree of overriding method + * @param overriderType type of overriding class + * @param overriddenMethodType type of overridden method + * @param overriddenType type of overridden class + * @return true if the override is allowed */ - public static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeMirror required) { - return new FoundRequired(found, required); + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedDeclaredType overriderType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType) { + + // Get the type of the overriding method. + AnnotatedExecutableType overriderMethodType = atypeFactory.getAnnotatedType(overriderTree); + + // Call the other version of the method, which takes overriderMethodType. Both versions + // exist to allow checkers to override one or the other depending on their needs. + return checkOverride( + overriderTree, + overriderMethodType, + overriderType, + overriddenMethodType, + overriddenType); } /** - * Creates string representations of {@link AnnotatedTypeMirror} and {@link - * AnnotatedTypeParameterBounds}s which are only verbose if required to differentiate the two - * types. + * Type checks that a method may override another method. Uses an OverrideChecker subclass as + * created by {@link #createOverrideChecker}. This version of the method exposes the + * AnnotatedExecutableType of the overriding method. Override this version of the method if you + * need to access that type. * - * @param found the found annotation - * @param required the required annotation - * @return a string representation of the two annotations + * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedDeclaredType, + * AnnotatedTypeMirror.AnnotatedExecutableType, AnnotatedTypeMirror.AnnotatedDeclaredType) + * @param overriderTree declaration tree of overriding method + * @param overriderMethodType type of the overriding method + * @param overriderType type of overriding class + * @param overriddenMethodType type of overridden method + * @param overriddenType type of overridden class + * @return true if the override is allowed */ - public static FoundRequired of( - AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) { - return new FoundRequired(found, required); - } - } - - /** - * Return whether or not the verbose toString should be used when printing the two annotated - * types. - * - * @param atm1 the first AnnotatedTypeMirror - * @param atm2 the second AnnotatedTypeMirror - * @return true iff neither argument contains "@", or there are two annotated types (in either - * ATM) such that their toStrings are the same but their verbose toStrings differ - */ - private static boolean shouldPrintVerbose(AnnotatedTypeMirror atm1, AnnotatedTypeMirror atm2) { - if (!atm1.toString().contains("@") && !atm2.toString().contains("@")) { - return true; - } - return containsSameToString(atm1, atm2); - } - - /** - * Return whether or not the verbose toString should be used when printing the annotated type and - * the bounds it is not within. - * - * @param atm the type - * @param bounds the bounds - * @return true iff bounds does not contain "@", or there are two annotated types (in either - * argument) such that their toStrings are the same but their verbose toStrings differ - */ - private static boolean shouldPrintVerbose( - AnnotatedTypeMirror atm, AnnotatedTypeParameterBounds bounds) { - if (!atm.toString().contains("@") && !bounds.toString().contains("@")) { - return true; - } - return containsSameToString(atm, bounds.getUpperBound(), bounds.getLowerBound()); - } - - /** - * A scanner that indicates whether any (component) types have the same toString but different - * verbose toString. If so, the Checker Framework prints types verbosely. - */ - private static final SimpleAnnotatedTypeScanner> - checkContainsSameToString = - new SimpleAnnotatedTypeScanner<>( - (AnnotatedTypeMirror type, Map map) -> { - if (type == null) { - return false; - } - String simple = type.toString(); - String verbose = map.get(simple); - if (verbose == null) { - map.put(simple, type.toString(true)); - return false; - } else { - return !verbose.equals(type.toString(true)); - } - }, - Boolean::logicalOr, - false); - - /** - * Return true iff there are two annotated types (anywhere in any ATM) such that their toStrings - * are the same but their verbose toStrings differ. If so, the Checker Framework prints types - * verbosely. - * - * @param atms annotated type mirrors to compare - * @return true iff there are two annotated types (anywhere in any ATM) such that their toStrings - * are the same but their verbose toStrings differ - */ - private static boolean containsSameToString(AnnotatedTypeMirror... atms) { - Map simpleToVerbose = new HashMap<>(); - for (AnnotatedTypeMirror atm : atms) { - if (checkContainsSameToString.visit(atm, simpleToVerbose)) { - return true; - } - } + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedExecutableType overriderMethodType, + AnnotatedDeclaredType overriderType, + AnnotatedExecutableType overriddenMethodType, + AnnotatedDeclaredType overriddenType) { + + // This needs to be done before overriderMethodType.getReturnType() and + // overriddenMethodType.getReturnType() + if (overriderMethodType.getTypeVariables().isEmpty() + && !overriddenMethodType.getTypeVariables().isEmpty()) { + overriddenMethodType = overriddenMethodType.getErased(); + } - return false; - } - - /** - * Checks that the array initializers are consistent with the array type. - * - * @param type the array elemen type - * @param initializers the initializers - * @return true if the check succeeds, false if an error message was issued - */ - protected boolean checkArrayInitialization( - AnnotatedTypeMirror type, List initializers) { - // TODO: set assignment context like for method arguments? - // Also in AbstractFlow. - boolean result = true; - for (ExpressionTree init : initializers) { - result = commonAssignmentCheck(type, init, "array.initializer.type.incompatible") && result; - } - return result; - } - - /** - * Checks that the annotations on the type arguments supplied to a type or a method invocation are - * within the bounds of the type variables as declared, and issues the - * "type.argument.type.incompatible" error if they are not. - * - * @param toptree the tree for error reporting, only used for inferred type arguments - * @param paramBounds the bounds of the type parameters from a class or method declaration - * @param typeargs the type arguments from the type or method invocation - * @param typeargTrees the type arguments as trees, used for error reporting - */ - protected void checkTypeArguments( - Tree toptree, - List paramBounds, - List typeargs, - List typeargTrees, - CharSequence typeOrMethodName, - List paramNames) { - - // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s%n", - // toptree, paramBounds, typeargs, typeargTrees); - - // If there are no type variables, do nothing. - if (paramBounds.isEmpty()) { - return; + OverrideChecker overrideChecker = + createOverrideChecker( + overriderTree, + overriderMethodType, + overriderType, + overriderMethodType.getReturnType(), + overriddenMethodType, + overriddenType, + overriddenMethodType.getReturnType()); + + return overrideChecker.checkOverride(); } - int size = paramBounds.size(); - assert size == typeargs.size() - : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " - + typeargs - + " and type parameter bounds" - + paramBounds; + /** + * Check that a method reference is allowed. Uses the OverrideChecker class. + * + * @param memberReferenceTree the tree for the method reference + * @return true if the method reference is allowed + */ + protected boolean checkMethodReferenceAsOverride( + MemberReferenceTree memberReferenceTree, Void p) { + + IPair result = + atypeFactory.getFnInterfaceFromTree(memberReferenceTree); + // The type to which the member reference is assigned -- also known as the target type of + // the reference. + AnnotatedTypeMirror functionalInterface = result.first; + // The type of the single method that is declared by the functional interface. + AnnotatedExecutableType functionType = result.second; + + // ========= Overriding Type ========= + // This doesn't get the correct type for a "MyOuter.super" based on the receiver of the + // enclosing method. + // That is handled separately in method receiver check. + + // The tree before :: is an expression or type use. + ExpressionTree preColonTree = memberReferenceTree.getQualifierExpression(); + MemberReferenceKind memRefKind = + MemberReferenceKind.getMemberReferenceKind(memberReferenceTree); + AnnotatedTypeMirror enclosingType; + if (TreeUtils.isLikeDiamondMemberReference(memberReferenceTree)) { + TypeElement typeElt = TypesUtils.getTypeElement(TreeUtils.typeOf(preColonTree)); + enclosingType = atypeFactory.getAnnotatedType(typeElt); + } else if (memberReferenceTree.getMode() == ReferenceMode.NEW + || memRefKind == MemberReferenceKind.UNBOUND + || memRefKind == MemberReferenceKind.STATIC) { + // The tree before :: is a type tree. + enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + } else { + // The tree before :: is an expression. + enclosingType = atypeFactory.getAnnotatedType(preColonTree); + } - for (int i = 0; i < size; i++) { + // ========= Overriding Executable ========= + // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference + ExecutableElement compileTimeDeclaration = TreeUtils.elementFromUse(memberReferenceTree); - AnnotatedTypeParameterBounds bounds = paramBounds.get(i); - AnnotatedTypeMirror typeArg = typeargs.get(i); + ParameterizedExecutableType preInference = + atypeFactory.methodFromUseWithoutTypeArgInference( + memberReferenceTree, compileTimeDeclaration, enclosingType); + if (TreeUtils.needsTypeArgInference(memberReferenceTree)) { + if (!checkTypeArgumentInference(memberReferenceTree, preInference.executableType)) { + return true; + } + } - if (atypeFactory.ignoreRawTypeArguments - && AnnotatedTypes.isTypeArgOfRawType(bounds.getUpperBound())) { - continue; - } + // The type of the compileTimeDeclaration if it were invoked with a receiver expression + // of type {@code type} + AnnotatedExecutableType invocationType = + atypeFactory.methodFromUse( + memberReferenceTree, compileTimeDeclaration, enclosingType) + .executableType; + + // This needs to be done before invocationType.getReturnType() and + // functionType.getReturnType() + if (invocationType.getTypeVariables().isEmpty() + && !functionType.getTypeVariables().isEmpty()) { + functionType = functionType.getErased(); + } - AnnotatedTypeMirror paramUpperBound = bounds.getUpperBound(); + // Use the function type's parameters to resolve polymorphic qualifiers. + QualifierPolymorphism poly = atypeFactory.getQualifierPolymorphism(); + poly.resolve(functionType, invocationType); - Tree reportErrorToTree; - if (typeargTrees == null || typeargTrees.isEmpty()) { - // The type arguments were inferred, report the error on the method invocation. - reportErrorToTree = toptree; - } else { - reportErrorToTree = typeargTrees.get(i); - } + AnnotatedTypeMirror invocationReturnType; + if (compileTimeDeclaration.getKind() == ElementKind.CONSTRUCTOR) { + if (enclosingType.getKind() == TypeKind.ARRAY) { + // Special casing for the return of array constructor + invocationReturnType = enclosingType; + } else { + invocationReturnType = + atypeFactory.getResultingTypeOfConstructorMemberReference( + memberReferenceTree, invocationType); + } + } else { + invocationReturnType = invocationType.getReturnType(); + } - checkHasQualifierParameterAsTypeArgument(typeArg, paramUpperBound, toptree); - commonAssignmentCheck( - paramUpperBound, - typeArg, - reportErrorToTree, - "type.argument.type.incompatible", - paramNames.get(i), - typeOrMethodName); - - if (!typeHierarchy.isSubtype(bounds.getLowerBound(), typeArg)) { - FoundRequired fr = FoundRequired.of(typeArg, bounds); - checker.reportError( - reportErrorToTree, - "type.argument.type.incompatible", - paramNames.get(i), - typeOrMethodName, - fr.found, - fr.required); - } - } - } - - /** - * Reports an error if the type argument has a qualifier parameter and the type parameter upper - * bound does not have a qualifier parameter. - * - * @param typeArgument type argument - * @param typeParameterUpperBound upper bound of the type parameter - * @param reportError where to report the error - */ - private void checkHasQualifierParameterAsTypeArgument( - AnnotatedTypeMirror typeArgument, - AnnotatedTypeMirror typeParameterUpperBound, - Tree reportError) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (atypeFactory.hasQualifierParameterInHierarchy(typeArgument, top) - && !getTypeFactory().hasQualifierParameterInHierarchy(typeParameterUpperBound, top)) { - checker.reportError(reportError, "type.argument.invalid.hasqualparam", top); - } - } - } - - /** - * Indicates whether to skip subtype checks on the receiver when checking method invocability. A - * visitor may, for example, allow a method to be invoked even if the receivers are siblings in a - * hierarchy, provided that some other condition (implemented by the visitor) is satisfied. - * - * @param tree the method invocation tree - * @param methodDefinitionReceiver the ATM of the receiver of the method definition - * @param methodCallReceiver the ATM of the receiver of the method call - * @return whether to skip subtype checks on the receiver - */ - protected boolean skipReceiverSubtypeCheck( - MethodInvocationTree tree, - AnnotatedTypeMirror methodDefinitionReceiver, - AnnotatedTypeMirror methodCallReceiver) { - return false; - } - - /** - * Tests whether the method can be invoked using the receiver of the 'tree' method invocation, and - * issues a "method.invocation.invalid" if the invocation is invalid. - * - *

          This implementation tests whether the receiver in the method invocation is a subtype of the - * method receiver type. This behavior can be specialized by overriding skipReceiverSubtypeCheck. - * - * @param method the type of the invoked method - * @param tree the method invocation tree - */ - protected void checkMethodInvocability( - AnnotatedExecutableType method, MethodInvocationTree tree) { - if (method.getReceiverType() == null) { - // Static methods don't have a receiver to check. - return; - } - if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) { - // TODO: Explicit "this()" calls of constructors have an implicit passed - // from the enclosing constructor. We must not use the self type, but - // instead should find a way to determine the receiver of the enclosing constructor. - // rcv = - // ((AnnotatedExecutableType)atypeFactory.getAnnotatedType(atypeFactory.getEnclosingMethod(tree))).getReceiverType(); - return; + AnnotatedTypeMirror functionTypeReturnType = functionType.getReturnType(); + if (functionTypeReturnType.getKind() == TypeKind.VOID) { + // If the functional interface return type is void, the overriding return type doesn't + // matter. + functionTypeReturnType = invocationReturnType; + } + + if (functionalInterface.getKind() == TypeKind.DECLARED) { + // Check the member reference as if invocationType overrides functionType. + OverrideChecker overrideChecker = + createOverrideChecker( + memberReferenceTree, + invocationType, + enclosingType, + invocationReturnType, + functionType, + (AnnotatedDeclaredType) functionalInterface, + functionTypeReturnType); + return overrideChecker.checkOverride(); + } else { + // If the functionalInterface is not a declared type, it must be from a wildcard from a + // raw type. In that case, only return false if raw types should not be ignored. + return !atypeFactory.ignoreRawTypeArguments; + } } - AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); - AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); - AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(tree); + /** + * Class to perform method override and method reference checks. + * + *

          Method references are checked similarly to method overrides, with the method reference + * viewed as overriding the functional interface's method. + * + *

          Checks that an overriding method's return type, parameter types, and receiver type are + * correct with respect to the annotations on the overridden method's return type, parameter + * types, and receiver type. + * + *

          Furthermore, any contracts on the method must satisfy behavioral subtyping, that is, + * postconditions must be at least as strong as the postcondition on the superclass, and + * preconditions must be at most as strong as the condition on the superclass. + * + *

          This method returns the result of the check, but also emits error messages as a side + * effect. + */ + public class OverrideChecker { + + /** + * The declaration of an overriding method. Or, it could be a method reference that is being + * passed to a method. + */ + protected final Tree overriderTree; + + /** True if {@link #overriderTree} is a MEMBER_REFERENCE. */ + protected final boolean isMethodReference; + + /** The type of the overriding method. */ + protected final AnnotatedExecutableType overrider; + + /** The subtype that declares the overriding method. */ + protected final AnnotatedTypeMirror overriderType; + + /** The type of the overridden method. */ + protected final AnnotatedExecutableType overridden; + + /** The supertype that declares the overridden method. */ + protected final AnnotatedDeclaredType overriddenType; + + /** The teturn type of the overridden method. */ + protected final AnnotatedTypeMirror overriddenReturnType; + + /** The return type of the overriding method. */ + protected final AnnotatedTypeMirror overriderReturnType; + + /** + * Create an OverrideChecker. + * + *

          Notice that the return types are passed in separately. This is to support some types + * of method references where the overrider's return type is not the appropriate type to + * check. + * + * @param overriderTree the AST node of the overriding method or method reference + * @param overrider the type of the overriding method + * @param overriderType the type enclosing the overrider method, usually an + * AnnotatedDeclaredType; for Method References may be something else + * @param overriderReturnType the return type of the overriding method + * @param overridden the type of the overridden method + * @param overriddenType the declared type enclosing the overridden method + * @param overriddenReturnType the return type of the overridden method + */ + public OverrideChecker( + Tree overriderTree, + AnnotatedExecutableType overrider, + AnnotatedTypeMirror overriderType, + AnnotatedTypeMirror overriderReturnType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType, + AnnotatedTypeMirror overriddenReturnType) { + + this.overriderTree = overriderTree; + this.overrider = overrider; + this.overriderType = overriderType; + this.overriderReturnType = overriderReturnType; + this.overridden = overridden; + this.overriddenType = overriddenType; + this.overriddenReturnType = overriddenReturnType; + + this.isMethodReference = overriderTree.getKind() == Tree.Kind.MEMBER_REFERENCE; + } - treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); + /** + * Perform the check. + * + * @return true if the override is allowed + */ + public boolean checkOverride() { + if (checker.shouldSkipUses(overriddenType.getUnderlyingType().asElement())) { + return true; + } - if (!skipReceiverSubtypeCheck(tree, methodReceiver, rcv)) { - // The diagnostic can be a bit misleading because the check is of the receiver but - // `tree` is the entire method invocation (where the receiver might be implicit). - commonAssignmentCheckStartDiagnostic(methodReceiver, treeReceiver, tree); - boolean success = typeHierarchy.isSubtype(treeReceiver, methodReceiver); - commonAssignmentCheckEndDiagnostic(success, null, methodReceiver, treeReceiver, tree); - if (!success) { - reportMethodInvocabilityError(tree, treeReceiver, methodReceiver); - } - } - } - - /** - * Report a method invocability error. Allows checkers to change how the message is output. - * - * @param tree the AST node at which to report the error - * @param found the actual type of the receiver - * @param expected the expected type of the receiver - */ - protected void reportMethodInvocabilityError( - MethodInvocationTree tree, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) { - checker.reportError( - tree, - "method.invocation.invalid", - TreeUtils.elementFromUse(tree), - found.toString(), - expected.toString()); - } - - /** - * Check that the (explicit) annotations on a new class tree are comparable to the result type of - * the constructor. Issue an error if not. - * - *

          Issue a warning if the annotations on the constructor invocation is a subtype of the - * constructor result type. This is equivalent to down-casting. - * - *

          For type checking of the enclosing expression of inner type instantiations, see {@link - * #checkEnclosingExpr(NewClassTree, AnnotatedTypeMirror.AnnotatedExecutableType)} - * - * @param invocation the AnnotatedDeclaredType of the constructor invocation - * @param constructor the AnnotatedExecutableType of the constructor declaration - * @param newClassTree the NewClassTree - */ - protected void checkConstructorInvocation( - AnnotatedDeclaredType invocation, - AnnotatedExecutableType constructor, - NewClassTree newClassTree) { - // Only check the primary annotations, the type arguments are checked elsewhere. - AnnotationMirrorSet explicitAnnos = atypeFactory.getExplicitNewClassAnnos(newClassTree); - if (explicitAnnos.isEmpty()) { - return; - } - for (AnnotationMirror explicit : explicitAnnos) { - // The return type of the constructor (resultAnnos) must be comparable to the - // annotations on the constructor invocation (explicitAnnos). - boolean resultIsSubtypeOfExplicit = - typeHierarchy.isSubtypeShallowEffective(constructor.getReturnType(), explicit); - if (!(typeHierarchy.isSubtypeShallowEffective(explicit, constructor.getReturnType()) - || resultIsSubtypeOfExplicit)) { - AnnotationMirror resultAnno = - constructor.getReturnType().getAnnotationInHierarchy(explicit); - checker.reportError( - newClassTree, - "constructor.invocation.invalid", - constructor.toString(), - explicit, - resultAnno); - return; - } else if (!resultIsSubtypeOfExplicit) { - AnnotationMirror resultAnno = - constructor.getReturnType().getAnnotationInHierarchy(explicit); - // Issue a warning if the annotations on the constructor invocation is a subtype of - // the constructor result type. This is equivalent to down-casting. - checker.reportWarning( - newClassTree, "cast.unsafe.constructor.invocation", resultAnno, explicit); - return; - } - } - } - - /** - * Helper method to type check the enclosing expression of an inner class instantiation. - * - * @param node the NewClassTree - * @param constructorType the annotatedExecutableType of the constructor declaration - */ - protected void checkEnclosingExpr(NewClassTree node, AnnotatedExecutableType constructorType) { - if (!checkEnclosingExpr) { - return; - } - AnnotatedTypeMirror formalReceiverType = constructorType.getReceiverType(); - if (formalReceiverType != null) { - AnnotatedTypeMirror passedReceiverType = atypeFactory.getReceiverType(node); - commonAssignmentCheck( - formalReceiverType, passedReceiverType, node, "enclosingexpr.type.incompatible"); - } - } - - /** - * A helper method to check that each passed argument is a subtype of the corresponding required - * argument. Issues an "argument.invalid" error for each passed argument that not a subtype of the - * required one. - * - *

          Note this method requires the lists to have the same length, as it does not handle cases - * like var args. - * - * @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree) - * @param requiredTypes the required types. This may differ from the formal parameter types, - * because it replaces a varargs parameter by multiple parameters with the vararg's element - * type. - * @param passedArgs the expressions passed to the corresponding types - * @param executableName the name of the method or constructor being called - * @param paramNames the names of the callee's formal parameters - */ - protected void checkArguments( - List requiredTypes, - List passedArgs, - CharSequence executableName, - List paramNames) { - int size = requiredTypes.size(); - assert size == passedArgs.size() - : "size mismatch between required args (" - + requiredTypes - + ") and passed args (" - + passedArgs - + ")"; - int maxParamNamesIndex = paramNames.size() - 1; - // Rather weak assertion, due to how varargs parameters are treated. - assert size >= maxParamNamesIndex - : String.format( - "mismatched lengths %d %d %d checkArguments(%s, %s, %s, %s)", - size, - passedArgs.size(), - paramNames.size(), - listToString(requiredTypes), - listToString(passedArgs), - executableName, - listToString(paramNames)); - - for (int i = 0; i < size; ++i) { - AnnotatedTypeMirror requiredType = requiredTypes.get(i); - ExpressionTree passedArg = passedArgs.get(i); - Object paramName = paramNames.get(Math.min(i, maxParamNamesIndex)); - commonAssignmentCheck( - requiredType, - passedArg, - "argument.type.incompatible", - // TODO: for expanded varargs parameters, maybe adjust the name - paramName, - executableName); - scan(passedArg, null); - } - } - - // com.sun.tools.javac.util.List has a toString that does not include surrounding "[...]", - // making it hard to interpret in messages. - /** - * Produce a printed representation of a list, in the standard format with surrounding "[...]". - * - * @param lst a list to format - * @return the printed representation of the list - */ - private String listToString(List lst) { - StringJoiner result = new StringJoiner(",", "[", "]"); - for (Object elt : lst) { - result.add(elt.toString()); - } - return result.toString(); - } - - /** - * Returns true if both types are type variables and outer contains inner. Outer contains inner - * implies: {@literal inner.upperBound <: outer.upperBound outer.lowerBound <: inner.lowerBound}. - * - * @return true if both types are type variables and outer contains inner - */ - protected boolean testTypevarContainment(AnnotatedTypeMirror inner, AnnotatedTypeMirror outer) { - if (inner.getKind() == TypeKind.TYPEVAR && outer.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable innerAtv = (AnnotatedTypeVariable) inner; - AnnotatedTypeVariable outerAtv = (AnnotatedTypeVariable) outer; - - if (AnnotatedTypes.areCorrespondingTypeVariables(elements, innerAtv, outerAtv)) { - return typeHierarchy.isSubtype(innerAtv.getUpperBound(), outerAtv.getUpperBound()) - && typeHierarchy.isSubtype(outerAtv.getLowerBound(), innerAtv.getLowerBound()); - } - } + boolean result = checkReturn(); + result &= checkParameters(); + if (isMethodReference) { + result &= checkMemberReferenceReceivers(); + } else { + result &= checkReceiverOverride(); + } + checkPreAndPostConditions(); + checkPurity(); - return false; - } - - /** - * Create an OverrideChecker. - * - *

          This exists so that subclasses can subclass OverrideChecker and use their subclass instead - * of using OverrideChecker itself. - * - * @param overriderTree the AST node of the overriding method or method reference - * @param overriderMethodType the type of the overriding method - * @param overriderType the type enclosing the overrider method, usually an AnnotatedDeclaredType; - * for Method References may be something else - * @param overriderReturnType the return type of the overriding method - * @param overriddenMethodType the type of the overridden method - * @param overriddenType the declared type enclosing the overridden method - * @param overriddenReturnType the return type of the overridden method - * @return an OverrideChecker - */ - protected OverrideChecker createOverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overriderMethodType, - AnnotatedTypeMirror overriderType, - AnnotatedTypeMirror overriderReturnType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - return new OverrideChecker( - overriderTree, - overriderMethodType, - overriderType, - overriderReturnType, - overriddenMethodType, - overriddenType, - overriddenReturnType); - } - - /** - * Type checks that a method may override another method. Uses an OverrideChecker subclass as - * created by {@link #createOverrideChecker}. This version of the method uses the annotated type - * factory to get the annotated type of the overriding method, and does NOT expose that type. - * - * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType, - * AnnotatedTypeMirror.AnnotatedDeclaredType) - * @param overriderTree declaration tree of overriding method - * @param overriderType type of overriding class - * @param overriddenMethodType type of overridden method - * @param overriddenType type of overridden class - * @return true if the override is allowed - */ - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedDeclaredType overriderType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType) { - - // Get the type of the overriding method. - AnnotatedExecutableType overriderMethodType = atypeFactory.getAnnotatedType(overriderTree); - - // Call the other version of the method, which takes overriderMethodType. Both versions - // exist to allow checkers to override one or the other depending on their needs. - return checkOverride( - overriderTree, overriderMethodType, overriderType, overriddenMethodType, overriddenType); - } - - /** - * Type checks that a method may override another method. Uses an OverrideChecker subclass as - * created by {@link #createOverrideChecker}. This version of the method exposes the - * AnnotatedExecutableType of the overriding method. Override this version of the method if you - * need to access that type. - * - * @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedDeclaredType, - * AnnotatedTypeMirror.AnnotatedExecutableType, AnnotatedTypeMirror.AnnotatedDeclaredType) - * @param overriderTree declaration tree of overriding method - * @param overriderMethodType type of the overriding method - * @param overriderType type of overriding class - * @param overriddenMethodType type of overridden method - * @param overriddenType type of overridden class - * @return true if the override is allowed - */ - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedExecutableType overriderMethodType, - AnnotatedDeclaredType overriderType, - AnnotatedExecutableType overriddenMethodType, - AnnotatedDeclaredType overriddenType) { - - // This needs to be done before overriderMethodType.getReturnType() and - // overriddenMethodType.getReturnType() - if (overriderMethodType.getTypeVariables().isEmpty() - && !overriddenMethodType.getTypeVariables().isEmpty()) { - overriddenMethodType = overriddenMethodType.getErased(); - } + return result; + } - OverrideChecker overrideChecker = - createOverrideChecker( - overriderTree, - overriderMethodType, - overriderType, - overriderMethodType.getReturnType(), - overriddenMethodType, - overriddenType, - overriddenMethodType.getReturnType()); - - return overrideChecker.checkOverride(); - } - - /** - * Check that a method reference is allowed. Uses the OverrideChecker class. - * - * @param memberReferenceTree the tree for the method reference - * @return true if the method reference is allowed - */ - protected boolean checkMethodReferenceAsOverride( - MemberReferenceTree memberReferenceTree, Void p) { - - IPair result = - atypeFactory.getFnInterfaceFromTree(memberReferenceTree); - // The type to which the member reference is assigned -- also known as the target type of - // the reference. - AnnotatedTypeMirror functionalInterface = result.first; - // The type of the single method that is declared by the functional interface. - AnnotatedExecutableType functionType = result.second; - - // ========= Overriding Type ========= - // This doesn't get the correct type for a "MyOuter.super" based on the receiver of the - // enclosing method. - // That is handled separately in method receiver check. - - // The tree before :: is an expression or type use. - ExpressionTree preColonTree = memberReferenceTree.getQualifierExpression(); - MemberReferenceKind memRefKind = - MemberReferenceKind.getMemberReferenceKind(memberReferenceTree); - AnnotatedTypeMirror enclosingType; - if (TreeUtils.isLikeDiamondMemberReference(memberReferenceTree)) { - TypeElement typeElt = TypesUtils.getTypeElement(TreeUtils.typeOf(preColonTree)); - enclosingType = atypeFactory.getAnnotatedType(typeElt); - } else if (memberReferenceTree.getMode() == ReferenceMode.NEW - || memRefKind == MemberReferenceKind.UNBOUND - || memRefKind == MemberReferenceKind.STATIC) { - // The tree before :: is a type tree. - enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(preColonTree); - } else { - // The tree before :: is an expression. - enclosingType = atypeFactory.getAnnotatedType(preColonTree); - } + /** Check that an override respects purity. */ + private void checkPurity() { + String msgKey = + isMethodReference ? "purity.invalid.methodref" : "purity.invalid.overriding"; + + // check purity annotations + EnumSet superPurity = + PurityUtils.getPurityKinds(atypeFactory, overridden.getElement()); + EnumSet subPurity = + PurityUtils.getPurityKinds(atypeFactory, overrider.getElement()); + if (!subPurity.containsAll(superPurity)) { + checker.reportError( + overriderTree, + msgKey, + overriderType, + overrider, + overriddenType, + overridden, + subPurity, + superPurity); + } + } - // ========= Overriding Executable ========= - // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference - ExecutableElement compileTimeDeclaration = TreeUtils.elementFromUse(memberReferenceTree); + /** + * Checks that overrides obey behavioral subtyping, that is, postconditions must be at least + * as strong as the postcondition on the superclass, and preconditions must be at most as + * strong as the condition on the superclass. + */ + private void checkPreAndPostConditions() { + String msgKey = isMethodReference ? "methodref" : "override"; + if (isMethodReference) { + // TODO: Support postconditions and method references. + // The parse context always expects instance methods, but method references can be + // static. + return; + } - ParameterizedExecutableType preInference = - atypeFactory.methodFromUseWithoutTypeArgInference( - memberReferenceTree, compileTimeDeclaration, enclosingType); - if (TreeUtils.needsTypeArgInference(memberReferenceTree)) { - if (!checkTypeArgumentInference(memberReferenceTree, preInference.executableType)) { - return true; - } - } + ContractsFromMethod contractsUtils = atypeFactory.getContractsFromMethod(); + + // Check preconditions + Set superPre = contractsUtils.getPreconditions(overridden.getElement()); + Set subPre = contractsUtils.getPreconditions(overrider.getElement()); + Set> superPre2 = + parseAndLocalizeContracts(superPre, overridden); + Set> subPre2 = + parseAndLocalizeContracts(subPre, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String premsg = "contracts.precondition." + msgKey + ".invalid"; + checkContractsSubset(overriderType, overriddenType, subPre2, superPre2, premsg); + + // Check postconditions + Set superPost = + contractsUtils.getPostconditions(overridden.getElement()); + Set subPost = contractsUtils.getPostconditions(overrider.getElement()); + Set> superPost2 = + parseAndLocalizeContracts(superPost, overridden); + Set> subPost2 = + parseAndLocalizeContracts(subPost, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String postmsg = "contracts.postcondition." + msgKey + ".invalid"; + checkContractsSubset(overriderType, overriddenType, superPost2, subPost2, postmsg); + + // Check conditional postconditions + Set superCPost = + contractsUtils.getConditionalPostconditions(overridden.getElement()); + Set subCPost = + contractsUtils.getConditionalPostconditions(overrider.getElement()); + // consider only 'true' postconditions + Set superCPostTrue = filterConditionalPostconditions(superCPost, true); + Set subCPostTrue = filterConditionalPostconditions(subCPost, true); + Set> superCPostTrue2 = + parseAndLocalizeContracts(superCPostTrue, overridden); + Set> subCPostTrue2 = + parseAndLocalizeContracts(subCPostTrue, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String posttruemsg = "contracts.conditional.postcondition.true." + msgKey + ".invalid"; + checkContractsSubset( + overriderType, overriddenType, superCPostTrue2, subCPostTrue2, posttruemsg); + + // consider only 'false' postconditions + Set superCPostFalse = filterConditionalPostconditions(superCPost, false); + Set subCPostFalse = filterConditionalPostconditions(subCPost, false); + Set> superCPostFalse2 = + parseAndLocalizeContracts(superCPostFalse, overridden); + Set> subCPostFalse2 = + parseAndLocalizeContracts(subCPostFalse, overrider); + @SuppressWarnings("compilermessages") + @CompilerMessageKey String postfalsemsg = + "contracts.conditional.postcondition.false." + msgKey + ".invalid"; + checkContractsSubset( + overriderType, overriddenType, superCPostFalse2, subCPostFalse2, postfalsemsg); + } - // The type of the compileTimeDeclaration if it were invoked with a receiver expression - // of type {@code type} - AnnotatedExecutableType invocationType = - atypeFactory.methodFromUse(memberReferenceTree, compileTimeDeclaration, enclosingType) - .executableType; + /** + * Issue a "methodref.receiver.invalid" or "methodref.receiver.bound.invalid" error if the + * receiver for the method reference does not satify overriding rules. + * + * @return true if the override is legal + */ + protected boolean checkMemberReferenceReceivers() { + if (overriderType.getKind() == TypeKind.ARRAY) { + // Assume the receiver for all method on arrays are @Top. + // This simplifies some logic because an AnnotatedExecutableType for an array method + // (ie String[]::clone) has a receiver of "Array." The UNBOUND check would then + // have to compare "Array" to "String[]". + return true; + } + MemberReferenceTree memberTree = (MemberReferenceTree) overriderTree; + MemberReferenceKind methodRefKind = + MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree); + // These act like a traditional override + if (methodRefKind == MemberReferenceKind.UNBOUND) { + AnnotatedTypeMirror overriderReceiver = overrider.getReceiverType(); + AnnotatedTypeMirror overriddenReceiver = overridden.getParameterTypes().get(0); + boolean success = typeHierarchy.isSubtype(overriddenReceiver, overriderReceiver); + if (!success) { + checker.reportError( + overriderTree, + "methodref.receiver.invalid", + overriderReceiver, + overriddenReceiver, + overriderType, + overrider, + overriddenType, + overridden); + } + return success; + } - // This needs to be done before invocationType.getReturnType() and - // functionType.getReturnType() - if (invocationType.getTypeVariables().isEmpty() && !functionType.getTypeVariables().isEmpty()) { - functionType = functionType.getErased(); - } + // The rest act like method invocations + AnnotatedTypeMirror receiverDecl; + AnnotatedTypeMirror receiverArg; + switch (methodRefKind) { + case UNBOUND: + throw new BugInCF("Case UNBOUND should already be handled."); + case SUPER: + receiverDecl = overrider.getReceiverType(); + receiverArg = + atypeFactory.getAnnotatedType(memberTree.getQualifierExpression()); + + AnnotatedTypeMirror selfType = atypeFactory.getSelfType(memberTree); + receiverArg.replaceAnnotations(selfType.getAnnotations()); + break; + case BOUND: + receiverDecl = overrider.getReceiverType(); + receiverArg = overriderType; + break; + case IMPLICIT_INNER: + // JLS 15.13.1 "It is a compile-time error if the method reference expression is + // of the form ClassType :: [TypeArguments] new and a compile-time error would + // occur when determining an enclosing instance for ClassType as specified in + // 15.9.2 (treating the method reference expression as if it were an unqualified + // class instance creation expression)." + + // So a member reference can only refer to an inner class constructor if a type + // that encloses the inner class can be found. So either "this" is that + // enclosing type or "this" has an enclosing type that is that type. + receiverDecl = overrider.getReceiverType(); + receiverArg = atypeFactory.getSelfType(memberTree); + while (!TypesUtils.isErasedSubtype( + receiverArg.getUnderlyingType(), + receiverDecl.getUnderlyingType(), + types)) { + receiverArg = ((AnnotatedDeclaredType) receiverArg).getEnclosingType(); + } + + break; + case TOPLEVEL: + case STATIC: + case ARRAY_CTOR: + default: + // Intentional fallthrough + // These don't have receivers + return true; + } - // Use the function type's parameters to resolve polymorphic qualifiers. - QualifierPolymorphism poly = atypeFactory.getQualifierPolymorphism(); - poly.resolve(functionType, invocationType); + boolean success = typeHierarchy.isSubtype(receiverArg, receiverDecl); + if (!success) { + checker.reportError( + overriderTree, + "methodref.receiver.bound.invalid", + receiverArg, + receiverDecl, + receiverArg, + overriderType, + overrider); + } - AnnotatedTypeMirror invocationReturnType; - if (compileTimeDeclaration.getKind() == ElementKind.CONSTRUCTOR) { - if (enclosingType.getKind() == TypeKind.ARRAY) { - // Special casing for the return of array constructor - invocationReturnType = enclosingType; - } else { - invocationReturnType = - atypeFactory.getResultingTypeOfConstructorMemberReference( - memberReferenceTree, invocationType); - } - } else { - invocationReturnType = invocationType.getReturnType(); - } + return success; + } - AnnotatedTypeMirror functionTypeReturnType = functionType.getReturnType(); - if (functionTypeReturnType.getKind() == TypeKind.VOID) { - // If the functional interface return type is void, the overriding return type doesn't - // matter. - functionTypeReturnType = invocationReturnType; - } + /** + * Issue an "override.receiver.invalid" error if the receiver override is not valid. + * + * @return true if the override is legal + */ + protected boolean checkReceiverOverride() { + AnnotatedDeclaredType overriderReceiver = overrider.getReceiverType(); + AnnotatedDeclaredType overriddenReceiver = overridden.getReceiverType(); + // Check the receiver type. + // isSubtype() requires its arguments to be actual subtypes with respect to the JLS, but + // an overrider receiver is not a subtype of the overridden receiver. So, just check + // primary annotations. + // TODO: this will need to be improved for generic receivers. + if (!typeHierarchy.isSubtypeShallowEffective(overriddenReceiver, overriderReceiver)) { + AnnotationMirrorSet declaredAnnos = + atypeFactory.getTypeDeclarationBounds(overriderType.getUnderlyingType()); + if (typeHierarchy.isSubtypeShallowEffective(overriderReceiver, declaredAnnos) + && typeHierarchy.isSubtypeShallowEffective( + declaredAnnos, overriderReceiver)) { + // All the type of an object must be no higher than its upper bound. So if the + // receiver is annotated with the upper bound qualifiers, then the override is + // safe. + return true; + } + FoundRequired pair = FoundRequired.of(overriderReceiver, overriddenReceiver); + checker.reportError( + overriderTree, + "override.receiver.invalid", + pair.found, + pair.required, + overriderType, + overrider, + overriddenType, + overridden); + return false; + } + return true; + } - if (functionalInterface.getKind() == TypeKind.DECLARED) { - // Check the member reference as if invocationType overrides functionType. - OverrideChecker overrideChecker = - createOverrideChecker( - memberReferenceTree, - invocationType, - enclosingType, - invocationReturnType, - functionType, - (AnnotatedDeclaredType) functionalInterface, - functionTypeReturnType); - return overrideChecker.checkOverride(); - } else { - // If the functionalInterface is not a declared type, it must be from a wildcard from a - // raw type. In that case, only return false if raw types should not be ignored. - return !atypeFactory.ignoreRawTypeArguments; - } - } - - /** - * Class to perform method override and method reference checks. - * - *

          Method references are checked similarly to method overrides, with the method reference - * viewed as overriding the functional interface's method. - * - *

          Checks that an overriding method's return type, parameter types, and receiver type are - * correct with respect to the annotations on the overridden method's return type, parameter - * types, and receiver type. - * - *

          Furthermore, any contracts on the method must satisfy behavioral subtyping, that is, - * postconditions must be at least as strong as the postcondition on the superclass, and - * preconditions must be at most as strong as the condition on the superclass. - * - *

          This method returns the result of the check, but also emits error messages as a side effect. - */ - public class OverrideChecker { + private boolean checkParameters() { + List overriderParams = overrider.getParameterTypes(); + List overriddenParams = overridden.getParameterTypes(); + + // Fix up method reference parameters. + // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.13.1 + if (isMethodReference) { + // The functional interface of an unbound member reference has an extra parameter + // (the receiver). + if (MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree) + == MemberReferenceKind.UNBOUND) { + overriddenParams = new ArrayList<>(overriddenParams); + overriddenParams.remove(0); + } + // Deal with varargs + if (overrider.isVarArgs() && !overridden.isVarArgs()) { + overriderParams = + AnnotatedTypes.expandVarArgsParametersFromTypes( + overrider, overriddenParams); + } + } - /** - * The declaration of an overriding method. Or, it could be a method reference that is being - * passed to a method. - */ - protected final Tree overriderTree; + boolean result = true; + for (int i = 0; i < overriderParams.size(); ++i) { + AnnotatedTypeMirror capturedParam = + atypeFactory.applyCaptureConversion(overriddenParams.get(i)); + boolean success = typeHierarchy.isSubtype(capturedParam, overriderParams.get(i)); + if (!success) { + success = + testTypevarContainment(overriddenParams.get(i), overriderParams.get(i)); + } + + checkParametersMsg(success, i, overriderParams, overriddenParams); + result &= success; + } + return result; + } + + private void checkParametersMsg( + boolean success, + int index, + List overriderParams, + List overriddenParams) { + if (success && !showchecks) { + return; + } - /** True if {@link #overriderTree} is a MEMBER_REFERENCE. */ - protected final boolean isMethodReference; + String msgKey = + isMethodReference ? "methodref.param.invalid" : "override.param.invalid"; + Tree posTree = + overriderTree instanceof MethodTree + ? ((MethodTree) overriderTree).getParameters().get(index) + : overriderTree; + + if (showchecks) { + System.out.printf( + " %s (at %s):%n" + + " overrider: %s %s (parameter %d type %s)%n" + + " overridden: %s %s" + + " (parameter %d type %s)%n", + (success + ? "success: overridden parameter type is subtype of overriding" + : "FAILURE: overridden parameter type is not subtype of overriding"), + fileAndLineNumber(posTree), + overrider, + overriderType, + index, + overriderParams.get(index).toString(), + overridden, + overriddenType, + index, + overriddenParams.get(index).toString()); + } + if (!success) { + FoundRequired pair = + FoundRequired.of(overriderParams.get(index), overriddenParams.get(index)); + checker.reportError( + posTree, + msgKey, + overrider.getElement().getParameters().get(index).toString(), + pair.found, + pair.required, + overriderType, + overrider, + overriddenType, + overridden); + } + } - /** The type of the overriding method. */ - protected final AnnotatedExecutableType overrider; + /** + * Returns true if the return type of the overridden method is a subtype of the return type + * of the overriding method. + * + * @return true if the return type is correct + */ + private boolean checkReturn() { + if ((overriderReturnType.getKind() == TypeKind.VOID)) { + // Nothing to check. + return true; + } + boolean success = typeHierarchy.isSubtype(overriderReturnType, overriddenReturnType); + if (!success) { + // If both the overridden method have type variables as return types and both + // types were defined in their respective methods then, they can be covariant or + // invariant use super/subtypes for the overrides locations + success = testTypevarContainment(overriderReturnType, overriddenReturnType); + } - /** The subtype that declares the overriding method. */ - protected final AnnotatedTypeMirror overriderType; + // Sometimes the overridden return type of a method reference becomes a captured + // type variable. This leads to defaulting that often makes the overriding return type + // invalid. We ignore these. This happens in Issue403/Issue404. + if (!success + && isMethodReference + && TypesUtils.isCapturedTypeVariable( + overriddenReturnType.getUnderlyingType())) { + if (ElementUtils.isMethod( + overridden.getElement(), functionApply, atypeFactory.getProcessingEnv())) { + success = + typeHierarchy.isSubtype( + overriderReturnType, + ((AnnotatedTypeVariable) overriddenReturnType).getUpperBound()); + } + } - /** The type of the overridden method. */ - protected final AnnotatedExecutableType overridden; + checkReturnMsg(success); + return success; + } - /** The supertype that declares the overridden method. */ - protected final AnnotatedDeclaredType overriddenType; + /** + * Issue an error message or log message about checking an overriding return type. + * + * @param success whether the check succeeded or failed + */ + private void checkReturnMsg(boolean success) { + if (success && !showchecks) { + return; + } - /** The teturn type of the overridden method. */ - protected final AnnotatedTypeMirror overriddenReturnType; + String msgKey = + isMethodReference ? "methodref.return.invalid" : "override.return.invalid"; + Tree posTree = + overriderTree instanceof MethodTree + ? ((MethodTree) overriderTree).getReturnType() + : overriderTree; + // The return type of a MethodTree is null for a constructor. + if (posTree == null) { + posTree = overriderTree; + } - /** The return type of the overriding method. */ - protected final AnnotatedTypeMirror overriderReturnType; + if (showchecks) { + System.out.printf( + " %s (at %s):%n" + + " overrider: %s %s (return type %s)%n" + + " overridden: %s %s (return type %s)%n", + (success + ? "success: overriding return type is subtype of overridden" + : "FAILURE: overriding return type is not subtype of overridden"), + fileAndLineNumber(posTree), + overrider, + overriderType, + overrider.getReturnType().toString(), + overridden, + overriddenType, + overridden.getReturnType().toString()); + } + if (!success) { + FoundRequired pair = FoundRequired.of(overriderReturnType, overriddenReturnType); + checker.reportError( + posTree, + msgKey, + pair.found, + pair.required, + overriderType, + overrider, + overriddenType, + overridden); + } + } + } /** - * Create an OverrideChecker. - * - *

          Notice that the return types are passed in separately. This is to support some types of - * method references where the overrider's return type is not the appropriate type to check. + * Filters the set of conditional postconditions to return only those whose annotation result + * value matches the value of the given boolean {@code b}. For example, if {@code b == true}, + * then the following {@code @EnsuresNonNullIf} conditional postcondition would match:
          + * {@code @EnsuresNonNullIf(expression="#1", result=true)}
          + * {@code boolean equals(@Nullable Object o)} * - * @param overriderTree the AST node of the overriding method or method reference - * @param overrider the type of the overriding method - * @param overriderType the type enclosing the overrider method, usually an - * AnnotatedDeclaredType; for Method References may be something else - * @param overriderReturnType the return type of the overriding method - * @param overridden the type of the overridden method - * @param overriddenType the declared type enclosing the overridden method - * @param overriddenReturnType the return type of the overridden method + * @param conditionalPostconditions each is a ConditionalPostcondition + * @param b the value required for the {@code result} element + * @return all the given conditional postconditions whose {@code result} is {@code b} */ - public OverrideChecker( - Tree overriderTree, - AnnotatedExecutableType overrider, - AnnotatedTypeMirror overriderType, - AnnotatedTypeMirror overriderReturnType, - AnnotatedExecutableType overridden, - AnnotatedDeclaredType overriddenType, - AnnotatedTypeMirror overriddenReturnType) { - - this.overriderTree = overriderTree; - this.overrider = overrider; - this.overriderType = overriderType; - this.overriderReturnType = overriderReturnType; - this.overridden = overridden; - this.overriddenType = overriddenType; - this.overriddenReturnType = overriddenReturnType; - - this.isMethodReference = overriderTree.getKind() == Tree.Kind.MEMBER_REFERENCE; + private Set filterConditionalPostconditions( + Set conditionalPostconditions, boolean b) { + if (conditionalPostconditions.isEmpty()) { + return Collections.emptySet(); + } + + Set result = + ArraySet.newArraySetOrLinkedHashSet(conditionalPostconditions.size()); + for (Contract c : conditionalPostconditions) { + ConditionalPostcondition p = (ConditionalPostcondition) c; + if (p.resultValue == b) { + result.add( + new Postcondition(p.expressionString, p.annotation, p.contractAnnotation)); + } + } + return result; } /** - * Perform the check. + * Checks that {@code mustSubset} is a subset of {@code set} in the following sense: For every + * expression in {@code mustSubset} there must be the same expression in {@code set}, with the + * same (or a stronger) annotation. * - * @return true if the override is allowed + *

          This uses field {@link #methodTree} to determine where to issue an error message. + * + * @param overriderType the subtype + * @param overriddenType the supertype + * @param mustSubset annotations that should be weaker + * @param set anontations that should be stronger + * @param messageKey message key for error messages */ - public boolean checkOverride() { - if (checker.shouldSkipUses(overriddenType.getUnderlyingType().asElement())) { - return true; - } + private void checkContractsSubset( + AnnotatedTypeMirror overriderType, + AnnotatedDeclaredType overriddenType, + Set> mustSubset, + Set> set, + @CompilerMessageKey String messageKey) { + for (IPair weak : mustSubset) { + JavaExpression jexpr = weak.first; + boolean found = false; + + for (IPair strong : set) { + // are we looking at a contract of the same receiver? + if (jexpr.equals(strong.first)) { + // check subtyping relationship of annotations + TypeMirror jexprTM = jexpr.getType(); + if (qualHierarchy.isSubtypeShallow( + strong.second, jexprTM, weak.second, jexprTM)) { + found = true; + break; + } + } + } - boolean result = checkReturn(); - result &= checkParameters(); - if (isMethodReference) { - result &= checkMemberReferenceReceivers(); - } else { - result &= checkReceiverOverride(); - } - checkPreAndPostConditions(); - checkPurity(); + if (!found) { - return result; - } + String overriddenTypeString = + overriddenType.getUnderlyingType().asElement().toString(); + String overriderTypeString; + if (overriderType.getKind() == TypeKind.DECLARED) { + DeclaredType overriderTypeMirror = + ((AnnotatedDeclaredType) overriderType).getUnderlyingType(); + overriderTypeString = overriderTypeMirror.asElement().toString(); + } else { + overriderTypeString = overriderType.toString(); + } - /** Check that an override respects purity. */ - private void checkPurity() { - String msgKey = isMethodReference ? "purity.invalid.methodref" : "purity.invalid.overriding"; + // weak.second is the AnnotationMirror that is too strong. It might be from the + // precondition or the postcondition. - // check purity annotations - EnumSet superPurity = - PurityUtils.getPurityKinds(atypeFactory, overridden.getElement()); - EnumSet subPurity = - PurityUtils.getPurityKinds(atypeFactory, overrider.getElement()); - if (!subPurity.containsAll(superPurity)) { - checker.reportError( - overriderTree, - msgKey, - overriderType, - overrider, - overriddenType, - overridden, - subPurity, - superPurity); - } - } + // These are the annotations that are too weak. + StringJoiner strongRelevantAnnos = + new StringJoiner(" ").setEmptyValue("no information"); + for (IPair strong : set) { + if (jexpr.equals(strong.first)) { + strongRelevantAnnos.add(strong.second.toString()); + } + } - /** - * Checks that overrides obey behavioral subtyping, that is, postconditions must be at least as - * strong as the postcondition on the superclass, and preconditions must be at most as strong as - * the condition on the superclass. - */ - private void checkPreAndPostConditions() { - String msgKey = isMethodReference ? "methodref" : "override"; - if (isMethodReference) { - // TODO: Support postconditions and method references. - // The parse context always expects instance methods, but method references can be - // static. - return; - } + Object overriddenAnno; + Object overriderAnno; + if (messageKey.contains(".precondition.")) { + overriddenAnno = strongRelevantAnnos; + overriderAnno = weak.second; + } else { + overriddenAnno = weak.second; + overriderAnno = strongRelevantAnnos; + } - ContractsFromMethod contractsUtils = atypeFactory.getContractsFromMethod(); - - // Check preconditions - Set superPre = contractsUtils.getPreconditions(overridden.getElement()); - Set subPre = contractsUtils.getPreconditions(overrider.getElement()); - Set> superPre2 = - parseAndLocalizeContracts(superPre, overridden); - Set> subPre2 = - parseAndLocalizeContracts(subPre, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String premsg = "contracts.precondition." + msgKey + ".invalid"; - checkContractsSubset(overriderType, overriddenType, subPre2, superPre2, premsg); - - // Check postconditions - Set superPost = contractsUtils.getPostconditions(overridden.getElement()); - Set subPost = contractsUtils.getPostconditions(overrider.getElement()); - Set> superPost2 = - parseAndLocalizeContracts(superPost, overridden); - Set> subPost2 = - parseAndLocalizeContracts(subPost, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String postmsg = "contracts.postcondition." + msgKey + ".invalid"; - checkContractsSubset(overriderType, overriddenType, superPost2, subPost2, postmsg); - - // Check conditional postconditions - Set superCPost = - contractsUtils.getConditionalPostconditions(overridden.getElement()); - Set subCPost = - contractsUtils.getConditionalPostconditions(overrider.getElement()); - // consider only 'true' postconditions - Set superCPostTrue = filterConditionalPostconditions(superCPost, true); - Set subCPostTrue = filterConditionalPostconditions(subCPost, true); - Set> superCPostTrue2 = - parseAndLocalizeContracts(superCPostTrue, overridden); - Set> subCPostTrue2 = - parseAndLocalizeContracts(subCPostTrue, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String posttruemsg = "contracts.conditional.postcondition.true." + msgKey + ".invalid"; - checkContractsSubset( - overriderType, overriddenType, superCPostTrue2, subCPostTrue2, posttruemsg); - - // consider only 'false' postconditions - Set superCPostFalse = filterConditionalPostconditions(superCPost, false); - Set subCPostFalse = filterConditionalPostconditions(subCPost, false); - Set> superCPostFalse2 = - parseAndLocalizeContracts(superCPostFalse, overridden); - Set> subCPostFalse2 = - parseAndLocalizeContracts(subCPostFalse, overrider); - @SuppressWarnings("compilermessages") - @CompilerMessageKey String postfalsemsg = "contracts.conditional.postcondition.false." + msgKey + ".invalid"; - checkContractsSubset( - overriderType, overriddenType, superCPostFalse2, subCPostFalse2, postfalsemsg); + checker.reportError( + methodTree, + messageKey, + jexpr, + methodTree.getName(), + overriddenTypeString, + overriddenAnno, + overriderTypeString, + overriderAnno); + } + } } /** - * Issue a "methodref.receiver.invalid" or "methodref.receiver.bound.invalid" error if the - * receiver for the method reference does not satify overriding rules. + * Localizes some contracts -- that is, viewpoint-adapts them to some method body, according to + * the value of {@link #methodTree}. * - * @return true if the override is legal + *

          The input is a set of {@link Contract}s, each of which contains an expression string and + * an annotation. In a {@link Contract}, Java expressions are exactly as written in source code, + * not standardized or viewpoint-adapted. + * + *

          The output is a set of pairs of {@link JavaExpression} (parsed expression string) and + * standardized annotation (with respect to the path of {@link #methodTree}. This method + * discards any contract whose expression cannot be parsed into a JavaExpression. + * + * @param contractSet a set of contracts + * @param methodType the type of the method that the contracts are for + * @return pairs of (expression, AnnotationMirror), which are localized contracts */ - protected boolean checkMemberReferenceReceivers() { - if (overriderType.getKind() == TypeKind.ARRAY) { - // Assume the receiver for all method on arrays are @Top. - // This simplifies some logic because an AnnotatedExecutableType for an array method - // (ie String[]::clone) has a receiver of "Array." The UNBOUND check would then - // have to compare "Array" to "String[]". - return true; - } - MemberReferenceTree memberTree = (MemberReferenceTree) overriderTree; - MemberReferenceKind methodRefKind = - MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree); - // These act like a traditional override - if (methodRefKind == MemberReferenceKind.UNBOUND) { - AnnotatedTypeMirror overriderReceiver = overrider.getReceiverType(); - AnnotatedTypeMirror overriddenReceiver = overridden.getParameterTypes().get(0); - boolean success = typeHierarchy.isSubtype(overriddenReceiver, overriderReceiver); - if (!success) { - checker.reportError( - overriderTree, - "methodref.receiver.invalid", - overriderReceiver, - overriddenReceiver, - overriderType, - overrider, - overriddenType, - overridden); - } - return success; - } - - // The rest act like method invocations - AnnotatedTypeMirror receiverDecl; - AnnotatedTypeMirror receiverArg; - switch (methodRefKind) { - case UNBOUND: - throw new BugInCF("Case UNBOUND should already be handled."); - case SUPER: - receiverDecl = overrider.getReceiverType(); - receiverArg = atypeFactory.getAnnotatedType(memberTree.getQualifierExpression()); - - AnnotatedTypeMirror selfType = atypeFactory.getSelfType(memberTree); - receiverArg.replaceAnnotations(selfType.getAnnotations()); - break; - case BOUND: - receiverDecl = overrider.getReceiverType(); - receiverArg = overriderType; - break; - case IMPLICIT_INNER: - // JLS 15.13.1 "It is a compile-time error if the method reference expression is - // of the form ClassType :: [TypeArguments] new and a compile-time error would - // occur when determining an enclosing instance for ClassType as specified in - // 15.9.2 (treating the method reference expression as if it were an unqualified - // class instance creation expression)." - - // So a member reference can only refer to an inner class constructor if a type - // that encloses the inner class can be found. So either "this" is that - // enclosing type or "this" has an enclosing type that is that type. - receiverDecl = overrider.getReceiverType(); - receiverArg = atypeFactory.getSelfType(memberTree); - while (!TypesUtils.isErasedSubtype( - receiverArg.getUnderlyingType(), receiverDecl.getUnderlyingType(), types)) { - receiverArg = ((AnnotatedDeclaredType) receiverArg).getEnclosingType(); - } - - break; - case TOPLEVEL: - case STATIC: - case ARRAY_CTOR: - default: - // Intentional fallthrough - // These don't have receivers - return true; - } - - boolean success = typeHierarchy.isSubtype(receiverArg, receiverDecl); - if (!success) { - checker.reportError( - overriderTree, - "methodref.receiver.bound.invalid", - receiverArg, - receiverDecl, - receiverArg, - overriderType, - overrider); - } + private Set> parseAndLocalizeContracts( + Set contractSet, AnnotatedExecutableType methodType) { + if (contractSet.isEmpty()) { + return Collections.emptySet(); + } - return success; + // This is the path to a place where the contract is being used, which might or might not be + // where the contract was defined. For example, methodTree might be an overriding + // definition, and the contract might be for a superclass. + MethodTree methodTree = this.methodTree; + + StringToJavaExpression stringToJavaExpr = + expression -> { + JavaExpression javaExpr = + StringToJavaExpression.atMethodDecl( + expression, methodType.getElement(), checker); + // methodType.getElement() is not necessarily the same method as methodTree, so + // viewpoint-adapt it to methodTree. + return javaExpr.atMethodBody(methodTree); + }; + + Set> result = + ArraySet.newArraySetOrHashSet(contractSet.size()); + for (Contract p : contractSet) { + String expressionString = p.expressionString; + AnnotationMirror annotation = + p.viewpointAdaptDependentTypeAnnotation( + atypeFactory, stringToJavaExpr, methodTree); + JavaExpression exprJe; + try { + // TODO: currently, these expressions are parsed many times. + // This could be optimized to store the result the first time. + // (same for other annotations) + exprJe = stringToJavaExpr.toJavaExpression(expressionString); + } catch (JavaExpressionParseException e) { + // report errors here + checker.report(methodTree, e.getDiagMessage()); + continue; + } + result.add(IPair.of(exprJe, annotation)); + } + return result; } /** - * Issue an "override.receiver.invalid" error if the receiver override is not valid. + * Call this only when the current path is an identifier. * - * @return true if the override is legal + * @return the enclosing member select, or null if the identifier is not the field in a member + * selection */ - protected boolean checkReceiverOverride() { - AnnotatedDeclaredType overriderReceiver = overrider.getReceiverType(); - AnnotatedDeclaredType overriddenReceiver = overridden.getReceiverType(); - // Check the receiver type. - // isSubtype() requires its arguments to be actual subtypes with respect to the JLS, but - // an overrider receiver is not a subtype of the overridden receiver. So, just check - // primary annotations. - // TODO: this will need to be improved for generic receivers. - if (!typeHierarchy.isSubtypeShallowEffective(overriddenReceiver, overriderReceiver)) { - AnnotationMirrorSet declaredAnnos = - atypeFactory.getTypeDeclarationBounds(overriderType.getUnderlyingType()); - if (typeHierarchy.isSubtypeShallowEffective(overriderReceiver, declaredAnnos) - && typeHierarchy.isSubtypeShallowEffective(declaredAnnos, overriderReceiver)) { - // All the type of an object must be no higher than its upper bound. So if the - // receiver is annotated with the upper bound qualifiers, then the override is - // safe. - return true; - } - FoundRequired pair = FoundRequired.of(overriderReceiver, overriddenReceiver); - checker.reportError( - overriderTree, - "override.receiver.invalid", - pair.found, - pair.required, - overriderType, - overrider, - overriddenType, - overridden); - return false; - } - return true; + protected @Nullable MemberSelectTree enclosingMemberSelect() { + TreePath path = this.getCurrentPath(); + assert path.getLeaf().getKind() == Tree.Kind.IDENTIFIER + : "expected identifier, found: " + path.getLeaf(); + if (path.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { + return (MemberSelectTree) path.getParentPath().getLeaf(); + } else { + return null; + } } - private boolean checkParameters() { - List overriderParams = overrider.getParameterTypes(); - List overriddenParams = overridden.getParameterTypes(); - - // Fix up method reference parameters. - // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.13.1 - if (isMethodReference) { - // The functional interface of an unbound member reference has an extra parameter - // (the receiver). - if (MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) overriderTree) - == MemberReferenceKind.UNBOUND) { - overriddenParams = new ArrayList<>(overriddenParams); - overriddenParams.remove(0); - } - // Deal with varargs - if (overrider.isVarArgs() && !overridden.isVarArgs()) { - overriderParams = - AnnotatedTypes.expandVarArgsParametersFromTypes(overrider, overriddenParams); + /** + * Returns the statement that encloses the given one. + * + * @param tree an AST node that is on the current path + * @return the statement that encloses the given one + */ + protected @Nullable Tree enclosingStatement(@FindDistinct Tree tree) { + TreePath path = this.getCurrentPath(); + while (path != null && path.getLeaf() != tree) { + path = path.getParentPath(); } - } - boolean result = true; - for (int i = 0; i < overriderParams.size(); ++i) { - AnnotatedTypeMirror capturedParam = - atypeFactory.applyCaptureConversion(overriddenParams.get(i)); - boolean success = typeHierarchy.isSubtype(capturedParam, overriderParams.get(i)); - if (!success) { - success = testTypevarContainment(overriddenParams.get(i), overriderParams.get(i)); + if (path != null) { + return path.getParentPath().getLeaf(); + } else { + return null; } - - checkParametersMsg(success, i, overriderParams, overriddenParams); - result &= success; - } - return result; } - private void checkParametersMsg( - boolean success, - int index, - List overriderParams, - List overriddenParams) { - if (success && !showchecks) { - return; - } - - String msgKey = isMethodReference ? "methodref.param.invalid" : "override.param.invalid"; - Tree posTree = - overriderTree instanceof MethodTree - ? ((MethodTree) overriderTree).getParameters().get(index) - : overriderTree; - - if (showchecks) { - System.out.printf( - " %s (at %s):%n" - + " overrider: %s %s (parameter %d type %s)%n" - + " overridden: %s %s" - + " (parameter %d type %s)%n", - (success - ? "success: overridden parameter type is subtype of overriding" - : "FAILURE: overridden parameter type is not subtype of overriding"), - fileAndLineNumber(posTree), - overrider, - overriderType, - index, - overriderParams.get(index).toString(), - overridden, - overriddenType, - index, - overriddenParams.get(index).toString()); - } - if (!success) { - FoundRequired pair = - FoundRequired.of(overriderParams.get(index), overriddenParams.get(index)); - checker.reportError( - posTree, - msgKey, - overrider.getElement().getParameters().get(index).toString(), - pair.found, - pair.required, - overriderType, - overrider, - overriddenType, - overridden); - } + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + checkAccess(tree, p); + return super.visitIdentifier(tree, p); } /** - * Returns true if the return type of the overridden method is a subtype of the return type of - * the overriding method. + * Issues an error if access is not allowed, based on an {@code @Unused} annotation. * - * @return true if the return type is correct + * @param identifierTree the identifier being accessed; the method does nothing if it is not a + * field + * @param p ignored */ - private boolean checkReturn() { - if ((overriderReturnType.getKind() == TypeKind.VOID)) { - // Nothing to check. - return true; - } - boolean success = typeHierarchy.isSubtype(overriderReturnType, overriddenReturnType); - if (!success) { - // If both the overridden method have type variables as return types and both - // types were defined in their respective methods then, they can be covariant or - // invariant use super/subtypes for the overrides locations - success = testTypevarContainment(overriderReturnType, overriddenReturnType); - } + protected void checkAccess(IdentifierTree identifierTree, Void p) { + MemberSelectTree memberSel = enclosingMemberSelect(); + ExpressionTree tree; + Element elem; + + if (memberSel == null) { + tree = identifierTree; + elem = TreeUtils.elementFromUse(identifierTree); + } else { + tree = memberSel; + elem = TreeUtils.elementFromUse(memberSel); + } - // Sometimes the overridden return type of a method reference becomes a captured - // type variable. This leads to defaulting that often makes the overriding return type - // invalid. We ignore these. This happens in Issue403/Issue404. - if (!success - && isMethodReference - && TypesUtils.isCapturedTypeVariable(overriddenReturnType.getUnderlyingType())) { - if (ElementUtils.isMethod( - overridden.getElement(), functionApply, atypeFactory.getProcessingEnv())) { - success = - typeHierarchy.isSubtype( - overriderReturnType, - ((AnnotatedTypeVariable) overriddenReturnType).getUpperBound()); + if (elem == null || !elem.getKind().isField()) { + return; } - } - checkReturnMsg(success); - return success; + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + + checkAccessAllowed(elem, receiver, tree); } /** - * Issue an error message or log message about checking an overriding return type. + * Issues an error if access not allowed, based on an @Unused annotation. * - * @param success whether the check succeeded or failed + * @param field the field to be accessed, whose declaration might be annotated by @Unused. It + * can also be (for example) {@code this}, in which case {@code receiverType} is null. + * @param receiverType the type of the expression whose field is accessed; null if the field is + * static + * @param accessTree the access expression */ - private void checkReturnMsg(boolean success) { - if (success && !showchecks) { - return; - } + protected void checkAccessAllowed( + Element field, + @Nullable AnnotatedTypeMirror receiverType, + @FindDistinct ExpressionTree accessTree) { + AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class); + if (unused == null) { + return; + } - String msgKey = isMethodReference ? "methodref.return.invalid" : "override.return.invalid"; - Tree posTree = - overriderTree instanceof MethodTree - ? ((MethodTree) overriderTree).getReturnType() - : overriderTree; - // The return type of a MethodTree is null for a constructor. - if (posTree == null) { - posTree = overriderTree; - } + String when = + AnnotationUtils.getElementValueClassName(unused, unusedWhenElement).toString(); - if (showchecks) { - System.out.printf( - " %s (at %s):%n" - + " overrider: %s %s (return type %s)%n" - + " overridden: %s %s (return type %s)%n", - (success - ? "success: overriding return type is subtype of overridden" - : "FAILURE: overriding return type is not subtype of overridden"), - fileAndLineNumber(posTree), - overrider, - overriderType, - overrider.getReturnType().toString(), - overridden, - overriddenType, - overridden.getReturnType().toString()); - } - if (!success) { - FoundRequired pair = FoundRequired.of(overriderReturnType, overriddenReturnType); - checker.reportError( - posTree, - msgKey, - pair.found, - pair.required, - overriderType, - overrider, - overriddenType, - overridden); - } - } - } - - /** - * Filters the set of conditional postconditions to return only those whose annotation result - * value matches the value of the given boolean {@code b}. For example, if {@code b == true}, then - * the following {@code @EnsuresNonNullIf} conditional postcondition would match:
          - * {@code @EnsuresNonNullIf(expression="#1", result=true)}
          - * {@code boolean equals(@Nullable Object o)} - * - * @param conditionalPostconditions each is a ConditionalPostcondition - * @param b the value required for the {@code result} element - * @return all the given conditional postconditions whose {@code result} is {@code b} - */ - private Set filterConditionalPostconditions( - Set conditionalPostconditions, boolean b) { - if (conditionalPostconditions.isEmpty()) { - return Collections.emptySet(); - } + // TODO: Don't just look at the receiver type, but at the declaration annotations on the + // receiver. (That will enable handling type annotations that are not part of the type + // system being checked.) - Set result = - ArraySet.newArraySetOrLinkedHashSet(conditionalPostconditions.size()); - for (Contract c : conditionalPostconditions) { - ConditionalPostcondition p = (ConditionalPostcondition) c; - if (p.resultValue == b) { - result.add(new Postcondition(p.expressionString, p.annotation, p.contractAnnotation)); - } - } - return result; - } - - /** - * Checks that {@code mustSubset} is a subset of {@code set} in the following sense: For every - * expression in {@code mustSubset} there must be the same expression in {@code set}, with the - * same (or a stronger) annotation. - * - *

          This uses field {@link #methodTree} to determine where to issue an error message. - * - * @param overriderType the subtype - * @param overriddenType the supertype - * @param mustSubset annotations that should be weaker - * @param set anontations that should be stronger - * @param messageKey message key for error messages - */ - private void checkContractsSubset( - AnnotatedTypeMirror overriderType, - AnnotatedDeclaredType overriddenType, - Set> mustSubset, - Set> set, - @CompilerMessageKey String messageKey) { - for (IPair weak : mustSubset) { - JavaExpression jexpr = weak.first; - boolean found = false; - - for (IPair strong : set) { - // are we looking at a contract of the same receiver? - if (jexpr.equals(strong.first)) { - // check subtyping relationship of annotations - TypeMirror jexprTM = jexpr.getType(); - if (qualHierarchy.isSubtypeShallow(strong.second, jexprTM, weak.second, jexprTM)) { - found = true; - break; - } + // TODO: This requires exactly the same type qualifier, but it should permit subqualifiers. + if (!AnnotationUtils.containsSameByName(receiverType.getAnnotations(), when)) { + return; } - } - if (!found) { + Tree tree = this.enclosingStatement(accessTree); - String overriddenTypeString = overriddenType.getUnderlyingType().asElement().toString(); - String overriderTypeString; - if (overriderType.getKind() == TypeKind.DECLARED) { - DeclaredType overriderTypeMirror = - ((AnnotatedDeclaredType) overriderType).getUnderlyingType(); - overriderTypeString = overriderTypeMirror.asElement().toString(); - } else { - overriderTypeString = overriderType.toString(); + if (tree != null + && tree.getKind() == Tree.Kind.ASSIGNMENT + && ((AssignmentTree) tree).getVariable() == accessTree + && ((AssignmentTree) tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL) { + // Assigning unused to null is OK. + return; } - // weak.second is the AnnotationMirror that is too strong. It might be from the - // precondition or the postcondition. - - // These are the annotations that are too weak. - StringJoiner strongRelevantAnnos = new StringJoiner(" ").setEmptyValue("no information"); - for (IPair strong : set) { - if (jexpr.equals(strong.first)) { - strongRelevantAnnos.add(strong.second.toString()); - } - } + checker.reportError(accessTree, "unallowed.access", field, receiverType); + } - Object overriddenAnno; - Object overriderAnno; - if (messageKey.contains(".precondition.")) { - overriddenAnno = strongRelevantAnnos; - overriderAnno = weak.second; - } else { - overriddenAnno = weak.second; - overriderAnno = strongRelevantAnnos; + /** + * Tests that the qualifiers present on {@code useType} are valid qualifiers, given the + * qualifiers on the declaration of the type, {@code declarationType}. + * + *

          The check is shallow, as it does not descend into generic or array types (i.e. only + * performing the validity check on the raw type or outermost array dimension). {@link + * BaseTypeVisitor#validateTypeOf(Tree)} would call this for each type argument or array + * dimension separately. + * + *

          In most cases, {@code useType} simply needs to be a subtype of {@code declarationType}. If + * a type system makes exceptions to this rule, its implementation should override this method. + * + *

          This method is not called if {@link + * BaseTypeValidator#shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror, Tree)} + * returns false -- by default, it is not called on the top level for locals and expressions. To + * enforce a type validity property everywhere, override methods such as {@link + * BaseTypeValidator#visitDeclared} rather than this method. + * + * @param declarationType the type of the class (TypeElement) + * @param useType the use of the class (instance type) + * @param tree the tree where the type is used + * @return true if the useType is a valid use of elemType + */ + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // Don't use isSubtype(ATM, ATM) because it will return false if the types have qualifier + // parameters. + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + TypeMirror declarationTM = declarationType.getUnderlyingType(); + AnnotationMirrorSet upperBounds = atypeFactory.getTypeDeclarationBounds(declarationTM); + for (AnnotationMirror top : tops) { + AnnotationMirror upperBound = qualHierarchy.findAnnotationInHierarchy(upperBounds, top); + if (!typeHierarchy.isSubtypeShallowEffective(useType, upperBound)) { + return false; + } } - - checker.reportError( - methodTree, - messageKey, - jexpr, - methodTree.getName(), - overriddenTypeString, - overriddenAnno, - overriderTypeString, - overriderAnno); - } - } - } - - /** - * Localizes some contracts -- that is, viewpoint-adapts them to some method body, according to - * the value of {@link #methodTree}. - * - *

          The input is a set of {@link Contract}s, each of which contains an expression string and an - * annotation. In a {@link Contract}, Java expressions are exactly as written in source code, not - * standardized or viewpoint-adapted. - * - *

          The output is a set of pairs of {@link JavaExpression} (parsed expression string) and - * standardized annotation (with respect to the path of {@link #methodTree}. This method discards - * any contract whose expression cannot be parsed into a JavaExpression. - * - * @param contractSet a set of contracts - * @param methodType the type of the method that the contracts are for - * @return pairs of (expression, AnnotationMirror), which are localized contracts - */ - private Set> parseAndLocalizeContracts( - Set contractSet, AnnotatedExecutableType methodType) { - if (contractSet.isEmpty()) { - return Collections.emptySet(); + return true; } - // This is the path to a place where the contract is being used, which might or might not be - // where the contract was defined. For example, methodTree might be an overriding - // definition, and the contract might be for a superclass. - MethodTree methodTree = this.methodTree; - - StringToJavaExpression stringToJavaExpr = - expression -> { - JavaExpression javaExpr = - StringToJavaExpression.atMethodDecl(expression, methodType.getElement(), checker); - // methodType.getElement() is not necessarily the same method as methodTree, so - // viewpoint-adapt it to methodTree. - return javaExpr.atMethodBody(methodTree); - }; - - Set> result = - ArraySet.newArraySetOrHashSet(contractSet.size()); - for (Contract p : contractSet) { - String expressionString = p.expressionString; - AnnotationMirror annotation = - p.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, methodTree); - JavaExpression exprJe; - try { - // TODO: currently, these expressions are parsed many times. - // This could be optimized to store the result the first time. - // (same for other annotations) - exprJe = stringToJavaExpr.toJavaExpression(expressionString); - } catch (JavaExpressionParseException e) { - // report errors here - checker.report(methodTree, e.getDiagMessage()); - continue; - } - result.add(IPair.of(exprJe, annotation)); - } - return result; - } - - /** - * Call this only when the current path is an identifier. - * - * @return the enclosing member select, or null if the identifier is not the field in a member - * selection - */ - protected @Nullable MemberSelectTree enclosingMemberSelect() { - TreePath path = this.getCurrentPath(); - assert path.getLeaf().getKind() == Tree.Kind.IDENTIFIER - : "expected identifier, found: " + path.getLeaf(); - if (path.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { - return (MemberSelectTree) path.getParentPath().getLeaf(); - } else { - return null; - } - } - - /** - * Returns the statement that encloses the given one. - * - * @param tree an AST node that is on the current path - * @return the statement that encloses the given one - */ - protected @Nullable Tree enclosingStatement(@FindDistinct Tree tree) { - TreePath path = this.getCurrentPath(); - while (path != null && path.getLeaf() != tree) { - path = path.getParentPath(); + /** + * Tests that the qualifiers present on the primitive type are valid. + * + * @param type the use of the primitive type + * @param tree the tree where the type is used + * @return true if the type is a valid use of the primitive type + */ + public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { + AnnotationMirrorSet bounds = + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + return typeHierarchy.isSubtypeShallowEffective(type, bounds); } - if (path != null) { - return path.getParentPath().getLeaf(); - } else { - return null; - } - } - - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - checkAccess(tree, p); - return super.visitIdentifier(tree, p); - } - - /** - * Issues an error if access is not allowed, based on an {@code @Unused} annotation. - * - * @param identifierTree the identifier being accessed; the method does nothing if it is not a - * field - * @param p ignored - */ - protected void checkAccess(IdentifierTree identifierTree, Void p) { - MemberSelectTree memberSel = enclosingMemberSelect(); - ExpressionTree tree; - Element elem; - - if (memberSel == null) { - tree = identifierTree; - elem = TreeUtils.elementFromUse(identifierTree); - } else { - tree = memberSel; - elem = TreeUtils.elementFromUse(memberSel); + /** + * Tests that the qualifiers present on the array type are valid. This method will be invoked + * for each array level independently, i.e. this method only needs to check the top-level + * qualifiers of an array. + * + * @param type the array type use + * @param tree the tree where the type is used + * @return true if the type is a valid array type + */ + public boolean isValidUse(AnnotatedArrayType type, Tree tree) { + AnnotationMirrorSet bounds = + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + return typeHierarchy.isSubtypeShallowEffective(type, bounds); } - if (elem == null || !elem.getKind().isField()) { - return; + /** + * Tests whether the tree expressed by the passed type tree is a valid type, and emits an error + * if that is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, + * check the return type. + * + * @param tree the AST type supplied by the user + * @return true if the tree is a valid type + */ + public boolean validateTypeOf(Tree tree) { + AnnotatedTypeMirror type; + // It's quite annoying that there is no TypeTree. + switch (tree.getKind()) { + case PRIMITIVE_TYPE: + case PARAMETERIZED_TYPE: + case ARRAY_TYPE: + case UNBOUNDED_WILDCARD: + case EXTENDS_WILDCARD: + case SUPER_WILDCARD: + case ANNOTATED_TYPE: + type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); + break; + case TYPE_PARAMETER: + type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); + validateTargetLocation( + tree, + ((AnnotatedTypeVariable) type).getUpperBound(), + TypeUseLocation.UPPER_BOUND); + validateTargetLocation( + tree, + ((AnnotatedTypeVariable) type).getLowerBound(), + TypeUseLocation.LOWER_BOUND); + break; + case METHOD: + type = atypeFactory.getMethodReturnType((MethodTree) tree); + if (type == null || type.getKind() == TypeKind.VOID) { + // Nothing to do for void methods. + // Note that for a constructor the AnnotatedExecutableType does + // not use void as return type. + return true; + } + if (TreeUtils.isConstructor((MethodTree) tree)) { + validateTargetLocation(tree, type, TypeUseLocation.CONSTRUCTOR_RESULT); + } else { + validateTargetLocation(tree, type, TypeUseLocation.RETURN); + } + break; + default: + type = atypeFactory.getAnnotatedType(tree); + } + return validateType(tree, type); } - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - - checkAccessAllowed(elem, receiver, tree); - } - - /** - * Issues an error if access not allowed, based on an @Unused annotation. - * - * @param field the field to be accessed, whose declaration might be annotated by @Unused. It can - * also be (for example) {@code this}, in which case {@code receiverType} is null. - * @param receiverType the type of the expression whose field is accessed; null if the field is - * static - * @param accessTree the access expression - */ - protected void checkAccessAllowed( - Element field, - @Nullable AnnotatedTypeMirror receiverType, - @FindDistinct ExpressionTree accessTree) { - AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class); - if (unused == null) { - return; + /** + * Tests whether the type and corresponding type tree is a valid type, and emits an error if + * that is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, tests + * the return type. + * + * @param tree the type tree supplied by the user + * @param type the type corresponding to tree + * @return true if the type is valid + */ + protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { + return typeValidator.isValid(type, tree); } - String when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement).toString(); + // This is a test to ensure that all types are valid + protected final TypeValidator typeValidator; - // TODO: Don't just look at the receiver type, but at the declaration annotations on the - // receiver. (That will enable handling type annotations that are not part of the type - // system being checked.) - - // TODO: This requires exactly the same type qualifier, but it should permit subqualifiers. - if (!AnnotationUtils.containsSameByName(receiverType.getAnnotations(), when)) { - return; + protected TypeValidator createTypeValidator() { + return new BaseTypeValidator(checker, this, atypeFactory); } - Tree tree = this.enclosingStatement(accessTree); + // ********************************************************************** + // Random helper methods + // ********************************************************************** - if (tree != null - && tree.getKind() == Tree.Kind.ASSIGNMENT - && ((AssignmentTree) tree).getVariable() == accessTree - && ((AssignmentTree) tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL) { - // Assigning unused to null is OK. - return; + /** + * Tests whether the expression should not be checked because of the tree referring to + * unannotated classes, as specified in the {@code checker.skipUses} property. + * + *

          It returns true if exprTree is a method invocation or a field access to a class whose + * qualified name matches the {@code checker.skipUses} property. + * + * @param exprTree any expression tree + * @return true if checker should not test exprTree + */ + protected final boolean shouldSkipUses(ExpressionTree exprTree) { + // System.out.printf("shouldSkipUses: %s: %s%n", exprTree.getClass(), exprTree); + // if (atypeFactory.isUnreachable(exprTree)) { + // return true; + // } + Element elm = TreeUtils.elementFromTree(exprTree); + return checker.shouldSkipUses(elm); } - checker.reportError(accessTree, "unallowed.access", field, receiverType); - } - - /** - * Tests that the qualifiers present on {@code useType} are valid qualifiers, given the qualifiers - * on the declaration of the type, {@code declarationType}. - * - *

          The check is shallow, as it does not descend into generic or array types (i.e. only - * performing the validity check on the raw type or outermost array dimension). {@link - * BaseTypeVisitor#validateTypeOf(Tree)} would call this for each type argument or array dimension - * separately. - * - *

          In most cases, {@code useType} simply needs to be a subtype of {@code declarationType}. If a - * type system makes exceptions to this rule, its implementation should override this method. - * - *

          This method is not called if {@link - * BaseTypeValidator#shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror, Tree)} - * returns false -- by default, it is not called on the top level for locals and expressions. To - * enforce a type validity property everywhere, override methods such as {@link - * BaseTypeValidator#visitDeclared} rather than this method. - * - * @param declarationType the type of the class (TypeElement) - * @param useType the use of the class (instance type) - * @param tree the tree where the type is used - * @return true if the useType is a valid use of elemType - */ - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // Don't use isSubtype(ATM, ATM) because it will return false if the types have qualifier - // parameters. - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - TypeMirror declarationTM = declarationType.getUnderlyingType(); - AnnotationMirrorSet upperBounds = atypeFactory.getTypeDeclarationBounds(declarationTM); - for (AnnotationMirror top : tops) { - AnnotationMirror upperBound = qualHierarchy.findAnnotationInHierarchy(upperBounds, top); - if (!typeHierarchy.isSubtypeShallowEffective(useType, upperBound)) { - return false; - } - } - return true; - } - - /** - * Tests that the qualifiers present on the primitive type are valid. - * - * @param type the use of the primitive type - * @param tree the tree where the type is used - * @return true if the type is a valid use of the primitive type - */ - public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { - AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); - return typeHierarchy.isSubtypeShallowEffective(type, bounds); - } - - /** - * Tests that the qualifiers present on the array type are valid. This method will be invoked for - * each array level independently, i.e. this method only needs to check the top-level qualifiers - * of an array. - * - * @param type the array type use - * @param tree the tree where the type is used - * @return true if the type is a valid array type - */ - public boolean isValidUse(AnnotatedArrayType type, Tree tree) { - AnnotationMirrorSet bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); - return typeHierarchy.isSubtypeShallowEffective(type, bounds); - } - - /** - * Tests whether the tree expressed by the passed type tree is a valid type, and emits an error if - * that is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, check - * the return type. - * - * @param tree the AST type supplied by the user - * @return true if the tree is a valid type - */ - public boolean validateTypeOf(Tree tree) { - AnnotatedTypeMirror type; - // It's quite annoying that there is no TypeTree. - switch (tree.getKind()) { - case PRIMITIVE_TYPE: - case PARAMETERIZED_TYPE: - case ARRAY_TYPE: - case UNBOUNDED_WILDCARD: - case EXTENDS_WILDCARD: - case SUPER_WILDCARD: - case ANNOTATED_TYPE: - type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); - break; - case TYPE_PARAMETER: - type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); - validateTargetLocation( - tree, ((AnnotatedTypeVariable) type).getUpperBound(), TypeUseLocation.UPPER_BOUND); - validateTargetLocation( - tree, ((AnnotatedTypeVariable) type).getLowerBound(), TypeUseLocation.LOWER_BOUND); - break; - case METHOD: - type = atypeFactory.getMethodReturnType((MethodTree) tree); - if (type == null || type.getKind() == TypeKind.VOID) { - // Nothing to do for void methods. - // Note that for a constructor the AnnotatedExecutableType does - // not use void as return type. - return true; - } - if (TreeUtils.isConstructor((MethodTree) tree)) { - validateTargetLocation(tree, type, TypeUseLocation.CONSTRUCTOR_RESULT); - } else { - validateTargetLocation(tree, type, TypeUseLocation.RETURN); - } - break; - default: - type = atypeFactory.getAnnotatedType(tree); + // ********************************************************************** + // Overriding to avoid visit part of the tree + // ********************************************************************** + + /** Override Compilation Unit so we won't visit package names or imports. */ + @Override + public Void visitCompilationUnit(CompilationUnitTree identifierTree, Void p) { + Void r = scan(identifierTree.getPackageAnnotations(), p); + // r = reduce(scan(identifierTree.getPackageName(), p), r); + // r = reduce(scan(identifierTree.getImports(), p), r); + r = reduce(scan(identifierTree.getTypeDecls(), p), r); + return r; } - return validateType(tree, type); - } - - /** - * Tests whether the type and corresponding type tree is a valid type, and emits an error if that - * is not the case (e.g. '@Mutable String'). If the tree is a method or constructor, tests the - * return type. - * - * @param tree the type tree supplied by the user - * @param type the type corresponding to tree - * @return true if the type is valid - */ - protected boolean validateType(Tree tree, AnnotatedTypeMirror type) { - return typeValidator.isValid(type, tree); - } - - // This is a test to ensure that all types are valid - protected final TypeValidator typeValidator; - - protected TypeValidator createTypeValidator() { - return new BaseTypeValidator(checker, this, atypeFactory); - } - - // ********************************************************************** - // Random helper methods - // ********************************************************************** - - /** - * Tests whether the expression should not be checked because of the tree referring to unannotated - * classes, as specified in the {@code checker.skipUses} property. - * - *

          It returns true if exprTree is a method invocation or a field access to a class whose - * qualified name matches the {@code checker.skipUses} property. - * - * @param exprTree any expression tree - * @return true if checker should not test exprTree - */ - protected final boolean shouldSkipUses(ExpressionTree exprTree) { - // System.out.printf("shouldSkipUses: %s: %s%n", exprTree.getClass(), exprTree); - // if (atypeFactory.isUnreachable(exprTree)) { - // return true; - // } - Element elm = TreeUtils.elementFromTree(exprTree); - return checker.shouldSkipUses(elm); - } - - // ********************************************************************** - // Overriding to avoid visit part of the tree - // ********************************************************************** - - /** Override Compilation Unit so we won't visit package names or imports. */ - @Override - public Void visitCompilationUnit(CompilationUnitTree identifierTree, Void p) { - Void r = scan(identifierTree.getPackageAnnotations(), p); - // r = reduce(scan(identifierTree.getPackageName(), p), r); - // r = reduce(scan(identifierTree.getImports(), p), r); - r = reduce(scan(identifierTree.getTypeDecls(), p), r); - return r; - } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java index db522cd4da8..9c8404de2f6 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/TypeValidator.java @@ -1,6 +1,7 @@ package org.checkerframework.common.basetype; import com.sun.source.tree.Tree; + import org.checkerframework.framework.type.AnnotatedTypeMirror; /** @@ -9,13 +10,13 @@ */ public interface TypeValidator { - /** - * The entry point to the type validator. Validate the type against the given tree. - * - * @param type the type to validate - * @param tree the tree from which the type originated. If the tree is a method tree, then - * validate its return type. If the tree is a variable tree, then validate its field type. - * @return true, iff the type is valid - */ - public boolean isValid(AnnotatedTypeMirror type, Tree tree); + /** + * The entry point to the type validator. Validate the type against the given tree. + * + * @param type the type to validate + * @param tree the tree from which the type originated. If the tree is a method tree, then + * validate its return type. If the tree is a variable tree, then validate its field type. + * @return true, iff the type is valid + */ + public boolean isValid(AnnotatedTypeMirror type, Tree tree); } diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java index 6edb24d99ea..596fc3ef4c9 100644 --- a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java @@ -1,16 +1,7 @@ package org.checkerframework.common.initializedfields; import com.sun.source.tree.VariableTree; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.BinaryName; import org.checkerframework.common.accumulation.AccumulationAnalysis; @@ -29,215 +20,237 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.UserError; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + /** The annotated type factory for the Initialized Fields Checker. */ public class InitializedFieldsAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory { - /** - * The type factories that determine whether the default value is consistent with the annotated - * type. If empty, warn about all uninitialized fields. - */ - private final List> defaultValueAtypeFactories; - - /** - * Creates a new InitializedFieldsAnnotatedTypeFactory. - * - * @param checker the checker - */ - public InitializedFieldsAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, InitializedFields.class, InitializedFieldsBottom.class); - - List checkerNames = getCheckerNames(); - - // There are usually few subcheckers. - defaultValueAtypeFactories = new ArrayList<>(2); - for (String checkerName : checkerNames) { - if (checkerName.equals(InitializedFieldsChecker.class.getCanonicalName())) { - continue; - } - @SuppressWarnings("signature:argument.type.incompatible") // -processor is a binary name - GenericAnnotatedTypeFactory atf = createTypeFactoryForProcessor(checkerName); - if (atf != null) { - // Add all the subcheckers so that default values are checked for the subcheckers. - for (BaseTypeChecker subchecker : atf.getChecker().getSubcheckers()) { - defaultValueAtypeFactories.add(subchecker.getTypeFactory()); - } - defaultValueAtypeFactories.add(atf); - } - } + /** + * The type factories that determine whether the default value is consistent with the annotated + * type. If empty, warn about all uninitialized fields. + */ + private final List> defaultValueAtypeFactories; - this.postInit(); - } - - /** - * Creates a new type factory for the given annotation processor, if it is a type-checker. This - * does NOT return an existing type factory. - * - * @param processorName the fully-qualified class name of an annotation processor - * @return the type factory for the given annotation processor, or null if it's not a checker - */ - private @Nullable GenericAnnotatedTypeFactory createTypeFactoryForProcessor( - @BinaryName String processorName) { - try { - Class checkerClass = Class.forName(processorName); - if (!BaseTypeChecker.class.isAssignableFrom(checkerClass)) { - return null; - } - @SuppressWarnings("unchecked") - BaseTypeChecker c = - ((Class) checkerClass).getDeclaredConstructor().newInstance(); - c.init(processingEnv); - c.initChecker(); - BaseTypeVisitor v = c.createSourceVisitorPublic(); - GenericAnnotatedTypeFactory atf = v.createTypeFactoryPublic(); - if (atf == null) { - throw new UserError("Cannot find %s; check the classpath or processorpath", processorName); - } - return atf; - } catch (ClassNotFoundException - | InstantiationException - | InvocationTargetException - | IllegalAccessException - | NoSuchMethodException e) { - throw new UserError("Problem instantiating " + processorName, e); - } - } + /** + * Creates a new InitializedFieldsAnnotatedTypeFactory. + * + * @param checker the checker + */ + public InitializedFieldsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, InitializedFields.class, InitializedFieldsBottom.class); - @Override - public InitializedFieldsContractsFromMethod getContractsFromMethod() { - return new InitializedFieldsContractsFromMethod(this); - } + List checkerNames = getCheckerNames(); - /** An array consisting only of the string "this". */ - private static final String[] thisStringArray = new String[] {"this"}; + // There are usually few subcheckers. + defaultValueAtypeFactories = new ArrayList<>(2); + for (String checkerName : checkerNames) { + if (checkerName.equals(InitializedFieldsChecker.class.getCanonicalName())) { + continue; + } + @SuppressWarnings("signature:argument.type.incompatible") // -processor is a binary name + GenericAnnotatedTypeFactory atf = + createTypeFactoryForProcessor(checkerName); + if (atf != null) { + // Add all the subcheckers so that default values are checked for the subcheckers. + for (BaseTypeChecker subchecker : atf.getChecker().getSubcheckers()) { + defaultValueAtypeFactories.add(subchecker.getTypeFactory()); + } + defaultValueAtypeFactories.add(atf); + } + } + + this.postInit(); + } - /** - * A subclass of ContractsFromMethod that adds a postcondition contract to each constructor, - * requiring that it initializes all fields. - */ - private class InitializedFieldsContractsFromMethod extends DefaultContractsFromMethod { /** - * Creates an InitializedFieldsContractsFromMethod for the given factory. + * Creates a new type factory for the given annotation processor, if it is a type-checker. This + * does NOT return an existing type factory. * - * @param factory the type factory associated with the newly-created ContractsFromMethod + * @param processorName the fully-qualified class name of an annotation processor + * @return the type factory for the given annotation processor, or null if it's not a checker */ - public InitializedFieldsContractsFromMethod(GenericAnnotatedTypeFactory factory) { - super(factory); + private @Nullable GenericAnnotatedTypeFactory createTypeFactoryForProcessor( + @BinaryName String processorName) { + try { + Class checkerClass = Class.forName(processorName); + if (!BaseTypeChecker.class.isAssignableFrom(checkerClass)) { + return null; + } + @SuppressWarnings("unchecked") + BaseTypeChecker c = + ((Class) checkerClass) + .getDeclaredConstructor() + .newInstance(); + c.init(processingEnv); + c.initChecker(); + BaseTypeVisitor v = c.createSourceVisitorPublic(); + GenericAnnotatedTypeFactory atf = v.createTypeFactoryPublic(); + if (atf == null) { + throw new UserError( + "Cannot find %s; check the classpath or processorpath", processorName); + } + return atf; + } catch (ClassNotFoundException + | InstantiationException + | InvocationTargetException + | IllegalAccessException + | NoSuchMethodException e) { + throw new UserError("Problem instantiating " + processorName, e); + } } @Override - public Set getPostconditions(ExecutableElement executableElement) { - Set result = super.getPostconditions(executableElement); - - // Only process constructors defined in source code being type-checked. - if (declarationFromElement(executableElement) != null - && executableElement.getKind() == ElementKind.CONSTRUCTOR) { - String[] fieldsToInitialize = - fieldsToInitialize((TypeElement) executableElement.getEnclosingElement()); - if (fieldsToInitialize.length != 0) { - AnnotationMirror initializedFieldsAnno; - { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, InitializedFields.class); - builder.setValue("value", fieldsToInitialize); - initializedFieldsAnno = builder.build(); - } - AnnotationMirror ensuresAnno; - { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, EnsuresInitializedFields.class); - builder.setValue("value", thisStringArray); - builder.setValue("fields", fieldsToInitialize); - ensuresAnno = builder.build(); - } - Contract.Postcondition ensuresContract = - new Contract.Postcondition("this", initializedFieldsAnno, ensuresAnno); - result.add(ensuresContract); + public InitializedFieldsContractsFromMethod getContractsFromMethod() { + return new InitializedFieldsContractsFromMethod(this); + } + + /** An array consisting only of the string "this". */ + private static final String[] thisStringArray = new String[] {"this"}; + + /** + * A subclass of ContractsFromMethod that adds a postcondition contract to each constructor, + * requiring that it initializes all fields. + */ + private class InitializedFieldsContractsFromMethod extends DefaultContractsFromMethod { + /** + * Creates an InitializedFieldsContractsFromMethod for the given factory. + * + * @param factory the type factory associated with the newly-created ContractsFromMethod + */ + public InitializedFieldsContractsFromMethod( + GenericAnnotatedTypeFactory factory) { + super(factory); } - } - return result; - } - } - - /** - * Returns the fields that the constructor must initialize. These are the fields F declared in - * this class that satisfy all of the following conditions: - * - *

            - *
          • F is a non-final field (if final, Java will issue a warning, so we don't need to). - *
          • F's declaration has no initializer. - *
          • No initialization block or static initialization block sets the field. (This is handled - * automatically because dataflow visits (static) initialization blocks as part of the - * constructor.) - *
          • F's annotated type is not consistent with the default value (0, 0.0, false, or null) - *
          - * - * @param type the type whose fields to list - * @return the fields whose type is not consistent with the default value, so the constructor must - * initialize them - */ - // It is a bit wasteful that this is recomputed for each constructor. - private String[] fieldsToInitialize(TypeElement type) { - List result = new ArrayList(); - - for (Element member : type.getEnclosedElements()) { - - if (member.getKind() != ElementKind.FIELD) { - continue; - } - - VariableElement field = (VariableElement) member; - if (ElementUtils.isFinal(field)) { - continue; - } - - VariableTree fieldTree = (VariableTree) declarationFromElement(field); - if (fieldTree.getInitializer() != null) { - continue; - } - - if (!defaultValueIsOK(field)) { - result.add(field.getSimpleName().toString()); - } - } + @Override + public Set getPostconditions(ExecutableElement executableElement) { + Set result = super.getPostconditions(executableElement); + + // Only process constructors defined in source code being type-checked. + if (declarationFromElement(executableElement) != null + && executableElement.getKind() == ElementKind.CONSTRUCTOR) { + String[] fieldsToInitialize = + fieldsToInitialize((TypeElement) executableElement.getEnclosingElement()); + if (fieldsToInitialize.length != 0) { + AnnotationMirror initializedFieldsAnno; + { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, InitializedFields.class); + builder.setValue("value", fieldsToInitialize); + initializedFieldsAnno = builder.build(); + } + AnnotationMirror ensuresAnno; + { + AnnotationBuilder builder = + new AnnotationBuilder( + processingEnv, EnsuresInitializedFields.class); + builder.setValue("value", thisStringArray); + builder.setValue("fields", fieldsToInitialize); + ensuresAnno = builder.build(); + } + Contract.Postcondition ensuresContract = + new Contract.Postcondition("this", initializedFieldsAnno, ensuresAnno); + result.add(ensuresContract); + } + } - return result.toArray(new String[0]); - } - - /** - * Returns true if the default field value (0, 0.0, false, or null) is consistent with the field's - * declared type. - * - * @param field a field - * @return true if the default field value is consistent with the field's declared type - */ - private boolean defaultValueIsOK(VariableElement field) { - if (defaultValueAtypeFactories.isEmpty()) { - return false; + return result; + } } - for (GenericAnnotatedTypeFactory defaultValueAtypeFactory : - defaultValueAtypeFactories) { - defaultValueAtypeFactory.setRoot(this.getRoot()); - // Set the root on all the subcheckers, too. - for (BaseTypeChecker subchecker : defaultValueAtypeFactory.getChecker().getSubcheckers()) { - AnnotatedTypeFactory subATF = subchecker.getTypeFactory(); - subATF.setRoot(this.getRoot()); - } - AnnotatedTypeMirror fieldType = defaultValueAtypeFactory.getAnnotatedType(field); - AnnotatedTypeMirror defaultValueType = - defaultValueAtypeFactory.getDefaultValueAnnotatedType(fieldType.getUnderlyingType()); - if (!defaultValueAtypeFactory.getTypeHierarchy().isSubtype(defaultValueType, fieldType)) { - return false; - } + /** + * Returns the fields that the constructor must initialize. These are the fields F declared in + * this class that satisfy all of the following conditions: + * + *
            + *
          • F is a non-final field (if final, Java will issue a warning, so we don't need to). + *
          • F's declaration has no initializer. + *
          • No initialization block or static initialization block sets the field. (This is handled + * automatically because dataflow visits (static) initialization blocks as part of the + * constructor.) + *
          • F's annotated type is not consistent with the default value (0, 0.0, false, or null) + *
          + * + * @param type the type whose fields to list + * @return the fields whose type is not consistent with the default value, so the constructor + * must initialize them + */ + // It is a bit wasteful that this is recomputed for each constructor. + private String[] fieldsToInitialize(TypeElement type) { + List result = new ArrayList(); + + for (Element member : type.getEnclosedElements()) { + + if (member.getKind() != ElementKind.FIELD) { + continue; + } + + VariableElement field = (VariableElement) member; + if (ElementUtils.isFinal(field)) { + continue; + } + + VariableTree fieldTree = (VariableTree) declarationFromElement(field); + if (fieldTree.getInitializer() != null) { + continue; + } + + if (!defaultValueIsOK(field)) { + result.add(field.getSimpleName().toString()); + } + } + + return result.toArray(new String[0]); } - return true; - } + /** + * Returns true if the default field value (0, 0.0, false, or null) is consistent with the + * field's declared type. + * + * @param field a field + * @return true if the default field value is consistent with the field's declared type + */ + private boolean defaultValueIsOK(VariableElement field) { + if (defaultValueAtypeFactories.isEmpty()) { + return false; + } + + for (GenericAnnotatedTypeFactory defaultValueAtypeFactory : + defaultValueAtypeFactories) { + defaultValueAtypeFactory.setRoot(this.getRoot()); + // Set the root on all the subcheckers, too. + for (BaseTypeChecker subchecker : + defaultValueAtypeFactory.getChecker().getSubcheckers()) { + AnnotatedTypeFactory subATF = subchecker.getTypeFactory(); + subATF.setRoot(this.getRoot()); + } + AnnotatedTypeMirror fieldType = defaultValueAtypeFactory.getAnnotatedType(field); + AnnotatedTypeMirror defaultValueType = + defaultValueAtypeFactory.getDefaultValueAnnotatedType( + fieldType.getUnderlyingType()); + if (!defaultValueAtypeFactory + .getTypeHierarchy() + .isSubtype(defaultValueType, fieldType)) { + return false; + } + } + + return true; + } - // Overridden because there is no InitalizedFieldsAnalysis. - @Override - protected AccumulationAnalysis createFlowAnalysis() { - return new AccumulationAnalysis(this.getChecker(), this); - } + // Overridden because there is no InitalizedFieldsAnalysis. + @Override + protected AccumulationAnalysis createFlowAnalysis() { + return new AccumulationAnalysis(this.getChecker(), this); + } } diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java index 921f1fd0020..c32e46a532b 100644 --- a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsTransfer.java @@ -13,25 +13,25 @@ /** Accumulates the names of fields that are initialized. */ public class InitializedFieldsTransfer extends AccumulationTransfer { - /** - * Create an InitializedFieldsTransfer. - * - * @param analysis the analysis - */ - public InitializedFieldsTransfer(AccumulationAnalysis analysis) { - super(analysis); - } + /** + * Create an InitializedFieldsTransfer. + * + * @param analysis the analysis + */ + public InitializedFieldsTransfer(AccumulationAnalysis analysis) { + super(analysis); + } - @Override - public TransferResult visitAssignment( - AssignmentNode node, TransferInput input) { - TransferResult result = - super.visitAssignment(node, input); - Node lhs = node.getTarget(); - if (lhs instanceof FieldAccessNode) { - FieldAccessNode fieldAccess = (FieldAccessNode) lhs; - accumulate(fieldAccess.getReceiver(), result, fieldAccess.getFieldName()); + @Override + public TransferResult visitAssignment( + AssignmentNode node, TransferInput input) { + TransferResult result = + super.visitAssignment(node, input); + Node lhs = node.getTarget(); + if (lhs instanceof FieldAccessNode) { + FieldAccessNode fieldAccess = (FieldAccessNode) lhs; + accumulate(fieldAccess.getReceiver(), result, fieldAccess.getFieldName()); + } + return result; } - return result; - } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java index 20cc4d6da52..0689e410b88 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValAnnotatedTypeFactory.java @@ -7,17 +7,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.UnionClassType; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -43,341 +33,362 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; + /** A type factory for the @ClassVal and @ClassBound annotations. */ public class ClassValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - protected final AnnotationMirror CLASSVAL_TOP = - AnnotationBuilder.fromClass(elements, UnknownClass.class); + protected final AnnotationMirror CLASSVAL_TOP = + AnnotationBuilder.fromClass(elements, UnknownClass.class); - /** The ClassBound.value argument/element. */ - private final ExecutableElement classBoundValueElement = - TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); + /** The ClassBound.value argument/element. */ + private final ExecutableElement classBoundValueElement = + TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); - /** The ClassVal.value argument/element. */ - private final ExecutableElement classValValueElement = - TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); + /** The ClassVal.value argument/element. */ + private final ExecutableElement classValValueElement = + TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); - /** - * Create a new ClassValAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this factory - */ - public ClassValAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); + /** + * Create a new ClassValAnnotatedTypeFactory. + * + * @param checker the type-checker associated with this factory + */ + public ClassValAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); - if (this.getClass() == ClassValAnnotatedTypeFactory.class) { - this.postInit(); - } - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList(UnknownClass.class, ClassVal.class, ClassBound.class, ClassValBottom.class)); - } - - /** - * Create a {@code @ClassVal} annotation with the given values. - * - * @param values the "value" field of the resulting {@code @ClassVal} annotation - * @return a {@code @ClassVal} annotation with the given values - */ - private AnnotationMirror createClassVal(List values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassVal.class); - builder.setValue("value", values); - return builder.build(); - } - - /** - * Create a {@code @ClassBound} annotation with the given values. - * - * @param values the "value" field of the resulting {@code @ClassBound} annotation - * @return a {@code @ClassBound} annotation with the given values - */ - private AnnotationMirror createClassBound(List values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassBound.class); - builder.setValue("value", values); - return builder.build(); - } - - /** - * Returns the list of classnames from {@code @ClassBound} or {@code @ClassVal} if anno is - * {@code @ClassBound} or {@code @ClassVal}, otherwise returns an empty list. - * - * @param anno any AnnotationMirror - * @return list of classnames in anno - */ - public List getClassNamesFromAnnotation(AnnotationMirror anno) { - if (areSameByClass(anno, ClassBound.class)) { - return AnnotationUtils.getElementValueArray(anno, classBoundValueElement, String.class); - } else if (areSameByClass(anno, ClassVal.class)) { - return AnnotationUtils.getElementValueArray(anno, classValValueElement, String.class); - } else { - return Collections.emptyList(); + if (this.getClass() == ClassValAnnotatedTypeFactory.class) { + this.postInit(); + } } - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new ClassValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } - /** The qualifier hierarchy for the ClassVal type system. */ - protected class ClassValQualifierHierarchy extends ElementQualifierHierarchy { + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + UnknownClass.class, + ClassVal.class, + ClassBound.class, + ClassValBottom.class)); + } /** - * Creates a ClassValQualifierHierarchy from the given classes. + * Create a {@code @ClassVal} annotation with the given values. * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils + * @param values the "value" field of the resulting {@code @ClassVal} annotation + * @return a {@code @ClassVal} annotation with the given values */ - public ClassValQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, ClassValAnnotatedTypeFactory.this); + private AnnotationMirror createClassVal(List values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassVal.class); + builder.setValue("value", values); + return builder.build(); } - /* - * Determines the least upper bound of a1 and a2. If both are ClassVal - * annotations, then the least upper bound is the set of elements - * obtained by combining the values of both annotations. + /** + * Create a {@code @ClassBound} annotation with the given values. + * + * @param values the "value" field of the resulting {@code @ClassBound} annotation + * @return a {@code @ClassBound} annotation with the given values */ - @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } else { - List a1ClassNames = getClassNamesFromAnnotation(a1); - List a2ClassNames = getClassNamesFromAnnotation(a2); - // There are usually few arguments/elements of @ClassBound and @ClassVal. - List lubClassNames = CollectionsPlume.listUnion(a1ClassNames, a2ClassNames); - - // If either annotation is a ClassBound, the lub must also be a class bound. - if (areSameByClass(a1, ClassBound.class) || areSameByClass(a2, ClassBound.class)) { - return createClassBound(lubClassNames); - } else { - return createClassVal(lubClassNames); - } - } + private AnnotationMirror createClassBound(List values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ClassBound.class); + builder.setValue("value", values); + return builder.build(); } - @Override - public @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } else { - List a1ClassNames = getClassNamesFromAnnotation(a1); - List a2ClassNames = getClassNamesFromAnnotation(a2); - List glbClassNames = CollectionsPlume.listIntersection(a1ClassNames, a2ClassNames); - - // If either annotation is a ClassVal, the glb must also be a ClassVal. - // For example: - // GLB( @ClassVal(a,b), @ClassBound(a,c)) is @ClassVal(a) - // because @ClassBound(a) is not a subtype of @ClassVal(a,b) - if (areSameByClass(a1, ClassVal.class) || areSameByClass(a2, ClassVal.class)) { - return createClassVal(glbClassNames); + /** + * Returns the list of classnames from {@code @ClassBound} or {@code @ClassVal} if anno is + * {@code @ClassBound} or {@code @ClassVal}, otherwise returns an empty list. + * + * @param anno any AnnotationMirror + * @return list of classnames in anno + */ + public List getClassNamesFromAnnotation(AnnotationMirror anno) { + if (areSameByClass(anno, ClassBound.class)) { + return AnnotationUtils.getElementValueArray(anno, classBoundValueElement, String.class); + } else if (areSameByClass(anno, ClassVal.class)) { + return AnnotationUtils.getElementValueArray(anno, classValValueElement, String.class); } else { - return createClassBound(glbClassNames); + return Collections.emptyList(); } - } } - /* - * Computes subtyping as per the subtyping in the qualifier hierarchy - * structure unless both annotations are ClassVal. In this case, rhs is - * a subtype of lhs iff lhs contains every element of rhs. - */ @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (AnnotationUtils.areSame(subAnno, superAnno) - || areSameByClass(superAnno, UnknownClass.class) - || areSameByClass(subAnno, ClassValBottom.class)) { - return true; - } - if (areSameByClass(subAnno, UnknownClass.class) - || areSameByClass(superAnno, ClassValBottom.class)) { - return false; - } - if (areSameByClass(superAnno, ClassVal.class) && areSameByClass(subAnno, ClassBound.class)) { - return false; - } - - // if super: ClassVal && sub is ClassVal - // if super: ClassBound && (sub is ClassBound or ClassVal) - - List supValues = getClassNamesFromAnnotation(superAnno); - List subValues = getClassNamesFromAnnotation(subAnno); - - return supValues.containsAll(subValues); - } - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new ClassValTreeAnnotator(this), super.createTreeAnnotator()); - } - - /** - * Implements the following type inference rules. - * - *
          -   * C.class:             @ClassVal(fully qualified name of C)
          -   * Class.forName(name): @ClassVal("name")
          -   * exp.getClass():      @ClassBound(fully qualified classname of exp)
          -   * 
          - */ - protected class ClassValTreeAnnotator extends TreeAnnotator { - - protected ClassValTreeAnnotator(ClassValAnnotatedTypeFactory factory) { - super(factory); + protected QualifierHierarchy createQualifierHierarchy() { + return new ClassValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - if (TreeUtils.isClassLiteral(tree)) { - // Create annotations for Class literals - // C.class: @ClassVal(fully qualified name of C) - ExpressionTree etree = tree.getExpression(); - Type classType = (Type) TreeUtils.typeOf(etree); - String name = getClassNameFromType(classType); - if (name != null && !name.equals("void")) { - AnnotationMirror newQual = createClassVal(Arrays.asList(name)); - type.replaceAnnotation(newQual); + /** The qualifier hierarchy for the ClassVal type system. */ + protected class ClassValQualifierHierarchy extends ElementQualifierHierarchy { + + /** + * Creates a ClassValQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + public ClassValQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, ClassValAnnotatedTypeFactory.this); } - } - return null; - } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - - if (isForNameMethodInvocation(tree)) { - // Class.forName(name): @ClassVal("name") - ExpressionTree arg = tree.getArguments().get(0); - List classNames = getStringValues(arg); - if (classNames != null) { - AnnotationMirror newQual = createClassVal(classNames); - type.replaceAnnotation(newQual); + /* + * Determines the least upper bound of a1 and a2. If both are ClassVal + * annotations, then the least upper bound is the set of elements + * obtained by combining the values of both annotations. + */ + @Override + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } else { + List a1ClassNames = getClassNamesFromAnnotation(a1); + List a2ClassNames = getClassNamesFromAnnotation(a2); + // There are usually few arguments/elements of @ClassBound and @ClassVal. + List lubClassNames = CollectionsPlume.listUnion(a1ClassNames, a2ClassNames); + + // If either annotation is a ClassBound, the lub must also be a class bound. + if (areSameByClass(a1, ClassBound.class) || areSameByClass(a2, ClassBound.class)) { + return createClassBound(lubClassNames); + } else { + return createClassVal(lubClassNames); + } + } + } + + @Override + public @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } else { + List a1ClassNames = getClassNamesFromAnnotation(a1); + List a2ClassNames = getClassNamesFromAnnotation(a2); + List glbClassNames = + CollectionsPlume.listIntersection(a1ClassNames, a2ClassNames); + + // If either annotation is a ClassVal, the glb must also be a ClassVal. + // For example: + // GLB( @ClassVal(a,b), @ClassBound(a,c)) is @ClassVal(a) + // because @ClassBound(a) is not a subtype of @ClassVal(a,b) + if (areSameByClass(a1, ClassVal.class) || areSameByClass(a2, ClassVal.class)) { + return createClassVal(glbClassNames); + } else { + return createClassBound(glbClassNames); + } + } } - } else if (isGetClassMethodInvocation(tree)) { - // exp.getClass(): @ClassBound(fully qualified class name of exp) - Type clType; - if (TreeUtils.getReceiverTree(tree) != null) { - clType = (Type) TreeUtils.typeOf(TreeUtils.getReceiverTree(tree)); - } else { // receiver is null, so it is implicitly "this" - ClassTree classTree = TreePathUtil.enclosingClass(getPath(tree)); - clType = (Type) TreeUtils.typeOf(classTree); + + /* + * Computes subtyping as per the subtyping in the qualifier hierarchy + * structure unless both annotations are ClassVal. In this case, rhs is + * a subtype of lhs iff lhs contains every element of rhs. + */ + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (AnnotationUtils.areSame(subAnno, superAnno) + || areSameByClass(superAnno, UnknownClass.class) + || areSameByClass(subAnno, ClassValBottom.class)) { + return true; + } + if (areSameByClass(subAnno, UnknownClass.class) + || areSameByClass(superAnno, ClassValBottom.class)) { + return false; + } + if (areSameByClass(superAnno, ClassVal.class) + && areSameByClass(subAnno, ClassBound.class)) { + return false; + } + + // if super: ClassVal && sub is ClassVal + // if super: ClassBound && (sub is ClassBound or ClassVal) + + List supValues = getClassNamesFromAnnotation(superAnno); + List subValues = getClassNamesFromAnnotation(subAnno); + + return supValues.containsAll(subValues); } - String className = getClassNameFromType(clType); - AnnotationMirror newQual = createClassBound(Arrays.asList(className)); - type.replaceAnnotation(newQual); - } - return null; } - /** - * Return true if this is an invocation of a method annotated with @ForName. An example of such - * a method is {@link Class#forName}. - * - * @param tree a method invocation - * @return true if this is an invocation of a method annotated with @ForName - */ - private boolean isForNameMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), ForName.class) != null; + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new ClassValTreeAnnotator(this), super.createTreeAnnotator()); } /** - * Return true if this is an invocation of a method annotated with @GetClass. An example of such - * a method is {@link Object#getClass}. + * Implements the following type inference rules. * - * @param tree a method invocation - * @return true if this is an invocation of a method annotated with @GetClass + *
          +     * C.class:             @ClassVal(fully qualified name of C)
          +     * Class.forName(name): @ClassVal("name")
          +     * exp.getClass():      @ClassBound(fully qualified classname of exp)
          +     * 
          */ - private boolean isGetClassMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetClass.class) != null; - } + protected class ClassValTreeAnnotator extends TreeAnnotator { - private @Nullable List getStringValues(ExpressionTree arg) { - ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotationMirror annotation = valueATF.getAnnotationMirror(arg, StringVal.class); - if (annotation == null) { - return null; - } - return AnnotationUtils.getElementValueArray( - annotation, valueATF.stringValValueElement, String.class); - } + protected ClassValTreeAnnotator(ClassValAnnotatedTypeFactory factory) { + super(factory); + } - // TODO: This looks like it returns a @BinaryName. Verify that fact and add a type - // qualifier. - /** - * Return String representation of class name. This will not return the correct name for - * anonymous classes. - */ - private String getClassNameFromType(Type classType) { - switch (classType.getKind()) { - case ARRAY: - String array = ""; - while (classType.getKind() == TypeKind.ARRAY) { - classType = ((ArrayType) classType).getComponentType(); - array += "[]"; - } - return getClassNameFromType(classType) + array; - case DECLARED: - StringBuilder className = - new StringBuilder(TypesUtils.getQualifiedName((DeclaredType) classType)); - if (classType.getEnclosingType() != null) { - while (classType.getEnclosingType().getKind() != TypeKind.NONE) { - classType = classType.getEnclosingType(); - int last = className.lastIndexOf("."); - if (last > -1) { - className.replace(last, last + 1, "$"); - } + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isClassLiteral(tree)) { + // Create annotations for Class literals + // C.class: @ClassVal(fully qualified name of C) + ExpressionTree etree = tree.getExpression(); + Type classType = (Type) TreeUtils.typeOf(etree); + String name = getClassNameFromType(classType); + if (name != null && !name.equals("void")) { + AnnotationMirror newQual = createClassVal(Arrays.asList(name)); + type.replaceAnnotation(newQual); + } + } + return null; + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + + if (isForNameMethodInvocation(tree)) { + // Class.forName(name): @ClassVal("name") + ExpressionTree arg = tree.getArguments().get(0); + List classNames = getStringValues(arg); + if (classNames != null) { + AnnotationMirror newQual = createClassVal(classNames); + type.replaceAnnotation(newQual); + } + } else if (isGetClassMethodInvocation(tree)) { + // exp.getClass(): @ClassBound(fully qualified class name of exp) + Type clType; + if (TreeUtils.getReceiverTree(tree) != null) { + clType = (Type) TreeUtils.typeOf(TreeUtils.getReceiverTree(tree)); + } else { // receiver is null, so it is implicitly "this" + ClassTree classTree = TreePathUtil.enclosingClass(getPath(tree)); + clType = (Type) TreeUtils.typeOf(classTree); + } + String className = getClassNameFromType(clType); + AnnotationMirror newQual = createClassBound(Arrays.asList(className)); + type.replaceAnnotation(newQual); + } + return null; + } + + /** + * Return true if this is an invocation of a method annotated with @ForName. An example of + * such a method is {@link Class#forName}. + * + * @param tree a method invocation + * @return true if this is an invocation of a method annotated with @ForName + */ + private boolean isForNameMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), ForName.class) != null; + } + + /** + * Return true if this is an invocation of a method annotated with @GetClass. An example of + * such a method is {@link Object#getClass}. + * + * @param tree a method invocation + * @return true if this is an invocation of a method annotated with @GetClass + */ + private boolean isGetClassMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetClass.class) != null; + } + + private @Nullable List getStringValues(ExpressionTree arg) { + ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotationMirror annotation = valueATF.getAnnotationMirror(arg, StringVal.class); + if (annotation == null) { + return null; } - } - return className.toString(); - case INTERSECTION: - // This could be more precise - return "java.lang.Object"; - case NULL: - return "java.lang.Object"; - case UNION: - classType = ((UnionClassType) classType).getLub(); - return getClassNameFromType(classType); - case TYPEVAR: - case WILDCARD: - classType = classType.getUpperBound(); - return getClassNameFromType(classType); - case INT: - return int.class.getCanonicalName(); - case LONG: - return long.class.getCanonicalName(); - case SHORT: - return short.class.getCanonicalName(); - case BYTE: - return byte.class.getCanonicalName(); - case CHAR: - return char.class.getCanonicalName(); - case DOUBLE: - return double.class.getCanonicalName(); - case FLOAT: - return float.class.getCanonicalName(); - case BOOLEAN: - return boolean.class.getCanonicalName(); - case VOID: - return "void"; - default: - throw new BugInCF( - "ClassValAnnotatedTypeFactory.getClassname: did not expect " + classType.getKind()); - } + return AnnotationUtils.getElementValueArray( + annotation, valueATF.stringValValueElement, String.class); + } + + // TODO: This looks like it returns a @BinaryName. Verify that fact and add a type + // qualifier. + /** + * Return String representation of class name. This will not return the correct name for + * anonymous classes. + */ + private String getClassNameFromType(Type classType) { + switch (classType.getKind()) { + case ARRAY: + String array = ""; + while (classType.getKind() == TypeKind.ARRAY) { + classType = ((ArrayType) classType).getComponentType(); + array += "[]"; + } + return getClassNameFromType(classType) + array; + case DECLARED: + StringBuilder className = + new StringBuilder( + TypesUtils.getQualifiedName((DeclaredType) classType)); + if (classType.getEnclosingType() != null) { + while (classType.getEnclosingType().getKind() != TypeKind.NONE) { + classType = classType.getEnclosingType(); + int last = className.lastIndexOf("."); + if (last > -1) { + className.replace(last, last + 1, "$"); + } + } + } + return className.toString(); + case INTERSECTION: + // This could be more precise + return "java.lang.Object"; + case NULL: + return "java.lang.Object"; + case UNION: + classType = ((UnionClassType) classType).getLub(); + return getClassNameFromType(classType); + case TYPEVAR: + case WILDCARD: + classType = classType.getUpperBound(); + return getClassNameFromType(classType); + case INT: + return int.class.getCanonicalName(); + case LONG: + return long.class.getCanonicalName(); + case SHORT: + return short.class.getCanonicalName(); + case BYTE: + return byte.class.getCanonicalName(); + case CHAR: + return char.class.getCanonicalName(); + case DOUBLE: + return double.class.getCanonicalName(); + case FLOAT: + return float.class.getCanonicalName(); + case BOOLEAN: + return boolean.class.getCanonicalName(); + case VOID: + return "void"; + default: + throw new BugInCF( + "ClassValAnnotatedTypeFactory.getClassname: did not expect " + + classType.getKind()); + } + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java index 6afc4dc630d..e3fc55fa96b 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValChecker.java @@ -1,12 +1,13 @@ package org.checkerframework.common.reflection; -import java.util.LinkedHashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; import org.plumelib.util.CollectionsPlume; +import java.util.LinkedHashSet; +import java.util.Set; + /** * The ClassVal Checker provides a sound estimate of the binary name of Class objects. * @@ -14,26 +15,26 @@ */ public class ClassValChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ClassValVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ClassValVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - // Don't call super otherwise MethodVal will be added as a subChecker - // which creates a circular dependency. - // Use the same Set implementation as super. - Set> subCheckers = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); - subCheckers.add(ValueChecker.class); - return subCheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Don't call super otherwise MethodVal will be added as a subChecker + // which creates a circular dependency. + // Use the same Set implementation as super. + Set> subCheckers = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); + subCheckers.add(ValueChecker.class); + return subCheckers; + } - @Override - public boolean shouldResolveReflection() { - // Because this checker is a subchecker of MethodVal, - // reflection can't be resolved. - return false; - } + @Override + public boolean shouldResolveReflection() { + // Because this checker is a subchecker of MethodVal, + // reflection can't be resolved. + return false; + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java index ab138975a3e..4704948ba9d 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java @@ -1,8 +1,7 @@ package org.checkerframework.common.reflection; import com.sun.source.tree.Tree; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -12,52 +11,59 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.plumelib.reflection.Signatures; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + /** A visitor to verify validity of {@code @}{@link ClassVal} annotations. */ public class ClassValVisitor extends BaseTypeVisitor { - /** - * Create a new ClassValVisitor. - * - * @param checker the associated type-checker - */ - public ClassValVisitor(BaseTypeChecker checker) { - super(checker); - } - - @Override - protected ClassValAnnotatedTypeFactory createTypeFactory() { - return new ClassValAnnotatedTypeFactory(checker); - } - - @Override - protected BaseTypeValidator createTypeValidator() { - return new ClassNameValidator(checker, this, atypeFactory); - } + /** + * Create a new ClassValVisitor. + * + * @param checker the associated type-checker + */ + public ClassValVisitor(BaseTypeChecker checker) { + super(checker); + } + + @Override + protected ClassValAnnotatedTypeFactory createTypeFactory() { + return new ClassValAnnotatedTypeFactory(checker); + } + + @Override + protected BaseTypeValidator createTypeValidator() { + return new ClassNameValidator(checker, this, atypeFactory); + } } class ClassNameValidator extends BaseTypeValidator { - public ClassNameValidator( - BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } - - /** - * This implementation reports an "illegal.classname" error if the type contains a @ClassVal - * annotation with a string that is not a valid class name. - */ - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - AnnotationMirror classVal = type.getAnnotation(ClassVal.class); - classVal = classVal == null ? type.getAnnotation(ClassBound.class) : classVal; - if (classVal != null) { - List classNames = - ((ClassValAnnotatedTypeFactory) atypeFactory).getClassNamesFromAnnotation(classVal); - for (String className : classNames) { - if (!Signatures.isFqBinaryName(className)) { - checker.reportError(tree, "illegal.classname", className, type); + public ClassNameValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } + + /** + * This implementation reports an "illegal.classname" error if the type contains a @ClassVal + * annotation with a string that is not a valid class name. + */ + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + AnnotationMirror classVal = type.getAnnotation(ClassVal.class); + classVal = classVal == null ? type.getAnnotation(ClassBound.class) : classVal; + if (classVal != null) { + List classNames = + ((ClassValAnnotatedTypeFactory) atypeFactory) + .getClassNamesFromAnnotation(classVal); + for (String className : classNames) { + if (!Signatures.isFqBinaryName(className)) { + checker.reportError(tree, "illegal.classname", className, type); + } + } } - } + return super.visitDeclared(type, tree); } - return super.visitDeclared(type, tree); - } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java index 7decc8dfea2..e30115d7ec4 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java @@ -24,21 +24,7 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.reflection.qual.Invoke; @@ -56,6 +42,23 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; + /** * Default implementation of {@link ReflectionResolver}. It resolves calls to: * @@ -68,613 +71,630 @@ */ public class DefaultReflectionResolver implements ReflectionResolver { - /** Message prefix added to verbose reflection messages. */ - public static final String MSG_PREFEX_REFLECTION = "[Reflection] "; - - private final BaseTypeChecker checker; - private final AnnotationProvider provider; - private final ProcessingEnvironment processingEnv; - private final Trees trees; - private final boolean debug; - - public DefaultReflectionResolver( - BaseTypeChecker checker, MethodValAnnotatedTypeFactory methodValProvider, boolean debug) { - this.checker = checker; - this.provider = methodValProvider; - this.processingEnv = checker.getProcessingEnvironment(); - this.trees = Trees.instance(processingEnv); - this.debug = debug; - } - - @Override - public boolean isReflectiveMethodInvocation(MethodInvocationTree tree) { - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - return (provider.getDeclAnnotation(methodElt, Invoke.class) != null - || provider.getDeclAnnotation(methodElt, NewInstance.class) != null); - } - - @Override - public ParameterizedExecutableType resolveReflectiveCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult) { - assert isReflectiveMethodInvocation(tree); - if (provider.getDeclAnnotation(TreeUtils.elementFromUse(tree), NewInstance.class) != null) { - return resolveConstructorCall(factory, tree, origResult); - } else { - return resolveMethodCall(factory, tree, origResult); - } - } - - /** - * Resolves a call to {@link Method#invoke(Object, Object...)}. - * - * @param factory the {@link AnnotatedTypeFactory} of the underlying type system - * @param tree the method invocation tree that has to be resolved - * @param origResult the original result from {@code factory.methodFromUse} - * @return the resolved type of the call - */ - private ParameterizedExecutableType resolveMethodCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult) { - debugReflection("Try to resolve reflective method call: " + tree); - List possibleMethods = resolveReflectiveMethod(tree, factory); - - // Reflective method could not be resolved - if (possibleMethods.isEmpty()) { - return origResult; + /** Message prefix added to verbose reflection messages. */ + public static final String MSG_PREFEX_REFLECTION = "[Reflection] "; + + private final BaseTypeChecker checker; + private final AnnotationProvider provider; + private final ProcessingEnvironment processingEnv; + private final Trees trees; + private final boolean debug; + + public DefaultReflectionResolver( + BaseTypeChecker checker, + MethodValAnnotatedTypeFactory methodValProvider, + boolean debug) { + this.checker = checker; + this.provider = methodValProvider; + this.processingEnv = checker.getProcessingEnvironment(); + this.trees = Trees.instance(processingEnv); + this.debug = debug; } - Set returnLub = null; - Set receiverGlb = null; - Set paramsGlb = null; - - // Iterate over all possible methods: lub return types, and glb receiver and parameter types - for (MethodInvocationTree resolvedTree : possibleMethods) { - debugReflection("Resolved method invocation: " + resolvedTree); - if (!checkMethodArguments(resolvedTree)) { - debugReflection("Spoofed tree's arguments did not match declaration" + resolvedTree); - // Calling methodFromUse on these sorts of trees will cause an assertion to fail in - // QualifierPolymorphism.PolyCollector.visitArray(...) - continue; - } - ParameterizedExecutableType resolvedResult = factory.methodFromUse(resolvedTree); - - AnnotatedTypeMirror returnType = resolvedResult.executableType.getReturnType(); - TypeMirror returnTM = returnType.getUnderlyingType(); - - // Lub return types - returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); - - // Glb receiver types (actual method receiver is passed as first - // argument to invoke(Object, Object[])) - // Check for static methods whose receiver is null - AnnotatedTypeMirror receiverType = resolvedResult.executableType.getReceiverType(); - if (receiverType == null) { - // If the method is static the first argument to Method.invoke isn't used, so assume - // top. - if (receiverGlb == null) { - receiverGlb = - new AnnotationMirrorSet(factory.getQualifierHierarchy().getTopAnnotations()); - } - } else { - TypeMirror receiverTM = receiverType.getUnderlyingType(); - receiverGlb = - glb(receiverGlb, receiverTM, receiverType.getAnnotations(), receiverTM, factory); - } - - // Glb parameter types. All formal parameter types get combined together because - // Method#invoke takes as argument an array of parameter types, so there is no way to - // distinguish the types of different formal parameters. - for (AnnotatedTypeMirror mirror : resolvedResult.executableType.getParameterTypes()) { - TypeMirror mirrorTM = mirror.getUnderlyingType(); - paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); - } + @Override + public boolean isReflectiveMethodInvocation(MethodInvocationTree tree) { + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + return (provider.getDeclAnnotation(methodElt, Invoke.class) != null + || provider.getDeclAnnotation(methodElt, NewInstance.class) != null); } - if (returnLub == null) { - // None of the spoofed tree's arguments matched the declared method - return origResult; + @Override + public ParameterizedExecutableType resolveReflectiveCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult) { + assert isReflectiveMethodInvocation(tree); + if (provider.getDeclAnnotation(TreeUtils.elementFromUse(tree), NewInstance.class) != null) { + return resolveConstructorCall(factory, tree, origResult); + } else { + return resolveMethodCall(factory, tree, origResult); + } } - /* - * Clear all original (return, receiver, parameter type) annotations and - * set lub/glb annotations from resolved method(s) + /** + * Resolves a call to {@link Method#invoke(Object, Object...)}. + * + * @param factory the {@link AnnotatedTypeFactory} of the underlying type system + * @param tree the method invocation tree that has to be resolved + * @param origResult the original result from {@code factory.methodFromUse} + * @return the resolved type of the call */ + private ParameterizedExecutableType resolveMethodCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult) { + debugReflection("Try to resolve reflective method call: " + tree); + List possibleMethods = resolveReflectiveMethod(tree, factory); + + // Reflective method could not be resolved + if (possibleMethods.isEmpty()) { + return origResult; + } - // return value - origResult.executableType.getReturnType().clearAnnotations(); - origResult.executableType.getReturnType().addAnnotations(returnLub); + Set returnLub = null; + Set receiverGlb = null; + Set paramsGlb = null; + + // Iterate over all possible methods: lub return types, and glb receiver and parameter types + for (MethodInvocationTree resolvedTree : possibleMethods) { + debugReflection("Resolved method invocation: " + resolvedTree); + if (!checkMethodArguments(resolvedTree)) { + debugReflection( + "Spoofed tree's arguments did not match declaration" + resolvedTree); + // Calling methodFromUse on these sorts of trees will cause an assertion to fail in + // QualifierPolymorphism.PolyCollector.visitArray(...) + continue; + } + ParameterizedExecutableType resolvedResult = factory.methodFromUse(resolvedTree); + + AnnotatedTypeMirror returnType = resolvedResult.executableType.getReturnType(); + TypeMirror returnTM = returnType.getUnderlyingType(); + + // Lub return types + returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); + + // Glb receiver types (actual method receiver is passed as first + // argument to invoke(Object, Object[])) + // Check for static methods whose receiver is null + AnnotatedTypeMirror receiverType = resolvedResult.executableType.getReceiverType(); + if (receiverType == null) { + // If the method is static the first argument to Method.invoke isn't used, so assume + // top. + if (receiverGlb == null) { + receiverGlb = + new AnnotationMirrorSet( + factory.getQualifierHierarchy().getTopAnnotations()); + } + } else { + TypeMirror receiverTM = receiverType.getUnderlyingType(); + receiverGlb = + glb( + receiverGlb, + receiverTM, + receiverType.getAnnotations(), + receiverTM, + factory); + } + + // Glb parameter types. All formal parameter types get combined together because + // Method#invoke takes as argument an array of parameter types, so there is no way to + // distinguish the types of different formal parameters. + for (AnnotatedTypeMirror mirror : resolvedResult.executableType.getParameterTypes()) { + TypeMirror mirrorTM = mirror.getUnderlyingType(); + paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); + } + } - // receiver type - origResult.executableType.getParameterTypes().get(0).clearAnnotations(); - origResult.executableType.getParameterTypes().get(0).addAnnotations(receiverGlb); + if (returnLub == null) { + // None of the spoofed tree's arguments matched the declared method + return origResult; + } - // parameter types - if (paramsGlb != null) { - AnnotatedArrayType origArrayType = - (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(1); - origArrayType.getComponentType().clearAnnotations(); - origArrayType.getComponentType().addAnnotations(paramsGlb); - } + /* + * Clear all original (return, receiver, parameter type) annotations and + * set lub/glb annotations from resolved method(s) + */ + + // return value + origResult.executableType.getReturnType().clearAnnotations(); + origResult.executableType.getReturnType().addAnnotations(returnLub); + + // receiver type + origResult.executableType.getParameterTypes().get(0).clearAnnotations(); + origResult.executableType.getParameterTypes().get(0).addAnnotations(receiverGlb); + + // parameter types + if (paramsGlb != null) { + AnnotatedArrayType origArrayType = + (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(1); + origArrayType.getComponentType().clearAnnotations(); + origArrayType.getComponentType().addAnnotations(paramsGlb); + } - debugReflection("Resolved annotations: " + origResult.executableType); - return origResult; - } - - /** - * Checks that arguments of a method invocation are consistent with their corresponding - * parameters. - * - * @param resolvedTree a method invocation - * @return true if arguments are consistent with parameters - */ - private boolean checkMethodArguments(MethodInvocationTree resolvedTree) { - // type.getKind() == actualType.getKind() - ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); - return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); - } - - /** - * Checks that arguments of a constructor invocation are consistent with their corresponding - * parameters. - * - * @param resolvedTree a constructor invocation - * @return true if arguments are consistent with parameters - */ - private boolean checkNewClassArguments(NewClassTree resolvedTree) { - ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); - return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); - } - - /** - * Checks that argument are consistent with their corresponding parameter types. Common code used - * by {@link #checkMethodArguments} and {@link #checkNewClassArguments}. - * - * @param parameters formal parameters - * @param arguments actual arguments - * @return true if argument are consistent with their corresponding parameter types - */ - private boolean checkArguments( - List parameters, List arguments) { - if (parameters.size() != arguments.size()) { - return false; + debugReflection("Resolved annotations: " + origResult.executableType); + return origResult; } - for (int i = 0; i < parameters.size(); i++) { - VariableElement param = parameters.get(i); - ExpressionTree arg = arguments.get(i); - TypeMirror argType = TreeUtils.typeOf(arg); - TypeMirror paramType = param.asType(); - if (argType.getKind() == TypeKind.ARRAY && paramType.getKind() != argType.getKind()) { - return false; - } + /** + * Checks that arguments of a method invocation are consistent with their corresponding + * parameters. + * + * @param resolvedTree a method invocation + * @return true if arguments are consistent with parameters + */ + private boolean checkMethodArguments(MethodInvocationTree resolvedTree) { + // type.getKind() == actualType.getKind() + ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); + return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } - return true; - } - - /** - * Resolves a call to {@link Constructor#newInstance(Object...)}. - * - * @param factory the {@link AnnotatedTypeFactory} of the underlying type system - * @param tree the method invocation tree (representing a constructor call) that has to be - * resolved - * @param origResult the original result from {@code factory.methodFromUse} - * @return the resolved type of the call - */ - private ParameterizedExecutableType resolveConstructorCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult) { - debugReflection("Try to resolve reflective constructor call: " + tree); - List possibleConstructors = resolveReflectiveConstructor(tree, factory); - - // Reflective constructor could not be resolved - if (possibleConstructors.isEmpty()) { - return origResult; + /** + * Checks that arguments of a constructor invocation are consistent with their corresponding + * parameters. + * + * @param resolvedTree a constructor invocation + * @return true if arguments are consistent with parameters + */ + private boolean checkNewClassArguments(NewClassTree resolvedTree) { + ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); + return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } - Set returnLub = null; - Set paramsGlb = null; - - // Iterate over all possible constructors: lub return types and glb parameter types - for (JCNewClass resolvedTree : possibleConstructors) { - debugReflection("Resolved constructor invocation: " + resolvedTree); - if (!checkNewClassArguments(resolvedTree)) { - debugReflection("Spoofed tree's arguments did not match declaration" + resolvedTree); - // Calling methodFromUse on these sorts of trees will cause an assertion to fail in - // QualifierPolymorphism.PolyCollector.visitArray(...) - continue; - } - ParameterizedExecutableType resolvedResult = factory.constructorFromUse(resolvedTree); - AnnotatedExecutableType executableType = resolvedResult.executableType; - AnnotatedTypeMirror returnType = executableType.getReturnType(); - TypeMirror returnTM = returnType.getUnderlyingType(); - - // Lub return types - returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); - - // Glb parameter types - for (AnnotatedTypeMirror mirror : executableType.getParameterTypes()) { - TypeMirror mirrorTM = mirror.getUnderlyingType(); - paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); - } - } - if (returnLub == null) { - // None of the spoofed tree's arguments matched the declared method - return origResult; - } - /* - * Clear all original (return, parameter type) annotations and set - * lub/glb annotations from resolved constructors. + /** + * Checks that argument are consistent with their corresponding parameter types. Common code + * used by {@link #checkMethodArguments} and {@link #checkNewClassArguments}. + * + * @param parameters formal parameters + * @param arguments actual arguments + * @return true if argument are consistent with their corresponding parameter types */ + private boolean checkArguments( + List parameters, List arguments) { + if (parameters.size() != arguments.size()) { + return false; + } - // return value - origResult.executableType.getReturnType().clearAnnotations(); - origResult.executableType.getReturnType().addAnnotations(returnLub); + for (int i = 0; i < parameters.size(); i++) { + VariableElement param = parameters.get(i); + ExpressionTree arg = arguments.get(i); + TypeMirror argType = TreeUtils.typeOf(arg); + TypeMirror paramType = param.asType(); + if (argType.getKind() == TypeKind.ARRAY && paramType.getKind() != argType.getKind()) { + return false; + } + } - // parameter types - if (paramsGlb != null) { - AnnotatedArrayType origArrayType = - (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(0); - origArrayType.getComponentType().clearAnnotations(); - origArrayType.getComponentType().addAnnotations(paramsGlb); + return true; } - debugReflection("Resolved annotations: " + origResult.executableType); - return origResult; - } - - /** - * Resolves a reflective method call and returns all possible corresponding method calls. - * - * @param tree the MethodInvocationTree AST node that is to be resolved (Method.invoke) - * @return a (potentially empty) list of all resolved MethodInvocationTrees - */ - private List resolveReflectiveMethod( - MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { - assert isReflectiveMethodInvocation(tree); - JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - TreeMaker make = TreeMaker.instance(context); - TreePath path = reflectionFactory.getPath(tree); - JavacScope scope = (JavacScope) trees.getScope(path); - Env env = scope.getEnv(); - - boolean unknown = isUnknownMethod(tree); - - AnnotationMirror estimate = getMethodVal(tree); - - if (estimate == null) { - debugReflection("MethodVal is unknown for: " + tree); - debugReflection("UnknownMethod annotation: " + unknown); - return Collections.emptyList(); - } + /** + * Resolves a call to {@link Constructor#newInstance(Object...)}. + * + * @param factory the {@link AnnotatedTypeFactory} of the underlying type system + * @param tree the method invocation tree (representing a constructor call) that has to be + * resolved + * @param origResult the original result from {@code factory.methodFromUse} + * @return the resolved type of the call + */ + private ParameterizedExecutableType resolveConstructorCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult) { + debugReflection("Try to resolve reflective constructor call: " + tree); + List possibleConstructors = resolveReflectiveConstructor(tree, factory); + + // Reflective constructor could not be resolved + if (possibleConstructors.isEmpty()) { + return origResult; + } - debugReflection("MethodVal type system annotations: " + estimate); - - List listClassNames = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValClassNameElement, String.class); - List listMethodNames = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValMethodNameElement, String.class); - List listParamLengths = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValParamsElement, Integer.class); - assert listClassNames.size() == listMethodNames.size() - && listClassNames.size() == listParamLengths.size(); - - List methodInvocations = new ArrayList<>(); - for (int i = 0; i < listClassNames.size(); ++i) { - String className = listClassNames.get(i); - String methodName = listMethodNames.get(i); - int paramLength = listParamLengths.get(i); - - // Get receiver, which is always the first argument of the invoke method - JCExpression receiver = methodInvocation.args.head; - // The remaining list contains the arguments - com.sun.tools.javac.util.List args = methodInvocation.args.tail; - - // Resolve the Symbol(s) for the current method - for (Symbol symbol : getMethodSymbolsfor(className, methodName, paramLength, env)) { - if (!processingEnv.getTypeUtils().isSubtype(receiver.type, symbol.owner.type)) { - continue; + Set returnLub = null; + Set paramsGlb = null; + + // Iterate over all possible constructors: lub return types and glb parameter types + for (JCNewClass resolvedTree : possibleConstructors) { + debugReflection("Resolved constructor invocation: " + resolvedTree); + if (!checkNewClassArguments(resolvedTree)) { + debugReflection( + "Spoofed tree's arguments did not match declaration" + resolvedTree); + // Calling methodFromUse on these sorts of trees will cause an assertion to fail in + // QualifierPolymorphism.PolyCollector.visitArray(...) + continue; + } + ParameterizedExecutableType resolvedResult = factory.constructorFromUse(resolvedTree); + AnnotatedExecutableType executableType = resolvedResult.executableType; + AnnotatedTypeMirror returnType = executableType.getReturnType(); + TypeMirror returnTM = returnType.getUnderlyingType(); + + // Lub return types + returnLub = lub(returnLub, returnTM, returnType.getAnnotations(), returnTM, factory); + + // Glb parameter types + for (AnnotatedTypeMirror mirror : executableType.getParameterTypes()) { + TypeMirror mirrorTM = mirror.getUnderlyingType(); + paramsGlb = glb(paramsGlb, mirrorTM, mirror.getAnnotations(), mirrorTM, factory); + } } - if ((symbol.flags() & Flags.PUBLIC) > 0) { - debugReflection("Resolved public method: " + symbol.owner + "." + symbol); - } else { - debugReflection("Resolved non-public method: " + symbol.owner + "." + symbol); + if (returnLub == null) { + // None of the spoofed tree's arguments matched the declared method + return origResult; + } + /* + * Clear all original (return, parameter type) annotations and set + * lub/glb annotations from resolved constructors. + */ + + // return value + origResult.executableType.getReturnType().clearAnnotations(); + origResult.executableType.getReturnType().addAnnotations(returnLub); + + // parameter types + if (paramsGlb != null) { + AnnotatedArrayType origArrayType = + (AnnotatedArrayType) origResult.executableType.getParameterTypes().get(0); + origArrayType.getComponentType().clearAnnotations(); + origArrayType.getComponentType().addAnnotations(paramsGlb); } - JCExpression method = TreeUtils.Select(make, receiver, symbol); - args = getCorrectedArgs(symbol, args); - // Build method invocation tree depending on the number of - // parameters - JCMethodInvocation syntTree = paramLength > 0 ? make.App(method, args) : make.App(method); - - // add method invocation tree to the list of possible method invocations - methodInvocations.add(syntTree); - } + debugReflection("Resolved annotations: " + origResult.executableType); + return origResult; } - return methodInvocations; - } - - private com.sun.tools.javac.util.List getCorrectedArgs( - Symbol symbol, com.sun.tools.javac.util.List args) { - if (symbol.getKind() == ElementKind.METHOD) { - MethodSymbol method = ((MethodSymbol) symbol); - // neg means too many arg, - // pos means to few args - int diff = method.getParameters().size() - args.size(); - if (diff > 0) { - // means too few args - int origArgSize = args.size(); - for (int i = 0; i < diff; i++) { - args = args.append(args.get(i % origArgSize)); + + /** + * Resolves a reflective method call and returns all possible corresponding method calls. + * + * @param tree the MethodInvocationTree AST node that is to be resolved (Method.invoke) + * @return a (potentially empty) list of all resolved MethodInvocationTrees + */ + private List resolveReflectiveMethod( + MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { + assert isReflectiveMethodInvocation(tree); + JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + TreeMaker make = TreeMaker.instance(context); + TreePath path = reflectionFactory.getPath(tree); + JavacScope scope = (JavacScope) trees.getScope(path); + Env env = scope.getEnv(); + + boolean unknown = isUnknownMethod(tree); + + AnnotationMirror estimate = getMethodVal(tree); + + if (estimate == null) { + debugReflection("MethodVal is unknown for: " + tree); + debugReflection("UnknownMethod annotation: " + unknown); + return Collections.emptyList(); } - } else if (diff < 0) { - // means too many args - com.sun.tools.javac.util.List tmp = com.sun.tools.javac.util.List.nil(); - for (int i = 0; i < method.getParameters().size(); i++) { - tmp = tmp.append(args.get(i)); + + debugReflection("MethodVal type system annotations: " + estimate); + + List listClassNames = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValClassNameElement, String.class); + List listMethodNames = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValMethodNameElement, String.class); + List listParamLengths = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValParamsElement, Integer.class); + assert listClassNames.size() == listMethodNames.size() + && listClassNames.size() == listParamLengths.size(); + + List methodInvocations = new ArrayList<>(); + for (int i = 0; i < listClassNames.size(); ++i) { + String className = listClassNames.get(i); + String methodName = listMethodNames.get(i); + int paramLength = listParamLengths.get(i); + + // Get receiver, which is always the first argument of the invoke method + JCExpression receiver = methodInvocation.args.head; + // The remaining list contains the arguments + com.sun.tools.javac.util.List args = methodInvocation.args.tail; + + // Resolve the Symbol(s) for the current method + for (Symbol symbol : getMethodSymbolsfor(className, methodName, paramLength, env)) { + if (!processingEnv.getTypeUtils().isSubtype(receiver.type, symbol.owner.type)) { + continue; + } + if ((symbol.flags() & Flags.PUBLIC) > 0) { + debugReflection("Resolved public method: " + symbol.owner + "." + symbol); + } else { + debugReflection("Resolved non-public method: " + symbol.owner + "." + symbol); + } + + JCExpression method = TreeUtils.Select(make, receiver, symbol); + args = getCorrectedArgs(symbol, args); + // Build method invocation tree depending on the number of + // parameters + JCMethodInvocation syntTree = + paramLength > 0 ? make.App(method, args) : make.App(method); + + // add method invocation tree to the list of possible method invocations + methodInvocations.add(syntTree); + } } - args = tmp; - } + return methodInvocations; } - return args; - } - - /** - * Resolves a reflective constructor call and returns all possible corresponding constructor - * calls. - * - * @param tree the MethodInvocationTree AST node that is to be resolved (Constructor.newInstance) - * @return a (potentially empty) list of all resolved MethodInvocationTrees - */ - private List resolveReflectiveConstructor( - MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { - assert isReflectiveMethodInvocation(tree); - JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - TreeMaker make = TreeMaker.instance(context); - TreePath path = reflectionFactory.getPath(tree); - JavacScope scope = (JavacScope) trees.getScope(path); - Env env = scope.getEnv(); - - AnnotationMirror estimate = getMethodVal(tree); - - if (estimate == null) { - debugReflection("MethodVal is unknown for: " + tree); - debugReflection("UnknownMethod annotation: " + isUnknownMethod(tree)); - return Collections.emptyList(); + + private com.sun.tools.javac.util.List getCorrectedArgs( + Symbol symbol, com.sun.tools.javac.util.List args) { + if (symbol.getKind() == ElementKind.METHOD) { + MethodSymbol method = ((MethodSymbol) symbol); + // neg means too many arg, + // pos means to few args + int diff = method.getParameters().size() - args.size(); + if (diff > 0) { + // means too few args + int origArgSize = args.size(); + for (int i = 0; i < diff; i++) { + args = args.append(args.get(i % origArgSize)); + } + } else if (diff < 0) { + // means too many args + com.sun.tools.javac.util.List tmp = + com.sun.tools.javac.util.List.nil(); + for (int i = 0; i < method.getParameters().size(); i++) { + tmp = tmp.append(args.get(i)); + } + args = tmp; + } + } + return args; } - debugReflection("MethodVal type system annotations: " + estimate); + /** + * Resolves a reflective constructor call and returns all possible corresponding constructor + * calls. + * + * @param tree the MethodInvocationTree AST node that is to be resolved + * (Constructor.newInstance) + * @return a (potentially empty) list of all resolved MethodInvocationTrees + */ + private List resolveReflectiveConstructor( + MethodInvocationTree tree, AnnotatedTypeFactory reflectionFactory) { + assert isReflectiveMethodInvocation(tree); + JCMethodInvocation methodInvocation = (JCMethodInvocation) tree; + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + TreeMaker make = TreeMaker.instance(context); + TreePath path = reflectionFactory.getPath(tree); + JavacScope scope = (JavacScope) trees.getScope(path); + Env env = scope.getEnv(); + + AnnotationMirror estimate = getMethodVal(tree); + + if (estimate == null) { + debugReflection("MethodVal is unknown for: " + tree); + debugReflection("UnknownMethod annotation: " + isUnknownMethod(tree)); + return Collections.emptyList(); + } - List listClassNames = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValClassNameElement, String.class); - List listParamLengths = - AnnotationUtils.getElementValueArray( - estimate, reflectionFactory.methodValParamsElement, Integer.class); - assert listClassNames.size() == listParamLengths.size(); + debugReflection("MethodVal type system annotations: " + estimate); - List constructorInvocations = new ArrayList<>(); - for (int i = 0; i < listClassNames.size(); ++i) { - String className = listClassNames.get(i); - int paramLength = listParamLengths.get(i); + List listClassNames = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValClassNameElement, String.class); + List listParamLengths = + AnnotationUtils.getElementValueArray( + estimate, reflectionFactory.methodValParamsElement, Integer.class); + assert listClassNames.size() == listParamLengths.size(); - // Resolve the Symbol for the current constructor - for (Symbol symbol : getConstructorSymbolsfor(className, paramLength, env)) { - debugReflection("Resolved constructor: " + symbol.owner + "." + symbol); + List constructorInvocations = new ArrayList<>(); + for (int i = 0; i < listClassNames.size(); ++i) { + String className = listClassNames.get(i); + int paramLength = listParamLengths.get(i); - JCNewClass syntTree = (JCNewClass) make.Create(symbol, methodInvocation.args); + // Resolve the Symbol for the current constructor + for (Symbol symbol : getConstructorSymbolsfor(className, paramLength, env)) { + debugReflection("Resolved constructor: " + symbol.owner + "." + symbol); - // add constructor invocation tree to the list of possible constructor invocations - constructorInvocations.add(syntTree); - } - } - return constructorInvocations; - } - - private AnnotationMirror getMethodVal(MethodInvocationTree tree) { - return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), MethodVal.class); - } - - /** Returns true if the receiver's type is @UnknownMethod. */ - private boolean isUnknownMethod(MethodInvocationTree tree) { - return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), UnknownMethod.class) - != null; - } - - /** - * Get set of MethodSymbols based on class name, method name, and parameter length. - * - * @param className the class that contains the method - * @param methodName the method's name - * @param paramLength the number of parameters - * @param env the environment - * @return the (potentially empty) set of corresponding method Symbol(s) - */ - private List getMethodSymbolsfor( - String className, String methodName, int paramLength, Env env) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Resolve resolve = Resolve.instance(context); - Names names = Names.instance(context); - - Symbol sym = getSymbol(className, env, names, resolve); - if (!sym.exists()) { - debugReflection("Unable to resolve class: " + className); - return Collections.emptyList(); - } + JCNewClass syntTree = (JCNewClass) make.Create(symbol, methodInvocation.args); - // The common case is probably that `result` is a singleton at method exit. - List result = new ArrayList<>(); - ClassSymbol classSym = (ClassSymbol) sym; - while (classSym != null) { - for (Symbol s : getEnclosedElements(classSym)) { - // check all member methods - if (s.getKind() == ElementKind.METHOD) { - // Check for method name and number of arguments - if (names.fromString(methodName) == s.name - && ((MethodSymbol) s).getParameters().size() == paramLength) { - result.add(s); - } + // add constructor invocation tree to the list of possible constructor invocations + constructorInvocations.add(syntTree); + } } - } - if (!result.isEmpty()) { - break; - } - Type t = classSym.getSuperclass(); - if (!t.hasTag(TypeTag.CLASS) || t.isErroneous()) { - break; - } - classSym = (ClassSymbol) t.tsym; + return constructorInvocations; } - if (result.isEmpty()) { - debugReflection("Unable to resolve method: " + className + "@" + methodName); + + private AnnotationMirror getMethodVal(MethodInvocationTree tree) { + return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), MethodVal.class); } - return result; - } - - /** - * Get set of Symbols for constructors based on class name and parameter length. - * - * @return the (potentially empty) set of corresponding constructor Symbol(s) - */ - private List getConstructorSymbolsfor( - String className, int paramLength, Env env) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Resolve resolve = Resolve.instance(context); - Names names = Names.instance(context); - - Symbol symClass = getSymbol(className, env, names, resolve); - if (!symClass.exists()) { - debugReflection("Unable to resolve class: " + className); - return Collections.emptyList(); + + /** Returns true if the receiver's type is @UnknownMethod. */ + private boolean isUnknownMethod(MethodInvocationTree tree) { + return provider.getAnnotationMirror(TreeUtils.getReceiverTree(tree), UnknownMethod.class) + != null; } - // TODO: Should this be used instead of the below?? - ElementFilter.constructorsIn(getEnclosedElements(symClass)); - - // The common case is probably that there is one constructor of the given parameter length. - List result = new ArrayList<>(2); - for (Symbol s : getEnclosedElements(symClass)) { - // Check all constructors - if (s.getKind() == ElementKind.CONSTRUCTOR) { - // Check for number of parameters - if (((MethodSymbol) s).getParameters().size() == paramLength) { - result.add(s); + /** + * Get set of MethodSymbols based on class name, method name, and parameter length. + * + * @param className the class that contains the method + * @param methodName the method's name + * @param paramLength the number of parameters + * @param env the environment + * @return the (potentially empty) set of corresponding method Symbol(s) + */ + private List getMethodSymbolsfor( + String className, String methodName, int paramLength, Env env) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Resolve resolve = Resolve.instance(context); + Names names = Names.instance(context); + + Symbol sym = getSymbol(className, env, names, resolve); + if (!sym.exists()) { + debugReflection("Unable to resolve class: " + className); + return Collections.emptyList(); + } + + // The common case is probably that `result` is a singleton at method exit. + List result = new ArrayList<>(); + ClassSymbol classSym = (ClassSymbol) sym; + while (classSym != null) { + for (Symbol s : getEnclosedElements(classSym)) { + // check all member methods + if (s.getKind() == ElementKind.METHOD) { + // Check for method name and number of arguments + if (names.fromString(methodName) == s.name + && ((MethodSymbol) s).getParameters().size() == paramLength) { + result.add(s); + } + } + } + if (!result.isEmpty()) { + break; + } + Type t = classSym.getSuperclass(); + if (!t.hasTag(TypeTag.CLASS) || t.isErroneous()) { + break; + } + classSym = (ClassSymbol) t.tsym; } - } + if (result.isEmpty()) { + debugReflection("Unable to resolve method: " + className + "@" + methodName); + } + return result; } - if (result.isEmpty()) { - debugReflection("Unable to resolve constructor!"); + + /** + * Get set of Symbols for constructors based on class name and parameter length. + * + * @return the (potentially empty) set of corresponding constructor Symbol(s) + */ + private List getConstructorSymbolsfor( + String className, int paramLength, Env env) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Resolve resolve = Resolve.instance(context); + Names names = Names.instance(context); + + Symbol symClass = getSymbol(className, env, names, resolve); + if (!symClass.exists()) { + debugReflection("Unable to resolve class: " + className); + return Collections.emptyList(); + } + + // TODO: Should this be used instead of the below?? + ElementFilter.constructorsIn(getEnclosedElements(symClass)); + + // The common case is probably that there is one constructor of the given parameter length. + List result = new ArrayList<>(2); + for (Symbol s : getEnclosedElements(symClass)) { + // Check all constructors + if (s.getKind() == ElementKind.CONSTRUCTOR) { + // Check for number of parameters + if (((MethodSymbol) s).getParameters().size() == paramLength) { + result.add(s); + } + } + } + if (result.isEmpty()) { + debugReflection("Unable to resolve constructor!"); + } + return result; } - return result; - } - - private Symbol getSymbol(String className, Env env, Names names, Resolve resolve) { - Method loadClass; - try { - loadClass = - Resolve.class.getDeclaredMethod( - "loadClass", Env.class, Name.class, RecoveryLoadClass.class); - loadClass.setAccessible(true); - } catch (SecurityException | NoSuchMethodException | IllegalArgumentException e) { - // A problem with javac is serious and must be reported. - throw new BugInCF("Error in obtaining reflective method.", e); + + private Symbol getSymbol(String className, Env env, Names names, Resolve resolve) { + Method loadClass; + try { + loadClass = + Resolve.class.getDeclaredMethod( + "loadClass", Env.class, Name.class, RecoveryLoadClass.class); + loadClass.setAccessible(true); + } catch (SecurityException | NoSuchMethodException | IllegalArgumentException e) { + // A problem with javac is serious and must be reported. + throw new BugInCF("Error in obtaining reflective method.", e); + } + try { + RecoveryLoadClass noRecovery = (e, n) -> null; + return (Symbol) loadClass.invoke(resolve, env, names.fromString(className), noRecovery); + } catch (SecurityException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { + // A problem with javac is serious and must be reported. + throw new BugInCF("Error in invoking reflective method.", e); + } } - try { - RecoveryLoadClass noRecovery = (e, n) -> null; - return (Symbol) loadClass.invoke(resolve, env, names.fromString(className), noRecovery); - } catch (SecurityException - | IllegalAccessException - | IllegalArgumentException - | InvocationTargetException e) { - // A problem with javac is serious and must be reported. - throw new BugInCF("Error in invoking reflective method.", e); + + /** + * Determine the enclosed elements for an element. This wrapper is useful to avoid a signature + * change in the called method. + * + * @param sym the element + * @return the enclosed elements + */ + @SuppressWarnings("ASTHelpersSuggestions") // Use local helper. + private static List getEnclosedElements(Symbol sym) { + return sym.getEnclosedElements(); } - } - - /** - * Determine the enclosed elements for an element. This wrapper is useful to avoid a signature - * change in the called method. - * - * @param sym the element - * @return the enclosed elements - */ - @SuppressWarnings("ASTHelpersSuggestions") // Use local helper. - private static List getEnclosedElements(Symbol sym) { - return sym.getEnclosedElements(); - } - - /** - * Build lub of the two types (represented by sets {@code set1} and {@code set2}) using the - * provided AnnotatedTypeFactory. - * - *

          If {@code set1} is {@code null} or empty, {@code set2} is returned. - * - * @param set1 the first type - * @param tm1 the type that is annotated by qualifier1 - * @param set2 the second type - * @param tm2 the type that is annotated by qualifier2 - * @param atypeFactory the type factory - * @return the lub of the two types - */ - private Set lub( - @Nullable Set set1, - TypeMirror tm1, - Set set2, - TypeMirror tm2, - AnnotatedTypeFactory atypeFactory) { - if (set1 == null || set1.isEmpty()) { - return set2; - } else { - return atypeFactory.getQualifierHierarchy().leastUpperBoundsShallow(set1, tm1, set2, tm2); + + /** + * Build lub of the two types (represented by sets {@code set1} and {@code set2}) using the + * provided AnnotatedTypeFactory. + * + *

          If {@code set1} is {@code null} or empty, {@code set2} is returned. + * + * @param set1 the first type + * @param tm1 the type that is annotated by qualifier1 + * @param set2 the second type + * @param tm2 the type that is annotated by qualifier2 + * @param atypeFactory the type factory + * @return the lub of the two types + */ + private Set lub( + @Nullable Set set1, + TypeMirror tm1, + Set set2, + TypeMirror tm2, + AnnotatedTypeFactory atypeFactory) { + if (set1 == null || set1.isEmpty()) { + return set2; + } else { + return atypeFactory + .getQualifierHierarchy() + .leastUpperBoundsShallow(set1, tm1, set2, tm2); + } } - } - - /** - * Build glb of the two types (represented by sets {@code set1} and {@code set2}) using the - * provided AnnotatedTypeFactory. - * - *

          If {@code set1} is {@code null} or empty, {@code set2} is returned. - * - * @param set1 the first type - * @param tm1 the type that is annotated by qualifier1 - * @param set2 the second type - * @param tm2 the type that is annotated by qualifier2 - * @param atypeFactory the type factory - * @return the glb of the two types - */ - private Set glb( - @Nullable Set set1, - TypeMirror tm1, - Set set2, - TypeMirror tm2, - AnnotatedTypeFactory atypeFactory) { - if (set1 == null || set1.isEmpty()) { - return set2; - } else { - return atypeFactory.getQualifierHierarchy().greatestLowerBoundsShallow(set1, tm1, set2, tm2); + + /** + * Build glb of the two types (represented by sets {@code set1} and {@code set2}) using the + * provided AnnotatedTypeFactory. + * + *

          If {@code set1} is {@code null} or empty, {@code set2} is returned. + * + * @param set1 the first type + * @param tm1 the type that is annotated by qualifier1 + * @param set2 the second type + * @param tm2 the type that is annotated by qualifier2 + * @param atypeFactory the type factory + * @return the glb of the two types + */ + private Set glb( + @Nullable Set set1, + TypeMirror tm1, + Set set2, + TypeMirror tm2, + AnnotatedTypeFactory atypeFactory) { + if (set1 == null || set1.isEmpty()) { + return set2; + } else { + return atypeFactory + .getQualifierHierarchy() + .greatestLowerBoundsShallow(set1, tm1, set2, tm2); + } } - } - - /** - * Reports debug information about the reflection resolution iff the corresponding debug flag is - * set. - * - * @param msg the debug message - */ - private void debugReflection(String msg) { - if (debug) { - checker.message(javax.tools.Diagnostic.Kind.NOTE, MSG_PREFEX_REFLECTION + msg); + + /** + * Reports debug information about the reflection resolution iff the corresponding debug flag is + * set. + * + * @param msg the debug message + */ + private void debugReflection(String msg) { + if (debug) { + checker.message(javax.tools.Diagnostic.Kind.NOTE, MSG_PREFEX_REFLECTION + msg); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java index e544ced8ab3..886d52fb016 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java @@ -2,18 +2,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -39,406 +28,431 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; -/** AnnotatedTypeFactory for the MethodVal Checker. */ -public class MethodValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - - /** {@link UnknownMethod} annotation mirror. */ - private final AnnotationMirror UNKNOWN_METHOD = - AnnotationBuilder.fromClass(elements, UnknownMethod.class); - - /** An array length that represents that the length is unknown. */ - private static final int UNKNOWN_PARAM_LENGTH = -1; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; - /** A list containing just {@link #UNKNOWN_PARAM_LENGTH}. */ - private static final List UNKNOWN_PARAM_LENGTH_LIST = - Collections.singletonList(UNKNOWN_PARAM_LENGTH); +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; - /** A list containing just 0. */ - private static List ZERO_LIST = Collections.singletonList(0); +/** AnnotatedTypeFactory for the MethodVal Checker. */ +public class MethodValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** A list containing just 1. */ - private static List ONE_LIST = Collections.singletonList(1); + /** {@link UnknownMethod} annotation mirror. */ + private final AnnotationMirror UNKNOWN_METHOD = + AnnotationBuilder.fromClass(elements, UnknownMethod.class); - /** An empty String list. */ - private static List EMPTY_STRING_LIST = Collections.emptyList(); + /** An array length that represents that the length is unknown. */ + private static final int UNKNOWN_PARAM_LENGTH = -1; - /** The ArrayLen.value argument/element. */ - public final ExecutableElement arrayLenValueElement = - TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); + /** A list containing just {@link #UNKNOWN_PARAM_LENGTH}. */ + private static final List UNKNOWN_PARAM_LENGTH_LIST = + Collections.singletonList(UNKNOWN_PARAM_LENGTH); - /** The ClassBound.value argument/element. */ - public final ExecutableElement classBoundValueElement = - TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); + /** A list containing just 0. */ + private static List ZERO_LIST = Collections.singletonList(0); - /** The ClassVal.value argument/element. */ - public final ExecutableElement classValValueElement = - TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); + /** A list containing just 1. */ + private static List ONE_LIST = Collections.singletonList(1); - /** The StringVal.value argument/element. */ - public final ExecutableElement stringValValueElement = - TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); + /** An empty String list. */ + private static List EMPTY_STRING_LIST = Collections.emptyList(); - /** - * Create a new MethodValAnnotatedTypeFactory. - * - * @param checker the type-checker associated with this factory - */ - public MethodValAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - if (this.getClass() == MethodValAnnotatedTypeFactory.class) { - this.postInit(); - } - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList(MethodVal.class, MethodValBottom.class, UnknownMethod.class)); - } - - @Override - protected void initializeReflectionResolution() { - boolean debug = "debug".equals(checker.getOption("resolveReflection")); - reflectionResolver = new DefaultReflectionResolver(checker, this, debug); - } - - /** - * Returns the methods that a {@code @MethodVal} represents. - * - * @param methodValAnno a {@code @MethodVal} annotation - * @return the methods that the given {@code @MethodVal} represents - */ - List getListOfMethodSignatures(AnnotationMirror methodValAnno) { - List methodNames = - AnnotationUtils.getElementValueArray( - methodValAnno, methodValMethodNameElement, String.class); - List classNames = - AnnotationUtils.getElementValueArray( - methodValAnno, methodValClassNameElement, String.class); - List params = - AnnotationUtils.getElementValueArray(methodValAnno, methodValParamsElement, Integer.class); - List list = new ArrayList<>(methodNames.size()); - for (int i = 0; i < methodNames.size(); i++) { - list.add(new MethodSignature(classNames.get(i), methodNames.get(i), params.get(i))); - } - return list; - } - - /** - * Creates a {@code @MethodVal} annotation. - * - * @param sigs the method signatures that the result should represent - * @return a {@code @MethodVal} annotation that represents {@code sigs} - */ - private AnnotationMirror createMethodVal(Collection sigs) { - int size = sigs.size(); - List classNames = new ArrayList<>(size); - List methodNames = new ArrayList<>(size); - List params = new ArrayList<>(size); - for (MethodSignature sig : sigs) { - classNames.add(sig.className); - methodNames.add(sig.methodName); - params.add(sig.params); - } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MethodVal.class); - builder.setValue("className", classNames); - builder.setValue("methodName", methodNames); - builder.setValue("params", params); - return builder.build(); - } - - /** - * Returns a list of class names for the given tree using the Class Val Checker. - * - * @param tree an ExpressionTree whose class names are requested - * @param mustBeExact whether @ClassBound may be read to produce the result; if false, - * only @ClassVal may be read - * @return list of class names or the empty list if no class names were found - */ - private List getClassNamesFromClassValChecker(ExpressionTree tree, boolean mustBeExact) { - ClassValAnnotatedTypeFactory classValATF = getTypeFactoryOfSubchecker(ClassValChecker.class); - AnnotatedTypeMirror classAnno = classValATF.getAnnotatedType(tree); - - AnnotationMirror classValAnno = classAnno.getAnnotation(ClassVal.class); - if (classValAnno != null) { - return AnnotationUtils.getElementValueArray(classValAnno, classValValueElement, String.class); - } else if (mustBeExact) { - return Collections.emptyList(); - } + /** The ArrayLen.value argument/element. */ + public final ExecutableElement arrayLenValueElement = + TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); - AnnotationMirror classBoundAnno = classAnno.getAnnotation(ClassBound.class); - if (classBoundAnno != null) { - return AnnotationUtils.getElementValueArray( - classBoundAnno, classBoundValueElement, String.class); - } else { - return Collections.emptyList(); - } - } - - /** - * Returns the string values for the argument passed. The String Values are estimated using the - * Value Checker. - * - * @param arg an ExpressionTree whose string values are sought - * @return string values of arg or the empty list if no values were found - */ - private List getMethodNamesFromStringArg(ExpressionTree arg) { - ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(arg); - AnnotationMirror annotation = valueAnno.getAnnotation(StringVal.class); - if (annotation != null) { - return AnnotationUtils.getElementValueArray(annotation, stringValValueElement, String.class); - } else { - return EMPTY_STRING_LIST; - } - } + /** The ClassBound.value argument/element. */ + public final ExecutableElement classBoundValueElement = + TreeUtils.getMethod(ClassBound.class, "value", 0, processingEnv); - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new MethodValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } + /** The ClassVal.value argument/element. */ + public final ExecutableElement classValValueElement = + TreeUtils.getMethod(ClassVal.class, "value", 0, processingEnv); - /** MethodValQualifierHierarchy. */ - protected class MethodValQualifierHierarchy extends ElementQualifierHierarchy { + /** The StringVal.value argument/element. */ + public final ExecutableElement stringValValueElement = + TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); /** - * Creates a MethodValQualifierHierarchy from the given classes. + * Create a new MethodValAnnotatedTypeFactory. * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils + * @param checker the type-checker associated with this factory */ - protected MethodValQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, MethodValAnnotatedTypeFactory.this); + public MethodValAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + if (this.getClass() == MethodValAnnotatedTypeFactory.class) { + this.postInit(); + } } - /* - * Determines the least upper bound of a1 and a2. If both are MethodVal - * annotations, then the least upper bound is the result of - * concatenating all value lists of a1 and a2. - */ @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } else if (AnnotationUtils.areSameByName(a1, a2)) { - List a1Sigs = getListOfMethodSignatures(a1); - List a2Sigs = getListOfMethodSignatures(a2); - List lubSigs = CollectionsPlume.listUnion(a1Sigs, a2Sigs); - AnnotationMirror result = createMethodVal(lubSigs); - return result; - } - return null; + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList(MethodVal.class, MethodValBottom.class, UnknownMethod.class)); } @Override - public @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - return null; - } else if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } else if (AnnotationUtils.areSameByName(a1, a2)) { - List a1Sigs = getListOfMethodSignatures(a1); - List a2Sigs = getListOfMethodSignatures(a2); - List lubSigs = CollectionsPlume.listIntersection(a1Sigs, a2Sigs); - AnnotationMirror result = createMethodVal(lubSigs); - return result; - } - return null; + protected void initializeReflectionResolution() { + boolean debug = "debug".equals(checker.getOption("resolveReflection")); + reflectionResolver = new DefaultReflectionResolver(checker, this, debug); } - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (AnnotationUtils.areSame(subAnno, superAnno) - || areSameByClass(superAnno, UnknownMethod.class) - || areSameByClass(subAnno, MethodValBottom.class)) { - return true; - } - if (areSameByClass(subAnno, UnknownMethod.class) - || areSameByClass(superAnno, MethodValBottom.class)) { - return false; - } - assert areSameByClass(subAnno, MethodVal.class) && areSameByClass(superAnno, MethodVal.class) - : "Unexpected annotation in MethodVal"; - List subSignatures = getListOfMethodSignatures(subAnno); - List superSignatures = getListOfMethodSignatures(superAnno); - return superSignatures.containsAll(subSignatures); - } - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new MethodValTreeAnnotator(this), super.createTreeAnnotator()); - } - - /** TreeAnnotator with the visitMethodInvocation method overridden. */ - protected class MethodValTreeAnnotator extends TreeAnnotator { - - protected MethodValTreeAnnotator(MethodValAnnotatedTypeFactory factory) { - super(factory); - } - - /* - * Special handling of getMethod and getDeclaredMethod calls. Attempts - * to get the annotation on the Class object receiver to get the - * className, the annotation on the String argument to get the - * methodName, and the parameters argument to get the param, and then - * builds a new MethodVal annotation from these + /** + * Returns the methods that a {@code @MethodVal} represents. + * + * @param methodValAnno a {@code @MethodVal} annotation + * @return the methods that the given {@code @MethodVal} represents */ - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - - List methodNames; - List params; - List classNames; - if (isGetConstructorMethodInvocation(tree)) { - // method name for constructors is always - methodNames = ReflectionResolver.INIT_LIST; - params = getConstructorParamsLen(tree.getArguments()); - classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), true); - - } else if (isGetMethodMethodInvocation(tree)) { - ExpressionTree methodNameArg = tree.getArguments().get(0); - methodNames = getMethodNamesFromStringArg(methodNameArg); - params = getMethodParamsLen(tree.getArguments()); - classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), false); - } else { - // Not a covered method invocation - return null; - } - - // Create MethodVal - if (methodNames.isEmpty() || classNames.isEmpty()) { - // No method name or classname is found, it could be any - // class or method, so, return @UnknownMethod. - type.replaceAnnotation(UNKNOWN_METHOD); - return null; - } - - Set methodSigs = - new HashSet<>( - CollectionsPlume.mapCapacity(methodNames.size() * classNames.size() * params.size())); - // The possible method signatures are the Cartesian product of all - // found class, method, and parameter lengths. - for (String methodName : methodNames) { - for (String className : classNames) { - for (Integer param : params) { - methodSigs.add(new MethodSignature(className, methodName, param)); - } + List getListOfMethodSignatures(AnnotationMirror methodValAnno) { + List methodNames = + AnnotationUtils.getElementValueArray( + methodValAnno, methodValMethodNameElement, String.class); + List classNames = + AnnotationUtils.getElementValueArray( + methodValAnno, methodValClassNameElement, String.class); + List params = + AnnotationUtils.getElementValueArray( + methodValAnno, methodValParamsElement, Integer.class); + List list = new ArrayList<>(methodNames.size()); + for (int i = 0; i < methodNames.size(); i++) { + list.add(new MethodSignature(classNames.get(i), methodNames.get(i), params.get(i))); } - } - - AnnotationMirror newQual = createMethodVal(methodSigs); - type.replaceAnnotation(newQual); - return null; + return list; } /** - * Returns true if the method being invoked is annotated with @GetConstructor. An example of - * such a method is {@link Class#getConstructor}. + * Creates a {@code @MethodVal} annotation. * - * @param tree a method invocation - * @return true if the method being invoked is annotated with @GetConstructor + * @param sigs the method signatures that the result should represent + * @return a {@code @MethodVal} annotation that represents {@code sigs} */ - private boolean isGetConstructorMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetConstructor.class) != null; + private AnnotationMirror createMethodVal(Collection sigs) { + int size = sigs.size(); + List classNames = new ArrayList<>(size); + List methodNames = new ArrayList<>(size); + List params = new ArrayList<>(size); + for (MethodSignature sig : sigs) { + classNames.add(sig.className); + methodNames.add(sig.methodName); + params.add(sig.params); + } + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MethodVal.class); + builder.setValue("className", classNames); + builder.setValue("methodName", methodNames); + builder.setValue("params", params); + return builder.build(); } /** - * Returns true if the method being invoked is annotated with @GetMethod. An example of such a - * method is {@link Class#getMethod}. + * Returns a list of class names for the given tree using the Class Val Checker. * - * @param tree a method invocation - * @return true if the method being invoked is annotated with @GetMethod + * @param tree an ExpressionTree whose class names are requested + * @param mustBeExact whether @ClassBound may be read to produce the result; if false, + * only @ClassVal may be read + * @return list of class names or the empty list if no class names were found */ - private boolean isGetMethodMethodInvocation(MethodInvocationTree tree) { - return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetMethod.class) != null; + private List getClassNamesFromClassValChecker( + ExpressionTree tree, boolean mustBeExact) { + ClassValAnnotatedTypeFactory classValATF = + getTypeFactoryOfSubchecker(ClassValChecker.class); + AnnotatedTypeMirror classAnno = classValATF.getAnnotatedType(tree); + + AnnotationMirror classValAnno = classAnno.getAnnotation(ClassVal.class); + if (classValAnno != null) { + return AnnotationUtils.getElementValueArray( + classValAnno, classValValueElement, String.class); + } else if (mustBeExact) { + return Collections.emptyList(); + } + + AnnotationMirror classBoundAnno = classAnno.getAnnotation(ClassBound.class); + if (classBoundAnno != null) { + return AnnotationUtils.getElementValueArray( + classBoundAnno, classBoundValueElement, String.class); + } else { + return Collections.emptyList(); + } } /** - * Returns a singleton list containing the number of parameters for a call to a method annotated - * with {@code @}{@link GetMethod}. + * Returns the string values for the argument passed. The String Values are estimated using the + * Value Checker. * - * @param args arguments to a call to a method such as {@code getMethod} - * @return the number of parameters + * @param arg an ExpressionTree whose string values are sought + * @return string values of arg or the empty list if no values were found */ - private List getMethodParamsLen(List args) { - assert !args.isEmpty() : "getMethod must have at least one parameter"; - - // Number of parameters in the created method object - int numParams = args.size() - 1; - if (numParams == 1) { - return getNumberOfParameterOneArg(args.get(1)); - } - return Collections.singletonList(numParams); + private List getMethodNamesFromStringArg(ExpressionTree arg) { + ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(arg); + AnnotationMirror annotation = valueAnno.getAnnotation(StringVal.class); + if (annotation != null) { + return AnnotationUtils.getElementValueArray( + annotation, stringValValueElement, String.class); + } else { + return EMPTY_STRING_LIST; + } } - /** - * Returns a singleton list containing the number of parameters for a call to a method annotated - * with {@code @}{@link GetConstructor}. - * - * @param args arguments to a call to a method such as {@code getConstructor} - * @return the number of parameters - */ - private List getConstructorParamsLen(List args) { - // Number of parameters in the created method object - int numParams = args.size(); - if (numParams == 1) { - return getNumberOfParameterOneArg(args.get(0)); - } - return Collections.singletonList(numParams); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new MethodValQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } - /** - * If getMethod(Object receiver, Object... params) or getConstructor(Object... params) have one - * argument for params, then the number of parameters in the underlying method or constructor - * must be: - * - *

            - *
          • 0: if the argument is null - *
          • x: if the argument is an array with @ArrayLen(x); note that x might be a set rather - * than a single value - *
          • UNKNOWN_PARAM_LENGTH: if the argument is an array with @UnknownVal - *
          • 1: otherwise - *
          - * - * @param argument the single argument in a call to {@code getMethod} or {@code getConstructor} - * @return a list, each of whose elementts is a possible the number of parameters; it is often, - * but not always, a singleton list - */ - private List getNumberOfParameterOneArg(ExpressionTree argument) { - AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(argument); - switch (atm.getKind()) { - case ARRAY: - ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class); - AnnotatedTypeMirror valueType = valueATF.getAnnotatedType(argument); - AnnotationMirror arrayLenAnno = valueType.getAnnotation(ArrayLen.class); - if (arrayLenAnno != null) { - return AnnotationUtils.getElementValueArray( - arrayLenAnno, arrayLenValueElement, Integer.class); - } else if (valueType.getAnnotation(BottomVal.class) != null) { - // happens in this case: (Class[]) null - return ZERO_LIST; - } - // the argument is an array with unknown array length - return UNKNOWN_PARAM_LENGTH_LIST; - case NULL: - // null is treated as the empty list of parameters, so size is 0. - return ZERO_LIST; - default: - // The argument is not an array or null, so it must be a class. - return ONE_LIST; - } + /** MethodValQualifierHierarchy. */ + protected class MethodValQualifierHierarchy extends ElementQualifierHierarchy { + + /** + * Creates a MethodValQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + protected MethodValQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, MethodValAnnotatedTypeFactory.this); + } + + /* + * Determines the least upper bound of a1 and a2. If both are MethodVal + * annotations, then the least upper bound is the result of + * concatenating all value lists of a1 and a2. + */ + @Override + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } else if (AnnotationUtils.areSameByName(a1, a2)) { + List a1Sigs = getListOfMethodSignatures(a1); + List a2Sigs = getListOfMethodSignatures(a2); + List lubSigs = CollectionsPlume.listUnion(a1Sigs, a2Sigs); + AnnotationMirror result = createMethodVal(lubSigs); + return result; + } + return null; + } + + @Override + public @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + return null; + } else if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } else if (AnnotationUtils.areSameByName(a1, a2)) { + List a1Sigs = getListOfMethodSignatures(a1); + List a2Sigs = getListOfMethodSignatures(a2); + List lubSigs = CollectionsPlume.listIntersection(a1Sigs, a2Sigs); + AnnotationMirror result = createMethodVal(lubSigs); + return result; + } + return null; + } + + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (AnnotationUtils.areSame(subAnno, superAnno) + || areSameByClass(superAnno, UnknownMethod.class) + || areSameByClass(subAnno, MethodValBottom.class)) { + return true; + } + if (areSameByClass(subAnno, UnknownMethod.class) + || areSameByClass(superAnno, MethodValBottom.class)) { + return false; + } + assert areSameByClass(subAnno, MethodVal.class) + && areSameByClass(superAnno, MethodVal.class) + : "Unexpected annotation in MethodVal"; + List subSignatures = getListOfMethodSignatures(subAnno); + List superSignatures = getListOfMethodSignatures(superAnno); + return superSignatures.containsAll(subSignatures); + } + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator(new MethodValTreeAnnotator(this), super.createTreeAnnotator()); + } + + /** TreeAnnotator with the visitMethodInvocation method overridden. */ + protected class MethodValTreeAnnotator extends TreeAnnotator { + + protected MethodValTreeAnnotator(MethodValAnnotatedTypeFactory factory) { + super(factory); + } + + /* + * Special handling of getMethod and getDeclaredMethod calls. Attempts + * to get the annotation on the Class object receiver to get the + * className, the annotation on the String argument to get the + * methodName, and the parameters argument to get the param, and then + * builds a new MethodVal annotation from these + */ + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + + List methodNames; + List params; + List classNames; + if (isGetConstructorMethodInvocation(tree)) { + // method name for constructors is always + methodNames = ReflectionResolver.INIT_LIST; + params = getConstructorParamsLen(tree.getArguments()); + classNames = + getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), true); + + } else if (isGetMethodMethodInvocation(tree)) { + ExpressionTree methodNameArg = tree.getArguments().get(0); + methodNames = getMethodNamesFromStringArg(methodNameArg); + params = getMethodParamsLen(tree.getArguments()); + classNames = + getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree), false); + } else { + // Not a covered method invocation + return null; + } + + // Create MethodVal + if (methodNames.isEmpty() || classNames.isEmpty()) { + // No method name or classname is found, it could be any + // class or method, so, return @UnknownMethod. + type.replaceAnnotation(UNKNOWN_METHOD); + return null; + } + + Set methodSigs = + new HashSet<>( + CollectionsPlume.mapCapacity( + methodNames.size() * classNames.size() * params.size())); + // The possible method signatures are the Cartesian product of all + // found class, method, and parameter lengths. + for (String methodName : methodNames) { + for (String className : classNames) { + for (Integer param : params) { + methodSigs.add(new MethodSignature(className, methodName, param)); + } + } + } + + AnnotationMirror newQual = createMethodVal(methodSigs); + type.replaceAnnotation(newQual); + return null; + } + + /** + * Returns true if the method being invoked is annotated with @GetConstructor. An example of + * such a method is {@link Class#getConstructor}. + * + * @param tree a method invocation + * @return true if the method being invoked is annotated with @GetConstructor + */ + private boolean isGetConstructorMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetConstructor.class) != null; + } + + /** + * Returns true if the method being invoked is annotated with @GetMethod. An example of such + * a method is {@link Class#getMethod}. + * + * @param tree a method invocation + * @return true if the method being invoked is annotated with @GetMethod + */ + private boolean isGetMethodMethodInvocation(MethodInvocationTree tree) { + return getDeclAnnotation(TreeUtils.elementFromUse(tree), GetMethod.class) != null; + } + + /** + * Returns a singleton list containing the number of parameters for a call to a method + * annotated with {@code @}{@link GetMethod}. + * + * @param args arguments to a call to a method such as {@code getMethod} + * @return the number of parameters + */ + private List getMethodParamsLen(List args) { + assert !args.isEmpty() : "getMethod must have at least one parameter"; + + // Number of parameters in the created method object + int numParams = args.size() - 1; + if (numParams == 1) { + return getNumberOfParameterOneArg(args.get(1)); + } + return Collections.singletonList(numParams); + } + + /** + * Returns a singleton list containing the number of parameters for a call to a method + * annotated with {@code @}{@link GetConstructor}. + * + * @param args arguments to a call to a method such as {@code getConstructor} + * @return the number of parameters + */ + private List getConstructorParamsLen(List args) { + // Number of parameters in the created method object + int numParams = args.size(); + if (numParams == 1) { + return getNumberOfParameterOneArg(args.get(0)); + } + return Collections.singletonList(numParams); + } + + /** + * If getMethod(Object receiver, Object... params) or getConstructor(Object... params) have + * one argument for params, then the number of parameters in the underlying method or + * constructor must be: + * + *
            + *
          • 0: if the argument is null + *
          • x: if the argument is an array with @ArrayLen(x); note that x might be a set rather + * than a single value + *
          • UNKNOWN_PARAM_LENGTH: if the argument is an array with @UnknownVal + *
          • 1: otherwise + *
          + * + * @param argument the single argument in a call to {@code getMethod} or {@code + * getConstructor} + * @return a list, each of whose elementts is a possible the number of parameters; it is + * often, but not always, a singleton list + */ + private List getNumberOfParameterOneArg(ExpressionTree argument) { + AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(argument); + switch (atm.getKind()) { + case ARRAY: + ValueAnnotatedTypeFactory valueATF = + getTypeFactoryOfSubchecker(ValueChecker.class); + AnnotatedTypeMirror valueType = valueATF.getAnnotatedType(argument); + AnnotationMirror arrayLenAnno = valueType.getAnnotation(ArrayLen.class); + if (arrayLenAnno != null) { + return AnnotationUtils.getElementValueArray( + arrayLenAnno, arrayLenValueElement, Integer.class); + } else if (valueType.getAnnotation(BottomVal.class) != null) { + // happens in this case: (Class[]) null + return ZERO_LIST; + } + // the argument is an array with unknown array length + return UNKNOWN_PARAM_LENGTH_LIST; + case NULL: + // null is treated as the empty list of parameters, so size is 0. + return ZERO_LIST; + default: + // The argument is not an array or null, so it must be a class. + return ONE_LIST; + } + } } - } } /** @@ -446,61 +460,61 @@ private List getNumberOfParameterOneArg(ExpressionTree argument) { * name, method name, number of parameters). */ class MethodSignature { - String className; - String methodName; - int params; - - public MethodSignature(String className, String methodName, int params) { - this.className = className; - this.methodName = methodName; - this.params = params; - } - - @Override - public int hashCode() { - return Objects.hash(className, methodName, params); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; + String className; + String methodName; + int params; + + public MethodSignature(String className, String methodName, int params) { + this.className = className; + this.methodName = methodName; + this.params = params; } - MethodSignature other = (MethodSignature) obj; - if (className == null) { - if (other.className != null) { - return false; - } - } else if (!className.equals(other.className)) { - return false; + + @Override + public int hashCode() { + return Objects.hash(className, methodName, params); } - if (methodName == null) { - if (other.methodName != null) { - return false; - } - } else if (!methodName.equals(other.methodName)) { - return false; + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MethodSignature other = (MethodSignature) obj; + if (className == null) { + if (other.className != null) { + return false; + } + } else if (!className.equals(other.className)) { + return false; + } + if (methodName == null) { + if (other.methodName != null) { + return false; + } + } else if (!methodName.equals(other.methodName)) { + return false; + } + if (params != other.params) { + return false; + } + return true; } - if (params != other.params) { - return false; + + @Override + public String toString() { + return "MethodSignature [className=" + + className + + ", methodName=" + + methodName + + ", params=" + + params + + "]"; } - return true; - } - - @Override - public String toString() { - return "MethodSignature [className=" - + className - + ", methodName=" - + methodName - + ", params=" - + params - + "]"; - } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java index 27e8054c880..3a3c0d1173e 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValChecker.java @@ -1,32 +1,33 @@ package org.checkerframework.common.reflection; -import java.util.LinkedHashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; import org.plumelib.util.CollectionsPlume; +import java.util.LinkedHashSet; +import java.util.Set; + /** * The MethodVal Checker provides a sound estimate of the signature of Method objects. * * @checker_framework.manual #methodval-and-classval-checkers MethodVal Checker */ public class MethodValChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new MethodValVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new MethodValVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - // Don't call super otherwise MethodVal will be added as a subChecker - // which creates a circular dependency. - // Use the same Set implementation as super. - Set> subCheckers = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); - subCheckers.add(ValueChecker.class); - subCheckers.add(ClassValChecker.class); - return subCheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Don't call super otherwise MethodVal will be added as a subChecker + // which creates a circular dependency. + // Use the same Set implementation as super. + Set> subCheckers = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(2)); + subCheckers.add(ValueChecker.class); + subCheckers.add(ClassValChecker.class); + return subCheckers; + } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java index 971a7285d21..772e170c610 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValVisitor.java @@ -1,8 +1,7 @@ package org.checkerframework.common.reflection; import com.sun.source.tree.Tree; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -11,73 +10,79 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.javacutil.AnnotationUtils; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + public class MethodValVisitor extends BaseTypeVisitor { - public MethodValVisitor(BaseTypeChecker checker) { - super(checker); - } + public MethodValVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected MethodValAnnotatedTypeFactory createTypeFactory() { - return new MethodValAnnotatedTypeFactory(checker); - } + @Override + protected MethodValAnnotatedTypeFactory createTypeFactory() { + return new MethodValAnnotatedTypeFactory(checker); + } - @Override - protected BaseTypeValidator createTypeValidator() { - return new MethodNameValidator(checker, this, atypeFactory); - } + @Override + protected BaseTypeValidator createTypeValidator() { + return new MethodNameValidator(checker, this, atypeFactory); + } } class MethodNameValidator extends BaseTypeValidator { - public MethodNameValidator( - BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } + public MethodNameValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - AnnotationMirror methodVal = type.getAnnotation(MethodVal.class); - if (methodVal != null) { - AnnotatedTypeFactory atypeFactory = checker.getTypeFactory(); - List classNames = - AnnotationUtils.getElementValueArray( - methodVal, atypeFactory.methodValClassNameElement, String.class); - List methodNames = - AnnotationUtils.getElementValueArray( - methodVal, atypeFactory.methodValMethodNameElement, String.class); - List params = - AnnotationUtils.getElementValueArray( - methodVal, atypeFactory.methodValParamsElement, Integer.class); - if (!(classNames.size() == methodNames.size() && classNames.size() == params.size())) { - checker.reportError(tree, "invalid.methodval", methodVal); - } + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + AnnotationMirror methodVal = type.getAnnotation(MethodVal.class); + if (methodVal != null) { + AnnotatedTypeFactory atypeFactory = checker.getTypeFactory(); + List classNames = + AnnotationUtils.getElementValueArray( + methodVal, atypeFactory.methodValClassNameElement, String.class); + List methodNames = + AnnotationUtils.getElementValueArray( + methodVal, atypeFactory.methodValMethodNameElement, String.class); + List params = + AnnotationUtils.getElementValueArray( + methodVal, atypeFactory.methodValParamsElement, Integer.class); + if (!(classNames.size() == methodNames.size() && classNames.size() == params.size())) { + checker.reportError(tree, "invalid.methodval", methodVal); + } - for (String methodName : methodNames) { - if (!legalMethodName(methodName)) { - checker.reportError(tree, "illegal.methodname", methodName, type); + for (String methodName : methodNames) { + if (!legalMethodName(methodName)) { + checker.reportError(tree, "illegal.methodname", methodName, type); + } + } } - } + return super.visitDeclared(type, tree); } - return super.visitDeclared(type, tree); - } - private boolean legalMethodName(String methodName) { - if (methodName.equals(ReflectionResolver.INIT)) { - return true; - } - if (methodName.length() < 1) { - return false; - } - char[] methodNameChars = methodName.toCharArray(); - if (!Character.isJavaIdentifierStart(methodNameChars[0])) { - return false; - } - for (int i = 1; i < methodNameChars.length; i++) { - if (!Character.isJavaIdentifierPart(methodNameChars[i])) { - return false; - } + private boolean legalMethodName(String methodName) { + if (methodName.equals(ReflectionResolver.INIT)) { + return true; + } + if (methodName.length() < 1) { + return false; + } + char[] methodNameChars = methodName.toCharArray(); + if (!Character.isJavaIdentifierStart(methodNameChars[0])) { + return false; + } + for (int i = 1; i < methodNameChars.length; i++) { + if (!Character.isJavaIdentifierPart(methodNameChars[i])) { + return false; + } + } + return true; } - return true; - } } diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java index cd333be6d05..7d9282da79a 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ReflectionResolver.java @@ -1,12 +1,14 @@ package org.checkerframework.common.reflection; import com.sun.source.tree.MethodInvocationTree; + +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; + import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; /** * Interface for reflection resolvers that handle reflective method calls such as {@link @@ -15,32 +17,32 @@ * @checker_framework.manual #reflection-resolution Reflection resolution */ public interface ReflectionResolver { - /** The "method name" of constructors. */ - public static final String INIT = ""; + /** The "method name" of constructors. */ + public static final String INIT = ""; - /** - * A list containing just the "method name" of constructors. Clients must not modify this list. - */ - public static final List INIT_LIST = Collections.singletonList(INIT); + /** + * A list containing just the "method name" of constructors. Clients must not modify this list. + */ + public static final List INIT_LIST = Collections.singletonList(INIT); - /** - * Returns true if the given tree represents a reflective method or constructor call. - * - * @return {@code true} iff tree is a reflective method invocation, {@code false} otherwise - */ - public boolean isReflectiveMethodInvocation(MethodInvocationTree tree); + /** + * Returns true if the given tree represents a reflective method or constructor call. + * + * @return {@code true} iff tree is a reflective method invocation, {@code false} otherwise + */ + public boolean isReflectiveMethodInvocation(MethodInvocationTree tree); - /** - * Resolve reflection and return the result of {@code factory.methodFromUse} for the actual, - * resolved method or constructor call. If the reflective method cannot be resolved the original - * result ({@code origResult}) is returned. - * - * @param factory the currently used AnnotatedTypeFactory - * @param tree the reflective invocation tree (m.invoke or c.newInstance) - * @param origResult the original result for the unresolved, reflective method call - */ - public ParameterizedExecutableType resolveReflectiveCall( - AnnotatedTypeFactory factory, - MethodInvocationTree tree, - ParameterizedExecutableType origResult); + /** + * Resolve reflection and return the result of {@code factory.methodFromUse} for the actual, + * resolved method or constructor call. If the reflective method cannot be resolved the original + * result ({@code origResult}) is returned. + * + * @param factory the currently used AnnotatedTypeFactory + * @param tree the reflective invocation tree (m.invoke or c.newInstance) + * @param origResult the original result for the unresolved, reflective method call + */ + public ParameterizedExecutableType resolveReflectiveCall( + AnnotatedTypeFactory factory, + MethodInvocationTree tree, + ParameterizedExecutableType origResult); } diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java index f00f650bf39..16cd54eddea 100644 --- a/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java +++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java @@ -1,10 +1,5 @@ package org.checkerframework.common.returnsreceiver; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -12,6 +7,12 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * A utility class to support fluent API generators so the checker can add {@code @This} annotations * on method return types when a generator has been used. To check whether a method is created by @@ -20,115 +21,120 @@ */ public class FluentAPIGenerator { - /** - * Check if a method was generated by a known fluent API generator and returns its receiver. - * - * @param t the annotated type of the method signature - * @return {@code true} if the method was created by a generator and returns {@code this} - */ - public static boolean check(AnnotatedExecutableType t) { - for (FluentAPIGenerators fluentAPIGenerator : FluentAPIGenerators.values()) { - if (fluentAPIGenerator.returnsThis(t)) { - return true; - } + /** + * Check if a method was generated by a known fluent API generator and returns its receiver. + * + * @param t the annotated type of the method signature + * @return {@code true} if the method was created by a generator and returns {@code this} + */ + public static boolean check(AnnotatedExecutableType t) { + for (FluentAPIGenerators fluentAPIGenerator : FluentAPIGenerators.values()) { + if (fluentAPIGenerator.returnsThis(t)) { + return true; + } + } + return false; } - return false; - } - /** - * Enum of supported fluent API generators. For such generators, the checker can automatically - * add @This annotations on method return types in the generated code. - */ - private enum FluentAPIGenerators { /** - * The AutoValue - * framework. + * Enum of supported fluent API generators. For such generators, the checker can automatically + * add @This annotations on method return types in the generated code. */ - AUTO_VALUE { + private enum FluentAPIGenerators { + /** + * The AutoValue + * framework. + */ + AUTO_VALUE { - /** - * The qualified name of the AutoValue Builder annotation. This needs to be constructed - * dynamically due to a side effect of the shadow plugin. See {@link - * #getAutoValueBuilderCanonicalName()} for more information. - */ - private final String AUTO_VALUE_BUILDER = getAutoValueBuilderCanonicalName(); + /** + * The qualified name of the AutoValue Builder annotation. This needs to be constructed + * dynamically due to a side effect of the shadow plugin. See {@link + * #getAutoValueBuilderCanonicalName()} for more information. + */ + private final String AUTO_VALUE_BUILDER = getAutoValueBuilderCanonicalName(); - @Override - public boolean returnsThis(AnnotatedExecutableType t) { - ExecutableElement element = t.getElement(); - TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); - boolean inAutoValueBuilder = - AnnotationUtils.containsSameByName( - enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); + @Override + public boolean returnsThis(AnnotatedExecutableType t) { + ExecutableElement element = t.getElement(); + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + boolean inAutoValueBuilder = + AnnotationUtils.containsSameByName( + enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); - if (!inAutoValueBuilder) { - // see if superclass is an AutoValue Builder, to handle generated code - TypeMirror superclass = enclosingElement.getSuperclass(); - // if enclosingElement is an interface, the superclass has TypeKind NONE - if (superclass.getKind() != TypeKind.NONE) { - // update enclosingElement to be for the superclass for this case - enclosingElement = TypesUtils.getTypeElement(superclass); - inAutoValueBuilder = - AnnotationUtils.containsSameByName( - enclosingElement.getAnnotationMirrors(), AUTO_VALUE_BUILDER); - } - } + if (!inAutoValueBuilder) { + // see if superclass is an AutoValue Builder, to handle generated code + TypeMirror superclass = enclosingElement.getSuperclass(); + // if enclosingElement is an interface, the superclass has TypeKind NONE + if (superclass.getKind() != TypeKind.NONE) { + // update enclosingElement to be for the superclass for this case + enclosingElement = TypesUtils.getTypeElement(superclass); + inAutoValueBuilder = + AnnotationUtils.containsSameByName( + enclosingElement.getAnnotationMirrors(), + AUTO_VALUE_BUILDER); + } + } - if (inAutoValueBuilder) { - AnnotatedTypeMirror returnType = t.getReturnType(); - if (returnType == null) { - throw new TypeSystemError("Return type cannot be null: " + t); - } - return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType())); - } - return false; - } + if (inAutoValueBuilder) { + AnnotatedTypeMirror returnType = t.getReturnType(); + if (returnType == null) { + throw new TypeSystemError("Return type cannot be null: " + t); + } + return enclosingElement.equals( + TypesUtils.getTypeElement(returnType.getUnderlyingType())); + } + return false; + } - /** - * Get the qualified name of the AutoValue Builder annotation. This method constructs the - * String dynamically, to ensure it does not get rewritten due to relocation of the {@code - * "com.google"} package during the build process. - * - * @return {@code "com.google.auto.value.AutoValue.Builder"} - */ - private @CanonicalName String getAutoValueBuilderCanonicalName() { - String com = "com"; - @SuppressWarnings("signature:assignment.type.incompatible") // string concatenation - @CanonicalName String result = com + "." + "google.auto.value.AutoValue.Builder"; - return result; - } - }, - /** Project Lombok. */ - LOMBOK { - @Override - public boolean returnsThis(AnnotatedExecutableType t) { - ExecutableElement element = t.getElement(); - Element enclosingElement = element.getEnclosingElement(); - boolean inLombokBuilder = - (AnnotationUtils.containsSameByName( - enclosingElement.getAnnotationMirrors(), "lombok.Generated") - || AnnotationUtils.containsSameByName( - element.getAnnotationMirrors(), "lombok.Generated")) - && enclosingElement.getSimpleName().toString().endsWith("Builder"); + /** + * Get the qualified name of the AutoValue Builder annotation. This method constructs + * the String dynamically, to ensure it does not get rewritten due to relocation of the + * {@code "com.google"} package during the build process. + * + * @return {@code "com.google.auto.value.AutoValue.Builder"} + */ + private @CanonicalName String getAutoValueBuilderCanonicalName() { + String com = "com"; + @SuppressWarnings("signature:assignment.type.incompatible") // string concatenation + @CanonicalName String result = com + "." + "google.auto.value.AutoValue.Builder"; + return result; + } + }, + /** Project Lombok. */ + LOMBOK { + @Override + public boolean returnsThis(AnnotatedExecutableType t) { + ExecutableElement element = t.getElement(); + Element enclosingElement = element.getEnclosingElement(); + boolean inLombokBuilder = + (AnnotationUtils.containsSameByName( + enclosingElement.getAnnotationMirrors(), + "lombok.Generated") + || AnnotationUtils.containsSameByName( + element.getAnnotationMirrors(), "lombok.Generated")) + && enclosingElement.getSimpleName().toString().endsWith("Builder"); - if (inLombokBuilder) { - AnnotatedTypeMirror returnType = t.getReturnType(); - if (returnType == null) { - throw new TypeSystemError("Return type cannot be null: " + t); - } - return enclosingElement.equals(TypesUtils.getTypeElement(returnType.getUnderlyingType())); - } - return false; - } - }; + if (inLombokBuilder) { + AnnotatedTypeMirror returnType = t.getReturnType(); + if (returnType == null) { + throw new TypeSystemError("Return type cannot be null: " + t); + } + return enclosingElement.equals( + TypesUtils.getTypeElement(returnType.getUnderlyingType())); + } + return false; + } + }; - /** - * Returns {@code true} if the method was created by this generator and returns {@code this}. - * - * @param t the annotated type of the method signature - * @return {@code true} if the method was created by this generator and returns {@code this} - */ - protected abstract boolean returnsThis(AnnotatedExecutableType t); - } + /** + * Returns {@code true} if the method was created by this generator and returns {@code + * this}. + * + * @param t the annotated type of the method signature + * @return {@code true} if the method was created by this generator and returns {@code this} + */ + protected abstract boolean returnsThis(AnnotatedExecutableType t); + } } diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java index 43b3b698962..5cb4df90afb 100644 --- a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverAnnotatedTypeFactory.java @@ -1,9 +1,5 @@ package org.checkerframework.common.returnsreceiver; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.returnsreceiver.qual.BottomThis; @@ -16,77 +12,83 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -/** The type factory for the Returns Receiver Checker. */ -public class ReturnsReceiverAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - - /** - * The {@code @}{@link This} annotation. The field is package visible (i.e., "package private") - * due to a use in {@link ReturnsReceiverVisitor} - */ - /*package-private*/ final AnnotationMirror THIS_ANNOTATION; - - /** - * Create a new {@code ReturnsReceiverAnnotatedTypeFactory}. - * - * @param checker the type-checker associated with this factory - */ - public ReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - THIS_ANNOTATION = AnnotationBuilder.fromClass(elements, This.class); - this.postInit(); - } +import java.lang.annotation.Annotation; +import java.util.Set; - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(BottomThis.class, UnknownThis.class, This.class); - } +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator( - new ReturnsReceiverTypeAnnotator(this), super.createTypeAnnotator()); - } +/** The type factory for the Returns Receiver Checker. */ +public class ReturnsReceiverAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** A TypeAnnotator to add the {@code @}{@link This} annotation. */ - private class ReturnsReceiverTypeAnnotator extends TypeAnnotator { + /** + * The {@code @}{@link This} annotation. The field is package visible (i.e., "package private") + * due to a use in {@link ReturnsReceiverVisitor} + */ + /*package-private*/ final AnnotationMirror THIS_ANNOTATION; /** - * Create a new ReturnsReceiverTypeAnnotator. + * Create a new {@code ReturnsReceiverAnnotatedTypeFactory}. * - * @param typeFactory the {@link AnnotatedTypeFactory} associated with this {@link - * TypeAnnotator} + * @param checker the type-checker associated with this factory */ - public ReturnsReceiverTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); + public ReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + THIS_ANNOTATION = AnnotationBuilder.fromClass(elements, This.class); + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(BottomThis.class, UnknownThis.class, This.class); } @Override - public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator( + new ReturnsReceiverTypeAnnotator(this), super.createTypeAnnotator()); + } + + /** A TypeAnnotator to add the {@code @}{@link This} annotation. */ + private class ReturnsReceiverTypeAnnotator extends TypeAnnotator { + + /** + * Create a new ReturnsReceiverTypeAnnotator. + * + * @param typeFactory the {@link AnnotatedTypeFactory} associated with this {@link + * TypeAnnotator} + */ + public ReturnsReceiverTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + @Override + public Void visitExecutable(AnnotatedTypeMirror.AnnotatedExecutableType t, Void p) { - // skip constructors, as we never need to add annotations to them - if (t.getElement().getKind() == ElementKind.CONSTRUCTOR) { - return super.visitExecutable(t, p); - } + // skip constructors, as we never need to add annotations to them + if (t.getElement().getKind() == ElementKind.CONSTRUCTOR) { + return super.visitExecutable(t, p); + } - AnnotatedTypeMirror returnType = t.getReturnType(); + AnnotatedTypeMirror returnType = t.getReturnType(); - // If any FluentAPIGenerator indicates the method returns this, - // add an @This annotation on the return type. - if (FluentAPIGenerator.check(t)) { - returnType.addMissingAnnotation(THIS_ANNOTATION); - } + // If any FluentAPIGenerator indicates the method returns this, + // add an @This annotation on the return type. + if (FluentAPIGenerator.check(t)) { + returnType.addMissingAnnotation(THIS_ANNOTATION); + } - // If return type is annotated with @This, add @This annotation to the receiver type. - // We cannot yet default all receivers to be @This due to - // https://github.com/typetools/checker-framework/issues/2931 - AnnotationMirror retAnnotation = returnType.getAnnotationInHierarchy(THIS_ANNOTATION); - if (retAnnotation != null && AnnotationUtils.areSame(retAnnotation, THIS_ANNOTATION)) { - AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = t.getReceiverType(); - if (receiverType != null) { - receiverType.addMissingAnnotation(THIS_ANNOTATION); + // If return type is annotated with @This, add @This annotation to the receiver type. + // We cannot yet default all receivers to be @This due to + // https://github.com/typetools/checker-framework/issues/2931 + AnnotationMirror retAnnotation = returnType.getAnnotationInHierarchy(THIS_ANNOTATION); + if (retAnnotation != null && AnnotationUtils.areSame(retAnnotation, THIS_ANNOTATION)) { + AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = t.getReceiverType(); + if (receiverType != null) { + receiverType.addMissingAnnotation(THIS_ANNOTATION); + } + } + return super.visitExecutable(t, p); } - } - return super.visitExecutable(t, p); } - } } diff --git a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java index cb7a88ddb43..d5a3f14eef5 100644 --- a/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/returnsreceiver/ReturnsReceiverVisitor.java @@ -7,53 +7,57 @@ import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; + /** The visitor for the Returns Receiver Checker. */ public class ReturnsReceiverVisitor extends BaseTypeVisitor { - /** - * Create a new {@code ReturnsReceiverVisitor}. - * - * @param checker the type-checker associated with this visitor - */ - public ReturnsReceiverVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Create a new {@code ReturnsReceiverVisitor}. + * + * @param checker the type-checker associated with this visitor + */ + public ReturnsReceiverVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - AnnotationMirror annot = TreeUtils.annotationFromAnnotationTree(tree); - // Warn if a @This annotation is in an illegal location. - if (AnnotationUtils.areSame(annot, getTypeFactory().THIS_ANNOTATION)) { - TreePath parentPath = getCurrentPath().getParentPath(); - Tree parent = parentPath.getLeaf(); - Tree grandparent = parentPath.getParentPath().getLeaf(); - Tree greatGrandparent = parentPath.getParentPath().getParentPath().getLeaf(); - boolean isReturnAnnot = - grandparent instanceof MethodTree - && (parent.equals(((MethodTree) grandparent).getReturnType()) - || parent instanceof ModifiersTree); - boolean isReceiverAnnot = - greatGrandparent instanceof MethodTree - && grandparent.equals(((MethodTree) greatGrandparent).getReceiverParameter()) - && parent.equals(((VariableTree) grandparent).getModifiers()); - boolean isCastAnnot = - grandparent instanceof TypeCastTree - && parent.equals(((TypeCastTree) grandparent).getType()); - if (!(isReturnAnnot || isReceiverAnnot || isCastAnnot)) { - checker.reportError(tree, "type.invalid.this.location"); - } - if (isReturnAnnot - && ElementUtils.isStatic(TreeUtils.elementFromDeclaration((MethodTree) grandparent))) { - checker.reportError(tree, "type.invalid.this.location"); - } + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + AnnotationMirror annot = TreeUtils.annotationFromAnnotationTree(tree); + // Warn if a @This annotation is in an illegal location. + if (AnnotationUtils.areSame(annot, getTypeFactory().THIS_ANNOTATION)) { + TreePath parentPath = getCurrentPath().getParentPath(); + Tree parent = parentPath.getLeaf(); + Tree grandparent = parentPath.getParentPath().getLeaf(); + Tree greatGrandparent = parentPath.getParentPath().getParentPath().getLeaf(); + boolean isReturnAnnot = + grandparent instanceof MethodTree + && (parent.equals(((MethodTree) grandparent).getReturnType()) + || parent instanceof ModifiersTree); + boolean isReceiverAnnot = + greatGrandparent instanceof MethodTree + && grandparent.equals( + ((MethodTree) greatGrandparent).getReceiverParameter()) + && parent.equals(((VariableTree) grandparent).getModifiers()); + boolean isCastAnnot = + grandparent instanceof TypeCastTree + && parent.equals(((TypeCastTree) grandparent).getType()); + if (!(isReturnAnnot || isReceiverAnnot || isCastAnnot)) { + checker.reportError(tree, "type.invalid.this.location"); + } + if (isReturnAnnot + && ElementUtils.isStatic( + TreeUtils.elementFromDeclaration((MethodTree) grandparent))) { + checker.reportError(tree, "type.invalid.this.location"); + } + } + return super.visitAnnotation(tree, p); } - return super.visitAnnotation(tree, p); - } } diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java index d663389fd1e..ed298cb57a2 100644 --- a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotatedTypeFactory.java @@ -1,11 +1,5 @@ package org.checkerframework.common.subtyping; -import java.io.File; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.qual.Unqualified; @@ -19,105 +13,118 @@ import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; +import java.io.File; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; + /** Defines {@link #createSupportedTypeQualifiers}. */ public class SubtypingAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public SubtypingAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } - - @Override - protected AnnotationClassLoader createAnnotationClassLoader() { - return new SubtypingAnnotationClassLoader(checker); - } + public SubtypingAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - // Subtyping Checker doesn't have a qual directory, so we instantiate the loader here to - // load externally declared annotations - loader = createAnnotationClassLoader(); + @Override + protected AnnotationClassLoader createAnnotationClassLoader() { + return new SubtypingAnnotationClassLoader(checker); + } - Set> qualSet = new LinkedHashSet<>(); + @Override + protected Set> createSupportedTypeQualifiers() { + // Subtyping Checker doesn't have a qual directory, so we instantiate the loader here to + // load externally declared annotations + loader = createAnnotationClassLoader(); - // load individually named qualifiers - for (String qualName : checker.getStringsOption("quals", ',')) { - if (!Signatures.isBinaryName(qualName)) { - throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); - } - Class anno = loader.loadExternalAnnotationClass(qualName); - if (anno == null) { - throw new UserError("Qualifier specified in -Aquals not found: " + qualName); - } - qualSet.add(anno); - } + Set> qualSet = new LinkedHashSet<>(); - // load directories of qualifiers - for (String dirName : checker.getStringsOption("qualDirs", ':')) { - if (!new File(dirName).exists()) { - throw new UserError("Directory specified in -AqualsDir does not exist: %s", dirName); - } - Set> annos = - loader.loadExternalAnnotationClassesFromDirectory(dirName); - if (annos.isEmpty()) { - throw new UserError("Directory specified in -AqualsDir contains no qualifiers: " + dirName); - } - qualSet.addAll(annos); - } + // load individually named qualifiers + for (String qualName : checker.getStringsOption("quals", ',')) { + if (!Signatures.isBinaryName(qualName)) { + throw new UserError("Malformed qualifier \"%s\" in -Aquals", qualName); + } + Class anno = loader.loadExternalAnnotationClass(qualName); + if (anno == null) { + throw new UserError("Qualifier specified in -Aquals not found: " + qualName); + } + qualSet.add(anno); + } - if (qualSet.isEmpty()) { - throw new UserError("SubtypingChecker: no qualifiers specified via -Aquals or -AqualDirs"); - } + // load directories of qualifiers + for (String dirName : checker.getStringsOption("qualDirs", ':')) { + if (!new File(dirName).exists()) { + throw new UserError( + "Directory specified in -AqualsDir does not exist: %s", dirName); + } + Set> annos = + loader.loadExternalAnnotationClassesFromDirectory(dirName); + if (annos.isEmpty()) { + throw new UserError( + "Directory specified in -AqualsDir contains no qualifiers: " + dirName); + } + qualSet.addAll(annos); + } - // check for subtype meta-annotation - for (Class qual : qualSet) { - Annotation subtypeOfAnnotation = qual.getAnnotation(SubtypeOf.class); - if (subtypeOfAnnotation != null) { - for (Class superqual : qual.getAnnotation(SubtypeOf.class).value()) { - if (!qualSet.contains(superqual)) { + if (qualSet.isEmpty()) { throw new UserError( - "SubtypingChecker: qualifier " - + qual - + " was specified via -Aquals but its super-qualifier " - + superqual - + " was not"); - } + "SubtypingChecker: no qualifiers specified via -Aquals or -AqualDirs"); } - } - } - - return qualSet; - } - /** - * If necessary, make Unqualified the default qualifier. Keep most logic in sync with super. - * - * @see - * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addCheckedCodeDefaults(org.checkerframework.framework.util.defaults.QualifierDefaults) - */ - @Override - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - boolean foundOtherwise = false; - // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy - for (Class qual : getSupportedTypeQualifiers()) { - DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); - if (defaultFor != null) { - TypeUseLocation[] locations = defaultFor.value(); - defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); - foundOtherwise = - foundOtherwise || Arrays.asList(locations).contains(TypeUseLocation.OTHERWISE); - } + // check for subtype meta-annotation + for (Class qual : qualSet) { + Annotation subtypeOfAnnotation = qual.getAnnotation(SubtypeOf.class); + if (subtypeOfAnnotation != null) { + for (Class superqual : + qual.getAnnotation(SubtypeOf.class).value()) { + if (!qualSet.contains(superqual)) { + throw new UserError( + "SubtypingChecker: qualifier " + + qual + + " was specified via -Aquals but its super-qualifier " + + superqual + + " was not"); + } + } + } + } - if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { - defs.addCheckedCodeDefault( - AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); - foundOtherwise = true; - } + return qualSet; } - // If Unqualified is a supported qualifier, make it the default. - AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); - if (!foundOtherwise && this.isSupportedQualifier(unqualified)) { - defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); + + /** + * If necessary, make Unqualified the default qualifier. Keep most logic in sync with super. + * + * @see + * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addCheckedCodeDefaults(org.checkerframework.framework.util.defaults.QualifierDefaults) + */ + @Override + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + boolean foundOtherwise = false; + // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy + for (Class qual : getSupportedTypeQualifiers()) { + DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); + if (defaultFor != null) { + TypeUseLocation[] locations = defaultFor.value(); + defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); + foundOtherwise = + foundOtherwise + || Arrays.asList(locations).contains(TypeUseLocation.OTHERWISE); + } + + if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { + defs.addCheckedCodeDefault( + AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); + foundOtherwise = true; + } + } + // If Unqualified is a supported qualifier, make it the default. + AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); + if (!foundOtherwise && this.isSupportedQualifier(unqualified)) { + defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java index 9939ed82211..f32e1ea3b32 100644 --- a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java +++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingAnnotationClassLoader.java @@ -1,21 +1,23 @@ package org.checkerframework.common.subtyping; -import java.lang.annotation.Annotation; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.type.AnnotationClassLoader; +import java.lang.annotation.Annotation; + public class SubtypingAnnotationClassLoader extends AnnotationClassLoader { - public SubtypingAnnotationClassLoader(BaseTypeChecker checker) { - super(checker); - } + public SubtypingAnnotationClassLoader(BaseTypeChecker checker) { + super(checker); + } - // Unqualified is a supported annotation for the Subtyping Checker, and is loaded only if listed - // in -Aquals. It intentionally has an empty @Target meta-annotation. All other annotations used - // with the subtyping checker must have a well-defined @Target meta-annotation. - @Override - protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { - return super.hasWellDefinedTargetMetaAnnotation(annoClass) || annoClass == Unqualified.class; - } + // Unqualified is a supported annotation for the Subtyping Checker, and is loaded only if listed + // in -Aquals. It intentionally has an empty @Target meta-annotation. All other annotations used + // with the subtyping checker must have a well-defined @Target meta-annotation. + @Override + protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { + return super.hasWellDefinedTargetMetaAnnotation(annoClass) + || annoClass == Unqualified.class; + } } diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java index 1a4aabe25c3..ad9a03ba98a 100644 --- a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java +++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java @@ -1,14 +1,16 @@ package org.checkerframework.common.subtyping; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.framework.source.SourceVisitor; + import java.lang.annotation.Annotation; import java.util.Locale; import java.util.NavigableSet; import java.util.Set; import java.util.TreeSet; + import javax.annotation.processing.SupportedOptions; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.common.basetype.BaseTypeVisitor; -import org.checkerframework.framework.source.SourceVisitor; /** * A checker for type qualifier systems that only checks subtyping relationships. @@ -26,40 +28,40 @@ @SupportedOptions({"quals", "qualDirs"}) public final class SubtypingChecker extends BaseTypeChecker { - /** - * Compute SuppressWarnings prefixes, based on the names of all the qualifiers. - * - *

          Provided for the convenience of checkers that do not subclass {@code SubtypingChecker} - * (because it is final). Clients should call it like: - * - *

          {@code
          -   * SubtypingChecker.getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes());
          -   * }
          - * - * @param visitor the visitor - * @param superSupportedTypeQualifiers the result of super.getSuppressWarningsPrefixes(), as - * executed by checker - * @return SuppressWarnings prefixes, based on the names of all the qualifiers - */ - public static NavigableSet getSuppressWarningsPrefixes( - SourceVisitor visitor, NavigableSet superSupportedTypeQualifiers) { - TreeSet result = new TreeSet<>(superSupportedTypeQualifiers); + /** + * Compute SuppressWarnings prefixes, based on the names of all the qualifiers. + * + *

          Provided for the convenience of checkers that do not subclass {@code SubtypingChecker} + * (because it is final). Clients should call it like: + * + *

          {@code
          +     * SubtypingChecker.getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes());
          +     * }
          + * + * @param visitor the visitor + * @param superSupportedTypeQualifiers the result of super.getSuppressWarningsPrefixes(), as + * executed by checker + * @return SuppressWarnings prefixes, based on the names of all the qualifiers + */ + public static NavigableSet getSuppressWarningsPrefixes( + SourceVisitor visitor, NavigableSet superSupportedTypeQualifiers) { + TreeSet result = new TreeSet<>(superSupportedTypeQualifiers); - // visitor can be null if there was an error when calling the visitor class's constructor -- - // that is, when there is a bug in a checker implementation. - if (visitor != null) { - Set> annos = - ((BaseTypeVisitor) visitor).getTypeFactory().getSupportedTypeQualifiers(); - for (Class anno : annos) { - result.add(anno.getSimpleName().toLowerCase(Locale.ROOT)); - } - } + // visitor can be null if there was an error when calling the visitor class's constructor -- + // that is, when there is a bug in a checker implementation. + if (visitor != null) { + Set> annos = + ((BaseTypeVisitor) visitor).getTypeFactory().getSupportedTypeQualifiers(); + for (Class anno : annos) { + result.add(anno.getSimpleName().toLowerCase(Locale.ROOT)); + } + } - return result; - } + return result; + } - @Override - public NavigableSet getSuppressWarningsPrefixes() { - return getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes()); - } + @Override + public NavigableSet getSuppressWarningsPrefixes() { + return getSuppressWarningsPrefixes(this.visitor, super.getSuppressWarningsPrefixes()); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java index 0f0aa99c8e7..e2d7e217358 100644 --- a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java +++ b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java @@ -1,19 +1,5 @@ package org.checkerframework.common.util; -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; @@ -34,6 +20,22 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; + /** * TypeVisualizer prints AnnotatedTypeMirrors as a directed graph where each node is a type and an * arrow is a reference. Arrows are labeled with the relationship that reference represents (e.g. an @@ -53,549 +55,561 @@ */ public class TypeVisualizer { - /** - * Creates a dot file at dest that contains a digraph for the structure of {@code type}. - * - * @param dest the destination dot file - * @param type the type to be written - */ - public static void drawToDot(File dest, AnnotatedTypeMirror type) { - Drawing drawer = new Drawing("Type", type); - drawer.draw(dest); - } - - /** - * Creates a dot file at dest that contains a digraph for the structure of {@code type}. - * - * @param dest the destination dot file, this string will be directly passed to new File(dest) - * @param type the type to be written - */ - public static void drawToDot(String dest, AnnotatedTypeMirror type) { - drawToDot(new File(dest), type); - } - - /** - * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that - * file into a png at the location dest. This method will fail if a temp file can't be created. - * - * @param dest the destination png file - * @param type the type to be written - */ - public static void drawToPng(File dest, AnnotatedTypeMirror type) { - try { - File dotFile = File.createTempFile(dest.getName(), ".dot"); - drawToDot(dotFile, type); - execDotToPng(dotFile, dest); - - } catch (Exception exc) { - throw new RuntimeException(exc); - } - } - - /** - * Draws a dot file for type in a temporary directory then calls the "dot" program to convert that - * file into a png at the location dest. This method will fail if a temp file can't be created. - * - * @param dest the destination png file, this string will be directly passed to new File(dest) - * @param type the type to be written - */ - public static void drawToPng(String dest, AnnotatedTypeMirror type) { - drawToPng(new File(dest), type); - } - - /** - * Converts the given dot file to a png file at the specified location. This method calls the - * program "dot" from Runtime.exec and will fail if "dot" is not on your class path. - * - * @param dotFile the dot file to convert - * @param pngFile the destination of the resultant png file - */ - public static void execDotToPng(File dotFile, File pngFile) { - String[] cmd = - new String[] {"dot", "-Tpng", dotFile.getAbsolutePath(), "-o", pngFile.getAbsolutePath()}; - System.out.println("Printing dotFile: " + dotFile + " to loc: " + pngFile); - System.out.flush(); - ExecUtil.execute(cmd, System.out, System.err); - } - - /** - * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to - * a dot file at {@code directory/varName}. - * - * @return true if the type variable was printed, otherwise false - */ - public static boolean printTypevarToDotIfMatches( - AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { - return printTypevarIfMatches(typeVariable, typeVarNames, directory, false); - } - - /** - * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable to - * a png file at {@code directory/varName.png}. - * - * @return true if the type variable was printed, otherwise false - */ - public static boolean printTypevarToPngIfMatches( - AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { - return printTypevarIfMatches(typeVariable, typeVarNames, directory, true); - } - - private static boolean printTypevarIfMatches( - AnnotatedTypeVariable typeVariable, - List typeVarNames, - String directory, - boolean png) { - String dirPath = directory.endsWith(File.separator) ? directory : directory + File.separator; - String varName = typeVariable.getUnderlyingType().asElement().toString(); - - if (typeVarNames.contains(varName)) { - if (png) { - TypeVisualizer.drawToPng(dirPath + varName + ".png", typeVariable); - } else { - TypeVisualizer.drawToDot(dirPath + varName + ".dot", typeVariable); - } - return true; - } - - return false; - } - - /** - * This class exists because there is no LinkedIdentityHashMap. - * - *

          Node is just a wrapper around type mirror that replaces .equals with referential equality. - * This is done to preserve the order types were traversed so that printing will occur in a - * hierarchical order. However, since there is no LinkedIdentityHashMap, it was easiest to just - * create a wrapper that performed referential equality on types and use a LinkedHashMap. - */ - private static class Node { - /** The delegate; that is, the wrapped value. */ - private final @InternedDistinct AnnotatedTypeMirror type; - /** - * Create a new Node that wraps the given type. + * Creates a dot file at dest that contains a digraph for the structure of {@code type}. * - * @param type the type that the newly-constructed Node represents + * @param dest the destination dot file + * @param type the type to be written */ - private Node(@FindDistinct AnnotatedTypeMirror type) { - this.type = type; - } - - @Override - public int hashCode() { - return type.hashCode(); + public static void drawToDot(File dest, AnnotatedTypeMirror type) { + Drawing drawer = new Drawing("Type", type); + drawer.draw(dest); } - @Override - public boolean equals(@Nullable Object obj) { - if (obj == null) { - return false; - } - if (obj instanceof Node) { - return ((Node) obj).type == this.type; - } - - return false; - } - } - - /** - * Drawing visits a type and writes a dot file to the location specified. It contains data - * structures to hold the intermediate dot information before printing. - */ - private static class Drawing { - /** A map from Node (type) to a dot string declaring that node. */ - private final Map nodes = new LinkedHashMap<>(); - - /** List of connections between nodes. Lines refer to identifiers in nodes.values(). */ - private final List lines = new ArrayList<>(); - - private final String graphName; - - /** The type being drawn. */ - private final AnnotatedTypeMirror type; - - /** Used to identify nodes uniquely. This field is monotonically increasing. */ - private int nextId = 0; - - public Drawing(String graphName, AnnotatedTypeMirror type) { - this.graphName = graphName; - this.type = type; + /** + * Creates a dot file at dest that contains a digraph for the structure of {@code type}. + * + * @param dest the destination dot file, this string will be directly passed to new File(dest) + * @param type the type to be written + */ + public static void drawToDot(String dest, AnnotatedTypeMirror type) { + drawToDot(new File(dest), type); } - public void draw(File file) { - addNodes(type); - addConnections(); - write(file); + /** + * Draws a dot file for type in a temporary directory then calls the "dot" program to convert + * that file into a png at the location dest. This method will fail if a temp file can't be + * created. + * + * @param dest the destination png file + * @param type the type to be written + */ + public static void drawToPng(File dest, AnnotatedTypeMirror type) { + try { + File dotFile = File.createTempFile(dest.getName(), ".dot"); + drawToDot(dotFile, type); + execDotToPng(dotFile, dest); + + } catch (Exception exc) { + throw new RuntimeException(exc); + } } - private void addNodes(AnnotatedTypeMirror type) { - new NodeDrawer().visit(type); + /** + * Draws a dot file for type in a temporary directory then calls the "dot" program to convert + * that file into a png at the location dest. This method will fail if a temp file can't be + * created. + * + * @param dest the destination png file, this string will be directly passed to new File(dest) + * @param type the type to be written + */ + public static void drawToPng(String dest, AnnotatedTypeMirror type) { + drawToPng(new File(dest), type); } - private void addConnections() { - ConnectionDrawer connectionDrawer = new ConnectionDrawer(); - for (Node node : nodes.keySet()) { - connectionDrawer.visit(node.type); - } + /** + * Converts the given dot file to a png file at the specified location. This method calls the + * program "dot" from Runtime.exec and will fail if "dot" is not on your class path. + * + * @param dotFile the dot file to convert + * @param pngFile the destination of the resultant png file + */ + public static void execDotToPng(File dotFile, File pngFile) { + String[] cmd = + new String[] { + "dot", "-Tpng", dotFile.getAbsolutePath(), "-o", pngFile.getAbsolutePath() + }; + System.out.println("Printing dotFile: " + dotFile + " to loc: " + pngFile); + System.out.flush(); + ExecUtil.execute(cmd, System.out, System.err); } /** - * Write this to a file. + * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable + * to a dot file at {@code directory/varName}. * - * @param file the file to write to + * @return true if the type variable was printed, otherwise false */ - private void write(File file) { - try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { - writer.write("digraph " + graphName + "{"); - writer.newLine(); - for (String line : lines) { - writer.write(line + ";"); - writer.newLine(); - } - writer.write("}"); - writer.flush(); - } catch (IOException exc) { - throw new BugInCF(exc, "Exception visualizing type:%nfile=%s%ntype=%s", file, type); - } + public static boolean printTypevarToDotIfMatches( + AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { + return printTypevarIfMatches(typeVariable, typeVarNames, directory, false); } /** - * Connection drawer is used to add the connections between all the nodes created by the - * NodeDrawer. It is not a scanner and is called on every node in the nodes map. + * If the name of typeVariable matches one in the list of typeVarNames, then print typeVariable + * to a png file at {@code directory/varName.png}. + * + * @return true if the type variable was printed, otherwise false */ - private class ConnectionDrawer implements AnnotatedTypeVisitor { - - @Override - public Void visit(AnnotatedTypeMirror type) { - type.accept(this, null); - return null; - } - - @Override - public Void visit(AnnotatedTypeMirror type, Void aVoid) { - return visit(type); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - List typeArgs = type.getTypeArguments(); - for (int i = 0; i < typeArgs.size(); i++) { - lines.add(connect(type, typeArgs.get(i)) + " " + makeTypeArgLabel(i)); - } - return null; - } - - @Override - public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { - List bounds = type.getBounds(); - for (int i = 0; i < bounds.size(); i++) { - lines.add(connect(type, bounds.get(i)) + " " + makeLabel("&")); - } - return null; - } - - @Override - public Void visitUnion(AnnotatedUnionType type, Void aVoid) { - List alternatives = type.getAlternatives(); - for (int i = 0; i < alternatives.size(); i++) { - lines.add(connect(type, alternatives.get(i)) + " " + makeLabel("|")); - } - return null; - } + public static boolean printTypevarToPngIfMatches( + AnnotatedTypeVariable typeVariable, List typeVarNames, String directory) { + return printTypevarIfMatches(typeVariable, typeVarNames, directory, true); + } - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { + private static boolean printTypevarIfMatches( + AnnotatedTypeVariable typeVariable, + List typeVarNames, + String directory, + boolean png) { + String dirPath = + directory.endsWith(File.separator) ? directory : directory + File.separator; + String varName = typeVariable.getUnderlyingType().asElement().toString(); + + if (typeVarNames.contains(varName)) { + if (png) { + TypeVisualizer.drawToPng(dirPath + varName + ".png", typeVariable); + } else { + TypeVisualizer.drawToDot(dirPath + varName + ".dot", typeVariable); + } + return true; + } - ExecutableElement methodElem = type.getElement(); - lines.add(connect(type, type.getReturnType()) + " " + makeLabel("returns")); + return false; + } - List typeVarElems = methodElem.getTypeParameters(); - List typeVars = type.getTypeVariables(); - for (int i = 0; i < typeVars.size(); i++) { - String typeVarName = typeVarElems.get(i).getSimpleName().toString(); - lines.add(connect(type, typeVars.get(i)) + " " + makeMethodTypeArgLabel(typeVarName)); + /** + * This class exists because there is no LinkedIdentityHashMap. + * + *

          Node is just a wrapper around type mirror that replaces .equals with referential equality. + * This is done to preserve the order types were traversed so that printing will occur in a + * hierarchical order. However, since there is no LinkedIdentityHashMap, it was easiest to just + * create a wrapper that performed referential equality on types and use a LinkedHashMap. + */ + private static class Node { + /** The delegate; that is, the wrapped value. */ + private final @InternedDistinct AnnotatedTypeMirror type; + + /** + * Create a new Node that wraps the given type. + * + * @param type the type that the newly-constructed Node represents + */ + private Node(@FindDistinct AnnotatedTypeMirror type) { + this.type = type; } - if (type.getReceiverType() != null) { - lines.add(connect(type, type.getReceiverType()) + " " + makeLabel("receiver")); + @Override + public int hashCode() { + return type.hashCode(); } - List paramElems = methodElem.getParameters(); - List params = type.getParameterTypes(); - for (int i = 0; i < params.size(); i++) { - String paramName = paramElems.get(i).getSimpleName().toString(); - lines.add(connect(type, params.get(i)) + " " + makeParamLabel(paramName)); - } + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof Node) { + return ((Node) obj).type == this.type; + } - List thrown = type.getThrownTypes(); - for (int i = 0; i < thrown.size(); i++) { - lines.add(connect(type, thrown.get(i)) + " " + makeThrownLabel(i)); + return false; } - - return null; - } - - @Override - public Void visitArray(AnnotatedArrayType type, Void aVoid) { - lines.add(connect(type, type.getComponentType())); - return null; - } - - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { - lines.add(connect(type, type.getUpperBound()) + " " + makeLabel("extends")); - lines.add(connect(type, type.getLowerBound()) + " " + makeLabel("super")); - return null; - } - - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - return null; - } - - @Override - public Void visitNoType(AnnotatedNoType type, Void aVoid) { - return null; - } - - @Override - public Void visitNull(AnnotatedNullType type, Void aVoid) { - return null; - } - - @Override - public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { - lines.add(connect(type, type.getExtendsBound()) + " " + makeLabel("extends")); - lines.add(connect(type, type.getSuperBound()) + " " + makeLabel("super")); - return null; - } - - private String connect(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - return nodes.get(new Node(from)) + " -> " + nodes.get(new Node(to)); - } - - private String makeLabel(String text) { - return "[label=\"" + text + "\"]"; - } - - private String makeTypeArgLabel(int argIndex) { - return makeLabel("<" + argIndex + ">"); - } - - private String makeMethodTypeArgLabel(String paramName) { - return makeLabel("<" + paramName + ">"); - } - - private String makeParamLabel(String paramName) { - return makeLabel(paramName); - } - - private String makeThrownLabel(int index) { - return makeLabel("throws: " + index); - } } - /** The default annotation formatter. */ - private static final DefaultAnnotationFormatter annoFormatter = - new DefaultAnnotationFormatter(); - /** - * Scans types and adds a mapping from type to dot node declaration representing that type in - * the enclosing drawing. + * Drawing visits a type and writes a dot file to the location specified. It contains data + * structures to hold the intermediate dot information before printing. */ - private class NodeDrawer implements AnnotatedTypeVisitor { - - /** Create a new NodeDrawer. */ - public NodeDrawer() {} + private static class Drawing { + /** A map from Node (type) to a dot string declaring that node. */ + private final Map nodes = new LinkedHashMap<>(); - private void visitAll(List types) { - for (AnnotatedTypeMirror type : types) { - visit(type); - } - } + /** List of connections between nodes. Lines refer to identifiers in nodes.values(). */ + private final List lines = new ArrayList<>(); - @Override - public Void visit(AnnotatedTypeMirror type) { - if (type != null) { - type.accept(this, null); - } + private final String graphName; - return null; - } - - @Override - public Void visit(AnnotatedTypeMirror type, Void aVoid) { - return visit(type); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode( - type, - getAnnoStr(type) - + " " - + type.getUnderlyingType().asElement().getSimpleName() - + (type.getTypeArguments().isEmpty() ? "" : "<...>"), - "shape=box"); - visitAll(type.getTypeArguments()); - } - return null; - } - - @Override - public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " Intersection", "shape=octagon"); - visitAll(type.getBounds()); - } + /** The type being drawn. */ + private final AnnotatedTypeMirror type; - return null; - } + /** Used to identify nodes uniquely. This field is monotonically increasing. */ + private int nextId = 0; - @Override - public Void visitUnion(AnnotatedUnionType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " Union", "shape=doubleoctagon"); - visitAll(type.getAlternatives()); + public Drawing(String graphName, AnnotatedTypeMirror type) { + this.graphName = graphName; + this.type = type; } - return null; - } - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, makeMethodLabel(type), "shape=box,peripheries=2"); - - visit(type.getReturnType()); - visitAll(type.getTypeVariables()); - - visit(type.getReceiverType()); - visitAll(type.getParameterTypes()); - - visitAll(type.getThrownTypes()); - - } else { - throw new BugInCF("Executable types should never be recursive%ntype=%s", type); - } - return null; - } - - @Override - public Void visitArray(AnnotatedArrayType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + "[]"); - visit(type.getComponentType()); - } - return null; - } - - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode( - type, - getAnnoStr(type) + " " + type.getUnderlyingType().asElement().getSimpleName(), - "shape=invtrapezium"); - visit(type.getUpperBound()); - visit(type.getLowerBound()); + public void draw(File file) { + addNodes(type); + addConnections(); + write(file); } - return null; - } - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " " + type.getKind()); + private void addNodes(AnnotatedTypeMirror type) { + new NodeDrawer().visit(type); } - return null; - } - @Override - public Void visitNoType(AnnotatedNoType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " None"); + private void addConnections() { + ConnectionDrawer connectionDrawer = new ConnectionDrawer(); + for (Node node : nodes.keySet()) { + connectionDrawer.visit(node.type); + } } - return null; - } - @Override - public Void visitNull(AnnotatedNullType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + " NullType"); + /** + * Write this to a file. + * + * @param file the file to write to + */ + private void write(File file) { + try (BufferedWriter writer = + Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { + writer.write("digraph " + graphName + "{"); + writer.newLine(); + for (String line : lines) { + writer.write(line + ";"); + writer.newLine(); + } + writer.write("}"); + writer.flush(); + } catch (IOException exc) { + throw new BugInCF(exc, "Exception visualizing type:%nfile=%s%ntype=%s", file, type); + } } - return null; - } - - @Override - public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { - if (checkOrAdd(type)) { - addLabeledNode(type, getAnnoStr(type) + "?", "shape=trapezium"); - visit(type.getExtendsBound()); - visit(type.getSuperBound()); - } - return null; - } - - /** - * Returns a string representation of the annotations on a type. - * - * @param atm an annotated type - * @return a string representation of the annotations on {@code atm} - */ - public String getAnnoStr(AnnotatedTypeMirror atm) { - StringJoiner sj = new StringJoiner(" "); - for (AnnotationMirror anno : atm.getAnnotations()) { - // TODO: More comprehensive escaping - sj.add(annoFormatter.formatAnnotationMirror(anno).replace("\"", "\\")); - } - return sj.toString(); - } - public boolean checkOrAdd(AnnotatedTypeMirror atm) { - Node node = new Node(atm); - if (nodes.containsKey(node)) { - return false; + /** + * Connection drawer is used to add the connections between all the nodes created by the + * NodeDrawer. It is not a scanner and is called on every node in the nodes map. + */ + private class ConnectionDrawer implements AnnotatedTypeVisitor { + + @Override + public Void visit(AnnotatedTypeMirror type) { + type.accept(this, null); + return null; + } + + @Override + public Void visit(AnnotatedTypeMirror type, Void aVoid) { + return visit(type); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + List typeArgs = type.getTypeArguments(); + for (int i = 0; i < typeArgs.size(); i++) { + lines.add(connect(type, typeArgs.get(i)) + " " + makeTypeArgLabel(i)); + } + return null; + } + + @Override + public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { + List bounds = type.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + lines.add(connect(type, bounds.get(i)) + " " + makeLabel("&")); + } + return null; + } + + @Override + public Void visitUnion(AnnotatedUnionType type, Void aVoid) { + List alternatives = type.getAlternatives(); + for (int i = 0; i < alternatives.size(); i++) { + lines.add(connect(type, alternatives.get(i)) + " " + makeLabel("|")); + } + return null; + } + + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { + + ExecutableElement methodElem = type.getElement(); + lines.add(connect(type, type.getReturnType()) + " " + makeLabel("returns")); + + List typeVarElems = methodElem.getTypeParameters(); + List typeVars = type.getTypeVariables(); + for (int i = 0; i < typeVars.size(); i++) { + String typeVarName = typeVarElems.get(i).getSimpleName().toString(); + lines.add( + connect(type, typeVars.get(i)) + + " " + + makeMethodTypeArgLabel(typeVarName)); + } + + if (type.getReceiverType() != null) { + lines.add(connect(type, type.getReceiverType()) + " " + makeLabel("receiver")); + } + + List paramElems = methodElem.getParameters(); + List params = type.getParameterTypes(); + for (int i = 0; i < params.size(); i++) { + String paramName = paramElems.get(i).getSimpleName().toString(); + lines.add(connect(type, params.get(i)) + " " + makeParamLabel(paramName)); + } + + List thrown = type.getThrownTypes(); + for (int i = 0; i < thrown.size(); i++) { + lines.add(connect(type, thrown.get(i)) + " " + makeThrownLabel(i)); + } + + return null; + } + + @Override + public Void visitArray(AnnotatedArrayType type, Void aVoid) { + lines.add(connect(type, type.getComponentType())); + return null; + } + + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { + lines.add(connect(type, type.getUpperBound()) + " " + makeLabel("extends")); + lines.add(connect(type, type.getLowerBound()) + " " + makeLabel("super")); + return null; + } + + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { + return null; + } + + @Override + public Void visitNoType(AnnotatedNoType type, Void aVoid) { + return null; + } + + @Override + public Void visitNull(AnnotatedNullType type, Void aVoid) { + return null; + } + + @Override + public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { + lines.add(connect(type, type.getExtendsBound()) + " " + makeLabel("extends")); + lines.add(connect(type, type.getSuperBound()) + " " + makeLabel("super")); + return null; + } + + private String connect(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + return nodes.get(new Node(from)) + " -> " + nodes.get(new Node(to)); + } + + private String makeLabel(String text) { + return "[label=\"" + text + "\"]"; + } + + private String makeTypeArgLabel(int argIndex) { + return makeLabel("<" + argIndex + ">"); + } + + private String makeMethodTypeArgLabel(String paramName) { + return makeLabel("<" + paramName + ">"); + } + + private String makeParamLabel(String paramName) { + return makeLabel(paramName); + } + + private String makeThrownLabel(int index) { + return makeLabel("throws: " + index); + } } - nodes.put(node, String.valueOf(nextId++)); - return true; - } - - public String makeLabeledNode(AnnotatedTypeMirror type, String label) { - return makeLabeledNode(type, label, null); - } - - public String makeLabeledNode(AnnotatedTypeMirror type, String label, String attributes) { - String attr = (attributes != null) ? ", " + attributes : ""; - return nodes.get(new Node(type)) + " [label=\"" + label + "\"" + attr + "]"; - } - - public void addLabeledNode(AnnotatedTypeMirror type, String label) { - lines.add(makeLabeledNode(type, label)); - } - - public void addLabeledNode(AnnotatedTypeMirror type, String label, String attributes) { - lines.add(makeLabeledNode(type, label, attributes)); - } - public String makeMethodLabel(AnnotatedExecutableType methodType) { - ExecutableElement methodElem = methodType.getElement(); - - StringBuilder builder = new StringBuilder(); - builder.append(methodElem.getReturnType().toString()); - builder.append(" <"); - - builder.append(StringsPlume.join(", ", methodElem.getTypeParameters())); - builder.append("> "); - - builder.append(methodElem.getSimpleName().toString()); - - builder.append("("); - builder.append(StringsPlume.join(",", methodElem.getParameters())); - builder.append(")"); - return builder.toString(); - } + /** The default annotation formatter. */ + private static final DefaultAnnotationFormatter annoFormatter = + new DefaultAnnotationFormatter(); + + /** + * Scans types and adds a mapping from type to dot node declaration representing that type + * in the enclosing drawing. + */ + private class NodeDrawer implements AnnotatedTypeVisitor { + + /** Create a new NodeDrawer. */ + public NodeDrawer() {} + + private void visitAll(List types) { + for (AnnotatedTypeMirror type : types) { + visit(type); + } + } + + @Override + public Void visit(AnnotatedTypeMirror type) { + if (type != null) { + type.accept(this, null); + } + + return null; + } + + @Override + public Void visit(AnnotatedTypeMirror type, Void aVoid) { + return visit(type); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode( + type, + getAnnoStr(type) + + " " + + type.getUnderlyingType().asElement().getSimpleName() + + (type.getTypeArguments().isEmpty() ? "" : "<...>"), + "shape=box"); + visitAll(type.getTypeArguments()); + } + return null; + } + + @Override + public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " Intersection", "shape=octagon"); + visitAll(type.getBounds()); + } + + return null; + } + + @Override + public Void visitUnion(AnnotatedUnionType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " Union", "shape=doubleoctagon"); + visitAll(type.getAlternatives()); + } + return null; + } + + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, makeMethodLabel(type), "shape=box,peripheries=2"); + + visit(type.getReturnType()); + visitAll(type.getTypeVariables()); + + visit(type.getReceiverType()); + visitAll(type.getParameterTypes()); + + visitAll(type.getThrownTypes()); + + } else { + throw new BugInCF("Executable types should never be recursive%ntype=%s", type); + } + return null; + } + + @Override + public Void visitArray(AnnotatedArrayType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + "[]"); + visit(type.getComponentType()); + } + return null; + } + + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode( + type, + getAnnoStr(type) + + " " + + type.getUnderlyingType().asElement().getSimpleName(), + "shape=invtrapezium"); + visit(type.getUpperBound()); + visit(type.getLowerBound()); + } + return null; + } + + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " " + type.getKind()); + } + return null; + } + + @Override + public Void visitNoType(AnnotatedNoType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " None"); + } + return null; + } + + @Override + public Void visitNull(AnnotatedNullType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + " NullType"); + } + return null; + } + + @Override + public Void visitWildcard(AnnotatedWildcardType type, Void aVoid) { + if (checkOrAdd(type)) { + addLabeledNode(type, getAnnoStr(type) + "?", "shape=trapezium"); + visit(type.getExtendsBound()); + visit(type.getSuperBound()); + } + return null; + } + + /** + * Returns a string representation of the annotations on a type. + * + * @param atm an annotated type + * @return a string representation of the annotations on {@code atm} + */ + public String getAnnoStr(AnnotatedTypeMirror atm) { + StringJoiner sj = new StringJoiner(" "); + for (AnnotationMirror anno : atm.getAnnotations()) { + // TODO: More comprehensive escaping + sj.add(annoFormatter.formatAnnotationMirror(anno).replace("\"", "\\")); + } + return sj.toString(); + } + + public boolean checkOrAdd(AnnotatedTypeMirror atm) { + Node node = new Node(atm); + if (nodes.containsKey(node)) { + return false; + } + nodes.put(node, String.valueOf(nextId++)); + return true; + } + + public String makeLabeledNode(AnnotatedTypeMirror type, String label) { + return makeLabeledNode(type, label, null); + } + + public String makeLabeledNode( + AnnotatedTypeMirror type, String label, String attributes) { + String attr = (attributes != null) ? ", " + attributes : ""; + return nodes.get(new Node(type)) + " [label=\"" + label + "\"" + attr + "]"; + } + + public void addLabeledNode(AnnotatedTypeMirror type, String label) { + lines.add(makeLabeledNode(type, label)); + } + + public void addLabeledNode(AnnotatedTypeMirror type, String label, String attributes) { + lines.add(makeLabeledNode(type, label, attributes)); + } + + public String makeMethodLabel(AnnotatedExecutableType methodType) { + ExecutableElement methodElem = methodType.getElement(); + + StringBuilder builder = new StringBuilder(); + builder.append(methodElem.getReturnType().toString()); + builder.append(" <"); + + builder.append(StringsPlume.join(", ", methodElem.getTypeParameters())); + builder.append("> "); + + builder.append(methodElem.getSimpleName().toString()); + + builder.append("("); + builder.append(StringsPlume.join(",", methodElem.getParameters())); + builder.append(")"); + return builder.toString(); + } + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java index a82d0570d05..5447191e581 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java @@ -17,18 +17,21 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.util.Log; + +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.source.SourceVisitor; +import org.checkerframework.framework.source.SupportedOptions; +import org.checkerframework.javacutil.AnnotationProvider; + import java.util.HashMap; import java.util.Map; import java.util.StringJoiner; import java.util.TreeSet; + import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Name; import javax.tools.Diagnostic; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.source.SourceVisitor; -import org.checkerframework.framework.source.SupportedOptions; -import org.checkerframework.javacutil.AnnotationProvider; /** * An annotation processor for counting the annotations in a program and for listing the potential @@ -76,252 +79,252 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) public class AnnotationStatistics extends SourceChecker { - /** - * Map from annotation name (as the {@code toString()} of its Name representation) to number of - * times the annotation was written in source code. - */ - final Map annotationCount = new HashMap<>(); - - /** Creates an AnnotationStatistics. */ - public AnnotationStatistics() { - // This checker never issues any warnings, so don't warn about - // @SuppressWarnings("allcheckers:..."). - this.useAllcheckersPrefix = false; - } - - @Override - public void typeProcessingOver() { - Log log = getCompilerLog(); - String output; - if (log.nerrors != 0) { - output = "Not counting annotations, because compilation issued an error."; - } else if (annotationCount.isEmpty()) { - output = "No annotations found."; - } else { - StringJoiner sj = new StringJoiner(System.lineSeparator()); - sj.add("Found annotations: "); - // alphabetize the annotations - for (String key : new TreeSet<>(annotationCount.keySet())) { - sj.add(key + "\t" + annotationCount.get(key)); - } - output = sj.toString(); - } - if (hasOption("annotationserror")) { - // Issue annotation details a compiler warning rather than printed. This may be useful, - // for example, when Maven swallows non-warning output from the annotation processor. - getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.WARNING, output); - } else { - System.out.println(output); - } - super.typeProcessingOver(); - } - - /** Increment the number of times annotation with name {@code annoName} has appeared. */ - protected void incrementCount(Name annoName) { - String annoString = annoName.toString(); - if (!annotationCount.containsKey(annoString)) { - annotationCount.put(annoString, 1); - } else { - annotationCount.put(annoString, annotationCount.get(annoString) + 1); - } - } - - @Override - protected SourceVisitor createSourceVisitor() { - return new Visitor(this); - } - - class Visitor extends SourceVisitor { - - /** Whether annotation locations should be printed. */ - private final boolean locations; - - /** Whether annotation details should be printed. */ - private final boolean annotations; - - /** Whether only a summary should be printed. */ - private final boolean annotationsummaryonly; - /** - * Create a new Visitor. - * - * @param l the AnnotationStatistics object, used for obtaining command-line arguments + * Map from annotation name (as the {@code toString()} of its Name representation) to number of + * times the annotation was written in source code. */ - public Visitor(AnnotationStatistics l) { - super(l); + final Map annotationCount = new HashMap<>(); - locations = !l.hasOption("nolocations"); - annotations = l.hasOption("annotations"); - annotationsummaryonly = l.hasOption("annotationsummaryonly"); + /** Creates an AnnotationStatistics. */ + public AnnotationStatistics() { + // This checker never issues any warnings, so don't warn about + // @SuppressWarnings("allcheckers:..."). + this.useAllcheckersPrefix = false; } @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - if (annotations) { - Name annoName = ((JCAnnotation) tree).annotationType.type.tsym.getQualifiedName(); - incrementCount(annoName); - - // An annotation is a body annotation if, while ascending the AST from the - // annotation to the root, we find a block immediately enclosed by a method. - // - // If an annotation is not a body annotation, it's a signature (declaration) - // annotation. - - boolean isBodyAnnotation = false; - TreePath path = getCurrentPath(); - Tree prev = null; - for (Tree t : path) { - if (prev != null - && prev.getKind() == Tree.Kind.BLOCK - && t.getKind() == Tree.Kind.METHOD) { - isBodyAnnotation = true; - break; - } - prev = t; + public void typeProcessingOver() { + Log log = getCompilerLog(); + String output; + if (log.nerrors != 0) { + output = "Not counting annotations, because compilation issued an error."; + } else if (annotationCount.isEmpty()) { + output = "No annotations found."; + } else { + StringJoiner sj = new StringJoiner(System.lineSeparator()); + sj.add("Found annotations: "); + // alphabetize the annotations + for (String key : new TreeSet<>(annotationCount.keySet())) { + sj.add(key + "\t" + annotationCount.get(key)); + } + output = sj.toString(); + } + if (hasOption("annotationserror")) { + // Issue annotation details a compiler warning rather than printed. This may be useful, + // for example, when Maven swallows non-warning output from the annotation processor. + getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.WARNING, output); + } else { + System.out.println(output); } + super.typeProcessingOver(); + } - if (!annotationsummaryonly) { - System.out.printf( - ":annotation %s %s %s %s%n", - tree.getAnnotationType(), - tree, - root.getSourceFile().getName(), - (isBodyAnnotation ? "body" : "sig")); + /** Increment the number of times annotation with name {@code annoName} has appeared. */ + protected void incrementCount(Name annoName) { + String annoString = annoName.toString(); + if (!annotationCount.containsKey(annoString)) { + annotationCount.put(annoString, 1); + } else { + annotationCount.put(annoString, annotationCount.get(annoString) + 1); } - } - return super.visitAnnotation(tree, p); } @Override - public Void visitArrayType(ArrayTypeTree tree, Void p) { - if (locations) { - System.out.println("array type"); - } - return super.visitArrayType(tree, p); + protected SourceVisitor createSourceVisitor() { + return new Visitor(this); } - @Override - public Void visitClass(ClassTree tree, Void p) { - if (shouldSkipDefs(tree)) { - // Not "return super.visitClass(classTree, p);" because that would recursively call - // visitors on subtrees; we want to skip the class entirely. - return null; - } - if (locations) { - System.out.println("class"); - if (tree.getExtendsClause() != null) { - System.out.println("class extends"); + class Visitor extends SourceVisitor { + + /** Whether annotation locations should be printed. */ + private final boolean locations; + + /** Whether annotation details should be printed. */ + private final boolean annotations; + + /** Whether only a summary should be printed. */ + private final boolean annotationsummaryonly; + + /** + * Create a new Visitor. + * + * @param l the AnnotationStatistics object, used for obtaining command-line arguments + */ + public Visitor(AnnotationStatistics l) { + super(l); + + locations = !l.hasOption("nolocations"); + annotations = l.hasOption("annotations"); + annotationsummaryonly = l.hasOption("annotationsummaryonly"); } - for (@SuppressWarnings("unused") Tree t : tree.getImplementsClause()) { - System.out.println("class implements"); + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + if (annotations) { + Name annoName = ((JCAnnotation) tree).annotationType.type.tsym.getQualifiedName(); + incrementCount(annoName); + + // An annotation is a body annotation if, while ascending the AST from the + // annotation to the root, we find a block immediately enclosed by a method. + // + // If an annotation is not a body annotation, it's a signature (declaration) + // annotation. + + boolean isBodyAnnotation = false; + TreePath path = getCurrentPath(); + Tree prev = null; + for (Tree t : path) { + if (prev != null + && prev.getKind() == Tree.Kind.BLOCK + && t.getKind() == Tree.Kind.METHOD) { + isBodyAnnotation = true; + break; + } + prev = t; + } + + if (!annotationsummaryonly) { + System.out.printf( + ":annotation %s %s %s %s%n", + tree.getAnnotationType(), + tree, + root.getSourceFile().getName(), + (isBodyAnnotation ? "body" : "sig")); + } + } + return super.visitAnnotation(tree, p); } - } - return super.visitClass(tree, p); - } - @Override - public Void visitMethod(MethodTree tree, Void p) { - if (locations) { - System.out.println("method return"); - System.out.println("method receiver"); - for (@SuppressWarnings("unused") Tree t : tree.getThrows()) { - System.out.println("method throws"); + @Override + public Void visitArrayType(ArrayTypeTree tree, Void p) { + if (locations) { + System.out.println("array type"); + } + return super.visitArrayType(tree, p); } - for (@SuppressWarnings("unused") Tree t : tree.getParameters()) { - System.out.println("method param"); + + @Override + public Void visitClass(ClassTree tree, Void p) { + if (shouldSkipDefs(tree)) { + // Not "return super.visitClass(classTree, p);" because that would recursively call + // visitors on subtrees; we want to skip the class entirely. + return null; + } + if (locations) { + System.out.println("class"); + if (tree.getExtendsClause() != null) { + System.out.println("class extends"); + } + for (@SuppressWarnings("unused") Tree t : tree.getImplementsClause()) { + System.out.println("class implements"); + } + } + return super.visitClass(tree, p); } - } - return super.visitMethod(tree, p); - } - @Override - public Void visitVariable(VariableTree tree, Void p) { - if (locations) { - System.out.println("variable"); - } - return super.visitVariable(tree, p); - } + @Override + public Void visitMethod(MethodTree tree, Void p) { + if (locations) { + System.out.println("method return"); + System.out.println("method receiver"); + for (@SuppressWarnings("unused") Tree t : tree.getThrows()) { + System.out.println("method throws"); + } + for (@SuppressWarnings("unused") Tree t : tree.getParameters()) { + System.out.println("method param"); + } + } + return super.visitMethod(tree, p); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - if (locations) { - for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { - System.out.println("method invocation type argument"); + @Override + public Void visitVariable(VariableTree tree, Void p) { + if (locations) { + System.out.println("variable"); + } + return super.visitVariable(tree, p); } - } - return super.visitMethodInvocation(tree, p); - } - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - if (locations) { - System.out.println("new class"); - for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { - System.out.println("new class type argument"); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + if (locations) { + for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { + System.out.println("method invocation type argument"); + } + } + return super.visitMethodInvocation(tree, p); } - } - return super.visitNewClass(tree, p); - } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - if (locations) { - System.out.println("new array"); - for (@SuppressWarnings("unused") Tree t : tree.getDimensions()) { - System.out.println("new array dimension"); + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + if (locations) { + System.out.println("new class"); + for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { + System.out.println("new class type argument"); + } + } + return super.visitNewClass(tree, p); } - } - return super.visitNewArray(tree, p); - } - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - if (locations) { - System.out.println("typecast"); - } - return super.visitTypeCast(tree, p); - } + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + if (locations) { + System.out.println("new array"); + for (@SuppressWarnings("unused") Tree t : tree.getDimensions()) { + System.out.println("new array dimension"); + } + } + return super.visitNewArray(tree, p); + } - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - if (locations) { - System.out.println("instanceof"); - } - return super.visitInstanceOf(tree, p); - } + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + if (locations) { + System.out.println("typecast"); + } + return super.visitTypeCast(tree, p); + } - @Override - public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { - if (locations) { - for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { - System.out.println("parameterized type"); + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + if (locations) { + System.out.println("instanceof"); + } + return super.visitInstanceOf(tree, p); } - } - return super.visitParameterizedType(tree, p); - } - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void p) { - if (locations) { - for (@SuppressWarnings("unused") Tree t : tree.getBounds()) { - System.out.println("type parameter bound"); + @Override + public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { + if (locations) { + for (@SuppressWarnings("unused") Tree t : tree.getTypeArguments()) { + System.out.println("parameterized type"); + } + } + return super.visitParameterizedType(tree, p); + } + + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void p) { + if (locations) { + for (@SuppressWarnings("unused") Tree t : tree.getBounds()) { + System.out.println("type parameter bound"); + } + } + return super.visitTypeParameter(tree, p); + } + + @Override + public Void visitWildcard(WildcardTree tree, Void p) { + if (locations) { + System.out.println("wildcard"); + } + return super.visitWildcard(tree, p); } - } - return super.visitTypeParameter(tree, p); } @Override - public Void visitWildcard(WildcardTree tree, Void p) { - if (locations) { - System.out.println("wildcard"); - } - return super.visitWildcard(tree, p); + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for this class."); } - } - - @Override - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for this class."); - } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java index ad3e5938e7c..5eed8083588 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java @@ -12,17 +12,20 @@ import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.TypeCastTree; import com.sun.tools.javac.util.Log; -import java.util.List; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.source.SourceVisitor; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.List; + +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + /** * An annotation processor for counting a few specific aspects about the size of Java code: * @@ -44,160 +47,160 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) public class JavaCodeStatistics extends SourceChecker { - /** The number of type parameter declarations and uses. */ - int generics = 0; - - /** The number of array accesses and dimensions in array creations. */ - int arrayAccesses = 0; - - /** The number of type casts. */ - int typecasts = 0; - - String[] warningKeys = { - "index", "lowerbound", "samelen", "searchindex", "substringindex", "upperbound" - }; - - /** - * The number of warning suppressions with at least one key that matches one of the Index Checker - * subcheckers. - */ - int numberOfIndexWarningSuppressions = 0; - - /** The SuppressWarnings.value field/element. */ - final ExecutableElement suppressWarningsValueElement = - TreeUtils.getMethod(SuppressWarnings.class, "value", 0, processingEnv); - - /** Creates a JavaCodeStatistics. */ - public JavaCodeStatistics() { - // This checker never issues any warnings, so don't warn about - // @SuppressWarnings("allcheckers:..."). - this.useAllcheckersPrefix = false; - } - - @Override - public void typeProcessingOver() { - Log log = getCompilerLog(); - if (log.nerrors != 0) { - System.out.printf("Not outputting statistics, because compilation issued an error.%n"); - } else { - System.out.printf("Found %d generic type uses.%n", generics); - System.out.printf("Found %d array accesses and creations.%n", arrayAccesses); - System.out.printf("Found %d typecasts.%n", typecasts); - System.out.printf( - "Found %d warning suppression annotations for the Index Checker.%n", - numberOfIndexWarningSuppressions); - } - super.typeProcessingOver(); - } + /** The number of type parameter declarations and uses. */ + int generics = 0; - @Override - protected SourceVisitor createSourceVisitor() { - return new Visitor(this); - } + /** The number of array accesses and dimensions in array creations. */ + int arrayAccesses = 0; - class Visitor extends SourceVisitor { + /** The number of type casts. */ + int typecasts = 0; - public Visitor(JavaCodeStatistics l) { - super(l); + String[] warningKeys = { + "index", "lowerbound", "samelen", "searchindex", "substringindex", "upperbound" + }; + + /** + * The number of warning suppressions with at least one key that matches one of the Index + * Checker subcheckers. + */ + int numberOfIndexWarningSuppressions = 0; + + /** The SuppressWarnings.value field/element. */ + final ExecutableElement suppressWarningsValueElement = + TreeUtils.getMethod(SuppressWarnings.class, "value", 0, processingEnv); + + /** Creates a JavaCodeStatistics. */ + public JavaCodeStatistics() { + // This checker never issues any warnings, so don't warn about + // @SuppressWarnings("allcheckers:..."). + this.useAllcheckersPrefix = false; } @Override - public Void visitAnnotation(AnnotationTree tree, Void aVoid) { - AnnotationMirror annotationMirror = TreeUtils.annotationFromAnnotationTree(tree); - if (AnnotationUtils.annotationName(annotationMirror) - .equals(SuppressWarnings.class.getCanonicalName())) { - List keys = - AnnotationUtils.getElementValueArray( - annotationMirror, suppressWarningsValueElement, String.class); - for (String foundKey : keys) { - for (String indexKey : warningKeys) { - if (foundKey.startsWith(indexKey)) { - numberOfIndexWarningSuppressions++; - return super.visitAnnotation(tree, aVoid); - } - } + public void typeProcessingOver() { + Log log = getCompilerLog(); + if (log.nerrors != 0) { + System.out.printf("Not outputting statistics, because compilation issued an error.%n"); + } else { + System.out.printf("Found %d generic type uses.%n", generics); + System.out.printf("Found %d array accesses and creations.%n", arrayAccesses); + System.out.printf("Found %d typecasts.%n", typecasts); + System.out.printf( + "Found %d warning suppression annotations for the Index Checker.%n", + numberOfIndexWarningSuppressions); } - } - return super.visitAnnotation(tree, aVoid); + super.typeProcessingOver(); } @Override - public Void visitAssert(AssertTree tree, Void aVoid) { - ExpressionTree detail = tree.getDetail(); - if (detail != null) { - String msg = detail.toString(); - for (String indexKey : warningKeys) { - String key = "@AssumeAssertion(" + indexKey; - if (msg.contains(key)) { - numberOfIndexWarningSuppressions++; + protected SourceVisitor createSourceVisitor() { + return new Visitor(this); + } + + class Visitor extends SourceVisitor { + + public Visitor(JavaCodeStatistics l) { + super(l); + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void aVoid) { + AnnotationMirror annotationMirror = TreeUtils.annotationFromAnnotationTree(tree); + if (AnnotationUtils.annotationName(annotationMirror) + .equals(SuppressWarnings.class.getCanonicalName())) { + List keys = + AnnotationUtils.getElementValueArray( + annotationMirror, suppressWarningsValueElement, String.class); + for (String foundKey : keys) { + for (String indexKey : warningKeys) { + if (foundKey.startsWith(indexKey)) { + numberOfIndexWarningSuppressions++; + return super.visitAnnotation(tree, aVoid); + } + } + } + } + return super.visitAnnotation(tree, aVoid); + } + + @Override + public Void visitAssert(AssertTree tree, Void aVoid) { + ExpressionTree detail = tree.getDetail(); + if (detail != null) { + String msg = detail.toString(); + for (String indexKey : warningKeys) { + String key = "@AssumeAssertion(" + indexKey; + if (msg.contains(key)) { + numberOfIndexWarningSuppressions++; + return super.visitAssert(tree, aVoid); + } + } + } return super.visitAssert(tree, aVoid); - } } - } - return super.visitAssert(tree, aVoid); - } - @Override - public Void visitClass(ClassTree tree, Void p) { - if (shouldSkipDefs(tree)) { - // Not "return super.visitClass(classTree, p);" because that would recursively call - // visitors on subtrees; we want to skip the class entirely. - return null; - } - generics += tree.getTypeParameters().size(); - return super.visitClass(tree, p); - } + @Override + public Void visitClass(ClassTree tree, Void p) { + if (shouldSkipDefs(tree)) { + // Not "return super.visitClass(classTree, p);" because that would recursively call + // visitors on subtrees; we want to skip the class entirely. + return null; + } + generics += tree.getTypeParameters().size(); + return super.visitClass(tree, p); + } - @Override - public Void visitNewArray(NewArrayTree tree, Void aVoid) { - arrayAccesses += tree.getDimensions().size(); + @Override + public Void visitNewArray(NewArrayTree tree, Void aVoid) { + arrayAccesses += tree.getDimensions().size(); - return super.visitNewArray(tree, aVoid); - } + return super.visitNewArray(tree, aVoid); + } - @Override - public Void visitNewClass(NewClassTree tree, Void aVoid) { - if (TreeUtils.isDiamondTree(tree)) { - generics++; - } - generics += tree.getTypeArguments().size(); - return super.visitNewClass(tree, aVoid); - } + @Override + public Void visitNewClass(NewClassTree tree, Void aVoid) { + if (TreeUtils.isDiamondTree(tree)) { + generics++; + } + generics += tree.getTypeArguments().size(); + return super.visitNewClass(tree, aVoid); + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void aVoid) { - generics += tree.getTypeArguments().size(); - return super.visitMethodInvocation(tree, aVoid); - } + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void aVoid) { + generics += tree.getTypeArguments().size(); + return super.visitMethodInvocation(tree, aVoid); + } - @Override - public Void visitMethod(MethodTree tree, Void aVoid) { - generics += tree.getTypeParameters().size(); - return super.visitMethod(tree, aVoid); - } + @Override + public Void visitMethod(MethodTree tree, Void aVoid) { + generics += tree.getTypeParameters().size(); + return super.visitMethod(tree, aVoid); + } - @Override - public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { - generics += tree.getTypeArguments().size(); - return super.visitParameterizedType(tree, p); - } + @Override + public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { + generics += tree.getTypeArguments().size(); + return super.visitParameterizedType(tree, p); + } - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void aVoid) { - arrayAccesses++; - return super.visitArrayAccess(tree, aVoid); + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void aVoid) { + arrayAccesses++; + return super.visitArrayAccess(tree, aVoid); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, Void aVoid) { + typecasts++; + return super.visitTypeCast(tree, aVoid); + } } @Override - public Void visitTypeCast(TypeCastTree tree, Void aVoid) { - typecasts++; - return super.visitTypeCast(tree, aVoid); + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for this class."); } - } - - @Override - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for this class."); - } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java b/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java index 96c8f04ee38..ccb1459e44c 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/DoNothingProcessor.java @@ -1,6 +1,7 @@ package org.checkerframework.common.util.debug; import java.util.Set; + import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -16,16 +17,16 @@ @SupportedAnnotationTypes("*") public class DoNothingProcessor extends AbstractProcessor { - /** Creates a DoNothingProcessor. */ - public DoNothingProcessor() {} + /** Creates a DoNothingProcessor. */ + public DoNothingProcessor() {} - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - return false; - } + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + return false; + } - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java index 0ff6f1a4ce2..e1ed6e3c36a 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java @@ -3,9 +3,26 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.source.SourceVisitor; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.javacutil.AbstractTypeProcessor; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.UserError; +import org.plumelib.reflection.Signatures; + import java.io.PrintStream; import java.lang.reflect.Constructor; import java.util.List; + import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; @@ -20,20 +37,6 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.AbstractElementVisitor8; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.source.SourceVisitor; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.javacutil.AbstractTypeProcessor; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.UserError; -import org.plumelib.reflection.Signatures; /** * Outputs the method signatures of a class with fully annotated types. @@ -71,290 +74,292 @@ @SupportedOptions("checker") public class SignaturePrinter extends AbstractTypeProcessor { - private SourceChecker checker; - - ///////// Initialization ///////////// - /** - * Initialization. - * - * @param env the ProcessingEnvironment - * @param checkerName the name of the checker - */ - private void init(ProcessingEnvironment env, @Nullable @BinaryName String checkerName) { - if (checkerName != null) { - try { - Class checkerClass = Class.forName(checkerName); - Constructor cons = checkerClass.getConstructor(); - checker = (SourceChecker) cons.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } else { - checker = - new SourceChecker() { - - @Override - protected SourceVisitor createSourceVisitor() { - return null; - } + private SourceChecker checker; - @Override - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for this class."); + ///////// Initialization ///////////// + /** + * Initialization. + * + * @param env the ProcessingEnvironment + * @param checkerName the name of the checker + */ + private void init(ProcessingEnvironment env, @Nullable @BinaryName String checkerName) { + if (checkerName != null) { + try { + Class checkerClass = Class.forName(checkerName); + Constructor cons = checkerClass.getConstructor(); + checker = (SourceChecker) cons.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); } - }; + } else { + checker = + new SourceChecker() { + + @Override + protected SourceVisitor createSourceVisitor() { + return null; + } + + @Override + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for this class."); + } + }; + } + checker.init(env); } - checker.init(env); - } - - @Override - public void typeProcessingStart() { - super.typeProcessingStart(); - String checkerName = processingEnv.getOptions().get("checker"); - if (!Signatures.isBinaryName(checkerName)) { - throw new UserError("Malformed checker name \"%s\"", checkerName); + + @Override + public void typeProcessingStart() { + super.typeProcessingStart(); + String checkerName = processingEnv.getOptions().get("checker"); + if (!Signatures.isBinaryName(checkerName)) { + throw new UserError("Malformed checker name \"%s\"", checkerName); + } + init(processingEnv, checkerName); } - init(processingEnv, checkerName); - } - - @Override - public void typeProcess(TypeElement element, TreePath p) { - // TODO: fix this mess - // checker.currentPath = p; - // CompilationUnitTree root = p != null ? p.getCompilationUnit() : null; - // ElementPrinter printer = new ElementPrinter(checker.createTypeFactory(), System.out); - // printer.visit(element); - } - - ////////// Printer ////////// - /** Element printer. */ - static class ElementPrinter extends AbstractElementVisitor8 { - /** String used for indentation. */ - private static final String INDENTION = " "; - - private final PrintStream out; - private String indent = ""; - private final AnnotatedTypeFactory atypeFactory; - - public ElementPrinter(AnnotatedTypeFactory atypeFactory, PrintStream out) { - this.atypeFactory = atypeFactory; - this.out = out; + + @Override + public void typeProcess(TypeElement element, TreePath p) { + // TODO: fix this mess + // checker.currentPath = p; + // CompilationUnitTree root = p != null ? p.getCompilationUnit() : null; + // ElementPrinter printer = new ElementPrinter(checker.createTypeFactory(), System.out); + // printer.visit(element); } - public void printTypeParams(List params) { - if (params.isEmpty()) { - return; - } + ////////// Printer ////////// + /** Element printer. */ + static class ElementPrinter extends AbstractElementVisitor8 { + /** String used for indentation. */ + private static final String INDENTION = " "; + + private final PrintStream out; + private String indent = ""; + private final AnnotatedTypeFactory atypeFactory; - out.print("<"); - boolean isntFirst = false; - for (AnnotatedTypeMirror param : params) { - if (isntFirst) { - out.print(", "); + public ElementPrinter(AnnotatedTypeFactory atypeFactory, PrintStream out) { + this.atypeFactory = atypeFactory; + this.out = out; } - isntFirst = true; - out.print(param); - } - out.print("> "); - } - public void printParameters(AnnotatedExecutableType type) { - ExecutableElement elem = type.getElement(); + public void printTypeParams(List params) { + if (params.isEmpty()) { + return; + } - out.print("("); - for (int i = 0; i < type.getParameterTypes().size(); ++i) { - if (i != 0) { - out.print(", "); + out.print("<"); + boolean isntFirst = false; + for (AnnotatedTypeMirror param : params) { + if (isntFirst) { + out.print(", "); + } + isntFirst = true; + out.print(param); + } + out.print("> "); + } + + public void printParameters(AnnotatedExecutableType type) { + ExecutableElement elem = type.getElement(); + + out.print("("); + for (int i = 0; i < type.getParameterTypes().size(); ++i) { + if (i != 0) { + out.print(", "); + } + printVariable( + type.getParameterTypes().get(i), + elem.getParameters().get(i).getSimpleName()); + } + out.print(")"); } - printVariable(type.getParameterTypes().get(i), elem.getParameters().get(i).getSimpleName()); - } - out.print(")"); - } - public void printThrows(AnnotatedExecutableType type) { - if (type.getThrownTypes().isEmpty()) { - return; - } + public void printThrows(AnnotatedExecutableType type) { + if (type.getThrownTypes().isEmpty()) { + return; + } - out.print(" throws "); + out.print(" throws "); - boolean isntFirst = false; - for (AnnotatedTypeMirror thrown : type.getThrownTypes()) { - if (isntFirst) { - out.print(", "); + boolean isntFirst = false; + for (AnnotatedTypeMirror thrown : type.getThrownTypes()) { + if (isntFirst) { + out.print(", "); + } + isntFirst = true; + out.print(thrown); + } } - isntFirst = true; - out.print(thrown); - } - } - public void printVariable(AnnotatedTypeMirror type, Name name, boolean isVarArg) { - out.print(type); - if (isVarArg) { - out.println("..."); - } - out.print(' '); - out.print(name); - } - - public void printVariable(AnnotatedTypeMirror type, Name name) { - printVariable(type, name, false); - } + public void printVariable(AnnotatedTypeMirror type, Name name, boolean isVarArg) { + out.print(type); + if (isVarArg) { + out.println("..."); + } + out.print(' '); + out.print(name); + } - public void printType(AnnotatedTypeMirror type) { - out.print(type); - out.print(' '); - } + public void printVariable(AnnotatedTypeMirror type, Name name) { + printVariable(type, name, false); + } - public void printName(CharSequence name) { - out.print(name); - } + public void printType(AnnotatedTypeMirror type) { + out.print(type); + out.print(' '); + } - @Override - public Void visitExecutable(ExecutableElement e, Void p) { - out.print(indent); - - AnnotatedExecutableType type = atypeFactory.getAnnotatedType(e); - printTypeParams(type.getTypeVariables()); - if (e.getKind() != ElementKind.CONSTRUCTOR) { - printType(type.getReturnType()); - } - printName(e.getSimpleName()); - printParameters(type); - printThrows(type); - out.println(';'); - - return null; - } + public void printName(CharSequence name) { + out.print(name); + } - @Override - public Void visitPackage(PackageElement e, Void p) { - throw new IllegalArgumentException("Cannot process packages"); - } + @Override + public Void visitExecutable(ExecutableElement e, Void p) { + out.print(indent); - private String typeIdentifier(TypeElement e) { - switch (e.getKind()) { - case INTERFACE: - return "interface"; - case CLASS: - return "class"; - case ANNOTATION_TYPE: - return "@interface"; - case ENUM: - return "enum"; - default: - if (ElementUtils.isRecordElement(e)) { - return "record"; - } - throw new IllegalArgumentException("Not a type element: " + e.getKind()); - } - } + AnnotatedExecutableType type = atypeFactory.getAnnotatedType(e); + printTypeParams(type.getTypeVariables()); + if (e.getKind() != ElementKind.CONSTRUCTOR) { + printType(type.getReturnType()); + } + printName(e.getSimpleName()); + printParameters(type); + printThrows(type); + out.println(';'); - @Override - public Void visitType(TypeElement e, Void p) { - String prevIndent = indent; + return null; + } - out.print(indent); - out.print(typeIdentifier(e)); - out.print(' '); - out.print(e.getSimpleName()); - out.print(' '); - AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(e); - printSupers(dt); - out.println("{"); + @Override + public Void visitPackage(PackageElement e, Void p) { + throw new IllegalArgumentException("Cannot process packages"); + } - indent += INDENTION; + private String typeIdentifier(TypeElement e) { + switch (e.getKind()) { + case INTERFACE: + return "interface"; + case CLASS: + return "class"; + case ANNOTATION_TYPE: + return "@interface"; + case ENUM: + return "enum"; + default: + if (ElementUtils.isRecordElement(e)) { + return "record"; + } + throw new IllegalArgumentException("Not a type element: " + e.getKind()); + } + } - for (Element enclosed : e.getEnclosedElements()) { - this.visit(enclosed); - } + @Override + public Void visitType(TypeElement e, Void p) { + String prevIndent = indent; - indent = prevIndent; - out.print(indent); - out.println("}"); + out.print(indent); + out.print(typeIdentifier(e)); + out.print(' '); + out.print(e.getSimpleName()); + out.print(' '); + AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(e); + printSupers(dt); + out.println("{"); - return null; - } + indent += INDENTION; - /** - * Print the supertypes. - * - * @param dt the type whos supertypes to print - */ - private void printSupers(AnnotatedDeclaredType dt) { - if (dt.directSupertypes().isEmpty()) { - return; - } + for (Element enclosed : e.getEnclosedElements()) { + this.visit(enclosed); + } - out.print("extends "); + indent = prevIndent; + out.print(indent); + out.println("}"); - boolean isntFirst = false; - for (AnnotatedDeclaredType st : dt.directSupertypes()) { - if (isntFirst) { - out.print(", "); + return null; } - isntFirst = true; - out.print(st); - } - out.print(' '); - } - @Override - public Void visitTypeParameter(TypeParameterElement e, Void p) { - throw new IllegalStateException("Shouldn't visit any type parameters"); - } + /** + * Print the supertypes. + * + * @param dt the type whos supertypes to print + */ + private void printSupers(AnnotatedDeclaredType dt) { + if (dt.directSupertypes().isEmpty()) { + return; + } - @Override - public Void visitVariable(VariableElement e, Void p) { - if (!e.getKind().isField()) { - throw new IllegalStateException("can only process fields, received " + e.getKind()); - } + out.print("extends "); - out.print(indent); - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(e); - this.printVariable(type, e.getSimpleName()); - out.println(';'); + boolean isntFirst = false; + for (AnnotatedDeclaredType st : dt.directSupertypes()) { + if (isntFirst) { + out.print(", "); + } + isntFirst = true; + out.print(st); + } + out.print(' '); + } - return null; - } - } + @Override + public Void visitTypeParameter(TypeParameterElement e, Void p) { + throw new IllegalStateException("Shouldn't visit any type parameters"); + } - public static void printUsage() { - System.out.println(" Usage: java SignaturePrinter [-Achecker=] classname"); - } + @Override + public Void visitVariable(VariableElement e, Void p) { + if (!e.getKind().isField()) { + throw new IllegalStateException("can only process fields, received " + e.getKind()); + } - private static final String CHECKER_ARG = "-Achecker="; + out.print(indent); + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(e); + this.printVariable(type, e.getSimpleName()); + out.println(';'); - public static void main(String[] args) { - if (!(args.length == 1 && !args[0].startsWith(CHECKER_ARG)) - && !(args.length == 2 && args[0].startsWith(CHECKER_ARG))) { - printUsage(); - return; + return null; + } } - // process arguments - String checkerName = null; - if (args[0].startsWith(CHECKER_ARG)) { - checkerName = args[0].substring(CHECKER_ARG.length()); - if (!Signatures.isBinaryName(checkerName)) { - throw new UserError("Bad checker name \"%s\"", checkerName); - } + public static void printUsage() { + System.out.println(" Usage: java SignaturePrinter [-Achecker=] classname"); } - // Setup compiler environment - Context context = new Context(); - JavacProcessingEnvironment env = JavacProcessingEnvironment.instance(context); - SignaturePrinter printer = new SignaturePrinter(); - printer.init(env, checkerName); - - String className = args[args.length - 1]; - TypeElement elem = env.getElementUtils().getTypeElement(className); - if (elem == null) { - System.err.println("Couldn't find class: " + className); - return; - } + private static final String CHECKER_ARG = "-Achecker="; + + public static void main(String[] args) { + if (!(args.length == 1 && !args[0].startsWith(CHECKER_ARG)) + && !(args.length == 2 && args[0].startsWith(CHECKER_ARG))) { + printUsage(); + return; + } - printer.typeProcess(elem, null); - } + // process arguments + String checkerName = null; + if (args[0].startsWith(CHECKER_ARG)) { + checkerName = args[0].substring(CHECKER_ARG.length()); + if (!Signatures.isBinaryName(checkerName)) { + throw new UserError("Bad checker name \"%s\"", checkerName); + } + } + + // Setup compiler environment + Context context = new Context(); + JavacProcessingEnvironment env = JavacProcessingEnvironment.instance(context); + SignaturePrinter printer = new SignaturePrinter(); + printer.init(env, checkerName); + + String className = args[args.length - 1]; + TypeElement elem = env.getElementUtils().getTypeElement(className); + if (elem == null) { + System.err.println("Couldn't find class: " + className); + return; + } + + printer.typeProcess(elem, null); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java b/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java index 1c892ed6dd5..6a5939872b9 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/TreeDebug.java @@ -10,7 +10,9 @@ import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; import com.sun.tools.javac.tree.JCTree.JCNewArray; + import java.util.Set; + import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -38,91 +40,91 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) public class TreeDebug extends AbstractProcessor { - protected Visitor createSourceVisitor(CompilationUnitTree root) { - return new Visitor(); - } + protected Visitor createSourceVisitor(CompilationUnitTree root) { + return new Visitor(); + } - private static final String LINE_SEPARATOR = System.lineSeparator(); + private static final String LINE_SEPARATOR = System.lineSeparator(); - public static class Visitor extends TreePathScanner { + public static class Visitor extends TreePathScanner { - private final StringBuilder buf; + private final StringBuilder buf; - public Visitor() { - buf = new StringBuilder(); - } + public Visitor() { + buf = new StringBuilder(); + } - @Override - public Void scan(Tree tree, Void p) { + @Override + public Void scan(Tree tree, Void p) { - // Indent according to subtrees. - if (getCurrentPath() != null) { - for (TreePath tp = getCurrentPath(); tp != null; tp = tp.getParentPath()) { - buf.append(" "); - } - } + // Indent according to subtrees. + if (getCurrentPath() != null) { + for (TreePath tp = getCurrentPath(); tp != null; tp = tp.getParentPath()) { + buf.append(" "); + } + } - // Add tree kind to the buffer. - if (tree == null) { - buf.append("null"); - } else { - buf.append(tree.getKind()); - } - buf.append(LINE_SEPARATOR); + // Add tree kind to the buffer. + if (tree == null) { + buf.append("null"); + } else { + buf.append(tree.getKind()); + } + buf.append(LINE_SEPARATOR); - // Visit subtrees. - super.scan(tree, p); + // Visit subtrees. + super.scan(tree, p); - // Display and clear the buffer. - System.out.print(buf.toString()); - buf.setLength(0); + // Display and clear the buffer. + System.out.print(buf.toString()); + buf.setLength(0); - return null; - } + return null; + } - /** - * Splices additional information for an AST node into the buffer. - * - * @param text additional information for the AST node - */ - private void insert(Object text) { - buf.insert(buf.length() - 1, " "); - buf.insert(buf.length() - 1, text); - } + /** + * Splices additional information for an AST node into the buffer. + * + * @param text additional information for the AST node + */ + private void insert(Object text) { + buf.insert(buf.length() - 1, " "); + buf.insert(buf.length() - 1, text); + } - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - insert(tree); - return super.visitIdentifier(tree, p); - } + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + insert(tree); + return super.visitIdentifier(tree, p); + } - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - insert(tree.getExpression() + "." + tree.getIdentifier()); - return super.visitMemberSelect(tree, p); - } + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + insert(tree.getExpression() + "." + tree.getIdentifier()); + return super.visitMemberSelect(tree, p); + } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - insert(((JCNewArray) tree).annotations); - insert("|"); - insert(((JCNewArray) tree).dimAnnotations); - return super.visitNewArray(tree, p); - } + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + insert(((JCNewArray) tree).annotations); + insert("|"); + insert(((JCNewArray) tree).dimAnnotations); + return super.visitNewArray(tree, p); + } - @Override - public Void visitLiteral(LiteralTree tree, Void p) { - insert(tree.getValue()); - return super.visitLiteral(tree, p); + @Override + public Void visitLiteral(LiteralTree tree, Void p) { + insert(tree.getValue()); + return super.visitLiteral(tree, p); + } } - } - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - for (TypeElement element : ElementFilter.typesIn(roundEnv.getRootElements())) { - TreePath path = Trees.instance(processingEnv).getPath(element); - new Visitor().scan(path, null); + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (TypeElement element : ElementFilter.typesIn(roundEnv.getRootElements())) { + TreePath path = Trees.instance(processingEnv).getPath(element); + new Visitor().scan(path, null); + } + return false; } - return false; - } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java index f39faf63dce..7a7f0a82e97 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java @@ -3,14 +3,17 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.Pretty; + +import org.checkerframework.javacutil.AbstractTypeProcessor; + import java.io.IOException; import java.io.StringWriter; import java.io.UncheckedIOException; + import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; -import org.checkerframework.javacutil.AbstractTypeProcessor; /** * A utility class for pretty-printing the AST of a program. @@ -39,25 +42,25 @@ @SupportedAnnotationTypes("*") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class TreePrinter extends AbstractTypeProcessor { - @Override - public void typeProcess(TypeElement element, TreePath tree) { - StringWriter out = new StringWriter(); - Pretty pretty = new Pretty(out, true); + @Override + public void typeProcess(TypeElement element, TreePath tree) { + StringWriter out = new StringWriter(); + Pretty pretty = new Pretty(out, true); - try { - pretty.printUnit((JCCompilationUnit) tree.getCompilationUnit(), null); - } catch (IOException e) { - throw new UncheckedIOException(e); + try { + pretty.printUnit((JCCompilationUnit) tree.getCompilationUnit(), null); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + System.out.println(out.toString()); } - System.out.println(out.toString()); - } - public static void main(String[] args) throws Exception { - String[] newArgs = new String[args.length + 3]; - newArgs[0] = "-processor"; - newArgs[1] = "org.checkerframework.common.util.debug.TreePrinter"; - newArgs[2] = "-proc:only"; - System.arraycopy(args, 0, newArgs, 3, args.length); - com.sun.tools.javac.Main.compile(newArgs); - } + public static void main(String[] args) throws Exception { + String[] newArgs = new String[args.length + 3]; + newArgs[0] = "-processor"; + newArgs[1] = "org.checkerframework.common.util.debug.TreePrinter"; + newArgs[2] = "-proc:only"; + System.arraycopy(args, 0, newArgs, 3, args.length); + com.sun.tools.javac.Main.compile(newArgs); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java index 6b973b68588..397ca1af285 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java @@ -5,14 +5,7 @@ import com.sun.source.tree.VariableTree; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; -import java.util.Collection; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -25,6 +18,16 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import java.util.Collection; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; + /** * A testing class that can be used to test {@link TypeElement}. In particular it tests that the * types read from classfiles are the same to the ones from Java files. @@ -48,222 +51,233 @@ */ public class TypeOutputtingChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new Visitor(this); - } - - /** Prints the types of the class and all of its enclosing fields, methods, and inner classes. */ - public static class Visitor extends BaseTypeVisitor> { - String currentClass; - - public Visitor(BaseTypeChecker checker) { - super(checker); - } - - // Print types of classes, methods, and fields @Override - public void processClassTree(ClassTree tree) { - TypeElement element = TreeUtils.elementFromDeclaration(tree); - currentClass = element.getSimpleName().toString(); - - AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); - System.out.println(tree.getSimpleName() + "\t" + type + "\t" + type.directSupertypes()); - - super.processClassTree(tree); + protected BaseTypeVisitor createSourceVisitor() { + return new Visitor(this); } - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement elem = TreeUtils.elementFromDeclaration(tree); - - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - System.out.println(currentClass + "." + elem + "\t\t" + type); - // Don't dig deeper - return null; - } - - @Override - public Void visitVariable(VariableTree tree, Void p) { - VariableElement elem = TreeUtils.elementFromDeclaration(tree); - if (elem.getKind().isField()) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); - System.out.println(currentClass + "." + elem + "\t\t" + type); - } - - // Don't dig deeper - return null; - } - } - - /** - * Main entry point. - * - * @param args command-line arguments - */ - @SuppressWarnings("signature:argument.type.incompatible") // user-supplied input, uncheckable - public static void main(String[] args) { - new TypeOutputtingChecker().run(args); - } - - /** - * Run the test. - * - * @param args command-line arguments - */ - public void run(@CanonicalName String[] args) { - ProcessingEnvironment env = JavacProcessingEnvironment.instance(new Context()); - Elements elements = env.getElementUtils(); - - // TODO: Instead of using a GeneralAnnotatedTypeFactory, just use standard javac classes - // to print explicit annotations. - AnnotatedTypeFactory atypeFactory = new GeneralAnnotatedTypeFactory(this); - - for (String className : args) { - TypeElement typeElt = elements.getTypeElement(className); - printClassType(typeElt, atypeFactory); - } - } - - /** Prints the types of the class and all of its enclosing fields, methods, and inner classes. */ - protected static void printClassType(TypeElement typeElt, AnnotatedTypeFactory atypeFactory) { - assert typeElt != null; - - String simpleName = typeElt.getSimpleName().toString(); - // Output class info - AnnotatedDeclaredType type = atypeFactory.fromElement(typeElt); - System.out.println(simpleName + "\t" + type + "\t" + type.directSupertypes()); - - // output fields and methods - for (Element enclosedElt : typeElt.getEnclosedElements()) { - if (enclosedElt instanceof TypeElement) { - printClassType((TypeElement) enclosedElt, atypeFactory); - } - if (!enclosedElt.getKind().isField() && !(enclosedElt instanceof ExecutableElement)) { - continue; - } - AnnotatedTypeMirror memberType = atypeFactory.fromElement(enclosedElt); - System.out.println(simpleName + "." + enclosedElt + "\t\t" + memberType); - } - } - - /** - * Stores any explicit annotation in AnnotatedTypeMirrors. It doesn't have a qualifier hierarchy, - * so it violates most of the specifications for AnnotatedTypeMirrors and AnnotatedTypeFactorys, - * which may cause crashes and other unexpected behaviors. - */ - public static class GeneralAnnotatedTypeFactory extends AnnotatedTypeFactory { - - public GeneralAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); + /** + * Prints the types of the class and all of its enclosing fields, methods, and inner classes. + */ + public static class Visitor extends BaseTypeVisitor> { + String currentClass; + + public Visitor(BaseTypeChecker checker) { + super(checker); + } + + // Print types of classes, methods, and fields + @Override + public void processClassTree(ClassTree tree) { + TypeElement element = TreeUtils.elementFromDeclaration(tree); + currentClass = element.getSimpleName().toString(); + + AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(tree); + System.out.println(tree.getSimpleName() + "\t" + type + "\t" + type.directSupertypes()); + + super.processClassTree(tree); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement elem = TreeUtils.elementFromDeclaration(tree); + + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + System.out.println(currentClass + "." + elem + "\t\t" + type); + // Don't dig deeper + return null; + } + + @Override + public Void visitVariable(VariableTree tree, Void p) { + VariableElement elem = TreeUtils.elementFromDeclaration(tree); + if (elem.getKind().isField()) { + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(tree); + System.out.println(currentClass + "." + elem + "\t\t" + type); + } + + // Don't dig deeper + return null; + } } - @Override - public void postProcessClassTree(ClassTree tree) { - // Do not store the qualifiers determined by this factory. This factory adds - // declaration annotations as type annotations, because TypeFromElement needs to read - // declaration annotations and this factory blindly supports all annotations. - // When storing those annotation to bytecode, the compiler chokes. See testcase - // tests/nullness/GeneralATFStore.java + /** + * Main entry point. + * + * @param args command-line arguments + */ + @SuppressWarnings("signature:argument.type.incompatible") // user-supplied input, uncheckable + public static void main(String[] args) { + new TypeOutputtingChecker().run(args); } - /** Return true to support any qualifier. No handling of aliases. */ - @Override - public boolean isSupportedQualifier(AnnotationMirror a) { - return true; + /** + * Run the test. + * + * @param args command-line arguments + */ + public void run(@CanonicalName String[] args) { + ProcessingEnvironment env = JavacProcessingEnvironment.instance(new Context()); + Elements elements = env.getElementUtils(); + + // TODO: Instead of using a GeneralAnnotatedTypeFactory, just use standard javac classes + // to print explicit annotations. + AnnotatedTypeFactory atypeFactory = new GeneralAnnotatedTypeFactory(this); + + for (String className : args) { + TypeElement typeElt = elements.getTypeElement(className); + printClassType(typeElt, atypeFactory); + } } - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new GeneralQualifierHierarchy(null); + /** + * Prints the types of the class and all of its enclosing fields, methods, and inner classes. + */ + protected static void printClassType(TypeElement typeElt, AnnotatedTypeFactory atypeFactory) { + assert typeElt != null; + + String simpleName = typeElt.getSimpleName().toString(); + // Output class info + AnnotatedDeclaredType type = atypeFactory.fromElement(typeElt); + System.out.println(simpleName + "\t" + type + "\t" + type.directSupertypes()); + + // output fields and methods + for (Element enclosedElt : typeElt.getEnclosedElements()) { + if (enclosedElt instanceof TypeElement) { + printClassType((TypeElement) enclosedElt, atypeFactory); + } + if (!enclosedElt.getKind().isField() && !(enclosedElt instanceof ExecutableElement)) { + continue; + } + AnnotatedTypeMirror memberType = atypeFactory.fromElement(enclosedElt); + System.out.println(simpleName + "." + enclosedElt + "\t\t" + memberType); + } } /** - * A very limited QualifierHierarchy that is used for access to qualifiers from different type - * systems. + * Stores any explicit annotation in AnnotatedTypeMirrors. It doesn't have a qualifier + * hierarchy, so it violates most of the specifications for AnnotatedTypeMirrors and + * AnnotatedTypeFactorys, which may cause crashes and other unexpected behaviors. */ - static class GeneralQualifierHierarchy extends QualifierHierarchy { - - /** - * Creates a new GeneralQualifierHierarchy. - * - * @param atypeFactory the associated type factory - */ - public GeneralQualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - // Always return true - @Override - public boolean isValid() { - return true; - } - - // Return the qualifier itself instead of the top. - @Override - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - return start; - } - - // Return the qualifier itself instead of the bottom. - @Override - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - return start; - } - - // Never find a corresponding qualifier. - @Override - public AnnotationMirror findAnnotationInSameHierarchy( - Collection annotations, AnnotationMirror annotationMirror) { - return null; - } - - // Not needed - raises error. - @Override - public AnnotationMirrorSet getTopAnnotations() { - throw new BugInCF("GeneralQualifierHierarchy:getTopAnnotations() shouldn't be called"); - } - - // Not needed - should raise error. Unfortunately, in inference we ask for bottom - // annotations. - // Return a dummy value that does no harm. - @Override - public AnnotationMirrorSet getBottomAnnotations() { - // throw new BugInCF("GeneralQualifierHierarchy.getBottomAnnotations() - // shouldn't be called"); - return new AnnotationMirrorSet(); - } - - // Not needed - raises error. - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called."); - } - - // Not needed - raises error. - @Override - public AnnotationMirror leastUpperBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - throw new BugInCF("GeneralQualifierHierarchy.leastUpperBound() shouldn't be called."); - } - - // Not needed - raises error. - @Override - public AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - throw new BugInCF("GeneralQualifierHierarchy.greatestLowerBound() shouldn't be called."); - } - - @Override - public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { - throw new BugInCF( - "GeneralQualifierHierarchy.getPolymorphicAnnotation() shouldn't be" + " called."); - } - - @Override - public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { - return false; - } + public static class GeneralAnnotatedTypeFactory extends AnnotatedTypeFactory { + + public GeneralAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } + + @Override + public void postProcessClassTree(ClassTree tree) { + // Do not store the qualifiers determined by this factory. This factory adds + // declaration annotations as type annotations, because TypeFromElement needs to read + // declaration annotations and this factory blindly supports all annotations. + // When storing those annotation to bytecode, the compiler chokes. See testcase + // tests/nullness/GeneralATFStore.java + } + + /** Return true to support any qualifier. No handling of aliases. */ + @Override + public boolean isSupportedQualifier(AnnotationMirror a) { + return true; + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new GeneralQualifierHierarchy(null); + } + + /** + * A very limited QualifierHierarchy that is used for access to qualifiers from different + * type systems. + */ + static class GeneralQualifierHierarchy extends QualifierHierarchy { + + /** + * Creates a new GeneralQualifierHierarchy. + * + * @param atypeFactory the associated type factory + */ + public GeneralQualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + // Always return true + @Override + public boolean isValid() { + return true; + } + + // Return the qualifier itself instead of the top. + @Override + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + return start; + } + + // Return the qualifier itself instead of the bottom. + @Override + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + return start; + } + + // Never find a corresponding qualifier. + @Override + public AnnotationMirror findAnnotationInSameHierarchy( + Collection annotations, + AnnotationMirror annotationMirror) { + return null; + } + + // Not needed - raises error. + @Override + public AnnotationMirrorSet getTopAnnotations() { + throw new BugInCF( + "GeneralQualifierHierarchy:getTopAnnotations() shouldn't be called"); + } + + // Not needed - should raise error. Unfortunately, in inference we ask for bottom + // annotations. + // Return a dummy value that does no harm. + @Override + public AnnotationMirrorSet getBottomAnnotations() { + // throw new BugInCF("GeneralQualifierHierarchy.getBottomAnnotations() + // shouldn't be called"); + return new AnnotationMirrorSet(); + } + + // Not needed - raises error. + @Override + public boolean isSubtypeQualifiers( + AnnotationMirror subAnno, AnnotationMirror superAnno) { + throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called."); + } + + // Not needed - raises error. + @Override + public AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + throw new BugInCF( + "GeneralQualifierHierarchy.leastUpperBound() shouldn't be called."); + } + + // Not needed - raises error. + @Override + public AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + throw new BugInCF( + "GeneralQualifierHierarchy.greatestLowerBound() shouldn't be called."); + } + + @Override + public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { + throw new BugInCF( + "GeneralQualifierHierarchy.getPolymorphicAnnotation() shouldn't be" + + " called."); + } + + @Override + public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { + return false; + } + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java index dfc3246d7c8..e48afbe8b02 100644 --- a/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java +++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java @@ -1,8 +1,9 @@ package org.checkerframework.common.util.report; -import javax.annotation.processing.SupportedOptions; import org.checkerframework.common.basetype.BaseTypeChecker; +import javax.annotation.processing.SupportedOptions; + /** * The Report Checker performs semantic searches over a program, for example, to find all methods * that override a specific method, all classes that inherit from a specific class, or all uses of @@ -35,6 +36,6 @@ @SupportedOptions({"reportTreeKinds", "reportModifiers"}) public class ReportChecker extends BaseTypeChecker { - /** Creates a ReportChecker. */ - public ReportChecker() {} + /** Creates a ReportChecker. */ + public ReportChecker() {} } diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java index c7f22c00832..d5cbea60357 100644 --- a/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java @@ -13,16 +13,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -41,270 +32,288 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + /** The visitor for the Report Checker. */ public class ReportVisitor extends BaseTypeVisitor { - /** The tree kinds that should be reported; may be null. */ - private final @Nullable EnumSet treeKinds; + /** The tree kinds that should be reported; may be null. */ + private final @Nullable EnumSet treeKinds; - /** The modifiers that should be reported; may be null. */ - private final @Nullable EnumSet modifiers; + /** The modifiers that should be reported; may be null. */ + private final @Nullable EnumSet modifiers; - public ReportVisitor(BaseTypeChecker checker) { - super(checker); + public ReportVisitor(BaseTypeChecker checker) { + super(checker); - EnumSet treeKindsTmp = EnumSet.noneOf(Tree.Kind.class); - for (String treeKind : checker.getStringsOption("reportTreeKinds", ',')) { - treeKindsTmp.add(Tree.Kind.valueOf(treeKind.toUpperCase(Locale.ROOT))); - } - treeKinds = treeKindsTmp.isEmpty() ? null : treeKindsTmp; + EnumSet treeKindsTmp = EnumSet.noneOf(Tree.Kind.class); + for (String treeKind : checker.getStringsOption("reportTreeKinds", ',')) { + treeKindsTmp.add(Tree.Kind.valueOf(treeKind.toUpperCase(Locale.ROOT))); + } + treeKinds = treeKindsTmp.isEmpty() ? null : treeKindsTmp; - EnumSet modifiersTmp = EnumSet.noneOf(Modifier.class); - for (String modifier : checker.getStringsOption("reportModifiers", ',')) { - modifiersTmp.add(Modifier.valueOf(modifier.toUpperCase(Locale.ROOT))); - } - modifiers = modifiersTmp.isEmpty() ? null : modifiersTmp; - } - - @SuppressWarnings("compilermessages") // These warnings are not translated. - @Override - public Void scan(Tree tree, Void p) { - if ((tree != null) && (treeKinds != null) && treeKinds.contains(tree.getKind())) { - // TODO: Also output the tree itself: TreeUtils.toStringTruncated(tree, 60) - checker.reportError(tree, "Tree.Kind." + tree.getKind()); + EnumSet modifiersTmp = EnumSet.noneOf(Modifier.class); + for (String modifier : checker.getStringsOption("reportModifiers", ',')) { + modifiersTmp.add(Modifier.valueOf(modifier.toUpperCase(Locale.ROOT))); + } + modifiers = modifiersTmp.isEmpty() ? null : modifiersTmp; } - return super.scan(tree, p); - } - - /** - * Check for uses of the {@link ReportUse} annotation. This method has to be called for every - * explicit or implicit use of a type, most cases are simply covered by the type validator. - * - * @param tree the tree for error reporting only - * @param member the element from which to start looking - */ - private void checkReportUse(Tree tree, Element member) { - Element loop = member; - while (loop != null) { - boolean report = this.atypeFactory.getDeclAnnotation(loop, ReportUse.class) != null; - if (report) { - checker.reportError( - tree, - "usage", - tree, - ElementUtils.getQualifiedName(loop), - loop.getKind(), - ElementUtils.getQualifiedName(member), - member.getKind()); - break; - } else { - if (loop.getKind() == ElementKind.PACKAGE) { - loop = ElementUtils.parentPackage((PackageElement) loop, elements); - continue; + + @SuppressWarnings("compilermessages") // These warnings are not translated. + @Override + public Void scan(Tree tree, Void p) { + if ((tree != null) && (treeKinds != null) && treeKinds.contains(tree.getKind())) { + // TODO: Also output the tree itself: TreeUtils.toStringTruncated(tree, 60) + checker.reportError(tree, "Tree.Kind." + tree.getKind()); } - } - // Package will always be the last iteration. - loop = loop.getEnclosingElement(); + return super.scan(tree, p); } - } - - /* Would we want this? Seems redundant, as all uses of the imported - * package should already be reported. - * Also, how do we get an element for the import? - public Void visitImport(ImportTree tree, Void p) { - checkReportUse(tree, elem); - } - */ - - @Override - public void processClassTree(ClassTree tree) { - TypeElement member = TreeUtils.elementFromDeclaration(tree); - boolean report = false; - // No need to check on the declaring class itself - // this.atypeFactory.getDeclAnnotation(member, ReportInherit.class) != null; - - // Check whether any superclass/interface had the ReportInherit annotation. - List suptypes = ElementUtils.getSuperTypes(member, elements); - for (TypeElement sup : suptypes) { - report = this.atypeFactory.getDeclAnnotation(sup, ReportInherit.class) != null; - if (report) { - checker.reportError(tree, "inherit", tree, ElementUtils.getQualifiedName(sup)); - } + + /** + * Check for uses of the {@link ReportUse} annotation. This method has to be called for every + * explicit or implicit use of a type, most cases are simply covered by the type validator. + * + * @param tree the tree for error reporting only + * @param member the element from which to start looking + */ + private void checkReportUse(Tree tree, Element member) { + Element loop = member; + while (loop != null) { + boolean report = this.atypeFactory.getDeclAnnotation(loop, ReportUse.class) != null; + if (report) { + checker.reportError( + tree, + "usage", + tree, + ElementUtils.getQualifiedName(loop), + loop.getKind(), + ElementUtils.getQualifiedName(member), + member.getKind()); + break; + } else { + if (loop.getKind() == ElementKind.PACKAGE) { + loop = ElementUtils.parentPackage((PackageElement) loop, elements); + continue; + } + } + // Package will always be the last iteration. + loop = loop.getEnclosingElement(); + } } - super.processClassTree(tree); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - ExecutableElement method = TreeUtils.elementFromDeclaration(tree); - boolean report = false; - - // Check all overridden methods. - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); - for (Map.Entry pair : overriddenMethods.entrySet()) { - // AnnotatedDeclaredType overriddenType = pair.getKey(); - ExecutableElement exe = pair.getValue(); - report = this.atypeFactory.getDeclAnnotation(exe, ReportOverride.class) != null; - if (report) { - // Set method to report the right method, if found. - method = exe; - break; - } + + /* Would we want this? Seems redundant, as all uses of the imported + * package should already be reported. + * Also, how do we get an element for the import? + public Void visitImport(ImportTree tree, Void p) { + checkReportUse(tree, elem); } + */ - if (report) { - checker.reportError(tree, "override", tree, ElementUtils.getQualifiedName(method)); + @Override + public void processClassTree(ClassTree tree) { + TypeElement member = TreeUtils.elementFromDeclaration(tree); + boolean report = false; + // No need to check on the declaring class itself + // this.atypeFactory.getDeclAnnotation(member, ReportInherit.class) != null; + + // Check whether any superclass/interface had the ReportInherit annotation. + List suptypes = ElementUtils.getSuperTypes(member, elements); + for (TypeElement sup : suptypes) { + report = this.atypeFactory.getDeclAnnotation(sup, ReportInherit.class) != null; + if (report) { + checker.reportError(tree, "inherit", tree, ElementUtils.getQualifiedName(sup)); + } + } + super.processClassTree(tree); } - return super.visitMethod(tree, p); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - ExecutableElement method = TreeUtils.elementFromUse(tree); - checkReportUse(tree, method); - boolean report = this.atypeFactory.getDeclAnnotation(method, ReportCall.class) != null; - - if (!report) { - // Find all methods that are overridden by the called method - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - // AnnotatedDeclaredType overriddenType = pair.getKey(); - ExecutableElement exe = pair.getValue(); - report = this.atypeFactory.getDeclAnnotation(exe, ReportCall.class) != null; + + @Override + public Void visitMethod(MethodTree tree, Void p) { + ExecutableElement method = TreeUtils.elementFromDeclaration(tree); + boolean report = false; + + // Check all overridden methods. + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + // AnnotatedDeclaredType overriddenType = pair.getKey(); + ExecutableElement exe = pair.getValue(); + report = this.atypeFactory.getDeclAnnotation(exe, ReportOverride.class) != null; + if (report) { + // Set method to report the right method, if found. + method = exe; + break; + } + } + if (report) { - // Always report the element that has the annotation. - // Alternative would be to always report the initial element. - method = exe; - break; + checker.reportError(tree, "override", tree, ElementUtils.getQualifiedName(method)); } - } + return super.visitMethod(tree, p); } - if (report) { - checker.reportError(tree, "methodcall", tree, ElementUtils.getQualifiedName(method)); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + ExecutableElement method = TreeUtils.elementFromUse(tree); + checkReportUse(tree, method); + boolean report = this.atypeFactory.getDeclAnnotation(method, ReportCall.class) != null; + + if (!report) { + // Find all methods that are overridden by the called method + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, method); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + // AnnotatedDeclaredType overriddenType = pair.getKey(); + ExecutableElement exe = pair.getValue(); + report = this.atypeFactory.getDeclAnnotation(exe, ReportCall.class) != null; + if (report) { + // Always report the element that has the annotation. + // Alternative would be to always report the initial element. + method = exe; + break; + } + } + } + + if (report) { + checker.reportError(tree, "methodcall", tree, ElementUtils.getQualifiedName(method)); + } + return super.visitMethodInvocation(tree, p); } - return super.visitMethodInvocation(tree, p); - } - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree); - checkReportUse(tree, member); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree); + checkReportUse(tree, member); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; - if (report) { - checker.reportError(tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); + if (report) { + checker.reportError( + tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); + } + return super.visitMemberSelect(tree, p); } - return super.visitMemberSelect(tree, p); - } - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportReadWrite.class) != null; - if (report) { - checker.reportError(tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); + if (report) { + checker.reportError( + tree, "fieldreadwrite", tree, ElementUtils.getQualifiedName(member)); + } + return super.visitIdentifier(tree, p); } - return super.visitIdentifier(tree, p); - } - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree.getVariable()); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportWrite.class) != null; + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree.getVariable()); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportWrite.class) != null; - if (report) { - checker.reportError(tree, "fieldwrite", tree, ElementUtils.getQualifiedName(member)); - } - return super.visitAssignment(tree, p); - } - - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - // TODO: should we introduce an annotation for this? - return super.visitArrayAccess(tree, p); - } - - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - Element member = TreeUtils.elementFromUse(tree); - boolean report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; - if (!report) { - // If the constructor is not annotated, check whether the class is. - member = member.getEnclosingElement(); - report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; - if (!report) { - // Check whether any superclass/interface had the ReportCreation annotation. - List suptypes = ElementUtils.getSuperTypes((TypeElement) member, elements); - for (TypeElement sup : suptypes) { - report = this.atypeFactory.getDeclAnnotation(sup, ReportCreation.class) != null; - if (report) { - // Set member to report the right member if found - member = sup; - break; - } + if (report) { + checker.reportError(tree, "fieldwrite", tree, ElementUtils.getQualifiedName(member)); } - } + return super.visitAssignment(tree, p); } - if (report) { - checker.reportError(tree, "creation", tree, ElementUtils.getQualifiedName(member)); + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + // TODO: should we introduce an annotation for this? + return super.visitArrayAccess(tree, p); } - return super.visitNewClass(tree, p); - } - - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - // TODO Should we report this if the array type is @ReportCreation? - return super.visitNewArray(tree, p); - } - - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - // TODO Is it worth adding a separate annotation for this? - return super.visitTypeCast(tree, p); - } - - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - // TODO Is it worth adding a separate annotation for this? - return super.visitInstanceOf(tree, p); - } - - @SuppressWarnings("compilermessages") // These warnings are not translated. - @Override - public Void visitModifiers(ModifiersTree tree, Void p) { - if (tree != null && modifiers != null) { - for (Modifier mod : tree.getFlags()) { - if (modifiers.contains(mod)) { - checker.reportError(tree, "Modifier." + mod); + + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + Element member = TreeUtils.elementFromUse(tree); + boolean report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; + if (!report) { + // If the constructor is not annotated, check whether the class is. + member = member.getEnclosingElement(); + report = this.atypeFactory.getDeclAnnotation(member, ReportCreation.class) != null; + if (!report) { + // Check whether any superclass/interface had the ReportCreation annotation. + List suptypes = + ElementUtils.getSuperTypes((TypeElement) member, elements); + for (TypeElement sup : suptypes) { + report = this.atypeFactory.getDeclAnnotation(sup, ReportCreation.class) != null; + if (report) { + // Set member to report the right member if found + member = sup; + break; + } + } + } + } + + if (report) { + checker.reportError(tree, "creation", tree, ElementUtils.getQualifiedName(member)); } - } + return super.visitNewClass(tree, p); + } + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + // TODO Should we report this if the array type is @ReportCreation? + return super.visitNewArray(tree, p); } - return super.visitModifiers(tree, p); - } - - @Override - protected BaseTypeValidator createTypeValidator() { - return new ReportTypeValidator(checker, this, atypeFactory); - } - - protected class ReportTypeValidator extends BaseTypeValidator { - public ReportTypeValidator( - BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); + + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + // TODO Is it worth adding a separate annotation for this? + return super.visitTypeCast(tree, p); } @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { - Element member = type.getUnderlyingType().asElement(); - checkReportUse(tree, member); + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + // TODO Is it worth adding a separate annotation for this? + return super.visitInstanceOf(tree, p); + } - return super.visitDeclared(type, tree); + @SuppressWarnings("compilermessages") // These warnings are not translated. + @Override + public Void visitModifiers(ModifiersTree tree, Void p) { + if (tree != null && modifiers != null) { + for (Modifier mod : tree.getFlags()) { + if (modifiers.contains(mod)) { + checker.reportError(tree, "Modifier." + mod); + } + } + } + return super.visitModifiers(tree, p); + } + + @Override + protected BaseTypeValidator createTypeValidator() { + return new ReportTypeValidator(checker, this, atypeFactory); + } + + protected class ReportTypeValidator extends BaseTypeValidator { + public ReportTypeValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + Element member = type.getUnderlyingType().asElement(); + checkReportUse(tree, member); + + return super.visitDeclared(type, tree); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java b/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java index ac80d9aa3f8..4d5c1f2ca0f 100644 --- a/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java +++ b/framework/src/main/java/org/checkerframework/common/value/JavaExpressionOptimizer.java @@ -1,8 +1,5 @@ package org.checkerframework.common.value; -import java.util.List; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; import org.checkerframework.dataflow.expression.FieldAccess; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.dataflow.expression.JavaExpressionConverter; @@ -11,6 +8,11 @@ import org.checkerframework.dataflow.expression.ValueLiteral; import org.checkerframework.framework.type.AnnotatedTypeFactory; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; + /** * Optimize the given JavaExpression. If the supplied factory is a {@code * ValueAnnotatedTypeFactory}, this implementation replaces any expression that the factory has an @@ -19,60 +21,62 @@ */ public class JavaExpressionOptimizer extends JavaExpressionConverter { - /** - * Annotated type factory. If it is a {@code ValueAnnotatedTypeFactory}, then more optimizations - * are possible. - */ - private final AnnotatedTypeFactory atypeFactory; + /** + * Annotated type factory. If it is a {@code ValueAnnotatedTypeFactory}, then more optimizations + * are possible. + */ + private final AnnotatedTypeFactory atypeFactory; - /** - * Creates a JavaExpressionOptimizer. - * - * @param atypeFactory an annotated type factory - */ - public JavaExpressionOptimizer(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** + * Creates a JavaExpressionOptimizer. + * + * @param atypeFactory an annotated type factory + */ + public JavaExpressionOptimizer(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - @Override - protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { - // Replace references to compile-time constant fields by the constant itself. - if (fieldAccessExpr.isFinal()) { - Object constant = fieldAccessExpr.getField().getConstantValue(); - if (constant != null && !(constant instanceof String)) { - return new ValueLiteral(fieldAccessExpr.getType(), constant); - } + @Override + protected JavaExpression visitFieldAccess(FieldAccess fieldAccessExpr, Void unused) { + // Replace references to compile-time constant fields by the constant itself. + if (fieldAccessExpr.isFinal()) { + Object constant = fieldAccessExpr.getField().getConstantValue(); + if (constant != null && !(constant instanceof String)) { + return new ValueLiteral(fieldAccessExpr.getType(), constant); + } + } + return super.visitFieldAccess(fieldAccessExpr, unused); } - return super.visitFieldAccess(fieldAccessExpr, unused); - } - @Override - protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { - if (atypeFactory instanceof ValueAnnotatedTypeFactory) { - Element element = localVarExpr.getElement(); - Long exactValue = - ValueCheckerUtils.getExactValue(element, (ValueAnnotatedTypeFactory) atypeFactory); - if (exactValue != null) { - return new ValueLiteral(localVarExpr.getType(), exactValue.intValue()); - } + @Override + protected JavaExpression visitLocalVariable(LocalVariable localVarExpr, Void unused) { + if (atypeFactory instanceof ValueAnnotatedTypeFactory) { + Element element = localVarExpr.getElement(); + Long exactValue = + ValueCheckerUtils.getExactValue( + element, (ValueAnnotatedTypeFactory) atypeFactory); + if (exactValue != null) { + return new ValueLiteral(localVarExpr.getType(), exactValue.intValue()); + } + } + return super.visitLocalVariable(localVarExpr, unused); } - return super.visitLocalVariable(localVarExpr, unused); - } - @Override - protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { - JavaExpression optReceiver = convert(methodCallExpr.getReceiver()); - List optArguments = convert(methodCallExpr.getArguments()); - // Length of string literal: convert it to an integer literal. - if (methodCallExpr.getElement().getSimpleName().contentEquals("length") - && optReceiver instanceof ValueLiteral) { - Object value = ((ValueLiteral) optReceiver).getValue(); - if (value instanceof String) { - return new ValueLiteral( - atypeFactory.types.getPrimitiveType(TypeKind.INT), ((String) value).length()); - } + @Override + protected JavaExpression visitMethodCall(MethodCall methodCallExpr, Void unused) { + JavaExpression optReceiver = convert(methodCallExpr.getReceiver()); + List optArguments = convert(methodCallExpr.getArguments()); + // Length of string literal: convert it to an integer literal. + if (methodCallExpr.getElement().getSimpleName().contentEquals("length") + && optReceiver instanceof ValueLiteral) { + Object value = ((ValueLiteral) optReceiver).getValue(); + if (value instanceof String) { + return new ValueLiteral( + atypeFactory.types.getPrimitiveType(TypeKind.INT), + ((String) value).length()); + } + } + return new MethodCall( + methodCallExpr.getType(), methodCallExpr.getElement(), optReceiver, optArguments); } - return new MethodCall( - methodCallExpr.getType(), methodCallExpr.getElement(), optReceiver, optArguments); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java b/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java index dd76ce607e5..1cef9b5690f 100644 --- a/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java +++ b/framework/src/main/java/org/checkerframework/common/value/RangeOrListOfValues.java @@ -1,8 +1,5 @@ package org.checkerframework.common.value; -import java.util.ArrayList; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.ArrayLenRange; import org.checkerframework.common.value.qual.IntVal; @@ -11,6 +8,11 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; +import java.util.ArrayList; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + /** * A mutable abstraction that can be either a range or a list of values that could come from an * {@link ArrayLen} or {@link IntVal}. This abstraction reduces the number of cases that {@link @@ -21,121 +23,121 @@ * to be used to reason about ArrayLen and ArrayLenRange values. */ class RangeOrListOfValues { - private Range range; - private List values; - private boolean isRange; + private Range range; + private List values; + private boolean isRange; - public RangeOrListOfValues(List values) { - this.values = new ArrayList<>(); - isRange = false; - addAll(values); - } + public RangeOrListOfValues(List values) { + this.values = new ArrayList<>(); + isRange = false; + addAll(values); + } - public RangeOrListOfValues(Range range) { - this.range = range; - isRange = true; - } + public RangeOrListOfValues(Range range) { + this.range = range; + isRange = true; + } - public void add(Range otherRange) { - if (isRange) { - range = range.union(otherRange); - } else { - convertToRange(); - add(otherRange); + public void add(Range otherRange) { + if (isRange) { + range = range.union(otherRange); + } else { + convertToRange(); + add(otherRange); + } } - } - /** - * If this is not a range, adds all members of newValues to the list. Otherwise, extends the range - * as appropriate based on the max and min of newValues. If adding newValues to a non-range would - * cause the list to become too large, converts this into a range. - * - *

          If reading from an {@link org.checkerframework.common.value.qual.IntRange} annotation, - * {@link #convertLongsToInts(List)} should be called before calling this method. - * - * @param newValues values to add - */ - public void addAll(List newValues) { - if (isRange) { - range = range.union(Range.create(newValues)); - } else { - for (Integer i : newValues) { - if (!values.contains(i)) { - values.add(i); + /** + * If this is not a range, adds all members of newValues to the list. Otherwise, extends the + * range as appropriate based on the max and min of newValues. If adding newValues to a + * non-range would cause the list to become too large, converts this into a range. + * + *

          If reading from an {@link org.checkerframework.common.value.qual.IntRange} annotation, + * {@link #convertLongsToInts(List)} should be called before calling this method. + * + * @param newValues values to add + */ + public void addAll(List newValues) { + if (isRange) { + range = range.union(Range.create(newValues)); + } else { + for (Integer i : newValues) { + if (!values.contains(i)) { + values.add(i); + } + } + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + convertToRange(); + } } - } - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - convertToRange(); - } } - } - /** - * Produces the most precise annotation that captures the information stored in this - * RangeOrListofValues. The result is either a {@link ArrayLen} or a {@link ArrayLenRange}. - * - * @param atypeFactory the type factory - * @return an annotation correspending to this RangeOrListofValues - */ - public AnnotationMirror createAnnotation(ValueAnnotatedTypeFactory atypeFactory) { - if (isRange) { - return atypeFactory.createArrayLenRangeAnnotation(range); - } else { - return atypeFactory.createArrayLenAnnotation(values); + /** + * Produces the most precise annotation that captures the information stored in this + * RangeOrListofValues. The result is either a {@link ArrayLen} or a {@link ArrayLenRange}. + * + * @param atypeFactory the type factory + * @return an annotation correspending to this RangeOrListofValues + */ + public AnnotationMirror createAnnotation(ValueAnnotatedTypeFactory atypeFactory) { + if (isRange) { + return atypeFactory.createArrayLenRangeAnnotation(range); + } else { + return atypeFactory.createArrayLenAnnotation(values); + } } - } - /** - * Converts a Long to an Integer by clipping it to the int range. - * - * @param l a Long integer - * @return the value clipped to the Integer range - */ - private static Integer convertLongToInt(Long l) { - if (l > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } else if (l < Integer.MIN_VALUE) { - return Integer.MIN_VALUE; - } else { - return l.intValue(); + /** + * Converts a Long to an Integer by clipping it to the int range. + * + * @param l a Long integer + * @return the value clipped to the Integer range + */ + private static Integer convertLongToInt(Long l) { + if (l > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else if (l < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } else { + return l.intValue(); + } } - } - /** - * To be called before addAll. Converts Longs to Integers by clipping them to the int range; meant - * to be used with ArrayLenRange (which only handles Ints). - * - * @param newValues a list of Long integers - * @return a list of Integers - */ - public static List convertLongsToInts(List newValues) { - return CollectionsPlume.mapList(RangeOrListOfValues::convertLongToInt, newValues); - } + /** + * To be called before addAll. Converts Longs to Integers by clipping them to the int range; + * meant to be used with ArrayLenRange (which only handles Ints). + * + * @param newValues a list of Long integers + * @return a list of Integers + */ + public static List convertLongsToInts(List newValues) { + return CollectionsPlume.mapList(RangeOrListOfValues::convertLongToInt, newValues); + } - /** - * Transforms this into a range. Fails if there are no values in the list. Has no effect if this - * is already a range. - */ - public void convertToRange() { - if (!isRange) { - isRange = true; - range = Range.create(values); - values = null; + /** + * Transforms this into a range. Fails if there are no values in the list. Has no effect if this + * is already a range. + */ + public void convertToRange() { + if (!isRange) { + isRange = true; + range = Range.create(values); + values = null; + } } - } - @Override - public String toString() { - if (isRange) { - return range.toString(); - } else { - if (values.isEmpty()) { - return "[]"; - } - String res = "["; - res += StringsPlume.join(", ", values); - res += "]"; - return res; + @Override + public String toString() { + if (isRange) { + return range.toString(); + } else { + if (values.isEmpty()) { + return "[]"; + } + String res = "["; + res += StringsPlume.join(", ", values); + res += "]"; + return res; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java b/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java index bd95213a6b9..3cde83c2690 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java @@ -3,6 +3,17 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; +import org.checkerframework.checker.signature.qual.ClassGetName; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; + import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -11,19 +22,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; + import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; -import org.checkerframework.checker.signature.qual.ClassGetName; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; // The use of reflection in ReflectiveEvaluator is troubling. // A static analysis such as the Checker Framework should always use compiler APIs, never @@ -40,357 +43,375 @@ */ public class ReflectiveEvaluator { - /** The checker that is using this ReflectiveEvaluator. */ - private final BaseTypeChecker checker; + /** The checker that is using this ReflectiveEvaluator. */ + private final BaseTypeChecker checker; - /** - * Whether to report warnings about problems with evaluation. Controlled by the -AreportEvalWarns - * command-line option. - */ - private final boolean reportWarnings; + /** + * Whether to report warnings about problems with evaluation. Controlled by the + * -AreportEvalWarns command-line option. + */ + private final boolean reportWarnings; - /** - * Create a new ReflectiveEvaluator. - * - * @param checker the BaseTypeChecker - * @param factory the annotated type factory - * @param reportWarnings if true, report warnings about problems with evaluation - */ - public ReflectiveEvaluator( - BaseTypeChecker checker, ValueAnnotatedTypeFactory factory, boolean reportWarnings) { - this.checker = checker; - this.reportWarnings = reportWarnings; - } - - /** - * Returns all possible values that the method may return, or null if the method could not be - * evaluated. - * - * @param allArgValues a list of lists where the first list corresponds to all possible values for - * the first argument. Pass null to indicate that the method has no arguments. - * @param receiverValues a list of possible receiver values. null indicates that the method has no - * receiver. - * @param tree location to report any errors - * @return all possible values that the method may return, or null if the method could not be - * evaluated - */ - public @Nullable List evaluateMethodCall( - @Nullable List> allArgValues, - @Nullable List receiverValues, - MethodInvocationTree tree) { - Method method = getMethodObject(tree); - if (method == null) { - return null; - } - - if (receiverValues == null) { - // Method does not have a receiver - // the first parameter of Method.invoke should be null - receiverValues = Collections.singletonList(null); + /** + * Create a new ReflectiveEvaluator. + * + * @param checker the BaseTypeChecker + * @param factory the annotated type factory + * @param reportWarnings if true, report warnings about problems with evaluation + */ + public ReflectiveEvaluator( + BaseTypeChecker checker, ValueAnnotatedTypeFactory factory, boolean reportWarnings) { + this.checker = checker; + this.reportWarnings = reportWarnings; } - List listOfArguments; - if (allArgValues == null) { - // Method does not have arguments - listOfArguments = Collections.singletonList(null); - } else { - // Find all possible argument sets - listOfArguments = cartesianProduct(allArgValues, allArgValues.size() - 1); - } - - if (method.isVarArgs()) { - int numberOfParameters = method.getParameterTypes().length; - listOfArguments = - CollectionsPlume.mapList( - (Object[] args) -> normalizeVararg(args, numberOfParameters), listOfArguments); - } + /** + * Returns all possible values that the method may return, or null if the method could not be + * evaluated. + * + * @param allArgValues a list of lists where the first list corresponds to all possible values + * for the first argument. Pass null to indicate that the method has no arguments. + * @param receiverValues a list of possible receiver values. null indicates that the method has + * no receiver. + * @param tree location to report any errors + * @return all possible values that the method may return, or null if the method could not be + * evaluated + */ + public @Nullable List evaluateMethodCall( + @Nullable List> allArgValues, + @Nullable List receiverValues, + MethodInvocationTree tree) { + Method method = getMethodObject(tree); + if (method == null) { + return null; + } - List results = new ArrayList<>(listOfArguments.size()); - for (Object[] arguments : listOfArguments) { - for (Object receiver : receiverValues) { - try { - results.add(method.invoke(receiver, arguments)); - } catch (InvocationTargetException e) { - if (reportWarnings) { - checker.reportWarning( - tree, "method.evaluation.exception", method, e.getTargetException().toString()); - } - // Method evaluation will always fail, so don't bother - // trying again - return null; - } catch (ExceptionInInitializerError e) { - if (reportWarnings) { - checker.reportWarning( - tree, "method.evaluation.exception", method, e.getCause().toString()); - } - return null; - } catch (IllegalArgumentException e) { - if (reportWarnings) { - String args = StringsPlume.join(", ", arguments); - checker.reportWarning( - tree, "method.evaluation.exception", method, e.getLocalizedMessage() + ": " + args); - } - return null; - } catch (Throwable e) { - // Catch any exception thrown because they shouldn't crash the type checker. - if (reportWarnings) { - checker.reportWarning(tree, "method.evaluation.failed", method); - } - return null; + if (receiverValues == null) { + // Method does not have a receiver + // the first parameter of Method.invoke should be null + receiverValues = Collections.singletonList(null); } - } - } - return results; - } - /** An empty Object array. */ - private static final Object[] emptyObjectArray = new Object[] {}; + List listOfArguments; + if (allArgValues == null) { + // Method does not have arguments + listOfArguments = Collections.singletonList(null); + } else { + // Find all possible argument sets + listOfArguments = cartesianProduct(allArgValues, allArgValues.size() - 1); + } - /** - * This method normalizes an array of arguments to a varargs method by changing the arguments - * associated with the varargs parameter into an array. - * - * @param arguments an array of arguments for {@code method}. The length is at least {@code - * numberOfParameters - 1}. - * @param numberOfParameters number of parameters of the vararg method - * @return the length of the array is exactly {@code numberOfParameters} - */ - private Object[] normalizeVararg(Object[] arguments, int numberOfParameters) { + if (method.isVarArgs()) { + int numberOfParameters = method.getParameterTypes().length; + listOfArguments = + CollectionsPlume.mapList( + (Object[] args) -> normalizeVararg(args, numberOfParameters), + listOfArguments); + } - if (arguments == null) { - // null means no arguments. For varargs no arguments is an empty array. - arguments = emptyObjectArray; - } - Object[] newArgs = new Object[numberOfParameters]; - Object[] varArgsArray; - int numOfVarArgs = arguments.length - numberOfParameters + 1; - if (numOfVarArgs > 0) { - System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); - varArgsArray = new Object[numOfVarArgs]; - System.arraycopy(arguments, numberOfParameters - 1, varArgsArray, 0, numOfVarArgs); - } else { - System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); - varArgsArray = emptyObjectArray; + List results = new ArrayList<>(listOfArguments.size()); + for (Object[] arguments : listOfArguments) { + for (Object receiver : receiverValues) { + try { + results.add(method.invoke(receiver, arguments)); + } catch (InvocationTargetException e) { + if (reportWarnings) { + checker.reportWarning( + tree, + "method.evaluation.exception", + method, + e.getTargetException().toString()); + } + // Method evaluation will always fail, so don't bother + // trying again + return null; + } catch (ExceptionInInitializerError e) { + if (reportWarnings) { + checker.reportWarning( + tree, + "method.evaluation.exception", + method, + e.getCause().toString()); + } + return null; + } catch (IllegalArgumentException e) { + if (reportWarnings) { + String args = StringsPlume.join(", ", arguments); + checker.reportWarning( + tree, + "method.evaluation.exception", + method, + e.getLocalizedMessage() + ": " + args); + } + return null; + } catch (Throwable e) { + // Catch any exception thrown because they shouldn't crash the type checker. + if (reportWarnings) { + checker.reportWarning(tree, "method.evaluation.failed", method); + } + return null; + } + } + } + return results; } - newArgs[numberOfParameters - 1] = varArgsArray; - return newArgs; - } - /** - * Method for reflectively obtaining a method object so it can (potentially) be statically - * executed by the checker for constant propagation. - * - * @param tree a method invocation tree - * @return the Method object corresponding to the method invocation tree - */ - private @Nullable Method getMethodObject(MethodInvocationTree tree) { - ExecutableElement ele = TreeUtils.elementFromUse(tree); - List> paramClasses = null; - try { - @CanonicalNameOrEmpty String className = - TypesUtils.getQualifiedName((DeclaredType) ele.getEnclosingElement().asType()); - paramClasses = getParameterClasses(ele); - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Class.toString - Class clazz = Class.forName(className.toString()); - Method method = - clazz.getMethod(ele.getSimpleName().toString(), paramClasses.toArray(new Class[0])); - @SuppressWarnings("deprecation") // TODO: find alternative - boolean acc = method.isAccessible(); - if (!acc) { - method.setAccessible(true); - } - return method; - } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { - if (reportWarnings) { - checker.reportWarning(tree, "class.find.failed", ele.getEnclosingElement()); - } - return null; + /** An empty Object array. */ + private static final Object[] emptyObjectArray = new Object[] {}; - } catch (Throwable e) { - // The class we attempted to getMethod from inside the call to getMethodObject. - Element classElem = ele.getEnclosingElement(); + /** + * This method normalizes an array of arguments to a varargs method by changing the arguments + * associated with the varargs parameter into an array. + * + * @param arguments an array of arguments for {@code method}. The length is at least {@code + * numberOfParameters - 1}. + * @param numberOfParameters number of parameters of the vararg method + * @return the length of the array is exactly {@code numberOfParameters} + */ + private Object[] normalizeVararg(Object[] arguments, int numberOfParameters) { - if (classElem == null) { - if (reportWarnings) { - checker.reportWarning(tree, "method.find.failed", ele.getSimpleName(), paramClasses); + if (arguments == null) { + // null means no arguments. For varargs no arguments is an empty array. + arguments = emptyObjectArray; } - } else { - if (reportWarnings) { - checker.reportWarning( - tree, "method.find.failed.in.class", ele.getSimpleName(), paramClasses, classElem); + Object[] newArgs = new Object[numberOfParameters]; + Object[] varArgsArray; + int numOfVarArgs = arguments.length - numberOfParameters + 1; + if (numOfVarArgs > 0) { + System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); + varArgsArray = new Object[numOfVarArgs]; + System.arraycopy(arguments, numberOfParameters - 1, varArgsArray, 0, numOfVarArgs); + } else { + System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); + varArgsArray = emptyObjectArray; } - } - return null; + newArgs[numberOfParameters - 1] = varArgsArray; + return newArgs; } - } - /** - * Returns the classes of the given method's formal parameters. - * - * @param ele a method or constructor - * @return the classes of the given method's formal parameters - * @throws ClassNotFoundException if the class cannot be found - */ - private List> getParameterClasses(ExecutableElement ele) throws ClassNotFoundException { - return CollectionsPlume.mapList( - (Element e) -> TypesUtils.getClassFromType(ElementUtils.getType(e)), ele.getParameters()); - } + /** + * Method for reflectively obtaining a method object so it can (potentially) be statically + * executed by the checker for constant propagation. + * + * @param tree a method invocation tree + * @return the Method object corresponding to the method invocation tree + */ + private @Nullable Method getMethodObject(MethodInvocationTree tree) { + ExecutableElement ele = TreeUtils.elementFromUse(tree); + List> paramClasses = null; + try { + @CanonicalNameOrEmpty String className = + TypesUtils.getQualifiedName((DeclaredType) ele.getEnclosingElement().asType()); + paramClasses = getParameterClasses(ele); + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Class.toString + Class clazz = Class.forName(className.toString()); + Method method = + clazz.getMethod( + ele.getSimpleName().toString(), paramClasses.toArray(new Class[0])); + @SuppressWarnings("deprecation") // TODO: find alternative + boolean acc = method.isAccessible(); + if (!acc) { + method.setAccessible(true); + } + return method; + } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { + if (reportWarnings) { + checker.reportWarning(tree, "class.find.failed", ele.getEnclosingElement()); + } + return null; - /** - * Returns all combinations of the elements of the given lists. - * - * @param allArgValues the lists whose cartesian product to form - * @param whichArg pass {@code allArgValues.size() - 1} - * @return all combinations of the elements of the given lists - */ - private List cartesianProduct(List> allArgValues, int whichArg) { - List argValues = allArgValues.get(whichArg); - List tuples = new ArrayList<>(argValues.size()); + } catch (Throwable e) { + // The class we attempted to getMethod from inside the call to getMethodObject. + Element classElem = ele.getEnclosingElement(); - for (Object value : argValues) { - if (whichArg == 0) { - Object[] objects = new Object[allArgValues.size()]; - objects[0] = value; - tuples.add(objects); - } else { - List lastTuples = cartesianProduct(allArgValues, whichArg - 1); - List copies = copy(lastTuples); - for (Object[] copy : copies) { - copy[whichArg] = value; + if (classElem == null) { + if (reportWarnings) { + checker.reportWarning( + tree, "method.find.failed", ele.getSimpleName(), paramClasses); + } + } else { + if (reportWarnings) { + checker.reportWarning( + tree, + "method.find.failed.in.class", + ele.getSimpleName(), + paramClasses, + classElem); + } + } + return null; } - tuples.addAll(copies); - } } - return tuples; - } - /** - * Returns a depth-2 copy of the given list. In the returned value, the list and the arrays in it - * are new, but the elements of the arrays are shared with the argument. - * - * @param lastTuples a list of arrays - * @return a depth-2 copy of the given list - */ - private List copy(List lastTuples) { - return CollectionsPlume.mapList( - (Object[] list) -> Arrays.copyOf(list, list.length), lastTuples); - } + /** + * Returns the classes of the given method's formal parameters. + * + * @param ele a method or constructor + * @return the classes of the given method's formal parameters + * @throws ClassNotFoundException if the class cannot be found + */ + private List> getParameterClasses(ExecutableElement ele) + throws ClassNotFoundException { + return CollectionsPlume.mapList( + (Element e) -> TypesUtils.getClassFromType(ElementUtils.getType(e)), + ele.getParameters()); + } - /** - * Return the value of a static field access. Return null if accessing the field reflectively - * fails. - * - * @param classname the class containing the field - * @param fieldName the name of the field - * @param tree the static field access in the program. It is a MemberSelectTree or an - * IdentifierTree and is used for diagnostics. - * @return the value of the static field access, or null if it cannot be determined - */ - public @Nullable Object evaluateStaticFieldAccess( - @ClassGetName String classname, String fieldName, ExpressionTree tree) { - try { - Class recClass = Class.forName(classname); - Field field = recClass.getField(fieldName); - return field.get(recClass); + /** + * Returns all combinations of the elements of the given lists. + * + * @param allArgValues the lists whose cartesian product to form + * @param whichArg pass {@code allArgValues.size() - 1} + * @return all combinations of the elements of the given lists + */ + private List cartesianProduct(List> allArgValues, int whichArg) { + List argValues = allArgValues.get(whichArg); + List tuples = new ArrayList<>(argValues.size()); - } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { - if (reportWarnings) { - checker.reportWarning( - tree, "class.find.failed", classname, e.getClass() + ": " + e.getMessage()); - } - return null; - } catch (Throwable e) { - // Catch all exceptions so that the checker doesn't crash. - if (reportWarnings) { - checker.reportWarning( - tree, - "field.access.failed", - fieldName, - classname, - e.getClass() + ": " + e.getMessage()); - } - return null; + for (Object value : argValues) { + if (whichArg == 0) { + Object[] objects = new Object[allArgValues.size()]; + objects[0] = value; + tuples.add(objects); + } else { + List lastTuples = cartesianProduct(allArgValues, whichArg - 1); + List copies = copy(lastTuples); + for (Object[] copy : copies) { + copy[whichArg] = value; + } + tuples.addAll(copies); + } + } + return tuples; } - } - public @Nullable List evaluteConstructorCall( - List> argValues, NewClassTree tree, TypeMirror typeToCreate) { - Constructor constructor; - try { - // get the constructor - constructor = getConstructorObject(tree, typeToCreate); - } catch (Throwable e) { - // Catch all exception so that the checker doesn't crash - if (reportWarnings) { - checker.reportWarning(tree, "constructor.invocation.failed"); - } - return null; - } - if (constructor == null) { - return null; + /** + * Returns a depth-2 copy of the given list. In the returned value, the list and the arrays in + * it are new, but the elements of the arrays are shared with the argument. + * + * @param lastTuples a list of arrays + * @return a depth-2 copy of the given list + */ + private List copy(List lastTuples) { + return CollectionsPlume.mapList( + (Object[] list) -> Arrays.copyOf(list, list.length), lastTuples); } - List listOfArguments; - if (argValues == null) { - // Method does not have arguments - listOfArguments = Collections.singletonList(null); - } else { - // Find all possible argument sets - listOfArguments = cartesianProduct(argValues, argValues.size() - 1); + /** + * Return the value of a static field access. Return null if accessing the field reflectively + * fails. + * + * @param classname the class containing the field + * @param fieldName the name of the field + * @param tree the static field access in the program. It is a MemberSelectTree or an + * IdentifierTree and is used for diagnostics. + * @return the value of the static field access, or null if it cannot be determined + */ + public @Nullable Object evaluateStaticFieldAccess( + @ClassGetName String classname, String fieldName, ExpressionTree tree) { + try { + Class recClass = Class.forName(classname); + Field field = recClass.getField(fieldName); + return field.get(recClass); + + } catch (ClassNotFoundException | UnsupportedClassVersionError | NoClassDefFoundError e) { + if (reportWarnings) { + checker.reportWarning( + tree, "class.find.failed", classname, e.getClass() + ": " + e.getMessage()); + } + return null; + } catch (Throwable e) { + // Catch all exceptions so that the checker doesn't crash. + if (reportWarnings) { + checker.reportWarning( + tree, + "field.access.failed", + fieldName, + classname, + e.getClass() + ": " + e.getMessage()); + } + return null; + } } - List results = new ArrayList<>(listOfArguments.size()); - for (Object[] arguments : listOfArguments) { - try { - results.add(constructor.newInstance(arguments)); - } catch (Throwable e) { - if (reportWarnings) { - checker.reportWarning( - tree, - "constructor.evaluation.failed", - typeToCreate, - StringsPlume.join(", ", arguments)); + public @Nullable List evaluteConstructorCall( + List> argValues, NewClassTree tree, TypeMirror typeToCreate) { + Constructor constructor; + try { + // get the constructor + constructor = getConstructorObject(tree, typeToCreate); + } catch (Throwable e) { + // Catch all exception so that the checker doesn't crash + if (reportWarnings) { + checker.reportWarning(tree, "constructor.invocation.failed"); + } + return null; + } + if (constructor == null) { + return null; } - return null; - } + + List listOfArguments; + if (argValues == null) { + // Method does not have arguments + listOfArguments = Collections.singletonList(null); + } else { + // Find all possible argument sets + listOfArguments = cartesianProduct(argValues, argValues.size() - 1); + } + + List results = new ArrayList<>(listOfArguments.size()); + for (Object[] arguments : listOfArguments) { + try { + results.add(constructor.newInstance(arguments)); + } catch (Throwable e) { + if (reportWarnings) { + checker.reportWarning( + tree, + "constructor.evaluation.failed", + typeToCreate, + StringsPlume.join(", ", arguments)); + } + return null; + } + } + return results; } - return results; - } - private Constructor getConstructorObject(NewClassTree tree, TypeMirror typeToCreate) - throws ClassNotFoundException, NoSuchMethodException { - ExecutableElement ele = TreeUtils.elementFromUse(tree); - List> paramClasses = getParameterClasses(ele); - Class recClass = boxPrimitives(TypesUtils.getClassFromType(typeToCreate)); - Constructor constructor = recClass.getConstructor(paramClasses.toArray(new Class[0])); - return constructor; - } + private Constructor getConstructorObject(NewClassTree tree, TypeMirror typeToCreate) + throws ClassNotFoundException, NoSuchMethodException { + ExecutableElement ele = TreeUtils.elementFromUse(tree); + List> paramClasses = getParameterClasses(ele); + Class recClass = boxPrimitives(TypesUtils.getClassFromType(typeToCreate)); + Constructor constructor = recClass.getConstructor(paramClasses.toArray(new Class[0])); + return constructor; + } - /** - * Returns the boxed primitive type if the passed type is an (unboxed) primitive. Otherwise it - * returns the passed type. - * - * @param type a type to box or to return unchanged - * @return a boxed primitive type, if the argument was primitive; otherwise the argument - */ - private static Class boxPrimitives(Class type) { - if (type == byte.class) { - return Byte.class; - } else if (type == short.class) { - return Short.class; - } else if (type == int.class) { - return Integer.class; - } else if (type == long.class) { - return Long.class; - } else if (type == float.class) { - return Float.class; - } else if (type == double.class) { - return Double.class; - } else if (type == char.class) { - return Character.class; - } else if (type == boolean.class) { - return Boolean.class; + /** + * Returns the boxed primitive type if the passed type is an (unboxed) primitive. Otherwise it + * returns the passed type. + * + * @param type a type to box or to return unchanged + * @return a boxed primitive type, if the argument was primitive; otherwise the argument + */ + private static Class boxPrimitives(Class type) { + if (type == byte.class) { + return Byte.class; + } else if (type == short.class) { + return Short.class; + } else if (type == int.class) { + return Integer.class; + } else if (type == long.class) { + return Long.class; + } else if (type == float.class) { + return Float.class; + } else if (type == double.class) { + return Double.class; + } else if (type == char.class) { + return Character.class; + } else if (type == boolean.class) { + return Boolean.class; + } + return type; } - return type; - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index 51e307efb82..5974901ac4c 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -4,22 +4,7 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.regex.Pattern; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.checker.regex.qual.Regex; @@ -78,1654 +63,1704 @@ import org.plumelib.util.ArraySet; import org.plumelib.util.CollectionsPlume; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** AnnotatedTypeFactory for the Value type system. */ public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** Fully-qualified class name of {@link UnknownVal}. */ - public static final String UNKNOWN_NAME = "org.checkerframework.common.value.qual.UnknownVal"; + /** Fully-qualified class name of {@link UnknownVal}. */ + public static final String UNKNOWN_NAME = "org.checkerframework.common.value.qual.UnknownVal"; + + /** Fully-qualified class name of {@link BottomVal}. */ + public static final String BOTTOMVAL_NAME = "org.checkerframework.common.value.qual.BottomVal"; - /** Fully-qualified class name of {@link BottomVal}. */ - public static final String BOTTOMVAL_NAME = "org.checkerframework.common.value.qual.BottomVal"; + /** Fully-qualified class name of {@link PolyValue}. */ + public static final String POLY_NAME = "org.checkerframework.common.value.qual.PolyValue"; - /** Fully-qualified class name of {@link PolyValue}. */ - public static final String POLY_NAME = "org.checkerframework.common.value.qual.PolyValue"; + /** Fully-qualified class name of {@link ArrayLen}. */ + public static final String ARRAYLEN_NAME = "org.checkerframework.common.value.qual.ArrayLen"; - /** Fully-qualified class name of {@link ArrayLen}. */ - public static final String ARRAYLEN_NAME = "org.checkerframework.common.value.qual.ArrayLen"; + /** Fully-qualified class name of {@link BoolVal}. */ + public static final String BOOLVAL_NAME = "org.checkerframework.common.value.qual.BoolVal"; - /** Fully-qualified class name of {@link BoolVal}. */ - public static final String BOOLVAL_NAME = "org.checkerframework.common.value.qual.BoolVal"; + /** Fully-qualified class name of {@link DoubleVal}. */ + public static final String DOUBLEVAL_NAME = "org.checkerframework.common.value.qual.DoubleVal"; - /** Fully-qualified class name of {@link DoubleVal}. */ - public static final String DOUBLEVAL_NAME = "org.checkerframework.common.value.qual.DoubleVal"; + /** Fully-qualified class name of {@link IntVal}. */ + public static final String INTVAL_NAME = "org.checkerframework.common.value.qual.IntVal"; - /** Fully-qualified class name of {@link IntVal}. */ - public static final String INTVAL_NAME = "org.checkerframework.common.value.qual.IntVal"; + /** Fully-qualified class name of {@link StringVal}. */ + public static final String STRINGVAL_NAME = "org.checkerframework.common.value.qual.StringVal"; - /** Fully-qualified class name of {@link StringVal}. */ - public static final String STRINGVAL_NAME = "org.checkerframework.common.value.qual.StringVal"; + /** Fully-qualified class name of {@link ArrayLenRange}. */ + public static final String ARRAYLENRANGE_NAME = + "org.checkerframework.common.value.qual.ArrayLenRange"; - /** Fully-qualified class name of {@link ArrayLenRange}. */ - public static final String ARRAYLENRANGE_NAME = - "org.checkerframework.common.value.qual.ArrayLenRange"; + /** Fully-qualified class name of {@link IntRange}. */ + public static final String INTRANGE_NAME = "org.checkerframework.common.value.qual.IntRange"; - /** Fully-qualified class name of {@link IntRange}. */ - public static final String INTRANGE_NAME = "org.checkerframework.common.value.qual.IntRange"; + /** Fully-qualified class name of {@link IntRangeFromGTENegativeOne}. */ + public static final String INTRANGE_FROMGTENEGONE_NAME = + "org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne"; - /** Fully-qualified class name of {@link IntRangeFromGTENegativeOne}. */ - public static final String INTRANGE_FROMGTENEGONE_NAME = - "org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne"; + /** Fully-qualified class name of {@link IntRangeFromNonNegative}. */ + public static final String INTRANGE_FROMNONNEG_NAME = + "org.checkerframework.common.value.qual.IntRangeFromNonNegative"; - /** Fully-qualified class name of {@link IntRangeFromNonNegative}. */ - public static final String INTRANGE_FROMNONNEG_NAME = - "org.checkerframework.common.value.qual.IntRangeFromNonNegative"; + /** Fully-qualified class name of {@link IntRangeFromPositive}. */ + public static final String INTRANGE_FROMPOS_NAME = + "org.checkerframework.common.value.qual.IntRangeFromPositive"; - /** Fully-qualified class name of {@link IntRangeFromPositive}. */ - public static final String INTRANGE_FROMPOS_NAME = - "org.checkerframework.common.value.qual.IntRangeFromPositive"; + /** Fully-qualified class name of {@link MinLen}. */ + public static final String MINLEN_NAME = "org.checkerframework.common.value.qual.MinLen"; - /** Fully-qualified class name of {@link MinLen}. */ - public static final String MINLEN_NAME = "org.checkerframework.common.value.qual.MinLen"; + /** Fully-qualified class name of {@link MatchesRegex}. */ + public static final String MATCHES_REGEX_NAME = + "org.checkerframework.common.value.qual.MatchesRegex"; - /** Fully-qualified class name of {@link MatchesRegex}. */ - public static final String MATCHES_REGEX_NAME = - "org.checkerframework.common.value.qual.MatchesRegex"; + /** Fully-qualified class name of {@link DoesNotMatchRegex}. */ + public static final String DOES_NOT_MATCH_REGEX_NAME = + "org.checkerframework.common.value.qual.DoesNotMatchRegex"; - /** Fully-qualified class name of {@link DoesNotMatchRegex}. */ - public static final String DOES_NOT_MATCH_REGEX_NAME = - "org.checkerframework.common.value.qual.DoesNotMatchRegex"; + /** The maximum number of values allowed in an annotation's array. */ + protected static final int MAX_VALUES = 10; - /** The maximum number of values allowed in an annotation's array. */ - protected static final int MAX_VALUES = 10; + /** The top type for this hierarchy. */ + protected final AnnotationMirror UNKNOWNVAL = + AnnotationBuilder.fromClass(elements, UnknownVal.class); - /** The top type for this hierarchy. */ - protected final AnnotationMirror UNKNOWNVAL = - AnnotationBuilder.fromClass(elements, UnknownVal.class); + /** The bottom type for this hierarchy. */ + protected final AnnotationMirror BOTTOMVAL = + AnnotationBuilder.fromClass(elements, BottomVal.class); - /** The bottom type for this hierarchy. */ - protected final AnnotationMirror BOTTOMVAL = - AnnotationBuilder.fromClass(elements, BottomVal.class); + /** The canonical @{@link PolyValue} annotation. */ + public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyValue.class); - /** The canonical @{@link PolyValue} annotation. */ - public final AnnotationMirror POLY = AnnotationBuilder.fromClass(elements, PolyValue.class); + /** The canonical @{@link BoolVal}(true) annotation. */ + public final AnnotationMirror BOOLEAN_TRUE = + createBooleanAnnotation(Collections.singletonList(true)); - /** The canonical @{@link BoolVal}(true) annotation. */ - public final AnnotationMirror BOOLEAN_TRUE = - createBooleanAnnotation(Collections.singletonList(true)); + /** The canonical @{@link BoolVal}(false) annotation. */ + public final AnnotationMirror BOOLEAN_FALSE = + createBooleanAnnotation(Collections.singletonList(false)); + + /** The value() element/field of an @ArrayLen annotation. */ + protected final ExecutableElement arrayLenValueElement = + TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); + + /** The from() element/field of an @ArrayLenRange annotation. */ + protected final ExecutableElement arrayLenRangeFromElement = + TreeUtils.getMethod(ArrayLenRange.class, "from", 0, processingEnv); + + /** The to() element/field of an @ArrayLenRange annotation. */ + protected final ExecutableElement arrayLenRangeToElement = + TreeUtils.getMethod(ArrayLenRange.class, "to", 0, processingEnv); + + /** The value() element/field of a @BoolVal annotation. */ + protected final ExecutableElement boolValValueElement = + TreeUtils.getMethod(BoolVal.class, "value", 0, processingEnv); + + /** The value() element/field of a @DoubleVal annotation. */ + protected final ExecutableElement doubleValValueElement = + TreeUtils.getMethod(DoubleVal.class, "value", 0, processingEnv); + + /** The from() element/field of an @IntRange annotation. */ + protected final ExecutableElement intRangeFromElement = + TreeUtils.getMethod(IntRange.class, "from", 0, processingEnv); + + /** The to() element/field of an @IntRange annotation. */ + protected final ExecutableElement intRangeToElement = + TreeUtils.getMethod(IntRange.class, "to", 0, processingEnv); + + /** The value() element/field of a @IntVal annotation. */ + protected final ExecutableElement intValValueElement = + TreeUtils.getMethod(IntVal.class, "value", 0, processingEnv); + + /** The value() element/field of a @MatchesRegex annotation. */ + public final ExecutableElement matchesRegexValueElement = + TreeUtils.getMethod(MatchesRegex.class, "value", 0, processingEnv); + + /** The value() element/field of a @DoesNotMatchRegex annotation. */ + public final ExecutableElement doesNotMatchRegexValueElement = + TreeUtils.getMethod(DoesNotMatchRegex.class, "value", 0, processingEnv); + + /** The value() element/field of a @MinLen annotation. */ + protected final ExecutableElement minLenValueElement = + TreeUtils.getMethod(MinLen.class, "value", 0, processingEnv); + + /** The field() element/field of a @MinLenFieldInvariant annotation. */ + protected final ExecutableElement minLenFieldInvariantFieldElement = + TreeUtils.getMethod(MinLenFieldInvariant.class, "field", 0, processingEnv); + + /** The minLen() element/field of a @MinLenFieldInvariant annotation. */ + protected final ExecutableElement minLenFieldInvariantMinLenElement = + TreeUtils.getMethod(MinLenFieldInvariant.class, "minLen", 0, processingEnv); + + /** The value() element/field of a @StringVal annotation. */ + public final ExecutableElement stringValValueElement = + TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); + + /** Should this type factory report warnings? */ + private final boolean reportEvalWarnings; + + /** Helper class that evaluates statically executable methods, constructors, and fields. */ + // TODO: only used in ValueTreeAnnotator. Should it move there? + protected final ReflectiveEvaluator evaluator; + + /** Helper class that holds references to special methods. */ + private final ValueMethodIdentifier methods; + + @SuppressWarnings("StaticAssignmentInConstructor") // static Range.ignoreOverflow is gross + public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + + reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); + Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW); + evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); + + addAliasedTypeAnnotation("android.support.annotation.IntRange", IntRange.class, true); + + // The actual ArrayLenRange is created by + // {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}; + // this line just registers the alias. The BottomVal is never used. + addAliasedTypeAnnotation(MinLen.class, BOTTOMVAL); + + // @Positive is aliased here because @Positive provides useful + // information about @MinLen annotations. + // @NonNegative and @GTENegativeOne are aliased similarly so + // that it's possible to overwrite a function annotated to return + // @NonNegative with, for instance, a function that returns an @IntVal(0). + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.Positive", createIntRangeFromPositive()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.NonNegative", + createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.GTENegativeOne", + createIntRangeFromGTENegativeOne()); + // Must also alias any alias of three annotations above: + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.LengthOf", + createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.IndexFor", + createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.IndexOrHigh", + createIntRangeFromNonNegative()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.IndexOrLow", + createIntRangeFromGTENegativeOne()); + addAliasedTypeAnnotation( + "org.checkerframework.checker.index.qual.SubstringIndexFor", + createIntRangeFromGTENegativeOne()); + addAliasedTypeAnnotation("javax.annotation.Nonnegative", createIntRangeFromNonNegative()); + + // PolyLength is syntactic sugar for both @PolySameLen and @PolyValue + addAliasedTypeAnnotation("org.checkerframework.checker.index.qual.PolyLength", POLY); + + // EnumVal is treated as StringVal internally by the checker. + addAliasedTypeAnnotation(EnumVal.class, StringVal.class, true); + + methods = new ValueMethodIdentifier(processingEnv); + + if (this.getClass() == ValueAnnotatedTypeFactory.class) { + this.postInit(); + } + } - /** The canonical @{@link BoolVal}(false) annotation. */ - public final AnnotationMirror BOOLEAN_FALSE = - createBooleanAnnotation(Collections.singletonList(false)); - - /** The value() element/field of an @ArrayLen annotation. */ - protected final ExecutableElement arrayLenValueElement = - TreeUtils.getMethod(ArrayLen.class, "value", 0, processingEnv); - - /** The from() element/field of an @ArrayLenRange annotation. */ - protected final ExecutableElement arrayLenRangeFromElement = - TreeUtils.getMethod(ArrayLenRange.class, "from", 0, processingEnv); - - /** The to() element/field of an @ArrayLenRange annotation. */ - protected final ExecutableElement arrayLenRangeToElement = - TreeUtils.getMethod(ArrayLenRange.class, "to", 0, processingEnv); - - /** The value() element/field of a @BoolVal annotation. */ - protected final ExecutableElement boolValValueElement = - TreeUtils.getMethod(BoolVal.class, "value", 0, processingEnv); - - /** The value() element/field of a @DoubleVal annotation. */ - protected final ExecutableElement doubleValValueElement = - TreeUtils.getMethod(DoubleVal.class, "value", 0, processingEnv); + /** Gets a helper object that holds references to methods with special handling. */ + ValueMethodIdentifier getMethodIdentifier() { + return methods; + } + + @Override + protected void applyInferredAnnotations(AnnotatedTypeMirror type, CFValue inferred) { + // Inference can widen an IntRange beyond the values possible for the Java type. Change the + // annotation here so it is no wider than is possible. + TypeMirror t = inferred.getUnderlyingType(); + AnnotationMirrorSet inferredAnnos = inferred.getAnnotations(); + AnnotationMirror intRange = + AnnotationUtils.getAnnotationByName(inferredAnnos, INTRANGE_NAME); + if (intRange != null && TypeKindUtils.primitiveOrBoxedToTypeKind(t) != null) { + Range range = getRange(intRange); + Range newRange = NumberUtils.castRange(t, range); + if (!newRange.equals(range)) { + inferredAnnos = AnnotationMirrorSet.singleton(createIntRangeAnnotation(newRange)); + } + } - /** The from() element/field of an @IntRange annotation. */ - protected final ExecutableElement intRangeFromElement = - TreeUtils.getMethod(IntRange.class, "from", 0, processingEnv); - - /** The to() element/field of an @IntRange annotation. */ - protected final ExecutableElement intRangeToElement = - TreeUtils.getMethod(IntRange.class, "to", 0, processingEnv); - - /** The value() element/field of a @IntVal annotation. */ - protected final ExecutableElement intValValueElement = - TreeUtils.getMethod(IntVal.class, "value", 0, processingEnv); - - /** The value() element/field of a @MatchesRegex annotation. */ - public final ExecutableElement matchesRegexValueElement = - TreeUtils.getMethod(MatchesRegex.class, "value", 0, processingEnv); - - /** The value() element/field of a @DoesNotMatchRegex annotation. */ - public final ExecutableElement doesNotMatchRegexValueElement = - TreeUtils.getMethod(DoesNotMatchRegex.class, "value", 0, processingEnv); - - /** The value() element/field of a @MinLen annotation. */ - protected final ExecutableElement minLenValueElement = - TreeUtils.getMethod(MinLen.class, "value", 0, processingEnv); - - /** The field() element/field of a @MinLenFieldInvariant annotation. */ - protected final ExecutableElement minLenFieldInvariantFieldElement = - TreeUtils.getMethod(MinLenFieldInvariant.class, "field", 0, processingEnv); - - /** The minLen() element/field of a @MinLenFieldInvariant annotation. */ - protected final ExecutableElement minLenFieldInvariantMinLenElement = - TreeUtils.getMethod(MinLenFieldInvariant.class, "minLen", 0, processingEnv); - - /** The value() element/field of a @StringVal annotation. */ - public final ExecutableElement stringValValueElement = - TreeUtils.getMethod(StringVal.class, "value", 0, processingEnv); - - /** Should this type factory report warnings? */ - private final boolean reportEvalWarnings; - - /** Helper class that evaluates statically executable methods, constructors, and fields. */ - // TODO: only used in ValueTreeAnnotator. Should it move there? - protected final ReflectiveEvaluator evaluator; - - /** Helper class that holds references to special methods. */ - private final ValueMethodIdentifier methods; - - @SuppressWarnings("StaticAssignmentInConstructor") // static Range.ignoreOverflow is gross - public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - - reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); - Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW); - evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); - - addAliasedTypeAnnotation("android.support.annotation.IntRange", IntRange.class, true); - - // The actual ArrayLenRange is created by - // {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}; - // this line just registers the alias. The BottomVal is never used. - addAliasedTypeAnnotation(MinLen.class, BOTTOMVAL); - - // @Positive is aliased here because @Positive provides useful - // information about @MinLen annotations. - // @NonNegative and @GTENegativeOne are aliased similarly so - // that it's possible to overwrite a function annotated to return - // @NonNegative with, for instance, a function that returns an @IntVal(0). - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.Positive", createIntRangeFromPositive()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.NonNegative", createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.GTENegativeOne", - createIntRangeFromGTENegativeOne()); - // Must also alias any alias of three annotations above: - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.LengthOf", createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.IndexFor", createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.IndexOrHigh", createIntRangeFromNonNegative()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.IndexOrLow", createIntRangeFromGTENegativeOne()); - addAliasedTypeAnnotation( - "org.checkerframework.checker.index.qual.SubstringIndexFor", - createIntRangeFromGTENegativeOne()); - addAliasedTypeAnnotation("javax.annotation.Nonnegative", createIntRangeFromNonNegative()); - - // PolyLength is syntactic sugar for both @PolySameLen and @PolyValue - addAliasedTypeAnnotation("org.checkerframework.checker.index.qual.PolyLength", POLY); - - // EnumVal is treated as StringVal internally by the checker. - addAliasedTypeAnnotation(EnumVal.class, StringVal.class, true); - - methods = new ValueMethodIdentifier(processingEnv); - - if (this.getClass() == ValueAnnotatedTypeFactory.class) { - this.postInit(); - } - } - - /** Gets a helper object that holds references to methods with special handling. */ - ValueMethodIdentifier getMethodIdentifier() { - return methods; - } - - @Override - protected void applyInferredAnnotations(AnnotatedTypeMirror type, CFValue inferred) { - // Inference can widen an IntRange beyond the values possible for the Java type. Change the - // annotation here so it is no wider than is possible. - TypeMirror t = inferred.getUnderlyingType(); - AnnotationMirrorSet inferredAnnos = inferred.getAnnotations(); - AnnotationMirror intRange = AnnotationUtils.getAnnotationByName(inferredAnnos, INTRANGE_NAME); - if (intRange != null && TypeKindUtils.primitiveOrBoxedToTypeKind(t) != null) { - Range range = getRange(intRange); - Range newRange = NumberUtils.castRange(t, range); - if (!newRange.equals(range)) { - inferredAnnos = AnnotationMirrorSet.singleton(createIntRangeAnnotation(newRange)); - } - } - - DefaultInferredTypesApplier applier = - new DefaultInferredTypesApplier(getQualifierHierarchy(), this); - applier.applyInferredType(type, inferredAnnos, inferred.getUnderlyingType()); - } - - @Override - public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { - if (AnnotationUtils.areSameByName(anno, MINLEN_NAME)) { - int from = getMinLenValue(anno); - return createArrayLenRangeAnnotation(from, Integer.MAX_VALUE); - } - - return super.canonicalAnnotation(anno); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - // Because the Value Checker includes its own alias annotations, - // the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - ArrayLen.class, - ArrayLenRange.class, - IntVal.class, - IntRange.class, - BoolVal.class, - StringVal.class, - MatchesRegex.class, - DoesNotMatchRegex.class, - DoubleVal.class, - BottomVal.class, - UnknownVal.class, - IntRangeFromPositive.class, - IntRangeFromNonNegative.class, - IntRangeFromGTENegativeOne.class, - PolyValue.class)); - } - - @Override - public CFTransfer createFlowTransferFunction( - CFAbstractAnalysis analysis) { - return new ValueTransfer(analysis); - } - - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new ValueQualifierHierarchy(this.getSupportedTypeQualifiers(), this); - } - - @Override - protected TypeHierarchy createTypeHierarchy() { - // This is a lot of code to replace annotations so that annotations that are equivalent - // qualifiers are the same annotation. - return new DefaultTypeHierarchy( - checker, - getQualifierHierarchy(), - ignoreRawTypeArguments, - checker.hasOption("invariantArrays")) { - @Override - public StructuralEqualityComparer createEqualityComparer() { - return new StructuralEqualityComparer(areEqualVisitHistory) { - @Override - protected boolean arePrimaryAnnosEqual( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - type1.replaceAnnotation( - convertToUnknown( - convertSpecialIntRangeToStandardIntRange( - type1.getAnnotationInHierarchy(UNKNOWNVAL)))); - type2.replaceAnnotation( - convertToUnknown( - convertSpecialIntRangeToStandardIntRange( - type2.getAnnotationInHierarchy(UNKNOWNVAL)))); - - return super.arePrimaryAnnosEqual(type1, type2); - } - }; - } - }; - } - - @Override - protected TypeAnnotator createTypeAnnotator() { - return new ListTypeAnnotator(new ValueTypeAnnotator(this), super.createTypeAnnotator()); - } - - @Override - public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { - AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, MinLenFieldInvariant.class); - if (fieldInvarAnno == null) { - return null; - } - List fields = - AnnotationUtils.getElementValueArray( - fieldInvarAnno, minLenFieldInvariantFieldElement, String.class); - List minlens = - AnnotationUtils.getElementValueArray( - fieldInvarAnno, minLenFieldInvariantMinLenElement, Integer.class); - List qualifiers = - CollectionsPlume.mapList( - (Integer minlen) -> createArrayLenRangeAnnotation(minlen, Integer.MAX_VALUE), minlens); - - FieldInvariants superInvariants = super.getFieldInvariants(element); - return new FieldInvariants(superInvariants, fields, qualifiers, this); - } - - /** - * Computes the classes of field invariant annotations; a helper function for {@link - * #getFieldInvariantDeclarationAnnotations}. - * - * @return the classes of field invariant annotations - */ - private Set> computeFieldInvariantDeclarationAnnotations() { - // include FieldInvariant so that @MinLenBottom can be used. - Set> superResult = super.getFieldInvariantDeclarationAnnotations(); - Set> set = - new HashSet<>(CollectionsPlume.mapCapacity(superResult.size() + 1)); - set.addAll(superResult); - set.add(MinLenFieldInvariant.class); - return set; - } - - /** The classes of field invariant annotations. */ - private final Set> fieldInvariantDeclarationAnnotations = - computeFieldInvariantDeclarationAnnotations(); - - @Override - protected Set> getFieldInvariantDeclarationAnnotations() { - return fieldInvariantDeclarationAnnotations; - } - - /** - * Creates array length annotations for the result of the Enum.values() method, which is the - * number of possible values of the enum. - */ - @Override - protected ParameterizedExecutableType methodFromUse( - ExpressionTree tree, - ExecutableElement methodElt, - AnnotatedTypeMirror receiverType, - boolean inferTypeArgs) { - - ParameterizedExecutableType superPair = - super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); - if (ElementUtils.matchesElement(methodElt, "values") - && methodElt.getEnclosingElement().getKind() == ElementKind.ENUM - && ElementUtils.isStatic(methodElt)) { - int count = - ElementUtils.getEnumConstants((TypeElement) methodElt.getEnclosingElement()).size(); - AnnotationMirror am = createArrayLenAnnotation(Collections.singletonList(count)); - superPair.executableType.getReturnType().replaceAnnotation(am); - } - return superPair; - } - - @Override - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - return AnnotationMirrorSet.singleton( - convertSpecialIntRangeToStandardIntRange(annos.first(), typeKind)); - } - - /** - * Finds the appropriate value for the {@code from} value of an annotated type mirror containing - * an {@code IntRange} annotation. - * - * @param atm an annotated type mirror that contains an {@code IntRange} annotation - * @return either the from value from the passed int range annotation, or the minimum value of the - * domain of the underlying type (i.e. Integer.MIN_VALUE if the underlying type is int) - */ - public long getFromValueFromIntRange(AnnotatedTypeMirror atm) { - TypeMirror type = atm.getUnderlyingType(); - long defaultValue = TypeKindUtils.minValue(toPrimitiveIntegralTypeKind(type)); - - AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); - return getIntRangeFromValue(intRangeAnno, defaultValue); - } - - /** - * Finds the appropriate value for the {@code to} value of an annotated type mirror containing an - * {@code IntRange} annotation. - * - * @param atm an annotated type mirror that contains an {@code IntRange} annotation - * @return either the to value from the passed int range annotation, or the maximum value of the - * domain of the underlying type (i.e. Integer.MAX_VALUE if the underlying type is int) - */ - public long getToValueFromIntRange(AnnotatedTypeMirror atm) { - TypeMirror type = atm.getUnderlyingType(); - long defaultValue = TypeKindUtils.maxValue(toPrimitiveIntegralTypeKind(type)); - - AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); - return getIntRangeToValue(intRangeAnno, defaultValue); - } - - /** - * Gets the from() element/field out of an IntRange annotation. The from() element/field must - * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @return its from() element/field - */ - protected long getIntRangeFromValue(AnnotationMirror intRangeAnno) { - return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, Long.MIN_VALUE); - } - - /** - * Gets the from() element/field out of an IntRange annotation. The from() element/field must - * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @param defaultValue the value to return if there is no from() element/field - * @return its from() element/field - */ - protected long getIntRangeFromValue(AnnotationMirror intRangeAnno, long defaultValue) { - return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, defaultValue); - } - - /** - * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. - * Clients should call {@link #getToValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @param defaultValue the value to retur if there is no to() element/field - * @return its to() element/field - */ - protected long getIntRangeToValue(AnnotationMirror intRangeAnno, long defaultValue) { - return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, defaultValue); - } - - /** - * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. - * Clients should call {@link #getToValueFromIntRange} if it might not exist. - * - * @param intRangeAnno an IntRange annotation - * @return its to() element/field - */ - protected long getIntRangeToValue(AnnotationMirror intRangeAnno) { - return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, Long.MAX_VALUE); - } - - /** - * Gets the from() element/field out of an ArrayLenRange annotation. - * - * @param anno an ArrayLenRange annotation - * @return its from() element/field - */ - protected int getArrayLenRangeFromValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValueInt(anno, arrayLenRangeFromElement, 0); - } - - /** - * Gets the to() element/field out of an ArrayLenRange annotation. - * - * @param anno an ArrayLenRange annotation - * @return its to() element/field - */ - protected int getArrayLenRangeToValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValueInt(anno, arrayLenRangeToElement, Integer.MAX_VALUE); - } - - /** - * Gets the value() element/field out of a MinLen annotation. - * - * @param anno a MinLen annotation - * @return its value() element/field - */ - protected int getMinLenValueValue(AnnotationMirror anno) { - return AnnotationUtils.getElementValueInt(anno, minLenValueElement, 0); - } - - /** - * Determine the primitive integral TypeKind for the given integral type. - * - * @param type the type to convert, must be an integral type, boxed or primitive - * @return one of INT, SHORT, BYTE, CHAR, or LONG - */ - private static TypeKind toPrimitiveIntegralTypeKind(TypeMirror type) { - TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - if (typeKind != null && TypeKindUtils.isIntegral(typeKind)) { - return typeKind; - } - throw new TypeSystemError(type.toString() + " expected to be an integral type."); - } - - /** - * Gets the values stored in either an ArrayLen annotation (ints) or an IntVal/DoubleVal/etc. - * annotation (longs), and casts the result to a long. - * - * @param anno annotation mirror from which to get values - * @return the values in {@code anno} casted to longs - */ - /*package-private*/ List getArrayLenOrIntValue(AnnotationMirror anno) { - if (AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME)) { - return CollectionsPlume.mapList(Integer::longValue, getArrayLength(anno)); - } else { - return getIntValues(anno); - } - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - // Don't call super.createTreeAnnotator because it includes the PropagationTreeAnnotator. - // Only use the PropagationTreeAnnotator for typing new arrays. The Value Checker - // computes types differently for all other trees normally typed by the - // PropagationTreeAnnotator. - TreeAnnotator arrayCreation = - new TreeAnnotator(this) { - PropagationTreeAnnotator propagationTreeAnnotator = - new PropagationTreeAnnotator(atypeFactory); - - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror mirror) { - return propagationTreeAnnotator.visitNewArray(tree, mirror); - } + DefaultInferredTypesApplier applier = + new DefaultInferredTypesApplier(getQualifierHierarchy(), this); + applier.applyInferredType(type, inferredAnnos, inferred.getUnderlyingType()); + } + + @Override + public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { + if (AnnotationUtils.areSameByName(anno, MINLEN_NAME)) { + int from = getMinLenValue(anno); + return createArrayLenRangeAnnotation(from, Integer.MAX_VALUE); + } + + return super.canonicalAnnotation(anno); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + // Because the Value Checker includes its own alias annotations, + // the qualifiers have to be explicitly defined. + return new LinkedHashSet<>( + Arrays.asList( + ArrayLen.class, + ArrayLenRange.class, + IntVal.class, + IntRange.class, + BoolVal.class, + StringVal.class, + MatchesRegex.class, + DoesNotMatchRegex.class, + DoubleVal.class, + BottomVal.class, + UnknownVal.class, + IntRangeFromPositive.class, + IntRangeFromNonNegative.class, + IntRangeFromGTENegativeOne.class, + PolyValue.class)); + } + + @Override + public CFTransfer createFlowTransferFunction( + CFAbstractAnalysis analysis) { + return new ValueTransfer(analysis); + } + + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new ValueQualifierHierarchy(this.getSupportedTypeQualifiers(), this); + } + + @Override + protected TypeHierarchy createTypeHierarchy() { + // This is a lot of code to replace annotations so that annotations that are equivalent + // qualifiers are the same annotation. + return new DefaultTypeHierarchy( + checker, + getQualifierHierarchy(), + ignoreRawTypeArguments, + checker.hasOption("invariantArrays")) { + @Override + public StructuralEqualityComparer createEqualityComparer() { + return new StructuralEqualityComparer(areEqualVisitHistory) { + @Override + protected boolean arePrimaryAnnosEqual( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + type1.replaceAnnotation( + convertToUnknown( + convertSpecialIntRangeToStandardIntRange( + type1.getAnnotationInHierarchy(UNKNOWNVAL)))); + type2.replaceAnnotation( + convertToUnknown( + convertSpecialIntRangeToStandardIntRange( + type2.getAnnotationInHierarchy(UNKNOWNVAL)))); + + return super.arePrimaryAnnosEqual(type1, type2); + } + }; + } }; - return new ListTreeAnnotator( - new ValueTreeAnnotator(this), - new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), - arrayCreation); - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm) { - return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @param primitiveKind a primitive TypeKind - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm, TypeKind primitiveKind) { - long max = Long.MAX_VALUE; - if (TypeKindUtils.isIntegral(primitiveKind)) { - Range maxRange = Range.create(primitiveKind); - max = maxRange.to; - } - return convertSpecialIntRangeToStandardIntRange(anm, max); - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @param typeMirror the Java type on which {@code anm} is written - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm, TypeMirror typeMirror) { - TypeKind primitiveKind; - if (TypesUtils.isPrimitive(typeMirror)) { - primitiveKind = typeMirror.getKind(); - } else if (TypesUtils.isBoxedPrimitive(typeMirror)) { - primitiveKind = types.unboxedType(typeMirror).getKind(); - } else { - primitiveKind = TypeKind.LONG; - } - - if (TypesUtils.isIntegralPrimitiveOrBoxed(typeMirror)) { - Range maxRange = Range.create(primitiveKind); - return convertSpecialIntRangeToStandardIntRange(anm, maxRange.to); - - } else { - return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); - } - } - - /** - * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link - * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. - * - * @param anm any annotation mirror - * @param max the max value to use - * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one - * doesn't exist - */ - private AnnotationMirror convertSpecialIntRangeToStandardIntRange( - AnnotationMirror anm, long max) { - if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMPOS_NAME)) { - return createIntRangeAnnotation(1, max); - } - - if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMNONNEG_NAME)) { - return createIntRangeAnnotation(0, max); - } - - if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMGTENEGONE_NAME)) { - return createIntRangeAnnotation(-1, max); - } - return anm; - } - - /** - * If {@code anno} is equivalent to UnknownVal, return UnknownVal; otherwise, return {@code anno}. - * - * @param anno any annotation mirror - * @return UnknownVal if {@code anno} is equivalent to it; otherwise, return {@code anno} - */ - /*package-private*/ AnnotationMirror convertToUnknown(AnnotationMirror anno) { - if (AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME)) { - Range range = getRange(anno); - if (range.from == 0 && range.to >= Integer.MAX_VALUE) { - return UNKNOWNVAL; - } - } else if (AnnotationUtils.areSameByName(anno, INTRANGE_NAME)) { - Range range = getRange(anno); - if (range.isLongEverything()) { - return UNKNOWNVAL; - } - } - return anno; - } - - /** - * Returns the estimate for the length of a string or array with whose annotated type is {@code - * type}. - * - * @param type annotated typed - * @return the estimate for the length of a string or array with whose annotated type is {@code - * type}. - */ - /*package-private*/ AnnotationMirror createArrayLengthResultAnnotation(AnnotatedTypeMirror type) { - AnnotationMirror arrayAnno = type.getAnnotationInHierarchy(UNKNOWNVAL); - switch (AnnotationUtils.annotationName(arrayAnno)) { - case ARRAYLEN_NAME: - // array.length, where array : @ArrayLen(x) - List lengths = getArrayLength(arrayAnno); - return createNumberAnnotationMirror(new ArrayList<>(lengths)); - case ARRAYLENRANGE_NAME: - // array.length, where array : @ArrayLenRange(x) - Range range = getRange(arrayAnno); - return createIntRangeAnnotation(range); - case STRINGVAL_NAME: - List strings = getStringValues(arrayAnno); - List lengthsS = ValueCheckerUtils.getLengthsForStringValues(strings); - return createNumberAnnotationMirror(new ArrayList<>(lengthsS)); - default: - return createIntRangeAnnotation(0, Integer.MAX_VALUE); - } - } - - /** - * Returns a constant value annotation with the {@code value}. The class of the annotation - * reflects the {@code resultType} given. - * - * @param resultType used to select which kind of value annotation is returned - * @param value value to use - * @return a constant value annotation with the {@code value} - */ - /*package-private*/ AnnotationMirror createResultingAnnotation( - TypeMirror resultType, Object value) { - return createResultingAnnotation(resultType, Collections.singletonList(value)); - } - - /** - * Returns a constant value annotation with the {@code values}. The class of the annotation - * reflects the {@code resultType} given. - * - * @param resultType used to select which kind of value annotation is returned - * @param values must be a homogeneous list: every element of it has the same class - * @return a constant value annotation with the {@code values} - */ - /*package-private*/ AnnotationMirror createResultingAnnotation( - TypeMirror resultType, List values) { - if (values == null) { - return UNKNOWNVAL; - } - // For some reason null is included in the list of values, - // so remove it so that it does not cause a NPE elsewhere. - values.remove(null); - if (values.isEmpty()) { - return BOTTOMVAL; - } - - if (TypesUtils.isString(resultType)) { - List stringVals = CollectionsPlume.mapList((Object o) -> (String) o, values); - return createStringAnnotation(stringVals); - } else if (TypesUtils.getClassFromType(resultType) == char[].class) { - List stringVals = - CollectionsPlume.mapList( - o -> { - if (o instanceof char[]) { - return new String((char[]) o); - } else { - return o.toString(); + } + + @Override + protected TypeAnnotator createTypeAnnotator() { + return new ListTypeAnnotator(new ValueTypeAnnotator(this), super.createTypeAnnotator()); + } + + @Override + public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { + AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, MinLenFieldInvariant.class); + if (fieldInvarAnno == null) { + return null; + } + List fields = + AnnotationUtils.getElementValueArray( + fieldInvarAnno, minLenFieldInvariantFieldElement, String.class); + List minlens = + AnnotationUtils.getElementValueArray( + fieldInvarAnno, minLenFieldInvariantMinLenElement, Integer.class); + List qualifiers = + CollectionsPlume.mapList( + (Integer minlen) -> + createArrayLenRangeAnnotation(minlen, Integer.MAX_VALUE), + minlens); + + FieldInvariants superInvariants = super.getFieldInvariants(element); + return new FieldInvariants(superInvariants, fields, qualifiers, this); + } + + /** + * Computes the classes of field invariant annotations; a helper function for {@link + * #getFieldInvariantDeclarationAnnotations}. + * + * @return the classes of field invariant annotations + */ + private Set> computeFieldInvariantDeclarationAnnotations() { + // include FieldInvariant so that @MinLenBottom can be used. + Set> superResult = + super.getFieldInvariantDeclarationAnnotations(); + Set> set = + new HashSet<>(CollectionsPlume.mapCapacity(superResult.size() + 1)); + set.addAll(superResult); + set.add(MinLenFieldInvariant.class); + return set; + } + + /** The classes of field invariant annotations. */ + private final Set> fieldInvariantDeclarationAnnotations = + computeFieldInvariantDeclarationAnnotations(); + + @Override + protected Set> getFieldInvariantDeclarationAnnotations() { + return fieldInvariantDeclarationAnnotations; + } + + /** + * Creates array length annotations for the result of the Enum.values() method, which is the + * number of possible values of the enum. + */ + @Override + protected ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + + ParameterizedExecutableType superPair = + super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs); + if (ElementUtils.matchesElement(methodElt, "values") + && methodElt.getEnclosingElement().getKind() == ElementKind.ENUM + && ElementUtils.isStatic(methodElt)) { + int count = + ElementUtils.getEnumConstants((TypeElement) methodElt.getEnclosingElement()) + .size(); + AnnotationMirror am = createArrayLenAnnotation(Collections.singletonList(count)); + superPair.executableType.getReturnType().replaceAnnotation(am); + } + return superPair; + } + + @Override + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + return AnnotationMirrorSet.singleton( + convertSpecialIntRangeToStandardIntRange(annos.first(), typeKind)); + } + + /** + * Finds the appropriate value for the {@code from} value of an annotated type mirror containing + * an {@code IntRange} annotation. + * + * @param atm an annotated type mirror that contains an {@code IntRange} annotation + * @return either the from value from the passed int range annotation, or the minimum value of + * the domain of the underlying type (i.e. Integer.MIN_VALUE if the underlying type is int) + */ + public long getFromValueFromIntRange(AnnotatedTypeMirror atm) { + TypeMirror type = atm.getUnderlyingType(); + long defaultValue = TypeKindUtils.minValue(toPrimitiveIntegralTypeKind(type)); + + AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); + return getIntRangeFromValue(intRangeAnno, defaultValue); + } + + /** + * Finds the appropriate value for the {@code to} value of an annotated type mirror containing + * an {@code IntRange} annotation. + * + * @param atm an annotated type mirror that contains an {@code IntRange} annotation + * @return either the to value from the passed int range annotation, or the maximum value of the + * domain of the underlying type (i.e. Integer.MAX_VALUE if the underlying type is int) + */ + public long getToValueFromIntRange(AnnotatedTypeMirror atm) { + TypeMirror type = atm.getUnderlyingType(); + long defaultValue = TypeKindUtils.maxValue(toPrimitiveIntegralTypeKind(type)); + + AnnotationMirror intRangeAnno = atm.getAnnotation(IntRange.class); + return getIntRangeToValue(intRangeAnno, defaultValue); + } + + /** + * Gets the from() element/field out of an IntRange annotation. The from() element/field must + * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @return its from() element/field + */ + protected long getIntRangeFromValue(AnnotationMirror intRangeAnno) { + return AnnotationUtils.getElementValueLong( + intRangeAnno, intRangeFromElement, Long.MIN_VALUE); + } + + /** + * Gets the from() element/field out of an IntRange annotation. The from() element/field must + * exist. Clients should call {@link #getFromValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @param defaultValue the value to return if there is no from() element/field + * @return its from() element/field + */ + protected long getIntRangeFromValue(AnnotationMirror intRangeAnno, long defaultValue) { + return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeFromElement, defaultValue); + } + + /** + * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. + * Clients should call {@link #getToValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @param defaultValue the value to retur if there is no to() element/field + * @return its to() element/field + */ + protected long getIntRangeToValue(AnnotationMirror intRangeAnno, long defaultValue) { + return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, defaultValue); + } + + /** + * Gets the to() element/field out of an IntRange annotation. The to() element/field must exist. + * Clients should call {@link #getToValueFromIntRange} if it might not exist. + * + * @param intRangeAnno an IntRange annotation + * @return its to() element/field + */ + protected long getIntRangeToValue(AnnotationMirror intRangeAnno) { + return AnnotationUtils.getElementValueLong(intRangeAnno, intRangeToElement, Long.MAX_VALUE); + } + + /** + * Gets the from() element/field out of an ArrayLenRange annotation. + * + * @param anno an ArrayLenRange annotation + * @return its from() element/field + */ + protected int getArrayLenRangeFromValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValueInt(anno, arrayLenRangeFromElement, 0); + } + + /** + * Gets the to() element/field out of an ArrayLenRange annotation. + * + * @param anno an ArrayLenRange annotation + * @return its to() element/field + */ + protected int getArrayLenRangeToValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValueInt(anno, arrayLenRangeToElement, Integer.MAX_VALUE); + } + + /** + * Gets the value() element/field out of a MinLen annotation. + * + * @param anno a MinLen annotation + * @return its value() element/field + */ + protected int getMinLenValueValue(AnnotationMirror anno) { + return AnnotationUtils.getElementValueInt(anno, minLenValueElement, 0); + } + + /** + * Determine the primitive integral TypeKind for the given integral type. + * + * @param type the type to convert, must be an integral type, boxed or primitive + * @return one of INT, SHORT, BYTE, CHAR, or LONG + */ + private static TypeKind toPrimitiveIntegralTypeKind(TypeMirror type) { + TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + if (typeKind != null && TypeKindUtils.isIntegral(typeKind)) { + return typeKind; + } + throw new TypeSystemError(type.toString() + " expected to be an integral type."); + } + + /** + * Gets the values stored in either an ArrayLen annotation (ints) or an IntVal/DoubleVal/etc. + * annotation (longs), and casts the result to a long. + * + * @param anno annotation mirror from which to get values + * @return the values in {@code anno} casted to longs + */ + /*package-private*/ List getArrayLenOrIntValue(AnnotationMirror anno) { + if (AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME)) { + return CollectionsPlume.mapList(Integer::longValue, getArrayLength(anno)); + } else { + return getIntValues(anno); + } + } + + @Override + protected TreeAnnotator createTreeAnnotator() { + // Don't call super.createTreeAnnotator because it includes the PropagationTreeAnnotator. + // Only use the PropagationTreeAnnotator for typing new arrays. The Value Checker + // computes types differently for all other trees normally typed by the + // PropagationTreeAnnotator. + TreeAnnotator arrayCreation = + new TreeAnnotator(this) { + PropagationTreeAnnotator propagationTreeAnnotator = + new PropagationTreeAnnotator(atypeFactory); + + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror mirror) { + return propagationTreeAnnotator.visitNewArray(tree, mirror); + } + }; + return new ListTreeAnnotator( + new ValueTreeAnnotator(this), + new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(), + arrayCreation); + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm) { + return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @param primitiveKind a primitive TypeKind + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm, TypeKind primitiveKind) { + long max = Long.MAX_VALUE; + if (TypeKindUtils.isIntegral(primitiveKind)) { + Range maxRange = Range.create(primitiveKind); + max = maxRange.to; + } + return convertSpecialIntRangeToStandardIntRange(anm, max); + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @param typeMirror the Java type on which {@code anm} is written + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm, TypeMirror typeMirror) { + TypeKind primitiveKind; + if (TypesUtils.isPrimitive(typeMirror)) { + primitiveKind = typeMirror.getKind(); + } else if (TypesUtils.isBoxedPrimitive(typeMirror)) { + primitiveKind = types.unboxedType(typeMirror).getKind(); + } else { + primitiveKind = TypeKind.LONG; + } + + if (TypesUtils.isIntegralPrimitiveOrBoxed(typeMirror)) { + Range maxRange = Range.create(primitiveKind); + return convertSpecialIntRangeToStandardIntRange(anm, maxRange.to); + + } else { + return convertSpecialIntRangeToStandardIntRange(anm, Long.MAX_VALUE); + } + } + + /** + * Converts {@link IntRangeFromPositive}, {@link IntRangeFromNonNegative}, or {@link + * IntRangeFromGTENegativeOne} to {@link IntRange}. Any other annotation is just returned. + * + * @param anm any annotation mirror + * @param max the max value to use + * @return the int range annotation is that equivalent to {@code anm}, or {@code anm} if one + * doesn't exist + */ + private AnnotationMirror convertSpecialIntRangeToStandardIntRange( + AnnotationMirror anm, long max) { + if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMPOS_NAME)) { + return createIntRangeAnnotation(1, max); + } + + if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMNONNEG_NAME)) { + return createIntRangeAnnotation(0, max); + } + + if (AnnotationUtils.areSameByName(anm, INTRANGE_FROMGTENEGONE_NAME)) { + return createIntRangeAnnotation(-1, max); + } + return anm; + } + + /** + * If {@code anno} is equivalent to UnknownVal, return UnknownVal; otherwise, return {@code + * anno}. + * + * @param anno any annotation mirror + * @return UnknownVal if {@code anno} is equivalent to it; otherwise, return {@code anno} + */ + /*package-private*/ AnnotationMirror convertToUnknown(AnnotationMirror anno) { + if (AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME)) { + Range range = getRange(anno); + if (range.from == 0 && range.to >= Integer.MAX_VALUE) { + return UNKNOWNVAL; + } + } else if (AnnotationUtils.areSameByName(anno, INTRANGE_NAME)) { + Range range = getRange(anno); + if (range.isLongEverything()) { + return UNKNOWNVAL; + } + } + return anno; + } + + /** + * Returns the estimate for the length of a string or array with whose annotated type is {@code + * type}. + * + * @param type annotated typed + * @return the estimate for the length of a string or array with whose annotated type is {@code + * type}. + */ + /*package-private*/ AnnotationMirror createArrayLengthResultAnnotation( + AnnotatedTypeMirror type) { + AnnotationMirror arrayAnno = type.getAnnotationInHierarchy(UNKNOWNVAL); + switch (AnnotationUtils.annotationName(arrayAnno)) { + case ARRAYLEN_NAME: + // array.length, where array : @ArrayLen(x) + List lengths = getArrayLength(arrayAnno); + return createNumberAnnotationMirror(new ArrayList<>(lengths)); + case ARRAYLENRANGE_NAME: + // array.length, where array : @ArrayLenRange(x) + Range range = getRange(arrayAnno); + return createIntRangeAnnotation(range); + case STRINGVAL_NAME: + List strings = getStringValues(arrayAnno); + List lengthsS = ValueCheckerUtils.getLengthsForStringValues(strings); + return createNumberAnnotationMirror(new ArrayList<>(lengthsS)); + default: + return createIntRangeAnnotation(0, Integer.MAX_VALUE); + } + } + + /** + * Returns a constant value annotation with the {@code value}. The class of the annotation + * reflects the {@code resultType} given. + * + * @param resultType used to select which kind of value annotation is returned + * @param value value to use + * @return a constant value annotation with the {@code value} + */ + /*package-private*/ AnnotationMirror createResultingAnnotation( + TypeMirror resultType, Object value) { + return createResultingAnnotation(resultType, Collections.singletonList(value)); + } + + /** + * Returns a constant value annotation with the {@code values}. The class of the annotation + * reflects the {@code resultType} given. + * + * @param resultType used to select which kind of value annotation is returned + * @param values must be a homogeneous list: every element of it has the same class + * @return a constant value annotation with the {@code values} + */ + /*package-private*/ AnnotationMirror createResultingAnnotation( + TypeMirror resultType, List values) { + if (values == null) { + return UNKNOWNVAL; + } + // For some reason null is included in the list of values, + // so remove it so that it does not cause a NPE elsewhere. + values.remove(null); + if (values.isEmpty()) { + return BOTTOMVAL; + } + + if (TypesUtils.isString(resultType)) { + List stringVals = CollectionsPlume.mapList((Object o) -> (String) o, values); + return createStringAnnotation(stringVals); + } else if (TypesUtils.getClassFromType(resultType) == char[].class) { + List stringVals = + CollectionsPlume.mapList( + o -> { + if (o instanceof char[]) { + return new String((char[]) o); + } else { + return o.toString(); + } + }, + values); + return createStringAnnotation(stringVals); + } + + TypeKind primitiveKind; + if (TypesUtils.isPrimitive(resultType)) { + primitiveKind = resultType.getKind(); + } else if (TypesUtils.isBoxedPrimitive(resultType)) { + primitiveKind = types.unboxedType(resultType).getKind(); + } else { + return UNKNOWNVAL; + } + + switch (primitiveKind) { + case BOOLEAN: + List boolVals = + CollectionsPlume.mapList((Object o) -> (Boolean) o, values); + return createBooleanAnnotation(boolVals); + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + case BYTE: + List numberVals = new ArrayList<>(values.size()); + List characterVals = new ArrayList<>(values.size()); + for (Object o : values) { + if (o instanceof Character) { + characterVals.add((Character) o); + } else { + numberVals.add((Number) o); + } + } + if (numberVals.isEmpty()) { + // Every value in the list is a Character. + return createCharAnnotation(characterVals); + } + return createNumberAnnotationMirror(new ArrayList<>(numberVals)); + case CHAR: + List charVals = new ArrayList<>(values.size()); + for (Object o : values) { + if (o instanceof Number) { + charVals.add((char) ((Number) o).intValue()); + } else { + charVals.add((char) o); + } } - }, - values); - return createStringAnnotation(stringVals); - } - - TypeKind primitiveKind; - if (TypesUtils.isPrimitive(resultType)) { - primitiveKind = resultType.getKind(); - } else if (TypesUtils.isBoxedPrimitive(resultType)) { - primitiveKind = types.unboxedType(resultType).getKind(); - } else { - return UNKNOWNVAL; - } - - switch (primitiveKind) { - case BOOLEAN: - List boolVals = CollectionsPlume.mapList((Object o) -> (Boolean) o, values); - return createBooleanAnnotation(boolVals); - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - case BYTE: - List numberVals = new ArrayList<>(values.size()); - List characterVals = new ArrayList<>(values.size()); - for (Object o : values) { - if (o instanceof Character) { - characterVals.add((Character) o); - } else { - numberVals.add((Number) o); - } - } - if (numberVals.isEmpty()) { - // Every value in the list is a Character. - return createCharAnnotation(characterVals); - } - return createNumberAnnotationMirror(new ArrayList<>(numberVals)); - case CHAR: - List charVals = new ArrayList<>(values.size()); - for (Object o : values) { - if (o instanceof Number) { - charVals.add((char) ((Number) o).intValue()); - } else { - charVals.add((char) o); - } - } - return createCharAnnotation(charVals); - default: - throw new UnsupportedOperationException("Unexpected kind:" + resultType); - } - } - - /** - * Returns a {@link IntVal} or {@link IntRange} annotation using the values. If {@code values} is - * null, then UnknownVal is returned; if {@code values} is empty, then bottom is returned. If the - * number of {@code values} is greater than MAX_VALUES, return an {@link IntRange}. In other - * cases, the values are sorted and duplicates are removed before an {@link IntVal} is created. - * - * @param values list of longs; duplicates are allowed and the values may be in any order - * @return an annotation depends on the values - */ - public AnnotationMirror createIntValAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - long valMin = values.get(0); - long valMax = values.get(values.size() - 1); - return createIntRangeAnnotation(valMin, valMax); - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Convert an {@code @IntRange} annotation to an {@code @IntVal} annotation, or to UNKNOWNVAL if - * the input is too wide to be represented as an {@code @IntVal}. - * - * @param intRangeAnno an {@code @IntRange} annotation - * @return an {@code @IntVal} annotation corresponding to the argument - */ - public AnnotationMirror convertIntRangeToIntVal(AnnotationMirror intRangeAnno) { - Range range = getRange(intRangeAnno); - List values = ValueCheckerUtils.getValuesFromRange(range, Long.class); - return createIntValAnnotation(values); - } - - /** - * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of doubles; duplicates are allowed and the values may be in any order - * @return a {@link DoubleVal} annotation using the values - */ - public AnnotationMirror createDoubleValAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoubleVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Convert an {@code @IntVal} annotation to a {@code @DoubleVal} annotation. - * - * @param intValAnno an {@code @IntVal} annotation - * @return a corresponding {@code @DoubleVal} annotation - */ - /*package-private*/ AnnotationMirror convertIntValToDoubleVal(AnnotationMirror intValAnno) { - List intValues = getIntValues(intValAnno); - return createDoubleValAnnotation(convertLongListToDoubleList(intValues)); - } - - /** - * Convert a {@code List} to a {@code List}. - * - * @param intValues a list of long integers - * @return a list of double floating-point values - */ - /*package-private*/ List convertLongListToDoubleList(List intValues) { - return CollectionsPlume.mapList(Long::doubleValue, intValues); - } - - /** - * Returns a {@link StringVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. If values is larger than - * the max number of values allowed (10 by default), then an {@link ArrayLen} or an {@link - * ArrayLenRange} annotation is returned. - * - * @param values list of strings; duplicates are allowed and the values may be in any order - * @return a {@link StringVal} annotation using the values - */ - public AnnotationMirror createStringAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - // Too many strings are replaced by their lengths - List lengths = ValueCheckerUtils.getLengthsForStringValues(values); - return createArrayLenAnnotation(lengths); - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, StringVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Returns a {@link ArrayLen} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. If values is larger than - * the max number of values allowed (10 by default), then an {@link ArrayLenRange} annotation is - * returned. - * - * @param values list of integers; duplicates are allowed and the values may be in any order - * @return a {@link ArrayLen} annotation using the values - */ - public AnnotationMirror createArrayLenAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.isEmpty() || Collections.min(values) < 0) { - return BOTTOMVAL; - } else if (values.size() > MAX_VALUES) { - return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); - } else { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLen.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Returns a {@link BoolVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of booleans; duplicates are allowed and the values may be in any order - * @return a {@link BoolVal} annotation using the values - */ - public AnnotationMirror createBooleanAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - // TODO: This seems wasteful. Why not create the 3 interesting AnnotationMirrors (with - // arguments {true}, {false}, and {true, false}, respectively) in advance and return one - // of them? (Maybe an advantage of this implementation is that it is identical to - // some other implementations and therefore might be less error-prone.) - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, BoolVal.class); - builder.setValue("value", values); - return builder.build(); - } - } - - /** - * Returns a {@link IntVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of characters; duplicates are allowed and the values may be in any order - * @return a {@link IntVal} annotation using the values - */ - public AnnotationMirror createCharAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - List longValues = CollectionsPlume.mapList((Character value) -> (long) value, values); - return createIntValAnnotation(longValues); - } - } - - /** - * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then - * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are - * sorted and duplicates are removed before the annotation is created. - * - * @param values list of doubleacters; duplicates are allowed and the values may be in any order - * @return a {@link IntVal} annotation using the values - */ - public AnnotationMirror createDoubleAnnotation(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } - if (values.isEmpty()) { - return BOTTOMVAL; - } - values = CollectionsPlume.withoutDuplicatesSorted(values); - if (values.size() > MAX_VALUES) { - return UNKNOWNVAL; - } else { - return createDoubleValAnnotation(values); - } - } - - /** - * Returns an annotation that represents the given set of values. - * - * @param values a homogeneous list: every element of it has the same class. This method does not - * modify or store it. - * @return an annotation that represents the given set of values - */ - public AnnotationMirror createNumberAnnotationMirror(@Nullable List values) { - if (values == null) { - return UNKNOWNVAL; - } else if (values.isEmpty()) { - return BOTTOMVAL; - } - Number first = values.get(0); - if (first instanceof Integer - || first instanceof Short - || first instanceof Long - || first instanceof Byte) { - List intValues = CollectionsPlume.mapList(Number::longValue, values); - return createIntValAnnotation(intValues); - } else if (first instanceof Double || first instanceof Float) { - List doubleValues = CollectionsPlume.mapList(Number::doubleValue, values); - return createDoubleValAnnotation(doubleValues); - } - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: unexpected class: " + first.getClass()); - } - - /** - * Create an {@code @IntRange} annotation from the two (inclusive) bounds. Does not return - * BOTTOMVAL or UNKNOWNVAL. - * - * @param from the lower bound - * @param to the upper bound - * @return an {@code @IntRange} annotation - */ - /*package-private*/ AnnotationMirror createIntRangeAnnotation(long from, long to) { - assert from <= to; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRange.class); - builder.setValue("from", from); - builder.setValue("to", to); - return builder.build(); - } - - /** - * Create an {@code @IntRange} or {@code @IntVal} annotation from the range. May return BOTTOMVAL - * or UNKNOWNVAL. - */ - public AnnotationMirror createIntRangeAnnotation(Range range) { - if (range.isNothing()) { - return BOTTOMVAL; - } else if (range.isLongEverything()) { - return UNKNOWNVAL; - } else if (range.isWiderThan(MAX_VALUES)) { - return createIntRangeAnnotation(range.from, range.to); - } else { - List newValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); - return createIntValAnnotation(newValues); - } - } - - /** - * Creates the special {@link IntRangeFromPositive} annotation, which is only used as an alias for - * the Index Checker's {@link org.checkerframework.checker.index.qual.Positive} annotation. It is - * treated everywhere as an IntRange annotation, but is not checked when it appears as the left - * hand side of an assignment (because the Lower Bound Checker will check it). - */ - private AnnotationMirror createIntRangeFromPositive() { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromPositive.class); - return builder.build(); - } - - /** - * Creates the special {@link IntRangeFromNonNegative} annotation, which is only used as an alias - * for the Index Checker's {@link org.checkerframework.checker.index.qual.NonNegative} annotation. - * It is treated everywhere as an IntRange annotation, but is not checked when it appears as the - * left hand side of an assignment (because the Lower Bound Checker will check it). - */ - private AnnotationMirror createIntRangeFromNonNegative() { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRangeFromNonNegative.class); - return builder.build(); - } - - /** - * Creates the special {@link IntRangeFromGTENegativeOne} annotation, which is only used as an - * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.GTENegativeOne} - * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it - * appears as the left hand side of an assignment (because the Lower Bound Checker will check it). - */ - private AnnotationMirror createIntRangeFromGTENegativeOne() { - AnnotationBuilder builder = - new AnnotationBuilder(processingEnv, IntRangeFromGTENegativeOne.class); - return builder.build(); - } - - /** - * Create an {@code @ArrayLenRange} annotation from the two (inclusive) bounds. Does not return - * BOTTOMVAL or UNKNOWNVAL. - */ - public AnnotationMirror createArrayLenRangeAnnotation(int from, int to) { - assert from <= to; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLenRange.class); - builder.setValue("from", from); - builder.setValue("to", to); - return builder.build(); - } - - /** - * Create an {@code @ArrayLenRange} annotation from the range. May return BOTTOMVAL or UNKNOWNVAL. - */ - public AnnotationMirror createArrayLenRangeAnnotation(Range range) { - if (range.isNothing()) { - return BOTTOMVAL; - } else if (range.isLongEverything() || !range.isWithinInteger()) { - return UNKNOWNVAL; - } else { - return createArrayLenRangeAnnotation( - Long.valueOf(range.from).intValue(), Long.valueOf(range.to).intValue()); - } - } - - /** - * Creates an {@code MatchesRegex} annotation for the given regular expressions. - * - * @param regexes a list of Java regular expressions - * @return a MatchesRegex annotation with those values - */ - public AnnotationMirror createMatchesRegexAnnotation(@Nullable List<@Regex String> regexes) { - if (regexes == null) { - return UNKNOWNVAL; - } - if (regexes.isEmpty()) { - return BOTTOMVAL; - } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MatchesRegex.class); - builder.setValue("value", regexes.toArray(new String[0])); - return builder.build(); - } - - /** - * Creates an {@code DoesNotMatchRegex} annotation for the given regular expressions. - * - * @param regexes a list of Java regular expressions - * @return a DoesNotMatchRegex annotation with those values - */ - public AnnotationMirror createDoesNotMatchRegexAnnotation(@Nullable List<@Regex String> regexes) { - if (regexes == null) { - return BOTTOMVAL; - } - if (regexes.isEmpty()) { - return UNKNOWNVAL; - } - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoesNotMatchRegex.class); - builder.setValue("value", regexes.toArray(new String[0])); - return builder.build(); - } - - /** - * Converts an {@code @StringVal} annotation to an {@code @ArrayLenRange} annotation. - * - * @param stringValAnno a StringVal annotation - * @return an ArrayLenRange annotation representing the possible lengths of the values of the - * given StringVal annotation - */ - /*package-private*/ AnnotationMirror convertStringValToArrayLenRange( - AnnotationMirror stringValAnno) { - List values = getStringValues(stringValAnno); - List lengths = ValueCheckerUtils.getLengthsForStringValues(values); - return createArrayLenRangeAnnotation(Collections.min(lengths), Collections.max(lengths)); - } - - /** - * Converts a {@code @StringVal} annotation to an {@code @ArrayLen} annotation. If the - * {@code @StringVal} annotation contains string values of more than MAX_VALUES distinct lengths, - * {@code @ArrayLenRange} annotation is returned instead. - * - * @param stringValAnno a {@code @StringVal} annotation - * @return a corresponding {@code @ArrayLen} annotation - */ - /*package-private*/ AnnotationMirror convertStringValToArrayLen(AnnotationMirror stringValAnno) { - List values = getStringValues(stringValAnno); - return createArrayLenAnnotation(ValueCheckerUtils.getLengthsForStringValues(values)); - } - - /** - * Converts an {@code StringVal} annotation to an {@code MatchesRegex} annotation that matches - * exactly the string values listed in the {@code StringVal}. - * - * @param stringValAnno a StringVal annotation - * @return an equivalent MatchesReges annotation - */ - /*package-private*/ AnnotationMirror convertStringValToMatchesRegex( - AnnotationMirror stringValAnno) { - List values = getStringValues(stringValAnno); - List<@Regex String> valuesAsRegexes = CollectionsPlume.mapList(Pattern::quote, values); - return createMatchesRegexAnnotation(valuesAsRegexes); - } - - /** - * Converts an {@code @ArrayLen} annotation to an {@code @ArrayLenRange} annotation. - * - * @param arrayLenAnno an ArrayLen annotation - * @return an ArrayLenRange annotation representing the bounds of the given ArrayLen annotation - */ - public AnnotationMirror convertArrayLenToArrayLenRange(AnnotationMirror arrayLenAnno) { - List values = getArrayLength(arrayLenAnno); - return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); - } - - /** Converts an {@code @IntVal} annotation to an {@code @IntRange} annotation. */ - public AnnotationMirror convertIntValToIntRange(AnnotationMirror intValAnno) { - List intValues = getIntValues(intValAnno); - return createIntRangeAnnotation(Collections.min(intValues), Collections.max(intValues)); - } - - /** - * Returns a {@link Range} bounded by the values specified in the given {@code @Range} annotation. - * Also returns an appropriate range if an {@code @IntVal} annotation is passed. Returns {@code - * null} if the annotation is null or if the annotation is not an {@code IntRange}, {@code - * IntRangeFromPositive}, {@code IntVal}, or {@code ArrayLenRange}. - * - * @param rangeAnno a {@code @Range} annotation - * @return the {@link Range} that the annotation represents - */ - public @Nullable Range getRange(@Nullable AnnotationMirror rangeAnno) { - if (rangeAnno == null) { - return null; - } - switch (AnnotationUtils.annotationName(rangeAnno)) { - case INTRANGE_FROMPOS_NAME: - return Range.create(1, Integer.MAX_VALUE); - case INTRANGE_FROMNONNEG_NAME: - return Range.create(0, Integer.MAX_VALUE); - case INTRANGE_FROMGTENEGONE_NAME: - return Range.create(-1, Integer.MAX_VALUE); - case INTVAL_NAME: - return ValueCheckerUtils.getRangeFromValues(getIntValues(rangeAnno)); - case INTRANGE_NAME: - // Assume rangeAnno is well-formed, i.e., 'from' is less than or equal to 'to'. - return Range.create(getIntRangeFromValue(rangeAnno), getIntRangeToValue(rangeAnno)); - case ARRAYLENRANGE_NAME: - return Range.create( - getArrayLenRangeFromValue(rangeAnno), getArrayLenRangeToValue(rangeAnno)); - default: + return createCharAnnotation(charVals); + default: + throw new UnsupportedOperationException("Unexpected kind:" + resultType); + } + } + + /** + * Returns a {@link IntVal} or {@link IntRange} annotation using the values. If {@code values} + * is null, then UnknownVal is returned; if {@code values} is empty, then bottom is returned. If + * the number of {@code values} is greater than MAX_VALUES, return an {@link IntRange}. In other + * cases, the values are sorted and duplicates are removed before an {@link IntVal} is created. + * + * @param values list of longs; duplicates are allowed and the values may be in any order + * @return an annotation depends on the values + */ + public AnnotationMirror createIntValAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + long valMin = values.get(0); + long valMax = values.get(values.size() - 1); + return createIntRangeAnnotation(valMin, valMax); + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Convert an {@code @IntRange} annotation to an {@code @IntVal} annotation, or to UNKNOWNVAL if + * the input is too wide to be represented as an {@code @IntVal}. + * + * @param intRangeAnno an {@code @IntRange} annotation + * @return an {@code @IntVal} annotation corresponding to the argument + */ + public AnnotationMirror convertIntRangeToIntVal(AnnotationMirror intRangeAnno) { + Range range = getRange(intRangeAnno); + List values = ValueCheckerUtils.getValuesFromRange(range, Long.class); + return createIntValAnnotation(values); + } + + /** + * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of doubles; duplicates are allowed and the values may be in any order + * @return a {@link DoubleVal} annotation using the values + */ + public AnnotationMirror createDoubleValAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoubleVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Convert an {@code @IntVal} annotation to a {@code @DoubleVal} annotation. + * + * @param intValAnno an {@code @IntVal} annotation + * @return a corresponding {@code @DoubleVal} annotation + */ + /*package-private*/ AnnotationMirror convertIntValToDoubleVal(AnnotationMirror intValAnno) { + List intValues = getIntValues(intValAnno); + return createDoubleValAnnotation(convertLongListToDoubleList(intValues)); + } + + /** + * Convert a {@code List} to a {@code List}. + * + * @param intValues a list of long integers + * @return a list of double floating-point values + */ + /*package-private*/ List convertLongListToDoubleList(List intValues) { + return CollectionsPlume.mapList(Long::doubleValue, intValues); + } + + /** + * Returns a {@link StringVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. If values is larger than + * the max number of values allowed (10 by default), then an {@link ArrayLen} or an {@link + * ArrayLenRange} annotation is returned. + * + * @param values list of strings; duplicates are allowed and the values may be in any order + * @return a {@link StringVal} annotation using the values + */ + public AnnotationMirror createStringAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + // Too many strings are replaced by their lengths + List lengths = ValueCheckerUtils.getLengthsForStringValues(values); + return createArrayLenAnnotation(lengths); + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, StringVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Returns a {@link ArrayLen} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. If values is larger than + * the max number of values allowed (10 by default), then an {@link ArrayLenRange} annotation is + * returned. + * + * @param values list of integers; duplicates are allowed and the values may be in any order + * @return a {@link ArrayLen} annotation using the values + */ + public AnnotationMirror createArrayLenAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.isEmpty() || Collections.min(values) < 0) { + return BOTTOMVAL; + } else if (values.size() > MAX_VALUES) { + return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); + } else { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLen.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Returns a {@link BoolVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of booleans; duplicates are allowed and the values may be in any order + * @return a {@link BoolVal} annotation using the values + */ + public AnnotationMirror createBooleanAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + // TODO: This seems wasteful. Why not create the 3 interesting AnnotationMirrors (with + // arguments {true}, {false}, and {true, false}, respectively) in advance and return one + // of them? (Maybe an advantage of this implementation is that it is identical to + // some other implementations and therefore might be less error-prone.) + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, BoolVal.class); + builder.setValue("value", values); + return builder.build(); + } + } + + /** + * Returns a {@link IntVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of characters; duplicates are allowed and the values may be in any order + * @return a {@link IntVal} annotation using the values + */ + public AnnotationMirror createCharAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + List longValues = + CollectionsPlume.mapList((Character value) -> (long) value, values); + return createIntValAnnotation(longValues); + } + } + + /** + * Returns a {@link DoubleVal} annotation using the values. If {@code values} is null, then + * UnknownVal is returned; if {@code values} is empty, then bottom is returned. The values are + * sorted and duplicates are removed before the annotation is created. + * + * @param values list of doubleacters; duplicates are allowed and the values may be in any order + * @return a {@link IntVal} annotation using the values + */ + public AnnotationMirror createDoubleAnnotation(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } + if (values.isEmpty()) { + return BOTTOMVAL; + } + values = CollectionsPlume.withoutDuplicatesSorted(values); + if (values.size() > MAX_VALUES) { + return UNKNOWNVAL; + } else { + return createDoubleValAnnotation(values); + } + } + + /** + * Returns an annotation that represents the given set of values. + * + * @param values a homogeneous list: every element of it has the same class. This method does + * not modify or store it. + * @return an annotation that represents the given set of values + */ + public AnnotationMirror createNumberAnnotationMirror(@Nullable List values) { + if (values == null) { + return UNKNOWNVAL; + } else if (values.isEmpty()) { + return BOTTOMVAL; + } + Number first = values.get(0); + if (first instanceof Integer + || first instanceof Short + || first instanceof Long + || first instanceof Byte) { + List intValues = CollectionsPlume.mapList(Number::longValue, values); + return createIntValAnnotation(intValues); + } else if (first instanceof Double || first instanceof Float) { + List doubleValues = CollectionsPlume.mapList(Number::doubleValue, values); + return createDoubleValAnnotation(doubleValues); + } + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: unexpected class: " + first.getClass()); + } + + /** + * Create an {@code @IntRange} annotation from the two (inclusive) bounds. Does not return + * BOTTOMVAL or UNKNOWNVAL. + * + * @param from the lower bound + * @param to the upper bound + * @return an {@code @IntRange} annotation + */ + /*package-private*/ AnnotationMirror createIntRangeAnnotation(long from, long to) { + assert from <= to; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, IntRange.class); + builder.setValue("from", from); + builder.setValue("to", to); + return builder.build(); + } + + /** + * Create an {@code @IntRange} or {@code @IntVal} annotation from the range. May return + * BOTTOMVAL or UNKNOWNVAL. + */ + public AnnotationMirror createIntRangeAnnotation(Range range) { + if (range.isNothing()) { + return BOTTOMVAL; + } else if (range.isLongEverything()) { + return UNKNOWNVAL; + } else if (range.isWiderThan(MAX_VALUES)) { + return createIntRangeAnnotation(range.from, range.to); + } else { + List newValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); + return createIntValAnnotation(newValues); + } + } + + /** + * Creates the special {@link IntRangeFromPositive} annotation, which is only used as an alias + * for the Index Checker's {@link org.checkerframework.checker.index.qual.Positive} annotation. + * It is treated everywhere as an IntRange annotation, but is not checked when it appears as the + * left hand side of an assignment (because the Lower Bound Checker will check it). + */ + private AnnotationMirror createIntRangeFromPositive() { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, IntRangeFromPositive.class); + return builder.build(); + } + + /** + * Creates the special {@link IntRangeFromNonNegative} annotation, which is only used as an + * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.NonNegative} + * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it + * appears as the left hand side of an assignment (because the Lower Bound Checker will check + * it). + */ + private AnnotationMirror createIntRangeFromNonNegative() { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, IntRangeFromNonNegative.class); + return builder.build(); + } + + /** + * Creates the special {@link IntRangeFromGTENegativeOne} annotation, which is only used as an + * alias for the Index Checker's {@link org.checkerframework.checker.index.qual.GTENegativeOne} + * annotation. It is treated everywhere as an IntRange annotation, but is not checked when it + * appears as the left hand side of an assignment (because the Lower Bound Checker will check + * it). + */ + private AnnotationMirror createIntRangeFromGTENegativeOne() { + AnnotationBuilder builder = + new AnnotationBuilder(processingEnv, IntRangeFromGTENegativeOne.class); + return builder.build(); + } + + /** + * Create an {@code @ArrayLenRange} annotation from the two (inclusive) bounds. Does not return + * BOTTOMVAL or UNKNOWNVAL. + */ + public AnnotationMirror createArrayLenRangeAnnotation(int from, int to) { + assert from <= to; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, ArrayLenRange.class); + builder.setValue("from", from); + builder.setValue("to", to); + return builder.build(); + } + + /** + * Create an {@code @ArrayLenRange} annotation from the range. May return BOTTOMVAL or + * UNKNOWNVAL. + */ + public AnnotationMirror createArrayLenRangeAnnotation(Range range) { + if (range.isNothing()) { + return BOTTOMVAL; + } else if (range.isLongEverything() || !range.isWithinInteger()) { + return UNKNOWNVAL; + } else { + return createArrayLenRangeAnnotation( + Long.valueOf(range.from).intValue(), Long.valueOf(range.to).intValue()); + } + } + + /** + * Creates an {@code MatchesRegex} annotation for the given regular expressions. + * + * @param regexes a list of Java regular expressions + * @return a MatchesRegex annotation with those values + */ + public AnnotationMirror createMatchesRegexAnnotation(@Nullable List<@Regex String> regexes) { + if (regexes == null) { + return UNKNOWNVAL; + } + if (regexes.isEmpty()) { + return BOTTOMVAL; + } + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MatchesRegex.class); + builder.setValue("value", regexes.toArray(new String[0])); + return builder.build(); + } + + /** + * Creates an {@code DoesNotMatchRegex} annotation for the given regular expressions. + * + * @param regexes a list of Java regular expressions + * @return a DoesNotMatchRegex annotation with those values + */ + public AnnotationMirror createDoesNotMatchRegexAnnotation( + @Nullable List<@Regex String> regexes) { + if (regexes == null) { + return BOTTOMVAL; + } + if (regexes.isEmpty()) { + return UNKNOWNVAL; + } + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, DoesNotMatchRegex.class); + builder.setValue("value", regexes.toArray(new String[0])); + return builder.build(); + } + + /** + * Converts an {@code @StringVal} annotation to an {@code @ArrayLenRange} annotation. + * + * @param stringValAnno a StringVal annotation + * @return an ArrayLenRange annotation representing the possible lengths of the values of the + * given StringVal annotation + */ + /*package-private*/ AnnotationMirror convertStringValToArrayLenRange( + AnnotationMirror stringValAnno) { + List values = getStringValues(stringValAnno); + List lengths = ValueCheckerUtils.getLengthsForStringValues(values); + return createArrayLenRangeAnnotation(Collections.min(lengths), Collections.max(lengths)); + } + + /** + * Converts a {@code @StringVal} annotation to an {@code @ArrayLen} annotation. If the + * {@code @StringVal} annotation contains string values of more than MAX_VALUES distinct + * lengths, {@code @ArrayLenRange} annotation is returned instead. + * + * @param stringValAnno a {@code @StringVal} annotation + * @return a corresponding {@code @ArrayLen} annotation + */ + /*package-private*/ AnnotationMirror convertStringValToArrayLen( + AnnotationMirror stringValAnno) { + List values = getStringValues(stringValAnno); + return createArrayLenAnnotation(ValueCheckerUtils.getLengthsForStringValues(values)); + } + + /** + * Converts an {@code StringVal} annotation to an {@code MatchesRegex} annotation that matches + * exactly the string values listed in the {@code StringVal}. + * + * @param stringValAnno a StringVal annotation + * @return an equivalent MatchesReges annotation + */ + /*package-private*/ AnnotationMirror convertStringValToMatchesRegex( + AnnotationMirror stringValAnno) { + List values = getStringValues(stringValAnno); + List<@Regex String> valuesAsRegexes = CollectionsPlume.mapList(Pattern::quote, values); + return createMatchesRegexAnnotation(valuesAsRegexes); + } + + /** + * Converts an {@code @ArrayLen} annotation to an {@code @ArrayLenRange} annotation. + * + * @param arrayLenAnno an ArrayLen annotation + * @return an ArrayLenRange annotation representing the bounds of the given ArrayLen annotation + */ + public AnnotationMirror convertArrayLenToArrayLenRange(AnnotationMirror arrayLenAnno) { + List values = getArrayLength(arrayLenAnno); + return createArrayLenRangeAnnotation(Collections.min(values), Collections.max(values)); + } + + /** Converts an {@code @IntVal} annotation to an {@code @IntRange} annotation. */ + public AnnotationMirror convertIntValToIntRange(AnnotationMirror intValAnno) { + List intValues = getIntValues(intValAnno); + return createIntRangeAnnotation(Collections.min(intValues), Collections.max(intValues)); + } + + /** + * Returns a {@link Range} bounded by the values specified in the given {@code @Range} + * annotation. Also returns an appropriate range if an {@code @IntVal} annotation is passed. + * Returns {@code null} if the annotation is null or if the annotation is not an {@code + * IntRange}, {@code IntRangeFromPositive}, {@code IntVal}, or {@code ArrayLenRange}. + * + * @param rangeAnno a {@code @Range} annotation + * @return the {@link Range} that the annotation represents + */ + public @Nullable Range getRange(@Nullable AnnotationMirror rangeAnno) { + if (rangeAnno == null) { + return null; + } + switch (AnnotationUtils.annotationName(rangeAnno)) { + case INTRANGE_FROMPOS_NAME: + return Range.create(1, Integer.MAX_VALUE); + case INTRANGE_FROMNONNEG_NAME: + return Range.create(0, Integer.MAX_VALUE); + case INTRANGE_FROMGTENEGONE_NAME: + return Range.create(-1, Integer.MAX_VALUE); + case INTVAL_NAME: + return ValueCheckerUtils.getRangeFromValues(getIntValues(rangeAnno)); + case INTRANGE_NAME: + // Assume rangeAnno is well-formed, i.e., 'from' is less than or equal to 'to'. + return Range.create(getIntRangeFromValue(rangeAnno), getIntRangeToValue(rangeAnno)); + case ARRAYLENRANGE_NAME: + return Range.create( + getArrayLenRangeFromValue(rangeAnno), getArrayLenRangeToValue(rangeAnno)); + default: + return null; + } + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is no constant-value + * annotation so the argument is null. + * + *

          The method returns a list of {@code Long} but is named {@code getIntValues} because it + * supports the {@code @IntVal} annotation. + * + * @param intAnno an {@code @IntVal} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getIntValues(@PolyNull AnnotationMirror intAnno) { + if (intAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is no constant-value + * annotation so the argument is null. + * + * @param doubleAnno a {@code @DoubleVal} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getDoubleValues(@PolyNull AnnotationMirror doubleAnno) { + if (doubleAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray( + doubleAnno, doubleValValueElement, Double.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible array lengths as a sorted list with no duplicate values. Returns + * the empty list if no values are possible (for dead code). Returns null if any value is + * possible -- that is, if no estimate can be made -- and this includes when there is no + * constant-value annotation so the argument is null. + * + * @param arrayAnno an {@code @ArrayLen} annotation, or null + * @return the possible array lengths, deduplicated and sorted + */ + public @PolyNull List getArrayLength(@PolyNull AnnotationMirror arrayAnno) { + if (arrayAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray( + arrayAnno, arrayLenValueElement, Integer.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is no constant-value + * annotation so the argument is null. + * + * @param intAnno an {@code @IntVal} annotation, or null + * @return the values represented by the given {@code @IntVal} annotation + */ + public @PolyNull List getCharValues(@PolyNull AnnotationMirror intAnno) { + if (intAnno == null) { + return Collections.emptyList(); + } + List intValues = + AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); + List charValues = + CollectionsPlume.mapList((Long i) -> (char) i.intValue(), intValues); + Collections.sort(charValues); + // TODO: Should this be an unmodifiable list? + return new ArrayList<>(charValues); + } + + /** + * Returns the single possible boolean value, or null if there is not exactly one possible + * value. + * + * @see #getBooleanValues + * @param boolAnno a {@code @BoolVal} annotation, or null + * @return the single possible boolean value, on null if that is not the case + */ + public @Nullable Boolean getBooleanValue(@Nullable AnnotationMirror boolAnno) { + if (boolAnno == null) { + return null; + } + List boolValues = + AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); + Set boolSet = new TreeSet<>(boolValues); + if (boolSet.size() == 1) { + return boolSet.iterator().next(); + } return null; } - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty - * list if no values are possible (for dead code). Returns null if any value is possible -- that - * is, if no estimate can be made -- and this includes when there is no constant-value annotation - * so the argument is null. - * - *

          The method returns a list of {@code Long} but is named {@code getIntValues} because it - * supports the {@code @IntVal} annotation. - * - * @param intAnno an {@code @IntVal} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getIntValues(@PolyNull AnnotationMirror intAnno) { - if (intAnno == null) { - return null; - } - List list = AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty - * list if no values are possible (for dead code). Returns null if any value is possible -- that - * is, if no estimate can be made -- and this includes when there is no constant-value annotation - * so the argument is null. - * - * @param doubleAnno a {@code @DoubleVal} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getDoubleValues(@PolyNull AnnotationMirror doubleAnno) { - if (doubleAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray(doubleAnno, doubleValValueElement, Double.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible array lengths as a sorted list with no duplicate values. Returns - * the empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is no constant-value - * annotation so the argument is null. - * - * @param arrayAnno an {@code @ArrayLen} annotation, or null - * @return the possible array lengths, deduplicated and sorted - */ - public @PolyNull List getArrayLength(@PolyNull AnnotationMirror arrayAnno) { - if (arrayAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray(arrayAnno, arrayLenValueElement, Integer.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty - * list if no values are possible (for dead code). Returns null if any value is possible -- that - * is, if no estimate can be made -- and this includes when there is no constant-value annotation - * so the argument is null. - * - * @param intAnno an {@code @IntVal} annotation, or null - * @return the values represented by the given {@code @IntVal} annotation - */ - public @PolyNull List getCharValues(@PolyNull AnnotationMirror intAnno) { - if (intAnno == null) { - return Collections.emptyList(); - } - List intValues = - AnnotationUtils.getElementValueArray(intAnno, intValValueElement, Long.class); - List charValues = - CollectionsPlume.mapList((Long i) -> (char) i.intValue(), intValues); - Collections.sort(charValues); - // TODO: Should this be an unmodifiable list? - return new ArrayList<>(charValues); - } - - /** - * Returns the single possible boolean value, or null if there is not exactly one possible value. - * - * @see #getBooleanValues - * @param boolAnno a {@code @BoolVal} annotation, or null - * @return the single possible boolean value, on null if that is not the case - */ - public @Nullable Boolean getBooleanValue(@Nullable AnnotationMirror boolAnno) { - if (boolAnno == null) { - return null; - } - List boolValues = - AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); - Set boolSet = new TreeSet<>(boolValues); - if (boolSet.size() == 1) { - return boolSet.iterator().next(); - } - return null; - } - - /** - * Returns the set of possible boolean values as a sorted list with no duplicate values. Returns - * the empty list if no values are possible (for dead code). Returns null if any value is possible - * -- that is, if no estimate can be made -- and this includes when there is no constant-value - * annotation so the argument is null. - * - * @param boolAnno a {@code @BoolVal} annotation, or null - * @return a singleton or empty list of possible boolean values, or null - */ - public @Nullable List getBooleanValues(@Nullable AnnotationMirror boolAnno) { - if (boolAnno == null) { - return Collections.emptyList(); - } - List boolValues = - AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); - if (boolValues.size() < 2) { - return boolValues; - } - // Remove duplicates. - Set boolSet = new ArraySet<>(2); - boolSet.addAll(boolValues); - if (boolSet.size() > 1) { - // boolSet={true,false}; - return null; - } - return new ArrayList<>(boolSet); - } - - /** - * Returns the set of possible values as a sorted list with no duplicate values. Returns the empty - * list if no values are possible (for dead code). Returns null if any value is possible -- that - * is, if no estimate can be made -- and this includes when there is no constant-value annotation - * so the argument is null. - * - * @param stringAnno a {@code @StringVal} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getStringValues(@PolyNull AnnotationMirror stringAnno) { - if (stringAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray(stringAnno, stringValValueElement, String.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible -- - * that is, if no estimate can be made -- and this includes when there is no @MatchesRegex - * annotation so the argument is null. - * - * @param matchesRegexAnno a {@code @MatchesRegex} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getMatchesRegexValues(@PolyNull AnnotationMirror matchesRegexAnno) { - if (matchesRegexAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray( - matchesRegexAnno, matchesRegexValueElement, String.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the - * empty list if no values are possible (for dead code). Returns null if any value is possible -- - * that is, if no estimate can be made -- and this includes when there is no @DoesNotMatchRegex - * annotation so the argument is null. - * - * @param doesNotMatchRegexAnno a {@code @DoesNotMatchRegex} annotation, or null - * @return the possible values, deduplicated and sorted - */ - public @PolyNull List getDoesNotMatchRegexValues( - @PolyNull AnnotationMirror doesNotMatchRegexAnno) { - if (doesNotMatchRegexAnno == null) { - return null; - } - List list = - AnnotationUtils.getElementValueArray( - doesNotMatchRegexAnno, doesNotMatchRegexValueElement, String.class); - list = CollectionsPlume.withoutDuplicatesSorted(list); - return list; - } - - /** - * Returns true if {@link #isIntRange(AnnotationMirror)} returns true for any annotation in the - * given set. - * - * @param anmSet a set of annotations - * @return true if any annotation is {@link IntRange} or related - */ - public boolean isIntRange(AnnotationMirrorSet anmSet) { - for (AnnotationMirror anm : anmSet) { - if (isIntRange(anm)) { - return true; - } - } - return false; - } - - /** - * Returns true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link - * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne}. - * - * @param anno annotation mirror - * @return true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link - * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne} - */ - public boolean isIntRange(AnnotationMirror anno) { - String name = AnnotationUtils.annotationName(anno); - return name.equals(INTRANGE_NAME) - || name.equals(INTRANGE_FROMPOS_NAME) - || name.equals(INTRANGE_FROMNONNEG_NAME) - || name.equals(INTRANGE_FROMGTENEGONE_NAME); - } - - public int getMinLenValue(AnnotatedTypeMirror atm) { - return getMinLenValue(atm.getAnnotationInHierarchy(UNKNOWNVAL)); - } - - /** - * Used to find the maximum length of an array. Returns null if there is no minimum length known, - * or if the passed annotation is null. - */ - public @Nullable Integer getMaxLenValue(@Nullable AnnotationMirror annotation) { - if (annotation == null) { - return null; - } - switch (AnnotationUtils.annotationName(annotation)) { - case ARRAYLENRANGE_NAME: - return Long.valueOf(getRange(annotation).to).intValue(); - case ARRAYLEN_NAME: - return Collections.max(getArrayLength(annotation)); - case STRINGVAL_NAME: - return Collections.max( - ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); - default: + + /** + * Returns the set of possible boolean values as a sorted list with no duplicate values. Returns + * the empty list if no values are possible (for dead code). Returns null if any value is + * possible -- that is, if no estimate can be made -- and this includes when there is no + * constant-value annotation so the argument is null. + * + * @param boolAnno a {@code @BoolVal} annotation, or null + * @return a singleton or empty list of possible boolean values, or null + */ + public @Nullable List getBooleanValues(@Nullable AnnotationMirror boolAnno) { + if (boolAnno == null) { + return Collections.emptyList(); + } + List boolValues = + AnnotationUtils.getElementValueArray(boolAnno, boolValValueElement, Boolean.class); + if (boolValues.size() < 2) { + return boolValues; + } + // Remove duplicates. + Set boolSet = new ArraySet<>(2); + boolSet.addAll(boolValues); + if (boolSet.size() > 1) { + // boolSet={true,false}; + return null; + } + return new ArrayList<>(boolSet); + } + + /** + * Returns the set of possible values as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is no constant-value + * annotation so the argument is null. + * + * @param stringAnno a {@code @StringVal} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getStringValues(@PolyNull AnnotationMirror stringAnno) { + if (stringAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray( + stringAnno, stringValValueElement, String.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is no @MatchesRegex + * annotation so the argument is null. + * + * @param matchesRegexAnno a {@code @MatchesRegex} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getMatchesRegexValues( + @PolyNull AnnotationMirror matchesRegexAnno) { + if (matchesRegexAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray( + matchesRegexAnno, matchesRegexValueElement, String.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns the set of possible regexes as a sorted list with no duplicate values. Returns the + * empty list if no values are possible (for dead code). Returns null if any value is possible + * -- that is, if no estimate can be made -- and this includes when there is + * no @DoesNotMatchRegex annotation so the argument is null. + * + * @param doesNotMatchRegexAnno a {@code @DoesNotMatchRegex} annotation, or null + * @return the possible values, deduplicated and sorted + */ + public @PolyNull List getDoesNotMatchRegexValues( + @PolyNull AnnotationMirror doesNotMatchRegexAnno) { + if (doesNotMatchRegexAnno == null) { + return null; + } + List list = + AnnotationUtils.getElementValueArray( + doesNotMatchRegexAnno, doesNotMatchRegexValueElement, String.class); + list = CollectionsPlume.withoutDuplicatesSorted(list); + return list; + } + + /** + * Returns true if {@link #isIntRange(AnnotationMirror)} returns true for any annotation in the + * given set. + * + * @param anmSet a set of annotations + * @return true if any annotation is {@link IntRange} or related + */ + public boolean isIntRange(AnnotationMirrorSet anmSet) { + for (AnnotationMirror anm : anmSet) { + if (isIntRange(anm)) { + return true; + } + } + return false; + } + + /** + * Returns true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link + * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne}. + * + * @param anno annotation mirror + * @return true if {@code anno} is an {@link IntRange}, {@link IntRangeFromPositive}, {@link + * IntRangeFromNonNegative}, or {@link IntRangeFromGTENegativeOne} + */ + public boolean isIntRange(AnnotationMirror anno) { + String name = AnnotationUtils.annotationName(anno); + return name.equals(INTRANGE_NAME) + || name.equals(INTRANGE_FROMPOS_NAME) + || name.equals(INTRANGE_FROMNONNEG_NAME) + || name.equals(INTRANGE_FROMGTENEGONE_NAME); + } + + public int getMinLenValue(AnnotatedTypeMirror atm) { + return getMinLenValue(atm.getAnnotationInHierarchy(UNKNOWNVAL)); + } + + /** + * Used to find the maximum length of an array. Returns null if there is no minimum length + * known, or if the passed annotation is null. + */ + public @Nullable Integer getMaxLenValue(@Nullable AnnotationMirror annotation) { + if (annotation == null) { + return null; + } + switch (AnnotationUtils.annotationName(annotation)) { + case ARRAYLENRANGE_NAME: + return Long.valueOf(getRange(annotation).to).intValue(); + case ARRAYLEN_NAME: + return Collections.max(getArrayLength(annotation)); + case STRINGVAL_NAME: + return Collections.max( + ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); + default: + return null; + } + } + + /** + * Finds a minimum length of an array specified by the provided annotation. Returns null if + * there is no minimum length known, or if the passed annotation is null. + * + *

          Note that this routine handles actual {@link MinLen} annotations, because it is called by + * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms + * {@link MinLen} annotations into {@link ArrayLenRange} annotations. + */ + private @Nullable Integer getSpecifiedMinLenValue(@Nullable AnnotationMirror annotation) { + if (annotation == null) { + return null; + } + switch (AnnotationUtils.annotationName(annotation)) { + case MINLEN_NAME: + return getMinLenValueValue(annotation); + case ARRAYLENRANGE_NAME: + return Long.valueOf(getRange(annotation).from).intValue(); + case ARRAYLEN_NAME: + return Collections.min(getArrayLength(annotation)); + case STRINGVAL_NAME: + return Collections.min( + ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); + default: + return null; + } + } + + /** + * Used to find the minimum length of an array, which is useful for array bounds checking. + * Returns 0 if there is no minimum length known, or if the passed annotation is null. + * + *

          Note that this routine handles actual {@link MinLen} annotations, because it is called by + * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms + * {@link MinLen} annotations into {@link ArrayLenRange} annotations. + */ + public int getMinLenValue(@Nullable AnnotationMirror annotation) { + Integer minLen = getSpecifiedMinLenValue(annotation); + if (minLen == null || minLen < 0) { + return 0; + } else { + return minLen; + } + } + + /** + * Returns the minimum length of an array. + * + * @param annotations the annotations on the array expression + * @return the minimum length of an array + */ + public int getMinLenValue(AnnotationMirrorSet annotations) { + int result = 0; + for (AnnotationMirror annotation : annotations) { + Integer minLen = getSpecifiedMinLenValue(annotation); + if (minLen != null) { + result = Integer.min(result, minLen); + } + } + if (result < 0) { + return 0; + } else { + return result; + } + } + + /** + * Returns the smallest possible value that an integral annotation might take on. The passed + * {@code AnnotatedTypeMirror} should contain either an {@code @IntRange} annotation or an + * {@code @IntVal} annotation. Returns null if it does not. + * + * @param atm annotated type + * @return the smallest possible integral for which the {@code atm} could be the type + */ + public @Nullable Long getMinimumIntegralValue(AnnotatedTypeMirror atm) { + AnnotationMirror anm = atm.getAnnotationInHierarchy(UNKNOWNVAL); + if (AnnotationUtils.areSameByName(anm, INTVAL_NAME)) { + List possibleValues = getIntValues(anm); + return Collections.min(possibleValues); + } else if (isIntRange(anm)) { + Range range = getRange(anm); + return range.from; + } return null; } - } - - /** - * Finds a minimum length of an array specified by the provided annotation. Returns null if there - * is no minimum length known, or if the passed annotation is null. - * - *

          Note that this routine handles actual {@link MinLen} annotations, because it is called by - * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms - * {@link MinLen} annotations into {@link ArrayLenRange} annotations. - */ - private @Nullable Integer getSpecifiedMinLenValue(@Nullable AnnotationMirror annotation) { - if (annotation == null) { - return null; - } - switch (AnnotationUtils.annotationName(annotation)) { - case MINLEN_NAME: - return getMinLenValueValue(annotation); - case ARRAYLENRANGE_NAME: - return Long.valueOf(getRange(annotation).from).intValue(); - case ARRAYLEN_NAME: - return Collections.min(getArrayLength(annotation)); - case STRINGVAL_NAME: - return Collections.min( - ValueCheckerUtils.getLengthsForStringValues(getStringValues(annotation))); - default: + + /** + * Returns the minimum length of an array expression or 0 if the min length is unknown. + * + * @param sequenceExpression a Java expression + * @param tree expression tree or variable declaration + * @param currentPath path to local scope + * @return min length of sequenceExpression or 0 + */ + public int getMinLenFromString(String sequenceExpression, Tree tree, TreePath currentPath) { + AnnotationMirror lengthAnno; + JavaExpression expressionObj; + try { + expressionObj = parseJavaExpressionString(sequenceExpression, currentPath); + } catch (JavaExpressionParseException e) { + // ignore parse errors and return 0. + return 0; + } + + if (expressionObj instanceof ValueLiteral) { + ValueLiteral sequenceLiteral = (ValueLiteral) expressionObj; + Object sequenceLiteralValue = sequenceLiteral.getValue(); + if (sequenceLiteralValue instanceof String) { + return ((String) sequenceLiteralValue).length(); + } + } else if (expressionObj instanceof ArrayCreation) { + ArrayCreation arrayCreation = (ArrayCreation) expressionObj; + // This is only expected to support array creations in varargs methods + return arrayCreation.getInitializers().size(); + } else if (expressionObj instanceof ArrayAccess) { + List annoList = + expressionObj.getType().getAnnotationMirrors(); + for (AnnotationMirror anno : annoList) { + String ANNO_NAME = AnnotationUtils.annotationName(anno); + if (ANNO_NAME.equals(MINLEN_NAME)) { + return getMinLenValue(canonicalAnnotation(anno)); + } else if (ANNO_NAME.equals(ARRAYLEN_NAME) + || ANNO_NAME.equals(ARRAYLENRANGE_NAME)) { + return getMinLenValue(anno); + } + } + } + + lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLenRange.class); + if (lengthAnno == null) { + lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLen.class); + } + if (lengthAnno == null) { + lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, StringVal.class); + } + + if (lengthAnno == null) { + // Could not find a more precise type, so return 0; + return 0; + } + + return getMinLenValue(lengthAnno); + } + + /** + * Returns the annotation type mirror for the type of {@code expressionTree} with default + * annotations applied. + */ + @Override + public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { + TypeMirror type = TreeUtils.typeOf(expressionTree); + if (type.getKind() != TypeKind.VOID) { + AnnotatedTypeMirror atm = type(expressionTree); + addDefaultAnnotations(atm); + return atm; + } return null; } - } - - /** - * Used to find the minimum length of an array, which is useful for array bounds checking. Returns - * 0 if there is no minimum length known, or if the passed annotation is null. - * - *

          Note that this routine handles actual {@link MinLen} annotations, because it is called by - * {@link ValueAnnotatedTypeFactory#canonicalAnnotation(AnnotationMirror)}, which transforms - * {@link MinLen} annotations into {@link ArrayLenRange} annotations. - */ - public int getMinLenValue(@Nullable AnnotationMirror annotation) { - Integer minLen = getSpecifiedMinLenValue(annotation); - if (minLen == null || minLen < 0) { - return 0; - } else { - return minLen; - } - } - - /** - * Returns the minimum length of an array. - * - * @param annotations the annotations on the array expression - * @return the minimum length of an array - */ - public int getMinLenValue(AnnotationMirrorSet annotations) { - int result = 0; - for (AnnotationMirror annotation : annotations) { - Integer minLen = getSpecifiedMinLenValue(annotation); - if (minLen != null) { - result = Integer.min(result, minLen); - } - } - if (result < 0) { - return 0; - } else { - return result; - } - } - - /** - * Returns the smallest possible value that an integral annotation might take on. The passed - * {@code AnnotatedTypeMirror} should contain either an {@code @IntRange} annotation or an - * {@code @IntVal} annotation. Returns null if it does not. - * - * @param atm annotated type - * @return the smallest possible integral for which the {@code atm} could be the type - */ - public @Nullable Long getMinimumIntegralValue(AnnotatedTypeMirror atm) { - AnnotationMirror anm = atm.getAnnotationInHierarchy(UNKNOWNVAL); - if (AnnotationUtils.areSameByName(anm, INTVAL_NAME)) { - List possibleValues = getIntValues(anm); - return Collections.min(possibleValues); - } else if (isIntRange(anm)) { - Range range = getRange(anm); - return range.from; - } - return null; - } - - /** - * Returns the minimum length of an array expression or 0 if the min length is unknown. - * - * @param sequenceExpression a Java expression - * @param tree expression tree or variable declaration - * @param currentPath path to local scope - * @return min length of sequenceExpression or 0 - */ - public int getMinLenFromString(String sequenceExpression, Tree tree, TreePath currentPath) { - AnnotationMirror lengthAnno; - JavaExpression expressionObj; - try { - expressionObj = parseJavaExpressionString(sequenceExpression, currentPath); - } catch (JavaExpressionParseException e) { - // ignore parse errors and return 0. - return 0; - } - - if (expressionObj instanceof ValueLiteral) { - ValueLiteral sequenceLiteral = (ValueLiteral) expressionObj; - Object sequenceLiteralValue = sequenceLiteral.getValue(); - if (sequenceLiteralValue instanceof String) { - return ((String) sequenceLiteralValue).length(); - } - } else if (expressionObj instanceof ArrayCreation) { - ArrayCreation arrayCreation = (ArrayCreation) expressionObj; - // This is only expected to support array creations in varargs methods - return arrayCreation.getInitializers().size(); - } else if (expressionObj instanceof ArrayAccess) { - List annoList = expressionObj.getType().getAnnotationMirrors(); - for (AnnotationMirror anno : annoList) { - String ANNO_NAME = AnnotationUtils.annotationName(anno); - if (ANNO_NAME.equals(MINLEN_NAME)) { - return getMinLenValue(canonicalAnnotation(anno)); - } else if (ANNO_NAME.equals(ARRAYLEN_NAME) || ANNO_NAME.equals(ARRAYLENRANGE_NAME)) { - return getMinLenValue(anno); - } - } - } - - lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLenRange.class); - if (lengthAnno == null) { - lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, ArrayLen.class); - } - if (lengthAnno == null) { - lengthAnno = getAnnotationFromJavaExpression(expressionObj, tree, StringVal.class); - } - - if (lengthAnno == null) { - // Could not find a more precise type, so return 0; - return 0; - } - - return getMinLenValue(lengthAnno); - } - - /** - * Returns the annotation type mirror for the type of {@code expressionTree} with default - * annotations applied. - */ - @Override - public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { - TypeMirror type = TreeUtils.typeOf(expressionTree); - if (type.getKind() != TypeKind.VOID) { - AnnotatedTypeMirror atm = type(expressionTree); - addDefaultAnnotations(atm); - return atm; - } - return null; - } - - /** A fact about an array, such as its length, cannot be changed via side effects to the array. */ - @Override - public boolean isImmutable(TypeMirror type) { - if (type.getKind() == TypeKind.ARRAY) { - return true; - } - return super.isImmutable(type); - } + + /** + * A fact about an array, such as its length, cannot be changed via side effects to the array. + */ + @Override + public boolean isImmutable(TypeMirror type) { + if (type.getKind() == TypeKind.ARRAY) { + return true; + } + return super.isImmutable(type); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java index 6a458091eda..1e3c911c23d 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java @@ -1,12 +1,13 @@ package org.checkerframework.common.value; -import java.util.LinkedHashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.util.Range; import org.checkerframework.framework.source.SupportedOptions; +import java.util.LinkedHashSet; +import java.util.Set; + /** * The Constant Value Checker is a constant propagation analysis: for each variable, it determines * whether that variable's value can be known at compile time. @@ -19,47 +20,47 @@ * @checker_framework.manual #constant-value-checker Constant Value Checker */ @SupportedOptions({ - ValueChecker.REPORT_EVAL_WARNS, - ValueChecker.IGNORE_RANGE_OVERFLOW, - ValueChecker.NON_NULL_STRINGS_CONCATENATION + ValueChecker.REPORT_EVAL_WARNS, + ValueChecker.IGNORE_RANGE_OVERFLOW, + ValueChecker.NON_NULL_STRINGS_CONCATENATION }) public class ValueChecker extends BaseTypeChecker { - /** - * Command-line option to warn the user if a @StaticallyExecutable method can't load and run at - * compile time. - */ - public static final String REPORT_EVAL_WARNS = "reportEvalWarns"; + /** + * Command-line option to warn the user if a @StaticallyExecutable method can't load and run at + * compile time. + */ + public static final String REPORT_EVAL_WARNS = "reportEvalWarns"; - /** Command-line option to ignore the possibility of overflow for range annotations. */ - public static final String IGNORE_RANGE_OVERFLOW = "ignoreRangeOverflow"; + /** Command-line option to ignore the possibility of overflow for range annotations. */ + public static final String IGNORE_RANGE_OVERFLOW = "ignoreRangeOverflow"; - /** Command-line option that assumes most expressions in String concatenations can be null. */ - public static final String NON_NULL_STRINGS_CONCATENATION = "nonNullStringsConcatenation"; + /** Command-line option that assumes most expressions in String concatenations can be null. */ + public static final String NON_NULL_STRINGS_CONCATENATION = "nonNullStringsConcatenation"; - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ValueVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ValueVisitor(this); + } - @Override - protected Set> getImmediateSubcheckerClasses() { - // Don't call super otherwise MethodVal will be added as a subChecker - // which creates a circular dependency. - // Use the same Set implementation as super. - return new LinkedHashSet<>(0); - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Don't call super otherwise MethodVal will be added as a subChecker + // which creates a circular dependency. + // Use the same Set implementation as super. + return new LinkedHashSet<>(0); + } - @Override - public boolean shouldResolveReflection() { - // Because this checker is a subchecker of MethodVal, - // reflection can't be resolved. - return false; - } + @Override + public boolean shouldResolveReflection() { + // Because this checker is a subchecker of MethodVal, + // reflection can't be resolved. + return false; + } - @Override - public void typeProcessingOver() { - // Reset ignore overflow. - Range.ignoreOverflow = false; - super.typeProcessingOver(); - } + @Override + public void typeProcessingOver() { + // Reset ignore overflow. + Range.ignoreOverflow = false; + super.typeProcessingOver(); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java index 94c563d374f..6fdd198236e 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java @@ -1,12 +1,7 @@ package org.checkerframework.common.value; import com.sun.source.tree.Tree; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.mustcall.qual.MustCallUnknown; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.qual.IntRange; @@ -23,407 +18,419 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; -/** Utility methods for the Value Checker. */ -public class ValueCheckerUtils { +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; - /** Do not instantiate. */ - private ValueCheckerUtils() { - throw new TypeSystemError("do not instantiate"); - } +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; - /** - * Get a list of the values of an annotation, and then cast the values to a given type. - * - * @param anno the annotation that contains values - * @param castTo the type that is cast to - * @param atypeFactory the type factory - * @return a list of values after the casting - */ - public static List getValuesCastedToType( - AnnotationMirror anno, TypeMirror castTo, ValueAnnotatedTypeFactory atypeFactory) { - return getValuesCastedToType(anno, castTo, false, atypeFactory); - } +/** Utility methods for the Value Checker. */ +public class ValueCheckerUtils { - /** - * Get a list of the values of an annotation, and then cast the values to a given type. - * - * @param anno the annotation that contains values - * @param castTo the unannotated type that is casted to - * @param isUnsigned true if the type being casted to is unsigned - * @param atypeFactory the type factory - * @return a list of values after the casting - */ - public static List getValuesCastedToType( - AnnotationMirror anno, - TypeMirror castTo, - boolean isUnsigned, - ValueAnnotatedTypeFactory atypeFactory) { - Class castType = TypesUtils.getClassFromType(castTo); - List values; - switch (AnnotationUtils.annotationName(anno)) { - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - values = convertDoubleVal(anno, castType, castTo, atypeFactory); - break; - case ValueAnnotatedTypeFactory.INTVAL_NAME: - List longs = atypeFactory.getIntValues(anno); - values = convertIntVal(longs, castType, castTo, isUnsigned); - break; - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - Range range = atypeFactory.getRange(anno); - List rangeValues = getValuesFromRange(range, Long.class); - values = convertIntVal(rangeValues, castType, castTo, isUnsigned); - break; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - values = convertStringVal(anno, castType, atypeFactory); - break; - case ValueAnnotatedTypeFactory.BOOLVAL_NAME: - values = convertBoolVal(anno, castType, atypeFactory); - break; - case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - values = Collections.emptyList(); - break; - default: - values = null; + /** Do not instantiate. */ + private ValueCheckerUtils() { + throw new TypeSystemError("do not instantiate"); } - return values; - } - /** Get the minimum and maximum of a list and return a range bounded by them. */ - public static @Nullable Range getRangeFromValues(List values) { - if (values == null) { - return null; - } else if (values.isEmpty()) { - return Range.NOTHING; + /** + * Get a list of the values of an annotation, and then cast the values to a given type. + * + * @param anno the annotation that contains values + * @param castTo the type that is cast to + * @param atypeFactory the type factory + * @return a list of values after the casting + */ + public static List getValuesCastedToType( + AnnotationMirror anno, TypeMirror castTo, ValueAnnotatedTypeFactory atypeFactory) { + return getValuesCastedToType(anno, castTo, false, atypeFactory); } - return Range.create(values); - } - /** - * Converts a long value to a boxed numeric type. - * - * @param value a long value - * @param expectedType the boxed numeric type of the result - * @return {@code value} converted to {@code expectedType} using standard conversion rules - */ - private static T convertLongToType(long value, Class expectedType) { - Object convertedValue; - if (expectedType == Integer.class) { - convertedValue = (int) value; - } else if (expectedType == Short.class) { - convertedValue = (short) value; - } else if (expectedType == Byte.class) { - convertedValue = (byte) value; - } else if (expectedType == Long.class) { - convertedValue = value; - } else if (expectedType == Double.class) { - convertedValue = (double) value; - } else if (expectedType == Float.class) { - convertedValue = (float) value; - } else if (expectedType == Character.class) { - convertedValue = (char) value; - } else { - throw new UnsupportedOperationException( - "ValueCheckerUtils: unexpected class: " + expectedType); + /** + * Get a list of the values of an annotation, and then cast the values to a given type. + * + * @param anno the annotation that contains values + * @param castTo the unannotated type that is casted to + * @param isUnsigned true if the type being casted to is unsigned + * @param atypeFactory the type factory + * @return a list of values after the casting + */ + public static List getValuesCastedToType( + AnnotationMirror anno, + TypeMirror castTo, + boolean isUnsigned, + ValueAnnotatedTypeFactory atypeFactory) { + Class castType = TypesUtils.getClassFromType(castTo); + List values; + switch (AnnotationUtils.annotationName(anno)) { + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + values = convertDoubleVal(anno, castType, castTo, atypeFactory); + break; + case ValueAnnotatedTypeFactory.INTVAL_NAME: + List longs = atypeFactory.getIntValues(anno); + values = convertIntVal(longs, castType, castTo, isUnsigned); + break; + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + Range range = atypeFactory.getRange(anno); + List rangeValues = getValuesFromRange(range, Long.class); + values = convertIntVal(rangeValues, castType, castTo, isUnsigned); + break; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + values = convertStringVal(anno, castType, atypeFactory); + break; + case ValueAnnotatedTypeFactory.BOOLVAL_NAME: + values = convertBoolVal(anno, castType, atypeFactory); + break; + case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + values = Collections.emptyList(); + break; + default: + values = null; + } + return values; } - return expectedType.cast(convertedValue); - } - /** - * Get all possible values from the given type and cast them into a boxed primitive type. Returns - * null if the list would have length greater than {@link ValueAnnotatedTypeFactory#MAX_VALUES}. - * - *

          {@code expectedType} must be a boxed type, not a primitive type, because primitive types - * cannot be stored in a list. - * - * @param the type of the values to obtain - * @param range the given range - * @param expectedType the expected type - * @return a list of all the values in the range, or null if there would be more than {@link - * ValueAnnotatedTypeFactory#MAX_VALUES} - */ - public static @Nullable List getValuesFromRange( - @Nullable Range range, Class expectedType) { - if (range == null || range.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - return null; + /** Get the minimum and maximum of a list and return a range bounded by them. */ + public static @Nullable Range getRangeFromValues(List values) { + if (values == null) { + return null; + } else if (values.isEmpty()) { + return Range.NOTHING; + } + return Range.create(values); } - if (range.isNothing()) { - return Collections.emptyList(); + + /** + * Converts a long value to a boxed numeric type. + * + * @param value a long value + * @param expectedType the boxed numeric type of the result + * @return {@code value} converted to {@code expectedType} using standard conversion rules + */ + private static T convertLongToType(long value, Class expectedType) { + Object convertedValue; + if (expectedType == Integer.class) { + convertedValue = (int) value; + } else if (expectedType == Short.class) { + convertedValue = (short) value; + } else if (expectedType == Byte.class) { + convertedValue = (byte) value; + } else if (expectedType == Long.class) { + convertedValue = value; + } else if (expectedType == Double.class) { + convertedValue = (double) value; + } else if (expectedType == Float.class) { + convertedValue = (float) value; + } else if (expectedType == Character.class) { + convertedValue = (char) value; + } else { + throw new UnsupportedOperationException( + "ValueCheckerUtils: unexpected class: " + expectedType); + } + return expectedType.cast(convertedValue); } - // The subtraction does not overflow, because the width has already been checked, so the - // bound difference is less than ValueAnnotatedTypeFactory.MAX_VALUES. - long boundDifference = range.to - range.from; + /** + * Get all possible values from the given type and cast them into a boxed primitive type. + * Returns null if the list would have length greater than {@link + * ValueAnnotatedTypeFactory#MAX_VALUES}. + * + *

          {@code expectedType} must be a boxed type, not a primitive type, because primitive types + * cannot be stored in a list. + * + * @param the type of the values to obtain + * @param range the given range + * @param expectedType the expected type + * @return a list of all the values in the range, or null if there would be more than {@link + * ValueAnnotatedTypeFactory#MAX_VALUES} + */ + public static @Nullable List getValuesFromRange( + @Nullable Range range, Class expectedType) { + if (range == null || range.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + return null; + } + if (range.isNothing()) { + return Collections.emptyList(); + } - // Each value is computed as a sum of the first value and an offset within the range, - // to avoid having range.to as an upper bound of the loop. range.to can be Long.MAX_VALUE, - // in which case a comparison value <= range.to would be always true. - // boundDifference is always much smaller than Long.MAX_VALUE - List values = new ArrayList<>((int) boundDifference + 1); - for (long offset = 0; offset <= boundDifference; offset++) { - long value = range.from + offset; - values.add(convertLongToType(value, expectedType)); + // The subtraction does not overflow, because the width has already been checked, so the + // bound difference is less than ValueAnnotatedTypeFactory.MAX_VALUES. + long boundDifference = range.to - range.from; + + // Each value is computed as a sum of the first value and an offset within the range, + // to avoid having range.to as an upper bound of the loop. range.to can be Long.MAX_VALUE, + // in which case a comparison value <= range.to would be always true. + // boundDifference is always much smaller than Long.MAX_VALUE + List values = new ArrayList<>((int) boundDifference + 1); + for (long offset = 0; offset <= boundDifference; offset++) { + long value = range.from + offset; + values.add(convertLongToType(value, expectedType)); + } + return values; } - return values; - } - /** - * Converts a list of objects to a list of their string representations. - * - * @param origValues the objects to format - * @return a list of the formatted objects - */ - private static @Nullable List convertToStringVal( - List origValues) { - if (origValues == null) { - return null; + /** + * Converts a list of objects to a list of their string representations. + * + * @param origValues the objects to format + * @return a list of the formatted objects + */ + private static @Nullable List convertToStringVal( + List origValues) { + if (origValues == null) { + return null; + } + return CollectionsPlume.mapList(Object::toString, origValues); } - return CollectionsPlume.mapList(Object::toString, origValues); - } - /** - * Convert the {@code value} argument/element of a @BoolVal annotation into a list. - * - * @param anno a @BoolVal annotation - * @param newClass if String.class, the returned list is a {@code List} - * @param atypeFactory the type factory, used for obtaining fields/elements from annotations - * @return the {@code value} of a @BoolVal annotation, as a {@code List} or a {@code - * List} - */ - private static List convertBoolVal( - AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { - List bools = - AnnotationUtils.getElementValueArray(anno, atypeFactory.boolValValueElement, Boolean.class); + /** + * Convert the {@code value} argument/element of a @BoolVal annotation into a list. + * + * @param anno a @BoolVal annotation + * @param newClass if String.class, the returned list is a {@code List} + * @param atypeFactory the type factory, used for obtaining fields/elements from annotations + * @return the {@code value} of a @BoolVal annotation, as a {@code List} or a {@code + * List} + */ + private static List convertBoolVal( + AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { + List bools = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.boolValValueElement, Boolean.class); - if (newClass == String.class) { - return convertToStringVal(bools); + if (newClass == String.class) { + return convertToStringVal(bools); + } + return bools; } - return bools; - } - /** - * Convert the {@code value} argument/element of a {@code @StringVal} annotation into a list. - * - * @param anno a {@code @StringVal} annotation - * @param newClass if char[].class, the returned list is a {@code List} - * @param atypeFactory the type factory, used for obtaining fields/elements from annotations - * @return the {@code value} of a {@code @StringVal} annotation, as a {@code List} or a - * {@code List} - */ - private static List convertStringVal( - AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { - List strings = atypeFactory.getStringValues(anno); - if (newClass == char[].class) { - return CollectionsPlume.mapList(String::toCharArray, strings); + /** + * Convert the {@code value} argument/element of a {@code @StringVal} annotation into a list. + * + * @param anno a {@code @StringVal} annotation + * @param newClass if char[].class, the returned list is a {@code List} + * @param atypeFactory the type factory, used for obtaining fields/elements from annotations + * @return the {@code value} of a {@code @StringVal} annotation, as a {@code List} or a + * {@code List} + */ + private static List convertStringVal( + AnnotationMirror anno, Class newClass, ValueAnnotatedTypeFactory atypeFactory) { + List strings = atypeFactory.getStringValues(anno); + if (newClass == char[].class) { + return CollectionsPlume.mapList(String::toCharArray, strings); + } + return strings; } - return strings; - } - /** - * Convert a list of longs to a given type - * - * @param longs the integral values to convert - * @param newClass determines the type of the result - * @param newType the type to which to cast, if newClass is numeric - * @param isUnsigned if true, treat {@code newType} as unsigned - * @return the {@code value} of a {@code @IntVal} annotation, as a {@code List} or a - * {@code List} - */ - private static @Nullable List convertIntVal( - List longs, Class newClass, TypeMirror newType, boolean isUnsigned) { - if (longs == null) { - return null; - } - if (newClass == String.class) { - return convertToStringVal(longs); - } else if (newClass == Character.class || newClass == char.class) { - return CollectionsPlume.mapList((Long l) -> (char) l.longValue(), longs); - } else if (newClass == Boolean.class) { - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: can't convert integral type to boolean"); + /** + * Convert a list of longs to a given type + * + * @param longs the integral values to convert + * @param newClass determines the type of the result + * @param newType the type to which to cast, if newClass is numeric + * @param isUnsigned if true, treat {@code newType} as unsigned + * @return the {@code value} of a {@code @IntVal} annotation, as a {@code List} or a + * {@code List} + */ + private static @Nullable List convertIntVal( + List longs, Class newClass, TypeMirror newType, boolean isUnsigned) { + if (longs == null) { + return null; + } + if (newClass == String.class) { + return convertToStringVal(longs); + } else if (newClass == Character.class || newClass == char.class) { + return CollectionsPlume.mapList((Long l) -> (char) l.longValue(), longs); + } else if (newClass == Boolean.class) { + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: can't convert integral type to boolean"); + } + return NumberUtils.castNumbers(newType, isUnsigned, longs); } - return NumberUtils.castNumbers(newType, isUnsigned, longs); - } - /** - * Convert the {@code value} argument/element of a @StringVal annotation into a list. - * - * @param anno a {@code @DoubleVal} annotation - * @param newClass the component type for the returned list - * @param newType the component type for the returned list - * @param atypeFactory the type factory, used for obtaining fields/elements from annotations - * @return the {@code value} of a {@code @DoubleVal} annotation - */ - private static @Nullable List convertDoubleVal( - AnnotationMirror anno, - Class newClass, - TypeMirror newType, - ValueAnnotatedTypeFactory atypeFactory) { - List doubles = atypeFactory.getDoubleValues(anno); - if (doubles == null) { - return null; - } - if (newClass == String.class) { - return convertToStringVal(doubles); - } else if (newClass == Character.class || newClass == char.class) { - return CollectionsPlume.mapList((Double l) -> (char) l.doubleValue(), doubles); - } else if (newClass == Boolean.class) { - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: can't convert double to boolean"); + /** + * Convert the {@code value} argument/element of a @StringVal annotation into a list. + * + * @param anno a {@code @DoubleVal} annotation + * @param newClass the component type for the returned list + * @param newType the component type for the returned list + * @param atypeFactory the type factory, used for obtaining fields/elements from annotations + * @return the {@code value} of a {@code @DoubleVal} annotation + */ + private static @Nullable List convertDoubleVal( + AnnotationMirror anno, + Class newClass, + TypeMirror newType, + ValueAnnotatedTypeFactory atypeFactory) { + List doubles = atypeFactory.getDoubleValues(anno); + if (doubles == null) { + return null; + } + if (newClass == String.class) { + return convertToStringVal(doubles); + } else if (newClass == Character.class || newClass == char.class) { + return CollectionsPlume.mapList((Double l) -> (char) l.doubleValue(), doubles); + } else if (newClass == Boolean.class) { + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: can't convert double to boolean"); + } + return NumberUtils.castNumbers(newType, doubles); } - return NumberUtils.castNumbers(newType, doubles); - } - /** - * Gets a list of lengths for a list of string values. - * - * @param values list of string values - * @return list of unique lengths of strings in {@code values} - */ - public static List getLengthsForStringValues(List values) { - List lengths = CollectionsPlume.mapList(String::length, values); - return CollectionsPlume.withoutDuplicatesSorted(lengths); - } - - /** - * Returns a range representing the possible integral values represented by the passed {@code - * AnnotatedTypeMirror}. If the passed {@code AnnotatedTypeMirror} does not contain an {@code - * IntRange} annotation or an {@code IntVal} annotation, returns null. - */ - public static @Nullable Range getPossibleValues( - AnnotatedTypeMirror valueType, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { - if (valueAnnotatedTypeFactory.isIntRange(valueType.getAnnotations())) { - return valueAnnotatedTypeFactory.getRange(valueType.getAnnotation(IntRange.class)); - } else { - List values = - valueAnnotatedTypeFactory.getIntValues(valueType.getAnnotation(IntVal.class)); - if (values != null) { - return Range.create(values); - } else { - return null; - } + /** + * Gets a list of lengths for a list of string values. + * + * @param values list of string values + * @return list of unique lengths of strings in {@code values} + */ + public static List getLengthsForStringValues(List values) { + List lengths = CollectionsPlume.mapList(String::length, values); + return CollectionsPlume.withoutDuplicatesSorted(lengths); } - } - /** - * Either returns the exact value of the given tree according to the Constant Value Checker, or - * null if the exact value is not known. This method should only be used by clients who need - * exactly one value -- such as the LBC's binary operator rules -- and not by those that need to - * know whether a valueType belongs to a particular qualifier. - */ - public static @Nullable Long getExactValue(Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null && possibleValues.from == possibleValues.to) { - return possibleValues.from; - } else { - return null; + /** + * Returns a range representing the possible integral values represented by the passed {@code + * AnnotatedTypeMirror}. If the passed {@code AnnotatedTypeMirror} does not contain an {@code + * IntRange} annotation or an {@code IntVal} annotation, returns null. + */ + public static @Nullable Range getPossibleValues( + AnnotatedTypeMirror valueType, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { + if (valueAnnotatedTypeFactory.isIntRange(valueType.getAnnotations())) { + return valueAnnotatedTypeFactory.getRange(valueType.getAnnotation(IntRange.class)); + } else { + List values = + valueAnnotatedTypeFactory.getIntValues(valueType.getAnnotation(IntVal.class)); + if (values != null) { + return Range.create(values); + } else { + return null; + } + } } - } - /** - * Returns the exact value of an annotated element according to the Constant Value Checker, or - * null if the exact value is not known. - * - * @param element the element to get the exact value from - * @param factory a ValueAnnotatedTypeFactory used for annotation accessing - * @return the exact value of the element if it is constant, or null otherwise - */ - public static @Nullable Long getExactValue(Element element, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(element); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null && possibleValues.from == possibleValues.to) { - return possibleValues.from; - } else { - return null; + /** + * Either returns the exact value of the given tree according to the Constant Value Checker, or + * null if the exact value is not known. This method should only be used by clients who need + * exactly one value -- such as the LBC's binary operator rules -- and not by those that need to + * know whether a valueType belongs to a particular qualifier. + */ + public static @Nullable Long getExactValue(Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null && possibleValues.from == possibleValues.to) { + return possibleValues.from; + } else { + return null; + } } - } - /** - * Either returns the exact string value of the given tree according to the Constant Value - * Checker, or null if the exact value is not known. This method should only be used by clients - * who need exactly one value and not by those that need to know whether a valueType belongs to a - * particular qualifier. - */ - public static @Nullable String getExactStringValue(Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - if (valueType.hasAnnotation(StringVal.class)) { - AnnotationMirror valueAnno = valueType.getAnnotation(StringVal.class); - List possibleValues = - AnnotationUtils.getElementValueArray( - valueAnno, factory.stringValValueElement, String.class); - if (possibleValues.size() == 1) { - return possibleValues.get(0); - } + /** + * Returns the exact value of an annotated element according to the Constant Value Checker, or + * null if the exact value is not known. + * + * @param element the element to get the exact value from + * @param factory a ValueAnnotatedTypeFactory used for annotation accessing + * @return the exact value of the element if it is constant, or null otherwise + */ + public static @Nullable Long getExactValue(Element element, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(element); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null && possibleValues.from == possibleValues.to) { + return possibleValues.from; + } else { + return null; + } } - return null; - } - /** - * Finds the minimum value in a Value Checker type. If there is no information (such as when the - * list of possible values is empty or null), returns null. Otherwise, returns the smallest value - * in the list of possible values. - */ - public static @Nullable Long getMinValue(Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null) { - return possibleValues.from; - } else { - return null; + /** + * Either returns the exact string value of the given tree according to the Constant Value + * Checker, or null if the exact value is not known. This method should only be used by clients + * who need exactly one value and not by those that need to know whether a valueType belongs to + * a particular qualifier. + */ + public static @Nullable String getExactStringValue( + Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + if (valueType.hasAnnotation(StringVal.class)) { + AnnotationMirror valueAnno = valueType.getAnnotation(StringVal.class); + List possibleValues = + AnnotationUtils.getElementValueArray( + valueAnno, factory.stringValValueElement, String.class); + if (possibleValues.size() == 1) { + return possibleValues.get(0); + } + } + return null; } - } - /** - * Finds the maximum value in a Value Checker type. If there is no information (such as when the - * list of possible values is empty or null), returns null. Otherwise, returns the smallest value - * in the list of possible values. - */ - public static @Nullable Long getMaxValue(Tree tree, ValueAnnotatedTypeFactory factory) { - AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); - Range possibleValues = getPossibleValues(valueType, factory); - if (possibleValues != null) { - return possibleValues.to; - } else { - return null; + /** + * Finds the minimum value in a Value Checker type. If there is no information (such as when the + * list of possible values is empty or null), returns null. Otherwise, returns the smallest + * value in the list of possible values. + */ + public static @Nullable Long getMinValue(Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null) { + return possibleValues.from; + } else { + return null; + } } - } - /** - * Looks up the minlen of a member select tree. The tree must be an access to a sequence length. - */ - public static @Nullable Integer getMinLenFromTree(Tree tree, ValueAnnotatedTypeFactory valueATF) { - AnnotatedTypeMirror minLenType = valueATF.getAnnotatedType(tree); - Long min = valueATF.getMinimumIntegralValue(minLenType); - if (min == null) { - return null; + /** + * Finds the maximum value in a Value Checker type. If there is no information (such as when the + * list of possible values is empty or null), returns null. Otherwise, returns the smallest + * value in the list of possible values. + */ + public static @Nullable Long getMaxValue(Tree tree, ValueAnnotatedTypeFactory factory) { + AnnotatedTypeMirror valueType = factory.getAnnotatedType(tree); + Range possibleValues = getPossibleValues(valueType, factory); + if (possibleValues != null) { + return possibleValues.to; + } else { + return null; + } } - if (min < 0 || min > Integer.MAX_VALUE) { - min = 0L; + + /** + * Looks up the minlen of a member select tree. The tree must be an access to a sequence length. + */ + public static @Nullable Integer getMinLenFromTree( + Tree tree, ValueAnnotatedTypeFactory valueATF) { + AnnotatedTypeMirror minLenType = valueATF.getAnnotatedType(tree); + Long min = valueATF.getMinimumIntegralValue(minLenType); + if (min == null) { + return null; + } + if (min < 0 || min > Integer.MAX_VALUE) { + min = 0L; + } + return min.intValue(); } - return min.intValue(); - } - /** - * Queries the Value Checker to determine if there is a known minimum length for the array - * represented by {@code tree}. If not, returns 0. - */ - public static int getMinLen(Tree tree, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { - AnnotatedTypeMirror minLenType = valueAnnotatedTypeFactory.getAnnotatedType(tree); - return valueAnnotatedTypeFactory.getMinLenValue(minLenType); - } + /** + * Queries the Value Checker to determine if there is a known minimum length for the array + * represented by {@code tree}. If not, returns 0. + */ + public static int getMinLen(Tree tree, ValueAnnotatedTypeFactory valueAnnotatedTypeFactory) { + AnnotatedTypeMirror minLenType = valueAnnotatedTypeFactory.getAnnotatedType(tree); + return valueAnnotatedTypeFactory.getMinLenValue(minLenType); + } - /** - * Optimize the given JavaExpression. See {@link JavaExpressionOptimizer} for more details. - * - * @param je the expression to optimize - * @param factory the annotated type factory - * @return an optimized version of the argument - */ - public static JavaExpression optimize(JavaExpression je, AnnotatedTypeFactory factory) { - ValueAnnotatedTypeFactory vatf = - ((GenericAnnotatedTypeFactory) factory) - .getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); - return new JavaExpressionOptimizer(vatf == null ? factory : vatf).convert(je); - } + /** + * Optimize the given JavaExpression. See {@link JavaExpressionOptimizer} for more details. + * + * @param je the expression to optimize + * @param factory the annotated type factory + * @return an optimized version of the argument + */ + public static JavaExpression optimize(JavaExpression je, AnnotatedTypeFactory factory) { + ValueAnnotatedTypeFactory vatf = + ((GenericAnnotatedTypeFactory) factory) + .getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); + return new JavaExpressionOptimizer(vatf == null ? factory : vatf).convert(je); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java index 0338c2362cb..262ca839d7c 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java @@ -1,147 +1,151 @@ package org.checkerframework.common.value; import com.sun.source.tree.Tree; + +import org.checkerframework.javacutil.TreeUtils; + import java.util.List; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; -import org.checkerframework.javacutil.TreeUtils; /** Stores methods that have special handling in the value checker. */ class ValueMethodIdentifier { - /** String.length() method. */ - private final ExecutableElement lengthMethod; - - /** Array.getLength() method. */ - private final ExecutableElement getLengthMethod; - - /** String.startsWith(String) method. */ - private final ExecutableElement startsWithMethod; - - /** String.endsWith(String) method. */ - private final ExecutableElement endsWithMethod; - - /** The {@code java.lang.Math#min()} methods. */ - private final List mathMinMethods; - - /** The {@code java.lang.Math#max()} methods. */ - private final List mathMaxMethods; - - /** Arrays.copyOf() methods. */ - private final List copyOfMethods; - - /** - * Initialize elements with methods that have special handling in the value checker. - * - * @param processingEnv the processing environment - */ - public ValueMethodIdentifier(ProcessingEnvironment processingEnv) { - lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); - getLengthMethod = TreeUtils.getMethod("java.lang.reflect.Array", "getLength", 1, processingEnv); - startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv); - endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv); - mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); - mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); - copyOfMethods = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv); - copyOfMethods.add(TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); - } - - /** - * Returns true iff the argument is an invocation of Math.min. - * - * @param methodTree a tree - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of Math.min - */ - public boolean isMathMin(Tree methodTree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); - } - - /** - * Returns true iff the argument is an invocation of Math.max. - * - * @param methodTree a tree - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of Math.max - */ - public boolean isMathMax(Tree methodTree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); - } - - /** - * Returns true if a tree is an invocation of the {@code String.length()} method. - * - * @param tree a tree - * @param processingEnv the processing environment - * @return true if a tree is an invocation of the {@code String.length()} method - */ - public boolean isStringLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, lengthMethod, processingEnv); - } - - /** - * Returns true if a tree is an invocation of the {@code Array.getLength()} method. - * - * @param tree tree to check - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of {@code Array.getLength()} method - */ - public boolean isArrayGetLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, getLengthMethod, processingEnv); - } - - /** - * Returns true if a method is the {@code String.length()} method. - * - * @param method the element to check - * @return true iff the argument methid is {@code String.length()} method - */ - public boolean isStringLengthMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(lengthMethod); - } - - /** - * Returns true if a method is the {@code Array.getLength()} method. - * - * @param method the element to check - * @return true iff the argument method is {@code Array.getLength()} method - */ - public boolean isArrayGetLengthMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(getLengthMethod); - } - - /** - * Returns true if a method is the {@code String.startsWith(String)} method. - * - * @param method the element to check - * @return true iff the argument method is {@code String.startsWith(String)} method - */ - public boolean isStartsWithMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(startsWithMethod); - } - - /** - * Returns true if a method is the {@code String.endsWith(String)} method. - * - * @param method a method - * @return true if the method is {@code String.endsWith(String)} - */ - public boolean isEndsWithMethod(ExecutableElement method) { - // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden - return method.equals(endsWithMethod); - } - - /** - * Returns true if a tree is an invocation of the {@code Arrays.copyOf()} method. - * - * @param tree tree to check - * @param processingEnv the processing environment - * @return true iff the argument is an invocation of {@code Arrays.copyOf()} method - */ - public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) { - return TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv); - } + /** String.length() method. */ + private final ExecutableElement lengthMethod; + + /** Array.getLength() method. */ + private final ExecutableElement getLengthMethod; + + /** String.startsWith(String) method. */ + private final ExecutableElement startsWithMethod; + + /** String.endsWith(String) method. */ + private final ExecutableElement endsWithMethod; + + /** The {@code java.lang.Math#min()} methods. */ + private final List mathMinMethods; + + /** The {@code java.lang.Math#max()} methods. */ + private final List mathMaxMethods; + + /** Arrays.copyOf() methods. */ + private final List copyOfMethods; + + /** + * Initialize elements with methods that have special handling in the value checker. + * + * @param processingEnv the processing environment + */ + public ValueMethodIdentifier(ProcessingEnvironment processingEnv) { + lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); + getLengthMethod = + TreeUtils.getMethod("java.lang.reflect.Array", "getLength", 1, processingEnv); + startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv); + endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv); + mathMinMethods = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); + mathMaxMethods = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); + copyOfMethods = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv); + copyOfMethods.add(TreeUtils.getMethod("java.util.Arrays", "copyOf", 3, processingEnv)); + } + + /** + * Returns true iff the argument is an invocation of Math.min. + * + * @param methodTree a tree + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of Math.min + */ + public boolean isMathMin(Tree methodTree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(methodTree, mathMinMethods, processingEnv); + } + + /** + * Returns true iff the argument is an invocation of Math.max. + * + * @param methodTree a tree + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of Math.max + */ + public boolean isMathMax(Tree methodTree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(methodTree, mathMaxMethods, processingEnv); + } + + /** + * Returns true if a tree is an invocation of the {@code String.length()} method. + * + * @param tree a tree + * @param processingEnv the processing environment + * @return true if a tree is an invocation of the {@code String.length()} method + */ + public boolean isStringLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, lengthMethod, processingEnv); + } + + /** + * Returns true if a tree is an invocation of the {@code Array.getLength()} method. + * + * @param tree tree to check + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of {@code Array.getLength()} method + */ + public boolean isArrayGetLengthInvocation(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, getLengthMethod, processingEnv); + } + + /** + * Returns true if a method is the {@code String.length()} method. + * + * @param method the element to check + * @return true iff the argument methid is {@code String.length()} method + */ + public boolean isStringLengthMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(lengthMethod); + } + + /** + * Returns true if a method is the {@code Array.getLength()} method. + * + * @param method the element to check + * @return true iff the argument method is {@code Array.getLength()} method + */ + public boolean isArrayGetLengthMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(getLengthMethod); + } + + /** + * Returns true if a method is the {@code String.startsWith(String)} method. + * + * @param method the element to check + * @return true iff the argument method is {@code String.startsWith(String)} method + */ + public boolean isStartsWithMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(startsWithMethod); + } + + /** + * Returns true if a method is the {@code String.endsWith(String)} method. + * + * @param method a method + * @return true if the method is {@code String.endsWith(String)} + */ + public boolean isEndsWithMethod(ExecutableElement method) { + // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden + return method.equals(endsWithMethod); + } + + /** + * Returns true if a tree is an invocation of the {@code Arrays.copyOf()} method. + * + * @param tree tree to check + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of {@code Arrays.copyOf()} method + */ + public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, copyOfMethods, processingEnv); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java index 02e37050c99..86d4b69eb9d 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java @@ -1,11 +1,5 @@ package org.checkerframework.common.value; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.regex.qual.Regex; import org.checkerframework.common.value.util.Range; @@ -15,562 +9,593 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.RegexUtil; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; + /** The qualifier hierarchy for the Value type system. */ final class ValueQualifierHierarchy extends ElementQualifierHierarchy { - // This shadows the same-named field in GenericAnnotatedTypeFactory, but has a more specific - // type. - /** The type factory to use. */ - @SuppressWarnings("HidingField") - private final ValueAnnotatedTypeFactory atypeFactory; - - /** - * Creates a ValueQualifierHierarchy from the given classes. - * - * @param atypeFactory a ValueAnnotatedTypeFactory - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @deprecated use {@link #ValueQualifierHierarchy(Collection, ValueAnnotatedTypeFactory)} which - * has the arguments in the other order - */ - @Deprecated // 2023-05-23 - ValueQualifierHierarchy( - ValueAnnotatedTypeFactory atypeFactory, - Collection> qualifierClasses) { - this(qualifierClasses, atypeFactory); - } - - /** - * Creates a ValueQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param atypeFactory the associated type factory - */ - ValueQualifierHierarchy( - Collection> qualifierClasses, - ValueAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, atypeFactory.getElementUtils(), atypeFactory); - this.atypeFactory = atypeFactory; - } - - /** - * Computes greatest lower bound of a @StringVal annotation with another Value Checker annotation. - * - * @param stringValAnno annotation of type @StringVal - * @param otherAnno annotation from the value checker hierarchy - * @return greatest lower bound of {@code stringValAnno} and {@code otherAnno} - */ - private AnnotationMirror glbOfStringVal( - AnnotationMirror stringValAnno, AnnotationMirror otherAnno) { - List values = atypeFactory.getStringValues(stringValAnno); - switch (AnnotationUtils.annotationName(otherAnno)) { - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - // Intersection of value lists - List otherValues = atypeFactory.getStringValues(otherAnno); - values.retainAll(otherValues); - break; - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - // Retain strings of correct lengths - List otherLengths = atypeFactory.getArrayLength(otherAnno); - ArrayList result = new ArrayList<>(values.size()); - for (String s : values) { - if (otherLengths.contains(s.length())) { - result.add(s); - } - } - values = result; - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - // Retain strings of lengths from a range - Range otherRange = atypeFactory.getRange(otherAnno); - ArrayList range = new ArrayList<>(values.size()); - for (String s : values) { - if (otherRange.contains(s.length())) { - range.add(s); - } - } - values = range; - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - List<@Regex String> matchesRegexes = - AnnotationUtils.getElementValueArray( - otherAnno, atypeFactory.matchesRegexValueElement, String.class); - // Retain the @StringVal values such that one of the regexes matches it. - values = RegexUtil.matchesSomeRegex(values, matchesRegexes); - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - List<@Regex String> doesNotMatchRegexes = - AnnotationUtils.getElementValueArray( - otherAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); - // Retain the @StringVal values such that none of the regexes matches it. - values = RegexUtil.matchesNoRegex(values, doesNotMatchRegexes); - break; - default: - return atypeFactory.BOTTOMVAL; - } + // This shadows the same-named field in GenericAnnotatedTypeFactory, but has a more specific + // type. + /** The type factory to use. */ + @SuppressWarnings("HidingField") + private final ValueAnnotatedTypeFactory atypeFactory; - return atypeFactory.createStringAnnotation(values); - } - - @Override - public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { - if (isSubtypeQualifiers(a1, a2)) { - return a1; - } else if (isSubtypeQualifiers(a2, a1)) { - return a2; - } else { - - // Implementation of GLB where one of the annotations is StringVal is needed for - // length-based refinement of constant string values. Other cases of length-based - // refinement are handled by subtype check. - if (AnnotationUtils.areSameByName(a1, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { - return glbOfStringVal(a1, a2); - } else if (AnnotationUtils.areSameByName(a2, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { - return glbOfStringVal(a2, a1); - } - - // Simply return BOTTOMVAL in other cases. Refine this if we discover use cases - // that need a more precise GLB. - return atypeFactory.BOTTOMVAL; - } - } - - @Override - public int numberOfIterationsBeforeWidening() { - return ValueAnnotatedTypeFactory.MAX_VALUES + 1; - } - - @Override - public AnnotationMirror widenedUpperBound( - AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { - AnnotationMirror lub = leastUpperBoundQualifiers(newQualifier, previousQualifier); - if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { - Range lubRange = atypeFactory.getRange(lub); - Range newRange = atypeFactory.getRange(newQualifier); - Range oldRange = atypeFactory.getRange(previousQualifier); - Range wubRange = widenedRange(newRange, oldRange, lubRange); - return atypeFactory.createIntRangeAnnotation(wubRange); - } else if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - Range lubRange = atypeFactory.getRange(lub); - Range newRange = atypeFactory.getRange(newQualifier); - Range oldRange = atypeFactory.getRange(previousQualifier); - Range wubRange = widenedRange(newRange, oldRange, lubRange); - return atypeFactory.createArrayLenRangeAnnotation(wubRange); - } else { - return lub; - } - } - - /** - * Determine the widened range from other ranges. - * - * @param newRange the new range - * @param oldRange the old range - * @param lubRange the LUB range - * @return the widened range - */ - private Range widenedRange(Range newRange, Range oldRange, Range lubRange) { - if (newRange == null || oldRange == null || lubRange.equals(oldRange)) { - return lubRange; - } - // If both bounds of the new range are bigger than the old range, then returned range - // should use the lower bound of the new range and a MAX_VALUE. - if ((newRange.from >= oldRange.from && newRange.to >= oldRange.to)) { - long max = lubRange.to; - if (max < Byte.MAX_VALUE) { - max = Byte.MAX_VALUE; - } else if (max < Short.MAX_VALUE) { - max = Short.MAX_VALUE; - } else if (max < Integer.MAX_VALUE) { - max = Integer.MAX_VALUE; - } else { - max = Long.MAX_VALUE; - } - return Range.create(newRange.from, max); + /** + * Creates a ValueQualifierHierarchy from the given classes. + * + * @param atypeFactory a ValueAnnotatedTypeFactory + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @deprecated use {@link #ValueQualifierHierarchy(Collection, ValueAnnotatedTypeFactory)} which + * has the arguments in the other order + */ + @Deprecated // 2023-05-23 + ValueQualifierHierarchy( + ValueAnnotatedTypeFactory atypeFactory, + Collection> qualifierClasses) { + this(qualifierClasses, atypeFactory); } - // If both bounds of the old range are bigger than the new range, then returned range - // should use a MIN_VALUE and the upper bound of the new range. - if ((newRange.from <= oldRange.from && newRange.to <= oldRange.to)) { - long min = lubRange.from; - if (min > Byte.MIN_VALUE) { - min = Byte.MIN_VALUE; - } else if (min > Short.MIN_VALUE) { - min = Short.MIN_VALUE; - } else if (min > Integer.MIN_VALUE) { - min = Integer.MIN_VALUE; - } else { - min = Long.MIN_VALUE; - } - return Range.create(min, newRange.to); + /** + * Creates a ValueQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param atypeFactory the associated type factory + */ + ValueQualifierHierarchy( + Collection> qualifierClasses, + ValueAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, atypeFactory.getElementUtils(), atypeFactory); + this.atypeFactory = atypeFactory; } - if (lubRange.isWithin(Byte.MIN_VALUE + 1, Byte.MAX_VALUE) - || lubRange.isWithin(Byte.MIN_VALUE, Byte.MAX_VALUE - 1)) { - return Range.BYTE_EVERYTHING; - } else if (lubRange.isWithin(Short.MIN_VALUE + 1, Short.MAX_VALUE) - || lubRange.isWithin(Short.MIN_VALUE, Short.MAX_VALUE - 1)) { - return Range.SHORT_EVERYTHING; - } else if (lubRange.isWithin(Long.MIN_VALUE + 1, Long.MAX_VALUE) - || lubRange.isWithin(Long.MIN_VALUE, Long.MAX_VALUE - 1)) { - return Range.INT_EVERYTHING; - } else { - return Range.EVERYTHING; - } - } - - /** - * Determines the least upper bound of a1 and a2, which contains the union of their sets of - * possible values. - * - * @return the least upper bound of a1 and a2 - */ - @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { - // The annotations are in different hierarchies - return null; + /** + * Computes greatest lower bound of a @StringVal annotation with another Value Checker + * annotation. + * + * @param stringValAnno annotation of type @StringVal + * @param otherAnno annotation from the value checker hierarchy + * @return greatest lower bound of {@code stringValAnno} and {@code otherAnno} + */ + private AnnotationMirror glbOfStringVal( + AnnotationMirror stringValAnno, AnnotationMirror otherAnno) { + List values = atypeFactory.getStringValues(stringValAnno); + switch (AnnotationUtils.annotationName(otherAnno)) { + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + // Intersection of value lists + List otherValues = atypeFactory.getStringValues(otherAnno); + values.retainAll(otherValues); + break; + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + // Retain strings of correct lengths + List otherLengths = atypeFactory.getArrayLength(otherAnno); + ArrayList result = new ArrayList<>(values.size()); + for (String s : values) { + if (otherLengths.contains(s.length())) { + result.add(s); + } + } + values = result; + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + // Retain strings of lengths from a range + Range otherRange = atypeFactory.getRange(otherAnno); + ArrayList range = new ArrayList<>(values.size()); + for (String s : values) { + if (otherRange.contains(s.length())) { + range.add(s); + } + } + values = range; + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + List<@Regex String> matchesRegexes = + AnnotationUtils.getElementValueArray( + otherAnno, atypeFactory.matchesRegexValueElement, String.class); + // Retain the @StringVal values such that one of the regexes matches it. + values = RegexUtil.matchesSomeRegex(values, matchesRegexes); + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + List<@Regex String> doesNotMatchRegexes = + AnnotationUtils.getElementValueArray( + otherAnno, + atypeFactory.doesNotMatchRegexValueElement, + String.class); + // Retain the @StringVal values such that none of the regexes matches it. + values = RegexUtil.matchesNoRegex(values, doesNotMatchRegexes); + break; + default: + return atypeFactory.BOTTOMVAL; + } + + return atypeFactory.createStringAnnotation(values); } - a1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a1); - a2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a2); + @Override + public AnnotationMirror greatestLowerBoundQualifiers(AnnotationMirror a1, AnnotationMirror a2) { + if (isSubtypeQualifiers(a1, a2)) { + return a1; + } else if (isSubtypeQualifiers(a2, a1)) { + return a2; + } else { - if (isSubtypeQualifiers(a1, a2)) { - return a2; - } else if (isSubtypeQualifiers(a2, a1)) { - return a1; - } - String qual1 = AnnotationUtils.annotationName(a1); - String qual2 = AnnotationUtils.annotationName(a2); - - if (qual1.equals(qual2)) { - // If both are the same type, determine the type and merge - switch (qual1) { - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - // special handling for IntRange - Range intrange1 = atypeFactory.getRange(a1); - Range intrange2 = atypeFactory.getRange(a2); - return atypeFactory.createIntRangeAnnotation(intrange1.union(intrange2)); - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - // special handling for ArrayLenRange - Range range1 = atypeFactory.getRange(a1); - Range range2 = atypeFactory.getRange(a2); - return atypeFactory.createArrayLenRangeAnnotation(range1.union(range2)); - case ValueAnnotatedTypeFactory.INTVAL_NAME: - List longs = atypeFactory.getIntValues(a1); - CollectionsPlume.adjoinAll(longs, atypeFactory.getIntValues(a2)); - return atypeFactory.createIntValAnnotation(longs); - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - List arrayLens = atypeFactory.getArrayLength(a1); - CollectionsPlume.adjoinAll(arrayLens, atypeFactory.getArrayLength(a2)); - return atypeFactory.createArrayLenAnnotation(arrayLens); - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - List strings = atypeFactory.getStringValues(a1); - CollectionsPlume.adjoinAll(strings, atypeFactory.getStringValues(a2)); - return atypeFactory.createStringAnnotation(strings); - case ValueAnnotatedTypeFactory.BOOLVAL_NAME: - List bools = atypeFactory.getBooleanValues(a1); - CollectionsPlume.adjoinAll(bools, atypeFactory.getBooleanValues(a2)); - return atypeFactory.createBooleanAnnotation(bools); - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - List doubles = atypeFactory.getDoubleValues(a1); - CollectionsPlume.adjoinAll(doubles, atypeFactory.getDoubleValues(a2)); - return atypeFactory.createDoubleAnnotation(doubles); - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - List<@Regex String> regexes = atypeFactory.getMatchesRegexValues(a1); - CollectionsPlume.adjoinAll(regexes, atypeFactory.getMatchesRegexValues(a2)); - return atypeFactory.createMatchesRegexAnnotation(regexes); - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - // The LUB is the intersection of the sets. - List<@Regex String> regexes1 = atypeFactory.getDoesNotMatchRegexValues(a1); - List<@Regex String> regexes2 = atypeFactory.getDoesNotMatchRegexValues(a2); - regexes1.retainAll(regexes2); - return atypeFactory.createDoesNotMatchRegexAnnotation(regexes1); - default: - throw new TypeSystemError("default case: %s %s %s%n", qual1, a1, a2); - } - } + // Implementation of GLB where one of the annotations is StringVal is needed for + // length-based refinement of constant string values. Other cases of length-based + // refinement are handled by subtype check. + if (AnnotationUtils.areSameByName(a1, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { + return glbOfStringVal(a1, a2); + } else if (AnnotationUtils.areSameByName( + a2, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { + return glbOfStringVal(a2, a1); + } - // Special handling for dealing with the lub of two annotations that are distinct but - // convertible (e.g. a StringVal and a MatchesRegex, or an IntVal and an IntRange). - // Each of these variables is an annotation of the given type, or is null if neither of - // the arguments to leastUpperBound is of the given types. - AnnotationMirror arrayLenAnno = null; - AnnotationMirror arrayLenRangeAnno = null; - AnnotationMirror stringValAnno = null; - AnnotationMirror matchesRegexAnno = null; - AnnotationMirror doesNotMatchRegexAnno = null; - AnnotationMirror intValAnno = null; - AnnotationMirror intRangeAnno = null; - AnnotationMirror doubleValAnno = null; - - switch (qual1) { - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - arrayLenAnno = a1; - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - arrayLenRangeAnno = a1; - break; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - stringValAnno = a1; - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - matchesRegexAnno = a1; - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - doesNotMatchRegexAnno = a1; - break; - case ValueAnnotatedTypeFactory.INTVAL_NAME: - intValAnno = a1; - break; - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - intRangeAnno = a1; - break; - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - doubleValAnno = a1; - break; - default: - // Do nothing + // Simply return BOTTOMVAL in other cases. Refine this if we discover use cases + // that need a more precise GLB. + return atypeFactory.BOTTOMVAL; + } } - switch (qual2) { - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - arrayLenAnno = a2; - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - arrayLenRangeAnno = a2; - break; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - stringValAnno = a2; - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - matchesRegexAnno = a2; - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - doesNotMatchRegexAnno = a2; - break; - case ValueAnnotatedTypeFactory.INTVAL_NAME: - intValAnno = a2; - break; - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - intRangeAnno = a2; - break; - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - doubleValAnno = a2; - break; - default: - // Do nothing + @Override + public int numberOfIterationsBeforeWidening() { + return ValueAnnotatedTypeFactory.MAX_VALUES + 1; } - // Special handling for dealing with the lub of an ArrayLenRange and an ArrayLen, - // a StringVal with one of them, or a StringVal and a MatchesRegex. - // Each of these converts one annotation to the other, then makes a recursive call. - if (arrayLenAnno != null && arrayLenRangeAnno != null) { - return leastUpperBoundQualifiers( - arrayLenRangeAnno, atypeFactory.convertArrayLenToArrayLenRange(arrayLenAnno)); - } else if (stringValAnno != null && arrayLenAnno != null) { - return leastUpperBoundQualifiers( - arrayLenAnno, atypeFactory.convertStringValToArrayLen(stringValAnno)); - } else if (stringValAnno != null && arrayLenRangeAnno != null) { - return leastUpperBoundQualifiers( - arrayLenRangeAnno, atypeFactory.convertStringValToArrayLenRange(stringValAnno)); - } else if (stringValAnno != null && matchesRegexAnno != null) { - return leastUpperBoundQualifiers( - matchesRegexAnno, atypeFactory.convertStringValToMatchesRegex(stringValAnno)); + @Override + public AnnotationMirror widenedUpperBound( + AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { + AnnotationMirror lub = leastUpperBoundQualifiers(newQualifier, previousQualifier); + if (AnnotationUtils.areSameByName(lub, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { + Range lubRange = atypeFactory.getRange(lub); + Range newRange = atypeFactory.getRange(newQualifier); + Range oldRange = atypeFactory.getRange(previousQualifier); + Range wubRange = widenedRange(newRange, oldRange, lubRange); + return atypeFactory.createIntRangeAnnotation(wubRange); + } else if (AnnotationUtils.areSameByName( + lub, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + Range lubRange = atypeFactory.getRange(lub); + Range newRange = atypeFactory.getRange(newQualifier); + Range oldRange = atypeFactory.getRange(previousQualifier); + Range wubRange = widenedRange(newRange, oldRange, lubRange); + return atypeFactory.createArrayLenRangeAnnotation(wubRange); + } else { + return lub; + } } - if (stringValAnno != null && doesNotMatchRegexAnno != null) { - // The lub is either doesNotMatchRegexAnno or UNKNOWNVAL. - List stringVals = atypeFactory.getStringValues(stringValAnno); - List<@Regex String> regexes = - AnnotationUtils.getElementValueArray( - doesNotMatchRegexAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); - if (RegexUtil.everyStringMatchesSomeRegex(stringVals, regexes)) { - return atypeFactory.UNKNOWNVAL; - } - return doesNotMatchRegexAnno; - } + /** + * Determine the widened range from other ranges. + * + * @param newRange the new range + * @param oldRange the old range + * @param lubRange the LUB range + * @return the widened range + */ + private Range widenedRange(Range newRange, Range oldRange, Range lubRange) { + if (newRange == null || oldRange == null || lubRange.equals(oldRange)) { + return lubRange; + } + // If both bounds of the new range are bigger than the old range, then returned range + // should use the lower bound of the new range and a MAX_VALUE. + if ((newRange.from >= oldRange.from && newRange.to >= oldRange.to)) { + long max = lubRange.to; + if (max < Byte.MAX_VALUE) { + max = Byte.MAX_VALUE; + } else if (max < Short.MAX_VALUE) { + max = Short.MAX_VALUE; + } else if (max < Integer.MAX_VALUE) { + max = Integer.MAX_VALUE; + } else { + max = Long.MAX_VALUE; + } + return Range.create(newRange.from, max); + } - // Annotations are both in the same hierarchy, but they are not the same. - // If a1 and a2 are not the same type of *Value annotation, they may still be mergeable - // because some values can be implicitly cast as others. For example, if a1 and a2 are - // both in {DoubleVal, IntVal} then they will be converted upwards: IntVal -> DoubleVal - // to arrive at a common annotation type. - - if (doubleValAnno != null) { - if (intRangeAnno != null) { - intValAnno = atypeFactory.convertIntRangeToIntVal(intRangeAnno); - if (AnnotationUtils.areSameByName(intValAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - intValAnno = null; + // If both bounds of the old range are bigger than the new range, then returned range + // should use a MIN_VALUE and the upper bound of the new range. + if ((newRange.from <= oldRange.from && newRange.to <= oldRange.to)) { + long min = lubRange.from; + if (min > Byte.MIN_VALUE) { + min = Byte.MIN_VALUE; + } else if (min > Short.MIN_VALUE) { + min = Short.MIN_VALUE; + } else if (min > Integer.MIN_VALUE) { + min = Integer.MIN_VALUE; + } else { + min = Long.MIN_VALUE; + } + return Range.create(min, newRange.to); } - } - if (intValAnno != null) { - // Convert intValAnno to a @DoubleVal AnnotationMirror - AnnotationMirror doubleValAnno2 = atypeFactory.convertIntValToDoubleVal(intValAnno); - return leastUpperBoundQualifiers(doubleValAnno, doubleValAnno2); - } - return atypeFactory.UNKNOWNVAL; - } - if (intRangeAnno != null && intValAnno != null) { - // Convert intValAnno to an @IntRange AnnotationMirror - AnnotationMirror intRangeAnno2 = atypeFactory.convertIntValToIntRange(intValAnno); - return leastUpperBoundQualifiers(intRangeAnno, intRangeAnno2); - } - // In all other cases, the LUB is UnknownVal. - return atypeFactory.UNKNOWNVAL; - } - - @Override - public boolean isSubtypeShallow( - AnnotationMirror subQualifier, - TypeMirror subType, - AnnotationMirror superQualifier, - TypeMirror superType) { - subQualifier = atypeFactory.convertSpecialIntRangeToStandardIntRange(subQualifier, subType); - superQualifier = - atypeFactory.convertSpecialIntRangeToStandardIntRange(superQualifier, superType); - return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); - } - - @Override - public @Nullable AnnotationMirror leastUpperBoundShallow( - AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { - qualifier1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier1, tm1); - qualifier2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier2, tm2); - return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); - } - - /** - * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both - * annotations are Value. In this case, subAnno is a subtype of superAnno iff superAnno contains - * at least every element of subAnno. - * - * @return true if subAnno is a subtype of superAnno, false otherwise - */ - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno); - superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno); - String subQualName = AnnotationUtils.annotationName(subAnno); - if (subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - superAnno = atypeFactory.convertToUnknown(superAnno); - } - String superQualName = AnnotationUtils.annotationName(superAnno); - if (superQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME) - || subQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return true; - } else if (superQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME) - || subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - return false; - } else if (superQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { - return subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME); - } else if (subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { - return false; - } else if (superQualName.equals(subQualName)) { - // Same annotation name, so might be subtype - if (subQualName.equals(ValueAnnotatedTypeFactory.INTRANGE_NAME) - || subQualName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - // Special case for range-based annotations - Range superRange = atypeFactory.getRange(superAnno); - Range subRange = atypeFactory.getRange(subAnno); - return superRange.contains(subRange); - } else if (subQualName.equals(ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { - List superValues = - AnnotationUtils.getElementValueArray( - superAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); - List subValues = - AnnotationUtils.getElementValueArray( - subAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); - return subValues.containsAll(superValues); - } else { - // The annotations have the same name, which is one of: - // ArrayLen, BoolVal, DoubleVal, EnumVal, StringVal, MatchesRegex. - @SuppressWarnings("deprecation") // concrete annotation class is not known - List superValues = - AnnotationUtils.getElementValueArray(superAnno, "value", Object.class, false); - @SuppressWarnings("deprecation") // concrete annotation class is not known - List subValues = - AnnotationUtils.getElementValueArray(subAnno, "value", Object.class, false); - return superValues.containsAll(subValues); - } + if (lubRange.isWithin(Byte.MIN_VALUE + 1, Byte.MAX_VALUE) + || lubRange.isWithin(Byte.MIN_VALUE, Byte.MAX_VALUE - 1)) { + return Range.BYTE_EVERYTHING; + } else if (lubRange.isWithin(Short.MIN_VALUE + 1, Short.MAX_VALUE) + || lubRange.isWithin(Short.MIN_VALUE, Short.MAX_VALUE - 1)) { + return Range.SHORT_EVERYTHING; + } else if (lubRange.isWithin(Long.MIN_VALUE + 1, Long.MAX_VALUE) + || lubRange.isWithin(Long.MIN_VALUE, Long.MAX_VALUE - 1)) { + return Range.INT_EVERYTHING; + } else { + return Range.EVERYTHING; + } } - switch (subQualName + superQualName) { - case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - List superValues = atypeFactory.getDoubleValues(superAnno); - List subValues = - atypeFactory.convertLongListToDoubleList(atypeFactory.getIntValues(subAnno)); - return superValues.containsAll(subValues); - case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.INTRANGE_NAME: - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - Range superRange = atypeFactory.getRange(superAnno); - List subLongValues = atypeFactory.getArrayLenOrIntValue(subAnno); - Range subLongRange = Range.create(subLongValues); - return superRange.contains(subLongRange); - case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - Range subRange = atypeFactory.getRange(subAnno); - if (subRange.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - return false; + + /** + * Determines the least upper bound of a1 and a2, which contains the union of their sets of + * possible values. + * + * @return the least upper bound of a1 and a2 + */ + @Override + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + if (!AnnotationUtils.areSameByName(getTopAnnotation(a1), getTopAnnotation(a2))) { + // The annotations are in different hierarchies + return null; + } + + a1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a1); + a2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(a2); + + if (isSubtypeQualifiers(a1, a2)) { + return a2; + } else if (isSubtypeQualifiers(a2, a1)) { + return a1; + } + String qual1 = AnnotationUtils.annotationName(a1); + String qual2 = AnnotationUtils.annotationName(a2); + + if (qual1.equals(qual2)) { + // If both are the same type, determine the type and merge + switch (qual1) { + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + // special handling for IntRange + Range intrange1 = atypeFactory.getRange(a1); + Range intrange2 = atypeFactory.getRange(a2); + return atypeFactory.createIntRangeAnnotation(intrange1.union(intrange2)); + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + // special handling for ArrayLenRange + Range range1 = atypeFactory.getRange(a1); + Range range2 = atypeFactory.getRange(a2); + return atypeFactory.createArrayLenRangeAnnotation(range1.union(range2)); + case ValueAnnotatedTypeFactory.INTVAL_NAME: + List longs = atypeFactory.getIntValues(a1); + CollectionsPlume.adjoinAll(longs, atypeFactory.getIntValues(a2)); + return atypeFactory.createIntValAnnotation(longs); + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + List arrayLens = atypeFactory.getArrayLength(a1); + CollectionsPlume.adjoinAll(arrayLens, atypeFactory.getArrayLength(a2)); + return atypeFactory.createArrayLenAnnotation(arrayLens); + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + List strings = atypeFactory.getStringValues(a1); + CollectionsPlume.adjoinAll(strings, atypeFactory.getStringValues(a2)); + return atypeFactory.createStringAnnotation(strings); + case ValueAnnotatedTypeFactory.BOOLVAL_NAME: + List bools = atypeFactory.getBooleanValues(a1); + CollectionsPlume.adjoinAll(bools, atypeFactory.getBooleanValues(a2)); + return atypeFactory.createBooleanAnnotation(bools); + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + List doubles = atypeFactory.getDoubleValues(a1); + CollectionsPlume.adjoinAll(doubles, atypeFactory.getDoubleValues(a2)); + return atypeFactory.createDoubleAnnotation(doubles); + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + List<@Regex String> regexes = atypeFactory.getMatchesRegexValues(a1); + CollectionsPlume.adjoinAll(regexes, atypeFactory.getMatchesRegexValues(a2)); + return atypeFactory.createMatchesRegexAnnotation(regexes); + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + // The LUB is the intersection of the sets. + List<@Regex String> regexes1 = atypeFactory.getDoesNotMatchRegexValues(a1); + List<@Regex String> regexes2 = atypeFactory.getDoesNotMatchRegexValues(a2); + regexes1.retainAll(regexes2); + return atypeFactory.createDoesNotMatchRegexAnnotation(regexes1); + default: + throw new TypeSystemError("default case: %s %s %s%n", qual1, a1, a2); + } } - List superDoubleValues = atypeFactory.getDoubleValues(superAnno); - List subDoubleValues = ValueCheckerUtils.getValuesFromRange(subRange, Double.class); - return superDoubleValues.containsAll(subDoubleValues); - case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.INTVAL_NAME: - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - Range subRange2 = atypeFactory.getRange(subAnno); - if (subRange2.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - return false; + + // Special handling for dealing with the lub of two annotations that are distinct but + // convertible (e.g. a StringVal and a MatchesRegex, or an IntVal and an IntRange). + // Each of these variables is an annotation of the given type, or is null if neither of + // the arguments to leastUpperBound is of the given types. + AnnotationMirror arrayLenAnno = null; + AnnotationMirror arrayLenRangeAnno = null; + AnnotationMirror stringValAnno = null; + AnnotationMirror matchesRegexAnno = null; + AnnotationMirror doesNotMatchRegexAnno = null; + AnnotationMirror intValAnno = null; + AnnotationMirror intRangeAnno = null; + AnnotationMirror doubleValAnno = null; + + switch (qual1) { + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + arrayLenAnno = a1; + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + arrayLenRangeAnno = a1; + break; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + stringValAnno = a1; + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + matchesRegexAnno = a1; + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + doesNotMatchRegexAnno = a1; + break; + case ValueAnnotatedTypeFactory.INTVAL_NAME: + intValAnno = a1; + break; + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + intRangeAnno = a1; + break; + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + doubleValAnno = a1; + break; + default: + // Do nothing } - List superValues2 = atypeFactory.getArrayLenOrIntValue(superAnno); - List subValues2 = ValueCheckerUtils.getValuesFromRange(subRange2, Long.class); - return superValues2.containsAll(subValues2); - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME: - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME: - - // Allow @ArrayLen(0) to be converted to @StringVal("") - List superStringValues = atypeFactory.getStringValues(superAnno); - return superStringValues.contains("") && atypeFactory.getMaxLenValue(subAnno) == 0; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - { - List strings = atypeFactory.getStringValues(subAnno); - List regexes = - AnnotationUtils.getElementValueArray( - superAnno, atypeFactory.matchesRegexValueElement, String.class); - return RegexUtil.everyStringMatchesSomeRegex(strings, regexes); + + switch (qual2) { + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + arrayLenAnno = a2; + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + arrayLenRangeAnno = a2; + break; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + stringValAnno = a2; + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + matchesRegexAnno = a2; + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + doesNotMatchRegexAnno = a2; + break; + case ValueAnnotatedTypeFactory.INTVAL_NAME: + intValAnno = a2; + break; + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + intRangeAnno = a2; + break; + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + doubleValAnno = a2; + break; + default: + // Do nothing } - case ValueAnnotatedTypeFactory.STRINGVAL_NAME - + ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - { - List strings = atypeFactory.getStringValues(subAnno); - List regexes = - AnnotationUtils.getElementValueArray( - superAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); - return RegexUtil.noStringMatchesAnyRegex(strings, regexes); + + // Special handling for dealing with the lub of an ArrayLenRange and an ArrayLen, + // a StringVal with one of them, or a StringVal and a MatchesRegex. + // Each of these converts one annotation to the other, then makes a recursive call. + if (arrayLenAnno != null && arrayLenRangeAnno != null) { + return leastUpperBoundQualifiers( + arrayLenRangeAnno, atypeFactory.convertArrayLenToArrayLenRange(arrayLenAnno)); + } else if (stringValAnno != null && arrayLenAnno != null) { + return leastUpperBoundQualifiers( + arrayLenAnno, atypeFactory.convertStringValToArrayLen(stringValAnno)); + } else if (stringValAnno != null && arrayLenRangeAnno != null) { + return leastUpperBoundQualifiers( + arrayLenRangeAnno, atypeFactory.convertStringValToArrayLenRange(stringValAnno)); + } else if (stringValAnno != null && matchesRegexAnno != null) { + return leastUpperBoundQualifiers( + matchesRegexAnno, atypeFactory.convertStringValToMatchesRegex(stringValAnno)); } - case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - // StringVal is a subtype of ArrayLen, if all the strings have one of the correct - // lengths. - List superIntValues = atypeFactory.getArrayLength(superAnno); - List subStringValues = atypeFactory.getStringValues(subAnno); - for (String value : subStringValues) { - if (!superIntValues.contains(value.length())) { - return false; - } + + if (stringValAnno != null && doesNotMatchRegexAnno != null) { + // The lub is either doesNotMatchRegexAnno or UNKNOWNVAL. + List stringVals = atypeFactory.getStringValues(stringValAnno); + List<@Regex String> regexes = + AnnotationUtils.getElementValueArray( + doesNotMatchRegexAnno, + atypeFactory.doesNotMatchRegexValueElement, + String.class); + if (RegexUtil.everyStringMatchesSomeRegex(stringVals, regexes)) { + return atypeFactory.UNKNOWNVAL; + } + return doesNotMatchRegexAnno; } - return true; - case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - // StringVal is a subtype of ArrayLenRange, if all the strings have a length in the - // range. - Range superRange2 = atypeFactory.getRange(superAnno); - List subValues3 = atypeFactory.getStringValues(subAnno); - for (String value : subValues3) { - if (!superRange2.contains(value.length())) { + + // Annotations are both in the same hierarchy, but they are not the same. + // If a1 and a2 are not the same type of *Value annotation, they may still be mergeable + // because some values can be implicitly cast as others. For example, if a1 and a2 are + // both in {DoubleVal, IntVal} then they will be converted upwards: IntVal -> DoubleVal + // to arrive at a common annotation type. + + if (doubleValAnno != null) { + if (intRangeAnno != null) { + intValAnno = atypeFactory.convertIntRangeToIntVal(intRangeAnno); + if (AnnotationUtils.areSameByName( + intValAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + intValAnno = null; + } + } + if (intValAnno != null) { + // Convert intValAnno to a @DoubleVal AnnotationMirror + AnnotationMirror doubleValAnno2 = atypeFactory.convertIntValToDoubleVal(intValAnno); + return leastUpperBoundQualifiers(doubleValAnno, doubleValAnno2); + } + return atypeFactory.UNKNOWNVAL; + } + if (intRangeAnno != null && intValAnno != null) { + // Convert intValAnno to an @IntRange AnnotationMirror + AnnotationMirror intRangeAnno2 = atypeFactory.convertIntValToIntRange(intValAnno); + return leastUpperBoundQualifiers(intRangeAnno, intRangeAnno2); + } + + // In all other cases, the LUB is UnknownVal. + return atypeFactory.UNKNOWNVAL; + } + + @Override + public boolean isSubtypeShallow( + AnnotationMirror subQualifier, + TypeMirror subType, + AnnotationMirror superQualifier, + TypeMirror superType) { + subQualifier = atypeFactory.convertSpecialIntRangeToStandardIntRange(subQualifier, subType); + superQualifier = + atypeFactory.convertSpecialIntRangeToStandardIntRange(superQualifier, superType); + return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType); + } + + @Override + public @Nullable AnnotationMirror leastUpperBoundShallow( + AnnotationMirror qualifier1, + TypeMirror tm1, + AnnotationMirror qualifier2, + TypeMirror tm2) { + qualifier1 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier1, tm1); + qualifier2 = atypeFactory.convertSpecialIntRangeToStandardIntRange(qualifier2, tm2); + return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2); + } + + /** + * Computes subtyping as per the subtyping in the qualifier hierarchy structure unless both + * annotations are Value. In this case, subAnno is a subtype of superAnno iff superAnno contains + * at least every element of subAnno. + * + * @return true if subAnno is a subtype of superAnno, false otherwise + */ + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno); + superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno); + String subQualName = AnnotationUtils.annotationName(subAnno); + if (subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + superAnno = atypeFactory.convertToUnknown(superAnno); + } + String superQualName = AnnotationUtils.annotationName(superAnno); + if (superQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME) + || subQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return true; + } else if (superQualName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME) + || subQualName.equals(ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { return false; - } + } else if (superQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { + return subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME); + } else if (subQualName.equals(ValueAnnotatedTypeFactory.POLY_NAME)) { + return false; + } else if (superQualName.equals(subQualName)) { + // Same annotation name, so might be subtype + if (subQualName.equals(ValueAnnotatedTypeFactory.INTRANGE_NAME) + || subQualName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + // Special case for range-based annotations + Range superRange = atypeFactory.getRange(superAnno); + Range subRange = atypeFactory.getRange(subAnno); + return superRange.contains(subRange); + } else if (subQualName.equals(ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { + List superValues = + AnnotationUtils.getElementValueArray( + superAnno, + atypeFactory.doesNotMatchRegexValueElement, + String.class); + List subValues = + AnnotationUtils.getElementValueArray( + subAnno, atypeFactory.doesNotMatchRegexValueElement, String.class); + return subValues.containsAll(superValues); + } else { + // The annotations have the same name, which is one of: + // ArrayLen, BoolVal, DoubleVal, EnumVal, StringVal, MatchesRegex. + @SuppressWarnings("deprecation") // concrete annotation class is not known + List superValues = + AnnotationUtils.getElementValueArray( + superAnno, "value", Object.class, false); + @SuppressWarnings("deprecation") // concrete annotation class is not known + List subValues = + AnnotationUtils.getElementValueArray(subAnno, "value", Object.class, false); + return superValues.containsAll(subValues); + } + } + switch (subQualName + superQualName) { + case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + List superValues = atypeFactory.getDoubleValues(superAnno); + List subValues = + atypeFactory.convertLongListToDoubleList( + atypeFactory.getIntValues(subAnno)); + return superValues.containsAll(subValues); + case ValueAnnotatedTypeFactory.INTVAL_NAME + ValueAnnotatedTypeFactory.INTRANGE_NAME: + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + Range superRange = atypeFactory.getRange(superAnno); + List subLongValues = atypeFactory.getArrayLenOrIntValue(subAnno); + Range subLongRange = Range.create(subLongValues); + return superRange.contains(subLongRange); + case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + Range subRange = atypeFactory.getRange(subAnno); + if (subRange.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + return false; + } + List superDoubleValues = atypeFactory.getDoubleValues(superAnno); + List subDoubleValues = + ValueCheckerUtils.getValuesFromRange(subRange, Double.class); + return superDoubleValues.containsAll(subDoubleValues); + case ValueAnnotatedTypeFactory.INTRANGE_NAME + ValueAnnotatedTypeFactory.INTVAL_NAME: + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + Range subRange2 = atypeFactory.getRange(subAnno); + if (subRange2.isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + return false; + } + List superValues2 = atypeFactory.getArrayLenOrIntValue(superAnno); + List subValues2 = ValueCheckerUtils.getValuesFromRange(subRange2, Long.class); + return superValues2.containsAll(subValues2); + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME + + ValueAnnotatedTypeFactory.STRINGVAL_NAME: + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME + ValueAnnotatedTypeFactory.STRINGVAL_NAME: + + // Allow @ArrayLen(0) to be converted to @StringVal("") + List superStringValues = atypeFactory.getStringValues(superAnno); + return superStringValues.contains("") && atypeFactory.getMaxLenValue(subAnno) == 0; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + + ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + { + List strings = atypeFactory.getStringValues(subAnno); + List regexes = + AnnotationUtils.getElementValueArray( + superAnno, atypeFactory.matchesRegexValueElement, String.class); + return RegexUtil.everyStringMatchesSomeRegex(strings, regexes); + } + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + + ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + { + List strings = atypeFactory.getStringValues(subAnno); + List regexes = + AnnotationUtils.getElementValueArray( + superAnno, + atypeFactory.doesNotMatchRegexValueElement, + String.class); + return RegexUtil.noStringMatchesAnyRegex(strings, regexes); + } + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + // StringVal is a subtype of ArrayLen, if all the strings have one of the correct + // lengths. + List superIntValues = atypeFactory.getArrayLength(superAnno); + List subStringValues = atypeFactory.getStringValues(subAnno); + for (String value : subStringValues) { + if (!superIntValues.contains(value.length())) { + return false; + } + } + return true; + case ValueAnnotatedTypeFactory.STRINGVAL_NAME + + ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + // StringVal is a subtype of ArrayLenRange, if all the strings have a length in the + // range. + Range superRange2 = atypeFactory.getRange(superAnno); + List subValues3 = atypeFactory.getStringValues(subAnno); + for (String value : subValues3) { + if (!superRange2.contains(value.length())) { + return false; + } + } + return true; + default: + return false; } - return true; - default: - return false; } - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java index 4b19ed0da65..6db49f108a6 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java @@ -1,15 +1,7 @@ package org.checkerframework.common.value; import com.sun.source.tree.ExpressionTree; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.ArrayLenRange; @@ -69,1607 +61,1660 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** The transfer class for the Value Checker. */ public class ValueTransfer extends CFTransfer { - /** The Value type factory. */ - protected final ValueAnnotatedTypeFactory atypeFactory; - - /** The Value qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; - - /** True if -AnonNullStringsConcatenation was passed on the command line. */ - private final boolean nonNullStringsConcatenation; - - /** - * Create a new ValueTransfer. - * - * @param analysis the corresponding analysis - */ - public ValueTransfer(CFAbstractAnalysis analysis) { - super(analysis); - atypeFactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory(); - qualHierarchy = atypeFactory.getQualifierHierarchy(); - nonNullStringsConcatenation = - atypeFactory.getChecker().hasOption("nonNullStringsConcatenation"); - } - - /** Returns a range of possible lengths for an integer from a range, as casted to a String. */ - private Range getIntRangeStringLengthRange(Node subNode, TransferInput p) { - Range valueRange = getIntRange(subNode, p); - - // Get lengths of the bounds - int fromLength = Long.toString(valueRange.from).length(); - int toLength = Long.toString(valueRange.to).length(); - - int lowerLength = Math.min(fromLength, toLength); - // In case the range contains 0, the minimum length is 1 even if both bounds are longer - if (valueRange.contains(0)) { - lowerLength = 1; - } - - int upperLength = Math.max(fromLength, toLength); - - return Range.create(lowerLength, upperLength); - } - - /** - * Returns a range of possible lengths for {@code subNode}, as casted to a String. - * - * @param subNode some subnode of {@code p} - * @param p a TransferInput - * @return a range of possible lengths for {@code subNode}, as casted to a String - */ - private @Nullable Range getStringLengthRange(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - - AnnotationMirror anno = getValueAnnotation(value); - if (anno == null) { - return null; - } - String annoName = AnnotationUtils.annotationName(anno); - if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - return atypeFactory.getRange(anno); - } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Range.NOTHING; - } - - TypeKind subNodeTypeKind = subNode.getType().getKind(); - - // handle values converted to string (ints, longs, longs with @IntRange) - if (subNode instanceof StringConversionNode) { - return getStringLengthRange(((StringConversionNode) subNode).getOperand(), p); - } else if (isIntRange(subNode, p)) { - return getIntRangeStringLengthRange(subNode, p); - } else if (subNodeTypeKind == TypeKind.INT) { - // ints are between 1 and 11 characters long - return Range.create(1, 11); - } else if (subNodeTypeKind == TypeKind.LONG) { - // longs are between 1 and 20 characters long - return Range.create(1, 20); - } - - return Range.create(0, Integer.MAX_VALUE); - } - - /** - * Returns a list of possible lengths for {@code subNode}, as casted to a String. Returns null if - * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is - * bottom. - */ - private @Nullable List getStringLengths( - Node subNode, TransferInput p) { - - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror anno = getValueAnnotation(value); - if (anno == null) { - return null; - } - String annoName = AnnotationUtils.annotationName(anno); - if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { - return atypeFactory.getArrayLength(anno); - } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Collections.emptyList(); - } - - TypeKind subNodeTypeKind = subNode.getType().getKind(); - - // handle values converted to string (characters, bytes, shorts, ints with @IntRange) - if (subNode instanceof StringConversionNode) { - return getStringLengths(((StringConversionNode) subNode).getOperand(), p); - } else if (subNodeTypeKind == TypeKind.CHAR) { - // characters always have length 1 - return Collections.singletonList(1); - } else if (isIntRange(subNode, p)) { - // Try to get a list of lengths from a range of integer values converted to string. - // @IntVal is not checked for, because if it is present, we would already have the - // actual string values. - Range lengthRange = getIntRangeStringLengthRange(subNode, p); - return ValueCheckerUtils.getValuesFromRange(lengthRange, Integer.class); - } else if (subNodeTypeKind == TypeKind.BYTE) { - // bytes are between 1 and 4 characters long - return ValueCheckerUtils.getValuesFromRange(Range.create(1, 4), Integer.class); - } else if (subNodeTypeKind == TypeKind.SHORT) { - // shorts are between 1 and 6 characters long - return ValueCheckerUtils.getValuesFromRange(Range.create(1, 6), Integer.class); - } else { - return null; - } - } - - /** - * Returns a list of possible values for {@code subNode}, as casted to a String. Returns null if - * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is - * bottom. - * - * @param subNode a subNode of p - * @param p a TransferInput - * @return a list of possible values for {@code subNode} or null - */ - private @Nullable List getStringValues(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror anno = getValueAnnotation(value); - if (anno == null) { - return null; - } - String annoName = AnnotationUtils.annotationName(anno); - switch (annoName) { - case ValueAnnotatedTypeFactory.UNKNOWN_NAME: - return null; - case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: + /** The Value type factory. */ + protected final ValueAnnotatedTypeFactory atypeFactory; + + /** The Value qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; + + /** True if -AnonNullStringsConcatenation was passed on the command line. */ + private final boolean nonNullStringsConcatenation; + + /** + * Create a new ValueTransfer. + * + * @param analysis the corresponding analysis + */ + public ValueTransfer(CFAbstractAnalysis analysis) { + super(analysis); + atypeFactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory(); + qualHierarchy = atypeFactory.getQualifierHierarchy(); + nonNullStringsConcatenation = + atypeFactory.getChecker().hasOption("nonNullStringsConcatenation"); + } + + /** Returns a range of possible lengths for an integer from a range, as casted to a String. */ + private Range getIntRangeStringLengthRange(Node subNode, TransferInput p) { + Range valueRange = getIntRange(subNode, p); + + // Get lengths of the bounds + int fromLength = Long.toString(valueRange.from).length(); + int toLength = Long.toString(valueRange.to).length(); + + int lowerLength = Math.min(fromLength, toLength); + // In case the range contains 0, the minimum length is 1 even if both bounds are longer + if (valueRange.contains(0)) { + lowerLength = 1; + } + + int upperLength = Math.max(fromLength, toLength); + + return Range.create(lowerLength, upperLength); + } + + /** + * Returns a range of possible lengths for {@code subNode}, as casted to a String. + * + * @param subNode some subnode of {@code p} + * @param p a TransferInput + * @return a range of possible lengths for {@code subNode}, as casted to a String + */ + private @Nullable Range getStringLengthRange(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + + AnnotationMirror anno = getValueAnnotation(value); + if (anno == null) { + return null; + } + String annoName = AnnotationUtils.annotationName(anno); + if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + return atypeFactory.getRange(anno); + } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Range.NOTHING; + } + + TypeKind subNodeTypeKind = subNode.getType().getKind(); + + // handle values converted to string (ints, longs, longs with @IntRange) + if (subNode instanceof StringConversionNode) { + return getStringLengthRange(((StringConversionNode) subNode).getOperand(), p); + } else if (isIntRange(subNode, p)) { + return getIntRangeStringLengthRange(subNode, p); + } else if (subNodeTypeKind == TypeKind.INT) { + // ints are between 1 and 11 characters long + return Range.create(1, 11); + } else if (subNodeTypeKind == TypeKind.LONG) { + // longs are between 1 and 20 characters long + return Range.create(1, 20); + } + + return Range.create(0, Integer.MAX_VALUE); + } + + /** + * Returns a list of possible lengths for {@code subNode}, as casted to a String. Returns null + * if {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is + * bottom. + */ + private @Nullable List getStringLengths( + Node subNode, TransferInput p) { + + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror anno = getValueAnnotation(value); + if (anno == null) { + return null; + } + String annoName = AnnotationUtils.annotationName(anno); + if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { + return atypeFactory.getArrayLength(anno); + } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Collections.emptyList(); + } + + TypeKind subNodeTypeKind = subNode.getType().getKind(); + + // handle values converted to string (characters, bytes, shorts, ints with @IntRange) + if (subNode instanceof StringConversionNode) { + return getStringLengths(((StringConversionNode) subNode).getOperand(), p); + } else if (subNodeTypeKind == TypeKind.CHAR) { + // characters always have length 1 + return Collections.singletonList(1); + } else if (isIntRange(subNode, p)) { + // Try to get a list of lengths from a range of integer values converted to string. + // @IntVal is not checked for, because if it is present, we would already have the + // actual string values. + Range lengthRange = getIntRangeStringLengthRange(subNode, p); + return ValueCheckerUtils.getValuesFromRange(lengthRange, Integer.class); + } else if (subNodeTypeKind == TypeKind.BYTE) { + // bytes are between 1 and 4 characters long + return ValueCheckerUtils.getValuesFromRange(Range.create(1, 4), Integer.class); + } else if (subNodeTypeKind == TypeKind.SHORT) { + // shorts are between 1 and 6 characters long + return ValueCheckerUtils.getValuesFromRange(Range.create(1, 6), Integer.class); + } else { + return null; + } + } + + /** + * Returns a list of possible values for {@code subNode}, as casted to a String. Returns null if + * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is + * bottom. + * + * @param subNode a subNode of p + * @param p a TransferInput + * @return a list of possible values for {@code subNode} or null + */ + private @Nullable List getStringValues( + Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror anno = getValueAnnotation(value); + if (anno == null) { + return null; + } + String annoName = AnnotationUtils.annotationName(anno); + switch (annoName) { + case ValueAnnotatedTypeFactory.UNKNOWN_NAME: + return null; + case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: + return Collections.emptyList(); + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + return atypeFactory.getStringValues(anno); + default: + // Do nothing. + } + + // @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string) + List values; + if (annoName.equals(ValueAnnotatedTypeFactory.BOOLVAL_NAME)) { + values = getBooleanValues(subNode, p); + } else if (subNode.getType().getKind() == TypeKind.CHAR) { + values = getCharValues(subNode, p); + } else if (subNode instanceof StringConversionNode) { + return getStringValues(((StringConversionNode) subNode).getOperand(), p); + } else if (isIntRange(subNode, p)) { + Range range = getIntRange(subNode, p); + List longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); + values = NumberUtils.castNumbers(subNode.getType(), longValues); + } else { + values = getNumericalValues(subNode, p); + } + if (values == null) { + return null; + } + List stringValues = CollectionsPlume.mapList(Object::toString, values); + // Empty list means bottom value + return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues; + } + + /** + * Create a @BoolVal CFValue for the given boolean value. + * + * @param value the value for the @BoolVal annotation + * @return a @BoolVal CFValue for the given boolean value + */ + private CFValue createBooleanCFValue(boolean value) { + return analysis.createSingleAnnotationValue( + value ? atypeFactory.BOOLEAN_TRUE : atypeFactory.BOOLEAN_FALSE, + atypeFactory.types.getPrimitiveType(TypeKind.BOOLEAN)); + } + + /** + * Get the unique possible boolean value from @BoolVal. Returns null if that is not the case + * (including if the CFValue is not @BoolVal). + * + * @param value a CFValue + * @return theboolean if {@code value} represents a single boolean value; otherwise null + */ + private @Nullable Boolean getBooleanValue(CFValue value) { + AnnotationMirror boolAnno = + AnnotationUtils.getAnnotationByName( + value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); + return atypeFactory.getBooleanValue(boolAnno); + } + + /** + * Get possible boolean values for a node. Returns null if there is no estimate, because the + * node's value is not @BoolVal. + * + * @param subNode the node whose value to obtain + * @param p the transfer input in which to look up values + * @return the possible boolean values for the node + */ + private @Nullable List getBooleanValues( + Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror intAnno = + AnnotationUtils.getAnnotationByName( + value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); + return atypeFactory.getBooleanValues(intAnno); + } + + /** Get possible char values from annotation @IntRange or @IntVal. */ + private List getCharValues(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + AnnotationMirror intAnno; + + intAnno = + AnnotationUtils.getAnnotationByName( + value.getAnnotations(), ValueAnnotatedTypeFactory.INTVAL_NAME); + if (intAnno != null) { + return atypeFactory.getCharValues(intAnno); + } + + if (atypeFactory.isIntRange(value.getAnnotations())) { + intAnno = + qualHierarchy.findAnnotationInHierarchy( + value.getAnnotations(), atypeFactory.UNKNOWNVAL); + Range range = atypeFactory.getRange(intAnno); + return ValueCheckerUtils.getValuesFromRange(range, Character.class); + } + return Collections.emptyList(); - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - return atypeFactory.getStringValues(anno); - default: - // Do nothing. - } - - // @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string) - List values; - if (annoName.equals(ValueAnnotatedTypeFactory.BOOLVAL_NAME)) { - values = getBooleanValues(subNode, p); - } else if (subNode.getType().getKind() == TypeKind.CHAR) { - values = getCharValues(subNode, p); - } else if (subNode instanceof StringConversionNode) { - return getStringValues(((StringConversionNode) subNode).getOperand(), p); - } else if (isIntRange(subNode, p)) { - Range range = getIntRange(subNode, p); - List longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); - values = NumberUtils.castNumbers(subNode.getType(), longValues); - } else { - values = getNumericalValues(subNode, p); - } - if (values == null) { - return null; - } - List stringValues = CollectionsPlume.mapList(Object::toString, values); - // Empty list means bottom value - return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues; - } - - /** - * Create a @BoolVal CFValue for the given boolean value. - * - * @param value the value for the @BoolVal annotation - * @return a @BoolVal CFValue for the given boolean value - */ - private CFValue createBooleanCFValue(boolean value) { - return analysis.createSingleAnnotationValue( - value ? atypeFactory.BOOLEAN_TRUE : atypeFactory.BOOLEAN_FALSE, - atypeFactory.types.getPrimitiveType(TypeKind.BOOLEAN)); - } - - /** - * Get the unique possible boolean value from @BoolVal. Returns null if that is not the case - * (including if the CFValue is not @BoolVal). - * - * @param value a CFValue - * @return theboolean if {@code value} represents a single boolean value; otherwise null - */ - private @Nullable Boolean getBooleanValue(CFValue value) { - AnnotationMirror boolAnno = - AnnotationUtils.getAnnotationByName( - value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); - return atypeFactory.getBooleanValue(boolAnno); - } - - /** - * Get possible boolean values for a node. Returns null if there is no estimate, because the - * node's value is not @BoolVal. - * - * @param subNode the node whose value to obtain - * @param p the transfer input in which to look up values - * @return the possible boolean values for the node - */ - private @Nullable List getBooleanValues( - Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror intAnno = - AnnotationUtils.getAnnotationByName( - value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); - return atypeFactory.getBooleanValues(intAnno); - } - - /** Get possible char values from annotation @IntRange or @IntVal. */ - private List getCharValues(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - AnnotationMirror intAnno; - - intAnno = - AnnotationUtils.getAnnotationByName( - value.getAnnotations(), ValueAnnotatedTypeFactory.INTVAL_NAME); - if (intAnno != null) { - return atypeFactory.getCharValues(intAnno); - } - - if (atypeFactory.isIntRange(value.getAnnotations())) { - intAnno = - qualHierarchy.findAnnotationInHierarchy(value.getAnnotations(), atypeFactory.UNKNOWNVAL); - Range range = atypeFactory.getRange(intAnno); - return ValueCheckerUtils.getValuesFromRange(range, Character.class); - } - - return Collections.emptyList(); - } - - private AnnotationMirror getValueAnnotation(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - return getValueAnnotation(value); - } - - /** - * Extract the Value Checker annotation from a CFValue object. - * - * @param cfValue a CFValue object - * @return the Value Checker annotation within cfValue - */ - private AnnotationMirror getValueAnnotation(CFValue cfValue) { - return qualHierarchy.findAnnotationInHierarchy( - cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL); - } - - /** - * Returns a list of possible values, or null if no estimate is available and any value is - * possible. - */ - private @Nullable List getNumericalValues( - Node subNode, TransferInput p) { - AnnotationMirror valueAnno = getValueAnnotation(subNode, p); - return getNumericalValues(subNode, valueAnno); - } - - /** - * Returns the numerical values in valueAnno casted to the type of subNode. - * - * @param subNode node - * @param valueAnno annotation mirror - * @return the numerical values in valueAnno casted to the type of subNode - */ - private @Nullable List getNumericalValues( - Node subNode, AnnotationMirror valueAnno) { - - if (valueAnno == null - || AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - return null; - } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Collections.emptyList(); - } - List values; - if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - values = atypeFactory.getIntValues(valueAnno); - } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { - values = atypeFactory.getDoubleValues(valueAnno); - } else { - return null; - } - return NumberUtils.castNumbers(subNode.getType(), values); - } - - /** Get possible integer range from annotation. */ - private Range getIntRange(Node subNode, TransferInput p) { - AnnotationMirror val = getValueAnnotation(subNode, p); - return getIntRangeFromAnnotation(subNode, val); - } - - /** - * Returns the {@link Range} object corresponding to the annotation {@code val} casted to the type - * of {@code node}. - * - * @param node a node - * @param val annotation mirror - * @return the {@link Range} object corresponding to the annotation {@code val} casted to the type - * of {@code node}. - */ - private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) { - Range range; - if (val == null || AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { - range = Range.EVERYTHING; - } else if (atypeFactory.isIntRange(val)) { - range = atypeFactory.getRange(val); - } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - List values = atypeFactory.getIntValues(val); - range = ValueCheckerUtils.getRangeFromValues(values); - } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { - List values = atypeFactory.getDoubleValues(val); - range = ValueCheckerUtils.getRangeFromValues(values); - } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - return Range.NOTHING; - } else { - range = Range.EVERYTHING; - } - return NumberUtils.castRange(node.getType(), range); - } - - /** - * Returns true if subNode is annotated with {@code @IntRange}. - * - * @param subNode subNode of {@code p} - * @param p a TransferInput - * @return true if this subNode is annotated with {@code @IntRange} - */ - private boolean isIntRange(Node subNode, TransferInput p) { - CFValue value = p.getValueOfSubNode(subNode); - return atypeFactory.isIntRange(value.getAnnotations()); - } - - /** - * Returns true if {@code node} an integral type and is {@code anno} is {@code @UnknownVal}. - * - * @param node a node - * @param anno annotation mirror - * @return true if node is annotated with {@code @UnknownVal} and it is an integral type - */ - private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) { - return AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.UNKNOWN_NAME) - && TypesUtils.isIntegralPrimitive(node.getType()); - } - - /** - * Returns true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}. - * - * @param node the node to inspect - * @param p storage - * @return true if this node is annotated with {@code @IntRange} or {@code @UnknownVal} - */ - private boolean isIntRangeOrIntegralUnknownVal(Node node, TransferInput p) { - if (isIntRange(node, p)) { - return true; - } - return isIntegralUnknownVal(node, getValueAnnotation(p.getValueOfSubNode(node))); - } - - /** - * Create a new transfer result based on the original result and the new annotation. - * - * @param result the original result - * @param resultAnno the new annotation - * @return the new transfer result - */ - private TransferResult createNewResult( - TransferResult result, AnnotationMirror resultAnno) { - CFValue newResultValue = - analysis.createSingleAnnotationValue( - resultAnno, result.getResultValue().getUnderlyingType()); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - - /** Create a boolean transfer result. */ - private TransferResult createNewResultBoolean( - CFStore thenStore, CFStore elseStore, List resultValues, TypeMirror underlyingType) { - AnnotationMirror boolVal = atypeFactory.createBooleanAnnotation(resultValues); - CFValue newResultValue = analysis.createSingleAnnotationValue(boolVal, underlyingType); - if (elseStore != null) { - return new ConditionalTransferResult<>(newResultValue, thenStore, elseStore); - } else { - return new RegularTransferResult<>(newResultValue, thenStore); - } - } - - @Override - public TransferResult visitFieldAccess( - FieldAccessNode node, TransferInput in) { - - TransferResult result = super.visitFieldAccess(node, in); - refineArrayAtLengthAccess(node, result.getRegularStore()); - return result; - } - - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput p) { - TransferResult result = super.visitMethodInvocation(n, p); - refineAtLengthInvocation(n, result.getRegularStore()); - return result; - } - - /** - * If array.length is encountered, transform its @IntVal annotation into an @ArrayLen annotation - * for array. - */ - private void refineArrayAtLengthAccess(FieldAccessNode arrayLengthNode, CFStore store) { - if (!NodeUtils.isArrayLengthFieldAccess(arrayLengthNode)) { - return; - } - - refineAtLengthAccess(arrayLengthNode, arrayLengthNode.getReceiver(), store); - } - - /** - * If length method is invoked for a sequence, transform its @IntVal annotation into an @ArrayLen - * annotation. - * - * @param lengthNode the length method invocation node - * @param store the Checker Framework store - */ - private void refineAtLengthInvocation(MethodInvocationNode lengthNode, CFStore store) { - if (atypeFactory - .getMethodIdentifier() - .isStringLengthMethod(lengthNode.getTarget().getMethod())) { - MethodAccessNode methodAccessNode = lengthNode.getTarget(); - refineAtLengthAccess(lengthNode, methodAccessNode.getReceiver(), store); - } else if (atypeFactory - .getMethodIdentifier() - .isArrayGetLengthMethod(lengthNode.getTarget().getMethod())) { - Node node = lengthNode.getArguments().get(0); - refineAtLengthAccess(lengthNode, node, store); - } - } - - /** - * Gets a value checker annotation relevant for an array or a string. - * - * @param arrayOrStringNode the node whose annotation to return - * @return the value checker annotation for the array or a string - */ - private AnnotationMirror getArrayOrStringAnnotation(Node arrayOrStringNode) { - AnnotationMirror arrayOrStringAnno = - atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), StringVal.class); - if (arrayOrStringAnno == null) { - arrayOrStringAnno = - atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLen.class); - } - if (arrayOrStringAnno == null) { - arrayOrStringAnno = - atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLenRange.class); - } - - return arrayOrStringAnno; - } - - /** - * Transform @IntVal or @IntRange annotations of an array or string length into an @ArrayLen - * or @ArrayLenRange annotation for the array or string. - * - * @param lengthNode an invocation of method {@code length} or an access of the {@code length} - * field - * @param receiverNode the receiver of {@code lengthNode} - * @param store the store to update - */ - private void refineAtLengthAccess(Node lengthNode, Node receiverNode, CFStore store) { - JavaExpression lengthExpr = JavaExpression.fromNode(lengthNode); - - // If the expression is not representable (for example if String.length() for some reason is - // not marked @Pure, then do not refine. - if (lengthExpr instanceof Unknown) { - return; - } - - CFValue value = store.getValue(lengthExpr); - if (value == null) { - return; - } - - AnnotationMirror lengthAnno = getValueAnnotation(value); - if (lengthAnno == null) { - return; - } - JavaExpression receiverJE = JavaExpression.fromNode(receiverNode); - if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { - // If the length is bottom, then this is dead code, so the receiver type - // should also be bottom. - store.insertValue(receiverJE, lengthAnno); - return; - } - - RangeOrListOfValues rolv; - if (atypeFactory.isIntRange(lengthAnno)) { - rolv = new RangeOrListOfValues(atypeFactory.getRange(lengthAnno)); - } else if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - List lengthValues = atypeFactory.getIntValues(lengthAnno); - rolv = new RangeOrListOfValues(RangeOrListOfValues.convertLongsToInts(lengthValues)); - } else { - return; - } - AnnotationMirror newRecAnno = rolv.createAnnotation(atypeFactory); - AnnotationMirror oldRecAnno = getArrayOrStringAnnotation(receiverNode); - - AnnotationMirror combinedRecAnno; - // If the receiver doesn't have an @ArrayLen annotation, use the new annotation. - // If it does have an annotation, combine the facts known about the receiver - // with the facts known about its length using GLB. - if (oldRecAnno == null) { - combinedRecAnno = newRecAnno; - } else { - TypeMirror receiverTM = receiverJE.getType(); - combinedRecAnno = - qualHierarchy.greatestLowerBoundShallow(oldRecAnno, receiverTM, newRecAnno, receiverTM); - } - store.insertValue(receiverJE, combinedRecAnno); - } - - @Override - public TransferResult visitStringConcatenate( - StringConcatenateNode n, TransferInput p) { - TransferResult result = super.visitStringConcatenate(n, p); - return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result); - } - - /** - * Calculates possible lengths of a result of string concatenation of strings with known lengths. - */ - private List calculateLengthAddition( - List leftLengths, List rightLengths) { - List result = new ArrayList<>(leftLengths.size() * rightLengths.size()); - - for (int left : leftLengths) { - for (int right : rightLengths) { - long resultLength = (long) left + right; - // Lengths not fitting into int are not allowed - if (resultLength <= Integer.MAX_VALUE) { - result.add((int) resultLength); + } + + private AnnotationMirror getValueAnnotation(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + return getValueAnnotation(value); + } + + /** + * Extract the Value Checker annotation from a CFValue object. + * + * @param cfValue a CFValue object + * @return the Value Checker annotation within cfValue + */ + private AnnotationMirror getValueAnnotation(CFValue cfValue) { + return qualHierarchy.findAnnotationInHierarchy( + cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL); + } + + /** + * Returns a list of possible values, or null if no estimate is available and any value is + * possible. + */ + private @Nullable List getNumericalValues( + Node subNode, TransferInput p) { + AnnotationMirror valueAnno = getValueAnnotation(subNode, p); + return getNumericalValues(subNode, valueAnno); + } + + /** + * Returns the numerical values in valueAnno casted to the type of subNode. + * + * @param subNode node + * @param valueAnno annotation mirror + * @return the numerical values in valueAnno casted to the type of subNode + */ + private @Nullable List getNumericalValues( + Node subNode, AnnotationMirror valueAnno) { + + if (valueAnno == null + || AnnotationUtils.areSameByName( + valueAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + return null; + } else if (AnnotationUtils.areSameByName( + valueAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Collections.emptyList(); + } + List values; + if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + values = atypeFactory.getIntValues(valueAnno); + } else if (AnnotationUtils.areSameByName( + valueAnno, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { + values = atypeFactory.getDoubleValues(valueAnno); + } else { + return null; + } + return NumberUtils.castNumbers(subNode.getType(), values); + } + + /** Get possible integer range from annotation. */ + private Range getIntRange(Node subNode, TransferInput p) { + AnnotationMirror val = getValueAnnotation(subNode, p); + return getIntRangeFromAnnotation(subNode, val); + } + + /** + * Returns the {@link Range} object corresponding to the annotation {@code val} casted to the + * type of {@code node}. + * + * @param node a node + * @param val annotation mirror + * @return the {@link Range} object corresponding to the annotation {@code val} casted to the + * type of {@code node}. + */ + private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) { + Range range; + if (val == null + || AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { + range = Range.EVERYTHING; + } else if (atypeFactory.isIntRange(val)) { + range = atypeFactory.getRange(val); + } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + List values = atypeFactory.getIntValues(val); + range = ValueCheckerUtils.getRangeFromValues(values); + } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { + List values = atypeFactory.getDoubleValues(val); + range = ValueCheckerUtils.getRangeFromValues(values); + } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + return Range.NOTHING; + } else { + range = Range.EVERYTHING; + } + return NumberUtils.castRange(node.getType(), range); + } + + /** + * Returns true if subNode is annotated with {@code @IntRange}. + * + * @param subNode subNode of {@code p} + * @param p a TransferInput + * @return true if this subNode is annotated with {@code @IntRange} + */ + private boolean isIntRange(Node subNode, TransferInput p) { + CFValue value = p.getValueOfSubNode(subNode); + return atypeFactory.isIntRange(value.getAnnotations()); + } + + /** + * Returns true if {@code node} an integral type and is {@code anno} is {@code @UnknownVal}. + * + * @param node a node + * @param anno annotation mirror + * @return true if node is annotated with {@code @UnknownVal} and it is an integral type + */ + private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) { + return AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.UNKNOWN_NAME) + && TypesUtils.isIntegralPrimitive(node.getType()); + } + + /** + * Returns true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}. + * + * @param node the node to inspect + * @param p storage + * @return true if this node is annotated with {@code @IntRange} or {@code @UnknownVal} + */ + private boolean isIntRangeOrIntegralUnknownVal(Node node, TransferInput p) { + if (isIntRange(node, p)) { + return true; } - } - } - - return result; - } - - /** - * Calculates a range of possible lengths of a result of string concatenation of strings with - * known ranges of lengths. - */ - private Range calculateLengthRangeAddition(Range leftLengths, Range rightLengths) { - return leftLengths.plus(rightLengths).intersect(Range.INT_EVERYTHING); - } - - /** - * Checks whether or not the passed node is nullable. This superficial check assumes that every - * node is nullable unless it is a primitive, String literal, or compile-time constant. - * - * @return false if the node's run-time can't be null; true if the node's run-time value may be - * null, or if this method is not precise enough - */ - private boolean isNullable(Node node) { - if (node instanceof StringConversionNode) { - if (((StringConversionNode) node).getOperand().getType().getKind().isPrimitive()) { - return false; - } - } else if (node instanceof StringLiteralNode) { - return false; - } else if (node instanceof StringConcatenateNode) { - return false; - } - - Element element = TreeUtils.elementFromTree((ExpressionTree) node.getTree()); - return !ElementUtils.isCompileTimeConstant(element); - } - - /** Creates an annotation for a result of string concatenation. */ - private AnnotationMirror createAnnotationForStringConcatenation( - Node leftOperand, Node rightOperand, TransferInput p) { - - // Try using sets of string values - List leftValues = getStringValues(leftOperand, p); - List rightValues = getStringValues(rightOperand, p); - - if (leftValues != null && rightValues != null) { - // Both operands have known string values, compute set of results - if (!nonNullStringsConcatenation) { - if (isNullable(leftOperand)) { - leftValues = CollectionsPlume.append(leftValues, "null"); + return isIntegralUnknownVal(node, getValueAnnotation(p.getValueOfSubNode(node))); + } + + /** + * Create a new transfer result based on the original result and the new annotation. + * + * @param result the original result + * @param resultAnno the new annotation + * @return the new transfer result + */ + private TransferResult createNewResult( + TransferResult result, AnnotationMirror resultAnno) { + CFValue newResultValue = + analysis.createSingleAnnotationValue( + resultAnno, result.getResultValue().getUnderlyingType()); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } + + /** Create a boolean transfer result. */ + private TransferResult createNewResultBoolean( + CFStore thenStore, + CFStore elseStore, + List resultValues, + TypeMirror underlyingType) { + AnnotationMirror boolVal = atypeFactory.createBooleanAnnotation(resultValues); + CFValue newResultValue = analysis.createSingleAnnotationValue(boolVal, underlyingType); + if (elseStore != null) { + return new ConditionalTransferResult<>(newResultValue, thenStore, elseStore); + } else { + return new RegularTransferResult<>(newResultValue, thenStore); + } + } + + @Override + public TransferResult visitFieldAccess( + FieldAccessNode node, TransferInput in) { + + TransferResult result = super.visitFieldAccess(node, in); + refineArrayAtLengthAccess(node, result.getRegularStore()); + return result; + } + + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput p) { + TransferResult result = super.visitMethodInvocation(n, p); + refineAtLengthInvocation(n, result.getRegularStore()); + return result; + } + + /** + * If array.length is encountered, transform its @IntVal annotation into an @ArrayLen annotation + * for array. + */ + private void refineArrayAtLengthAccess(FieldAccessNode arrayLengthNode, CFStore store) { + if (!NodeUtils.isArrayLengthFieldAccess(arrayLengthNode)) { + return; + } + + refineAtLengthAccess(arrayLengthNode, arrayLengthNode.getReceiver(), store); + } + + /** + * If length method is invoked for a sequence, transform its @IntVal annotation into + * an @ArrayLen annotation. + * + * @param lengthNode the length method invocation node + * @param store the Checker Framework store + */ + private void refineAtLengthInvocation(MethodInvocationNode lengthNode, CFStore store) { + if (atypeFactory + .getMethodIdentifier() + .isStringLengthMethod(lengthNode.getTarget().getMethod())) { + MethodAccessNode methodAccessNode = lengthNode.getTarget(); + refineAtLengthAccess(lengthNode, methodAccessNode.getReceiver(), store); + } else if (atypeFactory + .getMethodIdentifier() + .isArrayGetLengthMethod(lengthNode.getTarget().getMethod())) { + Node node = lengthNode.getArguments().get(0); + refineAtLengthAccess(lengthNode, node, store); } - if (isNullable(rightOperand)) { - rightValues = CollectionsPlume.append(rightValues, "null"); + } + + /** + * Gets a value checker annotation relevant for an array or a string. + * + * @param arrayOrStringNode the node whose annotation to return + * @return the value checker annotation for the array or a string + */ + private AnnotationMirror getArrayOrStringAnnotation(Node arrayOrStringNode) { + AnnotationMirror arrayOrStringAnno = + atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), StringVal.class); + if (arrayOrStringAnno == null) { + arrayOrStringAnno = + atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLen.class); } - } else { - if (leftOperand instanceof StringConversionNode) { - if (((StringConversionNode) leftOperand).getOperand().getType().getKind() - == TypeKind.NULL) { - leftValues = CollectionsPlume.append(leftValues, "null"); - } + if (arrayOrStringAnno == null) { + arrayOrStringAnno = + atypeFactory.getAnnotationMirror( + arrayOrStringNode.getTree(), ArrayLenRange.class); } - if (rightOperand instanceof StringConversionNode) { - if (((StringConversionNode) rightOperand).getOperand().getType().getKind() - == TypeKind.NULL) { - rightValues = CollectionsPlume.append(rightValues, "null"); - } + + return arrayOrStringAnno; + } + + /** + * Transform @IntVal or @IntRange annotations of an array or string length into an @ArrayLen + * or @ArrayLenRange annotation for the array or string. + * + * @param lengthNode an invocation of method {@code length} or an access of the {@code length} + * field + * @param receiverNode the receiver of {@code lengthNode} + * @param store the store to update + */ + private void refineAtLengthAccess(Node lengthNode, Node receiverNode, CFStore store) { + JavaExpression lengthExpr = JavaExpression.fromNode(lengthNode); + + // If the expression is not representable (for example if String.length() for some reason is + // not marked @Pure, then do not refine. + if (lengthExpr instanceof Unknown) { + return; } - } - List concatValues = new ArrayList<>(leftValues.size() * rightValues.size()); - for (String left : leftValues) { - for (String right : rightValues) { - concatValues.add(left + right); + CFValue value = store.getValue(lengthExpr); + if (value == null) { + return; } - } - return atypeFactory.createStringAnnotation(concatValues); - } - - // Try using sets of lengths - List leftLengths = - leftValues != null - ? ValueCheckerUtils.getLengthsForStringValues(leftValues) - : getStringLengths(leftOperand, p); - List rightLengths = - rightValues != null - ? ValueCheckerUtils.getLengthsForStringValues(rightValues) - : getStringLengths(rightOperand, p); - - if (leftLengths != null && rightLengths != null) { - // Both operands have known lengths, compute set of result lengths - if (!nonNullStringsConcatenation) { - if (isNullable(leftOperand)) { - leftLengths = new ArrayList<>(leftLengths); - leftLengths.add(4); // "null" + + AnnotationMirror lengthAnno = getValueAnnotation(value); + if (lengthAnno == null) { + return; } - if (isNullable(rightOperand)) { - rightLengths = new ArrayList<>(rightLengths); - rightLengths.add(4); // "null" + JavaExpression receiverJE = JavaExpression.fromNode(receiverNode); + if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { + // If the length is bottom, then this is dead code, so the receiver type + // should also be bottom. + store.insertValue(receiverJE, lengthAnno); + return; } - } - List concatLengths = calculateLengthAddition(leftLengths, rightLengths); - return atypeFactory.createArrayLenAnnotation(concatLengths); - } - - // Try using ranges of lengths - Range leftLengthRange = - leftLengths != null - ? ValueCheckerUtils.getRangeFromValues(leftLengths) - : getStringLengthRange(leftOperand, p); - Range rightLengthRange = - rightLengths != null - ? ValueCheckerUtils.getRangeFromValues(rightLengths) - : getStringLengthRange(rightOperand, p); - - if (leftLengthRange != null && rightLengthRange != null) { - // Both operands have a length from a known range, compute a range of result lengths - if (!nonNullStringsConcatenation) { - if (isNullable(leftOperand)) { - leftLengthRange = leftLengthRange.union(Range.create(4, 4)); // "null" + + RangeOrListOfValues rolv; + if (atypeFactory.isIntRange(lengthAnno)) { + rolv = new RangeOrListOfValues(atypeFactory.getRange(lengthAnno)); + } else if (AnnotationUtils.areSameByName( + lengthAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + List lengthValues = atypeFactory.getIntValues(lengthAnno); + rolv = new RangeOrListOfValues(RangeOrListOfValues.convertLongsToInts(lengthValues)); + } else { + return; } - if (isNullable(rightOperand)) { - rightLengthRange = rightLengthRange.union(Range.create(4, 4)); // "null" + AnnotationMirror newRecAnno = rolv.createAnnotation(atypeFactory); + AnnotationMirror oldRecAnno = getArrayOrStringAnnotation(receiverNode); + + AnnotationMirror combinedRecAnno; + // If the receiver doesn't have an @ArrayLen annotation, use the new annotation. + // If it does have an annotation, combine the facts known about the receiver + // with the facts known about its length using GLB. + if (oldRecAnno == null) { + combinedRecAnno = newRecAnno; + } else { + TypeMirror receiverTM = receiverJE.getType(); + combinedRecAnno = + qualHierarchy.greatestLowerBoundShallow( + oldRecAnno, receiverTM, newRecAnno, receiverTM); } - } - Range concatLengthRange = calculateLengthRangeAddition(leftLengthRange, rightLengthRange); - return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange); - } - - return atypeFactory.UNKNOWNVAL; - } - - public TransferResult stringConcatenation( - Node leftOperand, - Node rightOperand, - TransferInput p, - TransferResult result) { - - AnnotationMirror resultAnno = - createAnnotationForStringConcatenation(leftOperand, rightOperand, p); - - TypeMirror underlyingType = result.getResultValue().getUnderlyingType(); - CFValue newResultValue = analysis.createSingleAnnotationValue(resultAnno, underlyingType); - return new RegularTransferResult<>(newResultValue, result.getRegularStore()); - } - - /** Binary operations that are analyzed by the value checker. */ - enum NumericalBinaryOps { - ADDITION, - SUBTRACTION, - DIVISION, - REMAINDER, - MULTIPLICATION, - SHIFT_LEFT, - SIGNED_SHIFT_RIGHT, - UNSIGNED_SHIFT_RIGHT, - BITWISE_AND, - BITWISE_OR, - BITWISE_XOR; - } - - /** - * Get the refined annotation after a numerical binary operation. - * - * @param leftNode the node that represents the left operand - * @param rightNode the node that represents the right operand - * @param op the operator type - * @param p the transfer input - * @return the result annotation mirror - */ - private AnnotationMirror calculateNumericalBinaryOp( - Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) { - if (!isIntRangeOrIntegralUnknownVal(leftNode, p) - && !isIntRangeOrIntegralUnknownVal(rightNode, p)) { - List resultValues = calculateValuesBinaryOp(leftNode, rightNode, op, p); - return atypeFactory.createNumberAnnotationMirror(resultValues); - } else { - Range resultRange = calculateRangeBinaryOp(leftNode, rightNode, op, p); - return atypeFactory.createIntRangeAnnotation(resultRange); - } - } - - /** Calculate the result range after a binary operation between two numerical type nodes. */ - private Range calculateRangeBinaryOp( - Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) { - if (TypesUtils.isIntegralPrimitive(leftNode.getType()) - && TypesUtils.isIntegralPrimitive(rightNode.getType())) { - Range leftRange = getIntRange(leftNode, p); - Range rightRange = getIntRange(rightNode, p); - Range resultRange; - switch (op) { - case ADDITION: - resultRange = leftRange.plus(rightRange); - break; - case SUBTRACTION: - resultRange = leftRange.minus(rightRange); - break; - case MULTIPLICATION: - resultRange = leftRange.times(rightRange); - break; - case DIVISION: - resultRange = leftRange.divide(rightRange); - break; - case REMAINDER: - resultRange = leftRange.remainder(rightRange); - break; - case SHIFT_LEFT: - resultRange = leftRange.shiftLeft(rightRange); - break; - case SIGNED_SHIFT_RIGHT: - resultRange = leftRange.signedShiftRight(rightRange); - break; - case UNSIGNED_SHIFT_RIGHT: - resultRange = leftRange.unsignedShiftRight(rightRange); - break; - case BITWISE_AND: - resultRange = leftRange.bitwiseAnd(rightRange); - break; - case BITWISE_OR: - resultRange = leftRange.bitwiseOr(rightRange); - break; - case BITWISE_XOR: - resultRange = leftRange.bitwiseXor(rightRange); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - // Any integral type with less than 32 bits would be promoted to 32-bit int type during - // operations. - return leftNode.getType().getKind() == TypeKind.LONG - || rightNode.getType().getKind() == TypeKind.LONG - ? resultRange - : resultRange.intRange(); - } else { - return Range.EVERYTHING; - } - } - - /** Calculate the possible values after a binary operation between two numerical type nodes. */ - private @Nullable List calculateValuesBinaryOp( - Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) { - List lefts = getNumericalValues(leftNode, p); - List rights = getNumericalValues(rightNode, p); - if (lefts == null || rights == null) { - return null; - } - List resultValues = new ArrayList<>(lefts.size() * rights.size()); - for (Number left : lefts) { - NumberMath nmLeft = NumberMath.getNumberMath(left); - for (Number right : rights) { - switch (op) { - case ADDITION: - resultValues.add(nmLeft.plus(right)); - break; - case DIVISION: - Number result = nmLeft.divide(right); - if (result != null) { - resultValues.add(result); + store.insertValue(receiverJE, combinedRecAnno); + } + + @Override + public TransferResult visitStringConcatenate( + StringConcatenateNode n, TransferInput p) { + TransferResult result = super.visitStringConcatenate(n, p); + return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result); + } + + /** + * Calculates possible lengths of a result of string concatenation of strings with known + * lengths. + */ + private List calculateLengthAddition( + List leftLengths, List rightLengths) { + List result = new ArrayList<>(leftLengths.size() * rightLengths.size()); + + for (int left : leftLengths) { + for (int right : rightLengths) { + long resultLength = (long) left + right; + // Lengths not fitting into int are not allowed + if (resultLength <= Integer.MAX_VALUE) { + result.add((int) resultLength); + } } - break; - case MULTIPLICATION: - resultValues.add(nmLeft.times(right)); - break; - case REMAINDER: - Number resultR = nmLeft.remainder(right); - if (resultR != null) { - resultValues.add(resultR); + } + + return result; + } + + /** + * Calculates a range of possible lengths of a result of string concatenation of strings with + * known ranges of lengths. + */ + private Range calculateLengthRangeAddition(Range leftLengths, Range rightLengths) { + return leftLengths.plus(rightLengths).intersect(Range.INT_EVERYTHING); + } + + /** + * Checks whether or not the passed node is nullable. This superficial check assumes that every + * node is nullable unless it is a primitive, String literal, or compile-time constant. + * + * @return false if the node's run-time can't be null; true if the node's run-time value may be + * null, or if this method is not precise enough + */ + private boolean isNullable(Node node) { + if (node instanceof StringConversionNode) { + if (((StringConversionNode) node).getOperand().getType().getKind().isPrimitive()) { + return false; } - break; - case SUBTRACTION: - resultValues.add(nmLeft.minus(right)); - break; - case SHIFT_LEFT: - resultValues.add(nmLeft.shiftLeft(right)); - break; - case SIGNED_SHIFT_RIGHT: - resultValues.add(nmLeft.signedShiftRight(right)); - break; - case UNSIGNED_SHIFT_RIGHT: - resultValues.add(nmLeft.unsignedShiftRight(right)); - break; - case BITWISE_AND: - resultValues.add(nmLeft.bitwiseAnd(right)); - break; - case BITWISE_OR: - resultValues.add(nmLeft.bitwiseOr(right)); - break; - case BITWISE_XOR: - resultValues.add(nmLeft.bitwiseXor(right)); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } else if (node instanceof StringLiteralNode) { + return false; + } else if (node instanceof StringConcatenateNode) { + return false; } - } - } - return resultValues; - } - - @Override - public TransferResult visitNumericalAddition( - NumericalAdditionNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalAddition(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitNumericalSubtraction( - NumericalSubtractionNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalSubtraction(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitNumericalMultiplication( - NumericalMultiplicationNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalMultiplication(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.MULTIPLICATION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitIntegerDivision( - IntegerDivisionNode n, TransferInput p) { - TransferResult transferResult = super.visitIntegerDivision(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitFloatingDivision( - FloatingDivisionNode n, TransferInput p) { - TransferResult transferResult = super.visitFloatingDivision(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitIntegerRemainder( - IntegerRemainderNode n, TransferInput p) { - TransferResult transferResult = super.visitIntegerRemainder(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitFloatingRemainder( - FloatingRemainderNode n, TransferInput p) { - TransferResult transferResult = super.visitFloatingRemainder(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitLeftShift( - LeftShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitLeftShift(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitSignedRightShift( - SignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitSignedRightShift(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SIGNED_SHIFT_RIGHT, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitUnsignedRightShift( - UnsignedRightShiftNode n, TransferInput p) { - TransferResult transferResult = super.visitUnsignedRightShift(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseAnd( - BitwiseAndNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseAnd(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseOr( - BitwiseOrNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseOr(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseXor( - BitwiseXorNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseXor(n, p); - AnnotationMirror resultAnno = - calculateNumericalBinaryOp( - n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_XOR, p); - return createNewResult(transferResult, resultAnno); - } - - /** Unary operations that are analyzed by the value checker. */ - enum NumericalUnaryOps { - PLUS, - MINUS, - BITWISE_COMPLEMENT; - } - - /** - * Get the refined annotation after a numerical unary operation. - * - * @param operand the node that represents the operand - * @param op the operator type - * @param p the transfer input - * @return the result annotation mirror - */ - private AnnotationMirror calculateNumericalUnaryOp( - Node operand, NumericalUnaryOps op, TransferInput p) { - if (!isIntRange(operand, p)) { - List resultValues = calculateValuesUnaryOp(operand, op, p); - return atypeFactory.createNumberAnnotationMirror(resultValues); - } else { - Range resultRange = calculateRangeUnaryOp(operand, op, p); - return atypeFactory.createIntRangeAnnotation(resultRange); - } - } - - /** - * Calculate the result range after a unary operation of a numerical type node. - * - * @param operand the node that represents the operand - * @param op the operator type - * @param p the transfer input - * @return the result annotation mirror - */ - private Range calculateRangeUnaryOp( - Node operand, NumericalUnaryOps op, TransferInput p) { - if (TypesUtils.isIntegralPrimitive(operand.getType())) { - Range range = getIntRange(operand, p); - Range resultRange; - switch (op) { - case PLUS: - resultRange = range.unaryPlus(); - break; - case MINUS: - resultRange = range.unaryMinus(); - break; - case BITWISE_COMPLEMENT: - resultRange = range.bitwiseComplement(); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - // Any integral type with less than 32 bits would be promoted to 32-bit int type during - // operations. - return operand.getType().getKind() == TypeKind.LONG ? resultRange : resultRange.intRange(); - } else { - return Range.EVERYTHING; - } - } - - /** Calculate the possible values after a unary operation of a numerical type node. */ - private @Nullable List calculateValuesUnaryOp( - Node operand, NumericalUnaryOps op, TransferInput p) { - List lefts = getNumericalValues(operand, p); - if (lefts == null) { - return null; - } - List resultValues = new ArrayList<>(lefts.size()); - for (Number left : lefts) { - NumberMath nmLeft = NumberMath.getNumberMath(left); - switch (op) { - case PLUS: - resultValues.add(nmLeft.unaryPlus()); - break; - case MINUS: - resultValues.add(nmLeft.unaryMinus()); - break; - case BITWISE_COMPLEMENT: - resultValues.add(nmLeft.bitwiseComplement()); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - } - return resultValues; - } - - @Override - public TransferResult visitNumericalMinus( - NumericalMinusNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalMinus(n, p); - AnnotationMirror resultAnno = - calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitNumericalPlus( - NumericalPlusNode n, TransferInput p) { - TransferResult transferResult = super.visitNumericalPlus(n, p); - AnnotationMirror resultAnno = - calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p); - return createNewResult(transferResult, resultAnno); - } - - @Override - public TransferResult visitBitwiseComplement( - BitwiseComplementNode n, TransferInput p) { - TransferResult transferResult = super.visitBitwiseComplement(n, p); - AnnotationMirror resultAnno = - calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.BITWISE_COMPLEMENT, p); - return createNewResult(transferResult, resultAnno); - } - - enum ComparisonOperators { - EQUAL, - NOT_EQUAL, - GREATER_THAN, - GREATER_THAN_EQ, - LESS_THAN, - LESS_THAN_EQ; - } - - private @Nullable List calculateBinaryComparison( - Node leftNode, - CFValue leftValue, - Node rightNode, - CFValue rightValue, - ComparisonOperators op, - CFStore thenStore, - CFStore elseStore) { - AnnotationMirror leftAnno = getValueAnnotation(leftValue); - AnnotationMirror rightAnno = getValueAnnotation(rightValue); - - if (atypeFactory.isIntRange(leftAnno) - || atypeFactory.isIntRange(rightAnno) - || isIntegralUnknownVal(rightNode, rightAnno) - || isIntegralUnknownVal(leftNode, leftAnno)) { - // If either is @UnknownVal, then refineIntRanges will treat it as the max range and - // thus refine it if possible. Also, if either is an @IntVal, then it will be converted - // to a range. This is less precise in some cases, but avoids the complexity of - // comparing a list of values to a range. (This could be implemented in the future.) - return refineIntRanges(leftNode, leftAnno, rightNode, rightAnno, op, thenStore, elseStore); - } - - List lefts = getNumericalValues(leftNode, leftAnno); - List rights = getNumericalValues(rightNode, rightAnno); - - if (lefts == null || rights == null) { - // Appropriately handle bottom when something is compared to bottom. - if (AnnotationUtils.areSame(leftAnno, atypeFactory.BOTTOMVAL) - || AnnotationUtils.areSame(rightAnno, atypeFactory.BOTTOMVAL)) { - return Collections.emptyList(); - } - return null; + + Element element = TreeUtils.elementFromTree((ExpressionTree) node.getTree()); + return !ElementUtils.isCompileTimeConstant(element); } - // This is a list of all the values that the expression can evaluate to. - int numResultValues = lefts.size() * rights.size(); - List resultValues = new ArrayList<>(numResultValues); + /** Creates an annotation for a result of string concatenation. */ + private AnnotationMirror createAnnotationForStringConcatenation( + Node leftOperand, Node rightOperand, TransferInput p) { + + // Try using sets of string values + List leftValues = getStringValues(leftOperand, p); + List rightValues = getStringValues(rightOperand, p); + + if (leftValues != null && rightValues != null) { + // Both operands have known string values, compute set of results + if (!nonNullStringsConcatenation) { + if (isNullable(leftOperand)) { + leftValues = CollectionsPlume.append(leftValues, "null"); + } + if (isNullable(rightOperand)) { + rightValues = CollectionsPlume.append(rightValues, "null"); + } + } else { + if (leftOperand instanceof StringConversionNode) { + if (((StringConversionNode) leftOperand).getOperand().getType().getKind() + == TypeKind.NULL) { + leftValues = CollectionsPlume.append(leftValues, "null"); + } + } + if (rightOperand instanceof StringConversionNode) { + if (((StringConversionNode) rightOperand).getOperand().getType().getKind() + == TypeKind.NULL) { + rightValues = CollectionsPlume.append(rightValues, "null"); + } + } + } - // These lists are used to refine the values in the store based on the results of the - // comparison. - List thenLeftVals = new ArrayList<>(numResultValues); - List elseLeftVals = new ArrayList<>(numResultValues); - List thenRightVals = new ArrayList<>(numResultValues); - List elseRightVals = new ArrayList<>(numResultValues); + List concatValues = new ArrayList<>(leftValues.size() * rightValues.size()); + for (String left : leftValues) { + for (String right : rightValues) { + concatValues.add(left + right); + } + } + return atypeFactory.createStringAnnotation(concatValues); + } - for (Number left : lefts) { - NumberMath nmLeft = NumberMath.getNumberMath(left); - for (Number right : rights) { - Boolean result; - switch (op) { - case EQUAL: - result = nmLeft.equalTo(right); - break; - case GREATER_THAN: - result = nmLeft.greaterThan(right); - break; - case GREATER_THAN_EQ: - result = nmLeft.greaterThanEq(right); - break; - case LESS_THAN: - result = nmLeft.lessThan(right); - break; - case LESS_THAN_EQ: - result = nmLeft.lessThanEq(right); - break; - case NOT_EQUAL: - result = nmLeft.notEqualTo(right); - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + // Try using sets of lengths + List leftLengths = + leftValues != null + ? ValueCheckerUtils.getLengthsForStringValues(leftValues) + : getStringLengths(leftOperand, p); + List rightLengths = + rightValues != null + ? ValueCheckerUtils.getLengthsForStringValues(rightValues) + : getStringLengths(rightOperand, p); + + if (leftLengths != null && rightLengths != null) { + // Both operands have known lengths, compute set of result lengths + if (!nonNullStringsConcatenation) { + if (isNullable(leftOperand)) { + leftLengths = new ArrayList<>(leftLengths); + leftLengths.add(4); // "null" + } + if (isNullable(rightOperand)) { + rightLengths = new ArrayList<>(rightLengths); + rightLengths.add(4); // "null" + } + } + List concatLengths = calculateLengthAddition(leftLengths, rightLengths); + return atypeFactory.createArrayLenAnnotation(concatLengths); + } + + // Try using ranges of lengths + Range leftLengthRange = + leftLengths != null + ? ValueCheckerUtils.getRangeFromValues(leftLengths) + : getStringLengthRange(leftOperand, p); + Range rightLengthRange = + rightLengths != null + ? ValueCheckerUtils.getRangeFromValues(rightLengths) + : getStringLengthRange(rightOperand, p); + + if (leftLengthRange != null && rightLengthRange != null) { + // Both operands have a length from a known range, compute a range of result lengths + if (!nonNullStringsConcatenation) { + if (isNullable(leftOperand)) { + leftLengthRange = leftLengthRange.union(Range.create(4, 4)); // "null" + } + if (isNullable(rightOperand)) { + rightLengthRange = rightLengthRange.union(Range.create(4, 4)); // "null" + } + } + Range concatLengthRange = + calculateLengthRangeAddition(leftLengthRange, rightLengthRange); + return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange); + } + + return atypeFactory.UNKNOWNVAL; + } + + public TransferResult stringConcatenation( + Node leftOperand, + Node rightOperand, + TransferInput p, + TransferResult result) { + + AnnotationMirror resultAnno = + createAnnotationForStringConcatenation(leftOperand, rightOperand, p); + + TypeMirror underlyingType = result.getResultValue().getUnderlyingType(); + CFValue newResultValue = analysis.createSingleAnnotationValue(resultAnno, underlyingType); + return new RegularTransferResult<>(newResultValue, result.getRegularStore()); + } + + /** Binary operations that are analyzed by the value checker. */ + enum NumericalBinaryOps { + ADDITION, + SUBTRACTION, + DIVISION, + REMAINDER, + MULTIPLICATION, + SHIFT_LEFT, + SIGNED_SHIFT_RIGHT, + UNSIGNED_SHIFT_RIGHT, + BITWISE_AND, + BITWISE_OR, + BITWISE_XOR; + } + + /** + * Get the refined annotation after a numerical binary operation. + * + * @param leftNode the node that represents the left operand + * @param rightNode the node that represents the right operand + * @param op the operator type + * @param p the transfer input + * @return the result annotation mirror + */ + private AnnotationMirror calculateNumericalBinaryOp( + Node leftNode, + Node rightNode, + NumericalBinaryOps op, + TransferInput p) { + if (!isIntRangeOrIntegralUnknownVal(leftNode, p) + && !isIntRangeOrIntegralUnknownVal(rightNode, p)) { + List resultValues = calculateValuesBinaryOp(leftNode, rightNode, op, p); + return atypeFactory.createNumberAnnotationMirror(resultValues); + } else { + Range resultRange = calculateRangeBinaryOp(leftNode, rightNode, op, p); + return atypeFactory.createIntRangeAnnotation(resultRange); } - resultValues.add(result); - if (result) { - thenLeftVals.add(left); - thenRightVals.add(right); + } + + /** Calculate the result range after a binary operation between two numerical type nodes. */ + private Range calculateRangeBinaryOp( + Node leftNode, + Node rightNode, + NumericalBinaryOps op, + TransferInput p) { + if (TypesUtils.isIntegralPrimitive(leftNode.getType()) + && TypesUtils.isIntegralPrimitive(rightNode.getType())) { + Range leftRange = getIntRange(leftNode, p); + Range rightRange = getIntRange(rightNode, p); + Range resultRange; + switch (op) { + case ADDITION: + resultRange = leftRange.plus(rightRange); + break; + case SUBTRACTION: + resultRange = leftRange.minus(rightRange); + break; + case MULTIPLICATION: + resultRange = leftRange.times(rightRange); + break; + case DIVISION: + resultRange = leftRange.divide(rightRange); + break; + case REMAINDER: + resultRange = leftRange.remainder(rightRange); + break; + case SHIFT_LEFT: + resultRange = leftRange.shiftLeft(rightRange); + break; + case SIGNED_SHIFT_RIGHT: + resultRange = leftRange.signedShiftRight(rightRange); + break; + case UNSIGNED_SHIFT_RIGHT: + resultRange = leftRange.unsignedShiftRight(rightRange); + break; + case BITWISE_AND: + resultRange = leftRange.bitwiseAnd(rightRange); + break; + case BITWISE_OR: + resultRange = leftRange.bitwiseOr(rightRange); + break; + case BITWISE_XOR: + resultRange = leftRange.bitwiseXor(rightRange); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + // Any integral type with less than 32 bits would be promoted to 32-bit int type during + // operations. + return leftNode.getType().getKind() == TypeKind.LONG + || rightNode.getType().getKind() == TypeKind.LONG + ? resultRange + : resultRange.intRange(); } else { - elseLeftVals.add(left); - elseRightVals.add(right); + return Range.EVERYTHING; } - } - } - - createAnnotationFromResultsAndAddToStore(thenStore, thenLeftVals, leftNode); - createAnnotationFromResultsAndAddToStore(elseStore, elseLeftVals, leftNode); - createAnnotationFromResultsAndAddToStore(thenStore, thenRightVals, rightNode); - createAnnotationFromResultsAndAddToStore(elseStore, elseRightVals, rightNode); - - return resultValues; - } - - /** - * Calculates the result of a binary comparison on a pair of intRange annotations, and refines - * annotations appropriately. - */ - private @Nullable List refineIntRanges( - Node leftNode, - AnnotationMirror leftAnno, - Node rightNode, - AnnotationMirror rightAnno, - ComparisonOperators op, - CFStore thenStore, - CFStore elseStore) { - - Range leftRange = getIntRangeFromAnnotation(leftNode, leftAnno); - Range rightRange = getIntRangeFromAnnotation(rightNode, rightAnno); - - final Range thenRightRange; - final Range thenLeftRange; - final Range elseRightRange; - final Range elseLeftRange; - - switch (op) { - case EQUAL: - thenRightRange = rightRange.refineEqualTo(leftRange); - thenLeftRange = thenRightRange; // Only needs to be computed once. - elseRightRange = rightRange.refineNotEqualTo(leftRange); - elseLeftRange = leftRange.refineNotEqualTo(rightRange); - break; - case GREATER_THAN: - thenLeftRange = leftRange.refineGreaterThan(rightRange); - thenRightRange = rightRange.refineLessThan(leftRange); - elseRightRange = rightRange.refineGreaterThanEq(leftRange); - elseLeftRange = leftRange.refineLessThanEq(rightRange); - break; - case GREATER_THAN_EQ: - thenRightRange = rightRange.refineLessThanEq(leftRange); - thenLeftRange = leftRange.refineGreaterThanEq(rightRange); - elseLeftRange = leftRange.refineLessThan(rightRange); - elseRightRange = rightRange.refineGreaterThan(leftRange); - break; - case LESS_THAN: - thenLeftRange = leftRange.refineLessThan(rightRange); - thenRightRange = rightRange.refineGreaterThan(leftRange); - elseRightRange = rightRange.refineLessThanEq(leftRange); - elseLeftRange = leftRange.refineGreaterThanEq(rightRange); - break; - case LESS_THAN_EQ: - thenRightRange = rightRange.refineGreaterThanEq(leftRange); - thenLeftRange = leftRange.refineLessThanEq(rightRange); - elseLeftRange = leftRange.refineGreaterThan(rightRange); - elseRightRange = rightRange.refineLessThan(leftRange); - break; - case NOT_EQUAL: - thenRightRange = rightRange.refineNotEqualTo(leftRange); - thenLeftRange = leftRange.refineNotEqualTo(rightRange); - elseRightRange = rightRange.refineEqualTo(leftRange); - elseLeftRange = elseRightRange; // Equality only needs to be computed once. - break; - default: - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); } - createAnnotationFromRangeAndAddToStore(thenStore, thenRightRange, rightNode); - createAnnotationFromRangeAndAddToStore(thenStore, thenLeftRange, leftNode); - createAnnotationFromRangeAndAddToStore(elseStore, elseRightRange, rightNode); - createAnnotationFromRangeAndAddToStore(elseStore, elseLeftRange, leftNode); - - // TODO: Refine the type of the comparison. - return null; - } - - /** - * Takes a list of result values (i.e. the values possible after the comparison) and creates the - * appropriate annotation from them, then combines that annotation with the existing annotation on - * the node. The resulting annotation is inserted into the store. - * - * @param store the store - * @param results the result values - * @param node the node whose existing annotation to refine - */ - private void createAnnotationFromResultsAndAddToStore(CFStore store, List results, Node node) { - AnnotationMirror anno = atypeFactory.createResultingAnnotation(node.getType(), results); - addAnnotationToStore(store, anno, node); - } - - /** - * Takes a range and creates the appropriate annotation from it, then combines that annotation - * with the existing annotation on the node. The resulting annotation is inserted into the store. - * - * @param store the store - * @param range the range to create an annotation for - * @param node the node whose existing annotation to refine - */ - private void createAnnotationFromRangeAndAddToStore(CFStore store, Range range, Node node) { - AnnotationMirror anno = atypeFactory.createIntRangeAnnotation(range); - addAnnotationToStore(store, anno, node); - } - - private void addAnnotationToStore(CFStore store, AnnotationMirror anno, Node node) { - // If node is assignment, iterate over lhs and rhs; otherwise, iterator contains just node. - for (Node internal : splitAssignments(node)) { - JavaExpression je = JavaExpression.fromNode(internal); - CFValue currentValueFromStore; - if (CFAbstractStore.canInsertJavaExpression(je)) { - currentValueFromStore = store.getValue(je); - } else { - // Don't just `continue;` which would skip the calls to refine{Array,String}... - currentValueFromStore = null; - } - AnnotationMirror currentAnno = - (currentValueFromStore == null - ? atypeFactory.UNKNOWNVAL - : getValueAnnotation(currentValueFromStore)); - // Combine the new annotations based on the results of the comparison with the existing - // type. - AnnotationMirror newAnno = - qualHierarchy.greatestLowerBoundShallow(anno, je.getType(), currentAnno, je.getType()); - store.insertValue(je, newAnno); - - if (node instanceof FieldAccessNode) { - refineArrayAtLengthAccess((FieldAccessNode) internal, store); - } else if (node instanceof MethodInvocationNode) { - MethodInvocationNode miNode = (MethodInvocationNode) node; - refineAtLengthInvocation(miNode, store); - } - } - } - - @Override - public TransferResult visitLessThan( - LessThanNode n, TransferInput p) { - TransferResult transferResult = super.visitLessThan(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.LESS_THAN, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - public TransferResult visitLessThanOrEqual( - LessThanOrEqualNode n, TransferInput p) { - TransferResult transferResult = super.visitLessThanOrEqual(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.LESS_THAN_EQ, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - public TransferResult visitGreaterThan( - GreaterThanNode n, TransferInput p) { - TransferResult transferResult = super.visitGreaterThan(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.GREATER_THAN, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - public TransferResult visitGreaterThanOrEqual( - GreaterThanOrEqualNode n, TransferInput p) { - TransferResult transferResult = super.visitGreaterThanOrEqual(n, p); - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - List resultValues = - calculateBinaryComparison( - n.getLeftOperand(), - p.getValueOfSubNode(n.getLeftOperand()), - n.getRightOperand(), - p.getValueOfSubNode(n.getRightOperand()), - ComparisonOperators.GREATER_THAN_EQ, - thenStore, - elseStore); - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - - @Override - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult transferResult, - Node firstNode, - Node secondNode, - CFValue firstValue, - CFValue secondValue, - boolean notEqualTo) { - if (firstValue == null) { - return transferResult; - } - if (TypesUtils.isNumeric(firstNode.getType()) || TypesUtils.isNumeric(secondNode.getType())) { - CFStore thenStore = transferResult.getThenStore(); - CFStore elseStore = transferResult.getElseStore(); - // At least one must be a primitive otherwise reference equality is used. - List resultValues = - calculateBinaryComparison( - firstNode, - firstValue, - secondNode, - secondValue, - notEqualTo ? ComparisonOperators.NOT_EQUAL : ComparisonOperators.EQUAL, - thenStore, - elseStore); - if (transferResult.getResultValue() == null) { - // Happens for case labels - return transferResult; - } - TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); - return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); - } - return super.strengthenAnnotationOfEqualTo( - transferResult, firstNode, secondNode, firstValue, secondValue, notEqualTo); - } - - @Override - protected void processConditionalPostconditions( - MethodInvocationNode n, - ExecutableElement methodElement, - ExpressionTree tree, - CFStore thenStore, - CFStore elseStore) { - // For String.startsWith(String) and String.endsWith(String), refine the minimum length - // of the receiver to the minimum length of the argument. - ValueMethodIdentifier methodIdentifier = atypeFactory.getMethodIdentifier(); - if (methodIdentifier.isStartsWithMethod(methodElement) - || methodIdentifier.isEndsWithMethod(methodElement)) { - - Node argumentNode = n.getArgument(0); - AnnotationMirror argumentAnno = getArrayOrStringAnnotation(argumentNode); - int minLength = atypeFactory.getMinLenValue(argumentAnno); - // Update the annotation of the receiver - if (minLength != 0) { - JavaExpression receiver = JavaExpression.fromNode(n.getTarget().getReceiver()); - - AnnotationMirror minLenAnno = - atypeFactory.createArrayLenRangeAnnotation(minLength, Integer.MAX_VALUE); - thenStore.insertValuePermitNondeterministic(receiver, minLenAnno); - } - } - - super.processConditionalPostconditions(n, methodElement, tree, thenStore, elseStore); - } - - enum ConditionalOperators { - NOT, - OR, - AND; - } - - private static final List ALL_BOOLEANS = - Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE}); - - private List calculateConditionalOperator( - Node leftNode, Node rightNode, ConditionalOperators op, TransferInput p) { - List lefts = getBooleanValues(leftNode, p); - if (lefts == null) { - lefts = ALL_BOOLEANS; - } - List rights = null; - if (rightNode != null) { - rights = getBooleanValues(rightNode, p); - if (rights == null) { - rights = ALL_BOOLEANS; - } - } - // This list can contain duplicates. It is deduplicated later by createBooleanAnnotation. - List resultValues = new ArrayList<>(2); - switch (op) { - case NOT: - return CollectionsPlume.mapList((Boolean left) -> !left, lefts); - case OR: - for (Boolean left : lefts) { - for (Boolean right : rights) { - resultValues.add(left || right); - } + /** Calculate the possible values after a binary operation between two numerical type nodes. */ + private @Nullable List calculateValuesBinaryOp( + Node leftNode, + Node rightNode, + NumericalBinaryOps op, + TransferInput p) { + List lefts = getNumericalValues(leftNode, p); + List rights = getNumericalValues(rightNode, p); + if (lefts == null || rights == null) { + return null; + } + List resultValues = new ArrayList<>(lefts.size() * rights.size()); + for (Number left : lefts) { + NumberMath nmLeft = NumberMath.getNumberMath(left); + for (Number right : rights) { + switch (op) { + case ADDITION: + resultValues.add(nmLeft.plus(right)); + break; + case DIVISION: + Number result = nmLeft.divide(right); + if (result != null) { + resultValues.add(result); + } + break; + case MULTIPLICATION: + resultValues.add(nmLeft.times(right)); + break; + case REMAINDER: + Number resultR = nmLeft.remainder(right); + if (resultR != null) { + resultValues.add(resultR); + } + break; + case SUBTRACTION: + resultValues.add(nmLeft.minus(right)); + break; + case SHIFT_LEFT: + resultValues.add(nmLeft.shiftLeft(right)); + break; + case SIGNED_SHIFT_RIGHT: + resultValues.add(nmLeft.signedShiftRight(right)); + break; + case UNSIGNED_SHIFT_RIGHT: + resultValues.add(nmLeft.unsignedShiftRight(right)); + break; + case BITWISE_AND: + resultValues.add(nmLeft.bitwiseAnd(right)); + break; + case BITWISE_OR: + resultValues.add(nmLeft.bitwiseOr(right)); + break; + case BITWISE_XOR: + resultValues.add(nmLeft.bitwiseXor(right)); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + } } return resultValues; - case AND: - for (Boolean left : lefts) { - for (Boolean right : rights) { - resultValues.add(left && right); - } + } + + @Override + public TransferResult visitNumericalAddition( + NumericalAdditionNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalAddition(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitNumericalSubtraction( + NumericalSubtractionNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalSubtraction(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitNumericalMultiplication( + NumericalMultiplicationNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalMultiplication(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), + n.getRightOperand(), + NumericalBinaryOps.MULTIPLICATION, + p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitIntegerDivision( + IntegerDivisionNode n, TransferInput p) { + TransferResult transferResult = super.visitIntegerDivision(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitFloatingDivision( + FloatingDivisionNode n, TransferInput p) { + TransferResult transferResult = super.visitFloatingDivision(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitIntegerRemainder( + IntegerRemainderNode n, TransferInput p) { + TransferResult transferResult = super.visitIntegerRemainder(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitFloatingRemainder( + FloatingRemainderNode n, TransferInput p) { + TransferResult transferResult = super.visitFloatingRemainder(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitLeftShift( + LeftShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitLeftShift(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitSignedRightShift( + SignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitSignedRightShift(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), + n.getRightOperand(), + NumericalBinaryOps.SIGNED_SHIFT_RIGHT, + p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitUnsignedRightShift( + UnsignedRightShiftNode n, TransferInput p) { + TransferResult transferResult = super.visitUnsignedRightShift(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), + n.getRightOperand(), + NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, + p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseAnd( + BitwiseAndNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseAnd(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseOr( + BitwiseOrNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseOr(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseXor( + BitwiseXorNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseXor(n, p); + AnnotationMirror resultAnno = + calculateNumericalBinaryOp( + n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_XOR, p); + return createNewResult(transferResult, resultAnno); + } + + /** Unary operations that are analyzed by the value checker. */ + enum NumericalUnaryOps { + PLUS, + MINUS, + BITWISE_COMPLEMENT; + } + + /** + * Get the refined annotation after a numerical unary operation. + * + * @param operand the node that represents the operand + * @param op the operator type + * @param p the transfer input + * @return the result annotation mirror + */ + private AnnotationMirror calculateNumericalUnaryOp( + Node operand, NumericalUnaryOps op, TransferInput p) { + if (!isIntRange(operand, p)) { + List resultValues = calculateValuesUnaryOp(operand, op, p); + return atypeFactory.createNumberAnnotationMirror(resultValues); + } else { + Range resultRange = calculateRangeUnaryOp(operand, op, p); + return atypeFactory.createIntRangeAnnotation(resultRange); + } + } + + /** + * Calculate the result range after a unary operation of a numerical type node. + * + * @param operand the node that represents the operand + * @param op the operator type + * @param p the transfer input + * @return the result annotation mirror + */ + private Range calculateRangeUnaryOp( + Node operand, NumericalUnaryOps op, TransferInput p) { + if (TypesUtils.isIntegralPrimitive(operand.getType())) { + Range range = getIntRange(operand, p); + Range resultRange; + switch (op) { + case PLUS: + resultRange = range.unaryPlus(); + break; + case MINUS: + resultRange = range.unaryMinus(); + break; + case BITWISE_COMPLEMENT: + resultRange = range.bitwiseComplement(); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + // Any integral type with less than 32 bits would be promoted to 32-bit int type during + // operations. + return operand.getType().getKind() == TypeKind.LONG + ? resultRange + : resultRange.intRange(); + } else { + return Range.EVERYTHING; + } + } + + /** Calculate the possible values after a unary operation of a numerical type node. */ + private @Nullable List calculateValuesUnaryOp( + Node operand, NumericalUnaryOps op, TransferInput p) { + List lefts = getNumericalValues(operand, p); + if (lefts == null) { + return null; + } + List resultValues = new ArrayList<>(lefts.size()); + for (Number left : lefts) { + NumberMath nmLeft = NumberMath.getNumberMath(left); + switch (op) { + case PLUS: + resultValues.add(nmLeft.unaryPlus()); + break; + case MINUS: + resultValues.add(nmLeft.unaryMinus()); + break; + case BITWISE_COMPLEMENT: + resultValues.add(nmLeft.bitwiseComplement()); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } } return resultValues; } - throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); - } - - @Override - public TransferResult visitEqualTo( - EqualToNode n, TransferInput p) { - TransferResult res = super.visitEqualTo(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - CFValue leftV = p.getValueOfSubNode(leftN); - CFValue rightV = p.getValueOfSubNode(rightN); - - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); - - Boolean leftBoolean = getBooleanValue(leftV); - if (leftBoolean != null) { - CFValue notLeftV = createBooleanCFValue(!leftBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, true); - } - Boolean rightBoolean = getBooleanValue(rightV); - if (rightBoolean != null) { - CFValue notRightV = createBooleanCFValue(!rightBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, true); - } - - return res; - } - - @Override - public TransferResult visitNotEqual( - NotEqualNode n, TransferInput p) { - TransferResult res = super.visitNotEqual(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - CFValue leftV = p.getValueOfSubNode(leftN); - CFValue rightV = p.getValueOfSubNode(rightN); - - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); - - Boolean leftBoolean = getBooleanValue(leftV); - if (leftBoolean != null) { - CFValue notLeftV = createBooleanCFValue(!leftBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, false); - } - Boolean rightBoolean = getBooleanValue(rightV); - if (rightBoolean != null) { - CFValue notRightV = createBooleanCFValue(!rightBoolean); - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, false); - } - - return res; - } - - @Override - public TransferResult visitConditionalNot( - ConditionalNotNode n, TransferInput p) { - TransferResult transferResult = super.visitConditionalNot(n, p); - List resultValues = - calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p); - return createNewResultBoolean( - transferResult.getThenStore(), - transferResult.getElseStore(), - resultValues, - transferResult.getResultValue().getUnderlyingType()); - } - - @Override - public TransferResult visitConditionalAnd( - ConditionalAndNode n, TransferInput p) { - TransferResult transferResult = super.visitConditionalAnd(n, p); - List resultValues = - calculateConditionalOperator( - n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p); - return createNewResultBoolean( - transferResult.getThenStore(), - transferResult.getElseStore(), - resultValues, - transferResult.getResultValue().getUnderlyingType()); - } - - @Override - public TransferResult visitConditionalOr( - ConditionalOrNode n, TransferInput p) { - TransferResult transferResult = super.visitConditionalOr(n, p); - List resultValues = - calculateConditionalOperator( - n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p); - return createNewResultBoolean( - transferResult.getThenStore(), - transferResult.getElseStore(), - resultValues, - transferResult.getResultValue().getUnderlyingType()); - } + + @Override + public TransferResult visitNumericalMinus( + NumericalMinusNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalMinus(n, p); + AnnotationMirror resultAnno = + calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitNumericalPlus( + NumericalPlusNode n, TransferInput p) { + TransferResult transferResult = super.visitNumericalPlus(n, p); + AnnotationMirror resultAnno = + calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p); + return createNewResult(transferResult, resultAnno); + } + + @Override + public TransferResult visitBitwiseComplement( + BitwiseComplementNode n, TransferInput p) { + TransferResult transferResult = super.visitBitwiseComplement(n, p); + AnnotationMirror resultAnno = + calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.BITWISE_COMPLEMENT, p); + return createNewResult(transferResult, resultAnno); + } + + enum ComparisonOperators { + EQUAL, + NOT_EQUAL, + GREATER_THAN, + GREATER_THAN_EQ, + LESS_THAN, + LESS_THAN_EQ; + } + + private @Nullable List calculateBinaryComparison( + Node leftNode, + CFValue leftValue, + Node rightNode, + CFValue rightValue, + ComparisonOperators op, + CFStore thenStore, + CFStore elseStore) { + AnnotationMirror leftAnno = getValueAnnotation(leftValue); + AnnotationMirror rightAnno = getValueAnnotation(rightValue); + + if (atypeFactory.isIntRange(leftAnno) + || atypeFactory.isIntRange(rightAnno) + || isIntegralUnknownVal(rightNode, rightAnno) + || isIntegralUnknownVal(leftNode, leftAnno)) { + // If either is @UnknownVal, then refineIntRanges will treat it as the max range and + // thus refine it if possible. Also, if either is an @IntVal, then it will be converted + // to a range. This is less precise in some cases, but avoids the complexity of + // comparing a list of values to a range. (This could be implemented in the future.) + return refineIntRanges( + leftNode, leftAnno, rightNode, rightAnno, op, thenStore, elseStore); + } + + List lefts = getNumericalValues(leftNode, leftAnno); + List rights = getNumericalValues(rightNode, rightAnno); + + if (lefts == null || rights == null) { + // Appropriately handle bottom when something is compared to bottom. + if (AnnotationUtils.areSame(leftAnno, atypeFactory.BOTTOMVAL) + || AnnotationUtils.areSame(rightAnno, atypeFactory.BOTTOMVAL)) { + return Collections.emptyList(); + } + return null; + } + + // This is a list of all the values that the expression can evaluate to. + int numResultValues = lefts.size() * rights.size(); + List resultValues = new ArrayList<>(numResultValues); + + // These lists are used to refine the values in the store based on the results of the + // comparison. + List thenLeftVals = new ArrayList<>(numResultValues); + List elseLeftVals = new ArrayList<>(numResultValues); + List thenRightVals = new ArrayList<>(numResultValues); + List elseRightVals = new ArrayList<>(numResultValues); + + for (Number left : lefts) { + NumberMath nmLeft = NumberMath.getNumberMath(left); + for (Number right : rights) { + Boolean result; + switch (op) { + case EQUAL: + result = nmLeft.equalTo(right); + break; + case GREATER_THAN: + result = nmLeft.greaterThan(right); + break; + case GREATER_THAN_EQ: + result = nmLeft.greaterThanEq(right); + break; + case LESS_THAN: + result = nmLeft.lessThan(right); + break; + case LESS_THAN_EQ: + result = nmLeft.lessThanEq(right); + break; + case NOT_EQUAL: + result = nmLeft.notEqualTo(right); + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + resultValues.add(result); + if (result) { + thenLeftVals.add(left); + thenRightVals.add(right); + } else { + elseLeftVals.add(left); + elseRightVals.add(right); + } + } + } + + createAnnotationFromResultsAndAddToStore(thenStore, thenLeftVals, leftNode); + createAnnotationFromResultsAndAddToStore(elseStore, elseLeftVals, leftNode); + createAnnotationFromResultsAndAddToStore(thenStore, thenRightVals, rightNode); + createAnnotationFromResultsAndAddToStore(elseStore, elseRightVals, rightNode); + + return resultValues; + } + + /** + * Calculates the result of a binary comparison on a pair of intRange annotations, and refines + * annotations appropriately. + */ + private @Nullable List refineIntRanges( + Node leftNode, + AnnotationMirror leftAnno, + Node rightNode, + AnnotationMirror rightAnno, + ComparisonOperators op, + CFStore thenStore, + CFStore elseStore) { + + Range leftRange = getIntRangeFromAnnotation(leftNode, leftAnno); + Range rightRange = getIntRangeFromAnnotation(rightNode, rightAnno); + + final Range thenRightRange; + final Range thenLeftRange; + final Range elseRightRange; + final Range elseLeftRange; + + switch (op) { + case EQUAL: + thenRightRange = rightRange.refineEqualTo(leftRange); + thenLeftRange = thenRightRange; // Only needs to be computed once. + elseRightRange = rightRange.refineNotEqualTo(leftRange); + elseLeftRange = leftRange.refineNotEqualTo(rightRange); + break; + case GREATER_THAN: + thenLeftRange = leftRange.refineGreaterThan(rightRange); + thenRightRange = rightRange.refineLessThan(leftRange); + elseRightRange = rightRange.refineGreaterThanEq(leftRange); + elseLeftRange = leftRange.refineLessThanEq(rightRange); + break; + case GREATER_THAN_EQ: + thenRightRange = rightRange.refineLessThanEq(leftRange); + thenLeftRange = leftRange.refineGreaterThanEq(rightRange); + elseLeftRange = leftRange.refineLessThan(rightRange); + elseRightRange = rightRange.refineGreaterThan(leftRange); + break; + case LESS_THAN: + thenLeftRange = leftRange.refineLessThan(rightRange); + thenRightRange = rightRange.refineGreaterThan(leftRange); + elseRightRange = rightRange.refineLessThanEq(leftRange); + elseLeftRange = leftRange.refineGreaterThanEq(rightRange); + break; + case LESS_THAN_EQ: + thenRightRange = rightRange.refineGreaterThanEq(leftRange); + thenLeftRange = leftRange.refineLessThanEq(rightRange); + elseLeftRange = leftRange.refineGreaterThan(rightRange); + elseRightRange = rightRange.refineLessThan(leftRange); + break; + case NOT_EQUAL: + thenRightRange = rightRange.refineNotEqualTo(leftRange); + thenLeftRange = leftRange.refineNotEqualTo(rightRange); + elseRightRange = rightRange.refineEqualTo(leftRange); + elseLeftRange = elseRightRange; // Equality only needs to be computed once. + break; + default: + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + + createAnnotationFromRangeAndAddToStore(thenStore, thenRightRange, rightNode); + createAnnotationFromRangeAndAddToStore(thenStore, thenLeftRange, leftNode); + createAnnotationFromRangeAndAddToStore(elseStore, elseRightRange, rightNode); + createAnnotationFromRangeAndAddToStore(elseStore, elseLeftRange, leftNode); + + // TODO: Refine the type of the comparison. + return null; + } + + /** + * Takes a list of result values (i.e. the values possible after the comparison) and creates the + * appropriate annotation from them, then combines that annotation with the existing annotation + * on the node. The resulting annotation is inserted into the store. + * + * @param store the store + * @param results the result values + * @param node the node whose existing annotation to refine + */ + private void createAnnotationFromResultsAndAddToStore( + CFStore store, List results, Node node) { + AnnotationMirror anno = atypeFactory.createResultingAnnotation(node.getType(), results); + addAnnotationToStore(store, anno, node); + } + + /** + * Takes a range and creates the appropriate annotation from it, then combines that annotation + * with the existing annotation on the node. The resulting annotation is inserted into the + * store. + * + * @param store the store + * @param range the range to create an annotation for + * @param node the node whose existing annotation to refine + */ + private void createAnnotationFromRangeAndAddToStore(CFStore store, Range range, Node node) { + AnnotationMirror anno = atypeFactory.createIntRangeAnnotation(range); + addAnnotationToStore(store, anno, node); + } + + private void addAnnotationToStore(CFStore store, AnnotationMirror anno, Node node) { + // If node is assignment, iterate over lhs and rhs; otherwise, iterator contains just node. + for (Node internal : splitAssignments(node)) { + JavaExpression je = JavaExpression.fromNode(internal); + CFValue currentValueFromStore; + if (CFAbstractStore.canInsertJavaExpression(je)) { + currentValueFromStore = store.getValue(je); + } else { + // Don't just `continue;` which would skip the calls to refine{Array,String}... + currentValueFromStore = null; + } + AnnotationMirror currentAnno = + (currentValueFromStore == null + ? atypeFactory.UNKNOWNVAL + : getValueAnnotation(currentValueFromStore)); + // Combine the new annotations based on the results of the comparison with the existing + // type. + AnnotationMirror newAnno = + qualHierarchy.greatestLowerBoundShallow( + anno, je.getType(), currentAnno, je.getType()); + store.insertValue(je, newAnno); + + if (node instanceof FieldAccessNode) { + refineArrayAtLengthAccess((FieldAccessNode) internal, store); + } else if (node instanceof MethodInvocationNode) { + MethodInvocationNode miNode = (MethodInvocationNode) node; + refineAtLengthInvocation(miNode, store); + } + } + } + + @Override + public TransferResult visitLessThan( + LessThanNode n, TransferInput p) { + TransferResult transferResult = super.visitLessThan(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.LESS_THAN, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + public TransferResult visitLessThanOrEqual( + LessThanOrEqualNode n, TransferInput p) { + TransferResult transferResult = super.visitLessThanOrEqual(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.LESS_THAN_EQ, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + public TransferResult visitGreaterThan( + GreaterThanNode n, TransferInput p) { + TransferResult transferResult = super.visitGreaterThan(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.GREATER_THAN, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + public TransferResult visitGreaterThanOrEqual( + GreaterThanOrEqualNode n, TransferInput p) { + TransferResult transferResult = super.visitGreaterThanOrEqual(n, p); + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + List resultValues = + calculateBinaryComparison( + n.getLeftOperand(), + p.getValueOfSubNode(n.getLeftOperand()), + n.getRightOperand(), + p.getValueOfSubNode(n.getRightOperand()), + ComparisonOperators.GREATER_THAN_EQ, + thenStore, + elseStore); + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + + @Override + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult transferResult, + Node firstNode, + Node secondNode, + CFValue firstValue, + CFValue secondValue, + boolean notEqualTo) { + if (firstValue == null) { + return transferResult; + } + if (TypesUtils.isNumeric(firstNode.getType()) + || TypesUtils.isNumeric(secondNode.getType())) { + CFStore thenStore = transferResult.getThenStore(); + CFStore elseStore = transferResult.getElseStore(); + // At least one must be a primitive otherwise reference equality is used. + List resultValues = + calculateBinaryComparison( + firstNode, + firstValue, + secondNode, + secondValue, + notEqualTo ? ComparisonOperators.NOT_EQUAL : ComparisonOperators.EQUAL, + thenStore, + elseStore); + if (transferResult.getResultValue() == null) { + // Happens for case labels + return transferResult; + } + TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); + return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); + } + return super.strengthenAnnotationOfEqualTo( + transferResult, firstNode, secondNode, firstValue, secondValue, notEqualTo); + } + + @Override + protected void processConditionalPostconditions( + MethodInvocationNode n, + ExecutableElement methodElement, + ExpressionTree tree, + CFStore thenStore, + CFStore elseStore) { + // For String.startsWith(String) and String.endsWith(String), refine the minimum length + // of the receiver to the minimum length of the argument. + ValueMethodIdentifier methodIdentifier = atypeFactory.getMethodIdentifier(); + if (methodIdentifier.isStartsWithMethod(methodElement) + || methodIdentifier.isEndsWithMethod(methodElement)) { + + Node argumentNode = n.getArgument(0); + AnnotationMirror argumentAnno = getArrayOrStringAnnotation(argumentNode); + int minLength = atypeFactory.getMinLenValue(argumentAnno); + // Update the annotation of the receiver + if (minLength != 0) { + JavaExpression receiver = JavaExpression.fromNode(n.getTarget().getReceiver()); + + AnnotationMirror minLenAnno = + atypeFactory.createArrayLenRangeAnnotation(minLength, Integer.MAX_VALUE); + thenStore.insertValuePermitNondeterministic(receiver, minLenAnno); + } + } + + super.processConditionalPostconditions(n, methodElement, tree, thenStore, elseStore); + } + + enum ConditionalOperators { + NOT, + OR, + AND; + } + + private static final List ALL_BOOLEANS = + Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE}); + + private List calculateConditionalOperator( + Node leftNode, + Node rightNode, + ConditionalOperators op, + TransferInput p) { + List lefts = getBooleanValues(leftNode, p); + if (lefts == null) { + lefts = ALL_BOOLEANS; + } + List rights = null; + if (rightNode != null) { + rights = getBooleanValues(rightNode, p); + if (rights == null) { + rights = ALL_BOOLEANS; + } + } + // This list can contain duplicates. It is deduplicated later by createBooleanAnnotation. + List resultValues = new ArrayList<>(2); + switch (op) { + case NOT: + return CollectionsPlume.mapList((Boolean left) -> !left, lefts); + case OR: + for (Boolean left : lefts) { + for (Boolean right : rights) { + resultValues.add(left || right); + } + } + return resultValues; + case AND: + for (Boolean left : lefts) { + for (Boolean right : rights) { + resultValues.add(left && right); + } + } + return resultValues; + } + throw new TypeSystemError("ValueTransfer: unsupported operation: " + op); + } + + @Override + public TransferResult visitEqualTo( + EqualToNode n, TransferInput p) { + TransferResult res = super.visitEqualTo(n, p); + + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + CFValue leftV = p.getValueOfSubNode(leftN); + CFValue rightV = p.getValueOfSubNode(rightN); + + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); + + Boolean leftBoolean = getBooleanValue(leftV); + if (leftBoolean != null) { + CFValue notLeftV = createBooleanCFValue(!leftBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, true); + } + Boolean rightBoolean = getBooleanValue(rightV); + if (rightBoolean != null) { + CFValue notRightV = createBooleanCFValue(!rightBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, true); + } + + return res; + } + + @Override + public TransferResult visitNotEqual( + NotEqualNode n, TransferInput p) { + TransferResult res = super.visitNotEqual(n, p); + + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + CFValue leftV = p.getValueOfSubNode(leftN); + CFValue rightV = p.getValueOfSubNode(rightN); + + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); + + Boolean leftBoolean = getBooleanValue(leftV); + if (leftBoolean != null) { + CFValue notLeftV = createBooleanCFValue(!leftBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, false); + } + Boolean rightBoolean = getBooleanValue(rightV); + if (rightBoolean != null) { + CFValue notRightV = createBooleanCFValue(!rightBoolean); + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, false); + } + + return res; + } + + @Override + public TransferResult visitConditionalNot( + ConditionalNotNode n, TransferInput p) { + TransferResult transferResult = super.visitConditionalNot(n, p); + List resultValues = + calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p); + return createNewResultBoolean( + transferResult.getThenStore(), + transferResult.getElseStore(), + resultValues, + transferResult.getResultValue().getUnderlyingType()); + } + + @Override + public TransferResult visitConditionalAnd( + ConditionalAndNode n, TransferInput p) { + TransferResult transferResult = super.visitConditionalAnd(n, p); + List resultValues = + calculateConditionalOperator( + n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p); + return createNewResultBoolean( + transferResult.getThenStore(), + transferResult.getElseStore(), + resultValues, + transferResult.getResultValue().getUnderlyingType()); + } + + @Override + public TransferResult visitConditionalOr( + ConditionalOrNode n, TransferInput p) { + TransferResult transferResult = super.visitConditionalOr(n, p); + List resultValues = + calculateConditionalOperator( + n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p); + return createNewResultBoolean( + transferResult.getThenStore(), + transferResult.getElseStore(), + resultValues, + transferResult.getResultValue().getUnderlyingType()); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java index cf692774162..0a335f24fe1 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java @@ -11,19 +11,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.Name; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.BinaryName; import org.checkerframework.checker.signature.qual.Identifier; @@ -40,632 +28,674 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Name; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** The TreeAnnotator for this AnnotatedTypeFactory. It adds/replaces annotations. */ class ValueTreeAnnotator extends TreeAnnotator { - /** The type factory to use. Shadows the field from the superclass with a more specific type. */ - @SuppressWarnings("HidingField") - protected final ValueAnnotatedTypeFactory atypeFactory; - - /** The domain of the Constant Value Checker: the types for which it estimates possible values. */ - protected static final Set COVERED_CLASS_STRINGS = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - "int", - "java.lang.Integer", - "double", - "java.lang.Double", - "byte", - "java.lang.Byte", - "java.lang.String", - "char", - "java.lang.Character", - "float", - "java.lang.Float", - "boolean", - "java.lang.Boolean", - "long", - "java.lang.Long", - "short", - "java.lang.Short", - "char[]"))); - - /** - * Create a ValueTreeAnnotator. - * - * @param atypeFactory the ValueAnnotatedTypeFactory to use - */ - public ValueTreeAnnotator(ValueAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - this.atypeFactory = atypeFactory; - } - - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - - List dimensions = tree.getDimensions(); - List initializers = tree.getInitializers(); - - // Array construction can provide dimensions or use an initializer. - - // Dimensions provided - if (!dimensions.isEmpty()) { - handleDimensions(dimensions, (AnnotatedTypeMirror.AnnotatedArrayType) type); - } else { - // Initializer used - handleInitializers(initializers, (AnnotatedTypeMirror.AnnotatedArrayType) type); - - AnnotationMirror newQual; - Class clazz = TypesUtils.getClassFromType(type.getUnderlyingType()); - String stringVal = null; - if (clazz == char[].class) { - stringVal = getCharArrayStringVal(initializers); - } - - if (stringVal != null) { - newQual = atypeFactory.createStringAnnotation(Collections.singletonList(stringVal)); - type.replaceAnnotation(newQual); - } + /** The type factory to use. Shadows the field from the superclass with a more specific type. */ + @SuppressWarnings("HidingField") + protected final ValueAnnotatedTypeFactory atypeFactory; + + /** + * The domain of the Constant Value Checker: the types for which it estimates possible values. + */ + protected static final Set COVERED_CLASS_STRINGS = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + "int", + "java.lang.Integer", + "double", + "java.lang.Double", + "byte", + "java.lang.Byte", + "java.lang.String", + "char", + "java.lang.Character", + "float", + "java.lang.Float", + "boolean", + "java.lang.Boolean", + "long", + "java.lang.Long", + "short", + "java.lang.Short", + "char[]"))); + + /** + * Create a ValueTreeAnnotator. + * + * @param atypeFactory the ValueAnnotatedTypeFactory to use + */ + public ValueTreeAnnotator(ValueAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + this.atypeFactory = atypeFactory; } - return null; - } - - /** - * Recursive method to handle array initializations. Recursively descends the initializer to find - * each dimension's size and create the appropriate annotation for it. - * - *

          If the annotation of the dimension is {@code @IntVal}, create an {@code @ArrayLen} with the - * same set of possible values. If the annotation is {@code @IntRange}, create an - * {@code @ArrayLenRange}. If the annotation is {@code @BottomVal}, create an {@code @BottomVal} - * instead. In other cases, no annotations are created. - * - * @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of the - * size of that dimension - * @param type the AnnotatedTypeMirror of the array, which is side-effected by this method - */ - private void handleDimensions( - List dimensions, AnnotatedTypeMirror.AnnotatedArrayType type) { - if (dimensions.size() > 1) { - handleDimensions( - dimensions.subList(1, dimensions.size()), - (AnnotatedTypeMirror.AnnotatedArrayType) type.getComponentType()); - } - AnnotationMirror dimType = - atypeFactory - .getAnnotatedType(dimensions.get(0)) - .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - - if (AnnotationUtils.areSameByName(dimType, atypeFactory.BOTTOMVAL)) { - type.replaceAnnotation(atypeFactory.BOTTOMVAL); - } else { - RangeOrListOfValues rolv = null; - if (atypeFactory.isIntRange(dimType)) { - rolv = new RangeOrListOfValues(atypeFactory.getRange(dimType)); - } else if (AnnotationUtils.areSameByName(dimType, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - rolv = - new RangeOrListOfValues( - RangeOrListOfValues.convertLongsToInts(atypeFactory.getIntValues(dimType))); - } - if (rolv != null) { - AnnotationMirror newQual = rolv.createAnnotation(atypeFactory); - type.replaceAnnotation(newQual); - } - } - } - - /** - * Adds the ArrayLen/ArrayLenRange annotation from the array initializers to {@code type}. - * - *

          If type is a multi-dimensional array, the initializers might also contain arrays, so this - * method adds the annotations for those initializers, too. - * - * @param initializers initializer trees - * @param type array type to which annotations are added - */ - private void handleInitializers( - List initializers, AnnotatedTypeMirror.AnnotatedArrayType type) { - - type.replaceAnnotation( - atypeFactory.createArrayLenAnnotation(Collections.singletonList(initializers.size()))); - - if (type.getComponentType().getKind() != TypeKind.ARRAY) { - return; - } + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - // A list of arrayLens. arrayLenOfDimensions.get(i) is the array lengths for the ith - // dimension. - List arrayLenOfDimensions = new ArrayList<>(); - for (ExpressionTree init : initializers) { - AnnotatedTypeMirror componentType = atypeFactory.getAnnotatedType(init); - int dimension = 0; - while (componentType.getKind() == TypeKind.ARRAY) { - RangeOrListOfValues rolv = null; - if (dimension < arrayLenOfDimensions.size()) { - rolv = arrayLenOfDimensions.get(dimension); - } - AnnotationMirror arrayLen = componentType.getAnnotation(ArrayLen.class); - if (arrayLen != null) { - List currentLengths = atypeFactory.getArrayLength(arrayLen); - if (rolv != null) { - rolv.addAll(currentLengths); - } else { - arrayLenOfDimensions.add(new RangeOrListOfValues(currentLengths)); - } + List dimensions = tree.getDimensions(); + List initializers = tree.getInitializers(); + + // Array construction can provide dimensions or use an initializer. + + // Dimensions provided + if (!dimensions.isEmpty()) { + handleDimensions(dimensions, (AnnotatedTypeMirror.AnnotatedArrayType) type); } else { - // Check for an arrayLenRange annotation - AnnotationMirror arrayLenRangeAnno = componentType.getAnnotation(ArrayLenRange.class); - Range range; - if (arrayLenRangeAnno != null) { - range = atypeFactory.getRange(arrayLenRangeAnno); - } else { - range = Range.EVERYTHING; - } - if (rolv != null) { - rolv.add(range); - } else { - arrayLenOfDimensions.add(new RangeOrListOfValues(range)); - } + // Initializer used + handleInitializers(initializers, (AnnotatedTypeMirror.AnnotatedArrayType) type); + + AnnotationMirror newQual; + Class clazz = TypesUtils.getClassFromType(type.getUnderlyingType()); + String stringVal = null; + if (clazz == char[].class) { + stringVal = getCharArrayStringVal(initializers); + } + + if (stringVal != null) { + newQual = atypeFactory.createStringAnnotation(Collections.singletonList(stringVal)); + type.replaceAnnotation(newQual); + } } - dimension++; - componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); - } + return null; } - AnnotatedTypeMirror componentType = type.getComponentType(); - int i = 0; - while (componentType.getKind() == TypeKind.ARRAY && i < arrayLenOfDimensions.size()) { - RangeOrListOfValues rolv = arrayLenOfDimensions.get(i); - componentType.addAnnotation(rolv.createAnnotation(atypeFactory)); - componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); - i++; - } - } - - /** Convert a char array to a String. Return null if unable to convert. */ - private @Nullable String getCharArrayStringVal(List initializers) { - boolean allLiterals = true; - StringBuilder stringVal = new StringBuilder(); - for (ExpressionTree e : initializers) { - Range range = - atypeFactory.getRange( - atypeFactory.getAnnotatedType(e).getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - if (range != null && range.from == range.to) { - char charVal = (char) range.from; - stringVal.append(charVal); - } else { - allLiterals = false; - break; - } - } - if (allLiterals) { - return stringVal.toString(); - } - // If any part of the initializer isn't known, - // the stringval isn't known. - return null; - } - - // Side-effects the `atm` formal parameter. - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) { - if (handledByValueChecker(atm)) { - AnnotationMirror oldAnno = - atypeFactory - .getAnnotatedType(tree.getExpression()) - .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - if (oldAnno == null) { - return null; - } - - // I would like to call - // ((AnnotatedTypeTree) castTree).hasPrimaryAnnotation(Unsigned.class), - // but `Unsigned` is in the checker package and this code is in the common package. - List annoTrees = - TreeUtils.getExplicitAnnotationTrees(null, tree.getType()); - List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); - boolean isUnsigned = - AnnotationUtils.containsSameByName( - annos, "org.checkerframework.checker.signedness.qual.Unsigned"); - - TypeMirror newType = atm.getUnderlyingType(); - AnnotationMirror newAnno; - Range range; - - if (TypesUtils.isString(newType) || newType.getKind() == TypeKind.ARRAY) { - // Strings and arrays do not allow conversions - newAnno = oldAnno; - } else if (atypeFactory.isIntRange(oldAnno) - && (range = atypeFactory.getRange(oldAnno)) - .isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { - Class newClass = TypesUtils.getClassFromType(newType); - if (newClass == String.class) { - newAnno = atypeFactory.UNKNOWNVAL; - } else if (newClass == Boolean.class || newClass == boolean.class) { - throw new UnsupportedOperationException( - "ValueAnnotatedTypeFactory: can't convert int to boolean"); + /** + * Recursive method to handle array initializations. Recursively descends the initializer to + * find each dimension's size and create the appropriate annotation for it. + * + *

          If the annotation of the dimension is {@code @IntVal}, create an {@code @ArrayLen} with + * the same set of possible values. If the annotation is {@code @IntRange}, create an + * {@code @ArrayLenRange}. If the annotation is {@code @BottomVal}, create an {@code @BottomVal} + * instead. In other cases, no annotations are created. + * + * @param dimensions a list of ExpressionTrees where each ExpressionTree is a specifier of the + * size of that dimension + * @param type the AnnotatedTypeMirror of the array, which is side-effected by this method + */ + private void handleDimensions( + List dimensions, + AnnotatedTypeMirror.AnnotatedArrayType type) { + if (dimensions.size() > 1) { + handleDimensions( + dimensions.subList(1, dimensions.size()), + (AnnotatedTypeMirror.AnnotatedArrayType) type.getComponentType()); + } + AnnotationMirror dimType = + atypeFactory + .getAnnotatedType(dimensions.get(0)) + .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + + if (AnnotationUtils.areSameByName(dimType, atypeFactory.BOTTOMVAL)) { + type.replaceAnnotation(atypeFactory.BOTTOMVAL); } else { - newAnno = atypeFactory.createIntRangeAnnotation(NumberUtils.castRange(newType, range)); + RangeOrListOfValues rolv = null; + if (atypeFactory.isIntRange(dimType)) { + rolv = new RangeOrListOfValues(atypeFactory.getRange(dimType)); + } else if (AnnotationUtils.areSameByName( + dimType, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + rolv = + new RangeOrListOfValues( + RangeOrListOfValues.convertLongsToInts( + atypeFactory.getIntValues(dimType))); + } + if (rolv != null) { + AnnotationMirror newQual = rolv.createAnnotation(atypeFactory); + type.replaceAnnotation(newQual); + } } - } else { - List values = - ValueCheckerUtils.getValuesCastedToType(oldAnno, newType, isUnsigned, atypeFactory); - newAnno = atypeFactory.createResultingAnnotation(atm.getUnderlyingType(), values); - } - atm.addMissingAnnotations(Collections.singleton(newAnno)); - } else if (atm.getKind() == TypeKind.ARRAY) { - if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { - atm.addMissingAnnotations(Collections.singleton(atypeFactory.BOTTOMVAL)); - } - } - return null; - } - - /** - * Get the "value" element/field of the annotation on {@code type}, casted to the given type. - * Empty list means no value is possible (dead code). Null means no information is known -- any - * value is possible. - * - * @param type the type with a Value Checker annotation - * @param castTo the type to cast to - * @return the Value Checker annotation's value, casted to the given type - */ - private @Nullable List getValues(AnnotatedTypeMirror type, TypeMirror castTo) { - return getValues(type, castTo, false); - } - - /** - * Get the "value" element/field of the annotation on {@code type}, casted to the given type. - * Empty list means no value is possible (dead code). Null means no information is known -- any - * value is possible. - * - * @param type the type with a Value Checker annotation - * @param castTo the type to cast to - * @param isUnsigned if true, treat {@code castTo} as unsigned - * @return the Value Checker annotation's value, casted to the given type - */ - private @Nullable List getValues( - AnnotatedTypeMirror type, TypeMirror castTo, boolean isUnsigned) { - AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - if (anno == null) { - // If type is an AnnotatedTypeVariable (or other type without a primary annotation) - // then anno will be null. It would be safe to use the annotation on the upper - // bound; however, unless the upper bound was explicitly annotated, it will be - // unknown. AnnotatedTypes.findEffectiveAnnotationInHierarchy(, toSearch, top) - return null; } - return ValueCheckerUtils.getValuesCastedToType(anno, castTo, isUnsigned, atypeFactory); - } - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!handledByValueChecker(type)) { - return null; + /** + * Adds the ArrayLen/ArrayLenRange annotation from the array initializers to {@code type}. + * + *

          If type is a multi-dimensional array, the initializers might also contain arrays, so this + * method adds the annotations for those initializers, too. + * + * @param initializers initializer trees + * @param type array type to which annotations are added + */ + private void handleInitializers( + List initializers, + AnnotatedTypeMirror.AnnotatedArrayType type) { + + type.replaceAnnotation( + atypeFactory.createArrayLenAnnotation( + Collections.singletonList(initializers.size()))); + + if (type.getComponentType().getKind() != TypeKind.ARRAY) { + return; + } + + // A list of arrayLens. arrayLenOfDimensions.get(i) is the array lengths for the ith + // dimension. + List arrayLenOfDimensions = new ArrayList<>(); + for (ExpressionTree init : initializers) { + AnnotatedTypeMirror componentType = atypeFactory.getAnnotatedType(init); + int dimension = 0; + while (componentType.getKind() == TypeKind.ARRAY) { + RangeOrListOfValues rolv = null; + if (dimension < arrayLenOfDimensions.size()) { + rolv = arrayLenOfDimensions.get(dimension); + } + AnnotationMirror arrayLen = componentType.getAnnotation(ArrayLen.class); + if (arrayLen != null) { + List currentLengths = atypeFactory.getArrayLength(arrayLen); + if (rolv != null) { + rolv.addAll(currentLengths); + } else { + arrayLenOfDimensions.add(new RangeOrListOfValues(currentLengths)); + } + } else { + // Check for an arrayLenRange annotation + AnnotationMirror arrayLenRangeAnno = + componentType.getAnnotation(ArrayLenRange.class); + Range range; + if (arrayLenRangeAnno != null) { + range = atypeFactory.getRange(arrayLenRangeAnno); + } else { + range = Range.EVERYTHING; + } + if (rolv != null) { + rolv.add(range); + } else { + arrayLenOfDimensions.add(new RangeOrListOfValues(range)); + } + } + + dimension++; + componentType = + ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); + } + } + + AnnotatedTypeMirror componentType = type.getComponentType(); + int i = 0; + while (componentType.getKind() == TypeKind.ARRAY && i < arrayLenOfDimensions.size()) { + RangeOrListOfValues rolv = arrayLenOfDimensions.get(i); + componentType.addAnnotation(rolv.createAnnotation(atypeFactory)); + componentType = + ((AnnotatedTypeMirror.AnnotatedArrayType) componentType).getComponentType(); + i++; + } } - Object value = tree.getValue(); - switch (tree.getKind()) { - case BOOLEAN_LITERAL: - AnnotationMirror boolAnno = - atypeFactory.createBooleanAnnotation(Collections.singletonList((Boolean) value)); - type.replaceAnnotation(boolAnno); - return null; - case CHAR_LITERAL: - AnnotationMirror charAnno = - atypeFactory.createCharAnnotation(Collections.singletonList((Character) value)); - type.replaceAnnotation(charAnno); + /** Convert a char array to a String. Return null if unable to convert. */ + private @Nullable String getCharArrayStringVal(List initializers) { + boolean allLiterals = true; + StringBuilder stringVal = new StringBuilder(); + for (ExpressionTree e : initializers) { + Range range = + atypeFactory.getRange( + atypeFactory + .getAnnotatedType(e) + .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + if (range != null && range.from == range.to) { + char charVal = (char) range.from; + stringVal.append(charVal); + } else { + allLiterals = false; + break; + } + } + if (allLiterals) { + return stringVal.toString(); + } + // If any part of the initializer isn't known, + // the stringval isn't known. return null; + } - case DOUBLE_LITERAL: - case FLOAT_LITERAL: - case INT_LITERAL: - case LONG_LITERAL: - AnnotationMirror numberAnno = - atypeFactory.createNumberAnnotationMirror(Collections.singletonList((Number) value)); - type.replaceAnnotation(numberAnno); - return null; - case STRING_LITERAL: - AnnotationMirror stringAnno = - atypeFactory.createStringAnnotation(Collections.singletonList((String) value)); - type.replaceAnnotation(stringAnno); - return null; - default: + // Side-effects the `atm` formal parameter. + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) { + if (handledByValueChecker(atm)) { + AnnotationMirror oldAnno = + atypeFactory + .getAnnotatedType(tree.getExpression()) + .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + if (oldAnno == null) { + return null; + } + + // I would like to call + // ((AnnotatedTypeTree) castTree).hasPrimaryAnnotation(Unsigned.class), + // but `Unsigned` is in the checker package and this code is in the common package. + List annoTrees = + TreeUtils.getExplicitAnnotationTrees(null, tree.getType()); + List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); + boolean isUnsigned = + AnnotationUtils.containsSameByName( + annos, "org.checkerframework.checker.signedness.qual.Unsigned"); + + TypeMirror newType = atm.getUnderlyingType(); + AnnotationMirror newAnno; + Range range; + + if (TypesUtils.isString(newType) || newType.getKind() == TypeKind.ARRAY) { + // Strings and arrays do not allow conversions + newAnno = oldAnno; + } else if (atypeFactory.isIntRange(oldAnno) + && (range = atypeFactory.getRange(oldAnno)) + .isWiderThan(ValueAnnotatedTypeFactory.MAX_VALUES)) { + Class newClass = TypesUtils.getClassFromType(newType); + if (newClass == String.class) { + newAnno = atypeFactory.UNKNOWNVAL; + } else if (newClass == Boolean.class || newClass == boolean.class) { + throw new UnsupportedOperationException( + "ValueAnnotatedTypeFactory: can't convert int to boolean"); + } else { + newAnno = + atypeFactory.createIntRangeAnnotation( + NumberUtils.castRange(newType, range)); + } + } else { + List values = + ValueCheckerUtils.getValuesCastedToType( + oldAnno, newType, isUnsigned, atypeFactory); + newAnno = atypeFactory.createResultingAnnotation(atm.getUnderlyingType(), values); + } + atm.addMissingAnnotations(Collections.singleton(newAnno)); + } else if (atm.getKind() == TypeKind.ARRAY) { + if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { + atm.addMissingAnnotations(Collections.singleton(atypeFactory.BOTTOMVAL)); + } + } return null; } - } - - /** - * Given a MemberSelectTree representing a method call, return true if the method's declaration is - * annotated with {@code @StaticallyExecutable}. - */ - private boolean methodIsStaticallyExecutable(Element method) { - return atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null; - } - - /** - * Returns the Range of the Math.min or Math.max method, or null if the argument is none of these - * methods or their arguments are not annotated in ValueChecker hierarchy. - * - * @return the Range of the Math.min or Math.max method, or null if the argument is none of these - * methods or their arguments are not annotated in ValueChecker hierarchy - */ - private @Nullable Range getRangeForMathMinMax(MethodInvocationTree tree) { - if (atypeFactory.getMethodIdentifier().isMathMin(tree, atypeFactory.getProcessingEnv())) { - AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); - Range rangeArg1 = - atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - Range rangeArg2 = - atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - if (rangeArg1 != null && rangeArg2 != null) { - return rangeArg1.min(rangeArg2); - } - } else if (atypeFactory - .getMethodIdentifier() - .isMathMax(tree, atypeFactory.getProcessingEnv())) { - AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); - AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); - Range rangeArg1 = - atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - Range rangeArg2 = - atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); - if (rangeArg1 != null && rangeArg2 != null) { - return rangeArg1.max(rangeArg2); - } - } - return null; - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (type.hasAnnotation(atypeFactory.UNKNOWNVAL)) { - Range range = getRangeForMathMinMax(tree); - if (range != null) { - type.replaceAnnotation(atypeFactory.createIntRangeAnnotation(range)); - } - } - if (atypeFactory - .getMethodIdentifier() - .isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) { - List args = tree.getArguments(); - Range range = - ValueCheckerUtils.getPossibleValues( - atypeFactory.getAnnotatedType(args.get(1)), atypeFactory); - if (range != null) { - type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range)); - } + /** + * Get the "value" element/field of the annotation on {@code type}, casted to the given type. + * Empty list means no value is possible (dead code). Null means no information is known -- any + * value is possible. + * + * @param type the type with a Value Checker annotation + * @param castTo the type to cast to + * @return the Value Checker annotation's value, casted to the given type + */ + private @Nullable List getValues(AnnotatedTypeMirror type, TypeMirror castTo) { + return getValues(type, castTo, false); } - if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) - || !handledByValueChecker(type)) { - return null; + /** + * Get the "value" element/field of the annotation on {@code type}, casted to the given type. + * Empty list means no value is possible (dead code). Null means no information is known -- any + * value is possible. + * + * @param type the type with a Value Checker annotation + * @param castTo the type to cast to + * @param isUnsigned if true, treat {@code castTo} as unsigned + * @return the Value Checker annotation's value, casted to the given type + */ + private @Nullable List getValues( + AnnotatedTypeMirror type, TypeMirror castTo, boolean isUnsigned) { + AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + if (anno == null) { + // If type is an AnnotatedTypeVariable (or other type without a primary annotation) + // then anno will be null. It would be safe to use the annotation on the upper + // bound; however, unless the upper bound was explicitly annotated, it will be + // unknown. AnnotatedTypes.findEffectiveAnnotationInHierarchy(, toSearch, top) + return null; + } + return ValueCheckerUtils.getValuesCastedToType(anno, castTo, isUnsigned, atypeFactory); } - if (atypeFactory - .getMethodIdentifier() - .isStringLengthInvocation(tree, atypeFactory.getProcessingEnv())) { - AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); - AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType); - if (resultAnno != null) { - type.replaceAnnotation(resultAnno); - } - return null; + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!handledByValueChecker(type)) { + return null; + } + Object value = tree.getValue(); + switch (tree.getKind()) { + case BOOLEAN_LITERAL: + AnnotationMirror boolAnno = + atypeFactory.createBooleanAnnotation( + Collections.singletonList((Boolean) value)); + type.replaceAnnotation(boolAnno); + return null; + + case CHAR_LITERAL: + AnnotationMirror charAnno = + atypeFactory.createCharAnnotation( + Collections.singletonList((Character) value)); + type.replaceAnnotation(charAnno); + return null; + + case DOUBLE_LITERAL: + case FLOAT_LITERAL: + case INT_LITERAL: + case LONG_LITERAL: + AnnotationMirror numberAnno = + atypeFactory.createNumberAnnotationMirror( + Collections.singletonList((Number) value)); + type.replaceAnnotation(numberAnno); + return null; + case STRING_LITERAL: + AnnotationMirror stringAnno = + atypeFactory.createStringAnnotation( + Collections.singletonList((String) value)); + type.replaceAnnotation(stringAnno); + return null; + default: + return null; + } } - if (atypeFactory - .getMethodIdentifier() - .isArrayGetLengthInvocation(tree, atypeFactory.getProcessingEnv())) { - List args = tree.getArguments(); - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(args.get(0)); - AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(argType); - if (resultAnno != null) { - type.replaceAnnotation(resultAnno); - } - return null; + /** + * Given a MemberSelectTree representing a method call, return true if the method's declaration + * is annotated with {@code @StaticallyExecutable}. + */ + private boolean methodIsStaticallyExecutable(Element method) { + return atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null; } - // Get argument values - List arguments = tree.getArguments(); - ArrayList> argValues; - if (arguments.isEmpty()) { - argValues = null; - } else { - argValues = new ArrayList<>(arguments.size()); - for (ExpressionTree argument : arguments) { - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); - List values = getValues(argType, argType.getUnderlyingType()); - if (values == null || values.isEmpty()) { - // Values aren't known, so don't try to evaluate the method. - return null; + /** + * Returns the Range of the Math.min or Math.max method, or null if the argument is none of + * these methods or their arguments are not annotated in ValueChecker hierarchy. + * + * @return the Range of the Math.min or Math.max method, or null if the argument is none of + * these methods or their arguments are not annotated in ValueChecker hierarchy + */ + private @Nullable Range getRangeForMathMinMax(MethodInvocationTree tree) { + if (atypeFactory.getMethodIdentifier().isMathMin(tree, atypeFactory.getProcessingEnv())) { + AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); + Range rangeArg1 = + atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + Range rangeArg2 = + atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + if (rangeArg1 != null && rangeArg2 != null) { + return rangeArg1.min(rangeArg2); + } + } else if (atypeFactory + .getMethodIdentifier() + .isMathMax(tree, atypeFactory.getProcessingEnv())) { + AnnotatedTypeMirror arg1 = atypeFactory.getAnnotatedType(tree.getArguments().get(0)); + AnnotatedTypeMirror arg2 = atypeFactory.getAnnotatedType(tree.getArguments().get(1)); + Range rangeArg1 = + atypeFactory.getRange(arg1.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + Range rangeArg2 = + atypeFactory.getRange(arg2.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL)); + if (rangeArg1 != null && rangeArg2 != null) { + return rangeArg1.max(rangeArg2); + } } - argValues.add(values); - } + return null; } - // Get receiver values - AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); - List receiverValues; + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + if (type.hasAnnotation(atypeFactory.UNKNOWNVAL)) { + Range range = getRangeForMathMinMax(tree); + if (range != null) { + type.replaceAnnotation(atypeFactory.createIntRangeAnnotation(range)); + } + } + + if (atypeFactory + .getMethodIdentifier() + .isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) { + List args = tree.getArguments(); + Range range = + ValueCheckerUtils.getPossibleValues( + atypeFactory.getAnnotatedType(args.get(1)), atypeFactory); + if (range != null) { + type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range)); + } + } + + if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) + || !handledByValueChecker(type)) { + return null; + } + + if (atypeFactory + .getMethodIdentifier() + .isStringLengthInvocation(tree, atypeFactory.getProcessingEnv())) { + AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(tree); + AnnotationMirror resultAnno = + atypeFactory.createArrayLengthResultAnnotation(receiverType); + if (resultAnno != null) { + type.replaceAnnotation(resultAnno); + } + return null; + } + + if (atypeFactory + .getMethodIdentifier() + .isArrayGetLengthInvocation(tree, atypeFactory.getProcessingEnv())) { + List args = tree.getArguments(); + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(args.get(0)); + AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(argType); + if (resultAnno != null) { + type.replaceAnnotation(resultAnno); + } + return null; + } + + // Get argument values + List arguments = tree.getArguments(); + ArrayList> argValues; + if (arguments.isEmpty()) { + argValues = null; + } else { + argValues = new ArrayList<>(arguments.size()); + for (ExpressionTree argument : arguments) { + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); + List values = getValues(argType, argType.getUnderlyingType()); + if (values == null || values.isEmpty()) { + // Values aren't known, so don't try to evaluate the method. + return null; + } + argValues.add(values); + } + } + + // Get receiver values + AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); + List receiverValues; + + if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { + receiverValues = getValues(receiver, receiver.getUnderlyingType()); + if (receiverValues == null || receiverValues.isEmpty()) { + // Values aren't known, so don't try to evaluate the method. + return null; + } + } else { + receiverValues = null; + } + + // Evaluate method + List returnValues = + atypeFactory.evaluator.evaluateMethodCall(argValues, receiverValues, tree); + if (returnValues == null) { + return null; + } + AnnotationMirror returnType = + atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); + type.replaceAnnotation(returnType); - if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { - receiverValues = getValues(receiver, receiver.getUnderlyingType()); - if (receiverValues == null || receiverValues.isEmpty()) { - // Values aren't known, so don't try to evaluate the method. return null; - } - } else { - receiverValues = null; } - // Evaluate method - List returnValues = - atypeFactory.evaluator.evaluateMethodCall(argValues, receiverValues, tree); - if (returnValues == null) { - return null; - } - AnnotationMirror returnType = - atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); - type.replaceAnnotation(returnType); - - return null; - } - - @Override - public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { - if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) - || !handledByValueChecker(type)) { - return null; - } + @Override + public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { + if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) + || !handledByValueChecker(type)) { + return null; + } - // get argument values - List arguments = tree.getArguments(); - ArrayList> argValues; - if (arguments.isEmpty()) { - argValues = null; - } else { - argValues = new ArrayList<>(arguments.size()); - for (ExpressionTree argument : arguments) { - AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); - List values = getValues(argType, argType.getUnderlyingType()); - if (values == null || values.isEmpty()) { - // Values aren't known, so don't try to evaluate the method. - return null; + // get argument values + List arguments = tree.getArguments(); + ArrayList> argValues; + if (arguments.isEmpty()) { + argValues = null; + } else { + argValues = new ArrayList<>(arguments.size()); + for (ExpressionTree argument : arguments) { + AnnotatedTypeMirror argType = atypeFactory.getAnnotatedType(argument); + List values = getValues(argType, argType.getUnderlyingType()); + if (values == null || values.isEmpty()) { + // Values aren't known, so don't try to evaluate the method. + return null; + } + argValues.add(values); + } } - argValues.add(values); - } - } - // Evaluate method - List returnValues = - atypeFactory.evaluator.evaluteConstructorCall(argValues, tree, type.getUnderlyingType()); - if (returnValues == null) { - return null; - } - AnnotationMirror returnType = - atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); - type.replaceAnnotation(returnType); - - return null; - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - visitFieldAccess(tree, type); - visitEnumConstant(tree, type); - - if (TreeUtils.isArrayLengthAccess(tree)) { - // The field access is to the length field, as in "someArrayExpression.length" - AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(tree.getExpression()); - if (receiverType.getKind() == TypeKind.ARRAY) { - AnnotationMirror resultAnno = atypeFactory.createArrayLengthResultAnnotation(receiverType); - if (resultAnno != null) { - type.replaceAnnotation(resultAnno); + // Evaluate method + List returnValues = + atypeFactory.evaluator.evaluteConstructorCall( + argValues, tree, type.getUnderlyingType()); + if (returnValues == null) { + return null; } - } - } - return null; - } - - /** - * Visit a tree that might be a field access. - * - * @param tree a tree that might be a field access. It is either a MemberSelectTree or an - * IdentifierTree (if the programmer omitted the leading `this.`). - * @param type its type - */ - private void visitFieldAccess(ExpressionTree tree, AnnotatedTypeMirror type) { - if (!TreeUtils.isFieldAccess(tree) || !handledByValueChecker(type)) { - return; + AnnotationMirror returnType = + atypeFactory.createResultingAnnotation(type.getUnderlyingType(), returnValues); + type.replaceAnnotation(returnType); + + return null; } - VariableElement fieldElement = TreeUtils.variableElementFromTree(tree); - Object value = fieldElement.getConstantValue(); - if (value != null) { - // The field is a compile-time constant. - type.replaceAnnotation( - atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value)); - return; + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + visitFieldAccess(tree, type); + visitEnumConstant(tree, type); + + if (TreeUtils.isArrayLengthAccess(tree)) { + // The field access is to the length field, as in "someArrayExpression.length" + AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(tree.getExpression()); + if (receiverType.getKind() == TypeKind.ARRAY) { + AnnotationMirror resultAnno = + atypeFactory.createArrayLengthResultAnnotation(receiverType); + if (resultAnno != null) { + type.replaceAnnotation(resultAnno); + } + } + } + return null; } - if (ElementUtils.isStatic(fieldElement) && ElementUtils.isFinal(fieldElement)) { - // The field is static and final, but its declaration does not initialize it to a - // compile-time constant. Obtain its value reflectively. - Element classElement = fieldElement.getEnclosingElement(); - if (classElement != null) { - @SuppressWarnings("signature" // TODO: bug in ValueAnnotatedTypeFactory. - // evaluateStaticFieldAccess requires a @ClassGetName but this passes a - // @FullyQualifiedName. They differ for inner classes. - ) - @BinaryName String classname = ElementUtils.getQualifiedClassName(classElement).toString(); - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Name.toString() - @Identifier String fieldName = fieldElement.getSimpleName().toString(); - value = atypeFactory.evaluator.evaluateStaticFieldAccess(classname, fieldName, tree); + + /** + * Visit a tree that might be a field access. + * + * @param tree a tree that might be a field access. It is either a MemberSelectTree or an + * IdentifierTree (if the programmer omitted the leading `this.`). + * @param type its type + */ + private void visitFieldAccess(ExpressionTree tree, AnnotatedTypeMirror type) { + if (!TreeUtils.isFieldAccess(tree) || !handledByValueChecker(type)) { + return; + } + + VariableElement fieldElement = TreeUtils.variableElementFromTree(tree); + Object value = fieldElement.getConstantValue(); if (value != null) { - type.replaceAnnotation( - atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value)); + // The field is a compile-time constant. + type.replaceAnnotation( + atypeFactory.createResultingAnnotation(type.getUnderlyingType(), value)); + return; + } + if (ElementUtils.isStatic(fieldElement) && ElementUtils.isFinal(fieldElement)) { + // The field is static and final, but its declaration does not initialize it to a + // compile-time constant. Obtain its value reflectively. + Element classElement = fieldElement.getEnclosingElement(); + if (classElement != null) { + @SuppressWarnings("signature" // TODO: bug in ValueAnnotatedTypeFactory. + // evaluateStaticFieldAccess requires a @ClassGetName but this passes a + // @FullyQualifiedName. They differ for inner classes. + ) + @BinaryName String classname = ElementUtils.getQualifiedClassName(classElement).toString(); + @SuppressWarnings( + "signature") // https://tinyurl.com/cfissue/658 for Name.toString() + @Identifier String fieldName = fieldElement.getSimpleName().toString(); + value = + atypeFactory.evaluator.evaluateStaticFieldAccess( + classname, fieldName, tree); + if (value != null) { + type.replaceAnnotation( + atypeFactory.createResultingAnnotation( + type.getUnderlyingType(), value)); + } + return; + } } - return; - } } - } - - /** Returns true iff the given type is in the domain of the Constant Value Checker. */ - private boolean handledByValueChecker(AnnotatedTypeMirror type) { - TypeMirror tm = type.getUnderlyingType(); - /* TODO: compare performance to the more readable. - return TypesUtils.isPrimitive(tm) - || TypesUtils.isBoxedPrimitive(tm) - || TypesUtils.isString(tm) - || tm.toString().equals("char[]"); // Why? - */ - return COVERED_CLASS_STRINGS.contains(tm.toString()); - } - - @Override - public Void visitConditionalExpression( - ConditionalExpressionTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - // Work around for https://github.com/typetools/checker-framework/issues/602. - annotatedTypeMirror.replaceAnnotation(atypeFactory.UNKNOWNVAL); - return null; - } - - // An IdentifierTree can be a local variable (including formals, exception parameters, etc.) or - // an implicit field access (where `this.` is omitted). - // A field access is always an IdentifierTree or MemberSelectTree. - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { - visitFieldAccess(tree, type); - visitEnumConstant(tree, type); - return null; - } - - /** - * Default the type of an enum constant {@code E.V} to {@code @StringVal("V")}. Does nothing if - * the argument is not an enum constant. - * - * @param tree an Identifier or MemberSelect tree that might be an enum - * @param type the type of that tree - */ - private void visitEnumConstant(ExpressionTree tree, AnnotatedTypeMirror type) { - Element decl = TreeUtils.elementFromUse(tree); - if (decl.getKind() != ElementKind.ENUM_CONSTANT) { - return; + + /** Returns true iff the given type is in the domain of the Constant Value Checker. */ + private boolean handledByValueChecker(AnnotatedTypeMirror type) { + TypeMirror tm = type.getUnderlyingType(); + /* TODO: compare performance to the more readable. + return TypesUtils.isPrimitive(tm) + || TypesUtils.isBoxedPrimitive(tm) + || TypesUtils.isString(tm) + || tm.toString().equals("char[]"); // Why? + */ + return COVERED_CLASS_STRINGS.contains(tm.toString()); + } + + @Override + public Void visitConditionalExpression( + ConditionalExpressionTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + // Work around for https://github.com/typetools/checker-framework/issues/602. + annotatedTypeMirror.replaceAnnotation(atypeFactory.UNKNOWNVAL); + return null; } - Name id; - switch (tree.getKind()) { - case MEMBER_SELECT: - id = ((MemberSelectTree) tree).getIdentifier(); - break; - case IDENTIFIER: - id = ((IdentifierTree) tree).getName(); - break; - default: - throw new TypeSystemError("unexpected kind of enum constant use tree: " + tree.getKind()); + // An IdentifierTree can be a local variable (including formals, exception parameters, etc.) or + // an implicit field access (where `this.` is omitted). + // A field access is always an IdentifierTree or MemberSelectTree. + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { + visitFieldAccess(tree, type); + visitEnumConstant(tree, type); + return null; + } + + /** + * Default the type of an enum constant {@code E.V} to {@code @StringVal("V")}. Does nothing if + * the argument is not an enum constant. + * + * @param tree an Identifier or MemberSelect tree that might be an enum + * @param type the type of that tree + */ + private void visitEnumConstant(ExpressionTree tree, AnnotatedTypeMirror type) { + Element decl = TreeUtils.elementFromUse(tree); + if (decl.getKind() != ElementKind.ENUM_CONSTANT) { + return; + } + + Name id; + switch (tree.getKind()) { + case MEMBER_SELECT: + id = ((MemberSelectTree) tree).getIdentifier(); + break; + case IDENTIFIER: + id = ((IdentifierTree) tree).getName(); + break; + default: + throw new TypeSystemError( + "unexpected kind of enum constant use tree: " + tree.getKind()); + } + AnnotationMirror stringVal = + atypeFactory.createStringAnnotation(Collections.singletonList(id.toString())); + type.replaceAnnotation(stringVal); } - AnnotationMirror stringVal = - atypeFactory.createStringAnnotation(Collections.singletonList(id.toString())); - type.replaceAnnotation(stringVal); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java index c0b40b5c156..03766b95c4f 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTypeAnnotator.java @@ -1,18 +1,20 @@ package org.checkerframework.common.value; +import org.checkerframework.common.value.util.Range; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.typeannotator.TypeAnnotator; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TypeKindUtils; +import org.checkerframework.javacutil.TypesUtils; + import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.checkerframework.common.value.util.Range; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.typeannotator.TypeAnnotator; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.TypeKindUtils; -import org.checkerframework.javacutil.TypesUtils; /** * Performs pre-processing on annotations written by users, replacing illegal annotations by legal @@ -20,164 +22,167 @@ */ class ValueTypeAnnotator extends TypeAnnotator { - /** The type factory to use. Shadows the field from the superclass with a more specific type. */ - @SuppressWarnings("HidingField") - protected final ValueAnnotatedTypeFactory typeFactory; - - /** - * Construct a new ValueTypeAnnotator. - * - * @param typeFactory the type factory to use - */ - protected ValueTypeAnnotator(ValueAnnotatedTypeFactory typeFactory) { - super(typeFactory); - this.typeFactory = typeFactory; - } + /** The type factory to use. Shadows the field from the superclass with a more specific type. */ + @SuppressWarnings("HidingField") + protected final ValueAnnotatedTypeFactory typeFactory; - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - replaceWithNewAnnoInSpecialCases(type); - return super.scan(type, aVoid); - } + /** + * Construct a new ValueTypeAnnotator. + * + * @param typeFactory the type factory to use + */ + protected ValueTypeAnnotator(ValueAnnotatedTypeFactory typeFactory) { + super(typeFactory); + this.typeFactory = typeFactory; + } - /** - * This method performs pre-processing on annotations written by users. - * - *

          If any *Val annotation has > MAX_VALUES number of values provided, replaces the - * annotation by @IntRange for integral types, @ArrayLenRange for arrays, @ArrayLen - * or @ArrayLenRange for strings, and @UnknownVal for all other types. Works together with {@link - * ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} which issues warnings - * to users in these cases. - * - *

          If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value - * "from" is greater than the value "to", replaces the annotation by {@code @BottomVal}. The - * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} raises an error - * to users if the annotation was user-written. - * - *

          If any @ArrayLen annotation has a negative number, replaces the annotation by {@code - * BottomVal}. The {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} - * raises an error to users if the annotation was user-written. - * - *

          If a user only writes one side of an {@code IntRange} annotation, this method also computes - * an appropriate default based on the underlying type for the other side of the range. For - * instance, if the user writes {@code @IntRange(from = 1) short x;} then this method will - * translate the annotation to {@code @IntRange(from = 1, to = Short.MAX_VALUE}. - */ - private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) { - AnnotationMirror anno = atm.getAnnotationInHierarchy(typeFactory.UNKNOWNVAL); - if (anno == null || anno.getElementValues().isEmpty()) { - return; + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + replaceWithNewAnnoInSpecialCases(type); + return super.scan(type, aVoid); } - if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { - List values = typeFactory.getIntValues(anno); - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(Range.create(values))); - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { - List values = typeFactory.getArrayLength(anno); - if (values.isEmpty()) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else if (Collections.min(values) < 0) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(Range.create(values))); - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { - TypeMirror underlyingType = atm.getUnderlyingType(); - // If the underlying type is neither a primitive integral type nor boxed integral type, - // return without making changes. TypesUtils.isIntegralPrimitiveOrBoxed fails if passed - // a non-primitive type that is not a declared type, so it cannot be called directly. - if (!TypeKindUtils.isIntegral(underlyingType.getKind()) - && (underlyingType.getKind() != TypeKind.DECLARED - || !TypesUtils.isIntegralPrimitiveOrBoxed(underlyingType))) { - return; - } + /** + * This method performs pre-processing on annotations written by users. + * + *

          If any *Val annotation has > MAX_VALUES number of values provided, replaces the + * annotation by @IntRange for integral types, @ArrayLenRange for arrays, @ArrayLen + * or @ArrayLenRange for strings, and @UnknownVal for all other types. Works together with + * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} which issues + * warnings to users in these cases. + * + *

          If any @IntRange or @ArrayLenRange annotation has incorrect parameters, e.g. the value + * "from" is greater than the value "to", replaces the annotation by {@code @BottomVal}. The + * {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, Void)} raises an + * error to users if the annotation was user-written. + * + *

          If any @ArrayLen annotation has a negative number, replaces the annotation by {@code + * BottomVal}. The {@link ValueVisitor#visitAnnotation(com.sun.source.tree.AnnotationTree, + * Void)} raises an error to users if the annotation was user-written. + * + *

          If a user only writes one side of an {@code IntRange} annotation, this method also + * computes an appropriate default based on the underlying type for the other side of the range. + * For instance, if the user writes {@code @IntRange(from = 1) short x;} then this method will + * translate the annotation to {@code @IntRange(from = 1, to = Short.MAX_VALUE}. + */ + private void replaceWithNewAnnoInSpecialCases(AnnotatedTypeMirror atm) { + AnnotationMirror anno = atm.getAnnotationInHierarchy(typeFactory.UNKNOWNVAL); + if (anno == null || anno.getElementValues().isEmpty()) { + return; + } + + if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { + List values = typeFactory.getIntValues(anno); + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(Range.create(values))); + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { + List values = typeFactory.getArrayLength(anno); + if (values.isEmpty()) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else if (Collections.min(values) < 0) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + atm.replaceAnnotation( + typeFactory.createArrayLenRangeAnnotation(Range.create(values))); + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { + TypeMirror underlyingType = atm.getUnderlyingType(); + // If the underlying type is neither a primitive integral type nor boxed integral type, + // return without making changes. TypesUtils.isIntegralPrimitiveOrBoxed fails if passed + // a non-primitive type that is not a declared type, so it cannot be called directly. + if (!TypeKindUtils.isIntegral(underlyingType.getKind()) + && (underlyingType.getKind() != TypeKind.DECLARED + || !TypesUtils.isIntegralPrimitiveOrBoxed(underlyingType))) { + return; + } - // Compute appropriate defaults for integral ranges. - long from = typeFactory.getFromValueFromIntRange(atm); - long to = typeFactory.getToValueFromIntRange(atm); + // Compute appropriate defaults for integral ranges. + long from = typeFactory.getFromValueFromIntRange(atm); + long to = typeFactory.getToValueFromIntRange(atm); - if (from > to) { - // `from > to` either indicates a user error when writing an annotation or an error - // in the checker's implementation. `-from` should always be <= to. - // ValueVisitor#validateType will issue an error. - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else { - // Always do a replacement of the annotation here so that the defaults calculated - // above are correctly added to the annotation (assuming the annotation is - // well-formed). - atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(from, to)); - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - int from = typeFactory.getArrayLenRangeFromValue(anno); - int to = typeFactory.getArrayLenRangeToValue(anno); - if (from > to) { - // `from > to` either indicates a user error when writing an annotation or an error - // in the checker's implementation `-from` should always be <= to. - // ValueVisitor#validateType will issue an error. - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } else if (from < 0) { - // No array can have a length less than 0. Any time the type includes a from - // less than zero, it must indicate imprecision in the checker. - atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(0, to)); - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { - // The annotation is StringVal. If there are too many elements, - // ArrayLen or ArrayLenRange is used. - List values = typeFactory.getStringValues(anno); + if (from > to) { + // `from > to` either indicates a user error when writing an annotation or an error + // in the checker's implementation. `-from` should always be <= to. + // ValueVisitor#validateType will issue an error. + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else { + // Always do a replacement of the annotation here so that the defaults calculated + // above are correctly added to the annotation (assuming the annotation is + // well-formed). + atm.replaceAnnotation(typeFactory.createIntRangeAnnotation(from, to)); + } + } else if (AnnotationUtils.areSameByName( + anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + int from = typeFactory.getArrayLenRangeFromValue(anno); + int to = typeFactory.getArrayLenRangeToValue(anno); + if (from > to) { + // `from > to` either indicates a user error when writing an annotation or an error + // in the checker's implementation `-from` should always be <= to. + // ValueVisitor#validateType will issue an error. + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } else if (from < 0) { + // No array can have a length less than 0. Any time the type includes a from + // less than zero, it must indicate imprecision in the checker. + atm.replaceAnnotation(typeFactory.createArrayLenRangeAnnotation(0, to)); + } + } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.STRINGVAL_NAME)) { + // The annotation is StringVal. If there are too many elements, + // ArrayLen or ArrayLenRange is used. + List values = typeFactory.getStringValues(anno); - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - List lengths = ValueCheckerUtils.getLengthsForStringValues(values); - atm.replaceAnnotation(typeFactory.createArrayLenAnnotation(lengths)); - } + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + List lengths = ValueCheckerUtils.getLengthsForStringValues(values); + atm.replaceAnnotation(typeFactory.createArrayLenAnnotation(lengths)); + } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME)) { - // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor - // will issue a warning where the annotation was written. - List regexes = - AnnotationUtils.getElementValueArray( - anno, typeFactory.matchesRegexValueElement, String.class); - if (!allRegexesCompile(regexes)) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } - } else if (AnnotationUtils.areSameByName( - anno, ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { - // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor - // will issue a warning where the annotation was written. - List regexes = - AnnotationUtils.getElementValueArray( - anno, typeFactory.doesNotMatchRegexValueElement, String.class); - if (!allRegexesCompile(regexes)) { - atm.replaceAnnotation(typeFactory.BOTTOMVAL); - } - } else { - // In here the annotation is @*Val where (*) is not Int, String but other types - // (Bool, Double, or Enum). - // Therefore we extract its values in a generic way to check its size. - @SuppressWarnings("deprecation") // concrete annotation class is not known - List values = - AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); - if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - atm.replaceAnnotation(typeFactory.UNKNOWNVAL); - } + } else if (AnnotationUtils.areSameByName( + anno, ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME)) { + // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor + // will issue a warning where the annotation was written. + List regexes = + AnnotationUtils.getElementValueArray( + anno, typeFactory.matchesRegexValueElement, String.class); + if (!allRegexesCompile(regexes)) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } + } else if (AnnotationUtils.areSameByName( + anno, ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME)) { + // If the annotation contains an invalid regex, replace it with bottom. ValueVisitor + // will issue a warning where the annotation was written. + List regexes = + AnnotationUtils.getElementValueArray( + anno, typeFactory.doesNotMatchRegexValueElement, String.class); + if (!allRegexesCompile(regexes)) { + atm.replaceAnnotation(typeFactory.BOTTOMVAL); + } + } else { + // In here the annotation is @*Val where (*) is not Int, String but other types + // (Bool, Double, or Enum). + // Therefore we extract its values in a generic way to check its size. + @SuppressWarnings("deprecation") // concrete annotation class is not known + List values = + AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); + if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + atm.replaceAnnotation(typeFactory.UNKNOWNVAL); + } + } } - } - /** - * Returns true if all the given strings are valid regexes. - * - * @param regexes a list of strings that might all be regexes - * @return true if all the given strings are valid regexes - */ - private boolean allRegexesCompile(List regexes) { - for (String regex : regexes) { - try { - Pattern.compile(regex); - } catch (PatternSyntaxException e) { - return false; - } + /** + * Returns true if all the given strings are valid regexes. + * + * @param regexes a list of strings that might all be regexes + * @return true if all the given strings are valid regexes + */ + private boolean allRegexesCompile(List regexes) { + for (String regex : regexes) { + try { + Pattern.compile(regex); + } catch (PatternSyntaxException e) { + return false; + } + } + return true; } - return true; - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java index 6aa78aef9c9..e5ac2448680 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java @@ -5,17 +5,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; -import java.util.Collections; -import java.util.List; -import java.util.TreeSet; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -39,489 +29,527 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** Visitor for the Constant Value type system. */ public class ValueVisitor extends BaseTypeVisitor { - public ValueVisitor(BaseTypeChecker checker) { - super(checker); - } - - /** - * ValueVisitor overrides this method so that it does not have to check variables annotated with - * the {@link IntRangeFromPositive} annotation, the {@link IntRangeFromNonNegative} annotation, or - * the {@link IntRangeFromGTENegativeOne} annotation. This annotation is only introduced by the - * Index Checker's lower bound annotations. It is safe to defer checking of these values to the - * Index Checker because this is only introduced for explicitly-written {@code - * org.checkerframework.checker.index.qual.Positive}, explicitly-written {@code - * org.checkerframework.checker.index.qual.NonNegative}, and explicitly-written {@code - * org.checkerframework.checker.index.qual.GTENegativeOne} annotations, which must be checked by - * the Lower Bound Checker. - * - * @param varType the annotated type of the lvalue (usually a variable) - * @param valueExp the AST node for the rvalue (the new value) - * @param errorKey the error message key to use if the check fails - * @param extraArgs arguments to the error message key, before "found" and "expected" types - * @return true if the check succeeds, false if an error message was issued - */ - @Override - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - ExpressionTree valueExp, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - replaceSpecialIntRangeAnnotations(varType); - return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); - } - - @Override - @FormatMethod - protected boolean commonAssignmentCheck( - AnnotatedTypeMirror varType, - AnnotatedTypeMirror valueType, - Tree valueTree, - @CompilerMessageKey String errorKey, - Object... extraArgs) { - - replaceSpecialIntRangeAnnotations(varType); - - if (valueType.getKind() == TypeKind.CHAR - && valueType.hasAnnotation(getTypeFactory().UNKNOWNVAL)) { - valueType.addAnnotation(getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING)); + public ValueVisitor(BaseTypeChecker checker) { + super(checker); } - return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); - } - - /** - * Return types for methods that are annotated with {@code @IntRangeFromX} annotations need to be - * replaced with {@code @UnknownVal}. See the documentation on {@link - * #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String, Object[]) - * commonAssignmentCheck}. - * - *

          A separate override is necessary because checkOverride doesn't actually use the - * commonAssignmentCheck. - */ - @Override - protected boolean checkOverride( - MethodTree overriderTree, - AnnotatedTypeMirror.AnnotatedExecutableType overrider, - AnnotatedTypeMirror.AnnotatedDeclaredType overridingType, - AnnotatedTypeMirror.AnnotatedExecutableType overridden, - AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType) { - - replaceSpecialIntRangeAnnotations(overrider); - replaceSpecialIntRangeAnnotations(overridden); - - return super.checkOverride( - overriderTree, overrider, overridingType, overridden, overriddenType); - } - - /** - * Replaces any {@code IntRangeFromX} annotations with {@code @UnknownVal}. This is used to - * prevent these annotations from being required on the left hand side of assignments. - * - * @param varType an annotated type mirror that may contain IntRangeFromX annotations, which will - * be used on the lhs of an assignment or pseudo-assignment - */ - private void replaceSpecialIntRangeAnnotations(AnnotatedTypeMirror varType) { - AnnotatedTypeScanner replaceSpecialIntRangeAnnotations = - new AnnotatedTypeScanner() { - @Override - protected Void scan(AnnotatedTypeMirror type, Void p) { - if (type.hasAnnotation(IntRangeFromPositive.class) - || type.hasAnnotation(IntRangeFromNonNegative.class) - || type.hasAnnotation(IntRangeFromGTENegativeOne.class)) { - type.replaceAnnotation(atypeFactory.UNKNOWNVAL); - } - return super.scan(type, p); - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void p) { - // Don't call super so that the type arguments are not visited. - if (type.getEnclosingType() != null) { - scan(type.getEnclosingType(), p); - } - - return null; - } - }; - replaceSpecialIntRangeAnnotations.visit(varType); - } - - @Override - protected ValueAnnotatedTypeFactory createTypeFactory() { - return new ValueAnnotatedTypeFactory(checker); - } - - /** - * Warns about malformed constant-value annotations. - * - *

          Issues an error if any @IntRange annotation has its 'from' value greater than 'to' value. - * - *

          Issues an error if any constant-value annotation has no arguments. - * - *

          Issues a warning if any constant-value annotation has > MAX_VALUES arguments. - * - *

          Issues a warning if any @ArrayLen/@ArrayLenRange annotations contain a negative array - * length. - * - *

          Issues a warning if any {@literal @}MatchesRegex or {@literal @}DoesNotMatchRegex annotation - * contains an invalid regular expression. - */ - /* Implementation note: the ValueTypeAnnotator replaces such invalid annotations with valid ones. - * Therefore, the usual validation in #validateType cannot perform this validation. - * These warnings cannot be issued in the ValueAnnotatedTypeFactory, because the conversions - * might happen multiple times. - * On the other hand, not all validations can happen here, because only the annotations are - * available, not the full types. - * Therefore, some validation is still done in #validateType below. - */ - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - List args = tree.getArguments(); - - if (args.isEmpty()) { - // Nothing to do if there are no annotation arguments. - return super.visitAnnotation(tree, p); + /** + * ValueVisitor overrides this method so that it does not have to check variables annotated with + * the {@link IntRangeFromPositive} annotation, the {@link IntRangeFromNonNegative} annotation, + * or the {@link IntRangeFromGTENegativeOne} annotation. This annotation is only introduced by + * the Index Checker's lower bound annotations. It is safe to defer checking of these values to + * the Index Checker because this is only introduced for explicitly-written {@code + * org.checkerframework.checker.index.qual.Positive}, explicitly-written {@code + * org.checkerframework.checker.index.qual.NonNegative}, and explicitly-written {@code + * org.checkerframework.checker.index.qual.GTENegativeOne} annotations, which must be checked by + * the Lower Bound Checker. + * + * @param varType the annotated type of the lvalue (usually a variable) + * @param valueExp the AST node for the rvalue (the new value) + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types + * @return true if the check succeeds, false if an error message was issued + */ + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + replaceSpecialIntRangeAnnotations(varType); + return super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); } - AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); - switch (AnnotationUtils.annotationName(anno)) { - case ValueAnnotatedTypeFactory.INTRANGE_NAME: - // If there are 2 arguments, issue an error if from.greater.than.to. - // If there are fewer than 2 arguments, we needn't worry about this problem because - // the other argument will be defaulted to Long.MIN_VALUE or Long.MAX_VALUE - // accordingly. - if (args.size() == 2) { - long from = getTypeFactory().getIntRangeFromValue(anno); - long to = getTypeFactory().getIntRangeToValue(anno); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return null; - } - } - break; - case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: - case ValueAnnotatedTypeFactory.BOOLVAL_NAME: - case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: - case ValueAnnotatedTypeFactory.INTVAL_NAME: - case ValueAnnotatedTypeFactory.STRINGVAL_NAME: - @SuppressWarnings("deprecation") // concrete annotation class is not known - List values = - AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); - - if (values.isEmpty()) { - checker.reportWarning(tree, "no.values.given"); - return null; - } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { - checker.reportWarning( - tree, - (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTVAL_NAME) - ? "too.many.values.given.int" - : "too.many.values.given"), - ValueAnnotatedTypeFactory.MAX_VALUES); - return null; - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { - List arrayLens = getTypeFactory().getArrayLength(anno); - if (Collections.min(arrayLens) < 0) { - checker.reportWarning(tree, "negative.arraylen", Collections.min(arrayLens)); - return null; - } - } - break; - case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: - long from = getTypeFactory().getArrayLenRangeFromValue(anno); - long to = getTypeFactory().getArrayLenRangeToValue(anno); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return null; - } else if (from < 0) { - checker.reportWarning(tree, "negative.arraylen", from); - return null; - } - break; - case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: - List matchesRegexes = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.matchesRegexValueElement, String.class); - for (String regex : matchesRegexes) { - try { - Pattern.compile(regex); - } catch (PatternSyntaxException pse) { - checker.reportWarning(tree, "invalid.matches.regex", pse.getMessage()); - } + @Override + @FormatMethod + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + + replaceSpecialIntRangeAnnotations(varType); + + if (valueType.getKind() == TypeKind.CHAR + && valueType.hasAnnotation(getTypeFactory().UNKNOWNVAL)) { + valueType.addAnnotation( + getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING)); } - break; - case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: - List doesNotMatchRegexes = - AnnotationUtils.getElementValueArray( - anno, atypeFactory.doesNotMatchRegexValueElement, String.class); - for (String regex : doesNotMatchRegexes) { - try { - Pattern.compile(regex); - } catch (PatternSyntaxException pse) { - checker.reportWarning(tree, "invalid.doesnotmatch.regex", pse.getMessage()); - } - } - break; - default: - // Do nothing. + + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } - return super.visitAnnotation(tree, p); - } + /** + * Return types for methods that are annotated with {@code @IntRangeFromX} annotations need to + * be replaced with {@code @UnknownVal}. See the documentation on {@link + * #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String, Object[]) + * commonAssignmentCheck}. + * + *

          A separate override is necessary because checkOverride doesn't actually use the + * commonAssignmentCheck. + */ + @Override + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedTypeMirror.AnnotatedExecutableType overrider, + AnnotatedTypeMirror.AnnotatedDeclaredType overridingType, + AnnotatedTypeMirror.AnnotatedExecutableType overridden, + AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType) { + + replaceSpecialIntRangeAnnotations(overrider); + replaceSpecialIntRangeAnnotations(overridden); + + return super.checkOverride( + overriderTree, overrider, overridingType, overridden, overriddenType); + } - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { - return null; + /** + * Replaces any {@code IntRangeFromX} annotations with {@code @UnknownVal}. This is used to + * prevent these annotations from being required on the left hand side of assignments. + * + * @param varType an annotated type mirror that may contain IntRangeFromX annotations, which + * will be used on the lhs of an assignment or pseudo-assignment + */ + private void replaceSpecialIntRangeAnnotations(AnnotatedTypeMirror varType) { + AnnotatedTypeScanner replaceSpecialIntRangeAnnotations = + new AnnotatedTypeScanner() { + @Override + protected Void scan(AnnotatedTypeMirror type, Void p) { + if (type.hasAnnotation(IntRangeFromPositive.class) + || type.hasAnnotation(IntRangeFromNonNegative.class) + || type.hasAnnotation(IntRangeFromGTENegativeOne.class)) { + type.replaceAnnotation(atypeFactory.UNKNOWNVAL); + } + return super.scan(type, p); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void p) { + // Don't call super so that the type arguments are not visited. + if (type.getEnclosingType() != null) { + scan(type.getEnclosingType(), p); + } + + return null; + } + }; + replaceSpecialIntRangeAnnotations.visit(varType); } - AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(tree); - AnnotationMirror castAnno = castType.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - AnnotationMirror exprAnno = - atypeFactory - .getAnnotatedType(tree.getExpression()) - .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - - // It is always legal to cast to an IntRange type that includes all values - // of the underlying type. Do not warn about such casts. - // I.e. do not warn if an @IntRange(...) int is casted - // to a @IntRange(from = Byte.MIN_VALUE, to = Byte.MAX_VALUE byte). - if (castAnno != null - && exprAnno != null - && atypeFactory.isIntRange(castAnno) - && atypeFactory.isIntRange(exprAnno)) { - Range castRange = atypeFactory.getRange(castAnno); - TypeKind castTypeKind = castType.getKind(); - if (castTypeKind == TypeKind.BYTE && castRange.isByteEverything()) { - return p; - } - if (castTypeKind == TypeKind.CHAR && castRange.isCharEverything()) { - return p; - } - if (castTypeKind == TypeKind.SHORT && castRange.isShortEverything()) { - return p; - } - if (castTypeKind == TypeKind.INT && castRange.isIntEverything()) { - return p; - } - if (castTypeKind == TypeKind.LONG && castRange.isLongEverything()) { - return p; - } - if (Range.ignoreOverflow) { - // Range.ignoreOverflow is only set if this checker is ignoring overflow. - // In that case, do not warn if the range of the expression encompasses - // the whole type being casted to (i.e. the warning is actually about overflow). - Range exprRange = atypeFactory.getRange(exprAnno); - if (castTypeKind == TypeKind.BYTE - || castTypeKind == TypeKind.CHAR - || castTypeKind == TypeKind.SHORT - || castTypeKind == TypeKind.INT) { - exprRange = NumberUtils.castRange(castType.getUnderlyingType(), exprRange); - } - if (castRange.equals(exprRange)) { - return p; - } - } + @Override + protected ValueAnnotatedTypeFactory createTypeFactory() { + return new ValueAnnotatedTypeFactory(checker); } - return super.visitTypeCast(tree, p); - } - - // At this point, types are like: (@IntVal(-1) byte, @IntVal(255) int) and knowledge of - // signedness is gone. So, use castType's underlying type to infer correctness of the - // cast. This method returns true for (@IntVal(-1), @IntVal(255)) if the underlying type - // is `byte`, but not for any other underlying type. - @Override - protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { - TypeKind castTypeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(castType.getUnderlyingType()); - TypeKind exprTypeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(exprType.getUnderlyingType()); - if (castTypeKind != null - && exprTypeKind != null - && TypeKindUtils.isIntegral(castTypeKind) - && TypeKindUtils.isIntegral(exprTypeKind)) { - AnnotationMirrorSet castAnnos = castType.getAnnotations(); - AnnotationMirrorSet exprAnnos = exprType.getAnnotations(); - if (castAnnos.equals(exprAnnos)) { - return true; - } - assert castAnnos.size() == 1; - assert exprAnnos.size() == 1; - AnnotationMirror castAnno = castAnnos.first(); - AnnotationMirror exprAnno = exprAnnos.first(); - boolean castAnnoIsIntVal = atypeFactory.areSameByClass(castAnno, IntVal.class); - boolean exprAnnoIsIntVal = atypeFactory.areSameByClass(exprAnno, IntVal.class); - if (castAnnoIsIntVal && exprAnnoIsIntVal) { - List castValues = atypeFactory.getIntValues(castAnno); - List exprValues = atypeFactory.getIntValues(exprAnno); - if (castValues.size() == 1 && exprValues.size() == 1) { - // Special-case singleton sets for speed. - switch (castTypeKind) { - case BYTE: - return castValues.get(0).byteValue() == exprValues.get(0).byteValue(); - case INT: - return castValues.get(0).intValue() == exprValues.get(0).intValue(); - case SHORT: - return castValues.get(0).shortValue() == exprValues.get(0).shortValue(); - default: - return castValues.get(0).longValue() == exprValues.get(0).longValue(); - } - } else { - switch (castTypeKind) { - case BYTE: - { - TreeSet castValuesTree = - new TreeSet(CollectionsPlume.mapList(Number::byteValue, castValues)); - TreeSet exprValuesTree = - new TreeSet(CollectionsPlume.mapList(Number::byteValue, exprValues)); - return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); - } - case INT: - { - TreeSet castValuesTree = - new TreeSet(CollectionsPlume.mapList(Number::intValue, castValues)); - TreeSet exprValuesTree = - new TreeSet(CollectionsPlume.mapList(Number::intValue, exprValues)); - return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); - } - case SHORT: - { - TreeSet castValuesTree = - new TreeSet(CollectionsPlume.mapList(Number::shortValue, castValues)); - TreeSet exprValuesTree = - new TreeSet(CollectionsPlume.mapList(Number::shortValue, exprValues)); - return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); - } + + /** + * Warns about malformed constant-value annotations. + * + *

          Issues an error if any @IntRange annotation has its 'from' value greater than 'to' value. + * + *

          Issues an error if any constant-value annotation has no arguments. + * + *

          Issues a warning if any constant-value annotation has > MAX_VALUES arguments. + * + *

          Issues a warning if any @ArrayLen/@ArrayLenRange annotations contain a negative array + * length. + * + *

          Issues a warning if any {@literal @}MatchesRegex or {@literal @}DoesNotMatchRegex + * annotation contains an invalid regular expression. + */ + /* Implementation note: the ValueTypeAnnotator replaces such invalid annotations with valid ones. + * Therefore, the usual validation in #validateType cannot perform this validation. + * These warnings cannot be issued in the ValueAnnotatedTypeFactory, because the conversions + * might happen multiple times. + * On the other hand, not all validations can happen here, because only the annotations are + * available, not the full types. + * Therefore, some validation is still done in #validateType below. + */ + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + List args = tree.getArguments(); + + if (args.isEmpty()) { + // Nothing to do if there are no annotation arguments. + return super.visitAnnotation(tree, p); + } + + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(tree); + switch (AnnotationUtils.annotationName(anno)) { + case ValueAnnotatedTypeFactory.INTRANGE_NAME: + // If there are 2 arguments, issue an error if from.greater.than.to. + // If there are fewer than 2 arguments, we needn't worry about this problem because + // the other argument will be defaulted to Long.MIN_VALUE or Long.MAX_VALUE + // accordingly. + if (args.size() == 2) { + long from = getTypeFactory().getIntRangeFromValue(anno); + long to = getTypeFactory().getIntRangeToValue(anno); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return null; + } + } + break; + case ValueAnnotatedTypeFactory.ARRAYLEN_NAME: + case ValueAnnotatedTypeFactory.BOOLVAL_NAME: + case ValueAnnotatedTypeFactory.DOUBLEVAL_NAME: + case ValueAnnotatedTypeFactory.INTVAL_NAME: + case ValueAnnotatedTypeFactory.STRINGVAL_NAME: + @SuppressWarnings("deprecation") // concrete annotation class is not known + List values = + AnnotationUtils.getElementValueArray(anno, "value", Object.class, false); + + if (values.isEmpty()) { + checker.reportWarning(tree, "no.values.given"); + return null; + } else if (values.size() > ValueAnnotatedTypeFactory.MAX_VALUES) { + checker.reportWarning( + tree, + (AnnotationUtils.areSameByName( + anno, ValueAnnotatedTypeFactory.INTVAL_NAME) + ? "too.many.values.given.int" + : "too.many.values.given"), + ValueAnnotatedTypeFactory.MAX_VALUES); + return null; + } else if (AnnotationUtils.areSameByName( + anno, ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { + List arrayLens = getTypeFactory().getArrayLength(anno); + if (Collections.min(arrayLens) < 0) { + checker.reportWarning( + tree, "negative.arraylen", Collections.min(arrayLens)); + return null; + } + } + break; + case ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME: + long from = getTypeFactory().getArrayLenRangeFromValue(anno); + long to = getTypeFactory().getArrayLenRangeToValue(anno); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return null; + } else if (from < 0) { + checker.reportWarning(tree, "negative.arraylen", from); + return null; + } + break; + case ValueAnnotatedTypeFactory.MATCHES_REGEX_NAME: + List matchesRegexes = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.matchesRegexValueElement, String.class); + for (String regex : matchesRegexes) { + try { + Pattern.compile(regex); + } catch (PatternSyntaxException pse) { + checker.reportWarning(tree, "invalid.matches.regex", pse.getMessage()); + } + } + break; + case ValueAnnotatedTypeFactory.DOES_NOT_MATCH_REGEX_NAME: + List doesNotMatchRegexes = + AnnotationUtils.getElementValueArray( + anno, atypeFactory.doesNotMatchRegexValueElement, String.class); + for (String regex : doesNotMatchRegexes) { + try { + Pattern.compile(regex); + } catch (PatternSyntaxException pse) { + checker.reportWarning(tree, "invalid.doesnotmatch.regex", pse.getMessage()); + } + } + break; default: - { - TreeSet castValuesTree = new TreeSet<>(castValues); - TreeSet exprValuesTree = new TreeSet<>(exprValues); - return CollectionsPlume.sortedSetContainsAll(castValuesTree, exprValuesTree); - } - } + // Do nothing. } - } + + return super.visitAnnotation(tree, p); } - return super.isTypeCastSafe(castType, exprType); - } - - /** - * Overridden to issue errors at the appropriate place if an {@code IntRange} or {@code - * ArrayLenRange} annotation has {@code from > to}. {@code from > to} either indicates a user - * error when writing an annotation or an error in the checker's implementation, as {@code from} - * should always be {@code <= to}. Note that additional checks are performed in {@link - * #visitAnnotation(AnnotationTree, Void)}. - * - * @see #visitAnnotation(AnnotationTree, Void) - */ - @Override - public boolean validateType(Tree tree, AnnotatedTypeMirror type) { - replaceSpecialIntRangeAnnotations(type); - if (!super.validateType(tree, type)) { - return false; + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + if (tree.getExpression().getKind() == Tree.Kind.NULL_LITERAL) { + return null; + } + + AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(tree); + AnnotationMirror castAnno = castType.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + AnnotationMirror exprAnno = + atypeFactory + .getAnnotatedType(tree.getExpression()) + .getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + + // It is always legal to cast to an IntRange type that includes all values + // of the underlying type. Do not warn about such casts. + // I.e. do not warn if an @IntRange(...) int is casted + // to a @IntRange(from = Byte.MIN_VALUE, to = Byte.MAX_VALUE byte). + if (castAnno != null + && exprAnno != null + && atypeFactory.isIntRange(castAnno) + && atypeFactory.isIntRange(exprAnno)) { + Range castRange = atypeFactory.getRange(castAnno); + TypeKind castTypeKind = castType.getKind(); + if (castTypeKind == TypeKind.BYTE && castRange.isByteEverything()) { + return p; + } + if (castTypeKind == TypeKind.CHAR && castRange.isCharEverything()) { + return p; + } + if (castTypeKind == TypeKind.SHORT && castRange.isShortEverything()) { + return p; + } + if (castTypeKind == TypeKind.INT && castRange.isIntEverything()) { + return p; + } + if (castTypeKind == TypeKind.LONG && castRange.isLongEverything()) { + return p; + } + if (Range.ignoreOverflow) { + // Range.ignoreOverflow is only set if this checker is ignoring overflow. + // In that case, do not warn if the range of the expression encompasses + // the whole type being casted to (i.e. the warning is actually about overflow). + Range exprRange = atypeFactory.getRange(exprAnno); + if (castTypeKind == TypeKind.BYTE + || castTypeKind == TypeKind.CHAR + || castTypeKind == TypeKind.SHORT + || castTypeKind == TypeKind.INT) { + exprRange = NumberUtils.castRange(castType.getUnderlyingType(), exprRange); + } + if (castRange.equals(exprRange)) { + return p; + } + } + } + return super.visitTypeCast(tree, p); } - AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); - if (anno == null) { - return false; + // At this point, types are like: (@IntVal(-1) byte, @IntVal(255) int) and knowledge of + // signedness is gone. So, use castType's underlying type to infer correctness of the + // cast. This method returns true for (@IntVal(-1), @IntVal(255)) if the underlying type + // is `byte`, but not for any other underlying type. + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + TypeKind castTypeKind = + TypeKindUtils.primitiveOrBoxedToTypeKind(castType.getUnderlyingType()); + TypeKind exprTypeKind = + TypeKindUtils.primitiveOrBoxedToTypeKind(exprType.getUnderlyingType()); + if (castTypeKind != null + && exprTypeKind != null + && TypeKindUtils.isIntegral(castTypeKind) + && TypeKindUtils.isIntegral(exprTypeKind)) { + AnnotationMirrorSet castAnnos = castType.getAnnotations(); + AnnotationMirrorSet exprAnnos = exprType.getAnnotations(); + if (castAnnos.equals(exprAnnos)) { + return true; + } + assert castAnnos.size() == 1; + assert exprAnnos.size() == 1; + AnnotationMirror castAnno = castAnnos.first(); + AnnotationMirror exprAnno = exprAnnos.first(); + boolean castAnnoIsIntVal = atypeFactory.areSameByClass(castAnno, IntVal.class); + boolean exprAnnoIsIntVal = atypeFactory.areSameByClass(exprAnno, IntVal.class); + if (castAnnoIsIntVal && exprAnnoIsIntVal) { + List castValues = atypeFactory.getIntValues(castAnno); + List exprValues = atypeFactory.getIntValues(exprAnno); + if (castValues.size() == 1 && exprValues.size() == 1) { + // Special-case singleton sets for speed. + switch (castTypeKind) { + case BYTE: + return castValues.get(0).byteValue() == exprValues.get(0).byteValue(); + case INT: + return castValues.get(0).intValue() == exprValues.get(0).intValue(); + case SHORT: + return castValues.get(0).shortValue() == exprValues.get(0).shortValue(); + default: + return castValues.get(0).longValue() == exprValues.get(0).longValue(); + } + } else { + switch (castTypeKind) { + case BYTE: + { + TreeSet castValuesTree = + new TreeSet( + CollectionsPlume.mapList( + Number::byteValue, castValues)); + TreeSet exprValuesTree = + new TreeSet( + CollectionsPlume.mapList( + Number::byteValue, exprValues)); + return CollectionsPlume.sortedSetContainsAll( + castValuesTree, exprValuesTree); + } + case INT: + { + TreeSet castValuesTree = + new TreeSet( + CollectionsPlume.mapList( + Number::intValue, castValues)); + TreeSet exprValuesTree = + new TreeSet( + CollectionsPlume.mapList( + Number::intValue, exprValues)); + return CollectionsPlume.sortedSetContainsAll( + castValuesTree, exprValuesTree); + } + case SHORT: + { + TreeSet castValuesTree = + new TreeSet( + CollectionsPlume.mapList( + Number::shortValue, castValues)); + TreeSet exprValuesTree = + new TreeSet( + CollectionsPlume.mapList( + Number::shortValue, exprValues)); + return CollectionsPlume.sortedSetContainsAll( + castValuesTree, exprValuesTree); + } + default: + { + TreeSet castValuesTree = new TreeSet<>(castValues); + TreeSet exprValuesTree = new TreeSet<>(exprValues); + return CollectionsPlume.sortedSetContainsAll( + castValuesTree, exprValuesTree); + } + } + } + } + } + + return super.isTypeCastSafe(castType, exprType); } - if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { - if (TypesUtils.isIntegralPrimitiveOrBoxed(type.getUnderlyingType())) { - long from = atypeFactory.getFromValueFromIntRange(type); - long to = atypeFactory.getToValueFromIntRange(type); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return false; + /** + * Overridden to issue errors at the appropriate place if an {@code IntRange} or {@code + * ArrayLenRange} annotation has {@code from > to}. {@code from > to} either indicates a user + * error when writing an annotation or an error in the checker's implementation, as {@code from} + * should always be {@code <= to}. Note that additional checks are performed in {@link + * #visitAnnotation(AnnotationTree, Void)}. + * + * @see #visitAnnotation(AnnotationTree, Void) + */ + @Override + public boolean validateType(Tree tree, AnnotatedTypeMirror type) { + replaceSpecialIntRangeAnnotations(type); + if (!super.validateType(tree, type)) { + return false; } - } else { - TypeMirror utype = type.getUnderlyingType(); - if (!TypesUtils.isObject(utype) - && !TypesUtils.isDeclaredOfName(utype, "java.lang.Number") - && !TypesUtils.isFloatingPoint(utype)) { - checker.reportError(tree, "annotation.intrange.on.noninteger"); - return false; + + AnnotationMirror anno = type.getAnnotationInHierarchy(atypeFactory.UNKNOWNVAL); + if (anno == null) { + return false; + } + + if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.INTRANGE_NAME)) { + if (TypesUtils.isIntegralPrimitiveOrBoxed(type.getUnderlyingType())) { + long from = atypeFactory.getFromValueFromIntRange(type); + long to = atypeFactory.getToValueFromIntRange(type); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return false; + } + } else { + TypeMirror utype = type.getUnderlyingType(); + if (!TypesUtils.isObject(utype) + && !TypesUtils.isDeclaredOfName(utype, "java.lang.Number") + && !TypesUtils.isFloatingPoint(utype)) { + checker.reportError(tree, "annotation.intrange.on.noninteger"); + return false; + } + } + } else if (AnnotationUtils.areSameByName( + anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { + long from = getTypeFactory().getArrayLenRangeFromValue(anno); + long to = getTypeFactory().getArrayLenRangeToValue(anno); + if (from > to) { + checker.reportError(tree, "from.greater.than.to"); + return false; + } } - } - } else if (AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { - long from = getTypeFactory().getArrayLenRangeFromValue(anno); - long to = getTypeFactory().getArrayLenRangeToValue(anno); - if (from > to) { - checker.reportError(tree, "from.greater.than.to"); - return false; - } + + return true; } - return true; - } - - /** - * Returns true if an expression of the given type can be a compile-time constant value. - * - * @param tm a type - * @return true if an expression of the given type can be a compile-time constant value - */ - private boolean canBeConstant(TypeMirror tm) { - return TypesUtils.isPrimitive(tm) - || TypesUtils.isBoxedPrimitive(tm) - || TypesUtils.isString(tm) - || (tm.getKind() == TypeKind.ARRAY && canBeConstant(((ArrayType) tm).getComponentType())); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - super.visitMethod(tree, p); - - ExecutableElement method = TreeUtils.elementFromDeclaration(tree); - if (atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null) { - // The method is annotated as @StaticallyExecutable. - if (atypeFactory.getDeclAnnotation(method, Pure.class) == null) { - checker.reportWarning(tree, "statically.executable.not.pure"); - } - TypeMirror returnType = method.getReturnType(); - if (returnType.getKind() != TypeKind.VOID && !canBeConstant(returnType)) { - checker.reportError(tree, "statically.executable.nonconstant.return.type", returnType); - } - - // Ways to determine the receiver type. - // 1. This definition of receiverType is null when receiver is implicit and method has - // class com.sun.tools.javac.code.Symbol$MethodSymbol. WHY? - // TypeMirror receiverType = method.getReceiverType(); - // The same is true of TreeUtils.elementFromDeclaration(tree).getReceiverType() - // which seems to conflict with ExecutableType's documentation. - // 2. Can't use the tree, because the receiver might not be explicit. - // 3. Check whether method is static and use the declaring class. Doesn't handle all - // cases, but handles the most common ones. - TypeMirror receiverType = method.getReceiverType(); - // If the method is static, issue no warning. This is incorrect in the case of a - // constructor or a static method in an inner class. - if (!ElementUtils.isStatic(method)) { - receiverType = ElementUtils.getType(ElementUtils.enclosingTypeElement(method)); - } - if (receiverType != null - && receiverType.getKind() != TypeKind.NONE - && !canBeConstant(receiverType)) { - checker.reportError( - tree, - "statically.executable.nonconstant.parameter.type", - "this (the receiver)", - returnType); - } - - for (VariableElement param : method.getParameters()) { - TypeMirror paramType = param.asType(); - if (paramType.getKind() != TypeKind.NONE && !canBeConstant(paramType)) { - checker.reportError( - tree, - "statically.executable.nonconstant.parameter.type", - param.getSimpleName().toString(), - returnType); + /** + * Returns true if an expression of the given type can be a compile-time constant value. + * + * @param tm a type + * @return true if an expression of the given type can be a compile-time constant value + */ + private boolean canBeConstant(TypeMirror tm) { + return TypesUtils.isPrimitive(tm) + || TypesUtils.isBoxedPrimitive(tm) + || TypesUtils.isString(tm) + || (tm.getKind() == TypeKind.ARRAY + && canBeConstant(((ArrayType) tm).getComponentType())); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + super.visitMethod(tree, p); + + ExecutableElement method = TreeUtils.elementFromDeclaration(tree); + if (atypeFactory.getDeclAnnotation(method, StaticallyExecutable.class) != null) { + // The method is annotated as @StaticallyExecutable. + if (atypeFactory.getDeclAnnotation(method, Pure.class) == null) { + checker.reportWarning(tree, "statically.executable.not.pure"); + } + TypeMirror returnType = method.getReturnType(); + if (returnType.getKind() != TypeKind.VOID && !canBeConstant(returnType)) { + checker.reportError( + tree, "statically.executable.nonconstant.return.type", returnType); + } + + // Ways to determine the receiver type. + // 1. This definition of receiverType is null when receiver is implicit and method has + // class com.sun.tools.javac.code.Symbol$MethodSymbol. WHY? + // TypeMirror receiverType = method.getReceiverType(); + // The same is true of TreeUtils.elementFromDeclaration(tree).getReceiverType() + // which seems to conflict with ExecutableType's documentation. + // 2. Can't use the tree, because the receiver might not be explicit. + // 3. Check whether method is static and use the declaring class. Doesn't handle all + // cases, but handles the most common ones. + TypeMirror receiverType = method.getReceiverType(); + // If the method is static, issue no warning. This is incorrect in the case of a + // constructor or a static method in an inner class. + if (!ElementUtils.isStatic(method)) { + receiverType = ElementUtils.getType(ElementUtils.enclosingTypeElement(method)); + } + if (receiverType != null + && receiverType.getKind() != TypeKind.NONE + && !canBeConstant(receiverType)) { + checker.reportError( + tree, + "statically.executable.nonconstant.parameter.type", + "this (the receiver)", + returnType); + } + + for (VariableElement param : method.getParameters()) { + TypeMirror paramType = param.asType(); + if (paramType.getKind() != TypeKind.NONE && !canBeConstant(paramType)) { + checker.reportError( + tree, + "statically.executable.nonconstant.parameter.type", + param.getSimpleName().toString(), + returnType); + } + } } - } + return null; } - return null; - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java b/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java index 1f1a951a93f..9818aac69df 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/ByteMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ByteMath extends NumberMath { - byte number; + byte number; - public ByteMath(byte i) { - number = i; - } - - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); + public ByteMath(byte i) { + number = i; } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); + } + if (right instanceof Double) { + return number + right.doubleValue(); + } + if (right instanceof Float) { + return number + right.floatValue(); + } + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); + } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); + } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); + } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); + } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); + } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); - } - - @Override - public Number unaryPlus() { - return +number; - } - - @Override - public Number unaryMinus() { - return -number; - } - @Override - public Number bitwiseComplement() { - return ~number; - } - - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); + } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); + @Override + public Number unaryPlus() { + return +number; } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); + @Override + public Number unaryMinus() { + return -number; } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); + @Override + public Number bitwiseComplement() { + return ~number; } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); + } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number <= right.floatValue(); + + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number <= right.intValue(); + + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number <= right.longValue(); + + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number <= right.shortValue(); + + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java b/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java index f4dbd76d136..38d7c9f0a2f 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/DoubleMath.java @@ -1,307 +1,307 @@ package org.checkerframework.common.value.util; public class DoubleMath extends NumberMath { - double number; + double number; - public DoubleMath(double i) { - number = i; - } - - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); + public DoubleMath(double i) { + number = i; } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); + } + if (right instanceof Double) { + return number + right.doubleValue(); + } + if (right instanceof Float) { + return number + right.floatValue(); + } + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); + } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number divide(Number right) { - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number remainder(Number right) { - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); + @Override + public Number divide(Number right) { + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); - } - - @Override - public Number shiftLeft(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number signedShiftRight(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number unsignedShiftRight(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number bitwiseAnd(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number bitwiseXor(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number bitwiseOr(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number unaryPlus() { - return +number; - } - @Override - public Number unaryMinus() { - return -number; - } - - @Override - public Number bitwiseComplement() { - throw new UnsupportedOperationException(); - } - - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); + @Override + public Number remainder(Number right) { + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); + @Override + public Number shiftLeft(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); + @Override + public Number signedShiftRight(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); + @Override + public Number unsignedShiftRight(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); + @Override + public Number bitwiseAnd(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Double) { - return number < right.doubleValue(); + + @Override + public Number bitwiseXor(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number < right.floatValue(); + + @Override + public Number bitwiseOr(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number < right.intValue(); + + @Override + public Number unaryPlus() { + return +number; } - if (right instanceof Long) { - return number < right.longValue(); + + @Override + public Number unaryMinus() { + return -number; } - if (right instanceof Short) { - return number < right.shortValue(); + + @Override + public Number bitwiseComplement() { + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Double) { - return number <= right.doubleValue(); + + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); + } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number <= right.floatValue(); + + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number <= right.intValue(); + + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number <= right.longValue(); + + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number <= right.shortValue(); + + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java b/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java index 7e132137564..1fea5819a33 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/FloatMath.java @@ -1,307 +1,307 @@ package org.checkerframework.common.value.util; public class FloatMath extends NumberMath { - float number; + float number; - public FloatMath(float i) { - number = i; - } - - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); + public FloatMath(float i) { + number = i; } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); + } + if (right instanceof Double) { + return number + right.doubleValue(); + } + if (right instanceof Float) { + return number + right.floatValue(); + } + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); + } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number - right.longValue(); - } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); - } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number divide(Number right) { - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); - } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number remainder(Number right) { - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); - } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); + @Override + public Number divide(Number right) { + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); - } - - @Override - public Number shiftLeft(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number signedShiftRight(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number unsignedShiftRight(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number bitwiseAnd(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number bitwiseXor(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number bitwiseOr(Number right) { - throw new UnsupportedOperationException(); - } - - @Override - public Number unaryPlus() { - return +number; - } - @Override - public Number unaryMinus() { - return -number; - } - - @Override - public Number bitwiseComplement() { - throw new UnsupportedOperationException(); - } - - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); - } - if (right instanceof Float) { - return number == right.floatValue(); + @Override + public Number remainder(Number right) { + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); + @Override + public Number shiftLeft(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); - } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); + @Override + public Number signedShiftRight(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); - } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); + @Override + public Number unsignedShiftRight(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); + @Override + public Number bitwiseAnd(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Double) { - return number < right.doubleValue(); + + @Override + public Number bitwiseXor(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number < right.floatValue(); + + @Override + public Number bitwiseOr(Number right) { + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number < right.intValue(); + + @Override + public Number unaryPlus() { + return +number; } - if (right instanceof Long) { - return number < right.longValue(); + + @Override + public Number unaryMinus() { + return -number; } - if (right instanceof Short) { - return number < right.shortValue(); + + @Override + public Number bitwiseComplement() { + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Double) { - return number <= right.doubleValue(); + + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); + } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number <= right.floatValue(); + + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number <= right.intValue(); + + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number <= right.longValue(); + + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number <= right.shortValue(); + + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java b/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java index e6f208293d4..b1ef0e3a9a2 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/IntegerMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class IntegerMath extends NumberMath { - int number; + int number; - public IntegerMath(int i) { - number = i; - } - - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); + public IntegerMath(int i) { + number = i; } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); + } + if (right instanceof Double) { + return number + right.doubleValue(); + } + if (right instanceof Float) { + return number + right.floatValue(); + } + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); + } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); + } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); + } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); + } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); + } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); - } - - @Override - public Number unaryPlus() { - return +number; - } - - @Override - public Number unaryMinus() { - return -number; - } - @Override - public Number bitwiseComplement() { - return ~number; - } - - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); + } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); + @Override + public Number unaryPlus() { + return +number; } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); + @Override + public Number unaryMinus() { + return -number; } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); + @Override + public Number bitwiseComplement() { + return ~number; } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); + } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number <= right.floatValue(); + + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number <= right.intValue(); + + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number <= right.longValue(); + + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number <= right.shortValue(); + + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java b/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java index a746fcc0462..15ebfb3e257 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/LongMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class LongMath extends NumberMath { - long number; + long number; - public LongMath(long i) { - number = i; - } - - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); + public LongMath(long i) { + number = i; } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); + } + if (right instanceof Double) { + return number + right.doubleValue(); + } + if (right instanceof Float) { + return number + right.floatValue(); + } + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); + } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); + } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); + } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); + } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); + } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); - } - - @Override - public Number unaryPlus() { - return +number; - } - - @Override - public Number unaryMinus() { - return -number; - } - @Override - public Number bitwiseComplement() { - return ~number; - } - - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); + } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); + @Override + public Number unaryPlus() { + return +number; } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); + @Override + public Number unaryMinus() { + return -number; } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); + @Override + public Number bitwiseComplement() { + return ~number; } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); + } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number <= right.floatValue(); + + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number <= right.intValue(); + + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number <= right.longValue(); + + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number <= right.shortValue(); + + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java b/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java index 742331ceced..3d50a0633ad 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/NumberMath.java @@ -3,86 +3,86 @@ import org.checkerframework.checker.nullness.qual.Nullable; public abstract class NumberMath { - public static @Nullable NumberMath getNumberMath(Number number) { - if (number instanceof Byte) { - return new ByteMath(number.byteValue()); + public static @Nullable NumberMath getNumberMath(Number number) { + if (number instanceof Byte) { + return new ByteMath(number.byteValue()); + } + if (number instanceof Double) { + return new DoubleMath(number.doubleValue()); + } + if (number instanceof Float) { + return new FloatMath(number.floatValue()); + } + if (number instanceof Integer) { + return new IntegerMath(number.intValue()); + } + if (number instanceof Long) { + return new LongMath(number.longValue()); + } + if (number instanceof Short) { + return new ShortMath(number.shortValue()); + } + return null; } - if (number instanceof Double) { - return new DoubleMath(number.doubleValue()); - } - if (number instanceof Float) { - return new FloatMath(number.floatValue()); - } - if (number instanceof Integer) { - return new IntegerMath(number.intValue()); - } - if (number instanceof Long) { - return new LongMath(number.longValue()); - } - if (number instanceof Short) { - return new ShortMath(number.shortValue()); - } - return null; - } - public abstract Number plus(Number right); + public abstract Number plus(Number right); - public abstract Number minus(Number right); + public abstract Number minus(Number right); - public abstract Number times(Number right); + public abstract Number times(Number right); - /** - * Returns the result of dividing the {@code this} by {@code right}. If {@code right} is zero and - * this is an integer division, {@code null} is returned. - */ - public abstract @Nullable Number divide(Number right); + /** + * Returns the result of dividing the {@code this} by {@code right}. If {@code right} is zero + * and this is an integer division, {@code null} is returned. + */ + public abstract @Nullable Number divide(Number right); - /** - * Returns the result of {@code this % right}. If {@code right} is zero and this is an integer - * remainder, {@code null} is returned. - */ - public abstract @Nullable Number remainder(Number right); + /** + * Returns the result of {@code this % right}. If {@code right} is zero and this is an integer + * remainder, {@code null} is returned. + */ + public abstract @Nullable Number remainder(Number right); - public abstract Number shiftLeft(Number right); + public abstract Number shiftLeft(Number right); - public abstract Number signedShiftRight(Number right); + public abstract Number signedShiftRight(Number right); - public abstract Number unsignedShiftRight(Number right); + public abstract Number unsignedShiftRight(Number right); - public abstract Number bitwiseAnd(Number right); + public abstract Number bitwiseAnd(Number right); - public abstract Number bitwiseOr(Number right); + public abstract Number bitwiseOr(Number right); - public abstract Number bitwiseXor(Number right); + public abstract Number bitwiseXor(Number right); - public abstract Number unaryPlus(); + public abstract Number unaryPlus(); - public abstract Number unaryMinus(); + public abstract Number unaryMinus(); - public abstract Number bitwiseComplement(); + public abstract Number bitwiseComplement(); - public abstract Boolean equalTo(Number right); + public abstract Boolean equalTo(Number right); - public abstract Boolean notEqualTo(Number right); + public abstract Boolean notEqualTo(Number right); - public abstract Boolean greaterThan(Number right); + public abstract Boolean greaterThan(Number right); - public abstract Boolean greaterThanEq(Number right); + public abstract Boolean greaterThanEq(Number right); - public abstract Boolean lessThan(Number right); + public abstract Boolean lessThan(Number right); - public abstract Boolean lessThanEq(Number right); + public abstract Boolean lessThanEq(Number right); - public static boolean isIntegralZero(Number number) { - if (number instanceof Byte) { - return number.byteValue() == 0; - } else if (number instanceof Integer) { - return number.intValue() == 0; - } else if (number instanceof Long) { - return number.longValue() == 0; - } else if (number instanceof Short) { - return number.shortValue() == 0; + public static boolean isIntegralZero(Number number) { + if (number instanceof Byte) { + return number.byteValue() == 0; + } else if (number instanceof Integer) { + return number.intValue() == 0; + } else if (number instanceof Long) { + return number.longValue() == 0; + } else if (number instanceof Short) { + return number.shortValue() == 0; + } + return false; } - return false; - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java b/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java index b2e2d3fc33d..9c0b32fec3c 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/NumberUtils.java @@ -1,155 +1,157 @@ package org.checkerframework.common.value.util; -import java.util.List; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.TypeKindUtils; import org.plumelib.util.CollectionsPlume; -/** Utility routines for manipulating numbers. */ -public class NumberUtils { +import java.util.List; - /** Do not instantiate. */ - private NumberUtils() { - throw new Error("Do not instantiate"); - } +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; - /** - * Converts a {@code List} to a {@code List}, where A and B are numeric types. - * - * @param type the type to cast to - * @param numbers the numbers to cast to the given type - * @return a list of numbers of the given type - */ - public static List castNumbers( - TypeMirror type, List numbers) { - return castNumbers(type, false, numbers); - } +/** Utility routines for manipulating numbers. */ +public class NumberUtils { - /** - * Converts a {@code List} to a {@code List}, where A and B are numeric types. - * - * @param type the type to cast to - * @param isUnsigned if true, treat {@code type} as unsigned - * @param numbers the numbers to cast to the given type - * @return a list of numbers of the given type - */ - @SuppressWarnings("unchecked") - public static @Nullable List castNumbers( - TypeMirror type, boolean isUnsigned, List numbers) { - if (numbers == null) { - return null; + /** Do not instantiate. */ + private NumberUtils() { + throw new Error("Do not instantiate"); } - TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - if (typeKind == null) { - throw new UnsupportedOperationException(type.toString()); + + /** + * Converts a {@code List} to a {@code List}, where A and B are numeric types. + * + * @param type the type to cast to + * @param numbers the numbers to cast to the given type + * @return a list of numbers of the given type + */ + public static List castNumbers( + TypeMirror type, List numbers) { + return castNumbers(type, false, numbers); } - switch (typeKind) { - case BYTE: - if (isUnsigned) { - return CollectionsPlume.mapList( - NumberUtils::byteValueUnsigned, (Iterable) numbers); - } else { - return CollectionsPlume.mapList(Number::byteValue, numbers); + + /** + * Converts a {@code List} to a {@code List}, where A and B are numeric types. + * + * @param type the type to cast to + * @param isUnsigned if true, treat {@code type} as unsigned + * @param numbers the numbers to cast to the given type + * @return a list of numbers of the given type + */ + @SuppressWarnings("unchecked") + public static @Nullable List castNumbers( + TypeMirror type, boolean isUnsigned, List numbers) { + if (numbers == null) { + return null; } - case CHAR: - return CollectionsPlume.mapList(Number::intValue, numbers); - case DOUBLE: - return CollectionsPlume.mapList(Number::doubleValue, numbers); - case FLOAT: - return CollectionsPlume.mapList(Number::floatValue, numbers); - case INT: - if (isUnsigned) { - return CollectionsPlume.mapList( - NumberUtils::intValueUnsigned, (Iterable) numbers); - } else { - return CollectionsPlume.mapList(Number::intValue, numbers); + TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + if (typeKind == null) { + throw new UnsupportedOperationException(type.toString()); } - case LONG: - return CollectionsPlume.mapList(Number::longValue, numbers); - case SHORT: - if (isUnsigned) { - return CollectionsPlume.mapList( - NumberUtils::shortValueUnsigned, (Iterable) numbers); - } else { - return CollectionsPlume.mapList(Number::shortValue, numbers); + switch (typeKind) { + case BYTE: + if (isUnsigned) { + return CollectionsPlume.mapList( + NumberUtils::byteValueUnsigned, (Iterable) numbers); + } else { + return CollectionsPlume.mapList(Number::byteValue, numbers); + } + case CHAR: + return CollectionsPlume.mapList(Number::intValue, numbers); + case DOUBLE: + return CollectionsPlume.mapList(Number::doubleValue, numbers); + case FLOAT: + return CollectionsPlume.mapList(Number::floatValue, numbers); + case INT: + if (isUnsigned) { + return CollectionsPlume.mapList( + NumberUtils::intValueUnsigned, (Iterable) numbers); + } else { + return CollectionsPlume.mapList(Number::intValue, numbers); + } + case LONG: + return CollectionsPlume.mapList(Number::longValue, numbers); + case SHORT: + if (isUnsigned) { + return CollectionsPlume.mapList( + NumberUtils::shortValueUnsigned, (Iterable) numbers); + } else { + return CollectionsPlume.mapList(Number::shortValue, numbers); + } + default: + throw new UnsupportedOperationException(typeKind + ": " + type); } - default: - throw new UnsupportedOperationException(typeKind + ": " + type); } - } - /** - * Returns the given number, casted to unsigned byte. - * - * @param n a number - * @return the given number, casted to unsigned byte - */ - private static Short byteValueUnsigned(Number n) { - short result = n.byteValue(); - if (result < 0) { - result = (short) (result + 256); + /** + * Returns the given number, casted to unsigned byte. + * + * @param n a number + * @return the given number, casted to unsigned byte + */ + private static Short byteValueUnsigned(Number n) { + short result = n.byteValue(); + if (result < 0) { + result = (short) (result + 256); + } + return result; } - return result; - } - /** - * Returns the given number, casted to unsigned short. - * - * @param n a number - * @return the given number, casted to unsigned short - */ - private static Integer shortValueUnsigned(Number n) { - int result = n.shortValue(); - if (result < 0) { - result = result + 65536; + /** + * Returns the given number, casted to unsigned short. + * + * @param n a number + * @return the given number, casted to unsigned short + */ + private static Integer shortValueUnsigned(Number n) { + int result = n.shortValue(); + if (result < 0) { + result = result + 65536; + } + return result; } - return result; - } - /** - * Returns the given number, casted to unsigned int. - * - * @param n a number - * @return the given number, casted to unsigned int - */ - private static Long intValueUnsigned(Number n) { - long result = n.intValue(); - if (result < 0) { - result = result + 4294967296L; + /** + * Returns the given number, casted to unsigned int. + * + * @param n a number + * @return the given number, casted to unsigned int + */ + private static Long intValueUnsigned(Number n) { + long result = n.intValue(); + if (result < 0) { + result = result + 4294967296L; + } + return result; } - return result; - } - /** - * Return a range that restricts the given range to the given type. That is, return the range - * resulting from casting a value with the given range. - * - * @param type the type for the cast; the result will be within it - * @param range the original range; the result will be within it - * @return the intersection of the given range and the possible values of the given type - */ - public static Range castRange(TypeMirror type, Range range) { - TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - if (typeKind == null) { - throw new UnsupportedOperationException(type.toString()); - } - switch (typeKind) { - case BYTE: - return range.byteRange(); - case CHAR: - return range.charRange(); - case SHORT: - return range.shortRange(); - case INT: - return range.intRange(); - case LONG: - case FLOAT: - case DOUBLE: - return range; - default: - throw new UnsupportedOperationException(typeKind + ": " + type); + /** + * Return a range that restricts the given range to the given type. That is, return the range + * resulting from casting a value with the given range. + * + * @param type the type for the cast; the result will be within it + * @param range the original range; the result will be within it + * @return the intersection of the given range and the possible values of the given type + */ + public static Range castRange(TypeMirror type, Range range) { + TypeKind typeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + if (typeKind == null) { + throw new UnsupportedOperationException(type.toString()); + } + switch (typeKind) { + case BYTE: + return range.byteRange(); + case CHAR: + return range.charRange(); + case SHORT: + return range.shortRange(); + case INT: + return range.intRange(); + case LONG: + case FLOAT: + case DOUBLE: + return range; + default: + throw new UnsupportedOperationException(typeKind + ": " + type); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/Range.java b/framework/src/main/java/org/checkerframework/common/value/util/Range.java index 9f6f2a748ef..2d490e276a5 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/Range.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/Range.java @@ -1,14 +1,16 @@ package org.checkerframework.common.value.util; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.math.BigInteger; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; + import javax.lang.model.type.TypeKind; -import org.checkerframework.checker.interning.qual.InternedDistinct; -import org.checkerframework.checker.nullness.qual.Nullable; /** * The Range class models a 64-bit two's-complement integral interval, such as all integers between @@ -18,1237 +20,1242 @@ */ public class Range { - /** The lower bound of the interval, inclusive. */ - public final long from; - - /** The upper bound of the interval, inclusive. */ - public final long to; - - /** - * Should ranges take overflow into account or ignore it? - * - *
            - *
          • If {@code ignoreOverflow} is true, then operations that would result in more than the max - * value are clipped to the max value (and similarly for the min). - *
          • If {@code ignoreOverflow} is false, then operations that would result in more than the - * max wrap around according to the rules of twos-complement arithmetic and produce a - * smaller value (and similarly for the min). - *
          - * - *

          Any checker that uses this library should set this field. By default, this field is set to - * false (meaning overflow is taken into account), but a previous checker might have set it to - * true. - * - *

          A static field is used because passing an instance field throughout the class bloats the - * code. - */ - public static boolean ignoreOverflow = false; - - /** A range containing all possible 64-bit values. */ - public static final Range LONG_EVERYTHING = create(Long.MIN_VALUE, Long.MAX_VALUE); - - /** Long.MIN_VALUE, as a BigInteger. */ - private static final BigInteger BIG_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE); - - /** Long.MAX_VALUE, as a BigInteger. */ - private static final BigInteger BIG_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE); - - /** The number of Long values, as a BigInteger. */ - private static final BigInteger BIG_LONG_WIDTH = - BIG_LONG_MAX_VALUE.subtract(BIG_LONG_MIN_VALUE).add(BigInteger.ONE); - - /** A range containing all possible 32-bit values. */ - public static final Range INT_EVERYTHING = create(Integer.MIN_VALUE, Integer.MAX_VALUE); - - /** The number of values representable in 32 bits: 2^32 or {@code 1<<32}. */ - private static final long INT_WIDTH = INT_EVERYTHING.width(); - - /** A range containing all possible 16-bit values. */ - public static final Range SHORT_EVERYTHING = create(Short.MIN_VALUE, Short.MAX_VALUE); - - /** The number of values representable in 16 bits: 2^16 or 1<<16. */ - private static final long SHORT_WIDTH = SHORT_EVERYTHING.width(); - - /** A range containing all possible char values. */ - public static final Range CHAR_EVERYTHING = create(Character.MIN_VALUE, Character.MAX_VALUE); - - /** The number of values representable in char: */ - private static final long CHAR_WIDTH = CHAR_EVERYTHING.width(); - - /** A range containing all possible 8-bit values. */ - public static final Range BYTE_EVERYTHING = create(Byte.MIN_VALUE, Byte.MAX_VALUE); - - /** The number of values representable in 8 bits: 2^8 or 1<<8. */ - private static final long BYTE_WIDTH = BYTE_EVERYTHING.width(); - - /** The empty range. This is the only Range object that contains nothing */ - @SuppressWarnings( - "interning:assignment.type.incompatible") // no other constructor call makes this - public static final @InternedDistinct Range NOTHING = new Range(Long.MAX_VALUE, Long.MIN_VALUE); - - /** An alias to the range containing all possible 64-bit values. */ - public static final Range EVERYTHING = LONG_EVERYTHING; - - /** - * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. - * - *

          This is a private constructor that does no validation of arguments, so special instances - * (e.g., {@link #NOTHING}) can be created through it. - * - * @param from the lower bound (inclusive) - * @param to the upper bound (inclusive) - */ - private Range(long from, long to) { - this.from = from; - this.to = to; - } - - /** - * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. - * Requires {@code from <= to}. - * - * @param from the lower bound (inclusive) - * @param to the upper bound (inclusive) - * @return the Range [from..to] - */ - public static Range create(long from, long to) { - if (!(from <= to)) { - throw new IllegalArgumentException(String.format("Invalid Range: %s %s", from, to)); - } - return new Range(from, to); - } - - /** - * Create a Range from a collection of Numbers. - * - * @param values collection whose min and max values will be used as the range's from and to - * values - * @return a range that encompasses all the argument's values ({@link #NOTHING} if the argument is - * an empty collection) - */ - public static Range create(Collection values) { - if (values.isEmpty()) { - return NOTHING; - } - long min = values.iterator().next().longValue(); - long max = min; - for (Number value : values) { - long current = value.longValue(); - if (min > current) min = current; - if (max < current) max = current; + /** The lower bound of the interval, inclusive. */ + public final long from; + + /** The upper bound of the interval, inclusive. */ + public final long to; + + /** + * Should ranges take overflow into account or ignore it? + * + *

            + *
          • If {@code ignoreOverflow} is true, then operations that would result in more than the + * max value are clipped to the max value (and similarly for the min). + *
          • If {@code ignoreOverflow} is false, then operations that would result in more than the + * max wrap around according to the rules of twos-complement arithmetic and produce a + * smaller value (and similarly for the min). + *
          + * + *

          Any checker that uses this library should set this field. By default, this field is set to + * false (meaning overflow is taken into account), but a previous checker might have set it to + * true. + * + *

          A static field is used because passing an instance field throughout the class bloats the + * code. + */ + public static boolean ignoreOverflow = false; + + /** A range containing all possible 64-bit values. */ + public static final Range LONG_EVERYTHING = create(Long.MIN_VALUE, Long.MAX_VALUE); + + /** Long.MIN_VALUE, as a BigInteger. */ + private static final BigInteger BIG_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE); + + /** Long.MAX_VALUE, as a BigInteger. */ + private static final BigInteger BIG_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE); + + /** The number of Long values, as a BigInteger. */ + private static final BigInteger BIG_LONG_WIDTH = + BIG_LONG_MAX_VALUE.subtract(BIG_LONG_MIN_VALUE).add(BigInteger.ONE); + + /** A range containing all possible 32-bit values. */ + public static final Range INT_EVERYTHING = create(Integer.MIN_VALUE, Integer.MAX_VALUE); + + /** The number of values representable in 32 bits: 2^32 or {@code 1<<32}. */ + private static final long INT_WIDTH = INT_EVERYTHING.width(); + + /** A range containing all possible 16-bit values. */ + public static final Range SHORT_EVERYTHING = create(Short.MIN_VALUE, Short.MAX_VALUE); + + /** The number of values representable in 16 bits: 2^16 or 1<<16. */ + private static final long SHORT_WIDTH = SHORT_EVERYTHING.width(); + + /** A range containing all possible char values. */ + public static final Range CHAR_EVERYTHING = create(Character.MIN_VALUE, Character.MAX_VALUE); + + /** The number of values representable in char: */ + private static final long CHAR_WIDTH = CHAR_EVERYTHING.width(); + + /** A range containing all possible 8-bit values. */ + public static final Range BYTE_EVERYTHING = create(Byte.MIN_VALUE, Byte.MAX_VALUE); + + /** The number of values representable in 8 bits: 2^8 or 1<<8. */ + private static final long BYTE_WIDTH = BYTE_EVERYTHING.width(); + + /** The empty range. This is the only Range object that contains nothing */ + @SuppressWarnings( + "interning:assignment.type.incompatible") // no other constructor call makes this + public static final @InternedDistinct Range NOTHING = new Range(Long.MAX_VALUE, Long.MIN_VALUE); + + /** An alias to the range containing all possible 64-bit values. */ + public static final Range EVERYTHING = LONG_EVERYTHING; + + /** + * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. + * + *

          This is a private constructor that does no validation of arguments, so special instances + * (e.g., {@link #NOTHING}) can be created through it. + * + * @param from the lower bound (inclusive) + * @param to the upper bound (inclusive) + */ + private Range(long from, long to) { + this.from = from; + this.to = to; } - return create(min, max); - } - - /** - * Returns a Range representing all possible values for the given primitive type. - * - * @param typeKind one of INT, SHORT, BYTE, CHAR, or LONG - * @return the range for the given primitive type - */ - public static Range create(TypeKind typeKind) { - switch (typeKind) { - case INT: - return INT_EVERYTHING; - case SHORT: - return SHORT_EVERYTHING; - case BYTE: - return BYTE_EVERYTHING; - case CHAR: - return CHAR_EVERYTHING; - case LONG: - return LONG_EVERYTHING; - default: - throw new IllegalArgumentException( - "Invalid TypeKind for Range: expected INT, SHORT, BYTE, CHAR, or LONG, got " - + typeKind); - } - } - - /** - * Creates a range using BigInteger type bounds. - * - *

          If the BigInteger range is wider than the full range of the Long class, return EVERYTHING. - * - *

          If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is false, - * convert the bounds to Long type in accordance with Java twos-complement overflow rules, e.g., - * Long.MAX_VALUE + 1 is converted to Long.MIN_VALUE. - * - *

          If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is true, - * convert the bound that is outside Long's range to max/min value of a Long. - * - * @param bigFrom the lower bound of the BigInteger range - * @param bigTo the upper bound of the BigInteger range - * @return a range with Long type bounds converted from the BigInteger range - */ - private static Range create(BigInteger bigFrom, BigInteger bigTo) { - if (ignoreOverflow) { - bigFrom = bigFrom.max(BIG_LONG_MIN_VALUE); - bigTo = bigTo.min(BIG_LONG_MAX_VALUE); - } else { - BigInteger bigWidth = bigTo.subtract(bigFrom).add(BigInteger.ONE); - if (bigWidth.compareTo(BIG_LONG_WIDTH) > 0) { - return EVERYTHING; - } - } - long longFrom = bigFrom.longValue(); - long longTo = bigTo.longValue(); - return createOrElse(longFrom, longTo, EVERYTHING); - } - - /** - * Creates a Range if {@code from<=to}; otherwise returns the given Range value. - * - * @param from lower bound for the range - * @param to upper bound for the range - * @param alternate what to return if {@code from > to} - * @return a new Range [from..to], or {@code alternate} - */ - private static Range createOrElse(long from, long to, Range alternate) { - if (from <= to) { - return new Range(from, to); - } else { - return alternate; + + /** + * Constructs a range with its bounds specified by two parameters, {@code from} and {@code to}. + * Requires {@code from <= to}. + * + * @param from the lower bound (inclusive) + * @param to the upper bound (inclusive) + * @return the Range [from..to] + */ + public static Range create(long from, long to) { + if (!(from <= to)) { + throw new IllegalArgumentException(String.format("Invalid Range: %s %s", from, to)); + } + return new Range(from, to); } - } - - /** - * Returns a range with its bounds specified by two parameters, {@code from} and {@code to}. If - * {@code from} is greater than {@code to}, returns {@link #NOTHING}. - * - * @param from the lower bound (inclusive) - * @param to the upper bound (inclusive) - * @return newly-created Range or NOTHING - */ - private static Range createOrNothing(long from, long to) { - return createOrElse(from, to, NOTHING); - } - - /** - * Returns the number of values in this range. - * - * @return how many values are in the range - */ - private long width() { - return to - from + 1; - } - - @Override - public String toString() { - if (this.isNothing()) { - return "[]"; - } else { - return String.format("[%s..%s]", from, to); + + /** + * Create a Range from a collection of Numbers. + * + * @param values collection whose min and max values will be used as the range's from and to + * values + * @return a range that encompasses all the argument's values ({@link #NOTHING} if the argument + * is an empty collection) + */ + public static Range create(Collection values) { + if (values.isEmpty()) { + return NOTHING; + } + long min = values.iterator().next().longValue(); + long max = min; + for (Number value : values) { + long current = value.longValue(); + if (min > current) min = current; + if (max < current) max = current; + } + return create(min, max); } - } - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; + /** + * Returns a Range representing all possible values for the given primitive type. + * + * @param typeKind one of INT, SHORT, BYTE, CHAR, or LONG + * @return the range for the given primitive type + */ + public static Range create(TypeKind typeKind) { + switch (typeKind) { + case INT: + return INT_EVERYTHING; + case SHORT: + return SHORT_EVERYTHING; + case BYTE: + return BYTE_EVERYTHING; + case CHAR: + return CHAR_EVERYTHING; + case LONG: + return LONG_EVERYTHING; + default: + throw new IllegalArgumentException( + "Invalid TypeKind for Range: expected INT, SHORT, BYTE, CHAR, or LONG, got " + + typeKind); + } } - if (obj instanceof Range) { - return equalsRange((Range) obj); + + /** + * Creates a range using BigInteger type bounds. + * + *

          If the BigInteger range is wider than the full range of the Long class, return EVERYTHING. + * + *

          If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is + * false, convert the bounds to Long type in accordance with Java twos-complement overflow + * rules, e.g., Long.MAX_VALUE + 1 is converted to Long.MIN_VALUE. + * + *

          If one of the BigInteger bounds is out of Long's range and {@link #ignoreOverflow} is + * true, convert the bound that is outside Long's range to max/min value of a Long. + * + * @param bigFrom the lower bound of the BigInteger range + * @param bigTo the upper bound of the BigInteger range + * @return a range with Long type bounds converted from the BigInteger range + */ + private static Range create(BigInteger bigFrom, BigInteger bigTo) { + if (ignoreOverflow) { + bigFrom = bigFrom.max(BIG_LONG_MIN_VALUE); + bigTo = bigTo.min(BIG_LONG_MAX_VALUE); + } else { + BigInteger bigWidth = bigTo.subtract(bigFrom).add(BigInteger.ONE); + if (bigWidth.compareTo(BIG_LONG_WIDTH) > 0) { + return EVERYTHING; + } + } + long longFrom = bigFrom.longValue(); + long longTo = bigTo.longValue(); + return createOrElse(longFrom, longTo, EVERYTHING); } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(from, to); - } - - /** - * Compare two ranges in a type safe manner for equality without incurring the cost of an - * instanceof check such as equals(Object) does. - * - * @param range to compare against - * @return true for ranges that match from and to respectively - */ - private boolean equalsRange(Range range) { - return from == range.from && to == range.to; - } - - /** Return true if this range contains every {@code long} value. */ - public boolean isLongEverything() { - return equalsRange(LONG_EVERYTHING); - } - - /** Return true if this range contains every {@code int} value. */ - public boolean isIntEverything() { - return equalsRange(INT_EVERYTHING); - } - - /** Return true if this range contains every {@code short} value. */ - public boolean isShortEverything() { - return equalsRange(SHORT_EVERYTHING); - } - - /** Return true if this range contains every {@code char} value. */ - public boolean isCharEverything() { - return equalsRange(CHAR_EVERYTHING); - } - - /** Return true if this range contains every {@code byte} value. */ - public boolean isByteEverything() { - return equalsRange(BYTE_EVERYTHING); - } - - /** Return true if this range contains no values. */ - public boolean isNothing() { - return this == NOTHING; - } - - /** - * Converts this range to a 32-bit integral range. - * - *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Integer range, then - * that bound is set to the bound of the Integer range. - * - *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Integer class, return INT_EVERYTHING. - * - *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 32-bit integers, convert the bounds to Integer type in accordance with Java twos-complement - * overflow rules, e.g., Integer.MAX_VALUE + 1 is converted to Integer.MIN_VALUE. - * - * @return this range, converted to a 32-bit integral range - */ - @SuppressWarnings("UnnecessaryLongToIntConversion") - public Range intRange() { - if (this.isNothing()) { - return this; + + /** + * Creates a Range if {@code from<=to}; otherwise returns the given Range value. + * + * @param from lower bound for the range + * @param to upper bound for the range + * @param alternate what to return if {@code from > to} + * @return a new Range [from..to], or {@code alternate} + */ + private static Range createOrElse(long from, long to, Range alternate) { + if (from <= to) { + return new Range(from, to); + } else { + return alternate; + } } - if (INT_EVERYTHING.contains(this)) { - return this; + + /** + * Returns a range with its bounds specified by two parameters, {@code from} and {@code to}. If + * {@code from} is greater than {@code to}, returns {@link #NOTHING}. + * + * @param from the lower bound (inclusive) + * @param to the upper bound (inclusive) + * @return newly-created Range or NOTHING + */ + private static Range createOrNothing(long from, long to) { + return createOrElse(from, to, NOTHING); } - if (ignoreOverflow) { - return create(clipToRange(from, INT_EVERYTHING), clipToRange(to, INT_EVERYTHING)); + + /** + * Returns the number of values in this range. + * + * @return how many values are in the range + */ + private long width() { + return to - from + 1; } - if (this.isWiderThan(INT_WIDTH)) { - return INT_EVERYTHING; + + @Override + public String toString() { + if (this.isNothing()) { + return "[]"; + } else { + return String.format("[%s..%s]", from, to); + } } - return createOrElse((int) this.from, (int) this.to, INT_EVERYTHING); - } - - /** - * Converts a this range to a 16-bit short range. - * - *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Short range, then - * that bound is set to the bound of the Short range. - * - *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Short class, return SHORT_EVERYTHING. - * - *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 16-bit integers, convert the bounds to Short type in accordance with Java twos-complement - * overflow rules, e.g., Short.MAX_VALUE + 1 is converted to Short.MIN_VALUE. - * - * @return this range, converted to a 16-bit short range - */ - public Range shortRange() { - if (this.isNothing()) { - return this; + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Range) { + return equalsRange((Range) obj); + } + return false; } - if (SHORT_EVERYTHING.contains(this)) { - return this; + + @Override + public int hashCode() { + return Objects.hash(from, to); } - if (ignoreOverflow) { - return create(clipToRange(from, SHORT_EVERYTHING), clipToRange(to, SHORT_EVERYTHING)); + + /** + * Compare two ranges in a type safe manner for equality without incurring the cost of an + * instanceof check such as equals(Object) does. + * + * @param range to compare against + * @return true for ranges that match from and to respectively + */ + private boolean equalsRange(Range range) { + return from == range.from && to == range.to; } - if (this.isWiderThan(SHORT_WIDTH)) { - // short is promoted to int before the operation so no need for explicit casting - return SHORT_EVERYTHING; + + /** Return true if this range contains every {@code long} value. */ + public boolean isLongEverything() { + return equalsRange(LONG_EVERYTHING); } - return createOrElse((short) this.from, (short) this.to, SHORT_EVERYTHING); - } - - /** - * Converts this range to a char range. - * - *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Character range, - * then that bound is set to the bound of the Character range. - * - *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Character class, return CHAR_EVERYTHING. - * - *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 8-bit integers, convert the bounds to Character type in accordance with Java overflow rules - * (twos-complement), e.g., Character.MAX_VALUE + 1 is converted to Character.MIN_VALUE. - */ - public Range charRange() { - if (this.isNothing()) { - return this; + + /** Return true if this range contains every {@code int} value. */ + public boolean isIntEverything() { + return equalsRange(INT_EVERYTHING); } - if (CHAR_EVERYTHING.contains(this)) { - return this; + + /** Return true if this range contains every {@code short} value. */ + public boolean isShortEverything() { + return equalsRange(SHORT_EVERYTHING); } - if (ignoreOverflow) { - return create(clipToRange(from, CHAR_EVERYTHING), clipToRange(to, CHAR_EVERYTHING)); + + /** Return true if this range contains every {@code char} value. */ + public boolean isCharEverything() { + return equalsRange(CHAR_EVERYTHING); } - if (this.isWiderThan(CHAR_WIDTH)) { - // char is promoted to int before the operation so no need for explicit casting - return CHAR_EVERYTHING; + + /** Return true if this range contains every {@code byte} value. */ + public boolean isByteEverything() { + return equalsRange(BYTE_EVERYTHING); } - return createOrElse((char) this.from, (char) this.to, CHAR_EVERYTHING); - } - - /** - * Converts this range to an 8-bit byte range. - * - *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Byte range, then - * that bound is set to the bound of the Byte range. - * - *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full - * range of the Byte class, return BYTE_EVERYTHING. - * - *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as - * 8-bit integers, convert the bounds to Byte type in accordance with Java twos-complement - * overflow rules, e.g., Byte.MAX_VALUE + 1 is converted to Byte.MIN_VALUE. - * - * @return this range, converted to an 8-bit byte range - */ - public Range byteRange() { - if (this.isNothing()) { - return this; + + /** Return true if this range contains no values. */ + public boolean isNothing() { + return this == NOTHING; } - if (BYTE_EVERYTHING.contains(this)) { - return this; + + /** + * Converts this range to a 32-bit integral range. + * + *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Integer range, + * then that bound is set to the bound of the Integer range. + * + *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Integer class, return INT_EVERYTHING. + * + *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 32-bit integers, convert the bounds to Integer type in accordance with Java twos-complement + * overflow rules, e.g., Integer.MAX_VALUE + 1 is converted to Integer.MIN_VALUE. + * + * @return this range, converted to a 32-bit integral range + */ + @SuppressWarnings("UnnecessaryLongToIntConversion") + public Range intRange() { + if (this.isNothing()) { + return this; + } + if (INT_EVERYTHING.contains(this)) { + return this; + } + if (ignoreOverflow) { + return create(clipToRange(from, INT_EVERYTHING), clipToRange(to, INT_EVERYTHING)); + } + if (this.isWiderThan(INT_WIDTH)) { + return INT_EVERYTHING; + } + return createOrElse((int) this.from, (int) this.to, INT_EVERYTHING); } - if (ignoreOverflow) { - return create(clipToRange(from, BYTE_EVERYTHING), clipToRange(to, BYTE_EVERYTHING)); + + /** + * Converts a this range to a 16-bit short range. + * + *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Short range, then + * that bound is set to the bound of the Short range. + * + *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Short class, return SHORT_EVERYTHING. + * + *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 16-bit integers, convert the bounds to Short type in accordance with Java twos-complement + * overflow rules, e.g., Short.MAX_VALUE + 1 is converted to Short.MIN_VALUE. + * + * @return this range, converted to a 16-bit short range + */ + public Range shortRange() { + if (this.isNothing()) { + return this; + } + if (SHORT_EVERYTHING.contains(this)) { + return this; + } + if (ignoreOverflow) { + return create(clipToRange(from, SHORT_EVERYTHING), clipToRange(to, SHORT_EVERYTHING)); + } + if (this.isWiderThan(SHORT_WIDTH)) { + // short is promoted to int before the operation so no need for explicit casting + return SHORT_EVERYTHING; + } + return createOrElse((short) this.from, (short) this.to, SHORT_EVERYTHING); } - if (this.isWiderThan(BYTE_WIDTH)) { - // byte is promoted to int before the operation so no need for explicit casting - return BYTE_EVERYTHING; + + /** + * Converts this range to a char range. + * + *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Character range, + * then that bound is set to the bound of the Character range. + * + *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Character class, return CHAR_EVERYTHING. + * + *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 8-bit integers, convert the bounds to Character type in accordance with Java overflow rules + * (twos-complement), e.g., Character.MAX_VALUE + 1 is converted to Character.MIN_VALUE. + */ + public Range charRange() { + if (this.isNothing()) { + return this; + } + if (CHAR_EVERYTHING.contains(this)) { + return this; + } + if (ignoreOverflow) { + return create(clipToRange(from, CHAR_EVERYTHING), clipToRange(to, CHAR_EVERYTHING)); + } + if (this.isWiderThan(CHAR_WIDTH)) { + // char is promoted to int before the operation so no need for explicit casting + return CHAR_EVERYTHING; + } + return createOrElse((char) this.from, (char) this.to, CHAR_EVERYTHING); } - return createOrElse((byte) this.from, (byte) this.to, BYTE_EVERYTHING); - } - - /** - * Return x clipped to the given range; out-of-range values become extremal values. Appropriate - * only when {@link #ignoreOverflow} is true. - * - * @param x a value - * @param r a range - * @return a value within the range; if x is outside r, returns the min or max of r - */ - private long clipToRange(long x, Range r) { - if (x < r.from) { - return r.from; - } else if (x > r.to) { - return r.to; - } else { - return x; + + /** + * Converts this range to an 8-bit byte range. + * + *

          If {@link #ignoreOverflow} is true and one of the bounds is outside the Byte range, then + * that bound is set to the bound of the Byte range. + * + *

          If {@link #ignoreOverflow} is false and this range is too wide, i.e., wider than the full + * range of the Byte class, return BYTE_EVERYTHING. + * + *

          If {@link #ignoreOverflow} is false and the bounds of this range are not representable as + * 8-bit integers, convert the bounds to Byte type in accordance with Java twos-complement + * overflow rules, e.g., Byte.MAX_VALUE + 1 is converted to Byte.MIN_VALUE. + * + * @return this range, converted to an 8-bit byte range + */ + public Range byteRange() { + if (this.isNothing()) { + return this; + } + if (BYTE_EVERYTHING.contains(this)) { + return this; + } + if (ignoreOverflow) { + return create(clipToRange(from, BYTE_EVERYTHING), clipToRange(to, BYTE_EVERYTHING)); + } + if (this.isWiderThan(BYTE_WIDTH)) { + // byte is promoted to int before the operation so no need for explicit casting + return BYTE_EVERYTHING; + } + return createOrElse((byte) this.from, (byte) this.to, BYTE_EVERYTHING); } - } - - /** - * Returns true if the element is contained in this range. - * - * @param element the value to seek - * @return true if {@code element} is in this range - */ - public boolean contains(long element) { - return from <= element && element <= to; - } - - /** - * Returns true if the other range is contained in this range. - * - * @param other the range that might be within this one - * @return true if {@code other} is within this range - */ - public boolean contains(Range other) { - return other.isWithin(from, to); - } - - /** - * Returns the smallest range that includes all values contained in either of the two ranges. We - * call this the union of two ranges. - * - * @param right a range to union with this range - * @return a range resulting from the union of the specified range and this range - */ - public Range union(Range right) { - if (this.isNothing()) { - return right; - } else if (right.isNothing()) { - return this; + + /** + * Return x clipped to the given range; out-of-range values become extremal values. Appropriate + * only when {@link #ignoreOverflow} is true. + * + * @param x a value + * @param r a range + * @return a value within the range; if x is outside r, returns the min or max of r + */ + private long clipToRange(long x, Range r) { + if (x < r.from) { + return r.from; + } else if (x > r.to) { + return r.to; + } else { + return x; + } } - long resultFrom = Math.min(from, right.from); - long resultTo = Math.max(to, right.to); - return create(resultFrom, resultTo); - } - - /** - * Returns the smallest range that includes all values contained in both of the two ranges. We - * call this the intersection of two ranges. If there is no overlap between the two ranges, - * returns an empty range. - * - * @param right the range to intersect with this range - * @return a range resulting from the intersection of the specified range and this range - */ - public Range intersect(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * Returns true if the element is contained in this range. + * + * @param element the value to seek + * @return true if {@code element} is in this range + */ + public boolean contains(long element) { + return from <= element && element <= to; } - long resultFrom = Math.max(from, right.from); - long resultTo = Math.min(to, right.to); - return createOrNothing(resultFrom, resultTo); - } - - /** - * Returns the range with the lowest to and from values of this range and the passed range. - * - * @param other the range to compare - * @return the range with the lowest to and from values of this range and the passed range - */ - public Range min(Range other) { - return create(Math.min(this.from, other.from), Math.min(this.to, other.to)); - } - - /** - * Returns the range with the highest to and from values of this range and the passed range. - * - * @param other the range to compare - * @return the range with the highest to and from values of this range and the passed range - */ - public Range max(Range other) { - return create(Math.max(this.from, other.from), Math.max(this.to, other.to)); - } - - /** - * Returns the smallest range that includes all possible values resulting from adding an arbitrary - * value in the specified range to an arbitrary value in this range. We call this the addition of - * two ranges. - * - * @param right a range to be added to this range - * @return the range resulting from the addition of the specified range and this range - */ - public Range plus(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * Returns true if the other range is contained in this range. + * + * @param other the range that might be within this one + * @return true if {@code other} is within this range + */ + public boolean contains(Range other) { + return other.isWithin(from, to); } - if (this.isWithinHalfLong() && right.isWithinHalfLong()) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from + right.from; - long resultTo = to + right.to; - if (from > to) { - return Range.EVERYTHING; - } else { + /** + * Returns the smallest range that includes all values contained in either of the two ranges. We + * call this the union of two ranges. + * + * @param right a range to union with this range + * @return a range resulting from the union of the specified range and this range + */ + public Range union(Range right) { + if (this.isNothing()) { + return right; + } else if (right.isNothing()) { + return this; + } + + long resultFrom = Math.min(from, right.from); + long resultTo = Math.max(to, right.to); return create(resultFrom, resultTo); - } - } else { - BigInteger bigFrom = BigInteger.valueOf(from).add(BigInteger.valueOf(right.from)); - BigInteger bigTo = BigInteger.valueOf(to).add(BigInteger.valueOf(right.to)); - return create(bigFrom, bigTo); - } - } - - /** - * Returns the smallest range that includes all possible values resulting from subtracting an - * arbitrary value in the specified range from an arbitrary value in this range. We call this the - * subtraction of two ranges. - * - * @param right the range to be subtracted from this range - * @return the range resulting from subtracting the specified range from this range - */ - public Range minus(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; } - if (this.isWithinHalfLong() && right.isWithinHalfLong()) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from - right.to; - long resultTo = to - right.from; - return create(resultFrom, resultTo); - } else { - BigInteger bigFrom = BigInteger.valueOf(from).subtract(BigInteger.valueOf(right.to)); - BigInteger bigTo = BigInteger.valueOf(to).subtract(BigInteger.valueOf(right.from)); - return create(bigFrom, bigTo); - } - } - - /** - * Returns the smallest range that includes all possible values resulting from multiplying an - * arbitrary value in the specified range by an arbitrary value in this range. We call this the - * multiplication of two ranges. - * - * @param right the specified range to be multiplied by this range - * @return the range resulting from multiplying the specified range by this range - */ - public Range times(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; - } + /** + * Returns the smallest range that includes all values contained in both of the two ranges. We + * call this the intersection of two ranges. If there is no overlap between the two ranges, + * returns an empty range. + * + * @param right the range to intersect with this range + * @return a range resulting from the intersection of the specified range and this range + */ + public Range intersect(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } - // These bounds are adequate: Integer.MAX_VALUE^2 is still a bit less than Long.MAX_VALUE. - if (this.isWithinInteger() && right.isWithinInteger()) { - List possibleValues = - Arrays.asList(from * right.from, from * right.to, to * right.from, to * right.to); - return create(possibleValues); - } else { - BigInteger bigLeftFrom = BigInteger.valueOf(from); - BigInteger bigRightFrom = BigInteger.valueOf(right.from); - BigInteger bigRightTo = BigInteger.valueOf(right.to); - BigInteger bigLeftTo = BigInteger.valueOf(to); - List bigPossibleValues = - Arrays.asList( - bigLeftFrom.multiply(bigRightFrom), - bigLeftFrom.multiply(bigRightTo), - bigLeftTo.multiply(bigRightFrom), - bigLeftTo.multiply(bigRightTo)); - return create(Collections.min(bigPossibleValues), Collections.max(bigPossibleValues)); - } - } - - /** - * Returns the smallest range that includes all possible values resulting from dividing (integer - * division) an arbitrary value in this range by an arbitrary value in the specified range. We - * call this the division of two ranges. - * - * @param right the specified range by which this range is divided - * @return the range resulting from dividing this range by the specified range - */ - public Range divide(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + long resultFrom = Math.max(from, right.from); + long resultTo = Math.min(to, right.to); + return createOrNothing(resultFrom, resultTo); } - if (right.from == 0 && right.to == 0) { - return NOTHING; - } - // Special cases that involve overflow. - // The only overflow in integer division is Long.MIN_VALUE / -1 == Long.MIN_VALUE. - if (from == Long.MIN_VALUE && right.contains(-1)) { - // The values in the right range are all negative because right does not contain 0 but - // does contain 1. - if (from != to) { - // Special case 1: - // This range contains Long.MIN_VALUE and Long.MIN_VALUE + 1, which makes the - // result range EVERYTHING. - return EVERYTHING; - } else if (right.from != right.to) { - // Special case 2: - // This range contains only Long.MIN_VALUE, and the right range contains at least -1 - // and -2. The result range is from Long.MIN_VALUE to Long.MIN_VALUE / -2. - return create(Long.MIN_VALUE, Long.MIN_VALUE / -2); - } else { - // Special case 3: - // This range contains only Long.MIN_VALUE, and right contains only -1. - return create(Long.MIN_VALUE, Long.MIN_VALUE); - } - } - // We needn't worry about the overflow issue starting from here. - - // There are 9 different cases: - // (note: pos=positive, neg=negative, unk=unknown sign, np=non-positive, nn=non-negative) - long resultFrom; - long resultTo; - if (from > 0) { // this range is positive - if (right.from >= 0) { - // 1. right: nn - resultFrom = from / Math.max(right.to, 1); - resultTo = to / Math.max(right.from, 1); - } else if (right.to <= 0) { - // 2. right: np - resultFrom = to / Math.min(right.to, -1); - resultTo = from / Math.min(right.from, -1); - } else { - // 3. right: unk; values include -1 and 1 - resultFrom = -to; - resultTo = to; - } - } else if (to < 0) { // this range is negative - if (right.from >= 0) { - // 4. right: nn - resultFrom = from / Math.max(right.from, 1); - resultTo = to / Math.max(right.to, 1); - } else if (right.to <= 0) { - // 5. right: np - resultFrom = to / Math.min(right.from, -1); - resultTo = from / Math.min(right.to, -1); - } else { - // 6. right: unk; values include -1 and 1 - resultFrom = from; - resultTo = -from; - } - } else { // this range spans both signs - if (right.from >= 0) { - // 7. right: nn - resultFrom = from / Math.max(right.from, 1); - resultTo = to / Math.max(right.from, 1); - } else if (right.to <= 0) { - // 8. right: np - resultFrom = to / Math.min(right.to, -1); - resultTo = from / Math.min(right.to, -1); - } else { - // 9. right: unk; values include -1 and 1 - resultFrom = Math.min(from, -to); - resultTo = Math.max(-from, to); - } + + /** + * Returns the range with the lowest to and from values of this range and the passed range. + * + * @param other the range to compare + * @return the range with the lowest to and from values of this range and the passed range + */ + public Range min(Range other) { + return create(Math.min(this.from, other.from), Math.min(this.to, other.to)); } - return create(resultFrom, resultTo); - } - - /** - * Returns a range that includes all possible values of the remainder of dividing an arbitrary - * value in this range by an arbitrary value in the specified range. - * - *

          In the current implementation, the result might not be the smallest range that includes all - * the possible values. - * - * @param right the specified range by which this range is divided - * @return the range of the remainder of dividing this range by the specified range - */ - public Range remainder(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + + /** + * Returns the range with the highest to and from values of this range and the passed range. + * + * @param other the range to compare + * @return the range with the highest to and from values of this range and the passed range + */ + public Range max(Range other) { + return create(Math.max(this.from, other.from), Math.max(this.to, other.to)); } - if (right.from == 0 && right.to == 0) { - return NOTHING; + + /** + * Returns the smallest range that includes all possible values resulting from adding an + * arbitrary value in the specified range to an arbitrary value in this range. We call this the + * addition of two ranges. + * + * @param right a range to be added to this range + * @return the range resulting from the addition of the specified range and this range + */ + public Range plus(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + if (this.isWithinHalfLong() && right.isWithinHalfLong()) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from + right.from; + long resultTo = to + right.to; + if (from > to) { + return Range.EVERYTHING; + } else { + return create(resultFrom, resultTo); + } + } else { + BigInteger bigFrom = BigInteger.valueOf(from).add(BigInteger.valueOf(right.from)); + BigInteger bigTo = BigInteger.valueOf(to).add(BigInteger.valueOf(right.to)); + return create(bigFrom, bigTo); + } } - // Special cases that would cause overflow if we use the general method below - if (right.from == Long.MIN_VALUE) { - Range range; - // The value Long.MIN_VALUE as a divisor needs special handling as follows: - if (from == Long.MIN_VALUE) { - if (to == Long.MIN_VALUE) { - // This range only contains Long.MIN_VALUE, so the result range is {0}. - range = create(0, 0); - } else { // (to > Long.MIN_VALUE) - // When this range contains Long.MIN_VALUE, which would have a remainder of 0 if - // divided by Long.MIN_VALUE, the result range is {0} unioned with [from + 1, - // to]. - range = create(from + 1, to).union(create(0, 0)); + + /** + * Returns the smallest range that includes all possible values resulting from subtracting an + * arbitrary value in the specified range from an arbitrary value in this range. We call this + * the subtraction of two ranges. + * + * @param right the range to be subtracted from this range + * @return the range resulting from subtracting the specified range from this range + */ + public Range minus(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + if (this.isWithinHalfLong() && right.isWithinHalfLong()) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from - right.to; + long resultTo = to - right.from; + return create(resultFrom, resultTo); + } else { + BigInteger bigFrom = BigInteger.valueOf(from).subtract(BigInteger.valueOf(right.to)); + BigInteger bigTo = BigInteger.valueOf(to).subtract(BigInteger.valueOf(right.from)); + return create(bigFrom, bigTo); } - } else { // (from > Long.MIN_VALUE) - // When this range doesn't contain Long.MIN_VALUE, the remainder of each value - // in this range divided by Long.MIN_VALUE is this value itself. Therefore the - // result range is this range itself. - range = this; - } - // If right.to > Long.MIN_VALUE, union the previous result with the result of range - // [right.from + 1, right.to] divided by this range, which can be calculated using - // the general method (see below) - if (right.to > Long.MIN_VALUE) { - Range rangeAdditional = this.remainder(create(right.from + 1, right.to)); - range = range.union(rangeAdditional); - } - return range; } - // General method: - // Calculate range1: the result range of this range divided by EVERYTHING. For example, - // if this range is [3, 5], then the result range would be [0, 5]. If this range is [-3, 4], - // then the result range would be [-3, 4]. In general, the result range is {0} union with - // this range excluding the value Long.MIN_VALUE. - Range range1 = - create(Math.max(Long.MIN_VALUE + 1, from), Math.max(Long.MIN_VALUE + 1, to)) - .union(create(0, 0)); - // Calculate range2: the result range of range EVERYTHING divided by the right range. For - // example, if the right range is [-5, 3], then the result range would be [-4, 4]. If the - // right range is [3, 6], then the result range would be [-5, 5]. In general, the result - // range is calculated as following: - long maxAbsolute = Math.max(Math.abs(right.from), Math.abs(right.to)); - Range range2 = create(-maxAbsolute + 1, maxAbsolute - 1); - // Since range1 and range2 are both super sets of the minimal result range, we return the - // intersection of range1 and range2, which is correct (super set) and precise enough. - return range1.intersect(range2); - } - - /** - * Returns a range that includes all possible values resulting from left shifting an arbitrary - * value in this range by an arbitrary number of bits in the specified range. We call this the - * left shift of a range. - * - * @param right the range of bits by which this range is left shifted - * @return the range resulting from left shifting this range by the specified range - */ - public Range shiftLeft(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + + /** + * Returns the smallest range that includes all possible values resulting from multiplying an + * arbitrary value in the specified range by an arbitrary value in this range. We call this the + * multiplication of two ranges. + * + * @param right the specified range to be multiplied by this range + * @return the range resulting from multiplying the specified range by this range + */ + public Range times(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + // These bounds are adequate: Integer.MAX_VALUE^2 is still a bit less than Long.MAX_VALUE. + if (this.isWithinInteger() && right.isWithinInteger()) { + List possibleValues = + Arrays.asList( + from * right.from, from * right.to, to * right.from, to * right.to); + return create(possibleValues); + } else { + BigInteger bigLeftFrom = BigInteger.valueOf(from); + BigInteger bigRightFrom = BigInteger.valueOf(right.from); + BigInteger bigRightTo = BigInteger.valueOf(right.to); + BigInteger bigLeftTo = BigInteger.valueOf(to); + List bigPossibleValues = + Arrays.asList( + bigLeftFrom.multiply(bigRightFrom), + bigLeftFrom.multiply(bigRightTo), + bigLeftTo.multiply(bigRightFrom), + bigLeftTo.multiply(bigRightTo)); + return create(Collections.min(bigPossibleValues), Collections.max(bigPossibleValues)); + } } - // Shifting operations in Java are depending on the type of the left-hand operand: - // If the left-hand operand is int type, only the 5 lowest-order bits of the right-hand - // operand are used. - // If the left-hand operand is long type, only the 6 lowest-order bits of the right-hand - // operand are used. - // For example, while 1 << -1== 1 << 31, 1L << -1 == 1L << 63. - // For ths reason, we restrict the shift-bits to analyze in [0. 31] and give up the analysis - // when out of this range. - // - // Other possible solutions: - // 1. create different methods for int type and long type and use them accordingly - // 2. add an additional boolean parameter to indicate the type of the left-hand operand - // - // see https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.19 for more - // detail. - if (right.isWithin(0, 31)) { - if (this.isWithinInteger()) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from << (from >= 0 ? right.from : right.to); - long resultTo = to << (to >= 0 ? right.to : right.from); + /** + * Returns the smallest range that includes all possible values resulting from dividing (integer + * division) an arbitrary value in this range by an arbitrary value in the specified range. We + * call this the division of two ranges. + * + * @param right the specified range by which this range is divided + * @return the range resulting from dividing this range by the specified range + */ + public Range divide(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + if (right.from == 0 && right.to == 0) { + return NOTHING; + } + // Special cases that involve overflow. + // The only overflow in integer division is Long.MIN_VALUE / -1 == Long.MIN_VALUE. + if (from == Long.MIN_VALUE && right.contains(-1)) { + // The values in the right range are all negative because right does not contain 0 but + // does contain 1. + if (from != to) { + // Special case 1: + // This range contains Long.MIN_VALUE and Long.MIN_VALUE + 1, which makes the + // result range EVERYTHING. + return EVERYTHING; + } else if (right.from != right.to) { + // Special case 2: + // This range contains only Long.MIN_VALUE, and the right range contains at least -1 + // and -2. The result range is from Long.MIN_VALUE to Long.MIN_VALUE / -2. + return create(Long.MIN_VALUE, Long.MIN_VALUE / -2); + } else { + // Special case 3: + // This range contains only Long.MIN_VALUE, and right contains only -1. + return create(Long.MIN_VALUE, Long.MIN_VALUE); + } + } + // We needn't worry about the overflow issue starting from here. + + // There are 9 different cases: + // (note: pos=positive, neg=negative, unk=unknown sign, np=non-positive, nn=non-negative) + long resultFrom; + long resultTo; + if (from > 0) { // this range is positive + if (right.from >= 0) { + // 1. right: nn + resultFrom = from / Math.max(right.to, 1); + resultTo = to / Math.max(right.from, 1); + } else if (right.to <= 0) { + // 2. right: np + resultFrom = to / Math.min(right.to, -1); + resultTo = from / Math.min(right.from, -1); + } else { + // 3. right: unk; values include -1 and 1 + resultFrom = -to; + resultTo = to; + } + } else if (to < 0) { // this range is negative + if (right.from >= 0) { + // 4. right: nn + resultFrom = from / Math.max(right.from, 1); + resultTo = to / Math.max(right.to, 1); + } else if (right.to <= 0) { + // 5. right: np + resultFrom = to / Math.min(right.from, -1); + resultTo = from / Math.min(right.to, -1); + } else { + // 6. right: unk; values include -1 and 1 + resultFrom = from; + resultTo = -from; + } + } else { // this range spans both signs + if (right.from >= 0) { + // 7. right: nn + resultFrom = from / Math.max(right.from, 1); + resultTo = to / Math.max(right.from, 1); + } else if (right.to <= 0) { + // 8. right: np + resultFrom = to / Math.min(right.to, -1); + resultTo = from / Math.min(right.to, -1); + } else { + // 9. right: unk; values include -1 and 1 + resultFrom = Math.min(from, -to); + resultTo = Math.max(-from, to); + } + } return create(resultFrom, resultTo); - } else { - BigInteger bigFrom = - BigInteger.valueOf(from).shiftLeft(from >= 0 ? (int) right.from : (int) right.to); - BigInteger bigTo = - BigInteger.valueOf(to).shiftLeft(to >= 0 ? (int) right.to : (int) right.from); - return create(bigFrom, bigTo); - } - } else { - // In other cases, we give up on the calculation and return EVERYTHING (rare in - // practice). - return EVERYTHING; } - } - - /** - * Returns a range that includes all possible values resulting from signed right shifting an - * arbitrary value in this range by an arbitrary number of bits in the specified range. We call - * this the signed right shift operation of a range. - * - * @param right the range of bits by which this range is signed right shifted - * @return the range resulting from signed right shifting this range by the specified range - */ - public Range signedShiftRight(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + + /** + * Returns a range that includes all possible values of the remainder of dividing an arbitrary + * value in this range by an arbitrary value in the specified range. + * + *

          In the current implementation, the result might not be the smallest range that includes + * all the possible values. + * + * @param right the specified range by which this range is divided + * @return the range of the remainder of dividing this range by the specified range + */ + public Range remainder(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + if (right.from == 0 && right.to == 0) { + return NOTHING; + } + // Special cases that would cause overflow if we use the general method below + if (right.from == Long.MIN_VALUE) { + Range range; + // The value Long.MIN_VALUE as a divisor needs special handling as follows: + if (from == Long.MIN_VALUE) { + if (to == Long.MIN_VALUE) { + // This range only contains Long.MIN_VALUE, so the result range is {0}. + range = create(0, 0); + } else { // (to > Long.MIN_VALUE) + // When this range contains Long.MIN_VALUE, which would have a remainder of 0 if + // divided by Long.MIN_VALUE, the result range is {0} unioned with [from + 1, + // to]. + range = create(from + 1, to).union(create(0, 0)); + } + } else { // (from > Long.MIN_VALUE) + // When this range doesn't contain Long.MIN_VALUE, the remainder of each value + // in this range divided by Long.MIN_VALUE is this value itself. Therefore the + // result range is this range itself. + range = this; + } + // If right.to > Long.MIN_VALUE, union the previous result with the result of range + // [right.from + 1, right.to] divided by this range, which can be calculated using + // the general method (see below) + if (right.to > Long.MIN_VALUE) { + Range rangeAdditional = this.remainder(create(right.from + 1, right.to)); + range = range.union(rangeAdditional); + } + return range; + } + // General method: + // Calculate range1: the result range of this range divided by EVERYTHING. For example, + // if this range is [3, 5], then the result range would be [0, 5]. If this range is [-3, 4], + // then the result range would be [-3, 4]. In general, the result range is {0} union with + // this range excluding the value Long.MIN_VALUE. + Range range1 = + create(Math.max(Long.MIN_VALUE + 1, from), Math.max(Long.MIN_VALUE + 1, to)) + .union(create(0, 0)); + // Calculate range2: the result range of range EVERYTHING divided by the right range. For + // example, if the right range is [-5, 3], then the result range would be [-4, 4]. If the + // right range is [3, 6], then the result range would be [-5, 5]. In general, the result + // range is calculated as following: + long maxAbsolute = Math.max(Math.abs(right.from), Math.abs(right.to)); + Range range2 = create(-maxAbsolute + 1, maxAbsolute - 1); + // Since range1 and range2 are both super sets of the minimal result range, we return the + // intersection of range1 and range2, which is correct (super set) and precise enough. + return range1.intersect(range2); } - if (this.isWithinInteger() && right.isWithin(0, 31)) { - // This bound is adequate to guarantee no overflow when using long to evaluate - long resultFrom = from >> (from >= 0 ? right.to : right.from); - long resultTo = to >> (to >= 0 ? right.from : right.to); - return create(resultFrom, resultTo); - } else { - // Signed shift right operation for long type cannot be simulated with BigInteger. - // Give up on the calculation and return EVERYTHING instead. - return EVERYTHING; + /** + * Returns a range that includes all possible values resulting from left shifting an arbitrary + * value in this range by an arbitrary number of bits in the specified range. We call this the + * left shift of a range. + * + * @param right the range of bits by which this range is left shifted + * @return the range resulting from left shifting this range by the specified range + */ + public Range shiftLeft(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + // Shifting operations in Java are depending on the type of the left-hand operand: + // If the left-hand operand is int type, only the 5 lowest-order bits of the right-hand + // operand are used. + // If the left-hand operand is long type, only the 6 lowest-order bits of the right-hand + // operand are used. + // For example, while 1 << -1== 1 << 31, 1L << -1 == 1L << 63. + // For ths reason, we restrict the shift-bits to analyze in [0. 31] and give up the analysis + // when out of this range. + // + // Other possible solutions: + // 1. create different methods for int type and long type and use them accordingly + // 2. add an additional boolean parameter to indicate the type of the left-hand operand + // + // see https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.19 for more + // detail. + if (right.isWithin(0, 31)) { + if (this.isWithinInteger()) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from << (from >= 0 ? right.from : right.to); + long resultTo = to << (to >= 0 ? right.to : right.from); + return create(resultFrom, resultTo); + } else { + BigInteger bigFrom = + BigInteger.valueOf(from) + .shiftLeft(from >= 0 ? (int) right.from : (int) right.to); + BigInteger bigTo = + BigInteger.valueOf(to) + .shiftLeft(to >= 0 ? (int) right.to : (int) right.from); + return create(bigFrom, bigTo); + } + } else { + // In other cases, we give up on the calculation and return EVERYTHING (rare in + // practice). + return EVERYTHING; + } } - } - - /** - * When this range only contains non-negative values, the refined result should be the same as - * {@link #signedShiftRight(Range)}. We give up the analysis when this range contains negative - * value(s). - */ - public Range unsignedShiftRight(Range right) { - if (this.from >= 0) { - return signedShiftRight(right); + + /** + * Returns a range that includes all possible values resulting from signed right shifting an + * arbitrary value in this range by an arbitrary number of bits in the specified range. We call + * this the signed right shift operation of a range. + * + * @param right the range of bits by which this range is signed right shifted + * @return the range resulting from signed right shifting this range by the specified range + */ + public Range signedShiftRight(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + if (this.isWithinInteger() && right.isWithin(0, 31)) { + // This bound is adequate to guarantee no overflow when using long to evaluate + long resultFrom = from >> (from >= 0 ? right.to : right.from); + long resultTo = to >> (to >= 0 ? right.from : right.to); + return create(resultFrom, resultTo); + } else { + // Signed shift right operation for long type cannot be simulated with BigInteger. + // Give up on the calculation and return EVERYTHING instead. + return EVERYTHING; + } } - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * When this range only contains non-negative values, the refined result should be the same as + * {@link #signedShiftRight(Range)}. We give up the analysis when this range contains negative + * value(s). + */ + public Range unsignedShiftRight(Range right) { + if (this.from >= 0) { + return signedShiftRight(right); + } + + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + return EVERYTHING; } - return EVERYTHING; - } - - /** - * Returns a range that includes all possible values resulting from performing the bitwise and - * operation on a value in this range by a mask in the specified range. We call this the bitwise - * and operation of a range. - * - *

          The current implementation is conservative: it only refines the cases where the range of - * mask represents a constant. In other cases, it gives up on the refinement and returns {@code - * EVERYTHING} instead. - * - * @param right the range of mask of the bitwise and operation - * @return the range resulting from the bitwise and operation of this range and the specified - * range of mask - */ - public Range bitwiseAnd(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * Returns a range that includes all possible values resulting from performing the bitwise and + * operation on a value in this range by a mask in the specified range. We call this the bitwise + * and operation of a range. + * + *

          The current implementation is conservative: it only refines the cases where the range of + * mask represents a constant. In other cases, it gives up on the refinement and returns {@code + * EVERYTHING} instead. + * + * @param right the range of mask of the bitwise and operation + * @return the range resulting from the bitwise and operation of this range and the specified + * range of mask + */ + public Range bitwiseAnd(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + // We only refine the cases where the range of mask represent a constant. + // Recall these two's-complement facts: + // 11111111 represents -1 + // 10000000 represents MIN_VALUE + + Range constant = null; + Range variable = null; + if (right.isConstant()) { + constant = right; + variable = this; + } else if (this.isConstant()) { + constant = this; + variable = right; + } + + if (constant != null) { + long mask = constant.from; + if (mask >= 0) { + // Sign bit of mask is 0. The elements in the result range must be positive, and + // the result range is upper-bounded by the mask. + if (variable.from >= 0) { + // Case 1.1: The result range is upper-bounded by the upper bound of this range. + return create(0, Math.min(mask, variable.to)); + } else if (variable.to < 0) { + // Case 1.2: The result range is upper-bounded by the upper bound of this range + // after ignoring the sign bit. The upper bound of this range has the most bits + // (of the highest place values) set to 1. + return create(0, Math.min(mask, noSignBit(variable.to))); + } else { + // Case 1.3: Since this range contains -1, the upper bound of this range after + // ignoring the sign bit is Long.MAX_VALUE and thus doesn't contribute to + // further refinement. + return create(0, mask); + } + } else { + // Sign bit of mask is 1. + if (variable.from >= 0) { + // Case 2.1: Similar to case 1.1 except that the sign bit of the mask can be + // ignored. + return create(0, Math.min(noSignBit(mask), variable.to)); + } else if (variable.to < 0) { + // Case 2.2: The sign bit of the elements in the result range must be 1. + // Therefore the lower bound of the result range is Long.MIN_VALUE (when all + // 1-bits are mismatched between the mask and the element in this range). The + // result range is also upper-bounded by this mask itself and the upper bound of + // this range. (Because more set bits means a larger number -- still negative, + // but closer to 0.) + return create(Long.MIN_VALUE, Math.min(mask, variable.to)); + } else { + // Case 2.3: Similar to case 2.2 except that the elements in this range could + // be positive, and thus the result range is upper-bounded by the upper bound + // of this range and the mask after ignoring the sign bit. + return create(Long.MIN_VALUE, Math.min(noSignBit(mask), variable.to)); + } + } + } + + return EVERYTHING; } - // We only refine the cases where the range of mask represent a constant. - // Recall these two's-complement facts: - // 11111111 represents -1 - // 10000000 represents MIN_VALUE - - Range constant = null; - Range variable = null; - if (right.isConstant()) { - constant = right; - variable = this; - } else if (this.isConstant()) { - constant = this; - variable = right; + /** Return the argument, with its sign bit zeroed out. */ + private long noSignBit(Long mask) { + return mask & (-1L >>> 1); } - if (constant != null) { - long mask = constant.from; - if (mask >= 0) { - // Sign bit of mask is 0. The elements in the result range must be positive, and - // the result range is upper-bounded by the mask. - if (variable.from >= 0) { - // Case 1.1: The result range is upper-bounded by the upper bound of this range. - return create(0, Math.min(mask, variable.to)); - } else if (variable.to < 0) { - // Case 1.2: The result range is upper-bounded by the upper bound of this range - // after ignoring the sign bit. The upper bound of this range has the most bits - // (of the highest place values) set to 1. - return create(0, Math.min(mask, noSignBit(variable.to))); - } else { - // Case 1.3: Since this range contains -1, the upper bound of this range after - // ignoring the sign bit is Long.MAX_VALUE and thus doesn't contribute to - // further refinement. - return create(0, mask); + /** We give up the analysis for bitwise OR operation. */ + public Range bitwiseOr(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - } else { - // Sign bit of mask is 1. - if (variable.from >= 0) { - // Case 2.1: Similar to case 1.1 except that the sign bit of the mask can be - // ignored. - return create(0, Math.min(noSignBit(mask), variable.to)); - } else if (variable.to < 0) { - // Case 2.2: The sign bit of the elements in the result range must be 1. - // Therefore the lower bound of the result range is Long.MIN_VALUE (when all - // 1-bits are mismatched between the mask and the element in this range). The - // result range is also upper-bounded by this mask itself and the upper bound of - // this range. (Because more set bits means a larger number -- still negative, - // but closer to 0.) - return create(Long.MIN_VALUE, Math.min(mask, variable.to)); - } else { - // Case 2.3: Similar to case 2.2 except that the elements in this range could - // be positive, and thus the result range is upper-bounded by the upper bound - // of this range and the mask after ignoring the sign bit. - return create(Long.MIN_VALUE, Math.min(noSignBit(mask), variable.to)); + + return EVERYTHING; + } + + /** We give up the analysis for bitwise XOR operation. */ + public Range bitwiseXor(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; } - } + + return EVERYTHING; + } + + /** + * Returns the range of a variable that falls within this range after applying the unary plus + * operation (which is a no-op). + * + * @return this range + */ + public Range unaryPlus() { + return this; } - return EVERYTHING; - } + /** + * Returns the range of a variable that falls within this range after applying the unary minus + * operation. + * + * @return the resulted range of applying unary minus on an arbitrary value in this range + */ + public Range unaryMinus() { + if (this.isNothing()) { + return NOTHING; + } - /** Return the argument, with its sign bit zeroed out. */ - private long noSignBit(Long mask) { - return mask & (-1L >>> 1); - } + if (from == Long.MIN_VALUE && from != to) { + // the only case that needs special handling because of overflow + return EVERYTHING; + } - /** We give up the analysis for bitwise OR operation. */ - public Range bitwiseOr(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + return create(-to, -from); } - return EVERYTHING; - } + /** + * Returns the range of a variable that falls within this range after applying the bitwise + * complement operation. + * + * @return the resulting range of applying bitwise complement on an arbitrary value in this + * range + */ + public Range bitwiseComplement() { + if (this.isNothing()) { + return NOTHING; + } - /** We give up the analysis for bitwise XOR operation. */ - public Range bitwiseXor(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + return create(~to, ~from); } - return EVERYTHING; - } - - /** - * Returns the range of a variable that falls within this range after applying the unary plus - * operation (which is a no-op). - * - * @return this range - */ - public Range unaryPlus() { - return this; - } - - /** - * Returns the range of a variable that falls within this range after applying the unary minus - * operation. - * - * @return the resulted range of applying unary minus on an arbitrary value in this range - */ - public Range unaryMinus() { - if (this.isNothing()) { - return NOTHING; + /** + * Refines this range to reflect that some value in it can be less than a value in the given + * range. This is used for calculating the control-flow-refined result of the < operator. For + * example: + * + *

          +     * 
          +     *    {@literal @}IntRange(from = 0, to = 10) int a;
          +     *    {@literal @}IntRange(from = 3, to = 7) int b;
          +     *     ...
          +     *     if (a < b) {
          +     *         // range of a is now refined to [0, 6] because a value in range [7, 10]
          +     *         // cannot be smaller than variable b with range [3, 7].
          +     *         ...
          +     *     }
          +     * 
          +     * 
          + * + * Use the {@link #refineGreaterThanEq(Range)} method if you are also interested in refining the + * range of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineLessThan(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + if (right.to == Long.MIN_VALUE) { + return NOTHING; + } + + long resultTo = Math.min(to, right.to - 1); + return createOrNothing(from, resultTo); } - if (from == Long.MIN_VALUE && from != to) { - // the only case that needs special handling because of overflow - return EVERYTHING; + /** + * Refines this range to reflect that some value in it can be less than or equal to a value in + * the given range. This is used for calculating the control-flow-refined result of the <= + * operator. For example: + * + *
          +     * 
          +     *    {@literal @}IntRange(from = 0, to = 10) int a;
          +     *    {@literal @}IntRange(from = 3, to = 7) int b;
          +     *     ...
          +     *     if (a <= b) {
          +     *         // range of a is now refined to [0, 7] because a value in range [8, 10]
          +     *         // cannot be less than or equal to variable b with range [3, 7].
          +     *         ...
          +     *     }
          +     * 
          +     * 
          + * + * Use the {@link #refineGreaterThan(Range)} method if you are also interested in refining the + * range of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineLessThanEq(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + long resultTo = Math.min(to, right.to); + return createOrNothing(from, resultTo); } - return create(-to, -from); - } - - /** - * Returns the range of a variable that falls within this range after applying the bitwise - * complement operation. - * - * @return the resulting range of applying bitwise complement on an arbitrary value in this range - */ - public Range bitwiseComplement() { - if (this.isNothing()) { - return NOTHING; + /** + * Refines this range to reflect that some value in it can be greater than a value in the given + * range. This is used for calculating the control-flow-refined result of the > operator. For + * example: + * + *
          +     * 
          +     *    {@literal @}IntRange(from = 0, to = 10) int a;
          +     *    {@literal @}IntRange(from = 3, to = 7) int b;
          +     *     ...
          +     *     if (a > b) {
          +     *         // range of a is now refined to [4, 10] because a value in range [0, 3]
          +     *         // cannot be greater than variable b with range [3, 7].
          +     *         ...
          +     *     }
          +     * 
          +     * 
          + * + * Use the {@link #refineLessThanEq(Range)} method if you are also interested in refining the + * range of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineGreaterThan(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + if (right.from == Long.MAX_VALUE) { + return NOTHING; + } + + long resultFrom = Math.max(from, right.from + 1); + return createOrNothing(resultFrom, to); } - return create(~to, ~from); - } - - /** - * Refines this range to reflect that some value in it can be less than a value in the given - * range. This is used for calculating the control-flow-refined result of the < operator. For - * example: - * - *
          -   * 
          -   *    {@literal @}IntRange(from = 0, to = 10) int a;
          -   *    {@literal @}IntRange(from = 3, to = 7) int b;
          -   *     ...
          -   *     if (a < b) {
          -   *         // range of a is now refined to [0, 6] because a value in range [7, 10]
          -   *         // cannot be smaller than variable b with range [3, 7].
          -   *         ...
          -   *     }
          -   * 
          -   * 
          - * - * Use the {@link #refineGreaterThanEq(Range)} method if you are also interested in refining the - * range of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineLessThan(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * Refines this range to reflect that some value in it can be greater than or equal to a value + * in the given range. This is used for calculating the control-flow-refined result of the >= + * operator. For example: + * + *
          +     * 
          +     *    {@literal @}IntRange(from = 0, to = 10) int a;
          +     *    {@literal @}IntRange(from = 3, to = 7) int b;
          +     *     ...
          +     *     if (a >= b) {
          +     *         // range of a is now refined to [3, 10] because a value in range [0, 2]
          +     *         // cannot be greater than or equal to variable b with range [3, 7].
          +     *         ...
          +     *     }
          +     * 
          +     * 
          + * + * Use the {@link #refineLessThan(Range)} method if you are also interested in refining the + * range of {@code b} in the code above. + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineGreaterThanEq(Range right) { + if (this.isNothing() || right.isNothing()) { + return NOTHING; + } + + long resultFrom = Math.max(from, right.from); + return createOrNothing(resultFrom, to); } - if (right.to == Long.MIN_VALUE) { - return NOTHING; + /** + * Refines this range to reflect that some value in it can be equal to a value in the given + * range. This is used for calculating the control-flow-refined result of the == operator. For + * example: + * + *
          +     * 
          +     *    {@literal @}IntRange(from = 0, to = 10) int a;
          +     *    {@literal @}IntRange(from = 3, to = 15) int b;
          +     *     ...
          +     *     if (a == b) {
          +     *         // range of a is now refined to [3, 10] because a value in range [0, 2]
          +     *         // cannot be equal to variable b with range [3, 15].
          +     *         ...
          +     *     }
          +     * 
          +     * 
          + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineEqualTo(Range right) { + return this.intersect(right); } - long resultTo = Math.min(to, right.to - 1); - return createOrNothing(from, resultTo); - } - - /** - * Refines this range to reflect that some value in it can be less than or equal to a value in the - * given range. This is used for calculating the control-flow-refined result of the <= - * operator. For example: - * - *
          -   * 
          -   *    {@literal @}IntRange(from = 0, to = 10) int a;
          -   *    {@literal @}IntRange(from = 3, to = 7) int b;
          -   *     ...
          -   *     if (a <= b) {
          -   *         // range of a is now refined to [0, 7] because a value in range [8, 10]
          -   *         // cannot be less than or equal to variable b with range [3, 7].
          -   *         ...
          -   *     }
          -   * 
          -   * 
          - * - * Use the {@link #refineGreaterThan(Range)} method if you are also interested in refining the - * range of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineLessThanEq(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * Refines this range to reflect that some value in it must not be equal to a value in the given + * range. This only changes the range if the given range (right) contains exactly one integer, + * and that integer is one of the bounds of this range. This is used for calculating the + * control-flow-refined result of the != operator. For example: + * + *
          +     * 
          +     *    {@literal @}IntRange(from = 0, to = 10) int a;
          +     *    {@literal @}IntRange(from = 0, to = 0) int b;
          +     *     ...
          +     *     if (a != b) {
          +     *         // range of a is now refined to [1, 10] because it cannot
          +     *         // be zero.
          +     *         ...
          +     *     }
          +     * 
          +     * 
          + * + * @param right the specified {@code Range} to compare with + * @return the refined {@code Range} + */ + public Range refineNotEqualTo(Range right) { + if (right.isConstant()) { + if (this.to == right.to) { + return create(this.from, this.to - 1); + } else if (this.from == right.from) { + return create(this.from + 1, this.to); + } + } + return this; } - long resultTo = Math.min(to, right.to); - return createOrNothing(from, resultTo); - } - - /** - * Refines this range to reflect that some value in it can be greater than a value in the given - * range. This is used for calculating the control-flow-refined result of the > operator. For - * example: - * - *
          -   * 
          -   *    {@literal @}IntRange(from = 0, to = 10) int a;
          -   *    {@literal @}IntRange(from = 3, to = 7) int b;
          -   *     ...
          -   *     if (a > b) {
          -   *         // range of a is now refined to [4, 10] because a value in range [0, 3]
          -   *         // cannot be greater than variable b with range [3, 7].
          -   *         ...
          -   *     }
          -   * 
          -   * 
          - * - * Use the {@link #refineLessThanEq(Range)} method if you are also interested in refining the - * range of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineGreaterThan(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * Returns true if the range is wider than a given value, i.e., if the number of possible values + * enclosed by this range is more than the given value. + * + * @param value the value to compare with + * @return true if wider than the given value + */ + public boolean isWiderThan(long value) { + if (this.isWithin((Long.MIN_VALUE >> 1) + 1, Long.MAX_VALUE >> 1)) { + // This bound is adequate to guarantee no overflow when using long to evaluate. + // Long.MIN_VALUE >> 1 + 1 = -4611686018427387903 + // Long.MAX_VALUE >> 1 = 4611686018427387903 + return width() > value; + } else { + return BigInteger.valueOf(to) + .subtract(BigInteger.valueOf(from)) + .add(BigInteger.ONE) + .compareTo(BigInteger.valueOf(value)) + > 0; + } } - if (right.from == Long.MAX_VALUE) { - return NOTHING; + /** + * Returns true if this range represents a constant value. + * + * @return true if this range represents a constant value + */ + public boolean isConstant() { + return from == to; } - long resultFrom = Math.max(from, right.from + 1); - return createOrNothing(resultFrom, to); - } - - /** - * Refines this range to reflect that some value in it can be greater than or equal to a value in - * the given range. This is used for calculating the control-flow-refined result of the >= - * operator. For example: - * - *
          -   * 
          -   *    {@literal @}IntRange(from = 0, to = 10) int a;
          -   *    {@literal @}IntRange(from = 3, to = 7) int b;
          -   *     ...
          -   *     if (a >= b) {
          -   *         // range of a is now refined to [3, 10] because a value in range [0, 2]
          -   *         // cannot be greater than or equal to variable b with range [3, 7].
          -   *         ...
          -   *     }
          -   * 
          -   * 
          - * - * Use the {@link #refineLessThan(Range)} method if you are also interested in refining the range - * of {@code b} in the code above. - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineGreaterThanEq(Range right) { - if (this.isNothing() || right.isNothing()) { - return NOTHING; + /** + * Returns true if this range is completely contained in the range specified by the given lower + * bound inclusive and upper bound inclusive. + * + * @param lb lower bound for the range that might contain this one + * @param ub upper bound for the range that might contain this one + * @return true if this range is within the given bounds + */ + public boolean isWithin(long lb, long ub) { + assert lb <= ub; + return lb <= from && to <= ub; } - long resultFrom = Math.max(from, right.from); - return createOrNothing(resultFrom, to); - } - - /** - * Refines this range to reflect that some value in it can be equal to a value in the given range. - * This is used for calculating the control-flow-refined result of the == operator. For example: - * - *
          -   * 
          -   *    {@literal @}IntRange(from = 0, to = 10) int a;
          -   *    {@literal @}IntRange(from = 3, to = 15) int b;
          -   *     ...
          -   *     if (a == b) {
          -   *         // range of a is now refined to [3, 10] because a value in range [0, 2]
          -   *         // cannot be equal to variable b with range [3, 15].
          -   *         ...
          -   *     }
          -   * 
          -   * 
          - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineEqualTo(Range right) { - return this.intersect(right); - } - - /** - * Refines this range to reflect that some value in it must not be equal to a value in the given - * range. This only changes the range if the given range (right) contains exactly one integer, and - * that integer is one of the bounds of this range. This is used for calculating the - * control-flow-refined result of the != operator. For example: - * - *
          -   * 
          -   *    {@literal @}IntRange(from = 0, to = 10) int a;
          -   *    {@literal @}IntRange(from = 0, to = 0) int b;
          -   *     ...
          -   *     if (a != b) {
          -   *         // range of a is now refined to [1, 10] because it cannot
          -   *         // be zero.
          -   *         ...
          -   *     }
          -   * 
          -   * 
          - * - * @param right the specified {@code Range} to compare with - * @return the refined {@code Range} - */ - public Range refineNotEqualTo(Range right) { - if (right.isConstant()) { - if (this.to == right.to) { - return create(this.from, this.to - 1); - } else if (this.from == right.from) { - return create(this.from + 1, this.to); - } + /** + * Returns true if this range is contained inclusively between Long.MIN_VALUE/2 and + * Long.MAX_VALUE/2. Note: Long.MIN_VALUE/2 != -Long.MAX_VALUE/2 + */ + private boolean isWithinHalfLong() { + return isWithin(Long.MIN_VALUE >> 1, Long.MAX_VALUE >> 1); } - return this; - } - - /** - * Returns true if the range is wider than a given value, i.e., if the number of possible values - * enclosed by this range is more than the given value. - * - * @param value the value to compare with - * @return true if wider than the given value - */ - public boolean isWiderThan(long value) { - if (this.isWithin((Long.MIN_VALUE >> 1) + 1, Long.MAX_VALUE >> 1)) { - // This bound is adequate to guarantee no overflow when using long to evaluate. - // Long.MIN_VALUE >> 1 + 1 = -4611686018427387903 - // Long.MAX_VALUE >> 1 = 4611686018427387903 - return width() > value; - } else { - return BigInteger.valueOf(to) - .subtract(BigInteger.valueOf(from)) - .add(BigInteger.ONE) - .compareTo(BigInteger.valueOf(value)) - > 0; + + /** + * Returns true if this range is completely contained in the scope of the Integer type. + * + * @return true if the range is contained within the Integer range inclusive + */ + public boolean isWithinInteger() { + return isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE); } - } - - /** - * Returns true if this range represents a constant value. - * - * @return true if this range represents a constant value - */ - public boolean isConstant() { - return from == to; - } - - /** - * Returns true if this range is completely contained in the range specified by the given lower - * bound inclusive and upper bound inclusive. - * - * @param lb lower bound for the range that might contain this one - * @param ub upper bound for the range that might contain this one - * @return true if this range is within the given bounds - */ - public boolean isWithin(long lb, long ub) { - assert lb <= ub; - return lb <= from && to <= ub; - } - - /** - * Returns true if this range is contained inclusively between Long.MIN_VALUE/2 and - * Long.MAX_VALUE/2. Note: Long.MIN_VALUE/2 != -Long.MAX_VALUE/2 - */ - private boolean isWithinHalfLong() { - return isWithin(Long.MIN_VALUE >> 1, Long.MAX_VALUE >> 1); - } - - /** - * Returns true if this range is completely contained in the scope of the Integer type. - * - * @return true if the range is contained within the Integer range inclusive - */ - public boolean isWithinInteger() { - return isWithin(Integer.MIN_VALUE, Integer.MAX_VALUE); - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java b/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java index 7c3bdc71e25..bc70f02a989 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/ShortMath.java @@ -3,385 +3,385 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ShortMath extends NumberMath { - int number; + int number; - public ShortMath(int i) { - number = i; - } - - @Override - public Number plus(Number right) { - if (right instanceof Byte) { - return number + right.byteValue(); - } - if (right instanceof Double) { - return number + right.doubleValue(); - } - if (right instanceof Float) { - return number + right.floatValue(); - } - if (right instanceof Integer) { - return number + right.intValue(); - } - if (right instanceof Long) { - return number + right.longValue(); + public ShortMath(int i) { + number = i; } - if (right instanceof Short) { - return number + right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number minus(Number right) { - if (right instanceof Byte) { - return number - right.byteValue(); - } - if (right instanceof Double) { - return number - right.doubleValue(); - } - if (right instanceof Float) { - return number - right.floatValue(); - } - if (right instanceof Integer) { - return number - right.intValue(); - } - if (right instanceof Long) { - return number - right.longValue(); + @Override + public Number plus(Number right) { + if (right instanceof Byte) { + return number + right.byteValue(); + } + if (right instanceof Double) { + return number + right.doubleValue(); + } + if (right instanceof Float) { + return number + right.floatValue(); + } + if (right instanceof Integer) { + return number + right.intValue(); + } + if (right instanceof Long) { + return number + right.longValue(); + } + if (right instanceof Short) { + return number + right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number - right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number times(Number right) { - if (right instanceof Byte) { - return number * right.byteValue(); - } - if (right instanceof Double) { - return number * right.doubleValue(); - } - if (right instanceof Float) { - return number * right.floatValue(); - } - if (right instanceof Integer) { - return number * right.intValue(); - } - if (right instanceof Long) { - return number * right.longValue(); + @Override + public Number minus(Number right) { + if (right instanceof Byte) { + return number - right.byteValue(); + } + if (right instanceof Double) { + return number - right.doubleValue(); + } + if (right instanceof Float) { + return number - right.floatValue(); + } + if (right instanceof Integer) { + return number - right.intValue(); + } + if (right instanceof Long) { + return number - right.longValue(); + } + if (right instanceof Short) { + return number - right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number * right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number divide(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number / right.byteValue(); - } - if (right instanceof Double) { - return number / right.doubleValue(); - } - if (right instanceof Float) { - return number / right.floatValue(); - } - if (right instanceof Integer) { - return number / right.intValue(); + @Override + public Number times(Number right) { + if (right instanceof Byte) { + return number * right.byteValue(); + } + if (right instanceof Double) { + return number * right.doubleValue(); + } + if (right instanceof Float) { + return number * right.floatValue(); + } + if (right instanceof Integer) { + return number * right.intValue(); + } + if (right instanceof Long) { + return number * right.longValue(); + } + if (right instanceof Short) { + return number * right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number / right.longValue(); - } - if (right instanceof Short) { - return number / right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public @Nullable Number remainder(Number right) { - if (isIntegralZero(right)) { - return null; - } - if (right instanceof Byte) { - return number % right.byteValue(); - } - if (right instanceof Double) { - return number % right.doubleValue(); - } - if (right instanceof Float) { - return number % right.floatValue(); + @Override + public @Nullable Number divide(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number / right.byteValue(); + } + if (right instanceof Double) { + return number / right.doubleValue(); + } + if (right instanceof Float) { + return number / right.floatValue(); + } + if (right instanceof Integer) { + return number / right.intValue(); + } + if (right instanceof Long) { + return number / right.longValue(); + } + if (right instanceof Short) { + return number / right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number % right.intValue(); - } - if (right instanceof Long) { - return number % right.longValue(); - } - if (right instanceof Short) { - return number % right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number shiftLeft(Number right) { - if (right instanceof Byte) { - return number << right.byteValue(); - } - if (right instanceof Integer) { - return number << right.intValue(); - } - if (right instanceof Long) { - return number << right.longValue(); + @Override + public @Nullable Number remainder(Number right) { + if (isIntegralZero(right)) { + return null; + } + if (right instanceof Byte) { + return number % right.byteValue(); + } + if (right instanceof Double) { + return number % right.doubleValue(); + } + if (right instanceof Float) { + return number % right.floatValue(); + } + if (right instanceof Integer) { + return number % right.intValue(); + } + if (right instanceof Long) { + return number % right.longValue(); + } + if (right instanceof Short) { + return number % right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number << right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number signedShiftRight(Number right) { - if (right instanceof Byte) { - return number >> right.byteValue(); - } - if (right instanceof Integer) { - return number >> right.intValue(); - } - if (right instanceof Long) { - return number >> right.longValue(); + @Override + public Number shiftLeft(Number right) { + if (right instanceof Byte) { + return number << right.byteValue(); + } + if (right instanceof Integer) { + return number << right.intValue(); + } + if (right instanceof Long) { + return number << right.longValue(); + } + if (right instanceof Short) { + return number << right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number >> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number unsignedShiftRight(Number right) { - if (right instanceof Byte) { - return number >>> right.byteValue(); - } - if (right instanceof Integer) { - return number >>> right.intValue(); + @Override + public Number signedShiftRight(Number right) { + if (right instanceof Byte) { + return number >> right.byteValue(); + } + if (right instanceof Integer) { + return number >> right.intValue(); + } + if (right instanceof Long) { + return number >> right.longValue(); + } + if (right instanceof Short) { + return number >> right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number >>> right.longValue(); - } - if (right instanceof Short) { - return number >>> right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseAnd(Number right) { - if (right instanceof Byte) { - return number & right.byteValue(); - } - if (right instanceof Integer) { - return number & right.intValue(); - } - if (right instanceof Long) { - return number & right.longValue(); - } - if (right instanceof Short) { - return number & right.shortValue(); + @Override + public Number unsignedShiftRight(Number right) { + if (right instanceof Byte) { + return number >>> right.byteValue(); + } + if (right instanceof Integer) { + return number >>> right.intValue(); + } + if (right instanceof Long) { + return number >>> right.longValue(); + } + if (right instanceof Short) { + return number >>> right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseXor(Number right) { - if (right instanceof Byte) { - return number ^ right.byteValue(); + @Override + public Number bitwiseAnd(Number right) { + if (right instanceof Byte) { + return number & right.byteValue(); + } + if (right instanceof Integer) { + return number & right.intValue(); + } + if (right instanceof Long) { + return number & right.longValue(); + } + if (right instanceof Short) { + return number & right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number ^ right.intValue(); - } - if (right instanceof Long) { - return number ^ right.longValue(); - } - if (right instanceof Short) { - return number ^ right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Number bitwiseOr(Number right) { - if (right instanceof Byte) { - return number | right.byteValue(); - } - if (right instanceof Integer) { - return number | right.intValue(); - } - if (right instanceof Long) { - return number | right.longValue(); + @Override + public Number bitwiseXor(Number right) { + if (right instanceof Byte) { + return number ^ right.byteValue(); + } + if (right instanceof Integer) { + return number ^ right.intValue(); + } + if (right instanceof Long) { + return number ^ right.longValue(); + } + if (right instanceof Short) { + return number ^ right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number | right.shortValue(); - } - throw new UnsupportedOperationException(); - } - - @Override - public Number unaryPlus() { - return +number; - } - - @Override - public Number unaryMinus() { - return -number; - } - @Override - public Number bitwiseComplement() { - return ~number; - } - - @Override - public Boolean equalTo(Number right) { - if (right instanceof Byte) { - return number == right.byteValue(); - } - if (right instanceof Double) { - return number == right.doubleValue(); + @Override + public Number bitwiseOr(Number right) { + if (right instanceof Byte) { + return number | right.byteValue(); + } + if (right instanceof Integer) { + return number | right.intValue(); + } + if (right instanceof Long) { + return number | right.longValue(); + } + if (right instanceof Short) { + return number | right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number == right.floatValue(); - } - if (right instanceof Integer) { - return number == right.intValue(); - } - if (right instanceof Long) { - return number == right.longValue(); - } - if (right instanceof Short) { - return number == right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean notEqualTo(Number right) { - if (right instanceof Byte) { - return number != right.byteValue(); - } - if (right instanceof Double) { - return number != right.doubleValue(); + @Override + public Number unaryPlus() { + return +number; } - if (right instanceof Float) { - return number != right.floatValue(); - } - if (right instanceof Integer) { - return number != right.intValue(); - } - if (right instanceof Long) { - return number != right.longValue(); - } - if (right instanceof Short) { - return number != right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThan(Number right) { - if (right instanceof Byte) { - return number > right.byteValue(); - } - if (right instanceof Double) { - return number > right.doubleValue(); + @Override + public Number unaryMinus() { + return -number; } - if (right instanceof Float) { - return number > right.floatValue(); - } - if (right instanceof Integer) { - return number > right.intValue(); - } - if (right instanceof Long) { - return number > right.longValue(); - } - if (right instanceof Short) { - return number > right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean greaterThanEq(Number right) { - if (right instanceof Byte) { - return number >= right.byteValue(); - } - if (right instanceof Double) { - return number >= right.doubleValue(); + @Override + public Number bitwiseComplement() { + return ~number; } - if (right instanceof Float) { - return number >= right.floatValue(); - } - if (right instanceof Integer) { - return number >= right.intValue(); - } - if (right instanceof Long) { - return number >= right.longValue(); - } - if (right instanceof Short) { - return number >= right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThan(Number right) { - if (right instanceof Byte) { - return number < right.byteValue(); - } - if (right instanceof Double) { - return number < right.doubleValue(); + @Override + public Boolean equalTo(Number right) { + if (right instanceof Byte) { + return number == right.byteValue(); + } + if (right instanceof Double) { + return number == right.doubleValue(); + } + if (right instanceof Float) { + return number == right.floatValue(); + } + if (right instanceof Integer) { + return number == right.intValue(); + } + if (right instanceof Long) { + return number == right.longValue(); + } + if (right instanceof Short) { + return number == right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number < right.floatValue(); - } - if (right instanceof Integer) { - return number < right.intValue(); - } - if (right instanceof Long) { - return number < right.longValue(); - } - if (right instanceof Short) { - return number < right.shortValue(); - } - throw new UnsupportedOperationException(); - } - @Override - public Boolean lessThanEq(Number right) { - if (right instanceof Byte) { - return number <= right.byteValue(); - } - if (right instanceof Double) { - return number <= right.doubleValue(); + @Override + public Boolean notEqualTo(Number right) { + if (right instanceof Byte) { + return number != right.byteValue(); + } + if (right instanceof Double) { + return number != right.doubleValue(); + } + if (right instanceof Float) { + return number != right.floatValue(); + } + if (right instanceof Integer) { + return number != right.intValue(); + } + if (right instanceof Long) { + return number != right.longValue(); + } + if (right instanceof Short) { + return number != right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Float) { - return number <= right.floatValue(); + + @Override + public Boolean greaterThan(Number right) { + if (right instanceof Byte) { + return number > right.byteValue(); + } + if (right instanceof Double) { + return number > right.doubleValue(); + } + if (right instanceof Float) { + return number > right.floatValue(); + } + if (right instanceof Integer) { + return number > right.intValue(); + } + if (right instanceof Long) { + return number > right.longValue(); + } + if (right instanceof Short) { + return number > right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Integer) { - return number <= right.intValue(); + + @Override + public Boolean greaterThanEq(Number right) { + if (right instanceof Byte) { + return number >= right.byteValue(); + } + if (right instanceof Double) { + return number >= right.doubleValue(); + } + if (right instanceof Float) { + return number >= right.floatValue(); + } + if (right instanceof Integer) { + return number >= right.intValue(); + } + if (right instanceof Long) { + return number >= right.longValue(); + } + if (right instanceof Short) { + return number >= right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Long) { - return number <= right.longValue(); + + @Override + public Boolean lessThan(Number right) { + if (right instanceof Byte) { + return number < right.byteValue(); + } + if (right instanceof Double) { + return number < right.doubleValue(); + } + if (right instanceof Float) { + return number < right.floatValue(); + } + if (right instanceof Integer) { + return number < right.intValue(); + } + if (right instanceof Long) { + return number < right.longValue(); + } + if (right instanceof Short) { + return number < right.shortValue(); + } + throw new UnsupportedOperationException(); } - if (right instanceof Short) { - return number <= right.shortValue(); + + @Override + public Boolean lessThanEq(Number right) { + if (right instanceof Byte) { + return number <= right.byteValue(); + } + if (right instanceof Double) { + return number <= right.doubleValue(); + } + if (right instanceof Float) { + return number <= right.floatValue(); + } + if (right instanceof Integer) { + return number <= right.intValue(); + } + if (right instanceof Long) { + return number <= right.longValue(); + } + if (right instanceof Short) { + return number <= right.shortValue(); + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); - } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java index d88a4c27443..b74e7799e3c 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/AnnotationConverter.java @@ -1,16 +1,7 @@ package org.checkerframework.common.wholeprograminference; import com.sun.tools.javac.code.Type.ArrayType; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AnnotationDef; import org.checkerframework.afu.scenelib.annotations.field.AnnotationFieldType; @@ -28,6 +19,18 @@ import org.plumelib.util.ArrayMap; import org.plumelib.util.CollectionsPlume; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + /** * This class contains static methods that convert between {@link * org.checkerframework.afu.scenelib.annotations.Annotation} and {@link @@ -35,181 +38,181 @@ */ public class AnnotationConverter { - /** Creates a new AnnotationConverter. */ - AnnotationConverter() {} + /** Creates a new AnnotationConverter. */ + AnnotationConverter() {} - /** - * Converts an {@link javax.lang.model.element.AnnotationMirror} into an {@link - * org.checkerframework.afu.scenelib.annotations.Annotation}. - * - * @param am the AnnotationMirror - * @return the Annotation - */ - public static Annotation annotationMirrorToAnnotation(AnnotationMirror am) { - @SuppressWarnings("signature:argument") // TODO: bug for inner classes - AnnotationDef def = - new AnnotationDef( - AnnotationUtils.annotationName(am), - String.format( - "annotationMirrorToAnnotation %s [%s] keyset=%s", - am, am.getClass(), am.getElementValues().keySet())); - Map fieldTypes = new ArrayMap<>(am.getElementValues().size()); - // Handling cases where there are fields in annotations. - for (ExecutableElement ee : am.getElementValues().keySet()) { - AnnotationFieldType aft = getAnnotationFieldType(ee); - fieldTypes.put(ee.getSimpleName().toString(), aft); + /** + * Converts an {@link javax.lang.model.element.AnnotationMirror} into an {@link + * org.checkerframework.afu.scenelib.annotations.Annotation}. + * + * @param am the AnnotationMirror + * @return the Annotation + */ + public static Annotation annotationMirrorToAnnotation(AnnotationMirror am) { + @SuppressWarnings("signature:argument") // TODO: bug for inner classes + AnnotationDef def = + new AnnotationDef( + AnnotationUtils.annotationName(am), + String.format( + "annotationMirrorToAnnotation %s [%s] keyset=%s", + am, am.getClass(), am.getElementValues().keySet())); + Map fieldTypes = new ArrayMap<>(am.getElementValues().size()); + // Handling cases where there are fields in annotations. + for (ExecutableElement ee : am.getElementValues().keySet()) { + AnnotationFieldType aft = getAnnotationFieldType(ee); + fieldTypes.put(ee.getSimpleName().toString(), aft); + } + def.setFieldTypes(fieldTypes); + + // Now, we handle the values of those types below + Map values = am.getElementValues(); + Map newValues = new HashMap<>(values.size()); + for (ExecutableElement ee : values.keySet()) { + Object value = values.get(ee).getValue(); + if (value instanceof List) { + // If we have a List here, then it is a List of AnnotationValue. + // Convert each AnnotationValue to its respective Java type. + @SuppressWarnings("unchecked") + List valueList = (List) value; + value = CollectionsPlume.mapList(AnnotationValue::getValue, valueList); + } else if (value instanceof TypeMirror) { + try { + value = Class.forName(TypesUtils.binaryName((TypeMirror) value)); + } catch (ClassNotFoundException e) { + throw new BugInCF(e, "value = %s [%s]", value, value.getClass()); + } + } + newValues.put(ee.getSimpleName().toString(), value); + } + Annotation out = new Annotation(def, newValues); + return out; } - def.setFieldTypes(fieldTypes); - // Now, we handle the values of those types below - Map values = am.getElementValues(); - Map newValues = new HashMap<>(values.size()); - for (ExecutableElement ee : values.keySet()) { - Object value = values.get(ee).getValue(); - if (value instanceof List) { - // If we have a List here, then it is a List of AnnotationValue. - // Convert each AnnotationValue to its respective Java type. - @SuppressWarnings("unchecked") - List valueList = (List) value; - value = CollectionsPlume.mapList(AnnotationValue::getValue, valueList); - } else if (value instanceof TypeMirror) { - try { - value = Class.forName(TypesUtils.binaryName((TypeMirror) value)); - } catch (ClassNotFoundException e) { - throw new BugInCF(e, "value = %s [%s]", value, value.getClass()); + /** + * Converts an {@link org.checkerframework.afu.scenelib.annotations.Annotation} into an {@link + * javax.lang.model.element.AnnotationMirror}. + * + * @param anno the Annotation + * @param processingEnv the ProcessingEnvironment + * @return the AnnotationMirror + */ + protected static AnnotationMirror annotationToAnnotationMirror( + Annotation anno, ProcessingEnvironment processingEnv) { + AnnotationBuilder builder = + new AnnotationBuilder( + processingEnv, Signatures.binaryNameToFullyQualified(anno.def().name)); + for (String fieldKey : anno.fieldValues.keySet()) { + addFieldToAnnotationBuilder(fieldKey, anno.fieldValues.get(fieldKey), builder); } - } - newValues.put(ee.getSimpleName().toString(), value); + return builder.build(); } - Annotation out = new Annotation(def, newValues); - return out; - } - /** - * Converts an {@link org.checkerframework.afu.scenelib.annotations.Annotation} into an {@link - * javax.lang.model.element.AnnotationMirror}. - * - * @param anno the Annotation - * @param processingEnv the ProcessingEnvironment - * @return the AnnotationMirror - */ - protected static AnnotationMirror annotationToAnnotationMirror( - Annotation anno, ProcessingEnvironment processingEnv) { - AnnotationBuilder builder = - new AnnotationBuilder( - processingEnv, Signatures.binaryNameToFullyQualified(anno.def().name)); - for (String fieldKey : anno.fieldValues.keySet()) { - addFieldToAnnotationBuilder(fieldKey, anno.fieldValues.get(fieldKey), builder); + /** + * Returns the type of an element (that is, a field) of an annotation. + * + * @param ee an element (that is, a field) of an annotation + * @return the type of the given annotation field + */ + protected static @Nullable AnnotationFieldType getAnnotationFieldType(ExecutableElement ee) { + return typeMirrorToAnnotationFieldType(ee.getReturnType()); } - return builder.build(); - } - /** - * Returns the type of an element (that is, a field) of an annotation. - * - * @param ee an element (that is, a field) of an annotation - * @return the type of the given annotation field - */ - protected static @Nullable AnnotationFieldType getAnnotationFieldType(ExecutableElement ee) { - return typeMirrorToAnnotationFieldType(ee.getReturnType()); - } + /** + * Converts a TypeMirror to an AnnotationFieldType. + * + * @param tm a type for an annotation element/field: primitive, String, class, enum constant, or + * array thereof + * @return an AnnotationFieldType corresponding to the argument + */ + protected static AnnotationFieldType typeMirrorToAnnotationFieldType(TypeMirror tm) { + switch (tm.getKind()) { + case BOOLEAN: + return BasicAFT.forType(boolean.class); + // Primitives + case BYTE: + return BasicAFT.forType(byte.class); + case CHAR: + return BasicAFT.forType(char.class); + case DOUBLE: + return BasicAFT.forType(double.class); + case FLOAT: + return BasicAFT.forType(float.class); + case INT: + return BasicAFT.forType(int.class); + case LONG: + return BasicAFT.forType(long.class); + case SHORT: + return BasicAFT.forType(short.class); - /** - * Converts a TypeMirror to an AnnotationFieldType. - * - * @param tm a type for an annotation element/field: primitive, String, class, enum constant, or - * array thereof - * @return an AnnotationFieldType corresponding to the argument - */ - protected static AnnotationFieldType typeMirrorToAnnotationFieldType(TypeMirror tm) { - switch (tm.getKind()) { - case BOOLEAN: - return BasicAFT.forType(boolean.class); - // Primitives - case BYTE: - return BasicAFT.forType(byte.class); - case CHAR: - return BasicAFT.forType(char.class); - case DOUBLE: - return BasicAFT.forType(double.class); - case FLOAT: - return BasicAFT.forType(float.class); - case INT: - return BasicAFT.forType(int.class); - case LONG: - return BasicAFT.forType(long.class); - case SHORT: - return BasicAFT.forType(short.class); + case ARRAY: + TypeMirror componentType = ((ArrayType) tm).getComponentType(); + AnnotationFieldType componentAFT = typeMirrorToAnnotationFieldType(componentType); + return new ArrayAFT((ScalarAFT) componentAFT); - case ARRAY: - TypeMirror componentType = ((ArrayType) tm).getComponentType(); - AnnotationFieldType componentAFT = typeMirrorToAnnotationFieldType(componentType); - return new ArrayAFT((ScalarAFT) componentAFT); + case DECLARED: + String className = TypesUtils.getQualifiedName((DeclaredType) tm); + if (className.equals("java.lang.String")) { + return BasicAFT.forType(String.class); + } else if (className.equals("java.lang.Class")) { + return ClassTokenAFT.ctaft; + } else { + // This must be an enum constant. + return new EnumAFT(className); + } - case DECLARED: - String className = TypesUtils.getQualifiedName((DeclaredType) tm); - if (className.equals("java.lang.String")) { - return BasicAFT.forType(String.class); - } else if (className.equals("java.lang.Class")) { - return ClassTokenAFT.ctaft; - } else { - // This must be an enum constant. - return new EnumAFT(className); + default: + throw new BugInCF( + "typeMirrorToAnnotationFieldType: unexpected argument %s [%s %s]", + tm, tm.getKind(), tm.getClass()); } - - default: - throw new BugInCF( - "typeMirrorToAnnotationFieldType: unexpected argument %s [%s %s]", - tm, tm.getKind(), tm.getClass()); } - } - /** - * Adds a field to an AnnotationBuilder. - * - * @param fieldKey is the name of the field - * @param obj is the value of the field - * @param builder is the AnnotationBuilder - */ - @SuppressWarnings("unchecked") // This is actually checked in the first instanceOf call below. - protected static void addFieldToAnnotationBuilder( - String fieldKey, Object obj, AnnotationBuilder builder) { - if (obj instanceof List) { - builder.setValue(fieldKey, (List) obj); - } else if (obj instanceof String) { - builder.setValue(fieldKey, (String) obj); - } else if (obj instanceof Integer) { - builder.setValue(fieldKey, (Integer) obj); - } else if (obj instanceof Float) { - builder.setValue(fieldKey, (Float) obj); - } else if (obj instanceof Long) { - builder.setValue(fieldKey, (Long) obj); - } else if (obj instanceof Boolean) { - builder.setValue(fieldKey, (Boolean) obj); - } else if (obj instanceof Character) { - builder.setValue(fieldKey, (Character) obj); - } else if (obj instanceof Class) { - builder.setValue(fieldKey, (Class) obj); - } else if (obj instanceof Double) { - builder.setValue(fieldKey, (Double) obj); - } else if (obj instanceof Enum) { - builder.setValue(fieldKey, (Enum) obj); - } else if (obj instanceof Enum[]) { - builder.setValue(fieldKey, (Enum[]) obj); - } else if (obj instanceof AnnotationMirror) { - builder.setValue(fieldKey, (AnnotationMirror) obj); - } else if (obj instanceof Object[]) { - builder.setValue(fieldKey, (Object[]) obj); - } else if (obj instanceof TypeMirror) { - builder.setValue(fieldKey, (TypeMirror) obj); - } else if (obj instanceof Short) { - builder.setValue(fieldKey, (Short) obj); - } else if (obj instanceof VariableElement) { - builder.setValue(fieldKey, (VariableElement) obj); - } else if (obj instanceof VariableElement[]) { - builder.setValue(fieldKey, (VariableElement[]) obj); - } else { - throw new BugInCF("Unrecognized type: " + obj.getClass()); + /** + * Adds a field to an AnnotationBuilder. + * + * @param fieldKey is the name of the field + * @param obj is the value of the field + * @param builder is the AnnotationBuilder + */ + @SuppressWarnings("unchecked") // This is actually checked in the first instanceOf call below. + protected static void addFieldToAnnotationBuilder( + String fieldKey, Object obj, AnnotationBuilder builder) { + if (obj instanceof List) { + builder.setValue(fieldKey, (List) obj); + } else if (obj instanceof String) { + builder.setValue(fieldKey, (String) obj); + } else if (obj instanceof Integer) { + builder.setValue(fieldKey, (Integer) obj); + } else if (obj instanceof Float) { + builder.setValue(fieldKey, (Float) obj); + } else if (obj instanceof Long) { + builder.setValue(fieldKey, (Long) obj); + } else if (obj instanceof Boolean) { + builder.setValue(fieldKey, (Boolean) obj); + } else if (obj instanceof Character) { + builder.setValue(fieldKey, (Character) obj); + } else if (obj instanceof Class) { + builder.setValue(fieldKey, (Class) obj); + } else if (obj instanceof Double) { + builder.setValue(fieldKey, (Double) obj); + } else if (obj instanceof Enum) { + builder.setValue(fieldKey, (Enum) obj); + } else if (obj instanceof Enum[]) { + builder.setValue(fieldKey, (Enum[]) obj); + } else if (obj instanceof AnnotationMirror) { + builder.setValue(fieldKey, (AnnotationMirror) obj); + } else if (obj instanceof Object[]) { + builder.setValue(fieldKey, (Object[]) obj); + } else if (obj instanceof TypeMirror) { + builder.setValue(fieldKey, (TypeMirror) obj); + } else if (obj instanceof Short) { + builder.setValue(fieldKey, (Short) obj); + } else if (obj instanceof VariableElement) { + builder.setValue(fieldKey, (VariableElement) obj); + } else if (obj instanceof VariableElement[]) { + builder.setValue(fieldKey, (VariableElement[]) obj); + } else { + throw new BugInCF("Unrecognized type: " + obj.getClass()); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java index bfd21da0445..0cdcb99b9c7 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java @@ -1,26 +1,7 @@ package org.checkerframework.common.wholeprograminference; import com.google.common.collect.ComparisonChain; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; -import java.util.regex.Pattern; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AClass; import org.checkerframework.afu.scenelib.annotations.el.AField; @@ -43,6 +24,28 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.regex.Pattern; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + // In this file, "base name" means "type without its package part in binary name format". // For example, "Outer$Inner" is a base name. @@ -67,850 +70,861 @@ */ public final class SceneToStubWriter { - /** - * The entry point to this class is {@link #write}. - * - *

          This is a utility class with only static methods. It is not instantiable. - */ - private SceneToStubWriter() { - throw new BugInCF("Do not instantiate"); - } - - /** - * A pattern matching the name of an anonymous inner class, a local class, or a class nested - * within one of these types of classes. An anonymous inner class has a basename like Outer$1 and - * a local class has a basename like Outer$1Inner. See Java Language - * Specification, section 13.1. - */ - private static final Pattern anonymousInnerClassOrLocalClassPattern = Pattern.compile("\\$\\d+"); - - /** How far to indent when writing members of a stub file. */ - private static final String INDENT = " "; - - /** - * Writes the annotations in {@code scene} to {@code out} in stub file format. - * - * @param scene the scene to write out - * @param filename the name of the file to write (must end with .astub) - * @param checker the checker, for computing preconditions and postconditions - */ - public static void write(ASceneWrapper scene, String filename, BaseTypeChecker checker) { - writeImpl(scene, filename, checker); - } - - /** - * Returns the part of a binary name that specifies the package. - * - * @param className the binary name of a class - * @return the part of the name referring to the package, or null if there is no package name - */ - @SuppressWarnings("signature") // a valid non-empty package name is a dot separated identifier - private static @Nullable @DotSeparatedIdentifiers String packagePart( - @BinaryName String className) { - int lastdot = className.lastIndexOf('.'); - return (lastdot == -1) ? null : className.substring(0, lastdot); - } - - /** - * Returns the part of a binary name that specifies the basename of the class. - * - * @param className a binary name - * @return the part of the name representing the class's name without its package - */ - @SuppressWarnings("signature:return") // A binary name without its package is still a binary name - private static @BinaryName String basenamePart(@BinaryName String className) { - int lastdot = className.lastIndexOf('.'); - return className.substring(lastdot + 1); - } - - /** - * Returns the String representation of an annotation in Java source format. - * - * @param a the annotation to print - * @return the formatted annotation - */ - public static String formatAnnotation(Annotation a) { - StringBuilder sb = new StringBuilder(); - formatAnnotation(sb, a); - return sb.toString(); - } - - /** - * Formats an annotation in Java source format. - * - * @param sb where to format the annotation to - * @param a the annotation to print - */ - public static void formatAnnotation(StringBuilder sb, Annotation a) { - String fullAnnoName = a.def().name; - String simpleAnnoName = fullAnnoName.substring(fullAnnoName.lastIndexOf('.') + 1); - sb.append("@"); - sb.append(simpleAnnoName); - if (a.fieldValues.isEmpty()) { - return; - } else { - sb.append("("); - if (a.fieldValues.size() == 1 && a.fieldValues.containsKey("value")) { - AnnotationFieldType aft = a.def().fieldTypes.get("value"); - aft.format(sb, a.fieldValues.get("value")); - } else { - // This simulates: new StringJoiner(", ", "@" + simpleAnnoName + "(", ")") - for (Map.Entry f : a.fieldValues.entrySet()) { - AnnotationFieldType aft = a.def().fieldTypes.get(f.getKey()); - sb.append(f.getKey()); - sb.append("="); - aft.format(sb, f.getValue()); - sb.append(", "); - } - sb.delete(sb.length() - 2, sb.length()); - } - sb.append(")"); - } - } - - /** - * Returns all annotations in {@code annos} in a form suitable to be printed as Java source code. - * - *

          Each annotation is followed by a space, to separate it from following Java code. - * - * @param annos the annotations to format - * @return all annotations in {@code annos}, each followed by a space, in a form suitable to be - * printed as Java source code - */ - private static String formatAnnotations(Collection annos) { - StringBuilder sb = new StringBuilder(); - formatAnnotations(sb, annos); - return sb.toString(); - } - - /** - * Prints all annotations in {@code annos} to {@code sb} in a form suitable to be printed as Java - * source code. - * - *

          Each annotation is followed by a space, to separate it from following Java code. - * - * @param sb where to write the formatted annotations - * @param annos the annotations to format - */ - private static void formatAnnotations(StringBuilder sb, Collection annos) { - for (Annotation tla : annos) { - if (!isInternalJDKAnnotation(tla.def.name)) { - formatAnnotation(sb, tla); - sb.append(" "); - } - } - } - - /** - * Formats the type of an array so that it is printable in Java source code, with the annotations - * from the scenelib representation added in appropriate places. The result includes a trailing - * space. - * - * @param sb where to format the array type to - * @param scenelibRep the array's scenelib type element - * @param javacRep the representation of the array's type used by javac - */ - private static void formatArrayType( - StringBuilder sb, ATypeElement scenelibRep, ArrayType javacRep) { - TypeMirror componentType = javacRep.getComponentType(); - ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep); - while (componentType.getKind() == TypeKind.ARRAY) { - componentType = ((ArrayType) componentType).getComponentType(); - scenelibComponent = getNextArrayLevel(scenelibComponent); - } - formatType(sb, scenelibComponent, componentType); - formatArrayTypeImpl(sb, scenelibRep, javacRep); - } - - /** - * Formats the type of an array to be printable in Java source code, with the annotations from the - * scenelib representation added. This method formats only the "array" parts of an array type; it - * does not format (or attempt to format) the ultimate component type (that is, the non-array part - * of the array type). - * - * @param sb where to format the array type to - * @param scenelibRep the scene-lib representation - * @param javacRep the javac representation of the array type - */ - private static void formatArrayTypeImpl( - StringBuilder sb, ATypeElement scenelibRep, ArrayType javacRep) { - TypeMirror javacComponent = javacRep.getComponentType(); - ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep); - List explicitAnnos = javacRep.getAnnotationMirrors(); - for (AnnotationMirror explicitAnno : explicitAnnos) { - sb.append(explicitAnno.toString()); - sb.append(" "); - } - if (explicitAnnos.isEmpty() && scenelibRep != null) { - formatAnnotations(sb, scenelibRep.tlAnnotationsHere); - } - sb.append("[] "); - if (javacComponent.getKind() == TypeKind.ARRAY) { - formatArrayTypeImpl(sb, scenelibComponent, (ArrayType) javacComponent); - } - } - - /** Static mutable variable to improve performance of getNextArrayLevel. */ - private static List location; - - /** - * Gets the outermost array level (or the component if not an array) from the given type element, - * or null if scene-lib is not storing any more information about this array (for example, when - * the component type is unannotated). - * - * @param e the array type element; can be null - * @return the next level of the array, if scene-lib stores information on it. null if the input - * is null or scene-lib is not storing more information. - */ - private static @Nullable ATypeElement getNextArrayLevel(@Nullable ATypeElement e) { - if (e == null) { - return null; + /** + * The entry point to this class is {@link #write}. + * + *

          This is a utility class with only static methods. It is not instantiable. + */ + private SceneToStubWriter() { + throw new BugInCF("Do not instantiate"); } - for (Map.Entry, ATypeElement> ite : e.innerTypes.entrySet()) { - location = ite.getKey(); - if (location.contains(TypePathEntry.ARRAY_ELEMENT)) { - return ite.getValue(); - } + /** + * A pattern matching the name of an anonymous inner class, a local class, or a class nested + * within one of these types of classes. An anonymous inner class has a basename like Outer$1 + * and a local class has a basename like Outer$1Inner. See Java Language + * Specification, section 13.1. + */ + private static final Pattern anonymousInnerClassOrLocalClassPattern = + Pattern.compile("\\$\\d+"); + + /** How far to indent when writing members of a stub file. */ + private static final String INDENT = " "; + + /** + * Writes the annotations in {@code scene} to {@code out} in stub file format. + * + * @param scene the scene to write out + * @param filename the name of the file to write (must end with .astub) + * @param checker the checker, for computing preconditions and postconditions + */ + public static void write(ASceneWrapper scene, String filename, BaseTypeChecker checker) { + writeImpl(scene, filename, checker); } - return null; - } - - /** - * Formats a single formal parameter declaration. - * - * @param param the AField that represents the parameter - * @param parameterName the name of the parameter to display in the stub file. Stub files - * disregard formal parameter names, so this is aesthetic in almost all cases. The exception - * is the receiver parameter, whose name must be "this". - * @param basename the type name to use for the receiver parameter. Only used when the previous - * argument is exactly the String "this". - * @return the formatted formal parameter, as if it were written in Java source code - */ - private static String formatParameter(AField param, String parameterName, String basename) { - StringBuilder sb = new StringBuilder(); - formatParameter(sb, param, parameterName, basename); - return sb.toString(); - } - - /** - * Formats a single formal parameter declaration, as if it were written in Java source code. - * - * @param sb where to format the formal parameter to - * @param param the AField that represents the parameter - * @param parameterName the name of the parameter to display in the stub file. Stub files - * disregard formal parameter names, so this is aesthetic in almost all cases. The exception - * is the receiver parameter, whose name must be "this". - * @param basename the type name to use for the receiver parameter. Only used when the previous - * argument is exactly the String "this". - */ - private static void formatParameter( - StringBuilder sb, AField param, String parameterName, String basename) { - if (!param.tlAnnotationsHere.isEmpty()) { - for (Annotation declAnno : param.tlAnnotationsHere) { - formatAnnotation(sb, declAnno); - sb.append(" "); - } - sb.delete(sb.length() - 1, sb.length()); + + /** + * Returns the part of a binary name that specifies the package. + * + * @param className the binary name of a class + * @return the part of the name referring to the package, or null if there is no package name + */ + @SuppressWarnings("signature") // a valid non-empty package name is a dot separated identifier + private static @Nullable @DotSeparatedIdentifiers String packagePart( + @BinaryName String className) { + int lastdot = className.lastIndexOf('.'); + return (lastdot == -1) ? null : className.substring(0, lastdot); } - formatAFieldImpl(sb, param, parameterName, basename); - } - - /** - * Formats a field declaration or formal parameter so that it can be printed in a stub. - * - *

          This method does not add a trailing semicolon or comma. - * - *

          Usually, {@link #formatParameter(AField, String, String)} should be called to format method - * parameters, and {@link #printField(AField, String, PrintWriter, String)} should be called to - * print field declarations. Both use this method as their underlying implementation. - * - * @param aField the field declaration or formal parameter declaration to format; should not - * represent a local variable - * @param fieldName the name to use for the declaration in the stub file. This doesn't matter for - * parameters (except the "this" receiver parameter), but must be correct for fields. - * @param className the simple name of the enclosing class. This is only used for printing the - * type of an explicit receiver parameter (i.e., a parameter named "this"). - * @return a String suitable to print in a stub file - */ - private static String formatAFieldImpl(AField aField, String fieldName, String className) { - StringBuilder sb = new StringBuilder(); - formatAFieldImpl(sb, aField, fieldName, className); - return sb.toString(); - } - - /** - * Formats a field declaration or formal parameter so that it can be printed in a stub file. - * - *

          This method does not add a trailing semicolon or comma. - * - *

          Usually, {@link #formatParameter(AField, String, String)} should be called to format method - * parameters, and {@link #printField(AField, String, PrintWriter, String)} should be called to - * print field declarations. Both use this method as their underlying implementation. - * - * @param sb where to write the formatted declaration to - * @param aField the field declaration or formal parameter declaration to format; should not - * represent a local variable - * @param fieldName the name to use for the declaration in the stub file. This doesn't matter for - * parameters (except the "this" receiver parameter), but must be correct for fields. - * @param className the simple name of the enclosing class. This is only used for printing the - * type of an explicit receiver parameter (i.e., a parameter named "this"). - */ - private static void formatAFieldImpl( - StringBuilder sb, AField aField, String fieldName, String className) { - if ("this".equals(fieldName)) { - formatType(sb, aField.type, null, className); - } else { - formatType(sb, aField.type, aField.getTypeMirror()); + + /** + * Returns the part of a binary name that specifies the basename of the class. + * + * @param className a binary name + * @return the part of the name representing the class's name without its package + */ + @SuppressWarnings( + "signature:return") // A binary name without its package is still a binary name + private static @BinaryName String basenamePart(@BinaryName String className) { + int lastdot = className.lastIndexOf('.'); + return className.substring(lastdot + 1); } - sb.append(fieldName); - } - - /** - * Formats the given type for printing in Java source code. - * - * @param aType the scene-lib representation of the type, or null if only the unannotated type is - * to be printed - * @param javacType the javac representation of the type - * @return the type as it would appear in Java source code, followed by a trailing space - */ - private static String formatType(@Nullable ATypeElement aType, TypeMirror javacType) { - StringBuilder sb = new StringBuilder(); - formatType(sb, aType, javacType); - return sb.toString(); - } - - /** - * Formats the given type as it would appear in Java source code, followed by a trailing space. - * - * @param sb where to format the type to - * @param aType the scene-lib representation of the type, or null if only the unannotated type is - * to be printed - * @param javacType the javac representation of the type - */ - private static void formatType( - StringBuilder sb, @Nullable ATypeElement aType, TypeMirror javacType) { - // TypeMirror#toString prints multiple annotations on a single type - // separated by commas rather than by whitespace, as is required in source code. - String basetypeToPrint = javacType.toString().replaceAll(",@", " @"); - - // We must not print annotations in the default package that conflict with - // imported annotation names. - for (AnnotationMirror anm : javacType.getAnnotationMirrors()) { - String annotationName = AnnotationUtils.annotationName(anm); - String simpleName = annotationName.substring(annotationName.lastIndexOf('.') + 1); - // This checks if it is in the default package. - if (simpleName.equals(annotationName)) { - // In that case, do not print any annotations with the type, to - // avoid needing to parse an annotation string to remove it. - // TypeMirror does not provide any methods to remove annotations. - // This code relies on unannotated Java types not including spaces. - basetypeToPrint = basetypeToPrint.substring(basetypeToPrint.lastIndexOf(' ') + 1); - } + + /** + * Returns the String representation of an annotation in Java source format. + * + * @param a the annotation to print + * @return the formatted annotation + */ + public static String formatAnnotation(Annotation a) { + StringBuilder sb = new StringBuilder(); + formatAnnotation(sb, a); + return sb.toString(); } - formatType(sb, aType, javacType, basetypeToPrint); - } - - /** - * Formats the given type as it would appear in Java source code, followed by a trailing space. - * - *

          This overloaded version of this method exists only for receiver parameters, which are - * printed using the name of the class as {@code basetypeToPrint} instead of the javac type. The - * other version of this method should be preferred in every other case. - * - * @param sb where to formate the type to - * @param aType the scene-lib representation of the type, or null if only the unannotated type is - * to be printed - * @param javacType the javac representation of the type, or null if this is a receiver parameter - * @param basetypeToPrint the string representation of the type - */ - private static void formatType( - StringBuilder sb, - @Nullable ATypeElement aType, - @Nullable TypeMirror javacType, - String basetypeToPrint) { - // anonymous static classes shouldn't be printed with the "anonymous" tag that the AScene - // library uses - if (basetypeToPrint.startsWith(" f : a.fieldValues.entrySet()) { + AnnotationFieldType aft = a.def().fieldTypes.get(f.getKey()); + sb.append(f.getKey()); + sb.append("="); + aft.format(sb, f.getValue()); + sb.append(", "); + } + sb.delete(sb.length() - 2, sb.length()); + } + sb.append(")"); + } } - // fields don't need their generic types, and sometimes they are wrong. Just don't print - // them. - while (basetypeToPrint.contains("<")) { - int openCount = 1; - int pos = basetypeToPrint.indexOf('<'); - while (openCount > 0) { - pos++; - if (basetypeToPrint.charAt(pos) == '<') { - openCount++; - } - if (basetypeToPrint.charAt(pos) == '>') { - openCount--; - } - } - basetypeToPrint = - basetypeToPrint.substring(0, basetypeToPrint.indexOf('<')) - + basetypeToPrint.substring(pos + 1); + /** + * Returns all annotations in {@code annos} in a form suitable to be printed as Java source + * code. + * + *

          Each annotation is followed by a space, to separate it from following Java code. + * + * @param annos the annotations to format + * @return all annotations in {@code annos}, each followed by a space, in a form suitable to be + * printed as Java source code + */ + private static String formatAnnotations(Collection annos) { + StringBuilder sb = new StringBuilder(); + formatAnnotations(sb, annos); + return sb.toString(); } - // An array is not a receiver, so using the javacType to check for arrays is safe. - if (javacType != null && javacType.getKind() == TypeKind.ARRAY) { - formatArrayType(sb, aType, (ArrayType) javacType); - return; + /** + * Prints all annotations in {@code annos} to {@code sb} in a form suitable to be printed as + * Java source code. + * + *

          Each annotation is followed by a space, to separate it from following Java code. + * + * @param sb where to write the formatted annotations + * @param annos the annotations to format + */ + private static void formatAnnotations( + StringBuilder sb, Collection annos) { + for (Annotation tla : annos) { + if (!isInternalJDKAnnotation(tla.def.name)) { + formatAnnotation(sb, tla); + sb.append(" "); + } + } } - if (aType != null) { - formatAnnotations(sb, aType.tlAnnotationsHere); + /** + * Formats the type of an array so that it is printable in Java source code, with the + * annotations from the scenelib representation added in appropriate places. The result includes + * a trailing space. + * + * @param sb where to format the array type to + * @param scenelibRep the array's scenelib type element + * @param javacRep the representation of the array's type used by javac + */ + private static void formatArrayType( + StringBuilder sb, ATypeElement scenelibRep, ArrayType javacRep) { + TypeMirror componentType = javacRep.getComponentType(); + ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep); + while (componentType.getKind() == TypeKind.ARRAY) { + componentType = ((ArrayType) componentType).getComponentType(); + scenelibComponent = getNextArrayLevel(scenelibComponent); + } + formatType(sb, scenelibComponent, componentType); + formatArrayTypeImpl(sb, scenelibRep, javacRep); } - sb.append(basetypeToPrint); - sb.append(" "); - } - /** Writes an import statement for each annotation used in an {@link AScene}. */ - private static class ImportDefWriter extends DefCollector { + /** + * Formats the type of an array to be printable in Java source code, with the annotations from + * the scenelib representation added. This method formats only the "array" parts of an array + * type; it does not format (or attempt to format) the ultimate component type (that is, the + * non-array part of the array type). + * + * @param sb where to format the array type to + * @param scenelibRep the scene-lib representation + * @param javacRep the javac representation of the array type + */ + private static void formatArrayTypeImpl( + StringBuilder sb, ATypeElement scenelibRep, ArrayType javacRep) { + TypeMirror javacComponent = javacRep.getComponentType(); + ATypeElement scenelibComponent = getNextArrayLevel(scenelibRep); + List explicitAnnos = javacRep.getAnnotationMirrors(); + for (AnnotationMirror explicitAnno : explicitAnnos) { + sb.append(explicitAnno.toString()); + sb.append(" "); + } + if (explicitAnnos.isEmpty() && scenelibRep != null) { + formatAnnotations(sb, scenelibRep.tlAnnotationsHere); + } + sb.append("[] "); + if (javacComponent.getKind() == TypeKind.ARRAY) { + formatArrayTypeImpl(sb, scenelibComponent, (ArrayType) javacComponent); + } + } - /** The writer onto which to write the import statements. */ - private final PrintWriter printWriter; + /** Static mutable variable to improve performance of getNextArrayLevel. */ + private static List location; /** - * Constructs a new ImportDefWriter, which will run on the given AScene when its {@code visit} - * method is called. + * Gets the outermost array level (or the component if not an array) from the given type + * element, or null if scene-lib is not storing any more information about this array (for + * example, when the component type is unannotated). * - * @param scene the scene whose imported annotations should be printed - * @param printWriter the writer onto which to write the import statements - * @throws DefException if the DefCollector does not succeed + * @param e the array type element; can be null + * @return the next level of the array, if scene-lib stores information on it. null if the input + * is null or scene-lib is not storing more information. */ - ImportDefWriter(ASceneWrapper scene, PrintWriter printWriter) throws DefException { - super(scene.getAScene()); - this.printWriter = printWriter; + private static @Nullable ATypeElement getNextArrayLevel(@Nullable ATypeElement e) { + if (e == null) { + return null; + } + + for (Map.Entry, ATypeElement> ite : e.innerTypes.entrySet()) { + location = ite.getKey(); + if (location.contains(TypePathEntry.ARRAY_ELEMENT)) { + return ite.getValue(); + } + } + return null; } /** - * Write an import statement for a given AnnotationDef. This is only called once per annotation - * used in the scene. + * Formats a single formal parameter declaration. * - * @param d the annotation definition to print an import for + * @param param the AField that represents the parameter + * @param parameterName the name of the parameter to display in the stub file. Stub files + * disregard formal parameter names, so this is aesthetic in almost all cases. The exception + * is the receiver parameter, whose name must be "this". + * @param basename the type name to use for the receiver parameter. Only used when the previous + * argument is exactly the String "this". + * @return the formatted formal parameter, as if it were written in Java source code */ - @Override - protected void visitAnnotationDef(AnnotationDef d) { - if (!isInternalJDKAnnotation(d.name)) { - printWriter.println("import " + d.name + ";"); - } + private static String formatParameter(AField param, String parameterName, String basename) { + StringBuilder sb = new StringBuilder(); + formatParameter(sb, param, parameterName, basename); + return sb.toString(); } - } - - /** - * Return true if the given annotation is an internal JDK annotation, whose name includes '+'. - * - * @param annotationName the name of the annotation - * @return true iff this is an internal JDK annotation - */ - private static boolean isInternalJDKAnnotation(String annotationName) { - return annotationName.contains("+"); - } - - /** - * Print the hierarchy of outer classes up to and including the given class, and return the number - * of curly braces to close with. The classes are printed with appropriate opening curly braces, - * in standard Java style. - * - *

          In an AScene, an inner class name is a binary name like "Outer$Inner". In a stub file, inner - * classes must be nested, as in Java source code. - * - * @param basename the binary name of the class without the package part - * @param aClass the AClass for {@code basename} - * @param printWriter the writer where the class definition should be printed - * @param checker the type-checker whose annotations are being written - * @return the number of outer classes within which this class is nested - */ - private static int printClassDefinitions( - String basename, AClass aClass, PrintWriter printWriter, BaseTypeChecker checker) { - String[] classNames = basename.split("\\$"); - TypeElement innermostTypeElt = aClass.getTypeElement(); - if (innermostTypeElt == null) { - throw new BugInCF("typeElement was unexpectedly null in this aClass: " + aClass); + + /** + * Formats a single formal parameter declaration, as if it were written in Java source code. + * + * @param sb where to format the formal parameter to + * @param param the AField that represents the parameter + * @param parameterName the name of the parameter to display in the stub file. Stub files + * disregard formal parameter names, so this is aesthetic in almost all cases. The exception + * is the receiver parameter, whose name must be "this". + * @param basename the type name to use for the receiver parameter. Only used when the previous + * argument is exactly the String "this". + */ + private static void formatParameter( + StringBuilder sb, AField param, String parameterName, String basename) { + if (!param.tlAnnotationsHere.isEmpty()) { + for (Annotation declAnno : param.tlAnnotationsHere) { + formatAnnotation(sb, declAnno); + sb.append(" "); + } + sb.delete(sb.length() - 1, sb.length()); + } + formatAFieldImpl(sb, param, parameterName, basename); } - TypeElement[] typeElements = getTypeElementsForClasses(innermostTypeElt, classNames); - - for (int i = 0; i < classNames.length; i++) { - String nameToPrint = classNames[i]; - if (i == classNames.length - 1) { - printWriter.print(indents(i)); - printWriter.println("@AnnotatedFor(\"" + checker.getClass().getCanonicalName() + "\")"); - } - printWriter.print(indents(i)); - if (i == classNames.length - 1) { - // Only print class annotations on the innermost class, which corresponds to aClass. - // If there should be class annotations on another class, it will have its own stub - // file, which will eventually be merged with this one. - printWriter.print(formatAnnotations(aClass.getAnnotations())); - } - if (aClass.isAnnotation(nameToPrint)) { - printWriter.print("@interface "); - } else if (aClass.isEnum(nameToPrint)) { - printWriter.print("enum "); - } else if (aClass.isInterface(nameToPrint)) { - printWriter.print("interface "); - } else if (aClass.isRecord(nameToPrint)) { - printWriter.print("record "); - } else { - printWriter.print("class "); - } - printWriter.print(nameToPrint); - printTypeParameters(typeElements[i], printWriter); - printWriter.println(" {"); - if (aClass.isEnum(nameToPrint) && i != classNames.length - 1) { - // Print a blank set of enum constants if this is an outer enum. - printWriter.println(indents(i + 1) + "/* omitted enum constants */ ;"); - } - printWriter.println(); + + /** + * Formats a field declaration or formal parameter so that it can be printed in a stub. + * + *

          This method does not add a trailing semicolon or comma. + * + *

          Usually, {@link #formatParameter(AField, String, String)} should be called to format + * method parameters, and {@link #printField(AField, String, PrintWriter, String)} should be + * called to print field declarations. Both use this method as their underlying implementation. + * + * @param aField the field declaration or formal parameter declaration to format; should not + * represent a local variable + * @param fieldName the name to use for the declaration in the stub file. This doesn't matter + * for parameters (except the "this" receiver parameter), but must be correct for fields. + * @param className the simple name of the enclosing class. This is only used for printing the + * type of an explicit receiver parameter (i.e., a parameter named "this"). + * @return a String suitable to print in a stub file + */ + private static String formatAFieldImpl(AField aField, String fieldName, String className) { + StringBuilder sb = new StringBuilder(); + formatAFieldImpl(sb, aField, fieldName, className); + return sb.toString(); } - return classNames.length; - } - - /** - * Constructs an array of TypeElements corresponding to the list of classes. - * - * @param innermostTypeElt the innermost type element: either an inner class or an outer class - * without any inner classes that should be printed - * @param classNames the names of the containing classes, from outer to inner - * @return an array of TypeElements whose entry at a given index represents the type named at that - * index in {@code classNames} - */ - private static TypeElement @SameLen("#2") [] getTypeElementsForClasses( - TypeElement innermostTypeElt, String @MinLen(1) [] classNames) { - TypeElement[] result = new TypeElement[classNames.length]; - result[classNames.length - 1] = innermostTypeElt; - Element elt = innermostTypeElt; - for (int i = classNames.length - 2; i >= 0; i--) { - elt = elt.getEnclosingElement(); - result[i] = (TypeElement) elt; + + /** + * Formats a field declaration or formal parameter so that it can be printed in a stub file. + * + *

          This method does not add a trailing semicolon or comma. + * + *

          Usually, {@link #formatParameter(AField, String, String)} should be called to format + * method parameters, and {@link #printField(AField, String, PrintWriter, String)} should be + * called to print field declarations. Both use this method as their underlying implementation. + * + * @param sb where to write the formatted declaration to + * @param aField the field declaration or formal parameter declaration to format; should not + * represent a local variable + * @param fieldName the name to use for the declaration in the stub file. This doesn't matter + * for parameters (except the "this" receiver parameter), but must be correct for fields. + * @param className the simple name of the enclosing class. This is only used for printing the + * type of an explicit receiver parameter (i.e., a parameter named "this"). + */ + private static void formatAFieldImpl( + StringBuilder sb, AField aField, String fieldName, String className) { + if ("this".equals(fieldName)) { + formatType(sb, aField.type, null, className); + } else { + formatType(sb, aField.type, aField.getTypeMirror()); + } + sb.append(fieldName); } - return result; - } - - /** - * Prints all the fields of a given class. - * - * @param aClass the class whose fields should be printed - * @param printWriter the writer on which to print the fields - * @param indentLevel the indent string - */ - private static void printFields(AClass aClass, PrintWriter printWriter, String indentLevel) { - - if (aClass.getFields().isEmpty()) { - return; + + /** + * Formats the given type for printing in Java source code. + * + * @param aType the scene-lib representation of the type, or null if only the unannotated type + * is to be printed + * @param javacType the javac representation of the type + * @return the type as it would appear in Java source code, followed by a trailing space + */ + private static String formatType(@Nullable ATypeElement aType, TypeMirror javacType) { + StringBuilder sb = new StringBuilder(); + formatType(sb, aType, javacType); + return sb.toString(); } - printWriter.println(indentLevel + "// fields:"); - printWriter.println(); - for (Map.Entry fieldEntry : aClass.getFields().entrySet()) { - String fieldName = fieldEntry.getKey(); - AField aField = fieldEntry.getValue(); - printField(aField, fieldName, printWriter, indentLevel); + /** + * Formats the given type as it would appear in Java source code, followed by a trailing space. + * + * @param sb where to format the type to + * @param aType the scene-lib representation of the type, or null if only the unannotated type + * is to be printed + * @param javacType the javac representation of the type + */ + private static void formatType( + StringBuilder sb, @Nullable ATypeElement aType, TypeMirror javacType) { + // TypeMirror#toString prints multiple annotations on a single type + // separated by commas rather than by whitespace, as is required in source code. + String basetypeToPrint = javacType.toString().replaceAll(",@", " @"); + + // We must not print annotations in the default package that conflict with + // imported annotation names. + for (AnnotationMirror anm : javacType.getAnnotationMirrors()) { + String annotationName = AnnotationUtils.annotationName(anm); + String simpleName = annotationName.substring(annotationName.lastIndexOf('.') + 1); + // This checks if it is in the default package. + if (simpleName.equals(annotationName)) { + // In that case, do not print any annotations with the type, to + // avoid needing to parse an annotation string to remove it. + // TypeMirror does not provide any methods to remove annotations. + // This code relies on unannotated Java types not including spaces. + basetypeToPrint = basetypeToPrint.substring(basetypeToPrint.lastIndexOf(' ') + 1); + } + } + formatType(sb, aType, javacType, basetypeToPrint); } - } - - /** - * Prints a field declaration, including a trailing semicolon and a newline. - * - * @param aField the field declaration - * @param fieldName the name of the field - * @param printWriter the writer on which to print - * @param indentLevel the indent string - */ - private static void printField( - AField aField, String fieldName, PrintWriter printWriter, String indentLevel) { - if (aField.getTypeMirror() == null) { - // aField has no type mirror, so there are no inferred annotations and the field need - // not be printed. - return; + + /** + * Formats the given type as it would appear in Java source code, followed by a trailing space. + * + *

          This overloaded version of this method exists only for receiver parameters, which are + * printed using the name of the class as {@code basetypeToPrint} instead of the javac type. The + * other version of this method should be preferred in every other case. + * + * @param sb where to formate the type to + * @param aType the scene-lib representation of the type, or null if only the unannotated type + * is to be printed + * @param javacType the javac representation of the type, or null if this is a receiver + * parameter + * @param basetypeToPrint the string representation of the type + */ + private static void formatType( + StringBuilder sb, + @Nullable ATypeElement aType, + @Nullable TypeMirror javacType, + String basetypeToPrint) { + // anonymous static classes shouldn't be printed with the "anonymous" tag that the AScene + // library uses + if (basetypeToPrint.startsWith(" 0) { + pos++; + if (basetypeToPrint.charAt(pos) == '<') { + openCount++; + } + if (basetypeToPrint.charAt(pos) == '>') { + openCount--; + } + } + basetypeToPrint = + basetypeToPrint.substring(0, basetypeToPrint.indexOf('<')) + + basetypeToPrint.substring(pos + 1); + } + + // An array is not a receiver, so using the javacType to check for arrays is safe. + if (javacType != null && javacType.getKind() == TypeKind.ARRAY) { + formatArrayType(sb, aType, (ArrayType) javacType); + return; + } + + if (aType != null) { + formatAnnotations(sb, aType.tlAnnotationsHere); + } + sb.append(basetypeToPrint); + sb.append(" "); } - for (Annotation declAnno : aField.tlAnnotationsHere) { - printWriter.print(indentLevel); - printWriter.println(formatAnnotation(declAnno)); + /** Writes an import statement for each annotation used in an {@link AScene}. */ + private static class ImportDefWriter extends DefCollector { + + /** The writer onto which to write the import statements. */ + private final PrintWriter printWriter; + + /** + * Constructs a new ImportDefWriter, which will run on the given AScene when its {@code + * visit} method is called. + * + * @param scene the scene whose imported annotations should be printed + * @param printWriter the writer onto which to write the import statements + * @throws DefException if the DefCollector does not succeed + */ + ImportDefWriter(ASceneWrapper scene, PrintWriter printWriter) throws DefException { + super(scene.getAScene()); + this.printWriter = printWriter; + } + + /** + * Write an import statement for a given AnnotationDef. This is only called once per + * annotation used in the scene. + * + * @param d the annotation definition to print an import for + */ + @Override + protected void visitAnnotationDef(AnnotationDef d) { + if (!isInternalJDKAnnotation(d.name)) { + printWriter.println("import " + d.name + ";"); + } + } } - printWriter.print(indentLevel); - printWriter.print(formatAFieldImpl(aField, fieldName, /*enclosing class=*/ null)); - printWriter.println(";"); - printWriter.println(); - } - - /** - * Prints a method declaration in stub file format (i.e., without a method body). - * - * @param aMethod the method to print - * @param simplename the simple name of the enclosing class, for receiver parameters and - * constructor names - * @param printWriter where to print the method signature - * @param atf the type factory, for computing preconditions and postconditions - * @param indentLevel the indent string - */ - private static void printMethodDeclaration( - AMethod aMethod, - String simplename, - PrintWriter printWriter, - String indentLevel, - GenericAnnotatedTypeFactory atf) { - - if (aMethod.getTypeParameters() == null) { - // aMethod.setFieldsFromMethodElement has not been called - return; + /** + * Return true if the given annotation is an internal JDK annotation, whose name includes '+'. + * + * @param annotationName the name of the annotation + * @return true iff this is an internal JDK annotation + */ + private static boolean isInternalJDKAnnotation(String annotationName) { + return annotationName.contains("+"); } - for (Annotation declAnno : aMethod.tlAnnotationsHere) { - printWriter.print(indentLevel); - printWriter.println(formatAnnotation(declAnno)); + /** + * Print the hierarchy of outer classes up to and including the given class, and return the + * number of curly braces to close with. The classes are printed with appropriate opening curly + * braces, in standard Java style. + * + *

          In an AScene, an inner class name is a binary name like "Outer$Inner". In a stub file, + * inner classes must be nested, as in Java source code. + * + * @param basename the binary name of the class without the package part + * @param aClass the AClass for {@code basename} + * @param printWriter the writer where the class definition should be printed + * @param checker the type-checker whose annotations are being written + * @return the number of outer classes within which this class is nested + */ + private static int printClassDefinitions( + String basename, AClass aClass, PrintWriter printWriter, BaseTypeChecker checker) { + String[] classNames = basename.split("\\$"); + TypeElement innermostTypeElt = aClass.getTypeElement(); + if (innermostTypeElt == null) { + throw new BugInCF("typeElement was unexpectedly null in this aClass: " + aClass); + } + TypeElement[] typeElements = getTypeElementsForClasses(innermostTypeElt, classNames); + + for (int i = 0; i < classNames.length; i++) { + String nameToPrint = classNames[i]; + if (i == classNames.length - 1) { + printWriter.print(indents(i)); + printWriter.println( + "@AnnotatedFor(\"" + checker.getClass().getCanonicalName() + "\")"); + } + printWriter.print(indents(i)); + if (i == classNames.length - 1) { + // Only print class annotations on the innermost class, which corresponds to aClass. + // If there should be class annotations on another class, it will have its own stub + // file, which will eventually be merged with this one. + printWriter.print(formatAnnotations(aClass.getAnnotations())); + } + if (aClass.isAnnotation(nameToPrint)) { + printWriter.print("@interface "); + } else if (aClass.isEnum(nameToPrint)) { + printWriter.print("enum "); + } else if (aClass.isInterface(nameToPrint)) { + printWriter.print("interface "); + } else if (aClass.isRecord(nameToPrint)) { + printWriter.print("record "); + } else { + printWriter.print("class "); + } + printWriter.print(nameToPrint); + printTypeParameters(typeElements[i], printWriter); + printWriter.println(" {"); + if (aClass.isEnum(nameToPrint) && i != classNames.length - 1) { + // Print a blank set of enum constants if this is an outer enum. + printWriter.println(indents(i + 1) + "/* omitted enum constants */ ;"); + } + printWriter.println(); + } + return classNames.length; } - for (AnnotationMirror contractAnno : atf.getContractAnnotations(aMethod)) { - printWriter.print(indentLevel); - printWriter.println(contractAnno); + /** + * Constructs an array of TypeElements corresponding to the list of classes. + * + * @param innermostTypeElt the innermost type element: either an inner class or an outer class + * without any inner classes that should be printed + * @param classNames the names of the containing classes, from outer to inner + * @return an array of TypeElements whose entry at a given index represents the type named at + * that index in {@code classNames} + */ + private static TypeElement @SameLen("#2") [] getTypeElementsForClasses( + TypeElement innermostTypeElt, String @MinLen(1) [] classNames) { + TypeElement[] result = new TypeElement[classNames.length]; + result[classNames.length - 1] = innermostTypeElt; + Element elt = innermostTypeElt; + for (int i = classNames.length - 2; i >= 0; i--) { + elt = elt.getEnclosingElement(); + result[i] = (TypeElement) elt; + } + return result; } - printWriter.print(indentLevel); + /** + * Prints all the fields of a given class. + * + * @param aClass the class whose fields should be printed + * @param printWriter the writer on which to print the fields + * @param indentLevel the indent string + */ + private static void printFields(AClass aClass, PrintWriter printWriter, String indentLevel) { - printTypeParameters(aMethod.getTypeParameters(), printWriter); + if (aClass.getFields().isEmpty()) { + return; + } - String methodName = aMethod.getMethodName(); - // Use Java syntax for constructors. - if ("".equals(methodName)) { - // Set methodName, but don't output a return type. - methodName = simplename; - } else { - printWriter.print(formatType(aMethod.returnType, aMethod.getReturnTypeMirror())); + printWriter.println(indentLevel + "// fields:"); + printWriter.println(); + for (Map.Entry fieldEntry : aClass.getFields().entrySet()) { + String fieldName = fieldEntry.getKey(); + AField aField = fieldEntry.getValue(); + printField(aField, fieldName, printWriter, indentLevel); + } } - printWriter.print(methodName); - printWriter.print("("); - StringJoiner parameters = new StringJoiner(", "); - if (!aMethod.receiver.type.tlAnnotationsHere.isEmpty()) { - // Only output the receiver if it has an annotation. - parameters.add(formatParameter(aMethod.receiver, "this", simplename)); + /** + * Prints a field declaration, including a trailing semicolon and a newline. + * + * @param aField the field declaration + * @param fieldName the name of the field + * @param printWriter the writer on which to print + * @param indentLevel the indent string + */ + private static void printField( + AField aField, String fieldName, PrintWriter printWriter, String indentLevel) { + if (aField.getTypeMirror() == null) { + // aField has no type mirror, so there are no inferred annotations and the field need + // not be printed. + return; + } + + for (Annotation declAnno : aField.tlAnnotationsHere) { + printWriter.print(indentLevel); + printWriter.println(formatAnnotation(declAnno)); + } + + printWriter.print(indentLevel); + printWriter.print(formatAFieldImpl(aField, fieldName, /*enclosing class=*/ null)); + printWriter.println(";"); + printWriter.println(); } - for (Integer index : aMethod.getParameters().keySet()) { - AField param = aMethod.getParameters().get(index); - parameters.add(formatParameter(param, param.getName(), simplename)); + + /** + * Prints a method declaration in stub file format (i.e., without a method body). + * + * @param aMethod the method to print + * @param simplename the simple name of the enclosing class, for receiver parameters and + * constructor names + * @param printWriter where to print the method signature + * @param atf the type factory, for computing preconditions and postconditions + * @param indentLevel the indent string + */ + private static void printMethodDeclaration( + AMethod aMethod, + String simplename, + PrintWriter printWriter, + String indentLevel, + GenericAnnotatedTypeFactory atf) { + + if (aMethod.getTypeParameters() == null) { + // aMethod.setFieldsFromMethodElement has not been called + return; + } + + for (Annotation declAnno : aMethod.tlAnnotationsHere) { + printWriter.print(indentLevel); + printWriter.println(formatAnnotation(declAnno)); + } + + for (AnnotationMirror contractAnno : atf.getContractAnnotations(aMethod)) { + printWriter.print(indentLevel); + printWriter.println(contractAnno); + } + + printWriter.print(indentLevel); + + printTypeParameters(aMethod.getTypeParameters(), printWriter); + + String methodName = aMethod.getMethodName(); + // Use Java syntax for constructors. + if ("".equals(methodName)) { + // Set methodName, but don't output a return type. + methodName = simplename; + } else { + printWriter.print(formatType(aMethod.returnType, aMethod.getReturnTypeMirror())); + } + printWriter.print(methodName); + printWriter.print("("); + + StringJoiner parameters = new StringJoiner(", "); + if (!aMethod.receiver.type.tlAnnotationsHere.isEmpty()) { + // Only output the receiver if it has an annotation. + parameters.add(formatParameter(aMethod.receiver, "this", simplename)); + } + for (Integer index : aMethod.getParameters().keySet()) { + AField param = aMethod.getParameters().get(index); + parameters.add(formatParameter(param, param.getName(), simplename)); + } + printWriter.print(parameters.toString()); + printWriter.println(");"); + printWriter.println(); } - printWriter.print(parameters.toString()); - printWriter.println(");"); - printWriter.println(); - } - - /** - * The implementation of {@link #write}. Prints imports, classes, method signatures, and fields in - * stub file format, all with appropriate annotations. - * - * @param scene the scene to write - * @param filename the name of the file to write (must end in .astub) - * @param checker the checker, for computing preconditions - */ - private static void writeImpl(ASceneWrapper scene, String filename, BaseTypeChecker checker) { - // Sort by package name first so that output is deterministic and default package - // comes first; within package sort by class name. - @SuppressWarnings("signature") // scene-lib bytecode lacks signature annotations - List<@BinaryName String> classes = new ArrayList<>(scene.getAScene().getClasses().keySet()); - Collections.sort( - classes, - (o1, o2) -> - ComparisonChain.start() - .compare( - packagePart(o1), - packagePart(o2), - Comparator.nullsFirst(Comparator.naturalOrder())) - .compare(basenamePart(o1), basenamePart(o2)) - .result()); - - boolean anyClassPrintable = false; - - // The writer is not initialized until it is certain that at - // least one class can be written, to avoid empty stub files. - // An alternate approach would be to delete the file after it is closed, if the file is - // empty. - // It's not worth rewriting this code, since .stub files are obsolescent. - - FileWriter fileWriter = null; - PrintWriter printWriter = null; - try { - - // For each class - for (String clazz : classes) { - if (isPrintable(clazz, scene.getAScene().getClasses().get(clazz))) { - if (!anyClassPrintable) { - try { - if (fileWriter != null || printWriter != null) { - throw new Error("This can't happen"); - } - fileWriter = new FileWriter(filename, StandardCharsets.UTF_8); - printWriter = new PrintWriter(fileWriter); - } catch (IOException e) { - throw new BugInCF("error writing file during WPI: " + filename); - } - // Write out all imports - ImportDefWriter importDefWriter; + /** + * The implementation of {@link #write}. Prints imports, classes, method signatures, and fields + * in stub file format, all with appropriate annotations. + * + * @param scene the scene to write + * @param filename the name of the file to write (must end in .astub) + * @param checker the checker, for computing preconditions + */ + private static void writeImpl(ASceneWrapper scene, String filename, BaseTypeChecker checker) { + // Sort by package name first so that output is deterministic and default package + // comes first; within package sort by class name. + @SuppressWarnings("signature") // scene-lib bytecode lacks signature annotations + List<@BinaryName String> classes = new ArrayList<>(scene.getAScene().getClasses().keySet()); + Collections.sort( + classes, + (o1, o2) -> + ComparisonChain.start() + .compare( + packagePart(o1), + packagePart(o2), + Comparator.nullsFirst(Comparator.naturalOrder())) + .compare(basenamePart(o1), basenamePart(o2)) + .result()); + + boolean anyClassPrintable = false; + + // The writer is not initialized until it is certain that at + // least one class can be written, to avoid empty stub files. + // An alternate approach would be to delete the file after it is closed, if the file is + // empty. + // It's not worth rewriting this code, since .stub files are obsolescent. + + FileWriter fileWriter = null; + PrintWriter printWriter = null; + try { + + // For each class + for (String clazz : classes) { + if (isPrintable(clazz, scene.getAScene().getClasses().get(clazz))) { + if (!anyClassPrintable) { + try { + if (fileWriter != null || printWriter != null) { + throw new Error("This can't happen"); + } + fileWriter = new FileWriter(filename, StandardCharsets.UTF_8); + printWriter = new PrintWriter(fileWriter); + } catch (IOException e) { + throw new BugInCF("error writing file during WPI: " + filename); + } + + // Write out all imports + ImportDefWriter importDefWriter; + try { + importDefWriter = new ImportDefWriter(scene, printWriter); + } catch (DefException e) { + throw new BugInCF(e); + } + importDefWriter.visit(); + printWriter.println( + "import org.checkerframework.framework.qual.AnnotatedFor;"); + printWriter.println(); + anyClassPrintable = true; + } + printClass( + clazz, scene.getAScene().getClasses().get(clazz), checker, printWriter); + } + } + } finally { + if (printWriter != null) { + printWriter.close(); // does not throw IOException + } try { - importDefWriter = new ImportDefWriter(scene, printWriter); - } catch (DefException e) { - throw new BugInCF(e); + if (fileWriter != null) { + fileWriter.close(); + } + } catch (IOException e) { + // Nothing to do since exceptions thrown from a finally block have no effect. } - importDefWriter.visit(); - printWriter.println("import org.checkerframework.framework.qual.AnnotatedFor;"); - printWriter.println(); - anyClassPrintable = true; - } - printClass(clazz, scene.getAScene().getClasses().get(clazz), checker, printWriter); - } - } - } finally { - if (printWriter != null) { - printWriter.close(); // does not throw IOException - } - try { - if (fileWriter != null) { - fileWriter.close(); - } - } catch (IOException e) { - // Nothing to do since exceptions thrown from a finally block have no effect. - } - } - } - - /** - * Returns true if the class is printable in a stub file. A printable class is a class or enum - * (not a package or module) and is not anonymous. - * - * @param classname the class name - * @param aClass the representation of the class - * @return whether the class is printable, by the definition above - */ - private static boolean isPrintable(@BinaryName String classname, AClass aClass) { - String basename = basenamePart(classname); - - if ("package-info".equals(basename) || "module-info".equals(basename)) { - return false; + } } - // Do not attempt to print stubs for anonymous inner classes, local classes, or their inner - // classes, because the stub parser cannot read them. - if (anonymousInnerClassOrLocalClassPattern.matcher(basename).find()) { - return false; - } + /** + * Returns true if the class is printable in a stub file. A printable class is a class or enum + * (not a package or module) and is not anonymous. + * + * @param classname the class name + * @param aClass the representation of the class + * @return whether the class is printable, by the definition above + */ + private static boolean isPrintable(@BinaryName String classname, AClass aClass) { + String basename = basenamePart(classname); - if (aClass.getTypeElement() == null) { - throw new BugInCF( - "Tried printing an unprintable class to a stub file during WPI: " + aClass.className); - } + if ("package-info".equals(basename) || "module-info".equals(basename)) { + return false; + } + + // Do not attempt to print stubs for anonymous inner classes, local classes, or their inner + // classes, because the stub parser cannot read them. + if (anonymousInnerClassOrLocalClassPattern.matcher(basename).find()) { + return false; + } + + if (aClass.getTypeElement() == null) { + throw new BugInCF( + "Tried printing an unprintable class to a stub file during WPI: " + + aClass.className); + } - return true; - } - - /** - * Print the class body, or nothing if this is an anonymous inner class. Call {@link - * #isPrintable(String, AClass)} and check that it returns true before calling this method. - * - * @param classname the class name - * @param aClass the representation of the class - * @param checker the checker, for computing preconditions - * @param printWriter the writer on which to print - */ - private static void printClass( - @BinaryName String classname, - AClass aClass, - BaseTypeChecker checker, - PrintWriter printWriter) { - - String basename = basenamePart(classname); - String innermostClassname = - basename.contains("$") ? basename.substring(basename.lastIndexOf('$') + 1) : basename; - String pkg = packagePart(classname); - - if (pkg != null) { - printWriter.println("package " + pkg + ";"); + return true; } - int curlyCount = printClassDefinitions(basename, aClass, printWriter, checker); + /** + * Print the class body, or nothing if this is an anonymous inner class. Call {@link + * #isPrintable(String, AClass)} and check that it returns true before calling this method. + * + * @param classname the class name + * @param aClass the representation of the class + * @param checker the checker, for computing preconditions + * @param printWriter the writer on which to print + */ + private static void printClass( + @BinaryName String classname, + AClass aClass, + BaseTypeChecker checker, + PrintWriter printWriter) { + + String basename = basenamePart(classname); + String innermostClassname = + basename.contains("$") + ? basename.substring(basename.lastIndexOf('$') + 1) + : basename; + String pkg = packagePart(classname); + + if (pkg != null) { + printWriter.println("package " + pkg + ";"); + } - String indentLevel = indents(curlyCount); + int curlyCount = printClassDefinitions(basename, aClass, printWriter, checker); - List enumConstants = aClass.getEnumConstants(); - if (enumConstants != null) { - StringJoiner sj = new StringJoiner(", "); - for (VariableElement enumConstant : enumConstants) { - sj.add(enumConstant.getSimpleName()); - } + String indentLevel = indents(curlyCount); - printWriter.println(indentLevel + "// enum constants:"); - printWriter.println(); - printWriter.println(indentLevel + sj.toString() + ";"); - printWriter.println(); - } + List enumConstants = aClass.getEnumConstants(); + if (enumConstants != null) { + StringJoiner sj = new StringJoiner(", "); + for (VariableElement enumConstant : enumConstants) { + sj.add(enumConstant.getSimpleName()); + } - printFields(aClass, printWriter, indentLevel); - - if (!aClass.getMethods().isEmpty()) { - // print method signatures - printWriter.println(indentLevel + "// methods:"); - printWriter.println(); - for (Map.Entry methodEntry : aClass.getMethods().entrySet()) { - printMethodDeclaration( - methodEntry.getValue(), - innermostClassname, - printWriter, - indentLevel, - checker.getTypeFactory()); - } - } - for (int i = 0; i < curlyCount; i++) { - printWriter.println(indents(curlyCount - i - 1) + "}"); + printWriter.println(indentLevel + "// enum constants:"); + printWriter.println(); + printWriter.println(indentLevel + sj.toString() + ";"); + printWriter.println(); + } + + printFields(aClass, printWriter, indentLevel); + + if (!aClass.getMethods().isEmpty()) { + // print method signatures + printWriter.println(indentLevel + "// methods:"); + printWriter.println(); + for (Map.Entry methodEntry : aClass.getMethods().entrySet()) { + printMethodDeclaration( + methodEntry.getValue(), + innermostClassname, + printWriter, + indentLevel, + checker.getTypeFactory()); + } + } + for (int i = 0; i < curlyCount; i++) { + printWriter.println(indents(curlyCount - i - 1) + "}"); + } } - } - - /** - * Return a string containing n indents. - * - * @param n the number of indents - * @return a string containing that many indents - */ - private static String indents(int n) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < n; i++) { - sb.append(INDENT); + + /** + * Return a string containing n indents. + * + * @param n the number of indents + * @return a string containing that many indents + */ + private static String indents(int n) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) { + sb.append(INDENT); + } + return sb.toString(); } - return sb.toString(); - } - - /** - * Prints the type parameters of the given class, enclosed in {@code <...>}. - * - * @param type the TypeElement representing the class whose type parameters should be printed - * @param printWriter where to print the type parameters - */ - private static void printTypeParameters(TypeElement type, PrintWriter printWriter) { - List typeParameters = type.getTypeParameters(); - printTypeParameters(typeParameters, printWriter); - } - - /** - * Prints the given type parameters. - * - * @param typeParameters the type element to print - * @param printWriter where to print the type parameters - */ - private static void printTypeParameters( - List typeParameters, PrintWriter printWriter) { - if (typeParameters.isEmpty()) { - return; + + /** + * Prints the type parameters of the given class, enclosed in {@code <...>}. + * + * @param type the TypeElement representing the class whose type parameters should be printed + * @param printWriter where to print the type parameters + */ + private static void printTypeParameters(TypeElement type, PrintWriter printWriter) { + List typeParameters = type.getTypeParameters(); + printTypeParameters(typeParameters, printWriter); } - StringJoiner sj = new StringJoiner(", ", "<", ">"); - for (TypeParameterElement t : typeParameters) { - sj.add(t.getSimpleName().toString()); + + /** + * Prints the given type parameters. + * + * @param typeParameters the type element to print + * @param printWriter where to print the type parameters + */ + private static void printTypeParameters( + List typeParameters, PrintWriter printWriter) { + if (typeParameters.isEmpty()) { + return; + } + StringJoiner sj = new StringJoiner(", ", "<", ">"); + for (TypeParameterElement t : typeParameters) { + sj.add(t.getSimpleName().toString()); + } + printWriter.print(sj.toString()); } - printWriter.print(sj.toString()); - } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java index c5eaf1840ae..66b65bec6d8 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInference.java @@ -4,12 +4,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol.ClassSymbol; -import java.util.Map; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.analysis.Analysis; @@ -24,6 +19,14 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + /** * Interface for recording facts at (pseudo-)assignments. It is used by the -Ainfer command-line * argument. The -Ainfer command-line argument is used by the whole-program-inference loop, but this @@ -38,244 +41,249 @@ */ public interface WholeProgramInference { - /** - * Updates the parameter types of the constructor {@code constructorElt} based on the arguments in - * {@code objectCreationNode}. - * - *

          For each parameter in constructorElt: - * - *

            - *
          • If there is no stored annotated type for that parameter, then use the type of the - * corresponding argument in the object creation call objectCreationNode. - *
          • If there was a stored annotated type for that parameter, then its new type will be the - * LUB between the previous type and the type of the corresponding argument in the object - * creation call. - *
          - * - * @param objectCreationNode the Node that invokes the constructor - * @param constructorElt the Element of the constructor - * @param store the store just before the call - */ - void updateFromObjectCreation( - ObjectCreationNode objectCreationNode, - ExecutableElement constructorElt, - CFAbstractStore store); - - /** - * Updates the parameter types of the method {@code methodElt} based on the arguments in the - * method invocation {@code methodInvNode}. - * - *

          For each formal parameter in methodElt (including the receiver): - * - *

            - *
          • If there is no stored annotated type for that parameter, then use the type of the - * corresponding argument in the method call methodInvNode. - *
          • If there was a stored annotated type for that parameter, then its new type will be the - * LUB between the previous type and the type of the corresponding argument in the method - * call. - *
          - * - * @param methodInvNode the node representing a method invocation - * @param methodElt the element of the method being invoked - * @param store the store before the method call, used for inferring method preconditions - */ - void updateFromMethodInvocation( - MethodInvocationNode methodInvNode, ExecutableElement methodElt, CFAbstractStore store); - - /** - * Updates the parameter types (including the receiver) of the method {@code methodTree} based on - * the parameter types of the overridden method {@code overriddenMethod}. - * - *

          For each formal parameter in methodElt: - * - *

            - *
          • If there is no stored annotated type for that parameter, then use the type of the - * corresponding parameter on the overridden method. - *
          • If there is a stored annotated type for that parameter, then its new type will be the LUB - * between the previous type and the type of the corresponding parameter on the overridden - * method. - *
          - * - * @param methodTree the tree of the method that contains the parameter(s) - * @param methodElt the element of the method - * @param overriddenMethod the AnnotatedExecutableType of the overridden method - */ - void updateFromOverride( - MethodTree methodTree, ExecutableElement methodElt, AnnotatedExecutableType overriddenMethod); - - /** - * Updates the type of {@code lhs} based on an assignment of {@code rhs} to {@code lhs}. - * - *
            - *
          • If there is no stored annotated type for lhs, then use the type of the corresponding - * argument in the method call methodInvNode. - *
          • If there is a stored annotated type for lhs, then its new type will be the LUB between - * the previous type and the type of the corresponding argument in the method call. - *
          - * - * @param lhs the node representing the formal parameter - * @param rhs the node being assigned to the parameter in the method body - * @param paramElt the formal parameter - */ - void updateFromFormalParameterAssignment( - LocalVariableNode lhs, Node rhs, VariableElement paramElt); + /** + * Updates the parameter types of the constructor {@code constructorElt} based on the arguments + * in {@code objectCreationNode}. + * + *

          For each parameter in constructorElt: + * + *

            + *
          • If there is no stored annotated type for that parameter, then use the type of the + * corresponding argument in the object creation call objectCreationNode. + *
          • If there was a stored annotated type for that parameter, then its new type will be the + * LUB between the previous type and the type of the corresponding argument in the object + * creation call. + *
          + * + * @param objectCreationNode the Node that invokes the constructor + * @param constructorElt the Element of the constructor + * @param store the store just before the call + */ + void updateFromObjectCreation( + ObjectCreationNode objectCreationNode, + ExecutableElement constructorElt, + CFAbstractStore store); - /** - * Updates the type of {@code field} based on an assignment of {@code rhs} to {@code field}. If - * the field has a declaration annotation with the {@link IgnoreInWholeProgramInference} - * meta-annotation, no type annotation will be inferred for that field. - * - *

          If there is no stored entry for the field lhs, the entry will be created and its type will - * be the type of rhs. If there is a stored entry/type for lhs, its new type will be the LUB - * between the previous type and the type of rhs. - * - * @param field the field whose type will be refined. Must be either a FieldAccessNode or a - * LocalVariableNode whose element kind is FIELD. - * @param rhs the expression being assigned to the field - */ - void updateFromFieldAssignment(Node field, Node rhs); + /** + * Updates the parameter types of the method {@code methodElt} based on the arguments in the + * method invocation {@code methodInvNode}. + * + *

          For each formal parameter in methodElt (including the receiver): + * + *

            + *
          • If there is no stored annotated type for that parameter, then use the type of the + * corresponding argument in the method call methodInvNode. + *
          • If there was a stored annotated type for that parameter, then its new type will be the + * LUB between the previous type and the type of the corresponding argument in the method + * call. + *
          + * + * @param methodInvNode the node representing a method invocation + * @param methodElt the element of the method being invoked + * @param store the store before the method call, used for inferring method preconditions + */ + void updateFromMethodInvocation( + MethodInvocationNode methodInvNode, + ExecutableElement methodElt, + CFAbstractStore store); - /** - * Updates the type of {@code field} based on an assignment whose right-hand side has type {@code - * rhsATM}. See more details at {@link #updateFromFieldAssignment}. - * - * @param lhsTree the tree for the field whose type will be refined - * @param element the element for the field whose type will be refined - * @param fieldName the name of the field whose type will be refined - * @param rhsATM the type of the expression being assigned to the field - */ - void updateFieldFromType( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM); + /** + * Updates the parameter types (including the receiver) of the method {@code methodTree} based + * on the parameter types of the overridden method {@code overriddenMethod}. + * + *

          For each formal parameter in methodElt: + * + *

            + *
          • If there is no stored annotated type for that parameter, then use the type of the + * corresponding parameter on the overridden method. + *
          • If there is a stored annotated type for that parameter, then its new type will be the + * LUB between the previous type and the type of the corresponding parameter on the + * overridden method. + *
          + * + * @param methodTree the tree of the method that contains the parameter(s) + * @param methodElt the element of the method + * @param overriddenMethod the AnnotatedExecutableType of the overridden method + */ + void updateFromOverride( + MethodTree methodTree, + ExecutableElement methodElt, + AnnotatedExecutableType overriddenMethod); - /** - * Updates the return type of the method {@code methodTree} based on {@code returnedExpression}. - * Also updates the return types of any methods that this method overrides that are available as - * source code. - * - *

          If there is no stored annotated return type for the method methodTree, then the type of the - * return expression will be added to the return type of that method. If there is a stored - * annotated return type for the method methodTree, its new type will be the LUB between the - * previous type and the type of the return expression. - * - * @param retNode the node that contains the expression returned - * @param classSymbol the symbol of the class that contains the method - * @param methodTree the tree of the method whose return type may be updated - * @param overriddenMethods the methods that the given method return overrides, indexed by the - * annotated type of the superclass in which each method is defined - */ - void updateFromReturn( - ReturnNode retNode, - ClassSymbol classSymbol, - MethodTree methodTree, - Map overriddenMethods); + /** + * Updates the type of {@code lhs} based on an assignment of {@code rhs} to {@code lhs}. + * + *

            + *
          • If there is no stored annotated type for lhs, then use the type of the corresponding + * argument in the method call methodInvNode. + *
          • If there is a stored annotated type for lhs, then its new type will be the LUB between + * the previous type and the type of the corresponding argument in the method call. + *
          + * + * @param lhs the node representing the formal parameter + * @param rhs the node being assigned to the parameter in the method body + * @param paramElt the formal parameter + */ + void updateFromFormalParameterAssignment( + LocalVariableNode lhs, Node rhs, VariableElement paramElt); - /** - * Updates the preconditions or postconditions of the current method, from a store. - * - * @param methodElement the method or constructor whose preconditions or postconditions to update - * @param preOrPost whether to update preconditions or postconditions - * @param store the store at the method's entry or normal exit, for reading types of expressions - */ - void updateContracts( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - CFAbstractStore store); + /** + * Updates the type of {@code field} based on an assignment of {@code rhs} to {@code field}. If + * the field has a declaration annotation with the {@link IgnoreInWholeProgramInference} + * meta-annotation, no type annotation will be inferred for that field. + * + *

          If there is no stored entry for the field lhs, the entry will be created and its type will + * be the type of rhs. If there is a stored entry/type for lhs, its new type will be the LUB + * between the previous type and the type of rhs. + * + * @param field the field whose type will be refined. Must be either a FieldAccessNode or a + * LocalVariableNode whose element kind is FIELD. + * @param rhs the expression being assigned to the field + */ + void updateFromFieldAssignment(Node field, Node rhs); - // TODO: This Javadoc should explain why this method is in WholeProgramInference and not in some - // AnnotatedTypeMirror related class. - /** - * Updates sourceCodeATM to contain the LUB between sourceCodeATM and ajavaATM, ignoring missing - * AnnotationMirrors from ajavaATM -- it considers the LUB between an AnnotationMirror am and a - * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. - * - * @param sourceCodeATM the annotated type on the source code; side effected by this method - * @param ajavaATM the annotated type on the annotation file - */ - public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM); + /** + * Updates the type of {@code field} based on an assignment whose right-hand side has type + * {@code rhsATM}. See more details at {@link #updateFromFieldAssignment}. + * + * @param lhsTree the tree for the field whose type will be refined + * @param element the element for the field whose type will be refined + * @param fieldName the name of the field whose type will be refined + * @param rhsATM the type of the expression being assigned to the field + */ + void updateFieldFromType( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM); - /** - * Updates a method to add a declaration annotation. - * - * @param methodElt the method to annotate - * @param anno the declaration annotation to add to the method - */ - void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); + /** + * Updates the return type of the method {@code methodTree} based on {@code returnedExpression}. + * Also updates the return types of any methods that this method overrides that are available as + * source code. + * + *

          If there is no stored annotated return type for the method methodTree, then the type of + * the return expression will be added to the return type of that method. If there is a stored + * annotated return type for the method methodTree, its new type will be the LUB between the + * previous type and the type of the return expression. + * + * @param retNode the node that contains the expression returned + * @param classSymbol the symbol of the class that contains the method + * @param methodTree the tree of the method whose return type may be updated + * @param overriddenMethods the methods that the given method return overrides, indexed by the + * annotated type of the superclass in which each method is defined + */ + void updateFromReturn( + ReturnNode retNode, + ClassSymbol classSymbol, + MethodTree methodTree, + Map overriddenMethods); - /** - * Updates a method to add a declaration annotation. Optionally, may replace the current purity - * annotation on {@code elt} with the logical least upper bound between that purity annotation and - * {@code anno}, if {@code anno} is also a purity annotation. - * - * @param elt the method to annotate - * @param anno the declaration annotation to add to the method - * @param lubPurity if true and {@code anno} is a purity annotation, replaces the current purity - * annotation with a least upper bound rather than just adding {@code anno} - */ - void addMethodDeclarationAnnotation( - ExecutableElement elt, AnnotationMirror anno, boolean lubPurity); + /** + * Updates the preconditions or postconditions of the current method, from a store. + * + * @param methodElement the method or constructor whose preconditions or postconditions to + * update + * @param preOrPost whether to update preconditions or postconditions + * @param store the store at the method's entry or normal exit, for reading types of expressions + */ + void updateContracts( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + CFAbstractStore store); - /** - * Updates a field to add a declaration annotation. - * - * @param fieldElt the field to annotate - * @param anno the declaration annotation to add to the field - */ - void addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); + // TODO: This Javadoc should explain why this method is in WholeProgramInference and not in some + // AnnotatedTypeMirror related class. + /** + * Updates sourceCodeATM to contain the LUB between sourceCodeATM and ajavaATM, ignoring missing + * AnnotationMirrors from ajavaATM -- it considers the LUB between an AnnotationMirror am and a + * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. + * + * @param sourceCodeATM the annotated type on the source code; side effected by this method + * @param ajavaATM the annotated type on the annotation file + */ + public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM); - /** - * Adds a declaration annotation to a formal parameter. - * - * @param methodElt the method whose formal parameter will be annotated - * @param index_1based the index of the parameter (1-indexed) - * @param anno the annotation to add - */ - void addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); + /** + * Updates a method to add a declaration annotation. + * + * @param methodElt the method to annotate + * @param anno the declaration annotation to add to the method + */ + void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); - /** - * Adds an annotation to a class declaration. - * - * @param classElt the class declaration to annotate - * @param anno the annotation to add - */ - void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); + /** + * Updates a method to add a declaration annotation. Optionally, may replace the current purity + * annotation on {@code elt} with the logical least upper bound between that purity annotation + * and {@code anno}, if {@code anno} is also a purity annotation. + * + * @param elt the method to annotate + * @param anno the declaration annotation to add to the method + * @param lubPurity if true and {@code anno} is a purity annotation, replaces the current purity + * annotation with a least upper bound rather than just adding {@code anno} + */ + void addMethodDeclarationAnnotation( + ExecutableElement elt, AnnotationMirror anno, boolean lubPurity); - /** - * Writes the inferred results to a file. Ideally, it should be called at the end of the - * type-checking process. In practice, it is called after each class, because we don't know which - * class will be the last one in the type-checking process. - * - * @param format the file format in which to write the results - * @param checker the checker from which this method is called, for naming annotation files - */ - void writeResultsToFile(OutputFormat format, BaseTypeChecker checker); + /** + * Updates a field to add a declaration annotation. + * + * @param fieldElt the field to annotate + * @param anno the declaration annotation to add to the field + */ + void addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); - /** - * Performs any preparation required for inference on Elements of a class. Should be called on - * each toplevel class declaration in a compilation unit before processing it. - * - * @param classTree the class to preprocess - */ - void preprocessClassTree(ClassTree classTree); + /** + * Adds a declaration annotation to a formal parameter. + * + * @param methodElt the method whose formal parameter will be annotated + * @param index_1based the index of the parameter (1-indexed) + * @param anno the annotation to add + */ + void addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); - /** The kinds of output that whole-program inference can produce. */ - enum OutputFormat { /** - * Output the results of whole-program inference as a stub file that can be parsed back into the - * Checker Framework by the Stub Parser. + * Adds an annotation to a class declaration. + * + * @param classElt the class declaration to annotate + * @param anno the annotation to add */ - STUB(), + void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); /** - * Output the results of whole-program inference as a Java annotation index file. The Annotation - * File Utilities project contains code for reading and writing .jaif files. + * Writes the inferred results to a file. Ideally, it should be called at the end of the + * type-checking process. In practice, it is called after each class, because we don't know + * which class will be the last one in the type-checking process. + * + * @param format the file format in which to write the results + * @param checker the checker from which this method is called, for naming annotation files */ - JAIF(), + void writeResultsToFile(OutputFormat format, BaseTypeChecker checker); /** - * Output the results of whole-program inference as an ajava file that can be read in using the - * {@code -Aajava} option. + * Performs any preparation required for inference on Elements of a class. Should be called on + * each toplevel class declaration in a compilation unit before processing it. + * + * @param classTree the class to preprocess */ - AJAVA(), - } + void preprocessClassTree(ClassTree classTree); + + /** The kinds of output that whole-program inference can produce. */ + enum OutputFormat { + /** + * Output the results of whole-program inference as a stub file that can be parsed back into + * the Checker Framework by the Stub Parser. + */ + STUB(), + + /** + * Output the results of whole-program inference as a Java annotation index file. The + * Annotation File Utilities project contains code for reading and writing .jaif files. + */ + JAIF(), + + /** + * Output the results of whole-program inference as an ajava file that can be read in using + * the {@code -Aajava} option. + */ + AJAVA(), + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index 551db8eb0ee..ca87f41602b 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -5,17 +5,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol.ClassSymbol; -import java.util.List; -import java.util.Map; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; + import org.checkerframework.afu.scenelib.util.JVMNames; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.Nullable; @@ -61,6 +51,19 @@ import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; +import java.util.List; +import java.util.Map; + +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; + /** * This is the primary implementation of {@link * org.checkerframework.common.wholeprograminference.WholeProgramInference}. It uses an instance of @@ -105,1050 +108,1083 @@ // can yield different results (order of annotations). public class WholeProgramInferenceImplementation implements WholeProgramInference { - /** The type factory associated with this. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Whether to print debugging information when an inference is attempted, but cannot be completed. - * An inference can be attempted without success for example because the current storage system - * does not support placing annotation in the location for which an annotation was inferred. - */ - private final boolean showWpiFailedInferences; - - /** The storage for the inferred annotations. */ - private final WholeProgramInferenceStorage storage; - - /** Whether to ignore assignments where the rhs is null. */ - private final boolean ignoreNullAssignments; - - /** The @{@link Deterministic} annotation. */ - private final AnnotationMirror DETERMINISTIC; - - /** The @{@link SideEffectFree} annotation. */ - private final AnnotationMirror SIDE_EFFECT_FREE; - - /** The @{@link Pure} annotation. */ - private final AnnotationMirror PURE; - - /** The @{@link Impure} annotation. */ - private final AnnotationMirror IMPURE; - - /** The fully-qualified name of the @{@link Deterministic} class. */ - private final String DETERMINISTIC_NAME = "org.checkerframework.dataflow.qual.Deterministic"; - - /** The fully-qualified name of the @{@link SideEffectFree} class. */ - private final String SIDE_EFFECT_FREE_NAME = "org.checkerframework.dataflow.qual.SideEffectFree"; - - /** The fully-qualified name of the @{@link Pure} class. */ - private final String PURE_NAME = "org.checkerframework.dataflow.qual.Pure"; - - /** The fully-qualified name of the @{@link Impure} class. */ - private final String IMPURE_NAME = "org.checkerframework.dataflow.qual.Impure"; - - /** - * Constructs a new {@code WholeProgramInferenceImplementation} that has not yet inferred any - * annotations. - * - * @param atypeFactory the associated type factory - * @param storage the storage used for inferred annotations and for writing output files - * @param showWpiFailedInferences whether the {@code -AshowWpiFailedInferences} argument was - * passed to the checker, and therefore whether to print debugging messages when inference - * fails - */ - public WholeProgramInferenceImplementation( - AnnotatedTypeFactory atypeFactory, - WholeProgramInferenceStorage storage, - boolean showWpiFailedInferences) { - this.atypeFactory = atypeFactory; - this.storage = storage; - boolean isNullness = - atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); - this.ignoreNullAssignments = !isNullness; - this.showWpiFailedInferences = showWpiFailedInferences; - DETERMINISTIC = - AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Deterministic.class); - SIDE_EFFECT_FREE = - AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), SideEffectFree.class); - PURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Pure.class); - IMPURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Impure.class); - } - - /** - * Returns the storage for inferred annotations. - * - * @return the storage for the inferred annotations - */ - public WholeProgramInferenceStorage getStorage() { - return storage; - } - - @Override - public void updateFromObjectCreation( - ObjectCreationNode objectCreationNode, - ExecutableElement constructorElt, - CFAbstractStore store) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(constructorElt)) { - return; + /** The type factory associated with this. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** + * Whether to print debugging information when an inference is attempted, but cannot be + * completed. An inference can be attempted without success for example because the current + * storage system does not support placing annotation in the location for which an annotation + * was inferred. + */ + private final boolean showWpiFailedInferences; + + /** The storage for the inferred annotations. */ + private final WholeProgramInferenceStorage storage; + + /** Whether to ignore assignments where the rhs is null. */ + private final boolean ignoreNullAssignments; + + /** The @{@link Deterministic} annotation. */ + private final AnnotationMirror DETERMINISTIC; + + /** The @{@link SideEffectFree} annotation. */ + private final AnnotationMirror SIDE_EFFECT_FREE; + + /** The @{@link Pure} annotation. */ + private final AnnotationMirror PURE; + + /** The @{@link Impure} annotation. */ + private final AnnotationMirror IMPURE; + + /** The fully-qualified name of the @{@link Deterministic} class. */ + private final String DETERMINISTIC_NAME = "org.checkerframework.dataflow.qual.Deterministic"; + + /** The fully-qualified name of the @{@link SideEffectFree} class. */ + private final String SIDE_EFFECT_FREE_NAME = + "org.checkerframework.dataflow.qual.SideEffectFree"; + + /** The fully-qualified name of the @{@link Pure} class. */ + private final String PURE_NAME = "org.checkerframework.dataflow.qual.Pure"; + + /** The fully-qualified name of the @{@link Impure} class. */ + private final String IMPURE_NAME = "org.checkerframework.dataflow.qual.Impure"; + + /** + * Constructs a new {@code WholeProgramInferenceImplementation} that has not yet inferred any + * annotations. + * + * @param atypeFactory the associated type factory + * @param storage the storage used for inferred annotations and for writing output files + * @param showWpiFailedInferences whether the {@code -AshowWpiFailedInferences} argument was + * passed to the checker, and therefore whether to print debugging messages when inference + * fails + */ + public WholeProgramInferenceImplementation( + AnnotatedTypeFactory atypeFactory, + WholeProgramInferenceStorage storage, + boolean showWpiFailedInferences) { + this.atypeFactory = atypeFactory; + this.storage = storage; + boolean isNullness = + atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); + this.ignoreNullAssignments = !isNullness; + this.showWpiFailedInferences = showWpiFailedInferences; + DETERMINISTIC = + AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Deterministic.class); + SIDE_EFFECT_FREE = + AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), SideEffectFree.class); + PURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Pure.class); + IMPURE = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), Impure.class); } - // Don't infer types for code that can't be annotated anyway. - if (!storage.hasStorageLocationForMethod(constructorElt)) { - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "WPI could not store information" - + "about this constructor: " - + JVMNames.getJVMMethodSignature(constructorElt)); - } - return; + /** + * Returns the storage for inferred annotations. + * + * @return the storage for the inferred annotations + */ + public WholeProgramInferenceStorage getStorage() { + return storage; } - List arguments = objectCreationNode.getArguments(); - updateInferredExecutableParameterTypes( - constructorElt, arguments, null, objectCreationNode.getTree()); - updateContracts(Analysis.BeforeOrAfter.BEFORE, constructorElt, store); - } - - @Override - public void updateFromMethodInvocation( - MethodInvocationNode methodInvNode, - ExecutableElement methodElt, - CFAbstractStore store) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } + @Override + public void updateFromObjectCreation( + ObjectCreationNode objectCreationNode, + ExecutableElement constructorElt, + CFAbstractStore store) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(constructorElt)) { + return; + } - if (!storage.hasStorageLocationForMethod(methodElt)) { - return; - } + // Don't infer types for code that can't be annotated anyway. + if (!storage.hasStorageLocationForMethod(constructorElt)) { + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "WPI could not store information" + + "about this constructor: " + + JVMNames.getJVMMethodSignature(constructorElt)); + } + return; + } - // Don't infer formal parameter types from recursive calls. - // - // When performing WPI on a library, if there are no external calls (only recursive calls), - // then each iteration of WPI would make the formal parameter types more restrictive, - // leading to an infinite (or very long) loop. - // - // Consider - // void myMethod(int x) { ... myMethod(x-1) ... }` - // On one iteration, if x has type IntRange(to=100), the recursive call's argument has type - // IntRange(to=99). If that is the only call to `MyMethod`, then the formal parameter type - // would be updated. On the next iteration it would be refined again to @IntRange(to=98), - // and so forth. A recursive call should never restrict a formal parameter type. - if (isRecursiveCall(methodInvNode)) { - return; + List arguments = objectCreationNode.getArguments(); + updateInferredExecutableParameterTypes( + constructorElt, arguments, null, objectCreationNode.getTree()); + updateContracts(Analysis.BeforeOrAfter.BEFORE, constructorElt, store); } - List arguments = methodInvNode.getArguments(); - Node receiver = methodInvNode.getTarget().getReceiver(); - // Static methods have a "receiver" that is a class name rather than an expression. - // Do not attempt to use the class name as a receiver expression for inference - // purposes. - if (receiver instanceof ClassNameNode) { - receiver = null; + @Override + public void updateFromMethodInvocation( + MethodInvocationNode methodInvNode, + ExecutableElement methodElt, + CFAbstractStore store) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; + } + + if (!storage.hasStorageLocationForMethod(methodElt)) { + return; + } + + // Don't infer formal parameter types from recursive calls. + // + // When performing WPI on a library, if there are no external calls (only recursive calls), + // then each iteration of WPI would make the formal parameter types more restrictive, + // leading to an infinite (or very long) loop. + // + // Consider + // void myMethod(int x) { ... myMethod(x-1) ... }` + // On one iteration, if x has type IntRange(to=100), the recursive call's argument has type + // IntRange(to=99). If that is the only call to `MyMethod`, then the formal parameter type + // would be updated. On the next iteration it would be refined again to @IntRange(to=98), + // and so forth. A recursive call should never restrict a formal parameter type. + if (isRecursiveCall(methodInvNode)) { + return; + } + + List arguments = methodInvNode.getArguments(); + Node receiver = methodInvNode.getTarget().getReceiver(); + // Static methods have a "receiver" that is a class name rather than an expression. + // Do not attempt to use the class name as a receiver expression for inference + // purposes. + if (receiver instanceof ClassNameNode) { + receiver = null; + } + updateInferredExecutableParameterTypes( + methodElt, arguments, receiver, methodInvNode.getTree()); + updateContracts(Analysis.BeforeOrAfter.BEFORE, methodElt, store); } - updateInferredExecutableParameterTypes(methodElt, arguments, receiver, methodInvNode.getTree()); - updateContracts(Analysis.BeforeOrAfter.BEFORE, methodElt, store); - } - - /** - * Returns true if the given call is a recursive call. - * - * @param methodInvNode a method invocation - * @return true if the given call is a recursive call - */ - private boolean isRecursiveCall(MethodInvocationNode methodInvNode) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(methodInvNode.getTreePath()); - if (enclosingMethod == null) { - return false; + + /** + * Returns true if the given call is a recursive call. + * + * @param methodInvNode a method invocation + * @return true if the given call is a recursive call + */ + private boolean isRecursiveCall(MethodInvocationNode methodInvNode) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(methodInvNode.getTreePath()); + if (enclosingMethod == null) { + return false; + } + ExecutableElement methodInvocEle = TreeUtils.elementFromUse(methodInvNode.getTree()); + ExecutableElement methodDeclEle = TreeUtils.elementFromDeclaration(enclosingMethod); + return methodDeclEle.equals(methodInvocEle); } - ExecutableElement methodInvocEle = TreeUtils.elementFromUse(methodInvNode.getTree()); - ExecutableElement methodDeclEle = TreeUtils.elementFromDeclaration(enclosingMethod); - return methodDeclEle.equals(methodInvocEle); - } - - /** - * Updates inferred parameter types based on a call to a method or constructor. - * - * @param methodElt the element of the method or constructor being invoked - * @param arguments the arguments of the invocation - * @param receiver the receiver node, if there is one; null if there is not - * @param invocationTree the method or constructor invocation, used to viewpoint adapt any - * dependent types when storing newly-inferred annotations - */ - private void updateInferredExecutableParameterTypes( - ExecutableElement methodElt, - List arguments, - @Nullable Node receiver, - ExpressionTree invocationTree) { - - String file = storage.getFileForElement(methodElt); - // Need to check both that receiver is non-null and that this is not a constructor - // invocation: despite updateFromObjectCreation always passes null, it's possible - // for updateFromMethodInvocation to actually be a constructor invocation with a - // receiver: for example, when calling an inner class's constructor, the receiver - // can be an instance of the enclosing class. Constructor invocations should never - // have information inferred about their receivers. - if (receiver != null - && atypeFactory.wpiShouldInferTypesForReceivers() - && !methodElt.getSimpleName().contentEquals("")) { - AnnotatedTypeMirror receiverArgATM = atypeFactory.getReceiverType(invocationTree); - AnnotatedExecutableType methodDeclType = atypeFactory.getAnnotatedType(methodElt); - AnnotatedTypeMirror receiverParamATM = methodDeclType.getReceiverType(); - // update the set of annotations for the receiver type if it is not null. - if (receiverParamATM != null) { - atypeFactory.wpiAdjustForUpdateNonField(receiverArgATM); - T receiverAnnotations = - storage.getReceiverAnnotations(methodElt, receiverParamATM, atypeFactory); - if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { - ((GenericAnnotatedTypeFactory) this.atypeFactory) - .getDependentTypesHelper() - .delocalizeAtCallsite(receiverArgATM, invocationTree, arguments, receiver, methodElt); + + /** + * Updates inferred parameter types based on a call to a method or constructor. + * + * @param methodElt the element of the method or constructor being invoked + * @param arguments the arguments of the invocation + * @param receiver the receiver node, if there is one; null if there is not + * @param invocationTree the method or constructor invocation, used to viewpoint adapt any + * dependent types when storing newly-inferred annotations + */ + private void updateInferredExecutableParameterTypes( + ExecutableElement methodElt, + List arguments, + @Nullable Node receiver, + ExpressionTree invocationTree) { + + String file = storage.getFileForElement(methodElt); + // Need to check both that receiver is non-null and that this is not a constructor + // invocation: despite updateFromObjectCreation always passes null, it's possible + // for updateFromMethodInvocation to actually be a constructor invocation with a + // receiver: for example, when calling an inner class's constructor, the receiver + // can be an instance of the enclosing class. Constructor invocations should never + // have information inferred about their receivers. + if (receiver != null + && atypeFactory.wpiShouldInferTypesForReceivers() + && !methodElt.getSimpleName().contentEquals("")) { + AnnotatedTypeMirror receiverArgATM = atypeFactory.getReceiverType(invocationTree); + AnnotatedExecutableType methodDeclType = atypeFactory.getAnnotatedType(methodElt); + AnnotatedTypeMirror receiverParamATM = methodDeclType.getReceiverType(); + // update the set of annotations for the receiver type if it is not null. + if (receiverParamATM != null) { + atypeFactory.wpiAdjustForUpdateNonField(receiverArgATM); + T receiverAnnotations = + storage.getReceiverAnnotations(methodElt, receiverParamATM, atypeFactory); + if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { + ((GenericAnnotatedTypeFactory) this.atypeFactory) + .getDependentTypesHelper() + .delocalizeAtCallsite( + receiverArgATM, invocationTree, arguments, receiver, methodElt); + } + updateAnnotationSet( + receiverAnnotations, + TypeUseLocation.RECEIVER, + receiverArgATM, + receiverParamATM, + file); + } + } + + int numArguments = arguments.size(); + for (int i = 0; i < numArguments; i++) { + Node arg = arguments.get(i); + Tree argTree = arg.getTree(); + + VariableElement ve; + boolean varargsParam = + i >= methodElt.getParameters().size() - 1 && methodElt.isVarArgs(); + if (varargsParam && this.atypeFactory.wpiOutputFormat == OutputFormat.JAIF) { + // The AFU's org.checkerframework.afu.annotator.Main produces a non-compilable + // source file when JAIF-based WPI tries to output an annotated varargs parameter, + // such as when running the test + // checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java. + // Until that bug is fixed, do not attempt to infer information about varargs + // parameters in JAIF mode. + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Annotations cannot be placed on varargs parameters in -Ainfer=jaifs mode, because" + + " the JAIF format does not correctly support it.\n" + + "The signature of the method whose varargs parameter was not annotated is: " + + JVMNames.getJVMMethodSignature(methodElt)); + } + return; + } + List params = methodElt.getParameters(); + if (varargsParam) { + ve = params.get(params.size() - 1); + } else { + ve = params.get(i); + } + AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); + AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(argTree); + if (varargsParam) { + // Check whether argATM needs to be turned into an array type, so that the type + // structure matches paramATM. + boolean expandArgATM = false; + if (argATM.getKind() == TypeKind.ARRAY) { + int argATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) argATM); + // This unchecked cast is safe because the declared type of a varargs parameter + // is guaranteed to be an array of some kind. + int paramATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) paramATM); + if (paramATMDepth != argATMDepth) { + assert argATMDepth + 1 == paramATMDepth; + expandArgATM = true; + } + } else { + expandArgATM = true; + } + if (expandArgATM) { + if (argATM.getKind() == TypeKind.WILDCARD) { + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Javac cannot create an array type " + + "from a wildcard, so WPI did not attempt to infer a type for an array " + + "parameter.\n" + + "The signature of the method whose parameter had inference skipped is: " + + JVMNames.getJVMMethodSignature(methodElt)); + } + return; + } + AnnotatedTypeMirror argArray = + AnnotatedTypeMirror.createType( + TypesUtils.createArrayType( + argATM.getUnderlyingType(), atypeFactory.types), + atypeFactory, + false); + ((AnnotatedArrayType) argArray).setComponentType(argATM); + argATM = argArray; + } + } + atypeFactory.wpiAdjustForUpdateNonField(argATM); + // If storage.getParameterAnnotations receives an index that's larger than the size + // of the parameter list, scenes-backed inference can create duplicate entries + // for the varargs parameter (it indexes inferred annotations by the parameter number). + int paramIndex = varargsParam ? methodElt.getParameters().size() : i + 1; + T paramAnnotations = + storage.getParameterAnnotations( + methodElt, paramIndex, paramATM, ve, atypeFactory); + if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { + ((GenericAnnotatedTypeFactory) this.atypeFactory) + .getDependentTypesHelper() + .delocalizeAtCallsite( + argATM, invocationTree, arguments, receiver, methodElt); + } + updateAnnotationSet( + paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); } - updateAnnotationSet( - receiverAnnotations, TypeUseLocation.RECEIVER, receiverArgATM, receiverParamATM, file); - } } - int numArguments = arguments.size(); - for (int i = 0; i < numArguments; i++) { - Node arg = arguments.get(i); - Tree argTree = arg.getTree(); - - VariableElement ve; - boolean varargsParam = i >= methodElt.getParameters().size() - 1 && methodElt.isVarArgs(); - if (varargsParam && this.atypeFactory.wpiOutputFormat == OutputFormat.JAIF) { - // The AFU's org.checkerframework.afu.annotator.Main produces a non-compilable - // source file when JAIF-based WPI tries to output an annotated varargs parameter, - // such as when running the test - // checker/tests/ainfer-testchecker/non-annotated/AnonymousAndInnerClass.java. - // Until that bug is fixed, do not attempt to infer information about varargs - // parameters in JAIF mode. - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Annotations cannot be placed on varargs parameters in -Ainfer=jaifs mode, because" - + " the JAIF format does not correctly support it.\n" - + "The signature of the method whose varargs parameter was not annotated is: " - + JVMNames.getJVMMethodSignature(methodElt)); + @Override + public void updateContracts( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElt, + CFAbstractStore store) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; } - return; - } - List params = methodElt.getParameters(); - if (varargsParam) { - ve = params.get(params.size() - 1); - } else { - ve = params.get(i); - } - AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); - AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(argTree); - if (varargsParam) { - // Check whether argATM needs to be turned into an array type, so that the type - // structure matches paramATM. - boolean expandArgATM = false; - if (argATM.getKind() == TypeKind.ARRAY) { - int argATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) argATM); - // This unchecked cast is safe because the declared type of a varargs parameter - // is guaranteed to be an array of some kind. - int paramATMDepth = AnnotatedTypes.getArrayDepth((AnnotatedArrayType) paramATM); - if (paramATMDepth != argATMDepth) { - assert argATMDepth + 1 == paramATMDepth; - expandArgATM = true; - } - } else { - expandArgATM = true; + + if (store == null) { + throw new BugInCF( + "updateContracts(%s, %s, null) for %s", + preOrPost, methodElt, atypeFactory.getClass().getSimpleName()); } - if (expandArgATM) { - if (argATM.getKind() == TypeKind.WILDCARD) { - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Javac cannot create an array type " - + "from a wildcard, so WPI did not attempt to infer a type for an array " - + "parameter.\n" - + "The signature of the method whose parameter had inference skipped is: " - + JVMNames.getJVMMethodSignature(methodElt)); - } + + if (!storage.hasStorageLocationForMethod(methodElt)) { return; - } - AnnotatedTypeMirror argArray = - AnnotatedTypeMirror.createType( - TypesUtils.createArrayType(argATM.getUnderlyingType(), atypeFactory.types), - atypeFactory, - false); - ((AnnotatedArrayType) argArray).setComponentType(argATM); - argATM = argArray; } - } - atypeFactory.wpiAdjustForUpdateNonField(argATM); - // If storage.getParameterAnnotations receives an index that's larger than the size - // of the parameter list, scenes-backed inference can create duplicate entries - // for the varargs parameter (it indexes inferred annotations by the parameter number). - int paramIndex = varargsParam ? methodElt.getParameters().size() : i + 1; - T paramAnnotations = - storage.getParameterAnnotations(methodElt, paramIndex, paramATM, ve, atypeFactory); - if (this.atypeFactory instanceof GenericAnnotatedTypeFactory) { - ((GenericAnnotatedTypeFactory) this.atypeFactory) - .getDependentTypesHelper() - .delocalizeAtCallsite(argATM, invocationTree, arguments, receiver, methodElt); - } - updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); - } - } - - @Override - public void updateContracts( - Analysis.BeforeOrAfter preOrPost, ExecutableElement methodElt, CFAbstractStore store) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } - if (store == null) { - throw new BugInCF( - "updateContracts(%s, %s, null) for %s", - preOrPost, methodElt, atypeFactory.getClass().getSimpleName()); + // TODO: Probably move some part of this into the AnnotatedTypeFactory. + + // This code handles fields of "this" and method parameters (including the receiver + // parameter "this"), for now. In the future, extend it to other expressions. + TypeElement containingClass = (TypeElement) methodElt.getEnclosingElement(); + ThisReference thisReference = new ThisReference(containingClass.asType()); + ClassName classNameReceiver = new ClassName(containingClass.asType()); + // Fields of "this": + for (VariableElement fieldElement : + ElementFilter.fieldsIn(containingClass.getEnclosedElements())) { + if (atypeFactory.wpiOutputFormat == OutputFormat.JAIF + && containingClass.getNestingKind().isNested()) { + // Don't infer facts about fields of inner classes, because IndexFileWriter + // places the annotations incorrectly on the class declarations. + continue; + } + if (ElementUtils.isStatic(methodElt) && !ElementUtils.isStatic(fieldElement)) { + // A static method can't have precondition annotations about instance fields. + continue; + } + FieldAccess fa = + new FieldAccess( + (ElementUtils.isStatic(fieldElement) + ? classNameReceiver + : thisReference), + fieldElement.asType(), + fieldElement); + CFAbstractValue v = store.getFieldValue(fa); + AnnotatedTypeMirror fieldDeclType = atypeFactory.getAnnotatedType(fieldElement); + AnnotatedTypeMirror inferredType; + if (v != null) { + // This field is in the store. + inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, fieldDeclType); + atypeFactory.wpiAdjustForUpdateNonField(inferredType); + } else { + // This field is not in the store. Use the declared type. + inferredType = fieldDeclType; + } + T preOrPostConditionAnnos = + storage.getPreOrPostconditions( + preOrPost, methodElt, fa.toString(), fieldDeclType, atypeFactory); + if (preOrPostConditionAnnos == null) { + continue; + } + String file = storage.getFileForElement(methodElt); + updateAnnotationSet( + preOrPostConditionAnnos, + TypeUseLocation.FIELD, + inferredType, + fieldDeclType, + file, + false); + } + // Method parameters (other than the receiver parameter "this"): + // This loop is 1-indexed to match the syntax used in annotation arguments. + for (int index = 1; index <= methodElt.getParameters().size(); index++) { + VariableElement paramElt = methodElt.getParameters().get(index - 1); + + // Do not infer information about non-effectively-final method parameters, to avoid + // spurious flowexpr.parameter.not.final warnings. + if (!ElementUtils.isEffectivelyFinal(paramElt)) { + continue; + } + LocalVariable param = new LocalVariable(paramElt); + CFAbstractValue v = store.getValue(param); + AnnotatedTypeMirror declType = atypeFactory.getAnnotatedType(paramElt); + AnnotatedTypeMirror inferredType; + if (v != null) { + // This parameter is in the store. + inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, declType); + atypeFactory.wpiAdjustForUpdateNonField(inferredType); + } else { + // The parameter is not in the store, so don't attempt to create a postcondition for + // it, since anything other than its default type would not be verifiable. (Only + // postconditions are supported for parameters.) + continue; + } + T preOrPostConditionAnnos = + storage.getPreOrPostconditions( + preOrPost, methodElt, "#" + index, declType, atypeFactory); + if (preOrPostConditionAnnos != null) { + String file = storage.getFileForElement(methodElt); + updateAnnotationSet( + preOrPostConditionAnnos, + TypeUseLocation.PARAMETER, + inferredType, + declType, + file, + false); + } + } + // Receiver parameter ("this"): + if (!ElementUtils.isStatic(methodElt)) { // Static methods do not have a receiver. + CFAbstractValue v = store.getValue(thisReference); + if (v != null) { + // This parameter is in the store. + AnnotatedTypeMirror declaredType = + atypeFactory.getAnnotatedType(methodElt).getReceiverType(); + if (declaredType == null) { + // declaredType is null when the method being analyzed is a constructor (which + // doesn't have a receiver). + return; + } + AnnotatedTypeMirror inferredType = + AnnotatedTypeMirror.createType( + declaredType.getUnderlyingType(), atypeFactory, false); + inferredType.replaceAnnotations(v.getAnnotations()); + atypeFactory.wpiAdjustForUpdateNonField(inferredType); + T preOrPostConditionAnnos = + storage.getPreOrPostconditions( + preOrPost, methodElt, "this", declaredType, atypeFactory); + if (preOrPostConditionAnnos != null) { + String file = storage.getFileForElement(methodElt); + updateAnnotationSet( + preOrPostConditionAnnos, + TypeUseLocation.PARAMETER, + inferredType, + declaredType, + file, + false); + } + } + } } - if (!storage.hasStorageLocationForMethod(methodElt)) { - return; + /** + * Converts a CFAbstractValue to an AnnotatedTypeMirror. + * + * @param v a value to convert to an AnnotatedTypeMirror + * @param fieldType an {@code AnnotatedTypeMirror} with the same underlying type as {@code v} + * that is copied, then the copy is updated to use {@code v}'s annotations + * @return a copy of {@code fieldType} with {@code v}'s annotations + */ + private AnnotatedTypeMirror convertCFAbstractValueToAnnotatedTypeMirror( + CFAbstractValue v, AnnotatedTypeMirror fieldType) { + AnnotatedTypeMirror result = fieldType.deepCopy(); + result.replaceAnnotations(v.getAnnotations()); + return result; } - // TODO: Probably move some part of this into the AnnotatedTypeFactory. - - // This code handles fields of "this" and method parameters (including the receiver - // parameter "this"), for now. In the future, extend it to other expressions. - TypeElement containingClass = (TypeElement) methodElt.getEnclosingElement(); - ThisReference thisReference = new ThisReference(containingClass.asType()); - ClassName classNameReceiver = new ClassName(containingClass.asType()); - // Fields of "this": - for (VariableElement fieldElement : - ElementFilter.fieldsIn(containingClass.getEnclosedElements())) { - if (atypeFactory.wpiOutputFormat == OutputFormat.JAIF - && containingClass.getNestingKind().isNested()) { - // Don't infer facts about fields of inner classes, because IndexFileWriter - // places the annotations incorrectly on the class declarations. - continue; - } - if (ElementUtils.isStatic(methodElt) && !ElementUtils.isStatic(fieldElement)) { - // A static method can't have precondition annotations about instance fields. - continue; - } - FieldAccess fa = - new FieldAccess( - (ElementUtils.isStatic(fieldElement) ? classNameReceiver : thisReference), - fieldElement.asType(), - fieldElement); - CFAbstractValue v = store.getFieldValue(fa); - AnnotatedTypeMirror fieldDeclType = atypeFactory.getAnnotatedType(fieldElement); - AnnotatedTypeMirror inferredType; - if (v != null) { - // This field is in the store. - inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, fieldDeclType); - atypeFactory.wpiAdjustForUpdateNonField(inferredType); - } else { - // This field is not in the store. Use the declared type. - inferredType = fieldDeclType; - } - T preOrPostConditionAnnos = - storage.getPreOrPostconditions( - preOrPost, methodElt, fa.toString(), fieldDeclType, atypeFactory); - if (preOrPostConditionAnnos == null) { - continue; - } - String file = storage.getFileForElement(methodElt); - updateAnnotationSet( - preOrPostConditionAnnos, TypeUseLocation.FIELD, inferredType, fieldDeclType, file, false); - } - // Method parameters (other than the receiver parameter "this"): - // This loop is 1-indexed to match the syntax used in annotation arguments. - for (int index = 1; index <= methodElt.getParameters().size(); index++) { - VariableElement paramElt = methodElt.getParameters().get(index - 1); - - // Do not infer information about non-effectively-final method parameters, to avoid - // spurious flowexpr.parameter.not.final warnings. - if (!ElementUtils.isEffectivelyFinal(paramElt)) { - continue; - } - LocalVariable param = new LocalVariable(paramElt); - CFAbstractValue v = store.getValue(param); - AnnotatedTypeMirror declType = atypeFactory.getAnnotatedType(paramElt); - AnnotatedTypeMirror inferredType; - if (v != null) { - // This parameter is in the store. - inferredType = convertCFAbstractValueToAnnotatedTypeMirror(v, declType); - atypeFactory.wpiAdjustForUpdateNonField(inferredType); - } else { - // The parameter is not in the store, so don't attempt to create a postcondition for - // it, since anything other than its default type would not be verifiable. (Only - // postconditions are supported for parameters.) - continue; - } - T preOrPostConditionAnnos = - storage.getPreOrPostconditions(preOrPost, methodElt, "#" + index, declType, atypeFactory); - if (preOrPostConditionAnnos != null) { + @Override + public void updateFromOverride( + MethodTree methodTree, + ExecutableElement methodElt, + AnnotatedExecutableType overriddenMethod) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; + } + String file = storage.getFileForElement(methodElt); - updateAnnotationSet( - preOrPostConditionAnnos, - TypeUseLocation.PARAMETER, - inferredType, - declType, - file, - false); - } - } - // Receiver parameter ("this"): - if (!ElementUtils.isStatic(methodElt)) { // Static methods do not have a receiver. - CFAbstractValue v = store.getValue(thisReference); - if (v != null) { - // This parameter is in the store. - AnnotatedTypeMirror declaredType = - atypeFactory.getAnnotatedType(methodElt).getReceiverType(); - if (declaredType == null) { - // declaredType is null when the method being analyzed is a constructor (which - // doesn't have a receiver). - return; + + int numParams = overriddenMethod.getParameterTypes().size(); + for (int i = 0; i < numParams; i++) { + VariableElement ve = methodElt.getParameters().get(i); + AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); + AnnotatedTypeMirror argATM = overriddenMethod.getParameterTypes().get(i); + atypeFactory.wpiAdjustForUpdateNonField(argATM); + T paramAnnotations = + storage.getParameterAnnotations(methodElt, i + 1, paramATM, ve, atypeFactory); + updateAnnotationSet( + paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); } - AnnotatedTypeMirror inferredType = - AnnotatedTypeMirror.createType(declaredType.getUnderlyingType(), atypeFactory, false); - inferredType.replaceAnnotations(v.getAnnotations()); - atypeFactory.wpiAdjustForUpdateNonField(inferredType); - T preOrPostConditionAnnos = - storage.getPreOrPostconditions( - preOrPost, methodElt, "this", declaredType, atypeFactory); - if (preOrPostConditionAnnos != null) { - String file = storage.getFileForElement(methodElt); - updateAnnotationSet( - preOrPostConditionAnnos, - TypeUseLocation.PARAMETER, - inferredType, - declaredType, - file, - false); + + AnnotatedDeclaredType argADT = overriddenMethod.getReceiverType(); + if (argADT != null) { + AnnotatedTypeMirror paramATM = + atypeFactory.getAnnotatedType(methodTree).getReceiverType(); + if (paramATM != null) { + T receiver = storage.getReceiverAnnotations(methodElt, paramATM, atypeFactory); + updateAnnotationSet(receiver, TypeUseLocation.RECEIVER, argADT, paramATM, file); + } } - } - } - } - - /** - * Converts a CFAbstractValue to an AnnotatedTypeMirror. - * - * @param v a value to convert to an AnnotatedTypeMirror - * @param fieldType an {@code AnnotatedTypeMirror} with the same underlying type as {@code v} that - * is copied, then the copy is updated to use {@code v}'s annotations - * @return a copy of {@code fieldType} with {@code v}'s annotations - */ - private AnnotatedTypeMirror convertCFAbstractValueToAnnotatedTypeMirror( - CFAbstractValue v, AnnotatedTypeMirror fieldType) { - AnnotatedTypeMirror result = fieldType.deepCopy(); - result.replaceAnnotations(v.getAnnotations()); - return result; - } - - @Override - public void updateFromOverride( - MethodTree methodTree, - ExecutableElement methodElt, - AnnotatedExecutableType overriddenMethod) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; } - String file = storage.getFileForElement(methodElt); - - int numParams = overriddenMethod.getParameterTypes().size(); - for (int i = 0; i < numParams; i++) { - VariableElement ve = methodElt.getParameters().get(i); - AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(ve); - AnnotatedTypeMirror argATM = overriddenMethod.getParameterTypes().get(i); - atypeFactory.wpiAdjustForUpdateNonField(argATM); - T paramAnnotations = - storage.getParameterAnnotations(methodElt, i + 1, paramATM, ve, atypeFactory); - updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); - } + @Override + public void updateFromFormalParameterAssignment( + LocalVariableNode lhs, Node rhs, VariableElement paramElt) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(lhs.getElement())) { + return; + } - AnnotatedDeclaredType argADT = overriddenMethod.getReceiverType(); - if (argADT != null) { - AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(methodTree).getReceiverType(); - if (paramATM != null) { - T receiver = storage.getReceiverAnnotations(methodElt, paramATM, atypeFactory); - updateAnnotationSet(receiver, TypeUseLocation.RECEIVER, argADT, paramATM, file); - } - } - } - - @Override - public void updateFromFormalParameterAssignment( - LocalVariableNode lhs, Node rhs, VariableElement paramElt) { - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(lhs.getElement())) { - return; - } + Tree rhsTree = rhs.getTree(); + if (rhsTree == null) { + // TODO: Handle variable-length list as parameter. + // An ArrayCreationNode with a null tree is created when the + // parameter is a variable-length list. We are ignoring it for now. + // See Issue 682: https://github.com/typetools/checker-framework/issues/682 + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Could not update from formal parameter " + + "assignment, because an ArrayCreationNode with a null tree is created when " + + "the parameter is a variable-length list.\nParameter: " + + paramElt); + } + return; + } - Tree rhsTree = rhs.getTree(); - if (rhsTree == null) { - // TODO: Handle variable-length list as parameter. - // An ArrayCreationNode with a null tree is created when the - // parameter is a variable-length list. We are ignoring it for now. - // See Issue 682: https://github.com/typetools/checker-framework/issues/682 - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Could not update from formal parameter " - + "assignment, because an ArrayCreationNode with a null tree is created when " - + "the parameter is a variable-length list.\nParameter: " - + paramElt); - } - return; - } + ExecutableElement methodElt = (ExecutableElement) paramElt.getEnclosingElement(); - ExecutableElement methodElt = (ExecutableElement) paramElt.getEnclosingElement(); - - int index_1based = methodElt.getParameters().indexOf(paramElt) + 1; - if (index_1based == 0) { - // When paramElt is the parameter of a lambda contained in another - // method body, the enclosing element is the outer method body - // rather than the lambda itself (which has no element). WPI - // does not support inferring types for lambda parameters, so - // ignore it. - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "Could not update from formal " - + "parameter assignment inside a lambda expression, because lambda parameters " - + "cannot be annotated.\nParameter: " - + paramElt); - } - return; + int index_1based = methodElt.getParameters().indexOf(paramElt) + 1; + if (index_1based == 0) { + // When paramElt is the parameter of a lambda contained in another + // method body, the enclosing element is the outer method body + // rather than the lambda itself (which has no element). WPI + // does not support inferring types for lambda parameters, so + // ignore it. + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "Could not update from formal " + + "parameter assignment inside a lambda expression, because lambda parameters " + + "cannot be annotated.\nParameter: " + + paramElt); + } + return; + } + + AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(paramElt); + AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(rhsTree); + atypeFactory.wpiAdjustForUpdateNonField(argATM); + T paramAnnotations = + storage.getParameterAnnotations( + methodElt, index_1based, paramATM, paramElt, atypeFactory); + String file = storage.getFileForElement(methodElt); + updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); } - AnnotatedTypeMirror paramATM = atypeFactory.getAnnotatedType(paramElt); - AnnotatedTypeMirror argATM = atypeFactory.getAnnotatedType(rhsTree); - atypeFactory.wpiAdjustForUpdateNonField(argATM); - T paramAnnotations = - storage.getParameterAnnotations(methodElt, index_1based, paramATM, paramElt, atypeFactory); - String file = storage.getFileForElement(methodElt); - updateAnnotationSet(paramAnnotations, TypeUseLocation.PARAMETER, argATM, paramATM, file); - } - - @Override - public void updateFromFieldAssignment(Node lhs, Node rhs) { - - Element element; - String fieldName; - if (lhs instanceof FieldAccessNode) { - element = ((FieldAccessNode) lhs).getElement(); - fieldName = ((FieldAccessNode) lhs).getFieldName(); - } else if (lhs instanceof LocalVariableNode) { - element = ((LocalVariableNode) lhs).getElement(); - fieldName = ((LocalVariableNode) lhs).getName(); - } else { - throw new BugInCF( - "updateFromFieldAssignment received an unexpected node type: " + lhs.getClass()); + @Override + public void updateFromFieldAssignment(Node lhs, Node rhs) { + + Element element; + String fieldName; + if (lhs instanceof FieldAccessNode) { + element = ((FieldAccessNode) lhs).getElement(); + fieldName = ((FieldAccessNode) lhs).getFieldName(); + } else if (lhs instanceof LocalVariableNode) { + element = ((LocalVariableNode) lhs).getElement(); + fieldName = ((LocalVariableNode) lhs).getName(); + } else { + throw new BugInCF( + "updateFromFieldAssignment received an unexpected node type: " + + lhs.getClass()); + } + + // TODO: For a primitive such as long, this is yielding just @GuardedBy rather than + // @GuardedBy({}). + AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(rhs.getTree()); + atypeFactory.wpiAdjustForUpdateField(lhs.getTree(), element, fieldName, rhsATM); + + updateFieldFromType(lhs.getTree(), element, fieldName, rhsATM); } - // TODO: For a primitive such as long, this is yielding just @GuardedBy rather than - // @GuardedBy({}). - AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(rhs.getTree()); - atypeFactory.wpiAdjustForUpdateField(lhs.getTree(), element, fieldName, rhsATM); + @Override + public void updateFieldFromType( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { - updateFieldFromType(lhs.getTree(), element, fieldName, rhsATM); - } + if (ignoreFieldInWPI(element, fieldName)) { + return; + } - @Override - public void updateFieldFromType( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) { + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(element)) { + return; + } - if (ignoreFieldInWPI(element, fieldName)) { - return; - } + String file = storage.getFileForElement(element); + + AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(lhsTree); + T fieldAnnotations = storage.getFieldAnnotations(element, fieldName, lhsATM, atypeFactory); + + if (fieldAnnotations == null) { + return; + } - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(element)) { - return; + updateAnnotationSet(fieldAnnotations, TypeUseLocation.FIELD, rhsATM, lhsATM, file); } - String file = storage.getFileForElement(element); + /** + * Returns true if an assignment to the given field should be ignored by WPI. + * + * @param element the field's element + * @param fieldName the field's name + * @return true if an assignment to the given field should be ignored by WPI + */ + protected boolean ignoreFieldInWPI(Element element, String fieldName) { + // Do not attempt to infer types for fields that do not have valid names. For example, + // compiler-generated temporary variables will have invalid names. Recording facts about + // fields with invalid names causes jaif-based WPI to crash when reading the .jaif file, + // and stub-based WPI to generate unparsable stub files. See + // https://github.com/typetools/checker-framework/issues/3442 + if (!SourceVersion.isIdentifier(fieldName)) { + return true; + } - AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(lhsTree); - T fieldAnnotations = storage.getFieldAnnotations(element, fieldName, lhsATM, atypeFactory); + // Don't infer types if the inferred field has a declaration annotation with the + // @IgnoreInWholeProgramInference meta-annotation. + if (atypeFactory.getDeclAnnotation(element, IgnoreInWholeProgramInference.class) != null + || atypeFactory + .getDeclAnnotationWithMetaAnnotation( + element, IgnoreInWholeProgramInference.class) + .size() + > 0) { + return true; + } - if (fieldAnnotations == null) { - return; - } + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(element)) { + return true; + } - updateAnnotationSet(fieldAnnotations, TypeUseLocation.FIELD, rhsATM, lhsATM, file); - } - - /** - * Returns true if an assignment to the given field should be ignored by WPI. - * - * @param element the field's element - * @param fieldName the field's name - * @return true if an assignment to the given field should be ignored by WPI - */ - protected boolean ignoreFieldInWPI(Element element, String fieldName) { - // Do not attempt to infer types for fields that do not have valid names. For example, - // compiler-generated temporary variables will have invalid names. Recording facts about - // fields with invalid names causes jaif-based WPI to crash when reading the .jaif file, - // and stub-based WPI to generate unparsable stub files. See - // https://github.com/typetools/checker-framework/issues/3442 - if (!SourceVersion.isIdentifier(fieldName)) { - return true; + return false; } - // Don't infer types if the inferred field has a declaration annotation with the - // @IgnoreInWholeProgramInference meta-annotation. - if (atypeFactory.getDeclAnnotation(element, IgnoreInWholeProgramInference.class) != null - || atypeFactory - .getDeclAnnotationWithMetaAnnotation(element, IgnoreInWholeProgramInference.class) - .size() - > 0) { - return true; - } + @Override + public void updateFromReturn( + ReturnNode retNode, + ClassSymbol classSymbol, + MethodTree methodDeclTree, + Map overriddenMethods) { + // Don't infer types for code that isn't presented as source. + if (methodDeclTree == null + || !ElementUtils.isElementFromSourceCode( + TreeUtils.elementFromDeclaration(methodDeclTree))) { + return; + } - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(element)) { - return true; - } + // Whole-program inference ignores some locations. See Issue 682: + // https://github.com/typetools/checker-framework/issues/682 + if (classSymbol == null) { // TODO: Handle anonymous classes. + return; + } - return false; - } - - @Override - public void updateFromReturn( - ReturnNode retNode, - ClassSymbol classSymbol, - MethodTree methodDeclTree, - Map overriddenMethods) { - // Don't infer types for code that isn't presented as source. - if (methodDeclTree == null - || !ElementUtils.isElementFromSourceCode( - TreeUtils.elementFromDeclaration(methodDeclTree))) { - return; - } + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(methodDeclTree); + String file = storage.getFileForElement(methodElt); - // Whole-program inference ignores some locations. See Issue 682: - // https://github.com/typetools/checker-framework/issues/682 - if (classSymbol == null) { // TODO: Handle anonymous classes. - return; + AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(methodDeclTree).getReturnType(); + // Type of the expression returned + AnnotatedTypeMirror rhsATM = + atypeFactory.getAnnotatedType(retNode.getTree().getExpression()); + atypeFactory.wpiAdjustForUpdateNonField(rhsATM); + DependentTypesHelper dependentTypesHelper = + ((GenericAnnotatedTypeFactory) atypeFactory).getDependentTypesHelper(); + dependentTypesHelper.delocalize(rhsATM, methodDeclTree); + T returnTypeAnnos = storage.getReturnAnnotations(methodElt, lhsATM, atypeFactory); + updateAnnotationSet(returnTypeAnnos, TypeUseLocation.RETURN, rhsATM, lhsATM, file); + + // Now, update return types of overridden methods based on the implementation we just saw. + // This inference is similar to the inference procedure for method parameters: both are + // updated based only on the implementations (in this case) or call-sites (for method + // parameters) that are available to WPI. + // + // An alternative implementation would be to: + // * update only the method (not overridden methods) + // * when finished, propagate the final result to overridden methods + // + for (Map.Entry pair : + overriddenMethods.entrySet()) { + ExecutableElement overriddenMethodElement = pair.getValue(); + + // Don't infer types for code that isn't presented as source. + if (!ElementUtils.isElementFromSourceCode(overriddenMethodElement)) { + continue; + } + + AnnotatedExecutableType overriddenMethod = + atypeFactory.getAnnotatedType(overriddenMethodElement); + String superClassFile = storage.getFileForElement(overriddenMethodElement); + AnnotatedTypeMirror overriddenMethodReturnType = overriddenMethod.getReturnType(); + T storedOverriddenMethodReturnTypeAnnotations = + storage.getReturnAnnotations( + overriddenMethodElement, overriddenMethodReturnType, atypeFactory); + + updateAnnotationSet( + storedOverriddenMethodReturnTypeAnnotations, + TypeUseLocation.RETURN, + rhsATM, + overriddenMethodReturnType, + superClassFile); + } } - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(methodDeclTree); - String file = storage.getFileForElement(methodElt); - - AnnotatedTypeMirror lhsATM = atypeFactory.getAnnotatedType(methodDeclTree).getReturnType(); - // Type of the expression returned - AnnotatedTypeMirror rhsATM = atypeFactory.getAnnotatedType(retNode.getTree().getExpression()); - atypeFactory.wpiAdjustForUpdateNonField(rhsATM); - DependentTypesHelper dependentTypesHelper = - ((GenericAnnotatedTypeFactory) atypeFactory).getDependentTypesHelper(); - dependentTypesHelper.delocalize(rhsATM, methodDeclTree); - T returnTypeAnnos = storage.getReturnAnnotations(methodElt, lhsATM, atypeFactory); - updateAnnotationSet(returnTypeAnnos, TypeUseLocation.RETURN, rhsATM, lhsATM, file); - - // Now, update return types of overridden methods based on the implementation we just saw. - // This inference is similar to the inference procedure for method parameters: both are - // updated based only on the implementations (in this case) or call-sites (for method - // parameters) that are available to WPI. - // - // An alternative implementation would be to: - // * update only the method (not overridden methods) - // * when finished, propagate the final result to overridden methods - // - for (Map.Entry pair : overriddenMethods.entrySet()) { - ExecutableElement overriddenMethodElement = pair.getValue(); - - // Don't infer types for code that isn't presented as source. - if (!ElementUtils.isElementFromSourceCode(overriddenMethodElement)) { - continue; - } - - AnnotatedExecutableType overriddenMethod = - atypeFactory.getAnnotatedType(overriddenMethodElement); - String superClassFile = storage.getFileForElement(overriddenMethodElement); - AnnotatedTypeMirror overriddenMethodReturnType = overriddenMethod.getReturnType(); - T storedOverriddenMethodReturnTypeAnnotations = - storage.getReturnAnnotations( - overriddenMethodElement, overriddenMethodReturnType, atypeFactory); - - updateAnnotationSet( - storedOverriddenMethodReturnTypeAnnotations, - TypeUseLocation.RETURN, - rhsATM, - overriddenMethodReturnType, - superClassFile); + @Override + public void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno) { + this.addMethodDeclarationAnnotation(methodElt, anno, false); } - } - @Override - public void addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno) { - this.addMethodDeclarationAnnotation(methodElt, anno, false); - } + @Override + public void addMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno, boolean lubPurity) { - @Override - public void addMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno, boolean lubPurity) { + // Do not infer types for library code, only for type-checked source code. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; + } - // Do not infer types for library code, only for type-checked source code. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; - } + // Special-case handling for purity annotations. + AnnotationMirror annoToAdd; + if (!(lubPurity && isPurityAnno(anno))) { + annoToAdd = anno; + } else { + // It's a purity annotation and `lubPurity` is true. Do a "least upper bound" between + // the current purity annotation inferred for the method and anno. This is necessary + // to avoid WPI inferring incompatible purity annotations on methods that override + // methods from their superclass. + // TODO: this would be unnecessary if purity was implemented as a type system. + AnnotationMirror currentPurityAnno = getPurityAnnotation(methodElt); + if (currentPurityAnno == null) { + annoToAdd = anno; + } else { + // Clear the current purity annotation, because at this point a new one is + // definitely going to be inferred. + storage.removeMethodDeclarationAnnotation(methodElt, currentPurityAnno); + annoToAdd = lubPurityAnnotations(anno, currentPurityAnno); + } + } - // Special-case handling for purity annotations. - AnnotationMirror annoToAdd; - if (!(lubPurity && isPurityAnno(anno))) { - annoToAdd = anno; - } else { - // It's a purity annotation and `lubPurity` is true. Do a "least upper bound" between - // the current purity annotation inferred for the method and anno. This is necessary - // to avoid WPI inferring incompatible purity annotations on methods that override - // methods from their superclass. - // TODO: this would be unnecessary if purity was implemented as a type system. - AnnotationMirror currentPurityAnno = getPurityAnnotation(methodElt); - if (currentPurityAnno == null) { - annoToAdd = anno; - } else { - // Clear the current purity annotation, because at this point a new one is - // definitely going to be inferred. - storage.removeMethodDeclarationAnnotation(methodElt, currentPurityAnno); - annoToAdd = lubPurityAnnotations(anno, currentPurityAnno); - } + String file = storage.getFileForElement(methodElt); + boolean isNewAnnotation = storage.addMethodDeclarationAnnotation(methodElt, annoToAdd); + if (isNewAnnotation) { + storage.setFileModified(file); + } } - String file = storage.getFileForElement(methodElt); - boolean isNewAnnotation = storage.addMethodDeclarationAnnotation(methodElt, annoToAdd); - if (isNewAnnotation) { - storage.setFileModified(file); - } - } - - /** - * Computes a "least upper bound" between two purity annotations (an annotation is a purity - * annotation if and only if {@link #isPurityAnno(AnnotationMirror)} returns true). In the - * "lattice", Impure is the top, SideEffectFree and Deterministic are siblings below it, and Pure - * is the bottom, below them. Note that this routine is "fail-safe": Impure is returned if either - * of the input annotations is not actually a purity annotation. - * - * @param anno1 a purity annotation - * @param anno2 another purity annotation - * @return the "least upper bound" between anno1 and anno2, as described above - */ - private AnnotationMirror lubPurityAnnotations(AnnotationMirror anno1, AnnotationMirror anno2) { - // TODO: is this the best way to do this? Would it be easier to just write a real subtype - // routine for purity? Do we have code to handle this already somewhere? - - boolean anno1IsDet = - AnnotationUtils.areSameByName(anno1, PURE_NAME) - || AnnotationUtils.areSameByName(anno1, DETERMINISTIC_NAME); - boolean anno1IsSEF = - AnnotationUtils.areSameByName(anno1, PURE_NAME) - || AnnotationUtils.areSameByName(anno1, SIDE_EFFECT_FREE_NAME); - - boolean anno2IsDet = - AnnotationUtils.areSameByName(anno2, PURE_NAME) - || AnnotationUtils.areSameByName(anno2, DETERMINISTIC_NAME); - boolean anno2IsSEF = - AnnotationUtils.areSameByName(anno2, PURE_NAME) - || AnnotationUtils.areSameByName(anno2, SIDE_EFFECT_FREE_NAME); - - if (anno2IsSEF && anno2IsDet && anno1IsSEF && anno1IsDet) { - return PURE; - } else if (anno2IsSEF && anno1IsSEF) { - return SIDE_EFFECT_FREE; - } else if (anno2IsDet && anno1IsDet) { - return DETERMINISTIC; - } else { - return IMPURE; - } - } - - /** - * Returns the purity annotation ({@link Pure}, {@link SideEffectFree}, {@link Deterministic}, or - * {@link Impure}) currently associated with the given executable element in this round of - * inference, if there is one. Invariant: no more than one purity annotation should ever be - * present on a given executable element at a time. - * - * @param methodElt a method element - * @return the purity annotation, or null if none has yet been inferred - */ - private @Nullable AnnotationMirror getPurityAnnotation(ExecutableElement methodElt) { - AnnotationMirrorSet declAnnos = storage.getMethodDeclarationAnnotations(methodElt); - if (declAnnos.isEmpty()) { - return null; - } - for (AnnotationMirror declAnno : declAnnos) { - if (isPurityAnno(declAnno)) { - return declAnno; - } - } - return null; - } - - /** - * Returns true if the given annotation is {@link Pure}, {@link SideEffectFree}, {@link - * Deterministic}, or {@link Impure}. Returns false otherwise. - * - * @param anno an annotation - * @return true iff the annotation is a purity annotation - */ - private boolean isPurityAnno(AnnotationMirror anno) { - return AnnotationUtils.areSameByName(anno, PURE_NAME) - || AnnotationUtils.areSameByName(anno, SIDE_EFFECT_FREE_NAME) - || AnnotationUtils.areSameByName(anno, DETERMINISTIC_NAME) - || AnnotationUtils.areSameByName(anno, IMPURE_NAME); - } - - @Override - public void addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(field)) { - return; + /** + * Computes a "least upper bound" between two purity annotations (an annotation is a purity + * annotation if and only if {@link #isPurityAnno(AnnotationMirror)} returns true). In the + * "lattice", Impure is the top, SideEffectFree and Deterministic are siblings below it, and + * Pure is the bottom, below them. Note that this routine is "fail-safe": Impure is returned if + * either of the input annotations is not actually a purity annotation. + * + * @param anno1 a purity annotation + * @param anno2 another purity annotation + * @return the "least upper bound" between anno1 and anno2, as described above + */ + private AnnotationMirror lubPurityAnnotations(AnnotationMirror anno1, AnnotationMirror anno2) { + // TODO: is this the best way to do this? Would it be easier to just write a real subtype + // routine for purity? Do we have code to handle this already somewhere? + + boolean anno1IsDet = + AnnotationUtils.areSameByName(anno1, PURE_NAME) + || AnnotationUtils.areSameByName(anno1, DETERMINISTIC_NAME); + boolean anno1IsSEF = + AnnotationUtils.areSameByName(anno1, PURE_NAME) + || AnnotationUtils.areSameByName(anno1, SIDE_EFFECT_FREE_NAME); + + boolean anno2IsDet = + AnnotationUtils.areSameByName(anno2, PURE_NAME) + || AnnotationUtils.areSameByName(anno2, DETERMINISTIC_NAME); + boolean anno2IsSEF = + AnnotationUtils.areSameByName(anno2, PURE_NAME) + || AnnotationUtils.areSameByName(anno2, SIDE_EFFECT_FREE_NAME); + + if (anno2IsSEF && anno2IsDet && anno1IsSEF && anno1IsDet) { + return PURE; + } else if (anno2IsSEF && anno1IsSEF) { + return SIDE_EFFECT_FREE; + } else if (anno2IsDet && anno1IsDet) { + return DETERMINISTIC; + } else { + return IMPURE; + } } - String file = storage.getFileForElement(field); - boolean isNewAnnotation = storage.addFieldDeclarationAnnotation(field, anno); - if (isNewAnnotation) { - storage.setFileModified(file); - } - } - - @Override - public void addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + /** + * Returns the purity annotation ({@link Pure}, {@link SideEffectFree}, {@link Deterministic}, + * or {@link Impure}) currently associated with the given executable element in this round of + * inference, if there is one. Invariant: no more than one purity annotation should ever be + * present on a given executable element at a time. + * + * @param methodElt a method element + * @return the purity annotation, or null if none has yet been inferred + */ + private @Nullable AnnotationMirror getPurityAnnotation(ExecutableElement methodElt) { + AnnotationMirrorSet declAnnos = storage.getMethodDeclarationAnnotations(methodElt); + if (declAnnos.isEmpty()) { + return null; + } + for (AnnotationMirror declAnno : declAnnos) { + if (isPurityAnno(declAnno)) { + return declAnno; + } + } + return null; } - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return; + + /** + * Returns true if the given annotation is {@link Pure}, {@link SideEffectFree}, {@link + * Deterministic}, or {@link Impure}. Returns false otherwise. + * + * @param anno an annotation + * @return true iff the annotation is a purity annotation + */ + private boolean isPurityAnno(AnnotationMirror anno) { + return AnnotationUtils.areSameByName(anno, PURE_NAME) + || AnnotationUtils.areSameByName(anno, SIDE_EFFECT_FREE_NAME) + || AnnotationUtils.areSameByName(anno, DETERMINISTIC_NAME) + || AnnotationUtils.areSameByName(anno, IMPURE_NAME); } - String file = storage.getFileForElement(methodElt); - boolean isNewAnnotation = - storage.addDeclarationAnnotationToFormalParameter(methodElt, index_1based, anno); - if (isNewAnnotation) { - storage.setFileModified(file); + @Override + public void addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(field)) { + return; + } + + String file = storage.getFileForElement(field); + boolean isNewAnnotation = storage.addFieldDeclarationAnnotation(field, anno); + if (isNewAnnotation) { + storage.setFileModified(file); + } } - } - @Override - public void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(classElt)) { - return; + @Override + public void addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return; + } + + String file = storage.getFileForElement(methodElt); + boolean isNewAnnotation = + storage.addDeclarationAnnotationToFormalParameter(methodElt, index_1based, anno); + if (isNewAnnotation) { + storage.setFileModified(file); + } } - String file = storage.getFileForElement(classElt); - boolean isNewAnnotation = storage.addClassDeclarationAnnotation(classElt, anno); - if (isNewAnnotation) { - storage.setFileModified(file); + @Override + public void addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(classElt)) { + return; + } + + String file = storage.getFileForElement(classElt); + boolean isNewAnnotation = storage.addClassDeclarationAnnotation(classElt, anno); + if (isNewAnnotation) { + storage.setFileModified(file); + } } - } - - /** - * Updates the set of annotations in a location in a program. - * - *

            - *
          • If there was no previous annotation for that location, then the updated set will be the - * annotations in rhsATM. - *
          • If there was a previous annotation, the updated set will be the LUB between the previous - * annotation and rhsATM. - *
          - * - *

          Subclasses can customize this behavior. - * - * @param annotationsToUpdate the type whose annotations are modified by this method - * @param defLoc the location where the annotation will be added - * @param rhsATM the RHS of the annotated type on the source code - * @param lhsATM the LHS of the annotated type on the source code - * @param file the annotation file containing the executable; used for marking the scene as - * modified (needing to be written to disk) - */ - protected void updateAnnotationSet( - T annotationsToUpdate, - TypeUseLocation defLoc, - AnnotatedTypeMirror rhsATM, - AnnotatedTypeMirror lhsATM, - String file) { - updateAnnotationSet(annotationsToUpdate, defLoc, rhsATM, lhsATM, file, true); - } - - /** - * Updates the set of annotations in a location in a program. - * - *

            - *
          • If there was no previous annotation for that location, then the updated set will be the - * annotations in rhsATM. - *
          • If there was a previous annotation, the updated set will be the LUB between the previous - * annotation and rhsATM. - *
          - * - *

          Subclasses can customize this behavior. - * - * @param annotationsToUpdate the type whose annotations are modified by this method - * @param defLoc the location where the annotation will be added - * @param rhsATM the RHS of the annotated type on the source code - * @param lhsATM the LHS of the annotated type on the source code - * @param file annotation file containing the executable; used for marking the scene as modified - * (needing to be written to disk) - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - protected void updateAnnotationSet( - T annotationsToUpdate, - TypeUseLocation defLoc, - AnnotatedTypeMirror rhsATM, - AnnotatedTypeMirror lhsATM, - String file, - boolean ignoreIfAnnotated) { - if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { - return; + + /** + * Updates the set of annotations in a location in a program. + * + *

            + *
          • If there was no previous annotation for that location, then the updated set will be the + * annotations in rhsATM. + *
          • If there was a previous annotation, the updated set will be the LUB between the + * previous annotation and rhsATM. + *
          + * + *

          Subclasses can customize this behavior. + * + * @param annotationsToUpdate the type whose annotations are modified by this method + * @param defLoc the location where the annotation will be added + * @param rhsATM the RHS of the annotated type on the source code + * @param lhsATM the LHS of the annotated type on the source code + * @param file the annotation file containing the executable; used for marking the scene as + * modified (needing to be written to disk) + */ + protected void updateAnnotationSet( + T annotationsToUpdate, + TypeUseLocation defLoc, + AnnotatedTypeMirror rhsATM, + AnnotatedTypeMirror lhsATM, + String file) { + updateAnnotationSet(annotationsToUpdate, defLoc, rhsATM, lhsATM, file, true); } - AnnotatedTypeMirror atmFromStorage = - storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); - updateAtmWithLub(rhsATM, atmFromStorage); - - // For type variables, infer primary annotations for field type use locations, but - // for other locations only infer primary annotations if they are a super type of the upper - // bound of declaration of the type variable. - if (defLoc != TypeUseLocation.FIELD && lhsATM instanceof AnnotatedTypeVariable) { - AnnotatedTypeVariable lhsTV = (AnnotatedTypeVariable) lhsATM; - AnnotatedTypeMirror decl = - atypeFactory.getAnnotatedType(lhsTV.getUnderlyingType().asElement()); - AnnotationMirrorSet upperAnnos = decl.getEffectiveAnnotations(); - - // If the inferred type is a subtype of the upper bounds of the - // current type in the source code, do nothing. - TypeMirror rhsTM = rhsATM.getUnderlyingType(); - TypeMirror declTM = decl.getUnderlyingType(); - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - for (AnnotationMirror anno : rhsATM.getAnnotations()) { - AnnotationMirror upperAnno = qualHierarchy.findAnnotationInSameHierarchy(upperAnnos, anno); - if (qualHierarchy.isSubtypeShallow(anno, rhsTM, upperAnno, declTM)) { - rhsATM.removeAnnotation(anno); + /** + * Updates the set of annotations in a location in a program. + * + *

            + *
          • If there was no previous annotation for that location, then the updated set will be the + * annotations in rhsATM. + *
          • If there was a previous annotation, the updated set will be the LUB between the + * previous annotation and rhsATM. + *
          + * + *

          Subclasses can customize this behavior. + * + * @param annotationsToUpdate the type whose annotations are modified by this method + * @param defLoc the location where the annotation will be added + * @param rhsATM the RHS of the annotated type on the source code + * @param lhsATM the LHS of the annotated type on the source code + * @param file annotation file containing the executable; used for marking the scene as modified + * (needing to be written to disk) + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + protected void updateAnnotationSet( + T annotationsToUpdate, + TypeUseLocation defLoc, + AnnotatedTypeMirror rhsATM, + AnnotatedTypeMirror lhsATM, + String file, + boolean ignoreIfAnnotated) { + if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { + return; + } + + AnnotatedTypeMirror atmFromStorage = + storage.atmFromStorageLocation(rhsATM.getUnderlyingType(), annotationsToUpdate); + updateAtmWithLub(rhsATM, atmFromStorage); + + // For type variables, infer primary annotations for field type use locations, but + // for other locations only infer primary annotations if they are a super type of the upper + // bound of declaration of the type variable. + if (defLoc != TypeUseLocation.FIELD && lhsATM instanceof AnnotatedTypeVariable) { + AnnotatedTypeVariable lhsTV = (AnnotatedTypeVariable) lhsATM; + AnnotatedTypeMirror decl = + atypeFactory.getAnnotatedType(lhsTV.getUnderlyingType().asElement()); + AnnotationMirrorSet upperAnnos = decl.getEffectiveAnnotations(); + + // If the inferred type is a subtype of the upper bounds of the + // current type in the source code, do nothing. + TypeMirror rhsTM = rhsATM.getUnderlyingType(); + TypeMirror declTM = decl.getUnderlyingType(); + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + for (AnnotationMirror anno : rhsATM.getAnnotations()) { + AnnotationMirror upperAnno = + qualHierarchy.findAnnotationInSameHierarchy(upperAnnos, anno); + if (qualHierarchy.isSubtypeShallow(anno, rhsTM, upperAnno, declTM)) { + rhsATM.removeAnnotation(anno); + } + } + if (rhsATM.getAnnotations().isEmpty()) { + return; + } } - } - if (rhsATM.getAnnotations().isEmpty()) { - return; - } + storage.updateStorageLocationFromAtm( + rhsATM, lhsATM, annotationsToUpdate, defLoc, ignoreIfAnnotated); + storage.setFileModified(file); } - storage.updateStorageLocationFromAtm( - rhsATM, lhsATM, annotationsToUpdate, defLoc, ignoreIfAnnotated); - storage.setFileModified(file); - } - - /** - * Prints a debugging message about a failed inference. Must only be called after {@link - * #showWpiFailedInferences} has been checked, to avoid constructing the debugging message - * eagerly. - * - * @param reason a message describing the reason an inference was unsuccessful, which will be - * displayed to the user - */ - private void printFailedInferenceDebugMessage(String reason) { - assert showWpiFailedInferences; - // TODO: it would be nice if this message also included a line number - // for the file being analyzed, but I don't know how to get that information - // here, given that this message is called from places where only the annotated - // type mirrors for the LHS and RHS of some pseduo-assignment are available. - System.out.println("WPI failed to make an inference: " + reason); - } - - @Override - public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) { - - if (sourceCodeATM.getKind() != ajavaATM.getKind()) { - // Ignore null types: passing them to asSuper causes a crash, as they cannot be - // substituted for type variables. If sourceCodeATM is a null type, only the primary - // annotation will be considered anyway, so there is no danger of recursing into - // typevar bounds. - if (sourceCodeATM.getKind() != TypeKind.NULL) { - // This can happen e.g. when recursing into the bounds of a type variable: - // the bound on sourceCodeATM might be a declared type (such as T), while - // the ajavaATM might be a typevar (such as S extends T), or vice-versa. In - // that case, use asSuper to make the two ATMs fully-compatible. - sourceCodeATM = AnnotatedTypes.asSuper(this.atypeFactory, sourceCodeATM, ajavaATM); - } + + /** + * Prints a debugging message about a failed inference. Must only be called after {@link + * #showWpiFailedInferences} has been checked, to avoid constructing the debugging message + * eagerly. + * + * @param reason a message describing the reason an inference was unsuccessful, which will be + * displayed to the user + */ + private void printFailedInferenceDebugMessage(String reason) { + assert showWpiFailedInferences; + // TODO: it would be nice if this message also included a line number + // for the file being analyzed, but I don't know how to get that information + // here, given that this message is called from places where only the annotated + // type mirrors for the LHS and RHS of some pseduo-assignment are available. + System.out.println("WPI failed to make an inference: " + reason); } - switch (sourceCodeATM.getKind()) { - case TYPEVAR: - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), - ((AnnotatedTypeVariable) ajavaATM).getLowerBound()); - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), - ((AnnotatedTypeVariable) ajavaATM).getUpperBound()); - break; - case WILDCARD: - break; - // throw new BugInCF("This can't happen"); - // TODO: This comment is wrong: the wildcard case does get entered. - // Because inferring type arguments is not supported, wildcards won't be - // encountered. - // updateATMWithLUB( - // atf, - // ((AnnotatedWildcardType) sourceCodeATM).getExtendsBound(), - // ((AnnotatedWildcardType) ajavaATM).getExtendsBound()); - // updateATMWithLUB( - // atf, - // ((AnnotatedWildcardType) sourceCodeATM).getSuperBound(), - // ((AnnotatedWildcardType) ajavaATM).getSuperBound()); - // break; - case ARRAY: - AnnotatedTypeMirror sourceCodeComponent = - ((AnnotatedArrayType) sourceCodeATM).getComponentType(); - AnnotatedTypeMirror ajavaComponent = ((AnnotatedArrayType) ajavaATM).getComponentType(); - if (sourceCodeComponent.getKind() == ajavaComponent.getKind()) { - updateAtmWithLub(sourceCodeComponent, ajavaComponent); - } else { - if (showWpiFailedInferences) { - printFailedInferenceDebugMessage( - "attempted to update the component type of an array type, but found an unexpected" - + " difference in type structure.\n" - + "LHS kind: " - + sourceCodeComponent.getKind() - + "\nRHS kind: " - + ajavaComponent.getKind()); - break; - } + @Override + public void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror ajavaATM) { + + if (sourceCodeATM.getKind() != ajavaATM.getKind()) { + // Ignore null types: passing them to asSuper causes a crash, as they cannot be + // substituted for type variables. If sourceCodeATM is a null type, only the primary + // annotation will be considered anyway, so there is no danger of recursing into + // typevar bounds. + if (sourceCodeATM.getKind() != TypeKind.NULL) { + // This can happen e.g. when recursing into the bounds of a type variable: + // the bound on sourceCodeATM might be a declared type (such as T), while + // the ajavaATM might be a typevar (such as S extends T), or vice-versa. In + // that case, use asSuper to make the two ATMs fully-compatible. + sourceCodeATM = AnnotatedTypes.asSuper(this.atypeFactory, sourceCodeATM, ajavaATM); + } + } + + switch (sourceCodeATM.getKind()) { + case TYPEVAR: + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), + ((AnnotatedTypeVariable) ajavaATM).getLowerBound()); + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), + ((AnnotatedTypeVariable) ajavaATM).getUpperBound()); + break; + case WILDCARD: + break; + // throw new BugInCF("This can't happen"); + // TODO: This comment is wrong: the wildcard case does get entered. + // Because inferring type arguments is not supported, wildcards won't be + // encountered. + // updateATMWithLUB( + // atf, + // ((AnnotatedWildcardType) sourceCodeATM).getExtendsBound(), + // ((AnnotatedWildcardType) ajavaATM).getExtendsBound()); + // updateATMWithLUB( + // atf, + // ((AnnotatedWildcardType) sourceCodeATM).getSuperBound(), + // ((AnnotatedWildcardType) ajavaATM).getSuperBound()); + // break; + case ARRAY: + AnnotatedTypeMirror sourceCodeComponent = + ((AnnotatedArrayType) sourceCodeATM).getComponentType(); + AnnotatedTypeMirror ajavaComponent = + ((AnnotatedArrayType) ajavaATM).getComponentType(); + if (sourceCodeComponent.getKind() == ajavaComponent.getKind()) { + updateAtmWithLub(sourceCodeComponent, ajavaComponent); + } else { + if (showWpiFailedInferences) { + printFailedInferenceDebugMessage( + "attempted to update the component type of an array type, but found an unexpected" + + " difference in type structure.\n" + + "LHS kind: " + + sourceCodeComponent.getKind() + + "\nRHS kind: " + + ajavaComponent.getKind()); + break; + } + } + break; + // case DECLARED: + // Inferring annotations on type arguments is not supported, so no need to recur on + // generic types. If this was ever implemented, this method would need a + // VisitHistory object to prevent infinite recursion on types such as T extends + // List. + default: + // ATM only has primary annotations + break; } - break; - // case DECLARED: - // Inferring annotations on type arguments is not supported, so no need to recur on - // generic types. If this was ever implemented, this method would need a - // VisitHistory object to prevent infinite recursion on types such as T extends - // List. - default: - // ATM only has primary annotations - break; + + // LUB primary annotations + AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); + for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { + AnnotationMirror amAjava = ajavaATM.getAnnotationInHierarchy(amSource); + // amAjava only contains annotations from the ajava file, so it might be missing + // an annotation in the hierarchy. + if (amAjava != null) { + amSource = + atypeFactory + .getQualifierHierarchy() + .leastUpperBoundShallow( + amSource, + sourceCodeATM.getUnderlyingType(), + amAjava, + ajavaATM.getUnderlyingType()); + } + annosToReplace.add(amSource); + } + sourceCodeATM.replaceAnnotations(annosToReplace); + } + + @Override + public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { + storage.writeResultsToFile(outputFormat, checker); } - // LUB primary annotations - AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); - for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { - AnnotationMirror amAjava = ajavaATM.getAnnotationInHierarchy(amSource); - // amAjava only contains annotations from the ajava file, so it might be missing - // an annotation in the hierarchy. - if (amAjava != null) { - amSource = - atypeFactory - .getQualifierHierarchy() - .leastUpperBoundShallow( - amSource, - sourceCodeATM.getUnderlyingType(), - amAjava, - ajavaATM.getUnderlyingType()); - } - annosToReplace.add(amSource); + @Override + public void preprocessClassTree(ClassTree classTree) { + storage.preprocessClassTree(classTree); } - sourceCodeATM.replaceAnnotations(annosToReplace); - } - - @Override - public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { - storage.writeResultsToFile(outputFormat, checker); - } - - @Override - public void preprocessClassTree(ClassTree classTree) { - storage.preprocessClassTree(classTree); - } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index 0def33fd8db..1321a1eba1b 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -36,36 +36,7 @@ import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.Writer; -import java.lang.annotation.Annotation; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.stream.Collectors; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; + import org.checkerframework.afu.scenelib.util.JVMNames; import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -97,1851 +68,1938 @@ import org.plumelib.util.IPair; import org.plumelib.util.UtilPlume; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; + /** * This is an implementation of {@link WholeProgramInferenceStorage} that stores annotations * directly with the JavaParser node corresponding to the annotation's location. It outputs ajava * files. */ public class WholeProgramInferenceJavaParserStorage - implements WholeProgramInferenceStorage { - - /** - * Directory where .ajava files will be written to and read from. This directory is relative to - * where the javac command is executed. - */ - public static final File AJAVA_FILES_PATH = new File("build", "whole-program-inference"); - - /** The type factory associated with this. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The element utilities for {@code atypeFactory}. */ - protected final Elements elements; - - /** - * Maps from binary class name to the wrapper containing the class. Contains all classes in Java - * source files containing an Element for which an annotation has been inferred. - */ - private Map<@BinaryName String, ClassOrInterfaceAnnos> classToAnnos = new HashMap<>(); - - /** Maps from binary class name to binary names of all supertypes. */ - private Map<@BinaryName String, Set<@BinaryName String>> supertypesMap = new HashMap<>(); - - /** Maps from binary class name to binary names of all known subtypes. */ - private Map<@BinaryName String, Set<@BinaryName String>> subtypesMap = new HashMap<>(); - - /** - * Files containing classes for which an annotation has been inferred since the last time files - * were written to disk. - */ - private Set modifiedFiles = new HashSet<>(); - - /** Mapping from source file to the wrapper for the compilation unit parsed from that file. */ - private Map sourceToAnnos = new HashMap<>(); - - /** Maps from binary class name to the source file that contains it. */ - private Map classToSource = new HashMap<>(); - - /** Whether the {@code -AinferOutputOriginal} option was supplied to the checker. */ - private final boolean inferOutputOriginal; - - /** - * Returns the names of all qualifiers that are marked with {@link InvisibleQualifier}, and that - * are supported by the given type factory. - * - * @param atypeFactory a type factory - * @return the names of every invisible qualifier supported by {@code atypeFactory} - */ - public static Set getInvisibleQualifierNames(AnnotatedTypeFactory atypeFactory) { - return atypeFactory.getSupportedTypeQualifiers().stream() - .filter(WholeProgramInferenceJavaParserStorage::isInvisible) - .map(Class::getCanonicalName) - .collect(Collectors.toSet()); - } - - /** - * Is the definition of the given annotation class annotated with {@link InvisibleQualifier}? - * - * @param qual an annotation class - * @return true iff {@code qual} is meta-annotated with {@link InvisibleQualifier} - */ - @Pure - public static boolean isInvisible(Class qual) { - return Arrays.stream(qual.getAnnotations()) - .anyMatch(anno -> anno.annotationType() == InvisibleQualifier.class); - } - - /** - * Constructs a new {@code WholeProgramInferenceJavaParser} that has not yet inferred any - * annotations. - * - * @param atypeFactory the associated type factory - * @param inferOutputOriginal whether the -AinferOutputOriginal option was supplied to the checker - */ - public WholeProgramInferenceJavaParserStorage( - AnnotatedTypeFactory atypeFactory, boolean inferOutputOriginal) { - this.atypeFactory = atypeFactory; - this.elements = atypeFactory.getElementUtils(); - this.inferOutputOriginal = inferOutputOriginal; - } - - @Override - public String getFileForElement(Element elt) { - return addClassesForElement(elt); - } - - @Override - public void setFileModified(String path) { - modifiedFiles.add(path); - } - - /** - * Set the source file as modified, for the given class. - * - * @param className the binary name of a class that should be written to disk - */ - private void setClassModified(@Nullable @BinaryName String className) { - if (className == null) { - return; - } - String path = classToSource.get(className); - if (path != null) { - setFileModified(path); - } - } - - /** - * Set the source files as modified, for all the given classes. - * - * @param classNames the binary names of classes that should be written to disk - */ - private void setClassesModified(@Nullable Collection<@BinaryName String> classNames) { - if (classNames == null) { - return; - } - for (String className : classNames) { - setClassModified(className); - } - } - - /** - * For every modified file, consider its subclasses and superclasses modified, too. The reason is - * that an annotation change in a class might require annotations in its superclasses and - * supclasses to be modified, in order to preserve behavioral subtyping. Setting it modified will - * cause it to be written out, and while writing out, the annotations will be made consistent - * across the class hierarchy by {@link #wpiPrepareCompilationUnitForWriting}. - */ - public void setSupertypesAndSubtypesModified() { - // Copy into a list to avoid a ConcurrentModificationException. - for (String path : new ArrayList<>(modifiedFiles)) { - CompilationUnitAnnos cuAnnos = sourceToAnnos.get(path); - for (ClassOrInterfaceAnnos classAnnos : cuAnnos.types) { - String className = classAnnos.className; - setClassesModified(supertypesMap.get(className)); - setClassesModified(subtypesMap.get(className)); - } - } - } - - /// - /// Reading stored annotations - /// - - @Override - public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { - return getMethodAnnos(methodElt) != null; - } - - @Override - public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement methodElt) { - String className = ElementUtils.getEnclosingClassName(methodElt); - // Read in classes for the element. - getFileForElement(methodElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return AnnotationMirrorSet.emptySet(); - } - CallableDeclarationAnnos methodAnnos = - classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); - if (methodAnnos == null) { - return AnnotationMirrorSet.emptySet(); - } - return methodAnnos.getDeclarationAnnotations(); - } - - /** - * Get the annotations for a method or constructor. - * - * @param methodElt the method or constructor - * @return the annotations for a method or constructor - */ - private @Nullable CallableDeclarationAnnos getMethodAnnos(ExecutableElement methodElt) { - String className = ElementUtils.getEnclosingClassName(methodElt); - // Read in classes for the element. - getFileForElement(methodElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return null; - } - CallableDeclarationAnnos methodAnnos = - classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); - return methodAnnos; - } - - /** - * Get the annotations for a field. - * - * @param fieldElt a field - * @return the annotations for a field - */ - private @Nullable FieldAnnos getFieldAnnos(VariableElement fieldElt) { - String className = ElementUtils.getEnclosingClassName(fieldElt); - // Read in classes for the element. - getFileForElement(fieldElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return null; - } - FieldAnnos fieldAnnos = classAnnos.fields.get(fieldElt.getSimpleName().toString()); - return fieldAnnos; - } - - @Override - public AnnotatedTypeMirror getParameterAnnotations( - ExecutableElement methodElt, - @Positive int index_1based, - AnnotatedTypeMirror paramATM, - VariableElement ve, - AnnotatedTypeFactory atypeFactory) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // When processing anonymous inner classes outside their compilation units, - // it might not have been possible to create an appropriate CallableDeclarationAnnos: - // no element would have been available, causing the computed method signature to - // be incorrect. In this case, abort looking up annotations -- inference will fail, - // because even if WPI inferred something, it couldn't be printed. - return paramATM; - } - return methodAnnos.getParameterTypeInitialized(paramATM, index_1based, atypeFactory); - } - - @Override - public AnnotatedTypeMirror getReceiverAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror paramATM, - AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return paramATM; - } - return methodAnnos.getReceiverType(paramATM, atypeFactory); - } - - @Override - public AnnotatedTypeMirror getReturnAnnotations( - ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return atm; - } - return methodAnnos.getReturnType(atm, atypeFactory); - } - - @Override - public @Nullable AnnotatedTypeMirror getFieldAnnotations( - Element element, - String fieldName, - AnnotatedTypeMirror lhsATM, - AnnotatedTypeFactory atypeFactory) { - ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); - // Read in classes for the element. - getFileForElement(element); - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @BinaryName String className = enclosingClass.flatname.toString(); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - return null; - } - // If it's an enum constant it won't appear as a field - // and it won't have extra annotations, so just return the basic type: - if (classAnnos.enumConstants.contains(fieldName)) { - return lhsATM; - } else if (classAnnos.fields.get(fieldName) == null) { - // There might not be a corresponding entry for the field name - // in an anonymous class, if the field and class were defined in - // another compilation unit (for the same reason that a method - // might not have an entry, as in #getParameterAnnotations, above). - return null; - } else { - return classAnnos.fields.get(fieldName).getType(lhsATM, atypeFactory); - } - } - - @Override - public AnnotatedTypeMirror getPreOrPostconditions( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - switch (preOrPost) { - case BEFORE: - return getPreconditionsForExpression(methodElement, expression, declaredType, atypeFactory); - case AFTER: - return getPostconditionsForExpression( - methodElement, expression, declaredType, atypeFactory); - default: - throw new BugInCF("Unexpected " + preOrPost); - } - } - - /** - * Returns the precondition annotations for the given expression. - * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @param atypeFactory the type factory - * @return the precondition annotations for a field - */ - private AnnotatedTypeMirror getPreconditionsForExpression( - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return declaredType; - } - return methodAnnos.getPreconditionsForExpression(expression, declaredType, atypeFactory); - } - - /** - * Returns the postcondition annotations for an expression. - * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @param atypeFactory the type factory - * @return the postcondition annotations for a field - */ - private AnnotatedTypeMirror getPostconditionsForExpression( - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return declaredType; - } - return methodAnnos.getPostconditionsForExpression(expression, declaredType, atypeFactory); - } - - @Override - public boolean addMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno) { - - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = methodAnnos.addDeclarationAnnotation(anno); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(methodElt)); - } - return isNewAnnotation; - } - - @Override - public boolean removeMethodDeclarationAnnotation(ExecutableElement elt, AnnotationMirror anno) { - CallableDeclarationAnnos methodAnnos = getMethodAnnos(elt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - return methodAnnos.removeDeclarationAnnotation(anno); - } - - @Override - public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { - FieldAnnos fieldAnnos = getFieldAnnos(field); - if (fieldAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = fieldAnnos != null && fieldAnnos.addDeclarationAnnotation(anno); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(field)); - } - return isNewAnnotation; - } - - @Override - public boolean addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); - if (methodAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = - methodAnnos.addDeclarationAnnotationToFormalParameter(anno, index_1based); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(methodElt)); - } - return isNewAnnotation; - } - - @Override - public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { - String className = ElementUtils.getBinaryName(classElt); - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); - if (classAnnos == null) { - // See the comment on the similar exception in #getParameterAnnotations, above. - return false; - } - boolean isNewAnnotation = classAnnos.addAnnotationToClassDeclaration(anno); - if (isNewAnnotation) { - modifiedFiles.add(getFileForElement(classElt)); - } - return isNewAnnotation; - } - - @Override - public AnnotatedTypeMirror atmFromStorageLocation( - TypeMirror typeMirror, AnnotatedTypeMirror storageLocation) { - if (typeMirror.getKind() == TypeKind.TYPEVAR) { - // Only copy the primary annotation, because we don't currently have - // support for inferring type bounds. This avoids accidentally substituting the - // use of the type variable for its declaration when inferring annotations on - // fields with a type variable as their type. - AnnotatedTypeMirror asExpectedType = - AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); - asExpectedType.replaceAnnotations(storageLocation.getAnnotations()); - return asExpectedType; - } else { - return storageLocation; - } - } - - @Override - public void updateStorageLocationFromAtm( - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - AnnotatedTypeMirror typeToUpdate, - TypeUseLocation defLoc, - boolean ignoreIfAnnotated) { - // Only update the AnnotatedTypeMirror if there are no explicit annotations - if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { - for (AnnotationMirror am : newATM.getAnnotations()) { - typeToUpdate.replaceAnnotation(am); - } - } else if (curATM.getKind() == TypeKind.TYPEVAR) { - // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly - // annotated. So instead, only insert the annotation if there is not primary annotation - // of the same hierarchy. - for (AnnotationMirror am : newATM.getAnnotations()) { - if (curATM.getAnnotationInHierarchy(am) != null) { - // Don't insert if the type is already has a primary annotation - // in the same hierarchy. - break; - } - typeToUpdate.replaceAnnotation(am); - } - } - - // Need to check both newATM and curATM, because one might be a declared type - // even if the other is an array: it is permitted to assign e.g., a String[] - // to a location with static type Object **and vice-versa** (if a cast is used). - if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; - AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; - AnnotatedArrayType aatToUpdate = (AnnotatedArrayType) typeToUpdate; - updateStorageLocationFromAtm( - newAAT.getComponentType(), - oldAAT.getComponentType(), - aatToUpdate.getComponentType(), - defLoc, - ignoreIfAnnotated); - } - } - - /// - /// Reading in files - /// - - @Override - public void preprocessClassTree(ClassTree classTree) { - addClassTree(classTree); - } - - /** - * Reads in the source file containing {@code tree} and creates wrappers around all classes in the - * file. Stores the wrapper for the compilation unit in {@link #sourceToAnnos} and stores the - * wrappers of all classes in the file in {@link #classToAnnos}. - * - * @param tree tree for class to add - */ - private void addClassTree(ClassTree tree) { - TypeElement element = TreeUtils.elementFromDeclaration(tree); - if (element == null) { - // TODO: There should be an element here, or there is nowhere to store inferences about - // `tree`. - return; - } - String className = ElementUtils.getBinaryName(element); - if (classToAnnos.containsKey(className)) { - return; - } - - TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); - String path = ElementUtils.getSourceFilePath(toplevelClass); - addSourceFile(path); - CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); - TypeDeclaration javaParserNode = - sourceAnnos.getClassOrInterfaceDeclarationByName(toplevelClass.getSimpleName().toString()); - ClassTree toplevelClassTree = atypeFactory.getTreeUtils().getTree(toplevelClass); - createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); - } - - /** - * Reads in the file at {@code path} and creates a wrapper around its compilation unit. Stores the - * wrapper in {@link #sourceToAnnos}, but doesn't create wrappers around any classes in the file. - * - * @param path path to source file to read - */ - private void addSourceFile(String path) { - if (sourceToAnnos.containsKey(path)) { - return; - } - - CompilationUnit root; - try { - root = JavaParserUtil.parseCompilationUnit(new File(path)); - } catch (FileNotFoundException e) { - throw new BugInCF("Failed to read Java file " + path, e); - } - JavaParserUtil.concatenateAddedStringLiterals(root); - CompilationUnitAnnos sourceAnnos = new CompilationUnitAnnos(root); - sourceToAnnos.put(path, sourceAnnos); - - Optional oPackageDecl = root.getPackageDeclaration(); - String prefix = oPackageDecl.isPresent() ? oPackageDecl.get().getName().asString() + "." : ""; - List<@BinaryName String> typeNames = new ArrayList<>(); - for (TypeDeclaration type : root.getTypes()) { - addDeclaredTypes(type, prefix, typeNames); - } - for (String typeName : typeNames) { - classToSource.put(typeName, path); - } - } - - /** - * Computes the binary names of a type and all nested types. - * - * @param td a type declaration - * @param prefix the package, or package+outerclass, prefix in a binary name - * @param result a list to which to add the binary names of all classes defined in the compilation - * unit - */ - private static void addDeclaredTypes( - TypeDeclaration td, String prefix, List<@BinaryName String> result) { - @SuppressWarnings("signature:assignment") // string concatenation - @BinaryName String typeName = prefix + td.getName().asString(); - result.add(typeName); - for (BodyDeclaration member : td.getMembers()) { - if (member.isTypeDeclaration()) { - addDeclaredTypes(member.asTypeDeclaration(), typeName + "$", result); - } - } - } - - /** - * The first two arguments are a javac tree and a JavaParser node representing the same class. - * This method creates wrappers around all the classes, fields, and methods in that class, and - * stores those wrappers in {@code sourceAnnos}. - * - * @param javacClass javac tree for class - * @param javaParserClass a JavaParser node corresponding to the same class as {@code javacClass} - * @param sourceAnnos compilation unit wrapper to add new wrappers to - */ - private void createWrappersForClass( - ClassTree javacClass, TypeDeclaration javaParserClass, CompilationUnitAnnos sourceAnnos) { - JointJavacJavaParserVisitor visitor = - new DefaultJointVisitor() { - - /** - * The number of inner classes encountered, for use in computing their names as keys to - * various maps. This is an estimate only: an error might lead to inaccurate annotations - * being emitted, but that is ok: WPI should never be run without running the checker - * again afterwards to check the results. This field is only used when no element for the - * inner class is available, such as when it comes from another compilation unit. - */ - private int innerClassCount = 0; - - @Override - public void processClass( - ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { - addClass(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { - addClass(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) { - addClass(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) { - // TODO: consider supporting inferring annotations on annotation - // declarations. - // addClass(javacTree, javaParserNode); - } - - @Override - public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) { - ClassTree body = javacTree.getClassBody(); - if (body != null) { - addClass(body, null); - } - } - - /** - * Creates a wrapper around the class for {@code tree} and stores it in {@code - * sourceAnnos}. - * - *

          This method computes the name of the class when the element corresponding to tree is - * null and uses it as the key for {@code classToAnnos} - * - * @param tree tree to add. Its corresponding name is used as the key for {@code - * classToAnnos}. - * @param javaParserNode the node corresponding to the declaration, which is used to place - * annotations on the class itself. Can be null, e.g. for an anonymous class. - */ - private void addClass(ClassTree tree, @Nullable TypeDeclaration javaParserNode) { - String className; - TypeElement classElt = TreeUtils.elementFromDeclaration(tree); - if (classElt == null) { - // If such an element does not exist, compute the name of the class - // instead. This method of computing the name is not 100% guaranteed to - // be reliable, but it should be sufficient for WPI's purposes here: if - // the wrong name is computed, the worst outcome is a false positive - // because WPI inferred an untrue annotation. - Optional ofqn = javaParserClass.getFullyQualifiedName(); - if (!ofqn.isPresent()) { - throw new BugInCF("Missing getFullyQualifiedName() for " + javaParserClass); - } - if ("".contentEquals(tree.getSimpleName())) { - @SuppressWarnings("signature:assignment" // computed from string concatenation - ) - @BinaryName String computedName = ofqn.get() + "$" + ++innerClassCount; - className = computedName; - } else { - @SuppressWarnings("signature:assignment" // computed from string concatenation - ) - @BinaryName String computedName = ofqn.get() + "$" + tree.getSimpleName().toString(); - className = computedName; - } - } else { - className = ElementUtils.getBinaryName(classElt); - for (TypeElement supertypeElement : ElementUtils.getSuperTypes(classElt, elements)) { - String supertypeName = ElementUtils.getBinaryName(supertypeElement); - Set<@BinaryName String> supertypeSet = - supertypesMap.computeIfAbsent(className, k -> new TreeSet<>()); - supertypeSet.add(supertypeName); - Set<@BinaryName String> subtypeSet = - subtypesMap.computeIfAbsent(supertypeName, k -> new TreeSet<>()); - subtypeSet.add(className); - } - } - - ClassOrInterfaceAnnos typeWrapper = - new ClassOrInterfaceAnnos(className, javaParserNode); - if (!classToAnnos.containsKey(className)) { - classToAnnos.put(className, typeWrapper); - } - - sourceAnnos.types.add(typeWrapper); - } - - @Override - public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) { - addCallableDeclaration(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) { - addCallableDeclaration(javacTree, javaParserNode); - } - - /** - * Creates a wrapper around {@code javacTree} with the corresponding declaration {@code - * javaParserNode} and stores it in {@code sourceAnnos}. - * - * @param javacTree javac tree for declaration to add - * @param javaParserNode a JavaParser node for the same class as {@code javacTree} - */ - private void addCallableDeclaration( - MethodTree javacTree, CallableDeclaration javaParserNode) { - ExecutableElement element = TreeUtils.elementFromDeclaration(javacTree); - if (element == null) { - // element can be null if there is no element corresponding to the - // method, which happens for certain kinds of anonymous classes, - // such as Ordering$1 in PolyCollectorTypeVar.java in the - // all-systems test suite. - return; - } - String className = ElementUtils.getEnclosingClassName(element); - ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(className); - String executableSignature = JVMNames.getJVMMethodSignature(javacTree); - if (!enclosingClass.callableDeclarations.containsKey(executableSignature)) { - enclosingClass.callableDeclarations.put( - executableSignature, new CallableDeclarationAnnos(javaParserNode)); - } - } - - @Override - public void processVariable( - VariableTree javacTree, EnumConstantDeclaration javaParserNode) { - VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); - if (!elt.getKind().isField()) { - throw new BugInCF(elt + " is not a field but a " + elt.getKind()); - } - - String enclosingClassName = ElementUtils.getEnclosingClassName(elt); - ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); - String fieldName = javacTree.getName().toString(); - enclosingClass.enumConstants.add(fieldName); - - // Ensure that if an enum constant defines a class, that class gets - // registered properly. See e.g. - // https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.9.1 - // for the specification of an enum constant, which does permit it to - // define an anonymous class. - NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); - ClassTree constructorClassBody = constructor.getClassBody(); - if (constructorClassBody != null) { - // addClass assumes there is an element for its argument, but that is - // not always true! - if (TreeUtils.elementFromDeclaration(constructorClassBody) != null) { - addClass(constructorClassBody, null); - } - } - } - - @Override - public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) { - VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); - if (!elt.getKind().isField()) { - return; - } - - String enclosingClassName = ElementUtils.getEnclosingClassName(elt); - ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); - String fieldName = javacTree.getName().toString(); - if (!enclosingClass.fields.containsKey(fieldName)) { - enclosingClass.fields.put(fieldName, new FieldAnnos(javaParserNode)); - } - } - }; - visitor.visitClass(javacClass, javaParserClass); - } - - /** - * Calls {@link #addSourceFile(String)} for the file containing the given element. - * - * @param element the element for the source file to add - * @return path of the file containing {@code element} - */ - private String addClassesForElement(Element element) { - if (!ElementUtils.isElementFromSourceCode(element)) { - throw new BugInCF("Called addClassesForElement for non-source element: " + element); - } - - TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); - String path = ElementUtils.getSourceFilePath(toplevelClass); - if (toplevelClass.getKind() == ElementKind.ANNOTATION_TYPE) { - // Inferring annotations on elements of annotation declarations is not supported. - // One issue with supporting inference on annotation declaration elements is that - // AnnotatedTypeFactory#declarationFromElement returns null for annotation declarations - // quite commonly (because Trees#getTree, which it delegates to, does as well). - // In this case, we return path here without actually attempting to create the wrappers - // for the annotation declaration. The rest of WholeProgramInferenceJavaParserStorage - // already needs to handle classes without entries in the various tables (because of the - // possibility of classes outside the current compilation unit), so this is safe. - return path; - } - if (classToAnnos.containsKey(ElementUtils.getBinaryName(toplevelClass))) { - return path; - } + implements WholeProgramInferenceStorage { - addSourceFile(path); - CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); - ClassTree toplevelClassTree = (ClassTree) atypeFactory.declarationFromElement(toplevelClass); - TypeDeclaration javaParserNode = - sourceAnnos.getClassOrInterfaceDeclarationByName(toplevelClass.getSimpleName().toString()); - createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); - return path; - } - - /// - /// Writing to a file - /// - - // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because - // a scene may be modifed and written at any time, including before or after - // postProcessClassTree is called. - - /** - * Side-effects the compilation unit annotations to make any desired changes before writing to a - * file. - * - * @param compilationUnitAnnos the compilation unit annotations to modify - */ - public void wpiPrepareCompilationUnitForWriting(CompilationUnitAnnos compilationUnitAnnos) { - for (ClassOrInterfaceAnnos type : compilationUnitAnnos.types) { - wpiPrepareClassForWriting( - type, supertypesMap.get(type.className), subtypesMap.get(type.className)); - } - } - - /** - * Side-effects the class annotations to make any desired changes before writing to a file. - * - *

          Because of the side effect, clients may want to pass a copy into this method. - * - * @param classAnnos the class annotations to modify - * @param supertypes the binary names of all supertypes; not side-effected - * @param subtypes the binary names of all subtypes; not side-effected - */ - public void wpiPrepareClassForWriting( - ClassOrInterfaceAnnos classAnnos, - Collection<@BinaryName String> supertypes, - Collection<@BinaryName String> subtypes) { - if (classAnnos.callableDeclarations.isEmpty()) { - return; - } + /** + * Directory where .ajava files will be written to and read from. This directory is relative to + * where the javac command is executed. + */ + public static final File AJAVA_FILES_PATH = new File("build", "whole-program-inference"); - for (Map.Entry methodEntry : - classAnnos.callableDeclarations.entrySet()) { - String jvmSignature = methodEntry.getKey(); - List inSupertypes = - findOverrides(jvmSignature, supertypesMap.get(classAnnos.className)); - List inSubtypes = - findOverrides(jvmSignature, subtypesMap.get(classAnnos.className)); + /** The type factory associated with this. */ + protected final AnnotatedTypeFactory atypeFactory; - wpiPrepareMethodForWriting(methodEntry.getValue(), inSupertypes, inSubtypes); - } - } - - /** - * Return all the CallableDeclarationAnnos for the given signature. - * - * @param jvmSignature the JVM signature - * @param typeNames a collection of type names - * @return the CallableDeclarationAnnos for the given signature, in all of the types - */ - private List findOverrides( - String jvmSignature, @Nullable Collection<@BinaryName String> typeNames) { - if (typeNames == null) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); - for (String typeName : typeNames) { - ClassOrInterfaceAnnos classAnnos = classToAnnos.get(typeName); - if (classAnnos != null) { - CallableDeclarationAnnos callableAnnos = classAnnos.callableDeclarations.get(jvmSignature); - if (callableAnnos != null) { - result.add(callableAnnos); - } - } - } - return result; - } - - /** - * Side-effects the method or constructor annotations to make any desired changes before writing - * to a file. For example, this method may make inferred annotations consistent with one another - * between superclasses and subclasses. - * - * @param methodAnnos the method or constructor annotations to modify - * @param inSupertypes the method or constructor annotations for all overridden methods; not - * side-effected - * @param inSubtypes the method or constructor annotations for all overriding methods; not - * side-effected - */ - // TODO: Inferred annotations must be consistent both with one another and with - // programmer-written annotations. The latter are stored in elements and, with the given formal - // parameter list, are not accessible to this method. In the future, the annotations stored in - // elements should also be passed to this method (or maybe they are already available to the - // type factory?). I'm leaving that enhancement until later. - public void wpiPrepareMethodForWriting( - CallableDeclarationAnnos methodAnnos, - Collection inSupertypes, - Collection inSubtypes) { - atypeFactory.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); - } - - @Override - public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { - if (outputFormat != OutputFormat.AJAVA) { - throw new BugInCF("WholeProgramInferenceJavaParser used with output format " + outputFormat); - } + /** The element utilities for {@code atypeFactory}. */ + protected final Elements elements; - File outputDir = AJAVA_FILES_PATH; - if (!outputDir.exists()) { - outputDir.mkdirs(); - } + /** + * Maps from binary class name to the wrapper containing the class. Contains all classes in Java + * source files containing an Element for which an annotation has been inferred. + */ + private Map<@BinaryName String, ClassOrInterfaceAnnos> classToAnnos = new HashMap<>(); - setSupertypesAndSubtypesModified(); - - for (String path : modifiedFiles) { - // This calls deepCopy() because wpiPrepareCompilationUnitForWriting performs side - // effects that we don't want to be persistent. - CompilationUnitAnnos root = sourceToAnnos.get(path).deepCopy(); - wpiPrepareCompilationUnitForWriting(root); - File packageDir; - if (!root.compilationUnit.getPackageDeclaration().isPresent()) { - packageDir = AJAVA_FILES_PATH; - } else { - packageDir = - new File( - AJAVA_FILES_PATH, - root.compilationUnit - .getPackageDeclaration() - .get() - .getNameAsString() - .replaceAll("\\.", File.separator)); - } - - if (!packageDir.exists()) { - packageDir.mkdirs(); - } - - String name = new File(path).getName(); - if (name.endsWith(".java")) { - name = name.substring(0, name.length() - ".java".length()); - } - - String nameWithChecker = name + "-" + checker.getClass().getCanonicalName() + ".ajava"; - File outputPath = new File(packageDir, nameWithChecker); - if (this.inferOutputOriginal) { - File outputPathNoCheckerName = new File(packageDir, name + ".ajava"); - // Avoid re-writing this file for each checker that was run. - if (Files.notExists(outputPathNoCheckerName.toPath())) { - writeAjavaFile(outputPathNoCheckerName, root); - } - } - root.transferAnnotations(checker); - writeAjavaFile(outputPath, root); - } + /** Maps from binary class name to binary names of all supertypes. */ + private Map<@BinaryName String, Set<@BinaryName String>> supertypesMap = new HashMap<>(); - modifiedFiles.clear(); - } - - /** - * Write an ajava file to disk. - * - * @param outputPath the path to which the ajava file should be written - * @param root the compilation unit to be written - */ - private void writeAjavaFile(File outputPath, CompilationUnitAnnos root) { - try (Writer writer = Files.newBufferedWriter(outputPath.toPath(), StandardCharsets.UTF_8)) { - // This implementation uses JavaParser's lexical preserving printing, which writes the - // file such that its formatting is close to the original source file it was parsed from - // as possible. It is commented out because, this feature is very buggy and crashes when - // adding annotations in certain locations. - // LexicalPreservingPrinter.print(root.declaration, writer); - - // Do not print invisible qualifiers, to avoid cluttering the output. - Set invisibleQualifierNames = getInvisibleQualifierNames(this.atypeFactory); - DefaultPrettyPrinter prettyPrinter = - new DefaultPrettyPrinter() { - @Override - public String print(Node node) { - VoidVisitor visitor = - new DefaultPrettyPrinterVisitor(getConfiguration()) { - @Override - public void visit(MarkerAnnotationExpr n, Void arg) { - if (invisibleQualifierNames.contains(n.getName().toString())) { - return; - } - super.visit(n, arg); - } + /** Maps from binary class name to binary names of all known subtypes. */ + private Map<@BinaryName String, Set<@BinaryName String>> subtypesMap = new HashMap<>(); - @Override - public void visit(SingleMemberAnnotationExpr n, Void arg) { - if (invisibleQualifierNames.contains(n.getName().toString())) { - return; - } - super.visit(n, arg); - } - - @Override - public void visit(NormalAnnotationExpr n, Void arg) { - if (invisibleQualifierNames.contains(n.getName().toString())) { - return; - } - super.visit(n, arg); - } - }; - node.accept(visitor, null); - return visitor.toString(); - } - }; + /** + * Files containing classes for which an annotation has been inferred since the last time files + * were written to disk. + */ + private Set modifiedFiles = new HashSet<>(); - writer.write(prettyPrinter.print(root.compilationUnit)); - } catch (IOException e) { - throw new BugInCF("Error while writing ajava file " + outputPath, e); - } - } - - /** - * Adds an explicit receiver type to a JavaParser method declaration. - * - * @param methodDeclaration declaration to add a receiver to - */ - private static void addExplicitReceiver(MethodDeclaration methodDeclaration) { - if (methodDeclaration.getReceiverParameter().isPresent()) { - return; - } + /** Mapping from source file to the wrapper for the compilation unit parsed from that file. */ + private Map sourceToAnnos = new HashMap<>(); - com.github.javaparser.ast.Node parent = methodDeclaration.getParentNode().get(); - if (!(parent instanceof TypeDeclaration)) { - return; - } + /** Maps from binary class name to the source file that contains it. */ + private Map classToSource = new HashMap<>(); - TypeDeclaration parentDecl = (TypeDeclaration) parent; - ClassOrInterfaceType receiver = new ClassOrInterfaceType(); - receiver.setName(parentDecl.getName()); - if (parentDecl.isClassOrInterfaceDeclaration()) { - ClassOrInterfaceDeclaration parentClassDecl = parentDecl.asClassOrInterfaceDeclaration(); - if (!parentClassDecl.getTypeParameters().isEmpty()) { - NodeList typeArgs = new NodeList<>(); - for (TypeParameter typeParam : parentClassDecl.getTypeParameters()) { - ClassOrInterfaceType typeArg = new ClassOrInterfaceType(); - typeArg.setName(typeParam.getNameAsString()); - typeArgs.add(typeArg); - } - - receiver.setTypeArguments(typeArgs); - } - } + /** Whether the {@code -AinferOutputOriginal} option was supplied to the checker. */ + private final boolean inferOutputOriginal; - methodDeclaration.setReceiverParameter(new ReceiverParameter(receiver, "this")); - } - - /** - * Transfers all annotations for {@code annotatedType} and its nested types to {@code target}, - * which is the JavaParser node representing the same type. Does nothing if {@code annotatedType} - * is null (this may occur if there are no inferred annotations for the type). - * - * @param annotatedType type to transfer annotations from - * @param target the JavaParser type to transfer annotation to; must represent the same type as - * {@code annotatedType} - */ - private static void transferAnnotations( - @Nullable AnnotatedTypeMirror annotatedType, Type target) { - if (annotatedType == null) { - return; + /** + * Returns the names of all qualifiers that are marked with {@link InvisibleQualifier}, and that + * are supported by the given type factory. + * + * @param atypeFactory a type factory + * @return the names of every invisible qualifier supported by {@code atypeFactory} + */ + public static Set getInvisibleQualifierNames(AnnotatedTypeFactory atypeFactory) { + return atypeFactory.getSupportedTypeQualifiers().stream() + .filter(WholeProgramInferenceJavaParserStorage::isInvisible) + .map(Class::getCanonicalName) + .collect(Collectors.toSet()); } - target.accept(new AnnotationTransferVisitor(), annotatedType); - } - - /// - /// Storing annotations - /// - - /** - * Stores the JavaParser node for a compilation unit and the list of wrappers for the classes and - * interfaces in that compilation unit. - */ - private static class CompilationUnitAnnos implements DeepCopyable { - /** Compilation unit being wrapped. */ - public final CompilationUnit compilationUnit; - - /** Wrappers for classes and interfaces in {@code compilationUnit}. */ - public final List types; - /** - * Constructs a wrapper around the given compilation unit. + * Is the definition of the given annotation class annotated with {@link InvisibleQualifier}? * - * @param compilationUnit compilation unit to wrap + * @param qual an annotation class + * @return true iff {@code qual} is meta-annotated with {@link InvisibleQualifier} */ - public CompilationUnitAnnos(CompilationUnit compilationUnit) { - this.compilationUnit = compilationUnit; - this.types = new ArrayList<>(); + @Pure + public static boolean isInvisible(Class qual) { + return Arrays.stream(qual.getAnnotations()) + .anyMatch(anno -> anno.annotationType() == InvisibleQualifier.class); } /** - * Private constructor for use by deepCopy(). + * Constructs a new {@code WholeProgramInferenceJavaParser} that has not yet inferred any + * annotations. * - * @param compilationUnit compilation unit to wrap - * @param types wrappers for classes and interfaces in {@code compilationUnit} + * @param atypeFactory the associated type factory + * @param inferOutputOriginal whether the -AinferOutputOriginal option was supplied to the + * checker */ - private CompilationUnitAnnos( - CompilationUnit compilationUnit, List types) { - this.compilationUnit = compilationUnit; - this.types = types; + public WholeProgramInferenceJavaParserStorage( + AnnotatedTypeFactory atypeFactory, boolean inferOutputOriginal) { + this.atypeFactory = atypeFactory; + this.elements = atypeFactory.getElementUtils(); + this.inferOutputOriginal = inferOutputOriginal; } @Override - public CompilationUnitAnnos deepCopy() { - return new CompilationUnitAnnos(compilationUnit, CollectionsPlume.deepCopy(types)); + public String getFileForElement(Element elt) { + return addClassesForElement(elt); } - /** - * Transfers all annotations inferred by whole program inference for the wrapped compilation - * unit to their corresponding JavaParser locations. - * - * @param checker the checker who's name to include in the @AnnotatedFor annotation - */ - public void transferAnnotations(BaseTypeChecker checker) { - JavaParserUtil.clearAnnotations(compilationUnit); - for (TypeDeclaration typeDecl : compilationUnit.getTypes()) { - typeDecl.addSingleMemberAnnotation( - "org.checkerframework.framework.qual.AnnotatedFor", - "\"" + checker.getClass().getCanonicalName() + "\""); - } - - for (ClassOrInterfaceAnnos typeAnnos : types) { - typeAnnos.transferAnnotations(); - } + @Override + public void setFileModified(String path) { + modifiedFiles.add(path); } /** - * Returns the top-level type declaration named {@code name} in the compilation unit. + * Set the source file as modified, for the given class. * - * @param name name of type declaration - * @return the type declaration named {@code name} in the wrapped compilation unit + * @param className the binary name of a class that should be written to disk */ - public TypeDeclaration getClassOrInterfaceDeclarationByName(String name) { - return JavaParserUtil.getTypeDeclarationByName(compilationUnit, name); + private void setClassModified(@Nullable @BinaryName String className) { + if (className == null) { + return; + } + String path = classToSource.get(className); + if (path != null) { + setFileModified(path); + } } /** - * Returns a verbose printed representation of this. + * Set the source files as modified, for all the given classes. * - * @return a verbose printed representation of this + * @param classNames the binary names of classes that should be written to disk */ - @SuppressWarnings("UnusedMethod") - public String toStringVerbose() { - StringJoiner sb = new StringJoiner(System.lineSeparator()); - sb.add("CompilationUnitAnnos:"); - for (ClassOrInterfaceAnnos type : types) { - sb.add(type.toStringVerbose()); - } - return sb.toString(); + private void setClassesModified(@Nullable Collection<@BinaryName String> classNames) { + if (classNames == null) { + return; + } + for (String className : classNames) { + setClassModified(className); + } } - } - /** - * Stores wrappers for the locations where annotations may be inferred in a class or interface. - */ - private static class ClassOrInterfaceAnnos implements DeepCopyable { /** - * Mapping from JVM method signatures to the wrapper containing the corresponding executable. + * For every modified file, consider its subclasses and superclasses modified, too. The reason + * is that an annotation change in a class might require annotations in its superclasses and + * supclasses to be modified, in order to preserve behavioral subtyping. Setting it modified + * will cause it to be written out, and while writing out, the annotations will be made + * consistent across the class hierarchy by {@link #wpiPrepareCompilationUnitForWriting}. */ - public Map callableDeclarations = new HashMap<>(); + public void setSupertypesAndSubtypesModified() { + // Copy into a list to avoid a ConcurrentModificationException. + for (String path : new ArrayList<>(modifiedFiles)) { + CompilationUnitAnnos cuAnnos = sourceToAnnos.get(path); + for (ClassOrInterfaceAnnos classAnnos : cuAnnos.types) { + String className = classAnnos.className; + setClassesModified(supertypesMap.get(className)); + setClassesModified(subtypesMap.get(className)); + } + } + } - /** Mapping from field names to wrappers for those fields. */ - public Map fields = new HashMap<>(2); + /// + /// Reading stored annotations + /// - /** Collection of declared enum constants (empty if not an enum). */ - public Set enumConstants = new HashSet<>(2); + @Override + public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { + return getMethodAnnos(methodElt) != null; + } - /** - * Annotations on the declaration of the class (note that despite the name, these can also be - * type annotations). - */ - private @MonotonicNonNull AnnotationMirrorSet classAnnotations = null; + @Override + public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement methodElt) { + String className = ElementUtils.getEnclosingClassName(methodElt); + // Read in classes for the element. + getFileForElement(methodElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return AnnotationMirrorSet.emptySet(); + } + CallableDeclarationAnnos methodAnnos = + classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); + if (methodAnnos == null) { + return AnnotationMirrorSet.emptySet(); + } + return methodAnnos.getDeclarationAnnotations(); + } /** - * The JavaParser TypeDeclaration representing the class's declaration. Used for placing - * annotations inferred on the class declaration itself. + * Get the annotations for a method or constructor. + * + * @param methodElt the method or constructor + * @return the annotations for a method or constructor */ - private @MonotonicNonNull TypeDeclaration classDeclaration; - - /** The binary name of the class. */ - private @BinaryName String className; + private @Nullable CallableDeclarationAnnos getMethodAnnos(ExecutableElement methodElt) { + String className = ElementUtils.getEnclosingClassName(methodElt); + // Read in classes for the element. + getFileForElement(methodElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return null; + } + CallableDeclarationAnnos methodAnnos = + classAnnos.callableDeclarations.get(JVMNames.getJVMMethodSignature(methodElt)); + return methodAnnos; + } /** - * Create a new ClassOrInterfaceAnnos. + * Get the annotations for a field. * - * @param className the binary name of the class - * @param javaParserNode the JavaParser node corresponding to the class declaration, which is - * used for placing annotations on the class declaration + * @param fieldElt a field + * @return the annotations for a field */ - public ClassOrInterfaceAnnos( - @BinaryName String className, @Nullable TypeDeclaration javaParserNode) { - this.classDeclaration = javaParserNode; - this.className = className; + private @Nullable FieldAnnos getFieldAnnos(VariableElement fieldElt) { + String className = ElementUtils.getEnclosingClassName(fieldElt); + // Read in classes for the element. + getFileForElement(fieldElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return null; + } + FieldAnnos fieldAnnos = classAnnos.fields.get(fieldElt.getSimpleName().toString()); + return fieldAnnos; } @Override - public ClassOrInterfaceAnnos deepCopy() { - ClassOrInterfaceAnnos result = new ClassOrInterfaceAnnos(className, classDeclaration); - result.callableDeclarations = CollectionsPlume.deepCopyValues(callableDeclarations); - result.fields = CollectionsPlume.deepCopyValues(fields); - result.enumConstants = UtilPlume.clone(enumConstants); // no deep copy: elements are strings - if (classAnnotations != null) { - result.classAnnotations = classAnnotations.deepCopy(); - } - // no need to change classDeclaration - return result; + public AnnotatedTypeMirror getParameterAnnotations( + ExecutableElement methodElt, + @Positive int index_1based, + AnnotatedTypeMirror paramATM, + VariableElement ve, + AnnotatedTypeFactory atypeFactory) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // When processing anonymous inner classes outside their compilation units, + // it might not have been possible to create an appropriate CallableDeclarationAnnos: + // no element would have been available, causing the computed method signature to + // be incorrect. In this case, abort looking up annotations -- inference will fail, + // because even if WPI inferred something, it couldn't be printed. + return paramATM; + } + return methodAnnos.getParameterTypeInitialized(paramATM, index_1based, atypeFactory); } - /** - * Adds {@code annotation} to the set of annotations on the declaration of this class. - * - * @param annotation an annotation (can be declaration or type) - * @return true if this is a new annotation for this class - */ - public boolean addAnnotationToClassDeclaration(AnnotationMirror annotation) { - if (classAnnotations == null) { - classAnnotations = new AnnotationMirrorSet(); - } + @Override + public AnnotatedTypeMirror getReceiverAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror paramATM, + AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return paramATM; + } + return methodAnnos.getReceiverType(paramATM, atypeFactory); + } - return classAnnotations.add(annotation); + @Override + public AnnotatedTypeMirror getReturnAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror atm, + AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return atm; + } + return methodAnnos.getReturnType(atm, atypeFactory); } - /** - * Transfers all annotations inferred by whole program inference for the methods and fields in - * the wrapper class or interface to their corresponding JavaParser locations. - */ - public void transferAnnotations() { - for (CallableDeclarationAnnos callableAnnos : callableDeclarations.values()) { - callableAnnos.transferAnnotations(); - } - - if (classAnnotations != null && classDeclaration != null) { - for (AnnotationMirror annotation : classAnnotations) { - classDeclaration.addAnnotation( - AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( - annotation)); - } - } - - for (FieldAnnos field : fields.values()) { - field.transferAnnotations(); - } + @Override + public @Nullable AnnotatedTypeMirror getFieldAnnotations( + Element element, + String fieldName, + AnnotatedTypeMirror lhsATM, + AnnotatedTypeFactory atypeFactory) { + ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); + // Read in classes for the element. + getFileForElement(element); + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @BinaryName String className = enclosingClass.flatname.toString(); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + return null; + } + // If it's an enum constant it won't appear as a field + // and it won't have extra annotations, so just return the basic type: + if (classAnnos.enumConstants.contains(fieldName)) { + return lhsATM; + } else if (classAnnos.fields.get(fieldName) == null) { + // There might not be a corresponding entry for the field name + // in an anonymous class, if the field and class were defined in + // another compilation unit (for the same reason that a method + // might not have an entry, as in #getParameterAnnotations, above). + return null; + } else { + return classAnnos.fields.get(fieldName).getType(lhsATM, atypeFactory); + } } @Override - public String toString() { - String fieldsString = fields.toString(); - if (fieldsString.length() > 100) { - // The quoting increases the likelihood that all delimiters are balanced in the - // result. That makes it easier to manipulate the result (such as skipping over it) - // in an editor. The quoting also makes clear that the value is truncated. - fieldsString = "\"" + fieldsString.substring(0, 95) + "...\""; - } - - return "ClassOrInterfaceAnnos [" - + (classDeclaration == null ? "unnamed" : classDeclaration.getName()) - + ": callableDeclarations=" - // For deterministic output - + new TreeMap<>(callableDeclarations) - + ", fields=" - + fieldsString - + "]"; + public AnnotatedTypeMirror getPreOrPostconditions( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + switch (preOrPost) { + case BEFORE: + return getPreconditionsForExpression( + methodElement, expression, declaredType, atypeFactory); + case AFTER: + return getPostconditionsForExpression( + methodElement, expression, declaredType, atypeFactory); + default: + throw new BugInCF("Unexpected " + preOrPost); + } } /** - * Returns a verbose printed representation of this. + * Returns the precondition annotations for the given expression. * - * @return a verbose printed representation of this + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @param atypeFactory the type factory + * @return the precondition annotations for a field */ - public String toStringVerbose() { - return toString(); + private AnnotatedTypeMirror getPreconditionsForExpression( + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return declaredType; + } + return methodAnnos.getPreconditionsForExpression(expression, declaredType, atypeFactory); } - } - - /** - * Stores the JavaParser node for a method or constructor and the annotations that have been - * inferred about its parameters and return type. - */ - public class CallableDeclarationAnnos implements DeepCopyable { - /** Wrapped method or constructor declaration. */ - public final CallableDeclaration declaration; /** - * Inferred annotations for the return type, if the declaration represents a method. Initialized - * on first usage. + * Returns the postcondition annotations for an expression. + * + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @param atypeFactory the type factory + * @return the postcondition annotations for a field */ - private @MonotonicNonNull AnnotatedTypeMirror returnType = null; + private AnnotatedTypeMirror getPostconditionsForExpression( + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElement); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return declaredType; + } + return methodAnnos.getPostconditionsForExpression(expression, declaredType, atypeFactory); + } - /** - * Inferred annotations for the receiver type, if the declaration represents a method. - * Initialized on first usage. - */ - private @MonotonicNonNull AnnotatedTypeMirror receiverType = null; + @Override + public boolean addMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno) { - /** - * Inferred annotations for parameter types. The list is initialized the first time any - * parameter is accessed, and each parameter is initialized the first time it's accessed. - */ - private @MonotonicNonNull List<@Nullable AnnotatedTypeMirror> parameterTypes = null; + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = methodAnnos.addDeclarationAnnotation(anno); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(methodElt)); + } + return isNewAnnotation; + } - /** Declaration annotations on the parameters. */ - private @MonotonicNonNull Set> paramsDeclAnnos = null; + @Override + public boolean removeMethodDeclarationAnnotation(ExecutableElement elt, AnnotationMirror anno) { + CallableDeclarationAnnos methodAnnos = getMethodAnnos(elt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + return methodAnnos.removeDeclarationAnnotation(anno); + } - /** - * Annotations on the callable declaration. This does not include preconditions and - * postconditions. - */ - private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; + @Override + public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { + FieldAnnos fieldAnnos = getFieldAnnos(field); + if (fieldAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = fieldAnnos != null && fieldAnnos.addDeclarationAnnotation(anno); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(field)); + } + return isNewAnnotation; + } - /** - * Mapping from expression strings to pairs of (inferred precondition, declared type). The keys - * are strings representing JavaExpressions, using the same format as a user would in an {@link - * org.checkerframework.framework.qual.RequiresQualifier} annotation. - */ - private @MonotonicNonNull Map> - preconditions = null; + @Override + public boolean addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + CallableDeclarationAnnos methodAnnos = getMethodAnnos(methodElt); + if (methodAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = + methodAnnos.addDeclarationAnnotationToFormalParameter(anno, index_1based); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(methodElt)); + } + return isNewAnnotation; + } - /** - * Mapping from expression strings to pairs of (inferred postcondition, declared type). The - * okeys are strings representing JavaExpressions, using the same format as a user would in an - * {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation. - */ - private @MonotonicNonNull Map> - postconditions = null; + @Override + public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { + String className = ElementUtils.getBinaryName(classElt); + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(className); + if (classAnnos == null) { + // See the comment on the similar exception in #getParameterAnnotations, above. + return false; + } + boolean isNewAnnotation = classAnnos.addAnnotationToClassDeclaration(anno); + if (isNewAnnotation) { + modifiedFiles.add(getFileForElement(classElt)); + } + return isNewAnnotation; + } - /** - * Creates a wrapper for the given method or constructor declaration. - * - * @param declaration method or constructor declaration to wrap - */ - public CallableDeclarationAnnos(CallableDeclaration declaration) { - this.declaration = declaration; + @Override + public AnnotatedTypeMirror atmFromStorageLocation( + TypeMirror typeMirror, AnnotatedTypeMirror storageLocation) { + if (typeMirror.getKind() == TypeKind.TYPEVAR) { + // Only copy the primary annotation, because we don't currently have + // support for inferring type bounds. This avoids accidentally substituting the + // use of the type variable for its declaration when inferring annotations on + // fields with a type variable as their type. + AnnotatedTypeMirror asExpectedType = + AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); + asExpectedType.replaceAnnotations(storageLocation.getAnnotations()); + return asExpectedType; + } else { + return storageLocation; + } + } + + @Override + public void updateStorageLocationFromAtm( + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + AnnotatedTypeMirror typeToUpdate, + TypeUseLocation defLoc, + boolean ignoreIfAnnotated) { + // Only update the AnnotatedTypeMirror if there are no explicit annotations + if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { + for (AnnotationMirror am : newATM.getAnnotations()) { + typeToUpdate.replaceAnnotation(am); + } + } else if (curATM.getKind() == TypeKind.TYPEVAR) { + // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly + // annotated. So instead, only insert the annotation if there is not primary annotation + // of the same hierarchy. + for (AnnotationMirror am : newATM.getAnnotations()) { + if (curATM.getAnnotationInHierarchy(am) != null) { + // Don't insert if the type is already has a primary annotation + // in the same hierarchy. + break; + } + typeToUpdate.replaceAnnotation(am); + } + } + + // Need to check both newATM and curATM, because one might be a declared type + // even if the other is an array: it is permitted to assign e.g., a String[] + // to a location with static type Object **and vice-versa** (if a cast is used). + if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; + AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; + AnnotatedArrayType aatToUpdate = (AnnotatedArrayType) typeToUpdate; + updateStorageLocationFromAtm( + newAAT.getComponentType(), + oldAAT.getComponentType(), + aatToUpdate.getComponentType(), + defLoc, + ignoreIfAnnotated); + } } + /// + /// Reading in files + /// + @Override - public CallableDeclarationAnnos deepCopy() { - CallableDeclarationAnnos result = new CallableDeclarationAnnos(declaration); - result.returnType = DeepCopyable.deepCopyOrNull(this.returnType); - result.receiverType = DeepCopyable.deepCopyOrNull(this.receiverType); - if (parameterTypes != null) { - result.parameterTypes = CollectionsPlume.deepCopy(this.parameterTypes); - } - result.declarationAnnotations = DeepCopyable.deepCopyOrNull(this.declarationAnnotations); - - if (this.paramsDeclAnnos != null) { - result.paramsDeclAnnos = new ArraySet<>(this.paramsDeclAnnos); - } - result.preconditions = deepCopyMapOfStringToPair(this.preconditions); - result.postconditions = deepCopyMapOfStringToPair(this.postconditions); - return result; + public void preprocessClassTree(ClassTree classTree) { + addClassTree(classTree); } /** - * Returns the inferred type for the parameter at the given index. If necessary, initializes the - * {@code AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper - * around the base type for the parameter. + * Reads in the source file containing {@code tree} and creates wrappers around all classes in + * the file. Stores the wrapper for the compilation unit in {@link #sourceToAnnos} and stores + * the wrappers of all classes in the file in {@link #classToAnnos}. * - * @param type type for the parameter at {@code index}, used for initializing the returned - * {@code AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will be - * used - * @param index_1based index of the parameter to return the inferred annotations of (1-based) - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the parameter - * at the given index + * @param tree tree for class to add */ - public AnnotatedTypeMirror getParameterTypeInitialized( - AnnotatedTypeMirror type, @Positive int index_1based, AnnotatedTypeFactory atf) { - // 0-based index - int i = index_1based - 1; - - if (parameterTypes == null) { - parameterTypes = - new ArrayList<>(Collections.nCopies(declaration.getParameters().size(), null)); - } - - if (parameterTypes.get(i) == null) { - parameterTypes.set(i, AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false)); - } + private void addClassTree(ClassTree tree) { + TypeElement element = TreeUtils.elementFromDeclaration(tree); + if (element == null) { + // TODO: There should be an element here, or there is nowhere to store inferences about + // `tree`. + return; + } + String className = ElementUtils.getBinaryName(element); + if (classToAnnos.containsKey(className)) { + return; + } - return parameterTypes.get(i); + TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); + String path = ElementUtils.getSourceFilePath(toplevelClass); + addSourceFile(path); + CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); + TypeDeclaration javaParserNode = + sourceAnnos.getClassOrInterfaceDeclarationByName( + toplevelClass.getSimpleName().toString()); + ClassTree toplevelClassTree = atypeFactory.getTreeUtils().getTree(toplevelClass); + createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); } /** - * Returns the inferred type for the parameter at the given index, or null if there's no - * parameter at the given index or there's no inferred type for that parameter. + * Reads in the file at {@code path} and creates a wrapper around its compilation unit. Stores + * the wrapper in {@link #sourceToAnnos}, but doesn't create wrappers around any classes in the + * file. * - * @param index index of the parameter to return the inferred annotations of - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the parameter - * at the given index, or null if there's no parameter at {@code index} or if there's not - * inferred annotations for that parameter + * @param path path to source file to read */ - public @Nullable AnnotatedTypeMirror getParameterType(int index) { - if (parameterTypes == null || index < 0 || index >= parameterTypes.size()) { - return null; - } + private void addSourceFile(String path) { + if (sourceToAnnos.containsKey(path)) { + return; + } - return parameterTypes.get(index); + CompilationUnit root; + try { + root = JavaParserUtil.parseCompilationUnit(new File(path)); + } catch (FileNotFoundException e) { + throw new BugInCF("Failed to read Java file " + path, e); + } + JavaParserUtil.concatenateAddedStringLiterals(root); + CompilationUnitAnnos sourceAnnos = new CompilationUnitAnnos(root); + sourceToAnnos.put(path, sourceAnnos); + + Optional oPackageDecl = root.getPackageDeclaration(); + String prefix = + oPackageDecl.isPresent() ? oPackageDecl.get().getName().asString() + "." : ""; + List<@BinaryName String> typeNames = new ArrayList<>(); + for (TypeDeclaration type : root.getTypes()) { + addDeclaredTypes(type, prefix, typeNames); + } + for (String typeName : typeNames) { + classToSource.put(typeName, path); + } } /** - * Adds a declaration annotation to this parameter and returns whether it was a new annotation. + * Computes the binary names of a type and all nested types. * - * @param annotation the declaration annotation to add - * @param index_1based index of the parameter (1-indexed) - * @return true if {@code annotation} wasn't previously stored for this parameter + * @param td a type declaration + * @param prefix the package, or package+outerclass, prefix in a binary name + * @param result a list to which to add the binary names of all classes defined in the + * compilation unit */ - public boolean addDeclarationAnnotationToFormalParameter( - AnnotationMirror annotation, @Positive int index_1based) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - if (paramsDeclAnnos == null) { - // There are usually few formal parameters. - paramsDeclAnnos = new ArraySet<>(4); - } - - return paramsDeclAnnos.add(IPair.of(index_1based, annotation)); + private static void addDeclaredTypes( + TypeDeclaration td, String prefix, List<@BinaryName String> result) { + @SuppressWarnings("signature:assignment") // string concatenation + @BinaryName String typeName = prefix + td.getName().asString(); + result.add(typeName); + for (BodyDeclaration member : td.getMembers()) { + if (member.isTypeDeclaration()) { + addDeclaredTypes(member.asTypeDeclaration(), typeName + "$", result); + } + } } /** - * If this wrapper holds a method, returns the inferred type of the receiver. If necessary, - * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code - * atf} to a wrapper around the base type for the receiver type. + * The first two arguments are a javac tree and a JavaParser node representing the same class. + * This method creates wrappers around all the classes, fields, and methods in that class, and + * stores those wrappers in {@code sourceAnnos}. * - * @param type base type for the receiver type, used for initializing the returned {@code - * AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will be - * used - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the receiver - * type + * @param javacClass javac tree for class + * @param javaParserClass a JavaParser node corresponding to the same class as {@code + * javacClass} + * @param sourceAnnos compilation unit wrapper to add new wrappers to */ - public AnnotatedTypeMirror getReceiverType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { - if (receiverType == null) { - receiverType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); - } + private void createWrappersForClass( + ClassTree javacClass, + TypeDeclaration javaParserClass, + CompilationUnitAnnos sourceAnnos) { + JointJavacJavaParserVisitor visitor = + new DefaultJointVisitor() { + + /** + * The number of inner classes encountered, for use in computing their names as + * keys to various maps. This is an estimate only: an error might lead to + * inaccurate annotations being emitted, but that is ok: WPI should never be run + * without running the checker again afterwards to check the results. This field + * is only used when no element for the inner class is available, such as when + * it comes from another compilation unit. + */ + private int innerClassCount = 0; + + @Override + public void processClass( + ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { + addClass(javacTree, javaParserNode); + } - return receiverType; + @Override + public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { + addClass(javacTree, javaParserNode); + } + + @Override + public void processClass( + ClassTree javacTree, RecordDeclaration javaParserNode) { + addClass(javacTree, javaParserNode); + } + + @Override + public void processClass( + ClassTree javacTree, AnnotationDeclaration javaParserNode) { + // TODO: consider supporting inferring annotations on annotation + // declarations. + // addClass(javacTree, javaParserNode); + } + + @Override + public void processNewClass( + NewClassTree javacTree, ObjectCreationExpr javaParserNode) { + ClassTree body = javacTree.getClassBody(); + if (body != null) { + addClass(body, null); + } + } + + /** + * Creates a wrapper around the class for {@code tree} and stores it in {@code + * sourceAnnos}. + * + *

          This method computes the name of the class when the element corresponding + * to tree is null and uses it as the key for {@code classToAnnos} + * + * @param tree tree to add. Its corresponding name is used as the key for {@code + * classToAnnos}. + * @param javaParserNode the node corresponding to the declaration, which is + * used to place annotations on the class itself. Can be null, e.g. for an + * anonymous class. + */ + private void addClass( + ClassTree tree, @Nullable TypeDeclaration javaParserNode) { + String className; + TypeElement classElt = TreeUtils.elementFromDeclaration(tree); + if (classElt == null) { + // If such an element does not exist, compute the name of the class + // instead. This method of computing the name is not 100% guaranteed to + // be reliable, but it should be sufficient for WPI's purposes here: if + // the wrong name is computed, the worst outcome is a false positive + // because WPI inferred an untrue annotation. + Optional ofqn = javaParserClass.getFullyQualifiedName(); + if (!ofqn.isPresent()) { + throw new BugInCF( + "Missing getFullyQualifiedName() for " + javaParserClass); + } + if ("".contentEquals(tree.getSimpleName())) { + @SuppressWarnings( + "signature:assignment" // computed from string concatenation + ) + @BinaryName String computedName = ofqn.get() + "$" + ++innerClassCount; + className = computedName; + } else { + @SuppressWarnings( + "signature:assignment" // computed from string concatenation + ) + @BinaryName String computedName = + ofqn.get() + "$" + tree.getSimpleName().toString(); + className = computedName; + } + } else { + className = ElementUtils.getBinaryName(classElt); + for (TypeElement supertypeElement : + ElementUtils.getSuperTypes(classElt, elements)) { + String supertypeName = ElementUtils.getBinaryName(supertypeElement); + Set<@BinaryName String> supertypeSet = + supertypesMap.computeIfAbsent( + className, k -> new TreeSet<>()); + supertypeSet.add(supertypeName); + Set<@BinaryName String> subtypeSet = + subtypesMap.computeIfAbsent( + supertypeName, k -> new TreeSet<>()); + subtypeSet.add(className); + } + } + + ClassOrInterfaceAnnos typeWrapper = + new ClassOrInterfaceAnnos(className, javaParserNode); + if (!classToAnnos.containsKey(className)) { + classToAnnos.put(className, typeWrapper); + } + + sourceAnnos.types.add(typeWrapper); + } + + @Override + public void processMethod( + MethodTree javacTree, MethodDeclaration javaParserNode) { + addCallableDeclaration(javacTree, javaParserNode); + } + + @Override + public void processMethod( + MethodTree javacTree, ConstructorDeclaration javaParserNode) { + addCallableDeclaration(javacTree, javaParserNode); + } + + /** + * Creates a wrapper around {@code javacTree} with the corresponding declaration + * {@code javaParserNode} and stores it in {@code sourceAnnos}. + * + * @param javacTree javac tree for declaration to add + * @param javaParserNode a JavaParser node for the same class as {@code + * javacTree} + */ + private void addCallableDeclaration( + MethodTree javacTree, CallableDeclaration javaParserNode) { + ExecutableElement element = TreeUtils.elementFromDeclaration(javacTree); + if (element == null) { + // element can be null if there is no element corresponding to the + // method, which happens for certain kinds of anonymous classes, + // such as Ordering$1 in PolyCollectorTypeVar.java in the + // all-systems test suite. + return; + } + String className = ElementUtils.getEnclosingClassName(element); + ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(className); + String executableSignature = JVMNames.getJVMMethodSignature(javacTree); + if (!enclosingClass.callableDeclarations.containsKey(executableSignature)) { + enclosingClass.callableDeclarations.put( + executableSignature, + new CallableDeclarationAnnos(javaParserNode)); + } + } + + @Override + public void processVariable( + VariableTree javacTree, EnumConstantDeclaration javaParserNode) { + VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); + if (!elt.getKind().isField()) { + throw new BugInCF(elt + " is not a field but a " + elt.getKind()); + } + + String enclosingClassName = ElementUtils.getEnclosingClassName(elt); + ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); + String fieldName = javacTree.getName().toString(); + enclosingClass.enumConstants.add(fieldName); + + // Ensure that if an enum constant defines a class, that class gets + // registered properly. See e.g. + // https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.9.1 + // for the specification of an enum constant, which does permit it to + // define an anonymous class. + NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); + ClassTree constructorClassBody = constructor.getClassBody(); + if (constructorClassBody != null) { + // addClass assumes there is an element for its argument, but that is + // not always true! + if (TreeUtils.elementFromDeclaration(constructorClassBody) != null) { + addClass(constructorClassBody, null); + } + } + } + + @Override + public void processVariable( + VariableTree javacTree, VariableDeclarator javaParserNode) { + VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); + if (!elt.getKind().isField()) { + return; + } + + String enclosingClassName = ElementUtils.getEnclosingClassName(elt); + ClassOrInterfaceAnnos enclosingClass = classToAnnos.get(enclosingClassName); + String fieldName = javacTree.getName().toString(); + if (!enclosingClass.fields.containsKey(fieldName)) { + enclosingClass.fields.put(fieldName, new FieldAnnos(javaParserNode)); + } + } + }; + visitor.visitClass(javacClass, javaParserClass); } /** - * If this wrapper holds a method, returns the inferred type of the return type. If necessary, - * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code - * atf} to a wrapper around the base type for the return type. + * Calls {@link #addSourceFile(String)} for the file containing the given element. * - * @param type base type for the return type, used for initializing the returned {@code - * AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will be - * used - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the return - * type + * @param element the element for the source file to add + * @return path of the file containing {@code element} */ - public AnnotatedTypeMirror getReturnType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { - if (returnType == null) { - returnType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); - } + private String addClassesForElement(Element element) { + if (!ElementUtils.isElementFromSourceCode(element)) { + throw new BugInCF("Called addClassesForElement for non-source element: " + element); + } - return returnType; + TypeElement toplevelClass = ElementUtils.toplevelEnclosingTypeElement(element); + String path = ElementUtils.getSourceFilePath(toplevelClass); + if (toplevelClass.getKind() == ElementKind.ANNOTATION_TYPE) { + // Inferring annotations on elements of annotation declarations is not supported. + // One issue with supporting inference on annotation declaration elements is that + // AnnotatedTypeFactory#declarationFromElement returns null for annotation declarations + // quite commonly (because Trees#getTree, which it delegates to, does as well). + // In this case, we return path here without actually attempting to create the wrappers + // for the annotation declaration. The rest of WholeProgramInferenceJavaParserStorage + // already needs to handle classes without entries in the various tables (because of the + // possibility of classes outside the current compilation unit), so this is safe. + return path; + } + if (classToAnnos.containsKey(ElementUtils.getBinaryName(toplevelClass))) { + return path; + } + + addSourceFile(path); + CompilationUnitAnnos sourceAnnos = sourceToAnnos.get(path); + ClassTree toplevelClassTree = + (ClassTree) atypeFactory.declarationFromElement(toplevelClass); + TypeDeclaration javaParserNode = + sourceAnnos.getClassOrInterfaceDeclarationByName( + toplevelClass.getSimpleName().toString()); + createWrappersForClass(toplevelClassTree, javaParserNode, sourceAnnos); + return path; } + /// + /// Writing to a file + /// + + // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because + // a scene may be modifed and written at any time, including before or after + // postProcessClassTree is called. + /** - * Returns the inferred declaration annotations on this executable. Returns an empty set if - * there are no annotations. + * Side-effects the compilation unit annotations to make any desired changes before writing to a + * file. * - * @return the declaration annotations for this callable declaration + * @param compilationUnitAnnos the compilation unit annotations to modify */ - public AnnotationMirrorSet getDeclarationAnnotations() { - if (declarationAnnotations == null) { - return AnnotationMirrorSet.emptySet(); - } - - return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); + public void wpiPrepareCompilationUnitForWriting(CompilationUnitAnnos compilationUnitAnnos) { + for (ClassOrInterfaceAnnos type : compilationUnitAnnos.types) { + wpiPrepareClassForWriting( + type, supertypesMap.get(type.className), subtypesMap.get(type.className)); + } } /** - * Adds a declaration annotation to this callable declaration and returns whether it was a new - * annotation. + * Side-effects the class annotations to make any desired changes before writing to a file. + * + *

          Because of the side effect, clients may want to pass a copy into this method. * - * @param annotation the declaration annotation to add - * @return true if {@code annotation} wasn't previously stored for this callable declaration + * @param classAnnos the class annotations to modify + * @param supertypes the binary names of all supertypes; not side-effected + * @param subtypes the binary names of all subtypes; not side-effected */ - public boolean addDeclarationAnnotation(AnnotationMirror annotation) { - if (declarationAnnotations == null) { - declarationAnnotations = new AnnotationMirrorSet(); - } + public void wpiPrepareClassForWriting( + ClassOrInterfaceAnnos classAnnos, + Collection<@BinaryName String> supertypes, + Collection<@BinaryName String> subtypes) { + if (classAnnos.callableDeclarations.isEmpty()) { + return; + } + + for (Map.Entry methodEntry : + classAnnos.callableDeclarations.entrySet()) { + String jvmSignature = methodEntry.getKey(); + List inSupertypes = + findOverrides(jvmSignature, supertypesMap.get(classAnnos.className)); + List inSubtypes = + findOverrides(jvmSignature, subtypesMap.get(classAnnos.className)); - return declarationAnnotations.add(annotation); + wpiPrepareMethodForWriting(methodEntry.getValue(), inSupertypes, inSubtypes); + } } /** - * Attempts to remove the given declaration annotation from this callable declaration and - * returns whether an annotation was successfully removed. + * Return all the CallableDeclarationAnnos for the given signature. * - * @param anno an annotation - * @return true if {@code anno} was removed; false if it was not present or otherwise couldn't - * be removed + * @param jvmSignature the JVM signature + * @param typeNames a collection of type names + * @return the CallableDeclarationAnnos for the given signature, in all of the types */ - /*package-private*/ boolean removeDeclarationAnnotation(AnnotationMirror anno) { - if (declarationAnnotations == null) { - return false; - } - return declarationAnnotations.remove(anno); + private List findOverrides( + String jvmSignature, @Nullable Collection<@BinaryName String> typeNames) { + if (typeNames == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (String typeName : typeNames) { + ClassOrInterfaceAnnos classAnnos = classToAnnos.get(typeName); + if (classAnnos != null) { + CallableDeclarationAnnos callableAnnos = + classAnnos.callableDeclarations.get(jvmSignature); + if (callableAnnos != null) { + result.add(callableAnnos); + } + } + } + return result; } /** - * Returns the inferred preconditions for this callable declaration. The keys of the returned - * map use the same string formatting as the {@link - * org.checkerframework.framework.qual.RequiresQualifier} annotation, e.g. "#1" for the first - * parameter. + * Side-effects the method or constructor annotations to make any desired changes before writing + * to a file. For example, this method may make inferred annotations consistent with one another + * between superclasses and subclasses. * - *

          Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and - * such changes will be reflected in the receiver CallableDeclarationAnnos object. - * - * @return a mapping from Java expression string to pairs of (inferred precondition for the - * expression, declared type of the expression) - * @see #getPreconditionsForExpression + * @param methodAnnos the method or constructor annotations to modify + * @param inSupertypes the method or constructor annotations for all overridden methods; not + * side-effected + * @param inSubtypes the method or constructor annotations for all overriding methods; not + * side-effected */ - public Map> getPreconditions() { - if (preconditions == null) { - return Collections.emptyMap(); - } else { - return Collections.unmodifiableMap(preconditions); - } + // TODO: Inferred annotations must be consistent both with one another and with + // programmer-written annotations. The latter are stored in elements and, with the given formal + // parameter list, are not accessible to this method. In the future, the annotations stored in + // elements should also be passed to this method (or maybe they are already available to the + // type factory?). I'm leaving that enhancement until later. + public void wpiPrepareMethodForWriting( + CallableDeclarationAnnos methodAnnos, + Collection inSupertypes, + Collection inSubtypes) { + atypeFactory.wpiPrepareMethodForWriting(methodAnnos, inSupertypes, inSubtypes); + } + + @Override + public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checker) { + if (outputFormat != OutputFormat.AJAVA) { + throw new BugInCF( + "WholeProgramInferenceJavaParser used with output format " + outputFormat); + } + + File outputDir = AJAVA_FILES_PATH; + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + setSupertypesAndSubtypesModified(); + + for (String path : modifiedFiles) { + // This calls deepCopy() because wpiPrepareCompilationUnitForWriting performs side + // effects that we don't want to be persistent. + CompilationUnitAnnos root = sourceToAnnos.get(path).deepCopy(); + wpiPrepareCompilationUnitForWriting(root); + File packageDir; + if (!root.compilationUnit.getPackageDeclaration().isPresent()) { + packageDir = AJAVA_FILES_PATH; + } else { + packageDir = + new File( + AJAVA_FILES_PATH, + root.compilationUnit + .getPackageDeclaration() + .get() + .getNameAsString() + .replaceAll("\\.", File.separator)); + } + + if (!packageDir.exists()) { + packageDir.mkdirs(); + } + + String name = new File(path).getName(); + if (name.endsWith(".java")) { + name = name.substring(0, name.length() - ".java".length()); + } + + String nameWithChecker = name + "-" + checker.getClass().getCanonicalName() + ".ajava"; + File outputPath = new File(packageDir, nameWithChecker); + if (this.inferOutputOriginal) { + File outputPathNoCheckerName = new File(packageDir, name + ".ajava"); + // Avoid re-writing this file for each checker that was run. + if (Files.notExists(outputPathNoCheckerName.toPath())) { + writeAjavaFile(outputPathNoCheckerName, root); + } + } + root.transferAnnotations(checker); + writeAjavaFile(outputPath, root); + } + + modifiedFiles.clear(); } /** - * Returns the inferred postconditions for this callable declaration. The keys of the returned - * map use the same string formatting as the {@link - * org.checkerframework.framework.qual.EnsuresQualifier} annotation, e.g. "#1" for the first - * parameter. - * - *

          Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and - * such changes will be reflected in the receiver CallableDeclarationAnnos object. + * Write an ajava file to disk. * - * @return a mapping from Java expression string to pairs of (inferred postcondition for the - * expression, declared type of the expression) - * @see #getPostconditionsForExpression + * @param outputPath the path to which the ajava file should be written + * @param root the compilation unit to be written */ - public Map> getPostconditions() { - if (postconditions == null) { - return Collections.emptyMap(); - } - - return Collections.unmodifiableMap(postconditions); + private void writeAjavaFile(File outputPath, CompilationUnitAnnos root) { + try (Writer writer = Files.newBufferedWriter(outputPath.toPath(), StandardCharsets.UTF_8)) { + // This implementation uses JavaParser's lexical preserving printing, which writes the + // file such that its formatting is close to the original source file it was parsed from + // as possible. It is commented out because, this feature is very buggy and crashes when + // adding annotations in certain locations. + // LexicalPreservingPrinter.print(root.declaration, writer); + + // Do not print invisible qualifiers, to avoid cluttering the output. + Set invisibleQualifierNames = getInvisibleQualifierNames(this.atypeFactory); + DefaultPrettyPrinter prettyPrinter = + new DefaultPrettyPrinter() { + @Override + public String print(Node node) { + VoidVisitor visitor = + new DefaultPrettyPrinterVisitor(getConfiguration()) { + @Override + public void visit(MarkerAnnotationExpr n, Void arg) { + if (invisibleQualifierNames.contains( + n.getName().toString())) { + return; + } + super.visit(n, arg); + } + + @Override + public void visit(SingleMemberAnnotationExpr n, Void arg) { + if (invisibleQualifierNames.contains( + n.getName().toString())) { + return; + } + super.visit(n, arg); + } + + @Override + public void visit(NormalAnnotationExpr n, Void arg) { + if (invisibleQualifierNames.contains( + n.getName().toString())) { + return; + } + super.visit(n, arg); + } + }; + node.accept(visitor, null); + return visitor.toString(); + } + }; + + writer.write(prettyPrinter.print(root.compilationUnit)); + } catch (IOException e) { + throw new BugInCF("Error while writing ajava file " + outputPath, e); + } } /** - * Returns an AnnotatedTypeMirror containing the preconditions for the given expression. Changes - * to the returned AnnotatedTypeMirror are reflected in this CallableDeclarationAnnos. + * Adds an explicit receiver type to a JavaParser method declaration. * - * @param expression a string representing a Java expression, in the same format as the argument - * to a {@link org.checkerframework.framework.qual.RequiresQualifier} annotation - * @param declaredType the declared type of {@code expression} - * @param atf the annotated type factory of a given type system, whose type hierarchy will be - * used - * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred - * preconditions for the given expression + * @param methodDeclaration declaration to add a receiver to */ - public AnnotatedTypeMirror getPreconditionsForExpression( - String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { - if (preconditions == null) { - preconditions = new HashMap<>(1); - } - - if (!preconditions.containsKey(expression)) { - AnnotatedTypeMirror preconditionsType = - AnnotatedTypeMirror.createType(declaredType.getUnderlyingType(), atf, false); - preconditions.put(expression, IPair.of(preconditionsType, declaredType)); - } - - return preconditions.get(expression).first; + private static void addExplicitReceiver(MethodDeclaration methodDeclaration) { + if (methodDeclaration.getReceiverParameter().isPresent()) { + return; + } + + com.github.javaparser.ast.Node parent = methodDeclaration.getParentNode().get(); + if (!(parent instanceof TypeDeclaration)) { + return; + } + + TypeDeclaration parentDecl = (TypeDeclaration) parent; + ClassOrInterfaceType receiver = new ClassOrInterfaceType(); + receiver.setName(parentDecl.getName()); + if (parentDecl.isClassOrInterfaceDeclaration()) { + ClassOrInterfaceDeclaration parentClassDecl = + parentDecl.asClassOrInterfaceDeclaration(); + if (!parentClassDecl.getTypeParameters().isEmpty()) { + NodeList typeArgs = new NodeList<>(); + for (TypeParameter typeParam : parentClassDecl.getTypeParameters()) { + ClassOrInterfaceType typeArg = new ClassOrInterfaceType(); + typeArg.setName(typeParam.getNameAsString()); + typeArgs.add(typeArg); + } + + receiver.setTypeArguments(typeArgs); + } + } + + methodDeclaration.setReceiverParameter(new ReceiverParameter(receiver, "this")); } /** - * Returns an AnnotatedTypeMirror containing the postconditions for the given expression. - * Changes to the returned AnnotatedTypeMirror are reflected in this CallableDeclarationAnnos. + * Transfers all annotations for {@code annotatedType} and its nested types to {@code target}, + * which is the JavaParser node representing the same type. Does nothing if {@code + * annotatedType} is null (this may occur if there are no inferred annotations for the type). * - * @param expression a string representing a Java expression, in the same format as the argument - * to a {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation - * @param declaredType the declared type of {@code expression} - * @param atf the annotated type factory of a given type system, whose type hierarchy will be - * used - * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred - * postconditions for the given expression + * @param annotatedType type to transfer annotations from + * @param target the JavaParser type to transfer annotation to; must represent the same type as + * {@code annotatedType} */ - public AnnotatedTypeMirror getPostconditionsForExpression( - String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { - if (postconditions == null) { - postconditions = new HashMap<>(1); - } - - if (!postconditions.containsKey(expression)) { - AnnotatedTypeMirror postconditionsType = - AnnotatedTypeMirror.createType(declaredType.getUnderlyingType(), atf, false); - postconditions.put(expression, IPair.of(postconditionsType, declaredType)); - } - - return postconditions.get(expression).first; + private static void transferAnnotations( + @Nullable AnnotatedTypeMirror annotatedType, Type target) { + if (annotatedType == null) { + return; + } + + target.accept(new AnnotationTransferVisitor(), annotatedType); } + /// + /// Storing annotations + /// + /** - * Transfers all annotations inferred by whole program inference for the return type, receiver - * type, and parameter types for the wrapped declaration to their corresponding JavaParser - * locations. + * Stores the JavaParser node for a compilation unit and the list of wrappers for the classes + * and interfaces in that compilation unit. */ - public void transferAnnotations() { - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory genericAtf = - (GenericAnnotatedTypeFactory) atypeFactory; - for (AnnotationMirror contractAnno : genericAtf.getContractAnnotations(this)) { - declaration.addAnnotation( - AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( - contractAnno)); - } - } - - if (declarationAnnotations != null && declaration != null) { - for (AnnotationMirror annotation : declarationAnnotations) { - declaration.addAnnotation( - AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( - annotation)); - } - } - - if (paramsDeclAnnos != null) { - for (IPair pair : paramsDeclAnnos) { - Parameter param = declaration.getParameter(pair.first - 1); - param.addAnnotation( - AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( - pair.second)); - } - } - - if (returnType != null) { - // If a return type exists, then the declaration must be a method, not a - // constructor. - WholeProgramInferenceJavaParserStorage.transferAnnotations( - returnType, declaration.asMethodDeclaration().getType()); - } - - if (receiverType != null) { - addExplicitReceiver(declaration.asMethodDeclaration()); - // The receiver won't be present for an anonymous class. - if (declaration.getReceiverParameter().isPresent()) { - WholeProgramInferenceJavaParserStorage.transferAnnotations( - receiverType, declaration.getReceiverParameter().get().getType()); - } - } - - if (parameterTypes == null) { - return; - } - - for (int i = 0; i < parameterTypes.size(); i++) { - AnnotatedTypeMirror inferredType = parameterTypes.get(i); - if (inferredType == null) { - // Can occur if the only places that this method was called were - // outside the compilation unit. - continue; - } - Parameter param = declaration.getParameter(i); - Type javaParserType = param.getType(); - if (param.isVarArgs()) { - NodeList varArgsAnnoExprs = - AnnotationMirrorToAnnotationExprConversion.annotationMirrorSetToAnnotationExprList( - inferredType.getAnnotations()); - param.setVarArgsAnnotations(varArgsAnnoExprs); - - AnnotatedTypeMirror inferredComponentType = - ((AnnotatedArrayType) inferredType).getComponentType(); - WholeProgramInferenceJavaParserStorage.transferAnnotations( - inferredComponentType, javaParserType); - } else { - WholeProgramInferenceJavaParserStorage.transferAnnotations(inferredType, javaParserType); + private static class CompilationUnitAnnos implements DeepCopyable { + /** Compilation unit being wrapped. */ + public final CompilationUnit compilationUnit; + + /** Wrappers for classes and interfaces in {@code compilationUnit}. */ + public final List types; + + /** + * Constructs a wrapper around the given compilation unit. + * + * @param compilationUnit compilation unit to wrap + */ + public CompilationUnitAnnos(CompilationUnit compilationUnit) { + this.compilationUnit = compilationUnit; + this.types = new ArrayList<>(); } - } - } - @Override - public String toString() { - return "CallableDeclarationAnnos [declaration=" - + declaration - + ", parameterTypes=" - + parameterTypes - + ", receiverType=" - + receiverType - + ", returnType=" - + returnType - + "]"; - } - } - - /** - * Deep copy (according to the {@code DeepCopyable} interface) a pre- or post-condition map. - * - * @param orig the map to copy - * @return a deep copy of the map - */ - private static @Nullable Map> - deepCopyMapOfStringToPair( - @Nullable Map> orig) { - if (orig == null) { - return null; - } - Map> result = - new HashMap<>(CollectionsPlume.mapCapacity(orig.size())); - result.clear(); - for (Map.Entry> entry : - orig.entrySet()) { - String javaExpression = entry.getKey(); - IPair atms = entry.getValue(); - result.put(javaExpression, IPair.deepCopy(atms)); - } - return result; - } + /** + * Private constructor for use by deepCopy(). + * + * @param compilationUnit compilation unit to wrap + * @param types wrappers for classes and interfaces in {@code compilationUnit} + */ + private CompilationUnitAnnos( + CompilationUnit compilationUnit, List types) { + this.compilationUnit = compilationUnit; + this.types = types; + } - /** Stores the JavaParser node for a field and the annotations that have been inferred for it. */ - private static class FieldAnnos implements DeepCopyable { - /** Wrapped field declaration. */ - public final VariableDeclarator declaration; + @Override + public CompilationUnitAnnos deepCopy() { + return new CompilationUnitAnnos(compilationUnit, CollectionsPlume.deepCopy(types)); + } - /** Inferred type for field, initialized the first time it's accessed. */ - private @MonotonicNonNull AnnotatedTypeMirror type = null; + /** + * Transfers all annotations inferred by whole program inference for the wrapped compilation + * unit to their corresponding JavaParser locations. + * + * @param checker the checker who's name to include in the @AnnotatedFor annotation + */ + public void transferAnnotations(BaseTypeChecker checker) { + JavaParserUtil.clearAnnotations(compilationUnit); + for (TypeDeclaration typeDecl : compilationUnit.getTypes()) { + typeDecl.addSingleMemberAnnotation( + "org.checkerframework.framework.qual.AnnotatedFor", + "\"" + checker.getClass().getCanonicalName() + "\""); + } - /** Annotations on the field declaration. */ - private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; + for (ClassOrInterfaceAnnos typeAnnos : types) { + typeAnnos.transferAnnotations(); + } + } - /** - * Creates a wrapper for the given field declaration. - * - * @param declaration field declaration to wrap - */ - public FieldAnnos(VariableDeclarator declaration) { - this.declaration = declaration; - } + /** + * Returns the top-level type declaration named {@code name} in the compilation unit. + * + * @param name name of type declaration + * @return the type declaration named {@code name} in the wrapped compilation unit + */ + public TypeDeclaration getClassOrInterfaceDeclarationByName(String name) { + return JavaParserUtil.getTypeDeclarationByName(compilationUnit, name); + } - @Override - public FieldAnnos deepCopy() { - FieldAnnos result = new FieldAnnos(declaration); - result.type = DeepCopyable.deepCopyOrNull(this.type); - result.declarationAnnotations = DeepCopyable.deepCopyOrNull(this.declarationAnnotations); - return result; + /** + * Returns a verbose printed representation of this. + * + * @return a verbose printed representation of this + */ + @SuppressWarnings("UnusedMethod") + public String toStringVerbose() { + StringJoiner sb = new StringJoiner(System.lineSeparator()); + sb.add("CompilationUnitAnnos:"); + for (ClassOrInterfaceAnnos type : types) { + sb.add(type.toStringVerbose()); + } + return sb.toString(); + } } /** - * Returns the inferred type of the field. If necessary, initializes the {@code - * AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper around - * the base type for the field. - * - * @param type base type for the field, used for initializing the returned {@code - * AnnotatedTypeMirror} the first time it's accessed - * @param atf the annotated type factory of a given type system, whose type hierarchy will be - * used - * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the field + * Stores wrappers for the locations where annotations may be inferred in a class or interface. */ - public AnnotatedTypeMirror getType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { - if (this.type == null) { - this.type = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); - } + private static class ClassOrInterfaceAnnos implements DeepCopyable { + /** + * Mapping from JVM method signatures to the wrapper containing the corresponding + * executable. + */ + public Map callableDeclarations = new HashMap<>(); + + /** Mapping from field names to wrappers for those fields. */ + public Map fields = new HashMap<>(2); + + /** Collection of declared enum constants (empty if not an enum). */ + public Set enumConstants = new HashSet<>(2); + + /** + * Annotations on the declaration of the class (note that despite the name, these can also + * be type annotations). + */ + private @MonotonicNonNull AnnotationMirrorSet classAnnotations = null; + + /** + * The JavaParser TypeDeclaration representing the class's declaration. Used for placing + * annotations inferred on the class declaration itself. + */ + private @MonotonicNonNull TypeDeclaration classDeclaration; + + /** The binary name of the class. */ + private @BinaryName String className; + + /** + * Create a new ClassOrInterfaceAnnos. + * + * @param className the binary name of the class + * @param javaParserNode the JavaParser node corresponding to the class declaration, which + * is used for placing annotations on the class declaration + */ + public ClassOrInterfaceAnnos( + @BinaryName String className, @Nullable TypeDeclaration javaParserNode) { + this.classDeclaration = javaParserNode; + this.className = className; + } - return this.type; + @Override + public ClassOrInterfaceAnnos deepCopy() { + ClassOrInterfaceAnnos result = new ClassOrInterfaceAnnos(className, classDeclaration); + result.callableDeclarations = CollectionsPlume.deepCopyValues(callableDeclarations); + result.fields = CollectionsPlume.deepCopyValues(fields); + result.enumConstants = + UtilPlume.clone(enumConstants); // no deep copy: elements are strings + if (classAnnotations != null) { + result.classAnnotations = classAnnotations.deepCopy(); + } + // no need to change classDeclaration + return result; + } + + /** + * Adds {@code annotation} to the set of annotations on the declaration of this class. + * + * @param annotation an annotation (can be declaration or type) + * @return true if this is a new annotation for this class + */ + public boolean addAnnotationToClassDeclaration(AnnotationMirror annotation) { + if (classAnnotations == null) { + classAnnotations = new AnnotationMirrorSet(); + } + + return classAnnotations.add(annotation); + } + + /** + * Transfers all annotations inferred by whole program inference for the methods and fields + * in the wrapper class or interface to their corresponding JavaParser locations. + */ + public void transferAnnotations() { + for (CallableDeclarationAnnos callableAnnos : callableDeclarations.values()) { + callableAnnos.transferAnnotations(); + } + + if (classAnnotations != null && classDeclaration != null) { + for (AnnotationMirror annotation : classAnnotations) { + classDeclaration.addAnnotation( + AnnotationMirrorToAnnotationExprConversion + .annotationMirrorToAnnotationExpr(annotation)); + } + } + + for (FieldAnnos field : fields.values()) { + field.transferAnnotations(); + } + } + + @Override + public String toString() { + String fieldsString = fields.toString(); + if (fieldsString.length() > 100) { + // The quoting increases the likelihood that all delimiters are balanced in the + // result. That makes it easier to manipulate the result (such as skipping over it) + // in an editor. The quoting also makes clear that the value is truncated. + fieldsString = "\"" + fieldsString.substring(0, 95) + "...\""; + } + + return "ClassOrInterfaceAnnos [" + + (classDeclaration == null ? "unnamed" : classDeclaration.getName()) + + ": callableDeclarations=" + // For deterministic output + + new TreeMap<>(callableDeclarations) + + ", fields=" + + fieldsString + + "]"; + } + + /** + * Returns a verbose printed representation of this. + * + * @return a verbose printed representation of this + */ + public String toStringVerbose() { + return toString(); + } } /** - * Returns the inferred declaration annotations on this field, or an empty set if there are no - * annotations. - * - * @return the declaration annotations for this field declaration + * Stores the JavaParser node for a method or constructor and the annotations that have been + * inferred about its parameters and return type. */ - @SuppressWarnings("UnusedMethod") - public AnnotationMirrorSet getDeclarationAnnotations() { - if (declarationAnnotations == null) { - return AnnotationMirrorSet.emptySet(); - } + public class CallableDeclarationAnnos implements DeepCopyable { + /** Wrapped method or constructor declaration. */ + public final CallableDeclaration declaration; + + /** + * Inferred annotations for the return type, if the declaration represents a method. + * Initialized on first usage. + */ + private @MonotonicNonNull AnnotatedTypeMirror returnType = null; + + /** + * Inferred annotations for the receiver type, if the declaration represents a method. + * Initialized on first usage. + */ + private @MonotonicNonNull AnnotatedTypeMirror receiverType = null; + + /** + * Inferred annotations for parameter types. The list is initialized the first time any + * parameter is accessed, and each parameter is initialized the first time it's accessed. + */ + private @MonotonicNonNull List<@Nullable AnnotatedTypeMirror> parameterTypes = null; + + /** Declaration annotations on the parameters. */ + private @MonotonicNonNull Set> paramsDeclAnnos = null; + + /** + * Annotations on the callable declaration. This does not include preconditions and + * postconditions. + */ + private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; + + /** + * Mapping from expression strings to pairs of (inferred precondition, declared type). The + * keys are strings representing JavaExpressions, using the same format as a user would in + * an {@link org.checkerframework.framework.qual.RequiresQualifier} annotation. + */ + private @MonotonicNonNull Map> + preconditions = null; + + /** + * Mapping from expression strings to pairs of (inferred postcondition, declared type). The + * okeys are strings representing JavaExpressions, using the same format as a user would in + * an {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation. + */ + private @MonotonicNonNull Map> + postconditions = null; + + /** + * Creates a wrapper for the given method or constructor declaration. + * + * @param declaration method or constructor declaration to wrap + */ + public CallableDeclarationAnnos(CallableDeclaration declaration) { + this.declaration = declaration; + } + + @Override + public CallableDeclarationAnnos deepCopy() { + CallableDeclarationAnnos result = new CallableDeclarationAnnos(declaration); + result.returnType = DeepCopyable.deepCopyOrNull(this.returnType); + result.receiverType = DeepCopyable.deepCopyOrNull(this.receiverType); + if (parameterTypes != null) { + result.parameterTypes = CollectionsPlume.deepCopy(this.parameterTypes); + } + result.declarationAnnotations = + DeepCopyable.deepCopyOrNull(this.declarationAnnotations); + + if (this.paramsDeclAnnos != null) { + result.paramsDeclAnnos = new ArraySet<>(this.paramsDeclAnnos); + } + result.preconditions = deepCopyMapOfStringToPair(this.preconditions); + result.postconditions = deepCopyMapOfStringToPair(this.postconditions); + return result; + } + + /** + * Returns the inferred type for the parameter at the given index. If necessary, initializes + * the {@code AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a + * wrapper around the base type for the parameter. + * + * @param type type for the parameter at {@code index}, used for initializing the returned + * {@code AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will + * be used + * @param index_1based index of the parameter to return the inferred annotations of + * (1-based) + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the + * parameter at the given index + */ + public AnnotatedTypeMirror getParameterTypeInitialized( + AnnotatedTypeMirror type, @Positive int index_1based, AnnotatedTypeFactory atf) { + // 0-based index + int i = index_1based - 1; + + if (parameterTypes == null) { + parameterTypes = + new ArrayList<>( + Collections.nCopies(declaration.getParameters().size(), null)); + } + + if (parameterTypes.get(i) == null) { + parameterTypes.set( + i, AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false)); + } + + return parameterTypes.get(i); + } + + /** + * Returns the inferred type for the parameter at the given index, or null if there's no + * parameter at the given index or there's no inferred type for that parameter. + * + * @param index index of the parameter to return the inferred annotations of + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the + * parameter at the given index, or null if there's no parameter at {@code index} or if + * there's not inferred annotations for that parameter + */ + public @Nullable AnnotatedTypeMirror getParameterType(int index) { + if (parameterTypes == null || index < 0 || index >= parameterTypes.size()) { + return null; + } + + return parameterTypes.get(index); + } + + /** + * Adds a declaration annotation to this parameter and returns whether it was a new + * annotation. + * + * @param annotation the declaration annotation to add + * @param index_1based index of the parameter (1-indexed) + * @return true if {@code annotation} wasn't previously stored for this parameter + */ + public boolean addDeclarationAnnotationToFormalParameter( + AnnotationMirror annotation, @Positive int index_1based) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + if (paramsDeclAnnos == null) { + // There are usually few formal parameters. + paramsDeclAnnos = new ArraySet<>(4); + } + + return paramsDeclAnnos.add(IPair.of(index_1based, annotation)); + } + + /** + * If this wrapper holds a method, returns the inferred type of the receiver. If necessary, + * initializes the {@code AnnotatedTypeMirror} for that location using {@code type} and + * {@code atf} to a wrapper around the base type for the receiver type. + * + * @param type base type for the receiver type, used for initializing the returned {@code + * AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will + * be used + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the + * receiver type + */ + public AnnotatedTypeMirror getReceiverType( + AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { + if (receiverType == null) { + receiverType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); + } + + return receiverType; + } + + /** + * If this wrapper holds a method, returns the inferred type of the return type. If + * necessary, initializes the {@code AnnotatedTypeMirror} for that location using {@code + * type} and {@code atf} to a wrapper around the base type for the return type. + * + * @param type base type for the return type, used for initializing the returned {@code + * AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will + * be used + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the return + * type + */ + public AnnotatedTypeMirror getReturnType( + AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { + if (returnType == null) { + returnType = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); + } + + return returnType; + } + + /** + * Returns the inferred declaration annotations on this executable. Returns an empty set if + * there are no annotations. + * + * @return the declaration annotations for this callable declaration + */ + public AnnotationMirrorSet getDeclarationAnnotations() { + if (declarationAnnotations == null) { + return AnnotationMirrorSet.emptySet(); + } + + return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); + } + + /** + * Adds a declaration annotation to this callable declaration and returns whether it was a + * new annotation. + * + * @param annotation the declaration annotation to add + * @return true if {@code annotation} wasn't previously stored for this callable declaration + */ + public boolean addDeclarationAnnotation(AnnotationMirror annotation) { + if (declarationAnnotations == null) { + declarationAnnotations = new AnnotationMirrorSet(); + } + + return declarationAnnotations.add(annotation); + } + + /** + * Attempts to remove the given declaration annotation from this callable declaration and + * returns whether an annotation was successfully removed. + * + * @param anno an annotation + * @return true if {@code anno} was removed; false if it was not present or otherwise + * couldn't be removed + */ + /*package-private*/ boolean removeDeclarationAnnotation(AnnotationMirror anno) { + if (declarationAnnotations == null) { + return false; + } + return declarationAnnotations.remove(anno); + } + + /** + * Returns the inferred preconditions for this callable declaration. The keys of the + * returned map use the same string formatting as the {@link + * org.checkerframework.framework.qual.RequiresQualifier} annotation, e.g. "#1" for the + * first parameter. + * + *

          Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and + * such changes will be reflected in the receiver CallableDeclarationAnnos object. + * + * @return a mapping from Java expression string to pairs of (inferred precondition for the + * expression, declared type of the expression) + * @see #getPreconditionsForExpression + */ + public Map> getPreconditions() { + if (preconditions == null) { + return Collections.emptyMap(); + } else { + return Collections.unmodifiableMap(preconditions); + } + } + + /** + * Returns the inferred postconditions for this callable declaration. The keys of the + * returned map use the same string formatting as the {@link + * org.checkerframework.framework.qual.EnsuresQualifier} annotation, e.g. "#1" for the first + * parameter. + * + *

          Although the map is immutable, the AnnotatedTypeMirrors within it can be modified, and + * such changes will be reflected in the receiver CallableDeclarationAnnos object. + * + * @return a mapping from Java expression string to pairs of (inferred postcondition for the + * expression, declared type of the expression) + * @see #getPostconditionsForExpression + */ + public Map> getPostconditions() { + if (postconditions == null) { + return Collections.emptyMap(); + } + + return Collections.unmodifiableMap(postconditions); + } + + /** + * Returns an AnnotatedTypeMirror containing the preconditions for the given expression. + * Changes to the returned AnnotatedTypeMirror are reflected in this + * CallableDeclarationAnnos. + * + * @param expression a string representing a Java expression, in the same format as the + * argument to a {@link org.checkerframework.framework.qual.RequiresQualifier} + * annotation + * @param declaredType the declared type of {@code expression} + * @param atf the annotated type factory of a given type system, whose type hierarchy will + * be used + * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred + * preconditions for the given expression + */ + public AnnotatedTypeMirror getPreconditionsForExpression( + String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { + if (preconditions == null) { + preconditions = new HashMap<>(1); + } + + if (!preconditions.containsKey(expression)) { + AnnotatedTypeMirror preconditionsType = + AnnotatedTypeMirror.createType( + declaredType.getUnderlyingType(), atf, false); + preconditions.put(expression, IPair.of(preconditionsType, declaredType)); + } + + return preconditions.get(expression).first; + } + + /** + * Returns an AnnotatedTypeMirror containing the postconditions for the given expression. + * Changes to the returned AnnotatedTypeMirror are reflected in this + * CallableDeclarationAnnos. + * + * @param expression a string representing a Java expression, in the same format as the + * argument to a {@link org.checkerframework.framework.qual.EnsuresQualifier} annotation + * @param declaredType the declared type of {@code expression} + * @param atf the annotated type factory of a given type system, whose type hierarchy will + * be used + * @return an {@code AnnotatedTypeMirror} containing the annotations for the inferred + * postconditions for the given expression + */ + public AnnotatedTypeMirror getPostconditionsForExpression( + String expression, AnnotatedTypeMirror declaredType, AnnotatedTypeFactory atf) { + if (postconditions == null) { + postconditions = new HashMap<>(1); + } + + if (!postconditions.containsKey(expression)) { + AnnotatedTypeMirror postconditionsType = + AnnotatedTypeMirror.createType( + declaredType.getUnderlyingType(), atf, false); + postconditions.put(expression, IPair.of(postconditionsType, declaredType)); + } + + return postconditions.get(expression).first; + } + + /** + * Transfers all annotations inferred by whole program inference for the return type, + * receiver type, and parameter types for the wrapped declaration to their corresponding + * JavaParser locations. + */ + public void transferAnnotations() { + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + GenericAnnotatedTypeFactory genericAtf = + (GenericAnnotatedTypeFactory) atypeFactory; + for (AnnotationMirror contractAnno : genericAtf.getContractAnnotations(this)) { + declaration.addAnnotation( + AnnotationMirrorToAnnotationExprConversion + .annotationMirrorToAnnotationExpr(contractAnno)); + } + } + + if (declarationAnnotations != null && declaration != null) { + for (AnnotationMirror annotation : declarationAnnotations) { + declaration.addAnnotation( + AnnotationMirrorToAnnotationExprConversion + .annotationMirrorToAnnotationExpr(annotation)); + } + } + + if (paramsDeclAnnos != null) { + for (IPair pair : paramsDeclAnnos) { + Parameter param = declaration.getParameter(pair.first - 1); + param.addAnnotation( + AnnotationMirrorToAnnotationExprConversion + .annotationMirrorToAnnotationExpr(pair.second)); + } + } + + if (returnType != null) { + // If a return type exists, then the declaration must be a method, not a + // constructor. + WholeProgramInferenceJavaParserStorage.transferAnnotations( + returnType, declaration.asMethodDeclaration().getType()); + } + + if (receiverType != null) { + addExplicitReceiver(declaration.asMethodDeclaration()); + // The receiver won't be present for an anonymous class. + if (declaration.getReceiverParameter().isPresent()) { + WholeProgramInferenceJavaParserStorage.transferAnnotations( + receiverType, declaration.getReceiverParameter().get().getType()); + } + } + + if (parameterTypes == null) { + return; + } + + for (int i = 0; i < parameterTypes.size(); i++) { + AnnotatedTypeMirror inferredType = parameterTypes.get(i); + if (inferredType == null) { + // Can occur if the only places that this method was called were + // outside the compilation unit. + continue; + } + Parameter param = declaration.getParameter(i); + Type javaParserType = param.getType(); + if (param.isVarArgs()) { + NodeList varArgsAnnoExprs = + AnnotationMirrorToAnnotationExprConversion + .annotationMirrorSetToAnnotationExprList( + inferredType.getAnnotations()); + param.setVarArgsAnnotations(varArgsAnnoExprs); + + AnnotatedTypeMirror inferredComponentType = + ((AnnotatedArrayType) inferredType).getComponentType(); + WholeProgramInferenceJavaParserStorage.transferAnnotations( + inferredComponentType, javaParserType); + } else { + WholeProgramInferenceJavaParserStorage.transferAnnotations( + inferredType, javaParserType); + } + } + } - return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); + @Override + public String toString() { + return "CallableDeclarationAnnos [declaration=" + + declaration + + ", parameterTypes=" + + parameterTypes + + ", receiverType=" + + receiverType + + ", returnType=" + + returnType + + "]"; + } } /** - * Adds a declaration annotation to this field declaration and returns whether it was a new - * annotation. + * Deep copy (according to the {@code DeepCopyable} interface) a pre- or post-condition map. * - * @param annotation declaration annotation to add - * @return true if {@code annotation} wasn't previously stored for this field declaration + * @param orig the map to copy + * @return a deep copy of the map */ - public boolean addDeclarationAnnotation(AnnotationMirror annotation) { - if (declarationAnnotations == null) { - declarationAnnotations = new AnnotationMirrorSet(); - } - - return declarationAnnotations.add(annotation); + private static @Nullable Map> + deepCopyMapOfStringToPair( + @Nullable Map> orig) { + if (orig == null) { + return null; + } + Map> result = + new HashMap<>(CollectionsPlume.mapCapacity(orig.size())); + result.clear(); + for (Map.Entry> entry : + orig.entrySet()) { + String javaExpression = entry.getKey(); + IPair atms = entry.getValue(); + result.put(javaExpression, IPair.deepCopy(atms)); + } + return result; } /** - * Transfers all annotations inferred by whole program inference on this field to the JavaParser - * nodes for that field. + * Stores the JavaParser node for a field and the annotations that have been inferred for it. */ - public void transferAnnotations() { - if (declarationAnnotations != null) { - // Don't add directly to the type of the variable declarator, - // because declaration annotations need to be attached to the FieldDeclaration - // node instead. - Node declParent = declaration.getParentNode().orElse(null); - if (declParent instanceof FieldDeclaration) { - FieldDeclaration decl = (FieldDeclaration) declParent; - for (AnnotationMirror annotation : declarationAnnotations) { - decl.addAnnotation( - AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( - annotation)); - } - } - } - - // Don't transfer type annotations to variable declarators with sibling - // variable declarators, because they're printed incorrectly (as "???"). - // (A variable declarator can have siblings if it's part of a declaration - // like "int x, y, z;", which is bad style but legal Java.) - // In any event, WPI doesn't consider the LUB of the types of the siblings, - // so any inferred type is likely to be wrong. - // TODO: avoid inferring these types at all, or take the LUB of all assignments - // to the siblings. Unfortunately, VariableElements don't track whether they have - // siblings, and there's no other information about the declaration for - // WholeProgramInferenceImplementation to use: to determine that there are siblings, - // a parse tree is needed. - boolean foundVariableDeclarator = false; - for (Node child : this.declaration.getParentNode().get().getChildNodes()) { - if (child instanceof VariableDeclarator) { - if (foundVariableDeclarator) { - // This is the second VariableDeclarator that was found. - return; - } - foundVariableDeclarator = true; + private static class FieldAnnos implements DeepCopyable { + /** Wrapped field declaration. */ + public final VariableDeclarator declaration; + + /** Inferred type for field, initialized the first time it's accessed. */ + private @MonotonicNonNull AnnotatedTypeMirror type = null; + + /** Annotations on the field declaration. */ + private @MonotonicNonNull AnnotationMirrorSet declarationAnnotations = null; + + /** + * Creates a wrapper for the given field declaration. + * + * @param declaration field declaration to wrap + */ + public FieldAnnos(VariableDeclarator declaration) { + this.declaration = declaration; } - } - Type newType = (Type) declaration.getType().accept(new CloneVisitor(), null); - WholeProgramInferenceJavaParserStorage.transferAnnotations(type, newType); - declaration.setType(newType); - } - @Override - public String toString() { - return "FieldAnnos [declaration=" + declaration + ", type=" + type + "]"; + @Override + public FieldAnnos deepCopy() { + FieldAnnos result = new FieldAnnos(declaration); + result.type = DeepCopyable.deepCopyOrNull(this.type); + result.declarationAnnotations = + DeepCopyable.deepCopyOrNull(this.declarationAnnotations); + return result; + } + + /** + * Returns the inferred type of the field. If necessary, initializes the {@code + * AnnotatedTypeMirror} for that location using {@code type} and {@code atf} to a wrapper + * around the base type for the field. + * + * @param type base type for the field, used for initializing the returned {@code + * AnnotatedTypeMirror} the first time it's accessed + * @param atf the annotated type factory of a given type system, whose type hierarchy will + * be used + * @return an {@code AnnotatedTypeMirror} containing all annotations inferred for the field + */ + public AnnotatedTypeMirror getType(AnnotatedTypeMirror type, AnnotatedTypeFactory atf) { + if (this.type == null) { + this.type = AnnotatedTypeMirror.createType(type.getUnderlyingType(), atf, false); + } + + return this.type; + } + + /** + * Returns the inferred declaration annotations on this field, or an empty set if there are + * no annotations. + * + * @return the declaration annotations for this field declaration + */ + @SuppressWarnings("UnusedMethod") + public AnnotationMirrorSet getDeclarationAnnotations() { + if (declarationAnnotations == null) { + return AnnotationMirrorSet.emptySet(); + } + + return AnnotationMirrorSet.unmodifiableSet(declarationAnnotations); + } + + /** + * Adds a declaration annotation to this field declaration and returns whether it was a new + * annotation. + * + * @param annotation declaration annotation to add + * @return true if {@code annotation} wasn't previously stored for this field declaration + */ + public boolean addDeclarationAnnotation(AnnotationMirror annotation) { + if (declarationAnnotations == null) { + declarationAnnotations = new AnnotationMirrorSet(); + } + + return declarationAnnotations.add(annotation); + } + + /** + * Transfers all annotations inferred by whole program inference on this field to the + * JavaParser nodes for that field. + */ + public void transferAnnotations() { + if (declarationAnnotations != null) { + // Don't add directly to the type of the variable declarator, + // because declaration annotations need to be attached to the FieldDeclaration + // node instead. + Node declParent = declaration.getParentNode().orElse(null); + if (declParent instanceof FieldDeclaration) { + FieldDeclaration decl = (FieldDeclaration) declParent; + for (AnnotationMirror annotation : declarationAnnotations) { + decl.addAnnotation( + AnnotationMirrorToAnnotationExprConversion + .annotationMirrorToAnnotationExpr(annotation)); + } + } + } + + // Don't transfer type annotations to variable declarators with sibling + // variable declarators, because they're printed incorrectly (as "???"). + // (A variable declarator can have siblings if it's part of a declaration + // like "int x, y, z;", which is bad style but legal Java.) + // In any event, WPI doesn't consider the LUB of the types of the siblings, + // so any inferred type is likely to be wrong. + // TODO: avoid inferring these types at all, or take the LUB of all assignments + // to the siblings. Unfortunately, VariableElements don't track whether they have + // siblings, and there's no other information about the declaration for + // WholeProgramInferenceImplementation to use: to determine that there are siblings, + // a parse tree is needed. + boolean foundVariableDeclarator = false; + for (Node child : this.declaration.getParentNode().get().getChildNodes()) { + if (child instanceof VariableDeclarator) { + if (foundVariableDeclarator) { + // This is the second VariableDeclarator that was found. + return; + } + foundVariableDeclarator = true; + } + } + Type newType = (Type) declaration.getType().accept(new CloneVisitor(), null); + WholeProgramInferenceJavaParserStorage.transferAnnotations(type, newType); + declaration.setType(newType); + } + + @Override + public String toString() { + return "FieldAnnos [declaration=" + declaration + ", type=" + type + "]"; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java index cbe362f65cd..d41961853ea 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenesStorage.java @@ -4,22 +4,7 @@ import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; -import java.io.File; -import java.io.IOException; -import java.lang.annotation.Target; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.afu.scenelib.Annotation; import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AClass; @@ -65,6 +50,24 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Target; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * This class stores annotations using scenelib objects. * @@ -81,940 +84,956 @@ * will be better. {@link #writeScenes} rewrites the initial .jaif files and may create new ones. */ public class WholeProgramInferenceScenesStorage - implements WholeProgramInferenceStorage { - - /** - * Directory where .jaif files will be written to and read from. This directory is relative to - * where the CF's javac command is executed. - */ - public static final String JAIF_FILES_PATH = - "build" + File.separator + "whole-program-inference" + File.separator; - - /** The type factory associated with this WholeProgramInferenceScenesStorage. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** Annotations that should not be output to a .jaif or stub file. */ - private final AnnotationsInContexts annosToIgnore = new AnnotationsInContexts(); - - /** - * If true, assignments where the rhs is null are be ignored. - * - *

          If all assignments to a variable are null (because inference is being done with respect to a - * limited set of uses) then the variable is inferred to have bottom type. That inference is - * unlikely to be correct. To avoid that inference, set this variable to true. When the variable - * is true, if all assignments are null, then none are recorded, no inference is done, and the - * variable remains at its default type. - */ - private final boolean ignoreNullAssignments; - - /** Maps .jaif file paths (Strings) to Scenes. Relative to JAIF_FILES_PATH. */ - public final Map scenes = new HashMap<>(); - - /** - * Scenes that were modified since the last time all Scenes were written into .jaif files. Each - * String element of this set is a path (relative to JAIF_FILES_PATH) to the .jaif file of the - * corresponding Scene in the set. It is obtained by passing a class name as argument to the - * {@link #getJaifPath} method. - * - *

          Modifying a Scene means adding (or changing) a type annotation for a field, method return - * type, or method parameter type in the Scene. (Scenes are modified by the method {@link - * #updateAnnotationSetInScene}.) - */ - public final Set modifiedScenes = new HashSet<>(); - - /** - * This map relates inferred preconditions to the declared types of the expressions to which the - * precondition applies. It is necessary to keep this map here because the AFU does not have a - * dependency on the CF itself, where AnnotatedTypeMirror exists. - * - *

          The keys are the concatenation of the string representation of the method signature as - * stored by {@link AMethod} to which the precondition applies and the expression to which the - * precondition applies. - */ - private final Map preconditionsToDeclaredTypes = new HashMap<>(); - - /** - * This map relates inferred postconditions to the declared types of the expressions to which the - * postcondition applies. It is necessary to keep this map here because the AFU does not have a - * dependency on the CF itself, where AnnotatedTypeMirror exists. - * - *

          The keys are the concatenation of the string representation of the method signature as - * stored by {@link AMethod} to which the postcondition applies and the expression to which the - * postcondition applies. - */ - private final Map postconditionsToDeclaredTypes = new HashMap<>(); - - /** - * Default constructor. - * - * @param atypeFactory the type factory associated with this WholeProgramInferenceScenesStorage - */ - public WholeProgramInferenceScenesStorage(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - boolean isNullness = - atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); - this.ignoreNullAssignments = !isNullness; - } - - @Override - public String getFileForElement(Element elt) { - String className; - switch (elt.getKind()) { - case CONSTRUCTOR: - case METHOD: - className = ElementUtils.getEnclosingClassName((ExecutableElement) elt); - break; - case LOCAL_VARIABLE: - className = getEnclosingClassName((LocalVariableNode) elt); - break; - case FIELD: - case ENUM_CONSTANT: - ClassSymbol enclosingClass = ((VarSymbol) elt).enclClass(); - className = enclosingClass.flatname.toString(); - break; - case CLASS: - className = ElementUtils.getBinaryName((TypeElement) elt); - break; - case PARAMETER: - className = ElementUtils.getEnclosingClassName((VariableElement) elt); - break; - default: - throw new BugInCF("What element? %s %s", elt.getKind(), elt); - } - String file = getJaifPath(className); - return file; - } - - /** - * Get the annotations for a class. - * - * @param className the name of the class, in binary form - * @param file the path to the file that represents the class - * @param classSymbol optionally, the ClassSymbol representing the class - * @return the annotations for the class - */ - private AClass getClassAnnos( - @BinaryName String className, String file, @Nullable ClassSymbol classSymbol) { - // Possibly reads .jaif file to obtain a Scene. - ASceneWrapper scene = getScene(file); - AClass aClass = scene.getAScene().classes.getVivify(className); - scene.updateSymbolInformation(aClass, classSymbol); - return aClass; - } - - /** - * Get the annotations for a method or constructor. - * - * @param methodElt the method or constructor - * @return the annotations for a method or constructor - */ - private AMethod getMethodAnnos(ExecutableElement methodElt) { - String className = ElementUtils.getEnclosingClassName(methodElt); - String file = getFileForElement(methodElt); - AClass classAnnos = getClassAnnos(className, file, ((MethodSymbol) methodElt).enclClass()); - AMethod methodAnnos = classAnnos.methods.getVivify(JVMNames.getJVMMethodSignature(methodElt)); - methodAnnos.setFieldsFromMethodElement(methodElt); - return methodAnnos; - } - - /** - * Get the annotations for a field. - * - * @param fieldElt the field - * @return the annotations for a field - */ - private AField getFieldAnnos(VariableElement fieldElt) { - String className = ElementUtils.getEnclosingClassName(fieldElt); - String file = getFileForElement(fieldElt); - AClass classAnnos = getClassAnnos(className, file, ((VarSymbol) fieldElt).enclClass()); - AField fieldAnnos = classAnnos.fields.getVivify(fieldElt.getSimpleName().toString()); - return fieldAnnos; - } - - @Override - public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { - // The only location that the scenes implementation cannot store an annotation is on - // a member of an annotation type, which it cannot distinguish from a normal interface's - // method. Without this, the scenes implementation will attempt to annotate annotation - // elements, which is an error. - Element enclosingType = ElementUtils.enclosingTypeElement(methodElt); - return enclosingType == null || enclosingType.getKind() != ElementKind.ANNOTATION_TYPE; - } - - @Override - public ATypeElement getParameterAnnotations( - ExecutableElement methodElt, - @Positive int index_1based, - AnnotatedTypeMirror paramATM, - VariableElement ve, - AnnotatedTypeFactory atypeFactory) { - if (index_1based == 0) { - throw new TypeSystemError("0 is illegal as index argument to getParameterAnnotations"); - } - AMethod methodAnnos = getMethodAnnos(methodElt); - AField param = - methodAnnos.vivifyAndAddTypeMirrorToParameter( - index_1based - 1, paramATM.getUnderlyingType(), ve.getSimpleName()); - return param.type; - } - - @Override - public ATypeElement getReceiverAnnotations( - ExecutableElement methodElt, - AnnotatedTypeMirror paramATM, - AnnotatedTypeFactory atypeFactory) { - AMethod methodAnnos = getMethodAnnos(methodElt); - return methodAnnos.receiver.type; - } - - @Override - public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt) { - AMethod methodAnnos = getMethodAnnos(elt); - Set annos = methodAnnos.tlAnnotationsHere; - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (Annotation anno : annos) { - result.add( - AnnotationConverter.annotationToAnnotationMirror(anno, atypeFactory.getProcessingEnv())); - } - return result; - } - - @Override - public boolean removeMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno) { - AMethod methodAnnos = getMethodAnnos(methodElt); - return methodAnnos.tlAnnotationsHere.remove( - AnnotationConverter.annotationMirrorToAnnotation(anno)); - } - - @Override - public ATypeElement getReturnAnnotations( - ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory) { - AMethod methodAnnos = getMethodAnnos(methodElt); - return methodAnnos.returnType; - } - - @Override - public ATypeElement getFieldAnnotations( - Element element, - String fieldName, - AnnotatedTypeMirror lhsATM, - AnnotatedTypeFactory atypeFactory) { - ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); - String file = getFileForElement(element); - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @BinaryName String className = enclosingClass.flatname.toString(); - AClass classAnnos = getClassAnnos(className, file, enclosingClass); - AField field = classAnnos.fields.getVivify(fieldName); - field.setTypeMirror(lhsATM.getUnderlyingType()); - return field.type; - } - - @Override - public ATypeElement getPreOrPostconditions( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory) { - switch (preOrPost) { - case BEFORE: - return getPreconditionsForExpression(methodElement, expression, declaredType); - case AFTER: - return getPostconditionsForExpression(methodElement, expression, declaredType); - default: - throw new BugInCF("Unexpected " + preOrPost); - } - } - - /** - * Returns the precondition annotations for a Java expression. - * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @return the precondition annotations for a Java expression - */ - private ATypeElement getPreconditionsForExpression( - ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { - AMethod methodAnnos = getMethodAnnos(methodElement); - preconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); - return methodAnnos.vivifyAndAddTypeMirrorToPrecondition( - expression, declaredType.getUnderlyingType()) - .type; - } - - /** - * Returns the postcondition annotations for a Java expression. - * - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @return the postcondition annotations for a Java expression - */ - private ATypeElement getPostconditionsForExpression( - ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { - AMethod methodAnnos = getMethodAnnos(methodElement); - postconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); - return methodAnnos.vivifyAndAddTypeMirrorToPostcondition( - expression, declaredType.getUnderlyingType()) - .type; - } - - /** - * Fetches the declared type of an expression for which a precondition was inferred, for the given - * AMethod. - * - * @param m a method - * @param expression the expression - * @return the declared type - */ - public AnnotatedTypeMirror getPreconditionDeclaredType(AMethod m, String expression) { - String key = m.methodSignature + expression; - if (!preconditionsToDeclaredTypes.containsKey(key)) { - throw new BugInCF( - "attempted to retrieve the declared type of a precondition expression for which" - + "nothing was inferred: " - + key); - } - return preconditionsToDeclaredTypes.get(key); - } - - /** - * Fetches the declared type of an expression for which a postcondition was inferred, for the - * given AMethod. - * - * @param m a method - * @param expression the expression - * @return the declared type - */ - public AnnotatedTypeMirror getPostconditionDeclaredType(AMethod m, String expression) { - String key = m.methodSignature + expression; - if (!postconditionsToDeclaredTypes.containsKey(key)) { - throw new BugInCF( - "attempted to retrieve the declared type of a postcondition expression for which" - + "nothing was inferred: " - + key); - } - return postconditionsToDeclaredTypes.get(key); - } - - @Override - public boolean addMethodDeclarationAnnotation( - ExecutableElement methodElt, AnnotationMirror anno) { - - // Do not infer types for library code, only for type-checked source code. - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return false; - } - - AMethod methodAnnos = getMethodAnnos(methodElt); - - org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = - AnnotationConverter.annotationMirrorToAnnotation(anno); - boolean isNewAnnotation = methodAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - @Override - public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(field)) { - return false; - } - - AField fieldAnnos = getFieldAnnos(field); - - org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = - AnnotationConverter.annotationMirrorToAnnotation(anno); - - boolean isNewAnnotation = fieldAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - @Override - public boolean addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { - if (index_1based == 0) { - throw new TypeSystemError( - "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); - } - if (!ElementUtils.isElementFromSourceCode(methodElt)) { - return false; - } - - VariableElement paramElt = methodElt.getParameters().get(index_1based - 1); - AnnotatedTypeMirror paramAType = atypeFactory.getAnnotatedType(paramElt); - ATypeElement paramAnnos = - getParameterAnnotations(methodElt, index_1based, paramAType, paramElt, atypeFactory); - Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); - - boolean isNewAnnotation = paramAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - @Override - public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { - if (!ElementUtils.isElementFromSourceCode(classElt)) { - return false; - } - - AClass classAnnos = - getClassAnnos( - ElementUtils.getBinaryName(classElt), - getFileForElement(classElt), - (ClassSymbol) classElt); - - Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); - - boolean isNewAnnotation = classAnnos.tlAnnotationsHere.add(sceneAnno); - return isNewAnnotation; - } - - /** - * Write all modified scenes into files. (Scenes are modified by the method {@link - * #updateAnnotationSetInScene}.) - * - * @param outputFormat the output format to use when writing files - * @param checker the checker from which this method is called, for naming stub files - */ - public void writeScenes(OutputFormat outputFormat, BaseTypeChecker checker) { - // Create WPI directory if it doesn't exist already. - File jaifDir = new File(JAIF_FILES_PATH); - if (!jaifDir.exists()) { - jaifDir.mkdirs(); - } - // Write scenes into files. - for (String jaifPath : modifiedScenes) { - scenes.get(jaifPath).writeToFile(jaifPath, annosToIgnore, outputFormat, checker); - } - modifiedScenes.clear(); - } - - /** - * Returns the String representing the .jaif path of a class given its name. - * - * @param className the simple name of a class - * @return the path to the .jaif file - */ - protected String getJaifPath(String className) { - String jaifPath = JAIF_FILES_PATH + className + ".jaif"; - return jaifPath; - } - - /** - * Reads a Scene from the given .jaif file, or returns an empty Scene if the file does not exist. - * - * @param jaifPath the .jaif file - * @return the Scene read from the file, or an empty Scene if the file does not exist - */ - private ASceneWrapper getScene(String jaifPath) { - AScene scene; - if (!scenes.containsKey(jaifPath)) { - File jaifFile = new File(jaifPath); - scene = new AScene(); - if (jaifFile.exists()) { - try { - IndexFileParser.parseFile(jaifPath, scene); - } catch (IOException e) { - throw new UserError("Problem while reading %s: %s", jaifPath, e.getMessage()); - } - } - ASceneWrapper wrapper = new ASceneWrapper(scene); - scenes.put(jaifPath, wrapper); - return wrapper; - } else { - return scenes.get(jaifPath); - } - } - - /** - * Returns the scene-lib representation of the given className in the scene identified by the - * given jaifPath. - * - * @param className the name of the class to get, in binary form - * @param jaifPath the path to the jaif file that would represent that class (must end in ".jaif") - * @param classSymbol optionally, the ClassSymbol representing the class. Used to set the symbol - * information stored on an AClass. - * @return a version of the scene-lib representation of the class, augmented with symbol - * information if {@code classSymbol} was non-null - */ - protected AClass getAClass( - @BinaryName String className, String jaifPath, @Nullable ClassSymbol classSymbol) { - // Possibly reads .jaif file to obtain a Scene. - ASceneWrapper scene = getScene(jaifPath); - AClass aClass = scene.getAScene().classes.getVivify(className); - scene.updateSymbolInformation(aClass, classSymbol); - return aClass; - } - - /** - * Returns the scene-lib representation of the given className in the scene identified by the - * given jaifPath. - * - * @param className the name of the class to get, in binary form - * @param jaifPath the path to the jaif file that would represent that class (must end in ".jaif") - * @return the scene-lib representation of the class, possibly augmented with symbol information - * if {@link #getAClass(String, String, com.sun.tools.javac.code.Symbol.ClassSymbol)} has - * already been called with a non-null third argument - */ - protected AClass getAClass(@BinaryName String className, String jaifPath) { - return getAClass(className, jaifPath, null); - } - - /** - * Updates the set of annotations in a location of a Scene, as the result of a pseudo-assignment. - * - *

            - *
          • If there was no previous annotation for that location, then the updated set will be the - * annotations in rhsATM. - *
          • If there was a previous annotation, the updated set will be the LUB between the previous - * annotation and rhsATM. - *
          - * - * @param type the ATypeElement of the Scene which will be modified - * @param jaifPath path to a .jaif file for a Scene; used for marking the scene as modified - * (needing to be written to disk) - * @param rhsATM the RHS of the annotated type on the source code - * @param lhsATM the LHS of the annotated type on the source code - * @param defLoc the location where the annotation will be added - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - protected void updateAnnotationSetInScene( - ATypeElement type, - TypeUseLocation defLoc, - AnnotatedTypeMirror rhsATM, - AnnotatedTypeMirror lhsATM, - String jaifPath, - boolean ignoreIfAnnotated) { - if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { - return; - } - TypeMirror rhsTM = rhsATM.getUnderlyingType(); - AnnotatedTypeMirror atmFromScene = atmFromStorageLocation(rhsTM, type); - updateAtmWithLub(rhsATM, atmFromScene); - if (lhsATM instanceof AnnotatedTypeVariable) { - AnnotationMirrorSet upperAnnos = - ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations(); - // If the inferred type is a subtype of the upper bounds of the - // current type on the source code, halt. - if (upperAnnos.size() == rhsATM.getAnnotations().size() - && atypeFactory - .getQualifierHierarchy() - .isSubtypeShallow( - rhsATM.getAnnotations(), rhsTM, upperAnnos, lhsATM.getUnderlyingType())) { - return; - } - } - updateTypeElementFromATM(type, defLoc, rhsATM, lhsATM, ignoreIfAnnotated); - modifiedScenes.add(jaifPath); - } - - /** - * Updates sourceCodeATM to contain the LUB between sourceCodeATM and jaifATM, ignoring missing - * AnnotationMirrors from jaifATM -- it considers the LUB between an AnnotationMirror am and a - * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. - * - * @param sourceCodeATM the annotated type on the source code - * @param jaifATM the annotated type on the .jaif file - */ - private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror jaifATM) { - - switch (sourceCodeATM.getKind()) { - case TYPEVAR: - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), - ((AnnotatedTypeVariable) jaifATM).getLowerBound()); - updateAtmWithLub( - ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), - ((AnnotatedTypeVariable) jaifATM).getUpperBound()); - break; - // case WILDCARD: - // Because inferring type arguments is not supported, wildcards won't be encoutered - // updateAtmWithLub(((AnnotatedWildcardType) - // sourceCodeATM).getExtendsBound(), - // ((AnnotatedWildcardType) - // jaifATM).getExtendsBound()); - // updateAtmWithLub(((AnnotatedWildcardType) - // sourceCodeATM).getSuperBound(), - // ((AnnotatedWildcardType) jaifATM).getSuperBound()); - // break; - case ARRAY: - updateAtmWithLub( - ((AnnotatedArrayType) sourceCodeATM).getComponentType(), - ((AnnotatedArrayType) jaifATM).getComponentType()); - break; - // case DECLARED: - // inferring annotations on type arguments is not supported, so no need to recur on - // generic types. If this was every implemented, this method would need VisitHistory - // object to prevent infinite recursion on types such as T extends List. - default: - // ATM only has primary annotations - break; - } - - // LUB primary annotations - AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); - for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { - AnnotationMirror amJaif = jaifATM.getAnnotationInHierarchy(amSource); - // amJaif only contains annotations from the jaif, so it might be missing - // an annotation in the hierarchy - if (amJaif != null) { - amSource = - atypeFactory - .getQualifierHierarchy() - .leastUpperBoundShallow( - amSource, - sourceCodeATM.getUnderlyingType(), - amJaif, - jaifATM.getUnderlyingType()); - } - annosToReplace.add(amSource); - } - sourceCodeATM.replaceAnnotations(annosToReplace); - } - - /** - * Returns true if {@code am} should not be inserted in source code, for example {@link - * org.checkerframework.common.value.qual.BottomVal}. This happens when {@code am} cannot be - * inserted in source code or is the default for the location passed as argument. - * - *

          Invisible qualifiers, which are annotations that contain the {@link - * org.checkerframework.framework.qual.InvisibleQualifier} meta-annotation, also return true. - * - *

          TODO: Merge functionality somewhere else with {@link - * org.checkerframework.framework.util.defaults.QualifierDefaults}. Look into the - * createQualifierDefaults method in {@link GenericAnnotatedTypeFactory} (which uses the - * QualifierDefaults class linked above) before changing anything here. See - * https://github.com/typetools/checker-framework/issues/683 . - * - * @param am an annotation to test for whether it should be inserted into source code - * @param location where the location would be inserted; used to determine if {@code am} is the - * default for that location - * @param atm its kind is used to determine if {@code am} is the default for that kind - * @return true if am should not be inserted into source code, or if am is invisible - */ - private boolean shouldIgnore( - AnnotationMirror am, TypeUseLocation location, AnnotatedTypeMirror atm) { - Element elt = am.getAnnotationType().asElement(); - // Checks if am is an implementation detail (a type qualifier used - // internally by the type system and not meant to be seen by the user). - Target target = elt.getAnnotation(Target.class); - if (target != null && target.value().length == 0) { - return true; - } - if (elt.getAnnotation(InvisibleQualifier.class) != null) { - return true; - } - - // Checks if am is default - if (elt.getAnnotation(DefaultQualifierInHierarchy.class) != null) { - return true; - } - DefaultQualifier defaultQual = elt.getAnnotation(DefaultQualifier.class); - if (defaultQual != null) { - for (TypeUseLocation loc : defaultQual.locations()) { - if (loc == TypeUseLocation.ALL || loc == location) { - return true; - } - } - } - DefaultFor defaultQualForLocation = elt.getAnnotation(DefaultFor.class); - if (defaultQualForLocation != null) { - for (TypeUseLocation loc : defaultQualForLocation.value()) { - if (loc == TypeUseLocation.ALL || loc == location) { - return true; - } - } - } - - // Checks if am is a default annotation. - // This case checks if it is meta-annotated with @DefaultFor. - // TODO: Handle cases of annotations added via an - // org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator. - DefaultFor defaultFor = elt.getAnnotation(DefaultFor.class); - if (defaultFor != null) { - org.checkerframework.framework.qual.TypeKind[] types = defaultFor.typeKinds(); - TypeKind atmKind = atm.getUnderlyingType().getKind(); - if (hasMatchingTypeKind(atmKind, types)) { - return true; - } - } - - return false; - } - - /** Returns true, iff a matching TypeKind is found. */ - private boolean hasMatchingTypeKind( - TypeKind atmKind, org.checkerframework.framework.qual.TypeKind[] types) { - for (org.checkerframework.framework.qual.TypeKind tk : types) { - if (tk.name().equals(atmKind.name())) { - return true; - } - } - return false; - } - - /** - * Returns a subset of annosSet, consisting of the annotations supported by the type factory - * associated with this. These are not necessarily legal annotations: they have the right name, - * but they may lack elements (fields). - * - * @param annosSet a set of annotations - * @return the annoattions supported by this object's AnnotatedTypeFactory - */ - private Set getSupportedAnnosInSet(Set annosSet) { - Set output = new HashSet<>(1); - Set> supportedAnnos = - atypeFactory.getSupportedTypeQualifiers(); - for (Annotation anno : annosSet) { - for (Class clazz : supportedAnnos) { - // TODO: Remove comparison by name, and make this routine more efficient. - if (clazz.getName().equals(anno.def.name)) { - output.add(anno); - } - } - } - return output; - } - - @Override - public AnnotatedTypeMirror atmFromStorageLocation( - TypeMirror typeMirror, ATypeElement storageLocation) { - AnnotatedTypeMirror result = AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); - updateAtmFromATypeElement(result, storageLocation); - return result; - } - - /** - * Updates an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} to contain the - * {@link org.checkerframework.afu.scenelib.annotations.Annotation}s of an {@link - * org.checkerframework.afu.scenelib.annotations.el.ATypeElement}. - * - * @param result the AnnotatedTypeMirror to be modified - * @param storageLocation the {@link - * org.checkerframework.afu.scenelib.annotations.el.ATypeElement} used - */ - private void updateAtmFromATypeElement(AnnotatedTypeMirror result, ATypeElement storageLocation) { - Set annos = getSupportedAnnosInSet(storageLocation.tlAnnotationsHere); - for (Annotation anno : annos) { - AnnotationMirror am = - AnnotationConverter.annotationToAnnotationMirror(anno, atypeFactory.getProcessingEnv()); - result.addAnnotation(am); - } - if (result.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aat = (AnnotatedArrayType) result; - for (ATypeElement innerType : storageLocation.innerTypes.values()) { - updateAtmFromATypeElement(aat.getComponentType(), innerType); - } - } - if (result.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) result; - for (ATypeElement innerType : storageLocation.innerTypes.values()) { - updateAtmFromATypeElement(atv.getUpperBound(), innerType); - } - } - } - - @Override - public void updateStorageLocationFromAtm( - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - ATypeElement typeToUpdate, - TypeUseLocation defLoc, - boolean ignoreIfAnnotated) { - updateTypeElementFromATM(typeToUpdate, defLoc, newATM, curATM, ignoreIfAnnotated); - } - - /// - /// Writing to a file - /// - - // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because - // a scene may be modifed and written at any time, including before or after - // postProcessClassTree is called. - - /** - * Side-effects the compilation unit annotations to make any desired changes before writing to a - * file. - * - * @param compilationUnitAnnos the compilation unit annotations to modify - */ - public void prepareSceneForWriting(AScene compilationUnitAnnos) { - for (Map.Entry classEntry : compilationUnitAnnos.classes.entrySet()) { - wpiPrepareClassForWriting(classEntry.getValue()); - } - } - - /** - * Side-effects the class annotations to make any desired changes before writing to a file. - * - * @param classAnnos the class annotations to modify - */ - public void wpiPrepareClassForWriting(AClass classAnnos) { - for (Map.Entry methodEntry : classAnnos.methods.entrySet()) { - wpiPrepareMethodForWriting(methodEntry.getValue()); - } - } - - /** - * Side-effects the method or constructor annotations to make any desired changes before writing - * to a file. - * - * @param methodAnnos the method or constructor annotations to modify - */ - public void wpiPrepareMethodForWriting(AMethod methodAnnos) { - atypeFactory.wpiPrepareMethodForWriting(methodAnnos); - } - - @Override - public void writeResultsToFile( - WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker) { - if (outputFormat == OutputFormat.AJAVA) { - throw new BugInCF("WholeProgramInferenceScenes used with format " + outputFormat); - } - - for (String file : modifiedScenes) { - ASceneWrapper scene = scenes.get(file); - prepareSceneForWriting(scene.getAScene()); - } - - writeScenes(outputFormat, checker); - } - - @Override - public void setFileModified(String path) { - modifiedScenes.add(path); - } - - @Override - public void preprocessClassTree(ClassTree classTree) { - // This implementation does nothing. - } - - /** - * Updates an {@link org.checkerframework.afu.scenelib.annotations.el.ATypeElement} to have the - * annotations of an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} passed as - * argument. Annotations in the original set that should be ignored (see {@link #shouldIgnore}) - * are not added to the resulting set. This method also checks if the AnnotatedTypeMirror has - * explicit annotations in source code, and if that is the case no annotations are added for that - * location. - * - *

          This method removes from the ATypeElement all annotations supported by this object's - * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is - * called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. Therefore, it - * is not a problem to remove all annotations before inserting the new annotations. - * - * @param typeToUpdate the ATypeElement that will be updated - * @param defLoc the location where the annotation will be added - * @param newATM the AnnotatedTypeMirror whose annotations will be added to the ATypeElement - * @param curATM used to check if the element which will be updated has explicit annotations in - * source code - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - private void updateTypeElementFromATM( - ATypeElement typeToUpdate, - TypeUseLocation defLoc, - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - boolean ignoreIfAnnotated) { - // Clears only the annotations that are supported by the relevant AnnotatedTypeFactory. - // The others stay intact. - Set annosToRemove = getSupportedAnnosInSet(typeToUpdate.tlAnnotationsHere); - // This method may be called consecutive times for the same ATypeElement. Each time it is - // called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. - // Therefore, it is not a problem to remove all annotations before inserting the new - // annotations. - typeToUpdate.tlAnnotationsHere.removeAll(annosToRemove); - - // Only update the ATypeElement if there are no explicit annotations. - if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { - for (AnnotationMirror am : newATM.getAnnotations()) { - addAnnotationsToATypeElement( - newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); - } - } else if (curATM.getKind() == TypeKind.TYPEVAR) { - // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly - // annotated. So instead, only insert the annotation if there is not primary annotation - // of the same hierarchy. #shouldIgnore prevent annotations that are subtypes of type - // vars upper bound from being inserted. - for (AnnotationMirror am : newATM.getAnnotations()) { - if (curATM.getAnnotationInHierarchy(am) != null) { - // Don't insert if the type is already has a primary annotation - // in the same hierarchy. - break; - } - addAnnotationsToATypeElement( - newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); - } - } - - // Recursively update compound type and type variable type if they exist. - if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; - AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; - updateTypeElementFromATM( - typeToUpdate.innerTypes.getVivify( - TypePathEntry.getTypePathEntryListFromBinary(Collections.nCopies(2, 0))), - defLoc, - newAAT.getComponentType(), - oldAAT.getComponentType(), - ignoreIfAnnotated); - } - } - - private void addAnnotationsToATypeElement( - AnnotatedTypeMirror newATM, - ATypeElement typeToUpdate, - TypeUseLocation defLoc, - AnnotationMirror am, - boolean isEffectiveAnnotation) { - Annotation anno = AnnotationConverter.annotationMirrorToAnnotation(am); - typeToUpdate.tlAnnotationsHere.add(anno); - if (isEffectiveAnnotation || shouldIgnore(am, defLoc, newATM)) { - // firstKey works as a unique identifier for each annotation - // that should not be inserted in source code - String firstKey = aTypeElementToString(typeToUpdate); - IPair key = IPair.of(firstKey, defLoc); - Set annosIgnored = annosToIgnore.get(key); - if (annosIgnored == null) { - annosIgnored = new HashSet<>(CollectionsPlume.mapCapacity(1)); - annosToIgnore.put(key, annosIgnored); - } - annosIgnored.add(anno.def().toString()); - } - } - - /** - * Returns a string representation of an ATypeElement, for use as part of a key in {@link - * AnnotationsInContexts}. - * - * @param aType an ATypeElement to convert to a string representation - * @return a string representation of the argument - */ - public static String aTypeElementToString(ATypeElement aType) { - // return aType.description.toString() + aType.tlAnnotationsHere; - return aType.description.toString(); - } - - /** - * Maps the {@link #aTypeElementToString} representation of an ATypeElement and its - * TypeUseLocation to a set of names of annotations. - */ - public static class AnnotationsInContexts - extends HashMap, Set> { - private static final long serialVersionUID = 20200321L; - } - - /** - * Returns the "flatname" of the class enclosing {@code localVariableNode}. - * - * @param localVariableNode the {@link LocalVariableNode} - * @return the "flatname" of the class enclosing {@code localVariableNode} - */ - private static @BinaryName String getEnclosingClassName(LocalVariableNode localVariableNode) { - return ElementUtils.getBinaryName( - ElementUtils.enclosingTypeElement(localVariableNode.getElement())); - } + implements WholeProgramInferenceStorage { + + /** + * Directory where .jaif files will be written to and read from. This directory is relative to + * where the CF's javac command is executed. + */ + public static final String JAIF_FILES_PATH = + "build" + File.separator + "whole-program-inference" + File.separator; + + /** The type factory associated with this WholeProgramInferenceScenesStorage. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** Annotations that should not be output to a .jaif or stub file. */ + private final AnnotationsInContexts annosToIgnore = new AnnotationsInContexts(); + + /** + * If true, assignments where the rhs is null are be ignored. + * + *

          If all assignments to a variable are null (because inference is being done with respect to + * a limited set of uses) then the variable is inferred to have bottom type. That inference is + * unlikely to be correct. To avoid that inference, set this variable to true. When the variable + * is true, if all assignments are null, then none are recorded, no inference is done, and the + * variable remains at its default type. + */ + private final boolean ignoreNullAssignments; + + /** Maps .jaif file paths (Strings) to Scenes. Relative to JAIF_FILES_PATH. */ + public final Map scenes = new HashMap<>(); + + /** + * Scenes that were modified since the last time all Scenes were written into .jaif files. Each + * String element of this set is a path (relative to JAIF_FILES_PATH) to the .jaif file of the + * corresponding Scene in the set. It is obtained by passing a class name as argument to the + * {@link #getJaifPath} method. + * + *

          Modifying a Scene means adding (or changing) a type annotation for a field, method return + * type, or method parameter type in the Scene. (Scenes are modified by the method {@link + * #updateAnnotationSetInScene}.) + */ + public final Set modifiedScenes = new HashSet<>(); + + /** + * This map relates inferred preconditions to the declared types of the expressions to which the + * precondition applies. It is necessary to keep this map here because the AFU does not have a + * dependency on the CF itself, where AnnotatedTypeMirror exists. + * + *

          The keys are the concatenation of the string representation of the method signature as + * stored by {@link AMethod} to which the precondition applies and the expression to which the + * precondition applies. + */ + private final Map preconditionsToDeclaredTypes = new HashMap<>(); + + /** + * This map relates inferred postconditions to the declared types of the expressions to which + * the postcondition applies. It is necessary to keep this map here because the AFU does not + * have a dependency on the CF itself, where AnnotatedTypeMirror exists. + * + *

          The keys are the concatenation of the string representation of the method signature as + * stored by {@link AMethod} to which the postcondition applies and the expression to which the + * postcondition applies. + */ + private final Map postconditionsToDeclaredTypes = new HashMap<>(); + + /** + * Default constructor. + * + * @param atypeFactory the type factory associated with this WholeProgramInferenceScenesStorage + */ + public WholeProgramInferenceScenesStorage(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + boolean isNullness = + atypeFactory.getClass().getSimpleName().equals("NullnessAnnotatedTypeFactory"); + this.ignoreNullAssignments = !isNullness; + } + + @Override + public String getFileForElement(Element elt) { + String className; + switch (elt.getKind()) { + case CONSTRUCTOR: + case METHOD: + className = ElementUtils.getEnclosingClassName((ExecutableElement) elt); + break; + case LOCAL_VARIABLE: + className = getEnclosingClassName((LocalVariableNode) elt); + break; + case FIELD: + case ENUM_CONSTANT: + ClassSymbol enclosingClass = ((VarSymbol) elt).enclClass(); + className = enclosingClass.flatname.toString(); + break; + case CLASS: + className = ElementUtils.getBinaryName((TypeElement) elt); + break; + case PARAMETER: + className = ElementUtils.getEnclosingClassName((VariableElement) elt); + break; + default: + throw new BugInCF("What element? %s %s", elt.getKind(), elt); + } + String file = getJaifPath(className); + return file; + } + + /** + * Get the annotations for a class. + * + * @param className the name of the class, in binary form + * @param file the path to the file that represents the class + * @param classSymbol optionally, the ClassSymbol representing the class + * @return the annotations for the class + */ + private AClass getClassAnnos( + @BinaryName String className, String file, @Nullable ClassSymbol classSymbol) { + // Possibly reads .jaif file to obtain a Scene. + ASceneWrapper scene = getScene(file); + AClass aClass = scene.getAScene().classes.getVivify(className); + scene.updateSymbolInformation(aClass, classSymbol); + return aClass; + } + + /** + * Get the annotations for a method or constructor. + * + * @param methodElt the method or constructor + * @return the annotations for a method or constructor + */ + private AMethod getMethodAnnos(ExecutableElement methodElt) { + String className = ElementUtils.getEnclosingClassName(methodElt); + String file = getFileForElement(methodElt); + AClass classAnnos = getClassAnnos(className, file, ((MethodSymbol) methodElt).enclClass()); + AMethod methodAnnos = + classAnnos.methods.getVivify(JVMNames.getJVMMethodSignature(methodElt)); + methodAnnos.setFieldsFromMethodElement(methodElt); + return methodAnnos; + } + + /** + * Get the annotations for a field. + * + * @param fieldElt the field + * @return the annotations for a field + */ + private AField getFieldAnnos(VariableElement fieldElt) { + String className = ElementUtils.getEnclosingClassName(fieldElt); + String file = getFileForElement(fieldElt); + AClass classAnnos = getClassAnnos(className, file, ((VarSymbol) fieldElt).enclClass()); + AField fieldAnnos = classAnnos.fields.getVivify(fieldElt.getSimpleName().toString()); + return fieldAnnos; + } + + @Override + public boolean hasStorageLocationForMethod(ExecutableElement methodElt) { + // The only location that the scenes implementation cannot store an annotation is on + // a member of an annotation type, which it cannot distinguish from a normal interface's + // method. Without this, the scenes implementation will attempt to annotate annotation + // elements, which is an error. + Element enclosingType = ElementUtils.enclosingTypeElement(methodElt); + return enclosingType == null || enclosingType.getKind() != ElementKind.ANNOTATION_TYPE; + } + + @Override + public ATypeElement getParameterAnnotations( + ExecutableElement methodElt, + @Positive int index_1based, + AnnotatedTypeMirror paramATM, + VariableElement ve, + AnnotatedTypeFactory atypeFactory) { + if (index_1based == 0) { + throw new TypeSystemError("0 is illegal as index argument to getParameterAnnotations"); + } + AMethod methodAnnos = getMethodAnnos(methodElt); + AField param = + methodAnnos.vivifyAndAddTypeMirrorToParameter( + index_1based - 1, paramATM.getUnderlyingType(), ve.getSimpleName()); + return param.type; + } + + @Override + public ATypeElement getReceiverAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror paramATM, + AnnotatedTypeFactory atypeFactory) { + AMethod methodAnnos = getMethodAnnos(methodElt); + return methodAnnos.receiver.type; + } + + @Override + public AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt) { + AMethod methodAnnos = getMethodAnnos(elt); + Set annos = methodAnnos.tlAnnotationsHere; + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (Annotation anno : annos) { + result.add( + AnnotationConverter.annotationToAnnotationMirror( + anno, atypeFactory.getProcessingEnv())); + } + return result; + } + + @Override + public boolean removeMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno) { + AMethod methodAnnos = getMethodAnnos(methodElt); + return methodAnnos.tlAnnotationsHere.remove( + AnnotationConverter.annotationMirrorToAnnotation(anno)); + } + + @Override + public ATypeElement getReturnAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror atm, + AnnotatedTypeFactory atypeFactory) { + AMethod methodAnnos = getMethodAnnos(methodElt); + return methodAnnos.returnType; + } + + @Override + public ATypeElement getFieldAnnotations( + Element element, + String fieldName, + AnnotatedTypeMirror lhsATM, + AnnotatedTypeFactory atypeFactory) { + ClassSymbol enclosingClass = ((VarSymbol) element).enclClass(); + String file = getFileForElement(element); + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @BinaryName String className = enclosingClass.flatname.toString(); + AClass classAnnos = getClassAnnos(className, file, enclosingClass); + AField field = classAnnos.fields.getVivify(fieldName); + field.setTypeMirror(lhsATM.getUnderlyingType()); + return field.type; + } + + @Override + public ATypeElement getPreOrPostconditions( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory) { + switch (preOrPost) { + case BEFORE: + return getPreconditionsForExpression(methodElement, expression, declaredType); + case AFTER: + return getPostconditionsForExpression(methodElement, expression, declaredType); + default: + throw new BugInCF("Unexpected " + preOrPost); + } + } + + /** + * Returns the precondition annotations for a Java expression. + * + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @return the precondition annotations for a Java expression + */ + private ATypeElement getPreconditionsForExpression( + ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { + AMethod methodAnnos = getMethodAnnos(methodElement); + preconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); + return methodAnnos.vivifyAndAddTypeMirrorToPrecondition( + expression, declaredType.getUnderlyingType()) + .type; + } + + /** + * Returns the postcondition annotations for a Java expression. + * + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @return the postcondition annotations for a Java expression + */ + private ATypeElement getPostconditionsForExpression( + ExecutableElement methodElement, String expression, AnnotatedTypeMirror declaredType) { + AMethod methodAnnos = getMethodAnnos(methodElement); + postconditionsToDeclaredTypes.put(methodAnnos.methodSignature + expression, declaredType); + return methodAnnos.vivifyAndAddTypeMirrorToPostcondition( + expression, declaredType.getUnderlyingType()) + .type; + } + + /** + * Fetches the declared type of an expression for which a precondition was inferred, for the + * given AMethod. + * + * @param m a method + * @param expression the expression + * @return the declared type + */ + public AnnotatedTypeMirror getPreconditionDeclaredType(AMethod m, String expression) { + String key = m.methodSignature + expression; + if (!preconditionsToDeclaredTypes.containsKey(key)) { + throw new BugInCF( + "attempted to retrieve the declared type of a precondition expression for which" + + "nothing was inferred: " + + key); + } + return preconditionsToDeclaredTypes.get(key); + } + + /** + * Fetches the declared type of an expression for which a postcondition was inferred, for the + * given AMethod. + * + * @param m a method + * @param expression the expression + * @return the declared type + */ + public AnnotatedTypeMirror getPostconditionDeclaredType(AMethod m, String expression) { + String key = m.methodSignature + expression; + if (!postconditionsToDeclaredTypes.containsKey(key)) { + throw new BugInCF( + "attempted to retrieve the declared type of a postcondition expression for which" + + "nothing was inferred: " + + key); + } + return postconditionsToDeclaredTypes.get(key); + } + + @Override + public boolean addMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno) { + + // Do not infer types for library code, only for type-checked source code. + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return false; + } + + AMethod methodAnnos = getMethodAnnos(methodElt); + + org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = + AnnotationConverter.annotationMirrorToAnnotation(anno); + boolean isNewAnnotation = methodAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + @Override + public boolean addFieldDeclarationAnnotation(VariableElement field, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(field)) { + return false; + } + + AField fieldAnnos = getFieldAnnos(field); + + org.checkerframework.afu.scenelib.annotations.Annotation sceneAnno = + AnnotationConverter.annotationMirrorToAnnotation(anno); + + boolean isNewAnnotation = fieldAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + @Override + public boolean addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno) { + if (index_1based == 0) { + throw new TypeSystemError( + "0 is illegal as index argument to addDeclarationAnnotationToFormalParameter"); + } + if (!ElementUtils.isElementFromSourceCode(methodElt)) { + return false; + } + + VariableElement paramElt = methodElt.getParameters().get(index_1based - 1); + AnnotatedTypeMirror paramAType = atypeFactory.getAnnotatedType(paramElt); + ATypeElement paramAnnos = + getParameterAnnotations( + methodElt, index_1based, paramAType, paramElt, atypeFactory); + Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); + + boolean isNewAnnotation = paramAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + @Override + public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno) { + if (!ElementUtils.isElementFromSourceCode(classElt)) { + return false; + } + + AClass classAnnos = + getClassAnnos( + ElementUtils.getBinaryName(classElt), + getFileForElement(classElt), + (ClassSymbol) classElt); + + Annotation sceneAnno = AnnotationConverter.annotationMirrorToAnnotation(anno); + + boolean isNewAnnotation = classAnnos.tlAnnotationsHere.add(sceneAnno); + return isNewAnnotation; + } + + /** + * Write all modified scenes into files. (Scenes are modified by the method {@link + * #updateAnnotationSetInScene}.) + * + * @param outputFormat the output format to use when writing files + * @param checker the checker from which this method is called, for naming stub files + */ + public void writeScenes(OutputFormat outputFormat, BaseTypeChecker checker) { + // Create WPI directory if it doesn't exist already. + File jaifDir = new File(JAIF_FILES_PATH); + if (!jaifDir.exists()) { + jaifDir.mkdirs(); + } + // Write scenes into files. + for (String jaifPath : modifiedScenes) { + scenes.get(jaifPath).writeToFile(jaifPath, annosToIgnore, outputFormat, checker); + } + modifiedScenes.clear(); + } + + /** + * Returns the String representing the .jaif path of a class given its name. + * + * @param className the simple name of a class + * @return the path to the .jaif file + */ + protected String getJaifPath(String className) { + String jaifPath = JAIF_FILES_PATH + className + ".jaif"; + return jaifPath; + } + + /** + * Reads a Scene from the given .jaif file, or returns an empty Scene if the file does not + * exist. + * + * @param jaifPath the .jaif file + * @return the Scene read from the file, or an empty Scene if the file does not exist + */ + private ASceneWrapper getScene(String jaifPath) { + AScene scene; + if (!scenes.containsKey(jaifPath)) { + File jaifFile = new File(jaifPath); + scene = new AScene(); + if (jaifFile.exists()) { + try { + IndexFileParser.parseFile(jaifPath, scene); + } catch (IOException e) { + throw new UserError("Problem while reading %s: %s", jaifPath, e.getMessage()); + } + } + ASceneWrapper wrapper = new ASceneWrapper(scene); + scenes.put(jaifPath, wrapper); + return wrapper; + } else { + return scenes.get(jaifPath); + } + } + + /** + * Returns the scene-lib representation of the given className in the scene identified by the + * given jaifPath. + * + * @param className the name of the class to get, in binary form + * @param jaifPath the path to the jaif file that would represent that class (must end in + * ".jaif") + * @param classSymbol optionally, the ClassSymbol representing the class. Used to set the symbol + * information stored on an AClass. + * @return a version of the scene-lib representation of the class, augmented with symbol + * information if {@code classSymbol} was non-null + */ + protected AClass getAClass( + @BinaryName String className, String jaifPath, @Nullable ClassSymbol classSymbol) { + // Possibly reads .jaif file to obtain a Scene. + ASceneWrapper scene = getScene(jaifPath); + AClass aClass = scene.getAScene().classes.getVivify(className); + scene.updateSymbolInformation(aClass, classSymbol); + return aClass; + } + + /** + * Returns the scene-lib representation of the given className in the scene identified by the + * given jaifPath. + * + * @param className the name of the class to get, in binary form + * @param jaifPath the path to the jaif file that would represent that class (must end in + * ".jaif") + * @return the scene-lib representation of the class, possibly augmented with symbol information + * if {@link #getAClass(String, String, com.sun.tools.javac.code.Symbol.ClassSymbol)} has + * already been called with a non-null third argument + */ + protected AClass getAClass(@BinaryName String className, String jaifPath) { + return getAClass(className, jaifPath, null); + } + + /** + * Updates the set of annotations in a location of a Scene, as the result of a + * pseudo-assignment. + * + *

            + *
          • If there was no previous annotation for that location, then the updated set will be the + * annotations in rhsATM. + *
          • If there was a previous annotation, the updated set will be the LUB between the + * previous annotation and rhsATM. + *
          + * + * @param type the ATypeElement of the Scene which will be modified + * @param jaifPath path to a .jaif file for a Scene; used for marking the scene as modified + * (needing to be written to disk) + * @param rhsATM the RHS of the annotated type on the source code + * @param lhsATM the LHS of the annotated type on the source code + * @param defLoc the location where the annotation will be added + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + protected void updateAnnotationSetInScene( + ATypeElement type, + TypeUseLocation defLoc, + AnnotatedTypeMirror rhsATM, + AnnotatedTypeMirror lhsATM, + String jaifPath, + boolean ignoreIfAnnotated) { + if (rhsATM instanceof AnnotatedNullType && ignoreNullAssignments) { + return; + } + TypeMirror rhsTM = rhsATM.getUnderlyingType(); + AnnotatedTypeMirror atmFromScene = atmFromStorageLocation(rhsTM, type); + updateAtmWithLub(rhsATM, atmFromScene); + if (lhsATM instanceof AnnotatedTypeVariable) { + AnnotationMirrorSet upperAnnos = + ((AnnotatedTypeVariable) lhsATM).getUpperBound().getEffectiveAnnotations(); + // If the inferred type is a subtype of the upper bounds of the + // current type on the source code, halt. + if (upperAnnos.size() == rhsATM.getAnnotations().size() + && atypeFactory + .getQualifierHierarchy() + .isSubtypeShallow( + rhsATM.getAnnotations(), + rhsTM, + upperAnnos, + lhsATM.getUnderlyingType())) { + return; + } + } + updateTypeElementFromATM(type, defLoc, rhsATM, lhsATM, ignoreIfAnnotated); + modifiedScenes.add(jaifPath); + } + + /** + * Updates sourceCodeATM to contain the LUB between sourceCodeATM and jaifATM, ignoring missing + * AnnotationMirrors from jaifATM -- it considers the LUB between an AnnotationMirror am and a + * missing AnnotationMirror to be am. The results are stored in sourceCodeATM. + * + * @param sourceCodeATM the annotated type on the source code + * @param jaifATM the annotated type on the .jaif file + */ + private void updateAtmWithLub(AnnotatedTypeMirror sourceCodeATM, AnnotatedTypeMirror jaifATM) { + + switch (sourceCodeATM.getKind()) { + case TYPEVAR: + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getLowerBound(), + ((AnnotatedTypeVariable) jaifATM).getLowerBound()); + updateAtmWithLub( + ((AnnotatedTypeVariable) sourceCodeATM).getUpperBound(), + ((AnnotatedTypeVariable) jaifATM).getUpperBound()); + break; + // case WILDCARD: + // Because inferring type arguments is not supported, wildcards won't be encoutered + // updateAtmWithLub(((AnnotatedWildcardType) + // sourceCodeATM).getExtendsBound(), + // ((AnnotatedWildcardType) + // jaifATM).getExtendsBound()); + // updateAtmWithLub(((AnnotatedWildcardType) + // sourceCodeATM).getSuperBound(), + // ((AnnotatedWildcardType) jaifATM).getSuperBound()); + // break; + case ARRAY: + updateAtmWithLub( + ((AnnotatedArrayType) sourceCodeATM).getComponentType(), + ((AnnotatedArrayType) jaifATM).getComponentType()); + break; + // case DECLARED: + // inferring annotations on type arguments is not supported, so no need to recur on + // generic types. If this was every implemented, this method would need VisitHistory + // object to prevent infinite recursion on types such as T extends List. + default: + // ATM only has primary annotations + break; + } + + // LUB primary annotations + AnnotationMirrorSet annosToReplace = new AnnotationMirrorSet(); + for (AnnotationMirror amSource : sourceCodeATM.getAnnotations()) { + AnnotationMirror amJaif = jaifATM.getAnnotationInHierarchy(amSource); + // amJaif only contains annotations from the jaif, so it might be missing + // an annotation in the hierarchy + if (amJaif != null) { + amSource = + atypeFactory + .getQualifierHierarchy() + .leastUpperBoundShallow( + amSource, + sourceCodeATM.getUnderlyingType(), + amJaif, + jaifATM.getUnderlyingType()); + } + annosToReplace.add(amSource); + } + sourceCodeATM.replaceAnnotations(annosToReplace); + } + + /** + * Returns true if {@code am} should not be inserted in source code, for example {@link + * org.checkerframework.common.value.qual.BottomVal}. This happens when {@code am} cannot be + * inserted in source code or is the default for the location passed as argument. + * + *

          Invisible qualifiers, which are annotations that contain the {@link + * org.checkerframework.framework.qual.InvisibleQualifier} meta-annotation, also return true. + * + *

          TODO: Merge functionality somewhere else with {@link + * org.checkerframework.framework.util.defaults.QualifierDefaults}. Look into the + * createQualifierDefaults method in {@link GenericAnnotatedTypeFactory} (which uses the + * QualifierDefaults class linked above) before changing anything here. See + * https://github.com/typetools/checker-framework/issues/683 . + * + * @param am an annotation to test for whether it should be inserted into source code + * @param location where the location would be inserted; used to determine if {@code am} is the + * default for that location + * @param atm its kind is used to determine if {@code am} is the default for that kind + * @return true if am should not be inserted into source code, or if am is invisible + */ + private boolean shouldIgnore( + AnnotationMirror am, TypeUseLocation location, AnnotatedTypeMirror atm) { + Element elt = am.getAnnotationType().asElement(); + // Checks if am is an implementation detail (a type qualifier used + // internally by the type system and not meant to be seen by the user). + Target target = elt.getAnnotation(Target.class); + if (target != null && target.value().length == 0) { + return true; + } + if (elt.getAnnotation(InvisibleQualifier.class) != null) { + return true; + } + + // Checks if am is default + if (elt.getAnnotation(DefaultQualifierInHierarchy.class) != null) { + return true; + } + DefaultQualifier defaultQual = elt.getAnnotation(DefaultQualifier.class); + if (defaultQual != null) { + for (TypeUseLocation loc : defaultQual.locations()) { + if (loc == TypeUseLocation.ALL || loc == location) { + return true; + } + } + } + DefaultFor defaultQualForLocation = elt.getAnnotation(DefaultFor.class); + if (defaultQualForLocation != null) { + for (TypeUseLocation loc : defaultQualForLocation.value()) { + if (loc == TypeUseLocation.ALL || loc == location) { + return true; + } + } + } + + // Checks if am is a default annotation. + // This case checks if it is meta-annotated with @DefaultFor. + // TODO: Handle cases of annotations added via an + // org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator. + DefaultFor defaultFor = elt.getAnnotation(DefaultFor.class); + if (defaultFor != null) { + org.checkerframework.framework.qual.TypeKind[] types = defaultFor.typeKinds(); + TypeKind atmKind = atm.getUnderlyingType().getKind(); + if (hasMatchingTypeKind(atmKind, types)) { + return true; + } + } + + return false; + } + + /** Returns true, iff a matching TypeKind is found. */ + private boolean hasMatchingTypeKind( + TypeKind atmKind, org.checkerframework.framework.qual.TypeKind[] types) { + for (org.checkerframework.framework.qual.TypeKind tk : types) { + if (tk.name().equals(atmKind.name())) { + return true; + } + } + return false; + } + + /** + * Returns a subset of annosSet, consisting of the annotations supported by the type factory + * associated with this. These are not necessarily legal annotations: they have the right name, + * but they may lack elements (fields). + * + * @param annosSet a set of annotations + * @return the annoattions supported by this object's AnnotatedTypeFactory + */ + private Set getSupportedAnnosInSet(Set annosSet) { + Set output = new HashSet<>(1); + Set> supportedAnnos = + atypeFactory.getSupportedTypeQualifiers(); + for (Annotation anno : annosSet) { + for (Class clazz : supportedAnnos) { + // TODO: Remove comparison by name, and make this routine more efficient. + if (clazz.getName().equals(anno.def.name)) { + output.add(anno); + } + } + } + return output; + } + + @Override + public AnnotatedTypeMirror atmFromStorageLocation( + TypeMirror typeMirror, ATypeElement storageLocation) { + AnnotatedTypeMirror result = + AnnotatedTypeMirror.createType(typeMirror, atypeFactory, false); + updateAtmFromATypeElement(result, storageLocation); + return result; + } + + /** + * Updates an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} to contain the + * {@link org.checkerframework.afu.scenelib.annotations.Annotation}s of an {@link + * org.checkerframework.afu.scenelib.annotations.el.ATypeElement}. + * + * @param result the AnnotatedTypeMirror to be modified + * @param storageLocation the {@link + * org.checkerframework.afu.scenelib.annotations.el.ATypeElement} used + */ + private void updateAtmFromATypeElement( + AnnotatedTypeMirror result, ATypeElement storageLocation) { + Set annos = getSupportedAnnosInSet(storageLocation.tlAnnotationsHere); + for (Annotation anno : annos) { + AnnotationMirror am = + AnnotationConverter.annotationToAnnotationMirror( + anno, atypeFactory.getProcessingEnv()); + result.addAnnotation(am); + } + if (result.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aat = (AnnotatedArrayType) result; + for (ATypeElement innerType : storageLocation.innerTypes.values()) { + updateAtmFromATypeElement(aat.getComponentType(), innerType); + } + } + if (result.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) result; + for (ATypeElement innerType : storageLocation.innerTypes.values()) { + updateAtmFromATypeElement(atv.getUpperBound(), innerType); + } + } + } + + @Override + public void updateStorageLocationFromAtm( + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + ATypeElement typeToUpdate, + TypeUseLocation defLoc, + boolean ignoreIfAnnotated) { + updateTypeElementFromATM(typeToUpdate, defLoc, newATM, curATM, ignoreIfAnnotated); + } + + /// + /// Writing to a file + /// + + // The prepare*ForWriting hooks are needed in addition to the postProcessClassTree hook because + // a scene may be modifed and written at any time, including before or after + // postProcessClassTree is called. + + /** + * Side-effects the compilation unit annotations to make any desired changes before writing to a + * file. + * + * @param compilationUnitAnnos the compilation unit annotations to modify + */ + public void prepareSceneForWriting(AScene compilationUnitAnnos) { + for (Map.Entry classEntry : compilationUnitAnnos.classes.entrySet()) { + wpiPrepareClassForWriting(classEntry.getValue()); + } + } + + /** + * Side-effects the class annotations to make any desired changes before writing to a file. + * + * @param classAnnos the class annotations to modify + */ + public void wpiPrepareClassForWriting(AClass classAnnos) { + for (Map.Entry methodEntry : classAnnos.methods.entrySet()) { + wpiPrepareMethodForWriting(methodEntry.getValue()); + } + } + + /** + * Side-effects the method or constructor annotations to make any desired changes before writing + * to a file. + * + * @param methodAnnos the method or constructor annotations to modify + */ + public void wpiPrepareMethodForWriting(AMethod methodAnnos) { + atypeFactory.wpiPrepareMethodForWriting(methodAnnos); + } + + @Override + public void writeResultsToFile( + WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker) { + if (outputFormat == OutputFormat.AJAVA) { + throw new BugInCF("WholeProgramInferenceScenes used with format " + outputFormat); + } + + for (String file : modifiedScenes) { + ASceneWrapper scene = scenes.get(file); + prepareSceneForWriting(scene.getAScene()); + } + + writeScenes(outputFormat, checker); + } + + @Override + public void setFileModified(String path) { + modifiedScenes.add(path); + } + + @Override + public void preprocessClassTree(ClassTree classTree) { + // This implementation does nothing. + } + + /** + * Updates an {@link org.checkerframework.afu.scenelib.annotations.el.ATypeElement} to have the + * annotations of an {@link org.checkerframework.framework.type.AnnotatedTypeMirror} passed as + * argument. Annotations in the original set that should be ignored (see {@link #shouldIgnore}) + * are not added to the resulting set. This method also checks if the AnnotatedTypeMirror has + * explicit annotations in source code, and if that is the case no annotations are added for + * that location. + * + *

          This method removes from the ATypeElement all annotations supported by this object's + * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is + * called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. Therefore, + * it is not a problem to remove all annotations before inserting the new annotations. + * + * @param typeToUpdate the ATypeElement that will be updated + * @param defLoc the location where the annotation will be added + * @param newATM the AnnotatedTypeMirror whose annotations will be added to the ATypeElement + * @param curATM used to check if the element which will be updated has explicit annotations in + * source code + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + private void updateTypeElementFromATM( + ATypeElement typeToUpdate, + TypeUseLocation defLoc, + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + boolean ignoreIfAnnotated) { + // Clears only the annotations that are supported by the relevant AnnotatedTypeFactory. + // The others stay intact. + Set annosToRemove = getSupportedAnnosInSet(typeToUpdate.tlAnnotationsHere); + // This method may be called consecutive times for the same ATypeElement. Each time it is + // called, the AnnotatedTypeMirror has a better type estimate for the ATypeElement. + // Therefore, it is not a problem to remove all annotations before inserting the new + // annotations. + typeToUpdate.tlAnnotationsHere.removeAll(annosToRemove); + + // Only update the ATypeElement if there are no explicit annotations. + if (curATM.getExplicitAnnotations().isEmpty() || !ignoreIfAnnotated) { + for (AnnotationMirror am : newATM.getAnnotations()) { + addAnnotationsToATypeElement( + newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); + } + } else if (curATM.getKind() == TypeKind.TYPEVAR) { + // getExplicitAnnotations will be non-empty for type vars whose bounds are explicitly + // annotated. So instead, only insert the annotation if there is not primary annotation + // of the same hierarchy. #shouldIgnore prevent annotations that are subtypes of type + // vars upper bound from being inserted. + for (AnnotationMirror am : newATM.getAnnotations()) { + if (curATM.getAnnotationInHierarchy(am) != null) { + // Don't insert if the type is already has a primary annotation + // in the same hierarchy. + break; + } + addAnnotationsToATypeElement( + newATM, typeToUpdate, defLoc, am, curATM.hasEffectiveAnnotation(am)); + } + } + + // Recursively update compound type and type variable type if they exist. + if (newATM.getKind() == TypeKind.ARRAY && curATM.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType newAAT = (AnnotatedArrayType) newATM; + AnnotatedArrayType oldAAT = (AnnotatedArrayType) curATM; + updateTypeElementFromATM( + typeToUpdate.innerTypes.getVivify( + TypePathEntry.getTypePathEntryListFromBinary( + Collections.nCopies(2, 0))), + defLoc, + newAAT.getComponentType(), + oldAAT.getComponentType(), + ignoreIfAnnotated); + } + } + + private void addAnnotationsToATypeElement( + AnnotatedTypeMirror newATM, + ATypeElement typeToUpdate, + TypeUseLocation defLoc, + AnnotationMirror am, + boolean isEffectiveAnnotation) { + Annotation anno = AnnotationConverter.annotationMirrorToAnnotation(am); + typeToUpdate.tlAnnotationsHere.add(anno); + if (isEffectiveAnnotation || shouldIgnore(am, defLoc, newATM)) { + // firstKey works as a unique identifier for each annotation + // that should not be inserted in source code + String firstKey = aTypeElementToString(typeToUpdate); + IPair key = IPair.of(firstKey, defLoc); + Set annosIgnored = annosToIgnore.get(key); + if (annosIgnored == null) { + annosIgnored = new HashSet<>(CollectionsPlume.mapCapacity(1)); + annosToIgnore.put(key, annosIgnored); + } + annosIgnored.add(anno.def().toString()); + } + } + + /** + * Returns a string representation of an ATypeElement, for use as part of a key in {@link + * AnnotationsInContexts}. + * + * @param aType an ATypeElement to convert to a string representation + * @return a string representation of the argument + */ + public static String aTypeElementToString(ATypeElement aType) { + // return aType.description.toString() + aType.tlAnnotationsHere; + return aType.description.toString(); + } + + /** + * Maps the {@link #aTypeElementToString} representation of an ATypeElement and its + * TypeUseLocation to a set of names of annotations. + */ + public static class AnnotationsInContexts + extends HashMap, Set> { + private static final long serialVersionUID = 20200321L; + } + + /** + * Returns the "flatname" of the class enclosing {@code localVariableNode}. + * + * @param localVariableNode the {@link LocalVariableNode} + * @return the "flatname" of the class enclosing {@code localVariableNode} + */ + private static @BinaryName String getEnclosingClassName(LocalVariableNode localVariableNode) { + return ElementUtils.getBinaryName( + ElementUtils.enclosingTypeElement(localVariableNode.getElement())); + } } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java index 8a00fac866f..88dea76e226 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceStorage.java @@ -1,12 +1,7 @@ package org.checkerframework.common.wholeprograminference; import com.sun.source.tree.ClassTree; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -16,6 +11,13 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + /** * Stores annotations from whole-program inference. For a given location such as a field or method, * an object can be obtained containing the inferred annotations for that object. @@ -28,235 +30,241 @@ * a storage location. */ public interface WholeProgramInferenceStorage { - /** - * Returns the file corresponding to the given element. This may side-effect the storage to load - * the file if it hasn't been read yet. - * - * @param elt an element - * @return the path to the file where inference results for the element will be written - */ - public String getFileForElement(Element elt); + /** + * Returns the file corresponding to the given element. This may side-effect the storage to load + * the file if it hasn't been read yet. + * + * @param elt an element + * @return the path to the file where inference results for the element will be written + */ + public String getFileForElement(Element elt); - /** - * Given an ExecutableElement in a compilation unit that has already been read into storage, - * returns whether there exists a stored method matching {@code elt}. - * - *

          An implementation is permitted to return false if {@code elt} represents a method that was - * synthetically added by javac, such as zero-argument constructors or valueOf(String) methods for - * enum types. - * - * @param methodElt a method or constructor Element - * @return true if the storage has a method corresponding to {@code elt} - */ - public boolean hasStorageLocationForMethod(ExecutableElement methodElt); + /** + * Given an ExecutableElement in a compilation unit that has already been read into storage, + * returns whether there exists a stored method matching {@code elt}. + * + *

          An implementation is permitted to return false if {@code elt} represents a method that was + * synthetically added by javac, such as zero-argument constructors or valueOf(String) methods + * for enum types. + * + * @param methodElt a method or constructor Element + * @return true if the storage has a method corresponding to {@code elt} + */ + public boolean hasStorageLocationForMethod(ExecutableElement methodElt); - /** - * Get the annotations for a formal parameter type. - * - * @param methodElt the method or constructor Element - * @param index_1based the parameter index (1-based) - * @param paramATM the parameter type - * @param ve the parameter variable - * @param atypeFactory the type factory - * @return the annotations for a formal parameter type - */ - public T getParameterAnnotations( - ExecutableElement methodElt, - @Positive int index_1based, - AnnotatedTypeMirror paramATM, - VariableElement ve, - AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for a formal parameter type. + * + * @param methodElt the method or constructor Element + * @param index_1based the parameter index (1-based) + * @param paramATM the parameter type + * @param ve the parameter variable + * @param atypeFactory the type factory + * @return the annotations for a formal parameter type + */ + public T getParameterAnnotations( + ExecutableElement methodElt, + @Positive int index_1based, + AnnotatedTypeMirror paramATM, + VariableElement ve, + AnnotatedTypeFactory atypeFactory); - /** - * Get the annotations for the receiver type. - * - * @param methodElt the method or constructor Element - * @param paramATM the receiver type - * @param atypeFactory the type factory - * @return the annotations for the receiver type - */ - public T getReceiverAnnotations( - ExecutableElement methodElt, AnnotatedTypeMirror paramATM, AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for the receiver type. + * + * @param methodElt the method or constructor Element + * @param paramATM the receiver type + * @param atypeFactory the type factory + * @return the annotations for the receiver type + */ + public T getReceiverAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror paramATM, + AnnotatedTypeFactory atypeFactory); - /** - * Get the annotations for the return type. - * - * @param methodElt the method or constructor Element - * @param atm the return type - * @param atypeFactory the type factory - * @return the annotations for the return type - */ - public T getReturnAnnotations( - ExecutableElement methodElt, AnnotatedTypeMirror atm, AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for the return type. + * + * @param methodElt the method or constructor Element + * @param atm the return type + * @param atypeFactory the type factory + * @return the annotations for the return type + */ + public T getReturnAnnotations( + ExecutableElement methodElt, + AnnotatedTypeMirror atm, + AnnotatedTypeFactory atypeFactory); - /** - * Get the annotations for a field type. - * - * @param element the element for the field - * @param fieldName the simple field name - * @param lhsATM the field type - * @param atypeFactory the annotated type factory - * @return the annotations for a field type - */ - public T getFieldAnnotations( - Element element, - String fieldName, - AnnotatedTypeMirror lhsATM, - AnnotatedTypeFactory atypeFactory); + /** + * Get the annotations for a field type. + * + * @param element the element for the field + * @param fieldName the simple field name + * @param lhsATM the field type + * @param atypeFactory the annotated type factory + * @return the annotations for a field type + */ + public T getFieldAnnotations( + Element element, + String fieldName, + AnnotatedTypeMirror lhsATM, + AnnotatedTypeFactory atypeFactory); - /** - * Returns the pre- or postcondition annotations for an expression. The format of the expression - * is the same as a programmer would write in a {@link - * org.checkerframework.framework.qual.RequiresQualifier} or {@link - * org.checkerframework.framework.qual.EnsuresQualifier} annotation. - * - *

          This method may return null if the given expression is not a supported expression type. - * Currently, the supported expression types are: fields of "this" (e.g. "this.f", pre- and - * postconditions), "this" (postconditions only), and method parameters (e.g. "#1", "#2", - * postconditions only). - * - * @param preOrPost whether to get the precondition or postcondition - * @param methodElement the method - * @param expression the expression - * @param declaredType the declared type of the expression - * @param atypeFactory the type factory - * @return the pre- or postcondition annotations for an expression, or null if the given - * expression is not a supported expression type - */ - public @Nullable T getPreOrPostconditions( - Analysis.BeforeOrAfter preOrPost, - ExecutableElement methodElement, - String expression, - AnnotatedTypeMirror declaredType, - AnnotatedTypeFactory atypeFactory); + /** + * Returns the pre- or postcondition annotations for an expression. The format of the expression + * is the same as a programmer would write in a {@link + * org.checkerframework.framework.qual.RequiresQualifier} or {@link + * org.checkerframework.framework.qual.EnsuresQualifier} annotation. + * + *

          This method may return null if the given expression is not a supported expression type. + * Currently, the supported expression types are: fields of "this" (e.g. "this.f", pre- and + * postconditions), "this" (postconditions only), and method parameters (e.g. "#1", "#2", + * postconditions only). + * + * @param preOrPost whether to get the precondition or postcondition + * @param methodElement the method + * @param expression the expression + * @param declaredType the declared type of the expression + * @param atypeFactory the type factory + * @return the pre- or postcondition annotations for an expression, or null if the given + * expression is not a supported expression type + */ + public @Nullable T getPreOrPostconditions( + Analysis.BeforeOrAfter preOrPost, + ExecutableElement methodElement, + String expression, + AnnotatedTypeMirror declaredType, + AnnotatedTypeFactory atypeFactory); - /** - * Updates a method to add a declaration annotation. - * - * @param methodElt the method to annotate - * @param anno the declaration annotation to add to the method - * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false - * otherwise - */ - public boolean addMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); + /** + * Updates a method to add a declaration annotation. + * + * @param methodElt the method to annotate + * @param anno the declaration annotation to add to the method + * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false + * otherwise + */ + public boolean addMethodDeclarationAnnotation( + ExecutableElement methodElt, AnnotationMirror anno); - /** - * Updates a field to add a declaration annotation. - * - * @param fieldElt the field - * @param anno the declaration annotation to add to the field - * @return true if {@code anno} is a new declaration annotation for {@code fieldElt}, false - * otherwise - */ - public boolean addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); + /** + * Updates a field to add a declaration annotation. + * + * @param fieldElt the field + * @param anno the declaration annotation to add to the field + * @return true if {@code anno} is a new declaration annotation for {@code fieldElt}, false + * otherwise + */ + public boolean addFieldDeclarationAnnotation(VariableElement fieldElt, AnnotationMirror anno); - /** - * Adds a declaration annotation to a formal parameter. - * - * @param methodElt the method whose formal parameter will be annotated - * @param index_1based the index of the parameter (1-indexed) - * @param anno the annotation to add - * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false - * otherwise - */ - public boolean addDeclarationAnnotationToFormalParameter( - ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); + /** + * Adds a declaration annotation to a formal parameter. + * + * @param methodElt the method whose formal parameter will be annotated + * @param index_1based the index of the parameter (1-indexed) + * @param anno the annotation to add + * @return true if {@code anno} is a new declaration annotation for {@code methodElt}, false + * otherwise + */ + public boolean addDeclarationAnnotationToFormalParameter( + ExecutableElement methodElt, @Positive int index_1based, AnnotationMirror anno); - /** - * Adds an annotation to a class declaration. - * - * @param classElt the class declaration to annotate - * @param anno the annotation to add - * @return true if {@code anno} is a new declaration annotation for {@code classElt}, false - * otherwise - */ - public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); + /** + * Adds an annotation to a class declaration. + * + * @param classElt the class declaration to annotate + * @param anno the annotation to add + * @return true if {@code anno} is a new declaration annotation for {@code classElt}, false + * otherwise + */ + public boolean addClassDeclarationAnnotation(TypeElement classElt, AnnotationMirror anno); - /** - * Return the list of declaration annotations inferred on the given method so far in this round of - * WPI. - * - * @param elt a method - * @return the declaration annotations inferred on elt so far (may be empty) - */ - AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt); + /** + * Return the list of declaration annotations inferred on the given method so far in this round + * of WPI. + * + * @param elt a method + * @return the declaration annotations inferred on elt so far (may be empty) + */ + AnnotationMirrorSet getMethodDeclarationAnnotations(ExecutableElement elt); - /** - * Removes the given annotation from the given method element's inferred declaration annotation. - * If the given annotation was not in the list of inferred declaration annotations on the given - * method, calling this method is a no-op. - * - * @param methodElt a method element - * @param anno a declaration annotation to remove - * @return true if the annotation was successfully removed, false if not (e.g., if it wasn't - * present) - */ - boolean removeMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); + /** + * Removes the given annotation from the given method element's inferred declaration annotation. + * If the given annotation was not in the list of inferred declaration annotations on the given + * method, calling this method is a no-op. + * + * @param methodElt a method element + * @param anno a declaration annotation to remove + * @return true if the annotation was successfully removed, false if not (e.g., if it wasn't + * present) + */ + boolean removeMethodDeclarationAnnotation(ExecutableElement methodElt, AnnotationMirror anno); - /** - * Obtain the type from a storage location. - * - * @param typeMirror the underlying type for the result - * @param storageLocation the storage location from which to obtain annotations - * @return an annotated type mirror with underlying type {@code typeMirror} and annotations from - * {@code storageLocation} - */ - public AnnotatedTypeMirror atmFromStorageLocation(TypeMirror typeMirror, T storageLocation); + /** + * Obtain the type from a storage location. + * + * @param typeMirror the underlying type for the result + * @param storageLocation the storage location from which to obtain annotations + * @return an annotated type mirror with underlying type {@code typeMirror} and annotations from + * {@code storageLocation} + */ + public AnnotatedTypeMirror atmFromStorageLocation(TypeMirror typeMirror, T storageLocation); - /** - * Updates a storage location to have the annotations of the given {@code AnnotatedTypeMirror}. - * Annotations in the original set that should be ignored are not added to the resulting set. If - * {@code ignoreIfAnnotated} is true, doesn't add annotations for locations with explicit - * annotations in source code. - * - *

          This method removes from the storage location all annotations supported by the - * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is - * called, the new {@code AnnotatedTypeMirror} has a better type estimate for the given location. - * Therefore, it is not a problem to remove all annotations before inserting the new annotations. - * - *

          The {@code update*} methods in {@link WholeProgramInference} perform LUB. This one just does - * replacement. (Thus, the naming may be a bit confusing.) - * - * @param newATM the type whose annotations will be added to the {@code AnnotatedTypeMirror} - * @param curATM the annotations currently stored at the location, used to check if the element - * that will be updated has explicit annotations in source code - * @param storageLocationToUpdate the storage location that will be updated - * @param defLoc the location where the annotation will be added - * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the - * source code - */ - public void updateStorageLocationFromAtm( - AnnotatedTypeMirror newATM, - AnnotatedTypeMirror curATM, - T storageLocationToUpdate, - TypeUseLocation defLoc, - boolean ignoreIfAnnotated); + /** + * Updates a storage location to have the annotations of the given {@code AnnotatedTypeMirror}. + * Annotations in the original set that should be ignored are not added to the resulting set. If + * {@code ignoreIfAnnotated} is true, doesn't add annotations for locations with explicit + * annotations in source code. + * + *

          This method removes from the storage location all annotations supported by the + * AnnotatedTypeFactory before inserting new ones. It is assumed that every time this method is + * called, the new {@code AnnotatedTypeMirror} has a better type estimate for the given + * location. Therefore, it is not a problem to remove all annotations before inserting the new + * annotations. + * + *

          The {@code update*} methods in {@link WholeProgramInference} perform LUB. This one just + * does replacement. (Thus, the naming may be a bit confusing.) + * + * @param newATM the type whose annotations will be added to the {@code AnnotatedTypeMirror} + * @param curATM the annotations currently stored at the location, used to check if the element + * that will be updated has explicit annotations in source code + * @param storageLocationToUpdate the storage location that will be updated + * @param defLoc the location where the annotation will be added + * @param ignoreIfAnnotated if true, don't update any type that is explicitly annotated in the + * source code + */ + public void updateStorageLocationFromAtm( + AnnotatedTypeMirror newATM, + AnnotatedTypeMirror curATM, + T storageLocationToUpdate, + TypeUseLocation defLoc, + boolean ignoreIfAnnotated); - /** - * Writes the inferred results to a file. Ideally, it should be called at the end of the - * type-checking process. In practice, it is called after each class, because we don't know which - * class will be the last one in the type-checking process. - * - * @param outputFormat the file format in which to write the results - * @param checker the checker from which this method is called, for naming annotation files - */ - public void writeResultsToFile( - WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker); + /** + * Writes the inferred results to a file. Ideally, it should be called at the end of the + * type-checking process. In practice, it is called after each class, because we don't know + * which class will be the last one in the type-checking process. + * + * @param outputFormat the file format in which to write the results + * @param checker the checker from which this method is called, for naming annotation files + */ + public void writeResultsToFile( + WholeProgramInference.OutputFormat outputFormat, BaseTypeChecker checker); - /** - * Indicates that inferred annotations for the file at {@code path} have changed since last - * written. This causes output files for {@code path} to be written out next time {@link - * #writeResultsToFile} is called. - * - * @param path path to the file with annotations that have been modified - */ - public void setFileModified(String path); + /** + * Indicates that inferred annotations for the file at {@code path} have changed since last + * written. This causes output files for {@code path} to be written out next time {@link + * #writeResultsToFile} is called. + * + * @param path path to the file with annotations that have been modified + */ + public void setFileModified(String path); - /** - * Performs any preparation required for inference on the elements of a class. Should be called on - * each top-level class declaration in a compilation unit before processing it. - * - * @param classTree the class to preprocess - */ - void preprocessClassTree(ClassTree classTree); + /** + * Performs any preparation required for inference on the elements of a class. Should be called + * on each top-level class declaration in a compilation unit before processing it. + * + * @param classTree the class to preprocess + */ + void preprocessClassTree(ClassTree classTree); } diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java index aca2fb064e9..00e08d2c89b 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java @@ -1,20 +1,7 @@ package org.checkerframework.common.wholeprograminference.scenelib; import com.sun.tools.javac.code.Symbol.ClassSymbol; -import java.io.File; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.afu.scenelib.Annotation; import org.checkerframework.afu.scenelib.el.AClass; import org.checkerframework.afu.scenelib.el.AField; @@ -38,6 +25,22 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + /** * scene-lib (from the Annotation File Utilities) doesn't provide enough information to usefully * print stub files: it lacks information about what is and is not an enum, about the base types of @@ -51,208 +54,214 @@ */ public class ASceneWrapper { - /** The AScene being wrapped. */ - private final AScene theScene; + /** The AScene being wrapped. */ + private final AScene theScene; - /** - * Constructor. Pass the AScene to wrap. - * - * @param theScene the scene to wrap - */ - public ASceneWrapper(AScene theScene) { - this.theScene = theScene; - } + /** + * Constructor. Pass the AScene to wrap. + * + * @param theScene the scene to wrap + */ + public ASceneWrapper(AScene theScene) { + this.theScene = theScene; + } - /** - * Removes the specified annotations from an AScene. - * - * @param scene the scene from which to remove annotations - * @param annosToRemove annotations that should not be added to .jaif or stub files - */ - private void removeAnnosFromScene(AScene scene, AnnotationsInContexts annosToRemove) { - for (AClass aclass : scene.classes.values()) { - for (AField field : aclass.fields.values()) { - removeAnnosFromATypeElement(field.type, TypeUseLocation.FIELD, annosToRemove); - } - for (AMethod method : aclass.methods.values()) { - removeAnnosFromATypeElement(method.returnType, TypeUseLocation.RETURN, annosToRemove); - removeAnnosFromATypeElement(method.receiver.type, TypeUseLocation.RECEIVER, annosToRemove); - for (AField param : method.parameters.values()) { - removeAnnosFromATypeElement(param.type, TypeUseLocation.PARAMETER, annosToRemove); + /** + * Removes the specified annotations from an AScene. + * + * @param scene the scene from which to remove annotations + * @param annosToRemove annotations that should not be added to .jaif or stub files + */ + private void removeAnnosFromScene(AScene scene, AnnotationsInContexts annosToRemove) { + for (AClass aclass : scene.classes.values()) { + for (AField field : aclass.fields.values()) { + removeAnnosFromATypeElement(field.type, TypeUseLocation.FIELD, annosToRemove); + } + for (AMethod method : aclass.methods.values()) { + removeAnnosFromATypeElement( + method.returnType, TypeUseLocation.RETURN, annosToRemove); + removeAnnosFromATypeElement( + method.receiver.type, TypeUseLocation.RECEIVER, annosToRemove); + for (AField param : method.parameters.values()) { + removeAnnosFromATypeElement( + param.type, TypeUseLocation.PARAMETER, annosToRemove); + } + } } - } } - } - /** - * Removes the specified annotations from an ATypeElement. - * - * @param typeElt the type element from which to remove annotations - * @param loc the location where typeElt is used - * @param annosToRemove annotations that should not be added to .jaif or stub files - */ - private void removeAnnosFromATypeElement( - ATypeElement typeElt, TypeUseLocation loc, AnnotationsInContexts annosToRemove) { - String annosToRemoveKey = WholeProgramInferenceScenesStorage.aTypeElementToString(typeElt); - Set annosToRemoveForLocation = annosToRemove.get(IPair.of(annosToRemoveKey, loc)); - if (annosToRemoveForLocation != null) { - Set annosToRemoveHere = - ArraySet.newArraySetOrHashSet(annosToRemoveForLocation.size()); - for (Annotation anno : typeElt.tlAnnotationsHere) { - if (annosToRemoveForLocation.contains(anno.def().toString())) { - annosToRemoveHere.add(anno); + /** + * Removes the specified annotations from an ATypeElement. + * + * @param typeElt the type element from which to remove annotations + * @param loc the location where typeElt is used + * @param annosToRemove annotations that should not be added to .jaif or stub files + */ + private void removeAnnosFromATypeElement( + ATypeElement typeElt, TypeUseLocation loc, AnnotationsInContexts annosToRemove) { + String annosToRemoveKey = WholeProgramInferenceScenesStorage.aTypeElementToString(typeElt); + Set annosToRemoveForLocation = annosToRemove.get(IPair.of(annosToRemoveKey, loc)); + if (annosToRemoveForLocation != null) { + Set annosToRemoveHere = + ArraySet.newArraySetOrHashSet(annosToRemoveForLocation.size()); + for (Annotation anno : typeElt.tlAnnotationsHere) { + if (annosToRemoveForLocation.contains(anno.def().toString())) { + annosToRemoveHere.add(anno); + } + } + typeElt.tlAnnotationsHere.removeAll(annosToRemoveHere); } - } - typeElt.tlAnnotationsHere.removeAll(annosToRemoveHere); - } - // Recursively remove annotations from inner types - for (ATypeElement innerType : typeElt.innerTypes.values()) { - removeAnnosFromATypeElement(innerType, loc, annosToRemove); + // Recursively remove annotations from inner types + for (ATypeElement innerType : typeElt.innerTypes.values()) { + removeAnnosFromATypeElement(innerType, loc, annosToRemove); + } } - } - /** - * Write the scene wrapped by this object to a file at the given path. - * - * @param jaifPath the path of the file to be written, but ending in ".jaif". If {@code - * outputformat} is not {@code JAIF}, the path will be modified to match. - * @param annosToIgnore which annotations should be ignored in which contexts - * @param outputFormat the output format to use - * @param checker the checker from which this method is called, for naming stub files - */ - public void writeToFile( - String jaifPath, - AnnotationsInContexts annosToIgnore, - OutputFormat outputFormat, - BaseTypeChecker checker) { - assert jaifPath.endsWith(".jaif"); - AScene scene = theScene.clone(); - removeAnnosFromScene(scene, annosToIgnore); - scene.prune(); - String filepath; - switch (outputFormat) { - case JAIF: - filepath = jaifPath; - break; - case STUB: - String astubWithChecker = "-" + checker.getClass().getCanonicalName() + ".astub"; - filepath = jaifPath.replace(".jaif", astubWithChecker); - break; - default: - throw new BugInCF("Unhandled outputFormat " + outputFormat); - } - new File(filepath).delete(); - // Only write non-empty scenes into files. - if (!scene.isEmpty()) { - try { + /** + * Write the scene wrapped by this object to a file at the given path. + * + * @param jaifPath the path of the file to be written, but ending in ".jaif". If {@code + * outputformat} is not {@code JAIF}, the path will be modified to match. + * @param annosToIgnore which annotations should be ignored in which contexts + * @param outputFormat the output format to use + * @param checker the checker from which this method is called, for naming stub files + */ + public void writeToFile( + String jaifPath, + AnnotationsInContexts annosToIgnore, + OutputFormat outputFormat, + BaseTypeChecker checker) { + assert jaifPath.endsWith(".jaif"); + AScene scene = theScene.clone(); + removeAnnosFromScene(scene, annosToIgnore); + scene.prune(); + String filepath; switch (outputFormat) { - case STUB: - // For stub files, pass in the checker to compute contracts on the fly; - // precomputing yields incorrect annotations, most likely due to nested - // classes. - SceneToStubWriter.write(this, filepath, checker); - break; - case JAIF: - // For .jaif files, precompute contracts because the Annotation File - // Utilities knows nothing about (and cannot depend on) the Checker - // Framework. - for (Map.Entry classEntry : scene.classes.entrySet()) { - AClass aClass = classEntry.getValue(); - for (Map.Entry methodEntry : aClass.getMethods().entrySet()) { - AMethod aMethod = methodEntry.getValue(); - List contractAnnotationMirrors = - checker.getTypeFactory().getContractAnnotations(aMethod); - List contractAnnotations = - CollectionsPlume.mapList( - AnnotationConverter::annotationMirrorToAnnotation, - contractAnnotationMirrors); - aMethod.contracts = contractAnnotations; - } - } - try (Writer fw = Files.newBufferedWriter(Paths.get(filepath), StandardCharsets.UTF_8)) { - IndexFileWriter.write(scene, fw); + case JAIF: + filepath = jaifPath; + break; + case STUB: + String astubWithChecker = "-" + checker.getClass().getCanonicalName() + ".astub"; + filepath = jaifPath.replace(".jaif", astubWithChecker); + break; + default: + throw new BugInCF("Unhandled outputFormat " + outputFormat); + } + new File(filepath).delete(); + // Only write non-empty scenes into files. + if (!scene.isEmpty()) { + try { + switch (outputFormat) { + case STUB: + // For stub files, pass in the checker to compute contracts on the fly; + // precomputing yields incorrect annotations, most likely due to nested + // classes. + SceneToStubWriter.write(this, filepath, checker); + break; + case JAIF: + // For .jaif files, precompute contracts because the Annotation File + // Utilities knows nothing about (and cannot depend on) the Checker + // Framework. + for (Map.Entry classEntry : scene.classes.entrySet()) { + AClass aClass = classEntry.getValue(); + for (Map.Entry methodEntry : + aClass.getMethods().entrySet()) { + AMethod aMethod = methodEntry.getValue(); + List contractAnnotationMirrors = + checker.getTypeFactory().getContractAnnotations(aMethod); + List contractAnnotations = + CollectionsPlume.mapList( + AnnotationConverter::annotationMirrorToAnnotation, + contractAnnotationMirrors); + aMethod.contracts = contractAnnotations; + } + } + try (Writer fw = + Files.newBufferedWriter( + Paths.get(filepath), StandardCharsets.UTF_8)) { + IndexFileWriter.write(scene, fw); + } + break; + default: + throw new BugInCF("Unhandled outputFormat " + outputFormat); + } + } catch (IOException e) { + throw new UserError("Problem while writing %s: %s", filepath, e.getMessage()); + } catch (DefException e) { + throw new BugInCF(e); } - break; - default: - throw new BugInCF("Unhandled outputFormat " + outputFormat); } - } catch (IOException e) { - throw new UserError("Problem while writing %s: %s", filepath, e.getMessage()); - } catch (DefException e) { - throw new BugInCF(e); - } } - } - /** - * Updates the symbol information stored in AClass for the given class. May be called multiple - * times (and needs to be if the second parameter was null the first time it was called; only some - * calls provide the symbol information). - * - * @param aClass the class representation in which the symbol information is to be updated - * @param classSymbol the source of the symbol information; may be null, in which case this method - * does nothing - */ - public void updateSymbolInformation(AClass aClass, @Nullable ClassSymbol classSymbol) { - if (classSymbol == null) { - return; - } - if (classSymbol.isEnum()) { - List enumConstants = ElementUtils.getEnumConstants(classSymbol); - if (!aClass.isEnum(classSymbol.getSimpleName().toString())) { - aClass.setEnumConstants(enumConstants); - } else { - // Verify that the existing value is consistent. - List existingEnumConstants = aClass.getEnumConstants(); - if (existingEnumConstants.size() != enumConstants.size()) { - throw new BugInCF( - "inconsistent enum constants in WPI for class " - + classSymbol.getQualifiedName().toString()); + /** + * Updates the symbol information stored in AClass for the given class. May be called multiple + * times (and needs to be if the second parameter was null the first time it was called; only + * some calls provide the symbol information). + * + * @param aClass the class representation in which the symbol information is to be updated + * @param classSymbol the source of the symbol information; may be null, in which case this + * method does nothing + */ + public void updateSymbolInformation(AClass aClass, @Nullable ClassSymbol classSymbol) { + if (classSymbol == null) { + return; } - for (int i = 0; i < enumConstants.size(); i++) { - if (!existingEnumConstants.get(i).equals(enumConstants.get(i))) { - throw new BugInCF( - "inconsistent enum constants in WPI for class " - + classSymbol.getQualifiedName().toString()); - } + if (classSymbol.isEnum()) { + List enumConstants = ElementUtils.getEnumConstants(classSymbol); + if (!aClass.isEnum(classSymbol.getSimpleName().toString())) { + aClass.setEnumConstants(enumConstants); + } else { + // Verify that the existing value is consistent. + List existingEnumConstants = aClass.getEnumConstants(); + if (existingEnumConstants.size() != enumConstants.size()) { + throw new BugInCF( + "inconsistent enum constants in WPI for class " + + classSymbol.getQualifiedName().toString()); + } + for (int i = 0; i < enumConstants.size(); i++) { + if (!existingEnumConstants.get(i).equals(enumConstants.get(i))) { + throw new BugInCF( + "inconsistent enum constants in WPI for class " + + classSymbol.getQualifiedName().toString()); + } + } + } } - } - } - ClassSymbol outerClass = classSymbol; - ClassSymbol previous = classSymbol; - do { - if (outerClass.getKind() == ElementKind.ANNOTATION_TYPE) { - aClass.markAsAnnotation(outerClass.getSimpleName().toString()); - } else if (outerClass.isEnum()) { - aClass.markAsEnum(outerClass.getSimpleName().toString()); - } else if (outerClass.isInterface()) { - aClass.markAsInterface(outerClass.getSimpleName().toString()); - // } else if (outerClass.isRecord()) { - // aClass.markAsRecord(outerClass.getSimpleName().toString()); - } - Element element = classSymbol.getEnclosingElement(); - if (element == null || element.getKind() == ElementKind.PACKAGE) { - break; - } - TypeElement t = ElementUtils.enclosingTypeElement(element); - previous = outerClass; - outerClass = (ClassSymbol) t; - // It is necessary to check that previous isn't equal to outer class because - // otherwise this loop will sometimes run forever. - } while (outerClass != null && !previous.equals(outerClass)); + ClassSymbol outerClass = classSymbol; + ClassSymbol previous = classSymbol; + do { + if (outerClass.getKind() == ElementKind.ANNOTATION_TYPE) { + aClass.markAsAnnotation(outerClass.getSimpleName().toString()); + } else if (outerClass.isEnum()) { + aClass.markAsEnum(outerClass.getSimpleName().toString()); + } else if (outerClass.isInterface()) { + aClass.markAsInterface(outerClass.getSimpleName().toString()); + // } else if (outerClass.isRecord()) { + // aClass.markAsRecord(outerClass.getSimpleName().toString()); + } + Element element = classSymbol.getEnclosingElement(); + if (element == null || element.getKind() == ElementKind.PACKAGE) { + break; + } + TypeElement t = ElementUtils.enclosingTypeElement(element); + previous = outerClass; + outerClass = (ClassSymbol) t; + // It is necessary to check that previous isn't equal to outer class because + // otherwise this loop will sometimes run forever. + } while (outerClass != null && !previous.equals(outerClass)); - aClass.setTypeElement(classSymbol); - } + aClass.setTypeElement(classSymbol); + } - /** - * Avoid using this if possible; use the other methods of this class unless you absolutely need an - * AScene. - * - * @return the AScene representation of this - */ - public AScene getAScene() { - return theScene; - } + /** + * Avoid using this if possible; use the other methods of this class unless you absolutely need + * an AScene. + * + * @return the AScene representation of this + */ + public AScene getAScene() { + return theScene; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java index b572da4fc89..d78a97bd1ca 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationEqualityVisitor.java @@ -4,10 +4,12 @@ import com.github.javaparser.ast.comments.Comment; import com.github.javaparser.ast.expr.AnnotationExpr; import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; -import java.util.List; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.List; + /** * Given two ASTs representing the same Java file that may differ in annotations, tests if they have * the same annotations. @@ -16,79 +18,80 @@ * the second argument. Then, check {@link #getAnnotationsMatch}. */ public class AnnotationEqualityVisitor extends DoubleJavaParserVisitor { - /** Whether or not a node with mismatched annotations has been seen. */ - private boolean annotationsMatch; - - /** If a node with mismatched annotations has been seen, stores the node from the first AST. */ - private @MonotonicNonNull NodeWithAnnotations mismatchedNode1; + /** Whether or not a node with mismatched annotations has been seen. */ + private boolean annotationsMatch; - /** If a node with mismatched annotations has been seen, stores the node from the second AST. */ - private @MonotonicNonNull NodeWithAnnotations mismatchedNode2; + /** If a node with mismatched annotations has been seen, stores the node from the first AST. */ + private @MonotonicNonNull NodeWithAnnotations mismatchedNode1; - /** Constructs an {@code AnnotationEqualityVisitor}. */ - public AnnotationEqualityVisitor() { - annotationsMatch = true; - mismatchedNode1 = null; - mismatchedNode2 = null; - } + /** If a node with mismatched annotations has been seen, stores the node from the second AST. */ + private @MonotonicNonNull NodeWithAnnotations mismatchedNode2; - /** - * Returns whether a visited pair of nodes differed in annotations. - * - * @return true if some visited pair of nodes differed in annotations - */ - public boolean getAnnotationsMatch() { - return annotationsMatch; - } + /** Constructs an {@code AnnotationEqualityVisitor}. */ + public AnnotationEqualityVisitor() { + annotationsMatch = true; + mismatchedNode1 = null; + mismatchedNode2 = null; + } - /** - * If a visited pair of nodes has had mismatched annotations, returns the node from the first AST - * where annotations differed, or null otherwise. - * - * @return the node from the first AST with differing annotations or null - */ - public @Nullable NodeWithAnnotations getMismatchedNode1() { - return mismatchedNode1; - } + /** + * Returns whether a visited pair of nodes differed in annotations. + * + * @return true if some visited pair of nodes differed in annotations + */ + public boolean getAnnotationsMatch() { + return annotationsMatch; + } - /** - * If a visited pair of nodes has had mismatched annotations, returns the node from the second AST - * where annotations differed, or null otherwise. - * - * @return the node from the second AST with differing annotations or null - */ - public @Nullable NodeWithAnnotations getMismatchedNode2() { - return mismatchedNode2; - } + /** + * If a visited pair of nodes has had mismatched annotations, returns the node from the first + * AST where annotations differed, or null otherwise. + * + * @return the node from the first AST with differing annotations or null + */ + public @Nullable NodeWithAnnotations getMismatchedNode1() { + return mismatchedNode1; + } - @Override - public void defaultAction(T node1, T node2) { - if (!(node1 instanceof NodeWithAnnotations) || !(node2 instanceof NodeWithAnnotations)) { - return; + /** + * If a visited pair of nodes has had mismatched annotations, returns the node from the second + * AST where annotations differed, or null otherwise. + * + * @return the node from the second AST with differing annotations or null + */ + public @Nullable NodeWithAnnotations getMismatchedNode2() { + return mismatchedNode2; } - // Comparing annotations with "equals" considers comments in the AST attached to the - // annotations. These should be ignored because two valid ASTs for the same file may differ - // in where comments appear, or whether they appear at all. So, to check if two nodes have - // the same annotations we create copies with all comments removed and compare their lists - // of annotations directly. - Node node1Copy = node1.clone(); - Node node2Copy = node2.clone(); + @Override + public void defaultAction(T node1, T node2) { + if (!(node1 instanceof NodeWithAnnotations) + || !(node2 instanceof NodeWithAnnotations)) { + return; + } - for (Comment comment : node1Copy.getAllContainedComments()) { - comment.remove(); - } - for (Comment comment : node2Copy.getAllContainedComments()) { - comment.remove(); - } + // Comparing annotations with "equals" considers comments in the AST attached to the + // annotations. These should be ignored because two valid ASTs for the same file may differ + // in where comments appear, or whether they appear at all. So, to check if two nodes have + // the same annotations we create copies with all comments removed and compare their lists + // of annotations directly. + Node node1Copy = node1.clone(); + Node node2Copy = node2.clone(); + + for (Comment comment : node1Copy.getAllContainedComments()) { + comment.remove(); + } + for (Comment comment : node2Copy.getAllContainedComments()) { + comment.remove(); + } - List node1annos = ((NodeWithAnnotations) node1Copy).getAnnotations(); - List node2annos = ((NodeWithAnnotations) node2Copy).getAnnotations(); + List node1annos = ((NodeWithAnnotations) node1Copy).getAnnotations(); + List node2annos = ((NodeWithAnnotations) node2Copy).getAnnotations(); - if (!node1annos.equals(node2annos)) { - annotationsMatch = false; - mismatchedNode1 = (NodeWithAnnotations) node1; - mismatchedNode2 = (NodeWithAnnotations) node2; + if (!node1annos.equals(node2annos)) { + annotationsMatch = false; + mismatchedNode1 = (NodeWithAnnotations) node1; + mismatchedNode2 = (NodeWithAnnotations) node2; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java index 1de6be9e75d..5b3a2c790e9 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationFileStore.java @@ -2,6 +2,10 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.TypeDeclaration; + +import org.checkerframework.framework.util.JavaParserUtil; +import org.checkerframework.javacutil.BugInCF; + import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -9,70 +13,68 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.framework.util.JavaParserUtil; -import org.checkerframework.javacutil.BugInCF; /** * Stores a collection of annotation files. Given a type name, can return a list of paths to stored * annotation files corresponding to that type name. */ public class AnnotationFileStore { - /** - * Mapping from a fully qualified class name to the paths to annotation files that contain that - * type. - */ - private final Map> annotationFiles; + /** + * Mapping from a fully qualified class name to the paths to annotation files that contain that + * type. + */ + private final Map> annotationFiles; - /** Constructs an {@code AnnotationFileStore}. */ - public AnnotationFileStore() { - annotationFiles = new HashMap<>(); - } + /** Constructs an {@code AnnotationFileStore}. */ + public AnnotationFileStore() { + annotationFiles = new HashMap<>(); + } - /** - * If {@code location} is a file, stores it in this as an annotation file. If {@code location} is - * a directory, stores all annotation files contained in it. - * - * @param location an annotation file or a directory containing annotation files - */ - public void addFileOrDirectory(File location) { - if (location.isDirectory()) { - for (File child : location.listFiles()) { - addFileOrDirectory(child); - } + /** + * If {@code location} is a file, stores it in this as an annotation file. If {@code location} + * is a directory, stores all annotation files contained in it. + * + * @param location an annotation file or a directory containing annotation files + */ + public void addFileOrDirectory(File location) { + if (location.isDirectory()) { + for (File child : location.listFiles()) { + addFileOrDirectory(child); + } - return; - } + return; + } - if (location.isFile() && location.getName().endsWith(".ajava")) { - try { - CompilationUnit root = JavaParserUtil.parseCompilationUnit(location); - for (TypeDeclaration type : root.getTypes()) { - String name = JavaParserUtil.getFullyQualifiedName(type, root); + if (location.isFile() && location.getName().endsWith(".ajava")) { + try { + CompilationUnit root = JavaParserUtil.parseCompilationUnit(location); + for (TypeDeclaration type : root.getTypes()) { + String name = JavaParserUtil.getFullyQualifiedName(type, root); - if (!annotationFiles.containsKey(name)) { - annotationFiles.put(name, new ArrayList<>()); - } + if (!annotationFiles.containsKey(name)) { + annotationFiles.put(name, new ArrayList<>()); + } - annotationFiles.get(name).add(location.getPath()); + annotationFiles.get(name).add(location.getPath()); + } + } catch (FileNotFoundException e) { + throw new BugInCF("Unable to open annotation file: " + location.getPath(), e); + } } - } catch (FileNotFoundException e) { - throw new BugInCF("Unable to open annotation file: " + location.getPath(), e); - } } - } - /** - * Given a fully qualified type name, returns a List of paths to annotation files containing - * annotations for the type. - * - * @param typeName fully qualified name of a type - * @return a list of paths to annotation files with annotations for {@code typeName} - */ - public List getAnnotationFileForType(String typeName) { - if (!annotationFiles.containsKey(typeName)) { - return Collections.emptyList(); - } + /** + * Given a fully qualified type name, returns a List of paths to annotation files containing + * annotations for the type. + * + * @param typeName fully qualified name of a type + * @return a list of paths to annotation files with annotations for {@code typeName} + */ + public List getAnnotationFileForType(String typeName) { + if (!annotationFiles.containsKey(typeName)) { + return Collections.emptyList(); + } - return Collections.unmodifiableList(annotationFiles.get(typeName)); - } + return Collections.unmodifiableList(annotationFiles.get(typeName)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java index 026532cb02d..90509739378 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java @@ -23,8 +23,16 @@ import com.github.javaparser.ast.expr.UnaryExpr; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.utils.StringEscapeUtils; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypesUtils; + import java.util.List; import java.util.Map; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValueVisitor; @@ -34,231 +42,231 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypesUtils; /** * Methods for converting a {@code AnnotationMirror} into a JavaParser {@code AnnotationExpr}, * namely {@code annotationMirrorToAnnotationExpr}. */ public class AnnotationMirrorToAnnotationExprConversion { - /** - * Converts an AnnotationMirror into a JavaParser {@code AnnotationExpr}. - * - * @param annotation the annotation to convert - * @return a JavaParser {@code AnnotationExpr} representing the same annotation with the same - * element values. The converted annotation will contain the annotation's fully qualified - * name. - */ - public static AnnotationExpr annotationMirrorToAnnotationExpr(AnnotationMirror annotation) { - Map values = - annotation.getElementValues(); - Name name = createQualifiedName(AnnotationUtils.annotationName(annotation)); - if (values.isEmpty()) { - return new MarkerAnnotationExpr(name); - } + /** + * Converts an AnnotationMirror into a JavaParser {@code AnnotationExpr}. + * + * @param annotation the annotation to convert + * @return a JavaParser {@code AnnotationExpr} representing the same annotation with the same + * element values. The converted annotation will contain the annotation's fully qualified + * name. + */ + public static AnnotationExpr annotationMirrorToAnnotationExpr(AnnotationMirror annotation) { + Map values = + annotation.getElementValues(); + Name name = createQualifiedName(AnnotationUtils.annotationName(annotation)); + if (values.isEmpty()) { + return new MarkerAnnotationExpr(name); + } - NodeList convertedValues = convertAnnotationValues(values); - if (convertedValues.size() == 1 - && convertedValues.get(0).getName().asString().equals("value")) { - return new SingleMemberAnnotationExpr(name, convertedValues.get(0).getValue()); - } + NodeList convertedValues = convertAnnotationValues(values); + if (convertedValues.size() == 1 + && convertedValues.get(0).getName().asString().equals("value")) { + return new SingleMemberAnnotationExpr(name, convertedValues.get(0).getValue()); + } - return new NormalAnnotationExpr(name, convertedValues); - } - - /** - * Converts a Set of AnnotationMirror into List of JavaParser {@code AnnotationExpr}. - * - * @param annotationMirrors the annotations to convert - * @return a list of JavaParser {@code AnnotationExpr}s representing the same annotations - * @see #annotationMirrorToAnnotationExpr - */ - public static NodeList annotationMirrorSetToAnnotationExprList( - AnnotationMirrorSet annotationMirrors) { - NodeList result = new NodeList<>(); - for (AnnotationMirror am : annotationMirrors) { - result.add(annotationMirrorToAnnotationExpr(am)); - } - return result; - } - - /** - * Converts a mapping of (annotation element → value) into a list of key-value pairs - * containing the JavaParser representations of the same values. - * - * @param values mapping of element values from an {@code AnnotationMirror} - * @return a list of the key-value pairs in {@code values} converted to their JavaParser - * representations - */ - private static NodeList convertAnnotationValues( - Map values) { - NodeList convertedValues = new NodeList<>(); - AnnotationValueConverterVisitor converter = new AnnotationValueConverterVisitor(); - for (Map.Entry entry : - values.entrySet()) { - AnnotationValue value = entry.getValue(); - convertedValues.add( - new MemberValuePair( - entry.getKey().getSimpleName().toString(), value.accept(converter, null))); + return new NormalAnnotationExpr(name, convertedValues); } - return convertedValues; - } - - /** - * Given a fully qualified name, creates a JavaParser {@code Name} structure representing the same - * name. - * - * @param name the fully qualified name to convert - * @return a JavaParser {@code Name} holding {@code name} - */ - private static Name createQualifiedName(String name) { - String[] components = name.split("\\."); - Name result = new Name(components[0]); - for (int i = 1; i < components.length; i++) { - result = new Name(result, components[i]); + /** + * Converts a Set of AnnotationMirror into List of JavaParser {@code AnnotationExpr}. + * + * @param annotationMirrors the annotations to convert + * @return a list of JavaParser {@code AnnotationExpr}s representing the same annotations + * @see #annotationMirrorToAnnotationExpr + */ + public static NodeList annotationMirrorSetToAnnotationExprList( + AnnotationMirrorSet annotationMirrors) { + NodeList result = new NodeList<>(); + for (AnnotationMirror am : annotationMirrors) { + result.add(annotationMirrorToAnnotationExpr(am)); + } + return result; } - return result; - } - - /** - * A visitor that converts an annotation value from an {@code AnnotationMirror} to a JavaParser - * node that can appear in an {@code AnnotationExpr}. - */ - private static class AnnotationValueConverterVisitor - implements AnnotationValueVisitor { - @Override - public Expression visit(AnnotationValue value, Void p) { - // This is called only if the value couldn't be dispatched to any known type, which - // should never happen. - throw new BugInCF("Unknown annotation value type: " + value); - } + /** + * Converts a mapping of (annotation element → value) into a list of key-value pairs + * containing the JavaParser representations of the same values. + * + * @param values mapping of element values from an {@code AnnotationMirror} + * @return a list of the key-value pairs in {@code values} converted to their JavaParser + * representations + */ + private static NodeList convertAnnotationValues( + Map values) { + NodeList convertedValues = new NodeList<>(); + AnnotationValueConverterVisitor converter = new AnnotationValueConverterVisitor(); + for (Map.Entry entry : + values.entrySet()) { + AnnotationValue value = entry.getValue(); + convertedValues.add( + new MemberValuePair( + entry.getKey().getSimpleName().toString(), + value.accept(converter, null))); + } - @Override - public Expression visitAnnotation(AnnotationMirror value, Void p) { - return AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(value); + return convertedValues; } - @Override - public Expression visitArray(List value, Void p) { - NodeList valueExpressions = new NodeList<>(); - for (AnnotationValue arrayValue : value) { - valueExpressions.add(arrayValue.accept(this, null)); - } + /** + * Given a fully qualified name, creates a JavaParser {@code Name} structure representing the + * same name. + * + * @param name the fully qualified name to convert + * @return a JavaParser {@code Name} holding {@code name} + */ + private static Name createQualifiedName(String name) { + String[] components = name.split("\\."); + Name result = new Name(components[0]); + for (int i = 1; i < components.length; i++) { + result = new Name(result, components[i]); + } - return new ArrayInitializerExpr(valueExpressions); + return result; } - @Override - public Expression visitBoolean(boolean value, Void p) { - return new BooleanLiteralExpr(value); - } + /** + * A visitor that converts an annotation value from an {@code AnnotationMirror} to a JavaParser + * node that can appear in an {@code AnnotationExpr}. + */ + private static class AnnotationValueConverterVisitor + implements AnnotationValueVisitor { + @Override + public Expression visit(AnnotationValue value, Void p) { + // This is called only if the value couldn't be dispatched to any known type, which + // should never happen. + throw new BugInCF("Unknown annotation value type: " + value); + } - @Override - public Expression visitByte(byte value, Void p) { - // Annotation byte values are automatically cast to the correct type, so using an - // integer literal here works. - return toIntegerLiteralExpr(value); - } + @Override + public Expression visitAnnotation(AnnotationMirror value, Void p) { + return AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( + value); + } - @Override - public Expression visitChar(char value, Void p) { - return new CharLiteralExpr(value); - } + @Override + public Expression visitArray(List value, Void p) { + NodeList valueExpressions = new NodeList<>(); + for (AnnotationValue arrayValue : value) { + valueExpressions.add(arrayValue.accept(this, null)); + } - @Override - public Expression visitDouble(double value, Void p) { - return new DoubleLiteralExpr(value); - } + return new ArrayInitializerExpr(valueExpressions); + } - @Override - public Expression visitEnumConstant(VariableElement value, Void p) { - // The enclosing element of an enum constant is the enum type itself. - TypeElement enumElt = (TypeElement) value.getEnclosingElement(); - String[] components = enumElt.getQualifiedName().toString().split("\\."); - Expression enumName = new NameExpr(components[0]); - for (int i = 1; i < components.length; i++) { - enumName = new FieldAccessExpr(enumName, components[i]); - } - - return new FieldAccessExpr(enumName, value.getSimpleName().toString()); - } + @Override + public Expression visitBoolean(boolean value, Void p) { + return new BooleanLiteralExpr(value); + } - @Override - public Expression visitFloat(float value, Void p) { - return new DoubleLiteralExpr(value + "f"); - } + @Override + public Expression visitByte(byte value, Void p) { + // Annotation byte values are automatically cast to the correct type, so using an + // integer literal here works. + return toIntegerLiteralExpr(value); + } - @Override - public Expression visitInt(int value, Void p) { - return toIntegerLiteralExpr(value); - } + @Override + public Expression visitChar(char value, Void p) { + return new CharLiteralExpr(value); + } - @Override - public Expression visitLong(long value, Void p) { - if (value < 0) { - return new UnaryExpr( - new LongLiteralExpr(Long.toString(-value) + "L"), UnaryExpr.Operator.MINUS); - } + @Override + public Expression visitDouble(double value, Void p) { + return new DoubleLiteralExpr(value); + } - return new LongLiteralExpr(Long.toString(value) + "L"); - } + @Override + public Expression visitEnumConstant(VariableElement value, Void p) { + // The enclosing element of an enum constant is the enum type itself. + TypeElement enumElt = (TypeElement) value.getEnclosingElement(); + String[] components = enumElt.getQualifiedName().toString().split("\\."); + Expression enumName = new NameExpr(components[0]); + for (int i = 1; i < components.length; i++) { + enumName = new FieldAccessExpr(enumName, components[i]); + } - @Override - public Expression visitShort(short value, Void p) { - // Annotation short values are automatically cast to the correct type, so using an - // integer literal here works. - return toIntegerLiteralExpr(value); - } + return new FieldAccessExpr(enumName, value.getSimpleName().toString()); + } - @Override - public Expression visitString(String value, Void p) { - return new StringLiteralExpr(StringEscapeUtils.escapeJava(value)); - } + @Override + public Expression visitFloat(float value, Void p) { + return new DoubleLiteralExpr(value + "f"); + } - @Override - public Expression visitType(TypeMirror value, Void p) { - if (value.getKind() != TypeKind.DECLARED) { - throw new BugInCF("Unexpected type for class expression: " + value); - } - - DeclaredType type = (DeclaredType) value; - ClassOrInterfaceType parsedType; - try { - parsedType = StaticJavaParser.parseClassOrInterfaceType(TypesUtils.getQualifiedName(type)); - } catch (ParseProblemException e) { - throw new BugInCF("Invalid class or interface name: " + value, e); - } - - return new ClassExpr(parsedType); - } + @Override + public Expression visitInt(int value, Void p) { + return toIntegerLiteralExpr(value); + } - @Override - public @Nullable Expression visitUnknown(AnnotationValue value, Void p) { - return null; - } + @Override + public Expression visitLong(long value, Void p) { + if (value < 0) { + return new UnaryExpr( + new LongLiteralExpr(Long.toString(-value) + "L"), UnaryExpr.Operator.MINUS); + } - /** - * Creates a JavaParser expression node representing a literal with the given value. - * - *

          JavaParser represents a negative literal with a {@code UnaryExpr} containing a {@code - * IntegerLiteralExpr}, so this method won't necessarily return an {@code IntegerLiteralExpr}. - * - * @param value the value for the literal - * @return a JavaParser expression representing {@code value} - */ - private Expression toIntegerLiteralExpr(int value) { - if (value < 0) { - return new UnaryExpr( - new IntegerLiteralExpr(Integer.toString(-value)), UnaryExpr.Operator.MINUS); - } + return new LongLiteralExpr(Long.toString(value) + "L"); + } + + @Override + public Expression visitShort(short value, Void p) { + // Annotation short values are automatically cast to the correct type, so using an + // integer literal here works. + return toIntegerLiteralExpr(value); + } + + @Override + public Expression visitString(String value, Void p) { + return new StringLiteralExpr(StringEscapeUtils.escapeJava(value)); + } + + @Override + public Expression visitType(TypeMirror value, Void p) { + if (value.getKind() != TypeKind.DECLARED) { + throw new BugInCF("Unexpected type for class expression: " + value); + } + + DeclaredType type = (DeclaredType) value; + ClassOrInterfaceType parsedType; + try { + parsedType = + StaticJavaParser.parseClassOrInterfaceType( + TypesUtils.getQualifiedName(type)); + } catch (ParseProblemException e) { + throw new BugInCF("Invalid class or interface name: " + value, e); + } + + return new ClassExpr(parsedType); + } + + @Override + public @Nullable Expression visitUnknown(AnnotationValue value, Void p) { + return null; + } + + /** + * Creates a JavaParser expression node representing a literal with the given value. + * + *

          JavaParser represents a negative literal with a {@code UnaryExpr} containing a {@code + * IntegerLiteralExpr}, so this method won't necessarily return an {@code + * IntegerLiteralExpr}. + * + * @param value the value for the literal + * @return a JavaParser expression representing {@code value} + */ + private Expression toIntegerLiteralExpr(int value) { + if (value < 0) { + return new UnaryExpr( + new IntegerLiteralExpr(Integer.toString(-value)), UnaryExpr.Operator.MINUS); + } - return new IntegerLiteralExpr(Integer.toString(value)); + return new IntegerLiteralExpr(Integer.toString(value)); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java index 781b5365064..a2df56b8664 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/AnnotationTransferVisitor.java @@ -9,14 +9,16 @@ import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.type.TypeParameter; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; + /** * A visitor that adds all annotations from a {@code AnnotatedTypeMirror} to the corresponding * JavaParser type, including nested types like array components. @@ -24,62 +26,63 @@ *

          The {@code AnnotatedTypeMirror} is passed as the secondary parameter to the visit methods. */ public class AnnotationTransferVisitor extends VoidVisitorAdapter { - @Override - public void visit(ArrayType target, AnnotatedTypeMirror type) { - // type can also be a wildcard, in which case don't try to transfer component - // annotations (there can't be any) - if (type.getKind() == TypeKind.ARRAY) { - target.getComponentType().accept(this, ((AnnotatedArrayType) type).getComponentType()); - } - transferAnnotations(type, target); - } - - @Override - public void visit(ClassOrInterfaceType target, AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; - if (target.getTypeArguments().isPresent()) { - NodeList types = target.getTypeArguments().get(); - for (int i = 0; i < types.size(); i++) { - types.get(i).accept(this, declaredType.getTypeArguments().get(i)); + @Override + public void visit(ArrayType target, AnnotatedTypeMirror type) { + // type can also be a wildcard, in which case don't try to transfer component + // annotations (there can't be any) + if (type.getKind() == TypeKind.ARRAY) { + target.getComponentType().accept(this, ((AnnotatedArrayType) type).getComponentType()); } - } + transferAnnotations(type, target); } - transferAnnotations(type, target); - } + @Override + public void visit(ClassOrInterfaceType target, AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; + if (target.getTypeArguments().isPresent()) { + NodeList types = target.getTypeArguments().get(); + for (int i = 0; i < types.size(); i++) { + types.get(i).accept(this, declaredType.getTypeArguments().get(i)); + } + } + } - @Override - public void visit(PrimitiveType target, AnnotatedTypeMirror type) { - transferAnnotations(type, target); - } + transferAnnotations(type, target); + } - @Override - public void visit(TypeParameter target, AnnotatedTypeMirror type) { - AnnotatedTypeVariable annotatedTypeVar = (AnnotatedTypeVariable) type; - NodeList bounds = target.getTypeBound(); - if (bounds.size() == 1) { - bounds.get(0).accept(this, annotatedTypeVar.getUpperBound()); + @Override + public void visit(PrimitiveType target, AnnotatedTypeMirror type) { + transferAnnotations(type, target); } - } - /** - * Transfers annotations from {@code annotatedType} to {@code target}. Does nothing if {@code - * annotatedType} is null. - * - * @param annotatedType type with annotations to transfer - * @param target a JavaParser node representing the type to transfer annotations to - */ - private void transferAnnotations( - @Nullable AnnotatedTypeMirror annotatedType, NodeWithAnnotations target) { - if (annotatedType == null) { - return; + @Override + public void visit(TypeParameter target, AnnotatedTypeMirror type) { + AnnotatedTypeVariable annotatedTypeVar = (AnnotatedTypeVariable) type; + NodeList bounds = target.getTypeBound(); + if (bounds.size() == 1) { + bounds.get(0).accept(this, annotatedTypeVar.getUpperBound()); + } } - for (AnnotationMirror annotation : annotatedType.getAnnotations()) { - AnnotationExpr convertedAnnotation = - AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(annotation); - target.addAnnotation(convertedAnnotation); + /** + * Transfers annotations from {@code annotatedType} to {@code target}. Does nothing if {@code + * annotatedType} is null. + * + * @param annotatedType type with annotations to transfer + * @param target a JavaParser node representing the type to transfer annotations to + */ + private void transferAnnotations( + @Nullable AnnotatedTypeMirror annotatedType, NodeWithAnnotations target) { + if (annotatedType == null) { + return; + } + + for (AnnotationMirror annotation : annotatedType.getAnnotations()) { + AnnotationExpr convertedAnnotation = + AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr( + annotation); + target.addAnnotation(convertedAnnotation); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java index 2278a284c34..a40bce364f4 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/DefaultJointVisitor.java @@ -139,269 +139,271 @@ * this class, extend it and override a {@code process} method. */ public class DefaultJointVisitor extends JointJavacJavaParserVisitor { - @Override - public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) {} + @Override + public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) {} - @Override - public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) {} + @Override + public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) {} - @Override - public void processAnnotation( - AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) {} + @Override + public void processAnnotation( + AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) {} - @Override - public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) {} + @Override + public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) {} - @Override - public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) {} + @Override + public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) {} - @Override - public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) {} + @Override + public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) {} - @Override - public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) {} + @Override + public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) {} - @Override - public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) {} + @Override + public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) {} - @Override - public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) {} + @Override + public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) {} - @Override - public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) {} + @Override + public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) {} - @Override - public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) {} + @Override + public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) {} - @Override - public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) {} + @Override + public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) {} - @Override - public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) {} + @Override + public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) {} - @Override - public void processCatch(CatchTree javacTree, CatchClause javaParserNode) {} + @Override + public void processCatch(CatchTree javacTree, CatchClause javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) {} - @Override - public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) {} + @Override + public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) {} - @Override - public void processCompilationUnit( - CompilationUnitTree javacTree, CompilationUnit javaParserNode) {} + @Override + public void processCompilationUnit( + CompilationUnitTree javacTree, CompilationUnit javaParserNode) {} - @Override - public void processConditionalExpression( - ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) {} + @Override + public void processConditionalExpression( + ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) {} - @Override - public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) {} + @Override + public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) {} - @Override - public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) {} + @Override + public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) {} - @Override - public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) {} + @Override + public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) {} - @Override - public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) {} + @Override + public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) {} - @Override - public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) {} + @Override + public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) {} - @Override - public void processExpressionStatemen( - ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) {} + @Override + public void processExpressionStatemen( + ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) {} - @Override - public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) {} + @Override + public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) {} - @Override - public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) {} + @Override + public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) {} - @Override - public void processIf(IfTree javacTree, IfStmt javaParserNode) {} + @Override + public void processIf(IfTree javacTree, IfStmt javaParserNode) {} - @Override - public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) {} + @Override + public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) {} - @Override - public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) {} + @Override + public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) {} - @Override - public void processIntersectionType( - IntersectionTypeTree javacTree, IntersectionType javaParserNode) {} + @Override + public void processIntersectionType( + IntersectionTypeTree javacTree, IntersectionType javaParserNode) {} - @Override - public void processLabeledStatement(LabeledStatementTree javacTree, LabeledStmt javaParserNode) {} + @Override + public void processLabeledStatement( + LabeledStatementTree javacTree, LabeledStmt javaParserNode) {} - @Override - public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) {} + @Override + public void processLambdaExpression( + LambdaExpressionTree javacTree, LambdaExpr javaParserNode) {} - @Override - public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) {} + @Override + public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) {} - @Override - public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) {} + @Override + public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) {} - @Override - public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) {} + @Override + public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) {} - @Override - public void processMemberReference( - MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) {} + @Override + public void processMemberReference( + MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) {} - @Override - public void processMemberSelect( - MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) {} + @Override + public void processMemberSelect( + MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) {} - @Override - public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) {} + @Override + public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) {} - @Override - public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) {} + @Override + public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) {} - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) {} + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) {} - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, MethodCallExpr javaParserNode) {} + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, MethodCallExpr javaParserNode) {} - @Override - public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) {} + @Override + public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) {} - @Override - public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) {} + @Override + public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) {} - @Override - public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) {} + @Override + public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) {} - @Override - public void processOther(Tree javacTree, Node javaParserNode) {} + @Override + public void processOther(Tree javacTree, Node javaParserNode) {} - @Override - public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) {} + @Override + public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) {} - @Override - public void processParameterizedType( - ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) {} + @Override + public void processParameterizedType( + ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) {} - @Override - public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) {} + @Override + public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) {} - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) {} + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) {} - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) {} + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) {} - @Override - public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) {} + @Override + public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) {} - @Override - public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) {} + @Override + public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) {} - @Override - public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) {} + @Override + public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) {} - @Override - public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) {} + @Override + public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) {} - @Override - public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) {} + @Override + public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) {} - @Override - public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) {} + @Override + public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) {} - @Override - public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) {} + @Override + public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) {} - @Override - public void processTry(TryTree javacTree, TryStmt javaParserNode) {} + @Override + public void processTry(TryTree javacTree, TryStmt javaParserNode) {} - @Override - public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) {} + @Override + public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) {} - @Override - public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) {} + @Override + public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) {} - @Override - public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) {} + @Override + public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) {} - @Override - public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) {} + @Override + public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) {} - @Override - public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) {} + @Override + public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, Parameter javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, Parameter javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) {} - @Override - public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) {} + @Override + public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) {} - @Override - public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) {} + @Override + public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) {} - @Override - public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) {} + @Override + public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) {} - @Override - public void processYield(Tree javacTree, YieldStmt javaParserNode) {} + @Override + public void processYield(Tree javacTree, YieldStmt javaParserNode) {} - @Override - public void processCompoundAssignment( - CompoundAssignmentTree javacTree, AssignExpr javaParserNode) {} + @Override + public void processCompoundAssignment( + CompoundAssignmentTree javacTree, AssignExpr javaParserNode) {} } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java index 971fc8af9b2..3eb60ecccf4 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/DoubleJavaParserVisitor.java @@ -100,6 +100,7 @@ import com.github.javaparser.ast.type.VoidType; import com.github.javaparser.ast.type.WildcardType; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + import java.util.List; /** @@ -117,801 +118,804 @@ @SuppressWarnings("optional:method.invocation") // parallel structure of two data structures public abstract class DoubleJavaParserVisitor extends VoidVisitorAdapter { - /** Create a DoubleJavaParserVisitor. */ - public DoubleJavaParserVisitor() {} - - /** - * Default action performed on all pairs of nodes from matching ASTs. - * - * @param node1 first node in pair - * @param node2 second node in pair - * @param the Node type of {@code node1} and {@code node2} - */ - public abstract void defaultAction(T node1, T node2); - - /** - * Given two lists with the same size where corresponding elements represent nodes with - * almost-identical ASTs as specified in this class's description, visits each pair of - * corresponding elements in order. - * - * @param list1 first list of nodes - * @param list2 second list of nodes, which has the same size as the first list - */ - private void visitLists(List list1, List list2) { - if (list1.size() != list2.size()) { - throw new Error( - String.format( - "%s.visitLists(%s [size %d], %s [size %d])", - this.getClass().getCanonicalName(), list1, list1.size(), list2, list2.size())); - } - for (int i = 0; i < list1.size(); i++) { - list1.get(i).accept(this, list2.get(i)); - } - } - - @Override - public void visit(AnnotationDeclaration node1, Node other) { - AnnotationDeclaration node2 = (AnnotationDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(AnnotationMemberDeclaration node1, Node other) { - AnnotationMemberDeclaration node2 = (AnnotationMemberDeclaration) other; - defaultAction(node1, node2); - node1.getDefaultValue().ifPresent(dv -> dv.accept(this, node2.getDefaultValue().get())); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(ArrayAccessExpr node1, Node other) { - ArrayAccessExpr node2 = (ArrayAccessExpr) other; - defaultAction(node1, node2); - node1.getIndex().accept(this, node2.getIndex()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ArrayCreationExpr node1, Node other) { - ArrayCreationExpr node2 = (ArrayCreationExpr) other; - defaultAction(node1, node2); - node1.getElementType().accept(this, node2.getElementType()); - node1.getInitializer().ifPresent(init -> init.accept(this, node2.getInitializer().get())); - visitLists(node1.getLevels(), node2.getLevels()); - } - - @Override - public void visit(ArrayInitializerExpr node1, Node other) { - ArrayInitializerExpr node2 = (ArrayInitializerExpr) other; - defaultAction(node1, node2); - visitLists(node1.getValues(), node2.getValues()); - } - - @Override - public void visit(AssertStmt node1, Node other) { - AssertStmt node2 = (AssertStmt) other; - defaultAction(node1, node2); - node1.getCheck().accept(this, node2.getCheck()); - node1.getMessage().ifPresent(m -> m.accept(this, node2.getMessage().get())); - } - - @Override - public void visit(AssignExpr node1, Node other) { - AssignExpr node2 = (AssignExpr) other; - defaultAction(node1, node2); - node1.getTarget().accept(this, node2.getTarget()); - node1.getValue().accept(this, node2.getValue()); - } - - @Override - public void visit(BinaryExpr node1, Node other) { - BinaryExpr node2 = (BinaryExpr) other; - defaultAction(node1, node2); - node1.getLeft().accept(this, node2.getLeft()); - node1.getRight().accept(this, node2.getRight()); - } - - @Override - public void visit(BlockComment node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(BlockStmt node1, Node other) { - BlockStmt node2 = (BlockStmt) other; - defaultAction(node1, node2); - visitLists(node1.getStatements(), node2.getStatements()); - } - - @Override - public void visit(BooleanLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(BreakStmt node1, Node other) { - BreakStmt node2 = (BreakStmt) other; - defaultAction(node1, node2); - node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); - } - - @Override - public void visit(CastExpr node1, Node other) { - CastExpr node2 = (CastExpr) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(CatchClause node1, Node other) { - CatchClause node2 = (CatchClause) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getParameter().accept(this, node2.getParameter()); - } - - @Override - public void visit(CharLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ClassExpr node1, Node other) { - ClassExpr node2 = (ClassExpr) other; - defaultAction(node1, node2); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(ClassOrInterfaceDeclaration node1, Node other) { - ClassOrInterfaceDeclaration node2 = (ClassOrInterfaceDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getExtendedTypes(), node2.getExtendedTypes()); - visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ClassOrInterfaceType node1, Node other) { - ClassOrInterfaceType node2 = (ClassOrInterfaceType) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getScope().ifPresent(s -> s.accept(this, node2.getScope().get())); - node1.getTypeArguments().ifPresent(targs -> visitLists(targs, node2.getTypeArguments().get())); - } - - @Override - public void visit(CompilationUnit node1, Node other) { - CompilationUnit node2 = (CompilationUnit) other; - defaultAction(node1, node2); - node1.getModule().ifPresent(m -> m.accept(this, node2.getModule().get())); - node1 - .getPackageDeclaration() - .ifPresent(pd -> pd.accept(this, node2.getPackageDeclaration().get())); - visitLists(node1.getTypes(), node2.getTypes()); - } - - @Override - public void visit(ConditionalExpr node1, Node other) { - ConditionalExpr node2 = (ConditionalExpr) other; - defaultAction(node1, node2); - node1.getCondition().accept(this, node2.getCondition()); - node1.getElseExpr().accept(this, node2.getElseExpr()); - node1.getThenExpr().accept(this, node2.getThenExpr()); - } - - @Override - public void visit(ConstructorDeclaration node1, Node other) { - ConstructorDeclaration node2 = (ConstructorDeclaration) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - visitLists(node1.getParameters(), node2.getParameters()); - if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { - node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); - } - - visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - } - - @Override - public void visit(CompactConstructorDeclaration node1, Node other) { - CompactConstructorDeclaration node2 = (CompactConstructorDeclaration) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - - visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - } - - @Override - public void visit(ContinueStmt node1, Node other) { - ContinueStmt node2 = (ContinueStmt) other; - defaultAction(node1, node2); - node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); - } - - @Override - public void visit(DoStmt node1, Node other) { - DoStmt node2 = (DoStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getCondition().accept(this, node2.getCondition()); - } - - @Override - public void visit(DoubleLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(EmptyStmt node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(EnclosedExpr node1, Node other) { - EnclosedExpr node2 = (EnclosedExpr) other; - defaultAction(node1, node2); - node1.getInner().accept(this, node2.getInner()); - } - - @Override - public void visit(EnumConstantDeclaration node1, Node other) { - EnumConstantDeclaration node2 = (EnumConstantDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getArguments(), node2.getArguments()); - visitLists(node1.getClassBody(), node2.getClassBody()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(EnumDeclaration node1, Node other) { - EnumDeclaration node2 = (EnumDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getEntries(), node2.getEntries()); - visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ExplicitConstructorInvocationStmt node1, Node other) { - ExplicitConstructorInvocationStmt node2 = (ExplicitConstructorInvocationStmt) other; - defaultAction(node1, node2); - visitLists(node1.getArguments(), node2.getArguments()); - node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(ExpressionStmt node1, Node other) { - ExpressionStmt node2 = (ExpressionStmt) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(FieldAccessExpr node1, Node other) { - FieldAccessExpr node2 = (FieldAccessExpr) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getScope().accept(this, node2.getScope()); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(FieldDeclaration node1, Node other) { - FieldDeclaration node2 = (FieldDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - visitLists(node1.getVariables(), node2.getVariables()); - } - - @Override - public void visit(ForEachStmt node1, Node other) { - ForEachStmt node2 = (ForEachStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getIterable().accept(this, node2.getIterable()); - node1.getVariable().accept(this, node2.getVariable()); - } - - @Override - public void visit(ForStmt node1, Node other) { - ForStmt node2 = (ForStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getCompare().ifPresent(l -> l.accept(this, node2.getCompare().get())); - visitLists(node1.getInitialization(), node2.getInitialization()); - visitLists(node1.getUpdate(), node2.getUpdate()); - } - - @Override - public void visit(IfStmt node1, Node other) { - IfStmt node2 = (IfStmt) other; - defaultAction(node1, node2); - node1.getCondition().accept(this, node2.getCondition()); - node1.getElseStmt().ifPresent(l -> l.accept(this, node2.getElseStmt().get())); - node1.getThenStmt().accept(this, node2.getThenStmt()); - } - - @Override - public void visit(InitializerDeclaration node1, Node other) { - InitializerDeclaration node2 = (InitializerDeclaration) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - } - - @Override - public void visit(InstanceOfExpr node1, Node other) { - InstanceOfExpr node2 = (InstanceOfExpr) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(IntegerLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(JavadocComment node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(LabeledStmt node1, Node other) { - LabeledStmt node2 = (LabeledStmt) other; - defaultAction(node1, node2); - node1.getLabel().accept(this, node2.getLabel()); - node1.getStatement().accept(this, node2.getStatement()); - } - - @Override - public void visit(LineComment node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(LongLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(MarkerAnnotationExpr node1, Node other) { - MarkerAnnotationExpr node2 = (MarkerAnnotationExpr) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(MemberValuePair node1, Node other) { - MemberValuePair node2 = (MemberValuePair) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getValue().accept(this, node2.getName()); - } - - @Override - public void visit(MethodCallExpr node1, Node other) { - MethodCallExpr node2 = (MethodCallExpr) other; - defaultAction(node1, node2); - visitLists(node1.getArguments(), node2.getArguments()); - node1.getName().accept(this, node2.getName()); - node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(MethodDeclaration node1, Node other) { - MethodDeclaration node2 = (MethodDeclaration) other; - defaultAction(node1, node2); - node1.getBody().ifPresent(l -> l.accept(this, node2.getBody().get())); - node1.getType().accept(this, node2.getType()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - visitLists(node1.getParameters(), node2.getParameters()); - if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { - node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); - } - - visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - } - - @Override - public void visit(NameExpr node1, Node other) { - NameExpr node2 = (NameExpr) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(NormalAnnotationExpr node1, Node other) { - NormalAnnotationExpr node2 = (NormalAnnotationExpr) other; - defaultAction(node1, node2); - visitLists(node1.getPairs(), node2.getPairs()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(NullLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ObjectCreationExpr node1, Node other) { - ObjectCreationExpr node2 = (ObjectCreationExpr) other; - defaultAction(node1, node2); - node1 - .getAnonymousClassBody() - .ifPresent(l -> visitLists(l, node2.getAnonymousClassBody().get())); - visitLists(node1.getArguments(), node2.getArguments()); - node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); - node1.getType().accept(this, node2.getType()); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(PackageDeclaration node1, Node other) { - PackageDeclaration node2 = (PackageDeclaration) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(Parameter node1, Node other) { - Parameter node2 = (Parameter) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(PrimitiveType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(Name node1, Node other) { - Name node2 = (Name) other; - defaultAction(node1, node2); - node1.getQualifier().ifPresent(l -> l.accept(this, node2.getQualifier().get())); - } - - @Override - public void visit(SimpleName node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ArrayType node1, Node other) { - ArrayType node2 = (ArrayType) other; - defaultAction(node1, node2); - node1.getComponentType().accept(this, node2.getComponentType()); - } - - @Override - public void visit(ArrayCreationLevel node1, Node other) { - ArrayCreationLevel node2 = (ArrayCreationLevel) other; - defaultAction(node1, node2); - node1.getDimension().ifPresent(l -> l.accept(this, node2.getDimension().get())); - } - - @Override - public void visit(IntersectionType node1, Node other) { - IntersectionType node2 = (IntersectionType) other; - defaultAction(node1, node2); - visitLists(node1.getElements(), node2.getElements()); - } - - @Override - public void visit(UnionType node1, Node other) { - UnionType node2 = (UnionType) other; - defaultAction(node1, node2); - visitLists(node1.getElements(), node2.getElements()); - } - - @Override - public void visit(RecordDeclaration node1, Node other) { - RecordDeclaration node2 = (RecordDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); - visitLists(node1.getTypeParameters(), node2.getTypeParameters()); - visitLists(node1.getParameters(), node2.getParameters()); - visitLists(node1.getMembers(), node2.getMembers()); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { - node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); - } - } - - @Override - public void visit(ReturnStmt node1, Node other) { - ReturnStmt node2 = (ReturnStmt) other; - defaultAction(node1, node2); - node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); - } - - @Override - public void visit(SingleMemberAnnotationExpr node1, Node other) { - SingleMemberAnnotationExpr node2 = (SingleMemberAnnotationExpr) other; - defaultAction(node1, node2); - node1.getMemberValue().accept(this, node2.getMemberValue()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(StringLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(SuperExpr node1, Node other) { - SuperExpr node2 = (SuperExpr) other; - defaultAction(node1, node2); - node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); - } - - @Override - public void visit(SwitchEntry node1, Node other) { - SwitchEntry node2 = (SwitchEntry) other; - defaultAction(node1, node2); - visitLists(node1.getLabels(), node2.getLabels()); - visitLists(node1.getStatements(), node2.getStatements()); - } - - @Override - public void visit(SwitchStmt node1, Node other) { - SwitchStmt node2 = (SwitchStmt) other; - defaultAction(node1, node2); - visitLists(node1.getEntries(), node2.getEntries()); - node1.getSelector().accept(this, node2.getSelector()); - } - - @Override - public void visit(SynchronizedStmt node1, Node other) { - SynchronizedStmt node2 = (SynchronizedStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(ThisExpr node1, Node other) { - ThisExpr node2 = (ThisExpr) other; - defaultAction(node1, node2); - node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); - } - - @Override - public void visit(ThrowStmt node1, Node other) { - ThrowStmt node2 = (ThrowStmt) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(TryStmt node1, Node other) { - TryStmt node2 = (TryStmt) other; - defaultAction(node1, node2); - visitLists(node1.getCatchClauses(), node2.getCatchClauses()); - node1.getFinallyBlock().ifPresent(l -> l.accept(this, node2.getFinallyBlock().get())); - visitLists(node1.getResources(), node2.getResources()); - node1.getTryBlock().accept(this, node2.getTryBlock()); - } - - @Override - public void visit(LocalClassDeclarationStmt node1, Node other) { - LocalClassDeclarationStmt node2 = (LocalClassDeclarationStmt) other; - defaultAction(node1, node2); - node1.getClassDeclaration().accept(this, node2.getClassDeclaration()); - } - - @Override - public void visit(LocalRecordDeclarationStmt node1, Node other) { - LocalRecordDeclarationStmt node2 = (LocalRecordDeclarationStmt) other; - defaultAction(node1, node2); - node1.getRecordDeclaration().accept(this, node2.getRecordDeclaration()); - } - - @Override - public void visit(TypeParameter node1, Node other) { - TypeParameter node2 = (TypeParameter) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - // Since ajava files and its corresponding Java file may differ in whether they contain a - // type bound, only visit type bounds if they're present in both nodes. - if (node1.getTypeBound().isEmpty() == node2.getTypeBound().isEmpty()) { - visitLists(node1.getTypeBound(), node2.getTypeBound()); - } - } - - @Override - public void visit(UnaryExpr node1, Node other) { - UnaryExpr node2 = (UnaryExpr) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } - - @Override - public void visit(UnknownType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(VariableDeclarationExpr node1, Node other) { - VariableDeclarationExpr node2 = (VariableDeclarationExpr) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - visitLists(node1.getVariables(), node2.getVariables()); - } - - @Override - public void visit(VariableDeclarator node1, Node other) { - VariableDeclarator node2 = (VariableDeclarator) other; - defaultAction(node1, node2); - node1.getInitializer().ifPresent(l -> l.accept(this, node2.getInitializer().get())); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(VoidType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(WhileStmt node1, Node other) { - WhileStmt node2 = (WhileStmt) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - node1.getCondition().accept(this, node2.getCondition()); - } - - @Override - public void visit(WildcardType node1, Node other) { - WildcardType node2 = (WildcardType) other; - defaultAction(node1, node2); - node1.getExtendedType().ifPresent(l -> l.accept(this, node2.getExtendedType().get())); - node1.getSuperType().ifPresent(l -> l.accept(this, node2.getSuperType().get())); - } - - @Override - public void visit(LambdaExpr node1, Node other) { - LambdaExpr node2 = (LambdaExpr) other; - defaultAction(node1, node2); - node1.getBody().accept(this, node2.getBody()); - visitLists(node1.getParameters(), node2.getParameters()); - } - - @Override - public void visit(MethodReferenceExpr node1, Node other) { - MethodReferenceExpr node2 = (MethodReferenceExpr) other; - defaultAction(node1, node2); - node1.getScope().accept(this, node2.getScope()); - node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); - } - - @Override - public void visit(TypeExpr node1, Node other) { - TypeExpr node2 = (TypeExpr) other; - defaultAction(node1, node2); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(ImportDeclaration node1, Node other) { - ImportDeclaration node2 = (ImportDeclaration) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleDeclaration node1, Node other) { - ModuleDeclaration node2 = (ModuleDeclaration) other; - defaultAction(node1, node2); - visitLists(node1.getDirectives(), node2.getDirectives()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleRequiresDirective node1, Node other) { - ModuleRequiresDirective node2 = (ModuleRequiresDirective) other; - defaultAction(node1, node2); - visitLists(node1.getModifiers(), node2.getModifiers()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleExportsDirective node1, Node other) { - ModuleExportsDirective node2 = (ModuleExportsDirective) other; - defaultAction(node1, node2); - visitLists(node1.getModuleNames(), node2.getModuleNames()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleProvidesDirective node1, Node other) { - ModuleProvidesDirective node2 = (ModuleProvidesDirective) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - visitLists(node1.getWith(), node2.getWith()); - } - - @Override - public void visit(ModuleUsesDirective node1, Node other) { - ModuleUsesDirective node2 = (ModuleUsesDirective) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(ModuleOpensDirective node1, Node other) { - ModuleOpensDirective node2 = (ModuleOpensDirective) other; - defaultAction(node1, node2); - visitLists(node1.getModuleNames(), node2.getModuleNames()); - node1.getName().accept(this, node2.getName()); - } - - @Override - public void visit(UnparsableStmt node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(ReceiverParameter node1, Node other) { - ReceiverParameter node2 = (ReceiverParameter) other; - defaultAction(node1, node2); - node1.getName().accept(this, node2.getName()); - node1.getType().accept(this, node2.getType()); - } - - @Override - public void visit(VarType node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(Modifier node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(SwitchExpr node1, Node other) { - SwitchExpr node2 = (SwitchExpr) other; - defaultAction(node1, node2); - visitLists(node1.getEntries(), node2.getEntries()); - node1.getSelector().accept(this, node2.getSelector()); - } - - @Override - public void visit(TextBlockLiteralExpr node1, Node other) { - defaultAction(node1, other); - } - - @Override - public void visit(YieldStmt node1, Node other) { - YieldStmt node2 = (YieldStmt) other; - defaultAction(node1, node2); - node1.getExpression().accept(this, node2.getExpression()); - } + /** Create a DoubleJavaParserVisitor. */ + public DoubleJavaParserVisitor() {} + + /** + * Default action performed on all pairs of nodes from matching ASTs. + * + * @param node1 first node in pair + * @param node2 second node in pair + * @param the Node type of {@code node1} and {@code node2} + */ + public abstract void defaultAction(T node1, T node2); + + /** + * Given two lists with the same size where corresponding elements represent nodes with + * almost-identical ASTs as specified in this class's description, visits each pair of + * corresponding elements in order. + * + * @param list1 first list of nodes + * @param list2 second list of nodes, which has the same size as the first list + */ + private void visitLists(List list1, List list2) { + if (list1.size() != list2.size()) { + throw new Error( + String.format( + "%s.visitLists(%s [size %d], %s [size %d])", + this.getClass().getCanonicalName(), + list1, + list1.size(), + list2, + list2.size())); + } + for (int i = 0; i < list1.size(); i++) { + list1.get(i).accept(this, list2.get(i)); + } + } + + @Override + public void visit(AnnotationDeclaration node1, Node other) { + AnnotationDeclaration node2 = (AnnotationDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(AnnotationMemberDeclaration node1, Node other) { + AnnotationMemberDeclaration node2 = (AnnotationMemberDeclaration) other; + defaultAction(node1, node2); + node1.getDefaultValue().ifPresent(dv -> dv.accept(this, node2.getDefaultValue().get())); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(ArrayAccessExpr node1, Node other) { + ArrayAccessExpr node2 = (ArrayAccessExpr) other; + defaultAction(node1, node2); + node1.getIndex().accept(this, node2.getIndex()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ArrayCreationExpr node1, Node other) { + ArrayCreationExpr node2 = (ArrayCreationExpr) other; + defaultAction(node1, node2); + node1.getElementType().accept(this, node2.getElementType()); + node1.getInitializer().ifPresent(init -> init.accept(this, node2.getInitializer().get())); + visitLists(node1.getLevels(), node2.getLevels()); + } + + @Override + public void visit(ArrayInitializerExpr node1, Node other) { + ArrayInitializerExpr node2 = (ArrayInitializerExpr) other; + defaultAction(node1, node2); + visitLists(node1.getValues(), node2.getValues()); + } + + @Override + public void visit(AssertStmt node1, Node other) { + AssertStmt node2 = (AssertStmt) other; + defaultAction(node1, node2); + node1.getCheck().accept(this, node2.getCheck()); + node1.getMessage().ifPresent(m -> m.accept(this, node2.getMessage().get())); + } + + @Override + public void visit(AssignExpr node1, Node other) { + AssignExpr node2 = (AssignExpr) other; + defaultAction(node1, node2); + node1.getTarget().accept(this, node2.getTarget()); + node1.getValue().accept(this, node2.getValue()); + } + + @Override + public void visit(BinaryExpr node1, Node other) { + BinaryExpr node2 = (BinaryExpr) other; + defaultAction(node1, node2); + node1.getLeft().accept(this, node2.getLeft()); + node1.getRight().accept(this, node2.getRight()); + } + + @Override + public void visit(BlockComment node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(BlockStmt node1, Node other) { + BlockStmt node2 = (BlockStmt) other; + defaultAction(node1, node2); + visitLists(node1.getStatements(), node2.getStatements()); + } + + @Override + public void visit(BooleanLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(BreakStmt node1, Node other) { + BreakStmt node2 = (BreakStmt) other; + defaultAction(node1, node2); + node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); + } + + @Override + public void visit(CastExpr node1, Node other) { + CastExpr node2 = (CastExpr) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(CatchClause node1, Node other) { + CatchClause node2 = (CatchClause) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getParameter().accept(this, node2.getParameter()); + } + + @Override + public void visit(CharLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ClassExpr node1, Node other) { + ClassExpr node2 = (ClassExpr) other; + defaultAction(node1, node2); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(ClassOrInterfaceDeclaration node1, Node other) { + ClassOrInterfaceDeclaration node2 = (ClassOrInterfaceDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getExtendedTypes(), node2.getExtendedTypes()); + visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ClassOrInterfaceType node1, Node other) { + ClassOrInterfaceType node2 = (ClassOrInterfaceType) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getScope().ifPresent(s -> s.accept(this, node2.getScope().get())); + node1.getTypeArguments() + .ifPresent(targs -> visitLists(targs, node2.getTypeArguments().get())); + } + + @Override + public void visit(CompilationUnit node1, Node other) { + CompilationUnit node2 = (CompilationUnit) other; + defaultAction(node1, node2); + node1.getModule().ifPresent(m -> m.accept(this, node2.getModule().get())); + node1.getPackageDeclaration() + .ifPresent(pd -> pd.accept(this, node2.getPackageDeclaration().get())); + visitLists(node1.getTypes(), node2.getTypes()); + } + + @Override + public void visit(ConditionalExpr node1, Node other) { + ConditionalExpr node2 = (ConditionalExpr) other; + defaultAction(node1, node2); + node1.getCondition().accept(this, node2.getCondition()); + node1.getElseExpr().accept(this, node2.getElseExpr()); + node1.getThenExpr().accept(this, node2.getThenExpr()); + } + + @Override + public void visit(ConstructorDeclaration node1, Node other) { + ConstructorDeclaration node2 = (ConstructorDeclaration) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + visitLists(node1.getParameters(), node2.getParameters()); + if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { + node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); + } + + visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + } + + @Override + public void visit(CompactConstructorDeclaration node1, Node other) { + CompactConstructorDeclaration node2 = (CompactConstructorDeclaration) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + + visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + } + + @Override + public void visit(ContinueStmt node1, Node other) { + ContinueStmt node2 = (ContinueStmt) other; + defaultAction(node1, node2); + node1.getLabel().ifPresent(l -> l.accept(this, node2.getLabel().get())); + } + + @Override + public void visit(DoStmt node1, Node other) { + DoStmt node2 = (DoStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getCondition().accept(this, node2.getCondition()); + } + + @Override + public void visit(DoubleLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(EmptyStmt node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(EnclosedExpr node1, Node other) { + EnclosedExpr node2 = (EnclosedExpr) other; + defaultAction(node1, node2); + node1.getInner().accept(this, node2.getInner()); + } + + @Override + public void visit(EnumConstantDeclaration node1, Node other) { + EnumConstantDeclaration node2 = (EnumConstantDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getArguments(), node2.getArguments()); + visitLists(node1.getClassBody(), node2.getClassBody()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(EnumDeclaration node1, Node other) { + EnumDeclaration node2 = (EnumDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getEntries(), node2.getEntries()); + visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ExplicitConstructorInvocationStmt node1, Node other) { + ExplicitConstructorInvocationStmt node2 = (ExplicitConstructorInvocationStmt) other; + defaultAction(node1, node2); + visitLists(node1.getArguments(), node2.getArguments()); + node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(ExpressionStmt node1, Node other) { + ExpressionStmt node2 = (ExpressionStmt) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(FieldAccessExpr node1, Node other) { + FieldAccessExpr node2 = (FieldAccessExpr) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getScope().accept(this, node2.getScope()); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(FieldDeclaration node1, Node other) { + FieldDeclaration node2 = (FieldDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + visitLists(node1.getVariables(), node2.getVariables()); + } + + @Override + public void visit(ForEachStmt node1, Node other) { + ForEachStmt node2 = (ForEachStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getIterable().accept(this, node2.getIterable()); + node1.getVariable().accept(this, node2.getVariable()); + } + + @Override + public void visit(ForStmt node1, Node other) { + ForStmt node2 = (ForStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getCompare().ifPresent(l -> l.accept(this, node2.getCompare().get())); + visitLists(node1.getInitialization(), node2.getInitialization()); + visitLists(node1.getUpdate(), node2.getUpdate()); + } + + @Override + public void visit(IfStmt node1, Node other) { + IfStmt node2 = (IfStmt) other; + defaultAction(node1, node2); + node1.getCondition().accept(this, node2.getCondition()); + node1.getElseStmt().ifPresent(l -> l.accept(this, node2.getElseStmt().get())); + node1.getThenStmt().accept(this, node2.getThenStmt()); + } + + @Override + public void visit(InitializerDeclaration node1, Node other) { + InitializerDeclaration node2 = (InitializerDeclaration) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + } + + @Override + public void visit(InstanceOfExpr node1, Node other) { + InstanceOfExpr node2 = (InstanceOfExpr) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(IntegerLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(JavadocComment node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(LabeledStmt node1, Node other) { + LabeledStmt node2 = (LabeledStmt) other; + defaultAction(node1, node2); + node1.getLabel().accept(this, node2.getLabel()); + node1.getStatement().accept(this, node2.getStatement()); + } + + @Override + public void visit(LineComment node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(LongLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(MarkerAnnotationExpr node1, Node other) { + MarkerAnnotationExpr node2 = (MarkerAnnotationExpr) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(MemberValuePair node1, Node other) { + MemberValuePair node2 = (MemberValuePair) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getValue().accept(this, node2.getName()); + } + + @Override + public void visit(MethodCallExpr node1, Node other) { + MethodCallExpr node2 = (MethodCallExpr) other; + defaultAction(node1, node2); + visitLists(node1.getArguments(), node2.getArguments()); + node1.getName().accept(this, node2.getName()); + node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(MethodDeclaration node1, Node other) { + MethodDeclaration node2 = (MethodDeclaration) other; + defaultAction(node1, node2); + node1.getBody().ifPresent(l -> l.accept(this, node2.getBody().get())); + node1.getType().accept(this, node2.getType()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + visitLists(node1.getParameters(), node2.getParameters()); + if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { + node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); + } + + visitLists(node1.getThrownExceptions(), node2.getThrownExceptions()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + } + + @Override + public void visit(NameExpr node1, Node other) { + NameExpr node2 = (NameExpr) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(NormalAnnotationExpr node1, Node other) { + NormalAnnotationExpr node2 = (NormalAnnotationExpr) other; + defaultAction(node1, node2); + visitLists(node1.getPairs(), node2.getPairs()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(NullLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ObjectCreationExpr node1, Node other) { + ObjectCreationExpr node2 = (ObjectCreationExpr) other; + defaultAction(node1, node2); + node1.getAnonymousClassBody() + .ifPresent(l -> visitLists(l, node2.getAnonymousClassBody().get())); + visitLists(node1.getArguments(), node2.getArguments()); + node1.getScope().ifPresent(l -> l.accept(this, node2.getScope().get())); + node1.getType().accept(this, node2.getType()); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(PackageDeclaration node1, Node other) { + PackageDeclaration node2 = (PackageDeclaration) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(Parameter node1, Node other) { + Parameter node2 = (Parameter) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(PrimitiveType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(Name node1, Node other) { + Name node2 = (Name) other; + defaultAction(node1, node2); + node1.getQualifier().ifPresent(l -> l.accept(this, node2.getQualifier().get())); + } + + @Override + public void visit(SimpleName node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ArrayType node1, Node other) { + ArrayType node2 = (ArrayType) other; + defaultAction(node1, node2); + node1.getComponentType().accept(this, node2.getComponentType()); + } + + @Override + public void visit(ArrayCreationLevel node1, Node other) { + ArrayCreationLevel node2 = (ArrayCreationLevel) other; + defaultAction(node1, node2); + node1.getDimension().ifPresent(l -> l.accept(this, node2.getDimension().get())); + } + + @Override + public void visit(IntersectionType node1, Node other) { + IntersectionType node2 = (IntersectionType) other; + defaultAction(node1, node2); + visitLists(node1.getElements(), node2.getElements()); + } + + @Override + public void visit(UnionType node1, Node other) { + UnionType node2 = (UnionType) other; + defaultAction(node1, node2); + visitLists(node1.getElements(), node2.getElements()); + } + + @Override + public void visit(RecordDeclaration node1, Node other) { + RecordDeclaration node2 = (RecordDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getImplementedTypes(), node2.getImplementedTypes()); + visitLists(node1.getTypeParameters(), node2.getTypeParameters()); + visitLists(node1.getParameters(), node2.getParameters()); + visitLists(node1.getMembers(), node2.getMembers()); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + if (node1.getReceiverParameter().isPresent() && node2.getReceiverParameter().isPresent()) { + node1.getReceiverParameter().get().accept(this, node2.getReceiverParameter().get()); + } + } + + @Override + public void visit(ReturnStmt node1, Node other) { + ReturnStmt node2 = (ReturnStmt) other; + defaultAction(node1, node2); + node1.getExpression().ifPresent(l -> l.accept(this, node2.getExpression().get())); + } + + @Override + public void visit(SingleMemberAnnotationExpr node1, Node other) { + SingleMemberAnnotationExpr node2 = (SingleMemberAnnotationExpr) other; + defaultAction(node1, node2); + node1.getMemberValue().accept(this, node2.getMemberValue()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(StringLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(SuperExpr node1, Node other) { + SuperExpr node2 = (SuperExpr) other; + defaultAction(node1, node2); + node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); + } + + @Override + public void visit(SwitchEntry node1, Node other) { + SwitchEntry node2 = (SwitchEntry) other; + defaultAction(node1, node2); + visitLists(node1.getLabels(), node2.getLabels()); + visitLists(node1.getStatements(), node2.getStatements()); + } + + @Override + public void visit(SwitchStmt node1, Node other) { + SwitchStmt node2 = (SwitchStmt) other; + defaultAction(node1, node2); + visitLists(node1.getEntries(), node2.getEntries()); + node1.getSelector().accept(this, node2.getSelector()); + } + + @Override + public void visit(SynchronizedStmt node1, Node other) { + SynchronizedStmt node2 = (SynchronizedStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(ThisExpr node1, Node other) { + ThisExpr node2 = (ThisExpr) other; + defaultAction(node1, node2); + node1.getTypeName().ifPresent(l -> l.accept(this, node2.getTypeName().get())); + } + + @Override + public void visit(ThrowStmt node1, Node other) { + ThrowStmt node2 = (ThrowStmt) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(TryStmt node1, Node other) { + TryStmt node2 = (TryStmt) other; + defaultAction(node1, node2); + visitLists(node1.getCatchClauses(), node2.getCatchClauses()); + node1.getFinallyBlock().ifPresent(l -> l.accept(this, node2.getFinallyBlock().get())); + visitLists(node1.getResources(), node2.getResources()); + node1.getTryBlock().accept(this, node2.getTryBlock()); + } + + @Override + public void visit(LocalClassDeclarationStmt node1, Node other) { + LocalClassDeclarationStmt node2 = (LocalClassDeclarationStmt) other; + defaultAction(node1, node2); + node1.getClassDeclaration().accept(this, node2.getClassDeclaration()); + } + + @Override + public void visit(LocalRecordDeclarationStmt node1, Node other) { + LocalRecordDeclarationStmt node2 = (LocalRecordDeclarationStmt) other; + defaultAction(node1, node2); + node1.getRecordDeclaration().accept(this, node2.getRecordDeclaration()); + } + + @Override + public void visit(TypeParameter node1, Node other) { + TypeParameter node2 = (TypeParameter) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + // Since ajava files and its corresponding Java file may differ in whether they contain a + // type bound, only visit type bounds if they're present in both nodes. + if (node1.getTypeBound().isEmpty() == node2.getTypeBound().isEmpty()) { + visitLists(node1.getTypeBound(), node2.getTypeBound()); + } + } + + @Override + public void visit(UnaryExpr node1, Node other) { + UnaryExpr node2 = (UnaryExpr) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } + + @Override + public void visit(UnknownType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(VariableDeclarationExpr node1, Node other) { + VariableDeclarationExpr node2 = (VariableDeclarationExpr) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + visitLists(node1.getVariables(), node2.getVariables()); + } + + @Override + public void visit(VariableDeclarator node1, Node other) { + VariableDeclarator node2 = (VariableDeclarator) other; + defaultAction(node1, node2); + node1.getInitializer().ifPresent(l -> l.accept(this, node2.getInitializer().get())); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(VoidType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(WhileStmt node1, Node other) { + WhileStmt node2 = (WhileStmt) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + node1.getCondition().accept(this, node2.getCondition()); + } + + @Override + public void visit(WildcardType node1, Node other) { + WildcardType node2 = (WildcardType) other; + defaultAction(node1, node2); + node1.getExtendedType().ifPresent(l -> l.accept(this, node2.getExtendedType().get())); + node1.getSuperType().ifPresent(l -> l.accept(this, node2.getSuperType().get())); + } + + @Override + public void visit(LambdaExpr node1, Node other) { + LambdaExpr node2 = (LambdaExpr) other; + defaultAction(node1, node2); + node1.getBody().accept(this, node2.getBody()); + visitLists(node1.getParameters(), node2.getParameters()); + } + + @Override + public void visit(MethodReferenceExpr node1, Node other) { + MethodReferenceExpr node2 = (MethodReferenceExpr) other; + defaultAction(node1, node2); + node1.getScope().accept(this, node2.getScope()); + node1.getTypeArguments().ifPresent(l -> visitLists(l, node2.getTypeArguments().get())); + } + + @Override + public void visit(TypeExpr node1, Node other) { + TypeExpr node2 = (TypeExpr) other; + defaultAction(node1, node2); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(ImportDeclaration node1, Node other) { + ImportDeclaration node2 = (ImportDeclaration) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleDeclaration node1, Node other) { + ModuleDeclaration node2 = (ModuleDeclaration) other; + defaultAction(node1, node2); + visitLists(node1.getDirectives(), node2.getDirectives()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleRequiresDirective node1, Node other) { + ModuleRequiresDirective node2 = (ModuleRequiresDirective) other; + defaultAction(node1, node2); + visitLists(node1.getModifiers(), node2.getModifiers()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleExportsDirective node1, Node other) { + ModuleExportsDirective node2 = (ModuleExportsDirective) other; + defaultAction(node1, node2); + visitLists(node1.getModuleNames(), node2.getModuleNames()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleProvidesDirective node1, Node other) { + ModuleProvidesDirective node2 = (ModuleProvidesDirective) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + visitLists(node1.getWith(), node2.getWith()); + } + + @Override + public void visit(ModuleUsesDirective node1, Node other) { + ModuleUsesDirective node2 = (ModuleUsesDirective) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(ModuleOpensDirective node1, Node other) { + ModuleOpensDirective node2 = (ModuleOpensDirective) other; + defaultAction(node1, node2); + visitLists(node1.getModuleNames(), node2.getModuleNames()); + node1.getName().accept(this, node2.getName()); + } + + @Override + public void visit(UnparsableStmt node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(ReceiverParameter node1, Node other) { + ReceiverParameter node2 = (ReceiverParameter) other; + defaultAction(node1, node2); + node1.getName().accept(this, node2.getName()); + node1.getType().accept(this, node2.getType()); + } + + @Override + public void visit(VarType node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(Modifier node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(SwitchExpr node1, Node other) { + SwitchExpr node2 = (SwitchExpr) other; + defaultAction(node1, node2); + visitLists(node1.getEntries(), node2.getEntries()); + node1.getSelector().accept(this, node2.getSelector()); + } + + @Override + public void visit(TextBlockLiteralExpr node1, Node other) { + defaultAction(node1, other); + } + + @Override + public void visit(YieldStmt node1, Node other) { + YieldStmt node2 = (YieldStmt) other; + defaultAction(node1, node2); + node1.getExpression().accept(this, node2.getExpression()); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java index 68216b7302b..6c3ada58877 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/ExpectedTreesVisitor.java @@ -23,12 +23,14 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.tree.WhileLoopTree; -import java.util.HashSet; -import java.util.Set; + import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; +import java.util.HashSet; +import java.util.Set; + /** * After this visitor visits a tree, {@link #getTrees} returns all the trees that should match with * some JavaParser node. Some trees shouldn't be matched with a JavaParser node because there isn't @@ -39,363 +41,363 @@ * so the trees this class stores can be used to test if the entirety of the javac tree was visited. */ public class ExpectedTreesVisitor extends TreeScannerWithDefaults { - /** The set of trees that should be matched to a JavaParser node when visiting both. */ - private Set trees = new HashSet<>(); - - /** - * Returns the visited trees that should match to some JavaParser node. - * - * @return the visited trees that should match to some JavaParser node - */ - public Set getTrees() { - return trees; - } - - /** - * Records that {@code tree} should have a corresponding JavaParser node. - * - * @param tree the tree to record - */ - @Override - public void defaultAction(Tree tree) { - trees.add(tree); - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - // Skip annotations because ajava files are not required to have the same annotations as - // their corresponding Java files. - return null; - } - - @Override - public Void visitBindingPattern17(Tree tree, Void p) { - super.visitBindingPattern17(tree, p); - // JavaParser doesn't have a node for the VariableTree. - trees.remove(BindingPatternUtils.getVariable(tree)); - return null; - } - - @Override - public Void visitClass(ClassTree tree, Void p) { - defaultAction(tree); - scan(tree.getModifiers(), p); - scan(tree.getTypeParameters(), p); - scan(tree.getExtendsClause(), p); - scan(tree.getImplementsClause(), p); - if (tree.getKind() == Tree.Kind.ENUM) { - // Enum constants expand to a VariableTree like - // public static final MY_ENUM_CONSTANT = new MyEnum(args ...) - // The constructor invocation in the initializer has no corresponding JavaParser node, - // so this removes those invocations. This doesn't remove any trees that should be - // matched to a JavaParser node, because it's illegal to explicitly construct an - // instance of an enum. - for (Tree member : tree.getMembers()) { - member.accept(this, p); - if (member.getKind() != Tree.Kind.VARIABLE) { - continue; - } + /** The set of trees that should be matched to a JavaParser node when visiting both. */ + private Set trees = new HashSet<>(); + + /** + * Returns the visited trees that should match to some JavaParser node. + * + * @return the visited trees that should match to some JavaParser node + */ + public Set getTrees() { + return trees; + } - VariableTree variable = (VariableTree) member; - ExpressionTree initializer = variable.getInitializer(); - if (initializer == null || initializer.getKind() != Tree.Kind.NEW_CLASS) { - continue; - } + /** + * Records that {@code tree} should have a corresponding JavaParser node. + * + * @param tree the tree to record + */ + @Override + public void defaultAction(Tree tree) { + trees.add(tree); + } - NewClassTree constructor = (NewClassTree) initializer; - if (constructor.getIdentifier().getKind() != Tree.Kind.IDENTIFIER) { - continue; - } + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + // Skip annotations because ajava files are not required to have the same annotations as + // their corresponding Java files. + return null; + } - IdentifierTree name = (IdentifierTree) constructor.getIdentifier(); - if (name.getName().contentEquals(tree.getSimpleName())) { - trees.remove(variable.getType()); - trees.remove(constructor); - trees.remove(constructor.getIdentifier()); - } - } - } else if (TreeUtils.isRecordTree(tree)) { - // A record like: - // record MyRec(String myField) {} - // will be expanded by javac to: - // class MyRec { - // MyRec(String myField) { - // super(); - // } - // private final String myField; - // } - // So the constructor and the field declarations have no matching trees in the - // JavaParser AST node, and we must remove those trees (and their subtrees) from the - // `trees` field. - TreeScannerWithDefaults removeAllVisitor = - new TreeScannerWithDefaults() { - @Override - public void defaultAction(Tree tree) { - trees.remove(tree); + @Override + public Void visitBindingPattern17(Tree tree, Void p) { + super.visitBindingPattern17(tree, p); + // JavaParser doesn't have a node for the VariableTree. + trees.remove(BindingPatternUtils.getVariable(tree)); + return null; + } + + @Override + public Void visitClass(ClassTree tree, Void p) { + defaultAction(tree); + scan(tree.getModifiers(), p); + scan(tree.getTypeParameters(), p); + scan(tree.getExtendsClause(), p); + scan(tree.getImplementsClause(), p); + if (tree.getKind() == Tree.Kind.ENUM) { + // Enum constants expand to a VariableTree like + // public static final MY_ENUM_CONSTANT = new MyEnum(args ...) + // The constructor invocation in the initializer has no corresponding JavaParser node, + // so this removes those invocations. This doesn't remove any trees that should be + // matched to a JavaParser node, because it's illegal to explicitly construct an + // instance of an enum. + for (Tree member : tree.getMembers()) { + member.accept(this, p); + if (member.getKind() != Tree.Kind.VARIABLE) { + continue; + } + + VariableTree variable = (VariableTree) member; + ExpressionTree initializer = variable.getInitializer(); + if (initializer == null || initializer.getKind() != Tree.Kind.NEW_CLASS) { + continue; + } + + NewClassTree constructor = (NewClassTree) initializer; + if (constructor.getIdentifier().getKind() != Tree.Kind.IDENTIFIER) { + continue; + } + + IdentifierTree name = (IdentifierTree) constructor.getIdentifier(); + if (name.getName().contentEquals(tree.getSimpleName())) { + trees.remove(variable.getType()); + trees.remove(constructor); + trees.remove(constructor.getIdentifier()); + } } - }; - for (Tree member : tree.getMembers()) { - scan(member, p); - if (TreeUtils.isAutoGeneratedRecordMember(member)) { - member.accept(removeAllVisitor, null); - } else { - // If the user declares a compact canonical constructor, javac will - // automatically fill in the parameters. - // These trees also don't have a match: - if (member.getKind() == Tree.Kind.METHOD) { - MethodTree methodTree = (MethodTree) member; - if (TreeUtils.isCompactCanonicalRecordConstructor(methodTree)) { - for (VariableTree canonicalParameter : methodTree.getParameters()) { - canonicalParameter.accept(removeAllVisitor, null); - } + } else if (TreeUtils.isRecordTree(tree)) { + // A record like: + // record MyRec(String myField) {} + // will be expanded by javac to: + // class MyRec { + // MyRec(String myField) { + // super(); + // } + // private final String myField; + // } + // So the constructor and the field declarations have no matching trees in the + // JavaParser AST node, and we must remove those trees (and their subtrees) from the + // `trees` field. + TreeScannerWithDefaults removeAllVisitor = + new TreeScannerWithDefaults() { + @Override + public void defaultAction(Tree tree) { + trees.remove(tree); + } + }; + for (Tree member : tree.getMembers()) { + scan(member, p); + if (TreeUtils.isAutoGeneratedRecordMember(member)) { + member.accept(removeAllVisitor, null); + } else { + // If the user declares a compact canonical constructor, javac will + // automatically fill in the parameters. + // These trees also don't have a match: + if (member.getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) member; + if (TreeUtils.isCompactCanonicalRecordConstructor(methodTree)) { + for (VariableTree canonicalParameter : methodTree.getParameters()) { + canonicalParameter.accept(removeAllVisitor, null); + } + } + } + } } - } + } else { + scan(tree.getMembers(), p); } - } - } else { - scan(tree.getMembers(), p); + + return null; } - return null; - } + @Override + public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { + // Javac inserts calls to super() at the start of constructors with no this or super call. + // These don't have matching JavaParser nodes. + if (JointJavacJavaParserVisitor.isDefaultSuperConstructorCall(tree)) { + return null; + } + + // Whereas synthetic constructors should be skipped, regular super() and this() should still + // be added. JavaParser has no expression statement surrounding these, so remove the + // expression statement itself. + Void result = super.visitExpressionStatement(tree, p); + if (tree.getExpression().getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree invocation = (MethodInvocationTree) tree.getExpression(); + if (invocation.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { + IdentifierTree identifier = (IdentifierTree) invocation.getMethodSelect(); + if (identifier.getName().contentEquals("this") + || identifier.getName().contentEquals("super")) { + trees.remove(tree); + trees.remove(identifier); + } + } + } - @Override - public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { - // Javac inserts calls to super() at the start of constructors with no this or super call. - // These don't have matching JavaParser nodes. - if (JointJavacJavaParserVisitor.isDefaultSuperConstructorCall(tree)) { - return null; + return result; } - // Whereas synthetic constructors should be skipped, regular super() and this() should still - // be added. JavaParser has no expression statement surrounding these, so remove the - // expression statement itself. - Void result = super.visitExpressionStatement(tree, p); - if (tree.getExpression().getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree invocation = (MethodInvocationTree) tree.getExpression(); - if (invocation.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { - IdentifierTree identifier = (IdentifierTree) invocation.getMethodSelect(); - if (identifier.getName().contentEquals("this") - || identifier.getName().contentEquals("super")) { - trees.remove(tree); - trees.remove(identifier); + @Override + public Void visitForLoop(ForLoopTree tree, Void p) { + // Javac nests a for loop's updates in expression statements but JavaParser stores the + // statements directly, so remove the expression statements. + Void result = super.visitForLoop(tree, p); + for (StatementTree initializer : tree.getInitializer()) { + trees.remove(initializer); } - } - } - return result; - } + for (ExpressionStatementTree update : tree.getUpdate()) { + trees.remove(update); + } - @Override - public Void visitForLoop(ForLoopTree tree, Void p) { - // Javac nests a for loop's updates in expression statements but JavaParser stores the - // statements directly, so remove the expression statements. - Void result = super.visitForLoop(tree, p); - for (StatementTree initializer : tree.getInitializer()) { - trees.remove(initializer); + return result; } - for (ExpressionStatementTree update : tree.getUpdate()) { - trees.remove(update); + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + super.visitSwitch(tree, p); + // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getExpression()); + return null; } - return result; - } - - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - super.visitSwitch(tree, p); - // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getExpression()); - return null; - } - - @Override - public Void visitSwitchExpression17(Tree tree, Void p) { - super.visitSwitchExpression17(tree, p); - // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. - trees.remove(SwitchExpressionUtils.getExpression(tree)); - return null; - } - - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - super.visitSynchronized(tree, p); - // javac surrounds synchronized expressions in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getExpression()); - return null; - } - - @Override - public Void visitIf(IfTree tree, Void p) { - // In an if statement, javac stores the condition as a parenthesized expression, which has - // no corresponding JavaParserNode, so remove the parenthesized expression, but not its - // child. - Void result = super.visitIf(tree, p); - trees.remove(tree.getCondition()); - return result; - } - - @Override - public Void visitImport(ImportTree tree, Void p) { - // Javac stores an import like a.* as a member select, but JavaParser just stores "a", so - // don't add the member select in that case. - if (tree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree memberSelect = (MemberSelectTree) tree.getQualifiedIdentifier(); - if (memberSelect.getIdentifier().contentEquals("*")) { - memberSelect.getExpression().accept(this, p); + @Override + public Void visitSwitchExpression17(Tree tree, Void p) { + super.visitSwitchExpression17(tree, p); + // javac surrounds switch expression in a ParenthesizedTree but JavaParser does not. + trees.remove(SwitchExpressionUtils.getExpression(tree)); return null; - } } - return super.visitImport(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - // Synthetic default constructors don't have matching JavaParser nodes. Conservatively skip - // nullary (no-argument) constructor calls, even if they may not be synthetic. - if (JointJavacJavaParserVisitor.isNoArgumentConstructor(tree)) { - return null; + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + super.visitSynchronized(tree, p); + // javac surrounds synchronized expressions in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getExpression()); + return null; } - Void result = super.visitMethod(tree, p); - // A varargs parameter like String... is converted to String[], where the array type doesn't - // have a corresponding JavaParser AST node. Conservatively skip the array type (but not the - // component type) if it's the last argument. - if (!tree.getParameters().isEmpty()) { - VariableTree last = tree.getParameters().get(tree.getParameters().size() - 1); - if (last.getType().getKind() == Tree.Kind.ARRAY_TYPE) { - trees.remove(last.getType()); - } - - if (last.getType().getKind() == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) last.getType(); - if (annotatedType.getUnderlyingType().getKind() == Tree.Kind.ARRAY_TYPE) { - trees.remove(annotatedType); - trees.remove(annotatedType.getUnderlyingType()); - } - } + @Override + public Void visitIf(IfTree tree, Void p) { + // In an if statement, javac stores the condition as a parenthesized expression, which has + // no corresponding JavaParserNode, so remove the parenthesized expression, but not its + // child. + Void result = super.visitIf(tree, p); + trees.remove(tree.getCondition()); + return result; } - return result; - } + @Override + public Void visitImport(ImportTree tree, Void p) { + // Javac stores an import like a.* as a member select, but JavaParser just stores "a", so + // don't add the member select in that case. + if (tree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree memberSelect = (MemberSelectTree) tree.getQualifiedIdentifier(); + if (memberSelect.getIdentifier().contentEquals("*")) { + memberSelect.getExpression().accept(this, p); + return null; + } + } - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - Void result = super.visitMethodInvocation(tree, p); - // In a method invocation like myObject.myMethod(), the method invocation stores - // myObject.myMethod as its own MemberSelectTree which has no corresponding JavaParserNode. - if (tree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { - trees.remove(tree.getMethodSelect()); + return super.visitImport(tree, p); } - return result; - } + @Override + public Void visitMethod(MethodTree tree, Void p) { + // Synthetic default constructors don't have matching JavaParser nodes. Conservatively skip + // nullary (no-argument) constructor calls, even if they may not be synthetic. + if (JointJavacJavaParserVisitor.isNoArgumentConstructor(tree)) { + return null; + } + + Void result = super.visitMethod(tree, p); + // A varargs parameter like String... is converted to String[], where the array type doesn't + // have a corresponding JavaParser AST node. Conservatively skip the array type (but not the + // component type) if it's the last argument. + if (!tree.getParameters().isEmpty()) { + VariableTree last = tree.getParameters().get(tree.getParameters().size() - 1); + if (last.getType().getKind() == Tree.Kind.ARRAY_TYPE) { + trees.remove(last.getType()); + } - @Override - public Void visitModifiers(ModifiersTree tree, Void p) { - // Don't add ModifierTrees or children because they have no corresponding JavaParser node. - return null; - } + if (last.getType().getKind() == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) last.getType(); + if (annotatedType.getUnderlyingType().getKind() == Tree.Kind.ARRAY_TYPE) { + trees.remove(annotatedType); + trees.remove(annotatedType.getUnderlyingType()); + } + } + } - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - // Skip array initialization because it's not implemented yet. - return null; - } + return result; + } - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - defaultAction(tree); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + Void result = super.visitMethodInvocation(tree, p); + // In a method invocation like myObject.myMethod(), the method invocation stores + // myObject.myMethod as its own MemberSelectTree which has no corresponding JavaParserNode. + if (tree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { + trees.remove(tree.getMethodSelect()); + } - if (tree.getEnclosingExpression() != null) { - tree.getEnclosingExpression().accept(this, p); + return result; } - tree.getIdentifier().accept(this, p); - for (Tree typeArgument : tree.getTypeArguments()) { - typeArgument.accept(this, p); + @Override + public Void visitModifiers(ModifiersTree tree, Void p) { + // Don't add ModifierTrees or children because they have no corresponding JavaParser node. + return null; } - for (Tree arg : tree.getTypeArguments()) { - arg.accept(this, p); + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + // Skip array initialization because it's not implemented yet. + return null; } - if (tree.getClassBody() == null) { - return null; + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + defaultAction(tree); + + if (tree.getEnclosingExpression() != null) { + tree.getEnclosingExpression().accept(this, p); + } + + tree.getIdentifier().accept(this, p); + for (Tree typeArgument : tree.getTypeArguments()) { + typeArgument.accept(this, p); + } + + for (Tree arg : tree.getTypeArguments()) { + arg.accept(this, p); + } + + if (tree.getClassBody() == null) { + return null; + } + + // Anonymous class bodies require special handling. There isn't a corresponding JavaParser + // node, and synthetic constructors must be skipped. + ClassTree body = tree.getClassBody(); + scan(body.getModifiers(), p); + scan(body.getTypeParameters(), p); + scan(body.getImplementsClause(), p); + for (Tree member : body.getMembers()) { + // Constructors cannot be declared in an anonymous class, so don't add them. + if (member.getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) member; + if (methodTree.getName().contentEquals("")) { + continue; + } + } + + member.accept(this, p); + } + + return null; } - // Anonymous class bodies require special handling. There isn't a corresponding JavaParser - // node, and synthetic constructors must be skipped. - ClassTree body = tree.getClassBody(); - scan(body.getModifiers(), p); - scan(body.getTypeParameters(), p); - scan(body.getImplementsClause(), p); - for (Tree member : body.getMembers()) { - // Constructors cannot be declared in an anonymous class, so don't add them. - if (member.getKind() == Tree.Kind.METHOD) { - MethodTree methodTree = (MethodTree) member; - if (methodTree.getName().contentEquals("")) { - continue; + @Override + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + for (VariableTree parameter : tree.getParameters()) { + // Programmers may omit parameter types for lambda expressions. When not specified, + // javac infers them but JavaParser uses UnknownType. Conservatively, don't add + // parameter types for lambda expressions. + scan(parameter.getModifiers(), p); + scan(parameter.getNameExpression(), p); + assert parameter.getInitializer() == null; } - } - member.accept(this, p); + scan(tree.getBody(), p); + return null; } - return null; - } - - @Override - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - for (VariableTree parameter : tree.getParameters()) { - // Programmers may omit parameter types for lambda expressions. When not specified, - // javac infers them but JavaParser uses UnknownType. Conservatively, don't add - // parameter types for lambda expressions. - scan(parameter.getModifiers(), p); - scan(parameter.getNameExpression(), p); - assert parameter.getInitializer() == null; + @Override + public Void visitWhileLoop(WhileLoopTree tree, Void p) { + super.visitWhileLoop(tree, p); + // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getCondition()); + return null; } - scan(tree.getBody(), p); - return null; - } - - @Override - public Void visitWhileLoop(WhileLoopTree tree, Void p) { - super.visitWhileLoop(tree, p); - // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getCondition()); - return null; - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - super.visitDoWhileLoop(tree, p); - // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. - trees.remove(tree.getCondition()); - return null; - } - - @Override - public Void visitVariable(VariableTree tree, Void p) { - // Javac expands the keyword "var" in a variable declaration to its inferred type. - // JavaParser has a special "var" construct, so they won't match. If a javac type was - // generated this way, then it won't have a position in source code so in that case we don't - // add it. - if (TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { - return null; + @Override + public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + super.visitDoWhileLoop(tree, p); + // javac surrounds while loop conditions in a ParenthesizedTree but JavaParser does not. + trees.remove(tree.getCondition()); + return null; } - return super.visitVariable(tree, p); - } + @Override + public Void visitVariable(VariableTree tree, Void p) { + // Javac expands the keyword "var" in a variable declaration to its inferred type. + // JavaParser has a special "var" construct, so they won't match. If a javac type was + // generated this way, then it won't have a position in source code so in that case we don't + // add it. + if (TreeUtils.isVariableTreeDeclaredUsingVar(tree)) { + return null; + } + + return super.visitVariable(tree, p); + } - @Override - public Void visitYield17(Tree tree, Void p) { - // JavaParser does not parse yields correctly: - // https://github.com/javaparser/javaparser/issues/3364 - // So skip yields. - return null; - } + @Override + public Void visitYield17(Tree tree, Void p) { + // JavaParser does not parse yields correctly: + // https://github.com/javaparser/javaparser/issues/3364 + // So skip yields. + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java index 62e3b57ff80..641229d1899 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java @@ -16,6 +16,15 @@ import com.github.javaparser.printer.DefaultPrettyPrinter; import com.github.javaparser.utils.Pair; import com.sun.source.util.JavacTask; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.framework.stub.AnnotationFileParser; +import org.checkerframework.framework.util.JavaParserUtil; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.FilesPlume; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -37,6 +46,7 @@ import java.util.Map; import java.util.Set; import java.util.StringJoiner; + import javax.lang.model.element.ElementKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; @@ -47,575 +57,575 @@ import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.framework.stub.AnnotationFileParser; -import org.checkerframework.framework.util.JavaParserUtil; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.FilesPlume; /** This program inserts annotations from an ajava file into a Java file. See {@link #main}. */ public class InsertAjavaAnnotations { - /** Element utilities. */ - private final Elements elements; - - /** - * Constructs an {@code InsertAjavaAnnotations} using the given {@code Elements} instance. - * - * @param elements an instance of {@code Elements} - */ - public InsertAjavaAnnotations(Elements elements) { - this.elements = elements; - } - - /** - * Gets an instance of {@code Elements} from the current Java compiler. - * - * @return the Element utilities - */ - private static Elements createElements() { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - if (compiler == null) { - System.err.println("Could not get compiler instance"); - System.exit(1); - } - - DiagnosticCollector diagnostics = new DiagnosticCollector(); - try (JavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { - if (fileManager == null) { - System.err.println("Could not get file manager"); - System.exit(1); - } - - CompilationTask cTask = - compiler.getTask( - null, - fileManager, - diagnostics, - Collections.emptyList(), - null, - Collections.emptyList()); - if (!(cTask instanceof JavacTask)) { - System.err.println("Could not get a valid JavacTask: " + cTask.getClass()); - System.exit(1); - } - - return ((JavacTask) cTask).getElements(); - } catch (IOException e) { - throw new Error(e); - } - } - - /** Represents some text to be inserted at a file and its location. */ - private static class Insertion { - /** Offset of the insertion in the file, measured in characters from the beginning. */ - public final int position; - - /** The contents of the insertion. */ - public final String contents; - - /** Whether the insertion should be on its own separate line. */ - public final boolean ownLine; + /** Element utilities. */ + private final Elements elements; /** - * Constructs an insertion with the given position and contents. + * Constructs an {@code InsertAjavaAnnotations} using the given {@code Elements} instance. * - * @param position offset of the insertion in the file - * @param contents contents of the insertion + * @param elements an instance of {@code Elements} */ - public Insertion(int position, String contents) { - this(position, contents, false); + public InsertAjavaAnnotations(Elements elements) { + this.elements = elements; } /** - * Constructs an insertion with the given position and contents. + * Gets an instance of {@code Elements} from the current Java compiler. * - * @param position offset of the insertion in the file - * @param contents contents of the insertion - * @param ownLine true if this insertion should appear on its own separate line (doesn't affect - * the contents of the insertion) + * @return the Element utilities */ - public Insertion(int position, String contents, boolean ownLine) { - this.position = position; - this.contents = contents; - this.ownLine = ownLine; - } + private static Elements createElements() { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + System.err.println("Could not get compiler instance"); + System.exit(1); + } - @Override - public String toString() { - return "Insertion [contents=" + contents + ", position=" + position + "]"; - } - } - - /** - * Given two JavaParser ASTs representing the same Java file but with differing annotations, - * stores a list of {@link Insertion}s. The {@link Insertion}s represent how to textually modify - * the file of the second AST to insert all annotations in the first AST into the second AST, but - * this class doesn't modify the second AST itself. To use this class, call {@link - * #visit(CompilationUnit, Node)} on a pair of ASTs and then use the contents of {@link - * #insertions}. - */ - private class BuildInsertionsVisitor extends DoubleJavaParserVisitor { - /** - * The set of annotations found in the file. Keys are both fully-qualified and simple names. - * Contains an entry for the fully qualified name of each annotation and, if it was imported, - * its simple name. - * - *

          The map is populated from import statements and also when parsing a file that uses the - * fully qualified name of an annotation it doesn't import. - */ - private @MonotonicNonNull Map allAnnotations = null; + DiagnosticCollector diagnostics = new DiagnosticCollector(); + try (JavaFileManager fileManager = + compiler.getStandardFileManager(diagnostics, null, null)) { + if (fileManager == null) { + System.err.println("Could not get file manager"); + System.exit(1); + } - /** The annotation insertions seen so far. */ - public final List insertions = new ArrayList<>(); + CompilationTask cTask = + compiler.getTask( + null, + fileManager, + diagnostics, + Collections.emptyList(), + null, + Collections.emptyList()); + if (!(cTask instanceof JavacTask)) { + System.err.println("Could not get a valid JavacTask: " + cTask.getClass()); + System.exit(1); + } - /** A printer for annotations. */ - private final DefaultPrettyPrinter printer = new DefaultPrettyPrinter(); + return ((JavacTask) cTask).getElements(); + } catch (IOException e) { + throw new Error(e); + } + } - /** The lines of the String representation of the second AST. */ - private final List lines; + /** Represents some text to be inserted at a file and its location. */ + private static class Insertion { + /** Offset of the insertion in the file, measured in characters from the beginning. */ + public final int position; + + /** The contents of the insertion. */ + public final String contents; + + /** Whether the insertion should be on its own separate line. */ + public final boolean ownLine; + + /** + * Constructs an insertion with the given position and contents. + * + * @param position offset of the insertion in the file + * @param contents contents of the insertion + */ + public Insertion(int position, String contents) { + this(position, contents, false); + } - /** The line separator used in the text the second AST was parsed from */ - private final String lineSeparator; + /** + * Constructs an insertion with the given position and contents. + * + * @param position offset of the insertion in the file + * @param contents contents of the insertion + * @param ownLine true if this insertion should appear on its own separate line (doesn't + * affect the contents of the insertion) + */ + public Insertion(int position, String contents, boolean ownLine) { + this.position = position; + this.contents = contents; + this.ownLine = ownLine; + } - /** - * Stores the offsets of the lines in the string representation of the second AST. At index i, - * stores the number of characters from the start of the file to the beginning of the ith line. - */ - private final List cumulativeLineSizes; + @Override + public String toString() { + return "Insertion [contents=" + contents + ", position=" + position + "]"; + } + } /** - * Constructs a {@code BuildInsertionsVisitor} where {@code destFileContents} is the String - * representation of the AST to insert annotation into, that uses the given line separator. When - * visiting a node pair, the second node must always be from an AST generated from this String. - * - * @param destFileContents the String the second vistide AST was parsed from - * @param lineSeparator the line separator that {@code destFileContents} uses + * Given two JavaParser ASTs representing the same Java file but with differing annotations, + * stores a list of {@link Insertion}s. The {@link Insertion}s represent how to textually modify + * the file of the second AST to insert all annotations in the first AST into the second AST, + * but this class doesn't modify the second AST itself. To use this class, call {@link + * #visit(CompilationUnit, Node)} on a pair of ASTs and then use the contents of {@link + * #insertions}. */ - public BuildInsertionsVisitor(String destFileContents, String lineSeparator) { - allAnnotations = null; - String[] lines = destFileContents.split(lineSeparator); - this.lines = Arrays.asList(lines); - this.lineSeparator = lineSeparator; - cumulativeLineSizes = new ArrayList<>(lines.length); - cumulativeLineSizes.add(0); - for (int i = 1; i < lines.length; i++) { - int lastSize = cumulativeLineSizes.get(i - 1); - int lastLineLength = lines[i - 1].length() + lineSeparator.length(); - cumulativeLineSizes.add(lastSize + lastLineLength); - } - } + private class BuildInsertionsVisitor extends DoubleJavaParserVisitor { + /** + * The set of annotations found in the file. Keys are both fully-qualified and simple names. + * Contains an entry for the fully qualified name of each annotation and, if it was + * imported, its simple name. + * + *

          The map is populated from import statements and also when parsing a file that uses the + * fully qualified name of an annotation it doesn't import. + */ + private @MonotonicNonNull Map allAnnotations = null; + + /** The annotation insertions seen so far. */ + public final List insertions = new ArrayList<>(); + + /** A printer for annotations. */ + private final DefaultPrettyPrinter printer = new DefaultPrettyPrinter(); + + /** The lines of the String representation of the second AST. */ + private final List lines; + + /** The line separator used in the text the second AST was parsed from */ + private final String lineSeparator; + + /** + * Stores the offsets of the lines in the string representation of the second AST. At index + * i, stores the number of characters from the start of the file to the beginning of the ith + * line. + */ + private final List cumulativeLineSizes; + + /** + * Constructs a {@code BuildInsertionsVisitor} where {@code destFileContents} is the String + * representation of the AST to insert annotation into, that uses the given line separator. + * When visiting a node pair, the second node must always be from an AST generated from this + * String. + * + * @param destFileContents the String the second vistide AST was parsed from + * @param lineSeparator the line separator that {@code destFileContents} uses + */ + public BuildInsertionsVisitor(String destFileContents, String lineSeparator) { + allAnnotations = null; + String[] lines = destFileContents.split(lineSeparator); + this.lines = Arrays.asList(lines); + this.lineSeparator = lineSeparator; + cumulativeLineSizes = new ArrayList<>(lines.length); + cumulativeLineSizes.add(0); + for (int i = 1; i < lines.length; i++) { + int lastSize = cumulativeLineSizes.get(i - 1); + int lastLineLength = lines[i - 1].length() + lineSeparator.length(); + cumulativeLineSizes.add(lastSize + lastLineLength); + } + } - @Override - public void defaultAction(Node src, Node dest) { - if (!(src instanceof NodeWithAnnotations)) { - return; - } - NodeWithAnnotations srcWithAnnos = (NodeWithAnnotations) src; - - // If `src` is a declaration, its annotations are declaration annotations. - if (src instanceof MethodDeclaration) { - addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); - return; - } else if (src instanceof FieldDeclaration) { - addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); - return; - } - - // `src`'s annotations are type annotations. - Position position; - if (dest instanceof ClassOrInterfaceType) { - // In a multi-part name like my.package.MyClass, type annotations go directly in - // front of MyClass instead of the full name. - position = ((ClassOrInterfaceType) dest).getName().getBegin().get(); - } else { - position = dest.getBegin().get(); - } - addAnnotations(position, srcWithAnnos.getAnnotations(), 0, false); - } + @Override + public void defaultAction(Node src, Node dest) { + if (!(src instanceof NodeWithAnnotations)) { + return; + } + NodeWithAnnotations srcWithAnnos = (NodeWithAnnotations) src; + + // If `src` is a declaration, its annotations are declaration annotations. + if (src instanceof MethodDeclaration) { + addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); + return; + } else if (src instanceof FieldDeclaration) { + addAnnotationOnOwnLine(dest.getBegin().get(), srcWithAnnos.getAnnotations()); + return; + } - @Override - public void visit(ArrayType src, Node other) { - ArrayType dest = (ArrayType) other; - // The second component of this pair contains a list of ArrayBracketPairs from left to - // right. For example, if src contains String[][], then the list will contain the - // types String[] and String[][]. To insert array annotations in the correct location, - // we insert them directly to the right of the end of the previous element. - Pair> srcArrayTypes = ArrayType.unwrapArrayTypes(src); - Pair> destArrayTypes = - ArrayType.unwrapArrayTypes(dest); - // The first annotations go directly after the element type. - Position firstPosition = destArrayTypes.a.getEnd().get(); - addAnnotations(firstPosition, srcArrayTypes.b.get(0).getAnnotations(), 1, false); - for (int i = 1; i < srcArrayTypes.b.size(); i++) { - Position position = destArrayTypes.b.get(i - 1).getTokenRange().get().toRange().get().end; - addAnnotations(position, srcArrayTypes.b.get(i).getAnnotations(), 1, true); - } - - // Visit the component type. - srcArrayTypes.a.accept(this, destArrayTypes.a); - } + // `src`'s annotations are type annotations. + Position position; + if (dest instanceof ClassOrInterfaceType) { + // In a multi-part name like my.package.MyClass, type annotations go directly in + // front of MyClass instead of the full name. + position = ((ClassOrInterfaceType) dest).getName().getBegin().get(); + } else { + position = dest.getBegin().get(); + } + addAnnotations(position, srcWithAnnos.getAnnotations(), 0, false); + } - @Override - @SuppressWarnings("optional:method.invocation") // parallel structure of two data structures - public void visit(CompilationUnit src, Node other) { - CompilationUnit dest = (CompilationUnit) other; - defaultAction(src, dest); + @Override + public void visit(ArrayType src, Node other) { + ArrayType dest = (ArrayType) other; + // The second component of this pair contains a list of ArrayBracketPairs from left to + // right. For example, if src contains String[][], then the list will contain the + // types String[] and String[][]. To insert array annotations in the correct location, + // we insert them directly to the right of the end of the previous element. + Pair> srcArrayTypes = + ArrayType.unwrapArrayTypes(src); + Pair> destArrayTypes = + ArrayType.unwrapArrayTypes(dest); + // The first annotations go directly after the element type. + Position firstPosition = destArrayTypes.a.getEnd().get(); + addAnnotations(firstPosition, srcArrayTypes.b.get(0).getAnnotations(), 1, false); + for (int i = 1; i < srcArrayTypes.b.size(); i++) { + Position position = + destArrayTypes.b.get(i - 1).getTokenRange().get().toRange().get().end; + addAnnotations(position, srcArrayTypes.b.get(i).getAnnotations(), 1, true); + } - // Gather annotations used in the ajava file. - allAnnotations = getImportedAnnotations(src); + // Visit the component type. + srcArrayTypes.a.accept(this, destArrayTypes.a); + } - // Move any annotations that JavaParser puts in the declaration position but belong only - // in the type position. - src.accept(new TypeAnnotationMover(allAnnotations, elements), null); + @Override + @SuppressWarnings("optional:method.invocation") // parallel structure of two data structures + public void visit(CompilationUnit src, Node other) { + CompilationUnit dest = (CompilationUnit) other; + defaultAction(src, dest); + + // Gather annotations used in the ajava file. + allAnnotations = getImportedAnnotations(src); + + // Move any annotations that JavaParser puts in the declaration position but belong only + // in the type position. + src.accept(new TypeAnnotationMover(allAnnotations, elements), null); + + // Transfer import statements from the ajava file to the Java file. + + List newImports; + { // set `newImports` + NodeList destImports = dest.getImports(); + Set existingImports = + new HashSet<>(CollectionsPlume.mapCapacity(destImports.size())); + for (ImportDeclaration importDecl : destImports) { + existingImports.add(printer.print(importDecl)); + } + + newImports = new ArrayList<>(); + for (ImportDeclaration importDecl : src.getImports()) { + String importString = printer.print(importDecl); + if (!existingImports.contains(importString)) { + newImports.add(importString); + } + } + } - // Transfer import statements from the ajava file to the Java file. + if (!newImports.isEmpty()) { + int position; + int lineBreaksBeforeFirstImport; + if (!dest.getImports().isEmpty()) { + Position lastImportPosition = + dest.getImports().get(dest.getImports().size() - 1).getEnd().get(); + position = getFilePosition(lastImportPosition) + 1; + lineBreaksBeforeFirstImport = 1; + } else if (dest.getPackageDeclaration().isPresent()) { + Position packagePosition = dest.getPackageDeclaration().get().getEnd().get(); + position = getFilePosition(packagePosition) + 1; + lineBreaksBeforeFirstImport = 2; + } else { + position = 0; + lineBreaksBeforeFirstImport = 0; + } + + String insertionContent = ""; + // In Java 11, use String::repeat. + for (int i = 0; i < lineBreaksBeforeFirstImport; i++) { + insertionContent += lineSeparator; + } + insertionContent += String.join("", newImports); + + insertions.add(new Insertion(position, insertionContent)); + } - List newImports; - { // set `newImports` - NodeList destImports = dest.getImports(); - Set existingImports = - new HashSet<>(CollectionsPlume.mapCapacity(destImports.size())); - for (ImportDeclaration importDecl : destImports) { - existingImports.add(printer.print(importDecl)); + src.getModule().ifPresent(m -> m.accept(this, dest.getModule().get())); + src.getPackageDeclaration() + .ifPresent(pd -> pd.accept(this, dest.getPackageDeclaration().get())); + int numTypes = src.getTypes().size(); + for (int i = 0; i < numTypes; i++) { + src.getTypes().get(i).accept(this, dest.getTypes().get(i)); + } } - newImports = new ArrayList<>(); - for (ImportDeclaration importDecl : src.getImports()) { - String importString = printer.print(importDecl); - if (!existingImports.contains(importString)) { - newImports.add(importString); - } + /** + * Creates an insertion for a collection of annotations and adds it to {@link #insertions}. + * The annotations will appear on their own line (unless any non-whitespace characters + * precede the insertion position on its own line). + * + * @param position the position of the insertion + * @param annotations list of annotations to insert + */ + private void addAnnotationOnOwnLine(Position position, List annotations) { + String line = lines.get(position.line - 1); + int insertionColumn = position.column - 1; + boolean ownLine = true; + for (int i = 0; i < insertionColumn; i++) { + if (line.charAt(i) != ' ' && line.charAt(i) != '\t') { + ownLine = false; + break; + } + } + + if (ownLine) { + StringJoiner insertionContent = new StringJoiner(" "); + for (AnnotationExpr annotation : annotations) { + insertionContent.add(printer.print(annotation)); + } + + if (insertionContent.length() == 0) { + return; + } + + String leadingWhitespace = line.substring(0, insertionColumn); + int filePosition = getFilePosition(position); + insertions.add( + new Insertion( + filePosition, + insertionContent.toString() + lineSeparator + leadingWhitespace, + true)); + } else { + addAnnotations(position, annotations, 0, false); + } } - } - - if (!newImports.isEmpty()) { - int position; - int lineBreaksBeforeFirstImport; - if (!dest.getImports().isEmpty()) { - Position lastImportPosition = - dest.getImports().get(dest.getImports().size() - 1).getEnd().get(); - position = getFilePosition(lastImportPosition) + 1; - lineBreaksBeforeFirstImport = 1; - } else if (dest.getPackageDeclaration().isPresent()) { - Position packagePosition = dest.getPackageDeclaration().get().getEnd().get(); - position = getFilePosition(packagePosition) + 1; - lineBreaksBeforeFirstImport = 2; - } else { - position = 0; - lineBreaksBeforeFirstImport = 0; + + /** + * Creates an insertion for a collection of annotations at {@code position} + {@code offset} + * and adds it to {@link #insertions}. + * + * @param position the position of the insertion + * @param annotations list of annotations to insert + * @param offset additional offset of the insertion after {@code position} + * @param addSpaceBefore if true, the insertion content will start with a space + */ + private void addAnnotations( + Position position, + Iterable annotations, + int offset, + boolean addSpaceBefore) { + StringBuilder insertionContent = new StringBuilder(); + for (AnnotationExpr annotation : annotations) { + insertionContent.append(printer.print(annotation)); + insertionContent.append(" "); + } + + // Can't test `annotations.isEmpty()` earlier because `annotations` has type `Iterable`. + if (insertionContent.length() == 0) { + return; + } + + if (addSpaceBefore) { + insertionContent.insert(0, " "); + } + + int filePosition = getFilePosition(position) + offset; + insertions.add(new Insertion(filePosition, insertionContent.toString())); } - String insertionContent = ""; - // In Java 11, use String::repeat. - for (int i = 0; i < lineBreaksBeforeFirstImport; i++) { - insertionContent += lineSeparator; + /** + * Converts a Position (which contains a line and column) to an offset from the start of the + * file, in characters. + * + * @param position a Position + * @return the total offset of the position from the start of the file + */ + private int getFilePosition(Position position) { + return cumulativeLineSizes.get(position.line - 1) + (position.column - 1); } - insertionContent += String.join("", newImports); - - insertions.add(new Insertion(position, insertionContent)); - } - - src.getModule().ifPresent(m -> m.accept(this, dest.getModule().get())); - src.getPackageDeclaration() - .ifPresent(pd -> pd.accept(this, dest.getPackageDeclaration().get())); - int numTypes = src.getTypes().size(); - for (int i = 0; i < numTypes; i++) { - src.getTypes().get(i).accept(this, dest.getTypes().get(i)); - } } /** - * Creates an insertion for a collection of annotations and adds it to {@link #insertions}. The - * annotations will appear on their own line (unless any non-whitespace characters precede the - * insertion position on its own line). + * Returns all annotations imported by the annotation file as a mapping from simple and + * qualified names to TypeElements. * - * @param position the position of the insertion - * @param annotations list of annotations to insert + * @param cu compilation unit to extract imports from + * @return a map from names to TypeElement, for all annotations imported by the annotation file. + * Two entries for each annotation: one for the simple name and another for the + * fully-qualified name, with the same value. */ - private void addAnnotationOnOwnLine(Position position, List annotations) { - String line = lines.get(position.line - 1); - int insertionColumn = position.column - 1; - boolean ownLine = true; - for (int i = 0; i < insertionColumn; i++) { - if (line.charAt(i) != ' ' && line.charAt(i) != '\t') { - ownLine = false; - break; - } - } - - if (ownLine) { - StringJoiner insertionContent = new StringJoiner(" "); - for (AnnotationExpr annotation : annotations) { - insertionContent.add(printer.print(annotation)); + private Map getImportedAnnotations(CompilationUnit cu) { + if (cu.getImports() == null) { + return Collections.emptyMap(); } - if (insertionContent.length() == 0) { - return; + Map result = new HashMap<>(); + for (ImportDeclaration importDecl : cu.getImports()) { + if (importDecl.isAsterisk()) { + @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: + // com.github.javaparser.ast.expr.Name inherits toString, + // so there can be no annotation for it + ) + @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); + if (importDecl.isStatic()) { + // Wildcard import of members of a type (class or interface) + TypeElement element = elements.getTypeElement(imported); + if (element != null) { + // Find nested annotations + result.putAll(AnnotationFileParser.annosInType(element)); + } + + } else { + // Wildcard import of members of a package + PackageElement element = elements.getPackageElement(imported); + if (element != null) { + result.putAll(AnnotationFileParser.annosInPackage(element)); + } + } + } else { + @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is + // @FullyQualifiedName + ) + @FullyQualifiedName String imported = importDecl.getNameAsString(); + TypeElement importType = elements.getTypeElement(imported); + if (importType != null && importType.getKind() == ElementKind.ANNOTATION_TYPE) { + TypeElement annoElt = elements.getTypeElement(imported); + if (annoElt != null) { + result.put(annoElt.getSimpleName().toString(), annoElt); + } + } + } } - - String leadingWhitespace = line.substring(0, insertionColumn); - int filePosition = getFilePosition(position); - insertions.add( - new Insertion( - filePosition, - insertionContent.toString() + lineSeparator + leadingWhitespace, - true)); - } else { - addAnnotations(position, annotations, 0, false); - } + return result; } /** - * Creates an insertion for a collection of annotations at {@code position} + {@code offset} and - * adds it to {@link #insertions}. + * Inserts all annotations from the ajava file read from {@code annotationFile} into a Java file + * with contents {@code javaFileContents} that uses the given line separator and returns the + * resulting String. * - * @param position the position of the insertion - * @param annotations list of annotations to insert - * @param offset additional offset of the insertion after {@code position} - * @param addSpaceBefore if true, the insertion content will start with a space + * @param annotationFile input stream for an ajava file for {@code javaFileContents} + * @param javaFileContents contents of a Java file to insert annotations into + * @param lineSeparator the line separator {@code javaFileContents} uses + * @return a modified {@code javaFileContents} with annotations from {@code annotationFile} + * inserted */ - private void addAnnotations( - Position position, - Iterable annotations, - int offset, - boolean addSpaceBefore) { - StringBuilder insertionContent = new StringBuilder(); - for (AnnotationExpr annotation : annotations) { - insertionContent.append(printer.print(annotation)); - insertionContent.append(" "); - } - - // Can't test `annotations.isEmpty()` earlier because `annotations` has type `Iterable`. - if (insertionContent.length() == 0) { - return; - } - - if (addSpaceBefore) { - insertionContent.insert(0, " "); - } - - int filePosition = getFilePosition(position) + offset; - insertions.add(new Insertion(filePosition, insertionContent.toString())); + public String insertAnnotations( + InputStream annotationFile, String javaFileContents, String lineSeparator) { + CompilationUnit annotationCu = JavaParserUtil.parseCompilationUnit(annotationFile); + CompilationUnit javaCu = JavaParserUtil.parseCompilationUnit(javaFileContents); + BuildInsertionsVisitor insertionVisitor = + new BuildInsertionsVisitor(javaFileContents, lineSeparator); + annotationCu.accept(insertionVisitor, javaCu); + List insertions = insertionVisitor.insertions; + insertions.sort(InsertAjavaAnnotations::compareInsertions); + + StringBuilder result = new StringBuilder(javaFileContents); + for (Insertion insertion : insertions) { + result.insert(insertion.position, insertion.contents); + } + return result.toString(); } /** - * Converts a Position (which contains a line and column) to an offset from the start of the - * file, in characters. + * Compares two insertions in the reverse order of where their content should appear in the + * file. Making an insertion changes the offset values of all content after the insertion, so + * performing the insertions in reverse order of appearance removes the need to recalculate the + * positions of other insertions. + * + *

          The order in which insertions should appear is determined first by their absolute position + * in the file, and second by whether they have their own line. In a method like + * {@code @Pure @Tainting String myMethod()} both annotations should be inserted at the same + * location (right before "String"), but {@code @Pure} should always come first because it + * belongs on its own line. * - * @param position a Position - * @return the total offset of the position from the start of the file + * @param insertion1 the first insertion + * @param insertion2 the second insertion + * @return a negative integer, zero, or a positive integer if {@code insertion1} belongs before, + * at the same position, or after {@code insertion2} respectively in the above ordering */ - private int getFilePosition(Position position) { - return cumulativeLineSizes.get(position.line - 1) + (position.column - 1); - } - } - - /** - * Returns all annotations imported by the annotation file as a mapping from simple and qualified - * names to TypeElements. - * - * @param cu compilation unit to extract imports from - * @return a map from names to TypeElement, for all annotations imported by the annotation file. - * Two entries for each annotation: one for the simple name and another for the - * fully-qualified name, with the same value. - */ - private Map getImportedAnnotations(CompilationUnit cu) { - if (cu.getImports() == null) { - return Collections.emptyMap(); - } - - Map result = new HashMap<>(); - for (ImportDeclaration importDecl : cu.getImports()) { - if (importDecl.isAsterisk()) { - @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: - // com.github.javaparser.ast.expr.Name inherits toString, - // so there can be no annotation for it - ) - @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); - if (importDecl.isStatic()) { - // Wildcard import of members of a type (class or interface) - TypeElement element = elements.getTypeElement(imported); - if (element != null) { - // Find nested annotations - result.putAll(AnnotationFileParser.annosInType(element)); - } - - } else { - // Wildcard import of members of a package - PackageElement element = elements.getPackageElement(imported); - if (element != null) { - result.putAll(AnnotationFileParser.annosInPackage(element)); - } - } - } else { - @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is - // @FullyQualifiedName - ) - @FullyQualifiedName String imported = importDecl.getNameAsString(); - TypeElement importType = elements.getTypeElement(imported); - if (importType != null && importType.getKind() == ElementKind.ANNOTATION_TYPE) { - TypeElement annoElt = elements.getTypeElement(imported); - if (annoElt != null) { - result.put(annoElt.getSimpleName().toString(), annoElt); - } + private static int compareInsertions(Insertion insertion1, Insertion insertion2) { + int cmp = Integer.compare(insertion1.position, insertion2.position); + if (cmp == 0 && (insertion1.ownLine != insertion2.ownLine)) { + if (insertion1.ownLine) { + cmp = -1; + } else { + cmp = 1; + } } - } - } - return result; - } - - /** - * Inserts all annotations from the ajava file read from {@code annotationFile} into a Java file - * with contents {@code javaFileContents} that uses the given line separator and returns the - * resulting String. - * - * @param annotationFile input stream for an ajava file for {@code javaFileContents} - * @param javaFileContents contents of a Java file to insert annotations into - * @param lineSeparator the line separator {@code javaFileContents} uses - * @return a modified {@code javaFileContents} with annotations from {@code annotationFile} - * inserted - */ - public String insertAnnotations( - InputStream annotationFile, String javaFileContents, String lineSeparator) { - CompilationUnit annotationCu = JavaParserUtil.parseCompilationUnit(annotationFile); - CompilationUnit javaCu = JavaParserUtil.parseCompilationUnit(javaFileContents); - BuildInsertionsVisitor insertionVisitor = - new BuildInsertionsVisitor(javaFileContents, lineSeparator); - annotationCu.accept(insertionVisitor, javaCu); - List insertions = insertionVisitor.insertions; - insertions.sort(InsertAjavaAnnotations::compareInsertions); - - StringBuilder result = new StringBuilder(javaFileContents); - for (Insertion insertion : insertions) { - result.insert(insertion.position, insertion.contents); - } - return result.toString(); - } - - /** - * Compares two insertions in the reverse order of where their content should appear in the file. - * Making an insertion changes the offset values of all content after the insertion, so performing - * the insertions in reverse order of appearance removes the need to recalculate the positions of - * other insertions. - * - *

          The order in which insertions should appear is determined first by their absolute position - * in the file, and second by whether they have their own line. In a method like - * {@code @Pure @Tainting String myMethod()} both annotations should be inserted at the same - * location (right before "String"), but {@code @Pure} should always come first because it belongs - * on its own line. - * - * @param insertion1 the first insertion - * @param insertion2 the second insertion - * @return a negative integer, zero, or a positive integer if {@code insertion1} belongs before, - * at the same position, or after {@code insertion2} respectively in the above ordering - */ - private static int compareInsertions(Insertion insertion1, Insertion insertion2) { - int cmp = Integer.compare(insertion1.position, insertion2.position); - if (cmp == 0 && (insertion1.ownLine != insertion2.ownLine)) { - if (insertion1.ownLine) { - cmp = -1; - } else { - cmp = 1; - } - } - return -cmp; - } - - /** - * Inserts all annotations from an ajava file into a Java file. - * - * @param annotationFileName an ajava file - * @param javaFileName a Java file to insert annotation into - */ - public void insertAnnotations(String annotationFileName, String javaFileName) { - try { - File javaFile = new File(javaFileName); - String fileContents = FilesPlume.readString(Path.of(javaFileName)); - String lineSeparator = FilesPlume.inferLineSeparator(annotationFileName); - try (FileInputStream annotationInputStream = new FileInputStream(annotationFileName)) { - String result = insertAnnotations(annotationInputStream, fileContents, lineSeparator); - FilesPlume.writeString(javaFile, result); - } - } catch (IOException e) { - System.err.println( - "Failed to insert annotations from file " - + annotationFileName - + " into file " - + javaFileName); - System.exit(1); - } - } - - /** - * Inserts annotations from ajava files into Java files in place. - * - *

          The first argument is an ajava file or a directory containing ajava files. - * - *

          The second argument is a Java file or a directory containing Java files to insert - * annotations into. - * - *

          For each Java file, checks if any ajava files from the first argument match it. For each - * such ajava file, inserts all its annotations into the Java file. - * - * @param args command line arguments: the first element should be a path to ajava files and the - * second should be the directory containing Java files to insert into - */ - public static void main(String[] args) { - if (args.length != 2) { - System.out.println( - "Usage: java InsertAjavaAnnotations " - + " insertionVisitor = - new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { - if (!path.getFileName().toString().endsWith(".java")) { - return FileVisitResult.CONTINUE; - } - - CompilationUnit root = null; - try { - root = JavaParserUtil.parseCompilationUnit(path.toFile()); - } catch (IOException e) { - System.err.println("Failed to read file: " + path); - System.exit(1); - } - - List> rootTypes = root.getTypes(); - // Estimate of size. - Set annotationFilesForRoot = - new LinkedHashSet<>(CollectionsPlume.mapCapacity(rootTypes.size())); - for (TypeDeclaration type : rootTypes) { - String name = JavaParserUtil.getFullyQualifiedName(type, root); - annotationFilesForRoot.addAll(annotationFiles.getAnnotationFileForType(name)); - } - - for (String annotationFile : annotationFilesForRoot) { - inserter.insertAnnotations(annotationFile, path.toString()); + /** + * Inserts all annotations from an ajava file into a Java file. + * + * @param annotationFileName an ajava file + * @param javaFileName a Java file to insert annotation into + */ + public void insertAnnotations(String annotationFileName, String javaFileName) { + try { + File javaFile = new File(javaFileName); + String fileContents = FilesPlume.readString(Path.of(javaFileName)); + String lineSeparator = FilesPlume.inferLineSeparator(annotationFileName); + try (FileInputStream annotationInputStream = new FileInputStream(annotationFileName)) { + String result = + insertAnnotations(annotationInputStream, fileContents, lineSeparator); + FilesPlume.writeString(javaFile, result); } + } catch (IOException e) { + System.err.println( + "Failed to insert annotations from file " + + annotationFileName + + " into file " + + javaFileName); + System.exit(1); + } + } - return FileVisitResult.CONTINUE; - } - }; + /** + * Inserts annotations from ajava files into Java files in place. + * + *

          The first argument is an ajava file or a directory containing ajava files. + * + *

          The second argument is a Java file or a directory containing Java files to insert + * annotations into. + * + *

          For each Java file, checks if any ajava files from the first argument match it. For each + * such ajava file, inserts all its annotations into the Java file. + * + * @param args command line arguments: the first element should be a path to ajava files and the + * second should be the directory containing Java files to insert into + */ + public static void main(String[] args) { + if (args.length != 2) { + System.out.println( + "Usage: java InsertAjavaAnnotations " + + " insertionVisitor = + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + if (!path.getFileName().toString().endsWith(".java")) { + return FileVisitResult.CONTINUE; + } + + CompilationUnit root = null; + try { + root = JavaParserUtil.parseCompilationUnit(path.toFile()); + } catch (IOException e) { + System.err.println("Failed to read file: " + path); + System.exit(1); + } + + List> rootTypes = root.getTypes(); + // Estimate of size. + Set annotationFilesForRoot = + new LinkedHashSet<>(CollectionsPlume.mapCapacity(rootTypes.size())); + for (TypeDeclaration type : rootTypes) { + String name = JavaParserUtil.getFullyQualifiedName(type, root); + annotationFilesForRoot.addAll( + annotationFiles.getAnnotationFileForType(name)); + } + + for (String annotationFile : annotationFilesForRoot) { + inserter.insertAnnotations(annotationFile, path.toString()); + } + + return FileVisitResult.CONTINUE; + } + }; + + try { + Files.walkFileTree(Paths.get(javaSourceDir), insertionVisitor); + } catch (IOException e) { + System.out.println("Error while adding annotations to: " + javaSourceDir); + e.printStackTrace(); + System.exit(1); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java index 3379d5b94a8..d9fc6394b75 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java @@ -154,10 +154,7 @@ import com.sun.source.tree.WhileLoopTree; import com.sun.source.tree.WildcardTree; import com.sun.source.util.SimpleTreeVisitor; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; @@ -167,6 +164,11 @@ import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + /** * A visitor that processes javac trees and JavaParser nodes simultaneously, matching corresponding * nodes. @@ -186,2205 +188,2241 @@ * called before its children. */ public abstract class JointJavacJavaParserVisitor extends SimpleTreeVisitor { - @Override - public Void visitAnnotation(AnnotationTree javacTree, Node javaParserNode) { - // javac stores annotation arguments as assignments, so @MyAnno("myArg") is stored the same - // as @MyAnno(value="myArg") which has a single element argument list with an assignment. - if (javaParserNode instanceof MarkerAnnotationExpr) { - processAnnotation(javacTree, (MarkerAnnotationExpr) javaParserNode); - } else if (javaParserNode instanceof SingleMemberAnnotationExpr) { - SingleMemberAnnotationExpr node = (SingleMemberAnnotationExpr) javaParserNode; - processAnnotation(javacTree, node); - assert javacTree.getArguments().size() == 1; - ExpressionTree value = javacTree.getArguments().get(0); - assert value instanceof AssignmentTree; - AssignmentTree assignment = (AssignmentTree) value; - assert assignment.getVariable().getKind() == Tree.Kind.IDENTIFIER; - assert ((IdentifierTree) assignment.getVariable()).getName().contentEquals("value"); - assignment.getExpression().accept(this, node.getMemberValue()); - } else if (javaParserNode instanceof NormalAnnotationExpr) { - NormalAnnotationExpr node = (NormalAnnotationExpr) javaParserNode; - processAnnotation(javacTree, node); - assert javacTree.getArguments().size() == node.getPairs().size(); - Iterator argIter = node.getPairs().iterator(); - for (ExpressionTree arg : javacTree.getArguments()) { - assert arg instanceof AssignmentTree; - AssignmentTree assignment = (AssignmentTree) arg; - IdentifierTree memberName = (IdentifierTree) assignment.getVariable(); - MemberValuePair javaParserArg = argIter.next(); - assert memberName.getName().contentEquals(javaParserArg.getNameAsString()); - assignment.getExpression().accept(this, javaParserArg.getValue()); - } - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { - castNode(NodeWithAnnotations.class, javaParserNode, javacTree); - processAnnotatedType(javacTree, javaParserNode); - javacTree.getUnderlyingType().accept(this, javaParserNode); - return null; - } - - @Override - public Void visitArrayAccess(ArrayAccessTree javacTree, Node javaParserNode) { - ArrayAccessExpr node = castNode(ArrayAccessExpr.class, javaParserNode, javacTree); - processArrayAccess(javacTree, node); - javacTree.getExpression().accept(this, node.getName()); - javacTree.getIndex().accept(this, node.getIndex()); - return null; - } - - @Override - public Void visitArrayType(ArrayTypeTree javacTree, Node javaParserNode) { - ArrayType node = castNode(ArrayType.class, javaParserNode, javacTree); - processArrayType(javacTree, node); - javacTree.getType().accept(this, node.getComponentType()); - return null; - } - - @Override - public Void visitAssert(AssertTree javacTree, Node javaParserNode) { - AssertStmt node = castNode(AssertStmt.class, javaParserNode, javacTree); - processAssert(javacTree, node); - javacTree.getCondition().accept(this, node.getCheck()); - visitOptional(javacTree.getDetail(), node.getMessage()); - - return null; - } - - @Override - public Void visitAssignment(AssignmentTree javacTree, Node javaParserNode) { - AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); - processAssignment(javacTree, node); - javacTree.getVariable().accept(this, node.getTarget()); - javacTree.getExpression().accept(this, node.getValue()); - return null; - } - - @Override - public Void visitBinary(BinaryTree javacTree, Node javaParserNode) { - BinaryExpr node = castNode(BinaryExpr.class, javaParserNode, javacTree); - processBinary(javacTree, node); - javacTree.getLeftOperand().accept(this, node.getLeft()); - javacTree.getRightOperand().accept(this, node.getRight()); - return null; - } - - /** - * Visit a BindingPatternTree. - * - * @param javacTree a BindingPatternTree, typed as Tree to be backward-compatible - * @param javaParserNode a PatternExpr - * @return nothing - */ - @SuppressWarnings("UnusedVariable") - public Void visitBindingPattern17(Tree javacTree, Node javaParserNode) { - PatternExpr patternExpr = castNode(PatternExpr.class, javaParserNode, javacTree); - processBindingPattern(javacTree, patternExpr); - VariableTree variableTree = BindingPatternUtils.getVariable(javacTree); - // The name expression can be null, even when a name exists. - if (variableTree.getNameExpression() != null) { - variableTree.getNameExpression().accept(this, patternExpr.getName()); - } - - assert variableTree.getInitializer() == null; - variableTree.getType().accept(this, patternExpr.getType()); - - return null; - } - - @Override - public Void visitBlock(BlockTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof InitializerDeclaration) { - return javacTree.accept(this, ((InitializerDeclaration) javaParserNode).getBody()); - } - - BlockStmt node = castNode(BlockStmt.class, javaParserNode, javacTree); - processBlock(javacTree, node); - processStatements(javacTree.getStatements(), node.getStatements()); - return null; - } - - /** - * Given a matching sequence of statements for a block, visits each javac statement with its - * corresponding JavaParser statement, excluding synthetic javac trees like no-argument - * constructors. - * - * @param javacStatements sequence of javac trees for statements - * @param javaParserStatements sequence of JavaParser statements representing the same block as - * {@code javacStatements} - */ - private void processStatements( - Iterable javacStatements, Iterable javaParserStatements) { - PeekingIterator javacIter = - Iterators.peekingIterator(javacStatements.iterator()); - PeekingIterator javaParserIter = - Iterators.peekingIterator(javaParserStatements.iterator()); - - while (javacIter.hasNext() || javaParserIter.hasNext()) { - // Skip synthetic javac super() calls by checking if the JavaParser statement matches. - if (javacIter.hasNext() - && isDefaultSuperConstructorCall(javacIter.peek()) - && (!javaParserIter.hasNext() || !isDefaultSuperConstructorCall(javaParserIter.peek()))) { - javacIter.next(); - continue; - } - - // In javac, a line like "int i = 0, j = 0" is expanded as two sibling VariableTree - // instances. In javaParser this is one VariableDeclarationExpr with two nested - // VariableDeclarators. Match the declarators with the VariableTrees. - if (javaParserIter.hasNext() - && javacIter.peek().getKind() == Tree.Kind.VARIABLE - && javaParserIter.peek().isExpressionStmt() - && javaParserIter.peek().asExpressionStmt().getExpression().isVariableDeclarationExpr()) { - for (VariableDeclarator decl : - javaParserIter - .next() - .asExpressionStmt() - .getExpression() - .asVariableDeclarationExpr() - .getVariables()) { - assert javacIter.hasNext(); - javacIter.next().accept(this, decl); + @Override + public Void visitAnnotation(AnnotationTree javacTree, Node javaParserNode) { + // javac stores annotation arguments as assignments, so @MyAnno("myArg") is stored the same + // as @MyAnno(value="myArg") which has a single element argument list with an assignment. + if (javaParserNode instanceof MarkerAnnotationExpr) { + processAnnotation(javacTree, (MarkerAnnotationExpr) javaParserNode); + } else if (javaParserNode instanceof SingleMemberAnnotationExpr) { + SingleMemberAnnotationExpr node = (SingleMemberAnnotationExpr) javaParserNode; + processAnnotation(javacTree, node); + assert javacTree.getArguments().size() == 1; + ExpressionTree value = javacTree.getArguments().get(0); + assert value instanceof AssignmentTree; + AssignmentTree assignment = (AssignmentTree) value; + assert assignment.getVariable().getKind() == Tree.Kind.IDENTIFIER; + assert ((IdentifierTree) assignment.getVariable()).getName().contentEquals("value"); + assignment.getExpression().accept(this, node.getMemberValue()); + } else if (javaParserNode instanceof NormalAnnotationExpr) { + NormalAnnotationExpr node = (NormalAnnotationExpr) javaParserNode; + processAnnotation(javacTree, node); + assert javacTree.getArguments().size() == node.getPairs().size(); + Iterator argIter = node.getPairs().iterator(); + for (ExpressionTree arg : javacTree.getArguments()) { + assert arg instanceof AssignmentTree; + AssignmentTree assignment = (AssignmentTree) arg; + IdentifierTree memberName = (IdentifierTree) assignment.getVariable(); + MemberValuePair javaParserArg = argIter.next(); + assert memberName.getName().contentEquals(javaParserArg.getNameAsString()); + assignment.getExpression().accept(this, javaParserArg.getValue()); + } + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); } - continue; - } - - assert javacIter.hasNext(); - assert javaParserIter.hasNext(); - javacIter.next().accept(this, javaParserIter.next()); - } - - assert !javacIter.hasNext(); - assert !javaParserIter.hasNext(); - } - - /** - * Returns whether a javac statement represents a method call {@code super()}. - * - * @param statement the javac statement to check - * @return true if statement is a method invocation named "super" with no arguments, false - * otherwise - */ - public static boolean isDefaultSuperConstructorCall(StatementTree statement) { - if (statement.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { - return false; - } - - ExpressionStatementTree expressionStatement = (ExpressionStatementTree) statement; - if (expressionStatement.getExpression().getKind() != Tree.Kind.METHOD_INVOCATION) { - return false; - } - - MethodInvocationTree invocation = (MethodInvocationTree) expressionStatement.getExpression(); - if (invocation.getMethodSelect().getKind() != Tree.Kind.IDENTIFIER) { - return false; - } - - if (!((IdentifierTree) invocation.getMethodSelect()).getName().contentEquals("super")) { - return false; - } - - return invocation.getArguments().isEmpty(); - } - - /** - * Returns whether a JavaParser statement represents a method call {@code super()}. - * - * @param statement the JavaParser statement to check - * @return true if statement is an explicit super constructor invocation with no arguments - */ - private boolean isDefaultSuperConstructorCall(Statement statement) { - if (!statement.isExplicitConstructorInvocationStmt()) { - return false; - } - - ExplicitConstructorInvocationStmt invocation = statement.asExplicitConstructorInvocationStmt(); - boolean isSuper = !invocation.isThis(); - return isSuper && invocation.getArguments().isEmpty(); - } - - @Override - public Void visitBreak(BreakTree javacTree, Node javaParserNode) { - BreakStmt node = castNode(BreakStmt.class, javaParserNode, javacTree); - processBreak(javacTree, node); - return null; - } - - @Override - public Void visitCase(CaseTree javacTree, Node javaParserNode) { - SwitchEntry node = castNode(SwitchEntry.class, javaParserNode, javacTree); - processCase(javacTree, node); - // Java 12 introduced multiple label cases: - List labels = node.getLabels(); - List treeExpressions = CaseUtils.getExpressions(javacTree); - assert node.getLabels().size() == treeExpressions.size() - : String.format( - "node.getLabels() = %s, treeExpressions = %s", node.getLabels(), treeExpressions); - for (int i = 0; i < treeExpressions.size(); i++) { - treeExpressions.get(i).accept(this, labels.get(i)); - } - if (javacTree.getStatements() == null) { - Tree javacBody = CaseUtils.getBody(javacTree); - Statement nodeBody = node.getStatement(0); - if (javacBody.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { - javacBody.accept(this, node.getStatement(0)); - } else if (nodeBody.isExpressionStmt()) { - javacBody.accept(this, nodeBody.asExpressionStmt().getExpression()); - } else { - javacBody.accept(this, nodeBody); - } - } else { - processStatements(javacTree.getStatements(), node.getStatements()); - } - - return null; - } - - @Override - public Void visitCatch(CatchTree javacTree, Node javaParserNode) { - CatchClause node = castNode(CatchClause.class, javaParserNode, javacTree); - processCatch(javacTree, node); - javacTree.getParameter().accept(this, node.getParameter()); - javacTree.getBlock().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitClass(ClassTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof ClassOrInterfaceDeclaration) { - ClassOrInterfaceDeclaration node = (ClassOrInterfaceDeclaration) javaParserNode; - processClass(javacTree, node); - visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); - - if (javacTree.getKind() == Tree.Kind.CLASS) { - if (javacTree.getExtendsClause() == null) { - assert node.getExtendedTypes().isEmpty(); + return null; + } + + @Override + public Void visitAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { + castNode(NodeWithAnnotations.class, javaParserNode, javacTree); + processAnnotatedType(javacTree, javaParserNode); + javacTree.getUnderlyingType().accept(this, javaParserNode); + return null; + } + + @Override + public Void visitArrayAccess(ArrayAccessTree javacTree, Node javaParserNode) { + ArrayAccessExpr node = castNode(ArrayAccessExpr.class, javaParserNode, javacTree); + processArrayAccess(javacTree, node); + javacTree.getExpression().accept(this, node.getName()); + javacTree.getIndex().accept(this, node.getIndex()); + return null; + } + + @Override + public Void visitArrayType(ArrayTypeTree javacTree, Node javaParserNode) { + ArrayType node = castNode(ArrayType.class, javaParserNode, javacTree); + processArrayType(javacTree, node); + javacTree.getType().accept(this, node.getComponentType()); + return null; + } + + @Override + public Void visitAssert(AssertTree javacTree, Node javaParserNode) { + AssertStmt node = castNode(AssertStmt.class, javaParserNode, javacTree); + processAssert(javacTree, node); + javacTree.getCondition().accept(this, node.getCheck()); + visitOptional(javacTree.getDetail(), node.getMessage()); + + return null; + } + + @Override + public Void visitAssignment(AssignmentTree javacTree, Node javaParserNode) { + AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); + processAssignment(javacTree, node); + javacTree.getVariable().accept(this, node.getTarget()); + javacTree.getExpression().accept(this, node.getValue()); + return null; + } + + @Override + public Void visitBinary(BinaryTree javacTree, Node javaParserNode) { + BinaryExpr node = castNode(BinaryExpr.class, javaParserNode, javacTree); + processBinary(javacTree, node); + javacTree.getLeftOperand().accept(this, node.getLeft()); + javacTree.getRightOperand().accept(this, node.getRight()); + return null; + } + + /** + * Visit a BindingPatternTree. + * + * @param javacTree a BindingPatternTree, typed as Tree to be backward-compatible + * @param javaParserNode a PatternExpr + * @return nothing + */ + @SuppressWarnings("UnusedVariable") + public Void visitBindingPattern17(Tree javacTree, Node javaParserNode) { + PatternExpr patternExpr = castNode(PatternExpr.class, javaParserNode, javacTree); + processBindingPattern(javacTree, patternExpr); + VariableTree variableTree = BindingPatternUtils.getVariable(javacTree); + // The name expression can be null, even when a name exists. + if (variableTree.getNameExpression() != null) { + variableTree.getNameExpression().accept(this, patternExpr.getName()); + } + + assert variableTree.getInitializer() == null; + variableTree.getType().accept(this, patternExpr.getType()); + + return null; + } + + @Override + public Void visitBlock(BlockTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof InitializerDeclaration) { + return javacTree.accept(this, ((InitializerDeclaration) javaParserNode).getBody()); + } + + BlockStmt node = castNode(BlockStmt.class, javaParserNode, javacTree); + processBlock(javacTree, node); + processStatements(javacTree.getStatements(), node.getStatements()); + return null; + } + + /** + * Given a matching sequence of statements for a block, visits each javac statement with its + * corresponding JavaParser statement, excluding synthetic javac trees like no-argument + * constructors. + * + * @param javacStatements sequence of javac trees for statements + * @param javaParserStatements sequence of JavaParser statements representing the same block as + * {@code javacStatements} + */ + private void processStatements( + Iterable javacStatements, + Iterable javaParserStatements) { + PeekingIterator javacIter = + Iterators.peekingIterator(javacStatements.iterator()); + PeekingIterator javaParserIter = + Iterators.peekingIterator(javaParserStatements.iterator()); + + while (javacIter.hasNext() || javaParserIter.hasNext()) { + // Skip synthetic javac super() calls by checking if the JavaParser statement matches. + if (javacIter.hasNext() + && isDefaultSuperConstructorCall(javacIter.peek()) + && (!javaParserIter.hasNext() + || !isDefaultSuperConstructorCall(javaParserIter.peek()))) { + javacIter.next(); + continue; + } + + // In javac, a line like "int i = 0, j = 0" is expanded as two sibling VariableTree + // instances. In javaParser this is one VariableDeclarationExpr with two nested + // VariableDeclarators. Match the declarators with the VariableTrees. + if (javaParserIter.hasNext() + && javacIter.peek().getKind() == Tree.Kind.VARIABLE + && javaParserIter.peek().isExpressionStmt() + && javaParserIter + .peek() + .asExpressionStmt() + .getExpression() + .isVariableDeclarationExpr()) { + for (VariableDeclarator decl : + javaParserIter + .next() + .asExpressionStmt() + .getExpression() + .asVariableDeclarationExpr() + .getVariables()) { + assert javacIter.hasNext(); + javacIter.next().accept(this, decl); + } + + continue; + } + + assert javacIter.hasNext(); + assert javaParserIter.hasNext(); + javacIter.next().accept(this, javaParserIter.next()); + } + + assert !javacIter.hasNext(); + assert !javaParserIter.hasNext(); + } + + /** + * Returns whether a javac statement represents a method call {@code super()}. + * + * @param statement the javac statement to check + * @return true if statement is a method invocation named "super" with no arguments, false + * otherwise + */ + public static boolean isDefaultSuperConstructorCall(StatementTree statement) { + if (statement.getKind() != Tree.Kind.EXPRESSION_STATEMENT) { + return false; + } + + ExpressionStatementTree expressionStatement = (ExpressionStatementTree) statement; + if (expressionStatement.getExpression().getKind() != Tree.Kind.METHOD_INVOCATION) { + return false; + } + + MethodInvocationTree invocation = + (MethodInvocationTree) expressionStatement.getExpression(); + if (invocation.getMethodSelect().getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + + if (!((IdentifierTree) invocation.getMethodSelect()).getName().contentEquals("super")) { + return false; + } + + return invocation.getArguments().isEmpty(); + } + + /** + * Returns whether a JavaParser statement represents a method call {@code super()}. + * + * @param statement the JavaParser statement to check + * @return true if statement is an explicit super constructor invocation with no arguments + */ + private boolean isDefaultSuperConstructorCall(Statement statement) { + if (!statement.isExplicitConstructorInvocationStmt()) { + return false; + } + + ExplicitConstructorInvocationStmt invocation = + statement.asExplicitConstructorInvocationStmt(); + boolean isSuper = !invocation.isThis(); + return isSuper && invocation.getArguments().isEmpty(); + } + + @Override + public Void visitBreak(BreakTree javacTree, Node javaParserNode) { + BreakStmt node = castNode(BreakStmt.class, javaParserNode, javacTree); + processBreak(javacTree, node); + return null; + } + + @Override + public Void visitCase(CaseTree javacTree, Node javaParserNode) { + SwitchEntry node = castNode(SwitchEntry.class, javaParserNode, javacTree); + processCase(javacTree, node); + // Java 12 introduced multiple label cases: + List labels = node.getLabels(); + List treeExpressions = CaseUtils.getExpressions(javacTree); + assert node.getLabels().size() == treeExpressions.size() + : String.format( + "node.getLabels() = %s, treeExpressions = %s", + node.getLabels(), treeExpressions); + for (int i = 0; i < treeExpressions.size(); i++) { + treeExpressions.get(i).accept(this, labels.get(i)); + } + if (javacTree.getStatements() == null) { + Tree javacBody = CaseUtils.getBody(javacTree); + Statement nodeBody = node.getStatement(0); + if (javacBody.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { + javacBody.accept(this, node.getStatement(0)); + } else if (nodeBody.isExpressionStmt()) { + javacBody.accept(this, nodeBody.asExpressionStmt().getExpression()); + } else { + javacBody.accept(this, nodeBody); + } } else { - assert node.getExtendedTypes().size() == 1; - javacTree.getExtendsClause().accept(this, node.getExtendedTypes().get(0)); + processStatements(javacTree.getStatements(), node.getStatements()); } - visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); - } else if (javacTree.getKind() == Tree.Kind.INTERFACE) { - visitLists(javacTree.getImplementsClause(), node.getExtendedTypes()); - } - - visitClassMembers(javacTree.getMembers(), node.getMembers()); - } else if (javaParserNode instanceof RecordDeclaration) { - RecordDeclaration node = (RecordDeclaration) javaParserNode; - processClass(javacTree, node); - visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); - visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); - List membersWithoutAutoGenerated = - Lists.newArrayList( - Iterables.filter( - javacTree.getMembers(), - (Predicate) - (Tree m) -> { - // Filter out all auto-generated items: - return !TreeUtils.isAutoGeneratedRecordMember(m); - })); - visitClassMembers(membersWithoutAutoGenerated, node.getMembers()); - } else if (javaParserNode instanceof AnnotationDeclaration) { - AnnotationDeclaration node = (AnnotationDeclaration) javaParserNode; - processClass(javacTree, node); - visitClassMembers(javacTree.getMembers(), node.getMembers()); - } else if (javaParserNode instanceof LocalClassDeclarationStmt) { - javacTree.accept(this, ((LocalClassDeclarationStmt) javaParserNode).getClassDeclaration()); - } else if (javaParserNode instanceof LocalRecordDeclarationStmt) { - javacTree.accept(this, ((LocalRecordDeclarationStmt) javaParserNode).getRecordDeclaration()); - } else if (javaParserNode instanceof EnumDeclaration) { - EnumDeclaration node = (EnumDeclaration) javaParserNode; - processClass(javacTree, node); - visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); - // In an enum declaration, javac stores the enum constants expanded as constant variable - // members, whereas JavaParser stores them as one object. Need to match them. - assert javacTree.getKind() == Tree.Kind.ENUM; - List javacMembers = new ArrayList<>(javacTree.getMembers()); - // Discard a synthetic constructor if it exists. If there are any constants in this - // enum, then they will show up as the first members of the javac tree, except for - // possibly a synthetic constructor. - if (!node.getEntries().isEmpty()) { - while (!javacMembers.isEmpty() && javacMembers.get(0).getKind() != Tree.Kind.VARIABLE) { - javacMembers.remove(0); + return null; + } + + @Override + public Void visitCatch(CatchTree javacTree, Node javaParserNode) { + CatchClause node = castNode(CatchClause.class, javaParserNode, javacTree); + processCatch(javacTree, node); + javacTree.getParameter().accept(this, node.getParameter()); + javacTree.getBlock().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitClass(ClassTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof ClassOrInterfaceDeclaration) { + ClassOrInterfaceDeclaration node = (ClassOrInterfaceDeclaration) javaParserNode; + processClass(javacTree, node); + visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); + + if (javacTree.getKind() == Tree.Kind.CLASS) { + if (javacTree.getExtendsClause() == null) { + assert node.getExtendedTypes().isEmpty(); + } else { + assert node.getExtendedTypes().size() == 1; + javacTree.getExtendsClause().accept(this, node.getExtendedTypes().get(0)); + } + + visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); + } else if (javacTree.getKind() == Tree.Kind.INTERFACE) { + visitLists(javacTree.getImplementsClause(), node.getExtendedTypes()); + } + + visitClassMembers(javacTree.getMembers(), node.getMembers()); + } else if (javaParserNode instanceof RecordDeclaration) { + RecordDeclaration node = (RecordDeclaration) javaParserNode; + processClass(javacTree, node); + visitLists(javacTree.getTypeParameters(), node.getTypeParameters()); + visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); + List membersWithoutAutoGenerated = + Lists.newArrayList( + Iterables.filter( + javacTree.getMembers(), + (Predicate) + (Tree m) -> { + // Filter out all auto-generated items: + return !TreeUtils.isAutoGeneratedRecordMember(m); + })); + visitClassMembers(membersWithoutAutoGenerated, node.getMembers()); + } else if (javaParserNode instanceof AnnotationDeclaration) { + AnnotationDeclaration node = (AnnotationDeclaration) javaParserNode; + processClass(javacTree, node); + visitClassMembers(javacTree.getMembers(), node.getMembers()); + } else if (javaParserNode instanceof LocalClassDeclarationStmt) { + javacTree.accept( + this, ((LocalClassDeclarationStmt) javaParserNode).getClassDeclaration()); + } else if (javaParserNode instanceof LocalRecordDeclarationStmt) { + javacTree.accept( + this, ((LocalRecordDeclarationStmt) javaParserNode).getRecordDeclaration()); + } else if (javaParserNode instanceof EnumDeclaration) { + EnumDeclaration node = (EnumDeclaration) javaParserNode; + processClass(javacTree, node); + visitLists(javacTree.getImplementsClause(), node.getImplementedTypes()); + // In an enum declaration, javac stores the enum constants expanded as constant variable + // members, whereas JavaParser stores them as one object. Need to match them. + assert javacTree.getKind() == Tree.Kind.ENUM; + List javacMembers = new ArrayList<>(javacTree.getMembers()); + // Discard a synthetic constructor if it exists. If there are any constants in this + // enum, then they will show up as the first members of the javac tree, except for + // possibly a synthetic constructor. + if (!node.getEntries().isEmpty()) { + while (!javacMembers.isEmpty() + && javacMembers.get(0).getKind() != Tree.Kind.VARIABLE) { + javacMembers.remove(0); + } + } + + for (EnumConstantDeclaration entry : node.getEntries()) { + assert !javacMembers.isEmpty(); + javacMembers.get(0).accept(this, entry); + javacMembers.remove(0); + } + + visitClassMembers(javacMembers, node.getMembers()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); } - } - - for (EnumConstantDeclaration entry : node.getEntries()) { - assert !javacMembers.isEmpty(); - javacMembers.get(0).accept(this, entry); - javacMembers.remove(0); - } - - visitClassMembers(javacMembers, node.getMembers()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - /** - * Given a list of class members for javac and JavaParser, visits each javac member with its - * corresponding JavaParser member. Skips synthetic javac members. - * - * @param javacMembers a list of trees forming the members of a javac {@code ClassTree} - * @param javaParserMembers a list of nodes forming the members of a JavaParser {@code - * ClassOrInterfaceDeclaration} or an {@code ObjectCreationExpr} with an anonymous class body - * that corresponds to {@code javacMembers} - */ - private void visitClassMembers( - List javacMembers, List> javaParserMembers) { - PeekingIterator javacIter = Iterators.peekingIterator(javacMembers.iterator()); - PeekingIterator> javaParserIter = - Iterators.peekingIterator(javaParserMembers.iterator()); - while (javacIter.hasNext() || javaParserIter.hasNext()) { - // Skip javac's synthetic no-argument constructors. - if (javacIter.hasNext() - && isNoArgumentConstructor(javacIter.peek()) - && (!javaParserIter.hasNext() || !isNoArgumentConstructor(javaParserIter.peek()))) { - javacIter.next(); - continue; - } - - // In javac, a line like int i = 0, j = 0 is expanded as two sibling VariableTree - // instances. In JavaParser this is one FieldDeclaration with two nested - // VariableDeclarators. Match the declarators with the VariableTrees. - if (javaParserIter.hasNext() && javaParserIter.peek().isFieldDeclaration()) { - for (VariableDeclarator decl : javaParserIter.next().asFieldDeclaration().getVariables()) { - assert javacIter.hasNext(); - assert javacIter.peek().getKind() == Tree.Kind.VARIABLE; - javacIter.next().accept(this, decl); + + return null; + } + + /** + * Given a list of class members for javac and JavaParser, visits each javac member with its + * corresponding JavaParser member. Skips synthetic javac members. + * + * @param javacMembers a list of trees forming the members of a javac {@code ClassTree} + * @param javaParserMembers a list of nodes forming the members of a JavaParser {@code + * ClassOrInterfaceDeclaration} or an {@code ObjectCreationExpr} with an anonymous class + * body that corresponds to {@code javacMembers} + */ + private void visitClassMembers( + List javacMembers, List> javaParserMembers) { + PeekingIterator javacIter = Iterators.peekingIterator(javacMembers.iterator()); + PeekingIterator> javaParserIter = + Iterators.peekingIterator(javaParserMembers.iterator()); + while (javacIter.hasNext() || javaParserIter.hasNext()) { + // Skip javac's synthetic no-argument constructors. + if (javacIter.hasNext() + && isNoArgumentConstructor(javacIter.peek()) + && (!javaParserIter.hasNext() + || !isNoArgumentConstructor(javaParserIter.peek()))) { + javacIter.next(); + continue; + } + + // In javac, a line like int i = 0, j = 0 is expanded as two sibling VariableTree + // instances. In JavaParser this is one FieldDeclaration with two nested + // VariableDeclarators. Match the declarators with the VariableTrees. + if (javaParserIter.hasNext() && javaParserIter.peek().isFieldDeclaration()) { + for (VariableDeclarator decl : + javaParserIter.next().asFieldDeclaration().getVariables()) { + assert javacIter.hasNext(); + assert javacIter.peek().getKind() == Tree.Kind.VARIABLE; + javacIter.next().accept(this, decl); + } + + continue; + } + + assert javacIter.hasNext(); + assert javaParserIter.hasNext(); + javacIter.next().accept(this, javaParserIter.next()); + } + + assert !javacIter.hasNext(); + assert !javaParserIter.hasNext(); + } + + /** + * Visits the members of an anonymous class body. + * + *

          In normal classes, javac inserts a synthetic no-argument constructor if no constructor is + * explicitly defined, which is skipped when visiting members. Anonymous class bodies may + * introduce constructors that take arguments if the constructor invocation that created them + * was passed arguments. For example, if {@code MyClass} has a constructor taking a single + * integer argument, then writing {@code new MyClass(5) { }} expands to the javac tree + * + *

          {@code
          +     * new MyClass(5) {
          +     *     (int arg) {
          +     *         super(arg);
          +     *     }
          +     * }
          +     * }
          + * + *

          This method skips these synthetic constructors. + * + * @param javacBody body of an anonymous class body + * @param javaParserMembers list of members for the anonymous class body of an {@code + * ObjectCreationExpr} + */ + public void visitAnonymousClassBody( + ClassTree javacBody, List> javaParserMembers) { + List javacMembers = new ArrayList<>(javacBody.getMembers()); + if (!javacMembers.isEmpty()) { + Tree member = javacMembers.get(0); + if (member.getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) member; + if (methodTree.getName().contentEquals("")) { + javacMembers.remove(0); + } + } + } + + visitClassMembers(javacMembers, javaParserMembers); + } + + /** + * Returns whether {@code member} is a javac constructor declaration that takes no arguments. + * + * @param member the javac tree to check + * @return true if {@code member} is a method declaration with name {@code } that takes no + * arguments + */ + public static boolean isNoArgumentConstructor(Tree member) { + if (member.getKind() != Tree.Kind.METHOD) { + return false; } - continue; - } - - assert javacIter.hasNext(); - assert javaParserIter.hasNext(); - javacIter.next().accept(this, javaParserIter.next()); - } - - assert !javacIter.hasNext(); - assert !javaParserIter.hasNext(); - } - - /** - * Visits the members of an anonymous class body. - * - *

          In normal classes, javac inserts a synthetic no-argument constructor if no constructor is - * explicitly defined, which is skipped when visiting members. Anonymous class bodies may - * introduce constructors that take arguments if the constructor invocation that created them was - * passed arguments. For example, if {@code MyClass} has a constructor taking a single integer - * argument, then writing {@code new MyClass(5) { }} expands to the javac tree - * - *

          {@code
          -   * new MyClass(5) {
          -   *     (int arg) {
          -   *         super(arg);
          -   *     }
          -   * }
          -   * }
          - * - *

          This method skips these synthetic constructors. - * - * @param javacBody body of an anonymous class body - * @param javaParserMembers list of members for the anonymous class body of an {@code - * ObjectCreationExpr} - */ - public void visitAnonymousClassBody( - ClassTree javacBody, List> javaParserMembers) { - List javacMembers = new ArrayList<>(javacBody.getMembers()); - if (!javacMembers.isEmpty()) { - Tree member = javacMembers.get(0); - if (member.getKind() == Tree.Kind.METHOD) { MethodTree methodTree = (MethodTree) member; - if (methodTree.getName().contentEquals("")) { - javacMembers.remove(0); + return methodTree.getName().contentEquals("") && methodTree.getParameters().isEmpty(); + } + + /** + * Returns whether {@code member} is a JavaParser constructor declaration that takes no + * arguments. + * + * @param member the JavaParser body declaration to check + * @return true if {@code member} is a constructor declaration that takes no arguments + */ + private boolean isNoArgumentConstructor(BodyDeclaration member) { + return member.isConstructorDeclaration() + && member.asConstructorDeclaration().getParameters().isEmpty(); + } + + @Override + public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { + CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); + processCompilationUnit(javacTree, node); + visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); + visitLists(javacTree.getImports(), node.getImports()); + visitLists(javacTree.getTypeDecls(), node.getTypes()); + return null; + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree javacTree, Node javaParserNode) { + AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); + processCompoundAssignment(javacTree, node); + javacTree.getVariable().accept(this, node.getTarget()); + javacTree.getExpression().accept(this, node.getValue()); + return null; + } + + @Override + public Void visitConditionalExpression( + ConditionalExpressionTree javacTree, Node javaParserNode) { + ConditionalExpr node = castNode(ConditionalExpr.class, javaParserNode, javacTree); + processConditionalExpression(javacTree, node); + javacTree.getCondition().accept(this, node.getCondition()); + javacTree.getTrueExpression().accept(this, node.getThenExpr()); + javacTree.getFalseExpression().accept(this, node.getElseExpr()); + return null; + } + + @Override + public Void visitContinue(ContinueTree javacTree, Node javaParserNode) { + ContinueStmt node = castNode(ContinueStmt.class, javaParserNode, javacTree); + processContinue(javacTree, node); + return null; + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree javacTree, Node javaParserNode) { + DoStmt node = castNode(DoStmt.class, javaParserNode, javacTree); + processDoWhileLoop(javacTree, node); + // In javac the condition is parenthesized but not in JavaParser. + ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); + condition.accept(this, node.getCondition()); + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitEmptyStatement(EmptyStatementTree javacTree, Node javaParserNode) { + EmptyStmt node = castNode(EmptyStmt.class, javaParserNode, javacTree); + processEmptyStatement(javacTree, node); + return null; + } + + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree javacTree, Node javaParserNode) { + ForEachStmt node = castNode(ForEachStmt.class, javaParserNode, javacTree); + processEnhancedForLoop(javacTree, node); + javacTree.getVariable().accept(this, node.getVariableDeclarator()); + javacTree.getExpression().accept(this, node.getIterable()); + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitErroneous(ErroneousTree javacTree, Node javaParserNode) { + // An erroneous tree is a malformed expression, so skip. + return null; + } + + @Override + public Void visitExports(ExportsTree javacTree, Node javaParserNode) { + ModuleExportsDirective node = + castNode(ModuleExportsDirective.class, javaParserNode, javacTree); + processExports(javacTree, node); + visitLists(javacTree.getModuleNames(), node.getModuleNames()); + javacTree.getPackageName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitExpressionStatement(ExpressionStatementTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof ExpressionStmt) { + ExpressionStmt node = (ExpressionStmt) javaParserNode; + processExpressionStatemen(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { + // In this case the javac expression will be a MethodTree. Since JavaParser doesn't + // surround explicit constructor invocations in an expression statement, we match + // javaParserNode to the javac expression rather than the javac expression statement. + javacTree.getExpression().accept(this, javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitForLoop(ForLoopTree javacTree, Node javaParserNode) { + ForStmt node = castNode(ForStmt.class, javaParserNode, javacTree); + processForLoop(javacTree, node); + Iterator javacInitializers = javacTree.getInitializer().iterator(); + for (Expression initializer : node.getInitialization()) { + if (initializer.isVariableDeclarationExpr()) { + for (VariableDeclarator declarator : + initializer.asVariableDeclarationExpr().getVariables()) { + assert javacInitializers.hasNext(); + javacInitializers.next().accept(this, declarator); + } + } else if (initializer.isAssignExpr()) { + ExpressionStatementTree statement = + (ExpressionStatementTree) javacInitializers.next(); + statement.getExpression().accept(this, initializer); + } else { + assert javacInitializers.hasNext(); + StatementTree javacInitializer = javacInitializers.next(); + if (javacInitializer.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { + // JavaParser doesn't wrap other kinds of expressions in an expression + // statement, but javac does. For example, suppose that the initializer is + // "index++", as in the test all-systems/LightWeightCache.java. + ((ExpressionStatementTree) javacInitializer) + .getExpression() + .accept(this, initializer); + } else { + // This is likely to lead to a crash, if it ever happens: javacInitializer is a + // StatementTree of some kind, but initializer is a raw expression (not wrapped + // in a statement). + javacInitializer.accept(this, initializer); + } + } + } + assert !javacInitializers.hasNext(); + + visitOptional(javacTree.getCondition(), node.getCompare()); + + // Javac stores a list of expression statements and JavaParser stores a list of statements, + // the javac statements must be unwrapped. + assert javacTree.getUpdate().size() == node.getUpdate().size(); + Iterator javaParserUpdates = node.getUpdate().iterator(); + for (ExpressionStatementTree javacUpdate : javacTree.getUpdate()) { + // Match the inner javac expression with the JavaParser expression. + javacUpdate.getExpression().accept(this, javaParserUpdates.next()); + } + + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitIdentifier(IdentifierTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof ClassOrInterfaceType) { + processIdentifier(javacTree, (ClassOrInterfaceType) javaParserNode); + } else if (javaParserNode instanceof Name) { + processIdentifier(javacTree, (Name) javaParserNode); + } else if (javaParserNode instanceof NameExpr) { + processIdentifier(javacTree, (NameExpr) javaParserNode); + } else if (javaParserNode instanceof SimpleName) { + processIdentifier(javacTree, (SimpleName) javaParserNode); + } else if (javaParserNode instanceof ThisExpr) { + processIdentifier(javacTree, (ThisExpr) javaParserNode); + } else if (javaParserNode instanceof SuperExpr) { + processIdentifier(javacTree, (SuperExpr) javaParserNode); + } else if (javaParserNode instanceof TypeExpr) { + // This occurs in a member reference like MyClass::myMember. The MyClass is wrapped in a + // TypeExpr. + javacTree.accept(this, ((TypeExpr) javaParserNode).getType()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); } - } - } - - visitClassMembers(javacMembers, javaParserMembers); - } - - /** - * Returns whether {@code member} is a javac constructor declaration that takes no arguments. - * - * @param member the javac tree to check - * @return true if {@code member} is a method declaration with name {@code } that takes no - * arguments - */ - public static boolean isNoArgumentConstructor(Tree member) { - if (member.getKind() != Tree.Kind.METHOD) { - return false; - } - - MethodTree methodTree = (MethodTree) member; - return methodTree.getName().contentEquals("") && methodTree.getParameters().isEmpty(); - } - - /** - * Returns whether {@code member} is a JavaParser constructor declaration that takes no arguments. - * - * @param member the JavaParser body declaration to check - * @return true if {@code member} is a constructor declaration that takes no arguments - */ - private boolean isNoArgumentConstructor(BodyDeclaration member) { - return member.isConstructorDeclaration() - && member.asConstructorDeclaration().getParameters().isEmpty(); - } - - @Override - public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { - CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); - processCompilationUnit(javacTree, node); - visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); - visitLists(javacTree.getImports(), node.getImports()); - visitLists(javacTree.getTypeDecls(), node.getTypes()); - return null; - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree javacTree, Node javaParserNode) { - AssignExpr node = castNode(AssignExpr.class, javaParserNode, javacTree); - processCompoundAssignment(javacTree, node); - javacTree.getVariable().accept(this, node.getTarget()); - javacTree.getExpression().accept(this, node.getValue()); - return null; - } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree javacTree, Node javaParserNode) { - ConditionalExpr node = castNode(ConditionalExpr.class, javaParserNode, javacTree); - processConditionalExpression(javacTree, node); - javacTree.getCondition().accept(this, node.getCondition()); - javacTree.getTrueExpression().accept(this, node.getThenExpr()); - javacTree.getFalseExpression().accept(this, node.getElseExpr()); - return null; - } - - @Override - public Void visitContinue(ContinueTree javacTree, Node javaParserNode) { - ContinueStmt node = castNode(ContinueStmt.class, javaParserNode, javacTree); - processContinue(javacTree, node); - return null; - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree javacTree, Node javaParserNode) { - DoStmt node = castNode(DoStmt.class, javaParserNode, javacTree); - processDoWhileLoop(javacTree, node); - // In javac the condition is parenthesized but not in JavaParser. - ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); - condition.accept(this, node.getCondition()); - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitEmptyStatement(EmptyStatementTree javacTree, Node javaParserNode) { - EmptyStmt node = castNode(EmptyStmt.class, javaParserNode, javacTree); - processEmptyStatement(javacTree, node); - return null; - } - - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree javacTree, Node javaParserNode) { - ForEachStmt node = castNode(ForEachStmt.class, javaParserNode, javacTree); - processEnhancedForLoop(javacTree, node); - javacTree.getVariable().accept(this, node.getVariableDeclarator()); - javacTree.getExpression().accept(this, node.getIterable()); - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitErroneous(ErroneousTree javacTree, Node javaParserNode) { - // An erroneous tree is a malformed expression, so skip. - return null; - } - - @Override - public Void visitExports(ExportsTree javacTree, Node javaParserNode) { - ModuleExportsDirective node = castNode(ModuleExportsDirective.class, javaParserNode, javacTree); - processExports(javacTree, node); - visitLists(javacTree.getModuleNames(), node.getModuleNames()); - javacTree.getPackageName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitExpressionStatement(ExpressionStatementTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof ExpressionStmt) { - ExpressionStmt node = (ExpressionStmt) javaParserNode; - processExpressionStatemen(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { - // In this case the javac expression will be a MethodTree. Since JavaParser doesn't - // surround explicit constructor invocations in an expression statement, we match - // javaParserNode to the javac expression rather than the javac expression statement. - javacTree.getExpression().accept(this, javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitForLoop(ForLoopTree javacTree, Node javaParserNode) { - ForStmt node = castNode(ForStmt.class, javaParserNode, javacTree); - processForLoop(javacTree, node); - Iterator javacInitializers = javacTree.getInitializer().iterator(); - for (Expression initializer : node.getInitialization()) { - if (initializer.isVariableDeclarationExpr()) { - for (VariableDeclarator declarator : - initializer.asVariableDeclarationExpr().getVariables()) { - assert javacInitializers.hasNext(); - javacInitializers.next().accept(this, declarator); + + return null; + } + + @Override + public Void visitIf(IfTree javacTree, Node javaParserNode) { + IfStmt node = castNode(IfStmt.class, javaParserNode, javacTree); + processIf(javacTree, node); + assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; + ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); + condition.accept(this, node.getCondition()); + javacTree.getThenStatement().accept(this, node.getThenStmt()); + visitOptional(javacTree.getElseStatement(), node.getElseStmt()); + + return null; + } + + @Override + public Void visitImport(ImportTree javacTree, Node javaParserNode) { + ImportDeclaration node = castNode(ImportDeclaration.class, javaParserNode, javacTree); + processImport(javacTree, node); + // In javac trees, a name like "a.*" is stored as a member select, but JavaParser just + // stores "a" and records that the name ends in an asterisk. + if (node.isAsterisk()) { + assert javacTree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT; + MemberSelectTree identifier = (MemberSelectTree) javacTree.getQualifiedIdentifier(); + identifier.getExpression().accept(this, node.getName()); + } else { + javacTree.getQualifiedIdentifier().accept(this, node.getName()); } - } else if (initializer.isAssignExpr()) { - ExpressionStatementTree statement = (ExpressionStatementTree) javacInitializers.next(); - statement.getExpression().accept(this, initializer); - } else { - assert javacInitializers.hasNext(); - StatementTree javacInitializer = javacInitializers.next(); - if (javacInitializer.getKind() == Tree.Kind.EXPRESSION_STATEMENT) { - // JavaParser doesn't wrap other kinds of expressions in an expression - // statement, but javac does. For example, suppose that the initializer is - // "index++", as in the test all-systems/LightWeightCache.java. - ((ExpressionStatementTree) javacInitializer).getExpression().accept(this, initializer); + + return null; + } + + @Override + public Void visitInstanceOf(InstanceOfTree javacTree, Node javaParserNode) { + InstanceOfExpr node = castNode(InstanceOfExpr.class, javaParserNode, javacTree); + processInstanceOf(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + if (node.getPattern().isPresent()) { + Tree bindingPattern = InstanceOfUtils.getPattern(javacTree); + visitBindingPattern17(bindingPattern, node.getPattern().get()); } else { - // This is likely to lead to a crash, if it ever happens: javacInitializer is a - // StatementTree of some kind, but initializer is a raw expression (not wrapped - // in a statement). - javacInitializer.accept(this, initializer); + javacTree.getType().accept(this, node.getType()); + } + return null; + } + + @Override + public Void visitIntersectionType(IntersectionTypeTree javacTree, Node javaParserNode) { + IntersectionType node = castNode(IntersectionType.class, javaParserNode, javacTree); + processIntersectionType(javacTree, node); + visitLists(javacTree.getBounds(), node.getElements()); + return null; + } + + @Override + public Void visitLabeledStatement(LabeledStatementTree javacTree, Node javaParserNode) { + LabeledStmt node = castNode(LabeledStmt.class, javaParserNode, javacTree); + processLabeledStatement(javacTree, node); + javacTree.getStatement().accept(this, node.getStatement()); + return null; + } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree javacTree, Node javaParserNode) { + LambdaExpr node = castNode(LambdaExpr.class, javaParserNode, javacTree); + processLambdaExpression(javacTree, node); + visitLists(javacTree.getParameters(), node.getParameters()); + switch (javacTree.getBodyKind()) { + case EXPRESSION: + assert node.getBody() instanceof ExpressionStmt; + ExpressionStmt body = (ExpressionStmt) node.getBody(); + javacTree.getBody().accept(this, body.getExpression()); + break; + case STATEMENT: + javacTree.getBody().accept(this, node.getBody()); + break; } - } - } - assert !javacInitializers.hasNext(); - - visitOptional(javacTree.getCondition(), node.getCompare()); - - // Javac stores a list of expression statements and JavaParser stores a list of statements, - // the javac statements must be unwrapped. - assert javacTree.getUpdate().size() == node.getUpdate().size(); - Iterator javaParserUpdates = node.getUpdate().iterator(); - for (ExpressionStatementTree javacUpdate : javacTree.getUpdate()) { - // Match the inner javac expression with the JavaParser expression. - javacUpdate.getExpression().accept(this, javaParserUpdates.next()); - } - - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitIdentifier(IdentifierTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof ClassOrInterfaceType) { - processIdentifier(javacTree, (ClassOrInterfaceType) javaParserNode); - } else if (javaParserNode instanceof Name) { - processIdentifier(javacTree, (Name) javaParserNode); - } else if (javaParserNode instanceof NameExpr) { - processIdentifier(javacTree, (NameExpr) javaParserNode); - } else if (javaParserNode instanceof SimpleName) { - processIdentifier(javacTree, (SimpleName) javaParserNode); - } else if (javaParserNode instanceof ThisExpr) { - processIdentifier(javacTree, (ThisExpr) javaParserNode); - } else if (javaParserNode instanceof SuperExpr) { - processIdentifier(javacTree, (SuperExpr) javaParserNode); - } else if (javaParserNode instanceof TypeExpr) { - // This occurs in a member reference like MyClass::myMember. The MyClass is wrapped in a - // TypeExpr. - javacTree.accept(this, ((TypeExpr) javaParserNode).getType()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitIf(IfTree javacTree, Node javaParserNode) { - IfStmt node = castNode(IfStmt.class, javaParserNode, javacTree); - processIf(javacTree, node); - assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; - ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); - condition.accept(this, node.getCondition()); - javacTree.getThenStatement().accept(this, node.getThenStmt()); - visitOptional(javacTree.getElseStatement(), node.getElseStmt()); - - return null; - } - - @Override - public Void visitImport(ImportTree javacTree, Node javaParserNode) { - ImportDeclaration node = castNode(ImportDeclaration.class, javaParserNode, javacTree); - processImport(javacTree, node); - // In javac trees, a name like "a.*" is stored as a member select, but JavaParser just - // stores "a" and records that the name ends in an asterisk. - if (node.isAsterisk()) { - assert javacTree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT; - MemberSelectTree identifier = (MemberSelectTree) javacTree.getQualifiedIdentifier(); - identifier.getExpression().accept(this, node.getName()); - } else { - javacTree.getQualifiedIdentifier().accept(this, node.getName()); - } - - return null; - } - - @Override - public Void visitInstanceOf(InstanceOfTree javacTree, Node javaParserNode) { - InstanceOfExpr node = castNode(InstanceOfExpr.class, javaParserNode, javacTree); - processInstanceOf(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - if (node.getPattern().isPresent()) { - Tree bindingPattern = InstanceOfUtils.getPattern(javacTree); - visitBindingPattern17(bindingPattern, node.getPattern().get()); - } else { - javacTree.getType().accept(this, node.getType()); - } - return null; - } - - @Override - public Void visitIntersectionType(IntersectionTypeTree javacTree, Node javaParserNode) { - IntersectionType node = castNode(IntersectionType.class, javaParserNode, javacTree); - processIntersectionType(javacTree, node); - visitLists(javacTree.getBounds(), node.getElements()); - return null; - } - - @Override - public Void visitLabeledStatement(LabeledStatementTree javacTree, Node javaParserNode) { - LabeledStmt node = castNode(LabeledStmt.class, javaParserNode, javacTree); - processLabeledStatement(javacTree, node); - javacTree.getStatement().accept(this, node.getStatement()); - return null; - } - - @Override - public Void visitLambdaExpression(LambdaExpressionTree javacTree, Node javaParserNode) { - LambdaExpr node = castNode(LambdaExpr.class, javaParserNode, javacTree); - processLambdaExpression(javacTree, node); - visitLists(javacTree.getParameters(), node.getParameters()); - switch (javacTree.getBodyKind()) { - case EXPRESSION: - assert node.getBody() instanceof ExpressionStmt; - ExpressionStmt body = (ExpressionStmt) node.getBody(); - javacTree.getBody().accept(this, body.getExpression()); - break; - case STATEMENT: - javacTree.getBody().accept(this, node.getBody()); - break; - } - - return null; - } - - @Override - public Void visitLiteral(LiteralTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof LiteralExpr) { - processLiteral(javacTree, (LiteralExpr) javaParserNode); - } else if (javaParserNode instanceof UnaryExpr) { - // Occurs for negative literals such as -7. - processLiteral(javacTree, (UnaryExpr) javaParserNode); - } else if (javaParserNode instanceof BinaryExpr) { - // Occurs for expression like "a" + "b" where javac compresses them to "ab" but - // JavaParser doesn't. - processLiteral(javacTree, (BinaryExpr) javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitMemberReference(MemberReferenceTree javacTree, Node javaParserNode) { - MethodReferenceExpr node = castNode(MethodReferenceExpr.class, javaParserNode, javacTree); - processMemberReference(javacTree, node); - Tree preColonTree = javacTree.getQualifierExpression(); - if (node.getScope().isTypeExpr()) { - preColonTree.accept(this, node.getScope().asTypeExpr().getType()); - } else { - preColonTree.accept(this, node.getScope()); - } - - assert (javacTree.getTypeArguments() != null) == node.getTypeArguments().isPresent(); - if (node.getTypeArguments().isPresent()) { - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - return null; - } - - @Override - public Void visitMemberSelect(MemberSelectTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof FieldAccessExpr) { - FieldAccessExpr node = (FieldAccessExpr) javaParserNode; - processMemberSelect(javacTree, node); - javacTree.getExpression().accept(this, node.getScope()); - } else if (javaParserNode instanceof Name) { - Name node = (Name) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getQualifier().isPresent(); - javacTree.getExpression().accept(this, node.getQualifier().get()); - } else if (javaParserNode instanceof ClassOrInterfaceType) { - ClassOrInterfaceType node = (ClassOrInterfaceType) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getScope().isPresent(); - javacTree.getExpression().accept(this, node.getScope().get()); - } else if (javaParserNode instanceof ClassExpr) { - ClassExpr node = (ClassExpr) javaParserNode; - processMemberSelect(javacTree, node); - javacTree.getExpression().accept(this, node.getType()); - } else if (javaParserNode instanceof ThisExpr) { - ThisExpr node = (ThisExpr) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getTypeName().isPresent(); - javacTree.getExpression().accept(this, node.getTypeName().get()); - } else if (javaParserNode instanceof SuperExpr) { - SuperExpr node = (SuperExpr) javaParserNode; - processMemberSelect(javacTree, node); - assert node.getTypeName().isPresent(); - javacTree.getExpression().accept(this, node.getTypeName().get()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitMethod(MethodTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof MethodDeclaration) { - visitMethodForMethodDeclaration(javacTree, (MethodDeclaration) javaParserNode); - } else if (javaParserNode instanceof ConstructorDeclaration) { - visitMethodForConstructorDeclaration(javacTree, (ConstructorDeclaration) javaParserNode); - } else if (javaParserNode instanceof CompactConstructorDeclaration) { - visitMethodForConstructorDeclaration( - javacTree, (CompactConstructorDeclaration) javaParserNode); - } else if (javaParserNode instanceof AnnotationMemberDeclaration) { - visitMethodForAnnotationMemberDeclaration( - javacTree, (AnnotationMemberDeclaration) javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - throw new BugInCF("unreachable"); - } - return null; - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * MethodDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser method declaration - */ - private void visitMethodForMethodDeclaration( - MethodTree javacTree, MethodDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - // TODO: Handle modifiers. In javac this is a ModifiersTree but in JavaParser it's a list of - // modifiers. This is a problem because a ModifiersTree has separate accessors to - // annotations and other modifiers, so the order doesn't match. It might be that for - // JavaParser, the annotations and other modifiers are also accessed separately. - if (javacTree.getReturnType() != null) { - javacTree.getReturnType().accept(this, javaParserNode.getType()); - } - // Unlike other javac constructs, the javac list is non-null even if no type parameters are - // present. - visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); - // JavaParser sometimes inserts a receiver parameter that is not present in the source code. - // (Example: on an explicitly-written toString for an enum class.) - if (javacTree.getReceiverParameter() != null - && javaParserNode.getReceiverParameter().isPresent()) { - javacTree.getReceiverParameter().accept(this, javaParserNode.getReceiverParameter().get()); - } - - visitLists(javacTree.getParameters(), javaParserNode.getParameters()); - - visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); - visitOptional(javacTree.getBody(), javaParserNode.getBody()); - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * ConstructorDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser constructor declaration - */ - private void visitMethodForConstructorDeclaration( - MethodTree javacTree, ConstructorDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); - visitOptional(javacTree.getReceiverParameter(), javaParserNode.getReceiverParameter()); - visitLists(javacTree.getParameters(), javaParserNode.getParameters()); - visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); - javacTree.getBody().accept(this, javaParserNode.getBody()); - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * CompactConstructorDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser constructor declaration - */ - private void visitMethodForConstructorDeclaration( - MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); - visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); - javacTree.getBody().accept(this, javaParserNode.getBody()); - } - - /** - * Visits a method declaration in the case where the matched JavaParser node was a {@code - * AnnotationMemberDeclaration}. - * - * @param javacTree method declaration to visit - * @param javaParserNode corresponding JavaParser annotation member declaration - */ - private void visitMethodForAnnotationMemberDeclaration( - MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { - processMethod(javacTree, javaParserNode); - javacTree.getReturnType().accept(this, javaParserNode.getType()); - visitOptional(javacTree.getDefaultValue(), javaParserNode.getDefaultValue()); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof MethodCallExpr) { - MethodCallExpr node = (MethodCallExpr) javaParserNode; - processMethodInvocation(javacTree, node); - // In javac, the type arguments will be empty if no type arguments are specified, but in - // JavaParser the type arguments will have the none Optional value. - assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); - if (!javacTree.getTypeArguments().isEmpty()) { - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - // In JavaParser, the method name itself and receiver are stored as fields of the - // invocation itself, but in javac they might be combined into one MemberSelectTree. - // That member select may also be a single IdentifierTree if no receiver was written. - // This requires one layer of unnesting. - ExpressionTree methodSelect = javacTree.getMethodSelect(); - if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { - methodSelect.accept(this, node.getName()); - } else if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree selection = (MemberSelectTree) methodSelect; - assert node.getScope().isPresent(); - selection.getExpression().accept(this, node.getScope().get()); - } else { - throw new BugInCF("Unexpected method selection type: %s", methodSelect); - } - - visitLists(javacTree.getArguments(), node.getArguments()); - } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { - ExplicitConstructorInvocationStmt node = (ExplicitConstructorInvocationStmt) javaParserNode; - processMethodInvocation(javacTree, node); - assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); - if (!javacTree.getTypeArguments().isEmpty()) { - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - visitLists(javacTree.getArguments(), node.getArguments()); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitModifiers(ModifiersTree arg0, Node arg1) { - // TODO How to handle this? I don't think there's a corresponding JavaParser class, maybe - // the NodeWithModifiers interface? - return null; - } - - @Override - public Void visitModule(ModuleTree javacTree, Node javaParserNode) { - ModuleDeclaration node = castNode(ModuleDeclaration.class, javaParserNode, javacTree); - processModule(javacTree, node); - javacTree.getName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitNewArray(NewArrayTree javacTree, Node javaParserNode) { - // TODO: Implement this. - // - // Some notes: - // - javacTree.getAnnotations() seems to always return empty, any annotations on - // the base type seem to go on the type itself in javacTree.getType(). The JavaParser - // version doesn't even have a corresponding getAnnotations method. - // - When there are no initializers, both systems use similar representations. The - // dimensions line up. - // - When there is an initializer, they differ greatly for multi-dimensional arrays. Javac - // turns an expression like new int[][]{{1, 2}, {3, 4}} into a single NewArray tree with - // type int[] and two initializer elements {1, 2} and {3, 4}. However, for each of the - // sub-initializers, it creates an implicit NewArray tree with a null component type. - // JavaParser keeps the whole expression as one ArrayCreationExpr with multiple dimensions - // and the initializer stored in special ArrayInitializerExpr type. - return null; - } - - @Override - public Void visitNewClass(NewClassTree javacTree, Node javaParserNode) { - ObjectCreationExpr node = castNode(ObjectCreationExpr.class, javaParserNode, javacTree); - processNewClass(javacTree, node); - // When using Java 11 javac, an expression like this.new MyInnerClass() would store "this" - // as the enclosing expression. In Java 8 javac, this would be stored as new - // MyInnerClass(this). So, we only traverse the enclosing expression if present in both. - if (javacTree.getEnclosingExpression() != null && node.getScope().isPresent()) { - javacTree.getEnclosingExpression().accept(this, node.getScope().get()); - } - - javacTree.getIdentifier().accept(this, node.getType()); - if (javacTree.getTypeArguments().isEmpty()) { - assert !node.getTypeArguments().isPresent(); - } else { - assert node.getTypeArguments().isPresent(); - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - - // Remove synthetic javac argument. When using Java 11, an expression like this.new - // MyInnerClass() would store "this" as the enclosing expression. In Java 8, this would be - // stored as new MyInnerClass(this). So, for the argument lists to match, we may have to - // remove the first argument. - List javacArgs = new ArrayList<>(javacTree.getArguments()); - if (javacArgs.size() > node.getArguments().size()) { - javacArgs.remove(0); - } - - visitLists(javacArgs, node.getArguments()); - assert (javacTree.getClassBody() != null) == node.getAnonymousClassBody().isPresent(); - if (javacTree.getClassBody() != null) { - visitAnonymousClassBody(javacTree.getClassBody(), node.getAnonymousClassBody().get()); - } - - return null; - } - - @Override - public Void visitOpens(OpensTree javacTree, Node javaParserNode) { - ModuleOpensDirective node = castNode(ModuleOpensDirective.class, javaParserNode, javacTree); - processOpens(javacTree, node); - javacTree.getPackageName().accept(this, node.getName()); - visitLists(javacTree.getModuleNames(), node.getModuleNames()); - return null; - } - - @Override - public Void visitOther(Tree javacTree, Node javaParserNode) { - processOther(javacTree, javaParserNode); - return null; - } - - @Override - public Void visitPackage(PackageTree javacTree, Node javaParserNode) { - PackageDeclaration node = castNode(PackageDeclaration.class, javaParserNode, javacTree); - processPackage(javacTree, node); - // visitLists(javacTree.getAnnotations(), node.getAnnotations()); - javacTree.getPackageName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitParameterizedType(ParameterizedTypeTree javacTree, Node javaParserNode) { - ClassOrInterfaceType node = castNode(ClassOrInterfaceType.class, javaParserNode, javacTree); - processParameterizedType(javacTree, node); - javacTree.getType().accept(this, node); - // TODO: In a parameterized type, will the first branch ever run? - if (javacTree.getTypeArguments().isEmpty()) { - assert !node.getTypeArguments().isPresent() || node.getTypeArguments().get().isEmpty(); - } else { - assert node.getTypeArguments().isPresent(); - visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); - } - return null; - } - - @Override - public Void visitParenthesized(ParenthesizedTree javacTree, Node javaParserNode) { - EnclosedExpr node = castNode(EnclosedExpr.class, javaParserNode, javacTree); - processParenthesized(javacTree, node); - javacTree.getExpression().accept(this, node.getInner()); - return null; - } - - @Override - public Void visitPrimitiveType(PrimitiveTypeTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof PrimitiveType) { - processPrimitiveType(javacTree, (PrimitiveType) javaParserNode); - } else if (javaParserNode instanceof VoidType) { - processPrimitiveType(javacTree, (VoidType) javaParserNode); - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitProvides(ProvidesTree javacTree, Node javaParserNode) { - ModuleProvidesDirective node = - castNode(ModuleProvidesDirective.class, javaParserNode, javacTree); - processProvides(javacTree, node); - javacTree.getServiceName().accept(this, node.getName()); - visitLists(javacTree.getImplementationNames(), node.getWith()); - return null; - } - - @Override - public Void visitRequires(RequiresTree javacTree, Node javaParserNode) { - ModuleRequiresDirective node = - castNode(ModuleRequiresDirective.class, javaParserNode, javacTree); - processRequires(javacTree, node); - javacTree.getModuleName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitReturn(ReturnTree javacTree, Node javaParserNode) { - ReturnStmt node = castNode(ReturnStmt.class, javaParserNode, javacTree); - processReturn(javacTree, node); - visitOptional(javacTree.getExpression(), node.getExpression()); - - return null; - } - - @Override - public Void visitSwitch(SwitchTree javacTree, Node javaParserNode) { - SwitchStmt node = castNode(SwitchStmt.class, javaParserNode, javacTree); - processSwitch(javacTree, node); - // Switch expressions are always parenthesized in javac but never in JavaParser. - ExpressionTree expression = ((ParenthesizedTree) javacTree.getExpression()).getExpression(); - expression.accept(this, node.getSelector()); - visitLists(javacTree.getCases(), node.getEntries()); - return null; - } - - /** - * Visit a switch expression. - * - * @param javacTree switch expression tree - * @param javaParserNode a JavaParser node - * @return null - */ - public Void visitSwitchExpression17(Tree javacTree, Node javaParserNode) { - SwitchExpr node = castNode(SwitchExpr.class, javaParserNode, javacTree); - processSwitchExpression(javacTree, node); - - // Switch expressions are always parenthesized in javac but never in JavaParser. - ExpressionTree expression = - ((ParenthesizedTree) SwitchExpressionUtils.getExpression(javacTree)).getExpression(); - expression.accept(this, node.getSelector()); - - visitLists(SwitchExpressionUtils.getCases(javacTree), node.getEntries()); - return null; - } - - @Override - public Void visitSynchronized(SynchronizedTree javacTree, Node javaParserNode) { - SynchronizedStmt node = castNode(SynchronizedStmt.class, javaParserNode, javacTree); - processSynchronized(javacTree, node); - ((ParenthesizedTree) javacTree.getExpression()) - .getExpression() - .accept(this, node.getExpression()); - javacTree.getBlock().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitThrow(ThrowTree javacTree, Node javaParserNode) { - ThrowStmt node = castNode(ThrowStmt.class, javaParserNode, javacTree); - processThrow(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - return null; - } - - @Override - public Void visitTry(TryTree javacTree, Node javaParserNode) { - TryStmt node = castNode(TryStmt.class, javaParserNode, javacTree); - processTry(javacTree, node); - Iterator javacResources = javacTree.getResources().iterator(); - for (Expression resource : node.getResources()) { - if (resource.isVariableDeclarationExpr()) { - for (VariableDeclarator declarator : resource.asVariableDeclarationExpr().getVariables()) { - assert javacResources.hasNext(); - javacResources.next().accept(this, declarator); + + return null; + } + + @Override + public Void visitLiteral(LiteralTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof LiteralExpr) { + processLiteral(javacTree, (LiteralExpr) javaParserNode); + } else if (javaParserNode instanceof UnaryExpr) { + // Occurs for negative literals such as -7. + processLiteral(javacTree, (UnaryExpr) javaParserNode); + } else if (javaParserNode instanceof BinaryExpr) { + // Occurs for expression like "a" + "b" where javac compresses them to "ab" but + // JavaParser doesn't. + processLiteral(javacTree, (BinaryExpr) javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); } - } else { - assert javacResources.hasNext(); - javacResources.next().accept(this, resource); - } - } - - javacTree.getBlock().accept(this, node.getTryBlock()); - visitLists(javacTree.getCatches(), node.getCatchClauses()); - visitOptional(javacTree.getFinallyBlock(), node.getFinallyBlock()); - - return null; - } - - @Override - public Void visitTypeCast(TypeCastTree javacTree, Node javaParserNode) { - if (javaParserNode instanceof MethodReferenceExpr) { - // Work around https://github.com/javaparser/javaparser/issues/3855 - return null; - } - CastExpr node = castNode(CastExpr.class, javaParserNode, javacTree); - processTypeCast(javacTree, node); - javacTree.getType().accept(this, node.getType()); - javacTree.getExpression().accept(this, node.getExpression()); - return null; - } - - @Override - public Void visitTypeParameter(TypeParameterTree javacTree, Node javaParserNode) { - TypeParameter node = castNode(TypeParameter.class, javaParserNode, javacTree); - processTypeParameter(javacTree, node); - visitLists(javacTree.getBounds(), node.getTypeBound()); - return null; - } - - @Override - public Void visitUnary(UnaryTree javacTree, Node javaParserNode) { - UnaryExpr node = castNode(UnaryExpr.class, javaParserNode, javacTree); - processUnary(javacTree, node); - javacTree.getExpression().accept(this, node.getExpression()); - return null; - } - - @Override - public Void visitUnionType(UnionTypeTree javacTree, Node javaParserNode) { - UnionType node = castNode(UnionType.class, javaParserNode, javacTree); - processUnionType(javacTree, node); - visitLists(javacTree.getTypeAlternatives(), node.getElements()); - return null; - } - - @Override - public Void visitUses(UsesTree javacTree, Node javaParserNode) { - ModuleUsesDirective node = castNode(ModuleUsesDirective.class, javaParserNode, javacTree); - processUses(javacTree, node); - javacTree.getServiceName().accept(this, node.getName()); - return null; - } - - @Override - public Void visitVariable(VariableTree javacTree, Node javaParserNode) { - // Javac uses the class VariableTree to represent multiple syntactic concepts such as - // variable declarations, parameters, and fields. - if (javaParserNode instanceof VariableDeclarator) { - // JavaParser uses VariableDeclarator as parts of other declaration types like - // VariableDeclarationExpr when multiple variables may be declared. - VariableDeclarator node = (VariableDeclarator) javaParserNode; - processVariable(javacTree, node); - // Don't process the variable type when it's the Java keyword "var". - if (!node.getType().isVarType() - && (!node.getType().isClassOrInterfaceType() - || !node.getType().asClassOrInterfaceType().getName().asString().equals("var"))) { + + return null; + } + + @Override + public Void visitMemberReference(MemberReferenceTree javacTree, Node javaParserNode) { + MethodReferenceExpr node = castNode(MethodReferenceExpr.class, javaParserNode, javacTree); + processMemberReference(javacTree, node); + Tree preColonTree = javacTree.getQualifierExpression(); + if (node.getScope().isTypeExpr()) { + preColonTree.accept(this, node.getScope().asTypeExpr().getType()); + } else { + preColonTree.accept(this, node.getScope()); + } + + assert (javacTree.getTypeArguments() != null) == node.getTypeArguments().isPresent(); + if (node.getTypeArguments().isPresent()) { + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + return null; + } + + @Override + public Void visitMemberSelect(MemberSelectTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof FieldAccessExpr) { + FieldAccessExpr node = (FieldAccessExpr) javaParserNode; + processMemberSelect(javacTree, node); + javacTree.getExpression().accept(this, node.getScope()); + } else if (javaParserNode instanceof Name) { + Name node = (Name) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getQualifier().isPresent(); + javacTree.getExpression().accept(this, node.getQualifier().get()); + } else if (javaParserNode instanceof ClassOrInterfaceType) { + ClassOrInterfaceType node = (ClassOrInterfaceType) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getScope().isPresent(); + javacTree.getExpression().accept(this, node.getScope().get()); + } else if (javaParserNode instanceof ClassExpr) { + ClassExpr node = (ClassExpr) javaParserNode; + processMemberSelect(javacTree, node); + javacTree.getExpression().accept(this, node.getType()); + } else if (javaParserNode instanceof ThisExpr) { + ThisExpr node = (ThisExpr) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getTypeName().isPresent(); + javacTree.getExpression().accept(this, node.getTypeName().get()); + } else if (javaParserNode instanceof SuperExpr) { + SuperExpr node = (SuperExpr) javaParserNode; + processMemberSelect(javacTree, node); + assert node.getTypeName().isPresent(); + javacTree.getExpression().accept(this, node.getTypeName().get()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitMethod(MethodTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof MethodDeclaration) { + visitMethodForMethodDeclaration(javacTree, (MethodDeclaration) javaParserNode); + } else if (javaParserNode instanceof ConstructorDeclaration) { + visitMethodForConstructorDeclaration( + javacTree, (ConstructorDeclaration) javaParserNode); + } else if (javaParserNode instanceof CompactConstructorDeclaration) { + visitMethodForConstructorDeclaration( + javacTree, (CompactConstructorDeclaration) javaParserNode); + } else if (javaParserNode instanceof AnnotationMemberDeclaration) { + visitMethodForAnnotationMemberDeclaration( + javacTree, (AnnotationMemberDeclaration) javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + throw new BugInCF("unreachable"); + } + return null; + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * MethodDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser method declaration + */ + private void visitMethodForMethodDeclaration( + MethodTree javacTree, MethodDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + // TODO: Handle modifiers. In javac this is a ModifiersTree but in JavaParser it's a list of + // modifiers. This is a problem because a ModifiersTree has separate accessors to + // annotations and other modifiers, so the order doesn't match. It might be that for + // JavaParser, the annotations and other modifiers are also accessed separately. + if (javacTree.getReturnType() != null) { + javacTree.getReturnType().accept(this, javaParserNode.getType()); + } + // Unlike other javac constructs, the javac list is non-null even if no type parameters are + // present. + visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); + // JavaParser sometimes inserts a receiver parameter that is not present in the source code. + // (Example: on an explicitly-written toString for an enum class.) + if (javacTree.getReceiverParameter() != null + && javaParserNode.getReceiverParameter().isPresent()) { + javacTree + .getReceiverParameter() + .accept(this, javaParserNode.getReceiverParameter().get()); + } + + visitLists(javacTree.getParameters(), javaParserNode.getParameters()); + + visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); + visitOptional(javacTree.getBody(), javaParserNode.getBody()); + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * ConstructorDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser constructor declaration + */ + private void visitMethodForConstructorDeclaration( + MethodTree javacTree, ConstructorDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); + visitOptional(javacTree.getReceiverParameter(), javaParserNode.getReceiverParameter()); + visitLists(javacTree.getParameters(), javaParserNode.getParameters()); + visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); + javacTree.getBody().accept(this, javaParserNode.getBody()); + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * CompactConstructorDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser constructor declaration + */ + private void visitMethodForConstructorDeclaration( + MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + visitLists(javacTree.getTypeParameters(), javaParserNode.getTypeParameters()); + visitLists(javacTree.getThrows(), javaParserNode.getThrownExceptions()); + javacTree.getBody().accept(this, javaParserNode.getBody()); + } + + /** + * Visits a method declaration in the case where the matched JavaParser node was a {@code + * AnnotationMemberDeclaration}. + * + * @param javacTree method declaration to visit + * @param javaParserNode corresponding JavaParser annotation member declaration + */ + private void visitMethodForAnnotationMemberDeclaration( + MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { + processMethod(javacTree, javaParserNode); + javacTree.getReturnType().accept(this, javaParserNode.getType()); + visitOptional(javacTree.getDefaultValue(), javaParserNode.getDefaultValue()); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof MethodCallExpr) { + MethodCallExpr node = (MethodCallExpr) javaParserNode; + processMethodInvocation(javacTree, node); + // In javac, the type arguments will be empty if no type arguments are specified, but in + // JavaParser the type arguments will have the none Optional value. + assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); + if (!javacTree.getTypeArguments().isEmpty()) { + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + // In JavaParser, the method name itself and receiver are stored as fields of the + // invocation itself, but in javac they might be combined into one MemberSelectTree. + // That member select may also be a single IdentifierTree if no receiver was written. + // This requires one layer of unnesting. + ExpressionTree methodSelect = javacTree.getMethodSelect(); + if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { + methodSelect.accept(this, node.getName()); + } else if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree selection = (MemberSelectTree) methodSelect; + assert node.getScope().isPresent(); + selection.getExpression().accept(this, node.getScope().get()); + } else { + throw new BugInCF("Unexpected method selection type: %s", methodSelect); + } + + visitLists(javacTree.getArguments(), node.getArguments()); + } else if (javaParserNode instanceof ExplicitConstructorInvocationStmt) { + ExplicitConstructorInvocationStmt node = + (ExplicitConstructorInvocationStmt) javaParserNode; + processMethodInvocation(javacTree, node); + assert javacTree.getTypeArguments().isEmpty() != node.getTypeArguments().isPresent(); + if (!javacTree.getTypeArguments().isEmpty()) { + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + visitLists(javacTree.getArguments(), node.getArguments()); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitModifiers(ModifiersTree arg0, Node arg1) { + // TODO How to handle this? I don't think there's a corresponding JavaParser class, maybe + // the NodeWithModifiers interface? + return null; + } + + @Override + public Void visitModule(ModuleTree javacTree, Node javaParserNode) { + ModuleDeclaration node = castNode(ModuleDeclaration.class, javaParserNode, javacTree); + processModule(javacTree, node); + javacTree.getName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitNewArray(NewArrayTree javacTree, Node javaParserNode) { + // TODO: Implement this. + // + // Some notes: + // - javacTree.getAnnotations() seems to always return empty, any annotations on + // the base type seem to go on the type itself in javacTree.getType(). The JavaParser + // version doesn't even have a corresponding getAnnotations method. + // - When there are no initializers, both systems use similar representations. The + // dimensions line up. + // - When there is an initializer, they differ greatly for multi-dimensional arrays. Javac + // turns an expression like new int[][]{{1, 2}, {3, 4}} into a single NewArray tree with + // type int[] and two initializer elements {1, 2} and {3, 4}. However, for each of the + // sub-initializers, it creates an implicit NewArray tree with a null component type. + // JavaParser keeps the whole expression as one ArrayCreationExpr with multiple dimensions + // and the initializer stored in special ArrayInitializerExpr type. + return null; + } + + @Override + public Void visitNewClass(NewClassTree javacTree, Node javaParserNode) { + ObjectCreationExpr node = castNode(ObjectCreationExpr.class, javaParserNode, javacTree); + processNewClass(javacTree, node); + // When using Java 11 javac, an expression like this.new MyInnerClass() would store "this" + // as the enclosing expression. In Java 8 javac, this would be stored as new + // MyInnerClass(this). So, we only traverse the enclosing expression if present in both. + if (javacTree.getEnclosingExpression() != null && node.getScope().isPresent()) { + javacTree.getEnclosingExpression().accept(this, node.getScope().get()); + } + + javacTree.getIdentifier().accept(this, node.getType()); + if (javacTree.getTypeArguments().isEmpty()) { + assert !node.getTypeArguments().isPresent(); + } else { + assert node.getTypeArguments().isPresent(); + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + + // Remove synthetic javac argument. When using Java 11, an expression like this.new + // MyInnerClass() would store "this" as the enclosing expression. In Java 8, this would be + // stored as new MyInnerClass(this). So, for the argument lists to match, we may have to + // remove the first argument. + List javacArgs = new ArrayList<>(javacTree.getArguments()); + if (javacArgs.size() > node.getArguments().size()) { + javacArgs.remove(0); + } + + visitLists(javacArgs, node.getArguments()); + assert (javacTree.getClassBody() != null) == node.getAnonymousClassBody().isPresent(); + if (javacTree.getClassBody() != null) { + visitAnonymousClassBody(javacTree.getClassBody(), node.getAnonymousClassBody().get()); + } + + return null; + } + + @Override + public Void visitOpens(OpensTree javacTree, Node javaParserNode) { + ModuleOpensDirective node = castNode(ModuleOpensDirective.class, javaParserNode, javacTree); + processOpens(javacTree, node); + javacTree.getPackageName().accept(this, node.getName()); + visitLists(javacTree.getModuleNames(), node.getModuleNames()); + return null; + } + + @Override + public Void visitOther(Tree javacTree, Node javaParserNode) { + processOther(javacTree, javaParserNode); + return null; + } + + @Override + public Void visitPackage(PackageTree javacTree, Node javaParserNode) { + PackageDeclaration node = castNode(PackageDeclaration.class, javaParserNode, javacTree); + processPackage(javacTree, node); + // visitLists(javacTree.getAnnotations(), node.getAnnotations()); + javacTree.getPackageName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitParameterizedType(ParameterizedTypeTree javacTree, Node javaParserNode) { + ClassOrInterfaceType node = castNode(ClassOrInterfaceType.class, javaParserNode, javacTree); + processParameterizedType(javacTree, node); + javacTree.getType().accept(this, node); + // TODO: In a parameterized type, will the first branch ever run? + if (javacTree.getTypeArguments().isEmpty()) { + assert !node.getTypeArguments().isPresent() || node.getTypeArguments().get().isEmpty(); + } else { + assert node.getTypeArguments().isPresent(); + visitLists(javacTree.getTypeArguments(), node.getTypeArguments().get()); + } + return null; + } + + @Override + public Void visitParenthesized(ParenthesizedTree javacTree, Node javaParserNode) { + EnclosedExpr node = castNode(EnclosedExpr.class, javaParserNode, javacTree); + processParenthesized(javacTree, node); + javacTree.getExpression().accept(this, node.getInner()); + return null; + } + + @Override + public Void visitPrimitiveType(PrimitiveTypeTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof PrimitiveType) { + processPrimitiveType(javacTree, (PrimitiveType) javaParserNode); + } else if (javaParserNode instanceof VoidType) { + processPrimitiveType(javacTree, (VoidType) javaParserNode); + } else { + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitProvides(ProvidesTree javacTree, Node javaParserNode) { + ModuleProvidesDirective node = + castNode(ModuleProvidesDirective.class, javaParserNode, javacTree); + processProvides(javacTree, node); + javacTree.getServiceName().accept(this, node.getName()); + visitLists(javacTree.getImplementationNames(), node.getWith()); + return null; + } + + @Override + public Void visitRequires(RequiresTree javacTree, Node javaParserNode) { + ModuleRequiresDirective node = + castNode(ModuleRequiresDirective.class, javaParserNode, javacTree); + processRequires(javacTree, node); + javacTree.getModuleName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitReturn(ReturnTree javacTree, Node javaParserNode) { + ReturnStmt node = castNode(ReturnStmt.class, javaParserNode, javacTree); + processReturn(javacTree, node); + visitOptional(javacTree.getExpression(), node.getExpression()); + + return null; + } + + @Override + public Void visitSwitch(SwitchTree javacTree, Node javaParserNode) { + SwitchStmt node = castNode(SwitchStmt.class, javaParserNode, javacTree); + processSwitch(javacTree, node); + // Switch expressions are always parenthesized in javac but never in JavaParser. + ExpressionTree expression = ((ParenthesizedTree) javacTree.getExpression()).getExpression(); + expression.accept(this, node.getSelector()); + visitLists(javacTree.getCases(), node.getEntries()); + return null; + } + + /** + * Visit a switch expression. + * + * @param javacTree switch expression tree + * @param javaParserNode a JavaParser node + * @return null + */ + public Void visitSwitchExpression17(Tree javacTree, Node javaParserNode) { + SwitchExpr node = castNode(SwitchExpr.class, javaParserNode, javacTree); + processSwitchExpression(javacTree, node); + + // Switch expressions are always parenthesized in javac but never in JavaParser. + ExpressionTree expression = + ((ParenthesizedTree) SwitchExpressionUtils.getExpression(javacTree)) + .getExpression(); + expression.accept(this, node.getSelector()); + + visitLists(SwitchExpressionUtils.getCases(javacTree), node.getEntries()); + return null; + } + + @Override + public Void visitSynchronized(SynchronizedTree javacTree, Node javaParserNode) { + SynchronizedStmt node = castNode(SynchronizedStmt.class, javaParserNode, javacTree); + processSynchronized(javacTree, node); + ((ParenthesizedTree) javacTree.getExpression()) + .getExpression() + .accept(this, node.getExpression()); + javacTree.getBlock().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitThrow(ThrowTree javacTree, Node javaParserNode) { + ThrowStmt node = castNode(ThrowStmt.class, javaParserNode, javacTree); + processThrow(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + return null; + } + + @Override + public Void visitTry(TryTree javacTree, Node javaParserNode) { + TryStmt node = castNode(TryStmt.class, javaParserNode, javacTree); + processTry(javacTree, node); + Iterator javacResources = javacTree.getResources().iterator(); + for (Expression resource : node.getResources()) { + if (resource.isVariableDeclarationExpr()) { + for (VariableDeclarator declarator : + resource.asVariableDeclarationExpr().getVariables()) { + assert javacResources.hasNext(); + javacResources.next().accept(this, declarator); + } + } else { + assert javacResources.hasNext(); + javacResources.next().accept(this, resource); + } + } + + javacTree.getBlock().accept(this, node.getTryBlock()); + visitLists(javacTree.getCatches(), node.getCatchClauses()); + visitOptional(javacTree.getFinallyBlock(), node.getFinallyBlock()); + + return null; + } + + @Override + public Void visitTypeCast(TypeCastTree javacTree, Node javaParserNode) { + if (javaParserNode instanceof MethodReferenceExpr) { + // Work around https://github.com/javaparser/javaparser/issues/3855 + return null; + } + CastExpr node = castNode(CastExpr.class, javaParserNode, javacTree); + processTypeCast(javacTree, node); javacTree.getType().accept(this, node.getType()); - } - - // The name expression can be null, even when a name exists. - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - visitOptional(javacTree.getInitializer(), node.getInitializer()); - } else if (javaParserNode instanceof Parameter) { - Parameter node = (Parameter) javaParserNode; - processVariable(javacTree, node); - if (node.isVarArgs()) { - ArrayTypeTree arrayType; - // A varargs parameter's type will either be an ArrayTypeTree or an - // AnnotatedType depending on whether it has an annotation. - if (javacTree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { - arrayType = (ArrayTypeTree) javacTree.getType(); + javacTree.getExpression().accept(this, node.getExpression()); + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterTree javacTree, Node javaParserNode) { + TypeParameter node = castNode(TypeParameter.class, javaParserNode, javacTree); + processTypeParameter(javacTree, node); + visitLists(javacTree.getBounds(), node.getTypeBound()); + return null; + } + + @Override + public Void visitUnary(UnaryTree javacTree, Node javaParserNode) { + UnaryExpr node = castNode(UnaryExpr.class, javaParserNode, javacTree); + processUnary(javacTree, node); + javacTree.getExpression().accept(this, node.getExpression()); + return null; + } + + @Override + public Void visitUnionType(UnionTypeTree javacTree, Node javaParserNode) { + UnionType node = castNode(UnionType.class, javaParserNode, javacTree); + processUnionType(javacTree, node); + visitLists(javacTree.getTypeAlternatives(), node.getElements()); + return null; + } + + @Override + public Void visitUses(UsesTree javacTree, Node javaParserNode) { + ModuleUsesDirective node = castNode(ModuleUsesDirective.class, javaParserNode, javacTree); + processUses(javacTree, node); + javacTree.getServiceName().accept(this, node.getName()); + return null; + } + + @Override + public Void visitVariable(VariableTree javacTree, Node javaParserNode) { + // Javac uses the class VariableTree to represent multiple syntactic concepts such as + // variable declarations, parameters, and fields. + if (javaParserNode instanceof VariableDeclarator) { + // JavaParser uses VariableDeclarator as parts of other declaration types like + // VariableDeclarationExpr when multiple variables may be declared. + VariableDeclarator node = (VariableDeclarator) javaParserNode; + processVariable(javacTree, node); + // Don't process the variable type when it's the Java keyword "var". + if (!node.getType().isVarType() + && (!node.getType().isClassOrInterfaceType() + || !node.getType() + .asClassOrInterfaceType() + .getName() + .asString() + .equals("var"))) { + javacTree.getType().accept(this, node.getType()); + } + + // The name expression can be null, even when a name exists. + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + visitOptional(javacTree.getInitializer(), node.getInitializer()); + } else if (javaParserNode instanceof Parameter) { + Parameter node = (Parameter) javaParserNode; + processVariable(javacTree, node); + if (node.isVarArgs()) { + ArrayTypeTree arrayType; + // A varargs parameter's type will either be an ArrayTypeTree or an + // AnnotatedType depending on whether it has an annotation. + if (javacTree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { + arrayType = (ArrayTypeTree) javacTree.getType(); + } else { + AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) javacTree.getType(); + arrayType = (ArrayTypeTree) annotatedType.getUnderlyingType(); + } + + arrayType.getType().accept(this, node.getType()); + } else { + // Types for lambda parameters without explicit types don't have JavaParser nodes, + // don't process them. + if (!node.getType().isUnknownType()) { + javacTree.getType().accept(this, node.getType()); + } + } + + // The name expression can be null, even when a name exists. + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + assert javacTree.getInitializer() == null; + } else if (javaParserNode instanceof ReceiverParameter) { + ReceiverParameter node = (ReceiverParameter) javaParserNode; + processVariable(javacTree, node); + javacTree.getType().accept(this, node.getType()); + // The name expression can be null, even when a name exists. + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + assert javacTree.getInitializer() == null; + } else if (javaParserNode instanceof EnumConstantDeclaration) { + // In javac, an enum constant is expanded as a variable declaration initialized to a + // constuctor call. + EnumConstantDeclaration node = (EnumConstantDeclaration) javaParserNode; + processVariable(javacTree, node); + if (javacTree.getNameExpression() != null) { + javacTree.getNameExpression().accept(this, node.getName()); + } + + assert javacTree.getInitializer().getKind() == Tree.Kind.NEW_CLASS; + NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); + visitLists(constructor.getArguments(), node.getArguments()); + if (constructor.getClassBody() != null) { + visitAnonymousClassBody(constructor.getClassBody(), node.getClassBody()); + } else { + assert node.getClassBody().isEmpty(); + } } else { - AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) javacTree.getType(); - arrayType = (ArrayTypeTree) annotatedType.getUnderlyingType(); + throwUnexpectedNodeType(javacTree, javaParserNode); + } + + return null; + } + + @Override + public Void visitWhileLoop(WhileLoopTree javacTree, Node javaParserNode) { + WhileStmt node = castNode(WhileStmt.class, javaParserNode, javacTree); + processWhileLoop(javacTree, node); + // While loop conditions are always parenthesized in javac but never in JavaParser. + assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; + ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); + condition.accept(this, node.getCondition()); + javacTree.getStatement().accept(this, node.getBody()); + return null; + } + + @Override + public Void visitWildcard(WildcardTree javacTree, Node javaParserNode) { + WildcardType node = castNode(WildcardType.class, javaParserNode, javacTree); + processWildcard(javacTree, node); + // In javac, whether the bound is an extends or super clause depends on the kind of the + // tree. + assert (javacTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) + == node.getExtendedType().isPresent(); + assert (javacTree.getKind() == Tree.Kind.SUPER_WILDCARD) == node.getSuperType().isPresent(); + switch (javacTree.getKind()) { + case UNBOUNDED_WILDCARD: + break; + case EXTENDS_WILDCARD: + javacTree.getBound().accept(this, node.getExtendedType().get()); + break; + case SUPER_WILDCARD: + javacTree.getBound().accept(this, node.getSuperType().get()); + break; + default: + throw new BugInCF("Unexpected wildcard kind: %s", javacTree); + } + + return null; + } + + /** + * Visit a YieldTree + * + * @param tree a YieldTree, typed as Tree to be backward-compatible + * @param node a YieldStmt, typed as Node to be backward-compatible + * @return nothing + */ + public Void visitYield17(Tree tree, Node node) { + if (node instanceof YieldStmt) { + YieldStmt yieldStmt = castNode(YieldStmt.class, node, tree); + processYield(tree, yieldStmt); + + YieldUtils.getValue(tree).accept(this, yieldStmt.getExpression()); + return null; + } + // JavaParser does not parse yields correctly: + // https://github.com/javaparser/javaparser/issues/3364 + // So skip yields that aren't matched with a YieldStmt. + return null; + } + + /** + * Process an {@code AnnotationTree} with multiple key-value pairs like {@code @MyAnno(a=5, + * b=10)}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotation( + AnnotationTree javacTree, NormalAnnotationExpr javaParserNode); + + /** + * Process an {@code AnnotationTree} with no arguments like {@code @MyAnno}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotation( + AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode); + + /** + * Process an {@code AnnotationTree} with a single argument like {@code MyAnno(5)}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotation( + AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode); + + /** + * Process an {@code AnnotatedTypeTree}. + * + *

          In javac, a type with an annotation is represented as an {@code AnnotatedTypeTree} with a + * nested tree for the base type whereas in JavaParser the annotations are store directly on the + * node for the base type. As a result, the JavaParser base type node will be processed twice, + * once with the {@code AnnotatedTypeTree} and once with the tree for the base type. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode); + + /** + * Process an {@code ArrayAccessTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processArrayAccess( + ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode); + + /** + * Process an {@code ArrayTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode); + + /** + * Process an {@code AssertTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAssert(AssertTree javacTree, AssertStmt javaParserNode); + + /** + * Process an {@code AssignmentTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode); + + /** + * Process a {@code BinaryTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode); + + /** + * Process a {@code BindingPatternTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBindingPattern(Tree javacTree, PatternExpr javaParserNode); + + /** + * Process a {@code BlockTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBlock(BlockTree javacTree, BlockStmt javaParserNode); + + /** + * Process a {@code BreakTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processBreak(BreakTree javacTree, BreakStmt javaParserNode); + + /** + * Process a {@code CaseTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCase(CaseTree javacTree, SwitchEntry javaParserNode); + + /** + * Process a {@code CatchTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCatch(CatchTree javacTree, CatchClause javaParserNode); + + /** + * Process a {@code ClassTree} representing an annotation declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode); + + /** + * Process a {@code ClassTree} representing a class or interface declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass( + ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode); + + /** + * Process a {@code ClassTree} representing a record declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass(ClassTree javacTree, RecordDeclaration javaParserNode); + + /** + * Process a {@code ClassTree} representing an enum declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processClass(ClassTree javacTree, EnumDeclaration javaParserNode); + + /** + * Process a {@code CompilationUnitTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCompilationUnit( + CompilationUnitTree javacTree, CompilationUnit javaParserNode); + + /** + * Process a {@code ConditionalExpressionTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processConditionalExpression( + ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode); + + /** + * Process a {@code ContinueTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode); + + /** + * Process a {@code DoWhileLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode); + + /** + * Process an {@code EmptyStatementTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processEmptyStatement( + EmptyStatementTree javacTree, EmptyStmt javaParserNode); + + /** + * Process an {@code EnhancedForLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processEnhancedForLoop( + EnhancedForLoopTree javacTree, ForEachStmt javaParserNode); + + /** + * Process an {@code ExportsTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processExports( + ExportsTree javacTree, ModuleExportsDirective javaParserNode); + + /** + * Process an {@code ExpressionStatementTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processExpressionStatemen( + ExpressionStatementTree javacTree, ExpressionStmt javaParserNode); + + /** + * Process a {@code ForLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a class or interface type. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier( + IdentifierTree javacTree, ClassOrInterfaceType javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a name that may contain dots. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, Name javaParserNode); + + /** + * Process an {@code IdentifierTree} representing an expression that evaluates to the value of a + * variable. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a name without dots. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a {@code super} expression like the {@code + * super} in {@code super.myMethod()} or {@code MyClass.super.myMethod()}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode); + + /** + * Process an {@code IdentifierTree} representing a {@code this} expression like the {@code + * this} in {@code MyClass = this}, {@code this.myMethod()}, or {@code MyClass.this.myMethod()}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode); + + /** + * Process an {@code IfTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIf(IfTree javacTree, IfStmt javaParserNode); + + /** + * Process an {@code ImportTree}. + * + *

          Wildcards are stored differently between the two. In a statement like {@code import a.*;}, + * the name is stored as a {@code MemberSelectTree} with {@code a} and {@code *}. In JavaParser + * this is just stored as {@code a} but with a method that returns whether it has a wildcard. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processImport(ImportTree javacTree, ImportDeclaration javaParserNode); + + /** + * Process an {@code InstanceOfTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode); + + /** + * Process an {@code IntersectionType}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processIntersectionType( + IntersectionTypeTree javacTree, IntersectionType javaParserNode); + + /** + * Process a {@code LabeledStatement}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLabeledStatement( + LabeledStatementTree javacTree, LabeledStmt javaParserNode); + + /** + * Process a {@code LambdaExpressionTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLambdaExpression( + LambdaExpressionTree javacTree, LambdaExpr javaParserNode); + + /** + * Process a {@code LiteralTree} for a String literal defined using concatenation. + * + *

          For an expression like {@code "a" + "b"}, javac stores a single String literal {@code + * "ab"} but JavaParser stores it as an operation with two operands. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode); + + /** + * Process a {@code LiteralTree} for a literal expression prefixed with {@code +} or {@code -} + * like {@code +5} or {@code -2}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode); + + /** + * Process a {@code LiteralTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode); + + /** + * Process a {@code MemberReferenceTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberReference( + MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a class expression like {@code MyClass.class}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a type with a name containing dots, like {@code + * mypackage.MyClass}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect( + MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a field access expression like {@code myObj.myField}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect( + MemberSelectTree javacTree, FieldAccessExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a name that contains dots. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a this expression with a class like {@code + * MyClass.this}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode); + + /** + * Process a {@code MemberSelectTree} for a super expression with a class like {@code + * super.MyClass}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode); + + /** + * Process a {@code MethodTree} representing a regular method declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode); + + /** + * Process a {@code MethodTree} representing a constructor declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode); + + /** + * Process a {@code MethodTree} representing a compact constructor declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod( + MethodTree javacTree, CompactConstructorDeclaration javaParserNode); + + /** + * Process a {@code MethodTree} representing a value field for an annotation. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethod( + MethodTree javacTree, AnnotationMemberDeclaration javaParserNode); + + /** + * Process a {@code MethodInvocationTree} representing a constructor invocation. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethodInvocation( + MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode); + + /** + * Process a {@code MethodInvocationTree} representing a regular method invocation. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processMethodInvocation( + MethodInvocationTree javacTree, MethodCallExpr javaParserNode); + + /** + * Process a {@code ModuleTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode); + + /** + * Process a {@code NewClassTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode); + + /** + * Process an {@code OpensTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode); + + /** + * Process a {@code Tree} that isn't an instance of any specific tree class. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processOther(Tree javacTree, Node javaParserNode); + + /** + * Process a {@code PackageTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode); + + /** + * Process a {@code ParameterizedTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processParameterizedType( + ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode); + + /** + * Process a {@code ParenthesizedTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processParenthesized( + ParenthesizedTree javacTree, EnclosedExpr javaParserNode); + + /** + * Process a {@code PrimitiveTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processPrimitiveType( + PrimitiveTypeTree javacTree, PrimitiveType javaParserNode); + + /** + * Process a {@code PrimitiveTypeTree} representing a void type. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode); + + /** + * Process a {@code ProvidesTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processProvides( + ProvidesTree javacTree, ModuleProvidesDirective javaParserNode); + + /** + * Process a {@code RequiresTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processRequires( + RequiresTree javacTree, ModuleRequiresDirective javaParserNode); + + /** + * Process a {@code RetrunTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode); + + /** + * Process a {@code SwitchTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode); + + /** + * Process a {@code SwitchExpressionTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode); + + /** + * Process a {@code SynchronizedTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processSynchronized( + SynchronizedTree javacTree, SynchronizedStmt javaParserNode); + + /** + * Process a {@code ThrowTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode); + + /** + * Process a {@code TryTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processTry(TryTree javacTree, TryStmt javaParserNode); + + /** + * Process a {@code TypeCastTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode); + + /** + * Process a {@code TypeParameterTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processTypeParameter( + TypeParameterTree javacTree, TypeParameter javaParserNode); + + /** + * Process a {@code UnaryTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode); + + /** + * Process a {@code UnionTypeTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode); + + /** + * Process a {@code UsesTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode); + + /** + * Process a {@code VariableTree} representing an enum constant declaration. In an enum like + * {@code enum MyEnum { MY_CONSTANT }}, javac expands {@code MY_CONSTANT} as a constant + * variable. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable( + VariableTree javacTree, EnumConstantDeclaration javaParserNode); + + /** + * Process a {@code VariableTree} representing a parameter to a method or constructor. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable(VariableTree javacTree, Parameter javaParserNode); + + /** + * Process a {@code VariableTree} representing the receiver parameter of a method. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode); + + /** + * Process a {@code VariableTree} representing a regular variable declaration. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode); + + /** + * Process a {@code WhileLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode); + + /** + * Process a {@code WhileLoopTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processWildcard(WildcardTree javacTree, WildcardType javaParserNode); + + /** + * Process a {@code YieldTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding Javaparser node + */ + public abstract void processYield(Tree javacTree, YieldStmt javaParserNode); + + /** + * Process a {@code CompoundAssignmentTree}. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void processCompoundAssignment( + CompoundAssignmentTree javacTree, AssignExpr javaParserNode); + + /** + * Given a list of javac trees and a list of JavaParser nodes, where the elements of the lists + * correspond to each other, visit each javac tree along with its corresponding JavaParser node. + * + *

          The two lists must be of the same length and elements at corresponding positions must + * match. + * + * @param javacTrees list of trees + * @param javaParserNodes list of corresponding JavaParser nodes + */ + protected void visitLists( + List javacTrees, List javaParserNodes) { + if (javacTrees.size() != javaParserNodes.size()) { + throw new BugInCF( + "%s.visitLists(%s [size %d], %s [size %d])", + this.getClass().getCanonicalName(), + javacTrees, + javacTrees.size(), + javaParserNodes, + javaParserNodes.size()); + } + Iterator nodeIter = javaParserNodes.iterator(); + for (Tree tree : javacTrees) { + tree.accept(this, nodeIter.next()); + } + } + + /** + * Visit an optional syntax construct. Iff the javac tree is non-null, the JavaParser optional + * is present. + * + * @param javacTree a javac tree or null + * @param javaParserNode an optional JavaParser node, which might not be present + */ + @SuppressWarnings("optional:optional.parameter") // interface with JavaParser + protected void visitOptional( + @Nullable Tree javacTree, Optional javaParserNode) { + assert javacTree != null == javaParserNode.isPresent() + : String.format("visitOptional(%s, %s)", javacTree, javaParserNode); + if (javacTree != null) { + javacTree.accept(this, javaParserNode.get()); + } + } + + /** + * Cast {@code javaParserNode} to type {@code type} and return it. + * + * @param the type of {@code type} + * @param type the type to cast to + * @param javaParserNode the object to cast + * @param javacTree the javac tree that corresponds to {@code javaParserNode}; used only for + * error reporting + * @return javaParserNode, casted to {@code type} + */ + public T castNode(Class type, Node javaParserNode, Tree javacTree) { + if (type.isInstance(javaParserNode)) { + return type.cast(javaParserNode); } + throwUnexpectedNodeType(javacTree, javaParserNode, type); + throw new BugInCF("unreachable"); + } + + /** + * Given a javac tree and JavaParser node which were visited but didn't correspond to each + * other, throws an exception indicating that the visiting process failed for those nodes. + * + * @param javacTree a tree that was visited + * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but + * which was not of the correct type for that tree + * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the + * visitng process at {@code javacTree} and {@code javaParserNode} + */ + private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode) { + throw new BugInCF( + "desynced trees: %s [%s], %s [%s] %s", + javacTree, + javacTree.getClass(), + javaParserNode, + javaParserNode.getClass(), + // There is also XmlPrinter. + new YamlPrinter(true).output(javaParserNode)); + } - arrayType.getType().accept(this, node.getType()); - } else { - // Types for lambda parameters without explicit types don't have JavaParser nodes, - // don't process them. - if (!node.getType().isUnknownType()) { - javacTree.getType().accept(this, node.getType()); + /** + * Given a javac tree and JavaParser node which were visited but didn't correspond to each + * other, throws an exception indicating that the visiting process failed for those nodes + * because {@code javaParserNode} was expected to be of type {@code expectedType}. + * + * @param javacTree a tree that was visited + * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but + * which was not of the correct type for that tree + * @param expectedType the type {@code javaParserNode} was expected to be based on {@code + * javacTree} + * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the + * visitng process at {@code javacTree} and {@code javaParserNode} + */ + private void throwUnexpectedNodeType( + Tree javacTree, Node javaParserNode, Class expectedType) { + throw new BugInCF( + "desynced trees: %s [%s], %s [%s (expected %s)] %s", + javacTree, + javacTree.getClass(), + javaParserNode, + javaParserNode.getClass(), + expectedType, + new YamlPrinter(true).output(javaParserNode)); + } + + /** + * The default action for this visitor. This is inherited from SimpleTreeVisitor, but is only + * called for those methods which do not have an override of the visitXXX method in this class. + * Ultimately, those are the methods added post Java 11, such as for switch-expressions. + * + * @param tree the Javac tree + * @param node the Javaparser node + * @return nothing + */ + @Override + protected Void defaultAction(Tree tree, Node node) { + // Features added between JDK 12 and JDK 17 inclusive. + // Must use String comparison to support compiling on JDK 11 and earlier: + switch (tree.getKind().name()) { + case "BINDING_PATTERN": + return visitBindingPattern17(tree, node); + case "SWITCH_EXPRESSION": + return visitSwitchExpression17(tree, node); + case "YIELD": + return visitYield17(tree, node); } - } - - // The name expression can be null, even when a name exists. - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - assert javacTree.getInitializer() == null; - } else if (javaParserNode instanceof ReceiverParameter) { - ReceiverParameter node = (ReceiverParameter) javaParserNode; - processVariable(javacTree, node); - javacTree.getType().accept(this, node.getType()); - // The name expression can be null, even when a name exists. - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - assert javacTree.getInitializer() == null; - } else if (javaParserNode instanceof EnumConstantDeclaration) { - // In javac, an enum constant is expanded as a variable declaration initialized to a - // constuctor call. - EnumConstantDeclaration node = (EnumConstantDeclaration) javaParserNode; - processVariable(javacTree, node); - if (javacTree.getNameExpression() != null) { - javacTree.getNameExpression().accept(this, node.getName()); - } - - assert javacTree.getInitializer().getKind() == Tree.Kind.NEW_CLASS; - NewClassTree constructor = (NewClassTree) javacTree.getInitializer(); - visitLists(constructor.getArguments(), node.getArguments()); - if (constructor.getClassBody() != null) { - visitAnonymousClassBody(constructor.getClassBody(), node.getClassBody()); - } else { - assert node.getClassBody().isEmpty(); - } - } else { - throwUnexpectedNodeType(javacTree, javaParserNode); - } - - return null; - } - - @Override - public Void visitWhileLoop(WhileLoopTree javacTree, Node javaParserNode) { - WhileStmt node = castNode(WhileStmt.class, javaParserNode, javacTree); - processWhileLoop(javacTree, node); - // While loop conditions are always parenthesized in javac but never in JavaParser. - assert javacTree.getCondition().getKind() == Tree.Kind.PARENTHESIZED; - ExpressionTree condition = ((ParenthesizedTree) javacTree.getCondition()).getExpression(); - condition.accept(this, node.getCondition()); - javacTree.getStatement().accept(this, node.getBody()); - return null; - } - - @Override - public Void visitWildcard(WildcardTree javacTree, Node javaParserNode) { - WildcardType node = castNode(WildcardType.class, javaParserNode, javacTree); - processWildcard(javacTree, node); - // In javac, whether the bound is an extends or super clause depends on the kind of the - // tree. - assert (javacTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) - == node.getExtendedType().isPresent(); - assert (javacTree.getKind() == Tree.Kind.SUPER_WILDCARD) == node.getSuperType().isPresent(); - switch (javacTree.getKind()) { - case UNBOUNDED_WILDCARD: - break; - case EXTENDS_WILDCARD: - javacTree.getBound().accept(this, node.getExtendedType().get()); - break; - case SUPER_WILDCARD: - javacTree.getBound().accept(this, node.getSuperType().get()); - break; - default: - throw new BugInCF("Unexpected wildcard kind: %s", javacTree); - } - - return null; - } - - /** - * Visit a YieldTree - * - * @param tree a YieldTree, typed as Tree to be backward-compatible - * @param node a YieldStmt, typed as Node to be backward-compatible - * @return nothing - */ - public Void visitYield17(Tree tree, Node node) { - if (node instanceof YieldStmt) { - YieldStmt yieldStmt = castNode(YieldStmt.class, node, tree); - processYield(tree, yieldStmt); - - YieldUtils.getValue(tree).accept(this, yieldStmt.getExpression()); - return null; - } - // JavaParser does not parse yields correctly: - // https://github.com/javaparser/javaparser/issues/3364 - // So skip yields that aren't matched with a YieldStmt. - return null; - } - - /** - * Process an {@code AnnotationTree} with multiple key-value pairs like {@code @MyAnno(a=5, - * b=10)}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotation( - AnnotationTree javacTree, NormalAnnotationExpr javaParserNode); - - /** - * Process an {@code AnnotationTree} with no arguments like {@code @MyAnno}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotation( - AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode); - - /** - * Process an {@code AnnotationTree} with a single argument like {@code MyAnno(5)}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotation( - AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode); - - /** - * Process an {@code AnnotatedTypeTree}. - * - *

          In javac, a type with an annotation is represented as an {@code AnnotatedTypeTree} with a - * nested tree for the base type whereas in JavaParser the annotations are store directly on the - * node for the base type. As a result, the JavaParser base type node will be processed twice, - * once with the {@code AnnotatedTypeTree} and once with the tree for the base type. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode); - - /** - * Process an {@code ArrayAccessTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processArrayAccess( - ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode); - - /** - * Process an {@code ArrayTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode); - - /** - * Process an {@code AssertTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAssert(AssertTree javacTree, AssertStmt javaParserNode); - - /** - * Process an {@code AssignmentTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode); - - /** - * Process a {@code BinaryTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode); - - /** - * Process a {@code BindingPatternTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBindingPattern(Tree javacTree, PatternExpr javaParserNode); - - /** - * Process a {@code BlockTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBlock(BlockTree javacTree, BlockStmt javaParserNode); - - /** - * Process a {@code BreakTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processBreak(BreakTree javacTree, BreakStmt javaParserNode); - - /** - * Process a {@code CaseTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCase(CaseTree javacTree, SwitchEntry javaParserNode); - - /** - * Process a {@code CatchTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCatch(CatchTree javacTree, CatchClause javaParserNode); - - /** - * Process a {@code ClassTree} representing an annotation declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode); - - /** - * Process a {@code ClassTree} representing a class or interface declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass( - ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode); - - /** - * Process a {@code ClassTree} representing a record declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass(ClassTree javacTree, RecordDeclaration javaParserNode); - - /** - * Process a {@code ClassTree} representing an enum declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processClass(ClassTree javacTree, EnumDeclaration javaParserNode); - - /** - * Process a {@code CompilationUnitTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCompilationUnit( - CompilationUnitTree javacTree, CompilationUnit javaParserNode); - - /** - * Process a {@code ConditionalExpressionTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processConditionalExpression( - ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode); - - /** - * Process a {@code ContinueTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode); - - /** - * Process a {@code DoWhileLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode); - - /** - * Process an {@code EmptyStatementTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processEmptyStatement( - EmptyStatementTree javacTree, EmptyStmt javaParserNode); - - /** - * Process an {@code EnhancedForLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processEnhancedForLoop( - EnhancedForLoopTree javacTree, ForEachStmt javaParserNode); - - /** - * Process an {@code ExportsTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode); - - /** - * Process an {@code ExpressionStatementTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processExpressionStatemen( - ExpressionStatementTree javacTree, ExpressionStmt javaParserNode); - - /** - * Process a {@code ForLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a class or interface type. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier( - IdentifierTree javacTree, ClassOrInterfaceType javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a name that may contain dots. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, Name javaParserNode); - - /** - * Process an {@code IdentifierTree} representing an expression that evaluates to the value of a - * variable. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a name without dots. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a {@code super} expression like the {@code - * super} in {@code super.myMethod()} or {@code MyClass.super.myMethod()}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode); - - /** - * Process an {@code IdentifierTree} representing a {@code this} expression like the {@code this} - * in {@code MyClass = this}, {@code this.myMethod()}, or {@code MyClass.this.myMethod()}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode); - - /** - * Process an {@code IfTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIf(IfTree javacTree, IfStmt javaParserNode); - - /** - * Process an {@code ImportTree}. - * - *

          Wildcards are stored differently between the two. In a statement like {@code import a.*;}, - * the name is stored as a {@code MemberSelectTree} with {@code a} and {@code *}. In JavaParser - * this is just stored as {@code a} but with a method that returns whether it has a wildcard. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processImport(ImportTree javacTree, ImportDeclaration javaParserNode); - - /** - * Process an {@code InstanceOfTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode); - - /** - * Process an {@code IntersectionType}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processIntersectionType( - IntersectionTypeTree javacTree, IntersectionType javaParserNode); - - /** - * Process a {@code LabeledStatement}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLabeledStatement( - LabeledStatementTree javacTree, LabeledStmt javaParserNode); - - /** - * Process a {@code LambdaExpressionTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLambdaExpression( - LambdaExpressionTree javacTree, LambdaExpr javaParserNode); - - /** - * Process a {@code LiteralTree} for a String literal defined using concatenation. - * - *

          For an expression like {@code "a" + "b"}, javac stores a single String literal {@code "ab"} - * but JavaParser stores it as an operation with two operands. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode); - - /** - * Process a {@code LiteralTree} for a literal expression prefixed with {@code +} or {@code -} - * like {@code +5} or {@code -2}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode); - - /** - * Process a {@code LiteralTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode); - - /** - * Process a {@code MemberReferenceTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberReference( - MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a class expression like {@code MyClass.class}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a type with a name containing dots, like {@code - * mypackage.MyClass}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect( - MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a field access expression like {@code myObj.myField}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect( - MemberSelectTree javacTree, FieldAccessExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a name that contains dots. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a this expression with a class like {@code - * MyClass.this}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode); - - /** - * Process a {@code MemberSelectTree} for a super expression with a class like {@code - * super.MyClass}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode); - - /** - * Process a {@code MethodTree} representing a regular method declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode); - - /** - * Process a {@code MethodTree} representing a constructor declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode); - - /** - * Process a {@code MethodTree} representing a compact constructor declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod( - MethodTree javacTree, CompactConstructorDeclaration javaParserNode); - - /** - * Process a {@code MethodTree} representing a value field for an annotation. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethod( - MethodTree javacTree, AnnotationMemberDeclaration javaParserNode); - - /** - * Process a {@code MethodInvocationTree} representing a constructor invocation. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethodInvocation( - MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode); - - /** - * Process a {@code MethodInvocationTree} representing a regular method invocation. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processMethodInvocation( - MethodInvocationTree javacTree, MethodCallExpr javaParserNode); - - /** - * Process a {@code ModuleTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode); - - /** - * Process a {@code NewClassTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode); - - /** - * Process an {@code OpensTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode); - - /** - * Process a {@code Tree} that isn't an instance of any specific tree class. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processOther(Tree javacTree, Node javaParserNode); - - /** - * Process a {@code PackageTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode); - - /** - * Process a {@code ParameterizedTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processParameterizedType( - ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode); - - /** - * Process a {@code ParenthesizedTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processParenthesized( - ParenthesizedTree javacTree, EnclosedExpr javaParserNode); - - /** - * Process a {@code PrimitiveTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processPrimitiveType( - PrimitiveTypeTree javacTree, PrimitiveType javaParserNode); - - /** - * Process a {@code PrimitiveTypeTree} representing a void type. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode); - - /** - * Process a {@code ProvidesTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processProvides( - ProvidesTree javacTree, ModuleProvidesDirective javaParserNode); - - /** - * Process a {@code RequiresTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processRequires( - RequiresTree javacTree, ModuleRequiresDirective javaParserNode); - - /** - * Process a {@code RetrunTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode); - - /** - * Process a {@code SwitchTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode); - - /** - * Process a {@code SwitchExpressionTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode); - - /** - * Process a {@code SynchronizedTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processSynchronized( - SynchronizedTree javacTree, SynchronizedStmt javaParserNode); - - /** - * Process a {@code ThrowTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode); - - /** - * Process a {@code TryTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processTry(TryTree javacTree, TryStmt javaParserNode); - - /** - * Process a {@code TypeCastTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode); - - /** - * Process a {@code TypeParameterTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processTypeParameter( - TypeParameterTree javacTree, TypeParameter javaParserNode); - - /** - * Process a {@code UnaryTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode); - - /** - * Process a {@code UnionTypeTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode); - - /** - * Process a {@code UsesTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode); - - /** - * Process a {@code VariableTree} representing an enum constant declaration. In an enum like - * {@code enum MyEnum { MY_CONSTANT }}, javac expands {@code MY_CONSTANT} as a constant variable. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable( - VariableTree javacTree, EnumConstantDeclaration javaParserNode); - - /** - * Process a {@code VariableTree} representing a parameter to a method or constructor. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable(VariableTree javacTree, Parameter javaParserNode); - - /** - * Process a {@code VariableTree} representing the receiver parameter of a method. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode); - - /** - * Process a {@code VariableTree} representing a regular variable declaration. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode); - - /** - * Process a {@code WhileLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode); - - /** - * Process a {@code WhileLoopTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processWildcard(WildcardTree javacTree, WildcardType javaParserNode); - - /** - * Process a {@code YieldTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding Javaparser node - */ - public abstract void processYield(Tree javacTree, YieldStmt javaParserNode); - - /** - * Process a {@code CompoundAssignmentTree}. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void processCompoundAssignment( - CompoundAssignmentTree javacTree, AssignExpr javaParserNode); - - /** - * Given a list of javac trees and a list of JavaParser nodes, where the elements of the lists - * correspond to each other, visit each javac tree along with its corresponding JavaParser node. - * - *

          The two lists must be of the same length and elements at corresponding positions must match. - * - * @param javacTrees list of trees - * @param javaParserNodes list of corresponding JavaParser nodes - */ - protected void visitLists(List javacTrees, List javaParserNodes) { - if (javacTrees.size() != javaParserNodes.size()) { - throw new BugInCF( - "%s.visitLists(%s [size %d], %s [size %d])", - this.getClass().getCanonicalName(), - javacTrees, - javacTrees.size(), - javaParserNodes, - javaParserNodes.size()); - } - Iterator nodeIter = javaParserNodes.iterator(); - for (Tree tree : javacTrees) { - tree.accept(this, nodeIter.next()); - } - } - - /** - * Visit an optional syntax construct. Iff the javac tree is non-null, the JavaParser optional is - * present. - * - * @param javacTree a javac tree or null - * @param javaParserNode an optional JavaParser node, which might not be present - */ - @SuppressWarnings("optional:optional.parameter") // interface with JavaParser - protected void visitOptional(@Nullable Tree javacTree, Optional javaParserNode) { - assert javacTree != null == javaParserNode.isPresent() - : String.format("visitOptional(%s, %s)", javacTree, javaParserNode); - if (javacTree != null) { - javacTree.accept(this, javaParserNode.get()); - } - } - - /** - * Cast {@code javaParserNode} to type {@code type} and return it. - * - * @param the type of {@code type} - * @param type the type to cast to - * @param javaParserNode the object to cast - * @param javacTree the javac tree that corresponds to {@code javaParserNode}; used only for error - * reporting - * @return javaParserNode, casted to {@code type} - */ - public T castNode(Class type, Node javaParserNode, Tree javacTree) { - if (type.isInstance(javaParserNode)) { - return type.cast(javaParserNode); - } - throwUnexpectedNodeType(javacTree, javaParserNode, type); - throw new BugInCF("unreachable"); - } - - /** - * Given a javac tree and JavaParser node which were visited but didn't correspond to each other, - * throws an exception indicating that the visiting process failed for those nodes. - * - * @param javacTree a tree that was visited - * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but which - * was not of the correct type for that tree - * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the - * visitng process at {@code javacTree} and {@code javaParserNode} - */ - private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode) { - throw new BugInCF( - "desynced trees: %s [%s], %s [%s] %s", - javacTree, - javacTree.getClass(), - javaParserNode, - javaParserNode.getClass(), - // There is also XmlPrinter. - new YamlPrinter(true).output(javaParserNode)); - } - - /** - * Given a javac tree and JavaParser node which were visited but didn't correspond to each other, - * throws an exception indicating that the visiting process failed for those nodes because {@code - * javaParserNode} was expected to be of type {@code expectedType}. - * - * @param javacTree a tree that was visited - * @param javaParserNode a node that was visited at the same time as {@code javacTree}, but which - * was not of the correct type for that tree - * @param expectedType the type {@code javaParserNode} was expected to be based on {@code - * javacTree} - * @throws BugInCF that indicates the javac trees and JavaParser nodes were desynced during the - * visitng process at {@code javacTree} and {@code javaParserNode} - */ - private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode, Class expectedType) { - throw new BugInCF( - "desynced trees: %s [%s], %s [%s (expected %s)] %s", - javacTree, - javacTree.getClass(), - javaParserNode, - javaParserNode.getClass(), - expectedType, - new YamlPrinter(true).output(javaParserNode)); - } - - /** - * The default action for this visitor. This is inherited from SimpleTreeVisitor, but is only - * called for those methods which do not have an override of the visitXXX method in this class. - * Ultimately, those are the methods added post Java 11, such as for switch-expressions. - * - * @param tree the Javac tree - * @param node the Javaparser node - * @return nothing - */ - @Override - protected Void defaultAction(Tree tree, Node node) { - // Features added between JDK 12 and JDK 17 inclusive. - // Must use String comparison to support compiling on JDK 11 and earlier: - switch (tree.getKind().name()) { - case "BINDING_PATTERN": - return visitBindingPattern17(tree, node); - case "SWITCH_EXPRESSION": - return visitSwitchExpression17(tree, node); - case "YIELD": - return visitYield17(tree, node); - } - - return super.defaultAction(tree, node); - } + + return super.defaultAction(tree, node); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java index cf3007c1235..eb221e216be 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointVisitorWithDefaultAction.java @@ -141,446 +141,448 @@ *

          To use this class, override {@code defaultJointAction}. */ public abstract class JointVisitorWithDefaultAction extends JointJavacJavaParserVisitor { - /** - * Action performed on each javac tree and JavaParser node pair. - * - * @param javacTree tree to process - * @param javaParserNode corresponding JavaParser node - */ - public abstract void defaultJointAction(Tree javacTree, Node javaParserNode); - - @Override - public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAnnotation( - AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCatch(CatchTree javacTree, CatchClause javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCompilationUnit( - CompilationUnitTree javacTree, CompilationUnit javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processConditionalExpression( - ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processExpressionStatemen( - ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIf(IfTree javacTree, IfStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processIntersectionType( - IntersectionTypeTree javacTree, IntersectionType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLabeledStatement(LabeledStatementTree javacTree, LabeledStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberReference( - MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processMethodInvocation( - MethodInvocationTree javacTree, MethodCallExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processOther(Tree javacTree, Node javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processParameterizedType( - ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processTry(TryTree javacTree, TryStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, Parameter javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processYield(Tree javacTree, YieldStmt javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } - - @Override - public void processCompoundAssignment( - CompoundAssignmentTree javacTree, AssignExpr javaParserNode) { - defaultJointAction(javacTree, javaParserNode); - } + /** + * Action performed on each javac tree and JavaParser node pair. + * + * @param javacTree tree to process + * @param javaParserNode corresponding JavaParser node + */ + public abstract void defaultJointAction(Tree javacTree, Node javaParserNode); + + @Override + public void processAnnotation(AnnotationTree javacTree, NormalAnnotationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAnnotation(AnnotationTree javacTree, MarkerAnnotationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAnnotation( + AnnotationTree javacTree, SingleMemberAnnotationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAnnotatedType(AnnotatedTypeTree javacTree, Node javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processArrayAccess(ArrayAccessTree javacTree, ArrayAccessExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processArrayType(ArrayTypeTree javacTree, ArrayType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAssert(AssertTree javacTree, AssertStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processAssignment(AssignmentTree javacTree, AssignExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBinary(BinaryTree javacTree, BinaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBindingPattern(Tree javacTree, PatternExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBlock(BlockTree javacTree, BlockStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processBreak(BreakTree javacTree, BreakStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCase(CaseTree javacTree, SwitchEntry javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCatch(CatchTree javacTree, CatchClause javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, AnnotationDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, ClassOrInterfaceDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, EnumDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processClass(ClassTree javacTree, RecordDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCompilationUnit( + CompilationUnitTree javacTree, CompilationUnit javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processConditionalExpression( + ConditionalExpressionTree javacTree, ConditionalExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processContinue(ContinueTree javacTree, ContinueStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processDoWhileLoop(DoWhileLoopTree javacTree, DoStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processEmptyStatement(EmptyStatementTree javacTree, EmptyStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processEnhancedForLoop(EnhancedForLoopTree javacTree, ForEachStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processExports(ExportsTree javacTree, ModuleExportsDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processExpressionStatemen( + ExpressionStatementTree javacTree, ExpressionStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processForLoop(ForLoopTree javacTree, ForStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, ClassOrInterfaceType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, Name javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, NameExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, SimpleName javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, SuperExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIdentifier(IdentifierTree javacTree, ThisExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIf(IfTree javacTree, IfStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processImport(ImportTree javacTree, ImportDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processInstanceOf(InstanceOfTree javacTree, InstanceOfExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processIntersectionType( + IntersectionTypeTree javacTree, IntersectionType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLabeledStatement( + LabeledStatementTree javacTree, LabeledStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLambdaExpression(LambdaExpressionTree javacTree, LambdaExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLiteral(LiteralTree javacTree, BinaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLiteral(LiteralTree javacTree, UnaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processLiteral(LiteralTree javacTree, LiteralExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberReference( + MemberReferenceTree javacTree, MethodReferenceExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, ClassExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect( + MemberSelectTree javacTree, ClassOrInterfaceType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, FieldAccessExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, Name javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, ThisExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMemberSelect(MemberSelectTree javacTree, SuperExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, MethodDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, ConstructorDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, CompactConstructorDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethod(MethodTree javacTree, AnnotationMemberDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, ExplicitConstructorInvocationStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processMethodInvocation( + MethodInvocationTree javacTree, MethodCallExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processModule(ModuleTree javacTree, ModuleDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processOpens(OpensTree javacTree, ModuleOpensDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processOther(Tree javacTree, Node javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processPackage(PackageTree javacTree, PackageDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processParameterizedType( + ParameterizedTypeTree javacTree, ClassOrInterfaceType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processParenthesized(ParenthesizedTree javacTree, EnclosedExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, PrimitiveType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processPrimitiveType(PrimitiveTypeTree javacTree, VoidType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processProvides(ProvidesTree javacTree, ModuleProvidesDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processRequires(RequiresTree javacTree, ModuleRequiresDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processReturn(ReturnTree javacTree, ReturnStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processSwitch(SwitchTree javacTree, SwitchStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processSwitchExpression(Tree javacTree, SwitchExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processSynchronized(SynchronizedTree javacTree, SynchronizedStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processThrow(ThrowTree javacTree, ThrowStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processTry(TryTree javacTree, TryStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processTypeCast(TypeCastTree javacTree, CastExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processTypeParameter(TypeParameterTree javacTree, TypeParameter javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processUnary(UnaryTree javacTree, UnaryExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processUnionType(UnionTypeTree javacTree, UnionType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processUses(UsesTree javacTree, ModuleUsesDirective javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, EnumConstantDeclaration javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, Parameter javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, ReceiverParameter javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processVariable(VariableTree javacTree, VariableDeclarator javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processWhileLoop(WhileLoopTree javacTree, WhileStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processWildcard(WildcardTree javacTree, WildcardType javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processYield(Tree javacTree, YieldStmt javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } + + @Override + public void processCompoundAssignment( + CompoundAssignmentTree javacTree, AssignExpr javaParserNode) { + defaultJointAction(javacTree, javaParserNode); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java b/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java index fec65b35635..82eece358cf 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/TreeScannerWithDefaults.java @@ -61,6 +61,7 @@ import com.sun.source.tree.WhileLoopTree; import com.sun.source.tree.WildcardTree; import com.sun.source.util.TreeScanner; + import org.checkerframework.javacutil.SystemUtil; /** @@ -69,425 +70,425 @@ */ public abstract class TreeScannerWithDefaults extends TreeScanner { - /** - * Action performed on each visited tree. - * - * @param tree tree to perform action on - */ - public abstract void defaultAction(Tree tree); - - // TODO: use JCP to add version-specific behavior - @Override - public Void scan(Tree tree, Void unused) { - if (tree != null && SystemUtil.jreVersion >= 14) { - switch (tree.getKind().name()) { - case "SWITCH_EXPRESSION": - visitSwitchExpression17(tree, unused); - return null; - case "YIELD": - visitYield17(tree, unused); - return null; - case "BINDING_PATTERN": - visitBindingPattern17(tree, unused); - return null; - } - } - return super.scan(tree, unused); - } - - @Override - public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { - defaultAction(tree); - return super.visitAnnotatedType(tree, p); - } - - @Override - public Void visitAnnotation(AnnotationTree tree, Void p) { - defaultAction(tree); - return super.visitAnnotation(tree, p); - } - - @Override - public Void visitArrayAccess(ArrayAccessTree tree, Void p) { - defaultAction(tree); - return super.visitArrayAccess(tree, p); - } - - @Override - public Void visitArrayType(ArrayTypeTree tree, Void p) { - defaultAction(tree); - return super.visitArrayType(tree, p); - } - - @Override - public Void visitAssert(AssertTree tree, Void p) { - defaultAction(tree); - return super.visitAssert(tree, p); - } - - @Override - public Void visitAssignment(AssignmentTree tree, Void p) { - defaultAction(tree); - return super.visitAssignment(tree, p); - } - - @Override - public Void visitBinary(BinaryTree tree, Void p) { - defaultAction(tree); - return super.visitBinary(tree, p); - } - - /** - * Visit a binding pattern tree. - * - * @param tree a binding pattern tree - * @param p null - * @return null - */ - public Void visitBindingPattern17(Tree tree, Void p) { - defaultAction(tree); - return super.scan(tree, p); - } - - @Override - public Void visitBlock(BlockTree tree, Void p) { - defaultAction(tree); - return super.visitBlock(tree, p); - } - - @Override - public Void visitBreak(BreakTree tree, Void p) { - defaultAction(tree); - return super.visitBreak(tree, p); - } - - @Override - public Void visitCase(CaseTree tree, Void p) { - defaultAction(tree); - return super.visitCase(tree, p); - } - - @Override - public Void visitCatch(CatchTree tree, Void p) { - defaultAction(tree); - return super.visitCatch(tree, p); - } - - @Override - public Void visitClass(ClassTree tree, Void p) { - defaultAction(tree); - return super.visitClass(tree, p); - } - - @Override - public Void visitCompilationUnit(CompilationUnitTree tree, Void p) { - defaultAction(tree); - return super.visitCompilationUnit(tree, p); - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { - defaultAction(tree); - return super.visitCompoundAssignment(tree, p); - } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { - defaultAction(tree); - return super.visitConditionalExpression(tree, p); - } - - @Override - public Void visitContinue(ContinueTree tree, Void p) { - defaultAction(tree); - return super.visitContinue(tree, p); - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { - defaultAction(tree); - return super.visitDoWhileLoop(tree, p); - } - - @Override - public Void visitEmptyStatement(EmptyStatementTree tree, Void p) { - defaultAction(tree); - return super.visitEmptyStatement(tree, p); - } - - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { - defaultAction(tree); - return super.visitEnhancedForLoop(tree, p); - } - - @Override - public Void visitErroneous(ErroneousTree tree, Void p) { - defaultAction(tree); - return super.visitErroneous(tree, p); - } - - @Override - public Void visitExports(ExportsTree tree, Void p) { - defaultAction(tree); - return super.visitExports(tree, p); - } - - @Override - public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { - defaultAction(tree); - return super.visitExpressionStatement(tree, p); - } - - @Override - public Void visitForLoop(ForLoopTree tree, Void p) { - defaultAction(tree); - return super.visitForLoop(tree, p); - } - - @Override - public Void visitIdentifier(IdentifierTree tree, Void p) { - defaultAction(tree); - return super.visitIdentifier(tree, p); - } - - @Override - public Void visitIf(IfTree tree, Void p) { - defaultAction(tree); - return super.visitIf(tree, p); - } - - @Override - public Void visitImport(ImportTree tree, Void p) { - defaultAction(tree); - return super.visitImport(tree, p); - } - - @Override - public Void visitInstanceOf(InstanceOfTree tree, Void p) { - defaultAction(tree); - return super.visitInstanceOf(tree, p); - } - - @Override - public Void visitIntersectionType(IntersectionTypeTree tree, Void p) { - defaultAction(tree); - return super.visitIntersectionType(tree, p); - } - - @Override - public Void visitLabeledStatement(LabeledStatementTree tree, Void p) { - defaultAction(tree); - return super.visitLabeledStatement(tree, p); - } - - @Override - public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { - defaultAction(tree); - return super.visitLambdaExpression(tree, p); - } - - @Override - public Void visitLiteral(LiteralTree tree, Void p) { - defaultAction(tree); - return super.visitLiteral(tree, p); - } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void p) { - defaultAction(tree); - return super.visitMemberReference(tree, p); - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void p) { - defaultAction(tree); - return super.visitMemberSelect(tree, p); - } - - @Override - public Void visitMethod(MethodTree tree, Void p) { - defaultAction(tree); - return super.visitMethod(tree, p); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { - defaultAction(tree); - return super.visitMethodInvocation(tree, p); - } - - @Override - public Void visitModifiers(ModifiersTree tree, Void p) { - defaultAction(tree); - return super.visitModifiers(tree, p); - } - - @Override - public Void visitModule(ModuleTree tree, Void p) { - defaultAction(tree); - return super.visitModule(tree, p); - } - - @Override - public Void visitNewArray(NewArrayTree tree, Void p) { - defaultAction(tree); - return super.visitNewArray(tree, p); - } - - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - defaultAction(tree); - return super.visitNewClass(tree, p); - } - - @Override - public Void visitOpens(OpensTree tree, Void p) { - defaultAction(tree); - return super.visitOpens(tree, p); - } - - @Override - public Void visitOther(Tree tree, Void p) { - defaultAction(tree); - return super.visitOther(tree, p); - } - - @Override - public Void visitPackage(PackageTree tree, Void p) { - defaultAction(tree); - return super.visitPackage(tree, p); - } - - @Override - public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { - defaultAction(tree); - return super.visitParameterizedType(tree, p); - } - - @Override - public Void visitParenthesized(ParenthesizedTree tree, Void p) { - defaultAction(tree); - return super.visitParenthesized(tree, p); - } - - @Override - public Void visitPrimitiveType(PrimitiveTypeTree tree, Void p) { - defaultAction(tree); - return super.visitPrimitiveType(tree, p); - } - - @Override - public Void visitProvides(ProvidesTree tree, Void p) { - defaultAction(tree); - return super.visitProvides(tree, p); - } - - @Override - public Void visitRequires(RequiresTree tree, Void p) { - defaultAction(tree); - return super.visitRequires(tree, p); - } - - @Override - public Void visitReturn(ReturnTree tree, Void p) { - defaultAction(tree); - return super.visitReturn(tree, p); - } - - @Override - public Void visitSwitch(SwitchTree tree, Void p) { - defaultAction(tree); - return super.visitSwitch(tree, p); - } - - /** - * Visit a switch expression tree. - * - * @param tree switch expression tree - * @param p null - * @return null - */ - public Void visitSwitchExpression17(Tree tree, Void p) { - defaultAction(tree); - return super.scan(tree, p); - } - - @Override - public Void visitSynchronized(SynchronizedTree tree, Void p) { - defaultAction(tree); - return super.visitSynchronized(tree, p); - } - - @Override - public Void visitThrow(ThrowTree tree, Void p) { - defaultAction(tree); - return super.visitThrow(tree, p); - } - - @Override - public Void visitTry(TryTree tree, Void p) { - defaultAction(tree); - return super.visitTry(tree, p); - } - - @Override - public Void visitTypeCast(TypeCastTree tree, Void p) { - defaultAction(tree); - return super.visitTypeCast(tree, p); - } - - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void p) { - defaultAction(tree); - return super.visitTypeParameter(tree, p); - } - - @Override - public Void visitUnary(UnaryTree tree, Void p) { - defaultAction(tree); - return super.visitUnary(tree, p); - } - - @Override - public Void visitUnionType(UnionTypeTree tree, Void p) { - defaultAction(tree); - return super.visitUnionType(tree, p); - } - - @Override - public Void visitUses(UsesTree tree, Void p) { - defaultAction(tree); - return super.visitUses(tree, p); - } - - @Override - public Void visitVariable(VariableTree tree, Void p) { - defaultAction(tree); - return super.visitVariable(tree, p); - } - - @Override - public Void visitWhileLoop(WhileLoopTree tree, Void p) { - defaultAction(tree); - return super.visitWhileLoop(tree, p); - } - - @Override - public Void visitWildcard(WildcardTree tree, Void p) { - defaultAction(tree); - return super.visitWildcard(tree, p); - } - - /** - * Visit a yield tree. - * - * @param tree a yield tree - * @param p null - * @return null - */ - public Void visitYield17(Tree tree, Void p) { - defaultAction(tree); - return super.scan(tree, p); - } + /** + * Action performed on each visited tree. + * + * @param tree tree to perform action on + */ + public abstract void defaultAction(Tree tree); + + // TODO: use JCP to add version-specific behavior + @Override + public Void scan(Tree tree, Void unused) { + if (tree != null && SystemUtil.jreVersion >= 14) { + switch (tree.getKind().name()) { + case "SWITCH_EXPRESSION": + visitSwitchExpression17(tree, unused); + return null; + case "YIELD": + visitYield17(tree, unused); + return null; + case "BINDING_PATTERN": + visitBindingPattern17(tree, unused); + return null; + } + } + return super.scan(tree, unused); + } + + @Override + public Void visitAnnotatedType(AnnotatedTypeTree tree, Void p) { + defaultAction(tree); + return super.visitAnnotatedType(tree, p); + } + + @Override + public Void visitAnnotation(AnnotationTree tree, Void p) { + defaultAction(tree); + return super.visitAnnotation(tree, p); + } + + @Override + public Void visitArrayAccess(ArrayAccessTree tree, Void p) { + defaultAction(tree); + return super.visitArrayAccess(tree, p); + } + + @Override + public Void visitArrayType(ArrayTypeTree tree, Void p) { + defaultAction(tree); + return super.visitArrayType(tree, p); + } + + @Override + public Void visitAssert(AssertTree tree, Void p) { + defaultAction(tree); + return super.visitAssert(tree, p); + } + + @Override + public Void visitAssignment(AssignmentTree tree, Void p) { + defaultAction(tree); + return super.visitAssignment(tree, p); + } + + @Override + public Void visitBinary(BinaryTree tree, Void p) { + defaultAction(tree); + return super.visitBinary(tree, p); + } + + /** + * Visit a binding pattern tree. + * + * @param tree a binding pattern tree + * @param p null + * @return null + */ + public Void visitBindingPattern17(Tree tree, Void p) { + defaultAction(tree); + return super.scan(tree, p); + } + + @Override + public Void visitBlock(BlockTree tree, Void p) { + defaultAction(tree); + return super.visitBlock(tree, p); + } + + @Override + public Void visitBreak(BreakTree tree, Void p) { + defaultAction(tree); + return super.visitBreak(tree, p); + } + + @Override + public Void visitCase(CaseTree tree, Void p) { + defaultAction(tree); + return super.visitCase(tree, p); + } + + @Override + public Void visitCatch(CatchTree tree, Void p) { + defaultAction(tree); + return super.visitCatch(tree, p); + } + + @Override + public Void visitClass(ClassTree tree, Void p) { + defaultAction(tree); + return super.visitClass(tree, p); + } + + @Override + public Void visitCompilationUnit(CompilationUnitTree tree, Void p) { + defaultAction(tree); + return super.visitCompilationUnit(tree, p); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + defaultAction(tree); + return super.visitCompoundAssignment(tree, p); + } + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { + defaultAction(tree); + return super.visitConditionalExpression(tree, p); + } + + @Override + public Void visitContinue(ContinueTree tree, Void p) { + defaultAction(tree); + return super.visitContinue(tree, p); + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + defaultAction(tree); + return super.visitDoWhileLoop(tree, p); + } + + @Override + public Void visitEmptyStatement(EmptyStatementTree tree, Void p) { + defaultAction(tree); + return super.visitEmptyStatement(tree, p); + } + + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + defaultAction(tree); + return super.visitEnhancedForLoop(tree, p); + } + + @Override + public Void visitErroneous(ErroneousTree tree, Void p) { + defaultAction(tree); + return super.visitErroneous(tree, p); + } + + @Override + public Void visitExports(ExportsTree tree, Void p) { + defaultAction(tree); + return super.visitExports(tree, p); + } + + @Override + public Void visitExpressionStatement(ExpressionStatementTree tree, Void p) { + defaultAction(tree); + return super.visitExpressionStatement(tree, p); + } + + @Override + public Void visitForLoop(ForLoopTree tree, Void p) { + defaultAction(tree); + return super.visitForLoop(tree, p); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, Void p) { + defaultAction(tree); + return super.visitIdentifier(tree, p); + } + + @Override + public Void visitIf(IfTree tree, Void p) { + defaultAction(tree); + return super.visitIf(tree, p); + } + + @Override + public Void visitImport(ImportTree tree, Void p) { + defaultAction(tree); + return super.visitImport(tree, p); + } + + @Override + public Void visitInstanceOf(InstanceOfTree tree, Void p) { + defaultAction(tree); + return super.visitInstanceOf(tree, p); + } + + @Override + public Void visitIntersectionType(IntersectionTypeTree tree, Void p) { + defaultAction(tree); + return super.visitIntersectionType(tree, p); + } + + @Override + public Void visitLabeledStatement(LabeledStatementTree tree, Void p) { + defaultAction(tree); + return super.visitLabeledStatement(tree, p); + } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree tree, Void p) { + defaultAction(tree); + return super.visitLambdaExpression(tree, p); + } + + @Override + public Void visitLiteral(LiteralTree tree, Void p) { + defaultAction(tree); + return super.visitLiteral(tree, p); + } + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void p) { + defaultAction(tree); + return super.visitMemberReference(tree, p); + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void p) { + defaultAction(tree); + return super.visitMemberSelect(tree, p); + } + + @Override + public Void visitMethod(MethodTree tree, Void p) { + defaultAction(tree); + return super.visitMethod(tree, p); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { + defaultAction(tree); + return super.visitMethodInvocation(tree, p); + } + + @Override + public Void visitModifiers(ModifiersTree tree, Void p) { + defaultAction(tree); + return super.visitModifiers(tree, p); + } + + @Override + public Void visitModule(ModuleTree tree, Void p) { + defaultAction(tree); + return super.visitModule(tree, p); + } + + @Override + public Void visitNewArray(NewArrayTree tree, Void p) { + defaultAction(tree); + return super.visitNewArray(tree, p); + } + + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + defaultAction(tree); + return super.visitNewClass(tree, p); + } + + @Override + public Void visitOpens(OpensTree tree, Void p) { + defaultAction(tree); + return super.visitOpens(tree, p); + } + + @Override + public Void visitOther(Tree tree, Void p) { + defaultAction(tree); + return super.visitOther(tree, p); + } + + @Override + public Void visitPackage(PackageTree tree, Void p) { + defaultAction(tree); + return super.visitPackage(tree, p); + } + + @Override + public Void visitParameterizedType(ParameterizedTypeTree tree, Void p) { + defaultAction(tree); + return super.visitParameterizedType(tree, p); + } + + @Override + public Void visitParenthesized(ParenthesizedTree tree, Void p) { + defaultAction(tree); + return super.visitParenthesized(tree, p); + } + + @Override + public Void visitPrimitiveType(PrimitiveTypeTree tree, Void p) { + defaultAction(tree); + return super.visitPrimitiveType(tree, p); + } + + @Override + public Void visitProvides(ProvidesTree tree, Void p) { + defaultAction(tree); + return super.visitProvides(tree, p); + } + + @Override + public Void visitRequires(RequiresTree tree, Void p) { + defaultAction(tree); + return super.visitRequires(tree, p); + } + + @Override + public Void visitReturn(ReturnTree tree, Void p) { + defaultAction(tree); + return super.visitReturn(tree, p); + } + + @Override + public Void visitSwitch(SwitchTree tree, Void p) { + defaultAction(tree); + return super.visitSwitch(tree, p); + } + + /** + * Visit a switch expression tree. + * + * @param tree switch expression tree + * @param p null + * @return null + */ + public Void visitSwitchExpression17(Tree tree, Void p) { + defaultAction(tree); + return super.scan(tree, p); + } + + @Override + public Void visitSynchronized(SynchronizedTree tree, Void p) { + defaultAction(tree); + return super.visitSynchronized(tree, p); + } + + @Override + public Void visitThrow(ThrowTree tree, Void p) { + defaultAction(tree); + return super.visitThrow(tree, p); + } + + @Override + public Void visitTry(TryTree tree, Void p) { + defaultAction(tree); + return super.visitTry(tree, p); + } + + @Override + public Void visitTypeCast(TypeCastTree tree, Void p) { + defaultAction(tree); + return super.visitTypeCast(tree, p); + } + + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void p) { + defaultAction(tree); + return super.visitTypeParameter(tree, p); + } + + @Override + public Void visitUnary(UnaryTree tree, Void p) { + defaultAction(tree); + return super.visitUnary(tree, p); + } + + @Override + public Void visitUnionType(UnionTypeTree tree, Void p) { + defaultAction(tree); + return super.visitUnionType(tree, p); + } + + @Override + public Void visitUses(UsesTree tree, Void p) { + defaultAction(tree); + return super.visitUses(tree, p); + } + + @Override + public Void visitVariable(VariableTree tree, Void p) { + defaultAction(tree); + return super.visitVariable(tree, p); + } + + @Override + public Void visitWhileLoop(WhileLoopTree tree, Void p) { + defaultAction(tree); + return super.visitWhileLoop(tree, p); + } + + @Override + public Void visitWildcard(WildcardTree tree, Void p) { + defaultAction(tree); + return super.visitWildcard(tree, p); + } + + /** + * Visit a yield tree. + * + * @param tree a yield tree + * @param p null + * @return null + */ + public Void visitYield17(Tree tree, Void p) { + defaultAction(tree); + return super.scan(tree, p); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java b/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java index 9e5dd19da01..a3a9c82fdb9 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/TypeAnnotationMover.java @@ -6,6 +6,11 @@ import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.framework.stub.AnnotationFileParser; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.util.ArrayList; @@ -13,11 +18,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.framework.stub.AnnotationFileParser; /** * Moves annotations in a JavaParser AST from declaration position onto the types they correspond @@ -31,193 +34,197 @@ * declaration target. */ public class TypeAnnotationMover extends VoidVisitorAdapter { - /** - * Annotations imported by the file, stored as a mapping from names to the TypeElements for the - * annotations. Contains entries for the simple and fully qualified names of each annotation. - */ - private final Map allAnnotations; - - /** Element utilities. */ - private final Elements elements; - - /** - * Constructs a {@code TypeAnnotationMover}. - * - * @param allAnnotations the annotations imported by the file, as a mapping from annotation name - * to TypeElement. There should be two entries for each annotation: the annotation's simple - * name and its fully-qualified name both mapped to its TypeElement. - * @param elements the Element utilities - */ - public TypeAnnotationMover(Map allAnnotations, Elements elements) { - this.allAnnotations = new HashMap<>(allAnnotations); - this.elements = elements; - } - - @Override - public void visit(FieldDeclaration node, Void p) { - // Use the type of the first declared variable in the field declaration. - Type type = node.getVariable(0).getType(); - if (!type.isClassOrInterfaceType()) { - return; - } - - if (isMultiPartName(type)) { - return; + /** + * Annotations imported by the file, stored as a mapping from names to the TypeElements for the + * annotations. Contains entries for the simple and fully qualified names of each annotation. + */ + private final Map allAnnotations; + + /** Element utilities. */ + private final Elements elements; + + /** + * Constructs a {@code TypeAnnotationMover}. + * + * @param allAnnotations the annotations imported by the file, as a mapping from annotation name + * to TypeElement. There should be two entries for each annotation: the annotation's simple + * name and its fully-qualified name both mapped to its TypeElement. + * @param elements the Element utilities + */ + public TypeAnnotationMover(Map allAnnotations, Elements elements) { + this.allAnnotations = new HashMap<>(allAnnotations); + this.elements = elements; } - List annosToMove = getAnnotationsToMove(node, ElementType.FIELD); - if (annosToMove.isEmpty()) { - return; + @Override + public void visit(FieldDeclaration node, Void p) { + // Use the type of the first declared variable in the field declaration. + Type type = node.getVariable(0).getType(); + if (!type.isClassOrInterfaceType()) { + return; + } + + if (isMultiPartName(type)) { + return; + } + + List annosToMove = getAnnotationsToMove(node, ElementType.FIELD); + if (annosToMove.isEmpty()) { + return; + } + + node.getAnnotations().removeAll(annosToMove); + annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); } - node.getAnnotations().removeAll(annosToMove); - annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); - } - - @Override - public void visit(MethodDeclaration node, Void p) { - Type type = node.getType(); - if (!type.isClassOrInterfaceType()) { - return; - } + @Override + public void visit(MethodDeclaration node, Void p) { + Type type = node.getType(); + if (!type.isClassOrInterfaceType()) { + return; + } - if (isMultiPartName(type)) { - return; - } + if (isMultiPartName(type)) { + return; + } - List annosToMove = getAnnotationsToMove(node, ElementType.METHOD); - if (annosToMove.isEmpty()) { - return; - } + List annosToMove = getAnnotationsToMove(node, ElementType.METHOD); + if (annosToMove.isEmpty()) { + return; + } - node.getAnnotations().removeAll(annosToMove); - annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); - } - - /** - * Given a declaration, returns a List of annotations currently in declaration position that can't - * possibly be declaration annotations for that type of declaration. - * - * @param node a JavaParser node for declaration - * @param declarationType the type of declaration {@code node} represents; always FIELD or METHOD - * @return a list of annotations in declaration position that should be on the declaration's type - */ - private List getAnnotationsToMove( - NodeWithAnnotations node, ElementType declarationType) { - // There are usually no annotations that need to be moved. - List annosToMove = new ArrayList<>(0); - for (AnnotationExpr annotation : node.getAnnotations()) { - if (!isPossiblyDeclarationAnnotation(annotation, declarationType)) { - annosToMove.add(annotation); - } + node.getAnnotations().removeAll(annosToMove); + annosToMove.forEach(anno -> type.asClassOrInterfaceType().addAnnotation(anno)); } - return annosToMove; - } - - /** - * Returns the TypeElement for an annotation, or null if it cannot be found. - * - * @param annotation a JavaParser annotation - * @return the TypeElement for {@code annotation}, or null if it cannot be found - */ - private @Nullable TypeElement getAnnotationDeclaration(AnnotationExpr annotation) { - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); - TypeElement annoTypeElt = allAnnotations.get(annoNameFq); - if (annoTypeElt == null) { - annoTypeElt = elements.getTypeElement(annoNameFq); - if (annoTypeElt == null) { - // Not a supported annotation. - return null; - } - AnnotationFileParser.putAllNew( - allAnnotations, - AnnotationFileParser.createNameToAnnotationMap(Collections.singletonList(annoTypeElt))); + /** + * Given a declaration, returns a List of annotations currently in declaration position that + * can't possibly be declaration annotations for that type of declaration. + * + * @param node a JavaParser node for declaration + * @param declarationType the type of declaration {@code node} represents; always FIELD or + * METHOD + * @return a list of annotations in declaration position that should be on the declaration's + * type + */ + private List getAnnotationsToMove( + NodeWithAnnotations node, ElementType declarationType) { + // There are usually no annotations that need to be moved. + List annosToMove = new ArrayList<>(0); + for (AnnotationExpr annotation : node.getAnnotations()) { + if (!isPossiblyDeclarationAnnotation(annotation, declarationType)) { + annosToMove.add(annotation); + } + } + + return annosToMove; } - return annoTypeElt; - } - - /** - * Returns if {@code annotation} could be a declaration annotation for {@code declarationType}. - * This would be the case if the annotation isn't recognized at all, or if it has no - * {@code @Target} meta-annotation, or if it has {@code declarationType} as one of its targets. - * - * @param annotation a JavaParser annotation expression - * @param declarationType the declaration type to check if {@code annotation} might be a - * declaration annotation for - * @return true unless {@code annotation} definitely cannot be a declaration annotation for {@code - * declarationType} - */ - private boolean isPossiblyDeclarationAnnotation( - AnnotationExpr annotation, ElementType declarationType) { - TypeElement annotationType = getAnnotationDeclaration(annotation); - if (annotationType == null) { - return true; + /** + * Returns the TypeElement for an annotation, or null if it cannot be found. + * + * @param annotation a JavaParser annotation + * @return the TypeElement for {@code annotation}, or null if it cannot be found + */ + private @Nullable TypeElement getAnnotationDeclaration(AnnotationExpr annotation) { + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); + TypeElement annoTypeElt = allAnnotations.get(annoNameFq); + if (annoTypeElt == null) { + annoTypeElt = elements.getTypeElement(annoNameFq); + if (annoTypeElt == null) { + // Not a supported annotation. + return null; + } + AnnotationFileParser.putAllNew( + allAnnotations, + AnnotationFileParser.createNameToAnnotationMap( + Collections.singletonList(annoTypeElt))); + } + + return annoTypeElt; } - return isDeclarationAnnotation(annotationType, declarationType); - } - - /** - * Returns whether the annotation represented by {@code annotationDeclaration} might be a - * declaration annotation for {@code declarationType}. This holds if the TypeElement has no - * {@code @Target} meta-annotation, or if {@code declarationType} is a target of the annotation. - * - * @param annotationDeclaration declaration for an annotation - * @param declarationType the declaration type to check if the annotation might be a declaration - * annotation for - * @return true if {@code annotationDeclaration} contains {@code declarationType} as a target or - * doesn't contain {@code ElementType.TYPE_USE} as a target - */ - private boolean isDeclarationAnnotation( - TypeElement annotationDeclaration, ElementType declarationType) { - Target target = annotationDeclaration.getAnnotation(Target.class); - if (target == null) { - return true; + /** + * Returns if {@code annotation} could be a declaration annotation for {@code declarationType}. + * This would be the case if the annotation isn't recognized at all, or if it has no + * {@code @Target} meta-annotation, or if it has {@code declarationType} as one of its targets. + * + * @param annotation a JavaParser annotation expression + * @param declarationType the declaration type to check if {@code annotation} might be a + * declaration annotation for + * @return true unless {@code annotation} definitely cannot be a declaration annotation for + * {@code declarationType} + */ + private boolean isPossiblyDeclarationAnnotation( + AnnotationExpr annotation, ElementType declarationType) { + TypeElement annotationType = getAnnotationDeclaration(annotation); + if (annotationType == null) { + return true; + } + + return isDeclarationAnnotation(annotationType, declarationType); } - boolean hasTypeUse = false; - for (ElementType elementType : target.value()) { - if (elementType == declarationType) { - return true; - } - - if (elementType == ElementType.TYPE_USE) { - hasTypeUse = true; - } + /** + * Returns whether the annotation represented by {@code annotationDeclaration} might be a + * declaration annotation for {@code declarationType}. This holds if the TypeElement has no + * {@code @Target} meta-annotation, or if {@code declarationType} is a target of the annotation. + * + * @param annotationDeclaration declaration for an annotation + * @param declarationType the declaration type to check if the annotation might be a declaration + * annotation for + * @return true if {@code annotationDeclaration} contains {@code declarationType} as a target or + * doesn't contain {@code ElementType.TYPE_USE} as a target + */ + private boolean isDeclarationAnnotation( + TypeElement annotationDeclaration, ElementType declarationType) { + Target target = annotationDeclaration.getAnnotation(Target.class); + if (target == null) { + return true; + } + + boolean hasTypeUse = false; + for (ElementType elementType : target.value()) { + if (elementType == declarationType) { + return true; + } + + if (elementType == ElementType.TYPE_USE) { + hasTypeUse = true; + } + } + + if (!hasTypeUse) { + throw new Error( + String.format( + "Annotation %s cannot be used on declaration with type %s", + annotationDeclaration.getQualifiedName(), declarationType)); + } + return false; } - if (!hasTypeUse) { - throw new Error( - String.format( - "Annotation %s cannot be used on declaration with type %s", - annotationDeclaration.getQualifiedName(), declarationType)); + /** + * Returns whether {@code type} has a name containing multiple parts separated by dots, e.g. + * "java.lang.String" or "Outer.Inner". + * + *

          Annotations should not be moved onto a Type for which this method returns true. A type + * like {@code @Anno java.lang.String} is illegal since the annotation should go directly next + * to the rightmost part of the fully qualified name, like {@code java.lang. @Anno String}. So + * if a file contains a declaration like {@code @Anno java.lang.String myField}, the annotation + * must belong to the declaration and not the type. + * + *

          If a declaration contains an inner class type like {@code @Anno Outer.Inner myField}, it + * may be the case that {@code @Anno} belongs to the type {@code Outer}, not the declaration, + * and should be moved, but it's impossible to distinguish this from the above case using only + * the JavaParser AST for a file. To be safe, the annotation still shouldn't be moved, but this + * may lead to suboptimal formatting placing {@code @Anno} on its own line. + * + * @param type a JavaParser type node + * @return true if {@code type} has a multi-part name + */ + private boolean isMultiPartName(Type type) { + return type.isClassOrInterfaceType() + && type.asClassOrInterfaceType().getScope().isPresent(); } - return false; - } - - /** - * Returns whether {@code type} has a name containing multiple parts separated by dots, e.g. - * "java.lang.String" or "Outer.Inner". - * - *

          Annotations should not be moved onto a Type for which this method returns true. A type like - * {@code @Anno java.lang.String} is illegal since the annotation should go directly next to the - * rightmost part of the fully qualified name, like {@code java.lang. @Anno String}. So if a file - * contains a declaration like {@code @Anno java.lang.String myField}, the annotation must belong - * to the declaration and not the type. - * - *

          If a declaration contains an inner class type like {@code @Anno Outer.Inner myField}, it may - * be the case that {@code @Anno} belongs to the type {@code Outer}, not the declaration, and - * should be moved, but it's impossible to distinguish this from the above case using only the - * JavaParser AST for a file. To be safe, the annotation still shouldn't be moved, but this may - * lead to suboptimal formatting placing {@code @Anno} on its own line. - * - * @param type a JavaParser type node - * @return true if {@code type} has a multi-part name - */ - private boolean isMultiPartName(Type type) { - return type.isClassOrInterfaceType() && type.asClassOrInterfaceType().getScope().isPresent(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java index aabc1d8f89e..f98260e8876 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractAnalysis.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.flow; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.analysis.ForwardAnalysisImpl; @@ -24,6 +17,15 @@ import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + /** * {@link CFAbstractAnalysis} is an extensible org.checkerframework.dataflow analysis for the * Checker Framework that tracks the annotations using a flow-sensitive analysis. It uses an {@link @@ -36,234 +38,235 @@ * stores to access the {@link AnnotatedTypeFactory}, the qualifier hierarchy, etc. */ public abstract class CFAbstractAnalysis< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> - extends ForwardAnalysisImpl { - /** The qualifier hierarchy for which to track annotations. */ - protected final QualifierHierarchy qualHierarchy; + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> + extends ForwardAnalysisImpl { + /** The qualifier hierarchy for which to track annotations. */ + protected final QualifierHierarchy qualHierarchy; - /** The type hierarchy. */ - protected final TypeHierarchy typeHierarchy; + /** The type hierarchy. */ + protected final TypeHierarchy typeHierarchy; - /** - * The dependent type helper used to standardize both annotations belonging to the type hierarchy, - * and contract expressions. - */ - protected final DependentTypesHelper dependentTypesHelper; + /** + * The dependent type helper used to standardize both annotations belonging to the type + * hierarchy, and contract expressions. + */ + protected final DependentTypesHelper dependentTypesHelper; - /** A type factory that can provide static type annotations for AST Trees. */ - protected final GenericAnnotatedTypeFactory> - atypeFactory; + /** A type factory that can provide static type annotations for AST Trees. */ + protected final GenericAnnotatedTypeFactory> + atypeFactory; - /** A checker that contains command-line arguments and other information. */ - protected final SourceChecker checker; + /** A checker that contains command-line arguments and other information. */ + protected final SourceChecker checker; - /** - * A triple of field, value corresponding to the annotations on its declared type, value of its - * initializer. The value of the initializer is {@code null} if the field does not have one. - * - * @param type of value - */ - public static class FieldInitialValue> { + /** + * A triple of field, value corresponding to the annotations on its declared type, value of its + * initializer. The value of the initializer is {@code null} if the field does not have one. + * + * @param type of value + */ + public static class FieldInitialValue> { - /** A field access that corresponds to the declaration of a field. */ - public final FieldAccess fieldDecl; + /** A field access that corresponds to the declaration of a field. */ + public final FieldAccess fieldDecl; - /** The value corresponding to the annotations on the declared type of the field. */ - public final V declared; + /** The value corresponding to the annotations on the declared type of the field. */ + public final V declared; - /** The value of the initializer of the field, or null if no initializer exists. */ - public final @Nullable V initializer; + /** The value of the initializer of the field, or null if no initializer exists. */ + public final @Nullable V initializer; - /** - * Creates a new FieldInitialValue. - * - * @param fieldDecl a field access that corresponds to the declaration of a field - * @param declared value corresponding to the annotations on the declared type of {@code field} - * @param initializer value of the initializer of {@code field}, or null if no initializer - * exists - */ - public FieldInitialValue(FieldAccess fieldDecl, V declared, @Nullable V initializer) { - this.fieldDecl = fieldDecl; - this.declared = declared; - this.initializer = initializer; + /** + * Creates a new FieldInitialValue. + * + * @param fieldDecl a field access that corresponds to the declaration of a field + * @param declared value corresponding to the annotations on the declared type of {@code + * field} + * @param initializer value of the initializer of {@code field}, or null if no initializer + * exists + */ + public FieldInitialValue(FieldAccess fieldDecl, V declared, @Nullable V initializer) { + this.fieldDecl = fieldDecl; + this.declared = declared; + this.initializer = initializer; + } } - } - /** Initial abstract types for fields. */ - protected final List> fieldValues; + /** Initial abstract types for fields. */ + protected final List> fieldValues; - /** The associated processing environment. */ - protected final ProcessingEnvironment env; + /** The associated processing environment. */ + protected final ProcessingEnvironment env; - /** Instance of the types utility. */ - protected final Types types; + /** Instance of the types utility. */ + protected final Types types; - /** - * Create a CFAbstractAnalysis. - * - * @param checker a checker that contains command-line arguments and other information - * @param factory an annotated type factory to introduce type and dataflow rules - * @param maxCountBeforeWidening number of times a block can be analyzed before widening - */ - protected CFAbstractAnalysis( - BaseTypeChecker checker, - GenericAnnotatedTypeFactory> factory, - int maxCountBeforeWidening) { - super(maxCountBeforeWidening); - env = checker.getProcessingEnvironment(); - types = env.getTypeUtils(); - qualHierarchy = factory.getQualifierHierarchy(); - typeHierarchy = factory.getTypeHierarchy(); - dependentTypesHelper = factory.getDependentTypesHelper(); - this.atypeFactory = factory; - this.checker = checker; - this.transferFunction = createTransferFunction(); - this.fieldValues = new ArrayList<>(); - } + /** + * Create a CFAbstractAnalysis. + * + * @param checker a checker that contains command-line arguments and other information + * @param factory an annotated type factory to introduce type and dataflow rules + * @param maxCountBeforeWidening number of times a block can be analyzed before widening + */ + protected CFAbstractAnalysis( + BaseTypeChecker checker, + GenericAnnotatedTypeFactory> factory, + int maxCountBeforeWidening) { + super(maxCountBeforeWidening); + env = checker.getProcessingEnvironment(); + types = env.getTypeUtils(); + qualHierarchy = factory.getQualifierHierarchy(); + typeHierarchy = factory.getTypeHierarchy(); + dependentTypesHelper = factory.getDependentTypesHelper(); + this.atypeFactory = factory; + this.checker = checker; + this.transferFunction = createTransferFunction(); + this.fieldValues = new ArrayList<>(); + } - /** - * Create a CFAbstractAnalysis. - * - * @param checker a checker that contains command-line arguments and other information - * @param factory an annotated type factory to introduce type and dataflow rules - */ - protected CFAbstractAnalysis( - BaseTypeChecker checker, - GenericAnnotatedTypeFactory> factory) { - this(checker, factory, factory.getQualifierHierarchy().numberOfIterationsBeforeWidening()); - } + /** + * Create a CFAbstractAnalysis. + * + * @param checker a checker that contains command-line arguments and other information + * @param factory an annotated type factory to introduce type and dataflow rules + */ + protected CFAbstractAnalysis( + BaseTypeChecker checker, + GenericAnnotatedTypeFactory> factory) { + this(checker, factory, factory.getQualifierHierarchy().numberOfIterationsBeforeWidening()); + } - /** - * Analyze the given control flow graph. - * - * @param cfg control flow graph to analyze - * @param fieldValues initial values of the fields - */ - public void performAnalysis(ControlFlowGraph cfg, List> fieldValues) { - this.fieldValues.clear(); - this.fieldValues.addAll(fieldValues); - super.performAnalysis(cfg); - } + /** + * Analyze the given control flow graph. + * + * @param cfg control flow graph to analyze + * @param fieldValues initial values of the fields + */ + public void performAnalysis(ControlFlowGraph cfg, List> fieldValues) { + this.fieldValues.clear(); + this.fieldValues.addAll(fieldValues); + super.performAnalysis(cfg); + } - /** - * A list of initial abstract values for the fields. - * - * @return a list of initial abstract values for the fields - */ - public List> getFieldInitialValues() { - return fieldValues; - } + /** + * A list of initial abstract values for the fields. + * + * @return a list of initial abstract values for the fields + */ + public List> getFieldInitialValues() { + return fieldValues; + } - /** - * Returns the transfer function to be used by the analysis. - * - * @return the transfer function to be used by the analysis - */ - public T createTransferFunction() { - return atypeFactory.createFlowTransferFunction(this); - } + /** + * Returns the transfer function to be used by the analysis. + * + * @return the transfer function to be used by the analysis + */ + public T createTransferFunction() { + return atypeFactory.createFlowTransferFunction(this); + } - /** - * Returns an empty store of the appropriate type. - * - * @return an empty store of the appropriate type - */ - public abstract S createEmptyStore(boolean sequentialSemantics); + /** + * Returns an empty store of the appropriate type. + * + * @return an empty store of the appropriate type + */ + public abstract S createEmptyStore(boolean sequentialSemantics); - /** - * Returns an identical copy of the store {@code s}. - * - * @return an identical copy of the store {@code s} - */ - public abstract S createCopiedStore(S s); + /** + * Returns an identical copy of the store {@code s}. + * + * @return an identical copy of the store {@code s} + */ + public abstract S createCopiedStore(S s); - /** - * Creates an abstract value from the annotated type mirror. The value contains the set of primary - * annotations on the type, unless the type is an AnnotatedWildcardType. For an - * AnnotatedWildcardType, the annotations in the created value are the primary annotations on the - * extends bound. See {@link CFAbstractValue} for an explanation. - * - * @param type the type to convert into an abstract value - * @return an abstract value containing the given annotated {@code type} - */ - public @Nullable V createAbstractValue(AnnotatedTypeMirror type) { - AnnotationMirrorSet annos; - if (type.getKind() == TypeKind.WILDCARD) { - annos = ((AnnotatedWildcardType) type).getExtendsBound().getAnnotations(); - } else if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { - annos = ((AnnotatedTypeVariable) type).getUpperBound().getAnnotations(); - } else { - annos = type.getAnnotations(); + /** + * Creates an abstract value from the annotated type mirror. The value contains the set of + * primary annotations on the type, unless the type is an AnnotatedWildcardType. For an + * AnnotatedWildcardType, the annotations in the created value are the primary annotations on + * the extends bound. See {@link CFAbstractValue} for an explanation. + * + * @param type the type to convert into an abstract value + * @return an abstract value containing the given annotated {@code type} + */ + public @Nullable V createAbstractValue(AnnotatedTypeMirror type) { + AnnotationMirrorSet annos; + if (type.getKind() == TypeKind.WILDCARD) { + annos = ((AnnotatedWildcardType) type).getExtendsBound().getAnnotations(); + } else if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { + annos = ((AnnotatedTypeVariable) type).getUpperBound().getAnnotations(); + } else { + annos = type.getAnnotations(); + } + return createAbstractValue(annos, type.getUnderlyingType()); } - return createAbstractValue(annos, type.getUnderlyingType()); - } - /** - * Returns an abstract value containing the given {@code annotations} and {@code underlyingType}. - * Returns null if the annotation set has missing annotations. - * - * @param annotations the annotations for the result annotated type - * @param underlyingType the unannotated type for the result annotated type - * @return an abstract value containing the given {@code annotations} and {@code underlyingType} - */ - public abstract @Nullable V createAbstractValue( - AnnotationMirrorSet annotations, TypeMirror underlyingType); + /** + * Returns an abstract value containing the given {@code annotations} and {@code + * underlyingType}. Returns null if the annotation set has missing annotations. + * + * @param annotations the annotations for the result annotated type + * @param underlyingType the unannotated type for the result annotated type + * @return an abstract value containing the given {@code annotations} and {@code underlyingType} + */ + public abstract @Nullable V createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType); - /** Default implementation for {@link #createAbstractValue(AnnotationMirrorSet, TypeMirror)}. */ - public @Nullable CFValue defaultCreateAbstractValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { - return null; + /** Default implementation for {@link #createAbstractValue(AnnotationMirrorSet, TypeMirror)}. */ + public @Nullable CFValue defaultCreateAbstractValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; + } + return new CFValue(analysis, annotations, underlyingType); } - return new CFValue(analysis, annotations, underlyingType); - } - public TypeHierarchy getTypeHierarchy() { - return typeHierarchy; - } + public TypeHierarchy getTypeHierarchy() { + return typeHierarchy; + } - public GenericAnnotatedTypeFactory> - getTypeFactory() { - return atypeFactory; - } + public GenericAnnotatedTypeFactory> + getTypeFactory() { + return atypeFactory; + } - /** - * Returns an abstract value containing an annotated type with the annotation {@code anno}, and - * 'top' for all other hierarchies. The underlying type is {@code underlyingType}. - * - * @param anno the annotation for the result annotated type - * @param underlyingType the unannotated type for the result annotated type - * @return an abstract value with {@code anno} and {@code underlyingType} - */ - public V createSingleAnnotationValue(AnnotationMirror anno, TypeMirror underlyingType) { - QualifierHierarchy hierarchy = getTypeFactory().getQualifierHierarchy(); - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - annos.addAll(hierarchy.getTopAnnotations()); - AnnotationMirror f = hierarchy.findAnnotationInSameHierarchy(annos, anno); - annos.remove(f); - annos.add(anno); - return createAbstractValue(annos, underlyingType); - } + /** + * Returns an abstract value containing an annotated type with the annotation {@code anno}, and + * 'top' for all other hierarchies. The underlying type is {@code underlyingType}. + * + * @param anno the annotation for the result annotated type + * @param underlyingType the unannotated type for the result annotated type + * @return an abstract value with {@code anno} and {@code underlyingType} + */ + public V createSingleAnnotationValue(AnnotationMirror anno, TypeMirror underlyingType) { + QualifierHierarchy hierarchy = getTypeFactory().getQualifierHierarchy(); + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + annos.addAll(hierarchy.getTopAnnotations()); + AnnotationMirror f = hierarchy.findAnnotationInSameHierarchy(annos, anno); + annos.remove(f); + annos.add(anno); + return createAbstractValue(annos, underlyingType); + } - /** - * Get the types utility. - * - * @return {@link #types} - */ - public Types getTypes() { - return types; - } + /** + * Get the types utility. + * + * @return {@link #types} + */ + public Types getTypes() { + return types; + } - /** - * Get the processing environment. - * - * @return {@link #env} - */ - public ProcessingEnvironment getEnv() { - return env; - } + /** + * Get the processing environment. + * + * @return {@link #env} + */ + public ProcessingEnvironment getEnv() { + return env; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index b06413652d3..09020998f27 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,21 +1,5 @@ package org.checkerframework.framework.flow; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.BinaryOperator; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; @@ -47,6 +31,24 @@ import org.plumelib.util.ToStringComparator; import org.plumelib.util.UniqueId; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BinaryOperator; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + /** * A store for the Checker Framework analysis. It tracks the annotations of memory locations such as * local variables and fields. @@ -63,1287 +65,1304 @@ // TODO: Split this class into two parts: one that is reusable generally and // one that is specific to the Checker Framework. public abstract class CFAbstractStore, S extends CFAbstractStore> - implements Store, UniqueId { - - /** The analysis class this store belongs to. */ - protected final CFAbstractAnalysis analysis; - - /** Information collected about local variables (including method parameters). */ - protected final Map localVariableValues; - - /** Information collected about the current object. */ - protected V thisValue; - - /** Information collected about fields, using the internal representation {@link FieldAccess}. */ - protected Map fieldValues; - - /** - * Returns information about fields. Clients should not side-effect the returned value, which is - * aliased to internal state. - * - * @return information about fields - */ - public Map getFieldValues() { - return fieldValues; - } - - /** - * Information collected about array elements, using the internal representation {@link - * ArrayAccess}. - */ - protected final Map arrayValues; - - /** - * Information collected about method calls, using the internal representation {@link MethodCall}. - */ - protected final Map methodValues; - - /** - * Information collected about classname.class values, using the internal representation - * {@link ClassName}. - */ - protected final Map classValues; - - /** - * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running - * at all times)? - */ - protected final boolean sequentialSemantics; - - /** True if -AassumeSideEffectFree or -AassumePure was passed on the command line. */ - private final boolean assumeSideEffectFree; - - /** True if -AassumePureGetters was passed on the command line. */ - private final boolean assumePureGetters; - - /** The unique ID for the next-created object. */ - private static final AtomicLong nextUid = new AtomicLong(0); - - /** The unique ID of this object. */ - private final transient long uid = nextUid.getAndIncrement(); - - @Override - public long getUid() { - return uid; - } - - /* --------------------------------------------------------- */ - /* Initialization */ - /* --------------------------------------------------------- */ - - /** - * Creates a new CFAbstractStore. - * - * @param analysis the analysis class this store belongs to - * @param sequentialSemantics should the analysis use sequential Java semantics? - */ - protected CFAbstractStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { - this.analysis = analysis; - localVariableValues = new HashMap<>(); - thisValue = null; - fieldValues = new HashMap<>(); - methodValues = new HashMap<>(); - arrayValues = new HashMap<>(); - classValues = new HashMap<>(); - this.sequentialSemantics = sequentialSemantics; - assumeSideEffectFree = - analysis.checker.hasOption("assumeSideEffectFree") - || analysis.checker.hasOption("assumePure"); - assumePureGetters = analysis.checker.hasOption("assumePureGetters"); - } - - /** - * Copy constructor. - * - * @param other a CFAbstractStore to copy into this - */ - protected CFAbstractStore(CFAbstractStore other) { - this.analysis = other.analysis; - localVariableValues = new HashMap<>(other.localVariableValues); - thisValue = other.thisValue; - fieldValues = new HashMap<>(other.fieldValues); - methodValues = new HashMap<>(other.methodValues); - arrayValues = new HashMap<>(other.arrayValues); - classValues = new HashMap<>(other.classValues); - sequentialSemantics = other.sequentialSemantics; - assumeSideEffectFree = other.assumeSideEffectFree; - assumePureGetters = other.assumePureGetters; - } - - /** - * Set the abstract value of a method parameter (only adds the information to the store, does not - * remove any other knowledge). Any previous information is erased; this method should only be - * used to initialize the abstract value. - */ - public void initializeMethodParameter(LocalVariableNode p, @Nullable V value) { - if (value != null) { - localVariableValues.put(new LocalVariable(p.getElement()), value); + implements Store, UniqueId { + + /** The analysis class this store belongs to. */ + protected final CFAbstractAnalysis analysis; + + /** Information collected about local variables (including method parameters). */ + protected final Map localVariableValues; + + /** Information collected about the current object. */ + protected V thisValue; + + /** + * Information collected about fields, using the internal representation {@link FieldAccess}. + */ + protected Map fieldValues; + + /** + * Returns information about fields. Clients should not side-effect the returned value, which is + * aliased to internal state. + * + * @return information about fields + */ + public Map getFieldValues() { + return fieldValues; } - } - - /** - * Set the value of the current object. Any previous information is erased; this method should - * only be used to initialize the value. - */ - public void initializeThisValue(AnnotationMirror a, TypeMirror underlyingType) { - if (a != null) { - thisValue = analysis.createSingleAnnotationValue(a, underlyingType); + + /** + * Information collected about array elements, using the internal representation {@link + * ArrayAccess}. + */ + protected final Map arrayValues; + + /** + * Information collected about method calls, using the internal representation {@link + * MethodCall}. + */ + protected final Map methodValues; + + /** + * Information collected about classname.class values, using the internal representation + * {@link ClassName}. + */ + protected final Map classValues; + + /** + * Should the analysis use sequential Java semantics (i.e., assume that only one thread is + * running at all times)? + */ + protected final boolean sequentialSemantics; + + /** True if -AassumeSideEffectFree or -AassumePure was passed on the command line. */ + private final boolean assumeSideEffectFree; + + /** True if -AassumePureGetters was passed on the command line. */ + private final boolean assumePureGetters; + + /** The unique ID for the next-created object. */ + private static final AtomicLong nextUid = new AtomicLong(0); + + /** The unique ID of this object. */ + private final transient long uid = nextUid.getAndIncrement(); + + @Override + public long getUid() { + return uid; } - } - - /* --------------------------------------------------------- */ - /* Handling of fields */ - /* --------------------------------------------------------- */ - - /** - * Remove any information that might not be valid any more after a method call, and add - * information guaranteed by the method. - * - *

            - *
          1. If the method is side-effect-free (as indicated by {@link - * org.checkerframework.dataflow.qual.SideEffectFree} or {@link - * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. - *
          2. Otherwise, all information about field accesses {@code a.f} needs to be removed, except - * if the method {@code n} cannot modify {@code a.f}. This unmodifiability property holds if - * {@code a} is a local variable or {@code this}, and {@code f} is final, or if {@code a.f} - * has a {@link MonotonicQualifier} in the current store. Subclasses can change this - * behavior by overriding {@link #newFieldValueAfterMethodCall(FieldAccess, - * GenericAnnotatedTypeFactory, CFAbstractValue)}. - *
          3. Furthermore, if the field has a monotonic annotation, then its information can also be - * kept. - *
          - * - * Furthermore, if the method is deterministic, we store its result {@code val} in the store. - * - * @param methodInvocationNode method whose information is being updated - * @param atypeFactory the type factory of the associated checker - * @param val abstract value of the method call - */ - public void updateForMethodCall( - MethodInvocationNode methodInvocationNode, - GenericAnnotatedTypeFactory atypeFactory, - V val) { - ExecutableElement method = methodInvocationNode.getTarget().getMethod(); - - // Case 1: The method is side-effect-free. - boolean hasSideEffect = - !(assumeSideEffectFree - || (assumePureGetters && ElementUtils.isGetter(method)) - || atypeFactory.isSideEffectFree(method)); - if (hasSideEffect) { - boolean sideEffectsUnrefineAliases = atypeFactory.sideEffectsUnrefineAliases; - - // update local variables - // TODO: Also remove if any element/argument to the annotation is not - // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). - if (sideEffectsUnrefineAliases) { - localVariableValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); - } - - // update this value - if (sideEffectsUnrefineAliases) { - thisValue = null; - } - // update field values - if (sideEffectsUnrefineAliases) { - fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); - } else { - // Case 2 (unassignable fields) and case 3 (monotonic fields) - updateFieldValuesForMethodCall(atypeFactory); - } + /* --------------------------------------------------------- */ + /* Initialization */ + /* --------------------------------------------------------- */ + + /** + * Creates a new CFAbstractStore. + * + * @param analysis the analysis class this store belongs to + * @param sequentialSemantics should the analysis use sequential Java semantics? + */ + protected CFAbstractStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { + this.analysis = analysis; + localVariableValues = new HashMap<>(); + thisValue = null; + fieldValues = new HashMap<>(); + methodValues = new HashMap<>(); + arrayValues = new HashMap<>(); + classValues = new HashMap<>(); + this.sequentialSemantics = sequentialSemantics; + assumeSideEffectFree = + analysis.checker.hasOption("assumeSideEffectFree") + || analysis.checker.hasOption("assumePure"); + assumePureGetters = analysis.checker.hasOption("assumePureGetters"); + } - // update array values - arrayValues.clear(); + /** + * Copy constructor. + * + * @param other a CFAbstractStore to copy into this + */ + protected CFAbstractStore(CFAbstractStore other) { + this.analysis = other.analysis; + localVariableValues = new HashMap<>(other.localVariableValues); + thisValue = other.thisValue; + fieldValues = new HashMap<>(other.fieldValues); + methodValues = new HashMap<>(other.methodValues); + arrayValues = new HashMap<>(other.arrayValues); + classValues = new HashMap<>(other.classValues); + sequentialSemantics = other.sequentialSemantics; + assumeSideEffectFree = other.assumeSideEffectFree; + assumePureGetters = other.assumePureGetters; + } - // update method values - methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + /** + * Set the abstract value of a method parameter (only adds the information to the store, does + * not remove any other knowledge). Any previous information is erased; this method should only + * be used to initialize the abstract value. + */ + public void initializeMethodParameter(LocalVariableNode p, @Nullable V value) { + if (value != null) { + localVariableValues.put(new LocalVariable(p.getElement()), value); + } } - // store information about method call if possible - JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); - replaceValue(methodCall, val); - } - - /** - * Returns the new value of a field after a method call, or {@code null} if the field should be - * removed from the store. - * - *

          In this default implementation, the field's value is preserved if it is either unassignable - * (see {@link FieldAccess#isUnassignableByOtherCode()}) or has a monotonic qualifier (see {@link - * #newMonotonicFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory, - * CFAbstractValue)}). Otherwise, it is removed from the store. - * - * @param fieldAccess the field whose value to update - * @param atypeFactory AnnotatedTypeFactory of the associated checker - * @param value the field's value before the method call - * @return the field's value after the method call, or {@code null} if the field should be removed - * from the store - */ - protected V newFieldValueAfterMethodCall( - FieldAccess fieldAccess, GenericAnnotatedTypeFactory atypeFactory, V value) { - // Handle unassignable fields. - if (fieldAccess.isUnassignableByOtherCode()) { - return value; + /** + * Set the value of the current object. Any previous information is erased; this method should + * only be used to initialize the value. + */ + public void initializeThisValue(AnnotationMirror a, TypeMirror underlyingType) { + if (a != null) { + thisValue = analysis.createSingleAnnotationValue(a, underlyingType); + } } - // Handle fields with monotonic annotations. - return newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); - } - - /** - * Computes the value of a field whose declaration has a monotonic annotation, or returns {@code - * null} if the field has no monotonic annotation. - * - *

          Used by {@link #newFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory, - * CFAbstractValue)} to handle fields with monotonic annotations. - * - * @param fieldAccess the field whose value to compute - * @param atypeFactory AnnotatedTypeFactory of the associated checker - * @param value the field's value before the method call - * @return the field's value after the method call, or {@code null} if the field has no monotonic - * annotation - */ - protected V newMonotonicFieldValueAfterMethodCall( - FieldAccess fieldAccess, GenericAnnotatedTypeFactory atypeFactory, V value) { - // case 3: the field has a monotonic annotation - if (atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { - return null; + /* --------------------------------------------------------- */ + /* Handling of fields */ + /* --------------------------------------------------------- */ + + /** + * Remove any information that might not be valid any more after a method call, and add + * information guaranteed by the method. + * + *

            + *
          1. If the method is side-effect-free (as indicated by {@link + * org.checkerframework.dataflow.qual.SideEffectFree} or {@link + * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. + *
          2. Otherwise, all information about field accesses {@code a.f} needs to be removed, except + * if the method {@code n} cannot modify {@code a.f}. This unmodifiability property holds + * if {@code a} is a local variable or {@code this}, and {@code f} is final, or if {@code + * a.f} has a {@link MonotonicQualifier} in the current store. Subclasses can change this + * behavior by overriding {@link #newFieldValueAfterMethodCall(FieldAccess, + * GenericAnnotatedTypeFactory, CFAbstractValue)}. + *
          3. Furthermore, if the field has a monotonic annotation, then its information can also be + * kept. + *
          + * + * Furthermore, if the method is deterministic, we store its result {@code val} in the store. + * + * @param methodInvocationNode method whose information is being updated + * @param atypeFactory the type factory of the associated checker + * @param val abstract value of the method call + */ + public void updateForMethodCall( + MethodInvocationNode methodInvocationNode, + GenericAnnotatedTypeFactory atypeFactory, + V val) { + ExecutableElement method = methodInvocationNode.getTarget().getMethod(); + + // Case 1: The method is side-effect-free. + boolean hasSideEffect = + !(assumeSideEffectFree + || (assumePureGetters && ElementUtils.isGetter(method)) + || atypeFactory.isSideEffectFree(method)); + if (hasSideEffect) { + boolean sideEffectsUnrefineAliases = atypeFactory.sideEffectsUnrefineAliases; + + // update local variables + // TODO: Also remove if any element/argument to the annotation is not + // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). + if (sideEffectsUnrefineAliases) { + localVariableValues + .entrySet() + .removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + } + + // update this value + if (sideEffectsUnrefineAliases) { + thisValue = null; + } + + // update field values + if (sideEffectsUnrefineAliases) { + fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + } else { + // Case 2 (unassignable fields) and case 3 (monotonic fields) + updateFieldValuesForMethodCall(atypeFactory); + } + + // update array values + arrayValues.clear(); + + // update method values + methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + } + + // store information about method call if possible + JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); + replaceValue(methodCall, val); } - List> fieldAnnotationPairs = - atypeFactory.getAnnotationWithMetaAnnotation( - fieldAccess.getField(), MonotonicQualifier.class); - List metaAnnotations = - CollectionsPlume.withoutDuplicates( - CollectionsPlume.mapList(pair -> pair.second, fieldAnnotationPairs)); - List monotonicAnnotations = new ArrayList<>(metaAnnotations.size()); - for (AnnotationMirror metaAnnotation : metaAnnotations) { - @SuppressWarnings("deprecation") // permitted for use in the framework - Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); - monotonicAnnotations.add( - AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName)); + /** + * Returns the new value of a field after a method call, or {@code null} if the field should be + * removed from the store. + * + *

          In this default implementation, the field's value is preserved if it is either + * unassignable (see {@link FieldAccess#isUnassignableByOtherCode()}) or has a monotonic + * qualifier (see {@link #newMonotonicFieldValueAfterMethodCall(FieldAccess, + * GenericAnnotatedTypeFactory, CFAbstractValue)}). Otherwise, it is removed from the store. + * + * @param fieldAccess the field whose value to update + * @param atypeFactory AnnotatedTypeFactory of the associated checker + * @param value the field's value before the method call + * @return the field's value after the method call, or {@code null} if the field should be + * removed from the store + */ + protected V newFieldValueAfterMethodCall( + FieldAccess fieldAccess, + GenericAnnotatedTypeFactory atypeFactory, + V value) { + // Handle unassignable fields. + if (fieldAccess.isUnassignableByOtherCode()) { + return value; + } + + // Handle fields with monotonic annotations. + return newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); } - Collection valueAnnos = value.getAnnotations(); - V newValue = null; - for (AnnotationMirror monotonicAnnotation : monotonicAnnotations) { - // Make sure the target annotation is present. - if (AnnotationUtils.containsSame(valueAnnos, monotonicAnnotation)) { - newValue = - analysis - .createSingleAnnotationValue(monotonicAnnotation, value.getUnderlyingType()) - .mostSpecific(newValue, null); - } + + /** + * Computes the value of a field whose declaration has a monotonic annotation, or returns {@code + * null} if the field has no monotonic annotation. + * + *

          Used by {@link #newFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory, + * CFAbstractValue)} to handle fields with monotonic annotations. + * + * @param fieldAccess the field whose value to compute + * @param atypeFactory AnnotatedTypeFactory of the associated checker + * @param value the field's value before the method call + * @return the field's value after the method call, or {@code null} if the field has no + * monotonic annotation + */ + protected V newMonotonicFieldValueAfterMethodCall( + FieldAccess fieldAccess, + GenericAnnotatedTypeFactory atypeFactory, + V value) { + // case 3: the field has a monotonic annotation + if (atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { + return null; + } + + List> fieldAnnotationPairs = + atypeFactory.getAnnotationWithMetaAnnotation( + fieldAccess.getField(), MonotonicQualifier.class); + List metaAnnotations = + CollectionsPlume.withoutDuplicates( + CollectionsPlume.mapList(pair -> pair.second, fieldAnnotationPairs)); + List monotonicAnnotations = new ArrayList<>(metaAnnotations.size()); + for (AnnotationMirror metaAnnotation : metaAnnotations) { + @SuppressWarnings("deprecation") // permitted for use in the framework + Name annoName = + AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); + monotonicAnnotations.add( + AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName)); + } + Collection valueAnnos = value.getAnnotations(); + V newValue = null; + for (AnnotationMirror monotonicAnnotation : monotonicAnnotations) { + // Make sure the target annotation is present. + if (AnnotationUtils.containsSame(valueAnnos, monotonicAnnotation)) { + newValue = + analysis.createSingleAnnotationValue( + monotonicAnnotation, value.getUnderlyingType()) + .mostSpecific(newValue, null); + } + } + return newValue; } - return newValue; - } - - /** - * Helper for {@link #updateForMethodCall(MethodInvocationNode, GenericAnnotatedTypeFactory, - * CFAbstractValue)}. Remove any information about field values that might not be valid any more - * after a method call, and add information guaranteed by the method. - * - *

          More specifically, remove all information about fields except for unassignable fields and - * fields that have a monotonic annotation. - * - * @param atypeFactory AnnotatedTypeFactory of the associated checker - */ - private void updateFieldValuesForMethodCall( - GenericAnnotatedTypeFactory atypeFactory) { - Map newFieldValues = new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); - for (Map.Entry e : fieldValues.entrySet()) { - FieldAccess fieldAccess = e.getKey(); - V value = e.getValue(); - - V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); - if (newValue != null) { - // Keep information for all hierarchies where we had a monotonic annotation. - newFieldValues.put(fieldAccess, newValue); - } + + /** + * Helper for {@link #updateForMethodCall(MethodInvocationNode, GenericAnnotatedTypeFactory, + * CFAbstractValue)}. Remove any information about field values that might not be valid any more + * after a method call, and add information guaranteed by the method. + * + *

          More specifically, remove all information about fields except for unassignable fields and + * fields that have a monotonic annotation. + * + * @param atypeFactory AnnotatedTypeFactory of the associated checker + */ + private void updateFieldValuesForMethodCall( + GenericAnnotatedTypeFactory atypeFactory) { + Map newFieldValues = + new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); + for (Map.Entry e : fieldValues.entrySet()) { + FieldAccess fieldAccess = e.getKey(); + V value = e.getValue(); + + V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + if (newValue != null) { + // Keep information for all hierarchies where we had a monotonic annotation. + newFieldValues.put(fieldAccess, newValue); + } + } + fieldValues = newFieldValues; } - fieldValues = newFieldValues; - } - - /** - * Add the annotation {@code a} for the expression {@code expr} (correctly deciding where to store - * the information depending on the type of the expression {@code expr}). - * - *

          This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - * - *

          If there is already a value {@code v} present for {@code expr}, then the stronger of the new - * and old value are taken (according to the lattice). Note that this happens per hierarchy, and - * if the store already contains information about a hierarchy other than {@code a}s hierarchy, - * that information is preserved. - * - *

          If {@code expr} is nondeterministic, this method does not insert {@code value} into the - * store. - * - * @param expr an expression - * @param a an annotation for the expression - */ - public void insertValue(JavaExpression expr, AnnotationMirror a) { - insertValue(expr, analysis.createSingleAnnotationValue(a, expr.getType())); - } - - /** - * Like {@link #insertValue(JavaExpression, AnnotationMirror)}, but permits nondeterministic - * expressions to be stored. - * - *

          For an explanation of when to permit nondeterministic expressions, see {@link - * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. - * - * @param expr an expression - * @param a an annotation for the expression - */ - public void insertValuePermitNondeterministic(JavaExpression expr, AnnotationMirror a) { - insertValuePermitNondeterministic( - expr, analysis.createSingleAnnotationValue(a, expr.getType())); - } - - /** - * Add the annotation {@code newAnno} for the expression {@code expr} (correctly deciding where to - * store the information depending on the type of the expression {@code expr}). - * - *

          This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - * - *

          If there is already a value {@code v} present for {@code expr}, then the greatest lower - * bound of the new and old value is inserted into the store. - * - *

          Note that this happens per hierarchy, and if the store already contains information about a - * hierarchy other than {@code newAnno}'s hierarchy, that information is preserved. - * - *

          If {@code expr} is nondeterministic, this method does not insert {@code value} into the - * store. - * - * @param expr an expression - * @param newAnno the expression's annotation - */ - public final void insertOrRefine(JavaExpression expr, AnnotationMirror newAnno) { - insertOrRefine(expr, newAnno, false); - } - - /** - * Like {@link #insertOrRefine(JavaExpression, AnnotationMirror)}, but permits nondeterministic - * expressions to be inserted. - * - *

          For an explanation of when to permit nondeterministic expressions, see {@link - * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. - * - * @param expr an expression - * @param newAnno the expression's annotation - */ - public final void insertOrRefinePermitNondeterministic( - JavaExpression expr, AnnotationMirror newAnno) { - insertOrRefine(expr, newAnno, true); - } - - /** - * Helper function for {@link #insertOrRefine(JavaExpression, AnnotationMirror)} and {@link - * #insertOrRefinePermitNondeterministic}. - * - * @param expr an expression - * @param newAnno the expression's annotation - * @param permitNondeterministic whether nondeterministic expressions may be inserted into the - * store - */ - protected void insertOrRefine( - JavaExpression expr, AnnotationMirror newAnno, boolean permitNondeterministic) { - if (!canInsertJavaExpression(expr)) { - return; + + /** + * Add the annotation {@code a} for the expression {@code expr} (correctly deciding where to + * store the information depending on the type of the expression {@code expr}). + * + *

          This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + * + *

          If there is already a value {@code v} present for {@code expr}, then the stronger of the + * new and old value are taken (according to the lattice). Note that this happens per hierarchy, + * and if the store already contains information about a hierarchy other than {@code a}s + * hierarchy, that information is preserved. + * + *

          If {@code expr} is nondeterministic, this method does not insert {@code value} into the + * store. + * + * @param expr an expression + * @param a an annotation for the expression + */ + public void insertValue(JavaExpression expr, AnnotationMirror a) { + insertValue(expr, analysis.createSingleAnnotationValue(a, expr.getType())); } - if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { - return; + + /** + * Like {@link #insertValue(JavaExpression, AnnotationMirror)}, but permits nondeterministic + * expressions to be stored. + * + *

          For an explanation of when to permit nondeterministic expressions, see {@link + * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. + * + * @param expr an expression + * @param a an annotation for the expression + */ + public void insertValuePermitNondeterministic(JavaExpression expr, AnnotationMirror a) { + insertValuePermitNondeterministic( + expr, analysis.createSingleAnnotationValue(a, expr.getType())); } - V newValue = analysis.createSingleAnnotationValue(newAnno, expr.getType()); - V oldValue = getValue(expr); - if (oldValue == null) { - insertValue( - expr, - analysis.createSingleAnnotationValue(newAnno, expr.getType()), - permitNondeterministic); - return; + /** + * Add the annotation {@code newAnno} for the expression {@code expr} (correctly deciding where + * to store the information depending on the type of the expression {@code expr}). + * + *

          This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + * + *

          If there is already a value {@code v} present for {@code expr}, then the greatest lower + * bound of the new and old value is inserted into the store. + * + *

          Note that this happens per hierarchy, and if the store already contains information about + * a hierarchy other than {@code newAnno}'s hierarchy, that information is preserved. + * + *

          If {@code expr} is nondeterministic, this method does not insert {@code value} into the + * store. + * + * @param expr an expression + * @param newAnno the expression's annotation + */ + public final void insertOrRefine(JavaExpression expr, AnnotationMirror newAnno) { + insertOrRefine(expr, newAnno, false); } - computeNewValueAndInsert( - expr, newValue, CFAbstractValue::greatestLowerBound, permitNondeterministic); - } - - /** Returns true if {@code expr} can be stored in this store. */ - public static boolean canInsertJavaExpression(JavaExpression expr) { - if (expr instanceof FieldAccess - || expr instanceof ThisReference - || expr instanceof LocalVariable - || expr instanceof MethodCall - || expr instanceof ArrayAccess - || expr instanceof ClassName) { - return !expr.containsUnknown(); + + /** + * Like {@link #insertOrRefine(JavaExpression, AnnotationMirror)}, but permits nondeterministic + * expressions to be inserted. + * + *

          For an explanation of when to permit nondeterministic expressions, see {@link + * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}. + * + * @param expr an expression + * @param newAnno the expression's annotation + */ + public final void insertOrRefinePermitNondeterministic( + JavaExpression expr, AnnotationMirror newAnno) { + insertOrRefine(expr, newAnno, true); } - return false; - } - - /** - * Add the abstract value {@code value} for the expression {@code expr} (correctly deciding where - * to store the information depending on the type of the expression {@code expr}). - * - *

          This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - * - *

          If there is already a value {@code v} present for {@code expr}, then the stronger of the new - * and old value are taken (according to the lattice). Note that this happens per hierarchy, and - * if the store already contains information about a hierarchy for which {@code value} does not - * contain information, then that information is preserved. - * - *

          If {@code expr} is nondeterministic, this method does not insert {@code value} into the - * store. - * - * @param expr the expression to insert in the store - * @param value the value of the expression - */ - public final void insertValue(JavaExpression expr, @Nullable V value) { - insertValue(expr, value, false); - } - - /** - * Like {@link #insertValue(JavaExpression, CFAbstractValue)}, but updates the store even if - * {@code expr} is nondeterministic. - * - *

          Usually, nondeterministic JavaExpressions should not be stored in a Store. For example, in - * the body of {@code if (nondet() == 3) {...}}, the store should not record that the value of - * {@code nondet()} is 3, because it might not be 3 the next time {@code nondet()} is executed. - * - *

          However, contracts can mention a nondeterministic JavaExpression. For example, a contract - * might have a postcondition that {@code nondet()} is odd. This means that the next call to - * {@code nondet()} will return odd. Such a postcondition may be evicted from the store by calling - * a side-effecting method. - * - * @param expr the expression to insert in the store - * @param value the value of the expression - */ - public final void insertValuePermitNondeterministic(JavaExpression expr, @Nullable V value) { - insertValue(expr, value, true); - } - - /** - * Returns true if the given (expression, value) pair can be inserted in the store, namely if the - * value is non-null and the expression does not contain unknown or a nondeterministic expression. - * - *

          This method returning true does not guarantee that the value will be inserted; the - * implementation of {@link #insertValue( JavaExpression, CFAbstractValue, boolean)} might still - * not insert it. - * - * @param expr the expression to insert in the store - * @param value the value of the expression - * @param permitNondeterministic if false, returns false if {@code expr} is nondeterministic; if - * true, permits nondeterministic expressions to be placed in the store - * @return true if the given (expression, value) pair can be inserted in the store - */ - @EnsuresNonNullIf(expression = "#2", result = true) - protected boolean shouldInsert( - JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { - if (value == null) { - // No need to insert a null abstract value because it represents - // top and top is also the default value. - return false; + + /** + * Helper function for {@link #insertOrRefine(JavaExpression, AnnotationMirror)} and {@link + * #insertOrRefinePermitNondeterministic}. + * + * @param expr an expression + * @param newAnno the expression's annotation + * @param permitNondeterministic whether nondeterministic expressions may be inserted into the + * store + */ + protected void insertOrRefine( + JavaExpression expr, AnnotationMirror newAnno, boolean permitNondeterministic) { + if (!canInsertJavaExpression(expr)) { + return; + } + if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { + return; + } + + V newValue = analysis.createSingleAnnotationValue(newAnno, expr.getType()); + V oldValue = getValue(expr); + if (oldValue == null) { + insertValue( + expr, + analysis.createSingleAnnotationValue(newAnno, expr.getType()), + permitNondeterministic); + return; + } + computeNewValueAndInsert( + expr, newValue, CFAbstractValue::greatestLowerBound, permitNondeterministic); } - if (expr.containsUnknown()) { - // Expressions containing unknown expressions are not stored. - return false; + + /** Returns true if {@code expr} can be stored in this store. */ + public static boolean canInsertJavaExpression(JavaExpression expr) { + if (expr instanceof FieldAccess + || expr instanceof ThisReference + || expr instanceof LocalVariable + || expr instanceof MethodCall + || expr instanceof ArrayAccess + || expr instanceof ClassName) { + return !expr.containsUnknown(); + } + return false; } - if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { - // Nondeterministic expressions may not be stored. - // (They are likely to be quickly evicted, as soon as a side-effecting method is - // called.) - return false; + + /** + * Add the abstract value {@code value} for the expression {@code expr} (correctly deciding + * where to store the information depending on the type of the expression {@code expr}). + * + *

          This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + * + *

          If there is already a value {@code v} present for {@code expr}, then the stronger of the + * new and old value are taken (according to the lattice). Note that this happens per hierarchy, + * and if the store already contains information about a hierarchy for which {@code value} does + * not contain information, then that information is preserved. + * + *

          If {@code expr} is nondeterministic, this method does not insert {@code value} into the + * store. + * + * @param expr the expression to insert in the store + * @param value the value of the expression + */ + public final void insertValue(JavaExpression expr, @Nullable V value) { + insertValue(expr, value, false); } - return true; - } - - /** - * Helper method for {@link #insertValue(JavaExpression, CFAbstractValue)} and {@link - * #insertValuePermitNondeterministic}. - * - *

          Every overriding implementation should start with - * - *

          {@code
          -   * if (!shouldInsert) {
          -   *   return;
          -   * }
          -   * }
          - * - * @param expr the expression to insert in the store - * @param value the value of the expression - * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if - * true, permits nondeterministic expressions to be placed in the store - */ - protected void insertValue( - JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { - computeNewValueAndInsert( - expr, value, (old, newValue) -> newValue.mostSpecific(old, null), permitNondeterministic); - } - - /** - * Inserts the result of applying {@code merger} to {@code value} and the previous value for - * {@code expr}. - * - * @param expr the JavaExpression - * @param value the value of the JavaExpression - * @param merger the function used to merge {@code value} and the previous value of {@code expr} - * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if - * true, permits nondeterministic expressions to be placed in the store - */ - protected void computeNewValueAndInsert( - JavaExpression expr, - @Nullable V value, - BinaryOperator merger, - boolean permitNondeterministic) { - if (!shouldInsert(expr, value, permitNondeterministic)) { - return; + + /** + * Like {@link #insertValue(JavaExpression, CFAbstractValue)}, but updates the store even if + * {@code expr} is nondeterministic. + * + *

          Usually, nondeterministic JavaExpressions should not be stored in a Store. For example, in + * the body of {@code if (nondet() == 3) {...}}, the store should not record that the value of + * {@code nondet()} is 3, because it might not be 3 the next time {@code nondet()} is executed. + * + *

          However, contracts can mention a nondeterministic JavaExpression. For example, a contract + * might have a postcondition that {@code nondet()} is odd. This means that the next call to + * {@code nondet()} will return odd. Such a postcondition may be evicted from the store by + * calling a side-effecting method. + * + * @param expr the expression to insert in the store + * @param value the value of the expression + */ + public final void insertValuePermitNondeterministic(JavaExpression expr, @Nullable V value) { + insertValue(expr, value, true); } - if (expr instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) expr; - V oldValue = localVariableValues.get(localVar); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - localVariableValues.put(localVar, newValue); - } - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - // Only store information about final fields (where the receiver is - // also fixed) if concurrent semantics are enabled. - boolean isMonotonic = isMonotonicUpdate(fieldAcc, value); - if (sequentialSemantics || isMonotonic || fieldAcc.isUnassignableByOtherCode()) { - V oldValue = fieldValues.get(fieldAcc); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - fieldValues.put(fieldAcc, newValue); - } - } - } else if (expr instanceof MethodCall) { - MethodCall method = (MethodCall) expr; - // Don't store any information if concurrent semantics are enabled. - if (sequentialSemantics) { - V oldValue = methodValues.get(method); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - methodValues.put(method, newValue); + /** + * Returns true if the given (expression, value) pair can be inserted in the store, namely if + * the value is non-null and the expression does not contain unknown or a nondeterministic + * expression. + * + *

          This method returning true does not guarantee that the value will be inserted; the + * implementation of {@link #insertValue( JavaExpression, CFAbstractValue, boolean)} might still + * not insert it. + * + * @param expr the expression to insert in the store + * @param value the value of the expression + * @param permitNondeterministic if false, returns false if {@code expr} is nondeterministic; if + * true, permits nondeterministic expressions to be placed in the store + * @return true if the given (expression, value) pair can be inserted in the store + */ + @EnsuresNonNullIf(expression = "#2", result = true) + protected boolean shouldInsert( + JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { + if (value == null) { + // No need to insert a null abstract value because it represents + // top and top is also the default value. + return false; } - } - } else if (expr instanceof ArrayAccess) { - ArrayAccess arrayAccess = (ArrayAccess) expr; - if (sequentialSemantics) { - V oldValue = arrayValues.get(arrayAccess); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - arrayValues.put(arrayAccess, newValue); - } - } - } else if (expr instanceof ThisReference) { - ThisReference thisRef = (ThisReference) expr; - if (sequentialSemantics || thisRef.isUnassignableByOtherCode()) { - V oldValue = thisValue; - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - thisValue = newValue; + if (expr.containsUnknown()) { + // Expressions containing unknown expressions are not stored. + return false; } - } - } else if (expr instanceof ClassName) { - ClassName className = (ClassName) expr; - if (sequentialSemantics || className.isUnassignableByOtherCode()) { - V oldValue = classValues.get(className); - V newValue = merger.apply(oldValue, value); - if (newValue != null) { - classValues.put(className, newValue); + if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) { + // Nondeterministic expressions may not be stored. + // (They are likely to be quickly evicted, as soon as a side-effecting method is + // called.) + return false; } - } - } else { - // No other types of expressions need to be stored. + return true; } - } - - /** - * Return true if fieldAcc is an update of a monotonic qualifier to its target qualifier. - * (e.g. @MonotonicNonNull to @NonNull). Always returns false if {@code sequentialSemantics} is - * true. - * - * @return true if fieldAcc is an update of a monotonic qualifier to its target qualifier - * (e.g. @MonotonicNonNull to @NonNull) - */ - protected boolean isMonotonicUpdate(FieldAccess fieldAcc, V value) { - if (analysis.atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { - return false; + + /** + * Helper method for {@link #insertValue(JavaExpression, CFAbstractValue)} and {@link + * #insertValuePermitNondeterministic}. + * + *

          Every overriding implementation should start with + * + *

          {@code
          +     * if (!shouldInsert) {
          +     *   return;
          +     * }
          +     * }
          + * + * @param expr the expression to insert in the store + * @param value the value of the expression + * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if + * true, permits nondeterministic expressions to be placed in the store + */ + protected void insertValue( + JavaExpression expr, @Nullable V value, boolean permitNondeterministic) { + computeNewValueAndInsert( + expr, + value, + (old, newValue) -> newValue.mostSpecific(old, null), + permitNondeterministic); } - boolean isMonotonic = false; - // TODO: This check for !sequentialSemantics is an optimization that breaks the contract of - // the method, since the method name and documentation say nothing about sequential - // semantics. This check should be performed by callers of this method when needed. - // TODO: Update the javadoc of this method when the above to-do item is addressed. - if (!sequentialSemantics) { // only compute if necessary - AnnotatedTypeFactory atypeFactory = this.analysis.atypeFactory; - List> fieldAnnotations = - atypeFactory.getAnnotationWithMetaAnnotation( - fieldAcc.getField(), MonotonicQualifier.class); - for (IPair fieldAnnotation : fieldAnnotations) { - AnnotationMirror metaAnnotation = fieldAnnotation.second; - @SuppressWarnings("deprecation") // permitted for use in the framework - Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); - AnnotationMirror monotonicAnnotation = - AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); - // Make sure the 'target' annotation is present. - if (AnnotationUtils.containsSame(value.getAnnotations(), monotonicAnnotation)) { - isMonotonic = true; - break; + + /** + * Inserts the result of applying {@code merger} to {@code value} and the previous value for + * {@code expr}. + * + * @param expr the JavaExpression + * @param value the value of the JavaExpression + * @param merger the function used to merge {@code value} and the previous value of {@code expr} + * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if + * true, permits nondeterministic expressions to be placed in the store + */ + protected void computeNewValueAndInsert( + JavaExpression expr, + @Nullable V value, + BinaryOperator merger, + boolean permitNondeterministic) { + if (!shouldInsert(expr, value, permitNondeterministic)) { + return; + } + + if (expr instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) expr; + V oldValue = localVariableValues.get(localVar); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + localVariableValues.put(localVar, newValue); + } + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + // Only store information about final fields (where the receiver is + // also fixed) if concurrent semantics are enabled. + boolean isMonotonic = isMonotonicUpdate(fieldAcc, value); + if (sequentialSemantics || isMonotonic || fieldAcc.isUnassignableByOtherCode()) { + V oldValue = fieldValues.get(fieldAcc); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + fieldValues.put(fieldAcc, newValue); + } + } + } else if (expr instanceof MethodCall) { + MethodCall method = (MethodCall) expr; + // Don't store any information if concurrent semantics are enabled. + if (sequentialSemantics) { + V oldValue = methodValues.get(method); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + methodValues.put(method, newValue); + } + } + } else if (expr instanceof ArrayAccess) { + ArrayAccess arrayAccess = (ArrayAccess) expr; + if (sequentialSemantics) { + V oldValue = arrayValues.get(arrayAccess); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + arrayValues.put(arrayAccess, newValue); + } + } + } else if (expr instanceof ThisReference) { + ThisReference thisRef = (ThisReference) expr; + if (sequentialSemantics || thisRef.isUnassignableByOtherCode()) { + V oldValue = thisValue; + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + thisValue = newValue; + } + } + } else if (expr instanceof ClassName) { + ClassName className = (ClassName) expr; + if (sequentialSemantics || className.isUnassignableByOtherCode()) { + V oldValue = classValues.get(className); + V newValue = merger.apply(oldValue, value); + if (newValue != null) { + classValues.put(className, newValue); + } + } + } else { + // No other types of expressions need to be stored. } - } } - return isMonotonic; - } - public void insertThisValue(AnnotationMirror a, TypeMirror underlyingType) { - if (a == null) { - return; + /** + * Return true if fieldAcc is an update of a monotonic qualifier to its target qualifier. + * (e.g. @MonotonicNonNull to @NonNull). Always returns false if {@code sequentialSemantics} is + * true. + * + * @return true if fieldAcc is an update of a monotonic qualifier to its target qualifier + * (e.g. @MonotonicNonNull to @NonNull) + */ + protected boolean isMonotonicUpdate(FieldAccess fieldAcc, V value) { + if (analysis.atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { + return false; + } + boolean isMonotonic = false; + // TODO: This check for !sequentialSemantics is an optimization that breaks the contract of + // the method, since the method name and documentation say nothing about sequential + // semantics. This check should be performed by callers of this method when needed. + // TODO: Update the javadoc of this method when the above to-do item is addressed. + if (!sequentialSemantics) { // only compute if necessary + AnnotatedTypeFactory atypeFactory = this.analysis.atypeFactory; + List> fieldAnnotations = + atypeFactory.getAnnotationWithMetaAnnotation( + fieldAcc.getField(), MonotonicQualifier.class); + for (IPair fieldAnnotation : fieldAnnotations) { + AnnotationMirror metaAnnotation = fieldAnnotation.second; + @SuppressWarnings("deprecation") // permitted for use in the framework + Name annoName = + AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); + AnnotationMirror monotonicAnnotation = + AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); + // Make sure the 'target' annotation is present. + if (AnnotationUtils.containsSame(value.getAnnotations(), monotonicAnnotation)) { + isMonotonic = true; + break; + } + } + } + return isMonotonic; } - V value = analysis.createSingleAnnotationValue(a, underlyingType); + public void insertThisValue(AnnotationMirror a, TypeMirror underlyingType) { + if (a == null) { + return; + } - V oldValue = thisValue; - V newValue = value.mostSpecific(oldValue, null); - if (newValue != null) { - thisValue = newValue; - } - } - - /** - * Completely replaces the abstract value {@code value} for the expression {@code expr} (correctly - * deciding where to store the information depending on the type of the expression {@code expr}). - * Any previous information is discarded. - * - *

          This method does not take care of removing other information that might be influenced by - * changes to certain parts of the state. - */ - public void replaceValue(JavaExpression expr, @Nullable V value) { - clearValue(expr); - insertValue(expr, value); - } - - /** - * Remove any knowledge about the expression {@code expr} (correctly deciding where to remove the - * information depending on the type of the expression {@code expr}). - */ - public void clearValue(JavaExpression expr) { - if (expr.containsUnknown()) { - // Expressions containing unknown expressions are not stored. - return; - } - if (expr instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) expr; - localVariableValues.remove(localVar); - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - fieldValues.remove(fieldAcc); - } else if (expr instanceof MethodCall) { - MethodCall method = (MethodCall) expr; - methodValues.remove(method); - } else if (expr instanceof ArrayAccess) { - ArrayAccess a = (ArrayAccess) expr; - arrayValues.remove(a); - } else if (expr instanceof ClassName) { - ClassName c = (ClassName) expr; - classValues.remove(c); - } else { // thisValue ... - // No other types of expressions are stored. - } - } - - /** - * Returns the current abstract value of a Java expression, or {@code null} if no information is - * available. - * - * @return the current abstract value of a Java expression, or {@code null} if no information is - * available - */ - public @Nullable V getValue(JavaExpression expr) { - if (expr instanceof LocalVariable) { - LocalVariable localVar = (LocalVariable) expr; - return localVariableValues.get(localVar); - } else if (expr instanceof ThisReference) { - return thisValue; - } else if (expr instanceof FieldAccess) { - FieldAccess fieldAcc = (FieldAccess) expr; - return fieldValues.get(fieldAcc); - } else if (expr instanceof MethodCall) { - MethodCall method = (MethodCall) expr; - return methodValues.get(method); - } else if (expr instanceof ArrayAccess) { - ArrayAccess a = (ArrayAccess) expr; - return arrayValues.get(a); - } else if (expr instanceof ClassName) { - ClassName c = (ClassName) expr; - return classValues.get(c); - } else { - throw new BugInCF("Unexpected JavaExpression: " + expr + " (" + expr.getClass() + ")"); - } - } - - /** - * Returns the current abstract value of a field access, or {@code null} if no information is - * available. - * - * @param n the node whose abstract value to return - * @return the current abstract value of a field access, or {@code null} if no information is - * available - */ - public @Nullable V getValue(FieldAccessNode n) { - JavaExpression je = JavaExpression.fromNodeFieldAccess(n); - if (je instanceof FieldAccess) { - return fieldValues.get((FieldAccess) je); - } else if (je instanceof ClassName) { - return classValues.get((ClassName) je); - } else if (je instanceof ThisReference) { - // "return thisValue" is wrong, because the node refers to an outer this. - // So, return null for now. TODO: improve. - return null; - } else { - throw new BugInCF( - "Unexpected JavaExpression %s %s for FieldAccessNode %s", - je.getClass().getSimpleName(), je, n); - } - } - - /** - * Returns the current abstract value of a field access, or {@code null} if no information is - * available. - * - * @param fieldAccess the field access to look up in this store - * @return current abstract value of a field access, or {@code null} if no information is - * available - */ - public @Nullable V getFieldValue(FieldAccess fieldAccess) { - return fieldValues.get(fieldAccess); - } - - /** - * Returns the current abstract value of a method call, or {@code null} if no information is - * available. - * - * @param n a method call - * @return the current abstract value of a method call, or {@code null} if no information is - * available - */ - public @Nullable V getValue(MethodInvocationNode n) { - JavaExpression method = JavaExpression.fromNode(n); - if (method == null) { - return null; - } - return methodValues.get(method); - } - - /** - * Returns the current abstract value of a field access, or {@code null} if no information is - * available. - * - * @param n the node whose abstract value to return - * @return the current abstract value of a field access, or {@code null} if no information is - * available - */ - public @Nullable V getValue(ArrayAccessNode n) { - ArrayAccess arrayAccess = JavaExpression.fromArrayAccess(n); - return arrayValues.get(arrayAccess); - } - - /** - * Update the information in the store by considering an assignment with target {@code n}. - * - * @param n the left-hand side of an assignment - * @param val the right-hand value of an assignment - */ - public void updateForAssignment(Node n, @Nullable V val) { - JavaExpression je = JavaExpression.fromNode(n); - if (je instanceof ArrayAccess) { - updateForArrayAssignment((ArrayAccess) je, val); - } else if (je instanceof FieldAccess) { - updateForFieldAccessAssignment((FieldAccess) je, val); - } else if (je instanceof LocalVariable) { - updateForLocalVariableAssignment((LocalVariable) je, val); - } else { - throw new BugInCF("Unexpected je of class " + je.getClass()); - } - } - - /** - * Update the information in the store by considering a field assignment with target {@code n}, - * where the right hand side has the abstract value {@code val}. - * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void updateForFieldAccessAssignment(FieldAccess fieldAccess, @Nullable V val) { - removeConflicting(fieldAccess, val); - if (!fieldAccess.containsUnknown() && val != null) { - // Only store information about final fields (where the receiver is - // also fixed) if concurrent semantics are enabled. - if (sequentialSemantics - || isMonotonicUpdate(fieldAccess, val) - || fieldAccess.isUnassignableByOtherCode()) { - fieldValues.put(fieldAccess, val); - } - } - } - - /** - * Update the information in the store by considering an assignment with target {@code n}, where - * the target is an array access. - * - *

          See {@link #removeConflicting(ArrayAccess,CFAbstractValue)}, as it is called first by this - * method. - */ - protected void updateForArrayAssignment(ArrayAccess arrayAccess, @Nullable V val) { - removeConflicting(arrayAccess, val); - if (!arrayAccess.containsUnknown() && val != null) { - // Only store information about final fields (where the receiver is - // also fixed) if concurrent semantics are enabled. - if (sequentialSemantics) { - arrayValues.put(arrayAccess, val); - } + V value = analysis.createSingleAnnotationValue(a, underlyingType); + + V oldValue = thisValue; + V newValue = value.mostSpecific(oldValue, null); + if (newValue != null) { + thisValue = newValue; + } } - } - - /** - * Set the abstract value of a local variable in the store. Overwrites any value that might have - * been available previously. - * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void updateForLocalVariableAssignment(LocalVariable receiver, @Nullable V val) { - removeConflicting(receiver); - if (val != null) { - localVariableValues.put(receiver, val); + + /** + * Completely replaces the abstract value {@code value} for the expression {@code expr} + * (correctly deciding where to store the information depending on the type of the expression + * {@code expr}). Any previous information is discarded. + * + *

          This method does not take care of removing other information that might be influenced by + * changes to certain parts of the state. + */ + public void replaceValue(JavaExpression expr, @Nullable V value) { + clearValue(expr); + insertValue(expr, value); } - } - - /** - * Remove any information in this store that might not be true any more after {@code fieldAccess} - * has been assigned a new value (with the abstract value {@code val}). This includes the - * following steps (assume that {@code fieldAccess} is of the form a.f for some - * a. - * - *

            - *
          1. Update the abstract value of other field accesses b.g where the field - * is equal (that is, f=g), and the receiver b might alias the receiver of - * {@code fieldAccess}, a. This update will raise the abstract value for such field - * accesses to at least {@code val} (or the old value, if that was less precise). However, - * this is only necessary if the field g is not final. - *
          2. Remove any abstract values for field accesses b.g where {@code - * fieldAccess} might alias any expression in the receiver b. - *
          3. Remove any information about method calls. - *
          4. Remove any abstract values an array access b[i] where {@code - * fieldAccess} might alias any expression in the receiver a or index i. - *
          - * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void removeConflicting(FieldAccess fieldAccess, @Nullable V val) { - Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); - while (fieldValuesIterator.hasNext()) { - Map.Entry entry = fieldValuesIterator.next(); - FieldAccess otherFieldAccess = entry.getKey(); - V otherVal = entry.getValue(); - // case 2: - if (otherFieldAccess.getReceiver().containsModifiableAliasOf(this, fieldAccess)) { - fieldValuesIterator.remove(); // remove information completely - } - // case 1: - else if (fieldAccess.getField().equals(otherFieldAccess.getField())) { - if (canAlias(fieldAccess.getReceiver(), otherFieldAccess.getReceiver())) { - if (!otherFieldAccess.isFinal()) { - if (val != null) { - V newVal = val.leastUpperBound(otherVal); - entry.setValue(newVal); - } else { - // remove information completely - fieldValuesIterator.remove(); - } - } + + /** + * Remove any knowledge about the expression {@code expr} (correctly deciding where to remove + * the information depending on the type of the expression {@code expr}). + */ + public void clearValue(JavaExpression expr) { + if (expr.containsUnknown()) { + // Expressions containing unknown expressions are not stored. + return; + } + if (expr instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) expr; + localVariableValues.remove(localVar); + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + fieldValues.remove(fieldAcc); + } else if (expr instanceof MethodCall) { + MethodCall method = (MethodCall) expr; + methodValues.remove(method); + } else if (expr instanceof ArrayAccess) { + ArrayAccess a = (ArrayAccess) expr; + arrayValues.remove(a); + } else if (expr instanceof ClassName) { + ClassName c = (ClassName) expr; + classValues.remove(c); + } else { // thisValue ... + // No other types of expressions are stored. } - } } - Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); - while (arrayValuesIterator.hasNext()) { - Map.Entry entry = arrayValuesIterator.next(); - ArrayAccess otherArrayAccess = entry.getKey(); - if (otherArrayAccess.containsModifiableAliasOf(this, fieldAccess)) { - // remove information completely - arrayValuesIterator.remove(); - } + /** + * Returns the current abstract value of a Java expression, or {@code null} if no information is + * available. + * + * @return the current abstract value of a Java expression, or {@code null} if no information is + * available + */ + public @Nullable V getValue(JavaExpression expr) { + if (expr instanceof LocalVariable) { + LocalVariable localVar = (LocalVariable) expr; + return localVariableValues.get(localVar); + } else if (expr instanceof ThisReference) { + return thisValue; + } else if (expr instanceof FieldAccess) { + FieldAccess fieldAcc = (FieldAccess) expr; + return fieldValues.get(fieldAcc); + } else if (expr instanceof MethodCall) { + MethodCall method = (MethodCall) expr; + return methodValues.get(method); + } else if (expr instanceof ArrayAccess) { + ArrayAccess a = (ArrayAccess) expr; + return arrayValues.get(a); + } else if (expr instanceof ClassName) { + ClassName c = (ClassName) expr; + return classValues.get(c); + } else { + throw new BugInCF("Unexpected JavaExpression: " + expr + " (" + expr.getClass() + ")"); + } } - // case 3: - methodValues.clear(); - } - - /** - * Remove any information in the store that might not be true any more after {@code arrayAccess} - * has been assigned a new value (with the abstract value {@code val}). This includes the - * following steps (assume that {@code arrayAccess} is of the form a[i] for some - * a. - * - *
            - *
          1. Remove any abstract value for other array access b[j] where a - * and b can be aliases, or where either b or j contains a - * modifiable alias of a[i]. - *
          2. Remove any abstract values for field accesses b.g where a[i] - * might alias any expression in the receiver b and there is an array expression - * somewhere in the receiver. - *
          3. Remove any information about method calls. - *
          - * - * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the - * abstract value is not known). - */ - protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) { - Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); - while (arrayValuesIterator.hasNext()) { - Map.Entry entry = arrayValuesIterator.next(); - ArrayAccess otherArrayAccess = entry.getKey(); - // case 1: - if (otherArrayAccess.containsModifiableAliasOf(this, arrayAccess)) { - arrayValuesIterator.remove(); // remove information completely - } else if (canAlias(arrayAccess.getArray(), otherArrayAccess.getArray())) { - // TODO: one could be less strict here, and only raise the abstract - // value for all array expressions with potentially aliasing receivers. - arrayValuesIterator.remove(); // remove information completely - } + /** + * Returns the current abstract value of a field access, or {@code null} if no information is + * available. + * + * @param n the node whose abstract value to return + * @return the current abstract value of a field access, or {@code null} if no information is + * available + */ + public @Nullable V getValue(FieldAccessNode n) { + JavaExpression je = JavaExpression.fromNodeFieldAccess(n); + if (je instanceof FieldAccess) { + return fieldValues.get((FieldAccess) je); + } else if (je instanceof ClassName) { + return classValues.get((ClassName) je); + } else if (je instanceof ThisReference) { + // "return thisValue" is wrong, because the node refers to an outer this. + // So, return null for now. TODO: improve. + return null; + } else { + throw new BugInCF( + "Unexpected JavaExpression %s %s for FieldAccessNode %s", + je.getClass().getSimpleName(), je, n); + } } - // case 2: - Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); - while (fieldValuesIterator.hasNext()) { - Map.Entry entry = fieldValuesIterator.next(); - FieldAccess otherFieldAccess = entry.getKey(); - JavaExpression otherReceiver = otherFieldAccess.getReceiver(); - if (otherReceiver.containsModifiableAliasOf(this, arrayAccess) - && otherReceiver.containsOfClass(ArrayAccess.class)) { - // remove information completely - fieldValuesIterator.remove(); - } + /** + * Returns the current abstract value of a field access, or {@code null} if no information is + * available. + * + * @param fieldAccess the field access to look up in this store + * @return current abstract value of a field access, or {@code null} if no information is + * available + */ + public @Nullable V getFieldValue(FieldAccess fieldAccess) { + return fieldValues.get(fieldAccess); } - // case 3: - methodValues.clear(); - } - - /** - * Remove any information in this store that might not be true any more after {@code localVar} has - * been assigned a new value. This includes the following steps: - * - *
            - *
          1. Remove any abstract values for field accesses b.g where {@code - * localVar} might alias any expression in the receiver b. - *
          2. Remove any abstract values for array accesses a[i] where {@code - * localVar} might alias the receiver a. - *
          3. Remove any information about method calls where the receiver or any of the - * parameters contains {@code localVar}. - *
          - */ - protected void removeConflicting(LocalVariable var) { - Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); - while (fieldValuesIterator.hasNext()) { - Map.Entry entry = fieldValuesIterator.next(); - FieldAccess otherFieldAccess = entry.getKey(); - // case 1: - if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) { - fieldValuesIterator.remove(); - } + /** + * Returns the current abstract value of a method call, or {@code null} if no information is + * available. + * + * @param n a method call + * @return the current abstract value of a method call, or {@code null} if no information is + * available + */ + public @Nullable V getValue(MethodInvocationNode n) { + JavaExpression method = JavaExpression.fromNode(n); + if (method == null) { + return null; + } + return methodValues.get(method); } - Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); - while (arrayValuesIterator.hasNext()) { - Map.Entry entry = arrayValuesIterator.next(); - ArrayAccess otherArrayAccess = entry.getKey(); - // case 2: - if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) { - arrayValuesIterator.remove(); - } + /** + * Returns the current abstract value of a field access, or {@code null} if no information is + * available. + * + * @param n the node whose abstract value to return + * @return the current abstract value of a field access, or {@code null} if no information is + * available + */ + public @Nullable V getValue(ArrayAccessNode n) { + ArrayAccess arrayAccess = JavaExpression.fromArrayAccess(n); + return arrayValues.get(arrayAccess); } - Iterator> methodValuesIterator = methodValues.entrySet().iterator(); - while (methodValuesIterator.hasNext()) { - Map.Entry entry = methodValuesIterator.next(); - MethodCall otherMethodAccess = entry.getKey(); - // case 3: - if (otherMethodAccess.containsSyntacticEqualJavaExpression(var)) { - methodValuesIterator.remove(); - } + /** + * Update the information in the store by considering an assignment with target {@code n}. + * + * @param n the left-hand side of an assignment + * @param val the right-hand value of an assignment + */ + public void updateForAssignment(Node n, @Nullable V val) { + JavaExpression je = JavaExpression.fromNode(n); + if (je instanceof ArrayAccess) { + updateForArrayAssignment((ArrayAccess) je, val); + } else if (je instanceof FieldAccess) { + updateForFieldAccessAssignment((FieldAccess) je, val); + } else if (je instanceof LocalVariable) { + updateForLocalVariableAssignment((LocalVariable) je, val); + } else { + throw new BugInCF("Unexpected je of class " + je.getClass()); + } } - } - - /** - * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., - * returns {@code true} if not enough information is available to determine aliasing). - */ - @Override - public boolean canAlias(JavaExpression a, JavaExpression b) { - TypeMirror tb = b.getType(); - TypeMirror ta = a.getType(); - Types types = analysis.getTypes(); - return types.isSubtype(ta, tb) || types.isSubtype(tb, ta); - } - - /* --------------------------------------------------------- */ - /* Handling of local variables */ - /* --------------------------------------------------------- */ - - /** - * Returns the current abstract value of a local variable, or {@code null} if no information is - * available. - * - * @param n the local variable - * @return the current abstract value of a local variable, or {@code null} if no information is - * available - */ - public @Nullable V getValue(LocalVariableNode n) { - VariableElement el = n.getElement(); - return localVariableValues.get(new LocalVariable(el)); - } - - /* --------------------------------------------------------- */ - /* Handling of the current object */ - /* --------------------------------------------------------- */ - - /** - * Returns the current abstract value of the current object, or {@code null} if no information is - * available. - * - * @param n a reference to "this" - * @return the current abstract value of the current object, or {@code null} if no information is - * available - */ - public @Nullable V getValue(ThisNode n) { - return thisValue; - } - - /* --------------------------------------------------------- */ - /* Helper and miscellaneous methods */ - /* --------------------------------------------------------- */ - - @SuppressWarnings("unchecked") - @Override - public S copy() { - return analysis.createCopiedStore((S) this); - } - - @Override - public S leastUpperBound(S other) { - return upperBound(other, false); - } - - @Override - public S widenedUpperBound(S previous) { - return upperBound(previous, true); - } - - private S upperBound(S other, boolean shouldWiden) { - S newStore = analysis.createEmptyStore(sequentialSemantics); - - for (Map.Entry e : other.localVariableValues.entrySet()) { - // local variables that are only part of one store, but not the other are discarded, as - // one of store implicitly contains 'top' for that variable. - LocalVariable localVar = e.getKey(); - V thisVal = localVariableValues.get(localVar); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - - if (mergedVal != null) { - newStore.localVariableValues.put(localVar, mergedVal); + + /** + * Update the information in the store by considering a field assignment with target {@code n}, + * where the right hand side has the abstract value {@code val}. + * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void updateForFieldAccessAssignment(FieldAccess fieldAccess, @Nullable V val) { + removeConflicting(fieldAccess, val); + if (!fieldAccess.containsUnknown() && val != null) { + // Only store information about final fields (where the receiver is + // also fixed) if concurrent semantics are enabled. + if (sequentialSemantics + || isMonotonicUpdate(fieldAccess, val) + || fieldAccess.isUnassignableByOtherCode()) { + fieldValues.put(fieldAccess, val); + } } - } } - // information about the current object - { - V otherVal = other.thisValue; - V myVal = thisValue; - V mergedVal = myVal == null ? null : upperBoundOfValues(otherVal, myVal, shouldWiden); - if (mergedVal != null) { - newStore.thisValue = mergedVal; - } + /** + * Update the information in the store by considering an assignment with target {@code n}, where + * the target is an array access. + * + *

          See {@link #removeConflicting(ArrayAccess,CFAbstractValue)}, as it is called first by this + * method. + */ + protected void updateForArrayAssignment(ArrayAccess arrayAccess, @Nullable V val) { + removeConflicting(arrayAccess, val); + if (!arrayAccess.containsUnknown() && val != null) { + // Only store information about final fields (where the receiver is + // also fixed) if concurrent semantics are enabled. + if (sequentialSemantics) { + arrayValues.put(arrayAccess, val); + } + } } - for (Map.Entry e : other.fieldValues.entrySet()) { - // information about fields that are only part of one store, but not the other are - // discarded, as one store implicitly contains 'top' for that field. - FieldAccess el = e.getKey(); - V thisVal = fieldValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.fieldValues.put(el, mergedVal); + /** + * Set the abstract value of a local variable in the store. Overwrites any value that might have + * been available previously. + * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void updateForLocalVariableAssignment(LocalVariable receiver, @Nullable V val) { + removeConflicting(receiver); + if (val != null) { + localVariableValues.put(receiver, val); } - } } - for (Map.Entry e : other.arrayValues.entrySet()) { - // information about arrays that are only part of one store, but not the other are - // discarded, as one store implicitly contains 'top' for that array access. - ArrayAccess el = e.getKey(); - V thisVal = arrayValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.arrayValues.put(el, mergedVal); + + /** + * Remove any information in this store that might not be true any more after {@code + * fieldAccess} has been assigned a new value (with the abstract value {@code val}). This + * includes the following steps (assume that {@code fieldAccess} is of the form a.f for + * some a. + * + *

            + *
          1. Update the abstract value of other field accesses b.g where the + * field is equal (that is, f=g), and the receiver b might alias the + * receiver of {@code fieldAccess}, a. This update will raise the abstract value + * for such field accesses to at least {@code val} (or the old value, if that was less + * precise). However, this is only necessary if the field g is not final. + *
          2. Remove any abstract values for field accesses b.g where {@code + * fieldAccess} might alias any expression in the receiver b. + *
          3. Remove any information about method calls. + *
          4. Remove any abstract values an array access b[i] where {@code + * fieldAccess} might alias any expression in the receiver a or index i. + *
          + * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void removeConflicting(FieldAccess fieldAccess, @Nullable V val) { + Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); + while (fieldValuesIterator.hasNext()) { + Map.Entry entry = fieldValuesIterator.next(); + FieldAccess otherFieldAccess = entry.getKey(); + V otherVal = entry.getValue(); + // case 2: + if (otherFieldAccess.getReceiver().containsModifiableAliasOf(this, fieldAccess)) { + fieldValuesIterator.remove(); // remove information completely + } + // case 1: + else if (fieldAccess.getField().equals(otherFieldAccess.getField())) { + if (canAlias(fieldAccess.getReceiver(), otherFieldAccess.getReceiver())) { + if (!otherFieldAccess.isFinal()) { + if (val != null) { + V newVal = val.leastUpperBound(otherVal); + entry.setValue(newVal); + } else { + // remove information completely + fieldValuesIterator.remove(); + } + } + } + } + } + + Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); + while (arrayValuesIterator.hasNext()) { + Map.Entry entry = arrayValuesIterator.next(); + ArrayAccess otherArrayAccess = entry.getKey(); + if (otherArrayAccess.containsModifiableAliasOf(this, fieldAccess)) { + // remove information completely + arrayValuesIterator.remove(); + } } - } + + // case 3: + methodValues.clear(); } - for (Map.Entry e : other.methodValues.entrySet()) { - // information about methods that are only part of one store, but not the other are - // discarded, as one store implicitly contains 'top' for that field. - MethodCall el = e.getKey(); - V thisVal = methodValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.methodValues.put(el, mergedVal); + + /** + * Remove any information in the store that might not be true any more after {@code arrayAccess} + * has been assigned a new value (with the abstract value {@code val}). This includes the + * following steps (assume that {@code arrayAccess} is of the form a[i] for some + * a. + * + *
            + *
          1. Remove any abstract value for other array access b[j] where + * a and b can be aliases, or where either b or j + * contains a modifiable alias of a[i]. + *
          2. Remove any abstract values for field accesses b.g where + * a[i] might alias any expression in the receiver b and there is an + * array expression somewhere in the receiver. + *
          3. Remove any information about method calls. + *
          + * + * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the + * abstract value is not known). + */ + protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) { + Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); + while (arrayValuesIterator.hasNext()) { + Map.Entry entry = arrayValuesIterator.next(); + ArrayAccess otherArrayAccess = entry.getKey(); + // case 1: + if (otherArrayAccess.containsModifiableAliasOf(this, arrayAccess)) { + arrayValuesIterator.remove(); // remove information completely + } else if (canAlias(arrayAccess.getArray(), otherArrayAccess.getArray())) { + // TODO: one could be less strict here, and only raise the abstract + // value for all array expressions with potentially aliasing receivers. + arrayValuesIterator.remove(); // remove information completely + } } - } + + // case 2: + Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); + while (fieldValuesIterator.hasNext()) { + Map.Entry entry = fieldValuesIterator.next(); + FieldAccess otherFieldAccess = entry.getKey(); + JavaExpression otherReceiver = otherFieldAccess.getReceiver(); + if (otherReceiver.containsModifiableAliasOf(this, arrayAccess) + && otherReceiver.containsOfClass(ArrayAccess.class)) { + // remove information completely + fieldValuesIterator.remove(); + } + } + + // case 3: + methodValues.clear(); } - for (Map.Entry e : other.classValues.entrySet()) { - ClassName el = e.getKey(); - V thisVal = classValues.get(el); - if (thisVal != null) { - V otherVal = e.getValue(); - V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); - if (mergedVal != null) { - newStore.classValues.put(el, mergedVal); + + /** + * Remove any information in this store that might not be true any more after {@code localVar} + * has been assigned a new value. This includes the following steps: + * + *
            + *
          1. Remove any abstract values for field accesses b.g where {@code + * localVar} might alias any expression in the receiver b. + *
          2. Remove any abstract values for array accesses a[i] where {@code + * localVar} might alias the receiver a. + *
          3. Remove any information about method calls where the receiver or any of the + * parameters contains {@code localVar}. + *
          + */ + protected void removeConflicting(LocalVariable var) { + Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); + while (fieldValuesIterator.hasNext()) { + Map.Entry entry = fieldValuesIterator.next(); + FieldAccess otherFieldAccess = entry.getKey(); + // case 1: + if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) { + fieldValuesIterator.remove(); + } + } + + Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); + while (arrayValuesIterator.hasNext()) { + Map.Entry entry = arrayValuesIterator.next(); + ArrayAccess otherArrayAccess = entry.getKey(); + // case 2: + if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) { + arrayValuesIterator.remove(); + } + } + + Iterator> methodValuesIterator = + methodValues.entrySet().iterator(); + while (methodValuesIterator.hasNext()) { + Map.Entry entry = methodValuesIterator.next(); + MethodCall otherMethodAccess = entry.getKey(); + // case 3: + if (otherMethodAccess.containsSyntacticEqualJavaExpression(var)) { + methodValuesIterator.remove(); + } } - } } - return newStore; - } - - private V upperBoundOfValues(V otherVal, V thisVal, boolean shouldWiden) { - return shouldWiden ? thisVal.widenUpperBound(otherVal) : thisVal.leastUpperBound(otherVal); - } - - /** - * Returns true iff this {@link CFAbstractStore} contains a superset of the map entries of the - * argument {@link CFAbstractStore}. Note that we test the entry keys and values by Java equality, - * not by any subtype relationship. This method is used primarily to simplify the equals - * predicate. - */ - protected boolean supersetOf(CFAbstractStore other) { - for (Map.Entry e : other.localVariableValues.entrySet()) { - LocalVariable key = e.getKey(); - V value = localVariableValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } + + /** + * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e., + * returns {@code true} if not enough information is available to determine aliasing). + */ + @Override + public boolean canAlias(JavaExpression a, JavaExpression b) { + TypeMirror tb = b.getType(); + TypeMirror ta = a.getType(); + Types types = analysis.getTypes(); + return types.isSubtype(ta, tb) || types.isSubtype(tb, ta); } - if (!Objects.equals(thisValue, other.thisValue)) { - return false; + + /* --------------------------------------------------------- */ + /* Handling of local variables */ + /* --------------------------------------------------------- */ + + /** + * Returns the current abstract value of a local variable, or {@code null} if no information is + * available. + * + * @param n the local variable + * @return the current abstract value of a local variable, or {@code null} if no information is + * available + */ + public @Nullable V getValue(LocalVariableNode n) { + VariableElement el = n.getElement(); + return localVariableValues.get(new LocalVariable(el)); } - for (Map.Entry e : other.fieldValues.entrySet()) { - FieldAccess key = e.getKey(); - V value = fieldValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } + + /* --------------------------------------------------------- */ + /* Handling of the current object */ + /* --------------------------------------------------------- */ + + /** + * Returns the current abstract value of the current object, or {@code null} if no information + * is available. + * + * @param n a reference to "this" + * @return the current abstract value of the current object, or {@code null} if no information + * is available + */ + public @Nullable V getValue(ThisNode n) { + return thisValue; } - for (Map.Entry e : other.arrayValues.entrySet()) { - ArrayAccess key = e.getKey(); - V value = arrayValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } + + /* --------------------------------------------------------- */ + /* Helper and miscellaneous methods */ + /* --------------------------------------------------------- */ + + @SuppressWarnings("unchecked") + @Override + public S copy() { + return analysis.createCopiedStore((S) this); } - for (Map.Entry e : other.methodValues.entrySet()) { - MethodCall key = e.getKey(); - V value = methodValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } + + @Override + public S leastUpperBound(S other) { + return upperBound(other, false); } - for (Map.Entry e : other.classValues.entrySet()) { - ClassName key = e.getKey(); - V value = classValues.get(key); - if (value == null || !value.equals(e.getValue())) { - return false; - } + + @Override + public S widenedUpperBound(S previous) { + return upperBound(previous, true); } - return true; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof CFAbstractStore) { - @SuppressWarnings("unchecked") - CFAbstractStore other = (CFAbstractStore) o; - return this.supersetOf(other) && other.supersetOf(this); - } else { - return false; + + private S upperBound(S other, boolean shouldWiden) { + S newStore = analysis.createEmptyStore(sequentialSemantics); + + for (Map.Entry e : other.localVariableValues.entrySet()) { + // local variables that are only part of one store, but not the other are discarded, as + // one of store implicitly contains 'top' for that variable. + LocalVariable localVar = e.getKey(); + V thisVal = localVariableValues.get(localVar); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + + if (mergedVal != null) { + newStore.localVariableValues.put(localVar, mergedVal); + } + } + } + + // information about the current object + { + V otherVal = other.thisValue; + V myVal = thisValue; + V mergedVal = myVal == null ? null : upperBoundOfValues(otherVal, myVal, shouldWiden); + if (mergedVal != null) { + newStore.thisValue = mergedVal; + } + } + + for (Map.Entry e : other.fieldValues.entrySet()) { + // information about fields that are only part of one store, but not the other are + // discarded, as one store implicitly contains 'top' for that field. + FieldAccess el = e.getKey(); + V thisVal = fieldValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.fieldValues.put(el, mergedVal); + } + } + } + for (Map.Entry e : other.arrayValues.entrySet()) { + // information about arrays that are only part of one store, but not the other are + // discarded, as one store implicitly contains 'top' for that array access. + ArrayAccess el = e.getKey(); + V thisVal = arrayValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.arrayValues.put(el, mergedVal); + } + } + } + for (Map.Entry e : other.methodValues.entrySet()) { + // information about methods that are only part of one store, but not the other are + // discarded, as one store implicitly contains 'top' for that field. + MethodCall el = e.getKey(); + V thisVal = methodValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.methodValues.put(el, mergedVal); + } + } + } + for (Map.Entry e : other.classValues.entrySet()) { + ClassName el = e.getKey(); + V thisVal = classValues.get(el); + if (thisVal != null) { + V otherVal = e.getValue(); + V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); + if (mergedVal != null) { + newStore.classValues.put(el, mergedVal); + } + } + } + return newStore; } - } - - @Override - public int hashCode() { - // What is a good hash code to use? - return 22; - } - - @SideEffectFree - @Override - public String toString() { - return visualize(new StringCFGVisualizer<>()); - } - - @Override - public String visualize(CFGVisualizer viz) { - // This cast is guaranteed to be safe, as long as the CFGVisualizer is created by - // CFGVisualizer createCFGVisualizer() of - // GenericAnnotatedTypeFactory. - @SuppressWarnings("unchecked") - CFGVisualizer castedViz = (CFGVisualizer) viz; - String internal = internalVisualize(castedViz); - if (internal.trim().isEmpty()) { - return this.getClassAndUid() + "()"; - } else { - return this.getClassAndUid() + "(" + viz.getSeparator() + internal + ")"; + + private V upperBoundOfValues(V otherVal, V thisVal, boolean shouldWiden) { + return shouldWiden ? thisVal.widenUpperBound(otherVal) : thisVal.leastUpperBound(otherVal); } - } - - /** - * Adds a representation of the internal information of this Store to visualizer {@code viz}. - * - * @param viz the visualizer - * @return a representation of the internal information of this {@link Store} - */ - protected String internalVisualize(CFGVisualizer viz) { - StringJoiner res = new StringJoiner(viz.getSeparator()); - for (LocalVariable lv : ToStringComparator.sorted(localVariableValues.keySet())) { - res.add(viz.visualizeStoreLocalVar(lv, localVariableValues.get(lv))); + + /** + * Returns true iff this {@link CFAbstractStore} contains a superset of the map entries of the + * argument {@link CFAbstractStore}. Note that we test the entry keys and values by Java + * equality, not by any subtype relationship. This method is used primarily to simplify the + * equals predicate. + */ + protected boolean supersetOf(CFAbstractStore other) { + for (Map.Entry e : other.localVariableValues.entrySet()) { + LocalVariable key = e.getKey(); + V value = localVariableValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + if (!Objects.equals(thisValue, other.thisValue)) { + return false; + } + for (Map.Entry e : other.fieldValues.entrySet()) { + FieldAccess key = e.getKey(); + V value = fieldValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + for (Map.Entry e : other.arrayValues.entrySet()) { + ArrayAccess key = e.getKey(); + V value = arrayValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + for (Map.Entry e : other.methodValues.entrySet()) { + MethodCall key = e.getKey(); + V value = methodValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + for (Map.Entry e : other.classValues.entrySet()) { + ClassName key = e.getKey(); + V value = classValues.get(key); + if (value == null || !value.equals(e.getValue())) { + return false; + } + } + return true; } - if (thisValue != null) { - res.add(viz.visualizeStoreThisVal(thisValue)); + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof CFAbstractStore) { + @SuppressWarnings("unchecked") + CFAbstractStore other = (CFAbstractStore) o; + return this.supersetOf(other) && other.supersetOf(this); + } else { + return false; + } } - for (FieldAccess fa : ToStringComparator.sorted(fieldValues.keySet())) { - res.add(viz.visualizeStoreFieldVal(fa, fieldValues.get(fa))); + + @Override + public int hashCode() { + // What is a good hash code to use? + return 22; } - for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) { - res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa))); + + @SideEffectFree + @Override + public String toString() { + return visualize(new StringCFGVisualizer<>()); } - for (MethodCall fa : ToStringComparator.sorted(methodValues.keySet())) { - res.add(viz.visualizeStoreMethodVals(fa, methodValues.get(fa))); + + @Override + public String visualize(CFGVisualizer viz) { + // This cast is guaranteed to be safe, as long as the CFGVisualizer is created by + // CFGVisualizer createCFGVisualizer() of + // GenericAnnotatedTypeFactory. + @SuppressWarnings("unchecked") + CFGVisualizer castedViz = (CFGVisualizer) viz; + String internal = internalVisualize(castedViz); + if (internal.trim().isEmpty()) { + return this.getClassAndUid() + "()"; + } else { + return this.getClassAndUid() + "(" + viz.getSeparator() + internal + ")"; + } } - for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) { - res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa))); + + /** + * Adds a representation of the internal information of this Store to visualizer {@code viz}. + * + * @param viz the visualizer + * @return a representation of the internal information of this {@link Store} + */ + protected String internalVisualize(CFGVisualizer viz) { + StringJoiner res = new StringJoiner(viz.getSeparator()); + for (LocalVariable lv : ToStringComparator.sorted(localVariableValues.keySet())) { + res.add(viz.visualizeStoreLocalVar(lv, localVariableValues.get(lv))); + } + if (thisValue != null) { + res.add(viz.visualizeStoreThisVal(thisValue)); + } + for (FieldAccess fa : ToStringComparator.sorted(fieldValues.keySet())) { + res.add(viz.visualizeStoreFieldVal(fa, fieldValues.get(fa))); + } + for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) { + res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa))); + } + for (MethodCall fa : ToStringComparator.sorted(methodValues.keySet())) { + res.add(viz.visualizeStoreMethodVals(fa, methodValues.get(fa))); + } + for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) { + res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa))); + } + return res.toString(); } - return res.toString(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java index 1df6049f0db..26782a0ad3a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -6,19 +6,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; @@ -79,6 +67,21 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + /** * The default analysis transfer function for the Checker Framework. It propagates information * through assignments. It uses the {@link AnnotatedTypeFactory} to provide checker-specific logic @@ -92,1304 +95,1335 @@ * function logic themselves. */ public abstract class CFAbstractTransfer< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> - extends AbstractNodeVisitor, TransferInput> - implements ForwardTransferFunction { - - /** The analysis used by this transfer function. */ - protected final CFAbstractAnalysis analysis; - - /** - * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running - * at all times)? - */ - protected final boolean sequentialSemantics; - - /* NO-AFU Indicates that the whole-program inference is on. */ - /* NO-AFU - private final boolean infer; - */ - - /** - * Create a CFAbstractTransfer. - * - * @param analysis the analysis used by this transfer function - */ - protected CFAbstractTransfer(CFAbstractAnalysis analysis) { - this(analysis, false); - } - - /** - * Constructor that allows forcing concurrent semantics to be on for this instance of - * CFAbstractTransfer. - * - * @param analysis the analysis used by this transfer function - * @param forceConcurrentSemantics whether concurrent semantics should be forced to be on. If - * false, concurrent semantics are turned off by default, but the user can still turn them on - * via {@code -AconcurrentSemantics}. If true, the user cannot turn off concurrent semantics. - */ - protected CFAbstractTransfer( - CFAbstractAnalysis analysis, boolean forceConcurrentSemantics) { - this.analysis = analysis; - this.sequentialSemantics = - !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics")); + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> + extends AbstractNodeVisitor, TransferInput> + implements ForwardTransferFunction { + + /** The analysis used by this transfer function. */ + protected final CFAbstractAnalysis analysis; + + /** + * Should the analysis use sequential Java semantics (i.e., assume that only one thread is + * running at all times)? + */ + protected final boolean sequentialSemantics; + + /* NO-AFU Indicates that the whole-program inference is on. */ /* NO-AFU - this.infer = analysis.checker.hasOption("infer"); + private final boolean infer; */ - } - - /** - * Returns true if the transfer function uses sequential semantics, false if it uses concurrent - * semantics. Useful when creating an empty store, since a store makes different decisions - * depending on whether sequential or concurrent semantics are used. - * - * @return true if the transfer function uses sequential semantics, false if it uses concurrent - * semantics - */ - @Pure - public boolean usesSequentialSemantics() { - return sequentialSemantics; - } - - /** - * A hook for subclasses to modify the result of the transfer function. This method is called - * before returning the abstract value {@code value} as the result of the transfer function. - * - *

          If a subclass overrides this method, the subclass should also override {@link - * #finishValue(CFAbstractValue,CFAbstractStore,CFAbstractStore)}. - * - * @param value a value to possibly modify - * @param store the store - * @return the possibly-modified value - */ - @SideEffectFree - protected @Nullable V finishValue(@Nullable V value, S store) { - return value; - } - - /** - * A hook for subclasses to modify the result of the transfer function. This method is called - * before returning the abstract value {@code value} as the result of the transfer function. - * - *

          If a subclass overrides this method, the subclass should also override {@link - * #finishValue(CFAbstractValue,CFAbstractStore)}. - * - * @param value the value to finish - * @param thenStore the "then" store - * @param elseStore the "else" store - * @return the possibly-modified value - */ - @SideEffectFree - protected @Nullable V finishValue(@Nullable V value, S thenStore, S elseStore) { - return value; - } - - /** - * Returns the abstract value of a non-leaf tree {@code tree}, as computed by the {@link - * AnnotatedTypeFactory}. - * - * @return the abstract value of a non-leaf tree {@code tree}, as computed by the {@link - * AnnotatedTypeFactory} - */ - protected V getValueFromFactory(Tree tree, Node node) { - GenericAnnotatedTypeFactory> factory = - analysis.atypeFactory; - Tree preTree = analysis.getCurrentTree(); - analysis.setCurrentTree(tree); - AnnotatedTypeMirror at; - if (node instanceof MethodInvocationNode - && ((MethodInvocationNode) node).getIterableExpression() != null) { - ExpressionTree iter = ((MethodInvocationNode) node).getIterableExpression(); - at = factory.getIterableElementType(iter); - } else if (node instanceof ArrayAccessNode - && ((ArrayAccessNode) node).getArrayExpression() != null) { - ExpressionTree array = ((ArrayAccessNode) node).getArrayExpression(); - at = factory.getIterableElementType(array); - } else { - at = factory.getAnnotatedType(tree); + + /** + * Create a CFAbstractTransfer. + * + * @param analysis the analysis used by this transfer function + */ + protected CFAbstractTransfer(CFAbstractAnalysis analysis) { + this(analysis, false); } - analysis.setCurrentTree(preTree); - return analysis.createAbstractValue(at); - } - - /** The fixed initial store. */ - private @Nullable S fixedInitialStore = null; - - /** - * Set a fixed initial Store. - * - * @param s initial store; possible null - */ - public void setFixedInitialStore(@Nullable S s) { - fixedInitialStore = s; - } - - /** The initial store maps method formal parameters to their currently most refined type. */ - @Override - public S initialStore(UnderlyingAST underlyingAST, List parameters) { - if (underlyingAST.getKind() != UnderlyingAST.Kind.LAMBDA - && underlyingAST.getKind() != UnderlyingAST.Kind.METHOD) { - if (fixedInitialStore != null) { - return fixedInitialStore; - } else { - return analysis.createEmptyStore(sequentialSemantics); - } + + /** + * Constructor that allows forcing concurrent semantics to be on for this instance of + * CFAbstractTransfer. + * + * @param analysis the analysis used by this transfer function + * @param forceConcurrentSemantics whether concurrent semantics should be forced to be on. If + * false, concurrent semantics are turned off by default, but the user can still turn them + * on via {@code -AconcurrentSemantics}. If true, the user cannot turn off concurrent + * semantics. + */ + protected CFAbstractTransfer( + CFAbstractAnalysis analysis, boolean forceConcurrentSemantics) { + this.analysis = analysis; + this.sequentialSemantics = + !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics")); + /* NO-AFU + this.infer = analysis.checker.hasOption("infer"); + */ } - S store; - AnnotatedTypeFactory atypeFactory = analysis.getTypeFactory(); + /** + * Returns true if the transfer function uses sequential semantics, false if it uses concurrent + * semantics. Useful when creating an empty store, since a store makes different decisions + * depending on whether sequential or concurrent semantics are used. + * + * @return true if the transfer function uses sequential semantics, false if it uses concurrent + * semantics + */ + @Pure + public boolean usesSequentialSemantics() { + return sequentialSemantics; + } - if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { - if (fixedInitialStore != null) { - // copy knowledge - store = analysis.createCopiedStore(fixedInitialStore); - } else { - store = analysis.createEmptyStore(sequentialSemantics); - } + /** + * A hook for subclasses to modify the result of the transfer function. This method is called + * before returning the abstract value {@code value} as the result of the transfer function. + * + *

          If a subclass overrides this method, the subclass should also override {@link + * #finishValue(CFAbstractValue,CFAbstractStore,CFAbstractStore)}. + * + * @param value a value to possibly modify + * @param store the store + * @return the possibly-modified value + */ + @SideEffectFree + protected @Nullable V finishValue(@Nullable V value, S store) { + return value; + } - for (LocalVariableNode p : parameters) { - AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); - store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); - } + /** + * A hook for subclasses to modify the result of the transfer function. This method is called + * before returning the abstract value {@code value} as the result of the transfer function. + * + *

          If a subclass overrides this method, the subclass should also override {@link + * #finishValue(CFAbstractValue,CFAbstractStore)}. + * + * @param value the value to finish + * @param thenStore the "then" store + * @param elseStore the "else" store + * @return the possibly-modified value + */ + @SideEffectFree + protected @Nullable V finishValue(@Nullable V value, S thenStore, S elseStore) { + return value; + } - // add properties known through precondition - CFGMethod method = (CFGMethod) underlyingAST; - MethodTree methodDeclTree = method.getMethod(); - ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree); - addInformationFromPreconditions(store, atypeFactory, method, methodDeclTree, methodElem); - - addInitialFieldValues(store, method.getClassTree(), methodDeclTree); - - addFinalLocalValues(store, methodElem); - - /* NO-AFU - if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) { - Map overriddenMethods = - AnnotatedTypes.overriddenMethods( - atypeFactory.getElementUtils(), atypeFactory, methodElem); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedExecutableType overriddenMethod = - AnnotatedTypes.asMemberOf( - atypeFactory.getProcessingEnv().getTypeUtils(), - atypeFactory, - pair.getKey(), - pair.getValue()); - - // Infers parameter and receiver types of the method based - // on the overridden method. - atypeFactory - .getWholeProgramInference() - .updateFromOverride(methodDeclTree, methodElem, overriddenMethod); + /** + * Returns the abstract value of a non-leaf tree {@code tree}, as computed by the {@link + * AnnotatedTypeFactory}. + * + * @return the abstract value of a non-leaf tree {@code tree}, as computed by the {@link + * AnnotatedTypeFactory} + */ + protected V getValueFromFactory(Tree tree, Node node) { + GenericAnnotatedTypeFactory> factory = + analysis.atypeFactory; + Tree preTree = analysis.getCurrentTree(); + analysis.setCurrentTree(tree); + AnnotatedTypeMirror at; + if (node instanceof MethodInvocationNode + && ((MethodInvocationNode) node).getIterableExpression() != null) { + ExpressionTree iter = ((MethodInvocationNode) node).getIterableExpression(); + at = factory.getIterableElementType(iter); + } else if (node instanceof ArrayAccessNode + && ((ArrayAccessNode) node).getArrayExpression() != null) { + ExpressionTree array = ((ArrayAccessNode) node).getArrayExpression(); + at = factory.getIterableElementType(array); + } else { + at = factory.getAnnotatedType(tree); } - } - */ - - } else if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { - if (fixedInitialStore != null) { - // Create a copy and keep only the field values (nothing else applies). - store = analysis.createCopiedStore(fixedInitialStore); - // Allow that local variables are retained; they are effectively final, - // otherwise Java wouldn't allow access from within the lambda. - // TODO: what about the other information? Can code further down be simplified? - // store.localVariableValues.clear(); - store.classValues.clear(); - store.arrayValues.clear(); - store.methodValues.clear(); - } else { - store = analysis.createEmptyStore(sequentialSemantics); - } + analysis.setCurrentTree(preTree); + return analysis.createAbstractValue(at); + } - for (LocalVariableNode p : parameters) { - AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); - store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); - } + /** The fixed initial store. */ + private @Nullable S fixedInitialStore = null; - CFGLambda lambda = (CFGLambda) underlyingAST; - @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests - @InternedDistinct Tree enclosingTree = - TreePathUtil.enclosingOfKind( - atypeFactory.getPath(lambda.getLambdaTree()), TreeUtils.classAndMethodTreeKinds()); - - Element enclosingElement = null; - if (enclosingTree.getKind() == Tree.Kind.METHOD) { - // If it is in an initializer, we need to use locals from the initializer. - enclosingElement = TreeUtils.elementFromDeclaration((MethodTree) enclosingTree); - - } else if (TreeUtils.isClassTree(enclosingTree)) { - - // Try to find an enclosing initializer block. - // Would love to know if there was a better way. - // Find any enclosing element of the lambda (using trees). - // Then go up the elements to find an initializer element (which can't be found with - // the tree). - TreePath loopTree = atypeFactory.getPath(lambda.getLambdaTree()).getParentPath(); - Element anEnclosingElement = null; - while (loopTree.getLeaf() != enclosingTree) { - Element sym = TreeUtils.elementFromTree(loopTree.getLeaf()); - if (sym != null) { - anEnclosingElement = sym; - break; - } - loopTree = loopTree.getParentPath(); + /** + * Set a fixed initial Store. + * + * @param s initial store; possible null + */ + public void setFixedInitialStore(@Nullable S s) { + fixedInitialStore = s; + } + + /** The initial store maps method formal parameters to their currently most refined type. */ + @Override + public S initialStore(UnderlyingAST underlyingAST, List parameters) { + if (underlyingAST.getKind() != UnderlyingAST.Kind.LAMBDA + && underlyingAST.getKind() != UnderlyingAST.Kind.METHOD) { + if (fixedInitialStore != null) { + return fixedInitialStore; + } else { + return analysis.createEmptyStore(sequentialSemantics); + } } - while (anEnclosingElement != null - && !anEnclosingElement.equals(TreeUtils.elementFromTree(enclosingTree))) { - if (anEnclosingElement.getKind() == ElementKind.INSTANCE_INIT - || anEnclosingElement.getKind() == ElementKind.STATIC_INIT) { - enclosingElement = anEnclosingElement; - break; - } - anEnclosingElement = anEnclosingElement.getEnclosingElement(); + + S store; + AnnotatedTypeFactory atypeFactory = analysis.getTypeFactory(); + + if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + if (fixedInitialStore != null) { + // copy knowledge + store = analysis.createCopiedStore(fixedInitialStore); + } else { + store = analysis.createEmptyStore(sequentialSemantics); + } + + for (LocalVariableNode p : parameters) { + AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); + store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); + } + + // add properties known through precondition + CFGMethod method = (CFGMethod) underlyingAST; + MethodTree methodDeclTree = method.getMethod(); + ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree); + addInformationFromPreconditions( + store, atypeFactory, method, methodDeclTree, methodElem); + + addInitialFieldValues(store, method.getClassTree(), methodDeclTree); + + addFinalLocalValues(store, methodElem); + + /* NO-AFU + if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) { + Map overriddenMethods = + AnnotatedTypes.overriddenMethods( + atypeFactory.getElementUtils(), atypeFactory, methodElem); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf( + atypeFactory.getProcessingEnv().getTypeUtils(), + atypeFactory, + pair.getKey(), + pair.getValue()); + + // Infers parameter and receiver types of the method based + // on the overridden method. + atypeFactory + .getWholeProgramInference() + .updateFromOverride(methodDeclTree, methodElem, overriddenMethod); + } + } + */ + + } else if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { + if (fixedInitialStore != null) { + // Create a copy and keep only the field values (nothing else applies). + store = analysis.createCopiedStore(fixedInitialStore); + // Allow that local variables are retained; they are effectively final, + // otherwise Java wouldn't allow access from within the lambda. + // TODO: what about the other information? Can code further down be simplified? + // store.localVariableValues.clear(); + store.classValues.clear(); + store.arrayValues.clear(); + store.methodValues.clear(); + } else { + store = analysis.createEmptyStore(sequentialSemantics); + } + + for (LocalVariableNode p : parameters) { + AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); + store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); + } + + CFGLambda lambda = (CFGLambda) underlyingAST; + @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests + @InternedDistinct Tree enclosingTree = + TreePathUtil.enclosingOfKind( + atypeFactory.getPath(lambda.getLambdaTree()), + TreeUtils.classAndMethodTreeKinds()); + + Element enclosingElement = null; + if (enclosingTree.getKind() == Tree.Kind.METHOD) { + // If it is in an initializer, we need to use locals from the initializer. + enclosingElement = TreeUtils.elementFromDeclaration((MethodTree) enclosingTree); + + } else if (TreeUtils.isClassTree(enclosingTree)) { + + // Try to find an enclosing initializer block. + // Would love to know if there was a better way. + // Find any enclosing element of the lambda (using trees). + // Then go up the elements to find an initializer element (which can't be found with + // the tree). + TreePath loopTree = atypeFactory.getPath(lambda.getLambdaTree()).getParentPath(); + Element anEnclosingElement = null; + while (loopTree.getLeaf() != enclosingTree) { + Element sym = TreeUtils.elementFromTree(loopTree.getLeaf()); + if (sym != null) { + anEnclosingElement = sym; + break; + } + loopTree = loopTree.getParentPath(); + } + while (anEnclosingElement != null + && !anEnclosingElement.equals(TreeUtils.elementFromTree(enclosingTree))) { + if (anEnclosingElement.getKind() == ElementKind.INSTANCE_INIT + || anEnclosingElement.getKind() == ElementKind.STATIC_INIT) { + enclosingElement = anEnclosingElement; + break; + } + anEnclosingElement = anEnclosingElement.getEnclosingElement(); + } + } + if (enclosingElement != null) { + addFinalLocalValues(store, enclosingElement); + } + + // We want the initialization stuff, but need to throw out any refinements. + Map fieldValuesClone = new HashMap<>(store.fieldValues); + for (Map.Entry fieldValue : fieldValuesClone.entrySet()) { + AnnotatedTypeMirror declaredType = + atypeFactory.getAnnotatedType(fieldValue.getKey().getField()); + V lubbedValue = + analysis.createAbstractValue(declaredType) + .leastUpperBound(fieldValue.getValue()); + store.fieldValues.put(fieldValue.getKey(), lubbedValue); + } + } else { + assert false : "Unexpected tree: " + underlyingAST; + store = null; } - } - if (enclosingElement != null) { - addFinalLocalValues(store, enclosingElement); - } - // We want the initialization stuff, but need to throw out any refinements. - Map fieldValuesClone = new HashMap<>(store.fieldValues); - for (Map.Entry fieldValue : fieldValuesClone.entrySet()) { - AnnotatedTypeMirror declaredType = - atypeFactory.getAnnotatedType(fieldValue.getKey().getField()); - V lubbedValue = - analysis.createAbstractValue(declaredType).leastUpperBound(fieldValue.getValue()); - store.fieldValues.put(fieldValue.getKey(), lubbedValue); - } - } else { - assert false : "Unexpected tree: " + underlyingAST; - store = null; + return store; } - return store; - } - - /** - * Add field values to the initial store before {@code methodTree}. - * - *

          The initializer value is inserted into {@code store} if the field is final and the field - * type is immutable, as defined by {@link AnnotatedTypeFactory#isImmutable(TypeMirror)}. - * - *

          The declared value is inserted into {@code store} if: - * - *

            - *
          • {@code methodTree} is a constructor and the field has an initializer. (Use the - * declaration type rather than the initializer because an initialization block might have - * re-set it.) - *
          • {@code methodTree} is not a constructor and the receiver is fully initialized as - * determined by {@link #isNotFullyInitializedReceiver(MethodTree)}. - *
          - * - * @param store initial store into which field values are inserted; it may not be empty - * @param classTree the class that contains {@code methodTree} - * @param methodTree the method or constructor tree - */ - // TODO: should field visibility matter? An access from outside the class might observe - // the declared type instead of a refined type. Issue a warning to alert users? - private void addInitialFieldValues(S store, ClassTree classTree, MethodTree methodTree) { - boolean isConstructor = TreeUtils.isConstructor(methodTree); - boolean isStaticMethod = ElementUtils.isStatic(TreeUtils.elementFromDeclaration(methodTree)); - TypeElement classEle = TreeUtils.elementFromDeclaration(classTree); - for (FieldInitialValue fieldInitialValue : analysis.getFieldInitialValues()) { - VariableElement varEle = fieldInitialValue.fieldDecl.getField(); - boolean isStaticField = ElementUtils.isStatic(varEle); - if (isStaticMethod && !isStaticField) { - continue; - } - // Insert the value from the initializer of private final fields. - if (fieldInitialValue.initializer != null - // && varEle.getModifiers().contains(Modifier.PRIVATE) - && ElementUtils.isFinal(varEle) - && analysis.atypeFactory.isImmutable(ElementUtils.getType(varEle))) { - store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.initializer); - // The type from the initializer is always more specific than (or equal to) the - // declared type of the field. - // So, if there is an initializer, there is no point in inserting the declared type - // below. - continue; - } + /** + * Add field values to the initial store before {@code methodTree}. + * + *

          The initializer value is inserted into {@code store} if the field is final and the field + * type is immutable, as defined by {@link AnnotatedTypeFactory#isImmutable(TypeMirror)}. + * + *

          The declared value is inserted into {@code store} if: + * + *

            + *
          • {@code methodTree} is a constructor and the field has an initializer. (Use the + * declaration type rather than the initializer because an initialization block might have + * re-set it.) + *
          • {@code methodTree} is not a constructor and the receiver is fully initialized as + * determined by {@link #isNotFullyInitializedReceiver(MethodTree)}. + *
          + * + * @param store initial store into which field values are inserted; it may not be empty + * @param classTree the class that contains {@code methodTree} + * @param methodTree the method or constructor tree + */ + // TODO: should field visibility matter? An access from outside the class might observe + // the declared type instead of a refined type. Issue a warning to alert users? + private void addInitialFieldValues(S store, ClassTree classTree, MethodTree methodTree) { + boolean isConstructor = TreeUtils.isConstructor(methodTree); + boolean isStaticMethod = + ElementUtils.isStatic(TreeUtils.elementFromDeclaration(methodTree)); + TypeElement classEle = TreeUtils.elementFromDeclaration(classTree); + for (FieldInitialValue fieldInitialValue : analysis.getFieldInitialValues()) { + VariableElement varEle = fieldInitialValue.fieldDecl.getField(); + boolean isStaticField = ElementUtils.isStatic(varEle); + if (isStaticMethod && !isStaticField) { + continue; + } + // Insert the value from the initializer of private final fields. + if (fieldInitialValue.initializer != null + // && varEle.getModifiers().contains(Modifier.PRIVATE) + && ElementUtils.isFinal(varEle) + && analysis.atypeFactory.isImmutable(ElementUtils.getType(varEle))) { + store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.initializer); + // The type from the initializer is always more specific than (or equal to) the + // declared type of the field. + // So, if there is an initializer, there is no point in inserting the declared type + // below. + continue; + } - boolean isFieldOfCurrentClass = varEle.getEnclosingElement().equals(classEle); - // Maybe insert the declared type: - if (!isConstructor) { - // If it's not a constructor, use the declared type if the receiver of the method is - // fully initialized. - boolean isInitializedReceiver = !isNotFullyInitializedReceiver(methodTree); - if (isInitializedReceiver && isFieldOfCurrentClass) { - store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); + boolean isFieldOfCurrentClass = varEle.getEnclosingElement().equals(classEle); + // Maybe insert the declared type: + if (!isConstructor) { + // If it's not a constructor, use the declared type if the receiver of the method is + // fully initialized. + boolean isInitializedReceiver = !isNotFullyInitializedReceiver(methodTree); + if (isInitializedReceiver && isFieldOfCurrentClass) { + store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); + } + } else { + // If it is a constructor, then only use the declared type if the field has been + // initialized. + if (fieldInitialValue.initializer != null && isFieldOfCurrentClass) { + store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); + } + } } - } else { - // If it is a constructor, then only use the declared type if the field has been - // initialized. - if (fieldInitialValue.initializer != null && isFieldOfCurrentClass) { - store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); + } + + /** + * Adds information about effectively final variables (from outer scopes) + * + * @param store the store to add to + * @param enclosingElement the enclosing element of the code we are analyzing + */ + private void addFinalLocalValues(S store, Element enclosingElement) { + // add information about effectively final variables (from outer scopes) + for (Map.Entry e : + analysis.atypeFactory.getFinalLocalValues().entrySet()) { + + VariableElement elem = e.getKey(); + + // TODO: There is a design flaw where the values of final local values leaks + // into other methods of the same class. For example, in + // class a { void b() {...} void c() {...} } + // final local values from b() would be visible in the store for c(), + // even though they should only be visible in b() and in classes + // defined inside the method body of b(). + // This is partly because GenericAnnotatedTypeFactory.performFlowAnalysis does not call + // itself recursively to analyze inner classes, but instead pops classes off of a queue, + // and the information about known final local values is stored by + // GenericAnnotatedTypeFactory.analyze in GenericAnnotatedTypeFactory.flowResult, which + // is visible to all classes in the queue regardless of their level of recursion. + + // We work around this here by ensuring that we only add a final local value to a + // method's store if that method is enclosed by the method where the local variables + // were declared. + + // Find the enclosing method of the element + Element enclosingMethodOfVariableDeclaration = elem.getEnclosingElement(); + + if (enclosingMethodOfVariableDeclaration != null) { + + // Now find all the enclosing methods of the code we are analyzing. If any one of + // them matches the above, then the final local variable value applies. + Element enclosingMethodOfCurrentMethod = enclosingElement; + + while (enclosingMethodOfCurrentMethod != null) { + if (enclosingMethodOfVariableDeclaration.equals( + enclosingMethodOfCurrentMethod)) { + LocalVariable l = new LocalVariable(elem); + store.insertValue(l, e.getValue()); + break; + } + + enclosingMethodOfCurrentMethod = + enclosingMethodOfCurrentMethod.getEnclosingElement(); + } + } } - } } - } - - /** - * Adds information about effectively final variables (from outer scopes) - * - * @param store the store to add to - * @param enclosingElement the enclosing element of the code we are analyzing - */ - private void addFinalLocalValues(S store, Element enclosingElement) { - // add information about effectively final variables (from outer scopes) - for (Map.Entry e : analysis.atypeFactory.getFinalLocalValues().entrySet()) { - - VariableElement elem = e.getKey(); - - // TODO: There is a design flaw where the values of final local values leaks - // into other methods of the same class. For example, in - // class a { void b() {...} void c() {...} } - // final local values from b() would be visible in the store for c(), - // even though they should only be visible in b() and in classes - // defined inside the method body of b(). - // This is partly because GenericAnnotatedTypeFactory.performFlowAnalysis does not call - // itself recursively to analyze inner classes, but instead pops classes off of a queue, - // and the information about known final local values is stored by - // GenericAnnotatedTypeFactory.analyze in GenericAnnotatedTypeFactory.flowResult, which - // is visible to all classes in the queue regardless of their level of recursion. - - // We work around this here by ensuring that we only add a final local value to a - // method's store if that method is enclosed by the method where the local variables - // were declared. - - // Find the enclosing method of the element - Element enclosingMethodOfVariableDeclaration = elem.getEnclosingElement(); - - if (enclosingMethodOfVariableDeclaration != null) { - - // Now find all the enclosing methods of the code we are analyzing. If any one of - // them matches the above, then the final local variable value applies. - Element enclosingMethodOfCurrentMethod = enclosingElement; - - while (enclosingMethodOfCurrentMethod != null) { - if (enclosingMethodOfVariableDeclaration.equals(enclosingMethodOfCurrentMethod)) { - LocalVariable l = new LocalVariable(elem); - store.insertValue(l, e.getValue()); - break; - } - enclosingMethodOfCurrentMethod = enclosingMethodOfCurrentMethod.getEnclosingElement(); + /** + * Returns true if the receiver of a method or constructor might not be fully initialized + * according to {@code analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree)}. + * + * @param methodDeclTree the declaration of the method or constructor + * @return true if the receiver of a method or constructor might not be fully initialized + * @see GenericAnnotatedTypeFactory#isNotFullyInitializedReceiver(MethodTree) + */ + @Pure + protected final boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { + return analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree); + } + + /** + * Add the information from all the preconditions of a method to the initial store in the method + * body. + * + * @param initialStore the initial store for the method body + * @param factory the type factory + * @param methodAst the AST for a method declaration + * @param methodDeclTree the declaration of the method; is a field of {@code methodAst} + * @param methodElement the element for the method + */ + protected void addInformationFromPreconditions( + S initialStore, + AnnotatedTypeFactory factory, + CFGMethod methodAst, + MethodTree methodDeclTree, + ExecutableElement methodElement) { + ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); + Set preconditions = contractsUtils.getPreconditions(methodElement); + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody( + stringExpr, methodDeclTree, analysis.checker); + for (Precondition p : preconditions) { + String stringExpr = p.expressionString; + AnnotationMirror annotation = + p.viewpointAdaptDependentTypeAnnotation( + analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); + JavaExpression exprJe; + try { + // TODO: currently, these expressions are parsed at the declaration (i.e. here) and + // for every use. this could be optimized to store the result the first time. + // (same for other annotations) + exprJe = + StringToJavaExpression.atMethodBody( + stringExpr, methodDeclTree, analysis.checker); + } catch (JavaExpressionParseException e) { + // Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration(). + continue; + } + initialStore.insertValuePermitNondeterministic(exprJe, annotation); } - } } - } - - /** - * Returns true if the receiver of a method or constructor might not be fully initialized - * according to {@code analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree)}. - * - * @param methodDeclTree the declaration of the method or constructor - * @return true if the receiver of a method or constructor might not be fully initialized - * @see GenericAnnotatedTypeFactory#isNotFullyInitializedReceiver(MethodTree) - */ - @Pure - protected final boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { - return analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree); - } - - /** - * Add the information from all the preconditions of a method to the initial store in the method - * body. - * - * @param initialStore the initial store for the method body - * @param factory the type factory - * @param methodAst the AST for a method declaration - * @param methodDeclTree the declaration of the method; is a field of {@code methodAst} - * @param methodElement the element for the method - */ - protected void addInformationFromPreconditions( - S initialStore, - AnnotatedTypeFactory factory, - CFGMethod methodAst, - MethodTree methodDeclTree, - ExecutableElement methodElement) { - ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); - Set preconditions = contractsUtils.getPreconditions(methodElement); - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker); - for (Precondition p : preconditions) { - String stringExpr = p.expressionString; - AnnotationMirror annotation = - p.viewpointAdaptDependentTypeAnnotation( - analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); - JavaExpression exprJe; - try { - // TODO: currently, these expressions are parsed at the declaration (i.e. here) and - // for every use. this could be optimized to store the result the first time. - // (same for other annotations) - exprJe = StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker); - } catch (JavaExpressionParseException e) { - // Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration(). - continue; - } - initialStore.insertValuePermitNondeterministic(exprJe, annotation); + + /** + * The default visitor returns the input information unchanged, or in the case of conditional + * input information, merged. + */ + @Override + public TransferResult visitNode(Node n, TransferInput in) { + V value = null; + + // TODO: handle implicit/explicit this and go to correct factory method + Tree tree = n.getTree(); + if (tree != null) { + if (TreeUtils.canHaveTypeAnnotation(tree)) { + value = getValueFromFactory(tree, n); + } + } + + return createTransferResult(value, in); } - } - - /** - * The default visitor returns the input information unchanged, or in the case of conditional - * input information, merged. - */ - @Override - public TransferResult visitNode(Node n, TransferInput in) { - V value = null; - - // TODO: handle implicit/explicit this and go to correct factory method - Tree tree = n.getTree(); - if (tree != null) { - if (TreeUtils.canHaveTypeAnnotation(tree)) { - value = getValueFromFactory(tree, n); - } + + /** + * Creates a TransferResult. + * + *

          This default implementation returns the input information unchanged, or in the case of + * conditional input information, merged. + * + * @param value the value; possibly null + * @param in the transfer input + * @return the input information, as a TransferResult + */ + @SideEffectFree + protected TransferResult createTransferResult(@Nullable V value, TransferInput in) { + if (in.containsTwoStores()) { + S thenStore = in.getThenStore(); + S elseStore = in.getElseStore(); + return new ConditionalTransferResult<>( + finishValue(value, thenStore, elseStore), thenStore, elseStore); + } else { + S store = in.getRegularStore(); + return new RegularTransferResult<>(finishValue(value, store), store); + } } - return createTransferResult(value, in); - } - - /** - * Creates a TransferResult. - * - *

          This default implementation returns the input information unchanged, or in the case of - * conditional input information, merged. - * - * @param value the value; possibly null - * @param in the transfer input - * @return the input information, as a TransferResult - */ - @SideEffectFree - protected TransferResult createTransferResult(@Nullable V value, TransferInput in) { - if (in.containsTwoStores()) { - S thenStore = in.getThenStore(); - S elseStore = in.getElseStore(); - return new ConditionalTransferResult<>( - finishValue(value, thenStore, elseStore), thenStore, elseStore); - } else { - S store = in.getRegularStore(); - return new RegularTransferResult<>(finishValue(value, store), store); + /** + * Creates a TransferResult just like the given one, but with the given value. + * + *

          This default implementation returns the input information unchanged, or in the case of + * conditional input information, merged. + * + * @param value the value; possibly null + * @param in the TransferResult to copy + * @return the input information + */ + @SideEffectFree + protected TransferResult recreateTransferResult( + @Nullable V value, TransferResult in) { + if (in.containsTwoStores()) { + S thenStore = in.getThenStore(); + S elseStore = in.getElseStore(); + return new ConditionalTransferResult<>( + finishValue(value, thenStore, elseStore), thenStore, elseStore); + } else { + S store = in.getRegularStore(); + return new RegularTransferResult<>(finishValue(value, store), store); + } } - } - - /** - * Creates a TransferResult just like the given one, but with the given value. - * - *

          This default implementation returns the input information unchanged, or in the case of - * conditional input information, merged. - * - * @param value the value; possibly null - * @param in the TransferResult to copy - * @return the input information - */ - @SideEffectFree - protected TransferResult recreateTransferResult( - @Nullable V value, TransferResult in) { - if (in.containsTwoStores()) { - S thenStore = in.getThenStore(); - S elseStore = in.getElseStore(); - return new ConditionalTransferResult<>( - finishValue(value, thenStore, elseStore), thenStore, elseStore); - } else { - S store = in.getRegularStore(); - return new RegularTransferResult<>(finishValue(value, store), store); + + @Override + public TransferResult visitClassName(ClassNameNode n, TransferInput in) { + // The tree underlying a class name is a type tree. + V value = null; + + Tree tree = n.getTree(); + if (tree != null) { + if (TreeUtils.canHaveTypeAnnotation(tree)) { + GenericAnnotatedTypeFactory> + factory = analysis.atypeFactory; + analysis.setCurrentTree(tree); + AnnotatedTypeMirror at = factory.getAnnotatedTypeFromTypeTree(tree); + analysis.setCurrentTree(null); + value = analysis.createAbstractValue(at); + } + } + + return createTransferResult(value, in); } - } - @Override - public TransferResult visitClassName(ClassNameNode n, TransferInput in) { - // The tree underlying a class name is a type tree. - V value = null; + @Override + public TransferResult visitFieldAccess(FieldAccessNode n, TransferInput p) { + S store = p.getRegularStore(); + V storeValue = store.getValue(n); + // look up value in factory, and take the more specific one + // TODO: handle cases, where this is not allowed (e.g. constructors in non-null type + // systems) + V factoryValue = getValueFromFactory(n.getTree(), n); + V value = moreSpecificValue(factoryValue, storeValue); + return new RegularTransferResult<>(finishValue(value, store), store); + } - Tree tree = n.getTree(); - if (tree != null) { - if (TreeUtils.canHaveTypeAnnotation(tree)) { - GenericAnnotatedTypeFactory> factory = - analysis.atypeFactory; - analysis.setCurrentTree(tree); - AnnotatedTypeMirror at = factory.getAnnotatedTypeFromTypeTree(tree); - analysis.setCurrentTree(null); - value = analysis.createAbstractValue(at); - } + @Override + public TransferResult visitArrayAccess(ArrayAccessNode n, TransferInput p) { + S store = p.getRegularStore(); + V storeValue = store.getValue(n); + // look up value in factory, and take the more specific one + V factoryValue = getValueFromFactory(n.getTree(), n); + V value = moreSpecificValue(factoryValue, storeValue); + return new RegularTransferResult<>(finishValue(value, store), store); } - return createTransferResult(value, in); - } - - @Override - public TransferResult visitFieldAccess(FieldAccessNode n, TransferInput p) { - S store = p.getRegularStore(); - V storeValue = store.getValue(n); - // look up value in factory, and take the more specific one - // TODO: handle cases, where this is not allowed (e.g. constructors in non-null type - // systems) - V factoryValue = getValueFromFactory(n.getTree(), n); - V value = moreSpecificValue(factoryValue, storeValue); - return new RegularTransferResult<>(finishValue(value, store), store); - } - - @Override - public TransferResult visitArrayAccess(ArrayAccessNode n, TransferInput p) { - S store = p.getRegularStore(); - V storeValue = store.getValue(n); - // look up value in factory, and take the more specific one - V factoryValue = getValueFromFactory(n.getTree(), n); - V value = moreSpecificValue(factoryValue, storeValue); - return new RegularTransferResult<>(finishValue(value, store), store); - } - - /** Use the most specific type information available according to the store. */ - @Override - public TransferResult visitLocalVariable(LocalVariableNode n, TransferInput in) { - S store = in.getRegularStore(); - V valueFromStore = store.getValue(n); - V valueFromFactory = getValueFromFactory(n.getTree(), n); - V value = moreSpecificValue(valueFromFactory, valueFromStore); - return new RegularTransferResult<>(finishValue(value, store), store); - } - - @Override - public TransferResult visitThis(ThisNode n, TransferInput in) { - S store = in.getRegularStore(); - V valueFromStore = store.getValue(n); - - V valueFromFactory = null; - V value = null; - Tree tree = n.getTree(); - if (tree != null && TreeUtils.canHaveTypeAnnotation(tree)) { - valueFromFactory = getValueFromFactory(tree, n); + /** Use the most specific type information available according to the store. */ + @Override + public TransferResult visitLocalVariable(LocalVariableNode n, TransferInput in) { + S store = in.getRegularStore(); + V valueFromStore = store.getValue(n); + V valueFromFactory = getValueFromFactory(n.getTree(), n); + V value = moreSpecificValue(valueFromFactory, valueFromStore); + return new RegularTransferResult<>(finishValue(value, store), store); } - if (valueFromFactory == null) { - value = valueFromStore; - } else { - value = moreSpecificValue(valueFromFactory, valueFromStore); + @Override + public TransferResult visitThis(ThisNode n, TransferInput in) { + S store = in.getRegularStore(); + V valueFromStore = store.getValue(n); + + V valueFromFactory = null; + V value = null; + Tree tree = n.getTree(); + if (tree != null && TreeUtils.canHaveTypeAnnotation(tree)) { + valueFromFactory = getValueFromFactory(tree, n); + } + + if (valueFromFactory == null) { + value = valueFromStore; + } else { + value = moreSpecificValue(valueFromFactory, valueFromStore); + } + + return new RegularTransferResult<>(finishValue(value, store), store); } - return new RegularTransferResult<>(finishValue(value, store), store); - } - - @Override - public TransferResult visitTernaryExpression( - TernaryExpressionNode n, TransferInput p) { - TransferResult result = super.visitTernaryExpression(n, p); - S thenStore = result.getThenStore(); - S elseStore = result.getElseStore(); - - V thenValue = p.getValueOfSubNode(n.getThenOperand()); - V elseValue = p.getValueOfSubNode(n.getElseOperand()); - V resultValue = null; - if (thenValue != null && elseValue != null) { - // If a conditional expression is a poly expression, then its Java type is the type of - // its context. (For example, the type of the conditional expression in `Object o = b - // ? "" : "";` is `Object`, not `String`.) So, use the Java type of the conditional - // expression and the annotations for each branch. - TypeMirror conditionalType = TreeUtils.typeOf(n.getTree()); - // The resulting abstract value is the merge of the 'then' and 'else' branch. - resultValue = thenValue.leastUpperBound(elseValue, conditionalType); + @Override + public TransferResult visitTernaryExpression( + TernaryExpressionNode n, TransferInput p) { + TransferResult result = super.visitTernaryExpression(n, p); + S thenStore = result.getThenStore(); + S elseStore = result.getElseStore(); + + V thenValue = p.getValueOfSubNode(n.getThenOperand()); + V elseValue = p.getValueOfSubNode(n.getElseOperand()); + V resultValue = null; + if (thenValue != null && elseValue != null) { + // If a conditional expression is a poly expression, then its Java type is the type of + // its context. (For example, the type of the conditional expression in `Object o = b + // ? "" : "";` is `Object`, not `String`.) So, use the Java type of the conditional + // expression and the annotations for each branch. + TypeMirror conditionalType = TreeUtils.typeOf(n.getTree()); + // The resulting abstract value is the merge of the 'then' and 'else' branch. + resultValue = thenValue.leastUpperBound(elseValue, conditionalType); + } + V finishedValue = finishValue(resultValue, thenStore, elseStore); + return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore); } - V finishedValue = finishValue(resultValue, thenStore, elseStore); - return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore); - } - - @Override - public TransferResult visitSwitchExpressionNode( - SwitchExpressionNode n, TransferInput vsTransferInput) { - return visitLocalVariable(n.getSwitchExpressionVar(), vsTransferInput); - } - - /** Reverse the role of the 'thenStore' and 'elseStore'. */ - @Override - public TransferResult visitConditionalNot(ConditionalNotNode n, TransferInput p) { - TransferResult result = super.visitConditionalNot(n, p); - S thenStore = result.getThenStore(); - S elseStore = result.getElseStore(); - return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore); - } - - @Override - public TransferResult visitEqualTo(EqualToNode n, TransferInput p) { - TransferResult res = super.visitEqualTo(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - V leftV = p.getValueOfSubNode(leftN); - V rightV = p.getValueOfSubNode(rightN); - - if (res.containsTwoStores() - && (NodeUtils.isConstantBoolean(leftN, false) - || NodeUtils.isConstantBoolean(rightN, false))) { - S thenStore = res.getElseStore(); - S elseStore = res.getThenStore(); - res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); + + @Override + public TransferResult visitSwitchExpressionNode( + SwitchExpressionNode n, TransferInput vsTransferInput) { + return visitLocalVariable(n.getSwitchExpressionVar(), vsTransferInput); } - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); - return res; - } - - @Override - public TransferResult visitNotEqual(NotEqualNode n, TransferInput p) { - TransferResult res = super.visitNotEqual(n, p); - - Node leftN = n.getLeftOperand(); - Node rightN = n.getRightOperand(); - V leftV = p.getValueOfSubNode(leftN); - V rightV = p.getValueOfSubNode(rightN); - - if (res.containsTwoStores() - && (NodeUtils.isConstantBoolean(leftN, true) - || NodeUtils.isConstantBoolean(rightN, true))) { - S thenStore = res.getElseStore(); - S elseStore = res.getThenStore(); - res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); + /** Reverse the role of the 'thenStore' and 'elseStore'. */ + @Override + public TransferResult visitConditionalNot(ConditionalNotNode n, TransferInput p) { + TransferResult result = super.visitConditionalNot(n, p); + S thenStore = result.getThenStore(); + S elseStore = result.getElseStore(); + return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore); } - // if annotations differ, use the one that is more precise for both - // sides (and add it to the store if possible) - res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); - res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); - - return res; - } - - /** - * Refine the annotation of {@code secondNode} if the annotation {@code secondValue} is less - * precise than {@code firstValue}. This is possible, if {@code secondNode} is an expression that - * is tracked by the store (e.g., a local variable or a field). Clients usually call this twice - * with {@code firstNode} and {@code secondNode} reversed, to refine each of them. - * - *

          Note that when overriding this method, when a new type is inserted into the store, {@link - * #splitAssignments} should be called, and the new type should be inserted into the store for - * each of the resulting nodes. - * - * @param firstNode the node that might be more precise - * @param secondNode the node whose type to possibly refine - * @param firstValue the abstract value that might be more precise - * @param secondValue the abstract value that might be less precise - * @param res the previous result - * @param notEqualTo if true, indicates that the logic is flipped (i.e., the information is added - * to the {@code elseStore} instead of the {@code thenStore}) for a not-equal comparison. - * @return the conditional transfer result (if information has been added), or {@code res} - */ - protected TransferResult strengthenAnnotationOfEqualTo( - TransferResult res, - Node firstNode, - Node secondNode, - V firstValue, - V secondValue, - boolean notEqualTo) { - if (firstValue != null) { - // Only need to insert if the second value is actually different. - if (!firstValue.equals(secondValue)) { - List secondParts = splitAssignments(secondNode); - for (Node secondPart : secondParts) { - JavaExpression secondInternal = JavaExpression.fromNode(secondPart); - if (!secondInternal.isDeterministic(analysis.atypeFactory)) { - continue; - } - if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { - S thenStore = res.getThenStore(); - S elseStore = res.getElseStore(); - if (notEqualTo) { - elseStore.insertValue(secondInternal, firstValue); - } else { - thenStore.insertValue(secondInternal, firstValue); - } - // To handle `(a = b = c) == x`, repeat for all insertable receivers of - // splitted assignments instead of returning. + @Override + public TransferResult visitEqualTo(EqualToNode n, TransferInput p) { + TransferResult res = super.visitEqualTo(n, p); + + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + V leftV = p.getValueOfSubNode(leftN); + V rightV = p.getValueOfSubNode(rightN); + + if (res.containsTwoStores() + && (NodeUtils.isConstantBoolean(leftN, false) + || NodeUtils.isConstantBoolean(rightN, false))) { + S thenStore = res.getElseStore(); + S elseStore = res.getThenStore(); res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); - } } - } - } - return res; - } - - /** - * Takes a node, and either returns the node itself again (as a singleton list), or if the node is - * an assignment node, returns the lhs and rhs (where splitAssignments is applied recursively to - * the rhs -- that is, it is possible that the rhs does not appear in the result, but rather its - * lhs and rhs do). - * - * @param node possibly an assignment node - * @return a list containing all the right- and left-hand sides in the given assignment node; it - * contains just the node itself if it is not an assignment) - */ - @SideEffectFree - protected List splitAssignments(Node node) { - if (node instanceof AssignmentNode) { - List result = new ArrayList<>(2); - AssignmentNode a = (AssignmentNode) node; - result.add(a.getTarget()); - result.addAll(splitAssignments(a.getExpression())); - return result; - } else { - return Collections.singletonList(node); + + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); + return res; } - } - @Override - public TransferResult visitAssignment(AssignmentNode n, TransferInput in) { - Node lhs = n.getTarget(); - Node rhs = n.getExpression(); + @Override + public TransferResult visitNotEqual(NotEqualNode n, TransferInput p) { + TransferResult res = super.visitNotEqual(n, p); - V rhsValue = in.getValueOfSubNode(rhs); + Node leftN = n.getLeftOperand(); + Node rightN = n.getRightOperand(); + V leftV = p.getValueOfSubNode(leftN); + V rightV = p.getValueOfSubNode(rightN); - /* NO-AFU - if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) { - // Fields defined in interfaces are LocalVariableNodes with ElementKind of FIELD. - if (lhs instanceof FieldAccessNode - || (lhs instanceof LocalVariableNode - && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.FIELD)) { - // Updates inferred field type - analysis.atypeFactory.getWholeProgramInference().updateFromFieldAssignment(lhs, rhs); - } else if (lhs instanceof LocalVariableNode - && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) { - // lhs is a formal parameter of some method - VariableElement param = ((LocalVariableNode) lhs).getElement(); - analysis - .atypeFactory - .getWholeProgramInference() - .updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param); - } - */ + if (res.containsTwoStores() + && (NodeUtils.isConstantBoolean(leftN, true) + || NodeUtils.isConstantBoolean(rightN, true))) { + S thenStore = res.getElseStore(); + S elseStore = res.getThenStore(); + res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); + } + + // if annotations differ, use the one that is more precise for both + // sides (and add it to the store if possible) + res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); + res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); - if (n.isSynthetic() && in.containsTwoStores()) { - // This is a synthetic assignment node created for a ternary expression. In this case - // the `then` and `else` store are not merged. - S thenStore = in.getThenStore(); - S elseStore = in.getElseStore(); - processCommonAssignment(in, lhs, rhs, thenStore, rhsValue); - processCommonAssignment(in, lhs, rhs, elseStore, rhsValue); - return new ConditionalTransferResult<>( - finishValue(rhsValue, thenStore, elseStore), thenStore, elseStore); - } else { - S store = in.getRegularStore(); - processCommonAssignment(in, lhs, rhs, store, rhsValue); - return new RegularTransferResult<>(finishValue(rhsValue, store), store); + return res; } - } - @Override - public TransferResult visitReturn(ReturnNode n, TransferInput p) { - TransferResult result = super.visitReturn(n, p); + /** + * Refine the annotation of {@code secondNode} if the annotation {@code secondValue} is less + * precise than {@code firstValue}. This is possible, if {@code secondNode} is an expression + * that is tracked by the store (e.g., a local variable or a field). Clients usually call this + * twice with {@code firstNode} and {@code secondNode} reversed, to refine each of them. + * + *

          Note that when overriding this method, when a new type is inserted into the store, {@link + * #splitAssignments} should be called, and the new type should be inserted into the store for + * each of the resulting nodes. + * + * @param firstNode the node that might be more precise + * @param secondNode the node whose type to possibly refine + * @param firstValue the abstract value that might be more precise + * @param secondValue the abstract value that might be less precise + * @param res the previous result + * @param notEqualTo if true, indicates that the logic is flipped (i.e., the information is + * added to the {@code elseStore} instead of the {@code thenStore}) for a not-equal + * comparison. + * @return the conditional transfer result (if information has been added), or {@code res} + */ + protected TransferResult strengthenAnnotationOfEqualTo( + TransferResult res, + Node firstNode, + Node secondNode, + V firstValue, + V secondValue, + boolean notEqualTo) { + if (firstValue != null) { + // Only need to insert if the second value is actually different. + if (!firstValue.equals(secondValue)) { + List secondParts = splitAssignments(secondNode); + for (Node secondPart : secondParts) { + JavaExpression secondInternal = JavaExpression.fromNode(secondPart); + if (!secondInternal.isDeterministic(analysis.atypeFactory)) { + continue; + } + if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { + S thenStore = res.getThenStore(); + S elseStore = res.getElseStore(); + if (notEqualTo) { + elseStore.insertValue(secondInternal, firstValue); + } else { + thenStore.insertValue(secondInternal, firstValue); + } + // To handle `(a = b = c) == x`, repeat for all insertable receivers of + // splitted assignments instead of returning. + res = + new ConditionalTransferResult<>( + res.getResultValue(), thenStore, elseStore); + } + } + } + } + return res; + } - /* NO-AFU - if (shouldPerformWholeProgramInference(n.getTree())) { - // Retrieves class containing the method - ClassTree classTree = analysis.getContainingClass(n.getTree()); - // classTree is null e.g. if this is a return statement in a lambda. - if (classTree == null) { - return result; - } - ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromDeclaration(classTree); + /** + * Takes a node, and either returns the node itself again (as a singleton list), or if the node + * is an assignment node, returns the lhs and rhs (where splitAssignments is applied recursively + * to the rhs -- that is, it is possible that the rhs does not appear in the result, but rather + * its lhs and rhs do). + * + * @param node possibly an assignment node + * @return a list containing all the right- and left-hand sides in the given assignment node; it + * contains just the node itself if it is not an assignment) + */ + @SideEffectFree + protected List splitAssignments(Node node) { + if (node instanceof AssignmentNode) { + List result = new ArrayList<>(2); + AssignmentNode a = (AssignmentNode) node; + result.add(a.getTarget()); + result.addAll(splitAssignments(a.getExpression())); + return result; + } else { + return Collections.singletonList(node); + } + } - ExecutableElement methodElem = - TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree())); + @Override + public TransferResult visitAssignment(AssignmentNode n, TransferInput in) { + Node lhs = n.getTarget(); + Node rhs = n.getExpression(); + + V rhsValue = in.getValueOfSubNode(rhs); + + /* NO-AFU + if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) { + // Fields defined in interfaces are LocalVariableNodes with ElementKind of FIELD. + if (lhs instanceof FieldAccessNode + || (lhs instanceof LocalVariableNode + && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.FIELD)) { + // Updates inferred field type + analysis.atypeFactory.getWholeProgramInference().updateFromFieldAssignment(lhs, rhs); + } else if (lhs instanceof LocalVariableNode + && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) { + // lhs is a formal parameter of some method + VariableElement param = ((LocalVariableNode) lhs).getElement(); + analysis + .atypeFactory + .getWholeProgramInference() + .updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param); + } + */ + + if (n.isSynthetic() && in.containsTwoStores()) { + // This is a synthetic assignment node created for a ternary expression. In this case + // the `then` and `else` store are not merged. + S thenStore = in.getThenStore(); + S elseStore = in.getElseStore(); + processCommonAssignment(in, lhs, rhs, thenStore, rhsValue); + processCommonAssignment(in, lhs, rhs, elseStore, rhsValue); + return new ConditionalTransferResult<>( + finishValue(rhsValue, thenStore, elseStore), thenStore, elseStore); + } else { + S store = in.getRegularStore(); + processCommonAssignment(in, lhs, rhs, store, rhsValue); + return new RegularTransferResult<>(finishValue(rhsValue, store), store); + } + } + + @Override + public TransferResult visitReturn(ReturnNode n, TransferInput p) { + TransferResult result = super.visitReturn(n, p); + + /* NO-AFU + if (shouldPerformWholeProgramInference(n.getTree())) { + // Retrieves class containing the method + ClassTree classTree = analysis.getContainingClass(n.getTree()); + // classTree is null e.g. if this is a return statement in a lambda. + if (classTree == null) { + return result; + } + ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromDeclaration(classTree); + + ExecutableElement methodElem = + TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree())); + + Map overriddenMethods = + AnnotatedTypes.overriddenMethods( + analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem); - Map overriddenMethods = - AnnotatedTypes.overriddenMethods( - analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem); + // Updates the inferred return type of the method + analysis + .atypeFactory + .getWholeProgramInference() + .updateFromReturn( + n, classSymbol, analysis.getContainingMethod(n.getTree()), overriddenMethods); + } + */ - // Updates the inferred return type of the method - analysis - .atypeFactory - .getWholeProgramInference() - .updateFromReturn( - n, classSymbol, analysis.getContainingMethod(n.getTree()), overriddenMethods); + return result; } - */ - return result; - } - - @Override - public TransferResult visitLambdaResultExpression( - LambdaResultExpressionNode n, TransferInput in) { - return n.getResult().accept(this, in); - } - - /** - * Determine abstract value of right-hand side and update the store accordingly. - * - * @param in the store(s) before the assignment - * @param lhs the left-hand side of the assignment - * @param rhs the right-hand side of the assignment - * @param store the regular input store (from {@code in}) - * @param rhsValue the value of the right-hand side of the assignment - */ - protected void processCommonAssignment( - TransferInput in, Node lhs, Node rhs, S store, V rhsValue) { - - // update information in the store - store.updateForAssignment(lhs, rhsValue); - } - - @Override - public TransferResult visitObjectCreation(ObjectCreationNode n, TransferInput p) { - /* NO-AFU - if (shouldPerformWholeProgramInference(n.getTree())) { - NewClassTree newClassTree = n.getTree(); - // Can't infer annotations on an anonymous constructor, so use the super constructor. - ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); - if (newClassTree.getClassBody() == null || !TreeUtils.hasSyntheticArgument(newClassTree)) { - // TODO: WPI could be changed to handle the synthetic argument, but for now just - // don't infer annotations for those new class trees. - analysis - .atypeFactory - .getWholeProgramInference() - .updateFromObjectCreation(n, constructorElt, p.getRegularStore()); - } + @Override + public TransferResult visitLambdaResultExpression( + LambdaResultExpressionNode n, TransferInput in) { + return n.getResult().accept(this, in); } - */ - NewClassTree newClassTree = n.getTree(); - ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); - S store = p.getRegularStore(); - // add new information based on postcondition - processPostconditions(n, store, constructorElt, newClassTree); - return super.visitObjectCreation(n, p); - } - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode n, TransferInput in) { + /** + * Determine abstract value of right-hand side and update the store accordingly. + * + * @param in the store(s) before the assignment + * @param lhs the left-hand side of the assignment + * @param rhs the right-hand side of the assignment + * @param store the regular input store (from {@code in}) + * @param rhsValue the value of the right-hand side of the assignment + */ + protected void processCommonAssignment( + TransferInput in, Node lhs, Node rhs, S store, V rhsValue) { + + // update information in the store + store.updateForAssignment(lhs, rhsValue); + } - S store = in.getRegularStore(); - ExecutableElement method = n.getTarget().getMethod(); + @Override + public TransferResult visitObjectCreation(ObjectCreationNode n, TransferInput p) { + /* NO-AFU + if (shouldPerformWholeProgramInference(n.getTree())) { + NewClassTree newClassTree = n.getTree(); + // Can't infer annotations on an anonymous constructor, so use the super constructor. + ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); + if (newClassTree.getClassBody() == null || !TreeUtils.hasSyntheticArgument(newClassTree)) { + // TODO: WPI could be changed to handle the synthetic argument, but for now just + // don't infer annotations for those new class trees. + analysis + .atypeFactory + .getWholeProgramInference() + .updateFromObjectCreation(n, constructorElt, p.getRegularStore()); + } + } + */ + NewClassTree newClassTree = n.getTree(); + ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); + S store = p.getRegularStore(); + // add new information based on postcondition + processPostconditions(n, store, constructorElt, newClassTree); + return super.visitObjectCreation(n, p); + } - /* NO-AFU - // Perform WPI before the store has been side-effected. - if (shouldPerformWholeProgramInference(n.getTree(), method)) { - // Updates the inferred parameter types of the invoked method. - analysis.atypeFactory - .getWholeProgramInference() - .updateFromMethodInvocation(n, method, store); - } - */ + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode n, TransferInput in) { + + S store = in.getRegularStore(); + ExecutableElement method = n.getTarget().getMethod(); - ExpressionTree invocationTree = n.getTree(); + /* NO-AFU + // Perform WPI before the store has been side-effected. + if (shouldPerformWholeProgramInference(n.getTree(), method)) { + // Updates the inferred parameter types of the invoked method. + analysis.atypeFactory + .getWholeProgramInference() + .updateFromMethodInvocation(n, method, store); + } + */ - // Determine the abstract value for the method call. - // look up the call's value from factory - V factoryValue = (invocationTree == null) ? null : getValueFromFactory(invocationTree, n); - // look up the call's value in the store (if possible) - V storeValue = store.getValue(n); - V resValue = moreSpecificValue(factoryValue, storeValue); + ExpressionTree invocationTree = n.getTree(); - store.updateForMethodCall(n, analysis.atypeFactory, resValue); + // Determine the abstract value for the method call. + // look up the call's value from factory + V factoryValue = (invocationTree == null) ? null : getValueFromFactory(invocationTree, n); + // look up the call's value in the store (if possible) + V storeValue = store.getValue(n); + V resValue = moreSpecificValue(factoryValue, storeValue); - // add new information based on postcondition - processPostconditions(n, store, method, invocationTree); + store.updateForMethodCall(n, analysis.atypeFactory, resValue); - if (TypesUtils.isBooleanType(method.getReturnType())) { - S thenStore = store; - S elseStore = thenStore.copy(); + // add new information based on postcondition + processPostconditions(n, store, method, invocationTree); - // add new information based on conditional postcondition - processConditionalPostconditions(n, method, invocationTree, thenStore, elseStore); + if (TypesUtils.isBooleanType(method.getReturnType())) { + S thenStore = store; + S elseStore = thenStore.copy(); - return new ConditionalTransferResult<>( - finishValue(resValue, thenStore, elseStore), thenStore, elseStore); - } else { - return new RegularTransferResult<>(finishValue(resValue, store), store); + // add new information based on conditional postcondition + processConditionalPostconditions(n, method, invocationTree, thenStore, elseStore); + + return new ConditionalTransferResult<>( + finishValue(resValue, thenStore, elseStore), thenStore, elseStore); + } else { + return new RegularTransferResult<>(finishValue(resValue, store), store); + } } - } - - @Override - public TransferResult visitDeconstructorPattern( - DeconstructorPatternNode n, TransferInput in) { - // TODO: Implement getting the type of a DeconstructorPatternTree. - V value = null; - return createTransferResult(value, in); - } - - @Override - public TransferResult visitInstanceOf(InstanceOfNode node, TransferInput in) { - TransferResult result = super.visitInstanceOf(node, in); - for (LocalVariableNode bindingVar : node.getBindingVariables()) { - JavaExpression expr = JavaExpression.fromNode(bindingVar); - AnnotatedTypeMirror expType = - analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); - for (AnnotationMirror anno : expType.getAnnotations()) { - in.getRegularStore().insertOrRefine(expr, anno); - } + + @Override + public TransferResult visitDeconstructorPattern( + DeconstructorPatternNode n, TransferInput in) { + // TODO: Implement getting the type of a DeconstructorPatternTree. + V value = null; + return createTransferResult(value, in); } - // The "reference type" is the type after "instanceof". - Tree refTypeTree = node.getTree().getType(); - if (refTypeTree != null && refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { - AnnotatedTypeMirror refType = analysis.atypeFactory.getAnnotatedType(refTypeTree); - AnnotatedTypeMirror expType = - analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); - if (analysis.atypeFactory.getTypeHierarchy().isSubtype(refType, expType) - && !refType.getAnnotations().equals(expType.getAnnotations()) - && !expType.getAnnotations().isEmpty()) { - JavaExpression expr = JavaExpression.fromTree(node.getTree().getExpression()); - for (AnnotationMirror anno : refType.getAnnotations()) { - in.getRegularStore().insertOrRefine(expr, anno); + @Override + public TransferResult visitInstanceOf(InstanceOfNode node, TransferInput in) { + TransferResult result = super.visitInstanceOf(node, in); + for (LocalVariableNode bindingVar : node.getBindingVariables()) { + JavaExpression expr = JavaExpression.fromNode(bindingVar); + AnnotatedTypeMirror expType = + analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); + for (AnnotationMirror anno : expType.getAnnotations()) { + in.getRegularStore().insertOrRefine(expr, anno); + } + } + + // The "reference type" is the type after "instanceof". + Tree refTypeTree = node.getTree().getType(); + if (refTypeTree != null && refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { + AnnotatedTypeMirror refType = analysis.atypeFactory.getAnnotatedType(refTypeTree); + AnnotatedTypeMirror expType = + analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); + if (analysis.atypeFactory.getTypeHierarchy().isSubtype(refType, expType) + && !refType.getAnnotations().equals(expType.getAnnotations()) + && !expType.getAnnotations().isEmpty()) { + JavaExpression expr = JavaExpression.fromTree(node.getTree().getExpression()); + for (AnnotationMirror anno : refType.getAnnotations()) { + in.getRegularStore().insertOrRefine(expr, anno); + } + return new RegularTransferResult<>(result.getResultValue(), in.getRegularStore()); + } } - return new RegularTransferResult<>(result.getResultValue(), in.getRegularStore()); + return result; + } + + /* NO-AFU + * Returns true if whole-program inference should be performed. If the tree is in the scope of + * a @SuppressWarnings, then this method returns false. + * + * @param tree a tree + * @return whether to perform whole-program inference on the tree + */ + /* NO-AFU + protected boolean shouldPerformWholeProgramInference(Tree tree) { + TreePath path = this.analysis.atypeFactory.getPath(tree); + return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(path, "")); + } + */ + + /* NO-AFU + * Returns true if whole-program inference should be performed. If the expressionTree or lhsTree + * is in the scope of a @SuppressWarnings, then this method returns false. + * + * @param expressionTree the right-hand side of an assignment + * @param lhsTree the left-hand side of an assignment + * @return whether to perform whole-program inference + */ + /* NO-AFU + protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { + // Check that infer is true and the tree isn't in scope of a @SuppressWarnings + // before calling InternalUtils.symbol(lhs). + if (!shouldPerformWholeProgramInference(expressionTree)) { + return false; } + Element elt = TreeUtils.elementFromTree(lhsTree); + return !analysis.checker.shouldSuppressWarnings(elt, ""); + } + */ + + /* NO-AFU + * Returns true if whole-program inference should be performed. If the tree or element is in the + * scope of a @SuppressWarnings, then this method returns false. + * + * @param tree a tree + * @param elt its element + * @return whether to perform whole-program inference + */ + /* NO-AFU + private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) { + return shouldPerformWholeProgramInference(tree) + && !analysis.checker.shouldSuppressWarnings(elt, ""); } - return result; - } - - /* NO-AFU - * Returns true if whole-program inference should be performed. If the tree is in the scope of - * a @SuppressWarnings, then this method returns false. - * - * @param tree a tree - * @return whether to perform whole-program inference on the tree - */ - /* NO-AFU - protected boolean shouldPerformWholeProgramInference(Tree tree) { - TreePath path = this.analysis.atypeFactory.getPath(tree); - return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(path, "")); - } - */ - - /* NO-AFU - * Returns true if whole-program inference should be performed. If the expressionTree or lhsTree - * is in the scope of a @SuppressWarnings, then this method returns false. - * - * @param expressionTree the right-hand side of an assignment - * @param lhsTree the left-hand side of an assignment - * @return whether to perform whole-program inference - */ - /* NO-AFU - protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { - // Check that infer is true and the tree isn't in scope of a @SuppressWarnings - // before calling InternalUtils.symbol(lhs). - if (!shouldPerformWholeProgramInference(expressionTree)) { - return false; + */ + + /** + * Add information from the postconditions of a method to the store after an invocation. + * + * @param invocationNode a method call or an object creation + * @param store a store; is side-effected by this method + * @param executableElement the method or constructor being called + * @param invocationTree the tree for the method call or for the object creation + */ + protected void processPostconditions( + Node invocationNode, + S store, + ExecutableElement executableElement, + ExpressionTree invocationTree) { + ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); + Set postconditions = contractsUtils.getPostconditions(executableElement); + processPostconditionsAndConditionalPostconditions( + invocationNode, invocationTree, store, null, postconditions); } - Element elt = TreeUtils.elementFromTree(lhsTree); - return !analysis.checker.shouldSuppressWarnings(elt, ""); - } - */ - - /* NO-AFU - * Returns true if whole-program inference should be performed. If the tree or element is in the - * scope of a @SuppressWarnings, then this method returns false. - * - * @param tree a tree - * @param elt its element - * @return whether to perform whole-program inference - */ - /* NO-AFU - private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) { - return shouldPerformWholeProgramInference(tree) - && !analysis.checker.shouldSuppressWarnings(elt, ""); - } - */ - - /** - * Add information from the postconditions of a method to the store after an invocation. - * - * @param invocationNode a method call or an object creation - * @param store a store; is side-effected by this method - * @param executableElement the method or constructor being called - * @param invocationTree the tree for the method call or for the object creation - */ - protected void processPostconditions( - Node invocationNode, - S store, - ExecutableElement executableElement, - ExpressionTree invocationTree) { - ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); - Set postconditions = contractsUtils.getPostconditions(executableElement); - processPostconditionsAndConditionalPostconditions( - invocationNode, invocationTree, store, null, postconditions); - } - - /** - * Add information from the conditional postconditions of a method to the stores after an - * invocation. - * - * @param invocationNode a method call - * @param methodElement the method being called - * @param invocationTree the tree for the method call - * @param thenStore the "then" store; is side-effected by this method - * @param elseStore the "else" store; is side-effected by this method - */ - protected void processConditionalPostconditions( - MethodInvocationNode invocationNode, - ExecutableElement methodElement, - ExpressionTree invocationTree, - S thenStore, - S elseStore) { - ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); - Set conditionalPostconditions = - contractsUtils.getConditionalPostconditions(methodElement); - processPostconditionsAndConditionalPostconditions( - invocationNode, invocationTree, thenStore, elseStore, conditionalPostconditions); - } - - /** - * Add information from the postconditions and conditional postconditions of a method to the - * stores after an invocation. - * - * @param invocationNode a method call node or an object creation node - * @param invocationTree the tree for the method call or for the object creation - * @param thenStore the "then" store; is side-effected by this method - * @param elseStore the "else" store; is side-effected by this method - * @param postconditions the postconditions - */ - private void processPostconditionsAndConditionalPostconditions( - Node invocationNode, - ExpressionTree invocationTree, - S thenStore, - S elseStore, - Set postconditions) { - - StringToJavaExpression stringToJavaExpr = null; - if (invocationNode instanceof MethodInvocationNode) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodInvocation( - stringExpr, (MethodInvocationNode) invocationNode, analysis.checker); - } else if (invocationNode instanceof ObjectCreationNode) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atConstructorInvocation( - stringExpr, (NewClassTree) invocationTree, analysis.checker); - } else { - throw new BugInCF( - "CFAbstractTransfer.processPostconditionsAndConditionalPostconditions" - + " expects a MethodInvocationNode or ObjectCreationNode argument;" - + " received a " - + invocationNode.getClass().getSimpleName()); + + /** + * Add information from the conditional postconditions of a method to the stores after an + * invocation. + * + * @param invocationNode a method call + * @param methodElement the method being called + * @param invocationTree the tree for the method call + * @param thenStore the "then" store; is side-effected by this method + * @param elseStore the "else" store; is side-effected by this method + */ + protected void processConditionalPostconditions( + MethodInvocationNode invocationNode, + ExecutableElement methodElement, + ExpressionTree invocationTree, + S thenStore, + S elseStore) { + ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); + Set conditionalPostconditions = + contractsUtils.getConditionalPostconditions(methodElement); + processPostconditionsAndConditionalPostconditions( + invocationNode, invocationTree, thenStore, elseStore, conditionalPostconditions); } - for (Contract p : postconditions) { - // Viewpoint-adapt to the method use (the call site). - AnnotationMirror anno = - p.viewpointAdaptDependentTypeAnnotation( - analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); - - String expressionString = p.expressionString; - try { - JavaExpression je = stringToJavaExpr.toJavaExpression(expressionString); - - // "insertOrRefine" is called so that the postcondition information is added to any - // existing information rather than replacing it. If the called method is not - // side-effect-free, then the values that might have been changed by the method call - // are removed from the store before this method is called. - if (p.kind == Contract.Kind.CONDITIONALPOSTCONDITION) { - if (((ConditionalPostcondition) p).resultValue) { - thenStore.insertOrRefinePermitNondeterministic(je, anno); - } else { - elseStore.insertOrRefinePermitNondeterministic(je, anno); - } + /** + * Add information from the postconditions and conditional postconditions of a method to the + * stores after an invocation. + * + * @param invocationNode a method call node or an object creation node + * @param invocationTree the tree for the method call or for the object creation + * @param thenStore the "then" store; is side-effected by this method + * @param elseStore the "else" store; is side-effected by this method + * @param postconditions the postconditions + */ + private void processPostconditionsAndConditionalPostconditions( + Node invocationNode, + ExpressionTree invocationTree, + S thenStore, + S elseStore, + Set postconditions) { + + StringToJavaExpression stringToJavaExpr = null; + if (invocationNode instanceof MethodInvocationNode) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodInvocation( + stringExpr, + (MethodInvocationNode) invocationNode, + analysis.checker); + } else if (invocationNode instanceof ObjectCreationNode) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atConstructorInvocation( + stringExpr, (NewClassTree) invocationTree, analysis.checker); } else { - thenStore.insertOrRefinePermitNondeterministic(je, anno); + throw new BugInCF( + "CFAbstractTransfer.processPostconditionsAndConditionalPostconditions" + + " expects a MethodInvocationNode or ObjectCreationNode argument;" + + " received a " + + invocationNode.getClass().getSimpleName()); } - } catch (JavaExpressionParseException e) { - // report errors here - if (e.isFlowParseError()) { - Object[] args = new Object[e.args.length + 1]; - args[0] = - ElementUtils.getSimpleSignature( - (ExecutableElement) TreeUtils.elementFromUse(invocationTree)); - System.arraycopy(e.args, 0, args, 1, e.args.length); - analysis.checker.reportError(invocationTree, "flowexpr.parse.error.postcondition", args); - } else { - analysis.checker.report(invocationTree, e.getDiagMessage()); + + for (Contract p : postconditions) { + // Viewpoint-adapt to the method use (the call site). + AnnotationMirror anno = + p.viewpointAdaptDependentTypeAnnotation( + analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); + + String expressionString = p.expressionString; + try { + JavaExpression je = stringToJavaExpr.toJavaExpression(expressionString); + + // "insertOrRefine" is called so that the postcondition information is added to any + // existing information rather than replacing it. If the called method is not + // side-effect-free, then the values that might have been changed by the method call + // are removed from the store before this method is called. + if (p.kind == Contract.Kind.CONDITIONALPOSTCONDITION) { + if (((ConditionalPostcondition) p).resultValue) { + thenStore.insertOrRefinePermitNondeterministic(je, anno); + } else { + elseStore.insertOrRefinePermitNondeterministic(je, anno); + } + } else { + thenStore.insertOrRefinePermitNondeterministic(je, anno); + } + } catch (JavaExpressionParseException e) { + // report errors here + if (e.isFlowParseError()) { + Object[] args = new Object[e.args.length + 1]; + args[0] = + ElementUtils.getSimpleSignature( + (ExecutableElement) TreeUtils.elementFromUse(invocationTree)); + System.arraycopy(e.args, 0, args, 1, e.args.length); + analysis.checker.reportError( + invocationTree, "flowexpr.parse.error.postcondition", args); + } else { + analysis.checker.report(invocationTree, e.getDiagMessage()); + } + } } - } } - } - - /** A case produces no value, but it may imply some facts about switch selector expression. */ - @Override - public TransferResult visitCase(CaseNode n, TransferInput in) { - S store = in.getRegularStore(); - TransferResult lubResult = null; - // Case operands are the case constants. For example, A, B and C in case A, B, C:. - // This method refines the type of the selector expression and the synthetic variable that - // represents the selector expression to the type of the case constant if it is more - // precise. - // If there are multiple case constants then a new store is created for each case constant - // and then they are lubbed. This method returns the lubbed result. - for (Node caseOperand : n.getCaseOperands()) { - TransferResult result = - new ConditionalTransferResult<>( - finishValue(null, store), in.getThenStore().copy(), in.getElseStore().copy(), false); - V caseValue = in.getValueOfSubNode(caseOperand); - AssignmentNode assign = n.getSwitchOperand(); - V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget())); - result = - strengthenAnnotationOfEqualTo( - result, caseOperand, assign.getExpression(), caseValue, switchValue, false); - // Update value of switch temporary variable - result = - strengthenAnnotationOfEqualTo( - result, caseOperand, assign.getTarget(), caseValue, switchValue, false); - - // Lub the result of one case label constant with the result of the others. - if (lubResult == null) { - lubResult = result; - } else { - S thenStore = lubResult.getThenStore().leastUpperBound(result.getThenStore()); - S elseStore = lubResult.getElseStore().leastUpperBound(result.getElseStore()); - lubResult = - new ConditionalTransferResult<>( - null, thenStore, elseStore, lubResult.storeChanged() || result.storeChanged()); - } + + /** A case produces no value, but it may imply some facts about switch selector expression. */ + @Override + public TransferResult visitCase(CaseNode n, TransferInput in) { + S store = in.getRegularStore(); + TransferResult lubResult = null; + // Case operands are the case constants. For example, A, B and C in case A, B, C:. + // This method refines the type of the selector expression and the synthetic variable that + // represents the selector expression to the type of the case constant if it is more + // precise. + // If there are multiple case constants then a new store is created for each case constant + // and then they are lubbed. This method returns the lubbed result. + for (Node caseOperand : n.getCaseOperands()) { + TransferResult result = + new ConditionalTransferResult<>( + finishValue(null, store), + in.getThenStore().copy(), + in.getElseStore().copy(), + false); + V caseValue = in.getValueOfSubNode(caseOperand); + AssignmentNode assign = n.getSwitchOperand(); + V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget())); + result = + strengthenAnnotationOfEqualTo( + result, + caseOperand, + assign.getExpression(), + caseValue, + switchValue, + false); + // Update value of switch temporary variable + result = + strengthenAnnotationOfEqualTo( + result, caseOperand, assign.getTarget(), caseValue, switchValue, false); + + // Lub the result of one case label constant with the result of the others. + if (lubResult == null) { + lubResult = result; + } else { + S thenStore = lubResult.getThenStore().leastUpperBound(result.getThenStore()); + S elseStore = lubResult.getElseStore().leastUpperBound(result.getElseStore()); + lubResult = + new ConditionalTransferResult<>( + null, + thenStore, + elseStore, + lubResult.storeChanged() || result.storeChanged()); + } + } + return lubResult; } - return lubResult; - } - - /** - * In a cast {@code (@A C) e} of some expression {@code e} to a new type {@code @A C}, we usually - * take the annotation of the type {@code C} (here {@code @A}). However, if the inferred - * annotation of {@code e} is more precise, we keep that one. - */ - // @Override - // public TransferResult visitTypeCast(TypeCastNode n, - // TransferInput p) { - // TransferResult result = super.visitTypeCast(n, p); - // V value = result.getResultValue(); - // V operandValue = p.getValueOfSubNode(n.getOperand()); - // // Normally we take the value of the type cast node. However, if the old - // // flow-refined value was more precise, we keep that value. - // V resultValue = moreSpecificValue(value, operandValue); - // result.setResultValue(resultValue); - // return result; - // } - - /** - * Returns the abstract value of {@code (value1, value2)} that is more specific. If the two are - * incomparable, then {@code value1} is returned. - * - * @param value1 an abstract value - * @param value2 another abstract value - * @return the more specific value of the two parameters, or, if they are incomparable, {@code - * value1} - */ - @Pure - public V moreSpecificValue(V value1, V value2) { - if (value1 == null) { - return value2; + + /** + * In a cast {@code (@A C) e} of some expression {@code e} to a new type {@code @A C}, we + * usually take the annotation of the type {@code C} (here {@code @A}). However, if the inferred + * annotation of {@code e} is more precise, we keep that one. + */ + // @Override + // public TransferResult visitTypeCast(TypeCastNode n, + // TransferInput p) { + // TransferResult result = super.visitTypeCast(n, p); + // V value = result.getResultValue(); + // V operandValue = p.getValueOfSubNode(n.getOperand()); + // // Normally we take the value of the type cast node. However, if the old + // // flow-refined value was more precise, we keep that value. + // V resultValue = moreSpecificValue(value, operandValue); + // result.setResultValue(resultValue); + // return result; + // } + + /** + * Returns the abstract value of {@code (value1, value2)} that is more specific. If the two are + * incomparable, then {@code value1} is returned. + * + * @param value1 an abstract value + * @param value2 another abstract value + * @return the more specific value of the two parameters, or, if they are incomparable, {@code + * value1} + */ + @Pure + public V moreSpecificValue(V value1, V value2) { + if (value1 == null) { + return value2; + } + if (value2 == null) { + return value1; + } + return value1.mostSpecific(value2, value1); } - if (value2 == null) { - return value1; + + @Override + public TransferResult visitVariableDeclaration( + VariableDeclarationNode n, TransferInput p) { + S store = p.getRegularStore(); + return new RegularTransferResult<>(finishValue(null, store), store); } - return value1.mostSpecific(value2, value1); - } - - @Override - public TransferResult visitVariableDeclaration( - VariableDeclarationNode n, TransferInput p) { - S store = p.getRegularStore(); - return new RegularTransferResult<>(finishValue(null, store), store); - } - - @Override - public TransferResult visitWideningConversion( - WideningConversionNode n, TransferInput p) { - TransferResult result = super.visitWideningConversion(n, p); - // Combine annotations from the operand with the wide type - V operandValue = p.getValueOfSubNode(n.getOperand()); - V widenedValue = getWidenedValue(n.getType(), operandValue); - result.setResultValue(widenedValue); - return result; - } - - /** - * Returns an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}, adapted for narrowing. This is only called at a narrowing conversion. - * - * @param type the type to narrow to - * @param annotatedValue the type to narrow from - * @return an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}; returns null if {@code annotatedValue} is null - */ - @SideEffectFree - protected @PolyNull V getNarrowedValue(TypeMirror type, @PolyNull V annotatedValue) { - if (annotatedValue == null) { - return null; + + @Override + public TransferResult visitWideningConversion( + WideningConversionNode n, TransferInput p) { + TransferResult result = super.visitWideningConversion(n, p); + // Combine annotations from the operand with the wide type + V operandValue = p.getValueOfSubNode(n.getOperand()); + V widenedValue = getWidenedValue(n.getType(), operandValue); + result.setResultValue(widenedValue); + return result; } - AnnotationMirrorSet narrowedAnnos = - analysis.atypeFactory.getNarrowedAnnotations( - annotatedValue.getAnnotations(), - annotatedValue.getUnderlyingType().getKind(), - type.getKind()); - - return analysis.createAbstractValue(narrowedAnnos, type); - } - - /** - * Returns an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}, adapted for widening. This is only called at a widening conversion. - * - * @param type the type to widen to - * @param annotatedValue the type to widen from - * @return an abstract value with the given {@code type} and the annotations from {@code - * annotatedValue}; returns null if {@code annotatedValue} is null - */ - @SideEffectFree - protected @PolyNull V getWidenedValue(TypeMirror type, @PolyNull V annotatedValue) { - if (annotatedValue == null) { - return null; + + /** + * Returns an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}, adapted for narrowing. This is only called at a narrowing conversion. + * + * @param type the type to narrow to + * @param annotatedValue the type to narrow from + * @return an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}; returns null if {@code annotatedValue} is null + */ + @SideEffectFree + protected @PolyNull V getNarrowedValue(TypeMirror type, @PolyNull V annotatedValue) { + if (annotatedValue == null) { + return null; + } + AnnotationMirrorSet narrowedAnnos = + analysis.atypeFactory.getNarrowedAnnotations( + annotatedValue.getAnnotations(), + annotatedValue.getUnderlyingType().getKind(), + type.getKind()); + + return analysis.createAbstractValue(narrowedAnnos, type); } - AnnotationMirrorSet widenedAnnos = - analysis.atypeFactory.getWidenedAnnotations( - annotatedValue.getAnnotations(), - annotatedValue.getUnderlyingType().getKind(), - type.getKind()); - - return analysis.createAbstractValue(widenedAnnos, type); - } - - @Override - public TransferResult visitNarrowingConversion( - NarrowingConversionNode n, TransferInput p) { - TransferResult result = super.visitNarrowingConversion(n, p); - // Combine annotations from the operand with the narrow type - V operandValue = p.getValueOfSubNode(n.getOperand()); - V narrowedValue = getNarrowedValue(n.getType(), operandValue); - result.setResultValue(narrowedValue); - return result; - } - - @Override - public TransferResult visitStringConversion(StringConversionNode n, TransferInput p) { - TransferResult result = super.visitStringConversion(n, p); - result.setResultValue(p.getValueOfSubNode(n.getOperand())); - return result; - } - - @Override - public TransferResult visitExpressionStatement( - ExpressionStatementNode n, TransferInput vsTransferInput) { - // Merge the input - S info = vsTransferInput.getRegularStore(); - return new RegularTransferResult<>(finishValue(null, info), info); - } - - /** - * Inserts newAnno as the value into all stores (conditional or not) in the result for node. This - * is a utility method for subclasses. - * - * @param result the TransferResult holding the stores to modify - * @param target the receiver whose value should be modified - * @param newAnno the new value - */ - protected static void insertIntoStores( - TransferResult result, JavaExpression target, AnnotationMirror newAnno) { - if (result.containsTwoStores()) { - result.getThenStore().insertValue(target, newAnno); - result.getElseStore().insertValue(target, newAnno); - } else { - result.getRegularStore().insertValue(target, newAnno); + + /** + * Returns an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}, adapted for widening. This is only called at a widening conversion. + * + * @param type the type to widen to + * @param annotatedValue the type to widen from + * @return an abstract value with the given {@code type} and the annotations from {@code + * annotatedValue}; returns null if {@code annotatedValue} is null + */ + @SideEffectFree + protected @PolyNull V getWidenedValue(TypeMirror type, @PolyNull V annotatedValue) { + if (annotatedValue == null) { + return null; + } + AnnotationMirrorSet widenedAnnos = + analysis.atypeFactory.getWidenedAnnotations( + annotatedValue.getAnnotations(), + annotatedValue.getUnderlyingType().getKind(), + type.getKind()); + + return analysis.createAbstractValue(widenedAnnos, type); + } + + @Override + public TransferResult visitNarrowingConversion( + NarrowingConversionNode n, TransferInput p) { + TransferResult result = super.visitNarrowingConversion(n, p); + // Combine annotations from the operand with the narrow type + V operandValue = p.getValueOfSubNode(n.getOperand()); + V narrowedValue = getNarrowedValue(n.getType(), operandValue); + result.setResultValue(narrowedValue); + return result; + } + + @Override + public TransferResult visitStringConversion( + StringConversionNode n, TransferInput p) { + TransferResult result = super.visitStringConversion(n, p); + result.setResultValue(p.getValueOfSubNode(n.getOperand())); + return result; + } + + @Override + public TransferResult visitExpressionStatement( + ExpressionStatementNode n, TransferInput vsTransferInput) { + // Merge the input + S info = vsTransferInput.getRegularStore(); + return new RegularTransferResult<>(finishValue(null, info), info); + } + + /** + * Inserts newAnno as the value into all stores (conditional or not) in the result for node. + * This is a utility method for subclasses. + * + * @param result the TransferResult holding the stores to modify + * @param target the receiver whose value should be modified + * @param newAnno the new value + */ + protected static void insertIntoStores( + TransferResult result, + JavaExpression target, + AnnotationMirror newAnno) { + if (result.containsTwoStores()) { + result.getThenStore().insertValue(target, newAnno); + result.getElseStore().insertValue(target, newAnno); + } else { + result.getRegularStore().insertValue(target, newAnno); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java index e24fb4b5e20..a7be250fc9c 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java @@ -1,13 +1,5 @@ package org.checkerframework.framework.flow; -import java.util.Objects; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; -import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; import org.checkerframework.dataflow.analysis.Analysis; @@ -25,6 +17,16 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.StringsPlume; +import java.util.Objects; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Types; + /** * An implementation of an abstract value used by the Checker Framework * org.checkerframework.dataflow analysis. @@ -53,821 +55,848 @@ */ public abstract class CFAbstractValue> implements AbstractValue { - /** The analysis class this value belongs to. */ - protected final CFAbstractAnalysis analysis; - - /** The underlying (Java) type in this abstract value. */ - protected final TypeMirror underlyingType; - - /** The annotations in this abstract value. */ - protected final AnnotationMirrorSet annotations; - - /** - * Creates a new CFAbstractValue. - * - * @param analysis the analysis class this value belongs to - * @param annotations the annotations in this abstract value - * @param underlyingType the underlying (Java) type in this abstract value - */ - protected CFAbstractValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - this.analysis = analysis; - this.annotations = annotations; - this.underlyingType = underlyingType; - - assert validateSet(this.getAnnotations(), this.getUnderlyingType(), analysis.getTypeFactory()) - : "Encountered invalid type: " - + underlyingType - + " annotations: " - + StringsPlume.join(", ", annotations); - } - - /** - * Returns true if the set has an annotation from every hierarchy (or if it doesn't need to); - * returns false if the set is missing an annotation from some hierarchy. - * - * @param annos a set of annotations - * @param typeMirror where the annotations are written - * @param atypeFactory the type factory - * @return true if no annotations are missing - */ - public static boolean validateSet( - AnnotationMirrorSet annos, TypeMirror typeMirror, AnnotatedTypeFactory atypeFactory) { - - boolean canBeMissing = canBeMissingAnnotations(typeMirror); - if (canBeMissing) { - return true; - } + /** The analysis class this value belongs to. */ + protected final CFAbstractAnalysis analysis; - // TODO: temporarily disable stricter size comparison. - // if (annos.size() != hierarchy.getTopAnnotations().size()) { - // return false; - // } - - // The size of the set matches, but maybe it contains multiple annos of one - // hierarchy and none of another. - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror anno = qualHierarchy.findAnnotationInHierarchy(annos, top); - if (anno == null) { - return false; - } - } + /** The underlying (Java) type in this abstract value. */ + protected final TypeMirror underlyingType; - return true; - } - - /** - * Returns whether or not the set of annotations can be missing an annotation for any hierarchy. - * - * @return whether or not the set of annotations can be missing an annotation - */ - public boolean canBeMissingAnnotations() { - return canBeMissingAnnotations(underlyingType); - } - - /** - * Returns true if it is OK for the given type mirror not to be annotated, such as for VOID, NONE, - * PACKAGE, TYPEVAR, and some WILDCARD. - * - * @param typeMirror a type - * @return true if it is OK for the given type mirror not to be annotated - */ - private static boolean canBeMissingAnnotations(TypeMirror typeMirror) { - if (typeMirror == null) { - return false; - } - if (typeMirror.getKind() == TypeKind.VOID - || typeMirror.getKind() == TypeKind.NONE - || typeMirror.getKind() == TypeKind.PACKAGE) { - return true; - } - if (typeMirror.getKind() == TypeKind.WILDCARD) { - return canBeMissingAnnotations(((WildcardType) typeMirror).getExtendsBound()); + /** The annotations in this abstract value. */ + protected final AnnotationMirrorSet annotations; + + /** + * Creates a new CFAbstractValue. + * + * @param analysis the analysis class this value belongs to + * @param annotations the annotations in this abstract value + * @param underlyingType the underlying (Java) type in this abstract value + */ + protected CFAbstractValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + this.analysis = analysis; + this.annotations = annotations; + this.underlyingType = underlyingType; + + assert validateSet( + this.getAnnotations(), this.getUnderlyingType(), analysis.getTypeFactory()) + : "Encountered invalid type: " + + underlyingType + + " annotations: " + + StringsPlume.join(", ", annotations); } - return typeMirror.getKind() == TypeKind.TYPEVAR; - } - - /** - * Returns a set of annotations. If {@link #canBeMissingAnnotations()} returns true, then the set - * of annotations may not have an annotation for every hierarchy. - * - *

          To get the single annotation in a particular hierarchy, use {@link - * QualifierHierarchy#findAnnotationInHierarchy}. - * - * @return a set of annotations - */ - @Pure - public AnnotationMirrorSet getAnnotations() { - return annotations; - } - - @Pure - public TypeMirror getUnderlyingType() { - return underlyingType; - } - - @SuppressWarnings("interning:not.interned") // efficiency pre-test - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof CFAbstractValue)) { - return false; + + /** + * Returns true if the set has an annotation from every hierarchy (or if it doesn't need to); + * returns false if the set is missing an annotation from some hierarchy. + * + * @param annos a set of annotations + * @param typeMirror where the annotations are written + * @param atypeFactory the type factory + * @return true if no annotations are missing + */ + public static boolean validateSet( + AnnotationMirrorSet annos, TypeMirror typeMirror, AnnotatedTypeFactory atypeFactory) { + + boolean canBeMissing = canBeMissingAnnotations(typeMirror); + if (canBeMissing) { + return true; + } + + // TODO: temporarily disable stricter size comparison. + // if (annos.size() != hierarchy.getTopAnnotations().size()) { + // return false; + // } + + // The size of the set matches, but maybe it contains multiple annos of one + // hierarchy and none of another. + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror anno = qualHierarchy.findAnnotationInHierarchy(annos, top); + if (anno == null) { + return false; + } + } + + return true; } - CFAbstractValue other = (CFAbstractValue) obj; - if (this.getUnderlyingType() != other.getUnderlyingType() - && !analysis.getTypes().isSameType(this.getUnderlyingType(), other.getUnderlyingType())) { - return false; + /** + * Returns whether or not the set of annotations can be missing an annotation for any hierarchy. + * + * @return whether or not the set of annotations can be missing an annotation + */ + public boolean canBeMissingAnnotations() { + return canBeMissingAnnotations(underlyingType); } - return AnnotationUtils.areSame(this.getAnnotations(), other.getAnnotations()); - } - - @Pure - @Override - public int hashCode() { - return Objects.hash(getAnnotations(), underlyingType); - } - - /** - * Returns the string representation, using fully-qualified names. - * - * @return the string representation, using fully-qualified names - */ - @SideEffectFree - public String toStringFullyQualified() { - return "CFAV{" + annotations + ", " + underlyingType + '}'; - } - - /** - * Returns the string representation, using simple (not fully-qualified) names. - * - * @return the string representation, using simple (not fully-qualified) names - */ - @SideEffectFree - public String toStringSimple() { - return "CFAV{" - + AnnotationUtils.toStringSimple(annotations) - + ", " - + TypesUtils.simpleTypeName(underlyingType) - + '}'; - } - - /** - * Returns the string representation. - * - * @return the string representation - */ - @SideEffectFree - @Override - public String toString() { - return toStringSimple(); - } - - /** - * Returns the more specific of two values {@code this} and {@code other}. If they do not contain - * information for all hierarchies, then it is possible that information from both {@code this} - * and {@code other} are taken. - * - *

          If neither of the two is more specific for one of the hierarchies (i.e., if the two are - * incomparable as determined by {@link QualifierHierarchy#isSubtypeShallow(AnnotationMirror, - * TypeMirror, AnnotationMirror, TypeMirror)}, then the respective value from {@code backup} is - * used. - * - * @param other the other value to obtain information from - * @param backup the value to use if {@code this} and {@code other} are incomparable - * @return the more specific of two values {@code this} and {@code other} - */ - public V mostSpecific(@Nullable V other, @Nullable V backup) { - if (other == null) { - @SuppressWarnings("unchecked") - V v = (V) this; - return v; + + /** + * Returns true if it is OK for the given type mirror not to be annotated, such as for VOID, + * NONE, PACKAGE, TYPEVAR, and some WILDCARD. + * + * @param typeMirror a type + * @return true if it is OK for the given type mirror not to be annotated + */ + private static boolean canBeMissingAnnotations(TypeMirror typeMirror) { + if (typeMirror == null) { + return false; + } + if (typeMirror.getKind() == TypeKind.VOID + || typeMirror.getKind() == TypeKind.NONE + || typeMirror.getKind() == TypeKind.PACKAGE) { + return true; + } + if (typeMirror.getKind() == TypeKind.WILDCARD) { + return canBeMissingAnnotations(((WildcardType) typeMirror).getExtendsBound()); + } + return typeMirror.getKind() == TypeKind.TYPEVAR; } - Types types = analysis.getTypes(); - TypeMirror mostSpecifTypeMirror; - if (types.isAssignable(this.getUnderlyingType(), other.getUnderlyingType())) { - mostSpecifTypeMirror = this.getUnderlyingType(); - } else if (types.isAssignable(other.getUnderlyingType(), this.getUnderlyingType())) { - mostSpecifTypeMirror = other.getUnderlyingType(); - } else if (TypesUtils.isErasedSubtype( - this.getUnderlyingType(), other.getUnderlyingType(), types)) { - mostSpecifTypeMirror = this.getUnderlyingType(); - } else if (TypesUtils.isErasedSubtype( - other.getUnderlyingType(), this.getUnderlyingType(), types)) { - mostSpecifTypeMirror = other.getUnderlyingType(); - } else { - mostSpecifTypeMirror = this.getUnderlyingType(); + + /** + * Returns a set of annotations. If {@link #canBeMissingAnnotations()} returns true, then the + * set of annotations may not have an annotation for every hierarchy. + * + *

          To get the single annotation in a particular hierarchy, use {@link + * QualifierHierarchy#findAnnotationInHierarchy}. + * + * @return a set of annotations + */ + @Pure + public AnnotationMirrorSet getAnnotations() { + return annotations; } - MostSpecificVisitor ms = new MostSpecificVisitor(backup); - AnnotationMirrorSet mostSpecific = - ms.combineSets( - this.getUnderlyingType(), - this.getAnnotations(), - other.getUnderlyingType(), - other.getAnnotations(), - canBeMissingAnnotations(mostSpecifTypeMirror)); - if (ms.error) { - // return null because `ms.error` is only set to true when `backup` is null. - return null; + @Pure + public TypeMirror getUnderlyingType() { + return underlyingType; } - return analysis.createAbstractValue(mostSpecific, mostSpecifTypeMirror); - } - /** Computes the most specific annotations. */ - private class MostSpecificVisitor extends AnnotationSetCombiner { - /** If set to true, then this visitor was unable to find a most specific annotation. */ - boolean error = false; + @SuppressWarnings("interning:not.interned") // efficiency pre-test + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CFAbstractValue)) { + return false; + } + + CFAbstractValue other = (CFAbstractValue) obj; + if (this.getUnderlyingType() != other.getUnderlyingType() + && !analysis.getTypes() + .isSameType(this.getUnderlyingType(), other.getUnderlyingType())) { + return false; + } + return AnnotationUtils.areSame(this.getAnnotations(), other.getAnnotations()); + } - /** Set of annotations to use if a most specific value cannot be found. */ - final @Nullable AnnotationMirrorSet backupAMSet; + @Pure + @Override + public int hashCode() { + return Objects.hash(getAnnotations(), underlyingType); + } /** - * Create a {@link MostSpecificVisitor}. + * Returns the string representation, using fully-qualified names. * - * @param backup value to use if no most specific value is found + * @return the string representation, using fully-qualified names */ - public MostSpecificVisitor(@Nullable V backup) { - if (backup != null) { - this.backupAMSet = backup.getAnnotations(); - } else { - this.backupAMSet = null; - } + @SideEffectFree + public String toStringFullyQualified() { + return "CFAV{" + annotations + ", " + underlyingType + '}'; } /** - * Returns the backup annotation that is in the same hierarchy as {@code top}. + * Returns the string representation, using simple (not fully-qualified) names. * - * @param top an annotation - * @return the backup annotation that is in the same hierarchy as {@code top} + * @return the string representation, using simple (not fully-qualified) names */ - private @Nullable AnnotationMirror getBackupAnnoIn(AnnotationMirror top) { - if (backupAMSet == null) { - // If there is no backup value, but one is required, then the resulting set will - // not be the most specific. Indicate this with the error. - error = true; - return null; - } - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - return qualHierarchy.findAnnotationInHierarchy(backupAMSet, top); + @SideEffectFree + public String toStringSimple() { + return "CFAV{" + + AnnotationUtils.toStringSimple(annotations) + + ", " + + TypesUtils.simpleTypeName(underlyingType) + + '}'; } + /** + * Returns the string representation. + * + * @return the string representation + */ + @SideEffectFree @Override - protected @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top) { - if (aTypeMirror == null) { - throw new NullPointerException("combineTwoAnnotations: aTypeMirror==null"); - } - if (bTypeMirror == null) { - throw new NullPointerException("combineTwoAnnotations: bTypeMirror==null"); - } - GenericAnnotatedTypeFactory gatf = analysis.getTypeFactory(); - QualifierHierarchy qualHierarchy = gatf.getQualifierHierarchy(); - if (gatf.hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(aTypeMirror), top) - && gatf.hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(bTypeMirror), top)) { - // Both types have qualifier parameters, so they are related by invariance rather - // than subtyping. - if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror) - && qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { - return b; - } - } else if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror)) { - // `a` may not be a subtype of `b`, if one of the type mirrors isn't relevant, - // so return the lower of the two. - return lowestQualifier(a, b); - } else if (qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { - // `b` may not be a subtype of `a`, if one of the type mirrors isn't relevant, - // so return the lower of the two. - return lowestQualifier(a, b); - } - return getBackupAnnoIn(top); + public String toString() { + return toStringSimple(); } /** - * Returns the qualifier that is the lowest in the hierarchy. If the two qualifiers are not - * comparable, then returns the qualifier that is ordered first by {@link - * AnnotationUtils#compareAnnotationMirrors(AnnotationMirror, AnnotationMirror)}. + * Returns the more specific of two values {@code this} and {@code other}. If they do not + * contain information for all hierarchies, then it is possible that information from both + * {@code this} and {@code other} are taken. * - *

          This is similar to glb, but one of the given qualifiers is always returned. + *

          If neither of the two is more specific for one of the hierarchies (i.e., if the two are + * incomparable as determined by {@link QualifierHierarchy#isSubtypeShallow(AnnotationMirror, + * TypeMirror, AnnotationMirror, TypeMirror)}, then the respective value from {@code backup} is + * used. * - * @param qual1 a qualifier - * @param qual2 a qualifier - * @return the qualifier that is the lowest in the hierarchy + * @param other the other value to obtain information from + * @param backup the value to use if {@code this} and {@code other} are incomparable + * @return the more specific of two values {@code this} and {@code other} */ - private final AnnotationMirror lowestQualifier(AnnotationMirror qual1, AnnotationMirror qual2) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - if (qualHierarchy.isSubtypeQualifiersOnly(qual1, qual2)) { - return qual1; - } else if (qualHierarchy.isSubtypeQualifiersOnly(qual2, qual1)) { - return qual2; - } else { - int i = AnnotationUtils.compareAnnotationMirrors(qual1, qual2); - if (i > 0) { - return qual2; + public V mostSpecific(@Nullable V other, @Nullable V backup) { + if (other == null) { + @SuppressWarnings("unchecked") + V v = (V) this; + return v; + } + Types types = analysis.getTypes(); + TypeMirror mostSpecifTypeMirror; + if (types.isAssignable(this.getUnderlyingType(), other.getUnderlyingType())) { + mostSpecifTypeMirror = this.getUnderlyingType(); + } else if (types.isAssignable(other.getUnderlyingType(), this.getUnderlyingType())) { + mostSpecifTypeMirror = other.getUnderlyingType(); + } else if (TypesUtils.isErasedSubtype( + this.getUnderlyingType(), other.getUnderlyingType(), types)) { + mostSpecifTypeMirror = this.getUnderlyingType(); + } else if (TypesUtils.isErasedSubtype( + other.getUnderlyingType(), this.getUnderlyingType(), types)) { + mostSpecifTypeMirror = other.getUnderlyingType(); } else { - return qual1; + mostSpecifTypeMirror = this.getUnderlyingType(); + } + + MostSpecificVisitor ms = new MostSpecificVisitor(backup); + AnnotationMirrorSet mostSpecific = + ms.combineSets( + this.getUnderlyingType(), + this.getAnnotations(), + other.getUnderlyingType(), + other.getAnnotations(), + canBeMissingAnnotations(mostSpecifTypeMirror)); + if (ms.error) { + // return null because `ms.error` is only set to true when `backup` is null. + return null; } - } + return analysis.createAbstractValue(mostSpecific, mostSpecifTypeMirror); } - @Override - protected @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - if (canCombinedSetBeMissingAnnos) { - return null; - } else { - AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); - TypeMirror aTM = aAtv.getUnderlyingType(); - TypeMirror bTM = bAtv.getUnderlyingType(); - return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); - } + /** Computes the most specific annotations. */ + private class MostSpecificVisitor extends AnnotationSetCombiner { + /** If set to true, then this visitor was unable to find a most specific annotation. */ + boolean error = false; + + /** Set of annotations to use if a most specific value cannot be found. */ + final @Nullable AnnotationMirrorSet backupAMSet; + + /** + * Create a {@link MostSpecificVisitor}. + * + * @param backup value to use if no most specific value is found + */ + public MostSpecificVisitor(@Nullable V backup) { + if (backup != null) { + this.backupAMSet = backup.getAnnotations(); + } else { + this.backupAMSet = null; + } + } + + /** + * Returns the backup annotation that is in the same hierarchy as {@code top}. + * + * @param top an annotation + * @return the backup annotation that is in the same hierarchy as {@code top} + */ + private @Nullable AnnotationMirror getBackupAnnoIn(AnnotationMirror top) { + if (backupAMSet == null) { + // If there is no backup value, but one is required, then the resulting set will + // not be the most specific. Indicate this with the error. + error = true; + return null; + } + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + return qualHierarchy.findAnnotationInHierarchy(backupAMSet, top); + } + + @Override + protected @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top) { + if (aTypeMirror == null) { + throw new NullPointerException("combineTwoAnnotations: aTypeMirror==null"); + } + if (bTypeMirror == null) { + throw new NullPointerException("combineTwoAnnotations: bTypeMirror==null"); + } + GenericAnnotatedTypeFactory gatf = analysis.getTypeFactory(); + QualifierHierarchy qualHierarchy = gatf.getQualifierHierarchy(); + if (gatf.hasQualifierParameterInHierarchy(TypesUtils.getTypeElement(aTypeMirror), top) + && gatf.hasQualifierParameterInHierarchy( + TypesUtils.getTypeElement(bTypeMirror), top)) { + // Both types have qualifier parameters, so they are related by invariance rather + // than subtyping. + if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror) + && qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { + return b; + } + } else if (qualHierarchy.isSubtypeShallow(a, aTypeMirror, b, bTypeMirror)) { + // `a` may not be a subtype of `b`, if one of the type mirrors isn't relevant, + // so return the lower of the two. + return lowestQualifier(a, b); + } else if (qualHierarchy.isSubtypeShallow(b, bTypeMirror, a, aTypeMirror)) { + // `b` may not be a subtype of `a`, if one of the type mirrors isn't relevant, + // so return the lower of the two. + return lowestQualifier(a, b); + } + return getBackupAnnoIn(top); + } + + /** + * Returns the qualifier that is the lowest in the hierarchy. If the two qualifiers are not + * comparable, then returns the qualifier that is ordered first by {@link + * AnnotationUtils#compareAnnotationMirrors(AnnotationMirror, AnnotationMirror)}. + * + *

          This is similar to glb, but one of the given qualifiers is always returned. + * + * @param qual1 a qualifier + * @param qual2 a qualifier + * @return the qualifier that is the lowest in the hierarchy + */ + private final AnnotationMirror lowestQualifier( + AnnotationMirror qual1, AnnotationMirror qual2) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + if (qualHierarchy.isSubtypeQualifiersOnly(qual1, qual2)) { + return qual1; + } else if (qualHierarchy.isSubtypeQualifiersOnly(qual2, qual1)) { + return qual2; + } else { + int i = AnnotationUtils.compareAnnotationMirrors(qual1, qual2); + if (i > 0) { + return qual2; + } else { + return qual1; + } + } + } + + @Override + protected @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + if (canCombinedSetBeMissingAnnos) { + return null; + } else { + AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); + TypeMirror aTM = aAtv.getUnderlyingType(); + TypeMirror bTM = bAtv.getUnderlyingType(); + return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); + } + } + + @Override + protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + + AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); + TypeMirror upperBoundTM = typeVar.getUpperBound().getUnderlyingType(); + + if (!canCombinedSetBeMissingAnnos) { + TypeVariable typeVarTM = typeVar.getUnderlyingType(); + return combineTwoAnnotations(annotation, typeVarTM, upperBound, typeVarTM, top); + } + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + AnnotationMirrorSet lBSet = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); + AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); + TypeMirror lowerBoundTM = typeVar.getLowerBound().getUnderlyingType(); + + TypeMirror typeVarTM = typeVar.getUnderlyingType(); + if (qualHierarchy.isSubtypeShallow(upperBound, upperBoundTM, annotation, typeVarTM)) { + // no anno is more specific than anno + return null; + } else if (qualHierarchy.isSubtypeShallow( + annotation, typeVarTM, lowerBound, lowerBoundTM)) { + return lowestQualifier(annotation, lowerBound); + } else { + return getBackupAnnoIn(top); + } + } } + /** + * {@inheritDoc} + * + *

          Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} + * instead of this method. + */ @Override - protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - - AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); - TypeMirror upperBoundTM = typeVar.getUpperBound().getUnderlyingType(); - - if (!canCombinedSetBeMissingAnnos) { - TypeVariable typeVarTM = typeVar.getUnderlyingType(); - return combineTwoAnnotations(annotation, typeVarTM, upperBound, typeVarTM, top); - } - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - AnnotationMirrorSet lBSet = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); - AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); - TypeMirror lowerBoundTM = typeVar.getLowerBound().getUnderlyingType(); - - TypeMirror typeVarTM = typeVar.getUnderlyingType(); - if (qualHierarchy.isSubtypeShallow(upperBound, upperBoundTM, annotation, typeVarTM)) { - // no anno is more specific than anno - return null; - } else if (qualHierarchy.isSubtypeShallow(annotation, typeVarTM, lowerBound, lowerBoundTM)) { - return lowestQualifier(annotation, lowerBound); - } else { - return getBackupAnnoIn(top); - } + public final V leastUpperBound(@Nullable V other) { + return upperBound(other, false); } - } - - /** - * {@inheritDoc} - * - *

          Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead - * of this method. - */ - @Override - public final V leastUpperBound(@Nullable V other) { - return upperBound(other, false); - } - - /** - * Compute the least upper bound of two abstract values. The returned value has a Java type of - * {@code typeMirror}. {@code typeMirror} should be an upper bound of the Java types of {@code - * this} an {@code other}, but it does not have be to the least upper bound. - * - *

          Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead - * of this method. - * - * @param other another value - * @param typeMirror the underlying Java type of the returned value, which may or may not be the - * least upper bound - * @return the least upper bound of two abstract values - */ - public final V leastUpperBound(@Nullable V other, TypeMirror typeMirror) { - return upperBound(other, typeMirror, false); - } - - /** - * Compute an upper bound of two values that is wider than the least upper bound of the two - * values. Used to jump to a higher abstraction to allow faster termination of the fixed point - * computations in {@link Analysis}. - * - *

          A particular analysis might not require widening and should implement this method by calling - * leastUpperBound. - * - *

          Important: This method must fulfill the following contract: - * - *

            - *
          • Does not change {@code this}. - *
          • Does not change {@code previous}. - *
          • Returns a fresh object which is not aliased yet. - *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
          • Is commutative. - *
          - * - * Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead of - * this method. - * - * @param previous must be the previous value - * @return an upper bound of two values that is wider than the least upper bound of the two values - */ - public final V widenUpperBound(@Nullable V previous) { - return upperBound(previous, true); - } - - /** - * Returns the least upper bound of this and {@code other}. - * - * @param other an abstract value - * @param shouldWiden true if the lub should perform widening - * @return the least upper bound of this and {@code other} - */ - private V upperBound(@Nullable V other, boolean shouldWiden) { - if (other == null) { - @SuppressWarnings("unchecked") - V v = (V) this; - return v; - } - ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); - TypeMirror lubTypeMirror = - TypesUtils.leastUpperBound( - this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); - return upperBound(other, lubTypeMirror, shouldWiden); - } - - /** - * Returns an upper bound of {@code this} and {@code other}. The underlying type of the value - * returned is {@code upperBoundTypeMirror}. If {@code shouldWiden} is false, this method returns - * the least upper bound of {@code this} and {@code other}. - * - *

          This is the implementation of {@link #leastUpperBound(CFAbstractValue, TypeMirror)}, {@link - * #leastUpperBound(CFAbstractValue)}, {@link #widenUpperBound(CFAbstractValue)}, and {@link - * #upperBound(CFAbstractValue, boolean)}. Subclasses may override it. - * - * @param other an abstract value - * @param upperBoundTypeMirror the underlying type of the returned value - * @param shouldWiden true if the method should perform widening - * @return an upper bound of this and {@code other} - */ - protected V upperBound(@Nullable V other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { - ValueLub valueLub = new ValueLub(shouldWiden); - AnnotationMirrorSet lub = - valueLub.combineSets( - this.getUnderlyingType(), - this.getAnnotations(), - other.getUnderlyingType(), - other.getAnnotations(), - canBeMissingAnnotations(upperBoundTypeMirror)); - return analysis.createAbstractValue(lub, upperBoundTypeMirror); - } - - /** - * Computes the least upper bound or, if {@code shouldWiden} is true, an upper bounds of two sets - * of annotations. The computation accounts for sets that are missing annotations in hierarchies. - */ - protected class ValueLub extends AnnotationSetCombiner { /** - * If true, this class computes an upper bound; if false, this class computes the least upper - * bound. + * Compute the least upper bound of two abstract values. The returned value has a Java type of + * {@code typeMirror}. {@code typeMirror} should be an upper bound of the Java types of {@code + * this} an {@code other}, but it does not have be to the least upper bound. + * + *

          Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} + * instead of this method. + * + * @param other another value + * @param typeMirror the underlying Java type of the returned value, which may or may not be the + * least upper bound + * @return the least upper bound of two abstract values */ - private final boolean widen; + public final V leastUpperBound(@Nullable V other, TypeMirror typeMirror) { + return upperBound(other, typeMirror, false); + } /** - * Creates a {@link ValueLub}. + * Compute an upper bound of two values that is wider than the least upper bound of the two + * values. Used to jump to a higher abstraction to allow faster termination of the fixed point + * computations in {@link Analysis}. + * + *

          A particular analysis might not require widening and should implement this method by + * calling leastUpperBound. + * + *

          Important: This method must fulfill the following contract: * - * @param shouldWiden if true, this class computes an upper bound + *

            + *
          • Does not change {@code this}. + *
          • Does not change {@code previous}. + *
          • Returns a fresh object which is not aliased yet. + *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
          • Is commutative. + *
          + * + * Subclasses should override {@link #upperBound(CFAbstractValue, TypeMirror, boolean)} instead + * of this method. + * + * @param previous must be the previous value + * @return an upper bound of two values that is wider than the least upper bound of the two + * values */ - public ValueLub(boolean shouldWiden) { - this.widen = shouldWiden; + public final V widenUpperBound(@Nullable V previous) { + return upperBound(previous, true); } - @Override - protected @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - if (widen) { - return qualHierarchy.widenedUpperBound(a, b); - } else { - return qualHierarchy.leastUpperBoundShallow(a, aTypeMirror, b, bTypeMirror); - } + /** + * Returns the least upper bound of this and {@code other}. + * + * @param other an abstract value + * @param shouldWiden true if the lub should perform widening + * @return the least upper bound of this and {@code other} + */ + private V upperBound(@Nullable V other, boolean shouldWiden) { + if (other == null) { + @SuppressWarnings("unchecked") + V v = (V) this; + return v; + } + ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); + TypeMirror lubTypeMirror = + TypesUtils.leastUpperBound( + this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); + return upperBound(other, lubTypeMirror, shouldWiden); } - @Override - protected @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - if (canCombinedSetBeMissingAnnos) { - // don't add an annotation - return null; - } else { - AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); - return combineTwoAnnotations( - aUB, aAtv.getUnderlyingType(), bUB, bAtv.getUnderlyingType(), top); - } + /** + * Returns an upper bound of {@code this} and {@code other}. The underlying type of the value + * returned is {@code upperBoundTypeMirror}. If {@code shouldWiden} is false, this method + * returns the least upper bound of {@code this} and {@code other}. + * + *

          This is the implementation of {@link #leastUpperBound(CFAbstractValue, TypeMirror)}, + * {@link #leastUpperBound(CFAbstractValue)}, {@link #widenUpperBound(CFAbstractValue)}, and + * {@link #upperBound(CFAbstractValue, boolean)}. Subclasses may override it. + * + * @param other an abstract value + * @param upperBoundTypeMirror the underlying type of the returned value + * @param shouldWiden true if the method should perform widening + * @return an upper bound of this and {@code other} + */ + protected V upperBound( + @Nullable V other, TypeMirror upperBoundTypeMirror, boolean shouldWiden) { + ValueLub valueLub = new ValueLub(shouldWiden); + AnnotationMirrorSet lub = + valueLub.combineSets( + this.getUnderlyingType(), + this.getAnnotations(), + other.getUnderlyingType(), + other.getAnnotations(), + canBeMissingAnnotations(upperBoundTypeMirror)); + return analysis.createAbstractValue(lub, upperBoundTypeMirror); } - @Override - protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - TypeMirror typeVarTM = typeVar.getUnderlyingType(); - if (canCombinedSetBeMissingAnnos) { - // anno is the primary annotation on the use of a type variable. typeVar is a use of - // the same type variable that does not have a primary annotation. The lub of the - // two type variables is computed as follows. If anno is a subtype (or equal) to the - // annotation on the lower bound of typeVar, then typeVar is the lub, so no - // annotation is added to lubset. - // If anno is a supertype of the annotation on the lower bound of typeVar, then the - // lub is typeVar with a primary annotation of lub(anno, upperBound), where - // upperBound is the annotation on the upper bound of typeVar. - AnnotationMirrorSet lBSet = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); - AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); - if (qualHierarchy.isSubtypeQualifiersOnly(annotation, lowerBound)) { - return null; - } else { - return combineTwoAnnotations( - annotation, - typeVarTM, - typeVar.getEffectiveAnnotationInHierarchy(top), - typeVarTM, - top); + /** + * Computes the least upper bound or, if {@code shouldWiden} is true, an upper bounds of two + * sets of annotations. The computation accounts for sets that are missing annotations in + * hierarchies. + */ + protected class ValueLub extends AnnotationSetCombiner { + + /** + * If true, this class computes an upper bound; if false, this class computes the least + * upper bound. + */ + private final boolean widen; + + /** + * Creates a {@link ValueLub}. + * + * @param shouldWiden if true, this class computes an upper bound + */ + public ValueLub(boolean shouldWiden) { + this.widen = shouldWiden; } - } else { - return combineTwoAnnotations( - annotation, typeVarTM, typeVar.getEffectiveAnnotationInHierarchy(top), typeVarTM, top); - } - } - } - - /** - * Compute the greatest lower bound of two values. - * - *

          Important: This method must fulfill the following contract: - * - *

            - *
          • Does not change {@code this}. - *
          • Does not change {@code other}. - *
          • Returns a fresh object which is not aliased yet. - *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is - * more permissive. - *
          • Is commutative. - *
          - * - * @param other another value - * @return the greatest lower bound of two values - */ - public V greatestLowerBound(@Nullable V other) { - if (other == null) { - @SuppressWarnings("unchecked") - V v = (V) this; - return v; - } - ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); - TypeMirror glbTypeMirror = - TypesUtils.greatestLowerBound( - this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); - - ValueGlb valueGlb = new ValueGlb(); - AnnotationMirrorSet glb = - valueGlb.combineSets( - this.getUnderlyingType(), - this.getAnnotations(), - other.getUnderlyingType(), - other.getAnnotations(), - canBeMissingAnnotations(glbTypeMirror)); - return analysis.createAbstractValue(glb, glbTypeMirror); - } - - /** - * Computes the GLB of two sets of annotations. The computation accounts for sets that are missing - * annotations in hierarchies. - */ - protected class ValueGlb extends AnnotationSetCombiner { - @Override - protected @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top) { - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - return qualHierarchy.greatestLowerBoundShallow(a, aTypeMirror, b, bTypeMirror); - } + @Override + protected @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + if (widen) { + return qualHierarchy.widenedUpperBound(a, b); + } else { + return qualHierarchy.leastUpperBoundShallow(a, aTypeMirror, b, bTypeMirror); + } + } - @Override - protected @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - if (canCombinedSetBeMissingAnnos) { - // don't add an annotation - return null; - } else { - AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); - AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); - TypeMirror aTM = aAtv.getUnderlyingType(); - TypeMirror bTM = bAtv.getUnderlyingType(); - return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); - } - } + @Override + protected @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + if (canCombinedSetBeMissingAnnos) { + // don't add an annotation + return null; + } else { + AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); + return combineTwoAnnotations( + aUB, aAtv.getUnderlyingType(), bUB, bAtv.getUnderlyingType(), top); + } + } - @Override - protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos) { - TypeMirror typeVarTM = typeVar.getUnderlyingType(); - if (canCombinedSetBeMissingAnnos) { - // anno is the primary annotation on the use of a type variable. typeVar is a use of - // the same type variable that does not have a primary annotation. The glb of the - // two type variables is computed as follows. If anno is a supertype (or equal) to - // the annotation on the upper bound of typeVar, then typeVar is the glb, so no - // annotation is added to glbset. - // If anno is a subtype of the annotation on the upper bound of typeVar, then the - // glb is typeVar with a primary annotation of glb(anno, lowerBound), where - // lowerBound is the annotation on the lower bound of typeVar. - AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - if (qualHierarchy.isSubtypeQualifiersOnly(upperBound, annotation)) { - return null; - } else { - AnnotationMirrorSet lBSet = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); - AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); - return combineTwoAnnotations(annotation, typeVarTM, lowerBound, typeVarTM, top); + @Override + protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + TypeMirror typeVarTM = typeVar.getUnderlyingType(); + if (canCombinedSetBeMissingAnnos) { + // anno is the primary annotation on the use of a type variable. typeVar is a use of + // the same type variable that does not have a primary annotation. The lub of the + // two type variables is computed as follows. If anno is a subtype (or equal) to the + // annotation on the lower bound of typeVar, then typeVar is the lub, so no + // annotation is added to lubset. + // If anno is a supertype of the annotation on the lower bound of typeVar, then the + // lub is typeVar with a primary annotation of lub(anno, upperBound), where + // upperBound is the annotation on the upper bound of typeVar. + AnnotationMirrorSet lBSet = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, typeVar); + AnnotationMirror lowerBound = qualHierarchy.findAnnotationInHierarchy(lBSet, top); + if (qualHierarchy.isSubtypeQualifiersOnly(annotation, lowerBound)) { + return null; + } else { + return combineTwoAnnotations( + annotation, + typeVarTM, + typeVar.getEffectiveAnnotationInHierarchy(top), + typeVarTM, + top); + } + } else { + return combineTwoAnnotations( + annotation, + typeVarTM, + typeVar.getEffectiveAnnotationInHierarchy(top), + typeVarTM, + top); + } } - } else { - return combineTwoAnnotations( - annotation, typeVarTM, typeVar.getEffectiveAnnotationInHierarchy(top), typeVarTM, top); - } } - } - - /** - * Combines two sets of AnnotationMirrors by hierarchy. - * - *

          Subclasses must define how to combine sets by implementing the following methods: - * - *

            - *
          1. {@link #combineTwoAnnotations} - *
          2. {@link #combineAnnotationWithTypeVar} - *
          3. {@link #combineTwoTypeVars} - *
          - * - * If a set is missing an annotation in a hierarchy, and if the combined set can be missing an - * annotation, then there must be a TypeVariable for the set that can be used to find annotations - * on its bounds. - */ - protected abstract class AnnotationSetCombiner { /** - * Combines the two sets. + * Compute the greatest lower bound of two values. + * + *

          Important: This method must fulfill the following contract: + * + *

            + *
          • Does not change {@code this}. + *
          • Does not change {@code other}. + *
          • Returns a fresh object which is not aliased yet. + *
          • Returns an object of the same (dynamic) type as {@code this}, even if the signature is + * more permissive. + *
          • Is commutative. + *
          * - * @param aTypeMirror the type mirror associated with {@code aSet} - * @param aSet a set of annotation mirrors - * @param bTypeMirror the type mirror associated with {@code bSet} - * @param bSet a set of annotation mirrors - * @param canCombinedSetBeMissingAnnos whether or not the combined set can be missing - * annotations - * @return the combined sets + * @param other another value + * @return the greatest lower bound of two values */ - protected AnnotationMirrorSet combineSets( - TypeMirror aTypeMirror, - AnnotationMirrorSet aSet, - TypeMirror bTypeMirror, - AnnotationMirrorSet bSet, - boolean canCombinedSetBeMissingAnnos) { - if (aTypeMirror == null) { - throw new NullPointerException("combineSets: aTypeMirror==null"); - } - if (bTypeMirror == null) { - throw new NullPointerException("combineSets: bTypeMirror==null"); - } - - AnnotatedTypeVariable aAtv = getEffectiveTypeVar(aTypeMirror); - AnnotatedTypeVariable bAtv = getEffectiveTypeVar(bTypeMirror); - QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet combinedSets = new AnnotationMirrorSet(); - for (AnnotationMirror top : tops) { - AnnotationMirror a = qualHierarchy.findAnnotationInHierarchy(aSet, top); - AnnotationMirror b = qualHierarchy.findAnnotationInHierarchy(bSet, top); - AnnotationMirror result; - if (a != null && b != null) { - result = combineTwoAnnotations(a, aTypeMirror, b, bTypeMirror, top); - } else if (a != null) { - result = combineAnnotationWithTypeVar(a, bAtv, top, canCombinedSetBeMissingAnnos); - } else if (b != null) { - result = combineAnnotationWithTypeVar(b, aAtv, top, canCombinedSetBeMissingAnnos); - } else { - result = combineTwoTypeVars(aAtv, bAtv, top, canCombinedSetBeMissingAnnos); - } - if (result != null) { - combinedSets.add(result); + public V greatestLowerBound(@Nullable V other) { + if (other == null) { + @SuppressWarnings("unchecked") + V v = (V) this; + return v; } - } - return combinedSets; + ProcessingEnvironment processingEnv = analysis.getTypeFactory().getProcessingEnv(); + TypeMirror glbTypeMirror = + TypesUtils.greatestLowerBound( + this.getUnderlyingType(), other.getUnderlyingType(), processingEnv); + + ValueGlb valueGlb = new ValueGlb(); + AnnotationMirrorSet glb = + valueGlb.combineSets( + this.getUnderlyingType(), + this.getAnnotations(), + other.getUnderlyingType(), + other.getAnnotations(), + canBeMissingAnnotations(glbTypeMirror)); + return analysis.createAbstractValue(glb, glbTypeMirror); } /** - * Returns the result of combining the two annotations. This method is called when an annotation - * exists in both sets for the hierarchy whose top is {@code top}. - * - * @param a an annotation in the hierarchy - * @param aTypeMirror the type that is annotated by {@code a} - * @param b an annotation in the hierarchy - * @param bTypeMirror the type that is annotated by {@code b} - * @param top the top annotation in the hierarchy - * @return the result of combining the two annotations or null if no combination exists + * Computes the GLB of two sets of annotations. The computation accounts for sets that are + * missing annotations in hierarchies. */ - protected abstract @Nullable AnnotationMirror combineTwoAnnotations( - AnnotationMirror a, - TypeMirror aTypeMirror, - AnnotationMirror b, - TypeMirror bTypeMirror, - AnnotationMirror top); + protected class ValueGlb extends AnnotationSetCombiner { + + @Override + protected @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top) { + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + return qualHierarchy.greatestLowerBoundShallow(a, aTypeMirror, b, bTypeMirror); + } + + @Override + protected @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + if (canCombinedSetBeMissingAnnos) { + // don't add an annotation + return null; + } else { + AnnotationMirror aUB = aAtv.getEffectiveAnnotationInHierarchy(top); + AnnotationMirror bUB = bAtv.getEffectiveAnnotationInHierarchy(top); + TypeMirror aTM = aAtv.getUnderlyingType(); + TypeMirror bTM = bAtv.getUnderlyingType(); + return combineTwoAnnotations(aUB, aTM, bUB, bTM, top); + } + } + + @Override + protected @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos) { + TypeMirror typeVarTM = typeVar.getUnderlyingType(); + if (canCombinedSetBeMissingAnnos) { + // anno is the primary annotation on the use of a type variable. typeVar is a use of + // the same type variable that does not have a primary annotation. The glb of the + // two type variables is computed as follows. If anno is a supertype (or equal) to + // the annotation on the upper bound of typeVar, then typeVar is the glb, so no + // annotation is added to glbset. + // If anno is a subtype of the annotation on the upper bound of typeVar, then the + // glb is typeVar with a primary annotation of glb(anno, lowerBound), where + // lowerBound is the annotation on the lower bound of typeVar. + AnnotationMirror upperBound = typeVar.getEffectiveAnnotationInHierarchy(top); + QualifierHierarchy qualHierarchy = + analysis.getTypeFactory().getQualifierHierarchy(); + if (qualHierarchy.isSubtypeQualifiersOnly(upperBound, annotation)) { + return null; + } else { + AnnotationMirrorSet lBSet = + AnnotatedTypes.findEffectiveLowerBoundAnnotations( + qualHierarchy, typeVar); + AnnotationMirror lowerBound = + qualHierarchy.findAnnotationInHierarchy(lBSet, top); + return combineTwoAnnotations(annotation, typeVarTM, lowerBound, typeVarTM, top); + } + } else { + return combineTwoAnnotations( + annotation, + typeVarTM, + typeVar.getEffectiveAnnotationInHierarchy(top), + typeVarTM, + top); + } + } + } /** - * Returns the primary annotation that result from of combining the two {@link - * AnnotatedTypeVariable}. If the result has no primary annotation, {@code null} is returned. - * This method is called when no annotation exists in either sets for the hierarchy whose top is - * {@code top}. + * Combines two sets of AnnotationMirrors by hierarchy. + * + *

          Subclasses must define how to combine sets by implementing the following methods: * - * @param aAtv a type variable that does not have a primary annotation in {@code top} hierarchy - * @param bAtv a type variable that does not have a primary annotation in {@code top} hierarchy - * @param top the top annotation in the hierarchy - * @param canCombinedSetBeMissingAnnos whether or not - * @return the result of combining the two type variables, which may be null + *

            + *
          1. {@link #combineTwoAnnotations} + *
          2. {@link #combineAnnotationWithTypeVar} + *
          3. {@link #combineTwoTypeVars} + *
          + * + * If a set is missing an annotation in a hierarchy, and if the combined set can be missing an + * annotation, then there must be a TypeVariable for the set that can be used to find + * annotations on its bounds. */ - protected abstract @Nullable AnnotationMirror combineTwoTypeVars( - AnnotatedTypeVariable aAtv, - AnnotatedTypeVariable bAtv, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos); + protected abstract class AnnotationSetCombiner { + + /** + * Combines the two sets. + * + * @param aTypeMirror the type mirror associated with {@code aSet} + * @param aSet a set of annotation mirrors + * @param bTypeMirror the type mirror associated with {@code bSet} + * @param bSet a set of annotation mirrors + * @param canCombinedSetBeMissingAnnos whether or not the combined set can be missing + * annotations + * @return the combined sets + */ + protected AnnotationMirrorSet combineSets( + TypeMirror aTypeMirror, + AnnotationMirrorSet aSet, + TypeMirror bTypeMirror, + AnnotationMirrorSet bSet, + boolean canCombinedSetBeMissingAnnos) { + if (aTypeMirror == null) { + throw new NullPointerException("combineSets: aTypeMirror==null"); + } + if (bTypeMirror == null) { + throw new NullPointerException("combineSets: bTypeMirror==null"); + } + + AnnotatedTypeVariable aAtv = getEffectiveTypeVar(aTypeMirror); + AnnotatedTypeVariable bAtv = getEffectiveTypeVar(bTypeMirror); + QualifierHierarchy qualHierarchy = analysis.getTypeFactory().getQualifierHierarchy(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet combinedSets = new AnnotationMirrorSet(); + for (AnnotationMirror top : tops) { + AnnotationMirror a = qualHierarchy.findAnnotationInHierarchy(aSet, top); + AnnotationMirror b = qualHierarchy.findAnnotationInHierarchy(bSet, top); + AnnotationMirror result; + if (a != null && b != null) { + result = combineTwoAnnotations(a, aTypeMirror, b, bTypeMirror, top); + } else if (a != null) { + result = + combineAnnotationWithTypeVar( + a, bAtv, top, canCombinedSetBeMissingAnnos); + } else if (b != null) { + result = + combineAnnotationWithTypeVar( + b, aAtv, top, canCombinedSetBeMissingAnnos); + } else { + result = combineTwoTypeVars(aAtv, bAtv, top, canCombinedSetBeMissingAnnos); + } + if (result != null) { + combinedSets.add(result); + } + } + return combinedSets; + } + + /** + * Returns the result of combining the two annotations. This method is called when an + * annotation exists in both sets for the hierarchy whose top is {@code top}. + * + * @param a an annotation in the hierarchy + * @param aTypeMirror the type that is annotated by {@code a} + * @param b an annotation in the hierarchy + * @param bTypeMirror the type that is annotated by {@code b} + * @param top the top annotation in the hierarchy + * @return the result of combining the two annotations or null if no combination exists + */ + protected abstract @Nullable AnnotationMirror combineTwoAnnotations( + AnnotationMirror a, + TypeMirror aTypeMirror, + AnnotationMirror b, + TypeMirror bTypeMirror, + AnnotationMirror top); + + /** + * Returns the primary annotation that result from of combining the two {@link + * AnnotatedTypeVariable}. If the result has no primary annotation, {@code null} is + * returned. This method is called when no annotation exists in either sets for the + * hierarchy whose top is {@code top}. + * + * @param aAtv a type variable that does not have a primary annotation in {@code top} + * hierarchy + * @param bAtv a type variable that does not have a primary annotation in {@code top} + * hierarchy + * @param top the top annotation in the hierarchy + * @param canCombinedSetBeMissingAnnos whether or not + * @return the result of combining the two type variables, which may be null + */ + protected abstract @Nullable AnnotationMirror combineTwoTypeVars( + AnnotatedTypeVariable aAtv, + AnnotatedTypeVariable bAtv, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos); + + /** + * Returns the result of combining {@code annotation} with {@code typeVar}. + * + *

          This is called when an annotation exists for the hierarchy in one set, but not the + * other. + * + * @param annotation an annotation + * @param typeVar a type variable that does not have a primary annotation in the hierarchy + * @param top the top annotation of the hierarchy + * @param canCombinedSetBeMissingAnnos whether or not + * @return the result of combining {@code annotation} with {@code typeVar} + */ + protected abstract @Nullable AnnotationMirror combineAnnotationWithTypeVar( + AnnotationMirror annotation, + AnnotatedTypeVariable typeVar, + AnnotationMirror top, + boolean canCombinedSetBeMissingAnnos); + } /** - * Returns the result of combining {@code annotation} with {@code typeVar}. + * Returns the AnnotatedTypeVariable associated with the given TypeMirror or null. * - *

          This is called when an annotation exists for the hierarchy in one set, but not the other. + *

          If {@code typeMirror} is a type variable, then the {@link AnnotatedTypeVariable} of its + * declaration is returned. If {@code typeMirror} is a wildcard whose extends bounds is a type + * variable, then the {@link AnnotatedTypeVariable} for its declaration is returned. Otherwise, + * {@code null} is returned. * - * @param annotation an annotation - * @param typeVar a type variable that does not have a primary annotation in the hierarchy - * @param top the top annotation of the hierarchy - * @param canCombinedSetBeMissingAnnos whether or not - * @return the result of combining {@code annotation} with {@code typeVar} + * @param typeMirror a type mirror + * @return the AnnotatedTypeVariable associated with the given TypeMirror or null */ - protected abstract @Nullable AnnotationMirror combineAnnotationWithTypeVar( - AnnotationMirror annotation, - AnnotatedTypeVariable typeVar, - AnnotationMirror top, - boolean canCombinedSetBeMissingAnnos); - } - - /** - * Returns the AnnotatedTypeVariable associated with the given TypeMirror or null. - * - *

          If {@code typeMirror} is a type variable, then the {@link AnnotatedTypeVariable} of its - * declaration is returned. If {@code typeMirror} is a wildcard whose extends bounds is a type - * variable, then the {@link AnnotatedTypeVariable} for its declaration is returned. Otherwise, - * {@code null} is returned. - * - * @param typeMirror a type mirror - * @return the AnnotatedTypeVariable associated with the given TypeMirror or null - */ - private @Nullable AnnotatedTypeVariable getEffectiveTypeVar(@Nullable TypeMirror typeMirror) { - if (typeMirror == null) { - return null; - } else if (typeMirror.getKind() == TypeKind.WILDCARD) { - return getEffectiveTypeVar(((WildcardType) typeMirror).getExtendsBound()); - - } else if (typeMirror.getKind() == TypeKind.TYPEVAR) { - TypeVariable typevar = ((TypeVariable) typeMirror); - AnnotatedTypeMirror atm = analysis.getTypeFactory().getAnnotatedType(typevar.asElement()); - return (AnnotatedTypeVariable) atm; - } else { - return null; + private @Nullable AnnotatedTypeVariable getEffectiveTypeVar(@Nullable TypeMirror typeMirror) { + if (typeMirror == null) { + return null; + } else if (typeMirror.getKind() == TypeKind.WILDCARD) { + return getEffectiveTypeVar(((WildcardType) typeMirror).getExtendsBound()); + + } else if (typeMirror.getKind() == TypeKind.TYPEVAR) { + TypeVariable typevar = ((TypeVariable) typeMirror); + AnnotatedTypeMirror atm = + analysis.getTypeFactory().getAnnotatedType(typevar.asElement()); + return (AnnotatedTypeVariable) atm; + } else { + return null; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java index d0cea9a06b9..05a86465320 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAnalysis.java @@ -1,37 +1,38 @@ package org.checkerframework.framework.flow; -import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.type.TypeMirror; + /** The default org.checkerframework.dataflow analysis used in the Checker Framework. */ public class CFAnalysis extends CFAbstractAnalysis { - /** - * Creates a new {@code CFAnalysis}. - * - * @param checker the checker - * @param factory the factory - */ - public CFAnalysis( - BaseTypeChecker checker, - GenericAnnotatedTypeFactory factory) { - super(checker, factory); - } + /** + * Creates a new {@code CFAnalysis}. + * + * @param checker the checker + * @param factory the factory + */ + public CFAnalysis( + BaseTypeChecker checker, + GenericAnnotatedTypeFactory factory) { + super(checker, factory); + } - @Override - public CFStore createEmptyStore(boolean sequentialSemantics) { - return new CFStore(this, sequentialSemantics); - } + @Override + public CFStore createEmptyStore(boolean sequentialSemantics) { + return new CFStore(this, sequentialSemantics); + } - @Override - public CFStore createCopiedStore(CFStore s) { - return new CFStore(s); - } + @Override + public CFStore createCopiedStore(CFStore s) { + return new CFStore(s); + } - @Override - public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { - return defaultCreateAbstractValue(this, annotations, underlyingType); - } + @Override + public CFValue createAbstractValue(AnnotationMirrorSet annotations, TypeMirror underlyingType) { + return defaultCreateAbstractValue(this, annotations, underlyingType); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java b/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java index 9ca70e887e7..9a4b064933b 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFCFGBuilder.java @@ -7,13 +7,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.Collection; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.UnderlyingAST; @@ -29,197 +23,207 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.UserError; +import java.util.Collection; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * A control-flow graph builder (see {@link CFGBuilder}) that knows about the Checker Framework * annotations and their representation as {@link AnnotatedTypeMirror}s. */ public class CFCFGBuilder extends CFGBuilder { - /** This class should never be instantiated. Protected to still allow subclasses. */ - protected CFCFGBuilder() {} - - /** Build the control flow graph of some code. */ - public static ControlFlowGraph build( - CompilationUnitTree root, - UnderlyingAST underlyingAST, - BaseTypeChecker checker, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment env) { - boolean assumeAssertionsEnabled = checker.hasOption("assumeAssertionsAreEnabled"); - boolean assumeAssertionsDisabled = checker.hasOption("assumeAssertionsAreDisabled"); - if (assumeAssertionsEnabled && assumeAssertionsDisabled) { - throw new UserError( - "Assertions cannot be assumed to be enabled and disabled at the same time."); - } + /** This class should never be instantiated. Protected to still allow subclasses. */ + protected CFCFGBuilder() {} + + /** Build the control flow graph of some code. */ + public static ControlFlowGraph build( + CompilationUnitTree root, + UnderlyingAST underlyingAST, + BaseTypeChecker checker, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment env) { + boolean assumeAssertionsEnabled = checker.hasOption("assumeAssertionsAreEnabled"); + boolean assumeAssertionsDisabled = checker.hasOption("assumeAssertionsAreDisabled"); + if (assumeAssertionsEnabled && assumeAssertionsDisabled) { + throw new UserError( + "Assertions cannot be assumed to be enabled and disabled at the same time."); + } - // Subcheckers with dataflow share control-flow graph structure to - // allow a super-checker to query the stores of a subchecker. - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory asGATF = - (GenericAnnotatedTypeFactory) atypeFactory; - if (asGATF.hasOrIsSubchecker) { - ControlFlowGraph sharedCFG = asGATF.getSharedCFGForTree(underlyingAST.getCode()); - if (sharedCFG != null) { - return sharedCFG; + // Subcheckers with dataflow share control-flow graph structure to + // allow a super-checker to query the stores of a subchecker. + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + GenericAnnotatedTypeFactory asGATF = + (GenericAnnotatedTypeFactory) atypeFactory; + if (asGATF.hasOrIsSubchecker) { + ControlFlowGraph sharedCFG = asGATF.getSharedCFGForTree(underlyingAST.getCode()); + if (sharedCFG != null) { + return sharedCFG; + } + } } - } - } - CFTreeBuilder builder = new CFTreeBuilder(env); - PhaseOneResult phase1result = - new CFCFGTranslationPhaseOne( - builder, - checker, - atypeFactory, - assumeAssertionsEnabled, - assumeAssertionsDisabled, - env) - .process(root, underlyingAST); - ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); - ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - GenericAnnotatedTypeFactory asGATF = - (GenericAnnotatedTypeFactory) atypeFactory; - if (asGATF.hasOrIsSubchecker) { - asGATF.addSharedCFGForTree(underlyingAST.getCode(), phase3result); - } - } - return phase3result; - } - - /** - * Given a SourceChecker and an AssertTree, returns whether the AssertTree uses - * an @AssumeAssertion string that is relevant to the SourceChecker. - * - * @param checker the checker - * @param tree an assert tree - * @return true if the assert tree contains an @AssumeAssertion(checker) message string for any - * subchecker of the given checker's ultimate parent checker - */ - public static boolean assumeAssertionsActivatedForAssertTree( - BaseTypeChecker checker, AssertTree tree) { - ExpressionTree detail = tree.getDetail(); - if (detail != null) { - String msg = detail.toString(); - BaseTypeChecker ultimateParent = checker.getUltimateParentChecker(); - Collection prefixes = ultimateParent.getSuppressWarningsPrefixesOfSubcheckers(); - for (String prefix : prefixes) { - String assumeAssert = "@AssumeAssertion(" + prefix + ")"; - if (msg.contains(assumeAssert)) { - return true; + CFTreeBuilder builder = new CFTreeBuilder(env); + PhaseOneResult phase1result = + new CFCFGTranslationPhaseOne( + builder, + checker, + atypeFactory, + assumeAssertionsEnabled, + assumeAssertionsDisabled, + env) + .process(root, underlyingAST); + ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + GenericAnnotatedTypeFactory asGATF = + (GenericAnnotatedTypeFactory) atypeFactory; + if (asGATF.hasOrIsSubchecker) { + asGATF.addSharedCFGForTree(underlyingAST.getCode(), phase3result); + } } - } + return phase3result; } - return false; - } - - /** - * A specialized phase-one CFG builder, with a few modifications that make use of the type - * factory. It is responsible for: 1) translating foreach loops so that the declarations of their - * iteration variables have the right annotations, 2) registering the containing elements of - * artificial trees with the relevant type factories, and 3) generating appropriate assertion CFG - * structure in the presence of @AssumeAssertion assertion strings which mention the checker or - * its supercheckers. - */ - protected static class CFCFGTranslationPhaseOne extends CFGTranslationPhaseOne { - /** The associated checker. */ - protected final BaseTypeChecker checker; - - /** Type factory to provide types used during CFG building. */ - protected final AnnotatedTypeFactory atypeFactory; - - public CFCFGTranslationPhaseOne( - CFTreeBuilder builder, - BaseTypeChecker checker, - AnnotatedTypeFactory atypeFactory, - boolean assumeAssertionsEnabled, - boolean assumeAssertionsDisabled, - ProcessingEnvironment env) { - super(builder, atypeFactory, assumeAssertionsEnabled, assumeAssertionsDisabled, env); - this.checker = checker; - this.atypeFactory = atypeFactory; - } + /** + * Given a SourceChecker and an AssertTree, returns whether the AssertTree uses + * an @AssumeAssertion string that is relevant to the SourceChecker. + * + * @param checker the checker + * @param tree an assert tree + * @return true if the assert tree contains an @AssumeAssertion(checker) message string for any + * subchecker of the given checker's ultimate parent checker + */ + public static boolean assumeAssertionsActivatedForAssertTree( + BaseTypeChecker checker, AssertTree tree) { + ExpressionTree detail = tree.getDetail(); + if (detail != null) { + String msg = detail.toString(); + BaseTypeChecker ultimateParent = checker.getUltimateParentChecker(); + Collection prefixes = ultimateParent.getSuppressWarningsPrefixesOfSubcheckers(); + for (String prefix : prefixes) { + String assumeAssert = "@AssumeAssertion(" + prefix + ")"; + if (msg.contains(assumeAssert)) { + return true; + } + } + } - @Override - protected boolean assumeAssertionsEnabledFor(AssertTree tree) { - if (assumeAssertionsActivatedForAssertTree(checker, tree)) { - return true; - } - return super.assumeAssertionsEnabledFor(tree); + return false; } /** - * {@inheritDoc} - * - *

          Assigns a path to the artificial tree. - * - * @param tree the newly created Tree + * A specialized phase-one CFG builder, with a few modifications that make use of the type + * factory. It is responsible for: 1) translating foreach loops so that the declarations of + * their iteration variables have the right annotations, 2) registering the containing elements + * of artificial trees with the relevant type factories, and 3) generating appropriate assertion + * CFG structure in the presence of @AssumeAssertion assertion strings which mention the checker + * or its supercheckers. */ - @Override - public void handleArtificialTree(Tree tree) { - // Create a new child of the current path and assign to the artificial tree. - // Although intuitively, using the sibling of the current path as the artificial tree - // path makes more sense, it has the risk of improperly changing the defaulting scope - // of the artificial tree. - TreePath artificialPath = new TreePath(getCurrentPath(), tree); - atypeFactory.setPathForArtificialTree(tree, artificialPath); - } + protected static class CFCFGTranslationPhaseOne extends CFGTranslationPhaseOne { + /** The associated checker. */ + protected final BaseTypeChecker checker; + + /** Type factory to provide types used during CFG building. */ + protected final AnnotatedTypeFactory atypeFactory; + + public CFCFGTranslationPhaseOne( + CFTreeBuilder builder, + BaseTypeChecker checker, + AnnotatedTypeFactory atypeFactory, + boolean assumeAssertionsEnabled, + boolean assumeAssertionsDisabled, + ProcessingEnvironment env) { + super(builder, atypeFactory, assumeAssertionsEnabled, assumeAssertionsDisabled, env); + this.checker = checker; + this.atypeFactory = atypeFactory; + } - @Override - protected VariableTree createEnhancedForLoopIteratorVariable( - MethodInvocationTree iteratorCall, VariableElement variableElement) { - Tree annotatedIteratorTypeTree = - ((CFTreeBuilder) treeBuilder).buildAnnotatedType(TreeUtils.typeOf(iteratorCall)); - handleArtificialTree(annotatedIteratorTypeTree); - - // Declare and initialize a new, unique iterator variable - VariableTree iteratorVariable = - treeBuilder.buildVariableDecl( - annotatedIteratorTypeTree, - uniqueName("iter"), - variableElement.getEnclosingElement(), - iteratorCall); - return iteratorVariable; - } + @Override + protected boolean assumeAssertionsEnabledFor(AssertTree tree) { + if (assumeAssertionsActivatedForAssertTree(checker, tree)) { + return true; + } + return super.assumeAssertionsEnabledFor(tree); + } + + /** + * {@inheritDoc} + * + *

          Assigns a path to the artificial tree. + * + * @param tree the newly created Tree + */ + @Override + public void handleArtificialTree(Tree tree) { + // Create a new child of the current path and assign to the artificial tree. + // Although intuitively, using the sibling of the current path as the artificial tree + // path makes more sense, it has the risk of improperly changing the defaulting scope + // of the artificial tree. + TreePath artificialPath = new TreePath(getCurrentPath(), tree); + atypeFactory.setPathForArtificialTree(tree, artificialPath); + } + + @Override + protected VariableTree createEnhancedForLoopIteratorVariable( + MethodInvocationTree iteratorCall, VariableElement variableElement) { + Tree annotatedIteratorTypeTree = + ((CFTreeBuilder) treeBuilder) + .buildAnnotatedType(TreeUtils.typeOf(iteratorCall)); + handleArtificialTree(annotatedIteratorTypeTree); + + // Declare and initialize a new, unique iterator variable + VariableTree iteratorVariable = + treeBuilder.buildVariableDecl( + annotatedIteratorTypeTree, + uniqueName("iter"), + variableElement.getEnclosingElement(), + iteratorCall); + return iteratorVariable; + } - @Override - protected VariableTree createEnhancedForLoopArrayVariable( - ExpressionTree expression, VariableElement variableElement) { - - TypeMirror type = null; - if (TreeUtils.isLocalVariable(expression)) { - // It is necessary to get the elt because just getting the type of expression - // directly (via TreeUtils.typeOf) doesn't include annotations on the declarations - // of local variables, for some reason. - Element elt = TreeUtils.elementFromTree(expression); - if (elt != null) { - type = ElementUtils.getType(elt); + @Override + protected VariableTree createEnhancedForLoopArrayVariable( + ExpressionTree expression, VariableElement variableElement) { + + TypeMirror type = null; + if (TreeUtils.isLocalVariable(expression)) { + // It is necessary to get the elt because just getting the type of expression + // directly (via TreeUtils.typeOf) doesn't include annotations on the declarations + // of local variables, for some reason. + Element elt = TreeUtils.elementFromTree(expression); + if (elt != null) { + type = ElementUtils.getType(elt); + } + } + + // In all other cases, instead get the type of the expression. This case is + // also triggered when the type from the element is not an array, which can occur + // if the declaration of the local is a generic, such as in + // framework/tests/all-systems/java8inference/Issue1775.java. + // Getting the type from the expression itself guarantees the result will be an array. + if (type == null || type.getKind() != TypeKind.ARRAY) { + TypeMirror expressionType = TreeUtils.typeOf(expression); + type = expressionType; + } + + assert (type instanceof ArrayType) : "array types must be represented by ArrayType"; + + Tree annotatedArrayTypeTree = ((CFTreeBuilder) treeBuilder).buildAnnotatedType(type); + handleArtificialTree(annotatedArrayTypeTree); + + // Declare and initialize a temporary array variable + VariableTree arrayVariable = + treeBuilder.buildVariableDecl( + annotatedArrayTypeTree, + uniqueName("array"), + variableElement.getEnclosingElement(), + expression); + return arrayVariable; } - } - - // In all other cases, instead get the type of the expression. This case is - // also triggered when the type from the element is not an array, which can occur - // if the declaration of the local is a generic, such as in - // framework/tests/all-systems/java8inference/Issue1775.java. - // Getting the type from the expression itself guarantees the result will be an array. - if (type == null || type.getKind() != TypeKind.ARRAY) { - TypeMirror expressionType = TreeUtils.typeOf(expression); - type = expressionType; - } - - assert (type instanceof ArrayType) : "array types must be represented by ArrayType"; - - Tree annotatedArrayTypeTree = ((CFTreeBuilder) treeBuilder).buildAnnotatedType(type); - handleArtificialTree(annotatedArrayTypeTree); - - // Declare and initialize a temporary array variable - VariableTree arrayVariable = - treeBuilder.buildVariableDecl( - annotatedArrayTypeTree, - uniqueName("array"), - variableElement.getEnclosingElement(), - expression); - return arrayVariable; } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java index b8024425894..a2c6c17e7ed 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFStore.java @@ -3,16 +3,16 @@ /** The default store used in the Checker Framework. */ public class CFStore extends CFAbstractStore { - public CFStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - } + public CFStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } - /** - * Copy constructor. - * - * @param other the CFStore to copy - */ - public CFStore(CFAbstractStore other) { - super(other); - } + /** + * Copy constructor. + * + * @param other the CFStore to copy + */ + public CFStore(CFAbstractStore other) { + super(other); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java index 55905ac0adc..d463d43362a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFTransfer.java @@ -3,7 +3,7 @@ /** The default transfer function used in the Checker Framework. */ public class CFTransfer extends CFAbstractTransfer { - public CFTransfer(CFAbstractAnalysis analysis) { - super(analysis); - } + public CFTransfer(CFAbstractAnalysis analysis) { + super(analysis); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java b/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java index 15a7952d72a..8a9ff638724 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFTreeBuilder.java @@ -12,9 +12,14 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.util.List; + +import org.checkerframework.javacutil.TypeAnnotationUtils; +import org.checkerframework.javacutil.trees.TreeBuilder; + import java.util.Collection; import java.util.HashSet; import java.util.Set; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.ArrayType; @@ -23,8 +28,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.trees.TreeBuilder; /** * The TreeBuilder permits the creation of new AST Trees using the non-public Java compiler API @@ -33,173 +36,178 @@ */ public class CFTreeBuilder extends TreeBuilder { - /** - * To avoid infinite recursions, record each wildcard that has been converted to a tree. This set - * is cleared each time {@link #buildAnnotatedType(TypeMirror)} is called. - */ - private final Set visitedWildcards = new HashSet<>(); - - /** - * Creates a {@code CFTreeBuilder}. - * - * @param env environment - */ - public CFTreeBuilder(ProcessingEnvironment env) { - super(env); - } - - /** - * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. - * - * @param type the type - * @return a Tree representing the type - */ - public Tree buildAnnotatedType(TypeMirror type) { - visitedWildcards.clear(); - return createAnnotatedType(type); - } - - /** - * Converts a list of AnnotationMirrors to the a corresponding list of new AnnotationTrees. - * - * @param annotations the annotations - * @return new annotation trees representing the annotations - */ - private List convertAnnotationMirrorsToAnnotationTrees( - Collection annotations) { - List annotationTrees = List.nil(); - - for (AnnotationMirror am : annotations) { - // TODO: what TypeAnnotationPosition should be used? - Attribute.TypeCompound typeCompound = - TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( - am, TypeAnnotationUtils.unknownTAPosition(), env); - JCAnnotation annotationTree = maker.Annotation(typeCompound); - JCAnnotation typeAnnotationTree = - maker.TypeAnnotation(annotationTree.getAnnotationType(), annotationTree.getArguments()); - - typeAnnotationTree.attribute = typeCompound; - - annotationTrees = annotationTrees.append(typeAnnotationTree); + /** + * To avoid infinite recursions, record each wildcard that has been converted to a tree. This + * set is cleared each time {@link #buildAnnotatedType(TypeMirror)} is called. + */ + private final Set visitedWildcards = new HashSet<>(); + + /** + * Creates a {@code CFTreeBuilder}. + * + * @param env environment + */ + public CFTreeBuilder(ProcessingEnvironment env) { + super(env); } - return annotationTrees; - } - - /** - * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. This - * internal method differs from the public {@link #buildAnnotatedType(TypeMirror)} only in that it - * does not reset the list of visited wildcards. - * - * @param type the type for which to create a tree - * @return a Tree representing the type - */ - private Tree createAnnotatedType(TypeMirror type) { - // Implementation based on com.sun.tools.javac.tree.TreeMaker.Type - - // Convert the annotations from a set of AnnotationMirrors - // to a list of AnnotationTrees. - java.util.List annotations = type.getAnnotationMirrors(); - List annotationTrees = convertAnnotationMirrorsToAnnotationTrees(annotations); - - // Convert the underlying type from a TypeMirror to an ExpressionTree and combine with the - // AnnotationTrees to form a ClassTree of kind ANNOTATION_TYPE. - JCExpression typeTree; - switch (type.getKind()) { - case BYTE: - typeTree = maker.TypeIdent(TypeTag.BYTE); - break; - case CHAR: - typeTree = maker.TypeIdent(TypeTag.CHAR); - break; - case SHORT: - typeTree = maker.TypeIdent(TypeTag.SHORT); - break; - case INT: - typeTree = maker.TypeIdent(TypeTag.INT); - break; - case LONG: - typeTree = maker.TypeIdent(TypeTag.LONG); - break; - case FLOAT: - typeTree = maker.TypeIdent(TypeTag.FLOAT); - break; - case DOUBLE: - typeTree = maker.TypeIdent(TypeTag.DOUBLE); - break; - case BOOLEAN: - typeTree = maker.TypeIdent(TypeTag.BOOLEAN); - break; - case VOID: - typeTree = maker.TypeIdent(TypeTag.VOID); - break; - case TYPEVAR: - // No recursive annotations. - TypeVariable underlyingTypeVar = (TypeVariable) type; - typeTree = maker.Ident((TypeSymbol) underlyingTypeVar.asElement()); - break; - case WILDCARD: - WildcardType wildcardType = (WildcardType) type; - boolean visitedBefore = !visitedWildcards.add(wildcardType); - if (!visitedBefore && wildcardType.getExtendsBound() != null) { - Tree annotatedExtendsBound = createAnnotatedType(wildcardType.getExtendsBound()); - typeTree = - maker.Wildcard( - maker.TypeBoundKind(BoundKind.EXTENDS), (JCTree) annotatedExtendsBound); - } else if (!visitedBefore && wildcardType.getSuperBound() != null) { - Tree annotatedSuperBound = createAnnotatedType(wildcardType.getSuperBound()); - typeTree = - maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), (JCTree) annotatedSuperBound); - } else { - typeTree = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); - } - break; - case INTERSECTION: - IntersectionType intersectionType = (IntersectionType) type; - List components = List.nil(); - for (TypeMirror bound : intersectionType.getBounds()) { - components = components.append((JCExpression) createAnnotatedType(bound)); - } - typeTree = maker.TypeIntersection(components); - break; - // case UNION: - // TODO: case UNION similar to INTERSECTION, but write test first. - case DECLARED: - typeTree = maker.Type((Type) type); - - if (typeTree instanceof JCTypeApply) { - // Replace the type parameters with annotated versions. - DeclaredType annotatedDeclaredType = (DeclaredType) type; - List typeArgTrees = List.nil(); - for (TypeMirror arg : annotatedDeclaredType.getTypeArguments()) { - typeArgTrees = typeArgTrees.append((JCExpression) createAnnotatedType(arg)); - } - JCExpression clazz = (JCExpression) ((JCTypeApply) typeTree).getType(); - typeTree = maker.TypeApply(clazz, typeArgTrees); + + /** + * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. + * + * @param type the type + * @return a Tree representing the type + */ + public Tree buildAnnotatedType(TypeMirror type) { + visitedWildcards.clear(); + return createAnnotatedType(type); + } + + /** + * Converts a list of AnnotationMirrors to the a corresponding list of new AnnotationTrees. + * + * @param annotations the annotations + * @return new annotation trees representing the annotations + */ + private List convertAnnotationMirrorsToAnnotationTrees( + Collection annotations) { + List annotationTrees = List.nil(); + + for (AnnotationMirror am : annotations) { + // TODO: what TypeAnnotationPosition should be used? + Attribute.TypeCompound typeCompound = + TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( + am, TypeAnnotationUtils.unknownTAPosition(), env); + JCAnnotation annotationTree = maker.Annotation(typeCompound); + JCAnnotation typeAnnotationTree = + maker.TypeAnnotation( + annotationTree.getAnnotationType(), annotationTree.getArguments()); + + typeAnnotationTree.attribute = typeCompound; + + annotationTrees = annotationTrees.append(typeAnnotationTree); } - break; - case ARRAY: - ArrayType arrayType = (ArrayType) type; - Tree componentTree = createAnnotatedType(arrayType.getComponentType()); - typeTree = maker.TypeArray((JCExpression) componentTree); - break; - case ERROR: - typeTree = maker.TypeIdent(TypeTag.ERROR); - break; - default: - assert false : "unexpected type: " + type; - typeTree = null; - break; + return annotationTrees; } - typeTree.setType((Type) type); + /** + * Builds an AST Tree representing a type, including AnnotationTrees for its annotations. This + * internal method differs from the public {@link #buildAnnotatedType(TypeMirror)} only in that + * it does not reset the list of visited wildcards. + * + * @param type the type for which to create a tree + * @return a Tree representing the type + */ + private Tree createAnnotatedType(TypeMirror type) { + // Implementation based on com.sun.tools.javac.tree.TreeMaker.Type + + // Convert the annotations from a set of AnnotationMirrors + // to a list of AnnotationTrees. + java.util.List annotations = type.getAnnotationMirrors(); + List annotationTrees = convertAnnotationMirrorsToAnnotationTrees(annotations); + + // Convert the underlying type from a TypeMirror to an ExpressionTree and combine with the + // AnnotationTrees to form a ClassTree of kind ANNOTATION_TYPE. + JCExpression typeTree; + switch (type.getKind()) { + case BYTE: + typeTree = maker.TypeIdent(TypeTag.BYTE); + break; + case CHAR: + typeTree = maker.TypeIdent(TypeTag.CHAR); + break; + case SHORT: + typeTree = maker.TypeIdent(TypeTag.SHORT); + break; + case INT: + typeTree = maker.TypeIdent(TypeTag.INT); + break; + case LONG: + typeTree = maker.TypeIdent(TypeTag.LONG); + break; + case FLOAT: + typeTree = maker.TypeIdent(TypeTag.FLOAT); + break; + case DOUBLE: + typeTree = maker.TypeIdent(TypeTag.DOUBLE); + break; + case BOOLEAN: + typeTree = maker.TypeIdent(TypeTag.BOOLEAN); + break; + case VOID: + typeTree = maker.TypeIdent(TypeTag.VOID); + break; + case TYPEVAR: + // No recursive annotations. + TypeVariable underlyingTypeVar = (TypeVariable) type; + typeTree = maker.Ident((TypeSymbol) underlyingTypeVar.asElement()); + break; + case WILDCARD: + WildcardType wildcardType = (WildcardType) type; + boolean visitedBefore = !visitedWildcards.add(wildcardType); + if (!visitedBefore && wildcardType.getExtendsBound() != null) { + Tree annotatedExtendsBound = + createAnnotatedType(wildcardType.getExtendsBound()); + typeTree = + maker.Wildcard( + maker.TypeBoundKind(BoundKind.EXTENDS), + (JCTree) annotatedExtendsBound); + } else if (!visitedBefore && wildcardType.getSuperBound() != null) { + Tree annotatedSuperBound = createAnnotatedType(wildcardType.getSuperBound()); + typeTree = + maker.Wildcard( + maker.TypeBoundKind(BoundKind.SUPER), + (JCTree) annotatedSuperBound); + } else { + typeTree = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + } + break; + case INTERSECTION: + IntersectionType intersectionType = (IntersectionType) type; + List components = List.nil(); + for (TypeMirror bound : intersectionType.getBounds()) { + components = components.append((JCExpression) createAnnotatedType(bound)); + } + typeTree = maker.TypeIntersection(components); + break; + // case UNION: + // TODO: case UNION similar to INTERSECTION, but write test first. + case DECLARED: + typeTree = maker.Type((Type) type); + + if (typeTree instanceof JCTypeApply) { + // Replace the type parameters with annotated versions. + DeclaredType annotatedDeclaredType = (DeclaredType) type; + List typeArgTrees = List.nil(); + for (TypeMirror arg : annotatedDeclaredType.getTypeArguments()) { + typeArgTrees = typeArgTrees.append((JCExpression) createAnnotatedType(arg)); + } + JCExpression clazz = (JCExpression) ((JCTypeApply) typeTree).getType(); + typeTree = maker.TypeApply(clazz, typeArgTrees); + } + break; + case ARRAY: + ArrayType arrayType = (ArrayType) type; + Tree componentTree = createAnnotatedType(arrayType.getComponentType()); + typeTree = maker.TypeArray((JCExpression) componentTree); + break; + case ERROR: + typeTree = maker.TypeIdent(TypeTag.ERROR); + break; + default: + assert false : "unexpected type: " + type; + typeTree = null; + break; + } + + typeTree.setType((Type) type); - if (annotationTrees.isEmpty()) { - return typeTree; - } + if (annotationTrees.isEmpty()) { + return typeTree; + } - JCAnnotatedType annotatedTypeTree = maker.AnnotatedType(annotationTrees, typeTree); - annotatedTypeTree.setType((Type) type); + JCAnnotatedType annotatedTypeTree = maker.AnnotatedType(annotationTrees, typeTree); + annotatedTypeTree.setType((Type) type); - return annotatedTypeTree; - } + return annotatedTypeTree; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java index a268e23b09c..fe75cdfda6a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFValue.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.flow; -import javax.lang.model.type.TypeMirror; import org.checkerframework.javacutil.AnnotationMirrorSet; +import javax.lang.model.type.TypeMirror; + // TODO: CFAbstractValue is also a set of annotations and a TypeMirror. // This documentation does not clarify how this class is different. /** @@ -10,17 +11,17 @@ */ public class CFValue extends CFAbstractValue { - /** - * Creates a new CFValue. - * - * @param analysis the analysis - * @param annotations the annotations - * @param underlyingType the underlying type - */ - public CFValue( - CFAbstractAnalysis analysis, - AnnotationMirrorSet annotations, - TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - } + /** + * Creates a new CFValue. + * + * @param analysis the analysis + * @param annotations the annotations + * @param underlyingType the underlying type + */ + public CFValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java index 437a5554a0a..c9d49420cdb 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java @@ -4,6 +4,9 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -13,10 +16,10 @@ import java.util.List; import java.util.Map; import java.util.Set; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * An aggregate checker that packages multiple checkers together. The resulting checker invokes the @@ -33,159 +36,162 @@ */ public abstract class AggregateChecker extends SourceChecker { - protected final List checkers; - - /** - * Returns the list of supported checkers to be run together. Subclasses need to override this - * method. - * - * @return the list of checkers to be run - */ - protected abstract Collection> getSupportedCheckers(); - - /** Supported options for this checker. */ - private @MonotonicNonNull Set supportedOptions = null; - - /** Options passed to this checker. */ - private @MonotonicNonNull Map options = null; - - /** Create a new AggregateChecker. */ - protected AggregateChecker() { - Collection> checkerClasses = getSupportedCheckers(); - - checkers = new ArrayList<>(checkerClasses.size()); - for (Class checkerClass : checkerClasses) { - try { - SourceChecker instance = checkerClass.getDeclaredConstructor().newInstance(); - instance.setParentChecker(this); - checkers.add(instance); - } catch (Exception e) { - message(Diagnostic.Kind.ERROR, "Couldn't instantiate an instance of " + checkerClass); - } - } - } - - /** - * {@code processingEnv} needs to be set on each checker since we are not calling init on the - * checker, which leaves it null. If one of checkers is an AggregateChecker, its visitors will try - * use checker's processing env which should not be null. - */ - @Override - protected void setProcessingEnvironment(ProcessingEnvironment env) { - super.setProcessingEnvironment(env); - for (SourceChecker checker : checkers) { - checker.setProcessingEnvironment(env); + protected final List checkers; + + /** + * Returns the list of supported checkers to be run together. Subclasses need to override this + * method. + * + * @return the list of checkers to be run + */ + protected abstract Collection> getSupportedCheckers(); + + /** Supported options for this checker. */ + private @MonotonicNonNull Set supportedOptions = null; + + /** Options passed to this checker. */ + private @MonotonicNonNull Map options = null; + + /** Create a new AggregateChecker. */ + protected AggregateChecker() { + Collection> checkerClasses = getSupportedCheckers(); + + checkers = new ArrayList<>(checkerClasses.size()); + for (Class checkerClass : checkerClasses) { + try { + SourceChecker instance = checkerClass.getDeclaredConstructor().newInstance(); + instance.setParentChecker(this); + checkers.add(instance); + } catch (Exception e) { + message( + Diagnostic.Kind.ERROR, + "Couldn't instantiate an instance of " + checkerClass); + } + } } - } - - @Override - public void initChecker() { - // No need to call super, it might result in reflective instantiations - // of visitor/factory classes. - // super.initChecker(); - // To prevent the warning that initChecker wasn't called. - messager = processingEnv.getMessager(); - - // first initialize all checkers - for (SourceChecker checker : checkers) { - checker.initChecker(); - } - // then share options as necessary - for (SourceChecker checker : checkers) { - // We need to add all options that are activated for the aggregate to - // the individual checkers. - checker.addOptions(super.getOptions()); - // Each checker should "support" all possible lint options - otherwise - // subchecker A would complain about a lint option for subchecker B. - checker.setSupportedLintOptions(this.getSupportedLintOptions()); + + /** + * {@code processingEnv} needs to be set on each checker since we are not calling init on the + * checker, which leaves it null. If one of checkers is an AggregateChecker, its visitors will + * try use checker's processing env which should not be null. + */ + @Override + protected void setProcessingEnvironment(ProcessingEnvironment env) { + super.setProcessingEnvironment(env); + for (SourceChecker checker : checkers) { + checker.setProcessingEnvironment(env); + } } - allCheckersInited = true; - } - - // Whether all checkers were successfully initialized. - private boolean allCheckersInited = false; - - // AbstractTypeProcessor delegation - @Override - public final void typeProcess(TypeElement element, TreePath tree) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Log log = Log.instance(context); - if (log.nerrors > this.errsOnLastExit) { - // If there is a Java error, do not perform any of the component type checks, but come - // back for the next compilation unit. - this.errsOnLastExit = log.nerrors; - return; + + @Override + public void initChecker() { + // No need to call super, it might result in reflective instantiations + // of visitor/factory classes. + // super.initChecker(); + // To prevent the warning that initChecker wasn't called. + messager = processingEnv.getMessager(); + + // first initialize all checkers + for (SourceChecker checker : checkers) { + checker.initChecker(); + } + // then share options as necessary + for (SourceChecker checker : checkers) { + // We need to add all options that are activated for the aggregate to + // the individual checkers. + checker.addOptions(super.getOptions()); + // Each checker should "support" all possible lint options - otherwise + // subchecker A would complain about a lint option for subchecker B. + checker.setSupportedLintOptions(this.getSupportedLintOptions()); + } + allCheckersInited = true; } - if (!allCheckersInited) { - // If there was an initialization problem, an - // error was already output. Just quit. - return; + + // Whether all checkers were successfully initialized. + private boolean allCheckersInited = false; + + // AbstractTypeProcessor delegation + @Override + public final void typeProcess(TypeElement element, TreePath tree) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Log log = Log.instance(context); + if (log.nerrors > this.errsOnLastExit) { + // If there is a Java error, do not perform any of the component type checks, but come + // back for the next compilation unit. + this.errsOnLastExit = log.nerrors; + return; + } + if (!allCheckersInited) { + // If there was an initialization problem, an + // error was already output. Just quit. + return; + } + for (SourceChecker checker : checkers) { + checker.errsOnLastExit = this.errsOnLastExit; + checker.typeProcess(element, tree); + if (checker.javacErrored) { + this.javacErrored = true; + return; + } + this.errsOnLastExit = checker.errsOnLastExit; + } } - for (SourceChecker checker : checkers) { - checker.errsOnLastExit = this.errsOnLastExit; - checker.typeProcess(element, tree); - if (checker.javacErrored) { - this.javacErrored = true; - return; - } - this.errsOnLastExit = checker.errsOnLastExit; + + @Override + public void typeProcessingOver() { + for (SourceChecker checker : checkers) { + checker.typeProcessingOver(); + } + super.typeProcessingOver(); } - } - @Override - public void typeProcessingOver() { - for (SourceChecker checker : checkers) { - checker.typeProcessingOver(); + @Override + public final Set getSupportedOptions() { + if (this.supportedOptions == null) { + Set options = new HashSet<>(); + for (SourceChecker checker : checkers) { + options.addAll(checker.getSupportedOptions()); + } + options.addAll( + expandCFOptions( + Arrays.asList(this.getClass()), options.toArray(new String[0]))); + this.supportedOptions = options; + } + return this.supportedOptions; } - super.typeProcessingOver(); - } - - @Override - public final Set getSupportedOptions() { - if (this.supportedOptions == null) { - Set options = new HashSet<>(); - for (SourceChecker checker : checkers) { - options.addAll(checker.getSupportedOptions()); - } - options.addAll( - expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0]))); - this.supportedOptions = options; + + @Override + public final Map getOptions() { + if (this.options == null) { + Map options = new HashMap<>(super.getOptions()); + for (SourceChecker checker : checkers) { + options.putAll(checker.getOptions()); + } + this.options = Collections.unmodifiableMap(options); + } + return this.options; } - return this.supportedOptions; - } - - @Override - public final Map getOptions() { - if (this.options == null) { - Map options = new HashMap<>(super.getOptions()); - for (SourceChecker checker : checkers) { - options.putAll(checker.getOptions()); - } - this.options = Collections.unmodifiableMap(options); + + @Override + public final Set getSupportedLintOptions() { + Set lints = new HashSet<>(); + for (SourceChecker checker : checkers) { + lints.addAll(checker.getSupportedLintOptions()); + } + return lints; } - return this.options; - } - - @Override - public final Set getSupportedLintOptions() { - Set lints = new HashSet<>(); - for (SourceChecker checker : checkers) { - lints.addAll(checker.getSupportedLintOptions()); + + @Override + protected SourceVisitor createSourceVisitor() { + return new SourceVisitor(this) { + // Aggregate checkers do not visit source, + // the checkers in the aggregate checker do. + }; } - return lints; - } - - @Override - protected SourceVisitor createSourceVisitor() { - return new SourceVisitor(this) { - // Aggregate checkers do not visit source, - // the checkers in the aggregate checker do. - }; - } - - // TODO some methods in a component checker should behave differently if they - // are part of an aggregate, e.g. getSuppressWarningKeys should additionally - // return the name of the aggregate checker. - // We could add a query method in SourceChecker that refers to the aggregate, if present. - // At the moment, all the component checkers manually need to add the name of the aggregate. + + // TODO some methods in a component checker should behave differently if they + // are part of an aggregate, e.g. getSuppressWarningKeys should additionally + // return the name of the aggregate checker. + // We could add a query method in SourceChecker that refers to the aggregate, if present. + // At the moment, all the component checkers manually need to add the name of the aggregate. } diff --git a/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java index 00412d95f84..80312064795 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java +++ b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java @@ -1,16 +1,18 @@ package org.checkerframework.framework.source; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import javax.tools.Diagnostic; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.framework.qual.AnnotatedFor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import javax.tools.Diagnostic; + /** * A {@code DiagMessage} is a kind, a message key, and arguments. The message key will be expanded * according to the user locale. Any arguments will then be interpolated into the localized message. @@ -19,117 +21,118 @@ */ @AnnotatedFor("nullness") public class DiagMessage { - /** The kind of message. */ - private final Diagnostic.Kind kind; - - /** The message key. */ - private final @CompilerMessageKey String messageKey; - - /** The arguments that will be interpolated into the localized message. */ - private final Object[] args; - - /** - * Create a DiagMessage. - * - * @param kind the kind of message - * @param messageKey the message key - * @param args the arguments that will be interpolated into the localized message - */ - public DiagMessage(Diagnostic.Kind kind, @CompilerMessageKey String messageKey, Object... args) { - this.kind = kind; - this.messageKey = messageKey; - if (args == null) { - this.args = new Object[0]; /*null->nn*/ - } else { - this.args = Arrays.copyOf(args, args.length); + /** The kind of message. */ + private final Diagnostic.Kind kind; + + /** The message key. */ + private final @CompilerMessageKey String messageKey; + + /** The arguments that will be interpolated into the localized message. */ + private final Object[] args; + + /** + * Create a DiagMessage. + * + * @param kind the kind of message + * @param messageKey the message key + * @param args the arguments that will be interpolated into the localized message + */ + public DiagMessage( + Diagnostic.Kind kind, @CompilerMessageKey String messageKey, Object... args) { + this.kind = kind; + this.messageKey = messageKey; + if (args == null) { + this.args = new Object[0]; /*null->nn*/ + } else { + this.args = Arrays.copyOf(args, args.length); + } } - } - - /** - * Create a DiagMessage with kind ERROR. - * - * @param messageKey the message key - * @param args the arguments that will be interpolated into the localized message - * @return a new DiagMessage - */ - public static DiagMessage error(@CompilerMessageKey String messageKey, Object... args) { - return new DiagMessage(Diagnostic.Kind.ERROR, messageKey, args); - } - - /** - * Returns the kind of this DiagMessage. - * - * @return the kind of this DiagMessage - */ - public Diagnostic.Kind getKind() { - return this.kind; - } - - /** - * Returns the message key of this DiagMessage. - * - * @return the message key of this DiagMessage - */ - public @CompilerMessageKey String getMessageKey() { - return this.messageKey; - } - - /** - * Returns the customized optional arguments for the message. - * - * @return the customized optional arguments for the message - */ - public Object[] getArgs() { - return this.args; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof DiagMessage)) { - return false; + + /** + * Create a DiagMessage with kind ERROR. + * + * @param messageKey the message key + * @param args the arguments that will be interpolated into the localized message + * @return a new DiagMessage + */ + public static DiagMessage error(@CompilerMessageKey String messageKey, Object... args) { + return new DiagMessage(Diagnostic.Kind.ERROR, messageKey, args); + } + + /** + * Returns the kind of this DiagMessage. + * + * @return the kind of this DiagMessage + */ + public Diagnostic.Kind getKind() { + return this.kind; } - DiagMessage other = (DiagMessage) obj; + /** + * Returns the message key of this DiagMessage. + * + * @return the message key of this DiagMessage + */ + public @CompilerMessageKey String getMessageKey() { + return this.messageKey; + } + + /** + * Returns the customized optional arguments for the message. + * + * @return the customized optional arguments for the message + */ + public Object[] getArgs() { + return this.args; + } - return (kind == other.kind - && messageKey.equals(other.messageKey) - && Arrays.equals(args, other.args)); - } + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof DiagMessage)) { + return false; + } + + DiagMessage other = (DiagMessage) obj; + + return (kind == other.kind + && messageKey.equals(other.messageKey) + && Arrays.equals(args, other.args)); + } + + @Pure + @Override + public int hashCode() { + return Objects.hash(kind, messageKey, Arrays.hashCode(args)); + } - @Pure - @Override - public int hashCode() { - return Objects.hash(kind, messageKey, Arrays.hashCode(args)); - } + @SideEffectFree + @Override + public String toString() { + if (args.length == 0) { + return messageKey; + } - @SideEffectFree - @Override - public String toString() { - if (args.length == 0) { - return messageKey; + return kind + messageKey + " : " + Arrays.toString(args); } - return kind + messageKey + " : " + Arrays.toString(args); - } - - /** - * Returns the concatenation of the lists. - * - * @param list1 a list of DiagMessage, or null - * @param list2 a list of DiagMessage, or null - * @return the concatenation of the lists - */ - public static @Nullable List mergeLists( - @Nullable List list1, @Nullable List list2) { - if (list1 == null || list1.isEmpty()) { - return list2; - } else if (list2 == null || list2.isEmpty()) { - return list1; - } else { - List result = new ArrayList<>(list1.size() + list2.size()); - result.addAll(list1); - result.addAll(list2); - return result; + /** + * Returns the concatenation of the lists. + * + * @param list1 a list of DiagMessage, or null + * @param list2 a list of DiagMessage, or null + * @return the concatenation of the lists + */ + public static @Nullable List mergeLists( + @Nullable List list1, @Nullable List list2) { + if (list1 == null || list1.isEmpty()) { + return list2; + } else if (list2 == null || list2.isEmpty()) { + return list1; + } else { + List result = new ArrayList<>(list1.size() + list2.size()); + result.addAll(list1); + result.addAll(list2); + return result; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 8507ef0b442..586bf17a4d1 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -18,6 +18,36 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Position; + +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.formatter.qual.FormatMethod; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.util.CheckerMain; +import org.checkerframework.framework.util.OptionConfiguration; +import org.checkerframework.framework.util.TreePathCacher; +import org.checkerframework.javacutil.AbstractTypeProcessor; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypeSystemError; +import org.checkerframework.javacutil.UserError; +import org.plumelib.util.ArraySet; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.SystemPlume; +import org.plumelib.util.UtilPlume; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -48,6 +78,7 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import java.util.stream.Collectors; + import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; @@ -61,34 +92,6 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; -import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; -import org.checkerframework.checker.formatter.qual.FormatMethod; -import org.checkerframework.checker.interning.qual.InternedDistinct; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.PolyNull; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.util.CheckerMain; -import org.checkerframework.framework.util.OptionConfiguration; -import org.checkerframework.framework.util.TreePathCacher; -import org.checkerframework.javacutil.AbstractTypeProcessor; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.TreePathUtil; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypeSystemError; -import org.checkerframework.javacutil.UserError; -import org.plumelib.util.ArraySet; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.SystemPlume; -import org.plumelib.util.UtilPlume; // import io.github.classgraph.ClassGraph; @@ -104,3080 +107,3137 @@ * AbstractProcessor} (or even this class). */ @SupportedOptions({ - // When adding a new standard option: - // 1. Add a brief blurb here about the use case - // and a pointer to one prominent use of the option. - // 2. Update the Checker Framework manual: - // * docs/manual/introduction.tex contains a list of all options, - // which should be in the same order as this source code file. - // * a specific section should contain a detailed discussion. - - /// - /// Unsound checking: ignore some errors - /// - - // A comma-separated list of warnings to suppress - // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings - "suppressWarnings", - - // Set inclusion/exclusion of type uses, definitions, or files. - // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar - "skipUses", - "onlyUses", - "skipDefs", - "onlyDefs", - "skipFiles", - "onlyFiles", - "skipDirs", // Obsolete as of 2024-03-15, replaced by "skipFiles". - - // Unsoundly assume all methods have no side effects, are deterministic, or both. - "assumeSideEffectFree", - "assumeDeterministic", - "assumePure", - // Unsoundly assume getter methods have no side effects and are deterministic. - "assumePureGetters", - - // Whether to assume that assertions are enabled or disabled - // org.checkerframework.framework.flow.CFCFGBuilder.CFCFGBuilder - "assumeAssertionsAreEnabled", - "assumeAssertionsAreDisabled", - - // Whether to ignore all subtype tests for type arguments that - // were inferred for a raw type. Defaults to true. - // org.checkerframework.framework.type.TypeHierarchy.isSubtypeTypeArguments - "ignoreRawTypeArguments", - - // Do not validate meta-annotation @TargetLocations - "ignoreTargetLocations", - - // Treat checker errors as warnings - // org.checkerframework.framework.source.SourceChecker.report - "warns", - - /// - /// More sound (strict checking): enable errors that are disabled by default - /// - - // The next ones *increase* rather than *decrease* soundness. They will eventually be replaced - // by their complements (except -AconcurrentSemantics) and moved into the above section. - - // TODO: Checking of bodies of @SideEffectFree, @Deterministic, and - // @Pure methods is temporarily disabled unless -AcheckPurityAnnotations is - // supplied on the command line. - // Re-enable it after making the analysis more precise. - // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) - "checkPurityAnnotations", - - // TODO: Temporary option to make array subtyping invariant, - // which will be the new default soon. - "invariantArrays", - - // TODO: Temporary option to make casts stricter, in particular when - // casting to an array or generic type. This will be the new default soon. - "checkCastElementType", - - // Whether to type check the enclosing expression of an inner class instantiation. - "checkEnclosingExpr", - - // Whether to use conservative defaults for bytecode and/or source code. - // This option takes arguments "source" and/or "bytecode". - // The default is "-source,-bytecode" (eventually this will be changed to "-source,bytecode"). - // Note, in source code, conservative defaults are never - // applied to code in the scope of an @AnnotatedFor. - // See the "Compiling partially-annotated libraries" and - // "Default qualifiers for \<.class> files (conservative library defaults)" - // sections in the manual for more details - // org.checkerframework.framework.source.SourceChecker.useConservativeDefault - "useConservativeDefaultsForUncheckedCode", - - // Whether to assume sound concurrent semantics or - // simplified sequential semantics - // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics - "concurrentSemantics", - - // Issues a "redundant.anno" warning if the annotation explicitly written on the type is - // the same as the default annotation for this type and location. - "warnRedundantAnnotations", - - /// - /// Type-checking modes: enable/disable functionality - /// - - // Lint options - // org.checkerframework.framework.source.SourceChecker.getSupportedLintOptions() and similar - "lint", - - // Whether to suggest methods that could be marked @SideEffectFree, - // @Deterministic, or @Pure - // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) - "suggestPureMethods", - - // Whether to resolve reflective method invocations. - // "-AresolveReflection=debug" causes debugging information - // to be output. - "resolveReflection", - - // Whether to use whole-program inference. Takes an argument to specify the output format: - // "-Ainfer=stubs" or "-Ainfer=jaifs". - "infer", - - // Whether to output a copy of each file for which annotations were inferred, formatted - // as an ajava file. Can only be used with -Ainfer=ajava - "inferOutputOriginal", - - // With each warning, in addition to the concrete error key, - // output the SuppressWarnings strings that can be used to - // suppress that warning. - "showSuppressWarningsStrings", - - // Warn about @SuppressWarnings annotations that do not suppress any warnings. - // org.checkerframework.common.basetype.BaseTypeChecker.warnUnneededSuppressions - // org.checkerframework.framework.source.SourceChecker.warnUnneededSuppressions - // org.checkerframework.framework.source.SourceChecker.shouldSuppressWarnings(javax.lang.model.element.Element, java.lang.String) - // org.checkerframework.framework.source.SourceVisitor.checkForSuppressWarningsAnno - "warnUnneededSuppressions", - - // Exceptions to -AwarnUnneededSuppressions. - "warnUnneededSuppressionsExceptions", - - // Require that warning suppression annotations contain a checker key as a prefix in order for - // the warning to be suppressed. - // org.checkerframework.framework.source.SourceChecker.checkSuppressWarnings(java.lang.String[], - // java.lang.String) - "requirePrefixInWarningSuppressions", - - // Print a checker key as a prefix to each typechecking diagnostic. - // org.checkerframework.framework.source.SourceChecker.suppressWarningsString(java.lang.String) - "showPrefixInWarningMessages", - - // Ignore annotations in bytecode that have invalid annotation locations. - // See https://github.com/typetools/checker-framework/issues/2173 - // org.checkerframework.framework.type.ElementAnnotationApplier.apply - "ignoreInvalidAnnotationLocations", - - /// - /// Compatibility options - /// - - // Additional type and declaration annotation aliases - // -AaliasedTypeAnnos={aliases} or -AaliasedDeclAnnos={aliases} - // where `aliases` is in the format - // `FQN.canonical.Qualifier1:FQN.alias1.Qual1,FQN.alias2.Qual1;FQN.canonical.Qualifier2:FQN.alias1.Qual2` - // org.checkerframework.framework.type.AnnotatedTypeFactory - "aliasedTypeAnnos", - "aliasedDeclAnnos", - - /// - /// Partially-annotated libraries - /// - - // Additional stub files to use - // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() - "stubs", - - // Additional ajava files to use - // org.checkerframework.framework.type.AnnotatedTypeFactory.parserAjavaFiles() - "ajava", - - // Whether to print warnings about types/members in a stub file - // that were not found on the class path - // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFound - "stubWarnIfNotFound", - "stubNoWarnIfNotFound", - - // Whether to ignore missing classes even when warnIfNotFound is set to true and other classes - // from the same package are present (useful if a package spans more than one jar). - // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFoundIgnoresClasses - "stubWarnIfNotFoundIgnoresClasses", - - // Whether to print warnings about stub files that overwrite annotations from bytecode. - "stubWarnIfOverwritesBytecode", - - // Whether to print warnings about stub files that are redundant with the annotations from - // bytecode. - "stubWarnIfRedundantWithBytecode", - - // Whether to issue a NOTE rather than a WARNING for -AstubWarn* command-line options - "stubWarnNote", - - // With this option, annotations in stub files are used EVEN IF THE SOURCE FILE IS - // PRESENT. Only use this option when you intend to store types in stub files rather than - // directly in source code, such as during whole-program inference. The annotations in the - // stub files will be glb'd with those in the source code before local inference begins. - "mergeStubsWithSource", - - // Already listed above, but worth noting again in this section: - // "useConservativeDefaultsForUncheckedCode" - - /// - /// Debugging - /// - - /// Amount of detail in messages - - // Print the version of the Checker Framework - "version", - - // Print info about git repository from which the Checker Framework was compiled - "printGitProperties", - - // Whether to print @InvisibleQualifier marked annotations - // org.checkerframework.framework.type.AnnotatedTypeMirror.toString() - "printAllQualifiers", - - // Whether to print [] around a set of type parameters in order to clearly see where they end - // e.g. - // without this option E is printed: E extends F extends Object - // with this option: E [ extends F [ extends Object super Void ] super Void ] - // when multiple type variables are used this becomes useful very quickly - "printVerboseGenerics", - - // Whether to NOT output a stack trace for each framework error. - // org.checkerframework.framework.source.SourceChecker.logBugInCF - "noPrintErrorStack", - - // If true, issue a NOTE rather than a WARNING when performance is impeded by memory - // constraints. - "noWarnMemoryConstraints", - - // Only output error code, useful for testing framework. - // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) - "nomsgtext", - - // Do not perform a JRE version check. - "noJreVersionCheck", - - /// Format of messages - - // Output detailed message in simple-to-parse format, useful - // for tools parsing Checker Framework output. - // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) - "detailedmsgtext", - - /// Stub and JDK libraries - - // Ignore the standard jdk.astub file; primarily for testing or debugging. - // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() - "ignorejdkastub", - - // Whether to check that the annotated JDK is correctly provided - // org.checkerframework.common.basetype.BaseTypeVisitor.checkForAnnotatedJdk() - "permitMissingJdk", - - // Parse all JDK files at startup rather than as needed. - // org.checkerframework.framework.stub.AnnotationFileElementTypes.AnnotationFileElementTypes - "parseAllJdk", - - // Whether to print debugging messages while processing the stub files - // org.checkerframework.framework.stub.AnnotationFileParser.debugAnnotationFileParser - "stubDebug", - - /// Progress tracing - - // Output file names before checking - // org.checkerframework.framework.source.SourceChecker.typeProcess() - "filenames", - - // Output all subtyping checks - // org.checkerframework.common.basetype.BaseTypeVisitor - "showchecks", - - // Output a stack trace when reporting errors or warnings - // org.checkerframework.common.basetype.SourceChecker.printStackTrace() - "dumpOnErrors", - - /// Visualizing the CFG + // When adding a new standard option: + // 1. Add a brief blurb here about the use case + // and a pointer to one prominent use of the option. + // 2. Update the Checker Framework manual: + // * docs/manual/introduction.tex contains a list of all options, + // which should be in the same order as this source code file. + // * a specific section should contain a detailed discussion. + + /// + /// Unsound checking: ignore some errors + /// + + // A comma-separated list of warnings to suppress + // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings + "suppressWarnings", + + // Set inclusion/exclusion of type uses, definitions, or files. + // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar + "skipUses", + "onlyUses", + "skipDefs", + "onlyDefs", + "skipFiles", + "onlyFiles", + "skipDirs", // Obsolete as of 2024-03-15, replaced by "skipFiles". + + // Unsoundly assume all methods have no side effects, are deterministic, or both. + "assumeSideEffectFree", + "assumeDeterministic", + "assumePure", + // Unsoundly assume getter methods have no side effects and are deterministic. + "assumePureGetters", + + // Whether to assume that assertions are enabled or disabled + // org.checkerframework.framework.flow.CFCFGBuilder.CFCFGBuilder + "assumeAssertionsAreEnabled", + "assumeAssertionsAreDisabled", + + // Whether to ignore all subtype tests for type arguments that + // were inferred for a raw type. Defaults to true. + // org.checkerframework.framework.type.TypeHierarchy.isSubtypeTypeArguments + "ignoreRawTypeArguments", + + // Do not validate meta-annotation @TargetLocations + "ignoreTargetLocations", + + // Treat checker errors as warnings + // org.checkerframework.framework.source.SourceChecker.report + "warns", + + /// + /// More sound (strict checking): enable errors that are disabled by default + /// + + // The next ones *increase* rather than *decrease* soundness. They will eventually be replaced + // by their complements (except -AconcurrentSemantics) and moved into the above section. + + // TODO: Checking of bodies of @SideEffectFree, @Deterministic, and + // @Pure methods is temporarily disabled unless -AcheckPurityAnnotations is + // supplied on the command line. + // Re-enable it after making the analysis more precise. + // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) + "checkPurityAnnotations", + + // TODO: Temporary option to make array subtyping invariant, + // which will be the new default soon. + "invariantArrays", + + // TODO: Temporary option to make casts stricter, in particular when + // casting to an array or generic type. This will be the new default soon. + "checkCastElementType", + + // Whether to type check the enclosing expression of an inner class instantiation. + "checkEnclosingExpr", + + // Whether to use conservative defaults for bytecode and/or source code. + // This option takes arguments "source" and/or "bytecode". + // The default is "-source,-bytecode" (eventually this will be changed to "-source,bytecode"). + // Note, in source code, conservative defaults are never + // applied to code in the scope of an @AnnotatedFor. + // See the "Compiling partially-annotated libraries" and + // "Default qualifiers for \<.class> files (conservative library defaults)" + // sections in the manual for more details + // org.checkerframework.framework.source.SourceChecker.useConservativeDefault + "useConservativeDefaultsForUncheckedCode", + + // Whether to assume sound concurrent semantics or + // simplified sequential semantics + // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics + "concurrentSemantics", + + // Issues a "redundant.anno" warning if the annotation explicitly written on the type is + // the same as the default annotation for this type and location. + "warnRedundantAnnotations", + + /// + /// Type-checking modes: enable/disable functionality + /// + + // Lint options + // org.checkerframework.framework.source.SourceChecker.getSupportedLintOptions() and similar + "lint", + + // Whether to suggest methods that could be marked @SideEffectFree, + // @Deterministic, or @Pure + // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) + "suggestPureMethods", + + // Whether to resolve reflective method invocations. + // "-AresolveReflection=debug" causes debugging information + // to be output. + "resolveReflection", + + // Whether to use whole-program inference. Takes an argument to specify the output format: + // "-Ainfer=stubs" or "-Ainfer=jaifs". + "infer", + + // Whether to output a copy of each file for which annotations were inferred, formatted + // as an ajava file. Can only be used with -Ainfer=ajava + "inferOutputOriginal", + + // With each warning, in addition to the concrete error key, + // output the SuppressWarnings strings that can be used to + // suppress that warning. + "showSuppressWarningsStrings", + + // Warn about @SuppressWarnings annotations that do not suppress any warnings. + // org.checkerframework.common.basetype.BaseTypeChecker.warnUnneededSuppressions + // org.checkerframework.framework.source.SourceChecker.warnUnneededSuppressions + // org.checkerframework.framework.source.SourceChecker.shouldSuppressWarnings(javax.lang.model.element.Element, java.lang.String) + // org.checkerframework.framework.source.SourceVisitor.checkForSuppressWarningsAnno + "warnUnneededSuppressions", + + // Exceptions to -AwarnUnneededSuppressions. + "warnUnneededSuppressionsExceptions", + + // Require that warning suppression annotations contain a checker key as a prefix in order for + // the warning to be suppressed. + // org.checkerframework.framework.source.SourceChecker.checkSuppressWarnings(java.lang.String[], + // java.lang.String) + "requirePrefixInWarningSuppressions", + + // Print a checker key as a prefix to each typechecking diagnostic. + // org.checkerframework.framework.source.SourceChecker.suppressWarningsString(java.lang.String) + "showPrefixInWarningMessages", + + // Ignore annotations in bytecode that have invalid annotation locations. + // See https://github.com/typetools/checker-framework/issues/2173 + // org.checkerframework.framework.type.ElementAnnotationApplier.apply + "ignoreInvalidAnnotationLocations", + + /// + /// Compatibility options + /// + + // Additional type and declaration annotation aliases + // -AaliasedTypeAnnos={aliases} or -AaliasedDeclAnnos={aliases} + // where `aliases` is in the format + // `FQN.canonical.Qualifier1:FQN.alias1.Qual1,FQN.alias2.Qual1;FQN.canonical.Qualifier2:FQN.alias1.Qual2` + // org.checkerframework.framework.type.AnnotatedTypeFactory + "aliasedTypeAnnos", + "aliasedDeclAnnos", + + /// + /// Partially-annotated libraries + /// + + // Additional stub files to use + // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() + "stubs", + + // Additional ajava files to use + // org.checkerframework.framework.type.AnnotatedTypeFactory.parserAjavaFiles() + "ajava", + + // Whether to print warnings about types/members in a stub file + // that were not found on the class path + // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFound + "stubWarnIfNotFound", + "stubNoWarnIfNotFound", + + // Whether to ignore missing classes even when warnIfNotFound is set to true and other classes + // from the same package are present (useful if a package spans more than one jar). + // org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFoundIgnoresClasses + "stubWarnIfNotFoundIgnoresClasses", + + // Whether to print warnings about stub files that overwrite annotations from bytecode. + "stubWarnIfOverwritesBytecode", + + // Whether to print warnings about stub files that are redundant with the annotations from + // bytecode. + "stubWarnIfRedundantWithBytecode", + + // Whether to issue a NOTE rather than a WARNING for -AstubWarn* command-line options + "stubWarnNote", + + // With this option, annotations in stub files are used EVEN IF THE SOURCE FILE IS + // PRESENT. Only use this option when you intend to store types in stub files rather than + // directly in source code, such as during whole-program inference. The annotations in the + // stub files will be glb'd with those in the source code before local inference begins. + "mergeStubsWithSource", + + // Already listed above, but worth noting again in this section: + // "useConservativeDefaultsForUncheckedCode" + + /// + /// Debugging + /// + + /// Amount of detail in messages + + // Print the version of the Checker Framework + "version", + + // Print info about git repository from which the Checker Framework was compiled + "printGitProperties", + + // Whether to print @InvisibleQualifier marked annotations + // org.checkerframework.framework.type.AnnotatedTypeMirror.toString() + "printAllQualifiers", + + // Whether to print [] around a set of type parameters in order to clearly see where they end + // e.g. + // without this option E is printed: E extends F extends Object + // with this option: E [ extends F [ extends Object super Void ] super Void ] + // when multiple type variables are used this becomes useful very quickly + "printVerboseGenerics", + + // Whether to NOT output a stack trace for each framework error. + // org.checkerframework.framework.source.SourceChecker.logBugInCF + "noPrintErrorStack", + + // If true, issue a NOTE rather than a WARNING when performance is impeded by memory + // constraints. + "noWarnMemoryConstraints", + + // Only output error code, useful for testing framework. + // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) + "nomsgtext", + + // Do not perform a JRE version check. + "noJreVersionCheck", + + /// Format of messages + + // Output detailed message in simple-to-parse format, useful + // for tools parsing Checker Framework output. + // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) + "detailedmsgtext", + + /// Stub and JDK libraries + + // Ignore the standard jdk.astub file; primarily for testing or debugging. + // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() + "ignorejdkastub", + + // Whether to check that the annotated JDK is correctly provided + // org.checkerframework.common.basetype.BaseTypeVisitor.checkForAnnotatedJdk() + "permitMissingJdk", + + // Parse all JDK files at startup rather than as needed. + // org.checkerframework.framework.stub.AnnotationFileElementTypes.AnnotationFileElementTypes + "parseAllJdk", + + // Whether to print debugging messages while processing the stub files + // org.checkerframework.framework.stub.AnnotationFileParser.debugAnnotationFileParser + "stubDebug", + + /// Progress tracing + + // Output file names before checking + // org.checkerframework.framework.source.SourceChecker.typeProcess() + "filenames", + + // Output all subtyping checks + // org.checkerframework.common.basetype.BaseTypeVisitor + "showchecks", + + // Output a stack trace when reporting errors or warnings + // org.checkerframework.common.basetype.SourceChecker.printStackTrace() + "dumpOnErrors", + + /// Visualizing the CFG - // Implemented in the wrapper rather than this file, but worth noting here. - // -AoutputArgsToFile - - // Mechanism to visualize the control flow graph (CFG). - // The argument is a sequence of values or key-value pairs. - // The first argument has to be the fully-qualified name of the - // org.checkerframework.dataflow.cfg.CFGVisualizer implementation that should be used. The - // remaining values or key-value pairs are passed to CFGVisualizer.init. - // For example: - // -Acfgviz=MyViz,a,b=c,d - // instantiates class MyViz and calls CFGVisualizer.init - // with {"a" -> true, "b" -> "c", "d" -> true}. - "cfgviz", - - // Directory for .dot files generated from the CFG visualization in - // org.checkerframework.dataflow.cfg.DOTCFGVisualizer - // as initialized by - // org.checkerframework.framework.type.GenericAnnotatedTypeFactory.createCFGVisualizer() - // -Aflowdotdir=xyz - // is short-hand for - // -Acfgviz=org.checkerframework.dataflow.cfg.DOTCFGVisualizer,outdir=xyz - "flowdotdir", - - // Enable additional output in the CFG visualization. - // -Averbosecfg - // is short-hand for - // -Acfgviz=MyClass,verbose - "verbosecfg", - - /// Caches - - // Set the cache size for caches in AnnotatedTypeFactory - "atfCacheSize", - - // Sets AnnotatedTypeFactory shouldCache to false - "atfDoNotCache", - - /// Language Server Protocol(LSP) Support - - // Output detailed type information for nodes in AST - // org.checkerframework.framework.type.AnnotatedTypeFactory - "lspTypeInfo", - - /// Miscellaneous debugging options - - // Whether to output resource statistics at JVM shutdown - // org.checkerframework.framework.source.SourceChecker.shutdownHook() - "resourceStats", - - // Run checks that test ajava files. - // - // Whenever processing a source file, parse it with JavaParser and check that the AST can be - // matched with javac's tree. Crash if not. For testing the class JointJavacJavaParserVisitor. - // - // Also checks that annotations can be inserted. For each Java file, clears all annotations and - // reinserts them, then checks if the original and modified ASTs are equivalent. - "ajavaChecks", - - // Converts type argument inference crashes into errors. By default, this option is true. - // Use "-AconvertTypeArgInferenceCrashToWarning=false" to turn this option off and allow type - // argument inference crashes to crash the type checker. - "convertTypeArgInferenceCrashToWarning" + // Implemented in the wrapper rather than this file, but worth noting here. + // -AoutputArgsToFile + + // Mechanism to visualize the control flow graph (CFG). + // The argument is a sequence of values or key-value pairs. + // The first argument has to be the fully-qualified name of the + // org.checkerframework.dataflow.cfg.CFGVisualizer implementation that should be used. The + // remaining values or key-value pairs are passed to CFGVisualizer.init. + // For example: + // -Acfgviz=MyViz,a,b=c,d + // instantiates class MyViz and calls CFGVisualizer.init + // with {"a" -> true, "b" -> "c", "d" -> true}. + "cfgviz", + + // Directory for .dot files generated from the CFG visualization in + // org.checkerframework.dataflow.cfg.DOTCFGVisualizer + // as initialized by + // org.checkerframework.framework.type.GenericAnnotatedTypeFactory.createCFGVisualizer() + // -Aflowdotdir=xyz + // is short-hand for + // -Acfgviz=org.checkerframework.dataflow.cfg.DOTCFGVisualizer,outdir=xyz + "flowdotdir", + + // Enable additional output in the CFG visualization. + // -Averbosecfg + // is short-hand for + // -Acfgviz=MyClass,verbose + "verbosecfg", + + /// Caches + + // Set the cache size for caches in AnnotatedTypeFactory + "atfCacheSize", + + // Sets AnnotatedTypeFactory shouldCache to false + "atfDoNotCache", + + /// Language Server Protocol(LSP) Support + + // Output detailed type information for nodes in AST + // org.checkerframework.framework.type.AnnotatedTypeFactory + "lspTypeInfo", + + /// Miscellaneous debugging options + + // Whether to output resource statistics at JVM shutdown + // org.checkerframework.framework.source.SourceChecker.shutdownHook() + "resourceStats", + + // Run checks that test ajava files. + // + // Whenever processing a source file, parse it with JavaParser and check that the AST can be + // matched with javac's tree. Crash if not. For testing the class JointJavacJavaParserVisitor. + // + // Also checks that annotations can be inserted. For each Java file, clears all annotations and + // reinserts them, then checks if the original and modified ASTs are equivalent. + "ajavaChecks", + + // Converts type argument inference crashes into errors. By default, this option is true. + // Use "-AconvertTypeArgInferenceCrashToWarning=false" to turn this option off and allow type + // argument inference crashes to crash the type checker. + "convertTypeArgInferenceCrashToWarning" }) public abstract class SourceChecker extends AbstractTypeProcessor implements OptionConfiguration { - // TODO A checker should export itself through a separate interface, and maybe have an interface - // for all the methods for which it's safe to override. - - /** The message key that will suppress all warnings (it matches any message key). */ - public static final String SUPPRESS_ALL_MESSAGE_KEY = "all"; - - /** The SuppressWarnings prefix that will suppress warnings for all checkers. */ - public static final String SUPPRESS_ALL_PREFIX = "allcheckers"; - - /** The message key emitted when an unused warning suppression is found. */ - public static final @CompilerMessageKey String UNNEEDED_SUPPRESSION_KEY = "unneeded.suppression"; - - /** File name of the localized messages. */ - protected static final String MSGS_FILE = "messages.properties"; - - /** True if the Checker Framework version number has already been printed. */ - private static boolean printedVersion = false; - - /** - * Maps error keys to localized/custom error messages. Do not use directly; call {@link - * #fullMessageOf} or {@link #processErrorMessageArg}. Is set in {@link #initChecker}. - */ - protected Properties messagesProperties; - - /** - * Used to report error messages and warnings via the compiler. Is set in {@link - * #typeProcessingStart}. - */ - protected Messager messager; - - /** Element utilities. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() - protected Elements elements; - - /** Tree utilities; used as a helper for the {@link SourceVisitor}. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() - protected Trees trees; - - /** Type utilities. */ - @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() - protected Types types; - - /** The source tree that is being scanned. Is set in {@link #setRoot}. */ - protected @MonotonicNonNull @InternedDistinct CompilationUnitTree currentRoot; - - /** The visitor to use. */ - protected SourceVisitor visitor; - - /** - * Exceptions to {@code -AwarnUnneededSuppressions} processing. No warning about unneeded - * suppressions is issued if the SuppressWarnings string matches this pattern. - */ - private @Nullable Pattern warnUnneededSuppressionsExceptions; - - /** - * SuppressWarnings strings supplied via the -AsuppressWarnings option. Do not use directly, call - * {@link #getSuppressWarningsStringsFromOption()}. - */ - private String @MonotonicNonNull [] suppressWarningsStringsFromOption; - - /** True if {@link #suppressWarningsStringsFromOption} has been computed. */ - private boolean computedSuppressWarningsStringsFromOption = false; - - /** - * If true, use the "allcheckers:" warning string prefix. - * - *

          Checkers that never issue any error messages should set this to false. That prevents {@code - * -AwarnUnneededSuppressions} from issuing warnings about - * {@code @SuppressWarnings("allcheckers:...")}. - */ - protected boolean useAllcheckersPrefix = true; - - /** - * Regular expression pattern to specify Java classes that are not annotated, so warnings about - * uses of them should be suppressed. - * - *

          It contains the pattern specified by the user, through the option {@code checkers.skipUses}; - * otherwise it contains a pattern that can match no class. - */ - private @MonotonicNonNull Pattern skipUsesPattern; - - /** - * Regular expression pattern to specify Java classes that are annotated, so warnings about them - * should be issued but warnings about all other classes should be suppressed. - * - *

          It contains the pattern specified by the user, through the option {@code checkers.onlyUses}; - * otherwise it contains a pattern that matches every class. - */ - private @MonotonicNonNull Pattern onlyUsesPattern; - - /** - * Regular expression pattern to specify Java classes whose definition should not be checked. - * - *

          It contains the pattern specified by the user, through the option {@code checkers.skipDefs}; - * otherwise it contains a pattern that can match no class. - */ - private @MonotonicNonNull Pattern skipDefsPattern; - - /** - * Regular expression pattern to specify Java classes whose definition should be checked. - * - *

          It contains the pattern specified by the user, through the option {@code checkers.onlyDefs}; - * otherwise it contains a pattern that matches every class. - */ - private @MonotonicNonNull Pattern onlyDefsPattern; - - /** - * Regular expression pattern to specify files or directories that should not be checked. - * - *

          It contains the pattern specified by the user, through the option {@code - * checkers.skipFiles}; otherwise it contains a pattern that can match no directory. - */ - private @MonotonicNonNull Pattern skipFilesPattern; - - /** - * Regular expression pattern to specify files or directories that should be checked. - * - *

          It contains the pattern specified by the user, through the option {@code - * checkers.onlyFiles}; otherwise it contains a pattern that can match no directory. - */ - private @MonotonicNonNull Pattern onlyFilesPattern; - - /** The supported lint options. */ - private @MonotonicNonNull Set supportedLints; - - /** The enabled lint options. Is set in {@link #initChecker}. */ - private Set activeLints; - - /** - * The active options for this checker. This is a processed version of {@link - * ProcessingEnvironment#getOptions()}: If the option is of the form "-ACheckerName_key=value" and - * the current checker class, or one of its superclasses, is named "CheckerName", then add key - * → value. If the option is of the form "-ACheckerName_key=value" and the current checker - * class, and none of its superclasses, is named "CheckerName", then do not add key → value. - * If the option is of the form "-Akey=value", then add key → value. - * - *

          Both the simple and the canonical name of the checker can be used. Superclasses of the - * current checker are also considered. - */ - private @MonotonicNonNull Map activeOptions; - - /** - * The string that separates the checker name from the option name in a "-A" command-line - * argument. This string may only consist of valid Java identifier part characters, because it - * will be used within the key of an option. - */ - protected static final String OPTION_SEPARATOR = "_"; - - /** - * The checker that called this one, whether that be a BaseTypeChecker (used as a compound - * checker) or an AggregateChecker. Null if this is the checker that calls all others. Note that - * in the case of a compound checker, the compound checker is the parent, not the checker that was - * run prior to this one by the compound checker. - */ - protected @Nullable SourceChecker parentChecker; - - /** List of upstream checker names. Includes the current checker. */ - protected @MonotonicNonNull List<@FullyQualifiedName String> upstreamCheckerNames; - - /** - * TreePathCacher to share between instances. Initialized in getTreePathCacher (which is also - * called from {@link BaseTypeChecker#instantiateSubcheckers(Map)}). - */ - protected TreePathCacher treePathCacher = null; - - /** True if the -Afilenames command-line argument was passed. */ - private boolean printFilenames; - - /** True if the -Awarns command-line argument was passed. */ - private boolean warns; - - /** True if the -AshowSuppressWarningsStrings command-line argument was passed. */ - private boolean showSuppressWarningsStrings; - - /** True if the -ArequirePrefixInWarningSuppressions command-line argument was passed. */ - private boolean requirePrefixInWarningSuppressions; - - /** True if the -AshowPrefixInWarningMessages command-line argument was passed. */ - private boolean showPrefixInWarningMessages; - - /** True if the -AwarnUnneededSuppressions command-line argument was passed. */ - private boolean warnUnneededSuppressions; - - /** Creates a source checker. */ - protected SourceChecker() {} - - // Also see initChecker(). - @Override - public final synchronized void init(ProcessingEnvironment env) { - ProcessingEnvironment unwrappedEnv = unwrapProcessingEnvironment(env); - super.init(unwrappedEnv); - // The processingEnvironment field will be set by the superclass's init method. - // This is used to trigger AggregateChecker's setProcessingEnvironment. - setProcessingEnvironment(unwrappedEnv); - - // Keep in sync with check in checker-framework/build.gradle . - int jreVersion = SystemUtil.jreVersion; - if (!hasOption("noJreVersionCheck") - && jreVersion != 8 - && jreVersion != 11 - && jreVersion != 17 - && jreVersion != 21) { - message( - Diagnostic.Kind.NOTE, - "The Checker Framework is tested with JDK 8, 11, 17, and 21." - + " You are using version %d.", - jreVersion); - } - - if (!hasOption("warnUnneededSuppressionsExceptions")) { - warnUnneededSuppressionsExceptions = null; - } else { - String warnUnneededSuppressionsExceptionsString = - getOption("warnUnneededSuppressionsExceptions"); - if (warnUnneededSuppressionsExceptionsString == null) { - throw new UserError("Must supply an argument to -AwarnUnneededSuppressionsExceptions"); - } - try { - warnUnneededSuppressionsExceptions = - Pattern.compile(warnUnneededSuppressionsExceptionsString); - } catch (PatternSyntaxException e) { - throw new UserError( - "Argument to -AwarnUnneededSuppressionsExceptions is not a regular" - + " expression: " - + e.getMessage()); - } - } - - if (hasOption("printGitProperties")) { - printGitProperties(); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Getters and setters - /// - - /** - * Returns the {@link ProcessingEnvironment} that was supplied to this checker. - * - * @return the {@link ProcessingEnvironment} that was supplied to this checker - */ - public ProcessingEnvironment getProcessingEnvironment() { - return this.processingEnv; - } - - /** - * Set the processing environment of the current checker. - * - * @param env the new processing environment - */ - // This method is protected only to allow the AggregateChecker and BaseTypeChecker to call it. - protected void setProcessingEnvironment(ProcessingEnvironment env) { - this.processingEnv = env; - this.elements = processingEnv.getElementUtils(); - this.trees = Trees.instance(processingEnv); - this.types = processingEnv.getTypeUtils(); - } - - /** Set the parent checker of the current checker. */ - protected void setParentChecker(SourceChecker parentChecker) { - this.parentChecker = parentChecker; - } - - /** - * Returns the immediate parent checker of the current checker. - * - * @return the immediate parent checker of the current checker, or null if there is none - */ - public @Nullable SourceChecker getParentChecker() { - return this.parentChecker; - } - - /** - * Invoked when the current compilation unit root changes. - * - * @param newRoot the new compilation unit root - */ - @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests - protected void setRoot(CompilationUnitTree newRoot) { - this.currentRoot = newRoot; - visitor.setRoot(currentRoot); - - if (parentChecker == null) { - // Only clear the path cache if this is the main checker. - treePathCacher.clear(); - } - } - - /** - * Returns a list containing this checker name and all checkers it is a part of (that is, checkers - * that called it). - * - * @return a list containing this checker name and all checkers it is a part of (that is, checkers - * that called it) - */ - public List<@FullyQualifiedName String> getUpstreamCheckerNames() { - if (upstreamCheckerNames == null) { - upstreamCheckerNames = new ArrayList<>(); - - SourceChecker checker = this; - - while (checker != null) { - String className = checker.getClass().getCanonicalName(); - assert className != null : "@AssumeAssertion(nullness): checker classes have names"; - upstreamCheckerNames.add(className); - checker = checker.parentChecker; - } - } - - return upstreamCheckerNames; - } - - /** - * Returns the OptionConfiguration associated with this. - * - * @return the OptionConfiguration associated with this - */ - public OptionConfiguration getOptionConfiguration() { - return this; - } - - /** - * Returns the element utilities associated with this. - * - * @return the element utilities associated with this - */ - public Elements getElementUtils() { - return elements; - } - - /** - * Returns the type utilities associated with this. - * - * @return the type utilities associated with this - */ - public Types getTypeUtils() { - return types; - } - - /** - * Returns the tree utilities associated with this. - * - * @return the tree utilities associated with this - */ - public Trees getTreeUtils() { - return trees; - } - - /** - * Returns the SourceVisitor associated with this. - * - * @return the SourceVisitor associated with this - */ - public SourceVisitor getVisitor() { - return this.visitor; - } - - /** - * Provides the {@link SourceVisitor} that the checker should use to scan input source trees. - * - * @return a {@link SourceVisitor} to use to scan source trees - */ - protected abstract SourceVisitor createSourceVisitor(); - - /** - * Returns the AnnotationProvider (the type factory) associated with this. - * - * @return the AnnotationProvider (the type factory) associated with this - */ - public AnnotationProvider getAnnotationProvider() { - throw new UnsupportedOperationException( - "getAnnotationProvider is not implemented for " + this.getClass().getSimpleName() + "."); - } - - /** - * Provides a mapping of error keys to custom error messages. - * - *

          As a default, this implementation builds a {@link Properties} out of file {@code - * messages.properties}. It accumulates all the properties files in the Java class hierarchy from - * the checker up to {@code SourceChecker}. This permits subclasses to inherit default messages - * while being able to override them. - * - * @return a {@link Properties} that maps error keys to error message text - */ - public Properties getMessagesProperties() { - if (messagesProperties != null) { - return messagesProperties; - } - - messagesProperties = new Properties(); - - ArrayDeque> checkers = new ArrayDeque<>(); - Class currClass = this.getClass(); - while (currClass != AbstractTypeProcessor.class) { - checkers.addFirst(currClass); - currClass = currClass.getSuperclass(); - assert currClass != null : "@AssumeAssertion(nullness): won't encounter Object.class"; - } - - for (Class checker : checkers) { - messagesProperties.putAll(getProperties(checker, MSGS_FILE, true)); - } - return messagesProperties; - } - - /** - * Get the shared TreePathCacher instance. - * - * @return the shared TreePathCacher instance. - */ - public TreePathCacher getTreePathCacher() { - if (treePathCacher == null) { - // In case it wasn't already set in instantiateSubcheckers. - treePathCacher = new TreePathCacher(); - } - return treePathCacher; - } - - /** - * Return the given skip pattern if supplied by the user, or else a pattern that matches nothing. - * - * @param patternName "skipUses" or "skipDefs" - * @param options the command-line options - * @return the user-supplied regex for the given pattern, or a regex that matches nothing - */ - private Pattern getSkipPattern(String patternName, Map options) { - // Default is an illegal Java identifier substring - // so that it won't match anything. - // Note that AnnotatedType's toString output format contains characters such as "():{}". - return getPattern(patternName, options, "\\]'\"\\]"); - } - - /** - * Return the given only pattern if supplied by the user, or else a pattern that matches - * everything. - * - * @param patternName "onlyUses" or "onlyDefs" - * @param options the command-line options - * @return the user-supplied regex for the given pattern, or a regex that matches everything - */ - private Pattern getOnlyPattern(String patternName, Map options) { - // default matches everything - return getPattern(patternName, options, "."); - } - - private Pattern getPattern( - String patternName, Map options, String defaultPattern) { - String pattern; - if (options.containsKey(patternName)) { - pattern = options.get(patternName); - if (pattern == null) { - throw new UserError( - "The " + patternName + " property is empty; please fix your command line"); - } - } else { - pattern = System.getProperty("checkers." + patternName); - if (pattern == null) { - pattern = System.getenv(patternName); - } - if (pattern == null) { - pattern = ""; - } - } - - if (pattern.isEmpty()) { - pattern = defaultPattern; - } - - try { - return Pattern.compile(pattern); - } catch (PatternSyntaxException e) { - throw new UserError( - "The " + patternName + " property is not a regular expression: " + pattern); - } - } - - private Pattern getSkipUsesPattern(Map options) { - return getSkipPattern("skipUses", options); - } - - private Pattern getOnlyUsesPattern(Map options) { - return getOnlyPattern("onlyUses", options); - } - - private Pattern getSkipDefsPattern(Map options) { - return getSkipPattern("skipDefs", options); - } - - private Pattern getOnlyDefsPattern(Map options) { - return getOnlyPattern("onlyDefs", options); - } - - /** - * Extract the value of the {@code skipFiles} option given the value of the options passed to the - * checker. - * - * @param options the map of options and their values passed to the checker - * @return the value of the {@code skipFiles} option - */ - private Pattern getSkipFilesPattern(Map options) { - boolean hasSkipFiles = options.containsKey("skipFiles"); - boolean hasSkipDirs = options.containsKey("skipDirs"); - if (hasSkipFiles && hasSkipDirs) { - throw new UserError("Do not supply both -AskipFiles and -AskipDirs command-line options."); - } - // This logic isn't quite right because the checker.skipDirs property might exist. - if (hasSkipDirs) { - return getSkipPattern("skipDirs", options); - } else { - return getSkipPattern("skipFiles", options); - } - } - - /** - * Extract the value of the {@code onlyFiles} option given the value of the options passed to the - * checker. - * - * @param options the map of options and their values passed to the checker - * @return the value of the {@code onlyFiles} option - */ - private Pattern getOnlyFilesPattern(Map options) { - return getOnlyPattern("onlyFiles", options); - } - - /////////////////////////////////////////////////////////////////////////// - /// Type-checking - /// - - /** - * {@inheritDoc} - * - *

          Type-checkers are not supposed to override this. Instead override initChecker. This allows - * us to handle BugInCF only here and doesn't require all overriding implementations to be aware - * of BugInCF. - * - * @see AbstractProcessor#init(ProcessingEnvironment) - * @see SourceChecker#initChecker() - */ - @Override - public void typeProcessingStart() { - try { - super.typeProcessingStart(); - initChecker(); - if (this.messager == null) { - messager = processingEnv.getMessager(); - messager.printMessage( - Diagnostic.Kind.WARNING, - "You have forgotten to call super.initChecker in your " - + "subclass of SourceChecker, " - + this.getClass() - + "! Please ensure your checker is properly initialized."); - } - if (shouldAddShutdownHook()) { - Runtime.getRuntime() - .addShutdownHook( - new Thread() { - @Override - public void run() { - shutdownHook(); - } - }); - } - if (!printedVersion && hasOption("version")) { - messager.printMessage(Diagnostic.Kind.NOTE, "Checker Framework " + getCheckerVersion()); - printedVersion = true; - } - } catch (UserError ce) { - logUserError(ce); - } catch (TypeSystemError ce) { - logTypeSystemError(ce); - } catch (BugInCF ce) { - logBugInCF(ce); - } catch (Throwable t) { - logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcessingStart", t, null)); - } - } - - /** - * Initialize the checker. - * - * @see AbstractProcessor#init(ProcessingEnvironment) - */ - public void initChecker() { - // Grab the Trees and Messager instances now; other utilities - // (like Types and Elements) can be retrieved by subclasses. - Trees trees = Trees.instance(processingEnv); - assert trees != null; - this.trees = trees; - - this.messager = processingEnv.getMessager(); - this.messagesProperties = getMessagesProperties(); - - this.visitor = createSourceVisitor(); - - // Validate the lint flags, if they haven't been used already. - if (this.activeLints == null) { - this.activeLints = createActiveLints(getOptions()); - } - - printFilenames = hasOption("filenames"); - warns = hasOption("warns"); - showSuppressWarningsStrings = hasOption("showSuppressWarningsStrings"); - requirePrefixInWarningSuppressions = hasOption("requirePrefixInWarningSuppressions"); - showPrefixInWarningMessages = hasOption("showPrefixInWarningMessages"); - warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); - } - - /** Output the warning about source level at most once. */ - private boolean warnedAboutSourceLevel = false; - - /** - * If true, javac failed to compile the code or a previously-run annotation processor issued an - * error. - */ - protected boolean javacErrored = false; - - /** Output the warning about memory at most once. */ - private boolean warnedAboutGarbageCollection = false; - - /** - * The number of errors at the last exit of the type processor. At entry to the type processor we - * check whether the current error count is higher and then don't process the file, as it contains - * some Java errors. Needs to be protected to allow access from AggregateChecker and - * BaseTypeChecker. - */ - protected int errsOnLastExit = 0; - - /** - * Type-check the code using this checker's visitor. - * - * @see Processor#process(Set, RoundEnvironment) - */ - @Override - public void typeProcess(TypeElement e, TreePath p) { - if (javacErrored) { - return; - } - - // Cannot use BugInCF here because it is outside of the try/catch for BugInCF. - if (e == null) { - messager.printMessage(Diagnostic.Kind.ERROR, "Refusing to process empty TypeElement"); - return; - } - if (p == null) { - messager.printMessage( - Diagnostic.Kind.ERROR, "Refusing to process empty TreePath in TypeElement: " + e); - return; - } - - if (!warnedAboutGarbageCollection) { - String gcUsageMessage = SystemPlume.gcUsageMessage(.25, 60); - if (gcUsageMessage != null) { - boolean noWarnMemoryConstraints = - (processingEnv != null - && processingEnv.getOptions() != null - && processingEnv.getOptions().containsKey("noWarnMemoryConstraints")); - Diagnostic.Kind kind = - noWarnMemoryConstraints ? Diagnostic.Kind.NOTE : Diagnostic.Kind.WARNING; - messager.printMessage(kind, gcUsageMessage); - warnedAboutGarbageCollection = true; - } - } - - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - Source source = Source.instance(context); - // Don't use source.allowTypeAnnotations() because that API changed after 9. - // Also the enum constant Source.JDK1_8 was renamed at some point... - if (!warnedAboutSourceLevel && source.compareTo(Source.lookup("8")) < 0) { - messager.printMessage( - Diagnostic.Kind.WARNING, "-source " + source.name + " does not support type annotations"); - warnedAboutSourceLevel = true; - } - - Log log = Log.instance(context); - if (log.nerrors > this.errsOnLastExit) { - this.errsOnLastExit = log.nerrors; - javacErrored = true; - return; - } - - if (visitor == null) { - // typeProcessingStart invokes initChecker, which should have set the visitor. If the - // field is still null, an exception occurred during initialization, which was already - // logged there. Don't also cause a NPE here. - return; - } - if (p.getCompilationUnit() != currentRoot) { - setRoot(p.getCompilationUnit()); - if (printFilenames) { - // TODO: Have a command-line option to turn the timestamps on/off too, because - // they are nondeterministic across runs. - - // Add timestamp to indicate how long operations are taking. - // Duplicate messages are suppressed, so this might not appear in front of every - // " is type-checking " message (when a file takes less than a second to - // type-check). - message(Diagnostic.Kind.NOTE, Instant.now().toString()); - message( - Diagnostic.Kind.NOTE, - "%s is type-checking %s", - (Object) this.getClass().getSimpleName(), - currentRoot.getSourceFile().getName()); - } - } - - // Visit the attributed tree. - try { - visitor.visit(p); - warnUnneededSuppressions(); - } catch (UserError ce) { - logUserError(ce); - } catch (TypeSystemError ce) { - logTypeSystemError(ce); - } catch (BugInCF ce) { - logBugInCF(ce); - } catch (Throwable t) { - logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcess", t, p)); - } finally { - // Also add possibly deferred diagnostics, which will get published back in - // AbstractTypeProcessor. - this.errsOnLastExit = log.nerrors; - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Reporting type-checking errors; most clients use reportError() or reportWarning() - /// - - /** - * Reports an error. By default, prints it to the screen via the compiler's internal messager. - * - * @param source the source position information; may be an Element, a Tree, or null - * @param messageKey the message key - * @param args arguments for interpolation in the string corresponding to the given message key - */ - public void reportError( - @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { - report(source, Diagnostic.Kind.ERROR, messageKey, args); - } - - /** - * Reports a warning. By default, prints it to the screen via the compiler's internal messager. - * - * @param source the source position information; may be an Element, a Tree, or null - * @param messageKey the message key - * @param args arguments for interpolation in the string corresponding to the given message key - */ - public void reportWarning( - @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { - report(source, Diagnostic.Kind.MANDATORY_WARNING, messageKey, args); - } - - /** - * Reports a diagnostic message. By default, prints it to the screen via the compiler's internal - * messager. - * - *

          It is rare to use this method. Most clients should use {@link #reportError} or {@link - * #reportWarning}. - * - * @param source the source position information; may be an Element, a Tree, or null - * @param d the diagnostic message - */ - public void report(@Nullable Object source, DiagMessage d) { - report(source, d.getKind(), d.getMessageKey(), d.getArgs()); - } - - /** - * Reports a diagnostic message. By default, it prints it to the screen via the compiler's - * internal messager; however, it might also store it for later output. - * - * @param source the source position information; may be an Element or a Tree - * @param kind the type of message - * @param messageKey the message key - * @param args arguments for interpolation in the string corresponding to the given message key - */ - // Not a format method. However, messageKey should be either a format string for `args`, or a - // property key that maps to a format string for `args`. - // @FormatMethod - @SuppressWarnings("formatter:format.string.invalid") // arg is a format string or a property key - private void report( - Object source, Diagnostic.Kind kind, @CompilerMessageKey String messageKey, Object... args) { - assert messagesProperties != null : "null messagesProperties"; - - if (shouldSuppressWarnings(source, messageKey)) { - return; - } - Object preciseSource = getSourceWithPrecisePosition(source); - - if (args != null) { - for (int i = 0; i < args.length; ++i) { - args[i] = processErrorMessageArg(args[i]); - } - } - - if (kind == Diagnostic.Kind.NOTE) { - System.err.println("(NOTE) " + String.format(messageKey, args)); - return; - } - - String defaultFormat = "(" + messageKey + ")"; - String fmtString; - if (this.processingEnv.getOptions() != null /*nnbug*/ - && this.processingEnv.getOptions().containsKey("nomsgtext")) { - fmtString = defaultFormat; - } else if (this.processingEnv.getOptions() != null /*nnbug*/ - && this.processingEnv.getOptions().containsKey("detailedmsgtext")) { - // The -Adetailedmsgtext command-line option was given, so output - // a stylized error message for easy parsing by a tool. - fmtString = - detailedMsgTextPrefix(preciseSource, defaultFormat, args) - + fullMessageOf(messageKey, defaultFormat); - } else { - fmtString = - "[" - + suppressWarningsString(messageKey) - + "] " - + fullMessageOf(messageKey, defaultFormat); - } - String messageText; - try { - messageText = String.format(fmtString, args); - } catch (Exception e) { - throw new BugInCF( - "Invalid format string: \"" + fmtString + "\" args: " + Arrays.toString(args), e); - } - - if (kind == Diagnostic.Kind.ERROR && warns) { - kind = Diagnostic.Kind.MANDATORY_WARNING; - } - - if (preciseSource instanceof Element) { - messager.printMessage(kind, messageText, (Element) preciseSource); - } else if (preciseSource instanceof Tree) { - printOrStoreMessage(kind, messageText, (Tree) preciseSource, currentRoot); - } else { - throw new BugInCF( - "invalid position source of class " + preciseSource.getClass() + ": " + preciseSource); - } - } - - /** - * This method improves the source position information for message reporting. If the given {@code - * source} does not have a precise location, it will try to return an object with a precise - * location that encloses the {@code source}; otherwise, it simply returns the {@code source}. - * - *

          Currently, missing position only happens to artificial trees generated by the compiler. For - * example, a lambda expression "s -> ..." in the source code can be de-sugared into "(Type s) - * -> ..." in the corresponding {@link Tree}, where the "Type" {@link Tree} is an artificial - * tree. - * - * @param source the original source position information; may be an Element, a Tree, or null - * @return a source that may have more precise position information - */ - private @PolyNull Object getSourceWithPrecisePosition(@PolyNull Object source) { - if (!(source instanceof JCTree)) { - return source; - } - - JCTree tree = (JCTree) source; - if (tree.getPreferredPosition() != Position.NOPOS) { - return tree; - } - - TreePath path = getTreePathCacher().getPath(currentRoot, tree); - if (path == null) { - return tree; - } - - // find the first parent in AST path that has a precise preferred position - while (((JCTree) path.getLeaf()).getPreferredPosition() == Position.NOPOS - && path.getParentPath() != null) { - path = path.getParentPath(); - } - return path.getLeaf(); - } - - /** - * Print a non-localized message using the javac messager. This is preferable to using System.out - * or System.err, but should only be used for exceptional cases that don't happen in correct - * usage. Localized messages should be raised using {@link #reportError}, {@link #reportWarning}, - * etc. - * - * @param kind the kind of message to print - * @param msg the message text - * @param args optional arguments to substitute in the message - * @see SourceChecker#report(Object, DiagMessage) - */ - @FormatMethod - public void message(Diagnostic.Kind kind, String msg, Object... args) { - message(kind, String.format(msg, args)); - } - - /** - * Print a non-localized message using the javac messager. This is preferable to using System.out - * or System.err, but should only be used for exceptional cases that don't happen in correct - * usage. Localized messages should be raised using {@link #reportError}, {@link #reportWarning}, - * etc. - * - * @param kind the kind of message to print - * @param msg the message text - * @see SourceChecker#report(Object, DiagMessage) - */ - public void message(javax.tools.Diagnostic.Kind kind, String msg) { - if (messager == null) { - // If this method is called before initChecker() sets the field - messager = processingEnv.getMessager(); - } - messager.printMessage(kind, msg); - } - - /** - * Print the given message. - * - * @param msg the message to print x - */ - private void printMessage(String msg) { - if (messager == null) { - // If this method is called before initChecker() sets the field - messager = processingEnv.getMessager(); - } - messager.printMessage(Diagnostic.Kind.ERROR, msg); - } - - /** - * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. - * - *

          This method exists so that the BaseTypeChecker can override it. For compound checkers, it - * stores all messages and sorts them by location before outputting them. - * - * @param kind the kind of message to print - * @param message the message text - * @param source the source code position of the diagnostic message - * @param root the compilation unit - */ - protected void printOrStoreMessage( - javax.tools.Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - printOrStoreMessage(kind, message, source, root, trace); - } - - /** - * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. - * - *

          This method exists so that the BaseTypeChecker can override it. For compound checkers, it - * stores all messages and sorts them by location before outputting them. - * - * @param kind the kind of message to print - * @param message the message text - * @param source the source code position of the diagnostic message - * @param root the compilation unit - * @param trace the stack trace where the checker encountered an error. It is printed when the - * dumpOnErrors option is enabled. - */ - protected void printOrStoreMessage( - javax.tools.Diagnostic.Kind kind, - String message, - Tree source, - CompilationUnitTree root, - StackTraceElement[] trace) { - Trees.instance(processingEnv).printMessage(kind, message, source, root); - printStackTrace(trace); - } - - /** - * Output the given stack trace if the "dumpOnErrors" option is enabled. - * - * @param trace stack trace when the checker encountered a warning/error - */ - private void printStackTrace(StackTraceElement[] trace) { - if (hasOption("dumpOnErrors")) { - StringBuilder msg = new StringBuilder(); - for (StackTraceElement elem : trace) { - msg.append("\tat " + elem + "\n"); - } - message(Diagnostic.Kind.NOTE, msg.toString()); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Diagnostic message formatting - /// - - /** - * Returns the localized long message corresponding to this key. If not found, tries suffixes of - * this key, stripping off dot-separated prefixes. If still not found, returns {@code - * defaultValue}. - * - * @param messageKey a message key - * @param defaultValue a default value to use if {@code messageKey} is not a message key - * @return the localized long message corresponding to this key or a suffix, or {@code - * defaultValue} - */ - protected String fullMessageOf(String messageKey, String defaultValue) { - String key = messageKey; - - do { - String messageForKey = messagesProperties.getProperty(key); - if (messageForKey != null) { - return messageForKey; - } - - int dot = key.indexOf('.'); - if (dot < 0) { - return defaultValue; - } - key = key.substring(dot + 1); - } while (true); - } - - /** - * Process an argument to an error message before it is passed to String.format. - * - *

          This implementation expands the argument if it is exactly a message key. - * - *

          By contrast, {@link #fullMessageOf} processes the message key itself but not the arguments, - * and tries suffixes. - * - * @param arg the argument - * @return the result after processing - */ - protected Object processErrorMessageArg(Object arg) { - // Check to see if the argument itself is a property to be expanded - if (arg instanceof String) { - return messagesProperties.getProperty((String) arg, (String) arg); - } else { - return arg; - } - } - - /** Separates parts of a "detailed message", to permit easier parsing. */ - public static final String DETAILS_SEPARATOR = " $$ "; - - /** - * Returns all but the message key part of the message format output by {@code -Adetailedmsgtext}. - * - * @param source the object from which to obtain source position information; may be an Element, a - * Tree, or null - * @param defaultFormat the message key, in parentheses - * @param args arguments for interpolation in the string corresponding to the given message key - * @return the first part of the message format output by {@code -Adetailedmsgtext} - */ - private String detailedMsgTextPrefix( - @Nullable Object source, String defaultFormat, Object[] args) { - StringJoiner sj = new StringJoiner(DETAILS_SEPARATOR); - - // The parts, separated by " $$ " (DETAILS_SEPARATOR), are: - - // (1) error key - sj.add(defaultFormat); - - // (2) number of additional tokens, and those tokens; this depends on the error message, and - // an example is the found and expected types - if (args != null) { - sj.add(Integer.toString(args.length)); - for (Object arg : args) { - sj.add(Objects.toString(arg)); - } - } else { - // Output 0 for null arguments. - sj.add(Integer.toString(0)); - } - - // (3) The error position, as starting and ending characters in the source file. - sj.add(detailedMsgTextPositionString(sourceToTree(source), currentRoot)); - - // (4) The human-readable error message will be added by the caller. - sj.add(""); // Add DETAILS_SEPARATOR at the end. - return sj.toString(); - } - - /** - * Returns the most specific warning suppression string for the warning/error being printed. - * - *

            - *
          • If {@code -AshowSuppressWarningsStrings} was supplied on the command line, this is {@code - * [checkername1, checkername2]:msg}, where each {@code checkername} is a checker name or - * "allcheckers". - *
          • If {@code -ArequirePrefixInWarningSuppressions} or {@code -AshowPrefixInWarningMessages} - * was supplied on the command line, this is {@code checkername:msg} (where {@code - * checkername} may be "allcheckers"). - *
          • Otherwise, it is just {@code msg}. - *
          - * - * @param messageKey the simple, checker-specific error message key - * @return the most specific SuppressWarnings string for the warning/error being printed - */ - protected String suppressWarningsString(String messageKey) { - Collection prefixes = this.getSuppressWarningsPrefixes(); - prefixes.remove(SUPPRESS_ALL_PREFIX); - if (showSuppressWarningsStrings) { - List list = new ArrayList<>(prefixes); - // Make sure "allcheckers" is at the end of the list. - if (useAllcheckersPrefix) { - list.add(SUPPRESS_ALL_PREFIX); - } - return list + ":" + messageKey; - } else if (requirePrefixInWarningSuppressions || showPrefixInWarningMessages) { - // If the warning key must be prefixed with a prefix (a checker name), then add that to - // the SuppressWarnings string that is printed. - return getWarningMessagePrefix() + ":" + messageKey; - } else { - return messageKey; - } - } - - /** - * Convert a Tree, Element, or null, into a Tree or null. - * - * @param source the object from which to obtain source position information; may be an Element, a - * Tree, or null - * @return the tree associated with the given source object, or null if none - */ - private @Nullable Tree sourceToTree(@Nullable Object source) { - if (source instanceof Element) { - return trees.getTree((Element) source); - } else if (source instanceof Tree) { - return (Tree) source; - } else if (source == null) { - return null; - } else { - throw new BugInCF("Unexpected source %s [%s]", source, source.getClass()); - } - } - - /** - * For the given tree, compute the source positions for that tree. Return a "tuple"-like string - * (e.g. "( 1, 200 )" ) that contains the start and end position of the tree in the current - * compilation unit. Used only by the -Adetailedmsgtext output format. - * - * @param tree tree to locate within the current compilation unit - * @param currentRoot the current compilation unit - * @return a tuple string representing the range of characters that tree occupies in the source - * file, or the empty string if {@code tree} is null - */ - private String detailedMsgTextPositionString( - @Nullable Tree tree, CompilationUnitTree currentRoot) { - if (tree == null) { - return ""; - } - - SourcePositions sourcePositions = trees.getSourcePositions(); - long start = sourcePositions.getStartPosition(currentRoot, tree); - long end = sourcePositions.getEndPosition(currentRoot, tree); - - return "( " + start + ", " + end + " )"; - } - - /////////////////////////////////////////////////////////////////////////// - /// Lint options ("-Alint:xxxx" and "-Alint:-xxxx") - /// - - /** - * Determine which lint options are artive. - * - * @param options the command-line options - * @return the active lint options - */ - private Set createActiveLints(Map options) { - if (!options.containsKey("lint")) { - return Collections.emptySet(); - } - - String lintString = options.get("lint"); - if (lintString == null) { - return Collections.singleton("all"); - } - - List lintStrings = SystemUtil.COMMA_SPLITTER.splitToList(lintString); - Set activeLint = ArraySet.newArraySetOrHashSet(lintStrings.size()); - for (String s : lintStrings) { - if (!this.getSupportedLintOptions().contains(s) - && !(s.charAt(0) == '-' && this.getSupportedLintOptions().contains(s.substring(1))) - && !s.equals("all") - && !s.equals("none")) { - this.messager.printMessage( - Diagnostic.Kind.WARNING, - "Unsupported lint option: " + s + "; All options: " + this.getSupportedLintOptions()); - } - - activeLint.add(s); - if (s.equals("none")) { - activeLint.add("-all"); - } - } - - return Collections.unmodifiableSet(activeLint); - } - - /** - * Determines the value of the lint option with the given name. Just as javac uses - * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options - * are enabled with "-Alint:xxx" and disabled with "-Alint:-xxx". - * - * @throws IllegalArgumentException if the option name is not recognized via the {@link - * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} - * method - * @param name the name of the lint option to check for - * @return true if the lint option was given, false if it was not given or was given prepended - * with a "-" - * @see SourceChecker#getLintOption(String, boolean) - */ - public final boolean getLintOption(String name) { - return getLintOption(name, false); - } - - /** - * Determines the value of the lint option with the given name. Just as javac uses - * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options - * are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". - * - * @throws IllegalArgumentException if the option name is not recognized via the {@link - * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} - * method - * @param name the name of the lint option to check for - * @param def the default option value, returned if the option was not given - * @return true if the lint option was given, false if it was given prepended with a "-", or - * {@code def} if it was not given at all - * @see SourceChecker#getLintOption(String) - * @see SourceChecker#getOption(String) - */ - public final boolean getLintOption(String name, boolean def) { - if (!this.getSupportedLintOptions().contains(name)) { - throw new UserError("Illegal lint option: " + name); - } - - // This is only needed if initChecker() has not yet been called. - if (activeLints == null) { - activeLints = createActiveLints(getOptions()); - } - - if (activeLints.isEmpty()) { - return def; - } - - String tofind = name; - while (tofind != null) { - if (activeLints.contains(tofind)) { - return true; - } else if (activeLints.contains(String.format("-%s", tofind))) { - return false; - } - - tofind = parentOfOption(tofind); - } - - return def; - } - - /** - * Set the value of the lint option with the given name. Just as javac uses - * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint options - * are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". This method can be used by - * subclasses to enforce having certain lint options enabled/disabled. - * - * @throws IllegalArgumentException if the option name is not recognized via the {@link - * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} - * method - * @param name the name of the lint option to set - * @param val the option value - * @see SourceChecker#getLintOption(String) - * @see SourceChecker#getLintOption(String,boolean) - */ - protected final void setLintOption(String name, boolean val) { - if (!this.getSupportedLintOptions().contains(name)) { - throw new UserError("Illegal lint option: " + name); - } - - /* TODO: warn if the option is also provided on the command line(?) - boolean exists = false; - if (!activeLints.isEmpty()) { + // TODO A checker should export itself through a separate interface, and maybe have an interface + // for all the methods for which it's safe to override. + + /** The message key that will suppress all warnings (it matches any message key). */ + public static final String SUPPRESS_ALL_MESSAGE_KEY = "all"; + + /** The SuppressWarnings prefix that will suppress warnings for all checkers. */ + public static final String SUPPRESS_ALL_PREFIX = "allcheckers"; + + /** The message key emitted when an unused warning suppression is found. */ + public static final @CompilerMessageKey String UNNEEDED_SUPPRESSION_KEY = + "unneeded.suppression"; + + /** File name of the localized messages. */ + protected static final String MSGS_FILE = "messages.properties"; + + /** True if the Checker Framework version number has already been printed. */ + private static boolean printedVersion = false; + + /** + * Maps error keys to localized/custom error messages. Do not use directly; call {@link + * #fullMessageOf} or {@link #processErrorMessageArg}. Is set in {@link #initChecker}. + */ + protected Properties messagesProperties; + + /** + * Used to report error messages and warnings via the compiler. Is set in {@link + * #typeProcessingStart}. + */ + protected Messager messager; + + /** Element utilities. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() + protected Elements elements; + + /** Tree utilities; used as a helper for the {@link SourceVisitor}. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() + protected Trees trees; + + /** Type utilities. */ + @SuppressWarnings("nullness:initialization.field.uninitialized") // initialized in init() + protected Types types; + + /** The source tree that is being scanned. Is set in {@link #setRoot}. */ + protected @MonotonicNonNull @InternedDistinct CompilationUnitTree currentRoot; + + /** The visitor to use. */ + protected SourceVisitor visitor; + + /** + * Exceptions to {@code -AwarnUnneededSuppressions} processing. No warning about unneeded + * suppressions is issued if the SuppressWarnings string matches this pattern. + */ + private @Nullable Pattern warnUnneededSuppressionsExceptions; + + /** + * SuppressWarnings strings supplied via the -AsuppressWarnings option. Do not use directly, + * call {@link #getSuppressWarningsStringsFromOption()}. + */ + private String @MonotonicNonNull [] suppressWarningsStringsFromOption; + + /** True if {@link #suppressWarningsStringsFromOption} has been computed. */ + private boolean computedSuppressWarningsStringsFromOption = false; + + /** + * If true, use the "allcheckers:" warning string prefix. + * + *

          Checkers that never issue any error messages should set this to false. That prevents + * {@code -AwarnUnneededSuppressions} from issuing warnings about + * {@code @SuppressWarnings("allcheckers:...")}. + */ + protected boolean useAllcheckersPrefix = true; + + /** + * Regular expression pattern to specify Java classes that are not annotated, so warnings about + * uses of them should be suppressed. + * + *

          It contains the pattern specified by the user, through the option {@code + * checkers.skipUses}; otherwise it contains a pattern that can match no class. + */ + private @MonotonicNonNull Pattern skipUsesPattern; + + /** + * Regular expression pattern to specify Java classes that are annotated, so warnings about them + * should be issued but warnings about all other classes should be suppressed. + * + *

          It contains the pattern specified by the user, through the option {@code + * checkers.onlyUses}; otherwise it contains a pattern that matches every class. + */ + private @MonotonicNonNull Pattern onlyUsesPattern; + + /** + * Regular expression pattern to specify Java classes whose definition should not be checked. + * + *

          It contains the pattern specified by the user, through the option {@code + * checkers.skipDefs}; otherwise it contains a pattern that can match no class. + */ + private @MonotonicNonNull Pattern skipDefsPattern; + + /** + * Regular expression pattern to specify Java classes whose definition should be checked. + * + *

          It contains the pattern specified by the user, through the option {@code + * checkers.onlyDefs}; otherwise it contains a pattern that matches every class. + */ + private @MonotonicNonNull Pattern onlyDefsPattern; + + /** + * Regular expression pattern to specify files or directories that should not be checked. + * + *

          It contains the pattern specified by the user, through the option {@code + * checkers.skipFiles}; otherwise it contains a pattern that can match no directory. + */ + private @MonotonicNonNull Pattern skipFilesPattern; + + /** + * Regular expression pattern to specify files or directories that should be checked. + * + *

          It contains the pattern specified by the user, through the option {@code + * checkers.onlyFiles}; otherwise it contains a pattern that can match no directory. + */ + private @MonotonicNonNull Pattern onlyFilesPattern; + + /** The supported lint options. */ + private @MonotonicNonNull Set supportedLints; + + /** The enabled lint options. Is set in {@link #initChecker}. */ + private Set activeLints; + + /** + * The active options for this checker. This is a processed version of {@link + * ProcessingEnvironment#getOptions()}: If the option is of the form "-ACheckerName_key=value" + * and the current checker class, or one of its superclasses, is named "CheckerName", then add + * key → value. If the option is of the form "-ACheckerName_key=value" and the current + * checker class, and none of its superclasses, is named "CheckerName", then do not add key + * → value. If the option is of the form "-Akey=value", then add key → value. + * + *

          Both the simple and the canonical name of the checker can be used. Superclasses of the + * current checker are also considered. + */ + private @MonotonicNonNull Map activeOptions; + + /** + * The string that separates the checker name from the option name in a "-A" command-line + * argument. This string may only consist of valid Java identifier part characters, because it + * will be used within the key of an option. + */ + protected static final String OPTION_SEPARATOR = "_"; + + /** + * The checker that called this one, whether that be a BaseTypeChecker (used as a compound + * checker) or an AggregateChecker. Null if this is the checker that calls all others. Note that + * in the case of a compound checker, the compound checker is the parent, not the checker that + * was run prior to this one by the compound checker. + */ + protected @Nullable SourceChecker parentChecker; + + /** List of upstream checker names. Includes the current checker. */ + protected @MonotonicNonNull List<@FullyQualifiedName String> upstreamCheckerNames; + + /** + * TreePathCacher to share between instances. Initialized in getTreePathCacher (which is also + * called from {@link BaseTypeChecker#instantiateSubcheckers(Map)}). + */ + protected TreePathCacher treePathCacher = null; + + /** True if the -Afilenames command-line argument was passed. */ + private boolean printFilenames; + + /** True if the -Awarns command-line argument was passed. */ + private boolean warns; + + /** True if the -AshowSuppressWarningsStrings command-line argument was passed. */ + private boolean showSuppressWarningsStrings; + + /** True if the -ArequirePrefixInWarningSuppressions command-line argument was passed. */ + private boolean requirePrefixInWarningSuppressions; + + /** True if the -AshowPrefixInWarningMessages command-line argument was passed. */ + private boolean showPrefixInWarningMessages; + + /** True if the -AwarnUnneededSuppressions command-line argument was passed. */ + private boolean warnUnneededSuppressions; + + /** Creates a source checker. */ + protected SourceChecker() {} + + // Also see initChecker(). + @Override + public final synchronized void init(ProcessingEnvironment env) { + ProcessingEnvironment unwrappedEnv = unwrapProcessingEnvironment(env); + super.init(unwrappedEnv); + // The processingEnvironment field will be set by the superclass's init method. + // This is used to trigger AggregateChecker's setProcessingEnvironment. + setProcessingEnvironment(unwrappedEnv); + + // Keep in sync with check in checker-framework/build.gradle . + int jreVersion = SystemUtil.jreVersion; + if (!hasOption("noJreVersionCheck") + && jreVersion != 8 + && jreVersion != 11 + && jreVersion != 17 + && jreVersion != 21) { + message( + Diagnostic.Kind.NOTE, + "The Checker Framework is tested with JDK 8, 11, 17, and 21." + + " You are using version %d.", + jreVersion); + } + + if (!hasOption("warnUnneededSuppressionsExceptions")) { + warnUnneededSuppressionsExceptions = null; + } else { + String warnUnneededSuppressionsExceptionsString = + getOption("warnUnneededSuppressionsExceptions"); + if (warnUnneededSuppressionsExceptionsString == null) { + throw new UserError( + "Must supply an argument to -AwarnUnneededSuppressionsExceptions"); + } + try { + warnUnneededSuppressionsExceptions = + Pattern.compile(warnUnneededSuppressionsExceptionsString); + } catch (PatternSyntaxException e) { + throw new UserError( + "Argument to -AwarnUnneededSuppressionsExceptions is not a regular" + + " expression: " + + e.getMessage()); + } + } + + if (hasOption("printGitProperties")) { + printGitProperties(); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Getters and setters + /// + + /** + * Returns the {@link ProcessingEnvironment} that was supplied to this checker. + * + * @return the {@link ProcessingEnvironment} that was supplied to this checker + */ + public ProcessingEnvironment getProcessingEnvironment() { + return this.processingEnv; + } + + /** + * Set the processing environment of the current checker. + * + * @param env the new processing environment + */ + // This method is protected only to allow the AggregateChecker and BaseTypeChecker to call it. + protected void setProcessingEnvironment(ProcessingEnvironment env) { + this.processingEnv = env; + this.elements = processingEnv.getElementUtils(); + this.trees = Trees.instance(processingEnv); + this.types = processingEnv.getTypeUtils(); + } + + /** Set the parent checker of the current checker. */ + protected void setParentChecker(SourceChecker parentChecker) { + this.parentChecker = parentChecker; + } + + /** + * Returns the immediate parent checker of the current checker. + * + * @return the immediate parent checker of the current checker, or null if there is none + */ + public @Nullable SourceChecker getParentChecker() { + return this.parentChecker; + } + + /** + * Invoked when the current compilation unit root changes. + * + * @param newRoot the new compilation unit root + */ + @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests + protected void setRoot(CompilationUnitTree newRoot) { + this.currentRoot = newRoot; + visitor.setRoot(currentRoot); + + if (parentChecker == null) { + // Only clear the path cache if this is the main checker. + treePathCacher.clear(); + } + } + + /** + * Returns a list containing this checker name and all checkers it is a part of (that is, + * checkers that called it). + * + * @return a list containing this checker name and all checkers it is a part of (that is, + * checkers that called it) + */ + public List<@FullyQualifiedName String> getUpstreamCheckerNames() { + if (upstreamCheckerNames == null) { + upstreamCheckerNames = new ArrayList<>(); + + SourceChecker checker = this; + + while (checker != null) { + String className = checker.getClass().getCanonicalName(); + assert className != null : "@AssumeAssertion(nullness): checker classes have names"; + upstreamCheckerNames.add(className); + checker = checker.parentChecker; + } + } + + return upstreamCheckerNames; + } + + /** + * Returns the OptionConfiguration associated with this. + * + * @return the OptionConfiguration associated with this + */ + public OptionConfiguration getOptionConfiguration() { + return this; + } + + /** + * Returns the element utilities associated with this. + * + * @return the element utilities associated with this + */ + public Elements getElementUtils() { + return elements; + } + + /** + * Returns the type utilities associated with this. + * + * @return the type utilities associated with this + */ + public Types getTypeUtils() { + return types; + } + + /** + * Returns the tree utilities associated with this. + * + * @return the tree utilities associated with this + */ + public Trees getTreeUtils() { + return trees; + } + + /** + * Returns the SourceVisitor associated with this. + * + * @return the SourceVisitor associated with this + */ + public SourceVisitor getVisitor() { + return this.visitor; + } + + /** + * Provides the {@link SourceVisitor} that the checker should use to scan input source trees. + * + * @return a {@link SourceVisitor} to use to scan source trees + */ + protected abstract SourceVisitor createSourceVisitor(); + + /** + * Returns the AnnotationProvider (the type factory) associated with this. + * + * @return the AnnotationProvider (the type factory) associated with this + */ + public AnnotationProvider getAnnotationProvider() { + throw new UnsupportedOperationException( + "getAnnotationProvider is not implemented for " + + this.getClass().getSimpleName() + + "."); + } + + /** + * Provides a mapping of error keys to custom error messages. + * + *

          As a default, this implementation builds a {@link Properties} out of file {@code + * messages.properties}. It accumulates all the properties files in the Java class hierarchy + * from the checker up to {@code SourceChecker}. This permits subclasses to inherit default + * messages while being able to override them. + * + * @return a {@link Properties} that maps error keys to error message text + */ + public Properties getMessagesProperties() { + if (messagesProperties != null) { + return messagesProperties; + } + + messagesProperties = new Properties(); + + ArrayDeque> checkers = new ArrayDeque<>(); + Class currClass = this.getClass(); + while (currClass != AbstractTypeProcessor.class) { + checkers.addFirst(currClass); + currClass = currClass.getSuperclass(); + assert currClass != null : "@AssumeAssertion(nullness): won't encounter Object.class"; + } + + for (Class checker : checkers) { + messagesProperties.putAll(getProperties(checker, MSGS_FILE, true)); + } + return messagesProperties; + } + + /** + * Get the shared TreePathCacher instance. + * + * @return the shared TreePathCacher instance. + */ + public TreePathCacher getTreePathCacher() { + if (treePathCacher == null) { + // In case it wasn't already set in instantiateSubcheckers. + treePathCacher = new TreePathCacher(); + } + return treePathCacher; + } + + /** + * Return the given skip pattern if supplied by the user, or else a pattern that matches + * nothing. + * + * @param patternName "skipUses" or "skipDefs" + * @param options the command-line options + * @return the user-supplied regex for the given pattern, or a regex that matches nothing + */ + private Pattern getSkipPattern(String patternName, Map options) { + // Default is an illegal Java identifier substring + // so that it won't match anything. + // Note that AnnotatedType's toString output format contains characters such as "():{}". + return getPattern(patternName, options, "\\]'\"\\]"); + } + + /** + * Return the given only pattern if supplied by the user, or else a pattern that matches + * everything. + * + * @param patternName "onlyUses" or "onlyDefs" + * @param options the command-line options + * @return the user-supplied regex for the given pattern, or a regex that matches everything + */ + private Pattern getOnlyPattern(String patternName, Map options) { + // default matches everything + return getPattern(patternName, options, "."); + } + + private Pattern getPattern( + String patternName, Map options, String defaultPattern) { + String pattern; + if (options.containsKey(patternName)) { + pattern = options.get(patternName); + if (pattern == null) { + throw new UserError( + "The " + patternName + " property is empty; please fix your command line"); + } + } else { + pattern = System.getProperty("checkers." + patternName); + if (pattern == null) { + pattern = System.getenv(patternName); + } + if (pattern == null) { + pattern = ""; + } + } + + if (pattern.isEmpty()) { + pattern = defaultPattern; + } + + try { + return Pattern.compile(pattern); + } catch (PatternSyntaxException e) { + throw new UserError( + "The " + patternName + " property is not a regular expression: " + pattern); + } + } + + private Pattern getSkipUsesPattern(Map options) { + return getSkipPattern("skipUses", options); + } + + private Pattern getOnlyUsesPattern(Map options) { + return getOnlyPattern("onlyUses", options); + } + + private Pattern getSkipDefsPattern(Map options) { + return getSkipPattern("skipDefs", options); + } + + private Pattern getOnlyDefsPattern(Map options) { + return getOnlyPattern("onlyDefs", options); + } + + /** + * Extract the value of the {@code skipFiles} option given the value of the options passed to + * the checker. + * + * @param options the map of options and their values passed to the checker + * @return the value of the {@code skipFiles} option + */ + private Pattern getSkipFilesPattern(Map options) { + boolean hasSkipFiles = options.containsKey("skipFiles"); + boolean hasSkipDirs = options.containsKey("skipDirs"); + if (hasSkipFiles && hasSkipDirs) { + throw new UserError( + "Do not supply both -AskipFiles and -AskipDirs command-line options."); + } + // This logic isn't quite right because the checker.skipDirs property might exist. + if (hasSkipDirs) { + return getSkipPattern("skipDirs", options); + } else { + return getSkipPattern("skipFiles", options); + } + } + + /** + * Extract the value of the {@code onlyFiles} option given the value of the options passed to + * the checker. + * + * @param options the map of options and their values passed to the checker + * @return the value of the {@code onlyFiles} option + */ + private Pattern getOnlyFilesPattern(Map options) { + return getOnlyPattern("onlyFiles", options); + } + + /////////////////////////////////////////////////////////////////////////// + /// Type-checking + /// + + /** + * {@inheritDoc} + * + *

          Type-checkers are not supposed to override this. Instead override initChecker. This allows + * us to handle BugInCF only here and doesn't require all overriding implementations to be aware + * of BugInCF. + * + * @see AbstractProcessor#init(ProcessingEnvironment) + * @see SourceChecker#initChecker() + */ + @Override + public void typeProcessingStart() { + try { + super.typeProcessingStart(); + initChecker(); + if (this.messager == null) { + messager = processingEnv.getMessager(); + messager.printMessage( + Diagnostic.Kind.WARNING, + "You have forgotten to call super.initChecker in your " + + "subclass of SourceChecker, " + + this.getClass() + + "! Please ensure your checker is properly initialized."); + } + if (shouldAddShutdownHook()) { + Runtime.getRuntime() + .addShutdownHook( + new Thread() { + @Override + public void run() { + shutdownHook(); + } + }); + } + if (!printedVersion && hasOption("version")) { + messager.printMessage( + Diagnostic.Kind.NOTE, "Checker Framework " + getCheckerVersion()); + printedVersion = true; + } + } catch (UserError ce) { + logUserError(ce); + } catch (TypeSystemError ce) { + logTypeSystemError(ce); + } catch (BugInCF ce) { + logBugInCF(ce); + } catch (Throwable t) { + logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcessingStart", t, null)); + } + } + + /** + * Initialize the checker. + * + * @see AbstractProcessor#init(ProcessingEnvironment) + */ + public void initChecker() { + // Grab the Trees and Messager instances now; other utilities + // (like Types and Elements) can be retrieved by subclasses. + Trees trees = Trees.instance(processingEnv); + assert trees != null; + this.trees = trees; + + this.messager = processingEnv.getMessager(); + this.messagesProperties = getMessagesProperties(); + + this.visitor = createSourceVisitor(); + + // Validate the lint flags, if they haven't been used already. + if (this.activeLints == null) { + this.activeLints = createActiveLints(getOptions()); + } + + printFilenames = hasOption("filenames"); + warns = hasOption("warns"); + showSuppressWarningsStrings = hasOption("showSuppressWarningsStrings"); + requirePrefixInWarningSuppressions = hasOption("requirePrefixInWarningSuppressions"); + showPrefixInWarningMessages = hasOption("showPrefixInWarningMessages"); + warnUnneededSuppressions = hasOption("warnUnneededSuppressions"); + } + + /** Output the warning about source level at most once. */ + private boolean warnedAboutSourceLevel = false; + + /** + * If true, javac failed to compile the code or a previously-run annotation processor issued an + * error. + */ + protected boolean javacErrored = false; + + /** Output the warning about memory at most once. */ + private boolean warnedAboutGarbageCollection = false; + + /** + * The number of errors at the last exit of the type processor. At entry to the type processor + * we check whether the current error count is higher and then don't process the file, as it + * contains some Java errors. Needs to be protected to allow access from AggregateChecker and + * BaseTypeChecker. + */ + protected int errsOnLastExit = 0; + + /** + * Type-check the code using this checker's visitor. + * + * @see Processor#process(Set, RoundEnvironment) + */ + @Override + public void typeProcess(TypeElement e, TreePath p) { + if (javacErrored) { + return; + } + + // Cannot use BugInCF here because it is outside of the try/catch for BugInCF. + if (e == null) { + messager.printMessage(Diagnostic.Kind.ERROR, "Refusing to process empty TypeElement"); + return; + } + if (p == null) { + messager.printMessage( + Diagnostic.Kind.ERROR, + "Refusing to process empty TreePath in TypeElement: " + e); + return; + } + + if (!warnedAboutGarbageCollection) { + String gcUsageMessage = SystemPlume.gcUsageMessage(.25, 60); + if (gcUsageMessage != null) { + boolean noWarnMemoryConstraints = + (processingEnv != null + && processingEnv.getOptions() != null + && processingEnv + .getOptions() + .containsKey("noWarnMemoryConstraints")); + Diagnostic.Kind kind = + noWarnMemoryConstraints ? Diagnostic.Kind.NOTE : Diagnostic.Kind.WARNING; + messager.printMessage(kind, gcUsageMessage); + warnedAboutGarbageCollection = true; + } + } + + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + Source source = Source.instance(context); + // Don't use source.allowTypeAnnotations() because that API changed after 9. + // Also the enum constant Source.JDK1_8 was renamed at some point... + if (!warnedAboutSourceLevel && source.compareTo(Source.lookup("8")) < 0) { + messager.printMessage( + Diagnostic.Kind.WARNING, + "-source " + source.name + " does not support type annotations"); + warnedAboutSourceLevel = true; + } + + Log log = Log.instance(context); + if (log.nerrors > this.errsOnLastExit) { + this.errsOnLastExit = log.nerrors; + javacErrored = true; + return; + } + + if (visitor == null) { + // typeProcessingStart invokes initChecker, which should have set the visitor. If the + // field is still null, an exception occurred during initialization, which was already + // logged there. Don't also cause a NPE here. + return; + } + if (p.getCompilationUnit() != currentRoot) { + setRoot(p.getCompilationUnit()); + if (printFilenames) { + // TODO: Have a command-line option to turn the timestamps on/off too, because + // they are nondeterministic across runs. + + // Add timestamp to indicate how long operations are taking. + // Duplicate messages are suppressed, so this might not appear in front of every + // " is type-checking " message (when a file takes less than a second to + // type-check). + message(Diagnostic.Kind.NOTE, Instant.now().toString()); + message( + Diagnostic.Kind.NOTE, + "%s is type-checking %s", + (Object) this.getClass().getSimpleName(), + currentRoot.getSourceFile().getName()); + } + } + + // Visit the attributed tree. + try { + visitor.visit(p); + warnUnneededSuppressions(); + } catch (UserError ce) { + logUserError(ce); + } catch (TypeSystemError ce) { + logTypeSystemError(ce); + } catch (BugInCF ce) { + logBugInCF(ce); + } catch (Throwable t) { + logBugInCF(wrapThrowableAsBugInCF("SourceChecker.typeProcess", t, p)); + } finally { + // Also add possibly deferred diagnostics, which will get published back in + // AbstractTypeProcessor. + this.errsOnLastExit = log.nerrors; + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Reporting type-checking errors; most clients use reportError() or reportWarning() + /// + + /** + * Reports an error. By default, prints it to the screen via the compiler's internal messager. + * + * @param source the source position information; may be an Element, a Tree, or null + * @param messageKey the message key + * @param args arguments for interpolation in the string corresponding to the given message key + */ + public void reportError( + @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { + report(source, Diagnostic.Kind.ERROR, messageKey, args); + } + + /** + * Reports a warning. By default, prints it to the screen via the compiler's internal messager. + * + * @param source the source position information; may be an Element, a Tree, or null + * @param messageKey the message key + * @param args arguments for interpolation in the string corresponding to the given message key + */ + public void reportWarning( + @Nullable Object source, @CompilerMessageKey String messageKey, Object... args) { + report(source, Diagnostic.Kind.MANDATORY_WARNING, messageKey, args); + } + + /** + * Reports a diagnostic message. By default, prints it to the screen via the compiler's internal + * messager. + * + *

          It is rare to use this method. Most clients should use {@link #reportError} or {@link + * #reportWarning}. + * + * @param source the source position information; may be an Element, a Tree, or null + * @param d the diagnostic message + */ + public void report(@Nullable Object source, DiagMessage d) { + report(source, d.getKind(), d.getMessageKey(), d.getArgs()); + } + + /** + * Reports a diagnostic message. By default, it prints it to the screen via the compiler's + * internal messager; however, it might also store it for later output. + * + * @param source the source position information; may be an Element or a Tree + * @param kind the type of message + * @param messageKey the message key + * @param args arguments for interpolation in the string corresponding to the given message key + */ + // Not a format method. However, messageKey should be either a format string for `args`, or a + // property key that maps to a format string for `args`. + // @FormatMethod + @SuppressWarnings("formatter:format.string.invalid") // arg is a format string or a property key + private void report( + Object source, + Diagnostic.Kind kind, + @CompilerMessageKey String messageKey, + Object... args) { + assert messagesProperties != null : "null messagesProperties"; + + if (shouldSuppressWarnings(source, messageKey)) { + return; + } + Object preciseSource = getSourceWithPrecisePosition(source); + + if (args != null) { + for (int i = 0; i < args.length; ++i) { + args[i] = processErrorMessageArg(args[i]); + } + } + + if (kind == Diagnostic.Kind.NOTE) { + System.err.println("(NOTE) " + String.format(messageKey, args)); + return; + } + + String defaultFormat = "(" + messageKey + ")"; + String fmtString; + if (this.processingEnv.getOptions() != null /*nnbug*/ + && this.processingEnv.getOptions().containsKey("nomsgtext")) { + fmtString = defaultFormat; + } else if (this.processingEnv.getOptions() != null /*nnbug*/ + && this.processingEnv.getOptions().containsKey("detailedmsgtext")) { + // The -Adetailedmsgtext command-line option was given, so output + // a stylized error message for easy parsing by a tool. + fmtString = + detailedMsgTextPrefix(preciseSource, defaultFormat, args) + + fullMessageOf(messageKey, defaultFormat); + } else { + fmtString = + "[" + + suppressWarningsString(messageKey) + + "] " + + fullMessageOf(messageKey, defaultFormat); + } + String messageText; + try { + messageText = String.format(fmtString, args); + } catch (Exception e) { + throw new BugInCF( + "Invalid format string: \"" + fmtString + "\" args: " + Arrays.toString(args), + e); + } + + if (kind == Diagnostic.Kind.ERROR && warns) { + kind = Diagnostic.Kind.MANDATORY_WARNING; + } + + if (preciseSource instanceof Element) { + messager.printMessage(kind, messageText, (Element) preciseSource); + } else if (preciseSource instanceof Tree) { + printOrStoreMessage(kind, messageText, (Tree) preciseSource, currentRoot); + } else { + throw new BugInCF( + "invalid position source of class " + + preciseSource.getClass() + + ": " + + preciseSource); + } + } + + /** + * This method improves the source position information for message reporting. If the given + * {@code source} does not have a precise location, it will try to return an object with a + * precise location that encloses the {@code source}; otherwise, it simply returns the {@code + * source}. + * + *

          Currently, missing position only happens to artificial trees generated by the compiler. + * For example, a lambda expression "s -> ..." in the source code can be de-sugared into + * "(Type s) -> ..." in the corresponding {@link Tree}, where the "Type" {@link Tree} is an + * artificial tree. + * + * @param source the original source position information; may be an Element, a Tree, or null + * @return a source that may have more precise position information + */ + private @PolyNull Object getSourceWithPrecisePosition(@PolyNull Object source) { + if (!(source instanceof JCTree)) { + return source; + } + + JCTree tree = (JCTree) source; + if (tree.getPreferredPosition() != Position.NOPOS) { + return tree; + } + + TreePath path = getTreePathCacher().getPath(currentRoot, tree); + if (path == null) { + return tree; + } + + // find the first parent in AST path that has a precise preferred position + while (((JCTree) path.getLeaf()).getPreferredPosition() == Position.NOPOS + && path.getParentPath() != null) { + path = path.getParentPath(); + } + return path.getLeaf(); + } + + /** + * Print a non-localized message using the javac messager. This is preferable to using + * System.out or System.err, but should only be used for exceptional cases that don't happen in + * correct usage. Localized messages should be raised using {@link #reportError}, {@link + * #reportWarning}, etc. + * + * @param kind the kind of message to print + * @param msg the message text + * @param args optional arguments to substitute in the message + * @see SourceChecker#report(Object, DiagMessage) + */ + @FormatMethod + public void message(Diagnostic.Kind kind, String msg, Object... args) { + message(kind, String.format(msg, args)); + } + + /** + * Print a non-localized message using the javac messager. This is preferable to using + * System.out or System.err, but should only be used for exceptional cases that don't happen in + * correct usage. Localized messages should be raised using {@link #reportError}, {@link + * #reportWarning}, etc. + * + * @param kind the kind of message to print + * @param msg the message text + * @see SourceChecker#report(Object, DiagMessage) + */ + public void message(javax.tools.Diagnostic.Kind kind, String msg) { + if (messager == null) { + // If this method is called before initChecker() sets the field + messager = processingEnv.getMessager(); + } + messager.printMessage(kind, msg); + } + + /** + * Print the given message. + * + * @param msg the message to print x + */ + private void printMessage(String msg) { + if (messager == null) { + // If this method is called before initChecker() sets the field + messager = processingEnv.getMessager(); + } + messager.printMessage(Diagnostic.Kind.ERROR, msg); + } + + /** + * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. + * + *

          This method exists so that the BaseTypeChecker can override it. For compound checkers, it + * stores all messages and sorts them by location before outputting them. + * + * @param kind the kind of message to print + * @param message the message text + * @param source the source code position of the diagnostic message + * @param root the compilation unit + */ + protected void printOrStoreMessage( + javax.tools.Diagnostic.Kind kind, + String message, + Tree source, + CompilationUnitTree root) { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + printOrStoreMessage(kind, message, source, root, trace); + } + + /** + * Do not call this method. Call {@link #reportError} or {@link #reportWarning} instead. + * + *

          This method exists so that the BaseTypeChecker can override it. For compound checkers, it + * stores all messages and sorts them by location before outputting them. + * + * @param kind the kind of message to print + * @param message the message text + * @param source the source code position of the diagnostic message + * @param root the compilation unit + * @param trace the stack trace where the checker encountered an error. It is printed when the + * dumpOnErrors option is enabled. + */ + protected void printOrStoreMessage( + javax.tools.Diagnostic.Kind kind, + String message, + Tree source, + CompilationUnitTree root, + StackTraceElement[] trace) { + Trees.instance(processingEnv).printMessage(kind, message, source, root); + printStackTrace(trace); + } + + /** + * Output the given stack trace if the "dumpOnErrors" option is enabled. + * + * @param trace stack trace when the checker encountered a warning/error + */ + private void printStackTrace(StackTraceElement[] trace) { + if (hasOption("dumpOnErrors")) { + StringBuilder msg = new StringBuilder(); + for (StackTraceElement elem : trace) { + msg.append("\tat " + elem + "\n"); + } + message(Diagnostic.Kind.NOTE, msg.toString()); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Diagnostic message formatting + /// + + /** + * Returns the localized long message corresponding to this key. If not found, tries suffixes of + * this key, stripping off dot-separated prefixes. If still not found, returns {@code + * defaultValue}. + * + * @param messageKey a message key + * @param defaultValue a default value to use if {@code messageKey} is not a message key + * @return the localized long message corresponding to this key or a suffix, or {@code + * defaultValue} + */ + protected String fullMessageOf(String messageKey, String defaultValue) { + String key = messageKey; + + do { + String messageForKey = messagesProperties.getProperty(key); + if (messageForKey != null) { + return messageForKey; + } + + int dot = key.indexOf('.'); + if (dot < 0) { + return defaultValue; + } + key = key.substring(dot + 1); + } while (true); + } + + /** + * Process an argument to an error message before it is passed to String.format. + * + *

          This implementation expands the argument if it is exactly a message key. + * + *

          By contrast, {@link #fullMessageOf} processes the message key itself but not the + * arguments, and tries suffixes. + * + * @param arg the argument + * @return the result after processing + */ + protected Object processErrorMessageArg(Object arg) { + // Check to see if the argument itself is a property to be expanded + if (arg instanceof String) { + return messagesProperties.getProperty((String) arg, (String) arg); + } else { + return arg; + } + } + + /** Separates parts of a "detailed message", to permit easier parsing. */ + public static final String DETAILS_SEPARATOR = " $$ "; + + /** + * Returns all but the message key part of the message format output by {@code + * -Adetailedmsgtext}. + * + * @param source the object from which to obtain source position information; may be an Element, + * a Tree, or null + * @param defaultFormat the message key, in parentheses + * @param args arguments for interpolation in the string corresponding to the given message key + * @return the first part of the message format output by {@code -Adetailedmsgtext} + */ + private String detailedMsgTextPrefix( + @Nullable Object source, String defaultFormat, Object[] args) { + StringJoiner sj = new StringJoiner(DETAILS_SEPARATOR); + + // The parts, separated by " $$ " (DETAILS_SEPARATOR), are: + + // (1) error key + sj.add(defaultFormat); + + // (2) number of additional tokens, and those tokens; this depends on the error message, and + // an example is the found and expected types + if (args != null) { + sj.add(Integer.toString(args.length)); + for (Object arg : args) { + sj.add(Objects.toString(arg)); + } + } else { + // Output 0 for null arguments. + sj.add(Integer.toString(0)); + } + + // (3) The error position, as starting and ending characters in the source file. + sj.add(detailedMsgTextPositionString(sourceToTree(source), currentRoot)); + + // (4) The human-readable error message will be added by the caller. + sj.add(""); // Add DETAILS_SEPARATOR at the end. + return sj.toString(); + } + + /** + * Returns the most specific warning suppression string for the warning/error being printed. + * + *

            + *
          • If {@code -AshowSuppressWarningsStrings} was supplied on the command line, this is + * {@code [checkername1, checkername2]:msg}, where each {@code checkername} is a checker + * name or "allcheckers". + *
          • If {@code -ArequirePrefixInWarningSuppressions} or {@code + * -AshowPrefixInWarningMessages} was supplied on the command line, this is {@code + * checkername:msg} (where {@code checkername} may be "allcheckers"). + *
          • Otherwise, it is just {@code msg}. + *
          + * + * @param messageKey the simple, checker-specific error message key + * @return the most specific SuppressWarnings string for the warning/error being printed + */ + protected String suppressWarningsString(String messageKey) { + Collection prefixes = this.getSuppressWarningsPrefixes(); + prefixes.remove(SUPPRESS_ALL_PREFIX); + if (showSuppressWarningsStrings) { + List list = new ArrayList<>(prefixes); + // Make sure "allcheckers" is at the end of the list. + if (useAllcheckersPrefix) { + list.add(SUPPRESS_ALL_PREFIX); + } + return list + ":" + messageKey; + } else if (requirePrefixInWarningSuppressions || showPrefixInWarningMessages) { + // If the warning key must be prefixed with a prefix (a checker name), then add that to + // the SuppressWarnings string that is printed. + return getWarningMessagePrefix() + ":" + messageKey; + } else { + return messageKey; + } + } + + /** + * Convert a Tree, Element, or null, into a Tree or null. + * + * @param source the object from which to obtain source position information; may be an Element, + * a Tree, or null + * @return the tree associated with the given source object, or null if none + */ + private @Nullable Tree sourceToTree(@Nullable Object source) { + if (source instanceof Element) { + return trees.getTree((Element) source); + } else if (source instanceof Tree) { + return (Tree) source; + } else if (source == null) { + return null; + } else { + throw new BugInCF("Unexpected source %s [%s]", source, source.getClass()); + } + } + + /** + * For the given tree, compute the source positions for that tree. Return a "tuple"-like string + * (e.g. "( 1, 200 )" ) that contains the start and end position of the tree in the current + * compilation unit. Used only by the -Adetailedmsgtext output format. + * + * @param tree tree to locate within the current compilation unit + * @param currentRoot the current compilation unit + * @return a tuple string representing the range of characters that tree occupies in the source + * file, or the empty string if {@code tree} is null + */ + private String detailedMsgTextPositionString( + @Nullable Tree tree, CompilationUnitTree currentRoot) { + if (tree == null) { + return ""; + } + + SourcePositions sourcePositions = trees.getSourcePositions(); + long start = sourcePositions.getStartPosition(currentRoot, tree); + long end = sourcePositions.getEndPosition(currentRoot, tree); + + return "( " + start + ", " + end + " )"; + } + + /////////////////////////////////////////////////////////////////////////// + /// Lint options ("-Alint:xxxx" and "-Alint:-xxxx") + /// + + /** + * Determine which lint options are artive. + * + * @param options the command-line options + * @return the active lint options + */ + private Set createActiveLints(Map options) { + if (!options.containsKey("lint")) { + return Collections.emptySet(); + } + + String lintString = options.get("lint"); + if (lintString == null) { + return Collections.singleton("all"); + } + + List lintStrings = SystemUtil.COMMA_SPLITTER.splitToList(lintString); + Set activeLint = ArraySet.newArraySetOrHashSet(lintStrings.size()); + for (String s : lintStrings) { + if (!this.getSupportedLintOptions().contains(s) + && !(s.charAt(0) == '-' + && this.getSupportedLintOptions().contains(s.substring(1))) + && !s.equals("all") + && !s.equals("none")) { + this.messager.printMessage( + Diagnostic.Kind.WARNING, + "Unsupported lint option: " + + s + + "; All options: " + + this.getSupportedLintOptions()); + } + + activeLint.add(s); + if (s.equals("none")) { + activeLint.add("-all"); + } + } + + return Collections.unmodifiableSet(activeLint); + } + + /** + * Determines the value of the lint option with the given name. Just as javac uses + * "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint + * options are enabled with "-Alint:xxx" and disabled with "-Alint:-xxx". + * + * @throws IllegalArgumentException if the option name is not recognized via the {@link + * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} + * method + * @param name the name of the lint option to check for + * @return true if the lint option was given, false if it was not given or was given prepended + * with a "-" + * @see SourceChecker#getLintOption(String, boolean) + */ + public final boolean getLintOption(String name) { + return getLintOption(name, false); + } + + /** + * Determines the value of the lint option with the given name. Just as javac + * uses "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint + * options are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". + * + * @throws IllegalArgumentException if the option name is not recognized via the {@link + * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} + * method + * @param name the name of the lint option to check for + * @param def the default option value, returned if the option was not given + * @return true if the lint option was given, false if it was given prepended with a "-", or + * {@code def} if it was not given at all + * @see SourceChecker#getLintOption(String) + * @see SourceChecker#getOption(String) + */ + public final boolean getLintOption(String name, boolean def) { + if (!this.getSupportedLintOptions().contains(name)) { + throw new UserError("Illegal lint option: " + name); + } + + // This is only needed if initChecker() has not yet been called. + if (activeLints == null) { + activeLints = createActiveLints(getOptions()); + } + + if (activeLints.isEmpty()) { + return def; + } + String tofind = name; while (tofind != null) { - if (activeLints.contains(tofind) || // direct - activeLints.contains(String.format("-%s", tofind)) || // negation - activeLints.contains(tofind.substring(1))) { // name was negation - exists = true; + if (activeLints.contains(tofind)) { + return true; + } else if (activeLints.contains(String.format("-%s", tofind))) { + return false; } + tofind = parentOfOption(tofind); } + + return def; + } + + /** + * Set the value of the lint option with the given name. Just as javac + * uses "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, annotation-related lint + * options are enabled with "-Alint=xxx" and disabled with "-Alint=-xxx". This method can be + * used by subclasses to enforce having certain lint options enabled/disabled. + * + * @throws IllegalArgumentException if the option name is not recognized via the {@link + * SupportedLintOptions} annotation or the {@link SourceChecker#getSupportedLintOptions} + * method + * @param name the name of the lint option to set + * @param val the option value + * @see SourceChecker#getLintOption(String) + * @see SourceChecker#getLintOption(String,boolean) + */ + protected final void setLintOption(String name, boolean val) { + if (!this.getSupportedLintOptions().contains(name)) { + throw new UserError("Illegal lint option: " + name); + } + + /* TODO: warn if the option is also provided on the command line(?) + boolean exists = false; + if (!activeLints.isEmpty()) { + String tofind = name; + while (tofind != null) { + if (activeLints.contains(tofind) || // direct + activeLints.contains(String.format("-%s", tofind)) || // negation + activeLints.contains(tofind.substring(1))) { // name was negation + exists = true; + } + tofind = parentOfOption(tofind); + } + } + + if (exists) { + // TODO: Issue warning? + } + TODO: assert that name doesn't start with '-' + */ + + Set newlints = ArraySet.newArraySetOrHashSet(activeLints.size() + 1); + newlints.addAll(activeLints); + if (val) { + newlints.add(name); + } else { + newlints.add(String.format("-%s", name)); + } + activeLints = Collections.unmodifiableSet(newlints); + } + + /** + * Helper method to find the parent of a lint key. The lint hierarchy level is denoted by a + * colon ':'. 'all' is the root for all hierarchy. + * + *
          +     * Example
          +     *    cast:unsafe → cast
          +     *    cast        → all
          +     *    all         → {@code null}
          +     * 
          + * + * @param name the lint key whose parest to find + * @return the parent of the lint key + */ + private @Nullable String parentOfOption(String name) { + if (name.equals("all")) { + return null; + } + int colonIndex = name.lastIndexOf(':'); + if (colonIndex != -1) { + return name.substring(0, colonIndex); + } else { + return "all"; + } + } + + /** + * Returns the lint options recognized by this checker. Lint options are those which can be + * checked for via {@link SourceChecker#getLintOption}. + * + * @return an unmodifiable {@link Set} of the lint options recognized by this checker + */ + public Set getSupportedLintOptions() { + if (supportedLints == null) { + supportedLints = createSupportedLintOptions(); + } + return supportedLints; } - if (exists) { - // TODO: Issue warning? - } - TODO: assert that name doesn't start with '-' - */ - - Set newlints = ArraySet.newArraySetOrHashSet(activeLints.size() + 1); - newlints.addAll(activeLints); - if (val) { - newlints.add(name); - } else { - newlints.add(String.format("-%s", name)); - } - activeLints = Collections.unmodifiableSet(newlints); - } - - /** - * Helper method to find the parent of a lint key. The lint hierarchy level is denoted by a colon - * ':'. 'all' is the root for all hierarchy. - * - *
          -   * Example
          -   *    cast:unsafe → cast
          -   *    cast        → all
          -   *    all         → {@code null}
          -   * 
          - * - * @param name the lint key whose parest to find - * @return the parent of the lint key - */ - private @Nullable String parentOfOption(String name) { - if (name.equals("all")) { - return null; - } - int colonIndex = name.lastIndexOf(':'); - if (colonIndex != -1) { - return name.substring(0, colonIndex); - } else { - return "all"; - } - } - - /** - * Returns the lint options recognized by this checker. Lint options are those which can be - * checked for via {@link SourceChecker#getLintOption}. - * - * @return an unmodifiable {@link Set} of the lint options recognized by this checker - */ - public Set getSupportedLintOptions() { - if (supportedLints == null) { - supportedLints = createSupportedLintOptions(); - } - return supportedLints; - } - - /** Compute the set of supported lint options. */ - protected Set createSupportedLintOptions() { - SupportedLintOptions sl = this.getClass().getAnnotation(SupportedLintOptions.class); - - if (sl == null) { - return Collections.emptySet(); - } - - @Nullable String @Nullable [] slValue = sl.value(); - assert slValue != null; - - @Nullable String[] lintArray = slValue; - Set lintSet = new HashSet<>(lintArray.length); - lintSet.addAll(Arrays.asList(lintArray)); - return Collections.unmodifiableSet(lintSet); - } - - /** - * Set the supported lint options. Use of this method should be limited to the AggregateChecker, - * who needs to set the lint options to the union of all subcheckers. Also, e.g. the - * NullnessSubchecker need to use this method, as one is created by the other. - * - * @param newLints the new supported lint options, which replace any existing ones - */ - protected void setSupportedLintOptions(Set newLints) { - supportedLints = newLints; - } - - /////////////////////////////////////////////////////////////////////////// - /// Regular (non-lint) options ("-Axxxx") - /// - - /** - * Determine which options are active. - * - * @param options all provided options - * @return a value for {@link #activeOptions} - */ - private Map createActiveOptions(Map options) { - if (options.isEmpty()) { - return Collections.emptyMap(); - } - - Map activeOpts = new HashMap<>(CollectionsPlume.mapCapacity(options)); - - forEveryOption: - for (Map.Entry opt : options.entrySet()) { - String key = opt.getKey(); - String value = opt.getValue(); - - String[] split = key.split(OPTION_SEPARATOR); - - switch (split.length) { - case 1: - // No separator, option always active. - activeOpts.put(key, value); - break; - case 2: - Class clazz = this.getClass(); - - do { - if (clazz.getCanonicalName().equals(split[0]) - || clazz.getSimpleName().equals(split[0])) { - // Valid class-option pair. - activeOpts.put(split[1], value); - continue forEveryOption; + /** Compute the set of supported lint options. */ + protected Set createSupportedLintOptions() { + SupportedLintOptions sl = this.getClass().getAnnotation(SupportedLintOptions.class); + + if (sl == null) { + return Collections.emptySet(); + } + + @Nullable String @Nullable [] slValue = sl.value(); + assert slValue != null; + + @Nullable String[] lintArray = slValue; + Set lintSet = new HashSet<>(lintArray.length); + lintSet.addAll(Arrays.asList(lintArray)); + return Collections.unmodifiableSet(lintSet); + } + + /** + * Set the supported lint options. Use of this method should be limited to the AggregateChecker, + * who needs to set the lint options to the union of all subcheckers. Also, e.g. the + * NullnessSubchecker need to use this method, as one is created by the other. + * + * @param newLints the new supported lint options, which replace any existing ones + */ + protected void setSupportedLintOptions(Set newLints) { + supportedLints = newLints; + } + + /////////////////////////////////////////////////////////////////////////// + /// Regular (non-lint) options ("-Axxxx") + /// + + /** + * Determine which options are active. + * + * @param options all provided options + * @return a value for {@link #activeOptions} + */ + private Map createActiveOptions(Map options) { + if (options.isEmpty()) { + return Collections.emptyMap(); + } + + Map activeOpts = new HashMap<>(CollectionsPlume.mapCapacity(options)); + + forEveryOption: + for (Map.Entry opt : options.entrySet()) { + String key = opt.getKey(); + String value = opt.getValue(); + + String[] split = key.split(OPTION_SEPARATOR); + + switch (split.length) { + case 1: + // No separator, option always active. + activeOpts.put(key, value); + break; + case 2: + Class clazz = this.getClass(); + + do { + if (clazz.getCanonicalName().equals(split[0]) + || clazz.getSimpleName().equals(split[0])) { + // Valid class-option pair. + activeOpts.put(split[1], value); + continue forEveryOption; + } + + clazz = clazz.getSuperclass(); + } while (clazz != null + && !clazz.getName() + .equals(AbstractTypeProcessor.class.getCanonicalName())); + // Didn't find a matching class. Option might be for another processor. Add + // option anyways. javac will warn if no processor supports the option. + activeOpts.put(key, value); + break; + default: + // Too many separators. Option might be for another processor. Add option + // anyways. javac will warn if no processor supports the option. + activeOpts.put(key, value); } + // Don't add code here, there is a `continue` in the switch above. + } + return Collections.unmodifiableMap(activeOpts); + } + + /** + * Add additional active options. Use of this method should be limited to the AggregateChecker, + * who needs to set the active options to the union of all subcheckers. + * + * @param moreOpts the active options to add + */ + protected void addOptions(Map moreOpts) { + Map activeOpts = new HashMap<>(getOptions()); + activeOpts.putAll(moreOpts); + activeOptions = Collections.unmodifiableMap(activeOpts); + } + + @Override + public Map getOptions() { + if (activeOptions == null) { + activeOptions = createActiveOptions(processingEnv.getOptions()); + } + return activeOptions; + } + + @Override + public final boolean hasOption(String name) { + return getOptions().containsKey(name); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final String getOption(String name) { + return getOption(name, null); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final String getOption(String name, String defaultValue) { + // TODO: Should supportedOptions be cached? + Set supportedOptions = this.getSupportedOptions(); + if (!supportedOptions.contains(name)) { + throw new UserError( + "Illegal option: " + + name + + "; supported options = " + + String.join(",", supportedOptions)); + } + + if (activeOptions == null) { + activeOptions = createActiveOptions(processingEnv.getOptions()); + } + if (activeOptions.isEmpty()) { + return defaultValue; + } + + if (activeOptions.containsKey(name)) { + return activeOptions.get(name); + } else { + return defaultValue; + } + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final boolean getBooleanOption(String name) { + return getBooleanOption(name, false); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final boolean getBooleanOption(String name, boolean defaultValue) { + String value = getOption(name); + if (value == null) { + return defaultValue; + } + if (value.equals("true")) { + return true; + } + if (value.equals("false")) { + return false; + } + throw new UserError( + String.format( + "Value of %s option should be a boolean, but is \"%s\".", name, value)); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final List getStringsOption( + String name, char separator, List defaultValue) { + String value = getOption(name); + if (value == null) { + return defaultValue; + } + return Splitter.on(separator).omitEmptyStrings().splitToList(value); + } + + /** + * {@inheritDoc} + * + * @see SourceChecker#getLintOption(String,boolean) + */ + @Override + public final List getStringsOption( + String name, String separator, List defaultValue) { + String value = getOption(name); + if (value == null) { + return defaultValue; + } + return Splitter.on(separator).omitEmptyStrings().splitToList(value); + } + + /** + * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation + * provided version {@link javax.annotation.processing.SupportedOptions}. + */ + @Override + public Set getSupportedOptions() { + Set options = new HashSet<>(); + + // Support all options provided with the standard {@link + // javax.annotation.processing.SupportedOptions} annotation. + options.addAll(super.getSupportedOptions()); + + // For the Checker Framework annotation + // {@link org.checkerframework.framework.source.SupportedOptions} + // we additionally add + Class clazz = this.getClass(); + List> clazzPrefixes = new ArrayList<>(); + + do { + clazzPrefixes.add(clazz); + + SupportedOptions so = clazz.getAnnotation(SupportedOptions.class); + if (so != null) { + options.addAll(expandCFOptions(clazzPrefixes, so.value())); + } clazz = clazz.getSuperclass(); - } while (clazz != null - && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); - // Didn't find a matching class. Option might be for another processor. Add - // option anyways. javac will warn if no processor supports the option. - activeOpts.put(key, value); - break; - default: - // Too many separators. Option might be for another processor. Add option - // anyways. javac will warn if no processor supports the option. - activeOpts.put(key, value); - } - // Don't add code here, there is a `continue` in the switch above. - } - return Collections.unmodifiableMap(activeOpts); - } - - /** - * Add additional active options. Use of this method should be limited to the AggregateChecker, - * who needs to set the active options to the union of all subcheckers. - * - * @param moreOpts the active options to add - */ - protected void addOptions(Map moreOpts) { - Map activeOpts = new HashMap<>(getOptions()); - activeOpts.putAll(moreOpts); - activeOptions = Collections.unmodifiableMap(activeOpts); - } - - @Override - public Map getOptions() { - if (activeOptions == null) { - activeOptions = createActiveOptions(processingEnv.getOptions()); - } - return activeOptions; - } - - @Override - public final boolean hasOption(String name) { - return getOptions().containsKey(name); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final String getOption(String name) { - return getOption(name, null); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final String getOption(String name, String defaultValue) { - // TODO: Should supportedOptions be cached? - Set supportedOptions = this.getSupportedOptions(); - if (!supportedOptions.contains(name)) { - throw new UserError( - "Illegal option: " - + name - + "; supported options = " - + String.join(",", supportedOptions)); - } - - if (activeOptions == null) { - activeOptions = createActiveOptions(processingEnv.getOptions()); - } - - if (activeOptions.isEmpty()) { - return defaultValue; - } - - if (activeOptions.containsKey(name)) { - return activeOptions.get(name); - } else { - return defaultValue; - } - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final boolean getBooleanOption(String name) { - return getBooleanOption(name, false); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final boolean getBooleanOption(String name, boolean defaultValue) { - String value = getOption(name); - if (value == null) { - return defaultValue; - } - if (value.equals("true")) { - return true; - } - if (value.equals("false")) { - return false; - } - throw new UserError( - String.format("Value of %s option should be a boolean, but is \"%s\".", name, value)); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final List getStringsOption( - String name, char separator, List defaultValue) { - String value = getOption(name); - if (value == null) { - return defaultValue; - } - return Splitter.on(separator).omitEmptyStrings().splitToList(value); - } - - /** - * {@inheritDoc} - * - * @see SourceChecker#getLintOption(String,boolean) - */ - @Override - public final List getStringsOption( - String name, String separator, List defaultValue) { - String value = getOption(name); - if (value == null) { - return defaultValue; - } - return Splitter.on(separator).omitEmptyStrings().splitToList(value); - } - - /** - * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation - * provided version {@link javax.annotation.processing.SupportedOptions}. - */ - @Override - public Set getSupportedOptions() { - Set options = new HashSet<>(); - - // Support all options provided with the standard {@link - // javax.annotation.processing.SupportedOptions} annotation. - options.addAll(super.getSupportedOptions()); - - // For the Checker Framework annotation - // {@link org.checkerframework.framework.source.SupportedOptions} - // we additionally add - Class clazz = this.getClass(); - List> clazzPrefixes = new ArrayList<>(); - - do { - clazzPrefixes.add(clazz); - - SupportedOptions so = clazz.getAnnotation(SupportedOptions.class); - if (so != null) { - options.addAll(expandCFOptions(clazzPrefixes, so.value())); - } - clazz = clazz.getSuperclass(); - } while (clazz != null - && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); - - return Collections.unmodifiableSet(options); - } - - /** - * Generate the possible command-line option names by prefixing each class name from {@code - * classPrefixes} to {@code options}, separated by {@link #OPTION_SEPARATOR}. - * - * @param clazzPrefixes the classes to prefix - * @param options the option names - * @return the possible combinations that should be supported - */ - protected Collection expandCFOptions( - List> clazzPrefixes, String[] options) { - Set res = - new HashSet<>(CollectionsPlume.mapCapacity(options.length * (1 + clazzPrefixes.size()))); - for (String option : options) { - res.add(option); - for (Class clazz : clazzPrefixes) { - res.add(clazz.getCanonicalName() + OPTION_SEPARATOR + option); - res.add(clazz.getSimpleName() + OPTION_SEPARATOR + option); - } - } - return res; - } - - /** - * Overrides the default implementation to always return a singleton set containing only "*". - * - *

          javac uses this list to determine which classes process; javac only runs an annotation - * processor on classes that contain at least one of the mentioned annotations. Thus, the effect - * of returning "*" is as if the checker were annotated by {@code @SupportedAnnotationTypes("*")}: - * javac runs the checker on every class mentioned on the javac command line. This method also - * checks that subclasses do not contain a {@link SupportedAnnotationTypes} annotation. - * - *

          To specify the annotations that a checker recognizes as type qualifiers, see {@link - * AnnotatedTypeFactory#createSupportedTypeQualifiers()}. - * - * @throws Error if a subclass is annotated with {@link SupportedAnnotationTypes} - */ - @Override - public final Set getSupportedAnnotationTypes() { - SupportedAnnotationTypes supported = - this.getClass().getAnnotation(SupportedAnnotationTypes.class); - if (supported != null) { - throw new BugInCF( - "@SupportedAnnotationTypes should not be written on any checker;" - + " supported annotation types are inherited from SourceChecker."); - } - return Collections.singleton("*"); - } - - /////////////////////////////////////////////////////////////////////////// - /// Warning suppression and unneeded warnings - /// - - /** - * Returns the argument to {@code -AsuppressWarnings}, split on commas, or null if no such - * argument. Only ever called once; the value is cached in field {@link - * #suppressWarningsStringsFromOption}. - * - * @return the argument to {@code -AsuppressWarnings}, split on commas, or null if no such - * argument - */ - private String @Nullable [] getSuppressWarningsStringsFromOption() { - if (!computedSuppressWarningsStringsFromOption) { - computedSuppressWarningsStringsFromOption = true; - Map options = getAllOptions(); - if (options.containsKey("suppressWarnings")) { - String swStrings = options.get("suppressWarnings"); - if (swStrings != null) { - this.suppressWarningsStringsFromOption = swStrings.split(","); - } - } - } - - return this.suppressWarningsStringsFromOption; - } - - /** - * Returns the options passed to this checker and its immediate parent checker. - * - * @return the options passed to this checker and its immediate parent checker - */ - private Map getAllOptions() { - if (parentChecker == null) { - return getOptions(); - } - Map allOptions = new HashMap<>(this.getOptions()); - parentChecker - .getOptions() - .forEach( - (parentOptKey, parentOptVal) -> { - if (parentOptVal != null) { - allOptions.merge(parentOptKey, parentOptVal, this::combineOptionValues); - } - }); - return Collections.unmodifiableMap(allOptions); - } - - /** - * Combines two comma-delimited strings into a single comma-delimited string that does not contain - * duplicates. - * - *

          Checker option values are comma-delimited. This method combines two option values while - * discarding possible duplicates. - * - * @param optionValueA the first comma-delimited string - * @param optionValueB the second comma-delimited string - * @return a comma-delimited string containing values from the first and second string, with no - * duplicates - */ - private String combineOptionValues(String optionValueA, String optionValueB) { - Set optionValueASet = - Arrays.stream(optionValueA.split(",")).collect(Collectors.toSet()); - Set optionValueBSet = - Arrays.stream(optionValueB.split(",")).collect(Collectors.toSet()); - optionValueASet.addAll(optionValueBSet); - return String.join(",", optionValueASet); - } - - /** - * Issues a warning about any {@code @SuppressWarnings} that didn't suppress a warning, but starts - * with this checker name or "allcheckers". - */ - protected void warnUnneededSuppressions() { - if (!warnUnneededSuppressions) { - return; - } - - Set elementsSuppress = new HashSet<>(this.elementsWithSuppressedWarnings); - this.elementsWithSuppressedWarnings.clear(); - Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); - Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); - warnUnneededSuppressions(elementsSuppress, prefixes, errorKeys); - getVisitor().treesWithSuppressWarnings.clear(); - } - - /** - * Issues a warning about any {@code @SuppressWarnings} string that didn't suppress a warning, but - * starts with one of the given prefixes (checker names). Does nothing if the string doesn't start - * with a checker name. - * - * @param elementsSuppress elements with a {@code @SuppressWarnings} that actually suppressed a - * warning - * @param prefixes the SuppressWarnings prefixes that suppress all warnings from this checker - * @param allErrorKeys all error keys that can be issued by this checker - */ - protected void warnUnneededSuppressions( - Set elementsSuppress, Set prefixes, Set allErrorKeys) { - for (Tree tree : getVisitor().treesWithSuppressWarnings) { - Element elt = TreeUtils.elementFromTree(tree); - // TODO: This test is too coarse. The fact that this @SuppressWarnings suppressed - // *some* warning doesn't mean that every value in it did so. - if (elementsSuppress.contains(elt)) { - continue; - } - // tree has a @SuppressWarnings annotation that didn't suppress any warnings. - SuppressWarnings suppressAnno = elt.getAnnotation(SuppressWarnings.class); - String[] suppressWarningsStrings = suppressAnno.value(); - for (String suppressWarningsString : suppressWarningsStrings) { - if (warnUnneededSuppressionsExceptions != null - && warnUnneededSuppressionsExceptions.matcher(suppressWarningsString).find(0)) { - continue; - } - for (String prefix : prefixes) { - if (suppressWarningsString.equals(prefix) - || (suppressWarningsString.startsWith(prefix + ":") - && !suppressWarningsString.equals(prefix + ":unneeded.suppression"))) { - reportUnneededSuppression(tree, suppressWarningsString); - break; // Don't report the same warning string more than once. - } - } - } - } - } - - /** - * Issues a warning that the string in a {@code @SuppressWarnings} on {@code tree} isn't needed. - * - * @param tree has unneeded {@code @SuppressWarnings} - * @param suppressWarningsString the SuppressWarnings string that isn't needed - */ - private void reportUnneededSuppression(Tree tree, String suppressWarningsString) { - Tree swTree = findSuppressWarningsAnnotationTree(tree); - report( - swTree, - Diagnostic.Kind.MANDATORY_WARNING, - SourceChecker.UNNEEDED_SUPPRESSION_KEY, - "\"" + suppressWarningsString + "\"", - getClass().getSimpleName()); - } - - /** The name of the @SuppressWarnings annotation. */ - private static final @CanonicalName String suppressWarningsClassName = - SuppressWarnings.class.getCanonicalName(); - - /** - * Finds the tree that is a {@code @SuppressWarnings} annotation. - * - * @param tree a class, method, or variable tree annotated with {@code @SuppressWarnings} - * @return tree for {@code @SuppressWarnings} or {@code default} if one isn't found - */ - private Tree findSuppressWarningsAnnotationTree(Tree tree) { - List annotations; - if (TreeUtils.isClassTree(tree)) { - annotations = ((ClassTree) tree).getModifiers().getAnnotations(); - } else if (tree.getKind() == Tree.Kind.METHOD) { - annotations = ((MethodTree) tree).getModifiers().getAnnotations(); - } else { - annotations = ((VariableTree) tree).getModifiers().getAnnotations(); - } - - for (AnnotationTree annotationTree : annotations) { - if (AnnotationUtils.areSameByName( - TreeUtils.annotationFromAnnotationTree(annotationTree), suppressWarningsClassName)) { - return annotationTree; - } - } - throw new BugInCF("Did not find @SuppressWarnings: " + tree); - } - - /** - * Returns true if all the warnings pertaining to the given source should be suppressed. This - * implementation just delegates to an overloaded, more specific version of {@code - * shouldSuppressWarnings()}. - * - * @param src the position object to test; may be an Element, a Tree, or null - * @param errKey the error key the checker is emitting - * @return true if all warnings pertaining to the given source should be suppressed - * @see #shouldSuppressWarnings(Element, String) - * @see #shouldSuppressWarnings(Tree, String) - */ - private boolean shouldSuppressWarnings(@Nullable Object src, String errKey) { - if (src instanceof Element) { - return shouldSuppressWarnings((Element) src, errKey); - } else if (src instanceof Tree) { - return shouldSuppressWarnings((Tree) src, errKey); - } else if (src == null) { - return false; - } else { - throw new BugInCF("Unexpected source [" + src.getClass() + "] " + src); - } - } - - /** - * Returns true if all the warnings pertaining to a given tree should be suppressed. Returns true - * if the tree is within the scope of a @SuppressWarnings annotation, one of whose values - * suppresses the checker's warning. Also, returns true if the {@code errKey} matches a string in - * {@code -AsuppressWarnings}. - * - * @param tree the tree that might be a source of a warning - * @param errKey the error key the checker is emitting - * @return true if no warning should be emitted for the given tree because it is contained by a - * declaration with an appropriately-valued {@literal @}SuppressWarnings annotation; false - * otherwise - */ - public boolean shouldSuppressWarnings(Tree tree, String errKey) { - Collection prefixes = getSuppressWarningsPrefixes(); - if (prefixes.isEmpty() || (prefixes.contains(SUPPRESS_ALL_PREFIX) && prefixes.size() == 1)) { - throw new BugInCF( - "Checker must provide a SuppressWarnings prefix." - + " SourceChecker#getSuppressWarningsPrefixes was not overridden" - + " correctly."); - } - - if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { - // If the error key matches a warning string in the -AsuppressWarnings, then suppress - // the warning. - return true; - } - - TreePath path = getTreePathCacher().getPath(currentRoot, tree); - return shouldSuppressWarnings(path, errKey); - } - - /** - * Returns true if all the warnings pertaining to a given tree path should be suppressed. Returns - * true if the path is within the scope of a @SuppressWarnings annotation, one of whose values - * suppresses the checker's warning. - * - * @param path the TreePath that might be a source of, or related to, a warning - * @param errKey the error key the checker is emitting - * @return true if no warning should be emitted for the given path because it is contained by a - * declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false - * otherwise - */ - public boolean shouldSuppressWarnings(@Nullable TreePath path, String errKey) { - if (path == null) { - return false; - } - - // iterate through the path; continue until path contains no declarations - for (TreePath declPath = TreePathUtil.enclosingDeclarationPath(path); - declPath != null; - declPath = TreePathUtil.enclosingDeclarationPath(declPath.getParentPath())) { - - Tree decl = declPath.getLeaf(); - - if (decl.getKind() == Tree.Kind.VARIABLE) { - Element elt = TreeUtils.elementFromDeclaration((VariableTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { - return true; - } - } else if (decl.getKind() == Tree.Kind.METHOD) { - Element elt = TreeUtils.elementFromDeclaration((MethodTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { - return true; - } - - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; - } - } else if (TreeUtils.classTreeKinds().contains(decl.getKind())) { - // A class tree - Element elt = TreeUtils.elementFromDeclaration((ClassTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { - return true; - } - - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; - } - Element packageElement = elt.getEnclosingElement(); - if (packageElement != null && packageElement.getKind() == ElementKind.PACKAGE) { - if (shouldSuppressWarnings(packageElement, errKey)) { + } while (clazz != null + && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); + + return Collections.unmodifiableSet(options); + } + + /** + * Generate the possible command-line option names by prefixing each class name from {@code + * classPrefixes} to {@code options}, separated by {@link #OPTION_SEPARATOR}. + * + * @param clazzPrefixes the classes to prefix + * @param options the option names + * @return the possible combinations that should be supported + */ + protected Collection expandCFOptions( + List> clazzPrefixes, String[] options) { + Set res = + new HashSet<>( + CollectionsPlume.mapCapacity(options.length * (1 + clazzPrefixes.size()))); + for (String option : options) { + res.add(option); + for (Class clazz : clazzPrefixes) { + res.add(clazz.getCanonicalName() + OPTION_SEPARATOR + option); + res.add(clazz.getSimpleName() + OPTION_SEPARATOR + option); + } + } + return res; + } + + /** + * Overrides the default implementation to always return a singleton set containing only "*". + * + *

          javac uses this list to determine which classes process; javac only runs an annotation + * processor on classes that contain at least one of the mentioned annotations. Thus, the effect + * of returning "*" is as if the checker were annotated by + * {@code @SupportedAnnotationTypes("*")}: javac runs the checker on every class mentioned on + * the javac command line. This method also checks that subclasses do not contain a {@link + * SupportedAnnotationTypes} annotation. + * + *

          To specify the annotations that a checker recognizes as type qualifiers, see {@link + * AnnotatedTypeFactory#createSupportedTypeQualifiers()}. + * + * @throws Error if a subclass is annotated with {@link SupportedAnnotationTypes} + */ + @Override + public final Set getSupportedAnnotationTypes() { + SupportedAnnotationTypes supported = + this.getClass().getAnnotation(SupportedAnnotationTypes.class); + if (supported != null) { + throw new BugInCF( + "@SupportedAnnotationTypes should not be written on any checker;" + + " supported annotation types are inherited from SourceChecker."); + } + return Collections.singleton("*"); + } + + /////////////////////////////////////////////////////////////////////////// + /// Warning suppression and unneeded warnings + /// + + /** + * Returns the argument to {@code -AsuppressWarnings}, split on commas, or null if no such + * argument. Only ever called once; the value is cached in field {@link + * #suppressWarningsStringsFromOption}. + * + * @return the argument to {@code -AsuppressWarnings}, split on commas, or null if no such + * argument + */ + private String @Nullable [] getSuppressWarningsStringsFromOption() { + if (!computedSuppressWarningsStringsFromOption) { + computedSuppressWarningsStringsFromOption = true; + Map options = getAllOptions(); + if (options.containsKey("suppressWarnings")) { + String swStrings = options.get("suppressWarnings"); + if (swStrings != null) { + this.suppressWarningsStringsFromOption = swStrings.split(","); + } + } + } + + return this.suppressWarningsStringsFromOption; + } + + /** + * Returns the options passed to this checker and its immediate parent checker. + * + * @return the options passed to this checker and its immediate parent checker + */ + private Map getAllOptions() { + if (parentChecker == null) { + return getOptions(); + } + Map allOptions = new HashMap<>(this.getOptions()); + parentChecker + .getOptions() + .forEach( + (parentOptKey, parentOptVal) -> { + if (parentOptVal != null) { + allOptions.merge( + parentOptKey, parentOptVal, this::combineOptionValues); + } + }); + return Collections.unmodifiableMap(allOptions); + } + + /** + * Combines two comma-delimited strings into a single comma-delimited string that does not + * contain duplicates. + * + *

          Checker option values are comma-delimited. This method combines two option values while + * discarding possible duplicates. + * + * @param optionValueA the first comma-delimited string + * @param optionValueB the second comma-delimited string + * @return a comma-delimited string containing values from the first and second string, with no + * duplicates + */ + private String combineOptionValues(String optionValueA, String optionValueB) { + Set optionValueASet = + Arrays.stream(optionValueA.split(",")).collect(Collectors.toSet()); + Set optionValueBSet = + Arrays.stream(optionValueB.split(",")).collect(Collectors.toSet()); + optionValueASet.addAll(optionValueBSet); + return String.join(",", optionValueASet); + } + + /** + * Issues a warning about any {@code @SuppressWarnings} that didn't suppress a warning, but + * starts with this checker name or "allcheckers". + */ + protected void warnUnneededSuppressions() { + if (!warnUnneededSuppressions) { + return; + } + + Set elementsSuppress = new HashSet<>(this.elementsWithSuppressedWarnings); + this.elementsWithSuppressedWarnings.clear(); + Set prefixes = new HashSet<>(getSuppressWarningsPrefixes()); + Set errorKeys = new HashSet<>(messagesProperties.stringPropertyNames()); + warnUnneededSuppressions(elementsSuppress, prefixes, errorKeys); + getVisitor().treesWithSuppressWarnings.clear(); + } + + /** + * Issues a warning about any {@code @SuppressWarnings} string that didn't suppress a warning, + * but starts with one of the given prefixes (checker names). Does nothing if the string doesn't + * start with a checker name. + * + * @param elementsSuppress elements with a {@code @SuppressWarnings} that actually suppressed a + * warning + * @param prefixes the SuppressWarnings prefixes that suppress all warnings from this checker + * @param allErrorKeys all error keys that can be issued by this checker + */ + protected void warnUnneededSuppressions( + Set elementsSuppress, Set prefixes, Set allErrorKeys) { + for (Tree tree : getVisitor().treesWithSuppressWarnings) { + Element elt = TreeUtils.elementFromTree(tree); + // TODO: This test is too coarse. The fact that this @SuppressWarnings suppressed + // *some* warning doesn't mean that every value in it did so. + if (elementsSuppress.contains(elt)) { + continue; + } + // tree has a @SuppressWarnings annotation that didn't suppress any warnings. + SuppressWarnings suppressAnno = elt.getAnnotation(SuppressWarnings.class); + String[] suppressWarningsStrings = suppressAnno.value(); + for (String suppressWarningsString : suppressWarningsStrings) { + if (warnUnneededSuppressionsExceptions != null + && warnUnneededSuppressionsExceptions + .matcher(suppressWarningsString) + .find(0)) { + continue; + } + for (String prefix : prefixes) { + if (suppressWarningsString.equals(prefix) + || (suppressWarningsString.startsWith(prefix + ":") + && !suppressWarningsString.equals( + prefix + ":unneeded.suppression"))) { + reportUnneededSuppression(tree, suppressWarningsString); + break; // Don't report the same warning string more than once. + } + } + } + } + } + + /** + * Issues a warning that the string in a {@code @SuppressWarnings} on {@code tree} isn't needed. + * + * @param tree has unneeded {@code @SuppressWarnings} + * @param suppressWarningsString the SuppressWarnings string that isn't needed + */ + private void reportUnneededSuppression(Tree tree, String suppressWarningsString) { + Tree swTree = findSuppressWarningsAnnotationTree(tree); + report( + swTree, + Diagnostic.Kind.MANDATORY_WARNING, + SourceChecker.UNNEEDED_SUPPRESSION_KEY, + "\"" + suppressWarningsString + "\"", + getClass().getSimpleName()); + } + + /** The name of the @SuppressWarnings annotation. */ + private static final @CanonicalName String suppressWarningsClassName = + SuppressWarnings.class.getCanonicalName(); + + /** + * Finds the tree that is a {@code @SuppressWarnings} annotation. + * + * @param tree a class, method, or variable tree annotated with {@code @SuppressWarnings} + * @return tree for {@code @SuppressWarnings} or {@code default} if one isn't found + */ + private Tree findSuppressWarningsAnnotationTree(Tree tree) { + List annotations; + if (TreeUtils.isClassTree(tree)) { + annotations = ((ClassTree) tree).getModifiers().getAnnotations(); + } else if (tree.getKind() == Tree.Kind.METHOD) { + annotations = ((MethodTree) tree).getModifiers().getAnnotations(); + } else { + annotations = ((VariableTree) tree).getModifiers().getAnnotations(); + } + + for (AnnotationTree annotationTree : annotations) { + if (AnnotationUtils.areSameByName( + TreeUtils.annotationFromAnnotationTree(annotationTree), + suppressWarningsClassName)) { + return annotationTree; + } + } + throw new BugInCF("Did not find @SuppressWarnings: " + tree); + } + + /** + * Returns true if all the warnings pertaining to the given source should be suppressed. This + * implementation just delegates to an overloaded, more specific version of {@code + * shouldSuppressWarnings()}. + * + * @param src the position object to test; may be an Element, a Tree, or null + * @param errKey the error key the checker is emitting + * @return true if all warnings pertaining to the given source should be suppressed + * @see #shouldSuppressWarnings(Element, String) + * @see #shouldSuppressWarnings(Tree, String) + */ + private boolean shouldSuppressWarnings(@Nullable Object src, String errKey) { + if (src instanceof Element) { + return shouldSuppressWarnings((Element) src, errKey); + } else if (src instanceof Tree) { + return shouldSuppressWarnings((Tree) src, errKey); + } else if (src == null) { + return false; + } else { + throw new BugInCF("Unexpected source [" + src.getClass() + "] " + src); + } + } + + /** + * Returns true if all the warnings pertaining to a given tree should be suppressed. Returns + * true if the tree is within the scope of a @SuppressWarnings annotation, one of whose values + * suppresses the checker's warning. Also, returns true if the {@code errKey} matches a string + * in {@code -AsuppressWarnings}. + * + * @param tree the tree that might be a source of a warning + * @param errKey the error key the checker is emitting + * @return true if no warning should be emitted for the given tree because it is contained by a + * declaration with an appropriately-valued {@literal @}SuppressWarnings annotation; false + * otherwise + */ + public boolean shouldSuppressWarnings(Tree tree, String errKey) { + Collection prefixes = getSuppressWarningsPrefixes(); + if (prefixes.isEmpty() + || (prefixes.contains(SUPPRESS_ALL_PREFIX) && prefixes.size() == 1)) { + throw new BugInCF( + "Checker must provide a SuppressWarnings prefix." + + " SourceChecker#getSuppressWarningsPrefixes was not overridden" + + " correctly."); + } + + if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { + // If the error key matches a warning string in the -AsuppressWarnings, then suppress + // the warning. return true; - } - if (isAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. + } + + TreePath path = getTreePathCacher().getPath(currentRoot, tree); + return shouldSuppressWarnings(path, errKey); + } + + /** + * Returns true if all the warnings pertaining to a given tree path should be suppressed. + * Returns true if the path is within the scope of a @SuppressWarnings annotation, one of whose + * values suppresses the checker's warning. + * + * @param path the TreePath that might be a source of, or related to, a warning + * @param errKey the error key the checker is emitting + * @return true if no warning should be emitted for the given path because it is contained by a + * declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false + * otherwise + */ + public boolean shouldSuppressWarnings(@Nullable TreePath path, String errKey) { + if (path == null) { return false; - } - } - } else { - throw new BugInCF("Unexpected declaration kind: " + decl.getKind() + " " + decl); - } - } - - if (useConservativeDefault("source")) { - // If we got this far without hitting an @AnnotatedFor and returning - // false, we DO suppress the warning. - return true; - } - - return false; - } - - /** - * Should conservative defaults be used for the kind of unchecked code indicated by the parameter? - * - * @param kindOfCode source or bytecode - * @return whether conservative defaults should be used - */ - public boolean useConservativeDefault(String kindOfCode) { - boolean useUncheckedDefaultsForSource = false; - boolean useUncheckedDefaultsForByteCode = false; - for (String arg : this.getStringsOption("useConservativeDefaultsForUncheckedCode", ',')) { - boolean value = arg.indexOf("-") != 0; - arg = value ? arg : arg.substring(1); - if (arg.equals(kindOfCode)) { - return value; - } - } - if (kindOfCode.equals("source")) { - return useUncheckedDefaultsForSource; - } else if (kindOfCode.equals("bytecode")) { - return useUncheckedDefaultsForByteCode; - } else { - throw new UserError( - "SourceChecker: unexpected argument to useConservativeDefault: " + kindOfCode); - } - } - - /** - * Elements with a {@code @SuppressWarnings} that actually suppressed a warning for this checker. - */ - protected final Set elementsWithSuppressedWarnings = new HashSet<>(); - - /** - * Returns true if all the warnings pertaining to a given element should be suppressed. Returns - * true if the element is within the scope of a @SuppressWarnings annotation, one of whose values - * suppresses all the checker's warnings. - * - * @param elt the Element that might be a source of, or related to, a warning - * @param errKey the error key the checker is emitting - * @return true if no warning should be emitted for the given Element because it is contained by a - * declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false - * otherwise - */ - public boolean shouldSuppressWarnings(@Nullable Element elt, String errKey) { - if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { - return true; - } - - for (Element currElt = elt; currElt != null; currElt = currElt.getEnclosingElement()) { - SuppressWarnings suppressWarningsAnno = currElt.getAnnotation(SuppressWarnings.class); - if (suppressWarningsAnno != null) { - String[] suppressWarningsStrings = suppressWarningsAnno.value(); - if (shouldSuppress(suppressWarningsStrings, errKey)) { - if (warnUnneededSuppressions) { - elementsWithSuppressedWarnings.add(currElt); - } - return true; - } - } - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the - // enclosing elements, because they may not have an @AnnotatedFor. + } + + // iterate through the path; continue until path contains no declarations + for (TreePath declPath = TreePathUtil.enclosingDeclarationPath(path); + declPath != null; + declPath = TreePathUtil.enclosingDeclarationPath(declPath.getParentPath())) { + + Tree decl = declPath.getLeaf(); + + if (decl.getKind() == Tree.Kind.VARIABLE) { + Element elt = TreeUtils.elementFromDeclaration((VariableTree) decl); + if (shouldSuppressWarnings(elt, errKey)) { + return true; + } + } else if (decl.getKind() == Tree.Kind.METHOD) { + Element elt = TreeUtils.elementFromDeclaration((MethodTree) decl); + if (shouldSuppressWarnings(elt, errKey)) { + return true; + } + + if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + // Return false immediately. Do NOT check for AnnotatedFor in the enclosing + // elements as the closest AnnotatedFor is already found. + return false; + } + } else if (TreeUtils.classTreeKinds().contains(decl.getKind())) { + // A class tree + Element elt = TreeUtils.elementFromDeclaration((ClassTree) decl); + if (shouldSuppressWarnings(elt, errKey)) { + return true; + } + + if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + // Return false immediately. Do NOT check for AnnotatedFor in the enclosing + // elements as the closest AnnotatedFor is already found. + return false; + } + Element packageElement = elt.getEnclosingElement(); + if (packageElement != null && packageElement.getKind() == ElementKind.PACKAGE) { + if (shouldSuppressWarnings(packageElement, errKey)) { + return true; + } + if (isAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { + // Return false immediately. Do NOT check for AnnotatedFor in the enclosing + // elements as the closest AnnotatedFor is already found. + return false; + } + } + } else { + throw new BugInCF("Unexpected declaration kind: " + decl.getKind() + " " + decl); + } + } + + if (useConservativeDefault("source")) { + // If we got this far without hitting an @AnnotatedFor and returning + // false, we DO suppress the warning. + return true; + } + return false; - } - } - return false; - } - - /** - * Returns true if an error (whose message key is {@code messageKey}) should be suppressed. It is - * suppressed if any of the given SuppressWarnings strings suppresses it. - * - *

          A SuppressWarnings string may be of the following pattern: - * - *

            - *
          1. {@code "prefix"}, where prefix is a SuppressWarnings prefix, as specified by {@link - * #getSuppressWarningsPrefixes()}. For example, {@code "nullness"} and {@code - * "initialization"} for the Nullness Checker, {@code "regex"} for the Regex Checker. - *
          2. {@code "partial-message-key"}, where partial-message-key is a prefix or suffix of the - * message key that it may suppress. So "generic.argument" would suppress any errors whose - * message key contains "generic.argument". - *
          3. {@code "prefix:partial-message-key}, where the prefix and partial-message-key is as - * above. So "nullness:generic.argument", would suppress any errors in the Nullness Checker - * with a message key that contains "generic.argument". - *
          - * - * {@code "allcheckers"} is a prefix that suppresses a warning from any checker. {@code "all"} is - * a partial-message-key that suppresses a warning with any message key. - * - *

          If the {@code -ArequirePrefixInWarningSuppressions} command-line argument was supplied, then - * {@code "partial-message-key"} has no effect; {@code "prefix"} and {@code - * "prefix:partial-message-key"} are the only SuppressWarnings strings that have an effect. - * - * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, - * in which case this method returns false. - * @param messageKey the message key of the error the checker is emitting; a lowercase string, - * without any "checkername:" prefix - * @return true if an element of {@code suppressWarningsInEffect} suppresses the error - */ - private boolean shouldSuppress(String @Nullable [] suppressWarningsInEffect, String messageKey) { - Set prefixes = this.getSuppressWarningsPrefixes(); - return shouldSuppress(prefixes, suppressWarningsInEffect, messageKey); - } - - /** - * Helper method for {@link #shouldSuppress(String[], String)}. - * - * @param prefixes the SuppressWarnings prefixes used by this checker - * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, - * in which case this method returns false. - * @param messageKey the message key of the error the checker is emitting; a lowercase string, - * without any "checkername:" prefix - * @return true if one of the {@code suppressWarningsInEffect} suppresses the error - */ - private boolean shouldSuppress( - Set prefixes, String @Nullable [] suppressWarningsInEffect, String messageKey) { - if (suppressWarningsInEffect == null) { - return false; - } - - for (String currentSuppressWarningsInEffect : suppressWarningsInEffect) { - int colonPos = currentSuppressWarningsInEffect.indexOf(":"); - String messageKeyInSuppressWarningsString; - if (colonPos == -1) { - // The SuppressWarnings string has no colon, so it is not of the form - // prefix:partial-message-key. - if (prefixes.contains(currentSuppressWarningsInEffect)) { - // The value in the @SuppressWarnings is exactly a prefix. - // Suppress the warning unless its message key is "unneeded.suppression". - boolean result = !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); - return result; - } else if (requirePrefixInWarningSuppressions) { - // A prefix is required, but this SuppressWarnings string does not have a - // prefix; check the next SuppressWarnings string. - continue; - } else if (currentSuppressWarningsInEffect.equals(SUPPRESS_ALL_MESSAGE_KEY)) { - // Prefixes aren't required and the SuppressWarnings string is "all". - // Suppress the warning unless its message key is "unneeded.suppression". - boolean result = !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); - return result; - } - // The currentSuppressWarningsInEffect is not a prefix or a prefix:message-key, so - // it might be a message key. - messageKeyInSuppressWarningsString = currentSuppressWarningsInEffect; - } else { - // The SuppressWarnings string has a colon; that is, it has a prefix. - String currentSuppressWarningsPrefix = - currentSuppressWarningsInEffect.substring(0, colonPos); - if (!prefixes.contains(currentSuppressWarningsPrefix)) { - // The prefix of this SuppressWarnings string is a not a prefix supported by - // this checker. Proceed to the next SuppressWarnings string. - continue; - } - messageKeyInSuppressWarningsString = - currentSuppressWarningsInEffect.substring(colonPos + 1); - } - // Check if the message key in the warning suppression is part of the message key that - // the checker is emiting. - if (messageKeyMatches(messageKey, messageKeyInSuppressWarningsString)) { - return true; - } - } - - // None of the SuppressWarnings strings suppresses this error. - return false; - } - - /** - * Does the given messageKey match a messageKey that appears in a SuppressWarnings? Subclasses - * should override this method if they need additional logic to compare message keys. - * - * @param messageKey the message key of the error that is being emitted, without any "checker:" - * prefix - * @param messageKeyInSuppressWarningsString the message key in a {@code @SuppressWarnings} - * annotation - * @return true if the arguments match - */ - protected boolean messageKeyMatches( - String messageKey, String messageKeyInSuppressWarningsString) { - return messageKey.equals(messageKeyInSuppressWarningsString) - || messageKey.startsWith(messageKeyInSuppressWarningsString + ".") - || messageKey.endsWith("." + messageKeyInSuppressWarningsString) - || messageKey.contains("." + messageKeyInSuppressWarningsString + "."); - } - - /** - * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an - * upstream checker that called this one. - * - * @param elt the source code element to check, or null - * @return true if the element is annotated for this checker or an upstream checker - */ - private boolean isAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { - if (elt == null || !useConservativeDefault("source")) { - return false; - } - - AnnotatedFor anno = elt.getAnnotation(AnnotatedFor.class); - - String[] userAnnotatedFors = (anno == null ? null : anno.value()); - - if (userAnnotatedFors != null) { - List<@FullyQualifiedName String> upstreamCheckerNames = getUpstreamCheckerNames(); - - for (String userAnnotatedFor : userAnnotatedFors) { - if (CheckerMain.matchesCheckerOrSubcheckerFromList( - userAnnotatedFor, upstreamCheckerNames)) { - return true; - } - } - } - - return false; - } - - /** - * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings strings. - * - *

          The collection must not be empty and must not contain only {@link #SUPPRESS_ALL_PREFIX}. - * - * @return non-empty modifiable set of lower-case prefixes for SuppressWarnings strings - */ - public NavigableSet getSuppressWarningsPrefixes() { - return getStandardSuppressWarningsPrefixes(); - } - - /** - * Returns a sorted set of SuppressWarnings prefixes read from the {@link SuppressWarningsPrefix} - * meta-annotation on the checker class. Or if no {@link SuppressWarningsPrefix} is used, the - * checker name is used. {@link #SUPPRESS_ALL_PREFIX} is also added, at the end, unless {@link - * #useAllcheckersPrefix} is false. - * - * @return a sorted set of SuppressWarnings prefixes - */ - protected final NavigableSet getStandardSuppressWarningsPrefixes() { - NavigableSet prefixes = new TreeSet<>(); - if (useAllcheckersPrefix) { - prefixes.add(SUPPRESS_ALL_PREFIX); - } - SuppressWarningsPrefix prefixMetaAnno = - this.getClass().getAnnotation(SuppressWarningsPrefix.class); - if (prefixMetaAnno != null) { - for (String prefix : prefixMetaAnno.value()) { - prefixes.add(prefix); - } - return prefixes; - } - - // No @SuppressWarningsPrefixes annotation, by default infer key from class name. - String defaultPrefix = getDefaultSuppressWarningsPrefix(); - prefixes.add(defaultPrefix); - return prefixes; - } - - /** - * Returns the default SuppressWarnings prefix for this checker based on the checker name. - * - * @return the default SuppressWarnings prefix for this checker based on the checker name - */ - private String getDefaultSuppressWarningsPrefix() { - String className = this.getClass().getSimpleName(); - int indexOfChecker = className.lastIndexOf("Checker"); - if (indexOfChecker == -1) { - indexOfChecker = className.lastIndexOf("Subchecker"); - } - String result = (indexOfChecker == -1) ? className : className.substring(0, indexOfChecker); - return result.toLowerCase(Locale.ROOT); - } - - /** - * Returns the prefix that should be added when issuing an error or warning if the {@code - * -AshowPrefixInWarningMessages} command-line option was passed. - * - *

          The default implementation uses the default prefix based on the class name if that default - * prefix is contained in {@link #getSuppressWarningsPrefixes()}. Otherwise, it uses the first - * element of {@link #getSuppressWarningsPrefixes()}. - * - * @return the prefix that should be added when issuing an error or warning if the * {@code - * -AshowPrefixInWarningMessages} command-line option was passed - */ - protected String getWarningMessagePrefix() { - Collection prefixes = this.getSuppressWarningsPrefixes(); - prefixes.remove(SUPPRESS_ALL_PREFIX); - String defaultPrefix = getDefaultSuppressWarningsPrefix(); - if (prefixes.contains(defaultPrefix)) { - return defaultPrefix; - } else { - String firstKey = prefixes.iterator().next(); - return firstKey; - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Skipping uses and defs - /// - - /** - * Tests whether the class owner of the passed element is an unannotated class and matches the - * pattern specified in the {@code checker.skipUses} property. - * - * @param element an element - * @return true iff the enclosing class of element should be skipped - */ - public final boolean shouldSkipUses(@Nullable Element element) { - if (element == null) { - return false; - } - TypeElement typeElement = ElementUtils.enclosingTypeElement(element); - if (typeElement == null) { - throw new BugInCF("enclosingTypeElement(%s [%s]) => null%n", element, element.getClass()); - } - @SuppressWarnings("signature:assignment.type.incompatible" // TypeElement.toString(): - // @FullyQualifiedName - ) - @FullyQualifiedName String name = typeElement.toString(); - return shouldSkipUses(name); - } - - /** - * Tests whether the class owner of the passed type matches the pattern specified in the {@code - * checker.skipUses} property. In contrast to {@link #shouldSkipUses(Element)} this version can - * also be used from primitive types, which don't have an element. - * - *

          Checkers that require their annotations not to be checked on certain JDK classes may - * override this method to skip them. They shall call {@code super.shouldSkipUses(typeName)} to - * also skip the classes matching the pattern. - * - * @param typeName the fully-qualified name of a type - * @return true iff the enclosing class of element should be skipped - */ - public boolean shouldSkipUses(@FullyQualifiedName String typeName) { - // System.out.printf("shouldSkipUses(%s) %s%nskipUses %s%nonlyUses %s%nresult %s%n", - // element, - // name, - // skipUsesPattern.matcher(name).find(), - // onlyUsesPattern.matcher(name).find(), - // (skipUsesPattern.matcher(name).find() - // || ! onlyUsesPattern.matcher(name).find())); - // StackTraceElement[] stea = new Throwable().getStackTrace(); - // for (int i=0; i<3; i++) { - // System.out.println(" " + stea[i]); - // } - // System.out.println(); - if (skipUsesPattern == null) { - skipUsesPattern = getSkipUsesPattern(getOptions()); - } - if (onlyUsesPattern == null) { - onlyUsesPattern = getOnlyUsesPattern(getOptions()); - } - return skipUsesPattern.matcher(typeName).find() || !onlyUsesPattern.matcher(typeName).find(); - } - - /** - * Tests whether the class definition should not be checked because it matches the {@code - * checker.skipDefs} property. - * - * @param tree class to potentially skip - * @return true if checker should not test {@code tree} - */ - public boolean shouldSkipDefs(ClassTree tree) { - String qualifiedName = TreeUtils.typeOf(tree).toString(); - // System.out.printf("shouldSkipDefs(%s) %s%nskipDefs %s%nonlyDefs %s%nresult %s%n%n", - // tree, - // qualifiedName, - // skipDefsPattern.matcher(qualifiedName).find(), - // onlyDefsPattern.matcher(qualifiedName).find(), - // (skipDefsPattern.matcher(qualifiedName).find() - // || ! onlyDefsPattern.matcher(qualifiedName).find())); - if (skipDefsPattern == null) { - skipDefsPattern = getSkipDefsPattern(getOptions()); - } - if (onlyDefsPattern == null) { - onlyDefsPattern = getOnlyDefsPattern(getOptions()); - } - - return skipDefsPattern.matcher(qualifiedName).find() - || !onlyDefsPattern.matcher(qualifiedName).find(); - } - - /** - * Tests whether the method definition should not be checked because it matches the {@code - * checker.skipDefs} property. - * - *

          TODO: currently only uses the class definition. Refine pattern. Same for skipUses. - * - * @param cls class to potentially skip - * @param meth method to potentially skip - * @return true if checker should not test {@code meth} - */ - public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { - return shouldSkipDefs(cls); - } - - /////////////////////////////////////////////////////////////////////////// - /// Skipping files - /// - - /** - * Tests whether the enclosing file path of the passed tree matches the pattern specified in the - * {@code checker.skipFiles} property. - * - * @param tree a tree - * @return true iff the enclosing directory of the tree should be skipped - */ - public final boolean shouldSkipFiles(ClassTree tree) { - if (tree == null) { - return false; - } - TypeElement typeElement = TreeUtils.elementFromDeclaration(tree); - if (typeElement == null) { - throw new BugInCF("elementFromDeclaration(%s [%s]) => null%n", tree, tree.getClass()); - } - String sourceFilePathForElement = ElementUtils.getSourceFilePath(typeElement); - return shouldSkipFiles(sourceFilePathForElement); - } - - /** - * Tests whether the file at the file path should be not be checked because it matches the {@code - * checker.skipFiles} property. - * - * @param path the path to the file to potentially skip - * @return true iff the checker should not check the file at {@code path} - */ - private boolean shouldSkipFiles(String path) { - if (skipFilesPattern == null) { - skipFilesPattern = getSkipFilesPattern(getOptions()); - } - if (onlyFilesPattern == null) { - onlyFilesPattern = getOnlyFilesPattern(getOptions()); - } - - return skipFilesPattern.matcher(path).find() || !onlyFilesPattern.matcher(path).find(); - } - - /////////////////////////////////////////////////////////////////////////// - /// Errors other than type-checking errors - /// - - /** - * Log (that is, print) a user error. - * - * @param ce the user error to output - */ - private void logUserError(UserError ce) { - String msg = ce.getMessage(); - printMessage(msg); - } - - /** - * Log (that is, print) a type system error. - * - * @param ce the type system error to output - */ - private void logTypeSystemError(TypeSystemError ce) { - logBug( - ce, "A type system implementation is buggy. Please report the crash to the maintainer."); - } - - /** - * Log (that is, print) an internal error in the framework or a checker. - * - * @param ce the internal error to output - */ - private void logBugInCF(BugInCF ce) { - String checkerVersion; - checkerVersion = getCheckerVersion(); - String msg = "The Checker Framework crashed. Please report the crash. "; - if (checkerVersion != null) { - msg += String.format("Version: Checker Framework %s. ", checkerVersion); - } - logBug(ce, msg); - } - - /** - * Log (that is, print) an internal error in the framework or a checker. - * - * @param ce the internal error to output - * @param culprit a message to print about the cause - */ - private void logBug(Throwable ce, String culprit) { - StringJoiner msg = new StringJoiner(System.lineSeparator()); - if (ce.getCause() != null && ce.getCause() instanceof OutOfMemoryError) { - msg.add( - String.format( - "OutOfMemoryError (max memory = %d, total memory = %d, free memory =" + " %d)", - Runtime.getRuntime().maxMemory(), - Runtime.getRuntime().totalMemory(), - Runtime.getRuntime().freeMemory())); - } else { - msg.add(ce.getMessage()); - boolean noPrintErrorStack = - (processingEnv != null - && processingEnv.getOptions() != null - && processingEnv.getOptions().containsKey("noPrintErrorStack")); - - msg.add("; " + culprit); - if (noPrintErrorStack) { - msg.add( - " To see the full stack trace, don't invoke the compiler with" - + " -AnoPrintErrorStack"); - } else { - msg.add("Checker: " + this.getClass()); - if (this.visitor != null) { - msg.add("Visitor: " + this.visitor.getClass()); - } - if (this.currentRoot != null && this.currentRoot.getSourceFile() != null) { - msg.add("Compilation unit: " + this.currentRoot.getSourceFile().getName()); - } - - if (this.visitor != null) { - DiagnosticPosition pos = (DiagnosticPosition) this.visitor.lastVisited; - if (pos != null) { - DiagnosticSource source = new DiagnosticSource(this.currentRoot.getSourceFile(), null); - int linenr = source.getLineNumber(pos.getStartPosition()); - int col = source.getColumnNumber(pos.getStartPosition(), true); - String line = source.getLine(pos.getStartPosition()); - - msg.add("Last visited tree at line " + linenr + " column " + col + ":"); - msg.add(line); - } - } - - Throwable forStackTrace = ce.getCause() != null ? ce.getCause() : ce; - if (forStackTrace != null) { - msg.add( - "Exception: " + forStackTrace + "; " + UtilPlume.stackTraceToString(forStackTrace)); - boolean printClasspath = forStackTrace instanceof NoClassDefFoundError; - Throwable cause = forStackTrace.getCause(); - while (cause != null) { - msg.add("Underlying Exception: " + cause + "; " + UtilPlume.stackTraceToString(cause)); - printClasspath |= cause instanceof NoClassDefFoundError; - cause = cause.getCause(); - } - - if (printClasspath) { - msg.add("Inspect your classpath, as there was a NoClassDefFoundError."); - /* - msg.add("Classpath:"); - for (URI uri : new ClassGraph().getClasspathURIs()) { - msg.add(uri.toString()); - } - */ - } - } - } - } - - printMessage(msg.toString()); - } - - /** - * Converts a throwable to a BugInCF. - * - * @param methodName the method that caught the exception (redundant with stack trace) - * @param t the throwable to be converted to a BugInCF - * @param p what source code was being processed - * @return a BugInCF that wraps the given throwable - */ - private BugInCF wrapThrowableAsBugInCF(String methodName, Throwable t, @Nullable TreePath p) { - return new BugInCF( - methodName - + ": unexpected Throwable (" - + t.getClass().getSimpleName() - + ")" - + ((p == null) - ? "" - : " while processing " + p.getCompilationUnit().getSourceFile().getName()) - + (t.getMessage() == null ? "" : "; message: " + t.getMessage()), - t); - } - - /////////////////////////////////////////////////////////////////////////// - /// Shutdown - /// - - /** - * Return true to indicate that method {@link #shutdownHook} should be added as a shutdownHook of - * the JVM. - * - * @return true to add {@link #shutdownHook} as a shutdown hook of the JVM - */ - protected boolean shouldAddShutdownHook() { - return hasOption("resourceStats"); - } - - /** - * Method that gets called exactly once at shutdown time of the JVM. Checkers can override this - * method to customize the behavior. - * - *

          If you override this, you must also override {@link #shouldAddShutdownHook} to return true. - */ - protected void shutdownHook() { - if (hasOption("resourceStats")) { - // Check for the "resourceStats" option and don't call shouldAddShutdownHook - // to allow subclasses to override shouldXXX and shutdownHook and simply - // call the super implementations. - printStats(); - } - } - - /** Print resource usage statistics. */ - protected void printStats() { - List memoryPools = ManagementFactory.getMemoryPoolMXBeans(); - for (MemoryPoolMXBean memoryPool : memoryPools) { - System.out.println("Memory pool " + memoryPool.getName() + " statistics"); - System.out.println(" Pool type: " + memoryPool.getType()); - System.out.println(" Peak usage: " + memoryPool.getPeakUsage()); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Miscellaneous - /// - - /** - * A helper function to parse a Properties file. - * - * @param cls the class whose location is the base of the file path - * @param filePath the name/path of the file to be read - * @param permitNonExisting if true, return an empty Properties if the file does not exist or - * cannot be parsed; if false, issue an error - * @return the properties - */ - protected Properties getProperties(Class cls, String filePath, boolean permitNonExisting) { - Properties prop = new Properties(); - try (InputStream base = cls.getResourceAsStream(filePath)) { - if (base == null) { - // The property file was not found. - if (permitNonExisting) { - return prop; + } + + /** + * Should conservative defaults be used for the kind of unchecked code indicated by the + * parameter? + * + * @param kindOfCode source or bytecode + * @return whether conservative defaults should be used + */ + public boolean useConservativeDefault(String kindOfCode) { + boolean useUncheckedDefaultsForSource = false; + boolean useUncheckedDefaultsForByteCode = false; + for (String arg : this.getStringsOption("useConservativeDefaultsForUncheckedCode", ',')) { + boolean value = arg.indexOf("-") != 0; + arg = value ? arg : arg.substring(1); + if (arg.equals(kindOfCode)) { + return value; + } + } + if (kindOfCode.equals("source")) { + return useUncheckedDefaultsForSource; + } else if (kindOfCode.equals("bytecode")) { + return useUncheckedDefaultsForByteCode; + } else { + throw new UserError( + "SourceChecker: unexpected argument to useConservativeDefault: " + kindOfCode); + } + } + + /** + * Elements with a {@code @SuppressWarnings} that actually suppressed a warning for this + * checker. + */ + protected final Set elementsWithSuppressedWarnings = new HashSet<>(); + + /** + * Returns true if all the warnings pertaining to a given element should be suppressed. Returns + * true if the element is within the scope of a @SuppressWarnings annotation, one of whose + * values suppresses all the checker's warnings. + * + * @param elt the Element that might be a source of, or related to, a warning + * @param errKey the error key the checker is emitting + * @return true if no warning should be emitted for the given Element because it is contained by + * a declaration with an appropriately-valued {@code @SuppressWarnings} annotation; false + * otherwise + */ + public boolean shouldSuppressWarnings(@Nullable Element elt, String errKey) { + if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { + return true; + } + + for (Element currElt = elt; currElt != null; currElt = currElt.getEnclosingElement()) { + SuppressWarnings suppressWarningsAnno = currElt.getAnnotation(SuppressWarnings.class); + if (suppressWarningsAnno != null) { + String[] suppressWarningsStrings = suppressWarningsAnno.value(); + if (shouldSuppress(suppressWarningsStrings, errKey)) { + if (warnUnneededSuppressions) { + elementsWithSuppressedWarnings.add(currElt); + } + return true; + } + } + if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + // Return false immediately. Do NOT check for AnnotatedFor in the + // enclosing elements, because they may not have an @AnnotatedFor. + return false; + } + } + return false; + } + + /** + * Returns true if an error (whose message key is {@code messageKey}) should be suppressed. It + * is suppressed if any of the given SuppressWarnings strings suppresses it. + * + *

          A SuppressWarnings string may be of the following pattern: + * + *

            + *
          1. {@code "prefix"}, where prefix is a SuppressWarnings prefix, as specified by {@link + * #getSuppressWarningsPrefixes()}. For example, {@code "nullness"} and {@code + * "initialization"} for the Nullness Checker, {@code "regex"} for the Regex Checker. + *
          2. {@code "partial-message-key"}, where partial-message-key is a prefix or suffix of the + * message key that it may suppress. So "generic.argument" would suppress any errors whose + * message key contains "generic.argument". + *
          3. {@code "prefix:partial-message-key}, where the prefix and partial-message-key is as + * above. So "nullness:generic.argument", would suppress any errors in the Nullness + * Checker with a message key that contains "generic.argument". + *
          + * + * {@code "allcheckers"} is a prefix that suppresses a warning from any checker. {@code "all"} + * is a partial-message-key that suppresses a warning with any message key. + * + *

          If the {@code -ArequirePrefixInWarningSuppressions} command-line argument was supplied, + * then {@code "partial-message-key"} has no effect; {@code "prefix"} and {@code + * "prefix:partial-message-key"} are the only SuppressWarnings strings that have an effect. + * + * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, + * in which case this method returns false. + * @param messageKey the message key of the error the checker is emitting; a lowercase string, + * without any "checkername:" prefix + * @return true if an element of {@code suppressWarningsInEffect} suppresses the error + */ + private boolean shouldSuppress( + String @Nullable [] suppressWarningsInEffect, String messageKey) { + Set prefixes = this.getSuppressWarningsPrefixes(); + return shouldSuppress(prefixes, suppressWarningsInEffect, messageKey); + } + + /** + * Helper method for {@link #shouldSuppress(String[], String)}. + * + * @param prefixes the SuppressWarnings prefixes used by this checker + * @param suppressWarningsInEffect the SuppressWarnings strings that are in effect. May be null, + * in which case this method returns false. + * @param messageKey the message key of the error the checker is emitting; a lowercase string, + * without any "checkername:" prefix + * @return true if one of the {@code suppressWarningsInEffect} suppresses the error + */ + private boolean shouldSuppress( + Set prefixes, String @Nullable [] suppressWarningsInEffect, String messageKey) { + if (suppressWarningsInEffect == null) { + return false; + } + + for (String currentSuppressWarningsInEffect : suppressWarningsInEffect) { + int colonPos = currentSuppressWarningsInEffect.indexOf(":"); + String messageKeyInSuppressWarningsString; + if (colonPos == -1) { + // The SuppressWarnings string has no colon, so it is not of the form + // prefix:partial-message-key. + if (prefixes.contains(currentSuppressWarningsInEffect)) { + // The value in the @SuppressWarnings is exactly a prefix. + // Suppress the warning unless its message key is "unneeded.suppression". + boolean result = + !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); + return result; + } else if (requirePrefixInWarningSuppressions) { + // A prefix is required, but this SuppressWarnings string does not have a + // prefix; check the next SuppressWarnings string. + continue; + } else if (currentSuppressWarningsInEffect.equals(SUPPRESS_ALL_MESSAGE_KEY)) { + // Prefixes aren't required and the SuppressWarnings string is "all". + // Suppress the warning unless its message key is "unneeded.suppression". + boolean result = + !currentSuppressWarningsInEffect.equals(UNNEEDED_SUPPRESSION_KEY); + return result; + } + // The currentSuppressWarningsInEffect is not a prefix or a prefix:message-key, so + // it might be a message key. + messageKeyInSuppressWarningsString = currentSuppressWarningsInEffect; + } else { + // The SuppressWarnings string has a colon; that is, it has a prefix. + String currentSuppressWarningsPrefix = + currentSuppressWarningsInEffect.substring(0, colonPos); + if (!prefixes.contains(currentSuppressWarningsPrefix)) { + // The prefix of this SuppressWarnings string is a not a prefix supported by + // this checker. Proceed to the next SuppressWarnings string. + continue; + } + messageKeyInSuppressWarningsString = + currentSuppressWarningsInEffect.substring(colonPos + 1); + } + // Check if the message key in the warning suppression is part of the message key that + // the checker is emiting. + if (messageKeyMatches(messageKey, messageKeyInSuppressWarningsString)) { + return true; + } + } + + // None of the SuppressWarnings strings suppresses this error. + return false; + } + + /** + * Does the given messageKey match a messageKey that appears in a SuppressWarnings? Subclasses + * should override this method if they need additional logic to compare message keys. + * + * @param messageKey the message key of the error that is being emitted, without any "checker:" + * prefix + * @param messageKeyInSuppressWarningsString the message key in a {@code @SuppressWarnings} + * annotation + * @return true if the arguments match + */ + protected boolean messageKeyMatches( + String messageKey, String messageKeyInSuppressWarningsString) { + return messageKey.equals(messageKeyInSuppressWarningsString) + || messageKey.startsWith(messageKeyInSuppressWarningsString + ".") + || messageKey.endsWith("." + messageKeyInSuppressWarningsString) + || messageKey.contains("." + messageKeyInSuppressWarningsString + "."); + } + + /** + * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an + * upstream checker that called this one. + * + * @param elt the source code element to check, or null + * @return true if the element is annotated for this checker or an upstream checker + */ + private boolean isAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { + if (elt == null || !useConservativeDefault("source")) { + return false; + } + + AnnotatedFor anno = elt.getAnnotation(AnnotatedFor.class); + + String[] userAnnotatedFors = (anno == null ? null : anno.value()); + + if (userAnnotatedFors != null) { + List<@FullyQualifiedName String> upstreamCheckerNames = getUpstreamCheckerNames(); + + for (String userAnnotatedFor : userAnnotatedFors) { + if (CheckerMain.matchesCheckerOrSubcheckerFromList( + userAnnotatedFor, upstreamCheckerNames)) { + return true; + } + } + } + + return false; + } + + /** + * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings + * strings. + * + *

          The collection must not be empty and must not contain only {@link #SUPPRESS_ALL_PREFIX}. + * + * @return non-empty modifiable set of lower-case prefixes for SuppressWarnings strings + */ + public NavigableSet getSuppressWarningsPrefixes() { + return getStandardSuppressWarningsPrefixes(); + } + + /** + * Returns a sorted set of SuppressWarnings prefixes read from the {@link + * SuppressWarningsPrefix} meta-annotation on the checker class. Or if no {@link + * SuppressWarningsPrefix} is used, the checker name is used. {@link #SUPPRESS_ALL_PREFIX} is + * also added, at the end, unless {@link #useAllcheckersPrefix} is false. + * + * @return a sorted set of SuppressWarnings prefixes + */ + protected final NavigableSet getStandardSuppressWarningsPrefixes() { + NavigableSet prefixes = new TreeSet<>(); + if (useAllcheckersPrefix) { + prefixes.add(SUPPRESS_ALL_PREFIX); + } + SuppressWarningsPrefix prefixMetaAnno = + this.getClass().getAnnotation(SuppressWarningsPrefix.class); + if (prefixMetaAnno != null) { + for (String prefix : prefixMetaAnno.value()) { + prefixes.add(prefix); + } + return prefixes; + } + + // No @SuppressWarningsPrefixes annotation, by default infer key from class name. + String defaultPrefix = getDefaultSuppressWarningsPrefix(); + prefixes.add(defaultPrefix); + return prefixes; + } + + /** + * Returns the default SuppressWarnings prefix for this checker based on the checker name. + * + * @return the default SuppressWarnings prefix for this checker based on the checker name + */ + private String getDefaultSuppressWarningsPrefix() { + String className = this.getClass().getSimpleName(); + int indexOfChecker = className.lastIndexOf("Checker"); + if (indexOfChecker == -1) { + indexOfChecker = className.lastIndexOf("Subchecker"); + } + String result = (indexOfChecker == -1) ? className : className.substring(0, indexOfChecker); + return result.toLowerCase(Locale.ROOT); + } + + /** + * Returns the prefix that should be added when issuing an error or warning if the {@code + * -AshowPrefixInWarningMessages} command-line option was passed. + * + *

          The default implementation uses the default prefix based on the class name if that default + * prefix is contained in {@link #getSuppressWarningsPrefixes()}. Otherwise, it uses the first + * element of {@link #getSuppressWarningsPrefixes()}. + * + * @return the prefix that should be added when issuing an error or warning if the * {@code + * -AshowPrefixInWarningMessages} command-line option was passed + */ + protected String getWarningMessagePrefix() { + Collection prefixes = this.getSuppressWarningsPrefixes(); + prefixes.remove(SUPPRESS_ALL_PREFIX); + String defaultPrefix = getDefaultSuppressWarningsPrefix(); + if (prefixes.contains(defaultPrefix)) { + return defaultPrefix; + } else { + String firstKey = prefixes.iterator().next(); + return firstKey; + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Skipping uses and defs + /// + + /** + * Tests whether the class owner of the passed element is an unannotated class and matches the + * pattern specified in the {@code checker.skipUses} property. + * + * @param element an element + * @return true iff the enclosing class of element should be skipped + */ + public final boolean shouldSkipUses(@Nullable Element element) { + if (element == null) { + return false; + } + TypeElement typeElement = ElementUtils.enclosingTypeElement(element); + if (typeElement == null) { + throw new BugInCF( + "enclosingTypeElement(%s [%s]) => null%n", element, element.getClass()); + } + @SuppressWarnings("signature:assignment.type.incompatible" // TypeElement.toString(): + // @FullyQualifiedName + ) + @FullyQualifiedName String name = typeElement.toString(); + return shouldSkipUses(name); + } + + /** + * Tests whether the class owner of the passed type matches the pattern specified in the {@code + * checker.skipUses} property. In contrast to {@link #shouldSkipUses(Element)} this version can + * also be used from primitive types, which don't have an element. + * + *

          Checkers that require their annotations not to be checked on certain JDK classes may + * override this method to skip them. They shall call {@code super.shouldSkipUses(typeName)} to + * also skip the classes matching the pattern. + * + * @param typeName the fully-qualified name of a type + * @return true iff the enclosing class of element should be skipped + */ + public boolean shouldSkipUses(@FullyQualifiedName String typeName) { + // System.out.printf("shouldSkipUses(%s) %s%nskipUses %s%nonlyUses %s%nresult %s%n", + // element, + // name, + // skipUsesPattern.matcher(name).find(), + // onlyUsesPattern.matcher(name).find(), + // (skipUsesPattern.matcher(name).find() + // || ! onlyUsesPattern.matcher(name).find())); + // StackTraceElement[] stea = new Throwable().getStackTrace(); + // for (int i=0; i<3; i++) { + // System.out.println(" " + stea[i]); + // } + // System.out.println(); + if (skipUsesPattern == null) { + skipUsesPattern = getSkipUsesPattern(getOptions()); + } + if (onlyUsesPattern == null) { + onlyUsesPattern = getOnlyUsesPattern(getOptions()); + } + return skipUsesPattern.matcher(typeName).find() + || !onlyUsesPattern.matcher(typeName).find(); + } + + /** + * Tests whether the class definition should not be checked because it matches the {@code + * checker.skipDefs} property. + * + * @param tree class to potentially skip + * @return true if checker should not test {@code tree} + */ + public boolean shouldSkipDefs(ClassTree tree) { + String qualifiedName = TreeUtils.typeOf(tree).toString(); + // System.out.printf("shouldSkipDefs(%s) %s%nskipDefs %s%nonlyDefs %s%nresult %s%n%n", + // tree, + // qualifiedName, + // skipDefsPattern.matcher(qualifiedName).find(), + // onlyDefsPattern.matcher(qualifiedName).find(), + // (skipDefsPattern.matcher(qualifiedName).find() + // || ! onlyDefsPattern.matcher(qualifiedName).find())); + if (skipDefsPattern == null) { + skipDefsPattern = getSkipDefsPattern(getOptions()); + } + if (onlyDefsPattern == null) { + onlyDefsPattern = getOnlyDefsPattern(getOptions()); + } + + return skipDefsPattern.matcher(qualifiedName).find() + || !onlyDefsPattern.matcher(qualifiedName).find(); + } + + /** + * Tests whether the method definition should not be checked because it matches the {@code + * checker.skipDefs} property. + * + *

          TODO: currently only uses the class definition. Refine pattern. Same for skipUses. + * + * @param cls class to potentially skip + * @param meth method to potentially skip + * @return true if checker should not test {@code meth} + */ + public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { + return shouldSkipDefs(cls); + } + + /////////////////////////////////////////////////////////////////////////// + /// Skipping files + /// + + /** + * Tests whether the enclosing file path of the passed tree matches the pattern specified in the + * {@code checker.skipFiles} property. + * + * @param tree a tree + * @return true iff the enclosing directory of the tree should be skipped + */ + public final boolean shouldSkipFiles(ClassTree tree) { + if (tree == null) { + return false; + } + TypeElement typeElement = TreeUtils.elementFromDeclaration(tree); + if (typeElement == null) { + throw new BugInCF("elementFromDeclaration(%s [%s]) => null%n", tree, tree.getClass()); + } + String sourceFilePathForElement = ElementUtils.getSourceFilePath(typeElement); + return shouldSkipFiles(sourceFilePathForElement); + } + + /** + * Tests whether the file at the file path should be not be checked because it matches the + * {@code checker.skipFiles} property. + * + * @param path the path to the file to potentially skip + * @return true iff the checker should not check the file at {@code path} + */ + private boolean shouldSkipFiles(String path) { + if (skipFilesPattern == null) { + skipFilesPattern = getSkipFilesPattern(getOptions()); + } + if (onlyFilesPattern == null) { + onlyFilesPattern = getOnlyFilesPattern(getOptions()); + } + + return skipFilesPattern.matcher(path).find() || !onlyFilesPattern.matcher(path).find(); + } + + /////////////////////////////////////////////////////////////////////////// + /// Errors other than type-checking errors + /// + + /** + * Log (that is, print) a user error. + * + * @param ce the user error to output + */ + private void logUserError(UserError ce) { + String msg = ce.getMessage(); + printMessage(msg); + } + + /** + * Log (that is, print) a type system error. + * + * @param ce the type system error to output + */ + private void logTypeSystemError(TypeSystemError ce) { + logBug( + ce, + "A type system implementation is buggy. Please report the crash to the maintainer."); + } + + /** + * Log (that is, print) an internal error in the framework or a checker. + * + * @param ce the internal error to output + */ + private void logBugInCF(BugInCF ce) { + String checkerVersion; + checkerVersion = getCheckerVersion(); + String msg = "The Checker Framework crashed. Please report the crash. "; + if (checkerVersion != null) { + msg += String.format("Version: Checker Framework %s. ", checkerVersion); + } + logBug(ce, msg); + } + + /** + * Log (that is, print) an internal error in the framework or a checker. + * + * @param ce the internal error to output + * @param culprit a message to print about the cause + */ + private void logBug(Throwable ce, String culprit) { + StringJoiner msg = new StringJoiner(System.lineSeparator()); + if (ce.getCause() != null && ce.getCause() instanceof OutOfMemoryError) { + msg.add( + String.format( + "OutOfMemoryError (max memory = %d, total memory = %d, free memory =" + + " %d)", + Runtime.getRuntime().maxMemory(), + Runtime.getRuntime().totalMemory(), + Runtime.getRuntime().freeMemory())); } else { - throw new BugInCF("Couldn't locate properties file " + filePath); - } - } - - prop.load(base); - } catch (IOException e) { - throw new BugInCF("Couldn't parse properties file: " + filePath, e); - } - return prop; - } - - @Override - public final SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } - - /** True if the git.properties file has been printed. */ - private static boolean gitPropertiesPrinted = false; - - /** Print information about the git repository from which the Checker Framework was compiled. */ - private void printGitProperties() { - if (gitPropertiesPrinted) { - return; - } - gitPropertiesPrinted = true; - - try (InputStream in = getClass().getResourceAsStream("/git.properties"); - BufferedReader reader = - new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); ) { - String line; - while ((line = reader.readLine()) != null) { - System.out.println(line); - } - } catch (IOException e) { - System.out.println("IOException while reading git.properties: " + e.getMessage()); - } - } - - /** - * Returns the version of the Checker Framework. - * - * @return the Checker Framework version, or null if not available - */ - private @Nullable String getCheckerVersion() { - String version; - try { - Properties gitProperties = getProperties(getClass(), "/git.properties", false); - version = gitProperties.getProperty("git.build.version"); - if (version == null) { - throw new BugInCF("Could not find the version in git.properties"); - } - String branch = gitProperties.getProperty("git.branch"); - // git.dirty indicates modified tracked files and staged changes. Untracked content - // doesn't count, so not being dirty doesn't mean that exactly the printed commit is - // being run. - String dirty = gitProperties.getProperty("git.dirty"); - if (version.endsWith("-SNAPSHOT") || !branch.equals("master")) { - // Sometimes the branch is HEAD, which is not informative. - // How does that happen, and how can I fix it? - version += ", branch " + branch; - // For brevity, only date but not time of day. - version += ", " + gitProperties.getProperty("git.commit.time").substring(0, 10); - version += ", commit " + gitProperties.getProperty("git.commit.id.abbrev"); - if (dirty.equals("true")) { - version += ", dirty=true"; - } - } - } catch (Exception ex) { - // throws an exception when invoked during JUnit tests. - version = null; - } - return version; - } - - /** - * Gradle and IntelliJ wrap the processing environment to gather information about modifications - * done by annotation processor during incremental compilation. But the Checker Framework calls - * methods from javac that require the processing environment to be {@code - * com.sun.tools.javac.processing.JavacProcessingEnvironment}. They fail if given a proxy. This - * method unwraps a proxy if one is used. - * - * @param env a processing environment - * @return unwrapped environment if the argument is a proxy created by IntelliJ or Gradle; - * original value (the argument) if the argument is a javac processing environment - * @throws BugInCF if method fails to retrieve {@code - * com.sun.tools.javac.processing.JavacProcessingEnvironment} - */ - private static ProcessingEnvironment unwrapProcessingEnvironment(ProcessingEnvironment env) { - if (env.getClass().getName() - == "com.sun.tools.javac.processing.JavacProcessingEnvironment") { // interned - return env; - } - // IntelliJ >2020.3 wraps the processing environment in a dynamic proxy. - ProcessingEnvironment unwrappedIntelliJ = unwrapIntelliJ(env); - if (unwrappedIntelliJ != null) { - return unwrapProcessingEnvironment(unwrappedIntelliJ); - } - // Gradle incremental build also wraps the processing environment. - for (Class envClass = env.getClass(); - envClass != null; - envClass = envClass.getSuperclass()) { - ProcessingEnvironment unwrappedGradle = unwrapGradle(envClass, env); - if (unwrappedGradle != null) { - return unwrapProcessingEnvironment(unwrappedGradle); - } - } - throw new BugInCF("Unexpected processing environment: %s %s", env, env.getClass()); - } - - /** - * Tries to unwrap ProcessingEnvironment from proxy in IntelliJ 2020.3 or later. - * - * @param env possibly a dynamic proxy wrapping processing environment - * @return unwrapped processing environment, null if not successful - */ - private static @Nullable ProcessingEnvironment unwrapIntelliJ(ProcessingEnvironment env) { - if (!Proxy.isProxyClass(env.getClass())) { - return null; - } - InvocationHandler handler = Proxy.getInvocationHandler(env); - try { - Field field = handler.getClass().getDeclaredField("val$delegateTo"); - field.setAccessible(true); - Object o = field.get(handler); - if (o instanceof ProcessingEnvironment) { - return (ProcessingEnvironment) o; - } - return null; - } catch (NoSuchFieldException | IllegalAccessException e) { - return null; - } - } - - /** - * Tries to unwrap processing environment in Gradle incremental processing. Inspired by project - * Lombok. - * - * @param delegateClass a class in which to find a {@code delegate} field - * @param env a processing environment wrapper - * @return unwrapped processing environment, null if not successful - */ - private static @Nullable ProcessingEnvironment unwrapGradle( - Class delegateClass, ProcessingEnvironment env) { - try { - Field field = delegateClass.getDeclaredField("delegate"); - field.setAccessible(true); - Object o = field.get(env); - if (o instanceof ProcessingEnvironment) { - return (ProcessingEnvironment) o; - } - return null; - } catch (NoSuchFieldException | IllegalAccessException e) { - return null; - } - } - - /** - * Return the path to the current compilation unit. - * - * @return path to the current compilation unit - */ - public TreePath getPathToCompilationUnit() { - return getTreePathCacher().getPath(currentRoot, currentRoot); - } + msg.add(ce.getMessage()); + boolean noPrintErrorStack = + (processingEnv != null + && processingEnv.getOptions() != null + && processingEnv.getOptions().containsKey("noPrintErrorStack")); + + msg.add("; " + culprit); + if (noPrintErrorStack) { + msg.add( + " To see the full stack trace, don't invoke the compiler with" + + " -AnoPrintErrorStack"); + } else { + msg.add("Checker: " + this.getClass()); + if (this.visitor != null) { + msg.add("Visitor: " + this.visitor.getClass()); + } + if (this.currentRoot != null && this.currentRoot.getSourceFile() != null) { + msg.add("Compilation unit: " + this.currentRoot.getSourceFile().getName()); + } + + if (this.visitor != null) { + DiagnosticPosition pos = (DiagnosticPosition) this.visitor.lastVisited; + if (pos != null) { + DiagnosticSource source = + new DiagnosticSource(this.currentRoot.getSourceFile(), null); + int linenr = source.getLineNumber(pos.getStartPosition()); + int col = source.getColumnNumber(pos.getStartPosition(), true); + String line = source.getLine(pos.getStartPosition()); + + msg.add("Last visited tree at line " + linenr + " column " + col + ":"); + msg.add(line); + } + } + + Throwable forStackTrace = ce.getCause() != null ? ce.getCause() : ce; + if (forStackTrace != null) { + msg.add( + "Exception: " + + forStackTrace + + "; " + + UtilPlume.stackTraceToString(forStackTrace)); + boolean printClasspath = forStackTrace instanceof NoClassDefFoundError; + Throwable cause = forStackTrace.getCause(); + while (cause != null) { + msg.add( + "Underlying Exception: " + + cause + + "; " + + UtilPlume.stackTraceToString(cause)); + printClasspath |= cause instanceof NoClassDefFoundError; + cause = cause.getCause(); + } + + if (printClasspath) { + msg.add("Inspect your classpath, as there was a NoClassDefFoundError."); + /* + msg.add("Classpath:"); + for (URI uri : new ClassGraph().getClasspathURIs()) { + msg.add(uri.toString()); + } + */ + } + } + } + } + + printMessage(msg.toString()); + } + + /** + * Converts a throwable to a BugInCF. + * + * @param methodName the method that caught the exception (redundant with stack trace) + * @param t the throwable to be converted to a BugInCF + * @param p what source code was being processed + * @return a BugInCF that wraps the given throwable + */ + private BugInCF wrapThrowableAsBugInCF(String methodName, Throwable t, @Nullable TreePath p) { + return new BugInCF( + methodName + + ": unexpected Throwable (" + + t.getClass().getSimpleName() + + ")" + + ((p == null) + ? "" + : " while processing " + + p.getCompilationUnit().getSourceFile().getName()) + + (t.getMessage() == null ? "" : "; message: " + t.getMessage()), + t); + } + + /////////////////////////////////////////////////////////////////////////// + /// Shutdown + /// + + /** + * Return true to indicate that method {@link #shutdownHook} should be added as a shutdownHook + * of the JVM. + * + * @return true to add {@link #shutdownHook} as a shutdown hook of the JVM + */ + protected boolean shouldAddShutdownHook() { + return hasOption("resourceStats"); + } + + /** + * Method that gets called exactly once at shutdown time of the JVM. Checkers can override this + * method to customize the behavior. + * + *

          If you override this, you must also override {@link #shouldAddShutdownHook} to return + * true. + */ + protected void shutdownHook() { + if (hasOption("resourceStats")) { + // Check for the "resourceStats" option and don't call shouldAddShutdownHook + // to allow subclasses to override shouldXXX and shutdownHook and simply + // call the super implementations. + printStats(); + } + } + + /** Print resource usage statistics. */ + protected void printStats() { + List memoryPools = ManagementFactory.getMemoryPoolMXBeans(); + for (MemoryPoolMXBean memoryPool : memoryPools) { + System.out.println("Memory pool " + memoryPool.getName() + " statistics"); + System.out.println(" Pool type: " + memoryPool.getType()); + System.out.println(" Peak usage: " + memoryPool.getPeakUsage()); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Miscellaneous + /// + + /** + * A helper function to parse a Properties file. + * + * @param cls the class whose location is the base of the file path + * @param filePath the name/path of the file to be read + * @param permitNonExisting if true, return an empty Properties if the file does not exist or + * cannot be parsed; if false, issue an error + * @return the properties + */ + protected Properties getProperties(Class cls, String filePath, boolean permitNonExisting) { + Properties prop = new Properties(); + try (InputStream base = cls.getResourceAsStream(filePath)) { + if (base == null) { + // The property file was not found. + if (permitNonExisting) { + return prop; + } else { + throw new BugInCF("Couldn't locate properties file " + filePath); + } + } + + prop.load(base); + } catch (IOException e) { + throw new BugInCF("Couldn't parse properties file: " + filePath, e); + } + return prop; + } + + @Override + public final SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + /** True if the git.properties file has been printed. */ + private static boolean gitPropertiesPrinted = false; + + /** Print information about the git repository from which the Checker Framework was compiled. */ + private void printGitProperties() { + if (gitPropertiesPrinted) { + return; + } + gitPropertiesPrinted = true; + + try (InputStream in = getClass().getResourceAsStream("/git.properties"); + BufferedReader reader = + new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); ) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } catch (IOException e) { + System.out.println("IOException while reading git.properties: " + e.getMessage()); + } + } + + /** + * Returns the version of the Checker Framework. + * + * @return the Checker Framework version, or null if not available + */ + private @Nullable String getCheckerVersion() { + String version; + try { + Properties gitProperties = getProperties(getClass(), "/git.properties", false); + version = gitProperties.getProperty("git.build.version"); + if (version == null) { + throw new BugInCF("Could not find the version in git.properties"); + } + String branch = gitProperties.getProperty("git.branch"); + // git.dirty indicates modified tracked files and staged changes. Untracked content + // doesn't count, so not being dirty doesn't mean that exactly the printed commit is + // being run. + String dirty = gitProperties.getProperty("git.dirty"); + if (version.endsWith("-SNAPSHOT") || !branch.equals("master")) { + // Sometimes the branch is HEAD, which is not informative. + // How does that happen, and how can I fix it? + version += ", branch " + branch; + // For brevity, only date but not time of day. + version += ", " + gitProperties.getProperty("git.commit.time").substring(0, 10); + version += ", commit " + gitProperties.getProperty("git.commit.id.abbrev"); + if (dirty.equals("true")) { + version += ", dirty=true"; + } + } + } catch (Exception ex) { + // throws an exception when invoked during JUnit tests. + version = null; + } + return version; + } + + /** + * Gradle and IntelliJ wrap the processing environment to gather information about modifications + * done by annotation processor during incremental compilation. But the Checker Framework calls + * methods from javac that require the processing environment to be {@code + * com.sun.tools.javac.processing.JavacProcessingEnvironment}. They fail if given a proxy. This + * method unwraps a proxy if one is used. + * + * @param env a processing environment + * @return unwrapped environment if the argument is a proxy created by IntelliJ or Gradle; + * original value (the argument) if the argument is a javac processing environment + * @throws BugInCF if method fails to retrieve {@code + * com.sun.tools.javac.processing.JavacProcessingEnvironment} + */ + private static ProcessingEnvironment unwrapProcessingEnvironment(ProcessingEnvironment env) { + if (env.getClass().getName() + == "com.sun.tools.javac.processing.JavacProcessingEnvironment") { // interned + return env; + } + // IntelliJ >2020.3 wraps the processing environment in a dynamic proxy. + ProcessingEnvironment unwrappedIntelliJ = unwrapIntelliJ(env); + if (unwrappedIntelliJ != null) { + return unwrapProcessingEnvironment(unwrappedIntelliJ); + } + // Gradle incremental build also wraps the processing environment. + for (Class envClass = env.getClass(); + envClass != null; + envClass = envClass.getSuperclass()) { + ProcessingEnvironment unwrappedGradle = unwrapGradle(envClass, env); + if (unwrappedGradle != null) { + return unwrapProcessingEnvironment(unwrappedGradle); + } + } + throw new BugInCF("Unexpected processing environment: %s %s", env, env.getClass()); + } + + /** + * Tries to unwrap ProcessingEnvironment from proxy in IntelliJ 2020.3 or later. + * + * @param env possibly a dynamic proxy wrapping processing environment + * @return unwrapped processing environment, null if not successful + */ + private static @Nullable ProcessingEnvironment unwrapIntelliJ(ProcessingEnvironment env) { + if (!Proxy.isProxyClass(env.getClass())) { + return null; + } + InvocationHandler handler = Proxy.getInvocationHandler(env); + try { + Field field = handler.getClass().getDeclaredField("val$delegateTo"); + field.setAccessible(true); + Object o = field.get(handler); + if (o instanceof ProcessingEnvironment) { + return (ProcessingEnvironment) o; + } + return null; + } catch (NoSuchFieldException | IllegalAccessException e) { + return null; + } + } + + /** + * Tries to unwrap processing environment in Gradle incremental processing. Inspired by project + * Lombok. + * + * @param delegateClass a class in which to find a {@code delegate} field + * @param env a processing environment wrapper + * @return unwrapped processing environment, null if not successful + */ + private static @Nullable ProcessingEnvironment unwrapGradle( + Class delegateClass, ProcessingEnvironment env) { + try { + Field field = delegateClass.getDeclaredField("delegate"); + field.setAccessible(true); + Object o = field.get(env); + if (o instanceof ProcessingEnvironment) { + return (ProcessingEnvironment) o; + } + return null; + } catch (NoSuchFieldException | IllegalAccessException e) { + return null; + } + } + + /** + * Return the path to the current compilation unit. + * + * @return path to the current compilation unit + */ + public TreePath getPathToCompilationUnit() { + return getTreePathCacher().getPath(currentRoot, currentRoot); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java b/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java index 6d3e1740c3d..8320d7b22b4 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceVisitor.java @@ -8,14 +8,17 @@ import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; + +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; + import java.util.ArrayList; import java.util.List; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; /** * An AST visitor that provides a variety of compiler utilities and interfaces to facilitate @@ -23,105 +26,105 @@ */ public abstract class SourceVisitor extends TreePathScanner { - /** The {@link Trees} instance to use for scanning. */ - protected final Trees trees; - - /** The {@link Elements} helper to use when scanning. */ - protected final Elements elements; - - /** The {@link Types} helper to use when scanning. */ - protected final Types types; - - /** - * The root of the AST that this {@link SourceVisitor} will scan. - * - *

          Is set by {@link #setRoot}. - */ - protected CompilationUnitTree root; - - /** The trees that are annotated with {@code @SuppressWarnings}. */ - public final List treesWithSuppressWarnings; - - /** Whether or not a warning should be issued for unneeded warning suppressions. */ - private final boolean warnUnneededSuppressions; - - /** - * Creates a {@link SourceVisitor} to use for scanning a source tree. - * - * @param checker the checker to invoke on the input source tree - */ - protected SourceVisitor(SourceChecker checker) { - // Use the checker's processing environment to get the helpers we need. - ProcessingEnvironment env = checker.getProcessingEnvironment(); - - this.trees = Trees.instance(env); - this.elements = env.getElementUtils(); - this.types = env.getTypeUtils(); - - this.treesWithSuppressWarnings = new ArrayList<>(); - this.warnUnneededSuppressions = checker.hasOption("warnUnneededSuppressions"); - } - - /** - * Set the CompilationUnitTree to be used during any visits. For any later calls of {@code - * com.sun.source.util.TreePathScanner.scan(TreePath, P)}, the CompilationUnitTree of the TreePath - * has to be equal to {@code root}. - */ - public void setRoot(CompilationUnitTree root) { - this.root = root; - } - - /** - * Store the last Tree visited by the SourceVisitor. This is necessary because the finally blocks - * in {@link com.sun.source.util.TreePathScanner#scan(TreePath, Object)} and {@link - * com.sun.source.util.TreePathScanner#scan(Tree, Object)} set the visited Path to null. This - * field is used to report a rough location for the error in {@link - * org.checkerframework.framework.source.SourceChecker#logBugInCF(BugInCF)}. - */ - /*package-private*/ Tree lastVisited; - - /** Entry point for a type processor: the TreePath leaf is a top-level type tree within root. */ - public void visit(TreePath path) { - lastVisited = path.getLeaf(); - this.scan(path, null); - } - - @Override - public R scan(Tree tree, P p) { - lastVisited = tree; - return super.scan(tree, p); - } - - @Override - public R visitClass(ClassTree classTree, P p) { - storeSuppressWarningsAnno(classTree); - return super.visitClass(classTree, p); - } - - @Override - public R visitVariable(VariableTree variableTree, P p) { - storeSuppressWarningsAnno(variableTree); - return super.visitVariable(variableTree, p); - } - - @Override - public R visitMethod(MethodTree tree, P p) { - storeSuppressWarningsAnno(tree); - return super.visitMethod(tree, p); - } - - /** - * If {@code tree} has a {@code @SuppressWarnings} add it to treesWithSuppressWarnings. - * - * @param tree a declaration on which a {@code @SuppressWarnings} annotation may be placed - */ - private void storeSuppressWarningsAnno(Tree tree) { - if (!warnUnneededSuppressions) { - return; + /** The {@link Trees} instance to use for scanning. */ + protected final Trees trees; + + /** The {@link Elements} helper to use when scanning. */ + protected final Elements elements; + + /** The {@link Types} helper to use when scanning. */ + protected final Types types; + + /** + * The root of the AST that this {@link SourceVisitor} will scan. + * + *

          Is set by {@link #setRoot}. + */ + protected CompilationUnitTree root; + + /** The trees that are annotated with {@code @SuppressWarnings}. */ + public final List treesWithSuppressWarnings; + + /** Whether or not a warning should be issued for unneeded warning suppressions. */ + private final boolean warnUnneededSuppressions; + + /** + * Creates a {@link SourceVisitor} to use for scanning a source tree. + * + * @param checker the checker to invoke on the input source tree + */ + protected SourceVisitor(SourceChecker checker) { + // Use the checker's processing environment to get the helpers we need. + ProcessingEnvironment env = checker.getProcessingEnvironment(); + + this.trees = Trees.instance(env); + this.elements = env.getElementUtils(); + this.types = env.getTypeUtils(); + + this.treesWithSuppressWarnings = new ArrayList<>(); + this.warnUnneededSuppressions = checker.hasOption("warnUnneededSuppressions"); + } + + /** + * Set the CompilationUnitTree to be used during any visits. For any later calls of {@code + * com.sun.source.util.TreePathScanner.scan(TreePath, P)}, the CompilationUnitTree of the + * TreePath has to be equal to {@code root}. + */ + public void setRoot(CompilationUnitTree root) { + this.root = root; + } + + /** + * Store the last Tree visited by the SourceVisitor. This is necessary because the finally + * blocks in {@link com.sun.source.util.TreePathScanner#scan(TreePath, Object)} and {@link + * com.sun.source.util.TreePathScanner#scan(Tree, Object)} set the visited Path to null. This + * field is used to report a rough location for the error in {@link + * org.checkerframework.framework.source.SourceChecker#logBugInCF(BugInCF)}. + */ + /*package-private*/ Tree lastVisited; + + /** Entry point for a type processor: the TreePath leaf is a top-level type tree within root. */ + public void visit(TreePath path) { + lastVisited = path.getLeaf(); + this.scan(path, null); + } + + @Override + public R scan(Tree tree, P p) { + lastVisited = tree; + return super.scan(tree, p); } - Element elt = TreeUtils.elementFromTree(tree); - if (elt.getAnnotation(SuppressWarnings.class) != null) { - treesWithSuppressWarnings.add(tree); + + @Override + public R visitClass(ClassTree classTree, P p) { + storeSuppressWarningsAnno(classTree); + return super.visitClass(classTree, p); + } + + @Override + public R visitVariable(VariableTree variableTree, P p) { + storeSuppressWarningsAnno(variableTree); + return super.visitVariable(variableTree, p); + } + + @Override + public R visitMethod(MethodTree tree, P p) { + storeSuppressWarningsAnno(tree); + return super.visitMethod(tree, p); + } + + /** + * If {@code tree} has a {@code @SuppressWarnings} add it to treesWithSuppressWarnings. + * + * @param tree a declaration on which a {@code @SuppressWarnings} annotation may be placed + */ + private void storeSuppressWarningsAnno(Tree tree) { + if (!warnUnneededSuppressions) { + return; + } + Element elt = TreeUtils.elementFromTree(tree); + if (elt.getAnnotation(SuppressWarnings.class) != null) { + treesWithSuppressWarnings.add(tree); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java b/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java index 238b7c6a151..af2e3da8726 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SupportedLintOptions.java @@ -28,5 +28,5 @@ @Target(ElementType.TYPE) @Inherited public @interface SupportedLintOptions { - String[] value(); + String[] value(); } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java b/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java index 2353aeaad5c..d8a8edc2b91 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SupportedOptions.java @@ -23,5 +23,5 @@ @Target(ElementType.TYPE) @Inherited public @interface SupportedOptions { - String[] value(); + String[] value(); } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java index 18b2805fe8f..51604b60911 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SuppressWarningsPrefix.java @@ -30,12 +30,12 @@ // of prefix. public @interface SuppressWarningsPrefix { - /** - * Returns array of strings, any one of which causes this checker to suppress a warning when - * passed as the argument of {@literal @}{@link SuppressWarnings}. - * - * @return array of strings, any one of which causes this checker to suppress a warning when - * passed as the argument of {@literal @}{@link SuppressWarnings} - */ - String[] value(); + /** + * Returns array of strings, any one of which causes this checker to suppress a warning when + * passed as the argument of {@literal @}{@link SuppressWarnings}. + * + * @return array of strings, any one of which causes this checker to suppress a warning when + * passed as the argument of {@literal @}{@link SuppressWarnings} + */ + String[] value(); } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java index 19c56e7ed33..df2bba4ea38 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java @@ -1,21 +1,5 @@ package org.checkerframework.framework.stub; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.LineNumberReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import org.checkerframework.afu.scenelib.Annotation; import org.checkerframework.afu.scenelib.Annotations; import org.checkerframework.afu.scenelib.el.ABlock; @@ -39,228 +23,252 @@ import org.checkerframework.checker.signature.qual.BinaryName; import org.plumelib.util.ArraySet; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + /** * Utility that generates {@code @AnnotatedFor} class annotations. The {@link #main} method acts as * a filter: it reads a JAIF from standard input and writes an augmented JAIF to standard output. */ public class AddAnnotatedFor { - /** Definition of {@code @AnnotatedFor} annotation. */ - private static final AnnotationDef adAnnotatedFor; + /** Definition of {@code @AnnotatedFor} annotation. */ + private static final AnnotationDef adAnnotatedFor; - static { - Class annotatedFor = org.checkerframework.framework.qual.AnnotatedFor.class; - Set annotatedForMetaAnnotations = new HashSet<>(2); - annotatedForMetaAnnotations.add(Annotations.aRetentionSource); - annotatedForMetaAnnotations.add( - Annotations.createValueAnnotation( - Annotations.adTarget, Arrays.asList("TYPE", "METHOD", "CONSTRUCTOR", "PACKAGE"))); - @SuppressWarnings( - "signature") // TODO bug: AnnotationDef requires @BinaryName, gets CanonicalName - @BinaryName String name = annotatedFor.getCanonicalName(); - adAnnotatedFor = - new AnnotationDef( - name, - annotatedForMetaAnnotations, - Collections.singletonMap("value", new ArrayAFT(BasicAFT.forType(String.class))), - "AddAnnotatedFor."); - } - - /** Do not instantiate. */ - private AddAnnotatedFor() { - throw new Error("Do not instantiate"); - } - - /** - * Reads JAIF from the file indicated by the first element, or standard input if the argument - * array is empty; inserts any appropriate {@code @AnnotatedFor} annotations, based on the - * annotations defined in the input JAIF; and writes the augmented JAIF to standard output. - * - * @param args one jaif file, or empty to read from standard input - * @throws IOException if there is trouble reading or writing a file - * @throws DefException if two definitions cannot be unified - * @throws ParseException if the file is malformed - */ - public static void main(String[] args) throws IOException, DefException, ParseException { - if (args.length > 1) { - System.err.println("Supply 0 or 1 command-line arguments."); - System.exit(1); - } - AScene scene = new AScene(); - boolean useFile = args.length == 1; - String filename = useFile ? args[0] : "System.in"; - try (Reader r = - useFile - ? Files.newBufferedReader(Paths.get(filename), StandardCharsets.UTF_8) - : new InputStreamReader(System.in, StandardCharsets.UTF_8)) { - IndexFileParser.parse(new LineNumberReader(r), filename, scene); + static { + Class annotatedFor = org.checkerframework.framework.qual.AnnotatedFor.class; + Set annotatedForMetaAnnotations = new HashSet<>(2); + annotatedForMetaAnnotations.add(Annotations.aRetentionSource); + annotatedForMetaAnnotations.add( + Annotations.createValueAnnotation( + Annotations.adTarget, + Arrays.asList("TYPE", "METHOD", "CONSTRUCTOR", "PACKAGE"))); + @SuppressWarnings( + "signature") // TODO bug: AnnotationDef requires @BinaryName, gets CanonicalName + @BinaryName String name = annotatedFor.getCanonicalName(); + adAnnotatedFor = + new AnnotationDef( + name, + annotatedForMetaAnnotations, + Collections.singletonMap( + "value", new ArrayAFT(BasicAFT.forType(String.class))), + "AddAnnotatedFor."); } - scene.prune(); - addAnnotatedFor(scene); - IndexFileWriter.write( - scene, - new PrintWriter( - new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8)), true)); - } - /** - * Add {@code @AnnotatedFor} annotations to each class in the given scene. - * - * @param scene an {@code @AnnotatedFor} annotation is added to each class in this scene - */ - public static void addAnnotatedFor(AScene scene) { - for (AClass clazz : new HashSet<>(scene.classes.values())) { - Set annotatedFor = new ArraySet<>(2); // usually few @AnnotatedFor are applicable - clazz.accept(annotatedForVisitor, annotatedFor); - if (!annotatedFor.isEmpty()) { - // Set eliminates duplicates, but it must be converted to List; for whatever reason, - // IndexFileWriter recognizes array arguments only in List form. - List annotatedForList = new ArrayList<>(annotatedFor); - clazz.tlAnnotationsHere.add( - new Annotation(adAnnotatedFor, Annotations.valueFieldOnly(annotatedForList))); - } + /** Do not instantiate. */ + private AddAnnotatedFor() { + throw new Error("Do not instantiate"); } - } - /** - * This visitor collects the names of all the type systems, one of whose annotations is written. - * These need to be the arguments to an {@code AnnotatedFor} annotation on the class, so that all - * of the given type systems are run. - */ - private static final ElementVisitor> annotatedForVisitor = - new ElementVisitor>() { - @Override - public Void visitAnnotationDef(AnnotationDef el, Set annotatedFor) { - return null; + /** + * Reads JAIF from the file indicated by the first element, or standard input if the argument + * array is empty; inserts any appropriate {@code @AnnotatedFor} annotations, based on the + * annotations defined in the input JAIF; and writes the augmented JAIF to standard output. + * + * @param args one jaif file, or empty to read from standard input + * @throws IOException if there is trouble reading or writing a file + * @throws DefException if two definitions cannot be unified + * @throws ParseException if the file is malformed + */ + public static void main(String[] args) throws IOException, DefException, ParseException { + if (args.length > 1) { + System.err.println("Supply 0 or 1 command-line arguments."); + System.exit(1); } - - @Override - public Void visitBlock(ABlock el, Set annotatedFor) { - for (AField e : el.locals.values()) { - e.accept(this, annotatedFor); - } - return visitExpression(el, annotatedFor); + AScene scene = new AScene(); + boolean useFile = args.length == 1; + String filename = useFile ? args[0] : "System.in"; + try (Reader r = + useFile + ? Files.newBufferedReader(Paths.get(filename), StandardCharsets.UTF_8) + : new InputStreamReader(System.in, StandardCharsets.UTF_8)) { + IndexFileParser.parse(new LineNumberReader(r), filename, scene); } + scene.prune(); + addAnnotatedFor(scene); + IndexFileWriter.write( + scene, + new PrintWriter( + new BufferedWriter( + new OutputStreamWriter(System.out, StandardCharsets.UTF_8)), + true)); + } - @Override - public Void visitClass(AClass el, Set annotatedFor) { - for (ATypeElement e : el.bounds.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.extendsImplements.values()) { - e.accept(this, annotatedFor); - } - for (AExpression e : el.fieldInits.values()) { - e.accept(this, annotatedFor); - } - for (AField e : el.fields.values()) { - e.accept(this, annotatedFor); - } - for (ABlock e : el.instanceInits.values()) { - e.accept(this, annotatedFor); - } - for (AMethod e : el.methods.values()) { - e.accept(this, annotatedFor); - } - for (ABlock e : el.staticInits.values()) { - e.accept(this, annotatedFor); - } - return visitDeclaration(el, annotatedFor); + /** + * Add {@code @AnnotatedFor} annotations to each class in the given scene. + * + * @param scene an {@code @AnnotatedFor} annotation is added to each class in this scene + */ + public static void addAnnotatedFor(AScene scene) { + for (AClass clazz : new HashSet<>(scene.classes.values())) { + Set annotatedFor = + new ArraySet<>(2); // usually few @AnnotatedFor are applicable + clazz.accept(annotatedForVisitor, annotatedFor); + if (!annotatedFor.isEmpty()) { + // Set eliminates duplicates, but it must be converted to List; for whatever reason, + // IndexFileWriter recognizes array arguments only in List form. + List annotatedForList = new ArrayList<>(annotatedFor); + clazz.tlAnnotationsHere.add( + new Annotation( + adAnnotatedFor, Annotations.valueFieldOnly(annotatedForList))); + } } + } - @Override - public Void visitDeclaration(ADeclaration el, Set annotatedFor) { - for (ATypeElement e : el.insertAnnotations.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElementWithType e : el.insertTypecasts.values()) { - e.accept(this, annotatedFor); - } - return visitElement(el, annotatedFor); - } + /** + * This visitor collects the names of all the type systems, one of whose annotations is written. + * These need to be the arguments to an {@code AnnotatedFor} annotation on the class, so that + * all of the given type systems are run. + */ + private static final ElementVisitor> annotatedForVisitor = + new ElementVisitor>() { + @Override + public Void visitAnnotationDef(AnnotationDef el, Set annotatedFor) { + return null; + } - @Override - public Void visitExpression(AExpression el, Set annotatedFor) { - for (ATypeElement e : el.calls.values()) { - e.accept(this, annotatedFor); - } - for (AMethod e : el.funs.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.instanceofs.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.news.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.refs.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.typecasts.values()) { - e.accept(this, annotatedFor); - } - return visitElement(el, annotatedFor); - } + @Override + public Void visitBlock(ABlock el, Set annotatedFor) { + for (AField e : el.locals.values()) { + e.accept(this, annotatedFor); + } + return visitExpression(el, annotatedFor); + } - @Override - public Void visitField(AField el, Set annotatedFor) { - if (el.init != null) { - el.init.accept(this, annotatedFor); - } - return visitDeclaration(el, annotatedFor); - } + @Override + public Void visitClass(AClass el, Set annotatedFor) { + for (ATypeElement e : el.bounds.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.extendsImplements.values()) { + e.accept(this, annotatedFor); + } + for (AExpression e : el.fieldInits.values()) { + e.accept(this, annotatedFor); + } + for (AField e : el.fields.values()) { + e.accept(this, annotatedFor); + } + for (ABlock e : el.instanceInits.values()) { + e.accept(this, annotatedFor); + } + for (AMethod e : el.methods.values()) { + e.accept(this, annotatedFor); + } + for (ABlock e : el.staticInits.values()) { + e.accept(this, annotatedFor); + } + return visitDeclaration(el, annotatedFor); + } - @Override - public Void visitMethod(AMethod el, Set annotatedFor) { - if (el.body != null) { - el.body.accept(this, annotatedFor); - } - if (el.receiver != null) { - el.receiver.accept(this, annotatedFor); - } - if (el.returnType != null) { - el.returnType.accept(this, annotatedFor); - } - for (ATypeElement e : el.bounds.values()) { - e.accept(this, annotatedFor); - } - for (AField e : el.parameters.values()) { - e.accept(this, annotatedFor); - } - for (ATypeElement e : el.throwsException.values()) { - e.accept(this, annotatedFor); - } - return visitDeclaration(el, annotatedFor); - } + @Override + public Void visitDeclaration(ADeclaration el, Set annotatedFor) { + for (ATypeElement e : el.insertAnnotations.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElementWithType e : el.insertTypecasts.values()) { + e.accept(this, annotatedFor); + } + return visitElement(el, annotatedFor); + } - @Override - public Void visitTypeElement(ATypeElement el, Set annotatedFor) { - for (ATypeElement e : el.innerTypes.values()) { - e.accept(this, annotatedFor); - } - return visitElement(el, annotatedFor); - } + @Override + public Void visitExpression(AExpression el, Set annotatedFor) { + for (ATypeElement e : el.calls.values()) { + e.accept(this, annotatedFor); + } + for (AMethod e : el.funs.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.instanceofs.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.news.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.refs.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.typecasts.values()) { + e.accept(this, annotatedFor); + } + return visitElement(el, annotatedFor); + } - @Override - public Void visitTypeElementWithType(ATypeElementWithType el, Set annotatedFor) { - return visitTypeElement(el, annotatedFor); - } + @Override + public Void visitField(AField el, Set annotatedFor) { + if (el.init != null) { + el.init.accept(this, annotatedFor); + } + return visitDeclaration(el, annotatedFor); + } - @Override - public Void visitElement(AElement el, Set annotatedFor) { - for (Annotation a : el.tlAnnotationsHere) { - String s = a.def().name; - int j = s.indexOf(".qual."); - if (j > 0) { - int i = s.lastIndexOf('.', j - 1); - if (i > 0 && j - i > 1) { - annotatedFor.add(s.substring(i + 1, j)); - } - } - } - if (el.type != null) { - el.type.accept(this, annotatedFor); - } - return null; - } - }; + @Override + public Void visitMethod(AMethod el, Set annotatedFor) { + if (el.body != null) { + el.body.accept(this, annotatedFor); + } + if (el.receiver != null) { + el.receiver.accept(this, annotatedFor); + } + if (el.returnType != null) { + el.returnType.accept(this, annotatedFor); + } + for (ATypeElement e : el.bounds.values()) { + e.accept(this, annotatedFor); + } + for (AField e : el.parameters.values()) { + e.accept(this, annotatedFor); + } + for (ATypeElement e : el.throwsException.values()) { + e.accept(this, annotatedFor); + } + return visitDeclaration(el, annotatedFor); + } + + @Override + public Void visitTypeElement(ATypeElement el, Set annotatedFor) { + for (ATypeElement e : el.innerTypes.values()) { + e.accept(this, annotatedFor); + } + return visitElement(el, annotatedFor); + } + + @Override + public Void visitTypeElementWithType( + ATypeElementWithType el, Set annotatedFor) { + return visitTypeElement(el, annotatedFor); + } + + @Override + public Void visitElement(AElement el, Set annotatedFor) { + for (Annotation a : el.tlAnnotationsHere) { + String s = a.def().name; + int j = s.indexOf(".qual."); + if (j > 0) { + int i = s.lastIndexOf('.', j - 1); + if (i > 0 && j - i > 1) { + annotatedFor.add(s.substring(i + 1, j)); + } + } + } + if (el.type != null) { + el.type.accept(this, annotatedFor); + } + return null; + } + }; } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java index 4d318840afa..9dacf299664 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java @@ -1,6 +1,27 @@ package org.checkerframework.framework.stub; import com.sun.source.tree.CompilationUnitTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.StubFiles; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.stub.AnnotationFileParser.AnnotationFileAnnotations; +import org.checkerframework.framework.stub.AnnotationFileParser.RecordComponentStub; +import org.checkerframework.framework.stub.AnnotationFileUtil.AnnotationFileType; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.IPair; +import org.plumelib.util.SystemPlume; + import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -27,6 +48,7 @@ import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.stream.Stream; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -35,25 +57,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import javax.tools.Diagnostic; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.qual.StubFiles; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.stub.AnnotationFileParser.AnnotationFileAnnotations; -import org.checkerframework.framework.stub.AnnotationFileParser.RecordComponentStub; -import org.checkerframework.framework.stub.AnnotationFileUtil.AnnotationFileType; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.IPair; -import org.plumelib.util.SystemPlume; // import io.github.classgraph.ClassGraph; @@ -62,982 +65,1015 @@ * using an ajava file, only holds information on public elements as with stub files. */ public class AnnotationFileElementTypes { - /** Annotations from annotation files (but not from annotated JDK files). */ - private final AnnotationFileAnnotations annotationFileAnnos; - - /** The number of ongoing parsing tasks. */ - private int parsingCount; - - /** AnnotatedTypeFactory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * Mapping from fully-qualified class name to corresponding JDK stub file from the file system - * that have not yet been read. When a file is read, its mapping is removed from this map. - * - *

          By contrast, {@link #remainingJdkStubFilesJar} contains JDK stub files from checker.jar. - */ - private final Map remainingJdkStubFiles = new HashMap<>(); - - /** - * Mapping from fully-qualified class name to corresponding JDK stub files from checker.jar that - * have not yet been read. When a file is read, its mapping is removed from this map. - * - *

          By contrast, {@link #remainingJdkStubFiles} contains JDK stub files from the file system. - */ - private final Map remainingJdkStubFilesJar = new HashMap<>(); - - /** Which version number of the annotated JDK should be used? */ - private final String annotatedJdkVersion; - - /** Should the JDK be parsed? */ - private final boolean shouldParseJdk; - - /** Parse all JDK files at startup rather than as needed. */ - private final boolean parseAllJdkFiles; - - /** True if -ApermitMissingJdk was passed on the command line. */ - private final boolean permitMissingJdk; - - /** True if -Aignorejdkastub was passed on the command line. */ - private final boolean ignorejdkastub; - - /** True if -AstubDebug was passed on the command line. */ - private final boolean stubDebug; - - /** - * Stores the fully qualified name of top-level classes (from any type of stub file) that are - * currently being parsed. This can stop recursively parsing an annotated JDK class that is - * currently being processed, which prevents conflicts of definition and infinite loops. - */ - private final Set processingClasses = new LinkedHashSet<>(); - - /** - * Creates an empty annotation source. - * - * @param atypeFactory a type factory - */ - public AnnotationFileElementTypes(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.annotationFileAnnos = new AnnotationFileAnnotations(); - this.parsingCount = 0; - String release = SystemUtil.getReleaseValue(atypeFactory.getProcessingEnv()); - this.annotatedJdkVersion = release != null ? release : String.valueOf(SystemUtil.jreVersion); - - SourceChecker checker = atypeFactory.getChecker(); - this.ignorejdkastub = checker.hasOption("ignorejdkastub"); - this.shouldParseJdk = !ignorejdkastub; - this.parseAllJdkFiles = checker.hasOption("parseAllJdk"); - this.permitMissingJdk = checker.hasOption("permitMissingJdk"); - this.stubDebug = checker.hasOption("stubDebug"); - } - - /** - * Returns true if files are currently being parsed; otherwise, false. - * - * @return true if files are currently being parsed; otherwise, false - */ - public boolean isParsing() { - return parsingCount > 0; - } - - /** - * Parses the stub files in the following order: - * - *

            - *
          1. {@code jdk.astub} in the same directory as the checker, if it exists and the {@code - * ignorejdkastub} option is not supplied; - *
          2. {@code jdkN.astub} (where N is the current Java version or any higher value) in the same - * directory as the checker, if it exists and the {@code ignorejdkastub} option is not - * supplied; - *
          3. If parsing the annotated JDK as stub files, all {@code package-info.java} files under the - * {@code jdk/} directory; - *
          4. Stub files listed in a {@code @StubFiles} annotation on the checker; these files must be - * in same directory as the checker; - *
          5. Stub files returned by {@link BaseTypeChecker#getExtraStubFiles} (treated like those - * listed in a {@code @StubFiles} annotation on the checker); - *
          6. Stub files provided via the {@code -Astubs} compiler option. - *
          - * - *

          If a type is annotated with a qualifier from the same hierarchy in more than one stub file, - * the qualifier in the last stub file is applied. - * - *

          If using JDK 11, then the JDK stub files are only parsed if a type or declaration annotation - * is requested from a class in that file. - */ - // TODO: it's unclear for what Java versions a jdkN.astub is parsed. - public void parseStubFiles() { - assert parsingCount == 0; - ++parsingCount; - BaseTypeChecker checker = atypeFactory.getChecker(); - if (stubDebug) { - System.out.printf( - "entered parseStubFiles() for %s, ignorejdkastub=%s%n", - atypeFactory.getClass().getSimpleName(), ignorejdkastub); - } - if (!ignorejdkastub) { - // 1. Annotated JDK - // This preps but does not parse the JDK files (except package-info.java files). - // The JDK source code files will be parsed later, on demand. - prepJdkStubs(); - - // 2. jdk.astub - // Only look in .jar files, and parse it right away. - String[] jdkVersions = {"", annotatedJdkVersion}; - for (String jdkVersion : jdkVersions) { - String jdkVersionStub = "jdk" + jdkVersion + ".astub"; - parseOneStubFile(this.getClass(), jdkVersionStub); - parseOneStubFile(checker.getClass(), jdkVersionStub); - } - // This needs to be special-cased for every jdkX.astub for which files exist. :-( - // TODO: not clear what this is supposed to mean - if we are on Java 8, why parse Java - // 11 stub files? - // It would make more sense to parse this if we're e.g. on Java 12. - if (annotatedJdkVersion.equals("8")) { - String jdk11Stub = "jdk11.astub"; - parseOneStubFile(this.getClass(), jdk11Stub); - parseOneStubFile(checker.getClass(), jdk11Stub); - } + /** Annotations from annotation files (but not from annotated JDK files). */ + private final AnnotationFileAnnotations annotationFileAnnos; + + /** The number of ongoing parsing tasks. */ + private int parsingCount; + + /** AnnotatedTypeFactory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * Mapping from fully-qualified class name to corresponding JDK stub file from the file system + * that have not yet been read. When a file is read, its mapping is removed from this map. + * + *

          By contrast, {@link #remainingJdkStubFilesJar} contains JDK stub files from checker.jar. + */ + private final Map remainingJdkStubFiles = new HashMap<>(); + + /** + * Mapping from fully-qualified class name to corresponding JDK stub files from checker.jar that + * have not yet been read. When a file is read, its mapping is removed from this map. + * + *

          By contrast, {@link #remainingJdkStubFiles} contains JDK stub files from the file system. + */ + private final Map remainingJdkStubFilesJar = new HashMap<>(); + + /** Which version number of the annotated JDK should be used? */ + private final String annotatedJdkVersion; + + /** Should the JDK be parsed? */ + private final boolean shouldParseJdk; + + /** Parse all JDK files at startup rather than as needed. */ + private final boolean parseAllJdkFiles; + + /** True if -ApermitMissingJdk was passed on the command line. */ + private final boolean permitMissingJdk; + + /** True if -Aignorejdkastub was passed on the command line. */ + private final boolean ignorejdkastub; + + /** True if -AstubDebug was passed on the command line. */ + private final boolean stubDebug; + + /** + * Stores the fully qualified name of top-level classes (from any type of stub file) that are + * currently being parsed. This can stop recursively parsing an annotated JDK class that is + * currently being processed, which prevents conflicts of definition and infinite loops. + */ + private final Set processingClasses = new LinkedHashSet<>(); + + /** + * Creates an empty annotation source. + * + * @param atypeFactory a type factory + */ + public AnnotationFileElementTypes(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.annotationFileAnnos = new AnnotationFileAnnotations(); + this.parsingCount = 0; + String release = SystemUtil.getReleaseValue(atypeFactory.getProcessingEnv()); + this.annotatedJdkVersion = + release != null ? release : String.valueOf(SystemUtil.jreVersion); + + SourceChecker checker = atypeFactory.getChecker(); + this.ignorejdkastub = checker.hasOption("ignorejdkastub"); + this.shouldParseJdk = !ignorejdkastub; + this.parseAllJdkFiles = checker.hasOption("parseAllJdk"); + this.permitMissingJdk = checker.hasOption("permitMissingJdk"); + this.stubDebug = checker.hasOption("stubDebug"); } - // 3. Stub files listed in @StubFiles annotation on the checker - StubFiles stubFilesAnnotation = checker.getClass().getAnnotation(StubFiles.class); - if (stubFilesAnnotation != null) { - parseAnnotationFiles( - Arrays.asList(stubFilesAnnotation.value()), AnnotationFileType.BUILTIN_STUB); + /** + * Returns true if files are currently being parsed; otherwise, false. + * + * @return true if files are currently being parsed; otherwise, false + */ + public boolean isParsing() { + return parsingCount > 0; } - // 4. Stub files returned by the `getExtraStubFiles()` method - parseAnnotationFiles(checker.getExtraStubFiles(), AnnotationFileType.BUILTIN_STUB); + /** + * Parses the stub files in the following order: + * + *

            + *
          1. {@code jdk.astub} in the same directory as the checker, if it exists and the {@code + * ignorejdkastub} option is not supplied; + *
          2. {@code jdkN.astub} (where N is the current Java version or any higher value) in the + * same directory as the checker, if it exists and the {@code ignorejdkastub} option is + * not supplied; + *
          3. If parsing the annotated JDK as stub files, all {@code package-info.java} files under + * the {@code jdk/} directory; + *
          4. Stub files listed in a {@code @StubFiles} annotation on the checker; these files must + * be in same directory as the checker; + *
          5. Stub files returned by {@link BaseTypeChecker#getExtraStubFiles} (treated like those + * listed in a {@code @StubFiles} annotation on the checker); + *
          6. Stub files provided via the {@code -Astubs} compiler option. + *
          + * + *

          If a type is annotated with a qualifier from the same hierarchy in more than one stub + * file, the qualifier in the last stub file is applied. + * + *

          If using JDK 11, then the JDK stub files are only parsed if a type or declaration + * annotation is requested from a class in that file. + */ + // TODO: it's unclear for what Java versions a jdkN.astub is parsed. + public void parseStubFiles() { + assert parsingCount == 0; + ++parsingCount; + BaseTypeChecker checker = atypeFactory.getChecker(); + if (stubDebug) { + System.out.printf( + "entered parseStubFiles() for %s, ignorejdkastub=%s%n", + atypeFactory.getClass().getSimpleName(), ignorejdkastub); + } + if (!ignorejdkastub) { + // 1. Annotated JDK + // This preps but does not parse the JDK files (except package-info.java files). + // The JDK source code files will be parsed later, on demand. + prepJdkStubs(); + + // 2. jdk.astub + // Only look in .jar files, and parse it right away. + String[] jdkVersions = {"", annotatedJdkVersion}; + for (String jdkVersion : jdkVersions) { + String jdkVersionStub = "jdk" + jdkVersion + ".astub"; + parseOneStubFile(this.getClass(), jdkVersionStub); + parseOneStubFile(checker.getClass(), jdkVersionStub); + } + // This needs to be special-cased for every jdkX.astub for which files exist. :-( + // TODO: not clear what this is supposed to mean - if we are on Java 8, why parse Java + // 11 stub files? + // It would make more sense to parse this if we're e.g. on Java 12. + if (annotatedJdkVersion.equals("8")) { + String jdk11Stub = "jdk11.astub"; + parseOneStubFile(this.getClass(), jdk11Stub); + parseOneStubFile(checker.getClass(), jdk11Stub); + } + } - // 5. Stub files provided via -Astubs command-line option - String stubsOption = checker.getOption("stubs"); - if (stubsOption != null) { - parseAnnotationFiles( - SystemUtil.PATH_SEPARATOR_SPLITTER.splitToList(stubsOption), - AnnotationFileType.COMMAND_LINE_STUB); - } + // 3. Stub files listed in @StubFiles annotation on the checker + StubFiles stubFilesAnnotation = checker.getClass().getAnnotation(StubFiles.class); + if (stubFilesAnnotation != null) { + parseAnnotationFiles( + Arrays.asList(stubFilesAnnotation.value()), AnnotationFileType.BUILTIN_STUB); + } - --parsingCount; - assert parsingCount == 0; + // 4. Stub files returned by the `getExtraStubFiles()` method + parseAnnotationFiles(checker.getExtraStubFiles(), AnnotationFileType.BUILTIN_STUB); + + // 5. Stub files provided via -Astubs command-line option + String stubsOption = checker.getOption("stubs"); + if (stubsOption != null) { + parseAnnotationFiles( + SystemUtil.PATH_SEPARATOR_SPLITTER.splitToList(stubsOption), + AnnotationFileType.COMMAND_LINE_STUB); + } + + --parsingCount; + assert parsingCount == 0; - if (stubDebug) { - System.out.printf( - "exited parseStubFiles() for %s%n", atypeFactory.getClass().getSimpleName()); - } - } - - /** - * Parse one .astub file. - * - * @param checkerClass the location of the resource in the checker.jar file - * @param stubFileName the basename of the .astub file - */ - private void parseOneStubFile(Class checkerClass, String stubFileName) { - BaseTypeChecker checker = atypeFactory.getChecker(); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - try (InputStream jdkVersionStubIn = checkerClass.getResourceAsStream(stubFileName)) { - if (jdkVersionStubIn != null) { if (stubDebug) { - AnnotationFileParser.stubDebugStatic( - processingEnv, - "parseOneStubFile(%s, %s): jdkVersionStubIn = %s%n", - checkerClass.getSimpleName(), - stubFileName, - jdkVersionStubIn); + System.out.printf( + "exited parseStubFiles() for %s%n", atypeFactory.getClass().getSimpleName()); } - AnnotationFileParser.parseStubFile( - checkerClass.getResource(stubFileName).toString(), - jdkVersionStubIn, - atypeFactory, - processingEnv, - annotationFileAnnos, - AnnotationFileType.BUILTIN_STUB, - this); - } - } catch (IOException e) { - checker.message( - Diagnostic.Kind.NOTE, - "Could not read annotation resource from " + checkerClass + ": " + stubFileName); - } - } - - /** Parses the ajava files passed through the -Aajava command-line option. */ - public void parseAjavaFiles() { - assert parsingCount == 0; - ++parsingCount; - // TODO: Error if this is called more than once? - SourceChecker checker = atypeFactory.getChecker(); - List ajavaFiles = checker.getStringsOption("ajava", File.pathSeparator); - - parseAnnotationFiles(ajavaFiles, AnnotationFileType.AJAVA); - --parsingCount; - assert parsingCount == 0; - } - - /** - * Parses the ajava file at {@code ajavaPath} assuming {@code root} represents the compilation - * unit of that file. Uses {@code root} to get information from javac on specific elements of - * {@code ajavaPath}, enabling storage of more detailed annotation information than with just the - * ajava file. - * - * @param ajavaPath path to an ajava file - * @param root javac tree for the compilation unit stored in {@code ajavaFile} - */ - public void parseAjavaFileWithTree(String ajavaPath, CompilationUnitTree root) { - assert parsingCount == 0; - ++parsingCount; - SourceChecker checker = atypeFactory.getChecker(); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - try (InputStream in = new FileInputStream(ajavaPath)) { - if (stubDebug) { - AnnotationFileParser.stubDebugStatic( - processingEnv, - "parseAjavaFileWithTree(%s, %s): checker = %s, in = %s%n", - ajavaPath, - System.identityHashCode(root), - checker.getClass().getSimpleName(), - in); - } - AnnotationFileParser.parseAjavaFile( - ajavaPath, in, root, atypeFactory, processingEnv, annotationFileAnnos, this); - } catch (IOException e) { - checker.message(Diagnostic.Kind.NOTE, "Could not read ajava file: " + ajavaPath); } - --parsingCount; - assert parsingCount == 0; - } - - /** - * Parses the files in {@code annotationFiles} of the given file type. This includes files listed - * directly in {@code annotationFiles} and for each listed directory, also includes all files - * located in that directory (recursively). - * - * @param annotationFiles list of files and directories to parse - * @param fileType the file type of files to parse - */ - @SuppressWarnings("builder:required.method.not.called" // `allFiles` may contain multiple - // JarEntryAnnotationFileResource. Each of those references a zip file entry resource, which - // itself references a ZipFile resource -- the same ZipFile for multiple zip file entries. - // Closing any one of the zip file entries will close the ZipFile, which invalidates the - // other zipfile entries. Therefore, this code does not close any of them. This code may - // leak resources. - ) - private void parseAnnotationFiles(List annotationFiles, AnnotationFileType fileType) { - SourceChecker checker = atypeFactory.getChecker(); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - if (stubDebug) { - AnnotationFileParser.stubDebugStatic( - processingEnv, "AFET.parseAnnotationFiles(%s, %s)", annotationFiles, fileType); - } - for (String path : annotationFiles) { - // Special case when running in jtreg. - String base = System.getProperty("test.src"); - String fullPath = (base == null) ? path : base + "/" + path; - - List allFiles = - AnnotationFileUtil.allAnnotationFiles(fullPath, fileType); - if (allFiles != null) { - for (AnnotationFileResource resource : allFiles) { - // See note with the SuppressWarnings on this method for why this is not a - // try-with-resources. - BufferedInputStream annotationFileStream; - try { - annotationFileStream = new BufferedInputStream(resource.getInputStream()); - } catch (IOException e) { + /** + * Parse one .astub file. + * + * @param checkerClass the location of the resource in the checker.jar file + * @param stubFileName the basename of the .astub file + */ + private void parseOneStubFile(Class checkerClass, String stubFileName) { + BaseTypeChecker checker = atypeFactory.getChecker(); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + try (InputStream jdkVersionStubIn = checkerClass.getResourceAsStream(stubFileName)) { + if (jdkVersionStubIn != null) { + if (stubDebug) { + AnnotationFileParser.stubDebugStatic( + processingEnv, + "parseOneStubFile(%s, %s): jdkVersionStubIn = %s%n", + checkerClass.getSimpleName(), + stubFileName, + jdkVersionStubIn); + } + AnnotationFileParser.parseStubFile( + checkerClass.getResource(stubFileName).toString(), + jdkVersionStubIn, + atypeFactory, + processingEnv, + annotationFileAnnos, + AnnotationFileType.BUILTIN_STUB, + this); + } + } catch (IOException e) { checker.message( - Diagnostic.Kind.NOTE, - "Could not read annotation resource: " + resource.getDescription()); - continue; - } - // We use parseStubFile here even for ajava files because at this stage - // ajava files are parsed as stub files. The extra annotation data in an - // ajava file is parsed when type-checking the ajava file's corresponding - // Java file. - AnnotationFileParser.parseStubFile( - resource.getDescription(), - annotationFileStream, - atypeFactory, - processingEnv, - annotationFileAnnos, - fileType == AnnotationFileType.AJAVA ? AnnotationFileType.AJAVA_AS_STUB : fileType, - this); - } - } else { - // We didn't find the files. - // If the file has a prefix of "checker.jar/" then look for the file in the top - // level directory of the jar that contains the checker. - if (path.startsWith("checker.jar/")) { - // Note the missing `/` here - this makes sure that `path` starts with `/`. - path = path.substring("checker.jar".length()); + Diagnostic.Kind.NOTE, + "Could not read annotation resource from " + + checkerClass + + ": " + + stubFileName); } - boolean issueWarning; - try (InputStream in = checker.getClass().getResourceAsStream(path)) { - if (in != null) { - AnnotationFileParser.parseStubFile( - path, in, atypeFactory, processingEnv, annotationFileAnnos, fileType, this); - issueWarning = false; - } else { - issueWarning = true; - } + } + + /** Parses the ajava files passed through the -Aajava command-line option. */ + public void parseAjavaFiles() { + assert parsingCount == 0; + ++parsingCount; + // TODO: Error if this is called more than once? + SourceChecker checker = atypeFactory.getChecker(); + List ajavaFiles = checker.getStringsOption("ajava", File.pathSeparator); + + parseAnnotationFiles(ajavaFiles, AnnotationFileType.AJAVA); + --parsingCount; + assert parsingCount == 0; + } + + /** + * Parses the ajava file at {@code ajavaPath} assuming {@code root} represents the compilation + * unit of that file. Uses {@code root} to get information from javac on specific elements of + * {@code ajavaPath}, enabling storage of more detailed annotation information than with just + * the ajava file. + * + * @param ajavaPath path to an ajava file + * @param root javac tree for the compilation unit stored in {@code ajavaFile} + */ + public void parseAjavaFileWithTree(String ajavaPath, CompilationUnitTree root) { + assert parsingCount == 0; + ++parsingCount; + SourceChecker checker = atypeFactory.getChecker(); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + try (InputStream in = new FileInputStream(ajavaPath)) { + if (stubDebug) { + AnnotationFileParser.stubDebugStatic( + processingEnv, + "parseAjavaFileWithTree(%s, %s): checker = %s, in = %s%n", + ajavaPath, + System.identityHashCode(root), + checker.getClass().getSimpleName(), + in); + } + AnnotationFileParser.parseAjavaFile( + ajavaPath, in, root, atypeFactory, processingEnv, annotationFileAnnos, this); } catch (IOException e) { - issueWarning = true; - checker.message(Diagnostic.Kind.NOTE, "Could not read annotation resource: " + path); + checker.message(Diagnostic.Kind.NOTE, "Could not read ajava file: " + ajavaPath); } - if (issueWarning) { - // Didn't find the file. Possibly issue a warning. - - // When using a compound checker, the target file may be found by the - // current checker's parent checkers. Also check this to avoid a false - // warning. Currently, only the original checker will try to parse the - // target file, the parent checkers are only used to reduce false - // warnings. - SourceChecker currentChecker = checker; - boolean findByParentCheckers = false; - while (currentChecker != null) { - URL normalResource = currentChecker.getClass().getResource(path); - if (normalResource != null) { - // If the parent checker supports the stub file, there is no need - // for a warning. - findByParentCheckers = true; - break; - } - // See whether the stub file is mis-placed and issue a helpful warning. - URL topLevelResource = currentChecker.getClass().getResource("/" + path); - if (topLevelResource != null) { - currentChecker.message( - Diagnostic.Kind.WARNING, - path - + " should be in the same directory as " - + currentChecker.getClass().getSimpleName() - + ".class, but is at the top level of a jar file: " - + topLevelResource); - findByParentCheckers = true; - break; + --parsingCount; + assert parsingCount == 0; + } + + /** + * Parses the files in {@code annotationFiles} of the given file type. This includes files + * listed directly in {@code annotationFiles} and for each listed directory, also includes all + * files located in that directory (recursively). + * + * @param annotationFiles list of files and directories to parse + * @param fileType the file type of files to parse + */ + @SuppressWarnings("builder:required.method.not.called" // `allFiles` may contain multiple + // JarEntryAnnotationFileResource. Each of those references a zip file entry resource, which + // itself references a ZipFile resource -- the same ZipFile for multiple zip file entries. + // Closing any one of the zip file entries will close the ZipFile, which invalidates the + // other zipfile entries. Therefore, this code does not close any of them. This code may + // leak resources. + ) + private void parseAnnotationFiles(List annotationFiles, AnnotationFileType fileType) { + SourceChecker checker = atypeFactory.getChecker(); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + if (stubDebug) { + AnnotationFileParser.stubDebugStatic( + processingEnv, "AFET.parseAnnotationFiles(%s, %s)", annotationFiles, fileType); + } + for (String path : annotationFiles) { + // Special case when running in jtreg. + String base = System.getProperty("test.src"); + String fullPath = (base == null) ? path : base + "/" + path; + + List allFiles = + AnnotationFileUtil.allAnnotationFiles(fullPath, fileType); + if (allFiles != null) { + for (AnnotationFileResource resource : allFiles) { + // See note with the SuppressWarnings on this method for why this is not a + // try-with-resources. + BufferedInputStream annotationFileStream; + try { + annotationFileStream = new BufferedInputStream(resource.getInputStream()); + } catch (IOException e) { + checker.message( + Diagnostic.Kind.NOTE, + "Could not read annotation resource: " + resource.getDescription()); + continue; + } + // We use parseStubFile here even for ajava files because at this stage + // ajava files are parsed as stub files. The extra annotation data in an + // ajava file is parsed when type-checking the ajava file's corresponding + // Java file. + AnnotationFileParser.parseStubFile( + resource.getDescription(), + annotationFileStream, + atypeFactory, + processingEnv, + annotationFileAnnos, + fileType == AnnotationFileType.AJAVA + ? AnnotationFileType.AJAVA_AS_STUB + : fileType, + this); + } } else { - currentChecker = currentChecker.getParentChecker(); + // We didn't find the files. + // If the file has a prefix of "checker.jar/" then look for the file in the top + // level directory of the jar that contains the checker. + if (path.startsWith("checker.jar/")) { + // Note the missing `/` here - this makes sure that `path` starts with `/`. + path = path.substring("checker.jar".length()); + } + boolean issueWarning; + try (InputStream in = checker.getClass().getResourceAsStream(path)) { + if (in != null) { + AnnotationFileParser.parseStubFile( + path, + in, + atypeFactory, + processingEnv, + annotationFileAnnos, + fileType, + this); + issueWarning = false; + } else { + issueWarning = true; + } + } catch (IOException e) { + issueWarning = true; + checker.message( + Diagnostic.Kind.NOTE, "Could not read annotation resource: " + path); + } + + if (issueWarning) { + // Didn't find the file. Possibly issue a warning. + + // When using a compound checker, the target file may be found by the + // current checker's parent checkers. Also check this to avoid a false + // warning. Currently, only the original checker will try to parse the + // target file, the parent checkers are only used to reduce false + // warnings. + SourceChecker currentChecker = checker; + boolean findByParentCheckers = false; + while (currentChecker != null) { + URL normalResource = currentChecker.getClass().getResource(path); + if (normalResource != null) { + // If the parent checker supports the stub file, there is no need + // for a warning. + findByParentCheckers = true; + break; + } + // See whether the stub file is mis-placed and issue a helpful warning. + URL topLevelResource = currentChecker.getClass().getResource("/" + path); + if (topLevelResource != null) { + currentChecker.message( + Diagnostic.Kind.WARNING, + path + + " should be in the same directory as " + + currentChecker.getClass().getSimpleName() + + ".class, but is at the top level of a jar file: " + + topLevelResource); + findByParentCheckers = true; + break; + } else { + currentChecker = currentChecker.getParentChecker(); + } + } + // If there exists one parent checker that can find this file, don't report + // a warning. + if (!findByParentCheckers) { + File parentPath = new File(path).getParentFile(); + String parentPathDescription = + (parentPath == null + ? "current directory" + : "directory " + parentPath.getAbsolutePath()); + String msg = + checker.getClass().getSimpleName() + + " did not find annotation file or directory " + + path + + " on classpath or within " + + parentPathDescription + + (fullPath.equals(path) ? "" : (" or at " + fullPath)); + StringJoiner sj = new StringJoiner(System.lineSeparator() + " "); + sj.add(msg); + /* + sj.add("Classpath:"); + for (URI uri : new ClassGraph().getClasspathURIs()) { + sj.add(uri.toString()); + } + */ + checker.message(Diagnostic.Kind.WARNING, sj.toString()); + } + } } - } - // If there exists one parent checker that can find this file, don't report - // a warning. - if (!findByParentCheckers) { - File parentPath = new File(path).getParentFile(); - String parentPathDescription = - (parentPath == null - ? "current directory" - : "directory " + parentPath.getAbsolutePath()); - String msg = - checker.getClass().getSimpleName() - + " did not find annotation file or directory " - + path - + " on classpath or within " - + parentPathDescription - + (fullPath.equals(path) ? "" : (" or at " + fullPath)); - StringJoiner sj = new StringJoiner(System.lineSeparator() + " "); - sj.add(msg); - /* - sj.add("Classpath:"); - for (URI uri : new ClassGraph().getClasspathURIs()) { - sj.add(uri.toString()); - } - */ - checker.message(Diagnostic.Kind.WARNING, sj.toString()); - } } - } } - } - - /** - * Returns the annotated type for {@code e} containing only annotations explicitly written in an - * annotation file. Returns {@code null} if {@code e} does not appear in an annotation file. - * - * @param e an Element whose type is returned - * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in - * the annotation file and in the element. Returns {@code null} if {@code element} does not - * appear in an annotation file. - */ - public @Nullable AnnotatedTypeMirror getAnnotatedTypeMirror(Element e) { - maybeParseEnclosingJdkClass(e); - AnnotatedTypeMirror type = annotationFileAnnos.atypes.get(e); - return type == null ? null : type.deepCopy(); - } - - /** - * Returns the set of declaration annotations for {@code e} containing only annotations explicitly - * written in an annotation file or the empty set if {@code e} does not appear in an annotation - * file. - * - * @param elt element for which annotations are returned - * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in - * the annotation file and in the element. {@code null} is returned if {@code element} does - * not appear in an annotation file. - */ - public @Nullable AnnotationMirrorSet getDeclAnnotations(Element elt) { - if (stubDebug) { - if (isParsing()) { - System.out.printf("AFET.getDeclAnnotations(%s [%s])%n", elt, elt.getClass()); - } else { - System.out.printf("AFET.getDeclAnnotations(%s [%s]) IS NOT PARSING%n", elt, elt.getClass()); - } + + /** + * Returns the annotated type for {@code e} containing only annotations explicitly written in an + * annotation file. Returns {@code null} if {@code e} does not appear in an annotation file. + * + * @param e an Element whose type is returned + * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written + * in the annotation file and in the element. Returns {@code null} if {@code element} does + * not appear in an annotation file. + */ + public @Nullable AnnotatedTypeMirror getAnnotatedTypeMirror(Element e) { + maybeParseEnclosingJdkClass(e); + AnnotatedTypeMirror type = annotationFileAnnos.atypes.get(e); + return type == null ? null : type.deepCopy(); } - maybeParseEnclosingJdkClass(elt); - String eltName = ElementUtils.getQualifiedName(elt); - if (annotationFileAnnos.declAnnos.containsKey(eltName)) { - return annotationFileAnnos.declAnnos.get(eltName); - } else { - // Handle annotations on record declarations. - boolean canTransferAnnotationsToSameName; - Element enclosingType; // Do nothing unless this element is a record. - switch (elt.getKind()) { - case METHOD: - // Annotations transfer to zero-arg accessor methods of same name: - canTransferAnnotationsToSameName = ((ExecutableElement) elt).getParameters().isEmpty(); - enclosingType = elt.getEnclosingElement(); - break; - case FIELD: - // Annotations transfer to fields of same name: - canTransferAnnotationsToSameName = true; - enclosingType = elt.getEnclosingElement(); - break; - case PARAMETER: - // Annotations transfer to compact canonical constructor parameter of same name: - canTransferAnnotationsToSameName = - ElementUtils.isCompactCanonicalRecordConstructor(elt.getEnclosingElement()) - && elt.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR; - enclosingType = elt.getEnclosingElement().getEnclosingElement(); - break; - default: - canTransferAnnotationsToSameName = false; - enclosingType = null; - break; - } - - if (canTransferAnnotationsToSameName && ElementUtils.isRecordElement(enclosingType)) { - AnnotationFileParser.RecordStub recordStub = - annotationFileAnnos.records.get(enclosingType.getSimpleName().toString()); - if (recordStub != null - && recordStub.componentsByName.containsKey(elt.getSimpleName().toString())) { - RecordComponentStub recordComponentStub = - recordStub.componentsByName.get(elt.getSimpleName().toString()); - return recordComponentStub.getAnnotationsForTarget(elt.getKind()); + /** + * Returns the set of declaration annotations for {@code e} containing only annotations + * explicitly written in an annotation file or the empty set if {@code e} does not appear in an + * annotation file. + * + * @param elt element for which annotations are returned + * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written + * in the annotation file and in the element. {@code null} is returned if {@code element} + * does not appear in an annotation file. + */ + public @Nullable AnnotationMirrorSet getDeclAnnotations(Element elt) { + if (stubDebug) { + if (isParsing()) { + System.out.printf("AFET.getDeclAnnotations(%s [%s])%n", elt, elt.getClass()); + } else { + System.out.printf( + "AFET.getDeclAnnotations(%s [%s]) IS NOT PARSING%n", elt, elt.getClass()); + } } - } - } - return AnnotationMirrorSet.emptySet(); - } - - /** - * Adds annotations from stub files for the corresponding record components (if the given - * constructor/method is the canonical constructor or a record accessor). Such transfer is - * automatically done by javac usually, but not from stubs. - * - * @param types a Types instance used for checking type equivalence - * @param elt a member. This method does nothing if it's not a method or constructor. - * @param memberType the type corresponding to the element elt; side-effected by this method - */ - public void injectRecordComponentType( - Types types, Element elt, AnnotatedExecutableType memberType) { - if (isParsing()) { - throw new BugInCF("parsing while calling injectRecordComponentType"); + + maybeParseEnclosingJdkClass(elt); + String eltName = ElementUtils.getQualifiedName(elt); + if (annotationFileAnnos.declAnnos.containsKey(eltName)) { + return annotationFileAnnos.declAnnos.get(eltName); + } else { + // Handle annotations on record declarations. + boolean canTransferAnnotationsToSameName; + Element enclosingType; // Do nothing unless this element is a record. + switch (elt.getKind()) { + case METHOD: + // Annotations transfer to zero-arg accessor methods of same name: + canTransferAnnotationsToSameName = + ((ExecutableElement) elt).getParameters().isEmpty(); + enclosingType = elt.getEnclosingElement(); + break; + case FIELD: + // Annotations transfer to fields of same name: + canTransferAnnotationsToSameName = true; + enclosingType = elt.getEnclosingElement(); + break; + case PARAMETER: + // Annotations transfer to compact canonical constructor parameter of same name: + canTransferAnnotationsToSameName = + ElementUtils.isCompactCanonicalRecordConstructor( + elt.getEnclosingElement()) + && elt.getEnclosingElement().getKind() + == ElementKind.CONSTRUCTOR; + enclosingType = elt.getEnclosingElement().getEnclosingElement(); + break; + default: + canTransferAnnotationsToSameName = false; + enclosingType = null; + break; + } + + if (canTransferAnnotationsToSameName && ElementUtils.isRecordElement(enclosingType)) { + AnnotationFileParser.RecordStub recordStub = + annotationFileAnnos.records.get(enclosingType.getSimpleName().toString()); + if (recordStub != null + && recordStub.componentsByName.containsKey( + elt.getSimpleName().toString())) { + RecordComponentStub recordComponentStub = + recordStub.componentsByName.get(elt.getSimpleName().toString()); + return recordComponentStub.getAnnotationsForTarget(elt.getKind()); + } + } + } + return AnnotationMirrorSet.emptySet(); } - if (elt.getKind() == ElementKind.METHOD) { - if (((ExecutableElement) elt).getParameters().isEmpty()) { - String recordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); - AnnotationFileParser.RecordStub recordComponentType = - annotationFileAnnos.records.get(recordName); - if (recordComponentType != null) { - // If the record component has an annotation in the stub, the component - // annotation replaces any from the same hierarchy on the accessor method, - // unless there is an accessor in the stubs file (which may or may not have an - // annotation in the same hierarchy; the user may want to specify the annotation - // or deliberately not annotate the accessor). - // We thus only replace the method annotation with the component annotation - // if there is no accessor in the stubs file: - RecordComponentStub recordComponentStub = - recordComponentType.componentsByName.get(elt.getSimpleName().toString()); - if (recordComponentStub != null && !recordComponentStub.hasAccessorInStubs()) { - memberType - .getReturnType() - .replaceAnnotations(recordComponentStub.type.getAnnotations()); - } + /** + * Adds annotations from stub files for the corresponding record components (if the given + * constructor/method is the canonical constructor or a record accessor). Such transfer is + * automatically done by javac usually, but not from stubs. + * + * @param types a Types instance used for checking type equivalence + * @param elt a member. This method does nothing if it's not a method or constructor. + * @param memberType the type corresponding to the element elt; side-effected by this method + */ + public void injectRecordComponentType( + Types types, Element elt, AnnotatedExecutableType memberType) { + if (isParsing()) { + throw new BugInCF("parsing while calling injectRecordComponentType"); } - } - } else if (elt.getKind() == ElementKind.CONSTRUCTOR) { - if (AnnotationFileUtil.isCanonicalConstructor((ExecutableElement) elt, types)) { - TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); - AnnotationFileParser.RecordStub recordComponentType = - annotationFileAnnos.records.get(enclosing.getQualifiedName().toString()); - if (recordComponentType != null) { - List componentsInCanonicalConstructor = - recordComponentType.getComponentsInCanonicalConstructor(); - if (componentsInCanonicalConstructor != null) { - for (int i = 0; i < componentsInCanonicalConstructor.size(); i++) { - memberType - .getParameterTypes() - .get(i) - .replaceAnnotations(componentsInCanonicalConstructor.get(i).getAnnotations()); + + if (elt.getKind() == ElementKind.METHOD) { + if (((ExecutableElement) elt).getParameters().isEmpty()) { + String recordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); + AnnotationFileParser.RecordStub recordComponentType = + annotationFileAnnos.records.get(recordName); + if (recordComponentType != null) { + // If the record component has an annotation in the stub, the component + // annotation replaces any from the same hierarchy on the accessor method, + // unless there is an accessor in the stubs file (which may or may not have an + // annotation in the same hierarchy; the user may want to specify the annotation + // or deliberately not annotate the accessor). + // We thus only replace the method annotation with the component annotation + // if there is no accessor in the stubs file: + RecordComponentStub recordComponentStub = + recordComponentType.componentsByName.get( + elt.getSimpleName().toString()); + if (recordComponentStub != null && !recordComponentStub.hasAccessorInStubs()) { + memberType + .getReturnType() + .replaceAnnotations(recordComponentStub.type.getAnnotations()); + } + } + } + } else if (elt.getKind() == ElementKind.CONSTRUCTOR) { + if (AnnotationFileUtil.isCanonicalConstructor((ExecutableElement) elt, types)) { + TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); + AnnotationFileParser.RecordStub recordComponentType = + annotationFileAnnos.records.get(enclosing.getQualifiedName().toString()); + if (recordComponentType != null) { + List componentsInCanonicalConstructor = + recordComponentType.getComponentsInCanonicalConstructor(); + if (componentsInCanonicalConstructor != null) { + for (int i = 0; i < componentsInCanonicalConstructor.size(); i++) { + memberType + .getParameterTypes() + .get(i) + .replaceAnnotations( + componentsInCanonicalConstructor + .get(i) + .getAnnotations()); + } + } + } } - } } - } - } - } - - /** - * Returns the method type of the most specific fake override for the given element, when used as - * a member of the given type. - * - * @param elt element for which annotations are returned - * @param receiverType the type of the class that contains member (or a subtype of it) - * @return the most specific AnnotatedTypeMirror for {@code elt} that is a fake override, or null - * if there are no fake overrides - */ - public @Nullable AnnotatedExecutableType getFakeOverride( - Element elt, AnnotatedTypeMirror receiverType) { - if (isParsing()) { - throw new BugInCF("parsing while calling getFakeOverride"); } - if (elt.getKind() != ElementKind.METHOD) { - return null; - } + /** + * Returns the method type of the most specific fake override for the given element, when used + * as a member of the given type. + * + * @param elt element for which annotations are returned + * @param receiverType the type of the class that contains member (or a subtype of it) + * @return the most specific AnnotatedTypeMirror for {@code elt} that is a fake override, or + * null if there are no fake overrides + */ + public @Nullable AnnotatedExecutableType getFakeOverride( + Element elt, AnnotatedTypeMirror receiverType) { + if (isParsing()) { + throw new BugInCF("parsing while calling getFakeOverride"); + } - ExecutableElement method = (ExecutableElement) elt; + if (elt.getKind() != ElementKind.METHOD) { + return null; + } - // This is a list of pairs of (where defined, method type) for fake overrides. The second - // element of each pair is currently always an AnnotatedExecutableType. - List> candidates = - annotationFileAnnos.fakeOverrides.get(method); + ExecutableElement method = (ExecutableElement) elt; - if (candidates == null || candidates.isEmpty()) { - return null; - } + // This is a list of pairs of (where defined, method type) for fake overrides. The second + // element of each pair is currently always an AnnotatedExecutableType. + List> candidates = + annotationFileAnnos.fakeOverrides.get(method); - TypeMirror receiverTypeMirror = receiverType.getUnderlyingType(); - - // A list of fake receiver types. - List applicableClasses = new ArrayList<>(); - List applicableInterfaces = new ArrayList<>(); - for (IPair candidatePair : candidates) { - TypeMirror fakeLocation = candidatePair.first; - AnnotatedExecutableType candidate = (AnnotatedExecutableType) candidatePair.second; - if (atypeFactory.types.isSameType(receiverTypeMirror, fakeLocation)) { - return candidate; - } else if (atypeFactory.types.isSubtype(receiverTypeMirror, fakeLocation)) { - TypeElement fakeElement = TypesUtils.getTypeElement(fakeLocation); - switch (fakeElement.getKind()) { - case CLASS: - case ENUM: - applicableClasses.add(fakeLocation); - break; - case INTERFACE: - case ANNOTATION_TYPE: - applicableInterfaces.add(fakeLocation); - break; - default: - throw new BugInCF( - "What type? %s %s %s", fakeElement.getKind(), fakeElement.getClass(), fakeElement); + if (candidates == null || candidates.isEmpty()) { + return null; } - } - } - if (applicableClasses.isEmpty() && applicableInterfaces.isEmpty()) { - return null; - } - TypeMirror fakeReceiverType = - TypesUtils.mostSpecific( - !applicableClasses.isEmpty() ? applicableClasses : applicableInterfaces, - atypeFactory.getProcessingEnv()); - if (fakeReceiverType == null) { - StringJoiner message = new StringJoiner(System.lineSeparator()); - message.add( - String.format( - "No most specific fake override found for %s with receiver %s." - + " These fake overrides are applicable:", - elt, receiverTypeMirror)); - for (TypeMirror candidate : applicableClasses) { - message.add(" class candidate: " + candidate); - } - for (TypeMirror candidate : applicableInterfaces) { - message.add(" interface candidate: " + candidate); - } - throw new BugInCF(message.toString()); - } + TypeMirror receiverTypeMirror = receiverType.getUnderlyingType(); + + // A list of fake receiver types. + List applicableClasses = new ArrayList<>(); + List applicableInterfaces = new ArrayList<>(); + for (IPair candidatePair : candidates) { + TypeMirror fakeLocation = candidatePair.first; + AnnotatedExecutableType candidate = (AnnotatedExecutableType) candidatePair.second; + if (atypeFactory.types.isSameType(receiverTypeMirror, fakeLocation)) { + return candidate; + } else if (atypeFactory.types.isSubtype(receiverTypeMirror, fakeLocation)) { + TypeElement fakeElement = TypesUtils.getTypeElement(fakeLocation); + switch (fakeElement.getKind()) { + case CLASS: + case ENUM: + applicableClasses.add(fakeLocation); + break; + case INTERFACE: + case ANNOTATION_TYPE: + applicableInterfaces.add(fakeLocation); + break; + default: + throw new BugInCF( + "What type? %s %s %s", + fakeElement.getKind(), fakeElement.getClass(), fakeElement); + } + } + } - for (IPair candidatePair : candidates) { - TypeMirror candidateReceiverType = candidatePair.first; - if (atypeFactory.types.isSameType(fakeReceiverType, candidateReceiverType)) { - return (AnnotatedExecutableType) candidatePair.second; - } - } + if (applicableClasses.isEmpty() && applicableInterfaces.isEmpty()) { + return null; + } + TypeMirror fakeReceiverType = + TypesUtils.mostSpecific( + !applicableClasses.isEmpty() ? applicableClasses : applicableInterfaces, + atypeFactory.getProcessingEnv()); + if (fakeReceiverType == null) { + StringJoiner message = new StringJoiner(System.lineSeparator()); + message.add( + String.format( + "No most specific fake override found for %s with receiver %s." + + " These fake overrides are applicable:", + elt, receiverTypeMirror)); + for (TypeMirror candidate : applicableClasses) { + message.add(" class candidate: " + candidate); + } + for (TypeMirror candidate : applicableInterfaces) { + message.add(" interface candidate: " + candidate); + } + throw new BugInCF(message.toString()); + } - throw new BugInCF( - "No match for %s in %s %s %s", - fakeReceiverType, candidates, applicableClasses, applicableInterfaces); - } - - /// - /// End of public methods, private helper methods follow - /// - - /** - * Parses the outermost enclosing class of {@code e} if it is in the annotated JDK and it has not - * already been parsed. - * - * @param e element whose outermost enclosing class might be parsed, if it is in the JDK and has - * not already been parsed - */ - private void maybeParseEnclosingJdkClass(Element e) { - if (stubDebug) { - System.out.printf( - "maybeParseEnclosingJdkClass(%s encloses %s), shouldParseJdk=%s%n", - getOutermostEnclosingClass(e), e, shouldParseJdk); - } + for (IPair candidatePair : candidates) { + TypeMirror candidateReceiverType = candidatePair.first; + if (atypeFactory.types.isSameType(fakeReceiverType, candidateReceiverType)) { + return (AnnotatedExecutableType) candidatePair.second; + } + } - if (!shouldParseJdk - || e.getKind() == ElementKind.PACKAGE - || e.getKind() == ElementKind.MODULE) { - return; + throw new BugInCF( + "No match for %s in %s %s %s", + fakeReceiverType, candidates, applicableClasses, applicableInterfaces); } - String className = getOutermostEnclosingClass(e); - // `className` can be null if `e` is a package or module element. - if (className == null || className.isEmpty()) { - // TODO: maybe investigate other situations where the enclosing class is missing - // if (e.getKind() != ElementKind.PACKAGE && e.getKind() != - // ElementKind.MODULE) { - // atypeFactory.getChecker().reportWarning(e, "unexpected element " + e + - // " of - // kind " + e.getKind()); - // } - - return; - } + /// + /// End of public methods, private helper methods follow + /// + + /** + * Parses the outermost enclosing class of {@code e} if it is in the annotated JDK and it has + * not already been parsed. + * + * @param e element whose outermost enclosing class might be parsed, if it is in the JDK and has + * not already been parsed + */ + private void maybeParseEnclosingJdkClass(Element e) { + if (stubDebug) { + System.out.printf( + "maybeParseEnclosingJdkClass(%s encloses %s), shouldParseJdk=%s%n", + getOutermostEnclosingClass(e), e, shouldParseJdk); + } - if (processingClasses.contains(className)) { - // TODO: some declaration annotations in the enclosing class may still - // be missing, we can revisit this part if it's causing issues - return; - } + if (!shouldParseJdk + || e.getKind() == ElementKind.PACKAGE + || e.getKind() == ElementKind.MODULE) { + return; + } - if (remainingJdkStubFiles.containsKey(className)) { - parseJdkStubFile(remainingJdkStubFiles.remove(className)); - } else if (remainingJdkStubFilesJar.containsKey(className)) { - parseJdkJarEntry(remainingJdkStubFilesJar.remove(className)); - } else { - if (stubDebug) { - System.out.printf(" not in remaining JDK stub files: %s%n", className); - } - } - } - - /** - * Returns the fully qualified name of the outermost enclosing class of {@code e} or {@code null} - * if no such class exists for {@code e}, such as when {@code e} is a package or module element. - * - * @param e an element whose outermost enclosing class to return - * @return the canonical name of the outermost enclosing class of {@code e} or {@code null} if no - * class encloses {@code e} - */ - private @Nullable @CanonicalNameOrEmpty String getOutermostEnclosingClass(Element e) { - TypeElement enclosingClass = ElementUtils.enclosingTypeElement(e); - if (enclosingClass == null) { - return null; - } - while (true) { - Element element = enclosingClass.getEnclosingElement(); - if (element == null || element.getKind() == ElementKind.PACKAGE) { - break; - } - TypeElement t = ElementUtils.enclosingTypeElement(element); - if (t == null) { - break; - } - enclosingClass = t; - } - @SuppressWarnings("signature:assignment.type.incompatible" // https://tinyurl.com/cfissue/658: - // Name.toString should be @PolySignature - ) - @CanonicalNameOrEmpty String result = enclosingClass.getQualifiedName().toString(); - return result; - } - - /** - * Parses the stub file in {@code path}. - * - * @param path path to file to parse - */ - private void parseJdkStubFile(Path path) { - ++parsingCount; - try (FileInputStream jdkStub = new FileInputStream(path.toFile())) { - AnnotationFileParser.parseJdkFileAsStub( - path.toFile().getName(), - jdkStub, - atypeFactory, - atypeFactory.getProcessingEnv(), - annotationFileAnnos, - this); - } catch (IOException e) { - throw new BugInCF("cannot open the jdk stub file " + path, e); - } finally { - --parsingCount; - } - } - - /** - * Parses the stub file in the given jar entry. - * - * @param jarEntryName name of the jar entry to parse - */ - private void parseJdkJarEntry(String jarEntryName) { - if (stubDebug) { - System.out.printf("entered parseJdkJarEntry(%s)%n", jarEntryName); - } + String className = getOutermostEnclosingClass(e); + // `className` can be null if `e` is a package or module element. + if (className == null || className.isEmpty()) { + // TODO: maybe investigate other situations where the enclosing class is missing + // if (e.getKind() != ElementKind.PACKAGE && e.getKind() != + // ElementKind.MODULE) { + // atypeFactory.getChecker().reportWarning(e, "unexpected element " + e + + // " of + // kind " + e.getKind()); + // } + + return; + } - JarURLConnection connection = getJarURLConnectionToJdk(); - ++parsingCount; - try (JarFile jarFile = connection.getJarFile()) { - try (InputStream jdkStub = jarFile.getInputStream(jarFile.getJarEntry(jarEntryName))) { - AnnotationFileParser.parseJdkFileAsStub( - jarEntryName, - jdkStub, - atypeFactory, - atypeFactory.getProcessingEnv(), - annotationFileAnnos, - this); - } catch (IOException e) { - throw new BugInCF("cannot open the jdk stub file " + jarEntryName, e); - } - } catch (IOException e) { - throw new BugInCF("cannot open the Jar file " + connection.getEntryName(), e); - } catch (BugInCF e) { - throw new BugInCF("Exception while parsing " + jarEntryName + ": " + e.getMessage(), e); - } finally { - --parsingCount; - } + if (processingClasses.contains(className)) { + // TODO: some declaration annotations in the enclosing class may still + // be missing, we can revisit this part if it's causing issues + return; + } - if (stubDebug) { - System.out.printf("exited parseJdkJarEntry(%s)%n", jarEntryName); - } - } - - /** - * Returns a JarURLConnection to "/jdk*". - * - * @return a JarURLConnection to "/jdk*" - */ - private JarURLConnection getJarURLConnectionToJdk() { - URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); - JarURLConnection connection; - try { - connection = (JarURLConnection) resourceURL.openConnection(); - - // disable caching / connection sharing of the low level URLConnection to the Jarfile - connection.setDefaultUseCaches(false); - connection.setUseCaches(false); - - connection.connect(); - } catch (IOException e) { - throw new BugInCF("cannot open a connection to the Jar file " + resourceURL.getFile(), e); - } - return connection; - } - - /** - * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from file - * name to the class contained with in it. Also, parses all package-info.java files. - */ - private void prepJdkStubs() { - if (!shouldParseJdk) { - return; - } - URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); - if (stubDebug) { - System.out.printf( - "Loading JDK from class %s and url: %s%n", atypeFactory.getClass(), resourceURL); + if (remainingJdkStubFiles.containsKey(className)) { + parseJdkStubFile(remainingJdkStubFiles.remove(className)); + } else if (remainingJdkStubFilesJar.containsKey(className)) { + parseJdkJarEntry(remainingJdkStubFilesJar.remove(className)); + } else { + if (stubDebug) { + System.out.printf(" not in remaining JDK stub files: %s%n", className); + } + } } - if (resourceURL == null) { - if (permitMissingJdk) { - return; - } - throw new BugInCF( - "JDK not found for type factory " + atypeFactory.getClass().getSimpleName()); - } else if (resourceURL.getProtocol().contentEquals("jar")) { - prepJdkFromJar(resourceURL); - } else if (resourceURL.getProtocol().contentEquals("file")) { - prepJdkFromFile(resourceURL); - } else { - if (permitMissingJdk) { - return; - } - throw new BugInCF( - "JDK not found in " - + resourceURL - + ". Unsupported protocol: " - + resourceURL.getProtocol()); + + /** + * Returns the fully qualified name of the outermost enclosing class of {@code e} or {@code + * null} if no such class exists for {@code e}, such as when {@code e} is a package or module + * element. + * + * @param e an element whose outermost enclosing class to return + * @return the canonical name of the outermost enclosing class of {@code e} or {@code null} if + * no class encloses {@code e} + */ + private @Nullable @CanonicalNameOrEmpty String getOutermostEnclosingClass(Element e) { + TypeElement enclosingClass = ElementUtils.enclosingTypeElement(e); + if (enclosingClass == null) { + return null; + } + while (true) { + Element element = enclosingClass.getEnclosingElement(); + if (element == null || element.getKind() == ElementKind.PACKAGE) { + break; + } + TypeElement t = ElementUtils.enclosingTypeElement(element); + if (t == null) { + break; + } + enclosingClass = t; + } + @SuppressWarnings( + "signature:assignment.type.incompatible" // https://tinyurl.com/cfissue/658: + // Name.toString should be @PolySignature + ) + @CanonicalNameOrEmpty String result = enclosingClass.getQualifiedName().toString(); + return result; } - } - - /** - * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from file - * name to the class contained with in it. Also, parses all package-info.java files. - * - * @param jdkDirectory the URL pointing to the JDK directory - */ - private void prepJdkFromFile(URL jdkDirectory) { - Path root; - try { - root = Paths.get(jdkDirectory.toURI()); - } catch (URISyntaxException e) { - throw new BugInCF("Cannot parse URL: " + jdkDirectory.toString(), e); + + /** + * Parses the stub file in {@code path}. + * + * @param path path to file to parse + */ + private void parseJdkStubFile(Path path) { + ++parsingCount; + try (FileInputStream jdkStub = new FileInputStream(path.toFile())) { + AnnotationFileParser.parseJdkFileAsStub( + path.toFile().getName(), + jdkStub, + atypeFactory, + atypeFactory.getProcessingEnv(), + annotationFileAnnos, + this); + } catch (IOException e) { + throw new BugInCF("cannot open the jdk stub file " + path, e); + } finally { + --parsingCount; + } } - try (Stream walk = Files.walk(root)) { - List paths = - walk.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java")) - .collect(Collectors.toList()); - paths.sort(Path::compareTo); - for (Path path : paths) { - if (path.getFileName().toString().equals("package-info.java")) { - parseJdkStubFile(path); - continue; + /** + * Parses the stub file in the given jar entry. + * + * @param jarEntryName name of the jar entry to parse + */ + private void parseJdkJarEntry(String jarEntryName) { + if (stubDebug) { + System.out.printf("entered parseJdkJarEntry(%s)%n", jarEntryName); } - if (path.getFileName().toString().equals("module-info.java")) { - // JavaParser can't parse module-info files, so skip them. - continue; + + JarURLConnection connection = getJarURLConnectionToJdk(); + ++parsingCount; + try (JarFile jarFile = connection.getJarFile()) { + try (InputStream jdkStub = jarFile.getInputStream(jarFile.getJarEntry(jarEntryName))) { + AnnotationFileParser.parseJdkFileAsStub( + jarEntryName, + jdkStub, + atypeFactory, + atypeFactory.getProcessingEnv(), + annotationFileAnnos, + this); + } catch (IOException e) { + throw new BugInCF("cannot open the jdk stub file " + jarEntryName, e); + } + } catch (IOException e) { + throw new BugInCF("cannot open the Jar file " + connection.getEntryName(), e); + } catch (BugInCF e) { + throw new BugInCF("Exception while parsing " + jarEntryName + ": " + e.getMessage(), e); + } finally { + --parsingCount; } - if (parseAllJdkFiles) { - parseJdkStubFile(path); - continue; + + if (stubDebug) { + System.out.printf("exited parseJdkJarEntry(%s)%n", jarEntryName); } - Path relativePath = root.relativize(path); - // The number 4 is to strip off "/src//share/classes". - Path savepath = relativePath.subpath(4, relativePath.getNameCount()); - String savepathString = savepath.toString(); - // The number 5 is to remove trailing ".java". - String savepathWithoutExtension = savepathString.substring(0, savepathString.length() - 5); - String fqName = savepathWithoutExtension.replace(File.separatorChar, '.'); - remainingJdkStubFiles.put(fqName, path); - } - if (stubDebug) { - System.out.printf( - "Contents of remainingJdkStubFiles for %s from %s:%n", - atypeFactory.getClass().getSimpleName(), jdkDirectory); - printSortedIndented(remainingJdkStubFiles.keySet()); - System.out.printf( - "End of remainingJdkStubFiles for %s from %s.%n", - atypeFactory.getClass().getSimpleName(), jdkDirectory); - } - } catch (IOException e) { - throw new BugInCF("prepJdkFromFile(" + jdkDirectory + ")", e); } - } - - /** - * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFilesJar}, from - * file name to the class contained with in it. Also, parses all package-info.java files. - * - * @param jdkJarfile the URL pointing to the JDK jarfile - */ - private void prepJdkFromJar(@SuppressWarnings("UnusedVariable") URL jdkJarfile) { - JarURLConnection connection = getJarURLConnectionToJdk(); - - try (JarFile jarFile = connection.getJarFile()) { - ArrayList entries = CollectionsPlume.makeArrayList(jarFile.entries()); - entries.sort(Comparator.comparing(Object::toString)); - for (JarEntry jarEntry : entries) { - // filter out directories and non-Java files - if (jarEntry.isDirectory()) { - continue; + + /** + * Returns a JarURLConnection to "/jdk*". + * + * @return a JarURLConnection to "/jdk*" + */ + private JarURLConnection getJarURLConnectionToJdk() { + URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); + JarURLConnection connection; + try { + connection = (JarURLConnection) resourceURL.openConnection(); + + // disable caching / connection sharing of the low level URLConnection to the Jarfile + connection.setDefaultUseCaches(false); + connection.setUseCaches(false); + + connection.connect(); + } catch (IOException e) { + throw new BugInCF( + "cannot open a connection to the Jar file " + resourceURL.getFile(), e); + } + return connection; + } + + /** + * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from + * file name to the class contained with in it. Also, parses all package-info.java files. + */ + private void prepJdkStubs() { + if (!shouldParseJdk) { + return; } - String jarEntryName = jarEntry.getName(); - if (!(jarEntryName.startsWith("annotated-jdk") && jarEntryName.endsWith(".java")) - // JavaParser can't parse module-info files, so skip them. - || jarEntryName.endsWith("module-info.java")) { - continue; + URL resourceURL = atypeFactory.getClass().getResource("/annotated-jdk"); + if (stubDebug) { + System.out.printf( + "Loading JDK from class %s and url: %s%n", + atypeFactory.getClass(), resourceURL); } - if (parseAllJdkFiles || jarEntryName.endsWith("package-info.java")) { - parseJdkJarEntry(jarEntryName); - continue; + if (resourceURL == null) { + if (permitMissingJdk) { + return; + } + throw new BugInCF( + "JDK not found for type factory " + atypeFactory.getClass().getSimpleName()); + } else if (resourceURL.getProtocol().contentEquals("jar")) { + prepJdkFromJar(resourceURL); + } else if (resourceURL.getProtocol().contentEquals("file")) { + prepJdkFromFile(resourceURL); + } else { + if (permitMissingJdk) { + return; + } + throw new BugInCF( + "JDK not found in " + + resourceURL + + ". Unsupported protocol: " + + resourceURL.getProtocol()); } - int index = jarEntryName.indexOf("/share/classes/") + "/share/classes/".length(); - // "-5" is to remove ".java" from end of file name - String fqClassName = - jarEntryName.substring(index, jarEntryName.length() - 5).replace('/', '.'); - remainingJdkStubFilesJar.put(fqClassName, jarEntryName); - } - if (stubDebug) { - String factoryClass = atypeFactory.getClass().getSimpleName().toString(); - String jarFileURL = connection.getJarFileURL().toString(); - System.out.printf( - "Contents of remainingJdkStubFilesJar for %s from %s:%n", factoryClass, jarFileURL); - printSortedIndented(remainingJdkStubFilesJar.keySet()); - System.out.printf( - "End of remainingJdkStubFilesJar for %s from %s.%n", factoryClass, jarFileURL); - - System.out.printf("Contents of %s:%n", jarFileURL); - assert jarFileURL.startsWith("file:"); - ProcessBuilder pb = - new ProcessBuilder( - "/bin/sh", "-c", "jar tf '" + jarFileURL.substring(5) + "' | LC_ALL=C sort"); - pb.redirectOutput(Redirect.INHERIT); - pb.redirectError(Redirect.INHERIT); - Process p = pb.start(); + } + + /** + * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFiles}, from + * file name to the class contained with in it. Also, parses all package-info.java files. + * + * @param jdkDirectory the URL pointing to the JDK directory + */ + private void prepJdkFromFile(URL jdkDirectory) { + Path root; try { - p.waitFor(); - } catch (InterruptedException e) { - // do nothing + root = Paths.get(jdkDirectory.toURI()); + } catch (URISyntaxException e) { + throw new BugInCF("Cannot parse URL: " + jdkDirectory.toString(), e); + } + + try (Stream walk = Files.walk(root)) { + List paths = + walk.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java")) + .collect(Collectors.toList()); + paths.sort(Path::compareTo); + for (Path path : paths) { + if (path.getFileName().toString().equals("package-info.java")) { + parseJdkStubFile(path); + continue; + } + if (path.getFileName().toString().equals("module-info.java")) { + // JavaParser can't parse module-info files, so skip them. + continue; + } + if (parseAllJdkFiles) { + parseJdkStubFile(path); + continue; + } + Path relativePath = root.relativize(path); + // The number 4 is to strip off "/src//share/classes". + Path savepath = relativePath.subpath(4, relativePath.getNameCount()); + String savepathString = savepath.toString(); + // The number 5 is to remove trailing ".java". + String savepathWithoutExtension = + savepathString.substring(0, savepathString.length() - 5); + String fqName = savepathWithoutExtension.replace(File.separatorChar, '.'); + remainingJdkStubFiles.put(fqName, path); + } + if (stubDebug) { + System.out.printf( + "Contents of remainingJdkStubFiles for %s from %s:%n", + atypeFactory.getClass().getSimpleName(), jdkDirectory); + printSortedIndented(remainingJdkStubFiles.keySet()); + System.out.printf( + "End of remainingJdkStubFiles for %s from %s.%n", + atypeFactory.getClass().getSimpleName(), jdkDirectory); + } + } catch (IOException e) { + throw new BugInCF("prepJdkFromFile(" + jdkDirectory + ")", e); + } + } + + /** + * Walk through the JDK directory and create a mapping, {@link #remainingJdkStubFilesJar}, from + * file name to the class contained with in it. Also, parses all package-info.java files. + * + * @param jdkJarfile the URL pointing to the JDK jarfile + */ + private void prepJdkFromJar(@SuppressWarnings("UnusedVariable") URL jdkJarfile) { + JarURLConnection connection = getJarURLConnectionToJdk(); + + try (JarFile jarFile = connection.getJarFile()) { + ArrayList entries = CollectionsPlume.makeArrayList(jarFile.entries()); + entries.sort(Comparator.comparing(Object::toString)); + for (JarEntry jarEntry : entries) { + // filter out directories and non-Java files + if (jarEntry.isDirectory()) { + continue; + } + String jarEntryName = jarEntry.getName(); + if (!(jarEntryName.startsWith("annotated-jdk") && jarEntryName.endsWith(".java")) + // JavaParser can't parse module-info files, so skip them. + || jarEntryName.endsWith("module-info.java")) { + continue; + } + if (parseAllJdkFiles || jarEntryName.endsWith("package-info.java")) { + parseJdkJarEntry(jarEntryName); + continue; + } + int index = jarEntryName.indexOf("/share/classes/") + "/share/classes/".length(); + // "-5" is to remove ".java" from end of file name + String fqClassName = + jarEntryName.substring(index, jarEntryName.length() - 5).replace('/', '.'); + remainingJdkStubFilesJar.put(fqClassName, jarEntryName); + } + if (stubDebug) { + String factoryClass = atypeFactory.getClass().getSimpleName().toString(); + String jarFileURL = connection.getJarFileURL().toString(); + System.out.printf( + "Contents of remainingJdkStubFilesJar for %s from %s:%n", + factoryClass, jarFileURL); + printSortedIndented(remainingJdkStubFilesJar.keySet()); + System.out.printf( + "End of remainingJdkStubFilesJar for %s from %s.%n", + factoryClass, jarFileURL); + + System.out.printf("Contents of %s:%n", jarFileURL); + assert jarFileURL.startsWith("file:"); + ProcessBuilder pb = + new ProcessBuilder( + "/bin/sh", + "-c", + "jar tf '" + jarFileURL.substring(5) + "' | LC_ALL=C sort"); + pb.redirectOutput(Redirect.INHERIT); + pb.redirectError(Redirect.INHERIT); + Process p = pb.start(); + try { + p.waitFor(); + } catch (InterruptedException e) { + // do nothing + } + System.out.flush(); + SystemPlume.sleep(1); + System.out.printf("End of %s.%n", jarFileURL); + } + } catch (IOException e) { + throw new BugInCF("Cannot open the jar file " + connection.getJarFileURL(), e); } - System.out.flush(); - SystemPlume.sleep(1); - System.out.printf("End of %s.%n", jarFileURL); - } - } catch (IOException e) { - throw new BugInCF("Cannot open the jar file " + connection.getJarFileURL(), e); } - } - - /** - * This method is invoked each time before {@link AnnotationFileParser} processes a top-level - * type. - * - * @param typeName the fully qualified name of the top-level type - */ - void preProcessTopLevelType(String typeName) { - boolean added = processingClasses.add(typeName); - if (!added) { - throw new BugInCF( - "Trying to process type " + typeName + " which is already being processed."); + + /** + * This method is invoked each time before {@link AnnotationFileParser} processes a top-level + * type. + * + * @param typeName the fully qualified name of the top-level type + */ + void preProcessTopLevelType(String typeName) { + boolean added = processingClasses.add(typeName); + if (!added) { + throw new BugInCF( + "Trying to process type " + typeName + " which is already being processed."); + } } - } - - /** - * This method is invoked each time after {@link AnnotationFileParser} processes a top-level type. - * - * @param typeName the fully qualified name of the top-level type - */ - void postProcessTopLevelType(String typeName) { - boolean removed = processingClasses.remove(typeName); - if (!removed) { - throw new BugInCF("Cannot find the processing record for type " + typeName); + + /** + * This method is invoked each time after {@link AnnotationFileParser} processes a top-level + * type. + * + * @param typeName the fully qualified name of the top-level type + */ + void postProcessTopLevelType(String typeName) { + boolean removed = processingClasses.remove(typeName); + if (!removed) { + throw new BugInCF("Cannot find the processing record for type " + typeName); + } } - } - - /** - * Print the strings, in order, each on its own line, indented by two spaces. - * - * @param strings a collection of strings - */ - private void printSortedIndented(Collection strings) { - List stringList = new ArrayList<>(strings); - stringList.sort(String::compareTo); - for (String s : stringList) { - System.out.printf(" %s%n", s); + + /** + * Print the strings, in order, each on its own line, indented by two spaces. + * + * @param strings a collection of strings + */ + private void printSortedIndented(Collection strings) { + List stringList = new ArrayList<>(strings); + stringList.sort(String::compareTo); + for (String s : stringList) { + System.out.printf(" %s%n", s); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java index 0cae4b8fac4..5dc2655555e 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java @@ -56,36 +56,7 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; -import java.io.File; -import java.io.InputStream; -import java.lang.annotation.Target; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; + import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -118,6 +89,38 @@ import org.plumelib.util.IPair; import org.plumelib.util.SystemPlume; +import java.io.File; +import java.io.InputStream; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; + // From an implementation perspective, this class represents a single annotation file (stub file or // ajava file), notably its annotated types and its declaration annotations. // From a client perspective, it has static methods as described below in the Javadoc. @@ -142,3205 +145,3318 @@ */ public class AnnotationFileParser { - /** - * The type of file being parsed: stub file or ajava file. Also indicates its source, such as from - * the JDK, built in, or from the command line. - * - *

          Non-JDK stub files override JDK stub files. (Ordinarily, if two stubs are provided, they are - * merged.) - * - *

          For a built-in stub file, - * - *

            - *
          • private declarations are ignored, - *
          • some warning messages are not issued, and - *
          - */ - private final AnnotationFileType fileType; - - /** - * If parsing an ajava file, represents the javac tree for the compilation root of the file being - * parsed. - */ - private CompilationUnitTree root; - - /** - * Whether to print warnings about types/members that were not found. The warning states that a - * class/field in the file is not found on the user's real classpath. Since the file may contain - * packages that are not on the classpath, this can be OK, so default to false. - */ - private final boolean warnIfNotFound; - - /** - * Whether to ignore missing classes even when warnIfNotFound is set to true. This allows the - * files to contain classes not in the classpath (even if another class in the classpath has the - * same package), but still warn if members of the class (methods, fields) are missing. This - * option does nothing unless warnIfNotFound is also set. - */ - private final boolean warnIfNotFoundIgnoresClasses; - - /** Whether to print warnings about stub files that overwrite annotations from bytecode. */ - private final boolean warnIfStubOverwritesBytecode; - - /** - * Whether to print warnings about stub files that are redundant with annotations from bytecode. - */ - private final boolean warnIfStubRedundantWithBytecode; - - /** The diagnostic kind for stub file warnings: NOTE or WARNING. */ - private final Diagnostic.Kind stubWarnDiagnosticKind; - - /** Whether to print verbose debugging messages. */ - private final boolean debugAnnotationFileParser; - - /** The name of the file being processed; used only for diagnostic messages. */ - private final String filename; - - /** - * The AST of the parsed file that this class is processing. May be null if there was a problem - * parsing the file. (TODO: Should the Checker Framework just halt in that case?) - */ - // Not final in order to accommodate a default value. - private @Nullable StubUnit stubUnit; - - /** The processing environment */ - private final ProcessingEnvironment processingEnv; - - /** The type factory */ - private final AnnotatedTypeFactory atypeFactory; - - /** The element utilities */ - private final Elements elements; - - /** The manager that controls the stub file parsing process. */ - private final AnnotationFileElementTypes fileElementTypes; - - /** - * The set of annotations found in the file. Keys are both fully-qualified and simple names. There - * are two entries for each annotation: the annotation's simple name and its fully-qualified name. - * - *

          The map is populated from import statements and also by {@link #getAnnotation( - * AnnotationExpr, Map)} for annotations that are used fully-qualified. - * - * @see #getImportedAnnotations - */ - private Map allAnnotations; + /** + * The type of file being parsed: stub file or ajava file. Also indicates its source, such as + * from the JDK, built in, or from the command line. + * + *

          Non-JDK stub files override JDK stub files. (Ordinarily, if two stubs are provided, they + * are merged.) + * + *

          For a built-in stub file, + * + *

            + *
          • private declarations are ignored, + *
          • some warning messages are not issued, and + *
          + */ + private final AnnotationFileType fileType; + + /** + * If parsing an ajava file, represents the javac tree for the compilation root of the file + * being parsed. + */ + private CompilationUnitTree root; + + /** + * Whether to print warnings about types/members that were not found. The warning states that a + * class/field in the file is not found on the user's real classpath. Since the file may contain + * packages that are not on the classpath, this can be OK, so default to false. + */ + private final boolean warnIfNotFound; - /** - * A list of the fully-qualified names of enum constants and static fields with constant values - * that have been imported. - */ - private final List<@FullyQualifiedName String> importedConstants = new ArrayList<>(); + /** + * Whether to ignore missing classes even when warnIfNotFound is set to true. This allows the + * files to contain classes not in the classpath (even if another class in the classpath has the + * same package), but still warn if members of the class (methods, fields) are missing. This + * option does nothing unless warnIfNotFound is also set. + */ + private final boolean warnIfNotFoundIgnoresClasses; - /** A map of imported fully-qualified type names to type elements. */ - private final Map importedTypes = new HashMap<>(); + /** Whether to print warnings about stub files that overwrite annotations from bytecode. */ + private final boolean warnIfStubOverwritesBytecode; - /** The annotation {@code @FromStubFile}. */ - private final AnnotationMirror fromStubFileAnno; + /** + * Whether to print warnings about stub files that are redundant with annotations from bytecode. + */ + private final boolean warnIfStubRedundantWithBytecode; - /** - * List of AnnotatedTypeMirrors for class or method type parameters that are in scope of the - * elements currently parsed. - */ - private final List typeParameters = new ArrayList<>(); + /** The diagnostic kind for stub file warnings: NOTE or WARNING. */ + private final Diagnostic.Kind stubWarnDiagnosticKind; - /** - * The annotations on the declared package of the complation unit being processed. Contains null - * if not processing a compilation unit or if the file has no declared package. - */ - private @Nullable List<@Nullable AnnotationExpr> packageAnnos; + /** Whether to print verbose debugging messages. */ + private final boolean debugAnnotationFileParser; - // The following variables are stored in the AnnotationFileParser because otherwise they would - // need to be passed through everywhere, which would be verbose. + /** The name of the file being processed; used only for diagnostic messages. */ + private final String filename; - /** - * The name of the type that is currently being parsed. After processing a package declaration but - * before processing a type declaration, the type part of this may be null. - * - *

          It is used both for resolving symbols and for error messages. - */ - private FqName typeBeingParsed; + /** + * The AST of the parsed file that this class is processing. May be null if there was a problem + * parsing the file. (TODO: Should the Checker Framework just halt in that case?) + */ + // Not final in order to accommodate a default value. + private @Nullable StubUnit stubUnit; - /** - * Contains the annotations of the file currently being processed, or null if not currently - * processing a file. The {@code process*} methods side-effect this data structure. - */ - private @Nullable AnnotationFileAnnotations annotationFileAnnos; + /** The processing environment */ + private final ProcessingEnvironment processingEnv; - /** The line separator. */ - private static final String LINE_SEPARATOR = System.lineSeparator().intern(); + /** The type factory */ + private final AnnotatedTypeFactory atypeFactory; - /** Whether or not the {@code -AmergeStubsWithSource} command-line argument was passed. */ - private final boolean mergeStubsWithSource; + /** The element utilities */ + private final Elements elements; - /** - * The result of calling AnnotationFileParser.parse: the annotated types and declaration - * annotations from the file. - */ - public static class AnnotationFileAnnotations { + /** The manager that controls the stub file parsing process. */ + private final AnnotationFileElementTypes fileElementTypes; /** - * Map from element to its type as declared in the annotation file. + * The set of annotations found in the file. Keys are both fully-qualified and simple names. + * There are two entries for each annotation: the annotation's simple name and its + * fully-qualified name. * - *

          This is a fine-grained mapping that contains all sorts of elements; contrast with {@link - * #fakeOverrides}. + *

          The map is populated from import statements and also by {@link #getAnnotation( + * AnnotationExpr, Map)} for annotations that are used fully-qualified. + * + * @see #getImportedAnnotations + */ + private Map allAnnotations; + + /** + * A list of the fully-qualified names of enum constants and static fields with constant values + * that have been imported. + */ + private final List<@FullyQualifiedName String> importedConstants = new ArrayList<>(); + + /** A map of imported fully-qualified type names to type elements. */ + private final Map importedTypes = new HashMap<>(); + + /** The annotation {@code @FromStubFile}. */ + private final AnnotationMirror fromStubFileAnno; + + /** + * List of AnnotatedTypeMirrors for class or method type parameters that are in scope of the + * elements currently parsed. + */ + private final List typeParameters = new ArrayList<>(); + + /** + * The annotations on the declared package of the complation unit being processed. Contains null + * if not processing a compilation unit or if the file has no declared package. */ - public final Map atypes = new HashMap<>(); + private @Nullable List<@Nullable AnnotationExpr> packageAnnos; + + // The following variables are stored in the AnnotationFileParser because otherwise they would + // need to be passed through everywhere, which would be verbose. /** - * Map from a name (actually declaration element string) to the set of declaration annotations - * on it, as written in the annotation file. + * The name of the type that is currently being parsed. After processing a package declaration + * but before processing a type declaration, the type part of this may be null. * - *

          Map keys cannot be Element, because a different Element appears in the annotation files - * than in the real files. So, map keys are the verbose element name, as returned by - * ElementUtils.getQualifiedName. + *

          It is used both for resolving symbols and for error messages. */ - public final Map declAnnos = new HashMap<>(1); + private FqName typeBeingParsed; /** - * Map from a method element to all the fake overrides of it. Given a key {@code ee}, the fake - * overrides are always in subtypes of {@code ee.getEnclosingElement()}, which is the same as - * {@code ee.getReceiverType()}. + * Contains the annotations of the file currently being processed, or null if not currently + * processing a file. The {@code process*} methods side-effect this data structure. */ - public final Map>> - fakeOverrides = new HashMap<>(1); + private @Nullable AnnotationFileAnnotations annotationFileAnnos; + + /** The line separator. */ + private static final String LINE_SEPARATOR = System.lineSeparator().intern(); - /** Maps fully qualified record name to information in the stub file. */ - public final Map records = new HashMap<>(); - } + /** Whether or not the {@code -AmergeStubsWithSource} command-line argument was passed. */ + private final boolean mergeStubsWithSource; - /** Information about a record from a stub file. */ - public static class RecordStub { /** - * A map from name to record component. It must have deterministic insertion/iteration order: - * the order that they are declared in the record header. + * The result of calling AnnotationFileParser.parse: the annotated types and declaration + * annotations from the file. */ - public final Map componentsByName; + public static class AnnotationFileAnnotations { + + /** + * Map from element to its type as declared in the annotation file. + * + *

          This is a fine-grained mapping that contains all sorts of elements; contrast with + * {@link #fakeOverrides}. + */ + public final Map atypes = new HashMap<>(); + + /** + * Map from a name (actually declaration element string) to the set of declaration + * annotations on it, as written in the annotation file. + * + *

          Map keys cannot be Element, because a different Element appears in the annotation + * files than in the real files. So, map keys are the verbose element name, as returned by + * ElementUtils.getQualifiedName. + */ + public final Map declAnnos = new HashMap<>(1); + + /** + * Map from a method element to all the fake overrides of it. Given a key {@code ee}, the + * fake overrides are always in subtypes of {@code ee.getEnclosingElement()}, which is the + * same as {@code ee.getReceiverType()}. + */ + public final Map>> + fakeOverrides = new HashMap<>(1); + + /** Maps fully qualified record name to information in the stub file. */ + public final Map records = new HashMap<>(); + } + + /** Information about a record from a stub file. */ + public static class RecordStub { + /** + * A map from name to record component. It must have deterministic insertion/iteration + * order: the order that they are declared in the record header. + */ + public final Map componentsByName; + + /** + * If the canonical constructor is given in the stubs, the annotated types (in component + * declaration order) for the constructor. Null if not present in the stubs. + */ + public @MonotonicNonNull List componentsInCanonicalConstructor; + + /** + * Creates a new RecordStub. + * + * @param componentsByName a map from name to record component. It must have deterministic + * insertion/iteration order: the order that they are declared in the record header. + */ + public RecordStub(Map componentsByName) { + this.componentsByName = componentsByName; + } + + /** + * Returns the annotated types for the parameters to the canonical constructor. This is + * either from explicit annotations on the constructor in the stubs, otherwise it's taken + * from the annotations on the record components in the stubs. + * + * @return the annotated types for the parameters to the canonical constructor + */ + public List getComponentsInCanonicalConstructor() { + if (componentsInCanonicalConstructor != null) { + return componentsInCanonicalConstructor; + } else { + return CollectionsPlume.mapList(c -> c.type, componentsByName.values()); + } + } + } /** - * If the canonical constructor is given in the stubs, the annotated types (in component - * declaration order) for the constructor. Null if not present in the stubs. + * Information about a record component: its type, and whether there was an accessor in the + * stubs for that component. That is, for a component "foo" was there a method named exactly + * "foo()" in the stubs. If so, annotations on that accessor will take precedence over + * annotations that would otherwise be copied from the component in the stubs to the acessor. */ - public @MonotonicNonNull List componentsInCanonicalConstructor; + public static class RecordComponentStub { + /** The type of the record component. */ + public final AnnotatedTypeMirror type; + + /** + * The set of all annotations on the declaration of the record component. If applicable + * these will be copied to the corresponding field, accessor method, and compact canonical + * constructor parameter. + */ + private final AnnotationMirrorSet allAnnotations; + + /** Whether this component has an accessor of exactly the same name in the stubs file. */ + private boolean hasAccessorInStubs = false; + + /** + * Creates a new RecordComponentStub with the given type. + * + * @param type the type of the record component + * @param allAnnotations the declaration annotations on the component + */ + public RecordComponentStub(AnnotatedTypeMirror type, AnnotationMirrorSet allAnnotations) { + this.type = type; + this.allAnnotations = allAnnotations; + } + + /** + * Get the record component annotations that are applicable to the given element kind. + * + * @param elementKind the element kind to apply to (e.g., FIELD, METHOD) + * @return the set of annotations from the component that apply + */ + public AnnotationMirrorSet getAnnotationsForTarget(ElementKind elementKind) { + AnnotationMirrorSet filtered = new AnnotationMirrorSet(); + for (AnnotationMirror annoMirror : allAnnotations) { + Target target = + annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); + // Only add the declaration annotation if the annotation applies to the element. + if (AnnotationUtils.getElementKindsForTarget(target).contains(elementKind)) { + // `annoMirror` is applicable to `elt` + filtered.add(annoMirror); + } + } + return filtered; + } + + /** + * Returns whether there is an accessor in a stub file. + * + * @return true if some stub file contains an accessor for this record component + */ + public boolean hasAccessorInStubs() { + return hasAccessorInStubs; + } + } /** - * Creates a new RecordStub. + * Create a new AnnotationFileParser object, which will parse and extract annotations from the + * given file. * - * @param componentsByName a map from name to record component. It must have deterministic - * insertion/iteration order: the order that they are declared in the record header. + * @param filename name of annotation file, used only for diagnostic messages + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param fileType the type of file being parsed (stub file or ajava file) and its source + * @param fileElementTypes the manager that controls the stub file parsing process */ - public RecordStub(Map componentsByName) { - this.componentsByName = componentsByName; + private AnnotationFileParser( + String filename, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileType fileType, + AnnotationFileElementTypes fileElementTypes) { + this.filename = filename; + this.atypeFactory = atypeFactory; + this.processingEnv = processingEnv; + this.elements = processingEnv.getElementUtils(); + this.fileType = fileType; + this.root = null; + this.fileElementTypes = fileElementTypes; + + // TODO: This should use SourceChecker.getOptions() to allow + // setting these flags per checker. + Map options = processingEnv.getOptions(); + boolean stubWarnIfNotFoundOption = options.containsKey("stubWarnIfNotFound"); + boolean stubNoWarnIfNotFoundOption = options.containsKey("stubNoWarnIfNotFound"); + if (stubWarnIfNotFoundOption && stubNoWarnIfNotFoundOption) { + throw new UserError( + "Do not supply both -AstubWarnIfNotFound and -AstubNoWarnIfNotFound."); + } + this.warnIfNotFound = + stubWarnIfNotFoundOption + || (fileType.isCommandLine() && !stubNoWarnIfNotFoundOption); + + this.warnIfNotFoundIgnoresClasses = options.containsKey("stubWarnIfNotFoundIgnoresClasses"); + this.warnIfStubOverwritesBytecode = options.containsKey("stubWarnIfOverwritesBytecode"); + this.warnIfStubRedundantWithBytecode = + options.containsKey("stubWarnIfRedundantWithBytecode") + && atypeFactory.shouldWarnIfStubRedundantWithBytecode(); + this.stubWarnDiagnosticKind = + options.containsKey("stubWarnNote") + ? Diagnostic.Kind.NOTE + : Diagnostic.Kind.WARNING; + this.debugAnnotationFileParser = options.containsKey("stubDebug"); + + this.fromStubFileAnno = AnnotationBuilder.fromClass(elements, FromStubFile.class); + + this.mergeStubsWithSource = atypeFactory.getChecker().hasOption("mergeStubsWithSource"); } /** - * Returns the annotated types for the parameters to the canonical constructor. This is either - * from explicit annotations on the constructor in the stubs, otherwise it's taken from the - * annotations on the record components in the stubs. + * Sets the root of the file currently being parsed to {@code root}. * - * @return the annotated types for the parameters to the canonical constructor + * @param root compilation unit for the file being parsed */ - public List getComponentsInCanonicalConstructor() { - if (componentsInCanonicalConstructor != null) { - return componentsInCanonicalConstructor; - } else { - return CollectionsPlume.mapList(c -> c.type, componentsByName.values()); - } + private void setRoot(CompilationUnitTree root) { + this.root = root; } - } - - /** - * Information about a record component: its type, and whether there was an accessor in the stubs - * for that component. That is, for a component "foo" was there a method named exactly "foo()" in - * the stubs. If so, annotations on that accessor will take precedence over annotations that would - * otherwise be copied from the component in the stubs to the acessor. - */ - public static class RecordComponentStub { - /** The type of the record component. */ - public final AnnotatedTypeMirror type; /** - * The set of all annotations on the declaration of the record component. If applicable these - * will be copied to the corresponding field, accessor method, and compact canonical constructor - * parameter. + * All annotations defined in the package (but not those nested within classes in the package). + * Keys are both fully-qualified and simple names. + * + * @param packageElement a package + * @return a map from annotation name to TypeElement */ - private final AnnotationMirrorSet allAnnotations; - - /** Whether this component has an accessor of exactly the same name in the stubs file. */ - private boolean hasAccessorInStubs = false; + public static Map annosInPackage(PackageElement packageElement) { + return createNameToAnnotationMap( + ElementFilter.typesIn(packageElement.getEnclosedElements())); + } /** - * Creates a new RecordComponentStub with the given type. + * All annotations declared (directly) within a class. Keys are both fully-qualified and simple + * names. * - * @param type the type of the record component - * @param allAnnotations the declaration annotations on the component + * @param typeElement a type + * @return a map from annotation name to TypeElement */ - public RecordComponentStub(AnnotatedTypeMirror type, AnnotationMirrorSet allAnnotations) { - this.type = type; - this.allAnnotations = allAnnotations; + public static Map annosInType(TypeElement typeElement) { + return createNameToAnnotationMap(ElementFilter.typesIn(typeElement.getEnclosedElements())); } /** - * Get the record component annotations that are applicable to the given element kind. + * All annotations declared within any of the given elements. * - * @param elementKind the element kind to apply to (e.g., FIELD, METHOD) - * @return the set of annotations from the component that apply + * @param typeElements the elements whose annotations to retrieve + * @return a map from annotation names (both fully-qualified and simple names) to TypeElement */ - public AnnotationMirrorSet getAnnotationsForTarget(ElementKind elementKind) { - AnnotationMirrorSet filtered = new AnnotationMirrorSet(); - for (AnnotationMirror annoMirror : allAnnotations) { - Target target = annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); - // Only add the declaration annotation if the annotation applies to the element. - if (AnnotationUtils.getElementKindsForTarget(target).contains(elementKind)) { - // `annoMirror` is applicable to `elt` - filtered.add(annoMirror); + public static Map createNameToAnnotationMap( + List typeElements) { + Map result = new HashMap<>(); + for (TypeElement typeElm : typeElements) { + if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) { + putIfAbsent(result, typeElm.getSimpleName().toString(), typeElm); + putIfAbsent(result, typeElm.getQualifiedName().toString(), typeElm); + } } - } - return filtered; + return result; } - - /** - * Returns whether there is an accessor in a stub file. - * - * @return true if some stub file contains an accessor for this record component - */ - public boolean hasAccessorInStubs() { - return hasAccessorInStubs; - } - } - - /** - * Create a new AnnotationFileParser object, which will parse and extract annotations from the - * given file. - * - * @param filename name of annotation file, used only for diagnostic messages - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param fileType the type of file being parsed (stub file or ajava file) and its source - * @param fileElementTypes the manager that controls the stub file parsing process - */ - private AnnotationFileParser( - String filename, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileType fileType, - AnnotationFileElementTypes fileElementTypes) { - this.filename = filename; - this.atypeFactory = atypeFactory; - this.processingEnv = processingEnv; - this.elements = processingEnv.getElementUtils(); - this.fileType = fileType; - this.root = null; - this.fileElementTypes = fileElementTypes; - - // TODO: This should use SourceChecker.getOptions() to allow - // setting these flags per checker. - Map options = processingEnv.getOptions(); - boolean stubWarnIfNotFoundOption = options.containsKey("stubWarnIfNotFound"); - boolean stubNoWarnIfNotFoundOption = options.containsKey("stubNoWarnIfNotFound"); - if (stubWarnIfNotFoundOption && stubNoWarnIfNotFoundOption) { - throw new UserError("Do not supply both -AstubWarnIfNotFound and -AstubNoWarnIfNotFound."); - } - this.warnIfNotFound = - stubWarnIfNotFoundOption || (fileType.isCommandLine() && !stubNoWarnIfNotFoundOption); - - this.warnIfNotFoundIgnoresClasses = options.containsKey("stubWarnIfNotFoundIgnoresClasses"); - this.warnIfStubOverwritesBytecode = options.containsKey("stubWarnIfOverwritesBytecode"); - this.warnIfStubRedundantWithBytecode = - options.containsKey("stubWarnIfRedundantWithBytecode") - && atypeFactory.shouldWarnIfStubRedundantWithBytecode(); - this.stubWarnDiagnosticKind = - options.containsKey("stubWarnNote") ? Diagnostic.Kind.NOTE : Diagnostic.Kind.WARNING; - this.debugAnnotationFileParser = options.containsKey("stubDebug"); - - this.fromStubFileAnno = AnnotationBuilder.fromClass(elements, FromStubFile.class); - - this.mergeStubsWithSource = atypeFactory.getChecker().hasOption("mergeStubsWithSource"); - } - - /** - * Sets the root of the file currently being parsed to {@code root}. - * - * @param root compilation unit for the file being parsed - */ - private void setRoot(CompilationUnitTree root) { - this.root = root; - } - - /** - * All annotations defined in the package (but not those nested within classes in the package). - * Keys are both fully-qualified and simple names. - * - * @param packageElement a package - * @return a map from annotation name to TypeElement - */ - public static Map annosInPackage(PackageElement packageElement) { - return createNameToAnnotationMap(ElementFilter.typesIn(packageElement.getEnclosedElements())); - } - - /** - * All annotations declared (directly) within a class. Keys are both fully-qualified and simple - * names. - * - * @param typeElement a type - * @return a map from annotation name to TypeElement - */ - public static Map annosInType(TypeElement typeElement) { - return createNameToAnnotationMap(ElementFilter.typesIn(typeElement.getEnclosedElements())); - } - - /** - * All annotations declared within any of the given elements. - * - * @param typeElements the elements whose annotations to retrieve - * @return a map from annotation names (both fully-qualified and simple names) to TypeElement - */ - public static Map createNameToAnnotationMap(List typeElements) { - Map result = new HashMap<>(); - for (TypeElement typeElm : typeElements) { - if (typeElm.getKind() == ElementKind.ANNOTATION_TYPE) { - putIfAbsent(result, typeElm.getSimpleName().toString(), typeElm); - putIfAbsent(result, typeElm.getQualifiedName().toString(), typeElm); - } - } - return result; - } - - /** - * Get all members of a Type that are importable in an annotation file. Currently these are values - * of enums, or compile time constants. - * - * @param typeElement the type whose members to return - * @return a list of fully-qualified member names - */ - private static List<@FullyQualifiedName String> getImportableMembers(TypeElement typeElement) { - List memberElements = - ElementFilter.fieldsIn(typeElement.getEnclosedElements()); - List<@FullyQualifiedName String> result = new ArrayList<>(); - for (VariableElement varElement : memberElements) { - if (varElement.getConstantValue() != null - || varElement.getKind() == ElementKind.ENUM_CONSTANT) { - @SuppressWarnings("signature") // string concatenation - @FullyQualifiedName String fqName = - typeElement.getQualifiedName().toString() + "." + varElement.getSimpleName().toString(); - result.add(fqName); - } - } - return result; - } - - /** - * Returns all annotations imported by the annotation file, as a value for {@link - * #allAnnotations}. Note that this also modifies {@link #importedConstants} and {@link - * #importedTypes}. - * - *

          This method misses annotations that are not imported. The {@link #getAnnotation} method - * compensates for this deficiency by adding any fully-qualified annotation that it encounters. - * - * @return a map from names to TypeElement, for all annotations imported by the annotation file. - * Two entries for each annotation: one for the simple name and another for the - * fully-qualified name, with the same value. - * @see #allAnnotations - */ - private Map getImportedAnnotations() { - Map result = new HashMap<>(); - - // TODO: The size can be greater than 1, but this ignores all but the first element. - assert !stubUnit.getCompilationUnits().isEmpty(); - CompilationUnit cu = stubUnit.getCompilationUnits().get(0); - - if (cu.getImports() == null) { - return result; - } - - for (ImportDeclaration importDecl : cu.getImports()) { - try { - if (importDecl.isAsterisk()) { - @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: - // com.github.javaparser.ast.expr.Name inherits toString, - // so there can be no annotation for it - ) - @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); - if (importDecl.isStatic()) { - // Wildcard import of members of a type (class or interface) - TypeElement element = getTypeElement(imported, "imported type not found", importDecl); - if (element != null) { - // Find nested annotations - // Find compile time constant fields, or values of an enum - putAllNew(result, annosInType(element)); - importedConstants.addAll(getImportableMembers(element)); - addEnclosingTypesToImportedTypes(element); - } - - } else { - // Wildcard import of members of a package - PackageElement element = findPackage(imported, importDecl); - if (element != null) { - putAllNew(result, annosInPackage(element)); - addEnclosingTypesToImportedTypes(element); - } - } - } else { - // A single (non-wildcard) import. - @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is - // @FullyQualifiedName - ) - @FullyQualifiedName String imported = importDecl.getNameAsString(); - - TypeElement importType = elements.getTypeElement(imported); - if (importType == null && !importDecl.isStatic()) { - // Class or nested class (according to JSL), but we can't resolve - - stubWarnNotFound(importDecl, "imported type not found: " + imported); - } else if (importType == null) { - // static import of field or method. - - IPair<@FullyQualifiedName String, String> typeParts = - AnnotationFileUtil.partitionQualifiedName(imported); - String type = typeParts.first; - String fieldName = typeParts.second; - TypeElement enclType = - getTypeElement( - type, - String.format("enclosing type of static field %s not found", fieldName), - importDecl); - - if (enclType != null) { - // Don't use findFieldElement(enclType, fieldName), because we don't - // want a warning, imported might be a method. - for (VariableElement field : ElementUtils.getAllFieldsIn(enclType, elements)) { - // field.getSimpleName() is a CharSequence, not a String - if (fieldName.equals(field.getSimpleName().toString())) { - importedConstants.add(imported); - } - } + + /** + * Get all members of a Type that are importable in an annotation file. Currently these are + * values of enums, or compile time constants. + * + * @param typeElement the type whose members to return + * @return a list of fully-qualified member names + */ + private static List<@FullyQualifiedName String> getImportableMembers(TypeElement typeElement) { + List memberElements = + ElementFilter.fieldsIn(typeElement.getEnclosedElements()); + List<@FullyQualifiedName String> result = new ArrayList<>(); + for (VariableElement varElement : memberElements) { + if (varElement.getConstantValue() != null + || varElement.getKind() == ElementKind.ENUM_CONSTANT) { + @SuppressWarnings("signature") // string concatenation + @FullyQualifiedName String fqName = + typeElement.getQualifiedName().toString() + + "." + + varElement.getSimpleName().toString(); + result.add(fqName); } + } + return result; + } - } else if (importType.getKind() == ElementKind.ANNOTATION_TYPE) { - // Single annotation or nested annotation - TypeElement annoElt = elements.getTypeElement(imported); - if (annoElt != null) { - putIfAbsent(result, annoElt.getSimpleName().toString(), annoElt); - importedTypes.put(annoElt.getSimpleName().toString(), annoElt); - } else { - stubWarnNotFound(importDecl, "could not load import: " + imported); - } - } else { - // Class or nested class - // TODO: Is this needed? - importedConstants.add(imported); - TypeElement element = getTypeElement(imported, "imported type not found", importDecl); - importedTypes.put(element.getSimpleName().toString(), element); - } - } - } catch (AssertionError error) { - stubWarnNotFound(importDecl, error.toString()); - } - } - return result; - } - - // If a member is imported, then consider every containing class to also be imported. - private void addEnclosingTypesToImportedTypes(Element element) { - for (Element enclosedEle : element.getEnclosedElements()) { - if (enclosedEle.getKind().isClass()) { - importedTypes.put(enclosedEle.getSimpleName().toString(), (TypeElement) enclosedEle); - } - } - } - - /** - * The main entry point. Parse a stub file and side-effects the {@code annotationFileAnnos} - * argument. - * - * @param filename name of stub file, used only for diagnostic messages - * @param inputStream of stub file to parse - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param annotationFileAnnos annotations from the annotation file; side-effected by this method - * @param fileType the annotation file type and source - * @param fileElementTypes the manager that controls the stub file parsing process - */ - public static void parseStubFile( - String filename, - InputStream inputStream, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileAnnotations annotationFileAnnos, - AnnotationFileType fileType, - AnnotationFileElementTypes fileElementTypes) { - AnnotationFileParser afp = - new AnnotationFileParser(filename, atypeFactory, processingEnv, fileType, fileElementTypes); - try { - afp.parseStubUnit(inputStream); - afp.process(annotationFileAnnos); - } catch (ParseProblemException e) { - for (Problem p : e.getProblems()) { - afp.warn(null, p.getVerboseMessage()); - } - } catch (Throwable t) { - afp.warn(null, "Parse problem: " + t); - } - } - - /** - * The main entry point when parsing an ajava file. Parses an ajava file and side-effects the last - * two arguments. - * - * @param filename name of ajava file, used only for diagnostic messages - * @param inputStream of ajava file to parse - * @param root javac tree for the file to be parsed - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param ajavaAnnos annotations from the ajava file; side-effected by this method - * @param fileElementTypes the manager that controls the stub file parsing process - */ - public static void parseAjavaFile( - String filename, - InputStream inputStream, - CompilationUnitTree root, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileAnnotations ajavaAnnos, - AnnotationFileElementTypes fileElementTypes) { - AnnotationFileParser afp = - new AnnotationFileParser( - filename, atypeFactory, processingEnv, AnnotationFileType.AJAVA, fileElementTypes); - try { - afp.parseStubUnit(inputStream); - JavaParserUtil.concatenateAddedStringLiterals(afp.stubUnit); - afp.setRoot(root); - afp.process(ajavaAnnos); - } catch (ParseProblemException e) { - for (Problem p : e.getProblems()) { - afp.warn(null, filename + ": " + p.getVerboseMessage()); - } - } catch (Throwable t) { - afp.warn(null, "Parse problem: " + t); - } - } - - /** - * Parse a stub file that is a part of the annotated JDK and side-effects the {@code stubAnnos} - * argument. - * - * @param filename name of stub file, used only for diagnostic messages - * @param inputStream of stub file to parse - * @param atypeFactory the type factory - * @param processingEnv the processing environment - * @param stubAnnos annotations from the stub file; side-effected by this method - * @param fileElementTypes the manager that controls the stub file parsing process - */ - public static void parseJdkFileAsStub( - String filename, - InputStream inputStream, - AnnotatedTypeFactory atypeFactory, - ProcessingEnvironment processingEnv, - AnnotationFileAnnotations stubAnnos, - AnnotationFileElementTypes fileElementTypes) { - Map options = processingEnv.getOptions(); - boolean debugAnnotationFileParser = options.containsKey("stubDebug"); - if (debugAnnotationFileParser) { - stubDebugStatic( - processingEnv, - "parseJdkFileAsStub(%s, _, %s, _, _)%n", - filename, - atypeFactory.getClass().getSimpleName()); - } - - parseStubFile( - filename, - inputStream, - atypeFactory, - processingEnv, - stubAnnos, - AnnotationFileType.JDK_STUB, - fileElementTypes); - } - - /** - * Delegate to the Stub Parser to parse the annotation file to an AST, and save it in {@link - * #stubUnit}. Also sets {@link #allAnnotations}. Does not copy annotations out of {@link - * #stubUnit}; that is done by the {@code process*} methods. - * - *

          Subsequently, all work uses the AST. - * - * @param inputStream the stream from which to read an annotation file - */ - private void parseStubUnit(InputStream inputStream) { - stubDebug( - "started parsing annotation file %s for %s", - filename, atypeFactory.getClass().getSimpleName()); - stubUnit = JavaParserUtil.parseStubUnit(inputStream); - - // getImportedAnnotations() also modifies importedConstants and importedTypes. This should - // be refactored to be nicer. - allAnnotations = getImportedAnnotations(); - if (allAnnotations.isEmpty() - && fileType.isStub() - && fileType != AnnotationFileType.AJAVA_AS_STUB) { - // Issue a warning if the stub file contains no import statements. The warning is - // incorrect if the stub file contains fully-qualified annotations. - stubWarnNotFound( - null, - String.format( - "No supported annotations found! Does stub file %s import them?", filename)); - } - // Annotations in java.lang might be used without an import statement, so add them in case. - allAnnotations.putAll(annosInPackage(findPackage("java.lang", null))); - - if (debugAnnotationFileParser) { - stubDebug( - "finished parsing annotation file %s for %s", - filename, atypeFactory.getClass().getSimpleName()); - } - } - - /** - * Process {@link #stubUnit}, which is the AST produced by {@link #parseStubUnit}. Processing - * means copying annotations from Stub Parser data structures to {@code #annotationFileAnnos}. - * - * @param annotationFileAnnos annotations from the file; side-effected by this method - */ - private void process(AnnotationFileAnnotations annotationFileAnnos) { - this.annotationFileAnnos = annotationFileAnnos; - processStubUnit(this.stubUnit); - this.annotationFileAnnos = null; - } - - /** - * Process the given StubUnit: copy its annotations to {@code #annotationFileAnnos}. - * - * @param su the StubUnit to process - */ - private void processStubUnit(StubUnit su) { - for (CompilationUnit cu : su.getCompilationUnits()) { - processCompilationUnit(cu); - } - } - - /** - * Process the given CompilationUnit: copy its annotations to {@code #annotationFileAnnos}. - * - * @param cu the CompilationUnit to process - */ - private void processCompilationUnit(CompilationUnit cu) { - - if (cu.getPackageDeclaration().isPresent()) { - PackageDeclaration pDecl = cu.getPackageDeclaration().get(); - packageAnnos = pDecl.getAnnotations(); - if (debugAnnotationFileParser - || (!warnIfNotFoundIgnoresClasses && !hasNoAnnotationFileParserWarning(packageAnnos))) { - String packageName = pDecl.getName().toString(); - if (elements.getPackageElement(packageName) == null) { - stubWarnNotFound(pDecl, "package not found: " + packageName); - } - } - processPackage(pDecl); - } else { - packageAnnos = null; - typeBeingParsed = new FqName(null, null); - } - - if (fileType.isStub()) { - if (cu.getTypes() != null) { - for (TypeDeclaration typeDeclaration : cu.getTypes()) { - Optional typeDeclName = typeDeclaration.getFullyQualifiedName(); - - typeDeclName.ifPresent(fileElementTypes::preProcessTopLevelType); - try { - // Not processing an ajava file, so ignore the return value. - processTypeDecl(typeDeclaration, null, null); - } finally { - typeDeclName.ifPresent(fileElementTypes::postProcessTopLevelType); - } - } - } - } else { - root.accept(new AjavaAnnotationCollectorVisitor(), cu); - } - - packageAnnos = null; - } - - /** - * Process the given package declaration: copy its annotations to {@code #annotationFileAnnos}. - * - * @param packDecl the package declaration to process - */ - private void processPackage(PackageDeclaration packDecl) { - assert (packDecl != null); - if (!isAnnotatedForThisChecker(packDecl.getAnnotations())) { - return; - } - String packageName = packDecl.getNameAsString(); - typeBeingParsed = new FqName(packageName, null); - Element elem = elements.getPackageElement(packageName); - // If the element lookup fails (that is, elem == null), it's because we have an annotation - // for a package that isn't on the classpath, which is fine. - if (elem != null) { - recordDeclAnnotation(elem, packDecl.getAnnotations(), packDecl); - } - // TODO: Handle atypes??? - } - - /** - * Returns true if the given program construct need not be read: it is private and one of the - * following is true: - * - *

            - *
          • It is in the annotated JDK. Private constructs can't be referenced outside of the JDK and - * might refer to types that are not accessible. - *
          • It is not an ajava file and {@code -AmergeStubsWithSource} was not supplied. As described - * at https://eisop.github.io/cf/manual/#stub-multiple-specifications, source files take - * precedence over stub files unless {@code -AmergeStubsWithSource} is supplied. As - * described at https://eisop.github.io/cf/manual/#ajava-using, source files do not take - * precedence over ajava files (when reading an ajava file, it is as if {@code - * -AmergeStubsWithSource} were supplied). - *
          - * - * @param node a declaration - * @return true if the given program construct is in the annotated JDK and is private - */ - private boolean skipNode(NodeWithAccessModifiers node) { - // Must include everything with no access modifier, because stub files are allowed to omit - // the access modifier. Also, interface methods have no access modifier, but they are still - // public. - // Must include protected JDK methods. For example, Object.clone is protected, but it - // contains annotations that apply to calls like `super.clone()` and `myArray.clone()`. - return (fileType == AnnotationFileType.BUILTIN_STUB - || (fileType.isStub() - && fileType != AnnotationFileType.AJAVA_AS_STUB - && !mergeStubsWithSource)) - && node.getModifiers().contains(Modifier.privateModifier()); - } - - /** - * Returns the string representation of {@code n}, one one line, truncated to {@code length} - * characters. - * - * @param n a JavaParser node - * @param length the maximum length of the string representation - * @return the truncated string representation of {@code n} - */ - private String javaParserNodeToStringTruncated(Node n, int length) { - String oneLine = - n.toString() - .replace("\t", " ") - .replace("\n", " ") - .replace("\r", " ") - .replaceAll(" +", " "); - if (oneLine.length() <= length) { - return oneLine; - } else { - return oneLine.substring(0, length - 3) + "..."; - } - } - - /** - * Process a type declaration: copy its annotations to {@code #annotationFileAnnos}. - * - *

          This method stores the declaration's type parameters in {@link #typeParameters}. When - * processing an ajava file, where traversal is handled externaly by a {@link - * org.checkerframework.framework.ajava.JointJavacJavaParserVisitor}, these type variables must be - * removed after processing the type's members. Otherwise, this method removes them. - * - * @param typeDecl the type declaration to process - * @param outerTypeName the name of the containing class, when processing a nested class; - * otherwise null - * @param classTree the tree corresponding to typeDecl if processing an ajava file, null otherwise - * @return a list of types variables for {@code typeDecl}. Only non-null if processing an ajava - * file, in which case the contents should be removed from {@link #typeParameters} after - * processing the type declaration's members - */ - private @Nullable List processTypeDecl( - TypeDeclaration typeDecl, @Nullable String outerTypeName, @Nullable ClassTree classTree) { - assert typeBeingParsed != null; - if (skipNode(typeDecl)) { - return null; - } - String innerName; - @FullyQualifiedName String fqTypeName; - TypeElement typeElt; - if (classTree != null) { - typeElt = TreeUtils.elementFromDeclaration(classTree); - innerName = typeElt.getQualifiedName().toString(); - typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); - fqTypeName = typeBeingParsed.toString(); - } else { - String packagePrefix = outerTypeName == null ? "" : outerTypeName + "."; - innerName = packagePrefix + typeDecl.getNameAsString(); - typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); - fqTypeName = typeBeingParsed.toString(); - typeElt = elements.getTypeElement(fqTypeName); - } - - if (!isAnnotatedForThisChecker(typeDecl.getAnnotations())) { - return null; - } - if (typeElt == null) { - if (debugAnnotationFileParser - || (!warnIfNotFoundIgnoresClasses - && !hasNoAnnotationFileParserWarning(typeDecl.getAnnotations()) - && !hasNoAnnotationFileParserWarning(packageAnnos))) { - if (elements.getAllTypeElements(fqTypeName).isEmpty()) { - stubWarnNotFound(typeDecl, "type not found: " + fqTypeName); - } else { - stubWarnNotFound( - typeDecl, - "type not found uniquely: " - + fqTypeName - + " : " - + elements.getAllTypeElements(fqTypeName)); - } - } - return null; - } - - List typeDeclTypeParameters = null; - if (typeElt.getKind() == ElementKind.ENUM) { - if (!(typeDecl instanceof EnumDeclaration)) { - warn( - typeDecl, - innerName - + " is an enum, but stub file declared it as " - + javaParserNodeToStringTruncated(typeDecl, 100)); - return null; - } - typeDeclTypeParameters = processEnum((EnumDeclaration) typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } else if (typeElt.getKind() == ElementKind.ANNOTATION_TYPE) { - if (!(typeDecl instanceof AnnotationDeclaration)) { - warn( - typeDecl, - innerName - + " is an annotation, but stub file declared it as " - + javaParserNodeToStringTruncated(typeDecl, 100)); - return null; - } - typeDeclTypeParameters = processType(typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } else if (typeDecl instanceof ClassOrInterfaceDeclaration) { - // TODO: This test is never satisfied, because it is the opposite of that on the line - // above. - if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) { - warn( - typeDecl, - innerName - + " is a class or interface, but stub file declared it as " - + javaParserNodeToStringTruncated(typeDecl, 100)); - return null; - } - typeDeclTypeParameters = processType(typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } else if (typeDecl instanceof RecordDeclaration) { - typeDeclTypeParameters = processType(typeDecl, typeElt); - typeParameters.addAll(typeDeclTypeParameters); - } // else it's an EmptyTypeDeclaration. TODO: An EmptyTypeDeclaration can have - // annotations, right? - - // If processing an ajava file, then traversal is handled by a visitor, rather than the rest - // of this method. - if (fileType == AnnotationFileType.AJAVA) { - return typeDeclTypeParameters; - } - - if (typeDecl instanceof RecordDeclaration) { - RecordDeclaration recordDecl = (RecordDeclaration) typeDecl; - NodeList recordMembers = recordDecl.getParameters(); - Map byName = - ArrayMap.newArrayMapOrLinkedHashMap(recordMembers.size()); - for (Parameter recordMember : recordMembers) { - RecordComponentStub stub = - processRecordField( - recordMember, - findFieldElement(typeElt, recordMember.getNameAsString(), recordMember)); - byName.put(recordMember.getNameAsString(), stub); - } - annotationFileAnnos.records.put( - recordDecl.getFullyQualifiedName().get(), new RecordStub(byName)); - } - - IPair>, Map>>> members = - getMembers(typeDecl, typeElt, typeDecl); - for (Map.Entry> entry : members.first.entrySet()) { - Element elt = entry.getKey(); - BodyDeclaration decl = entry.getValue(); - switch (elt.getKind()) { - case FIELD: - processField((FieldDeclaration) decl, (VariableElement) elt); - break; - case ENUM_CONSTANT: - // Enum constants can occur as fields in stubs files when their - // type has an annotation on it, e.g. see DeviceTypeTest which ends up with - // the TRACKER enum constant annotated with DefaultType: - if (decl instanceof FieldDeclaration) { - processField((FieldDeclaration) decl, (VariableElement) elt); - } else if (decl instanceof EnumConstantDeclaration) { - processEnumConstant((EnumConstantDeclaration) decl, (VariableElement) elt); - } else { - throw new BugInCF( - "unexpected decl type " - + decl.getClass() - + " for ENUM_CONSTANT kind, original: " - + decl); - } - break; - case CONSTRUCTOR: - case METHOD: - processCallableDeclaration((CallableDeclaration) decl, (ExecutableElement) elt); - break; - case CLASS: - case INTERFACE: - // Not processing an ajava file, so ignore the return value. - processTypeDecl((ClassOrInterfaceDeclaration) decl, innerName, null); - break; - case ENUM: - // Not processing an ajava file, so ignore the return value. - processTypeDecl((EnumDeclaration) decl, innerName, null); - break; - default: - /* do nothing */ - stubWarnNotFound(decl, "AnnotationFileParser ignoring: " + elt); - break; - } - } - for (Map.Entry>> entry : members.second.entrySet()) { - ExecutableElement fakeOverridden = (ExecutableElement) entry.getKey(); - List> fakeOverrideDecls = entry.getValue(); - for (BodyDeclaration bodyDecl : fakeOverrideDecls) { - processFakeOverride(fakeOverridden, (CallableDeclaration) bodyDecl, typeElt); - } - } - - if (typeDeclTypeParameters != null) { - typeParameters.removeAll(typeDeclTypeParameters); - } - - return null; - } - - /** - * Returns true if the argument contains {@code @NoAnnotationFileParserWarning}. - * - * @param aexprs collection of annotation expressions - * @return true if {@code aexprs} contains {@code @NoAnnotationFileParserWarning} - */ - private boolean hasNoAnnotationFileParserWarning(Iterable aexprs) { - if (aexprs == null) { - return false; - } - for (AnnotationExpr anno : aexprs) { - if (anno.getNameAsString().equals("NoAnnotationFileParserWarning")) { - return true; - } - } - return false; - } - - /** - * Process the type's declaration: copy its annotations to {@code #annotationFileAnnos}. Does not - * process any of its members. Returns the type's type parameter declarations. - * - * @param decl a type declaration - * @param elt the type's element - * @return the type's type parameter declarations - */ - private List processType(TypeDeclaration decl, TypeElement elt) { - - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - AnnotatedDeclaredType type = atypeFactory.fromElement(elt); - annotate(type, decl.getAnnotations(), decl); - - List typeArguments = type.getTypeArguments(); - List typeParameters; - if (decl instanceof NodeWithTypeParameters) { - typeParameters = ((NodeWithTypeParameters) decl).getTypeParameters(); - } else { - typeParameters = Collections.emptyList(); - } - - // It can be the case that args=[] and params=null, so don't crash in that case. - // if ((typeParameters == null) != (typeArguments == null)) { - // throw new Error(String.format("parseType (%s, %s): inconsistent nullness for args and - // params%n args = %s%n params = %s%n", decl, elt, typeArguments, typeParameters)); - // } - - if (debugAnnotationFileParser) { - int numParams = (typeParameters == null ? 0 : typeParameters.size()); - int numArgs = (typeArguments == null ? 0 : typeArguments.size()); - if (numParams != numArgs) { - stubDebug( - "parseType: mismatched sizes for typeParameters=%s (size %d)" - + " and typeArguments=%s (size %d);" - + " decl=%s; elt=%s (%s); type=%s (%s); typeBeingParsed=%s", - typeParameters, - numParams, - typeArguments, - numArgs, - decl.toString().replace(LINE_SEPARATOR, " "), - elt.toString().replace(LINE_SEPARATOR, " "), - elt.getClass(), - type, - type.getClass(), - typeBeingParsed); - stubDebug("proceeding despite mismatched sizes"); - } - } - - annotateTypeParameters(decl, elt, typeArguments, typeParameters); - if (decl instanceof ClassOrInterfaceDeclaration) { - annotateSupertypes((ClassOrInterfaceDeclaration) decl, type); - } - putMerge(annotationFileAnnos.atypes, elt, type); - List typeVariables = new ArrayList<>(type.getTypeArguments().size()); - for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { - if (typeV.getKind() != TypeKind.TYPEVAR) { - warn( - decl, - "expected an AnnotatedTypeVariable but found type kind " - + typeV.getKind() - + ": " - + typeV); - } else { - typeVariables.add((AnnotatedTypeVariable) typeV); - } - } - return typeVariables; - } - - /** - * Process an enum: copy its annotations to {@code #annotationFileAnnos}. Returns the enum's type - * parameter declarations. - * - * @param decl enum declaration - * @param elt element representing enum - * @return the enum's type parameter declarations - */ - private List processEnum(EnumDeclaration decl, TypeElement elt) { - - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - AnnotatedDeclaredType type = atypeFactory.fromElement(elt); - annotate(type, decl.getAnnotations(), decl); - - putMerge(annotationFileAnnos.atypes, elt, type); - List typeVariables = new ArrayList<>(type.getTypeArguments().size()); - for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { - if (typeV.getKind() != TypeKind.TYPEVAR) { - warn( - decl, - "expected an AnnotatedTypeVariable but found type kind " - + typeV.getKind() - + ": " - + typeV); - } else { - typeVariables.add((AnnotatedTypeVariable) typeV); - } - } - return typeVariables; - } - - private void annotateSupertypes( - ClassOrInterfaceDeclaration typeDecl, AnnotatedDeclaredType type) { - if (typeDecl.getExtendedTypes() != null) { - for (ClassOrInterfaceType supertype : typeDecl.getExtendedTypes()) { - AnnotatedDeclaredType annotatedSupertype = - findAnnotatedType(supertype, type.directSupertypes(), typeDecl); - if (annotatedSupertype == null) { - warn( - typeDecl, - "stub file does not match bytecode: " - + "could not find direct superclass " - + supertype - + " from type " - + type); - } else { - annotate(annotatedSupertype, supertype, null, typeDecl); - } - } - } - if (typeDecl.getImplementedTypes() != null) { - for (ClassOrInterfaceType supertype : typeDecl.getImplementedTypes()) { - AnnotatedDeclaredType annotatedSupertype = - findAnnotatedType(supertype, type.directSupertypes(), typeDecl); - if (annotatedSupertype == null) { - warn( - typeDecl, - "stub file does not match bytecode: " - + "could not find direct superinterface " - + supertype - + " from type " - + type); - } else { - annotate(annotatedSupertype, supertype, null, typeDecl); - } - } - } - } - - /** - * Process a method or constructor declaration: copy its annotations to {@code - * #annotationFileAnnos}. - * - * @param decl a method or constructor declaration, as read from an annotation file - * @param elt the method or constructor's element - * @return type variables for the method - */ - private @Nullable List processCallableDeclaration( - CallableDeclaration decl, ExecutableElement elt) { - if (!isAnnotatedForThisChecker(decl.getAnnotations())) { - return null; - } - // Declaration annotations - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - if (decl.isMethodDeclaration()) { - // AnnotationFileParser parses all annotations in type annotation position as type - // annotations. - recordDeclAnnotation(elt, ((MethodDeclaration) decl).getType().getAnnotations(), decl); - } - markAsFromStubFile(elt); - - AnnotatedExecutableType methodType; - try { - methodType = atypeFactory.fromElement(elt); - } catch (ErrorTypeKindException e) { - stubWarnNotFound(decl, "Error type kind occurred: " + e.getLocalizedMessage()); - return Collections.emptyList(); - } - - AnnotatedExecutableType origMethodType = - warnIfStubRedundantWithBytecode ? methodType.deepCopy() : null; - - // Type Parameters - annotateTypeParameters(decl, elt, methodType.getTypeVariables(), decl.getTypeParameters()); - typeParameters.addAll(methodType.getTypeVariables()); - - // Return type, from declaration annotations on the method or constructor - if (decl.isMethodDeclaration()) { - MethodDeclaration methodDeclaration = (MethodDeclaration) decl; - if (methodDeclaration.getParameters().isEmpty()) { - String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); - RecordStub recordStub = annotationFileAnnos.records.get(qualRecordName); - if (recordStub != null) { - RecordComponentStub recordComponentStub = - recordStub.componentsByName.get(methodDeclaration.getNameAsString()); - if (recordComponentStub != null) { - recordComponentStub.hasAccessorInStubs = true; - } - } - } - - try { - annotate( - methodType.getReturnType(), methodDeclaration.getType(), decl.getAnnotations(), decl); - } catch (ErrorTypeKindException e) { - // See https://github.com/typetools/checker-framework/issues/244 . - // Issue a warning, to enable fixes to the classpath. - stubWarnNotFound(decl, "Error type kind occurred: " + e); - } - } else { - assert decl.isConstructorDeclaration(); - if (AnnotationFileUtil.isCanonicalConstructor(elt, atypeFactory.types)) { - // If this is the (user-written) canonical constructor, record that the component - // annotations should not be automatically transferred: - String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); - if (annotationFileAnnos.records.containsKey(qualRecordName)) { - List parameters = elt.getParameters(); - ArrayList annotatedParameters = new ArrayList<>(parameters.size()); - for (int i = 0; i < parameters.size(); i++) { - VariableElement parameter = parameters.get(i); - AnnotatedTypeMirror atm = - AnnotatedTypeMirror.createType(parameter.asType(), atypeFactory, false); - annotate(atm, decl.getParameter(i).getAnnotations(), decl.getParameter(i)); - annotatedParameters.add(atm); - } - annotationFileAnnos.records.get(qualRecordName).componentsInCanonicalConstructor = - annotatedParameters; - } - } - annotate(methodType.getReturnType(), decl.getAnnotations(), decl); - } - - // Parameters - processParameters(decl, elt, methodType); - - // Receiver - if (decl.getReceiverParameter().isPresent()) { - ReceiverParameter receiverParameter = decl.getReceiverParameter().get(); - if (methodType.getReceiverType() == null) { - if (decl.isConstructorDeclaration()) { - warn( - receiverParameter, - "parseParameter: constructor %s of a top-level class" - + " cannot have receiver annotations %s", - methodType, - receiverParameter.getAnnotations()); - } else { - warn( - receiverParameter, - "parseParameter: static method %s cannot have receiver annotations %s", - methodType, - receiverParameter.getAnnotations()); - } - } else { - // Add declaration annotations. - annotate( - methodType.getReceiverType(), receiverParameter.getAnnotations(), receiverParameter); - // Add type annotations. - annotate( - methodType.getReceiverType(), - receiverParameter.getType(), - receiverParameter.getAnnotations(), - receiverParameter); - } - } - - if (warnIfStubRedundantWithBytecode - && methodType.toString().equals(origMethodType.toString()) - && fileType != AnnotationFileType.BUILTIN_STUB) { - warn( - decl, - String.format( - "stub file specification is same as bytecode for %s", - ElementUtils.getQualifiedName(elt))); - } - - // Store the type. - putMerge(annotationFileAnnos.atypes, elt, methodType); - if (fileType.isStub()) { - typeParameters.removeAll(methodType.getTypeVariables()); - } - - return methodType.getTypeVariables(); - } - - /** - * Process the parameters of a method or constructor declaration: copy their annotations to {@code - * #annotationFileAnnos}. - * - * @param method a method or constructor declaration - * @param elt the element for {@code method} - * @param methodType the annotated type of {@code method} - */ - private void processParameters( - CallableDeclaration method, ExecutableElement elt, AnnotatedExecutableType methodType) { - List params = method.getParameters(); - List paramElts = elt.getParameters(); - List paramTypes = methodType.getParameterTypes(); - - for (int i = 0; i < methodType.getParameterTypes().size(); ++i) { - VariableElement paramElt = paramElts.get(i); - AnnotatedTypeMirror paramType = paramTypes.get(i); - Parameter param = params.get(i); - - recordDeclAnnotation(paramElt, param.getAnnotations(), param); - recordDeclAnnotation(paramElt, param.getType().getAnnotations(), param); - - if (param.isVarArgs()) { - assert paramType.getKind() == TypeKind.ARRAY; - // The "type" of param is actually the component type of the vararg. - // For example, in "Object..." the type would be "Object". - annotate( - ((AnnotatedArrayType) paramType).getComponentType(), - param.getType(), - param.getAnnotations(), - param); - // The "VarArgsAnnotations" are those just before "...". - annotate(paramType, param.getVarArgsAnnotations(), param); - } else { - annotate(paramType, param.getType(), param.getAnnotations(), param); - putMerge(annotationFileAnnos.atypes, paramElt, paramType); - } - } - } - - /** - * Clear (remove) existing annotations on the type. - * - *

          Stub files override annotations read from .class files. Using {@code replaceAnnotation} - * usually achieves this; however, for annotations on type variables, it is sometimes necessary to - * remove an existing annotation, leaving no annotation on the type variable. This method does so. - * - * @param atype the type to modify - * @param typeDef the type from the annotation file, used only for diagnostic messages - */ - @SuppressWarnings("unused") // for disabled warning message - private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) { - // TODO: only produce output if the removed annotation isn't the top or default - // annotation in the type hierarchy. See https://tinyurl.com/cfissue/2759 . - /* - if (!atype.getAnnotations().isEmpty()) { - stubWarnOverwritesBytecode( - String.format( - "in file %s at line %s removed existing annotations on type: %s", - filename.substring(filename.lastIndexOf('/') + 1), - typeDef.getBegin().get().line, - atype.toString(true))); - } - */ - // Clear existing annotations, which only makes a difference for - // type variables, but doesn't hurt in other cases. - atype.clearAnnotations(); - } - - /** - * Add the annotations from {@code type} to {@code atype}. Type annotations that parsed as - * declaration annotations (i.e., type annotations in {@code declAnnos}) are applied to the - * innermost component type. - * - * @param atype annotated type to which to add annotations - * @param type parsed type - * @param declAnnos annotations stored on the declaration of the variable with this type or null - * @param astNode where to report errors - */ - private void annotateAsArray( - AnnotatedArrayType atype, - ReferenceType type, - @Nullable NodeList declAnnos, - NodeWithRange astNode) { - annotateInnermostComponentType(atype, declAnnos, astNode); - Type typeDef = type; - AnnotatedTypeMirror currentAtype = atype; - while (typeDef.isArrayType()) { - if (currentAtype.getKind() != TypeKind.ARRAY) { - warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); - return; - } - - // handle generic type - clearAnnotations(currentAtype, typeDef); - - List annotations = typeDef.getAnnotations(); - if (annotations != null) { - annotate(currentAtype, annotations, astNode); - } - typeDef = ((com.github.javaparser.ast.type.ArrayType) typeDef).getComponentType(); - currentAtype = ((AnnotatedArrayType) currentAtype).getComponentType(); - } - if (currentAtype.getKind() == TypeKind.ARRAY) { - warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); - } - } - - private @Nullable ClassOrInterfaceType unwrapDeclaredType(Type type) { - if (type instanceof ClassOrInterfaceType) { - return (ClassOrInterfaceType) type; - } else if (type instanceof ReferenceType && type.getArrayLevel() == 0) { - return unwrapDeclaredType(type.getElementType()); - } else { - return null; - } - } - - /** - * Add to formal parameter {@code atype}: - * - *

            - *
          1. the annotations from {@code typeDef}, and - *
          2. any type annotations that parsed as declaration annotations (i.e., type annotations in - * {@code declAnnos}). - *
          - * - * @param atype annotated type to which to add annotations - * @param typeDef parsed type - * @param declAnnos annotations stored on the declaration of the variable with this type, or null - * @param astNode where to report errors - */ - private void annotate( - AnnotatedTypeMirror atype, - Type typeDef, - @Nullable NodeList declAnnos, - NodeWithRange astNode) { - if (atype.getKind() == TypeKind.ARRAY) { - if (typeDef instanceof ReferenceType) { - annotateAsArray((AnnotatedArrayType) atype, (ReferenceType) typeDef, declAnnos, astNode); - } else { - warn(astNode, "expected ReferenceType but found: " + typeDef); - } - return; - } - - clearAnnotations(atype, typeDef); - - // Primary annotations for the type of a variable declaration are not stored in typeDef, but - // rather as declaration annotations (passed as declAnnos to this method). But, if typeDef - // is not the type of a variable, then the primary annotations are stored in typeDef. - NodeList primaryAnnotations; - if (typeDef.getAnnotations().isEmpty() && declAnnos != null) { - primaryAnnotations = declAnnos; - } else { - primaryAnnotations = typeDef.getAnnotations(); - } - if (atype.getKind() != TypeKind.WILDCARD) { - // The primary annotation on a wildcard applies to the super or extends bound and - // are added below. - annotate(atype, primaryAnnotations, astNode); - } - - switch (atype.getKind()) { - case DECLARED: - ClassOrInterfaceType declType = unwrapDeclaredType(typeDef); - if (declType == null) { - break; - } - AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType) atype; - // Process type arguments. - @SuppressWarnings("optional:optional.collection") // JavaParser uses Optional - Optional> oDeclTypeArgs = declType.getTypeArguments(); - List adeclTypeArgs = adeclType.getTypeArguments(); - if (oDeclTypeArgs.isPresent() - && !oDeclTypeArgs.get().isEmpty() - && !adeclTypeArgs.isEmpty()) { - NodeList declTypeArgs = oDeclTypeArgs.get(); - if (declTypeArgs.size() != adeclTypeArgs.size()) { - warn( - astNode, - String.format( - "Mismatch in type argument size between %s (%d) and %s (%d)", - declType, declTypeArgs.size(), adeclType, adeclTypeArgs.size())); - break; - } - for (int i = 0; i < declTypeArgs.size(); ++i) { - annotate(adeclTypeArgs.get(i), declTypeArgs.get(i), null, astNode); - } - } - break; - case WILDCARD: - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atype; - // Ensure that the file also has a wildcard type, report an error otherwise - if (!typeDef.isWildcardType()) { - // We throw an error here, as otherwise we are just getting a generic cast error - // on the very next line. - warn( - astNode, - "wildcard type <" - + atype - + "> does not match type in stubs file" - + filename - + ": <" - + typeDef - + ">" - + " while parsing " - + typeBeingParsed); - return; - } - WildcardType wildcardDef = (WildcardType) typeDef; - if (wildcardDef.getExtendedType().isPresent()) { - annotate( - wildcardType.getExtendsBound(), wildcardDef.getExtendedType().get(), null, astNode); - annotate(wildcardType.getSuperBound(), primaryAnnotations, astNode); - } else if (wildcardDef.getSuperType().isPresent()) { - annotate(wildcardType.getSuperBound(), wildcardDef.getSuperType().get(), null, astNode); - annotate(wildcardType.getExtendsBound(), primaryAnnotations, astNode); - } else if (primaryAnnotations.isEmpty()) { - // Unannotated unbounded wildcard "?": remove any existing annotations and - // add the annotations from the type variable corresponding to the wildcard. - wildcardType.getExtendsBound().clearAnnotations(); - wildcardType.getSuperBound().clearAnnotations(); - AnnotatedTypeVariable atv = - (AnnotatedTypeVariable) - atypeFactory.getAnnotatedType(wildcardType.getTypeVariable().asElement()); - wildcardType.getExtendsBound().addAnnotations(atv.getUpperBound().getAnnotations()); - wildcardType.getSuperBound().addAnnotations(atv.getLowerBound().getAnnotations()); - } else { - // Annotated unbounded wildcard "@A ?": use annotations. - annotate(atype, primaryAnnotations, astNode); - } - break; - case TYPEVAR: - // Add annotations from the declaration of the TypeVariable - AnnotatedTypeVariable typeVarUse = (AnnotatedTypeVariable) atype; - Types typeUtils = processingEnv.getTypeUtils(); - for (AnnotatedTypeVariable typePar : typeParameters) { - if (typeUtils.isSameType(typePar.getUnderlyingType(), atype.getUnderlyingType())) { - atypeFactory.replaceAnnotations(typePar.getUpperBound(), typeVarUse.getUpperBound()); - atypeFactory.replaceAnnotations(typePar.getLowerBound(), typeVarUse.getLowerBound()); - } - } - // Add back the primary annotations. - annotate(atype, primaryAnnotations, astNode); - break; - default: - // No additional annotations to add. - } - } - - /** - * Process the field declaration in decl: copy its annotations to {@code #annotationFileAnnos}. - * - * @param decl the declaration in the annotation file - * @param elt the element representing that same declaration - */ - private void processField(FieldDeclaration decl, VariableElement elt) { - if (skipNode(decl)) { - // Don't process private fields of the JDK. They can't be referenced outside of the JDK - // and might refer to types that are not accessible. - return; - } - markAsFromStubFile(elt); - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - // AnnotationFileParser parses all annotations in type annotation position as type - // annotations - recordDeclAnnotation(elt, decl.getElementType().getAnnotations(), decl); - AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); - - VariableDeclarator fieldVarDecl = null; - String eltName = elt.getSimpleName().toString(); - for (VariableDeclarator var : decl.getVariables()) { - if (var.getName().toString().equals(eltName)) { - fieldVarDecl = var; - break; - } - } - assert fieldVarDecl != null; - annotate(fieldType, fieldVarDecl.getType(), decl.getAnnotations(), fieldVarDecl); - putMerge(annotationFileAnnos.atypes, elt, fieldType); - } - - /** - * Processes a parameter in a record header (i.e., a record component). - * - * @param decl the parameter in the record header - * @param elt the corresponding variable declaration element - * @return a representation of the record component in the stub file - */ - private RecordComponentStub processRecordField(Parameter decl, VariableElement elt) { - markAsFromStubFile(elt); - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - // AnnotationFileParser parses all annotations in type annotation position as type - // annotations. - recordDeclAnnotation(elt, decl.getType().getAnnotations(), decl); - AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); - - annotate(fieldType, decl.getType(), decl.getAnnotations(), decl); - putMerge(annotationFileAnnos.atypes, elt, fieldType); - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - for (AnnotationExpr annotation : decl.getAnnotations()) { - AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); - annos.add(annoMirror); - } - return new RecordComponentStub(fieldType, annos); - } - - /** - * Adds the annotations present on the declaration of an enum constant to the ATM of that - * constant. - * - * @param decl the enum constant, in Javaparser AST form (the source of annotations) - * @param elt the enum constant declaration, as an element (the destination for annotations) - */ - private void processEnumConstant(EnumConstantDeclaration decl, VariableElement elt) { - markAsFromStubFile(elt); - recordDeclAnnotation(elt, decl.getAnnotations(), decl); - AnnotatedTypeMirror enumConstType = atypeFactory.fromElement(elt); - annotate(enumConstType, decl.getAnnotations(), decl); - putMerge(annotationFileAnnos.atypes, elt, enumConstType); - } - - /** - * Returns the innermost component type of {@code type}. - * - * @param type array type - * @return the innermost component type of {@code type} - */ - private AnnotatedTypeMirror innermostComponentType(AnnotatedArrayType type) { - AnnotatedTypeMirror componentType = type; - while (componentType.getKind() == TypeKind.ARRAY) { - componentType = ((AnnotatedArrayType) componentType).getComponentType(); - } - return componentType; - } - - /** - * Adds {@code annotations} to the innermost component type of {@code type}. - * - * @param type array type - * @param annotations annotations to add - * @param astNode where to report errors - */ - private void annotateInnermostComponentType( - AnnotatedArrayType type, List annotations, NodeWithRange astNode) { - annotate(innermostComponentType(type), annotations, astNode); - } - - /** - * Annotate the type with the given type annotations, removing any existing annotations from the - * same qualifier hierarchies. - * - * @param type the type to annotate - * @param annotations the new annotations for the type; if null, nothing is done - * @param astNode where to report errors - */ - private void annotate( - AnnotatedTypeMirror type, - @Nullable List annotations, - NodeWithRange astNode) { - if (annotations == null) { - return; - } - for (AnnotationExpr annotation : annotations) { - AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); - if (annoMirror != null) { - type.replaceAnnotation(annoMirror); - } else { - // TODO: Maybe always warn here. It's so easy to forget an import statement and - // have an annotation silently ignored. - stubWarnNotFound(astNode, "unknown annotation " + annotation); - } - } - } - - /** - * Adds to {@code annotationFileAnnos} all the annotations in {@code annotations} that are - * applicable to {@code elt}'s location. For example, if an annotation is a type annotation but - * {@code elt} is a field declaration, the type annotation will be ignored. - * - * @param elt the element to be annotated - * @param annotations the set of annotations that may be applicable to elt - * @param astNode where to report errors - */ - private void recordDeclAnnotation( - Element elt, List annotations, NodeWithRange astNode) { - if (annotations == null || annotations.isEmpty()) { - return; - } - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - for (AnnotationExpr annotation : annotations) { - AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); - if (annoMirror != null) { - // The @Target annotation on `annotation`/`annoMirror` - Target target = annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); - // Only add the declaration annotation if the annotation applies to the element. - if (AnnotationUtils.getElementKindsForTarget(target).contains(elt.getKind())) { - // `annoMirror` is applicable to `elt` - annos.add(annoMirror); - } - } else { - // TODO: Maybe always warn here. It's so easy to forget an import statement and - // have an annotation silently ignored. - stubWarnNotFound(astNode, String.format("unknown annotation %s", annotation)); - } - } - String eltName = ElementUtils.getQualifiedName(elt); - putOrAddToDeclAnnos(eltName, annos); - } - - /** - * Adds the declaration annotation {@code @FromStubFile} to {@link #annotationFileAnnos}, unless - * we are parsing the JDK as a stub file. - * - * @param elt an element to be annotated as {@code @FromStubFile} - */ - private void markAsFromStubFile(Element elt) { - if (fileType == AnnotationFileType.AJAVA || fileType == AnnotationFileType.JDK_STUB) { - return; - } - putOrAddToDeclAnnos( - ElementUtils.getQualifiedName(elt), AnnotationMirrorSet.singleton(fromStubFileAnno)); - } - - private void annotateTypeParameters( - BodyDeclaration decl, // for debugging - Object elt, // for debugging; TypeElement or ExecutableElement - List typeArguments, - List typeParameters) { - if (typeParameters == null) { - return; - } - - if (typeParameters.size() != typeArguments.size()) { - String msg = - String.format( - "annotateTypeParameters: mismatched sizes:" - + " typeParameters (size %d)=%s;" - + " typeArguments (size %d)=%s;" - + " decl=%s; elt=%s (%s).", - typeParameters.size(), - typeParameters, - typeArguments.size(), - typeArguments, - decl.toString().replace(LINE_SEPARATOR, " "), - elt.toString().replace(LINE_SEPARATOR, " "), - elt.getClass()); - if (!debugAnnotationFileParser) { - msg = msg + "; for more details, run with -AstubDebug"; - } - warn(decl, msg); - return; - } - for (int i = 0; i < typeParameters.size(); ++i) { - TypeParameter param = typeParameters.get(i); - AnnotatedTypeVariable paramType = (AnnotatedTypeVariable) typeArguments.get(i); - - // Handle type bounds - if (param.getTypeBound() == null || param.getTypeBound().isEmpty()) { - // No type bound, so annotations are both lower and upper bounds. - annotate(paramType, param.getAnnotations(), param); - } else if (param.getTypeBound() != null && !param.getTypeBound().isEmpty()) { - annotate(paramType.getLowerBound(), param.getAnnotations(), param); - if (param.getTypeBound().size() == 1) { - // The additional declAnnos (third argument) is always null in this call to - // `annotate`, but the type bound (second argument) might have annotations. - annotate(paramType.getUpperBound(), param.getTypeBound().get(0), null, param); - } else { - // param.getTypeBound().size() > 1 - ArrayList typeBoundsWithAnotations = - new ArrayList<>(param.getTypeBound().size()); - for (ClassOrInterfaceType typeBound : param.getTypeBound()) { - if (!typeBound.getAnnotations().isEmpty()) { - typeBoundsWithAnotations.add(typeBound); - } - } - int numBounds = typeBoundsWithAnotations.size(); - if (numBounds == 0) { - // nothing to do - } else if (numBounds == 1) { - annotate(paramType.getUpperBound(), typeBoundsWithAnotations.get(0), null, param); - } else { - // TODO: add support for intersection types - // One problem is that `annotate()` removes any existing annotations from - // the same qualifier hierarchies, so paramType.getLowerBound() would end up - // with the annotations of only the last type bound. - - // String msg = - // String.format( - // "annotateTypeParameters: multiple type bounds: - // typeParameters=%s; " - // + "param #%d=%s; bounds=%s; decl=%s; elt=%s (%s).", - // typeParameters, - // i, - // param, - // param.getTypeBound(), - // decl.toString().replace(LINE_SEPARATOR, " "), - // elt.toString().replace(LINE_SEPARATOR, " "), - // elt.getClass()); - // warn(decl, msg); + /** + * Returns all annotations imported by the annotation file, as a value for {@link + * #allAnnotations}. Note that this also modifies {@link #importedConstants} and {@link + * #importedTypes}. + * + *

          This method misses annotations that are not imported. The {@link #getAnnotation} method + * compensates for this deficiency by adding any fully-qualified annotation that it encounters. + * + * @return a map from names to TypeElement, for all annotations imported by the annotation file. + * Two entries for each annotation: one for the simple name and another for the + * fully-qualified name, with the same value. + * @see #allAnnotations + */ + private Map getImportedAnnotations() { + Map result = new HashMap<>(); - stubWarnNotFound( - param, "annotations on intersection types are not yet supported: " + param); - } - } - if (param.getTypeBound().size() == 1 - && param.getTypeBound().get(0).getAnnotations().isEmpty() - && TypesUtils.isObject(paramType.getUpperBound().getUnderlyingType())) { - // If there is an explicit "T extends Object" type parameter bound, - // treat it like an explicit use of "Object" in code. - AnnotatedTypeMirror ub = atypeFactory.getAnnotatedType(Object.class); - paramType.getUpperBound().replaceAnnotations(ub.getAnnotations()); - } - } - - putMerge(annotationFileAnnos.atypes, paramType.getUnderlyingType().asElement(), paramType); - } - } - - /** - * Returns a pair of mappings. For each member declaration of the JavaParser type declaration - * {@code typeDecl}: - * - *

            - *
          • If {@code typeElt} contains a member element for it, the first mapping maps the member - * element to it. - *
          • If it is a fake override, the second mapping maps each element it overrides to it. - *
          • Otherwise, does nothing. - *
          - * - * This method does not read or write the field {@link #annotationFileAnnos}. - * - * @param typeDecl a JavaParser type declaration - * @param typeElt the javac element for {@code typeDecl} - * @return two mappings: from javac elements to their JavaParser declaration, and from javac - * elements to fake overrides of them - * @param astNode where to report errors - */ - private IPair>, Map>>> - getMembers(TypeDeclaration typeDecl, TypeElement typeElt, NodeWithRange astNode) { - assert (typeElt.getSimpleName().contentEquals(typeDecl.getNameAsString()) - || typeDecl.getNameAsString().endsWith("$" + typeElt.getSimpleName())) - : String.format("%s %s", typeElt.getSimpleName(), typeDecl.getName()); - - Map> elementsToDecl = new LinkedHashMap<>(); - Map>> fakeOverrideDecls = new LinkedHashMap<>(); - - for (BodyDeclaration member : typeDecl.getMembers()) { - putNewElement( - elementsToDecl, fakeOverrideDecls, typeElt, member, typeDecl.getNameAsString(), astNode); - } - // For an enum type declaration, also add the enum constants - if (typeDecl instanceof EnumDeclaration) { - EnumDeclaration enumDecl = (EnumDeclaration) typeDecl; - // getEntries() gives the list of enum constant declarations - for (BodyDeclaration member : enumDecl.getEntries()) { - putNewElement( - elementsToDecl, - fakeOverrideDecls, - typeElt, - member, - typeDecl.getNameAsString(), - astNode); - } - } - - return IPair.of(elementsToDecl, fakeOverrideDecls); - } - - // Used only by getMembers(). - /** - * If {@code typeElt} contains an element for {@code member}, adds to {@code elementsToDecl} a - * mapping from member's element to member. Does nothing if a mapping already exists. - * - *

          Otherwise (if there is no element for {@code member}), adds to {@code fakeOverrideDecls} - * zero or more mappings. Each mapping is from an element that {@code member} would override to - * {@code member}. - * - *

          This method does not read or write field {@link #annotationFileAnnos}. - * - * @param elementsToDecl the mapping that is side-effected by this method - * @param fakeOverrideDecls fake overrides, also side-effected by this method - * @param typeElt the class in which {@code member} is declared - * @param member the stub file declaration of a method - * @param typeDeclName used only for debugging - * @param astNode where to report errors - */ - private void putNewElement( - Map> elementsToDecl, - Map>> fakeOverrideDecls, - TypeElement typeElt, - BodyDeclaration member, - String typeDeclName, - NodeWithRange astNode) { - if (member instanceof MethodDeclaration) { - MethodDeclaration method = (MethodDeclaration) member; - Element elt = findElement(typeElt, method, /* noWarn= */ true); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, method); - } else { - ExecutableElement overriddenMethod = fakeOverriddenMethod(typeElt, method); - if (overriddenMethod == null) { - // Didn't find the element and it isn't a fake override. Issue a warning. - findElement(typeElt, method, /* noWarn= */ false); - } else { - List> l = - fakeOverrideDecls.computeIfAbsent(overriddenMethod, __ -> new ArrayList<>(1)); - l.add(member); - } - } - } else if (member instanceof ConstructorDeclaration) { - Element elt = findElement(typeElt, (ConstructorDeclaration) member); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else if (member instanceof FieldDeclaration) { - FieldDeclaration fieldDecl = (FieldDeclaration) member; - for (VariableDeclarator var : fieldDecl.getVariables()) { - Element varelt = findElement(typeElt, var); - if (varelt != null) { - putIfAbsent(elementsToDecl, varelt, fieldDecl); - } - } - } else if (member instanceof EnumConstantDeclaration) { - Element elt = findElement(typeElt, (EnumConstantDeclaration) member, astNode); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else if (member instanceof ClassOrInterfaceDeclaration) { - Element elt = findElement(typeElt, (ClassOrInterfaceDeclaration) member); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else if (member instanceof EnumDeclaration) { - Element elt = findElement(typeElt, (EnumDeclaration) member); - if (elt != null) { - putIfAbsent(elementsToDecl, elt, member); - } - } else { - stubDebug("ignoring element of type %s in %s", member.getClass(), typeDeclName); - } - } - - /** - * Given a method declaration that does not correspond to an element, returns the method it - * directly overrides or implements. As Java does, this prefers a method in a superclass to one in - * an interface. - * - *

          As with regular overrides, the parameter types must be exact matches; contravariance is not - * permitted. - * - * @param typeElt the type in which the method appears - * @param methodDecl the method declaration that does not correspond to an element - * @return the methods that the given method declaration would override, or null if none - */ - private @Nullable ExecutableElement fakeOverriddenMethod( - TypeElement typeElt, MethodDeclaration methodDecl) { - for (Element elt : typeElt.getEnclosedElements()) { - if (elt.getKind() != ElementKind.METHOD) { - continue; - } - ExecutableElement candidate = (ExecutableElement) elt; - if (!candidate.getSimpleName().contentEquals(methodDecl.getName().getIdentifier())) { - continue; - } - List candidateParams = candidate.getParameters(); - if (sameTypes(candidateParams, methodDecl.getParameters())) { - return candidate; - } - } - - TypeElement superType = ElementUtils.getSuperClass(typeElt); - if (superType != null) { - ExecutableElement result = fakeOverriddenMethod(superType, methodDecl); - if (result != null) { + // TODO: The size can be greater than 1, but this ignores all but the first element. + assert !stubUnit.getCompilationUnits().isEmpty(); + CompilationUnit cu = stubUnit.getCompilationUnits().get(0); + + if (cu.getImports() == null) { + return result; + } + + for (ImportDeclaration importDecl : cu.getImports()) { + try { + if (importDecl.isAsterisk()) { + @SuppressWarnings("signature" // https://tinyurl.com/cfissue/3094: + // com.github.javaparser.ast.expr.Name inherits toString, + // so there can be no annotation for it + ) + @DotSeparatedIdentifiers String imported = importDecl.getName().toString(); + if (importDecl.isStatic()) { + // Wildcard import of members of a type (class or interface) + TypeElement element = + getTypeElement(imported, "imported type not found", importDecl); + if (element != null) { + // Find nested annotations + // Find compile time constant fields, or values of an enum + putAllNew(result, annosInType(element)); + importedConstants.addAll(getImportableMembers(element)); + addEnclosingTypesToImportedTypes(element); + } + + } else { + // Wildcard import of members of a package + PackageElement element = findPackage(imported, importDecl); + if (element != null) { + putAllNew(result, annosInPackage(element)); + addEnclosingTypesToImportedTypes(element); + } + } + } else { + // A single (non-wildcard) import. + @SuppressWarnings("signature" // importDecl is non-wildcard, so its name is + // @FullyQualifiedName + ) + @FullyQualifiedName String imported = importDecl.getNameAsString(); + + TypeElement importType = elements.getTypeElement(imported); + if (importType == null && !importDecl.isStatic()) { + // Class or nested class (according to JSL), but we can't resolve + + stubWarnNotFound(importDecl, "imported type not found: " + imported); + } else if (importType == null) { + // static import of field or method. + + IPair<@FullyQualifiedName String, String> typeParts = + AnnotationFileUtil.partitionQualifiedName(imported); + String type = typeParts.first; + String fieldName = typeParts.second; + TypeElement enclType = + getTypeElement( + type, + String.format( + "enclosing type of static field %s not found", + fieldName), + importDecl); + + if (enclType != null) { + // Don't use findFieldElement(enclType, fieldName), because we don't + // want a warning, imported might be a method. + for (VariableElement field : + ElementUtils.getAllFieldsIn(enclType, elements)) { + // field.getSimpleName() is a CharSequence, not a String + if (fieldName.equals(field.getSimpleName().toString())) { + importedConstants.add(imported); + } + } + } + + } else if (importType.getKind() == ElementKind.ANNOTATION_TYPE) { + // Single annotation or nested annotation + TypeElement annoElt = elements.getTypeElement(imported); + if (annoElt != null) { + putIfAbsent(result, annoElt.getSimpleName().toString(), annoElt); + importedTypes.put(annoElt.getSimpleName().toString(), annoElt); + } else { + stubWarnNotFound(importDecl, "could not load import: " + imported); + } + } else { + // Class or nested class + // TODO: Is this needed? + importedConstants.add(imported); + TypeElement element = + getTypeElement(imported, "imported type not found", importDecl); + importedTypes.put(element.getSimpleName().toString(), element); + } + } + } catch (AssertionError error) { + stubWarnNotFound(importDecl, error.toString()); + } + } return result; - } } - for (TypeMirror interfaceTypeMirror : typeElt.getInterfaces()) { - TypeElement interfaceElement = (TypeElement) ((DeclaredType) interfaceTypeMirror).asElement(); - ExecutableElement result = fakeOverriddenMethod(interfaceElement, methodDecl); - if (result != null) { - return result; - } - } - - return null; - } - - /** - * Returns true if the two signatures (represented as lists of formal parameters) are the same. No - * contravariance is permitted. - * - * @param javacParams parameter list in javac form - * @param javaParserParams parameter list in JavaParser form - * @return true if the two signatures are the same - */ - private boolean sameTypes( - List javacParams, NodeList javaParserParams) { - if (javacParams.size() != javaParserParams.size()) { - return false; - } - for (int i = 0; i < javacParams.size(); i++) { - TypeMirror javacType = javacParams.get(i).asType(); - Parameter javaParserParam = javaParserParams.get(i); - Type javaParserType = javaParserParam.getType(); - if (javacType.getKind() == TypeKind.TYPEVAR) { - // TODO: Hack, need to viewpoint-adapt. - javacType = ((TypeVariable) javacType).getUpperBound(); - } - if (!sameType(javacType, javaParserType)) { - return false; - } - } - return true; - } - - /** - * Returns true if the two types are the same. - * - * @param javacType type in javac form - * @param javaParserType type in JavaParser form - * @return true if the two types are the same - */ - private boolean sameType(TypeMirror javacType, Type javaParserType) { - - switch (javacType.getKind()) { - case BOOLEAN: - return javaParserType.equals(PrimitiveType.booleanType()); - case BYTE: - return javaParserType.equals(PrimitiveType.byteType()); - case CHAR: - return javaParserType.equals(PrimitiveType.charType()); - case DOUBLE: - return javaParserType.equals(PrimitiveType.doubleType()); - case FLOAT: - return javaParserType.equals(PrimitiveType.floatType()); - case INT: - return javaParserType.equals(PrimitiveType.intType()); - case LONG: - return javaParserType.equals(PrimitiveType.longType()); - case SHORT: - return javaParserType.equals(PrimitiveType.shortType()); - - case DECLARED: - case TYPEVAR: - if (!(javaParserType instanceof ClassOrInterfaceType)) { - return false; - } - com.sun.tools.javac.code.Type javacTypeInternal = (com.sun.tools.javac.code.Type) javacType; - ClassOrInterfaceType javaParserClassType = (ClassOrInterfaceType) javaParserType; - - // Use asString() because toString() includes annotations. - String javaParserString = javaParserClassType.asString(); - Element javacElement = javacTypeInternal.asElement(); - // Check both fully-qualified name and simple name. - return javacElement.toString().equals(javaParserString) - || javacElement.getSimpleName().contentEquals(javaParserString); - - case ARRAY: - return javaParserType.isArrayType() - && sameType( - ((ArrayType) javacType).getComponentType(), - javaParserType.asArrayType().getComponentType()); - - default: - throw new BugInCF("unhandled type %s of kind %s", javacType, javacType.getKind()); - } - } - - /** - * Process a fake override: copy its annotations to the fake overrides part of {@code - * #annotationFileAnnos}. - * - * @param element a real element - * @param decl a fake override of the element - * @param fakeLocation where the fake override was defined - */ - private void processFakeOverride( - ExecutableElement element, CallableDeclaration decl, TypeElement fakeLocation) { - // This is a fresh type, which this code may side-effect. - AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(element); - - // Here is a hacky solution that does not use the visitor. It just handles the return type. - // TODO: Walk the type and the declaration, copying annotations from the declaration to the - // element. I think PR #3977 has a visitor that does that, which I should use after it is - // merged. - - // The annotations on the method. These include type annotations on the return type. - NodeList annotations = decl.getAnnotations(); - annotate(methodType.getReturnType(), ((MethodDeclaration) decl).getType(), annotations, decl); - - List> l = - annotationFileAnnos.fakeOverrides.computeIfAbsent(element, __ -> new ArrayList<>(1)); - l.add(IPair.of(fakeLocation.asType(), methodType)); - } - - /** - * Return the annotated type corresponding to {@code type}, or null if none exists. More - * specifically, returns the element of {@code types} whose name matches {@code type}. - * - * @param type the type to search for - * @param types the list of AnnotatedDeclaredTypes to search in - * @param astNode where to report errors - * @return the annotated type in {@code types} corresponding to {@code type}, or null if none - * exists - */ - private @Nullable AnnotatedDeclaredType findAnnotatedType( - ClassOrInterfaceType type, List types, NodeWithRange astNode) { - String typeString = type.getNameAsString(); - for (AnnotatedDeclaredType supertype : types) { - if (supertype.getUnderlyingType().asElement().getSimpleName().contentEquals(typeString)) { - return supertype; - } - } - stubWarnNotFound(astNode, "direct supertype " + typeString + " not found"); - if (debugAnnotationFileParser) { - stubDebug("direct supertypes that were searched:"); - for (AnnotatedDeclaredType supertype : types) { - stubDebug(" %s", supertype); - } - } - return null; - } - - /** - * Looks for the nested type element in the typeElt and returns it if the element has the same - * name as provided class or interface declaration. In case nested element is not found it returns - * null. - * - * @param typeElt an element where nested type element should be looked for - * @param ciDecl class or interface declaration which name should be found among nested elements - * of the typeElt - * @return nested in typeElt element with the name of the class or interface, or null if nested - * element is not found - */ - private @Nullable Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) { - String wantedClassOrInterfaceName = ciDecl.getNameAsString(); - for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { - if (wantedClassOrInterfaceName.equals(typeElement.getSimpleName().toString())) { - return typeElement; - } - } - - stubWarnNotFound( - ciDecl, "class/interface " + wantedClassOrInterfaceName + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - stubDebug(" type declarations of %s:", typeElt); - for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - return null; - } - - /** - * Looks for the nested enum element in the typeElt and returns it if the element has the same - * name as provided enum declaration. In case nested element is not found it returns null. - * - * @param typeElt an element where nested enum element should be looked for - * @param enumDecl enum declaration which name should be found among nested elements of the - * typeElt - * @return nested in typeElt enum element with the name of the provided enum, or null if nested - * element is not found - */ - private @Nullable Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) { - String wantedEnumName = enumDecl.getNameAsString(); - for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { - if (wantedEnumName.equals(typeElement.getSimpleName().toString())) { - return typeElement; - } - } - - stubWarnNotFound(enumDecl, "enum " + wantedEnumName + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - stubDebug(" type declarations of %s:", typeElt); - for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - return null; - } - - /** - * Looks for an enum constant element in the typeElt and returns it if the element has the same - * name as provided. In case enum constant element is not found it returns null. - * - * @param typeElt type element where enum constant element should be looked for - * @param enumConstDecl the declaration of the enum constant - * @param astNode where to report errors - * @return enum constant element in typeElt with the provided name, or null if enum constant - * element is not found - */ - private @Nullable VariableElement findElement( - TypeElement typeElt, EnumConstantDeclaration enumConstDecl, NodeWithRange astNode) { - String enumConstName = enumConstDecl.getNameAsString(); - return findFieldElement(typeElt, enumConstName, astNode); - } - - /** - * Looks for a method element in {@code typeElt} that has the same name and formal parameter types - * as {@code methodDecl}. Returns null, and possibly issues a warning, if no such method element - * is found. - * - * @param typeElt type element where method element should be looked for - * @param methodDecl method declaration with signature that should be found among methods in the - * typeElt - * @param noWarn if true, don't issue a warning if the element is not found - * @return method element in typeElt with the same signature as the provided method declaration or - * null if method element is not found - */ - private @Nullable ExecutableElement findElement( - TypeElement typeElt, MethodDeclaration methodDecl, boolean noWarn) { - if (skipNode(methodDecl)) { - return null; - } - String wantedMethodName = methodDecl.getNameAsString(); - int wantedMethodParams = - (methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size(); - String wantedMethodString = AnnotationFileUtil.toString(methodDecl); - for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (wantedMethodParams == method.getParameters().size() - && wantedMethodName.contentEquals(method.getSimpleName().toString()) - && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { - return method; - } - } - if (!noWarn) { - if (methodDecl.getAccessSpecifier() == AccessSpecifier.NONE) { - // This might be a false positive warning. The stub parser permits a stub file to - // omit the access specifier, but package-private methods aren't in the TypeElement. - stubWarnNotFound( - methodDecl, - "package-private method " - + wantedMethodString - + " not found in type " - + typeElt - + System.lineSeparator() - + "If the method is not package-private," - + " add an access specifier in the stub file" - + " and use -AstubDebug to receive a more useful error message."); - } else { - stubWarnNotFound( - methodDecl, "method " + wantedMethodString + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - stubDebug(" methods of %s:", typeElt); - for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - } - } - return null; - } - - /** - * Looks for a constructor element in the typeElt and returns it if the element has the same - * signature as provided constructor declaration. In case constructor element is not found it - * returns null. - * - * @param typeElt type element where constructor element should be looked for - * @param constructorDecl constructor declaration with signature that should be found among - * constructors in the typeElt - * @return constructor element in typeElt with the same signature as the provided constructor - * declaration or null if constructor element is not found - */ - private @Nullable ExecutableElement findElement( - TypeElement typeElt, ConstructorDeclaration constructorDecl) { - if (skipNode(constructorDecl)) { - return null; - } - int wantedMethodParams = - (constructorDecl.getParameters() == null) ? 0 : constructorDecl.getParameters().size(); - String wantedMethodString = AnnotationFileUtil.toString(constructorDecl); - for (ExecutableElement method : ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { - if (wantedMethodParams == method.getParameters().size() - && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { - return method; - } - } - - stubWarnNotFound( - constructorDecl, "constructor " + wantedMethodString + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - for (ExecutableElement method : ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", method); - } - } - return null; - } - - /** - * Returns the element for the given variable. - * - * @param typeElt the type in which the variable is contained - * @param variable the variable whose element to return - * @return the element for the given variable - */ - private VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) { - String fieldName = variable.getNameAsString(); - return findFieldElement(typeElt, fieldName, variable); - } - - /** - * Looks for a field element in the typeElt and returns it if the element has the same name as - * provided. In case field element is not found it returns null. - * - * @param typeElt type element where field element should be looked for - * @param fieldName field name that should be found - * @param astNode where to report errors - * @return field element in typeElt with the provided name or null if field element is not found - */ - private @Nullable VariableElement findFieldElement( - TypeElement typeElt, String fieldName, NodeWithRange astNode) { - for (VariableElement field : ElementUtils.getAllFieldsIn(typeElt, elements)) { - // field.getSimpleName() is a CharSequence, not a String - if (fieldName.equals(field.getSimpleName().toString())) { - return field; - } - } - - stubWarnNotFound(astNode, "field " + fieldName + " not found in type " + typeElt); - if (debugAnnotationFileParser) { - for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) { - stubDebug(" %s", field); - } - } - return null; - } - - /** - * Given a fully-qualified type name, return a TypeElement for it, or null if none exists. Also - * cache in importedTypes. - * - * @param name a fully-qualified type name - * @return a TypeElement for the name, or null - */ - private @Nullable TypeElement getTypeElementOrNull(@FullyQualifiedName String name) { - TypeElement typeElement = elements.getTypeElement(name); - if (typeElement != null) { - importedTypes.put(name, typeElement); - } - // for debugging: warn("getTypeElementOrNull(%s) => %s", name, typeElement); - return typeElement; - } - - /** - * Get the type element for the given fully-qualified type name. If none is found, issue a warning - * and return null. - * - * @param typeName a type name - * @param msg a warning message to issue if the type element for {@code typeName} cannot be found - * @param astNode where to report errors - * @return the type element for the given fully-qualified type name, or null - */ - private @Nullable TypeElement getTypeElement( - @FullyQualifiedName String typeName, String msg, NodeWithRange astNode) { - TypeElement classElement = elements.getTypeElement(typeName); - if (classElement == null) { - stubWarnNotFound(astNode, msg + ": " + typeName); - } - return classElement; - } - - /** - * Returns the element for the given package. - * - * @param packageName the package's name - * @param astNode where to report errors - * @return the element for the given package - */ - private PackageElement findPackage(String packageName, NodeWithRange astNode) { - PackageElement packageElement = elements.getPackageElement(packageName); - if (packageElement == null) { - stubWarnNotFound(astNode, "imported package not found: " + packageName); - } - return packageElement; - } - - /** - * Returns true if one of the annotations is {@link AnnotatedFor} and this checker is in its list - * of checkers. If none of the annotations are {@code AnnotatedFor}, then also return true. - * - * @param annotations a list of JavaParser annotations - * @return true if one of the annotations is {@link AnnotatedFor} and its list of checkers does - * not contain this checker - */ - private boolean isAnnotatedForThisChecker(List annotations) { - if (fileType == AnnotationFileType.JDK_STUB) { - // The JDK stubs have purity annotations that should be read for all checkers. - // TODO: Parse the JDK stubs, but only save the declaration annotations. - return true; - } - for (AnnotationExpr ae : annotations) { - if (ae.getNameAsString().equals("AnnotatedFor") - || ae.getNameAsString().equals("org.checkerframework.framework.qual.AnnotatedFor")) { - AnnotationMirror af = getAnnotation(ae, allAnnotations); - if (atypeFactory.areSameByClass(af, AnnotatedFor.class)) { - return atypeFactory.doesAnnotatedForApplyToThisChecker(af); - } - } - } - return true; - } - - /** - * Convert {@code annotation} into an AnnotationMirror. Returns null if the annotation isn't - * supported by the checker or if some error occurred while converting it. - * - * @param annotation syntax tree for an annotation - * @param allAnnotations map from simple name to annotation definition; side-effected by this - * method - * @return the AnnotationMirror for the annotation, or null if it cannot be built - */ - private @Nullable AnnotationMirror getAnnotation( - AnnotationExpr annotation, Map allAnnotations) { - - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 - @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); - TypeElement annoTypeElt = allAnnotations.get(annoNameFq); - if (annoTypeElt == null) { - // If the annotation was not imported, then #getImportedAnnotations did not add it to - // the allAnnotations field. This code adds the annotation when it is encountered (i.e. - // here). - // Note that this does not call AnnotationFileParser#getTypeElement to avoid a spurious - // diagnostic if the annotation is actually unknown. - annoTypeElt = elements.getTypeElement(annoNameFq); - if (annoTypeElt == null) { - // Not a supported annotation -> ignore - return null; - } - putAllNew(allAnnotations, createNameToAnnotationMap(Collections.singletonList(annoTypeElt))); - } - @SuppressWarnings("signature") // not anonymous, so name is not empty - @CanonicalName String annoName = annoTypeElt.getQualifiedName().toString(); - - if (annotation instanceof MarkerAnnotationExpr) { - return AnnotationBuilder.fromName(elements, annoName); - } else if (annotation instanceof NormalAnnotationExpr) { - NormalAnnotationExpr nrmanno = (NormalAnnotationExpr) annotation; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); - List pairs = nrmanno.getPairs(); - if (pairs != null) { - for (MemberValuePair mvp : pairs) { - String member = mvp.getNameAsString(); - Expression exp = mvp.getValue(); - try { - builderAddElement(builder, member, exp); - } catch (AnnotationFileParserException e) { - warn( - exp, - "for annotation %s, could not add %s=%s because %s", - annotation, - member, - exp, - e.getMessage()); - return null; - } - } - } - return builder.build(); - } else if (annotation instanceof SingleMemberAnnotationExpr) { - SingleMemberAnnotationExpr sglanno = (SingleMemberAnnotationExpr) annotation; - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); - Expression valExpr = sglanno.getMemberValue(); - try { - builderAddElement(builder, "value", valExpr); - } catch (AnnotationFileParserException e) { - warn( - valExpr, - "for annotation %s, could not add value=%s because %s", - annotation, - valExpr, - e.getMessage()); - return null; - } - return builder.build(); - } else { - throw new BugInCF("AnnotationFileParser: unknown annotation type: " + annotation); - } - } - - /** - * Returns the value of {@code expr}. - * - * @param name the name of an annotation element/argument, used for diagnostic messages - * @param expr the expression to determine the value of - * @param valueKind the type of the result - * @return the value of {@code expr} - * @throws AnnotationFileParserException if a problem occurred getting the value - */ - private Object getValueOfExpressionInAnnotation(String name, Expression expr, TypeKind valueKind) - throws AnnotationFileParserException { - if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) { - VariableElement elem; - if (expr instanceof NameExpr) { - elem = findVariableElement((NameExpr) expr); - } else { - elem = findVariableElement((FieldAccessExpr) expr); - } - if (elem == null) { - throw new AnnotationFileParserException(String.format("variable %s not found", expr)); - } - Object value = elem.getConstantValue() != null ? elem.getConstantValue() : elem; - if (value instanceof Number) { - return convert((Number) value, valueKind); - } else { - return value; - } - } else if (expr instanceof StringLiteralExpr) { - return ((StringLiteralExpr) expr).asString(); - } else if (expr instanceof BooleanLiteralExpr) { - return ((BooleanLiteralExpr) expr).getValue(); - } else if (expr instanceof CharLiteralExpr) { - return convert((int) ((CharLiteralExpr) expr).asChar(), valueKind); - } else if (expr instanceof DoubleLiteralExpr) { - // No conversion needed if the expression is a double, the annotation value must be a - // double, too. - return ((DoubleLiteralExpr) expr).asDouble(); - } else if (expr instanceof IntegerLiteralExpr) { - return convert(((IntegerLiteralExpr) expr).asNumber(), valueKind); - } else if (expr instanceof LongLiteralExpr) { - return convert(((LongLiteralExpr) expr).asNumber(), valueKind); - } else if (expr instanceof UnaryExpr) { - switch (expr.toString()) { - // Special-case the minimum values. Separately parsing a "-" and a value - // doesn't correctly handle the minimum values, because the absolute value of - // the smallest member of an integral type is larger than the largest value. - case "-9223372036854775808L": - case "-9223372036854775808l": - return convert(Long.MIN_VALUE, valueKind, false); - case "-2147483648": - return convert(Integer.MIN_VALUE, valueKind, false); - default: - if (((UnaryExpr) expr).getOperator() == UnaryExpr.Operator.MINUS) { - Object value = - getValueOfExpressionInAnnotation( - name, ((UnaryExpr) expr).getExpression(), valueKind); - if (value instanceof Number) { - return convert((Number) value, valueKind, true); - } - } - throw new AnnotationFileParserException( - "unexpected Unary annotation expression: " + expr); - } - } else if (expr instanceof ClassExpr) { - ClassExpr classExpr = (ClassExpr) expr; - @SuppressWarnings("signature") // Type.toString(): @FullyQualifiedName - @FullyQualifiedName String className = classExpr.getType().toString(); - if (importedTypes.containsKey(className)) { - return importedTypes.get(className).asType(); - } - TypeElement typeElement = findTypeOfName(className); - if (typeElement == null) { - throw new AnnotationFileParserException("unknown class name " + className); - } - - return typeElement.asType(); - } else if (expr instanceof NullLiteralExpr) { - throw new AnnotationFileParserException("illegal annotation value null, for " + name); - } else { - throw new AnnotationFileParserException("unexpected annotation expression: " + expr); - } - } - - /** - * Returns the TypeElement with the name {@code name}, if one exists. Otherwise, checks the class - * and package of {@code typeBeingParsed} for a class named {@code name}. - * - * @param name classname (simple, or Outer.Inner, or fully-qualified) - * @return the TypeElement for {@code name}, or null if not found - */ - @SuppressWarnings("signature:argument.type.incompatible") // string concatenation - private @Nullable TypeElement findTypeOfName(@FullyQualifiedName String name) { - String packageName = typeBeingParsed.packageName; - String packagePrefix = (packageName == null) ? "" : packageName + "."; - - // warn("findTypeOfName(%s), typeBeingParsed %s %s", name, packageName, enclosingClass); - - // As soon as typeElement is set to a non-null value, it will be returned. - TypeElement typeElement = getTypeElementOrNull(name); - if (typeElement == null && packageName != null) { - typeElement = getTypeElementOrNull(packagePrefix + name); - } - String enclosingClass = typeBeingParsed.className; - while (typeElement == null && enclosingClass != null) { - typeElement = getTypeElementOrNull(packagePrefix + enclosingClass + "." + name); - int lastDot = enclosingClass.lastIndexOf('.'); - if (lastDot == -1) { - break; - } else { - enclosingClass = enclosingClass.substring(0, lastDot); - } - } - if (typeElement == null && !"java.lang".equals(packageName)) { - typeElement = getTypeElementOrNull("java.lang." + name); - } - return typeElement; - } - - /** - * Converts {@code number} to {@code expectedKind}. - * - *

          
          -   *   @interface Anno { long value(); }
          -   *   @Anno(1)
          -   * 
          - * - * To properly build @Anno, the IntegerLiteralExpr "1" must be converted from an int to a long. - */ - private Object convert(Number number, TypeKind expectedKind) { - return convert(number, expectedKind, false); - } - - /** - * Converts {@code number} to {@code expectedKind}. The value converted is multiplied by -1 if - * {@code negate} is true - * - * @param number a Number value to be converted - * @param expectedKind one of type {byte, short, int, long, char, float, double} - * @param negate whether to negate the value of the Number Object while converting - * @return the converted Object - */ - private Object convert(Number number, TypeKind expectedKind, boolean negate) { - byte scalefactor = (byte) (negate ? -1 : 1); - switch (expectedKind) { - case BYTE: - return number.byteValue() * scalefactor; - case SHORT: - return number.shortValue() * scalefactor; - case INT: - return number.intValue() * scalefactor; - case LONG: - return number.longValue() * scalefactor; - case CHAR: - // It's not possible for `number` to be negative when `expectedkind` is a CHAR, and - // casting a negative value to char is illegal. - if (negate) { - throw new BugInCF( - "convert(%s, %s, %s): can't negate a char", number, expectedKind, negate); - } - return (char) number.intValue(); - case FLOAT: - return number.floatValue() * scalefactor; - case DOUBLE: - return number.doubleValue() * scalefactor; - default: - throw new BugInCF("unexpected expectedKind: " + expectedKind); - } - } - - /** - * Adds an annotation element (argument) to {@code builder}. The element is a Java expression. - * - * @param builder the builder to side-effect - * @param name the element name - * @param expr the element value - * @throws AnnotationFileParserException if the expression cannot be parsed and added to {@code - * builder} - */ - private void builderAddElement(AnnotationBuilder builder, String name, Expression expr) - throws AnnotationFileParserException { - ExecutableElement var = builder.findElement(name); - TypeMirror declaredType = var.getReturnType(); - TypeKind valueKind; - if (declaredType.getKind() == TypeKind.ARRAY) { - valueKind = ((ArrayType) declaredType).getComponentType().getKind(); - } else { - valueKind = declaredType.getKind(); - } - if (expr instanceof ArrayInitializerExpr) { - if (declaredType.getKind() != TypeKind.ARRAY) { - throw new AnnotationFileParserException( - "unhandled annotation attribute type: " + expr + " and declaredType: " + declaredType); - } - - List arrayExpressions = ((ArrayInitializerExpr) expr).getValues(); - Object[] values = new Object[arrayExpressions.size()]; - - for (int i = 0; i < arrayExpressions.size(); ++i) { - Expression eltExpr = arrayExpressions.get(i); - values[i] = getValueOfExpressionInAnnotation(name, eltExpr, valueKind); - } - builder.setValue(name, values); - } else { - Object value = getValueOfExpressionInAnnotation(name, expr, valueKind); - if (declaredType.getKind() == TypeKind.ARRAY) { - Object[] valueArray = {value}; - builder.setValue(name, valueArray); - } else { - builderSetValue(builder, name, value); - } - } - } - - /** - * Cast to non-array values so that correct the correct AnnotationBuilder#setValue method is - * called. (Different types of values are handled differently.) - * - * @param builder the builder to side-effect - * @param name the element name - * @param value the element value - */ - private void builderSetValue(AnnotationBuilder builder, String name, Object value) { - if (value instanceof Boolean) { - builder.setValue(name, (Boolean) value); - } else if (value instanceof Character) { - builder.setValue(name, (Character) value); - } else if (value instanceof Class) { - builder.setValue(name, (Class) value); - } else if (value instanceof Double) { - builder.setValue(name, (Double) value); - } else if (value instanceof Enum) { - builder.setValue(name, (Enum) value); - } else if (value instanceof Float) { - builder.setValue(name, (Float) value); - } else if (value instanceof Integer) { - builder.setValue(name, (Integer) value); - } else if (value instanceof Long) { - builder.setValue(name, (Long) value); - } else if (value instanceof Short) { - builder.setValue(name, (Short) value); - } else if (value instanceof String) { - builder.setValue(name, (String) value); - } else if (value instanceof TypeMirror) { - builder.setValue(name, (TypeMirror) value); - } else if (value instanceof VariableElement) { - builder.setValue(name, (VariableElement) value); - } else { - throw new BugInCF("unexpected builder value: %s", value); - } - } - - /** - * Mapping of a name access expression that has already been encountered to the resolved variable - * element. - */ - private final Map findVariableElementNameCache = new HashMap<>(); - - /** - * Returns the element for the given variable. - * - * @param nexpr the variable name - * @return the element for the given variable - */ - private @Nullable VariableElement findVariableElement(NameExpr nexpr) { - if (findVariableElementNameCache.containsKey(nexpr)) { - return findVariableElementNameCache.get(nexpr); - } - - VariableElement res = null; - boolean importFound = false; - for (String imp : importedConstants) { - IPair<@FullyQualifiedName String, String> partitionedName = - AnnotationFileUtil.partitionQualifiedName(imp); - String typeName = partitionedName.first; - String fieldName = partitionedName.second; - if (fieldName.equals(nexpr.getNameAsString())) { - TypeElement enclType = - getTypeElement( - typeName, - String.format("enclosing type of static import %s not found", fieldName), - nexpr); - - if (enclType == null) { - return null; - } else { - importFound = true; - res = findFieldElement(enclType, fieldName, nexpr); - break; - } - } - } - - if (res == null) { - if (importFound) { - // TODO: Is this warning redundant? Maybe imported but invalid types or fields will - // have warnings from above. - stubWarnNotFound(nexpr, nexpr.getName() + " was imported but not found"); - } else { - stubWarnNotFound(nexpr, "static field " + nexpr.getName() + " is not imported"); - } - } - - findVariableElementNameCache.put(nexpr, res); - return res; - } - - /** - * Mapping of a field access expression that has already been encountered to the resolved variable - * element. - */ - private final Map findVariableElementFieldCache = - new HashMap<>(); - - /** - * Returns the VariableElement for the given field access. - * - * @param faexpr a field access expression - * @return the VariableElement for the given field access - */ - @SuppressWarnings("signature:argument.type.incompatible") // string manipulation - private @Nullable VariableElement findVariableElement(FieldAccessExpr faexpr) { - if (findVariableElementFieldCache.containsKey(faexpr)) { - return findVariableElementFieldCache.get(faexpr); - } - TypeElement rcvElt = elements.getTypeElement(faexpr.getScope().toString()); - if (rcvElt == null) { - // Search importedConstants for full annotation name. - for (String imp : importedConstants) { - // TODO: should this use AnnotationFileUtil.partitionQualifiedName? - String[] importDelimited = imp.split("\\."); - if (importDelimited[importDelimited.length - 1].equals(faexpr.getScope().toString())) { - StringBuilder fullAnnotation = new StringBuilder(); - for (int i = 0; i < importDelimited.length - 1; i++) { - fullAnnotation.append(importDelimited[i]); - fullAnnotation.append('.'); - } - fullAnnotation.append(faexpr.getScope().toString()); - rcvElt = elements.getTypeElement(fullAnnotation); - break; - } - } - - if (rcvElt == null) { - stubWarnNotFound(faexpr, "type " + faexpr.getScope() + " not found"); - return null; - } - } - - VariableElement res = findFieldElement(rcvElt, faexpr.getNameAsString(), faexpr); - findVariableElementFieldCache.put(faexpr, res); - return res; - } - - /////////////////////////////////////////////////////////////////////////// - /// Map utilities - /// - - /** - * Just like Map.put, but does not override any existing value in the map. - * - * @param the key type - * @param the value type - * @param m a map - * @param key a key - * @param value the value to associate with the key, if the key isn't already in the map - */ - public static void putIfAbsent(Map m, K key, V value) { - if (key == null) { - throw new BugInCF("AnnotationFileParser: key is null for value " + value); - } - if (!m.containsKey(key)) { - m.put(key, value); - } - } - - /** - * If the key is already in the {@code annotationFileAnnos.declAnnos} map, then add the annos to - * the map value. Otherwise put the key and the annos in the map. - * - * @param key a name (actually declaration element string) - * @param annos the set of declaration annotations on it, as written in the annotation file; is - * not modified - */ - private void putOrAddToDeclAnnos(String key, AnnotationMirrorSet annos) { - AnnotationMirrorSet stored = annotationFileAnnos.declAnnos.get(key); - if (stored == null) { - annotationFileAnnos.declAnnos.put(key, new AnnotationMirrorSet(annos)); - } else { - // TODO: Currently, we assume there can be at most one annotation of the same name - // in both `stored` and `annos`. Maybe we should consider the situation of multiple - // entries having the same name. - AnnotationMirrorSet annotationsToAdd = annos; - if (fileType == AnnotationFileType.JDK_STUB) { - // JDK annotations should not replace any annotation of the same type. - annotationsToAdd = - annos.stream() - .filter(am -> !AnnotationUtils.containsSameByName(stored, am)) - .collect(Collectors.toCollection(AnnotationMirrorSet::new)); - } else { - // Annotations that are not from the annotated JDK may replace existing - // annotations of the same type. - stored.removeIf(am -> AnnotationUtils.containsSameByName(annos, am)); - } - stored.addAll(annotationsToAdd); - } - } - - /** - * Just like Map.put, but modifies an existing annotated type for the given key in {@code m}. If - * {@code m} already has an annotated type for {@code key}, each annotation in {@code newType} - * will replace annotations from the same hierarchy at the same location in the existing annotated - * type. Annotations in other hierarchies will be preserved. - * - * @param m the map to put the new type into - * @param key the key for the map - * @param newType the new type for the key - */ - private void putMerge( - Map m, Element key, AnnotatedTypeMirror newType) { - if (key == null) { - throw new BugInCF("AnnotationFileParser: key is null"); - } - if (m.containsKey(key)) { - AnnotatedTypeMirror existingType = m.get(key); - // If the newType is from a JDK stub file, then keep the existing type. This - // way user-supplied stub files override JDK stub files. - // This works because the JDK is always parsed last, on demand, after all other stub - // files. - if (fileType != AnnotationFileType.JDK_STUB) { - atypeFactory.replaceAnnotations(newType, existingType); - } - // existingType is already in the map, so no need to put into m. - } else { - m.put(key, newType); - } - } - - /** - * Just like Map.putAll, but modifies existing values using {@link #putIfAbsent(Map, Object, - * Object)}. - * - * @param m the destination map - * @param m2 the source map - * @param the key type for the maps - * @param the value type for the maps - */ - public static void putAllNew(Map m, Map m2) { - for (Map.Entry e2 : m2.entrySet()) { - putIfAbsent(m, e2.getKey(), e2.getValue()); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Issue warnings - /// - - /** The warnings that have been issued so far. */ - private static final Set warnings = new HashSet<>(); - - /** - * Issues the given warning about missing elements, only if it has not been previously issued and - * the -AstubWarnIfNotFound command-line argument was passed. - * - * @param astNode where to report errors - * @param warning warning to print - */ - private void stubWarnNotFound(NodeWithRange astNode, String warning) { - stubWarnNotFound(astNode, warning, warnIfNotFound); - } - - /** - * Issues the given warning about missing elements, only if it has not been previously issued and - * the {@code warnIfNotFound} formal parameter is true. - * - * @param astNode where to report errors - * @param warning warning to print - * @param warnIfNotFound whether to print warnings about types/members that were not found - */ - private void stubWarnNotFound(NodeWithRange astNode, String warning, boolean warnIfNotFound) { - if (warnIfNotFound || debugAnnotationFileParser) { - warn(astNode, warning); - } - } - - /** - * Issues the given warning about overwriting bytecode, only if it has not been previously issued - * and the -AstubWarnIfOverwritesBytecode command-line argument was passed. - * - * @param astNode where to report errors - * @param message the warning message to print - */ - @SuppressWarnings("UnusedMethod") // not currently used - private void stubWarnOverwritesBytecode(NodeWithRange astNode, String message) { - if (warnIfStubOverwritesBytecode || debugAnnotationFileParser) { - warn(astNode, message); - } - } - - /** - * Issues a warning, only if it has not been previously issued. - * - * @param astNode where to report errors - * @param warning a format string - * @param args the arguments for {@code warning} - */ - @FormatMethod - private void warn(@Nullable NodeWithRange astNode, String warning, Object... args) { - if (!fileType.isBuiltIn()) { - warn(astNode, String.format(warning, args)); - } - } - - /** - * Issues a warning, only if it has not been previously issued. - * - * @param astNode where to report errors - * @param warning a warning message - */ - private void warn(@Nullable NodeWithRange astNode, String warning) { - if (fileType != AnnotationFileType.JDK_STUB) { - if (warnings.add(warning)) { - processingEnv - .getMessager() - .printMessage(stubWarnDiagnosticKind, fileAndLine(astNode) + warning); - } - } - } - - /** - * If {@code warning} hasn't been printed yet, and {@link #debugAnnotationFileParser} is true, - * prints the given warning as a diagnostic message. - * - * @param fmt format string - * @param args arguments to the format string - */ - @FormatMethod - private void stubDebug(String fmt, Object... args) { - if (debugAnnotationFileParser) { - String warning = String.format(fmt, args); - if (warnings.add(warning)) { - System.out.flush(); - SystemPlume.sleep(1); - processingEnv - .getMessager() - .printMessage(javax.tools.Diagnostic.Kind.NOTE, "AnnotationFileParser: " + warning); - System.out.flush(); - SystemPlume.sleep(1); - } - } - } - - /** - * If {@code warning} hasn't been printed yet, prints the given warning as a diagnostic message. - * Ignores {@code debugAnnotationFileParser}. - * - * @param processingEnv the processing environment - * @param fmt format string - * @param args arguments to the format string - */ - @FormatMethod - /*package-private*/ static void stubDebugStatic( - ProcessingEnvironment processingEnv, String fmt, Object... args) { - String warning = String.format(fmt, args); - if (warnings.add(warning)) { - System.out.flush(); - SystemPlume.sleep(1); - processingEnv - .getMessager() - .printMessage(javax.tools.Diagnostic.Kind.NOTE, "AnnotationFileParser: " + warning); - System.out.flush(); - SystemPlume.sleep(1); - } - } - - /** - * After obtaining the JavaParser AST for an ajava file and the javac tree for its corresponding - * Java file, walks both in tandem. For each program construct with annotations, stores the - * annotations from the ajava file in {@link #annotationFileAnnos} by calling the process method - * corresponding to that construct, such as {@link #processCallableDeclaration} or {@link - * #processField}. - */ - private class AjavaAnnotationCollectorVisitor extends DefaultJointVisitor { - - /** Default constructor. */ - private AjavaAnnotationCollectorVisitor() {} - - // This method overrides super.visitCompilationUnit() to prevent parsing import - // statements. Requiring imports in both ajava file and the source file to be - // exactly same is error-prone and unnecessary. - @Override - public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { - CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); - processCompilationUnit(javacTree, node); - visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); - visitLists(javacTree.getTypeDecls(), node.getTypes()); - return null; - } - - @Override - public Void visitClass(ClassTree javacTree, Node javaParserNode) { - List typeDeclTypeParameters = null; - boolean shouldProcessTypeDecl = - javaParserNode instanceof TypeDeclaration - && !(javaParserNode instanceof AnnotationDeclaration); - Optional typeDeclName = Optional.empty(); - boolean callListener = false; - - if (shouldProcessTypeDecl) { - TypeDeclaration typeDecl = (TypeDeclaration) javaParserNode; - typeDeclName = typeDecl.getFullyQualifiedName(); - callListener = typeDeclName.isPresent() && typeDecl.isTopLevelType(); - } - - if (callListener) { - @SuppressWarnings("optional:method.invocation.invalid") // from callListener - String typeDeclNameString = typeDeclName.get(); - fileElementTypes.preProcessTopLevelType(typeDeclNameString); - } - try { - if (shouldProcessTypeDecl) { - typeDeclTypeParameters = - processTypeDecl((TypeDeclaration) javaParserNode, null, javacTree); - } - super.visitClass(javacTree, javaParserNode); - } finally { - if (typeDeclTypeParameters != null) { - typeParameters.removeAll(typeDeclTypeParameters); - } - if (callListener) { - @SuppressWarnings("optional:method.invocation.invalid") // from callListener - String typeDeclNameString = typeDeclName.get(); - fileElementTypes.postProcessTopLevelType(typeDeclNameString); + // If a member is imported, then consider every containing class to also be imported. + private void addEnclosingTypesToImportedTypes(Element element) { + for (Element enclosedEle : element.getEnclosedElements()) { + if (enclosedEle.getKind().isClass()) { + importedTypes.put( + enclosedEle.getSimpleName().toString(), (TypeElement) enclosedEle); + } } - } + } - return null; + /** + * The main entry point. Parse a stub file and side-effects the {@code annotationFileAnnos} + * argument. + * + * @param filename name of stub file, used only for diagnostic messages + * @param inputStream of stub file to parse + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param annotationFileAnnos annotations from the annotation file; side-effected by this method + * @param fileType the annotation file type and source + * @param fileElementTypes the manager that controls the stub file parsing process + */ + public static void parseStubFile( + String filename, + InputStream inputStream, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileAnnotations annotationFileAnnos, + AnnotationFileType fileType, + AnnotationFileElementTypes fileElementTypes) { + AnnotationFileParser afp = + new AnnotationFileParser( + filename, atypeFactory, processingEnv, fileType, fileElementTypes); + try { + afp.parseStubUnit(inputStream); + afp.process(annotationFileAnnos); + } catch (ParseProblemException e) { + for (Problem p : e.getProblems()) { + afp.warn(null, p.getVerboseMessage()); + } + } catch (Throwable t) { + afp.warn(null, "Parse problem: " + t); + } } - @Override - public Void visitVariable(VariableTree javacTree, Node javaParserNode) { - VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); - if (elt != null) { - if (elt.getKind() == ElementKind.FIELD) { - VariableDeclarator varDecl = (VariableDeclarator) javaParserNode; - processField((FieldDeclaration) varDecl.getParentNode().get(), elt); + /** + * The main entry point when parsing an ajava file. Parses an ajava file and side-effects the + * last two arguments. + * + * @param filename name of ajava file, used only for diagnostic messages + * @param inputStream of ajava file to parse + * @param root javac tree for the file to be parsed + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param ajavaAnnos annotations from the ajava file; side-effected by this method + * @param fileElementTypes the manager that controls the stub file parsing process + */ + public static void parseAjavaFile( + String filename, + InputStream inputStream, + CompilationUnitTree root, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileAnnotations ajavaAnnos, + AnnotationFileElementTypes fileElementTypes) { + AnnotationFileParser afp = + new AnnotationFileParser( + filename, + atypeFactory, + processingEnv, + AnnotationFileType.AJAVA, + fileElementTypes); + try { + afp.parseStubUnit(inputStream); + JavaParserUtil.concatenateAddedStringLiterals(afp.stubUnit); + afp.setRoot(root); + afp.process(ajavaAnnos); + } catch (ParseProblemException e) { + for (Problem p : e.getProblems()) { + afp.warn(null, filename + ": " + p.getVerboseMessage()); + } + } catch (Throwable t) { + afp.warn(null, "Parse problem: " + t); } + } - if (elt.getKind() == ElementKind.ENUM_CONSTANT) { - processEnumConstant((EnumConstantDeclaration) javaParserNode, elt); + /** + * Parse a stub file that is a part of the annotated JDK and side-effects the {@code stubAnnos} + * argument. + * + * @param filename name of stub file, used only for diagnostic messages + * @param inputStream of stub file to parse + * @param atypeFactory the type factory + * @param processingEnv the processing environment + * @param stubAnnos annotations from the stub file; side-effected by this method + * @param fileElementTypes the manager that controls the stub file parsing process + */ + public static void parseJdkFileAsStub( + String filename, + InputStream inputStream, + AnnotatedTypeFactory atypeFactory, + ProcessingEnvironment processingEnv, + AnnotationFileAnnotations stubAnnos, + AnnotationFileElementTypes fileElementTypes) { + Map options = processingEnv.getOptions(); + boolean debugAnnotationFileParser = options.containsKey("stubDebug"); + if (debugAnnotationFileParser) { + stubDebugStatic( + processingEnv, + "parseJdkFileAsStub(%s, _, %s, _, _)%n", + filename, + atypeFactory.getClass().getSimpleName()); } - } - super.visitVariable(javacTree, javaParserNode); - return null; + parseStubFile( + filename, + inputStream, + atypeFactory, + processingEnv, + stubAnnos, + AnnotationFileType.JDK_STUB, + fileElementTypes); } - @Override - public Void visitMethod(MethodTree javacTree, Node javaParserNode) { - List variablesToClear = null; - Element elt = TreeUtils.elementFromDeclaration(javacTree); - if (javaParserNode instanceof CallableDeclaration) { - variablesToClear = - processCallableDeclaration( - (CallableDeclaration) javaParserNode, (ExecutableElement) elt); - } + /** + * Delegate to the Stub Parser to parse the annotation file to an AST, and save it in {@link + * #stubUnit}. Also sets {@link #allAnnotations}. Does not copy annotations out of {@link + * #stubUnit}; that is done by the {@code process*} methods. + * + *

          Subsequently, all work uses the AST. + * + * @param inputStream the stream from which to read an annotation file + */ + private void parseStubUnit(InputStream inputStream) { + stubDebug( + "started parsing annotation file %s for %s", + filename, atypeFactory.getClass().getSimpleName()); + stubUnit = JavaParserUtil.parseStubUnit(inputStream); + + // getImportedAnnotations() also modifies importedConstants and importedTypes. This should + // be refactored to be nicer. + allAnnotations = getImportedAnnotations(); + if (allAnnotations.isEmpty() + && fileType.isStub() + && fileType != AnnotationFileType.AJAVA_AS_STUB) { + // Issue a warning if the stub file contains no import statements. The warning is + // incorrect if the stub file contains fully-qualified annotations. + stubWarnNotFound( + null, + String.format( + "No supported annotations found! Does stub file %s import them?", + filename)); + } + // Annotations in java.lang might be used without an import statement, so add them in case. + allAnnotations.putAll(annosInPackage(findPackage("java.lang", null))); - super.visitMethod(javacTree, javaParserNode); - if (variablesToClear != null) { - typeParameters.removeAll(variablesToClear); - } + if (debugAnnotationFileParser) { + stubDebug( + "finished parsing annotation file %s for %s", + filename, atypeFactory.getClass().getSimpleName()); + } + } - return null; + /** + * Process {@link #stubUnit}, which is the AST produced by {@link #parseStubUnit}. Processing + * means copying annotations from Stub Parser data structures to {@code #annotationFileAnnos}. + * + * @param annotationFileAnnos annotations from the file; side-effected by this method + */ + private void process(AnnotationFileAnnotations annotationFileAnnos) { + this.annotationFileAnnos = annotationFileAnnos; + processStubUnit(this.stubUnit); + this.annotationFileAnnos = null; } - } - /** - * Return the prefix for a warning line: A file name, line number, and column number. - * - * @param astNode where to report errors - * @return file name, line number, and column number - */ - private String fileAndLine(NodeWithRange astNode) { - String filenamePrinted = - (processingEnv.getOptions().containsKey("nomsgtext") - ? new File(filename).getName() - : filename); + /** + * Process the given StubUnit: copy its annotations to {@code #annotationFileAnnos}. + * + * @param su the StubUnit to process + */ + private void processStubUnit(StubUnit su) { + for (CompilationUnit cu : su.getCompilationUnits()) { + processCompilationUnit(cu); + } + } - Optional begin = astNode == null ? Optional.empty() : astNode.getBegin(); - String lineAndColumn = (begin.isPresent() ? begin.get() + ":" : ""); - return filenamePrinted + ":" + lineAndColumn + " "; - } + /** + * Process the given CompilationUnit: copy its annotations to {@code #annotationFileAnnos}. + * + * @param cu the CompilationUnit to process + */ + private void processCompilationUnit(CompilationUnit cu) { + + if (cu.getPackageDeclaration().isPresent()) { + PackageDeclaration pDecl = cu.getPackageDeclaration().get(); + packageAnnos = pDecl.getAnnotations(); + if (debugAnnotationFileParser + || (!warnIfNotFoundIgnoresClasses + && !hasNoAnnotationFileParserWarning(packageAnnos))) { + String packageName = pDecl.getName().toString(); + if (elements.getPackageElement(packageName) == null) { + stubWarnNotFound(pDecl, "package not found: " + packageName); + } + } + processPackage(pDecl); + } else { + packageAnnos = null; + typeBeingParsed = new FqName(null, null); + } - /** An exception indicating a problem while parsing an annotation file. */ - public static class AnnotationFileParserException extends Exception { + if (fileType.isStub()) { + if (cu.getTypes() != null) { + for (TypeDeclaration typeDeclaration : cu.getTypes()) { + Optional typeDeclName = typeDeclaration.getFullyQualifiedName(); + + typeDeclName.ifPresent(fileElementTypes::preProcessTopLevelType); + try { + // Not processing an ajava file, so ignore the return value. + processTypeDecl(typeDeclaration, null, null); + } finally { + typeDeclName.ifPresent(fileElementTypes::postProcessTopLevelType); + } + } + } + } else { + root.accept(new AjavaAnnotationCollectorVisitor(), cu); + } - private static final long serialVersionUID = 20201222; + packageAnnos = null; + } /** - * Create a new AnnotationFileParserException. + * Process the given package declaration: copy its annotations to {@code #annotationFileAnnos}. * - * @param message a description of the problem + * @param packDecl the package declaration to process */ - AnnotationFileParserException(String message) { - super(message); + private void processPackage(PackageDeclaration packDecl) { + assert (packDecl != null); + if (!isAnnotatedForThisChecker(packDecl.getAnnotations())) { + return; + } + String packageName = packDecl.getNameAsString(); + typeBeingParsed = new FqName(packageName, null); + Element elem = elements.getPackageElement(packageName); + // If the element lookup fails (that is, elem == null), it's because we have an annotation + // for a package that isn't on the classpath, which is fine. + if (elem != null) { + recordDeclAnnotation(elem, packDecl.getAnnotations(), packDecl); + } + // TODO: Handle atypes??? } - } - - /////////////////////////////////////////////////////////////////////////// - /// Parse state - /// - - /** Represents a class: its package name and name (including outer class names if any). */ - private static class FqName { - /** Name of the package being parsed, or null. */ - public final @Nullable String packageName; /** - * Name of the type being parsed. Includes outer class names if any. Null if the parser has - * parsed a package declaration but has not yet gotten to a type declaration. + * Returns true if the given program construct need not be read: it is private and one of the + * following is true: + * + *

            + *
          • It is in the annotated JDK. Private constructs can't be referenced outside of the JDK + * and might refer to types that are not accessible. + *
          • It is not an ajava file and {@code -AmergeStubsWithSource} was not supplied. As + * described at https://eisop.github.io/cf/manual/#stub-multiple-specifications, source + * files take precedence over stub files unless {@code -AmergeStubsWithSource} is + * supplied. As described at https://eisop.github.io/cf/manual/#ajava-using, source files + * do not take precedence over ajava files (when reading an ajava file, it is as if {@code + * -AmergeStubsWithSource} were supplied). + *
          + * + * @param node a declaration + * @return true if the given program construct is in the annotated JDK and is private */ - public final @Nullable String className; + private boolean skipNode(NodeWithAccessModifiers node) { + // Must include everything with no access modifier, because stub files are allowed to omit + // the access modifier. Also, interface methods have no access modifier, but they are still + // public. + // Must include protected JDK methods. For example, Object.clone is protected, but it + // contains annotations that apply to calls like `super.clone()` and `myArray.clone()`. + return (fileType == AnnotationFileType.BUILTIN_STUB + || (fileType.isStub() + && fileType != AnnotationFileType.AJAVA_AS_STUB + && !mergeStubsWithSource)) + && node.getModifiers().contains(Modifier.privateModifier()); + } /** - * Create a new FqName, which represents a class. + * Returns the string representation of {@code n}, one one line, truncated to {@code length} + * characters. * - * @param packageName name of the package, or null - * @param className unqualified name of the type, including outer class names if any. May be - * null. + * @param n a JavaParser node + * @param length the maximum length of the string representation + * @return the truncated string representation of {@code n} */ - public FqName(@Nullable String packageName, @Nullable String className) { - this.packageName = packageName; - this.className = className; + private String javaParserNodeToStringTruncated(Node n, int length) { + String oneLine = + n.toString() + .replace("\t", " ") + .replace("\n", " ") + .replace("\r", " ") + .replaceAll(" +", " "); + if (oneLine.length() <= length) { + return oneLine; + } else { + return oneLine.substring(0, length - 3) + "..."; + } } - /** Fully-qualified name of the class. */ - @Override - @SuppressWarnings("signature") // string concatenation - public @FullyQualifiedName String toString() { - if (packageName == null) { - return className; - } else { - return packageName + "." + className; - } + /** + * Process a type declaration: copy its annotations to {@code #annotationFileAnnos}. + * + *

          This method stores the declaration's type parameters in {@link #typeParameters}. When + * processing an ajava file, where traversal is handled externaly by a {@link + * org.checkerframework.framework.ajava.JointJavacJavaParserVisitor}, these type variables must + * be removed after processing the type's members. Otherwise, this method removes them. + * + * @param typeDecl the type declaration to process + * @param outerTypeName the name of the containing class, when processing a nested class; + * otherwise null + * @param classTree the tree corresponding to typeDecl if processing an ajava file, null + * otherwise + * @return a list of types variables for {@code typeDecl}. Only non-null if processing an ajava + * file, in which case the contents should be removed from {@link #typeParameters} after + * processing the type declaration's members + */ + private @Nullable List processTypeDecl( + TypeDeclaration typeDecl, + @Nullable String outerTypeName, + @Nullable ClassTree classTree) { + assert typeBeingParsed != null; + if (skipNode(typeDecl)) { + return null; + } + String innerName; + @FullyQualifiedName String fqTypeName; + TypeElement typeElt; + if (classTree != null) { + typeElt = TreeUtils.elementFromDeclaration(classTree); + innerName = typeElt.getQualifiedName().toString(); + typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); + fqTypeName = typeBeingParsed.toString(); + } else { + String packagePrefix = outerTypeName == null ? "" : outerTypeName + "."; + innerName = packagePrefix + typeDecl.getNameAsString(); + typeBeingParsed = new FqName(typeBeingParsed.packageName, innerName); + fqTypeName = typeBeingParsed.toString(); + typeElt = elements.getTypeElement(fqTypeName); + } + + if (!isAnnotatedForThisChecker(typeDecl.getAnnotations())) { + return null; + } + if (typeElt == null) { + if (debugAnnotationFileParser + || (!warnIfNotFoundIgnoresClasses + && !hasNoAnnotationFileParserWarning(typeDecl.getAnnotations()) + && !hasNoAnnotationFileParserWarning(packageAnnos))) { + if (elements.getAllTypeElements(fqTypeName).isEmpty()) { + stubWarnNotFound(typeDecl, "type not found: " + fqTypeName); + } else { + stubWarnNotFound( + typeDecl, + "type not found uniquely: " + + fqTypeName + + " : " + + elements.getAllTypeElements(fqTypeName)); + } + } + return null; + } + + List typeDeclTypeParameters = null; + if (typeElt.getKind() == ElementKind.ENUM) { + if (!(typeDecl instanceof EnumDeclaration)) { + warn( + typeDecl, + innerName + + " is an enum, but stub file declared it as " + + javaParserNodeToStringTruncated(typeDecl, 100)); + return null; + } + typeDeclTypeParameters = processEnum((EnumDeclaration) typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } else if (typeElt.getKind() == ElementKind.ANNOTATION_TYPE) { + if (!(typeDecl instanceof AnnotationDeclaration)) { + warn( + typeDecl, + innerName + + " is an annotation, but stub file declared it as " + + javaParserNodeToStringTruncated(typeDecl, 100)); + return null; + } + typeDeclTypeParameters = processType(typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } else if (typeDecl instanceof ClassOrInterfaceDeclaration) { + // TODO: This test is never satisfied, because it is the opposite of that on the line + // above. + if (!(typeDecl instanceof ClassOrInterfaceDeclaration)) { + warn( + typeDecl, + innerName + + " is a class or interface, but stub file declared it as " + + javaParserNodeToStringTruncated(typeDecl, 100)); + return null; + } + typeDeclTypeParameters = processType(typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } else if (typeDecl instanceof RecordDeclaration) { + typeDeclTypeParameters = processType(typeDecl, typeElt); + typeParameters.addAll(typeDeclTypeParameters); + } // else it's an EmptyTypeDeclaration. TODO: An EmptyTypeDeclaration can have + // annotations, right? + + // If processing an ajava file, then traversal is handled by a visitor, rather than the rest + // of this method. + if (fileType == AnnotationFileType.AJAVA) { + return typeDeclTypeParameters; + } + + if (typeDecl instanceof RecordDeclaration) { + RecordDeclaration recordDecl = (RecordDeclaration) typeDecl; + NodeList recordMembers = recordDecl.getParameters(); + Map byName = + ArrayMap.newArrayMapOrLinkedHashMap(recordMembers.size()); + for (Parameter recordMember : recordMembers) { + RecordComponentStub stub = + processRecordField( + recordMember, + findFieldElement( + typeElt, recordMember.getNameAsString(), recordMember)); + byName.put(recordMember.getNameAsString(), stub); + } + annotationFileAnnos.records.put( + recordDecl.getFullyQualifiedName().get(), new RecordStub(byName)); + } + + IPair>, Map>>> members = + getMembers(typeDecl, typeElt, typeDecl); + for (Map.Entry> entry : members.first.entrySet()) { + Element elt = entry.getKey(); + BodyDeclaration decl = entry.getValue(); + switch (elt.getKind()) { + case FIELD: + processField((FieldDeclaration) decl, (VariableElement) elt); + break; + case ENUM_CONSTANT: + // Enum constants can occur as fields in stubs files when their + // type has an annotation on it, e.g. see DeviceTypeTest which ends up with + // the TRACKER enum constant annotated with DefaultType: + if (decl instanceof FieldDeclaration) { + processField((FieldDeclaration) decl, (VariableElement) elt); + } else if (decl instanceof EnumConstantDeclaration) { + processEnumConstant((EnumConstantDeclaration) decl, (VariableElement) elt); + } else { + throw new BugInCF( + "unexpected decl type " + + decl.getClass() + + " for ENUM_CONSTANT kind, original: " + + decl); + } + break; + case CONSTRUCTOR: + case METHOD: + processCallableDeclaration( + (CallableDeclaration) decl, (ExecutableElement) elt); + break; + case CLASS: + case INTERFACE: + // Not processing an ajava file, so ignore the return value. + processTypeDecl((ClassOrInterfaceDeclaration) decl, innerName, null); + break; + case ENUM: + // Not processing an ajava file, so ignore the return value. + processTypeDecl((EnumDeclaration) decl, innerName, null); + break; + default: + /* do nothing */ + stubWarnNotFound(decl, "AnnotationFileParser ignoring: " + elt); + break; + } + } + for (Map.Entry>> entry : members.second.entrySet()) { + ExecutableElement fakeOverridden = (ExecutableElement) entry.getKey(); + List> fakeOverrideDecls = entry.getValue(); + for (BodyDeclaration bodyDecl : fakeOverrideDecls) { + processFakeOverride(fakeOverridden, (CallableDeclaration) bodyDecl, typeElt); + } + } + + if (typeDeclTypeParameters != null) { + typeParameters.removeAll(typeDeclTypeParameters); + } + + return null; + } + + /** + * Returns true if the argument contains {@code @NoAnnotationFileParserWarning}. + * + * @param aexprs collection of annotation expressions + * @return true if {@code aexprs} contains {@code @NoAnnotationFileParserWarning} + */ + private boolean hasNoAnnotationFileParserWarning(Iterable aexprs) { + if (aexprs == null) { + return false; + } + for (AnnotationExpr anno : aexprs) { + if (anno.getNameAsString().equals("NoAnnotationFileParserWarning")) { + return true; + } + } + return false; + } + + /** + * Process the type's declaration: copy its annotations to {@code #annotationFileAnnos}. Does + * not process any of its members. Returns the type's type parameter declarations. + * + * @param decl a type declaration + * @param elt the type's element + * @return the type's type parameter declarations + */ + private List processType(TypeDeclaration decl, TypeElement elt) { + + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + AnnotatedDeclaredType type = atypeFactory.fromElement(elt); + annotate(type, decl.getAnnotations(), decl); + + List typeArguments = type.getTypeArguments(); + List typeParameters; + if (decl instanceof NodeWithTypeParameters) { + typeParameters = ((NodeWithTypeParameters) decl).getTypeParameters(); + } else { + typeParameters = Collections.emptyList(); + } + + // It can be the case that args=[] and params=null, so don't crash in that case. + // if ((typeParameters == null) != (typeArguments == null)) { + // throw new Error(String.format("parseType (%s, %s): inconsistent nullness for args and + // params%n args = %s%n params = %s%n", decl, elt, typeArguments, typeParameters)); + // } + + if (debugAnnotationFileParser) { + int numParams = (typeParameters == null ? 0 : typeParameters.size()); + int numArgs = (typeArguments == null ? 0 : typeArguments.size()); + if (numParams != numArgs) { + stubDebug( + "parseType: mismatched sizes for typeParameters=%s (size %d)" + + " and typeArguments=%s (size %d);" + + " decl=%s; elt=%s (%s); type=%s (%s); typeBeingParsed=%s", + typeParameters, + numParams, + typeArguments, + numArgs, + decl.toString().replace(LINE_SEPARATOR, " "), + elt.toString().replace(LINE_SEPARATOR, " "), + elt.getClass(), + type, + type.getClass(), + typeBeingParsed); + stubDebug("proceeding despite mismatched sizes"); + } + } + + annotateTypeParameters(decl, elt, typeArguments, typeParameters); + if (decl instanceof ClassOrInterfaceDeclaration) { + annotateSupertypes((ClassOrInterfaceDeclaration) decl, type); + } + putMerge(annotationFileAnnos.atypes, elt, type); + List typeVariables = new ArrayList<>(type.getTypeArguments().size()); + for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { + if (typeV.getKind() != TypeKind.TYPEVAR) { + warn( + decl, + "expected an AnnotatedTypeVariable but found type kind " + + typeV.getKind() + + ": " + + typeV); + } else { + typeVariables.add((AnnotatedTypeVariable) typeV); + } + } + return typeVariables; + } + + /** + * Process an enum: copy its annotations to {@code #annotationFileAnnos}. Returns the enum's + * type parameter declarations. + * + * @param decl enum declaration + * @param elt element representing enum + * @return the enum's type parameter declarations + */ + private List processEnum(EnumDeclaration decl, TypeElement elt) { + + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + AnnotatedDeclaredType type = atypeFactory.fromElement(elt); + annotate(type, decl.getAnnotations(), decl); + + putMerge(annotationFileAnnos.atypes, elt, type); + List typeVariables = new ArrayList<>(type.getTypeArguments().size()); + for (AnnotatedTypeMirror typeV : type.getTypeArguments()) { + if (typeV.getKind() != TypeKind.TYPEVAR) { + warn( + decl, + "expected an AnnotatedTypeVariable but found type kind " + + typeV.getKind() + + ": " + + typeV); + } else { + typeVariables.add((AnnotatedTypeVariable) typeV); + } + } + return typeVariables; + } + + private void annotateSupertypes( + ClassOrInterfaceDeclaration typeDecl, AnnotatedDeclaredType type) { + if (typeDecl.getExtendedTypes() != null) { + for (ClassOrInterfaceType supertype : typeDecl.getExtendedTypes()) { + AnnotatedDeclaredType annotatedSupertype = + findAnnotatedType(supertype, type.directSupertypes(), typeDecl); + if (annotatedSupertype == null) { + warn( + typeDecl, + "stub file does not match bytecode: " + + "could not find direct superclass " + + supertype + + " from type " + + type); + } else { + annotate(annotatedSupertype, supertype, null, typeDecl); + } + } + } + if (typeDecl.getImplementedTypes() != null) { + for (ClassOrInterfaceType supertype : typeDecl.getImplementedTypes()) { + AnnotatedDeclaredType annotatedSupertype = + findAnnotatedType(supertype, type.directSupertypes(), typeDecl); + if (annotatedSupertype == null) { + warn( + typeDecl, + "stub file does not match bytecode: " + + "could not find direct superinterface " + + supertype + + " from type " + + type); + } else { + annotate(annotatedSupertype, supertype, null, typeDecl); + } + } + } + } + + /** + * Process a method or constructor declaration: copy its annotations to {@code + * #annotationFileAnnos}. + * + * @param decl a method or constructor declaration, as read from an annotation file + * @param elt the method or constructor's element + * @return type variables for the method + */ + private @Nullable List processCallableDeclaration( + CallableDeclaration decl, ExecutableElement elt) { + if (!isAnnotatedForThisChecker(decl.getAnnotations())) { + return null; + } + // Declaration annotations + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + if (decl.isMethodDeclaration()) { + // AnnotationFileParser parses all annotations in type annotation position as type + // annotations. + recordDeclAnnotation(elt, ((MethodDeclaration) decl).getType().getAnnotations(), decl); + } + markAsFromStubFile(elt); + + AnnotatedExecutableType methodType; + try { + methodType = atypeFactory.fromElement(elt); + } catch (ErrorTypeKindException e) { + stubWarnNotFound(decl, "Error type kind occurred: " + e.getLocalizedMessage()); + return Collections.emptyList(); + } + + AnnotatedExecutableType origMethodType = + warnIfStubRedundantWithBytecode ? methodType.deepCopy() : null; + + // Type Parameters + annotateTypeParameters(decl, elt, methodType.getTypeVariables(), decl.getTypeParameters()); + typeParameters.addAll(methodType.getTypeVariables()); + + // Return type, from declaration annotations on the method or constructor + if (decl.isMethodDeclaration()) { + MethodDeclaration methodDeclaration = (MethodDeclaration) decl; + if (methodDeclaration.getParameters().isEmpty()) { + String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); + RecordStub recordStub = annotationFileAnnos.records.get(qualRecordName); + if (recordStub != null) { + RecordComponentStub recordComponentStub = + recordStub.componentsByName.get(methodDeclaration.getNameAsString()); + if (recordComponentStub != null) { + recordComponentStub.hasAccessorInStubs = true; + } + } + } + + try { + annotate( + methodType.getReturnType(), + methodDeclaration.getType(), + decl.getAnnotations(), + decl); + } catch (ErrorTypeKindException e) { + // See https://github.com/typetools/checker-framework/issues/244 . + // Issue a warning, to enable fixes to the classpath. + stubWarnNotFound(decl, "Error type kind occurred: " + e); + } + } else { + assert decl.isConstructorDeclaration(); + if (AnnotationFileUtil.isCanonicalConstructor(elt, atypeFactory.types)) { + // If this is the (user-written) canonical constructor, record that the component + // annotations should not be automatically transferred: + String qualRecordName = ElementUtils.getQualifiedName(elt.getEnclosingElement()); + if (annotationFileAnnos.records.containsKey(qualRecordName)) { + List parameters = elt.getParameters(); + ArrayList annotatedParameters = + new ArrayList<>(parameters.size()); + for (int i = 0; i < parameters.size(); i++) { + VariableElement parameter = parameters.get(i); + AnnotatedTypeMirror atm = + AnnotatedTypeMirror.createType( + parameter.asType(), atypeFactory, false); + annotate(atm, decl.getParameter(i).getAnnotations(), decl.getParameter(i)); + annotatedParameters.add(atm); + } + annotationFileAnnos.records.get(qualRecordName) + .componentsInCanonicalConstructor = + annotatedParameters; + } + } + annotate(methodType.getReturnType(), decl.getAnnotations(), decl); + } + + // Parameters + processParameters(decl, elt, methodType); + + // Receiver + if (decl.getReceiverParameter().isPresent()) { + ReceiverParameter receiverParameter = decl.getReceiverParameter().get(); + if (methodType.getReceiverType() == null) { + if (decl.isConstructorDeclaration()) { + warn( + receiverParameter, + "parseParameter: constructor %s of a top-level class" + + " cannot have receiver annotations %s", + methodType, + receiverParameter.getAnnotations()); + } else { + warn( + receiverParameter, + "parseParameter: static method %s cannot have receiver annotations %s", + methodType, + receiverParameter.getAnnotations()); + } + } else { + // Add declaration annotations. + annotate( + methodType.getReceiverType(), + receiverParameter.getAnnotations(), + receiverParameter); + // Add type annotations. + annotate( + methodType.getReceiverType(), + receiverParameter.getType(), + receiverParameter.getAnnotations(), + receiverParameter); + } + } + + if (warnIfStubRedundantWithBytecode + && methodType.toString().equals(origMethodType.toString()) + && fileType != AnnotationFileType.BUILTIN_STUB) { + warn( + decl, + String.format( + "stub file specification is same as bytecode for %s", + ElementUtils.getQualifiedName(elt))); + } + + // Store the type. + putMerge(annotationFileAnnos.atypes, elt, methodType); + if (fileType.isStub()) { + typeParameters.removeAll(methodType.getTypeVariables()); + } + + return methodType.getTypeVariables(); + } + + /** + * Process the parameters of a method or constructor declaration: copy their annotations to + * {@code #annotationFileAnnos}. + * + * @param method a method or constructor declaration + * @param elt the element for {@code method} + * @param methodType the annotated type of {@code method} + */ + private void processParameters( + CallableDeclaration method, + ExecutableElement elt, + AnnotatedExecutableType methodType) { + List params = method.getParameters(); + List paramElts = elt.getParameters(); + List paramTypes = methodType.getParameterTypes(); + + for (int i = 0; i < methodType.getParameterTypes().size(); ++i) { + VariableElement paramElt = paramElts.get(i); + AnnotatedTypeMirror paramType = paramTypes.get(i); + Parameter param = params.get(i); + + recordDeclAnnotation(paramElt, param.getAnnotations(), param); + recordDeclAnnotation(paramElt, param.getType().getAnnotations(), param); + + if (param.isVarArgs()) { + assert paramType.getKind() == TypeKind.ARRAY; + // The "type" of param is actually the component type of the vararg. + // For example, in "Object..." the type would be "Object". + annotate( + ((AnnotatedArrayType) paramType).getComponentType(), + param.getType(), + param.getAnnotations(), + param); + // The "VarArgsAnnotations" are those just before "...". + annotate(paramType, param.getVarArgsAnnotations(), param); + } else { + annotate(paramType, param.getType(), param.getAnnotations(), param); + putMerge(annotationFileAnnos.atypes, paramElt, paramType); + } + } + } + + /** + * Clear (remove) existing annotations on the type. + * + *

          Stub files override annotations read from .class files. Using {@code replaceAnnotation} + * usually achieves this; however, for annotations on type variables, it is sometimes necessary + * to remove an existing annotation, leaving no annotation on the type variable. This method + * does so. + * + * @param atype the type to modify + * @param typeDef the type from the annotation file, used only for diagnostic messages + */ + @SuppressWarnings("unused") // for disabled warning message + private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) { + // TODO: only produce output if the removed annotation isn't the top or default + // annotation in the type hierarchy. See https://tinyurl.com/cfissue/2759 . + /* + if (!atype.getAnnotations().isEmpty()) { + stubWarnOverwritesBytecode( + String.format( + "in file %s at line %s removed existing annotations on type: %s", + filename.substring(filename.lastIndexOf('/') + 1), + typeDef.getBegin().get().line, + atype.toString(true))); + } + */ + // Clear existing annotations, which only makes a difference for + // type variables, but doesn't hurt in other cases. + atype.clearAnnotations(); + } + + /** + * Add the annotations from {@code type} to {@code atype}. Type annotations that parsed as + * declaration annotations (i.e., type annotations in {@code declAnnos}) are applied to the + * innermost component type. + * + * @param atype annotated type to which to add annotations + * @param type parsed type + * @param declAnnos annotations stored on the declaration of the variable with this type or null + * @param astNode where to report errors + */ + private void annotateAsArray( + AnnotatedArrayType atype, + ReferenceType type, + @Nullable NodeList declAnnos, + NodeWithRange astNode) { + annotateInnermostComponentType(atype, declAnnos, astNode); + Type typeDef = type; + AnnotatedTypeMirror currentAtype = atype; + while (typeDef.isArrayType()) { + if (currentAtype.getKind() != TypeKind.ARRAY) { + warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); + return; + } + + // handle generic type + clearAnnotations(currentAtype, typeDef); + + List annotations = typeDef.getAnnotations(); + if (annotations != null) { + annotate(currentAtype, annotations, astNode); + } + typeDef = ((com.github.javaparser.ast.type.ArrayType) typeDef).getComponentType(); + currentAtype = ((AnnotatedArrayType) currentAtype).getComponentType(); + } + if (currentAtype.getKind() == TypeKind.ARRAY) { + warn(astNode, "mismatched array lengths; atype: " + atype + "%n type: " + type); + } + } + + private @Nullable ClassOrInterfaceType unwrapDeclaredType(Type type) { + if (type instanceof ClassOrInterfaceType) { + return (ClassOrInterfaceType) type; + } else if (type instanceof ReferenceType && type.getArrayLevel() == 0) { + return unwrapDeclaredType(type.getElementType()); + } else { + return null; + } + } + + /** + * Add to formal parameter {@code atype}: + * + *

            + *
          1. the annotations from {@code typeDef}, and + *
          2. any type annotations that parsed as declaration annotations (i.e., type annotations in + * {@code declAnnos}). + *
          + * + * @param atype annotated type to which to add annotations + * @param typeDef parsed type + * @param declAnnos annotations stored on the declaration of the variable with this type, or + * null + * @param astNode where to report errors + */ + private void annotate( + AnnotatedTypeMirror atype, + Type typeDef, + @Nullable NodeList declAnnos, + NodeWithRange astNode) { + if (atype.getKind() == TypeKind.ARRAY) { + if (typeDef instanceof ReferenceType) { + annotateAsArray( + (AnnotatedArrayType) atype, (ReferenceType) typeDef, declAnnos, astNode); + } else { + warn(astNode, "expected ReferenceType but found: " + typeDef); + } + return; + } + + clearAnnotations(atype, typeDef); + + // Primary annotations for the type of a variable declaration are not stored in typeDef, but + // rather as declaration annotations (passed as declAnnos to this method). But, if typeDef + // is not the type of a variable, then the primary annotations are stored in typeDef. + NodeList primaryAnnotations; + if (typeDef.getAnnotations().isEmpty() && declAnnos != null) { + primaryAnnotations = declAnnos; + } else { + primaryAnnotations = typeDef.getAnnotations(); + } + if (atype.getKind() != TypeKind.WILDCARD) { + // The primary annotation on a wildcard applies to the super or extends bound and + // are added below. + annotate(atype, primaryAnnotations, astNode); + } + + switch (atype.getKind()) { + case DECLARED: + ClassOrInterfaceType declType = unwrapDeclaredType(typeDef); + if (declType == null) { + break; + } + AnnotatedDeclaredType adeclType = (AnnotatedDeclaredType) atype; + // Process type arguments. + @SuppressWarnings( + "optional:optional.collection") // JavaParser uses Optional + Optional> oDeclTypeArgs = declType.getTypeArguments(); + List adeclTypeArgs = adeclType.getTypeArguments(); + if (oDeclTypeArgs.isPresent() + && !oDeclTypeArgs.get().isEmpty() + && !adeclTypeArgs.isEmpty()) { + NodeList declTypeArgs = oDeclTypeArgs.get(); + if (declTypeArgs.size() != adeclTypeArgs.size()) { + warn( + astNode, + String.format( + "Mismatch in type argument size between %s (%d) and %s (%d)", + declType, + declTypeArgs.size(), + adeclType, + adeclTypeArgs.size())); + break; + } + for (int i = 0; i < declTypeArgs.size(); ++i) { + annotate(adeclTypeArgs.get(i), declTypeArgs.get(i), null, astNode); + } + } + break; + case WILDCARD: + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atype; + // Ensure that the file also has a wildcard type, report an error otherwise + if (!typeDef.isWildcardType()) { + // We throw an error here, as otherwise we are just getting a generic cast error + // on the very next line. + warn( + astNode, + "wildcard type <" + + atype + + "> does not match type in stubs file" + + filename + + ": <" + + typeDef + + ">" + + " while parsing " + + typeBeingParsed); + return; + } + WildcardType wildcardDef = (WildcardType) typeDef; + if (wildcardDef.getExtendedType().isPresent()) { + annotate( + wildcardType.getExtendsBound(), + wildcardDef.getExtendedType().get(), + null, + astNode); + annotate(wildcardType.getSuperBound(), primaryAnnotations, astNode); + } else if (wildcardDef.getSuperType().isPresent()) { + annotate( + wildcardType.getSuperBound(), + wildcardDef.getSuperType().get(), + null, + astNode); + annotate(wildcardType.getExtendsBound(), primaryAnnotations, astNode); + } else if (primaryAnnotations.isEmpty()) { + // Unannotated unbounded wildcard "?": remove any existing annotations and + // add the annotations from the type variable corresponding to the wildcard. + wildcardType.getExtendsBound().clearAnnotations(); + wildcardType.getSuperBound().clearAnnotations(); + AnnotatedTypeVariable atv = + (AnnotatedTypeVariable) + atypeFactory.getAnnotatedType( + wildcardType.getTypeVariable().asElement()); + wildcardType + .getExtendsBound() + .addAnnotations(atv.getUpperBound().getAnnotations()); + wildcardType + .getSuperBound() + .addAnnotations(atv.getLowerBound().getAnnotations()); + } else { + // Annotated unbounded wildcard "@A ?": use annotations. + annotate(atype, primaryAnnotations, astNode); + } + break; + case TYPEVAR: + // Add annotations from the declaration of the TypeVariable + AnnotatedTypeVariable typeVarUse = (AnnotatedTypeVariable) atype; + Types typeUtils = processingEnv.getTypeUtils(); + for (AnnotatedTypeVariable typePar : typeParameters) { + if (typeUtils.isSameType( + typePar.getUnderlyingType(), atype.getUnderlyingType())) { + atypeFactory.replaceAnnotations( + typePar.getUpperBound(), typeVarUse.getUpperBound()); + atypeFactory.replaceAnnotations( + typePar.getLowerBound(), typeVarUse.getLowerBound()); + } + } + // Add back the primary annotations. + annotate(atype, primaryAnnotations, astNode); + break; + default: + // No additional annotations to add. + } + } + + /** + * Process the field declaration in decl: copy its annotations to {@code #annotationFileAnnos}. + * + * @param decl the declaration in the annotation file + * @param elt the element representing that same declaration + */ + private void processField(FieldDeclaration decl, VariableElement elt) { + if (skipNode(decl)) { + // Don't process private fields of the JDK. They can't be referenced outside of the JDK + // and might refer to types that are not accessible. + return; + } + markAsFromStubFile(elt); + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + // AnnotationFileParser parses all annotations in type annotation position as type + // annotations + recordDeclAnnotation(elt, decl.getElementType().getAnnotations(), decl); + AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); + + VariableDeclarator fieldVarDecl = null; + String eltName = elt.getSimpleName().toString(); + for (VariableDeclarator var : decl.getVariables()) { + if (var.getName().toString().equals(eltName)) { + fieldVarDecl = var; + break; + } + } + assert fieldVarDecl != null; + annotate(fieldType, fieldVarDecl.getType(), decl.getAnnotations(), fieldVarDecl); + putMerge(annotationFileAnnos.atypes, elt, fieldType); + } + + /** + * Processes a parameter in a record header (i.e., a record component). + * + * @param decl the parameter in the record header + * @param elt the corresponding variable declaration element + * @return a representation of the record component in the stub file + */ + private RecordComponentStub processRecordField(Parameter decl, VariableElement elt) { + markAsFromStubFile(elt); + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + // AnnotationFileParser parses all annotations in type annotation position as type + // annotations. + recordDeclAnnotation(elt, decl.getType().getAnnotations(), decl); + AnnotatedTypeMirror fieldType = atypeFactory.fromElement(elt); + + annotate(fieldType, decl.getType(), decl.getAnnotations(), decl); + putMerge(annotationFileAnnos.atypes, elt, fieldType); + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + for (AnnotationExpr annotation : decl.getAnnotations()) { + AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); + annos.add(annoMirror); + } + return new RecordComponentStub(fieldType, annos); + } + + /** + * Adds the annotations present on the declaration of an enum constant to the ATM of that + * constant. + * + * @param decl the enum constant, in Javaparser AST form (the source of annotations) + * @param elt the enum constant declaration, as an element (the destination for annotations) + */ + private void processEnumConstant(EnumConstantDeclaration decl, VariableElement elt) { + markAsFromStubFile(elt); + recordDeclAnnotation(elt, decl.getAnnotations(), decl); + AnnotatedTypeMirror enumConstType = atypeFactory.fromElement(elt); + annotate(enumConstType, decl.getAnnotations(), decl); + putMerge(annotationFileAnnos.atypes, elt, enumConstType); + } + + /** + * Returns the innermost component type of {@code type}. + * + * @param type array type + * @return the innermost component type of {@code type} + */ + private AnnotatedTypeMirror innermostComponentType(AnnotatedArrayType type) { + AnnotatedTypeMirror componentType = type; + while (componentType.getKind() == TypeKind.ARRAY) { + componentType = ((AnnotatedArrayType) componentType).getComponentType(); + } + return componentType; + } + + /** + * Adds {@code annotations} to the innermost component type of {@code type}. + * + * @param type array type + * @param annotations annotations to add + * @param astNode where to report errors + */ + private void annotateInnermostComponentType( + AnnotatedArrayType type, List annotations, NodeWithRange astNode) { + annotate(innermostComponentType(type), annotations, astNode); + } + + /** + * Annotate the type with the given type annotations, removing any existing annotations from the + * same qualifier hierarchies. + * + * @param type the type to annotate + * @param annotations the new annotations for the type; if null, nothing is done + * @param astNode where to report errors + */ + private void annotate( + AnnotatedTypeMirror type, + @Nullable List annotations, + NodeWithRange astNode) { + if (annotations == null) { + return; + } + for (AnnotationExpr annotation : annotations) { + AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); + if (annoMirror != null) { + type.replaceAnnotation(annoMirror); + } else { + // TODO: Maybe always warn here. It's so easy to forget an import statement and + // have an annotation silently ignored. + stubWarnNotFound(astNode, "unknown annotation " + annotation); + } + } + } + + /** + * Adds to {@code annotationFileAnnos} all the annotations in {@code annotations} that are + * applicable to {@code elt}'s location. For example, if an annotation is a type annotation but + * {@code elt} is a field declaration, the type annotation will be ignored. + * + * @param elt the element to be annotated + * @param annotations the set of annotations that may be applicable to elt + * @param astNode where to report errors + */ + private void recordDeclAnnotation( + Element elt, List annotations, NodeWithRange astNode) { + if (annotations == null || annotations.isEmpty()) { + return; + } + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + for (AnnotationExpr annotation : annotations) { + AnnotationMirror annoMirror = getAnnotation(annotation, allAnnotations); + if (annoMirror != null) { + // The @Target annotation on `annotation`/`annoMirror` + Target target = + annoMirror.getAnnotationType().asElement().getAnnotation(Target.class); + // Only add the declaration annotation if the annotation applies to the element. + if (AnnotationUtils.getElementKindsForTarget(target).contains(elt.getKind())) { + // `annoMirror` is applicable to `elt` + annos.add(annoMirror); + } + } else { + // TODO: Maybe always warn here. It's so easy to forget an import statement and + // have an annotation silently ignored. + stubWarnNotFound(astNode, String.format("unknown annotation %s", annotation)); + } + } + String eltName = ElementUtils.getQualifiedName(elt); + putOrAddToDeclAnnos(eltName, annos); + } + + /** + * Adds the declaration annotation {@code @FromStubFile} to {@link #annotationFileAnnos}, unless + * we are parsing the JDK as a stub file. + * + * @param elt an element to be annotated as {@code @FromStubFile} + */ + private void markAsFromStubFile(Element elt) { + if (fileType == AnnotationFileType.AJAVA || fileType == AnnotationFileType.JDK_STUB) { + return; + } + putOrAddToDeclAnnos( + ElementUtils.getQualifiedName(elt), + AnnotationMirrorSet.singleton(fromStubFileAnno)); + } + + private void annotateTypeParameters( + BodyDeclaration decl, // for debugging + Object elt, // for debugging; TypeElement or ExecutableElement + List typeArguments, + List typeParameters) { + if (typeParameters == null) { + return; + } + + if (typeParameters.size() != typeArguments.size()) { + String msg = + String.format( + "annotateTypeParameters: mismatched sizes:" + + " typeParameters (size %d)=%s;" + + " typeArguments (size %d)=%s;" + + " decl=%s; elt=%s (%s).", + typeParameters.size(), + typeParameters, + typeArguments.size(), + typeArguments, + decl.toString().replace(LINE_SEPARATOR, " "), + elt.toString().replace(LINE_SEPARATOR, " "), + elt.getClass()); + if (!debugAnnotationFileParser) { + msg = msg + "; for more details, run with -AstubDebug"; + } + warn(decl, msg); + return; + } + for (int i = 0; i < typeParameters.size(); ++i) { + TypeParameter param = typeParameters.get(i); + AnnotatedTypeVariable paramType = (AnnotatedTypeVariable) typeArguments.get(i); + + // Handle type bounds + if (param.getTypeBound() == null || param.getTypeBound().isEmpty()) { + // No type bound, so annotations are both lower and upper bounds. + annotate(paramType, param.getAnnotations(), param); + } else if (param.getTypeBound() != null && !param.getTypeBound().isEmpty()) { + annotate(paramType.getLowerBound(), param.getAnnotations(), param); + if (param.getTypeBound().size() == 1) { + // The additional declAnnos (third argument) is always null in this call to + // `annotate`, but the type bound (second argument) might have annotations. + annotate(paramType.getUpperBound(), param.getTypeBound().get(0), null, param); + } else { + // param.getTypeBound().size() > 1 + ArrayList typeBoundsWithAnotations = + new ArrayList<>(param.getTypeBound().size()); + for (ClassOrInterfaceType typeBound : param.getTypeBound()) { + if (!typeBound.getAnnotations().isEmpty()) { + typeBoundsWithAnotations.add(typeBound); + } + } + int numBounds = typeBoundsWithAnotations.size(); + if (numBounds == 0) { + // nothing to do + } else if (numBounds == 1) { + annotate( + paramType.getUpperBound(), + typeBoundsWithAnotations.get(0), + null, + param); + } else { + // TODO: add support for intersection types + // One problem is that `annotate()` removes any existing annotations from + // the same qualifier hierarchies, so paramType.getLowerBound() would end up + // with the annotations of only the last type bound. + + // String msg = + // String.format( + // "annotateTypeParameters: multiple type bounds: + // typeParameters=%s; " + // + "param #%d=%s; bounds=%s; decl=%s; elt=%s (%s).", + // typeParameters, + // i, + // param, + // param.getTypeBound(), + // decl.toString().replace(LINE_SEPARATOR, " "), + // elt.toString().replace(LINE_SEPARATOR, " "), + // elt.getClass()); + // warn(decl, msg); + + stubWarnNotFound( + param, + "annotations on intersection types are not yet supported: " + + param); + } + } + if (param.getTypeBound().size() == 1 + && param.getTypeBound().get(0).getAnnotations().isEmpty() + && TypesUtils.isObject(paramType.getUpperBound().getUnderlyingType())) { + // If there is an explicit "T extends Object" type parameter bound, + // treat it like an explicit use of "Object" in code. + AnnotatedTypeMirror ub = atypeFactory.getAnnotatedType(Object.class); + paramType.getUpperBound().replaceAnnotations(ub.getAnnotations()); + } + } + + putMerge( + annotationFileAnnos.atypes, + paramType.getUnderlyingType().asElement(), + paramType); + } + } + + /** + * Returns a pair of mappings. For each member declaration of the JavaParser type declaration + * {@code typeDecl}: + * + *
            + *
          • If {@code typeElt} contains a member element for it, the first mapping maps the member + * element to it. + *
          • If it is a fake override, the second mapping maps each element it overrides to it. + *
          • Otherwise, does nothing. + *
          + * + * This method does not read or write the field {@link #annotationFileAnnos}. + * + * @param typeDecl a JavaParser type declaration + * @param typeElt the javac element for {@code typeDecl} + * @return two mappings: from javac elements to their JavaParser declaration, and from javac + * elements to fake overrides of them + * @param astNode where to report errors + */ + private IPair>, Map>>> + getMembers(TypeDeclaration typeDecl, TypeElement typeElt, NodeWithRange astNode) { + assert (typeElt.getSimpleName().contentEquals(typeDecl.getNameAsString()) + || typeDecl.getNameAsString().endsWith("$" + typeElt.getSimpleName())) + : String.format("%s %s", typeElt.getSimpleName(), typeDecl.getName()); + + Map> elementsToDecl = new LinkedHashMap<>(); + Map>> fakeOverrideDecls = new LinkedHashMap<>(); + + for (BodyDeclaration member : typeDecl.getMembers()) { + putNewElement( + elementsToDecl, + fakeOverrideDecls, + typeElt, + member, + typeDecl.getNameAsString(), + astNode); + } + // For an enum type declaration, also add the enum constants + if (typeDecl instanceof EnumDeclaration) { + EnumDeclaration enumDecl = (EnumDeclaration) typeDecl; + // getEntries() gives the list of enum constant declarations + for (BodyDeclaration member : enumDecl.getEntries()) { + putNewElement( + elementsToDecl, + fakeOverrideDecls, + typeElt, + member, + typeDecl.getNameAsString(), + astNode); + } + } + + return IPair.of(elementsToDecl, fakeOverrideDecls); + } + + // Used only by getMembers(). + /** + * If {@code typeElt} contains an element for {@code member}, adds to {@code elementsToDecl} a + * mapping from member's element to member. Does nothing if a mapping already exists. + * + *

          Otherwise (if there is no element for {@code member}), adds to {@code fakeOverrideDecls} + * zero or more mappings. Each mapping is from an element that {@code member} would override to + * {@code member}. + * + *

          This method does not read or write field {@link #annotationFileAnnos}. + * + * @param elementsToDecl the mapping that is side-effected by this method + * @param fakeOverrideDecls fake overrides, also side-effected by this method + * @param typeElt the class in which {@code member} is declared + * @param member the stub file declaration of a method + * @param typeDeclName used only for debugging + * @param astNode where to report errors + */ + private void putNewElement( + Map> elementsToDecl, + Map>> fakeOverrideDecls, + TypeElement typeElt, + BodyDeclaration member, + String typeDeclName, + NodeWithRange astNode) { + if (member instanceof MethodDeclaration) { + MethodDeclaration method = (MethodDeclaration) member; + Element elt = findElement(typeElt, method, /* noWarn= */ true); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, method); + } else { + ExecutableElement overriddenMethod = fakeOverriddenMethod(typeElt, method); + if (overriddenMethod == null) { + // Didn't find the element and it isn't a fake override. Issue a warning. + findElement(typeElt, method, /* noWarn= */ false); + } else { + List> l = + fakeOverrideDecls.computeIfAbsent( + overriddenMethod, __ -> new ArrayList<>(1)); + l.add(member); + } + } + } else if (member instanceof ConstructorDeclaration) { + Element elt = findElement(typeElt, (ConstructorDeclaration) member); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else if (member instanceof FieldDeclaration) { + FieldDeclaration fieldDecl = (FieldDeclaration) member; + for (VariableDeclarator var : fieldDecl.getVariables()) { + Element varelt = findElement(typeElt, var); + if (varelt != null) { + putIfAbsent(elementsToDecl, varelt, fieldDecl); + } + } + } else if (member instanceof EnumConstantDeclaration) { + Element elt = findElement(typeElt, (EnumConstantDeclaration) member, astNode); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else if (member instanceof ClassOrInterfaceDeclaration) { + Element elt = findElement(typeElt, (ClassOrInterfaceDeclaration) member); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else if (member instanceof EnumDeclaration) { + Element elt = findElement(typeElt, (EnumDeclaration) member); + if (elt != null) { + putIfAbsent(elementsToDecl, elt, member); + } + } else { + stubDebug("ignoring element of type %s in %s", member.getClass(), typeDeclName); + } + } + + /** + * Given a method declaration that does not correspond to an element, returns the method it + * directly overrides or implements. As Java does, this prefers a method in a superclass to one + * in an interface. + * + *

          As with regular overrides, the parameter types must be exact matches; contravariance is + * not permitted. + * + * @param typeElt the type in which the method appears + * @param methodDecl the method declaration that does not correspond to an element + * @return the methods that the given method declaration would override, or null if none + */ + private @Nullable ExecutableElement fakeOverriddenMethod( + TypeElement typeElt, MethodDeclaration methodDecl) { + for (Element elt : typeElt.getEnclosedElements()) { + if (elt.getKind() != ElementKind.METHOD) { + continue; + } + ExecutableElement candidate = (ExecutableElement) elt; + if (!candidate.getSimpleName().contentEquals(methodDecl.getName().getIdentifier())) { + continue; + } + List candidateParams = candidate.getParameters(); + if (sameTypes(candidateParams, methodDecl.getParameters())) { + return candidate; + } + } + + TypeElement superType = ElementUtils.getSuperClass(typeElt); + if (superType != null) { + ExecutableElement result = fakeOverriddenMethod(superType, methodDecl); + if (result != null) { + return result; + } + } + + for (TypeMirror interfaceTypeMirror : typeElt.getInterfaces()) { + TypeElement interfaceElement = + (TypeElement) ((DeclaredType) interfaceTypeMirror).asElement(); + ExecutableElement result = fakeOverriddenMethod(interfaceElement, methodDecl); + if (result != null) { + return result; + } + } + + return null; + } + + /** + * Returns true if the two signatures (represented as lists of formal parameters) are the same. + * No contravariance is permitted. + * + * @param javacParams parameter list in javac form + * @param javaParserParams parameter list in JavaParser form + * @return true if the two signatures are the same + */ + private boolean sameTypes( + List javacParams, NodeList javaParserParams) { + if (javacParams.size() != javaParserParams.size()) { + return false; + } + for (int i = 0; i < javacParams.size(); i++) { + TypeMirror javacType = javacParams.get(i).asType(); + Parameter javaParserParam = javaParserParams.get(i); + Type javaParserType = javaParserParam.getType(); + if (javacType.getKind() == TypeKind.TYPEVAR) { + // TODO: Hack, need to viewpoint-adapt. + javacType = ((TypeVariable) javacType).getUpperBound(); + } + if (!sameType(javacType, javaParserType)) { + return false; + } + } + return true; + } + + /** + * Returns true if the two types are the same. + * + * @param javacType type in javac form + * @param javaParserType type in JavaParser form + * @return true if the two types are the same + */ + private boolean sameType(TypeMirror javacType, Type javaParserType) { + + switch (javacType.getKind()) { + case BOOLEAN: + return javaParserType.equals(PrimitiveType.booleanType()); + case BYTE: + return javaParserType.equals(PrimitiveType.byteType()); + case CHAR: + return javaParserType.equals(PrimitiveType.charType()); + case DOUBLE: + return javaParserType.equals(PrimitiveType.doubleType()); + case FLOAT: + return javaParserType.equals(PrimitiveType.floatType()); + case INT: + return javaParserType.equals(PrimitiveType.intType()); + case LONG: + return javaParserType.equals(PrimitiveType.longType()); + case SHORT: + return javaParserType.equals(PrimitiveType.shortType()); + + case DECLARED: + case TYPEVAR: + if (!(javaParserType instanceof ClassOrInterfaceType)) { + return false; + } + com.sun.tools.javac.code.Type javacTypeInternal = + (com.sun.tools.javac.code.Type) javacType; + ClassOrInterfaceType javaParserClassType = (ClassOrInterfaceType) javaParserType; + + // Use asString() because toString() includes annotations. + String javaParserString = javaParserClassType.asString(); + Element javacElement = javacTypeInternal.asElement(); + // Check both fully-qualified name and simple name. + return javacElement.toString().equals(javaParserString) + || javacElement.getSimpleName().contentEquals(javaParserString); + + case ARRAY: + return javaParserType.isArrayType() + && sameType( + ((ArrayType) javacType).getComponentType(), + javaParserType.asArrayType().getComponentType()); + + default: + throw new BugInCF("unhandled type %s of kind %s", javacType, javacType.getKind()); + } + } + + /** + * Process a fake override: copy its annotations to the fake overrides part of {@code + * #annotationFileAnnos}. + * + * @param element a real element + * @param decl a fake override of the element + * @param fakeLocation where the fake override was defined + */ + private void processFakeOverride( + ExecutableElement element, CallableDeclaration decl, TypeElement fakeLocation) { + // This is a fresh type, which this code may side-effect. + AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(element); + + // Here is a hacky solution that does not use the visitor. It just handles the return type. + // TODO: Walk the type and the declaration, copying annotations from the declaration to the + // element. I think PR #3977 has a visitor that does that, which I should use after it is + // merged. + + // The annotations on the method. These include type annotations on the return type. + NodeList annotations = decl.getAnnotations(); + annotate( + methodType.getReturnType(), + ((MethodDeclaration) decl).getType(), + annotations, + decl); + + List> l = + annotationFileAnnos.fakeOverrides.computeIfAbsent( + element, __ -> new ArrayList<>(1)); + l.add(IPair.of(fakeLocation.asType(), methodType)); + } + + /** + * Return the annotated type corresponding to {@code type}, or null if none exists. More + * specifically, returns the element of {@code types} whose name matches {@code type}. + * + * @param type the type to search for + * @param types the list of AnnotatedDeclaredTypes to search in + * @param astNode where to report errors + * @return the annotated type in {@code types} corresponding to {@code type}, or null if none + * exists + */ + private @Nullable AnnotatedDeclaredType findAnnotatedType( + ClassOrInterfaceType type, + List types, + NodeWithRange astNode) { + String typeString = type.getNameAsString(); + for (AnnotatedDeclaredType supertype : types) { + if (supertype + .getUnderlyingType() + .asElement() + .getSimpleName() + .contentEquals(typeString)) { + return supertype; + } + } + stubWarnNotFound(astNode, "direct supertype " + typeString + " not found"); + if (debugAnnotationFileParser) { + stubDebug("direct supertypes that were searched:"); + for (AnnotatedDeclaredType supertype : types) { + stubDebug(" %s", supertype); + } + } + return null; + } + + /** + * Looks for the nested type element in the typeElt and returns it if the element has the same + * name as provided class or interface declaration. In case nested element is not found it + * returns null. + * + * @param typeElt an element where nested type element should be looked for + * @param ciDecl class or interface declaration which name should be found among nested elements + * of the typeElt + * @return nested in typeElt element with the name of the class or interface, or null if nested + * element is not found + */ + private @Nullable Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) { + String wantedClassOrInterfaceName = ciDecl.getNameAsString(); + for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { + if (wantedClassOrInterfaceName.equals(typeElement.getSimpleName().toString())) { + return typeElement; + } + } + + stubWarnNotFound( + ciDecl, + "class/interface " + wantedClassOrInterfaceName + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + stubDebug(" type declarations of %s:", typeElt); + for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + return null; + } + + /** + * Looks for the nested enum element in the typeElt and returns it if the element has the same + * name as provided enum declaration. In case nested element is not found it returns null. + * + * @param typeElt an element where nested enum element should be looked for + * @param enumDecl enum declaration which name should be found among nested elements of the + * typeElt + * @return nested in typeElt enum element with the name of the provided enum, or null if nested + * element is not found + */ + private @Nullable Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) { + String wantedEnumName = enumDecl.getNameAsString(); + for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { + if (wantedEnumName.equals(typeElement.getSimpleName().toString())) { + return typeElement; + } + } + + stubWarnNotFound(enumDecl, "enum " + wantedEnumName + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + stubDebug(" type declarations of %s:", typeElt); + for (TypeElement method : ElementFilter.typesIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + return null; + } + + /** + * Looks for an enum constant element in the typeElt and returns it if the element has the same + * name as provided. In case enum constant element is not found it returns null. + * + * @param typeElt type element where enum constant element should be looked for + * @param enumConstDecl the declaration of the enum constant + * @param astNode where to report errors + * @return enum constant element in typeElt with the provided name, or null if enum constant + * element is not found + */ + private @Nullable VariableElement findElement( + TypeElement typeElt, EnumConstantDeclaration enumConstDecl, NodeWithRange astNode) { + String enumConstName = enumConstDecl.getNameAsString(); + return findFieldElement(typeElt, enumConstName, astNode); + } + + /** + * Looks for a method element in {@code typeElt} that has the same name and formal parameter + * types as {@code methodDecl}. Returns null, and possibly issues a warning, if no such method + * element is found. + * + * @param typeElt type element where method element should be looked for + * @param methodDecl method declaration with signature that should be found among methods in the + * typeElt + * @param noWarn if true, don't issue a warning if the element is not found + * @return method element in typeElt with the same signature as the provided method declaration + * or null if method element is not found + */ + private @Nullable ExecutableElement findElement( + TypeElement typeElt, MethodDeclaration methodDecl, boolean noWarn) { + if (skipNode(methodDecl)) { + return null; + } + String wantedMethodName = methodDecl.getNameAsString(); + int wantedMethodParams = + (methodDecl.getParameters() == null) ? 0 : methodDecl.getParameters().size(); + String wantedMethodString = AnnotationFileUtil.toString(methodDecl); + for (ExecutableElement method : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (wantedMethodParams == method.getParameters().size() + && wantedMethodName.contentEquals(method.getSimpleName().toString()) + && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { + return method; + } + } + if (!noWarn) { + if (methodDecl.getAccessSpecifier() == AccessSpecifier.NONE) { + // This might be a false positive warning. The stub parser permits a stub file to + // omit the access specifier, but package-private methods aren't in the TypeElement. + stubWarnNotFound( + methodDecl, + "package-private method " + + wantedMethodString + + " not found in type " + + typeElt + + System.lineSeparator() + + "If the method is not package-private," + + " add an access specifier in the stub file" + + " and use -AstubDebug to receive a more useful error message."); + } else { + stubWarnNotFound( + methodDecl, + "method " + wantedMethodString + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + stubDebug(" methods of %s:", typeElt); + for (ExecutableElement method : + ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + } + } + return null; + } + + /** + * Looks for a constructor element in the typeElt and returns it if the element has the same + * signature as provided constructor declaration. In case constructor element is not found it + * returns null. + * + * @param typeElt type element where constructor element should be looked for + * @param constructorDecl constructor declaration with signature that should be found among + * constructors in the typeElt + * @return constructor element in typeElt with the same signature as the provided constructor + * declaration or null if constructor element is not found + */ + private @Nullable ExecutableElement findElement( + TypeElement typeElt, ConstructorDeclaration constructorDecl) { + if (skipNode(constructorDecl)) { + return null; + } + int wantedMethodParams = + (constructorDecl.getParameters() == null) + ? 0 + : constructorDecl.getParameters().size(); + String wantedMethodString = AnnotationFileUtil.toString(constructorDecl); + for (ExecutableElement method : + ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { + if (wantedMethodParams == method.getParameters().size() + && ElementUtils.getSimpleSignature(method).equals(wantedMethodString)) { + return method; + } + } + + stubWarnNotFound( + constructorDecl, + "constructor " + wantedMethodString + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + for (ExecutableElement method : + ElementFilter.constructorsIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", method); + } + } + return null; + } + + /** + * Returns the element for the given variable. + * + * @param typeElt the type in which the variable is contained + * @param variable the variable whose element to return + * @return the element for the given variable + */ + private VariableElement findElement(TypeElement typeElt, VariableDeclarator variable) { + String fieldName = variable.getNameAsString(); + return findFieldElement(typeElt, fieldName, variable); + } + + /** + * Looks for a field element in the typeElt and returns it if the element has the same name as + * provided. In case field element is not found it returns null. + * + * @param typeElt type element where field element should be looked for + * @param fieldName field name that should be found + * @param astNode where to report errors + * @return field element in typeElt with the provided name or null if field element is not found + */ + private @Nullable VariableElement findFieldElement( + TypeElement typeElt, String fieldName, NodeWithRange astNode) { + for (VariableElement field : ElementUtils.getAllFieldsIn(typeElt, elements)) { + // field.getSimpleName() is a CharSequence, not a String + if (fieldName.equals(field.getSimpleName().toString())) { + return field; + } + } + + stubWarnNotFound(astNode, "field " + fieldName + " not found in type " + typeElt); + if (debugAnnotationFileParser) { + for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements())) { + stubDebug(" %s", field); + } + } + return null; + } + + /** + * Given a fully-qualified type name, return a TypeElement for it, or null if none exists. Also + * cache in importedTypes. + * + * @param name a fully-qualified type name + * @return a TypeElement for the name, or null + */ + private @Nullable TypeElement getTypeElementOrNull(@FullyQualifiedName String name) { + TypeElement typeElement = elements.getTypeElement(name); + if (typeElement != null) { + importedTypes.put(name, typeElement); + } + // for debugging: warn("getTypeElementOrNull(%s) => %s", name, typeElement); + return typeElement; + } + + /** + * Get the type element for the given fully-qualified type name. If none is found, issue a + * warning and return null. + * + * @param typeName a type name + * @param msg a warning message to issue if the type element for {@code typeName} cannot be + * found + * @param astNode where to report errors + * @return the type element for the given fully-qualified type name, or null + */ + private @Nullable TypeElement getTypeElement( + @FullyQualifiedName String typeName, String msg, NodeWithRange astNode) { + TypeElement classElement = elements.getTypeElement(typeName); + if (classElement == null) { + stubWarnNotFound(astNode, msg + ": " + typeName); + } + return classElement; + } + + /** + * Returns the element for the given package. + * + * @param packageName the package's name + * @param astNode where to report errors + * @return the element for the given package + */ + private PackageElement findPackage(String packageName, NodeWithRange astNode) { + PackageElement packageElement = elements.getPackageElement(packageName); + if (packageElement == null) { + stubWarnNotFound(astNode, "imported package not found: " + packageName); + } + return packageElement; + } + + /** + * Returns true if one of the annotations is {@link AnnotatedFor} and this checker is in its + * list of checkers. If none of the annotations are {@code AnnotatedFor}, then also return true. + * + * @param annotations a list of JavaParser annotations + * @return true if one of the annotations is {@link AnnotatedFor} and its list of checkers does + * not contain this checker + */ + private boolean isAnnotatedForThisChecker(List annotations) { + if (fileType == AnnotationFileType.JDK_STUB) { + // The JDK stubs have purity annotations that should be read for all checkers. + // TODO: Parse the JDK stubs, but only save the declaration annotations. + return true; + } + for (AnnotationExpr ae : annotations) { + if (ae.getNameAsString().equals("AnnotatedFor") + || ae.getNameAsString() + .equals("org.checkerframework.framework.qual.AnnotatedFor")) { + AnnotationMirror af = getAnnotation(ae, allAnnotations); + if (atypeFactory.areSameByClass(af, AnnotatedFor.class)) { + return atypeFactory.doesAnnotatedForApplyToThisChecker(af); + } + } + } + return true; + } + + /** + * Convert {@code annotation} into an AnnotationMirror. Returns null if the annotation isn't + * supported by the checker or if some error occurred while converting it. + * + * @param annotation syntax tree for an annotation + * @param allAnnotations map from simple name to annotation definition; side-effected by this + * method + * @return the AnnotationMirror for the annotation, or null if it cannot be built + */ + private @Nullable AnnotationMirror getAnnotation( + AnnotationExpr annotation, Map allAnnotations) { + + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/3094 + @FullyQualifiedName String annoNameFq = annotation.getNameAsString(); + TypeElement annoTypeElt = allAnnotations.get(annoNameFq); + if (annoTypeElt == null) { + // If the annotation was not imported, then #getImportedAnnotations did not add it to + // the allAnnotations field. This code adds the annotation when it is encountered (i.e. + // here). + // Note that this does not call AnnotationFileParser#getTypeElement to avoid a spurious + // diagnostic if the annotation is actually unknown. + annoTypeElt = elements.getTypeElement(annoNameFq); + if (annoTypeElt == null) { + // Not a supported annotation -> ignore + return null; + } + putAllNew( + allAnnotations, + createNameToAnnotationMap(Collections.singletonList(annoTypeElt))); + } + @SuppressWarnings("signature") // not anonymous, so name is not empty + @CanonicalName String annoName = annoTypeElt.getQualifiedName().toString(); + + if (annotation instanceof MarkerAnnotationExpr) { + return AnnotationBuilder.fromName(elements, annoName); + } else if (annotation instanceof NormalAnnotationExpr) { + NormalAnnotationExpr nrmanno = (NormalAnnotationExpr) annotation; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); + List pairs = nrmanno.getPairs(); + if (pairs != null) { + for (MemberValuePair mvp : pairs) { + String member = mvp.getNameAsString(); + Expression exp = mvp.getValue(); + try { + builderAddElement(builder, member, exp); + } catch (AnnotationFileParserException e) { + warn( + exp, + "for annotation %s, could not add %s=%s because %s", + annotation, + member, + exp, + e.getMessage()); + return null; + } + } + } + return builder.build(); + } else if (annotation instanceof SingleMemberAnnotationExpr) { + SingleMemberAnnotationExpr sglanno = (SingleMemberAnnotationExpr) annotation; + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, annoName); + Expression valExpr = sglanno.getMemberValue(); + try { + builderAddElement(builder, "value", valExpr); + } catch (AnnotationFileParserException e) { + warn( + valExpr, + "for annotation %s, could not add value=%s because %s", + annotation, + valExpr, + e.getMessage()); + return null; + } + return builder.build(); + } else { + throw new BugInCF("AnnotationFileParser: unknown annotation type: " + annotation); + } + } + + /** + * Returns the value of {@code expr}. + * + * @param name the name of an annotation element/argument, used for diagnostic messages + * @param expr the expression to determine the value of + * @param valueKind the type of the result + * @return the value of {@code expr} + * @throws AnnotationFileParserException if a problem occurred getting the value + */ + private Object getValueOfExpressionInAnnotation( + String name, Expression expr, TypeKind valueKind) throws AnnotationFileParserException { + if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) { + VariableElement elem; + if (expr instanceof NameExpr) { + elem = findVariableElement((NameExpr) expr); + } else { + elem = findVariableElement((FieldAccessExpr) expr); + } + if (elem == null) { + throw new AnnotationFileParserException( + String.format("variable %s not found", expr)); + } + Object value = elem.getConstantValue() != null ? elem.getConstantValue() : elem; + if (value instanceof Number) { + return convert((Number) value, valueKind); + } else { + return value; + } + } else if (expr instanceof StringLiteralExpr) { + return ((StringLiteralExpr) expr).asString(); + } else if (expr instanceof BooleanLiteralExpr) { + return ((BooleanLiteralExpr) expr).getValue(); + } else if (expr instanceof CharLiteralExpr) { + return convert((int) ((CharLiteralExpr) expr).asChar(), valueKind); + } else if (expr instanceof DoubleLiteralExpr) { + // No conversion needed if the expression is a double, the annotation value must be a + // double, too. + return ((DoubleLiteralExpr) expr).asDouble(); + } else if (expr instanceof IntegerLiteralExpr) { + return convert(((IntegerLiteralExpr) expr).asNumber(), valueKind); + } else if (expr instanceof LongLiteralExpr) { + return convert(((LongLiteralExpr) expr).asNumber(), valueKind); + } else if (expr instanceof UnaryExpr) { + switch (expr.toString()) { + // Special-case the minimum values. Separately parsing a "-" and a value + // doesn't correctly handle the minimum values, because the absolute value of + // the smallest member of an integral type is larger than the largest value. + case "-9223372036854775808L": + case "-9223372036854775808l": + return convert(Long.MIN_VALUE, valueKind, false); + case "-2147483648": + return convert(Integer.MIN_VALUE, valueKind, false); + default: + if (((UnaryExpr) expr).getOperator() == UnaryExpr.Operator.MINUS) { + Object value = + getValueOfExpressionInAnnotation( + name, ((UnaryExpr) expr).getExpression(), valueKind); + if (value instanceof Number) { + return convert((Number) value, valueKind, true); + } + } + throw new AnnotationFileParserException( + "unexpected Unary annotation expression: " + expr); + } + } else if (expr instanceof ClassExpr) { + ClassExpr classExpr = (ClassExpr) expr; + @SuppressWarnings("signature") // Type.toString(): @FullyQualifiedName + @FullyQualifiedName String className = classExpr.getType().toString(); + if (importedTypes.containsKey(className)) { + return importedTypes.get(className).asType(); + } + TypeElement typeElement = findTypeOfName(className); + if (typeElement == null) { + throw new AnnotationFileParserException("unknown class name " + className); + } + + return typeElement.asType(); + } else if (expr instanceof NullLiteralExpr) { + throw new AnnotationFileParserException("illegal annotation value null, for " + name); + } else { + throw new AnnotationFileParserException("unexpected annotation expression: " + expr); + } + } + + /** + * Returns the TypeElement with the name {@code name}, if one exists. Otherwise, checks the + * class and package of {@code typeBeingParsed} for a class named {@code name}. + * + * @param name classname (simple, or Outer.Inner, or fully-qualified) + * @return the TypeElement for {@code name}, or null if not found + */ + @SuppressWarnings("signature:argument.type.incompatible") // string concatenation + private @Nullable TypeElement findTypeOfName(@FullyQualifiedName String name) { + String packageName = typeBeingParsed.packageName; + String packagePrefix = (packageName == null) ? "" : packageName + "."; + + // warn("findTypeOfName(%s), typeBeingParsed %s %s", name, packageName, enclosingClass); + + // As soon as typeElement is set to a non-null value, it will be returned. + TypeElement typeElement = getTypeElementOrNull(name); + if (typeElement == null && packageName != null) { + typeElement = getTypeElementOrNull(packagePrefix + name); + } + String enclosingClass = typeBeingParsed.className; + while (typeElement == null && enclosingClass != null) { + typeElement = getTypeElementOrNull(packagePrefix + enclosingClass + "." + name); + int lastDot = enclosingClass.lastIndexOf('.'); + if (lastDot == -1) { + break; + } else { + enclosingClass = enclosingClass.substring(0, lastDot); + } + } + if (typeElement == null && !"java.lang".equals(packageName)) { + typeElement = getTypeElementOrNull("java.lang." + name); + } + return typeElement; + } + + /** + * Converts {@code number} to {@code expectedKind}. + * + *

          
          +     *   @interface Anno { long value(); }
          +     *   @Anno(1)
          +     * 
          + * + * To properly build @Anno, the IntegerLiteralExpr "1" must be converted from an int to a long. + */ + private Object convert(Number number, TypeKind expectedKind) { + return convert(number, expectedKind, false); + } + + /** + * Converts {@code number} to {@code expectedKind}. The value converted is multiplied by -1 if + * {@code negate} is true + * + * @param number a Number value to be converted + * @param expectedKind one of type {byte, short, int, long, char, float, double} + * @param negate whether to negate the value of the Number Object while converting + * @return the converted Object + */ + private Object convert(Number number, TypeKind expectedKind, boolean negate) { + byte scalefactor = (byte) (negate ? -1 : 1); + switch (expectedKind) { + case BYTE: + return number.byteValue() * scalefactor; + case SHORT: + return number.shortValue() * scalefactor; + case INT: + return number.intValue() * scalefactor; + case LONG: + return number.longValue() * scalefactor; + case CHAR: + // It's not possible for `number` to be negative when `expectedkind` is a CHAR, and + // casting a negative value to char is illegal. + if (negate) { + throw new BugInCF( + "convert(%s, %s, %s): can't negate a char", + number, expectedKind, negate); + } + return (char) number.intValue(); + case FLOAT: + return number.floatValue() * scalefactor; + case DOUBLE: + return number.doubleValue() * scalefactor; + default: + throw new BugInCF("unexpected expectedKind: " + expectedKind); + } + } + + /** + * Adds an annotation element (argument) to {@code builder}. The element is a Java expression. + * + * @param builder the builder to side-effect + * @param name the element name + * @param expr the element value + * @throws AnnotationFileParserException if the expression cannot be parsed and added to {@code + * builder} + */ + private void builderAddElement(AnnotationBuilder builder, String name, Expression expr) + throws AnnotationFileParserException { + ExecutableElement var = builder.findElement(name); + TypeMirror declaredType = var.getReturnType(); + TypeKind valueKind; + if (declaredType.getKind() == TypeKind.ARRAY) { + valueKind = ((ArrayType) declaredType).getComponentType().getKind(); + } else { + valueKind = declaredType.getKind(); + } + if (expr instanceof ArrayInitializerExpr) { + if (declaredType.getKind() != TypeKind.ARRAY) { + throw new AnnotationFileParserException( + "unhandled annotation attribute type: " + + expr + + " and declaredType: " + + declaredType); + } + + List arrayExpressions = ((ArrayInitializerExpr) expr).getValues(); + Object[] values = new Object[arrayExpressions.size()]; + + for (int i = 0; i < arrayExpressions.size(); ++i) { + Expression eltExpr = arrayExpressions.get(i); + values[i] = getValueOfExpressionInAnnotation(name, eltExpr, valueKind); + } + builder.setValue(name, values); + } else { + Object value = getValueOfExpressionInAnnotation(name, expr, valueKind); + if (declaredType.getKind() == TypeKind.ARRAY) { + Object[] valueArray = {value}; + builder.setValue(name, valueArray); + } else { + builderSetValue(builder, name, value); + } + } + } + + /** + * Cast to non-array values so that correct the correct AnnotationBuilder#setValue method is + * called. (Different types of values are handled differently.) + * + * @param builder the builder to side-effect + * @param name the element name + * @param value the element value + */ + private void builderSetValue(AnnotationBuilder builder, String name, Object value) { + if (value instanceof Boolean) { + builder.setValue(name, (Boolean) value); + } else if (value instanceof Character) { + builder.setValue(name, (Character) value); + } else if (value instanceof Class) { + builder.setValue(name, (Class) value); + } else if (value instanceof Double) { + builder.setValue(name, (Double) value); + } else if (value instanceof Enum) { + builder.setValue(name, (Enum) value); + } else if (value instanceof Float) { + builder.setValue(name, (Float) value); + } else if (value instanceof Integer) { + builder.setValue(name, (Integer) value); + } else if (value instanceof Long) { + builder.setValue(name, (Long) value); + } else if (value instanceof Short) { + builder.setValue(name, (Short) value); + } else if (value instanceof String) { + builder.setValue(name, (String) value); + } else if (value instanceof TypeMirror) { + builder.setValue(name, (TypeMirror) value); + } else if (value instanceof VariableElement) { + builder.setValue(name, (VariableElement) value); + } else { + throw new BugInCF("unexpected builder value: %s", value); + } + } + + /** + * Mapping of a name access expression that has already been encountered to the resolved + * variable element. + */ + private final Map findVariableElementNameCache = new HashMap<>(); + + /** + * Returns the element for the given variable. + * + * @param nexpr the variable name + * @return the element for the given variable + */ + private @Nullable VariableElement findVariableElement(NameExpr nexpr) { + if (findVariableElementNameCache.containsKey(nexpr)) { + return findVariableElementNameCache.get(nexpr); + } + + VariableElement res = null; + boolean importFound = false; + for (String imp : importedConstants) { + IPair<@FullyQualifiedName String, String> partitionedName = + AnnotationFileUtil.partitionQualifiedName(imp); + String typeName = partitionedName.first; + String fieldName = partitionedName.second; + if (fieldName.equals(nexpr.getNameAsString())) { + TypeElement enclType = + getTypeElement( + typeName, + String.format( + "enclosing type of static import %s not found", fieldName), + nexpr); + + if (enclType == null) { + return null; + } else { + importFound = true; + res = findFieldElement(enclType, fieldName, nexpr); + break; + } + } + } + + if (res == null) { + if (importFound) { + // TODO: Is this warning redundant? Maybe imported but invalid types or fields will + // have warnings from above. + stubWarnNotFound(nexpr, nexpr.getName() + " was imported but not found"); + } else { + stubWarnNotFound(nexpr, "static field " + nexpr.getName() + " is not imported"); + } + } + + findVariableElementNameCache.put(nexpr, res); + return res; + } + + /** + * Mapping of a field access expression that has already been encountered to the resolved + * variable element. + */ + private final Map findVariableElementFieldCache = + new HashMap<>(); + + /** + * Returns the VariableElement for the given field access. + * + * @param faexpr a field access expression + * @return the VariableElement for the given field access + */ + @SuppressWarnings("signature:argument.type.incompatible") // string manipulation + private @Nullable VariableElement findVariableElement(FieldAccessExpr faexpr) { + if (findVariableElementFieldCache.containsKey(faexpr)) { + return findVariableElementFieldCache.get(faexpr); + } + TypeElement rcvElt = elements.getTypeElement(faexpr.getScope().toString()); + if (rcvElt == null) { + // Search importedConstants for full annotation name. + for (String imp : importedConstants) { + // TODO: should this use AnnotationFileUtil.partitionQualifiedName? + String[] importDelimited = imp.split("\\."); + if (importDelimited[importDelimited.length - 1].equals( + faexpr.getScope().toString())) { + StringBuilder fullAnnotation = new StringBuilder(); + for (int i = 0; i < importDelimited.length - 1; i++) { + fullAnnotation.append(importDelimited[i]); + fullAnnotation.append('.'); + } + fullAnnotation.append(faexpr.getScope().toString()); + rcvElt = elements.getTypeElement(fullAnnotation); + break; + } + } + + if (rcvElt == null) { + stubWarnNotFound(faexpr, "type " + faexpr.getScope() + " not found"); + return null; + } + } + + VariableElement res = findFieldElement(rcvElt, faexpr.getNameAsString(), faexpr); + findVariableElementFieldCache.put(faexpr, res); + return res; + } + + /////////////////////////////////////////////////////////////////////////// + /// Map utilities + /// + + /** + * Just like Map.put, but does not override any existing value in the map. + * + * @param the key type + * @param the value type + * @param m a map + * @param key a key + * @param value the value to associate with the key, if the key isn't already in the map + */ + public static void putIfAbsent(Map m, K key, V value) { + if (key == null) { + throw new BugInCF("AnnotationFileParser: key is null for value " + value); + } + if (!m.containsKey(key)) { + m.put(key, value); + } + } + + /** + * If the key is already in the {@code annotationFileAnnos.declAnnos} map, then add the annos to + * the map value. Otherwise put the key and the annos in the map. + * + * @param key a name (actually declaration element string) + * @param annos the set of declaration annotations on it, as written in the annotation file; is + * not modified + */ + private void putOrAddToDeclAnnos(String key, AnnotationMirrorSet annos) { + AnnotationMirrorSet stored = annotationFileAnnos.declAnnos.get(key); + if (stored == null) { + annotationFileAnnos.declAnnos.put(key, new AnnotationMirrorSet(annos)); + } else { + // TODO: Currently, we assume there can be at most one annotation of the same name + // in both `stored` and `annos`. Maybe we should consider the situation of multiple + // entries having the same name. + AnnotationMirrorSet annotationsToAdd = annos; + if (fileType == AnnotationFileType.JDK_STUB) { + // JDK annotations should not replace any annotation of the same type. + annotationsToAdd = + annos.stream() + .filter(am -> !AnnotationUtils.containsSameByName(stored, am)) + .collect(Collectors.toCollection(AnnotationMirrorSet::new)); + } else { + // Annotations that are not from the annotated JDK may replace existing + // annotations of the same type. + stored.removeIf(am -> AnnotationUtils.containsSameByName(annos, am)); + } + stored.addAll(annotationsToAdd); + } + } + + /** + * Just like Map.put, but modifies an existing annotated type for the given key in {@code m}. If + * {@code m} already has an annotated type for {@code key}, each annotation in {@code newType} + * will replace annotations from the same hierarchy at the same location in the existing + * annotated type. Annotations in other hierarchies will be preserved. + * + * @param m the map to put the new type into + * @param key the key for the map + * @param newType the new type for the key + */ + private void putMerge( + Map m, Element key, AnnotatedTypeMirror newType) { + if (key == null) { + throw new BugInCF("AnnotationFileParser: key is null"); + } + if (m.containsKey(key)) { + AnnotatedTypeMirror existingType = m.get(key); + // If the newType is from a JDK stub file, then keep the existing type. This + // way user-supplied stub files override JDK stub files. + // This works because the JDK is always parsed last, on demand, after all other stub + // files. + if (fileType != AnnotationFileType.JDK_STUB) { + atypeFactory.replaceAnnotations(newType, existingType); + } + // existingType is already in the map, so no need to put into m. + } else { + m.put(key, newType); + } + } + + /** + * Just like Map.putAll, but modifies existing values using {@link #putIfAbsent(Map, Object, + * Object)}. + * + * @param m the destination map + * @param m2 the source map + * @param the key type for the maps + * @param the value type for the maps + */ + public static void putAllNew(Map m, Map m2) { + for (Map.Entry e2 : m2.entrySet()) { + putIfAbsent(m, e2.getKey(), e2.getValue()); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Issue warnings + /// + + /** The warnings that have been issued so far. */ + private static final Set warnings = new HashSet<>(); + + /** + * Issues the given warning about missing elements, only if it has not been previously issued + * and the -AstubWarnIfNotFound command-line argument was passed. + * + * @param astNode where to report errors + * @param warning warning to print + */ + private void stubWarnNotFound(NodeWithRange astNode, String warning) { + stubWarnNotFound(astNode, warning, warnIfNotFound); + } + + /** + * Issues the given warning about missing elements, only if it has not been previously issued + * and the {@code warnIfNotFound} formal parameter is true. + * + * @param astNode where to report errors + * @param warning warning to print + * @param warnIfNotFound whether to print warnings about types/members that were not found + */ + private void stubWarnNotFound( + NodeWithRange astNode, String warning, boolean warnIfNotFound) { + if (warnIfNotFound || debugAnnotationFileParser) { + warn(astNode, warning); + } + } + + /** + * Issues the given warning about overwriting bytecode, only if it has not been previously + * issued and the -AstubWarnIfOverwritesBytecode command-line argument was passed. + * + * @param astNode where to report errors + * @param message the warning message to print + */ + @SuppressWarnings("UnusedMethod") // not currently used + private void stubWarnOverwritesBytecode(NodeWithRange astNode, String message) { + if (warnIfStubOverwritesBytecode || debugAnnotationFileParser) { + warn(astNode, message); + } + } + + /** + * Issues a warning, only if it has not been previously issued. + * + * @param astNode where to report errors + * @param warning a format string + * @param args the arguments for {@code warning} + */ + @FormatMethod + private void warn(@Nullable NodeWithRange astNode, String warning, Object... args) { + if (!fileType.isBuiltIn()) { + warn(astNode, String.format(warning, args)); + } + } + + /** + * Issues a warning, only if it has not been previously issued. + * + * @param astNode where to report errors + * @param warning a warning message + */ + private void warn(@Nullable NodeWithRange astNode, String warning) { + if (fileType != AnnotationFileType.JDK_STUB) { + if (warnings.add(warning)) { + processingEnv + .getMessager() + .printMessage(stubWarnDiagnosticKind, fileAndLine(astNode) + warning); + } + } + } + + /** + * If {@code warning} hasn't been printed yet, and {@link #debugAnnotationFileParser} is true, + * prints the given warning as a diagnostic message. + * + * @param fmt format string + * @param args arguments to the format string + */ + @FormatMethod + private void stubDebug(String fmt, Object... args) { + if (debugAnnotationFileParser) { + String warning = String.format(fmt, args); + if (warnings.add(warning)) { + System.out.flush(); + SystemPlume.sleep(1); + processingEnv + .getMessager() + .printMessage( + javax.tools.Diagnostic.Kind.NOTE, + "AnnotationFileParser: " + warning); + System.out.flush(); + SystemPlume.sleep(1); + } + } + } + + /** + * If {@code warning} hasn't been printed yet, prints the given warning as a diagnostic message. + * Ignores {@code debugAnnotationFileParser}. + * + * @param processingEnv the processing environment + * @param fmt format string + * @param args arguments to the format string + */ + @FormatMethod + /*package-private*/ static void stubDebugStatic( + ProcessingEnvironment processingEnv, String fmt, Object... args) { + String warning = String.format(fmt, args); + if (warnings.add(warning)) { + System.out.flush(); + SystemPlume.sleep(1); + processingEnv + .getMessager() + .printMessage( + javax.tools.Diagnostic.Kind.NOTE, "AnnotationFileParser: " + warning); + System.out.flush(); + SystemPlume.sleep(1); + } + } + + /** + * After obtaining the JavaParser AST for an ajava file and the javac tree for its corresponding + * Java file, walks both in tandem. For each program construct with annotations, stores the + * annotations from the ajava file in {@link #annotationFileAnnos} by calling the process method + * corresponding to that construct, such as {@link #processCallableDeclaration} or {@link + * #processField}. + */ + private class AjavaAnnotationCollectorVisitor extends DefaultJointVisitor { + + /** Default constructor. */ + private AjavaAnnotationCollectorVisitor() {} + + // This method overrides super.visitCompilationUnit() to prevent parsing import + // statements. Requiring imports in both ajava file and the source file to be + // exactly same is error-prone and unnecessary. + @Override + public Void visitCompilationUnit(CompilationUnitTree javacTree, Node javaParserNode) { + CompilationUnit node = castNode(CompilationUnit.class, javaParserNode, javacTree); + processCompilationUnit(javacTree, node); + visitOptional(javacTree.getPackage(), node.getPackageDeclaration()); + visitLists(javacTree.getTypeDecls(), node.getTypes()); + return null; + } + + @Override + public Void visitClass(ClassTree javacTree, Node javaParserNode) { + List typeDeclTypeParameters = null; + boolean shouldProcessTypeDecl = + javaParserNode instanceof TypeDeclaration + && !(javaParserNode instanceof AnnotationDeclaration); + Optional typeDeclName = Optional.empty(); + boolean callListener = false; + + if (shouldProcessTypeDecl) { + TypeDeclaration typeDecl = (TypeDeclaration) javaParserNode; + typeDeclName = typeDecl.getFullyQualifiedName(); + callListener = typeDeclName.isPresent() && typeDecl.isTopLevelType(); + } + + if (callListener) { + @SuppressWarnings("optional:method.invocation.invalid") // from callListener + String typeDeclNameString = typeDeclName.get(); + fileElementTypes.preProcessTopLevelType(typeDeclNameString); + } + try { + if (shouldProcessTypeDecl) { + typeDeclTypeParameters = + processTypeDecl((TypeDeclaration) javaParserNode, null, javacTree); + } + super.visitClass(javacTree, javaParserNode); + } finally { + if (typeDeclTypeParameters != null) { + typeParameters.removeAll(typeDeclTypeParameters); + } + if (callListener) { + @SuppressWarnings("optional:method.invocation.invalid") // from callListener + String typeDeclNameString = typeDeclName.get(); + fileElementTypes.postProcessTopLevelType(typeDeclNameString); + } + } + + return null; + } + + @Override + public Void visitVariable(VariableTree javacTree, Node javaParserNode) { + VariableElement elt = TreeUtils.elementFromDeclaration(javacTree); + if (elt != null) { + if (elt.getKind() == ElementKind.FIELD) { + VariableDeclarator varDecl = (VariableDeclarator) javaParserNode; + processField((FieldDeclaration) varDecl.getParentNode().get(), elt); + } + + if (elt.getKind() == ElementKind.ENUM_CONSTANT) { + processEnumConstant((EnumConstantDeclaration) javaParserNode, elt); + } + } + + super.visitVariable(javacTree, javaParserNode); + return null; + } + + @Override + public Void visitMethod(MethodTree javacTree, Node javaParserNode) { + List variablesToClear = null; + Element elt = TreeUtils.elementFromDeclaration(javacTree); + if (javaParserNode instanceof CallableDeclaration) { + variablesToClear = + processCallableDeclaration( + (CallableDeclaration) javaParserNode, (ExecutableElement) elt); + } + + super.visitMethod(javacTree, javaParserNode); + if (variablesToClear != null) { + typeParameters.removeAll(variablesToClear); + } + + return null; + } + } + + /** + * Return the prefix for a warning line: A file name, line number, and column number. + * + * @param astNode where to report errors + * @return file name, line number, and column number + */ + private String fileAndLine(NodeWithRange astNode) { + String filenamePrinted = + (processingEnv.getOptions().containsKey("nomsgtext") + ? new File(filename).getName() + : filename); + + Optional begin = astNode == null ? Optional.empty() : astNode.getBegin(); + String lineAndColumn = (begin.isPresent() ? begin.get() + ":" : ""); + return filenamePrinted + ":" + lineAndColumn + " "; + } + + /** An exception indicating a problem while parsing an annotation file. */ + public static class AnnotationFileParserException extends Exception { + + private static final long serialVersionUID = 20201222; + + /** + * Create a new AnnotationFileParserException. + * + * @param message a description of the problem + */ + AnnotationFileParserException(String message) { + super(message); + } + } + + /////////////////////////////////////////////////////////////////////////// + /// Parse state + /// + + /** Represents a class: its package name and name (including outer class names if any). */ + private static class FqName { + /** Name of the package being parsed, or null. */ + public final @Nullable String packageName; + + /** + * Name of the type being parsed. Includes outer class names if any. Null if the parser has + * parsed a package declaration but has not yet gotten to a type declaration. + */ + public final @Nullable String className; + + /** + * Create a new FqName, which represents a class. + * + * @param packageName name of the package, or null + * @param className unqualified name of the type, including outer class names if any. May be + * null. + */ + public FqName(@Nullable String packageName, @Nullable String className) { + this.packageName = packageName; + this.className = className; + } + + /** Fully-qualified name of the class. */ + @Override + @SuppressWarnings("signature") // string concatenation + public @FullyQualifiedName String toString() { + if (packageName == null) { + return className; + } else { + return packageName + "." + className; + } + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java index a120d91d1e7..14a66f4a009 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileResource.java @@ -5,13 +5,13 @@ /** Interface for sources of stub data. */ public interface AnnotationFileResource { - /** - * Returns a user-friendly description of the resource (e.g. a filesystem path). - * - * @return a description of the resource - */ - String getDescription(); + /** + * Returns a user-friendly description of the resource (e.g. a filesystem path). + * + * @return a description of the resource + */ + String getDescription(); - /** Returns a stream for reading the contents of the resource. */ - InputStream getInputStream() throws IOException; + /** Returns a stream for reading the contents of the resource. */ + InputStream getInputStream() throws IOException; } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java index 50718c08213..f7001b6fb5d 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java @@ -15,6 +15,13 @@ import com.github.javaparser.ast.type.VoidType; import com.github.javaparser.ast.type.WildcardType; import com.github.javaparser.ast.visitor.SimpleVoidVisitor; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.plumelib.util.IPair; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -25,484 +32,482 @@ import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; + import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.plumelib.util.IPair; /** Utility class for annotation files (stub files and ajava files). */ public class AnnotationFileUtil { - /** - * The types of files that can contain annotations. Also indicates the file's source, such as from - * the JDK, built in, or from the command line. - * - *

          Stub files have extension ".astub". Ajava files have extension ".ajava". - */ - public enum AnnotationFileType { - /** Stub file in the annotated JDK. */ - JDK_STUB, - /** Stub file built into a checker. */ - BUILTIN_STUB, - /** Stub file provided on command line. */ - COMMAND_LINE_STUB, - /** Ajava file being parsed as if it is a stub file. */ - AJAVA_AS_STUB, - /** Ajava file provided on command line. */ - AJAVA; - /** - * Returns true if this represents a stub file. + * The types of files that can contain annotations. Also indicates the file's source, such as + * from the JDK, built in, or from the command line. * - * @return true if this represents a stub file + *

          Stub files have extension ".astub". Ajava files have extension ".ajava". */ - public boolean isStub() { - switch (this) { - case JDK_STUB: - case BUILTIN_STUB: - case COMMAND_LINE_STUB: - case AJAVA_AS_STUB: - return true; - case AJAVA: - return false; - default: - throw new BugInCF("unhandled case " + this); - } - } + public enum AnnotationFileType { + /** Stub file in the annotated JDK. */ + JDK_STUB, + /** Stub file built into a checker. */ + BUILTIN_STUB, + /** Stub file provided on command line. */ + COMMAND_LINE_STUB, + /** Ajava file being parsed as if it is a stub file. */ + AJAVA_AS_STUB, + /** Ajava file provided on command line. */ + AJAVA; + + /** + * Returns true if this represents a stub file. + * + * @return true if this represents a stub file + */ + public boolean isStub() { + switch (this) { + case JDK_STUB: + case BUILTIN_STUB: + case COMMAND_LINE_STUB: + case AJAVA_AS_STUB: + return true; + case AJAVA: + return false; + default: + throw new BugInCF("unhandled case " + this); + } + } - /** - * Returns true if this annotation file is built-in (not provided on the command line). - * - * @return true if this annotation file is built-in (not provided on the command line) - */ - public boolean isBuiltIn() { - switch (this) { - case JDK_STUB: - case BUILTIN_STUB: - return true; - case COMMAND_LINE_STUB: - case AJAVA_AS_STUB: - case AJAVA: - return false; - default: - throw new BugInCF("unhandled case " + this); - } + /** + * Returns true if this annotation file is built-in (not provided on the command line). + * + * @return true if this annotation file is built-in (not provided on the command line) + */ + public boolean isBuiltIn() { + switch (this) { + case JDK_STUB: + case BUILTIN_STUB: + return true; + case COMMAND_LINE_STUB: + case AJAVA_AS_STUB: + case AJAVA: + return false; + default: + throw new BugInCF("unhandled case " + this); + } + } + + /** + * Returns true if this annotation file was provided on the command line (not built-in). + * + * @return true if this annotation file was provided on the command line (not built-in) + */ + public boolean isCommandLine() { + switch (this) { + case JDK_STUB: + case BUILTIN_STUB: + return false; + case COMMAND_LINE_STUB: + case AJAVA_AS_STUB: + case AJAVA: + return true; + default: + throw new BugInCF("unhandled case " + this); + } + } } /** - * Returns true if this annotation file was provided on the command line (not built-in). + * Finds the type declaration with the given class name in a StubUnit. * - * @return true if this annotation file was provided on the command line (not built-in) + * @param className fully qualified name of the type declaration to find + * @param indexFile a StubUnit to search + * @return the declaration in {@code indexFile} with {@code className} if it exists, null + * otherwise. */ - public boolean isCommandLine() { - switch (this) { - case JDK_STUB: - case BUILTIN_STUB: - return false; - case COMMAND_LINE_STUB: - case AJAVA_AS_STUB: - case AJAVA: - return true; - default: - throw new BugInCF("unhandled case " + this); - } + /*package-private*/ static @Nullable TypeDeclaration findDeclaration( + String className, StubUnit indexFile) { + int indexOfDot = className.lastIndexOf('.'); + + if (indexOfDot == -1) { + // classes not within a package needs to be the first in the index file + CompilationUnit cu = indexFile.getCompilationUnits().get(0); + assert !cu.getPackageDeclaration().isPresent(); + return findDeclaration(className, cu); + } + + String packageName = className.substring(0, indexOfDot); + String simpleName = className.substring(indexOfDot + 1); + + for (CompilationUnit cu : indexFile.getCompilationUnits()) { + if (cu.getPackageDeclaration().isPresent() + && cu.getPackageDeclaration().get().getNameAsString().equals(packageName)) { + TypeDeclaration type = findDeclaration(simpleName, cu); + if (type != null) { + return type; + } + } + } + + // Couldn't find it + return null; } - } - - /** - * Finds the type declaration with the given class name in a StubUnit. - * - * @param className fully qualified name of the type declaration to find - * @param indexFile a StubUnit to search - * @return the declaration in {@code indexFile} with {@code className} if it exists, null - * otherwise. - */ - /*package-private*/ static @Nullable TypeDeclaration findDeclaration( - String className, StubUnit indexFile) { - int indexOfDot = className.lastIndexOf('.'); - - if (indexOfDot == -1) { - // classes not within a package needs to be the first in the index file - CompilationUnit cu = indexFile.getCompilationUnits().get(0); - assert !cu.getPackageDeclaration().isPresent(); - return findDeclaration(className, cu); + + /*package-private*/ static TypeDeclaration findDeclaration( + TypeElement type, StubUnit indexFile) { + return findDeclaration(type.getQualifiedName().toString(), indexFile); } - String packageName = className.substring(0, indexOfDot); - String simpleName = className.substring(indexOfDot + 1); + /*package-private*/ static @Nullable FieldDeclaration findDeclaration( + VariableElement field, StubUnit indexFile) { + TypeDeclaration type = + findDeclaration((TypeElement) field.getEnclosingElement(), indexFile); + if (type == null) { + return null; + } - for (CompilationUnit cu : indexFile.getCompilationUnits()) { - if (cu.getPackageDeclaration().isPresent() - && cu.getPackageDeclaration().get().getNameAsString().equals(packageName)) { - TypeDeclaration type = findDeclaration(simpleName, cu); - if (type != null) { - return type; + for (BodyDeclaration member : type.getMembers()) { + if (!(member instanceof FieldDeclaration)) { + continue; + } + FieldDeclaration decl = (FieldDeclaration) member; + for (VariableDeclarator var : decl.getVariables()) { + if (toString(var).equals(field.getSimpleName().toString())) { + return decl; + } + } } - } + return null; } - // Couldn't find it - return null; - } - - /*package-private*/ static TypeDeclaration findDeclaration( - TypeElement type, StubUnit indexFile) { - return findDeclaration(type.getQualifiedName().toString(), indexFile); - } + /*package-private*/ static @Nullable BodyDeclaration findDeclaration( + ExecutableElement method, StubUnit indexFile) { + TypeDeclaration type = + findDeclaration((TypeElement) method.getEnclosingElement(), indexFile); + if (type == null) { + return null; + } - /*package-private*/ static @Nullable FieldDeclaration findDeclaration( - VariableElement field, StubUnit indexFile) { - TypeDeclaration type = findDeclaration((TypeElement) field.getEnclosingElement(), indexFile); - if (type == null) { - return null; + String methodRep = toString(method); + + for (BodyDeclaration member : type.getMembers()) { + if (member instanceof MethodDeclaration) { + if (toString((MethodDeclaration) member).equals(methodRep)) { + return member; + } + } else if (member instanceof ConstructorDeclaration) { + if (toString((ConstructorDeclaration) member).equals(methodRep)) { + return member; + } + } + } + return null; } - for (BodyDeclaration member : type.getMembers()) { - if (!(member instanceof FieldDeclaration)) { - continue; - } - FieldDeclaration decl = (FieldDeclaration) member; - for (VariableDeclarator var : decl.getVariables()) { - if (toString(var).equals(field.getSimpleName().toString())) { - return decl; + /*package-private*/ static @Nullable TypeDeclaration findDeclaration( + String simpleName, CompilationUnit cu) { + for (TypeDeclaration type : cu.getTypes()) { + if (simpleName.equals(type.getNameAsString())) { + return type; + } } - } - } - return null; - } - - /*package-private*/ static @Nullable BodyDeclaration findDeclaration( - ExecutableElement method, StubUnit indexFile) { - TypeDeclaration type = - findDeclaration((TypeElement) method.getEnclosingElement(), indexFile); - if (type == null) { - return null; + // Couldn't find it + return null; } - String methodRep = toString(method); + /*package-private*/ static String toString(MethodDeclaration method) { + return ElementPrinter.toString(method); + } - for (BodyDeclaration member : type.getMembers()) { - if (member instanceof MethodDeclaration) { - if (toString((MethodDeclaration) member).equals(methodRep)) { - return member; - } - } else if (member instanceof ConstructorDeclaration) { - if (toString((ConstructorDeclaration) member).equals(methodRep)) { - return member; - } - } + /*package-private*/ static String toString(ConstructorDeclaration constructor) { + return ElementPrinter.toString(constructor); } - return null; - } - - /*package-private*/ static @Nullable TypeDeclaration findDeclaration( - String simpleName, CompilationUnit cu) { - for (TypeDeclaration type : cu.getTypes()) { - if (simpleName.equals(type.getNameAsString())) { - return type; - } + + /*package-private*/ static String toString(VariableDeclarator field) { + return field.getNameAsString(); } - // Couldn't find it - return null; - } - - /*package-private*/ static String toString(MethodDeclaration method) { - return ElementPrinter.toString(method); - } - - /*package-private*/ static String toString(ConstructorDeclaration constructor) { - return ElementPrinter.toString(constructor); - } - - /*package-private*/ static String toString(VariableDeclarator field) { - return field.getNameAsString(); - } - - /*package-private*/ static String toString(FieldDeclaration field) { - assert field.getVariables().size() == 1; - return toString(field.getVariables().get(0)); - } - - /*package-private*/ static String toString(VariableElement element) { - assert element.getKind().isField(); - return element.getSimpleName().toString(); - } - - /*package-private*/ static @Nullable String toString(Element element) { - if (element instanceof ExecutableElement) { - return toString((ExecutableElement) element); - } else if (element instanceof VariableElement) { - return toString((VariableElement) element); - } else { - return null; + + /*package-private*/ static String toString(FieldDeclaration field) { + assert field.getVariables().size() == 1; + return toString(field.getVariables().get(0)); } - } - - /** - * Split a name (which comes from an import statement) into the part before the last period and - * the part after the last period. - * - * @param imported the name to split - * @return a pair of the type name and the field name - */ - @SuppressWarnings("signature") // string parsing - public static IPair<@FullyQualifiedName String, String> partitionQualifiedName(String imported) { - @FullyQualifiedName String typeName = imported.substring(0, imported.lastIndexOf(".")); - String name = imported.substring(imported.lastIndexOf(".") + 1); - IPair typeParts = IPair.of(typeName, name); - return typeParts; - } - - private static final class ElementPrinter extends SimpleVoidVisitor { - public static String toString(Node n) { - ElementPrinter printer = new ElementPrinter(); - n.accept(printer, null); - return printer.getOutput(); + + /*package-private*/ static String toString(VariableElement element) { + assert element.getKind().isField(); + return element.getSimpleName().toString(); } - private final StringBuilder sb = new StringBuilder(); + /*package-private*/ static @Nullable String toString(Element element) { + if (element instanceof ExecutableElement) { + return toString((ExecutableElement) element); + } else if (element instanceof VariableElement) { + return toString((VariableElement) element); + } else { + return null; + } + } - public String getOutput() { - return sb.toString(); + /** + * Split a name (which comes from an import statement) into the part before the last period and + * the part after the last period. + * + * @param imported the name to split + * @return a pair of the type name and the field name + */ + @SuppressWarnings("signature") // string parsing + public static IPair<@FullyQualifiedName String, String> partitionQualifiedName( + String imported) { + @FullyQualifiedName String typeName = imported.substring(0, imported.lastIndexOf(".")); + String name = imported.substring(imported.lastIndexOf(".") + 1); + IPair typeParts = IPair.of(typeName, name); + return typeParts; } - @Override - public void visit(ConstructorDeclaration n, Void arg) { - sb.append(""); + private static final class ElementPrinter extends SimpleVoidVisitor { + public static String toString(Node n) { + ElementPrinter printer = new ElementPrinter(); + n.accept(printer, null); + return printer.getOutput(); + } - sb.append("("); - if (n.getParameters() != null) { - for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { - Parameter p = i.next(); - p.accept(this, arg); + private final StringBuilder sb = new StringBuilder(); - if (i.hasNext()) { - sb.append(","); - } + public String getOutput() { + return sb.toString(); } - } - sb.append(")"); - } - @Override - public void visit(MethodDeclaration n, Void arg) { - sb.append(n.getName()); + @Override + public void visit(ConstructorDeclaration n, Void arg) { + sb.append(""); + + sb.append("("); + if (n.getParameters() != null) { + for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { + Parameter p = i.next(); + p.accept(this, arg); + + if (i.hasNext()) { + sb.append(","); + } + } + } + sb.append(")"); + } - sb.append("("); - if (n.getParameters() != null) { - for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { - Parameter p = i.next(); - p.accept(this, arg); + @Override + public void visit(MethodDeclaration n, Void arg) { + sb.append(n.getName()); + + sb.append("("); + if (n.getParameters() != null) { + for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { + Parameter p = i.next(); + p.accept(this, arg); + + if (i.hasNext()) { + sb.append(","); + } + } + } + sb.append(")"); + } - if (i.hasNext()) { - sb.append(","); - } + @Override + public void visit(Parameter n, Void arg) { + n.getType().accept(this, arg); + if (n.isVarArgs()) { + sb.append("[]"); + } } - } - sb.append(")"); - } - @Override - public void visit(Parameter n, Void arg) { - n.getType().accept(this, arg); - if (n.isVarArgs()) { - sb.append("[]"); - } - } + // Types + @Override + public void visit(ClassOrInterfaceType n, Void arg) { + sb.append(n.getName()); + } - // Types - @Override - public void visit(ClassOrInterfaceType n, Void arg) { - sb.append(n.getName()); - } + @Override + public void visit(PrimitiveType n, Void arg) { + switch (n.getType()) { + case BOOLEAN: + sb.append("boolean"); + break; + case BYTE: + sb.append("byte"); + break; + case CHAR: + sb.append("char"); + break; + case DOUBLE: + sb.append("double"); + break; + case FLOAT: + sb.append("float"); + break; + case INT: + sb.append("int"); + break; + case LONG: + sb.append("long"); + break; + case SHORT: + sb.append("short"); + break; + default: + throw new BugInCF("AnnotationFileUtil: unknown type: " + n.getType()); + } + } - @Override - public void visit(PrimitiveType n, Void arg) { - switch (n.getType()) { - case BOOLEAN: - sb.append("boolean"); - break; - case BYTE: - sb.append("byte"); - break; - case CHAR: - sb.append("char"); - break; - case DOUBLE: - sb.append("double"); - break; - case FLOAT: - sb.append("float"); - break; - case INT: - sb.append("int"); - break; - case LONG: - sb.append("long"); - break; - case SHORT: - sb.append("short"); - break; - default: - throw new BugInCF("AnnotationFileUtil: unknown type: " + n.getType()); - } - } + @Override + public void visit(com.github.javaparser.ast.type.ArrayType n, Void arg) { + n.getComponentType().accept(this, arg); + sb.append("[]"); + } - @Override - public void visit(com.github.javaparser.ast.type.ArrayType n, Void arg) { - n.getComponentType().accept(this, arg); - sb.append("[]"); + @Override + public void visit(VoidType n, Void arg) { + sb.append("void"); + } + + @Override + public void visit(WildcardType n, Void arg) { + // We don't write type arguments + // TODO: Why? + throw new BugInCF("AnnotationFileUtil: don't print type args"); + } } - @Override - public void visit(VoidType n, Void arg) { - sb.append("void"); + /** + * Return annotation files found at a given file system location (does not look on classpath). + * + * @param location an annotation file (stub file or ajava file), a jarfile, or a directory. Look + * for it as an absolute file and relative to the current directory. + * @param fileType file type of files to collect + * @return annotation files with the given file type found in the file system (does not look on + * classpath). Returns null if the file system location does not exist; the caller may wish + * to issue a warning in that case. + */ + public static @Nullable List allAnnotationFiles( + String location, AnnotationFileType fileType) { + File file = new File(location); + if (file.exists()) { + List resources = new ArrayList<>(); + addAnnotationFilesToList(file, resources, fileType); + return resources; + } + + // The file doesn't exist. Maybe it is relative to the current working directory, so try + // that. + file = new File(System.getProperty("user.dir"), location); + if (file.exists()) { + List resources = new ArrayList<>(); + addAnnotationFilesToList(file, resources, fileType); + return resources; + } + + return null; } - @Override - public void visit(WildcardType n, Void arg) { - // We don't write type arguments - // TODO: Why? - throw new BugInCF("AnnotationFileUtil: don't print type args"); + /** + * Returns true if the given file is an annotation file of the given type. + * + * @param f the file to check + * @param fileType the type of file to check against + * @return true if {@code f} is a file with file type matching {@code fileType}, false otherwise + */ + private static boolean isAnnotationFile(File f, AnnotationFileType fileType) { + return f.isFile() && isAnnotationFile(f.getName(), fileType); } - } - - /** - * Return annotation files found at a given file system location (does not look on classpath). - * - * @param location an annotation file (stub file or ajava file), a jarfile, or a directory. Look - * for it as an absolute file and relative to the current directory. - * @param fileType file type of files to collect - * @return annotation files with the given file type found in the file system (does not look on - * classpath). Returns null if the file system location does not exist; the caller may wish to - * issue a warning in that case. - */ - public static @Nullable List allAnnotationFiles( - String location, AnnotationFileType fileType) { - File file = new File(location); - if (file.exists()) { - List resources = new ArrayList<>(); - addAnnotationFilesToList(file, resources, fileType); - return resources; + + /** + * Returns true if the given file is an annotation file of the given kind. + * + * @param path a file + * @param fileType the type of file to check against + * @return true if {@code path} represents a file with file type matching {@code fileType}, + * false otherwise + */ + private static boolean isAnnotationFile(String path, AnnotationFileType fileType) { + return path.endsWith(fileType.isStub() ? ".astub" : ".ajava"); } - // The file doesn't exist. Maybe it is relative to the current working directory, so try - // that. - file = new File(System.getProperty("user.dir"), location); - if (file.exists()) { - List resources = new ArrayList<>(); - addAnnotationFilesToList(file, resources, fileType); - return resources; + private static boolean isJar(File f) { + return f.isFile() && f.getName().endsWith(".jar"); } - return null; - } - - /** - * Returns true if the given file is an annotation file of the given type. - * - * @param f the file to check - * @param fileType the type of file to check against - * @return true if {@code f} is a file with file type matching {@code fileType}, false otherwise - */ - private static boolean isAnnotationFile(File f, AnnotationFileType fileType) { - return f.isFile() && isAnnotationFile(f.getName(), fileType); - } - - /** - * Returns true if the given file is an annotation file of the given kind. - * - * @param path a file - * @param fileType the type of file to check against - * @return true if {@code path} represents a file with file type matching {@code fileType}, false - * otherwise - */ - private static boolean isAnnotationFile(String path, AnnotationFileType fileType) { - return path.endsWith(fileType.isStub() ? ".astub" : ".ajava"); - } - - private static boolean isJar(File f) { - return f.isFile() && f.getName().endsWith(".jar"); - } - - /** - * Side-effects {@code resources} by adding annotation files of the given file type to it. - * - * @param location an annotation file (a stub file or ajava file), a jarfile, or a directory. If a - * stub file or ajava file, add it to the {@code resources} list. If a jarfile, use all - * annotation files (of type {@code fileType}) contained in it. If a directory, recurse on all - * files contained in it. - * @param resources the list to add the found files to - * @param fileType type of annotation files to add - */ - @SuppressWarnings({ - "JdkObsolete", // JarFile.entries() - "nullness:argument", // inference failed in Arrays.sort - "builder:required.method.not.called" // ownership passed to list of - // JarEntryAnnotationFileResource, where `file` appears in every element of the list - }) - private static void addAnnotationFilesToList( - File location, List resources, AnnotationFileType fileType) { - if (isAnnotationFile(location, fileType)) { - resources.add(new FileAnnotationFileResource(location)); - } else if (isJar(location)) { - JarFile file; - try { - file = new JarFile(location); - } catch (IOException e) { - System.err.println("AnnotationFileUtil: could not process JAR file: " + location); - return; - } - Enumeration entries = file.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (isAnnotationFile(entry.getName(), fileType)) { - resources.add(new JarEntryAnnotationFileResource(file, entry)); + /** + * Side-effects {@code resources} by adding annotation files of the given file type to it. + * + * @param location an annotation file (a stub file or ajava file), a jarfile, or a directory. If + * a stub file or ajava file, add it to the {@code resources} list. If a jarfile, use all + * annotation files (of type {@code fileType}) contained in it. If a directory, recurse on + * all files contained in it. + * @param resources the list to add the found files to + * @param fileType type of annotation files to add + */ + @SuppressWarnings({ + "JdkObsolete", // JarFile.entries() + "nullness:argument", // inference failed in Arrays.sort + "builder:required.method.not.called" // ownership passed to list of + // JarEntryAnnotationFileResource, where `file` appears in every element of the list + }) + private static void addAnnotationFilesToList( + File location, List resources, AnnotationFileType fileType) { + if (isAnnotationFile(location, fileType)) { + resources.add(new FileAnnotationFileResource(location)); + } else if (isJar(location)) { + JarFile file; + try { + file = new JarFile(location); + } catch (IOException e) { + System.err.println("AnnotationFileUtil: could not process JAR file: " + location); + return; + } + Enumeration entries = file.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (isAnnotationFile(entry.getName(), fileType)) { + resources.add(new JarEntryAnnotationFileResource(file, entry)); + } + } + + } else if (location.isDirectory()) { + File[] directoryContents = location.listFiles(); + Arrays.sort(directoryContents, Comparator.comparing(File::getName)); + for (File enclosed : directoryContents) { + addAnnotationFilesToList(enclosed, resources, fileType); + } } - } - - } else if (location.isDirectory()) { - File[] directoryContents = location.listFiles(); - Arrays.sort(directoryContents, Comparator.comparing(File::getName)); - for (File enclosed : directoryContents) { - addAnnotationFilesToList(enclosed, resources, fileType); - } - } - } - - /** - * Returns true if the given {@link ExecutableElement} is the canonical constructor of a record - * (i.e., the parameter types of the constructor correspond to the parameter types of the record - * components, ignoring annotations). - * - * @param elt the constructor/method to check - * @param types the Types instance to use for comparing types - * @return true if elt is the canonical constructor of the record containing it - */ - public static boolean isCanonicalConstructor(ExecutableElement elt, Types types) { - if (elt.getKind() != ElementKind.CONSTRUCTOR) { - return false; } - TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); - if (!ElementUtils.isRecordElement(enclosing)) { - return false; - } - List recordComponents = ElementUtils.getRecordComponents(enclosing); - if (recordComponents.size() == elt.getParameters().size()) { - for (int i = 0; i < recordComponents.size(); i++) { - if (!types.isSameType( - recordComponents.get(i).asType(), elt.getParameters().get(i).asType())) { - return false; + + /** + * Returns true if the given {@link ExecutableElement} is the canonical constructor of a record + * (i.e., the parameter types of the constructor correspond to the parameter types of the record + * components, ignoring annotations). + * + * @param elt the constructor/method to check + * @param types the Types instance to use for comparing types + * @return true if elt is the canonical constructor of the record containing it + */ + public static boolean isCanonicalConstructor(ExecutableElement elt, Types types) { + if (elt.getKind() != ElementKind.CONSTRUCTOR) { + return false; + } + TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); + if (!ElementUtils.isRecordElement(enclosing)) { + return false; + } + List recordComponents = ElementUtils.getRecordComponents(enclosing); + if (recordComponents.size() == elt.getParameters().size()) { + for (int i = 0; i < recordComponents.size(); i++) { + if (!types.isSameType( + recordComponents.get(i).asType(), elt.getParameters().get(i).asType())) { + return false; + } + } + return true; } - } - return true; + return false; } - return false; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java index d79cf51016e..152742efb99 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/FileAnnotationFileResource.java @@ -7,26 +7,26 @@ /** {@link File}-based implementation of {@link AnnotationFileResource}. */ public class FileAnnotationFileResource implements AnnotationFileResource { - /** The underlying file. */ - private final File file; + /** The underlying file. */ + private final File file; - /** - * Constructs a {@code AnnotationFileResource} for the specified annotation file (stub file or - * ajava file). - * - * @param file the annotation file - */ - public FileAnnotationFileResource(File file) { - this.file = file; - } + /** + * Constructs a {@code AnnotationFileResource} for the specified annotation file (stub file or + * ajava file). + * + * @param file the annotation file + */ + public FileAnnotationFileResource(File file) { + this.file = file; + } - @Override - public String getDescription() { - return file.getAbsolutePath(); - } + @Override + public String getDescription() { + return file.getAbsolutePath(); + } - @Override - public InputStream getInputStream() throws IOException { - return new FileInputStream(file); - } + @Override + public InputStream getInputStream() throws IOException { + return new FileInputStream(file); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java b/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java index 475f9241707..2bc8e2391ef 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/JarEntryAnnotationFileResource.java @@ -7,30 +7,31 @@ /** {@link JarEntry}-based implementation of {@link AnnotationFileResource}. */ public class JarEntryAnnotationFileResource implements AnnotationFileResource { - /** The underlying JarFile. */ - private final JarFile file; + /** The underlying JarFile. */ + private final JarFile file; - /** The entry in the jar file. */ - private final JarEntry entry; + /** The entry in the jar file. */ + private final JarEntry entry; - /** - * Constructs a {@code AnnotationFileResource} for the specified entry in the specified JAR file. - * - * @param file the JAR file - * @param entry the JAR entry - */ - public JarEntryAnnotationFileResource(JarFile file, JarEntry entry) { - this.file = file; - this.entry = entry; - } + /** + * Constructs a {@code AnnotationFileResource} for the specified entry in the specified JAR + * file. + * + * @param file the JAR file + * @param entry the JAR entry + */ + public JarEntryAnnotationFileResource(JarFile file, JarEntry entry) { + this.file = file; + this.entry = entry; + } - @Override - public String getDescription() { - return file.getName() + "!" + entry.getName(); - } + @Override + public String getDescription() { + return file.getName() + "!" + entry.getName(); + } - @Override - public InputStream getInputStream() throws IOException { - return file.getInputStream(entry); - } + @Override + public InputStream getInputStream() throws IOException { + return file.getInputStream(entry); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java b/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java index 73dc0675091..2d923e1d289 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/JavaStubifier.java @@ -20,12 +20,14 @@ import com.github.javaparser.utils.ParserCollectionStrategy; import com.github.javaparser.utils.ProjectRoot; import com.github.javaparser.utils.SourceRoot; + +import org.checkerframework.framework.util.JavaParserUtil; + import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; -import org.checkerframework.framework.util.JavaParserUtil; /** * Process Java source files in a directory to produce, in-place, minimal stub files. @@ -42,220 +44,221 @@ * */ public class JavaStubifier { - /** - * Processes each provided command-line argument; see class documentation for details. - * - * @param args command-line arguments: directories to process - */ - public static void main(String[] args) { - if (args.length < 1) { - System.err.println("Usage: provide one or more directory names to process"); - System.exit(1); - } - for (String arg : args) { - process(arg); + /** + * Processes each provided command-line argument; see class documentation for details. + * + * @param args command-line arguments: directories to process + */ + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("Usage: provide one or more directory names to process"); + System.exit(1); + } + for (String arg : args) { + process(arg); + } } - } - - /** - * Process each file in the given directory; see class documentation for details. - * - * @param dir directory to process - */ - private static void process(String dir) { - Path root = dirnameToPath(dir); - MinimizerCallback mc = new MinimizerCallback(); - CollectionStrategy strategy = new ParserCollectionStrategy(); - // Required to include directories that contain a module-info.java, which don't parse by - // default. - strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); - ProjectRoot projectRoot = strategy.collect(root); - projectRoot - .getSourceRoots() - .forEach( - sourceRoot -> { - try { - sourceRoot.parse("", mc); - } catch (IOException e) { - System.err.println("IOException: " + e); - } - }); - } + /** + * Process each file in the given directory; see class documentation for details. + * + * @param dir directory to process + */ + private static void process(String dir) { + Path root = dirnameToPath(dir); + MinimizerCallback mc = new MinimizerCallback(); + CollectionStrategy strategy = new ParserCollectionStrategy(); + // Required to include directories that contain a module-info.java, which don't parse by + // default. + strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); + ProjectRoot projectRoot = strategy.collect(root); - /** - * Converts a directory name to a path. It issues a warning and terminates the program if the - * argument does not exist or is not a directory. - * - *

          Unlike {@code Paths.get}, it handles "." which means the current directory in Unix. - * - * @param dir a directory name - * @return a path for the directory name - */ - public static Path dirnameToPath(String dir) { - File f = new File(dir); - if (!f.exists()) { - System.err.printf("Directory %s (%s) does not exist.%n", dir, f); - System.exit(1); - } - if (!f.isDirectory()) { - System.err.printf("Not a directory: %s (%s).%n", dir, f); - System.exit(1); + projectRoot + .getSourceRoots() + .forEach( + sourceRoot -> { + try { + sourceRoot.parse("", mc); + } catch (IOException e) { + System.err.println("IOException: " + e); + } + }); } - String absoluteDir = f.getAbsolutePath(); - if (absoluteDir.endsWith("/.")) { - absoluteDir = absoluteDir.substring(0, absoluteDir.length() - 2); + + /** + * Converts a directory name to a path. It issues a warning and terminates the program if the + * argument does not exist or is not a directory. + * + *

          Unlike {@code Paths.get}, it handles "." which means the current directory in Unix. + * + * @param dir a directory name + * @return a path for the directory name + */ + public static Path dirnameToPath(String dir) { + File f = new File(dir); + if (!f.exists()) { + System.err.printf("Directory %s (%s) does not exist.%n", dir, f); + System.exit(1); + } + if (!f.isDirectory()) { + System.err.printf("Not a directory: %s (%s).%n", dir, f); + System.exit(1); + } + String absoluteDir = f.getAbsolutePath(); + if (absoluteDir.endsWith("/.")) { + absoluteDir = absoluteDir.substring(0, absoluteDir.length() - 2); + } + return Paths.get(absoluteDir); } - return Paths.get(absoluteDir); - } - /** Callback to process each Java file; see class documentation for details. */ - private static class MinimizerCallback implements SourceRoot.Callback { - /** The visitor instance. */ - private final MinimizerVisitor mv; + /** Callback to process each Java file; see class documentation for details. */ + private static class MinimizerCallback implements SourceRoot.Callback { + /** The visitor instance. */ + private final MinimizerVisitor mv; - /** Create a MinimizerCallback instance. */ - public MinimizerCallback() { - this.mv = new MinimizerVisitor(); - } + /** Create a MinimizerCallback instance. */ + public MinimizerCallback() { + this.mv = new MinimizerVisitor(); + } - @Override - public Result process(Path localPath, Path absolutePath, ParseResult result) { - Result res = Result.SAVE; - // System.out.printf("Minimizing %s%n", absolutePath); - Optional opt = result.getResult(); - if (opt.isPresent()) { - CompilationUnit cu = opt.get(); - // Only remove the "contained" comments so that the copyright comment is not - // removed. - cu.getAllContainedComments().forEach(Node::remove); - mv.visit(cu, null); - if (cu.findAll(ClassOrInterfaceDeclaration.class).isEmpty() - && cu.findAll(AnnotationDeclaration.class).isEmpty() - && cu.findAll(EnumDeclaration.class).isEmpty() - && !absolutePath.endsWith("package-info.java")) { - // All content is removed, delete this file. - new File(absolutePath.toUri()).delete(); - res = Result.DONT_SAVE; + @Override + public Result process( + Path localPath, Path absolutePath, ParseResult result) { + Result res = Result.SAVE; + // System.out.printf("Minimizing %s%n", absolutePath); + Optional opt = result.getResult(); + if (opt.isPresent()) { + CompilationUnit cu = opt.get(); + // Only remove the "contained" comments so that the copyright comment is not + // removed. + cu.getAllContainedComments().forEach(Node::remove); + mv.visit(cu, null); + if (cu.findAll(ClassOrInterfaceDeclaration.class).isEmpty() + && cu.findAll(AnnotationDeclaration.class).isEmpty() + && cu.findAll(EnumDeclaration.class).isEmpty() + && !absolutePath.endsWith("package-info.java")) { + // All content is removed, delete this file. + new File(absolutePath.toUri()).delete(); + res = Result.DONT_SAVE; + } + } + return res; } - } - return res; } - } - /** Visitor to process one compilation unit; see class documentation for details. */ - private static class MinimizerVisitor extends ModifierVisitor { - /** Whether to consider members implicitly public. */ - private boolean implicitlyPublic = false; + /** Visitor to process one compilation unit; see class documentation for details. */ + private static class MinimizerVisitor extends ModifierVisitor { + /** Whether to consider members implicitly public. */ + private boolean implicitlyPublic = false; - @Override - public ClassOrInterfaceDeclaration visit(ClassOrInterfaceDeclaration cid, Void arg) { - boolean prevIP = implicitlyPublic; - if (cid.isInterface()) { - // All members of interfaces are implicitly public. - implicitlyPublic = true; - } - super.visit(cid, arg); - if (cid.isInterface()) { - implicitlyPublic = prevIP; - } - // Do not remove private or package-private classes, because there could - // be externally-visible members in externally-visible subclasses. - return cid; - } + @Override + public ClassOrInterfaceDeclaration visit(ClassOrInterfaceDeclaration cid, Void arg) { + boolean prevIP = implicitlyPublic; + if (cid.isInterface()) { + // All members of interfaces are implicitly public. + implicitlyPublic = true; + } + super.visit(cid, arg); + if (cid.isInterface()) { + implicitlyPublic = prevIP; + } + // Do not remove private or package-private classes, because there could + // be externally-visible members in externally-visible subclasses. + return cid; + } - @Override - public EnumDeclaration visit(EnumDeclaration ed, Void arg) { - super.visit(ed, arg); - // Enums can't be extended, so it is ok to remove them if they are not externally - // visible. - removeIfPrivateOrPkgPrivate(ed); - return ed; - } + @Override + public EnumDeclaration visit(EnumDeclaration ed, Void arg) { + super.visit(ed, arg); + // Enums can't be extended, so it is ok to remove them if they are not externally + // visible. + removeIfPrivateOrPkgPrivate(ed); + return ed; + } - @Override - public ConstructorDeclaration visit(ConstructorDeclaration cd, Void arg) { - super.visit(cd, arg); - // Constructors cannot be overridden, so it is ok to remove them if they are - // not externally visible. - if (!removeIfPrivateOrPkgPrivate(cd)) { - // ConstructorDeclaration has to have a body - cd.setBody(new BlockStmt()); - } - return cd; - } + @Override + public ConstructorDeclaration visit(ConstructorDeclaration cd, Void arg) { + super.visit(cd, arg); + // Constructors cannot be overridden, so it is ok to remove them if they are + // not externally visible. + if (!removeIfPrivateOrPkgPrivate(cd)) { + // ConstructorDeclaration has to have a body + cd.setBody(new BlockStmt()); + } + return cd; + } - @Override - public MethodDeclaration visit(MethodDeclaration md, Void arg) { - super.visit(md, arg); - // Non-private methods could be overridden with larger visibility. - // So it is only safe to remove private methods, which can't be overridden. - if (!removeIfPrivate(md)) { - md.removeBody(); - } - return md; - } + @Override + public MethodDeclaration visit(MethodDeclaration md, Void arg) { + super.visit(md, arg); + // Non-private methods could be overridden with larger visibility. + // So it is only safe to remove private methods, which can't be overridden. + if (!removeIfPrivate(md)) { + md.removeBody(); + } + return md; + } - @Override - public FieldDeclaration visit(FieldDeclaration fd, Void arg) { - super.visit(fd, arg); - // It is safe to remove fields that are not externally visible. - if (!removeIfPrivateOrPkgPrivate(fd)) { - fd.getVariables().forEach(v -> v.getInitializer().ifPresent(Node::remove)); - } - return fd; - } + @Override + public FieldDeclaration visit(FieldDeclaration fd, Void arg) { + super.visit(fd, arg); + // It is safe to remove fields that are not externally visible. + if (!removeIfPrivateOrPkgPrivate(fd)) { + fd.getVariables().forEach(v -> v.getInitializer().ifPresent(Node::remove)); + } + return fd; + } - @Override - public InitializerDeclaration visit(InitializerDeclaration id, Void arg) { - super.visit(id, arg); - id.remove(); - return id; - } + @Override + public InitializerDeclaration visit(InitializerDeclaration id, Void arg) { + super.visit(id, arg); + id.remove(); + return id; + } - @Override - public NormalAnnotationExpr visit(NormalAnnotationExpr nae, Void arg) { - super.visit(nae, arg); - if (nae.getNameAsString().equals("Deprecated")) { - nae.setPairs(new NodeList<>()); - } - return nae; - } + @Override + public NormalAnnotationExpr visit(NormalAnnotationExpr nae, Void arg) { + super.visit(nae, arg); + if (nae.getNameAsString().equals("Deprecated")) { + nae.setPairs(new NodeList<>()); + } + return nae; + } - /** - * Remove the whole node if it is private or package private. - * - * @param node a Node to inspect - * @return true if the node was removed - */ - private boolean removeIfPrivateOrPkgPrivate(NodeWithAccessModifiers node) { - if (implicitlyPublic) { - return false; - } - AccessSpecifier as = node.getAccessSpecifier(); - if (as == AccessSpecifier.PRIVATE || as == AccessSpecifier.NONE) { - ((Node) node).remove(); - return true; - } - return false; - } + /** + * Remove the whole node if it is private or package private. + * + * @param node a Node to inspect + * @return true if the node was removed + */ + private boolean removeIfPrivateOrPkgPrivate(NodeWithAccessModifiers node) { + if (implicitlyPublic) { + return false; + } + AccessSpecifier as = node.getAccessSpecifier(); + if (as == AccessSpecifier.PRIVATE || as == AccessSpecifier.NONE) { + ((Node) node).remove(); + return true; + } + return false; + } - /** - * Remove the whole node if it is private. - * - * @param node a Node to inspect - * @return true if the node was removed - */ - private boolean removeIfPrivate(NodeWithAccessModifiers node) { - if (implicitlyPublic) { - return false; - } - AccessSpecifier as = node.getAccessSpecifier(); - if (as == AccessSpecifier.PRIVATE) { - ((Node) node).remove(); - return true; - } - return false; + /** + * Remove the whole node if it is private. + * + * @param node a Node to inspect + * @return true if the node was removed + */ + private boolean removeIfPrivate(NodeWithAccessModifiers node) { + if (implicitlyPublic) { + return false; + } + AccessSpecifier as = node.getAccessSpecifier(); + if (as == AccessSpecifier.PRIVATE) { + ((Node) node).remove(); + return true; + } + return false; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java index eeec1d2c2a3..ba511ea5bc3 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java @@ -25,6 +25,15 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.common.reflect.ClassPath; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.util.JavaParserUtil; +import org.checkerframework.javacutil.BugInCF; +import org.plumelib.util.ArraysPlume; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; @@ -42,13 +51,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.util.JavaParserUtil; -import org.checkerframework.javacutil.BugInCF; -import org.plumelib.util.ArraysPlume; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; /** * Process Java source files to remove annotations that ought to be inferred. @@ -80,530 +82,539 @@ */ public class RemoveAnnotationsForInference { - /** - * Do not instantiate. This is a standalone program whose entry point is {@link #main(String[])}. - */ - private RemoveAnnotationsForInference() { - throw new Error("Do not instantiate RemoveAnnotationsForInference."); - } - - /** - * A list of annotations not to remove (i.e., to keep in the source code). Used to prevent - * project-specific annotations that must remain for the project to build from being removed by - * this program. (It would be burdensome to add all project-specific annotations to the global - * list in {@link #isTrustedAnnotation(String)}.) - */ - private static @MonotonicNonNull Set annotationsToKeep = null; - - /** - * Processes each provided command-line argument; see {@link RemoveAnnotationsForInference class - * documentation} for details. - * - * @param args command-line arguments: directories to process - */ - public static void main(String[] args) { - // TODO: using plume-lib's "Options" project here would be better, but would add a dependency to - // the whole Checker Framework, which is undesirable. Move this program elsewhere (e.g., to a - // plume-lib project)? - if (args[0].contentEquals("-keepFile")) { - if (args.length < 2) { - System.err.println( - "Usage: -keepFile requires an argument immediately after it: the path to the keep" - + " file."); - System.exit(2); - } - String keepFilePath = args[1]; - try (Stream lines = Files.lines(Paths.get(keepFilePath))) { - annotationsToKeep = lines.collect(Collectors.toSet()); - } catch (FileNotFoundException e) { - System.err.println("Error: Keep file " + keepFilePath + " not found."); - System.exit(3); - } catch (IOException e) { - System.err.println("Problem reading keep file " + keepFilePath + ": " + e.getMessage()); - System.exit(4); - } - - // Check for common mistake of adding "@" before the annotation name. - for (String annotationToKeep : annotationsToKeep) { - if (annotationToKeep.startsWith("@")) { - System.err.println( - "Error: Keep file includes an @ symbol before this annotation: " - + annotationToKeep - + ". Annotations should be listed in the keep file without the @ symbol."); - System.exit(5); - } - } - - args = ArraysPlume.subarray(args, 2, args.length - 2); - } - if (args.length < 1) { - System.err.println("Usage: provide one or more directory names to process"); - System.exit(1); - } - for (String arg : args) { - process(arg); - } - } - - /** - * Maps from simple names to fully-qualified names of annotations. (Actually, it includes every - * class on the classpath.) - */ - static Multimap simpleToFullyQualified = ArrayListMultimap.create(); - - static { - try { - ClassPath cp = ClassPath.from(RemoveAnnotationsForInference.class.getClassLoader()); - for (ClassPath.ClassInfo ci : cp.getTopLevelClasses()) { - // There is no way to determine whether `ci` represents an annotation, without - // loading it. - // I could filter using a heuristic: only include classes in a package named "qual". - simpleToFullyQualified.put(ci.getSimpleName(), ci.getName()); - } - } catch (IOException e) { - throw new BugInCF(e); + /** + * Do not instantiate. This is a standalone program whose entry point is {@link + * #main(String[])}. + */ + private RemoveAnnotationsForInference() { + throw new Error("Do not instantiate RemoveAnnotationsForInference."); } - } - - /** - * Process each file in the given directory; see the {@link RemoveAnnotationsForInference class - * documentation} for details. - * - * @param dir directory to process - */ - private static void process(String dir) { - - Path root = JavaStubifier.dirnameToPath(dir); - - RemoveAnnotationsCallback rac = new RemoveAnnotationsCallback(); - CollectionStrategy strategy = new ParserCollectionStrategy(); - // Required to include directories that contain a module-info.java, which don't parse by - // default. - strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); - ProjectRoot projectRoot = strategy.collect(root); - - for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { - try { - sourceRoot.parse("", rac); - } catch (IOException e) { - throw new BugInCF(e); - } + + /** + * A list of annotations not to remove (i.e., to keep in the source code). Used to prevent + * project-specific annotations that must remain for the project to build from being removed by + * this program. (It would be burdensome to add all project-specific annotations to the global + * list in {@link #isTrustedAnnotation(String)}.) + */ + private static @MonotonicNonNull Set annotationsToKeep = null; + + /** + * Processes each provided command-line argument; see {@link RemoveAnnotationsForInference class + * documentation} for details. + * + * @param args command-line arguments: directories to process + */ + public static void main(String[] args) { + // TODO: using plume-lib's "Options" project here would be better, but would add a + // dependency to + // the whole Checker Framework, which is undesirable. Move this program elsewhere (e.g., to + // a + // plume-lib project)? + if (args[0].contentEquals("-keepFile")) { + if (args.length < 2) { + System.err.println( + "Usage: -keepFile requires an argument immediately after it: the path to the keep" + + " file."); + System.exit(2); + } + String keepFilePath = args[1]; + try (Stream lines = Files.lines(Paths.get(keepFilePath))) { + annotationsToKeep = lines.collect(Collectors.toSet()); + } catch (FileNotFoundException e) { + System.err.println("Error: Keep file " + keepFilePath + " not found."); + System.exit(3); + } catch (IOException e) { + System.err.println( + "Problem reading keep file " + keepFilePath + ": " + e.getMessage()); + System.exit(4); + } + + // Check for common mistake of adding "@" before the annotation name. + for (String annotationToKeep : annotationsToKeep) { + if (annotationToKeep.startsWith("@")) { + System.err.println( + "Error: Keep file includes an @ symbol before this annotation: " + + annotationToKeep + + ". Annotations should be listed in the keep file without the @ symbol."); + System.exit(5); + } + } + + args = ArraysPlume.subarray(args, 2, args.length - 2); + } + if (args.length < 1) { + System.err.println("Usage: provide one or more directory names to process"); + System.exit(1); + } + for (String arg : args) { + process(arg); + } } - } - - /** - * Callback to process each Java file; see the {@link RemoveAnnotationsForInference class - * documentation} for details. - */ - private static class RemoveAnnotationsCallback implements SourceRoot.Callback { - /** The visitor instance. */ - private final RemoveAnnotationsVisitor rav = new RemoveAnnotationsVisitor(); - - @Override - public Result process(Path localPath, Path absolutePath, ParseResult result) { - Optional opt = result.getResult(); - if (opt.isPresent()) { - CompilationUnit cu = opt.get(); - List removals = rav.visit(cu, null); - removeAnnotations(absolutePath, removals); - } - return Result.DONT_SAVE; + + /** + * Maps from simple names to fully-qualified names of annotations. (Actually, it includes every + * class on the classpath.) + */ + static Multimap simpleToFullyQualified = ArrayListMultimap.create(); + + static { + try { + ClassPath cp = ClassPath.from(RemoveAnnotationsForInference.class.getClassLoader()); + for (ClassPath.ClassInfo ci : cp.getTopLevelClasses()) { + // There is no way to determine whether `ci` represents an annotation, without + // loading it. + // I could filter using a heuristic: only include classes in a package named "qual". + simpleToFullyQualified.put(ci.getSimpleName(), ci.getName()); + } + } catch (IOException e) { + throw new BugInCF(e); + } } - } - - // An earlier implementation used ModifierVisitor. However, JavaParser's unparser can change - // the structure of the program. For example, it changes `protected @Nullable Object x;` to - // `@Nullable protected Object x;` which yields a type.anno.before.modifier error. - - /** - * Rewrites the file in place, removing the given annotations from it. - * - * @param absolutePath the path to the file - * @param removals the annotations to remove - */ - static void removeAnnotations(Path absolutePath, List removals) { - if (removals.isEmpty()) { - return; + + /** + * Process each file in the given directory; see the {@link RemoveAnnotationsForInference class + * documentation} for details. + * + * @param dir directory to process + */ + private static void process(String dir) { + + Path root = JavaStubifier.dirnameToPath(dir); + + RemoveAnnotationsCallback rac = new RemoveAnnotationsCallback(); + CollectionStrategy strategy = new ParserCollectionStrategy(); + // Required to include directories that contain a module-info.java, which don't parse by + // default. + strategy.getParserConfiguration().setLanguageLevel(JavaParserUtil.DEFAULT_LANGUAGE_LEVEL); + ProjectRoot projectRoot = strategy.collect(root); + + for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { + try { + sourceRoot.parse("", rac); + } catch (IOException e) { + throw new BugInCF(e); + } + } } - List lines; - try { - lines = Files.readAllLines(absolutePath); - } catch (IOException e) { - System.out.printf("Problem reading %s: %s%n", absolutePath, e.getMessage()); - System.exit(1); - throw new Error("unreachable"); + /** + * Callback to process each Java file; see the {@link RemoveAnnotationsForInference class + * documentation} for details. + */ + private static class RemoveAnnotationsCallback implements SourceRoot.Callback { + /** The visitor instance. */ + private final RemoveAnnotationsVisitor rav = new RemoveAnnotationsVisitor(); + + @Override + public Result process( + Path localPath, Path absolutePath, ParseResult result) { + Optional opt = result.getResult(); + if (opt.isPresent()) { + CompilationUnit cu = opt.get(); + List removals = rav.visit(cu, null); + removeAnnotations(absolutePath, removals); + } + return Result.DONT_SAVE; + } } - PositionUtils.sortByBeginPosition(removals); - Collections.reverse(removals); - - // This code (correctly) assumes that no element of `removals` is contained within another. - for (AnnotationExpr removal : removals) { - Position begin = removal.getBegin().get(); - Position end = removal.getEnd().get(); - int beginLine = begin.line - 1; - int beginColumn = begin.column - 1; - int endLine = end.line - 1; - int endColumn = end.column; // a JavaParser range is inclusive of the character at "end" - if (beginLine == endLine) { - String line = lines.get(beginLine); - String prefix = line.substring(0, beginColumn); - String suffix = line.substring(endColumn); - - // Remove whitespace to beautify formatting. - suffix = CharMatcher.whitespace().trimLeadingFrom(suffix); - if (suffix.startsWith("[")) { - prefix = CharMatcher.whitespace().trimTrailingFrom(prefix); + // An earlier implementation used ModifierVisitor. However, JavaParser's unparser can change + // the structure of the program. For example, it changes `protected @Nullable Object x;` to + // `@Nullable protected Object x;` which yields a type.anno.before.modifier error. + + /** + * Rewrites the file in place, removing the given annotations from it. + * + * @param absolutePath the path to the file + * @param removals the annotations to remove + */ + static void removeAnnotations(Path absolutePath, List removals) { + if (removals.isEmpty()) { + return; } - String newLine = prefix + suffix; - replaceLine(lines, beginLine, newLine); - } else { - String newLastLine = lines.get(endLine).substring(endColumn); - replaceLine(lines, endLine, newLastLine); - for (int lineno = endLine - 1; lineno > beginLine; lineno--) { - lines.remove(lineno); + List lines; + try { + lines = Files.readAllLines(absolutePath); + } catch (IOException e) { + System.out.printf("Problem reading %s: %s%n", absolutePath, e.getMessage()); + System.exit(1); + throw new Error("unreachable"); } - String newFirstLine = lines.get(beginLine).substring(0, beginColumn); - replaceLine(lines, beginLine, newFirstLine); - } - } - try (PrintWriter pw = - new PrintWriter( - Files.newBufferedWriter(Paths.get(absolutePath.toString()), StandardCharsets.UTF_8))) { - for (String line : lines) { - pw.println(line); - } - } catch (IOException e) { - throw new UncheckedIOException("problem writing " + absolutePath.toString(), e); - } - } - - /** - * If {@code newLine} is blank, removes the given line. Otherwise replaces the given line. - * - * @param lines the list in which to do replacement or removal - * @param lineno the index of the line to be removed or replaced - * @param newLine the new line for index {@code lineno} - */ - static void replaceLine(List lines, int lineno, String newLine) { - if (StringsPlume.isBlank(newLine)) { - lines.remove(lineno); - } else { - lines.set(lineno, newLine); + PositionUtils.sortByBeginPosition(removals); + Collections.reverse(removals); + + // This code (correctly) assumes that no element of `removals` is contained within another. + for (AnnotationExpr removal : removals) { + Position begin = removal.getBegin().get(); + Position end = removal.getEnd().get(); + int beginLine = begin.line - 1; + int beginColumn = begin.column - 1; + int endLine = end.line - 1; + int endColumn = end.column; // a JavaParser range is inclusive of the character at "end" + if (beginLine == endLine) { + String line = lines.get(beginLine); + String prefix = line.substring(0, beginColumn); + String suffix = line.substring(endColumn); + + // Remove whitespace to beautify formatting. + suffix = CharMatcher.whitespace().trimLeadingFrom(suffix); + if (suffix.startsWith("[")) { + prefix = CharMatcher.whitespace().trimTrailingFrom(prefix); + } + + String newLine = prefix + suffix; + replaceLine(lines, beginLine, newLine); + } else { + String newLastLine = lines.get(endLine).substring(endColumn); + replaceLine(lines, endLine, newLastLine); + for (int lineno = endLine - 1; lineno > beginLine; lineno--) { + lines.remove(lineno); + } + String newFirstLine = lines.get(beginLine).substring(0, beginColumn); + replaceLine(lines, beginLine, newFirstLine); + } + } + + try (PrintWriter pw = + new PrintWriter( + Files.newBufferedWriter( + Paths.get(absolutePath.toString()), StandardCharsets.UTF_8))) { + for (String line : lines) { + pw.println(line); + } + } catch (IOException e) { + throw new UncheckedIOException("problem writing " + absolutePath.toString(), e); + } } - } - - /** - * Visits one compilation unit, collecting the annotations that should be removed. See the {@link - * RemoveAnnotationsForInference class documentation} for more details. - * - *

          The annotations will be removed from the source code by the {@link #removeAnnotations} - * method. - */ - private static class RemoveAnnotationsVisitor - extends GenericListVisitorAdapter { /** - * Returns annotations that should be removed from source code. + * If {@code newLine} is blank, removes the given line. Otherwise replaces the given line. * - * @param n an annotation - * @param superResult the result of calling {@code super.visit} on n; this includes processing - * the subcomponents of n - * @return the argument to remove it, or superResult to retain it + * @param lines the list in which to do replacement or removal + * @param lineno the index of the line to be removed or replaced + * @param newLine the new line for index {@code lineno} */ - List processAnnotation(AnnotationExpr n, List superResult) { - if (n == null) { - // TODO: How is this possible? - return superResult; - } - - String name = n.getNameAsString(); - - // Retain annotations defined in the JDK. - if (isJdkAnnotation(name)) { - return superResult; - } - // Retain trusted annotations. - if (isTrustedAnnotation(name)) { - return superResult; - } - // Retain annotations that the user requested specifically should be kept. - if (shouldBeKept(name)) { - return superResult; - } - // Retain annotations for which warnings are suppressed. - if (isSuppressed(n)) { - return superResult; - } - - // The default behavior is to remove the annotation. - // Don't include superResult, which is contained within `n`. - return Collections.singletonList(n); + static void replaceLine(List lines, int lineno, String newLine) { + if (StringsPlume.isBlank(newLine)) { + lines.remove(lineno); + } else { + lines.set(lineno, newLine); + } } - // There are three JavaParser AST nodes that represent annotations + /** + * Visits one compilation unit, collecting the annotations that should be removed. See the + * {@link RemoveAnnotationsForInference class documentation} for more details. + * + *

          The annotations will be removed from the source code by the {@link #removeAnnotations} + * method. + */ + private static class RemoveAnnotationsVisitor + extends GenericListVisitorAdapter { + + /** + * Returns annotations that should be removed from source code. + * + * @param n an annotation + * @param superResult the result of calling {@code super.visit} on n; this includes + * processing the subcomponents of n + * @return the argument to remove it, or superResult to retain it + */ + List processAnnotation(AnnotationExpr n, List superResult) { + if (n == null) { + // TODO: How is this possible? + return superResult; + } + + String name = n.getNameAsString(); + + // Retain annotations defined in the JDK. + if (isJdkAnnotation(name)) { + return superResult; + } + // Retain trusted annotations. + if (isTrustedAnnotation(name)) { + return superResult; + } + // Retain annotations that the user requested specifically should be kept. + if (shouldBeKept(name)) { + return superResult; + } + // Retain annotations for which warnings are suppressed. + if (isSuppressed(n)) { + return superResult; + } + + // The default behavior is to remove the annotation. + // Don't include superResult, which is contained within `n`. + return Collections.singletonList(n); + } + + // There are three JavaParser AST nodes that represent annotations + + @Override + public List visit(MarkerAnnotationExpr n, Void arg) { + return processAnnotation(n, super.visit(n, arg)); + } - @Override - public List visit(MarkerAnnotationExpr n, Void arg) { - return processAnnotation(n, super.visit(n, arg)); + @Override + public List visit(NormalAnnotationExpr n, Void arg) { + return processAnnotation(n, super.visit(n, arg)); + } + + @Override + public List visit(SingleMemberAnnotationExpr n, Void arg) { + return processAnnotation(n, super.visit(n, arg)); + } } - @Override - public List visit(NormalAnnotationExpr n, Void arg) { - return processAnnotation(n, super.visit(n, arg)); + /** + * Returns true if the given annotation is defined in the JDK. + * + * @param name the annotation's name (simple or fully-qualified) + * @return true if the given annotation is defined in the JDK + */ + static boolean isJdkAnnotation(String name) { + return name.equals("Serial") + || name.equals("java.io.Serial") + || name.equals("Deprecated") + || name.equals("java.lang.Deprecated") + || name.equals("FunctionalInterface") + || name.equals("java.lang.FunctionalInterface") + || name.equals("Override") + || name.equals("java.lang.Override") + || name.equals("SafeVarargs") + || name.equals("java.lang.SafeVarargs") + || name.equals("Documented") + || name.equals("java.lang.annotation.Documented") + || name.equals("Inherited") + || name.equals("java.lang.annotation.Inherited") + || name.equals("Native") + || name.equals("java.lang.annotation.Native") + || name.equals("Repeatable") + || name.equals("java.lang.annotation.Repeatable") + || name.equals("Retention") + || name.equals("java.lang.annotation.Retention") + || name.equals("SuppressWarnings") + || name.equals("java.lang.SuppressWarnings") + || name.equals("Target") + || name.equals("java.lang.annotation.Target"); } - @Override - public List visit(SingleMemberAnnotationExpr n, Void arg) { - return processAnnotation(n, super.visit(n, arg)); + /** + * Returns true if the given annotation is trusted, not checked/verified. + * + * @param name the annotation's name (simple or fully-qualified) + * @return true if the given annotation is trusted, not verified + */ + static boolean isTrustedAnnotation(String name) { + // This list was determined by grepping for "trusted" in `qual` directories. + return name.equals("Untainted") + || name.equals("org.checkerframework.checker.tainting.qual.Untainted") + || name.equals("InternedDistinct") + || name.equals("org.checkerframework.checker.interning.qual.InternedDistinct") + || name.equals("ReturnsReceiver") + || name.equals("org.checkerframework.checker.builder.qual.ReturnsReceiver") + || name.equals("TerminatesExecution") + || name.equals("org.checkerframework.dataflow.qual.TerminatesExecution") + || name.equals("Covariant") + || name.equals("org.checkerframework.framework.qual.Covariant") + || name.equals("NonLeaked") + || name.equals("org.checkerframework.common.aliasing.qual.NonLeaked") + || name.equals("LeakedToResult") + || name.equals("org.checkerframework.common.aliasing.qual.LeakedToResult"); } - } - - /** - * Returns true if the given annotation is defined in the JDK. - * - * @param name the annotation's name (simple or fully-qualified) - * @return true if the given annotation is defined in the JDK - */ - static boolean isJdkAnnotation(String name) { - return name.equals("Serial") - || name.equals("java.io.Serial") - || name.equals("Deprecated") - || name.equals("java.lang.Deprecated") - || name.equals("FunctionalInterface") - || name.equals("java.lang.FunctionalInterface") - || name.equals("Override") - || name.equals("java.lang.Override") - || name.equals("SafeVarargs") - || name.equals("java.lang.SafeVarargs") - || name.equals("Documented") - || name.equals("java.lang.annotation.Documented") - || name.equals("Inherited") - || name.equals("java.lang.annotation.Inherited") - || name.equals("Native") - || name.equals("java.lang.annotation.Native") - || name.equals("Repeatable") - || name.equals("java.lang.annotation.Repeatable") - || name.equals("Retention") - || name.equals("java.lang.annotation.Retention") - || name.equals("SuppressWarnings") - || name.equals("java.lang.SuppressWarnings") - || name.equals("Target") - || name.equals("java.lang.annotation.Target"); - } - - /** - * Returns true if the given annotation is trusted, not checked/verified. - * - * @param name the annotation's name (simple or fully-qualified) - * @return true if the given annotation is trusted, not verified - */ - static boolean isTrustedAnnotation(String name) { - // This list was determined by grepping for "trusted" in `qual` directories. - return name.equals("Untainted") - || name.equals("org.checkerframework.checker.tainting.qual.Untainted") - || name.equals("InternedDistinct") - || name.equals("org.checkerframework.checker.interning.qual.InternedDistinct") - || name.equals("ReturnsReceiver") - || name.equals("org.checkerframework.checker.builder.qual.ReturnsReceiver") - || name.equals("TerminatesExecution") - || name.equals("org.checkerframework.dataflow.qual.TerminatesExecution") - || name.equals("Covariant") - || name.equals("org.checkerframework.framework.qual.Covariant") - || name.equals("NonLeaked") - || name.equals("org.checkerframework.common.aliasing.qual.NonLeaked") - || name.equals("LeakedToResult") - || name.equals("org.checkerframework.common.aliasing.qual.LeakedToResult"); - } - - /** - * Returns true iff the annotation is present in the user-supplied file of annotations to keep - * (via the {@code -keepFile} command-line option). - * - * @param name the annotation's name (simple or fully-qualified) - * @return true if the user requested that this annotation be kept in the source code - */ - private static boolean shouldBeKept(String name) { - return annotationsToKeep != null && annotationsToKeep.contains(name); - } - - // This approach searches upward to find all the active warning suppressions. - // An alternative, more efficient approach would be to track the current set of warning - // suppressions, using a stack. - // There are two problems with the alternative approach (and besides, this approach is fast - // enough as it is). - // 1. JavaParser sometimes visits members before the annotation, so there was not a chance to - // observe the annotation and place it on the suppression stack. This should be fixed for - // ModifierVisitor (but not for other visitors such as GenericListVisitorAdapter) in - // JavaParser release 3.19.0. - // 2. A user might write an annotation before @SuppressWarnings, as in: - // @Interned @SuppressWarnings("interning") - // The {@code @Interned} annotation is visited before the {@code @SuppressWarnings} - // annotation is. This could be addressed by searching just the parent's annotations. - - /** - * Returns true if warnings about the given annotation are suppressed. - * - *

          Its heuristic is to look for a {@code @SuppressWarnings} annotation on a containing program - * element, whose string is one of the elements of the annotation's fully-qualified name. - * - * @param arg an annotation - * @return true if warnings about the given annotation are suppressed - */ - private static boolean isSuppressed(AnnotationExpr arg) { - String name = arg.getNameAsString(); - - // If it's a simple name for which we know a fully-qualified name, - // try all fully-qualified names that it could expand to. - Collection names; - if (simpleToFullyQualified.containsKey(name)) { - names = simpleToFullyQualified.get(name); - } else { - names = Collections.singletonList(name); + + /** + * Returns true iff the annotation is present in the user-supplied file of annotations to keep + * (via the {@code -keepFile} command-line option). + * + * @param name the annotation's name (simple or fully-qualified) + * @return true if the user requested that this annotation be kept in the source code + */ + private static boolean shouldBeKept(String name) { + return annotationsToKeep != null && annotationsToKeep.contains(name); } - Iterator itor = new Node.ParentsVisitor(arg); - while (itor.hasNext()) { - Node n = itor.next(); - if (n instanceof NodeWithAnnotations) { - for (AnnotationExpr ae : ((NodeWithAnnotations) n).getAnnotations()) { - if (suppresses(ae, names)) { - return true; - } + // This approach searches upward to find all the active warning suppressions. + // An alternative, more efficient approach would be to track the current set of warning + // suppressions, using a stack. + // There are two problems with the alternative approach (and besides, this approach is fast + // enough as it is). + // 1. JavaParser sometimes visits members before the annotation, so there was not a chance to + // observe the annotation and place it on the suppression stack. This should be fixed for + // ModifierVisitor (but not for other visitors such as GenericListVisitorAdapter) in + // JavaParser release 3.19.0. + // 2. A user might write an annotation before @SuppressWarnings, as in: + // @Interned @SuppressWarnings("interning") + // The {@code @Interned} annotation is visited before the {@code @SuppressWarnings} + // annotation is. This could be addressed by searching just the parent's annotations. + + /** + * Returns true if warnings about the given annotation are suppressed. + * + *

          Its heuristic is to look for a {@code @SuppressWarnings} annotation on a containing + * program element, whose string is one of the elements of the annotation's fully-qualified + * name. + * + * @param arg an annotation + * @return true if warnings about the given annotation are suppressed + */ + private static boolean isSuppressed(AnnotationExpr arg) { + String name = arg.getNameAsString(); + + // If it's a simple name for which we know a fully-qualified name, + // try all fully-qualified names that it could expand to. + Collection names; + if (simpleToFullyQualified.containsKey(name)) { + names = simpleToFullyQualified.get(name); + } else { + names = Collections.singletonList(name); } - } - } - return false; - } - - /** - * Returns true if {@code suppressor} suppresses warnings regarding {@code suppressees}. - * - * @param suppressor an annotation that might be {@code @SuppressWarnings} or like it - * @param suppressees an annotation for which warnings might be suppressed. This is actually a - * list: if the annotation was written unqualified, it contains all the fully-qualified names - * that the unqualified annotation might stand for. - * @return true if {@code suppressor} suppresses warnings regarding {@code suppressees} - */ - static boolean suppresses(AnnotationExpr suppressor, Collection suppressees) { - List suppressWarningsStrings = suppressWarningsStrings(suppressor); - if (suppressWarningsStrings == null) { - return false; - } - List checkerNames = - CollectionsPlume.mapList( - RemoveAnnotationsForInference::checkerName, suppressWarningsStrings); - // "allcheckers" suppresses all warnings. - if (checkerNames.contains("allcheckers")) { - return true; - } - // Try every element of suppressee's fully-qualified name. - for (String suppressee : suppressees) { - for (String fqPart : suppressee.split("\\.")) { - if (checkerNames.contains(fqPart)) { - return true; + Iterator itor = new Node.ParentsVisitor(arg); + while (itor.hasNext()) { + Node n = itor.next(); + if (n instanceof NodeWithAnnotations) { + for (AnnotationExpr ae : ((NodeWithAnnotations) n).getAnnotations()) { + if (suppresses(ae, names)) { + return true; + } + } + } } - } + return false; } - return false; - } - - /** - * Given a @SuppressWarnings annotation, returns its strings. Given a different annotation that - * suppresses warnings (e.g., @IgnoreInWholeProgramInference, @Inject, @Singleton), returns - * strings for what it suppresses. Otherwise, returns null. - * - * @param n an annotation - * @return the (effective) arguments to {@code @SuppressWarnings}, or null - */ - private static @Nullable List suppressWarningsStrings(AnnotationExpr n) { - String name = n.getNameAsString(); - - if (name.equals("SuppressWarnings") || name.equals("java.lang.SuppressWarnings")) { - if (n instanceof MarkerAnnotationExpr) { - return Collections.emptyList(); - } else if (n instanceof NormalAnnotationExpr) { - NodeList pairs = ((NormalAnnotationExpr) n).getPairs(); - assert pairs.size() == 1; - MemberValuePair pair = pairs.get(0); - assert pair.getName().asString().equals("value"); - return annotationElementStrings(pair.getValue()); - } else if (n instanceof SingleMemberAnnotationExpr) { - return annotationElementStrings(((SingleMemberAnnotationExpr) n).getMemberValue()); - } else { - throw new BugInCF("Unexpected AnnotationExpr of type %s: %s", n.getClass(), n); - } + /** + * Returns true if {@code suppressor} suppresses warnings regarding {@code suppressees}. + * + * @param suppressor an annotation that might be {@code @SuppressWarnings} or like it + * @param suppressees an annotation for which warnings might be suppressed. This is actually a + * list: if the annotation was written unqualified, it contains all the fully-qualified + * names that the unqualified annotation might stand for. + * @return true if {@code suppressor} suppresses warnings regarding {@code suppressees} + */ + static boolean suppresses(AnnotationExpr suppressor, Collection suppressees) { + List suppressWarningsStrings = suppressWarningsStrings(suppressor); + if (suppressWarningsStrings == null) { + return false; + } + List checkerNames = + CollectionsPlume.mapList( + RemoveAnnotationsForInference::checkerName, suppressWarningsStrings); + // "allcheckers" suppresses all warnings. + if (checkerNames.contains("allcheckers")) { + return true; + } + + // Try every element of suppressee's fully-qualified name. + for (String suppressee : suppressees) { + for (String fqPart : suppressee.split("\\.")) { + if (checkerNames.contains(fqPart)) { + return true; + } + } + } + + return false; } - if (name.equals("IgnoreInWholeProgramInference") - || name.equals("org.checkerframework.framework.qual.IgnoreInWholeProgramInference") - || name.equals("Inject") - || name.equals("javax.inject.Inject") - || name.equals("Singleton") - || name.equals("javax.inject.Singleton") - || name.equals("Option") - || name.equals("org.plumelib.options.Option")) { - return Collections.singletonList("allcheckers"); + /** + * Given a @SuppressWarnings annotation, returns its strings. Given a different annotation that + * suppresses warnings (e.g., @IgnoreInWholeProgramInference, @Inject, @Singleton), returns + * strings for what it suppresses. Otherwise, returns null. + * + * @param n an annotation + * @return the (effective) arguments to {@code @SuppressWarnings}, or null + */ + private static @Nullable List suppressWarningsStrings(AnnotationExpr n) { + String name = n.getNameAsString(); + + if (name.equals("SuppressWarnings") || name.equals("java.lang.SuppressWarnings")) { + if (n instanceof MarkerAnnotationExpr) { + return Collections.emptyList(); + } else if (n instanceof NormalAnnotationExpr) { + NodeList pairs = ((NormalAnnotationExpr) n).getPairs(); + assert pairs.size() == 1; + MemberValuePair pair = pairs.get(0); + assert pair.getName().asString().equals("value"); + return annotationElementStrings(pair.getValue()); + } else if (n instanceof SingleMemberAnnotationExpr) { + return annotationElementStrings(((SingleMemberAnnotationExpr) n).getMemberValue()); + } else { + throw new BugInCF("Unexpected AnnotationExpr of type %s: %s", n.getClass(), n); + } + } + + if (name.equals("IgnoreInWholeProgramInference") + || name.equals("org.checkerframework.framework.qual.IgnoreInWholeProgramInference") + || name.equals("Inject") + || name.equals("javax.inject.Inject") + || name.equals("Singleton") + || name.equals("javax.inject.Singleton") + || name.equals("Option") + || name.equals("org.plumelib.options.Option")) { + return Collections.singletonList("allcheckers"); + } + + return null; } - return null; - } - - /** - * Given an annotation argument for an element of type String[], return a list of strings. Returns - * null if the list of suppressed strings is unknown (e.g., if the argument is a name expression). - * - * @param e an annotation argument - * @return the strings expressed by {@code e} - */ - private static @Nullable List annotationElementStrings(Expression e) { - if (e instanceof StringLiteralExpr) { - return Collections.singletonList(((StringLiteralExpr) e).asString()); - } else if (e instanceof ArrayInitializerExpr) { - NodeList values = ((ArrayInitializerExpr) e).getValues(); - List result = new ArrayList<>(values.size()); - for (Expression v : values) { - if (v instanceof StringLiteralExpr) { - result.add(((StringLiteralExpr) v).asString()); - } else if (v instanceof NameExpr) { - // TODO: is it better to return null here, thus causing nothing under this - // warning to be treated as "suppressed", or to return any keys that are string - // literals? Returning null here ensures that if any argument to the SW - // annotation isn't a string literal, then none of them are considered. - return null; + /** + * Given an annotation argument for an element of type String[], return a list of strings. + * Returns null if the list of suppressed strings is unknown (e.g., if the argument is a name + * expression). + * + * @param e an annotation argument + * @return the strings expressed by {@code e} + */ + private static @Nullable List annotationElementStrings(Expression e) { + if (e instanceof StringLiteralExpr) { + return Collections.singletonList(((StringLiteralExpr) e).asString()); + } else if (e instanceof ArrayInitializerExpr) { + NodeList values = ((ArrayInitializerExpr) e).getValues(); + List result = new ArrayList<>(values.size()); + for (Expression v : values) { + if (v instanceof StringLiteralExpr) { + result.add(((StringLiteralExpr) v).asString()); + } else if (v instanceof NameExpr) { + // TODO: is it better to return null here, thus causing nothing under this + // warning to be treated as "suppressed", or to return any keys that are string + // literals? Returning null here ensures that if any argument to the SW + // annotation isn't a string literal, then none of them are considered. + return null; + } else { + throw new BugInCF( + "Unexpected annotation element of type %s: %s", v.getClass(), v); + } + } + return result; + } else if (e instanceof NameExpr) { + // TODO: it would be better to check if the NameExpr represents a compile-time constant, + // and, if so, to use its value. But, it's not possible to determine that from just the + // result of the parser. + return null; } else { - throw new BugInCF("Unexpected annotation element of type %s: %s", v.getClass(), v); + throw new BugInCF("Unexpected %s: %s", e.getClass(), e); } - } - return result; - } else if (e instanceof NameExpr) { - // TODO: it would be better to check if the NameExpr represents a compile-time constant, - // and, if so, to use its value. But, it's not possible to determine that from just the - // result of the parser. - return null; - } else { - throw new BugInCF("Unexpected %s: %s", e.getClass(), e); } - } - - /** - * Returns the "checker name" part of a SuppressWarnings string: the part before the colon, or the - * whole thing if it contains no colon. - * - * @param s a SuppressWarnings string: the argument to {@code @SuppressWarnings} - * @return the part of s before the colon, or the whole thing if it contains no colon - */ - private static String checkerName(String s) { - int colonPos = s.indexOf(":"); - if (colonPos == -1) { - return s; - } else { - return s.substring(colonPos + 1); + + /** + * Returns the "checker name" part of a SuppressWarnings string: the part before the colon, or + * the whole thing if it contains no colon. + * + * @param s a SuppressWarnings string: the argument to {@code @SuppressWarnings} + * @return the part of s before the colon, or the whole thing if it contains no colon + */ + private static String checkerName(String s) { + int colonPos = s.indexOf(":"); + if (colonPos == -1) { + return s; + } else { + return s.substring(colonPos + 1); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java index af69a930f10..a4be1123196 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java @@ -5,11 +5,21 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Options; + +import org.checkerframework.checker.mustcall.qual.MustCallUnknown; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.StringsPlume; + import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -22,13 +32,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import org.checkerframework.checker.mustcall.qual.MustCallUnknown; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.StringsPlume; /** * Generates a stub file from a single class or an entire package. @@ -39,428 +42,429 @@ * @checker_framework.manual #stub Using stub classes */ public class StubGenerator { - /** The indentation for the class. */ - private static final String INDENTION = " "; - - /** The output stream. */ - private final PrintStream out; - - /** the current indentation for the line being processed. */ - private String currentIndention = ""; - - /** the package of the class being processed. */ - private String currentPackage = null; - - /** Constructs a {@code StubGenerator} that outputs to {@code System.out}. */ - public StubGenerator() { - this(System.out); - } - - /** - * Constructs a {@code StubGenerator} that outputs to the provided output stream. - * - * @param out the output stream - */ - public StubGenerator(PrintStream out) { - this.out = out; - } - - /** - * Constructs a {@code StubGenerator} that outputs to the provided output stream. - * - * @param out the output stream - */ - public StubGenerator(OutputStream out) { - this.out = new PrintStream(out); - } - - /** Generate the stub file for all the classes within the provided package. */ - public void stubFromField(Element elt) { - if (!(elt.getKind() == ElementKind.FIELD)) { - return; - } - - String pkg = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); - if (!"".equals(pkg)) { - currentPackage = pkg; - currentIndention = " "; - indent(); - } - VariableElement field = (VariableElement) elt; - printFieldDecl(field); - } - - /** Generate the stub file for all the classes within the provided package. */ - public void stubFromPackage(PackageElement packageElement) { - currentPackage = packageElement.getQualifiedName().toString(); - - indent(); - out.print("package "); - out.print(currentPackage); - out.println(";"); - - for (TypeElement element : ElementFilter.typesIn(packageElement.getEnclosedElements())) { - if (isPublicOrProtected(element)) { - out.println(); - printClass(element); - } - } - } - - /** - * Generate the stub file for all the classes within the package that contains {@code elt}. - * - * @param elt a method or constructor; generate stub files for its package - */ - public void stubFromMethod(ExecutableElement elt) { - if (!(elt.getKind() == ElementKind.CONSTRUCTOR || elt.getKind() == ElementKind.METHOD)) { - return; + /** The indentation for the class. */ + private static final String INDENTION = " "; + + /** The output stream. */ + private final PrintStream out; + + /** the current indentation for the line being processed. */ + private String currentIndention = ""; + + /** the package of the class being processed. */ + private String currentPackage = null; + + /** Constructs a {@code StubGenerator} that outputs to {@code System.out}. */ + public StubGenerator() { + this(System.out); + } + + /** + * Constructs a {@code StubGenerator} that outputs to the provided output stream. + * + * @param out the output stream + */ + public StubGenerator(PrintStream out) { + this.out = out; + } + + /** + * Constructs a {@code StubGenerator} that outputs to the provided output stream. + * + * @param out the output stream + */ + public StubGenerator(OutputStream out) { + this.out = new PrintStream(out); + } + + /** Generate the stub file for all the classes within the provided package. */ + public void stubFromField(Element elt) { + if (!(elt.getKind() == ElementKind.FIELD)) { + return; + } + + String pkg = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); + if (!"".equals(pkg)) { + currentPackage = pkg; + currentIndention = " "; + indent(); + } + VariableElement field = (VariableElement) elt; + printFieldDecl(field); + } + + /** Generate the stub file for all the classes within the provided package. */ + public void stubFromPackage(PackageElement packageElement) { + currentPackage = packageElement.getQualifiedName().toString(); + + indent(); + out.print("package "); + out.print(currentPackage); + out.println(";"); + + for (TypeElement element : ElementFilter.typesIn(packageElement.getEnclosedElements())) { + if (isPublicOrProtected(element)) { + out.println(); + printClass(element); + } + } + } + + /** + * Generate the stub file for all the classes within the package that contains {@code elt}. + * + * @param elt a method or constructor; generate stub files for its package + */ + public void stubFromMethod(ExecutableElement elt) { + if (!(elt.getKind() == ElementKind.CONSTRUCTOR || elt.getKind() == ElementKind.METHOD)) { + return; + } + + String newPackage = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); + if (!newPackage.isEmpty()) { + currentPackage = newPackage; + currentIndention = " "; + indent(); + } + + printMethodDecl(elt); + } + + /** Generate the stub file for provided class. The generated file includes the package name. */ + public void stubFromType(TypeElement typeElement) { + + // only output stub for classes or interfaces. not enums + if (typeElement.getKind() != ElementKind.CLASS + && typeElement.getKind() != ElementKind.INTERFACE) { + return; + } + + String newPackageName = + ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(typeElement)); + boolean newPackage = !newPackageName.equals(currentPackage); + currentPackage = newPackageName; + + if (newPackage) { + indent(); + + out.print("package "); + out.print(currentPackage); + out.println(";"); + out.println(); + } + String fullClassName = ElementUtils.getQualifiedClassName(typeElement).toString(); + + String className = + fullClassName.substring( + fullClassName.indexOf(currentPackage) + + currentPackage.length() + // +1 because currentPackage doesn't include + // the . between the package name and the classname + + 1); + + int index = className.lastIndexOf('.'); + if (index == -1) { + printClass(typeElement); + } else { + String outer = className.substring(0, index); + printClass(typeElement, outer.replace('.', '$')); + } + } + + /** helper method that outputs the index for the provided class. */ + private void printClass(TypeElement typeElement) { + printClass(typeElement, null); + } + + /** + * Helper method that prints the stub file for the provided class. + * + * @param typeElement the class to output + * @param outerClass the outer class of the class, or null if {@code typeElement} is a top-level + * class + */ + private void printClass(TypeElement typeElement, @Nullable String outerClass) { + indent(); + + List teannos = typeElement.getAnnotationMirrors(); + if (teannos != null && !teannos.isEmpty()) { + for (AnnotationMirror am : teannos) { + out.println(am); + } + } + + // This could be a `switch` statement. + if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { + out.print("@interface"); + } else if (typeElement.getKind() == ElementKind.ENUM) { + out.print("enum"); + } else if (typeElement.getKind() == ElementKind.INTERFACE) { + out.print("interface"); + } else if (ElementUtils.isRecordElement(typeElement)) { + out.print("record"); + } else if (typeElement.getKind() == ElementKind.CLASS) { + out.print("class"); + } else { + // Shouldn't this throw an exception? + return; + } + + out.print(' '); + if (outerClass != null) { + out.print(outerClass + "$"); + } + out.print(typeElement.getSimpleName()); + + // Type parameters + if (!typeElement.getTypeParameters().isEmpty()) { + out.print('<'); + out.print(formatList(typeElement.getTypeParameters())); + out.print('>'); + } + + // Extends + if (typeElement.getSuperclass().getKind() != TypeKind.NONE + && !TypesUtils.isObject(typeElement.getSuperclass())) { + out.print(" extends "); + out.print(formatType(typeElement.getSuperclass())); + } + + // implements + if (!typeElement.getInterfaces().isEmpty()) { + boolean isInterface = typeElement.getKind() == ElementKind.INTERFACE; + out.print(isInterface ? " extends " : " implements "); + List ls = + CollectionsPlume.mapList( + StubGenerator::formatType, typeElement.getInterfaces()); + out.print(formatList(ls)); + } + + out.println(" {"); + String tempIndention = currentIndention; + + currentIndention = currentIndention + INDENTION; + + // Inner classes, which the stub generator prints later. + List innerClass = new ArrayList<>(); + // side-effects innerClass + printTypeMembers(typeElement.getEnclosedElements(), innerClass); + + currentIndention = tempIndention; + indent(); + out.println("}"); + + for (TypeElement element : innerClass) { + printClass(element, typeElement.getSimpleName().toString()); + } + } + + /** + * Helper method that outputs the public or protected inner members of a class. + * + * @param members list of the class members + */ + private void printTypeMembers(List members, List innerClass) { + for (Element element : members) { + if (isPublicOrProtected(element)) { + printMember(element, innerClass); + } + } + } + + /** Helper method that outputs the declaration of the member. */ + private void printMember(Element member, List innerClass) { + if (member.getKind().isField()) { + printFieldDecl((VariableElement) member); + } else if (member instanceof ExecutableElement) { + printMethodDecl((ExecutableElement) member); + } else if (member instanceof TypeElement) { + innerClass.add((TypeElement) member); + } + } + + /** + * Helper method that outputs the field declaration for the given field. + * + *

          It indicates whether the field is {@code protected}. + */ + private void printFieldDecl(VariableElement field) { + if ("class".equals(field.getSimpleName().toString())) { + error("Cannot write class literals in stub files."); + return; + } + + indent(); + + List veannos = field.getAnnotationMirrors(); + if (veannos != null && !veannos.isEmpty()) { + for (AnnotationMirror am : veannos) { + out.println(am); + } + } + + // if protected, indicate that, but not public + if (field.getModifiers().contains(Modifier.PROTECTED)) { + out.print("protected "); + } + if (field.getModifiers().contains(Modifier.STATIC)) { + out.print("static "); + } + if (field.getModifiers().contains(Modifier.FINAL)) { + out.print("final "); + } + + out.print(formatType(field.asType())); + + out.print(" "); + out.print(field.getSimpleName()); + out.println(';'); + } + + /** + * Helper method that outputs the method declaration for the given method. + * + *

          IT indicates whether the field is {@code protected}. + */ + private void printMethodDecl(ExecutableElement method) { + indent(); + + List eeannos = method.getAnnotationMirrors(); + if (eeannos != null && !eeannos.isEmpty()) { + for (AnnotationMirror am : eeannos) { + out.println(am); + } + } + + // if protected, indicate that, but not public + if (method.getModifiers().contains(Modifier.PROTECTED)) { + out.print("protected "); + } + if (method.getModifiers().contains(Modifier.STATIC)) { + out.print("static "); + } + + // print Generic arguments + if (!method.getTypeParameters().isEmpty()) { + out.print('<'); + out.print(formatList(method.getTypeParameters())); + out.print("> "); + } + + // not return type for constructors + if (method.getKind() != ElementKind.CONSTRUCTOR) { + out.print(formatType(method.getReturnType())); + out.print(" "); + out.print(method.getSimpleName()); + } else { + out.print(method.getEnclosingElement().getSimpleName()); + } + + out.print('('); + + boolean isFirst = true; + for (VariableElement param : method.getParameters()) { + if (!isFirst) { + out.print(", "); + } + out.print(formatType(param.asType())); + out.print(' '); + out.print(param.getSimpleName()); + isFirst = false; + } + + out.print(')'); + + if (!method.getThrownTypes().isEmpty()) { + out.print(" throws "); + List ltt = + CollectionsPlume.mapList(StubGenerator::formatType, method.getThrownTypes()); + out.print(formatList(ltt)); + } + out.println(';'); + } + + /** Indent the current line. */ + private void indent() { + out.print(currentIndention); + } + + /** + * Return a string representation of the list in the form of {@code item1, item2, item3, ...}, + * without surrounding square brackets as the default representation has. + * + * @param lst a list to format + * @return a string representation of the list, without surrounding square brackets + */ + private String formatList(@MustCallUnknown List lst) { + return StringsPlume.join(", ", lst); + } + + /** Returns true if the element is public or protected element. */ + private boolean isPublicOrProtected(Element element) { + return element.getModifiers().contains(Modifier.PUBLIC) + || element.getModifiers().contains(Modifier.PROTECTED); + } + + /** + * Returns the simple name of the type. + * + * @param typeRep a type + * @return the simple name of the type + */ + private static String formatType(TypeMirror typeRep) { + StringTokenizer tokenizer = new StringTokenizer(typeRep.toString(), "()<>[], ", true); + StringBuilder sb = new StringBuilder(); + + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (token.length() == 1 || token.lastIndexOf('.') == -1) { + sb.append(token); + } else { + int index = token.lastIndexOf('.'); + sb.append(token.substring(index + 1)); + } + } + return sb.toString(); + } + + /** + * The main entry point to StubGenerator. + * + * @param args command-line arguments + */ + @SuppressWarnings("signature") // User-supplied arguments to main + public static void main(String[] args) { + if (args.length != 1) { + System.out.println("Usage:"); + System.out.println(" java StubGenerator [class or package name]"); + return; + } + + Context context = new Context(); + Options options = Options.instance(context); + if (SystemUtil.jreVersion == 8) { + options.put(Option.SOURCE, "8"); + options.put(Option.TARGET, "8"); + } + + JavaCompiler javac = JavaCompiler.instance(context); + javac.initModules(com.sun.tools.javac.util.List.nil()); + javac.enterDone(); + + ProcessingEnvironment env = JavacProcessingEnvironment.instance(context); + + StubGenerator generator = new StubGenerator(); + + if (env.getElementUtils().getPackageElement(args[0]) != null) { + generator.stubFromPackage(env.getElementUtils().getPackageElement(args[0])); + } else if (env.getElementUtils().getTypeElement(args[0]) != null) { + generator.stubFromType(env.getElementUtils().getTypeElement(args[0])); + } else { + error("Couldn't find a package or a class named " + args[0]); + } + } + + private static void error(String string) { + System.err.println("StubGenerator: " + string); } - - String newPackage = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); - if (!newPackage.isEmpty()) { - currentPackage = newPackage; - currentIndention = " "; - indent(); - } - - printMethodDecl(elt); - } - - /** Generate the stub file for provided class. The generated file includes the package name. */ - public void stubFromType(TypeElement typeElement) { - - // only output stub for classes or interfaces. not enums - if (typeElement.getKind() != ElementKind.CLASS - && typeElement.getKind() != ElementKind.INTERFACE) { - return; - } - - String newPackageName = - ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(typeElement)); - boolean newPackage = !newPackageName.equals(currentPackage); - currentPackage = newPackageName; - - if (newPackage) { - indent(); - - out.print("package "); - out.print(currentPackage); - out.println(";"); - out.println(); - } - String fullClassName = ElementUtils.getQualifiedClassName(typeElement).toString(); - - String className = - fullClassName.substring( - fullClassName.indexOf(currentPackage) - + currentPackage.length() - // +1 because currentPackage doesn't include - // the . between the package name and the classname - + 1); - - int index = className.lastIndexOf('.'); - if (index == -1) { - printClass(typeElement); - } else { - String outer = className.substring(0, index); - printClass(typeElement, outer.replace('.', '$')); - } - } - - /** helper method that outputs the index for the provided class. */ - private void printClass(TypeElement typeElement) { - printClass(typeElement, null); - } - - /** - * Helper method that prints the stub file for the provided class. - * - * @param typeElement the class to output - * @param outerClass the outer class of the class, or null if {@code typeElement} is a top-level - * class - */ - private void printClass(TypeElement typeElement, @Nullable String outerClass) { - indent(); - - List teannos = typeElement.getAnnotationMirrors(); - if (teannos != null && !teannos.isEmpty()) { - for (AnnotationMirror am : teannos) { - out.println(am); - } - } - - // This could be a `switch` statement. - if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { - out.print("@interface"); - } else if (typeElement.getKind() == ElementKind.ENUM) { - out.print("enum"); - } else if (typeElement.getKind() == ElementKind.INTERFACE) { - out.print("interface"); - } else if (ElementUtils.isRecordElement(typeElement)) { - out.print("record"); - } else if (typeElement.getKind() == ElementKind.CLASS) { - out.print("class"); - } else { - // Shouldn't this throw an exception? - return; - } - - out.print(' '); - if (outerClass != null) { - out.print(outerClass + "$"); - } - out.print(typeElement.getSimpleName()); - - // Type parameters - if (!typeElement.getTypeParameters().isEmpty()) { - out.print('<'); - out.print(formatList(typeElement.getTypeParameters())); - out.print('>'); - } - - // Extends - if (typeElement.getSuperclass().getKind() != TypeKind.NONE - && !TypesUtils.isObject(typeElement.getSuperclass())) { - out.print(" extends "); - out.print(formatType(typeElement.getSuperclass())); - } - - // implements - if (!typeElement.getInterfaces().isEmpty()) { - boolean isInterface = typeElement.getKind() == ElementKind.INTERFACE; - out.print(isInterface ? " extends " : " implements "); - List ls = - CollectionsPlume.mapList(StubGenerator::formatType, typeElement.getInterfaces()); - out.print(formatList(ls)); - } - - out.println(" {"); - String tempIndention = currentIndention; - - currentIndention = currentIndention + INDENTION; - - // Inner classes, which the stub generator prints later. - List innerClass = new ArrayList<>(); - // side-effects innerClass - printTypeMembers(typeElement.getEnclosedElements(), innerClass); - - currentIndention = tempIndention; - indent(); - out.println("}"); - - for (TypeElement element : innerClass) { - printClass(element, typeElement.getSimpleName().toString()); - } - } - - /** - * Helper method that outputs the public or protected inner members of a class. - * - * @param members list of the class members - */ - private void printTypeMembers(List members, List innerClass) { - for (Element element : members) { - if (isPublicOrProtected(element)) { - printMember(element, innerClass); - } - } - } - - /** Helper method that outputs the declaration of the member. */ - private void printMember(Element member, List innerClass) { - if (member.getKind().isField()) { - printFieldDecl((VariableElement) member); - } else if (member instanceof ExecutableElement) { - printMethodDecl((ExecutableElement) member); - } else if (member instanceof TypeElement) { - innerClass.add((TypeElement) member); - } - } - - /** - * Helper method that outputs the field declaration for the given field. - * - *

          It indicates whether the field is {@code protected}. - */ - private void printFieldDecl(VariableElement field) { - if ("class".equals(field.getSimpleName().toString())) { - error("Cannot write class literals in stub files."); - return; - } - - indent(); - - List veannos = field.getAnnotationMirrors(); - if (veannos != null && !veannos.isEmpty()) { - for (AnnotationMirror am : veannos) { - out.println(am); - } - } - - // if protected, indicate that, but not public - if (field.getModifiers().contains(Modifier.PROTECTED)) { - out.print("protected "); - } - if (field.getModifiers().contains(Modifier.STATIC)) { - out.print("static "); - } - if (field.getModifiers().contains(Modifier.FINAL)) { - out.print("final "); - } - - out.print(formatType(field.asType())); - - out.print(" "); - out.print(field.getSimpleName()); - out.println(';'); - } - - /** - * Helper method that outputs the method declaration for the given method. - * - *

          IT indicates whether the field is {@code protected}. - */ - private void printMethodDecl(ExecutableElement method) { - indent(); - - List eeannos = method.getAnnotationMirrors(); - if (eeannos != null && !eeannos.isEmpty()) { - for (AnnotationMirror am : eeannos) { - out.println(am); - } - } - - // if protected, indicate that, but not public - if (method.getModifiers().contains(Modifier.PROTECTED)) { - out.print("protected "); - } - if (method.getModifiers().contains(Modifier.STATIC)) { - out.print("static "); - } - - // print Generic arguments - if (!method.getTypeParameters().isEmpty()) { - out.print('<'); - out.print(formatList(method.getTypeParameters())); - out.print("> "); - } - - // not return type for constructors - if (method.getKind() != ElementKind.CONSTRUCTOR) { - out.print(formatType(method.getReturnType())); - out.print(" "); - out.print(method.getSimpleName()); - } else { - out.print(method.getEnclosingElement().getSimpleName()); - } - - out.print('('); - - boolean isFirst = true; - for (VariableElement param : method.getParameters()) { - if (!isFirst) { - out.print(", "); - } - out.print(formatType(param.asType())); - out.print(' '); - out.print(param.getSimpleName()); - isFirst = false; - } - - out.print(')'); - - if (!method.getThrownTypes().isEmpty()) { - out.print(" throws "); - List ltt = - CollectionsPlume.mapList(StubGenerator::formatType, method.getThrownTypes()); - out.print(formatList(ltt)); - } - out.println(';'); - } - - /** Indent the current line. */ - private void indent() { - out.print(currentIndention); - } - - /** - * Return a string representation of the list in the form of {@code item1, item2, item3, ...}, - * without surrounding square brackets as the default representation has. - * - * @param lst a list to format - * @return a string representation of the list, without surrounding square brackets - */ - private String formatList(@MustCallUnknown List lst) { - return StringsPlume.join(", ", lst); - } - - /** Returns true if the element is public or protected element. */ - private boolean isPublicOrProtected(Element element) { - return element.getModifiers().contains(Modifier.PUBLIC) - || element.getModifiers().contains(Modifier.PROTECTED); - } - - /** - * Returns the simple name of the type. - * - * @param typeRep a type - * @return the simple name of the type - */ - private static String formatType(TypeMirror typeRep) { - StringTokenizer tokenizer = new StringTokenizer(typeRep.toString(), "()<>[], ", true); - StringBuilder sb = new StringBuilder(); - - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - if (token.length() == 1 || token.lastIndexOf('.') == -1) { - sb.append(token); - } else { - int index = token.lastIndexOf('.'); - sb.append(token.substring(index + 1)); - } - } - return sb.toString(); - } - - /** - * The main entry point to StubGenerator. - * - * @param args command-line arguments - */ - @SuppressWarnings("signature") // User-supplied arguments to main - public static void main(String[] args) { - if (args.length != 1) { - System.out.println("Usage:"); - System.out.println(" java StubGenerator [class or package name]"); - return; - } - - Context context = new Context(); - Options options = Options.instance(context); - if (SystemUtil.jreVersion == 8) { - options.put(Option.SOURCE, "8"); - options.put(Option.TARGET, "8"); - } - - JavaCompiler javac = JavaCompiler.instance(context); - javac.initModules(com.sun.tools.javac.util.List.nil()); - javac.enterDone(); - - ProcessingEnvironment env = JavacProcessingEnvironment.instance(context); - - StubGenerator generator = new StubGenerator(); - - if (env.getElementUtils().getPackageElement(args[0]) != null) { - generator.stubFromPackage(env.getElementUtils().getPackageElement(args[0])); - } else if (env.getElementUtils().getTypeElement(args[0]) != null) { - generator.stubFromType(env.getElementUtils().getTypeElement(args[0])); - } else { - error("Couldn't find a package or a class named " + args[0]); - } - } - - private static void error(String string) { - System.err.println("StubGenerator: " + string); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java index 41ac774eec3..68b2ec4ddfb 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java @@ -34,23 +34,7 @@ import com.github.javaparser.ast.type.VoidType; import com.github.javaparser.ast.type.WildcardType; import com.github.javaparser.ast.visitor.GenericVisitorAdapter; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedWriter; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; + import org.checkerframework.afu.scenelib.annotations.Annotation; import org.checkerframework.afu.scenelib.annotations.el.AClass; import org.checkerframework.afu.scenelib.annotations.el.ADeclaration; @@ -75,6 +59,24 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.reflection.Signatures; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Convert a JAIF file plus a stub file into index files (JAIFs). Note that the resulting index * files will not include annotation definitions, for which stubfiles do not generally provide @@ -84,622 +86,631 @@ * #main(String[])} method converts multiple stub files, instantiating the class multiple times. */ public class ToIndexFileConverter extends GenericVisitorAdapter { - // The possessive modifiers "*+" are for efficiency only. - // private static Pattern packagePattern = - // Pattern.compile("\\bpackage *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); - /** A pattern that matches an import statement. */ - private static final Pattern importPattern = - Pattern.compile("\\bimport *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); - - /** - * Package name that is active at the current point in the input file. Changes as package - * declarations are encountered. - */ - private final @DotSeparatedIdentifiers String pkgName; - - /** Imports that appear in the stub file. */ - private final List imports; - - /** A scene read from the input JAIF file, and will be written to the output JAIF file. */ - private final AScene scene; - - /** - * Creates a new ToIndexFileConverter. - * - * @param pkgDecl the AST node for package declaration - * @param importDecls the AST nodes for import declarations - * @param scene scene for visitor methods to fill in - */ - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString - public ToIndexFileConverter( - @Nullable PackageDeclaration pkgDecl, List importDecls, AScene scene) { - this.scene = scene; - pkgName = pkgDecl == null ? null : pkgDecl.getNameAsString(); - if (importDecls == null) { - imports = Collections.emptyList(); - } else { - ArrayList imps = new ArrayList<>(importDecls.size()); - for (ImportDeclaration decl : importDecls) { - if (!decl.isStatic()) { - Matcher m = importPattern.matcher(decl.toString()); - if (m.find()) { - String s = m.group(1); - if (s != null) { - imps.add(s); + // The possessive modifiers "*+" are for efficiency only. + // private static Pattern packagePattern = + // Pattern.compile("\\bpackage *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); + /** A pattern that matches an import statement. */ + private static final Pattern importPattern = + Pattern.compile("\\bimport *+((?:[^.]*+[.] *+)*+[^ ]*) *+;"); + + /** + * Package name that is active at the current point in the input file. Changes as package + * declarations are encountered. + */ + private final @DotSeparatedIdentifiers String pkgName; + + /** Imports that appear in the stub file. */ + private final List imports; + + /** A scene read from the input JAIF file, and will be written to the output JAIF file. */ + private final AScene scene; + + /** + * Creates a new ToIndexFileConverter. + * + * @param pkgDecl the AST node for package declaration + * @param importDecls the AST nodes for import declarations + * @param scene scene for visitor methods to fill in + */ + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString + public ToIndexFileConverter( + @Nullable PackageDeclaration pkgDecl, + List importDecls, + AScene scene) { + this.scene = scene; + pkgName = pkgDecl == null ? null : pkgDecl.getNameAsString(); + if (importDecls == null) { + imports = Collections.emptyList(); + } else { + ArrayList imps = new ArrayList<>(importDecls.size()); + for (ImportDeclaration decl : importDecls) { + if (!decl.isStatic()) { + Matcher m = importPattern.matcher(decl.toString()); + if (m.find()) { + String s = m.group(1); + if (s != null) { + imps.add(s); + } + } + } } - } + imps.trimToSize(); + imports = Collections.unmodifiableList(imps); } - } - imps.trimToSize(); - imports = Collections.unmodifiableList(imps); - } - } - - /** - * Parse stub files and write out equivalent JAIFs. Note that the results do not include - * annotation definitions, for which stubfiles do not generally provide complete information. - * - * @param args name of JAIF with annotation definition, followed by names of stub files to be - * converted (if none given, program reads from standard input) - */ - public static void main(String[] args) { - if (args.length < 1) { - System.err.println("usage: java ToIndexFileConverter myfile.jaif [stubfile...]"); - System.err.println("(myfile.jaif contains needed annotation definitions)"); - System.exit(1); } - AScene scene = new AScene(); - try { - // args[0] is a jaif file with needed annotation definitions - IndexFileParser.parseFile(args[0], scene); - - if (args.length == 1) { - convert(scene, System.in, System.out); - return; - } - - for (int i = 1; i < args.length; i++) { - String f0 = args[i]; - String f1 = (f0.endsWith(".astub") ? f0.substring(0, f0.length() - 6) : f0) + ".jaif"; - try (InputStream in = new BufferedInputStream(new FileInputStream(f0)); - OutputStream out = new BufferedOutputStream(new FileOutputStream(f1)); ) { - convert(new AScene(scene), in, out); + /** + * Parse stub files and write out equivalent JAIFs. Note that the results do not include + * annotation definitions, for which stubfiles do not generally provide complete information. + * + * @param args name of JAIF with annotation definition, followed by names of stub files to be + * converted (if none given, program reads from standard input) + */ + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("usage: java ToIndexFileConverter myfile.jaif [stubfile...]"); + System.err.println("(myfile.jaif contains needed annotation definitions)"); + System.exit(1); } - } - } catch (Throwable e) { - e.printStackTrace(); - System.exit(1); - } - } - - /** - * Augment given scene with information from stubfile, reading stubs from input stream and writing - * JAIF to output stream. - * - * @param scene the initial scene - * @param in stubfile contents - * @param out the output stream for the JAIF file that holds the augmented scene - * @throws ParseException if the stub file cannot be parsed - * @throws DefException if two different definitions of the same annotation cannot be unified - * @throws IOException if there is trouble with file reading or writing - */ - private static void convert(AScene scene, InputStream in, OutputStream out) - throws IOException, DefException, ParseException { - StubUnit iu; - try { - iu = JavaParserUtil.parseStubUnit(in); - } catch (ParseProblemException e) { - iu = null; - throw new BugInCF( - "ToIndexFileConverter: exception from JavaParser.parseStubUnit for InputStream." - + System.lineSeparator() - + "Problem message with problems encountered: " - + e.getMessage()); - } - extractScene(iu, scene); - try (Writer w = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) { - IndexFileWriter.write(scene, w); - } - } - - /** - * Entry point of recursive-descent IndexUnit to AScene transformer. It operates by visiting the - * stub and scene in parallel, descending into them in the same way. It augments the existing - * scene (it does not create a new scene). - * - * @param iu {@link StubUnit} representing stubfile - */ - private static void extractScene(StubUnit iu, AScene scene) { - for (CompilationUnit cu : iu.getCompilationUnits()) { - NodeList> typeDecls = cu.getTypes(); - if (typeDecls != null && cu.getPackageDeclaration().isPresent()) { - List impDecls = cu.getImports(); - PackageDeclaration pkgDecl = cu.getPackageDeclaration().get(); - for (TypeDeclaration typeDecl : typeDecls) { - ToIndexFileConverter converter = new ToIndexFileConverter(pkgDecl, impDecls, scene); - String pkgName = converter.pkgName; - String name = typeDecl.getNameAsString(); - if (pkgName != null) { - name = pkgName + "." + name; - } - typeDecl.accept(converter, scene.classes.getVivify(name)); + + AScene scene = new AScene(); + try { + // args[0] is a jaif file with needed annotation definitions + IndexFileParser.parseFile(args[0], scene); + + if (args.length == 1) { + convert(scene, System.in, System.out); + return; + } + + for (int i = 1; i < args.length; i++) { + String f0 = args[i]; + String f1 = + (f0.endsWith(".astub") ? f0.substring(0, f0.length() - 6) : f0) + ".jaif"; + try (InputStream in = new BufferedInputStream(new FileInputStream(f0)); + OutputStream out = new BufferedOutputStream(new FileOutputStream(f1)); ) { + convert(new AScene(scene), in, out); + } + } + } catch (Throwable e) { + e.printStackTrace(); + System.exit(1); } - } - } - } - - /** - * Builds simplified annotation from its declaration. Only the name is included, because stubfiles - * do not generally have access to the full definitions of annotations. - */ - private static @Nullable Annotation extractAnnotation(AnnotationExpr expr) { - String exprName = expr.toString().substring(1); // leave off leading '@' - - // Eliminate jdk.Profile+Annotation, a synthetic annotation that - // the JDK adds, apparently for profiling. - if (exprName.contains("+")) { - return null; - } - @SuppressWarnings("signature") // special case for annotations containing "+" - AnnotationDef def = - new AnnotationDef(exprName, "ToIndexFileConverter.extractAnnotation(" + expr + ")"); - def.setFieldTypes(Collections.emptyMap()); - return new Annotation(def, Collections.emptyMap()); - } - - @Override - public Void visit(AnnotationDeclaration decl, AElement elem) { - return null; - } - - @Override - public Void visit(BlockStmt stmt, AElement elem) { - return null; - // super.visit(stmt, elem); - } - - @Override - public Void visit(ClassOrInterfaceDeclaration decl, AElement elem) { - visitDecl(decl, (ADeclaration) elem); - return super.visit(decl, elem); - } - - @Override - public Void visit(ConstructorDeclaration decl, AElement elem) { - List params = decl.getParameters(); - List rcvrAnnos = decl.getAnnotations(); - BlockStmt body = decl.getBody(); - StringBuilder sb = new StringBuilder("("); - AClass clazz = (AClass) elem; - AMethod method; - - // Some of the methods in the generated parser use null to represent an empty list. - if (params != null) { - for (Parameter param : params) { - Type ptype = param.getType(); - sb.append(getJVML(ptype)); - } } - sb.append(")V"); - method = clazz.methods.getVivify(sb.toString()); - visitDecl(decl, method); - if (params != null) { - for (int i = 0; i < params.size(); i++) { - Parameter param = params.get(i); - AField field = method.parameters.getVivify(i); - visitType(param.getType(), field.type); - } + + /** + * Augment given scene with information from stubfile, reading stubs from input stream and + * writing JAIF to output stream. + * + * @param scene the initial scene + * @param in stubfile contents + * @param out the output stream for the JAIF file that holds the augmented scene + * @throws ParseException if the stub file cannot be parsed + * @throws DefException if two different definitions of the same annotation cannot be unified + * @throws IOException if there is trouble with file reading or writing + */ + private static void convert(AScene scene, InputStream in, OutputStream out) + throws IOException, DefException, ParseException { + StubUnit iu; + try { + iu = JavaParserUtil.parseStubUnit(in); + } catch (ParseProblemException e) { + iu = null; + throw new BugInCF( + "ToIndexFileConverter: exception from JavaParser.parseStubUnit for InputStream." + + System.lineSeparator() + + "Problem message with problems encountered: " + + e.getMessage()); + } + extractScene(iu, scene); + try (Writer w = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) { + IndexFileWriter.write(scene, w); + } } - if (rcvrAnnos != null) { - for (AnnotationExpr expr : rcvrAnnos) { - Annotation anno = extractAnnotation(expr); - method.receiver.tlAnnotationsHere.add(anno); - } + + /** + * Entry point of recursive-descent IndexUnit to AScene transformer. It operates by visiting the + * stub and scene in parallel, descending into them in the same way. It augments the existing + * scene (it does not create a new scene). + * + * @param iu {@link StubUnit} representing stubfile + */ + private static void extractScene(StubUnit iu, AScene scene) { + for (CompilationUnit cu : iu.getCompilationUnits()) { + NodeList> typeDecls = cu.getTypes(); + if (typeDecls != null && cu.getPackageDeclaration().isPresent()) { + List impDecls = cu.getImports(); + PackageDeclaration pkgDecl = cu.getPackageDeclaration().get(); + for (TypeDeclaration typeDecl : typeDecls) { + ToIndexFileConverter converter = + new ToIndexFileConverter(pkgDecl, impDecls, scene); + String pkgName = converter.pkgName; + String name = typeDecl.getNameAsString(); + if (pkgName != null) { + name = pkgName + "." + name; + } + typeDecl.accept(converter, scene.classes.getVivify(name)); + } + } + } } - return body == null ? null : body.accept(this, method); - // return super.visit(decl, elem); - } - - @Override - public Void visit(EnumConstantDeclaration decl, AElement elem) { - AField field = ((AClass) elem).fields.getVivify(decl.getNameAsString()); - visitDecl(decl, field); - return super.visit(decl, field); - } - - @Override - public Void visit(EnumDeclaration decl, AElement elem) { - visitDecl(decl, (ADeclaration) elem); - return super.visit(decl, elem); - } - - @Override - public Void visit(FieldDeclaration decl, AElement elem) { - for (VariableDeclarator v : decl.getVariables()) { - AClass clazz = (AClass) elem; - AField field = clazz.fields.getVivify(v.getNameAsString()); - visitDecl(decl, field); - visitType(decl.getCommonType(), field.type); + + /** + * Builds simplified annotation from its declaration. Only the name is included, because + * stubfiles do not generally have access to the full definitions of annotations. + */ + private static @Nullable Annotation extractAnnotation(AnnotationExpr expr) { + String exprName = expr.toString().substring(1); // leave off leading '@' + + // Eliminate jdk.Profile+Annotation, a synthetic annotation that + // the JDK adds, apparently for profiling. + if (exprName.contains("+")) { + return null; + } + @SuppressWarnings("signature") // special case for annotations containing "+" + AnnotationDef def = + new AnnotationDef(exprName, "ToIndexFileConverter.extractAnnotation(" + expr + ")"); + def.setFieldTypes(Collections.emptyMap()); + return new Annotation(def, Collections.emptyMap()); } - return null; - } - - @Override - public Void visit(InitializerDeclaration decl, AElement elem) { - BlockStmt block = decl.getBody(); - AClass clazz = (AClass) elem; - block.accept(this, clazz.methods.getVivify(decl.isStatic() ? "" : "")); - return null; - } - - @Override - public Void visit(MethodDeclaration decl, AElement elem) { - Type type = decl.getType(); - List params = decl.getParameters(); - List typeParams = decl.getTypeParameters(); - Optional rcvrParam = decl.getReceiverParameter(); - BlockStmt body = decl.getBody().orElse(null); - StringBuilder sb = new StringBuilder(decl.getNameAsString()).append('('); - AClass clazz = (AClass) elem; - AMethod method; - if (params != null) { - for (Parameter param : params) { - Type ptype = param.getType(); - sb.append(getJVML(ptype)); - } + + @Override + public Void visit(AnnotationDeclaration decl, AElement elem) { + return null; } - sb.append(')').append(getJVML(type)); - method = clazz.methods.getVivify(sb.toString()); - visitDecl(decl, method); - visitType(type, method.returnType); - if (params != null) { - for (int i = 0; i < params.size(); i++) { - Parameter param = params.get(i); - AField field = method.parameters.getVivify(i); - visitType(param.getType(), field.type); - } + + @Override + public Void visit(BlockStmt stmt, AElement elem) { + return null; + // super.visit(stmt, elem); } - if (rcvrParam.isPresent()) { - for (AnnotationExpr expr : rcvrParam.get().getAnnotations()) { - Annotation anno = extractAnnotation(expr); - method.receiver.type.tlAnnotationsHere.add(anno); - } + + @Override + public Void visit(ClassOrInterfaceDeclaration decl, AElement elem) { + visitDecl(decl, (ADeclaration) elem); + return super.visit(decl, elem); } - if (typeParams != null) { - for (int i = 0; i < typeParams.size(); i++) { - TypeParameter typeParam = typeParams.get(i); - List bounds = typeParam.getTypeBound(); - if (bounds != null) { - for (int j = 0; j < bounds.size(); j++) { - ClassOrInterfaceType bound = bounds.get(j); - BoundLocation loc = new BoundLocation(i, j); - bound.accept(this, method.bounds.getVivify(loc)); - } + + @Override + public Void visit(ConstructorDeclaration decl, AElement elem) { + List params = decl.getParameters(); + List rcvrAnnos = decl.getAnnotations(); + BlockStmt body = decl.getBody(); + StringBuilder sb = new StringBuilder("("); + AClass clazz = (AClass) elem; + AMethod method; + + // Some of the methods in the generated parser use null to represent an empty list. + if (params != null) { + for (Parameter param : params) { + Type ptype = param.getType(); + sb.append(getJVML(ptype)); + } } - } - } - return body == null ? null : body.accept(this, method); - } - - @Override - public Void visit(ObjectCreationExpr expr, AElement elem) { - ClassOrInterfaceType type = expr.getType(); - AClass clazz = scene.classes.getVivify(type.getNameAsString()); - Expression scope = expr.getScope().orElse(null); - List typeArgs = expr.getTypeArguments().orElse(null); - List args = expr.getArguments(); - NodeList> bodyDecls = expr.getAnonymousClassBody().orElse(null); - if (scope != null) { - scope.accept(this, elem); - } - if (args != null) { - for (Expression arg : args) { - arg.accept(this, elem); - } + sb.append(")V"); + method = clazz.methods.getVivify(sb.toString()); + visitDecl(decl, method); + if (params != null) { + for (int i = 0; i < params.size(); i++) { + Parameter param = params.get(i); + AField field = method.parameters.getVivify(i); + visitType(param.getType(), field.type); + } + } + if (rcvrAnnos != null) { + for (AnnotationExpr expr : rcvrAnnos) { + Annotation anno = extractAnnotation(expr); + method.receiver.tlAnnotationsHere.add(anno); + } + } + return body == null ? null : body.accept(this, method); + // return super.visit(decl, elem); } - if (typeArgs != null) { - for (Type typeArg : typeArgs) { - typeArg.accept(this, elem); - } + + @Override + public Void visit(EnumConstantDeclaration decl, AElement elem) { + AField field = ((AClass) elem).fields.getVivify(decl.getNameAsString()); + visitDecl(decl, field); + return super.visit(decl, field); } - type.accept(this, clazz); - if (bodyDecls != null) { - for (BodyDeclaration decl : bodyDecls) { - decl.accept(this, clazz); - } + + @Override + public Void visit(EnumDeclaration decl, AElement elem) { + visitDecl(decl, (ADeclaration) elem); + return super.visit(decl, elem); } - return null; - } - - @Override - public Void visit(VariableDeclarationExpr expr, AElement elem) { - List annos = expr.getAnnotations(); - AMethod method = (AMethod) elem; - List varDecls = expr.getVariables(); - for (int i = 0; i < varDecls.size(); i++) { - VariableDeclarator decl = varDecls.get(i); - LocalLocation loc = new LocalLocation(i, decl.getNameAsString()); - AField field = method.body.locals.getVivify(loc); - visitType(expr.getCommonType(), field.type); - if (annos != null) { - for (AnnotationExpr annoExpr : annos) { - Annotation anno = extractAnnotation(annoExpr); - field.tlAnnotationsHere.add(anno); + + @Override + public Void visit(FieldDeclaration decl, AElement elem) { + for (VariableDeclarator v : decl.getVariables()) { + AClass clazz = (AClass) elem; + AField field = clazz.fields.getVivify(v.getNameAsString()); + visitDecl(decl, field); + visitType(decl.getCommonType(), field.type); } - } + return null; } - return null; - } - - /** - * Copies information from an AST declaration node to an {@link ADeclaration}. Called by visitors - * for BodyDeclaration subclasses. - */ - private Void visitDecl(BodyDeclaration decl, ADeclaration elem) { - NodeList annoExprs = decl.getAnnotations(); - if (annoExprs != null) { - for (AnnotationExpr annoExpr : annoExprs) { - Annotation anno = extractAnnotation(annoExpr); - elem.tlAnnotationsHere.add(anno); - } + + @Override + public Void visit(InitializerDeclaration decl, AElement elem) { + BlockStmt block = decl.getBody(); + AClass clazz = (AClass) elem; + block.accept(this, clazz.methods.getVivify(decl.isStatic() ? "" : "")); + return null; } - return null; - } - - /** Copies information from an AST type node to an {@link ATypeElement}. */ - private Void visitType(Type type, ATypeElement elem) { - List exprs = type.getAnnotations(); - if (exprs != null) { - for (AnnotationExpr expr : exprs) { - Annotation anno = extractAnnotation(expr); - if (anno != null) { - elem.tlAnnotationsHere.add(anno); + + @Override + public Void visit(MethodDeclaration decl, AElement elem) { + Type type = decl.getType(); + List params = decl.getParameters(); + List typeParams = decl.getTypeParameters(); + Optional rcvrParam = decl.getReceiverParameter(); + BlockStmt body = decl.getBody().orElse(null); + StringBuilder sb = new StringBuilder(decl.getNameAsString()).append('('); + AClass clazz = (AClass) elem; + AMethod method; + if (params != null) { + for (Parameter param : params) { + Type ptype = param.getType(); + sb.append(getJVML(ptype)); + } } - } - } - visitInnerTypes(type, elem); - return null; - } - - /** - * Copies information from an AST type node's inner type nodes to an {@link ATypeElement}. - * - * @param type the AST Type node to inspect - * @param elem destination type element - */ - private static Void visitInnerTypes(Type type, ATypeElement elem) { - return type.accept( - new GenericVisitorAdapter>() { - @Override - public Void visit(ClassOrInterfaceType type, List loc) { - if (type.getTypeArguments().isPresent()) { - List typeArgs = type.getTypeArguments().get(); - for (int i = 0; i < typeArgs.size(); i++) { - Type inner = typeArgs.get(i); - List ext = extendedTypePath(loc, 3, i); - visitInnerType(inner, ext); - } + sb.append(')').append(getJVML(type)); + method = clazz.methods.getVivify(sb.toString()); + visitDecl(decl, method); + visitType(type, method.returnType); + if (params != null) { + for (int i = 0; i < params.size(); i++) { + Parameter param = params.get(i); + AField field = method.parameters.getVivify(i); + visitType(param.getType(), field.type); } - return null; - } - - @Override - public Void visit(ArrayType type, List loc) { - List ext = loc; - int n = type.getArrayLevel(); - Type currentType = type; - for (int i = 0; i < n; i++) { - ext = extendedTypePath(ext, 1, 0); - for (AnnotationExpr expr : currentType.getAnnotations()) { - ATypeElement typeElem = elem.innerTypes.getVivify(ext); + } + if (rcvrParam.isPresent()) { + for (AnnotationExpr expr : rcvrParam.get().getAnnotations()) { Annotation anno = extractAnnotation(expr); - typeElem.tlAnnotationsHere.add(anno); - } - currentType = - ((com.github.javaparser.ast.type.ArrayType) currentType).getComponentType(); + method.receiver.type.tlAnnotationsHere.add(anno); } - return null; - } - - @Override - public Void visit(WildcardType type, List loc) { - ReferenceType lower = type.getExtendedType().orElse(null); - ReferenceType upper = type.getSuperType().orElse(null); - if (lower != null) { - List ext = extendedTypePath(loc, 2, 0); - visitInnerType(lower, ext); + } + if (typeParams != null) { + for (int i = 0; i < typeParams.size(); i++) { + TypeParameter typeParam = typeParams.get(i); + List bounds = typeParam.getTypeBound(); + if (bounds != null) { + for (int j = 0; j < bounds.size(); j++) { + ClassOrInterfaceType bound = bounds.get(j); + BoundLocation loc = new BoundLocation(i, j); + bound.accept(this, method.bounds.getVivify(loc)); + } + } } - if (upper != null) { - List ext = extendedTypePath(loc, 2, 0); - visitInnerType(upper, ext); + } + return body == null ? null : body.accept(this, method); + } + + @Override + public Void visit(ObjectCreationExpr expr, AElement elem) { + ClassOrInterfaceType type = expr.getType(); + AClass clazz = scene.classes.getVivify(type.getNameAsString()); + Expression scope = expr.getScope().orElse(null); + List typeArgs = expr.getTypeArguments().orElse(null); + List args = expr.getArguments(); + NodeList> bodyDecls = expr.getAnonymousClassBody().orElse(null); + if (scope != null) { + scope.accept(this, elem); + } + if (args != null) { + for (Expression arg : args) { + arg.accept(this, elem); } - return null; - } - - /** Copies information from an AST inner type node to an {@link ATypeElement}. */ - private void visitInnerType(Type type, List loc) { - ATypeElement typeElem = elem.innerTypes.getVivify(loc); - for (AnnotationExpr expr : type.getAnnotations()) { - Annotation anno = extractAnnotation(expr); - typeElem.tlAnnotationsHere.add(anno); - type.accept(this, loc); + } + if (typeArgs != null) { + for (Type typeArg : typeArgs) { + typeArg.accept(this, elem); } - } - - /** - * Extends type path by one element. - * - * @see TypePathEntry(int, int) - */ - private List extendedTypePath(List loc, int tag, int arg) { - List path = new ArrayList<>(loc.size() + 1); - path.addAll(loc); - path.add(TypePathEntry.create(tag, arg)); - return path; - } - }, - Collections.emptyList()); - } - - /** - * Computes a type's "binary name". - * - * @param type the type - * @return the type's binary name - */ - private String getJVML(Type type) { - return type.accept( - new GenericVisitorAdapter() { - @Override - public String visit(ClassOrInterfaceType type, Void v) { - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getNameAsString - @FullyQualifiedName String typeName = type.getNameAsString(); - @SuppressWarnings("signature" // TODO: bug in ToIndexFileConverter: - // resolve requires a @BinaryName, but this passes a @FullyQualifiedName. - // They differ for inner classes. - ) - String name = resolve(typeName); - if (name == null) { - // could be defined in the same stub file - return "L" + typeName + ";"; + } + type.accept(this, clazz); + if (bodyDecls != null) { + for (BodyDeclaration decl : bodyDecls) { + decl.accept(this, clazz); } - return "L" + String.join("/", name.split("\\.")) + ";"; - } - - @Override - public String visit(PrimitiveType type, Void v) { - switch (type.getType()) { - case BOOLEAN: - return "Z"; - case BYTE: - return "B"; - case CHAR: - return "C"; - case DOUBLE: - return "D"; - case FLOAT: - return "F"; - case INT: - return "I"; - case LONG: - return "J"; - case SHORT: - return "S"; - default: - throw new BugInCF("unknown primitive type " + type); + } + return null; + } + + @Override + public Void visit(VariableDeclarationExpr expr, AElement elem) { + List annos = expr.getAnnotations(); + AMethod method = (AMethod) elem; + List varDecls = expr.getVariables(); + for (int i = 0; i < varDecls.size(); i++) { + VariableDeclarator decl = varDecls.get(i); + LocalLocation loc = new LocalLocation(i, decl.getNameAsString()); + AField field = method.body.locals.getVivify(loc); + visitType(expr.getCommonType(), field.type); + if (annos != null) { + for (AnnotationExpr annoExpr : annos) { + Annotation anno = extractAnnotation(annoExpr); + field.tlAnnotationsHere.add(anno); + } } - } - - @Override - public String visit(ArrayType type, Void v) { - String typeName = type.getElementType().accept(this, null); - StringBuilder sb = new StringBuilder(); - int n = type.getArrayLevel(); - for (int i = 0; i < n; i++) { - sb.append("["); + } + return null; + } + + /** + * Copies information from an AST declaration node to an {@link ADeclaration}. Called by + * visitors for BodyDeclaration subclasses. + */ + private Void visitDecl(BodyDeclaration decl, ADeclaration elem) { + NodeList annoExprs = decl.getAnnotations(); + if (annoExprs != null) { + for (AnnotationExpr annoExpr : annoExprs) { + Annotation anno = extractAnnotation(annoExpr); + elem.tlAnnotationsHere.add(anno); } - sb.append(typeName); - return sb.toString(); - } - - @Override - public String visit(VoidType type, Void v) { - return "V"; - } - - @Override - public String visit(WildcardType type, Void v) { - return type.getSuperType().get().accept(this, null); - } - }, - null); - } - - /** - * Finds the fully qualified name of the class with the given name. - * - * @param className possibly unqualified name of class - * @return fully qualified name of class that {@code className} identifies in the current context, - * or null if resolution fails - */ - private @Nullable @BinaryName String resolve(@BinaryName String className) { - - if (pkgName != null) { - String qualifiedName = Signatures.addPackage(pkgName, className); - if (loadClass(qualifiedName) != null) { - return qualifiedName; - } + } + return null; } - { - // Every Java program implicitly does "import java.lang.*", - // so see whether this class is in that package. - String qualifiedName = Signatures.addPackage("java.lang", className); - if (loadClass(qualifiedName) != null) { - return qualifiedName; - } + /** Copies information from an AST type node to an {@link ATypeElement}. */ + private Void visitType(Type type, ATypeElement elem) { + List exprs = type.getAnnotations(); + if (exprs != null) { + for (AnnotationExpr expr : exprs) { + Annotation anno = extractAnnotation(expr); + if (anno != null) { + elem.tlAnnotationsHere.add(anno); + } + } + } + visitInnerTypes(type, elem); + return null; } - for (String declName : imports) { - String qualifiedName = mergeImport(declName, className); - if (loadClass(qualifiedName) != null) { - return qualifiedName; - } + /** + * Copies information from an AST type node's inner type nodes to an {@link ATypeElement}. + * + * @param type the AST Type node to inspect + * @param elem destination type element + */ + private static Void visitInnerTypes(Type type, ATypeElement elem) { + return type.accept( + new GenericVisitorAdapter>() { + @Override + public Void visit(ClassOrInterfaceType type, List loc) { + if (type.getTypeArguments().isPresent()) { + List typeArgs = type.getTypeArguments().get(); + for (int i = 0; i < typeArgs.size(); i++) { + Type inner = typeArgs.get(i); + List ext = extendedTypePath(loc, 3, i); + visitInnerType(inner, ext); + } + } + return null; + } + + @Override + public Void visit(ArrayType type, List loc) { + List ext = loc; + int n = type.getArrayLevel(); + Type currentType = type; + for (int i = 0; i < n; i++) { + ext = extendedTypePath(ext, 1, 0); + for (AnnotationExpr expr : currentType.getAnnotations()) { + ATypeElement typeElem = elem.innerTypes.getVivify(ext); + Annotation anno = extractAnnotation(expr); + typeElem.tlAnnotationsHere.add(anno); + } + currentType = + ((com.github.javaparser.ast.type.ArrayType) currentType) + .getComponentType(); + } + return null; + } + + @Override + public Void visit(WildcardType type, List loc) { + ReferenceType lower = type.getExtendedType().orElse(null); + ReferenceType upper = type.getSuperType().orElse(null); + if (lower != null) { + List ext = extendedTypePath(loc, 2, 0); + visitInnerType(lower, ext); + } + if (upper != null) { + List ext = extendedTypePath(loc, 2, 0); + visitInnerType(upper, ext); + } + return null; + } + + /** + * Copies information from an AST inner type node to an {@link ATypeElement}. + */ + private void visitInnerType(Type type, List loc) { + ATypeElement typeElem = elem.innerTypes.getVivify(loc); + for (AnnotationExpr expr : type.getAnnotations()) { + Annotation anno = extractAnnotation(expr); + typeElem.tlAnnotationsHere.add(anno); + type.accept(this, loc); + } + } + + /** + * Extends type path by one element. + * + * @see TypePathEntry(int, int) + */ + private List extendedTypePath( + List loc, int tag, int arg) { + List path = new ArrayList<>(loc.size() + 1); + path.addAll(loc); + path.add(TypePathEntry.create(tag, arg)); + return path; + } + }, + Collections.emptyList()); } - if (loadClass(className) != null) { - return className; + /** + * Computes a type's "binary name". + * + * @param type the type + * @return the type's binary name + */ + private String getJVML(Type type) { + return type.accept( + new GenericVisitorAdapter() { + @Override + public String visit(ClassOrInterfaceType type, Void v) { + @SuppressWarnings( + "signature") // https://tinyurl.com/cfissue/658 for getNameAsString + @FullyQualifiedName String typeName = type.getNameAsString(); + @SuppressWarnings("signature" // TODO: bug in ToIndexFileConverter: + // resolve requires a @BinaryName, but this passes a @FullyQualifiedName. + // They differ for inner classes. + ) + String name = resolve(typeName); + if (name == null) { + // could be defined in the same stub file + return "L" + typeName + ";"; + } + return "L" + String.join("/", name.split("\\.")) + ";"; + } + + @Override + public String visit(PrimitiveType type, Void v) { + switch (type.getType()) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case CHAR: + return "C"; + case DOUBLE: + return "D"; + case FLOAT: + return "F"; + case INT: + return "I"; + case LONG: + return "J"; + case SHORT: + return "S"; + default: + throw new BugInCF("unknown primitive type " + type); + } + } + + @Override + public String visit(ArrayType type, Void v) { + String typeName = type.getElementType().accept(this, null); + StringBuilder sb = new StringBuilder(); + int n = type.getArrayLevel(); + for (int i = 0; i < n; i++) { + sb.append("["); + } + sb.append(typeName); + return sb.toString(); + } + + @Override + public String visit(VoidType type, Void v) { + return "V"; + } + + @Override + public String visit(WildcardType type, Void v) { + return type.getSuperType().get().accept(this, null); + } + }, + null); } - return null; - } - - /** - * Combines an import with a partial binary name, yielding a binary name. - * - * @param importName package name or (for an inner class) the outer class name - * @param className the class name - * @return fully qualified class name if resolution succeeds, null otherwise - */ - @SuppressWarnings("signature") // string manipulation of signature strings - private static @Nullable @BinaryName String mergeImport( - String importName, @BinaryName String className) { - if (importName.isEmpty() || importName.equals(className)) { - return className; + /** + * Finds the fully qualified name of the class with the given name. + * + * @param className possibly unqualified name of class + * @return fully qualified name of class that {@code className} identifies in the current + * context, or null if resolution fails + */ + private @Nullable @BinaryName String resolve(@BinaryName String className) { + + if (pkgName != null) { + String qualifiedName = Signatures.addPackage(pkgName, className); + if (loadClass(qualifiedName) != null) { + return qualifiedName; + } + } + + { + // Every Java program implicitly does "import java.lang.*", + // so see whether this class is in that package. + String qualifiedName = Signatures.addPackage("java.lang", className); + if (loadClass(qualifiedName) != null) { + return qualifiedName; + } + } + + for (String declName : imports) { + String qualifiedName = mergeImport(declName, className); + if (loadClass(qualifiedName) != null) { + return qualifiedName; + } + } + + if (loadClass(className) != null) { + return className; + } + + return null; } - String[] importSplit = importName.split("\\."); - String[] classSplit = className.split("\\."); - String importEnd = importSplit[importSplit.length - 1]; - if ("*".equals(importEnd)) { - return importName.substring(0, importName.length() - 1) + className; - } else { - // find overlap such as in - // import a.b.C.D; - // C.D myvar; - int i = importSplit.length; - int n = i - classSplit.length; - while (--i >= n) { - if (!classSplit[i - n].equals(importSplit[i])) { - return null; + + /** + * Combines an import with a partial binary name, yielding a binary name. + * + * @param importName package name or (for an inner class) the outer class name + * @param className the class name + * @return fully qualified class name if resolution succeeds, null otherwise + */ + @SuppressWarnings("signature") // string manipulation of signature strings + private static @Nullable @BinaryName String mergeImport( + String importName, @BinaryName String className) { + if (importName.isEmpty() || importName.equals(className)) { + return className; + } + String[] importSplit = importName.split("\\."); + String[] classSplit = className.split("\\."); + String importEnd = importSplit[importSplit.length - 1]; + if ("*".equals(importEnd)) { + return importName.substring(0, importName.length() - 1) + className; + } else { + // find overlap such as in + // import a.b.C.D; + // C.D myvar; + int i = importSplit.length; + int n = i - classSplit.length; + while (--i >= n) { + if (!classSplit[i - n].equals(importSplit[i])) { + return null; + } + } + return importName; } - } - return importName; } - } - - /** - * Finds the {@link Class} corresponding to a name. - * - * @param className a class name - * @return the {@link Class} object corresponding to {@code className}, or null if none found - */ - private static @Nullable Class loadClass(@ClassGetName String className) { - assert className != null; - try { - return Class.forName(className, false, null); - } catch (ClassNotFoundException e) { - return null; + + /** + * Finds the {@link Class} corresponding to a name. + * + * @param className a class name + * @return the {@link Class} object corresponding to {@code className}, or null if none found + */ + private static @Nullable Class loadClass(@ClassGetName String className) { + assert className != null; + try { + return Class.forName(className, false, null); + } catch (ClassNotFoundException e) { + return null; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index ca499d46f4a..a2121ad4bf0 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -1,16 +1,5 @@ package org.checkerframework.framework.type; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -23,6 +12,19 @@ import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.IPair; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; + /** * Abstract utility class for performing viewpoint adaptation. * @@ -35,468 +37,491 @@ */ public abstract class AbstractViewpointAdapter implements ViewpointAdapter { - /** - * True iff we are adapting type variable bounds. This prevents calling combineTypeWithType on - * type variable if it is an upper bound of another type variable. We only viewpoint adapt a type - * variable that is not an upper-bound. - */ - private boolean isTypeVarExtends = false; - - /** The annotated type factory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Construct an abstract viewpoint adapter with the given type factory. - * - * @param atypeFactory the type factory to use - */ - protected AbstractViewpointAdapter(final AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } - - @Override - public void viewpointAdaptMember( - AnnotatedTypeMirror receiverType, Element memberElement, AnnotatedTypeMirror memberType) { - if (!shouldAdaptMember(memberType, memberElement)) { - return; + /** + * True iff we are adapting type variable bounds. This prevents calling combineTypeWithType on + * type variable if it is an upper bound of another type variable. We only viewpoint adapt a + * type variable that is not an upper-bound. + */ + private boolean isTypeVarExtends = false; + + /** The annotated type factory. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** + * Construct an abstract viewpoint adapter with the given type factory. + * + * @param atypeFactory the type factory to use + */ + protected AbstractViewpointAdapter(final AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; } - AnnotatedTypeMirror decltype = atypeFactory.getAnnotatedType(memberElement); - AnnotatedTypeMirror combinedType = combineTypeWithType(receiverType, decltype); - memberType.replaceAnnotations(combinedType.getAnnotations()); - if (memberType.getKind() == TypeKind.DECLARED && combinedType.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adtType = (AnnotatedDeclaredType) memberType; - AnnotatedDeclaredType adtCombinedType = (AnnotatedDeclaredType) combinedType; - adtType.setTypeArguments(adtCombinedType.getTypeArguments()); - } else if (memberType.getKind() == TypeKind.ARRAY && combinedType.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aatType = (AnnotatedArrayType) memberType; - AnnotatedArrayType aatCombinedType = (AnnotatedArrayType) combinedType; - aatType.setComponentType(aatCombinedType.getComponentType()); - } - } - - /** - * Determines whether a particular member should be viewpoint adapted or not. The default - * implementation adapts all members except for local variables and method formal parameters. - * - * @param type type of the member, used to decide whether a member should be viewpoint adapted or - * not. A subclass of {@link ViewpointAdapter} may disable viewpoint adaptation for elements - * based on their types. - * @param element element of the member - * @return true if the member needs viewpoint adaptation - */ - protected boolean shouldAdaptMember(AnnotatedTypeMirror type, Element element) { - if (element.getKind() == ElementKind.LOCAL_VARIABLE - || element.getKind() == ElementKind.PARAMETER) { - return false; - } - return true; - } - - @Override - public void viewpointAdaptConstructor( - AnnotatedTypeMirror receiverType, - ExecutableElement constructorElt, - AnnotatedExecutableType constructorType) { - // constructorType's typevar are not substituted when calling viewpointAdaptConstructor - AnnotatedExecutableType unsubstitutedConstructorType = constructorType.deepCopy(); - - // For constructors, we adapt parameter types, return type and type parameters - List parameterTypes = unsubstitutedConstructorType.getParameterTypes(); - List typeVariables = unsubstitutedConstructorType.getTypeVariables(); - AnnotatedTypeMirror constructorReturn = unsubstitutedConstructorType.getReturnType(); - - IdentityHashMap mappings = new IdentityHashMap<>(); - for (AnnotatedTypeMirror parameterType : parameterTypes) { - AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); - mappings.put(parameterType, p); - } - for (AnnotatedTypeMirror typeVariable : typeVariables) { - AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); - mappings.put(typeVariable, tv); + @Override + public void viewpointAdaptMember( + AnnotatedTypeMirror receiverType, + Element memberElement, + AnnotatedTypeMirror memberType) { + if (!shouldAdaptMember(memberType, memberElement)) { + return; + } + + AnnotatedTypeMirror decltype = atypeFactory.getAnnotatedType(memberElement); + AnnotatedTypeMirror combinedType = combineTypeWithType(receiverType, decltype); + memberType.replaceAnnotations(combinedType.getAnnotations()); + if (memberType.getKind() == TypeKind.DECLARED + && combinedType.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adtType = (AnnotatedDeclaredType) memberType; + AnnotatedDeclaredType adtCombinedType = (AnnotatedDeclaredType) combinedType; + adtType.setTypeArguments(adtCombinedType.getTypeArguments()); + } else if (memberType.getKind() == TypeKind.ARRAY + && combinedType.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aatType = (AnnotatedArrayType) memberType; + AnnotatedArrayType aatCombinedType = (AnnotatedArrayType) combinedType; + aatType.setComponentType(aatCombinedType.getComponentType()); + } } - AnnotatedTypeMirror cr = combineTypeWithType(receiverType, constructorReturn); - mappings.put(constructorReturn, cr); - - unsubstitutedConstructorType = - (AnnotatedExecutableType) - AnnotatedTypeCopierWithReplacement.replace(unsubstitutedConstructorType, mappings); - - constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes()); - constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables()); - constructorType.setReturnType(unsubstitutedConstructorType.getReturnType()); - } - - @Override - public void viewpointAdaptMethod( - AnnotatedTypeMirror receiverType, - ExecutableElement methodElt, - AnnotatedExecutableType methodType) { - if (!shouldAdaptMethod(methodElt)) { - return; + + /** + * Determines whether a particular member should be viewpoint adapted or not. The default + * implementation adapts all members except for local variables and method formal parameters. + * + * @param type type of the member, used to decide whether a member should be viewpoint adapted + * or not. A subclass of {@link ViewpointAdapter} may disable viewpoint adaptation for + * elements based on their types. + * @param element element of the member + * @return true if the member needs viewpoint adaptation + */ + protected boolean shouldAdaptMember(AnnotatedTypeMirror type, Element element) { + if (element.getKind() == ElementKind.LOCAL_VARIABLE + || element.getKind() == ElementKind.PARAMETER) { + return false; + } + return true; } - // methodType's typevar are not substituted when calling viewpointAdaptMethod - AnnotatedExecutableType unsubstitutedMethodType = methodType.deepCopy(); + @Override + public void viewpointAdaptConstructor( + AnnotatedTypeMirror receiverType, + ExecutableElement constructorElt, + AnnotatedExecutableType constructorType) { + // constructorType's typevar are not substituted when calling viewpointAdaptConstructor + AnnotatedExecutableType unsubstitutedConstructorType = constructorType.deepCopy(); - // For methods, we additionally adapt method receiver compared to constructors - List parameterTypes = unsubstitutedMethodType.getParameterTypes(); - List typeVariables = unsubstitutedMethodType.getTypeVariables(); - AnnotatedTypeMirror returnType = unsubstitutedMethodType.getReturnType(); - AnnotatedTypeMirror methodReceiver = unsubstitutedMethodType.getReceiverType(); + // For constructors, we adapt parameter types, return type and type parameters + List parameterTypes = unsubstitutedConstructorType.getParameterTypes(); + List typeVariables = unsubstitutedConstructorType.getTypeVariables(); + AnnotatedTypeMirror constructorReturn = unsubstitutedConstructorType.getReturnType(); + + IdentityHashMap mappings = + new IdentityHashMap<>(); + for (AnnotatedTypeMirror parameterType : parameterTypes) { + AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); + mappings.put(parameterType, p); + } + for (AnnotatedTypeMirror typeVariable : typeVariables) { + AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); + mappings.put(typeVariable, tv); + } + AnnotatedTypeMirror cr = combineTypeWithType(receiverType, constructorReturn); + mappings.put(constructorReturn, cr); - IdentityHashMap mappings = new IdentityHashMap<>(); + unsubstitutedConstructorType = + (AnnotatedExecutableType) + AnnotatedTypeCopierWithReplacement.replace( + unsubstitutedConstructorType, mappings); - for (AnnotatedTypeMirror parameterType : parameterTypes) { - AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); - mappings.put(parameterType, p); + constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes()); + constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables()); + constructorType.setReturnType(unsubstitutedConstructorType.getReturnType()); } - for (AnnotatedTypeVariable typeVariable : typeVariables) { - AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); - mappings.put(typeVariable, tv); - } + @Override + public void viewpointAdaptMethod( + AnnotatedTypeMirror receiverType, + ExecutableElement methodElt, + AnnotatedExecutableType methodType) { + if (!shouldAdaptMethod(methodElt)) { + return; + } - if (returnType.getKind() != TypeKind.VOID) { - AnnotatedTypeMirror r = combineTypeWithType(receiverType, returnType); - mappings.put(returnType, r); - } + // methodType's typevar are not substituted when calling viewpointAdaptMethod + AnnotatedExecutableType unsubstitutedMethodType = methodType.deepCopy(); - if (methodReceiver != null) { - AnnotatedTypeMirror mr = combineTypeWithType(receiverType, methodReceiver); - mappings.put(methodReceiver, mr); - } + // For methods, we additionally adapt method receiver compared to constructors + List parameterTypes = unsubstitutedMethodType.getParameterTypes(); + List typeVariables = unsubstitutedMethodType.getTypeVariables(); + AnnotatedTypeMirror returnType = unsubstitutedMethodType.getReturnType(); + AnnotatedTypeMirror methodReceiver = unsubstitutedMethodType.getReceiverType(); - unsubstitutedMethodType = - (AnnotatedExecutableType) - AnnotatedTypeCopierWithReplacement.replace(unsubstitutedMethodType, mappings); - - // Because we can't viewpoint adapt asMemberOf result, we adapt the declared method first, - // and sets the corresponding parts to asMemberOf result - methodType.setReturnType(unsubstitutedMethodType.getReturnType()); - methodType.setReceiverType(unsubstitutedMethodType.getReceiverType()); - methodType.setParameterTypes(unsubstitutedMethodType.getParameterTypes()); - methodType.setTypeVariables(unsubstitutedMethodType.getTypeVariables()); - } - - /** - * Determine if an invocation of the given method needs to be adapted. - * - * @param element the executable element for a method - * @return true if an invocation of the executable element needs to be adapted - */ - protected boolean shouldAdaptMethod(ExecutableElement element) { - return !ElementUtils.isStatic(element); - } - - @Override - public void viewpointAdaptTypeParameterBounds( - AnnotatedTypeMirror receiverType, List typeParameterBounds) { - List adaptedTypeParameterBounds = - new ArrayList<>(typeParameterBounds.size()); - for (AnnotatedTypeParameterBounds typeParameterBound : typeParameterBounds) { - AnnotatedTypeMirror adaptedUpper = - combineTypeWithType(receiverType, typeParameterBound.getUpperBound()); - AnnotatedTypeMirror adaptedLower = - combineTypeWithType(receiverType, typeParameterBound.getLowerBound()); - adaptedTypeParameterBounds.add(new AnnotatedTypeParameterBounds(adaptedUpper, adaptedLower)); - } + IdentityHashMap mappings = + new IdentityHashMap<>(); - typeParameterBounds.clear(); - typeParameterBounds.addAll(adaptedTypeParameterBounds); - } - - /** - * Viewpoint adapt declared type to receiver type, and return the result atm - * - * @param receiver receiver type - * @param declared declared type - * @return {@link AnnotatedTypeMirror} after viewpoint adaptation - */ - protected AnnotatedTypeMirror combineTypeWithType( - AnnotatedTypeMirror receiver, AnnotatedTypeMirror declared) { - assert receiver != null && declared != null; - - AnnotatedTypeMirror result = declared; - - if (receiver.getKind() == TypeKind.TYPEVAR) { - receiver = ((AnnotatedTypeVariable) receiver).getUpperBound(); - } - AnnotationMirror receiverAnnotation = extractAnnotationMirror(receiver); - if (receiverAnnotation != null) { - result = combineAnnotationWithType(receiverAnnotation, declared); - result = substituteTVars(receiver, result); + for (AnnotatedTypeMirror parameterType : parameterTypes) { + AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); + mappings.put(parameterType, p); + } + + for (AnnotatedTypeVariable typeVariable : typeVariables) { + AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); + mappings.put(typeVariable, tv); + } + + if (returnType.getKind() != TypeKind.VOID) { + AnnotatedTypeMirror r = combineTypeWithType(receiverType, returnType); + mappings.put(returnType, r); + } + + if (methodReceiver != null) { + AnnotatedTypeMirror mr = combineTypeWithType(receiverType, methodReceiver); + mappings.put(methodReceiver, mr); + } + + unsubstitutedMethodType = + (AnnotatedExecutableType) + AnnotatedTypeCopierWithReplacement.replace( + unsubstitutedMethodType, mappings); + + // Because we can't viewpoint adapt asMemberOf result, we adapt the declared method first, + // and sets the corresponding parts to asMemberOf result + methodType.setReturnType(unsubstitutedMethodType.getReturnType()); + methodType.setReceiverType(unsubstitutedMethodType.getReceiverType()); + methodType.setParameterTypes(unsubstitutedMethodType.getParameterTypes()); + methodType.setTypeVariables(unsubstitutedMethodType.getTypeVariables()); } - return result; - } - - /** - * Extract the relevant qualifier from an {@link AnnotatedTypeMirror}. - * - * @param atm AnnotatedTypeMirror from which qualifier is extracted - * @return extracted qualifier - */ - protected abstract AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm); - - /** - * Combine receiver qualifiers with declared types. Qualifiers are extracted from declared types - * to further perform viewpoint adaptation only between two qualifiers. - * - * @param receiverAnnotation receiver qualifier - * @param declared declared type - * @return {@link AnnotatedTypeMirror} after viewpoint adaptation - */ - protected AnnotatedTypeMirror combineAnnotationWithType( - AnnotationMirror receiverAnnotation, AnnotatedTypeMirror declared) { - if (declared.getKind().isPrimitive()) { - AnnotatedPrimitiveType apt = (AnnotatedPrimitiveType) declared.shallowCopy(); - - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(apt)); - apt.replaceAnnotation(resultAnnotation); - return apt; - } else if (declared.getKind() == TypeKind.TYPEVAR) { - if (!isTypeVarExtends) { - isTypeVarExtends = true; - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) declared.shallowCopy(); - IdentityHashMap mappings = - new IdentityHashMap<>(); + /** + * Determine if an invocation of the given method needs to be adapted. + * + * @param element the executable element for a method + * @return true if an invocation of the executable element needs to be adapted + */ + protected boolean shouldAdaptMethod(ExecutableElement element) { + return !ElementUtils.isStatic(element); + } - // For type variables, we recursively adapt upper and lower bounds - AnnotatedTypeMirror resUpper = - combineAnnotationWithType(receiverAnnotation, atv.getUpperBound()); - mappings.put(atv.getUpperBound(), resUpper); + @Override + public void viewpointAdaptTypeParameterBounds( + AnnotatedTypeMirror receiverType, + List typeParameterBounds) { + List adaptedTypeParameterBounds = + new ArrayList<>(typeParameterBounds.size()); + for (AnnotatedTypeParameterBounds typeParameterBound : typeParameterBounds) { + AnnotatedTypeMirror adaptedUpper = + combineTypeWithType(receiverType, typeParameterBound.getUpperBound()); + AnnotatedTypeMirror adaptedLower = + combineTypeWithType(receiverType, typeParameterBound.getLowerBound()); + adaptedTypeParameterBounds.add( + new AnnotatedTypeParameterBounds(adaptedUpper, adaptedLower)); + } - AnnotatedTypeMirror resLower = - combineAnnotationWithType(receiverAnnotation, atv.getLowerBound()); - mappings.put(atv.getLowerBound(), resLower); + typeParameterBounds.clear(); + typeParameterBounds.addAll(adaptedTypeParameterBounds); + } - AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(atv, mappings); + /** + * Viewpoint adapt declared type to receiver type, and return the result atm + * + * @param receiver receiver type + * @param declared declared type + * @return {@link AnnotatedTypeMirror} after viewpoint adaptation + */ + protected AnnotatedTypeMirror combineTypeWithType( + AnnotatedTypeMirror receiver, AnnotatedTypeMirror declared) { + assert receiver != null && declared != null; + + AnnotatedTypeMirror result = declared; + + if (receiver.getKind() == TypeKind.TYPEVAR) { + receiver = ((AnnotatedTypeVariable) receiver).getUpperBound(); + } + AnnotationMirror receiverAnnotation = extractAnnotationMirror(receiver); + if (receiverAnnotation != null) { + result = combineAnnotationWithType(receiverAnnotation, declared); + result = substituteTVars(receiver, result); + } - isTypeVarExtends = false; return result; - } - return declared; - } else if (declared.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adt = (AnnotatedDeclaredType) declared.shallowCopy(); - - // Mapping between declared type argument to combined type argument - IdentityHashMap mappings = new IdentityHashMap<>(); - - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(adt)); - - // Recursively combine type arguments and store to map - for (AnnotatedTypeMirror typeArgument : adt.getTypeArguments()) { - // Recursively adapt the type arguments of this adt - AnnotatedTypeMirror combinedTypeArgument = - combineAnnotationWithType(receiverAnnotation, typeArgument); - mappings.put(typeArgument, combinedTypeArgument); - } - - // Construct result type - AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); - result.replaceAnnotation(resultAnnotation); - - return result; - } else if (declared.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aat = (AnnotatedArrayType) declared.shallowCopy(); - - // Replace the main qualifier - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(aat)); - aat.replaceAnnotation(resultAnnotation); - - // Combine component type recursively and sets combined component type - AnnotatedTypeMirror compo = aat.getComponentType(); - // Recursively call itself first on the component type - AnnotatedTypeMirror combinedCompoType = combineAnnotationWithType(receiverAnnotation, compo); - aat.setComponentType(combinedCompoType); - - return aat; - } else if (declared.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType awt = (AnnotatedWildcardType) declared.shallowCopy(); - IdentityHashMap mappings = new IdentityHashMap<>(); - - // There is no main qualifier for a wildcard - - // Adapt extend - AnnotatedTypeMirror extend = awt.getExtendsBound(); - if (extend != null) { - // Recursively adapt the extends bound of this awt - AnnotatedTypeMirror combinedExtend = combineAnnotationWithType(receiverAnnotation, extend); - mappings.put(extend, combinedExtend); - } - - // Adapt super - AnnotatedTypeMirror zuper = awt.getSuperBound(); - if (zuper != null) { - // Recursively adapt the lower bound of this awt - AnnotatedTypeMirror combinedZuper = combineAnnotationWithType(receiverAnnotation, zuper); - mappings.put(zuper, combinedZuper); - } - - AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); - return result; - } else if (declared.getKind() == TypeKind.NULL) { - AnnotatedNullType ant = (AnnotatedNullType) declared.shallowCopy(true); - AnnotationMirror resultAnnotation = - combineAnnotationWithAnnotation(receiverAnnotation, extractAnnotationMirror(ant)); - ant.replaceAnnotation(resultAnnotation); - return ant; - } else { - throw new BugInCF( - "ViewpointAdapter::combineAnnotationWithType: Unknown decl: " - + declared - + " of kind: " - + declared.getKind()); - } - } - - /** - * Viewpoint adapt declared qualifier to receiver qualifier. - * - * @param receiverAnnotation receiver qualifier - * @param declaredAnnotation declared qualifier - * @return result qualifier after viewpoint adaptation - */ - @SideEffectFree - protected abstract AnnotationMirror combineAnnotationWithAnnotation( - AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation); - - /** - * If rhs contains/is a type variable use whose type arguments should be inferred from the - * receiver, i.e. lhs, this method substitutes that type argument into rhs, and returns the - * reference to rhs. This method is side effect free, because rhs will be copied and that copy - * gets modified and returned. - * - * @param lhs type from which type arguments are extracted to replace formal type parameters of - * rhs. - * @param rhs AnnotatedTypeMirror that might be a formal type parameter - * @return rhs' copy with its type parameter substituted - */ - private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTypeMirror rhs) { - if (rhs.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) rhs.shallowCopy(); - - // Base case where actual type argument is extracted - if (lhs.getKind() == TypeKind.DECLARED) { - rhs = getTypeVariableSubstitution((AnnotatedDeclaredType) lhs, atv); - } - } else if (rhs.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adt = (AnnotatedDeclaredType) rhs.shallowCopy(); - IdentityHashMap mappings = new IdentityHashMap<>(); - - for (AnnotatedTypeMirror formalTypeParameter : adt.getTypeArguments()) { - AnnotatedTypeMirror actualTypeArgument = substituteTVars(lhs, formalTypeParameter); - mappings.put(formalTypeParameter, actualTypeArgument); - // The following code does the wrong thing! - } - // We must use AnnotatedTypeReplacer to replace the formal type parameters with actual - // type arguments, but not replace with its main qualifier - rhs = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); - } else if (rhs.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType awt = (AnnotatedWildcardType) rhs.shallowCopy(); - IdentityHashMap mappings = new IdentityHashMap<>(); - - AnnotatedTypeMirror extend = awt.getExtendsBound(); - if (extend != null) { - AnnotatedTypeMirror substExtend = substituteTVars(lhs, extend); - mappings.put(extend, substExtend); - } - - AnnotatedTypeMirror zuper = awt.getSuperBound(); - if (zuper != null) { - AnnotatedTypeMirror substZuper = substituteTVars(lhs, zuper); - mappings.put(zuper, substZuper); - } - - rhs = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); - } else if (rhs.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType aat = (AnnotatedArrayType) rhs.shallowCopy(); - IdentityHashMap mappings = new IdentityHashMap<>(); - - AnnotatedTypeMirror compnentType = aat.getComponentType(); - // Type variable of compnentType already gets substituted - AnnotatedTypeMirror substCompnentType = substituteTVars(lhs, compnentType); - mappings.put(compnentType, substCompnentType); - - // Construct result type - rhs = AnnotatedTypeCopierWithReplacement.replace(aat, mappings); - } else if (rhs.getKind().isPrimitive() || rhs.getKind() == TypeKind.NULL) { - // nothing to do for primitive types and the null type - } else { - throw new BugInCF( - "ViewpointAdapter::substituteTVars: Cannot handle rhs: " - + rhs - + " of kind: " - + rhs.getKind()); } - return rhs; - } - - /** - * Return actual type argument for formal type parameter "var" from "type" - * - * @param type type from which type arguments are extracted to replace "var" - * @param var formal type parameter that needs real type arguments - * @return Real type argument - */ - private AnnotatedTypeMirror getTypeVariableSubstitution( - AnnotatedDeclaredType type, AnnotatedTypeVariable var) { - IPair res = findDeclType(type, var); - - if (res == null) { - return var; + /** + * Extract the relevant qualifier from an {@link AnnotatedTypeMirror}. + * + * @param atm AnnotatedTypeMirror from which qualifier is extracted + * @return extracted qualifier + */ + protected abstract AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm); + + /** + * Combine receiver qualifiers with declared types. Qualifiers are extracted from declared types + * to further perform viewpoint adaptation only between two qualifiers. + * + * @param receiverAnnotation receiver qualifier + * @param declared declared type + * @return {@link AnnotatedTypeMirror} after viewpoint adaptation + */ + protected AnnotatedTypeMirror combineAnnotationWithType( + AnnotationMirror receiverAnnotation, AnnotatedTypeMirror declared) { + if (declared.getKind().isPrimitive()) { + AnnotatedPrimitiveType apt = (AnnotatedPrimitiveType) declared.shallowCopy(); + + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation( + receiverAnnotation, extractAnnotationMirror(apt)); + apt.replaceAnnotation(resultAnnotation); + return apt; + } else if (declared.getKind() == TypeKind.TYPEVAR) { + if (!isTypeVarExtends) { + isTypeVarExtends = true; + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) declared.shallowCopy(); + IdentityHashMap mappings = + new IdentityHashMap<>(); + + // For type variables, we recursively adapt upper and lower bounds + AnnotatedTypeMirror resUpper = + combineAnnotationWithType(receiverAnnotation, atv.getUpperBound()); + mappings.put(atv.getUpperBound(), resUpper); + + AnnotatedTypeMirror resLower = + combineAnnotationWithType(receiverAnnotation, atv.getLowerBound()); + mappings.put(atv.getLowerBound(), resLower); + + AnnotatedTypeMirror result = + AnnotatedTypeCopierWithReplacement.replace(atv, mappings); + + isTypeVarExtends = false; + return result; + } + return declared; + } else if (declared.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) declared.shallowCopy(); + + // Mapping between declared type argument to combined type argument + IdentityHashMap mappings = + new IdentityHashMap<>(); + + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation( + receiverAnnotation, extractAnnotationMirror(adt)); + + // Recursively combine type arguments and store to map + for (AnnotatedTypeMirror typeArgument : adt.getTypeArguments()) { + // Recursively adapt the type arguments of this adt + AnnotatedTypeMirror combinedTypeArgument = + combineAnnotationWithType(receiverAnnotation, typeArgument); + mappings.put(typeArgument, combinedTypeArgument); + } + + // Construct result type + AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); + result.replaceAnnotation(resultAnnotation); + + return result; + } else if (declared.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aat = (AnnotatedArrayType) declared.shallowCopy(); + + // Replace the main qualifier + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation( + receiverAnnotation, extractAnnotationMirror(aat)); + aat.replaceAnnotation(resultAnnotation); + + // Combine component type recursively and sets combined component type + AnnotatedTypeMirror compo = aat.getComponentType(); + // Recursively call itself first on the component type + AnnotatedTypeMirror combinedCompoType = + combineAnnotationWithType(receiverAnnotation, compo); + aat.setComponentType(combinedCompoType); + + return aat; + } else if (declared.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType awt = (AnnotatedWildcardType) declared.shallowCopy(); + IdentityHashMap mappings = + new IdentityHashMap<>(); + + // There is no main qualifier for a wildcard + + // Adapt extend + AnnotatedTypeMirror extend = awt.getExtendsBound(); + if (extend != null) { + // Recursively adapt the extends bound of this awt + AnnotatedTypeMirror combinedExtend = + combineAnnotationWithType(receiverAnnotation, extend); + mappings.put(extend, combinedExtend); + } + + // Adapt super + AnnotatedTypeMirror zuper = awt.getSuperBound(); + if (zuper != null) { + // Recursively adapt the lower bound of this awt + AnnotatedTypeMirror combinedZuper = + combineAnnotationWithType(receiverAnnotation, zuper); + mappings.put(zuper, combinedZuper); + } + + AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); + return result; + } else if (declared.getKind() == TypeKind.NULL) { + AnnotatedNullType ant = (AnnotatedNullType) declared.shallowCopy(true); + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation( + receiverAnnotation, extractAnnotationMirror(ant)); + ant.replaceAnnotation(resultAnnotation); + return ant; + } else { + throw new BugInCF( + "ViewpointAdapter::combineAnnotationWithType: Unknown decl: " + + declared + + " of kind: " + + declared.getKind()); + } } - AnnotatedDeclaredType decltype = res.first; - int foundindex = res.second; - - List tas = decltype.getTypeArguments(); - // return a copy, as we want to modify the type later. - return tas.get(foundindex).shallowCopy(true); - } - - /** - * Find the index (position) of this type variable from type - * - * @param type type from which we infer actual type arguments - * @param var formal type parameter - * @return index(position) of this type variable from type - */ - private IPair findDeclType( - AnnotatedDeclaredType type, AnnotatedTypeVariable var) { - Element varelem = var.getUnderlyingType().asElement(); - - DeclaredType dtype = type.getUnderlyingType(); - TypeElement el = (TypeElement) dtype.asElement(); - List tparams = el.getTypeParameters(); - int foundindex = 0; - - for (TypeParameterElement tparam : tparams) { - if (tparam.equals(varelem)) { - // we found the right index! - break; - } - ++foundindex; + /** + * Viewpoint adapt declared qualifier to receiver qualifier. + * + * @param receiverAnnotation receiver qualifier + * @param declaredAnnotation declared qualifier + * @return result qualifier after viewpoint adaptation + */ + @SideEffectFree + protected abstract AnnotationMirror combineAnnotationWithAnnotation( + AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation); + + /** + * If rhs contains/is a type variable use whose type arguments should be inferred from the + * receiver, i.e. lhs, this method substitutes that type argument into rhs, and returns the + * reference to rhs. This method is side effect free, because rhs will be copied and that copy + * gets modified and returned. + * + * @param lhs type from which type arguments are extracted to replace formal type parameters of + * rhs. + * @param rhs AnnotatedTypeMirror that might be a formal type parameter + * @return rhs' copy with its type parameter substituted + */ + private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTypeMirror rhs) { + if (rhs.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) rhs.shallowCopy(); + + // Base case where actual type argument is extracted + if (lhs.getKind() == TypeKind.DECLARED) { + rhs = getTypeVariableSubstitution((AnnotatedDeclaredType) lhs, atv); + } + } else if (rhs.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) rhs.shallowCopy(); + IdentityHashMap mappings = + new IdentityHashMap<>(); + + for (AnnotatedTypeMirror formalTypeParameter : adt.getTypeArguments()) { + AnnotatedTypeMirror actualTypeArgument = substituteTVars(lhs, formalTypeParameter); + mappings.put(formalTypeParameter, actualTypeArgument); + // The following code does the wrong thing! + } + // We must use AnnotatedTypeReplacer to replace the formal type parameters with actual + // type arguments, but not replace with its main qualifier + rhs = AnnotatedTypeCopierWithReplacement.replace(adt, mappings); + } else if (rhs.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType awt = (AnnotatedWildcardType) rhs.shallowCopy(); + IdentityHashMap mappings = + new IdentityHashMap<>(); + + AnnotatedTypeMirror extend = awt.getExtendsBound(); + if (extend != null) { + AnnotatedTypeMirror substExtend = substituteTVars(lhs, extend); + mappings.put(extend, substExtend); + } + + AnnotatedTypeMirror zuper = awt.getSuperBound(); + if (zuper != null) { + AnnotatedTypeMirror substZuper = substituteTVars(lhs, zuper); + mappings.put(zuper, substZuper); + } + + rhs = AnnotatedTypeCopierWithReplacement.replace(awt, mappings); + } else if (rhs.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType aat = (AnnotatedArrayType) rhs.shallowCopy(); + IdentityHashMap mappings = + new IdentityHashMap<>(); + + AnnotatedTypeMirror compnentType = aat.getComponentType(); + // Type variable of compnentType already gets substituted + AnnotatedTypeMirror substCompnentType = substituteTVars(lhs, compnentType); + mappings.put(compnentType, substCompnentType); + + // Construct result type + rhs = AnnotatedTypeCopierWithReplacement.replace(aat, mappings); + } else if (rhs.getKind().isPrimitive() || rhs.getKind() == TypeKind.NULL) { + // nothing to do for primitive types and the null type + } else { + throw new BugInCF( + "ViewpointAdapter::substituteTVars: Cannot handle rhs: " + + rhs + + " of kind: " + + rhs.getKind()); + } + + return rhs; } - if (foundindex >= tparams.size()) { - // Didn't find the desired type => Head for super type of "type"! - for (AnnotatedDeclaredType sup : type.directSupertypes()) { - IPair res = findDeclType(sup, var); - if (res != null) { - return res; + /** + * Return actual type argument for formal type parameter "var" from "type" + * + * @param type type from which type arguments are extracted to replace "var" + * @param var formal type parameter that needs real type arguments + * @return Real type argument + */ + private AnnotatedTypeMirror getTypeVariableSubstitution( + AnnotatedDeclaredType type, AnnotatedTypeVariable var) { + IPair res = findDeclType(type, var); + + if (res == null) { + return var; } - } - // We reach this point if the variable wasn't found in any recursive call on ALL direct - // supertypes. - return null; + + AnnotatedDeclaredType decltype = res.first; + int foundindex = res.second; + + List tas = decltype.getTypeArguments(); + // return a copy, as we want to modify the type later. + return tas.get(foundindex).shallowCopy(true); } - return IPair.of(type, foundindex); - } + /** + * Find the index (position) of this type variable from type + * + * @param type type from which we infer actual type arguments + * @param var formal type parameter + * @return index(position) of this type variable from type + */ + private IPair findDeclType( + AnnotatedDeclaredType type, AnnotatedTypeVariable var) { + Element varelem = var.getUnderlyingType().asElement(); + + DeclaredType dtype = type.getUnderlyingType(); + TypeElement el = (TypeElement) dtype.asElement(); + List tparams = el.getTypeParameters(); + int foundindex = 0; + + for (TypeParameterElement tparam : tparams) { + if (tparam.equals(varelem)) { + // we found the right index! + break; + } + ++foundindex; + } + + if (foundindex >= tparams.size()) { + // Didn't find the desired type => Head for super type of "type"! + for (AnnotatedDeclaredType sup : type.directSupertypes()) { + IPair res = findDeclType(sup, var); + if (res != null) { + return res; + } + } + // We reach this point if the variable wasn't found in any recursive call on ALL direct + // supertypes. + return null; + } + + return IPair.of(type, foundindex); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java index 57ef0e8a9d3..39606dc75c3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -17,6 +13,11 @@ import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; import org.plumelib.util.CollectionsPlume; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; + /** * AnnotatedTypeCopier is a visitor that deep copies an AnnotatedTypeMirror exactly, including any * lazily initialized fields. That is, if a field has already been initialized, it will be @@ -41,348 +42,355 @@ * @see org.checkerframework.framework.type.AnnotatedTypeCopierWithReplacement */ public class AnnotatedTypeCopier - implements AnnotatedTypeVisitor< - AnnotatedTypeMirror, IdentityHashMap> { - - /** - * This is a hack to handle the curious behavior of substitution on an AnnotatedExecutableType. - * - * @see org.checkerframework.framework.type.TypeVariableSubstitutor It is poor form to include - * such a flag on the base class for exclusive use in a subclass but it is the least bad - * option in this case. - */ - protected boolean visitingExecutableTypeParam = false; - - /** - * See {@link #AnnotatedTypeCopier(boolean)}. - * - * @see #AnnotatedTypeCopier(boolean) - */ - protected final boolean copyAnnotations; - - /** - * Creates an AnnotatedTypeCopier that may or may not copyAnnotations By default - * AnnotatedTypeCopier provides two major properties in its copies: - * - *

            - *
          1. Structure preservation -- the exact structure of the original AnnotatedTypeMirror is - * preserved in the copy including all component types. - *
          2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror - * and its components have been copied to the new type. - *
          - * - * If copyAnnotations is set to false, the second property, annotation preservation, is removed. - * This is useful for cases in which the user may want to copy the structure of a type exactly but - * NOT its annotations. - */ - public AnnotatedTypeCopier(boolean copyAnnotations) { - this.copyAnnotations = copyAnnotations; - } - - /** - * Creates an AnnotatedTypeCopier that copies both the structure and annotations of the source - * AnnotatedTypeMirror. - * - * @see #AnnotatedTypeCopier(boolean) - */ - public AnnotatedTypeCopier() { - this(true); - } - - @Override - public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { - return type.accept(this, new IdentityHashMap<>()); - } - - @Override - public AnnotatedTypeMirror visit( - AnnotatedTypeMirror type, - IdentityHashMap originalToCopy) { - return type.accept(this, originalToCopy); - } - - @Override - public AnnotatedTypeMirror visitDeclared( - AnnotatedDeclaredType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); + implements AnnotatedTypeVisitor< + AnnotatedTypeMirror, IdentityHashMap> { + + /** + * This is a hack to handle the curious behavior of substitution on an AnnotatedExecutableType. + * + * @see org.checkerframework.framework.type.TypeVariableSubstitutor It is poor form to include + * such a flag on the base class for exclusive use in a subclass but it is the least bad + * option in this case. + */ + protected boolean visitingExecutableTypeParam = false; + + /** + * See {@link #AnnotatedTypeCopier(boolean)}. + * + * @see #AnnotatedTypeCopier(boolean) + */ + protected final boolean copyAnnotations; + + /** + * Creates an AnnotatedTypeCopier that may or may not copyAnnotations By default + * AnnotatedTypeCopier provides two major properties in its copies: + * + *
            + *
          1. Structure preservation -- the exact structure of the original AnnotatedTypeMirror is + * preserved in the copy including all component types. + *
          2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror + * and its components have been copied to the new type. + *
          + * + * If copyAnnotations is set to false, the second property, annotation preservation, is removed. + * This is useful for cases in which the user may want to copy the structure of a type exactly + * but NOT its annotations. + */ + public AnnotatedTypeCopier(boolean copyAnnotations) { + this.copyAnnotations = copyAnnotations; } - AnnotatedDeclaredType copy = makeOrReturnCopy(original, originalToCopy); - - if (original.isUnderlyingTypeRaw()) { - copy.setIsUnderlyingTypeRaw(); + /** + * Creates an AnnotatedTypeCopier that copies both the structure and annotations of the source + * AnnotatedTypeMirror. + * + * @see #AnnotatedTypeCopier(boolean) + */ + public AnnotatedTypeCopier() { + this(true); } - if (original.enclosingType != null) { - copy.enclosingType = (AnnotatedDeclaredType) visit(original.enclosingType, originalToCopy); + @Override + public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { + return type.accept(this, new IdentityHashMap<>()); } - if (original.typeArgs != null) { - List copyTypeArgs = - CollectionsPlume.mapList( - (AnnotatedTypeMirror typeArg) -> visit(typeArg, originalToCopy), - original.getTypeArguments()); - copy.setTypeArguments(copyTypeArgs); + @Override + public AnnotatedTypeMirror visit( + AnnotatedTypeMirror type, + IdentityHashMap originalToCopy) { + return type.accept(this, originalToCopy); } - return copy; - } - - @Override - public AnnotatedTypeMirror visitIntersection( - AnnotatedIntersectionType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); + @Override + public AnnotatedTypeMirror visitDeclared( + AnnotatedDeclaredType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } + + AnnotatedDeclaredType copy = makeOrReturnCopy(original, originalToCopy); + + if (original.isUnderlyingTypeRaw()) { + copy.setIsUnderlyingTypeRaw(); + } + + if (original.enclosingType != null) { + copy.enclosingType = + (AnnotatedDeclaredType) visit(original.enclosingType, originalToCopy); + } + + if (original.typeArgs != null) { + List copyTypeArgs = + CollectionsPlume.mapList( + (AnnotatedTypeMirror typeArg) -> visit(typeArg, originalToCopy), + original.getTypeArguments()); + copy.setTypeArguments(copyTypeArgs); + } + + return copy; } - AnnotatedIntersectionType copy = makeOrReturnCopy(original, originalToCopy); - - if (original.bounds != null) { - List copySupertypes = - CollectionsPlume.mapList( - (AnnotatedTypeMirror bound) -> visit(bound, originalToCopy), original.bounds); - copy.bounds = Collections.unmodifiableList(copySupertypes); + @Override + public AnnotatedTypeMirror visitIntersection( + AnnotatedIntersectionType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } + + AnnotatedIntersectionType copy = makeOrReturnCopy(original, originalToCopy); + + if (original.bounds != null) { + List copySupertypes = + CollectionsPlume.mapList( + (AnnotatedTypeMirror bound) -> visit(bound, originalToCopy), + original.bounds); + copy.bounds = Collections.unmodifiableList(copySupertypes); + } + + return copy; } - return copy; - } + @Override + public AnnotatedTypeMirror visitUnion( + AnnotatedUnionType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } + + AnnotatedUnionType copy = makeOrReturnCopy(original, originalToCopy); + + if (original.alternatives != null) { + List copyAlternatives = + CollectionsPlume.mapList( + (AnnotatedDeclaredType supertype) -> + (AnnotatedDeclaredType) visit(supertype, originalToCopy), + original.alternatives); + copy.alternatives = Collections.unmodifiableList(copyAlternatives); + } + + return copy; + } - @Override - public AnnotatedTypeMirror visitUnion( - AnnotatedUnionType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); + @Override + public AnnotatedTypeMirror visitExecutable( + AnnotatedExecutableType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } + + AnnotatedExecutableType copy = makeOrReturnCopy(original, originalToCopy); + + copy.setElement(original.getElement()); + + if (original.getReceiverType() != null) { + copy.setReceiverType( + (AnnotatedDeclaredType) visit(original.getReceiverType(), originalToCopy)); + } + + List originalParameterTypes = original.getParameterTypes(); + if (originalParameterTypes.isEmpty()) { + copy.setParameterTypes(Collections.emptyList()); + } else { + List copyParamTypes = + new ArrayList<>(originalParameterTypes.size()); + for (AnnotatedTypeMirror param : originalParameterTypes) { + copyParamTypes.add(visit(param, originalToCopy)); + } + copy.setParameterTypes(Collections.unmodifiableList(copyParamTypes)); + } + + if (original.getVarargType() != null) { + copy.setVarargType(original.getVarargType()); + } else { + copy.computeVarargType(); + } + + List originalThrownTypes = original.getThrownTypes(); + if (originalThrownTypes.isEmpty()) { + copy.setThrownTypes(Collections.emptyList()); + } else { + List copyThrownTypes = new ArrayList<>(originalThrownTypes.size()); + for (AnnotatedTypeMirror thrown : original.getThrownTypes()) { + copyThrownTypes.add(visit(thrown, originalToCopy)); + } + copy.setThrownTypes(Collections.unmodifiableList(copyThrownTypes)); + } + + copy.setReturnType(visit(original.getReturnType(), originalToCopy)); + + List originalTypeVariables = original.getTypeVariables(); + if (originalTypeVariables.isEmpty()) { + copy.setTypeVariables(Collections.emptyList()); + } else { + List copyTypeVarTypes = + new ArrayList<>(originalTypeVariables.size()); + for (AnnotatedTypeVariable typeVariable : originalTypeVariables) { + // This field is needed to identify exactly when the declaration of an executable's + // type parameter is visited. When subtypes of this class visit the type + // parameter's component types, they will likely set visitingExecutableTypeParam to + // false. + // Therefore, we set this variable on each iteration of the loop. + // See TypeVariableSubstitutor.Visitor.visitTypeVariable for an example of this. + visitingExecutableTypeParam = true; + copyTypeVarTypes.add((AnnotatedTypeVariable) visit(typeVariable, originalToCopy)); + } + copy.setTypeVariables(Collections.unmodifiableList(copyTypeVarTypes)); + visitingExecutableTypeParam = false; + } + + return copy; } - AnnotatedUnionType copy = makeOrReturnCopy(original, originalToCopy); + @Override + public AnnotatedTypeMirror visitArray( + AnnotatedArrayType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } - if (original.alternatives != null) { - List copyAlternatives = - CollectionsPlume.mapList( - (AnnotatedDeclaredType supertype) -> - (AnnotatedDeclaredType) visit(supertype, originalToCopy), - original.alternatives); - copy.alternatives = Collections.unmodifiableList(copyAlternatives); - } + AnnotatedArrayType copy = makeOrReturnCopy(original, originalToCopy); - return copy; - } + copy.setComponentType(visit(original.getComponentType(), originalToCopy)); - @Override - public AnnotatedTypeMirror visitExecutable( - AnnotatedExecutableType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); + return copy; } - AnnotatedExecutableType copy = makeOrReturnCopy(original, originalToCopy); + @Override + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } - copy.setElement(original.getElement()); + AnnotatedTypeVariable copy = makeOrReturnCopy(original, originalToCopy); - if (original.getReceiverType() != null) { - copy.setReceiverType( - (AnnotatedDeclaredType) visit(original.getReceiverType(), originalToCopy)); - } + if (original.getUpperBoundField() != null) { + copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); + } - List originalParameterTypes = original.getParameterTypes(); - if (originalParameterTypes.isEmpty()) { - copy.setParameterTypes(Collections.emptyList()); - } else { - List copyParamTypes = new ArrayList<>(originalParameterTypes.size()); - for (AnnotatedTypeMirror param : originalParameterTypes) { - copyParamTypes.add(visit(param, originalToCopy)); - } - copy.setParameterTypes(Collections.unmodifiableList(copyParamTypes)); - } + if (original.getLowerBoundField() != null) { + copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); + } - if (original.getVarargType() != null) { - copy.setVarargType(original.getVarargType()); - } else { - copy.computeVarargType(); + return copy; } - List originalThrownTypes = original.getThrownTypes(); - if (originalThrownTypes.isEmpty()) { - copy.setThrownTypes(Collections.emptyList()); - } else { - List copyThrownTypes = new ArrayList<>(originalThrownTypes.size()); - for (AnnotatedTypeMirror thrown : original.getThrownTypes()) { - copyThrownTypes.add(visit(thrown, originalToCopy)); - } - copy.setThrownTypes(Collections.unmodifiableList(copyThrownTypes)); + @Override + public AnnotatedTypeMirror visitPrimitive( + AnnotatedPrimitiveType original, + IdentityHashMap originalToCopy) { + return makeOrReturnCopy(original, originalToCopy); } - copy.setReturnType(visit(original.getReturnType(), originalToCopy)); - - List originalTypeVariables = original.getTypeVariables(); - if (originalTypeVariables.isEmpty()) { - copy.setTypeVariables(Collections.emptyList()); - } else { - List copyTypeVarTypes = new ArrayList<>(originalTypeVariables.size()); - for (AnnotatedTypeVariable typeVariable : originalTypeVariables) { - // This field is needed to identify exactly when the declaration of an executable's - // type parameter is visited. When subtypes of this class visit the type - // parameter's component types, they will likely set visitingExecutableTypeParam to - // false. - // Therefore, we set this variable on each iteration of the loop. - // See TypeVariableSubstitutor.Visitor.visitTypeVariable for an example of this. - visitingExecutableTypeParam = true; - copyTypeVarTypes.add((AnnotatedTypeVariable) visit(typeVariable, originalToCopy)); - } - copy.setTypeVariables(Collections.unmodifiableList(copyTypeVarTypes)); - visitingExecutableTypeParam = false; + @Override + public AnnotatedTypeMirror visitNoType( + AnnotatedNoType original, + IdentityHashMap originalToCopy) { + return makeOrReturnCopy(original, originalToCopy); } - return copy; - } - - @Override - public AnnotatedTypeMirror visitArray( - AnnotatedArrayType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); + @Override + public AnnotatedTypeMirror visitNull( + AnnotatedNullType original, + IdentityHashMap originalToCopy) { + return makeOrReturnCopy(original, originalToCopy); } - AnnotatedArrayType copy = makeOrReturnCopy(original, originalToCopy); + @Override + public AnnotatedTypeMirror visitWildcard( + AnnotatedWildcardType original, + IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return originalToCopy.get(original); + } - copy.setComponentType(visit(original.getComponentType(), originalToCopy)); + AnnotatedWildcardType copy = makeOrReturnCopy(original, originalToCopy); - return copy; - } + if (original.isTypeArgOfRawType()) { + copy.setTypeArgOfRawType(); + } - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } - - AnnotatedTypeVariable copy = makeOrReturnCopy(original, originalToCopy); + if (original.getExtendsBoundField() != null) { + copy.setExtendsBound(visit(original.getExtendsBoundField(), originalToCopy).asUse()); + } - if (original.getUpperBoundField() != null) { - copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); - } + if (original.getSuperBoundField() != null) { + copy.setSuperBound(visit(original.getSuperBoundField(), originalToCopy).asUse()); + } - if (original.getLowerBoundField() != null) { - copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); - } - - return copy; - } - - @Override - public AnnotatedTypeMirror visitPrimitive( - AnnotatedPrimitiveType original, - IdentityHashMap originalToCopy) { - return makeOrReturnCopy(original, originalToCopy); - } - - @Override - public AnnotatedTypeMirror visitNoType( - AnnotatedNoType original, - IdentityHashMap originalToCopy) { - return makeOrReturnCopy(original, originalToCopy); - } - - @Override - public AnnotatedTypeMirror visitNull( - AnnotatedNullType original, - IdentityHashMap originalToCopy) { - return makeOrReturnCopy(original, originalToCopy); - } - - @Override - public AnnotatedTypeMirror visitWildcard( - AnnotatedWildcardType original, - IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return originalToCopy.get(original); - } - - AnnotatedWildcardType copy = makeOrReturnCopy(original, originalToCopy); - - if (original.isTypeArgOfRawType()) { - copy.setTypeArgOfRawType(); - } + copy.setTypeVariable(original.getTypeVariable()); - if (original.getExtendsBoundField() != null) { - copy.setExtendsBound(visit(original.getExtendsBoundField(), originalToCopy).asUse()); + return copy; } - if (original.getSuperBoundField() != null) { - copy.setSuperBound(visit(original.getSuperBoundField(), originalToCopy).asUse()); + /** + * For any given object in the type being copied, we only want to generate one copy of that + * object. When that object is encountered again, using the previously generated copy will + * preserve the structure of the original AnnotatedTypeMirror. + * + *

          makeOrReturnCopy first checks to see if an object has been encountered before. If so, it + * returns the previously generated duplicate of that object if not, it creates a duplicate of + * the object and stores it in the history, originalToCopy + * + * @param original a reference to a type to copy + * @param originalToCopy a mapping of previously encountered references to the copies made for + * those references + * @param the type of original copy, this is a shortcut to avoid having to insert casts all + * over the visitor + * @return a copy of original + */ + @SuppressWarnings("unchecked") + protected T makeOrReturnCopy( + T original, IdentityHashMap originalToCopy) { + if (originalToCopy.containsKey(original)) { + return (T) originalToCopy.get(original); + } + + T copy = makeCopy(original); + originalToCopy.put(original, copy); + + return copy; } - copy.setTypeVariable(original.getTypeVariable()); - - return copy; - } - - /** - * For any given object in the type being copied, we only want to generate one copy of that - * object. When that object is encountered again, using the previously generated copy will - * preserve the structure of the original AnnotatedTypeMirror. - * - *

          makeOrReturnCopy first checks to see if an object has been encountered before. If so, it - * returns the previously generated duplicate of that object if not, it creates a duplicate of the - * object and stores it in the history, originalToCopy - * - * @param original a reference to a type to copy - * @param originalToCopy a mapping of previously encountered references to the copies made for - * those references - * @param the type of original copy, this is a shortcut to avoid having to insert casts all - * over the visitor - * @return a copy of original - */ - @SuppressWarnings("unchecked") - protected T makeOrReturnCopy( - T original, IdentityHashMap originalToCopy) { - if (originalToCopy.containsKey(original)) { - return (T) originalToCopy.get(original); + /** + * Returns a copy of the given type. + * + * @param the type of the AnnotatedTypeMirror to copy + * @param original an AnnotatedTypeMirror (more specifically, a {@code T}) + * @return a copy of the given AnnotatedTypeMirror + */ + @SuppressWarnings("unchecked") + protected T makeCopy(T original) { + T copy = + (T) + AnnotatedTypeMirror.createType( + original.getUnderlyingType(), + original.atypeFactory, + original.isDeclaration()); + maybeCopyPrimaryAnnotations(original, copy); + + return copy; } - T copy = makeCopy(original); - originalToCopy.put(original, copy); - - return copy; - } - - /** - * Returns a copy of the given type. - * - * @param the type of the AnnotatedTypeMirror to copy - * @param original an AnnotatedTypeMirror (more specifically, a {@code T}) - * @return a copy of the given AnnotatedTypeMirror - */ - @SuppressWarnings("unchecked") - protected T makeCopy(T original) { - T copy = - (T) - AnnotatedTypeMirror.createType( - original.getUnderlyingType(), original.atypeFactory, original.isDeclaration()); - maybeCopyPrimaryAnnotations(original, copy); - - return copy; - } - - /** - * This method is called in any location in which a primary annotation would be copied from source - * to dest. Note, this method obeys the copyAnnotations field. Subclasses of AnnotatedTypeCopier - * can use this method to customize annotations before copying. - * - * @param source the type whose primary annotations are being copied - * @param dest a copy of source that should receive its primary annotations - */ - protected void maybeCopyPrimaryAnnotations(AnnotatedTypeMirror source, AnnotatedTypeMirror dest) { - if (copyAnnotations) { - dest.addAnnotations(source.getAnnotationsField()); + /** + * This method is called in any location in which a primary annotation would be copied from + * source to dest. Note, this method obeys the copyAnnotations field. Subclasses of + * AnnotatedTypeCopier can use this method to customize annotations before copying. + * + * @param source the type whose primary annotations are being copied + * @param dest a copy of source that should receive its primary annotations + */ + protected void maybeCopyPrimaryAnnotations( + AnnotatedTypeMirror source, AnnotatedTypeMirror dest) { + if (copyAnnotations) { + dest.addAnnotations(source.getAnnotationsField()); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java index 224027e102d..42e44fb0a8c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java @@ -1,81 +1,84 @@ package org.checkerframework.framework.type; -import java.util.IdentityHashMap; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import java.util.IdentityHashMap; + /** Duplicates annotated types and replaces components according to a replacement map. */ public class AnnotatedTypeCopierWithReplacement { - /** - * Return a copy of type after making the specified replacements. - * - * @param type the type that will be copied with replaced components - * @param replacementMap a mapping of {@literal referenceToReplace => referenceOfReplacement} - * @return a duplicate of type in which every reference that was a key in replacementMap has been - * replaced by its corresponding value - */ - public static AnnotatedTypeMirror replace( - AnnotatedTypeMirror type, - IdentityHashMap - replacementMap) { - return new Visitor(replacementMap).visit(type); - } - - /** - * AnnotatedTypeCopier maintains a mapping of {@literal typeVisited => copyOfTypeVisited} When a - * reference, typeVisited, is encountered again, it will use the recorded reference, - * copyOfTypeVisited, instead of generating a new copy of typeVisited. Visitor pre-populates this - * mapping so that references are replaced not by their copies but by those in the replacementMap - * provided in the constructor. - * - *

          All types NOT in the replacement map are duplicated as per AnnotatedTypeCopier.visit - */ - protected static class Visitor extends AnnotatedTypeCopier { - - private final IdentityHashMap - originalMappings; - - public Visitor( - final IdentityHashMap - mappings) { - originalMappings = new IdentityHashMap<>(mappings); + /** + * Return a copy of type after making the specified replacements. + * + * @param type the type that will be copied with replaced components + * @param replacementMap a mapping of {@literal referenceToReplace => referenceOfReplacement} + * @return a duplicate of type in which every reference that was a key in replacementMap has + * been replaced by its corresponding value + */ + public static AnnotatedTypeMirror replace( + AnnotatedTypeMirror type, + IdentityHashMap + replacementMap) { + return new Visitor(replacementMap).visit(type); } - @Override - public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { - return type.accept(this, new IdentityHashMap<>(originalMappings)); - } + /** + * AnnotatedTypeCopier maintains a mapping of {@literal typeVisited => copyOfTypeVisited} When a + * reference, typeVisited, is encountered again, it will use the recorded reference, + * copyOfTypeVisited, instead of generating a new copy of typeVisited. Visitor pre-populates + * this mapping so that references are replaced not by their copies but by those in the + * replacementMap provided in the constructor. + * + *

          All types NOT in the replacement map are duplicated as per AnnotatedTypeCopier.visit + */ + protected static class Visitor extends AnnotatedTypeCopier { - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - // AnnotatedTypeCopier will visit the type parameters of a method and copy them. - // Without this flag, any mappings in originalToCopy would replace the type parameters. - // However, we do not replace the type parameters in an AnnotatedExecutableType. Also, - // AnnotatedExecutableType.typeVarTypes is of type List so if the - // mapping contained a type parameter -> (Non-type variable AnnotatedTypeMirror) then a - // runtime exception would occur. - if (visitingExecutableTypeParam) { - visitingExecutableTypeParam = false; - AnnotatedTypeVariable copy = - (AnnotatedTypeVariable) - AnnotatedTypeMirror.createType( - original.getUnderlyingType(), original.atypeFactory, original.isDeclaration()); - maybeCopyPrimaryAnnotations(original, copy); - originalToCopy.put(original, copy); + private final IdentityHashMap + originalMappings; - if (original.getUpperBoundField() != null) { - copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); + public Visitor( + final IdentityHashMap + mappings) { + originalMappings = new IdentityHashMap<>(mappings); } - if (original.getLowerBoundField() != null) { - copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); + @Override + public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) { + return type.accept(this, new IdentityHashMap<>(originalMappings)); } - return copy; - } - return super.visitTypeVariable(original, originalToCopy); + @Override + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + // AnnotatedTypeCopier will visit the type parameters of a method and copy them. + // Without this flag, any mappings in originalToCopy would replace the type parameters. + // However, we do not replace the type parameters in an AnnotatedExecutableType. Also, + // AnnotatedExecutableType.typeVarTypes is of type List so if the + // mapping contained a type parameter -> (Non-type variable AnnotatedTypeMirror) then a + // runtime exception would occur. + if (visitingExecutableTypeParam) { + visitingExecutableTypeParam = false; + AnnotatedTypeVariable copy = + (AnnotatedTypeVariable) + AnnotatedTypeMirror.createType( + original.getUnderlyingType(), + original.atypeFactory, + original.isDeclaration()); + maybeCopyPrimaryAnnotations(original, copy); + originalToCopy.put(original, copy); + + if (original.getUpperBoundField() != null) { + copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); + } + + if (original.getLowerBoundField() != null) { + copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy)); + } + return copy; + } + + return super.visitTypeVariable(original, originalToCopy); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index f1dbd03de3b..a20e6eb5b3b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -27,49 +27,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Options; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Target; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.IntersectionType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; + import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; @@ -134,6 +92,51 @@ import org.plumelib.util.IPair; import org.plumelib.util.StringsPlume; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + /** * The methods of this class take an element or AST node, and return the annotated type as an {@link * AnnotatedTypeMirror}. The methods are: @@ -167,5768 +170,5885 @@ */ public class AnnotatedTypeFactory implements AnnotationProvider { - /** Whether to print verbose debugging messages about stub files. */ - private final boolean debugStubParser; + /** Whether to print verbose debugging messages about stub files. */ + private final boolean debugStubParser; - /** The {@link Trees} instance to use for tree node path finding. */ - protected final Trees trees; + /** The {@link Trees} instance to use for tree node path finding. */ + protected final Trees trees; - /** Optional! The AST of the source file being operated on. */ - // TODO: when should root be null? What are the use cases? - // None of the existing test checkers has a null root. - // Should not be modified between calls to "visit". - private @Nullable CompilationUnitTree root; + /** Optional! The AST of the source file being operated on. */ + // TODO: when should root be null? What are the use cases? + // None of the existing test checkers has a null root. + // Should not be modified between calls to "visit". + private @Nullable CompilationUnitTree root; - /** The processing environment to use for accessing compiler internals. */ - protected final ProcessingEnvironment processingEnv; + /** The processing environment to use for accessing compiler internals. */ + protected final ProcessingEnvironment processingEnv; - /** Utility class for working with {@link Element}s. */ - protected final Elements elements; + /** Utility class for working with {@link Element}s. */ + protected final Elements elements; - /** Utility class for working with {@link TypeMirror}s. */ - public final Types types; + /** Utility class for working with {@link TypeMirror}s. */ + public final Types types; - /** - * A TreePath to the current tree that an external "visitor" is visiting. The visitor is either a - * subclass of {@link BaseTypeVisitor} or {@link - * org.checkerframework.framework.flow.CFAbstractTransfer}. - */ - private @Nullable TreePath visitorTreePath; + /** + * A TreePath to the current tree that an external "visitor" is visiting. The visitor is either + * a subclass of {@link BaseTypeVisitor} or {@link + * org.checkerframework.framework.flow.CFAbstractTransfer}. + */ + private @Nullable TreePath visitorTreePath; - // These variables cannot be static because they depend on the ProcessingEnvironment. - /** The AnnotatedFor.value argument/element. */ - protected final ExecutableElement annotatedForValueElement; + // These variables cannot be static because they depend on the ProcessingEnvironment. + /** The AnnotatedFor.value argument/element. */ + protected final ExecutableElement annotatedForValueElement; - /** The EnsuresQualifier.expression field/element. */ - protected final ExecutableElement ensuresQualifierExpressionElement; + /** The EnsuresQualifier.expression field/element. */ + protected final ExecutableElement ensuresQualifierExpressionElement; - /** The EnsuresQualifier.List.value field/element. */ - protected final ExecutableElement ensuresQualifierListValueElement; + /** The EnsuresQualifier.List.value field/element. */ + protected final ExecutableElement ensuresQualifierListValueElement; - /** The EnsuresQualifierIf.expression field/element. */ - protected final ExecutableElement ensuresQualifierIfExpressionElement; + /** The EnsuresQualifierIf.expression field/element. */ + protected final ExecutableElement ensuresQualifierIfExpressionElement; - /** The EnsuresQualifierIf.result argument/element. */ - protected final ExecutableElement ensuresQualifierIfResultElement; + /** The EnsuresQualifierIf.result argument/element. */ + protected final ExecutableElement ensuresQualifierIfResultElement; - /** The EnsuresQualifierIf.List.value field/element. */ - protected final ExecutableElement ensuresQualifierIfListValueElement; + /** The EnsuresQualifierIf.List.value field/element. */ + protected final ExecutableElement ensuresQualifierIfListValueElement; - /** The FieldInvariant.field argument/element. */ - protected final ExecutableElement fieldInvariantFieldElement; + /** The FieldInvariant.field argument/element. */ + protected final ExecutableElement fieldInvariantFieldElement; - /** The FieldInvariant.qualifier argument/element. */ - protected final ExecutableElement fieldInvariantQualifierElement; + /** The FieldInvariant.qualifier argument/element. */ + protected final ExecutableElement fieldInvariantQualifierElement; - /** The HasQualifierParameter.value field/element. */ - protected final ExecutableElement hasQualifierParameterValueElement; + /** The HasQualifierParameter.value field/element. */ + protected final ExecutableElement hasQualifierParameterValueElement; - /** The MethodVal.className argument/element. */ - public final ExecutableElement methodValClassNameElement; + /** The MethodVal.className argument/element. */ + public final ExecutableElement methodValClassNameElement; - /** The MethodVal.methodName argument/element. */ - public final ExecutableElement methodValMethodNameElement; + /** The MethodVal.methodName argument/element. */ + public final ExecutableElement methodValMethodNameElement; - /** The MethodVal.params argument/element. */ - public final ExecutableElement methodValParamsElement; + /** The MethodVal.params argument/element. */ + public final ExecutableElement methodValParamsElement; - /** The NoQualifierParameter.value field/element. */ - protected final ExecutableElement noQualifierParameterValueElement; + /** The NoQualifierParameter.value field/element. */ + protected final ExecutableElement noQualifierParameterValueElement; - /** The RequiresQualifier.expression field/element. */ - protected final ExecutableElement requiresQualifierExpressionElement; + /** The RequiresQualifier.expression field/element. */ + protected final ExecutableElement requiresQualifierExpressionElement; - /** The RequiresQualifier.List.value field/element. */ - protected final ExecutableElement requiresQualifierListValueElement; + /** The RequiresQualifier.List.value field/element. */ + protected final ExecutableElement requiresQualifierListValueElement; - /** The RequiresQualifier type. */ - protected final TypeMirror requiresQualifierTM; + /** The RequiresQualifier type. */ + protected final TypeMirror requiresQualifierTM; - /** The RequiresQualifier.List type. */ - protected final TypeMirror requiresQualifierListTM; + /** The RequiresQualifier.List type. */ + protected final TypeMirror requiresQualifierListTM; - /** The EnsuresQualifier type. */ - protected final TypeMirror ensuresQualifierTM; + /** The EnsuresQualifier type. */ + protected final TypeMirror ensuresQualifierTM; - /** The EnsuresQualifier.List type. */ - protected final TypeMirror ensuresQualifierListTM; + /** The EnsuresQualifier.List type. */ + protected final TypeMirror ensuresQualifierListTM; - /** The EnsuresQualifierIf type. */ - protected final TypeMirror ensuresQualifierIfTM; + /** The EnsuresQualifierIf type. */ + protected final TypeMirror ensuresQualifierIfTM; - /** The EnsuresQualifierIf.List type. */ - protected final TypeMirror ensuresQualifierIfListTM; + /** The EnsuresQualifierIf.List type. */ + protected final TypeMirror ensuresQualifierIfListTM; - /** - * ===== postInit initialized fields ==== Note: qualHierarchy and typeHierarchy are both - * initialized in the postInit. - * - * @see #postInit() This means, they cannot be final and cannot be referred to in any subclass - * constructor or method until after postInit is called - */ + /** + * ===== postInit initialized fields ==== Note: qualHierarchy and typeHierarchy are both + * initialized in the postInit. + * + * @see #postInit() This means, they cannot be final and cannot be referred to in any subclass + * constructor or method until after postInit is called + */ - /** Represent the annotation relations. */ - // This field cannot be final because it is set in `postInit()`. - protected QualifierHierarchy qualHierarchy; - - /** Represent the type relations. */ - protected TypeHierarchy typeHierarchy; - - /* NO-AFU Performs whole-program inference. If null, whole-program inference is disabled. */ - /* NO-AFU - private final @Nullable WholeProgramInference wholeProgramInference; - */ - - /** Viewpoint adapter used to perform viewpoint adaptation or null */ - protected @Nullable ViewpointAdapter viewpointAdapter; - - /** - * This formatter is used for converting AnnotatedTypeMirrors to Strings. This formatter will be - * used by all AnnotatedTypeMirrors created by this factory in their toString methods. - */ - protected final AnnotatedTypeFormatter typeFormatter; - - /** - * Annotation formatter is used to format AnnotationMirrors. It is primarily used by SourceChecker - * when generating error messages. - */ - private final AnnotationFormatter annotationFormatter; - - /** Holds the qualifier upper bounds for type uses. */ - protected QualifierUpperBounds qualifierUpperBounds; - - /** - * Provides utility method to substitute arguments for their type variables. Field should be - * final, but can only be set in postInit, because subtypes might need other state to be - * initialized first. - */ - protected TypeVariableSubstitutor typeVarSubstitutor; - - /** Provides utility method to infer type arguments. */ - protected TypeArgumentInference typeArgumentInference; - - /** - * Caches the supported type qualifier classes. Call {@link #getSupportedTypeQualifiers()} instead - * of using this field directly, as it may not have been initialized. - */ - private @MonotonicNonNull Set> supportedQuals = null; - - /** - * Caches the fully-qualified names of the classes in {@link #supportedQuals}. Call {@link - * #getSupportedTypeQualifierNames()} instead of using this field directly, as it may not have - * been initialized. - */ - private @MonotonicNonNull Set<@CanonicalName String> supportedQualNames = null; - - /** Parses stub files and stores annotations on public elements from stub files. */ - public final AnnotationFileElementTypes stubTypes; - - /** Parses ajava files and stores annotations on public elements from ajava files. */ - public final AnnotationFileElementTypes ajavaTypes; - - /** - * If type checking a Java file, stores annotations read from an ajava file for that class if one - * exists. Unlike {@link #ajavaTypes}, which only stores annotations on public elements, this - * stores annotations on all element locations such as in anonymous class bodies. - */ - protected @Nullable AnnotationFileElementTypes currentFileAjavaTypes; - - /** - * A cache used to store elements whose declaration annotations have already been stored by - * calling the method {@link #getDeclAnnotations(Element)}. - */ - private final Map cacheDeclAnnos; - - /** - * A set containing declaration annotations that should be inherited. A declaration annotation - * will be inherited if it is in this set, or if it has the meta-annotation @InheritedAnnotation. - */ - private final AnnotationMirrorSet inheritedAnnotations = new AnnotationMirrorSet(); - - /** The checker to use for option handling and resource management. */ - protected final BaseTypeChecker checker; - - /** - * Scans all parts of the {@link AnnotatedTypeMirror} so that all of its fields are initialized. - */ - private final SimpleAnnotatedTypeScanner atmInitializer = - new SimpleAnnotatedTypeScanner<>((type1, q) -> null); - - /** - * True if all methods should be assumed to be @SideEffectFree, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeSideEffectFree; - - /** - * True if all methods should be assumed to be @Deterministic, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumeDeterministic; - - /** - * True if all getter methods should be assumed to be @Pure, for the purposes of - * org.checkerframework.dataflow analysis. - */ - private final boolean assumePureGetters; - - /** True if -AmergeStubsWithSource was provided on the command line. */ - private final boolean mergeStubsWithSource; - - /** - * Initializes all fields of {@code type}. - * - * @param type annotated type mirror - */ - public void initializeAtm(AnnotatedTypeMirror type) { - atmInitializer.visit(type); - } - - /** Map keys are canonical names of aliased annotations. */ - private final Map<@FullyQualifiedName String, Alias> aliases = new HashMap<>(); - - /** - * A map from the canonical name of a declaration annotation to the mapping of the canonical name - * of a declaration annotation with the same meaning (an alias) to the annotation mirror that - * should be used instead (an instance of the canonical declaration annotation). - */ - // A further generalization is to do something similar to `aliases`, where we allow copying - // elements from the alias to the canonical annotation. - private final Map<@FullyQualifiedName String, Map<@FullyQualifiedName String, AnnotationMirror>> - declAliases = new HashMap<>(); - - /** - * Information about one annotation alias. - * - *

          The information is either an AnotationMirror that can be used directly, or information for a - * builder (name and fields not to copy); see checkRep. - */ - private static class Alias { - /** The canonical annotation (or null if copyElements == true). */ - final AnnotationMirror canonical; - - /** Whether elements should be copied over when translating to the canonical annotation. */ - final boolean copyElements; - - /** The canonical annotation name (or null if copyElements == false). */ - final @CanonicalName String canonicalName; - - /** Which elements should not be copied over (or null if copyElements == false). */ - final String[] ignorableElements; - - /** - * Create an Alias with the given components. - * - * @param aliasName the alias name; only used for debugging - * @param canonical the canonical annotation - * @param copyElements whether elements should be copied over when translating to the canonical - * annotation - * @param canonicalName the canonical annotation name (or null if copyElements == false) - * @param ignorableElements elements that should not be copied over - */ - Alias( - String aliasName, - AnnotationMirror canonical, - boolean copyElements, - @Nullable @CanonicalName String canonicalName, - String[] ignorableElements) { - this.canonical = canonical; - this.copyElements = copyElements; - this.canonicalName = canonicalName; - this.ignorableElements = ignorableElements; - checkRep(aliasName); - } - - /** - * Throw an exception if this object is malformed. - * - * @param aliasName the alias name; only used for diagnostic messages - */ - void checkRep(String aliasName) { - if (copyElements) { - if (!(canonical == null && canonicalName != null && ignorableElements != null)) { - throw new BugInCF( - "Bad Alias for %s: [canonical=%s] copyElements=%s canonicalName=%s" - + " ignorableElements=%s", - aliasName, canonical, copyElements, canonicalName, ignorableElements); - } - } else { - if (!(canonical != null && canonicalName == null && ignorableElements == null)) { - throw new BugInCF( - "Bad Alias for %s: canonical=%s copyElements=%s [canonicalName=%s" - + " ignorableElements=%s]", - aliasName, canonical, copyElements, canonicalName, ignorableElements); - } - } - } - } - - /** Unique ID counter; for debugging purposes. */ - private static int uidCounter = 0; - - /** Unique ID of the current object; for debugging purposes. */ - public final int uid; - - /** - * Object that is used to resolve reflective method calls, if reflection resolution is turned on. - */ - protected ReflectionResolver reflectionResolver; - - /** This loads type annotation classes via reflective lookup. */ - protected AnnotationClassLoader loader; - - /* NO-AFU - * Which whole-program inference output format to use, if doing whole-program inference. This - * variable would be final, but it is not set unless WPI is enabled. - */ - /* NO-AFU - public WholeProgramInference.OutputFormat wpiOutputFormat; - */ - - /** - * Should results be cached? This means that ATM.deepCopy() will be called. ATM.deepCopy() used to - * (and perhaps still does) side effect the ATM being copied. So setting this to false is not - * equivalent to setting shouldReadCache to false. - */ - public boolean shouldCache; - - /** Size of LRU cache if one isn't specified using the atfCacheSize option. */ - private static final int DEFAULT_CACHE_SIZE = 300; - - /** Mapping from a Tree to its annotated type; defaults have been applied. */ - private final Map classAndMethodTreeCache; - - /** - * Mapping from an expression tree to its annotated type; before defaults are applied, just what - * the programmer wrote. - */ - protected final Map fromExpressionTreeCache; - - /** - * Mapping from a member tree to its annotated type; before defaults are applied, just what the - * programmer wrote. - */ - protected final Map fromMemberTreeCache; - - /** - * Mapping from a type tree to its annotated type; before defaults are applied, just what the - * programmer wrote. - */ - protected final Map fromTypeTreeCache; - - /** - * Mapping from an Element to its annotated type; before defaults are applied, just what the - * programmer wrote. - */ - private final Map elementCache; - - /** Mapping from an Element to the source Tree of the declaration. */ - private final Map elementToTreeCache; - - /** Mapping from a Tree to its TreePath. Shared between all instances. */ - private final TreePathCacher treePathCache; - - /** Whether to ignore type arguments from raw types. */ - public final boolean ignoreRawTypeArguments; - - /** The Object.getClass method. */ - protected final ExecutableElement objectGetClass; - - /** Size of the annotationClassNames cache. */ - private static final int ANNOTATION_CACHE_SIZE = 500; - - /** Maps classes representing AnnotationMirrors to their canonical names. */ - private final Map, @CanonicalName String> annotationClassNames; - - /** An annotated type of the declaration of {@link Iterable} without any annotations. */ - private AnnotatedDeclaredType iterableDeclType; - - /** - * If the option "lspTypeInfo" is defined, this presenter will report the type information of - * every type-checked class. This information can be visualized by an editor/IDE that supports - * LSP. - */ - protected final TypeInformationPresenter typeInformationPresenter; - - /** - * Constructs a factory from the given checker. - * - *

          A subclass must call postInit at the end of its constructor. postInit must be the last call - * in the constructor or else types from stub files may not be created as expected. - * - * @param checker the {@link SourceChecker} to which this factory belongs - */ - public AnnotatedTypeFactory(BaseTypeChecker checker) { - uid = ++uidCounter; - this.processingEnv = checker.getProcessingEnvironment(); - this.checker = checker; - this.assumeSideEffectFree = - checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); - this.assumeDeterministic = - checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); - this.assumePureGetters = checker.hasOption("assumePureGetters"); - - this.trees = Trees.instance(processingEnv); - this.elements = processingEnv.getElementUtils(); - this.types = processingEnv.getTypeUtils(); - - this.stubTypes = new AnnotationFileElementTypes(this); - this.ajavaTypes = new AnnotationFileElementTypes(this); - this.currentFileAjavaTypes = null; - - this.cacheDeclAnnos = new HashMap<>(); - - // get the shared instance from the checker - this.treePathCache = checker.getTreePathCacher(); - - this.shouldCache = !checker.hasOption("atfDoNotCache"); - if (shouldCache) { - int cacheSize = getCacheSize(); - this.classAndMethodTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.fromExpressionTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.fromMemberTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.fromTypeTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.elementCache = CollectionsPlume.createLruCache(cacheSize); - this.elementToTreeCache = CollectionsPlume.createLruCache(cacheSize); - this.annotationClassNames = - Collections.synchronizedMap(CollectionsPlume.createLruCache(ANNOTATION_CACHE_SIZE)); - } else { - this.classAndMethodTreeCache = null; - this.fromExpressionTreeCache = null; - this.fromMemberTreeCache = null; - this.fromTypeTreeCache = null; - this.elementCache = null; - this.elementToTreeCache = null; - this.annotationClassNames = null; - } - - this.typeFormatter = createAnnotatedTypeFormatter(); - this.annotationFormatter = createAnnotationFormatter(); - this.typeInformationPresenter = createTypeInformationPresenter(); - - // Alias provided via -AaliasedTypeAnnos command-line option. - // This can only be used for annotations whose attributes have the same names as in the - // canonical annotation, e.g. this will not be usable to declare an alias @Regex(index = 5) - // for @Regex(value = 5). - if (checker.hasOption("aliasedTypeAnnos")) { - String aliasesOption = checker.getOption("aliasedTypeAnnos"); - String[] annos = aliasesOption.split(";"); - for (String alias : annos) { - IPair, @FullyQualifiedName String[]> aliasPair = - parseAliasesFromString(alias); - for (@FullyQualifiedName String a : aliasPair.second) { - addAliasedTypeAnnotation(a, aliasPair.first, true); - } - } - } + /** Represent the annotation relations. */ + // This field cannot be final because it is set in `postInit()`. + protected QualifierHierarchy qualHierarchy; - // Alias provided via -AaliasedDeclAnnos command-line option. - // This can only be used for annotations without attributes, - // e.g. this will not be usable to declare an alias for @EnsuresNonNull(...). - if (checker.hasOption("aliasedDeclAnnos")) { - String aliasesOption = checker.getOption("aliasedDeclAnnos"); - String[] annos = aliasesOption.split(";"); - for (String alias : annos) { - IPair, @FullyQualifiedName String[]> aliasPair = - parseAliasesFromString(alias); - AnnotationMirror anno = AnnotationBuilder.fromClass(elements, aliasPair.first); - for (String a : aliasPair.second) { - addAliasedDeclAnnotation(a, aliasPair.first.getCanonicalName(), anno); - } - } - } + /** Represent the type relations. */ + protected TypeHierarchy typeHierarchy; + /* NO-AFU Performs whole-program inference. If null, whole-program inference is disabled. */ /* NO-AFU - if (checker.hasOption("infer")) { - checkInvalidOptionsInferSignatures(); - String inferArg = checker.getOption("infer"); - // No argument means "jaifs", for (temporary) backwards compatibility. - if (inferArg == null) { - inferArg = "jaifs"; - } - switch (inferArg) { - case "stubs": - wpiOutputFormat = WholeProgramInference.OutputFormat.STUB; - break; - case "jaifs": - wpiOutputFormat = WholeProgramInference.OutputFormat.JAIF; - break; - case "ajava": - wpiOutputFormat = WholeProgramInference.OutputFormat.AJAVA; - break; - default: - throw new UserError( - "Bad argument -Ainfer=" - + inferArg - + " should be one of: -Ainfer=jaifs, -Ainfer=stubs, -Ainfer=ajava"); - } - boolean showWpiFailedInferences = checker.hasOption("showWpiFailedInferences"); - boolean inferOutputOriginal = checker.hasOption("inferOutputOriginal"); - if (inferOutputOriginal && wpiOutputFormat != WholeProgramInference.OutputFormat.AJAVA) { - checker.message( - Diagnostic.Kind.WARNING, - "-AinferOutputOriginal only works with -Ainfer=ajava, so it is being ignored."); - } - if (wpiOutputFormat == WholeProgramInference.OutputFormat.AJAVA) { - wholeProgramInference = - new WholeProgramInferenceImplementation( - this, - new WholeProgramInferenceJavaParserStorage(this, inferOutputOriginal), - showWpiFailedInferences); - } else { - wholeProgramInference = - new WholeProgramInferenceImplementation( - this, new WholeProgramInferenceScenesStorage(this), showWpiFailedInferences); - } - if (!checker.hasOption("warns")) { - // Without -Awarns, the inference output may be incomplete, because javac halts - // after issuing an error. - checker.message(Diagnostic.Kind.ERROR, "Do not supply -Ainfer without -Awarns"); - } - } else { - wholeProgramInference = null; - } + private final @Nullable WholeProgramInference wholeProgramInference; */ - ignoreRawTypeArguments = checker.getBooleanOption("ignoreRawTypeArguments", true); - - objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv); - - this.debugStubParser = checker.hasOption("stubDebug"); - - annotatedForValueElement = TreeUtils.getMethod(AnnotatedFor.class, "value", 0, processingEnv); - ensuresQualifierExpressionElement = - TreeUtils.getMethod(EnsuresQualifier.class, "expression", 0, processingEnv); - ensuresQualifierListValueElement = - TreeUtils.getMethod(EnsuresQualifier.List.class, "value", 0, processingEnv); - ensuresQualifierIfExpressionElement = - TreeUtils.getMethod(EnsuresQualifierIf.class, "expression", 0, processingEnv); - ensuresQualifierIfResultElement = - TreeUtils.getMethod(EnsuresQualifierIf.class, "result", 0, processingEnv); - ensuresQualifierIfListValueElement = - TreeUtils.getMethod(EnsuresQualifierIf.List.class, "value", 0, processingEnv); - fieldInvariantFieldElement = - TreeUtils.getMethod(FieldInvariant.class, "field", 0, processingEnv); - fieldInvariantQualifierElement = - TreeUtils.getMethod(FieldInvariant.class, "qualifier", 0, processingEnv); - hasQualifierParameterValueElement = - TreeUtils.getMethod(HasQualifierParameter.class, "value", 0, processingEnv); - methodValClassNameElement = TreeUtils.getMethod(MethodVal.class, "className", 0, processingEnv); - methodValMethodNameElement = - TreeUtils.getMethod(MethodVal.class, "methodName", 0, processingEnv); - methodValParamsElement = TreeUtils.getMethod(MethodVal.class, "params", 0, processingEnv); - noQualifierParameterValueElement = - TreeUtils.getMethod(NoQualifierParameter.class, "value", 0, processingEnv); - requiresQualifierExpressionElement = - TreeUtils.getMethod(RequiresQualifier.class, "expression", 0, processingEnv); - requiresQualifierListValueElement = - TreeUtils.getMethod(RequiresQualifier.List.class, "value", 0, processingEnv); - - requiresQualifierTM = - ElementUtils.getTypeElement(processingEnv, RequiresQualifier.class).asType(); - requiresQualifierListTM = - ElementUtils.getTypeElement(processingEnv, RequiresQualifier.List.class).asType(); - ensuresQualifierTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.class).asType(); - ensuresQualifierListTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.List.class).asType(); - ensuresQualifierIfTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.class).asType(); - ensuresQualifierIfListTM = - ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.List.class).asType(); - - mergeStubsWithSource = checker.hasOption("mergeStubsWithSource"); - } - - /** - * Parse a string in the format {@code FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2} - * to a pair of {@code (FQN.canonical.Qualifier.class, ["FQN.alias1.Qual1", "FQN.alias2.Qual2"])}. - * - * @param alias in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2 - * @return a pair with the first argument being the canonical qualifier class and the second - * argument being the list of aliases with fully qualified names - */ - // signature is suppressed because there is no way to reason about parsed strings - @SuppressWarnings({"unchecked", "signature"}) - private IPair, @FullyQualifiedName String[]> parseAliasesFromString( - String alias) { - String[] parts = alias.split(":"); - if (parts.length != 2) { - throw new UserError( - String.format( - "Alias argument must be in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2, got %s instead.", - alias)); - } - Class canonical; - try { - canonical = (Class) Class.forName(parts[0].trim()); - } catch (ClassNotFoundException | ClassCastException ex) { - throw new UserError(String.format("The name %s is an invalid annotation name.", parts[0])); - } - String[] aliases = parts[1].trim().split("\\s*,\\s*"); - return IPair.of(canonical, aliases); - } - - /** - * Requires that supportedQuals is non-null and non-empty and each element is a type qualifier. - * That is, no element has a {@code @Target} meta-annotation that contains something besides - * TYPE_USE or TYPE_PARAMETER. (@Target({}) is allowed.) @ - * - * @throws BugInCF If supportedQuals is empty or contaions a non-type qualifier - */ - private void checkSupportedQualsAreTypeQuals() { - if (supportedQuals == null || supportedQuals.isEmpty()) { - throw new TypeSystemError("Found no supported qualifiers."); - } - for (Class annotationClass : supportedQuals) { - // Check @Target values - ElementType[] targetValues = annotationClass.getAnnotation(Target.class).value(); - List badTargetValues = new ArrayList<>(0); - for (ElementType element : targetValues) { - if (!(element == ElementType.TYPE_USE || element == ElementType.TYPE_PARAMETER)) { - // if there's an ElementType with an enumerated value of something other - // than TYPE_USE or TYPE_PARAMETER then it isn't a valid qualifier - badTargetValues.add(element); - } - } - if (!badTargetValues.isEmpty()) { - String msg = - "The @Target meta-annotation on type qualifier " - + annotationClass.toString() - + " must not contain " - + StringsPlume.conjunction("or", badTargetValues) - + "."; - throw new TypeSystemError(msg); - } - } - } - - /* NO-AFU - * This method is called only when {@code -Ainfer} is passed as an option. It checks if another - * option that should not occur simultaneously with the whole-program inference is also passed - * as argument, and aborts the process if that is the case. For example, the whole-program - * inference process was not designed to work with conservative defaults. - * - *

          Subclasses may override this method to add more options. - */ - /* NO-AFU - protected void checkInvalidOptionsInferSignatures() { - // See Issue 683 - // https://github.com/typetools/checker-framework/issues/683 - if (checker.useConservativeDefault("source") - || checker.useConservativeDefault("bytecode")) { - throw new UserError( - "The option -Ainfer=... cannot be used together with conservative defaults."); - } - } - */ - - /** - * Actions that logically belong in the constructor, but need to run after the subclass - * constructor has completed. In particular, {@link AnnotationFileElementTypes#parseStubFiles()} - * may try to do type resolution with this AnnotatedTypeFactory. - */ - protected void postInit( - @UnderInitialization(AnnotatedTypeFactory.class) AnnotatedTypeFactory this) { - this.qualHierarchy = createQualifierHierarchy(); - if (qualHierarchy == null) { - throw new TypeSystemError( - "AnnotatedTypeFactory with null qualifier hierarchy not supported."); - } else if (!qualHierarchy.isValid()) { - throw new TypeSystemError( - "AnnotatedTypeFactory: invalid qualifier hierarchy: %s %s ", - qualHierarchy.getClass(), qualHierarchy); - } - this.typeHierarchy = createTypeHierarchy(); - this.typeVarSubstitutor = createTypeVariableSubstitutor(); - this.typeArgumentInference = createTypeArgumentInference(); - this.viewpointAdapter = createViewpointAdapter(); - this.qualifierUpperBounds = createQualifierUpperBounds(); - - // TODO: is this the best location for declaring this alias? - addAliasedDeclAnnotation( - org.jmlspecs.annotation.Pure.class, - org.checkerframework.dataflow.qual.Pure.class, - AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class)); - - // Accommodate the inability to write @InheritedAnnotation on these annotations. - addInheritedAnnotation( - AnnotationBuilder.fromClass(elements, org.checkerframework.dataflow.qual.Pure.class)); - addInheritedAnnotation( - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.SideEffectFree.class)); - addInheritedAnnotation( - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.Deterministic.class)); - addInheritedAnnotation( - AnnotationBuilder.fromClass( - elements, org.checkerframework.dataflow.qual.TerminatesExecution.class)); - - initializeReflectionResolution(); - - if (this.getClass() == AnnotatedTypeFactory.class) { - this.parseAnnotationFiles(); - } - TypeMirror iterableTypeMirror = - ElementUtils.getTypeElement(processingEnv, Iterable.class).asType(); - this.iterableDeclType = - (AnnotatedDeclaredType) AnnotatedTypeMirror.createType(iterableTypeMirror, this, true); - } - - /** - * Returns the checker associated with this factory. - * - * @return the checker associated with this factory - */ - public BaseTypeChecker getChecker() { - return checker; - } - - /** - * Returns the names of the annotation processors that are being run. - * - * @return the names of the annotation processors that are being run - */ - @SuppressWarnings("JdkObsolete") // ClassLoader.getResources returns an Enumeration - public List getCheckerNames() { - com.sun.tools.javac.util.Context context = - ((JavacProcessingEnvironment) processingEnv).getContext(); - String processorArg = Options.instance(context).get("-processor"); - if (processorArg != null) { - return SystemUtil.COMMA_SPLITTER.splitToList(processorArg); - } - try { - String filename = "META-INF/services/javax.annotation.processing.Processor"; - List result = new ArrayList<>(); - Enumeration urls = getClass().getClassLoader().getResources(filename); - while (urls.hasMoreElements()) { - URL url = urls.nextElement(); - try (BufferedReader in = - new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { - result.addAll(in.lines().collect(Collectors.toList())); - } - } - return result; - } catch (IOException e) { - throw new BugInCF(e); - } - } - - /** - * Creates {@link QualifierUpperBounds} for this type factory. - * - * @return a new {@link QualifierUpperBounds} for this type factory - */ - protected QualifierUpperBounds createQualifierUpperBounds() { - return new QualifierUpperBounds(this); - } - - /** - * Return {@link QualifierUpperBounds} for this type factory. - * - * @return {@link QualifierUpperBounds} for this type factory - */ - public QualifierUpperBounds getQualifierUpperBounds() { - return qualifierUpperBounds; - } - - /* NO-AFU - * Returns the WholeProgramInference instance (may be null). - * - * @return the WholeProgramInference instance, or null - */ - /* NO-AFU - public @Nullable WholeProgramInference getWholeProgramInference() { - return wholeProgramInference; - } - */ - - /** Initialize reflection resolution. */ - protected void initializeReflectionResolution() { - if (checker.shouldResolveReflection()) { - boolean debug = "debug".equals(checker.getOption("resolveReflection")); - - MethodValChecker methodValChecker = checker.getSubchecker(MethodValChecker.class); - assert methodValChecker != null - : "AnnotatedTypeFactory: reflection resolution was requested," - + " but MethodValChecker isn't a subchecker."; - MethodValAnnotatedTypeFactory methodValATF = - (MethodValAnnotatedTypeFactory) methodValChecker.getAnnotationProvider(); - - reflectionResolver = new DefaultReflectionResolver(checker, methodValATF, debug); - } - } - - /** - * Get the current CompilationUnitTree. - * - * @return the current compilation unit being used, or null - */ - protected @Nullable CompilationUnitTree getRoot() { - return root; - } - - /** - * Set the CompilationUnitTree that should be used. - * - * @param root the new compilation unit to use - */ - public void setRoot(@Nullable CompilationUnitTree root) { - /* NO-AFU - if (root != null && wholeProgramInference != null) { - for (Tree typeDecl : root.getTypeDecls()) { - if (typeDecl.getKind() == Tree.Kind.CLASS) { - ClassTree classTree = (ClassTree) typeDecl; - wholeProgramInference.preprocessClassTree(classTree); - } - } - } - */ + /** Viewpoint adapter used to perform viewpoint adaptation or null */ + protected @Nullable ViewpointAdapter viewpointAdapter; - this.root = root; - // Do not clear here. Only the primary checker should clear this cache. - // treePathCache.clear(); - - if (shouldCache) { - // Clear the caches with trees because once the compilation unit changes, - // the trees may be modified and lose type arguments. - elementToTreeCache.clear(); - fromExpressionTreeCache.clear(); - fromMemberTreeCache.clear(); - fromTypeTreeCache.clear(); - classAndMethodTreeCache.clear(); - - // There is no need to clear the following cache, it is limited by cache size and it - // contents won't change between compilation units. - // elementCache.clear(); - } - - if (root != null && checker.hasOption("ajava")) { - // Search for an ajava file with annotations for the current source file and the current - // checker. It will be in a directory specified by the "ajava" option in a subdirectory - // corresponding to this file's package. For example, a file in package a.b would be in - // a subdirectory a/b. The filename is ClassName-checker.qualified.name.ajava. If such a - // file exists, read its detailed annotation data, including annotations on private - // elements. - - String packagePrefix = - root.getPackageName() != null - ? TreeUtils.nameExpressionToString(root.getPackageName()) + "." - : ""; - - // The method getName() returns a path. - String rootFile = root.getSourceFile().getName(); - String className = rootFile; - // Extract the basename. - int lastSeparator = className.lastIndexOf(File.separator); - if (lastSeparator != -1) { - className = className.substring(lastSeparator + 1); - } - // Drop the ".java" extension. - if (className.endsWith(".java")) { - className = className.substring(0, className.length() - ".java".length()); - } + /** + * This formatter is used for converting AnnotatedTypeMirrors to Strings. This formatter will be + * used by all AnnotatedTypeMirrors created by this factory in their toString methods. + */ + protected final AnnotatedTypeFormatter typeFormatter; - String qualifiedName = packagePrefix + className; - - // If the set candidateAjavaFiles has exactly one element after the loop, a specific - // .ajava file was supplied, with no ambiguity, and can be parsed. For an explanation, - // see the comment below about possible ambiguity. - Set candidateAjavaFiles = new HashSet<>(1); - // All .ajava files for this class + checker combo end in this string. - String ajavaEnding = - qualifiedName.replaceAll("\\.", "/") - + "-" - + checker.getClass().getCanonicalName() - + ".ajava"; - for (String ajavaLocation : checker.getStringsOption("ajava", File.pathSeparator)) { - // ajavaLocation might either be (1) a directory, or (2) the name of a specific - // ajava file. This code must handle both possible cases. - // Case (1): ajavaPath is a directory - String ajavaPath = ajavaLocation + File.separator + ajavaEnding; - File ajavaFileInDir = new File(ajavaPath); - if (ajavaFileInDir.exists()) { - // There is a candidate ajava file in one of the root directories. - candidateAjavaFiles.add(ajavaPath); - } else { - // Check case (2): ajavaPath might be a specific .ajava file. The tricky thing - // about this is that the "root" is not known, so the correct .ajava file might - // be ambiguous. Consider the following: there are two ajava files: - // ~/foo/foo/Bar-checker.ajava and ~/baz/foo/Bar-checker.ajava. Which is the - // correct one for class foo.Bar? It depends on whether there is a foo.foo.Bar - // or a baz.foo.Bar elsewhere in the project. For that reason, parsing using a - // specific file is done at the **end** of the loop, and if there is more than - // one match no file is parsed for this class and a warning is issued instead. - // The user can disambiguate by supplying a root directory, instead of specific - // files. - if (ajavaLocation.endsWith(File.separator + ajavaEnding)) { - // This is a candidate ajava file. If it is the only candidate, then it - // might be unambiguous. If not, issue a warning. - candidateAjavaFiles.add(ajavaLocation); - } - } - } - if (candidateAjavaFiles.size() == 1) { - currentFileAjavaTypes = new AnnotationFileElementTypes(this); - String ajavaPath = candidateAjavaFiles.toArray(new String[0])[0]; - try { - currentFileAjavaTypes.parseAjavaFileWithTree(ajavaPath, root); - } catch (Throwable e) { - throw new Error( - "Problem while parsing " + ajavaPath + " that corresponds to " + rootFile, e); - } - } else if (candidateAjavaFiles.size() > 1) { - checker.reportWarning(root, "ambiguous.ajava", String.join(", ", candidateAjavaFiles)); - } - } else { - currentFileAjavaTypes = null; - } - } - - @SideEffectFree - @Override - public String toString() { - return getClass().getSimpleName() + "#" + uid; - } - - /** - * Returns the {@link QualifierHierarchy} to be used by this checker. - * - *

          The implementation builds the type qualifier hierarchy for the {@link - * #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current - * implementation returns an instance of {@code NoElementQualifierHierarchy}. - * - *

          Subclasses must override this method if their qualifiers have elements; the method must - * return an implementation of {@link QualifierHierarchy}, such as {@link - * ElementQualifierHierarchy}. - * - * @return a QualifierHierarchy for this type system - */ - protected QualifierHierarchy createQualifierHierarchy() { - return new NoElementQualifierHierarchy( - this.getSupportedTypeQualifiers(), - elements, - (GenericAnnotatedTypeFactory) this); - } - - /** - * Returns the type qualifier hierarchy graph to be used by this processor. - * - * @see #createQualifierHierarchy() - * @return the {@link QualifierHierarchy} for this checker - */ - public final QualifierHierarchy getQualifierHierarchy() { - return qualHierarchy; - } - - /** - * Returns true if the given qualifer is one of the top annotations for the qualifer hierarchy. - * - * @param qualifier a type qualifier - * @return true if the given qualifer is one of the top annotations for the qualifer hierarchy - */ - public final boolean isTop(AnnotationMirror qualifier) { - return qualHierarchy.isTop(qualifier); - } - - /** - * Creates the type hierarchy to be used by this factory. - * - *

          Subclasses may override this method to specify new type-checking rules beyond the typical - * Java subtyping rules. - * - * @return the type relations class to check type subtyping - */ - protected TypeHierarchy createTypeHierarchy() { - return new DefaultTypeHierarchy( - checker, - getQualifierHierarchy(), - ignoreRawTypeArguments, - checker.hasOption("invariantArrays")); - } - - public final TypeHierarchy getTypeHierarchy() { - return typeHierarchy; - } - - /** - * Factory method to create a ViewpointAdapter. Subclasses should implement and instantiate a - * ViewpointAdapter subclass if viewpoint adaptation is needed for a type system. - * - * @return viewpoint adapter to perform viewpoint adaptation or null - */ - protected @Nullable ViewpointAdapter createViewpointAdapter() { - return null; - } - - /** TypeVariableSubstitutor provides a method to replace type parameters with their arguments. */ - protected TypeVariableSubstitutor createTypeVariableSubstitutor() { - return new TypeVariableSubstitutor(); - } - - public TypeVariableSubstitutor getTypeVarSubstitutor() { - return typeVarSubstitutor; - } - - /** - * Creates the object that infers type arguments. - * - * @return the object that infers type arguments - */ - protected TypeArgumentInference createTypeArgumentInference() { - return new DefaultTypeArgumentInference(); - } - - public TypeArgumentInference getTypeArgumentInference() { - return typeArgumentInference; - } - - /** - * Factory method to easily change what {@link AnnotationClassLoader} is created to load type - * annotation classes. Subclasses can override this method and return a custom - * AnnotationClassLoader subclass to customize loading logic. - */ - protected AnnotationClassLoader createAnnotationClassLoader() { - return new AnnotationClassLoader(checker); - } - - /** - * Returns a mutable set of annotation classes that are supported by a checker. - * - *

          Subclasses may override this method to return a mutable set of their supported type - * qualifiers through one of the 5 approaches shown below. - * - *

          Subclasses should not call this method; they should call {@link #getSupportedTypeQualifiers} - * instead. - * - *

          By default, a checker supports all annotations located in a subdirectory called {@literal - * qual} that's located in the same directory as the checker. Note that only annotations defined - * with the {@code @Target({ElementType.TYPE_USE})} meta-annotation (and optionally with the - * additional value of {@code ElementType.TYPE_PARAMETER}, but no other {@code ElementType} - * values) are automatically considered as supported annotations. - * - *

          To support a different set of annotations than those in the {@literal qual} subdirectory, or - * that have other {@code ElementType} values, see examples below. - * - *

          In total, there are 5 ways to indicate annotations that are supported by a checker: - * - *

            - *
          1. Only support annotations located in a checker's {@literal qual} directory: - *

            This is the default behavior. Simply place those annotations within the {@literal - * qual} directory. - *

          2. Support annotations located in a checker's {@literal qual} directory and a list of other - * annotations: - *

            Place those annotations within the {@literal qual} directory, and override {@link - * #createSupportedTypeQualifiers()} by calling {@link #getBundledTypeQualifiers(Class...)} - * with a varargs parameter list of the other annotations. Code example: - *

            -   * {@code @Override protected Set> createSupportedTypeQualifiers() {
            -   *      return getBundledTypeQualifiers(Regex.class, PartialRegex.class, RegexBottom.class, UnknownRegex.class);
            -   *  } }
            -   * 
            - *
          3. Supporting only annotations that are explicitly listed: Override {@link - * #createSupportedTypeQualifiers()} and return a mutable set of the supported annotations. - * Code example: - *
            -   * {@code @Override protected Set> createSupportedTypeQualifiers() {
            -   *      return new HashSet>(
            -   *              Arrays.asList(A.class, B.class));
            -   *  } }
            -   * 
            - * The set of qualifiers returned by {@link #createSupportedTypeQualifiers()} must be a - * fresh, mutable set. The methods {@link #getBundledTypeQualifiers(Class...)} must return a - * fresh, mutable set - *
          - * - * @return the type qualifiers supported this processor, or an empty set if none - */ - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers(); - } - - /** - * Loads all annotations contained in the qual directory of a checker via reflection; if a - * polymorphic type qualifier exists, and an explicit array of annotations to the set of - * annotation classes. - * - *

          This method can be called in the overridden versions of {@link - * #createSupportedTypeQualifiers()} in each checker. - * - * @param explicitlyListedAnnotations a varargs array of explicitly listed annotation classes to - * be added to the returned set. For example, it is used frequently to add Bottom qualifiers. - * @return a mutable set of the loaded and listed annotation classes - */ - @SafeVarargs - protected final Set> getBundledTypeQualifiers( - Class... explicitlyListedAnnotations) { - return loadTypeAnnotationsFromQualDir(explicitlyListedAnnotations); - } - - /** - * Instantiates the AnnotationClassLoader and loads all annotations contained in the qual - * directory of a checker via reflection, and has the option to include an explicitly stated list - * of annotations (eg ones found in a different directory than qual). - * - *

          The annotations that are automatically loaded must have the {@link - * java.lang.annotation.Target Target} meta-annotation with the value of {@link - * ElementType#TYPE_USE} (and optionally {@link ElementType#TYPE_PARAMETER}). If it has other - * {@link ElementType} values, it won't be loaded. Other annotation classes must be explicitly - * listed even if they are in the same directory as the checker's qual directory. - * - * @param explicitlyListedAnnotations a set of explicitly listed annotation classes to be added to - * the returned set, for example, it is used frequently to add Bottom qualifiers - * @return a set of annotation class instances - */ - @SafeVarargs - @SuppressWarnings("varargs") - private final Set> loadTypeAnnotationsFromQualDir( - Class... explicitlyListedAnnotations) { - if (loader != null) { - loader.close(); - } - loader = createAnnotationClassLoader(); - - Set> annotations = loader.getBundledAnnotationClasses(); - - // add in all explicitly Listed qualifiers - if (explicitlyListedAnnotations != null) { - annotations.addAll(Arrays.asList(explicitlyListedAnnotations)); - } - - return annotations; - } - - /** - * Creates the {@link AnnotatedTypeFormatter} used by this type factory and all {@link - * AnnotatedTypeMirror}s it creates. The {@link AnnotatedTypeFormatter} is used in {@link - * AnnotatedTypeMirror#toString()} and will affect the error messages printed for checkers that - * use this type factory. - * - * @return the {@link AnnotatedTypeFormatter} to pass to all {@link AnnotatedTypeMirror}s - */ - protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { - boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); - return new DefaultAnnotatedTypeFormatter( - printVerboseGenerics, - // -AprintVerboseGenerics implies -AprintAllQualifiers - printVerboseGenerics || checker.hasOption("printAllQualifiers")); - } - - /** - * Return the current {@link AnnotatedTypeFormatter}. - * - * @return the current {@link AnnotatedTypeFormatter} - */ - public AnnotatedTypeFormatter getAnnotatedTypeFormatter() { - return typeFormatter; - } - - /** - * Creates the {@link AnnotationFormatter} used by this type factory. - * - * @return the {@link AnnotationFormatter} used by this type factory - */ - protected AnnotationFormatter createAnnotationFormatter() { - return new DefaultAnnotationFormatter(); - } - - /** - * Return the current {@link AnnotationFormatter}. - * - * @return the current {@link AnnotationFormatter} - */ - public AnnotationFormatter getAnnotationFormatter() { - return annotationFormatter; - } - - /** - * Creates the {@link TypeInformationPresenter} used in {@link #postProcessClassTree(ClassTree)} - * to output type information about the current class. - * - * @return the {@link TypeInformationPresenter} used by this type factory, or null - */ - protected @Nullable TypeInformationPresenter createTypeInformationPresenter() { - // TODO: look into a similar mechanism as for CFG visualization. - if (checker.hasOption("lspTypeInfo")) { - return new LspTypeInformationPresenter(this); - } else { - return null; - } - } - - /** - * Returns an immutable set of the classes corresponding to the type qualifiers supported by this - * checker. - * - *

          Subclasses cannot override this method; they should override {@link - * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. - * - * @see #createSupportedTypeQualifiers() - * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are - * supported - */ - public final Set> getSupportedTypeQualifiers() { - if (this.supportedQuals == null) { - supportedQuals = createSupportedTypeQualifiers(); - checkSupportedQualsAreTypeQuals(); - } - return supportedQuals; - } - - /** - * Returns an immutable set of the fully qualified names of the type qualifiers supported by this - * checker. - * - *

          Subclasses cannot override this method; they should override {@link - * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. - * - * @see #createSupportedTypeQualifiers() - * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers are - * supported - */ - public final Set<@CanonicalName String> getSupportedTypeQualifierNames() { - if (this.supportedQualNames == null) { - supportedQualNames = new HashSet<>(); - for (Class clazz : getSupportedTypeQualifiers()) { - supportedQualNames.add(clazz.getCanonicalName()); - } - supportedQualNames = Collections.unmodifiableSet(supportedQualNames); - } - return supportedQualNames; - } - - // ********************************************************************** - // Factories for annotated types that account for default qualifiers - // ********************************************************************** - - /** - * Returns the size for LRU caches. It is either the value supplied via the {@code -AatfCacheSize} - * option or the default cache size. - * - * @return cache size passed as argument to checker or DEFAULT_CACHE_SIZE - */ - protected int getCacheSize() { - String option = checker.getOption("atfCacheSize"); - if (option == null) { - return DEFAULT_CACHE_SIZE; - } - try { - return Integer.valueOf(option); - } catch (NumberFormatException ex) { - throw new UserError("atfCacheSize was not an integer: " + option); - } - } - - /** - * Returns an AnnotatedTypeMirror representing the annotated type of {@code elt}. - * - * @param elt the element - * @return the annotated type of {@code elt} - */ - public AnnotatedTypeMirror getAnnotatedType(Element elt) { - if (elt == null) { - throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null element"); - } - // Annotations explicitly written in the source code, - // or obtained from bytecode. - AnnotatedTypeMirror type = fromElement(elt); - addComputedTypeAnnotations(elt, type); - return type; - } - - /** - * Returns an AnnotatedTypeMirror representing the annotated type of {@code clazz}. - * - * @param clazz a class - * @return the annotated type of {@code clazz} - */ - public AnnotatedTypeMirror getAnnotatedType(Class clazz) { - return getAnnotatedType(elements.getTypeElement(clazz.getCanonicalName())); - } - - @Override - public @Nullable AnnotationMirror getAnnotationMirror( - Tree tree, Class target) { - if (isSupportedQualifier(target)) { - AnnotatedTypeMirror atm = getAnnotatedType(tree); - return atm.getAnnotation(target); - } - return null; - } - - /** - * Returns an AnnotatedTypeMirror representing the annotated type of {@code tree}. - * - * @param tree the AST node - * @return the annotated type of {@code tree} - */ - public AnnotatedTypeMirror getAnnotatedType(Tree tree) { - if (tree == null) { - throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null tree"); - } - if (shouldCache && classAndMethodTreeCache.containsKey(tree)) { - return classAndMethodTreeCache.get(tree).deepCopy(); - } - - AnnotatedTypeMirror type; - if (TreeUtils.isClassTree(tree)) { - type = fromClass((ClassTree) tree); - } else if (tree.getKind() == Tree.Kind.METHOD || tree.getKind() == Tree.Kind.VARIABLE) { - type = fromMember(tree); - } else if (TreeUtils.isExpressionTree(tree)) { - tree = TreeUtils.withoutParens((ExpressionTree) tree); - type = fromExpression((ExpressionTree) tree); - } else { - throw new BugInCF( - "AnnotatedTypeFactory.getAnnotatedType: query of annotated type for tree " - + tree.getKind()); - } - - addComputedTypeAnnotations(tree, type); - if (tree.getKind() == Kind.TYPE_CAST) { - type = applyCaptureConversion(type); - } - - if (shouldCache && (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD)) { - // Don't cache VARIABLE - classAndMethodTreeCache.put(tree, type.deepCopy()); - } else { - // No caching otherwise - } - - return type; - } - - /** - * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} before the classTree is type - * checked. - * - * @param classTree the class on which to perform preprocessing - */ - public void preProcessClassTree(ClassTree classTree) {} - - /** - * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} after the ClassTree has been type - * checked. - * - *

          The default implementation uses this to store the defaulted AnnotatedTypeMirrors and - * inherited declaration annotations back into the corresponding Elements. Subclasses might want - * to override this method if storing defaulted types is not desirable. - */ - public void postProcessClassTree(ClassTree tree) { - TypesIntoElements.store(processingEnv, this, tree); - DeclarationsIntoElements.store(processingEnv, this, tree); - - if (typeInformationPresenter != null) { - typeInformationPresenter.process(tree, getPath(tree)); - } + /** + * Annotation formatter is used to format AnnotationMirrors. It is primarily used by + * SourceChecker when generating error messages. + */ + private final AnnotationFormatter annotationFormatter; - /* NO-AFU - if (wholeProgramInference != null) { - // Write out the results of whole-program inference, just once for each class. As soon - // as any class is finished processing, all modified scenes are written to files, in - // case this was the last class to be processed. Post-processing of subsequent classes - // might result in re-writing some of the scenes if new information has been written to - // them. - wholeProgramInference.writeResultsToFile(wpiOutputFormat, this.checker); - } - */ - } - - /** - * Determines the annotated type from a type in tree form. - * - *

          Note that we cannot decide from a Tree whether it is a type use or an expression. - * TreeUtils.isTypeTree is only an under-approximation. For example, an identifier can be either a - * type or an expression. - * - * @param tree the type tree - * @return the annotated type of the type in the AST - */ - public AnnotatedTypeMirror getAnnotatedTypeFromTypeTree(Tree tree) { - if (tree == null) { - throw new BugInCF("AnnotatedTypeFactory.getAnnotatedTypeFromTypeTree: null tree"); - } - AnnotatedTypeMirror type = fromTypeTree(tree); - addComputedTypeAnnotations(tree, type); - return type; - } - - /** - * Returns the set of qualifiers that are the upper bounds for a use of the type. - * - *

          For a specific type system, the type declaration bound is retrieved in the following - * precedence: (1) the annotation on the type declaration bound (2) if an annotation with - * {@code @UpperBoundFor} mentions the type or the type kind, use that annotation (3) the top - * annotation - * - * @param type a type whose upper bounds to obtain - * @return the set of qualifiers that are the upper bounds for a use of the type - */ - public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { - return qualifierUpperBounds.getBoundQualifiers(type); - } - - /** - * Compare the given {@code annos} with the declaration bounds of {@code type} and return the - * appropriate qualifiers. For each qualifier in {@code annos}, if it is a subtype of the - * declaration bound in the same hierarchy, it will be added to the result; otherwise, the - * declaration bound will be added to the result instead. - * - * @param type java type that specifies the qualifier upper bound - * @param annos a set of qualifiers to be compared with the declaration bounds of {@code type} - * @return the modified {@code annos} after applying the rules described above - */ - public AnnotationMirrorSet getAnnotationOrTypeDeclarationBound( - TypeMirror type, Set annos) { - AnnotationMirrorSet boundAnnos = getTypeDeclarationBounds(type); - AnnotationMirrorSet results = new AnnotationMirrorSet(); - - for (AnnotationMirror anno : annos) { - AnnotationMirror boundAnno = qualHierarchy.findAnnotationInSameHierarchy(boundAnnos, anno); - assert boundAnno != null; - - if (!qualHierarchy.isSubtypeQualifiersOnly(anno, boundAnno)) { - results.add(boundAnno); - } else { - results.add(anno); - } - } - return results; - } - - /** - * Returns the set of qualifiers that are the upper bound for a type use if no other bound is - * specified for the type. - * - *

          This implementation returns the top qualifiers by default. Subclass may override to return - * different qualifiers. - * - * @return the set of qualifiers that are the upper bound for a type use if no other bound is - * specified for the type - */ - protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { - return qualHierarchy.getTopAnnotations(); - } - - /** - * Returns the type of the extends or implements clause. - * - *

          The primary qualifier is either an explicit annotation on {@code clause}, or it is the - * qualifier upper bounds for uses of the type of the clause. - * - * @param clause tree that represents an extends or implements clause - * @return the type of the extends or implements clause - */ - public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { - AnnotatedTypeMirror fromTypeTree = fromTypeTree(clause); - AnnotationMirrorSet bound = getTypeDeclarationBounds(fromTypeTree.getUnderlyingType()); - fromTypeTree.addMissingAnnotations(bound); - addComputedTypeAnnotations(clause, fromTypeTree); - return fromTypeTree; - } - - // ********************************************************************** - // Factories for annotated types that do not account for default qualifiers. - // They only include qualifiers explicitly inserted by the user. - // ********************************************************************** - - /** - * Creates an AnnotatedTypeMirror for {@code elt} that includes: annotations explicitly written on - * the element and annotations from stub files. - * - *

          Does not include default qualifiers. To obtain them, use {@link #getAnnotatedType(Element)}. - * - *

          Does not include fake overrides from the stub file. - * - * @param elt the element - * @return AnnotatedTypeMirror of the element with explicitly-written and stub file annotations - */ - public AnnotatedTypeMirror fromElement(Element elt) { - if (shouldCache && elementCache.containsKey(elt)) { - return elementCache.get(elt).deepCopy(); - } - if (elt.getKind() == ElementKind.PACKAGE) { - return toAnnotatedType(elt.asType(), false); - } - AnnotatedTypeMirror type; - - // Because of a bug in Java 8, annotations on type parameters are not stored in elements, so - // get explicit annotations from the tree. (This bug has been fixed in Java 9.) Also, since - // annotations computed by the AnnotatedTypeFactory are stored in the element, the - // annotations have to be retrieved from the tree so that only explicit annotations are - // returned. - Tree decl = declarationFromElement(elt); - - if (decl == null) { - type = stubTypes.getAnnotatedTypeMirror(elt); - if (type == null) { - type = toAnnotatedType(elt.asType(), ElementUtils.isTypeDeclaration(elt)); - ElementAnnotationApplier.apply(type, elt, this); - } - } else if (decl instanceof ClassTree) { - type = fromClass((ClassTree) decl); - } else if (decl instanceof VariableTree) { - type = fromMember(decl); - } else if (decl instanceof MethodTree) { - type = fromMember(decl); - } else if (decl.getKind() == Tree.Kind.TYPE_PARAMETER) { - type = fromTypeTree(decl); - } else { - throw new BugInCF( - "AnnotatedTypeFactory.fromElement: cannot be here. decl: " - + decl.getKind() - + " elt: " - + elt); - } - - type = mergeAnnotationFileAnnosIntoType(type, elt, ajavaTypes); - if (currentFileAjavaTypes != null) { - type = mergeAnnotationFileAnnosIntoType(type, elt, currentFileAjavaTypes); - } - - if (mergeStubsWithSource) { - if (debugStubParser) { - System.out.printf("fromElement: mergeStubsIntoType(%s, %s)", type, elt); - } - type = mergeAnnotationFileAnnosIntoType(type, elt, stubTypes); - if (debugStubParser) { - System.out.printf(" => %s%n", type); - } - } - // Caching is disabled if annotation files are being parsed, because calls to this - // method before the annotation files are fully read can return incorrect results. - if (shouldCache - && !stubTypes.isParsing() - && !ajavaTypes.isParsing() - && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { - elementCache.put(elt, type.deepCopy()); - } - return type; - } - - /** - * Returns an AnnotatedDeclaredType with explicit annotations from the ClassTree {@code tree}. - * - * @param tree the class declaration - * @return AnnotatedDeclaredType with explicit annotations from {@code tree} - */ - private AnnotatedDeclaredType fromClass(ClassTree tree) { - return TypeFromTree.fromClassTree(this, tree); - } - - /** - * Creates an AnnotatedTypeMirror for a variable or method declaration tree. The - * AnnotatedTypeMirror contains annotations explicitly written on the tree, and possibly others as - * described below. - * - *

          If a VariableTree is a parameter to a lambda, this method also adds annotations from the - * declared type of the functional interface and the executable type of its method. - * - *

          The returned AnnotatedTypeMirror also contains explicitly written annotations from any ajava - * file and if {@code -AmergeStubsWithSource} is passed, it also merges any explicitly written - * annotations from stub files. - * - * @param tree a {@link MethodTree} or {@link VariableTree} - * @return AnnotatedTypeMirror with explicit annotations from {@code tree} - */ - private AnnotatedTypeMirror fromMember(Tree tree) { - if (!(tree instanceof MethodTree || tree instanceof VariableTree)) { - throw new BugInCF( - "AnnotatedTypeFactory.fromMember: not a method or variable declaration: " + tree); - } - if (shouldCache && fromMemberTreeCache.containsKey(tree)) { - return fromMemberTreeCache.get(tree).deepCopy(); - } - AnnotatedTypeMirror result = TypeFromTree.fromMember(this, tree); - - result = mergeAnnotationFileAnnosIntoType(result, tree, ajavaTypes); - if (currentFileAjavaTypes != null) { - result = mergeAnnotationFileAnnosIntoType(result, tree, currentFileAjavaTypes); - } - - if (mergeStubsWithSource) { - if (debugStubParser) { - System.out.printf("fromClass: mergeStubsIntoType(%s, %s)", result, tree); - } - result = mergeAnnotationFileAnnosIntoType(result, tree, stubTypes); - if (debugStubParser) { - System.out.printf(" => %s%n", result); - } - } + /** Holds the qualifier upper bounds for type uses. */ + protected QualifierUpperBounds qualifierUpperBounds; - if (shouldCache) { - fromMemberTreeCache.put(tree, result.deepCopy()); - } - - return result; - } - - /** - * Merges types from annotation files for {@code tree} into {@code type} by taking the greatest - * lower bound of the annotations in both. - * - * @param type the type to apply annotation file types to - * @param tree the tree from which to read annotation file types - * @param source storage for current annotation file annotations - * @return the given type, side-effected to add the annotation file types - */ - private AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( - @Nullable AnnotatedTypeMirror type, Tree tree, AnnotationFileElementTypes source) { - Element elt = TreeUtils.elementFromTree(tree); - return mergeAnnotationFileAnnosIntoType(type, elt, source); - } - - /** - * A scanner used to combine annotations from two AnnotatedTypeMirrors. The scanner requires - * {@link #qualHierarchy}, which is set in {@link #postInit()} rather than the construtor, so - * lazily initialize this field before use. - */ - private @MonotonicNonNull AnnotatedTypeCombiner annotatedTypeCombiner = null; - - /** - * Merges types from annotation files for {@code elt} into {@code type} by taking the greatest - * lower bound of the annotations in both. - * - * @param type the type to apply annotation file types to - * @param elt the element from which to read annotation file types - * @param source storage for current annotation file annotations - * @return the type, side-effected to add the annotation file types - */ - protected AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( - @Nullable AnnotatedTypeMirror type, Element elt, AnnotationFileElementTypes source) { - AnnotatedTypeMirror typeFromFile = source.getAnnotatedTypeMirror(elt); - if (typeFromFile == null) { - return type; - } - if (type == null) { - return typeFromFile; - } - if (annotatedTypeCombiner == null) { - annotatedTypeCombiner = new AnnotatedTypeCombiner(qualHierarchy); - } - // Must merge (rather than only take the annotation file type if it is a subtype) to support - // WPI. - annotatedTypeCombiner.visit(typeFromFile, type); - return type; - } - - /** - * Creates an AnnotatedTypeMirror for an ExpressionTree. The AnnotatedTypeMirror contains explicit - * annotations written on the expression and for some expressions, annotations from - * sub-expressions that could have been explicitly written, defaulted, refined, or otherwise - * computed. (Expression whose type include annotations from sub-expressions are: ArrayAccessTree, - * ConditionalExpressionTree, IdentifierTree, MemberSelectTree, and MethodInvocationTree.) - * - *

          For example, the AnnotatedTypeMirror returned for an array access expression is the fully - * annotated type of the array component of the array being accessed. - * - * @param tree an expression - * @return AnnotatedTypeMirror of the expressions either fully-annotated or partially annotated - * depending on the kind of expression - * @see TypeFromExpressionVisitor - */ - private AnnotatedTypeMirror fromExpression(ExpressionTree tree) { - if (shouldCache && fromExpressionTreeCache.containsKey(tree)) { - return fromExpressionTreeCache.get(tree).deepCopy(); - } - - AnnotatedTypeMirror result = TypeFromTree.fromExpression(this, tree); - - if (shouldCache - && tree.getKind() != Tree.Kind.NEW_CLASS - && tree.getKind() != Tree.Kind.NEW_ARRAY - && tree.getKind() != Tree.Kind.CONDITIONAL_EXPRESSION) { - // Don't cache the type of some expressions, because incorrect annotations would be - // cached during dataflow analysis. See Issue #602. - fromExpressionTreeCache.put(tree, result.deepCopy()); - } - return result; - } - - /** - * Creates an AnnotatedTypeMirror for the tree. The AnnotatedTypeMirror contains annotations - * explicitly written on the tree. It also adds type arguments to raw types that include - * annotations from the element declaration of the type {@link #fromElement(Element)}. - * - *

          Called on the following trees: AnnotatedTypeTree, ArrayTypeTree, ParameterizedTypeTree, - * PrimitiveTypeTree, TypeParameterTree, WildcardTree, UnionType, IntersectionTypeTree, and - * IdentifierTree, MemberSelectTree. - * - * @param tree the type tree - * @return the (partially) annotated type of the type in the AST - */ - /*package-private*/ final AnnotatedTypeMirror fromTypeTree(Tree tree) { - if (shouldCache && fromTypeTreeCache.containsKey(tree)) { - return fromTypeTreeCache.get(tree).deepCopy(); - } - - AnnotatedTypeMirror result = TypeFromTree.fromTypeTree(this, tree); - - if (shouldCache) { - fromTypeTreeCache.put(tree, result.deepCopy()); - } - return result; - } - - // ********************************************************************** - // Customization methods meant to be overridden by subclasses to include - // defaulted annotations - // ********************************************************************** - - /** - * Changes annotations on a type obtained from a {@link Tree}. By default, this method does - * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting and inference - * (flow-sensitive type refinement). Its subclasses usually override it only to customize default - * annotations. - * - *

          Subclasses that override this method should also override {@link - * #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)}. - * - * @param tree an AST node - * @param type the type obtained from {@code tree} - */ - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - // Pass. - } - - /** - * Changes annotations on a type obtained from an {@link Element}. By default, this method does - * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting. - * - *

          Subclasses that override this method should also override {@link - * #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. - * - * @param elt an element - * @param type the type obtained from {@code elt} - */ - protected void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - // Pass. - } - - /** - * Adds default annotations to {@code type}. This method should only be used in places where the - * correct annotations cannot be computed because of type argument of raw types. (See {@link - * AnnotatedWildcardType#isTypeArgOfRawType()}.) - * - * @param type annotated type to which default annotations are added - */ - public void addDefaultAnnotations(AnnotatedTypeMirror type) { - // Pass. - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize directSupertypes(). - * Overriding methods should merely change the annotations on the supertypes, without adding or - * removing new types. - * - *

          The default provided implementation adds {@code type} annotations to {@code supertypes}. - * This allows the {@code type} and its supertypes to have the qualifiers. - * - * @param type the type whose supertypes are desired - * @param supertypes the supertypes as specified by the base AnnotatedTypeFactory - */ - protected void postDirectSuperTypes( - AnnotatedTypeMirror type, List supertypes) { - // Use the effective annotations here to get the correct annotations - // for type variables and wildcards. - AnnotationMirrorSet annotations = type.getEffectiveAnnotations(); - for (AnnotatedTypeMirror supertype : supertypes) { - if (!annotations.equals(supertype.getEffectiveAnnotations())) { - supertype.clearAnnotations(); - // TODO: is this correct for type variables and wildcards? - supertype.addAnnotations(annotations); - } - } - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize - * AnnotatedTypes.asMemberOf(). Overriding methods should merely change the annotations on the - * subtypes, without changing the types. - * - * @param type the annotated type of the element - * @param owner the annotated type of the receiver of the accessing tree - * @param element the element of the field or method - */ - public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { - if (element.getKind() == ElementKind.FIELD) { - addAnnotationFromFieldInvariant(type, owner, (VariableElement) element); - } - addComputedTypeAnnotations(element, type); - - if (viewpointAdapter != null && type.getKind() != TypeKind.EXECUTABLE) { - viewpointAdapter.viewpointAdaptMember(owner, element, type); - } - } - - /** - * Adds the qualifier specified by a field invariant for {@code field} to {@code type}. - * - * @param type annotated type to which the annotation is added - * @param accessedVia the annotated type of the receiver of the accessing tree. (Only used to get - * the type element of the underling type.) - * @param field element representing the field - */ - protected void addAnnotationFromFieldInvariant( - AnnotatedTypeMirror type, AnnotatedTypeMirror accessedVia, VariableElement field) { - TypeMirror declaringType = accessedVia.getUnderlyingType(); - // Find the first upper bound that isn't a wildcard or type variable - while (declaringType.getKind() == TypeKind.WILDCARD - || declaringType.getKind() == TypeKind.TYPEVAR) { - if (declaringType.getKind() == TypeKind.WILDCARD) { - declaringType = TypesUtils.wildUpperBound(declaringType, processingEnv); - } else if (declaringType.getKind() == TypeKind.TYPEVAR) { - declaringType = ((TypeVariable) declaringType).getUpperBound(); - } - } - TypeElement typeElement = TypesUtils.getTypeElement(declaringType); - if (ElementUtils.enclosingTypeElement(field).equals(typeElement)) { - // If the field is declared in the accessedVia class, then the field in the invariant - // cannot be this field, even if the field has the same name. - return; - } - - FieldInvariants invariants = getFieldInvariants(typeElement); - if (invariants == null) { - return; - } - List invariantAnnos = invariants.getQualifiersFor(field.getSimpleName()); - type.replaceAnnotations(invariantAnnos); - } - - /** - * Returns the field invariants for the given class, as expressed by the user in {@link - * FieldInvariant @FieldInvariant} method annotations. - * - *

          Subclasses may implement their own field invariant annotations if {@link - * FieldInvariant @FieldInvariant} is not expressive enough. They must override this method to - * properly create AnnotationMirror and also override {@link - * #getFieldInvariantDeclarationAnnotations()} to return their field invariants. - * - * @param element class for which to get invariants - * @return field invariants for {@code element} - */ - public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { - if (element == null) { - return null; - } - AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, FieldInvariant.class); - if (fieldInvarAnno == null) { - return null; - } - List fields = - AnnotationUtils.getElementValueArray( - fieldInvarAnno, fieldInvariantFieldElement, String.class); - List<@CanonicalName Name> classes = - AnnotationUtils.getElementValueClassNames(fieldInvarAnno, fieldInvariantQualifierElement); - List qualifiers = - CollectionsPlume.mapList( - name -> - // Calling AnnotationBuilder.fromName (which ignores - // elements/fields) is acceptable because @FieldInvariant - // does not handle classes with elements/fields. - AnnotationBuilder.fromName(elements, name), - classes); - if (qualifiers.size() == 1) { - while (fields.size() > qualifiers.size()) { - qualifiers.add(qualifiers.get(0)); - } - } - if (fields.size() != qualifiers.size()) { - // The user wrote a malformed @FieldInvariant annotation, so just return a malformed - // FieldInvariants object. The BaseTypeVisitor will issue an error. - return new FieldInvariants(fields, qualifiers, this); - } + /** + * Provides utility method to substitute arguments for their type variables. Field should be + * final, but can only be set in postInit, because subtypes might need other state to be + * initialized first. + */ + protected TypeVariableSubstitutor typeVarSubstitutor; - // Only keep qualifiers that are supported by this checker. (The other qualifiers cannot - // be checked by this checker, so they must be ignored.) - List annotatedFields = new ArrayList<>(); - List supportedQualifiers = new ArrayList<>(); - for (int i = 0; i < fields.size(); i++) { - if (isSupportedQualifier(qualifiers.get(i))) { - annotatedFields.add(fields.get(i)); - supportedQualifiers.add(qualifiers.get(i)); - } - } - if (annotatedFields.isEmpty()) { - return null; - } - - return new FieldInvariants(annotatedFields, supportedQualifiers, this); - } - - /** - * Returns the element of {@code annoTrees} that is a use of one of the field invariant - * annotations (as specified via {@link #getFieldInvariantDeclarationAnnotations()}. If one isn't - * found, null is returned. - * - * @param annoTrees list of trees to search; the result is one of the list elements, or null - * @return the AnnotationTree that is a use of one of the field invariant annotations, or null if - * one isn't found - */ - public @Nullable AnnotationTree getFieldInvariantAnnotationTree( - @Nullable List annoTrees) { - List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); - for (int i = 0; i < annos.size(); i++) { - for (Class clazz : getFieldInvariantDeclarationAnnotations()) { - if (areSameByClass(annos.get(i), clazz)) { - return annoTrees.get(i); - } - } - } - return null; - } - - /** The classes of field invariant annotations. */ - private final Set> fieldInvariantDeclarationAnnotations = - Collections.singleton(FieldInvariant.class); - - /** - * Returns the set of classes of field invariant annotations. - * - * @return the set of classes of field invariant annotations - */ - protected Set> getFieldInvariantDeclarationAnnotations() { - return fieldInvariantDeclarationAnnotations; - } - - /** - * Adapt the upper bounds of the type variables of a class relative to the type instantiation. In - * some type systems, the upper bounds depend on the instantiation of the class. For example, in - * the Generic Universe Type system, consider a class declaration - * - *

          {@code   class C }
          - * - * then the instantiation - * - *
          {@code   @Rep C<@Rep Object> }
          - * - * is legal. The upper bounds of class C have to be adapted by the main modifier. - * - *

          An example of an adaptation follows. Suppose, I have a declaration: - * - *

          {@code  class MyClass>}
          - * - * And an instantiation: - * - *
          {@code  new MyClass<@NonNull String>()}
          - * - *

          The upper bound of E adapted to the argument String, would be {@code List<@NonNull String>} - * and the lower bound would be an AnnotatedNullType. - * - *

          TODO: ensure that this method is consistently used instead of directly querying the type - * variables. - * - * @param type the use of the type - * @param element the corresponding element - * @return the adapted bounds of the type parameters - */ - public List typeVariablesFromUse( - AnnotatedDeclaredType type, TypeElement element) { - AnnotatedDeclaredType generic = getAnnotatedType(element); - List targs = type.getTypeArguments(); - List tvars = generic.getTypeArguments(); - - assert targs.size() == tvars.size() - : "Mismatch in type argument size between " + type + " and " + generic; - - // System.err.printf("TVFU%n type: %s%n generic: %s%n", type, generic); - - Map typeParamToTypeArg = new HashMap<>(); - - AnnotatedDeclaredType enclosing = type; - while (enclosing != null) { - List enclosingTArgs = enclosing.getTypeArguments(); - AnnotatedDeclaredType declaredType = - getAnnotatedType((TypeElement) enclosing.getUnderlyingType().asElement()); - List enclosingTVars = declaredType.getTypeArguments(); - for (int i = 0; i < enclosingTArgs.size(); i++) { - AnnotatedTypeVariable enclosingTVar = (AnnotatedTypeVariable) enclosingTVars.get(i); - typeParamToTypeArg.put(enclosingTVar.getUnderlyingType(), enclosingTArgs.get(i)); - } - enclosing = enclosing.getEnclosingType(); - } - - List res = new ArrayList<>(tvars.size()); - - for (AnnotatedTypeMirror atm : tvars) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) atm; - AnnotatedTypeMirror upper = - typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getUpperBound()); - AnnotatedTypeMirror lower = - typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getLowerBound()); - res.add(new AnnotatedTypeParameterBounds(upper, lower)); - } - - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptTypeParameterBounds(type, res); - } - return res; - } - - /** - * Creates and returns an AnnotatedNullType qualified with {@code annotations}. - * - * @param annotations the set of AnnotationMirrors to qualify the returned type with - * @return AnnotatedNullType qualified with {@code annotations} - */ - public AnnotatedNullType getAnnotatedNullType(Set annotations) { - AnnotatedTypeMirror.AnnotatedNullType nullType = - (AnnotatedNullType) toAnnotatedType(processingEnv.getTypeUtils().getNullType(), false); - nullType.addAnnotations(annotations); - return nullType; - } - - // ********************************************************************** - // Utilities method for getting specific types from trees or elements - // ********************************************************************** - - /** - * Return the implicit receiver type of an expression tree. - * - *

          The result is null for expressions that don't have a receiver, e.g. for a local variable or - * method parameter access. The result is also null for expressions that have an explicit - * receiver. - * - *

          Clients should generally call {@link #getReceiverType}. - * - * @param tree the expression that might have an implicit receiver - * @return the type of the implicit receiver. Returns null if the expression has an explicit - * receiver or doesn't have a receiver. - */ - protected @Nullable AnnotatedDeclaredType getImplicitReceiverType(ExpressionTree tree) { - assert (tree.getKind() == Tree.Kind.IDENTIFIER - || tree.getKind() == Tree.Kind.MEMBER_SELECT - || tree.getKind() == Tree.Kind.METHOD_INVOCATION - || tree.getKind() == Tree.Kind.NEW_CLASS) - : "Unexpected tree kind: " + tree.getKind(); - - // Return null if the element kind has no receiver. - Element element = TreeUtils.elementFromUse(tree); - assert element != null : "Unexpected null element for tree: " + tree; - if (!ElementUtils.hasReceiver(element)) { - return null; - } - - // Return null if the receiver is explicit. - if (TreeUtils.getReceiverTree(tree) != null) { - return null; - } - - TypeElement elementOfImplicitReceiver = ElementUtils.enclosingTypeElement(element); - if (tree.getKind() == Tree.Kind.NEW_CLASS) { - if (elementOfImplicitReceiver.getEnclosingElement() != null) { - elementOfImplicitReceiver = - ElementUtils.enclosingTypeElement(elementOfImplicitReceiver.getEnclosingElement()); - } else { - elementOfImplicitReceiver = null; - } - if (elementOfImplicitReceiver == null) { - // If the typeElt does not have an enclosing class, then the NewClassTree - // does not have an implicit receiver. - return null; - } - } + /** Provides utility method to infer type arguments. */ + protected TypeArgumentInference typeArgumentInference; - TypeMirror typeOfImplicitReceiver = elementOfImplicitReceiver.asType(); - AnnotatedDeclaredType thisType = getSelfType(tree); - if (thisType == null) { - return null; - } - // An implicit receiver is the first enclosing type that is a subtype of the type where the - // element is declared. - while (thisType != null && !isSubtype(thisType.getUnderlyingType(), typeOfImplicitReceiver)) { - thisType = thisType.getEnclosingType(); - } - return thisType; - } - - /** - * Returns the type of {@code this} at the location of {@code tree}. Returns {@code null} if - * {@code tree} is in a location where {@code this} has no meaning, such as the body of a static - * method. - * - *

          The parameter is an arbitrary tree and does not have to mention "this", neither explicitly - * nor implicitly. This method can be overridden for type-system specific behavior. - * - * @param tree location used to decide the type of {@code this} - * @return the type of {@code this} at the location of {@code tree} - */ - public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { - if (TreeUtils.isClassTree(tree)) { - return getAnnotatedType(TreeUtils.elementFromDeclaration((ClassTree) tree)); - } - - Tree enclosingTree = getEnclosingClassOrMethod(tree); - if (enclosingTree == null) { - // tree is inside an annotation, where "this" is not allowed. So, no self type exists. - return null; - } else if (enclosingTree.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = (MethodTree) enclosingTree; - if (TreeUtils.isConstructor(enclosingMethod)) { - return (AnnotatedDeclaredType) getAnnotatedType(enclosingMethod).getReturnType(); - } else { - return getAnnotatedType(enclosingMethod).getReceiverType(); - } - } else if (TreeUtils.isClassTree(enclosingTree)) { - return (AnnotatedDeclaredType) getAnnotatedType(enclosingTree); - } - return null; - } - - /** A set containing class, method, and annotation tree kinds. */ - private static final Set classMethodAnnotationKinds = - EnumSet.copyOf(TreeUtils.classTreeKinds()); - - static { - classMethodAnnotationKinds.add(Tree.Kind.METHOD); - classMethodAnnotationKinds.add(Tree.Kind.TYPE_ANNOTATION); - classMethodAnnotationKinds.add(Tree.Kind.ANNOTATION); - } - - /** - * Returns the innermost enclosing method or class tree of {@code tree}. Since artificial trees - * are assigned to be the child node of the original tree, their enclosing trees are found the - * same way as normal trees. - * - *

          If the tree is inside an annotation, then {@code null} is returned. - * - * @param tree tree to whose innermost enclosing method or class to return - * @return the innermost enclosing method or class tree of {@code tree}, or {@code null} if {@code - * tree} is inside an annotation - */ - public @Nullable Tree getEnclosingClassOrMethod(Tree tree) { - TreePath path = getPath(tree); - Tree enclosing = TreePathUtil.enclosingOfKind(path, classMethodAnnotationKinds); - if (enclosing != null) { - if (enclosing.getKind() == Tree.Kind.ANNOTATION - || enclosing.getKind() == Tree.Kind.TYPE_ANNOTATION) { - return null; - } - return enclosing; - } - - return TreePathUtil.enclosingClass(path); - } - - /** - * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} - * that is the same type as {@code typeElement}. - * - * @param typeElement type of the enclosing type to return - * @param tree location to use - * @return the enclosing type at the location of {@code tree} that is the same type as {@code - * typeElement} - */ - public AnnotatedDeclaredType getEnclosingType(TypeElement typeElement, Tree tree) { - AnnotatedDeclaredType thisType = getSelfType(tree); - while (!isSameType(thisType.getUnderlyingType(), typeElement.asType())) { - thisType = thisType.getEnclosingType(); - } - return thisType; - } - - /** - * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} - * that is a subtype of {@code typeElement}. - * - * @param typeElement super type of the enclosing type to return - * @param tree location to use - * @return the enclosing type at the location of {@code tree} that is a subtype of {@code - * typeElement} - */ - public AnnotatedDeclaredType getEnclosingSubType(TypeElement typeElement, Tree tree) { - AnnotatedDeclaredType thisType = getSelfType(tree); - while (!isSubtype(thisType.getUnderlyingType(), typeElement.asType())) { - thisType = thisType.getEnclosingType(); - } - return thisType; - } - - /** - * Returns true if the erasure of {@code type1} is a Java subtype of the erasure of {@code type2}. - * - * @param type1 a type - * @param type2 a type - * @return true if the erasure of {@code type1} is a Java subtype of the erasure of {@code type2} - */ - private boolean isSubtype(TypeMirror type1, TypeMirror type2) { - return types.isSubtype(types.erasure(type1), types.erasure(type2)); - } - - /** - * Returns true if the erasure of {@code type1} is the same Java type as the erasure of {@code - * type2}. - * - * @param type1 a type - * @param type2 a type - * @return true if the erasure of {@code type1} is the same Java type as the erasure of {@code - * type2} - */ - private boolean isSameType(TypeMirror type1, TypeMirror type2) { - return types.isSameType(types.erasure(type1), types.erasure(type2)); - } - - /** - * Returns the receiver type of the expression tree, which might be the type of an implicit {@code - * this}. Returns null if the expression has no explicit or implicit receiver. - * - * @param expression the expression for which to determine the receiver type - * @return the type of the receiver of expression - */ - public final @Nullable AnnotatedTypeMirror getReceiverType(ExpressionTree expression) { - AnnotatedTypeMirror receiverType; - ExpressionTree receiver = TreeUtils.getReceiverTree(expression); - if (receiver != null) { - receiverType = getAnnotatedType(receiver); - } else { - Element element = TreeUtils.elementFromTree(expression); - if (element != null && ElementUtils.hasReceiver(element)) { - // The tree references an element that has a receiver, but the tree does not have an - // explicit receiver. So, the tree must have an implicit receiver of "this" or - // "Outer.this". - receiverType = getImplicitReceiverType(expression); - } else { - receiverType = null; - } - } - // In Java versions below 11, consider the following code: - // class Outer { - // class Inner{} - // } - // class Top { - // void test(Outer outer) { - // outer.new Inner(){}; - // } - // } - // the receiverType of outer.new Inner(){} is Top instead of Outer, - // because Java below 11 organizes newClassTree of an anonymous class in a different - // way: there is a synthetic argument representing the enclosing expression type. - // In such case, use the synthetic argument as its receiver type. - if ((expression instanceof NewClassTree) - && TreeUtils.hasSyntheticArgument((NewClassTree) expression)) { - receiverType = getAnnotatedType(((NewClassTree) expression).getArguments().get(0)); - } - return receiverType; - } - - /** The type for an instantiated generic method or constructor. */ - public static class ParameterizedExecutableType { - /** The method's/constructor's type. */ - public final AnnotatedExecutableType executableType; - - /** The types of the generic type arguments. */ - public final List typeArgs; - - /** Create a ParameterizedExecutableType. */ - public ParameterizedExecutableType( - AnnotatedExecutableType executableType, List typeArgs) { - this.executableType = executableType; - this.typeArgs = typeArgs; - } + /** + * Caches the supported type qualifier classes. Call {@link #getSupportedTypeQualifiers()} + * instead of using this field directly, as it may not have been initialized. + */ + private @MonotonicNonNull Set> supportedQuals = null; - @Override - public String toString() { - if (typeArgs.isEmpty()) { - return executableType.toString(); - } else { - StringJoiner typeArgsString = new StringJoiner(",", "<", ">"); - for (AnnotatedTypeMirror atm : typeArgs) { - typeArgsString.add(atm.toString()); - } - return typeArgsString + " " + executableType.toString(); - } - } - } - - /** - * Determines the type of the invoked method based on the passed method invocation tree. - * - *

          The returned method type has all type variables resolved, whether based on receiver type, - * passed type parameters if any, and method invocation parameter. - * - *

          Subclasses may override this method to customize inference of types or qualifiers based on - * method invocation parameters. - * - *

          As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types, - * AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type - * should be in accordance to its specification. - * - *

          The return type is a pair of the type of the invoked method and the (inferred) type - * arguments. Note that neither the explicitly passed nor the inferred type arguments are - * guaranteed to be subtypes of the corresponding upper bounds. See method {@link - * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type - * argument well-formedness. - * - *

          Note that "this" and "super" constructor invocations are also handled by this method - * (explicit or implicit ones, at the beginning of a constructor). Method {@link - * #constructorFromUse(NewClassTree)} is only used for a constructor invocation in a "new" - * expression. - * - * @param tree the method invocation tree - * @return the type of the invoked method and any (explict or inferred) type arguments - */ - public final ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { - return methodFromUse(tree, true); - } - - /** - * Returns the same as {@link #methodFromUse(MethodInvocationTree)}, but without inferred type - * arguments. - * - * @param tree a method invocation tree - * @return the type of the invoked method and any explicit type arguments - */ - public ParameterizedExecutableType methodFromUseWithoutTypeArgInference( - MethodInvocationTree tree) { - return methodFromUse(tree, false); - } - - /** - * The implementation of {@link #methodFromUse(MethodInvocationTree)} and {@link - * #methodFromUseWithoutTypeArgInference(MethodInvocationTree)}. - * - * @param tree a method invocation tree - * @param inferTypeArgs whether type arguments should be inferred - * @return the type of the invoked method, any explicit type arguments, and if {@code - * inferTypeArgs} is true, any inferred type arguments - */ - protected ParameterizedExecutableType methodFromUse( - MethodInvocationTree tree, boolean inferTypeArgs) { - ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - AnnotatedTypeMirror receiverType = getReceiverType(tree); - if (receiverType == null - && (TreeUtils.isSuperConstructorCall(tree) || TreeUtils.isThisConstructorCall(tree))) { - // super() and this() calls don't have a receiver, but they should be view-point adapted - // as if "this" is the receiver. - receiverType = getSelfType(tree); - } - if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) { - receiverType = applyCaptureConversion(receiverType); - } - - ParameterizedExecutableType result = - methodFromUse(tree, methodElt, receiverType, inferTypeArgs); - if (checker.shouldResolveReflection() - && reflectionResolver.isReflectiveMethodInvocation(tree)) { - result = reflectionResolver.resolveReflectiveCall(this, tree, result); - } - - AnnotatedExecutableType method = result.executableType; - if (AnnotatedTypes.isTypeArgOfRawType(method.getReturnType())) { - // Get the correct Java type from the tree and use it as the upper bound of the - // wildcard. - TypeMirror tm = TreeUtils.typeOf(tree); - AnnotatedTypeMirror t = toAnnotatedType(tm, false); - - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) method.getReturnType(); - if (ignoreRawTypeArguments) { - // Remove the annotations so that default annotations are used instead. - // (See call to addDefaultAnnotations below.) - t.clearAnnotations(); - } else { - t.replaceAnnotations(wildcard.getExtendsBound().getAnnotations()); - } - wildcard.setExtendsBound(t); - addDefaultAnnotations(wildcard); - } - - // Store varargType before calling setParameterTypes, otherwise we may lose the varargType - // as it is the last element of the original parameterTypes. - method.computeVarargType(); - // Adapt parameters, which makes parameters and arguments be the same size for later - // checking. - List parameters = - AnnotatedTypes.adaptParameters(this, method, tree.getArguments(), null); - method.setParameterTypes(parameters); - return result; - } - - /** - * Determines the type of the invoked method based on the passed expression tree, executable - * element, and receiver type. - * - * @param tree either a MethodInvocationTree or a MemberReferenceTree - * @param methodElt the element of the referenced method - * @param receiverType the type of the receiver - * @return the type of the method being invoked with tree and the (inferred) type arguments - * @see #methodFromUse(MethodInvocationTree) - */ - public final ParameterizedExecutableType methodFromUse( - ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - return methodFromUse(tree, methodElt, receiverType, true); - } - - /** - * Returns the same as {@link #methodFromUse(ExpressionTree, ExecutableElement, - * AnnotatedTypeMirror)}, but without inferred type arguments. - * - * @param tree either a MethodInvocationTree or a MemberReferenceTree - * @param methodElt the element of the referenced method - * @param receiverType the type of the receiver - * @return the type of the method being invoked with tree without inferring type arguments - */ - public final ParameterizedExecutableType methodFromUseWithoutTypeArgInference( - ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - return methodFromUse(tree, methodElt, receiverType, false); - } - - /** - * The implementation of {@link #methodFromUse(ExpressionTree, ExecutableElement, - * AnnotatedTypeMirror)} and {@link #methodFromUseWithoutTypeArgInference(ExpressionTree, - * ExecutableElement, AnnotatedTypeMirror)}. - * - * @param tree either a MethodInvocationTree or a MemberReferenceTree - * @param methodElt the element of the referenced method - * @param receiverType the type of the receiver - * @param inferTypeArgs whether type arguments should be inferred - * @return the type of the invoked method - */ - protected ParameterizedExecutableType methodFromUse( - ExpressionTree tree, - ExecutableElement methodElt, - AnnotatedTypeMirror receiverType, - boolean inferTypeArgs) { - AnnotatedExecutableType memberTypeWithoutOverrides = - getAnnotatedType(methodElt); // get unsubstituted type - AnnotatedExecutableType memberTypeWithOverrides = - applyFakeOverrides(receiverType, methodElt, memberTypeWithoutOverrides); - memberTypeWithOverrides = applyRecordTypesToAccessors(methodElt, memberTypeWithOverrides); - methodFromUsePreSubstitution(tree, memberTypeWithOverrides, inferTypeArgs); - - // Perform viewpoint adaption before type argument substitution. - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptMethod(receiverType, methodElt, memberTypeWithOverrides); - } - - AnnotatedExecutableType methodType = - AnnotatedTypes.asMemberOf(types, this, receiverType, methodElt, memberTypeWithOverrides); - List typeargs = new ArrayList<>(methodElt.getTypeParameters().size()); - - IPair, Boolean> pair = - AnnotatedTypes.findTypeArguments(this, tree, methodElt, methodType, inferTypeArgs); - Map typeParamToTypeArg = pair.first; - if (!typeParamToTypeArg.isEmpty()) { - for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { - if (typeParamToTypeArg.get(tv.getUnderlyingType()) == null) { - // throw new BugInCF( - // "AnnotatedTypeFactory.methodFromUse:mismatch between" - // + " declared method type variables and the inferred method - // type arguments." - // + " Method type variables: " - // + methodType.getTypeVariables() - // + "; " - // + "Inferred method type arguments: " - // + typeParamToTypeArg); - } - typeargs.add(typeParamToTypeArg.get(tv.getUnderlyingType())); - } - methodType = - (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, methodType); - } - - if (pair.second) { - methodType.setReturnType(methodType.getReturnType().getErased()); - } - - if (tree.getKind() == Tree.Kind.METHOD_INVOCATION - && TreeUtils.isMethodInvocation(tree, objectGetClass, processingEnv)) { - adaptGetClassReturnTypeToReceiver(methodType, receiverType, tree); - } - - methodType.setReturnType(applyCaptureConversion(methodType.getReturnType())); - return new ParameterizedExecutableType(methodType, typeargs); - } - - /** - * Given a member and its type, returns the type with fake overrides applied to it. - * - * @param receiverType the type of the class that contains member (or a subtype of it) - * @param member a type member, such as a method or field - * @param memberType the type of {@code member} - * @return {@code memberType}, adjusted according to fake overrides - */ - private AnnotatedExecutableType applyFakeOverrides( - AnnotatedTypeMirror receiverType, Element member, AnnotatedExecutableType memberType) { - // Currently, handle only methods, not fields. TODO: Handle fields. - if (memberType.getKind() != TypeKind.EXECUTABLE) { - return memberType; - } - - AnnotationFileElementTypes afet = stubTypes; - AnnotatedExecutableType methodType = afet.getFakeOverride(member, receiverType); - if (methodType == null) { - methodType = memberType; - } - return methodType; - } - - /** - * Given a method, checks if there is: a record component with the same name AND the record - * component has an annotation AND the method has no-arguments. If so, replaces the annotations on - * the method return type with those from the record type in the same hierarchy. - * - * @param member a method or constructor - * @param memberType the type of the method/constructor; side-effected by this method - * @return {@code memberType} with annotations replaced if applicable - */ - private AnnotatedExecutableType applyRecordTypesToAccessors( - ExecutableElement member, AnnotatedExecutableType memberType) { - if (memberType.getKind() != TypeKind.EXECUTABLE) { - throw new BugInCF( - "member %s has type %s of kind %s", member, memberType, memberType.getKind()); - } - - stubTypes.injectRecordComponentType(types, member, memberType); - - return memberType; - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the - * declared method type before type variable substitution. - * - * @param tree either a method invocation or a member reference tree - * @param type declared method type before type variable substitution - * @param resolvePolyQuals whether to resolve polymorphic qualifiers - */ - protected void methodFromUsePreSubstitution( - ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { - assert tree instanceof MethodInvocationTree || tree instanceof MemberReferenceTree; - } - - /** - * Java special-cases the return type of {@link java.lang.Class#getClass() getClass()}. Though the - * method has a return type of {@code Class}, the compiler special cases this return-type and - * changes the bound of the type argument to the erasure of the receiver type. For example: - * - *

            - *
          • {@code x.getClass()} has the type {@code Class< ? extends erasure_of_x >} - *
          • {@code someInteger.getClass()} has the type {@code Class< ? extends Integer >} - *
          - * - * @param getClassType this must be a type representing a call to Object.getClass otherwise a - * runtime exception will be thrown. It is modified by side effect. - * @param receiverType the receiver type of the method invocation (not the declared receiver type) - * @param tree getClass method invocation tree - */ - protected void adaptGetClassReturnTypeToReceiver( - AnnotatedExecutableType getClassType, AnnotatedTypeMirror receiverType, ExpressionTree tree) { - TypeMirror type = TreeUtils.typeOf(tree); - AnnotatedTypeMirror returnType = AnnotatedTypeMirror.createType(type, this, false); - - if (returnType == null - || !(returnType.getKind() == TypeKind.DECLARED) - || ((AnnotatedDeclaredType) returnType).getTypeArguments().size() != 1) { - throw new BugInCF( - "Unexpected type passed to AnnotatedTypes.adaptGetClassReturnTypeToReceiver%n" - + "getClassType=%s%nreceiverType=%s", - getClassType, receiverType); - } - - AnnotatedWildcardType classWildcardArg = - (AnnotatedWildcardType) - ((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0); - getClassType.setReturnType(returnType); - - // Usually, the only locations that will add annotations to the return type are getClass in - // stub files defaults and propagation tree annotator. Since getClass is final they cannot - // come from source code. Also, since the newBound is an erased type we have no type - // arguments. So, we just copy the annotations from the bound of the declared type to the - // new bound. - AnnotationMirrorSet newAnnos = new AnnotationMirrorSet(); - AnnotationMirrorSet receiverTypeBoundAnnos = - getTypeDeclarationBounds(receiverType.getErased().getUnderlyingType()); - AnnotationMirrorSet wildcardBoundAnnos = classWildcardArg.getEffectiveAnnotations(); - for (AnnotationMirror receiverTypeBoundAnno : receiverTypeBoundAnnos) { - AnnotationMirror wildcardAnno = - qualHierarchy.findAnnotationInSameHierarchy(wildcardBoundAnnos, receiverTypeBoundAnno); - if (typeHierarchy.isSubtypeShallowEffective(receiverTypeBoundAnno, classWildcardArg)) { - newAnnos.add(receiverTypeBoundAnno); - } else { - newAnnos.add(wildcardAnno); - } - } - AnnotatedTypeMirror newTypeArg = - ((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0); - ((AnnotatedTypeVariable) newTypeArg).getUpperBound().replaceAnnotations(newAnnos); - } - - /** - * Return the element type of {@code expression}. This is usually the type of {@code - * expression.itertor().next()}. If {@code expression} is an array, it is the component type of - * the array. - * - * @param expression an expression whose type is an array or implements {@link Iterable} - * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, - * the component type of the array. - */ - public AnnotatedTypeMirror getIterableElementType(ExpressionTree expression) { - return getIterableElementType(expression, getAnnotatedType(expression)); - } - - /** - * Return the element type of {@code iterableType}. This is usually the type of {@code - * expression.itertor().next()}. If {@code expression} is an array, it is the component type of - * the array. - * - * @param expression an expression whose type is an array or implements {@link Iterable} - * @param iterableType the type of the expression - * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, - * the component type of the array. - */ - protected AnnotatedTypeMirror getIterableElementType( - ExpressionTree expression, AnnotatedTypeMirror iterableType) { - switch (iterableType.getKind()) { - case ARRAY: - return ((AnnotatedArrayType) iterableType).getComponentType(); - case WILDCARD: - return getIterableElementType( - expression, ((AnnotatedWildcardType) iterableType).getExtendsBound().deepCopy()); - case TYPEVAR: - return getIterableElementType( - expression, ((AnnotatedTypeVariable) iterableType).getUpperBound()); - case DECLARED: - AnnotatedDeclaredType dt = - AnnotatedTypes.asSuper(this, iterableType, this.iterableDeclType); - if (dt.getTypeArguments().isEmpty()) { - TypeElement e = ElementUtils.getTypeElement(processingEnv, Object.class); - return getAnnotatedType(e); - } else { - return dt.getTypeArguments().get(0); - } - - // TODO: Properly desugar Iterator.next(), which is needed if an annotated JDK has - // annotations on Iterator#next. - // The below doesn't work because methodFromUse() assumes that the expression tree - // matches the method element. - // TypeElement iteratorElement = - // ElementUtils.getTypeElement(processingEnv, Iterator.class); - // AnnotatedTypeMirror iteratorType = - // AnnotatedTypeMirror.createType(iteratorElement.asType(), this, false); - // Map mapping = new HashMap<>(); - // mapping.put( - // (TypeVariable) iteratorElement.getTypeParameters().get(0).asType(), - // typeArg); - // iteratorType = typeVarSubstitutor.substitute(mapping, iteratorType); - // ExecutableElement next = - // TreeUtils.getMethod("java.util.Iterator", "next", 0, processingEnv); - // ParameterizedExecutableType m = methodFromUse(expression, next, iteratorType); - // return m.executableType.getReturnType(); - default: - throw new BugInCF( - "AnnotatedTypeFactory.getIterableElementType: not iterable type: " + iterableType); - } - } - - /** - * Determines the type of the invoked constructor based on the passed new class tree. - * - *

          The returned method type has all type variables resolved, whether based on receiver type, - * passed type parameters if any, and constructor invocation parameter. - * - *

          Subclasses may override this method to customize inference of types or qualifiers based on - * constructor invocation parameters. - * - *

          As an implementation detail, this method depends on {@link AnnotatedTypes#asMemberOf(Types, - * AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and customization based on receiver type - * should be in accordance with its specification. - * - *

          The return type is a pair of the type of the invoked constructor and the (inferred) type - * arguments. Note that neither the explicitly passed nor the inferred type arguments are - * guaranteed to be subtypes of the corresponding upper bounds. See method {@link - * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of type - * argument well-formedness. - * - *

          Note that "this" and "super" constructor invocations are handled by method {@link - * #methodFromUse}. This method only handles constructor invocations in a "new" expression. - * - * @param tree the constructor invocation tree - * @return the annotated type of the invoked constructor (as an executable type) and the - * (inferred) type arguments - */ - public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { - return constructorFromUse(tree, true); - } - - /** - * The same as {@link #constructorFromUse(NewClassTree)}, but no type arguments are inferred. - * - * @param tree the constructor invocation tree - * @return the annotated type of the invoked constructor (as an executable type) and the explicit - * type arguments - */ - public ParameterizedExecutableType constructorFromUseWithoutTypeArgInference(NewClassTree tree) { - return constructorFromUse(tree, false); - } - - /** - * Gets the type of the resulting constructor call of a MemberReferenceTree. - * - * @param memberReferenceTree MemberReferenceTree where the member is a constructor - * @param constructorType AnnotatedExecutableType of the declaration of the constructor - * @return AnnotatedTypeMirror of the resulting type of the constructor - */ - public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference( - MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) { - assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW; - - // The return type for constructors should only have explicit annotations from the - // constructor. The code below recreates some of the logic from TypeFromTree.visitNewClass to do - // this. - - // The return type of the constructor will be the type of the expression of the member - // reference tree. - AnnotatedTypeMirror constructorReturnType = - fromTypeTree(memberReferenceTree.getQualifierExpression()); - - if (constructorReturnType.getKind() == TypeKind.DECLARED) { - // Keep only explicit annotations and those from @Poly - AnnotatedTypes.copyOnlyExplicitConstructorAnnotations( - this, (AnnotatedDeclaredType) constructorReturnType, constructorType); - } - - // Now add back defaulting. - addComputedTypeAnnotations(memberReferenceTree.getQualifierExpression(), constructorReturnType); - return constructorReturnType; - } - - /** - * The implementation of {@link #constructorFromUse(NewClassTree)} and {@link - * #constructorFromUseWithoutTypeArgInference(NewClassTree)}. - * - * @param tree the constructor invocation tree - * @param inferTypeArgs whether the type arguments should be inferred - * @return the annotated type of the invoked constructor (as an executable type) and the type - * arguments - */ - protected ParameterizedExecutableType constructorFromUse( - NewClassTree tree, boolean inferTypeArgs) { - // Get the annotations written on the new class tree. - AnnotatedDeclaredType type = - (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false); - if (!TreeUtils.isDiamondTree(tree)) { - if (tree.getClassBody() == null) { - type.setTypeArguments(getExplicitNewClassClassTypeArgs(tree)); - } - } else { - type = getAnnotatedType(TypesUtils.getTypeElement(type.underlyingType)); - // Add explicit annotations below. - type.clearAnnotations(); - } - - AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(tree); - type.addAnnotations(explicitAnnos); - - // Get the enclosing type of the constructor, if one exists. - // this.new InnerClass() - AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(tree); - type.setEnclosingType(enclosingType); - - // Add computed annotations to the type. - addComputedTypeAnnotations(tree, type); - - ExecutableElement ctor = TreeUtils.elementFromUse(tree); - AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type - constructorFromUsePreSubstitution(tree, con, inferTypeArgs); - - if (tree.getClassBody() != null) { - // Because the anonymous constructor can't have explicit annotations on its parameters, - // they are copied from the super constructor invoked in the anonymous constructor. To - // do this: - // 1. get unsubstituted type of the super constructor. - // 2. adapt it to this call site. - // 3. compute and store the vararg type. - // 4. copy the parameters to the anonymous constructor, `con`. - // 5. copy annotations on the return type to `con`. - AnnotatedExecutableType superCon = getAnnotatedType(TreeUtils.getSuperConstructor(tree)); - constructorFromUsePreSubstitution(tree, superCon, inferTypeArgs); - // no viewpoint adaptation needed for super invocation - superCon = AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon); - con.computeVarargType(superCon); - if (superCon.getParameterTypes().size() == con.getParameterTypes().size()) { - con.setParameterTypes(superCon.getParameterTypes()); - } else { - // If the super class of the anonymous class has an enclosing type, then it is the - // first parameter of the anonymous constructor. For example, - // class Outer { class Inner {} } - // new Inner(){}; - // Then javac creates the following constructor: - // (.Outer x0) { - // x0.super(); - // } - // So the code below deals with this. - // Because the anonymous constructor doesn't have annotated receiver type, - // we copy the receiver type from the super constructor invoked in the anonymous - // constructor - List p = new ArrayList<>(superCon.getParameterTypes().size() + 1); - p.add(con.getParameterTypes().get(0)); - con.setReceiverType(superCon.getReceiverType()); - p.addAll(superCon.getParameterTypes()); - con.setParameterTypes(Collections.unmodifiableList(p)); - } - con.getReturnType().replaceAnnotations(superCon.getReturnType().getAnnotations()); - } else { - // Store varargType before calling setParameterTypes, otherwise we may lose the - // varargType as it is the last element of the original parameterTypes. - // AnnotatedTypes.asMemberOf handles vararg type properly, so we do not need to compute - // vararg type again. - con.computeVarargType(); - con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con); - } - - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); - } - - IPair, Boolean> pair = - AnnotatedTypes.findTypeArguments(this, tree, ctor, con, inferTypeArgs); - Map typeParamToTypeArg = new HashMap<>(pair.first); - List typeargs; - if (typeParamToTypeArg.isEmpty()) { - typeargs = Collections.emptyList(); - } else { - typeargs = - CollectionsPlume.mapList( - (AnnotatedTypeVariable tv) -> typeParamToTypeArg.get(tv.getUnderlyingType()), - con.getTypeVariables()); - } - - con = (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, con); - - stubTypes.injectRecordComponentType(types, ctor, con); - if (enclosingType != null) { - // Reset the enclosing type because it can be substituted incorrectly. - ((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(enclosingType); - } - if (type.isUnderlyingTypeRaw() || TypesUtils.isRaw(TreeUtils.typeOf(tree))) { - ((AnnotatedDeclaredType) con.getReturnType()).setIsUnderlyingTypeRaw(); - } - if (ctor.getEnclosingElement().getKind() == ElementKind.ENUM) { - AnnotationMirrorSet enumAnnos = getEnumConstructorQualifiers(); - con.getReturnType().replaceAnnotations(enumAnnos); - } - - // Adapt parameters, which makes parameters and arguments be the same size for later - // checking. The vararg type of con has been already computed and stored when calling - // typeVarSubstitutor.substitute. - List parameters = - AnnotatedTypes.adaptParameters(this, con, tree.getArguments(), tree); - con.setParameterTypes(parameters); - - return new ParameterizedExecutableType(con, typeargs); - } - - /** - * Returns the annotations that should be applied to enum constructors. This implementation - * returns an empty set. Subclasses can override to return a different set. - * - * @return the annotations that should be applied to enum constructors - */ - protected AnnotationMirrorSet getEnumConstructorQualifiers() { - return new AnnotationMirrorSet(); - } - - /** - * Returns the annotations explicitly written on a NewClassTree. - * - *

          {@code new @HERE Class()} - * - * @param newClassTree a constructor invocation - * @return the annotations explicitly written on a NewClassTree - */ - public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { - if (newClassTree.getClassBody() != null) { - // In Java 17+, the annotations are on the identifier, so copy them. - AnnotatedTypeMirror identifierType = fromTypeTree(newClassTree.getIdentifier()); - // In Java 11 and lower, if newClassTree creates an anonymous class, then annotations in - // this location: - // new @HERE Class() {} - // are not on the identifier newClassTree, but rather on the modifier newClassTree. - List annoTrees = - newClassTree.getClassBody().getModifiers().getAnnotations(); - // Add the annotations to an AnnotatedTypeMirror removes the annotations that are not - // supported by this type system. - identifierType.addAnnotations(TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees)); - return identifierType.getAnnotations(); - } else { - return fromTypeTree(newClassTree.getIdentifier()).getAnnotations(); - } - } - - /** - * Returns the partially-annotated explicit class type arguments of the new class tree. The {@code - * AnnotatedTypeMirror} only include the annotations explicitly written on the explict type - * arguments. (If {@code newClass} use a diamond operator, this method returns the empty list.) - * For example, when called with {@code new MyClass<@HERE String>()} this method would return a - * list containing {@code @HERE String}. - * - * @param newClass a new class tree - * @return the partially annotated {@code AnnotatedTypeMirror}s for the (explicit) class type - * arguments of the new class tree - */ - protected List getExplicitNewClassClassTypeArgs(NewClassTree newClass) { - if (!TreeUtils.isDiamondTree(newClass)) { - return ((AnnotatedDeclaredType) fromTypeTree(newClass.getIdentifier())).getTypeArguments(); - } - return Collections.emptyList(); - } - - /** - * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the - * declared constructor type before type variable substitution. - * - * @param tree a NewClassTree from constructorFromUse() - * @param type declared method type before type variable substitution - * @param resolvePolyQuals whether to resolve polymorphic qualifiers - */ - protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) {} - - /** - * Returns the return type of the method {@code m}. - * - * @param m tree of a method declaration - * @return the return type of the method - */ - public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { - AnnotatedExecutableType methodType = getAnnotatedType(m); - AnnotatedTypeMirror ret = methodType.getReturnType(); - return ret; - } - - /** - * Returns the return type of the method {@code m} at the return statement {@code r}. This - * implementation just calls {@link #getMethodReturnType(MethodTree)}, but subclasses may override - * this method to change the type based on the return statement. - * - * @param m tree of a method declaration - * @param r a return statement within method {@code m} - * @return the return type of the method {@code m} at the return statement {@code r} - */ - public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { - return getMethodReturnType(m); - } - - /** - * Returns the annotated boxed type of the given primitive type. The returned type would only have - * the annotations on the given type. - * - *

          Subclasses may override this method safely to override this behavior. - * - * @param type the primitive type - * @return the boxed declared type of the passed primitive type - */ - public AnnotatedDeclaredType getBoxedType(AnnotatedPrimitiveType type) { - TypeElement typeElt = types.boxedClass(type.getUnderlyingType()); - AnnotatedDeclaredType dt = fromElement(typeElt).asUse(); - dt.addAnnotations(type.getAnnotations()); - return dt; - } - - /** - * Return a primitive type: either the argument, or the result of unboxing it (which might affect - * its annotations). - * - *

          Subclasses should override {@link #getUnboxedType} rather than this method. - * - * @param type a type: a primitive or boxed primitive - * @return the unboxed variant of the type - */ - public final AnnotatedPrimitiveType applyUnboxing(AnnotatedTypeMirror type) { - TypeMirror underlying = type.getUnderlyingType(); - if (TypesUtils.isPrimitive(underlying)) { - return (AnnotatedPrimitiveType) type; - } else if (TypesUtils.isBoxedPrimitive(underlying)) { - return getUnboxedType((AnnotatedDeclaredType) type); - } else { - throw new BugInCF("Bad argument to applyUnboxing: " + type); - } - } - - /** - * Returns the annotated primitive type of the given declared type if it is a boxed declared type. - * Otherwise, it throws IllegalArgumentException exception. - * - *

          In the {@code AnnotatedTypeFactory} implementation, the returned type has the same primary - * annotations as the given type. Subclasses may override this behavior. - * - * @param type the declared type - * @return the unboxed primitive type - * @throws IllegalArgumentException if the type given has no unbox conversion - */ - public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) - throws IllegalArgumentException { - PrimitiveType primitiveType = types.unboxedType(type.getUnderlyingType()); - AnnotatedPrimitiveType pt = - (AnnotatedPrimitiveType) AnnotatedTypeMirror.createType(primitiveType, this, false); - pt.addAnnotations(type.getAnnotations()); - return pt; - } - - /** - * Returns AnnotatedDeclaredType with underlying type String and annotations copied from type. - * Subclasses may change the annotations. - * - * @param type type to convert to String - * @return AnnotatedTypeMirror that results from converting type to a String type - */ - // TODO: Test that this is called in all the correct locations - // See Issue #715 - // https://github.com/typetools/checker-framework/issues/715 - public AnnotatedDeclaredType getStringType(AnnotatedTypeMirror type) { - TypeMirror stringTypeMirror = TypesUtils.typeFromClass(String.class, types, elements); - AnnotatedDeclaredType stringATM = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType(stringTypeMirror, this, type.isDeclaration()); - stringATM.addAnnotations(type.getEffectiveAnnotations()); - return stringATM; - } - - /** - * Returns a widened type if applicable, otherwise returns its first argument. - * - *

          Subclasses should override {@link #getWidenedAnnotations} rather than this method. - * - * @param exprType type to possibly widen - * @param widenedType type to possibly widen to; its annotations are ignored - * @return if widening is applicable, the result of converting {@code type} to the underlying type - * of {@code widenedType}; otherwise {@code type} - */ - public final AnnotatedTypeMirror getWidenedType( - AnnotatedTypeMirror exprType, AnnotatedTypeMirror widenedType) { - TypeKind exprKind = exprType.getKind(); - TypeKind widenedKind = widenedType.getKind(); - - if (!TypeKindUtils.isNumeric(widenedKind)) { - // The target type is not a numeric primitive, so primitive widening is not applicable. - return exprType; - } - - AnnotatedPrimitiveType exprPrimitiveType; - if (TypeKindUtils.isNumeric(exprKind)) { - exprPrimitiveType = (AnnotatedPrimitiveType) exprType; - } else if (TypesUtils.isNumericBoxed(exprType.getUnderlyingType())) { - exprPrimitiveType = getUnboxedType((AnnotatedDeclaredType) exprType); - } else { - return exprType; - } - - switch (TypeKindUtils.getPrimitiveConversionKind( - exprPrimitiveType.getKind(), widenedType.getKind())) { - case WIDENING: - return getWidenedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); - case NARROWING: - return getNarrowedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); - case SAME: - return exprType; - default: - throw new BugInCF("unhandled PrimitiveConversionKind"); - } - } - - /** - * Applies widening if applicable, otherwise returns its first argument. - * - *

          Subclasses should override {@link #getWidenedAnnotations} rather than this method. - * - * @param exprAnnos annotations to possibly widen - * @param exprTypeMirror type to possibly widen - * @param widenedType type to possibly widen to; its annotations are ignored - * @return if widening is applicable, the result of converting {@code type} to the underlying type - * of {@code widenedType}; otherwise {@code type} - */ - public final AnnotatedTypeMirror getWidenedType( - AnnotationMirrorSet exprAnnos, TypeMirror exprTypeMirror, AnnotatedTypeMirror widenedType) { - AnnotatedTypeMirror exprType = toAnnotatedType(exprTypeMirror, false); - exprType.replaceAnnotations(exprAnnos); - return getWidenedType(exprType, widenedType); - } - - /** - * Returns an AnnotatedPrimitiveType with underlying type {@code widenedTypeMirror} and with - * annotations copied or adapted from {@code type}. - * - * @param type type to widen; a primitive or boxed primitive - * @param widenedTypeMirror underlying type for the returned type mirror; a primitive or boxed - * primitive (same boxing as {@code type}) - * @return result of converting {@code type} to {@code widenedTypeMirror} - */ - private AnnotatedPrimitiveType getWidenedPrimitive( - AnnotatedPrimitiveType type, TypeMirror widenedTypeMirror) { - AnnotatedPrimitiveType result = - (AnnotatedPrimitiveType) - AnnotatedTypeMirror.createType(widenedTypeMirror, this, type.isDeclaration()); - result.addAnnotations( - getWidenedAnnotations(type.getAnnotations(), type.getKind(), result.getKind())); - return result; - } - - /** - * Returns annotations applicable to type {@code narrowedTypeKind}, that are copied or adapted - * from {@code annos}. - * - * @param annos annotations to narrow, from a primitive or boxed primitive - * @param typeKind primitive type to narrow - * @param narrowedTypeKind target for the returned annotations; a primitive type that is narrower - * than {@code typeKind} (in the sense of JLS 5.1.3). - * @return result of converting {@code annos} from {@code typeKind} to {@code narrowedTypeKind} - */ - public AnnotationMirrorSet getNarrowedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { - return annos; - } - - /** - * Returns annotations applicable to type {@code widenedTypeKind}, that are copied or adapted from - * {@code annos}. - * - * @param annos annotations to widen, from a primitive or boxed primitive - * @param typeKind primitive type to widen - * @param widenedTypeKind target for the returned annotations; a primitive type that is wider than - * {@code typeKind} (in the sense of JLS 5.1.2) - * @return result of converting {@code annos} from {@code typeKind} to {@code widenedTypeKind} - */ - public AnnotationMirrorSet getWidenedAnnotations( - AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { - return annos; - } - - /** - * Returns the types of the two arguments to the BinaryTree. Please refer to {@link - * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} )} for more details. - * - * @param tree a binary tree - * @return the types of the two arguments - */ - public IPair binaryTreeArgTypes(BinaryTree tree) { - return binaryTreeArgTypes( - TreeUtils.typeOf(tree), - getAnnotatedType(tree.getLeftOperand()), - getAnnotatedType(tree.getRightOperand())); - } - - /** - * Returns the types of the two arguments to the CompoundAssignmentTree. Please refer to {@link - * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} ) for more details. - * - * @param tree a compound assignment tree - * @return the types of the two arguments - */ - public IPair compoundAssignmentTreeArgTypes( - CompoundAssignmentTree tree) { - return binaryTreeArgTypes( - TreeUtils.typeOf(tree.getVariable()), - getAnnotatedType(tree.getVariable()), - getAnnotatedType(tree.getExpression())); - } - - /** - * Returns the types of the two arguments to a binary operation. There are two special cases: - * - *

          1. If both operands have numeric type, widening and unboxing will be applied accordingly. - * - *

          2. If we have a non-string operand in a string concatenation (i.e., result is a string), we - * will always return a string ATM for the operand. The resulting ATM will have the original - * annotations with the declaration bounds of string type applied. Please check {@link - * #getAnnotationOrTypeDeclarationBound} for more details. - * - * @param resultType the type of the result of a binary operation - * @param left the type of the left argument of a binary operation - * @param right the type of the right argument of a binary operation - * @return the types of the two arguments - */ - protected IPair binaryTreeArgTypes( - TypeMirror resultType, AnnotatedTypeMirror left, AnnotatedTypeMirror right) { - TypeKind widenedNumericType = - TypeKindUtils.widenedNumericType(left.getUnderlyingType(), right.getUnderlyingType()); - if (TypeKindUtils.isNumeric(widenedNumericType)) { - TypeMirror widenedNumericTypeMirror = types.getPrimitiveType(widenedNumericType); - AnnotatedPrimitiveType leftUnboxed = applyUnboxing(left); - AnnotatedPrimitiveType rightUnboxed = applyUnboxing(right); - AnnotatedPrimitiveType leftWidened = - (leftUnboxed.getKind() == widenedNumericType - ? leftUnboxed - : getWidenedPrimitive(leftUnboxed, widenedNumericTypeMirror)); - AnnotatedPrimitiveType rightWidened = - (rightUnboxed.getKind() == widenedNumericType - ? rightUnboxed - : getWidenedPrimitive(rightUnboxed, widenedNumericTypeMirror)); - return IPair.of(leftWidened, rightWidened); - } else if (TypesUtils.isString(resultType)) { - // the result of a binary operation is String iff it's string concatenation - AnnotatedTypeMirror leftStringConverted = left; - AnnotatedTypeMirror rightStringConverted = right; - - if (!TypesUtils.isString(left.getUnderlyingType())) { - leftStringConverted = toAnnotatedType(resultType, false); - AnnotationMirrorSet annos = - getAnnotationOrTypeDeclarationBound(resultType, left.getEffectiveAnnotations()); - leftStringConverted.addAnnotations(annos); - } - if (!TypesUtils.isString(right.getUnderlyingType())) { - rightStringConverted = toAnnotatedType(resultType, false); - AnnotationMirrorSet annos = - getAnnotationOrTypeDeclarationBound(resultType, right.getEffectiveAnnotations()); - rightStringConverted.addAnnotations(annos); - } - return IPair.of(leftStringConverted, rightStringConverted); - } - - return IPair.of(left, right); - } - - /** - * Returns AnnotatedPrimitiveType with underlying type {@code narrowedTypeMirror} and with - * annotations copied or adapted from {@code type}. - * - *

          Currently this method is called only for primitives that are narrowed at assignments from - * literal ints, for example, {@code byte b = 1;}. All other narrowing conversions happen at - * typecasts. - * - * @param type type to narrow - * @param narrowedTypeMirror underlying type for the returned type mirror - * @return result of converting {@code type} to {@code narrowedTypeMirror} - */ - public AnnotatedPrimitiveType getNarrowedPrimitive( - AnnotatedPrimitiveType type, TypeMirror narrowedTypeMirror) { - AnnotatedPrimitiveType narrowed = - (AnnotatedPrimitiveType) - AnnotatedTypeMirror.createType(narrowedTypeMirror, this, type.isDeclaration()); - narrowed.addAnnotations(type.getAnnotations()); - return narrowed; - } - - // ********************************************************************** - // random methods wrapping #getAnnotatedType(Tree) and #fromElement(Tree) - // with appropriate casts to reduce casts on the client side - // ********************************************************************** - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedDeclaredType getAnnotatedType(ClassTree tree) { - return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedDeclaredType getAnnotatedType(NewClassTree tree) { - return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedArrayType getAnnotatedType(NewArrayTree tree) { - return (AnnotatedArrayType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Tree)}. - * - * @see #getAnnotatedType(Tree) - */ - public final AnnotatedExecutableType getAnnotatedType(MethodTree tree) { - return (AnnotatedExecutableType) getAnnotatedType((Tree) tree); - } - - /** - * See {@link #getAnnotatedType(Element)}. - * - * @see #getAnnotatedType(Element) - */ - public final AnnotatedDeclaredType getAnnotatedType(TypeElement elt) { - return (AnnotatedDeclaredType) getAnnotatedType((Element) elt); - } - - /** - * See {@link #getAnnotatedType(Element)}. - * - * @see #getAnnotatedType(Element) - */ - public final AnnotatedExecutableType getAnnotatedType(ExecutableElement elt) { - return (AnnotatedExecutableType) getAnnotatedType((Element) elt); - } - - /** - * See {@link #fromElement(Element)}. - * - * @see #fromElement(Element) - */ - public final AnnotatedDeclaredType fromElement(TypeElement elt) { - return (AnnotatedDeclaredType) fromElement((Element) elt); - } - - /** - * See {@link #fromElement(Element)}. - * - * @see #fromElement(Element) - */ - public final AnnotatedExecutableType fromElement(ExecutableElement elt) { - return (AnnotatedExecutableType) fromElement((Element) elt); - } - - // ********************************************************************** - // Helper methods for this classes - // ********************************************************************** - - /** - * Returns true if the given annotation is a part of the type system under which this type factory - * operates. Null is never a supported qualifier; the parameter is nullable to allow the result of - * canonicalAnnotation to be passed in directly. - * - * @param a any annotation - * @return true if that annotation is part of the type system under which this type factory - * operates, false otherwise - */ - @EnsuresNonNullIf(expression = "#1", result = true) - public boolean isSupportedQualifier(@Nullable AnnotationMirror a) { - if (a == null) { - return false; - } - return isSupportedQualifier(AnnotationUtils.annotationName(a)); - } - - /** - * Returns true if the given class is a part of the type system under which this type factory - * operates. - * - * @param clazz annotation class - * @return true if that class is a type qualifier in the type system under which this type factory - * operates, false otherwise - */ - public boolean isSupportedQualifier(Class clazz) { - return getSupportedTypeQualifiers().contains(clazz); - } - - /** - * Returns true if the given class name is a part of the type system under which this type factory - * operates. - * - * @param className fully-qualified annotation class name - * @return true if that class name is a type qualifier in the type system under which this type - * factory operates, false otherwise - */ - public boolean isSupportedQualifier(String className) { - return getSupportedTypeQualifierNames().contains(className); - } - - /** - * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code - * canonicalAnno} that will be used by the Checker Framework in the alias's place. - * - *

          By specifying the alias/canonical relationship using this method, the elements of the alias - * are not preserved when the canonical annotation to use is constructed from the alias. If you - * want the elements to be copied over as well, use {@link #addAliasedTypeAnnotation(Class, Class, - * boolean, String...)}. - * - * @param aliasClass the class of the aliased annotation - * @param canonicalAnno the canonical annotation - */ - protected void addAliasedTypeAnnotation(Class aliasClass, AnnotationMirror canonicalAnno) { - if (getSupportedTypeQualifiers().contains(aliasClass)) { - throw new BugInCF( - "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", - aliasClass, this.getClass().getSimpleName()); - } - addAliasedTypeAnnotation(aliasClass.getCanonicalName(), canonicalAnno); - } - - /** - * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for - * the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in - * the alias's place. - * - *

          Use this method if the alias class is not necessarily on the classpath at Checker Framework - * compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, AnnotationMirror)} - * which prevents the possibility of a typo in the class name. - * - * @param aliasName the canonical name of the aliased annotation - * @param canonicalAnno the canonical annotation - */ - // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the - // name of an external annotation is a canonical name. - protected void addAliasedTypeAnnotation( - @FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) { - aliases.put(aliasName, new Alias(aliasName, canonicalAnno, false, null, null)); - } - - /** - * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code - * canonicalClass} that will be used by the Checker Framework in the alias's place. - * - *

          You may specify the copyElements flag to indicate whether you want the elements of the alias - * to be copied over when the canonical annotation is constructed as a copy of {@code - * canonicalClass}. Be careful that the framework will try to copy the elements by name matching, - * so make sure that names and types of the elements to be copied over are exactly the same as the - * ones in the canonical annotation. Otherwise, an 'Couldn't find element in annotation' error is - * raised. - * - *

          To facilitate the cases where some of the elements are ignored on purpose when constructing - * the canonical annotation, this method also provides a varargs {@code ignorableElements} for you - * to explicitly specify the ignoring rules. For example, {@code - * org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code - * org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of - * {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases where - * all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class, - * AnnotationMirror)} instead. - * - * @param aliasClass the class of the aliased annotation - * @param canonicalClass the class of the canonical annotation - * @param copyElements a flag that indicates whether you want to copy the elements over when - * getting the alias from the canonical annotation - * @param ignorableElements a list of elements that can be safely dropped when the elements are - * being copied over - */ - protected void addAliasedTypeAnnotation( - Class aliasClass, - Class canonicalClass, - boolean copyElements, - String... ignorableElements) { - if (getSupportedTypeQualifiers().contains(aliasClass)) { - throw new BugInCF( - "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", - aliasClass, this.getClass().getSimpleName()); - } - addAliasedTypeAnnotation( - aliasClass.getCanonicalName(), canonicalClass, copyElements, ignorableElements); - } - - /** - * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias for - * the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework in - * the alias's place. - * - *

          Use this method if the alias class is not necessarily on the classpath at Checker Framework - * compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, Class, boolean, - * String[])} which prevents the possibility of a typo in the class name. - * - * @param aliasName the canonical name of the aliased class - * @param canonicalAnno the canonical annotation - * @param copyElements a flag that indicates whether we want to copy the elements over when - * getting the alias from the canonical annotation - * @param ignorableElements a list of elements that can be safely dropped when the elements are - * being copied over - */ - // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the - // name of an external annotation is a canoncal name. - protected void addAliasedTypeAnnotation( - @FullyQualifiedName String aliasName, - Class canonicalAnno, - boolean copyElements, - String... ignorableElements) { - // The copyElements argument disambiguates overloading. - if (!copyElements) { - throw new BugInCF("Do not call with false"); - } - aliases.put( - aliasName, - new Alias( - aliasName, null, copyElements, canonicalAnno.getCanonicalName(), ignorableElements)); - } - - /** - * Returns the canonical annotation for the passed annotation. Returns null if the passed - * annotation is not an alias of a canonical one in the framework. - * - *

          A canonical annotation is the internal annotation that will be used by the Checker Framework - * in the aliased annotation's place. - * - * @param a the qualifier to check for an alias - * @return the canonical annotation, or null if none exists - */ - public @Nullable AnnotationMirror canonicalAnnotation(AnnotationMirror a) { - TypeElement elem = (TypeElement) a.getAnnotationType().asElement(); - String qualName = elem.getQualifiedName().toString(); - Alias alias = aliases.get(qualName); - if (alias == null) { - return null; - } - if (alias.copyElements) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, alias.canonicalName); - builder.copyElementValuesFromAnnotation(a, alias.ignorableElements); - return builder.build(); - } else { - return alias.canonical; - } - } - - /** - * Add the annotation {@code alias} as an alias for the declaration annotation {@code annotation}, - * where the annotation mirror {@code annotationToUse} will be used instead. If multiple calls are - * made with the same {@code annotation}, then the {@code annotationToUse} must be the same. - * - *

          The point of {@code annotationToUse} is that it may include elements/fields. - * - * @param alias the class of the alias annotation - * @param annotation the class of the canonical annotation - * @param annotationToUse the annotation mirror to use - */ - protected void addAliasedDeclAnnotation( - Class alias, - Class annotation, - AnnotationMirror annotationToUse) { - addAliasedDeclAnnotation( - alias.getCanonicalName(), annotation.getCanonicalName(), annotationToUse); - } - - /** - * Add the annotation {@code alias} as an alias for the declaration annotation {@code annotation}, - * where the annotation mirror {@code annotationToUse} will be used instead. If multiple calls are - * made with the same {@code annotation}, then the {@code annotationToUse} must be the same. - * - *

          The point of {@code annotationToUse} is that it may include elements/fields. - * - * @param alias the fully-qualified name of the alias annotation - * @param annotation the fully-qualified name of the canonical annotation - * @param annotationToUse the annotation mirror to use - */ - protected void addAliasedDeclAnnotation( - @FullyQualifiedName String alias, - @FullyQualifiedName String annotation, - AnnotationMirror annotationToUse) { - Map<@FullyQualifiedName String, AnnotationMirror> mapping = declAliases.get(annotation); - if (mapping == null) { - mapping = new HashMap<>(1); - declAliases.put(annotation, mapping); - } - AnnotationMirror prev = mapping.put(alias, annotationToUse); - // There already was a mapping. Raise an error. - if (prev != null && !AnnotationUtils.areSame(prev, annotationToUse)) { - throw new TypeSystemError( - "Multiple aliases for %s: %s cannot map to %s and %s.", - annotation, alias, prev, annotationToUse); - } - } - - /** - * Adds the annotation {@code annotation} in the set of declaration annotations that should be - * inherited. A declaration annotation will be inherited if it is in this list, or if it has the - * meta-annotation @InheritedAnnotation. The meta-annotation @InheritedAnnotation should be used - * instead of this method, if possible. - */ - protected void addInheritedAnnotation(AnnotationMirror annotation) { - inheritedAnnotations.add(annotation); - } - - /** - * A convenience method that converts a {@link TypeMirror} to an empty {@link AnnotatedTypeMirror} - * using {@link AnnotatedTypeMirror#createType}. - * - * @param t the {@link TypeMirror} - * @param declaration true if the result should be marked as a type declaration - * @return an {@link AnnotatedTypeMirror} that has {@code t} as its underlying type - */ - protected final AnnotatedTypeMirror toAnnotatedType(TypeMirror t, boolean declaration) { - return AnnotatedTypeMirror.createType(t, this, declaration); - } - - /** - * Determines an empty annotated type of the given tree. In other words, finds the {@link - * TypeMirror} for the tree and converts that into an {@link AnnotatedTypeMirror}, but does not - * add any annotations to the result. - * - *

          Most users will want to use {@link #getAnnotatedType(Tree)} instead; this method is mostly - * for internal use. - * - * @param tree the tree to analyze - * @return the type of {@code tree}, without any annotations - */ - protected final AnnotatedTypeMirror type(Tree tree) { - boolean isDeclaration = TreeUtils.isClassTree(tree); - - // Attempt to obtain the type via JCTree. - if (TreeUtils.typeOf(tree) != null) { - AnnotatedTypeMirror result = toAnnotatedType(TreeUtils.typeOf(tree), isDeclaration); - return result; - } - - // Attempt to obtain the type via TreePath (slower). - TreePath path = this.getPath(tree); - assert path != null - : "No path or type in tree: " + tree + " [" + tree.getClass().getSimpleName() + "]"; - - TypeMirror t = trees.getTypeMirror(path); - assert validType(t) : "Invalid type " + t + " for tree " + t; - - AnnotatedTypeMirror result = toAnnotatedType(t, isDeclaration); - return result; - } - - /** - * Gets the declaration tree for the element, if the source is available. - * - *

          TODO: would be nice to move this to InternalUtils/TreeUtils. - * - * @param elt an element - * @return the tree declaration of the element if found - */ - public final @Nullable Tree declarationFromElement(Element elt) { - // if root is null, we cannot find any declaration - if (root == null) { - return null; - } - if (shouldCache && elementToTreeCache.containsKey(elt)) { - return elementToTreeCache.get(elt); - } - - // Check for new declarations, outside of the AST. - if (elt instanceof DetachedVarSymbol) { - return ((DetachedVarSymbol) elt).getDeclaration(); - } - - // TODO: handle type parameter declarations? - Tree fromElt; - // Prevent calling declarationFor on elements we know we don't have the tree for. - - switch (ElementUtils.getKindRecordAsClass(elt)) { - case CLASS: // Including RECORD - case ENUM: - case INTERFACE: - case ANNOTATION_TYPE: - case FIELD: - case ENUM_CONSTANT: - case METHOD: - case CONSTRUCTOR: - fromElt = trees.getTree(elt); - break; - default: - fromElt = - com.sun.tools.javac.tree.TreeInfo.declarationFor( - (com.sun.tools.javac.code.Symbol) elt, (com.sun.tools.javac.tree.JCTree) root); - break; - } - if (shouldCache) { - elementToTreeCache.put(elt, fromElt); - } - return fromElt; - } - - /** - * Returns true if {@code tree} is within a constructor. - * - * @param tree the tree that might be within a constructor - * @return true if {@code tree} is within a constructor - */ - protected final boolean isWithinConstructor(Tree tree) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getPath(tree)); - return enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod); - } - - /** - * Sets the path to the tree that an external "visitor" is visiting. The visitor is either a - * subclass of {@link BaseTypeVisitor} or {@link - * org.checkerframework.framework.flow.CFAbstractTransfer}. - * - * @param visitorTreePath path to the current tree that an external "visitor" is visiting - */ - public void setVisitorTreePath(@Nullable TreePath visitorTreePath) { - this.visitorTreePath = visitorTreePath; - } - - /** - * Returns the path to the tree that an external "visitor" is visiting. The type factory does not - * update this value as it computes the types of any tree or element needed compute the type of - * the tree being visited. Therefore this path may not be the path to the tree whose type is being - * computed. This method should not be used directly. Use {@link #getPath(Tree)} instead. - * - *

          This method is used to save the previous tree path and to give a hint to {@link - * #getPath(Tree)} on where to look for a tree rather than searching starting at the root. - * - * @return the path to the tree that an external "visitor" is visiting - */ - public @Nullable TreePath getVisitorTreePath() { - return visitorTreePath; - } - - /** - * Gets the path for the given {@link Tree} under the current root by checking from the visitor's - * current path, and using {@link Trees#getPath(CompilationUnitTree, Tree)} (which is much slower) - * only if {@code tree} is not found on the current path. - * - *

          Note that the given Tree has to be within the current compilation unit, otherwise null will - * be returned. - * - *

          Within a subclass of BaseTypeVisitor, use {@code getCurrentPath()} rather than this method. - * - * @param tree the {@link Tree} to get the path for - * @return the path for {@code tree} under the current root. Returns null if {@code tree} is not - * within the current compilation unit. - */ - public final @Nullable TreePath getPath(@FindDistinct Tree tree) { - assert root != null - : "AnnotatedTypeFactory.getPath(" - + tree.getKind() - + "): root needs to be set when used on trees; factory: " - + this.getClass().getSimpleName(); - - if (tree == null) { - return null; - } - - if (treePathCache.isCached(tree)) { - return treePathCache.getPath(root, tree); - } - - TreePath currentPath = visitorTreePath; - if (currentPath == null) { - TreePath path = TreePath.getPath(root, tree); - treePathCache.addPath(tree, path); - return path; - } - - // This method uses multiple heuristics to avoid calling - // TreePath.getPath() - - // If the current path you are visiting is for this tree we are done - if (currentPath.getLeaf() == tree) { - treePathCache.addPath(tree, currentPath); - return currentPath; - } - - // When running on Daikon, we noticed that a lot of calls happened - // within a small subtree containing the tree we are currently visiting - - // When testing on Daikon, two steps resulted in the best performance - if (currentPath.getParentPath() != null) { - currentPath = currentPath.getParentPath(); - treePathCache.addPath(currentPath.getLeaf(), currentPath); - if (currentPath.getLeaf() == tree) { - return currentPath; - } - if (currentPath.getParentPath() != null) { - currentPath = currentPath.getParentPath(); - treePathCache.addPath(currentPath.getLeaf(), currentPath); - if (currentPath.getLeaf() == tree) { - return currentPath; - } - } - } + /** + * Caches the fully-qualified names of the classes in {@link #supportedQuals}. Call {@link + * #getSupportedTypeQualifierNames()} instead of using this field directly, as it may not have + * been initialized. + */ + private @MonotonicNonNull Set<@CanonicalName String> supportedQualNames = null; - TreePath pathWithinSubtree = TreePath.getPath(currentPath, tree); - if (pathWithinSubtree != null) { - treePathCache.addPath(tree, pathWithinSubtree); - return pathWithinSubtree; - } + /** Parses stub files and stores annotations on public elements from stub files. */ + public final AnnotationFileElementTypes stubTypes; - // climb the current path till we see that - // Works when getPath called on the enclosing method, enclosing class. - TreePath current = currentPath; - while (current != null) { - treePathCache.addPath(current.getLeaf(), current); - if (current.getLeaf() == tree) { - return current; - } - current = current.getParentPath(); - } - - // OK, we give up. Use the cache to look up. - return treePathCache.getPath(root, tree); - } - - /** - * Set the tree path for the given artificial tree. - * - *

          See {@code - * org.checkerframework.framework.flow.CFCFGBuilder.CFCFGTranslationPhaseOne.handleArtificialTree(Tree)}. - * - * @param tree the artificial {@link Tree} to set the path for - * @param path the {@link TreePath} for the artificial tree - */ - public final void setPathForArtificialTree(Tree tree, TreePath path) { - treePathCache.addPath(tree, path); - } - - /** - * Assert that the type is a type of valid type mirror, i.e. not an ERROR or OTHER type. - * - * @param type an annotated type - * @return true if the type is a valid annotated type, false otherwise - */ - /*package-private*/ static final boolean validAnnotatedType(AnnotatedTypeMirror type) { - if (type == null) { - return false; - } - return validType(type.getUnderlyingType()); - } - - /** - * Used for asserting that a type is valid for converting to an annotated type. - * - * @return true if {@code type} can be converted to an annotated type, false otherwise - */ - private static boolean validType(TypeMirror type) { - if (type == null) { - return false; - } - switch (type.getKind()) { - case ERROR: - case OTHER: - case PACKAGE: - return false; - default: - return true; - } - } - - /** - * Parses all annotation files in the following order: - * - *

            - *
          1. Stub files, see {@link AnnotationFileElementTypes#parseStubFiles()}; - *
          2. Ajava files, see {@link AnnotationFileElementTypes#parseAjavaFiles()}. - *
          - * - *

          If a type is annotated with a qualifier from the same hierarchy in more than one stub file, - * the qualifier in the last stub file is applied. - * - *

          The annotations are stored by side-effecting {@link #stubTypes} and {@link #ajavaTypes}. - */ - protected void parseAnnotationFiles() { - stubTypes.parseStubFiles(); - ajavaTypes.parseAjavaFiles(); - } - - /** - * Returns all of the declaration annotations whose name equals the passed annotation class (or is - * an alias for it) including annotations: - * - *

            - *
          • on the element - *
          • written in stubfiles - *
          • inherited from overridden methods, (see {@link InheritedAnnotation}) - *
          • inherited from superclasses or super interfaces (see {@link Inherited}) - *
          - * - * @see #getDeclAnnotationNoAliases - * @param elt the element to retrieve the declaration annotation from - * @param anno annotation class - * @return the annotation mirror for anno - */ - @Override - public final AnnotationMirror getDeclAnnotation(Element elt, Class anno) { - AnnotationMirror result = getDeclAnnotation(elt, anno, true); - return result; - } - - /** - * Returns the annotation mirror used to annotate this element, whose name equals the passed - * annotation class. Looks in the same places specified by {@link #getDeclAnnotation(Element, - * Class)}. Returns null if none exists. Does not check for aliases of the annotation class. - * - *

          Call this method from a checker that needs to alias annotations for one purpose and not for - * another. For example, in the Lock Checker, {@code @LockingFree} and {@code @ReleasesNoLocks} - * are both aliases of {@code @SideEffectFree} since they are all considered side-effect-free with - * regard to the set of locks held before and after the method call. However, a {@code - * synchronized} block is permitted inside a {@code @ReleasesNoLocks} method but not inside a - * {@code @LockingFree} or {@code @SideEffectFree} method. - * - * @see #getDeclAnnotation - * @param elt the element to retrieve the declaration annotation from - * @param anno annotation class - * @return the annotation mirror for anno - */ - public final @Nullable AnnotationMirror getDeclAnnotationNoAliases( - Element elt, Class anno) { - return getDeclAnnotation(elt, anno, false); - } - - /** - * Returns true if the element appears in a stub file (Currently only works for methods, - * constructors, and fields). - */ - public boolean isFromStubFile(Element element) { - return this.getDeclAnnotation(element, FromStubFile.class) != null; - } - - /** - * Returns true if the element is from bytecode and the if the element did not appear in a stub - * file. Currently only works for methods, constructors, and fields. - */ - public boolean isFromByteCode(Element element) { - if (isFromStubFile(element)) { - return false; - } - return ElementUtils.isElementFromByteCode(element); - } - - /** - * Returns true if redundancy between a stub file and bytecode should be reported. - * - *

          For most type systems the default behavior of returning true is correct. For subcheckers, - * redundancy in one of the type hierarchies can be ok. Such implementations should return false. - * - * @return whether to warn about redundancy between a stub file and bytecode - */ - public boolean shouldWarnIfStubRedundantWithBytecode() { - return true; - } - - /** - * Returns the actual annotation mirror used to annotate this element, whose name equals the - * passed annotation class (or is an alias for it). Looks in the same places specified by {@link - * #getDeclAnnotation(Element, Class)}. Returns null if none exists. May return the canonical - * annotation that annotationName is an alias for. - * - *

          This is the private implementation of the same-named, public method. - * - *

          An option is provided to not check for aliases of annotations. For example, an annotated - * type factory may use aliasing for a pair of annotations for convenience while needing in some - * cases to determine a strict ordering between them, such as when determining whether the - * annotations on an overrider method are more specific than the annotations of an overridden - * method. - * - * @param elt the element to retrieve the annotation from - * @param annoClass the class of the annotation to retrieve - * @param checkAliases whether to return an annotation mirror for an alias of the requested - * annotation class name - * @return the annotation mirror for the requested annotation, or null if not found - */ - private @Nullable AnnotationMirror getDeclAnnotation( - Element elt, Class annoClass, boolean checkAliases) { - return getDeclAnnotation(elt, annoClass.getCanonicalName(), checkAliases); - } - - /** - * Returns the actual annotation mirror used to annotate this element, whose name equals the - * passed canonical annotation name (or is an alias for it). Returns null if none exists. May - * return the canonical annotation that annotationName is an alias for. - * - *

          An option is provided not to check for aliases of annotations. For example, an annotated - * type factory may use aliasing for a pair of annotations for convenience while needing in some - * cases to determine a strict ordering between them, such as when determining whether the - * annotations on an overrider method are more specific than the annotations of an overridden - * method. - * - * @param elt the element to retrieve the annotation from - * @param annoName the canonical annotation name to retrieve - * @param checkAliases whether to return an annotation mirror for an alias of the requested - * annotation class name - * @return the annotation mirror for the requested annotation, or null if not found - */ - private AnnotationMirror getDeclAnnotation( - Element elt, @FullyQualifiedName String annoName, boolean checkAliases) { - AnnotationMirrorSet declAnnos = getDeclAnnotations(elt); - - for (AnnotationMirror am : declAnnos) { - if (AnnotationUtils.areSameByName(am, annoName)) { - return am; - } - } - if (!checkAliases) { - return null; - } - // Look through aliases. - Map<@FullyQualifiedName String, AnnotationMirror> aliases = declAliases.get(annoName); - if (aliases == null) { - return null; - } - for (AnnotationMirror am : declAnnos) { - AnnotationMirror match = aliases.get(AnnotationUtils.annotationName(am)); - if (match != null) { - return match; - } - } + /** Parses ajava files and stores annotations on public elements from ajava files. */ + public final AnnotationFileElementTypes ajavaTypes; - // Not found. - return null; - } - - /** - * Returns all of the declaration annotations on this element including annotations: - * - *

            - *
          • on the element - *
          • written in stubfiles - *
          • inherited from overridden methods, (see {@link InheritedAnnotation}) - *
          • inherited from superclasses or super interfaces (see {@link Inherited}) - *
          - * - *

          This method returns the actual annotations not their aliases. {@link - * #getDeclAnnotation(Element, Class)} returns aliases. - * - * @param elt the element for which to determine annotations - * @return all of the declaration annotations on this element, written in stub files, or inherited - */ - public AnnotationMirrorSet getDeclAnnotations(Element elt) { - AnnotationMirrorSet cachedValue = cacheDeclAnnos.get(elt); - if (cachedValue != null) { - // Found in cache, return result. - return cachedValue; - } - - AnnotationMirrorSet results = new AnnotationMirrorSet(); - // Retrieving the annotations from the element. - // This includes annotations inherited from superclasses, but not superinterfaces or - // overridden methods. - List fromEle = elements.getAllAnnotationMirrors(elt); - for (AnnotationMirror annotation : fromEle) { - try { - results.add(annotation); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // If a CompletionFailure occurs, issue a warning. - checker.reportWarning( - annotation.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(elt), - annotation); - } - } + /** + * If type checking a Java file, stores annotations read from an ajava file for that class if + * one exists. Unlike {@link #ajavaTypes}, which only stores annotations on public elements, + * this stores annotations on all element locations such as in anonymous class bodies. + */ + protected @Nullable AnnotationFileElementTypes currentFileAjavaTypes; - // Add annotations from annotation files. - results.addAll(stubTypes.getDeclAnnotations(elt)); - results.addAll(ajavaTypes.getDeclAnnotations(elt)); - if (currentFileAjavaTypes != null) { - results.addAll(currentFileAjavaTypes.getDeclAnnotations(elt)); - } - - if (elt.getKind() == ElementKind.METHOD) { - // Retrieve the annotations from the overridden method's element. - inheritOverriddenDeclAnnos((ExecutableElement) elt, results); - } else if (ElementUtils.isTypeDeclaration(elt)) { - inheritOverriddenDeclAnnosFromTypeDecl(elt.asType(), results); - } - - // Add the element and its annotations to the cache. - if (!stubTypes.isParsing() - && !ajavaTypes.isParsing() - && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { - cacheDeclAnnos.put(elt, results); - } - return results; - } - - /** - * Adds into {@code results} the inherited declaration annotations found in all elements of the - * super types of {@code typeMirror}. (Both superclasses and superinterfaces.) - * - * @param typeMirror type - * @param results the set of AnnotationMirrors to which this method adds declarations annotations - */ - private void inheritOverriddenDeclAnnosFromTypeDecl( - TypeMirror typeMirror, AnnotationMirrorSet results) { - List superTypes = types.directSupertypes(typeMirror); - for (TypeMirror superType : superTypes) { - TypeElement elt = TypesUtils.getTypeElement(superType); - if (elt == null) { - continue; - } - AnnotationMirrorSet superAnnos = getDeclAnnotations(elt); - for (AnnotationMirror annotation : superAnnos) { - List annotationsOnAnnotation; - try { - annotationsOnAnnotation = - annotation.getAnnotationType().asElement().getAnnotationMirrors(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. - checker.reportWarning( - annotation.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(elt), - annotation); - continue; - } - if (containsSameByClass(annotationsOnAnnotation, Inherited.class) - || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) { - addOrMerge(results, annotation); - } - } - } - } - - /** - * Adds into {@code results} the declaration annotations found in all elements that the method - * element {@code elt} overrides. - * - * @param elt method element - * @param results {@code elt} local declaration annotations. The ones found in stub files and in - * the element itself. - */ - private void inheritOverriddenDeclAnnos(ExecutableElement elt, AnnotationMirrorSet results) { - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, this, elt); - - if (overriddenMethods != null) { - for (ExecutableElement superElt : overriddenMethods.values()) { - AnnotationMirrorSet superAnnos = getDeclAnnotations(superElt); - - for (AnnotationMirror annotation : superAnnos) { - List annotationsOnAnnotation; - try { - annotationsOnAnnotation = - annotation.getAnnotationType().asElement().getAnnotationMirrors(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. - checker.reportWarning( - annotation.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(elt), - annotation); - continue; - } - if (containsSameByClass(annotationsOnAnnotation, InheritedAnnotation.class) - || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) { - addOrMerge(results, annotation); - } - } - } - } - } - - private void addOrMerge(AnnotationMirrorSet results, AnnotationMirror annotation) { - if (AnnotationUtils.containsSameByName(results, annotation)) { - /* - * TODO: feature request: figure out a way to merge multiple annotations - * of the same kind. For some annotations this might mean merging some - * arrays, for others it might mean converting a single annotation into a - * container annotation. We should define a protected method for subclasses - * to adapt the behavior. - * For now, do nothing and just take the first, most concrete, annotation. - AnnotationMirror prev = null; - for (AnnotationMirror an : results) { - if (AnnotationUtils.areSameByName(an, annotation)) { - prev = an; - break; - } - } - results.remove(prev); - AnnotationMirror merged = ...; - results.add(merged); - */ - } else { - results.add(annotation); - } - } - - /** - * Returns a list of all declaration annotations used to annotate the element, which have a - * meta-annotation (i.e., an annotation on that annotation) with class {@code - * metaAnnotationClass}. - * - * @param element the element for which to determine annotations - * @param metaAnnotationClass the class of the meta-annotation that needs to be present - * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at - * {@code element}, and {@code metaAnno} is the annotation mirror (of type {@code - * metaAnnotationClass}) used to meta-annotate the declaration of {@code anno} - */ - public List> getDeclAnnotationWithMetaAnnotation( - Element element, Class metaAnnotationClass) { - List> result = new ArrayList<>(); - AnnotationMirrorSet annotationMirrors = getDeclAnnotations(element); - - for (AnnotationMirror candidate : annotationMirrors) { - List metaAnnotationsOnAnnotation; - try { - metaAnnotationsOnAnnotation = - candidate.getAnnotationType().asElement().getAnnotationMirrors(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Fix for Issue 309: If a CompletionFailure occurs, issue a warning. - // I didn't find a nicer alternative to check whether the Symbol can be completed. - // The completer field of a Symbol might be non-null also in successful cases. - // Issue a warning (exception only happens once) and continue. - checker.reportWarning( - candidate.getAnnotationType().asElement(), - "annotation.not.completed", - ElementUtils.getQualifiedName(element), - candidate); - continue; - } - // First call copier, if exception, continue normal modula laws. - for (AnnotationMirror ma : metaAnnotationsOnAnnotation) { - if (areSameByClass(ma, metaAnnotationClass)) { - // This candidate has the right kind of meta-annotation. - // It might be a real contract, or a list of contracts. - if (isListForRepeatedAnnotation(candidate)) { - @SuppressWarnings("deprecation") // concrete annotation class is not known - List wrappedCandidates = - AnnotationUtils.getElementValueArray( - candidate, "value", AnnotationMirror.class, false); - for (AnnotationMirror wrappedCandidate : wrappedCandidates) { - result.add(IPair.of(wrappedCandidate, ma)); - } - } else { - result.add(IPair.of(candidate, ma)); - } - } - } - } - return result; - } - - /** Cache for {@link #isListForRepeatedAnnotation}. */ - private final Map isListForRepeatedAnnotationCache = new HashMap<>(); - - /** - * Returns true if the given annotation is a wrapper for multiple repeated annotations. - * - * @param a an annotation that might be a wrapper - * @return true if the argument is a wrapper for multiple repeated annotations - */ - private boolean isListForRepeatedAnnotation(AnnotationMirror a) { - DeclaredType annotationType = a.getAnnotationType(); - Boolean resultObject = isListForRepeatedAnnotationCache.get(annotationType); - if (resultObject != null) { - return resultObject; - } - boolean result = isListForRepeatedAnnotationImplementation(annotationType); - isListForRepeatedAnnotationCache.put(annotationType, result); - return result; - } - - /** - * Returns true if the annotation is a wrapper for multiple repeated annotations. - * - * @param annotationType the declaration of the annotation to test - * @return true if the annotation is a wrapper for multiple repeated annotations - */ - private boolean isListForRepeatedAnnotationImplementation(DeclaredType annotationType) { - TypeMirror enclosingType = annotationType.getEnclosingType(); - if (enclosingType == null) { - return false; - } - if (!annotationType.asElement().getSimpleName().contentEquals("List")) { - return false; - } - List annoElements = annotationType.asElement().getEnclosedElements(); - if (annoElements.size() != 1) { - return false; - } - // TODO: should check that the type of the single element is: "array of enclosingType". - return true; - } - - /** - * Returns a list of all annotations used to annotate this element, which have a meta-annotation - * (i.e., an annotation on that annotation) with class {@code metaAnnotationClass}. - * - * @param element the element at which to look for annotations - * @param metaAnnotationClass the class of the meta-annotation that needs to be present - * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror at - * {@code element}, and {@code metaAnno} is the annotation mirror used to annotate {@code - * anno}. - */ - public List> getAnnotationWithMetaAnnotation( - Element element, Class metaAnnotationClass) { - AnnotationMirrorSet annotationMirrors = new AnnotationMirrorSet(); - // Consider real annotations. - annotationMirrors.addAll(getAnnotatedType(element).getAnnotations()); - // Consider declaration annotations - annotationMirrors.addAll(getDeclAnnotations(element)); - - List> result = new ArrayList<>(); - - // Go through all annotations found. - for (AnnotationMirror annotation : annotationMirrors) { - List annotationsOnAnnotation = - annotation.getAnnotationType().asElement().getAnnotationMirrors(); - for (AnnotationMirror a : annotationsOnAnnotation) { - if (areSameByClass(a, metaAnnotationClass)) { - result.add(IPair.of(annotation, a)); - } - } - } - return result; - } - - /** - * Whether or not the {@code annotatedTypeMirror} has a qualifier parameter. - * - * @param annotatedTypeMirror the type to check - * @param top the top of the hierarchy to check - * @return true if the type has a qualifier parameter - */ - public boolean hasQualifierParameterInHierarchy( - AnnotatedTypeMirror annotatedTypeMirror, AnnotationMirror top) { - return AnnotationUtils.containsSame(getQualifierParameterHierarchies(annotatedTypeMirror), top); - } - - /** - * Whether or not the {@code element} has a qualifier parameter. - * - * @param element element to check - * @param top the top of the hierarchy to check - * @return true if the type has a qualifier parameter - */ - public boolean hasQualifierParameterInHierarchy(@Nullable Element element, AnnotationMirror top) { - if (element == null) { - return false; - } - return AnnotationUtils.containsSame(getQualifierParameterHierarchies(element), top); - } - - /** - * Returns whether the {@code HasQualifierParameter} annotation was explicitly written on {@code - * element} for the hierarchy given by {@code top}. - * - * @param element the Element to check - * @param top the top qualifier for the hierarchy to check - * @return whether the class given by {@code element} has been explicitly annotated with {@code - * HasQualifierParameter} for the given hierarchy - */ - public boolean hasExplicitQualifierParameterInHierarchy(Element element, AnnotationMirror top) { - return AnnotationUtils.containsSame( - getSupportedAnnotationsInElementAnnotation( - element, HasQualifierParameter.class, hasQualifierParameterValueElement), - top); - } - - /** - * Returns whether the {@code NoQualifierParameter} annotation was explicitly written on {@code - * element} for the hierarchy given by {@code top}. - * - * @param element the Element to check - * @param top the top qualifier for the hierarchy to check - * @return whether the class given by {@code element} has been explicitly annotated with {@code - * NoQualifierParameter} for the given hierarchy - */ - public boolean hasExplicitNoQualifierParameterInHierarchy(Element element, AnnotationMirror top) { - return AnnotationUtils.containsSame( - getSupportedAnnotationsInElementAnnotation( - element, NoQualifierParameter.class, noQualifierParameterValueElement), - top); - } - - /** - * Returns the set of top annotations representing all the hierarchies for which this type has a - * qualifier parameter. - * - * @param annotatedType the type to check - * @return the set of top annotations representing all the hierarchies for which this type has a - * qualifier parameter - */ - public AnnotationMirrorSet getQualifierParameterHierarchies(AnnotatedTypeMirror annotatedType) { - while (annotatedType.getKind() == TypeKind.TYPEVAR - || annotatedType.getKind() == TypeKind.WILDCARD) { - if (annotatedType.getKind() == TypeKind.TYPEVAR) { - annotatedType = ((AnnotatedTypeVariable) annotatedType).getUpperBound(); - } else if (annotatedType.getKind() == TypeKind.WILDCARD) { - annotatedType = ((AnnotatedWildcardType) annotatedType).getSuperBound(); - } - } + /** + * A cache used to store elements whose declaration annotations have already been stored by + * calling the method {@link #getDeclAnnotations(Element)}. + */ + private final Map cacheDeclAnnos; - if (annotatedType.getKind() != TypeKind.DECLARED) { - return AnnotationMirrorSet.emptySet(); - } + /** + * A set containing declaration annotations that should be inherited. A declaration annotation + * will be inherited if it is in this set, or if it has the + * meta-annotation @InheritedAnnotation. + */ + private final AnnotationMirrorSet inheritedAnnotations = new AnnotationMirrorSet(); - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) annotatedType; - Element element = declaredType.getUnderlyingType().asElement(); - if (element == null) { - return AnnotationMirrorSet.emptySet(); - } - return getQualifierParameterHierarchies(element); - } + /** The checker to use for option handling and resource management. */ + protected final BaseTypeChecker checker; - /** - * Returns the set of top annotations representing all the hierarchies for which this element has - * a qualifier parameter. - * - * @param element the Element to check - * @return the set of top annotations representing all the hierarchies for which this element has - * a qualifier parameter - */ - public AnnotationMirrorSet getQualifierParameterHierarchies(Element element) { - if (!ElementUtils.isTypeDeclaration(element)) { - return AnnotationMirrorSet.emptySet(); - } + /** + * Scans all parts of the {@link AnnotatedTypeMirror} so that all of its fields are initialized. + */ + private final SimpleAnnotatedTypeScanner atmInitializer = + new SimpleAnnotatedTypeScanner<>((type1, q) -> null); - AnnotationMirrorSet found = new AnnotationMirrorSet(); - found.addAll( - getSupportedAnnotationsInElementAnnotation( - element, HasQualifierParameter.class, hasQualifierParameterValueElement)); - AnnotationMirrorSet hasQualifierParameterTops = new AnnotationMirrorSet(); - PackageElement packageElement = ElementUtils.enclosingPackage(element); + /** + * True if all methods should be assumed to be @SideEffectFree, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeSideEffectFree; - // Traverse all packages containing this element. - while (packageElement != null) { - AnnotationMirrorSet packageDefaultTops = - getSupportedAnnotationsInElementAnnotation( - packageElement, HasQualifierParameter.class, hasQualifierParameterValueElement); - hasQualifierParameterTops.addAll(packageDefaultTops); + /** + * True if all methods should be assumed to be @Deterministic, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumeDeterministic; - packageElement = ElementUtils.parentPackage(packageElement, elements); - } + /** + * True if all getter methods should be assumed to be @Pure, for the purposes of + * org.checkerframework.dataflow analysis. + */ + private final boolean assumePureGetters; - AnnotationMirrorSet noQualifierParamClasses = - getSupportedAnnotationsInElementAnnotation( - element, NoQualifierParameter.class, noQualifierParameterValueElement); - for (AnnotationMirror anno : hasQualifierParameterTops) { - if (!AnnotationUtils.containsSame(noQualifierParamClasses, anno)) { - found.add(anno); - } - } + /** True if -AmergeStubsWithSource was provided on the command line. */ + private final boolean mergeStubsWithSource; - return found; - } - - /** - * Returns a set of supported annotation mirrors corresponding to the annotation classes listed in - * the value element of an annotation with class {@code annoClass} on {@code element}. - * - * @param element the Element to check - * @param annoClass the class for an annotation that's written on elements, whose value element is - * a list of annotation classes. It is always HasQualifierParameter or NoQualifierParameter - * @param valueElement the {@code value} field/element of an annotation with class {@code - * annoClass} - * @return the set of supported annotations with classes listed in the value element of an - * annotation with class {@code annoClass} on the {@code element}. Returns an empty set if - * {@code annoClass} is not written on {@code element} or {@code element} is null. - */ - private AnnotationMirrorSet getSupportedAnnotationsInElementAnnotation( - @Nullable Element element, - Class annoClass, - ExecutableElement valueElement) { - if (element == null) { - return AnnotationMirrorSet.emptySet(); - } - // TODO: caching - AnnotationMirror annotation = getDeclAnnotation(element, annoClass); - if (annotation == null) { - return AnnotationMirrorSet.emptySet(); - } - - AnnotationMirrorSet found = new AnnotationMirrorSet(); - List<@CanonicalName Name> qualClasses = - AnnotationUtils.getElementValueClassNames(annotation, valueElement); - for (Name qual : qualClasses) { - AnnotationMirror annotationMirror = AnnotationBuilder.fromName(elements, qual); - if (isSupportedQualifier(annotationMirror)) { - found.add(annotationMirror); - } - } - return found; - } - - /** - * A scanner that replaces annotations in one type with annotations from another. Used by {@link - * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror)} and {@link - * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. - */ - private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); - - /** - * Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code from} - * will be used everywhere they exist, but annotations in {@code to} will be kept anywhere that - * {@code from} is unannotated. - * - * @param from the annotated type mirror from which to take new annotations - * @param to the annotated type mirror to which the annotations will be added - */ - public void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - annotatedTypeReplacer.visit(from, to); - } - - /** - * Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}. - * Annotations from {@code from} will be used everywhere they exist, but annotations in {@code to} - * will be kept anywhere that {@code from} is unannotated. - * - * @param from the annotated type mirror from which to take new annotations - * @param to the annotated type mirror to which the annotations will be added - * @param top the top type of the hierarchy whose annotations will be added - */ - public void replaceAnnotations( - AnnotatedTypeMirror from, AnnotatedTypeMirror to, AnnotationMirror top) { - annotatedTypeReplacer.setTop(top); - annotatedTypeReplacer.visit(from, to); - annotatedTypeReplacer.setTop(null); - } - - /** The implementation of the visitor for #containsCapturedTypes. */ - private final SimpleAnnotatedTypeScanner containsCapturedTypes = - new SimpleAnnotatedTypeScanner<>( - (type, p) -> TypesUtils.isCapturedTypeVariable(type.getUnderlyingType()), - Boolean::logicalOr, - false); - - /** - * Returns true if {@code type} contains any captured type variables. - * - * @param type type to check - * @return true if {@code type} contains any captured type variables - */ - public boolean containsCapturedTypes(AnnotatedTypeMirror type) { - return containsCapturedTypes.visit(type); - } - - /** - * Returns the function type that this member reference targets. - * - *

          The function type is the type of the single method declared in the functional interface - * adapted as if it were invoked using the functional interface as the receiver expression. - * - *

          The target type of a member reference is the type to which it is assigned or casted. - * - * @param tree member reference tree - * @return the function type that this method reference targets - */ - public AnnotatedExecutableType getFunctionTypeFromTree(MemberReferenceTree tree) { - return getFnInterfaceFromTree(tree).second; - } - - /** - * Returns the function type that this lambda targets. - * - *

          The function type is the type of the single method declared in the functional interface - * adapted as if it were invoked using the functional interface as the receiver expression. - * - *

          The target type of a lambda is the type to which it is assigned or casted. - * - * @param tree lambda expression tree - * @return the function type that this lambda targets - */ - public AnnotatedExecutableType getFunctionTypeFromTree(LambdaExpressionTree tree) { - return getFnInterfaceFromTree(tree).second; - } - - /** - * Returns the functional interface and the function type that this lambda or member references - * targets. - * - *

          The function type is the type of the single method declared in the functional interface - * adapted as if it were invoked using the functional interface as the receiver expression. - * - *

          The target type of a lambda or a method reference is the type to which it is assigned or - * casted. - * - * @param tree lambda expression tree or member reference tree - * @return the functional interface and the function type that this method reference or lambda - * targets - */ - public IPair getFnInterfaceFromTree(Tree tree) { - // Functional interface - // This is the target type of `tree`. - AnnotatedTypeMirror functionalInterfaceType = getFunctionalInterfaceType(tree); - if (functionalInterfaceType.getKind() == TypeKind.DECLARED) { - functionalInterfaceType = - makeGroundTargetType( - (AnnotatedDeclaredType) functionalInterfaceType, - (DeclaredType) TreeUtils.typeOf(tree)); - } - - // Functional method - ExecutableElement fnElement = TreeUtils.findFunction(tree, processingEnv); - - // Function type - AnnotatedExecutableType functionType = - AnnotatedTypes.asMemberOf(types, this, functionalInterfaceType, fnElement); - return IPair.of(functionalInterfaceType, functionType); - } - - /** - * Get the AnnotatedDeclaredType for the FunctionalInterface from assignment context of the method - * reference or lambda expression which may be a variable assignment, a method call, or a cast. - * - *

          The assignment context is not always correct, so we must search up the AST. It will - * recursively search for lambdas nested in lambdas. - * - * @param tree the tree of the lambda or method reference - * @return the functional interface type or a type argument from a raw type - */ - private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) { - TreePath parentPath = getPath(tree).getParentPath(); - Tree parentTree = parentPath.getLeaf(); - switch (parentTree.getKind()) { - case PARENTHESIZED: - return getFunctionalInterfaceType(parentTree); - - case TYPE_CAST: - TypeCastTree cast = (TypeCastTree) parentTree; - assertIsFunctionalInterface(trees.getTypeMirror(getPath(cast.getType())), parentTree, tree); - AnnotatedTypeMirror castATM = getAnnotatedType(cast.getType()); - if (castATM.getKind() == TypeKind.INTERSECTION) { - AnnotatedIntersectionType itype = (AnnotatedIntersectionType) castATM; - for (AnnotatedTypeMirror t : itype.directSupertypes()) { - if (TypesUtils.isFunctionalInterface(t.getUnderlyingType(), getProcessingEnv())) { - return t; + /** + * Initializes all fields of {@code type}. + * + * @param type annotated type mirror + */ + public void initializeAtm(AnnotatedTypeMirror type) { + atmInitializer.visit(type); + } + + /** Map keys are canonical names of aliased annotations. */ + private final Map<@FullyQualifiedName String, Alias> aliases = new HashMap<>(); + + /** + * A map from the canonical name of a declaration annotation to the mapping of the canonical + * name of a declaration annotation with the same meaning (an alias) to the annotation mirror + * that should be used instead (an instance of the canonical declaration annotation). + */ + // A further generalization is to do something similar to `aliases`, where we allow copying + // elements from the alias to the canonical annotation. + private final Map<@FullyQualifiedName String, Map<@FullyQualifiedName String, AnnotationMirror>> + declAliases = new HashMap<>(); + + /** + * Information about one annotation alias. + * + *

          The information is either an AnotationMirror that can be used directly, or information for + * a builder (name and fields not to copy); see checkRep. + */ + private static class Alias { + /** The canonical annotation (or null if copyElements == true). */ + final AnnotationMirror canonical; + + /** Whether elements should be copied over when translating to the canonical annotation. */ + final boolean copyElements; + + /** The canonical annotation name (or null if copyElements == false). */ + final @CanonicalName String canonicalName; + + /** Which elements should not be copied over (or null if copyElements == false). */ + final String[] ignorableElements; + + /** + * Create an Alias with the given components. + * + * @param aliasName the alias name; only used for debugging + * @param canonical the canonical annotation + * @param copyElements whether elements should be copied over when translating to the + * canonical annotation + * @param canonicalName the canonical annotation name (or null if copyElements == false) + * @param ignorableElements elements that should not be copied over + */ + Alias( + String aliasName, + AnnotationMirror canonical, + boolean copyElements, + @Nullable @CanonicalName String canonicalName, + String[] ignorableElements) { + this.canonical = canonical; + this.copyElements = copyElements; + this.canonicalName = canonicalName; + this.ignorableElements = ignorableElements; + checkRep(aliasName); + } + + /** + * Throw an exception if this object is malformed. + * + * @param aliasName the alias name; only used for diagnostic messages + */ + void checkRep(String aliasName) { + if (copyElements) { + if (!(canonical == null && canonicalName != null && ignorableElements != null)) { + throw new BugInCF( + "Bad Alias for %s: [canonical=%s] copyElements=%s canonicalName=%s" + + " ignorableElements=%s", + aliasName, canonical, copyElements, canonicalName, ignorableElements); + } + } else { + if (!(canonical != null && canonicalName == null && ignorableElements == null)) { + throw new BugInCF( + "Bad Alias for %s: canonical=%s copyElements=%s [canonicalName=%s" + + " ignorableElements=%s]", + aliasName, canonical, copyElements, canonicalName, ignorableElements); + } + } + } + } + + /** Unique ID counter; for debugging purposes. */ + private static int uidCounter = 0; + + /** Unique ID of the current object; for debugging purposes. */ + public final int uid; + + /** + * Object that is used to resolve reflective method calls, if reflection resolution is turned + * on. + */ + protected ReflectionResolver reflectionResolver; + + /** This loads type annotation classes via reflective lookup. */ + protected AnnotationClassLoader loader; + + /* NO-AFU + * Which whole-program inference output format to use, if doing whole-program inference. This + * variable would be final, but it is not set unless WPI is enabled. + */ + /* NO-AFU + public WholeProgramInference.OutputFormat wpiOutputFormat; + */ + + /** + * Should results be cached? This means that ATM.deepCopy() will be called. ATM.deepCopy() used + * to (and perhaps still does) side effect the ATM being copied. So setting this to false is not + * equivalent to setting shouldReadCache to false. + */ + public boolean shouldCache; + + /** Size of LRU cache if one isn't specified using the atfCacheSize option. */ + private static final int DEFAULT_CACHE_SIZE = 300; + + /** Mapping from a Tree to its annotated type; defaults have been applied. */ + private final Map classAndMethodTreeCache; + + /** + * Mapping from an expression tree to its annotated type; before defaults are applied, just what + * the programmer wrote. + */ + protected final Map fromExpressionTreeCache; + + /** + * Mapping from a member tree to its annotated type; before defaults are applied, just what the + * programmer wrote. + */ + protected final Map fromMemberTreeCache; + + /** + * Mapping from a type tree to its annotated type; before defaults are applied, just what the + * programmer wrote. + */ + protected final Map fromTypeTreeCache; + + /** + * Mapping from an Element to its annotated type; before defaults are applied, just what the + * programmer wrote. + */ + private final Map elementCache; + + /** Mapping from an Element to the source Tree of the declaration. */ + private final Map elementToTreeCache; + + /** Mapping from a Tree to its TreePath. Shared between all instances. */ + private final TreePathCacher treePathCache; + + /** Whether to ignore type arguments from raw types. */ + public final boolean ignoreRawTypeArguments; + + /** The Object.getClass method. */ + protected final ExecutableElement objectGetClass; + + /** Size of the annotationClassNames cache. */ + private static final int ANNOTATION_CACHE_SIZE = 500; + + /** Maps classes representing AnnotationMirrors to their canonical names. */ + private final Map, @CanonicalName String> annotationClassNames; + + /** An annotated type of the declaration of {@link Iterable} without any annotations. */ + private AnnotatedDeclaredType iterableDeclType; + + /** + * If the option "lspTypeInfo" is defined, this presenter will report the type information of + * every type-checked class. This information can be visualized by an editor/IDE that supports + * LSP. + */ + protected final TypeInformationPresenter typeInformationPresenter; + + /** + * Constructs a factory from the given checker. + * + *

          A subclass must call postInit at the end of its constructor. postInit must be the last + * call in the constructor or else types from stub files may not be created as expected. + * + * @param checker the {@link SourceChecker} to which this factory belongs + */ + public AnnotatedTypeFactory(BaseTypeChecker checker) { + uid = ++uidCounter; + this.processingEnv = checker.getProcessingEnvironment(); + this.checker = checker; + this.assumeSideEffectFree = + checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"); + this.assumeDeterministic = + checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"); + this.assumePureGetters = checker.hasOption("assumePureGetters"); + + this.trees = Trees.instance(processingEnv); + this.elements = processingEnv.getElementUtils(); + this.types = processingEnv.getTypeUtils(); + + this.stubTypes = new AnnotationFileElementTypes(this); + this.ajavaTypes = new AnnotationFileElementTypes(this); + this.currentFileAjavaTypes = null; + + this.cacheDeclAnnos = new HashMap<>(); + + // get the shared instance from the checker + this.treePathCache = checker.getTreePathCacher(); + + this.shouldCache = !checker.hasOption("atfDoNotCache"); + if (shouldCache) { + int cacheSize = getCacheSize(); + this.classAndMethodTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.fromExpressionTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.fromMemberTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.fromTypeTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.elementCache = CollectionsPlume.createLruCache(cacheSize); + this.elementToTreeCache = CollectionsPlume.createLruCache(cacheSize); + this.annotationClassNames = + Collections.synchronizedMap( + CollectionsPlume.createLruCache(ANNOTATION_CACHE_SIZE)); + } else { + this.classAndMethodTreeCache = null; + this.fromExpressionTreeCache = null; + this.fromMemberTreeCache = null; + this.fromTypeTreeCache = null; + this.elementCache = null; + this.elementToTreeCache = null; + this.annotationClassNames = null; + } + + this.typeFormatter = createAnnotatedTypeFormatter(); + this.annotationFormatter = createAnnotationFormatter(); + this.typeInformationPresenter = createTypeInformationPresenter(); + + // Alias provided via -AaliasedTypeAnnos command-line option. + // This can only be used for annotations whose attributes have the same names as in the + // canonical annotation, e.g. this will not be usable to declare an alias @Regex(index = 5) + // for @Regex(value = 5). + if (checker.hasOption("aliasedTypeAnnos")) { + String aliasesOption = checker.getOption("aliasedTypeAnnos"); + String[] annos = aliasesOption.split(";"); + for (String alias : annos) { + IPair, @FullyQualifiedName String[]> aliasPair = + parseAliasesFromString(alias); + for (@FullyQualifiedName String a : aliasPair.second) { + addAliasedTypeAnnotation(a, aliasPair.first, true); + } + } + } + + // Alias provided via -AaliasedDeclAnnos command-line option. + // This can only be used for annotations without attributes, + // e.g. this will not be usable to declare an alias for @EnsuresNonNull(...). + if (checker.hasOption("aliasedDeclAnnos")) { + String aliasesOption = checker.getOption("aliasedDeclAnnos"); + String[] annos = aliasesOption.split(";"); + for (String alias : annos) { + IPair, @FullyQualifiedName String[]> aliasPair = + parseAliasesFromString(alias); + AnnotationMirror anno = AnnotationBuilder.fromClass(elements, aliasPair.first); + for (String a : aliasPair.second) { + addAliasedDeclAnnotation(a, aliasPair.first.getCanonicalName(), anno); + } + } + } + + /* NO-AFU + if (checker.hasOption("infer")) { + checkInvalidOptionsInferSignatures(); + String inferArg = checker.getOption("infer"); + // No argument means "jaifs", for (temporary) backwards compatibility. + if (inferArg == null) { + inferArg = "jaifs"; + } + switch (inferArg) { + case "stubs": + wpiOutputFormat = WholeProgramInference.OutputFormat.STUB; + break; + case "jaifs": + wpiOutputFormat = WholeProgramInference.OutputFormat.JAIF; + break; + case "ajava": + wpiOutputFormat = WholeProgramInference.OutputFormat.AJAVA; + break; + default: + throw new UserError( + "Bad argument -Ainfer=" + + inferArg + + " should be one of: -Ainfer=jaifs, -Ainfer=stubs, -Ainfer=ajava"); + } + boolean showWpiFailedInferences = checker.hasOption("showWpiFailedInferences"); + boolean inferOutputOriginal = checker.hasOption("inferOutputOriginal"); + if (inferOutputOriginal && wpiOutputFormat != WholeProgramInference.OutputFormat.AJAVA) { + checker.message( + Diagnostic.Kind.WARNING, + "-AinferOutputOriginal only works with -Ainfer=ajava, so it is being ignored."); + } + if (wpiOutputFormat == WholeProgramInference.OutputFormat.AJAVA) { + wholeProgramInference = + new WholeProgramInferenceImplementation( + this, + new WholeProgramInferenceJavaParserStorage(this, inferOutputOriginal), + showWpiFailedInferences); + } else { + wholeProgramInference = + new WholeProgramInferenceImplementation( + this, new WholeProgramInferenceScenesStorage(this), showWpiFailedInferences); + } + if (!checker.hasOption("warns")) { + // Without -Awarns, the inference output may be incomplete, because javac halts + // after issuing an error. + checker.message(Diagnostic.Kind.ERROR, "Do not supply -Ainfer without -Awarns"); + } + } else { + wholeProgramInference = null; + } + */ + + ignoreRawTypeArguments = checker.getBooleanOption("ignoreRawTypeArguments", true); + + objectGetClass = TreeUtils.getMethod("java.lang.Object", "getClass", 0, processingEnv); + + this.debugStubParser = checker.hasOption("stubDebug"); + + annotatedForValueElement = + TreeUtils.getMethod(AnnotatedFor.class, "value", 0, processingEnv); + ensuresQualifierExpressionElement = + TreeUtils.getMethod(EnsuresQualifier.class, "expression", 0, processingEnv); + ensuresQualifierListValueElement = + TreeUtils.getMethod(EnsuresQualifier.List.class, "value", 0, processingEnv); + ensuresQualifierIfExpressionElement = + TreeUtils.getMethod(EnsuresQualifierIf.class, "expression", 0, processingEnv); + ensuresQualifierIfResultElement = + TreeUtils.getMethod(EnsuresQualifierIf.class, "result", 0, processingEnv); + ensuresQualifierIfListValueElement = + TreeUtils.getMethod(EnsuresQualifierIf.List.class, "value", 0, processingEnv); + fieldInvariantFieldElement = + TreeUtils.getMethod(FieldInvariant.class, "field", 0, processingEnv); + fieldInvariantQualifierElement = + TreeUtils.getMethod(FieldInvariant.class, "qualifier", 0, processingEnv); + hasQualifierParameterValueElement = + TreeUtils.getMethod(HasQualifierParameter.class, "value", 0, processingEnv); + methodValClassNameElement = + TreeUtils.getMethod(MethodVal.class, "className", 0, processingEnv); + methodValMethodNameElement = + TreeUtils.getMethod(MethodVal.class, "methodName", 0, processingEnv); + methodValParamsElement = TreeUtils.getMethod(MethodVal.class, "params", 0, processingEnv); + noQualifierParameterValueElement = + TreeUtils.getMethod(NoQualifierParameter.class, "value", 0, processingEnv); + requiresQualifierExpressionElement = + TreeUtils.getMethod(RequiresQualifier.class, "expression", 0, processingEnv); + requiresQualifierListValueElement = + TreeUtils.getMethod(RequiresQualifier.List.class, "value", 0, processingEnv); + + requiresQualifierTM = + ElementUtils.getTypeElement(processingEnv, RequiresQualifier.class).asType(); + requiresQualifierListTM = + ElementUtils.getTypeElement(processingEnv, RequiresQualifier.List.class).asType(); + ensuresQualifierTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.class).asType(); + ensuresQualifierListTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifier.List.class).asType(); + ensuresQualifierIfTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.class).asType(); + ensuresQualifierIfListTM = + ElementUtils.getTypeElement(processingEnv, EnsuresQualifierIf.List.class).asType(); + + mergeStubsWithSource = checker.hasOption("mergeStubsWithSource"); + } + + /** + * Parse a string in the format {@code + * FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2} to a pair of {@code + * (FQN.canonical.Qualifier.class, ["FQN.alias1.Qual1", "FQN.alias2.Qual2"])}. + * + * @param alias in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2 + * @return a pair with the first argument being the canonical qualifier class and the second + * argument being the list of aliases with fully qualified names + */ + // signature is suppressed because there is no way to reason about parsed strings + @SuppressWarnings({"unchecked", "signature"}) + private IPair, @FullyQualifiedName String[]> parseAliasesFromString( + String alias) { + String[] parts = alias.split(":"); + if (parts.length != 2) { + throw new UserError( + String.format( + "Alias argument must be in the form of FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2, got %s instead.", + alias)); + } + Class canonical; + try { + canonical = (Class) Class.forName(parts[0].trim()); + } catch (ClassNotFoundException | ClassCastException ex) { + throw new UserError( + String.format("The name %s is an invalid annotation name.", parts[0])); + } + String[] aliases = parts[1].trim().split("\\s*,\\s*"); + return IPair.of(canonical, aliases); + } + + /** + * Requires that supportedQuals is non-null and non-empty and each element is a type qualifier. + * That is, no element has a {@code @Target} meta-annotation that contains something besides + * TYPE_USE or TYPE_PARAMETER. (@Target({}) is allowed.) @ + * + * @throws BugInCF If supportedQuals is empty or contaions a non-type qualifier + */ + private void checkSupportedQualsAreTypeQuals() { + if (supportedQuals == null || supportedQuals.isEmpty()) { + throw new TypeSystemError("Found no supported qualifiers."); + } + for (Class annotationClass : supportedQuals) { + // Check @Target values + ElementType[] targetValues = annotationClass.getAnnotation(Target.class).value(); + List badTargetValues = new ArrayList<>(0); + for (ElementType element : targetValues) { + if (!(element == ElementType.TYPE_USE || element == ElementType.TYPE_PARAMETER)) { + // if there's an ElementType with an enumerated value of something other + // than TYPE_USE or TYPE_PARAMETER then it isn't a valid qualifier + badTargetValues.add(element); + } + } + if (!badTargetValues.isEmpty()) { + String msg = + "The @Target meta-annotation on type qualifier " + + annotationClass.toString() + + " must not contain " + + StringsPlume.conjunction("or", badTargetValues) + + "."; + throw new TypeSystemError(msg); + } + } + } + + /* NO-AFU + * This method is called only when {@code -Ainfer} is passed as an option. It checks if another + * option that should not occur simultaneously with the whole-program inference is also passed + * as argument, and aborts the process if that is the case. For example, the whole-program + * inference process was not designed to work with conservative defaults. + * + *

          Subclasses may override this method to add more options. + */ + /* NO-AFU + protected void checkInvalidOptionsInferSignatures() { + // See Issue 683 + // https://github.com/typetools/checker-framework/issues/683 + if (checker.useConservativeDefault("source") + || checker.useConservativeDefault("bytecode")) { + throw new UserError( + "The option -Ainfer=... cannot be used together with conservative defaults."); + } + } + */ + + /** + * Actions that logically belong in the constructor, but need to run after the subclass + * constructor has completed. In particular, {@link AnnotationFileElementTypes#parseStubFiles()} + * may try to do type resolution with this AnnotatedTypeFactory. + */ + protected void postInit( + @UnderInitialization(AnnotatedTypeFactory.class) AnnotatedTypeFactory this) { + this.qualHierarchy = createQualifierHierarchy(); + if (qualHierarchy == null) { + throw new TypeSystemError( + "AnnotatedTypeFactory with null qualifier hierarchy not supported."); + } else if (!qualHierarchy.isValid()) { + throw new TypeSystemError( + "AnnotatedTypeFactory: invalid qualifier hierarchy: %s %s ", + qualHierarchy.getClass(), qualHierarchy); + } + this.typeHierarchy = createTypeHierarchy(); + this.typeVarSubstitutor = createTypeVariableSubstitutor(); + this.typeArgumentInference = createTypeArgumentInference(); + this.viewpointAdapter = createViewpointAdapter(); + this.qualifierUpperBounds = createQualifierUpperBounds(); + + // TODO: is this the best location for declaring this alias? + addAliasedDeclAnnotation( + org.jmlspecs.annotation.Pure.class, + org.checkerframework.dataflow.qual.Pure.class, + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.Pure.class)); + + // Accommodate the inability to write @InheritedAnnotation on these annotations. + addInheritedAnnotation( + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.Pure.class)); + addInheritedAnnotation( + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.SideEffectFree.class)); + addInheritedAnnotation( + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.Deterministic.class)); + addInheritedAnnotation( + AnnotationBuilder.fromClass( + elements, org.checkerframework.dataflow.qual.TerminatesExecution.class)); + + initializeReflectionResolution(); + + if (this.getClass() == AnnotatedTypeFactory.class) { + this.parseAnnotationFiles(); + } + TypeMirror iterableTypeMirror = + ElementUtils.getTypeElement(processingEnv, Iterable.class).asType(); + this.iterableDeclType = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType(iterableTypeMirror, this, true); + } + + /** + * Returns the checker associated with this factory. + * + * @return the checker associated with this factory + */ + public BaseTypeChecker getChecker() { + return checker; + } + + /** + * Returns the names of the annotation processors that are being run. + * + * @return the names of the annotation processors that are being run + */ + @SuppressWarnings("JdkObsolete") // ClassLoader.getResources returns an Enumeration + public List getCheckerNames() { + com.sun.tools.javac.util.Context context = + ((JavacProcessingEnvironment) processingEnv).getContext(); + String processorArg = Options.instance(context).get("-processor"); + if (processorArg != null) { + return SystemUtil.COMMA_SPLITTER.splitToList(processorArg); + } + try { + String filename = "META-INF/services/javax.annotation.processing.Processor"; + List result = new ArrayList<>(); + Enumeration urls = getClass().getClassLoader().getResources(filename); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + try (BufferedReader in = + new BufferedReader( + new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + result.addAll(in.lines().collect(Collectors.toList())); + } + } + return result; + } catch (IOException e) { + throw new BugInCF(e); + } + } + + /** + * Creates {@link QualifierUpperBounds} for this type factory. + * + * @return a new {@link QualifierUpperBounds} for this type factory + */ + protected QualifierUpperBounds createQualifierUpperBounds() { + return new QualifierUpperBounds(this); + } + + /** + * Return {@link QualifierUpperBounds} for this type factory. + * + * @return {@link QualifierUpperBounds} for this type factory + */ + public QualifierUpperBounds getQualifierUpperBounds() { + return qualifierUpperBounds; + } + + /* NO-AFU + * Returns the WholeProgramInference instance (may be null). + * + * @return the WholeProgramInference instance, or null + */ + /* NO-AFU + public @Nullable WholeProgramInference getWholeProgramInference() { + return wholeProgramInference; + } + */ + + /** Initialize reflection resolution. */ + protected void initializeReflectionResolution() { + if (checker.shouldResolveReflection()) { + boolean debug = "debug".equals(checker.getOption("resolveReflection")); + + MethodValChecker methodValChecker = checker.getSubchecker(MethodValChecker.class); + assert methodValChecker != null + : "AnnotatedTypeFactory: reflection resolution was requested," + + " but MethodValChecker isn't a subchecker."; + MethodValAnnotatedTypeFactory methodValATF = + (MethodValAnnotatedTypeFactory) methodValChecker.getAnnotationProvider(); + + reflectionResolver = new DefaultReflectionResolver(checker, methodValATF, debug); + } + } + + /** + * Get the current CompilationUnitTree. + * + * @return the current compilation unit being used, or null + */ + protected @Nullable CompilationUnitTree getRoot() { + return root; + } + + /** + * Set the CompilationUnitTree that should be used. + * + * @param root the new compilation unit to use + */ + public void setRoot(@Nullable CompilationUnitTree root) { + /* NO-AFU + if (root != null && wholeProgramInference != null) { + for (Tree typeDecl : root.getTypeDecls()) { + if (typeDecl.getKind() == Tree.Kind.CLASS) { + ClassTree classTree = (ClassTree) typeDecl; + wholeProgramInference.preprocessClassTree(classTree); + } + } + } + */ + + this.root = root; + // Do not clear here. Only the primary checker should clear this cache. + // treePathCache.clear(); + + if (shouldCache) { + // Clear the caches with trees because once the compilation unit changes, + // the trees may be modified and lose type arguments. + elementToTreeCache.clear(); + fromExpressionTreeCache.clear(); + fromMemberTreeCache.clear(); + fromTypeTreeCache.clear(); + classAndMethodTreeCache.clear(); + + // There is no need to clear the following cache, it is limited by cache size and it + // contents won't change between compilation units. + // elementCache.clear(); + } + + if (root != null && checker.hasOption("ajava")) { + // Search for an ajava file with annotations for the current source file and the current + // checker. It will be in a directory specified by the "ajava" option in a subdirectory + // corresponding to this file's package. For example, a file in package a.b would be in + // a subdirectory a/b. The filename is ClassName-checker.qualified.name.ajava. If such a + // file exists, read its detailed annotation data, including annotations on private + // elements. + + String packagePrefix = + root.getPackageName() != null + ? TreeUtils.nameExpressionToString(root.getPackageName()) + "." + : ""; + + // The method getName() returns a path. + String rootFile = root.getSourceFile().getName(); + String className = rootFile; + // Extract the basename. + int lastSeparator = className.lastIndexOf(File.separator); + if (lastSeparator != -1) { + className = className.substring(lastSeparator + 1); + } + // Drop the ".java" extension. + if (className.endsWith(".java")) { + className = className.substring(0, className.length() - ".java".length()); + } + + String qualifiedName = packagePrefix + className; + + // If the set candidateAjavaFiles has exactly one element after the loop, a specific + // .ajava file was supplied, with no ambiguity, and can be parsed. For an explanation, + // see the comment below about possible ambiguity. + Set candidateAjavaFiles = new HashSet<>(1); + // All .ajava files for this class + checker combo end in this string. + String ajavaEnding = + qualifiedName.replaceAll("\\.", "/") + + "-" + + checker.getClass().getCanonicalName() + + ".ajava"; + for (String ajavaLocation : checker.getStringsOption("ajava", File.pathSeparator)) { + // ajavaLocation might either be (1) a directory, or (2) the name of a specific + // ajava file. This code must handle both possible cases. + // Case (1): ajavaPath is a directory + String ajavaPath = ajavaLocation + File.separator + ajavaEnding; + File ajavaFileInDir = new File(ajavaPath); + if (ajavaFileInDir.exists()) { + // There is a candidate ajava file in one of the root directories. + candidateAjavaFiles.add(ajavaPath); + } else { + // Check case (2): ajavaPath might be a specific .ajava file. The tricky thing + // about this is that the "root" is not known, so the correct .ajava file might + // be ambiguous. Consider the following: there are two ajava files: + // ~/foo/foo/Bar-checker.ajava and ~/baz/foo/Bar-checker.ajava. Which is the + // correct one for class foo.Bar? It depends on whether there is a foo.foo.Bar + // or a baz.foo.Bar elsewhere in the project. For that reason, parsing using a + // specific file is done at the **end** of the loop, and if there is more than + // one match no file is parsed for this class and a warning is issued instead. + // The user can disambiguate by supplying a root directory, instead of specific + // files. + if (ajavaLocation.endsWith(File.separator + ajavaEnding)) { + // This is a candidate ajava file. If it is the only candidate, then it + // might be unambiguous. If not, issue a warning. + candidateAjavaFiles.add(ajavaLocation); + } + } + } + if (candidateAjavaFiles.size() == 1) { + currentFileAjavaTypes = new AnnotationFileElementTypes(this); + String ajavaPath = candidateAjavaFiles.toArray(new String[0])[0]; + try { + currentFileAjavaTypes.parseAjavaFileWithTree(ajavaPath, root); + } catch (Throwable e) { + throw new Error( + "Problem while parsing " + + ajavaPath + + " that corresponds to " + + rootFile, + e); + } + } else if (candidateAjavaFiles.size() > 1) { + checker.reportWarning( + root, "ambiguous.ajava", String.join(", ", candidateAjavaFiles)); + } + } else { + currentFileAjavaTypes = null; + } + } + + @SideEffectFree + @Override + public String toString() { + return getClass().getSimpleName() + "#" + uid; + } + + /** + * Returns the {@link QualifierHierarchy} to be used by this checker. + * + *

          The implementation builds the type qualifier hierarchy for the {@link + * #getSupportedTypeQualifiers()} using the meta-annotations found in them. The current + * implementation returns an instance of {@code NoElementQualifierHierarchy}. + * + *

          Subclasses must override this method if their qualifiers have elements; the method must + * return an implementation of {@link QualifierHierarchy}, such as {@link + * ElementQualifierHierarchy}. + * + * @return a QualifierHierarchy for this type system + */ + protected QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy( + this.getSupportedTypeQualifiers(), + elements, + (GenericAnnotatedTypeFactory) this); + } + + /** + * Returns the type qualifier hierarchy graph to be used by this processor. + * + * @see #createQualifierHierarchy() + * @return the {@link QualifierHierarchy} for this checker + */ + public final QualifierHierarchy getQualifierHierarchy() { + return qualHierarchy; + } + + /** + * Returns true if the given qualifer is one of the top annotations for the qualifer hierarchy. + * + * @param qualifier a type qualifier + * @return true if the given qualifer is one of the top annotations for the qualifer hierarchy + */ + public final boolean isTop(AnnotationMirror qualifier) { + return qualHierarchy.isTop(qualifier); + } + + /** + * Creates the type hierarchy to be used by this factory. + * + *

          Subclasses may override this method to specify new type-checking rules beyond the typical + * Java subtyping rules. + * + * @return the type relations class to check type subtyping + */ + protected TypeHierarchy createTypeHierarchy() { + return new DefaultTypeHierarchy( + checker, + getQualifierHierarchy(), + ignoreRawTypeArguments, + checker.hasOption("invariantArrays")); + } + + public final TypeHierarchy getTypeHierarchy() { + return typeHierarchy; + } + + /** + * Factory method to create a ViewpointAdapter. Subclasses should implement and instantiate a + * ViewpointAdapter subclass if viewpoint adaptation is needed for a type system. + * + * @return viewpoint adapter to perform viewpoint adaptation or null + */ + protected @Nullable ViewpointAdapter createViewpointAdapter() { + return null; + } + + /** + * TypeVariableSubstitutor provides a method to replace type parameters with their arguments. + */ + protected TypeVariableSubstitutor createTypeVariableSubstitutor() { + return new TypeVariableSubstitutor(); + } + + public TypeVariableSubstitutor getTypeVarSubstitutor() { + return typeVarSubstitutor; + } + + /** + * Creates the object that infers type arguments. + * + * @return the object that infers type arguments + */ + protected TypeArgumentInference createTypeArgumentInference() { + return new DefaultTypeArgumentInference(); + } + + public TypeArgumentInference getTypeArgumentInference() { + return typeArgumentInference; + } + + /** + * Factory method to easily change what {@link AnnotationClassLoader} is created to load type + * annotation classes. Subclasses can override this method and return a custom + * AnnotationClassLoader subclass to customize loading logic. + */ + protected AnnotationClassLoader createAnnotationClassLoader() { + return new AnnotationClassLoader(checker); + } + + /** + * Returns a mutable set of annotation classes that are supported by a checker. + * + *

          Subclasses may override this method to return a mutable set of their supported type + * qualifiers through one of the 5 approaches shown below. + * + *

          Subclasses should not call this method; they should call {@link + * #getSupportedTypeQualifiers} instead. + * + *

          By default, a checker supports all annotations located in a subdirectory called {@literal + * qual} that's located in the same directory as the checker. Note that only annotations defined + * with the {@code @Target({ElementType.TYPE_USE})} meta-annotation (and optionally with the + * additional value of {@code ElementType.TYPE_PARAMETER}, but no other {@code ElementType} + * values) are automatically considered as supported annotations. + * + *

          To support a different set of annotations than those in the {@literal qual} subdirectory, + * or that have other {@code ElementType} values, see examples below. + * + *

          In total, there are 5 ways to indicate annotations that are supported by a checker: + * + *

            + *
          1. Only support annotations located in a checker's {@literal qual} directory: + *

            This is the default behavior. Simply place those annotations within the {@literal + * qual} directory. + *

          2. Support annotations located in a checker's {@literal qual} directory and a list of + * other annotations: + *

            Place those annotations within the {@literal qual} directory, and override {@link + * #createSupportedTypeQualifiers()} by calling {@link + * #getBundledTypeQualifiers(Class...)} with a varargs parameter list of the other + * annotations. Code example: + *

            +     * {@code @Override protected Set> createSupportedTypeQualifiers() {
            +     *      return getBundledTypeQualifiers(Regex.class, PartialRegex.class, RegexBottom.class, UnknownRegex.class);
            +     *  } }
            +     * 
            + *
          3. Supporting only annotations that are explicitly listed: Override {@link + * #createSupportedTypeQualifiers()} and return a mutable set of the supported + * annotations. Code example: + *
            +     * {@code @Override protected Set> createSupportedTypeQualifiers() {
            +     *      return new HashSet>(
            +     *              Arrays.asList(A.class, B.class));
            +     *  } }
            +     * 
            + * The set of qualifiers returned by {@link #createSupportedTypeQualifiers()} must be a + * fresh, mutable set. The methods {@link #getBundledTypeQualifiers(Class...)} must return + * a fresh, mutable set + *
          + * + * @return the type qualifiers supported this processor, or an empty set if none + */ + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers(); + } + + /** + * Loads all annotations contained in the qual directory of a checker via reflection; if a + * polymorphic type qualifier exists, and an explicit array of annotations to the set of + * annotation classes. + * + *

          This method can be called in the overridden versions of {@link + * #createSupportedTypeQualifiers()} in each checker. + * + * @param explicitlyListedAnnotations a varargs array of explicitly listed annotation classes to + * be added to the returned set. For example, it is used frequently to add Bottom + * qualifiers. + * @return a mutable set of the loaded and listed annotation classes + */ + @SafeVarargs + protected final Set> getBundledTypeQualifiers( + Class... explicitlyListedAnnotations) { + return loadTypeAnnotationsFromQualDir(explicitlyListedAnnotations); + } + + /** + * Instantiates the AnnotationClassLoader and loads all annotations contained in the qual + * directory of a checker via reflection, and has the option to include an explicitly stated + * list of annotations (eg ones found in a different directory than qual). + * + *

          The annotations that are automatically loaded must have the {@link + * java.lang.annotation.Target Target} meta-annotation with the value of {@link + * ElementType#TYPE_USE} (and optionally {@link ElementType#TYPE_PARAMETER}). If it has other + * {@link ElementType} values, it won't be loaded. Other annotation classes must be explicitly + * listed even if they are in the same directory as the checker's qual directory. + * + * @param explicitlyListedAnnotations a set of explicitly listed annotation classes to be added + * to the returned set, for example, it is used frequently to add Bottom qualifiers + * @return a set of annotation class instances + */ + @SafeVarargs + @SuppressWarnings("varargs") + private final Set> loadTypeAnnotationsFromQualDir( + Class... explicitlyListedAnnotations) { + if (loader != null) { + loader.close(); + } + loader = createAnnotationClassLoader(); + + Set> annotations = loader.getBundledAnnotationClasses(); + + // add in all explicitly Listed qualifiers + if (explicitlyListedAnnotations != null) { + annotations.addAll(Arrays.asList(explicitlyListedAnnotations)); + } + + return annotations; + } + + /** + * Creates the {@link AnnotatedTypeFormatter} used by this type factory and all {@link + * AnnotatedTypeMirror}s it creates. The {@link AnnotatedTypeFormatter} is used in {@link + * AnnotatedTypeMirror#toString()} and will affect the error messages printed for checkers that + * use this type factory. + * + * @return the {@link AnnotatedTypeFormatter} to pass to all {@link AnnotatedTypeMirror}s + */ + protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() { + boolean printVerboseGenerics = checker.hasOption("printVerboseGenerics"); + return new DefaultAnnotatedTypeFormatter( + printVerboseGenerics, + // -AprintVerboseGenerics implies -AprintAllQualifiers + printVerboseGenerics || checker.hasOption("printAllQualifiers")); + } + + /** + * Return the current {@link AnnotatedTypeFormatter}. + * + * @return the current {@link AnnotatedTypeFormatter} + */ + public AnnotatedTypeFormatter getAnnotatedTypeFormatter() { + return typeFormatter; + } + + /** + * Creates the {@link AnnotationFormatter} used by this type factory. + * + * @return the {@link AnnotationFormatter} used by this type factory + */ + protected AnnotationFormatter createAnnotationFormatter() { + return new DefaultAnnotationFormatter(); + } + + /** + * Return the current {@link AnnotationFormatter}. + * + * @return the current {@link AnnotationFormatter} + */ + public AnnotationFormatter getAnnotationFormatter() { + return annotationFormatter; + } + + /** + * Creates the {@link TypeInformationPresenter} used in {@link #postProcessClassTree(ClassTree)} + * to output type information about the current class. + * + * @return the {@link TypeInformationPresenter} used by this type factory, or null + */ + protected @Nullable TypeInformationPresenter createTypeInformationPresenter() { + // TODO: look into a similar mechanism as for CFG visualization. + if (checker.hasOption("lspTypeInfo")) { + return new LspTypeInformationPresenter(this); + } else { + return null; + } + } + + /** + * Returns an immutable set of the classes corresponding to the type qualifiers supported by + * this checker. + * + *

          Subclasses cannot override this method; they should override {@link + * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. + * + * @see #createSupportedTypeQualifiers() + * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers + * are supported + */ + public final Set> getSupportedTypeQualifiers() { + if (this.supportedQuals == null) { + supportedQuals = createSupportedTypeQualifiers(); + checkSupportedQualsAreTypeQuals(); + } + return supportedQuals; + } + + /** + * Returns an immutable set of the fully qualified names of the type qualifiers supported by + * this checker. + * + *

          Subclasses cannot override this method; they should override {@link + * #createSupportedTypeQualifiers createSupportedTypeQualifiers} instead. + * + * @see #createSupportedTypeQualifiers() + * @return an immutable set of the supported type qualifiers, or an empty set if no qualifiers + * are supported + */ + public final Set<@CanonicalName String> getSupportedTypeQualifierNames() { + if (this.supportedQualNames == null) { + supportedQualNames = new HashSet<>(); + for (Class clazz : getSupportedTypeQualifiers()) { + supportedQualNames.add(clazz.getCanonicalName()); + } + supportedQualNames = Collections.unmodifiableSet(supportedQualNames); + } + return supportedQualNames; + } + + // ********************************************************************** + // Factories for annotated types that account for default qualifiers + // ********************************************************************** + + /** + * Returns the size for LRU caches. It is either the value supplied via the {@code + * -AatfCacheSize} option or the default cache size. + * + * @return cache size passed as argument to checker or DEFAULT_CACHE_SIZE + */ + protected int getCacheSize() { + String option = checker.getOption("atfCacheSize"); + if (option == null) { + return DEFAULT_CACHE_SIZE; + } + try { + return Integer.valueOf(option); + } catch (NumberFormatException ex) { + throw new UserError("atfCacheSize was not an integer: " + option); + } + } + + /** + * Returns an AnnotatedTypeMirror representing the annotated type of {@code elt}. + * + * @param elt the element + * @return the annotated type of {@code elt} + */ + public AnnotatedTypeMirror getAnnotatedType(Element elt) { + if (elt == null) { + throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null element"); + } + // Annotations explicitly written in the source code, + // or obtained from bytecode. + AnnotatedTypeMirror type = fromElement(elt); + addComputedTypeAnnotations(elt, type); + return type; + } + + /** + * Returns an AnnotatedTypeMirror representing the annotated type of {@code clazz}. + * + * @param clazz a class + * @return the annotated type of {@code clazz} + */ + public AnnotatedTypeMirror getAnnotatedType(Class clazz) { + return getAnnotatedType(elements.getTypeElement(clazz.getCanonicalName())); + } + + @Override + public @Nullable AnnotationMirror getAnnotationMirror( + Tree tree, Class target) { + if (isSupportedQualifier(target)) { + AnnotatedTypeMirror atm = getAnnotatedType(tree); + return atm.getAnnotation(target); + } + return null; + } + + /** + * Returns an AnnotatedTypeMirror representing the annotated type of {@code tree}. + * + * @param tree the AST node + * @return the annotated type of {@code tree} + */ + public AnnotatedTypeMirror getAnnotatedType(Tree tree) { + if (tree == null) { + throw new BugInCF("AnnotatedTypeFactory.getAnnotatedType: null tree"); + } + if (shouldCache && classAndMethodTreeCache.containsKey(tree)) { + return classAndMethodTreeCache.get(tree).deepCopy(); + } + + AnnotatedTypeMirror type; + if (TreeUtils.isClassTree(tree)) { + type = fromClass((ClassTree) tree); + } else if (tree.getKind() == Tree.Kind.METHOD || tree.getKind() == Tree.Kind.VARIABLE) { + type = fromMember(tree); + } else if (TreeUtils.isExpressionTree(tree)) { + tree = TreeUtils.withoutParens((ExpressionTree) tree); + type = fromExpression((ExpressionTree) tree); + } else { + throw new BugInCF( + "AnnotatedTypeFactory.getAnnotatedType: query of annotated type for tree " + + tree.getKind()); + } + + addComputedTypeAnnotations(tree, type); + if (tree.getKind() == Kind.TYPE_CAST) { + type = applyCaptureConversion(type); + } + + if (shouldCache && (TreeUtils.isClassTree(tree) || tree.getKind() == Tree.Kind.METHOD)) { + // Don't cache VARIABLE + classAndMethodTreeCache.put(tree, type.deepCopy()); + } else { + // No caching otherwise + } + + return type; + } + + /** + * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} before the classTree is type + * checked. + * + * @param classTree the class on which to perform preprocessing + */ + public void preProcessClassTree(ClassTree classTree) {} + + /** + * Called by {@link BaseTypeVisitor#visitClass(ClassTree, Void)} after the ClassTree has been + * type checked. + * + *

          The default implementation uses this to store the defaulted AnnotatedTypeMirrors and + * inherited declaration annotations back into the corresponding Elements. Subclasses might want + * to override this method if storing defaulted types is not desirable. + */ + public void postProcessClassTree(ClassTree tree) { + TypesIntoElements.store(processingEnv, this, tree); + DeclarationsIntoElements.store(processingEnv, this, tree); + + if (typeInformationPresenter != null) { + typeInformationPresenter.process(tree, getPath(tree)); + } + + /* NO-AFU + if (wholeProgramInference != null) { + // Write out the results of whole-program inference, just once for each class. As soon + // as any class is finished processing, all modified scenes are written to files, in + // case this was the last class to be processed. Post-processing of subsequent classes + // might result in re-writing some of the scenes if new information has been written to + // them. + wholeProgramInference.writeResultsToFile(wpiOutputFormat, this.checker); + } + */ + } + + /** + * Determines the annotated type from a type in tree form. + * + *

          Note that we cannot decide from a Tree whether it is a type use or an expression. + * TreeUtils.isTypeTree is only an under-approximation. For example, an identifier can be either + * a type or an expression. + * + * @param tree the type tree + * @return the annotated type of the type in the AST + */ + public AnnotatedTypeMirror getAnnotatedTypeFromTypeTree(Tree tree) { + if (tree == null) { + throw new BugInCF("AnnotatedTypeFactory.getAnnotatedTypeFromTypeTree: null tree"); + } + AnnotatedTypeMirror type = fromTypeTree(tree); + addComputedTypeAnnotations(tree, type); + return type; + } + + /** + * Returns the set of qualifiers that are the upper bounds for a use of the type. + * + *

          For a specific type system, the type declaration bound is retrieved in the following + * precedence: (1) the annotation on the type declaration bound (2) if an annotation with + * {@code @UpperBoundFor} mentions the type or the type kind, use that annotation (3) the top + * annotation + * + * @param type a type whose upper bounds to obtain + * @return the set of qualifiers that are the upper bounds for a use of the type + */ + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { + return qualifierUpperBounds.getBoundQualifiers(type); + } + + /** + * Compare the given {@code annos} with the declaration bounds of {@code type} and return the + * appropriate qualifiers. For each qualifier in {@code annos}, if it is a subtype of the + * declaration bound in the same hierarchy, it will be added to the result; otherwise, the + * declaration bound will be added to the result instead. + * + * @param type java type that specifies the qualifier upper bound + * @param annos a set of qualifiers to be compared with the declaration bounds of {@code type} + * @return the modified {@code annos} after applying the rules described above + */ + public AnnotationMirrorSet getAnnotationOrTypeDeclarationBound( + TypeMirror type, Set annos) { + AnnotationMirrorSet boundAnnos = getTypeDeclarationBounds(type); + AnnotationMirrorSet results = new AnnotationMirrorSet(); + + for (AnnotationMirror anno : annos) { + AnnotationMirror boundAnno = + qualHierarchy.findAnnotationInSameHierarchy(boundAnnos, anno); + assert boundAnno != null; + + if (!qualHierarchy.isSubtypeQualifiersOnly(anno, boundAnno)) { + results.add(boundAnno); + } else { + results.add(anno); + } + } + return results; + } + + /** + * Returns the set of qualifiers that are the upper bound for a type use if no other bound is + * specified for the type. + * + *

          This implementation returns the top qualifiers by default. Subclass may override to return + * different qualifiers. + * + * @return the set of qualifiers that are the upper bound for a type use if no other bound is + * specified for the type + */ + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + return qualHierarchy.getTopAnnotations(); + } + + /** + * Returns the type of the extends or implements clause. + * + *

          The primary qualifier is either an explicit annotation on {@code clause}, or it is the + * qualifier upper bounds for uses of the type of the clause. + * + * @param clause tree that represents an extends or implements clause + * @return the type of the extends or implements clause + */ + public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { + AnnotatedTypeMirror fromTypeTree = fromTypeTree(clause); + AnnotationMirrorSet bound = getTypeDeclarationBounds(fromTypeTree.getUnderlyingType()); + fromTypeTree.addMissingAnnotations(bound); + addComputedTypeAnnotations(clause, fromTypeTree); + return fromTypeTree; + } + + // ********************************************************************** + // Factories for annotated types that do not account for default qualifiers. + // They only include qualifiers explicitly inserted by the user. + // ********************************************************************** + + /** + * Creates an AnnotatedTypeMirror for {@code elt} that includes: annotations explicitly written + * on the element and annotations from stub files. + * + *

          Does not include default qualifiers. To obtain them, use {@link + * #getAnnotatedType(Element)}. + * + *

          Does not include fake overrides from the stub file. + * + * @param elt the element + * @return AnnotatedTypeMirror of the element with explicitly-written and stub file annotations + */ + public AnnotatedTypeMirror fromElement(Element elt) { + if (shouldCache && elementCache.containsKey(elt)) { + return elementCache.get(elt).deepCopy(); + } + if (elt.getKind() == ElementKind.PACKAGE) { + return toAnnotatedType(elt.asType(), false); + } + AnnotatedTypeMirror type; + + // Because of a bug in Java 8, annotations on type parameters are not stored in elements, so + // get explicit annotations from the tree. (This bug has been fixed in Java 9.) Also, since + // annotations computed by the AnnotatedTypeFactory are stored in the element, the + // annotations have to be retrieved from the tree so that only explicit annotations are + // returned. + Tree decl = declarationFromElement(elt); + + if (decl == null) { + type = stubTypes.getAnnotatedTypeMirror(elt); + if (type == null) { + type = toAnnotatedType(elt.asType(), ElementUtils.isTypeDeclaration(elt)); + ElementAnnotationApplier.apply(type, elt, this); + } + } else if (decl instanceof ClassTree) { + type = fromClass((ClassTree) decl); + } else if (decl instanceof VariableTree) { + type = fromMember(decl); + } else if (decl instanceof MethodTree) { + type = fromMember(decl); + } else if (decl.getKind() == Tree.Kind.TYPE_PARAMETER) { + type = fromTypeTree(decl); + } else { + throw new BugInCF( + "AnnotatedTypeFactory.fromElement: cannot be here. decl: " + + decl.getKind() + + " elt: " + + elt); + } + + type = mergeAnnotationFileAnnosIntoType(type, elt, ajavaTypes); + if (currentFileAjavaTypes != null) { + type = mergeAnnotationFileAnnosIntoType(type, elt, currentFileAjavaTypes); + } + + if (mergeStubsWithSource) { + if (debugStubParser) { + System.out.printf("fromElement: mergeStubsIntoType(%s, %s)", type, elt); + } + type = mergeAnnotationFileAnnosIntoType(type, elt, stubTypes); + if (debugStubParser) { + System.out.printf(" => %s%n", type); + } + } + // Caching is disabled if annotation files are being parsed, because calls to this + // method before the annotation files are fully read can return incorrect results. + if (shouldCache + && !stubTypes.isParsing() + && !ajavaTypes.isParsing() + && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { + elementCache.put(elt, type.deepCopy()); + } + return type; + } + + /** + * Returns an AnnotatedDeclaredType with explicit annotations from the ClassTree {@code tree}. + * + * @param tree the class declaration + * @return AnnotatedDeclaredType with explicit annotations from {@code tree} + */ + private AnnotatedDeclaredType fromClass(ClassTree tree) { + return TypeFromTree.fromClassTree(this, tree); + } + + /** + * Creates an AnnotatedTypeMirror for a variable or method declaration tree. The + * AnnotatedTypeMirror contains annotations explicitly written on the tree, and possibly others + * as described below. + * + *

          If a VariableTree is a parameter to a lambda, this method also adds annotations from the + * declared type of the functional interface and the executable type of its method. + * + *

          The returned AnnotatedTypeMirror also contains explicitly written annotations from any + * ajava file and if {@code -AmergeStubsWithSource} is passed, it also merges any explicitly + * written annotations from stub files. + * + * @param tree a {@link MethodTree} or {@link VariableTree} + * @return AnnotatedTypeMirror with explicit annotations from {@code tree} + */ + private AnnotatedTypeMirror fromMember(Tree tree) { + if (!(tree instanceof MethodTree || tree instanceof VariableTree)) { + throw new BugInCF( + "AnnotatedTypeFactory.fromMember: not a method or variable declaration: " + + tree); + } + if (shouldCache && fromMemberTreeCache.containsKey(tree)) { + return fromMemberTreeCache.get(tree).deepCopy(); + } + AnnotatedTypeMirror result = TypeFromTree.fromMember(this, tree); + + result = mergeAnnotationFileAnnosIntoType(result, tree, ajavaTypes); + if (currentFileAjavaTypes != null) { + result = mergeAnnotationFileAnnosIntoType(result, tree, currentFileAjavaTypes); + } + + if (mergeStubsWithSource) { + if (debugStubParser) { + System.out.printf("fromClass: mergeStubsIntoType(%s, %s)", result, tree); + } + result = mergeAnnotationFileAnnosIntoType(result, tree, stubTypes); + if (debugStubParser) { + System.out.printf(" => %s%n", result); + } + } + + if (shouldCache) { + fromMemberTreeCache.put(tree, result.deepCopy()); + } + + return result; + } + + /** + * Merges types from annotation files for {@code tree} into {@code type} by taking the greatest + * lower bound of the annotations in both. + * + * @param type the type to apply annotation file types to + * @param tree the tree from which to read annotation file types + * @param source storage for current annotation file annotations + * @return the given type, side-effected to add the annotation file types + */ + private AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( + @Nullable AnnotatedTypeMirror type, Tree tree, AnnotationFileElementTypes source) { + Element elt = TreeUtils.elementFromTree(tree); + return mergeAnnotationFileAnnosIntoType(type, elt, source); + } + + /** + * A scanner used to combine annotations from two AnnotatedTypeMirrors. The scanner requires + * {@link #qualHierarchy}, which is set in {@link #postInit()} rather than the construtor, so + * lazily initialize this field before use. + */ + private @MonotonicNonNull AnnotatedTypeCombiner annotatedTypeCombiner = null; + + /** + * Merges types from annotation files for {@code elt} into {@code type} by taking the greatest + * lower bound of the annotations in both. + * + * @param type the type to apply annotation file types to + * @param elt the element from which to read annotation file types + * @param source storage for current annotation file annotations + * @return the type, side-effected to add the annotation file types + */ + protected AnnotatedTypeMirror mergeAnnotationFileAnnosIntoType( + @Nullable AnnotatedTypeMirror type, Element elt, AnnotationFileElementTypes source) { + AnnotatedTypeMirror typeFromFile = source.getAnnotatedTypeMirror(elt); + if (typeFromFile == null) { + return type; + } + if (type == null) { + return typeFromFile; + } + if (annotatedTypeCombiner == null) { + annotatedTypeCombiner = new AnnotatedTypeCombiner(qualHierarchy); + } + // Must merge (rather than only take the annotation file type if it is a subtype) to support + // WPI. + annotatedTypeCombiner.visit(typeFromFile, type); + return type; + } + + /** + * Creates an AnnotatedTypeMirror for an ExpressionTree. The AnnotatedTypeMirror contains + * explicit annotations written on the expression and for some expressions, annotations from + * sub-expressions that could have been explicitly written, defaulted, refined, or otherwise + * computed. (Expression whose type include annotations from sub-expressions are: + * ArrayAccessTree, ConditionalExpressionTree, IdentifierTree, MemberSelectTree, and + * MethodInvocationTree.) + * + *

          For example, the AnnotatedTypeMirror returned for an array access expression is the fully + * annotated type of the array component of the array being accessed. + * + * @param tree an expression + * @return AnnotatedTypeMirror of the expressions either fully-annotated or partially annotated + * depending on the kind of expression + * @see TypeFromExpressionVisitor + */ + private AnnotatedTypeMirror fromExpression(ExpressionTree tree) { + if (shouldCache && fromExpressionTreeCache.containsKey(tree)) { + return fromExpressionTreeCache.get(tree).deepCopy(); + } + + AnnotatedTypeMirror result = TypeFromTree.fromExpression(this, tree); + + if (shouldCache + && tree.getKind() != Tree.Kind.NEW_CLASS + && tree.getKind() != Tree.Kind.NEW_ARRAY + && tree.getKind() != Tree.Kind.CONDITIONAL_EXPRESSION) { + // Don't cache the type of some expressions, because incorrect annotations would be + // cached during dataflow analysis. See Issue #602. + fromExpressionTreeCache.put(tree, result.deepCopy()); + } + return result; + } + + /** + * Creates an AnnotatedTypeMirror for the tree. The AnnotatedTypeMirror contains annotations + * explicitly written on the tree. It also adds type arguments to raw types that include + * annotations from the element declaration of the type {@link #fromElement(Element)}. + * + *

          Called on the following trees: AnnotatedTypeTree, ArrayTypeTree, ParameterizedTypeTree, + * PrimitiveTypeTree, TypeParameterTree, WildcardTree, UnionType, IntersectionTypeTree, and + * IdentifierTree, MemberSelectTree. + * + * @param tree the type tree + * @return the (partially) annotated type of the type in the AST + */ + /*package-private*/ final AnnotatedTypeMirror fromTypeTree(Tree tree) { + if (shouldCache && fromTypeTreeCache.containsKey(tree)) { + return fromTypeTreeCache.get(tree).deepCopy(); + } + + AnnotatedTypeMirror result = TypeFromTree.fromTypeTree(this, tree); + + if (shouldCache) { + fromTypeTreeCache.put(tree, result.deepCopy()); + } + return result; + } + + // ********************************************************************** + // Customization methods meant to be overridden by subclasses to include + // defaulted annotations + // ********************************************************************** + + /** + * Changes annotations on a type obtained from a {@link Tree}. By default, this method does + * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting and inference + * (flow-sensitive type refinement). Its subclasses usually override it only to customize + * default annotations. + * + *

          Subclasses that override this method should also override {@link + * #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)}. + * + * @param tree an AST node + * @param type the type obtained from {@code tree} + */ + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + // Pass. + } + + /** + * Changes annotations on a type obtained from an {@link Element}. By default, this method does + * nothing. GenericAnnotatedTypeFactory uses this method to implement defaulting. + * + *

          Subclasses that override this method should also override {@link + * #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. + * + * @param elt an element + * @param type the type obtained from {@code elt} + */ + protected void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + // Pass. + } + + /** + * Adds default annotations to {@code type}. This method should only be used in places where the + * correct annotations cannot be computed because of type argument of raw types. (See {@link + * AnnotatedWildcardType#isTypeArgOfRawType()}.) + * + * @param type annotated type to which default annotations are added + */ + public void addDefaultAnnotations(AnnotatedTypeMirror type) { + // Pass. + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize directSupertypes(). + * Overriding methods should merely change the annotations on the supertypes, without adding or + * removing new types. + * + *

          The default provided implementation adds {@code type} annotations to {@code supertypes}. + * This allows the {@code type} and its supertypes to have the qualifiers. + * + * @param type the type whose supertypes are desired + * @param supertypes the supertypes as specified by the base AnnotatedTypeFactory + */ + protected void postDirectSuperTypes( + AnnotatedTypeMirror type, List supertypes) { + // Use the effective annotations here to get the correct annotations + // for type variables and wildcards. + AnnotationMirrorSet annotations = type.getEffectiveAnnotations(); + for (AnnotatedTypeMirror supertype : supertypes) { + if (!annotations.equals(supertype.getEffectiveAnnotations())) { + supertype.clearAnnotations(); + // TODO: is this correct for type variables and wildcards? + supertype.addAnnotations(annotations); + } + } + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize + * AnnotatedTypes.asMemberOf(). Overriding methods should merely change the annotations on the + * subtypes, without changing the types. + * + * @param type the annotated type of the element + * @param owner the annotated type of the receiver of the accessing tree + * @param element the element of the field or method + */ + public void postAsMemberOf( + AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { + if (element.getKind() == ElementKind.FIELD) { + addAnnotationFromFieldInvariant(type, owner, (VariableElement) element); + } + addComputedTypeAnnotations(element, type); + + if (viewpointAdapter != null && type.getKind() != TypeKind.EXECUTABLE) { + viewpointAdapter.viewpointAdaptMember(owner, element, type); + } + } + + /** + * Adds the qualifier specified by a field invariant for {@code field} to {@code type}. + * + * @param type annotated type to which the annotation is added + * @param accessedVia the annotated type of the receiver of the accessing tree. (Only used to + * get the type element of the underling type.) + * @param field element representing the field + */ + protected void addAnnotationFromFieldInvariant( + AnnotatedTypeMirror type, AnnotatedTypeMirror accessedVia, VariableElement field) { + TypeMirror declaringType = accessedVia.getUnderlyingType(); + // Find the first upper bound that isn't a wildcard or type variable + while (declaringType.getKind() == TypeKind.WILDCARD + || declaringType.getKind() == TypeKind.TYPEVAR) { + if (declaringType.getKind() == TypeKind.WILDCARD) { + declaringType = TypesUtils.wildUpperBound(declaringType, processingEnv); + } else if (declaringType.getKind() == TypeKind.TYPEVAR) { + declaringType = ((TypeVariable) declaringType).getUpperBound(); + } + } + TypeElement typeElement = TypesUtils.getTypeElement(declaringType); + if (ElementUtils.enclosingTypeElement(field).equals(typeElement)) { + // If the field is declared in the accessedVia class, then the field in the invariant + // cannot be this field, even if the field has the same name. + return; + } + + FieldInvariants invariants = getFieldInvariants(typeElement); + if (invariants == null) { + return; + } + List invariantAnnos = invariants.getQualifiersFor(field.getSimpleName()); + type.replaceAnnotations(invariantAnnos); + } + + /** + * Returns the field invariants for the given class, as expressed by the user in {@link + * FieldInvariant @FieldInvariant} method annotations. + * + *

          Subclasses may implement their own field invariant annotations if {@link + * FieldInvariant @FieldInvariant} is not expressive enough. They must override this method to + * properly create AnnotationMirror and also override {@link + * #getFieldInvariantDeclarationAnnotations()} to return their field invariants. + * + * @param element class for which to get invariants + * @return field invariants for {@code element} + */ + public @Nullable FieldInvariants getFieldInvariants(TypeElement element) { + if (element == null) { + return null; + } + AnnotationMirror fieldInvarAnno = getDeclAnnotation(element, FieldInvariant.class); + if (fieldInvarAnno == null) { + return null; + } + List fields = + AnnotationUtils.getElementValueArray( + fieldInvarAnno, fieldInvariantFieldElement, String.class); + List<@CanonicalName Name> classes = + AnnotationUtils.getElementValueClassNames( + fieldInvarAnno, fieldInvariantQualifierElement); + List qualifiers = + CollectionsPlume.mapList( + name -> + // Calling AnnotationBuilder.fromName (which ignores + // elements/fields) is acceptable because @FieldInvariant + // does not handle classes with elements/fields. + AnnotationBuilder.fromName(elements, name), + classes); + if (qualifiers.size() == 1) { + while (fields.size() > qualifiers.size()) { + qualifiers.add(qualifiers.get(0)); + } + } + if (fields.size() != qualifiers.size()) { + // The user wrote a malformed @FieldInvariant annotation, so just return a malformed + // FieldInvariants object. The BaseTypeVisitor will issue an error. + return new FieldInvariants(fields, qualifiers, this); + } + + // Only keep qualifiers that are supported by this checker. (The other qualifiers cannot + // be checked by this checker, so they must be ignored.) + List annotatedFields = new ArrayList<>(); + List supportedQualifiers = new ArrayList<>(); + for (int i = 0; i < fields.size(); i++) { + if (isSupportedQualifier(qualifiers.get(i))) { + annotatedFields.add(fields.get(i)); + supportedQualifiers.add(qualifiers.get(i)); + } + } + if (annotatedFields.isEmpty()) { + return null; + } + + return new FieldInvariants(annotatedFields, supportedQualifiers, this); + } + + /** + * Returns the element of {@code annoTrees} that is a use of one of the field invariant + * annotations (as specified via {@link #getFieldInvariantDeclarationAnnotations()}. If one + * isn't found, null is returned. + * + * @param annoTrees list of trees to search; the result is one of the list elements, or null + * @return the AnnotationTree that is a use of one of the field invariant annotations, or null + * if one isn't found + */ + public @Nullable AnnotationTree getFieldInvariantAnnotationTree( + @Nullable List annoTrees) { + List annos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); + for (int i = 0; i < annos.size(); i++) { + for (Class clazz : getFieldInvariantDeclarationAnnotations()) { + if (areSameByClass(annos.get(i), clazz)) { + return annoTrees.get(i); + } + } + } + return null; + } + + /** The classes of field invariant annotations. */ + private final Set> fieldInvariantDeclarationAnnotations = + Collections.singleton(FieldInvariant.class); + + /** + * Returns the set of classes of field invariant annotations. + * + * @return the set of classes of field invariant annotations + */ + protected Set> getFieldInvariantDeclarationAnnotations() { + return fieldInvariantDeclarationAnnotations; + } + + /** + * Adapt the upper bounds of the type variables of a class relative to the type instantiation. + * In some type systems, the upper bounds depend on the instantiation of the class. For example, + * in the Generic Universe Type system, consider a class declaration + * + *

          {@code   class C }
          + * + * then the instantiation + * + *
          {@code   @Rep C<@Rep Object> }
          + * + * is legal. The upper bounds of class C have to be adapted by the main modifier. + * + *

          An example of an adaptation follows. Suppose, I have a declaration: + * + *

          {@code  class MyClass>}
          + * + * And an instantiation: + * + *
          {@code  new MyClass<@NonNull String>()}
          + * + *

          The upper bound of E adapted to the argument String, would be {@code List<@NonNull + * String>} and the lower bound would be an AnnotatedNullType. + * + *

          TODO: ensure that this method is consistently used instead of directly querying the type + * variables. + * + * @param type the use of the type + * @param element the corresponding element + * @return the adapted bounds of the type parameters + */ + public List typeVariablesFromUse( + AnnotatedDeclaredType type, TypeElement element) { + AnnotatedDeclaredType generic = getAnnotatedType(element); + List targs = type.getTypeArguments(); + List tvars = generic.getTypeArguments(); + + assert targs.size() == tvars.size() + : "Mismatch in type argument size between " + type + " and " + generic; + + // System.err.printf("TVFU%n type: %s%n generic: %s%n", type, generic); + + Map typeParamToTypeArg = new HashMap<>(); + + AnnotatedDeclaredType enclosing = type; + while (enclosing != null) { + List enclosingTArgs = enclosing.getTypeArguments(); + AnnotatedDeclaredType declaredType = + getAnnotatedType((TypeElement) enclosing.getUnderlyingType().asElement()); + List enclosingTVars = declaredType.getTypeArguments(); + for (int i = 0; i < enclosingTArgs.size(); i++) { + AnnotatedTypeVariable enclosingTVar = (AnnotatedTypeVariable) enclosingTVars.get(i); + typeParamToTypeArg.put(enclosingTVar.getUnderlyingType(), enclosingTArgs.get(i)); + } + enclosing = enclosing.getEnclosingType(); + } + + List res = new ArrayList<>(tvars.size()); + + for (AnnotatedTypeMirror atm : tvars) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) atm; + AnnotatedTypeMirror upper = + typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getUpperBound()); + AnnotatedTypeMirror lower = + typeVarSubstitutor.substitute(typeParamToTypeArg, atv.getLowerBound()); + res.add(new AnnotatedTypeParameterBounds(upper, lower)); + } + + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptTypeParameterBounds(type, res); + } + return res; + } + + /** + * Creates and returns an AnnotatedNullType qualified with {@code annotations}. + * + * @param annotations the set of AnnotationMirrors to qualify the returned type with + * @return AnnotatedNullType qualified with {@code annotations} + */ + public AnnotatedNullType getAnnotatedNullType(Set annotations) { + AnnotatedTypeMirror.AnnotatedNullType nullType = + (AnnotatedNullType) + toAnnotatedType(processingEnv.getTypeUtils().getNullType(), false); + nullType.addAnnotations(annotations); + return nullType; + } + + // ********************************************************************** + // Utilities method for getting specific types from trees or elements + // ********************************************************************** + + /** + * Return the implicit receiver type of an expression tree. + * + *

          The result is null for expressions that don't have a receiver, e.g. for a local variable + * or method parameter access. The result is also null for expressions that have an explicit + * receiver. + * + *

          Clients should generally call {@link #getReceiverType}. + * + * @param tree the expression that might have an implicit receiver + * @return the type of the implicit receiver. Returns null if the expression has an explicit + * receiver or doesn't have a receiver. + */ + protected @Nullable AnnotatedDeclaredType getImplicitReceiverType(ExpressionTree tree) { + assert (tree.getKind() == Tree.Kind.IDENTIFIER + || tree.getKind() == Tree.Kind.MEMBER_SELECT + || tree.getKind() == Tree.Kind.METHOD_INVOCATION + || tree.getKind() == Tree.Kind.NEW_CLASS) + : "Unexpected tree kind: " + tree.getKind(); + + // Return null if the element kind has no receiver. + Element element = TreeUtils.elementFromUse(tree); + assert element != null : "Unexpected null element for tree: " + tree; + if (!ElementUtils.hasReceiver(element)) { + return null; + } + + // Return null if the receiver is explicit. + if (TreeUtils.getReceiverTree(tree) != null) { + return null; + } + + TypeElement elementOfImplicitReceiver = ElementUtils.enclosingTypeElement(element); + if (tree.getKind() == Tree.Kind.NEW_CLASS) { + if (elementOfImplicitReceiver.getEnclosingElement() != null) { + elementOfImplicitReceiver = + ElementUtils.enclosingTypeElement( + elementOfImplicitReceiver.getEnclosingElement()); + } else { + elementOfImplicitReceiver = null; + } + if (elementOfImplicitReceiver == null) { + // If the typeElt does not have an enclosing class, then the NewClassTree + // does not have an implicit receiver. + return null; + } + } + + TypeMirror typeOfImplicitReceiver = elementOfImplicitReceiver.asType(); + AnnotatedDeclaredType thisType = getSelfType(tree); + if (thisType == null) { + return null; + } + // An implicit receiver is the first enclosing type that is a subtype of the type where the + // element is declared. + while (thisType != null + && !isSubtype(thisType.getUnderlyingType(), typeOfImplicitReceiver)) { + thisType = thisType.getEnclosingType(); + } + return thisType; + } + + /** + * Returns the type of {@code this} at the location of {@code tree}. Returns {@code null} if + * {@code tree} is in a location where {@code this} has no meaning, such as the body of a static + * method. + * + *

          The parameter is an arbitrary tree and does not have to mention "this", neither explicitly + * nor implicitly. This method can be overridden for type-system specific behavior. + * + * @param tree location used to decide the type of {@code this} + * @return the type of {@code this} at the location of {@code tree} + */ + public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) { + if (TreeUtils.isClassTree(tree)) { + return getAnnotatedType(TreeUtils.elementFromDeclaration((ClassTree) tree)); + } + + Tree enclosingTree = getEnclosingClassOrMethod(tree); + if (enclosingTree == null) { + // tree is inside an annotation, where "this" is not allowed. So, no self type exists. + return null; + } else if (enclosingTree.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = (MethodTree) enclosingTree; + if (TreeUtils.isConstructor(enclosingMethod)) { + return (AnnotatedDeclaredType) getAnnotatedType(enclosingMethod).getReturnType(); + } else { + return getAnnotatedType(enclosingMethod).getReceiverType(); + } + } else if (TreeUtils.isClassTree(enclosingTree)) { + return (AnnotatedDeclaredType) getAnnotatedType(enclosingTree); + } + return null; + } + + /** A set containing class, method, and annotation tree kinds. */ + private static final Set classMethodAnnotationKinds = + EnumSet.copyOf(TreeUtils.classTreeKinds()); + + static { + classMethodAnnotationKinds.add(Tree.Kind.METHOD); + classMethodAnnotationKinds.add(Tree.Kind.TYPE_ANNOTATION); + classMethodAnnotationKinds.add(Tree.Kind.ANNOTATION); + } + + /** + * Returns the innermost enclosing method or class tree of {@code tree}. Since artificial trees + * are assigned to be the child node of the original tree, their enclosing trees are found the + * same way as normal trees. + * + *

          If the tree is inside an annotation, then {@code null} is returned. + * + * @param tree tree to whose innermost enclosing method or class to return + * @return the innermost enclosing method or class tree of {@code tree}, or {@code null} if + * {@code tree} is inside an annotation + */ + public @Nullable Tree getEnclosingClassOrMethod(Tree tree) { + TreePath path = getPath(tree); + Tree enclosing = TreePathUtil.enclosingOfKind(path, classMethodAnnotationKinds); + if (enclosing != null) { + if (enclosing.getKind() == Tree.Kind.ANNOTATION + || enclosing.getKind() == Tree.Kind.TYPE_ANNOTATION) { + return null; + } + return enclosing; + } + + return TreePathUtil.enclosingClass(path); + } + + /** + * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} + * that is the same type as {@code typeElement}. + * + * @param typeElement type of the enclosing type to return + * @param tree location to use + * @return the enclosing type at the location of {@code tree} that is the same type as {@code + * typeElement} + */ + public AnnotatedDeclaredType getEnclosingType(TypeElement typeElement, Tree tree) { + AnnotatedDeclaredType thisType = getSelfType(tree); + while (!isSameType(thisType.getUnderlyingType(), typeElement.asType())) { + thisType = thisType.getEnclosingType(); + } + return thisType; + } + + /** + * Returns the {@link AnnotatedTypeMirror} of the enclosing type at the location of {@code tree} + * that is a subtype of {@code typeElement}. + * + * @param typeElement super type of the enclosing type to return + * @param tree location to use + * @return the enclosing type at the location of {@code tree} that is a subtype of {@code + * typeElement} + */ + public AnnotatedDeclaredType getEnclosingSubType(TypeElement typeElement, Tree tree) { + AnnotatedDeclaredType thisType = getSelfType(tree); + while (!isSubtype(thisType.getUnderlyingType(), typeElement.asType())) { + thisType = thisType.getEnclosingType(); + } + return thisType; + } + + /** + * Returns true if the erasure of {@code type1} is a Java subtype of the erasure of {@code + * type2}. + * + * @param type1 a type + * @param type2 a type + * @return true if the erasure of {@code type1} is a Java subtype of the erasure of {@code + * type2} + */ + private boolean isSubtype(TypeMirror type1, TypeMirror type2) { + return types.isSubtype(types.erasure(type1), types.erasure(type2)); + } + + /** + * Returns true if the erasure of {@code type1} is the same Java type as the erasure of {@code + * type2}. + * + * @param type1 a type + * @param type2 a type + * @return true if the erasure of {@code type1} is the same Java type as the erasure of {@code + * type2} + */ + private boolean isSameType(TypeMirror type1, TypeMirror type2) { + return types.isSameType(types.erasure(type1), types.erasure(type2)); + } + + /** + * Returns the receiver type of the expression tree, which might be the type of an implicit + * {@code this}. Returns null if the expression has no explicit or implicit receiver. + * + * @param expression the expression for which to determine the receiver type + * @return the type of the receiver of expression + */ + public final @Nullable AnnotatedTypeMirror getReceiverType(ExpressionTree expression) { + AnnotatedTypeMirror receiverType; + ExpressionTree receiver = TreeUtils.getReceiverTree(expression); + if (receiver != null) { + receiverType = getAnnotatedType(receiver); + } else { + Element element = TreeUtils.elementFromTree(expression); + if (element != null && ElementUtils.hasReceiver(element)) { + // The tree references an element that has a receiver, but the tree does not have an + // explicit receiver. So, the tree must have an implicit receiver of "this" or + // "Outer.this". + receiverType = getImplicitReceiverType(expression); + } else { + receiverType = null; + } + } + // In Java versions below 11, consider the following code: + // class Outer { + // class Inner{} + // } + // class Top { + // void test(Outer outer) { + // outer.new Inner(){}; + // } + // } + // the receiverType of outer.new Inner(){} is Top instead of Outer, + // because Java below 11 organizes newClassTree of an anonymous class in a different + // way: there is a synthetic argument representing the enclosing expression type. + // In such case, use the synthetic argument as its receiver type. + if ((expression instanceof NewClassTree) + && TreeUtils.hasSyntheticArgument((NewClassTree) expression)) { + receiverType = getAnnotatedType(((NewClassTree) expression).getArguments().get(0)); + } + return receiverType; + } + + /** The type for an instantiated generic method or constructor. */ + public static class ParameterizedExecutableType { + /** The method's/constructor's type. */ + public final AnnotatedExecutableType executableType; + + /** The types of the generic type arguments. */ + public final List typeArgs; + + /** Create a ParameterizedExecutableType. */ + public ParameterizedExecutableType( + AnnotatedExecutableType executableType, List typeArgs) { + this.executableType = executableType; + this.typeArgs = typeArgs; + } + + @Override + public String toString() { + if (typeArgs.isEmpty()) { + return executableType.toString(); + } else { + StringJoiner typeArgsString = new StringJoiner(",", "<", ">"); + for (AnnotatedTypeMirror atm : typeArgs) { + typeArgsString.add(atm.toString()); + } + return typeArgsString + " " + executableType.toString(); + } + } + } + + /** + * Determines the type of the invoked method based on the passed method invocation tree. + * + *

          The returned method type has all type variables resolved, whether based on receiver type, + * passed type parameters if any, and method invocation parameter. + * + *

          Subclasses may override this method to customize inference of types or qualifiers based on + * method invocation parameters. + * + *

          As an implementation detail, this method depends on {@link + * AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and + * customization based on receiver type should be in accordance to its specification. + * + *

          The return type is a pair of the type of the invoked method and the (inferred) type + * arguments. Note that neither the explicitly passed nor the inferred type arguments are + * guaranteed to be subtypes of the corresponding upper bounds. See method {@link + * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of + * type argument well-formedness. + * + *

          Note that "this" and "super" constructor invocations are also handled by this method + * (explicit or implicit ones, at the beginning of a constructor). Method {@link + * #constructorFromUse(NewClassTree)} is only used for a constructor invocation in a "new" + * expression. + * + * @param tree the method invocation tree + * @return the type of the invoked method and any (explict or inferred) type arguments + */ + public final ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { + return methodFromUse(tree, true); + } + + /** + * Returns the same as {@link #methodFromUse(MethodInvocationTree)}, but without inferred type + * arguments. + * + * @param tree a method invocation tree + * @return the type of the invoked method and any explicit type arguments + */ + public ParameterizedExecutableType methodFromUseWithoutTypeArgInference( + MethodInvocationTree tree) { + return methodFromUse(tree, false); + } + + /** + * The implementation of {@link #methodFromUse(MethodInvocationTree)} and {@link + * #methodFromUseWithoutTypeArgInference(MethodInvocationTree)}. + * + * @param tree a method invocation tree + * @param inferTypeArgs whether type arguments should be inferred + * @return the type of the invoked method, any explicit type arguments, and if {@code + * inferTypeArgs} is true, any inferred type arguments + */ + protected ParameterizedExecutableType methodFromUse( + MethodInvocationTree tree, boolean inferTypeArgs) { + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + AnnotatedTypeMirror receiverType = getReceiverType(tree); + if (receiverType == null + && (TreeUtils.isSuperConstructorCall(tree) + || TreeUtils.isThisConstructorCall(tree))) { + // super() and this() calls don't have a receiver, but they should be view-point adapted + // as if "this" is the receiver. + receiverType = getSelfType(tree); + } + if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) { + receiverType = applyCaptureConversion(receiverType); + } + + ParameterizedExecutableType result = + methodFromUse(tree, methodElt, receiverType, inferTypeArgs); + if (checker.shouldResolveReflection() + && reflectionResolver.isReflectiveMethodInvocation(tree)) { + result = reflectionResolver.resolveReflectiveCall(this, tree, result); + } + + AnnotatedExecutableType method = result.executableType; + if (AnnotatedTypes.isTypeArgOfRawType(method.getReturnType())) { + // Get the correct Java type from the tree and use it as the upper bound of the + // wildcard. + TypeMirror tm = TreeUtils.typeOf(tree); + AnnotatedTypeMirror t = toAnnotatedType(tm, false); + + AnnotatedWildcardType wildcard = (AnnotatedWildcardType) method.getReturnType(); + if (ignoreRawTypeArguments) { + // Remove the annotations so that default annotations are used instead. + // (See call to addDefaultAnnotations below.) + t.clearAnnotations(); + } else { + t.replaceAnnotations(wildcard.getExtendsBound().getAnnotations()); + } + wildcard.setExtendsBound(t); + addDefaultAnnotations(wildcard); + } + + // Store varargType before calling setParameterTypes, otherwise we may lose the varargType + // as it is the last element of the original parameterTypes. + method.computeVarargType(); + // Adapt parameters, which makes parameters and arguments be the same size for later + // checking. + List parameters = + AnnotatedTypes.adaptParameters(this, method, tree.getArguments(), null); + method.setParameterTypes(parameters); + return result; + } + + /** + * Determines the type of the invoked method based on the passed expression tree, executable + * element, and receiver type. + * + * @param tree either a MethodInvocationTree or a MemberReferenceTree + * @param methodElt the element of the referenced method + * @param receiverType the type of the receiver + * @return the type of the method being invoked with tree and the (inferred) type arguments + * @see #methodFromUse(MethodInvocationTree) + */ + public final ParameterizedExecutableType methodFromUse( + ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { + return methodFromUse(tree, methodElt, receiverType, true); + } + + /** + * Returns the same as {@link #methodFromUse(ExpressionTree, ExecutableElement, + * AnnotatedTypeMirror)}, but without inferred type arguments. + * + * @param tree either a MethodInvocationTree or a MemberReferenceTree + * @param methodElt the element of the referenced method + * @param receiverType the type of the receiver + * @return the type of the method being invoked with tree without inferring type arguments + */ + public final ParameterizedExecutableType methodFromUseWithoutTypeArgInference( + ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { + return methodFromUse(tree, methodElt, receiverType, false); + } + + /** + * The implementation of {@link #methodFromUse(ExpressionTree, ExecutableElement, + * AnnotatedTypeMirror)} and {@link #methodFromUseWithoutTypeArgInference(ExpressionTree, + * ExecutableElement, AnnotatedTypeMirror)}. + * + * @param tree either a MethodInvocationTree or a MemberReferenceTree + * @param methodElt the element of the referenced method + * @param receiverType the type of the receiver + * @param inferTypeArgs whether type arguments should be inferred + * @return the type of the invoked method + */ + protected ParameterizedExecutableType methodFromUse( + ExpressionTree tree, + ExecutableElement methodElt, + AnnotatedTypeMirror receiverType, + boolean inferTypeArgs) { + AnnotatedExecutableType memberTypeWithoutOverrides = + getAnnotatedType(methodElt); // get unsubstituted type + AnnotatedExecutableType memberTypeWithOverrides = + applyFakeOverrides(receiverType, methodElt, memberTypeWithoutOverrides); + memberTypeWithOverrides = applyRecordTypesToAccessors(methodElt, memberTypeWithOverrides); + methodFromUsePreSubstitution(tree, memberTypeWithOverrides, inferTypeArgs); + + // Perform viewpoint adaption before type argument substitution. + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptMethod(receiverType, methodElt, memberTypeWithOverrides); + } + + AnnotatedExecutableType methodType = + AnnotatedTypes.asMemberOf( + types, this, receiverType, methodElt, memberTypeWithOverrides); + List typeargs = new ArrayList<>(methodElt.getTypeParameters().size()); + + IPair, Boolean> pair = + AnnotatedTypes.findTypeArguments(this, tree, methodElt, methodType, inferTypeArgs); + Map typeParamToTypeArg = pair.first; + if (!typeParamToTypeArg.isEmpty()) { + for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { + if (typeParamToTypeArg.get(tv.getUnderlyingType()) == null) { + // throw new BugInCF( + // "AnnotatedTypeFactory.methodFromUse:mismatch between" + // + " declared method type variables and the inferred method + // type arguments." + // + " Method type variables: " + // + methodType.getTypeVariables() + // + "; " + // + "Inferred method type arguments: " + // + typeParamToTypeArg); + } + typeargs.add(typeParamToTypeArg.get(tv.getUnderlyingType())); + } + methodType = + (AnnotatedExecutableType) + typeVarSubstitutor.substitute(typeParamToTypeArg, methodType); + } + + if (pair.second) { + methodType.setReturnType(methodType.getReturnType().getErased()); + } + + if (tree.getKind() == Tree.Kind.METHOD_INVOCATION + && TreeUtils.isMethodInvocation(tree, objectGetClass, processingEnv)) { + adaptGetClassReturnTypeToReceiver(methodType, receiverType, tree); + } + + methodType.setReturnType(applyCaptureConversion(methodType.getReturnType())); + return new ParameterizedExecutableType(methodType, typeargs); + } + + /** + * Given a member and its type, returns the type with fake overrides applied to it. + * + * @param receiverType the type of the class that contains member (or a subtype of it) + * @param member a type member, such as a method or field + * @param memberType the type of {@code member} + * @return {@code memberType}, adjusted according to fake overrides + */ + private AnnotatedExecutableType applyFakeOverrides( + AnnotatedTypeMirror receiverType, Element member, AnnotatedExecutableType memberType) { + // Currently, handle only methods, not fields. TODO: Handle fields. + if (memberType.getKind() != TypeKind.EXECUTABLE) { + return memberType; + } + + AnnotationFileElementTypes afet = stubTypes; + AnnotatedExecutableType methodType = afet.getFakeOverride(member, receiverType); + if (methodType == null) { + methodType = memberType; + } + return methodType; + } + + /** + * Given a method, checks if there is: a record component with the same name AND the record + * component has an annotation AND the method has no-arguments. If so, replaces the annotations + * on the method return type with those from the record type in the same hierarchy. + * + * @param member a method or constructor + * @param memberType the type of the method/constructor; side-effected by this method + * @return {@code memberType} with annotations replaced if applicable + */ + private AnnotatedExecutableType applyRecordTypesToAccessors( + ExecutableElement member, AnnotatedExecutableType memberType) { + if (memberType.getKind() != TypeKind.EXECUTABLE) { + throw new BugInCF( + "member %s has type %s of kind %s", member, memberType, memberType.getKind()); + } + + stubTypes.injectRecordComponentType(types, member, memberType); + + return memberType; + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the + * declared method type before type variable substitution. + * + * @param tree either a method invocation or a member reference tree + * @param type declared method type before type variable substitution + * @param resolvePolyQuals whether to resolve polymorphic qualifiers + */ + protected void methodFromUsePreSubstitution( + ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { + assert tree instanceof MethodInvocationTree || tree instanceof MemberReferenceTree; + } + + /** + * Java special-cases the return type of {@link java.lang.Class#getClass() getClass()}. Though + * the method has a return type of {@code Class}, the compiler special cases this return-type + * and changes the bound of the type argument to the erasure of the receiver type. For example: + * + *

            + *
          • {@code x.getClass()} has the type {@code Class< ? extends erasure_of_x >} + *
          • {@code someInteger.getClass()} has the type {@code Class< ? extends Integer >} + *
          + * + * @param getClassType this must be a type representing a call to Object.getClass otherwise a + * runtime exception will be thrown. It is modified by side effect. + * @param receiverType the receiver type of the method invocation (not the declared receiver + * type) + * @param tree getClass method invocation tree + */ + protected void adaptGetClassReturnTypeToReceiver( + AnnotatedExecutableType getClassType, + AnnotatedTypeMirror receiverType, + ExpressionTree tree) { + TypeMirror type = TreeUtils.typeOf(tree); + AnnotatedTypeMirror returnType = AnnotatedTypeMirror.createType(type, this, false); + + if (returnType == null + || !(returnType.getKind() == TypeKind.DECLARED) + || ((AnnotatedDeclaredType) returnType).getTypeArguments().size() != 1) { + throw new BugInCF( + "Unexpected type passed to AnnotatedTypes.adaptGetClassReturnTypeToReceiver%n" + + "getClassType=%s%nreceiverType=%s", + getClassType, receiverType); + } + + AnnotatedWildcardType classWildcardArg = + (AnnotatedWildcardType) + ((AnnotatedDeclaredType) getClassType.getReturnType()) + .getTypeArguments() + .get(0); + getClassType.setReturnType(returnType); + + // Usually, the only locations that will add annotations to the return type are getClass in + // stub files defaults and propagation tree annotator. Since getClass is final they cannot + // come from source code. Also, since the newBound is an erased type we have no type + // arguments. So, we just copy the annotations from the bound of the declared type to the + // new bound. + AnnotationMirrorSet newAnnos = new AnnotationMirrorSet(); + AnnotationMirrorSet receiverTypeBoundAnnos = + getTypeDeclarationBounds(receiverType.getErased().getUnderlyingType()); + AnnotationMirrorSet wildcardBoundAnnos = classWildcardArg.getEffectiveAnnotations(); + for (AnnotationMirror receiverTypeBoundAnno : receiverTypeBoundAnnos) { + AnnotationMirror wildcardAnno = + qualHierarchy.findAnnotationInSameHierarchy( + wildcardBoundAnnos, receiverTypeBoundAnno); + if (typeHierarchy.isSubtypeShallowEffective(receiverTypeBoundAnno, classWildcardArg)) { + newAnnos.add(receiverTypeBoundAnno); + } else { + newAnnos.add(wildcardAnno); + } + } + AnnotatedTypeMirror newTypeArg = + ((AnnotatedDeclaredType) getClassType.getReturnType()).getTypeArguments().get(0); + ((AnnotatedTypeVariable) newTypeArg).getUpperBound().replaceAnnotations(newAnnos); + } + + /** + * Return the element type of {@code expression}. This is usually the type of {@code + * expression.itertor().next()}. If {@code expression} is an array, it is the component type of + * the array. + * + * @param expression an expression whose type is an array or implements {@link Iterable} + * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, + * the component type of the array. + */ + public AnnotatedTypeMirror getIterableElementType(ExpressionTree expression) { + return getIterableElementType(expression, getAnnotatedType(expression)); + } + + /** + * Return the element type of {@code iterableType}. This is usually the type of {@code + * expression.itertor().next()}. If {@code expression} is an array, it is the component type of + * the array. + * + * @param expression an expression whose type is an array or implements {@link Iterable} + * @param iterableType the type of the expression + * @return the type of {@code expression.itertor().next()} or if {@code expression} is an array, + * the component type of the array. + */ + protected AnnotatedTypeMirror getIterableElementType( + ExpressionTree expression, AnnotatedTypeMirror iterableType) { + switch (iterableType.getKind()) { + case ARRAY: + return ((AnnotatedArrayType) iterableType).getComponentType(); + case WILDCARD: + return getIterableElementType( + expression, + ((AnnotatedWildcardType) iterableType).getExtendsBound().deepCopy()); + case TYPEVAR: + return getIterableElementType( + expression, ((AnnotatedTypeVariable) iterableType).getUpperBound()); + case DECLARED: + AnnotatedDeclaredType dt = + AnnotatedTypes.asSuper(this, iterableType, this.iterableDeclType); + if (dt.getTypeArguments().isEmpty()) { + TypeElement e = ElementUtils.getTypeElement(processingEnv, Object.class); + return getAnnotatedType(e); + } else { + return dt.getTypeArguments().get(0); + } + + // TODO: Properly desugar Iterator.next(), which is needed if an annotated JDK has + // annotations on Iterator#next. + // The below doesn't work because methodFromUse() assumes that the expression tree + // matches the method element. + // TypeElement iteratorElement = + // ElementUtils.getTypeElement(processingEnv, Iterator.class); + // AnnotatedTypeMirror iteratorType = + // AnnotatedTypeMirror.createType(iteratorElement.asType(), this, false); + // Map mapping = new HashMap<>(); + // mapping.put( + // (TypeVariable) iteratorElement.getTypeParameters().get(0).asType(), + // typeArg); + // iteratorType = typeVarSubstitutor.substitute(mapping, iteratorType); + // ExecutableElement next = + // TreeUtils.getMethod("java.util.Iterator", "next", 0, processingEnv); + // ParameterizedExecutableType m = methodFromUse(expression, next, iteratorType); + // return m.executableType.getReturnType(); + default: + throw new BugInCF( + "AnnotatedTypeFactory.getIterableElementType: not iterable type: " + + iterableType); + } + } + + /** + * Determines the type of the invoked constructor based on the passed new class tree. + * + *

          The returned method type has all type variables resolved, whether based on receiver type, + * passed type parameters if any, and constructor invocation parameter. + * + *

          Subclasses may override this method to customize inference of types or qualifiers based on + * constructor invocation parameters. + * + *

          As an implementation detail, this method depends on {@link + * AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element)}, and + * customization based on receiver type should be in accordance with its specification. + * + *

          The return type is a pair of the type of the invoked constructor and the (inferred) type + * arguments. Note that neither the explicitly passed nor the inferred type arguments are + * guaranteed to be subtypes of the corresponding upper bounds. See method {@link + * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of + * type argument well-formedness. + * + *

          Note that "this" and "super" constructor invocations are handled by method {@link + * #methodFromUse}. This method only handles constructor invocations in a "new" expression. + * + * @param tree the constructor invocation tree + * @return the annotated type of the invoked constructor (as an executable type) and the + * (inferred) type arguments + */ + public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + return constructorFromUse(tree, true); + } + + /** + * The same as {@link #constructorFromUse(NewClassTree)}, but no type arguments are inferred. + * + * @param tree the constructor invocation tree + * @return the annotated type of the invoked constructor (as an executable type) and the + * explicit type arguments + */ + public ParameterizedExecutableType constructorFromUseWithoutTypeArgInference( + NewClassTree tree) { + return constructorFromUse(tree, false); + } + + /** + * Gets the type of the resulting constructor call of a MemberReferenceTree. + * + * @param memberReferenceTree MemberReferenceTree where the member is a constructor + * @param constructorType AnnotatedExecutableType of the declaration of the constructor + * @return AnnotatedTypeMirror of the resulting type of the constructor + */ + public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference( + MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) { + assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW; + + // The return type for constructors should only have explicit annotations from the + // constructor. The code below recreates some of the logic from TypeFromTree.visitNewClass + // to do + // this. + + // The return type of the constructor will be the type of the expression of the member + // reference tree. + AnnotatedTypeMirror constructorReturnType = + fromTypeTree(memberReferenceTree.getQualifierExpression()); + + if (constructorReturnType.getKind() == TypeKind.DECLARED) { + // Keep only explicit annotations and those from @Poly + AnnotatedTypes.copyOnlyExplicitConstructorAnnotations( + this, (AnnotatedDeclaredType) constructorReturnType, constructorType); + } + + // Now add back defaulting. + addComputedTypeAnnotations( + memberReferenceTree.getQualifierExpression(), constructorReturnType); + return constructorReturnType; + } + + /** + * The implementation of {@link #constructorFromUse(NewClassTree)} and {@link + * #constructorFromUseWithoutTypeArgInference(NewClassTree)}. + * + * @param tree the constructor invocation tree + * @param inferTypeArgs whether the type arguments should be inferred + * @return the annotated type of the invoked constructor (as an executable type) and the type + * arguments + */ + protected ParameterizedExecutableType constructorFromUse( + NewClassTree tree, boolean inferTypeArgs) { + // Get the annotations written on the new class tree. + AnnotatedDeclaredType type = + (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false); + if (!TreeUtils.isDiamondTree(tree)) { + if (tree.getClassBody() == null) { + type.setTypeArguments(getExplicitNewClassClassTypeArgs(tree)); + } + } else { + type = getAnnotatedType(TypesUtils.getTypeElement(type.underlyingType)); + // Add explicit annotations below. + type.clearAnnotations(); + } + + AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(tree); + type.addAnnotations(explicitAnnos); + + // Get the enclosing type of the constructor, if one exists. + // this.new InnerClass() + AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(tree); + type.setEnclosingType(enclosingType); + + // Add computed annotations to the type. + addComputedTypeAnnotations(tree, type); + + ExecutableElement ctor = TreeUtils.elementFromUse(tree); + AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type + constructorFromUsePreSubstitution(tree, con, inferTypeArgs); + + if (tree.getClassBody() != null) { + // Because the anonymous constructor can't have explicit annotations on its parameters, + // they are copied from the super constructor invoked in the anonymous constructor. To + // do this: + // 1. get unsubstituted type of the super constructor. + // 2. adapt it to this call site. + // 3. compute and store the vararg type. + // 4. copy the parameters to the anonymous constructor, `con`. + // 5. copy annotations on the return type to `con`. + AnnotatedExecutableType superCon = + getAnnotatedType(TreeUtils.getSuperConstructor(tree)); + constructorFromUsePreSubstitution(tree, superCon, inferTypeArgs); + // no viewpoint adaptation needed for super invocation + superCon = + AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon); + con.computeVarargType(superCon); + if (superCon.getParameterTypes().size() == con.getParameterTypes().size()) { + con.setParameterTypes(superCon.getParameterTypes()); + } else { + // If the super class of the anonymous class has an enclosing type, then it is the + // first parameter of the anonymous constructor. For example, + // class Outer { class Inner {} } + // new Inner(){}; + // Then javac creates the following constructor: + // (.Outer x0) { + // x0.super(); + // } + // So the code below deals with this. + // Because the anonymous constructor doesn't have annotated receiver type, + // we copy the receiver type from the super constructor invoked in the anonymous + // constructor + List p = + new ArrayList<>(superCon.getParameterTypes().size() + 1); + p.add(con.getParameterTypes().get(0)); + con.setReceiverType(superCon.getReceiverType()); + p.addAll(superCon.getParameterTypes()); + con.setParameterTypes(Collections.unmodifiableList(p)); + } + con.getReturnType().replaceAnnotations(superCon.getReturnType().getAnnotations()); + } else { + // Store varargType before calling setParameterTypes, otherwise we may lose the + // varargType as it is the last element of the original parameterTypes. + // AnnotatedTypes.asMemberOf handles vararg type properly, so we do not need to compute + // vararg type again. + con.computeVarargType(); + con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con); + } + + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); + } + + IPair, Boolean> pair = + AnnotatedTypes.findTypeArguments(this, tree, ctor, con, inferTypeArgs); + Map typeParamToTypeArg = new HashMap<>(pair.first); + List typeargs; + if (typeParamToTypeArg.isEmpty()) { + typeargs = Collections.emptyList(); + } else { + typeargs = + CollectionsPlume.mapList( + (AnnotatedTypeVariable tv) -> + typeParamToTypeArg.get(tv.getUnderlyingType()), + con.getTypeVariables()); + } + + con = (AnnotatedExecutableType) typeVarSubstitutor.substitute(typeParamToTypeArg, con); + + stubTypes.injectRecordComponentType(types, ctor, con); + if (enclosingType != null) { + // Reset the enclosing type because it can be substituted incorrectly. + ((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(enclosingType); + } + if (type.isUnderlyingTypeRaw() || TypesUtils.isRaw(TreeUtils.typeOf(tree))) { + ((AnnotatedDeclaredType) con.getReturnType()).setIsUnderlyingTypeRaw(); + } + if (ctor.getEnclosingElement().getKind() == ElementKind.ENUM) { + AnnotationMirrorSet enumAnnos = getEnumConstructorQualifiers(); + con.getReturnType().replaceAnnotations(enumAnnos); + } + + // Adapt parameters, which makes parameters and arguments be the same size for later + // checking. The vararg type of con has been already computed and stored when calling + // typeVarSubstitutor.substitute. + List parameters = + AnnotatedTypes.adaptParameters(this, con, tree.getArguments(), tree); + con.setParameterTypes(parameters); + + return new ParameterizedExecutableType(con, typeargs); + } + + /** + * Returns the annotations that should be applied to enum constructors. This implementation + * returns an empty set. Subclasses can override to return a different set. + * + * @return the annotations that should be applied to enum constructors + */ + protected AnnotationMirrorSet getEnumConstructorQualifiers() { + return new AnnotationMirrorSet(); + } + + /** + * Returns the annotations explicitly written on a NewClassTree. + * + *

          {@code new @HERE Class()} + * + * @param newClassTree a constructor invocation + * @return the annotations explicitly written on a NewClassTree + */ + public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { + if (newClassTree.getClassBody() != null) { + // In Java 17+, the annotations are on the identifier, so copy them. + AnnotatedTypeMirror identifierType = fromTypeTree(newClassTree.getIdentifier()); + // In Java 11 and lower, if newClassTree creates an anonymous class, then annotations in + // this location: + // new @HERE Class() {} + // are not on the identifier newClassTree, but rather on the modifier newClassTree. + List annoTrees = + newClassTree.getClassBody().getModifiers().getAnnotations(); + // Add the annotations to an AnnotatedTypeMirror removes the annotations that are not + // supported by this type system. + identifierType.addAnnotations(TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees)); + return identifierType.getAnnotations(); + } else { + return fromTypeTree(newClassTree.getIdentifier()).getAnnotations(); + } + } + + /** + * Returns the partially-annotated explicit class type arguments of the new class tree. The + * {@code AnnotatedTypeMirror} only include the annotations explicitly written on the explict + * type arguments. (If {@code newClass} use a diamond operator, this method returns the empty + * list.) For example, when called with {@code new MyClass<@HERE String>()} this method would + * return a list containing {@code @HERE String}. + * + * @param newClass a new class tree + * @return the partially annotated {@code AnnotatedTypeMirror}s for the (explicit) class type + * arguments of the new class tree + */ + protected List getExplicitNewClassClassTypeArgs(NewClassTree newClass) { + if (!TreeUtils.isDiamondTree(newClass)) { + return ((AnnotatedDeclaredType) fromTypeTree(newClass.getIdentifier())) + .getTypeArguments(); + } + return Collections.emptyList(); + } + + /** + * A callback method for the AnnotatedTypeFactory subtypes to customize the handling of the + * declared constructor type before type variable substitution. + * + * @param tree a NewClassTree from constructorFromUse() + * @param type declared method type before type variable substitution + * @param resolvePolyQuals whether to resolve polymorphic qualifiers + */ + protected void constructorFromUsePreSubstitution( + NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) {} + + /** + * Returns the return type of the method {@code m}. + * + * @param m tree of a method declaration + * @return the return type of the method + */ + public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { + AnnotatedExecutableType methodType = getAnnotatedType(m); + AnnotatedTypeMirror ret = methodType.getReturnType(); + return ret; + } + + /** + * Returns the return type of the method {@code m} at the return statement {@code r}. This + * implementation just calls {@link #getMethodReturnType(MethodTree)}, but subclasses may + * override this method to change the type based on the return statement. + * + * @param m tree of a method declaration + * @param r a return statement within method {@code m} + * @return the return type of the method {@code m} at the return statement {@code r} + */ + public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { + return getMethodReturnType(m); + } + + /** + * Returns the annotated boxed type of the given primitive type. The returned type would only + * have the annotations on the given type. + * + *

          Subclasses may override this method safely to override this behavior. + * + * @param type the primitive type + * @return the boxed declared type of the passed primitive type + */ + public AnnotatedDeclaredType getBoxedType(AnnotatedPrimitiveType type) { + TypeElement typeElt = types.boxedClass(type.getUnderlyingType()); + AnnotatedDeclaredType dt = fromElement(typeElt).asUse(); + dt.addAnnotations(type.getAnnotations()); + return dt; + } + + /** + * Return a primitive type: either the argument, or the result of unboxing it (which might + * affect its annotations). + * + *

          Subclasses should override {@link #getUnboxedType} rather than this method. + * + * @param type a type: a primitive or boxed primitive + * @return the unboxed variant of the type + */ + public final AnnotatedPrimitiveType applyUnboxing(AnnotatedTypeMirror type) { + TypeMirror underlying = type.getUnderlyingType(); + if (TypesUtils.isPrimitive(underlying)) { + return (AnnotatedPrimitiveType) type; + } else if (TypesUtils.isBoxedPrimitive(underlying)) { + return getUnboxedType((AnnotatedDeclaredType) type); + } else { + throw new BugInCF("Bad argument to applyUnboxing: " + type); + } + } + + /** + * Returns the annotated primitive type of the given declared type if it is a boxed declared + * type. Otherwise, it throws IllegalArgumentException exception. + * + *

          In the {@code AnnotatedTypeFactory} implementation, the returned type has the same primary + * annotations as the given type. Subclasses may override this behavior. + * + * @param type the declared type + * @return the unboxed primitive type + * @throws IllegalArgumentException if the type given has no unbox conversion + */ + public AnnotatedPrimitiveType getUnboxedType(AnnotatedDeclaredType type) + throws IllegalArgumentException { + PrimitiveType primitiveType = types.unboxedType(type.getUnderlyingType()); + AnnotatedPrimitiveType pt = + (AnnotatedPrimitiveType) AnnotatedTypeMirror.createType(primitiveType, this, false); + pt.addAnnotations(type.getAnnotations()); + return pt; + } + + /** + * Returns AnnotatedDeclaredType with underlying type String and annotations copied from type. + * Subclasses may change the annotations. + * + * @param type type to convert to String + * @return AnnotatedTypeMirror that results from converting type to a String type + */ + // TODO: Test that this is called in all the correct locations + // See Issue #715 + // https://github.com/typetools/checker-framework/issues/715 + public AnnotatedDeclaredType getStringType(AnnotatedTypeMirror type) { + TypeMirror stringTypeMirror = TypesUtils.typeFromClass(String.class, types, elements); + AnnotatedDeclaredType stringATM = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + stringTypeMirror, this, type.isDeclaration()); + stringATM.addAnnotations(type.getEffectiveAnnotations()); + return stringATM; + } + + /** + * Returns a widened type if applicable, otherwise returns its first argument. + * + *

          Subclasses should override {@link #getWidenedAnnotations} rather than this method. + * + * @param exprType type to possibly widen + * @param widenedType type to possibly widen to; its annotations are ignored + * @return if widening is applicable, the result of converting {@code type} to the underlying + * type of {@code widenedType}; otherwise {@code type} + */ + public final AnnotatedTypeMirror getWidenedType( + AnnotatedTypeMirror exprType, AnnotatedTypeMirror widenedType) { + TypeKind exprKind = exprType.getKind(); + TypeKind widenedKind = widenedType.getKind(); + + if (!TypeKindUtils.isNumeric(widenedKind)) { + // The target type is not a numeric primitive, so primitive widening is not applicable. + return exprType; + } + + AnnotatedPrimitiveType exprPrimitiveType; + if (TypeKindUtils.isNumeric(exprKind)) { + exprPrimitiveType = (AnnotatedPrimitiveType) exprType; + } else if (TypesUtils.isNumericBoxed(exprType.getUnderlyingType())) { + exprPrimitiveType = getUnboxedType((AnnotatedDeclaredType) exprType); + } else { + return exprType; + } + + switch (TypeKindUtils.getPrimitiveConversionKind( + exprPrimitiveType.getKind(), widenedType.getKind())) { + case WIDENING: + return getWidenedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); + case NARROWING: + return getNarrowedPrimitive(exprPrimitiveType, widenedType.getUnderlyingType()); + case SAME: + return exprType; + default: + throw new BugInCF("unhandled PrimitiveConversionKind"); + } + } + + /** + * Applies widening if applicable, otherwise returns its first argument. + * + *

          Subclasses should override {@link #getWidenedAnnotations} rather than this method. + * + * @param exprAnnos annotations to possibly widen + * @param exprTypeMirror type to possibly widen + * @param widenedType type to possibly widen to; its annotations are ignored + * @return if widening is applicable, the result of converting {@code type} to the underlying + * type of {@code widenedType}; otherwise {@code type} + */ + public final AnnotatedTypeMirror getWidenedType( + AnnotationMirrorSet exprAnnos, + TypeMirror exprTypeMirror, + AnnotatedTypeMirror widenedType) { + AnnotatedTypeMirror exprType = toAnnotatedType(exprTypeMirror, false); + exprType.replaceAnnotations(exprAnnos); + return getWidenedType(exprType, widenedType); + } + + /** + * Returns an AnnotatedPrimitiveType with underlying type {@code widenedTypeMirror} and with + * annotations copied or adapted from {@code type}. + * + * @param type type to widen; a primitive or boxed primitive + * @param widenedTypeMirror underlying type for the returned type mirror; a primitive or boxed + * primitive (same boxing as {@code type}) + * @return result of converting {@code type} to {@code widenedTypeMirror} + */ + private AnnotatedPrimitiveType getWidenedPrimitive( + AnnotatedPrimitiveType type, TypeMirror widenedTypeMirror) { + AnnotatedPrimitiveType result = + (AnnotatedPrimitiveType) + AnnotatedTypeMirror.createType( + widenedTypeMirror, this, type.isDeclaration()); + result.addAnnotations( + getWidenedAnnotations(type.getAnnotations(), type.getKind(), result.getKind())); + return result; + } + + /** + * Returns annotations applicable to type {@code narrowedTypeKind}, that are copied or adapted + * from {@code annos}. + * + * @param annos annotations to narrow, from a primitive or boxed primitive + * @param typeKind primitive type to narrow + * @param narrowedTypeKind target for the returned annotations; a primitive type that is + * narrower than {@code typeKind} (in the sense of JLS 5.1.3). + * @return result of converting {@code annos} from {@code typeKind} to {@code narrowedTypeKind} + */ + public AnnotationMirrorSet getNarrowedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind narrowedTypeKind) { + return annos; + } + + /** + * Returns annotations applicable to type {@code widenedTypeKind}, that are copied or adapted + * from {@code annos}. + * + * @param annos annotations to widen, from a primitive or boxed primitive + * @param typeKind primitive type to widen + * @param widenedTypeKind target for the returned annotations; a primitive type that is wider + * than {@code typeKind} (in the sense of JLS 5.1.2) + * @return result of converting {@code annos} from {@code typeKind} to {@code widenedTypeKind} + */ + public AnnotationMirrorSet getWidenedAnnotations( + AnnotationMirrorSet annos, TypeKind typeKind, TypeKind widenedTypeKind) { + return annos; + } + + /** + * Returns the types of the two arguments to the BinaryTree. Please refer to {@link + * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} )} for more + * details. + * + * @param tree a binary tree + * @return the types of the two arguments + */ + public IPair binaryTreeArgTypes(BinaryTree tree) { + return binaryTreeArgTypes( + TreeUtils.typeOf(tree), + getAnnotatedType(tree.getLeftOperand()), + getAnnotatedType(tree.getRightOperand())); + } + + /** + * Returns the types of the two arguments to the CompoundAssignmentTree. Please refer to {@link + * #binaryTreeArgTypes(TypeMirror, AnnotatedTypeMirror, AnnotatedTypeMirror)} ) for more + * details. + * + * @param tree a compound assignment tree + * @return the types of the two arguments + */ + public IPair compoundAssignmentTreeArgTypes( + CompoundAssignmentTree tree) { + return binaryTreeArgTypes( + TreeUtils.typeOf(tree.getVariable()), + getAnnotatedType(tree.getVariable()), + getAnnotatedType(tree.getExpression())); + } + + /** + * Returns the types of the two arguments to a binary operation. There are two special cases: + * + *

          1. If both operands have numeric type, widening and unboxing will be applied accordingly. + * + *

          2. If we have a non-string operand in a string concatenation (i.e., result is a string), + * we will always return a string ATM for the operand. The resulting ATM will have the original + * annotations with the declaration bounds of string type applied. Please check {@link + * #getAnnotationOrTypeDeclarationBound} for more details. + * + * @param resultType the type of the result of a binary operation + * @param left the type of the left argument of a binary operation + * @param right the type of the right argument of a binary operation + * @return the types of the two arguments + */ + protected IPair binaryTreeArgTypes( + TypeMirror resultType, AnnotatedTypeMirror left, AnnotatedTypeMirror right) { + TypeKind widenedNumericType = + TypeKindUtils.widenedNumericType( + left.getUnderlyingType(), right.getUnderlyingType()); + if (TypeKindUtils.isNumeric(widenedNumericType)) { + TypeMirror widenedNumericTypeMirror = types.getPrimitiveType(widenedNumericType); + AnnotatedPrimitiveType leftUnboxed = applyUnboxing(left); + AnnotatedPrimitiveType rightUnboxed = applyUnboxing(right); + AnnotatedPrimitiveType leftWidened = + (leftUnboxed.getKind() == widenedNumericType + ? leftUnboxed + : getWidenedPrimitive(leftUnboxed, widenedNumericTypeMirror)); + AnnotatedPrimitiveType rightWidened = + (rightUnboxed.getKind() == widenedNumericType + ? rightUnboxed + : getWidenedPrimitive(rightUnboxed, widenedNumericTypeMirror)); + return IPair.of(leftWidened, rightWidened); + } else if (TypesUtils.isString(resultType)) { + // the result of a binary operation is String iff it's string concatenation + AnnotatedTypeMirror leftStringConverted = left; + AnnotatedTypeMirror rightStringConverted = right; + + if (!TypesUtils.isString(left.getUnderlyingType())) { + leftStringConverted = toAnnotatedType(resultType, false); + AnnotationMirrorSet annos = + getAnnotationOrTypeDeclarationBound( + resultType, left.getEffectiveAnnotations()); + leftStringConverted.addAnnotations(annos); + } + if (!TypesUtils.isString(right.getUnderlyingType())) { + rightStringConverted = toAnnotatedType(resultType, false); + AnnotationMirrorSet annos = + getAnnotationOrTypeDeclarationBound( + resultType, right.getEffectiveAnnotations()); + rightStringConverted.addAnnotations(annos); + } + return IPair.of(leftStringConverted, rightStringConverted); + } + + return IPair.of(left, right); + } + + /** + * Returns AnnotatedPrimitiveType with underlying type {@code narrowedTypeMirror} and with + * annotations copied or adapted from {@code type}. + * + *

          Currently this method is called only for primitives that are narrowed at assignments from + * literal ints, for example, {@code byte b = 1;}. All other narrowing conversions happen at + * typecasts. + * + * @param type type to narrow + * @param narrowedTypeMirror underlying type for the returned type mirror + * @return result of converting {@code type} to {@code narrowedTypeMirror} + */ + public AnnotatedPrimitiveType getNarrowedPrimitive( + AnnotatedPrimitiveType type, TypeMirror narrowedTypeMirror) { + AnnotatedPrimitiveType narrowed = + (AnnotatedPrimitiveType) + AnnotatedTypeMirror.createType( + narrowedTypeMirror, this, type.isDeclaration()); + narrowed.addAnnotations(type.getAnnotations()); + return narrowed; + } + + // ********************************************************************** + // random methods wrapping #getAnnotatedType(Tree) and #fromElement(Tree) + // with appropriate casts to reduce casts on the client side + // ********************************************************************** + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedDeclaredType getAnnotatedType(ClassTree tree) { + return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedDeclaredType getAnnotatedType(NewClassTree tree) { + return (AnnotatedDeclaredType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedArrayType getAnnotatedType(NewArrayTree tree) { + return (AnnotatedArrayType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Tree)}. + * + * @see #getAnnotatedType(Tree) + */ + public final AnnotatedExecutableType getAnnotatedType(MethodTree tree) { + return (AnnotatedExecutableType) getAnnotatedType((Tree) tree); + } + + /** + * See {@link #getAnnotatedType(Element)}. + * + * @see #getAnnotatedType(Element) + */ + public final AnnotatedDeclaredType getAnnotatedType(TypeElement elt) { + return (AnnotatedDeclaredType) getAnnotatedType((Element) elt); + } + + /** + * See {@link #getAnnotatedType(Element)}. + * + * @see #getAnnotatedType(Element) + */ + public final AnnotatedExecutableType getAnnotatedType(ExecutableElement elt) { + return (AnnotatedExecutableType) getAnnotatedType((Element) elt); + } + + /** + * See {@link #fromElement(Element)}. + * + * @see #fromElement(Element) + */ + public final AnnotatedDeclaredType fromElement(TypeElement elt) { + return (AnnotatedDeclaredType) fromElement((Element) elt); + } + + /** + * See {@link #fromElement(Element)}. + * + * @see #fromElement(Element) + */ + public final AnnotatedExecutableType fromElement(ExecutableElement elt) { + return (AnnotatedExecutableType) fromElement((Element) elt); + } + + // ********************************************************************** + // Helper methods for this classes + // ********************************************************************** + + /** + * Returns true if the given annotation is a part of the type system under which this type + * factory operates. Null is never a supported qualifier; the parameter is nullable to allow the + * result of canonicalAnnotation to be passed in directly. + * + * @param a any annotation + * @return true if that annotation is part of the type system under which this type factory + * operates, false otherwise + */ + @EnsuresNonNullIf(expression = "#1", result = true) + public boolean isSupportedQualifier(@Nullable AnnotationMirror a) { + if (a == null) { + return false; + } + return isSupportedQualifier(AnnotationUtils.annotationName(a)); + } + + /** + * Returns true if the given class is a part of the type system under which this type factory + * operates. + * + * @param clazz annotation class + * @return true if that class is a type qualifier in the type system under which this type + * factory operates, false otherwise + */ + public boolean isSupportedQualifier(Class clazz) { + return getSupportedTypeQualifiers().contains(clazz); + } + + /** + * Returns true if the given class name is a part of the type system under which this type + * factory operates. + * + * @param className fully-qualified annotation class name + * @return true if that class name is a type qualifier in the type system under which this type + * factory operates, false otherwise + */ + public boolean isSupportedQualifier(String className) { + return getSupportedTypeQualifierNames().contains(className); + } + + /** + * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code + * canonicalAnno} that will be used by the Checker Framework in the alias's place. + * + *

          By specifying the alias/canonical relationship using this method, the elements of the + * alias are not preserved when the canonical annotation to use is constructed from the alias. + * If you want the elements to be copied over as well, use {@link + * #addAliasedTypeAnnotation(Class, Class, boolean, String...)}. + * + * @param aliasClass the class of the aliased annotation + * @param canonicalAnno the canonical annotation + */ + protected void addAliasedTypeAnnotation(Class aliasClass, AnnotationMirror canonicalAnno) { + if (getSupportedTypeQualifiers().contains(aliasClass)) { + throw new BugInCF( + "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", + aliasClass, this.getClass().getSimpleName()); + } + addAliasedTypeAnnotation(aliasClass.getCanonicalName(), canonicalAnno); + } + + /** + * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias + * for the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework + * in the alias's place. + * + *

          Use this method if the alias class is not necessarily on the classpath at Checker + * Framework compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, + * AnnotationMirror)} which prevents the possibility of a typo in the class name. + * + * @param aliasName the canonical name of the aliased annotation + * @param canonicalAnno the canonical annotation + */ + // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the + // name of an external annotation is a canonical name. + protected void addAliasedTypeAnnotation( + @FullyQualifiedName String aliasName, AnnotationMirror canonicalAnno) { + aliases.put(aliasName, new Alias(aliasName, canonicalAnno, false, null, null)); + } + + /** + * Adds the annotation {@code aliasClass} as an alias for the canonical annotation {@code + * canonicalClass} that will be used by the Checker Framework in the alias's place. + * + *

          You may specify the copyElements flag to indicate whether you want the elements of the + * alias to be copied over when the canonical annotation is constructed as a copy of {@code + * canonicalClass}. Be careful that the framework will try to copy the elements by name + * matching, so make sure that names and types of the elements to be copied over are exactly the + * same as the ones in the canonical annotation. Otherwise, an 'Couldn't find element in + * annotation' error is raised. + * + *

          To facilitate the cases where some of the elements are ignored on purpose when + * constructing the canonical annotation, this method also provides a varargs {@code + * ignorableElements} for you to explicitly specify the ignoring rules. For example, {@code + * org.checkerframework.checker.index.qual.IndexFor} is an alias of {@code + * org.checkerframework.checker.index.qual.NonNegative}, but the element "value" of + * {@code @IndexFor} should be ignored when constructing {@code @NonNegative}. In the cases + * where all elements are ignored, we can simply use {@link #addAliasedTypeAnnotation(Class, + * AnnotationMirror)} instead. + * + * @param aliasClass the class of the aliased annotation + * @param canonicalClass the class of the canonical annotation + * @param copyElements a flag that indicates whether you want to copy the elements over when + * getting the alias from the canonical annotation + * @param ignorableElements a list of elements that can be safely dropped when the elements are + * being copied over + */ + protected void addAliasedTypeAnnotation( + Class aliasClass, + Class canonicalClass, + boolean copyElements, + String... ignorableElements) { + if (getSupportedTypeQualifiers().contains(aliasClass)) { + throw new BugInCF( + "AnnotatedTypeFactory: alias %s should not be in type hierarchy for %s", + aliasClass, this.getClass().getSimpleName()); + } + addAliasedTypeAnnotation( + aliasClass.getCanonicalName(), canonicalClass, copyElements, ignorableElements); + } + + /** + * Adds the annotation, whose fully-qualified name is given by {@code aliasName}, as an alias + * for the canonical annotation {@code canonicalAnno} that will be used by the Checker Framework + * in the alias's place. + * + *

          Use this method if the alias class is not necessarily on the classpath at Checker + * Framework compile and run time. Otherwise, use {@link #addAliasedTypeAnnotation(Class, Class, + * boolean, String[])} which prevents the possibility of a typo in the class name. + * + * @param aliasName the canonical name of the aliased class + * @param canonicalAnno the canonical annotation + * @param copyElements a flag that indicates whether we want to copy the elements over when + * getting the alias from the canonical annotation + * @param ignorableElements a list of elements that can be safely dropped when the elements are + * being copied over + */ + // aliasName is annotated as @FullyQualifiedName because there is no way to confirm that the + // name of an external annotation is a canoncal name. + protected void addAliasedTypeAnnotation( + @FullyQualifiedName String aliasName, + Class canonicalAnno, + boolean copyElements, + String... ignorableElements) { + // The copyElements argument disambiguates overloading. + if (!copyElements) { + throw new BugInCF("Do not call with false"); + } + aliases.put( + aliasName, + new Alias( + aliasName, + null, + copyElements, + canonicalAnno.getCanonicalName(), + ignorableElements)); + } + + /** + * Returns the canonical annotation for the passed annotation. Returns null if the passed + * annotation is not an alias of a canonical one in the framework. + * + *

          A canonical annotation is the internal annotation that will be used by the Checker + * Framework in the aliased annotation's place. + * + * @param a the qualifier to check for an alias + * @return the canonical annotation, or null if none exists + */ + public @Nullable AnnotationMirror canonicalAnnotation(AnnotationMirror a) { + TypeElement elem = (TypeElement) a.getAnnotationType().asElement(); + String qualName = elem.getQualifiedName().toString(); + Alias alias = aliases.get(qualName); + if (alias == null) { + return null; + } + if (alias.copyElements) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, alias.canonicalName); + builder.copyElementValuesFromAnnotation(a, alias.ignorableElements); + return builder.build(); + } else { + return alias.canonical; + } + } + + /** + * Add the annotation {@code alias} as an alias for the declaration annotation {@code + * annotation}, where the annotation mirror {@code annotationToUse} will be used instead. If + * multiple calls are made with the same {@code annotation}, then the {@code annotationToUse} + * must be the same. + * + *

          The point of {@code annotationToUse} is that it may include elements/fields. + * + * @param alias the class of the alias annotation + * @param annotation the class of the canonical annotation + * @param annotationToUse the annotation mirror to use + */ + protected void addAliasedDeclAnnotation( + Class alias, + Class annotation, + AnnotationMirror annotationToUse) { + addAliasedDeclAnnotation( + alias.getCanonicalName(), annotation.getCanonicalName(), annotationToUse); + } + + /** + * Add the annotation {@code alias} as an alias for the declaration annotation {@code + * annotation}, where the annotation mirror {@code annotationToUse} will be used instead. If + * multiple calls are made with the same {@code annotation}, then the {@code annotationToUse} + * must be the same. + * + *

          The point of {@code annotationToUse} is that it may include elements/fields. + * + * @param alias the fully-qualified name of the alias annotation + * @param annotation the fully-qualified name of the canonical annotation + * @param annotationToUse the annotation mirror to use + */ + protected void addAliasedDeclAnnotation( + @FullyQualifiedName String alias, + @FullyQualifiedName String annotation, + AnnotationMirror annotationToUse) { + Map<@FullyQualifiedName String, AnnotationMirror> mapping = declAliases.get(annotation); + if (mapping == null) { + mapping = new HashMap<>(1); + declAliases.put(annotation, mapping); + } + AnnotationMirror prev = mapping.put(alias, annotationToUse); + // There already was a mapping. Raise an error. + if (prev != null && !AnnotationUtils.areSame(prev, annotationToUse)) { + throw new TypeSystemError( + "Multiple aliases for %s: %s cannot map to %s and %s.", + annotation, alias, prev, annotationToUse); + } + } + + /** + * Adds the annotation {@code annotation} in the set of declaration annotations that should be + * inherited. A declaration annotation will be inherited if it is in this list, or if it has the + * meta-annotation @InheritedAnnotation. The meta-annotation @InheritedAnnotation should be used + * instead of this method, if possible. + */ + protected void addInheritedAnnotation(AnnotationMirror annotation) { + inheritedAnnotations.add(annotation); + } + + /** + * A convenience method that converts a {@link TypeMirror} to an empty {@link + * AnnotatedTypeMirror} using {@link AnnotatedTypeMirror#createType}. + * + * @param t the {@link TypeMirror} + * @param declaration true if the result should be marked as a type declaration + * @return an {@link AnnotatedTypeMirror} that has {@code t} as its underlying type + */ + protected final AnnotatedTypeMirror toAnnotatedType(TypeMirror t, boolean declaration) { + return AnnotatedTypeMirror.createType(t, this, declaration); + } + + /** + * Determines an empty annotated type of the given tree. In other words, finds the {@link + * TypeMirror} for the tree and converts that into an {@link AnnotatedTypeMirror}, but does not + * add any annotations to the result. + * + *

          Most users will want to use {@link #getAnnotatedType(Tree)} instead; this method is mostly + * for internal use. + * + * @param tree the tree to analyze + * @return the type of {@code tree}, without any annotations + */ + protected final AnnotatedTypeMirror type(Tree tree) { + boolean isDeclaration = TreeUtils.isClassTree(tree); + + // Attempt to obtain the type via JCTree. + if (TreeUtils.typeOf(tree) != null) { + AnnotatedTypeMirror result = toAnnotatedType(TreeUtils.typeOf(tree), isDeclaration); + return result; + } + + // Attempt to obtain the type via TreePath (slower). + TreePath path = this.getPath(tree); + assert path != null + : "No path or type in tree: " + tree + " [" + tree.getClass().getSimpleName() + "]"; + + TypeMirror t = trees.getTypeMirror(path); + assert validType(t) : "Invalid type " + t + " for tree " + t; + + AnnotatedTypeMirror result = toAnnotatedType(t, isDeclaration); + return result; + } + + /** + * Gets the declaration tree for the element, if the source is available. + * + *

          TODO: would be nice to move this to InternalUtils/TreeUtils. + * + * @param elt an element + * @return the tree declaration of the element if found + */ + public final @Nullable Tree declarationFromElement(Element elt) { + // if root is null, we cannot find any declaration + if (root == null) { + return null; + } + if (shouldCache && elementToTreeCache.containsKey(elt)) { + return elementToTreeCache.get(elt); + } + + // Check for new declarations, outside of the AST. + if (elt instanceof DetachedVarSymbol) { + return ((DetachedVarSymbol) elt).getDeclaration(); + } + + // TODO: handle type parameter declarations? + Tree fromElt; + // Prevent calling declarationFor on elements we know we don't have the tree for. + + switch (ElementUtils.getKindRecordAsClass(elt)) { + case CLASS: // Including RECORD + case ENUM: + case INTERFACE: + case ANNOTATION_TYPE: + case FIELD: + case ENUM_CONSTANT: + case METHOD: + case CONSTRUCTOR: + fromElt = trees.getTree(elt); + break; + default: + fromElt = + com.sun.tools.javac.tree.TreeInfo.declarationFor( + (com.sun.tools.javac.code.Symbol) elt, + (com.sun.tools.javac.tree.JCTree) root); + break; + } + if (shouldCache) { + elementToTreeCache.put(elt, fromElt); + } + return fromElt; + } + + /** + * Returns true if {@code tree} is within a constructor. + * + * @param tree the tree that might be within a constructor + * @return true if {@code tree} is within a constructor + */ + protected final boolean isWithinConstructor(Tree tree) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getPath(tree)); + return enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod); + } + + /** + * Sets the path to the tree that an external "visitor" is visiting. The visitor is either a + * subclass of {@link BaseTypeVisitor} or {@link + * org.checkerframework.framework.flow.CFAbstractTransfer}. + * + * @param visitorTreePath path to the current tree that an external "visitor" is visiting + */ + public void setVisitorTreePath(@Nullable TreePath visitorTreePath) { + this.visitorTreePath = visitorTreePath; + } + + /** + * Returns the path to the tree that an external "visitor" is visiting. The type factory does + * not update this value as it computes the types of any tree or element needed compute the type + * of the tree being visited. Therefore this path may not be the path to the tree whose type is + * being computed. This method should not be used directly. Use {@link #getPath(Tree)} instead. + * + *

          This method is used to save the previous tree path and to give a hint to {@link + * #getPath(Tree)} on where to look for a tree rather than searching starting at the root. + * + * @return the path to the tree that an external "visitor" is visiting + */ + public @Nullable TreePath getVisitorTreePath() { + return visitorTreePath; + } + + /** + * Gets the path for the given {@link Tree} under the current root by checking from the + * visitor's current path, and using {@link Trees#getPath(CompilationUnitTree, Tree)} (which is + * much slower) only if {@code tree} is not found on the current path. + * + *

          Note that the given Tree has to be within the current compilation unit, otherwise null + * will be returned. + * + *

          Within a subclass of BaseTypeVisitor, use {@code getCurrentPath()} rather than this + * method. + * + * @param tree the {@link Tree} to get the path for + * @return the path for {@code tree} under the current root. Returns null if {@code tree} is not + * within the current compilation unit. + */ + public final @Nullable TreePath getPath(@FindDistinct Tree tree) { + assert root != null + : "AnnotatedTypeFactory.getPath(" + + tree.getKind() + + "): root needs to be set when used on trees; factory: " + + this.getClass().getSimpleName(); + + if (tree == null) { + return null; + } + + if (treePathCache.isCached(tree)) { + return treePathCache.getPath(root, tree); + } + + TreePath currentPath = visitorTreePath; + if (currentPath == null) { + TreePath path = TreePath.getPath(root, tree); + treePathCache.addPath(tree, path); + return path; + } + + // This method uses multiple heuristics to avoid calling + // TreePath.getPath() + + // If the current path you are visiting is for this tree we are done + if (currentPath.getLeaf() == tree) { + treePathCache.addPath(tree, currentPath); + return currentPath; + } + + // When running on Daikon, we noticed that a lot of calls happened + // within a small subtree containing the tree we are currently visiting + + // When testing on Daikon, two steps resulted in the best performance + if (currentPath.getParentPath() != null) { + currentPath = currentPath.getParentPath(); + treePathCache.addPath(currentPath.getLeaf(), currentPath); + if (currentPath.getLeaf() == tree) { + return currentPath; + } + if (currentPath.getParentPath() != null) { + currentPath = currentPath.getParentPath(); + treePathCache.addPath(currentPath.getLeaf(), currentPath); + if (currentPath.getLeaf() == tree) { + return currentPath; + } + } + } + + TreePath pathWithinSubtree = TreePath.getPath(currentPath, tree); + if (pathWithinSubtree != null) { + treePathCache.addPath(tree, pathWithinSubtree); + return pathWithinSubtree; + } + + // climb the current path till we see that + // Works when getPath called on the enclosing method, enclosing class. + TreePath current = currentPath; + while (current != null) { + treePathCache.addPath(current.getLeaf(), current); + if (current.getLeaf() == tree) { + return current; + } + current = current.getParentPath(); + } + + // OK, we give up. Use the cache to look up. + return treePathCache.getPath(root, tree); + } + + /** + * Set the tree path for the given artificial tree. + * + *

          See {@code + * org.checkerframework.framework.flow.CFCFGBuilder.CFCFGTranslationPhaseOne.handleArtificialTree(Tree)}. + * + * @param tree the artificial {@link Tree} to set the path for + * @param path the {@link TreePath} for the artificial tree + */ + public final void setPathForArtificialTree(Tree tree, TreePath path) { + treePathCache.addPath(tree, path); + } + + /** + * Assert that the type is a type of valid type mirror, i.e. not an ERROR or OTHER type. + * + * @param type an annotated type + * @return true if the type is a valid annotated type, false otherwise + */ + /*package-private*/ static final boolean validAnnotatedType(AnnotatedTypeMirror type) { + if (type == null) { + return false; + } + return validType(type.getUnderlyingType()); + } + + /** + * Used for asserting that a type is valid for converting to an annotated type. + * + * @return true if {@code type} can be converted to an annotated type, false otherwise + */ + private static boolean validType(TypeMirror type) { + if (type == null) { + return false; + } + switch (type.getKind()) { + case ERROR: + case OTHER: + case PACKAGE: + return false; + default: + return true; + } + } + + /** + * Parses all annotation files in the following order: + * + *

            + *
          1. Stub files, see {@link AnnotationFileElementTypes#parseStubFiles()}; + *
          2. Ajava files, see {@link AnnotationFileElementTypes#parseAjavaFiles()}. + *
          + * + *

          If a type is annotated with a qualifier from the same hierarchy in more than one stub + * file, the qualifier in the last stub file is applied. + * + *

          The annotations are stored by side-effecting {@link #stubTypes} and {@link #ajavaTypes}. + */ + protected void parseAnnotationFiles() { + stubTypes.parseStubFiles(); + ajavaTypes.parseAjavaFiles(); + } + + /** + * Returns all of the declaration annotations whose name equals the passed annotation class (or + * is an alias for it) including annotations: + * + *

            + *
          • on the element + *
          • written in stubfiles + *
          • inherited from overridden methods, (see {@link InheritedAnnotation}) + *
          • inherited from superclasses or super interfaces (see {@link Inherited}) + *
          + * + * @see #getDeclAnnotationNoAliases + * @param elt the element to retrieve the declaration annotation from + * @param anno annotation class + * @return the annotation mirror for anno + */ + @Override + public final AnnotationMirror getDeclAnnotation(Element elt, Class anno) { + AnnotationMirror result = getDeclAnnotation(elt, anno, true); + return result; + } + + /** + * Returns the annotation mirror used to annotate this element, whose name equals the passed + * annotation class. Looks in the same places specified by {@link #getDeclAnnotation(Element, + * Class)}. Returns null if none exists. Does not check for aliases of the annotation class. + * + *

          Call this method from a checker that needs to alias annotations for one purpose and not + * for another. For example, in the Lock Checker, {@code @LockingFree} and + * {@code @ReleasesNoLocks} are both aliases of {@code @SideEffectFree} since they are all + * considered side-effect-free with regard to the set of locks held before and after the method + * call. However, a {@code synchronized} block is permitted inside a {@code @ReleasesNoLocks} + * method but not inside a {@code @LockingFree} or {@code @SideEffectFree} method. + * + * @see #getDeclAnnotation + * @param elt the element to retrieve the declaration annotation from + * @param anno annotation class + * @return the annotation mirror for anno + */ + public final @Nullable AnnotationMirror getDeclAnnotationNoAliases( + Element elt, Class anno) { + return getDeclAnnotation(elt, anno, false); + } + + /** + * Returns true if the element appears in a stub file (Currently only works for methods, + * constructors, and fields). + */ + public boolean isFromStubFile(Element element) { + return this.getDeclAnnotation(element, FromStubFile.class) != null; + } + + /** + * Returns true if the element is from bytecode and the if the element did not appear in a stub + * file. Currently only works for methods, constructors, and fields. + */ + public boolean isFromByteCode(Element element) { + if (isFromStubFile(element)) { + return false; + } + return ElementUtils.isElementFromByteCode(element); + } + + /** + * Returns true if redundancy between a stub file and bytecode should be reported. + * + *

          For most type systems the default behavior of returning true is correct. For subcheckers, + * redundancy in one of the type hierarchies can be ok. Such implementations should return + * false. + * + * @return whether to warn about redundancy between a stub file and bytecode + */ + public boolean shouldWarnIfStubRedundantWithBytecode() { + return true; + } + + /** + * Returns the actual annotation mirror used to annotate this element, whose name equals the + * passed annotation class (or is an alias for it). Looks in the same places specified by {@link + * #getDeclAnnotation(Element, Class)}. Returns null if none exists. May return the canonical + * annotation that annotationName is an alias for. + * + *

          This is the private implementation of the same-named, public method. + * + *

          An option is provided to not check for aliases of annotations. For example, an annotated + * type factory may use aliasing for a pair of annotations for convenience while needing in some + * cases to determine a strict ordering between them, such as when determining whether the + * annotations on an overrider method are more specific than the annotations of an overridden + * method. + * + * @param elt the element to retrieve the annotation from + * @param annoClass the class of the annotation to retrieve + * @param checkAliases whether to return an annotation mirror for an alias of the requested + * annotation class name + * @return the annotation mirror for the requested annotation, or null if not found + */ + private @Nullable AnnotationMirror getDeclAnnotation( + Element elt, Class annoClass, boolean checkAliases) { + return getDeclAnnotation(elt, annoClass.getCanonicalName(), checkAliases); + } + + /** + * Returns the actual annotation mirror used to annotate this element, whose name equals the + * passed canonical annotation name (or is an alias for it). Returns null if none exists. May + * return the canonical annotation that annotationName is an alias for. + * + *

          An option is provided not to check for aliases of annotations. For example, an annotated + * type factory may use aliasing for a pair of annotations for convenience while needing in some + * cases to determine a strict ordering between them, such as when determining whether the + * annotations on an overrider method are more specific than the annotations of an overridden + * method. + * + * @param elt the element to retrieve the annotation from + * @param annoName the canonical annotation name to retrieve + * @param checkAliases whether to return an annotation mirror for an alias of the requested + * annotation class name + * @return the annotation mirror for the requested annotation, or null if not found + */ + private AnnotationMirror getDeclAnnotation( + Element elt, @FullyQualifiedName String annoName, boolean checkAliases) { + AnnotationMirrorSet declAnnos = getDeclAnnotations(elt); + + for (AnnotationMirror am : declAnnos) { + if (AnnotationUtils.areSameByName(am, annoName)) { + return am; } - } - // We should never reach here: isFunctionalInterface performs the same check - // and would have raised an error already. - throw new BugInCF( - "Expected the type of a cast tree in an assignment context to contain" - + " a functional interface bound." - + " Found type: %s for tree: %s in lambda tree: %s", - castATM, cast, tree); - } - return castATM; - - case NEW_CLASS: - NewClassTree newClass = (NewClassTree) parentTree; - int indexOfLambda = newClass.getArguments().indexOf(tree); - ParameterizedExecutableType con = this.constructorFromUse(newClass); - AnnotatedTypeMirror constructorParam = - AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(con.executableType, indexOfLambda); - assertIsFunctionalInterface(constructorParam.getUnderlyingType(), parentTree, tree); - return constructorParam; - - case NEW_ARRAY: - NewArrayTree newArray = (NewArrayTree) parentTree; - AnnotatedArrayType newArrayATM = getAnnotatedType(newArray); - AnnotatedTypeMirror elementATM = newArrayATM.getComponentType(); - assertIsFunctionalInterface(elementATM.getUnderlyingType(), parentTree, tree); - return elementATM; - - case METHOD_INVOCATION: - MethodInvocationTree method = (MethodInvocationTree) parentTree; - int index = method.getArguments().indexOf(tree); - ParameterizedExecutableType exe = this.methodFromUse(method); - AnnotatedTypeMirror param = - AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(exe.executableType, index); - assertIsFunctionalInterface(param.getUnderlyingType(), parentTree, tree); - return param; - - case VARIABLE: - VariableTree varTree = (VariableTree) parentTree; - assertIsFunctionalInterface(TreeUtils.typeOf(varTree), parentTree, tree); - return getAnnotatedTypeFromTypeTree(varTree.getType()); - - case ASSIGNMENT: - AssignmentTree assignmentTree = (AssignmentTree) parentTree; - assertIsFunctionalInterface(TreeUtils.typeOf(assignmentTree), parentTree, tree); - return getAnnotatedType(assignmentTree.getVariable()); - - case RETURN: - Tree enclosing = - TreePathUtil.enclosingOfKind( - getPath(parentTree), - new HashSet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION))); - - if (enclosing.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = (MethodTree) enclosing; - return getAnnotatedType(enclosingMethod.getReturnType()); + } + if (!checkAliases) { + return null; + } + // Look through aliases. + Map<@FullyQualifiedName String, AnnotationMirror> aliases = declAliases.get(annoName); + if (aliases == null) { + return null; + } + for (AnnotationMirror am : declAnnos) { + AnnotationMirror match = aliases.get(AnnotationUtils.annotationName(am)); + if (match != null) { + return match; + } + } + + // Not found. + return null; + } + + /** + * Returns all of the declaration annotations on this element including annotations: + * + *

            + *
          • on the element + *
          • written in stubfiles + *
          • inherited from overridden methods, (see {@link InheritedAnnotation}) + *
          • inherited from superclasses or super interfaces (see {@link Inherited}) + *
          + * + *

          This method returns the actual annotations not their aliases. {@link + * #getDeclAnnotation(Element, Class)} returns aliases. + * + * @param elt the element for which to determine annotations + * @return all of the declaration annotations on this element, written in stub files, or + * inherited + */ + public AnnotationMirrorSet getDeclAnnotations(Element elt) { + AnnotationMirrorSet cachedValue = cacheDeclAnnos.get(elt); + if (cachedValue != null) { + // Found in cache, return result. + return cachedValue; + } + + AnnotationMirrorSet results = new AnnotationMirrorSet(); + // Retrieving the annotations from the element. + // This includes annotations inherited from superclasses, but not superinterfaces or + // overridden methods. + List fromEle = elements.getAllAnnotationMirrors(elt); + for (AnnotationMirror annotation : fromEle) { + try { + results.add(annotation); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // If a CompletionFailure occurs, issue a warning. + checker.reportWarning( + annotation.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(elt), + annotation); + } + } + + // Add annotations from annotation files. + results.addAll(stubTypes.getDeclAnnotations(elt)); + results.addAll(ajavaTypes.getDeclAnnotations(elt)); + if (currentFileAjavaTypes != null) { + results.addAll(currentFileAjavaTypes.getDeclAnnotations(elt)); + } + + if (elt.getKind() == ElementKind.METHOD) { + // Retrieve the annotations from the overridden method's element. + inheritOverriddenDeclAnnos((ExecutableElement) elt, results); + } else if (ElementUtils.isTypeDeclaration(elt)) { + inheritOverriddenDeclAnnosFromTypeDecl(elt.asType(), results); + } + + // Add the element and its annotations to the cache. + if (!stubTypes.isParsing() + && !ajavaTypes.isParsing() + && (currentFileAjavaTypes == null || !currentFileAjavaTypes.isParsing())) { + cacheDeclAnnos.put(elt, results); + } + return results; + } + + /** + * Adds into {@code results} the inherited declaration annotations found in all elements of the + * super types of {@code typeMirror}. (Both superclasses and superinterfaces.) + * + * @param typeMirror type + * @param results the set of AnnotationMirrors to which this method adds declarations + * annotations + */ + private void inheritOverriddenDeclAnnosFromTypeDecl( + TypeMirror typeMirror, AnnotationMirrorSet results) { + List superTypes = types.directSupertypes(typeMirror); + for (TypeMirror superType : superTypes) { + TypeElement elt = TypesUtils.getTypeElement(superType); + if (elt == null) { + continue; + } + AnnotationMirrorSet superAnnos = getDeclAnnotations(elt); + for (AnnotationMirror annotation : superAnnos) { + List annotationsOnAnnotation; + try { + annotationsOnAnnotation = + annotation.getAnnotationType().asElement().getAnnotationMirrors(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. + checker.reportWarning( + annotation.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(elt), + annotation); + continue; + } + if (containsSameByClass(annotationsOnAnnotation, Inherited.class) + || AnnotationUtils.containsSameByName(inheritedAnnotations, annotation)) { + addOrMerge(results, annotation); + } + } + } + } + + /** + * Adds into {@code results} the declaration annotations found in all elements that the method + * element {@code elt} overrides. + * + * @param elt method element + * @param results {@code elt} local declaration annotations. The ones found in stub files and in + * the element itself. + */ + private void inheritOverriddenDeclAnnos(ExecutableElement elt, AnnotationMirrorSet results) { + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, this, elt); + + if (overriddenMethods != null) { + for (ExecutableElement superElt : overriddenMethods.values()) { + AnnotationMirrorSet superAnnos = getDeclAnnotations(superElt); + + for (AnnotationMirror annotation : superAnnos) { + List annotationsOnAnnotation; + try { + annotationsOnAnnotation = + annotation.getAnnotationType().asElement().getAnnotationMirrors(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Fix for Issue 348: If a CompletionFailure occurs, issue a warning. + checker.reportWarning( + annotation.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(elt), + annotation); + continue; + } + if (containsSameByClass(annotationsOnAnnotation, InheritedAnnotation.class) + || AnnotationUtils.containsSameByName( + inheritedAnnotations, annotation)) { + addOrMerge(results, annotation); + } + } + } + } + } + + private void addOrMerge(AnnotationMirrorSet results, AnnotationMirror annotation) { + if (AnnotationUtils.containsSameByName(results, annotation)) { + /* + * TODO: feature request: figure out a way to merge multiple annotations + * of the same kind. For some annotations this might mean merging some + * arrays, for others it might mean converting a single annotation into a + * container annotation. We should define a protected method for subclasses + * to adapt the behavior. + * For now, do nothing and just take the first, most concrete, annotation. + AnnotationMirror prev = null; + for (AnnotationMirror an : results) { + if (AnnotationUtils.areSameByName(an, annotation)) { + prev = an; + break; + } + } + results.remove(prev); + AnnotationMirror merged = ...; + results.add(merged); + */ } else { - LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) enclosing; - AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); - return methodExe.getReturnType(); - } - - case LAMBDA_EXPRESSION: - LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) parentTree; - AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); - return methodExe.getReturnType(); - - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree conditionalExpressionTree = - (ConditionalExpressionTree) parentTree; - AnnotatedTypeMirror trueType = - getAnnotatedType(conditionalExpressionTree.getTrueExpression()); - AnnotatedTypeMirror falseType = - getAnnotatedType(conditionalExpressionTree.getFalseExpression()); - - // Known cases where we must use LUB because falseType/trueType will not be equal: - // a) when one of the types is a type variable that extends a functional interface - // or extends a type variable that extends a functional interface - // b) When one of the two sides of the expression is a reference to a sub-interface. - // e.g. interface ConsumeStr { - // public void consume(String s) - // } - // interface SubConsumer extends ConsumeStr { - // default void someOtherMethod() { ... } - // } - // SubConsumer s = ...; - // ConsumeStr stringConsumer = (someCondition) ? s : System.out::println; - AnnotatedTypeMirror conditionalType = - AnnotatedTypes.leastUpperBound(this, trueType, falseType); - assertIsFunctionalInterface(conditionalType.getUnderlyingType(), parentTree, tree); - return conditionalType; - case CASE: - // Get the functional interface type of the whole switch expression. - Tree switchTree = parentPath.getParentPath().getLeaf(); - return getFunctionalInterfaceType(switchTree); - - default: - if (parentTree.getKind().toString().equals("YIELD")) { - TreePath pathToCase = TreePathUtil.pathTillOfKind(parentPath, Kind.CASE); - return getFunctionalInterfaceType(pathToCase.getParentPath().getLeaf()); + results.add(annotation); + } + } + + /** + * Returns a list of all declaration annotations used to annotate the element, which have a + * meta-annotation (i.e., an annotation on that annotation) with class {@code + * metaAnnotationClass}. + * + * @param element the element for which to determine annotations + * @param metaAnnotationClass the class of the meta-annotation that needs to be present + * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror + * at {@code element}, and {@code metaAnno} is the annotation mirror (of type {@code + * metaAnnotationClass}) used to meta-annotate the declaration of {@code anno} + */ + public List> getDeclAnnotationWithMetaAnnotation( + Element element, Class metaAnnotationClass) { + List> result = new ArrayList<>(); + AnnotationMirrorSet annotationMirrors = getDeclAnnotations(element); + + for (AnnotationMirror candidate : annotationMirrors) { + List metaAnnotationsOnAnnotation; + try { + metaAnnotationsOnAnnotation = + candidate.getAnnotationType().asElement().getAnnotationMirrors(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Fix for Issue 309: If a CompletionFailure occurs, issue a warning. + // I didn't find a nicer alternative to check whether the Symbol can be completed. + // The completer field of a Symbol might be non-null also in successful cases. + // Issue a warning (exception only happens once) and continue. + checker.reportWarning( + candidate.getAnnotationType().asElement(), + "annotation.not.completed", + ElementUtils.getQualifiedName(element), + candidate); + continue; + } + // First call copier, if exception, continue normal modula laws. + for (AnnotationMirror ma : metaAnnotationsOnAnnotation) { + if (areSameByClass(ma, metaAnnotationClass)) { + // This candidate has the right kind of meta-annotation. + // It might be a real contract, or a list of contracts. + if (isListForRepeatedAnnotation(candidate)) { + @SuppressWarnings("deprecation") // concrete annotation class is not known + List wrappedCandidates = + AnnotationUtils.getElementValueArray( + candidate, "value", AnnotationMirror.class, false); + for (AnnotationMirror wrappedCandidate : wrappedCandidates) { + result.add(IPair.of(wrappedCandidate, ma)); + } + } else { + result.add(IPair.of(candidate, ma)); + } + } + } + } + return result; + } + + /** Cache for {@link #isListForRepeatedAnnotation}. */ + private final Map isListForRepeatedAnnotationCache = new HashMap<>(); + + /** + * Returns true if the given annotation is a wrapper for multiple repeated annotations. + * + * @param a an annotation that might be a wrapper + * @return true if the argument is a wrapper for multiple repeated annotations + */ + private boolean isListForRepeatedAnnotation(AnnotationMirror a) { + DeclaredType annotationType = a.getAnnotationType(); + Boolean resultObject = isListForRepeatedAnnotationCache.get(annotationType); + if (resultObject != null) { + return resultObject; + } + boolean result = isListForRepeatedAnnotationImplementation(annotationType); + isListForRepeatedAnnotationCache.put(annotationType, result); + return result; + } + + /** + * Returns true if the annotation is a wrapper for multiple repeated annotations. + * + * @param annotationType the declaration of the annotation to test + * @return true if the annotation is a wrapper for multiple repeated annotations + */ + private boolean isListForRepeatedAnnotationImplementation(DeclaredType annotationType) { + TypeMirror enclosingType = annotationType.getEnclosingType(); + if (enclosingType == null) { + return false; + } + if (!annotationType.asElement().getSimpleName().contentEquals("List")) { + return false; + } + List annoElements = annotationType.asElement().getEnclosedElements(); + if (annoElements.size() != 1) { + return false; + } + // TODO: should check that the type of the single element is: "array of enclosingType". + return true; + } + + /** + * Returns a list of all annotations used to annotate this element, which have a meta-annotation + * (i.e., an annotation on that annotation) with class {@code metaAnnotationClass}. + * + * @param element the element at which to look for annotations + * @param metaAnnotationClass the class of the meta-annotation that needs to be present + * @return a list of pairs {@code (anno, metaAnno)} where {@code anno} is the annotation mirror + * at {@code element}, and {@code metaAnno} is the annotation mirror used to annotate {@code + * anno}. + */ + public List> getAnnotationWithMetaAnnotation( + Element element, Class metaAnnotationClass) { + AnnotationMirrorSet annotationMirrors = new AnnotationMirrorSet(); + // Consider real annotations. + annotationMirrors.addAll(getAnnotatedType(element).getAnnotations()); + // Consider declaration annotations + annotationMirrors.addAll(getDeclAnnotations(element)); + + List> result = new ArrayList<>(); + + // Go through all annotations found. + for (AnnotationMirror annotation : annotationMirrors) { + List annotationsOnAnnotation = + annotation.getAnnotationType().asElement().getAnnotationMirrors(); + for (AnnotationMirror a : annotationsOnAnnotation) { + if (areSameByClass(a, metaAnnotationClass)) { + result.add(IPair.of(annotation, a)); + } + } + } + return result; + } + + /** + * Whether or not the {@code annotatedTypeMirror} has a qualifier parameter. + * + * @param annotatedTypeMirror the type to check + * @param top the top of the hierarchy to check + * @return true if the type has a qualifier parameter + */ + public boolean hasQualifierParameterInHierarchy( + AnnotatedTypeMirror annotatedTypeMirror, AnnotationMirror top) { + return AnnotationUtils.containsSame( + getQualifierParameterHierarchies(annotatedTypeMirror), top); + } + + /** + * Whether or not the {@code element} has a qualifier parameter. + * + * @param element element to check + * @param top the top of the hierarchy to check + * @return true if the type has a qualifier parameter + */ + public boolean hasQualifierParameterInHierarchy( + @Nullable Element element, AnnotationMirror top) { + if (element == null) { + return false; + } + return AnnotationUtils.containsSame(getQualifierParameterHierarchies(element), top); + } + + /** + * Returns whether the {@code HasQualifierParameter} annotation was explicitly written on {@code + * element} for the hierarchy given by {@code top}. + * + * @param element the Element to check + * @param top the top qualifier for the hierarchy to check + * @return whether the class given by {@code element} has been explicitly annotated with {@code + * HasQualifierParameter} for the given hierarchy + */ + public boolean hasExplicitQualifierParameterInHierarchy(Element element, AnnotationMirror top) { + return AnnotationUtils.containsSame( + getSupportedAnnotationsInElementAnnotation( + element, HasQualifierParameter.class, hasQualifierParameterValueElement), + top); + } + + /** + * Returns whether the {@code NoQualifierParameter} annotation was explicitly written on {@code + * element} for the hierarchy given by {@code top}. + * + * @param element the Element to check + * @param top the top qualifier for the hierarchy to check + * @return whether the class given by {@code element} has been explicitly annotated with {@code + * NoQualifierParameter} for the given hierarchy + */ + public boolean hasExplicitNoQualifierParameterInHierarchy( + Element element, AnnotationMirror top) { + return AnnotationUtils.containsSame( + getSupportedAnnotationsInElementAnnotation( + element, NoQualifierParameter.class, noQualifierParameterValueElement), + top); + } + + /** + * Returns the set of top annotations representing all the hierarchies for which this type has a + * qualifier parameter. + * + * @param annotatedType the type to check + * @return the set of top annotations representing all the hierarchies for which this type has a + * qualifier parameter + */ + public AnnotationMirrorSet getQualifierParameterHierarchies(AnnotatedTypeMirror annotatedType) { + while (annotatedType.getKind() == TypeKind.TYPEVAR + || annotatedType.getKind() == TypeKind.WILDCARD) { + if (annotatedType.getKind() == TypeKind.TYPEVAR) { + annotatedType = ((AnnotatedTypeVariable) annotatedType).getUpperBound(); + } else if (annotatedType.getKind() == TypeKind.WILDCARD) { + annotatedType = ((AnnotatedWildcardType) annotatedType).getSuperBound(); + } + } + + if (annotatedType.getKind() != TypeKind.DECLARED) { + return AnnotationMirrorSet.emptySet(); + } + + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) annotatedType; + Element element = declaredType.getUnderlyingType().asElement(); + if (element == null) { + return AnnotationMirrorSet.emptySet(); + } + return getQualifierParameterHierarchies(element); + } + + /** + * Returns the set of top annotations representing all the hierarchies for which this element + * has a qualifier parameter. + * + * @param element the Element to check + * @return the set of top annotations representing all the hierarchies for which this element + * has a qualifier parameter + */ + public AnnotationMirrorSet getQualifierParameterHierarchies(Element element) { + if (!ElementUtils.isTypeDeclaration(element)) { + return AnnotationMirrorSet.emptySet(); + } + + AnnotationMirrorSet found = new AnnotationMirrorSet(); + found.addAll( + getSupportedAnnotationsInElementAnnotation( + element, HasQualifierParameter.class, hasQualifierParameterValueElement)); + AnnotationMirrorSet hasQualifierParameterTops = new AnnotationMirrorSet(); + PackageElement packageElement = ElementUtils.enclosingPackage(element); + + // Traverse all packages containing this element. + while (packageElement != null) { + AnnotationMirrorSet packageDefaultTops = + getSupportedAnnotationsInElementAnnotation( + packageElement, + HasQualifierParameter.class, + hasQualifierParameterValueElement); + hasQualifierParameterTops.addAll(packageDefaultTops); + + packageElement = ElementUtils.parentPackage(packageElement, elements); + } + + AnnotationMirrorSet noQualifierParamClasses = + getSupportedAnnotationsInElementAnnotation( + element, NoQualifierParameter.class, noQualifierParameterValueElement); + for (AnnotationMirror anno : hasQualifierParameterTops) { + if (!AnnotationUtils.containsSame(noQualifierParamClasses, anno)) { + found.add(anno); + } + } + + return found; + } + + /** + * Returns a set of supported annotation mirrors corresponding to the annotation classes listed + * in the value element of an annotation with class {@code annoClass} on {@code element}. + * + * @param element the Element to check + * @param annoClass the class for an annotation that's written on elements, whose value element + * is a list of annotation classes. It is always HasQualifierParameter or + * NoQualifierParameter + * @param valueElement the {@code value} field/element of an annotation with class {@code + * annoClass} + * @return the set of supported annotations with classes listed in the value element of an + * annotation with class {@code annoClass} on the {@code element}. Returns an empty set if + * {@code annoClass} is not written on {@code element} or {@code element} is null. + */ + private AnnotationMirrorSet getSupportedAnnotationsInElementAnnotation( + @Nullable Element element, + Class annoClass, + ExecutableElement valueElement) { + if (element == null) { + return AnnotationMirrorSet.emptySet(); + } + // TODO: caching + AnnotationMirror annotation = getDeclAnnotation(element, annoClass); + if (annotation == null) { + return AnnotationMirrorSet.emptySet(); + } + + AnnotationMirrorSet found = new AnnotationMirrorSet(); + List<@CanonicalName Name> qualClasses = + AnnotationUtils.getElementValueClassNames(annotation, valueElement); + for (Name qual : qualClasses) { + AnnotationMirror annotationMirror = AnnotationBuilder.fromName(elements, qual); + if (isSupportedQualifier(annotationMirror)) { + found.add(annotationMirror); + } + } + return found; + } + + /** + * A scanner that replaces annotations in one type with annotations from another. Used by {@link + * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror)} and {@link + * #replaceAnnotations(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. + */ + private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); + + /** + * Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code + * from} will be used everywhere they exist, but annotations in {@code to} will be kept anywhere + * that {@code from} is unannotated. + * + * @param from the annotated type mirror from which to take new annotations + * @param to the annotated type mirror to which the annotations will be added + */ + public void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + annotatedTypeReplacer.visit(from, to); + } + + /** + * Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}. + * Annotations from {@code from} will be used everywhere they exist, but annotations in {@code + * to} will be kept anywhere that {@code from} is unannotated. + * + * @param from the annotated type mirror from which to take new annotations + * @param to the annotated type mirror to which the annotations will be added + * @param top the top type of the hierarchy whose annotations will be added + */ + public void replaceAnnotations( + AnnotatedTypeMirror from, AnnotatedTypeMirror to, AnnotationMirror top) { + annotatedTypeReplacer.setTop(top); + annotatedTypeReplacer.visit(from, to); + annotatedTypeReplacer.setTop(null); + } + + /** The implementation of the visitor for #containsCapturedTypes. */ + private final SimpleAnnotatedTypeScanner containsCapturedTypes = + new SimpleAnnotatedTypeScanner<>( + (type, p) -> TypesUtils.isCapturedTypeVariable(type.getUnderlyingType()), + Boolean::logicalOr, + false); + + /** + * Returns true if {@code type} contains any captured type variables. + * + * @param type type to check + * @return true if {@code type} contains any captured type variables + */ + public boolean containsCapturedTypes(AnnotatedTypeMirror type) { + return containsCapturedTypes.visit(type); + } + + /** + * Returns the function type that this member reference targets. + * + *

          The function type is the type of the single method declared in the functional interface + * adapted as if it were invoked using the functional interface as the receiver expression. + * + *

          The target type of a member reference is the type to which it is assigned or casted. + * + * @param tree member reference tree + * @return the function type that this method reference targets + */ + public AnnotatedExecutableType getFunctionTypeFromTree(MemberReferenceTree tree) { + return getFnInterfaceFromTree(tree).second; + } + + /** + * Returns the function type that this lambda targets. + * + *

          The function type is the type of the single method declared in the functional interface + * adapted as if it were invoked using the functional interface as the receiver expression. + * + *

          The target type of a lambda is the type to which it is assigned or casted. + * + * @param tree lambda expression tree + * @return the function type that this lambda targets + */ + public AnnotatedExecutableType getFunctionTypeFromTree(LambdaExpressionTree tree) { + return getFnInterfaceFromTree(tree).second; + } + + /** + * Returns the functional interface and the function type that this lambda or member references + * targets. + * + *

          The function type is the type of the single method declared in the functional interface + * adapted as if it were invoked using the functional interface as the receiver expression. + * + *

          The target type of a lambda or a method reference is the type to which it is assigned or + * casted. + * + * @param tree lambda expression tree or member reference tree + * @return the functional interface and the function type that this method reference or lambda + * targets + */ + public IPair getFnInterfaceFromTree(Tree tree) { + // Functional interface + // This is the target type of `tree`. + AnnotatedTypeMirror functionalInterfaceType = getFunctionalInterfaceType(tree); + if (functionalInterfaceType.getKind() == TypeKind.DECLARED) { + functionalInterfaceType = + makeGroundTargetType( + (AnnotatedDeclaredType) functionalInterfaceType, + (DeclaredType) TreeUtils.typeOf(tree)); + } + + // Functional method + ExecutableElement fnElement = TreeUtils.findFunction(tree, processingEnv); + + // Function type + AnnotatedExecutableType functionType = + AnnotatedTypes.asMemberOf(types, this, functionalInterfaceType, fnElement); + return IPair.of(functionalInterfaceType, functionType); + } + + /** + * Get the AnnotatedDeclaredType for the FunctionalInterface from assignment context of the + * method reference or lambda expression which may be a variable assignment, a method call, or a + * cast. + * + *

          The assignment context is not always correct, so we must search up the AST. It will + * recursively search for lambdas nested in lambdas. + * + * @param tree the tree of the lambda or method reference + * @return the functional interface type or a type argument from a raw type + */ + private AnnotatedTypeMirror getFunctionalInterfaceType(Tree tree) { + TreePath parentPath = getPath(tree).getParentPath(); + Tree parentTree = parentPath.getLeaf(); + switch (parentTree.getKind()) { + case PARENTHESIZED: + return getFunctionalInterfaceType(parentTree); + + case TYPE_CAST: + TypeCastTree cast = (TypeCastTree) parentTree; + assertIsFunctionalInterface( + trees.getTypeMirror(getPath(cast.getType())), parentTree, tree); + AnnotatedTypeMirror castATM = getAnnotatedType(cast.getType()); + if (castATM.getKind() == TypeKind.INTERSECTION) { + AnnotatedIntersectionType itype = (AnnotatedIntersectionType) castATM; + for (AnnotatedTypeMirror t : itype.directSupertypes()) { + if (TypesUtils.isFunctionalInterface( + t.getUnderlyingType(), getProcessingEnv())) { + return t; + } + } + // We should never reach here: isFunctionalInterface performs the same check + // and would have raised an error already. + throw new BugInCF( + "Expected the type of a cast tree in an assignment context to contain" + + " a functional interface bound." + + " Found type: %s for tree: %s in lambda tree: %s", + castATM, cast, tree); + } + return castATM; + + case NEW_CLASS: + NewClassTree newClass = (NewClassTree) parentTree; + int indexOfLambda = newClass.getArguments().indexOf(tree); + ParameterizedExecutableType con = this.constructorFromUse(newClass); + AnnotatedTypeMirror constructorParam = + AnnotatedTypes.getAnnotatedTypeMirrorOfParameter( + con.executableType, indexOfLambda); + assertIsFunctionalInterface(constructorParam.getUnderlyingType(), parentTree, tree); + return constructorParam; + + case NEW_ARRAY: + NewArrayTree newArray = (NewArrayTree) parentTree; + AnnotatedArrayType newArrayATM = getAnnotatedType(newArray); + AnnotatedTypeMirror elementATM = newArrayATM.getComponentType(); + assertIsFunctionalInterface(elementATM.getUnderlyingType(), parentTree, tree); + return elementATM; + + case METHOD_INVOCATION: + MethodInvocationTree method = (MethodInvocationTree) parentTree; + int index = method.getArguments().indexOf(tree); + ParameterizedExecutableType exe = this.methodFromUse(method); + AnnotatedTypeMirror param = + AnnotatedTypes.getAnnotatedTypeMirrorOfParameter(exe.executableType, index); + assertIsFunctionalInterface(param.getUnderlyingType(), parentTree, tree); + return param; + + case VARIABLE: + VariableTree varTree = (VariableTree) parentTree; + assertIsFunctionalInterface(TreeUtils.typeOf(varTree), parentTree, tree); + return getAnnotatedTypeFromTypeTree(varTree.getType()); + + case ASSIGNMENT: + AssignmentTree assignmentTree = (AssignmentTree) parentTree; + assertIsFunctionalInterface(TreeUtils.typeOf(assignmentTree), parentTree, tree); + return getAnnotatedType(assignmentTree.getVariable()); + + case RETURN: + Tree enclosing = + TreePathUtil.enclosingOfKind( + getPath(parentTree), + new HashSet<>( + Arrays.asList( + Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION))); + + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = (MethodTree) enclosing; + return getAnnotatedType(enclosingMethod.getReturnType()); + } else { + LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) enclosing; + AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); + return methodExe.getReturnType(); + } + + case LAMBDA_EXPRESSION: + LambdaExpressionTree enclosingLambda = (LambdaExpressionTree) parentTree; + AnnotatedExecutableType methodExe = getFunctionTypeFromTree(enclosingLambda); + return methodExe.getReturnType(); + + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditionalExpressionTree = + (ConditionalExpressionTree) parentTree; + AnnotatedTypeMirror trueType = + getAnnotatedType(conditionalExpressionTree.getTrueExpression()); + AnnotatedTypeMirror falseType = + getAnnotatedType(conditionalExpressionTree.getFalseExpression()); + + // Known cases where we must use LUB because falseType/trueType will not be equal: + // a) when one of the types is a type variable that extends a functional interface + // or extends a type variable that extends a functional interface + // b) When one of the two sides of the expression is a reference to a sub-interface. + // e.g. interface ConsumeStr { + // public void consume(String s) + // } + // interface SubConsumer extends ConsumeStr { + // default void someOtherMethod() { ... } + // } + // SubConsumer s = ...; + // ConsumeStr stringConsumer = (someCondition) ? s : System.out::println; + AnnotatedTypeMirror conditionalType = + AnnotatedTypes.leastUpperBound(this, trueType, falseType); + assertIsFunctionalInterface(conditionalType.getUnderlyingType(), parentTree, tree); + return conditionalType; + case CASE: + // Get the functional interface type of the whole switch expression. + Tree switchTree = parentPath.getParentPath().getLeaf(); + return getFunctionalInterfaceType(switchTree); + + default: + if (parentTree.getKind().toString().equals("YIELD")) { + TreePath pathToCase = TreePathUtil.pathTillOfKind(parentPath, Kind.CASE); + return getFunctionalInterfaceType(pathToCase.getParentPath().getLeaf()); + } + throw new BugInCF( + "Could not find functional interface from assignment context. " + + "Unexpected tree type: " + + parentTree.getKind() + + " For lambda tree: " + + tree); } + } + + /** + * Throws an exception if the type is not a funtional interface. + * + * @param typeMirror a type that must be a funtional interface + * @param contextTree the tree that has the given type; used only for diagnostic messages + * @param tree a labmba tree that encloses {@code contextTree}; used only for diagnostic + * messages + */ + private void assertIsFunctionalInterface(TypeMirror typeMirror, Tree contextTree, Tree tree) { + if (typeMirror.getKind() == TypeKind.WILDCARD) { + // Ignore wildcards, because they are type arguments from raw types. + return; + } + Type type = (Type) typeMirror; + if (TypesUtils.isFunctionalInterface(type, processingEnv)) { + return; + } + + if (type.getKind() == TypeKind.INTERSECTION) { + IntersectionType itype = (IntersectionType) type; + for (TypeMirror t : itype.getBounds()) { + if (TypesUtils.isFunctionalInterface(t, processingEnv)) { + // As long as any of the bounds is a functional interface, we should be fine. + return; + } + } + } + throw new BugInCF( - "Could not find functional interface from assignment context. " - + "Unexpected tree type: " - + parentTree.getKind() - + " For lambda tree: " - + tree); - } - } - - /** - * Throws an exception if the type is not a funtional interface. - * - * @param typeMirror a type that must be a funtional interface - * @param contextTree the tree that has the given type; used only for diagnostic messages - * @param tree a labmba tree that encloses {@code contextTree}; used only for diagnostic messages - */ - private void assertIsFunctionalInterface(TypeMirror typeMirror, Tree contextTree, Tree tree) { - if (typeMirror.getKind() == TypeKind.WILDCARD) { - // Ignore wildcards, because they are type arguments from raw types. - return; - } - Type type = (Type) typeMirror; - if (TypesUtils.isFunctionalInterface(type, processingEnv)) { - return; - } - - if (type.getKind() == TypeKind.INTERSECTION) { - IntersectionType itype = (IntersectionType) type; - for (TypeMirror t : itype.getBounds()) { - if (TypesUtils.isFunctionalInterface(t, processingEnv)) { - // As long as any of the bounds is a functional interface, we should be fine. - return; + "Expected the type of %s tree in assignment context to be a functional interface. " + + "Found type: %s for tree: %s in lambda tree: %s", + contextTree.getKind(), type, contextTree, tree); + } + + /** + * Create the ground target type of the functional interface. + * + *

          Basically, it replaces the wildcards with their bounds doing a capture conversion like glb + * for extends bounds. + * + * @see "JLS 9.9" + * @param functionalType the functional interface type + * @param groundTargetJavaType the Java type as found by javac + * @return the grounded functional type + */ + private AnnotatedDeclaredType makeGroundTargetType( + AnnotatedDeclaredType functionalType, DeclaredType groundTargetJavaType) { + if (functionalType.getTypeArguments().isEmpty()) { + return functionalType; + } + + List bounds = + this.typeVariablesFromUse( + functionalType, + (TypeElement) functionalType.getUnderlyingType().asElement()); + + boolean sizesDiffer = + functionalType.getTypeArguments().size() + != groundTargetJavaType.getTypeArguments().size(); + + // This is the declared type of the functional type meaning that the type arguments are the + // type parameters. + DeclaredType declaredType = + (DeclaredType) functionalType.getUnderlyingType().asElement().asType(); + Map typeVarToTypeArg = + new HashMap<>(functionalType.getTypeArguments().size()); + for (int i = 0; i < functionalType.getTypeArguments().size(); i++) { + TypeVariable typeVariable = (TypeVariable) declaredType.getTypeArguments().get(i); + AnnotatedTypeMirror argType = functionalType.getTypeArguments().get(i); + + if (argType.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) argType; + + TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType(); + + if (wildcardType.isTypeArgOfRawType()) { + // Keep the type arguments from raw types so that it is ignored by later + // subtyping and containment checks. + typeVarToTypeArg.put(typeVariable, wildcardType); + } else if (isExtendsWildcard(wildcardType)) { + TypeMirror correctArgType; + if (sizesDiffer) { + // The Java type is raw. + TypeMirror typeParamUbType = + bounds.get(i).getUpperBound().getUnderlyingType(); + correctArgType = + TypesUtils.greatestLowerBound( + typeParamUbType, + wildcardUbType, + this.checker.getProcessingEnvironment()); + } else { + correctArgType = groundTargetJavaType.getTypeArguments().get(i); + } + + final AnnotatedTypeMirror newArg; + if (types.isSameType(wildcardUbType, correctArgType)) { + newArg = wildcardType.getExtendsBound().deepCopy(); + } else if (correctArgType.getKind() == TypeKind.TYPEVAR) { + newArg = this.toAnnotatedType(correctArgType, false); + AnnotatedTypeVariable newArgAsTypeVar = (AnnotatedTypeVariable) newArg; + newArgAsTypeVar + .getUpperBound() + .replaceAnnotations( + wildcardType.getExtendsBound().getAnnotations()); + newArgAsTypeVar + .getLowerBound() + .replaceAnnotations(wildcardType.getSuperBound().getAnnotations()); + } else { + newArg = this.toAnnotatedType(correctArgType, false); + newArg.replaceAnnotations(wildcardType.getExtendsBound().getAnnotations()); + } + + typeVarToTypeArg.put(typeVariable, newArg); + } else { + typeVarToTypeArg.put(typeVariable, wildcardType.getSuperBound()); + } + } else { + typeVarToTypeArg.put(typeVariable, argType); + } + } + + // The ground functional type must be created using type variable substitution or else the + // underlying type will not match the annotated type. + AnnotatedDeclaredType groundFunctionalType = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + declaredType, this, functionalType.isDeclaration()); + initializeAtm(groundFunctionalType); + groundFunctionalType = + (AnnotatedDeclaredType) + getTypeVarSubstitutor().substitute(typeVarToTypeArg, groundFunctionalType); + groundFunctionalType.addAnnotations(functionalType.getAnnotations()); + + // When the groundTargetJavaType is different from the underlying type of functionalType, + // only the main annotations are copied. Add default annotations in places without + // annotations. + addDefaultAnnotations(groundFunctionalType); + return groundFunctionalType; + } + + /** + * Return true if {@code type} should be captured. + * + *

          {@code type} should be captured if all of the following are true: + * + *

            + *
          • {@code type} and {@code typeMirror} are both declared types. + *
          • {@code type} its underlying type is not raw. + *
          • {@code type} has a wildcard as a type argument and {@code typeMirror} has a captured + * type variable as the corresponding type argument. + *
          + * + * @param type annotated type that might need to be captured + * @param typeMirror the capture of the underlying type of {@code type} + * @return true if {@code type} should be captured + */ + private boolean shouldCapture(AnnotatedTypeMirror type, TypeMirror typeMirror) { + if (type.getKind() != TypeKind.DECLARED || typeMirror.getKind() != TypeKind.DECLARED) { + return false; + } + + AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; + DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; + if (capturedTypeMirror.getTypeArguments().isEmpty()) { + return false; } - } - } - throw new BugInCF( - "Expected the type of %s tree in assignment context to be a functional interface. " - + "Found type: %s for tree: %s in lambda tree: %s", - contextTree.getKind(), type, contextTree, tree); - } - - /** - * Create the ground target type of the functional interface. - * - *

          Basically, it replaces the wildcards with their bounds doing a capture conversion like glb - * for extends bounds. - * - * @see "JLS 9.9" - * @param functionalType the functional interface type - * @param groundTargetJavaType the Java type as found by javac - * @return the grounded functional type - */ - private AnnotatedDeclaredType makeGroundTargetType( - AnnotatedDeclaredType functionalType, DeclaredType groundTargetJavaType) { - if (functionalType.getTypeArguments().isEmpty()) { - return functionalType; - } - - List bounds = - this.typeVariablesFromUse( - functionalType, (TypeElement) functionalType.getUnderlyingType().asElement()); - - boolean sizesDiffer = - functionalType.getTypeArguments().size() != groundTargetJavaType.getTypeArguments().size(); - - // This is the declared type of the functional type meaning that the type arguments are the - // type parameters. - DeclaredType declaredType = - (DeclaredType) functionalType.getUnderlyingType().asElement().asType(); - Map typeVarToTypeArg = - new HashMap<>(functionalType.getTypeArguments().size()); - for (int i = 0; i < functionalType.getTypeArguments().size(); i++) { - TypeVariable typeVariable = (TypeVariable) declaredType.getTypeArguments().get(i); - AnnotatedTypeMirror argType = functionalType.getTypeArguments().get(i); - - if (argType.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) argType; - - TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType(); - - if (wildcardType.isTypeArgOfRawType()) { - // Keep the type arguments from raw types so that it is ignored by later - // subtyping and containment checks. - typeVarToTypeArg.put(typeVariable, wildcardType); - } else if (isExtendsWildcard(wildcardType)) { - TypeMirror correctArgType; - if (sizesDiffer) { - // The Java type is raw. - TypeMirror typeParamUbType = bounds.get(i).getUpperBound().getUnderlyingType(); - correctArgType = - TypesUtils.greatestLowerBound( - typeParamUbType, wildcardUbType, this.checker.getProcessingEnvironment()); - } else { - correctArgType = groundTargetJavaType.getTypeArguments().get(i); - } + if (uncapturedType.isUnderlyingTypeRaw()) { + return false; + } - final AnnotatedTypeMirror newArg; - if (types.isSameType(wildcardUbType, correctArgType)) { - newArg = wildcardType.getExtendsBound().deepCopy(); - } else if (correctArgType.getKind() == TypeKind.TYPEVAR) { - newArg = this.toAnnotatedType(correctArgType, false); - AnnotatedTypeVariable newArgAsTypeVar = (AnnotatedTypeVariable) newArg; - newArgAsTypeVar - .getUpperBound() - .replaceAnnotations(wildcardType.getExtendsBound().getAnnotations()); - newArgAsTypeVar - .getLowerBound() - .replaceAnnotations(wildcardType.getSuperBound().getAnnotations()); - } else { - newArg = this.toAnnotatedType(correctArgType, false); - newArg.replaceAnnotations(wildcardType.getExtendsBound().getAnnotations()); - } + for (AnnotatedTypeMirror typeArg : uncapturedType.getTypeArguments()) { + if (AnnotatedTypes.isTypeArgOfRawType(typeArg)) { + return false; + } + } - typeVarToTypeArg.put(typeVariable, newArg); - } else { - typeVarToTypeArg.put(typeVariable, wildcardType.getSuperBound()); + if (capturedTypeMirror.getTypeArguments().size() + != uncapturedType.getTypeArguments().size()) { + throw new BugInCF( + "Not the same number of type arguments: capturedTypeMirror: %s uncapturedType:" + + " %s", + capturedTypeMirror, uncapturedType); } - } else { - typeVarToTypeArg.put(typeVariable, argType); - } - } - // The ground functional type must be created using type variable substitution or else the - // underlying type will not match the annotated type. - AnnotatedDeclaredType groundFunctionalType = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType(declaredType, this, functionalType.isDeclaration()); - initializeAtm(groundFunctionalType); - groundFunctionalType = - (AnnotatedDeclaredType) - getTypeVarSubstitutor().substitute(typeVarToTypeArg, groundFunctionalType); - groundFunctionalType.addAnnotations(functionalType.getAnnotations()); - - // When the groundTargetJavaType is different from the underlying type of functionalType, - // only the main annotations are copied. Add default annotations in places without - // annotations. - addDefaultAnnotations(groundFunctionalType); - return groundFunctionalType; - } - - /** - * Return true if {@code type} should be captured. - * - *

          {@code type} should be captured if all of the following are true: - * - *

            - *
          • {@code type} and {@code typeMirror} are both declared types. - *
          • {@code type} its underlying type is not raw. - *
          • {@code type} has a wildcard as a type argument and {@code typeMirror} has a captured type - * variable as the corresponding type argument. - *
          - * - * @param type annotated type that might need to be captured - * @param typeMirror the capture of the underlying type of {@code type} - * @return true if {@code type} should be captured - */ - private boolean shouldCapture(AnnotatedTypeMirror type, TypeMirror typeMirror) { - if (type.getKind() != TypeKind.DECLARED || typeMirror.getKind() != TypeKind.DECLARED) { - return false; - } - - AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; - DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; - if (capturedTypeMirror.getTypeArguments().isEmpty()) { - return false; - } - - if (uncapturedType.isUnderlyingTypeRaw()) { - return false; - } - - for (AnnotatedTypeMirror typeArg : uncapturedType.getTypeArguments()) { - if (AnnotatedTypes.isTypeArgOfRawType(typeArg)) { + for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { + TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD + && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) + || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { + return true; + } + } return false; - } } - if (capturedTypeMirror.getTypeArguments().size() != uncapturedType.getTypeArguments().size()) { - throw new BugInCF( - "Not the same number of type arguments: capturedTypeMirror: %s uncapturedType:" + " %s", - capturedTypeMirror, uncapturedType); + /** + * Apply capture conversion to {@code typeToCapture}. + * + *

          Capture conversion is the process of converting wildcards in a parameterized type to fresh + * type variables. See JLS + * 5.1.10 for details. + * + *

          If {@code type} is not a declared type or if it does not have any wildcard type arguments, + * this method returns {@code type}. + * + * @param typeToCapture type to capture + * @return the result of applying capture conversion to {@code typeToCapture} + */ + public AnnotatedTypeMirror applyCaptureConversion(AnnotatedTypeMirror typeToCapture) { + TypeMirror capturedTypeMirror = types.capture(typeToCapture.getUnderlyingType()); + return applyCaptureConversion(typeToCapture, capturedTypeMirror); } - for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { - TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD - && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) - || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { - return true; - } - } - return false; - } - - /** - * Apply capture conversion to {@code typeToCapture}. - * - *

          Capture conversion is the process of converting wildcards in a parameterized type to fresh - * type variables. See JLS 5.1.10 - * for details. - * - *

          If {@code type} is not a declared type or if it does not have any wildcard type arguments, - * this method returns {@code type}. - * - * @param typeToCapture type to capture - * @return the result of applying capture conversion to {@code typeToCapture} - */ - public AnnotatedTypeMirror applyCaptureConversion(AnnotatedTypeMirror typeToCapture) { - TypeMirror capturedTypeMirror = types.capture(typeToCapture.getUnderlyingType()); - return applyCaptureConversion(typeToCapture, capturedTypeMirror); - } - - /** - * Apply capture conversion to {@code type}. - * - *

          Capture conversion is the process of converting wildcards in a parameterized type to fresh - * type variables. See JLS 5.1.10 - * for details. - * - *

          If {@code type} is not a declared type or if it does not have any wildcard type arguments, - * this method returns {@code type}. - * - * @param type type to capture - * @param typeMirror the result of applying capture conversion to the underlying type of {@code - * type}; it is used as the underlying type of the returned type - * @return the result of applying capture conversion to {@code type} - */ - public AnnotatedTypeMirror applyCaptureConversion( - AnnotatedTypeMirror type, TypeMirror typeMirror) { - // If the type contains type arguments of raw types, don't capture, but mark all - // wildcards that should have been captured as "raw" before it is returned. - if (typeMirror.getKind() == TypeKind.DECLARED && type.getKind() == TypeKind.DECLARED) { - boolean fromRawType = false; - AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; - for (AnnotatedTypeMirror typeArg : uncapturedType.getTypeArguments()) { - if (AnnotatedTypes.isTypeArgOfRawType(typeArg)) { - fromRawType = true; - break; + /** + * Apply capture conversion to {@code type}. + * + *

          Capture conversion is the process of converting wildcards in a parameterized type to fresh + * type variables. See JLS + * 5.1.10 for details. + * + *

          If {@code type} is not a declared type or if it does not have any wildcard type arguments, + * this method returns {@code type}. + * + * @param type type to capture + * @param typeMirror the result of applying capture conversion to the underlying type of {@code + * type}; it is used as the underlying type of the returned type + * @return the result of applying capture conversion to {@code type} + */ + public AnnotatedTypeMirror applyCaptureConversion( + AnnotatedTypeMirror type, TypeMirror typeMirror) { + // If the type contains type arguments of raw types, don't capture, but mark all + // wildcards that should have been captured as "raw" before it is returned. + if (typeMirror.getKind() == TypeKind.DECLARED && type.getKind() == TypeKind.DECLARED) { + boolean fromRawType = false; + AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; + for (AnnotatedTypeMirror typeArg : uncapturedType.getTypeArguments()) { + if (AnnotatedTypes.isTypeArgOfRawType(typeArg)) { + fromRawType = true; + break; + } + } + if (fromRawType) { + DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; + for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { + AnnotatedTypeMirror uncapturedTypeArg = + uncapturedType.getTypeArguments().get(i); + TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); + if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD + && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) + || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { + ((AnnotatedWildcardType) uncapturedTypeArg).setTypeArgOfRawType(); + } + } + return type; + } } - } - if (fromRawType) { + + if (!shouldCapture(type, typeMirror)) { + return type; + } + + AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; - for (int i = 0; i < capturedTypeMirror.getTypeArguments().size(); i++) { - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - TypeMirror capturedTypeArgTM = capturedTypeMirror.getTypeArguments().get(i); - if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD - && (TypesUtils.isCapturedTypeVariable(capturedTypeArgTM) - || capturedTypeArgTM.getKind() != TypeKind.WILDCARD)) { - ((AnnotatedWildcardType) uncapturedTypeArg).setTypeArgOfRawType(); - } + // `capturedType` is the return value of this method. + AnnotatedDeclaredType capturedType = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType(capturedTypeMirror, this, false); + + nonWildcardTypeArgCopier.copy(uncapturedType, capturedType); + + AnnotatedDeclaredType typeDeclaration = + (AnnotatedDeclaredType) + getAnnotatedType(uncapturedType.getUnderlyingType().asElement()); + + // A mapping from type variable to its type argument in the captured type. + Map typeVarToAnnotatedTypeArg = new HashMap<>(); + // A mapping from a captured type variable to the annotated captured type variable. + Map capturedTypeVarToAnnotatedTypeVar = + new HashMap<>(); + // `newTypeArgs` will be the type arguments of the result of this method. + List newTypeArgs = new ArrayList<>(); + for (int i = 0; i < typeDeclaration.getTypeArguments().size(); i++) { + TypeVariable typeVarTypeMirror = + (TypeVariable) typeDeclaration.getTypeArguments().get(i).getUnderlyingType(); + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + AnnotatedTypeMirror capturedTypeArg = capturedType.getTypeArguments().get(i); + if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD) { + // The type argument is a captured type variable. Use the type argument from the + // newly created and yet-to-be annotated capturedType. (The annotations are added + // by #annotateCapturedTypeVar, which is called at the end of this method.) + typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, capturedTypeArg); + newTypeArgs.add(capturedTypeArg); + if (TypesUtils.isCapturedTypeVariable(capturedTypeArg.getUnderlyingType())) { + // Also, add a mapping from the captured type variable to the annotated captured + // type variable, so that if one captured type variable refers to another, the + // same AnnotatedTypeVariable object is used. + capturedTypeVarToAnnotatedTypeVar.put( + ((AnnotatedTypeVariable) capturedTypeArg).getUnderlyingType(), + (AnnotatedTypeVariable) capturedTypeArg); + } else { + // Javac used a declared type instead of a captured type variable. This seems + // to happen when the bounds of the captured type variable would have been + // identical. This seems to be a violation of the JLS, but javac does this, so + // the Checker Framework must handle that case. (See + // https://bugs.openjdk.org/browse/JDK-8054309.) + replaceAnnotations( + ((AnnotatedWildcardType) uncapturedTypeArg).getSuperBound(), + capturedTypeArg); + } + } else { + // The type argument is not a wildcard. + // typeVarTypeMirror is the type parameter for which uncapturedTypeArg is a type + // argument. + typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, uncapturedTypeArg); + if (uncapturedTypeArg.getKind() == TypeKind.TYPEVAR) { + // If the type arg is a type variable also add it to the + // typeVarToAnnotatedTypeArg map, so that references to the type variable are + // substituted. + AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) uncapturedTypeArg; + typeVarToAnnotatedTypeArg.put(typeVar.getUnderlyingType(), typeVar); + } + newTypeArgs.add(uncapturedTypeArg); + } } - return type; - } - } - if (!shouldCapture(type, typeMirror)) { - return type; - } - - AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; - DeclaredType capturedTypeMirror = (DeclaredType) typeMirror; - // `capturedType` is the return value of this method. - AnnotatedDeclaredType capturedType = - (AnnotatedDeclaredType) AnnotatedTypeMirror.createType(capturedTypeMirror, this, false); - - nonWildcardTypeArgCopier.copy(uncapturedType, capturedType); - - AnnotatedDeclaredType typeDeclaration = - (AnnotatedDeclaredType) getAnnotatedType(uncapturedType.getUnderlyingType().asElement()); - - // A mapping from type variable to its type argument in the captured type. - Map typeVarToAnnotatedTypeArg = new HashMap<>(); - // A mapping from a captured type variable to the annotated captured type variable. - Map capturedTypeVarToAnnotatedTypeVar = new HashMap<>(); - // `newTypeArgs` will be the type arguments of the result of this method. - List newTypeArgs = new ArrayList<>(); - for (int i = 0; i < typeDeclaration.getTypeArguments().size(); i++) { - TypeVariable typeVarTypeMirror = - (TypeVariable) typeDeclaration.getTypeArguments().get(i).getUnderlyingType(); - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - AnnotatedTypeMirror capturedTypeArg = capturedType.getTypeArguments().get(i); - if (uncapturedTypeArg.getKind() == TypeKind.WILDCARD) { - // The type argument is a captured type variable. Use the type argument from the - // newly created and yet-to-be annotated capturedType. (The annotations are added - // by #annotateCapturedTypeVar, which is called at the end of this method.) - typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, capturedTypeArg); - newTypeArgs.add(capturedTypeArg); - if (TypesUtils.isCapturedTypeVariable(capturedTypeArg.getUnderlyingType())) { - // Also, add a mapping from the captured type variable to the annotated captured - // type variable, so that if one captured type variable refers to another, the - // same AnnotatedTypeVariable object is used. - capturedTypeVarToAnnotatedTypeVar.put( - ((AnnotatedTypeVariable) capturedTypeArg).getUnderlyingType(), - (AnnotatedTypeVariable) capturedTypeArg); - } else { - // Javac used a declared type instead of a captured type variable. This seems - // to happen when the bounds of the captured type variable would have been - // identical. This seems to be a violation of the JLS, but javac does this, so - // the Checker Framework must handle that case. (See - // https://bugs.openjdk.org/browse/JDK-8054309.) - replaceAnnotations( - ((AnnotatedWildcardType) uncapturedTypeArg).getSuperBound(), capturedTypeArg); - } - } else { - // The type argument is not a wildcard. - // typeVarTypeMirror is the type parameter for which uncapturedTypeArg is a type - // argument. - typeVarToAnnotatedTypeArg.put(typeVarTypeMirror, uncapturedTypeArg); - if (uncapturedTypeArg.getKind() == TypeKind.TYPEVAR) { - // If the type arg is a type variable also add it to the - // typeVarToAnnotatedTypeArg map, so that references to the type variable are - // substituted. - AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) uncapturedTypeArg; - typeVarToAnnotatedTypeArg.put(typeVar.getUnderlyingType(), typeVar); - } - newTypeArgs.add(uncapturedTypeArg); - } + // Set the annotations of each captured type variable. + List orderToCapture = + order(capturedTypeVarToAnnotatedTypeVar.values()); + for (AnnotatedTypeVariable capturedTypeArg : orderToCapture) { + int i = + capturedTypeMirror + .getTypeArguments() + .indexOf(capturedTypeArg.getUnderlyingType()); + AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); + AnnotatedTypeVariable typeVariable = + (AnnotatedTypeVariable) typeDeclaration.getTypeArguments().get(i); + annotateCapturedTypeVar( + typeVarToAnnotatedTypeArg, + capturedTypeVarToAnnotatedTypeVar, + (AnnotatedWildcardType) uncapturedTypeArg, + typeVariable, + capturedTypeArg); + newTypeArgs.set(i, capturedTypeArg); + } + + capturedType.setTypeArguments(newTypeArgs); + capturedType.addAnnotations(uncapturedType.getAnnotations()); + return capturedType; } - // Set the annotations of each captured type variable. - List orderToCapture = order(capturedTypeVarToAnnotatedTypeVar.values()); - for (AnnotatedTypeVariable capturedTypeArg : orderToCapture) { - int i = capturedTypeMirror.getTypeArguments().indexOf(capturedTypeArg.getUnderlyingType()); - AnnotatedTypeMirror uncapturedTypeArg = uncapturedType.getTypeArguments().get(i); - AnnotatedTypeVariable typeVariable = - (AnnotatedTypeVariable) typeDeclaration.getTypeArguments().get(i); - annotateCapturedTypeVar( - typeVarToAnnotatedTypeArg, - capturedTypeVarToAnnotatedTypeVar, - (AnnotatedWildcardType) uncapturedTypeArg, - typeVariable, - capturedTypeArg); - newTypeArgs.set(i, capturedTypeArg); - } - - capturedType.setTypeArguments(newTypeArgs); - capturedType.addAnnotations(uncapturedType.getAnnotations()); - return capturedType; - } - - /** - * Copy the non-wildcard type args from a uncapturedType to its capturedType. Also, ensure that - * type variables in capturedType are the same object when they are refer to the same type - * variable. - * - *

          To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. - */ - private final NonWildcardTypeArgCopier nonWildcardTypeArgCopier = new NonWildcardTypeArgCopier(); - - /** - * Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. Also, - * ensure that type variables in {@code capturedType} are the same object when they refer to the - * same type variable. - * - *

          To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. - */ - private class NonWildcardTypeArgCopier extends AnnotatedTypeCopier { + /** + * Copy the non-wildcard type args from a uncapturedType to its capturedType. Also, ensure that + * type variables in capturedType are the same object when they are refer to the same type + * variable. + * + *

          To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. + */ + private final NonWildcardTypeArgCopier nonWildcardTypeArgCopier = + new NonWildcardTypeArgCopier(); /** * Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. Also, - * ensure that type variables {@code capturedType} are the same object when they are refer to - * the same type variable. - * - * @param uncapturedType a declared type that has not under gone capture conversion - * @param capturedType the captured version of {@code uncapturedType} before it has been - * annotated - */ - private void copy(AnnotatedDeclaredType uncapturedType, AnnotatedDeclaredType capturedType) { - // The name "originalToCopy" means a mapping from the original to the copy, not an - // original that needs to be copied. - IdentityHashMap originalToCopy = - new IdentityHashMap<>(); - originalToCopy.put(uncapturedType, capturedType); - int numTypeArgs = uncapturedType.getTypeArguments().size(); - - AnnotatedTypeMirror[] newTypeArgs = new AnnotatedTypeMirror[numTypeArgs]; - // Mapping from type var to it's AnnotatedTypeVariable. These are type variables - // that are type arguments of the uncaptured type. - Map typeVarToAnnotatedTypeVar = new HashMap<>(numTypeArgs); - // Copy the non-wildcard type args from uncapturedType to newTypeArgs. - // If the non-wildcard type arg is a type var, add it to typeVarToAnnotatedTypeVar. - for (int i = 0; i < numTypeArgs; i++) { - AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); - if (uncapturedArg.getKind() != TypeKind.WILDCARD) { - AnnotatedTypeMirror copyOfArg = visit(uncapturedArg, originalToCopy); - newTypeArgs[i] = copyOfArg; - if (copyOfArg.getKind() == TypeKind.TYPEVAR) { - typeVarToAnnotatedTypeVar.put( - ((AnnotatedTypeVariable) copyOfArg).getUnderlyingType(), copyOfArg); - } + * ensure that type variables in {@code capturedType} are the same object when they refer to the + * same type variable. + * + *

          To use, call {@link NonWildcardTypeArgCopier#copy} rather than a visit method. + */ + private class NonWildcardTypeArgCopier extends AnnotatedTypeCopier { + + /** + * Copy the non-wildcard type args from {@code uncapturedType} to {@code capturedType}. + * Also, ensure that type variables {@code capturedType} are the same object when they are + * refer to the same type variable. + * + * @param uncapturedType a declared type that has not under gone capture conversion + * @param capturedType the captured version of {@code uncapturedType} before it has been + * annotated + */ + private void copy( + AnnotatedDeclaredType uncapturedType, AnnotatedDeclaredType capturedType) { + // The name "originalToCopy" means a mapping from the original to the copy, not an + // original that needs to be copied. + IdentityHashMap originalToCopy = + new IdentityHashMap<>(); + originalToCopy.put(uncapturedType, capturedType); + int numTypeArgs = uncapturedType.getTypeArguments().size(); + + AnnotatedTypeMirror[] newTypeArgs = new AnnotatedTypeMirror[numTypeArgs]; + // Mapping from type var to it's AnnotatedTypeVariable. These are type variables + // that are type arguments of the uncaptured type. + Map typeVarToAnnotatedTypeVar = + new HashMap<>(numTypeArgs); + // Copy the non-wildcard type args from uncapturedType to newTypeArgs. + // If the non-wildcard type arg is a type var, add it to typeVarToAnnotatedTypeVar. + for (int i = 0; i < numTypeArgs; i++) { + AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); + if (uncapturedArg.getKind() != TypeKind.WILDCARD) { + AnnotatedTypeMirror copyOfArg = visit(uncapturedArg, originalToCopy); + newTypeArgs[i] = copyOfArg; + if (copyOfArg.getKind() == TypeKind.TYPEVAR) { + typeVarToAnnotatedTypeVar.put( + ((AnnotatedTypeVariable) copyOfArg).getUnderlyingType(), copyOfArg); + } + } + } + + // Substitute the type variables in each type argument of capturedType using + // typeVarToAnnotatedTypeVar. + // This makes type variables in capturedType the same object when they are the same type + // variable. + for (int i = 0; i < numTypeArgs; i++) { + AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); + AnnotatedTypeMirror capturedArg = capturedType.getTypeArguments().get(i); + // Note: This `if` statement can't be replaced with + // if (TypesUtils.isCapturedTypeVariable(capturedArg)) + // because if the bounds of the captured wildcard are equal, then instead of a + // captured wildcard, the type of the bound is used. + if (uncapturedArg.getKind() == TypeKind.WILDCARD) { + AnnotatedTypeMirror newCapArg = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + typeVarToAnnotatedTypeVar, capturedArg); + newTypeArgs[i] = newCapArg; + } + } + // Set capturedType type args to newTypeArgs. + capturedType.setTypeArguments(Arrays.asList(newTypeArgs)); + + // Visit the enclosing type. + if (uncapturedType.getEnclosingType() != null) { + capturedType.setEnclosingType( + (AnnotatedDeclaredType) + visit(uncapturedType.getEnclosingType(), originalToCopy)); + } } - } + } - // Substitute the type variables in each type argument of capturedType using - // typeVarToAnnotatedTypeVar. - // This makes type variables in capturedType the same object when they are the same type - // variable. - for (int i = 0; i < numTypeArgs; i++) { - AnnotatedTypeMirror uncapturedArg = uncapturedType.getTypeArguments().get(i); - AnnotatedTypeMirror capturedArg = capturedType.getTypeArguments().get(i); - // Note: This `if` statement can't be replaced with - // if (TypesUtils.isCapturedTypeVariable(capturedArg)) - // because if the bounds of the captured wildcard are equal, then instead of a - // captured wildcard, the type of the bound is used. - if (uncapturedArg.getKind() == TypeKind.WILDCARD) { - AnnotatedTypeMirror newCapArg = - typeVarSubstitutor.substituteWithoutCopyingTypeArguments( - typeVarToAnnotatedTypeVar, capturedArg); - newTypeArgs[i] = newCapArg; + /** + * Returns the list of type variables such that a type variable in the list only references type + * variables at a lower index than itself. + * + * @param collection a collection of type variables + * @return the type variables ordered so that each type variable only references earlier type + * variables + */ + public List order(Collection collection) { + List list = new ArrayList<>(collection); + List ordered = new ArrayList<>(); + while (!list.isEmpty()) { + AnnotatedTypeVariable free = doesNotContainOthers(list); + list.remove(free); + ordered.add(free); } - } - // Set capturedType type args to newTypeArgs. - capturedType.setTypeArguments(Arrays.asList(newTypeArgs)); + return ordered; + } - // Visit the enclosing type. - if (uncapturedType.getEnclosingType() != null) { - capturedType.setEnclosingType( - (AnnotatedDeclaredType) visit(uncapturedType.getEnclosingType(), originalToCopy)); - } + /** + * Returns the first TypeVariable in {@code collection} that does not lexically contain any + * other type in the collection. Or if all the TypeVariables contain another, then it returns + * the first TypeVariable in {@code collection}. + * + * @param collection a collection of type variables + * @return the first TypeVariable in {@code collection} that does not contain any other type in + * the collection, except possibly itself + */ + @SuppressWarnings("interning:not.interned") // must be the same object from collection + private AnnotatedTypeVariable doesNotContainOthers( + Collection collection) { + AnnotatedTypeVariable first = null; + for (AnnotatedTypeVariable candidate : collection) { + if (first == null) { + first = candidate; + } + boolean doesNotContain = true; + for (AnnotatedTypeVariable other : collection) { + if (candidate != other + && captureScanner.visit(candidate, other.getUnderlyingType())) { + doesNotContain = false; + break; + } + } + if (doesNotContain) { + return candidate; + } + } + return first; } - } - - /** - * Returns the list of type variables such that a type variable in the list only references type - * variables at a lower index than itself. - * - * @param collection a collection of type variables - * @return the type variables ordered so that each type variable only references earlier type - * variables - */ - public List order(Collection collection) { - List list = new ArrayList<>(collection); - List ordered = new ArrayList<>(); - while (!list.isEmpty()) { - AnnotatedTypeVariable free = doesNotContainOthers(list); - list.remove(free); - ordered.add(free); - } - return ordered; - } - - /** - * Returns the first TypeVariable in {@code collection} that does not lexically contain any other - * type in the collection. Or if all the TypeVariables contain another, then it returns the first - * TypeVariable in {@code collection}. - * - * @param collection a collection of type variables - * @return the first TypeVariable in {@code collection} that does not contain any other type in - * the collection, except possibly itself - */ - @SuppressWarnings("interning:not.interned") // must be the same object from collection - private AnnotatedTypeVariable doesNotContainOthers( - Collection collection) { - AnnotatedTypeVariable first = null; - for (AnnotatedTypeVariable candidate : collection) { - if (first == null) { - first = candidate; - } - boolean doesNotContain = true; - for (AnnotatedTypeVariable other : collection) { - if (candidate != other && captureScanner.visit(candidate, other.getUnderlyingType())) { - doesNotContain = false; - break; + + /** + * Scanner that returns true if the underlying type of any part of an {@link + * AnnotatedTypeMirror} is the passed captured type variable. + * + *

          The second argument to visit must be a captured type variable. + */ + @SuppressWarnings("interning:not.interned") // Captured type vars can be compared with ==. + private final SimpleAnnotatedTypeScanner captureScanner = + new SimpleAnnotatedTypeScanner<>( + (type, other) -> type.getUnderlyingType() == other, Boolean::logicalOr, false); + + /** + * Set the annotated bounds for fresh type variable {@code capturedTypeVar}, so that it is the + * capture of {@code wildcard}. Also, sets {@code capturedTypeVar} primary annotation if the + * annotation on the bounds is identical. + * + * @param typeVarToAnnotatedTypeArg mapping from a (type mirror) type variable to its (annotated + * type mirror) type argument + * @param capturedTypeVarToAnnotatedTypeVar mapping from a captured type variable to its {@link + * AnnotatedTypeMirror} + * @param wildcard wildcard which is converted to {@code capturedTypeVar} + * @param typeVariable type variable for which {@code wildcard} is a type argument + * @param capturedTypeVar the fresh type variable which is side-effected by this method + */ + private void annotateCapturedTypeVar( + Map typeVarToAnnotatedTypeArg, + Map capturedTypeVarToAnnotatedTypeVar, + AnnotatedWildcardType wildcard, + AnnotatedTypeVariable typeVariable, + AnnotatedTypeVariable capturedTypeVar) { + AnnotatedTypeMirror typeVarUpperBound = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + typeVarToAnnotatedTypeArg, typeVariable.getUpperBound()); + AnnotatedTypeMirror upperBound = + AnnotatedTypes.annotatedGLB(this, typeVarUpperBound, wildcard.getExtendsBound()); + if (upperBound.getKind() == TypeKind.INTERSECTION + && capturedTypeVar.getUpperBound().getKind() != TypeKind.INTERSECTION) { + // There is a bug in javac such that the upper bound of the captured type variable is + // not the greatest lower bound. So the + // captureTypeVar.getUnderlyingType().getUpperBound() may not + // be the same type as upperbound.getUnderlyingType(). See + // framework/tests/all-systems/Issue4890Interfaces.java, + // framework/tests/all-systems/Issue4890.java and + // framework/tests/all-systems/Issue4877.java. + // (I think this is https://bugs.openjdk.org/browse/JDK-8039222.) + for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) upperBound).getBounds()) { + if (types.isSameType( + bound.underlyingType, + capturedTypeVar.getUpperBound().getUnderlyingType())) { + upperBound = bound; + } + } } - } - if (doesNotContain) { - return candidate; - } + + capturedTypeVar.setUpperBound(upperBound); + + // typeVariable's lower bound is a NullType, so there's nothing to substitute. + AnnotatedTypeMirror lowerBound = + AnnotatedTypes.leastUpperBound( + this, typeVariable.getLowerBound(), wildcard.getSuperBound()); + capturedTypeVar.setLowerBound(lowerBound); + + // Add as a primary annotation any qualifiers that are the same on the upper and lower + // bound. + AnnotationMirrorSet p = + new AnnotationMirrorSet(capturedTypeVar.getUpperBound().getAnnotations()); + p.retainAll(capturedTypeVar.getLowerBound().getAnnotations()); + capturedTypeVar.replaceAnnotations(p); + + capturedTypeVarSubstitutor.substitute(capturedTypeVar, capturedTypeVarToAnnotatedTypeVar); } - return first; - } - - /** - * Scanner that returns true if the underlying type of any part of an {@link AnnotatedTypeMirror} - * is the passed captured type variable. - * - *

          The second argument to visit must be a captured type variable. - */ - @SuppressWarnings("interning:not.interned") // Captured type vars can be compared with ==. - private final SimpleAnnotatedTypeScanner captureScanner = - new SimpleAnnotatedTypeScanner<>( - (type, other) -> type.getUnderlyingType() == other, Boolean::logicalOr, false); - - /** - * Set the annotated bounds for fresh type variable {@code capturedTypeVar}, so that it is the - * capture of {@code wildcard}. Also, sets {@code capturedTypeVar} primary annotation if the - * annotation on the bounds is identical. - * - * @param typeVarToAnnotatedTypeArg mapping from a (type mirror) type variable to its (annotated - * type mirror) type argument - * @param capturedTypeVarToAnnotatedTypeVar mapping from a captured type variable to its {@link - * AnnotatedTypeMirror} - * @param wildcard wildcard which is converted to {@code capturedTypeVar} - * @param typeVariable type variable for which {@code wildcard} is a type argument - * @param capturedTypeVar the fresh type variable which is side-effected by this method - */ - private void annotateCapturedTypeVar( - Map typeVarToAnnotatedTypeArg, - Map capturedTypeVarToAnnotatedTypeVar, - AnnotatedWildcardType wildcard, - AnnotatedTypeVariable typeVariable, - AnnotatedTypeVariable capturedTypeVar) { - AnnotatedTypeMirror typeVarUpperBound = - typeVarSubstitutor.substituteWithoutCopyingTypeArguments( - typeVarToAnnotatedTypeArg, typeVariable.getUpperBound()); - AnnotatedTypeMirror upperBound = - AnnotatedTypes.annotatedGLB(this, typeVarUpperBound, wildcard.getExtendsBound()); - if (upperBound.getKind() == TypeKind.INTERSECTION - && capturedTypeVar.getUpperBound().getKind() != TypeKind.INTERSECTION) { - // There is a bug in javac such that the upper bound of the captured type variable is - // not the greatest lower bound. So the - // captureTypeVar.getUnderlyingType().getUpperBound() may not - // be the same type as upperbound.getUnderlyingType(). See - // framework/tests/all-systems/Issue4890Interfaces.java, - // framework/tests/all-systems/Issue4890.java and - // framework/tests/all-systems/Issue4877.java. - // (I think this is https://bugs.openjdk.org/browse/JDK-8039222.) - for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) upperBound).getBounds()) { - if (types.isSameType( - bound.underlyingType, capturedTypeVar.getUpperBound().getUnderlyingType())) { - upperBound = bound; + + /** + * Substitutes references to captured type variables. + * + *

          Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code + * substitute} to use. + */ + public final CapturedTypeVarSubstitutor capturedTypeVarSubstitutor = + new CapturedTypeVarSubstitutor(); + + /** + * Substitutes references to captured types in {@code type} using {@code + * capturedTypeVarToAnnotatedTypeVar}. + * + *

          Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code + * substitute} to use. + */ + public static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier { + + /** Creates a CapturedTypeVarSubstitutor. */ + public CapturedTypeVarSubstitutor() {} + + /** A mapping from a captured type variable to its AnnotatedTypeVariable. */ + private Map capturedTypeVarToAnnotatedTypeVar; + + /** + * Substitutes references to captured type variable in {@code type} using {@code + * capturedTypeVarToAnnotatedTypeVar}. + * + *

          Unlike {@link #typeVarSubstitutor}, this method does not copy the type. + * + * @param type the type whose captured type variables are substituted with those in {@code + * capturedTypeVarToAnnotatedTypeVar} + * @param capturedTypeVarToAnnotatedTypeVar mapping from a TypeVariable (which is a captured + * type variable) to an AnnotatedTypeVariable + */ + public void substitute( + AnnotatedTypeVariable type, + Map capturedTypeVarToAnnotatedTypeVar) { + this.capturedTypeVarToAnnotatedTypeVar = capturedTypeVarToAnnotatedTypeVar; + IdentityHashMap mapping = + new IdentityHashMap<>(); + visit(type.getLowerBound(), mapping); + visit(type.getUpperBound(), mapping); + this.capturedTypeVarToAnnotatedTypeVar = null; } - } + + @Override + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + AnnotatedTypeMirror cap = + capturedTypeVarToAnnotatedTypeVar.get(original.getUnderlyingType()); + if (cap != null) { + return cap; + } + return super.visitTypeVariable(original, originalToCopy); + } + + @Override + protected T makeOrReturnCopy( + T original, + IdentityHashMap originalToCopy) { + AnnotatedTypeMirror copy = originalToCopy.get(original); + if (copy != null) { + @SuppressWarnings( + "unchecked" // the key-value pairs in originalToCopy are always the same + // kind of AnnotatedTypeMirror. + ) + T copyCasted = (T) copy; + return copyCasted; + } + + if (original.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeMirror captureType = + capturedTypeVarToAnnotatedTypeVar.get( + ((AnnotatedTypeVariable) original).getUnderlyingType()); + if (captureType != null) { + originalToCopy.put(original, captureType); + @SuppressWarnings( + "unchecked" // the key-value pairs in originalToCopy are always the same + // kind of AnnotatedTypeMirror. + ) + T captureTypeCasted = (T) captureType; + return captureTypeCasted; + } + } + originalToCopy.put(original, original); + return original; + } + } + + /** + * Check that a wildcard is an extends wildcard. + * + * @param awt the wildcard type + * @return true if awt is an extends wildcard + */ + private boolean isExtendsWildcard(AnnotatedWildcardType awt) { + return awt.getUnderlyingType().getSuperBound() == null; + } + + /** + * Returns the utility class for working with {@link Element}s. + * + * @return the utility class for working with {@link Element}s + */ + public final Elements getElementUtils() { + return this.elements; + } + + /** Accessor for the tree utilities. */ + public Trees getTreeUtils() { + return this.trees; + } + + /** + * Accessor for the processing environment. + * + * @return the processing environment + */ + public ProcessingEnvironment getProcessingEnv() { + return this.processingEnv; } - capturedTypeVar.setUpperBound(upperBound); + /** Matches addition of a constant. */ + private static final Pattern plusConstant = Pattern.compile(" *\\+ *(-?[0-9]+)$"); + + /** Matches subtraction of a constant. */ + private static final Pattern minusConstant = Pattern.compile(" *- *(-?[0-9]+)$"); - // typeVariable's lower bound is a NullType, so there's nothing to substitute. - AnnotatedTypeMirror lowerBound = - AnnotatedTypes.leastUpperBound( - this, typeVariable.getLowerBound(), wildcard.getSuperBound()); - capturedTypeVar.setLowerBound(lowerBound); + /** Matches a string whose only parens are at the beginning and end of the string. */ + private static final Pattern surroundingParensPattern = Pattern.compile("^\\([^()]\\)"); - // Add as a primary annotation any qualifiers that are the same on the upper and lower - // bound. - AnnotationMirrorSet p = - new AnnotationMirrorSet(capturedTypeVar.getUpperBound().getAnnotations()); - p.retainAll(capturedTypeVar.getLowerBound().getAnnotations()); - capturedTypeVar.replaceAnnotations(p); + /** + * Given an expression, split it into a subexpression and a constant offset. For example: + * + *

          {@code
          +     * "a" => <"a", "0">
          +     * "a + 5" => <"a", "5">
          +     * "a + -5" => <"a", "-5">
          +     * "a - 5" => <"a", "-5">
          +     * }
          + * + * There are methods that can only take as input an expression that represents a JavaExpression. + * The purpose of this is to pre-process expressions to make those methods more likely to + * succeed. + * + * @param expression an expression to remove a constant offset from + * @return a sub-expression and a constant offset. The offset is "0" if this routine is unable + * to splite the given expression + */ + // TODO: generalize. There is no reason this couldn't handle arbitrary addition and subtraction + // expressions, given the Index Checker's support for OffsetEquation. That might even make its + // implementation simpler. + public static IPair getExpressionAndOffset(String expression) { + String expr = expression; + String offset = "0"; + + // Is this normalization necessary? + // Remove surrounding whitespace. + expr = expr.trim(); + // Remove surrounding parentheses. + if (surroundingParensPattern.matcher(expr).matches()) { + expr = expr.substring(1, expr.length() - 2).trim(); + } - capturedTypeVarSubstitutor.substitute(capturedTypeVar, capturedTypeVarToAnnotatedTypeVar); - } + Matcher mPlus = plusConstant.matcher(expr); + Matcher mMinus = minusConstant.matcher(expr); + if (mPlus.find()) { + expr = expr.substring(0, mPlus.start()); + offset = mPlus.group(1); + } else if (mMinus.find()) { + expr = expr.substring(0, mMinus.start()); + offset = negateConstant(mMinus.group(1)); + } - /** - * Substitutes references to captured type variables. - * - *

          Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code - * substitute} to use. - */ - public final CapturedTypeVarSubstitutor capturedTypeVarSubstitutor = - new CapturedTypeVarSubstitutor(); + if (offset.equals("-0")) { + offset = "0"; + } - /** - * Substitutes references to captured types in {@code type} using {@code - * capturedTypeVarToAnnotatedTypeVar}. - * - *

          Unlike {@link #typeVarSubstitutor}, this class does not copy the type. Call {@code - * substitute} to use. - */ - public static class CapturedTypeVarSubstitutor extends AnnotatedTypeCopier { + expr = expr.intern(); + offset = offset.intern(); - /** Creates a CapturedTypeVarSubstitutor. */ - public CapturedTypeVarSubstitutor() {} + return IPair.of(expr, offset); + } - /** A mapping from a captured type variable to its AnnotatedTypeVariable. */ - private Map capturedTypeVarToAnnotatedTypeVar; + /** + * Given an expression string, returns its negation. + * + * @param constantExpression a string representing an integer constant + * @return the negation of constantExpression + */ + // Also see Subsequence.negateString which is similar but more sophisticated. + public static String negateConstant(String constantExpression) { + if (constantExpression.startsWith("-")) { + return constantExpression.substring(1); + } else { + if (constantExpression.startsWith("+")) { + constantExpression = constantExpression.substring(1); + } + return "-" + constantExpression; + } + } /** - * Substitutes references to captured type variable in {@code type} using {@code - * capturedTypeVarToAnnotatedTypeVar}. + * Returns {@code null} or an annotated type mirror that type argument inference should assume + * {@code expressionTree} is assigned to. + * + *

          If {@code null} is returned, inference proceeds normally. * - *

          Unlike {@link #typeVarSubstitutor}, this method does not copy the type. + *

          If a type is returned, then inference assumes that {@code expressionTree} was asigned to + * it. This biases the inference algorithm toward the annotations in the returned type. In + * particular, if the annotations on type variables in invariant positions are a super type of + * the annotations inferred, the super type annotations are chosen. * - * @param type the type whose captured type variables are substituted with those in {@code - * capturedTypeVarToAnnotatedTypeVar} - * @param capturedTypeVarToAnnotatedTypeVar mapping from a TypeVariable (which is a captured - * type variable) to an AnnotatedTypeVariable + *

          This implementation returns null, but subclasses may override this method to return a + * type. + * + * @param expressionTree an expression which has no assignment context and for which type + * arguments need to be inferred + * @return {@code null} or an annotated type mirror that inferrence should pretend {@code + * expressionTree} is assigned to */ - public void substitute( - AnnotatedTypeVariable type, - Map capturedTypeVarToAnnotatedTypeVar) { - this.capturedTypeVarToAnnotatedTypeVar = capturedTypeVarToAnnotatedTypeVar; - IdentityHashMap mapping = new IdentityHashMap<>(); - visit(type.getLowerBound(), mapping); - visit(type.getUpperBound(), mapping); - this.capturedTypeVarToAnnotatedTypeVar = null; + public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { + return null; } - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - AnnotatedTypeMirror cap = capturedTypeVarToAnnotatedTypeVar.get(original.getUnderlyingType()); - if (cap != null) { - return cap; - } - return super.visitTypeVariable(original, originalToCopy); + /** + * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. + * + *

          This method is faster than {@link AnnotationUtils#areSameByClass(AnnotationMirror, Class)} + * because it caches the name of the class rather than computing it each time. + * + * @param am the AnnotationMirror whose class to compare + * @param annoClass the class to compare + * @return true if annoclass is the class of am + */ + public boolean areSameByClass(AnnotationMirror am, Class annoClass) { + if (!shouldCache) { + return AnnotationUtils.areSameByName(am, annoClass.getCanonicalName()); + } + @SuppressWarnings("nullness") // assume getCanonicalName returns non-null + String canonicalName = + annotationClassNames.computeIfAbsent(annoClass, Class::getCanonicalName); + return AnnotationUtils.areSameByName(am, canonicalName); } - @Override - protected T makeOrReturnCopy( - T original, IdentityHashMap originalToCopy) { - AnnotatedTypeMirror copy = originalToCopy.get(original); - if (copy != null) { - @SuppressWarnings("unchecked" // the key-value pairs in originalToCopy are always the same - // kind of AnnotatedTypeMirror. - ) - T copyCasted = (T) copy; - return copyCasted; - } + /** + * Checks that the collection contains the annotation. Using {@code Collection.contains} does + * not always work, because it does not use {@code areSame()} for comparison. + * + *

          This method is faster than {@link AnnotationUtils#containsSameByClass(Collection, Class)} + * because is caches the name of the class rather than computing it each time. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation class to search for in c + * @return true iff c contains anno, according to areSameByClass + */ + public boolean containsSameByClass( + Collection c, Class anno) { + return getAnnotationByClass(c, anno) != null; + } - if (original.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeMirror captureType = - capturedTypeVarToAnnotatedTypeVar.get( - ((AnnotatedTypeVariable) original).getUnderlyingType()); - if (captureType != null) { - originalToCopy.put(original, captureType); - @SuppressWarnings("unchecked" // the key-value pairs in originalToCopy are always the same - // kind of AnnotatedTypeMirror. - ) - T captureTypeCasted = (T) captureType; - return captureTypeCasted; + /** + * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. + * + *

          This method is faster than {@link AnnotationUtils#getAnnotationByClass(Collection, Class)} + * because is caches the name of the class rather than computing it each time. + * + * @param c a collection of AnnotationMirrors + * @param anno the class to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according + * to areSameByClass; otherwise, {@code null} + */ + public @Nullable AnnotationMirror getAnnotationByClass( + Collection c, Class anno) { + for (AnnotationMirror an : c) { + if (areSameByClass(an, anno)) { + return an; + } } + return null; + } + + /* NO-AFU + * Changes the type of {@code rhsATM} when being assigned to a field, for use by whole-program + * inference. The default implementation does nothing. + * + * @param lhsTree the tree for the field whose type will be changed + * @param element the element for the field whose type will be changed + * @param fieldName the name of the field whose type will be changed + * @param rhsATM the type of the expression being assigned to the field, which is side-effected + * by this method + */ + /* NO-AFU + public void wpiAdjustForUpdateField( + Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {} + */ + + /* NO-AFU + * Changes the type of {@code rhsATM} when being assigned to anything other than a field, for + * use by whole-program inference. The default implementation does nothing. + * + * @param rhsATM the type of the rhs of the pseudo-assignment, which is side-effected by this + * method + */ + /* NO-AFU + public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {} + */ + + /* NO-AFU + * Returns whether whole-program inference should infer types for receiver expressions. For some + * type systems, such as nullness, it doesn't make sense for WPI to do inference on receivers. + * + * @return true if WPI should infer types for method receiver parameters, false otherwise + */ + /* NO-AFU + public boolean wpiShouldInferTypesForReceivers() { + return true; + } + */ + + /* NO-AFU + * Side-effects the method or constructor annotations to make any desired changes before writing + * to an annotation file. + * + * @param methodAnnos the method or constructor annotations to modify + */ + /* NO-AFU + public void wpiPrepareMethodForWriting(AMethod methodAnnos) { + // This implementation does nothing. + } + */ + + /* NO-AFU + * Side-effects the method or constructor annotations to make any desired changes before writing + * to an ajava file. + * + *

          Overriding implementations should call {@code super.wpiPrepareMethodForWriting()}. + * + * @param methodAnnos the method or constructor annotations to modify + * @param inSupertypes the method or constructor annotations for all overridden methods; not + * side-effected + * @param inSubtypes the method or constructor annotations for all overriding methods; not + * side-effected + */ + /* NO-AFU + public void wpiPrepareMethodForWriting( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, + Collection inSupertypes, + Collection inSubtypes) { + Map> precondMap = + methodAnnos.getPreconditions(); + Map> postcondMap = + methodAnnos.getPostconditions(); + for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSupertype : + inSupertypes) { + makeConditionConsistentWithOtherMethod(precondMap, inSupertype, true, true); + makeConditionConsistentWithOtherMethod(postcondMap, inSupertype, false, true); } - originalToCopy.put(original, original); - return original; - } - } - - /** - * Check that a wildcard is an extends wildcard. - * - * @param awt the wildcard type - * @return true if awt is an extends wildcard - */ - private boolean isExtendsWildcard(AnnotatedWildcardType awt) { - return awt.getUnderlyingType().getSuperBound() == null; - } - - /** - * Returns the utility class for working with {@link Element}s. - * - * @return the utility class for working with {@link Element}s - */ - public final Elements getElementUtils() { - return this.elements; - } - - /** Accessor for the tree utilities. */ - public Trees getTreeUtils() { - return this.trees; - } - - /** - * Accessor for the processing environment. - * - * @return the processing environment - */ - public ProcessingEnvironment getProcessingEnv() { - return this.processingEnv; - } - - /** Matches addition of a constant. */ - private static final Pattern plusConstant = Pattern.compile(" *\\+ *(-?[0-9]+)$"); - - /** Matches subtraction of a constant. */ - private static final Pattern minusConstant = Pattern.compile(" *- *(-?[0-9]+)$"); - - /** Matches a string whose only parens are at the beginning and end of the string. */ - private static final Pattern surroundingParensPattern = Pattern.compile("^\\([^()]\\)"); - - /** - * Given an expression, split it into a subexpression and a constant offset. For example: - * - *

          {@code
          -   * "a" => <"a", "0">
          -   * "a + 5" => <"a", "5">
          -   * "a + -5" => <"a", "-5">
          -   * "a - 5" => <"a", "-5">
          -   * }
          - * - * There are methods that can only take as input an expression that represents a JavaExpression. - * The purpose of this is to pre-process expressions to make those methods more likely to succeed. - * - * @param expression an expression to remove a constant offset from - * @return a sub-expression and a constant offset. The offset is "0" if this routine is unable to - * splite the given expression - */ - // TODO: generalize. There is no reason this couldn't handle arbitrary addition and subtraction - // expressions, given the Index Checker's support for OffsetEquation. That might even make its - // implementation simpler. - public static IPair getExpressionAndOffset(String expression) { - String expr = expression; - String offset = "0"; - - // Is this normalization necessary? - // Remove surrounding whitespace. - expr = expr.trim(); - // Remove surrounding parentheses. - if (surroundingParensPattern.matcher(expr).matches()) { - expr = expr.substring(1, expr.length() - 2).trim(); - } - - Matcher mPlus = plusConstant.matcher(expr); - Matcher mMinus = minusConstant.matcher(expr); - if (mPlus.find()) { - expr = expr.substring(0, mPlus.start()); - offset = mPlus.group(1); - } else if (mMinus.find()) { - expr = expr.substring(0, mMinus.start()); - offset = negateConstant(mMinus.group(1)); - } - - if (offset.equals("-0")) { - offset = "0"; - } - - expr = expr.intern(); - offset = offset.intern(); - - return IPair.of(expr, offset); - } - - /** - * Given an expression string, returns its negation. - * - * @param constantExpression a string representing an integer constant - * @return the negation of constantExpression - */ - // Also see Subsequence.negateString which is similar but more sophisticated. - public static String negateConstant(String constantExpression) { - if (constantExpression.startsWith("-")) { - return constantExpression.substring(1); - } else { - if (constantExpression.startsWith("+")) { - constantExpression = constantExpression.substring(1); - } - return "-" + constantExpression; - } - } - - /** - * Returns {@code null} or an annotated type mirror that type argument inference should assume - * {@code expressionTree} is assigned to. - * - *

          If {@code null} is returned, inference proceeds normally. - * - *

          If a type is returned, then inference assumes that {@code expressionTree} was asigned to it. - * This biases the inference algorithm toward the annotations in the returned type. In particular, - * if the annotations on type variables in invariant positions are a super type of the annotations - * inferred, the super type annotations are chosen. - * - *

          This implementation returns null, but subclasses may override this method to return a type. - * - * @param expressionTree an expression which has no assignment context and for which type - * arguments need to be inferred - * @return {@code null} or an annotated type mirror that inferrence should pretend {@code - * expressionTree} is assigned to - */ - public @Nullable AnnotatedTypeMirror getDummyAssignedTo(ExpressionTree expressionTree) { - return null; - } - - /** - * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. - * - *

          This method is faster than {@link AnnotationUtils#areSameByClass(AnnotationMirror, Class)} - * because it caches the name of the class rather than computing it each time. - * - * @param am the AnnotationMirror whose class to compare - * @param annoClass the class to compare - * @return true if annoclass is the class of am - */ - public boolean areSameByClass(AnnotationMirror am, Class annoClass) { - if (!shouldCache) { - return AnnotationUtils.areSameByName(am, annoClass.getCanonicalName()); - } - @SuppressWarnings("nullness") // assume getCanonicalName returns non-null - String canonicalName = annotationClassNames.computeIfAbsent(annoClass, Class::getCanonicalName); - return AnnotationUtils.areSameByName(am, canonicalName); - } - - /** - * Checks that the collection contains the annotation. Using {@code Collection.contains} does not - * always work, because it does not use {@code areSame()} for comparison. - * - *

          This method is faster than {@link AnnotationUtils#containsSameByClass(Collection, Class)} - * because is caches the name of the class rather than computing it each time. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation class to search for in c - * @return true iff c contains anno, according to areSameByClass - */ - public boolean containsSameByClass( - Collection c, Class anno) { - return getAnnotationByClass(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. - * - *

          This method is faster than {@link AnnotationUtils#getAnnotationByClass(Collection, Class)} - * because is caches the name of the class rather than computing it each time. - * - * @param c a collection of AnnotationMirrors - * @param anno the class to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to - * areSameByClass; otherwise, {@code null} - */ - public @Nullable AnnotationMirror getAnnotationByClass( - Collection c, Class anno) { - for (AnnotationMirror an : c) { - if (areSameByClass(an, anno)) { - return an; + for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSubtype : inSubtypes) { + makeConditionConsistentWithOtherMethod(precondMap, inSubtype, true, false); + makeConditionConsistentWithOtherMethod(postcondMap, inSubtype, false, false); } } - return null; - } - - /* NO-AFU - * Changes the type of {@code rhsATM} when being assigned to a field, for use by whole-program - * inference. The default implementation does nothing. - * - * @param lhsTree the tree for the field whose type will be changed - * @param element the element for the field whose type will be changed - * @param fieldName the name of the field whose type will be changed - * @param rhsATM the type of the expression being assigned to the field, which is side-effected - * by this method - */ - /* NO-AFU - public void wpiAdjustForUpdateField( - Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {} - */ - - /* NO-AFU - * Changes the type of {@code rhsATM} when being assigned to anything other than a field, for - * use by whole-program inference. The default implementation does nothing. - * - * @param rhsATM the type of the rhs of the pseudo-assignment, which is side-effected by this - * method - */ - /* NO-AFU - public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {} - */ - - /* NO-AFU - * Returns whether whole-program inference should infer types for receiver expressions. For some - * type systems, such as nullness, it doesn't make sense for WPI to do inference on receivers. - * - * @return true if WPI should infer types for method receiver parameters, false otherwise - */ - /* NO-AFU - public boolean wpiShouldInferTypesForReceivers() { - return true; - } - */ - - /* NO-AFU - * Side-effects the method or constructor annotations to make any desired changes before writing - * to an annotation file. - * - * @param methodAnnos the method or constructor annotations to modify - */ - /* NO-AFU - public void wpiPrepareMethodForWriting(AMethod methodAnnos) { - // This implementation does nothing. - } - */ - - /* NO-AFU - * Side-effects the method or constructor annotations to make any desired changes before writing - * to an ajava file. - * - *

          Overriding implementations should call {@code super.wpiPrepareMethodForWriting()}. - * - * @param methodAnnos the method or constructor annotations to modify - * @param inSupertypes the method or constructor annotations for all overridden methods; not - * side-effected - * @param inSubtypes the method or constructor annotations for all overriding methods; not - * side-effected - */ - /* NO-AFU - public void wpiPrepareMethodForWriting( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, - Collection inSupertypes, - Collection inSubtypes) { - Map> precondMap = - methodAnnos.getPreconditions(); - Map> postcondMap = - methodAnnos.getPostconditions(); - for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSupertype : - inSupertypes) { - makeConditionConsistentWithOtherMethod(precondMap, inSupertype, true, true); - makeConditionConsistentWithOtherMethod(postcondMap, inSupertype, false, true); - } - for (WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos inSubtype : inSubtypes) { - makeConditionConsistentWithOtherMethod(precondMap, inSubtype, true, false); - makeConditionConsistentWithOtherMethod(postcondMap, inSubtype, false, false); - } - } - */ - - /* NO-AFU - * Performs side effects to make {@code conditionMap} obey behavioral subtyping constraints with - * {@code otherDeclAnnos}, that is, postconditions must be at least as strong as the postcondition - * on the superclass, and preconditions must be at most as strong as the condition on the - * superclass. - * - *

          Overriding implementations should call {@code - * super.makeConditionConsistentWithOtherMethod()}. - * - * @param conditionMap pre- or post-condition annotations on a method M; may be side-effected - * @param otherDeclAnnos annotations on a method that M overrides or that overrides M; that is, on - * a method in the same "method family" as M; may be side-effected - * @param isPrecondition true if the annotations are pre-condition annotations, false if they are - * post-condition annotations - * @param otherIsSupertype true if {@code otherDeclAnnos} are on a supertype; false if they are on - * a subtype - */ - /* NO-AFU - protected void makeConditionConsistentWithOtherMethod( - Map> conditionMap, - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos otherDeclAnnos, - boolean isPrecondition, - boolean otherIsSupertype) { - for (Map.Entry> entry : - conditionMap.entrySet()) { - String expr = entry.getKey(); - IPair pair = entry.getValue(); - AnnotatedTypeMirror inferredType = pair.first; - AnnotatedTypeMirror declaredType = pair.second; - if (otherIsSupertype ? isPrecondition : !isPrecondition) { - // other is a supertype & compare preconditions, or - // other is a subtype & compare postconditions. - Map> otherConditionMap = - isPrecondition ? otherDeclAnnos.getPreconditions() : otherDeclAnnos.getPostconditions(); - // TODO: Complete support for "every expression" conditions, then remove the - // `!otherConditionMap.containsKey(expr)` test. - // If a condition map contains the key "every expression", that means that inference - // completed without inferring any conditions of that type. For example, if no - // @EnsuresCalledMethods was inferred for any expression, the map would contain the - // key "every expression", which is not a legal Java expression. - if (otherConditionMap.containsKey("every expression") - || !otherConditionMap.containsKey(expr)) { - // `otherInferredType` was inferred to be the top type. - // Put the top type on `inferredType`. - inferredType.replaceAnnotations(declaredType.getAnnotations()); - } else { - AnnotatedTypeMirror otherInferredType = - isPrecondition - ? otherDeclAnnos.getPreconditionsForExpression(expr, declaredType, this) - : otherDeclAnnos.getPostconditionsForExpression(expr, declaredType, this); - this.getWholeProgramInference().updateAtmWithLub(inferredType, otherInferredType); + */ + + /* NO-AFU + * Performs side effects to make {@code conditionMap} obey behavioral subtyping constraints with + * {@code otherDeclAnnos}, that is, postconditions must be at least as strong as the postcondition + * on the superclass, and preconditions must be at most as strong as the condition on the + * superclass. + * + *

          Overriding implementations should call {@code + * super.makeConditionConsistentWithOtherMethod()}. + * + * @param conditionMap pre- or post-condition annotations on a method M; may be side-effected + * @param otherDeclAnnos annotations on a method that M overrides or that overrides M; that is, on + * a method in the same "method family" as M; may be side-effected + * @param isPrecondition true if the annotations are pre-condition annotations, false if they are + * post-condition annotations + * @param otherIsSupertype true if {@code otherDeclAnnos} are on a supertype; false if they are on + * a subtype + */ + /* NO-AFU + protected void makeConditionConsistentWithOtherMethod( + Map> conditionMap, + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos otherDeclAnnos, + boolean isPrecondition, + boolean otherIsSupertype) { + for (Map.Entry> entry : + conditionMap.entrySet()) { + String expr = entry.getKey(); + IPair pair = entry.getValue(); + AnnotatedTypeMirror inferredType = pair.first; + AnnotatedTypeMirror declaredType = pair.second; + if (otherIsSupertype ? isPrecondition : !isPrecondition) { + // other is a supertype & compare preconditions, or + // other is a subtype & compare postconditions. + Map> otherConditionMap = + isPrecondition ? otherDeclAnnos.getPreconditions() : otherDeclAnnos.getPostconditions(); + // TODO: Complete support for "every expression" conditions, then remove the + // `!otherConditionMap.containsKey(expr)` test. + // If a condition map contains the key "every expression", that means that inference + // completed without inferring any conditions of that type. For example, if no + // @EnsuresCalledMethods was inferred for any expression, the map would contain the + // key "every expression", which is not a legal Java expression. + if (otherConditionMap.containsKey("every expression") + || !otherConditionMap.containsKey(expr)) { + // `otherInferredType` was inferred to be the top type. + // Put the top type on `inferredType`. + inferredType.replaceAnnotations(declaredType.getAnnotations()); + } else { + AnnotatedTypeMirror otherInferredType = + isPrecondition + ? otherDeclAnnos.getPreconditionsForExpression(expr, declaredType, this) + : otherDeclAnnos.getPostconditionsForExpression(expr, declaredType, this); + this.getWholeProgramInference().updateAtmWithLub(inferredType, otherInferredType); + } } } } - } - */ - - /** - * Does {@code annotatedForAnno}, which is an {@link - * org.checkerframework.framework.qual.AnnotatedFor} annotation, apply to this checker? - * - * @param annotatedForAnno an {@link AnnotatedFor} annotation - * @return whether {@code annotatedForAnno} applies to this checker - */ - public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForAnno) { - List annotatedForCheckers = - AnnotationUtils.getElementValueArray( - annotatedForAnno, annotatedForValueElement, String.class); - List<@FullyQualifiedName String> upstreamCheckerNames = checker.getUpstreamCheckerNames(); - for (String annoForChecker : annotatedForCheckers) { - if (upstreamCheckerNames.contains(annoForChecker) - || CheckerMain.matchesFullyQualifiedProcessor( - annoForChecker, upstreamCheckerNames, true)) { - return true; - } - } - return false; - } - - /** - * Get the {@code expression} field/element of the given contract annotation. - * - * @param contractAnno a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link - * EnsuresQualifier} - * @return the {@code expression} field/element of the given annotation - */ - public List getContractExpressions(AnnotationMirror contractAnno) { - DeclaredType annoType = contractAnno.getAnnotationType(); - if (types.isSameType(annoType, requiresQualifierTM)) { - return AnnotationUtils.getElementValueArray( - contractAnno, requiresQualifierExpressionElement, String.class); - } else if (types.isSameType(annoType, ensuresQualifierTM)) { - return AnnotationUtils.getElementValueArray( - contractAnno, ensuresQualifierExpressionElement, String.class); - } else if (types.isSameType(annoType, ensuresQualifierIfTM)) { - return AnnotationUtils.getElementValueArray( - contractAnno, ensuresQualifierIfExpressionElement, String.class); - } else { - throw new BugInCF("Not a contract annotation: " + contractAnno); - } - } - - /** - * Get the {@code value} field/element of the given contract list annotation. - * - * @param contractListAnno a {@link org.checkerframework.framework.qual.RequiresQualifier.List - * RequiresQualifier.List}, {@link org.checkerframework.framework.qual.EnsuresQualifier.List - * EnsuresQualifier.List}, or {@link - * org.checkerframework.framework.qual.EnsuresQualifierIf.List EnsuresQualifierIf.List} - * @return the {@code value} field/element of the given annotation - */ - public List getContractListValues(AnnotationMirror contractListAnno) { - DeclaredType annoType = contractListAnno.getAnnotationType(); - if (types.isSameType(annoType, requiresQualifierListTM)) { - return AnnotationUtils.getElementValueArray( - contractListAnno, requiresQualifierListValueElement, AnnotationMirror.class); - } else if (types.isSameType(annoType, ensuresQualifierListTM)) { - return AnnotationUtils.getElementValueArray( - contractListAnno, ensuresQualifierListValueElement, AnnotationMirror.class); - } else if (types.isSameType(annoType, ensuresQualifierIfListTM)) { - return AnnotationUtils.getElementValueArray( - contractListAnno, ensuresQualifierIfListValueElement, AnnotationMirror.class); - } else { - throw new BugInCF("Not a contract list annotation: " + contractListAnno); - } - } - - /** - * Returns true if the type is immutable. Subclasses can override this method to add types that - * are mutable, but the annotated type of an object is immutable. - * - * @param type type to test - * @return true if the type is immutable - */ - public boolean isImmutable(TypeMirror type) { - return TypesUtils.isImmutableTypeInJdk(type); - } - - @Override - public boolean isSideEffectFree(ExecutableElement methodElement) { - if (assumeSideEffectFree || (assumePureGetters && ElementUtils.isGetter(methodElement))) { - return true; + */ + + /** + * Does {@code annotatedForAnno}, which is an {@link + * org.checkerframework.framework.qual.AnnotatedFor} annotation, apply to this checker? + * + * @param annotatedForAnno an {@link AnnotatedFor} annotation + * @return whether {@code annotatedForAnno} applies to this checker + */ + public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForAnno) { + List annotatedForCheckers = + AnnotationUtils.getElementValueArray( + annotatedForAnno, annotatedForValueElement, String.class); + List<@FullyQualifiedName String> upstreamCheckerNames = checker.getUpstreamCheckerNames(); + for (String annoForChecker : annotatedForCheckers) { + if (upstreamCheckerNames.contains(annoForChecker) + || CheckerMain.matchesFullyQualifiedProcessor( + annoForChecker, upstreamCheckerNames, true)) { + return true; + } + } + return false; } - if (ElementUtils.isRecordAccessor(methodElement) - && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { - return true; + + /** + * Get the {@code expression} field/element of the given contract annotation. + * + * @param contractAnno a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifier} + * @return the {@code expression} field/element of the given annotation + */ + public List getContractExpressions(AnnotationMirror contractAnno) { + DeclaredType annoType = contractAnno.getAnnotationType(); + if (types.isSameType(annoType, requiresQualifierTM)) { + return AnnotationUtils.getElementValueArray( + contractAnno, requiresQualifierExpressionElement, String.class); + } else if (types.isSameType(annoType, ensuresQualifierTM)) { + return AnnotationUtils.getElementValueArray( + contractAnno, ensuresQualifierExpressionElement, String.class); + } else if (types.isSameType(annoType, ensuresQualifierIfTM)) { + return AnnotationUtils.getElementValueArray( + contractAnno, ensuresQualifierIfExpressionElement, String.class); + } else { + throw new BugInCF("Not a contract annotation: " + contractAnno); + } } - for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { - if (areSameByClass(anno, org.checkerframework.dataflow.qual.SideEffectFree.class) - || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) - || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { - return true; - } + + /** + * Get the {@code value} field/element of the given contract list annotation. + * + * @param contractListAnno a {@link org.checkerframework.framework.qual.RequiresQualifier.List + * RequiresQualifier.List}, {@link org.checkerframework.framework.qual.EnsuresQualifier.List + * EnsuresQualifier.List}, or {@link + * org.checkerframework.framework.qual.EnsuresQualifierIf.List EnsuresQualifierIf.List} + * @return the {@code value} field/element of the given annotation + */ + public List getContractListValues(AnnotationMirror contractListAnno) { + DeclaredType annoType = contractListAnno.getAnnotationType(); + if (types.isSameType(annoType, requiresQualifierListTM)) { + return AnnotationUtils.getElementValueArray( + contractListAnno, requiresQualifierListValueElement, AnnotationMirror.class); + } else if (types.isSameType(annoType, ensuresQualifierListTM)) { + return AnnotationUtils.getElementValueArray( + contractListAnno, ensuresQualifierListValueElement, AnnotationMirror.class); + } else if (types.isSameType(annoType, ensuresQualifierIfListTM)) { + return AnnotationUtils.getElementValueArray( + contractListAnno, ensuresQualifierIfListValueElement, AnnotationMirror.class); + } else { + throw new BugInCF("Not a contract list annotation: " + contractListAnno); + } } - return false; - } - @Override - public boolean isDeterministic(ExecutableElement methodElement) { - if (assumeDeterministic || (assumePureGetters && ElementUtils.isGetter(methodElement))) { - return true; + /** + * Returns true if the type is immutable. Subclasses can override this method to add types that + * are mutable, but the annotated type of an object is immutable. + * + * @param type type to test + * @return true if the type is immutable + */ + public boolean isImmutable(TypeMirror type) { + return TypesUtils.isImmutableTypeInJdk(type); } - if (ElementUtils.isRecordAccessor(methodElement) - && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { - return true; + + @Override + public boolean isSideEffectFree(ExecutableElement methodElement) { + if (assumeSideEffectFree || (assumePureGetters && ElementUtils.isGetter(methodElement))) { + return true; + } + if (ElementUtils.isRecordAccessor(methodElement) + && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { + return true; + } + for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { + if (areSameByClass(anno, org.checkerframework.dataflow.qual.SideEffectFree.class) + || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) + || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { + return true; + } + } + return false; } - for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { - if (areSameByClass(anno, org.checkerframework.dataflow.qual.Deterministic.class) - || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) - || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { - return true; - } + + @Override + public boolean isDeterministic(ExecutableElement methodElement) { + if (assumeDeterministic || (assumePureGetters && ElementUtils.isGetter(methodElement))) { + return true; + } + if (ElementUtils.isRecordAccessor(methodElement) + && ElementUtils.isAutoGeneratedRecordMember(methodElement)) { + return true; + } + for (AnnotationMirror anno : getDeclAnnotations(methodElement)) { + if (areSameByClass(anno, org.checkerframework.dataflow.qual.Deterministic.class) + || areSameByClass(anno, org.checkerframework.dataflow.qual.Pure.class) + || areSameByClass(anno, org.jmlspecs.annotation.Pure.class)) { + return true; + } + } + return false; } - return false; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java index 74bc1879711..0475b56fe86 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFormatter.java @@ -8,25 +8,25 @@ * @see org.checkerframework.framework.util.AnnotationFormatter */ public interface AnnotatedTypeFormatter { - /** - * Formats type into a String. Uses an implementation specific default for printing "invisible - * annotations" - * - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @param type the type to be converted - * @return a string representation of type - */ - @SideEffectFree - public String format(AnnotatedTypeMirror type); + /** + * Formats type into a String. Uses an implementation specific default for printing "invisible + * annotations" + * + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @param type the type to be converted + * @return a string representation of type + */ + @SideEffectFree + public String format(AnnotatedTypeMirror type); - /** - * Formats type into a String. - * - * @param type the type to be converted - * @param printVerbose whether or not to print verbosely - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @return a string representation of type - */ - @SideEffectFree - public String format(AnnotatedTypeMirror type, boolean printVerbose); + /** + * Formats type into a String. + * + * @param type the type to be converted + * @param printVerbose whether or not to print verbosely + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @return a string representation of type + */ + @SideEffectFree + public String format(AnnotatedTypeMirror type, boolean printVerbose); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index 0fb9ec44666..666d3dfe6a7 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -1,12 +1,31 @@ package org.checkerframework.framework.type; import com.sun.tools.javac.code.Symbol.MethodSymbol; + +import org.checkerframework.checker.formatter.qual.FormatMethod; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TypeKindUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.DeepCopyable; + import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -26,22 +45,6 @@ import javax.lang.model.type.UnionType; import javax.lang.model.type.WildcardType; import javax.lang.model.util.Types; -import org.checkerframework.checker.formatter.qual.FormatMethod; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TypeKindUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.DeepCopyable; /** * Represents an annotated type in the Java programming language, including: @@ -71,2612 +74,2666 @@ */ public abstract class AnnotatedTypeMirror implements DeepCopyable { - /** An EqualityAtmComparer. */ - protected static final EqualityAtmComparer EQUALITY_COMPARER = new EqualityAtmComparer(); - - /** A HashcodeAtmVisitor. */ - protected static final HashcodeAtmVisitor HASHCODE_VISITOR = new HashcodeAtmVisitor(); - - /** The factory to use for lazily creating annotated types. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The actual type wrapped by this AnnotatedTypeMirror. */ - protected final TypeMirror underlyingType; - - /** - * Saves the result of {@code underlyingType.toString().hashCode()} to use when computing the hash - * code of this. (Because AnnotatedTypeMirrors are mutable, the hash code for this cannot be - * saved.) Call {@link #getUnderlyingTypeHashCode()} rather than using the field directly. - */ - private int underlyingTypeHashCode = -1; - - /** The annotations on this type. */ - // AnnotationMirror doesn't override Object.hashCode, .equals, so we use - // the class name of Annotation instead. - // Caution: Assumes that a type can have at most one AnnotationMirror for any Annotation type. - protected final AnnotationMirrorSet primaryAnnotations = new AnnotationMirrorSet(); - - // /** The explicitly written annotations on this type. */ - // TODO: use this to cache the result once computed? For generic types? - // protected final AnnotationMirrorSet explicitannotations = - // new AnnotationMirrorSet(); - - /** - * Constructor for AnnotatedTypeMirror. - * - * @param underlyingType the underlying type - * @param atypeFactory used to create further types and to access global information (Types, - * Elements, ...) - */ - private AnnotatedTypeMirror(TypeMirror underlyingType, AnnotatedTypeFactory atypeFactory) { - this.underlyingType = underlyingType; - assert atypeFactory != null; - this.atypeFactory = atypeFactory; - } - - /// This class doesn't customize the clone() method; use deepCopy() instead. - // @Override - // public AnnotatedTypeMirror clone() { ... } - - /** - * Creates an AnnotatedTypeMirror for the provided type. The result contains no annotations. - * - * @param type the underlying type for the resulting AnnotatedTypeMirror - * @param atypeFactory the type factory that will build the result - * @param isDeclaration true if the result should represent a declaration, rather than a use, of a - * type - * @return an AnnotatedTypeMirror whose underlying type is {@code type} - */ - public static AnnotatedTypeMirror createType( - TypeMirror type, AnnotatedTypeFactory atypeFactory, boolean isDeclaration) { - if (type == null) { - throw new BugInCF("AnnotatedTypeMirror.createType: input type must not be null"); - } + /** An EqualityAtmComparer. */ + protected static final EqualityAtmComparer EQUALITY_COMPARER = new EqualityAtmComparer(); - AnnotatedTypeMirror result; - switch (type.getKind()) { - case ARRAY: - result = new AnnotatedArrayType((ArrayType) type, atypeFactory); - break; - case DECLARED: - result = new AnnotatedDeclaredType((DeclaredType) type, atypeFactory, isDeclaration); - break; - case ERROR: - throw new ErrorTypeKindException( - "AnnotatedTypeMirror.createType: input is not compilable. Found error type:" - + " " - + type); - - case EXECUTABLE: - result = new AnnotatedExecutableType((ExecutableType) type, atypeFactory); - break; - case VOID: - case PACKAGE: - case NONE: - result = new AnnotatedNoType((NoType) type, atypeFactory); - break; - case NULL: - result = new AnnotatedNullType((NullType) type, atypeFactory); - break; - case TYPEVAR: - result = new AnnotatedTypeVariable((TypeVariable) type, atypeFactory, isDeclaration); - break; - case WILDCARD: - result = new AnnotatedWildcardType((WildcardType) type, atypeFactory); - break; - case INTERSECTION: - result = new AnnotatedIntersectionType((IntersectionType) type, atypeFactory); - break; - case UNION: - result = new AnnotatedUnionType((UnionType) type, atypeFactory); - break; - default: - if (type.getKind().isPrimitive()) { - result = new AnnotatedPrimitiveType((PrimitiveType) type, atypeFactory); - break; - } - throw new BugInCF( - "AnnotatedTypeMirror.createType: unidentified type " - + type - + " (" - + type.getKind() - + ")"); - } - /*if (jctype.isAnnotated()) { - result.addAnnotations(jctype.getAnnotationMirrors()); - }*/ - return result; - } - - @Override - public final boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } + /** A HashcodeAtmVisitor. */ + protected static final HashcodeAtmVisitor HASHCODE_VISITOR = new HashcodeAtmVisitor(); - if (!(o instanceof AnnotatedTypeMirror)) { - return false; - } + /** The factory to use for lazily creating annotated types. */ + protected final AnnotatedTypeFactory atypeFactory; - return EQUALITY_COMPARER.visit(this, (AnnotatedTypeMirror) o, null); - } - - @Pure - @Override - public final int hashCode() { - return HASHCODE_VISITOR.visit(this); - } - - /** - * Applies a visitor to this type. - * - * @param the return type of the visitor's methods - * @param

          the type of the additional parameter to the visitor's methods - * @param v the visitor operating on this type - * @param p additional parameter to the visitor - * @return a visitor-specified result - */ - public abstract R accept(AnnotatedTypeVisitor v, P p); - - /** - * Returns the {@code kind} of this type. - * - * @return the kind of this type - */ - public TypeKind getKind() { - return underlyingType.getKind(); - } - - /** - * Given a primitive type, return its kind. Given a boxed primitive type, return the corresponding - * primitive type kind. Otherwise, return null. - * - * @return a primitive type kind if this is a primitive type or boxed primitive type; otherwise - * null - */ - public @Nullable TypeKind getPrimitiveKind() { - return TypeKindUtils.primitiveOrBoxedToTypeKind(getUnderlyingType()); - } - - /** - * Returns the underlying unannotated Java type, which this wraps. - * - * @return the underlying type - */ - public TypeMirror getUnderlyingType() { - return underlyingType; - } - - /** - * Returns true if this type mirror represents a declaration, rather than a use, of a type. - * - *

          For example, {@code class List { ... }} declares a new type {@code List}, while {@code - * List} is a use of the type. - * - * @return true if this represents a declaration - */ - public boolean isDeclaration() { - return false; - } - - public AnnotatedTypeMirror asUse() { - return this; - } - - /** - * Returns true if this type has a primary annotation in the same hierarchy as {@code annotation}. - * - *

          This method does not account for annotations in deep types (type arguments, array - * components, etc). - * - * @param annotation the qualifier hierarchy to check for - * @return true iff this type has a primary annotation in the same hierarchy as {@code - * annotation}. - */ - // typetools: hasPrimaryAnnotationInHierarchy - public boolean hasAnnotationInHierarchy(AnnotationMirror annotation) { - return getAnnotationInHierarchy(annotation) != null; - } - - /** - * Returns the primary annotation on this type that is in the same hierarchy as {@code - * annotation}. For {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code - * null} may be returned when the upper bound may have an annotation with that class, so {@link - * #getEffectiveAnnotationInHierarchy(AnnotationMirror)} should be called instead. - * - *

          This method does not account for annotations in deep types (type arguments, array - * components, etc). - * - *

          May return null if the receiver is a type variable or a wildcard without a primary - * annotation, or if the receiver is not yet fully annotated. - * - * @param annotation an annotation in the qualifier hierarchy to check for - * @return the annotation mirror whose class is named {@code annoNAme} or null - */ - // typetools: getPrimaryAnnotationInHierarchy - public @Nullable AnnotationMirror getAnnotationInHierarchy(AnnotationMirror annotation) { - if (primaryAnnotations.isEmpty()) { - return null; - } - AnnotationMirror canonical = annotation; - if (!atypeFactory.isSupportedQualifier(canonical)) { - canonical = atypeFactory.canonicalAnnotation(annotation); - if (canonical == null) { - // This can happen if annotation is unrelated to this AnnotatedTypeMirror. - return null; - } - } - if (atypeFactory.isSupportedQualifier(canonical)) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - AnnotationMirror anno = - qualHierarchy.findAnnotationInSameHierarchy(primaryAnnotations, canonical); - if (anno != null) { - return anno; - } - } - return null; - } - - /** - * Returns the "effective" annotation from the same hierarchy as {@code annotation}, otherwise - * returns {@code null}. - * - *

          An effective annotation is the annotation on the type itself, or on the upper/extends bound - * of a type variable/wildcard (recursively, until a class type is reached). - * - * @param annotation an annotation in the qualifier hierarchy to check for - * @return an annotation from the same hierarchy as {@code annotation} if present - */ - public @Nullable AnnotationMirror getEffectiveAnnotationInHierarchy(AnnotationMirror annotation) { - AnnotationMirror canonical = annotation; - if (!atypeFactory.isSupportedQualifier(canonical)) { - canonical = atypeFactory.canonicalAnnotation(annotation); - } - if (atypeFactory.isSupportedQualifier(canonical)) { - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - AnnotationMirror anno = - qualHierarchy.findAnnotationInSameHierarchy(getEffectiveAnnotations(), canonical); - if (anno != null) { - return anno; - } - } - return null; - } - - /** - * Returns the primary annotations on this type. For {@link AnnotatedTypeVariable}s and {@link - * AnnotatedWildcardType}s, the returned annotations may be empty or missing annotations in - * hierarchies, so {@link #getEffectiveAnnotations()} should be called instead. - * - *

          It does not include annotations in deep types (type arguments, array components, etc). - * - *

          To get the single primary annotation in a particular hierarchy, use {@link - * #getAnnotationInHierarchy}. - * - * @return an unmodifiable set of the annotations on this - */ - // typetools: getPrimaryAnnotations - // typetools: removed method getPrimaryAnnotation - public final AnnotationMirrorSet getAnnotations() { - return AnnotationMirrorSet.unmodifiableSet(primaryAnnotations); - } - - /** - * Returns the annotations on this type; mutations affect this object, because the return type is - * an alias of the {@code annotations} field. It does not include annotations in deep types (type - * arguments, array components, etc). - * - *

          The returned set should not be modified, but for efficiency reasons modification is not - * prevented. Modifications might break invariants. - * - * @return the set of the annotations on this; mutations affect this object - */ - // typetools: getPrimaryAnnotationsField - protected final AnnotationMirrorSet getAnnotationsField() { - return primaryAnnotations; - } - - /** - * Returns the "effective" annotations on this type, i.e. the annotations on the type itself, or - * on the upper/extends bound of a type variable/wildcard (recursively, until a class type is - * reached). If this is fully-annotated, the returned set will contain one annotation per - * hierarchy. - * - * @return a set of the annotations on this - */ - // TODO: When the current, deprecated `getAnnotations()` (deprecation date 2023-06-15) is - // removed, rename all the "getEffectiveAnnotation...()" methods to just "getAnnotation...()". - // EISOP will not do this renaming, it would introduce inconsistent behavior with how - // getAnnotations in javac APIs works. - // Removed getEffectiveAnnotation - public AnnotationMirrorSet getEffectiveAnnotations() { - AnnotationMirrorSet effectiveAnnotations = getErased().getAnnotations(); - // assert atypeFactory.qualHierarchy.getWidth() == effectiveAnnotations - // .size() : "Invalid number of effective annotations (" - // + effectiveAnnotations + "). Should be " - // + atypeFactory.qualHierarchy.getWidth() + " but is " - // + effectiveAnnotations.size() + ". Type: " + this; - return effectiveAnnotations; - } - - /** - * Returns the primary annotation on this type whose class is {@code annoClass}. For {@link - * AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be returned when - * the upper bound may have an annotation with that class, so {@link - * #getEffectiveAnnotation(Class)} should be called instead. - * - * @param annoClass annotation class - * @return the annotation mirror whose class is {@code annoClass} or null - */ - // typetools: getPrimaryAnnotation - public @Nullable AnnotationMirror getAnnotation(Class annoClass) { - for (AnnotationMirror annoMirror : primaryAnnotations) { - if (atypeFactory.areSameByClass(annoMirror, annoClass)) { - return annoMirror; - } - } - return null; - } - - /** - * Returns the primary annotations on this type whose annotation class name {@code annoName}. For - * {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be returned - * when the upper bound may have an annotation with that class, so {@link - * #getEffectiveAnnotation(Class)} should be called instead. - * - * @param annoName annotation class name - * @return the annotation mirror whose class is named {@code annoName} or null - */ - // typetools: getPrimaryAnnotation - public @Nullable AnnotationMirror getAnnotation(String annoName) { - for (AnnotationMirror annoMirror : primaryAnnotations) { - if (AnnotationUtils.areSameByName(annoMirror, annoName)) { - return annoMirror; - } - } - return null; - } - - /** - * Returns the set of explicitly written annotations on this type that are supported by this - * checker. This is useful to check the validity of annotations explicitly present on a type, as - * flow inference might add annotations that were not previously present. Note that since - * AnnotatedTypeMirror instances are created for type uses, this method will return explicit - * annotations in type use locations but will not return explicit annotations that had an impact - * on defaulting, such as an explicit annotation on a class declaration. For example, given: - * - *

          {@code @MyExplicitAnno class MyClass {}; MyClass myClassInstance; } - * - *

          the result of calling {@code - * atypeFactory.getAnnotatedType(variableTreeForMyClassInstance).getExplicitAnnotations()} - * - *

          will not contain {@code @MyExplicitAnno}. - * - * @return the set of explicitly written annotations on this type that are supported by this - * checker - */ - public AnnotationMirrorSet getExplicitAnnotations() { - // TODO JSR 308: The explicit type annotations should be always present - AnnotationMirrorSet explicitAnnotations = new AnnotationMirrorSet(); - List typeAnnotations = - this.getUnderlyingType().getAnnotationMirrors(); - - for (AnnotationMirror explicitAnno : typeAnnotations) { - if (atypeFactory.isSupportedQualifier(explicitAnno)) { - explicitAnnotations.add(explicitAnno); - } - } + /** The actual type wrapped by this AnnotatedTypeMirror. */ + protected final TypeMirror underlyingType; - return explicitAnnotations; - } - - /** - * Returns true if this type has a primary annotation that is the same as {@code a}. - * - *

          This method considers the annotation's values. If the type is {@code @A("s") @B(3) Object}, - * then a call with {@code @A("t")} or {@code @A} will return false, whereas a call with - * {@code @B(3)} will return true. - * - *

          In contrast to {@link #hasAnnotationRelaxed(AnnotationMirror)} this method also compares - * annotation values. - * - * @param a the annotation to check for - * @return true iff this type has a primary annotation that is the same as {@code a} - * @see #hasAnnotationRelaxed(AnnotationMirror) - */ - // typetools: hasPrimaryAnnotation - public boolean hasAnnotation(AnnotationMirror a) { - return AnnotationUtils.containsSame(primaryAnnotations, a); - } - - /** - * Returns true if this type has a primary annotation that has the same annotation type as {@code - * a}. This method does not consider an annotation's values. - * - * @param a the class of annotation to check for - * @return true iff the type contains an annotation with the same type as the annotation given by - * {@code a} - */ - public boolean hasAnnotation(Class a) { - return getAnnotation(a) != null; - } - - /** - * Returns the "effective" annotation on this type with the class {@code annoClass} or {@code - * null} if this type does not have one. - * - *

          An effective annotation is the annotation on the type itself, or on the upper/extends bound - * of a type variable/wildcard (recursively, until a class type is reached). - * - * @param annoClass annotation class - * @return the effective annotation with the same class as {@code annoClass} - */ - public @Nullable AnnotationMirror getEffectiveAnnotation(Class annoClass) { - for (AnnotationMirror annoMirror : getEffectiveAnnotations()) { - if (atypeFactory.areSameByClass(annoMirror, annoClass)) { - return annoMirror; - } - } - return null; - } - - /** - * A version of {@link #hasAnnotation(Class)} that considers annotations on the upper bound of - * wildcards and type variables. - */ - public boolean hasEffectiveAnnotation(Class a) { - return getEffectiveAnnotation(a) != null; - } - - /** - * A version of {@link #hasAnnotation(AnnotationMirror)} that considers annotations on the upper - * bound of wildcards and type variables. - */ - public boolean hasEffectiveAnnotation(AnnotationMirror a) { - return AnnotationUtils.containsSame(getEffectiveAnnotations(), a); - } - - /** - * Returns true if this type contains the given annotation explicitly written at declaration. This - * method considers the annotation's values. If the type is {@code @A("s") @B(3) Object}, a call - * with {@code @A("t")} or {@code @A} will return false, whereas a call with {@code @B(3)} will - * return true. - * - *

          In contrast to {@link #hasExplicitAnnotationRelaxed(AnnotationMirror)} this method also - * compares annotation values. - * - *

          See the documentation for {@link #getExplicitAnnotations()} for details on which explicit - * annotations are not included. - * - * @param a the annotation to check for - * @return true iff the annotation {@code a} is explicitly written on the type - * @see #hasExplicitAnnotationRelaxed(AnnotationMirror) - * @see #getExplicitAnnotations() - */ - public boolean hasExplicitAnnotation(AnnotationMirror a) { - return AnnotationUtils.containsSame(getExplicitAnnotations(), a); - } - - /** - * Returns true if this type has a primary annotation that has the same annotation class as {@code - * a}. - * - *

          This method does not consider an annotation's values. If the type is {@code @A("s") @B(3) - * Object}, then a call with {@code @A("t")}, {@code @A}, or {@code @B} will return true. - * - * @param a the annotation to check for - * @return true iff the type has a primary annotation with the same type as {@code a} - * @see #hasAnnotation(AnnotationMirror) - */ - // typetools: hasPrimaryAnnotationRelaxed - public boolean hasAnnotationRelaxed(AnnotationMirror a) { - return AnnotationUtils.containsSameByName(primaryAnnotations, a); - } - - /** - * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that considers annotations on the - * upper bound of wildcards and type variables. - */ - public boolean hasEffectiveAnnotationRelaxed(AnnotationMirror a) { - return AnnotationUtils.containsSameByName(getEffectiveAnnotations(), a); - } - - /** - * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that only considers annotations - * that are explicitly written on the type. - * - *

          See the documentation for {@link #getExplicitAnnotations()} for details on which explicit - * annotations are not included. - */ - public boolean hasExplicitAnnotationRelaxed(AnnotationMirror a) { - return AnnotationUtils.containsSameByName(getExplicitAnnotations(), a); - } - - /** - * Returns true if this type contains an explicitly written annotation with the same annotation - * type as a particular annotation. This method does not consider an annotation's values. - * - *

          See the documentation for {@link #getExplicitAnnotations()} for details on which explicit - * annotations are not included. - * - * @param a the class of annotation to check for - * @return true iff the type contains an explicitly written annotation with the same type as the - * annotation given by {@code a} - * @see #getExplicitAnnotations() - */ - public boolean hasExplicitAnnotation(Class a) { - return AnnotationUtils.containsSameByName(getExplicitAnnotations(), getAnnotation(a)); - } - - /** - * Adds the canonical version of {@code annotation} as a primary annotation of this type and, in - * the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and {@link - * AnnotatedIntersectionType}s, adds it to all bounds. (The canonical version is found via {@link - * AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of {@code annotation} is - * not a supported qualifier, then no annotation is added. If this type already has annotation in - * the same hierarchy as {@code annotation}, the behavior of this method is undefined. - * - * @param annotation the annotation to add - */ - public void addAnnotation(AnnotationMirror annotation) { - if (annotation == null) { - throw new BugInCF("AnnotatedTypeMirror.addAnnotation: null argument."); - } - if (atypeFactory.isSupportedQualifier(annotation)) { - this.primaryAnnotations.add(annotation); - } else { - AnnotationMirror canonical = atypeFactory.canonicalAnnotation(annotation); - if (atypeFactory.isSupportedQualifier(canonical)) { - addAnnotation(canonical); - } - } - } - - /** - * Adds an annotation to this type, removing any existing primary annotations from the same - * qualifier hierarchy first. - * - * @param a the annotation to add - */ - public void replaceAnnotation(AnnotationMirror a) { - this.removeAnnotationInHierarchy(a); - this.addAnnotation(a); - } - - /** - * Adds an annotation to this type. - * - * @param a the class of the annotation to add - * @deprecated This method creates a new {@code AnnotationMirror} every time it is called. Instead - * of calling this method, store the {@code AnnotationMirror} in a field and use {@link - * #addAnnotation(AnnotationMirror)} instead. - */ - @Deprecated // 2023-06-15 - public void addAnnotation(Class a) { - AnnotationMirror anno = AnnotationBuilder.fromClass(atypeFactory.elements, a); - addAnnotation(anno); - } - - /** - * Adds the canonical version of all {@code annotations} as primary annotations of this type and, - * in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and {@link - * AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found via - * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code - * annotation} is not a supported qualifier, then that annotation is not add added. If this type - * already has annotation in the same hierarchy as any of the {@code annotations}, the behavior of - * this method is undefined. - * - * @param annotations the annotations to add - */ - public void addAnnotations(Iterable annotations) { - for (AnnotationMirror a : annotations) { - this.addAnnotation(a); - } - } - - /** - * Adds only the annotations in {@code annotations} that the type does not already have a primary - * annotation in the same hierarchy. - * - *

          The canonical version of the {@code annotations} are added as primary annotations of this - * type and, in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and - * {@link AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found - * via {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an - * annotation is not a supported qualifier, then that annotation is not add added. - * - * @param annotations the annotations to add - */ - public void addMissingAnnotations(Iterable annotations) { - for (AnnotationMirror a : annotations) { - addMissingAnnotation(a); - } - } - - /** - * Add {@code annotation} if the type does not already have a primary annotation in the same - * hierarchy. - * - *

          The canonical version of the {@code annotation} is added as a primary annotation of this - * type and (in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and - * {@link AnnotatedIntersectionType}s) added to all bounds. (The canonical version is found via - * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code - * annotation} is not a supported qualifier, then that annotation is not add added. - * - * @param annotation the annotations to add - */ - public void addMissingAnnotation(AnnotationMirror annotation) { - if (!this.hasAnnotationInHierarchy(annotation)) { - this.addAnnotation(annotation); - } - } - - /** - * Adds multiple annotations to this type, removing any existing primary annotations from the same - * qualifier hierarchy first. - * - * @param replAnnos the annotations to replace - */ - public void replaceAnnotations(Iterable replAnnos) { - for (AnnotationMirror a : replAnnos) { - this.replaceAnnotation(a); - } - } - - /** - * Removes a primary annotation from the type. - * - * @param a the annotation to remove - * @return true if the annotation was removed, false if the type's annotations were unchanged - */ - // typetools removePrimaryAnnotation - public boolean removeAnnotation(AnnotationMirror a) { - AnnotationMirror anno = AnnotationUtils.getSame(primaryAnnotations, a); - if (anno != null) { - return primaryAnnotations.remove(anno); - } - return false; - } - - /** - * Removes a primary annotation of the given class from the type. - * - * @param a the class of the annotation to remove - * @return true if the annotation was removed, false if the type's annotations were unchanged - */ - // typetools: removePrimaryAnnotationByClass - public boolean removeAnnotationByClass(Class a) { - AnnotationMirror anno = atypeFactory.getAnnotationByClass(primaryAnnotations, a); - if (anno != null) { - return this.removeAnnotation(anno); - } - return false; - } - - /** - * Remove any primary annotation that is in the same qualifier hierarchy as the parameter. - * - * @param a an annotation from the same qualifier hierarchy - * @return if an annotation was removed - */ - // typetools removePrimaryAnnotationInHierarchy - public boolean removeAnnotationInHierarchy(AnnotationMirror a) { - AnnotationMirror prev = this.getAnnotationInHierarchy(a); - if (prev != null) { - return this.removeAnnotation(prev); - } - return false; - } - - /** - * Remove an annotation that is in the same qualifier hierarchy as the parameter, unless it's the - * top annotation. - * - * @param a an annotation from the same qualifier hierarchy - * @return if an annotation was removed - * @deprecated This will be removed in a future release - */ - @Deprecated // 2023-06-15 - public boolean removeNonTopAnnotationInHierarchy(AnnotationMirror a) { - AnnotationMirror prev = this.getAnnotationInHierarchy(a); - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - if (prev != null && !prev.equals(qualHierarchy.getTopAnnotation(a))) { - return this.removeAnnotation(prev); - } - return false; - } - - /** - * Removes multiple primary annotations from the type. - * - * @param annotations the annotations to remove - * @return true if at least one annotation was removed, false if the type's annotations were - * unchanged - */ - // typetools: removePrimaryAnnotations - public boolean removeAnnotations(Iterable annotations) { - boolean changed = false; - for (AnnotationMirror a : annotations) { - changed |= this.removeAnnotation(a); - } - return changed; - } - - /** Removes all primary annotations on this type. */ - // typetools: clearPrimaryAnnotations - public void clearAnnotations() { - primaryAnnotations.clear(); - } - - @SideEffectFree - @Override - public final String toString() { - return atypeFactory.getAnnotatedTypeFormatter().format(this); - } - - @SideEffectFree - public final String toString(boolean verbose) { - return atypeFactory.getAnnotatedTypeFormatter().format(this, verbose); - } - - /** - * Returns the erasure type of this type, according to JLS specifications. - * - * @see https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.6 - * @return the erasure of this AnnotatedTypeMirror, this is always a copy even if the erasure and - * the original type are equivalent - */ - public AnnotatedTypeMirror getErased() { - return deepCopy(); - } - - /** - * Returns a deep copy of this type. A deep copy implies that each component type is copied - * recursively and the returned type refers to those copies in its component locations. - * - *

          Note: deepCopy provides two important properties in the returned copy: - * - *

            - *
          1. Structure preservation -- The exact structure of the original AnnotatedTypeMirror is - * preserved in the copy including all component types. - *
          2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror - * and its components have been copied to the new type. - *
          - * - * If copyAnnotations is set to false, the second property, Annotation preservation, is removed. - * This is useful for cases in which the user may want to copy the structure of a type exactly but - * NOT its annotations. - * - * @return a deep copy - */ - public abstract AnnotatedTypeMirror deepCopy(boolean copyAnnotations); - - /** - * Returns a deep copy of this type with annotations. - * - *

          Each subclass implements this method with the subclass return type. The method body must - * always be a call to deepCopy(true). - * - * @return a deep copy of this type with annotations - * @see #deepCopy(boolean) - */ - @Override - public abstract AnnotatedTypeMirror deepCopy(); - - /** - * Returns a shallow copy of this type. A shallow copy implies that each component type in the - * output copy refers to the same object as the object being copied. - * - * @param copyAnnotations whether copy should have annotations, i.e. whether field {@code - * annotations} should be copied. - */ - public abstract AnnotatedTypeMirror shallowCopy(boolean copyAnnotations); - - /** - * Returns a shallow copy of this type with annotations. - * - *

          Each subclass implements this method with the subclass return type. The method body must - * always be a call to shallowCopy(true). - * - * @see #shallowCopy(boolean) - * @return a shallow copy of this type with annotations - */ - public abstract AnnotatedTypeMirror shallowCopy(); - - /** - * Whether this contains any captured type variables. - * - * @return whether the {@code type} contains any captured type variables - */ - public boolean containsCapturedTypes() { - return atypeFactory.containsCapturedTypes(this); - } - - /** - * Create an {@link AnnotatedDeclaredType} with the underlying type of {@link Object}. It includes - * any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. - * - * @param atypeFactory type factory to use - * @return AnnotatedDeclaredType for Object - */ - protected static AnnotatedDeclaredType createTypeOfObject(AnnotatedTypeFactory atypeFactory) { - AnnotatedDeclaredType objectType = - atypeFactory.fromElement( - atypeFactory.elements.getTypeElement(Object.class.getCanonicalName())); - objectType.declaration = false; - return objectType; - } - - /** - * Create an {@link AnnotatedDeclaredType} with the underlying type of {@code java.lang.Record}. - * It includes any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. - * - * @param atypeFactory type factory to use - * @return AnnotatedDeclaredType for Record - */ - protected static AnnotatedDeclaredType createTypeOfRecord(AnnotatedTypeFactory atypeFactory) { - AnnotatedDeclaredType recordType = - atypeFactory.fromElement(atypeFactory.elements.getTypeElement("java.lang.Record")); - recordType.declaration = false; - return recordType; - } - - /** - * Returns the result of calling {@code underlyingType.toString().hashcode()}. This method saves - * the result in a field so that it isn't recomputed each time. - * - * @return the result of calling {@code underlyingType.toString().hashcode()} - */ - public int getUnderlyingTypeHashCode() { - if (underlyingTypeHashCode == -1) { - underlyingTypeHashCode = underlyingType.toString().hashCode(); - } - return underlyingTypeHashCode; - } + /** + * Saves the result of {@code underlyingType.toString().hashCode()} to use when computing the + * hash code of this. (Because AnnotatedTypeMirrors are mutable, the hash code for this cannot + * be saved.) Call {@link #getUnderlyingTypeHashCode()} rather than using the field directly. + */ + private int underlyingTypeHashCode = -1; - /** Represents a declared type (whether class or interface). */ - public static class AnnotatedDeclaredType extends AnnotatedTypeMirror { + /** The annotations on this type. */ + // AnnotationMirror doesn't override Object.hashCode, .equals, so we use + // the class name of Annotation instead. + // Caution: Assumes that a type can have at most one AnnotationMirror for any Annotation type. + protected final AnnotationMirrorSet primaryAnnotations = new AnnotationMirrorSet(); - /** Parametrized Type Arguments. */ - protected @MonotonicNonNull List typeArgs; + // /** The explicitly written annotations on this type. */ + // TODO: use this to cache the result once computed? For generic types? + // protected final AnnotationMirrorSet explicitannotations = + // new AnnotationMirrorSet(); /** - * Whether the type was initially raw, i.e. the user did not provide the type arguments. - * typeArgs will contain inferred type arguments, which might be too conservative at the moment. + * Constructor for AnnotatedTypeMirror. * - *

          Ideally, the field would be final. However, when we determine the supertype of a raw type, - * we need to set isUnderlyingTypeRaw for the supertype. + * @param underlyingType the underlying type + * @param atypeFactory used to create further types and to access global information (Types, + * Elements, ...) */ - private boolean isUnderlyingTypeRaw; - - /** The enclosing type. May be null. May be changed. */ - protected @Nullable AnnotatedDeclaredType enclosingType; + private AnnotatedTypeMirror(TypeMirror underlyingType, AnnotatedTypeFactory atypeFactory) { + this.underlyingType = underlyingType; + assert atypeFactory != null; + this.atypeFactory = atypeFactory; + } - /** True if this represents a declaration, rather than a use, of a type. */ - private boolean declaration; + /// This class doesn't customize the clone() method; use deepCopy() instead. + // @Override + // public AnnotatedTypeMirror clone() { ... } /** - * Constructor for this type. The result contains no annotations. + * Creates an AnnotatedTypeMirror for the provided type. The result contains no annotations. * - * @param type underlying kind of this type - * @param atypeFactory the AnnotatedTypeFactory used to create this type + * @param type the underlying type for the resulting AnnotatedTypeMirror + * @param atypeFactory the type factory that will build the result + * @param isDeclaration true if the result should represent a declaration, rather than a use, of + * a type + * @return an AnnotatedTypeMirror whose underlying type is {@code type} */ - private AnnotatedDeclaredType( - DeclaredType type, AnnotatedTypeFactory atypeFactory, boolean declaration) { - super(type, atypeFactory); - TypeElement typeelem = (TypeElement) type.asElement(); - DeclaredType declty = (DeclaredType) typeelem.asType(); - isUnderlyingTypeRaw = - !declty.getTypeArguments().isEmpty() && type.getTypeArguments().isEmpty(); - - TypeMirror encl = type.getEnclosingType(); - if (encl.getKind() == TypeKind.DECLARED) { - this.enclosingType = (AnnotatedDeclaredType) createType(encl, atypeFactory, declaration); - } else if (encl.getKind() == TypeKind.NONE) { - this.enclosingType = null; - } else { - throw new BugInCF( - "AnnotatedDeclaredType: unsupported enclosing type: " - + type.getEnclosingType() - + " (" - + encl.getKind() - + ")"); - } - - this.declaration = declaration; - } + public static AnnotatedTypeMirror createType( + TypeMirror type, AnnotatedTypeFactory atypeFactory, boolean isDeclaration) { + if (type == null) { + throw new BugInCF("AnnotatedTypeMirror.createType: input type must not be null"); + } - @Override - public boolean isDeclaration() { - return declaration; + AnnotatedTypeMirror result; + switch (type.getKind()) { + case ARRAY: + result = new AnnotatedArrayType((ArrayType) type, atypeFactory); + break; + case DECLARED: + result = + new AnnotatedDeclaredType((DeclaredType) type, atypeFactory, isDeclaration); + break; + case ERROR: + throw new ErrorTypeKindException( + "AnnotatedTypeMirror.createType: input is not compilable. Found error type:" + + " " + + type); + + case EXECUTABLE: + result = new AnnotatedExecutableType((ExecutableType) type, atypeFactory); + break; + case VOID: + case PACKAGE: + case NONE: + result = new AnnotatedNoType((NoType) type, atypeFactory); + break; + case NULL: + result = new AnnotatedNullType((NullType) type, atypeFactory); + break; + case TYPEVAR: + result = + new AnnotatedTypeVariable((TypeVariable) type, atypeFactory, isDeclaration); + break; + case WILDCARD: + result = new AnnotatedWildcardType((WildcardType) type, atypeFactory); + break; + case INTERSECTION: + result = new AnnotatedIntersectionType((IntersectionType) type, atypeFactory); + break; + case UNION: + result = new AnnotatedUnionType((UnionType) type, atypeFactory); + break; + default: + if (type.getKind().isPrimitive()) { + result = new AnnotatedPrimitiveType((PrimitiveType) type, atypeFactory); + break; + } + throw new BugInCF( + "AnnotatedTypeMirror.createType: unidentified type " + + type + + " (" + + type.getKind() + + ")"); + } + /*if (jctype.isAnnotated()) { + result.addAnnotations(jctype.getAnnotationMirrors()); + }*/ + return result; } @Override - public AnnotatedDeclaredType deepCopy(boolean copyAnnotations) { - return (AnnotatedDeclaredType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + public final boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } - @Override - public AnnotatedDeclaredType deepCopy() { - return deepCopy(true); - } + if (!(o instanceof AnnotatedTypeMirror)) { + return false; + } - @Override - public AnnotatedDeclaredType asUse() { - if (!this.isDeclaration()) { - return this; - } - AnnotatedDeclaredType result = this.shallowCopy(true); - result.declaration = false; - if (this.enclosingType != null) { - result.enclosingType = this.enclosingType.asUse(); - } - // setTypeArguments calls asUse on all the new type arguments. - result.setTypeArguments(typeArgs); - - // If "this" is a type declaration with a type variable that references itself, e.g. - // MyClass>, then the type variable is a declaration, i.e. the first - // T, but the reference to the type variable is a use, i.e. the second T. When "this" - // is converted to a use, then both type variables are uses and should be the same - // object. - // The code below does this. - Map mapping = new HashMap<>(typeArgs.size()); - for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { - AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; - mapping.put(typeVar.getUnderlyingType(), typeVar); - } - for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { - AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; - AnnotatedTypeMirror upperBound = - atypeFactory - .getTypeVarSubstitutor() - .substituteWithoutCopyingTypeArguments(mapping, typeVar.getUpperBound()); - typeVar.setUpperBound(upperBound); - } - - return result; + return EQUALITY_COMPARER.visit(this, (AnnotatedTypeMirror) o, null); } + @Pure @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitDeclared(this, p); + public final int hashCode() { + return HASHCODE_VISITOR.visit(this); } /** - * Sets the type arguments on this type. + * Applies a visitor to this type. * - * @param ts a list of type arguments to be captured by this method + * @param the return type of the visitor's methods + * @param

          the type of the additional parameter to the visitor's methods + * @param v the visitor operating on this type + * @param p additional parameter to the visitor + * @return a visitor-specified result */ - public void setTypeArguments(List ts) { - if (ts == null || ts.isEmpty()) { - typeArgs = Collections.emptyList(); - } else if (isDeclaration()) { - for (AnnotatedTypeMirror typeArg : ts) { - if (typeArg.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF( - "Type declaration must have type variables as type arguments. Found %s", typeArg); - } - if (!typeArg.isDeclaration()) { - throw new BugInCF( - "Type declarations must have type variables that are declarations. Found %s", - typeArg); - } - } - typeArgs = Collections.unmodifiableList(ts); - } else { - List uses = CollectionsPlume.mapList(AnnotatedTypeMirror::asUse, ts); - typeArgs = Collections.unmodifiableList(uses); - } - } + public abstract R accept(AnnotatedTypeVisitor v, P p); /** - * Returns the type arguments for this type. + * Returns the {@code kind} of this type. * - * @return the type arguments for this type + * @return the kind of this type */ - public List getTypeArguments() { - if (typeArgs != null) { - return typeArgs; - } - - DeclaredType t = getUnderlyingType(); - typeArgs = new ArrayList<>(t.getTypeArguments().size()); - - if (isUnderlyingTypeRaw()) { - TypeElement typeElement = (TypeElement) atypeFactory.types.asElement(t); - Map typeParameterToWildcard = new HashMap<>(); - for (TypeParameterElement typeParameterEle : typeElement.getTypeParameters()) { - TypeVariable typeParameterVar = (TypeVariable) typeParameterEle.asType(); - TypeMirror wildcard = - BoundsInitializer.getUpperBoundAsWildcard(typeParameterVar, atypeFactory.types); - AnnotatedWildcardType atmWild = - (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wildcard, atypeFactory, false); - atmWild.setTypeArgOfRawType(); - BoundsInitializer.initializeBounds(atmWild); - typeArgs.add(atmWild); - typeParameterToWildcard.put(typeParameterVar, atmWild); - } - TypeVariableSubstitutor suber = atypeFactory.getTypeVarSubstitutor(); - for (AnnotatedTypeMirror atm : typeArgs) { - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atm; - wildcardType.setExtendsBound( - suber.substituteWithoutCopyingTypeArguments( - typeParameterToWildcard, wildcardType.getExtendsBound())); - } - } else if (isDeclaration()) { - for (TypeMirror javaTypeArg : t.getTypeArguments()) { - AnnotatedTypeVariable tv = - (AnnotatedTypeVariable) - AnnotatedTypeMirror.createType(javaTypeArg, atypeFactory, true); - typeArgs.add(tv); - } - } else { - for (TypeMirror javaTypeArg : t.getTypeArguments()) { - AnnotatedTypeMirror typeArg = - AnnotatedTypeMirror.createType(javaTypeArg, atypeFactory, false); - typeArgs.add(typeArg); - } - } - return typeArgs; + public TypeKind getKind() { + return underlyingType.getKind(); } /** - * Returns true if the underlying type is raw. The receiver of this method is not raw, however; - * its annotated type arguments have been inferred. + * Given a primitive type, return its kind. Given a boxed primitive type, return the + * corresponding primitive type kind. Otherwise, return null. * - * @return true iff the type was raw + * @return a primitive type kind if this is a primitive type or boxed primitive type; otherwise + * null */ - public boolean isUnderlyingTypeRaw() { - return isUnderlyingTypeRaw; + public @Nullable TypeKind getPrimitiveKind() { + return TypeKindUtils.primitiveOrBoxedToTypeKind(getUnderlyingType()); } /** - * Set the isUnderlyingTypeRaw flag to true. This should only be necessary when determining the - * supertypes of a raw type. + * Returns the underlying unannotated Java type, which this wraps. + * + * @return the underlying type */ - protected void setIsUnderlyingTypeRaw() { - this.isUnderlyingTypeRaw = true; + public TypeMirror getUnderlyingType() { + return underlyingType; } - @Override - public DeclaredType getUnderlyingType() { - return (DeclaredType) underlyingType; + /** + * Returns true if this type mirror represents a declaration, rather than a use, of a type. + * + *

          For example, {@code class List { ... }} declares a new type {@code List}, while + * {@code List} is a use of the type. + * + * @return true if this represents a declaration + */ + public boolean isDeclaration() { + return false; } - @Override - public List directSupertypes() { - return Collections.unmodifiableList(SupertypeFinder.directSupertypes(this)); + public AnnotatedTypeMirror asUse() { + return this; } - @Override - public AnnotatedDeclaredType shallowCopy() { - return shallowCopy(true); + /** + * Returns true if this type has a primary annotation in the same hierarchy as {@code + * annotation}. + * + *

          This method does not account for annotations in deep types (type arguments, array + * components, etc). + * + * @param annotation the qualifier hierarchy to check for + * @return true iff this type has a primary annotation in the same hierarchy as {@code + * annotation}. + */ + // typetools: hasPrimaryAnnotationInHierarchy + public boolean hasAnnotationInHierarchy(AnnotationMirror annotation) { + return getAnnotationInHierarchy(annotation) != null; } - @Override - public AnnotatedDeclaredType shallowCopy(boolean copyAnnotations) { - AnnotatedDeclaredType type = - new AnnotatedDeclaredType(getUnderlyingType(), atypeFactory, declaration); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.setEnclosingType(getEnclosingType()); - type.setTypeArguments(getTypeArguments()); - return type; + /** + * Returns the primary annotation on this type that is in the same hierarchy as {@code + * annotation}. For {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code + * null} may be returned when the upper bound may have an annotation with that class, so {@link + * #getEffectiveAnnotationInHierarchy(AnnotationMirror)} should be called instead. + * + *

          This method does not account for annotations in deep types (type arguments, array + * components, etc). + * + *

          May return null if the receiver is a type variable or a wildcard without a primary + * annotation, or if the receiver is not yet fully annotated. + * + * @param annotation an annotation in the qualifier hierarchy to check for + * @return the annotation mirror whose class is named {@code annoNAme} or null + */ + // typetools: getPrimaryAnnotationInHierarchy + public @Nullable AnnotationMirror getAnnotationInHierarchy(AnnotationMirror annotation) { + if (primaryAnnotations.isEmpty()) { + return null; + } + AnnotationMirror canonical = annotation; + if (!atypeFactory.isSupportedQualifier(canonical)) { + canonical = atypeFactory.canonicalAnnotation(annotation); + if (canonical == null) { + // This can happen if annotation is unrelated to this AnnotatedTypeMirror. + return null; + } + } + if (atypeFactory.isSupportedQualifier(canonical)) { + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + AnnotationMirror anno = + qualHierarchy.findAnnotationInSameHierarchy(primaryAnnotations, canonical); + if (anno != null) { + return anno; + } + } + return null; } /** - * Return the declared type with its type arguments removed. This also replaces the underlying - * type with its erasure. + * Returns the "effective" annotation from the same hierarchy as {@code annotation}, otherwise + * returns {@code null}. * - * @return a fresh copy of the declared type with no type arguments + *

          An effective annotation is the annotation on the type itself, or on the upper/extends + * bound of a type variable/wildcard (recursively, until a class type is reached). + * + * @param annotation an annotation in the qualifier hierarchy to check for + * @return an annotation from the same hierarchy as {@code annotation} if present */ - @Override - public AnnotatedDeclaredType getErased() { - AnnotatedDeclaredType erased = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType( - atypeFactory.types.erasure(underlyingType), atypeFactory, false); - erased.addAnnotations(this.getAnnotations()); - AnnotatedDeclaredType erasedEnclosing = erased.getEnclosingType(); - AnnotatedDeclaredType thisEnclosing = this.getEnclosingType(); - while (erasedEnclosing != null) { - erasedEnclosing.addAnnotations(thisEnclosing.getAnnotations()); - erasedEnclosing = erasedEnclosing.getEnclosingType(); - thisEnclosing = thisEnclosing.getEnclosingType(); - } - return erased; + public @Nullable AnnotationMirror getEffectiveAnnotationInHierarchy( + AnnotationMirror annotation) { + AnnotationMirror canonical = annotation; + if (!atypeFactory.isSupportedQualifier(canonical)) { + canonical = atypeFactory.canonicalAnnotation(annotation); + } + if (atypeFactory.isSupportedQualifier(canonical)) { + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + AnnotationMirror anno = + qualHierarchy.findAnnotationInSameHierarchy( + getEffectiveAnnotations(), canonical); + if (anno != null) { + return anno; + } + } + return null; } /** - * Sets the enclosing type. + * Returns the primary annotations on this type. For {@link AnnotatedTypeVariable}s and {@link + * AnnotatedWildcardType}s, the returned annotations may be empty or missing annotations in + * hierarchies, so {@link #getEffectiveAnnotations()} should be called instead. * - * @param enclosingType the new enclosing type + *

          It does not include annotations in deep types (type arguments, array components, etc). + * + *

          To get the single primary annotation in a particular hierarchy, use {@link + * #getAnnotationInHierarchy}. + * + * @return an unmodifiable set of the annotations on this */ - public void setEnclosingType(@Nullable AnnotatedDeclaredType enclosingType) { - this.enclosingType = enclosingType; + // typetools: getPrimaryAnnotations + // typetools: removed method getPrimaryAnnotation + public final AnnotationMirrorSet getAnnotations() { + return AnnotationMirrorSet.unmodifiableSet(primaryAnnotations); } /** - * Returns the enclosing type, as in the type of {@code A} in the type {@code A.B}. May return - * null. + * Returns the annotations on this type; mutations affect this object, because the return type + * is an alias of the {@code annotations} field. It does not include annotations in deep types + * (type arguments, array components, etc). * - * @return enclosingType the enclosing type, or null if this is a top-level type + *

          The returned set should not be modified, but for efficiency reasons modification is not + * prevented. Modifications might break invariants. + * + * @return the set of the annotations on this; mutations affect this object */ - public @Nullable AnnotatedDeclaredType getEnclosingType() { - return enclosingType; + // typetools: getPrimaryAnnotationsField + protected final AnnotationMirrorSet getAnnotationsField() { + return primaryAnnotations; } - } - - /** Represents a type of an executable. An executable is a method, constructor, or initializer. */ - public static class AnnotatedExecutableType extends AnnotatedTypeMirror { - - /** The element of the method. */ - /*package-private*/ @MonotonicNonNull ExecutableElement element; /** - * Creates an {@link AnnotatedExecutableType}. + * Returns the "effective" annotations on this type, i.e. the annotations on the type itself, or + * on the upper/extends bound of a type variable/wildcard (recursively, until a class type is + * reached). If this is fully-annotated, the returned set will contain one annotation per + * hierarchy. * - * @param type the Java type - * @param factory the factory + * @return a set of the annotations on this */ - private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factory) { - super(type, factory); + // TODO: When the current, deprecated `getAnnotations()` (deprecation date 2023-06-15) is + // removed, rename all the "getEffectiveAnnotation...()" methods to just "getAnnotation...()". + // EISOP will not do this renaming, it would introduce inconsistent behavior with how + // getAnnotations in javac APIs works. + // Removed getEffectiveAnnotation + public AnnotationMirrorSet getEffectiveAnnotations() { + AnnotationMirrorSet effectiveAnnotations = getErased().getAnnotations(); + // assert atypeFactory.qualHierarchy.getWidth() == effectiveAnnotations + // .size() : "Invalid number of effective annotations (" + // + effectiveAnnotations + "). Should be " + // + atypeFactory.qualHierarchy.getWidth() + " but is " + // + effectiveAnnotations.size() + ". Type: " + this; + return effectiveAnnotations; } - /** The parameter types; an unmodifiable list. */ - /*package-private*/ @MonotonicNonNull List paramTypes = null; - - /** Whether {@link #paramTypes} has been computed. */ - private boolean paramTypesComputed = false; - /** - * The receiver type of this executable type; null for static methods and constructors of - * top-level classes. + * Returns the primary annotation on this type whose class is {@code annoClass}. For {@link + * AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be returned when + * the upper bound may have an annotation with that class, so {@link + * #getEffectiveAnnotation(Class)} should be called instead. + * + * @param annoClass annotation class + * @return the annotation mirror whose class is {@code annoClass} or null */ - /*package-private*/ @Nullable AnnotatedDeclaredType receiverType; + // typetools: getPrimaryAnnotation + public @Nullable AnnotationMirror getAnnotation(Class annoClass) { + for (AnnotationMirror annoMirror : primaryAnnotations) { + if (atypeFactory.areSameByClass(annoMirror, annoClass)) { + return annoMirror; + } + } + return null; + } /** - * The varargs type is the last element of {@link #paramTypes} if the method or constructor - * accepts a variable number of arguments and the {@link #paramTypes} has not been expanded yet. - * This type needs to be stored in the field to avoid being affected by calling {@link - * AnnotatedTypes#adaptParameters(AnnotatedTypeFactory, - * AnnotatedTypeMirror.AnnotatedExecutableType, List, com.sun.source.tree.NewClassTree)}. + * Returns the primary annotations on this type whose annotation class name {@code annoName}. + * For {@link AnnotatedTypeVariable}s and {@link AnnotatedWildcardType}s, {@code null} may be + * returned when the upper bound may have an annotation with that class, so {@link + * #getEffectiveAnnotation(Class)} should be called instead. + * + * @param annoName annotation class name + * @return the annotation mirror whose class is named {@code annoName} or null */ - private @MonotonicNonNull AnnotatedArrayType varargType = null; - - /** Whether {@link #receiverType} has been computed. */ - private boolean receiverTypeComputed = false; - - /** The return type. */ - /*package-private*/ @MonotonicNonNull AnnotatedTypeMirror returnType; - - /** Whether {@link #returnType} has been computed. */ - private boolean returnTypeComputed = false; - - /** The thrown types; an unmodifiable list. */ - /*package-private*/ @MonotonicNonNull List thrownTypes; - - /** Whether {@link #thrownTypes} has been computed. */ - private boolean thrownTypesComputed = false; - - /** The type variables; an unmodifiable list. */ - /*package-private*/ @MonotonicNonNull List typeVarTypes; - - /** Whether {@link #typeVarTypes} has been computed. */ - private boolean typeVarTypesComputed = false; + // typetools: getPrimaryAnnotation + public @Nullable AnnotationMirror getAnnotation(String annoName) { + for (AnnotationMirror annoMirror : primaryAnnotations) { + if (AnnotationUtils.areSameByName(annoMirror, annoName)) { + return annoMirror; + } + } + return null; + } /** - * Returns true if this type represents a varargs method. + * Returns the set of explicitly written annotations on this type that are supported by this + * checker. This is useful to check the validity of annotations explicitly present on a type, as + * flow inference might add annotations that were not previously present. Note that since + * AnnotatedTypeMirror instances are created for type uses, this method will return explicit + * annotations in type use locations but will not return explicit annotations that had an impact + * on defaulting, such as an explicit annotation on a class declaration. For example, given: * - * @return true if this type represents a varargs method + *

          {@code @MyExplicitAnno class MyClass {}; MyClass myClassInstance; } + * + *

          the result of calling {@code + * atypeFactory.getAnnotatedType(variableTreeForMyClassInstance).getExplicitAnnotations()} + * + *

          will not contain {@code @MyExplicitAnno}. + * + * @return the set of explicitly written annotations on this type that are supported by this + * checker */ - public boolean isVarArgs() { - return this.element.isVarArgs(); - } + public AnnotationMirrorSet getExplicitAnnotations() { + // TODO JSR 308: The explicit type annotations should be always present + AnnotationMirrorSet explicitAnnotations = new AnnotationMirrorSet(); + List typeAnnotations = + this.getUnderlyingType().getAnnotationMirrors(); - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitExecutable(this, p); - } + for (AnnotationMirror explicitAnno : typeAnnotations) { + if (atypeFactory.isSupportedQualifier(explicitAnno)) { + explicitAnnotations.add(explicitAnno); + } + } - @Override - public ExecutableType getUnderlyingType() { - return (ExecutableType) this.underlyingType; + return explicitAnnotations; } /** - * It never makes sense to add annotations to an executable type. Instead, they should be added - * to the appropriate component. + * Returns true if this type has a primary annotation that is the same as {@code a}. + * + *

          This method considers the annotation's values. If the type is {@code @A("s") @B(3) + * Object}, then a call with {@code @A("t")} or {@code @A} will return false, whereas a call + * with {@code @B(3)} will return true. * - * @deprecated add to the appropriate component + *

          In contrast to {@link #hasAnnotationRelaxed(AnnotationMirror)} this method also compares + * annotation values. + * + * @param a the annotation to check for + * @return true iff this type has a primary annotation that is the same as {@code a} + * @see #hasAnnotationRelaxed(AnnotationMirror) */ - @Deprecated // not for removal - @Override - public void addAnnotation(AnnotationMirror annotation) { - assert false : "AnnotatedExecutableType.addAnnotation should never be called"; + // typetools: hasPrimaryAnnotation + public boolean hasAnnotation(AnnotationMirror a) { + return AnnotationUtils.containsSame(primaryAnnotations, a); } /** - * Sets the parameter types of this executable type, excluding the receiver.If paramTypes has - * been computed and this type is a varargs method, computes and store {@link #varargType} - * before calling this method, @see {@link #varargType} + * Returns true if this type has a primary annotation that has the same annotation type as + * {@code a}. This method does not consider an annotation's values. * - * @param params an unmodifiable list of parameter types to be captured by this method, - * excluding the receiver + * @param a the class of annotation to check for + * @return true iff the type contains an annotation with the same type as the annotation given + * by {@code a} */ - /*package-private*/ void setParameterTypes(List params) { - if (paramTypesComputed && isVarArgs() && varargType == null) { - throw new BugInCF("Set vararg type before resetting parameter types"); - } - paramTypes = params; - paramTypesComputed = true; + public boolean hasAnnotation(Class a) { + return getAnnotation(a) != null; } /** - * Returns the parameter types of this executable type, excluding the receiver. + * Returns the "effective" annotation on this type with the class {@code annoClass} or {@code + * null} if this type does not have one. + * + *

          An effective annotation is the annotation on the type itself, or on the upper/extends + * bound of a type variable/wildcard (recursively, until a class type is reached). * - * @return the parameter types of this executable type, excluding the receiver + * @param annoClass annotation class + * @return the effective annotation with the same class as {@code annoClass} */ - public List getParameterTypes() { - if (!paramTypesComputed) { - assert paramTypes == null; - List underlyingParameterTypes = - ((ExecutableType) underlyingType).getParameterTypes(); - if (underlyingParameterTypes.isEmpty()) { - setParameterTypes(Collections.emptyList()); - } else { - List newParamTypes = - new ArrayList<>(underlyingParameterTypes.size()); - for (TypeMirror t : underlyingParameterTypes) { - if (t.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed - // yet (see Issue #244). - throw new ErrorTypeKindException( - "Problem with parameter type of %s.%s: %s [%s %s]", - element, element.getEnclosingElement(), t, t.getKind(), t.getClass()); + public @Nullable AnnotationMirror getEffectiveAnnotation( + Class annoClass) { + for (AnnotationMirror annoMirror : getEffectiveAnnotations()) { + if (atypeFactory.areSameByClass(annoMirror, annoClass)) { + return annoMirror; } - newParamTypes.add(createType(t, atypeFactory, false)); - } - setParameterTypes(Collections.unmodifiableList(newParamTypes)); } - } - // No need to copy or wrap; it is an unmodifiable list. - return paramTypes; + return null; } /** - * Sets the vararg type of this executable type. - * - * @param varargType the vararg type of this executable type + * A version of {@link #hasAnnotation(Class)} that considers annotations on the upper bound of + * wildcards and type variables. */ - /*package-private*/ void setVarargType(@NonNull AnnotatedArrayType varargType) { - this.varargType = varargType; + public boolean hasEffectiveAnnotation(Class a) { + return getEffectiveAnnotation(a) != null; } /** - * Computes the vararg type of this executable type and stores it in {@link #varargType}. - * - *

          This method computes {@link #varargType} using the {@link #paramTypes} of this executable - * type. To use the {@link #paramTypes} from different executable type, use {@link - * #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. + * A version of {@link #hasAnnotation(AnnotationMirror)} that considers annotations on the upper + * bound of wildcards and type variables. */ - /*package-private*/ void computeVarargType() { - computeVarargType(paramTypes); + public boolean hasEffectiveAnnotation(AnnotationMirror a) { + return AnnotationUtils.containsSame(getEffectiveAnnotations(), a); } /** - * Computes the vararg type using the passed executable type and stores it in this {@link - * #varargType}. + * Returns true if this type contains the given annotation explicitly written at declaration. + * This method considers the annotation's values. If the type is {@code @A("s") @B(3) Object}, a + * call with {@code @A("t")} or {@code @A} will return false, whereas a call with {@code @B(3)} + * will return true. + * + *

          In contrast to {@link #hasExplicitAnnotationRelaxed(AnnotationMirror)} this method also + * compares annotation values. + * + *

          See the documentation for {@link #getExplicitAnnotations()} for details on which explicit + * annotations are not included. * - * @param annotatedExecutableType an AnnotatedExecutableType + * @param a the annotation to check for + * @return true iff the annotation {@code a} is explicitly written on the type + * @see #hasExplicitAnnotationRelaxed(AnnotationMirror) + * @see #getExplicitAnnotations() */ - /*package-private*/ void computeVarargType(AnnotatedExecutableType annotatedExecutableType) { - computeVarargType(annotatedExecutableType.getParameterTypes()); + public boolean hasExplicitAnnotation(AnnotationMirror a) { + return AnnotationUtils.containsSame(getExplicitAnnotations(), a); } /** - * Helper function for {@link #computeVarargType()} and {@link - * #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. + * Returns true if this type has a primary annotation that has the same annotation class as + * {@code a}. * - * @param paramTypes the parameter types to determine the vararg type + *

          This method does not consider an annotation's values. If the type is {@code @A("s") @B(3) + * Object}, then a call with {@code @A("t")}, {@code @A}, or {@code @B} will return true. + * + * @param a the annotation to check for + * @return true iff the type has a primary annotation with the same type as {@code a} + * @see #hasAnnotation(AnnotationMirror) + */ + // typetools: hasPrimaryAnnotationRelaxed + public boolean hasAnnotationRelaxed(AnnotationMirror a) { + return AnnotationUtils.containsSameByName(primaryAnnotations, a); + } + + /** + * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that considers annotations on + * the upper bound of wildcards and type variables. */ - private void computeVarargType(List paramTypes) { - if (!isVarArgs()) { - return; - } - varargType = (AnnotatedArrayType) paramTypes.get(paramTypes.size() - 1); + public boolean hasEffectiveAnnotationRelaxed(AnnotationMirror a) { + return AnnotationUtils.containsSameByName(getEffectiveAnnotations(), a); } /** - * Returns the vararg type of this executable type. + * A version of {@link #hasAnnotationRelaxed(AnnotationMirror)} that only considers annotations + * that are explicitly written on the type. * - * @return the vararg type of this executable type + *

          See the documentation for {@link #getExplicitAnnotations()} for details on which explicit + * annotations are not included. */ - public @Nullable AnnotatedArrayType getVarargType() { - return varargType; + public boolean hasExplicitAnnotationRelaxed(AnnotationMirror a) { + return AnnotationUtils.containsSameByName(getExplicitAnnotations(), a); } /** - * Sets the return type of this executable type. + * Returns true if this type contains an explicitly written annotation with the same annotation + * type as a particular annotation. This method does not consider an annotation's values. + * + *

          See the documentation for {@link #getExplicitAnnotations()} for details on which explicit + * annotations are not included. * - * @param returnType the new return type + * @param a the class of annotation to check for + * @return true iff the type contains an explicitly written annotation with the same type as the + * annotation given by {@code a} + * @see #getExplicitAnnotations() */ - /*package-private*/ void setReturnType(AnnotatedTypeMirror returnType) { - this.returnType = returnType; - returnTypeComputed = true; + public boolean hasExplicitAnnotation(Class a) { + return AnnotationUtils.containsSameByName(getExplicitAnnotations(), getAnnotation(a)); } /** - * The return type of a method or constructor. For constructors, the return type is not VOID, - * but the type of the enclosing class. + * Adds the canonical version of {@code annotation} as a primary annotation of this type and, in + * the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and {@link + * AnnotatedIntersectionType}s, adds it to all bounds. (The canonical version is found via + * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of {@code + * annotation} is not a supported qualifier, then no annotation is added. If this type already + * has annotation in the same hierarchy as {@code annotation}, the behavior of this method is + * undefined. * - * @return the return type of this executable type + * @param annotation the annotation to add */ - public AnnotatedTypeMirror getReturnType() { - if (!returnTypeComputed) { - assert returnType == null : "returnType = " + returnType; - if (element != null && ((ExecutableType) underlyingType).getReturnType() != null) { - TypeMirror aret = ((ExecutableType) underlyingType).getReturnType(); - if (aret.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed yet - // (see Issue #244). - throw new ErrorTypeKindException( - "Problem with return type of %s.%s: %s [%s %s]", - element, element.getEnclosingElement(), aret, aret.getKind(), aret.getClass()); - } - if (((MethodSymbol) element).isConstructor()) { - // For constructors, the underlying return type is void. - // Take the type of the enclosing class instead. - aret = element.getEnclosingElement().asType(); - if (aret.getKind() == TypeKind.ERROR) { - throw new ErrorTypeKindException( - "Input is not compilable; problem with constructor %s return type: %s [%s %s]" - + " (enclosing element = %s [%s])", - element, - aret, - aret.getKind(), - aret.getClass(), - element.getEnclosingElement(), - element.getEnclosingElement().getClass()); + public void addAnnotation(AnnotationMirror annotation) { + if (annotation == null) { + throw new BugInCF("AnnotatedTypeMirror.addAnnotation: null argument."); + } + if (atypeFactory.isSupportedQualifier(annotation)) { + this.primaryAnnotations.add(annotation); + } else { + AnnotationMirror canonical = atypeFactory.canonicalAnnotation(annotation); + if (atypeFactory.isSupportedQualifier(canonical)) { + addAnnotation(canonical); } - } - returnType = createType(aret, atypeFactory, false); } - returnTypeComputed = true; - } - return returnType; } /** - * Sets the receiver type on this executable type. + * Adds an annotation to this type, removing any existing primary annotations from the same + * qualifier hierarchy first. * - * @param receiverType the receiver type + * @param a the annotation to add */ - public void setReceiverType(@Nullable AnnotatedDeclaredType receiverType) { - this.receiverType = receiverType; - receiverTypeComputed = true; + public void replaceAnnotation(AnnotationMirror a) { + this.removeAnnotationInHierarchy(a); + this.addAnnotation(a); } /** - * Returns the receiver type of this executable type; null for static methods and constructors - * of top-level classes. + * Adds an annotation to this type. * - * @return the receiver type of this executable type; null for static methods and constructors - * of top-level classes + * @param a the class of the annotation to add + * @deprecated This method creates a new {@code AnnotationMirror} every time it is called. + * Instead of calling this method, store the {@code AnnotationMirror} in a field and use + * {@link #addAnnotation(AnnotationMirror)} instead. */ - public @Nullable AnnotatedDeclaredType getReceiverType() { - if (!receiverTypeComputed) { - assert receiverType == null; - Element element = getElement(); - if (ElementUtils.hasReceiver(element)) { - // Initial value of `encl`; might be updated. - TypeElement encl = ElementUtils.enclosingTypeElement(element); - if (element.getKind() == ElementKind.CONSTRUCTOR) { - // Can only reach this branch if we're the constructor of a nested class - encl = ElementUtils.enclosingTypeElement(encl.getEnclosingElement()); - } - TypeMirror enclType = encl.asType(); - if (enclType.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed yet - // (see Issue #244). - throw new ErrorTypeKindException( - "Problem with receiver type of %s.%s: %s [%s %s]", - element, - element.getEnclosingElement(), - enclType, - enclType.getKind(), - enclType.getClass()); - } - AnnotatedTypeMirror type = createType(enclType, atypeFactory, false); - assert type instanceof AnnotatedDeclaredType; - receiverType = (AnnotatedDeclaredType) type; - } - receiverTypeComputed = true; - } - return receiverType; + @Deprecated // 2023-06-15 + public void addAnnotation(Class a) { + AnnotationMirror anno = AnnotationBuilder.fromClass(atypeFactory.elements, a); + addAnnotation(anno); } /** - * Sets the thrown types of this executable type. + * Adds the canonical version of all {@code annotations} as primary annotations of this type + * and, in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and + * {@link AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found + * via {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code + * annotation} is not a supported qualifier, then that annotation is not add added. If this type + * already has annotation in the same hierarchy as any of the {@code annotations}, the behavior + * of this method is undefined. * - * @param thrownTypes an unmodifiable list of thrown types to be captured by this method + * @param annotations the annotations to add */ - /*package-private*/ void setThrownTypes(List thrownTypes) { - this.thrownTypes = thrownTypes; - thrownTypesComputed = true; + public void addAnnotations(Iterable annotations) { + for (AnnotationMirror a : annotations) { + this.addAnnotation(a); + } } /** - * Returns the thrown types of this executable type. + * Adds only the annotations in {@code annotations} that the type does not already have a + * primary annotation in the same hierarchy. + * + *

          The canonical version of the {@code annotations} are added as primary annotations of this + * type and, in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and + * {@link AnnotatedIntersectionType}s, adds them to all bounds. (The canonical version is found + * via {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an + * annotation is not a supported qualifier, then that annotation is not add added. * - * @return the thrown types of this executable type + * @param annotations the annotations to add */ - public List getThrownTypes() { - if (!thrownTypesComputed) { - assert thrownTypes == null; - List underlyingThrownTypes = - ((ExecutableType) underlyingType).getThrownTypes(); - if (underlyingThrownTypes.isEmpty()) { - setThrownTypes(Collections.emptyList()); - } else { - List newThrownTypes = new ArrayList<>(underlyingThrownTypes.size()); - for (TypeMirror t : underlyingThrownTypes) { - if (t.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed - // yet (see Issue #244). - throw new ErrorTypeKindException( - "Problem with thrown type of %s.%s: %s [%s %s]", - element, element.getEnclosingElement(), t, t.getKind(), t.getClass()); - } - newThrownTypes.add(createType(t, atypeFactory, false)); - } - setThrownTypes(Collections.unmodifiableList(newThrownTypes)); + public void addMissingAnnotations(Iterable annotations) { + for (AnnotationMirror a : annotations) { + addMissingAnnotation(a); } - } - // No need to copy or wrap; it is an unmodifiable list. - return thrownTypes; } /** - * Sets the type variables associated with this executable type. + * Add {@code annotation} if the type does not already have a primary annotation in the same + * hierarchy. * - * @param types an unmodifiable list of type variables of this executable type to be captured by - * this method + *

          The canonical version of the {@code annotation} is added as a primary annotation of this + * type and (in the case of {@link AnnotatedTypeVariable}s, {@link AnnotatedWildcardType}s, and + * {@link AnnotatedIntersectionType}s) added to all bounds. (The canonical version is found via + * {@link AnnotatedTypeFactory#canonicalAnnotation}.) If the canonical version of an {@code + * annotation} is not a supported qualifier, then that annotation is not add added. + * + * @param annotation the annotations to add */ - /*package-private*/ void setTypeVariables(List types) { - typeVarTypes = types; - typeVarTypesComputed = true; + public void addMissingAnnotation(AnnotationMirror annotation) { + if (!this.hasAnnotationInHierarchy(annotation)) { + this.addAnnotation(annotation); + } } /** - * Returns the type variables of this executable type, if any. + * Adds multiple annotations to this type, removing any existing primary annotations from the + * same qualifier hierarchy first. * - * @return the type variables of this executable type, if any + * @param replAnnos the annotations to replace */ - public List getTypeVariables() { - if (!typeVarTypesComputed) { - assert typeVarTypes == null; - List underlyingTypeVariables = - ((ExecutableType) underlyingType).getTypeVariables(); - if (underlyingTypeVariables.isEmpty()) { - setTypeVariables(Collections.emptyList()); - } else { - List newTypeVarTypes = - new ArrayList<>(underlyingTypeVariables.size()); - for (TypeMirror t : underlyingTypeVariables) { - if (t.getKind() == TypeKind.ERROR) { - // Maybe the input is uncompilable, or maybe the type is not completed - // yet (see Issue #244). - throw new ErrorTypeKindException( - "Problem with type variables of %s.%s: %s [%s %s]", - element, element.getEnclosingElement(), t, t.getKind(), t.getClass()); - } - newTypeVarTypes.add((AnnotatedTypeVariable) createType(t, atypeFactory, true)); - } - setTypeVariables(Collections.unmodifiableList(newTypeVarTypes)); + public void replaceAnnotations(Iterable replAnnos) { + for (AnnotationMirror a : replAnnos) { + this.replaceAnnotation(a); } - } - // No need to copy or wrap; it is an unmodifiable list. - return typeVarTypes; - } - - @Override - public AnnotatedExecutableType deepCopy(boolean copyAnnotations) { - return (AnnotatedExecutableType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } - - @Override - public AnnotatedExecutableType deepCopy() { - return deepCopy(true); - } - - @Override - public AnnotatedExecutableType shallowCopy(boolean copyAnnotations) { - AnnotatedExecutableType type = new AnnotatedExecutableType(getUnderlyingType(), atypeFactory); - - type.setElement(getElement()); - type.setParameterTypes(getParameterTypes()); - if (getVarargType() != null) { - type.setVarargType(getVarargType()); - } else { - type.computeVarargType(); - } - type.setReceiverType(getReceiverType()); - type.setReturnType(getReturnType()); - type.setThrownTypes(getThrownTypes()); - type.setTypeVariables(getTypeVariables()); - - return type; - } - - @Override - public AnnotatedExecutableType shallowCopy() { - return shallowCopy(true); } /** - * Returns the element of this AnnotatedExecutableType. + * Removes a primary annotation from the type. * - * @return the element of this AnnotatedExecutableType + * @param a the annotation to remove + * @return true if the annotation was removed, false if the type's annotations were unchanged */ - public ExecutableElement getElement() { - return element; + // typetools removePrimaryAnnotation + public boolean removeAnnotation(AnnotationMirror a) { + AnnotationMirror anno = AnnotationUtils.getSame(primaryAnnotations, a); + if (anno != null) { + return primaryAnnotations.remove(anno); + } + return false; } /** - * Sets the element of this AnnotatedExecutableType. + * Removes a primary annotation of the given class from the type. * - * @param elem the new element for this AnnotatedExecutableType + * @param a the class of the annotation to remove + * @return true if the annotation was removed, false if the type's annotations were unchanged */ - public void setElement(ExecutableElement elem) { - this.element = elem; - } - - @Override - public AnnotatedExecutableType getErased() { - AnnotatedExecutableType type = - new AnnotatedExecutableType( - (ExecutableType) atypeFactory.types.erasure(getUnderlyingType()), atypeFactory); - type.setElement(getElement()); - type.setParameterTypes(erasureList(getParameterTypes())); - if (getVarargType() != null) { - type.setVarargType(getVarargType().getErased()); - } else { - type.computeVarargType(); - } - if (getReceiverType() != null) { - type.setReceiverType(getReceiverType().getErased()); - } else { - type.setReceiverType(null); - } - type.setReturnType(getReturnType().getErased()); - type.setThrownTypes(erasureList(getThrownTypes())); - - return type; + // typetools: removePrimaryAnnotationByClass + public boolean removeAnnotationByClass(Class a) { + AnnotationMirror anno = atypeFactory.getAnnotationByClass(primaryAnnotations, a); + if (anno != null) { + return this.removeAnnotation(anno); + } + return false; } /** - * Returns the erased types corresponding to the given types. + * Remove any primary annotation that is in the same qualifier hierarchy as the parameter. * - * @param lst annotated type mirrors - * @return erased annotated type mirrors in an unmodifiable list + * @param a an annotation from the same qualifier hierarchy + * @return if an annotation was removed */ - private List erasureList(List lst) { - if (lst.isEmpty()) { - return Collections.emptyList(); - } else { - return Collections.unmodifiableList( - CollectionsPlume.mapList(AnnotatedTypeMirror::getErased, lst)); - } - } - } - - /** - * Represents Array types in java. A multidimensional array type is represented as an array type - * whose component type is also an array type. - */ - public static class AnnotatedArrayType extends AnnotatedTypeMirror { - - private AnnotatedArrayType(ArrayType type, AnnotatedTypeFactory factory) { - super(type, factory); - } - - /** The component type of this array type. */ - /*package-private*/ @MonotonicNonNull AnnotatedTypeMirror componentType; - - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitArray(this, p); - } - - @Override - public ArrayType getUnderlyingType() { - return (ArrayType) this.underlyingType; + // typetools removePrimaryAnnotationInHierarchy + public boolean removeAnnotationInHierarchy(AnnotationMirror a) { + AnnotationMirror prev = this.getAnnotationInHierarchy(a); + if (prev != null) { + return this.removeAnnotation(prev); + } + return false; } /** - * Sets the component type of this array. + * Remove an annotation that is in the same qualifier hierarchy as the parameter, unless it's + * the top annotation. * - * @param type the component type + * @param a an annotation from the same qualifier hierarchy + * @return if an annotation was removed + * @deprecated This will be removed in a future release */ - public void setComponentType(AnnotatedTypeMirror type) { - this.componentType = type; + @Deprecated // 2023-06-15 + public boolean removeNonTopAnnotationInHierarchy(AnnotationMirror a) { + AnnotationMirror prev = this.getAnnotationInHierarchy(a); + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + if (prev != null && !prev.equals(qualHierarchy.getTopAnnotation(a))) { + return this.removeAnnotation(prev); + } + return false; } /** - * Returns the component type of this array. + * Removes multiple primary annotations from the type. * - * @return the component type of this array + * @param annotations the annotations to remove + * @return true if at least one annotation was removed, false if the type's annotations were + * unchanged */ - public AnnotatedTypeMirror getComponentType() { - if (componentType == null) { // lazy init - setComponentType( - createType(((ArrayType) underlyingType).getComponentType(), atypeFactory, false)); - } - return componentType; - } - - @Override - public AnnotatedArrayType deepCopy(boolean copyAnnotations) { - return (AnnotatedArrayType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } - - @Override - public AnnotatedArrayType deepCopy() { - return deepCopy(true); - } - - @Override - public AnnotatedArrayType shallowCopy(boolean copyAnnotations) { - AnnotatedArrayType type = new AnnotatedArrayType((ArrayType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.setComponentType(getComponentType()); - return type; - } - - @Override - public AnnotatedArrayType shallowCopy() { - return shallowCopy(true); - } - - @Override - public AnnotatedArrayType getErased() { - // IMPORTANT NOTE: The returned type is a fresh Object because - // the componentType is the only component of arrays and the - // call to getErased will return a fresh object. - // | T[ ] | = |T| [ ] - AnnotatedArrayType at = shallowCopy(); - AnnotatedTypeMirror ct = at.getComponentType().getErased(); - at.setComponentType(ct); - return at; - } - } - - /** - * Throw an exception if the boundType is null or a declaration. - * - * @param boundDescription the variety of bound: "Lower", "Super", or "Extends" - * @param boundType the type being tested - * @param thisType the object for which boundType is a bound - */ - private static void checkBound( - String boundDescription, AnnotatedTypeMirror boundType, AnnotatedTypeMirror thisType) { - if (boundType == null || boundType.isDeclaration()) { - throw new BugInCF( - "%s bounds should never be null or a declaration.%n new bound = %s%n type =" + " %s", - boundDescription, boundType, thisType); - } - } - - /** - * Represents a type variable. A type variable may be explicitly declared by a type parameter of a - * type, method, or constructor. A type variable may also be declared implicitly, as by the - * capture conversion of a wildcard type argument (see chapter 5 of The Java Language - * Specification, Third Edition). - */ - public static class AnnotatedTypeVariable extends AnnotatedTypeMirror { - - private AnnotatedTypeVariable( - TypeVariable type, AnnotatedTypeFactory atypeFactory, boolean declaration) { - super(type, atypeFactory); - this.declaration = declaration; + // typetools: removePrimaryAnnotations + public boolean removeAnnotations(Iterable annotations) { + boolean changed = false; + for (AnnotationMirror a : annotations) { + changed |= this.removeAnnotation(a); + } + return changed; } - /** The lower bound of the type variable. */ - private AnnotatedTypeMirror lowerBound; - - /** The upper bound of the type variable. */ - private AnnotatedTypeMirror upperBound; - - private boolean declaration; - - @Override - public boolean isDeclaration() { - return declaration; + /** Removes all primary annotations on this type. */ + // typetools: clearPrimaryAnnotations + public void clearAnnotations() { + primaryAnnotations.clear(); } + @SideEffectFree @Override - public void addAnnotation(AnnotationMirror annotation) { - super.addAnnotation(annotation); - fixupBoundAnnotations(); + public final String toString() { + return atypeFactory.getAnnotatedTypeFormatter().format(this); } - @Override - public boolean removeAnnotation(AnnotationMirror a) { - boolean ret = super.removeAnnotation(a); - if (lowerBound != null) { - ret |= lowerBound.removeAnnotation(a); - } - if (upperBound != null) { - ret |= upperBound.removeAnnotation(a); - } - return ret; + @SideEffectFree + public final String toString(boolean verbose) { + return atypeFactory.getAnnotatedTypeFormatter().format(this, verbose); } /** - * Change whether this {@code AnnotatedTypeVariable} is considered a use or a declaration (use - * this method with caution). + * Returns the erasure type of this type, according to JLS specifications. * - * @param declaration true if this type variable should be considered a declaration + * @see https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.6 + * @return the erasure of this AnnotatedTypeMirror, this is always a copy even if the erasure + * and the original type are equivalent */ - public void setDeclaration(boolean declaration) { - this.declaration = declaration; - } - - @Override - public AnnotatedTypeVariable asUse() { - if (!this.isDeclaration()) { - return this; - } - - AnnotatedTypeVariable result = this.shallowCopy(); - result.declaration = false; - Map mapping = new HashMap<>(1); - mapping.put(getUnderlyingType(), result); - AnnotatedTypeMirror upperBound = - atypeFactory - .getTypeVarSubstitutor() - .substituteWithoutCopyingTypeArguments(mapping, result.getUpperBound()); - result.setUpperBound(upperBound); - - return result; - } - - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitTypeVariable(this, p); - } - - @Override - public TypeVariable getUnderlyingType() { - return (TypeVariable) this.underlyingType; + public AnnotatedTypeMirror getErased() { + return deepCopy(); } /** - * Set the lower bound of this variable type. + * Returns a deep copy of this type. A deep copy implies that each component type is copied + * recursively and the returned type refers to those copies in its component locations. + * + *

          Note: deepCopy provides two important properties in the returned copy: * - *

          Returns the lower bound of this type variable. While a type parameter cannot include an - * explicit lower bound declaration, capture conversion can produce a type variable with a - * non-trivial lower bound. Type variables otherwise have a lower bound of NullType. + *

            + *
          1. Structure preservation -- The exact structure of the original AnnotatedTypeMirror is + * preserved in the copy including all component types. + *
          2. Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror + * and its components have been copied to the new type. + *
          * - * @param type the lower bound type + * If copyAnnotations is set to false, the second property, Annotation preservation, is removed. + * This is useful for cases in which the user may want to copy the structure of a type exactly + * but NOT its annotations. + * + * @return a deep copy */ - public void setLowerBound(AnnotatedTypeMirror type) { - checkBound("Lower", type, this); - this.lowerBound = type; - fixupBoundAnnotations(); - } + public abstract AnnotatedTypeMirror deepCopy(boolean copyAnnotations); /** - * Get the lower bound field directly, bypassing any lazy initialization. This method is - * necessary to prevent infinite recursions in initialization. In general, prefer getLowerBound. + * Returns a deep copy of this type with annotations. * - * @return the lower bound field + *

          Each subclass implements this method with the subclass return type. The method body must + * always be a call to deepCopy(true). + * + * @return a deep copy of this type with annotations + * @see #deepCopy(boolean) */ - public AnnotatedTypeMirror getLowerBoundField() { - return lowerBound; - } + @Override + public abstract AnnotatedTypeMirror deepCopy(); /** - * Returns the lower bound type of this type variable. + * Returns a shallow copy of this type. A shallow copy implies that each component type in the + * output copy refers to the same object as the object being copied. * - * @return the lower bound type of this type variable + * @param copyAnnotations whether copy should have annotations, i.e. whether field {@code + * annotations} should be copied. */ - public AnnotatedTypeMirror getLowerBound() { - if (lowerBound == null) { // lazy init - BoundsInitializer.initializeBounds(this); - fixupBoundAnnotations(); - } - return lowerBound; - } - - // If the lower bound was not present in underlyingType, then its annotation was defaulted - // from the AnnotatedTypeFactory. If the lower bound annotation is a supertype of the upper - // bound annotation, then the type is ill-formed. In that case, change the defaulted lower - // bound to be consistent with the explicitly-written upper bound. - // - // As a concrete example, if the default annotation is @Nullable, then the type "X extends - // @NonNull Y" should not be converted into "X extends @NonNull Y super @Nullable - // bottomtype" but be converted into "X extends @NonNull Y super @NonNull bottomtype". - // - // In addition, ensure consistency of annotations on type variables - // and the upper bound. Assume class C. - // The type of "@Nullable X" has to be "@Nullable X extends @Nullable Object", - // because otherwise the annotations are inconsistent. - private void fixupBoundAnnotations() { - if (!this.getAnnotationsField().isEmpty()) { - AnnotationMirrorSet newAnnos = this.getAnnotationsField(); - if (upperBound != null) { - upperBound.replaceAnnotations(newAnnos); - } - - // Note: - // if the lower bound is a type variable then when we place annotations on the - // primary annotation this will actually cause the type variable to be exact and - // propagate the primary annotation to the type variable because primary annotations - // overwrite the upper and lower bounds of type variables when - // getUpperBound/getLowerBound is called. - if (lowerBound != null) { - lowerBound.replaceAnnotations(newAnnos); - } - } - } + public abstract AnnotatedTypeMirror shallowCopy(boolean copyAnnotations); /** - * Set the upper bound of this variable type. + * Returns a shallow copy of this type with annotations. * - * @param type the upper bound type + *

          Each subclass implements this method with the subclass return type. The method body must + * always be a call to shallowCopy(true). + * + * @see #shallowCopy(boolean) + * @return a shallow copy of this type with annotations */ - public void setUpperBound(AnnotatedTypeMirror type) { - checkBound("Upper", type, this); - this.upperBound = type; - fixupBoundAnnotations(); - } + public abstract AnnotatedTypeMirror shallowCopy(); /** - * Get the upper bound field directly, bypassing any lazy initialization. This method is - * necessary to prevent infinite recursions in initialization. In general, prefer getUpperBound. + * Whether this contains any captured type variables. * - * @return the upper bound field + * @return whether the {@code type} contains any captured type variables */ - public AnnotatedTypeMirror getUpperBoundField() { - return upperBound; + public boolean containsCapturedTypes() { + return atypeFactory.containsCapturedTypes(this); } /** - * Get the upper bound of the type variable, possibly lazily initializing it. Attention: If the - * upper bound is lazily initialized, it will not contain any annotations! Callers of the method - * have to make sure that an AnnotatedTypeFactory first processed the bound. + * Create an {@link AnnotatedDeclaredType} with the underlying type of {@link Object}. It + * includes any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. * - * @return the upper bound type of this type variable + * @param atypeFactory type factory to use + * @return AnnotatedDeclaredType for Object */ - public AnnotatedTypeMirror getUpperBound() { - if (upperBound == null) { // lazy init - BoundsInitializer.initializeBounds(this); - fixupBoundAnnotations(); - } - return upperBound; + protected static AnnotatedDeclaredType createTypeOfObject(AnnotatedTypeFactory atypeFactory) { + AnnotatedDeclaredType objectType = + atypeFactory.fromElement( + atypeFactory.elements.getTypeElement(Object.class.getCanonicalName())); + objectType.declaration = false; + return objectType; } - public AnnotatedTypeParameterBounds getBounds() { - return new AnnotatedTypeParameterBounds(getUpperBound(), getLowerBound()); + /** + * Create an {@link AnnotatedDeclaredType} with the underlying type of {@code java.lang.Record}. + * It includes any annotations placed by {@link AnnotatedTypeFactory#fromElement(Element)}. + * + * @param atypeFactory type factory to use + * @return AnnotatedDeclaredType for Record + */ + protected static AnnotatedDeclaredType createTypeOfRecord(AnnotatedTypeFactory atypeFactory) { + AnnotatedDeclaredType recordType = + atypeFactory.fromElement(atypeFactory.elements.getTypeElement("java.lang.Record")); + recordType.declaration = false; + return recordType; } - public AnnotatedTypeParameterBounds getBoundFields() { - return new AnnotatedTypeParameterBounds(getUpperBoundField(), getLowerBoundField()); - } + /** + * Returns the result of calling {@code underlyingType.toString().hashcode()}. This method saves + * the result in a field so that it isn't recomputed each time. + * + * @return the result of calling {@code underlyingType.toString().hashcode()} + */ + public int getUnderlyingTypeHashCode() { + if (underlyingTypeHashCode == -1) { + underlyingTypeHashCode = underlyingType.toString().hashCode(); + } + return underlyingTypeHashCode; + } + + /** Represents a declared type (whether class or interface). */ + public static class AnnotatedDeclaredType extends AnnotatedTypeMirror { + + /** Parametrized Type Arguments. */ + protected @MonotonicNonNull List typeArgs; + + /** + * Whether the type was initially raw, i.e. the user did not provide the type arguments. + * typeArgs will contain inferred type arguments, which might be too conservative at the + * moment. + * + *

          Ideally, the field would be final. However, when we determine the supertype of a raw + * type, we need to set isUnderlyingTypeRaw for the supertype. + */ + private boolean isUnderlyingTypeRaw; + + /** The enclosing type. May be null. May be changed. */ + protected @Nullable AnnotatedDeclaredType enclosingType; + + /** True if this represents a declaration, rather than a use, of a type. */ + private boolean declaration; + + /** + * Constructor for this type. The result contains no annotations. + * + * @param type underlying kind of this type + * @param atypeFactory the AnnotatedTypeFactory used to create this type + */ + private AnnotatedDeclaredType( + DeclaredType type, AnnotatedTypeFactory atypeFactory, boolean declaration) { + super(type, atypeFactory); + TypeElement typeelem = (TypeElement) type.asElement(); + DeclaredType declty = (DeclaredType) typeelem.asType(); + isUnderlyingTypeRaw = + !declty.getTypeArguments().isEmpty() && type.getTypeArguments().isEmpty(); + + TypeMirror encl = type.getEnclosingType(); + if (encl.getKind() == TypeKind.DECLARED) { + this.enclosingType = + (AnnotatedDeclaredType) createType(encl, atypeFactory, declaration); + } else if (encl.getKind() == TypeKind.NONE) { + this.enclosingType = null; + } else { + throw new BugInCF( + "AnnotatedDeclaredType: unsupported enclosing type: " + + type.getEnclosingType() + + " (" + + encl.getKind() + + ")"); + } - @Override - public AnnotatedTypeVariable deepCopy(boolean copyAnnotations) { - return (AnnotatedTypeVariable) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + this.declaration = declaration; + } - @Override - public AnnotatedTypeVariable deepCopy() { - return deepCopy(true); - } + @Override + public boolean isDeclaration() { + return declaration; + } - @Override - public AnnotatedTypeVariable shallowCopy(boolean copyAnnotations) { - // Because type variables can refer to themselves, they can't be shallow copied, so - // return a deep copy instead. - AnnotatedTypeVariable type = deepCopy(true); - if (!copyAnnotations) { - type.getAnnotationsField().clear(); - } - return type; - } + @Override + public AnnotatedDeclaredType deepCopy(boolean copyAnnotations) { + return (AnnotatedDeclaredType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - @Override - public AnnotatedTypeVariable shallowCopy() { - return shallowCopy(true); + @Override + public AnnotatedDeclaredType deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedDeclaredType asUse() { + if (!this.isDeclaration()) { + return this; + } + AnnotatedDeclaredType result = this.shallowCopy(true); + result.declaration = false; + if (this.enclosingType != null) { + result.enclosingType = this.enclosingType.asUse(); + } + // setTypeArguments calls asUse on all the new type arguments. + result.setTypeArguments(typeArgs); + + // If "this" is a type declaration with a type variable that references itself, e.g. + // MyClass>, then the type variable is a declaration, i.e. the first + // T, but the reference to the type variable is a use, i.e. the second T. When "this" + // is converted to a use, then both type variables are uses and should be the same + // object. + // The code below does this. + Map mapping = new HashMap<>(typeArgs.size()); + for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { + AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; + mapping.put(typeVar.getUnderlyingType(), typeVar); + } + for (AnnotatedTypeMirror typeArg : result.getTypeArguments()) { + AnnotatedTypeVariable typeVar = (AnnotatedTypeVariable) typeArg; + AnnotatedTypeMirror upperBound = + atypeFactory + .getTypeVarSubstitutor() + .substituteWithoutCopyingTypeArguments( + mapping, typeVar.getUpperBound()); + typeVar.setUpperBound(upperBound); + } + + return result; + } + + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitDeclared(this, p); + } + + /** + * Sets the type arguments on this type. + * + * @param ts a list of type arguments to be captured by this method + */ + public void setTypeArguments(List ts) { + if (ts == null || ts.isEmpty()) { + typeArgs = Collections.emptyList(); + } else if (isDeclaration()) { + for (AnnotatedTypeMirror typeArg : ts) { + if (typeArg.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF( + "Type declaration must have type variables as type arguments. Found %s", + typeArg); + } + if (!typeArg.isDeclaration()) { + throw new BugInCF( + "Type declarations must have type variables that are declarations. Found %s", + typeArg); + } + } + typeArgs = Collections.unmodifiableList(ts); + } else { + List uses = + CollectionsPlume.mapList(AnnotatedTypeMirror::asUse, ts); + typeArgs = Collections.unmodifiableList(uses); + } + } + + /** + * Returns the type arguments for this type. + * + * @return the type arguments for this type + */ + public List getTypeArguments() { + if (typeArgs != null) { + return typeArgs; + } + + DeclaredType t = getUnderlyingType(); + typeArgs = new ArrayList<>(t.getTypeArguments().size()); + + if (isUnderlyingTypeRaw()) { + TypeElement typeElement = (TypeElement) atypeFactory.types.asElement(t); + Map typeParameterToWildcard = new HashMap<>(); + for (TypeParameterElement typeParameterEle : typeElement.getTypeParameters()) { + TypeVariable typeParameterVar = (TypeVariable) typeParameterEle.asType(); + TypeMirror wildcard = + BoundsInitializer.getUpperBoundAsWildcard( + typeParameterVar, atypeFactory.types); + AnnotatedWildcardType atmWild = + (AnnotatedWildcardType) + AnnotatedTypeMirror.createType(wildcard, atypeFactory, false); + atmWild.setTypeArgOfRawType(); + BoundsInitializer.initializeBounds(atmWild); + typeArgs.add(atmWild); + typeParameterToWildcard.put(typeParameterVar, atmWild); + } + TypeVariableSubstitutor suber = atypeFactory.getTypeVarSubstitutor(); + for (AnnotatedTypeMirror atm : typeArgs) { + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atm; + wildcardType.setExtendsBound( + suber.substituteWithoutCopyingTypeArguments( + typeParameterToWildcard, wildcardType.getExtendsBound())); + } + } else if (isDeclaration()) { + for (TypeMirror javaTypeArg : t.getTypeArguments()) { + AnnotatedTypeVariable tv = + (AnnotatedTypeVariable) + AnnotatedTypeMirror.createType(javaTypeArg, atypeFactory, true); + typeArgs.add(tv); + } + } else { + for (TypeMirror javaTypeArg : t.getTypeArguments()) { + AnnotatedTypeMirror typeArg = + AnnotatedTypeMirror.createType(javaTypeArg, atypeFactory, false); + typeArgs.add(typeArg); + } + } + return typeArgs; + } + + /** + * Returns true if the underlying type is raw. The receiver of this method is not raw, + * however; its annotated type arguments have been inferred. + * + * @return true iff the type was raw + */ + public boolean isUnderlyingTypeRaw() { + return isUnderlyingTypeRaw; + } + + /** + * Set the isUnderlyingTypeRaw flag to true. This should only be necessary when determining + * the supertypes of a raw type. + */ + protected void setIsUnderlyingTypeRaw() { + this.isUnderlyingTypeRaw = true; + } + + @Override + public DeclaredType getUnderlyingType() { + return (DeclaredType) underlyingType; + } + + @Override + public List directSupertypes() { + return Collections.unmodifiableList(SupertypeFinder.directSupertypes(this)); + } + + @Override + public AnnotatedDeclaredType shallowCopy() { + return shallowCopy(true); + } + + @Override + public AnnotatedDeclaredType shallowCopy(boolean copyAnnotations) { + AnnotatedDeclaredType type = + new AnnotatedDeclaredType(getUnderlyingType(), atypeFactory, declaration); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.setEnclosingType(getEnclosingType()); + type.setTypeArguments(getTypeArguments()); + return type; + } + + /** + * Return the declared type with its type arguments removed. This also replaces the + * underlying type with its erasure. + * + * @return a fresh copy of the declared type with no type arguments + */ + @Override + public AnnotatedDeclaredType getErased() { + AnnotatedDeclaredType erased = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + atypeFactory.types.erasure(underlyingType), + atypeFactory, + false); + erased.addAnnotations(this.getAnnotations()); + AnnotatedDeclaredType erasedEnclosing = erased.getEnclosingType(); + AnnotatedDeclaredType thisEnclosing = this.getEnclosingType(); + while (erasedEnclosing != null) { + erasedEnclosing.addAnnotations(thisEnclosing.getAnnotations()); + erasedEnclosing = erasedEnclosing.getEnclosingType(); + thisEnclosing = thisEnclosing.getEnclosingType(); + } + return erased; + } + + /** + * Sets the enclosing type. + * + * @param enclosingType the new enclosing type + */ + public void setEnclosingType(@Nullable AnnotatedDeclaredType enclosingType) { + this.enclosingType = enclosingType; + } + + /** + * Returns the enclosing type, as in the type of {@code A} in the type {@code A.B}. May + * return null. + * + * @return enclosingType the enclosing type, or null if this is a top-level type + */ + public @Nullable AnnotatedDeclaredType getEnclosingType() { + return enclosingType; + } } /** - * This method will traverse the upper bound of this type variable calling getErased until it - * finds the concrete upper bound. e.g. - * - *

          {@code  , T extends S, S extends List>}
          - * - * A call to getErased will return the type List - * - * @return the erasure of the upper bound of this type - *

          IMPORTANT NOTE: getErased should always return a FRESH object. This will occur for - * type variables if all other getErased methods are implemented appropriately. Therefore, - * to avoid extra copy calls, this method will not call deepCopy on getUpperBound + * Represents a type of an executable. An executable is a method, constructor, or initializer. */ - @Override - public AnnotatedTypeMirror getErased() { - // |T extends A&B| = |A| - return this.getUpperBound().getErased(); - } - } - - /** - * A pseudo-type used where no actual type is appropriate. The kinds of NoType are: - * - *

            - *
          • VOID -- corresponds to the keyword void. - *
          • PACKAGE -- the pseudo-type of a package element. - *
          • NONE -- used in other cases where no actual type is appropriate; for example, the - * superclass of java.lang.Object. - *
          - */ - public static class AnnotatedNoType extends AnnotatedTypeMirror { - - private AnnotatedNoType(NoType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + public static class AnnotatedExecutableType extends AnnotatedTypeMirror { - // No need for methods - // Might like to override annotate(), include(), execlude() - // AS NoType does not accept any annotations + /** The element of the method. */ + /*package-private*/ @MonotonicNonNull ExecutableElement element; - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitNoType(this, p); - } + /** + * Creates an {@link AnnotatedExecutableType}. + * + * @param type the Java type + * @param factory the factory + */ + private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factory) { + super(type, factory); + } - @Override - public NoType getUnderlyingType() { - return (NoType) this.underlyingType; - } + /** The parameter types; an unmodifiable list. */ + /*package-private*/ @MonotonicNonNull List paramTypes = null; - @Override - public AnnotatedNoType deepCopy(boolean copyAnnotations) { - return (AnnotatedNoType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + /** Whether {@link #paramTypes} has been computed. */ + private boolean paramTypesComputed = false; - @Override - public AnnotatedNoType deepCopy() { - return deepCopy(true); - } + /** + * The receiver type of this executable type; null for static methods and constructors of + * top-level classes. + */ + /*package-private*/ @Nullable AnnotatedDeclaredType receiverType; - @Override - public AnnotatedNoType shallowCopy(boolean copyAnnotations) { - AnnotatedNoType type = new AnnotatedNoType((NoType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - return type; - } + /** + * The varargs type is the last element of {@link #paramTypes} if the method or constructor + * accepts a variable number of arguments and the {@link #paramTypes} has not been expanded + * yet. This type needs to be stored in the field to avoid being affected by calling {@link + * AnnotatedTypes#adaptParameters(AnnotatedTypeFactory, + * AnnotatedTypeMirror.AnnotatedExecutableType, List, com.sun.source.tree.NewClassTree)}. + */ + private @MonotonicNonNull AnnotatedArrayType varargType = null; - @Override - public AnnotatedNoType shallowCopy() { - return shallowCopy(true); - } - } + /** Whether {@link #receiverType} has been computed. */ + private boolean receiverTypeComputed = false; - /** Represents the null type. This is the type of the expression {@code null}. */ - public static class AnnotatedNullType extends AnnotatedTypeMirror { + /** The return type. */ + /*package-private*/ @MonotonicNonNull AnnotatedTypeMirror returnType; - private AnnotatedNullType(NullType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + /** Whether {@link #returnType} has been computed. */ + private boolean returnTypeComputed = false; - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitNull(this, p); - } + /** The thrown types; an unmodifiable list. */ + /*package-private*/ @MonotonicNonNull List thrownTypes; - @Override - public NullType getUnderlyingType() { - return (NullType) this.underlyingType; - } + /** Whether {@link #thrownTypes} has been computed. */ + private boolean thrownTypesComputed = false; - @Override - public AnnotatedNullType deepCopy(boolean copyAnnotations) { - return (AnnotatedNullType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + /** The type variables; an unmodifiable list. */ + /*package-private*/ @MonotonicNonNull List typeVarTypes; - @Override - public AnnotatedNullType deepCopy() { - return deepCopy(true); - } + /** Whether {@link #typeVarTypes} has been computed. */ + private boolean typeVarTypesComputed = false; - @Override - public AnnotatedNullType shallowCopy(boolean copyAnnotations) { - AnnotatedNullType type = new AnnotatedNullType((NullType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - return type; - } + /** + * Returns true if this type represents a varargs method. + * + * @return true if this type represents a varargs method + */ + public boolean isVarArgs() { + return this.element.isVarArgs(); + } - @Override - public AnnotatedNullType shallowCopy() { - return shallowCopy(true); - } - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitExecutable(this, p); + } - /** - * Represents a primitive type. These include {@code boolean}, {@code byte}, {@code short}, {@code - * int}, {@code long}, {@code char}, {@code float}, and {@code double}. - */ - public static class AnnotatedPrimitiveType extends AnnotatedTypeMirror { + @Override + public ExecutableType getUnderlyingType() { + return (ExecutableType) this.underlyingType; + } - private AnnotatedPrimitiveType(PrimitiveType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + /** + * It never makes sense to add annotations to an executable type. Instead, they should be + * added to the appropriate component. + * + * @deprecated add to the appropriate component + */ + @Deprecated // not for removal + @Override + public void addAnnotation(AnnotationMirror annotation) { + assert false : "AnnotatedExecutableType.addAnnotation should never be called"; + } - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitPrimitive(this, p); - } + /** + * Sets the parameter types of this executable type, excluding the receiver.If paramTypes + * has been computed and this type is a varargs method, computes and store {@link + * #varargType} before calling this method, @see {@link #varargType} + * + * @param params an unmodifiable list of parameter types to be captured by this method, + * excluding the receiver + */ + /*package-private*/ void setParameterTypes(List params) { + if (paramTypesComputed && isVarArgs() && varargType == null) { + throw new BugInCF("Set vararg type before resetting parameter types"); + } + paramTypes = params; + paramTypesComputed = true; + } - @Override - public PrimitiveType getUnderlyingType() { - return (PrimitiveType) this.underlyingType; - } + /** + * Returns the parameter types of this executable type, excluding the receiver. + * + * @return the parameter types of this executable type, excluding the receiver + */ + public List getParameterTypes() { + if (!paramTypesComputed) { + assert paramTypes == null; + List underlyingParameterTypes = + ((ExecutableType) underlyingType).getParameterTypes(); + if (underlyingParameterTypes.isEmpty()) { + setParameterTypes(Collections.emptyList()); + } else { + List newParamTypes = + new ArrayList<>(underlyingParameterTypes.size()); + for (TypeMirror t : underlyingParameterTypes) { + if (t.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed + // yet (see Issue #244). + throw new ErrorTypeKindException( + "Problem with parameter type of %s.%s: %s [%s %s]", + element, + element.getEnclosingElement(), + t, + t.getKind(), + t.getClass()); + } + newParamTypes.add(createType(t, atypeFactory, false)); + } + setParameterTypes(Collections.unmodifiableList(newParamTypes)); + } + } + // No need to copy or wrap; it is an unmodifiable list. + return paramTypes; + } - @Override - public AnnotatedPrimitiveType deepCopy(boolean copyAnnotations) { - return (AnnotatedPrimitiveType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + /** + * Sets the vararg type of this executable type. + * + * @param varargType the vararg type of this executable type + */ + /*package-private*/ void setVarargType(@NonNull AnnotatedArrayType varargType) { + this.varargType = varargType; + } - @Override - public AnnotatedPrimitiveType deepCopy() { - return deepCopy(true); - } + /** + * Computes the vararg type of this executable type and stores it in {@link #varargType}. + * + *

          This method computes {@link #varargType} using the {@link #paramTypes} of this + * executable type. To use the {@link #paramTypes} from different executable type, use + * {@link #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. + */ + /*package-private*/ void computeVarargType() { + computeVarargType(paramTypes); + } - @Override - public AnnotatedPrimitiveType shallowCopy(boolean copyAnnotations) { - AnnotatedPrimitiveType type = - new AnnotatedPrimitiveType((PrimitiveType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - return type; - } + /** + * Computes the vararg type using the passed executable type and stores it in this {@link + * #varargType}. + * + * @param annotatedExecutableType an AnnotatedExecutableType + */ + /*package-private*/ void computeVarargType( + AnnotatedExecutableType annotatedExecutableType) { + computeVarargType(annotatedExecutableType.getParameterTypes()); + } - @Override - public AnnotatedPrimitiveType shallowCopy() { - return shallowCopy(true); - } - } - - /** - * Represents a wildcard type argument. Examples include: - * - *

          ? ? extends Number ? super T - * - *

          A wildcard may have its upper bound explicitly set by an extends clause, its lower bound - * explicitly set by a super clause, or neither (but not both). - */ - public static class AnnotatedWildcardType extends AnnotatedTypeMirror { - /** Lower ({@code super}) bound. */ - private AnnotatedTypeMirror superBound; - - /** Upper ({@code extends} bound. */ - private AnnotatedTypeMirror extendsBound; + /** + * Helper function for {@link #computeVarargType()} and {@link + * #computeVarargType(AnnotatedTypeMirror.AnnotatedExecutableType)}. + * + * @param paramTypes the parameter types to determine the vararg type + */ + private void computeVarargType(List paramTypes) { + if (!isVarArgs()) { + return; + } + varargType = (AnnotatedArrayType) paramTypes.get(paramTypes.size() - 1); + } - /** - * Whether this is a type argument for a type whose {@code #underlyingType} is raw. The Checker - * Framework gives raw types wildcard type arguments so that the annotated type can be used as - * if the annotated type was not raw. - */ - private boolean typeArgOfRawType = false; + /** + * Returns the vararg type of this executable type. + * + * @return the vararg type of this executable type + */ + public @Nullable AnnotatedArrayType getVarargType() { + return varargType; + } - /** - * The type variable to which this wildcard is an argument. Used to initialize the upper bound - * of unbounded wildcards and wildcards in raw types. - */ - @SuppressWarnings("nullness") // is reset during initialization - private @NonNull TypeVariable typeVariable = null; + /** + * Sets the return type of this executable type. + * + * @param returnType the new return type + */ + /*package-private*/ void setReturnType(AnnotatedTypeMirror returnType) { + this.returnType = returnType; + returnTypeComputed = true; + } - private AnnotatedWildcardType(WildcardType type, AnnotatedTypeFactory factory) { - super(type, factory); - } + /** + * The return type of a method or constructor. For constructors, the return type is not + * VOID, but the type of the enclosing class. + * + * @return the return type of this executable type + */ + public AnnotatedTypeMirror getReturnType() { + if (!returnTypeComputed) { + assert returnType == null : "returnType = " + returnType; + if (element != null && ((ExecutableType) underlyingType).getReturnType() != null) { + TypeMirror aret = ((ExecutableType) underlyingType).getReturnType(); + if (aret.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed yet + // (see Issue #244). + throw new ErrorTypeKindException( + "Problem with return type of %s.%s: %s [%s %s]", + element, + element.getEnclosingElement(), + aret, + aret.getKind(), + aret.getClass()); + } + if (((MethodSymbol) element).isConstructor()) { + // For constructors, the underlying return type is void. + // Take the type of the enclosing class instead. + aret = element.getEnclosingElement().asType(); + if (aret.getKind() == TypeKind.ERROR) { + throw new ErrorTypeKindException( + "Input is not compilable; problem with constructor %s return type: %s [%s %s]" + + " (enclosing element = %s [%s])", + element, + aret, + aret.getKind(), + aret.getClass(), + element.getEnclosingElement(), + element.getEnclosingElement().getClass()); + } + } + returnType = createType(aret, atypeFactory, false); + } + returnTypeComputed = true; + } + return returnType; + } - @Override - public void addAnnotation(AnnotationMirror annotation) { - super.addAnnotation(annotation); - fixupBoundAnnotations(); - } + /** + * Sets the receiver type on this executable type. + * + * @param receiverType the receiver type + */ + public void setReceiverType(@Nullable AnnotatedDeclaredType receiverType) { + this.receiverType = receiverType; + receiverTypeComputed = true; + } - @Override - public boolean removeAnnotation(AnnotationMirror a) { - boolean ret = super.removeAnnotation(a); - if (superBound != null) { - ret |= superBound.removeAnnotation(a); - } - if (extendsBound != null) { - ret |= extendsBound.removeAnnotation(a); - } - return ret; - } + /** + * Returns the receiver type of this executable type; null for static methods and + * constructors of top-level classes. + * + * @return the receiver type of this executable type; null for static methods and + * constructors of top-level classes + */ + public @Nullable AnnotatedDeclaredType getReceiverType() { + if (!receiverTypeComputed) { + assert receiverType == null; + Element element = getElement(); + if (ElementUtils.hasReceiver(element)) { + // Initial value of `encl`; might be updated. + TypeElement encl = ElementUtils.enclosingTypeElement(element); + if (element.getKind() == ElementKind.CONSTRUCTOR) { + // Can only reach this branch if we're the constructor of a nested class + encl = ElementUtils.enclosingTypeElement(encl.getEnclosingElement()); + } + TypeMirror enclType = encl.asType(); + if (enclType.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed yet + // (see Issue #244). + throw new ErrorTypeKindException( + "Problem with receiver type of %s.%s: %s [%s %s]", + element, + element.getEnclosingElement(), + enclType, + enclType.getKind(), + enclType.getClass()); + } + AnnotatedTypeMirror type = createType(enclType, atypeFactory, false); + assert type instanceof AnnotatedDeclaredType; + receiverType = (AnnotatedDeclaredType) type; + } + receiverTypeComputed = true; + } + return receiverType; + } - /** - * Sets the super bound of this wildcard. - * - * @param type the type of the lower bound - */ - public void setSuperBound(AnnotatedTypeMirror type) { - checkBound("Super", type, this); - this.superBound = type; - fixupBoundAnnotations(); - } + /** + * Sets the thrown types of this executable type. + * + * @param thrownTypes an unmodifiable list of thrown types to be captured by this method + */ + /*package-private*/ void setThrownTypes(List thrownTypes) { + this.thrownTypes = thrownTypes; + thrownTypesComputed = true; + } - public AnnotatedTypeMirror getSuperBoundField() { - return superBound; - } + /** + * Returns the thrown types of this executable type. + * + * @return the thrown types of this executable type + */ + public List getThrownTypes() { + if (!thrownTypesComputed) { + assert thrownTypes == null; + List underlyingThrownTypes = + ((ExecutableType) underlyingType).getThrownTypes(); + if (underlyingThrownTypes.isEmpty()) { + setThrownTypes(Collections.emptyList()); + } else { + List newThrownTypes = + new ArrayList<>(underlyingThrownTypes.size()); + for (TypeMirror t : underlyingThrownTypes) { + if (t.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed + // yet (see Issue #244). + throw new ErrorTypeKindException( + "Problem with thrown type of %s.%s: %s [%s %s]", + element, + element.getEnclosingElement(), + t, + t.getKind(), + t.getClass()); + } + newThrownTypes.add(createType(t, atypeFactory, false)); + } + setThrownTypes(Collections.unmodifiableList(newThrownTypes)); + } + } + // No need to copy or wrap; it is an unmodifiable list. + return thrownTypes; + } - /** - * Returns the lower bound of this wildcard. If no lower bound is explicitly declared, returns - * an {@link AnnotatedNullType}. - * - * @return the lower bound of this wildcard, or an {@link AnnotatedNullType} if none is - * explicitly declared - */ - public AnnotatedTypeMirror getSuperBound() { - if (superBound == null) { - BoundsInitializer.initializeBounds(this); - fixupBoundAnnotations(); - } - return this.superBound; - } + /** + * Sets the type variables associated with this executable type. + * + * @param types an unmodifiable list of type variables of this executable type to be + * captured by this method + */ + /*package-private*/ void setTypeVariables(List types) { + typeVarTypes = types; + typeVarTypesComputed = true; + } - /** - * Sets the upper bound of this wildcard. - * - * @param type the type of the upper bound - */ - public void setExtendsBound(AnnotatedTypeMirror type) { - checkBound("Extends", type, this); - this.extendsBound = type; - fixupBoundAnnotations(); - } + /** + * Returns the type variables of this executable type, if any. + * + * @return the type variables of this executable type, if any + */ + public List getTypeVariables() { + if (!typeVarTypesComputed) { + assert typeVarTypes == null; + List underlyingTypeVariables = + ((ExecutableType) underlyingType).getTypeVariables(); + if (underlyingTypeVariables.isEmpty()) { + setTypeVariables(Collections.emptyList()); + } else { + List newTypeVarTypes = + new ArrayList<>(underlyingTypeVariables.size()); + for (TypeMirror t : underlyingTypeVariables) { + if (t.getKind() == TypeKind.ERROR) { + // Maybe the input is uncompilable, or maybe the type is not completed + // yet (see Issue #244). + throw new ErrorTypeKindException( + "Problem with type variables of %s.%s: %s [%s %s]", + element, + element.getEnclosingElement(), + t, + t.getKind(), + t.getClass()); + } + newTypeVarTypes.add( + (AnnotatedTypeVariable) createType(t, atypeFactory, true)); + } + setTypeVariables(Collections.unmodifiableList(newTypeVarTypes)); + } + } + // No need to copy or wrap; it is an unmodifiable list. + return typeVarTypes; + } - public AnnotatedTypeMirror getExtendsBoundField() { - return extendsBound; - } + @Override + public AnnotatedExecutableType deepCopy(boolean copyAnnotations) { + return (AnnotatedExecutableType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - /** - * Returns the upper bound of this wildcard. If no upper bound is explicitly declared, returns - * the upper bound of the type variable to which the wildcard is bound. - * - * @return the upper bound of this wildcard. If no upper bound is explicitly declared, returns - * the upper bound of the type variable to which the wildcard is bound. - */ - public AnnotatedTypeMirror getExtendsBound() { - if (extendsBound == null) { - BoundsInitializer.initializeBounds(this); - fixupBoundAnnotations(); - } - return this.extendsBound; - } + @Override + public AnnotatedExecutableType deepCopy() { + return deepCopy(true); + } - private void fixupBoundAnnotations() { - if (!this.getAnnotationsField().isEmpty()) { - if (superBound != null) { - superBound.replaceAnnotations(this.getAnnotationsField()); + @Override + public AnnotatedExecutableType shallowCopy(boolean copyAnnotations) { + AnnotatedExecutableType type = + new AnnotatedExecutableType(getUnderlyingType(), atypeFactory); + + type.setElement(getElement()); + type.setParameterTypes(getParameterTypes()); + if (getVarargType() != null) { + type.setVarargType(getVarargType()); + } else { + type.computeVarargType(); + } + type.setReceiverType(getReceiverType()); + type.setReturnType(getReturnType()); + type.setThrownTypes(getThrownTypes()); + type.setTypeVariables(getTypeVariables()); + + return type; } - if (extendsBound != null) { - extendsBound.replaceAnnotations(this.getAnnotationsField()); + + @Override + public AnnotatedExecutableType shallowCopy() { + return shallowCopy(true); + } + + /** + * Returns the element of this AnnotatedExecutableType. + * + * @return the element of this AnnotatedExecutableType + */ + public ExecutableElement getElement() { + return element; + } + + /** + * Sets the element of this AnnotatedExecutableType. + * + * @param elem the new element for this AnnotatedExecutableType + */ + public void setElement(ExecutableElement elem) { + this.element = elem; + } + + @Override + public AnnotatedExecutableType getErased() { + AnnotatedExecutableType type = + new AnnotatedExecutableType( + (ExecutableType) atypeFactory.types.erasure(getUnderlyingType()), + atypeFactory); + type.setElement(getElement()); + type.setParameterTypes(erasureList(getParameterTypes())); + if (getVarargType() != null) { + type.setVarargType(getVarargType().getErased()); + } else { + type.computeVarargType(); + } + if (getReceiverType() != null) { + type.setReceiverType(getReceiverType().getErased()); + } else { + type.setReceiverType(null); + } + type.setReturnType(getReturnType().getErased()); + type.setThrownTypes(erasureList(getThrownTypes())); + + return type; + } + + /** + * Returns the erased types corresponding to the given types. + * + * @param lst annotated type mirrors + * @return erased annotated type mirrors in an unmodifiable list + */ + private List erasureList(List lst) { + if (lst.isEmpty()) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableList( + CollectionsPlume.mapList(AnnotatedTypeMirror::getErased, lst)); + } } - } } /** - * Sets type variable to which this wildcard is an argument. This method should only be called - * during initialization of the type. - * - * @param typeParameterElement the type variable to which this wildcard is an argument + * Represents Array types in java. A multidimensional array type is represented as an array type + * whose component type is also an array type. */ - /*package-private*/ void setTypeVariable(TypeParameterElement typeParameterElement) { - this.typeVariable = (TypeVariable) typeParameterElement.asType(); + public static class AnnotatedArrayType extends AnnotatedTypeMirror { + + private AnnotatedArrayType(ArrayType type, AnnotatedTypeFactory factory) { + super(type, factory); + } + + /** The component type of this array type. */ + /*package-private*/ @MonotonicNonNull AnnotatedTypeMirror componentType; + + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitArray(this, p); + } + + @Override + public ArrayType getUnderlyingType() { + return (ArrayType) this.underlyingType; + } + + /** + * Sets the component type of this array. + * + * @param type the component type + */ + public void setComponentType(AnnotatedTypeMirror type) { + this.componentType = type; + } + + /** + * Returns the component type of this array. + * + * @return the component type of this array + */ + public AnnotatedTypeMirror getComponentType() { + if (componentType == null) { // lazy init + setComponentType( + createType( + ((ArrayType) underlyingType).getComponentType(), + atypeFactory, + false)); + } + return componentType; + } + + @Override + public AnnotatedArrayType deepCopy(boolean copyAnnotations) { + return (AnnotatedArrayType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } + + @Override + public AnnotatedArrayType deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedArrayType shallowCopy(boolean copyAnnotations) { + AnnotatedArrayType type = + new AnnotatedArrayType((ArrayType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.setComponentType(getComponentType()); + return type; + } + + @Override + public AnnotatedArrayType shallowCopy() { + return shallowCopy(true); + } + + @Override + public AnnotatedArrayType getErased() { + // IMPORTANT NOTE: The returned type is a fresh Object because + // the componentType is the only component of arrays and the + // call to getErased will return a fresh object. + // | T[ ] | = |T| [ ] + AnnotatedArrayType at = shallowCopy(); + AnnotatedTypeMirror ct = at.getComponentType().getErased(); + at.setComponentType(ct); + return at; + } } /** - * Sets type variable to which this wildcard is an argument. This method should only be called - * during initialization of the type. + * Throw an exception if the boundType is null or a declaration. * - * @param typeVariable the type variable to which this wildcard is an argument + * @param boundDescription the variety of bound: "Lower", "Super", or "Extends" + * @param boundType the type being tested + * @param thisType the object for which boundType is a bound */ - /*package-private*/ void setTypeVariable(TypeVariable typeVariable) { - this.typeVariable = typeVariable; + private static void checkBound( + String boundDescription, AnnotatedTypeMirror boundType, AnnotatedTypeMirror thisType) { + if (boundType == null || boundType.isDeclaration()) { + throw new BugInCF( + "%s bounds should never be null or a declaration.%n new bound = %s%n type =" + + " %s", + boundDescription, boundType, thisType); + } } /** - * Returns the type variable to which this wildcard is an argument. Used to initialize the upper - * bound of wildcards in raw types. - * - * @return the type variable to which this wildcard is an argument + * Represents a type variable. A type variable may be explicitly declared by a type parameter of + * a type, method, or constructor. A type variable may also be declared implicitly, as by the + * capture conversion of a wildcard type argument (see chapter 5 of The Java Language + * Specification, Third Edition). */ - public TypeVariable getTypeVariable() { - return typeVariable; - } + public static class AnnotatedTypeVariable extends AnnotatedTypeMirror { - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitWildcard(this, p); - } + private AnnotatedTypeVariable( + TypeVariable type, AnnotatedTypeFactory atypeFactory, boolean declaration) { + super(type, atypeFactory); + this.declaration = declaration; + } - @Override - public WildcardType getUnderlyingType() { - return (WildcardType) this.underlyingType; - } + /** The lower bound of the type variable. */ + private AnnotatedTypeMirror lowerBound; - @Override - public AnnotatedWildcardType deepCopy(boolean copyAnnotations) { - return (AnnotatedWildcardType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + /** The upper bound of the type variable. */ + private AnnotatedTypeMirror upperBound; - @Override - public AnnotatedWildcardType deepCopy() { - return deepCopy(true); - } + private boolean declaration; - @Override - public AnnotatedWildcardType shallowCopy(boolean copyAnnotations) { - // Because wildcards can refer to themselves, they can't be shallow copied, so return a - // deep copy instead. - AnnotatedWildcardType type = deepCopy(true); - if (!copyAnnotations) { - type.getAnnotationsField().clear(); - } - return type; - } + @Override + public boolean isDeclaration() { + return declaration; + } - @Override - public AnnotatedWildcardType shallowCopy() { - return shallowCopy(true); - } + @Override + public void addAnnotation(AnnotationMirror annotation) { + super.addAnnotation(annotation); + fixupBoundAnnotations(); + } - /** - * @see - * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable#getErased() - */ - @Override - public AnnotatedTypeMirror getErased() { - // |? extends A&B| = |A| - return getExtendsBound().getErased(); - } + @Override + public boolean removeAnnotation(AnnotationMirror a) { + boolean ret = super.removeAnnotation(a); + if (lowerBound != null) { + ret |= lowerBound.removeAnnotation(a); + } + if (upperBound != null) { + ret |= upperBound.removeAnnotation(a); + } + return ret; + } - /** Set that this wildcard is a type argument of a raw type. */ - public void setTypeArgOfRawType() { - typeArgOfRawType = true; - } + /** + * Change whether this {@code AnnotatedTypeVariable} is considered a use or a declaration + * (use this method with caution). + * + * @param declaration true if this type variable should be considered a declaration + */ + public void setDeclaration(boolean declaration) { + this.declaration = declaration; + } - /** - * Whether this is a type argument to a type whose {@code #underlyingType} is raw. The Checker - * Framework gives raw types wildcard type arguments so that the annotated type can be used as - * if the annotated type was not raw. - * - * @return whether this is a type argument to a type whose {@code #underlyingType} is raw - */ - public boolean isTypeArgOfRawType() { - return typeArgOfRawType; - } - } + @Override + public AnnotatedTypeVariable asUse() { + if (!this.isDeclaration()) { + return this; + } - /** - * Represents an intersection type. - * - *

          For example: {@code MyObject & Serializable & Comparable} - */ - public static class AnnotatedIntersectionType extends AnnotatedTypeMirror { + AnnotatedTypeVariable result = this.shallowCopy(); + result.declaration = false; + Map mapping = new HashMap<>(1); + mapping.put(getUnderlyingType(), result); + AnnotatedTypeMirror upperBound = + atypeFactory + .getTypeVarSubstitutor() + .substituteWithoutCopyingTypeArguments(mapping, result.getUpperBound()); + result.setUpperBound(upperBound); + + return result; + } - /** - * A list of the bounds of this which are also its direct super types. - * - *

          Is set by {@link #shallowCopy}. - */ - protected List bounds; + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitTypeVariable(this, p); + } - /** - * Creates an {@code AnnotatedIntersectionType} with the underlying type {@code type}. The - * result contains no annotations. - * - * @param type underlying kind of this type - * @param atypeFactory the factory used to construct this intersection type - */ - private AnnotatedIntersectionType(IntersectionType type, AnnotatedTypeFactory atypeFactory) { - super(type, atypeFactory); + @Override + public TypeVariable getUnderlyingType() { + return (TypeVariable) this.underlyingType; + } + + /** + * Set the lower bound of this variable type. + * + *

          Returns the lower bound of this type variable. While a type parameter cannot include + * an explicit lower bound declaration, capture conversion can produce a type variable with + * a non-trivial lower bound. Type variables otherwise have a lower bound of NullType. + * + * @param type the lower bound type + */ + public void setLowerBound(AnnotatedTypeMirror type) { + checkBound("Lower", type, this); + this.lowerBound = type; + fixupBoundAnnotations(); + } + + /** + * Get the lower bound field directly, bypassing any lazy initialization. This method is + * necessary to prevent infinite recursions in initialization. In general, prefer + * getLowerBound. + * + * @return the lower bound field + */ + public AnnotatedTypeMirror getLowerBoundField() { + return lowerBound; + } + + /** + * Returns the lower bound type of this type variable. + * + * @return the lower bound type of this type variable + */ + public AnnotatedTypeMirror getLowerBound() { + if (lowerBound == null) { // lazy init + BoundsInitializer.initializeBounds(this); + fixupBoundAnnotations(); + } + return lowerBound; + } + + // If the lower bound was not present in underlyingType, then its annotation was defaulted + // from the AnnotatedTypeFactory. If the lower bound annotation is a supertype of the upper + // bound annotation, then the type is ill-formed. In that case, change the defaulted lower + // bound to be consistent with the explicitly-written upper bound. + // + // As a concrete example, if the default annotation is @Nullable, then the type "X extends + // @NonNull Y" should not be converted into "X extends @NonNull Y super @Nullable + // bottomtype" but be converted into "X extends @NonNull Y super @NonNull bottomtype". + // + // In addition, ensure consistency of annotations on type variables + // and the upper bound. Assume class C. + // The type of "@Nullable X" has to be "@Nullable X extends @Nullable Object", + // because otherwise the annotations are inconsistent. + private void fixupBoundAnnotations() { + if (!this.getAnnotationsField().isEmpty()) { + AnnotationMirrorSet newAnnos = this.getAnnotationsField(); + if (upperBound != null) { + upperBound.replaceAnnotations(newAnnos); + } + + // Note: + // if the lower bound is a type variable then when we place annotations on the + // primary annotation this will actually cause the type variable to be exact and + // propagate the primary annotation to the type variable because primary annotations + // overwrite the upper and lower bounds of type variables when + // getUpperBound/getLowerBound is called. + if (lowerBound != null) { + lowerBound.replaceAnnotations(newAnnos); + } + } + } + + /** + * Set the upper bound of this variable type. + * + * @param type the upper bound type + */ + public void setUpperBound(AnnotatedTypeMirror type) { + checkBound("Upper", type, this); + this.upperBound = type; + fixupBoundAnnotations(); + } + + /** + * Get the upper bound field directly, bypassing any lazy initialization. This method is + * necessary to prevent infinite recursions in initialization. In general, prefer + * getUpperBound. + * + * @return the upper bound field + */ + public AnnotatedTypeMirror getUpperBoundField() { + return upperBound; + } + + /** + * Get the upper bound of the type variable, possibly lazily initializing it. Attention: If + * the upper bound is lazily initialized, it will not contain any annotations! Callers of + * the method have to make sure that an AnnotatedTypeFactory first processed the bound. + * + * @return the upper bound type of this type variable + */ + public AnnotatedTypeMirror getUpperBound() { + if (upperBound == null) { // lazy init + BoundsInitializer.initializeBounds(this); + fixupBoundAnnotations(); + } + return upperBound; + } + + public AnnotatedTypeParameterBounds getBounds() { + return new AnnotatedTypeParameterBounds(getUpperBound(), getLowerBound()); + } + + public AnnotatedTypeParameterBounds getBoundFields() { + return new AnnotatedTypeParameterBounds(getUpperBoundField(), getLowerBoundField()); + } + + @Override + public AnnotatedTypeVariable deepCopy(boolean copyAnnotations) { + return (AnnotatedTypeVariable) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } + + @Override + public AnnotatedTypeVariable deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedTypeVariable shallowCopy(boolean copyAnnotations) { + // Because type variables can refer to themselves, they can't be shallow copied, so + // return a deep copy instead. + AnnotatedTypeVariable type = deepCopy(true); + if (!copyAnnotations) { + type.getAnnotationsField().clear(); + } + return type; + } + + @Override + public AnnotatedTypeVariable shallowCopy() { + return shallowCopy(true); + } + + /** + * This method will traverse the upper bound of this type variable calling getErased until + * it finds the concrete upper bound. e.g. + * + *

          {@code  , T extends S, S extends List>}
          + * + * A call to getErased will return the type List + * + * @return the erasure of the upper bound of this type + *

          IMPORTANT NOTE: getErased should always return a FRESH object. This will occur for + * type variables if all other getErased methods are implemented appropriately. + * Therefore, to avoid extra copy calls, this method will not call deepCopy on + * getUpperBound + */ + @Override + public AnnotatedTypeMirror getErased() { + // |T extends A&B| = |A| + return this.getUpperBound().getErased(); + } } /** - * {@inheritDoc} + * A pseudo-type used where no actual type is appropriate. The kinds of NoType are: * - *

          Also, copies {@code a} to all the bounds. - * - * @param annotation the annotation to add + *

            + *
          • VOID -- corresponds to the keyword void. + *
          • PACKAGE -- the pseudo-type of a package element. + *
          • NONE -- used in other cases where no actual type is appropriate; for example, the + * superclass of java.lang.Object. + *
          */ - @Override - public void addAnnotation(AnnotationMirror annotation) { - super.addAnnotation(annotation); - fixupBoundAnnotations(); - } + public static class AnnotatedNoType extends AnnotatedTypeMirror { - @Override - public boolean removeAnnotation(AnnotationMirror a) { - boolean ret = super.removeAnnotation(a); - if (bounds != null) { - for (AnnotatedTypeMirror bound : bounds) { - ret |= bound.removeAnnotation(a); + private AnnotatedNoType(NoType type, AnnotatedTypeFactory factory) { + super(type, factory); } - } - return ret; - } - /** - * Copies {@link #primaryAnnotations} to all the bounds, replacing any existing annotations in - * the same hierarchy. - */ - private void fixupBoundAnnotations() { - if (!this.getAnnotationsField().isEmpty()) { - AnnotationMirrorSet newAnnos = this.getAnnotationsField(); - if (bounds != null) { - for (AnnotatedTypeMirror bound : bounds) { - bound.replaceAnnotations(newAnnos); - } - } - } - } + // No need for methods + // Might like to override annotate(), include(), execlude() + // AS NoType does not accept any annotations - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitIntersection(this, p); - } + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitNoType(this, p); + } - @Override - public IntersectionType getUnderlyingType() { - return (IntersectionType) super.getUnderlyingType(); - } + @Override + public NoType getUnderlyingType() { + return (NoType) this.underlyingType; + } - @Override - public AnnotatedIntersectionType deepCopy(boolean copyAnnotations) { - return (AnnotatedIntersectionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + @Override + public AnnotatedNoType deepCopy(boolean copyAnnotations) { + return (AnnotatedNoType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } - @Override - public AnnotatedIntersectionType deepCopy() { - return deepCopy(true); - } + @Override + public AnnotatedNoType deepCopy() { + return deepCopy(true); + } - @Override - public AnnotatedIntersectionType shallowCopy(boolean copyAnnotations) { - AnnotatedIntersectionType type = - new AnnotatedIntersectionType((IntersectionType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.bounds = this.bounds; - return type; + @Override + public AnnotatedNoType shallowCopy(boolean copyAnnotations) { + AnnotatedNoType type = new AnnotatedNoType((NoType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + return type; + } + + @Override + public AnnotatedNoType shallowCopy() { + return shallowCopy(true); + } } - @Override - public AnnotatedIntersectionType shallowCopy() { - return shallowCopy(true); + /** Represents the null type. This is the type of the expression {@code null}. */ + public static class AnnotatedNullType extends AnnotatedTypeMirror { + + private AnnotatedNullType(NullType type, AnnotatedTypeFactory factory) { + super(type, factory); + } + + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitNull(this, p); + } + + @Override + public NullType getUnderlyingType() { + return (NullType) this.underlyingType; + } + + @Override + public AnnotatedNullType deepCopy(boolean copyAnnotations) { + return (AnnotatedNullType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } + + @Override + public AnnotatedNullType deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedNullType shallowCopy(boolean copyAnnotations) { + AnnotatedNullType type = new AnnotatedNullType((NullType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + return type; + } + + @Override + public AnnotatedNullType shallowCopy() { + return shallowCopy(true); + } } /** - * {@inheritDoc} - * - *

          This returns the same types as {@link #getBounds()}. - * - * @return the direct super types of this + * Represents a primitive type. These include {@code boolean}, {@code byte}, {@code short}, + * {@code int}, {@code long}, {@code char}, {@code float}, and {@code double}. */ - @Override - public List directSupertypes() { - return getBounds(); + public static class AnnotatedPrimitiveType extends AnnotatedTypeMirror { + + private AnnotatedPrimitiveType(PrimitiveType type, AnnotatedTypeFactory factory) { + super(type, factory); + } + + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitPrimitive(this, p); + } + + @Override + public PrimitiveType getUnderlyingType() { + return (PrimitiveType) this.underlyingType; + } + + @Override + public AnnotatedPrimitiveType deepCopy(boolean copyAnnotations) { + return (AnnotatedPrimitiveType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } + + @Override + public AnnotatedPrimitiveType deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedPrimitiveType shallowCopy(boolean copyAnnotations) { + AnnotatedPrimitiveType type = + new AnnotatedPrimitiveType((PrimitiveType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + return type; + } + + @Override + public AnnotatedPrimitiveType shallowCopy() { + return shallowCopy(true); + } } /** - * This returns the bounds of the intersection type. Although only declared types can appear in - * an explicitly written intersections, during capture conversion, intersections with other - * kinds of types are created. + * Represents a wildcard type argument. Examples include: * - *

          This returns the same types as {@link #directSupertypes()}. + *

          ? ? extends Number ? super T * - * @return the bounds of this, which are also the direct super types of this + *

          A wildcard may have its upper bound explicitly set by an extends clause, its lower bound + * explicitly set by a super clause, or neither (but not both). */ - public List getBounds() { - if (bounds == null) { - List ubounds = ((IntersectionType) underlyingType).getBounds(); - List res = - CollectionsPlume.mapList( - (TypeMirror bnd) -> createType(bnd, atypeFactory, false), ubounds); - bounds = Collections.unmodifiableList(res); - fixupBoundAnnotations(); - } - return bounds; - } + public static class AnnotatedWildcardType extends AnnotatedTypeMirror { + /** Lower ({@code super}) bound. */ + private AnnotatedTypeMirror superBound; - /** - * Sets the bounds. - * - * @param bounds a list of bounds to be captured by this method - */ - public void setBounds(List bounds) { - this.bounds = bounds; + /** Upper ({@code extends} bound. */ + private AnnotatedTypeMirror extendsBound; + + /** + * Whether this is a type argument for a type whose {@code #underlyingType} is raw. The + * Checker Framework gives raw types wildcard type arguments so that the annotated type can + * be used as if the annotated type was not raw. + */ + private boolean typeArgOfRawType = false; + + /** + * The type variable to which this wildcard is an argument. Used to initialize the upper + * bound of unbounded wildcards and wildcards in raw types. + */ + @SuppressWarnings("nullness") // is reset during initialization + private @NonNull TypeVariable typeVariable = null; + + private AnnotatedWildcardType(WildcardType type, AnnotatedTypeFactory factory) { + super(type, factory); + } + + @Override + public void addAnnotation(AnnotationMirror annotation) { + super.addAnnotation(annotation); + fixupBoundAnnotations(); + } + + @Override + public boolean removeAnnotation(AnnotationMirror a) { + boolean ret = super.removeAnnotation(a); + if (superBound != null) { + ret |= superBound.removeAnnotation(a); + } + if (extendsBound != null) { + ret |= extendsBound.removeAnnotation(a); + } + return ret; + } + + /** + * Sets the super bound of this wildcard. + * + * @param type the type of the lower bound + */ + public void setSuperBound(AnnotatedTypeMirror type) { + checkBound("Super", type, this); + this.superBound = type; + fixupBoundAnnotations(); + } + + public AnnotatedTypeMirror getSuperBoundField() { + return superBound; + } + + /** + * Returns the lower bound of this wildcard. If no lower bound is explicitly declared, + * returns an {@link AnnotatedNullType}. + * + * @return the lower bound of this wildcard, or an {@link AnnotatedNullType} if none is + * explicitly declared + */ + public AnnotatedTypeMirror getSuperBound() { + if (superBound == null) { + BoundsInitializer.initializeBounds(this); + fixupBoundAnnotations(); + } + return this.superBound; + } + + /** + * Sets the upper bound of this wildcard. + * + * @param type the type of the upper bound + */ + public void setExtendsBound(AnnotatedTypeMirror type) { + checkBound("Extends", type, this); + this.extendsBound = type; + fixupBoundAnnotations(); + } + + public AnnotatedTypeMirror getExtendsBoundField() { + return extendsBound; + } + + /** + * Returns the upper bound of this wildcard. If no upper bound is explicitly declared, + * returns the upper bound of the type variable to which the wildcard is bound. + * + * @return the upper bound of this wildcard. If no upper bound is explicitly declared, + * returns the upper bound of the type variable to which the wildcard is bound. + */ + public AnnotatedTypeMirror getExtendsBound() { + if (extendsBound == null) { + BoundsInitializer.initializeBounds(this); + fixupBoundAnnotations(); + } + return this.extendsBound; + } + + private void fixupBoundAnnotations() { + if (!this.getAnnotationsField().isEmpty()) { + if (superBound != null) { + superBound.replaceAnnotations(this.getAnnotationsField()); + } + if (extendsBound != null) { + extendsBound.replaceAnnotations(this.getAnnotationsField()); + } + } + } + + /** + * Sets type variable to which this wildcard is an argument. This method should only be + * called during initialization of the type. + * + * @param typeParameterElement the type variable to which this wildcard is an argument + */ + /*package-private*/ void setTypeVariable(TypeParameterElement typeParameterElement) { + this.typeVariable = (TypeVariable) typeParameterElement.asType(); + } + + /** + * Sets type variable to which this wildcard is an argument. This method should only be + * called during initialization of the type. + * + * @param typeVariable the type variable to which this wildcard is an argument + */ + /*package-private*/ void setTypeVariable(TypeVariable typeVariable) { + this.typeVariable = typeVariable; + } + + /** + * Returns the type variable to which this wildcard is an argument. Used to initialize the + * upper bound of wildcards in raw types. + * + * @return the type variable to which this wildcard is an argument + */ + public TypeVariable getTypeVariable() { + return typeVariable; + } + + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitWildcard(this, p); + } + + @Override + public WildcardType getUnderlyingType() { + return (WildcardType) this.underlyingType; + } + + @Override + public AnnotatedWildcardType deepCopy(boolean copyAnnotations) { + return (AnnotatedWildcardType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } + + @Override + public AnnotatedWildcardType deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedWildcardType shallowCopy(boolean copyAnnotations) { + // Because wildcards can refer to themselves, they can't be shallow copied, so return a + // deep copy instead. + AnnotatedWildcardType type = deepCopy(true); + if (!copyAnnotations) { + type.getAnnotationsField().clear(); + } + return type; + } + + @Override + public AnnotatedWildcardType shallowCopy() { + return shallowCopy(true); + } + + /** + * @see + * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable#getErased() + */ + @Override + public AnnotatedTypeMirror getErased() { + // |? extends A&B| = |A| + return getExtendsBound().getErased(); + } + + /** Set that this wildcard is a type argument of a raw type. */ + public void setTypeArgOfRawType() { + typeArgOfRawType = true; + } + + /** + * Whether this is a type argument to a type whose {@code #underlyingType} is raw. The + * Checker Framework gives raw types wildcard type arguments so that the annotated type can + * be used as if the annotated type was not raw. + * + * @return whether this is a type argument to a type whose {@code #underlyingType} is raw + */ + public boolean isTypeArgOfRawType() { + return typeArgOfRawType; + } } /** - * Copy the first annotation (in each hierarchy) on a bound to the primary annotation location - * of the intersection type. + * Represents an intersection type. * - *

          For example, in the type {@code @NonNull Object & @Initialized @Nullable Serializable}, - * {@code @Nullable} and {@code @Initialized} are copied to the primary annotation location. + *

          For example: {@code MyObject & Serializable & Comparable} */ - public void copyIntersectionBoundAnnotations() { - AnnotationMirrorSet annos = new AnnotationMirrorSet(); - for (AnnotatedTypeMirror bound : getBounds()) { - for (AnnotationMirror a : bound.getAnnotations()) { - if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, a) - == null) { - annos.add(a); - } - } - } - addAnnotations(annos); - } - } + public static class AnnotatedIntersectionType extends AnnotatedTypeMirror { - // TODO: Ensure union types are handled everywhere. - // TODO: Should field "annotations" contain anything? - public static class AnnotatedUnionType extends AnnotatedTypeMirror { + /** + * A list of the bounds of this which are also its direct super types. + * + *

          Is set by {@link #shallowCopy}. + */ + protected List bounds; - /** - * Creates a new AnnotatedUnionType. - * - * @param type underlying kind of this type - * @param atypeFactory type factory - */ - private AnnotatedUnionType(UnionType type, AnnotatedTypeFactory atypeFactory) { - super(type, atypeFactory); - } + /** + * Creates an {@code AnnotatedIntersectionType} with the underlying type {@code type}. The + * result contains no annotations. + * + * @param type underlying kind of this type + * @param atypeFactory the factory used to construct this intersection type + */ + private AnnotatedIntersectionType( + IntersectionType type, AnnotatedTypeFactory atypeFactory) { + super(type, atypeFactory); + } - @Override - public R accept(AnnotatedTypeVisitor v, P p) { - return v.visitUnion(this, p); - } + /** + * {@inheritDoc} + * + *

          Also, copies {@code a} to all the bounds. + * + * @param annotation the annotation to add + */ + @Override + public void addAnnotation(AnnotationMirror annotation) { + super.addAnnotation(annotation); + fixupBoundAnnotations(); + } - @Override - public AnnotatedUnionType deepCopy(boolean copyAnnotations) { - return (AnnotatedUnionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); - } + @Override + public boolean removeAnnotation(AnnotationMirror a) { + boolean ret = super.removeAnnotation(a); + if (bounds != null) { + for (AnnotatedTypeMirror bound : bounds) { + ret |= bound.removeAnnotation(a); + } + } + return ret; + } - @Override - public AnnotatedUnionType deepCopy() { - return deepCopy(true); - } + /** + * Copies {@link #primaryAnnotations} to all the bounds, replacing any existing annotations + * in the same hierarchy. + */ + private void fixupBoundAnnotations() { + if (!this.getAnnotationsField().isEmpty()) { + AnnotationMirrorSet newAnnos = this.getAnnotationsField(); + if (bounds != null) { + for (AnnotatedTypeMirror bound : bounds) { + bound.replaceAnnotations(newAnnos); + } + } + } + } - @Override - public AnnotatedUnionType shallowCopy(boolean copyAnnotations) { - AnnotatedUnionType type = new AnnotatedUnionType((UnionType) underlyingType, atypeFactory); - if (copyAnnotations) { - type.addAnnotations(this.getAnnotationsField()); - } - type.alternatives = this.alternatives; - return type; + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitIntersection(this, p); + } + + @Override + public IntersectionType getUnderlyingType() { + return (IntersectionType) super.getUnderlyingType(); + } + + @Override + public AnnotatedIntersectionType deepCopy(boolean copyAnnotations) { + return (AnnotatedIntersectionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } + + @Override + public AnnotatedIntersectionType deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedIntersectionType shallowCopy(boolean copyAnnotations) { + AnnotatedIntersectionType type = + new AnnotatedIntersectionType((IntersectionType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.bounds = this.bounds; + return type; + } + + @Override + public AnnotatedIntersectionType shallowCopy() { + return shallowCopy(true); + } + + /** + * {@inheritDoc} + * + *

          This returns the same types as {@link #getBounds()}. + * + * @return the direct super types of this + */ + @Override + public List directSupertypes() { + return getBounds(); + } + + /** + * This returns the bounds of the intersection type. Although only declared types can appear + * in an explicitly written intersections, during capture conversion, intersections with + * other kinds of types are created. + * + *

          This returns the same types as {@link #directSupertypes()}. + * + * @return the bounds of this, which are also the direct super types of this + */ + public List getBounds() { + if (bounds == null) { + List ubounds = + ((IntersectionType) underlyingType).getBounds(); + List res = + CollectionsPlume.mapList( + (TypeMirror bnd) -> createType(bnd, atypeFactory, false), ubounds); + bounds = Collections.unmodifiableList(res); + fixupBoundAnnotations(); + } + return bounds; + } + + /** + * Sets the bounds. + * + * @param bounds a list of bounds to be captured by this method + */ + public void setBounds(List bounds) { + this.bounds = bounds; + } + + /** + * Copy the first annotation (in each hierarchy) on a bound to the primary annotation + * location of the intersection type. + * + *

          For example, in the type {@code @NonNull Object & @Initialized @Nullable + * Serializable}, {@code @Nullable} and {@code @Initialized} are copied to the primary + * annotation location. + */ + public void copyIntersectionBoundAnnotations() { + AnnotationMirrorSet annos = new AnnotationMirrorSet(); + for (AnnotatedTypeMirror bound : getBounds()) { + for (AnnotationMirror a : bound.getAnnotations()) { + if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, a) + == null) { + annos.add(a); + } + } + } + addAnnotations(annos); + } } - @Override - public AnnotatedUnionType shallowCopy() { - return shallowCopy(true); + // TODO: Ensure union types are handled everywhere. + // TODO: Should field "annotations" contain anything? + public static class AnnotatedUnionType extends AnnotatedTypeMirror { + + /** + * Creates a new AnnotatedUnionType. + * + * @param type underlying kind of this type + * @param atypeFactory type factory + */ + private AnnotatedUnionType(UnionType type, AnnotatedTypeFactory atypeFactory) { + super(type, atypeFactory); + } + + @Override + public R accept(AnnotatedTypeVisitor v, P p) { + return v.visitUnion(this, p); + } + + @Override + public AnnotatedUnionType deepCopy(boolean copyAnnotations) { + return (AnnotatedUnionType) new AnnotatedTypeCopier(copyAnnotations).visit(this); + } + + @Override + public AnnotatedUnionType deepCopy() { + return deepCopy(true); + } + + @Override + public AnnotatedUnionType shallowCopy(boolean copyAnnotations) { + AnnotatedUnionType type = + new AnnotatedUnionType((UnionType) underlyingType, atypeFactory); + if (copyAnnotations) { + type.addAnnotations(this.getAnnotationsField()); + } + type.alternatives = this.alternatives; + return type; + } + + @Override + public AnnotatedUnionType shallowCopy() { + return shallowCopy(true); + } + + /** + * The types that are unioned to form this AnnotatedUnionType. + * + *

          Is set by {@link #getAlternatives} and {@link #shallowCopy}. + */ + protected @MonotonicNonNull List alternatives; + + /** + * Returns the types that are unioned to form this AnnotatedUnionType. + * + * @return the types that are unioned to form this AnnotatedUnionType + */ + public List getAlternatives() { + if (alternatives == null) { + List ualts = ((UnionType) underlyingType).getAlternatives(); + List res = + CollectionsPlume.mapList( + (TypeMirror alt) -> + (AnnotatedDeclaredType) + createType(alt, atypeFactory, false), + ualts); + alternatives = Collections.unmodifiableList(res); + } + return alternatives; + } } /** - * The types that are unioned to form this AnnotatedUnionType. + * This method returns a list of AnnotatedTypeMirrors where the Java type of each ATM is an + * immediate supertype (class or interface) of the Java type of this. The interface types, if + * any, appear at the end of the list. If the directSuperType has type arguments, then the + * annotations on those type arguments are taken with proper translation from the declaration of + * the Java type of this. * - *

          Is set by {@link #getAlternatives} and {@link #shallowCopy}. - */ - protected @MonotonicNonNull List alternatives; - - /** - * Returns the types that are unioned to form this AnnotatedUnionType. + *

          For example, + * + *

          +     * {@code class B { ... } }
          +     * {@code class A extends B<@NonNull String> { ... } }
          +     * {@code @Nullable A a;}
          +     * 
          + * + * The direct supertype of the ATM {@code @Nullable A} is {@code @Nullable B<@NonNull String>}. + * + *

          An example with more complex type arguments: + * + *

          +     * {@code class D { ... } }
          +     * {@code class A extends D { ... } }
          +     * {@code @Nullable A<@NonNull String, @NonNull Object> a;}
          +     * 
          + * + * The direct supertype of the ATM {@code @Nullable A<@NonNull String, @NonNull Object>} is + * {@code @Nullable B<@NonNull Object, @NonNull String>}. + * + *

          An example with more than one direct supertype: * - * @return the types that are unioned to form this AnnotatedUnionType + *

          +     * {@code class B implements List { ... } }
          +     * {@code class A extends B<@NonNull String> implements List { ... } }
          +     * {@code @Nullable A a;}
          +     * 
          + * + * The direct supertypes of the ATM {@code @Nullable A} are {@code @Nullable B <@NonNull + * String>} and {@code @Nullable List<@NonNull Integer>}. + * + * @return the immediate supertypes of this + * @see Types#directSupertypes(TypeMirror) */ - public List getAlternatives() { - if (alternatives == null) { - List ualts = ((UnionType) underlyingType).getAlternatives(); - List res = - CollectionsPlume.mapList( - (TypeMirror alt) -> (AnnotatedDeclaredType) createType(alt, atypeFactory, false), - ualts); - alternatives = Collections.unmodifiableList(res); - } - return alternatives; + public List directSupertypes() { + return SupertypeFinder.directSupertypes(this); } - } - - /** - * This method returns a list of AnnotatedTypeMirrors where the Java type of each ATM is an - * immediate supertype (class or interface) of the Java type of this. The interface types, if any, - * appear at the end of the list. If the directSuperType has type arguments, then the annotations - * on those type arguments are taken with proper translation from the declaration of the Java type - * of this. - * - *

          For example, - * - *

          -   * {@code class B { ... } }
          -   * {@code class A extends B<@NonNull String> { ... } }
          -   * {@code @Nullable A a;}
          -   * 
          - * - * The direct supertype of the ATM {@code @Nullable A} is {@code @Nullable B<@NonNull String>}. - * - *

          An example with more complex type arguments: - * - *

          -   * {@code class D { ... } }
          -   * {@code class A extends D { ... } }
          -   * {@code @Nullable A<@NonNull String, @NonNull Object> a;}
          -   * 
          - * - * The direct supertype of the ATM {@code @Nullable A<@NonNull String, @NonNull Object>} is - * {@code @Nullable B<@NonNull Object, @NonNull String>}. - * - *

          An example with more than one direct supertype: - * - *

          -   * {@code class B implements List { ... } }
          -   * {@code class A extends B<@NonNull String> implements List { ... } }
          -   * {@code @Nullable A a;}
          -   * 
          - * - * The direct supertypes of the ATM {@code @Nullable A} are {@code @Nullable B <@NonNull String>} - * and {@code @Nullable List<@NonNull Integer>}. - * - * @return the immediate supertypes of this - * @see Types#directSupertypes(TypeMirror) - */ - public List directSupertypes() { - return SupertypeFinder.directSupertypes(this); - } - - /** - * Returns true if this type has a primary annotation in the same hierarchy as {@code annotation}. - * - * @param annotation the qualifier hierarchy to check for - * @return true iff this type has a primary annotation in the same hierarchy as {@code - * annotation}. - * @deprecated use {@link #hasAnnotationInHierarchy(AnnotationMirror)} - */ - @Deprecated // 2023-06-15 - public boolean isAnnotatedInHierarchy(AnnotationMirror annotation) { - return hasAnnotationInHierarchy(annotation); - } - - /** An ERROR TypeKind was found. */ - @SuppressWarnings("serial") - public static class ErrorTypeKindException extends Error { /** - * Creates an ErrorTypeKindException. + * Returns true if this type has a primary annotation in the same hierarchy as {@code + * annotation}. * - * @param format format string - * @param args arguments to the format string + * @param annotation the qualifier hierarchy to check for + * @return true iff this type has a primary annotation in the same hierarchy as {@code + * annotation}. + * @deprecated use {@link #hasAnnotationInHierarchy(AnnotationMirror)} */ - @FormatMethod - public ErrorTypeKindException(String format, Object... args) { - super(String.format(format, args)); + @Deprecated // 2023-06-15 + public boolean isAnnotatedInHierarchy(AnnotationMirror annotation) { + return hasAnnotationInHierarchy(annotation); + } + + /** An ERROR TypeKind was found. */ + @SuppressWarnings("serial") + public static class ErrorTypeKindException extends Error { + + /** + * Creates an ErrorTypeKindException. + * + * @param format format string + * @param args arguments to the format string + */ + @FormatMethod + public ErrorTypeKindException(String format, Object... args) { + super(String.format(format, args)); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java index c845f5ddea3..6a1aabe5d75 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeParameterBounds.java @@ -1,53 +1,54 @@ package org.checkerframework.framework.type; -import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; + /** Represents upper and lower bounds, each an AnnotatedTypeMirror. */ public class AnnotatedTypeParameterBounds { - private final AnnotatedTypeMirror upper; - private final AnnotatedTypeMirror lower; - - public AnnotatedTypeParameterBounds(AnnotatedTypeMirror upper, AnnotatedTypeMirror lower) { - this.upper = upper; - this.lower = lower; - } - - public AnnotatedTypeMirror getUpperBound() { - return upper; - } - - public AnnotatedTypeMirror getLowerBound() { - return lower; - } - - @Override - public String toString() { - return "[extends " + upper + " super " + lower + "]"; - } - - /** - * Return a possibly-verbose string representation of this. - * - * @param verbose if true, returned representation is verbose - * @return a possibly-verbose string representation of this - */ - public String toString(boolean verbose) { - return "[extends " + upper.toString(verbose) + " super " + lower.toString(verbose) + "]"; - } - - @Override - public int hashCode() { - return Objects.hash(upper, lower); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (!(obj instanceof AnnotatedTypeParameterBounds)) { - return false; + private final AnnotatedTypeMirror upper; + private final AnnotatedTypeMirror lower; + + public AnnotatedTypeParameterBounds(AnnotatedTypeMirror upper, AnnotatedTypeMirror lower) { + this.upper = upper; + this.lower = lower; + } + + public AnnotatedTypeMirror getUpperBound() { + return upper; + } + + public AnnotatedTypeMirror getLowerBound() { + return lower; + } + + @Override + public String toString() { + return "[extends " + upper + " super " + lower + "]"; + } + + /** + * Return a possibly-verbose string representation of this. + * + * @param verbose if true, returned representation is verbose + * @return a possibly-verbose string representation of this + */ + public String toString(boolean verbose) { + return "[extends " + upper.toString(verbose) + " super " + lower.toString(verbose) + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(upper, lower); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof AnnotatedTypeParameterBounds)) { + return false; + } + AnnotatedTypeParameterBounds other = (AnnotatedTypeParameterBounds) obj; + return (this.upper == null ? other.upper == null : this.upper.equals(other.upper)) + && (this.lower == null ? other.lower == null : this.lower.equals(other.lower)); } - AnnotatedTypeParameterBounds other = (AnnotatedTypeParameterBounds) obj; - return (this.upper == null ? other.upper == null : this.upper.equals(other.upper)) - && (this.lower == null ? other.lower == null : this.lower.equals(other.lower)); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java index 7940b4edadd..0689d78727d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java @@ -1,15 +1,17 @@ package org.checkerframework.framework.type; -import java.util.ArrayList; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.visitor.DoubleAnnotatedTypeScanner; import org.checkerframework.javacutil.BugInCF; +import java.util.ArrayList; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; + /** * Replaces or adds all the annotations in the parameter with the annotations from the visited type. * An annotation is replaced if the parameter type already has an annotation in the same hierarchy @@ -25,107 +27,107 @@ */ public class AnnotatedTypeReplacer extends DoubleAnnotatedTypeScanner { - /** If top != null we replace only the annotations in the hierarchy of top. */ - private @Nullable AnnotationMirror top; + /** If top != null we replace only the annotations in the hierarchy of top. */ + private @Nullable AnnotationMirror top; - /** Construct an AnnotatedTypeReplacer that will replace all annotations. */ - public AnnotatedTypeReplacer() { - this.top = null; - } + /** Construct an AnnotatedTypeReplacer that will replace all annotations. */ + public AnnotatedTypeReplacer() { + this.top = null; + } - /** - * Construct an AnnotatedTypeReplacer that will only replace annotations in {@code top}'s - * hierarchy. - * - * @param top if top != null, then only annotations in the hierarchy of top are affected - */ - public AnnotatedTypeReplacer(@Nullable AnnotationMirror top) { - this.top = top; - } + /** + * Construct an AnnotatedTypeReplacer that will only replace annotations in {@code top}'s + * hierarchy. + * + * @param top if top != null, then only annotations in the hierarchy of top are affected + */ + public AnnotatedTypeReplacer(@Nullable AnnotationMirror top) { + this.top = top; + } - /** - * If {@code top != null}, then only annotations in the hierarchy of {@code top} are affected; - * otherwise, all annotations are replaced. - * - * @param top if top != null, then only annotations in the hierarchy of top are replaced; - * otherwise, all annotations are replaced. - */ - public void setTop(@Nullable AnnotationMirror top) { - this.top = top; - } + /** + * If {@code top != null}, then only annotations in the hierarchy of {@code top} are affected; + * otherwise, all annotations are replaced. + * + * @param top if top != null, then only annotations in the hierarchy of top are replaced; + * otherwise, all annotations are replaced. + */ + public void setTop(@Nullable AnnotationMirror top) { + this.top = top; + } - @SuppressWarnings("interning:not.interned") // assertion - @Override - protected Void defaultAction(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - assert from != to; - if (from != null && to != null) { - replaceAnnotations(from, to); + @SuppressWarnings("interning:not.interned") // assertion + @Override + protected Void defaultAction(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + assert from != to; + if (from != null && to != null) { + replaceAnnotations(from, to); + } + return null; } - return null; - } - /** - * Replace the annotations in to with the annotations in from, wherever from has an annotation. - * - * @param from the source of the annotations - * @param to the destination of the annotations, modified by this method - */ - protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - if (top == null) { - to.replaceAnnotations(from.getAnnotations()); - } else { - AnnotationMirror replacement = from.getAnnotationInHierarchy(top); - if (replacement != null) { - to.replaceAnnotation(from.getAnnotationInHierarchy(top)); - } + /** + * Replace the annotations in to with the annotations in from, wherever from has an annotation. + * + * @param from the source of the annotations + * @param to the destination of the annotations, modified by this method + */ + protected void replaceAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + if (top == null) { + to.replaceAnnotations(from.getAnnotations()); + } else { + AnnotationMirror replacement = from.getAnnotationInHierarchy(top); + if (replacement != null) { + to.replaceAnnotation(from.getAnnotationInHierarchy(top)); + } + } } - } - @Override - public Void visitTypeVariable(AnnotatedTypeVariable from, AnnotatedTypeMirror to) { - resolvePrimaries(from, to); - return super.visitTypeVariable(from, to); - } + @Override + public Void visitTypeVariable(AnnotatedTypeVariable from, AnnotatedTypeMirror to) { + resolvePrimaries(from, to); + return super.visitTypeVariable(from, to); + } - @Override - public Void visitWildcard(AnnotatedWildcardType from, AnnotatedTypeMirror to) { - resolvePrimaries(from, to); - return super.visitWildcard(from, to); - } + @Override + public Void visitWildcard(AnnotatedWildcardType from, AnnotatedTypeMirror to) { + resolvePrimaries(from, to); + return super.visitWildcard(from, to); + } - /** - * For type variables and wildcards, the absence of a primary annotations has an implied meaning - * on substitution. Therefore, in these cases we remove the primary annotation and rely on the - * fact that the bounds are also merged into the type to. - * - * @param from a type variable or wildcard - * @param to the destination annotated type mirror - */ - public void resolvePrimaries(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - if (from.getKind() == TypeKind.WILDCARD || from.getKind() == TypeKind.TYPEVAR) { - if (top != null) { - if (from.getAnnotationInHierarchy(top) == null) { - to.removeAnnotationInHierarchy(top); - } - } else { - List toRemove = new ArrayList<>(1); - for (AnnotationMirror toPrimaryAnno : to.getAnnotations()) { - if (from.getAnnotationInHierarchy(toPrimaryAnno) == null) { - // Doing the removal here directly can lead to a - // ConcurrentModificationException, - // because this loop is iterating over the annotations in `to`. - toRemove.add(toPrimaryAnno); - } - } - for (AnnotationMirror annoToRemove : toRemove) { - to.removeAnnotation(annoToRemove); + /** + * For type variables and wildcards, the absence of a primary annotations has an implied meaning + * on substitution. Therefore, in these cases we remove the primary annotation and rely on the + * fact that the bounds are also merged into the type to. + * + * @param from a type variable or wildcard + * @param to the destination annotated type mirror + */ + public void resolvePrimaries(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + if (from.getKind() == TypeKind.WILDCARD || from.getKind() == TypeKind.TYPEVAR) { + if (top != null) { + if (from.getAnnotationInHierarchy(top) == null) { + to.removeAnnotationInHierarchy(top); + } + } else { + List toRemove = new ArrayList<>(1); + for (AnnotationMirror toPrimaryAnno : to.getAnnotations()) { + if (from.getAnnotationInHierarchy(toPrimaryAnno) == null) { + // Doing the removal here directly can lead to a + // ConcurrentModificationException, + // because this loop is iterating over the annotations in `to`. + toRemove.add(toPrimaryAnno); + } + } + for (AnnotationMirror annoToRemove : toRemove) { + to.removeAnnotation(annoToRemove); + } + } + } else { + throw new BugInCF( + "ResolvePrimaries's from argument should be a type variable OR wildcard%n" + + "from=%s%nto=%s", + from.toString(true), to.toString(true)); } - } - } else { - throw new BugInCF( - "ResolvePrimaries's from argument should be a type variable OR wildcard%n" - + "from=%s%nto=%s", - from.toString(true), to.toString(true)); } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java index 82f7226fb0d..233b42f66df 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java @@ -1,5 +1,21 @@ package org.checkerframework.framework.type; +import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; +import org.checkerframework.checker.mustcall.qual.InheritableMustCall; +import org.checkerframework.checker.mustcall.qual.Owning; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; +import org.checkerframework.checker.signature.qual.Identifier; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.UserError; +import org.plumelib.reflection.Signatures; + import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -24,27 +40,13 @@ import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; -import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods; -import org.checkerframework.checker.mustcall.qual.InheritableMustCall; -import org.checkerframework.checker.mustcall.qual.Owning; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; -import org.checkerframework.checker.signature.qual.Identifier; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.InternalUtils; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.UserError; -import org.plumelib.reflection.Signatures; /** * This class assists the {@link AnnotatedTypeFactory} by reflectively looking up the list of @@ -68,783 +70,798 @@ * org.checkerframework.checker.units.UnitsAnnotationClassLoader} for an example. */ @SuppressWarnings( - "mustcall:inconsistent.mustcall.subtype" // No need to check that AnnotationClassLoaders are + "mustcall:inconsistent.mustcall.subtype" // No need to check that AnnotationClassLoaders are // closed. (Just one is created per type factory.) ) @InheritableMustCall({}) public class AnnotationClassLoader implements Closeable { - /** For issuing errors to the user. */ - protected final BaseTypeChecker checker; - - // For loading from a source package directory - /** The package name. */ - private final @DotSeparatedIdentifiers String packageName; - - /** The package name, with periods replaced by slashes. */ - private final String packageNameWithSlashes; - - /** The atomic package names (the package name split at dots). */ - private final List<@Identifier String> fullyQualifiedPackageNameSegments; - - /** The name of a Checker's qualifier package. */ - private static final String QUAL_PACKAGE = "qual"; - - // For loading from a Jar file - /** The suffix for a .jar file. */ - private static final String JAR_SUFFIX = ".jar"; - - /** The suffix for a .class file. */ - private static final String CLASS_SUFFIX = ".class"; - - // Constants - /** The package separator. */ - private static final char DOT = '.'; - - /** The path separator, in .jar files, binary names, etc. */ - private static final char SLASH = '/'; - - /** - * Processing Env used to create an {@link AnnotationBuilder}, which is in turn used to build the - * annotation mirror from the loaded class. - */ - protected final ProcessingEnvironment processingEnv; - - /** The resource URL of the qual directory of a checker class. */ - private final URL resourceURL; - - /** The class loader used to load annotation classes. */ - @SuppressWarnings("builder:required.method.not.called") // this class is @MustCall({}) - protected final @Owning URLClassLoader classLoader; - - /** - * The annotation classes bundled with a checker (located in its qual directory) that are deemed - * supported by the checker (non-alias annotations). Each checker can override {@link - * #isSupportedAnnotationClass(Class)} to determine whether an annotation is supported or not. - * Call {@link #getBundledAnnotationClasses()} to obtain a reference to the set of classes. - */ - private final Set> supportedBundledAnnotationClasses; - - /** - * Constructor for loading annotations defined for a checker. - * - * @param checker a {@link BaseTypeChecker} or its subclass - */ - @SuppressWarnings("signature") // TODO: reduce use of string manipulation - public AnnotationClassLoader(BaseTypeChecker checker) { - this.checker = checker; - processingEnv = checker.getProcessingEnvironment(); - - // package name must use dots, this is later prepended to annotation - // class names as we load the classes using the class loader - Package checkerPackage = checker.getClass().getPackage(); - packageName = - checkerPackage != null && !checkerPackage.getName().isEmpty() - ? checkerPackage.getName() + DOT + QUAL_PACKAGE - : QUAL_PACKAGE; - - // the package name with dots replaced by slashes will be used to scan file directories - packageNameWithSlashes = packageName.replace(DOT, SLASH); - - // Each component of the fully qualified package name will be used later to recursively - // descend from a root directory to see if the package exists in some particular root - // directory. - fullyQualifiedPackageNameSegments = new ArrayList<>(); - - // from the fully qualified package name, split it at every dot then add to the list - fullyQualifiedPackageNameSegments.addAll(SystemUtil.DOT_SPLITTER.splitToList(packageName)); - - classLoader = getClassLoader(); - - URL localResourceURL; - if (classLoader != null) { - // if the application classloader is accessible, then directly retrieve the resource URL - // of the qual package resource URLs must use slashes - localResourceURL = classLoader.getResource(packageNameWithSlashes); - - // thread based application classloader, if needed in the future: - // resourceURL = - // Thread.currentThread().getContextClassLoader().getResource(packageNameWithSlashes); - } else { - // Signal failure to find resource - localResourceURL = null; + /** For issuing errors to the user. */ + protected final BaseTypeChecker checker; + + // For loading from a source package directory + /** The package name. */ + private final @DotSeparatedIdentifiers String packageName; + + /** The package name, with periods replaced by slashes. */ + private final String packageNameWithSlashes; + + /** The atomic package names (the package name split at dots). */ + private final List<@Identifier String> fullyQualifiedPackageNameSegments; + + /** The name of a Checker's qualifier package. */ + private static final String QUAL_PACKAGE = "qual"; + + // For loading from a Jar file + /** The suffix for a .jar file. */ + private static final String JAR_SUFFIX = ".jar"; + + /** The suffix for a .class file. */ + private static final String CLASS_SUFFIX = ".class"; + + // Constants + /** The package separator. */ + private static final char DOT = '.'; + + /** The path separator, in .jar files, binary names, etc. */ + private static final char SLASH = '/'; + + /** + * Processing Env used to create an {@link AnnotationBuilder}, which is in turn used to build + * the annotation mirror from the loaded class. + */ + protected final ProcessingEnvironment processingEnv; + + /** The resource URL of the qual directory of a checker class. */ + private final URL resourceURL; + + /** The class loader used to load annotation classes. */ + @SuppressWarnings("builder:required.method.not.called") // this class is @MustCall({}) + protected final @Owning URLClassLoader classLoader; + + /** + * The annotation classes bundled with a checker (located in its qual directory) that are deemed + * supported by the checker (non-alias annotations). Each checker can override {@link + * #isSupportedAnnotationClass(Class)} to determine whether an annotation is supported or not. + * Call {@link #getBundledAnnotationClasses()} to obtain a reference to the set of classes. + */ + private final Set> supportedBundledAnnotationClasses; + + /** + * Constructor for loading annotations defined for a checker. + * + * @param checker a {@link BaseTypeChecker} or its subclass + */ + @SuppressWarnings("signature") // TODO: reduce use of string manipulation + public AnnotationClassLoader(BaseTypeChecker checker) { + this.checker = checker; + processingEnv = checker.getProcessingEnvironment(); + + // package name must use dots, this is later prepended to annotation + // class names as we load the classes using the class loader + Package checkerPackage = checker.getClass().getPackage(); + packageName = + checkerPackage != null && !checkerPackage.getName().isEmpty() + ? checkerPackage.getName() + DOT + QUAL_PACKAGE + : QUAL_PACKAGE; + + // the package name with dots replaced by slashes will be used to scan file directories + packageNameWithSlashes = packageName.replace(DOT, SLASH); + + // Each component of the fully qualified package name will be used later to recursively + // descend from a root directory to see if the package exists in some particular root + // directory. + fullyQualifiedPackageNameSegments = new ArrayList<>(); + + // from the fully qualified package name, split it at every dot then add to the list + fullyQualifiedPackageNameSegments.addAll(SystemUtil.DOT_SPLITTER.splitToList(packageName)); + + classLoader = getClassLoader(); + + URL localResourceURL; + if (classLoader != null) { + // if the application classloader is accessible, then directly retrieve the resource URL + // of the qual package resource URLs must use slashes + localResourceURL = classLoader.getResource(packageNameWithSlashes); + + // thread based application classloader, if needed in the future: + // resourceURL = + // Thread.currentThread().getContextClassLoader().getResource(packageNameWithSlashes); + } else { + // Signal failure to find resource + localResourceURL = null; + } + + if (localResourceURL == null) { + // if the application classloader is not accessible (which means the checker class was + // loaded using the bootstrap classloader) or if the classloader didn't find the + // package, then scan the classpaths to find a jar or directory which contains the qual + // package and set the resource URL to that jar or qual directory + localResourceURL = getURLFromClasspaths(); + } + resourceURL = localResourceURL; + + supportedBundledAnnotationClasses = new LinkedHashSet<>(); + + loadBundledAnnotationClasses(); } - if (localResourceURL == null) { - // if the application classloader is not accessible (which means the checker class was - // loaded using the bootstrap classloader) or if the classloader didn't find the - // package, then scan the classpaths to find a jar or directory which contains the qual - // package and set the resource URL to that jar or qual directory - localResourceURL = getURLFromClasspaths(); + @EnsuresCalledMethods(value = "classLoader", methods = "close") + @Override + public void close() { + try { + classLoader.close(); + } catch (IOException e) { + checker.message(Diagnostic.Kind.NOTE, "Failed to close AnnotationClassLoader"); + } } - resourceURL = localResourceURL; - supportedBundledAnnotationClasses = new LinkedHashSet<>(); + /** + * Scans all classpaths and returns the resource URL to the jar which contains the checker's + * qual package, or the qual package directory if it exists, or null if no jar or directory + * contains the package. + * + * @return a URL to the jar that contains the qual package, or to the qual package's directory, + * or null if no jar or directory contains the qual package + */ + private @Nullable URL getURLFromClasspaths() { + // TODO: This method could probably be replaced with + // io.github.classgraph.ClassGraph#getClasspathURIs() + + // Debug use, uncomment if needed to see all of the classpaths (boot + // classpath, extension classpath, and classpath) + // printPaths(); + + URL url = null; + + // obtain all classpaths + Set paths = getClasspaths(); + + // In checkers, there will be a resource URL for the qual directory. But when called in the + // framework (eg GeneralAnnotatedTypeFactory), there won't be a resourceURL since there + // isn't a qual directory. + + // Each path from the set of classpaths will be checked to see if it contains the qual + // directory of a checker, if so, the first directory or jar that contains the package will + // be used as the source for loading classes from the qual package. + + // If either a directory or a jar contains the package, resourceURL will be updated to refer + // to that source, otherwise resourceURL remains as null. + + // If both a jar and a directory contain the qual package, then the order of the jar and the + // directory in the command line option(s) or environment variables will decide which one + // gets examined first. + for (String path : paths) { + // see if the current classpath segment is a jar or a directory + if (path.endsWith(JAR_SUFFIX)) { + // current classpath segment is a jar + url = getJarURL(path); + + // see if the jar contains the package + if (url != null && containsPackage(url)) { + return url; + } + } else { + // current classpath segment is a directory + url = getDirectoryURL(path); + + // see if the directory contains the package + if (url != null && containsPackage(url)) { + // append a slash if necessary + if (!path.endsWith(Character.toString(SLASH))) { + path += SLASH; + } + + // update URL to the qual directory + url = getDirectoryURL(path + packageNameWithSlashes); + + return url; + } + } + } - loadBundledAnnotationClasses(); - } + // if no jar or directory contains the qual package, then return null + return null; + } - @EnsuresCalledMethods(value = "classLoader", methods = "close") - @Override - public void close() { - try { - classLoader.close(); - } catch (IOException e) { - checker.message(Diagnostic.Kind.NOTE, "Failed to close AnnotationClassLoader"); + /** + * Checks to see if the jar or directory referred by the URL contains the qual package of a + * specific checker. + * + * @param url a URL referring to either a jar or a directory + * @return true if the jar or the directory contains the qual package, false otherwise + */ + private boolean containsPackage(URL url) { + // see whether the resource URL has a protocol of jar or file + if (url.getProtocol().equals("jar")) { + // try to open up the jar file + try { + JarURLConnection connection = (JarURLConnection) url.openConnection(); + try (JarFile jarFile = connection.getJarFile()) { + // check to see if the jar file contains the package + return checkJarForPackage(jarFile); + } + } catch (IOException e) { + // do nothing for missing or un-openable Jar files + } + } else if (url.getProtocol().equals("file")) { + // open up the directory + File rootDir = new File(url.getFile()); + + // check to see if the directory contains the package + return checkDirForPackage(rootDir, fullyQualifiedPackageNameSegments.iterator()); + } + + return false; } - } - - /** - * Scans all classpaths and returns the resource URL to the jar which contains the checker's qual - * package, or the qual package directory if it exists, or null if no jar or directory contains - * the package. - * - * @return a URL to the jar that contains the qual package, or to the qual package's directory, or - * null if no jar or directory contains the qual package - */ - private @Nullable URL getURLFromClasspaths() { - // TODO: This method could probably be replaced with - // io.github.classgraph.ClassGraph#getClasspathURIs() - - // Debug use, uncomment if needed to see all of the classpaths (boot - // classpath, extension classpath, and classpath) - // printPaths(); - - URL url = null; - - // obtain all classpaths - Set paths = getClasspaths(); - - // In checkers, there will be a resource URL for the qual directory. But when called in the - // framework (eg GeneralAnnotatedTypeFactory), there won't be a resourceURL since there - // isn't a qual directory. - - // Each path from the set of classpaths will be checked to see if it contains the qual - // directory of a checker, if so, the first directory or jar that contains the package will - // be used as the source for loading classes from the qual package. - - // If either a directory or a jar contains the package, resourceURL will be updated to refer - // to that source, otherwise resourceURL remains as null. - - // If both a jar and a directory contain the qual package, then the order of the jar and the - // directory in the command line option(s) or environment variables will decide which one - // gets examined first. - for (String path : paths) { - // see if the current classpath segment is a jar or a directory - if (path.endsWith(JAR_SUFFIX)) { - // current classpath segment is a jar - url = getJarURL(path); - - // see if the jar contains the package - if (url != null && containsPackage(url)) { - return url; + + /** + * Checks to see if the jar file contains the qual package of a specific checker. + * + * @param jar a jar file + * @return true if the jar file contains the qual package, false otherwise + */ + @SuppressWarnings("JdkObsolete") + private boolean checkJarForPackage(JarFile jar) { + Enumeration jarEntries = jar.entries(); + + // loop through the entries in the jar + while (jarEntries.hasMoreElements()) { + JarEntry je = jarEntries.nextElement(); + + // Each entry is the fully qualified path and file name to a particular artifact in the + // jar file (eg a class file). + // If the jar has the package, one of the entry's name will begin with the package name + // in slash notation. + String entryName = je.getName(); + if (entryName.startsWith(packageNameWithSlashes + SLASH)) { + return true; + } } - } else { - // current classpath segment is a directory - url = getDirectoryURL(path); - // see if the directory contains the package - if (url != null && containsPackage(url)) { - // append a slash if necessary - if (!path.endsWith(Character.toString(SLASH))) { - path += SLASH; - } + return false; + } - // update URL to the qual directory - url = getDirectoryURL(path + packageNameWithSlashes); + /** + * Checks to see if the current directory contains the qual package through recursion currentDir + * starts at the root directory (a directory passed in as part of the classpaths), the iterator + * goes through each segment of the fully qualified package name (each segment is separated by a + * dot). + * + *

          Each step of the recursion checks to see if there's a subdirectory in the current + * directory that has a name matching the package name segment, if so, it recursively descends + * into that subdirectory to check the next package name segment + * + *

          If there's no more segments left, then we've found the qual directory of interest + * + *

          If we've checked every subdirectory and none of them match the current package name + * segment, then the qual directory of interest does not exist in the given root directory (at + * the beginning of recursion) + * + * @param currentDir current directory + * @param pkgNames an iterator which provides each segment of the fully qualified qual package + * name + * @return true if the qual package exists within the root directory, false otherwise + */ + private boolean checkDirForPackage(File currentDir, Iterator pkgNames) { + // if the iterator has no more package name segments, then we've found + // the qual directory of interest + if (!pkgNames.hasNext()) { + return true; + } + // if the file doesn't exist or it isn't a directory, return false + if (currentDir == null || !currentDir.isDirectory()) { + return false; + } - return url; + // if it isn't empty, dequeue one segment of the fully qualified package name + String currentPackageDirName = pkgNames.next(); + + // scan current directory to see if there's a sub-directory that has a + // matching name as the package name segment + for (File file : currentDir.listFiles()) { + if (file.isDirectory() && file.getName().equals(currentPackageDirName)) { + // if so, recursively descend and look at the next segment of + // the package name + return checkDirForPackage(file, pkgNames); + } } - } + + // if no sub-directory has a matching name, then that means there isn't + // a matching qual package + return false; } - // if no jar or directory contains the qual package, then return null - return null; - } - - /** - * Checks to see if the jar or directory referred by the URL contains the qual package of a - * specific checker. - * - * @param url a URL referring to either a jar or a directory - * @return true if the jar or the directory contains the qual package, false otherwise - */ - private boolean containsPackage(URL url) { - // see whether the resource URL has a protocol of jar or file - if (url.getProtocol().equals("jar")) { - // try to open up the jar file - try { - JarURLConnection connection = (JarURLConnection) url.openConnection(); - try (JarFile jarFile = connection.getJarFile()) { - // check to see if the jar file contains the package - return checkJarForPackage(jarFile); + /** + * Given an absolute path to a directory, this method will return a URL reference to that + * directory. + * + * @param absolutePathToDirectory an absolute path to a directory + * @return a URL reference to the directory, or null if the URL is malformed + */ + private @Nullable URL getDirectoryURL(String absolutePathToDirectory) { + URL directoryURL = null; + + try { + directoryURL = new File(absolutePathToDirectory).toURI().toURL(); + } catch (MalformedURLException e) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.NOTE, + "Directory URL " + absolutePathToDirectory + " is malformed"); } - } catch (IOException e) { - // do nothing for missing or un-openable Jar files - } - } else if (url.getProtocol().equals("file")) { - // open up the directory - File rootDir = new File(url.getFile()); - - // check to see if the directory contains the package - return checkDirForPackage(rootDir, fullyQualifiedPackageNameSegments.iterator()); - } - return false; - } - - /** - * Checks to see if the jar file contains the qual package of a specific checker. - * - * @param jar a jar file - * @return true if the jar file contains the qual package, false otherwise - */ - @SuppressWarnings("JdkObsolete") - private boolean checkJarForPackage(JarFile jar) { - Enumeration jarEntries = jar.entries(); - - // loop through the entries in the jar - while (jarEntries.hasMoreElements()) { - JarEntry je = jarEntries.nextElement(); - - // Each entry is the fully qualified path and file name to a particular artifact in the - // jar file (eg a class file). - // If the jar has the package, one of the entry's name will begin with the package name - // in slash notation. - String entryName = je.getName(); - if (entryName.startsWith(packageNameWithSlashes + SLASH)) { - return true; - } + return directoryURL; } - return false; - } - - /** - * Checks to see if the current directory contains the qual package through recursion currentDir - * starts at the root directory (a directory passed in as part of the classpaths), the iterator - * goes through each segment of the fully qualified package name (each segment is separated by a - * dot). - * - *

          Each step of the recursion checks to see if there's a subdirectory in the current directory - * that has a name matching the package name segment, if so, it recursively descends into that - * subdirectory to check the next package name segment - * - *

          If there's no more segments left, then we've found the qual directory of interest - * - *

          If we've checked every subdirectory and none of them match the current package name segment, - * then the qual directory of interest does not exist in the given root directory (at the - * beginning of recursion) - * - * @param currentDir current directory - * @param pkgNames an iterator which provides each segment of the fully qualified qual package - * name - * @return true if the qual package exists within the root directory, false otherwise - */ - private boolean checkDirForPackage(File currentDir, Iterator pkgNames) { - // if the iterator has no more package name segments, then we've found - // the qual directory of interest - if (!pkgNames.hasNext()) { - return true; - } - // if the file doesn't exist or it isn't a directory, return false - if (currentDir == null || !currentDir.isDirectory()) { - return false; - } + /** + * Given an absolute path to a jar file, this method will return a URL reference to that jar + * file. + * + * @param absolutePathToJarFile an absolute path to a jar file + * @return a URL reference to the jar file, or null if the URL is malformed + */ + private @Nullable URL getJarURL(String absolutePathToJarFile) { + URL jarURL = null; + + try { + String normalizedPath = absolutePathToJarFile.replace("\\", "/"); + String osName = System.getProperty("os.name").toString().toLowerCase(Locale.ENGLISH); + String prefix = osName.startsWith("windows") ? "jar:file:///" : "jar:file:"; + + jarURL = new URI(prefix + normalizedPath + "!/").toURL(); + } catch (MalformedURLException | URISyntaxException e) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.NOTE, + "Jar URL " + absolutePathToJarFile + " is malformed"); + } - // if it isn't empty, dequeue one segment of the fully qualified package name - String currentPackageDirName = pkgNames.next(); - - // scan current directory to see if there's a sub-directory that has a - // matching name as the package name segment - for (File file : currentDir.listFiles()) { - if (file.isDirectory() && file.getName().equals(currentPackageDirName)) { - // if so, recursively descend and look at the next segment of - // the package name - return checkDirForPackage(file, pkgNames); - } + return jarURL; } - // if no sub-directory has a matching name, then that means there isn't - // a matching qual package - return false; - } - - /** - * Given an absolute path to a directory, this method will return a URL reference to that - * directory. - * - * @param absolutePathToDirectory an absolute path to a directory - * @return a URL reference to the directory, or null if the URL is malformed - */ - private @Nullable URL getDirectoryURL(String absolutePathToDirectory) { - URL directoryURL = null; - - try { - directoryURL = new File(absolutePathToDirectory).toURI().toURL(); - } catch (MalformedURLException e) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.NOTE, "Directory URL " + absolutePathToDirectory + " is malformed"); - } + /** + * Obtains and returns a set of the classpaths from compiler options, system environment + * variables, and by examining the classloader to see what paths it has access to. + * + *

          The classpaths will be obtained in the order of: + * + *

            + *
          1. extension paths (from java.ext.dirs) + *
          2. classpaths (set in {@code CLASSPATH}, or through {@code -classpath} and {@code -cp}) + *
          3. paths accessible and examined by the classloader + *
          + * + * In each of these paths, the order of the paths as specified in the command line options or + * environment variables will be the order returned in the set + * + * @return an immutable linked hashset of the classpaths + */ + private Set getClasspaths() { + Set paths = new LinkedHashSet<>(); + + // add all extension paths + paths.addAll(SystemUtil.getPathsProperty("java.ext.dirs")); + + // add all paths in CLASSPATH, -cp, and -classpath + paths.addAll(SystemUtil.getPathsProperty("java.class.path")); + + // add all paths that are examined by the classloader + if (classLoader != null) { + URL[] urls = classLoader.getURLs(); + for (int i = 0; i < urls.length; i++) { + paths.add(urls[i].getFile().toString()); + } + } - return directoryURL; - } - - /** - * Given an absolute path to a jar file, this method will return a URL reference to that jar file. - * - * @param absolutePathToJarFile an absolute path to a jar file - * @return a URL reference to the jar file, or null if the URL is malformed - */ - private @Nullable URL getJarURL(String absolutePathToJarFile) { - URL jarURL = null; - - try { - String normalizedPath = absolutePathToJarFile.replace("\\", "/"); - String osName = System.getProperty("os.name").toString().toLowerCase(Locale.ENGLISH); - String prefix = osName.startsWith("windows") ? "jar:file:///" : "jar:file:"; - - jarURL = new URI(prefix + normalizedPath + "!/").toURL(); - } catch (MalformedURLException | URISyntaxException e) { - processingEnv - .getMessager() - .printMessage(Diagnostic.Kind.NOTE, "Jar URL " + absolutePathToJarFile + " is malformed"); + return Collections.unmodifiableSet(paths); } - return jarURL; - } - - /** - * Obtains and returns a set of the classpaths from compiler options, system environment - * variables, and by examining the classloader to see what paths it has access to. - * - *

          The classpaths will be obtained in the order of: - * - *

            - *
          1. extension paths (from java.ext.dirs) - *
          2. classpaths (set in {@code CLASSPATH}, or through {@code -classpath} and {@code -cp}) - *
          3. paths accessible and examined by the classloader - *
          - * - * In each of these paths, the order of the paths as specified in the command line options or - * environment variables will be the order returned in the set - * - * @return an immutable linked hashset of the classpaths - */ - private Set getClasspaths() { - Set paths = new LinkedHashSet<>(); - - // add all extension paths - paths.addAll(SystemUtil.getPathsProperty("java.ext.dirs")); - - // add all paths in CLASSPATH, -cp, and -classpath - paths.addAll(SystemUtil.getPathsProperty("java.class.path")); - - // add all paths that are examined by the classloader - if (classLoader != null) { - URL[] urls = classLoader.getURLs(); - for (int i = 0; i < urls.length; i++) { - paths.add(urls[i].getFile().toString()); - } + /** + * Obtains the classloader used to load the checker class, if that isn't available then it will + * try to obtain the system classloader. + * + * @return the classloader used to load the checker class, or the system classloader, or null if + * both are unavailable + */ + private @Nullable URLClassLoader getClassLoader() { + ClassLoader result = InternalUtils.getClassLoaderForClass(checker.getClass()); + if (result instanceof URLClassLoader) { + return (@Nullable URLClassLoader) result; + } else { + // Java 9+ use an internal classloader that doesn't support getting URLs. Ignore. + return null; + } } - return Collections.unmodifiableSet(paths); - } - - /** - * Obtains the classloader used to load the checker class, if that isn't available then it will - * try to obtain the system classloader. - * - * @return the classloader used to load the checker class, or the system classloader, or null if - * both are unavailable - */ - private @Nullable URLClassLoader getClassLoader() { - ClassLoader result = InternalUtils.getClassLoaderForClass(checker.getClass()); - if (result instanceof URLClassLoader) { - return (@Nullable URLClassLoader) result; - } else { - // Java 9+ use an internal classloader that doesn't support getting URLs. Ignore. - return null; - } - } - - /** Debug Use: Displays all classpaths examined by the class loader. */ - @SuppressWarnings("unused") // for debugging - protected final void printPaths() { - // all paths in Xbootclasspath - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "bootclass path:"); - for (String path : SystemUtil.getPathsProperty("sun.boot.class.path")) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); - } + /** Debug Use: Displays all classpaths examined by the class loader. */ + @SuppressWarnings("unused") // for debugging + protected final void printPaths() { + // all paths in Xbootclasspath + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "bootclass path:"); + for (String path : SystemUtil.getPathsProperty("sun.boot.class.path")) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); + } - // all extension paths - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "extension dirs:"); - for (String path : SystemUtil.getPathsProperty("java.ext.dirs")) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); - } + // all extension paths + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "extension dirs:"); + for (String path : SystemUtil.getPathsProperty("java.ext.dirs")) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); + } + + // all paths in CLASSPATH, -cp, and -classpath + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "java.class.path property:"); + for (String path : SystemUtil.getPathsProperty("java.class.path")) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); + } - // all paths in CLASSPATH, -cp, and -classpath - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "java.class.path property:"); - for (String path : SystemUtil.getPathsProperty("java.class.path")) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + path); + // add all paths that are examined by the classloader + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.NOTE, "classloader examined paths:"); + if (classLoader != null) { + URL[] urls = classLoader.getURLs(); + for (int i = 0; i < urls.length; i++) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.NOTE, "\t" + urls[i].getFile()); + } + } else { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.NOTE, "classloader unavailable"); + } } - // add all paths that are examined by the classloader - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "classloader examined paths:"); - if (classLoader != null) { - URL[] urls = classLoader.getURLs(); - for (int i = 0; i < urls.length; i++) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t" + urls[i].getFile()); - } - } else { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "classloader unavailable"); + /** + * Loads the set of annotation classes in the qual directory of a checker shipped with the + * Checker Framework. + */ + private void loadBundledAnnotationClasses() { + // retrieve the fully qualified class names of the annotations + Set<@BinaryName String> annotationNames; + // see whether the resource URL has a protocol of jar or file + if (resourceURL != null && resourceURL.getProtocol().contentEquals("jar")) { + // if the checker class file is contained within a jar, then the resource URL for the + // qual directory will have the protocol "jar". This means the whole checker is loaded + // as a jar file. + + JarURLConnection connection; + // create a connection to the jar file + try { + connection = (JarURLConnection) resourceURL.openConnection(); + + // disable caching / connection sharing of the low level URLConnection to the Jar + // file + connection.setDefaultUseCaches(false); + connection.setUseCaches(false); + + // connect to the Jar file + connection.connect(); + } catch (IOException e) { + throw new BugInCF( + "AnnotationClassLoader: cannot open a connection to the Jar file " + + resourceURL.getFile()); + } + + // open up that jar file and extract annotation class names + try (JarFile jarFile = connection.getJarFile()) { + // get class names inside the jar file within the particular package + annotationNames = getBundledAnnotationNamesFromJar(jarFile); + } catch (IOException e) { + throw new BugInCF( + "AnnotationClassLoader: cannot open the Jar file " + resourceURL.getFile()); + } + } else if (resourceURL != null && resourceURL.getProtocol().contentEquals("file")) { + // If the checker class file is found within the file system itself within some + // directory (usually development build directories), then process the package as a file + // directory in the file system and load the annotations contained in the qual + // directory. + + // open up the directory + File packageDir = new File(resourceURL.getFile()); + annotationNames = getAnnotationNamesFromDirectory(packageName, packageDir, packageDir); + } else { + // We do not support a resource URL with any other protocols, so create an empty set. + annotationNames = Collections.emptySet(); + } + if (annotationNames.isEmpty()) { + PackageElement pkgEle = checker.getElementUtils().getPackageElement(packageName); + if (pkgEle != null) { + for (Element e : pkgEle.getEnclosedElements()) { + if (e.getKind() == ElementKind.ANNOTATION_TYPE) { + @SuppressWarnings( + "signature:assignment.type.incompatible") // Elements needs to be + // annotated. + @BinaryName String annoBinName = + checker.getElementUtils().getBinaryName((TypeElement) e).toString(); + annotationNames.add(annoBinName); + } + } + } + } + supportedBundledAnnotationClasses.addAll(loadAnnotationClasses(annotationNames)); } - } - - /** - * Loads the set of annotation classes in the qual directory of a checker shipped with the Checker - * Framework. - */ - private void loadBundledAnnotationClasses() { - // retrieve the fully qualified class names of the annotations - Set<@BinaryName String> annotationNames; - // see whether the resource URL has a protocol of jar or file - if (resourceURL != null && resourceURL.getProtocol().contentEquals("jar")) { - // if the checker class file is contained within a jar, then the resource URL for the - // qual directory will have the protocol "jar". This means the whole checker is loaded - // as a jar file. - - JarURLConnection connection; - // create a connection to the jar file - try { - connection = (JarURLConnection) resourceURL.openConnection(); - - // disable caching / connection sharing of the low level URLConnection to the Jar - // file - connection.setDefaultUseCaches(false); - connection.setUseCaches(false); - - // connect to the Jar file - connection.connect(); - } catch (IOException e) { - throw new BugInCF( - "AnnotationClassLoader: cannot open a connection to the Jar file " - + resourceURL.getFile()); - } - - // open up that jar file and extract annotation class names - try (JarFile jarFile = connection.getJarFile()) { - // get class names inside the jar file within the particular package - annotationNames = getBundledAnnotationNamesFromJar(jarFile); - } catch (IOException e) { - throw new BugInCF( - "AnnotationClassLoader: cannot open the Jar file " + resourceURL.getFile()); - } - } else if (resourceURL != null && resourceURL.getProtocol().contentEquals("file")) { - // If the checker class file is found within the file system itself within some - // directory (usually development build directories), then process the package as a file - // directory in the file system and load the annotations contained in the qual - // directory. - - // open up the directory - File packageDir = new File(resourceURL.getFile()); - annotationNames = getAnnotationNamesFromDirectory(packageName, packageDir, packageDir); - } else { - // We do not support a resource URL with any other protocols, so create an empty set. - annotationNames = Collections.emptySet(); + + /** + * Gets the set of annotation classes in the qual directory of a checker shipped with the + * Checker Framework. Note that the returned set from this method is mutable. This method is + * intended to be called within {@link AnnotatedTypeFactory#createSupportedTypeQualifiers() + * createSupportedTypeQualifiers()} (or its helper methods) to help define the set of supported + * qualifiers. + * + * @see AnnotatedTypeFactory#createSupportedTypeQualifiers() + * @return a mutable set of the loaded bundled annotation classes + */ + public final Set> getBundledAnnotationClasses() { + return supportedBundledAnnotationClasses; } - if (annotationNames.isEmpty()) { - PackageElement pkgEle = checker.getElementUtils().getPackageElement(packageName); - if (pkgEle != null) { - for (Element e : pkgEle.getEnclosedElements()) { - if (e.getKind() == ElementKind.ANNOTATION_TYPE) { - @SuppressWarnings("signature:assignment.type.incompatible") // Elements needs to be - // annotated. - @BinaryName String annoBinName = - checker.getElementUtils().getBinaryName((TypeElement) e).toString(); - annotationNames.add(annoBinName); - } + + /** + * Retrieves the annotation class file names from the qual directory contained inside a jar. + * + * @param jar the JarFile containing the annotation class files + * @return a set of fully qualified class names of the annotations + */ + @SuppressWarnings("JdkObsolete") + private Set<@BinaryName String> getBundledAnnotationNamesFromJar(JarFile jar) { + Set<@BinaryName String> annos = new LinkedHashSet<>(); + + // get an enumeration iterator for all the content entries in the jar file + Enumeration jarEntries = jar.entries(); + + // enumerate through the entries + while (jarEntries.hasMoreElements()) { + JarEntry je = jarEntries.nextElement(); + // filter out directories and non-class files + if (je.isDirectory() || !je.getName().endsWith(CLASS_SUFFIX)) { + continue; + } + + String className = Signatures.classfilenameToBinaryName(je.getName()); + + // filter for qual package + if (className.startsWith(packageName + DOT)) { + // add to set + annos.add(className); + } } - } + + return annos; } - supportedBundledAnnotationClasses.addAll(loadAnnotationClasses(annotationNames)); - } - - /** - * Gets the set of annotation classes in the qual directory of a checker shipped with the Checker - * Framework. Note that the returned set from this method is mutable. This method is intended to - * be called within {@link AnnotatedTypeFactory#createSupportedTypeQualifiers() - * createSupportedTypeQualifiers()} (or its helper methods) to help define the set of supported - * qualifiers. - * - * @see AnnotatedTypeFactory#createSupportedTypeQualifiers() - * @return a mutable set of the loaded bundled annotation classes - */ - public final Set> getBundledAnnotationClasses() { - return supportedBundledAnnotationClasses; - } - - /** - * Retrieves the annotation class file names from the qual directory contained inside a jar. - * - * @param jar the JarFile containing the annotation class files - * @return a set of fully qualified class names of the annotations - */ - @SuppressWarnings("JdkObsolete") - private Set<@BinaryName String> getBundledAnnotationNamesFromJar(JarFile jar) { - Set<@BinaryName String> annos = new LinkedHashSet<>(); - - // get an enumeration iterator for all the content entries in the jar file - Enumeration jarEntries = jar.entries(); - - // enumerate through the entries - while (jarEntries.hasMoreElements()) { - JarEntry je = jarEntries.nextElement(); - // filter out directories and non-class files - if (je.isDirectory() || !je.getName().endsWith(CLASS_SUFFIX)) { - continue; - } - - String className = Signatures.classfilenameToBinaryName(je.getName()); - - // filter for qual package - if (className.startsWith(packageName + DOT)) { - // add to set - annos.add(className); - } + + /** + * This method takes as input the canonical name of an external annotation class and loads and + * returns that class via the class loader. This method returns null if the external annotation + * class was loaded successfully but was deemed not supported by a checker. Errors are issued if + * the external class is not an annotation, or if it could not be loaded successfully. + * + * @param annoName canonical name of an external annotation class, e.g. + * "myproject.qual.myannotation" + * @return the loaded annotation class, or null if it was not a supported annotation as decided + * by {@link #isSupportedAnnotationClass(Class)} + */ + public final @Nullable Class loadExternalAnnotationClass( + @BinaryName String annoName) { + return loadAnnotationClass(annoName, true); } - return annos; - } - - /** - * This method takes as input the canonical name of an external annotation class and loads and - * returns that class via the class loader. This method returns null if the external annotation - * class was loaded successfully but was deemed not supported by a checker. Errors are issued if - * the external class is not an annotation, or if it could not be loaded successfully. - * - * @param annoName canonical name of an external annotation class, e.g. - * "myproject.qual.myannotation" - * @return the loaded annotation class, or null if it was not a supported annotation as decided by - * {@link #isSupportedAnnotationClass(Class)} - */ - public final @Nullable Class loadExternalAnnotationClass( - @BinaryName String annoName) { - return loadAnnotationClass(annoName, true); - } - - /** - * This method takes as input a fully qualified path to a directory, and loads and returns the set - * of all supported annotation classes from that directory. - * - * @param dirName absolute path to a directory containing annotation classes - * @return a set of annotation classes - */ - public final Set> loadExternalAnnotationClassesFromDirectory( - String dirName) { - File rootDirectory = new File(dirName); - Set<@BinaryName String> annoNames = - getAnnotationNamesFromDirectory(null, rootDirectory, rootDirectory); - return loadAnnotationClasses(annoNames); - } - - /** - * Retrieves all annotation names from the current directory, and recursively descends and - * retrieves annotation names from sub-directories. - * - * @param packageName the name of the package that contains the qual package, or null - * @param rootDirectory a {@link File} object representing the root directory of a set of - * annotations, which is subtracted from class names to retrieve each class's fully qualified - * class names - * @param currentDirectory a {@link File} object representing the current sub-directory of the - * root directory - * @return a set fully qualified annotation class name, for annotations in the root directory or - * its sub-directories - */ - @SuppressWarnings("signature") // TODO: reduce use of string manipulation - private Set<@BinaryName String> getAnnotationNamesFromDirectory( - @Nullable @DotSeparatedIdentifiers String packageName, - File rootDirectory, - File currentDirectory) { - Set<@BinaryName String> results = new LinkedHashSet<>(); - - // Full path to root directory - String rootPath = rootDirectory.getAbsolutePath(); - - // check every file and directory within the current directory - File[] directoryContents = currentDirectory.listFiles(); - if (directoryContents == null) { - throw new UserError("Directory does not exist: %s", currentDirectory); + /** + * This method takes as input a fully qualified path to a directory, and loads and returns the + * set of all supported annotation classes from that directory. + * + * @param dirName absolute path to a directory containing annotation classes + * @return a set of annotation classes + */ + public final Set> loadExternalAnnotationClassesFromDirectory( + String dirName) { + File rootDirectory = new File(dirName); + Set<@BinaryName String> annoNames = + getAnnotationNamesFromDirectory(null, rootDirectory, rootDirectory); + return loadAnnotationClasses(annoNames); } - Arrays.sort(directoryContents, Comparator.comparing(File::getName)); - for (File file : directoryContents) { - if (file.isFile()) { - // TODO: simplify all this string manipulation. - - // Full file name, including path to file - String fullFileName = file.getAbsolutePath(); - // Simple file name - String fileName = - fullFileName.substring( - fullFileName.lastIndexOf(File.separator) + 1, fullFileName.length()); - // Path to file - String filePath = fullFileName.substring(0, fullFileName.lastIndexOf(File.separator)); - // Package name beginning with "qual" - String qualPackage = null; - if (!filePath.equals(rootPath)) { - qualPackage = - filePath.substring(rootPath.length() + 1, filePath.length()).replace(SLASH, DOT); + + /** + * Retrieves all annotation names from the current directory, and recursively descends and + * retrieves annotation names from sub-directories. + * + * @param packageName the name of the package that contains the qual package, or null + * @param rootDirectory a {@link File} object representing the root directory of a set of + * annotations, which is subtracted from class names to retrieve each class's fully + * qualified class names + * @param currentDirectory a {@link File} object representing the current sub-directory of the + * root directory + * @return a set fully qualified annotation class name, for annotations in the root directory or + * its sub-directories + */ + @SuppressWarnings("signature") // TODO: reduce use of string manipulation + private Set<@BinaryName String> getAnnotationNamesFromDirectory( + @Nullable @DotSeparatedIdentifiers String packageName, + File rootDirectory, + File currentDirectory) { + Set<@BinaryName String> results = new LinkedHashSet<>(); + + // Full path to root directory + String rootPath = rootDirectory.getAbsolutePath(); + + // check every file and directory within the current directory + File[] directoryContents = currentDirectory.listFiles(); + if (directoryContents == null) { + throw new UserError("Directory does not exist: %s", currentDirectory); + } + Arrays.sort(directoryContents, Comparator.comparing(File::getName)); + for (File file : directoryContents) { + if (file.isFile()) { + // TODO: simplify all this string manipulation. + + // Full file name, including path to file + String fullFileName = file.getAbsolutePath(); + // Simple file name + String fileName = + fullFileName.substring( + fullFileName.lastIndexOf(File.separator) + 1, + fullFileName.length()); + // Path to file + String filePath = + fullFileName.substring(0, fullFileName.lastIndexOf(File.separator)); + // Package name beginning with "qual" + String qualPackage = null; + if (!filePath.equals(rootPath)) { + qualPackage = + filePath.substring(rootPath.length() + 1, filePath.length()) + .replace(SLASH, DOT); + } + // Simple annotation name, which is the same as the file name (without directory) + // but with file extension removed. + @BinaryName String annotationName = fileName; + if (fileName.lastIndexOf(DOT) != -1) { + annotationName = fileName.substring(0, fileName.lastIndexOf(DOT)); + } + + // Fully qualified annotation class name (a @BinaryName, not a @FullyQualifiedName) + @BinaryName String fullyQualifiedAnnoName = + Signatures.addPackage( + packageName, Signatures.addPackage(qualPackage, annotationName)); + + if (fileName.endsWith(CLASS_SUFFIX)) { + // add the fully qualified annotation class name to the set + results.add(fullyQualifiedAnnoName); + } + } else if (file.isDirectory()) { + // recursively add all sub directories's fully qualified annotation class name + results.addAll(getAnnotationNamesFromDirectory(packageName, rootDirectory, file)); + } } - // Simple annotation name, which is the same as the file name (without directory) - // but with file extension removed. - @BinaryName String annotationName = fileName; - if (fileName.lastIndexOf(DOT) != -1) { - annotationName = fileName.substring(0, fileName.lastIndexOf(DOT)); + + return results; + } + + /** + * Loads the class indicated by the name, and checks to see if it is an annotation that is + * supported by a checker. + * + * @param className the name of the class, in binary name format + * @param issueError set to true to issue a warning when a loaded annotation is not a type + * annotation. It is useful to set this to true if a given annotation must be a well-defined + * type annotation (eg for annotation class names given as command line arguments). It + * should be set to false if the annotation is a meta-annotation or non-type annotation. + * @return the loaded annotation class if it has a {@code @Target} meta-annotation with the + * required ElementType values, and is a supported annotation by a checker. If the + * annotation is not supported by a checker, null is returned. + */ + protected final @Nullable Class loadAnnotationClass( + @BinaryName String className, boolean issueError) { + + // load the class + Class cls = null; + try { + if (classLoader != null) { + cls = Class.forName(className, true, classLoader); + } else { + cls = Class.forName(className); + } + } catch (ClassNotFoundException e) { + throw new UserError( + checker.getClass().getSimpleName() + + ": could not load class for annotation: " + + className + + ". Ensure that it is a type annotation" + + " and your classpath is correct."); } - // Fully qualified annotation class name (a @BinaryName, not a @FullyQualifiedName) - @BinaryName String fullyQualifiedAnnoName = - Signatures.addPackage(packageName, Signatures.addPackage(qualPackage, annotationName)); + // If the freshly loaded class is not an annotation, then issue error if required and then + // return null + if (!cls.isAnnotation()) { + if (issueError) { + throw new UserError( + checker.getClass().getSimpleName() + + ": the loaded class: " + + cls.getCanonicalName() + + " is not a type annotation."); + } + return null; + } - if (fileName.endsWith(CLASS_SUFFIX)) { - // add the fully qualified annotation class name to the set - results.add(fullyQualifiedAnnoName); + Class annoClass = cls.asSubclass(Annotation.class); + // Check the loaded annotation to see if it has a @Target meta-annotation with the required + // ElementType values + if (hasWellDefinedTargetMetaAnnotation(annoClass)) { + // If so, return the loaded annotation if it is supported by a checker + return isSupportedAnnotationClass(annoClass) ? annoClass : null; + } else if (issueError) { + // issueError is set to true for loading explicitly named external annotations. + // We issue an error here when one of those annotations is not well-defined, since the + // user expects these external annotations to be loaded. + throw new UserError( + checker.getClass().getSimpleName() + + ": the loaded annotation: " + + annoClass.getCanonicalName() + + " is not a type annotation." + + " Check its @Target meta-annotation."); + } else { + // issueError is set to false for loading the qual directory or any external + // directories. + // We don't issue any errors since there may be meta-annotations or non-type annotations + // in such directories. + return null; } - } else if (file.isDirectory()) { - // recursively add all sub directories's fully qualified annotation class name - results.addAll(getAnnotationNamesFromDirectory(packageName, rootDirectory, file)); - } } - return results; - } - - /** - * Loads the class indicated by the name, and checks to see if it is an annotation that is - * supported by a checker. - * - * @param className the name of the class, in binary name format - * @param issueError set to true to issue a warning when a loaded annotation is not a type - * annotation. It is useful to set this to true if a given annotation must be a well-defined - * type annotation (eg for annotation class names given as command line arguments). It should - * be set to false if the annotation is a meta-annotation or non-type annotation. - * @return the loaded annotation class if it has a {@code @Target} meta-annotation with the - * required ElementType values, and is a supported annotation by a checker. If the annotation - * is not supported by a checker, null is returned. - */ - protected final @Nullable Class loadAnnotationClass( - @BinaryName String className, boolean issueError) { - - // load the class - Class cls = null; - try { - if (classLoader != null) { - cls = Class.forName(className, true, classLoader); - } else { - cls = Class.forName(className); - } - } catch (ClassNotFoundException e) { - throw new UserError( - checker.getClass().getSimpleName() - + ": could not load class for annotation: " - + className - + ". Ensure that it is a type annotation" - + " and your classpath is correct."); - } + /** + * Loads a set of annotations indicated by their names. + * + * @param annoNames a set of binary names for annotation classes + * @return a set of loaded annotation classes + * @see #loadAnnotationClass(String, boolean) + */ + protected final Set> loadAnnotationClasses( + @Nullable Set<@BinaryName String> annoNames) { + Set> loadedClasses = new LinkedHashSet<>(); + + if (annoNames != null && !annoNames.isEmpty()) { + // loop through each class name & load the class + for (String annoName : annoNames) { + Class annoClass = loadAnnotationClass(annoName, false); + if (annoClass != null) { + loadedClasses.add(annoClass); + } + } + } - // If the freshly loaded class is not an annotation, then issue error if required and then - // return null - if (!cls.isAnnotation()) { - if (issueError) { - throw new UserError( - checker.getClass().getSimpleName() - + ": the loaded class: " - + cls.getCanonicalName() - + " is not a type annotation."); - } - return null; + return loadedClasses; } - Class annoClass = cls.asSubclass(Annotation.class); - // Check the loaded annotation to see if it has a @Target meta-annotation with the required - // ElementType values - if (hasWellDefinedTargetMetaAnnotation(annoClass)) { - // If so, return the loaded annotation if it is supported by a checker - return isSupportedAnnotationClass(annoClass) ? annoClass : null; - } else if (issueError) { - // issueError is set to true for loading explicitly named external annotations. - // We issue an error here when one of those annotations is not well-defined, since the - // user expects these external annotations to be loaded. - throw new UserError( - checker.getClass().getSimpleName() - + ": the loaded annotation: " - + annoClass.getCanonicalName() - + " is not a type annotation." - + " Check its @Target meta-annotation."); - } else { - // issueError is set to false for loading the qual directory or any external - // directories. - // We don't issue any errors since there may be meta-annotations or non-type annotations - // in such directories. - return null; - } - } - - /** - * Loads a set of annotations indicated by their names. - * - * @param annoNames a set of binary names for annotation classes - * @return a set of loaded annotation classes - * @see #loadAnnotationClass(String, boolean) - */ - protected final Set> loadAnnotationClasses( - @Nullable Set<@BinaryName String> annoNames) { - Set> loadedClasses = new LinkedHashSet<>(); - - if (annoNames != null && !annoNames.isEmpty()) { - // loop through each class name & load the class - for (String annoName : annoNames) { - Class annoClass = loadAnnotationClass(annoName, false); - if (annoClass != null) { - loadedClasses.add(annoClass); - } - } + /** + * Checks to see whether a particular annotation class has the {@link Target} meta-annotation, + * and has the required {@link ElementType} values. + * + *

          A subclass may override this method to load annotations that are not intended to be + * annotated in source code. E.g.: {@code SubtypingChecker} overrides this method to load {@code + * Unqualified}. + * + * @param annoClass an annotation class + * @return true if the annotation is well defined, false if it isn't + */ + protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { + return annoClass.getAnnotation(Target.class) != null + && AnnotationUtils.hasTypeQualifierElementTypes( + annoClass.getAnnotation(Target.class).value(), annoClass); } - return loadedClasses; - } - - /** - * Checks to see whether a particular annotation class has the {@link Target} meta-annotation, and - * has the required {@link ElementType} values. - * - *

          A subclass may override this method to load annotations that are not intended to be - * annotated in source code. E.g.: {@code SubtypingChecker} overrides this method to load {@code - * Unqualified}. - * - * @param annoClass an annotation class - * @return true if the annotation is well defined, false if it isn't - */ - protected boolean hasWellDefinedTargetMetaAnnotation(Class annoClass) { - return annoClass.getAnnotation(Target.class) != null - && AnnotationUtils.hasTypeQualifierElementTypes( - annoClass.getAnnotation(Target.class).value(), annoClass); - } - - /** - * Checks to see whether a particular annotation class is supported. - * - *

          By default, all loaded annotations that pass the basic checks in {@link - * #loadAnnotationClass(String, boolean)} are supported. - * - *

          Individual checkers can create a subclass of AnnotationClassLoader and override this method - * to indicate whether a particular annotation is supported. - * - * @param annoClass an annotation class - * @return true if the annotation is supported, false if it isn't - */ - protected boolean isSupportedAnnotationClass(Class annoClass) { - return true; - } + /** + * Checks to see whether a particular annotation class is supported. + * + *

          By default, all loaded annotations that pass the basic checks in {@link + * #loadAnnotationClass(String, boolean)} are supported. + * + *

          Individual checkers can create a subclass of AnnotationClassLoader and override this + * method to indicate whether a particular annotation is supported. + * + * @param annoClass an annotation class + * @return true if the annotation is supported, false if it isn't + */ + protected boolean isSupportedAnnotationClass(Class annoClass) { + return true; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java index e82c1b810c8..b1f04feb537 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java @@ -1,14 +1,5 @@ package org.checkerframework.framework.type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -23,830 +14,850 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Types; + /** * Implements asSuper {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, AnnotatedTypeMirror, * AnnotatedTypeMirror)}. */ public class AsSuperVisitor extends AbstractAtmComboVisitor { - /** Type utilities. */ - private final Types types; - - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy. */ - private final QualifierHierarchy qualHierarchy; - - /** - * Whether or not the type being visited is a type argument from a raw type. If true, then the - * underlying type may not have the correct relationship with the supertype. - */ - private boolean isTypeArgumentFromRawType = false; - - /** - * Create a new AsSuperVisitor. - * - * @param atypeFactory the type factory - */ - public AsSuperVisitor(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.types = atypeFactory.types; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - } - - /** - * Implements asSuper. See {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, - * AnnotatedTypeMirror, AnnotatedTypeMirror)} for details. - * - * @param the type of the supertype - * @param type type from which to copy annotations - * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java - * type. - * @return a copy of {@code superType} with annotations copied from {@code type} and type - * variables substituted from {@code type}. - */ - @SuppressWarnings({ - "unchecked", - "interning:not.interned" // optimized special case - }) - public T asSuper(AnnotatedTypeMirror type, T superType) { - if (type == null || superType == null) { - throw new BugInCF( - "AsSuperVisitor.asSuper(%s, %s): arguments cannot be null", type, superType); - } - - if (type == superType) { - return (T) type.deepCopy(); - } - - // This visitor modifies superType and may return type, so pass it copies so that the - // parameters to asSuper are not changed and a copy is returned. - AnnotatedTypeMirror copyType = type.deepCopy(); - AnnotatedTypeMirror copySuperType = superType.deepCopy(); - reset(); - AnnotatedTypeMirror result = visit(copyType, copySuperType, null); - - if (result == null) { - throw new BugInCF( - "AsSuperVisitor returned null.%ntype: %s%nsuperType: %s", type, copySuperType); - } - - return (T) result; - } - - /** Resets this. */ - private void reset() { - isTypeArgumentFromRawType = false; - } - - @Override - public AnnotatedTypeMirror visit( - AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { - ensurePrimaryIsCorrectForUnions(type); - return super.visit(type, superType, p); - } - - /** - * The code in this class is assuming that the primary annotation of an {@link AnnotatedUnionType} - * is the least upper bound of its alternatives. This method makes this assumption true. - * - * @param type any kind of {@code AnnotatedTypeMirror} - */ - private void ensurePrimaryIsCorrectForUnions(AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.UNION) { - AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) type; - AnnotationMirrorSet lubs = null; - for (AnnotatedDeclaredType altern : annotatedUnionType.getAlternatives()) { - if (lubs == null) { - lubs = altern.getAnnotations(); - } else { - TypeMirror typeMirror = type.getUnderlyingType(); - AnnotationMirrorSet newLubs = new AnnotationMirrorSet(); - for (AnnotationMirror lub : lubs) { - AnnotationMirror anno = altern.getAnnotationInHierarchy(lub); - newLubs.add( - qualHierarchy.leastUpperBoundShallow( - anno, altern.getUnderlyingType(), lub, typeMirror)); - } - lubs = newLubs; + /** Type utilities. */ + private final Types types; + + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** The qualifier hierarchy. */ + private final QualifierHierarchy qualHierarchy; + + /** + * Whether or not the type being visited is a type argument from a raw type. If true, then the + * underlying type may not have the correct relationship with the supertype. + */ + private boolean isTypeArgumentFromRawType = false; + + /** + * Create a new AsSuperVisitor. + * + * @param atypeFactory the type factory + */ + public AsSuperVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.types = atypeFactory.types; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + } + + /** + * Implements asSuper. See {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, + * AnnotatedTypeMirror, AnnotatedTypeMirror)} for details. + * + * @param the type of the supertype + * @param type type from which to copy annotations + * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java + * type. + * @return a copy of {@code superType} with annotations copied from {@code type} and type + * variables substituted from {@code type}. + */ + @SuppressWarnings({ + "unchecked", + "interning:not.interned" // optimized special case + }) + public T asSuper(AnnotatedTypeMirror type, T superType) { + if (type == null || superType == null) { + throw new BugInCF( + "AsSuperVisitor.asSuper(%s, %s): arguments cannot be null", type, superType); + } + + if (type == superType) { + return (T) type.deepCopy(); + } + + // This visitor modifies superType and may return type, so pass it copies so that the + // parameters to asSuper are not changed and a copy is returned. + AnnotatedTypeMirror copyType = type.deepCopy(); + AnnotatedTypeMirror copySuperType = superType.deepCopy(); + reset(); + AnnotatedTypeMirror result = visit(copyType, copySuperType, null); + + if (result == null) { + throw new BugInCF( + "AsSuperVisitor returned null.%ntype: %s%nsuperType: %s", type, copySuperType); + } + + return (T) result; + } + + /** Resets this. */ + private void reset() { + isTypeArgumentFromRawType = false; + } + + @Override + public AnnotatedTypeMirror visit( + AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { + ensurePrimaryIsCorrectForUnions(type); + return super.visit(type, superType, p); + } + + /** + * The code in this class is assuming that the primary annotation of an {@link + * AnnotatedUnionType} is the least upper bound of its alternatives. This method makes this + * assumption true. + * + * @param type any kind of {@code AnnotatedTypeMirror} + */ + private void ensurePrimaryIsCorrectForUnions(AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.UNION) { + AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) type; + AnnotationMirrorSet lubs = null; + for (AnnotatedDeclaredType altern : annotatedUnionType.getAlternatives()) { + if (lubs == null) { + lubs = altern.getAnnotations(); + } else { + TypeMirror typeMirror = type.getUnderlyingType(); + AnnotationMirrorSet newLubs = new AnnotationMirrorSet(); + for (AnnotationMirror lub : lubs) { + AnnotationMirror anno = altern.getAnnotationInHierarchy(lub); + newLubs.add( + qualHierarchy.leastUpperBoundShallow( + anno, altern.getUnderlyingType(), lub, typeMirror)); + } + lubs = newLubs; + } + } + type.replaceAnnotations(lubs); + } + } + + private AnnotatedTypeMirror errorTypeNotErasedSubtypeOfSuperType( + AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { + if (TypesUtils.isString(superType.getUnderlyingType())) { + // Any type can be converted to String + return visit(atypeFactory.getStringType(type), superType, p); + } + if (isTypeArgumentFromRawType) { + return copyPrimaryAnnos(type, superType); + } + throw new BugInCF( + "AsSuperVisitor: type is not an erased subtype of supertype." + + "%ntype: %s%nsuperType: %s", + type, superType); + } + + private AnnotatedTypeMirror copyPrimaryAnnos(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + // There may have been annotations added by a recursive call to asSuper, so replace existing + // annotations + to.replaceAnnotations(new ArrayList<>(from.getAnnotations())); + // if to is a Typevar or Wildcard, then replaceAnnotations also sets primary annotations on + // the bounds to from.getAnnotations() + + if (to.getKind() == TypeKind.UNION) { + // Make sure that the alternatives have a primary annotations + // Alternatives cannot have type arguments, so asSuper isn't called recursively + AnnotatedUnionType unionType = (AnnotatedUnionType) to; + for (AnnotatedDeclaredType altern : unionType.getAlternatives()) { + altern.addMissingAnnotations(unionType.getAnnotations()); + } + } + return to; + } + + /** + * A helper method for asSuper(AMT, Wildcard) methods to use to annotate the wildcard's lower + * bound. + * + *

          If the lower bound of superType is Null, then return copyPrimarayAnnos(type, superType) + * + *

          otherwise, return asSuper(type, superType.getLowerBound() + * + *

          An error is issued if type is a Primitive or Wildcard -- those case are handled in + * asSuper(Primitive, Wildcard) and asSuper(Wildcard, Wildcard) + * + *

          An error is issued if the lower bound of superType is not Null and type is not a subtype + * of the lower bound. + */ + private AnnotatedTypeMirror asSuperWildcardLowerBound( + AnnotatedTypeMirror type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror lowerBound = superType.getSuperBound(); + return asSuperLowerBound(type, p, lowerBound); + } + + /** Same as #asSuperWildcardLowerBound, but for Typevars. */ + private AnnotatedTypeMirror asSuperTypevarLowerBound( + AnnotatedTypeMirror type, AnnotatedTypeVariable superType, Void p) { + AnnotatedTypeMirror lowerBound = superType.getLowerBound(); + return asSuperLowerBound(type, p, lowerBound); + } + + private AnnotatedTypeMirror asSuperLowerBound( + AnnotatedTypeMirror type, Void p, AnnotatedTypeMirror lowerBound) { + if (lowerBound.getKind() == TypeKind.NULL) { + AnnotationMirrorSet typeLowerBound = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type); + lowerBound.replaceAnnotations(typeLowerBound); + return lowerBound; + } + if (areErasedJavaTypesEquivalent(type, lowerBound)) { + return visit(type, lowerBound, p); + } + // If type and lowerBound are not the same type, then lowerBound is a subtype of type, + // but there is no way to convert type to a subtype -- there is not an asSub method. So, + // just copy the primary annotations. + return copyPrimaryAnnos(type, lowerBound); + } + + /** + * Returns true if the underlying, erased Java type of {@code subtype} is a subtype of the + * underlying, erased Java type of {@code supertype}. + * + * @param subtype a type + * @param supertype a type + * @return true if the underlying, erased Java type of {@code subtype} is a subtype of the + * underlying, erased Java type of {@code supertype} + */ + private boolean isErasedJavaSubtype( + AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype) { + TypeMirror javaSubtype = types.erasure(subtype.getUnderlyingType()); + TypeMirror javaSupertype = types.erasure(supertype.getUnderlyingType()); + return types.isSubtype(javaSubtype, javaSupertype); + } + + /** + * Returns true if the underlying, erased Java type of {@code typeA} and {@code typeB} are + * equivalent. + * + * @param typeA a type + * @param typeB a type + * @return true if the underlying, erased Java type of {@code typeA} and {@code typeB} are + * equivalent + */ + private boolean areErasedJavaTypesEquivalent( + AnnotatedTypeMirror typeA, AnnotatedTypeMirror typeB) { + TypeMirror underlyingTypeA = types.erasure(typeA.getUnderlyingType()); + TypeMirror underlyingTypeB = types.erasure(typeB.getUnderlyingType()); + return types.isSameType(underlyingTypeA, underlyingTypeB); + } + + // + @Override + public AnnotatedTypeMirror visitArray_Array( + AnnotatedArrayType type, AnnotatedArrayType superType, Void p) { + AnnotatedTypeMirror asSuperCT = + visit(type.getComponentType(), superType.getComponentType(), p); + superType.setComponentType(asSuperCT); + return copyPrimaryAnnos(type, superType); + } + + /** The fully-qualified names of java.lang.Cloneable and java.io.Serializable. */ + private static List cloneableOrSerializable = + Arrays.asList("java.lang.Cloneable", "java.io.Serializable"); + + @Override + public AnnotatedTypeMirror visitArray_Intersection( + AnnotatedArrayType type, AnnotatedIntersectionType superType, Void p) { + for (AnnotatedTypeMirror bounds : superType.getBounds()) { + if (!(TypesUtils.isObject(bounds.getUnderlyingType()) + || TypesUtils.isDeclaredOfName( + bounds.getUnderlyingType(), cloneableOrSerializable))) { + return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); + } + copyPrimaryAnnos(type, bounds); + } + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitArray_Declared( + AnnotatedArrayType type, AnnotatedDeclaredType superType, Void p) { + + TypeElement array = TypesUtils.getTypeElement(type.getUnderlyingType()); + TypeElement possibleArray = TypesUtils.getTypeElement(superType.getUnderlyingType()); + // If the TypeElements of type and superType are equal, then superType's underlyingType is + // Array.class. Array.class is the receiver of methods such as clone() of which an array + // can be the receiver. (new int[].clone()) + boolean isArrayClass = array.equals(possibleArray); + + if (isArrayClass + || TypesUtils.isObject(superType.getUnderlyingType()) + || TypesUtils.isDeclaredOfName( + superType.getUnderlyingType(), cloneableOrSerializable)) { + return copyPrimaryAnnos(type, superType); } - } - type.replaceAnnotations(lubs); - } - } - - private AnnotatedTypeMirror errorTypeNotErasedSubtypeOfSuperType( - AnnotatedTypeMirror type, AnnotatedTypeMirror superType, Void p) { - if (TypesUtils.isString(superType.getUnderlyingType())) { - // Any type can be converted to String - return visit(atypeFactory.getStringType(type), superType, p); - } - if (isTypeArgumentFromRawType) { - return copyPrimaryAnnos(type, superType); - } - throw new BugInCF( - "AsSuperVisitor: type is not an erased subtype of supertype." + "%ntype: %s%nsuperType: %s", - type, superType); - } - - private AnnotatedTypeMirror copyPrimaryAnnos(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - // There may have been annotations added by a recursive call to asSuper, so replace existing - // annotations - to.replaceAnnotations(new ArrayList<>(from.getAnnotations())); - // if to is a Typevar or Wildcard, then replaceAnnotations also sets primary annotations on - // the bounds to from.getAnnotations() - - if (to.getKind() == TypeKind.UNION) { - // Make sure that the alternatives have a primary annotations - // Alternatives cannot have type arguments, so asSuper isn't called recursively - AnnotatedUnionType unionType = (AnnotatedUnionType) to; - for (AnnotatedDeclaredType altern : unionType.getAlternatives()) { - altern.addMissingAnnotations(unionType.getAnnotations()); - } - } - return to; - } - - /** - * A helper method for asSuper(AMT, Wildcard) methods to use to annotate the wildcard's lower - * bound. - * - *

          If the lower bound of superType is Null, then return copyPrimarayAnnos(type, superType) - * - *

          otherwise, return asSuper(type, superType.getLowerBound() - * - *

          An error is issued if type is a Primitive or Wildcard -- those case are handled in - * asSuper(Primitive, Wildcard) and asSuper(Wildcard, Wildcard) - * - *

          An error is issued if the lower bound of superType is not Null and type is not a subtype of - * the lower bound. - */ - private AnnotatedTypeMirror asSuperWildcardLowerBound( - AnnotatedTypeMirror type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror lowerBound = superType.getSuperBound(); - return asSuperLowerBound(type, p, lowerBound); - } - - /** Same as #asSuperWildcardLowerBound, but for Typevars. */ - private AnnotatedTypeMirror asSuperTypevarLowerBound( - AnnotatedTypeMirror type, AnnotatedTypeVariable superType, Void p) { - AnnotatedTypeMirror lowerBound = superType.getLowerBound(); - return asSuperLowerBound(type, p, lowerBound); - } - - private AnnotatedTypeMirror asSuperLowerBound( - AnnotatedTypeMirror type, Void p, AnnotatedTypeMirror lowerBound) { - if (lowerBound.getKind() == TypeKind.NULL) { - AnnotationMirrorSet typeLowerBound = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type); - lowerBound.replaceAnnotations(typeLowerBound); - return lowerBound; - } - if (areErasedJavaTypesEquivalent(type, lowerBound)) { - return visit(type, lowerBound, p); - } - // If type and lowerBound are not the same type, then lowerBound is a subtype of type, - // but there is no way to convert type to a subtype -- there is not an asSub method. So, - // just copy the primary annotations. - return copyPrimaryAnnos(type, lowerBound); - } - - /** - * Returns true if the underlying, erased Java type of {@code subtype} is a subtype of the - * underlying, erased Java type of {@code supertype}. - * - * @param subtype a type - * @param supertype a type - * @return true if the underlying, erased Java type of {@code subtype} is a subtype of the - * underlying, erased Java type of {@code supertype} - */ - private boolean isErasedJavaSubtype( - AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype) { - TypeMirror javaSubtype = types.erasure(subtype.getUnderlyingType()); - TypeMirror javaSupertype = types.erasure(supertype.getUnderlyingType()); - return types.isSubtype(javaSubtype, javaSupertype); - } - - /** - * Returns true if the underlying, erased Java type of {@code typeA} and {@code typeB} are - * equivalent. - * - * @param typeA a type - * @param typeB a type - * @return true if the underlying, erased Java type of {@code typeA} and {@code typeB} are - * equivalent - */ - private boolean areErasedJavaTypesEquivalent( - AnnotatedTypeMirror typeA, AnnotatedTypeMirror typeB) { - TypeMirror underlyingTypeA = types.erasure(typeA.getUnderlyingType()); - TypeMirror underlyingTypeB = types.erasure(typeB.getUnderlyingType()); - return types.isSameType(underlyingTypeA, underlyingTypeB); - } - - // - @Override - public AnnotatedTypeMirror visitArray_Array( - AnnotatedArrayType type, AnnotatedArrayType superType, Void p) { - AnnotatedTypeMirror asSuperCT = visit(type.getComponentType(), superType.getComponentType(), p); - superType.setComponentType(asSuperCT); - return copyPrimaryAnnos(type, superType); - } - - /** The fully-qualified names of java.lang.Cloneable and java.io.Serializable. */ - private static List cloneableOrSerializable = - Arrays.asList("java.lang.Cloneable", "java.io.Serializable"); - - @Override - public AnnotatedTypeMirror visitArray_Intersection( - AnnotatedArrayType type, AnnotatedIntersectionType superType, Void p) { - for (AnnotatedTypeMirror bounds : superType.getBounds()) { - if (!(TypesUtils.isObject(bounds.getUnderlyingType()) - || TypesUtils.isDeclaredOfName(bounds.getUnderlyingType(), cloneableOrSerializable))) { return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - copyPrimaryAnnos(type, bounds); - } - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitArray_Declared( - AnnotatedArrayType type, AnnotatedDeclaredType superType, Void p) { - - TypeElement array = TypesUtils.getTypeElement(type.getUnderlyingType()); - TypeElement possibleArray = TypesUtils.getTypeElement(superType.getUnderlyingType()); - // If the TypeElements of type and superType are equal, then superType's underlyingType is - // Array.class. Array.class is the receiver of methods such as clone() of which an array - // can be the receiver. (new int[].clone()) - boolean isArrayClass = array.equals(possibleArray); - - if (isArrayClass - || TypesUtils.isObject(superType.getUnderlyingType()) - || TypesUtils.isDeclaredOfName(superType.getUnderlyingType(), cloneableOrSerializable)) { - return copyPrimaryAnnos(type, superType); - } - return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitArray_Typevar( - AnnotatedArrayType type, AnnotatedTypeVariable superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); - superType.setLowerBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitArray_Wildcard( - AnnotatedArrayType type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - // - @Override - public AnnotatedTypeMirror visitDeclared_Declared( - AnnotatedDeclaredType type, AnnotatedDeclaredType superType, Void p) { - if (areErasedJavaTypesEquivalent(type, superType)) { - return type; - } - - // Not same erased Java type. - // Walk up the directSupertypes. - // directSupertypes() annotates type variables correctly and handles substitution. - for (AnnotatedDeclaredType dst : type.directSupertypes()) { - if (isErasedJavaSubtype(dst, superType)) { - // If two direct supertypes of type, dst1 and dst2, are subtypes of superType then - // asSuper(dst1, superType) and asSuper(dst2, superType) return equivalent ATMs, so - // return the first one found. - return visit(dst, superType, p); - } - } - - return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Intersection( - AnnotatedDeclaredType type, AnnotatedIntersectionType superType, Void p) { - List newBounds = new ArrayList<>(); - // Each type in the intersection must be a supertype of type, so call asSuper on all types - // in the intersection. - for (AnnotatedTypeMirror superBound : superType.getBounds()) { - if (types.isSubtype(type.getUnderlyingType(), superBound.getUnderlyingType())) { - AnnotatedTypeMirror found = visit(type, superBound, p); - newBounds.add(found); - } - } - // The ATM for each type in an intersection is stored in the direct super types field. - superType.setBounds(newBounds); - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Primitive( - AnnotatedDeclaredType type, AnnotatedPrimitiveType superType, Void p) { - if (!TypesUtils.isBoxedPrimitive(type.getUnderlyingType())) { - throw new BugInCF("AsSuperVisitor Declared_Primitive: type is not a boxed primitive."); - } - AnnotatedTypeMirror unboxedType = atypeFactory.getUnboxedType(type); - return copyPrimaryAnnos(unboxedType, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Typevar( - AnnotatedDeclaredType type, AnnotatedTypeVariable superType, Void p) { - // setUpperBound() may have a side effect on parameter "type" when the upper bound of - // "superType" equals to "type" (referencing the same object: changes will be shared) - // copy before visiting to avoid - // without fix, this would fail: - // https://github.com/typetools/checker-framework/blob/ed340b2dfa1e51bbc0a7313f22638179d15bf2df/checker/tests/nullness/Issue2432b.java - AnnotatedTypeMirror typeCopy = type.deepCopy(); - AnnotatedTypeMirror upperBound = visit(typeCopy, superType.getUpperBound(), p).asUse(); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p).asUse(); - superType.setLowerBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Union( - AnnotatedDeclaredType type, AnnotatedUnionType superType, Void p) { - // Alternatives in a union type can't have type args, so just copy the primary annotation - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitDeclared_Wildcard( - AnnotatedDeclaredType type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p).asUse(); - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p).asUse(); - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - // - - @Override - public AnnotatedTypeMirror visitIntersection_Declared( - AnnotatedIntersectionType type, AnnotatedDeclaredType superType, Void p) { - for (AnnotatedTypeMirror bound : type.getBounds()) { - // Find the directSuperType that is a subtype of superType, then recur on that type so - // that type arguments in superType are annotated correctly. - if (bound.getKind() == TypeKind.DECLARED - && isErasedJavaSubtype((AnnotatedDeclaredType) bound, superType)) { - AnnotatedTypeMirror asSuper = visit(bound, superType, p); - - // The directSuperType might have a primary annotation that is a supertype of - // primary annotation on type. Copy the primary annotation, because it is more - // precise. - return copyPrimaryAnnos(type, asSuper); - } - } - return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Intersection( - AnnotatedIntersectionType type, AnnotatedIntersectionType superType, Void p) { - List newDirectSupertypes = new ArrayList<>(); - for (AnnotatedTypeMirror superBound : superType.getBounds()) { - AnnotatedTypeMirror found = null; - TypeMirror javaSupertype = types.erasure(superBound.getUnderlyingType()); - for (AnnotatedTypeMirror bound : type.getBounds()) { - TypeMirror javaSubtype = types.erasure(bound.getUnderlyingType()); - if (types.isSubtype(javaSubtype, javaSupertype)) { - found = visit(bound, superBound, p); - newDirectSupertypes.add(found); - break; + } + + @Override + public AnnotatedTypeMirror visitArray_Typevar( + AnnotatedArrayType type, AnnotatedTypeVariable superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); + superType.setLowerBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitArray_Wildcard( + AnnotatedArrayType type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + // + @Override + public AnnotatedTypeMirror visitDeclared_Declared( + AnnotatedDeclaredType type, AnnotatedDeclaredType superType, Void p) { + if (areErasedJavaTypesEquivalent(type, superType)) { + return type; + } + + // Not same erased Java type. + // Walk up the directSupertypes. + // directSupertypes() annotates type variables correctly and handles substitution. + for (AnnotatedDeclaredType dst : type.directSupertypes()) { + if (isErasedJavaSubtype(dst, superType)) { + // If two direct supertypes of type, dst1 and dst2, are subtypes of superType then + // asSuper(dst1, superType) and asSuper(dst2, superType) return equivalent ATMs, so + // return the first one found. + return visit(dst, superType, p); + } + } + + return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Intersection( + AnnotatedDeclaredType type, AnnotatedIntersectionType superType, Void p) { + List newBounds = new ArrayList<>(); + // Each type in the intersection must be a supertype of type, so call asSuper on all types + // in the intersection. + for (AnnotatedTypeMirror superBound : superType.getBounds()) { + if (types.isSubtype(type.getUnderlyingType(), superBound.getUnderlyingType())) { + AnnotatedTypeMirror found = visit(type, superBound, p); + newBounds.add(found); + } + } + // The ATM for each type in an intersection is stored in the direct super types field. + superType.setBounds(newBounds); + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Primitive( + AnnotatedDeclaredType type, AnnotatedPrimitiveType superType, Void p) { + if (!TypesUtils.isBoxedPrimitive(type.getUnderlyingType())) { + throw new BugInCF("AsSuperVisitor Declared_Primitive: type is not a boxed primitive."); + } + AnnotatedTypeMirror unboxedType = atypeFactory.getUnboxedType(type); + return copyPrimaryAnnos(unboxedType, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Typevar( + AnnotatedDeclaredType type, AnnotatedTypeVariable superType, Void p) { + // setUpperBound() may have a side effect on parameter "type" when the upper bound of + // "superType" equals to "type" (referencing the same object: changes will be shared) + // copy before visiting to avoid + // without fix, this would fail: + // https://github.com/typetools/checker-framework/blob/ed340b2dfa1e51bbc0a7313f22638179d15bf2df/checker/tests/nullness/Issue2432b.java + AnnotatedTypeMirror typeCopy = type.deepCopy(); + AnnotatedTypeMirror upperBound = visit(typeCopy, superType.getUpperBound(), p).asUse(); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p).asUse(); + superType.setLowerBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Union( + AnnotatedDeclaredType type, AnnotatedUnionType superType, Void p) { + // Alternatives in a union type can't have type args, so just copy the primary annotation + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitDeclared_Wildcard( + AnnotatedDeclaredType type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p).asUse(); + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p).asUse(); + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + // + + @Override + public AnnotatedTypeMirror visitIntersection_Declared( + AnnotatedIntersectionType type, AnnotatedDeclaredType superType, Void p) { + for (AnnotatedTypeMirror bound : type.getBounds()) { + // Find the directSuperType that is a subtype of superType, then recur on that type so + // that type arguments in superType are annotated correctly. + if (bound.getKind() == TypeKind.DECLARED + && isErasedJavaSubtype((AnnotatedDeclaredType) bound, superType)) { + AnnotatedTypeMirror asSuper = visit(bound, superType, p); + + // The directSuperType might have a primary annotation that is a supertype of + // primary annotation on type. Copy the primary annotation, because it is more + // precise. + return copyPrimaryAnnos(type, asSuper); + } + } + return errorTypeNotErasedSubtypeOfSuperType(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Intersection( + AnnotatedIntersectionType type, AnnotatedIntersectionType superType, Void p) { + List newDirectSupertypes = new ArrayList<>(); + for (AnnotatedTypeMirror superBound : superType.getBounds()) { + AnnotatedTypeMirror found = null; + TypeMirror javaSupertype = types.erasure(superBound.getUnderlyingType()); + for (AnnotatedTypeMirror bound : type.getBounds()) { + TypeMirror javaSubtype = types.erasure(bound.getUnderlyingType()); + if (types.isSubtype(javaSubtype, javaSupertype)) { + found = visit(bound, superBound, p); + newDirectSupertypes.add(found); + break; + } + } + if (found == null) { + throw new BugInCF( + "AsSuperVisitor visitIntersection_Intersection:%ntype: %s superType: %s", + type, superType); + } + } + superType.setBounds(newDirectSupertypes); + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Primitive( + AnnotatedIntersectionType type, AnnotatedPrimitiveType superType, Void p) { + for (AnnotatedTypeMirror bound : type.getBounds()) { + // Find the directSuperType that is a subtype of superType, then recur on that type + // so that type arguments in superType are annotated correctly + if (TypesUtils.isBoxedPrimitive(bound.getUnderlyingType())) { + AnnotatedTypeMirror asSuper = visit(bound, superType, p); + + // The directSuperType might have a primary annotation that is a supertype of + // primary annotation on type. Copy the primary annotation, because it is more + // precise. + return copyPrimaryAnnos(type, asSuper); + } } - } - if (found == null) { + // Cannot happen: one of the types in the intersection must be a subtype of superType. throw new BugInCF( - "AsSuperVisitor visitIntersection_Intersection:%ntype: %s superType: %s", - type, superType); - } - } - superType.setBounds(newDirectSupertypes); - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Primitive( - AnnotatedIntersectionType type, AnnotatedPrimitiveType superType, Void p) { - for (AnnotatedTypeMirror bound : type.getBounds()) { - // Find the directSuperType that is a subtype of superType, then recur on that type - // so that type arguments in superType are annotated correctly - if (TypesUtils.isBoxedPrimitive(bound.getUnderlyingType())) { - AnnotatedTypeMirror asSuper = visit(bound, superType, p); - - // The directSuperType might have a primary annotation that is a supertype of - // primary annotation on type. Copy the primary annotation, because it is more - // precise. + "AsSuperVisitor visitIntersection_Primitive:%ntype: %s superType: %s", + type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Typevar( + AnnotatedIntersectionType type, AnnotatedTypeVariable superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); + superType.setLowerBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Union( + AnnotatedIntersectionType type, AnnotatedUnionType superType, Void p) { + TypeMirror javaSupertype = types.erasure(type.getUnderlyingType()); + for (AnnotatedTypeMirror bound : type.getBounds()) { + TypeMirror javaSubtype = types.erasure(superType.getUnderlyingType()); + if (types.isSubtype(javaSubtype, javaSupertype)) { + AnnotatedTypeMirror asSuper = visit(bound, superType, p); + return copyPrimaryAnnos(type, asSuper); + } + } + // Cannot happen: one of the types in the intersection must be a subtype of superType. + throw new BugInCF( + "AsSuperVisitor visitIntersection_Union:%ntype: %s%nsuperType: %s", + type, superType); + } + + @Override + public AnnotatedTypeMirror visitIntersection_Wildcard( + AnnotatedIntersectionType type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + // + + @Override + public AnnotatedTypeMirror visitPrimitive_Primitive( + AnnotatedPrimitiveType type, AnnotatedPrimitiveType superType, Void p) { + return copyPrimaryAnnos(type, superType); + } + + /** + * A helper method for visiting a primitive and a non-primitive. + * + * @param type a primitive type + * @param superType some other type + * @param p ignore + * @return {@code type}, viewed as a {@code superType} + */ + private AnnotatedTypeMirror visitPrimitive_Other( + AnnotatedPrimitiveType type, AnnotatedTypeMirror superType, Void p) { + return visit(atypeFactory.getBoxedType(type), superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Declared( + AnnotatedPrimitiveType type, AnnotatedDeclaredType superType, Void p) { + if (TypesUtils.isBoxedPrimitive(superType.getUnderlyingType())) { + TypeMirror unboxedSuper = types.unboxedType(superType.getUnderlyingType()); + if (unboxedSuper.getKind() != type.getKind() + && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { + AnnotatedPrimitiveType narrowedType = + atypeFactory.getNarrowedPrimitive(type, unboxedSuper); + return visit(narrowedType, superType, p); + } + } + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Intersection( + AnnotatedPrimitiveType type, AnnotatedIntersectionType superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Typevar( + AnnotatedPrimitiveType type, AnnotatedTypeVariable superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Union( + AnnotatedPrimitiveType type, AnnotatedUnionType superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitPrimitive_Wildcard( + AnnotatedPrimitiveType type, AnnotatedWildcardType superType, Void p) { + return visitPrimitive_Other(type, superType, p); + } + + // + + // + private AnnotatedTypeMirror visitTypevar_NotTypevarNorWildcard( + AnnotatedTypeVariable type, AnnotatedTypeMirror superType, Void p) { + AnnotatedTypeMirror asSuper = visit(type.getUpperBound(), superType, p); return copyPrimaryAnnos(type, asSuper); - } - } - // Cannot happen: one of the types in the intersection must be a subtype of superType. - throw new BugInCF( - "AsSuperVisitor visitIntersection_Primitive:%ntype: %s superType: %s", type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Typevar( - AnnotatedIntersectionType type, AnnotatedTypeVariable superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperTypevarLowerBound(type, superType, p); - superType.setLowerBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Union( - AnnotatedIntersectionType type, AnnotatedUnionType superType, Void p) { - TypeMirror javaSupertype = types.erasure(type.getUnderlyingType()); - for (AnnotatedTypeMirror bound : type.getBounds()) { - TypeMirror javaSubtype = types.erasure(superType.getUnderlyingType()); - if (types.isSubtype(javaSubtype, javaSupertype)) { - AnnotatedTypeMirror asSuper = visit(bound, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Declared( + AnnotatedTypeVariable type, AnnotatedDeclaredType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Intersection( + AnnotatedTypeVariable type, AnnotatedIntersectionType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Primitive( + AnnotatedTypeVariable type, AnnotatedPrimitiveType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Typevar( + AnnotatedTypeVariable type, AnnotatedTypeVariable superType, Void p) { + // Clear the superType annotations and copy over the primary annotations before computing + // bounds, so that the superType annotations don't override the type annotations on the + // bounds. + superType.clearAnnotations(); + copyPrimaryAnnos(type, superType); + + AnnotatedTypeMirror upperBound = visit(type.getUpperBound(), superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound; + if (type.getLowerBound().getKind() == TypeKind.NULL + && superType.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getLowerBound()); + } else if (type.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getLowerBound(), p); + } else { + lowerBound = asSuperTypevarLowerBound(type.getLowerBound(), superType, p); + } + superType.setLowerBound(lowerBound); + + return superType; + } + + @Override + public AnnotatedTypeMirror visitTypevar_Union( + AnnotatedTypeVariable type, AnnotatedUnionType superType, Void p) { + return visitTypevar_NotTypevarNorWildcard(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitTypevar_Wildcard( + AnnotatedTypeVariable type, AnnotatedWildcardType superType, Void p) { + AnnotatedTypeMirror upperBound; + if (superType.getExtendsBound().getUnderlyingType().getKind() == TypeKind.TYPEVAR + && TypesUtils.areSame( + type.getUnderlyingType(), + (TypeVariable) superType.getExtendsBound().getUnderlyingType())) { + upperBound = visit(type, superType.getExtendsBound(), p); + } else { + upperBound = visit(type.getUpperBound(), superType.getExtendsBound(), p); + } + superType.setExtendsBound(upperBound); + + AnnotatedTypeMirror lowerBound; + if (type.getLowerBound().getKind() == TypeKind.NULL + && superType.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getSuperBound()); + } else if (type.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getSuperBound(), p); + } else { + lowerBound = asSuperWildcardLowerBound(type.getLowerBound(), superType, p); + } + superType.setSuperBound(lowerBound); + + return copyPrimaryAnnos(type, superType); + } + + // + + /* The primary annotation on a union type is the LUB of the primary annotations on its alternatives. #ensurePrimaryIsCorrectForUnions ensures that this is the case. + + All the alternatives in a union type must be subtype of Throwable and cannot have type arguments; + however, a union type can be a subtype of an interface with a type argument. For example: + interface Interface{} + class MyException1 extends Throwable implements Interface{} + class MyException2 extends Throwable implements Interface{} + + MyException1 <: MyException1 | MyException2 <: Interface + MyException1 | MyException2 <: Throwable & Interface + */ + // + + private AnnotatedTypeMirror visitUnion_Other( + AnnotatedUnionType type, AnnotatedTypeMirror superType, Void p) { + // asSuper on any of the alternatives is the same, so just use the first one. + AnnotatedTypeMirror asSuper = visit(type.getAlternatives().get(0), superType, p); + return copyPrimaryAnnos(type, asSuper); + } + + @Override + public AnnotatedTypeMirror visitUnion_Declared( + AnnotatedUnionType type, AnnotatedDeclaredType superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitUnion_Intersection( + AnnotatedUnionType type, AnnotatedIntersectionType superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitUnion_Typevar( + AnnotatedUnionType type, AnnotatedTypeVariable superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + @Override + public AnnotatedTypeMirror visitUnion_Union( + AnnotatedUnionType type, AnnotatedUnionType superType, Void p) { + for (AnnotatedTypeMirror superAltern : superType.getAlternatives()) { + copyPrimaryAnnos(type, superAltern); + } + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitUnion_Wildcard( + AnnotatedUnionType type, AnnotatedWildcardType superType, Void p) { + return visitUnion_Other(type, superType, p); + } + + // + + // + + /** + * Implementation of asSuper for converting wildcards to super types that are not type variables + * or wildcards. + * + * @param type the type + * @param superType the super type + * @return {@code type} converted to {@code superType} + */ + private AnnotatedTypeMirror visitWildcard_NotTypevarNorWildcard( + AnnotatedWildcardType type, AnnotatedTypeMirror superType) { + boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; + if (type.isTypeArgOfRawType()) { + isTypeArgumentFromRawType = true; + } + AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, null); + isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; + atypeFactory.addDefaultAnnotations(superType); + return copyPrimaryAnnos(type, asSuper); - } - } - // Cannot happen: one of the types in the intersection must be a subtype of superType. - throw new BugInCF( - "AsSuperVisitor visitIntersection_Union:%ntype: %s%nsuperType: %s", type, superType); - } - - @Override - public AnnotatedTypeMirror visitIntersection_Wildcard( - AnnotatedIntersectionType type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound = visit(type, superType.getExtendsBound(), p); - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound = asSuperWildcardLowerBound(type, superType, p); - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - // - - @Override - public AnnotatedTypeMirror visitPrimitive_Primitive( - AnnotatedPrimitiveType type, AnnotatedPrimitiveType superType, Void p) { - return copyPrimaryAnnos(type, superType); - } - - /** - * A helper method for visiting a primitive and a non-primitive. - * - * @param type a primitive type - * @param superType some other type - * @param p ignore - * @return {@code type}, viewed as a {@code superType} - */ - private AnnotatedTypeMirror visitPrimitive_Other( - AnnotatedPrimitiveType type, AnnotatedTypeMirror superType, Void p) { - return visit(atypeFactory.getBoxedType(type), superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Declared( - AnnotatedPrimitiveType type, AnnotatedDeclaredType superType, Void p) { - if (TypesUtils.isBoxedPrimitive(superType.getUnderlyingType())) { - TypeMirror unboxedSuper = types.unboxedType(superType.getUnderlyingType()); - if (unboxedSuper.getKind() != type.getKind() - && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { - AnnotatedPrimitiveType narrowedType = atypeFactory.getNarrowedPrimitive(type, unboxedSuper); - return visit(narrowedType, superType, p); - } - } - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Intersection( - AnnotatedPrimitiveType type, AnnotatedIntersectionType superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Typevar( - AnnotatedPrimitiveType type, AnnotatedTypeVariable superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Union( - AnnotatedPrimitiveType type, AnnotatedUnionType superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitPrimitive_Wildcard( - AnnotatedPrimitiveType type, AnnotatedWildcardType superType, Void p) { - return visitPrimitive_Other(type, superType, p); - } - - // - - // - private AnnotatedTypeMirror visitTypevar_NotTypevarNorWildcard( - AnnotatedTypeVariable type, AnnotatedTypeMirror superType, Void p) { - AnnotatedTypeMirror asSuper = visit(type.getUpperBound(), superType, p); - return copyPrimaryAnnos(type, asSuper); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Declared( - AnnotatedTypeVariable type, AnnotatedDeclaredType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Intersection( - AnnotatedTypeVariable type, AnnotatedIntersectionType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Primitive( - AnnotatedTypeVariable type, AnnotatedPrimitiveType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Typevar( - AnnotatedTypeVariable type, AnnotatedTypeVariable superType, Void p) { - // Clear the superType annotations and copy over the primary annotations before computing - // bounds, so that the superType annotations don't override the type annotations on the - // bounds. - superType.clearAnnotations(); - copyPrimaryAnnos(type, superType); - - AnnotatedTypeMirror upperBound = visit(type.getUpperBound(), superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound; - if (type.getLowerBound().getKind() == TypeKind.NULL - && superType.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getLowerBound()); - } else if (type.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getLowerBound(), p); - } else { - lowerBound = asSuperTypevarLowerBound(type.getLowerBound(), superType, p); - } - superType.setLowerBound(lowerBound); - - return superType; - } - - @Override - public AnnotatedTypeMirror visitTypevar_Union( - AnnotatedTypeVariable type, AnnotatedUnionType superType, Void p) { - return visitTypevar_NotTypevarNorWildcard(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitTypevar_Wildcard( - AnnotatedTypeVariable type, AnnotatedWildcardType superType, Void p) { - AnnotatedTypeMirror upperBound; - if (superType.getExtendsBound().getUnderlyingType().getKind() == TypeKind.TYPEVAR - && TypesUtils.areSame( - type.getUnderlyingType(), - (TypeVariable) superType.getExtendsBound().getUnderlyingType())) { - upperBound = visit(type, superType.getExtendsBound(), p); - } else { - upperBound = visit(type.getUpperBound(), superType.getExtendsBound(), p); - } - superType.setExtendsBound(upperBound); - - AnnotatedTypeMirror lowerBound; - if (type.getLowerBound().getKind() == TypeKind.NULL - && superType.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getLowerBound(), superType.getSuperBound()); - } else if (type.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getSuperBound(), p); - } else { - lowerBound = asSuperWildcardLowerBound(type.getLowerBound(), superType, p); - } - superType.setSuperBound(lowerBound); - - return copyPrimaryAnnos(type, superType); - } - - // - - /* The primary annotation on a union type is the LUB of the primary annotations on its alternatives. #ensurePrimaryIsCorrectForUnions ensures that this is the case. - - All the alternatives in a union type must be subtype of Throwable and cannot have type arguments; - however, a union type can be a subtype of an interface with a type argument. For example: - interface Interface{} - class MyException1 extends Throwable implements Interface{} - class MyException2 extends Throwable implements Interface{} - - MyException1 <: MyException1 | MyException2 <: Interface - MyException1 | MyException2 <: Throwable & Interface - */ - // - - private AnnotatedTypeMirror visitUnion_Other( - AnnotatedUnionType type, AnnotatedTypeMirror superType, Void p) { - // asSuper on any of the alternatives is the same, so just use the first one. - AnnotatedTypeMirror asSuper = visit(type.getAlternatives().get(0), superType, p); - return copyPrimaryAnnos(type, asSuper); - } - - @Override - public AnnotatedTypeMirror visitUnion_Declared( - AnnotatedUnionType type, AnnotatedDeclaredType superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitUnion_Intersection( - AnnotatedUnionType type, AnnotatedIntersectionType superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitUnion_Typevar( - AnnotatedUnionType type, AnnotatedTypeVariable superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - @Override - public AnnotatedTypeMirror visitUnion_Union( - AnnotatedUnionType type, AnnotatedUnionType superType, Void p) { - for (AnnotatedTypeMirror superAltern : superType.getAlternatives()) { - copyPrimaryAnnos(type, superAltern); - } - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitUnion_Wildcard( - AnnotatedUnionType type, AnnotatedWildcardType superType, Void p) { - return visitUnion_Other(type, superType, p); - } - - // - - // - - /** - * Implementation of asSuper for converting wildcards to super types that are not type variables - * or wildcards. - * - * @param type the type - * @param superType the super type - * @return {@code type} converted to {@code superType} - */ - private AnnotatedTypeMirror visitWildcard_NotTypevarNorWildcard( - AnnotatedWildcardType type, AnnotatedTypeMirror superType) { - boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; - if (type.isTypeArgOfRawType()) { - isTypeArgumentFromRawType = true; - } - AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, null); - isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; - atypeFactory.addDefaultAnnotations(superType); - - return copyPrimaryAnnos(type, asSuper); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Array( - AnnotatedWildcardType type, AnnotatedArrayType superType, Void p) { - return visitWildcard_NotTypevarNorWildcard(type, superType); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Declared( - AnnotatedWildcardType type, AnnotatedDeclaredType superType, Void p) { - return visitWildcard_NotTypevarNorWildcard(type, superType); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Intersection( - AnnotatedWildcardType type, AnnotatedIntersectionType superType, Void p) { - return visitWildcard_NotTypevarNorWildcard(type, superType); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Primitive( - AnnotatedWildcardType type, AnnotatedPrimitiveType superType, Void p) { - return visitWildcard_NotTypevarNorWildcard(type, superType); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Typevar( - AnnotatedWildcardType type, AnnotatedTypeVariable superType, Void p) { - boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; - if (type.isTypeArgOfRawType()) { - isTypeArgumentFromRawType = true; - } - AnnotatedTypeMirror upperBound = visit(type.getExtendsBound(), superType.getUpperBound(), p); - superType.setUpperBound(upperBound); - - AnnotatedTypeMirror lowerBound; - if (type.getSuperBound().getKind() == TypeKind.NULL - && superType.getLowerBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getLowerBound()); - } else if (type.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getLowerBound(), p); - } else { - lowerBound = asSuperTypevarLowerBound(type.getSuperBound(), superType, p); - } - superType.setLowerBound(lowerBound); - isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; - atypeFactory.addDefaultAnnotations(superType); - - return copyPrimaryAnnos(type, superType); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Union( - AnnotatedWildcardType type, AnnotatedUnionType superType, Void p) { - return visitWildcard_NotTypevarNorWildcard(type, superType); - } - - @Override - public AnnotatedTypeMirror visitWildcard_Wildcard( - AnnotatedWildcardType type, AnnotatedWildcardType superType, Void p) { - boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; - if (type.isTypeArgOfRawType()) { - isTypeArgumentFromRawType = true; - superType.setTypeArgOfRawType(); - } - if (types.isSubtype( - type.getExtendsBound().getUnderlyingType(), - superType.getExtendsBound().getUnderlyingType())) { - AnnotatedTypeMirror upperBound = - visit(type.getExtendsBound(), superType.getExtendsBound(), p); - superType.setExtendsBound(upperBound); - } else { - // The upper bound of a wildcard can be a super type of upper bound of the type - // parameter for which it is an argument. - // See org.checkerframework.framework.type.AnnotatedTypeFactory.widenToUpperBound for an - // example. In these cases, the upper bound of type might be a super type of the - // upper bound of superType. - - // The underlying type of the annotated type mirror returned by asSuper must be the - // same as the passed type, so just copy the primary annotations. - copyPrimaryAnnos(type.getExtendsBound(), superType.getExtendsBound()); - - // Add defaults in case any locations are missing annotations. - atypeFactory.addDefaultAnnotations(superType.getExtendsBound()); - } - - AnnotatedTypeMirror lowerBound; - if (type.getSuperBound().getKind() == TypeKind.NULL - && superType.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getSuperBound()); - } else if (type.getSuperBound().getKind() == TypeKind.NULL) { - lowerBound = visit(type, superType.getSuperBound(), p); - } else { - lowerBound = asSuperWildcardLowerBound(type.getSuperBound(), superType, p); - } - superType.setSuperBound(lowerBound); - isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; - atypeFactory.addDefaultAnnotations(superType); - - return copyPrimaryAnnos(type, superType); - } - - /** - * Returns true if the atypeFactory for this is the given value. - * - * @param atypeFactory a factory to compare to that of this - * @return true if the atypeFactory for this is the given value - */ - public boolean sameAnnotatedTypeFactory(@FindDistinct AnnotatedTypeFactory atypeFactory) { - return this.atypeFactory == atypeFactory; - } - // + } + + @Override + public AnnotatedTypeMirror visitWildcard_Array( + AnnotatedWildcardType type, AnnotatedArrayType superType, Void p) { + return visitWildcard_NotTypevarNorWildcard(type, superType); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Declared( + AnnotatedWildcardType type, AnnotatedDeclaredType superType, Void p) { + return visitWildcard_NotTypevarNorWildcard(type, superType); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Intersection( + AnnotatedWildcardType type, AnnotatedIntersectionType superType, Void p) { + return visitWildcard_NotTypevarNorWildcard(type, superType); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Primitive( + AnnotatedWildcardType type, AnnotatedPrimitiveType superType, Void p) { + return visitWildcard_NotTypevarNorWildcard(type, superType); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Typevar( + AnnotatedWildcardType type, AnnotatedTypeVariable superType, Void p) { + boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; + if (type.isTypeArgOfRawType()) { + isTypeArgumentFromRawType = true; + } + AnnotatedTypeMirror upperBound = + visit(type.getExtendsBound(), superType.getUpperBound(), p); + superType.setUpperBound(upperBound); + + AnnotatedTypeMirror lowerBound; + if (type.getSuperBound().getKind() == TypeKind.NULL + && superType.getLowerBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getLowerBound()); + } else if (type.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getLowerBound(), p); + } else { + lowerBound = asSuperTypevarLowerBound(type.getSuperBound(), superType, p); + } + superType.setLowerBound(lowerBound); + isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; + atypeFactory.addDefaultAnnotations(superType); + + return copyPrimaryAnnos(type, superType); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Union( + AnnotatedWildcardType type, AnnotatedUnionType superType, Void p) { + return visitWildcard_NotTypevarNorWildcard(type, superType); + } + + @Override + public AnnotatedTypeMirror visitWildcard_Wildcard( + AnnotatedWildcardType type, AnnotatedWildcardType superType, Void p) { + boolean oldIsTypeArgumentFromRawType = isTypeArgumentFromRawType; + if (type.isTypeArgOfRawType()) { + isTypeArgumentFromRawType = true; + superType.setTypeArgOfRawType(); + } + if (types.isSubtype( + type.getExtendsBound().getUnderlyingType(), + superType.getExtendsBound().getUnderlyingType())) { + AnnotatedTypeMirror upperBound = + visit(type.getExtendsBound(), superType.getExtendsBound(), p); + superType.setExtendsBound(upperBound); + } else { + // The upper bound of a wildcard can be a super type of upper bound of the type + // parameter for which it is an argument. + // See org.checkerframework.framework.type.AnnotatedTypeFactory.widenToUpperBound for an + // example. In these cases, the upper bound of type might be a super type of the + // upper bound of superType. + + // The underlying type of the annotated type mirror returned by asSuper must be the + // same as the passed type, so just copy the primary annotations. + copyPrimaryAnnos(type.getExtendsBound(), superType.getExtendsBound()); + + // Add defaults in case any locations are missing annotations. + atypeFactory.addDefaultAnnotations(superType.getExtendsBound()); + } + + AnnotatedTypeMirror lowerBound; + if (type.getSuperBound().getKind() == TypeKind.NULL + && superType.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = copyPrimaryAnnos(type.getSuperBound(), superType.getSuperBound()); + } else if (type.getSuperBound().getKind() == TypeKind.NULL) { + lowerBound = visit(type, superType.getSuperBound(), p); + } else { + lowerBound = asSuperWildcardLowerBound(type.getSuperBound(), superType, p); + } + superType.setSuperBound(lowerBound); + isTypeArgumentFromRawType = oldIsTypeArgumentFromRawType; + atypeFactory.addDefaultAnnotations(superType); + + return copyPrimaryAnnos(type, superType); + } + + /** + * Returns true if the atypeFactory for this is the given value. + * + * @param atypeFactory a factory to compare to that of this + * @return true if the atypeFactory for this is the given value + */ + public boolean sameAnnotatedTypeFactory(@FindDistinct AnnotatedTypeFactory atypeFactory) { + return this.atypeFactory == atypeFactory; + } + // } diff --git a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java index a9240ef177c..7213bf174d1 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java @@ -1,9 +1,21 @@ package org.checkerframework.framework.type; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypeAnnotationUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.ArrayType; @@ -21,16 +33,6 @@ import javax.lang.model.type.UnionType; import javax.lang.model.type.WildcardType; import javax.lang.model.util.Types; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; /** * {@code BoundsInitializer} creates AnnotatedTypeMirrors (without annotations) for the bounds of @@ -44,261 +46,270 @@ */ public class BoundsInitializer { - /** Class cannot be instantiated. */ - private BoundsInitializer() { - throw new AssertionError("Class BoundsInitializer cannot be instantiated."); - } - - /** - * Creates and sets the upper and lower bounds of {@code typeVar} that match the upper and lower - * bounds of the underlying type of {@code typeVar}. - * - * @param typeVar an {@link AnnotatedTypeVariable} - */ - public static void initializeBounds(AnnotatedTypeVariable typeVar) { - new BoundInitializerVisitor(typeVar.atypeFactory).initializeTypeVariable(typeVar); - } - - /** - * Creates and sets the extends and super bounds of {@code wildcard} that match the extends and - * super bounds of the underlying type of {@code wildcard}. - * - * @param wildcard an {@link AnnotatedWildcardType} - */ - public static void initializeBounds(AnnotatedWildcardType wildcard) { - new BoundInitializerVisitor(wildcard.atypeFactory).initializeWildcard(wildcard); - } - - /** - * Returns a wildcard whose extends bound is the same as {@code typeVariable}'s upper bound. If - * the upper bound is an intersection, then this method returns an unbound wildcard. - * - * @param typeVariable a type variable - * @param types types util - * @return a wildcard whose extends bound is the same as the upper bound of {@code typeVariable} - */ - public static WildcardType getUpperBoundAsWildcard(TypeVariable typeVariable, Types types) { - TypeMirror upperBound = typeVariable.getUpperBound(); - switch (upperBound.getKind()) { - case ARRAY: - case DECLARED: - case TYPEVAR: - return types.getWildcardType(upperBound, null); - case INTERSECTION: - // Can't create a wildcard with an intersection as an extends bound, so use - // an unbound wildcard instead. - return types.getWildcardType(null, null); - default: - throw new BugInCF( - "Unexpected upper bound kind: %s type: %s", upperBound.getKind(), upperBound); + /** Class cannot be instantiated. */ + private BoundsInitializer() { + throw new AssertionError("Class BoundsInitializer cannot be instantiated."); } - } - - /** - * A class that creates an {@link AnnotatedTypeMirror} to match a TypeMirror. This visitor is only - * used to initialize recursive type variables or wildcards, because at some point instead of - * creating a new type, a previously created type is returned. This makes the {@code - * AnnotatedTypeMirror} recursive. - */ - private static class BoundInitializerVisitor implements TypeVisitor { - - /** AnnotatedTypeFactory used to create AnnotatedTypeMirrors. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * A map from a Java type variable to its {@link AnnotatedTypeVariable}. Used to set up - * recursive type variables. - */ - private final Map typeVarToAtm = new HashMap<>(); - - /** - * A map from a Java wildcard to its {@link AnnotatedWildcardType}. Used to set up recursive - * wildcards. - */ - private final Map wildcardToAtm = new HashMap<>(); - - /** - * A map from a Java type variable in a raw type to an {@link AnnotatedWildcardType}. Used to - * set up recursive type variables. - */ - private final Map typeParamToWildcard = new HashMap<>(); /** - * Creates a {@link BoundInitializerVisitor}. + * Creates and sets the upper and lower bounds of {@code typeVar} that match the upper and lower + * bounds of the underlying type of {@code typeVar}. * - * @param atypeFactory the type factory + * @param typeVar an {@link AnnotatedTypeVariable} */ - public BoundInitializerVisitor(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; + public static void initializeBounds(AnnotatedTypeVariable typeVar) { + new BoundInitializerVisitor(typeVar.atypeFactory).initializeTypeVariable(typeVar); } /** - * Creates a {@link AnnotatedTypeMirror} with the same structure as {@code javaType}. + * Creates and sets the extends and super bounds of {@code wildcard} that match the extends and + * super bounds of the underlying type of {@code wildcard}. * - * @param javaType a Java type - * @return a new {@link AnnotatedTypeMirror} with the same structure as {@code javaType} + * @param wildcard an {@link AnnotatedWildcardType} */ - private AnnotatedTypeMirror createAnnotatedType(TypeMirror javaType) { - return AnnotatedTypeMirror.createType(javaType, atypeFactory, false); + public static void initializeBounds(AnnotatedWildcardType wildcard) { + new BoundInitializerVisitor(wildcard.atypeFactory).initializeWildcard(wildcard); } /** - * Sets the upper and lower bounds of {@code annotatedTypeVariable} to {@code - * AnnotatedTypeMirror} that match the upper and lower bounds of the underlying type of {@code - * annotatedTypeVariable} by visiting each bound. This method should only be called once per - * {@link TypeVariable}. + * Returns a wildcard whose extends bound is the same as {@code typeVariable}'s upper bound. If + * the upper bound is an intersection, then this method returns an unbound wildcard. * - * @param annotatedTypeVariable an annotated type variable + * @param typeVariable a type variable + * @param types types util + * @return a wildcard whose extends bound is the same as the upper bound of {@code typeVariable} */ - private void initializeTypeVariable(AnnotatedTypeVariable annotatedTypeVariable) { - TypeVariable t = annotatedTypeVariable.getUnderlyingType(); - if (!annotatedTypeVariable.isDeclaration()) { - t = (TypeVariable) TypeAnnotationUtils.unannotatedType(t); - typeVarToAtm.put(t, annotatedTypeVariable); - } - - TypeMirror lowerBound = TypesUtils.getTypeVariableLowerBound(t, atypeFactory.processingEnv); - annotatedTypeVariable.setLowerBound(visit(lowerBound)); - annotatedTypeVariable.setUpperBound(visit(t.getUpperBound())); + public static WildcardType getUpperBoundAsWildcard(TypeVariable typeVariable, Types types) { + TypeMirror upperBound = typeVariable.getUpperBound(); + switch (upperBound.getKind()) { + case ARRAY: + case DECLARED: + case TYPEVAR: + return types.getWildcardType(upperBound, null); + case INTERSECTION: + // Can't create a wildcard with an intersection as an extends bound, so use + // an unbound wildcard instead. + return types.getWildcardType(null, null); + default: + throw new BugInCF( + "Unexpected upper bound kind: %s type: %s", + upperBound.getKind(), upperBound); + } } /** - * Sets the extends and super bounds of {@code annotatedWildcardType} to {@code - * AnnotatedTypeMirror} that match the upper and lower bounds of the underlying type of {@code - * annotatedWildcardType} by calling visiting each bound. This method should only be called once - * per {@link WildcardType}. - * - * @param annotatedWildcardType an annotated wildcard type + * A class that creates an {@link AnnotatedTypeMirror} to match a TypeMirror. This visitor is + * only used to initialize recursive type variables or wildcards, because at some point instead + * of creating a new type, a previously created type is returned. This makes the {@code + * AnnotatedTypeMirror} recursive. */ - private void initializeWildcard(AnnotatedWildcardType annotatedWildcardType) { - WildcardType t = annotatedWildcardType.getUnderlyingType(); - wildcardToAtm.put(t, annotatedWildcardType); - - TypeMirror lowerBound = TypesUtils.wildLowerBound(t, atypeFactory.processingEnv); - annotatedWildcardType.setSuperBound(visit(lowerBound)); - TypeMirror upperBound = t.getExtendsBound(); - if (upperBound == null) { - upperBound = TypesUtils.getObjectTypeMirror(atypeFactory.processingEnv); - } - annotatedWildcardType.setExtendsBound(visit(upperBound)); - } + private static class BoundInitializerVisitor implements TypeVisitor { + + /** AnnotatedTypeFactory used to create AnnotatedTypeMirrors. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * A map from a Java type variable to its {@link AnnotatedTypeVariable}. Used to set up + * recursive type variables. + */ + private final Map typeVarToAtm = new HashMap<>(); + + /** + * A map from a Java wildcard to its {@link AnnotatedWildcardType}. Used to set up recursive + * wildcards. + */ + private final Map wildcardToAtm = new HashMap<>(); + + /** + * A map from a Java type variable in a raw type to an {@link AnnotatedWildcardType}. Used + * to set up recursive type variables. + */ + private final Map typeParamToWildcard = + new HashMap<>(); + + /** + * Creates a {@link BoundInitializerVisitor}. + * + * @param atypeFactory the type factory + */ + public BoundInitializerVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - @Override - public AnnotatedTypeMirror visit(TypeMirror t, Void unused) { - return t.accept(this, null); - } + /** + * Creates a {@link AnnotatedTypeMirror} with the same structure as {@code javaType}. + * + * @param javaType a Java type + * @return a new {@link AnnotatedTypeMirror} with the same structure as {@code javaType} + */ + private AnnotatedTypeMirror createAnnotatedType(TypeMirror javaType) { + return AnnotatedTypeMirror.createType(javaType, atypeFactory, false); + } - @Override - public AnnotatedTypeMirror visitPrimitive(PrimitiveType t, Void unused) { - return createAnnotatedType(t); - } + /** + * Sets the upper and lower bounds of {@code annotatedTypeVariable} to {@code + * AnnotatedTypeMirror} that match the upper and lower bounds of the underlying type of + * {@code annotatedTypeVariable} by visiting each bound. This method should only be called + * once per {@link TypeVariable}. + * + * @param annotatedTypeVariable an annotated type variable + */ + private void initializeTypeVariable(AnnotatedTypeVariable annotatedTypeVariable) { + TypeVariable t = annotatedTypeVariable.getUnderlyingType(); + if (!annotatedTypeVariable.isDeclaration()) { + t = (TypeVariable) TypeAnnotationUtils.unannotatedType(t); + typeVarToAtm.put(t, annotatedTypeVariable); + } + + TypeMirror lowerBound = + TypesUtils.getTypeVariableLowerBound(t, atypeFactory.processingEnv); + annotatedTypeVariable.setLowerBound(visit(lowerBound)); + annotatedTypeVariable.setUpperBound(visit(t.getUpperBound())); + } - @Override - public AnnotatedTypeMirror visitNull(NullType t, Void unused) { - return createAnnotatedType(t); - } + /** + * Sets the extends and super bounds of {@code annotatedWildcardType} to {@code + * AnnotatedTypeMirror} that match the upper and lower bounds of the underlying type of + * {@code annotatedWildcardType} by calling visiting each bound. This method should only be + * called once per {@link WildcardType}. + * + * @param annotatedWildcardType an annotated wildcard type + */ + private void initializeWildcard(AnnotatedWildcardType annotatedWildcardType) { + WildcardType t = annotatedWildcardType.getUnderlyingType(); + wildcardToAtm.put(t, annotatedWildcardType); + + TypeMirror lowerBound = TypesUtils.wildLowerBound(t, atypeFactory.processingEnv); + annotatedWildcardType.setSuperBound(visit(lowerBound)); + TypeMirror upperBound = t.getExtendsBound(); + if (upperBound == null) { + upperBound = TypesUtils.getObjectTypeMirror(atypeFactory.processingEnv); + } + annotatedWildcardType.setExtendsBound(visit(upperBound)); + } - @Override - public AnnotatedTypeMirror visitArray(ArrayType t, Void unused) { - AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) createAnnotatedType(t); - annotatedArrayType.setComponentType(visit(t.getComponentType())); - return annotatedArrayType; - } + @Override + public AnnotatedTypeMirror visit(TypeMirror t, Void unused) { + return t.accept(this, null); + } - @Override - public AnnotatedTypeMirror visitDeclared(DeclaredType t, Void unused) { - AnnotatedDeclaredType annotatedDeclaredType = (AnnotatedDeclaredType) createAnnotatedType(t); - if (t.getEnclosingType() != null && t.getEnclosingType().getKind() == TypeKind.DECLARED) { - annotatedDeclaredType.setEnclosingType((AnnotatedDeclaredType) visit(t.getEnclosingType())); - } - - TypeElement typeElement = (TypeElement) atypeFactory.types.asElement(t); - List typeArgs = new ArrayList<>(typeElement.getTypeParameters().size()); - if (annotatedDeclaredType.isUnderlyingTypeRaw()) { - for (TypeParameterElement typeParameterEle : typeElement.getTypeParameters()) { - TypeVariable typeVar = (TypeVariable) typeParameterEle.asType(); - AnnotatedWildcardType wildcardType = typeParamToWildcard.get(typeVar); - if (wildcardType == null) { - TypeMirror javaTypeArg = getUpperBoundAsWildcard(typeVar, atypeFactory.types); - wildcardType = (AnnotatedWildcardType) createAnnotatedType(javaTypeArg); - wildcardType.setTypeArgOfRawType(); - typeParamToWildcard.put(typeVar, wildcardType); - initializeWildcard(wildcardType); - } - typeArgs.add(wildcardType); + @Override + public AnnotatedTypeMirror visitPrimitive(PrimitiveType t, Void unused) { + return createAnnotatedType(t); } - } else { - for (TypeMirror javaTypeArg : t.getTypeArguments()) { - typeArgs.add(visit(javaTypeArg)); + + @Override + public AnnotatedTypeMirror visitNull(NullType t, Void unused) { + return createAnnotatedType(t); } - } - annotatedDeclaredType.setTypeArguments(typeArgs); - return annotatedDeclaredType; - } + @Override + public AnnotatedTypeMirror visitArray(ArrayType t, Void unused) { + AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) createAnnotatedType(t); + annotatedArrayType.setComponentType(visit(t.getComponentType())); + return annotatedArrayType; + } + + @Override + public AnnotatedTypeMirror visitDeclared(DeclaredType t, Void unused) { + AnnotatedDeclaredType annotatedDeclaredType = + (AnnotatedDeclaredType) createAnnotatedType(t); + if (t.getEnclosingType() != null + && t.getEnclosingType().getKind() == TypeKind.DECLARED) { + annotatedDeclaredType.setEnclosingType( + (AnnotatedDeclaredType) visit(t.getEnclosingType())); + } + + TypeElement typeElement = (TypeElement) atypeFactory.types.asElement(t); + List typeArgs = + new ArrayList<>(typeElement.getTypeParameters().size()); + if (annotatedDeclaredType.isUnderlyingTypeRaw()) { + for (TypeParameterElement typeParameterEle : typeElement.getTypeParameters()) { + TypeVariable typeVar = (TypeVariable) typeParameterEle.asType(); + AnnotatedWildcardType wildcardType = typeParamToWildcard.get(typeVar); + if (wildcardType == null) { + TypeMirror javaTypeArg = + getUpperBoundAsWildcard(typeVar, atypeFactory.types); + wildcardType = (AnnotatedWildcardType) createAnnotatedType(javaTypeArg); + wildcardType.setTypeArgOfRawType(); + typeParamToWildcard.put(typeVar, wildcardType); + initializeWildcard(wildcardType); + } + typeArgs.add(wildcardType); + } + } else { + for (TypeMirror javaTypeArg : t.getTypeArguments()) { + typeArgs.add(visit(javaTypeArg)); + } + } + + annotatedDeclaredType.setTypeArguments(typeArgs); + return annotatedDeclaredType; + } - @Override - public AnnotatedTypeMirror visitTypeVariable(TypeVariable t, Void unused) { - t = (TypeVariable) TypeAnnotationUtils.unannotatedType(t); - AnnotatedTypeVariable annotatedTypeVariable = typeVarToAtm.get(t); - if (annotatedTypeVariable != null) { - return annotatedTypeVariable; - } + @Override + public AnnotatedTypeMirror visitTypeVariable(TypeVariable t, Void unused) { + t = (TypeVariable) TypeAnnotationUtils.unannotatedType(t); + AnnotatedTypeVariable annotatedTypeVariable = typeVarToAtm.get(t); + if (annotatedTypeVariable != null) { + return annotatedTypeVariable; + } - annotatedTypeVariable = (AnnotatedTypeVariable) createAnnotatedType(t); - initializeTypeVariable(annotatedTypeVariable); + annotatedTypeVariable = (AnnotatedTypeVariable) createAnnotatedType(t); + initializeTypeVariable(annotatedTypeVariable); - return annotatedTypeVariable; - } + return annotatedTypeVariable; + } - @Override - public AnnotatedTypeMirror visitWildcard(WildcardType t, Void unused) { - AnnotatedWildcardType annotatedWildcardType = wildcardToAtm.get(t); - if (annotatedWildcardType != null) { - return annotatedWildcardType; - } - annotatedWildcardType = (AnnotatedWildcardType) createAnnotatedType(t); - initializeWildcard(annotatedWildcardType); + @Override + public AnnotatedTypeMirror visitWildcard(WildcardType t, Void unused) { + AnnotatedWildcardType annotatedWildcardType = wildcardToAtm.get(t); + if (annotatedWildcardType != null) { + return annotatedWildcardType; + } + annotatedWildcardType = (AnnotatedWildcardType) createAnnotatedType(t); + initializeWildcard(annotatedWildcardType); - return annotatedWildcardType; - } + return annotatedWildcardType; + } - @Override - public AnnotatedTypeMirror visitExecutable(ExecutableType t, Void unused) { - throw new RuntimeException("Don't do this"); - } + @Override + public AnnotatedTypeMirror visitExecutable(ExecutableType t, Void unused) { + throw new RuntimeException("Don't do this"); + } - @Override - public AnnotatedTypeMirror visitNoType(NoType t, Void unused) { - return createAnnotatedType(t); - } + @Override + public AnnotatedTypeMirror visitNoType(NoType t, Void unused) { + return createAnnotatedType(t); + } - @Override - public AnnotatedTypeMirror visitUnion(UnionType t, Void unused) { - AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) createAnnotatedType(t); + @Override + public AnnotatedTypeMirror visitUnion(UnionType t, Void unused) { + AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) createAnnotatedType(t); - annotatedUnionType.alternatives = - CollectionsPlume.mapList( - alternative -> (AnnotatedDeclaredType) visit(alternative), t.getAlternatives()); - return annotatedUnionType; - } + annotatedUnionType.alternatives = + CollectionsPlume.mapList( + alternative -> (AnnotatedDeclaredType) visit(alternative), + t.getAlternatives()); + return annotatedUnionType; + } - @Override - public AnnotatedTypeMirror visitIntersection(IntersectionType t, Void unused) { - AnnotatedIntersectionType annotatedIntersectionType = - (AnnotatedIntersectionType) createAnnotatedType(t); - annotatedIntersectionType.bounds = CollectionsPlume.mapList(this::visit, t.getBounds()); - return annotatedIntersectionType; - } + @Override + public AnnotatedTypeMirror visitIntersection(IntersectionType t, Void unused) { + AnnotatedIntersectionType annotatedIntersectionType = + (AnnotatedIntersectionType) createAnnotatedType(t); + annotatedIntersectionType.bounds = CollectionsPlume.mapList(this::visit, t.getBounds()); + return annotatedIntersectionType; + } - @Override - public AnnotatedTypeMirror visitError(ErrorType t, Void unused) { - return createAnnotatedType(t); - } + @Override + public AnnotatedTypeMirror visitError(ErrorType t, Void unused) { + return createAnnotatedType(t); + } - @Override - public AnnotatedTypeMirror visitUnknown(TypeMirror t, Void unused) { - return createAnnotatedType(t); + @Override + public AnnotatedTypeMirror visitUnknown(TypeMirror t, Void unused) { + return createAnnotatedType(t); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java b/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java index 76efc2b6e7e..6e8e9057adf 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DeclarationsIntoElements.java @@ -6,14 +6,16 @@ import com.sun.tools.javac.code.Attribute.Compound; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; + import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeAnnotationUtils; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + /** * A helper class that puts the declaration annotations from a method declaration back into the * corresponding Elements, so that they get stored in the bytecode by the compiler. @@ -24,50 +26,50 @@ */ public final class DeclarationsIntoElements { - /** Do not instantiate. */ - private DeclarationsIntoElements() { - throw new AssertionError("Class DeclarationsIntoElements cannot be instantiated."); - } + /** Do not instantiate. */ + private DeclarationsIntoElements() { + throw new AssertionError("Class DeclarationsIntoElements cannot be instantiated."); + } - /** - * The entry point. - * - * @param atypeFactory the type factory - * @param tree the ClassTree to process - */ - public static void store( - ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, ClassTree tree) { - for (Tree mem : tree.getMembers()) { - if (mem.getKind() == Tree.Kind.METHOD) { - storeMethod(env, atypeFactory, (MethodTree) mem); - } + /** + * The entry point. + * + * @param atypeFactory the type factory + * @param tree the ClassTree to process + */ + public static void store( + ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, ClassTree tree) { + for (Tree mem : tree.getMembers()) { + if (mem.getKind() == Tree.Kind.METHOD) { + storeMethod(env, atypeFactory, (MethodTree) mem); + } + } } - } - /** - * Add inherited declaration annotations from overridden methods into the corresponding Elements - * so they are written into bytecode. - * - * @param env the processing environment - * @param atypeFactory the type factory - * @param meth the MethodTree to add the annotations - */ - private static void storeMethod( - ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, MethodTree meth) { - ExecutableElement element = TreeUtils.elementFromDeclaration(meth); - MethodSymbol sym = (MethodSymbol) element; - java.util.List elementAnnos = element.getAnnotationMirrors(); + /** + * Add inherited declaration annotations from overridden methods into the corresponding Elements + * so they are written into bytecode. + * + * @param env the processing environment + * @param atypeFactory the type factory + * @param meth the MethodTree to add the annotations + */ + private static void storeMethod( + ProcessingEnvironment env, AnnotatedTypeFactory atypeFactory, MethodTree meth) { + ExecutableElement element = TreeUtils.elementFromDeclaration(meth); + MethodSymbol sym = (MethodSymbol) element; + java.util.List elementAnnos = element.getAnnotationMirrors(); - AnnotationMirrorSet declAnnotations = atypeFactory.getDeclAnnotations(sym); - List tcs = List.nil(); + AnnotationMirrorSet declAnnotations = atypeFactory.getDeclAnnotations(sym); + List tcs = List.nil(); - for (AnnotationMirror anno : declAnnotations) { - // Only add the annotation if it isn't in the Element already. - if (!AnnotationUtils.containsSame(elementAnnos, anno)) { - tcs = tcs.append(TypeAnnotationUtils.createCompoundFromAnnotationMirror(anno, env)); - } - } + for (AnnotationMirror anno : declAnnotations) { + // Only add the annotation if it isn't in the Element already. + if (!AnnotationUtils.containsSame(elementAnnos, anno)) { + tcs = tcs.append(TypeAnnotationUtils.createCompoundFromAnnotationMirror(anno, env)); + } + } - sym.appendAttributes(tcs); - } + sym.appendAttributes(tcs); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java index 7a3f52d4ded..6a12dd075c3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java @@ -1,15 +1,7 @@ package org.checkerframework.framework.type; import com.sun.tools.javac.code.Type; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -28,6 +20,17 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.WeakIdentityHashMap; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVariable; + /** * An AnnotatedTypeFormatter used by default by all AnnotatedTypeFactory (and therefore all * annotated types). @@ -37,473 +40,479 @@ */ public class DefaultAnnotatedTypeFormatter implements AnnotatedTypeFormatter { - /** The formatting visitor. */ - protected final FormattingVisitor formattingVisitor; - - /** - * Constructs a DefaultAnnotatedTypeFormatter that does not print invisible annotations by - * default. - */ - public DefaultAnnotatedTypeFormatter() { - this(new DefaultAnnotationFormatter(), true, false); - } - - /** - * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more - * information - * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print - * invisible annotations - */ - public DefaultAnnotatedTypeFormatter( - boolean printVerboseGenerics, boolean defaultPrintInvisibleAnnos) { - this(new DefaultAnnotationFormatter(), printVerboseGenerics, defaultPrintInvisibleAnnos); - } - - /** - * @param formatter an object that converts annotation mirrors to strings - * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more - * information - * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print - * invisible annotations - */ - public DefaultAnnotatedTypeFormatter( - AnnotationFormatter formatter, - boolean printVerboseGenerics, - boolean defaultPrintInvisibleAnnos) { - this(new FormattingVisitor(formatter, printVerboseGenerics, defaultPrintInvisibleAnnos)); - } - - /** - * Used by subclasses and other constructors to specify the underlying implementation of this - * DefaultAnnotatedTypeFormatter. - */ - protected DefaultAnnotatedTypeFormatter(FormattingVisitor visitor) { - this.formattingVisitor = visitor; - } - - /** - * Maps from type variables to deterministic IDs. This is useful for comparing output across two - * runs of the Checker Framework. - * - *

          This map is necessary for deterministic and informative output. javac might print two - * distinct capture-converted variables as "capture#222" if the second is created after the first - * is garbage-collected, or if they just happen to have the same hash code based on memory layout. - * Such javac output is misleading because it looks like the two printed representations refer to - * the same variable. - * - *

          This map contains type variables that have been formatted. Therefore, the numbers may differ - * between Checker Framework runs if the different runs print different values (say, one of them - * prints more type variables than the other). - */ - protected static final Map captureConversionIds = - new WeakIdentityHashMap<>(); - - /** The last deterministic capture conversion ID that was used. */ - protected static int prevCaptureConversionId = 0; - - /** - * Returns a deterministic capture conversion ID for the given javac captured type. - * - * @param capturedType a type variable, which must be a capture-converted type variable - * @return a deterministic capture conversion ID - */ - protected static int getCaptureConversionId(TypeVariable capturedType) { - return captureConversionIds.computeIfAbsent(capturedType, key -> ++prevCaptureConversionId); - } - - @Override - public String format(AnnotatedTypeMirror type) { - formattingVisitor.resetPrintVerboseSettings(); - return formattingVisitor.visit(type); - } - - @Override - public String format(AnnotatedTypeMirror type, boolean printVerbose) { - formattingVisitor.setVerboseSettings(printVerbose); - return formattingVisitor.visit(type); - } - - /** A scanning visitor that prints the entire AnnotatedTypeMirror passed to visit. */ - protected static class FormattingVisitor - implements AnnotatedTypeVisitor> { - - /** The object responsible for converting annotations to strings. */ - protected final AnnotationFormatter annoFormatter; + /** The formatting visitor. */ + protected final FormattingVisitor formattingVisitor; /** - * Represents whether or not invisible annotations should be printed if the client of this class - * does not use the printInvisibleAnnos parameter. + * Constructs a DefaultAnnotatedTypeFormatter that does not print invisible annotations by + * default. */ - protected final boolean defaultInvisiblesSetting; + public DefaultAnnotatedTypeFormatter() { + this(new DefaultAnnotationFormatter(), true, false); + } /** - * For a given call to format, this setting specifies whether or not to printInvisibles. If a - * user did not specify a printInvisible parameter in the call to format then this value will - * equal DefaultAnnotatedTypeFormatter.defaultInvisibleSettings for this object + * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more + * information + * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print + * invisible annotations */ - protected boolean currentPrintInvisibleSetting; - - /** Default value of currentPrintVerboseGenerics. */ - protected final boolean defaultPrintVerboseGenerics; + public DefaultAnnotatedTypeFormatter( + boolean printVerboseGenerics, boolean defaultPrintInvisibleAnnos) { + this(new DefaultAnnotationFormatter(), printVerboseGenerics, defaultPrintInvisibleAnnos); + } /** - * Prints type variables in a less ambiguous manner using [] to delimit them. Always prints both - * bounds even if they lower bound is an AnnotatedNull type. + * @param formatter an object that converts annotation mirrors to strings + * @param printVerboseGenerics for type parameters, their uses, and wildcards, print more + * information + * @param defaultPrintInvisibleAnnos whether or not this AnnotatedTypeFormatter should print + * invisible annotations */ - protected boolean currentPrintVerboseGenerics; - - /** Whether the visitor is currently printing a raw type. */ - protected boolean currentlyPrintingRaw; + public DefaultAnnotatedTypeFormatter( + AnnotationFormatter formatter, + boolean printVerboseGenerics, + boolean defaultPrintInvisibleAnnos) { + this(new FormattingVisitor(formatter, printVerboseGenerics, defaultPrintInvisibleAnnos)); + } /** - * Creates the visitor. - * - * @param annoFormatter formatter used for {@code AnnotationMirror}s - * @param printVerboseGenerics whether to verbosely print type variables and wildcards - * @param defaultInvisiblesSetting whether to print invisible qualifiers + * Used by subclasses and other constructors to specify the underlying implementation of this + * DefaultAnnotatedTypeFormatter. */ - public FormattingVisitor( - AnnotationFormatter annoFormatter, - boolean printVerboseGenerics, - boolean defaultInvisiblesSetting) { - this.annoFormatter = annoFormatter; - this.defaultPrintVerboseGenerics = printVerboseGenerics; - this.currentPrintVerboseGenerics = printVerboseGenerics; - this.defaultInvisiblesSetting = defaultInvisiblesSetting; - this.currentPrintInvisibleSetting = false; - this.currentlyPrintingRaw = false; + protected DefaultAnnotatedTypeFormatter(FormattingVisitor visitor) { + this.formattingVisitor = visitor; } - /** Set the current verbose settings to use while printing. */ - protected void setVerboseSettings(boolean printVerbose) { - this.currentPrintInvisibleSetting = printVerbose; - this.currentPrintVerboseGenerics = printVerbose; - } + /** + * Maps from type variables to deterministic IDs. This is useful for comparing output across two + * runs of the Checker Framework. + * + *

          This map is necessary for deterministic and informative output. javac might print two + * distinct capture-converted variables as "capture#222" if the second is created after the + * first is garbage-collected, or if they just happen to have the same hash code based on memory + * layout. Such javac output is misleading because it looks like the two printed representations + * refer to the same variable. + * + *

          This map contains type variables that have been formatted. Therefore, the numbers may + * differ between Checker Framework runs if the different runs print different values (say, one + * of them prints more type variables than the other). + */ + protected static final Map captureConversionIds = + new WeakIdentityHashMap<>(); - /** Set verbose settings to the default. */ - protected void resetPrintVerboseSettings() { - this.currentPrintInvisibleSetting = defaultInvisiblesSetting; - this.currentPrintVerboseGenerics = defaultPrintVerboseGenerics; - } + /** The last deterministic capture conversion ID that was used. */ + protected static int prevCaptureConversionId = 0; /** - * Print, to sb, {@code keyWord} followed by {@code field}. NULL types are substituted with - * their annotations followed by " Void" + * Returns a deterministic capture conversion ID for the given javac captured type. + * + * @param capturedType a type variable, which must be a capture-converted type variable + * @return a deterministic capture conversion ID */ - @SideEffectFree - protected void printBound( - String keyWord, - AnnotatedTypeMirror field, - Set visiting, - StringBuilder sb) { - if (!currentPrintVerboseGenerics && (field == null || field.getKind() == TypeKind.NULL)) { - return; - } - - sb.append(" "); - sb.append(keyWord); - sb.append(" "); - - if (field == null) { - sb.append(""); - } else if (field.getKind() != TypeKind.NULL) { - sb.append(visit(field, visiting)); - } else { - sb.append( - annoFormatter.formatAnnotationString( - field.getAnnotations(), currentPrintInvisibleSetting)); - sb.append("Void"); - } + protected static int getCaptureConversionId(TypeVariable capturedType) { + return captureConversionIds.computeIfAbsent(capturedType, key -> ++prevCaptureConversionId); } - @SideEffectFree @Override - public String visit(AnnotatedTypeMirror type) { - return type.accept(this, Collections.newSetFromMap(new IdentityHashMap<>())); + public String format(AnnotatedTypeMirror type) { + formattingVisitor.resetPrintVerboseSettings(); + return formattingVisitor.visit(type); } @Override - public String visit(AnnotatedTypeMirror type, Set annotatedTypeVariables) { - return type.accept(this, annotatedTypeVariables); + public String format(AnnotatedTypeMirror type, boolean printVerbose) { + formattingVisitor.setVerboseSettings(printVerbose); + return formattingVisitor.visit(type); } - @Override - public String visitDeclared(AnnotatedDeclaredType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (type.isDeclaration() && currentPrintInvisibleSetting) { - sb.append("/*DECL*/ "); - } - - if (type.enclosingType != null) { - sb.append(this.visit(type.enclosingType, visiting)); - sb.append('.'); - } - Element typeElt = type.getUnderlyingType().asElement(); - String smpl = typeElt.getSimpleName().toString(); - if (smpl.isEmpty()) { - // For anonymous classes smpl is empty - toString - // of the element is more useful. - smpl = typeElt.toString(); - } - sb.append( - annoFormatter.formatAnnotationString( - type.getAnnotations(), currentPrintInvisibleSetting)); - sb.append(smpl); - - boolean oldPrintingRaw = currentlyPrintingRaw; - if (type.isUnderlyingTypeRaw()) { - currentlyPrintingRaw = true; - } - if (type.typeArgs != null) { - // getTypeArguments sets the field if it does not already exist. - List typeArgs = type.typeArgs; - if (!typeArgs.isEmpty()) { - StringJoiner sj = new StringJoiner(", ", "<", ">"); - if (!currentPrintVerboseGenerics && currentlyPrintingRaw) { - sj.add("/*RAW*/"); - } else { - for (AnnotatedTypeMirror typeArg : typeArgs) { - sj.add(visit(typeArg, visiting)); - } - } - sb.append(sj); + /** A scanning visitor that prints the entire AnnotatedTypeMirror passed to visit. */ + protected static class FormattingVisitor + implements AnnotatedTypeVisitor> { + + /** The object responsible for converting annotations to strings. */ + protected final AnnotationFormatter annoFormatter; + + /** + * Represents whether or not invisible annotations should be printed if the client of this + * class does not use the printInvisibleAnnos parameter. + */ + protected final boolean defaultInvisiblesSetting; + + /** + * For a given call to format, this setting specifies whether or not to printInvisibles. If + * a user did not specify a printInvisible parameter in the call to format then this value + * will equal DefaultAnnotatedTypeFormatter.defaultInvisibleSettings for this object + */ + protected boolean currentPrintInvisibleSetting; + + /** Default value of currentPrintVerboseGenerics. */ + protected final boolean defaultPrintVerboseGenerics; + + /** + * Prints type variables in a less ambiguous manner using [] to delimit them. Always prints + * both bounds even if they lower bound is an AnnotatedNull type. + */ + protected boolean currentPrintVerboseGenerics; + + /** Whether the visitor is currently printing a raw type. */ + protected boolean currentlyPrintingRaw; + + /** + * Creates the visitor. + * + * @param annoFormatter formatter used for {@code AnnotationMirror}s + * @param printVerboseGenerics whether to verbosely print type variables and wildcards + * @param defaultInvisiblesSetting whether to print invisible qualifiers + */ + public FormattingVisitor( + AnnotationFormatter annoFormatter, + boolean printVerboseGenerics, + boolean defaultInvisiblesSetting) { + this.annoFormatter = annoFormatter; + this.defaultPrintVerboseGenerics = printVerboseGenerics; + this.currentPrintVerboseGenerics = printVerboseGenerics; + this.defaultInvisiblesSetting = defaultInvisiblesSetting; + this.currentPrintInvisibleSetting = false; + this.currentlyPrintingRaw = false; } - } else { - sb.append("<" + "/*Type args not initialized*/" + ">"); - } - currentlyPrintingRaw = oldPrintingRaw; - return sb.toString(); - } - @Override - public String visitIntersection( - AnnotatedIntersectionType type, Set visiting) { - if (type.bounds == null) { - return "/*Intersection not initialized*/"; - } - - StringBuilder sb = new StringBuilder(); - - boolean isFirst = true; - for (AnnotatedTypeMirror bound : type.getBounds()) { - if (!isFirst) { - sb.append(" & "); + /** Set the current verbose settings to use while printing. */ + protected void setVerboseSettings(boolean printVerbose) { + this.currentPrintInvisibleSetting = printVerbose; + this.currentPrintVerboseGenerics = printVerbose; } - sb.append(visit(bound, visiting)); - isFirst = false; - } - return sb.toString(); - } - @Override - public String visitUnion(AnnotatedUnionType type, Set visiting) { - if (type.alternatives == null) { - return "/*Union not initialized*/"; - } + /** Set verbose settings to the default. */ + protected void resetPrintVerboseSettings() { + this.currentPrintInvisibleSetting = defaultInvisiblesSetting; + this.currentPrintVerboseGenerics = defaultPrintVerboseGenerics; + } - StringBuilder sb = new StringBuilder(); + /** + * Print, to sb, {@code keyWord} followed by {@code field}. NULL types are substituted with + * their annotations followed by " Void" + */ + @SideEffectFree + protected void printBound( + String keyWord, + AnnotatedTypeMirror field, + Set visiting, + StringBuilder sb) { + if (!currentPrintVerboseGenerics + && (field == null || field.getKind() == TypeKind.NULL)) { + return; + } - boolean isFirst = true; - for (AnnotatedDeclaredType adt : type.getAlternatives()) { - if (!isFirst) { - sb.append(" | "); + sb.append(" "); + sb.append(keyWord); + sb.append(" "); + + if (field == null) { + sb.append(""); + } else if (field.getKind() != TypeKind.NULL) { + sb.append(visit(field, visiting)); + } else { + sb.append( + annoFormatter.formatAnnotationString( + field.getAnnotations(), currentPrintInvisibleSetting)); + sb.append("Void"); + } } - sb.append(visit(adt, visiting)); - isFirst = false; - } - return sb.toString(); - } - @Override - public String visitExecutable(AnnotatedExecutableType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (type.typeVarTypes == null || !type.typeVarTypes.isEmpty()) { - StringJoiner sj = new StringJoiner(", ", "<", "> "); - if (type.typeVarTypes == null) { - sj.add("/*Type var not initialized*/"); - } else { - for (AnnotatedTypeVariable atv : type.getTypeVariables()) { - sj.add(visit(atv, visiting)); - } + @SideEffectFree + @Override + public String visit(AnnotatedTypeMirror type) { + return type.accept(this, Collections.newSetFromMap(new IdentityHashMap<>())); } - sb.append(sj); - } - if (type.returnType != null) { - sb.append(visit(type.getReturnType(), visiting)); - } else { - sb.append("/*Return type not initialized*/"); - } - sb.append(' '); - if (type.getElement() != null) { - sb.append(type.getElement().getSimpleName()); - } else { - sb.append("METHOD"); - } - sb.append('('); - AnnotatedDeclaredType rcv = type.receiverType; - if (rcv != null) { - sb.append(visit(rcv, visiting)); - sb.append(" this"); - } - if (type.paramTypes == null) { - sb.append("/*Parameters not initialized*/"); - } else if (!type.paramTypes.isEmpty()) { - int p = 0; - for (AnnotatedTypeMirror atm : type.paramTypes) { - if (rcv != null || p > 0) { - sb.append(", "); - } - sb.append(visit(atm, visiting)); - // Output some parameter names to make it look more like a method. - // TODO: go to the element and look up real parameter names, maybe. - sb.append(" p"); - sb.append(p++); + + @Override + public String visit( + AnnotatedTypeMirror type, Set annotatedTypeVariables) { + return type.accept(this, annotatedTypeVariables); } - } - sb.append(')'); - if (type.thrownTypes == null) { - sb.append("/*Throws not initialized*/"); - } else if (!type.thrownTypes.isEmpty()) { - sb.append(" throws "); - for (AnnotatedTypeMirror atm : type.getThrownTypes()) { - sb.append(visit(atm, visiting)); + + @Override + public String visitDeclared(AnnotatedDeclaredType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (type.isDeclaration() && currentPrintInvisibleSetting) { + sb.append("/*DECL*/ "); + } + + if (type.enclosingType != null) { + sb.append(this.visit(type.enclosingType, visiting)); + sb.append('.'); + } + Element typeElt = type.getUnderlyingType().asElement(); + String smpl = typeElt.getSimpleName().toString(); + if (smpl.isEmpty()) { + // For anonymous classes smpl is empty - toString + // of the element is more useful. + smpl = typeElt.toString(); + } + sb.append( + annoFormatter.formatAnnotationString( + type.getAnnotations(), currentPrintInvisibleSetting)); + sb.append(smpl); + + boolean oldPrintingRaw = currentlyPrintingRaw; + if (type.isUnderlyingTypeRaw()) { + currentlyPrintingRaw = true; + } + if (type.typeArgs != null) { + // getTypeArguments sets the field if it does not already exist. + List typeArgs = type.typeArgs; + if (!typeArgs.isEmpty()) { + StringJoiner sj = new StringJoiner(", ", "<", ">"); + if (!currentPrintVerboseGenerics && currentlyPrintingRaw) { + sj.add("/*RAW*/"); + } else { + for (AnnotatedTypeMirror typeArg : typeArgs) { + sj.add(visit(typeArg, visiting)); + } + } + sb.append(sj); + } + } else { + sb.append("<" + "/*Type args not initialized*/" + ">"); + } + currentlyPrintingRaw = oldPrintingRaw; + return sb.toString(); } - } - return sb.toString(); - } - @Override - public String visitArray(AnnotatedArrayType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - - AnnotatedArrayType array = type; - AnnotatedTypeMirror component; - while (true) { - component = array.componentType; - if (!array.getAnnotations().isEmpty()) { - sb.append(' '); - sb.append( - annoFormatter.formatAnnotationString( - array.getAnnotations(), currentPrintInvisibleSetting)); + @Override + public String visitIntersection( + AnnotatedIntersectionType type, Set visiting) { + if (type.bounds == null) { + return "/*Intersection not initialized*/"; + } + + StringBuilder sb = new StringBuilder(); + + boolean isFirst = true; + for (AnnotatedTypeMirror bound : type.getBounds()) { + if (!isFirst) { + sb.append(" & "); + } + sb.append(visit(bound, visiting)); + isFirst = false; + } + return sb.toString(); } - sb.append("[]"); - if (!(component instanceof AnnotatedArrayType)) { - if (component == null) { - sb.insert(0, "/*Not Initialized*/"); - } - sb.insert(0, visit(component, visiting)); - break; + + @Override + public String visitUnion(AnnotatedUnionType type, Set visiting) { + if (type.alternatives == null) { + return "/*Union not initialized*/"; + } + + StringBuilder sb = new StringBuilder(); + + boolean isFirst = true; + for (AnnotatedDeclaredType adt : type.getAlternatives()) { + if (!isFirst) { + sb.append(" | "); + } + sb.append(visit(adt, visiting)); + isFirst = false; + } + return sb.toString(); } - array = (AnnotatedArrayType) component; - } - return sb.toString(); - } - @Override - public String visitTypeVariable(AnnotatedTypeVariable type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (TypesUtils.isCapturedTypeVariable(type.underlyingType)) { - // underlyingType.toString() has this form: "capture#826 of ? extends - // java.lang.Object". - // assert underlyingType.toString().startsWith("capture#"); - // We output only the "capture#826" part. - - // TODO: If deterministic output is not needed, we could avoid the use of - // getCaptureConversionId() by using this code instead: - // sb.append(underlyingType, 0, underlyingType.indexOf(" of ")); - // The choice would be controlled by a command-line argument. - - // We output a deterministic number; we prefix it by "0" - // so we know whether a number is deterministic or from javac. - sb.append("capture#0").append(getCaptureConversionId((TypeVariable) type.underlyingType)); - } else { - sb.append(type.underlyingType); - } - - if (!visiting.contains(type)) { - if (type.isDeclaration() && currentPrintInvisibleSetting) { - sb.append("/*DECL*/ "); + @Override + public String visitExecutable( + AnnotatedExecutableType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (type.typeVarTypes == null || !type.typeVarTypes.isEmpty()) { + StringJoiner sj = new StringJoiner(", ", "<", "> "); + if (type.typeVarTypes == null) { + sj.add("/*Type var not initialized*/"); + } else { + for (AnnotatedTypeVariable atv : type.getTypeVariables()) { + sj.add(visit(atv, visiting)); + } + } + sb.append(sj); + } + if (type.returnType != null) { + sb.append(visit(type.getReturnType(), visiting)); + } else { + sb.append("/*Return type not initialized*/"); + } + sb.append(' '); + if (type.getElement() != null) { + sb.append(type.getElement().getSimpleName()); + } else { + sb.append("METHOD"); + } + sb.append('('); + AnnotatedDeclaredType rcv = type.receiverType; + if (rcv != null) { + sb.append(visit(rcv, visiting)); + sb.append(" this"); + } + if (type.paramTypes == null) { + sb.append("/*Parameters not initialized*/"); + } else if (!type.paramTypes.isEmpty()) { + int p = 0; + for (AnnotatedTypeMirror atm : type.paramTypes) { + if (rcv != null || p > 0) { + sb.append(", "); + } + sb.append(visit(atm, visiting)); + // Output some parameter names to make it look more like a method. + // TODO: go to the element and look up real parameter names, maybe. + sb.append(" p"); + sb.append(p++); + } + } + sb.append(')'); + if (type.thrownTypes == null) { + sb.append("/*Throws not initialized*/"); + } else if (!type.thrownTypes.isEmpty()) { + sb.append(" throws "); + for (AnnotatedTypeMirror atm : type.getThrownTypes()) { + sb.append(visit(atm, visiting)); + } + } + return sb.toString(); } - try { - visiting.add(type); - if (currentPrintVerboseGenerics) { - sb.append("["); - } - printBound("extends", type.getUpperBoundField(), visiting, sb); - printBound("super", type.getLowerBoundField(), visiting, sb); - if (currentPrintVerboseGenerics) { - sb.append("]"); - } - - } finally { - visiting.remove(type); + @Override + public String visitArray(AnnotatedArrayType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + + AnnotatedArrayType array = type; + AnnotatedTypeMirror component; + while (true) { + component = array.componentType; + if (!array.getAnnotations().isEmpty()) { + sb.append(' '); + sb.append( + annoFormatter.formatAnnotationString( + array.getAnnotations(), currentPrintInvisibleSetting)); + } + sb.append("[]"); + if (!(component instanceof AnnotatedArrayType)) { + if (component == null) { + sb.insert(0, "/*Not Initialized*/"); + } + sb.insert(0, visit(component, visiting)); + break; + } + array = (AnnotatedArrayType) component; + } + return sb.toString(); } - } - return sb.toString(); - } - @SideEffectFree - @Override - public String visitPrimitive(AnnotatedPrimitiveType type, Set visiting) { - return formatFlatType(type); - } + @Override + public String visitTypeVariable( + AnnotatedTypeVariable type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (TypesUtils.isCapturedTypeVariable(type.underlyingType)) { + // underlyingType.toString() has this form: "capture#826 of ? extends + // java.lang.Object". + // assert underlyingType.toString().startsWith("capture#"); + // We output only the "capture#826" part. + + // TODO: If deterministic output is not needed, we could avoid the use of + // getCaptureConversionId() by using this code instead: + // sb.append(underlyingType, 0, underlyingType.indexOf(" of ")); + // The choice would be controlled by a command-line argument. + + // We output a deterministic number; we prefix it by "0" + // so we know whether a number is deterministic or from javac. + sb.append("capture#0") + .append(getCaptureConversionId((TypeVariable) type.underlyingType)); + } else { + sb.append(type.underlyingType); + } - @SideEffectFree - @Override - public String visitNoType(AnnotatedNoType type, Set visiting) { - return formatFlatType(type); - } + if (!visiting.contains(type)) { + if (type.isDeclaration() && currentPrintInvisibleSetting) { + sb.append("/*DECL*/ "); + } + + try { + visiting.add(type); + if (currentPrintVerboseGenerics) { + sb.append("["); + } + printBound("extends", type.getUpperBoundField(), visiting, sb); + printBound("super", type.getLowerBoundField(), visiting, sb); + if (currentPrintVerboseGenerics) { + sb.append("]"); + } + + } finally { + visiting.remove(type); + } + } + return sb.toString(); + } - @SideEffectFree - @Override - public String visitNull(AnnotatedNullType type, Set visiting) { - return annoFormatter.formatAnnotationString( - type.getAnnotations(), currentPrintInvisibleSetting) - + "NullType"; - } + @SideEffectFree + @Override + public String visitPrimitive( + AnnotatedPrimitiveType type, Set visiting) { + return formatFlatType(type); + } - @Override - public String visitWildcard(AnnotatedWildcardType type, Set visiting) { - StringBuilder sb = new StringBuilder(); - if (type.isTypeArgOfRawType()) { - if (currentlyPrintingRaw) { - sb.append("/*RAW TYPE ARGUMENT:*/ "); - } else { - sb.append("/*INFERENCE FAILED for:*/ "); + @SideEffectFree + @Override + public String visitNoType(AnnotatedNoType type, Set visiting) { + return formatFlatType(type); } - } - - sb.append( - annoFormatter.formatAnnotationString( - type.getAnnotationsField(), currentPrintInvisibleSetting)); - - sb.append("?"); - if (!visiting.contains(type)) { - try { - visiting.add(type); - - if (currentPrintVerboseGenerics) { - sb.append("["); - } - printBound("extends", type.getExtendsBoundField(), visiting, sb); - printBound("super", type.getSuperBoundField(), visiting, sb); - if (currentPrintVerboseGenerics) { - sb.append("]"); - } - - } finally { - visiting.remove(type); + + @SideEffectFree + @Override + public String visitNull(AnnotatedNullType type, Set visiting) { + return annoFormatter.formatAnnotationString( + type.getAnnotations(), currentPrintInvisibleSetting) + + "NullType"; } - } - return sb.toString(); - } - @SideEffectFree - protected String formatFlatType(AnnotatedTypeMirror flatType) { - return annoFormatter.formatAnnotationString( - flatType.getAnnotations(), currentPrintInvisibleSetting) - + TypeAnnotationUtils.unannotatedType((Type) flatType.getUnderlyingType()); + @Override + public String visitWildcard(AnnotatedWildcardType type, Set visiting) { + StringBuilder sb = new StringBuilder(); + if (type.isTypeArgOfRawType()) { + if (currentlyPrintingRaw) { + sb.append("/*RAW TYPE ARGUMENT:*/ "); + } else { + sb.append("/*INFERENCE FAILED for:*/ "); + } + } + + sb.append( + annoFormatter.formatAnnotationString( + type.getAnnotationsField(), currentPrintInvisibleSetting)); + + sb.append("?"); + if (!visiting.contains(type)) { + try { + visiting.add(type); + + if (currentPrintVerboseGenerics) { + sb.append("["); + } + printBound("extends", type.getExtendsBoundField(), visiting, sb); + printBound("super", type.getSuperBoundField(), visiting, sb); + if (currentPrintVerboseGenerics) { + sb.append("]"); + } + + } finally { + visiting.remove(type); + } + } + return sb.toString(); + } + + @SideEffectFree + protected String formatFlatType(AnnotatedTypeMirror flatType) { + return annoFormatter.formatAnnotationString( + flatType.getAnnotations(), currentPrintInvisibleSetting) + + TypeAnnotationUtils.unannotatedType((Type) flatType.getUnderlyingType()); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java index a334d964a2d..e9551a21606 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultInferredTypesApplier.java @@ -1,150 +1,157 @@ package org.checkerframework.framework.type; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; + /** Utility class for applying the annotations inferred by dataflow to a given type. */ public class DefaultInferredTypesApplier { - // At the moment, only Inference uses the omitSubtypingCheck option. - // In actuality the subtyping check should be unnecessary since inferred - // types should be subtypes of their declaration. - private final boolean omitSubtypingCheck; - - private final QualifierHierarchy hierarchy; - private final AnnotatedTypeFactory atypeFactory; + // At the moment, only Inference uses the omitSubtypingCheck option. + // In actuality the subtyping check should be unnecessary since inferred + // types should be subtypes of their declaration. + private final boolean omitSubtypingCheck; - public DefaultInferredTypesApplier( - QualifierHierarchy hierarchy, AnnotatedTypeFactory atypeFactory) { - this(false, hierarchy, atypeFactory); - } + private final QualifierHierarchy hierarchy; + private final AnnotatedTypeFactory atypeFactory; - public DefaultInferredTypesApplier( - boolean omitSubtypingCheck, QualifierHierarchy hierarchy, AnnotatedTypeFactory atypeFactory) { - this.omitSubtypingCheck = omitSubtypingCheck; - this.hierarchy = hierarchy; - this.atypeFactory = atypeFactory; - } - - /** - * For each top in qualifier hierarchy, traverse inferred and copy the required annotations over - * to type. - * - * @param type the type to which annotations are being applied - * @param inferredSet the type inferred by data flow - * @param inferredTypeMirror underlying inferred type - */ - public void applyInferredType( - AnnotatedTypeMirror type, AnnotationMirrorSet inferredSet, TypeMirror inferredTypeMirror) { - if (inferredSet == null) { - return; - } - if (inferredTypeMirror.getKind() == TypeKind.WILDCARD) { - // Dataflow might infer a wildcard that extends a type variable for types that are - // actually type variables. Use the type variable instead. - while (inferredTypeMirror.getKind() == TypeKind.WILDCARD - && (((WildcardType) inferredTypeMirror).getExtendsBound() != null)) { - inferredTypeMirror = ((WildcardType) inferredTypeMirror).getExtendsBound(); - } + public DefaultInferredTypesApplier( + QualifierHierarchy hierarchy, AnnotatedTypeFactory atypeFactory) { + this(false, hierarchy, atypeFactory); } - for (AnnotationMirror top : hierarchy.getTopAnnotations()) { - AnnotationMirror inferred = hierarchy.findAnnotationInHierarchy(inferredSet, top); - apply(type, inferred, inferredTypeMirror, top); + public DefaultInferredTypesApplier( + boolean omitSubtypingCheck, + QualifierHierarchy hierarchy, + AnnotatedTypeFactory atypeFactory) { + this.omitSubtypingCheck = omitSubtypingCheck; + this.hierarchy = hierarchy; + this.atypeFactory = atypeFactory; } - } - private void apply( - AnnotatedTypeMirror type, - AnnotationMirror inferred, - TypeMirror inferredTypeMirror, - AnnotationMirror top) { - AnnotationMirror primary = type.getAnnotationInHierarchy(top); - if (inferred == null) { - if (primary == null) { - // Type doesn't have a primary either, nothing to remove - } else if (type.getKind() == TypeKind.TYPEVAR) { - removePrimaryAnnotationTypeVar( - (AnnotatedTypeVariable) type, inferredTypeMirror, top, primary); - } else { - removePrimaryTypeVarApplyUpperBound(type, inferredTypeMirror, top, primary); - } - } else { - if (primary == null) { - AnnotationMirrorSet lowerbounds = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, type); - primary = hierarchy.findAnnotationInHierarchy(lowerbounds, top); - } - if ((omitSubtypingCheck - || hierarchy.isSubtypeShallow( - inferred, inferredTypeMirror, primary, type.getUnderlyingType()))) { - type.replaceAnnotation(inferred); - } - } - } + /** + * For each top in qualifier hierarchy, traverse inferred and copy the required annotations over + * to type. + * + * @param type the type to which annotations are being applied + * @param inferredSet the type inferred by data flow + * @param inferredTypeMirror underlying inferred type + */ + public void applyInferredType( + AnnotatedTypeMirror type, + AnnotationMirrorSet inferredSet, + TypeMirror inferredTypeMirror) { + if (inferredSet == null) { + return; + } + if (inferredTypeMirror.getKind() == TypeKind.WILDCARD) { + // Dataflow might infer a wildcard that extends a type variable for types that are + // actually type variables. Use the type variable instead. + while (inferredTypeMirror.getKind() == TypeKind.WILDCARD + && (((WildcardType) inferredTypeMirror).getExtendsBound() != null)) { + inferredTypeMirror = ((WildcardType) inferredTypeMirror).getExtendsBound(); + } + } + for (AnnotationMirror top : hierarchy.getTopAnnotations()) { + AnnotationMirror inferred = hierarchy.findAnnotationInHierarchy(inferredSet, top); - private void removePrimaryTypeVarApplyUpperBound( - AnnotatedTypeMirror type, - TypeMirror inferredTypeMirror, - AnnotationMirror top, - AnnotationMirror notInferred) { - if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF("Inferred value should not be missing annotations: " + inferredTypeMirror); + apply(type, inferred, inferredTypeMirror, top); + } } - if (TypesUtils.isCapturedTypeVariable(inferredTypeMirror)) { - return; + + private void apply( + AnnotatedTypeMirror type, + AnnotationMirror inferred, + TypeMirror inferredTypeMirror, + AnnotationMirror top) { + AnnotationMirror primary = type.getAnnotationInHierarchy(top); + if (inferred == null) { + if (primary == null) { + // Type doesn't have a primary either, nothing to remove + } else if (type.getKind() == TypeKind.TYPEVAR) { + removePrimaryAnnotationTypeVar( + (AnnotatedTypeVariable) type, inferredTypeMirror, top, primary); + } else { + removePrimaryTypeVarApplyUpperBound(type, inferredTypeMirror, top, primary); + } + } else { + if (primary == null) { + AnnotationMirrorSet lowerbounds = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(hierarchy, type); + primary = hierarchy.findAnnotationInHierarchy(lowerbounds, top); + } + if ((omitSubtypingCheck + || hierarchy.isSubtypeShallow( + inferred, inferredTypeMirror, primary, type.getUnderlyingType()))) { + type.replaceAnnotation(inferred); + } + } } - TypeVariable typeVar = (TypeVariable) inferredTypeMirror; - AnnotatedTypeVariable typeVariableDecl = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); - AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); + private void removePrimaryTypeVarApplyUpperBound( + AnnotatedTypeMirror type, + TypeMirror inferredTypeMirror, + AnnotationMirror top, + AnnotationMirror notInferred) { + if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF( + "Inferred value should not be missing annotations: " + inferredTypeMirror); + } + if (TypesUtils.isCapturedTypeVariable(inferredTypeMirror)) { + return; + } - if (omitSubtypingCheck - || hierarchy.isSubtypeShallow(upperBound, typeVar, notInferred, type.getUnderlyingType())) { - type.replaceAnnotation(upperBound); - } - } + TypeVariable typeVar = (TypeVariable) inferredTypeMirror; + AnnotatedTypeVariable typeVariableDecl = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); + AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); - private void removePrimaryAnnotationTypeVar( - AnnotatedTypeVariable annotatedTypeVariable, - TypeMirror inferredTypeMirror, - AnnotationMirror top, - AnnotationMirror previousAnnotation) { - if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF("Missing annos"); + if (omitSubtypingCheck + || hierarchy.isSubtypeShallow( + upperBound, typeVar, notInferred, type.getUnderlyingType())) { + type.replaceAnnotation(upperBound); + } } - TypeVariable typeVar = (TypeVariable) inferredTypeMirror; - AnnotatedTypeVariable typeVariableDecl = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); - AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); - if (omitSubtypingCheck - || hierarchy.isSubtypeShallow( - upperBound, - typeVariableDecl.getUnderlyingType(), - previousAnnotation, - annotatedTypeVariable.getUnderlyingType())) { - // TODO: clean up this method and whole class. - AnnotationMirror ub = typeVariableDecl.getUpperBound().getAnnotationInHierarchy(top); - AnnotationMirror lb = typeVariableDecl.getLowerBound().getAnnotationInHierarchy(top); - AnnotatedTypeMirror atvUB = annotatedTypeVariable.getUpperBound(); - AnnotatedTypeMirror atvLB = annotatedTypeVariable.getLowerBound(); - AnnotationMirror atvUBAnno = atvUB.getAnnotationInHierarchy(top); - AnnotationMirror atvLBAnno = atvLB.getAnnotationInHierarchy(top); - annotatedTypeVariable.removeAnnotationInHierarchy(top); - atvUB.addAnnotation(atvUBAnno); - atvLB.addAnnotation(atvLBAnno); - apply(atvUB, ub, typeVar.getUpperBound(), top); - apply(atvLB, lb, typeVar.getLowerBound(), top); + private void removePrimaryAnnotationTypeVar( + AnnotatedTypeVariable annotatedTypeVariable, + TypeMirror inferredTypeMirror, + AnnotationMirror top, + AnnotationMirror previousAnnotation) { + if (inferredTypeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Missing annos"); + } + TypeVariable typeVar = (TypeVariable) inferredTypeMirror; + AnnotatedTypeVariable typeVariableDecl = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeVar.asElement()); + AnnotationMirror upperBound = typeVariableDecl.getEffectiveAnnotationInHierarchy(top); + if (omitSubtypingCheck + || hierarchy.isSubtypeShallow( + upperBound, + typeVariableDecl.getUnderlyingType(), + previousAnnotation, + annotatedTypeVariable.getUnderlyingType())) { + // TODO: clean up this method and whole class. + AnnotationMirror ub = typeVariableDecl.getUpperBound().getAnnotationInHierarchy(top); + AnnotationMirror lb = typeVariableDecl.getLowerBound().getAnnotationInHierarchy(top); + AnnotatedTypeMirror atvUB = annotatedTypeVariable.getUpperBound(); + AnnotatedTypeMirror atvLB = annotatedTypeVariable.getLowerBound(); + AnnotationMirror atvUBAnno = atvUB.getAnnotationInHierarchy(top); + AnnotationMirror atvLBAnno = atvLB.getAnnotationInHierarchy(top); + + annotatedTypeVariable.removeAnnotationInHierarchy(top); + atvUB.addAnnotation(atvUBAnno); + atvLB.addAnnotation(atvLBAnno); + apply(atvUB, ub, typeVar.getUpperBound(), top); + apply(atvLB, lb, typeVar.getLowerBound(), top); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 604a7a42a8b..8fa970f7fb2 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -1,15 +1,5 @@ package org.checkerframework.framework.type; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.qual.Covariant; @@ -30,6 +20,18 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + /** * Default implementation of TypeHierarchy that implements the JLS specification with minor * deviations as outlined by the Checker Framework manual. Changes to the JLS include forbidding @@ -47,1257 +49,1285 @@ *

          The visit methods return true if the first argument is a subtype of the second argument. */ public class DefaultTypeHierarchy extends AbstractAtmComboVisitor - implements TypeHierarchy { - /** - * The type-checker that is associated with this. - * - *

          Used for processingEnvironment when needed. - */ - protected final BaseTypeChecker checker; - - /** The qualifier hierarchy that is associated with this. */ - protected final QualifierHierarchy qualHierarchy; - - /** The equality comparer. */ - protected final StructuralEqualityComparer equalityComparer; - - /** Whether to ignore raw types. */ - protected final boolean ignoreRawTypes; - - /** Whether to make array subtyping invariant with respect to array component types. */ - protected final boolean invariantArrayComponents; - - /** The top annotation of the hierarchy currently being checked. */ - protected AnnotationMirror currentTop; - - /** Stores the result of isSubtype, if that result is true. */ - protected final SubtypeVisitHistory isSubtypeVisitHistory; - - /** - * Stores the result of {@link #areEqualInHierarchy(AnnotatedTypeMirror, AnnotatedTypeMirror)} for - * type arguments. Prevents infinite recursion on types that refer to themselves. (Stores both - * true and false results.) - */ - protected final StructuralEqualityVisitHistory areEqualVisitHistory; - - /** The Covariant.value field/element. */ - protected final ExecutableElement covariantValueElement; - - /** - * Creates a DefaultTypeHierarchy. - * - * @param checker the type-checker that is associated with this - * @param qualHierarchy the qualifier hierarchy that is associated with this - * @param ignoreRawTypes whether to ignore raw types - * @param invariantArrayComponents whether to make array subtyping invariant with respect to array - * component types - */ - public DefaultTypeHierarchy( - BaseTypeChecker checker, - QualifierHierarchy qualHierarchy, - boolean ignoreRawTypes, - boolean invariantArrayComponents) { - this.checker = checker; - this.qualHierarchy = qualHierarchy; - this.isSubtypeVisitHistory = new SubtypeVisitHistory(); - this.areEqualVisitHistory = new StructuralEqualityVisitHistory(); - this.equalityComparer = createEqualityComparer(); - - this.ignoreRawTypes = ignoreRawTypes; - this.invariantArrayComponents = invariantArrayComponents; - - covariantValueElement = - TreeUtils.getMethod(Covariant.class, "value", 0, checker.getProcessingEnvironment()); - } - - /** - * Create the equality comparer. - * - * @return the equality comparer - */ - protected StructuralEqualityComparer createEqualityComparer() { - return new StructuralEqualityComparer(areEqualVisitHistory); - } - - /** - * Returns true if subtype {@literal <:} supertype. - * - *

          This implementation iterates over all top annotations and invokes {@link - * #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. Most type systems - * should not override this method, but instead override {@link #isSubtype(AnnotatedTypeMirror, - * AnnotatedTypeMirror, AnnotationMirror)} or some of the {@code visitXXX} methods. - * - * @param subtype expected subtype - * @param supertype expected supertype - * @return true if subtype is a subtype of supertype or equal to it - */ - @Override - public boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (!isSubtype(subtype, supertype, top)) { - return false; - } - } + implements TypeHierarchy { + /** + * The type-checker that is associated with this. + * + *

          Used for processingEnvironment when needed. + */ + protected final BaseTypeChecker checker; + + /** The qualifier hierarchy that is associated with this. */ + protected final QualifierHierarchy qualHierarchy; - return true; - } + /** The equality comparer. */ + protected final StructuralEqualityComparer equalityComparer; - /** A set of annotations and a {@link TypeMirror}. */ - @AnnotatedFor("nullness") - private static class ShallowType { + /** Whether to ignore raw types. */ + protected final boolean ignoreRawTypes; + + /** Whether to make array subtyping invariant with respect to array component types. */ + protected final boolean invariantArrayComponents; + + /** The top annotation of the hierarchy currently being checked. */ + protected AnnotationMirror currentTop; + + /** Stores the result of isSubtype, if that result is true. */ + protected final SubtypeVisitHistory isSubtypeVisitHistory; + + /** + * Stores the result of {@link #areEqualInHierarchy(AnnotatedTypeMirror, AnnotatedTypeMirror)} + * for type arguments. Prevents infinite recursion on types that refer to themselves. (Stores + * both true and false results.) + */ + protected final StructuralEqualityVisitHistory areEqualVisitHistory; - /** A set of annotations. */ - AnnotationMirrorSet annos; + /** The Covariant.value field/element. */ + protected final ExecutableElement covariantValueElement; - /** A TypeMirror. */ - TypeMirror typeMirror; + /** + * Creates a DefaultTypeHierarchy. + * + * @param checker the type-checker that is associated with this + * @param qualHierarchy the qualifier hierarchy that is associated with this + * @param ignoreRawTypes whether to ignore raw types + * @param invariantArrayComponents whether to make array subtyping invariant with respect to + * array component types + */ + public DefaultTypeHierarchy( + BaseTypeChecker checker, + QualifierHierarchy qualHierarchy, + boolean ignoreRawTypes, + boolean invariantArrayComponents) { + this.checker = checker; + this.qualHierarchy = qualHierarchy; + this.isSubtypeVisitHistory = new SubtypeVisitHistory(); + this.areEqualVisitHistory = new StructuralEqualityVisitHistory(); + this.equalityComparer = createEqualityComparer(); + + this.ignoreRawTypes = ignoreRawTypes; + this.invariantArrayComponents = invariantArrayComponents; + + covariantValueElement = + TreeUtils.getMethod( + Covariant.class, "value", 0, checker.getProcessingEnvironment()); + } /** - * Creates a {@code ShallowType}. + * Create the equality comparer. * - * @param annos a set of annotations - * @param typeMirror a type mirror + * @return the equality comparer */ - private ShallowType(AnnotationMirrorSet annos, TypeMirror typeMirror) { - this.annos = annos; - this.typeMirror = typeMirror; + protected StructuralEqualityComparer createEqualityComparer() { + return new StructuralEqualityComparer(areEqualVisitHistory); } /** - * Creates a {@code ShallowType} from {@code type}: the annotations are the effective - * annotations on {@code type} and the type mirror is the underlying type of {@code type}. + * Returns true if subtype {@literal <:} supertype. + * + *

          This implementation iterates over all top annotations and invokes {@link + * #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. Most type systems + * should not override this method, but instead override {@link #isSubtype(AnnotatedTypeMirror, + * AnnotatedTypeMirror, AnnotationMirror)} or some of the {@code visitXXX} methods. * - * @param type an annotated type to convert to a {@code ShallowType} - * @return a shallow type created from {@code type} + * @param subtype expected subtype + * @param supertype expected supertype + * @return true if subtype is a subtype of supertype or equal to it */ - @SuppressWarnings("nullness") // AnnotatedTypeMirror isn't annotated for nullness. - public static ShallowType create(AnnotatedTypeMirror type) { - AnnotatedTypeMirror erasedType = type.getErased(); - TypeMirror typeMirror = - erasedType.getKind() == type.getKind() - ? type.getUnderlyingType() - : erasedType.getUnderlyingType(); - // The effective annotations are the primary annotations on the erased type. - return new ShallowType(erasedType.getAnnotations(), typeMirror); - } - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - ShallowType subShallowType = ShallowType.create(subtype); - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - subShallowType.annos, - subShallowType.typeMirror, - superShallowType.annos, - superShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror hierarchy) { - ShallowType subShallowType = ShallowType.create(subtype); - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, hierarchy), - subShallowType.typeMirror, - qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, hierarchy), - superShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, Collection superQualifiers) { - ShallowType subShallowType = ShallowType.create(subtype); - return qualHierarchy.isSubtypeShallow( - subShallowType.annos, superQualifiers, subShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - Collection subQualifiers, AnnotatedTypeMirror supertype) { - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - subQualifiers, superShallowType.annos, superShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, AnnotationMirror superQualifier) { - ShallowType subShallowType = ShallowType.create(subtype); - return qualHierarchy.isSubtypeShallow( - qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, superQualifier), - superQualifier, - subShallowType.typeMirror); - } - - @Override - public boolean isSubtypeShallowEffective( - AnnotationMirror subQualifier, AnnotatedTypeMirror supertype) { - ShallowType superShallowType = ShallowType.create(supertype); - return qualHierarchy.isSubtypeShallow( - subQualifier, - qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, subQualifier), - superShallowType.typeMirror); - } - - /** - * Returns true if {@code subtype <: supertype}, but only for the hierarchy of which {@code top} - * is the top. - * - * @param subtype expected subtype - * @param supertype expected supertype - * @param top the top of the hierarchy for which we want to make a comparison - * @return true if {@code subtype} is a subtype of or equal to {@code supertype}, in the qualifier - * hierarchy whose top is {@code top} - */ - protected boolean isSubtype( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror top) { - assert top != null; - currentTop = top; - return AtmCombo.accept(subtype, supertype, null, this); - } - - /** - * Returns error message for the case when two types shouldn't be compared. - * - * @return error message for the case when two types shouldn't be compared - */ - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Void p) { - return super.defaultErrorMessage(subtype, supertype, p) - + System.lineSeparator() - + " visitHistory = " - + isSubtypeVisitHistory; - } - - /** - * Compare the primary annotations of {@code subtype} and {@code supertype}. Neither type can be - * missing annotations. - * - * @param subtype a type that might be a subtype (with respect to primary annotations) - * @param supertype a type that might be a supertype (with respect to primary annotations) - * @return true if the primary annotation on subtype {@literal <:} primary annotation on supertype - * for the current top. - */ - protected boolean isPrimarySubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - TypeMirror subTM = subtype.getUnderlyingType(); - TypeMirror superTM = supertype.getUnderlyingType(); - - AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); - AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); - if (checker.getTypeFactory().hasQualifierParameterInHierarchy(supertype, currentTop) - && checker.getTypeFactory().hasQualifierParameterInHierarchy(subtype, currentTop)) { - // If the types have a class qualifier parameter, the qualifiers must be equivalent. - return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM) - && qualHierarchy.isSubtypeShallow(supertypeAnno, superTM, subtypeAnno, subTM); - } - - return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM); - } - - /** - * Like {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}, but uses a cache to prevent - * infinite recursion on recursive types. - * - * @param subtype a type that may be a subtype - * @param supertype a type that may be a supertype - * @return true if subtype {@literal <:} supertype - */ - protected boolean isSubtypeCaching(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { - if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { - // visitHistory only contains pairs in a subtype relationship. - return true; - } - - boolean result = isSubtype(subtype, supertype, currentTop); - // The call to put has no effect if result is false. - isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); - return result; - } - - /** - * Are all the types in {@code subtypes} a subtype of {@code supertype}? - * - *

          The underlying type mirrors of {@code subtypes} must be subtypes of the underlying type - * mirror of {@code supertype}. - */ - protected boolean areAllSubtypes( - Iterable subtypes, AnnotatedTypeMirror supertype) { - for (AnnotatedTypeMirror subtype : subtypes) { - if (!isSubtype(subtype, supertype, currentTop)) { - return false; - } - } - - return true; - } - - protected boolean areEqualInHierarchy(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - return equalityComparer.areEqualInHierarchy(type1, type2, currentTop); - } - - /** - * Returns true if {@code outside} contains {@code inside}, that is, if the set of types denoted - * by {@code outside} is a superset of, or equal to, the set of types denoted by {@code inside}. - * - *

          Containment is described in JLS section - * 4.5.1 "Type Arguments of Parameterized Types". - * - *

          As described in JLS section - * 4.10.2 Subtyping among Class and Interface Types, a declared type S is considered a - * supertype of another declared type T only if all of S's type arguments "contain" the - * corresponding type arguments of the subtype T. - * - * @param inside a possibly-contained type; its underlying type is contained by {@code outside}'s - * underlying type - * @param outside a possibly-containing type; its underlying type contains {@code inside}'s - * underlying type - * @param canBeCovariant whether or not type arguments are allowed to be covariant - * @return true if inside is contained by outside, or if canBeCovariant == true and {@code inside - * <: outside} - */ - protected boolean isContainedBy( - AnnotatedTypeMirror inside, AnnotatedTypeMirror outside, boolean canBeCovariant) { - Boolean previousResult = areEqualVisitHistory.get(inside, outside, currentTop); - if (previousResult != null) { - return previousResult; - } - - if (shouldIgnoreRawTypeArgs(inside) || shouldIgnoreRawTypeArgs(outside)) { - areEqualVisitHistory.put(inside, outside, currentTop, true); - return true; - } - - if (outside.getKind() == TypeKind.WILDCARD) { - // This is all cases except bullet 6, "T <= T". - AnnotatedWildcardType outsideWildcard = (AnnotatedWildcardType) outside; - - // Add a placeholder in case of recursion, to prevent infinite regress. - areEqualVisitHistory.put(inside, outside, currentTop, true); - boolean result = - isContainedWithinBounds( - inside, - outsideWildcard.getSuperBound(), - outsideWildcard.getExtendsBound(), - canBeCovariant); - areEqualVisitHistory.put(inside, outside, currentTop, result); - return result; - } else if (TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType())) { - // Sometimes the wildcard has been captured too early, so treat the captured type variable - // as wildcard. - // This is all cases except bullet 6, "T <= T". - AnnotatedTypeVariable outsideTypeVar = (AnnotatedTypeVariable) outside; - - // Add a placeholder in case of recursion, to prevent infinite regress. - areEqualVisitHistory.put(inside, outside, currentTop, true); - boolean result = - isContainedWithinBounds( - inside, - outsideTypeVar.getLowerBound(), - outsideTypeVar.getUpperBound(), - canBeCovariant); - areEqualVisitHistory.put(inside, outside, currentTop, result); - if (result) { - return true; - } - areEqualVisitHistory.remove(inside, outsideTypeVar, currentTop); - } - - // The remainder of the method is bullet 6, "T <= T". - if (canBeCovariant) { - return isSubtype(inside, outside, currentTop); - } - return areEqualInHierarchy(inside, outside); - } - - /** - * Let {@code outside} be {@code ? super outsideLower extends outsideUpper}. Returns true if - * {@code outside} contains {@code inside}, that is, if the set of types denoted by {@code - * outside} is a superset of, or equal to, the set of types denoted by {@code inside}. - * - *

          This method is a helper method for {@link #isContainedBy(AnnotatedTypeMirror, - * AnnotatedTypeMirror, boolean)}. - * - * @param inside a possibly-contained type - * @param outsideLower the lower bound of the possibly-containing type - * @param outsideUpper the upper bound of the possibly-containing type - * @param canBeCovariant whether or not type arguments are allowed to be covariant - * @return true if inside is contained by outside, or if canBeCovariant == true and {@code inside - * <: outside} - */ - protected boolean isContainedWithinBounds( - AnnotatedTypeMirror inside, - AnnotatedTypeMirror outsideLower, - AnnotatedTypeMirror outsideUpper, - boolean canBeCovariant) { - try { - if (canBeCovariant) { - if (outsideLower.getKind() == TypeKind.NULL) { - return isSubtype(inside, outsideUpper); - } else { - return isSubtype(outsideLower, inside); + @Override + public boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (!isSubtype(subtype, supertype, top)) { + return false; + } } - } - // If inside is a wildcard, then isSubtype(outsideLower, inside) calls - // isSubtype(outsideLower, inside.getLowerBound()) and isSubtype(inside, outsideUpper) - // calls isSubtype(inside.getUpperBound(), outsideUpper). This is slightly different - // from the algorithm in the JLS. Only one of the Java type bounds can be specified, - // but there can be annotations on both the upper and lower bound of a wildcard. - return isSubtype(outsideLower, inside) && isSubtype(inside, outsideUpper); - } catch (Throwable ex) { - // Work around: - // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8265255 - if (ex.getMessage().contains("AsSuperVisitor")) { - return false; - } - throw ex; - } - } - - /** - * Returns true if {@code type} is a type argument from a raw type and if the checker should not - * issue warnings about such type arguments. - * - * @param type type to check - * @return true if {@code type} is a type argument from a raw type and if the checker should not - * issue warnings about such type arguments. - */ - private boolean shouldIgnoreRawTypeArgs(AnnotatedTypeMirror type) { - return this.ignoreRawTypes && AnnotatedTypes.isTypeArgOfRawType(type); - } - - // ------------------------------------------------------------------------ - // The rest of this file is the visitor methods. It is a lot of methods, one for each - // combination of types. - - // ------------------------------------------------------------------------ - // Arrays as subtypes - - @Override - public Boolean visitArray_Array( - AnnotatedArrayType subtype, AnnotatedArrayType supertype, Void p) { - return isPrimarySubtype(subtype, supertype) - && (invariantArrayComponents - ? areEqualInHierarchy(subtype.getComponentType(), supertype.getComponentType()) - : isSubtype(subtype.getComponentType(), supertype.getComponentType(), currentTop)); - } - - @Override - public Boolean visitArray_Declared( - AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitArray_Null(AnnotatedArrayType subtype, AnnotatedNullType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitArray_Intersection( - AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, Void p) { - return isSubtype( - AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype), - supertype, - currentTop); - } - - @Override - public Boolean visitArray_Wildcard( - AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - @Override - public Boolean visitArray_Typevar( - AnnotatedArrayType subtype, AnnotatedTypeVariable superType, Void p) { - return visitType_Typevar(subtype, superType); - } - - // ------------------------------------------------------------------------ - // Declared as subtype - @Override - public Boolean visitDeclared_Array( - AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitDeclared_Declared( - AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, Void p) { - if (!isPrimarySubtype(subtype, supertype)) { - return false; - } - - if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { - return true; - } - - boolean result = - visitTypeArgs( - subtype, supertype, subtype.isUnderlyingTypeRaw(), supertype.isUnderlyingTypeRaw()); - isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); - - return result; - } - - /** - * Returns true if the type arguments in {@code supertype} contain the type arguments in {@code - * subtype} and false otherwise. See {@link #isContainedBy} for an explanation of containment. - * - * @param subtype a possible subtype - * @param supertype a possible supertype - * @param subtypeRaw whether {@code subtype} is a raw type - * @param supertypeRaw whether {@code supertype} is a raw type - * @return true if the type arguments in {@code supertype} contain the type arguments in {@code - * subtype} and false otherwise - */ - protected boolean visitTypeArgs( - AnnotatedDeclaredType subtype, - AnnotatedDeclaredType supertype, - boolean subtypeRaw, - boolean supertypeRaw) { - AnnotatedTypeFactory typeFactory = subtype.atypeFactory; - - // JLS 11: 4.10.2. Subtyping among Class and Interface Types - // 4th paragraph, bullet 1. - AnnotatedDeclaredType subtypeAsSuper = - AnnotatedTypes.castedAsSuper(typeFactory, subtype, supertype); - - if (ignoreRawTypes && (subtypeRaw || supertypeRaw)) { - return true; - } - - List subtypeTypeArgs = subtypeAsSuper.getTypeArguments(); - List supertypeTypeArgs = supertype.getTypeArguments(); - - if (subtypeTypeArgs.size() != supertypeTypeArgs.size()) { - throw new BugInCF("Type arguments are not the same size: %s %s", subtypeAsSuper, supertype); - } - // This method, `visitTypeArgs`, is called even if `subtype` doesn't have type arguments. - if (subtypeTypeArgs.isEmpty()) { - return true; - } - - List covariantArgIndexes = getCovariantArgIndexes(supertype); - - // JLS 11: 4.10.2. Subtyping among Class and Interface Types - // 4th paragraph, bullet 2 - try { - if (isContainedMany( - subtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes)) { - return true; - } - } catch (Exception e) { - // Some types need to be captured first, so ignore crashes. - for (int i = 0; i < supertypeTypeArgs.size(); i++) { - areEqualVisitHistory.remove( - subtypeAsSuper.getTypeArguments().get(i), supertypeTypeArgs.get(i), currentTop); - } - } - // 5th paragraph: - // Instead of calling isSubtype with the captured type, just check for containment. - AnnotatedDeclaredType capturedSubtype = - (AnnotatedDeclaredType) typeFactory.applyCaptureConversion(subtype); - AnnotatedDeclaredType capturedSubtypeAsSuper = - AnnotatedTypes.castedAsSuper(typeFactory, capturedSubtype, supertype); - return isContainedMany( - capturedSubtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes); - } - - @Override - public List getCovariantArgIndexes(AnnotatedDeclaredType type) { - TypeElement supertypeElem = (TypeElement) type.getUnderlyingType().asElement(); - AnnotationMirror covariantAnno = - type.atypeFactory.getDeclAnnotation(supertypeElem, Covariant.class); - if (covariantAnno == null) { - return Collections.emptyList(); - } - - return AnnotationUtils.getElementValueArray( - covariantAnno, covariantValueElement, Integer.class); - } - - /** - * Calls {@link #isContainedBy(AnnotatedTypeMirror, AnnotatedTypeMirror, boolean)} on the two - * lists of type arguments. Returns true if every type argument in {@code supertypeTypeArgs} - * contains the type argument at the same index in {@code subtypeTypeArgs}. - * - * @param subtypeTypeArgs subtype arguments - * @param supertypeTypeArgs supertype arguments - * @param covariantArgIndexes indexes into the type arguments list which correspond to the type - * arguments that are marked @{@link Covariant}. - * @return whether {@code supertypeTypeArgs} contain {@code subtypeTypeArgs} - */ - protected boolean isContainedMany( - List subtypeTypeArgs, - List supertypeTypeArgs, - List covariantArgIndexes) { - for (int i = 0; i < supertypeTypeArgs.size(); i++) { - AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i); - AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i); - boolean covariant = covariantArgIndexes.contains(i); - if (!isContainedBy(subTypeArg, superTypeArg, covariant)) { - return false; - } - } - return true; - } - - @Override - public Boolean visitDeclared_Intersection( - AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, Void p) { - return visitType_Intersection(subtype, supertype); - } - - @Override - public Boolean visitDeclared_Null( - AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitDeclared_Primitive( - AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, Void p) { - AnnotatedTypeMirror unboxedType; - try { - unboxedType = subtype.atypeFactory.getUnboxedType(subtype); - } catch (IllegalArgumentException ex) { - throw new BugInCF( - "DefaultTypeHierarchy: subtype isn't a boxed type: subtype: %s superType: %s", - subtype, supertype); - } - return isPrimarySubtype(unboxedType, supertype); - } - - @Override - public Boolean visitDeclared_Typevar( - AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitType_Typevar(subtype, supertype); - } - - @Override - public Boolean visitDeclared_Union( - AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Void p) { - Types types = checker.getTypeUtils(); - for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { - if (TypesUtils.isErasedSubtype( - subtype.getUnderlyingType(), supertypeAltern.getUnderlyingType(), types) - && isSubtype(subtype, supertypeAltern, currentTop)) { - return true; - } - } - return false; - } - - @Override - public Boolean visitDeclared_Wildcard( - AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // Intersection as subtype - @Override - public Boolean visitIntersection_Declared( - AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, Void p) { - return visitIntersection_Type(subtype, supertype); - } - - @Override - public Boolean visitIntersection_Primitive( - AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, Void p) { - for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { - if (TypesUtils.isBoxedPrimitive(subtypeBound.getUnderlyingType()) - && isSubtype(subtypeBound, supertype, currentTop)) { + return true; - } - } - return false; - } - - @Override - public Boolean visitIntersection_Intersection( - AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, Void p) { - Types types = checker.getTypeUtils(); - for (AnnotatedTypeMirror subBound : subtype.getBounds()) { - for (AnnotatedTypeMirror superBound : supertype.getBounds()) { - if (TypesUtils.isErasedSubtype( - subBound.getUnderlyingType(), superBound.getUnderlyingType(), types) - && !isSubtype(subBound, superBound, currentTop)) { - return false; + } + + /** A set of annotations and a {@link TypeMirror}. */ + @AnnotatedFor("nullness") + private static class ShallowType { + + /** A set of annotations. */ + AnnotationMirrorSet annos; + + /** A TypeMirror. */ + TypeMirror typeMirror; + + /** + * Creates a {@code ShallowType}. + * + * @param annos a set of annotations + * @param typeMirror a type mirror + */ + private ShallowType(AnnotationMirrorSet annos, TypeMirror typeMirror) { + this.annos = annos; + this.typeMirror = typeMirror; + } + + /** + * Creates a {@code ShallowType} from {@code type}: the annotations are the effective + * annotations on {@code type} and the type mirror is the underlying type of {@code type}. + * + * @param type an annotated type to convert to a {@code ShallowType} + * @return a shallow type created from {@code type} + */ + @SuppressWarnings("nullness") // AnnotatedTypeMirror isn't annotated for nullness. + public static ShallowType create(AnnotatedTypeMirror type) { + AnnotatedTypeMirror erasedType = type.getErased(); + TypeMirror typeMirror = + erasedType.getKind() == type.getKind() + ? type.getUnderlyingType() + : erasedType.getUnderlyingType(); + // The effective annotations are the primary annotations on the erased type. + return new ShallowType(erasedType.getAnnotations(), typeMirror); } - } } - return true; - } - @Override - public Boolean visitIntersection_Null( - AnnotatedIntersectionType subtype, AnnotatedNullType supertype, Void p) { - // this can occur through capture conversion/comparing bounds - for (AnnotatedTypeMirror bound : subtype.getBounds()) { - if (isPrimarySubtype(bound, supertype)) { - return true; - } - } - return false; - } - - @Override - public Boolean visitIntersection_Typevar( - AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitIntersection_Type(subtype, supertype); - } - - @Override - public Boolean visitIntersection_Wildcard( - AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, Void p) { - return visitIntersection_Type(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // Null as subtype - @Override - public Boolean visitNull_Array(AnnotatedNullType subtype, AnnotatedArrayType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Declared( - AnnotatedNullType subtype, AnnotatedDeclaredType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Typevar( - AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitType_Typevar(subtype, supertype); - } - - @Override - public Boolean visitNull_Wildcard( - AnnotatedNullType subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - @Override - public Boolean visitNull_Null(AnnotatedNullType subtype, AnnotatedNullType supertype, Void p) { - // this can occur when comparing typevar lower bounds since they are usually null types - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Union(AnnotatedNullType subtype, AnnotatedUnionType supertype, Void p) { - for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { - if (isSubtype(subtype, supertypeAltern, currentTop)) { + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + ShallowType subShallowType = ShallowType.create(subtype); + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + subShallowType.annos, + subShallowType.typeMirror, + superShallowType.annos, + superShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, + AnnotatedTypeMirror supertype, + AnnotationMirror hierarchy) { + ShallowType subShallowType = ShallowType.create(subtype); + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, hierarchy), + subShallowType.typeMirror, + qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, hierarchy), + superShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, Collection superQualifiers) { + ShallowType subShallowType = ShallowType.create(subtype); + return qualHierarchy.isSubtypeShallow( + subShallowType.annos, superQualifiers, subShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + Collection subQualifiers, AnnotatedTypeMirror supertype) { + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + subQualifiers, superShallowType.annos, superShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, AnnotationMirror superQualifier) { + ShallowType subShallowType = ShallowType.create(subtype); + return qualHierarchy.isSubtypeShallow( + qualHierarchy.findAnnotationInSameHierarchy(subShallowType.annos, superQualifier), + superQualifier, + subShallowType.typeMirror); + } + + @Override + public boolean isSubtypeShallowEffective( + AnnotationMirror subQualifier, AnnotatedTypeMirror supertype) { + ShallowType superShallowType = ShallowType.create(supertype); + return qualHierarchy.isSubtypeShallow( + subQualifier, + qualHierarchy.findAnnotationInSameHierarchy(superShallowType.annos, subQualifier), + superShallowType.typeMirror); + } + + /** + * Returns true if {@code subtype <: supertype}, but only for the hierarchy of which {@code top} + * is the top. + * + * @param subtype expected subtype + * @param supertype expected supertype + * @param top the top of the hierarchy for which we want to make a comparison + * @return true if {@code subtype} is a subtype of or equal to {@code supertype}, in the + * qualifier hierarchy whose top is {@code top} + */ + protected boolean isSubtype( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror top) { + assert top != null; + currentTop = top; + return AtmCombo.accept(subtype, supertype, null, this); + } + + /** + * Returns error message for the case when two types shouldn't be compared. + * + * @return error message for the case when two types shouldn't be compared + */ + @Override + public String defaultErrorMessage( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, Void p) { + return super.defaultErrorMessage(subtype, supertype, p) + + System.lineSeparator() + + " visitHistory = " + + isSubtypeVisitHistory; + } + + /** + * Compare the primary annotations of {@code subtype} and {@code supertype}. Neither type can be + * missing annotations. + * + * @param subtype a type that might be a subtype (with respect to primary annotations) + * @param supertype a type that might be a supertype (with respect to primary annotations) + * @return true if the primary annotation on subtype {@literal <:} primary annotation on + * supertype for the current top. + */ + protected boolean isPrimarySubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + TypeMirror subTM = subtype.getUnderlyingType(); + TypeMirror superTM = supertype.getUnderlyingType(); + + AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); + AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); + if (checker.getTypeFactory().hasQualifierParameterInHierarchy(supertype, currentTop) + && checker.getTypeFactory().hasQualifierParameterInHierarchy(subtype, currentTop)) { + // If the types have a class qualifier parameter, the qualifiers must be equivalent. + return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM) + && qualHierarchy.isSubtypeShallow(supertypeAnno, superTM, subtypeAnno, subTM); + } + + return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, supertypeAnno, superTM); + } + + /** + * Like {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}, but uses a cache to + * prevent infinite recursion on recursive types. + * + * @param subtype a type that may be a subtype + * @param supertype a type that may be a supertype + * @return true if subtype {@literal <:} supertype + */ + protected boolean isSubtypeCaching(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) { + if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { + // visitHistory only contains pairs in a subtype relationship. + return true; + } + + boolean result = isSubtype(subtype, supertype, currentTop); + // The call to put has no effect if result is false. + isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); + return result; + } + + /** + * Are all the types in {@code subtypes} a subtype of {@code supertype}? + * + *

          The underlying type mirrors of {@code subtypes} must be subtypes of the underlying type + * mirror of {@code supertype}. + */ + protected boolean areAllSubtypes( + Iterable subtypes, AnnotatedTypeMirror supertype) { + for (AnnotatedTypeMirror subtype : subtypes) { + if (!isSubtype(subtype, supertype, currentTop)) { + return false; + } + } + return true; - } - } - return false; - } - - @Override - public Boolean visitNull_Intersection( - AnnotatedNullType subtype, AnnotatedIntersectionType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitNull_Primitive( - AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // Primitive as subtype - @Override - public Boolean visitPrimitive_Declared( - AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, Void p) { - AnnotatedTypeFactory atypeFactory = subtype.atypeFactory; - Types types = atypeFactory.types; - AnnotatedPrimitiveType narrowedType = subtype; - if (TypesUtils.isBoxedPrimitive(supertype.getUnderlyingType())) { - TypeMirror unboxedSuper = types.unboxedType(supertype.getUnderlyingType()); - if (unboxedSuper.getKind() != subtype.getKind() - && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { - narrowedType = atypeFactory.getNarrowedPrimitive(subtype, unboxedSuper); - } - } - AnnotatedTypeMirror boxedSubtype = atypeFactory.getBoxedType(narrowedType); - return isPrimarySubtype(boxedSubtype, supertype); - } - - @Override - public Boolean visitPrimitive_Primitive( - AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, Void p) { - return isPrimarySubtype(subtype, supertype); - } - - @Override - public Boolean visitPrimitive_Intersection( - AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, Void p) { - return visitType_Intersection(subtype, supertype); - } - - @Override - public Boolean visitPrimitive_Typevar( - AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, Void p) { - return AtmCombo.accept(subtype, supertype.getUpperBound(), null, this); - } - - @Override - public Boolean visitPrimitive_Wildcard( - AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, Void p) { - if (shouldIgnoreRawTypeArgs(supertype)) { - return true; - } - // this can occur when passing a primitive to a method on a raw type (see test - // checker/tests/nullness/RawAndPrimitive.java). This can also occur because we don't box - // primitives when we should and don't capture convert. - return isPrimarySubtype(subtype, supertype.getSuperBound()); - } - - // ------------------------------------------------------------------------ - // Union as subtype - @Override - public Boolean visitUnion_Declared( - AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, Void p) { - return visitUnion_Type(subtype, supertype); - } - - @Override - public Boolean visitUnion_Intersection( - AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, Void p) { - // For example: - // void method(T param) {} - // ... - // catch (Exception1 | Exception2 union) { // Assuming Exception1 and Exception2 implement - // Cloneable - // method(union); - // This case happens when checking that the inferred type argument is a subtype of the - // declared type argument of method. - // See org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments - return visitUnion_Type(subtype, supertype); - } - - @Override - public Boolean visitUnion_Union( - AnnotatedUnionType subtype, AnnotatedUnionType supertype, Void p) { - // For example: - // void method(T param) {} - // ... - // catch (Exception1 | Exception2 union) { - // method(union); - // This case happens when checking the arguments to method after type variable substitution - return visitUnion_Type(subtype, supertype); - } - - @Override - public Boolean visitUnion_Wildcard( - AnnotatedUnionType subtype, AnnotatedWildcardType supertype, Void p) { - // For example: - // } catch (RuntimeException | IOException e) { - // ArrayList lWildcard = new ArrayList<>(); - // lWildcard.add(e); - - return visitType_Wildcard(subtype, supertype); - } - - @Override - public Boolean visitUnion_Typevar( - AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, Void p) { - // For example: - // } catch (RuntimeException | IOException e) { - // ArrayList lWildcard = new ArrayList<>(); - // lWildcard.add(e); - - return visitType_Typevar(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // typevar as subtype - @Override - public Boolean visitTypevar_Declared( - AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, Void p) { - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Intersection( - AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, Void p) { - // this can happen when checking type param bounds - return visitType_Intersection(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Primitive( - AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, Void p) { - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Array( - AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, Void p) { - // This happens when the type variable is a captured wildcard. - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Typevar( - AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, Void p) { - TypeMirror subTM = subtype.getUnderlyingType(); - TypeMirror superTM = supertype.getUnderlyingType(); - if (AnnotatedTypes.haveSameDeclaration(checker.getTypeUtils(), subtype, supertype)) { - // The underlying types of subtype and supertype are uses of the same type parameter, - // but they may have different primary annotations. - AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); - boolean subtypeHasAnno = subtypeAnno != null; - AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); - boolean supertypeHasAnno = supertypeAnno != null; - - if (subtypeHasAnno && supertypeHasAnno) { - // If both have primary annotations then just check the primary annotations - // as the bounds are the same. + } + + protected boolean areEqualInHierarchy(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + return equalityComparer.areEqualInHierarchy(type1, type2, currentTop); + } + + /** + * Returns true if {@code outside} contains {@code inside}, that is, if the set of types denoted + * by {@code outside} is a superset of, or equal to, the set of types denoted by {@code inside}. + * + *

          Containment is described in JLS section + * 4.5.1 "Type Arguments of Parameterized Types". + * + *

          As described in JLS section + * 4.10.2 Subtyping among Class and Interface Types, a declared type S is considered a + * supertype of another declared type T only if all of S's type arguments "contain" the + * corresponding type arguments of the subtype T. + * + * @param inside a possibly-contained type; its underlying type is contained by {@code + * outside}'s underlying type + * @param outside a possibly-containing type; its underlying type contains {@code inside}'s + * underlying type + * @param canBeCovariant whether or not type arguments are allowed to be covariant + * @return true if inside is contained by outside, or if canBeCovariant == true and {@code + * inside <: outside} + */ + protected boolean isContainedBy( + AnnotatedTypeMirror inside, AnnotatedTypeMirror outside, boolean canBeCovariant) { + Boolean previousResult = areEqualVisitHistory.get(inside, outside, currentTop); + if (previousResult != null) { + return previousResult; + } + + if (shouldIgnoreRawTypeArgs(inside) || shouldIgnoreRawTypeArgs(outside)) { + areEqualVisitHistory.put(inside, outside, currentTop, true); + return true; + } + + if (outside.getKind() == TypeKind.WILDCARD) { + // This is all cases except bullet 6, "T <= T". + AnnotatedWildcardType outsideWildcard = (AnnotatedWildcardType) outside; + + // Add a placeholder in case of recursion, to prevent infinite regress. + areEqualVisitHistory.put(inside, outside, currentTop, true); + boolean result = + isContainedWithinBounds( + inside, + outsideWildcard.getSuperBound(), + outsideWildcard.getExtendsBound(), + canBeCovariant); + areEqualVisitHistory.put(inside, outside, currentTop, result); + return result; + } else if (TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType())) { + // Sometimes the wildcard has been captured too early, so treat the captured type + // variable + // as wildcard. + // This is all cases except bullet 6, "T <= T". + AnnotatedTypeVariable outsideTypeVar = (AnnotatedTypeVariable) outside; + + // Add a placeholder in case of recursion, to prevent infinite regress. + areEqualVisitHistory.put(inside, outside, currentTop, true); + boolean result = + isContainedWithinBounds( + inside, + outsideTypeVar.getLowerBound(), + outsideTypeVar.getUpperBound(), + canBeCovariant); + areEqualVisitHistory.put(inside, outside, currentTop, result); + if (result) { + return true; + } + areEqualVisitHistory.remove(inside, outsideTypeVar, currentTop); + } + + // The remainder of the method is bullet 6, "T <= T". + if (canBeCovariant) { + return isSubtype(inside, outside, currentTop); + } + return areEqualInHierarchy(inside, outside); + } + + /** + * Let {@code outside} be {@code ? super outsideLower extends outsideUpper}. Returns true if + * {@code outside} contains {@code inside}, that is, if the set of types denoted by {@code + * outside} is a superset of, or equal to, the set of types denoted by {@code inside}. + * + *

          This method is a helper method for {@link #isContainedBy(AnnotatedTypeMirror, + * AnnotatedTypeMirror, boolean)}. + * + * @param inside a possibly-contained type + * @param outsideLower the lower bound of the possibly-containing type + * @param outsideUpper the upper bound of the possibly-containing type + * @param canBeCovariant whether or not type arguments are allowed to be covariant + * @return true if inside is contained by outside, or if canBeCovariant == true and {@code + * inside <: outside} + */ + protected boolean isContainedWithinBounds( + AnnotatedTypeMirror inside, + AnnotatedTypeMirror outsideLower, + AnnotatedTypeMirror outsideUpper, + boolean canBeCovariant) { + try { + if (canBeCovariant) { + if (outsideLower.getKind() == TypeKind.NULL) { + return isSubtype(inside, outsideUpper); + } else { + return isSubtype(outsideLower, inside); + } + } + // If inside is a wildcard, then isSubtype(outsideLower, inside) calls + // isSubtype(outsideLower, inside.getLowerBound()) and isSubtype(inside, outsideUpper) + // calls isSubtype(inside.getUpperBound(), outsideUpper). This is slightly different + // from the algorithm in the JLS. Only one of the Java type bounds can be specified, + // but there can be annotations on both the upper and lower bound of a wildcard. + return isSubtype(outsideLower, inside) && isSubtype(inside, outsideUpper); + } catch (Throwable ex) { + // Work around: + // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8265255 + if (ex.getMessage().contains("AsSuperVisitor")) { + return false; + } + throw ex; + } + } + + /** + * Returns true if {@code type} is a type argument from a raw type and if the checker should not + * issue warnings about such type arguments. + * + * @param type type to check + * @return true if {@code type} is a type argument from a raw type and if the checker should not + * issue warnings about such type arguments. + */ + private boolean shouldIgnoreRawTypeArgs(AnnotatedTypeMirror type) { + return this.ignoreRawTypes && AnnotatedTypes.isTypeArgOfRawType(type); + } + + // ------------------------------------------------------------------------ + // The rest of this file is the visitor methods. It is a lot of methods, one for each + // combination of types. + + // ------------------------------------------------------------------------ + // Arrays as subtypes + + @Override + public Boolean visitArray_Array( + AnnotatedArrayType subtype, AnnotatedArrayType supertype, Void p) { + return isPrimarySubtype(subtype, supertype) + && (invariantArrayComponents + ? areEqualInHierarchy( + subtype.getComponentType(), supertype.getComponentType()) + : isSubtype( + subtype.getComponentType(), + supertype.getComponentType(), + currentTop)); + } + + @Override + public Boolean visitArray_Declared( + AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitArray_Null( + AnnotatedArrayType subtype, AnnotatedNullType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitArray_Intersection( + AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, Void p) { + return isSubtype( + AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype), + supertype, + currentTop); + } + + @Override + public Boolean visitArray_Wildcard( + AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + @Override + public Boolean visitArray_Typevar( + AnnotatedArrayType subtype, AnnotatedTypeVariable superType, Void p) { + return visitType_Typevar(subtype, superType); + } + + // ------------------------------------------------------------------------ + // Declared as subtype + @Override + public Boolean visitDeclared_Array( + AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Void p) { return isPrimarySubtype(subtype, supertype); - } else if (!subtypeHasAnno && !supertypeHasAnno) { - // Two unannotated uses of the same type parameter need to compare - // both upper and lower bounds. - - // Upper bound of the subtype needs to be below the upper bound of the supertype. - if (!qualHierarchy.isSubtypeShallow( - subtype.getEffectiveAnnotationInHierarchy(currentTop), - subTM, - supertype.getEffectiveAnnotationInHierarchy(currentTop), - superTM)) { - return false; + } + + @Override + public Boolean visitDeclared_Declared( + AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, Void p) { + if (!isPrimarySubtype(subtype, supertype)) { + return false; } - // Lower bound of the subtype needs to be below the lower bound of the supertype. - // TODO: Think through this and add better test coverage. - AnnotationMirrorSet subLBs = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, subtype); - AnnotationMirror subLB = qualHierarchy.findAnnotationInHierarchy(subLBs, currentTop); - AnnotationMirrorSet superLBs = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); - AnnotationMirror superLB = qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); - return qualHierarchy.isSubtypeShallow(subLB, subTM, superLB, superTM); - } else if (subtypeHasAnno && !supertypeHasAnno) { - // This is the case "@A T <: T" where T is a type variable. - // TODO: should this also test the upper bounds? - AnnotationMirrorSet superLBs = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); - AnnotationMirror superLB = qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); - return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, superLB, superTM); - } else if (!subtypeHasAnno && supertypeHasAnno) { - // This is the case "T <: @A T" where T is a type variable. - // TODO: should this also test the lower bounds? - return qualHierarchy.isSubtypeShallow( - subtype.getEffectiveAnnotationInHierarchy(currentTop), subTM, supertypeAnno, superTM); - } else { - throw new BugInCF("Unreachable"); - } + if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { + return true; + } + + boolean result = + visitTypeArgs( + subtype, + supertype, + subtype.isUnderlyingTypeRaw(), + supertype.isUnderlyingTypeRaw()); + isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); + + return result; } - if (AnnotatedTypes.areCorrespondingTypeVariables( - checker.getProcessingEnvironment().getElementUtils(), subtype, supertype)) { - if (areEqualInHierarchy(subtype, supertype)) { - return true; - } + /** + * Returns true if the type arguments in {@code supertype} contain the type arguments in {@code + * subtype} and false otherwise. See {@link #isContainedBy} for an explanation of containment. + * + * @param subtype a possible subtype + * @param supertype a possible supertype + * @param subtypeRaw whether {@code subtype} is a raw type + * @param supertypeRaw whether {@code supertype} is a raw type + * @return true if the type arguments in {@code supertype} contain the type arguments in {@code + * subtype} and false otherwise + */ + protected boolean visitTypeArgs( + AnnotatedDeclaredType subtype, + AnnotatedDeclaredType supertype, + boolean subtypeRaw, + boolean supertypeRaw) { + AnnotatedTypeFactory typeFactory = subtype.atypeFactory; + + // JLS 11: 4.10.2. Subtyping among Class and Interface Types + // 4th paragraph, bullet 1. + AnnotatedDeclaredType subtypeAsSuper = + AnnotatedTypes.castedAsSuper(typeFactory, subtype, supertype); + + if (ignoreRawTypes && (subtypeRaw || supertypeRaw)) { + return true; + } + + List subtypeTypeArgs = subtypeAsSuper.getTypeArguments(); + List supertypeTypeArgs = supertype.getTypeArguments(); + + if (subtypeTypeArgs.size() != supertypeTypeArgs.size()) { + throw new BugInCF( + "Type arguments are not the same size: %s %s", subtypeAsSuper, supertype); + } + // This method, `visitTypeArgs`, is called even if `subtype` doesn't have type arguments. + if (subtypeTypeArgs.isEmpty()) { + return true; + } + + List covariantArgIndexes = getCovariantArgIndexes(supertype); + + // JLS 11: 4.10.2. Subtyping among Class and Interface Types + // 4th paragraph, bullet 2 + try { + if (isContainedMany( + subtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes)) { + return true; + } + } catch (Exception e) { + // Some types need to be captured first, so ignore crashes. + for (int i = 0; i < supertypeTypeArgs.size(); i++) { + areEqualVisitHistory.remove( + subtypeAsSuper.getTypeArguments().get(i), + supertypeTypeArgs.get(i), + currentTop); + } + } + // 5th paragraph: + // Instead of calling isSubtype with the captured type, just check for containment. + AnnotatedDeclaredType capturedSubtype = + (AnnotatedDeclaredType) typeFactory.applyCaptureConversion(subtype); + AnnotatedDeclaredType capturedSubtypeAsSuper = + AnnotatedTypes.castedAsSuper(typeFactory, capturedSubtype, supertype); + return isContainedMany( + capturedSubtypeAsSuper.getTypeArguments(), supertypeTypeArgs, covariantArgIndexes); } - if (TypesUtils.isCapturedTypeVariable(subTM) && TypesUtils.isCapturedTypeVariable(superTM)) { - // This case happens when the captured type variables should be the same type, but - // aren't because type argument inference isn't implemented correctly. - if (isContainedWithinBounds( - subtype, supertype.getLowerBound(), supertype.getUpperBound(), false)) { - return true; - } - } - - if (supertype.getLowerBound().getKind() != TypeKind.NULL) { - return visit(subtype, supertype.getLowerBound(), p); - } - // check that the upper bound of the subtype is below the lower bound of the supertype - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Null( - AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Void p) { - return visitTypevar_Type(subtype, supertype); - } - - @Override - public Boolean visitTypevar_Wildcard( - AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, Void p) { - return visitType_Wildcard(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // wildcard as subtype - - @Override - public Boolean visitWildcard_Array( - AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Void p) { - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Declared( - AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, Void p) { - if (subtype.isTypeArgOfRawType()) { - if (ignoreRawTypes) { - return true; - } else if (supertype.getTypeArguments().isEmpty()) { - // visitWildcard_Type doesn't check type arguments from raw types, because the - // underlying Java types may not be in the correct relationship. But, if the - // declared type does not have type arguments, then checking primary annotations is - // sufficient. - // For example, if the wildcard is ? extends @Nullable Object and the supertype is - // @Nullable String, then it is safe to return true. However if the supertype is - // @NullableList<@NonNull String> then it's not possible to decide if it is a - // subtype of the wildcard. - return isSubtypeShallowEffective(subtype, supertype, currentTop); - } - } - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Intersection( - AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, Void p) { - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Primitive( - AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, Void p) { - if (subtype.isTypeArgOfRawType()) { - return isSubtypeShallowEffective(subtype, supertype, currentTop); - } - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Typevar( - AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, Void p) { - return visitWildcard_Type(subtype, supertype); - } - - @Override - public Boolean visitWildcard_Wildcard( - AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, Void p) { - return visitWildcard_Type(subtype, supertype); - } - - // ------------------------------------------------------------------------ - // These "visit" methods are utility methods that aren't part of the visit interface - // but that handle cases that more than one visit method shares in common. - - /** - * An intersection is a supertype if all of its bounds are a supertype of subtype. - * - * @param subtype the possible subtype - * @param supertype the possible supertype - * @return true {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitType_Intersection( - AnnotatedTypeMirror subtype, AnnotatedIntersectionType supertype) { - if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { - return true; - } - boolean result = true; - for (AnnotatedTypeMirror bound : supertype.getBounds()) { - // Only call isSubtype if the Java type is actually a subtype; otherwise, - // only check primary qualifiers. - if (TypesUtils.isErasedSubtype( - subtype.getUnderlyingType(), bound.getUnderlyingType(), subtype.atypeFactory.types) - && !isSubtype(subtype, bound, currentTop)) { - result = false; - break; - } - } - isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); - return result; - } - - /** - * An intersection is a subtype if one of its bounds is a subtype of {@code supertype}. - * - * @param subtype an intersection type - * @param supertype an annotated type - * @return whether {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitIntersection_Type( - AnnotatedIntersectionType subtype, AnnotatedTypeMirror supertype) { - Types types = checker.getTypeUtils(); - // The primary annotations of the bounds should already be the same as the annotations on - // the intersection type. - for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { - if (TypesUtils.isErasedSubtype( - subtypeBound.getUnderlyingType(), supertype.getUnderlyingType(), types) - && isSubtype(subtypeBound, supertype, currentTop)) { + @Override + public List getCovariantArgIndexes(AnnotatedDeclaredType type) { + TypeElement supertypeElem = (TypeElement) type.getUnderlyingType().asElement(); + AnnotationMirror covariantAnno = + type.atypeFactory.getDeclAnnotation(supertypeElem, Covariant.class); + if (covariantAnno == null) { + return Collections.emptyList(); + } + + return AnnotationUtils.getElementValueArray( + covariantAnno, covariantValueElement, Integer.class); + } + + /** + * Calls {@link #isContainedBy(AnnotatedTypeMirror, AnnotatedTypeMirror, boolean)} on the two + * lists of type arguments. Returns true if every type argument in {@code supertypeTypeArgs} + * contains the type argument at the same index in {@code subtypeTypeArgs}. + * + * @param subtypeTypeArgs subtype arguments + * @param supertypeTypeArgs supertype arguments + * @param covariantArgIndexes indexes into the type arguments list which correspond to the type + * arguments that are marked @{@link Covariant}. + * @return whether {@code supertypeTypeArgs} contain {@code subtypeTypeArgs} + */ + protected boolean isContainedMany( + List subtypeTypeArgs, + List supertypeTypeArgs, + List covariantArgIndexes) { + for (int i = 0; i < supertypeTypeArgs.size(); i++) { + AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i); + AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i); + boolean covariant = covariantArgIndexes.contains(i); + if (!isContainedBy(subTypeArg, superTypeArg, covariant)) { + return false; + } + } return true; - } - } - return false; - } - - /** - * A type variable is a supertype if its lower bound is above subtype. - * - * @param subtype a type that might be a subtype - * @param supertype a type that might be a supertype - * @return true if {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitType_Typevar( - AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype) { - return isSubtypeCaching(subtype, supertype.getLowerBound()); - } - - /** - * A type variable is a subtype if its upper bound is below the supertype. - * - * @param subtype a type that might be a subtype - * @param supertype a type that might be a supertype - * @return true if {@code subtype} is a subtype of {@code supertype} - */ - protected boolean visitTypevar_Type( - AnnotatedTypeVariable subtype, AnnotatedTypeMirror supertype) { - AnnotatedTypeMirror subtypeUpperBound = subtype.getUpperBound(); - if (TypesUtils.isBoxedPrimitive(subtypeUpperBound.getUnderlyingType()) - && supertype instanceof AnnotatedPrimitiveType) { - subtypeUpperBound = - subtype.atypeFactory.getUnboxedType((AnnotatedDeclaredType) subtypeUpperBound); - } - if (supertype.getKind() == TypeKind.DECLARED - && TypesUtils.getTypeElement(supertype.getUnderlyingType()).getKind() - == ElementKind.INTERFACE) { - // The supertype is an interface. - subtypeUpperBound = getNonWildcardOrTypeVarUpperBound(subtypeUpperBound); - if (subtypeUpperBound.getKind() == TypeKind.INTERSECTION) { - // Only compare the primary annotations. + } + + @Override + public Boolean visitDeclared_Intersection( + AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, Void p) { + return visitType_Intersection(subtype, supertype); + } + + @Override + public Boolean visitDeclared_Null( + AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitDeclared_Primitive( + AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, Void p) { + AnnotatedTypeMirror unboxedType; + try { + unboxedType = subtype.atypeFactory.getUnboxedType(subtype); + } catch (IllegalArgumentException ex) { + throw new BugInCF( + "DefaultTypeHierarchy: subtype isn't a boxed type: subtype: %s superType: %s", + subtype, supertype); + } + return isPrimarySubtype(unboxedType, supertype); + } + + @Override + public Boolean visitDeclared_Typevar( + AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitType_Typevar(subtype, supertype); + } + + @Override + public Boolean visitDeclared_Union( + AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Void p) { Types types = checker.getTypeUtils(); - for (AnnotatedTypeMirror bound : - ((AnnotatedIntersectionType) subtypeUpperBound).getBounds()) { - // Make sure the upper bound is no wildcard or type variable. - bound = getNonWildcardOrTypeVarUpperBound(bound); - if (TypesUtils.isErasedSubtype( - bound.getUnderlyingType(), supertype.getUnderlyingType(), types) - && isPrimarySubtype(bound, supertype)) { - return true; - } + for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { + if (TypesUtils.isErasedSubtype( + subtype.getUnderlyingType(), supertypeAltern.getUnderlyingType(), types) + && isSubtype(subtype, supertypeAltern, currentTop)) { + return true; + } } return false; - } - } - try { - return isSubtypeCaching(subtypeUpperBound, supertype); - } catch (BugInCF e) { - if (TypesUtils.isCapturedTypeVariable(subtype.underlyingType)) { - // The upper bound of captured type variable may be computed incorrectly by javac. - // javac computes the upper bound as a declared type, when it should be an - // intersection type. - // (This is a bug in the GLB algorithm; see - // https://bugs.openjdk.org/browse/JDK-8039222) - // In this case, the upperbound is not a subtype of `supertype` and the Checker - // Framework crashes. So catch that crash and just return false. - // TODO: catch the problem more locally. + } + + @Override + public Boolean visitDeclared_Wildcard( + AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // Intersection as subtype + @Override + public Boolean visitIntersection_Declared( + AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, Void p) { + return visitIntersection_Type(subtype, supertype); + } + + @Override + public Boolean visitIntersection_Primitive( + AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, Void p) { + for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { + if (TypesUtils.isBoxedPrimitive(subtypeBound.getUnderlyingType()) + && isSubtype(subtypeBound, supertype, currentTop)) { + return true; + } + } return false; - } - throw e; - } - } - - /** - * If {@code type} is a type variable or wildcard recur on its upper bound until an upper bound is - * found that is neither a type variable nor a wildcard. - * - * @param type the type - * @return if {@code type} is a type variable or wildcard, recur on its upper bound until an upper - * bound is found that is neither a type variable nor a wildcard. Otherwise, return {@code - * type} itself. - */ - private AnnotatedTypeMirror getNonWildcardOrTypeVarUpperBound(AnnotatedTypeMirror type) { - while (type.getKind() == TypeKind.TYPEVAR || type.getKind() == TypeKind.WILDCARD) { - if (type.getKind() == TypeKind.TYPEVAR) { - type = ((AnnotatedTypeVariable) type).getUpperBound(); - } - if (type.getKind() == TypeKind.WILDCARD) { - type = ((AnnotatedWildcardType) type).getExtendsBound(); - } - } - return type; - } - - /** - * A union type is a subtype if ALL of its alternatives are subtypes of supertype. - * - * @param subtype the potential subtype to check - * @param supertype the supertype to check - * @return whether all the alternatives of subtype are subtypes of supertype - */ - protected boolean visitUnion_Type(AnnotatedUnionType subtype, AnnotatedTypeMirror supertype) { - return areAllSubtypes(subtype.getAlternatives(), supertype); - } - - /** - * Check a wildcard type's relation against a subtype. - * - * @param subtype the potential subtype to check - * @param supertype the wildcard supertype to check - * @return whether the subtype is a subtype of the supertype's super bound - */ - protected boolean visitType_Wildcard( - AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype) { - if (supertype.isTypeArgOfRawType()) { - return ignoreRawTypes; - } - return isSubtype(subtype, supertype.getSuperBound(), currentTop); - } - - /** - * Check a wildcard type's relation against a supertype. - * - * @param subtype the potential wildcard subtype to check - * @param supertype the supertype to check - * @return whether the subtype's extends bound is a subtype of the supertype - */ - protected boolean visitWildcard_Type( - AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype) { - if (subtype.isTypeArgOfRawType()) { - return ignoreRawTypes; - } - - if (supertype.getKind() == TypeKind.WILDCARD) { - // This can happen at a method invocation where a type variable in the method - // declaration is substituted with a wildcard. - // For example: - // void method(Gen t) {} - // Gen x; - // method(x); - // visitWildcard_Type is called when checking the method call `method(x)`, - // and also when checking lambdas. - - boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null; - boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null; - - if (subtypeHasAnno && supertypeHasAnno) { - // If both have primary annotations then just check the primary annotations - // as the bounds are the same. - return isPrimarySubtype(subtype, supertype); - } else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype)) { - // Two unannotated uses of wildcard types are the same type + } + + @Override + public Boolean visitIntersection_Intersection( + AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, Void p) { + Types types = checker.getTypeUtils(); + for (AnnotatedTypeMirror subBound : subtype.getBounds()) { + for (AnnotatedTypeMirror superBound : supertype.getBounds()) { + if (TypesUtils.isErasedSubtype( + subBound.getUnderlyingType(), superBound.getUnderlyingType(), types) + && !isSubtype(subBound, superBound, currentTop)) { + return false; + } + } + } return true; - } } - return isSubtype(subtype.getExtendsBound(), supertype, currentTop); - } + @Override + public Boolean visitIntersection_Null( + AnnotatedIntersectionType subtype, AnnotatedNullType supertype, Void p) { + // this can occur through capture conversion/comparing bounds + for (AnnotatedTypeMirror bound : subtype.getBounds()) { + if (isPrimarySubtype(bound, supertype)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitIntersection_Typevar( + AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitIntersection_Type(subtype, supertype); + } + + @Override + public Boolean visitIntersection_Wildcard( + AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, Void p) { + return visitIntersection_Type(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // Null as subtype + @Override + public Boolean visitNull_Array( + AnnotatedNullType subtype, AnnotatedArrayType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Declared( + AnnotatedNullType subtype, AnnotatedDeclaredType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Typevar( + AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitType_Typevar(subtype, supertype); + } + + @Override + public Boolean visitNull_Wildcard( + AnnotatedNullType subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + @Override + public Boolean visitNull_Null(AnnotatedNullType subtype, AnnotatedNullType supertype, Void p) { + // this can occur when comparing typevar lower bounds since they are usually null types + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Union( + AnnotatedNullType subtype, AnnotatedUnionType supertype, Void p) { + for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { + if (isSubtype(subtype, supertypeAltern, currentTop)) { + return true; + } + } + return false; + } + + @Override + public Boolean visitNull_Intersection( + AnnotatedNullType subtype, AnnotatedIntersectionType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitNull_Primitive( + AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // Primitive as subtype + @Override + public Boolean visitPrimitive_Declared( + AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, Void p) { + AnnotatedTypeFactory atypeFactory = subtype.atypeFactory; + Types types = atypeFactory.types; + AnnotatedPrimitiveType narrowedType = subtype; + if (TypesUtils.isBoxedPrimitive(supertype.getUnderlyingType())) { + TypeMirror unboxedSuper = types.unboxedType(supertype.getUnderlyingType()); + if (unboxedSuper.getKind() != subtype.getKind() + && TypesUtils.canBeNarrowingPrimitiveConversion(unboxedSuper, types)) { + narrowedType = atypeFactory.getNarrowedPrimitive(subtype, unboxedSuper); + } + } + AnnotatedTypeMirror boxedSubtype = atypeFactory.getBoxedType(narrowedType); + return isPrimarySubtype(boxedSubtype, supertype); + } + + @Override + public Boolean visitPrimitive_Primitive( + AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, Void p) { + return isPrimarySubtype(subtype, supertype); + } + + @Override + public Boolean visitPrimitive_Intersection( + AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, Void p) { + return visitType_Intersection(subtype, supertype); + } + + @Override + public Boolean visitPrimitive_Typevar( + AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, Void p) { + return AtmCombo.accept(subtype, supertype.getUpperBound(), null, this); + } + + @Override + public Boolean visitPrimitive_Wildcard( + AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, Void p) { + if (shouldIgnoreRawTypeArgs(supertype)) { + return true; + } + // this can occur when passing a primitive to a method on a raw type (see test + // checker/tests/nullness/RawAndPrimitive.java). This can also occur because we don't box + // primitives when we should and don't capture convert. + return isPrimarySubtype(subtype, supertype.getSuperBound()); + } + + // ------------------------------------------------------------------------ + // Union as subtype + @Override + public Boolean visitUnion_Declared( + AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, Void p) { + return visitUnion_Type(subtype, supertype); + } + + @Override + public Boolean visitUnion_Intersection( + AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, Void p) { + // For example: + // void method(T param) {} + // ... + // catch (Exception1 | Exception2 union) { // Assuming Exception1 and Exception2 implement + // Cloneable + // method(union); + // This case happens when checking that the inferred type argument is a subtype of the + // declared type argument of method. + // See org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments + return visitUnion_Type(subtype, supertype); + } + + @Override + public Boolean visitUnion_Union( + AnnotatedUnionType subtype, AnnotatedUnionType supertype, Void p) { + // For example: + // void method(T param) {} + // ... + // catch (Exception1 | Exception2 union) { + // method(union); + // This case happens when checking the arguments to method after type variable substitution + return visitUnion_Type(subtype, supertype); + } + + @Override + public Boolean visitUnion_Wildcard( + AnnotatedUnionType subtype, AnnotatedWildcardType supertype, Void p) { + // For example: + // } catch (RuntimeException | IOException e) { + // ArrayList lWildcard = new ArrayList<>(); + // lWildcard.add(e); + + return visitType_Wildcard(subtype, supertype); + } + + @Override + public Boolean visitUnion_Typevar( + AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, Void p) { + // For example: + // } catch (RuntimeException | IOException e) { + // ArrayList lWildcard = new ArrayList<>(); + // lWildcard.add(e); + + return visitType_Typevar(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // typevar as subtype + @Override + public Boolean visitTypevar_Declared( + AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, Void p) { + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Intersection( + AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, Void p) { + // this can happen when checking type param bounds + return visitType_Intersection(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Primitive( + AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, Void p) { + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Array( + AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, Void p) { + // This happens when the type variable is a captured wildcard. + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Typevar( + AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, Void p) { + TypeMirror subTM = subtype.getUnderlyingType(); + TypeMirror superTM = supertype.getUnderlyingType(); + if (AnnotatedTypes.haveSameDeclaration(checker.getTypeUtils(), subtype, supertype)) { + // The underlying types of subtype and supertype are uses of the same type parameter, + // but they may have different primary annotations. + AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); + boolean subtypeHasAnno = subtypeAnno != null; + AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); + boolean supertypeHasAnno = supertypeAnno != null; + + if (subtypeHasAnno && supertypeHasAnno) { + // If both have primary annotations then just check the primary annotations + // as the bounds are the same. + return isPrimarySubtype(subtype, supertype); + } else if (!subtypeHasAnno && !supertypeHasAnno) { + // Two unannotated uses of the same type parameter need to compare + // both upper and lower bounds. + + // Upper bound of the subtype needs to be below the upper bound of the supertype. + if (!qualHierarchy.isSubtypeShallow( + subtype.getEffectiveAnnotationInHierarchy(currentTop), + subTM, + supertype.getEffectiveAnnotationInHierarchy(currentTop), + superTM)) { + return false; + } + + // Lower bound of the subtype needs to be below the lower bound of the supertype. + // TODO: Think through this and add better test coverage. + AnnotationMirrorSet subLBs = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, subtype); + AnnotationMirror subLB = + qualHierarchy.findAnnotationInHierarchy(subLBs, currentTop); + AnnotationMirrorSet superLBs = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); + AnnotationMirror superLB = + qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); + return qualHierarchy.isSubtypeShallow(subLB, subTM, superLB, superTM); + } else if (subtypeHasAnno && !supertypeHasAnno) { + // This is the case "@A T <: T" where T is a type variable. + // TODO: should this also test the upper bounds? + AnnotationMirrorSet superLBs = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype); + AnnotationMirror superLB = + qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop); + return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, superLB, superTM); + } else if (!subtypeHasAnno && supertypeHasAnno) { + // This is the case "T <: @A T" where T is a type variable. + // TODO: should this also test the lower bounds? + return qualHierarchy.isSubtypeShallow( + subtype.getEffectiveAnnotationInHierarchy(currentTop), + subTM, + supertypeAnno, + superTM); + } else { + throw new BugInCF("Unreachable"); + } + } + + if (AnnotatedTypes.areCorrespondingTypeVariables( + checker.getProcessingEnvironment().getElementUtils(), subtype, supertype)) { + if (areEqualInHierarchy(subtype, supertype)) { + return true; + } + } + + if (TypesUtils.isCapturedTypeVariable(subTM) + && TypesUtils.isCapturedTypeVariable(superTM)) { + // This case happens when the captured type variables should be the same type, but + // aren't because type argument inference isn't implemented correctly. + if (isContainedWithinBounds( + subtype, supertype.getLowerBound(), supertype.getUpperBound(), false)) { + return true; + } + } + + if (supertype.getLowerBound().getKind() != TypeKind.NULL) { + return visit(subtype, supertype.getLowerBound(), p); + } + // check that the upper bound of the subtype is below the lower bound of the supertype + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Null( + AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Void p) { + return visitTypevar_Type(subtype, supertype); + } + + @Override + public Boolean visitTypevar_Wildcard( + AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, Void p) { + return visitType_Wildcard(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // wildcard as subtype + + @Override + public Boolean visitWildcard_Array( + AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Declared( + AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, Void p) { + if (subtype.isTypeArgOfRawType()) { + if (ignoreRawTypes) { + return true; + } else if (supertype.getTypeArguments().isEmpty()) { + // visitWildcard_Type doesn't check type arguments from raw types, because the + // underlying Java types may not be in the correct relationship. But, if the + // declared type does not have type arguments, then checking primary annotations is + // sufficient. + // For example, if the wildcard is ? extends @Nullable Object and the supertype is + // @Nullable String, then it is safe to return true. However if the supertype is + // @NullableList<@NonNull String> then it's not possible to decide if it is a + // subtype of the wildcard. + return isSubtypeShallowEffective(subtype, supertype, currentTop); + } + } + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Intersection( + AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Primitive( + AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, Void p) { + if (subtype.isTypeArgOfRawType()) { + return isSubtypeShallowEffective(subtype, supertype, currentTop); + } + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Typevar( + AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + @Override + public Boolean visitWildcard_Wildcard( + AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, Void p) { + return visitWildcard_Type(subtype, supertype); + } + + // ------------------------------------------------------------------------ + // These "visit" methods are utility methods that aren't part of the visit interface + // but that handle cases that more than one visit method shares in common. + + /** + * An intersection is a supertype if all of its bounds are a supertype of subtype. + * + * @param subtype the possible subtype + * @param supertype the possible supertype + * @return true {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitType_Intersection( + AnnotatedTypeMirror subtype, AnnotatedIntersectionType supertype) { + if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) { + return true; + } + boolean result = true; + for (AnnotatedTypeMirror bound : supertype.getBounds()) { + // Only call isSubtype if the Java type is actually a subtype; otherwise, + // only check primary qualifiers. + if (TypesUtils.isErasedSubtype( + subtype.getUnderlyingType(), + bound.getUnderlyingType(), + subtype.atypeFactory.types) + && !isSubtype(subtype, bound, currentTop)) { + result = false; + break; + } + } + isSubtypeVisitHistory.put(subtype, supertype, currentTop, result); + return result; + } + + /** + * An intersection is a subtype if one of its bounds is a subtype of {@code supertype}. + * + * @param subtype an intersection type + * @param supertype an annotated type + * @return whether {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitIntersection_Type( + AnnotatedIntersectionType subtype, AnnotatedTypeMirror supertype) { + Types types = checker.getTypeUtils(); + // The primary annotations of the bounds should already be the same as the annotations on + // the intersection type. + for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) { + if (TypesUtils.isErasedSubtype( + subtypeBound.getUnderlyingType(), supertype.getUnderlyingType(), types) + && isSubtype(subtypeBound, supertype, currentTop)) { + return true; + } + } + return false; + } + + /** + * A type variable is a supertype if its lower bound is above subtype. + * + * @param subtype a type that might be a subtype + * @param supertype a type that might be a supertype + * @return true if {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitType_Typevar( + AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype) { + return isSubtypeCaching(subtype, supertype.getLowerBound()); + } + + /** + * A type variable is a subtype if its upper bound is below the supertype. + * + * @param subtype a type that might be a subtype + * @param supertype a type that might be a supertype + * @return true if {@code subtype} is a subtype of {@code supertype} + */ + protected boolean visitTypevar_Type( + AnnotatedTypeVariable subtype, AnnotatedTypeMirror supertype) { + AnnotatedTypeMirror subtypeUpperBound = subtype.getUpperBound(); + if (TypesUtils.isBoxedPrimitive(subtypeUpperBound.getUnderlyingType()) + && supertype instanceof AnnotatedPrimitiveType) { + subtypeUpperBound = + subtype.atypeFactory.getUnboxedType((AnnotatedDeclaredType) subtypeUpperBound); + } + if (supertype.getKind() == TypeKind.DECLARED + && TypesUtils.getTypeElement(supertype.getUnderlyingType()).getKind() + == ElementKind.INTERFACE) { + // The supertype is an interface. + subtypeUpperBound = getNonWildcardOrTypeVarUpperBound(subtypeUpperBound); + if (subtypeUpperBound.getKind() == TypeKind.INTERSECTION) { + // Only compare the primary annotations. + Types types = checker.getTypeUtils(); + for (AnnotatedTypeMirror bound : + ((AnnotatedIntersectionType) subtypeUpperBound).getBounds()) { + // Make sure the upper bound is no wildcard or type variable. + bound = getNonWildcardOrTypeVarUpperBound(bound); + if (TypesUtils.isErasedSubtype( + bound.getUnderlyingType(), supertype.getUnderlyingType(), types) + && isPrimarySubtype(bound, supertype)) { + return true; + } + } + return false; + } + } + try { + return isSubtypeCaching(subtypeUpperBound, supertype); + } catch (BugInCF e) { + if (TypesUtils.isCapturedTypeVariable(subtype.underlyingType)) { + // The upper bound of captured type variable may be computed incorrectly by javac. + // javac computes the upper bound as a declared type, when it should be an + // intersection type. + // (This is a bug in the GLB algorithm; see + // https://bugs.openjdk.org/browse/JDK-8039222) + // In this case, the upperbound is not a subtype of `supertype` and the Checker + // Framework crashes. So catch that crash and just return false. + // TODO: catch the problem more locally. + return false; + } + throw e; + } + } + + /** + * If {@code type} is a type variable or wildcard recur on its upper bound until an upper bound + * is found that is neither a type variable nor a wildcard. + * + * @param type the type + * @return if {@code type} is a type variable or wildcard, recur on its upper bound until an + * upper bound is found that is neither a type variable nor a wildcard. Otherwise, return + * {@code type} itself. + */ + private AnnotatedTypeMirror getNonWildcardOrTypeVarUpperBound(AnnotatedTypeMirror type) { + while (type.getKind() == TypeKind.TYPEVAR || type.getKind() == TypeKind.WILDCARD) { + if (type.getKind() == TypeKind.TYPEVAR) { + type = ((AnnotatedTypeVariable) type).getUpperBound(); + } + if (type.getKind() == TypeKind.WILDCARD) { + type = ((AnnotatedWildcardType) type).getExtendsBound(); + } + } + return type; + } + + /** + * A union type is a subtype if ALL of its alternatives are subtypes of supertype. + * + * @param subtype the potential subtype to check + * @param supertype the supertype to check + * @return whether all the alternatives of subtype are subtypes of supertype + */ + protected boolean visitUnion_Type(AnnotatedUnionType subtype, AnnotatedTypeMirror supertype) { + return areAllSubtypes(subtype.getAlternatives(), supertype); + } + + /** + * Check a wildcard type's relation against a subtype. + * + * @param subtype the potential subtype to check + * @param supertype the wildcard supertype to check + * @return whether the subtype is a subtype of the supertype's super bound + */ + protected boolean visitType_Wildcard( + AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype) { + if (supertype.isTypeArgOfRawType()) { + return ignoreRawTypes; + } + return isSubtype(subtype, supertype.getSuperBound(), currentTop); + } + + /** + * Check a wildcard type's relation against a supertype. + * + * @param subtype the potential wildcard subtype to check + * @param supertype the supertype to check + * @return whether the subtype's extends bound is a subtype of the supertype + */ + protected boolean visitWildcard_Type( + AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype) { + if (subtype.isTypeArgOfRawType()) { + return ignoreRawTypes; + } + + if (supertype.getKind() == TypeKind.WILDCARD) { + // This can happen at a method invocation where a type variable in the method + // declaration is substituted with a wildcard. + // For example: + // void method(Gen t) {} + // Gen x; + // method(x); + // visitWildcard_Type is called when checking the method call `method(x)`, + // and also when checking lambdas. + + boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null; + boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null; + + if (subtypeHasAnno && supertypeHasAnno) { + // If both have primary annotations then just check the primary annotations + // as the bounds are the same. + return isPrimarySubtype(subtype, supertype); + } else if (!subtypeHasAnno + && !supertypeHasAnno + && areEqualInHierarchy(subtype, supertype)) { + // Two unannotated uses of wildcard types are the same type + return true; + } + } + + return isSubtype(subtype.getExtendsBound(), supertype, currentTop); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java index cf3e34d23a6..e0f090fdaf6 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java @@ -4,12 +4,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Symbol; -import java.util.List; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; @@ -27,6 +22,14 @@ import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.IPair; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; + /** * Utility methods for adding the annotations that are stored in an Element to the type that * represents that element (or a use of that Element). @@ -44,180 +47,183 @@ */ public final class ElementAnnotationApplier { - /** Do not instantiate. */ - private ElementAnnotationApplier() { - throw new AssertionError("Class ElementAnnotationApplier cannot be instantiated."); - } - - /** - * Add all of the relevant annotations stored in Element to type. This includes both top-level - * primary annotations and nested annotations. For the most part the TypeAnnotationPosition of the - * element annotations are used to locate the annotation in the right AnnotatedTypeMirror location - * though the individual applier classes may have special rules (such as those for upper and lower - * bounds and intersections). - * - *

          Note: Element annotations come from two sources. - * - *

            - *
          1. Annotations found on elements may represent those in source code or bytecode; these are - * added to the element by the compiler. - *
          2. The annotations may also represent those that were inferred or defaulted by the Checker - * Framework after a previous call to this method. The Checker Framework will store any - * annotations on declarations back into the elements that represent them (see {@link - * org.checkerframework.framework.type.TypesIntoElements}). Subsequent, calls to apply will - * encounter these annotations on the provided element. - *
          - * - * Note: This is not the ONLY place that annotations are explicitly added to types. See {@link - * org.checkerframework.framework.type.TypeFromTree}. - * - * @param type the type to which we wish to apply the element's annotations - * @param element an element that possibly contains annotations - * @param typeFactory the typeFactory used to create the given type - */ - public static void apply( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) { - try { - applyInternal(type, element, typeFactory); - } catch (UnexpectedAnnotationLocationException e) { - reportInvalidLocation(element, typeFactory); + /** Do not instantiate. */ + private ElementAnnotationApplier() { + throw new AssertionError("Class ElementAnnotationApplier cannot be instantiated."); } - // Also copy annotations from type parameters to their uses. - new TypeVarAnnotator().visit(type, typeFactory); - } - - /** Issues an "invalid.annotation.location.bytecode warning. */ - private static void reportInvalidLocation(Element element, AnnotatedTypeFactory typeFactory) { - Element report = element; - if (element.getEnclosingElement().getKind() == ElementKind.METHOD) { - report = element.getEnclosingElement(); - } - // There's a bug in Java 8 compiler that creates bad bytecode such that an - // annotation on a lambda parameter is applied to a method parameter. (This bug has - // been fixed in Java 9.) If this happens, then the location could refer to a - // location, such as a type argument, that doesn't exist. Since Java 8 bytecode - // might be on the classpath, catch this exception and ignore the type. - // TODO: Issue an error if this annotation is from Java 9+ bytecode. - if (!typeFactory.checker.hasOption("ignoreInvalidAnnotationLocations")) { - typeFactory.checker.reportWarning( - element, "invalid.annotation.location.bytecode", ElementUtils.getQualifiedName(report)); - } - } - - /** Same as apply except that annotations aren't copied from type parameter declarations. */ - private static void applyInternal( - final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) - throws UnexpectedAnnotationLocationException { - if (element == null) { - throw new BugInCF("ElementAnnotationUtil.apply: element cannot be null"); - } else if (TypeVarUseApplier.accepts(type, element)) { - TypeVarUseApplier.apply(type, element, typeFactory); - } else if (VariableApplier.accepts(type, element)) { - if (!ElementUtils.isLocalVariable(element)) { - // For local variables we have the source code, - // so there is no need to look at the Element. - // This is needed to avoid a bug in the JDK: - // https://github.com/eisop/checker-framework/issues/14 - VariableApplier.apply(type, element); - } - } else if (MethodApplier.accepts(type, element)) { - MethodApplier.apply(type, element, typeFactory); - } else if (TypeDeclarationApplier.accepts(type, element)) { - TypeDeclarationApplier.apply(type, element, typeFactory); - } else if (ClassTypeParamApplier.accepts(type, element)) { - ClassTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); - } else if (MethodTypeParamApplier.accepts(type, element)) { - MethodTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); - } else if (ParamApplier.accepts(type, element)) { - ParamApplier.apply(type, (VariableElement) element, typeFactory); - } else if (isCaptureConvertedTypeVar(element)) { - // Types resulting from capture conversion cannot have explicit annotations - } else if (ElementUtils.isBindingVariable(element)) { - // TODO: verify that there are no type use annotations that would need decoding - } else { - throw new BugInCF( - "ElementAnnotationUtil.apply: illegal argument: " - + element - + " [" - + element.getKind() - + "]" - + " with type " - + type); + + /** + * Add all of the relevant annotations stored in Element to type. This includes both top-level + * primary annotations and nested annotations. For the most part the TypeAnnotationPosition of + * the element annotations are used to locate the annotation in the right AnnotatedTypeMirror + * location though the individual applier classes may have special rules (such as those for + * upper and lower bounds and intersections). + * + *

          Note: Element annotations come from two sources. + * + *

            + *
          1. Annotations found on elements may represent those in source code or bytecode; these are + * added to the element by the compiler. + *
          2. The annotations may also represent those that were inferred or defaulted by the Checker + * Framework after a previous call to this method. The Checker Framework will store any + * annotations on declarations back into the elements that represent them (see {@link + * org.checkerframework.framework.type.TypesIntoElements}). Subsequent, calls to apply + * will encounter these annotations on the provided element. + *
          + * + * Note: This is not the ONLY place that annotations are explicitly added to types. See {@link + * org.checkerframework.framework.type.TypeFromTree}. + * + * @param type the type to which we wish to apply the element's annotations + * @param element an element that possibly contains annotations + * @param typeFactory the typeFactory used to create the given type + */ + public static void apply( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) { + try { + applyInternal(type, element, typeFactory); + } catch (UnexpectedAnnotationLocationException e) { + reportInvalidLocation(element, typeFactory); + } + // Also copy annotations from type parameters to their uses. + new TypeVarAnnotator().visit(type, typeFactory); } - } - - /** - * Annotate the list of supertypes using the annotations on the TypeElement representing a class - * or interface. - * - * @param supertypes types representing supertype declarations of TypeElement - * @param subtypeElement an element representing the declaration of the class which is a subtype - * of supertypes - */ - public static void annotateSupers( - List supertypes, TypeElement subtypeElement) { - try { - SuperTypeApplier.annotateSupers(supertypes, subtypeElement); - } catch (UnexpectedAnnotationLocationException e) { - reportInvalidLocation(subtypeElement, supertypes.get(0).atypeFactory); + + /** Issues an "invalid.annotation.location.bytecode warning. */ + private static void reportInvalidLocation(Element element, AnnotatedTypeFactory typeFactory) { + Element report = element; + if (element.getEnclosingElement().getKind() == ElementKind.METHOD) { + report = element.getEnclosingElement(); + } + // There's a bug in Java 8 compiler that creates bad bytecode such that an + // annotation on a lambda parameter is applied to a method parameter. (This bug has + // been fixed in Java 9.) If this happens, then the location could refer to a + // location, such as a type argument, that doesn't exist. Since Java 8 bytecode + // might be on the classpath, catch this exception and ignore the type. + // TODO: Issue an error if this annotation is from Java 9+ bytecode. + if (!typeFactory.checker.hasOption("ignoreInvalidAnnotationLocations")) { + typeFactory.checker.reportWarning( + element, + "invalid.annotation.location.bytecode", + ElementUtils.getQualifiedName(report)); + } } - } - - /** - * Helper method to get the lambda tree for ParamApplier. Ideally, this method would be located in - * ElementAnnotationUtil but since AnnotatedTypeFactory.declarationFromElement is protected, it - * has been placed here. - * - * @param varEle the element that may represent a lambda's parameter - * @return a LambdaExpressionTree if the varEle represents a parameter in a lambda expression, - * otherwise null - */ - public static @Nullable IPair getParamAndLambdaTree( - VariableElement varEle, AnnotatedTypeFactory typeFactory) { - VariableTree paramDecl = (VariableTree) typeFactory.declarationFromElement(varEle); - - if (paramDecl != null) { - Tree parentTree = typeFactory.getPath(paramDecl).getParentPath().getLeaf(); - if (parentTree != null && parentTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - return IPair.of(paramDecl, (LambdaExpressionTree) parentTree); - } + + /** Same as apply except that annotations aren't copied from type parameter declarations. */ + private static void applyInternal( + final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory typeFactory) + throws UnexpectedAnnotationLocationException { + if (element == null) { + throw new BugInCF("ElementAnnotationUtil.apply: element cannot be null"); + } else if (TypeVarUseApplier.accepts(type, element)) { + TypeVarUseApplier.apply(type, element, typeFactory); + } else if (VariableApplier.accepts(type, element)) { + if (!ElementUtils.isLocalVariable(element)) { + // For local variables we have the source code, + // so there is no need to look at the Element. + // This is needed to avoid a bug in the JDK: + // https://github.com/eisop/checker-framework/issues/14 + VariableApplier.apply(type, element); + } + } else if (MethodApplier.accepts(type, element)) { + MethodApplier.apply(type, element, typeFactory); + } else if (TypeDeclarationApplier.accepts(type, element)) { + TypeDeclarationApplier.apply(type, element, typeFactory); + } else if (ClassTypeParamApplier.accepts(type, element)) { + ClassTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); + } else if (MethodTypeParamApplier.accepts(type, element)) { + MethodTypeParamApplier.apply((AnnotatedTypeVariable) type, element, typeFactory); + } else if (ParamApplier.accepts(type, element)) { + ParamApplier.apply(type, (VariableElement) element, typeFactory); + } else if (isCaptureConvertedTypeVar(element)) { + // Types resulting from capture conversion cannot have explicit annotations + } else if (ElementUtils.isBindingVariable(element)) { + // TODO: verify that there are no type use annotations that would need decoding + } else { + throw new BugInCF( + "ElementAnnotationUtil.apply: illegal argument: " + + element + + " [" + + element.getKind() + + "]" + + " with type " + + type); + } } - return null; - } - - /** - * Was the type passed in generated by capture conversion. - * - * @param element the element which type represents - * @return true if type was generated via capture conversion false otherwise - */ - private static boolean isCaptureConvertedTypeVar(Element element) { - Element enclosure = element.getEnclosingElement(); - return (((Symbol) enclosure).kind == com.sun.tools.javac.code.Kinds.Kind.NIL); - } - - /** - * Annotates uses of type variables with annotation written explicitly on the type parameter - * declaration and/or its upper bound. - */ - private static class TypeVarAnnotator extends AnnotatedTypeScanner { - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeFactory factory) { - TypeParameterElement tpelt = (TypeParameterElement) type.getUnderlyingType().asElement(); - - if (type.getAnnotations().isEmpty() - && type.getUpperBound().getAnnotations().isEmpty() - && tpelt.getEnclosingElement().getKind() != ElementKind.TYPE_PARAMETER) { + /** + * Annotate the list of supertypes using the annotations on the TypeElement representing a class + * or interface. + * + * @param supertypes types representing supertype declarations of TypeElement + * @param subtypeElement an element representing the declaration of the class which is a subtype + * of supertypes + */ + public static void annotateSupers( + List supertypes, TypeElement subtypeElement) { try { - ElementAnnotationApplier.applyInternal(type, tpelt, factory); + SuperTypeApplier.annotateSupers(supertypes, subtypeElement); } catch (UnexpectedAnnotationLocationException e) { - // The above is the second call to applyInternal on this type and element, so - // any errors were already reported by the first call. (See the only use of this - // class.) + reportInvalidLocation(subtypeElement, supertypes.get(0).atypeFactory); + } + } + + /** + * Helper method to get the lambda tree for ParamApplier. Ideally, this method would be located + * in ElementAnnotationUtil but since AnnotatedTypeFactory.declarationFromElement is protected, + * it has been placed here. + * + * @param varEle the element that may represent a lambda's parameter + * @return a LambdaExpressionTree if the varEle represents a parameter in a lambda expression, + * otherwise null + */ + public static @Nullable IPair getParamAndLambdaTree( + VariableElement varEle, AnnotatedTypeFactory typeFactory) { + VariableTree paramDecl = (VariableTree) typeFactory.declarationFromElement(varEle); + + if (paramDecl != null) { + Tree parentTree = typeFactory.getPath(paramDecl).getParentPath().getLeaf(); + if (parentTree != null && parentTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + return IPair.of(paramDecl, (LambdaExpressionTree) parentTree); + } + } + + return null; + } + + /** + * Was the type passed in generated by capture conversion. + * + * @param element the element which type represents + * @return true if type was generated via capture conversion false otherwise + */ + private static boolean isCaptureConvertedTypeVar(Element element) { + Element enclosure = element.getEnclosingElement(); + return (((Symbol) enclosure).kind == com.sun.tools.javac.code.Kinds.Kind.NIL); + } + + /** + * Annotates uses of type variables with annotation written explicitly on the type parameter + * declaration and/or its upper bound. + */ + private static class TypeVarAnnotator extends AnnotatedTypeScanner { + @Override + public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeFactory factory) { + TypeParameterElement tpelt = + (TypeParameterElement) type.getUnderlyingType().asElement(); + + if (type.getAnnotations().isEmpty() + && type.getUpperBound().getAnnotations().isEmpty() + && tpelt.getEnclosingElement().getKind() != ElementKind.TYPE_PARAMETER) { + try { + ElementAnnotationApplier.applyInternal(type, tpelt, factory); + } catch (UnexpectedAnnotationLocationException e) { + // The above is the second call to applyInternal on this type and element, so + // any errors were already reported by the first call. (See the only use of this + // class.) + } + } + return super.visitTypeVariable(type, factory); } - } - return super.visitTypeVariable(type, factory); } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java index 03b733167f5..15cbc22dc99 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/ElementQualifierHierarchy.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.type; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -22,6 +15,15 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** * A {@link QualifierHierarchy} where qualifiers may be represented by annotations with elements. * @@ -36,229 +38,230 @@ @AnnotatedFor("nullness") public abstract class ElementQualifierHierarchy extends QualifierHierarchy { - /** {@link org.checkerframework.javacutil.ElementUtils}. */ - private final Elements elements; + /** {@link org.checkerframework.javacutil.ElementUtils}. */ + private final Elements elements; - /** {@link QualifierKindHierarchy}. */ - protected final QualifierKindHierarchy qualifierKindHierarchy; + /** {@link QualifierKindHierarchy}. */ + protected final QualifierKindHierarchy qualifierKindHierarchy; - // The following fields duplicate information in qualifierKindHierarchy, but using - // AnnotationMirrors instead of QualifierKinds. + // The following fields duplicate information in qualifierKindHierarchy, but using + // AnnotationMirrors instead of QualifierKinds. - /** A mapping from top QualifierKinds to their corresponding AnnotationMirror. */ - protected final Map topsMap; + /** A mapping from top QualifierKinds to their corresponding AnnotationMirror. */ + protected final Map topsMap; - /** The set of top annotation mirrors. */ - protected final AnnotationMirrorSet tops; + /** The set of top annotation mirrors. */ + protected final AnnotationMirrorSet tops; - /** A mapping from bottom QualifierKinds to their corresponding AnnotationMirror. */ - protected final Map bottomsMap; + /** A mapping from bottom QualifierKinds to their corresponding AnnotationMirror. */ + protected final Map bottomsMap; - /** The set of bottom annotation mirrors. */ - protected final AnnotationMirrorSet bottoms; + /** The set of bottom annotation mirrors. */ + protected final AnnotationMirrorSet bottoms; - /** - * A mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations do not - * have elements. - */ - protected final Map kindToElementlessQualifier; + /** + * A mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations do not + * have elements. + */ + protected final Map kindToElementlessQualifier; - /** - * Creates a ElementQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - * @param atypeFactory the associated type factory - */ - protected ElementQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** + * Creates a ElementQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + * @param atypeFactory the associated type factory + */ + protected ElementQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); - this.elements = elements; - this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); + this.elements = elements; + this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); - this.topsMap = Collections.unmodifiableMap(createTopsMap()); - this.tops = AnnotationMirrorSet.unmodifiableSet(topsMap.values()); + this.topsMap = Collections.unmodifiableMap(createTopsMap()); + this.tops = AnnotationMirrorSet.unmodifiableSet(topsMap.values()); - this.bottomsMap = Collections.unmodifiableMap(createBottomsMap()); - this.bottoms = AnnotationMirrorSet.unmodifiableSet(bottomsMap.values()); + this.bottomsMap = Collections.unmodifiableMap(createBottomsMap()); + this.bottoms = AnnotationMirrorSet.unmodifiableSet(bottomsMap.values()); - this.kindToElementlessQualifier = createElementlessQualifierMap(); - } + this.kindToElementlessQualifier = createElementlessQualifierMap(); + } - @Override - public boolean isValid() { - for (AnnotationMirror top : tops) { - // This throws an error if poly is a qualifier that has an element. - getPolymorphicAnnotation(top); + @Override + public boolean isValid() { + for (AnnotationMirror top : tops) { + // This throws an error if poly is a qualifier that has an element. + getPolymorphicAnnotation(top); + } + return true; } - return true; - } - - /** - * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of - * QualifierKindHierarchy.) - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @return the newly created qualifier kind hierarchy - */ - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization ElementQualifierHierarchy this, - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses); - } - - /** - * Creates a mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations - * do not have elements. - * - * @return the mapping - */ - @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) - protected Map createElementlessQualifierMap( - @UnderInitialization ElementQualifierHierarchy this) { - Map quals = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { - if (!kind.hasElements()) { - quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); - } + + /** + * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of + * QualifierKindHierarchy.) + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @return the newly created qualifier kind hierarchy + */ + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization ElementQualifierHierarchy this, + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses); } - return Collections.unmodifiableMap(quals); - } - - /** - * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is top and - * the AnnotationMirror is top in their respective hierarchies. - * - *

          This implementation works if the top annotation has no elements, or if it has elements, - * provides a default, and that default is the top. Otherwise, subclasses must override this. - * - * @return a mapping from top QualifierKind to top AnnotationMirror - */ - @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) - protected Map createTopsMap( - @UnderInitialization ElementQualifierHierarchy this) { - Map topsMap = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.getTops()) { - topsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + + /** + * Creates a mapping from QualifierKind to AnnotationMirror for all qualifiers whose annotations + * do not have elements. + * + * @return the mapping + */ + @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) + protected Map createElementlessQualifierMap( + @UnderInitialization ElementQualifierHierarchy this) { + Map quals = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { + if (!kind.hasElements()) { + quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + } + } + return Collections.unmodifiableMap(quals); } - return topsMap; - } - - /** - * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is bottom and - * the AnnotationMirror is bottom in their respective hierarchies. - * - *

          This implementation works if the bottom annotation has no elements, or if it has elements, - * provides a default, and that default is the bottom. Otherwise, subclasses must override this. - * - * @return a mapping from bottom QualifierKind to bottom AnnotationMirror - */ - @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) - protected Map createBottomsMap( - @UnderInitialization ElementQualifierHierarchy this) { - Map bottomsMap = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.getBottoms()) { - bottomsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + + /** + * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is top and + * the AnnotationMirror is top in their respective hierarchies. + * + *

          This implementation works if the top annotation has no elements, or if it has elements, + * provides a default, and that default is the top. Otherwise, subclasses must override this. + * + * @return a mapping from top QualifierKind to top AnnotationMirror + */ + @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) + protected Map createTopsMap( + @UnderInitialization ElementQualifierHierarchy this) { + Map topsMap = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.getTops()) { + topsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + } + return topsMap; + } + + /** + * Creates a mapping from QualifierKind to AnnotationMirror, where the QualifierKind is bottom + * and the AnnotationMirror is bottom in their respective hierarchies. + * + *

          This implementation works if the bottom annotation has no elements, or if it has elements, + * provides a default, and that default is the bottom. Otherwise, subclasses must override this. + * + * @return a mapping from bottom QualifierKind to bottom AnnotationMirror + */ + @RequiresNonNull({"this.qualifierKindHierarchy", "this.elements"}) + protected Map createBottomsMap( + @UnderInitialization ElementQualifierHierarchy this) { + Map bottomsMap = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.getBottoms()) { + bottomsMap.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + } + return bottomsMap; + } + + /** + * Returns the qualifier kind for the given annotation. + * + * @param anno an annotation mirror that is in this hierarchy + * @return the qualifier kind for the given annotation + */ + protected QualifierKind getQualifierKind(AnnotationMirror anno) { + String name = AnnotationUtils.annotationName(anno); + QualifierKind result = getQualifierKind(name); + if (result == null) { + throw new BugInCF("No qualifier kind for " + anno); + } + return result; + } + + /** + * Returns the qualifier kind for the annotation with the canonical name {@code name}. + * + * @param name fully qualified annotation name + * @return the qualifier kind for the annotation named {@code name} + */ + protected QualifierKind getQualifierKind(@CanonicalName String name) { + QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); + if (kind == null) { + throw new BugInCF("QualifierKind %s not in hierarchy", name); + } + return kind; + } + + @Override + public AnnotationMirrorSet getTopAnnotations() { + return tops; } - return bottomsMap; - } - - /** - * Returns the qualifier kind for the given annotation. - * - * @param anno an annotation mirror that is in this hierarchy - * @return the qualifier kind for the given annotation - */ - protected QualifierKind getQualifierKind(AnnotationMirror anno) { - String name = AnnotationUtils.annotationName(anno); - QualifierKind result = getQualifierKind(name); - if (result == null) { - throw new BugInCF("No qualifier kind for " + anno); + + @Override + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + @SuppressWarnings( + "nullness:assignment.type.incompatible") // All tops are a key for topsMap. + @NonNull AnnotationMirror result = topsMap.get(kind.getTop()); + return result; } - return result; - } - - /** - * Returns the qualifier kind for the annotation with the canonical name {@code name}. - * - * @param name fully qualified annotation name - * @return the qualifier kind for the annotation named {@code name} - */ - protected QualifierKind getQualifierKind(@CanonicalName String name) { - QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); - if (kind == null) { - throw new BugInCF("QualifierKind %s not in hierarchy", name); + + @Override + public AnnotationMirrorSet getBottomAnnotations() { + return bottoms; } - return kind; - } - - @Override - public AnnotationMirrorSet getTopAnnotations() { - return tops; - } - - @Override - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - @SuppressWarnings("nullness:assignment.type.incompatible") // All tops are a key for topsMap. - @NonNull AnnotationMirror result = topsMap.get(kind.getTop()); - return result; - } - - @Override - public AnnotationMirrorSet getBottomAnnotations() { - return bottoms; - } - - @Override - public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { - QualifierKind polyKind = getQualifierKind(start).getPolymorphic(); - if (polyKind == null) { - return null; + + @Override + public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { + QualifierKind polyKind = getQualifierKind(start).getPolymorphic(); + if (polyKind == null) { + return null; + } + AnnotationMirror poly = kindToElementlessQualifier.get(polyKind); + if (poly == null) { + throw new TypeSystemError( + "Poly %s has an element. Override" + + " ElementQualifierHierarchy#getPolymorphicAnnotation.", + polyKind); + } + return poly; } - AnnotationMirror poly = kindToElementlessQualifier.get(polyKind); - if (poly == null) { - throw new TypeSystemError( - "Poly %s has an element. Override" - + " ElementQualifierHierarchy#getPolymorphicAnnotation.", - polyKind); + + @Override + public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { + return getQualifierKind(qualifier).isPoly(); } - return poly; - } - - @Override - public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { - return getQualifierKind(qualifier).isPoly(); - } - - @Override - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - @SuppressWarnings( - "nullness:assignment.type.incompatible") // All bottoms are keys for bottomsMap. - @NonNull AnnotationMirror result = bottomsMap.get(kind.getBottom()); - return result; - } - - @Override - public @Nullable AnnotationMirror findAnnotationInSameHierarchy( - Collection annos, AnnotationMirror annotationMirror) { - QualifierKind kind = getQualifierKind(annotationMirror); - for (AnnotationMirror candidate : annos) { - QualifierKind candidateKind = getQualifierKind(candidate); - if (candidateKind.isInSameHierarchyAs(kind)) { - return candidate; - } + + @Override + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + @SuppressWarnings( + "nullness:assignment.type.incompatible") // All bottoms are keys for bottomsMap. + @NonNull AnnotationMirror result = bottomsMap.get(kind.getBottom()); + return result; + } + + @Override + public @Nullable AnnotationMirror findAnnotationInSameHierarchy( + Collection annos, AnnotationMirror annotationMirror) { + QualifierKind kind = getQualifierKind(annotationMirror); + for (AnnotationMirror candidate : annos) { + QualifierKind candidateKind = getQualifierKind(candidate); + if (candidateKind.isInSameHierarchyAs(kind)) { + return candidate; + } + } + return null; + } + + @Override + public @Nullable AnnotationMirror findAnnotationInHierarchy( + Collection annos, AnnotationMirror top) { + return findAnnotationInSameHierarchy(annos, top); } - return null; - } - - @Override - public @Nullable AnnotationMirror findAnnotationInHierarchy( - Collection annos, AnnotationMirror top) { - return findAnnotationInSameHierarchy(annos, top); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java index d1e5e1fd4ea..f957136cccf 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java @@ -20,59 +20,60 @@ */ public class EqualityAtmComparer extends EquivalentAtmComboScanner { - /** - * Return true if {@code type1} and {@code type2} have equivalent sets of annotations. - * - * @param type1 a type - * @param type2 a type - * @return true if {@code type1} and {@code type2} have equivalent sets of annotations - */ - protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - return AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations()); - } - - /** - * Return true if the twe types are the same. - * - * @param type1 the first type to compare - * @param type2 the second type to compare - * @return true if the twe types are the same - */ - @EqualsMethod // to make Interning Checker permit the == comparison - protected boolean compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (type1 == type2) { - return true; - } - if (type1 == null || type2 == null) { - return false; + /** + * Return true if {@code type1} and {@code type2} have equivalent sets of annotations. + * + * @param type1 a type + * @param type2 a type + * @return true if {@code type1} and {@code type2} have equivalent sets of annotations + */ + protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + return AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations()); } - @SuppressWarnings("TypeEquals") // TODO - boolean sameUnderlyingType = type1.getUnderlyingType().equals(type2.getUnderlyingType()); - return sameUnderlyingType && arePrimaryAnnosEqual(type1, type2); - } + /** + * Return true if the twe types are the same. + * + * @param type1 the first type to compare + * @param type2 the second type to compare + * @return true if the twe types are the same + */ + @EqualsMethod // to make Interning Checker permit the == comparison + protected boolean compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (type1 == type2) { + return true; + } + if (type1 == null || type2 == null) { + return false; + } + + @SuppressWarnings("TypeEquals") // TODO + boolean sameUnderlyingType = type1.getUnderlyingType().equals(type2.getUnderlyingType()); + return sameUnderlyingType && arePrimaryAnnosEqual(type1, type2); + } - @SuppressWarnings("interning:not.interned") - @Override - protected Boolean scanWithNull(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { - // one of them should be null, therefore they are only equal if the other is null - return type1 == type2; - } + @SuppressWarnings("interning:not.interned") + @Override + protected Boolean scanWithNull( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { + // one of them should be null, therefore they are only equal if the other is null + return type1 == type2; + } - @Override - protected Boolean scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void v) { - return compare(type1, type2) && reduce(true, super.scan(type1, type2, v)); - } + @Override + protected Boolean scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void v) { + return compare(type1, type2) && reduce(true, super.scan(type1, type2, v)); + } - /** Used to combine the results from component types or a type and its component types. */ - @Override - protected Boolean reduce(Boolean r1, Boolean r2) { - if (r1 == null) { - return r2; - } else if (r2 == null) { - return r1; - } else { - return r1 && r2; + /** Used to combine the results from component types or a type and its component types. */ + @Override + protected Boolean reduce(Boolean r1, Boolean r2) { + if (r1 == null) { + return r2; + } else if (r2 == null) { + return r1; + } else { + return r1 && r2; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java index a789b79dd73..b73d8448eed 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -14,32 +14,7 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.lang.annotation.Annotation; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.StringJoiner; -import java.util.regex.Pattern; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; + import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -124,6 +99,34 @@ import org.plumelib.util.IPair; import org.plumelib.util.SystemPlume; +import java.lang.annotation.Annotation; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.StringJoiner; +import java.util.regex.Pattern; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + /** * A factory that extends {@link AnnotatedTypeFactory} to optionally use flow-sensitive qualifier * inference. @@ -135,2519 +138,2510 @@ * AnnotatedTypeFactory}; it is not clear why they are defined in this class. */ public abstract class GenericAnnotatedTypeFactory< - Value extends CFAbstractValue, - Store extends CFAbstractStore, - TransferFunction extends CFAbstractTransfer, - FlowAnalysis extends CFAbstractAnalysis> - extends AnnotatedTypeFactory { - - /** - * Whether to output verbose, low-level debugging messages. Also see {@code TreeAnnotator.debug} - * and {@link AnnotatedTypeFactory#debugStubParser}. - */ - private static final boolean debug = false; - - /** To cache the supported monotonic type qualifiers. */ - private @MonotonicNonNull Set> supportedMonotonicQuals; - - /** to annotate types based on the given tree */ - protected TypeAnnotator typeAnnotator; - - /** for use in addAnnotationsFromDefaultForType */ - private DefaultQualifierForUseTypeAnnotator defaultQualifierForUseTypeAnnotator; - - /** for use in addAnnotationsFromDefaultForType */ - private DefaultForTypeAnnotator defaultForTypeAnnotator; - - /** to annotate types based on the given un-annotated types */ - protected TreeAnnotator treeAnnotator; - - /** to handle any polymorphic types */ - protected QualifierPolymorphism poly; - - /** to handle defaults specified by the user */ - protected QualifierDefaults defaults; - - /** To handle dependent type annotations and contract expressions. */ - protected DependentTypesHelper dependentTypesHelper; - - /** To handle method pre- and postconditions. */ - protected final ContractsFromMethod contractsUtils; - - /** - * The Java types on which users may write this type system's type annotations. null means no - * restrictions. Arrays are handled by separate field {@code #arraysAreRelevant}. - * - *

          If the relevant type is generic, this contains its erasure. - * - *

          Although a {@code Class} object exists for every element, this does not contain those - * {@code Class} objects because the elements will be compared to TypeMirrors for which Class - * objects may not exist (they might not be on the classpath). - */ - public final @Nullable Set relevantJavaTypes; - - /** - * Whether users may write type annotations on arrays. Ignored unless {@link #relevantJavaTypes} - * is non-null. - */ - protected final boolean arraysAreRelevant; - - // Flow related fields - - /** Should flow be used by default? */ - protected static boolean flowByDefault = true; - - /** - * Should use flow-sensitive type refinement analysis? This value can be changed when an - * AnnotatedTypeMirror without annotations from data flow is required. - * - * @see #getAnnotatedTypeLhs(Tree) - */ - private boolean useFlow; - - /** Is this type factory configured to use flow-sensitive type refinement? */ - private final boolean everUseFlow; - - /** - * Should the local variable default annotation be applied to type variables? - * - *

          It is initialized to true if data flow is used by the checker. It is set to false when - * getting the assignment context for type argument inference. - * - * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault - */ - private boolean shouldDefaultTypeVarLocals; - - /** - * The inferred types applier utility to use. Initialized in postInit() and should not be - * re-assigned after initialization. - */ - private DefaultInferredTypesApplier inferredTypesApplier; - - /** - * Elements representing variables for which the type of the initializer is being determined in - * order to apply qualifier parameter defaults. - * - *

          Local variables with a qualifier parameter get their declared type from the type of their - * initializer. Sometimes the initializer's type depends on the type of the variable, such as - * during type variable inference or when a variable is used in its own initializer as in "Object - * o = (o = null)". This creates a circular dependency resulting in infinite recursion. To prevent - * this, variables in this set should not be typed based on their initializer, but by using normal - * defaults. - * - *

          This set should only be modified in - * GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults which clears variables - * after computing their initializer types. - * - * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults - */ - private final Set variablesUnderInitialization = new HashSet<>(); - - /** - * Caches types of initializers for local variables with a qualifier parameter, so that they - * aren't computed each time the type of a variable is looked up. - * - * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults - */ - private final Map initializerCache; - - /** - * Should the analysis assume that side effects to a value can change the type of aliased - * references? - * - *

          For many type systems, once a local variable's type is refined, side effects to the - * variable's value do not change the variable's type annotations. For some type systems, a side - * effect to the value could change them; set this field to true. - */ - // Not final so that subclasses can set it. - public boolean sideEffectsUnrefineAliases = false; - - /** - * True if this checker either has one or more subcheckers, or if this checker is a subchecker. - * False otherwise. All uses of the methods {@link #addSharedCFGForTree(Tree, ControlFlowGraph)} - * and {@link #getSharedCFGForTree(Tree)} should be guarded by a check that this is true. - */ - public final boolean hasOrIsSubchecker; - - /** An empty store. */ - // Set in postInit only - protected Store emptyStore; - - // Set in postInit only - protected FlowAnalysis analysis; - - // Set in postInit only - protected TransferFunction transfer; - - // Maintain for every class the store that is used when we analyze initialization code - protected Store initializationStore; - - // Maintain for every class the store that is used when we analyze static initialization code - protected Store initializationStaticStore; - - /** - * Caches for {@link AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, - * IdentityHashMap, Map)}. This cache is enabled if {@link #shouldCache} is true. The cache size - * is derived from {@link #getCacheSize()}. - * - * @see AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, - * IdentityHashMap, Map) - */ - protected final Map< - TransferInput, IdentityHashMap>> - flowResultAnalysisCaches; - - /** - * Subcheckers share the same ControlFlowGraph for each analyzed code statement. This maps from - * code statements to the shared control flow graphs. This map is null in all subcheckers (i.e. - * any checker for which getParentChecker() returns non-null). This map is also unused (and - * therefore null) for a checker with no subcheckers with which it can share CFGs. - * - *

          The initial capacity of the map is set by {@link #getCacheSize()}. - */ - protected @MonotonicNonNull Map subcheckerSharedCFG; - - /** - * If true, {@link #setRoot(CompilationUnitTree)} should clear the {@link #subcheckerSharedCFG} - * map, freeing memory. - * - *

          For each compilation unit, all the subcheckers run first and finally the ultimate parent - * checker runs. The ultimate parent checker's {@link #setRoot(CompilationUnitTree)} (the last to - * run) sets this field to true. - * - *

          In first subchecker to run for the next compilation unit, {@link - * #setRoot(CompilationUnitTree)} observes the true value, clears the {@link #subcheckerSharedCFG} - * map, and sets this field back to false. That first subchecker will create a CFG and re-populate - * the map, and subsequent subcheckers will use the map. - */ - protected boolean shouldClearSubcheckerSharedCFGs = true; - - /** - * Creates a type factory. Its compilation unit is not yet set. - * - * @param checker the checker to which this type factory belongs - * @param useFlow whether flow analysis should be performed - */ - protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { - super(checker); - - this.everUseFlow = useFlow; - this.shouldDefaultTypeVarLocals = useFlow; - this.useFlow = useFlow; - - this.flowResult = null; - this.regularExitStores = new IdentityHashMap<>(); - this.exceptionalExitStores = new IdentityHashMap<>(); - this.returnStatementStores = new IdentityHashMap<>(); - - this.initializationStore = null; - this.initializationStaticStore = null; - - this.cfgVisualizer = createCFGVisualizer(); - this.handleCFGViz = checker.hasOption("flowdotdir") || checker.hasOption("cfgviz"); - - if (shouldCache) { - int cacheSize = getCacheSize(); - flowResultAnalysisCaches = CollectionsPlume.createLruCache(cacheSize); - initializerCache = CollectionsPlume.createLruCache(cacheSize); - } else { - flowResultAnalysisCaches = null; - initializerCache = null; - } - - RelevantJavaTypes relevantJavaTypesAnno = - checker.getClass().getAnnotation(RelevantJavaTypes.class); - if (relevantJavaTypesAnno == null) { - this.relevantJavaTypes = null; - this.arraysAreRelevant = true; - } else { - Types types = getChecker().getTypeUtils(); - Elements elements = getElementUtils(); - Class[] classes = relevantJavaTypesAnno.value(); - Set relevantJavaTypesTemp = - new HashSet<>(CollectionsPlume.mapCapacity(classes.length)); - boolean arraysAreRelevantTemp = false; - for (Class clazz : classes) { - if (clazz == Object[].class) { - arraysAreRelevantTemp = true; - } else if (clazz.isArray()) { - throw new TypeSystemError( - "Don't use arrays other than Object[] in @RelevantJavaTypes on " - + this.getClass().getSimpleName()); + Value extends CFAbstractValue, + Store extends CFAbstractStore, + TransferFunction extends CFAbstractTransfer, + FlowAnalysis extends CFAbstractAnalysis> + extends AnnotatedTypeFactory { + + /** + * Whether to output verbose, low-level debugging messages. Also see {@code TreeAnnotator.debug} + * and {@link AnnotatedTypeFactory#debugStubParser}. + */ + private static final boolean debug = false; + + /** To cache the supported monotonic type qualifiers. */ + private @MonotonicNonNull Set> supportedMonotonicQuals; + + /** to annotate types based on the given tree */ + protected TypeAnnotator typeAnnotator; + + /** for use in addAnnotationsFromDefaultForType */ + private DefaultQualifierForUseTypeAnnotator defaultQualifierForUseTypeAnnotator; + + /** for use in addAnnotationsFromDefaultForType */ + private DefaultForTypeAnnotator defaultForTypeAnnotator; + + /** to annotate types based on the given un-annotated types */ + protected TreeAnnotator treeAnnotator; + + /** to handle any polymorphic types */ + protected QualifierPolymorphism poly; + + /** to handle defaults specified by the user */ + protected QualifierDefaults defaults; + + /** To handle dependent type annotations and contract expressions. */ + protected DependentTypesHelper dependentTypesHelper; + + /** To handle method pre- and postconditions. */ + protected final ContractsFromMethod contractsUtils; + + /** + * The Java types on which users may write this type system's type annotations. null means no + * restrictions. Arrays are handled by separate field {@code #arraysAreRelevant}. + * + *

          If the relevant type is generic, this contains its erasure. + * + *

          Although a {@code Class} object exists for every element, this does not contain those + * {@code Class} objects because the elements will be compared to TypeMirrors for which Class + * objects may not exist (they might not be on the classpath). + */ + public final @Nullable Set relevantJavaTypes; + + /** + * Whether users may write type annotations on arrays. Ignored unless {@link #relevantJavaTypes} + * is non-null. + */ + protected final boolean arraysAreRelevant; + + // Flow related fields + + /** Should flow be used by default? */ + protected static boolean flowByDefault = true; + + /** + * Should use flow-sensitive type refinement analysis? This value can be changed when an + * AnnotatedTypeMirror without annotations from data flow is required. + * + * @see #getAnnotatedTypeLhs(Tree) + */ + private boolean useFlow; + + /** Is this type factory configured to use flow-sensitive type refinement? */ + private final boolean everUseFlow; + + /** + * Should the local variable default annotation be applied to type variables? + * + *

          It is initialized to true if data flow is used by the checker. It is set to false when + * getting the assignment context for type argument inference. + * + * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault + */ + private boolean shouldDefaultTypeVarLocals; + + /** + * The inferred types applier utility to use. Initialized in postInit() and should not be + * re-assigned after initialization. + */ + private DefaultInferredTypesApplier inferredTypesApplier; + + /** + * Elements representing variables for which the type of the initializer is being determined in + * order to apply qualifier parameter defaults. + * + *

          Local variables with a qualifier parameter get their declared type from the type of their + * initializer. Sometimes the initializer's type depends on the type of the variable, such as + * during type variable inference or when a variable is used in its own initializer as in + * "Object o = (o = null)". This creates a circular dependency resulting in infinite recursion. + * To prevent this, variables in this set should not be typed based on their initializer, but by + * using normal defaults. + * + *

          This set should only be modified in + * GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults which clears + * variables after computing their initializer types. + * + * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults + */ + private final Set variablesUnderInitialization = new HashSet<>(); + + /** + * Caches types of initializers for local variables with a qualifier parameter, so that they + * aren't computed each time the type of a variable is looked up. + * + * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults + */ + private final Map initializerCache; + + /** + * Should the analysis assume that side effects to a value can change the type of aliased + * references? + * + *

          For many type systems, once a local variable's type is refined, side effects to the + * variable's value do not change the variable's type annotations. For some type systems, a side + * effect to the value could change them; set this field to true. + */ + // Not final so that subclasses can set it. + public boolean sideEffectsUnrefineAliases = false; + + /** + * True if this checker either has one or more subcheckers, or if this checker is a subchecker. + * False otherwise. All uses of the methods {@link #addSharedCFGForTree(Tree, ControlFlowGraph)} + * and {@link #getSharedCFGForTree(Tree)} should be guarded by a check that this is true. + */ + public final boolean hasOrIsSubchecker; + + /** An empty store. */ + // Set in postInit only + protected Store emptyStore; + + // Set in postInit only + protected FlowAnalysis analysis; + + // Set in postInit only + protected TransferFunction transfer; + + // Maintain for every class the store that is used when we analyze initialization code + protected Store initializationStore; + + // Maintain for every class the store that is used when we analyze static initialization code + protected Store initializationStaticStore; + + /** + * Caches for {@link AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, + * IdentityHashMap, Map)}. This cache is enabled if {@link #shouldCache} is true. The cache size + * is derived from {@link #getCacheSize()}. + * + * @see AnalysisResult#runAnalysisFor(Node, Analysis.BeforeOrAfter, TransferInput, + * IdentityHashMap, Map) + */ + protected final Map< + TransferInput, + IdentityHashMap>> + flowResultAnalysisCaches; + + /** + * Subcheckers share the same ControlFlowGraph for each analyzed code statement. This maps from + * code statements to the shared control flow graphs. This map is null in all subcheckers (i.e. + * any checker for which getParentChecker() returns non-null). This map is also unused (and + * therefore null) for a checker with no subcheckers with which it can share CFGs. + * + *

          The initial capacity of the map is set by {@link #getCacheSize()}. + */ + protected @MonotonicNonNull Map subcheckerSharedCFG; + + /** + * If true, {@link #setRoot(CompilationUnitTree)} should clear the {@link #subcheckerSharedCFG} + * map, freeing memory. + * + *

          For each compilation unit, all the subcheckers run first and finally the ultimate parent + * checker runs. The ultimate parent checker's {@link #setRoot(CompilationUnitTree)} (the last + * to run) sets this field to true. + * + *

          In first subchecker to run for the next compilation unit, {@link + * #setRoot(CompilationUnitTree)} observes the true value, clears the {@link + * #subcheckerSharedCFG} map, and sets this field back to false. That first subchecker will + * create a CFG and re-populate the map, and subsequent subcheckers will use the map. + */ + protected boolean shouldClearSubcheckerSharedCFGs = true; + + /** + * Creates a type factory. Its compilation unit is not yet set. + * + * @param checker the checker to which this type factory belongs + * @param useFlow whether flow analysis should be performed + */ + protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) { + super(checker); + + this.everUseFlow = useFlow; + this.shouldDefaultTypeVarLocals = useFlow; + this.useFlow = useFlow; + + this.flowResult = null; + this.regularExitStores = new IdentityHashMap<>(); + this.exceptionalExitStores = new IdentityHashMap<>(); + this.returnStatementStores = new IdentityHashMap<>(); + + this.initializationStore = null; + this.initializationStaticStore = null; + + this.cfgVisualizer = createCFGVisualizer(); + this.handleCFGViz = checker.hasOption("flowdotdir") || checker.hasOption("cfgviz"); + + if (shouldCache) { + int cacheSize = getCacheSize(); + flowResultAnalysisCaches = CollectionsPlume.createLruCache(cacheSize); + initializerCache = CollectionsPlume.createLruCache(cacheSize); } else { - TypeMirror relevantType = TypesUtils.typeFromClass(clazz, types, elements); - TypeMirror erased = types.erasure(relevantType); - relevantJavaTypesTemp.add(erased); + flowResultAnalysisCaches = null; + initializerCache = null; } - } - this.relevantJavaTypes = Collections.unmodifiableSet(relevantJavaTypesTemp); - this.arraysAreRelevant = arraysAreRelevantTemp; - } - - contractsUtils = createContractsFromMethod(); - - hasOrIsSubchecker = - !this.getChecker().getSubcheckers().isEmpty() - || this.getChecker().getParentChecker() != null; - - // Every subclass must call postInit, but it must be called after - // all other initialization is finished. - } - - /** - * Determines whether flow-sensitive type refinement should be used or not. - * - * @return whether flow-sensitive type refinement should be used or not - * @see #useFlow - */ - protected boolean getUseFlow() { - return useFlow; - } - - @Override - protected void postInit( - @UnderInitialization(GenericAnnotatedTypeFactory.class) GenericAnnotatedTypeFactory this) { - super.postInit(); - - this.dependentTypesHelper = createDependentTypesHelper(); - this.defaults = createAndInitQualifierDefaults(); - this.treeAnnotator = createTreeAnnotator(); - this.typeAnnotator = createTypeAnnotator(); - this.defaultQualifierForUseTypeAnnotator = createDefaultForUseTypeAnnotator(); - this.defaultForTypeAnnotator = createDefaultForTypeAnnotator(); - - this.poly = createQualifierPolymorphism(); - - this.analysis = createFlowAnalysis(); - this.transfer = analysis.getTransferFunction(); - this.emptyStore = analysis.createEmptyStore(transfer.usesSequentialSemantics()); - - this.parseAnnotationFiles(); - - this.inferredTypesApplier = new DefaultInferredTypesApplier(getQualifierHierarchy(), this); - } - - /** - * Performs flow-sensitive type refinement on {@code classTree} if this type factory is configured - * to do so. - * - * @param classTree tree on which to perform flow-sensitive type refinement - */ - @Override - public void preProcessClassTree(ClassTree classTree) { - if (this.everUseFlow) { - checkAndPerformFlowAnalysis(classTree); - } - } - - /** - * Creates a type factory. Its compilation unit is not yet set. This constructor might get - * reflectively called by BaseTypeVisitor.createTypeFactory and uses flowByDefault to determine - * whether flow refinement should be enabled. Subclasses should instead use the two-parameter - * constructor and explicitly set whether to use flow refinement. - * - * @param checker the checker to which this type factory belongs - */ - protected GenericAnnotatedTypeFactory(BaseTypeChecker checker) { - this(checker, flowByDefault); - } - - @Override - public void setRoot(@Nullable CompilationUnitTree root) { - if (this.defaultQualifierForUseTypeAnnotator == null) { - throw new TypeSystemError( - "Does the constructor for %s call postInit()?", this.getClass().getSimpleName()); - } - - super.setRoot(root); - this.scannedClasses.clear(); - // this.reachableNodes.clear(); - this.flowResult = null; - this.regularExitStores.clear(); - this.exceptionalExitStores.clear(); - this.returnStatementStores.clear(); - this.initializationStore = null; - this.initializationStaticStore = null; - - if (shouldCache) { - this.flowResultAnalysisCaches.clear(); - this.initializerCache.clear(); - this.defaultQualifierForUseTypeAnnotator.clearCache(); - - if (this.checker.getParentChecker() == null) { - // This is an ultimate parent checker, so after it runs the shared CFG it is using - // will no longer be needed, and can be cleared. - this.shouldClearSubcheckerSharedCFGs = true; - if (this.checker.getSubcheckers().isEmpty()) { - // If this checker has no subcheckers, then any maps that are currently - // being maintained should be cleared right away. - clearSharedCFG(this); - } - } else { - GenericAnnotatedTypeFactory ultimateParentATF = - this.checker.getUltimateParentChecker().getTypeFactory(); - clearSharedCFG(ultimateParentATF); - } + + RelevantJavaTypes relevantJavaTypesAnno = + checker.getClass().getAnnotation(RelevantJavaTypes.class); + if (relevantJavaTypesAnno == null) { + this.relevantJavaTypes = null; + this.arraysAreRelevant = true; + } else { + Types types = getChecker().getTypeUtils(); + Elements elements = getElementUtils(); + Class[] classes = relevantJavaTypesAnno.value(); + Set relevantJavaTypesTemp = + new HashSet<>(CollectionsPlume.mapCapacity(classes.length)); + boolean arraysAreRelevantTemp = false; + for (Class clazz : classes) { + if (clazz == Object[].class) { + arraysAreRelevantTemp = true; + } else if (clazz.isArray()) { + throw new TypeSystemError( + "Don't use arrays other than Object[] in @RelevantJavaTypes on " + + this.getClass().getSimpleName()); + } else { + TypeMirror relevantType = TypesUtils.typeFromClass(clazz, types, elements); + TypeMirror erased = types.erasure(relevantType); + relevantJavaTypesTemp.add(erased); + } + } + this.relevantJavaTypes = Collections.unmodifiableSet(relevantJavaTypesTemp); + this.arraysAreRelevant = arraysAreRelevantTemp; + } + + contractsUtils = createContractsFromMethod(); + + hasOrIsSubchecker = + !this.getChecker().getSubcheckers().isEmpty() + || this.getChecker().getParentChecker() != null; + + // Every subclass must call postInit, but it must be called after + // all other initialization is finished. } - } - - /** - * Clears the caches associated with the shared CFG for the given type factory, if it is safe to - * do so. - * - * @param factory a type factory - */ - private void clearSharedCFG(GenericAnnotatedTypeFactory factory) { - if (factory.shouldClearSubcheckerSharedCFGs) { - // This is the first subchecker running in a group that share CFGs, so it must clear its - // ultimate parent's shared CFG before adding a new shared CFG. - factory.shouldClearSubcheckerSharedCFGs = false; - if (factory.subcheckerSharedCFG != null) { - factory.subcheckerSharedCFG.clear(); - } + + /** + * Determines whether flow-sensitive type refinement should be used or not. + * + * @return whether flow-sensitive type refinement should be used or not + * @see #useFlow + */ + protected boolean getUseFlow() { + return useFlow; + } + + @Override + protected void postInit( + @UnderInitialization(GenericAnnotatedTypeFactory.class) GenericAnnotatedTypeFactory + this) { + super.postInit(); + + this.dependentTypesHelper = createDependentTypesHelper(); + this.defaults = createAndInitQualifierDefaults(); + this.treeAnnotator = createTreeAnnotator(); + this.typeAnnotator = createTypeAnnotator(); + this.defaultQualifierForUseTypeAnnotator = createDefaultForUseTypeAnnotator(); + this.defaultForTypeAnnotator = createDefaultForTypeAnnotator(); + + this.poly = createQualifierPolymorphism(); + + this.analysis = createFlowAnalysis(); + this.transfer = analysis.getTransferFunction(); + this.emptyStore = analysis.createEmptyStore(transfer.usesSequentialSemantics()); + + this.parseAnnotationFiles(); + + this.inferredTypesApplier = new DefaultInferredTypesApplier(getQualifierHierarchy(), this); } - } - - // ********************************************************************** - // Factory Methods for the appropriate annotator classes - // ********************************************************************** - - /** - * Returns an immutable set of the monotonic type qualifiers supported by this checker. - * - * @return the monotonic type qualifiers supported this processor, or an empty set if none - * @see MonotonicQualifier - */ - public final Set> getSupportedMonotonicTypeQualifiers() { - if (supportedMonotonicQuals == null) { - supportedMonotonicQuals = new HashSet<>(); - for (Class anno : getSupportedTypeQualifiers()) { - MonotonicQualifier mono = anno.getAnnotation(MonotonicQualifier.class); - if (mono != null) { - supportedMonotonicQuals.add(anno); + + /** + * Performs flow-sensitive type refinement on {@code classTree} if this type factory is + * configured to do so. + * + * @param classTree tree on which to perform flow-sensitive type refinement + */ + @Override + public void preProcessClassTree(ClassTree classTree) { + if (this.everUseFlow) { + checkAndPerformFlowAnalysis(classTree); } - } } - return supportedMonotonicQuals; - } - - /** - * Returns a {@link TreeAnnotator} that adds annotations to a type based on the contents of a - * tree. - * - *

          The default tree annotator is a {@link ListTreeAnnotator} of the following: - * - *

            - *
          1. {@link PropagationTreeAnnotator}: Propagates annotations from subtrees - *
          2. {@link LiteralTreeAnnotator}: Adds annotations based on {@link QualifierForLiterals} - * meta-annotations - *
          3. {@link DependentTypesTreeAnnotator}: Adapts dependent annotations based on context - *
          - * - *

          Subclasses may override this method to specify additional tree annotators, for example: - * - *

          -   * new ListTreeAnnotator(super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this));
          -   * 
          - * - * @return a tree annotator - */ - protected TreeAnnotator createTreeAnnotator() { - List treeAnnotators = new ArrayList<>(2); - treeAnnotators.add(new PropagationTreeAnnotator(this)); - treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); - if (dependentTypesHelper.hasDependentAnnotations()) { - treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); - } - return new ListTreeAnnotator(treeAnnotators); - } - - /** - * Returns a {@link DefaultForTypeAnnotator} that adds annotations to a type based on the content - * of the type itself. - * - *

          Subclass may override this method. The default type annotator is a {@link ListTypeAnnotator} - * of the following: - * - *

            - *
          1. {@link IrrelevantTypeAnnotator}: Adds top to types not listed in the {@code @}{@link - * RelevantJavaTypes} annotation on the checker. - *
          2. {@link PropagationTypeAnnotator}: Propagates annotation onto wildcards. - *
          - * - * @return a type annotator - */ - protected TypeAnnotator createTypeAnnotator() { - List typeAnnotators = new ArrayList<>(1); - if (relevantJavaTypes != null) { - typeAnnotators.add(new IrrelevantTypeAnnotator(this)); - } - typeAnnotators.add(new PropagationTypeAnnotator(this)); - return new ListTypeAnnotator(typeAnnotators); - } - - /** - * Returns the annotations that should appear on the given irrelevant Java type. If the type is - * relevant, this method's behavior is undefined. - * - * @param tm an irrelevant Java type - * @return the annotations that should appear on the given irrelevant Java type - */ - public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { - return getQualifierHierarchy().getTopAnnotations(); - } - - /** - * Creates an {@link DefaultQualifierForUseTypeAnnotator}. - * - * @return a new {@link DefaultQualifierForUseTypeAnnotator} - */ - protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { - return new DefaultQualifierForUseTypeAnnotator(this); - } - - /** - * Creates an {@link DefaultForTypeAnnotator}. - * - * @return a new {@link DefaultForTypeAnnotator} - */ - protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { - return new DefaultForTypeAnnotator(this); - } - - /** - * Returns the {@link DefaultForTypeAnnotator}. - * - * @return the {@link DefaultForTypeAnnotator} - */ - public DefaultForTypeAnnotator getDefaultForTypeAnnotator() { - return defaultForTypeAnnotator; - } - - /** - * Returns the appropriate flow analysis class that is used for the org.checkerframework.dataflow - * analysis. - * - *

          This implementation uses the checker naming convention to create the appropriate analysis. - * If no transfer function is found, it returns an instance of {@link CFAnalysis}. - * - *

          Subclasses have to override this method to create the appropriate analysis if they do not - * follow the checker naming convention. - * - * @return the appropriate flow analysis class that is used for the org.checkerframework.dataflow - * analysis - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - protected FlowAnalysis createFlowAnalysis() { - // Try to reflectively load the visitor. - Class checkerClass = checker.getClass(); - - while (checkerClass != BaseTypeChecker.class) { - FlowAnalysis result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Analysis"), - new Class[] {BaseTypeChecker.class, this.getClass()}, - new Object[] {checker, this}); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If an analysis couldn't be loaded reflectively, return the default. - return (FlowAnalysis) new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this); - } - - /** - * Returns the appropriate transfer function that is used for the given - * org.checkerframework.dataflow analysis. - * - *

          This implementation uses the checker naming convention to create the appropriate transfer - * function. If no transfer function is found, it returns an instance of {@link CFTransfer}. - * - *

          Subclasses have to override this method to create the appropriate transfer function if they - * do not follow the checker naming convention. - * - * @param analysis a dataflow analysis - * @return a new transfer function - */ - // A more precise type for the parameter would be FlowAnalysis, which - // is the type parameter bounded by the current parameter type CFAbstractAnalysis. - // However, we ran into issues in callers of the method if we used that type. - public TransferFunction createFlowTransferFunction( - CFAbstractAnalysis analysis) { - // Try to reflectively load the visitor. - Class checkerClass = checker.getClass(); - - while (checkerClass != BaseTypeChecker.class) { - TransferFunction result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Transfer"), - new Class[] {analysis.getClass()}, - new Object[] {analysis}); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If a transfer function couldn't be loaded reflectively, return the default. - @SuppressWarnings("unchecked") - TransferFunction ret = - (TransferFunction) - new CFTransfer((CFAbstractAnalysis) analysis); - return ret; - } - - /** - * Creates a {@link DependentTypesHelper} and returns it. Use {@link #getDependentTypesHelper} to - * access the value. - * - * @return a new {@link DependentTypesHelper} - */ - protected DependentTypesHelper createDependentTypesHelper() { - return new DependentTypesHelper(this); - } - - /** - * Returns the DependentTypesHelper. - * - * @return the DependentTypesHelper - */ - public DependentTypesHelper getDependentTypesHelper() { - return dependentTypesHelper; - } - - /** - * Creates an {@link DefaultContractsFromMethod} and returns it. If contract annotations are not - * used for a type system, override this method and return a {@link NoContractsFromMethod}. - * - * @return a new {@link ContractsFromMethod} - */ - protected ContractsFromMethod createContractsFromMethod() { - return new DefaultContractsFromMethod(this); - } - - /** - * Returns the helper for method pre- and postconditions. - * - * @return the helper for method pre- and postconditions - */ - public ContractsFromMethod getContractsFromMethod() { - return contractsUtils; - } - - @Override - protected List getExplicitNewClassClassTypeArgs(NewClassTree newClassTree) { - List superResult = super.getExplicitNewClassClassTypeArgs(newClassTree); - for (AnnotatedTypeMirror superR : superResult) { - dependentTypesHelper.atExpression(superR, newClassTree); - } - return superResult; - } - - @Override - public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { - AnnotationMirrorSet superResult = super.getExplicitNewClassAnnos(newClassTree); - AnnotatedTypeMirror dummy = getAnnotatedNullType(superResult); - dependentTypesHelper.atExpression(dummy, newClassTree); - return dummy.getAnnotations(); - } - - /** - * Create {@link QualifierDefaults} which handles checker specified defaults, and initialize the - * created {@link QualifierDefaults}. Subclasses should override {@link - * GenericAnnotatedTypeFactory#addCheckedCodeDefaults(QualifierDefaults defs)} to add more - * defaults or use different defaults. - * - * @return the QualifierDefaults object - */ - // TODO: When changing this method, also look into - // {@link - // org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesHelper#shouldIgnore}. - // Both methods should have some functionality merged into a single location. - // See Issue 683 - // https://github.com/typetools/checker-framework/issues/683 - protected final QualifierDefaults createAndInitQualifierDefaults() { - QualifierDefaults defs = createQualifierDefaults(); - addCheckedCodeDefaults(defs); - addCheckedStandardDefaults(defs); - addUncheckedStandardDefaults(defs); - checkForDefaultQualifierInHierarchy(defs); - - return defs; - } - - /** - * Create {@link QualifierDefaults} which handles checker specified defaults. Sub-classes override - * this method to provide a different {@code QualifierDefault} implementation. - */ - protected QualifierDefaults createQualifierDefaults() { - return new QualifierDefaults(elements, this); - } - - /** - * Creates and returns a string containing the number of qualifiers and the canonical class names - * of each qualifier that has been added to this checker's supported qualifier set. The names are - * alphabetically sorted. - * - * @return a string containing the number of qualifiers and canonical names of each qualifier - */ - protected final String getSortedQualifierNames() { - Set> stq = getSupportedTypeQualifiers(); - if (stq.isEmpty()) { - return "No qualifiers examined"; - } - if (stq.size() == 1) { - return "1 qualifier examined: " + stq.iterator().next().getCanonicalName(); - } - - // Create a list of the supported qualifiers and sort the list alphabetically - List> sortedSupportedQuals = new ArrayList<>(stq); - sortedSupportedQuals.sort(Comparator.comparing(Class::getCanonicalName)); - - // display the number of qualifiers as well as the names of each qualifier. - StringJoiner sj = - new StringJoiner(", ", sortedSupportedQuals.size() + " qualifiers examined: ", ""); - for (Class qual : sortedSupportedQuals) { - sj.add(qual.getCanonicalName()); - } - return sj.toString(); - } - - /** - * Adds default qualifiers for type-checked code by reading {@link DefaultFor} and {@link - * DefaultQualifierInHierarchy} meta-annotations. Subclasses may override this method to add - * defaults that cannot be specified with a {@link DefaultFor} or {@link - * DefaultQualifierInHierarchy} meta-annotations. - * - * @param defs the QualifierDefault object to which defaults are added - */ - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy - for (Class qual : getSupportedTypeQualifiers()) { - DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); - if (defaultFor != null) { - TypeUseLocation[] locations = defaultFor.value(); - defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); - } - if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { - defs.addCheckedCodeDefault( - AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); - } + /** + * Creates a type factory. Its compilation unit is not yet set. This constructor might get + * reflectively called by BaseTypeVisitor.createTypeFactory and uses flowByDefault to determine + * whether flow refinement should be enabled. Subclasses should instead use the two-parameter + * constructor and explicitly set whether to use flow refinement. + * + * @param checker the checker to which this type factory belongs + */ + protected GenericAnnotatedTypeFactory(BaseTypeChecker checker) { + this(checker, flowByDefault); + } + + @Override + public void setRoot(@Nullable CompilationUnitTree root) { + if (this.defaultQualifierForUseTypeAnnotator == null) { + throw new TypeSystemError( + "Does the constructor for %s call postInit()?", + this.getClass().getSimpleName()); + } + + super.setRoot(root); + this.scannedClasses.clear(); + // this.reachableNodes.clear(); + this.flowResult = null; + this.regularExitStores.clear(); + this.exceptionalExitStores.clear(); + this.returnStatementStores.clear(); + this.initializationStore = null; + this.initializationStaticStore = null; + + if (shouldCache) { + this.flowResultAnalysisCaches.clear(); + this.initializerCache.clear(); + this.defaultQualifierForUseTypeAnnotator.clearCache(); + + if (this.checker.getParentChecker() == null) { + // This is an ultimate parent checker, so after it runs the shared CFG it is using + // will no longer be needed, and can be cleared. + this.shouldClearSubcheckerSharedCFGs = true; + if (this.checker.getSubcheckers().isEmpty()) { + // If this checker has no subcheckers, then any maps that are currently + // being maintained should be cleared right away. + clearSharedCFG(this); + } + } else { + GenericAnnotatedTypeFactory ultimateParentATF = + this.checker.getUltimateParentChecker().getTypeFactory(); + clearSharedCFG(ultimateParentATF); + } + } } - } - - /** - * Adds the standard CLIMB defaults that do not conflict with previously added defaults. - * - * @param defs {@link QualifierDefaults} object to which defaults are added - */ - protected void addCheckedStandardDefaults(QualifierDefaults defs) { - if (this.everUseFlow) { - defs.addClimbStandardDefaults(); - } - } - - /** - * Adds standard unchecked defaults that do not conflict with previously added defaults. - * - * @param defs {@link QualifierDefaults} object to which defaults are added - */ - protected void addUncheckedStandardDefaults(QualifierDefaults defs) { - defs.addUncheckedStandardDefaults(); - } - - /** - * Check that a default qualifier (in at least one hierarchy) has been set and issue an error if - * not. - * - * @param defs {@link QualifierDefaults} object to which defaults are added - */ - protected void checkForDefaultQualifierInHierarchy(QualifierDefaults defs) { - if (!defs.hasDefaultsForCheckedCode()) { - throw new BugInCF( - "GenericAnnotatedTypeFactory.createQualifierDefaults:" - + " @DefaultQualifierInHierarchy or @DefaultFor(TypeUseLocation.OTHERWISE)" - + " not found. Every checker must specify a default qualifier. " - + getSortedQualifierNames()); - } - - // If a default unchecked code qualifier isn't specified, the defaults - // for checked code will be used. - } - - /** - * Creates the {@link QualifierPolymorphism} instance which supports the QualifierPolymorphism - * mechanism. - * - * @return the QualifierPolymorphism instance to use - */ - protected QualifierPolymorphism createQualifierPolymorphism() { - return new DefaultQualifierPolymorphism(processingEnv, this); - } - - /** - * Gives the current {@link QualifierPolymorphism} instance which supports the - * QualifierPolymorphism mechanism. - * - * @return the QualifierPolymorphism instance to use - */ - public QualifierPolymorphism getQualifierPolymorphism() { - return this.poly; - } - - // ********************************************************************** - // Factory Methods for the appropriate annotator classes - // ********************************************************************** - - @Override - protected void postDirectSuperTypes( - AnnotatedTypeMirror type, List supertypes) { - super.postDirectSuperTypes(type, supertypes); - if (type.getKind() == TypeKind.DECLARED) { - for (AnnotatedTypeMirror supertype : supertypes) { - Element elt = ((DeclaredType) supertype.getUnderlyingType()).asElement(); - addComputedTypeAnnotations(elt, supertype); - } + + /** + * Clears the caches associated with the shared CFG for the given type factory, if it is safe to + * do so. + * + * @param factory a type factory + */ + private void clearSharedCFG(GenericAnnotatedTypeFactory factory) { + if (factory.shouldClearSubcheckerSharedCFGs) { + // This is the first subchecker running in a group that share CFGs, so it must clear its + // ultimate parent's shared CFG before adding a new shared CFG. + factory.shouldClearSubcheckerSharedCFGs = false; + if (factory.subcheckerSharedCFG != null) { + factory.subcheckerSharedCFG.clear(); + } + } } - } - - /** - * Returns the primary annotation on expression if it were evaluated at path. - * - * @param expression a Java expression - * @param tree current tree - * @param path location at which expression is evaluated - * @param clazz class of the annotation - * @return the annotation on expression or null if one does not exist - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - */ - public @Nullable AnnotationMirror getAnnotationFromJavaExpressionString( - String expression, Tree tree, TreePath path, Class clazz) - throws JavaExpressionParseException { - JavaExpression expressionObj = parseJavaExpressionString(expression, path); - return getAnnotationFromJavaExpression(expressionObj, tree, clazz); - } - - /** - * Returns the primary annotation on an expression, at a particular location. - * - * @param expr the expression for which the annotation is returned - * @param tree current tree - * @param clazz the Class of the annotation - * @return the annotation on expression or null if one does not exist - */ - public @Nullable AnnotationMirror getAnnotationFromJavaExpression( - JavaExpression expr, Tree tree, Class clazz) { - return getAnnotationByClass(getAnnotationsFromJavaExpression(expr, tree), clazz); - } - - /** - * Returns the primary annotations on an expression, at a particular location. - * - * @param expr the expression for which the annotation is returned - * @param tree current tree - * @return the annotation on expression or null if one does not exist - */ - public @Nullable AnnotationMirrorSet getAnnotationsFromJavaExpression( - JavaExpression expr, Tree tree) { - // Look in the store - if (CFAbstractStore.canInsertJavaExpression(expr)) { - Store store = getStoreBefore(tree); - // `store` can be null if the tree is in a field initializer. - if (store != null) { - Value value = store.getValue(expr); - if (value != null) { - // Is it possible that this lacks some annotations that appear in the type - // factory? - return value.getAnnotations(); + + // ********************************************************************** + // Factory Methods for the appropriate annotator classes + // ********************************************************************** + + /** + * Returns an immutable set of the monotonic type qualifiers supported by this checker. + * + * @return the monotonic type qualifiers supported this processor, or an empty set if none + * @see MonotonicQualifier + */ + public final Set> getSupportedMonotonicTypeQualifiers() { + if (supportedMonotonicQuals == null) { + supportedMonotonicQuals = new HashSet<>(); + for (Class anno : getSupportedTypeQualifiers()) { + MonotonicQualifier mono = anno.getAnnotation(MonotonicQualifier.class); + if (mono != null) { + supportedMonotonicQuals.add(anno); + } + } + } + return supportedMonotonicQuals; + } + + /** + * Returns a {@link TreeAnnotator} that adds annotations to a type based on the contents of a + * tree. + * + *

          The default tree annotator is a {@link ListTreeAnnotator} of the following: + * + *

            + *
          1. {@link PropagationTreeAnnotator}: Propagates annotations from subtrees + *
          2. {@link LiteralTreeAnnotator}: Adds annotations based on {@link QualifierForLiterals} + * meta-annotations + *
          3. {@link DependentTypesTreeAnnotator}: Adapts dependent annotations based on context + *
          + * + *

          Subclasses may override this method to specify additional tree annotators, for example: + * + *

          +     * new ListTreeAnnotator(super.createTreeAnnotator(), new KeyLookupTreeAnnotator(this));
          +     * 
          + * + * @return a tree annotator + */ + protected TreeAnnotator createTreeAnnotator() { + List treeAnnotators = new ArrayList<>(2); + treeAnnotators.add(new PropagationTreeAnnotator(this)); + treeAnnotators.add(new LiteralTreeAnnotator(this).addStandardLiteralQualifiers()); + if (dependentTypesHelper.hasDependentAnnotations()) { + treeAnnotators.add(dependentTypesHelper.createDependentTypesTreeAnnotator()); + } + return new ListTreeAnnotator(treeAnnotators); + } + + /** + * Returns a {@link DefaultForTypeAnnotator} that adds annotations to a type based on the + * content of the type itself. + * + *

          Subclass may override this method. The default type annotator is a {@link + * ListTypeAnnotator} of the following: + * + *

            + *
          1. {@link IrrelevantTypeAnnotator}: Adds top to types not listed in the {@code @}{@link + * RelevantJavaTypes} annotation on the checker. + *
          2. {@link PropagationTypeAnnotator}: Propagates annotation onto wildcards. + *
          + * + * @return a type annotator + */ + protected TypeAnnotator createTypeAnnotator() { + List typeAnnotators = new ArrayList<>(1); + if (relevantJavaTypes != null) { + typeAnnotators.add(new IrrelevantTypeAnnotator(this)); + } + typeAnnotators.add(new PropagationTypeAnnotator(this)); + return new ListTypeAnnotator(typeAnnotators); + } + + /** + * Returns the annotations that should appear on the given irrelevant Java type. If the type is + * relevant, this method's behavior is undefined. + * + * @param tm an irrelevant Java type + * @return the annotations that should appear on the given irrelevant Java type + */ + public AnnotationMirrorSet annotationsForIrrelevantJavaType(TypeMirror tm) { + return getQualifierHierarchy().getTopAnnotations(); + } + + /** + * Creates an {@link DefaultQualifierForUseTypeAnnotator}. + * + * @return a new {@link DefaultQualifierForUseTypeAnnotator} + */ + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new DefaultQualifierForUseTypeAnnotator(this); + } + + /** + * Creates an {@link DefaultForTypeAnnotator}. + * + * @return a new {@link DefaultForTypeAnnotator} + */ + protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { + return new DefaultForTypeAnnotator(this); + } + + /** + * Returns the {@link DefaultForTypeAnnotator}. + * + * @return the {@link DefaultForTypeAnnotator} + */ + public DefaultForTypeAnnotator getDefaultForTypeAnnotator() { + return defaultForTypeAnnotator; + } + + /** + * Returns the appropriate flow analysis class that is used for the + * org.checkerframework.dataflow analysis. + * + *

          This implementation uses the checker naming convention to create the appropriate analysis. + * If no transfer function is found, it returns an instance of {@link CFAnalysis}. + * + *

          Subclasses have to override this method to create the appropriate analysis if they do not + * follow the checker naming convention. + * + * @return the appropriate flow analysis class that is used for the + * org.checkerframework.dataflow analysis + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + protected FlowAnalysis createFlowAnalysis() { + // Try to reflectively load the visitor. + Class checkerClass = checker.getClass(); + + while (checkerClass != BaseTypeChecker.class) { + FlowAnalysis result = + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, "Analysis"), + new Class[] {BaseTypeChecker.class, this.getClass()}, + new Object[] {checker, this}); + if (result != null) { + return result; + } + checkerClass = checkerClass.getSuperclass(); + } + + // If an analysis couldn't be loaded reflectively, return the default. + return (FlowAnalysis) new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this); + } + + /** + * Returns the appropriate transfer function that is used for the given + * org.checkerframework.dataflow analysis. + * + *

          This implementation uses the checker naming convention to create the appropriate transfer + * function. If no transfer function is found, it returns an instance of {@link CFTransfer}. + * + *

          Subclasses have to override this method to create the appropriate transfer function if + * they do not follow the checker naming convention. + * + * @param analysis a dataflow analysis + * @return a new transfer function + */ + // A more precise type for the parameter would be FlowAnalysis, which + // is the type parameter bounded by the current parameter type CFAbstractAnalysis. + // However, we ran into issues in callers of the method if we used that type. + public TransferFunction createFlowTransferFunction( + CFAbstractAnalysis analysis) { + // Try to reflectively load the visitor. + Class checkerClass = checker.getClass(); + + while (checkerClass != BaseTypeChecker.class) { + TransferFunction result = + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, "Transfer"), + new Class[] {analysis.getClass()}, + new Object[] {analysis}); + if (result != null) { + return result; + } + checkerClass = checkerClass.getSuperclass(); + } + + // If a transfer function couldn't be loaded reflectively, return the default. + @SuppressWarnings("unchecked") + TransferFunction ret = + (TransferFunction) + new CFTransfer((CFAbstractAnalysis) analysis); + return ret; + } + + /** + * Creates a {@link DependentTypesHelper} and returns it. Use {@link #getDependentTypesHelper} + * to access the value. + * + * @return a new {@link DependentTypesHelper} + */ + protected DependentTypesHelper createDependentTypesHelper() { + return new DependentTypesHelper(this); + } + + /** + * Returns the DependentTypesHelper. + * + * @return the DependentTypesHelper + */ + public DependentTypesHelper getDependentTypesHelper() { + return dependentTypesHelper; + } + + /** + * Creates an {@link DefaultContractsFromMethod} and returns it. If contract annotations are not + * used for a type system, override this method and return a {@link NoContractsFromMethod}. + * + * @return a new {@link ContractsFromMethod} + */ + protected ContractsFromMethod createContractsFromMethod() { + return new DefaultContractsFromMethod(this); + } + + /** + * Returns the helper for method pre- and postconditions. + * + * @return the helper for method pre- and postconditions + */ + public ContractsFromMethod getContractsFromMethod() { + return contractsUtils; + } + + @Override + protected List getExplicitNewClassClassTypeArgs( + NewClassTree newClassTree) { + List superResult = + super.getExplicitNewClassClassTypeArgs(newClassTree); + for (AnnotatedTypeMirror superR : superResult) { + dependentTypesHelper.atExpression(superR, newClassTree); + } + return superResult; + } + + @Override + public AnnotationMirrorSet getExplicitNewClassAnnos(NewClassTree newClassTree) { + AnnotationMirrorSet superResult = super.getExplicitNewClassAnnos(newClassTree); + AnnotatedTypeMirror dummy = getAnnotatedNullType(superResult); + dependentTypesHelper.atExpression(dummy, newClassTree); + return dummy.getAnnotations(); + } + + /** + * Create {@link QualifierDefaults} which handles checker specified defaults, and initialize the + * created {@link QualifierDefaults}. Subclasses should override {@link + * GenericAnnotatedTypeFactory#addCheckedCodeDefaults(QualifierDefaults defs)} to add more + * defaults or use different defaults. + * + * @return the QualifierDefaults object + */ + // TODO: When changing this method, also look into + // {@link + // org.checkerframework.common.wholeprograminference.WholeProgramInferenceScenesHelper#shouldIgnore}. + // Both methods should have some functionality merged into a single location. + // See Issue 683 + // https://github.com/typetools/checker-framework/issues/683 + protected final QualifierDefaults createAndInitQualifierDefaults() { + QualifierDefaults defs = createQualifierDefaults(); + addCheckedCodeDefaults(defs); + addCheckedStandardDefaults(defs); + addUncheckedStandardDefaults(defs); + checkForDefaultQualifierInHierarchy(defs); + + return defs; + } + + /** + * Create {@link QualifierDefaults} which handles checker specified defaults. Sub-classes + * override this method to provide a different {@code QualifierDefault} implementation. + */ + protected QualifierDefaults createQualifierDefaults() { + return new QualifierDefaults(elements, this); + } + + /** + * Creates and returns a string containing the number of qualifiers and the canonical class + * names of each qualifier that has been added to this checker's supported qualifier set. The + * names are alphabetically sorted. + * + * @return a string containing the number of qualifiers and canonical names of each qualifier + */ + protected final String getSortedQualifierNames() { + Set> stq = getSupportedTypeQualifiers(); + if (stq.isEmpty()) { + return "No qualifiers examined"; + } + if (stq.size() == 1) { + return "1 qualifier examined: " + stq.iterator().next().getCanonicalName(); + } + + // Create a list of the supported qualifiers and sort the list alphabetically + List> sortedSupportedQuals = new ArrayList<>(stq); + sortedSupportedQuals.sort(Comparator.comparing(Class::getCanonicalName)); + + // display the number of qualifiers as well as the names of each qualifier. + StringJoiner sj = + new StringJoiner(", ", sortedSupportedQuals.size() + " qualifiers examined: ", ""); + for (Class qual : sortedSupportedQuals) { + sj.add(qual.getCanonicalName()); + } + return sj.toString(); + } + + /** + * Adds default qualifiers for type-checked code by reading {@link DefaultFor} and {@link + * DefaultQualifierInHierarchy} meta-annotations. Subclasses may override this method to add + * defaults that cannot be specified with a {@link DefaultFor} or {@link + * DefaultQualifierInHierarchy} meta-annotations. + * + * @param defs the QualifierDefault object to which defaults are added + */ + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + // Add defaults from @DefaultFor and @DefaultQualifierInHierarchy + for (Class qual : getSupportedTypeQualifiers()) { + DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); + if (defaultFor != null) { + TypeUseLocation[] locations = defaultFor.value(); + defs.addCheckedCodeDefaults(AnnotationBuilder.fromClass(elements, qual), locations); + } + + if (qual.getAnnotation(DefaultQualifierInHierarchy.class) != null) { + defs.addCheckedCodeDefault( + AnnotationBuilder.fromClass(elements, qual), TypeUseLocation.OTHERWISE); + } } - } } - // Look in the type factory, if not found in the store. - if (expr instanceof LocalVariable) { - Element ele = ((LocalVariable) expr).getElement(); - // Because of - // https://github.com/eisop/checker-framework/issues/14 - // and the workaround in - // org.checkerframework.framework.type.ElementAnnotationApplier.applyInternal - // The annotationMirror may not contain all explicitly written annotations. - return getAnnotatedType(ele).getAnnotations(); - } else if (expr instanceof FieldAccess) { - Element ele = ((FieldAccess) expr).getField(); - return getAnnotatedType(ele).getAnnotations(); - } else { - return AnnotationMirrorSet.emptySet(); - } - } - - /** - * Produces the JavaExpression as if {@code expression} were written at {@code currentPath}. - * - * @param expression a Java expression - * @param currentPath the current path - * @return the JavaExpression associated with expression on currentPath - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - */ - public JavaExpression parseJavaExpressionString(String expression, TreePath currentPath) - throws JavaExpressionParseException { - return StringToJavaExpression.atPath(expression, currentPath, checker); - } - - /** - * Produces the JavaExpression and offset associated with an expression. For instance, "n+1" has - * no associated JavaExpression, but this method produces a pair of a JavaExpression (for "n") and - * an offset ("1"). - * - * @param expression a Java expression, possibly with a constant offset - * @param currentPath location at which expression is evaluated - * @return the JavaExpression and offset for the given expression - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - */ - public IPair getExpressionAndOffsetFromJavaExpressionString( - String expression, TreePath currentPath) throws JavaExpressionParseException { - IPair p = getExpressionAndOffset(expression); - JavaExpression r = parseJavaExpressionString(p.first, currentPath); - return IPair.of(r, p.second); - } - - /** - * Returns the annotation mirror from dataflow for {@code expression}. - * - *

          This will output a different annotation than {@link - * #getAnnotationFromJavaExpressionString(String, Tree, TreePath, Class)}, because if the - * specified annotation isn't found in the store, the type from the factory is used. - * - * @param expression a Java expression - * @param tree the tree at the location to parse the expression - * @param currentPath location at which expression is evaluated - * @throws JavaExpressionParseException thrown if the expression cannot be parsed - * @return an AnnotationMirror representing the type in the store at the given location from this - * type factory's type system, or null if one is not available - */ - public @Nullable AnnotationMirror getAnnotationMirrorFromJavaExpressionString( - String expression, Tree tree, TreePath currentPath) throws JavaExpressionParseException { - JavaExpression je = parseJavaExpressionString(expression, currentPath); - if (je == null || !CFAbstractStore.canInsertJavaExpression(je)) { - return null; - } - Store store = getStoreBefore(tree); - Value value = store.getValue(je); - return value != null ? value.getAnnotations().iterator().next() : null; - } - - /* - * Returns true if the {@code exprTree} is unreachable. This is a conservative estimate and may - * return {@code false} even though the {@code exprTree} is unreachable. - * - * @param exprTree an expression tree - * @return true if the {@code exprTree} is unreachable - * - public boolean isUnreachable(ExpressionTree exprTree) { - if (!everUseFlow) { - return false; - } - Set nodes = getNodesForTree(exprTree); - if (nodes == null) { - // Dataflow has no any information about the tree, so conservatively consider the tree - // reachable. - return false; - } - for (Node n : nodes) { - if (n.getTree() != null && reachableNodes.contains(n.getTree())) { - return false; - } - } - // None of the corresponding nodes is reachable, so this tree is dead. - return true; - } - */ - - /** - * Track the state of org.checkerframework.dataflow analysis scanning for each class tree in the - * compilation unit. - */ - protected enum ScanState { - /** Dataflow analysis in progress. */ - IN_PROGRESS, - /** Dataflow analysis finished. */ - FINISHED - } - - /** Map from ClassTree to their dataflow analysis state. */ - protected final Map scannedClasses = new HashMap<>(); - - /* - * A set of trees whose corresponding nodes are reachable. This is not an exhaustive set of - * reachable trees. Use {@link #isUnreachable(ExpressionTree)} instead of this set directly. - * - *

          This cannot be a set of Nodes, because two LocalVariableNodes are equal if they have the - * same name but represent different uses of the variable. So instead of storing Nodes, it - * stores the result of {@code Node#getTree}. - */ - // private final Set reachableNodes = new HashSet<>(); - - /** - * The result of the flow analysis. Invariant: - * - *

          -   *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
          -   * 
          - * - * Note that flowResult contains analysis results for Trees from multiple classes which are - * produced by multiple calls to performFlowAnalysis. - */ - protected @MonotonicNonNull AnalysisResult flowResult; - - /** - * A mapping from methods (or other code blocks) to their regular exit store (used to check - * postconditions). - */ - protected final IdentityHashMap regularExitStores; - - /** A mapping from methods (or other code blocks) to their exceptional exit store. */ - protected final IdentityHashMap exceptionalExitStores; - - /** A mapping from methods to a list with all return statements and the corresponding store. */ - protected final IdentityHashMap>>> - returnStatementStores; - - /** - * Returns the regular exit store for a method or another code block (such as static - * initializers). Returns {@code null} if there is no such store. This can happen because the - * method cannot exit through the regular exit block, or it is abstract or in an interface. - * - * @param tree a MethodTree or other code block, such as a static initializer - * @return the regular exit store, or {@code null} - */ - public @Nullable Store getRegularExitStore(Tree tree) { - if (regularExitStores == null) { - if (tree.getKind() == Tree.Kind.METHOD) { - if (((MethodTree) tree).getBody() == null) { - // No body: the method is abstract or in an interface - return null; + /** + * Adds the standard CLIMB defaults that do not conflict with previously added defaults. + * + * @param defs {@link QualifierDefaults} object to which defaults are added + */ + protected void addCheckedStandardDefaults(QualifierDefaults defs) { + if (this.everUseFlow) { + defs.addClimbStandardDefaults(); } - } - throw new BugInCF("regularExitStores==null for [" + tree.getClass() + "]" + tree); - } - return regularExitStores.get(tree); - } - - /** - * Returns the exceptional exit store for a method or another code block (such as static - * initializers). - * - * @param tree a MethodTree or other code block, such as a static initializer - * @return the exceptional exit store, or {@code null}, if there is no such store - */ - public @Nullable Store getExceptionalExitStore(Tree tree) { - return exceptionalExitStores.get(tree); - } - - /** - * Returns a list of all return statements of {@code method} paired with their corresponding - * {@link TransferResult}. If {@code method} has no return statement, then the empty list is - * returned. - * - * @param methodTree method whose return statements should be returned - * @return a list of all return statements of {@code method} paired with their corresponding - * {@link TransferResult} or an empty list if {@code method} has no return statements - */ - public List>> getReturnStatementStores( - MethodTree methodTree) { - assert returnStatementStores.containsKey(methodTree); - return returnStatementStores.get(methodTree); - } - - /** - * Returns the store immediately before a given {@link Tree}. - * - * @return the store immediately before a given {@link Tree} - */ - public Store getStoreBefore(Tree tree) { - if (!analysis.isRunning()) { - return flowResult.getStoreBefore(tree); - } - Set nodes = analysis.getNodesForTree(tree); - if (nodes != null) { - return getStoreBefore(nodes); - } else { - return flowResult.getStoreBefore(tree); - } - } - - /** - * Returns the store immediately before a given Set of {@link Node}s. - * - * @return the store immediately before a given Set of {@link Node}s - */ - public Store getStoreBefore(Set nodes) { - Store merge = null; - for (Node aNode : nodes) { - Store s = getStoreBefore(aNode); - if (merge == null) { - merge = s; - } else if (s != null) { - merge = merge.leastUpperBound(s); - } } - return merge; - } - - /** - * Returns the store immediately before a given node. - * - * @param node a node whose pre-store to return - * @return the store immediately before {@code node} - */ - public @Nullable Store getStoreBefore(Node node) { - if (!analysis.isRunning()) { - return flowResult.getStoreBefore(node); - } - TransferInput prevStore = analysis.getInput(node.getBlock()); - if (prevStore == null) { - return null; - } - Store store = - AnalysisResult.runAnalysisFor( - node, - Analysis.BeforeOrAfter.BEFORE, - prevStore, - analysis.getNodeValues(), - flowResultAnalysisCaches); - return store; - } - - /** - * Returns the store immediately after a given tree. - * - *

          May return null; for example, after a {@code return} statement. - * - * @param tree the tree whose post-store to return - * @return the store immediately after a given tree - */ - public @Nullable Store getStoreAfter(Tree tree) { - if (!analysis.isRunning()) { - return flowResult.getStoreAfter(tree); - } - Set nodes = analysis.getNodesForTree(tree); - return getStoreAfter(nodes); - } - - /** - * Returns the store immediately after a given set of nodes. - * - * @param nodes the nodes whose post-stores to LUB - * @return the LUB of the stores store immediately after {@code nodes} - */ - public Store getStoreAfter(Set nodes) { - Store merge = null; - for (Node node : nodes) { - Store s = getStoreAfter(node); - if (merge == null) { - merge = s; - } else if (s != null) { - merge = merge.leastUpperBound(s); - } + + /** + * Adds standard unchecked defaults that do not conflict with previously added defaults. + * + * @param defs {@link QualifierDefaults} object to which defaults are added + */ + protected void addUncheckedStandardDefaults(QualifierDefaults defs) { + defs.addUncheckedStandardDefaults(); + } + + /** + * Check that a default qualifier (in at least one hierarchy) has been set and issue an error if + * not. + * + * @param defs {@link QualifierDefaults} object to which defaults are added + */ + protected void checkForDefaultQualifierInHierarchy(QualifierDefaults defs) { + if (!defs.hasDefaultsForCheckedCode()) { + throw new BugInCF( + "GenericAnnotatedTypeFactory.createQualifierDefaults:" + + " @DefaultQualifierInHierarchy or @DefaultFor(TypeUseLocation.OTHERWISE)" + + " not found. Every checker must specify a default qualifier. " + + getSortedQualifierNames()); + } + + // If a default unchecked code qualifier isn't specified, the defaults + // for checked code will be used. + } + + /** + * Creates the {@link QualifierPolymorphism} instance which supports the QualifierPolymorphism + * mechanism. + * + * @return the QualifierPolymorphism instance to use + */ + protected QualifierPolymorphism createQualifierPolymorphism() { + return new DefaultQualifierPolymorphism(processingEnv, this); + } + + /** + * Gives the current {@link QualifierPolymorphism} instance which supports the + * QualifierPolymorphism mechanism. + * + * @return the QualifierPolymorphism instance to use + */ + public QualifierPolymorphism getQualifierPolymorphism() { + return this.poly; + } + + // ********************************************************************** + // Factory Methods for the appropriate annotator classes + // ********************************************************************** + + @Override + protected void postDirectSuperTypes( + AnnotatedTypeMirror type, List supertypes) { + super.postDirectSuperTypes(type, supertypes); + if (type.getKind() == TypeKind.DECLARED) { + for (AnnotatedTypeMirror supertype : supertypes) { + Element elt = ((DeclaredType) supertype.getUnderlyingType()).asElement(); + addComputedTypeAnnotations(elt, supertype); + } + } } - return merge; - } - - /** - * Returns the store immediately after a given {@link Node}. - * - * @param node node after which the store is returned - * @return the store immediately after a given {@link Node} - */ - public Store getStoreAfter(Node node) { - if (!analysis.isRunning()) { - return flowResult.getStoreAfter(node); - } - Store res = - AnalysisResult.runAnalysisFor( - node, - Analysis.BeforeOrAfter.AFTER, - analysis.getInput(node.getBlock()), - analysis.getNodeValues(), - flowResultAnalysisCaches); - return res; - } - - /** - * See {@link org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)}. - * - * @param tree a tree - * @return the {@link Node}s for a given {@link Tree} - * @see org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree) - */ - public @Nullable Set getNodesForTree(Tree tree) { - return flowResult.getNodesForTree(tree); - } - - /** - * Return the first {@link Node} for a given {@link Tree} that has class {@code kind}. - * - *

          You probably don't want to use this function: iterate over the result of {@link - * #getNodesForTree(Tree)} yourself or ask for a conservative approximation of the store using - * {@link #getStoreBefore(Tree)} or {@link #getStoreAfter(Tree)}. This method is for code that - * uses a {@link Node} in a rather unusual way. Callers should probably be rewritten to not use a - * {@link Node} at all. - * - * @param the class of the node to return - * @param tree a tree in which to search for a node of class {@code kind} - * @param kind the class of the node to return - * @return the first {@link Node} for a given {@link Tree} that has class {@code kind} - * @see #getNodesForTree(Tree) - * @see #getStoreBefore(Tree) - * @see #getStoreAfter(Tree) - */ - public @Nullable T getFirstNodeOfKindForTree(Tree tree, Class kind) { - Set nodes = getNodesForTree(tree); - for (Node node : nodes) { - if (node.getClass() == kind) { - return kind.cast(node); - } + + /** + * Returns the primary annotation on expression if it were evaluated at path. + * + * @param expression a Java expression + * @param tree current tree + * @param path location at which expression is evaluated + * @param clazz class of the annotation + * @return the annotation on expression or null if one does not exist + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + */ + public @Nullable AnnotationMirror getAnnotationFromJavaExpressionString( + String expression, Tree tree, TreePath path, Class clazz) + throws JavaExpressionParseException { + JavaExpression expressionObj = parseJavaExpressionString(expression, path); + return getAnnotationFromJavaExpression(expressionObj, tree, clazz); + } + + /** + * Returns the primary annotation on an expression, at a particular location. + * + * @param expr the expression for which the annotation is returned + * @param tree current tree + * @param clazz the Class of the annotation + * @return the annotation on expression or null if one does not exist + */ + public @Nullable AnnotationMirror getAnnotationFromJavaExpression( + JavaExpression expr, Tree tree, Class clazz) { + return getAnnotationByClass(getAnnotationsFromJavaExpression(expr, tree), clazz); + } + + /** + * Returns the primary annotations on an expression, at a particular location. + * + * @param expr the expression for which the annotation is returned + * @param tree current tree + * @return the annotation on expression or null if one does not exist + */ + public @Nullable AnnotationMirrorSet getAnnotationsFromJavaExpression( + JavaExpression expr, Tree tree) { + // Look in the store + if (CFAbstractStore.canInsertJavaExpression(expr)) { + Store store = getStoreBefore(tree); + // `store` can be null if the tree is in a field initializer. + if (store != null) { + Value value = store.getValue(expr); + if (value != null) { + // Is it possible that this lacks some annotations that appear in the type + // factory? + return value.getAnnotations(); + } + } + } + + // Look in the type factory, if not found in the store. + if (expr instanceof LocalVariable) { + Element ele = ((LocalVariable) expr).getElement(); + // Because of + // https://github.com/eisop/checker-framework/issues/14 + // and the workaround in + // org.checkerframework.framework.type.ElementAnnotationApplier.applyInternal + // The annotationMirror may not contain all explicitly written annotations. + return getAnnotatedType(ele).getAnnotations(); + } else if (expr instanceof FieldAccess) { + Element ele = ((FieldAccess) expr).getField(); + return getAnnotatedType(ele).getAnnotations(); + } else { + return AnnotationMirrorSet.emptySet(); + } } - return null; - } - - /** - * Returns the value of effectively final local variables. - * - * @return the value of effectively final local variables - */ - public Map getFinalLocalValues() { - return flowResult.getFinalLocalValues(); - } - - /** - * Returns true if the receiver of a method or constructor might not be fully initialized. - * - * @param methodDeclTree the declaration of the method or constructor - * @return true if the receiver of a method or constructor might not be fully initialized - */ - @Pure - public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { - return TreeUtils.isConstructor(methodDeclTree); - } - - /** - * Perform a org.checkerframework.dataflow analysis over a single class tree and its nested - * classes. - * - * @param classTree the class to analyze - */ - protected void performFlowAnalysis(ClassTree classTree) { - if (flowResult == null) { - this.regularExitStores.clear(); - this.exceptionalExitStores.clear(); - this.returnStatementStores.clear(); - this.flowResult = new AnalysisResult<>(flowResultAnalysisCaches); - } - - // no need to scan annotations - if (classTree.getKind() == Tree.Kind.ANNOTATION_TYPE) { - // Mark finished so that default annotations will be applied. - scannedClasses.put(classTree, ScanState.FINISHED); - return; - } - - // class trees and their initial stores - Queue> classQueue = new ArrayDeque<>(); - List> fieldValues = new ArrayList<>(); - - // No captured store for top-level classes. - classQueue.add(IPair.of(classTree, null)); - - while (!classQueue.isEmpty()) { - IPair qel = classQueue.remove(); - ClassTree ct = qel.first; - Store capturedStore = qel.second; - scannedClasses.put(ct, ScanState.IN_PROGRESS); - - TreePath preTreePath = getVisitorTreePath(); - - // Don't call AnnotatedTypeFactory#getPath, because it uses visitorTreePath. - setVisitorTreePath(TreePath.getPath(this.getRoot(), ct)); - - // start with the captured store as initialization store - initializationStaticStore = capturedStore; - initializationStore = capturedStore; - - // The store is null if the lambda is unreachable. - Queue> lambdaQueue = new ArrayDeque<>(); - - // Queue up classes (for top-level `while` loop) and methods (for within this `try` - // construct); analyze top-level blocks and variable initializers as they are - // encountered. - try { - List methods = new ArrayList<>(); - List members = ct.getMembers(); - if (!Ordering.from(sortVariablesFirst).isOrdered(members)) { - members = new ArrayList<>(members); - // Process variables before methods, so all field initializers are observed - // before the constructor is analyzed and reports uninitialized variables. - members.sort(sortVariablesFirst); - } - for (Tree m : members) { - switch (TreeUtils.getKindRecordAsClass(m)) { - case CLASS: // Including RECORD - case ANNOTATION_TYPE: - case INTERFACE: - case ENUM: - // Visit inner and nested class trees. - // TODO: Use no store for them? What can be captured? - classQueue.add(IPair.of((ClassTree) m, capturedStore)); - break; - case METHOD: - MethodTree mt = (MethodTree) m; - // Skip abstract and native methods because they have no body. - Set flags = mt.getModifiers().getFlags(); - if (flags.contains(Modifier.ABSTRACT) || flags.contains(Modifier.NATIVE)) { - break; - } - // Abstract methods in an interface have a null body but do not have an - // ABSTRACT flag. - if (mt.getBody() == null) { - break; - } + /** + * Produces the JavaExpression as if {@code expression} were written at {@code currentPath}. + * + * @param expression a Java expression + * @param currentPath the current path + * @return the JavaExpression associated with expression on currentPath + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + */ + public JavaExpression parseJavaExpressionString(String expression, TreePath currentPath) + throws JavaExpressionParseException { + return StringToJavaExpression.atPath(expression, currentPath, checker); + } + + /** + * Produces the JavaExpression and offset associated with an expression. For instance, "n+1" has + * no associated JavaExpression, but this method produces a pair of a JavaExpression (for "n") + * and an offset ("1"). + * + * @param expression a Java expression, possibly with a constant offset + * @param currentPath location at which expression is evaluated + * @return the JavaExpression and offset for the given expression + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + */ + public IPair getExpressionAndOffsetFromJavaExpressionString( + String expression, TreePath currentPath) throws JavaExpressionParseException { + IPair p = getExpressionAndOffset(expression); + JavaExpression r = parseJavaExpressionString(p.first, currentPath); + return IPair.of(r, p.second); + } + + /** + * Returns the annotation mirror from dataflow for {@code expression}. + * + *

          This will output a different annotation than {@link + * #getAnnotationFromJavaExpressionString(String, Tree, TreePath, Class)}, because if the + * specified annotation isn't found in the store, the type from the factory is used. + * + * @param expression a Java expression + * @param tree the tree at the location to parse the expression + * @param currentPath location at which expression is evaluated + * @throws JavaExpressionParseException thrown if the expression cannot be parsed + * @return an AnnotationMirror representing the type in the store at the given location from + * this type factory's type system, or null if one is not available + */ + public @Nullable AnnotationMirror getAnnotationMirrorFromJavaExpressionString( + String expression, Tree tree, TreePath currentPath) + throws JavaExpressionParseException { + JavaExpression je = parseJavaExpressionString(expression, currentPath); + if (je == null || !CFAbstractStore.canInsertJavaExpression(je)) { + return null; + } + Store store = getStoreBefore(tree); + Value value = store.getValue(je); + return value != null ? value.getAnnotations().iterator().next() : null; + } - // Wait with scanning the method until all other members - // have been processed. - CFGMethod met = new CFGMethod(mt, ct); - methods.add(met); - break; - case VARIABLE: - VariableTree vt = (VariableTree) m; - ExpressionTree initializer = vt.getInitializer(); - AnnotatedTypeMirror declaredType = getAnnotatedTypeLhs(vt); - Value declaredValue = analysis.createAbstractValue(declaredType); - FieldAccess fieldExpr = (FieldAccess) JavaExpression.fromVariableTree(vt); - // analyze initializer if present - if (initializer != null) { - boolean isStatic = vt.getModifiers().getFlags().contains(Modifier.STATIC); - analyze( - classQueue, - lambdaQueue, - new CFGStatement(vt, ct), - fieldValues, - classTree, - true, - true, - isStatic, - capturedStore); - Value initializerValue = flowResult.getValue(initializer); - if (initializerValue != null) { - fieldValues.add( - new FieldInitialValue<>(fieldExpr, declaredValue, initializerValue)); - break; + /* + * Returns true if the {@code exprTree} is unreachable. This is a conservative estimate and may + * return {@code false} even though the {@code exprTree} is unreachable. + * + * @param exprTree an expression tree + * @return true if the {@code exprTree} is unreachable + * + public boolean isUnreachable(ExpressionTree exprTree) { + if (!everUseFlow) { + return false; + } + Set nodes = getNodesForTree(exprTree); + if (nodes == null) { + // Dataflow has no any information about the tree, so conservatively consider the tree + // reachable. + return false; + } + for (Node n : nodes) { + if (n.getTree() != null && reachableNodes.contains(n.getTree())) { + return false; + } + } + // None of the corresponding nodes is reachable, so this tree is dead. + return true; + } + */ + + /** + * Track the state of org.checkerframework.dataflow analysis scanning for each class tree in the + * compilation unit. + */ + protected enum ScanState { + /** Dataflow analysis in progress. */ + IN_PROGRESS, + /** Dataflow analysis finished. */ + FINISHED + } + + /** Map from ClassTree to their dataflow analysis state. */ + protected final Map scannedClasses = new HashMap<>(); + + /* + * A set of trees whose corresponding nodes are reachable. This is not an exhaustive set of + * reachable trees. Use {@link #isUnreachable(ExpressionTree)} instead of this set directly. + * + *

          This cannot be a set of Nodes, because two LocalVariableNodes are equal if they have the + * same name but represent different uses of the variable. So instead of storing Nodes, it + * stores the result of {@code Node#getTree}. + */ + // private final Set reachableNodes = new HashSet<>(); + + /** + * The result of the flow analysis. Invariant: + * + *

          +     *  scannedClasses.get(c) == FINISHED for some class c ⇒ flowResult != null
          +     * 
          + * + * Note that flowResult contains analysis results for Trees from multiple classes which are + * produced by multiple calls to performFlowAnalysis. + */ + protected @MonotonicNonNull AnalysisResult flowResult; + + /** + * A mapping from methods (or other code blocks) to their regular exit store (used to check + * postconditions). + */ + protected final IdentityHashMap regularExitStores; + + /** A mapping from methods (or other code blocks) to their exceptional exit store. */ + protected final IdentityHashMap exceptionalExitStores; + + /** A mapping from methods to a list with all return statements and the corresponding store. */ + protected final IdentityHashMap< + MethodTree, List>>> + returnStatementStores; + + /** + * Returns the regular exit store for a method or another code block (such as static + * initializers). Returns {@code null} if there is no such store. This can happen because the + * method cannot exit through the regular exit block, or it is abstract or in an interface. + * + * @param tree a MethodTree or other code block, such as a static initializer + * @return the regular exit store, or {@code null} + */ + public @Nullable Store getRegularExitStore(Tree tree) { + if (regularExitStores == null) { + if (tree.getKind() == Tree.Kind.METHOD) { + if (((MethodTree) tree).getBody() == null) { + // No body: the method is abstract or in an interface + return null; } - } - fieldValues.add(new FieldInitialValue<>(fieldExpr, declaredValue, null)); - break; - case BLOCK: - BlockTree b = (BlockTree) m; - analyze( - classQueue, - lambdaQueue, - new CFGStatement(b, ct), - fieldValues, - ct, - true, - true, - b.isStatic(), - capturedStore); - break; - default: - assert false : "Unexpected member: " + m.getKind(); - break; - } - } - - // Now analyze all methods. - // TODO: at this point, we don't have any information about - // fields of superclasses. - for (CFGMethod met : methods) { - analyze( - classQueue, - lambdaQueue, - met, - fieldValues, - classTree, - TreeUtils.isConstructor(met.getMethod()), - false, - false, - capturedStore); - } - - while (!lambdaQueue.isEmpty()) { - IPair lambdaPair = lambdaQueue.poll(); - MethodTree mt = - (MethodTree) - TreePathUtil.enclosingOfKind(getPath(lambdaPair.first), Tree.Kind.METHOD); - analyze( - classQueue, - lambdaQueue, - new CFGLambda(lambdaPair.first, classTree, mt), - fieldValues, - classTree, - false, - false, - false, - lambdaPair.second); - } - - // By convention we store the static initialization store as the regular exit - // store of the class node, so that it can later be used to check - // that all fields are initialized properly. - // See InitializationVisitor.visitClass(). - if (initializationStaticStore == null) { - regularExitStores.put(ct, emptyStore); + } + throw new BugInCF("regularExitStores==null for [" + tree.getClass() + "]" + tree); + } + return regularExitStores.get(tree); + } + + /** + * Returns the exceptional exit store for a method or another code block (such as static + * initializers). + * + * @param tree a MethodTree or other code block, such as a static initializer + * @return the exceptional exit store, or {@code null}, if there is no such store + */ + public @Nullable Store getExceptionalExitStore(Tree tree) { + return exceptionalExitStores.get(tree); + } + + /** + * Returns a list of all return statements of {@code method} paired with their corresponding + * {@link TransferResult}. If {@code method} has no return statement, then the empty list is + * returned. + * + * @param methodTree method whose return statements should be returned + * @return a list of all return statements of {@code method} paired with their corresponding + * {@link TransferResult} or an empty list if {@code method} has no return statements + */ + public List>> getReturnStatementStores( + MethodTree methodTree) { + assert returnStatementStores.containsKey(methodTree); + return returnStatementStores.get(methodTree); + } + + /** + * Returns the store immediately before a given {@link Tree}. + * + * @return the store immediately before a given {@link Tree} + */ + public Store getStoreBefore(Tree tree) { + if (!analysis.isRunning()) { + return flowResult.getStoreBefore(tree); + } + Set nodes = analysis.getNodesForTree(tree); + if (nodes != null) { + return getStoreBefore(nodes); } else { - regularExitStores.put(ct, initializationStaticStore); + return flowResult.getStoreBefore(tree); + } + } + + /** + * Returns the store immediately before a given Set of {@link Node}s. + * + * @return the store immediately before a given Set of {@link Node}s + */ + public Store getStoreBefore(Set nodes) { + Store merge = null; + for (Node aNode : nodes) { + Store s = getStoreBefore(aNode); + if (merge == null) { + merge = s; + } else if (s != null) { + merge = merge.leastUpperBound(s); + } + } + return merge; + } + + /** + * Returns the store immediately before a given node. + * + * @param node a node whose pre-store to return + * @return the store immediately before {@code node} + */ + public @Nullable Store getStoreBefore(Node node) { + if (!analysis.isRunning()) { + return flowResult.getStoreBefore(node); + } + TransferInput prevStore = analysis.getInput(node.getBlock()); + if (prevStore == null) { + return null; + } + Store store = + AnalysisResult.runAnalysisFor( + node, + Analysis.BeforeOrAfter.BEFORE, + prevStore, + analysis.getNodeValues(), + flowResultAnalysisCaches); + return store; + } + + /** + * Returns the store immediately after a given tree. + * + *

          May return null; for example, after a {@code return} statement. + * + * @param tree the tree whose post-store to return + * @return the store immediately after a given tree + */ + public @Nullable Store getStoreAfter(Tree tree) { + if (!analysis.isRunning()) { + return flowResult.getStoreAfter(tree); + } + Set nodes = analysis.getNodesForTree(tree); + return getStoreAfter(nodes); + } + + /** + * Returns the store immediately after a given set of nodes. + * + * @param nodes the nodes whose post-stores to LUB + * @return the LUB of the stores store immediately after {@code nodes} + */ + public Store getStoreAfter(Set nodes) { + Store merge = null; + for (Node node : nodes) { + Store s = getStoreAfter(node); + if (merge == null) { + merge = s; + } else if (s != null) { + merge = merge.leastUpperBound(s); + } + } + return merge; + } + + /** + * Returns the store immediately after a given {@link Node}. + * + * @param node node after which the store is returned + * @return the store immediately after a given {@link Node} + */ + public Store getStoreAfter(Node node) { + if (!analysis.isRunning()) { + return flowResult.getStoreAfter(node); + } + Store res = + AnalysisResult.runAnalysisFor( + node, + Analysis.BeforeOrAfter.AFTER, + analysis.getInput(node.getBlock()), + analysis.getNodeValues(), + flowResultAnalysisCaches); + return res; + } + + /** + * See {@link org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree)}. + * + * @param tree a tree + * @return the {@link Node}s for a given {@link Tree} + * @see org.checkerframework.dataflow.analysis.AnalysisResult#getNodesForTree(Tree) + */ + public @Nullable Set getNodesForTree(Tree tree) { + return flowResult.getNodesForTree(tree); + } + + /** + * Return the first {@link Node} for a given {@link Tree} that has class {@code kind}. + * + *

          You probably don't want to use this function: iterate over the result of {@link + * #getNodesForTree(Tree)} yourself or ask for a conservative approximation of the store using + * {@link #getStoreBefore(Tree)} or {@link #getStoreAfter(Tree)}. This method is for code that + * uses a {@link Node} in a rather unusual way. Callers should probably be rewritten to not use + * a {@link Node} at all. + * + * @param the class of the node to return + * @param tree a tree in which to search for a node of class {@code kind} + * @param kind the class of the node to return + * @return the first {@link Node} for a given {@link Tree} that has class {@code kind} + * @see #getNodesForTree(Tree) + * @see #getStoreBefore(Tree) + * @see #getStoreAfter(Tree) + */ + public @Nullable T getFirstNodeOfKindForTree(Tree tree, Class kind) { + Set nodes = getNodesForTree(tree); + for (Node node : nodes) { + if (node.getClass() == kind) { + return kind.cast(node); + } + } + return null; + } + + /** + * Returns the value of effectively final local variables. + * + * @return the value of effectively final local variables + */ + public Map getFinalLocalValues() { + return flowResult.getFinalLocalValues(); + } + + /** + * Returns true if the receiver of a method or constructor might not be fully initialized. + * + * @param methodDeclTree the declaration of the method or constructor + * @return true if the receiver of a method or constructor might not be fully initialized + */ + @Pure + public boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { + return TreeUtils.isConstructor(methodDeclTree); + } + + /** + * Perform a org.checkerframework.dataflow analysis over a single class tree and its nested + * classes. + * + * @param classTree the class to analyze + */ + protected void performFlowAnalysis(ClassTree classTree) { + if (flowResult == null) { + this.regularExitStores.clear(); + this.exceptionalExitStores.clear(); + this.returnStatementStores.clear(); + this.flowResult = new AnalysisResult<>(flowResultAnalysisCaches); + } + + // no need to scan annotations + if (classTree.getKind() == Tree.Kind.ANNOTATION_TYPE) { + // Mark finished so that default annotations will be applied. + scannedClasses.put(classTree, ScanState.FINISHED); + return; } - } finally { - setVisitorTreePath(preTreePath); - } - scannedClasses.put(ct, ScanState.FINISHED); + // class trees and their initial stores + Queue> classQueue = new ArrayDeque<>(); + List> fieldValues = new ArrayList<>(); + + // No captured store for top-level classes. + classQueue.add(IPair.of(classTree, null)); + + while (!classQueue.isEmpty()) { + IPair qel = classQueue.remove(); + ClassTree ct = qel.first; + Store capturedStore = qel.second; + scannedClasses.put(ct, ScanState.IN_PROGRESS); + + TreePath preTreePath = getVisitorTreePath(); + + // Don't call AnnotatedTypeFactory#getPath, because it uses visitorTreePath. + setVisitorTreePath(TreePath.getPath(this.getRoot(), ct)); + + // start with the captured store as initialization store + initializationStaticStore = capturedStore; + initializationStore = capturedStore; + + // The store is null if the lambda is unreachable. + Queue> lambdaQueue = new ArrayDeque<>(); + + // Queue up classes (for top-level `while` loop) and methods (for within this `try` + // construct); analyze top-level blocks and variable initializers as they are + // encountered. + try { + List methods = new ArrayList<>(); + List members = ct.getMembers(); + if (!Ordering.from(sortVariablesFirst).isOrdered(members)) { + members = new ArrayList<>(members); + // Process variables before methods, so all field initializers are observed + // before the constructor is analyzed and reports uninitialized variables. + members.sort(sortVariablesFirst); + } + for (Tree m : members) { + switch (TreeUtils.getKindRecordAsClass(m)) { + case CLASS: // Including RECORD + case ANNOTATION_TYPE: + case INTERFACE: + case ENUM: + // Visit inner and nested class trees. + // TODO: Use no store for them? What can be captured? + classQueue.add(IPair.of((ClassTree) m, capturedStore)); + break; + case METHOD: + MethodTree mt = (MethodTree) m; + + // Skip abstract and native methods because they have no body. + Set flags = mt.getModifiers().getFlags(); + if (flags.contains(Modifier.ABSTRACT) + || flags.contains(Modifier.NATIVE)) { + break; + } + // Abstract methods in an interface have a null body but do not have an + // ABSTRACT flag. + if (mt.getBody() == null) { + break; + } + + // Wait with scanning the method until all other members + // have been processed. + CFGMethod met = new CFGMethod(mt, ct); + methods.add(met); + break; + case VARIABLE: + VariableTree vt = (VariableTree) m; + ExpressionTree initializer = vt.getInitializer(); + AnnotatedTypeMirror declaredType = getAnnotatedTypeLhs(vt); + Value declaredValue = analysis.createAbstractValue(declaredType); + FieldAccess fieldExpr = + (FieldAccess) JavaExpression.fromVariableTree(vt); + // analyze initializer if present + if (initializer != null) { + boolean isStatic = + vt.getModifiers().getFlags().contains(Modifier.STATIC); + analyze( + classQueue, + lambdaQueue, + new CFGStatement(vt, ct), + fieldValues, + classTree, + true, + true, + isStatic, + capturedStore); + Value initializerValue = flowResult.getValue(initializer); + if (initializerValue != null) { + fieldValues.add( + new FieldInitialValue<>( + fieldExpr, declaredValue, initializerValue)); + break; + } + } + fieldValues.add( + new FieldInitialValue<>(fieldExpr, declaredValue, null)); + break; + case BLOCK: + BlockTree b = (BlockTree) m; + analyze( + classQueue, + lambdaQueue, + new CFGStatement(b, ct), + fieldValues, + ct, + true, + true, + b.isStatic(), + capturedStore); + break; + default: + assert false : "Unexpected member: " + m.getKind(); + break; + } + } + + // Now analyze all methods. + // TODO: at this point, we don't have any information about + // fields of superclasses. + for (CFGMethod met : methods) { + analyze( + classQueue, + lambdaQueue, + met, + fieldValues, + classTree, + TreeUtils.isConstructor(met.getMethod()), + false, + false, + capturedStore); + } + + while (!lambdaQueue.isEmpty()) { + IPair lambdaPair = lambdaQueue.poll(); + MethodTree mt = + (MethodTree) + TreePathUtil.enclosingOfKind( + getPath(lambdaPair.first), Tree.Kind.METHOD); + analyze( + classQueue, + lambdaQueue, + new CFGLambda(lambdaPair.first, classTree, mt), + fieldValues, + classTree, + false, + false, + false, + lambdaPair.second); + } + + // By convention we store the static initialization store as the regular exit + // store of the class node, so that it can later be used to check + // that all fields are initialized properly. + // See InitializationVisitor.visitClass(). + if (initializationStaticStore == null) { + regularExitStores.put(ct, emptyStore); + } else { + regularExitStores.put(ct, initializationStaticStore); + } + } finally { + setVisitorTreePath(preTreePath); + } + + scannedClasses.put(ct, ScanState.FINISHED); + } } - } - /** Sorts a list of trees with the variables first. */ - private final Comparator sortVariablesFirst = - (t1, t2) -> { - boolean variable1 = t1.getKind() == Tree.Kind.VARIABLE; - boolean variable2 = t2.getKind() == Tree.Kind.VARIABLE; - if (variable1 && !variable2) { - return -1; - } else if (!variable1 && variable2) { - return 1; + /** Sorts a list of trees with the variables first. */ + private final Comparator sortVariablesFirst = + (t1, t2) -> { + boolean variable1 = t1.getKind() == Tree.Kind.VARIABLE; + boolean variable2 = t2.getKind() == Tree.Kind.VARIABLE; + if (variable1 && !variable2) { + return -1; + } else if (!variable1 && variable2) { + return 1; + } else { + return 0; + } + }; + + /** + * Analyze the AST {@code ast} and store the result. Additional operations that should be + * performed after analysis should be implemented in {@link #postAnalyze(ControlFlowGraph)}. + * + * @param classQueue the queue for encountered class trees and their initial stores + * @param lambdaQueue the queue for encountered lambda expression trees and their initial stores + * @param ast the AST to analyze + * @param fieldValues the abstract values for all fields of the same class + * @param currentClass the class we are currently looking at + * @param isInitializationCode are we analyzing a (static/non-static) initializer block of a + * class + * @param updateInitializationStore should the initialization store be updated + * @param isStatic are we analyzing a static construct + * @param capturedStore the input Store to use for captured variables, e.g. in a lambda + * @see #postAnalyze(org.checkerframework.dataflow.cfg.ControlFlowGraph) + */ + protected void analyze( + Queue> classQueue, + Queue> lambdaQueue, + UnderlyingAST ast, + List> fieldValues, + ClassTree currentClass, + boolean isInitializationCode, + boolean updateInitializationStore, + boolean isStatic, + @Nullable Store capturedStore) { + ControlFlowGraph cfg = + CFCFGBuilder.build(this.getRoot(), ast, checker, this, processingEnv); + /* + cfg.getAllNodes(this::isIgnoredExceptionType) + .forEach( + node -> { + if (node.getTree() != null) { + reachableNodes.add(node.getTree()); + } + }); + */ + if (isInitializationCode) { + Store initStore = !isStatic ? initializationStore : initializationStaticStore; + if (initStore != null) { + // we have already seen initialization code and analyzed it, and + // the analysis ended with the store initStore. + // use it to start the next analysis. + transfer.setFixedInitialStore(initStore); + } else { + transfer.setFixedInitialStore(capturedStore); + } } else { - return 0; - } - }; - - /** - * Analyze the AST {@code ast} and store the result. Additional operations that should be - * performed after analysis should be implemented in {@link #postAnalyze(ControlFlowGraph)}. - * - * @param classQueue the queue for encountered class trees and their initial stores - * @param lambdaQueue the queue for encountered lambda expression trees and their initial stores - * @param ast the AST to analyze - * @param fieldValues the abstract values for all fields of the same class - * @param currentClass the class we are currently looking at - * @param isInitializationCode are we analyzing a (static/non-static) initializer block of a class - * @param updateInitializationStore should the initialization store be updated - * @param isStatic are we analyzing a static construct - * @param capturedStore the input Store to use for captured variables, e.g. in a lambda - * @see #postAnalyze(org.checkerframework.dataflow.cfg.ControlFlowGraph) - */ - protected void analyze( - Queue> classQueue, - Queue> lambdaQueue, - UnderlyingAST ast, - List> fieldValues, - ClassTree currentClass, - boolean isInitializationCode, - boolean updateInitializationStore, - boolean isStatic, - @Nullable Store capturedStore) { - ControlFlowGraph cfg = CFCFGBuilder.build(this.getRoot(), ast, checker, this, processingEnv); - /* - cfg.getAllNodes(this::isIgnoredExceptionType) - .forEach( - node -> { - if (node.getTree() != null) { - reachableNodes.add(node.getTree()); - } - }); - */ - if (isInitializationCode) { - Store initStore = !isStatic ? initializationStore : initializationStaticStore; - if (initStore != null) { - // we have already seen initialization code and analyzed it, and - // the analysis ended with the store initStore. - // use it to start the next analysis. - transfer.setFixedInitialStore(initStore); - } else { - transfer.setFixedInitialStore(capturedStore); - } - } else { - transfer.setFixedInitialStore(capturedStore); - } - analysis.performAnalysis(cfg, fieldValues); - AnalysisResult result = analysis.getResult(); - - // store result - flowResult.combine(result); - if (ast.getKind() == UnderlyingAST.Kind.METHOD) { - // store exit store (for checking postconditions) - CFGMethod mast = (CFGMethod) ast; - MethodTree method = mast.getMethod(); - Store regularExitStore = analysis.getRegularExitStore(); - if (regularExitStore != null) { - regularExitStores.put(method, regularExitStore); - } - Store exceptionalExitStore = analysis.getExceptionalExitStore(); - if (exceptionalExitStore != null) { - exceptionalExitStores.put(method, exceptionalExitStore); - } - returnStatementStores.put(method, analysis.getReturnStatementStores()); - } else if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { - CFGStatement block = (CFGStatement) ast; - Store regularExitStore = analysis.getRegularExitStore(); - if (regularExitStore != null) { - regularExitStores.put(block.getCode(), regularExitStore); - } - Store exceptionalExitStore = analysis.getExceptionalExitStore(); - if (exceptionalExitStore != null) { - exceptionalExitStores.put(block.getCode(), exceptionalExitStore); - } - } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { - // TODO: Postconditions? + transfer.setFixedInitialStore(capturedStore); + } + analysis.performAnalysis(cfg, fieldValues); + AnalysisResult result = analysis.getResult(); + + // store result + flowResult.combine(result); + if (ast.getKind() == UnderlyingAST.Kind.METHOD) { + // store exit store (for checking postconditions) + CFGMethod mast = (CFGMethod) ast; + MethodTree method = mast.getMethod(); + Store regularExitStore = analysis.getRegularExitStore(); + if (regularExitStore != null) { + regularExitStores.put(method, regularExitStore); + } + Store exceptionalExitStore = analysis.getExceptionalExitStore(); + if (exceptionalExitStore != null) { + exceptionalExitStores.put(method, exceptionalExitStore); + } + returnStatementStores.put(method, analysis.getReturnStatementStores()); + } else if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { + CFGStatement block = (CFGStatement) ast; + Store regularExitStore = analysis.getRegularExitStore(); + if (regularExitStore != null) { + regularExitStores.put(block.getCode(), regularExitStore); + } + Store exceptionalExitStore = analysis.getExceptionalExitStore(); + if (exceptionalExitStore != null) { + exceptionalExitStores.put(block.getCode(), exceptionalExitStore); + } + } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { + // TODO: Postconditions? + + CFGLambda block = (CFGLambda) ast; + Store regularExitStore = analysis.getRegularExitStore(); + if (regularExitStore != null) { + regularExitStores.put(block.getCode(), regularExitStore); + } + Store exceptionalExitStore = analysis.getExceptionalExitStore(); + if (exceptionalExitStore != null) { + exceptionalExitStores.put(block.getCode(), exceptionalExitStore); + } + } else { + assert false : "Unexpected AST kind: " + ast.getKind(); + } - CFGLambda block = (CFGLambda) ast; - Store regularExitStore = analysis.getRegularExitStore(); - if (regularExitStore != null) { - regularExitStores.put(block.getCode(), regularExitStore); - } - Store exceptionalExitStore = analysis.getExceptionalExitStore(); - if (exceptionalExitStore != null) { - exceptionalExitStores.put(block.getCode(), exceptionalExitStore); - } - } else { - assert false : "Unexpected AST kind: " + ast.getKind(); + if (isInitializationCode && updateInitializationStore) { + Store newInitStore = analysis.getRegularExitStore(); + if (!isStatic) { + initializationStore = newInitStore; + } else { + initializationStaticStore = newInitStore; + } + } + + // add classes declared in CFG + for (ClassTree cls : cfg.getDeclaredClasses()) { + classQueue.add(IPair.of(cls, getStoreBefore(cls))); + } + // add lambdas declared in CFG + for (LambdaExpressionTree lambda : cfg.getDeclaredLambdas()) { + lambdaQueue.add(IPair.of(lambda, getStoreBefore(lambda))); + } + + postAnalyze(cfg); } - if (isInitializationCode && updateInitializationStore) { - Store newInitStore = analysis.getRegularExitStore(); - if (!isStatic) { - initializationStore = newInitStore; - } else { - initializationStaticStore = newInitStore; - } + /** + * Returns true if {@code typeMirror} is an exception type that should be ignored. + * + * @param typeMirror an exception type + * @return true if {@code typeMirror} is an exception type that should be ignored + */ + public boolean isIgnoredExceptionType(TypeMirror typeMirror) { + return false; } - // add classes declared in CFG - for (ClassTree cls : cfg.getDeclaredClasses()) { - classQueue.add(IPair.of(cls, getStoreBefore(cls))); - } - // add lambdas declared in CFG - for (LambdaExpressionTree lambda : cfg.getDeclaredLambdas()) { - lambdaQueue.add(IPair.of(lambda, getStoreBefore(lambda))); - } - - postAnalyze(cfg); - } - - /** - * Returns true if {@code typeMirror} is an exception type that should be ignored. - * - * @param typeMirror an exception type - * @return true if {@code typeMirror} is an exception type that should be ignored - */ - public boolean isIgnoredExceptionType(TypeMirror typeMirror) { - return false; - } - - /** - * Perform any additional operations on a CFG. Called once per CFG, after the CFG has been - * analyzed by {@link #analyze(Queue, Queue, UnderlyingAST, List, ClassTree, boolean, boolean, - * boolean, CFAbstractStore)}. This method can be used to initialize additional state or to - * perform any analyses that are easier to perform on the CFG instead of the AST. - * - * @param cfg the CFG - * @see #analyze(java.util.Queue, java.util.Queue, - * org.checkerframework.dataflow.cfg.UnderlyingAST, java.util.List, - * com.sun.source.tree.ClassTree, boolean, boolean, boolean, - * org.checkerframework.framework.flow.CFAbstractStore) - */ - protected void postAnalyze(ControlFlowGraph cfg) { - handleCFGViz(cfg); - } - - /** Whether handling CFG visualization is necessary. */ - private final boolean handleCFGViz; - - /** - * Handle the visualization of the CFG, if necessary. - * - * @param cfg the CFG - */ - protected void handleCFGViz(ControlFlowGraph cfg) { - if (handleCFGViz) { - getCFGVisualizer().visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); - } - } - - /** - * Returns the type of the left-hand side of an assignment without applying local variable - * defaults to type variables. - * - *

          The type variables that are types of local variables are defaulted to top so that they can - * be refined by dataflow. When these types are used as context during type argument inference, - * this default is too conservative. So this method is used instead of {@link - * GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree)}. - * - *

          {@link - * org.checkerframework.framework.util.typeinference8.types.InferenceFactory#assignedToVariable(AnnotatedTypeFactory, - * Tree)} explains why a different type is used. - * - * @param lhsTree left-hand side of an assignment - * @return AnnotatedTypeMirror of {@code lhsTree} - */ - public AnnotatedTypeMirror getAnnotatedTypeLhsNoTypeVarDefault(Tree lhsTree) { - boolean old = this.shouldDefaultTypeVarLocals; - shouldDefaultTypeVarLocals = false; - AnnotatedTypeMirror type = getAnnotatedTypeLhs(lhsTree); - this.shouldDefaultTypeVarLocals = old; - return type; - } - - /** - * Whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This can be used by subclasses - * whenever the type of expression differs depending on whether it occurs on the left- or - * right-hand side of an assignment (e.g., in the presence of type refinements or for - * uninitialized fields in the Initialization Checker.) - * - * @see #isComputingAnnotatedTypeMirrorOfLhs() - */ - private boolean computingAnnotatedTypeMirrorOfLhs = false; - - /** - * Returns whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This controls which - * hierarchies' qualifiers are changed based on the receiver type and the declared annotations for - * a field. - * - * @return whether {@link #getAnnotatedTypeLhs(Tree)} is running right now - * @see #getAnnotatedTypeLhs(Tree) - */ - public boolean isComputingAnnotatedTypeMirrorOfLhs() { - return computingAnnotatedTypeMirrorOfLhs; - } - - /** - * Returns the type of a JavaExpression {@code expr} if it were evaluated before a tree {@code - * tree}. - * - *

          This is used by {@link BaseTypeVisitor#visitMethodInvocation(MethodInvocationTree, Void)} to - * check the preconditions of method calls. - * - * @param expr the expression to type - * @param tree a tree - * @return the type of {@code expr} if it were evaluated before tree {@code tree} - */ - public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { - Store store = getStoreBefore(tree); - Value value = null; - if (CFAbstractStore.canInsertJavaExpression(expr)) { - value = store.getValue(expr); - } - Set annos = null; - if (value != null) { - annos = value.getAnnotations(); - } else { - // If there is no information in the store (possible if e.g., no refinement - // of the field has occurred), use top instead of automatically - // issuing a warning. This is not perfectly precise: for example, - // if jeExpr is a field it would be more precise to use the field's - // declared type rather than top. However, doing so would be unsound - // in at least three circumstances where the type of the field depends - // on the type of the receiver: (1) all fields in Nullness Checker, - // because of possibility that the receiver is under initialization, - // (2) polymorphic fields, and (3) fields whose type is a type variable. - // Using top here instead means that the method is always sound; - // a subclass can then override it with a more precise implementation. - annos = getQualifierHierarchy().getTopAnnotations(); - } - - AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(expr.getType(), this, false); - res.addAnnotations(annos); - return res; - } - - /** - * Returns the type of a left-hand side of an assignment. - * - *

          The default implementation returns the type without considering dataflow type refinement. - * Subclass can override this method and add additional logic for computing the type of a LHS. - * - * @param lhsTree left-hand side of an assignment - * @return AnnotatedTypeMirror of {@code lhsTree} - */ - public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { - boolean oldUseFlow = useFlow; - boolean oldShouldCache = shouldCache; - boolean oldComputingAnnotatedTypeMirrorOfLhs = computingAnnotatedTypeMirrorOfLhs; - useFlow = false; - // Don't cache the result because getAnnotatedType(lhsTree) could - // be called from elsewhere and would expect flow-sensitive type refinements. - shouldCache = false; - computingAnnotatedTypeMirrorOfLhs = true; - - AnnotatedTypeMirror res; - switch (lhsTree.getKind()) { - case VARIABLE: - boolean isVarTree = TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) lhsTree); - if (isVarTree) { - // If this variable is declared using `var`, re-enable caching to avoid - // re-computing the initializer expression type. - shouldCache = oldShouldCache; - } - res = getAnnotatedType(lhsTree); - // Value of shouldCache no longer used below, so no need to reset. - break; - case IDENTIFIER: - Element elt = TreeUtils.elementFromTree(lhsTree); - if (elt != null) { - Tree decl = declarationFromElement(elt); - if (decl != null - && decl.getKind() == Tree.Kind.VARIABLE - && TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) decl)) { - // If this identifier accesses a variable that was declared using `var`, - // re-enable caching to avoid re-computing the initializer expression type. - shouldCache = oldShouldCache; - } - } - res = getAnnotatedType(lhsTree); - // Value of shouldCache no longer used below, so no need to reset. - break; - case MEMBER_SELECT: - case ARRAY_ACCESS: - res = getAnnotatedType(lhsTree); - break; - case PARENTHESIZED: - res = getAnnotatedTypeLhs(TreeUtils.withoutParens((ExpressionTree) lhsTree)); - break; - default: - if (TreeUtils.isTypeTree(lhsTree)) { - // lhsTree is a type tree at the pseudo assignment of a returned expression to - // declared return type. - res = getAnnotatedType(lhsTree); + /** + * Perform any additional operations on a CFG. Called once per CFG, after the CFG has been + * analyzed by {@link #analyze(Queue, Queue, UnderlyingAST, List, ClassTree, boolean, boolean, + * boolean, CFAbstractStore)}. This method can be used to initialize additional state or to + * perform any analyses that are easier to perform on the CFG instead of the AST. + * + * @param cfg the CFG + * @see #analyze(java.util.Queue, java.util.Queue, + * org.checkerframework.dataflow.cfg.UnderlyingAST, java.util.List, + * com.sun.source.tree.ClassTree, boolean, boolean, boolean, + * org.checkerframework.framework.flow.CFAbstractStore) + */ + protected void postAnalyze(ControlFlowGraph cfg) { + handleCFGViz(cfg); + } + + /** Whether handling CFG visualization is necessary. */ + private final boolean handleCFGViz; + + /** + * Handle the visualization of the CFG, if necessary. + * + * @param cfg the CFG + */ + protected void handleCFGViz(ControlFlowGraph cfg) { + if (handleCFGViz) { + getCFGVisualizer().visualizeWithAction(cfg, cfg.getEntryBlock(), analysis); + } + } + + /** + * Returns the type of the left-hand side of an assignment without applying local variable + * defaults to type variables. + * + *

          The type variables that are types of local variables are defaulted to top so that they can + * be refined by dataflow. When these types are used as context during type argument inference, + * this default is too conservative. So this method is used instead of {@link + * GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree)}. + * + *

          {@link + * org.checkerframework.framework.util.typeinference8.types.InferenceFactory#assignedToVariable(AnnotatedTypeFactory, + * Tree)} explains why a different type is used. + * + * @param lhsTree left-hand side of an assignment + * @return AnnotatedTypeMirror of {@code lhsTree} + */ + public AnnotatedTypeMirror getAnnotatedTypeLhsNoTypeVarDefault(Tree lhsTree) { + boolean old = this.shouldDefaultTypeVarLocals; + shouldDefaultTypeVarLocals = false; + AnnotatedTypeMirror type = getAnnotatedTypeLhs(lhsTree); + this.shouldDefaultTypeVarLocals = old; + return type; + } + + /** + * Whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This can be used by + * subclasses whenever the type of expression differs depending on whether it occurs on the + * left- or right-hand side of an assignment (e.g., in the presence of type refinements or for + * uninitialized fields in the Initialization Checker.) + * + * @see #isComputingAnnotatedTypeMirrorOfLhs() + */ + private boolean computingAnnotatedTypeMirrorOfLhs = false; + + /** + * Returns whether {@link #getAnnotatedTypeLhs(Tree)} is running right now. This controls which + * hierarchies' qualifiers are changed based on the receiver type and the declared annotations + * for a field. + * + * @return whether {@link #getAnnotatedTypeLhs(Tree)} is running right now + * @see #getAnnotatedTypeLhs(Tree) + */ + public boolean isComputingAnnotatedTypeMirrorOfLhs() { + return computingAnnotatedTypeMirrorOfLhs; + } + + /** + * Returns the type of a JavaExpression {@code expr} if it were evaluated before a tree {@code + * tree}. + * + *

          This is used by {@link BaseTypeVisitor#visitMethodInvocation(MethodInvocationTree, Void)} + * to check the preconditions of method calls. + * + * @param expr the expression to type + * @param tree a tree + * @return the type of {@code expr} if it were evaluated before tree {@code tree} + */ + public AnnotatedTypeMirror getAnnotatedTypeBefore(JavaExpression expr, ExpressionTree tree) { + Store store = getStoreBefore(tree); + Value value = null; + if (CFAbstractStore.canInsertJavaExpression(expr)) { + value = store.getValue(expr); + } + Set annos = null; + if (value != null) { + annos = value.getAnnotations(); } else { - throw new BugInCF( - "GenericAnnotatedTypeFactory: Unexpected tree passed to" - + " getAnnotatedTypeLhs. lhsTree: " - + lhsTree - + " Tree.Kind: " - + lhsTree.getKind()); - } - } - useFlow = oldUseFlow; - shouldCache = oldShouldCache; - computingAnnotatedTypeMirrorOfLhs = oldComputingAnnotatedTypeMirrorOfLhs; - return res; - } - - /** - * As long as everUseFlow is true, always enable flow refinement for the receiver. This method is - * an implementation detail and visible inside the package only. See the comment in this testcase - * for more details framework/tests/viewpointtest/TestGetAnnotatedLhs.java. - * - * @see #getAnnotatedType(Tree) - * @see #getAnnotatedTypeLhs(Tree) - * @param tree an expression tree - * @return the refined type of the expression tree - */ - /*package-private*/ AnnotatedTypeMirror getAnnotatedTypeWithReceiverRefinement(Tree tree) { - boolean oldUseFlow = useFlow; - useFlow = everUseFlow; - AnnotatedTypeMirror result = getAnnotatedType(tree); - useFlow = oldUseFlow; - return result; - } - - /** - * Returns the type of a varargs array of a method invocation or a constructor invocation. Returns - * null only if private field {@code useFlow} is false. - * - * @param tree a method invocation or a constructor invocation - * @return AnnotatedTypeMirror of varargs array for a method or constructor invocation {@code - * tree}; returns null if private field {@code useFlow} is false - */ - public @Nullable AnnotatedTypeMirror getAnnotatedTypeVarargsArray(Tree tree) { - if (!useFlow) { - return null; - } - - // Get the synthetic NewArray tree that dataflow creates as the last argument of a call to a - // vararg method. Do this by getting the MethodInvocationNode to which "tree" maps. The last - // argument node of the MethodInvocationNode stores the synthetic NewArray tree. - List args; - switch (tree.getKind()) { - case METHOD_INVOCATION: - args = getFirstNodeOfKindForTree(tree, MethodInvocationNode.class).getArguments(); - break; - case NEW_CLASS: - args = getFirstNodeOfKindForTree(tree, ObjectCreationNode.class).getArguments(); - break; - default: - throw new BugInCF("Unexpected kind of tree: " + tree); - } - - assert !args.isEmpty() : "Arguments are empty"; - Node varargsArray = args.get(args.size() - 1); - AnnotatedTypeMirror varargtype = getAnnotatedType(varargsArray.getTree()); - return varargtype; - } - - /** - * Returns the type of {@code v + 1} or {@code v - 1} where {@code v} is the expression in the - * postfixed increment or decrement expression. - * - * @param tree a postfixed increment or decrement tree - * @return AnnotatedTypeMirror of a right-hand side of an assignment for unary operation - */ - public AnnotatedTypeMirror getAnnotatedTypeRhsUnaryAssign(UnaryTree tree) { - if (!useFlow) { - return getAnnotatedType(tree); - } - BinaryTree binaryTree = flowResult.getPostfixBinaryTree(tree); - return getAnnotatedType(binaryTree); - } - - @Override - protected ParameterizedExecutableType constructorFromUse( - NewClassTree tree, boolean inferTypeArgs) { - ParameterizedExecutableType mType = super.constructorFromUse(tree, inferTypeArgs); - AnnotatedExecutableType method = mType.executableType; - dependentTypesHelper.atConstructorInvocation(method, tree); - return mType; - } - - @Override - protected void constructorFromUsePreSubstitution( - NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { - if (resolvePolyQuals) { - poly.resolve(tree, type); - } - } - - @Override - public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { - AnnotatedTypeMirror returnType = super.getMethodReturnType(m); - dependentTypesHelper.atMethodBody(returnType, m); - return returnType; - } - - @Override - public void addDefaultAnnotations(AnnotatedTypeMirror type) { - addAnnotationsFromDefaultForType(null, type); - typeAnnotator.visit(type, null); - defaults.annotate((Element) null, type); - } - - /** - * Removes all primary annotations on a copy of the type and calculates the default annotations - * that apply to the copied type, without type refinements. - * - * @param tree tree where the type is used - * @param type type to determine the defaulted version for - * @return the annotated type mirror with default annotations - */ - public AnnotatedTypeMirror getDefaultAnnotations(Tree tree, AnnotatedTypeMirror type) { - AnnotatedTypeMirror copy = type.deepCopy(); - copy.removeAnnotations(type.getAnnotations()); - addComputedTypeAnnotationsWithoutFlow(tree, copy); - return copy; - } - - /** - * Like {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding implementations - * typically simply pass the boolean to calls to super. - * - * @param tree an AST node - * @param type the type obtained from tree - * @param iUseFlow whether to use information from dataflow analysis - * @deprecated use {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} or {@link - * #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror)} if you want to add - * computed type annotations without using flow information - */ - @Deprecated // 2024-07-07 - @SuppressWarnings("unused") - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { - addComputedTypeAnnotationsWithoutFlow(tree, type); - } - - /** - * A helper method to add computed type annotations to a type without using flow information. - * - *

          This method is final; override {@link #addComputedTypeAnnotations(Tree, - * AnnotatedTypeMirror)} instead. - * - * @param tree an AST node - * @param type the type obtained from tree - * @see #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror) - */ - protected final void addComputedTypeAnnotationsWithoutFlow(Tree tree, AnnotatedTypeMirror type) { - boolean oldUseflow = useFlow; - useFlow = false; - addComputedTypeAnnotations(tree, type); - useFlow = oldUseflow; - } - - /** - * {@inheritDoc} - * - *

          This method adds defaults and flow-sensitive type refinements. - * - * @see #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror) - */ - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - if (this.getRoot() == null && ajavaTypes.isParsing()) { - return; - } - assert this.getRoot() != null - : "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: " - + " root needs to be set when used on trees; factory: " - + this.getClass(); - - String thisClass = null; - String treeString = null; - if (debug) { - thisClass = this.getClass().getSimpleName(); - if (thisClass.endsWith("AnnotatedTypeFactory")) { - thisClass = thisClass.substring(0, thisClass.length() - "AnnotatedTypeFactory".length()); - } - treeString = TreeUtils.toStringTruncated(tree, 60); - } - log( - "%s GATF.addComputedTypeAnnotations#1(%s, %s, %s)%n", - thisClass, treeString, type, this.useFlow); - if (!TreeUtils.isExpressionTree(tree)) { - // Don't apply defaults to expressions. Their types may be computed from subexpressions - // in treeAnnotator. - addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); - log("%s GATF.addComputedTypeAnnotations#2(%s, %s)%n", thisClass, treeString, type); - } - applyQualifierParameterDefaults(tree, type); - log("%s GATF.addComputedTypeAnnotations#3(%s, %s)%n", thisClass, treeString, type); - treeAnnotator.visit(tree, type); - log( - "%s GATF.addComputedTypeAnnotations#4(%s, %s)%n treeAnnotator=%s%n", - thisClass, treeString, type, treeAnnotator); - if (TreeUtils.isExpressionTree(tree)) { - // If a tree annotator did not add a type, add the DefaultForUse default. - addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); - log("%s GATF.addComputedTypeAnnotations#5(%s, %s)%n", thisClass, treeString, type); - } - typeAnnotator.visit(type, null); - log( - "%s GATF.addComputedTypeAnnotations#6(%s, %s)%n typeAnnotator=%s%n", - thisClass, treeString, type, typeAnnotator); - defaults.annotate(tree, type); - log("%s GATF.addComputedTypeAnnotations#7(%s, %s)%n", thisClass, treeString, type); - - if (this.useFlow) { - Value inferred = getInferredValueFor(tree); - if (inferred != null) { - applyInferredAnnotations(type, inferred); + // If there is no information in the store (possible if e.g., no refinement + // of the field has occurred), use top instead of automatically + // issuing a warning. This is not perfectly precise: for example, + // if jeExpr is a field it would be more precise to use the field's + // declared type rather than top. However, doing so would be unsound + // in at least three circumstances where the type of the field depends + // on the type of the receiver: (1) all fields in Nullness Checker, + // because of possibility that the receiver is under initialization, + // (2) polymorphic fields, and (3) fields whose type is a type variable. + // Using top here instead means that the method is always sound; + // a subclass can then override it with a more precise implementation. + annos = getQualifierHierarchy().getTopAnnotations(); + } + + AnnotatedTypeMirror res = AnnotatedTypeMirror.createType(expr.getType(), this, false); + res.addAnnotations(annos); + return res; + } + + /** + * Returns the type of a left-hand side of an assignment. + * + *

          The default implementation returns the type without considering dataflow type refinement. + * Subclass can override this method and add additional logic for computing the type of a LHS. + * + * @param lhsTree left-hand side of an assignment + * @return AnnotatedTypeMirror of {@code lhsTree} + */ + public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { + boolean oldUseFlow = useFlow; + boolean oldShouldCache = shouldCache; + boolean oldComputingAnnotatedTypeMirrorOfLhs = computingAnnotatedTypeMirrorOfLhs; + useFlow = false; + // Don't cache the result because getAnnotatedType(lhsTree) could + // be called from elsewhere and would expect flow-sensitive type refinements. + shouldCache = false; + computingAnnotatedTypeMirrorOfLhs = true; + + AnnotatedTypeMirror res; + switch (lhsTree.getKind()) { + case VARIABLE: + boolean isVarTree = + TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) lhsTree); + if (isVarTree) { + // If this variable is declared using `var`, re-enable caching to avoid + // re-computing the initializer expression type. + shouldCache = oldShouldCache; + } + res = getAnnotatedType(lhsTree); + // Value of shouldCache no longer used below, so no need to reset. + break; + case IDENTIFIER: + Element elt = TreeUtils.elementFromTree(lhsTree); + if (elt != null) { + Tree decl = declarationFromElement(elt); + if (decl != null + && decl.getKind() == Tree.Kind.VARIABLE + && TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) decl)) { + // If this identifier accesses a variable that was declared using `var`, + // re-enable caching to avoid re-computing the initializer expression type. + shouldCache = oldShouldCache; + } + } + res = getAnnotatedType(lhsTree); + // Value of shouldCache no longer used below, so no need to reset. + break; + case MEMBER_SELECT: + case ARRAY_ACCESS: + res = getAnnotatedType(lhsTree); + break; + case PARENTHESIZED: + res = getAnnotatedTypeLhs(TreeUtils.withoutParens((ExpressionTree) lhsTree)); + break; + default: + if (TreeUtils.isTypeTree(lhsTree)) { + // lhsTree is a type tree at the pseudo assignment of a returned expression to + // declared return type. + res = getAnnotatedType(lhsTree); + } else { + throw new BugInCF( + "GenericAnnotatedTypeFactory: Unexpected tree passed to" + + " getAnnotatedTypeLhs. lhsTree: " + + lhsTree + + " Tree.Kind: " + + lhsTree.getKind()); + } + } + useFlow = oldUseFlow; + shouldCache = oldShouldCache; + computingAnnotatedTypeMirrorOfLhs = oldComputingAnnotatedTypeMirrorOfLhs; + return res; + } + + /** + * As long as everUseFlow is true, always enable flow refinement for the receiver. This method + * is an implementation detail and visible inside the package only. See the comment in this + * testcase for more details framework/tests/viewpointtest/TestGetAnnotatedLhs.java. + * + * @see #getAnnotatedType(Tree) + * @see #getAnnotatedTypeLhs(Tree) + * @param tree an expression tree + * @return the refined type of the expression tree + */ + /*package-private*/ AnnotatedTypeMirror getAnnotatedTypeWithReceiverRefinement(Tree tree) { + boolean oldUseFlow = useFlow; + useFlow = everUseFlow; + AnnotatedTypeMirror result = getAnnotatedType(tree); + useFlow = oldUseFlow; + return result; + } + + /** + * Returns the type of a varargs array of a method invocation or a constructor invocation. + * Returns null only if private field {@code useFlow} is false. + * + * @param tree a method invocation or a constructor invocation + * @return AnnotatedTypeMirror of varargs array for a method or constructor invocation {@code + * tree}; returns null if private field {@code useFlow} is false + */ + public @Nullable AnnotatedTypeMirror getAnnotatedTypeVarargsArray(Tree tree) { + if (!useFlow) { + return null; + } + + // Get the synthetic NewArray tree that dataflow creates as the last argument of a call to a + // vararg method. Do this by getting the MethodInvocationNode to which "tree" maps. The last + // argument node of the MethodInvocationNode stores the synthetic NewArray tree. + List args; + switch (tree.getKind()) { + case METHOD_INVOCATION: + args = getFirstNodeOfKindForTree(tree, MethodInvocationNode.class).getArguments(); + break; + case NEW_CLASS: + args = getFirstNodeOfKindForTree(tree, ObjectCreationNode.class).getArguments(); + break; + default: + throw new BugInCF("Unexpected kind of tree: " + tree); + } + + assert !args.isEmpty() : "Arguments are empty"; + Node varargsArray = args.get(args.size() - 1); + AnnotatedTypeMirror varargtype = getAnnotatedType(varargsArray.getTree()); + return varargtype; + } + + /** + * Returns the type of {@code v + 1} or {@code v - 1} where {@code v} is the expression in the + * postfixed increment or decrement expression. + * + * @param tree a postfixed increment or decrement tree + * @return AnnotatedTypeMirror of a right-hand side of an assignment for unary operation + */ + public AnnotatedTypeMirror getAnnotatedTypeRhsUnaryAssign(UnaryTree tree) { + if (!useFlow) { + return getAnnotatedType(tree); + } + BinaryTree binaryTree = flowResult.getPostfixBinaryTree(tree); + return getAnnotatedType(binaryTree); + } + + @Override + protected ParameterizedExecutableType constructorFromUse( + NewClassTree tree, boolean inferTypeArgs) { + ParameterizedExecutableType mType = super.constructorFromUse(tree, inferTypeArgs); + AnnotatedExecutableType method = mType.executableType; + dependentTypesHelper.atConstructorInvocation(method, tree); + return mType; + } + + @Override + protected void constructorFromUsePreSubstitution( + NewClassTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { + if (resolvePolyQuals) { + poly.resolve(tree, type); + } + } + + @Override + public AnnotatedTypeMirror getMethodReturnType(MethodTree m) { + AnnotatedTypeMirror returnType = super.getMethodReturnType(m); + dependentTypesHelper.atMethodBody(returnType, m); + return returnType; + } + + @Override + public void addDefaultAnnotations(AnnotatedTypeMirror type) { + addAnnotationsFromDefaultForType(null, type); + typeAnnotator.visit(type, null); + defaults.annotate((Element) null, type); + } + + /** + * Removes all primary annotations on a copy of the type and calculates the default annotations + * that apply to the copied type, without type refinements. + * + * @param tree tree where the type is used + * @param type type to determine the defaulted version for + * @return the annotated type mirror with default annotations + */ + public AnnotatedTypeMirror getDefaultAnnotations(Tree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror copy = type.deepCopy(); + copy.removeAnnotations(type.getAnnotations()); + addComputedTypeAnnotationsWithoutFlow(tree, copy); + return copy; + } + + /** + * Like {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding + * implementations typically simply pass the boolean to calls to super. + * + * @param tree an AST node + * @param type the type obtained from tree + * @param iUseFlow whether to use information from dataflow analysis + * @deprecated use {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} or {@link + * #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror)} if you want to add + * computed type annotations without using flow information + */ + @Deprecated // 2024-07-07 + @SuppressWarnings("unused") + protected void addComputedTypeAnnotations( + Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { + addComputedTypeAnnotationsWithoutFlow(tree, type); + } + + /** + * A helper method to add computed type annotations to a type without using flow information. + * + *

          This method is final; override {@link #addComputedTypeAnnotations(Tree, + * AnnotatedTypeMirror)} instead. + * + * @param tree an AST node + * @param type the type obtained from tree + * @see #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror) + */ + protected final void addComputedTypeAnnotationsWithoutFlow( + Tree tree, AnnotatedTypeMirror type) { + boolean oldUseflow = useFlow; + useFlow = false; + addComputedTypeAnnotations(tree, type); + useFlow = oldUseflow; + } + + /** + * {@inheritDoc} + * + *

          This method adds defaults and flow-sensitive type refinements. + * + * @see #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror) + */ + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + if (this.getRoot() == null && ajavaTypes.isParsing()) { + return; + } + assert this.getRoot() != null + : "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: " + + " root needs to be set when used on trees; factory: " + + this.getClass(); + + String thisClass = null; + String treeString = null; + if (debug) { + thisClass = this.getClass().getSimpleName(); + if (thisClass.endsWith("AnnotatedTypeFactory")) { + thisClass = + thisClass.substring( + 0, thisClass.length() - "AnnotatedTypeFactory".length()); + } + treeString = TreeUtils.toStringTruncated(tree, 60); + } log( - "%s GATF.addComputedTypeAnnotations#8(%s, %s), inferred=%s%n", - thisClass, treeString, type, inferred); - } + "%s GATF.addComputedTypeAnnotations#1(%s, %s, %s)%n", + thisClass, treeString, type, this.useFlow); + if (!TreeUtils.isExpressionTree(tree)) { + // Don't apply defaults to expressions. Their types may be computed from subexpressions + // in treeAnnotator. + addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); + log("%s GATF.addComputedTypeAnnotations#2(%s, %s)%n", thisClass, treeString, type); + } + applyQualifierParameterDefaults(tree, type); + log("%s GATF.addComputedTypeAnnotations#3(%s, %s)%n", thisClass, treeString, type); + treeAnnotator.visit(tree, type); + log( + "%s GATF.addComputedTypeAnnotations#4(%s, %s)%n treeAnnotator=%s%n", + thisClass, treeString, type, treeAnnotator); + if (TreeUtils.isExpressionTree(tree)) { + // If a tree annotator did not add a type, add the DefaultForUse default. + addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); + log("%s GATF.addComputedTypeAnnotations#5(%s, %s)%n", thisClass, treeString, type); + } + typeAnnotator.visit(type, null); + log( + "%s GATF.addComputedTypeAnnotations#6(%s, %s)%n typeAnnotator=%s%n", + thisClass, treeString, type, typeAnnotator); + defaults.annotate(tree, type); + log("%s GATF.addComputedTypeAnnotations#7(%s, %s)%n", thisClass, treeString, type); + + if (this.useFlow) { + Value inferred = getInferredValueFor(tree); + if (inferred != null) { + applyInferredAnnotations(type, inferred); + log( + "%s GATF.addComputedTypeAnnotations#8(%s, %s), inferred=%s%n", + thisClass, treeString, type, inferred); + } + } + log( + "%s GATF.addComputedTypeAnnotations#9(%s, %s, %s) done%n", + thisClass, treeString, type, this.useFlow); + } + + /** + * Flow analysis will be performed if all of the following are true. + * + *

            + *
          • {@code tree} is a {@link ClassTree} + *
          • Flow analysis has not already been performed on {@code tree} + *
          + * + * @param tree the tree to check and possibly perform flow analysis on + */ + protected void checkAndPerformFlowAnalysis(Tree tree) { + // For performance reasons, we require that getAnnotatedType is called + // on the ClassTree before it's called on any code contained in the class, + // so that we can perform flow analysis on the class. Previously we + // used TreePath.getPath to find enclosing classes, but that call + // alone consumed more than 10% of execution time. See + // BaseTypeVisitor.visitClass for the call to getAnnotatedType that + // triggers analysis. + if (tree instanceof ClassTree) { + ClassTree classTree = (ClassTree) tree; + if (!scannedClasses.containsKey(classTree)) { + performFlowAnalysis(classTree); + } + } } - log( - "%s GATF.addComputedTypeAnnotations#9(%s, %s, %s) done%n", - thisClass, treeString, type, this.useFlow); - } - - /** - * Flow analysis will be performed if all of the following are true. - * - *
            - *
          • {@code tree} is a {@link ClassTree} - *
          • Flow analysis has not already been performed on {@code tree} - *
          - * - * @param tree the tree to check and possibly perform flow analysis on - */ - protected void checkAndPerformFlowAnalysis(Tree tree) { - // For performance reasons, we require that getAnnotatedType is called - // on the ClassTree before it's called on any code contained in the class, - // so that we can perform flow analysis on the class. Previously we - // used TreePath.getPath to find enclosing classes, but that call - // alone consumed more than 10% of execution time. See - // BaseTypeVisitor.visitClass for the call to getAnnotatedType that - // triggers analysis. - if (tree instanceof ClassTree) { - ClassTree classTree = (ClassTree) tree; - if (!scannedClasses.containsKey(classTree)) { - performFlowAnalysis(classTree); - } + + /** + * Returns the inferred value (by the org.checkerframework.dataflow analysis) for a given tree. + * + * @param tree the tree + * @return the value for the tree, if one has been computed by dataflow. If no value has been + * computed, null is returned (this does not mean that no value will ever be computed for + * the given tree). + */ + public @Nullable Value getInferredValueFor(Tree tree) { + if (tree == null) { + throw new BugInCF( + "GenericAnnotatedTypeFactory.getInferredValueFor called with null tree"); + } + if (!analysis.isRunning() && flowResult == null) { + // When parsing stub or ajava files, the analysis is not running (it has not yet + // started), and flowResult is null (no analysis has occurred). Instead of attempting to + // find a non-existent inferred type, return null. + return null; + } + Value as = null; + if (analysis.isRunning()) { + as = analysis.getValue(tree); + } + if (as == null) { + as = flowResult.getValue(tree); + } + return as; + } + + /** + * Applies the annotations inferred by the org.checkerframework.dataflow analysis to the type + * {@code type}. + * + * @param type the type to modify + * @param inferred the inferred annotations to apply + */ + protected void applyInferredAnnotations(AnnotatedTypeMirror type, Value inferred) { + inferredTypesApplier.applyInferredType( + type, inferred.getAnnotations(), inferred.getUnderlyingType()); + } + + /** + * Applies defaults for types in a class with an qualifier parameter. + * + *

          Within a class with {@code @HasQualifierParameter}, types with that class default to the + * polymorphic qualifier rather than the typical default. Local variables with a type that has a + * qualifier parameter are initialized to the type of their initializer, rather than the default + * for local variables. + * + * @param tree a Tree whose type is {@code type} + * @param type where the defaults are applied + */ + protected void applyQualifierParameterDefaults(Tree tree, AnnotatedTypeMirror type) { + applyQualifierParameterDefaults(TreeUtils.elementFromTree(tree), type); + } + + /** + * Applies defaults for types in a class with an qualifier parameter. + * + *

          Within a class with {@code @HasQualifierParameter}, types with that class default to the + * polymorphic qualifier rather than the typical default. Local variables with a type that has a + * qualifier parameter are initialized to the type of their initializer, rather than the default + * for local variables. + * + * @param elt an Element whose type is {@code type} + * @param type where the defaults are applied + */ + protected void applyQualifierParameterDefaults( + @Nullable Element elt, AnnotatedTypeMirror type) { + if (elt == null) { + return; + } + switch (elt.getKind()) { + case CONSTRUCTOR: + case METHOD: + case FIELD: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + case LOCAL_VARIABLE: + case PARAMETER: + break; + default: + return; + } + + applyLocalVariableQualifierParameterDefaults(elt, type); + + TypeElement enclosingClass = ElementUtils.enclosingTypeElement(elt); + AnnotationMirrorSet tops; + if (enclosingClass != null) { + tops = getQualifierParameterHierarchies(enclosingClass); + } else { + return; + } + if (tops.isEmpty()) { + return; + } + AnnotationMirrorSet polyWithQualParam = new AnnotationMirrorSet(); + for (AnnotationMirror top : tops) { + AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); + if (poly != null) { + polyWithQualParam.add(poly); + } + } + new TypeAnnotator(this) { + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + if (type.getUnderlyingType().asElement().equals(enclosingClass)) { + type.addMissingAnnotations(polyWithQualParam); + } + return super.visitDeclared(type, aVoid); + } + }.visit(type); + } + + /** + * Defaults local variables with types that have a qualifier parameter to the type of their + * initializer, if an initializer is present. Does nothing for local variables with no + * initializer. + * + * @param elt an Element whose type is {@code type} + * @param type where the defaults are applied + */ + private void applyLocalVariableQualifierParameterDefaults( + Element elt, AnnotatedTypeMirror type) { + if (!ElementUtils.isLocalVariable(elt) + || getQualifierParameterHierarchies(type).isEmpty() + || variablesUnderInitialization.contains(elt)) { + return; + } + + Tree declTree = declarationFromElement(elt); + if (declTree == null || declTree.getKind() != Tree.Kind.VARIABLE) { + return; + } + + ExpressionTree initializer = ((VariableTree) declTree).getInitializer(); + if (initializer == null) { + return; + } + + VariableElement variableElt = (VariableElement) elt; + variablesUnderInitialization.add(variableElt); + AnnotatedTypeMirror initializerType; + if (shouldCache && initializerCache.containsKey(initializer)) { + initializerType = initializerCache.get(initializer); + } else { + // When this method is called by getAnnotatedTypeLhs, flow is turned off. + // Turn it back on so the type of the initializer is the refined type. + boolean oldUseFlow = useFlow; + useFlow = everUseFlow; + try { + initializerType = getAnnotatedType(initializer); + } finally { + useFlow = oldUseFlow; + } + } + + AnnotationMirrorSet qualParamTypes = new AnnotationMirrorSet(); + for (AnnotationMirror initializerAnnotation : initializerType.getAnnotations()) { + if (hasQualifierParameterInHierarchy( + type, qualHierarchy.getTopAnnotation(initializerAnnotation))) { + qualParamTypes.add(initializerAnnotation); + } + } + + type.addMissingAnnotations(qualParamTypes); + variablesUnderInitialization.remove(variableElt); + if (shouldCache) { + initializerCache.put(initializer, initializerType); + } } - } - - /** - * Returns the inferred value (by the org.checkerframework.dataflow analysis) for a given tree. - * - * @param tree the tree - * @return the value for the tree, if one has been computed by dataflow. If no value has been - * computed, null is returned (this does not mean that no value will ever be computed for the - * given tree). - */ - public @Nullable Value getInferredValueFor(Tree tree) { - if (tree == null) { - throw new BugInCF("GenericAnnotatedTypeFactory.getInferredValueFor called with null tree"); - } - if (!analysis.isRunning() && flowResult == null) { - // When parsing stub or ajava files, the analysis is not running (it has not yet - // started), and flowResult is null (no analysis has occurred). Instead of attempting to - // find a non-existent inferred type, return null. - return null; - } - Value as = null; - if (analysis.isRunning()) { - as = analysis.getValue(tree); - } - if (as == null) { - as = flowResult.getValue(tree); - } - return as; - } - - /** - * Applies the annotations inferred by the org.checkerframework.dataflow analysis to the type - * {@code type}. - * - * @param type the type to modify - * @param inferred the inferred annotations to apply - */ - protected void applyInferredAnnotations(AnnotatedTypeMirror type, Value inferred) { - inferredTypesApplier.applyInferredType( - type, inferred.getAnnotations(), inferred.getUnderlyingType()); - } - - /** - * Applies defaults for types in a class with an qualifier parameter. - * - *

          Within a class with {@code @HasQualifierParameter}, types with that class default to the - * polymorphic qualifier rather than the typical default. Local variables with a type that has a - * qualifier parameter are initialized to the type of their initializer, rather than the default - * for local variables. - * - * @param tree a Tree whose type is {@code type} - * @param type where the defaults are applied - */ - protected void applyQualifierParameterDefaults(Tree tree, AnnotatedTypeMirror type) { - applyQualifierParameterDefaults(TreeUtils.elementFromTree(tree), type); - } - - /** - * Applies defaults for types in a class with an qualifier parameter. - * - *

          Within a class with {@code @HasQualifierParameter}, types with that class default to the - * polymorphic qualifier rather than the typical default. Local variables with a type that has a - * qualifier parameter are initialized to the type of their initializer, rather than the default - * for local variables. - * - * @param elt an Element whose type is {@code type} - * @param type where the defaults are applied - */ - protected void applyQualifierParameterDefaults(@Nullable Element elt, AnnotatedTypeMirror type) { - if (elt == null) { - return; - } - switch (elt.getKind()) { - case CONSTRUCTOR: - case METHOD: - case FIELD: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - case LOCAL_VARIABLE: - case PARAMETER: - break; - default: - return; - } - - applyLocalVariableQualifierParameterDefaults(elt, type); - - TypeElement enclosingClass = ElementUtils.enclosingTypeElement(elt); - AnnotationMirrorSet tops; - if (enclosingClass != null) { - tops = getQualifierParameterHierarchies(enclosingClass); - } else { - return; - } - if (tops.isEmpty()) { - return; - } - AnnotationMirrorSet polyWithQualParam = new AnnotationMirrorSet(); - for (AnnotationMirror top : tops) { - AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); - if (poly != null) { - polyWithQualParam.add(poly); - } + + /** + * To add annotations to the type of method or constructor parameters, add a {@link + * TypeAnnotator} using {@link #createTypeAnnotator()} and see the comment in {@link + * TypeAnnotator#visitExecutable(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType, + * Void)}. + * + * @param elt an element + * @param type the type obtained from {@code elt} + */ + @Override + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + addAnnotationsFromDefaultForType(elt, type); + applyQualifierParameterDefaults(elt, type); + typeAnnotator.visit(type, null); + defaults.annotate(elt, type); + dependentTypesHelper.atLocalVariable(type, elt); + } + + @Override + protected ParameterizedExecutableType methodFromUse( + MethodInvocationTree tree, boolean inferTypeArg) { + ParameterizedExecutableType mType = super.methodFromUse(tree, inferTypeArg); + AnnotatedExecutableType method = mType.executableType; + dependentTypesHelper.atMethodInvocation(method, tree); + return mType; + } + + @Override + public void methodFromUsePreSubstitution( + ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { + super.methodFromUsePreSubstitution(tree, type, resolvePolyQuals); + if (tree instanceof MethodInvocationTree && resolvePolyQuals) { + poly.resolve((MethodInvocationTree) tree, type); + } } - new TypeAnnotator(this) { - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - if (type.getUnderlyingType().asElement().equals(enclosingClass)) { - type.addMissingAnnotations(polyWithQualParam); + + @Override + public List typeVariablesFromUse( + AnnotatedDeclaredType type, TypeElement element) { + List f = super.typeVariablesFromUse(type, element); + dependentTypesHelper.atParameterizedTypeUse(f, element); + return f; + } + + /** + * Returns the empty store. + * + * @return the empty store + */ + public Store getEmptyStore() { + return emptyStore; + } + + /** + * Returns the type factory used by a subchecker. Throws an exception if no matching subchecker + * was found or if the type factory is null. The caller must know the exact checker class to + * request. + * + *

          Because the visitor path is copied, call this method each time a subfactory is needed + * rather than store the returned subfactory in a field. + * + * @param subCheckerClass the exact class of the subchecker + * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} + * @return the AnnotatedTypeFactory of the subchecker; never null + * @see #getTypeFactoryOfSubcheckerOrNull + */ + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public final > T getTypeFactoryOfSubchecker( + Class subCheckerClass) { + T result = getTypeFactoryOfSubcheckerOrNull(subCheckerClass); + if (result == null) { + throw new TypeSystemError( + "In " + + this.getClass().getSimpleName() + + ", no type factory found for " + + subCheckerClass.getSimpleName() + + "."); } - return super.visitDeclared(type, aVoid); - } - }.visit(type); - } - - /** - * Defaults local variables with types that have a qualifier parameter to the type of their - * initializer, if an initializer is present. Does nothing for local variables with no - * initializer. - * - * @param elt an Element whose type is {@code type} - * @param type where the defaults are applied - */ - private void applyLocalVariableQualifierParameterDefaults(Element elt, AnnotatedTypeMirror type) { - if (!ElementUtils.isLocalVariable(elt) - || getQualifierParameterHierarchies(type).isEmpty() - || variablesUnderInitialization.contains(elt)) { - return; - } - - Tree declTree = declarationFromElement(elt); - if (declTree == null || declTree.getKind() != Tree.Kind.VARIABLE) { - return; - } - - ExpressionTree initializer = ((VariableTree) declTree).getInitializer(); - if (initializer == null) { - return; - } - - VariableElement variableElt = (VariableElement) elt; - variablesUnderInitialization.add(variableElt); - AnnotatedTypeMirror initializerType; - if (shouldCache && initializerCache.containsKey(initializer)) { - initializerType = initializerCache.get(initializer); - } else { - // When this method is called by getAnnotatedTypeLhs, flow is turned off. - // Turn it back on so the type of the initializer is the refined type. - boolean oldUseFlow = useFlow; - useFlow = everUseFlow; - try { - initializerType = getAnnotatedType(initializer); - } finally { - useFlow = oldUseFlow; - } + return result; } - AnnotationMirrorSet qualParamTypes = new AnnotationMirrorSet(); - for (AnnotationMirror initializerAnnotation : initializerType.getAnnotations()) { - if (hasQualifierParameterInHierarchy( - type, qualHierarchy.getTopAnnotation(initializerAnnotation))) { - qualParamTypes.add(initializerAnnotation); - } + /** + * Returns the type factory used by a subchecker. Returns null if no matching subchecker was + * found or if the type factory is null. The caller must know the exact checker class to + * request. + * + *

          Because the visitor path is copied, call this method each time a subfactory is needed + * rather than store the returned subfactory in a field. + * + * @param subCheckerClass the exact class of the subchecker + * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} + * @return the AnnotatedTypeFactory of the subchecker or null if no subchecker exists + * @see #getTypeFactoryOfSubchecker + */ + @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse + public > + @Nullable T getTypeFactoryOfSubcheckerOrNull( + Class subCheckerClass) { + BaseTypeChecker subchecker = checker.getSubchecker(subCheckerClass); + if (subchecker == null) { + return null; + } + + @SuppressWarnings( + "unchecked" // This might not be safe, but the caller of the method should use the + // correct type. + ) + T subFactory = (T) subchecker.getTypeFactory(); + if (subFactory != null) { + subFactory.setVisitorTreePath(getVisitorTreePath()); + } + return subFactory; + } + + /** + * Should the local variable default annotation be applied to type variables? + * + *

          It is initialized to true if data flow is used by the checker. It is set to false when + * getting the assignment context for type argument inference. + * + * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault + * @return shouldDefaultTypeVarLocals + */ + public boolean getShouldDefaultTypeVarLocals() { + return shouldDefaultTypeVarLocals; + } + + /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ + protected final CFGVisualizer cfgVisualizer; + + /** + * Create a new CFGVisualizer. + * + * @return a new CFGVisualizer, or null if none will be used on this run + */ + protected @Nullable CFGVisualizer createCFGVisualizer() { + if (checker.hasOption("flowdotdir")) { + String flowdotdir = checker.getOption("flowdotdir"); + if (flowdotdir.isEmpty()) { + throw new UserError("Empty string provided for -Aflowdotdir command-line argument"); + } + boolean verbose = checker.hasOption("verbosecfg"); + + Map args = new HashMap<>(2); + args.put("outdir", flowdotdir); + args.put("verbose", verbose); + args.put("checkerName", getCheckerName()); + + CFGVisualizer res = new DOTCFGVisualizer<>(); + res.init(args); + return res; + } else if (checker.hasOption("cfgviz")) { + List opts = checker.getStringsOption("cfgviz", ','); + if (opts.isEmpty()) { + throw new UserError( + "-Acfgviz specified without arguments, should be" + + " -Acfgviz=VizClassName[,opts,...]"); + } + String vizClassName = opts.get(0); + if (!Signatures.isBinaryName(vizClassName)) { + throw new UserError( + "Bad -Acfgviz class name \"%s\", should be a binary name.", vizClassName); + } + + Map args = processCFGVisualizerOption(opts); + if (!args.containsKey("verbose")) { + boolean verbose = checker.hasOption("verbosecfg"); + args.put("verbose", verbose); + } + args.put("checkerName", getCheckerName()); + + CFGVisualizer res = + BaseTypeChecker.invokeConstructorFor(vizClassName, null, null); + res.init(args); + return res; + } + // Nobody expected to use cfgVisualizer if neither option given. + return null; + } + + /** + * A simple utility method to determine a short checker name to be used by CFG visualizations. + */ + private String getCheckerName() { + String checkerName = checker.getClass().getSimpleName(); + if (checkerName.endsWith("Checker")) { + checkerName = checkerName.substring(0, checkerName.length() - "Checker".length()); + } else if (checkerName.endsWith("Subchecker")) { + checkerName = checkerName.substring(0, checkerName.length() - "Subchecker".length()); + } + return checkerName; + } + + /** + * Parse keys or key-value pairs into a map from key to value (to true if no value is provided). + * + * @param opts the CFG visualization options + * @return a map that represents the options + */ + private Map processCFGVisualizerOption(List opts) { + Map res = new HashMap<>(CollectionsPlume.mapCapacity(opts.size() - 1)); + // Index 0 is the visualizer class name and can be ignored. + for (int i = 1; i < opts.size(); ++i) { + String opt = opts.get(i); + String[] split = opt.split("="); + switch (split.length) { + case 1: + res.put(split[0], true); + break; + case 2: + res.put(split[0], split[1]); + break; + default: + throw new UserError("Too many '=' in cfgviz option: " + opt); + } + } + return res; } - type.addMissingAnnotations(qualParamTypes); - variablesUnderInitialization.remove(variableElt); - if (shouldCache) { - initializerCache.put(initializer, initializerType); - } - } - - /** - * To add annotations to the type of method or constructor parameters, add a {@link TypeAnnotator} - * using {@link #createTypeAnnotator()} and see the comment in {@link - * TypeAnnotator#visitExecutable(org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType, - * Void)}. - * - * @param elt an element - * @param type the type obtained from {@code elt} - */ - @Override - public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - addAnnotationsFromDefaultForType(elt, type); - applyQualifierParameterDefaults(elt, type); - typeAnnotator.visit(type, null); - defaults.annotate(elt, type); - dependentTypesHelper.atLocalVariable(type, elt); - } - - @Override - protected ParameterizedExecutableType methodFromUse( - MethodInvocationTree tree, boolean inferTypeArg) { - ParameterizedExecutableType mType = super.methodFromUse(tree, inferTypeArg); - AnnotatedExecutableType method = mType.executableType; - dependentTypesHelper.atMethodInvocation(method, tree); - return mType; - } - - @Override - public void methodFromUsePreSubstitution( - ExpressionTree tree, AnnotatedExecutableType type, boolean resolvePolyQuals) { - super.methodFromUsePreSubstitution(tree, type, resolvePolyQuals); - if (tree instanceof MethodInvocationTree && resolvePolyQuals) { - poly.resolve((MethodInvocationTree) tree, type); - } - } - - @Override - public List typeVariablesFromUse( - AnnotatedDeclaredType type, TypeElement element) { - List f = super.typeVariablesFromUse(type, element); - dependentTypesHelper.atParameterizedTypeUse(f, element); - return f; - } - - /** - * Returns the empty store. - * - * @return the empty store - */ - public Store getEmptyStore() { - return emptyStore; - } - - /** - * Returns the type factory used by a subchecker. Throws an exception if no matching subchecker - * was found or if the type factory is null. The caller must know the exact checker class to - * request. - * - *

          Because the visitor path is copied, call this method each time a subfactory is needed rather - * than store the returned subfactory in a field. - * - * @param subCheckerClass the exact class of the subchecker - * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} - * @return the AnnotatedTypeFactory of the subchecker; never null - * @see #getTypeFactoryOfSubcheckerOrNull - */ - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public final > T getTypeFactoryOfSubchecker( - Class subCheckerClass) { - T result = getTypeFactoryOfSubcheckerOrNull(subCheckerClass); - if (result == null) { - throw new TypeSystemError( - "In " - + this.getClass().getSimpleName() - + ", no type factory found for " - + subCheckerClass.getSimpleName() - + "."); - } - return result; - } - - /** - * Returns the type factory used by a subchecker. Returns null if no matching subchecker was found - * or if the type factory is null. The caller must know the exact checker class to request. - * - *

          Because the visitor path is copied, call this method each time a subfactory is needed rather - * than store the returned subfactory in a field. - * - * @param subCheckerClass the exact class of the subchecker - * @param the type of {@code subCheckerClass}'s {@link AnnotatedTypeFactory} - * @return the AnnotatedTypeFactory of the subchecker or null if no subchecker exists - * @see #getTypeFactoryOfSubchecker - */ - @SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse - public > - @Nullable T getTypeFactoryOfSubcheckerOrNull( - Class subCheckerClass) { - BaseTypeChecker subchecker = checker.getSubchecker(subCheckerClass); - if (subchecker == null) { - return null; - } - - @SuppressWarnings( - "unchecked" // This might not be safe, but the caller of the method should use the - // correct type. - ) - T subFactory = (T) subchecker.getTypeFactory(); - if (subFactory != null) { - subFactory.setVisitorTreePath(getVisitorTreePath()); - } - return subFactory; - } - - /** - * Should the local variable default annotation be applied to type variables? - * - *

          It is initialized to true if data flow is used by the checker. It is set to false when - * getting the assignment context for type argument inference. - * - * @see GenericAnnotatedTypeFactory#getAnnotatedTypeLhsNoTypeVarDefault - * @return shouldDefaultTypeVarLocals - */ - public boolean getShouldDefaultTypeVarLocals() { - return shouldDefaultTypeVarLocals; - } - - /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ - protected final CFGVisualizer cfgVisualizer; - - /** - * Create a new CFGVisualizer. - * - * @return a new CFGVisualizer, or null if none will be used on this run - */ - protected @Nullable CFGVisualizer createCFGVisualizer() { - if (checker.hasOption("flowdotdir")) { - String flowdotdir = checker.getOption("flowdotdir"); - if (flowdotdir.isEmpty()) { - throw new UserError("Empty string provided for -Aflowdotdir command-line argument"); - } - boolean verbose = checker.hasOption("verbosecfg"); - - Map args = new HashMap<>(2); - args.put("outdir", flowdotdir); - args.put("verbose", verbose); - args.put("checkerName", getCheckerName()); - - CFGVisualizer res = new DOTCFGVisualizer<>(); - res.init(args); - return res; - } else if (checker.hasOption("cfgviz")) { - List opts = checker.getStringsOption("cfgviz", ','); - if (opts.isEmpty()) { - throw new UserError( - "-Acfgviz specified without arguments, should be" - + " -Acfgviz=VizClassName[,opts,...]"); - } - String vizClassName = opts.get(0); - if (!Signatures.isBinaryName(vizClassName)) { - throw new UserError( - "Bad -Acfgviz class name \"%s\", should be a binary name.", vizClassName); - } + /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ + public CFGVisualizer getCFGVisualizer() { + return cfgVisualizer; + } - Map args = processCFGVisualizerOption(opts); - if (!args.containsKey("verbose")) { - boolean verbose = checker.hasOption("verbosecfg"); - args.put("verbose", verbose); - } - args.put("checkerName", getCheckerName()); - - CFGVisualizer res = - BaseTypeChecker.invokeConstructorFor(vizClassName, null, null); - res.init(args); - return res; - } - // Nobody expected to use cfgVisualizer if neither option given. - return null; - } - - /** A simple utility method to determine a short checker name to be used by CFG visualizations. */ - private String getCheckerName() { - String checkerName = checker.getClass().getSimpleName(); - if (checkerName.endsWith("Checker")) { - checkerName = checkerName.substring(0, checkerName.length() - "Checker".length()); - } else if (checkerName.endsWith("Subchecker")) { - checkerName = checkerName.substring(0, checkerName.length() - "Subchecker".length()); - } - return checkerName; - } - - /** - * Parse keys or key-value pairs into a map from key to value (to true if no value is provided). - * - * @param opts the CFG visualization options - * @return a map that represents the options - */ - private Map processCFGVisualizerOption(List opts) { - Map res = new HashMap<>(CollectionsPlume.mapCapacity(opts.size() - 1)); - // Index 0 is the visualizer class name and can be ignored. - for (int i = 1; i < opts.size(); ++i) { - String opt = opts.get(i); - String[] split = opt.split("="); - switch (split.length) { - case 1: - res.put(split[0], true); - break; - case 2: - res.put(split[0], split[1]); - break; - default: - throw new UserError("Too many '=' in cfgviz option: " + opt); - } + @Override + public void postAsMemberOf( + AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { + super.postAsMemberOf(type, owner, element); + if (element.getKind() == ElementKind.FIELD) { + poly.resolve(((VariableElement) element), owner, type); + } } - return res; - } - - /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ - public CFGVisualizer getCFGVisualizer() { - return cfgVisualizer; - } - - @Override - public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) { - super.postAsMemberOf(type, owner, element); - if (element.getKind() == ElementKind.FIELD) { - poly.resolve(((VariableElement) element), owner, type); - } - } - - /** - * Adds default qualifiers based on the underlying type of {@code type} to {@code type}. If {@code - * element} is a local variable, or if the type already has an annotation from the relevant type - * hierarchy, then the defaults are not added. - * - *

          (This uses both the {@link DefaultQualifierForUseTypeAnnotator} and {@link - * DefaultForTypeAnnotator}.) - * - * @param element possibly null element whose type is {@code type} - * @param type the type to which defaults are added - */ - protected void addAnnotationsFromDefaultForType( - @Nullable Element element, AnnotatedTypeMirror type) { - if (element != null && ElementUtils.isLocalVariable(element)) { - // It's a local variable. - if (type.getKind() == TypeKind.DECLARED) { - // If this is a type for a local variable, don't apply the default to the primary - // location. - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; - if (declaredType.getEnclosingType() != null) { - defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType()); - defaultForTypeAnnotator.visit(declaredType.getEnclosingType()); - } - for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) { - defaultQualifierForUseTypeAnnotator.visit(typeArg); - defaultForTypeAnnotator.visit(typeArg); - } - } else if (type.getKind().isPrimitive()) { - // Don't apply the default for local variables with primitive types. (The primary - // location is the only location, so this is a special case of the above.) - } else { - defaultQualifierForUseTypeAnnotator.visit(type); - defaultForTypeAnnotator.visit(type); - } - } else { - // It's not a local variable. - defaultQualifierForUseTypeAnnotator.visit(type); - defaultForTypeAnnotator.visit(type); - } - } - - /** - * Output a message, if logging is on. - * - * @param format a format string - * @param args arguments to the format string - */ - @FormatMethod - private static void log(String format, Object... args) { - if (debug) { - SystemPlume.sleep(1); // logging can interleave with typechecker output - System.out.printf(format, args); - } - } - - /** For each type, whether it is relevant. A cache to avoid repeated re-computation. */ - private final Map isRelevantCache = CollectionsPlume.createLruCache(300); - - /** - * Returns true if users can write type annotations from this type system directly on the given - * Java type. - * - *

          For a compound type, returns true only if a programmer may write a type qualifier on the top - * level of the compound type. That is, this method may return false, when it is possible to write - * type qualifiers on elements of the type. - * - *

          Subclasses should override {@code #isRelevantImpl} instead of this method. - * - * @param tm a type - * @return true if users can write type annotations from this type system directly on the given - * Java type - */ - public final boolean isRelevant(TypeMirror tm) { - if (relevantJavaTypes == null) { - return true; - } - if (tm.getKind() != TypeKind.PACKAGE && tm.getKind() != TypeKind.MODULE) { - tm = types.erasure(tm); - } - Boolean cachedResult = isRelevantCache.get(tm); - if (cachedResult != null) { - return cachedResult; - } - boolean result = isRelevantImpl(tm); - isRelevantCache.put(tm, result); - return result; - } - - /** - * Returns true if users can write type annotations from this type system directly on the given - * Java type. - * - *

          For a compound type, returns true only if it a programmer may write a type qualifier on the - * top level of the compound type. That is, this method may return false, when it is possible to - * write type qualifiers on elements of the type. - * - *

          Subclasses should override {@code #isRelevantImpl} instead of this method. - * - * @param tm a type - * @return true if users can write type annotations from this type system directly on the given - * Java type - */ - public final boolean isRelevant(AnnotatedTypeMirror tm) { - return isRelevant(tm.getUnderlyingType()); - } - - /** - * Returns true if users can write type annotations from this type system on the given Java type. - * Does not use a cache. - * - *

          Clients should never call this. Call {@link #isRelevant} instead. This is a helper method - * for {@link #isRelevant}. - * - * @param tm a type - * @return true if users can write type annotations from this type system on the given Java type - */ - protected boolean isRelevantImpl(TypeMirror tm) { - if (relevantJavaTypes == null) { - return true; - } - if (relevantJavaTypes.contains(tm)) { - return true; - } - - switch (tm.getKind()) { - - // Primitives have no subtyping relationships, but the lookup might have failed - // because tm has metadata such as annotations. - case BOOLEAN: - case BYTE: - case CHAR: - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - for (TypeMirror relevantJavaType : relevantJavaTypes) { - if (types.isSameType(tm, relevantJavaType)) { - return true; - } + + /** + * Adds default qualifiers based on the underlying type of {@code type} to {@code type}. If + * {@code element} is a local variable, or if the type already has an annotation from the + * relevant type hierarchy, then the defaults are not added. + * + *

          (This uses both the {@link DefaultQualifierForUseTypeAnnotator} and {@link + * DefaultForTypeAnnotator}.) + * + * @param element possibly null element whose type is {@code type} + * @param type the type to which defaults are added + */ + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + if (element != null && ElementUtils.isLocalVariable(element)) { + // It's a local variable. + if (type.getKind() == TypeKind.DECLARED) { + // If this is a type for a local variable, don't apply the default to the primary + // location. + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; + if (declaredType.getEnclosingType() != null) { + defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType()); + defaultForTypeAnnotator.visit(declaredType.getEnclosingType()); + } + for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) { + defaultQualifierForUseTypeAnnotator.visit(typeArg); + defaultForTypeAnnotator.visit(typeArg); + } + } else if (type.getKind().isPrimitive()) { + // Don't apply the default for local variables with primitive types. (The primary + // location is the only location, so this is a special case of the above.) + } else { + defaultQualifierForUseTypeAnnotator.visit(type); + defaultForTypeAnnotator.visit(type); + } + } else { + // It's not a local variable. + defaultQualifierForUseTypeAnnotator.visit(type); + defaultForTypeAnnotator.visit(type); } - return false; + } - // Void is never relevant - case VOID: - return false; + /** + * Output a message, if logging is on. + * + * @param format a format string + * @param args arguments to the format string + */ + @FormatMethod + private static void log(String format, Object... args) { + if (debug) { + SystemPlume.sleep(1); // logging can interleave with typechecker output + System.out.printf(format, args); + } + } - case ARRAY: - return arraysAreRelevant; + /** For each type, whether it is relevant. A cache to avoid repeated re-computation. */ + private final Map isRelevantCache = CollectionsPlume.createLruCache(300); + + /** + * Returns true if users can write type annotations from this type system directly on the given + * Java type. + * + *

          For a compound type, returns true only if a programmer may write a type qualifier on the + * top level of the compound type. That is, this method may return false, when it is possible to + * write type qualifiers on elements of the type. + * + *

          Subclasses should override {@code #isRelevantImpl} instead of this method. + * + * @param tm a type + * @return true if users can write type annotations from this type system directly on the given + * Java type + */ + public final boolean isRelevant(TypeMirror tm) { + if (relevantJavaTypes == null) { + return true; + } + if (tm.getKind() != TypeKind.PACKAGE && tm.getKind() != TypeKind.MODULE) { + tm = types.erasure(tm); + } + Boolean cachedResult = isRelevantCache.get(tm); + if (cachedResult != null) { + return cachedResult; + } + boolean result = isRelevantImpl(tm); + isRelevantCache.put(tm, result); + return result; + } - case DECLARED: - for (TypeMirror relevantJavaType : relevantJavaTypes) { - if (types.isSubtype(relevantJavaType, tm) || types.isSubtype(tm, relevantJavaType)) { + /** + * Returns true if users can write type annotations from this type system directly on the given + * Java type. + * + *

          For a compound type, returns true only if it a programmer may write a type qualifier on + * the top level of the compound type. That is, this method may return false, when it is + * possible to write type qualifiers on elements of the type. + * + *

          Subclasses should override {@code #isRelevantImpl} instead of this method. + * + * @param tm a type + * @return true if users can write type annotations from this type system directly on the given + * Java type + */ + public final boolean isRelevant(AnnotatedTypeMirror tm) { + return isRelevant(tm.getUnderlyingType()); + } + + /** + * Returns true if users can write type annotations from this type system on the given Java + * type. Does not use a cache. + * + *

          Clients should never call this. Call {@link #isRelevant} instead. This is a helper method + * for {@link #isRelevant}. + * + * @param tm a type + * @return true if users can write type annotations from this type system on the given Java type + */ + protected boolean isRelevantImpl(TypeMirror tm) { + if (relevantJavaTypes == null) { + return true; + } + if (relevantJavaTypes.contains(tm)) { return true; - } } - return false; - case TYPEVAR: - return isRelevant(((TypeVariable) tm).getUpperBound()); + switch (tm.getKind()) { - case NULL: - for (TypeMirror relevantJavaType : relevantJavaTypes) { - switch (relevantJavaType.getKind()) { + // Primitives have no subtyping relationships, but the lookup might have failed + // because tm has metadata such as annotations. case BOOLEAN: case BYTE: case CHAR: @@ -2656,553 +2650,599 @@ protected boolean isRelevantImpl(TypeMirror tm) { case INT: case LONG: case SHORT: - continue; + for (TypeMirror relevantJavaType : relevantJavaTypes) { + if (types.isSameType(tm, relevantJavaType)) { + return true; + } + } + return false; - case ERROR: - case NONE: + // Void is never relevant case VOID: - continue; + return false; + + case ARRAY: + return arraysAreRelevant; + case DECLARED: + for (TypeMirror relevantJavaType : relevantJavaTypes) { + if (types.isSubtype(relevantJavaType, tm) + || types.isSubtype(tm, relevantJavaType)) { + return true; + } + } + return false; + + case TYPEVAR: + return isRelevant(((TypeVariable) tm).getUpperBound()); + + case NULL: + for (TypeMirror relevantJavaType : relevantJavaTypes) { + switch (relevantJavaType.getKind()) { + case BOOLEAN: + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + continue; + + case ERROR: + case NONE: + case VOID: + continue; + + case MODULE: + case PACKAGE: + continue; + + case NULL: + default: + return true; + } + } + return false; + + case EXECUTABLE: case MODULE: case PACKAGE: - continue; + return false; - case NULL: default: - return true; - } + throw new BugInCF("isRelevantHelper(%s): Unexpected TypeKind %s", tm, tm.getKind()); } - return false; + } - case EXECUTABLE: - case MODULE: - case PACKAGE: - return false; + /** The cached message about relevant types. */ + private @MonotonicNonNull String irrelevantExtraMessage = null; + + /** + * Returns a string that can be passed to the "anno.on.irrelevant" error, giving information + * about which types are relevant. + * + * @return a string that can be passed to the "anno.on.irrelevant" error, possibly the empty + * string + */ + public String irrelevantExtraMessage() { + if (irrelevantExtraMessage == null) { + if (relevantJavaTypes == null) { + irrelevantExtraMessage = ""; + } else { + irrelevantExtraMessage = "; only applicable to " + relevantJavaTypes; + if (arraysAreRelevant) { + irrelevantExtraMessage += " and arrays"; + } + } + } + return irrelevantExtraMessage; + } + + /** + * Return the type of the default value of the given type. The default value is 0, false, or + * null. + * + * @param typeMirror a type + * @return the annotated type of {@code type}'s default value + */ + // TODO: Cache results to avoid recomputation. + public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { + Tree defaultValueTree = TreeUtils.getDefaultValueTree(typeMirror, processingEnv); + TypeMirror defaultValueTM = TreeUtils.typeOf(defaultValueTree); + AnnotatedTypeMirror defaultValueATM = + AnnotatedTypeMirror.createType(defaultValueTM, this, false); + addComputedTypeAnnotationsWithoutFlow(defaultValueTree, defaultValueATM); + return defaultValueATM; + } + + /* NO-AFU + * Return the contract annotations (that is, pre- and post-conditions) for the given AMethod. Does + * not modify the AMethod. + * + *

          This overload must only be called when using WholeProgramInferenceScenes. + * + * @param m the AFU representation of a method + * @return the contract annotations for the method + */ + /* NO-AFU + public List getContractAnnotations(AMethod m) { + List preconds = getPreconditionAnnotations(m); + List postconds = getPostconditionAnnotations(m, preconds); + + List result = preconds; + result.addAll(postconds); + return result; + } + */ + + /* NO-AFU + * Return the precondition annotations for the given AMethod. Does not modify the AMethod. + * + *

          This overload must only be called when using WholeProgramInferenceScenes. + * + * @param m the AFU representation of a method + * @return the precondition annotations for the method + */ + /* NO-AFU + public List getPreconditionAnnotations(AMethod m) { + int size = m.getPreconditions().size(); + List result = new ArrayList<>(size); + if (size == 0) { + return result; + } + + WholeProgramInferenceImplementation wholeProgramInference = + (WholeProgramInferenceImplementation) getWholeProgramInference(); + WholeProgramInferenceScenesStorage storage = + (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); - default: - throw new BugInCF("isRelevantHelper(%s): Unexpected TypeKind %s", tm, tm.getKind()); - } - } - - /** The cached message about relevant types. */ - private @MonotonicNonNull String irrelevantExtraMessage = null; - - /** - * Returns a string that can be passed to the "anno.on.irrelevant" error, giving information about - * which types are relevant. - * - * @return a string that can be passed to the "anno.on.irrelevant" error, possibly the empty - * string - */ - public String irrelevantExtraMessage() { - if (irrelevantExtraMessage == null) { - if (relevantJavaTypes == null) { - irrelevantExtraMessage = ""; - } else { - irrelevantExtraMessage = "; only applicable to " + relevantJavaTypes; - if (arraysAreRelevant) { - irrelevantExtraMessage += " and arrays"; + for (Map.Entry entry : m.getPreconditions().entrySet()) { + TypeMirror typeMirror = entry.getValue().getTypeMirror(); + if (typeMirror == null) { + throw new BugInCF( + "null TypeMirror in AField inferred by WPI precondition inference. AField: " + + entry.getValue().toString()); } + Collections.sort(result, Ordering.usingToString()); + return result; + } + */ + + /* NO-AFU + * Return the postcondition annotations for the given AMethod. Does not modify the AMethod. + * + *

          This overload must only be called when using WholeProgramInferenceScenes. + * + * @param m the AFU representation of a method + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions + * @return the postcondition annotations for the method + */ + /* NO-AFU + public List getPostconditionAnnotations( + AMethod m, List preconds) { + int size = m.getPostconditions().size(); + List result = new ArrayList<>(size); + if (size == 0) { + return result; } + + WholeProgramInferenceImplementation wholeProgramInference = + (WholeProgramInferenceImplementation) getWholeProgramInference(); + WholeProgramInferenceScenesStorage storage = + (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); + + for (Map.Entry entry : m.getPostconditions().entrySet()) { + TypeMirror typeMirror = entry.getValue().getTypeMirror(); + if (typeMirror == null) { + throw new BugInCF( + "null TypeMirror in AField inferred by WPI postcondition inference. AField: " + + entry.getValue().toString()); + } + Collections.sort(result, Ordering.usingToString()); + return result; } - return irrelevantExtraMessage; - } - - /** - * Return the type of the default value of the given type. The default value is 0, false, or null. - * - * @param typeMirror a type - * @return the annotated type of {@code type}'s default value - */ - // TODO: Cache results to avoid recomputation. - public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { - Tree defaultValueTree = TreeUtils.getDefaultValueTree(typeMirror, processingEnv); - TypeMirror defaultValueTM = TreeUtils.typeOf(defaultValueTree); - AnnotatedTypeMirror defaultValueATM = - AnnotatedTypeMirror.createType(defaultValueTM, this, false); - addComputedTypeAnnotationsWithoutFlow(defaultValueTree, defaultValueATM); - return defaultValueATM; - } - - /* NO-AFU - * Return the contract annotations (that is, pre- and post-conditions) for the given AMethod. Does - * not modify the AMethod. - * - *

          This overload must only be called when using WholeProgramInferenceScenes. - * - * @param m the AFU representation of a method - * @return the contract annotations for the method - */ - /* NO-AFU - public List getContractAnnotations(AMethod m) { - List preconds = getPreconditionAnnotations(m); - List postconds = getPostconditionAnnotations(m, preconds); - - List result = preconds; - result.addAll(postconds); - return result; - } - */ - - /* NO-AFU - * Return the precondition annotations for the given AMethod. Does not modify the AMethod. - * - *

          This overload must only be called when using WholeProgramInferenceScenes. - * - * @param m the AFU representation of a method - * @return the precondition annotations for the method - */ - /* NO-AFU - public List getPreconditionAnnotations(AMethod m) { - int size = m.getPreconditions().size(); - List result = new ArrayList<>(size); - if (size == 0) { + */ + + /* NO-AFU + * Return the contract annotations (that is, pre- and post-conditions) for the given + * CallableDeclarationAnnos. Does not modify the CallableDeclarationAnnos. + * + *

          This overload must only be called when using WholeProgramInferenceJavaParserStorage. + * + * @param methodAnnos annotation data for a method + * @return contract annotations for the method + */ + /* NO-AFU + public List getContractAnnotations( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + List preconds = getPreconditionAnnotations(methodAnnos); + List postconds = getPostconditionAnnotations(methodAnnos, preconds); + + List result = preconds; + result.addAll(postconds); return result; } + */ - WholeProgramInferenceImplementation wholeProgramInference = - (WholeProgramInferenceImplementation) getWholeProgramInference(); - WholeProgramInferenceScenesStorage storage = - (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); - - for (Map.Entry entry : m.getPreconditions().entrySet()) { - TypeMirror typeMirror = entry.getValue().getTypeMirror(); - if (typeMirror == null) { - throw new BugInCF( - "null TypeMirror in AField inferred by WPI precondition inference. AField: " - + entry.getValue().toString()); + /* NO-AFU + * Return the precondition annotations for the given CallableDeclarationAnnos. Does not modify + * the CallableDeclarationAnnos. + * + *

          This overload must only be called when using WholeProgramInferenceJavaParserStorage. + * + * @param methodAnnos annotation data for a method + * @return precondition annotations for the method + */ + /* NO-AFU + public List getPreconditionAnnotations( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { + List result = new ArrayList<>(); + for (Map.Entry> entry : + methodAnnos.getPreconditions().entrySet()) { + result.addAll( + getPreconditionAnnotations( + entry.getKey(), entry.getValue().first, entry.getValue().second)); } Collections.sort(result, Ordering.usingToString()); return result; - } - */ - - /* NO-AFU - * Return the postcondition annotations for the given AMethod. Does not modify the AMethod. - * - *

          This overload must only be called when using WholeProgramInferenceScenes. - * - * @param m the AFU representation of a method - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions - * @return the postcondition annotations for the method - */ - /* NO-AFU - public List getPostconditionAnnotations( - AMethod m, List preconds) { - int size = m.getPostconditions().size(); - List result = new ArrayList<>(size); - if (size == 0) { - return result; } + */ - WholeProgramInferenceImplementation wholeProgramInference = - (WholeProgramInferenceImplementation) getWholeProgramInference(); - WholeProgramInferenceScenesStorage storage = - (WholeProgramInferenceScenesStorage) wholeProgramInference.getStorage(); - - for (Map.Entry entry : m.getPostconditions().entrySet()) { - TypeMirror typeMirror = entry.getValue().getTypeMirror(); - if (typeMirror == null) { - throw new BugInCF( - "null TypeMirror in AField inferred by WPI postcondition inference. AField: " - + entry.getValue().toString()); + /* NO-AFU + * Return the postcondition annotations for the given CallableDeclarationAnnos. Does not modify + * the CallableDeclarationAnnos. + * + *

          This overload must only be called when using WholeProgramInferenceJavaParserStorage. + * + * @param methodAnnos annotation data for a method + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions + * @return postcondition annotations for the method + */ + /* NO-AFU + public List getPostconditionAnnotations( + WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, + List preconds) { + List result = new ArrayList<>(); + for (Map.Entry> entry : + methodAnnos.getPostconditions().entrySet()) { + result.addAll( + getPostconditionAnnotations( + entry.getKey(), entry.getValue().first, entry.getValue().second, preconds)); } Collections.sort(result, Ordering.usingToString()); return result; - } - */ - - /* NO-AFU - * Return the contract annotations (that is, pre- and post-conditions) for the given - * CallableDeclarationAnnos. Does not modify the CallableDeclarationAnnos. - * - *

          This overload must only be called when using WholeProgramInferenceJavaParserStorage. - * - * @param methodAnnos annotation data for a method - * @return contract annotations for the method - */ - /* NO-AFU - public List getContractAnnotations( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - List preconds = getPreconditionAnnotations(methodAnnos); - List postconds = getPostconditionAnnotations(methodAnnos, preconds); - - List result = preconds; - result.addAll(postconds); - return result; - } - */ - - /* NO-AFU - * Return the precondition annotations for the given CallableDeclarationAnnos. Does not modify - * the CallableDeclarationAnnos. - * - *

          This overload must only be called when using WholeProgramInferenceJavaParserStorage. - * - * @param methodAnnos annotation data for a method - * @return precondition annotations for the method - */ - /* NO-AFU - public List getPreconditionAnnotations( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { - List result = new ArrayList<>(); - for (Map.Entry> entry : - methodAnnos.getPreconditions().entrySet()) { - result.addAll( - getPreconditionAnnotations( - entry.getKey(), entry.getValue().first, entry.getValue().second)); - } - Collections.sort(result, Ordering.usingToString()); - return result; - } - */ - - /* NO-AFU - * Return the postcondition annotations for the given CallableDeclarationAnnos. Does not modify - * the CallableDeclarationAnnos. - * - *

          This overload must only be called when using WholeProgramInferenceJavaParserStorage. - * - * @param methodAnnos annotation data for a method - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions - * @return postcondition annotations for the method - */ - /* NO-AFU - public List getPostconditionAnnotations( - WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos, - List preconds) { - List result = new ArrayList<>(); - for (Map.Entry> entry : - methodAnnos.getPostconditions().entrySet()) { - result.addAll( - getPostconditionAnnotations( - entry.getKey(), entry.getValue().first, entry.getValue().second, preconds)); - } - Collections.sort(result, Ordering.usingToString()); - return result; - } - */ - - /* NO-AFU - * Returns a list of inferred {@code @RequiresQualifier} annotations for the given expression. - * By default this list does not include any qualifier that has elements/arguments, which - * {@code @RequiresQualifier} does not support. Subclasses may remove this restriction by - * overriding {@link #createRequiresOrEnsuresQualifier}. - * - *

          Each annotation in the list is of the form - * {@code @RequiresQualifier(expression="expression", qualifier=MyQual.class)}. {@code - * expression} must be a valid Java Expression string, in the same format used by {@link - * RequiresQualifier}. - * - * @param expression an expression - * @param inferredType the type of the expression, on method entry - * @param declaredType the declared type of the expression - * @return precondition annotations for the element (possibly an empty list) - */ - /* NO-AFU - public final List getPreconditionAnnotations( - String expression, AnnotatedTypeMirror inferredType, AnnotatedTypeMirror declaredType) { - return getPreOrPostconditionAnnotations( - expression, inferredType, declaredType, BeforeOrAfter.BEFORE, null); - } - */ - - /* NO-AFU - * Returns a list of inferred {@code @EnsuresQualifier} annotations for the given expression. By - * default this list does not include any qualifier that has elements/arguments, which - * {@code @EnsuresQualifier} does not support; and, preconditions are not used to suppress - * redundant postconditions. Subclasses may remove these restrictions by overriding {@link - * #createRequiresOrEnsuresQualifier}. - * - *

          Each annotation in the list is of the form - * {@code @EnsuresQualifier(expression="expression", qualifier=MyQual.class)}. {@code - * expression} must be a valid Java Expression string, in the same format used by {@link - * EnsuresQualifier}. - * - * @param expression an expression - * @param inferredType the type of the expression, on method exit - * @param declaredType the declared type of the expression - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions - * @return postcondition annotations for the element (possibly an empty list) - */ - /* NO-AFU - public final List getPostconditionAnnotations( - String expression, - AnnotatedTypeMirror inferredType, - AnnotatedTypeMirror declaredType, - List preconds) { - return getPreOrPostconditionAnnotations( - expression, inferredType, declaredType, BeforeOrAfter.AFTER, preconds); - } - */ - - /* NO-AFU - * Creates pre- and postcondition annotations. Helper method for {@link - * #getPreconditionAnnotations} and {@link #getPostconditionAnnotations}. - * - *

          Returns a {@code @RequiresQualifier} or {@code @EnsuresQualifier} annotation for the given - * expression. Returns an empty list if none can be created, because the qualifier has - * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not - * support. - * - *

          This implementation makes no assumptions about preconditions suppressing postconditions, - * but subclasses may do so. - * - * @param expression an expression whose type annotations to return - * @param inferredType the type of the expression, on method entry or exit (depending on the - * value of {@code preOrPost}) - * @param declaredType the declared type of the expression, which is used to determine if the - * inferred type supplies no additional information beyond the declared type - * @param preOrPost whether to return preconditions or postconditions - * @param preconds the precondition annotations for the method; used to suppress redundant - * postconditions; non-null exactly when {@code preOrPost} is {@code AFTER} - * @return precondition or postcondition annotations for the element (possibly an empty list) - */ - /* NO-AFU - protected List getPreOrPostconditionAnnotations( - String expression, - AnnotatedTypeMirror inferredType, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - assert (preOrPost == BeforeOrAfter.BEFORE) == (preconds == null); - - if (getWholeProgramInference() == null) { - return Collections.emptyList(); - } - - // TODO: should this only check the top-level annotations? - if (declaredType.equals(inferredType)) { - return Collections.emptyList(); - } - - List result = new ArrayList<>(); - for (AnnotationMirror inferredAm : inferredType.getAnnotations()) { - AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); - if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { - continue; + } + */ + + /* NO-AFU + * Returns a list of inferred {@code @RequiresQualifier} annotations for the given expression. + * By default this list does not include any qualifier that has elements/arguments, which + * {@code @RequiresQualifier} does not support. Subclasses may remove this restriction by + * overriding {@link #createRequiresOrEnsuresQualifier}. + * + *

          Each annotation in the list is of the form + * {@code @RequiresQualifier(expression="expression", qualifier=MyQual.class)}. {@code + * expression} must be a valid Java Expression string, in the same format used by {@link + * RequiresQualifier}. + * + * @param expression an expression + * @param inferredType the type of the expression, on method entry + * @param declaredType the declared type of the expression + * @return precondition annotations for the element (possibly an empty list) + */ + /* NO-AFU + public final List getPreconditionAnnotations( + String expression, AnnotatedTypeMirror inferredType, AnnotatedTypeMirror declaredType) { + return getPreOrPostconditionAnnotations( + expression, inferredType, declaredType, BeforeOrAfter.BEFORE, null); + } + */ + + /* NO-AFU + * Returns a list of inferred {@code @EnsuresQualifier} annotations for the given expression. By + * default this list does not include any qualifier that has elements/arguments, which + * {@code @EnsuresQualifier} does not support; and, preconditions are not used to suppress + * redundant postconditions. Subclasses may remove these restrictions by overriding {@link + * #createRequiresOrEnsuresQualifier}. + * + *

          Each annotation in the list is of the form + * {@code @EnsuresQualifier(expression="expression", qualifier=MyQual.class)}. {@code + * expression} must be a valid Java Expression string, in the same format used by {@link + * EnsuresQualifier}. + * + * @param expression an expression + * @param inferredType the type of the expression, on method exit + * @param declaredType the declared type of the expression + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions + * @return postcondition annotations for the element (possibly an empty list) + */ + /* NO-AFU + public final List getPostconditionAnnotations( + String expression, + AnnotatedTypeMirror inferredType, + AnnotatedTypeMirror declaredType, + List preconds) { + return getPreOrPostconditionAnnotations( + expression, inferredType, declaredType, BeforeOrAfter.AFTER, preconds); + } + */ + + /* NO-AFU + * Creates pre- and postcondition annotations. Helper method for {@link + * #getPreconditionAnnotations} and {@link #getPostconditionAnnotations}. + * + *

          Returns a {@code @RequiresQualifier} or {@code @EnsuresQualifier} annotation for the given + * expression. Returns an empty list if none can be created, because the qualifier has + * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not + * support. + * + *

          This implementation makes no assumptions about preconditions suppressing postconditions, + * but subclasses may do so. + * + * @param expression an expression whose type annotations to return + * @param inferredType the type of the expression, on method entry or exit (depending on the + * value of {@code preOrPost}) + * @param declaredType the declared type of the expression, which is used to determine if the + * inferred type supplies no additional information beyond the declared type + * @param preOrPost whether to return preconditions or postconditions + * @param preconds the precondition annotations for the method; used to suppress redundant + * postconditions; non-null exactly when {@code preOrPost} is {@code AFTER} + * @return precondition or postcondition annotations for the element (possibly an empty list) + */ + /* NO-AFU + protected List getPreOrPostconditionAnnotations( + String expression, + AnnotatedTypeMirror inferredType, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + assert (preOrPost == BeforeOrAfter.BEFORE) == (preconds == null); + + if (getWholeProgramInference() == null) { + return Collections.emptyList(); } // TODO: should this only check the top-level annotations? if (declaredType.equals(inferredType)) { - return Collections.emptyList(); + return Collections.emptyList(); } List result = new ArrayList<>(); for (AnnotationMirror inferredAm : inferredType.getAnnotations()) { - AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); - if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { - continue; - } - AnnotationMirror anno = - createRequiresOrEnsuresQualifier( - expression, inferredAm, declaredType, preOrPost, preconds); - if (anno != null) { - result.add(anno); - } - } - return result; - } - */ - - /** - * Matches parameter expressions as they appear in {@link EnsuresQualifier} and {@link - * RequiresQualifier} annotations, e.g. "#1", "#2", etc. - */ - protected static final Pattern formalParameterPattern = Pattern.compile("^#[0-9]+$"); - - /** - * Creates a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for - * the given expression. - * - *

          This is of the form {@code @RequiresQualifier(expression="expression", - * qualifier=MyQual.class)} or {@code @EnsuresQualifier(expression="expression", - * qualifier=MyQual.class)}, where "expression" is exactly the string {@code expression} and - * MyQual is the annotation represented by {@code qualifier}. - * - *

          Returns null if the expression is invalid when combined with the kind of annotation: for - * example, precondition annotations on "this" and parameters ("#1", etc.) are not supported, - * because receiver/parameter annotations should be inferred instead. - * - *

          This implementation returns null if no annotation can be created, because the qualifier has - * elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not - * support. Subclasses may override this method to return qualifiers that do have arguments - * instead of returning null. - * - * @param expression the expression to which the qualifier applies - * @param qualifier the qualifier that must be present - * @param declaredType the declared type of the expression, which is used to avoid inferring - * redundant pre- or postcondition annotations - * @param preOrPost whether to return a precondition or postcondition annotation - * @param preconds the list of precondition annotations; used to suppress redundant - * postconditions; non-null exactly when {@code preOrPost} is {@code BeforeOrAfter.BEFORE} - * @return a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for - * the given expression, or null - */ - protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( - String expression, - AnnotationMirror qualifier, - AnnotatedTypeMirror declaredType, - Analysis.BeforeOrAfter preOrPost, - @Nullable List preconds) { - // Do not generate RequiresQualifier annotations for "this" or parameter expressions. - if (preOrPost == BeforeOrAfter.BEFORE - && ("this".equals(expression) || formalParameterPattern.matcher(expression).matches())) { - return null; - } - if (!qualifier.getElementValues().isEmpty()) { - // @RequiresQualifier and @EnsuresQualifier do not yet support annotations with - // elements/arguments. - return null; - } - - AnnotationBuilder builder = - new AnnotationBuilder( - processingEnv, - preOrPost == BeforeOrAfter.BEFORE ? RequiresQualifier.class : EnsuresQualifier.class); - builder.setValue("expression", new String[] {expression}); - builder.setValue("qualifier", AnnotationUtils.annotationMirrorToClass(qualifier)); - return builder.build(); - } - - /** - * Add a new entry to the shared CFG. If this is a subchecker, this method delegates to the - * superchecker's GenericAnnotatedTypeFactory, if it exists. Duplicate keys must map to the same - * CFG. - * - *

          Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is - * nonsensical to have a shared CFG when a checker is running alone. - * - * @param tree the source code corresponding to cfg - * @param cfg the control flow graph to use for tree - * @return whether a shared CFG was found to actually add to (duplicate keys also return true) - */ - public boolean addSharedCFGForTree(Tree tree, ControlFlowGraph cfg) { - if (!shouldCache) { - return false; - } - BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); - @SuppressWarnings("interning") // Checking reference equality. - boolean parentIsThisChecker = parentChecker == this.checker; - if (parentIsThisChecker) { - // This is the ultimate parent. - if (this.subcheckerSharedCFG == null) { - this.subcheckerSharedCFG = new HashMap<>(getCacheSize()); - } - if (!this.subcheckerSharedCFG.containsKey(tree)) { - this.subcheckerSharedCFG.put(tree, cfg); - } else { - assert this.subcheckerSharedCFG.get(tree).equals(cfg); - } - return true; - } - - // This is a subchecker. - if (parentChecker != null) { - GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); - return parentAtf.addSharedCFGForTree(tree, cfg); - } else { - return false; - } - } - - /** - * Get the shared control flow graph used for {@code tree} by this checker's topmost superchecker. - * Returns null if no information is available about the given tree, or if this checker has a - * parent checker that does not have a GenericAnnotatedTypeFactory. - * - *

          Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is - * nonsensical to have a shared CFG when a checker is running alone. - * - * @param tree the tree whose CFG should be looked up - * @return the CFG stored by this checker's uppermost superchecker for tree, or null if it is not - * available - */ - public @Nullable ControlFlowGraph getSharedCFGForTree(Tree tree) { - if (!shouldCache) { - return null; - } - BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); - @SuppressWarnings("interning") // Checking reference equality. - boolean parentIsThisChecker = parentChecker == this.checker; - if (parentIsThisChecker) { - // This is the ultimate parent; - return this.subcheckerSharedCFG == null - ? null - : this.subcheckerSharedCFG.getOrDefault(tree, null); - } - - // This is a subchecker. - if (parentChecker != null) { - GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); - return parentAtf.getSharedCFGForTree(tree); - } else { - return null; - } - } - - /** - * If kind = CONDITIONALPOSTCONDITION, return the result element, e.g. {@link - * EnsuresQualifierIf#result}. Otherwise, return null. - * - * @param kind the kind of {@code contractAnnotation} - * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link - * EnsuresQualifierIf} - * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a - * {@code result} element - */ - public @Nullable Boolean getEnsuresQualifierIfResult( - Contract.Kind kind, AnnotationMirror contractAnnotation) { - if (kind == Contract.Kind.CONDITIONALPOSTCONDITION) { - if (contractAnnotation instanceof EnsuresQualifierIf) { - // It's the framework annotation @EnsuresQualifierIf - return AnnotationUtils.getElementValueBoolean( - contractAnnotation, ensuresQualifierIfResultElement, /*default is irrelevant*/ false); - } else { - // It's a checker-specific annotation such as @EnsuresMinLenIf + AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); + if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { + continue; + } + + // TODO: should this only check the top-level annotations? + if (declaredType.equals(inferredType)) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (AnnotationMirror inferredAm : inferredType.getAnnotations()) { + AnnotationMirror declaredAm = declaredType.getAnnotationInHierarchy(inferredAm); + if (declaredAm == null || AnnotationUtils.areSame(inferredAm, declaredAm)) { + continue; + } + AnnotationMirror anno = + createRequiresOrEnsuresQualifier( + expression, inferredAm, declaredType, preOrPost, preconds); + if (anno != null) { + result.add(anno); + } + } + return result; + } + */ + + /** + * Matches parameter expressions as they appear in {@link EnsuresQualifier} and {@link + * RequiresQualifier} annotations, e.g. "#1", "#2", etc. + */ + protected static final Pattern formalParameterPattern = Pattern.compile("^#[0-9]+$"); + + /** + * Creates a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for + * the given expression. + * + *

          This is of the form {@code @RequiresQualifier(expression="expression", + * qualifier=MyQual.class)} or {@code @EnsuresQualifier(expression="expression", + * qualifier=MyQual.class)}, where "expression" is exactly the string {@code expression} and + * MyQual is the annotation represented by {@code qualifier}. + * + *

          Returns null if the expression is invalid when combined with the kind of annotation: for + * example, precondition annotations on "this" and parameters ("#1", etc.) are not supported, + * because receiver/parameter annotations should be inferred instead. + * + *

          This implementation returns null if no annotation can be created, because the qualifier + * has elements/arguments, which {@code @RequiresQualifier} and {@code @EnsuresQualifier} do not + * support. Subclasses may override this method to return qualifiers that do have arguments + * instead of returning null. + * + * @param expression the expression to which the qualifier applies + * @param qualifier the qualifier that must be present + * @param declaredType the declared type of the expression, which is used to avoid inferring + * redundant pre- or postcondition annotations + * @param preOrPost whether to return a precondition or postcondition annotation + * @param preconds the list of precondition annotations; used to suppress redundant + * postconditions; non-null exactly when {@code preOrPost} is {@code BeforeOrAfter.BEFORE} + * @return a {@code RequiresQualifier("...")} or {@code EnsuresQualifier("...")} annotation for + * the given expression, or null + */ + protected @Nullable AnnotationMirror createRequiresOrEnsuresQualifier( + String expression, + AnnotationMirror qualifier, + AnnotatedTypeMirror declaredType, + Analysis.BeforeOrAfter preOrPost, + @Nullable List preconds) { + // Do not generate RequiresQualifier annotations for "this" or parameter expressions. + if (preOrPost == BeforeOrAfter.BEFORE + && ("this".equals(expression) + || formalParameterPattern.matcher(expression).matches())) { + return null; + } + if (!qualifier.getElementValues().isEmpty()) { + // @RequiresQualifier and @EnsuresQualifier do not yet support annotations with + // elements/arguments. + return null; + } + + AnnotationBuilder builder = + new AnnotationBuilder( + processingEnv, + preOrPost == BeforeOrAfter.BEFORE + ? RequiresQualifier.class + : EnsuresQualifier.class); + builder.setValue("expression", new String[] {expression}); + builder.setValue("qualifier", AnnotationUtils.annotationMirrorToClass(qualifier)); + return builder.build(); + } + + /** + * Add a new entry to the shared CFG. If this is a subchecker, this method delegates to the + * superchecker's GenericAnnotatedTypeFactory, if it exists. Duplicate keys must map to the same + * CFG. + * + *

          Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is + * nonsensical to have a shared CFG when a checker is running alone. + * + * @param tree the source code corresponding to cfg + * @param cfg the control flow graph to use for tree + * @return whether a shared CFG was found to actually add to (duplicate keys also return true) + */ + public boolean addSharedCFGForTree(Tree tree, ControlFlowGraph cfg) { + if (!shouldCache) { + return false; + } + BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); + @SuppressWarnings("interning") // Checking reference equality. + boolean parentIsThisChecker = parentChecker == this.checker; + if (parentIsThisChecker) { + // This is the ultimate parent. + if (this.subcheckerSharedCFG == null) { + this.subcheckerSharedCFG = new HashMap<>(getCacheSize()); + } + if (!this.subcheckerSharedCFG.containsKey(tree)) { + this.subcheckerSharedCFG.put(tree, cfg); + } else { + assert this.subcheckerSharedCFG.get(tree).equals(cfg); + } + return true; + } + + // This is a subchecker. + if (parentChecker != null) { + GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); + return parentAtf.addSharedCFGForTree(tree, cfg); + } else { + return false; + } + } + + /** + * Get the shared control flow graph used for {@code tree} by this checker's topmost + * superchecker. Returns null if no information is available about the given tree, or if this + * checker has a parent checker that does not have a GenericAnnotatedTypeFactory. + * + *

          Calls to this method should be guarded by checking {@link #hasOrIsSubchecker}; it is + * nonsensical to have a shared CFG when a checker is running alone. + * + * @param tree the tree whose CFG should be looked up + * @return the CFG stored by this checker's uppermost superchecker for tree, or null if it is + * not available + */ + public @Nullable ControlFlowGraph getSharedCFGForTree(Tree tree) { + if (!shouldCache) { + return null; + } + BaseTypeChecker parentChecker = this.checker.getUltimateParentChecker(); + @SuppressWarnings("interning") // Checking reference equality. + boolean parentIsThisChecker = parentChecker == this.checker; + if (parentIsThisChecker) { + // This is the ultimate parent; + return this.subcheckerSharedCFG == null + ? null + : this.subcheckerSharedCFG.getOrDefault(tree, null); + } + + // This is a subchecker. + if (parentChecker != null) { + GenericAnnotatedTypeFactory parentAtf = parentChecker.getTypeFactory(); + return parentAtf.getSharedCFGForTree(tree); + } else { + return null; + } + } + + /** + * If kind = CONDITIONALPOSTCONDITION, return the result element, e.g. {@link + * EnsuresQualifierIf#result}. Otherwise, return null. + * + * @param kind the kind of {@code contractAnnotation} + * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifierIf} + * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have + * a {@code result} element + */ + public @Nullable Boolean getEnsuresQualifierIfResult( + Contract.Kind kind, AnnotationMirror contractAnnotation) { + if (kind == Contract.Kind.CONDITIONALPOSTCONDITION) { + if (contractAnnotation instanceof EnsuresQualifierIf) { + // It's the framework annotation @EnsuresQualifierIf + return AnnotationUtils.getElementValueBoolean( + contractAnnotation, + ensuresQualifierIfResultElement, /*default is irrelevant*/ + false); + } else { + // It's a checker-specific annotation such as @EnsuresMinLenIf + @SuppressWarnings("deprecation") // concrete annotation class is not known + Boolean result = + AnnotationUtils.getElementValue( + contractAnnotation, "result", Boolean.class, false); + return result; + } + } else { + return null; + } + } + + /** + * If {@code contractAnnotation} is a framework annotation, return its {@code expression} + * element. Otherwise, {@code contractAnnotation} is defined in a checker. If kind = + * CONDITIONALPOSTCONDITION, return its {@code expression} element, else return its {@code + * value} element. + * + * @param kind the kind of {@code contractAnnotation} + * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifierIf} + * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have + * a {@code result} element + */ + public @Nullable List getContractExpressions( + Contract.Kind kind, AnnotationMirror contractAnnotation) { + // First, handle framework annotations. + if (contractAnnotation instanceof RequiresQualifier) { + return AnnotationUtils.getElementValueArray( + contractAnnotation, requiresQualifierExpressionElement, String.class); + } else if (contractAnnotation instanceof EnsuresQualifier) { + return AnnotationUtils.getElementValueArray( + contractAnnotation, ensuresQualifierExpressionElement, String.class); + } else if (contractAnnotation instanceof EnsuresQualifierIf) { + return AnnotationUtils.getElementValueArray( + contractAnnotation, ensuresQualifierIfExpressionElement, String.class); + } + // `contractAnnotation` is defined in a checker. + String elementName = + kind == Contract.Kind.CONDITIONALPOSTCONDITION ? "expression" : "value"; @SuppressWarnings("deprecation") // concrete annotation class is not known - Boolean result = - AnnotationUtils.getElementValue(contractAnnotation, "result", Boolean.class, false); + List result = + AnnotationUtils.getElementValueArray( + contractAnnotation, elementName, String.class, true); return result; - } - } else { - return null; - } - } - - /** - * If {@code contractAnnotation} is a framework annotation, return its {@code expression} element. - * Otherwise, {@code contractAnnotation} is defined in a checker. If kind = - * CONDITIONALPOSTCONDITION, return its {@code expression} element, else return its {@code value} - * element. - * - * @param kind the kind of {@code contractAnnotation} - * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link - * EnsuresQualifierIf} - * @return the {@code result} element of {@code contractAnnotation}, or null if it doesn't have a - * {@code result} element - */ - public @Nullable List getContractExpressions( - Contract.Kind kind, AnnotationMirror contractAnnotation) { - // First, handle framework annotations. - if (contractAnnotation instanceof RequiresQualifier) { - return AnnotationUtils.getElementValueArray( - contractAnnotation, requiresQualifierExpressionElement, String.class); - } else if (contractAnnotation instanceof EnsuresQualifier) { - return AnnotationUtils.getElementValueArray( - contractAnnotation, ensuresQualifierExpressionElement, String.class); - } else if (contractAnnotation instanceof EnsuresQualifierIf) { - return AnnotationUtils.getElementValueArray( - contractAnnotation, ensuresQualifierIfExpressionElement, String.class); - } - // `contractAnnotation` is defined in a checker. - String elementName = kind == Contract.Kind.CONDITIONALPOSTCONDITION ? "expression" : "value"; - @SuppressWarnings("deprecation") // concrete annotation class is not known - List result = - AnnotationUtils.getElementValueArray(contractAnnotation, elementName, String.class, true); - return result; - } + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java index 1796ef1b894..90f873429e3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/HashcodeAtmVisitor.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.type; -import java.util.Objects; import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner; +import java.util.Objects; + /** * Computes the hashcode of an AnnotatedTypeMirror using the underlying type and primary annotations * and the hash code of component types of AnnotatedTypeMirror. @@ -14,24 +15,24 @@ */ public class HashcodeAtmVisitor extends SimpleAnnotatedTypeScanner { - /** Creates a {@link HashcodeAtmVisitor}. */ - public HashcodeAtmVisitor() { - super(Integer::sum, 0); - } + /** Creates a {@link HashcodeAtmVisitor}. */ + public HashcodeAtmVisitor() { + super(Integer::sum, 0); + } - /** - * Generates hashcode for type using the underlying type and the primary annotation. This method - * does not descend into component types (this occurs in the scan method) - * - * @param type the type - */ - @Override - protected Integer defaultAction(AnnotatedTypeMirror type, Void v) { - // To differentiate between partially initialized type's (which may have null components) - // and fully initialized types, null values are allowed - if (type == null) { - return 0; + /** + * Generates hashcode for type using the underlying type and the primary annotation. This method + * does not descend into component types (this occurs in the scan method) + * + * @param type the type + */ + @Override + protected Integer defaultAction(AnnotatedTypeMirror type, Void v) { + // To differentiate between partially initialized type's (which may have null components) + // and fully initialized types, null values are allowed + if (type == null) { + return 0; + } + return Objects.hash(type.getUnderlyingTypeHashCode(), type.getAnnotations().toString()); } - return Objects.hash(type.getUnderlyingTypeHashCode(), type.getAnnotations().toString()); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java index b7926bf874a..5e1d89d6bfd 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/MostlyNoElementQualifierHierarchy.java @@ -1,14 +1,16 @@ package org.checkerframework.framework.type; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.util.QualifierKind; +import org.checkerframework.framework.util.QualifierKindHierarchy; + import java.lang.annotation.Annotation; import java.util.Collection; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.util.QualifierKind; -import org.checkerframework.framework.util.QualifierKindHierarchy; /** * A {@link org.checkerframework.framework.type.QualifierHierarchy} where qualifiers may be @@ -38,121 +40,121 @@ @AnnotatedFor("nullness") public abstract class MostlyNoElementQualifierHierarchy extends ElementQualifierHierarchy { - /** - * Creates a MostlyNoElementQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - * @param atypeFactory the associated type factory - */ - protected MostlyNoElementQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, elements, atypeFactory); - } + /** + * Creates a MostlyNoElementQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + * @param atypeFactory the associated type factory + */ + protected MostlyNoElementQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, elements, atypeFactory); + } - @Override - public final boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - QualifierKind subKind = getQualifierKind(subAnno); - QualifierKind superKind = getQualifierKind(superAnno); - if (subKind.isSubtypeOf(superKind)) { - if (superKind.hasElements() && subKind.hasElements()) { - return isSubtypeWithElements(subAnno, subKind, superAnno, superKind); - } else { - return true; - } + @Override + public final boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + QualifierKind subKind = getQualifierKind(subAnno); + QualifierKind superKind = getQualifierKind(superAnno); + if (subKind.isSubtypeOf(superKind)) { + if (superKind.hasElements() && subKind.hasElements()) { + return isSubtypeWithElements(subAnno, subKind, superAnno, superKind); + } else { + return true; + } + } + return false; } - return false; - } - /** - * Returns true if {@code subAnno} is a subtype of {@code superAnno}. Both {@code subAnno} and - * {@code superAnno} are annotations with elements. {@code subKind} is a sub qualifier kind of - * {@code superKind}. - * - * @param subAnno possible subtype annotation; has elements - * @param subKind the QualifierKind of {@code subAnno} - * @param superAnno possible super annotation; has elements - * @param superKind the QualifierKind of {@code superAnno} - * @return true if {@code subAnno} is a subtype of {@code superAnno} - */ - protected abstract boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind); + /** + * Returns true if {@code subAnno} is a subtype of {@code superAnno}. Both {@code subAnno} and + * {@code superAnno} are annotations with elements. {@code subKind} is a sub qualifier kind of + * {@code superKind}. + * + * @param subAnno possible subtype annotation; has elements + * @param subKind the QualifierKind of {@code subAnno} + * @param superAnno possible super annotation; has elements + * @param superKind the QualifierKind of {@code superAnno} + * @return true if {@code subAnno} is a subtype of {@code superAnno} + */ + protected abstract boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind); - @Override - public final @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); - QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); - if (lub == null) { - // Qualifiers are not in the same hierarchy. - return null; - } - if (lub.hasElements()) { - return leastUpperBoundWithElements(a1, qual1, a2, qual2, lub); + @Override + public final @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); + QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); + if (lub == null) { + // Qualifiers are not in the same hierarchy. + return null; + } + if (lub.hasElements()) { + return leastUpperBoundWithElements(a1, qual1, a2, qual2, lub); + } + return kindToElementlessQualifier.get(lub); } - return kindToElementlessQualifier.get(lub); - } - /** - * Returns the least upper bound of {@code a1} and {@code a2} in cases where the lub of {@code - * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the lub of - * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link - * #leastUpperBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns - * the correct {@code AnnotationMirror} without calling this method. - * - * @param a1 first annotation - * @param qualifierKind1 QualifierKind for {@code a1} - * @param a2 second annotation - * @param qualifierKind2 QualifierKind for {@code a2} - * @param lubKind the kind of the lub of {@code qualifierKind1} and {@code qualifierKind2} - * @return the least upper bound of {@code a1} and {@code a2} - */ - protected abstract AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind); + /** + * Returns the least upper bound of {@code a1} and {@code a2} in cases where the lub of {@code + * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the lub + * of {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link + * #leastUpperBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns + * the correct {@code AnnotationMirror} without calling this method. + * + * @param a1 first annotation + * @param qualifierKind1 QualifierKind for {@code a1} + * @param a2 second annotation + * @param qualifierKind2 QualifierKind for {@code a2} + * @param lubKind the kind of the lub of {@code qualifierKind1} and {@code qualifierKind2} + * @return the least upper bound of {@code a1} and {@code a2} + */ + protected abstract AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind); - @Override - public final @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); - QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); - if (glb == null) { - // Qualifiers are not in the same hierarchy. - return null; - } - if (glb.hasElements()) { - return greatestLowerBoundWithElements(a1, qual1, a2, qual2, glb); + @Override + public final @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); + QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); + if (glb == null) { + // Qualifiers are not in the same hierarchy. + return null; + } + if (glb.hasElements()) { + return greatestLowerBoundWithElements(a1, qual1, a2, qual2, glb); + } + return kindToElementlessQualifier.get(glb); } - return kindToElementlessQualifier.get(glb); - } - /** - * Returns the greatest lower bound of {@code a1} and {@code a2} in cases where the glb of {@code - * qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If the glb of - * {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then {@link - * #greatestLowerBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} returns - * the correct {@code AnnotationMirror} without calling this method. - * - * @param a1 first annotation - * @param qualifierKind1 QualifierKind for {@code a1} - * @param a2 second annotation - * @param qualifierKind2 QualifierKind for {@code a2} - * @return the greatest lower bound between {@code a1} and {@code a2} - */ - protected abstract AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind); + /** + * Returns the greatest lower bound of {@code a1} and {@code a2} in cases where the glb of + * {@code qualifierKind1} and {@code qualifierKind2} is a qualifier kind that has elements. If + * the glb of {@code qualifierKind1} and {@code qualifierKind2} does not have elements, then + * {@link #greatestLowerBoundShallow(AnnotationMirror, TypeMirror, AnnotationMirror, + * TypeMirror)} returns the correct {@code AnnotationMirror} without calling this method. + * + * @param a1 first annotation + * @param qualifierKind1 QualifierKind for {@code a1} + * @param a2 second annotation + * @param qualifierKind2 QualifierKind for {@code a2} + * @return the greatest lower bound between {@code a1} and {@code a2} + */ + protected abstract AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java index 0d35d4f94f2..f4d31d7fc7d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java @@ -1,13 +1,5 @@ package org.checkerframework.framework.type; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -22,6 +14,16 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** * A {@link QualifierHierarchy} where no qualifier has arguments; that is, no qualifier is * represented by an annotation with elements. The meta-annotation {@link @@ -34,226 +36,229 @@ @AnnotatedFor("nullness") public class NoElementQualifierHierarchy extends QualifierHierarchy { - /** {@link QualifierKindHierarchy}. */ - protected final QualifierKindHierarchy qualifierKindHierarchy; + /** {@link QualifierKindHierarchy}. */ + protected final QualifierKindHierarchy qualifierKindHierarchy; - /** Set of top annotation mirrors. */ - protected final AnnotationMirrorSet tops; + /** Set of top annotation mirrors. */ + protected final AnnotationMirrorSet tops; - /** Set of bottom annotation mirrors. */ - protected final AnnotationMirrorSet bottoms; + /** Set of bottom annotation mirrors. */ + protected final AnnotationMirrorSet bottoms; - /** Mapping from {@link QualifierKind} to its corresponding {@link AnnotationMirror}. */ - protected final Map kindToAnnotationMirror; + /** Mapping from {@link QualifierKind} to its corresponding {@link AnnotationMirror}. */ + protected final Map kindToAnnotationMirror; - /** Set of all annotations in all the hierarchies. */ - protected final Set qualifiers; + /** Set of all annotations in all the hierarchies. */ + protected final Set qualifiers; - /** - * Creates a NoElementQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - * @param atypeFactory the associated type factory - */ - public NoElementQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); + /** + * Creates a NoElementQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + * @param atypeFactory the associated type factory + */ + public NoElementQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); - this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); + this.qualifierKindHierarchy = createQualifierKindHierarchy(qualifierClasses); - this.kindToAnnotationMirror = createAnnotationMirrors(elements); - this.qualifiers = AnnotationMirrorSet.unmodifiableSet(kindToAnnotationMirror.values()); + this.kindToAnnotationMirror = createAnnotationMirrors(elements); + this.qualifiers = AnnotationMirrorSet.unmodifiableSet(kindToAnnotationMirror.values()); - this.tops = createTops(); - this.bottoms = createBottoms(); - } - - /** - * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of - * QualifierKindHierarchy.) - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @return the newly created qualifier kind hierarchy - */ - protected QualifierKindHierarchy createQualifierKindHierarchy( - @UnderInitialization NoElementQualifierHierarchy this, - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses); - } + this.tops = createTops(); + this.bottoms = createBottoms(); + } - /** - * Creates and returns a mapping from qualifier kind to an annotation mirror created from the - * qualifier kind's annotation class. - * - * @param elements element utils - * @return a mapping from qualifier kind to its annotation mirror - */ - @RequiresNonNull("this.qualifierKindHierarchy") - protected Map createAnnotationMirrors( - @UnderInitialization NoElementQualifierHierarchy this, Elements elements) { - Map quals = new TreeMap<>(); - for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { - if (kind.hasElements()) { - throw new TypeSystemError( - kind - + " has elements, so the checker cannot use NoElementQualifierHierarchy." - + " The checker should override createQualifierHierarchy()."); - } - quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + /** + * Create the {@link QualifierKindHierarchy}. (Subclasses may override to return a subclass of + * QualifierKindHierarchy.) + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @return the newly created qualifier kind hierarchy + */ + protected QualifierKindHierarchy createQualifierKindHierarchy( + @UnderInitialization NoElementQualifierHierarchy this, + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses); } - return Collections.unmodifiableMap(quals); - } - /** - * Creates and returns the unmodifiable set of top {@link AnnotationMirror}s. - * - * @return the unmodifiable set of top {@link AnnotationMirror}s - */ - @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) - protected AnnotationMirrorSet createTops(@UnderInitialization NoElementQualifierHierarchy this) { - AnnotationMirrorSet tops = new AnnotationMirrorSet(); - for (QualifierKind top : qualifierKindHierarchy.getTops()) { - @SuppressWarnings("nullness:assignment.type.incompatible" // All QualifierKinds are keys in - // kindToAnnotationMirror - ) - @NonNull AnnotationMirror topAnno = kindToAnnotationMirror.get(top); - tops.add(topAnno); + /** + * Creates and returns a mapping from qualifier kind to an annotation mirror created from the + * qualifier kind's annotation class. + * + * @param elements element utils + * @return a mapping from qualifier kind to its annotation mirror + */ + @RequiresNonNull("this.qualifierKindHierarchy") + protected Map createAnnotationMirrors( + @UnderInitialization NoElementQualifierHierarchy this, Elements elements) { + Map quals = new TreeMap<>(); + for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { + if (kind.hasElements()) { + throw new TypeSystemError( + kind + + " has elements, so the checker cannot use NoElementQualifierHierarchy." + + " The checker should override createQualifierHierarchy()."); + } + quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); + } + return Collections.unmodifiableMap(quals); } - return AnnotationMirrorSet.unmodifiableSet(tops); - } - /** - * Creates and returns the unmodifiable set of bottom {@link AnnotationMirror}s. - * - * @return the unmodifiable set of bottom {@link AnnotationMirror}s - */ - @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) - protected AnnotationMirrorSet createBottoms( - @UnderInitialization NoElementQualifierHierarchy this) { - AnnotationMirrorSet bottoms = new AnnotationMirrorSet(); - for (QualifierKind bottom : qualifierKindHierarchy.getBottoms()) { - @SuppressWarnings("nullness:assignment.type.incompatible" // All QualifierKinds are keys in - // kindToAnnotationMirror - ) - @NonNull AnnotationMirror bottomAnno = kindToAnnotationMirror.get(bottom); - bottoms.add(bottomAnno); + /** + * Creates and returns the unmodifiable set of top {@link AnnotationMirror}s. + * + * @return the unmodifiable set of top {@link AnnotationMirror}s + */ + @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) + protected AnnotationMirrorSet createTops( + @UnderInitialization NoElementQualifierHierarchy this) { + AnnotationMirrorSet tops = new AnnotationMirrorSet(); + for (QualifierKind top : qualifierKindHierarchy.getTops()) { + @SuppressWarnings( + "nullness:assignment.type.incompatible" // All QualifierKinds are keys in + // kindToAnnotationMirror + ) + @NonNull AnnotationMirror topAnno = kindToAnnotationMirror.get(top); + tops.add(topAnno); + } + return AnnotationMirrorSet.unmodifiableSet(tops); } - return AnnotationMirrorSet.unmodifiableSet(bottoms); - } - /** - * Returns the {@link QualifierKind} for the given annotation. - * - * @param anno an annotation that is a qualifier in this - * @return the {@code QualifierKind} for the given annotation - */ - protected QualifierKind getQualifierKind(AnnotationMirror anno) { - String name = AnnotationUtils.annotationName(anno); - QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); - if (kind == null) { - throw new BugInCF("Annotation not in hierarchy: %s", anno); + /** + * Creates and returns the unmodifiable set of bottom {@link AnnotationMirror}s. + * + * @return the unmodifiable set of bottom {@link AnnotationMirror}s + */ + @RequiresNonNull({"this.kindToAnnotationMirror", "this.qualifierKindHierarchy"}) + protected AnnotationMirrorSet createBottoms( + @UnderInitialization NoElementQualifierHierarchy this) { + AnnotationMirrorSet bottoms = new AnnotationMirrorSet(); + for (QualifierKind bottom : qualifierKindHierarchy.getBottoms()) { + @SuppressWarnings( + "nullness:assignment.type.incompatible" // All QualifierKinds are keys in + // kindToAnnotationMirror + ) + @NonNull AnnotationMirror bottomAnno = kindToAnnotationMirror.get(bottom); + bottoms.add(bottomAnno); + } + return AnnotationMirrorSet.unmodifiableSet(bottoms); } - return kind; - } - @Override - public @Nullable AnnotationMirror findAnnotationInSameHierarchy( - Collection annos, AnnotationMirror annotationMirror) { - if (annos.isEmpty()) { - return null; + /** + * Returns the {@link QualifierKind} for the given annotation. + * + * @param anno an annotation that is a qualifier in this + * @return the {@code QualifierKind} for the given annotation + */ + protected QualifierKind getQualifierKind(AnnotationMirror anno) { + String name = AnnotationUtils.annotationName(anno); + QualifierKind kind = qualifierKindHierarchy.getQualifierKind(name); + if (kind == null) { + throw new BugInCF("Annotation not in hierarchy: %s", anno); + } + return kind; } - QualifierKind kind = getQualifierKind(annotationMirror); - for (AnnotationMirror candidate : annos) { - QualifierKind candidateKind = getQualifierKind(candidate); - if (candidateKind.isInSameHierarchyAs(kind)) { - return candidate; - } + + @Override + public @Nullable AnnotationMirror findAnnotationInSameHierarchy( + Collection annos, AnnotationMirror annotationMirror) { + if (annos.isEmpty()) { + return null; + } + QualifierKind kind = getQualifierKind(annotationMirror); + for (AnnotationMirror candidate : annos) { + QualifierKind candidateKind = getQualifierKind(candidate); + if (candidateKind.isInSameHierarchyAs(kind)) { + return candidate; + } + } + return null; } - return null; - } - @Override - public @Nullable AnnotationMirror findAnnotationInHierarchy( - Collection annos, AnnotationMirror top) { - return findAnnotationInSameHierarchy(annos, top); - } + @Override + public @Nullable AnnotationMirror findAnnotationInHierarchy( + Collection annos, AnnotationMirror top) { + return findAnnotationInSameHierarchy(annos, top); + } - @Override - public AnnotationMirrorSet getTopAnnotations() { - return tops; - } + @Override + public AnnotationMirrorSet getTopAnnotations() { + return tops; + } - @Override - @SuppressWarnings( - "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding - // kindToAnnotationMirror - ) - public AnnotationMirror getTopAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - return kindToAnnotationMirror.get(kind.getTop()); - } + @Override + @SuppressWarnings( + "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding + // kindToAnnotationMirror + ) + public AnnotationMirror getTopAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + return kindToAnnotationMirror.get(kind.getTop()); + } - @Override - public AnnotationMirrorSet getBottomAnnotations() { - return bottoms; - } + @Override + public AnnotationMirrorSet getBottomAnnotations() { + return bottoms; + } - @Override - @SuppressWarnings( - "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding - // kindToAnnotationMirror - ) - public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { - QualifierKind kind = getQualifierKind(start); - return kindToAnnotationMirror.get(kind.getBottom()); - } + @Override + @SuppressWarnings( + "nullness:return.type.incompatible" // every QualifierKind is a key in its corresponding + // kindToAnnotationMirror + ) + public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { + QualifierKind kind = getQualifierKind(start); + return kindToAnnotationMirror.get(kind.getBottom()); + } - @Override - public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { - QualifierKind poly = getQualifierKind(start).getPolymorphic(); - if (poly == null) { - return null; + @Override + public @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { + QualifierKind poly = getQualifierKind(start).getPolymorphic(); + if (poly == null) { + return null; + } + return kindToAnnotationMirror.get(poly); } - return kindToAnnotationMirror.get(poly); - } - @Override - public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { - return getQualifierKind(qualifier).isPoly(); - } + @Override + public boolean isPolymorphicQualifier(AnnotationMirror qualifier) { + return getQualifierKind(qualifier).isPoly(); + } - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - QualifierKind subKind = getQualifierKind(subAnno); - QualifierKind superKind = getQualifierKind(superAnno); - return subKind.isSubtypeOf(superKind); - } + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + QualifierKind subKind = getQualifierKind(subAnno); + QualifierKind superKind = getQualifierKind(superAnno); + return subKind.isSubtypeOf(superKind); + } - @Override - public @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); + @Override + public @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); - QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); - if (lub == null) { - return null; + QualifierKind lub = qualifierKindHierarchy.leastUpperBound(qual1, qual2); + if (lub == null) { + return null; + } + return kindToAnnotationMirror.get(lub); } - return kindToAnnotationMirror.get(lub); - } - @Override - public @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror a1, AnnotationMirror a2) { - QualifierKind qual1 = getQualifierKind(a1); - QualifierKind qual2 = getQualifierKind(a2); - QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); - if (glb == null) { - return null; + @Override + public @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror a1, AnnotationMirror a2) { + QualifierKind qual1 = getQualifierKind(a1); + QualifierKind qual2 = getQualifierKind(a2); + QualifierKind glb = qualifierKindHierarchy.greatestLowerBound(qual1, qual2); + if (glb == null) { + return null; + } + return kindToAnnotationMirror.get(glb); } - return kindToAnnotationMirror.get(glb); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java index 57005a451e7..3d1d0692b2b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.type; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.mustcall.qual.MustCallUnknown; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -16,6 +9,15 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * Represents multiple type qualifier hierarchies. {@link #getWidth} gives the number of hierarchies * that this object represents. Each hierarchy has its own top and bottom, and subtyping @@ -32,790 +34,810 @@ @AnnotatedFor("nullness") public abstract class QualifierHierarchy { - /** The associated type factory. This is used only for checking whether types are relevant. */ - protected GenericAnnotatedTypeFactory atypeFactory; - - /** - * Creates a new QualifierHierarchy. - * - * @param atypeFactory the associated type factory - */ - public QualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } - - /** - * Determine whether this QualifierHierarchy is valid. - * - * @return true if this QualifierHierarchy is valid - */ - public boolean isValid() { - return true; - } - - // ********************************************************************** - // Getter methods about this hierarchy - // ********************************************************************** - - /** - * Returns the width of this hierarchy, i.e. the expected number of annotations on any valid type. - * - * @return the width of this QualifierHierarchy - */ - public int getWidth() { - return getTopAnnotations().size(); - } - - /** - * Returns the top (ultimate super) type qualifiers in the type system. The size of this set is - * equal to {@link #getWidth}. - * - * @return the top (ultimate super) type qualifiers in the type system - */ - public abstract AnnotationMirrorSet getTopAnnotations(); - - /** - * Returns true if the given qualifer is one of the top annotations for this qualifer hierarchy. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return true if the given qualifer is one of the top annotations for this qualifer hierarchy - */ - public boolean isTop(AnnotationMirror qualifier) { - return AnnotationUtils.containsSame(getTopAnnotations(), qualifier); - } - - /** - * Return the top qualifier for the given qualifier, that is, the qualifier that is a supertype of - * {@code qualifier} but no further supertypes exist. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the top qualifier of {@code qualifier}'s hierarchy - */ - public abstract AnnotationMirror getTopAnnotation(AnnotationMirror qualifier); - - /** - * Returns the bottom type qualifiers in the hierarchy. The size of this set is equal to {@link - * #getWidth}. - * - * @return the bottom type qualifiers in the hierarchy - */ - public abstract AnnotationMirrorSet getBottomAnnotations(); - - /** - * Return the bottom for the given qualifier, that is, the qualifier that is a subtype of {@code - * qualifier} but no further subtypes exist. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the bottom qualifier of {@code qualifier}'s hierarchy - */ - public abstract AnnotationMirror getBottomAnnotation(AnnotationMirror qualifier); - - /** - * Returns the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no polymorphic qualifier in that hierarchy. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no polymorphic qualifier in that hierarchy - */ - public abstract @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror qualifier); - - /** - * Returns {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code - * false}. - * - * @param qualifier qualifier - * @return {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code - * false}. - */ - public abstract boolean isPolymorphicQualifier(AnnotationMirror qualifier); - - /** - * Returns the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no parametric qualifier in that hierarchy. - * - * @param qualifier any qualifier from one of the qualifier hierarchies represented by this - * @return the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code - * null} if there is no polymorphic qualifier in that hierarchy - */ - public @Nullable AnnotationMirror getParametricQualifier(AnnotationMirror qualifier) { - return null; - } - - /** - * Returns {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code - * false}. - * - * @param qualifier qualifier - * @return {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code - * false}. - */ - public boolean isParametricQualifier(AnnotationMirror qualifier) { - return false; - } - - // ********************************************************************** - // Qualifier Hierarchy Queries - // ********************************************************************** - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy, ignoring Java basetypes. - * - *

          Clients should generally call {@link #isSubtypeShallow}. However, subtypes should generally - * override this method (if needed). - * - *

          This method behaves the same as {@link #isSubtypeQualifiersOnly(AnnotationMirror, - * AnnotationMirror)}, which calls this method. This method is for clients inside the framework, - * and it has {@code protected} access to prevent use by clients outside the framework. This makes - * it easy to find places where code outside the framework is ignoring Java basetypes -- at calls - * to {@link #isSubtypeQualifiersOnly}. - * - * @param subQualifier possible subqualifier - * @param superQualifier possible superqualifier - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} - */ - protected abstract boolean isSubtypeQualifiers( - AnnotationMirror subQualifier, AnnotationMirror superQualifier); - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy, ignoring Java basetypes. - * - *

          This method is for clients outside the framework, and should not be used by framework code. - * - * @param subQualifier possible subqualifier - * @param superQualifier possible superqualifier - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} - */ - public final boolean isSubtypeQualifiersOnly( - AnnotationMirror subQualifier, AnnotationMirror superQualifier) { - return isSubtypeQualifiers(subQualifier, superQualifier); - } - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy. The types {@code subType} and {@code superType} are - * not necessarily in a Java subtyping relationship with one another and are only used by this - * method for special cases when qualifier subtyping depends on the Java basetype. - * - *

          Clients should usually call {@code isSubtypeShallow()} (this method). Rarely, to ignore the - * Java basetype, a client can call {@link #isSubtypeQualifiersOnly}. - * - *

          Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier - * subtyping depends on Java basetypes. - * - * @param subQualifier possible subqualifier - * @param subType the Java basetype associated with {@code subQualifier} - * @param superQualifier possible superqualifier - * @param superType the Java basetype associated with {@code superQualifier} - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} - */ - @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. - public boolean isSubtypeShallow( - AnnotationMirror subQualifier, - TypeMirror subType, - AnnotationMirror superQualifier, - TypeMirror superType) { - if (!atypeFactory.isRelevant(subType) || !atypeFactory.isRelevant(superType)) { - // At least one of the types is not relevant. - return true; + /** The associated type factory. This is used only for checking whether types are relevant. */ + protected GenericAnnotatedTypeFactory atypeFactory; + + /** + * Creates a new QualifierHierarchy. + * + * @param atypeFactory the associated type factory + */ + public QualifierHierarchy(GenericAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; } - return isSubtypeQualifiers(subQualifier, superQualifier); - } - - /** - * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, - * according to the type qualifier hierarchy. The type {@code typeMirror} is only used by this - * method for special cases when qualifier subtyping depends on the Java basetype. - * - *

          Clients should usually call {@link #isSubtypeShallow(AnnotationMirror, AnnotationMirror, - * TypeMirror)} (this method) or {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, - * AnnotationMirror, TypeMirror)}. Rarely, to ignore the Java basetype, a client can call {@link - * #isSubtypeQualifiersOnly}. - * - *

          Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier - * subtyping depends on Java basetypes. - * - * @param subQualifier possible subqualifier - * @param superQualifier possible superqualifier - * @param typeMirror the Java basetype associated with both {@code subQualifier} and {@code - * superQualifier} - * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code superQualifier} - */ - public final boolean isSubtypeShallow( - AnnotationMirror subQualifier, AnnotationMirror superQualifier, TypeMirror typeMirror) { - return isSubtypeShallow(subQualifier, typeMirror, superQualifier, typeMirror); - } - - /** - * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers}. The types {@code subType} and - * {@code superType} are not necessarily in a Java subtyping relationship with one another and are - * only used by this method for special cases when qualifier subtyping depends on the Java - * basetype. - * - *

          Subtypes of {@code QualifierHierarchy} more often override {@link - * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this - * method. - * - * @param subQualifiers a set of qualifiers; exactly one per hierarchy - * @param subType the type associated with {@code subQualifiers} - * @param superQualifiers a set of qualifiers; exactly one per hierarchy - * @param superType the type associated with {@code superQualifiers} - * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers} - */ - public final boolean isSubtypeShallow( - Collection subQualifiers, - TypeMirror subType, - Collection superQualifiers, - TypeMirror superType) { - assertSameSize(subQualifiers, superQualifiers); - for (AnnotationMirror subQual : subQualifiers) { - AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual); - if (superQual == null) { - throw new BugInCF( - "QualifierHierarchy: missing annotation in hierarchy %s. found: %s", - subQual, StringsPlume.join(",", superQualifiers)); - } - if (!isSubtypeShallow(subQual, subType, superQual, superType)) { - return false; - } + + /** + * Determine whether this QualifierHierarchy is valid. + * + * @return true if this QualifierHierarchy is valid + */ + public boolean isValid() { + return true; } - return true; - } - - /** - * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers}. The types {@code subType} and - * {@code superType} are not necessarily in a Java subtyping relationship with one another and are - * only used by this method for special cases when qualifier subtyping depends on the Java - * basetype. - * - *

          Subtypes of {@code QualifierHierarchy} more often override {@link - * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this - * method. - * - * @param subQualifiers a set of qualifiers; exactly one per hierarchy - * @param superQualifiers a set of qualifiers; exactly one per hierarchy - * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers} - */ - public boolean isSubtypeQualifiersOnly( - Collection subQualifiers, - Collection superQualifiers) { - assertSameSize(subQualifiers, superQualifiers); - for (AnnotationMirror subQual : subQualifiers) { - AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual); - if (superQual == null) { - throw new BugInCF( - "QualifierHierarchy: missing annotation in hierarchy %s. found: %s", - subQual, StringsPlume.join(",", superQualifiers)); - } - if (!isSubtypeQualifiersOnly(subQual, superQual)) { + + // ********************************************************************** + // Getter methods about this hierarchy + // ********************************************************************** + + /** + * Returns the width of this hierarchy, i.e. the expected number of annotations on any valid + * type. + * + * @return the width of this QualifierHierarchy + */ + public int getWidth() { + return getTopAnnotations().size(); + } + + /** + * Returns the top (ultimate super) type qualifiers in the type system. The size of this set is + * equal to {@link #getWidth}. + * + * @return the top (ultimate super) type qualifiers in the type system + */ + public abstract AnnotationMirrorSet getTopAnnotations(); + + /** + * Returns true if the given qualifer is one of the top annotations for this qualifer hierarchy. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return true if the given qualifer is one of the top annotations for this qualifer hierarchy + */ + public boolean isTop(AnnotationMirror qualifier) { + return AnnotationUtils.containsSame(getTopAnnotations(), qualifier); + } + + /** + * Return the top qualifier for the given qualifier, that is, the qualifier that is a supertype + * of {@code qualifier} but no further supertypes exist. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the top qualifier of {@code qualifier}'s hierarchy + */ + public abstract AnnotationMirror getTopAnnotation(AnnotationMirror qualifier); + + /** + * Returns the bottom type qualifiers in the hierarchy. The size of this set is equal to {@link + * #getWidth}. + * + * @return the bottom type qualifiers in the hierarchy + */ + public abstract AnnotationMirrorSet getBottomAnnotations(); + + /** + * Return the bottom for the given qualifier, that is, the qualifier that is a subtype of {@code + * qualifier} but no further subtypes exist. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the bottom qualifier of {@code qualifier}'s hierarchy + */ + public abstract AnnotationMirror getBottomAnnotation(AnnotationMirror qualifier); + + /** + * Returns the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy + */ + public abstract @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror qualifier); + + /** + * Returns {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code + * false}. + * + * @param qualifier qualifier + * @return {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code + * false}. + */ + public abstract boolean isPolymorphicQualifier(AnnotationMirror qualifier); + + /** + * Returns the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no parametric qualifier in that hierarchy. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the parametric qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy + */ + public @Nullable AnnotationMirror getParametricQualifier(AnnotationMirror qualifier) { + return null; + } + + /** + * Returns {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code + * false}. + * + * @param qualifier qualifier + * @return {@code true} if the qualifier is a parametric qualifier; otherwise, returns {@code + * false}. + */ + public boolean isParametricQualifier(AnnotationMirror qualifier) { return false; - } } - return true; - } - - /** - * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier of or equal to the - * qualifier in the same hierarchy in {@code superQualifiers}. The type {@code typeMirror} is only - * used by this method for special cases when qualifier subtyping depends on the Java basetype. - * - *

          Subtypes of {@code QualifierHierarchy} more often override {@link - * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this - * method. - * - * @param subQualifiers a set of qualifiers; exactly one per hierarchy - * @param superQualifiers a set of qualifiers; exactly one per hierarchy - * @param typeMirror the type associated with both sets of qualifiers - * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the - * qualifier in the same hierarchy in {@code superQualifiers} - */ - public final boolean isSubtypeShallow( - Collection subQualifiers, - Collection superQualifiers, - TypeMirror typeMirror) { - return isSubtypeShallow(subQualifiers, typeMirror, superQualifiers, typeMirror); - } - - /** - * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code - * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. - * Ignores Java basetypes. - * - *

          Examples: - * - *

            - *
          • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable - *
          - * - * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} - * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code qualifier1} - * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from - * different hierarchies - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - protected abstract @Nullable AnnotationMirror leastUpperBoundQualifiers( - AnnotationMirror qualifier1, AnnotationMirror qualifier2); - - /** - * Returns the least upper bound of all the collections of qualifiers. The result is the lub of - * the qualifier for the same hierarchy in each set. - * - * @param qualifiers a collection of collections of qualifiers. Each inner collection has exactly - * one qualifier per hierarchy. - * @return the least upper bound of the collections of qualifiers - */ - public Set leastUpperBoundsQualifiersOnly( - Collection> qualifiers) { - if (qualifiers.isEmpty()) { - return AnnotationMirrorSet.emptySet(); + + // ********************************************************************** + // Qualifier Hierarchy Queries + // ********************************************************************** + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy, ignoring Java basetypes. + * + *

          Clients should generally call {@link #isSubtypeShallow}. However, subtypes should + * generally override this method (if needed). + * + *

          This method behaves the same as {@link #isSubtypeQualifiersOnly(AnnotationMirror, + * AnnotationMirror)}, which calls this method. This method is for clients inside the framework, + * and it has {@code protected} access to prevent use by clients outside the framework. This + * makes it easy to find places where code outside the framework is ignoring Java basetypes -- + * at calls to {@link #isSubtypeQualifiersOnly}. + * + * @param subQualifier possible subqualifier + * @param superQualifier possible superqualifier + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code + * superQualifier} + */ + protected abstract boolean isSubtypeQualifiers( + AnnotationMirror subQualifier, AnnotationMirror superQualifier); + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy, ignoring Java basetypes. + * + *

          This method is for clients outside the framework, and should not be used by framework + * code. + * + * @param subQualifier possible subqualifier + * @param superQualifier possible superqualifier + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code + * superQualifier} + */ + public final boolean isSubtypeQualifiersOnly( + AnnotationMirror subQualifier, AnnotationMirror superQualifier) { + return isSubtypeQualifiers(subQualifier, superQualifier); + } + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy. The types {@code subType} and {@code superType} + * are not necessarily in a Java subtyping relationship with one another and are only used by + * this method for special cases when qualifier subtyping depends on the Java basetype. + * + *

          Clients should usually call {@code isSubtypeShallow()} (this method). Rarely, to ignore + * the Java basetype, a client can call {@link #isSubtypeQualifiersOnly}. + * + *

          Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier + * subtyping depends on Java basetypes. + * + * @param subQualifier possible subqualifier + * @param subType the Java basetype associated with {@code subQualifier} + * @param superQualifier possible superqualifier + * @param superType the Java basetype associated with {@code superQualifier} + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code + * superQualifier} + */ + @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. + public boolean isSubtypeShallow( + AnnotationMirror subQualifier, + TypeMirror subType, + AnnotationMirror superQualifier, + TypeMirror superType) { + if (!atypeFactory.isRelevant(subType) || !atypeFactory.isRelevant(superType)) { + // At least one of the types is not relevant. + return true; + } + return isSubtypeQualifiers(subQualifier, superQualifier); } - Iterator> itor = qualifiers.iterator(); - Set result = new AnnotationMirrorSet(itor.next()); - while (itor.hasNext()) { - Collection annos = itor.next(); - result = leastUpperBoundsQualifiersOnly(result, annos); + + /** + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy. The type {@code typeMirror} is only used by this + * method for special cases when qualifier subtyping depends on the Java basetype. + * + *

          Clients should usually call {@link #isSubtypeShallow(AnnotationMirror, AnnotationMirror, + * TypeMirror)} (this method) or {@link #isSubtypeShallow(AnnotationMirror, TypeMirror, + * AnnotationMirror, TypeMirror)}. Rarely, to ignore the Java basetype, a client can call {@link + * #isSubtypeQualifiersOnly}. + * + *

          Subtypes should override {@link #isSubtypeQualifiers} (not this method), unless qualifier + * subtyping depends on Java basetypes. + * + * @param subQualifier possible subqualifier + * @param superQualifier possible superqualifier + * @param typeMirror the Java basetype associated with both {@code subQualifier} and {@code + * superQualifier} + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code + * superQualifier} + */ + public final boolean isSubtypeShallow( + AnnotationMirror subQualifier, AnnotationMirror superQualifier, TypeMirror typeMirror) { + return isSubtypeShallow(subQualifier, typeMirror, superQualifier, typeMirror); + } + + /** + * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers}. The types {@code subType} and + * {@code superType} are not necessarily in a Java subtyping relationship with one another and + * are only used by this method for special cases when qualifier subtyping depends on the Java + * basetype. + * + *

          Subtypes of {@code QualifierHierarchy} more often override {@link + * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this + * method. + * + * @param subQualifiers a set of qualifiers; exactly one per hierarchy + * @param subType the type associated with {@code subQualifiers} + * @param superQualifiers a set of qualifiers; exactly one per hierarchy + * @param superType the type associated with {@code superQualifiers} + * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers} + */ + public final boolean isSubtypeShallow( + Collection subQualifiers, + TypeMirror subType, + Collection superQualifiers, + TypeMirror superType) { + assertSameSize(subQualifiers, superQualifiers); + for (AnnotationMirror subQual : subQualifiers) { + AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual); + if (superQual == null) { + throw new BugInCF( + "QualifierHierarchy: missing annotation in hierarchy %s. found: %s", + subQual, StringsPlume.join(",", superQualifiers)); + } + if (!isSubtypeShallow(subQual, subType, superQual, superType)) { + return false; + } + } + return true; + } + + /** + * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers}. The types {@code subType} and + * {@code superType} are not necessarily in a Java subtyping relationship with one another and + * are only used by this method for special cases when qualifier subtyping depends on the Java + * basetype. + * + *

          Subtypes of {@code QualifierHierarchy} more often override {@link + * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this + * method. + * + * @param subQualifiers a set of qualifiers; exactly one per hierarchy + * @param superQualifiers a set of qualifiers; exactly one per hierarchy + * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers} + */ + public boolean isSubtypeQualifiersOnly( + Collection subQualifiers, + Collection superQualifiers) { + assertSameSize(subQualifiers, superQualifiers); + for (AnnotationMirror subQual : subQualifiers) { + AnnotationMirror superQual = findAnnotationInSameHierarchy(superQualifiers, subQual); + if (superQual == null) { + throw new BugInCF( + "QualifierHierarchy: missing annotation in hierarchy %s. found: %s", + subQual, StringsPlume.join(",", superQualifiers)); + } + if (!isSubtypeQualifiersOnly(subQual, superQual)) { + return false; + } + } + return true; } - return result; - } - - /** - * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code - * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. - * Ignores Java basetypes. - * - *

          Examples: - * - *

            - *
          • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable - *
          - * - * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} - * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code qualifier1} - * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from - * different hierarchies - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - public final @Nullable AnnotationMirror leastUpperBoundQualifiersOnly( - AnnotationMirror qualifier1, AnnotationMirror qualifier2) { - return leastUpperBoundQualifiers(qualifier1, qualifier2); - } - - /** - * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the - * qualifier for the same hierarchy in each set. - * - * @param qualifiers1 a set of qualifiers; exactly one per hierarchy - * @param qualifiers2 a set of qualifiers; exactly one per hierarchy - * @return the least upper bound of the two sets of qualifiers - */ - public Set leastUpperBoundsQualifiersOnly( - Collection qualifiers1, - Collection qualifiers2) { - assertSameSize(qualifiers1, qualifiers2); - if (qualifiers1.isEmpty()) { - throw new BugInCF( - "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); + + /** + * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier of or equal to the + * qualifier in the same hierarchy in {@code superQualifiers}. The type {@code typeMirror} is + * only used by this method for special cases when qualifier subtyping depends on the Java + * basetype. + * + *

          Subtypes of {@code QualifierHierarchy} more often override {@link + * #isSubtypeShallow(AnnotationMirror, TypeMirror, AnnotationMirror, TypeMirror)} than this + * method. + * + * @param subQualifiers a set of qualifiers; exactly one per hierarchy + * @param superQualifiers a set of qualifiers; exactly one per hierarchy + * @param typeMirror the type associated with both sets of qualifiers + * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers} + */ + public final boolean isSubtypeShallow( + Collection subQualifiers, + Collection superQualifiers, + TypeMirror typeMirror) { + return isSubtypeShallow(subQualifiers, typeMirror, superQualifiers, typeMirror); } - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror a1 : qualifiers1) { - for (AnnotationMirror a2 : qualifiers2) { - AnnotationMirror lub = leastUpperBoundQualifiersOnly(a1, a2); - if (lub != null) { - result.add(lub); + /** + * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code + * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier + * hierarchy. Ignores Java basetypes. + * + *

          Examples: + * + *

            + *
          • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable + *
          + * + * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} + * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code + * qualifier1} + * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from + * different hierarchies + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + protected abstract @Nullable AnnotationMirror leastUpperBoundQualifiers( + AnnotationMirror qualifier1, AnnotationMirror qualifier2); + + /** + * Returns the least upper bound of all the collections of qualifiers. The result is the lub of + * the qualifier for the same hierarchy in each set. + * + * @param qualifiers a collection of collections of qualifiers. Each inner collection has + * exactly one qualifier per hierarchy. + * @return the least upper bound of the collections of qualifiers + */ + public Set leastUpperBoundsQualifiersOnly( + Collection> qualifiers) { + if (qualifiers.isEmpty()) { + return AnnotationMirrorSet.emptySet(); + } + Iterator> itor = qualifiers.iterator(); + Set result = new AnnotationMirrorSet(itor.next()); + while (itor.hasNext()) { + Collection annos = itor.next(); + result = leastUpperBoundsQualifiersOnly(result, annos); } - } + return result; } - assertSameSize(result, qualifiers1); - return result; - } - - /** - * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code - * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier hierarchy. - * - *

          Examples: - * - *

            - *
          • leastUpperBound('Nullable', 'NonNull') ⇒ Nullable - *
          - * - * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} - * @param tm1 the type on which qualifier1 appears - * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code qualifier1} - * @param tm2 the type on which qualifier2 appears - * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from - * different hierarchies - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. - public @Nullable AnnotationMirror leastUpperBoundShallow( - AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { - boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); - boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); - if (tm1IsRelevant == tm2IsRelevant) { - return leastUpperBoundQualifiers(qualifier1, qualifier2); - } else if (tm1IsRelevant) { - return qualifier1; - } else { // if (tm2IsRelevant) { - return qualifier2; + /** + * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code + * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier + * hierarchy. Ignores Java basetypes. + * + *

          Examples: + * + *

            + *
          • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable + *
          + * + * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} + * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code + * qualifier1} + * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from + * different hierarchies + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + public final @Nullable AnnotationMirror leastUpperBoundQualifiersOnly( + AnnotationMirror qualifier1, AnnotationMirror qualifier2) { + return leastUpperBoundQualifiers(qualifier1, qualifier2); } - } - - /** - * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the - * qualifier for the same hierarchy in each set. - * - * @param qualifiers1 a set of qualifiers; exactly one per hierarchy - * @param tm1 the type on which qualifiers1 appear - * @param qualifiers2 a set of qualifiers; exactly one per hierarchy - * @param tm2 the type on which qualifiers2 appear - * @return the least upper bound of the two sets of qualifiers - */ - public final Set leastUpperBoundsShallow( - Collection qualifiers1, - TypeMirror tm1, - Collection qualifiers2, - TypeMirror tm2) { - assertSameSize(qualifiers1, qualifiers2); - if (qualifiers1.isEmpty()) { - throw new BugInCF( - "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); + + /** + * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy + * @return the least upper bound of the two sets of qualifiers + */ + public Set leastUpperBoundsQualifiersOnly( + Collection qualifiers1, + Collection qualifiers2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); + } + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror lub = leastUpperBoundQualifiersOnly(a1, a2); + if (lub != null) { + result.add(lub); + } + } + } + + assertSameSize(result, qualifiers1); + return result; } - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror a1 : qualifiers1) { - for (AnnotationMirror a2 : qualifiers2) { - AnnotationMirror lub = leastUpperBoundShallow(a1, tm1, a2, tm2); - if (lub != null) { - result.add(lub); + /** + * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code + * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier + * hierarchy. + * + *

          Examples: + * + *

            + *
          • leastUpperBound('Nullable', 'NonNull') ⇒ Nullable + *
          + * + * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} + * @param tm1 the type on which qualifier1 appears + * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code + * qualifier1} + * @param tm2 the type on which qualifier2 appears + * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from + * different hierarchies + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. + public @Nullable AnnotationMirror leastUpperBoundShallow( + AnnotationMirror qualifier1, + TypeMirror tm1, + AnnotationMirror qualifier2, + TypeMirror tm2) { + boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); + boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); + if (tm1IsRelevant == tm2IsRelevant) { + return leastUpperBoundQualifiers(qualifier1, qualifier2); + } else if (tm1IsRelevant) { + return qualifier1; + } else { // if (tm2IsRelevant) { + return qualifier2; } - } } - assertSameSize(result, qualifiers1); - return result; - } - - /** - * Returns the number of iterations dataflow should perform before {@link - * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never be - * called. - * - * @return the number of iterations dataflow should perform before {@link - * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never - * be called. - */ - public int numberOfIterationsBeforeWidening() { - return -1; - } - - /** - * If the qualifier hierarchy has an infinite ascending chain, then the dataflow analysis might - * never reach a fixed point. To prevent this, implement this method such that it returns an upper - * bound for the two qualifiers that is a strict super type of the least upper bound. If this - * method is implemented, also override {@link #numberOfIterationsBeforeWidening()} to return a - * positive number. - * - *

          {@code newQualifier} is newest qualifier dataflow computed for some expression and {@code - * previousQualifier} is the qualifier dataflow computed on the last iteration. - * - *

          If the qualifier hierarchy has no infinite ascending chain, returns the least upper bound of - * the two annotations. - * - * @param newQualifier new qualifier dataflow computed for some expression; must be in the same - * hierarchy as {@code previousQualifier} - * @param previousQualifier the previous qualifier dataflow computed on the last iteration; must - * be in the same hierarchy as {@code previousQualifier} - * @return an upper bound that is higher than the least upper bound of newQualifier and - * previousQualifier (or the lub if the qualifier hierarchy does not require this) - */ - public AnnotationMirror widenedUpperBound( - AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { - AnnotationMirror widenedUpperBound = leastUpperBoundQualifiers(newQualifier, previousQualifier); - if (widenedUpperBound == null) { - throw new BugInCF( - "widenedUpperBound(%s, %s): unrelated qualifiers", newQualifier, previousQualifier); + /** + * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy + * @param tm1 the type on which qualifiers1 appear + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy + * @param tm2 the type on which qualifiers2 appear + * @return the least upper bound of the two sets of qualifiers + */ + public final Set leastUpperBoundsShallow( + Collection qualifiers1, + TypeMirror tm1, + Collection qualifiers2, + TypeMirror tm2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); + } + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror lub = leastUpperBoundShallow(a1, tm1, a2, tm2); + if (lub != null) { + result.add(lub); + } + } + } + + assertSameSize(result, qualifiers1); + return result; } - return widenedUpperBound; - } - - /** - * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null if - * the qualifiers are not from the same qualifier hierarchy. - * - * @param qualifier1 first qualifier - * @param qualifier2 second qualifier - * @return greatest lower bound of the two annotations, or null if the two annotations are not - * from the same hierarchy - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - protected abstract @Nullable AnnotationMirror greatestLowerBoundQualifiers( - AnnotationMirror qualifier1, AnnotationMirror qualifier2); - - /** - * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null if - * the qualifiers are not from the same qualifier hierarchy. - * - * @param qualifier1 first qualifier - * @param qualifier2 second qualifier - * @return greatest lower bound of the two annotations, or null if the two annotations are not - * from the same hierarchy - */ - // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the - // collection version of LUB below. - public final @Nullable AnnotationMirror greatestLowerBoundQualifiersOnly( - AnnotationMirror qualifier1, AnnotationMirror qualifier2) { - return greatestLowerBoundQualifiers(qualifier1, qualifier2); - } - - /** - * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null if - * the qualifiers are not from the same qualifier hierarchy. - * - * @param qualifier1 first qualifier - * @param tm1 the type that is annotated by qualifier1 - * @param qualifier2 second qualifier - * @param tm2 the type that is annotated by qualifier2 - * @return greatest lower bound of the two annotations, or null if the two annotations are not - * from the same hierarchy - */ - @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. - public @Nullable AnnotationMirror greatestLowerBoundShallow( - AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) { - boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); - boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); - if (tm1IsRelevant == tm2IsRelevant) { - return greatestLowerBoundQualifiers(qualifier1, qualifier2); - } else if (tm1IsRelevant) { - return qualifier1; - } else { // if (tm2IsRelevant) { - return qualifier2; + + /** + * Returns the number of iterations dataflow should perform before {@link + * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never be + * called. + * + * @return the number of iterations dataflow should perform before {@link + * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should + * never be called. + */ + public int numberOfIterationsBeforeWidening() { + return -1; } - } - - /** - * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the - * qualifier for the same hierarchy in each set. - * - * @param qualifiers1 a set of qualifiers; exactly one per hierarchy - * @param qualifiers2 a set of qualifiers; exactly one per hierarchy - * @return the greatest lower bound of the two sets of qualifiers - */ - public Set greatestLowerBoundsQualifiersOnly( - Collection qualifiers1, - Collection qualifiers2) { - assertSameSize(qualifiers1, qualifiers2); - if (qualifiers1.isEmpty()) { - throw new BugInCF( - "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty sets"); + + /** + * If the qualifier hierarchy has an infinite ascending chain, then the dataflow analysis might + * never reach a fixed point. To prevent this, implement this method such that it returns an + * upper bound for the two qualifiers that is a strict super type of the least upper bound. If + * this method is implemented, also override {@link #numberOfIterationsBeforeWidening()} to + * return a positive number. + * + *

          {@code newQualifier} is newest qualifier dataflow computed for some expression and {@code + * previousQualifier} is the qualifier dataflow computed on the last iteration. + * + *

          If the qualifier hierarchy has no infinite ascending chain, returns the least upper bound + * of the two annotations. + * + * @param newQualifier new qualifier dataflow computed for some expression; must be in the same + * hierarchy as {@code previousQualifier} + * @param previousQualifier the previous qualifier dataflow computed on the last iteration; must + * be in the same hierarchy as {@code previousQualifier} + * @return an upper bound that is higher than the least upper bound of newQualifier and + * previousQualifier (or the lub if the qualifier hierarchy does not require this) + */ + public AnnotationMirror widenedUpperBound( + AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { + AnnotationMirror widenedUpperBound = + leastUpperBoundQualifiers(newQualifier, previousQualifier); + if (widenedUpperBound == null) { + throw new BugInCF( + "widenedUpperBound(%s, %s): unrelated qualifiers", + newQualifier, previousQualifier); + } + return widenedUpperBound; } - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror a1 : qualifiers1) { - for (AnnotationMirror a2 : qualifiers2) { - AnnotationMirror glb = greatestLowerBoundQualifiersOnly(a1, a2); - if (glb != null) { - result.add(glb); + /** + * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null + * if the qualifiers are not from the same qualifier hierarchy. + * + * @param qualifier1 first qualifier + * @param qualifier2 second qualifier + * @return greatest lower bound of the two annotations, or null if the two annotations are not + * from the same hierarchy + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + protected abstract @Nullable AnnotationMirror greatestLowerBoundQualifiers( + AnnotationMirror qualifier1, AnnotationMirror qualifier2); + + /** + * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null + * if the qualifiers are not from the same qualifier hierarchy. + * + * @param qualifier1 first qualifier + * @param qualifier2 second qualifier + * @return greatest lower bound of the two annotations, or null if the two annotations are not + * from the same hierarchy + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + public final @Nullable AnnotationMirror greatestLowerBoundQualifiersOnly( + AnnotationMirror qualifier1, AnnotationMirror qualifier2) { + return greatestLowerBoundQualifiers(qualifier1, qualifier2); + } + + /** + * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null + * if the qualifiers are not from the same qualifier hierarchy. + * + * @param qualifier1 first qualifier + * @param tm1 the type that is annotated by qualifier1 + * @param qualifier2 second qualifier + * @param tm2 the type that is annotated by qualifier2 + * @return greatest lower bound of the two annotations, or null if the two annotations are not + * from the same hierarchy + */ + @SuppressWarnings({"nullness", "keyfor"}) // AnnotatedTypeFactory hasn't been annotated. + public @Nullable AnnotationMirror greatestLowerBoundShallow( + AnnotationMirror qualifier1, + TypeMirror tm1, + AnnotationMirror qualifier2, + TypeMirror tm2) { + boolean tm1IsRelevant = atypeFactory.isRelevant(tm1); + boolean tm2IsRelevant = atypeFactory.isRelevant(tm2); + if (tm1IsRelevant == tm2IsRelevant) { + return greatestLowerBoundQualifiers(qualifier1, qualifier2); + } else if (tm1IsRelevant) { + return qualifier1; + } else { // if (tm2IsRelevant) { + return qualifier2; } - } } - assertSameSize(qualifiers1, qualifiers2, result); - return result; - } - - /** - * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the - * qualifier for the same hierarchy in each set. - * - * @param qualifiers1 a set of qualifiers; exactly one per hierarchy - * @param tm1 the type that is annotated by qualifier1 - * @param qualifiers2 a set of qualifiers; exactly one per hierarchy - * @param tm2 the type that is annotated by qualifier2 - * @return the greatest lower bound of the two sets of qualifiers - */ - public final Set greatestLowerBoundsShallow( - Collection qualifiers1, - TypeMirror tm1, - Collection qualifiers2, - TypeMirror tm2) { - assertSameSize(qualifiers1, qualifiers2); - if (qualifiers1.isEmpty()) { - throw new BugInCF( - "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty" + " sets"); + /** + * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy + * @return the greatest lower bound of the two sets of qualifiers + */ + public Set greatestLowerBoundsQualifiersOnly( + Collection qualifiers1, + Collection qualifiers2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty sets"); + } + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror glb = greatestLowerBoundQualifiersOnly(a1, a2); + if (glb != null) { + result.add(glb); + } + } + } + + assertSameSize(qualifiers1, qualifiers2, result); + return result; } - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror a1 : qualifiers1) { - for (AnnotationMirror a2 : qualifiers2) { - AnnotationMirror glb = greatestLowerBoundShallow(a1, tm1, a2, tm2); - if (glb != null) { - result.add(glb); + /** + * Returns the greatest lower bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. + * + * @param qualifiers1 a set of qualifiers; exactly one per hierarchy + * @param tm1 the type that is annotated by qualifier1 + * @param qualifiers2 a set of qualifiers; exactly one per hierarchy + * @param tm2 the type that is annotated by qualifier2 + * @return the greatest lower bound of the two sets of qualifiers + */ + public final Set greatestLowerBoundsShallow( + Collection qualifiers1, + TypeMirror tm1, + Collection qualifiers2, + TypeMirror tm2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty" + + " sets"); + } + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror glb = greatestLowerBoundShallow(a1, tm1, a2, tm2); + if (glb != null) { + result.add(glb); + } + } } - } + + assertSameSize(qualifiers1, qualifiers2, result); + return result; } - assertSameSize(qualifiers1, qualifiers2, result); - return result; - } - - /** - * Returns the greatest lower bound the all the collections of qualifiers. The result is the glb - * of the qualifier for the same hierarchy in each set. - * - * @param qualifiers a collection of collections of qualifiers. Each inner collection has exactly - * one qualifier per hierarchy. - * @return the greatest lower bound of the collections of qualifiers - */ - public Set greatestLowerBoundsQualifiersOnly( - Collection> qualifiers) { - if (qualifiers.isEmpty()) { - return AnnotationMirrorSet.emptySet(); + /** + * Returns the greatest lower bound the all the collections of qualifiers. The result is the glb + * of the qualifier for the same hierarchy in each set. + * + * @param qualifiers a collection of collections of qualifiers. Each inner collection has + * exactly one qualifier per hierarchy. + * @return the greatest lower bound of the collections of qualifiers + */ + public Set greatestLowerBoundsQualifiersOnly( + Collection> qualifiers) { + if (qualifiers.isEmpty()) { + return AnnotationMirrorSet.emptySet(); + } + Iterator> itor = qualifiers.iterator(); + Set result = new AnnotationMirrorSet(itor.next()); + while (itor.hasNext()) { + Collection annos = itor.next(); + result = greatestLowerBoundsQualifiersOnly(result, annos); + } + return result; } - Iterator> itor = qualifiers.iterator(); - Set result = new AnnotationMirrorSet(itor.next()); - while (itor.hasNext()) { - Collection annos = itor.next(); - result = greatestLowerBoundsQualifiersOnly(result, annos); + + /** + * Returns true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set + * with fewer qualifiers than the width of the QualifierHierarchy. + * + * @param type the type to test + * @return true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set + * with fewer qualifiers than the width of the QualifierHierarchy + */ + public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { + return type.getKind() == TypeKind.TYPEVAR + || type.getKind() == TypeKind.WILDCARD + || + // TODO: or should the union/intersection be the LUB of the alternatives? + type.getKind() == TypeKind.UNION + || type.getKind() == TypeKind.INTERSECTION; } - return result; - } - - /** - * Returns true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set with - * fewer qualifiers than the width of the QualifierHierarchy. - * - * @param type the type to test - * @return true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set with - * fewer qualifiers than the width of the QualifierHierarchy - */ - public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { - return type.getKind() == TypeKind.TYPEVAR - || type.getKind() == TypeKind.WILDCARD - || - // TODO: or should the union/intersection be the LUB of the alternatives? - type.getKind() == TypeKind.UNION - || type.getKind() == TypeKind.INTERSECTION; - } - - /** - * Returns the annotation in {@code qualifiers} that is in the same hierarchy as {@code - * qualifier}. - * - *

          The default implementation calls {@link #getTopAnnotation(AnnotationMirror)} and then calls - * {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)}. So, if {@code qualifier} is a - * top qualifier, then call {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)} - * directly is faster. - * - * @param qualifiers the set of annotations to search - * @param qualifier annotation that is in the same hierarchy as the returned annotation - * @return annotation in the same hierarchy as qualifier, or null if one is not found - */ - public @Nullable AnnotationMirror findAnnotationInSameHierarchy( - Collection qualifiers, AnnotationMirror qualifier) { - AnnotationMirror top = this.getTopAnnotation(qualifier); - return findAnnotationInHierarchy(qualifiers, top); - } - - /** - * Returns the annotation in {@code qualifiers} that is in the hierarchy for which {@code top} is - * top. - * - * @param qualifiers the set of annotations to search - * @param top the top annotation in the hierarchy to which the returned annotation belongs - * @return annotation in the same hierarchy as annotationMirror, or null if one is not found - */ - public @Nullable AnnotationMirror findAnnotationInHierarchy( - Collection qualifiers, AnnotationMirror top) { - for (AnnotationMirror anno : qualifiers) { - if (isSubtypeQualifiers(anno, top)) { - return anno; - } + + /** + * Returns the annotation in {@code qualifiers} that is in the same hierarchy as {@code + * qualifier}. + * + *

          The default implementation calls {@link #getTopAnnotation(AnnotationMirror)} and then + * calls {@link #findAnnotationInHierarchy(Collection, AnnotationMirror)}. So, if {@code + * qualifier} is a top qualifier, then call {@link #findAnnotationInHierarchy(Collection, + * AnnotationMirror)} directly is faster. + * + * @param qualifiers the set of annotations to search + * @param qualifier annotation that is in the same hierarchy as the returned annotation + * @return annotation in the same hierarchy as qualifier, or null if one is not found + */ + public @Nullable AnnotationMirror findAnnotationInSameHierarchy( + Collection qualifiers, AnnotationMirror qualifier) { + AnnotationMirror top = this.getTopAnnotation(qualifier); + return findAnnotationInHierarchy(qualifiers, top); } - return null; - } - - /** - * Update a mapping from {@code key} to a set of AnnotationMirrors. If {@code key} is not already - * in the map, then put it in the map with a value of a new set containing {@code qualifier}. If - * the map contains {@code key}, then add {@code qualifier} to the set to which {@code key} maps. - * If that set contains a qualifier in the same hierarchy as {@code qualifier}, then don't add it - * and return false. - * - * @param map the mapping to modify - * @param key the key to update or add - * @param qualifier the value to update or add - * @param type of the map's keys - * @return true if the update was done; false if there was a qualifier hierarchy collision - */ - public boolean updateMappingToMutableSet( - Map map, T key, AnnotationMirror qualifier) { - // https://github.com/typetools/checker-framework/issues/2000 - @SuppressWarnings("nullness:argument.type.incompatible") - boolean mapContainsKey = map.containsKey(key); - if (mapContainsKey) { - @SuppressWarnings("nullness:assignment.type.incompatible") // key is a key for map. - @NonNull AnnotationMirrorSet prevs = map.get(key); - AnnotationMirror old = findAnnotationInSameHierarchy(prevs, qualifier); - if (old != null) { - return false; - } - prevs.add(qualifier); - map.put(key, prevs); - } else { - AnnotationMirrorSet set = new AnnotationMirrorSet(); - set.add(qualifier); - map.put(key, set); + + /** + * Returns the annotation in {@code qualifiers} that is in the hierarchy for which {@code top} + * is top. + * + * @param qualifiers the set of annotations to search + * @param top the top annotation in the hierarchy to which the returned annotation belongs + * @return annotation in the same hierarchy as annotationMirror, or null if one is not found + */ + public @Nullable AnnotationMirror findAnnotationInHierarchy( + Collection qualifiers, AnnotationMirror top) { + for (AnnotationMirror anno : qualifiers) { + if (isSubtypeQualifiers(anno, top)) { + return anno; + } + } + return null; } - return true; - } - - /** - * Throws an exception if the given collections do not have the same size. - * - * @param c1 the first collection - * @param c2 the second collection - */ - public static void assertSameSize(Collection c1, Collection c2) { - if (c1.size() != c2.size()) { - throw new BugInCF( - "inconsistent sizes (%d, %d):%n [%s]%n [%s]", - c1.size(), c2.size(), StringsPlume.join(",", c1), StringsPlume.join(",", c2)); + + /** + * Update a mapping from {@code key} to a set of AnnotationMirrors. If {@code key} is not + * already in the map, then put it in the map with a value of a new set containing {@code + * qualifier}. If the map contains {@code key}, then add {@code qualifier} to the set to which + * {@code key} maps. If that set contains a qualifier in the same hierarchy as {@code + * qualifier}, then don't add it and return false. + * + * @param map the mapping to modify + * @param key the key to update or add + * @param qualifier the value to update or add + * @param type of the map's keys + * @return true if the update was done; false if there was a qualifier hierarchy collision + */ + public boolean updateMappingToMutableSet( + Map map, T key, AnnotationMirror qualifier) { + // https://github.com/typetools/checker-framework/issues/2000 + @SuppressWarnings("nullness:argument.type.incompatible") + boolean mapContainsKey = map.containsKey(key); + if (mapContainsKey) { + @SuppressWarnings("nullness:assignment.type.incompatible") // key is a key for map. + @NonNull AnnotationMirrorSet prevs = map.get(key); + AnnotationMirror old = findAnnotationInSameHierarchy(prevs, qualifier); + if (old != null) { + return false; + } + prevs.add(qualifier); + map.put(key, prevs); + } else { + AnnotationMirrorSet set = new AnnotationMirrorSet(); + set.add(qualifier); + map.put(key, set); + } + return true; } - } - - /** - * Throws an exception if the result and the inputs do not all have the same size. - * - * @param c1 the first collection - * @param c2 the second collection - * @param result the result collection - */ - public static void assertSameSize( - @MustCallUnknown Collection c1, - @MustCallUnknown Collection c2, - @MustCallUnknown Collection result) { - if (c1.size() != result.size() || c2.size() != result.size()) { - throw new BugInCF( - "inconsistent sizes (%d, %d, %d):%n %s%n %s%n %s", - c1.size(), - c2.size(), - result.size(), - StringsPlume.join(",", c1), - StringsPlume.join(",", c2), - StringsPlume.join(",", result)); + + /** + * Throws an exception if the given collections do not have the same size. + * + * @param c1 the first collection + * @param c2 the second collection + */ + public static void assertSameSize(Collection c1, Collection c2) { + if (c1.size() != c2.size()) { + throw new BugInCF( + "inconsistent sizes (%d, %d):%n [%s]%n [%s]", + c1.size(), c2.size(), StringsPlume.join(",", c1), StringsPlume.join(",", c2)); + } + } + + /** + * Throws an exception if the result and the inputs do not all have the same size. + * + * @param c1 the first collection + * @param c2 the second collection + * @param result the result collection + */ + public static void assertSameSize( + @MustCallUnknown Collection c1, + @MustCallUnknown Collection c2, + @MustCallUnknown Collection result) { + if (c1.size() != result.size() || c2.size() != result.size()) { + throw new BugInCF( + "inconsistent sizes (%d, %d, %d):%n %s%n %s%n %s", + c1.size(), + c2.size(), + result.size(), + StringsPlume.join(",", c1), + StringsPlume.join(",", c2), + StringsPlume.join(",", result)); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java index 56d3409cc4e..1cc33a457da 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierUpperBounds.java @@ -1,160 +1,166 @@ package org.checkerframework.framework.type; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.framework.qual.UpperBoundFor; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypesUtils; + import java.lang.annotation.Annotation; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import java.util.Set; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.framework.qual.UpperBoundFor; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypesUtils; /** Class that computes and stores the qualifier upper bounds for type uses. */ public class QualifierUpperBounds { - /** Map from {@link TypeKind} to annotations. */ - private final Map typeKinds; - - /** Map from canonical class name strings to annotations. */ - private final Map<@CanonicalName String, AnnotationMirrorSet> types; - - /** {@link QualifierHierarchy} */ - private final QualifierHierarchy qualHierarchy; - - private final AnnotatedTypeFactory atypeFactory; - - /** - * Creates a {@link QualifierUpperBounds} from the given checker, using that checker to determine - * the annotations that are in the type hierarchy. - */ - public QualifierUpperBounds(AnnotatedTypeFactory typeFactory) { - this.atypeFactory = typeFactory; - this.typeKinds = new EnumMap<>(TypeKind.class); - this.types = new HashMap<>(); - - this.qualHierarchy = typeFactory.getQualifierHierarchy(); - - // Get type qualifiers from the checker. - Set> quals = typeFactory.getSupportedTypeQualifiers(); - - // For each qualifier, read the @UpperBoundFor annotation and put its type classes and kinds - // into maps. - for (Class qual : quals) { - UpperBoundFor upperBoundFor = qual.getAnnotation(UpperBoundFor.class); - if (upperBoundFor == null) { - continue; - } - - AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); - for (org.checkerframework.framework.qual.TypeKind typeKind : upperBoundFor.typeKinds()) { - TypeKind mappedTk = mapTypeKinds(typeKind); - addTypeKind(mappedTk, theQual); - } - - for (Class typeName : upperBoundFor.types()) { - addType(typeName, theQual); - } - } - } - - /** - * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link TypeKind}. - * - * @param typeKind the Checker Framework TypeKind - * @return the javax TypeKind - */ - private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { - return TypeKind.valueOf(typeKind.name()); - } - - /** Add default qualifier, {@code theQual}, for the given TypeKind. */ - public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); - if (!res) { - throw new BugInCF( - "QualifierUpperBounds: invalid update of typeKinds $s at %s with %s.", - typeKinds, typeKind, theQual); + /** Map from {@link TypeKind} to annotations. */ + private final Map typeKinds; + + /** Map from canonical class name strings to annotations. */ + private final Map<@CanonicalName String, AnnotationMirrorSet> types; + + /** {@link QualifierHierarchy} */ + private final QualifierHierarchy qualHierarchy; + + private final AnnotatedTypeFactory atypeFactory; + + /** + * Creates a {@link QualifierUpperBounds} from the given checker, using that checker to + * determine the annotations that are in the type hierarchy. + */ + public QualifierUpperBounds(AnnotatedTypeFactory typeFactory) { + this.atypeFactory = typeFactory; + this.typeKinds = new EnumMap<>(TypeKind.class); + this.types = new HashMap<>(); + + this.qualHierarchy = typeFactory.getQualifierHierarchy(); + + // Get type qualifiers from the checker. + Set> quals = typeFactory.getSupportedTypeQualifiers(); + + // For each qualifier, read the @UpperBoundFor annotation and put its type classes and kinds + // into maps. + for (Class qual : quals) { + UpperBoundFor upperBoundFor = qual.getAnnotation(UpperBoundFor.class); + if (upperBoundFor == null) { + continue; + } + + AnnotationMirror theQual = + AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); + for (org.checkerframework.framework.qual.TypeKind typeKind : + upperBoundFor.typeKinds()) { + TypeKind mappedTk = mapTypeKinds(typeKind); + addTypeKind(mappedTk, theQual); + } + + for (Class typeName : upperBoundFor.types()) { + addType(typeName, theQual); + } + } } - } - - /** Add default qualifier, {@code theQual}, for the given class. */ - public void addType(Class type, AnnotationMirror theQual) { - String typeNameString = type.getCanonicalName(); - boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); - if (!res) { - throw new BugInCF( - "QualifierUpperBounds: invalid update of types $s at %s with %s.", types, type, theQual); + + /** + * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link TypeKind}. + * + * @param typeKind the Checker Framework TypeKind + * @return the javax TypeKind + */ + private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { + return TypeKind.valueOf(typeKind.name()); } - } - - /** - * Returns the set of qualifiers that are the upper bounds for a use of the type. - * - * @param type the TypeMirror - * @return the set of qualifiers that are the upper bounds for a use of the type - */ - public AnnotationMirrorSet getBoundQualifiers(TypeMirror type) { - AnnotationMirrorSet bounds = new AnnotationMirrorSet(); - String qname; - if (type.getKind() == TypeKind.DECLARED) { - DeclaredType declaredType = (DeclaredType) type; - bounds.addAll(getAnnotationFromElement(declaredType.asElement())); - qname = TypesUtils.getQualifiedName(declaredType); - } else if (type.getKind().isPrimitive()) { - qname = type.toString(); - } else { - qname = null; + + /** Add default qualifier, {@code theQual}, for the given TypeKind. */ + public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); + if (!res) { + throw new BugInCF( + "QualifierUpperBounds: invalid update of typeKinds $s at %s with %s.", + typeKinds, typeKind, theQual); + } } - if (qname != null && types.containsKey(qname)) { - AnnotationMirrorSet fnd = types.get(qname); - addMissingAnnotations(bounds, fnd); + /** Add default qualifier, {@code theQual}, for the given class. */ + public void addType(Class type, AnnotationMirror theQual) { + String typeNameString = type.getCanonicalName(); + boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); + if (!res) { + throw new BugInCF( + "QualifierUpperBounds: invalid update of types $s at %s with %s.", + types, type, theQual); + } } - // If the type's kind is in the appropriate map, annotate the type. + /** + * Returns the set of qualifiers that are the upper bounds for a use of the type. + * + * @param type the TypeMirror + * @return the set of qualifiers that are the upper bounds for a use of the type + */ + public AnnotationMirrorSet getBoundQualifiers(TypeMirror type) { + AnnotationMirrorSet bounds = new AnnotationMirrorSet(); + String qname; + if (type.getKind() == TypeKind.DECLARED) { + DeclaredType declaredType = (DeclaredType) type; + bounds.addAll(getAnnotationFromElement(declaredType.asElement())); + qname = TypesUtils.getQualifiedName(declaredType); + } else if (type.getKind().isPrimitive()) { + qname = type.toString(); + } else { + qname = null; + } + + if (qname != null && types.containsKey(qname)) { + AnnotationMirrorSet fnd = types.get(qname); + addMissingAnnotations(bounds, fnd); + } + + // If the type's kind is in the appropriate map, annotate the type. + + if (typeKinds.containsKey(type.getKind())) { + AnnotationMirrorSet fnd = typeKinds.get(type.getKind()); + addMissingAnnotations(bounds, fnd); + } + + addMissingAnnotations(bounds, atypeFactory.getDefaultTypeDeclarationBounds()); + return bounds; + } - if (typeKinds.containsKey(type.getKind())) { - AnnotationMirrorSet fnd = typeKinds.get(type.getKind()); - addMissingAnnotations(bounds, fnd); + /** + * Returns the explicit annotations on the element. Subclass can override this behavior to add + * annotations. + * + * @param element element whose annotations to return + * @return the explicit annotations on the element + */ + protected AnnotationMirrorSet getAnnotationFromElement(Element element) { + return atypeFactory.fromElement(element).getAnnotations(); } - addMissingAnnotations(bounds, atypeFactory.getDefaultTypeDeclarationBounds()); - return bounds; - } - - /** - * Returns the explicit annotations on the element. Subclass can override this behavior to add - * annotations. - * - * @param element element whose annotations to return - * @return the explicit annotations on the element - */ - protected AnnotationMirrorSet getAnnotationFromElement(Element element) { - return atypeFactory.fromElement(element).getAnnotations(); - } - - /** - * Adds each annotation in {@code missing} to {@code annos}, for which no annotation from the same - * qualifier hierarchy is present. - * - * @param annos an annotation set to side-effect - * @param missing annotations to add to {@code annos}, if {@code annos} does not have an - * annotation from the same qualifier hierarchy - */ - private void addMissingAnnotations( - AnnotationMirrorSet annos, Set missing) { - for (AnnotationMirror miss : missing) { - if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, miss) == null) { - annos.add(miss); - } + /** + * Adds each annotation in {@code missing} to {@code annos}, for which no annotation from the + * same qualifier hierarchy is present. + * + * @param annos an annotation set to side-effect + * @param missing annotations to add to {@code annos}, if {@code annos} does not have an + * annotation from the same qualifier hierarchy + */ + private void addMissingAnnotations( + AnnotationMirrorSet annos, Set missing) { + for (AnnotationMirror miss : missing) { + if (atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(annos, miss) + == null) { + annos.add(miss); + } + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java index 731d746bad3..3d0cbc8fd32 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java @@ -1,11 +1,5 @@ package org.checkerframework.framework.type; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -20,6 +14,14 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.StringsPlume; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * A visitor used to compare two type mirrors for "structural" equality. Structural equality implies * that, for two objects, all fields are also structurally equal and for primitives their values are @@ -28,348 +30,353 @@ *

          See also DefaultTypeHierarchy, and SubtypeVisitHistory */ public class StructuralEqualityComparer extends AbstractAtmComboVisitor { - /** History saving the result of previous comparisons. */ - protected final StructuralEqualityVisitHistory visitHistory; - - // See org.checkerframework.framework.type.DefaultTypeHierarchy.currentTop - private AnnotationMirror currentTop = null; - - /** - * Create a StructuralEqualityComparer. - * - * @param typeargVisitHistory history saving the result of previous comparisons - */ - public StructuralEqualityComparer(StructuralEqualityVisitHistory typeargVisitHistory) { - this.visitHistory = typeargVisitHistory; - } - - @Override - public Boolean defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { - - return super.defaultAction(type1, type2, p); - } - - /** - * Called for every combination that isn't specifically handled. - * - * @return error message explaining the two types' classes are not the same - */ - @Override - public String defaultErrorMessage(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { - return super.defaultErrorMessage(type1, type2, p) - + System.lineSeparator() - + " visitHistory = " - + visitHistory; - } - - /** - * Returns true if type1 and type2 are structurally equivalent. With one exception, - * type1.getClass().equals(type2.getClass()) must be true. However, because the Checker Framework - * sometimes "infers" Typevars to be Wildcards, we allow the combination Wildcard,Typevar. In this - * case, the two types are "equal" if their bounds are. - * - * @param type1 the first AnnotatedTypeMirror to compare - * @param type2 the second AnnotatedTypeMirror to compare - * @return true if type1 and type2 are equal - */ - @EqualsMethod - private boolean areEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (type1 == type2) { - return true; - } - assert currentTop != null; - if (type1 == null || type2 == null) { - return false; + /** History saving the result of previous comparisons. */ + protected final StructuralEqualityVisitHistory visitHistory; + + // See org.checkerframework.framework.type.DefaultTypeHierarchy.currentTop + private AnnotationMirror currentTop = null; + + /** + * Create a StructuralEqualityComparer. + * + * @param typeargVisitHistory history saving the result of previous comparisons + */ + public StructuralEqualityComparer(StructuralEqualityVisitHistory typeargVisitHistory) { + this.visitHistory = typeargVisitHistory; } - return AtmCombo.accept(type1, type2, null, this); - } - - public boolean areEqualInHierarchy( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror top) { - assert top != null; - boolean areEqual; - AnnotationMirror prevTop = currentTop; - currentTop = top; - try { - areEqual = areEqual(type1, type2); - } finally { - currentTop = prevTop; + + @Override + public Boolean defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { + + return super.defaultAction(type1, type2, p); } - return areEqual; - } - - /** - * Return true if type1 and type2 have the same set of annotations. - * - * @param type1 a type - * @param type2 a type - * @return true if type1 and type2 have the same set of annotations - */ - protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (currentTop != null) { - AnnotationMirror anno1 = type1.getAnnotationInHierarchy(currentTop); - AnnotationMirror anno2 = type2.getAnnotationInHierarchy(currentTop); - TypeMirror typeMirror1 = type1.underlyingType; - TypeMirror typeMirror2 = type2.underlyingType; - QualifierHierarchy qh = type1.atypeFactory.getQualifierHierarchy(); - return qh.isSubtypeShallow(anno1, typeMirror1, anno2, typeMirror2) - && qh.isSubtypeShallow(anno2, typeMirror2, anno1, typeMirror1); - } else { - throw new BugInCF("currentTop null"); + /** + * Called for every combination that isn't specifically handled. + * + * @return error message explaining the two types' classes are not the same + */ + @Override + public String defaultErrorMessage( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { + return super.defaultErrorMessage(type1, type2, p) + + System.lineSeparator() + + " visitHistory = " + + visitHistory; } - } - - /** - * Compare each type in types1 and types2 pairwise and return true if they are all equal. This - * method throws an exceptions if types1.size() != types2.size() - * - * @return true if for each pair (t1 = types1.get(i); t2 = types2.get(i)), areEqual(t1,t2) - */ - protected boolean areAllEqual( - Collection types1, - Collection types2) { - if (types1.size() != types2.size()) { - throw new BugInCF( - "Mismatching collection sizes:%n types 1: %s (%d)%n types 2: %s (%d)", - StringsPlume.join("; ", types1), - types1.size(), - StringsPlume.join("; ", types2), - types2.size()); + + /** + * Returns true if type1 and type2 are structurally equivalent. With one exception, + * type1.getClass().equals(type2.getClass()) must be true. However, because the Checker + * Framework sometimes "infers" Typevars to be Wildcards, we allow the combination + * Wildcard,Typevar. In this case, the two types are "equal" if their bounds are. + * + * @param type1 the first AnnotatedTypeMirror to compare + * @param type2 the second AnnotatedTypeMirror to compare + * @return true if type1 and type2 are equal + */ + @EqualsMethod + private boolean areEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (type1 == type2) { + return true; + } + assert currentTop != null; + if (type1 == null || type2 == null) { + return false; + } + return AtmCombo.accept(type1, type2, null, this); } - Iterator types1Iter = types1.iterator(); - Iterator types2Iter = types2.iterator(); - while (types1Iter.hasNext()) { - AnnotatedTypeMirror type1 = types1Iter.next(); - AnnotatedTypeMirror type2 = types2Iter.next(); - if (!checkOrAreEqual(type1, type2)) { - return false; - } + public boolean areEqualInHierarchy( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror top) { + assert top != null; + boolean areEqual; + AnnotationMirror prevTop = currentTop; + currentTop = top; + try { + areEqual = areEqual(type1, type2); + } finally { + currentTop = prevTop; + } + + return areEqual; } - return true; - } - - /** - * First check visitHistory to see if type1 and type2 have been compared once already. If so - * return true; otherwise compare them and put them in visitHistory. - * - * @param type1 the first type - * @param type2 the second type - * @return whether the two types are equal - */ - protected boolean checkOrAreEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; + /** + * Return true if type1 and type2 have the same set of annotations. + * + * @param type1 a type + * @param type2 a type + * @return true if type1 and type2 have the same set of annotations + */ + protected boolean arePrimaryAnnosEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (currentTop != null) { + AnnotationMirror anno1 = type1.getAnnotationInHierarchy(currentTop); + AnnotationMirror anno2 = type2.getAnnotationInHierarchy(currentTop); + TypeMirror typeMirror1 = type1.underlyingType; + TypeMirror typeMirror2 = type2.underlyingType; + QualifierHierarchy qh = type1.atypeFactory.getQualifierHierarchy(); + return qh.isSubtypeShallow(anno1, typeMirror1, anno2, typeMirror2) + && qh.isSubtypeShallow(anno2, typeMirror2, anno1, typeMirror1); + } else { + throw new BugInCF("currentTop null"); + } } - Boolean result = areEqual(type1, type2); - visitHistory.put(type1, type2, currentTop, result); - return result; - } - - /** - * Two arrays are equal if: - * - *

            - *
          1. Their sets of primary annotations are equal, and - *
          2. Their component types are equal - *
          - */ - @Override - public Boolean visitArray_Array(AnnotatedArrayType type1, AnnotatedArrayType type2, Void p) { - if (!arePrimaryAnnosEqual(type1, type2)) { - return false; + /** + * Compare each type in types1 and types2 pairwise and return true if they are all equal. This + * method throws an exceptions if types1.size() != types2.size() + * + * @return true if for each pair (t1 = types1.get(i); t2 = types2.get(i)), areEqual(t1,t2) + */ + protected boolean areAllEqual( + Collection types1, + Collection types2) { + if (types1.size() != types2.size()) { + throw new BugInCF( + "Mismatching collection sizes:%n types 1: %s (%d)%n types 2: %s (%d)", + StringsPlume.join("; ", types1), + types1.size(), + StringsPlume.join("; ", types2), + types2.size()); + } + + Iterator types1Iter = types1.iterator(); + Iterator types2Iter = types2.iterator(); + while (types1Iter.hasNext()) { + AnnotatedTypeMirror type1 = types1Iter.next(); + AnnotatedTypeMirror type2 = types2Iter.next(); + if (!checkOrAreEqual(type1, type2)) { + return false; + } + } + + return true; } - return areEqual(type1.getComponentType(), type2.getComponentType()); - } - - /** - * Two declared types are equal if: - * - *
            - *
          1. The types are of the same class/interfaces - *
          2. Their sets of primary annotations are equal - *
          3. Their sets of type arguments are equal or one type is raw - *
          - */ - @Override - public Boolean visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void p) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; + /** + * First check visitHistory to see if type1 and type2 have been compared once already. If so + * return true; otherwise compare them and put them in visitHistory. + * + * @param type1 the first type + * @param type2 the second type + * @return whether the two types are equal + */ + protected boolean checkOrAreEqual(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; + } + + Boolean result = areEqual(type1, type2); + visitHistory.put(type1, type2, currentTop, result); + return result; } - // TODO: same class/interface is not enforced. Why? + /** + * Two arrays are equal if: + * + *
            + *
          1. Their sets of primary annotations are equal, and + *
          2. Their component types are equal + *
          + */ + @Override + public Boolean visitArray_Array(AnnotatedArrayType type1, AnnotatedArrayType type2, Void p) { + if (!arePrimaryAnnosEqual(type1, type2)) { + return false; + } - if (!arePrimaryAnnosEqual(type1, type2)) { - return false; + return areEqual(type1.getComponentType(), type2.getComponentType()); } - // Prevent infinite recursion e.g. in Issue1587b - visitHistory.put(type1, type2, currentTop, true); - - List type1Args = type1.getTypeArguments(); - List type2Args = type2.getTypeArguments(); - - // Capture the types because the wildcards are only not equal if they are provably distinct. - // Provably distinct is computed using the captured and erased upper bounds of wildcards. - // See JLS 4.5.1. Type Arguments of Parameterized Types. - AnnotatedTypeFactory atypeFactory = type1.atypeFactory; - AnnotatedDeclaredType capturedType1 = - (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type1); - AnnotatedDeclaredType capturedType2 = - (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type2); - visitHistory.put(capturedType1, capturedType2, currentTop, true); - - List capturedType1Args = capturedType1.getTypeArguments(); - List capturedType2Args = capturedType2.getTypeArguments(); - boolean result = true; - for (int i = 0; i < type1.getTypeArguments().size(); i++) { - AnnotatedTypeMirror type1Arg = type1Args.get(i); - AnnotatedTypeMirror type2Arg = type2Args.get(i); - Boolean pastResultTA = visitHistory.get(type1Arg, type2Arg, currentTop); - if (pastResultTA != null) { - result = pastResultTA; - } else { - if (type1Arg.getKind() != TypeKind.WILDCARD || type2Arg.getKind() != TypeKind.WILDCARD) { - result = areEqual(type1Arg, type2Arg); - } else { - AnnotatedWildcardType wildcardType1 = (AnnotatedWildcardType) type1Arg; - AnnotatedWildcardType wildcardType2 = (AnnotatedWildcardType) type2Arg; - if (type1.atypeFactory.ignoreRawTypeArguments - && (wildcardType1.isTypeArgOfRawType() || wildcardType2.isTypeArgOfRawType())) { - result = true; - } else { - AnnotatedTypeMirror capturedType1Arg = capturedType1Args.get(i); - AnnotatedTypeMirror capturedType2Arg = capturedType2Args.get(i); - result = areEqual(capturedType1Arg.getErased(), capturedType2Arg.getErased()); - } + + /** + * Two declared types are equal if: + * + *
            + *
          1. The types are of the same class/interfaces + *
          2. Their sets of primary annotations are equal + *
          3. Their sets of type arguments are equal or one type is raw + *
          + */ + @Override + public Boolean visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void p) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; + } + + // TODO: same class/interface is not enforced. Why? + + if (!arePrimaryAnnosEqual(type1, type2)) { + return false; + } + // Prevent infinite recursion e.g. in Issue1587b + visitHistory.put(type1, type2, currentTop, true); + + List type1Args = type1.getTypeArguments(); + List type2Args = type2.getTypeArguments(); + + // Capture the types because the wildcards are only not equal if they are provably distinct. + // Provably distinct is computed using the captured and erased upper bounds of wildcards. + // See JLS 4.5.1. Type Arguments of Parameterized Types. + AnnotatedTypeFactory atypeFactory = type1.atypeFactory; + AnnotatedDeclaredType capturedType1 = + (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type1); + AnnotatedDeclaredType capturedType2 = + (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type2); + visitHistory.put(capturedType1, capturedType2, currentTop, true); + + List capturedType1Args = capturedType1.getTypeArguments(); + List capturedType2Args = capturedType2.getTypeArguments(); + boolean result = true; + for (int i = 0; i < type1.getTypeArguments().size(); i++) { + AnnotatedTypeMirror type1Arg = type1Args.get(i); + AnnotatedTypeMirror type2Arg = type2Args.get(i); + Boolean pastResultTA = visitHistory.get(type1Arg, type2Arg, currentTop); + if (pastResultTA != null) { + result = pastResultTA; + } else { + if (type1Arg.getKind() != TypeKind.WILDCARD + || type2Arg.getKind() != TypeKind.WILDCARD) { + result = areEqual(type1Arg, type2Arg); + } else { + AnnotatedWildcardType wildcardType1 = (AnnotatedWildcardType) type1Arg; + AnnotatedWildcardType wildcardType2 = (AnnotatedWildcardType) type2Arg; + if (type1.atypeFactory.ignoreRawTypeArguments + && (wildcardType1.isTypeArgOfRawType() + || wildcardType2.isTypeArgOfRawType())) { + result = true; + } else { + AnnotatedTypeMirror capturedType1Arg = capturedType1Args.get(i); + AnnotatedTypeMirror capturedType2Arg = capturedType2Args.get(i); + result = + areEqual( + capturedType1Arg.getErased(), capturedType2Arg.getErased()); + } + } + } + if (!result) { + break; + } } - } - if (!result) { - break; - } + + visitHistory.put(capturedType1, capturedType2, currentTop, result); + visitHistory.put(type1, type2, currentTop, result); + return result; } - visitHistory.put(capturedType1, capturedType2, currentTop, result); - visitHistory.put(type1, type2, currentTop, result); - return result; - } - - /** - * Two intersection types are equal if: - * - *
            - *
          • Their sets of primary annotations are equal - *
          • Their sets of bounds (the types being intersected) are equal - *
          - */ - @Override - public Boolean visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void p) { - if (!arePrimaryAnnosEqual(type1, type2)) { - return false; + /** + * Two intersection types are equal if: + * + *
            + *
          • Their sets of primary annotations are equal + *
          • Their sets of bounds (the types being intersected) are equal + *
          + */ + @Override + public Boolean visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void p) { + if (!arePrimaryAnnosEqual(type1, type2)) { + return false; + } + + boolean result = areAllEqual(type1.getBounds(), type2.getBounds()); + visitHistory.put(type1, type2, currentTop, result); + return result; } - boolean result = areAllEqual(type1.getBounds(), type2.getBounds()); - visitHistory.put(type1, type2, currentTop, result); - return result; - } - - /** - * Two primitive types are equal if: - * - *
            - *
          • Their sets of primary annotations are equal - *
          - */ - @Override - public Boolean visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void p) { - return arePrimaryAnnosEqual(type1, type2); - } - - @Override - public Boolean visitNull_Null(AnnotatedNullType type1, AnnotatedNullType type2, Void unused) { - return arePrimaryAnnosEqual(type1, type2); - } - - /** - * Two type variables are equal if: - * - *
            - *
          • Their bounds are equal - *
          - * - * Note: Primary annotations will be taken into account when the bounds are retrieved - */ - @Override - public Boolean visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void p) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; + /** + * Two primitive types are equal if: + * + *
            + *
          • Their sets of primary annotations are equal + *
          + */ + @Override + public Boolean visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void p) { + return arePrimaryAnnosEqual(type1, type2); } - Boolean result = - areEqual(type1.getUpperBound(), type2.getUpperBound()) - && areEqual(type1.getLowerBound(), type2.getLowerBound()); - visitHistory.put(type1, type2, currentTop, result); - return result; - } - - /** - * Two wildcards are equal if: - * - *
            - *
          • Their bounds are equal - *
          - * - * Note: Primary annotations will be taken into account when the bounds are retrieved - */ - @Override - public Boolean visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void p) { - Boolean pastResult = visitHistory.get(type1, type2, currentTop); - if (pastResult != null) { - return pastResult; + @Override + public Boolean visitNull_Null(AnnotatedNullType type1, AnnotatedNullType type2, Void unused) { + return arePrimaryAnnosEqual(type1, type2); } - if (type1.atypeFactory.ignoreRawTypeArguments - && (type1.isTypeArgOfRawType() || type2.isTypeArgOfRawType())) { - return true; + /** + * Two type variables are equal if: + * + *
            + *
          • Their bounds are equal + *
          + * + * Note: Primary annotations will be taken into account when the bounds are retrieved + */ + @Override + public Boolean visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void p) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; + } + + Boolean result = + areEqual(type1.getUpperBound(), type2.getUpperBound()) + && areEqual(type1.getLowerBound(), type2.getLowerBound()); + visitHistory.put(type1, type2, currentTop, result); + return result; } - Boolean result = - areEqual(type1.getExtendsBound(), type2.getExtendsBound()) - && areEqual(type1.getSuperBound(), type2.getSuperBound()); - visitHistory.put(type1, type2, currentTop, result); - return result; - } - - // Since we don't do a boxing conversion between primitive and declared types, in some cases - // we must compare primitives with their boxed counterparts. - @Override - public Boolean visitDeclared_Primitive( - AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, Void p) { - if (!TypesUtils.isBoxOf(type1.getUnderlyingType(), type2.getUnderlyingType())) { - throw new BugInCF(defaultErrorMessage(type1, type2, p)); + /** + * Two wildcards are equal if: + * + *
            + *
          • Their bounds are equal + *
          + * + * Note: Primary annotations will be taken into account when the bounds are retrieved + */ + @Override + public Boolean visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void p) { + Boolean pastResult = visitHistory.get(type1, type2, currentTop); + if (pastResult != null) { + return pastResult; + } + + if (type1.atypeFactory.ignoreRawTypeArguments + && (type1.isTypeArgOfRawType() || type2.isTypeArgOfRawType())) { + return true; + } + + Boolean result = + areEqual(type1.getExtendsBound(), type2.getExtendsBound()) + && areEqual(type1.getSuperBound(), type2.getSuperBound()); + visitHistory.put(type1, type2, currentTop, result); + return result; } - return arePrimaryAnnosEqual(type1, type2); - } + // Since we don't do a boxing conversion between primitive and declared types, in some cases + // we must compare primitives with their boxed counterparts. + @Override + public Boolean visitDeclared_Primitive( + AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, Void p) { + if (!TypesUtils.isBoxOf(type1.getUnderlyingType(), type2.getUnderlyingType())) { + throw new BugInCF(defaultErrorMessage(type1, type2, p)); + } - @Override - public Boolean visitPrimitive_Declared( - AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, Void p) { - if (!TypesUtils.isBoxOf(type2.getUnderlyingType(), type1.getUnderlyingType())) { - throw new BugInCF(defaultErrorMessage(type1, type2, p)); + return arePrimaryAnnosEqual(type1, type2); } - return arePrimaryAnnosEqual(type1, type2); - } + @Override + public Boolean visitPrimitive_Declared( + AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, Void p) { + if (!TypesUtils.isBoxOf(type2.getUnderlyingType(), type1.getUnderlyingType())) { + throw new BugInCF(defaultErrorMessage(type1, type2, p)); + } + + return arePrimaryAnnosEqual(type1, type2); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java index 71cd9db1f57..8fbfff30c7e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityVisitHistory.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.type; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; +import javax.lang.model.element.AnnotationMirror; + /** * Stores the result of {@link StructuralEqualityComparer} for type arguments. * @@ -10,81 +11,83 @@ */ public class StructuralEqualityVisitHistory { - /** - * Types in this history are structurally equal. (Use {@link SubtypeVisitHistory} because it - * implements a {@code Map, AnnotationMirrorSet>}) - */ - private final SubtypeVisitHistory trueHistory; + /** + * Types in this history are structurally equal. (Use {@link SubtypeVisitHistory} because it + * implements a {@code Map, + * AnnotationMirrorSet>}) + */ + private final SubtypeVisitHistory trueHistory; - /** - * Types in this history are not structurally equal. (Use {@link SubtypeVisitHistory} because it - * implements a {@code Map, AnnotationMirrorSet>}) - */ - private final SubtypeVisitHistory falseHistory; + /** + * Types in this history are not structurally equal. (Use {@link SubtypeVisitHistory} because it + * implements a {@code Map, + * AnnotationMirrorSet>}) + */ + private final SubtypeVisitHistory falseHistory; - /** Creates an empty StructuralEqualityVisitHistory. */ - public StructuralEqualityVisitHistory() { - this.trueHistory = new SubtypeVisitHistory(); - this.falseHistory = new SubtypeVisitHistory(); - } + /** Creates an empty StructuralEqualityVisitHistory. */ + public StructuralEqualityVisitHistory() { + this.trueHistory = new SubtypeVisitHistory(); + this.falseHistory = new SubtypeVisitHistory(); + } - /** - * Put result of comparing {@code type1} and {@code type2} for structural equality for the given - * hierarchy. - * - * @param type1 the first type - * @param type2 the second type - * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy - * are considered - * @param result whether {@code type1} is structurally equal to {@code type2} - */ - public void put( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - AnnotationMirror hierarchy, - boolean result) { - if (result) { - trueHistory.put(type1, type2, hierarchy, true); - falseHistory.remove(type1, type2, hierarchy); - } else { - falseHistory.put(type1, type2, hierarchy, true); - trueHistory.remove(type1, type2, hierarchy); + /** + * Put result of comparing {@code type1} and {@code type2} for structural equality for the given + * hierarchy. + * + * @param type1 the first type + * @param type2 the second type + * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy + * are considered + * @param result whether {@code type1} is structurally equal to {@code type2} + */ + public void put( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + AnnotationMirror hierarchy, + boolean result) { + if (result) { + trueHistory.put(type1, type2, hierarchy, true); + falseHistory.remove(type1, type2, hierarchy); + } else { + falseHistory.put(type1, type2, hierarchy, true); + trueHistory.remove(type1, type2, hierarchy); + } } - } - /** - * Return whether or not the two types are structurally equal for the given hierarchy or {@code - * null} if the types have not been visited for the given hierarchy. - * - * @param type1 the first type - * @param type2 the second type - * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy - * are considered - * @return whether or not the two types are structurally equal for the given hierarchy or {@code - * null} if the types have not been visited for the given hierarchy - */ - public @Nullable Boolean get( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { - if (falseHistory.contains(type1, type2, hierarchy)) { - return false; - } else if (trueHistory.contains(type1, type2, hierarchy)) { - return true; + /** + * Return whether or not the two types are structurally equal for the given hierarchy or {@code + * null} if the types have not been visited for the given hierarchy. + * + * @param type1 the first type + * @param type2 the second type + * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy + * are considered + * @return whether or not the two types are structurally equal for the given hierarchy or {@code + * null} if the types have not been visited for the given hierarchy + */ + public @Nullable Boolean get( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { + if (falseHistory.contains(type1, type2, hierarchy)) { + return false; + } else if (trueHistory.contains(type1, type2, hierarchy)) { + return true; + } + return null; } - return null; - } - /** - * Remove the result of comparing {@code type1} and {@code type2} for structural equality for the - * given hierarchy. - * - * @param type1 the first type - * @param type2 the second type - * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy - * are considered - */ - public void remove( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { - falseHistory.remove(type1, type2, hierarchy); - trueHistory.remove(type1, type2, hierarchy); - } + /** + * Remove the result of comparing {@code type1} and {@code type2} for structural equality for + * the given hierarchy. + * + * @param type1 the first type + * @param type2 the second type + * @param hierarchy the top of the relevant type hierarchy; only annotations from that hierarchy + * are considered + */ + public void remove( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror hierarchy) { + falseHistory.remove(type1, type2, hierarchy); + trueHistory.remove(type1, type2, hierarchy); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java index 7e4e84445ec..c072d946797 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSubsetQualifierHierarchy.java @@ -1,17 +1,19 @@ package org.checkerframework.framework.type; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.util.QualifierKind; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; + import java.lang.annotation.Annotation; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.util.QualifierKind; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; /** * A {@link org.checkerframework.framework.type.QualifierHierarchy} where, when a qualifier has @@ -24,105 +26,109 @@ @AnnotatedFor("nullness") public class SubtypeIsSubsetQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /** The processing environment; used for creating annotations. */ - private final ProcessingEnvironment processingEnv; + /** The processing environment; used for creating annotations. */ + private final ProcessingEnvironment processingEnv; - /** - * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param processingEnv processing environment - * @param atypeFactory the associated type factory - */ - public SubtypeIsSubsetQualifierHierarchy( - Collection> qualifierClasses, - ProcessingEnvironment processingEnv, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); - this.processingEnv = processingEnv; - } + /** + * Creates a SubtypeIsSubsetQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param processingEnv processing environment + * @param atypeFactory the associated type factory + */ + public SubtypeIsSubsetQualifierHierarchy( + Collection> qualifierClasses, + ProcessingEnvironment processingEnv, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); + this.processingEnv = processingEnv; + } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == superKind) { - List superValues = valuesStringList(superAnno); - List subValues = valuesStringList(subAnno); - return superValues.containsAll(subValues); + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == superKind) { + List superValues = valuesStringList(superAnno); + List subValues = valuesStringList(subAnno); + return superValues.containsAll(subValues); + } + return subKind.isSubtypeOf(superKind); } - return subKind.isSubtypeOf(superKind); - } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.addAll(a2Values); - return createAnnotationMirrorWithValue(lubKind, set); - } else if (lubKind == qualifierKind1) { - return a1; - } else if (lubKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.addAll(a2Values); + return createAnnotationMirrorWithValue(lubKind, set); + } else if (lubKind == qualifierKind1) { + return a1; + } else if (lubKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF( + "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); + } } - } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.retainAll(a2Values); - return createAnnotationMirrorWithValue(glbKind, set); - } else if (glbKind == qualifierKind1) { - return a1; - } else if (glbKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.retainAll(a2Values); + return createAnnotationMirrorWithValue(glbKind, set); + } else if (glbKind == qualifierKind1) { + return a1; + } else if (glbKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF( + "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); + } } - } - /** - * Returns a mutable list containing the {@code values} element of the given annotation. The - * {@code values} element must be an array of strings. - * - * @param anno an annotation - * @return a mutable list containing the {@code values} element; may be the empty list - */ - private List valuesStringList(AnnotationMirror anno) { - @SuppressWarnings("deprecation") // concrete annotation class is not known - List result = AnnotationUtils.getElementValueArray(anno, "value", String.class, true); - return result; - } + /** + * Returns a mutable list containing the {@code values} element of the given annotation. The + * {@code values} element must be an array of strings. + * + * @param anno an annotation + * @return a mutable list containing the {@code values} element; may be the empty list + */ + private List valuesStringList(AnnotationMirror anno) { + @SuppressWarnings("deprecation") // concrete annotation class is not known + List result = + AnnotationUtils.getElementValueArray(anno, "value", String.class, true); + return result; + } - /** - * Returns an AnnotationMirror corresponding to the given kind and values. - * - * @param kind the qualifier kind - * @param values the annotation's {@code values} element/argument - * @return an annotation of the given kind and values - */ - private AnnotationMirror createAnnotationMirrorWithValue(QualifierKind kind, Set values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); - builder.setValue("value", values.toArray()); - return builder.build(); - } + /** + * Returns an AnnotationMirror corresponding to the given kind and values. + * + * @param kind the qualifier kind + * @param values the annotation's {@code values} element/argument + * @return an annotation of the given kind and values + */ + private AnnotationMirror createAnnotationMirrorWithValue( + QualifierKind kind, Set values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); + builder.setValue("value", values.toArray()); + return builder.build(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java index 8f9a434e1aa..6a8c6fddaea 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeIsSupersetQualifierHierarchy.java @@ -1,17 +1,19 @@ package org.checkerframework.framework.type; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.util.QualifierKind; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; + import java.lang.annotation.Annotation; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.util.QualifierKind; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; /** * A {@link org.checkerframework.framework.type.QualifierHierarchy} where, when a qualifier has @@ -24,105 +26,109 @@ @AnnotatedFor("nullness") public class SubtypeIsSupersetQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - /** The processing environment; used for creating annotations. */ - private final ProcessingEnvironment processingEnv; + /** The processing environment; used for creating annotations. */ + private final ProcessingEnvironment processingEnv; - /** - * Creates a SubtypeIsSupersetQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param processingEnv processing environment - * @param atypeFactory the associated type factory - */ - public SubtypeIsSupersetQualifierHierarchy( - Collection> qualifierClasses, - ProcessingEnvironment processingEnv, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); - this.processingEnv = processingEnv; - } + /** + * Creates a SubtypeIsSupersetQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param processingEnv processing environment + * @param atypeFactory the associated type factory + */ + public SubtypeIsSupersetQualifierHierarchy( + Collection> qualifierClasses, + ProcessingEnvironment processingEnv, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, processingEnv.getElementUtils(), atypeFactory); + this.processingEnv = processingEnv; + } - @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - if (subKind == superKind) { - List superValues = valuesStringList(superAnno); - List subValues = valuesStringList(subAnno); - return subValues.containsAll(superValues); + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + if (subKind == superKind) { + List superValues = valuesStringList(superAnno); + List subValues = valuesStringList(subAnno); + return subValues.containsAll(superValues); + } + return subKind.isSubtypeOf(superKind); } - return subKind.isSubtypeOf(superKind); - } - @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.retainAll(a2Values); - return createAnnotationMirrorWithValue(lubKind, set); - } else if (lubKind == qualifierKind1) { - return a1; - } else if (lubKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.retainAll(a2Values); + return createAnnotationMirrorWithValue(lubKind, set); + } else if (lubKind == qualifierKind1) { + return a1; + } else if (lubKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF( + "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, lubKind); + } } - } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2) { - List a1Values = valuesStringList(a1); - List a2Values = valuesStringList(a2); - Set set = new LinkedHashSet<>(a1Values); - set.addAll(a2Values); - return createAnnotationMirrorWithValue(glbKind, set); - } else if (glbKind == qualifierKind1) { - return a1; - } else if (glbKind == qualifierKind2) { - return a2; - } else { - throw new BugInCF("Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2) { + List a1Values = valuesStringList(a1); + List a2Values = valuesStringList(a2); + Set set = new LinkedHashSet<>(a1Values); + set.addAll(a2Values); + return createAnnotationMirrorWithValue(glbKind, set); + } else if (glbKind == qualifierKind1) { + return a1; + } else if (glbKind == qualifierKind2) { + return a2; + } else { + throw new BugInCF( + "Unexpected QualifierKinds %s %s", qualifierKind1, qualifierKind2, glbKind); + } } - } - /** - * Returns a mutable list containing the {@code values} element of the given annotation. The - * {@code values} element must be an array of strings. - * - * @param anno an annotation - * @return a mutable list containing the {@code values} element; may be the empty list - */ - private List valuesStringList(AnnotationMirror anno) { - @SuppressWarnings("deprecation") // concrete annotation class is not known - List result = AnnotationUtils.getElementValueArray(anno, "value", String.class, true); - return result; - } + /** + * Returns a mutable list containing the {@code values} element of the given annotation. The + * {@code values} element must be an array of strings. + * + * @param anno an annotation + * @return a mutable list containing the {@code values} element; may be the empty list + */ + private List valuesStringList(AnnotationMirror anno) { + @SuppressWarnings("deprecation") // concrete annotation class is not known + List result = + AnnotationUtils.getElementValueArray(anno, "value", String.class, true); + return result; + } - /** - * Returns an AnnotationMirror corresponding to the given kind and values. - * - * @param kind the qualifier kind - * @param values the annotation's {@code values} element/argument - * @return an annotation of the given kind and values - */ - private AnnotationMirror createAnnotationMirrorWithValue(QualifierKind kind, Set values) { - AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); - builder.setValue("value", values.toArray()); - return builder.build(); - } + /** + * Returns an AnnotationMirror corresponding to the given kind and values. + * + * @param kind the qualifier kind + * @param values the annotation's {@code values} element/argument + * @return an annotation of the given kind and values + */ + private AnnotationMirror createAnnotationMirrorWithValue( + QualifierKind kind, Set values) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, kind.getAnnotationClass()); + builder.setValue("value", values.toArray()); + return builder.build(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java index f946e1f03df..48de587c95b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java @@ -1,10 +1,12 @@ package org.checkerframework.framework.type; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.plumelib.util.IPair; + import java.util.HashMap; import java.util.Map; + import javax.lang.model.element.AnnotationMirror; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.plumelib.util.IPair; /** * THIS CLASS IS DESIGNED FOR USE WITH DefaultTypeHierarchy, DefaultRawnessComparer, and @@ -24,77 +26,77 @@ // TODO: do we need to clear the history sometimes? public class SubtypeVisitHistory { - /** - * The keys are pairs of types; the value is the set of qualifier hierarchy roots for which the - * key is in a subtype relationship. - */ - private final Map, AnnotationMirrorSet> visited; - - /** Creates a new SubtypeVisitHistory. */ - public SubtypeVisitHistory() { - this.visited = new HashMap<>(); - } + /** + * The keys are pairs of types; the value is the set of qualifier hierarchy roots for which the + * key is in a subtype relationship. + */ + private final Map, AnnotationMirrorSet> visited; - /** - * Put a visit for {@code type1}, {@code type2}, and {@code top} in the history. Has no effect if - * isSubtype is false. - * - * @param type1 the first type - * @param type2 the second type - * @param currentTop the top of the relevant type hierarchy; only annotations from that hierarchy - * are considered - * @param isSubtype whether {@code type1} is a subtype of {@code type2}; if false, this method - * does nothing - */ - public void put( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - AnnotationMirror currentTop, - boolean isSubtype) { - if (!isSubtype) { - // Only store information about subtype relations that hold. - return; + /** Creates a new SubtypeVisitHistory. */ + public SubtypeVisitHistory() { + this.visited = new HashMap<>(); } - IPair key = IPair.of(type1, type2); - AnnotationMirrorSet hit = visited.get(key); - if (hit != null) { - hit.add(currentTop); - } else { - hit = new AnnotationMirrorSet(); - hit.add(currentTop); - this.visited.put(key, hit); + /** + * Put a visit for {@code type1}, {@code type2}, and {@code top} in the history. Has no effect + * if isSubtype is false. + * + * @param type1 the first type + * @param type2 the second type + * @param currentTop the top of the relevant type hierarchy; only annotations from that + * hierarchy are considered + * @param isSubtype whether {@code type1} is a subtype of {@code type2}; if false, this method + * does nothing + */ + public void put( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + AnnotationMirror currentTop, + boolean isSubtype) { + if (!isSubtype) { + // Only store information about subtype relations that hold. + return; + } + IPair key = IPair.of(type1, type2); + AnnotationMirrorSet hit = visited.get(key); + + if (hit != null) { + hit.add(currentTop); + } else { + hit = new AnnotationMirrorSet(); + hit.add(currentTop); + this.visited.put(key, hit); + } } - } - /** Remove {@code type1} and {@code type2}. */ - public void remove( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { - IPair key = IPair.of(type1, type2); - AnnotationMirrorSet hit = visited.get(key); - if (hit != null) { - hit.remove(currentTop); - if (hit.isEmpty()) { - visited.remove(key); - } + /** Remove {@code type1} and {@code type2}. */ + public void remove( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { + IPair key = IPair.of(type1, type2); + AnnotationMirrorSet hit = visited.get(key); + if (hit != null) { + hit.remove(currentTop); + if (hit.isEmpty()) { + visited.remove(key); + } + } } - } - /** - * Returns true if type1 and type2 (or an equivalent pair) have been passed to the put method - * previously. - * - * @return true if an equivalent pair has already been added to the history - */ - public boolean contains( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { - IPair key = IPair.of(type1, type2); - AnnotationMirrorSet hit = visited.get(key); - return hit != null && hit.contains(currentTop); - } + /** + * Returns true if type1 and type2 (or an equivalent pair) have been passed to the put method + * previously. + * + * @return true if an equivalent pair has already been added to the history + */ + public boolean contains( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror currentTop) { + IPair key = IPair.of(type1, type2); + AnnotationMirrorSet hit = visited.get(key); + return hit != null && hit.contains(currentTop); + } - @Override - public String toString() { - return "VisitHistory( " + visited + " )"; - } + @Override + public String toString() { + return "VisitHistory( " + visited + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java index 32b19e2c47a..dda45221a64 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java @@ -2,6 +2,18 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.Tree; + +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; + import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -9,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; @@ -18,16 +31,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Types; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.TreeUtils; /** * Finds the direct supertypes of an input AnnotatedTypeMirror. See directSupertypes(AnnotatedDeclaredType type) { - SupertypeFindingVisitor supertypeFindingVisitor = - new SupertypeFindingVisitor(type.atypeFactory); - List supertypes = - supertypeFindingVisitor.visitDeclared(type.asUse(), null); - type.atypeFactory.postDirectSuperTypes(type, supertypes); - return supertypes; - } - - // Version of method above for all types - /** - * See {@link Types#directSupertypes(TypeMirror)}. - * - * @param type the type whose supertypes to return - * @return the immediate supertypes of {@code type} - * @see Types#directSupertypes(TypeMirror) - */ - public static final List directSupertypes( - AnnotatedTypeMirror type) { - SupertypeFindingVisitor supertypeFindingVisitor = - new SupertypeFindingVisitor(type.atypeFactory); - List supertypes = supertypeFindingVisitor.visit(type, null); - type.atypeFactory.postDirectSuperTypes(type, supertypes); - return supertypes; - } - - /** Computes the direct supertypes of annotated types. */ - private static class SupertypeFindingVisitor - extends SimpleAnnotatedTypeVisitor, Void> { - - /** Types util class. */ - private final Types types; - - /** Annotated type factory. */ - private final AnnotatedTypeFactory atypeFactory; + /** Do not instantiate. */ + private SupertypeFinder() { + throw new AssertionError("Class SupertypeFinder cannot be instantiated."); + } + // Version of method below for declared types /** - * Creates a {@code SupertypeFindingVisitor}. + * See {@link Types#directSupertypes(TypeMirror)}. * - * @param atypeFactory annotated type factory + * @param type the type whose supertypes to return + * @return the immediate supertypes of {@code type} + * @see Types#directSupertypes(TypeMirror) */ - SupertypeFindingVisitor(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.types = atypeFactory.types; - } - - @Override - public List defaultAction(AnnotatedTypeMirror t, Void p) { - return Collections.emptyList(); + public static List directSupertypes(AnnotatedDeclaredType type) { + SupertypeFindingVisitor supertypeFindingVisitor = + new SupertypeFindingVisitor(type.atypeFactory); + List supertypes = + supertypeFindingVisitor.visitDeclared(type.asUse(), null); + type.atypeFactory.postDirectSuperTypes(type, supertypes); + return supertypes; } + // Version of method above for all types /** - * Primitive Rules: + * See {@link Types#directSupertypes(TypeMirror)}. * - *
          {@code
          -     * double >1 float
          -     * float >1 long
          -     * long >1 int
          -     * int >1 char
          -     * int >1 short
          -     * short >1 byte
          -     * }
          - * - * For easiness: - * - *
          {@code
          -     * boxed(primitiveType) >: primitiveType
          -     * }
          + * @param type the type whose supertypes to return + * @return the immediate supertypes of {@code type} + * @see Types#directSupertypes(TypeMirror) */ - @Override - public List visitPrimitive(AnnotatedPrimitiveType type, Void p) { - List superTypes = new ArrayList<>(1); - AnnotationMirrorSet annotations = type.getAnnotations(); - - // Find Boxed type - TypeElement boxed = types.boxedClass(type.getUnderlyingType()); - AnnotatedDeclaredType boxedType = atypeFactory.getAnnotatedType(boxed); - boxedType.replaceAnnotations(annotations); - superTypes.add(boxedType); - - TypeKind superPrimitiveType = null; - - if (type.getKind() == TypeKind.BOOLEAN) { - // Nothing - } else if (type.getKind() == TypeKind.BYTE) { - superPrimitiveType = TypeKind.SHORT; - } else if (type.getKind() == TypeKind.CHAR) { - superPrimitiveType = TypeKind.INT; - } else if (type.getKind() == TypeKind.DOUBLE) { - // Nothing - } else if (type.getKind() == TypeKind.FLOAT) { - superPrimitiveType = TypeKind.DOUBLE; - } else if (type.getKind() == TypeKind.INT) { - superPrimitiveType = TypeKind.LONG; - } else if (type.getKind() == TypeKind.LONG) { - superPrimitiveType = TypeKind.FLOAT; - } else if (type.getKind() == TypeKind.SHORT) { - superPrimitiveType = TypeKind.INT; - } else { - assert false : "Forgot the primitive " + type; - } - - if (superPrimitiveType != null) { - AnnotatedPrimitiveType superPrimitive = - (AnnotatedPrimitiveType) - atypeFactory.toAnnotatedType(types.getPrimitiveType(superPrimitiveType), false); - superPrimitive.addAnnotations(annotations); - superTypes.add(superPrimitive); - } - - return superTypes; + public static final List directSupertypes( + AnnotatedTypeMirror type) { + SupertypeFindingVisitor supertypeFindingVisitor = + new SupertypeFindingVisitor(type.atypeFactory); + List supertypes = supertypeFindingVisitor.visit(type, null); + type.atypeFactory.postDirectSuperTypes(type, supertypes); + return supertypes; } - @Override - public List visitDeclared(AnnotatedDeclaredType type, Void p) { - // AnnotationMirrorSet annotations = type.getAnnotations(); + /** Computes the direct supertypes of annotated types. */ + private static class SupertypeFindingVisitor + extends SimpleAnnotatedTypeVisitor, Void> { + + /** Types util class. */ + private final Types types; - TypeElement typeElement = (TypeElement) type.getUnderlyingType().asElement(); + /** Annotated type factory. */ + private final AnnotatedTypeFactory atypeFactory; - if (type.getTypeArguments().size() != typeElement.getTypeParameters().size()) { - if (!type.isUnderlyingTypeRaw()) { - throw new BugInCF( - "AnnotatedDeclaredType's element has a different number of type" - + " parameters than type.%ntype=%s%nelement=%s", - type, typeElement); + /** + * Creates a {@code SupertypeFindingVisitor}. + * + * @param atypeFactory annotated type factory + */ + SupertypeFindingVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.types = atypeFactory.types; } - } - List supertypes = new ArrayList<>(); - ClassTree classTree = atypeFactory.trees.getTree(typeElement); - // Testing against enum and annotation. Ideally we can simply use element! - if (classTree != null) { - supertypes.addAll(supertypesFromTree(type, classTree)); - } else { - supertypes.addAll(supertypesFromElement(type, typeElement)); - // Element elem = type.getElement() == null ? typeElement : type.getElement(); - } - - if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { - TypeElement jlaElement = - atypeFactory.elements.getTypeElement(Annotation.class.getCanonicalName()); - AnnotatedDeclaredType jlaAnnotation = atypeFactory.fromElement(jlaElement); - jlaAnnotation.addAnnotations(type.getAnnotations()); - supertypes.add(jlaAnnotation); - } - - Map typeVarToTypeArg = getTypeVarToTypeArg(type); - - List superTypesNew = new ArrayList<>(); - for (AnnotatedDeclaredType dt : supertypes) { - type.atypeFactory.initializeAtm(dt); - superTypesNew.add( - (AnnotatedDeclaredType) - atypeFactory.getTypeVarSubstitutor().substitute(typeVarToTypeArg, dt)); - } - - return superTypesNew; - } - /** - * Creates a mapping from a type parameter to its corresponding annotated type argument for all - * type parameters of {@code type}, its enclosing types, and all super types of all {@code - * type}'s enclosing types. - * - *

          It does not get the type parameters of the supertypes of {@code type} because the result - * of this method is used to substitute the type arguments of the supertypes of {@code type}. - * - * @param type a type - * @return a mapping from each type parameter to its corresponding annotated type argument - */ - private Map getTypeVarToTypeArg(AnnotatedDeclaredType type) { - Map mapping = new HashMap<>(); - // addTypeVarsFromEnclosingTypes can't be called with `type` because it calls - // `directSupertypes(types)`, which then calls this method. Add the type variables from - // `type` and then call addTypeVarsFromEnclosingTypes on the enclosing type. - addTypeVariablesToMapping(type, mapping); - addTypeVarsFromEnclosingTypes(type.getEnclosingType(), mapping); - return mapping; - } + @Override + public List defaultAction(AnnotatedTypeMirror t, Void p) { + return Collections.emptyList(); + } - /** - * Adds a mapping from a type parameter to its corresponding annotated type argument for all - * type parameters of {@code type}. - * - * @param type a type - * @param mapping type variable to type argument map; side-effected by this method - */ - private void addTypeVariablesToMapping( - AnnotatedDeclaredType type, Map mapping) { - TypeElement enclosingTypeElement = (TypeElement) type.getUnderlyingType().asElement(); - List typeParams = enclosingTypeElement.getTypeParameters(); - List typeArgs = type.getTypeArguments(); - for (int i = 0; i < type.getTypeArguments().size(); ++i) { - AnnotatedTypeMirror typArg = typeArgs.get(i); - TypeParameterElement ele = typeParams.get(i); - mapping.put((TypeVariable) ele.asType(), typArg); - } - } + /** + * Primitive Rules: + * + *

          {@code
          +         * double >1 float
          +         * float >1 long
          +         * long >1 int
          +         * int >1 char
          +         * int >1 short
          +         * short >1 byte
          +         * }
          + * + * For easiness: + * + *
          {@code
          +         * boxed(primitiveType) >: primitiveType
          +         * }
          + */ + @Override + public List visitPrimitive(AnnotatedPrimitiveType type, Void p) { + List superTypes = new ArrayList<>(1); + AnnotationMirrorSet annotations = type.getAnnotations(); + + // Find Boxed type + TypeElement boxed = types.boxedClass(type.getUnderlyingType()); + AnnotatedDeclaredType boxedType = atypeFactory.getAnnotatedType(boxed); + boxedType.replaceAnnotations(annotations); + superTypes.add(boxedType); + + TypeKind superPrimitiveType = null; + + if (type.getKind() == TypeKind.BOOLEAN) { + // Nothing + } else if (type.getKind() == TypeKind.BYTE) { + superPrimitiveType = TypeKind.SHORT; + } else if (type.getKind() == TypeKind.CHAR) { + superPrimitiveType = TypeKind.INT; + } else if (type.getKind() == TypeKind.DOUBLE) { + // Nothing + } else if (type.getKind() == TypeKind.FLOAT) { + superPrimitiveType = TypeKind.DOUBLE; + } else if (type.getKind() == TypeKind.INT) { + superPrimitiveType = TypeKind.LONG; + } else if (type.getKind() == TypeKind.LONG) { + superPrimitiveType = TypeKind.FLOAT; + } else if (type.getKind() == TypeKind.SHORT) { + superPrimitiveType = TypeKind.INT; + } else { + assert false : "Forgot the primitive " + type; + } - /** - * Adds a mapping from a type parameter to its corresponding annotated type argument for all - * type parameters of {@code enclosing} and its enclosing types. This method recurs on all the - * super types of {@code enclosing}. - * - * @param mapping type variable to type argument map; side-effected by this method - * @param enclosing a type - */ - private void addTypeVarsFromEnclosingTypes( - AnnotatedDeclaredType enclosing, Map mapping) { - while (enclosing != null) { - addTypeVariablesToMapping(enclosing, mapping); - for (AnnotatedDeclaredType enclSuper : directSupertypes(enclosing)) { - addTypeVarsFromEnclosingTypes(enclSuper, mapping); + if (superPrimitiveType != null) { + AnnotatedPrimitiveType superPrimitive = + (AnnotatedPrimitiveType) + atypeFactory.toAnnotatedType( + types.getPrimitiveType(superPrimitiveType), false); + superPrimitive.addAnnotations(annotations); + superTypes.add(superPrimitive); + } + + return superTypes; } - enclosing = enclosing.getEnclosingType(); - } - } - private List supertypesFromElement( - AnnotatedDeclaredType type, TypeElement typeElement) { - List supertypes = new ArrayList<>(); - // Find the super types: Start with enums and superclass - if (typeElement.getKind() == ElementKind.ENUM) { - supertypes.add(createEnumSuperType(type, typeElement)); - } else if (typeElement.getSuperclass().getKind() != TypeKind.NONE - && typeElement.getSuperclass().getKind() != TypeKind.ERROR) { - DeclaredType superClass = (DeclaredType) typeElement.getSuperclass(); - AnnotatedDeclaredType dt = - (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(superClass, false); - supertypes.add(dt); - - } else if (!ElementUtils.isObject(typeElement)) { - supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); - } - - for (TypeMirror st : typeElement.getInterfaces()) { - if (st.getKind() == TypeKind.ERROR) { - // This can happen while parsing the JDK. - continue; + @Override + public List visitDeclared(AnnotatedDeclaredType type, Void p) { + // AnnotationMirrorSet annotations = type.getAnnotations(); + + TypeElement typeElement = (TypeElement) type.getUnderlyingType().asElement(); + + if (type.getTypeArguments().size() != typeElement.getTypeParameters().size()) { + if (!type.isUnderlyingTypeRaw()) { + throw new BugInCF( + "AnnotatedDeclaredType's element has a different number of type" + + " parameters than type.%ntype=%s%nelement=%s", + type, typeElement); + } + } + List supertypes = new ArrayList<>(); + ClassTree classTree = atypeFactory.trees.getTree(typeElement); + // Testing against enum and annotation. Ideally we can simply use element! + if (classTree != null) { + supertypes.addAll(supertypesFromTree(type, classTree)); + } else { + supertypes.addAll(supertypesFromElement(type, typeElement)); + // Element elem = type.getElement() == null ? typeElement : type.getElement(); + } + + if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { + TypeElement jlaElement = + atypeFactory.elements.getTypeElement(Annotation.class.getCanonicalName()); + AnnotatedDeclaredType jlaAnnotation = atypeFactory.fromElement(jlaElement); + jlaAnnotation.addAnnotations(type.getAnnotations()); + supertypes.add(jlaAnnotation); + } + + Map typeVarToTypeArg = getTypeVarToTypeArg(type); + + List superTypesNew = new ArrayList<>(); + for (AnnotatedDeclaredType dt : supertypes) { + type.atypeFactory.initializeAtm(dt); + superTypesNew.add( + (AnnotatedDeclaredType) + atypeFactory + .getTypeVarSubstitutor() + .substitute(typeVarToTypeArg, dt)); + } + + return superTypesNew; } - if (type.isUnderlyingTypeRaw()) { - st = types.erasure(st); + + /** + * Creates a mapping from a type parameter to its corresponding annotated type argument for + * all type parameters of {@code type}, its enclosing types, and all super types of all + * {@code type}'s enclosing types. + * + *

          It does not get the type parameters of the supertypes of {@code type} because the + * result of this method is used to substitute the type arguments of the supertypes of + * {@code type}. + * + * @param type a type + * @return a mapping from each type parameter to its corresponding annotated type argument + */ + private Map getTypeVarToTypeArg( + AnnotatedDeclaredType type) { + Map mapping = new HashMap<>(); + // addTypeVarsFromEnclosingTypes can't be called with `type` because it calls + // `directSupertypes(types)`, which then calls this method. Add the type variables from + // `type` and then call addTypeVarsFromEnclosingTypes on the enclosing type. + addTypeVariablesToMapping(type, mapping); + addTypeVarsFromEnclosingTypes(type.getEnclosingType(), mapping); + return mapping; } - AnnotatedDeclaredType ast = (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(st, false); - supertypes.add(ast); - if (type.isUnderlyingTypeRaw()) { - if (st.getKind() == TypeKind.DECLARED) { - List typeArgs = ((DeclaredType) st).getTypeArguments(); - List annotatedTypeArgs = ast.getTypeArguments(); - for (int i = 0; i < typeArgs.size(); i++) { - atypeFactory.addComputedTypeAnnotations( - types.asElement(typeArgs.get(i)), annotatedTypeArgs.get(i)); + + /** + * Adds a mapping from a type parameter to its corresponding annotated type argument for all + * type parameters of {@code type}. + * + * @param type a type + * @param mapping type variable to type argument map; side-effected by this method + */ + private void addTypeVariablesToMapping( + AnnotatedDeclaredType type, Map mapping) { + TypeElement enclosingTypeElement = (TypeElement) type.getUnderlyingType().asElement(); + List typeParams = + enclosingTypeElement.getTypeParameters(); + List typeArgs = type.getTypeArguments(); + for (int i = 0; i < type.getTypeArguments().size(); ++i) { + AnnotatedTypeMirror typArg = typeArgs.get(i); + TypeParameterElement ele = typeParams.get(i); + mapping.put((TypeVariable) ele.asType(), typArg); } - } } - } - ElementAnnotationApplier.annotateSupers(supertypes, typeElement); - if (type.isUnderlyingTypeRaw()) { - for (AnnotatedDeclaredType adt : supertypes) { - adt.setIsUnderlyingTypeRaw(); + /** + * Adds a mapping from a type parameter to its corresponding annotated type argument for all + * type parameters of {@code enclosing} and its enclosing types. This method recurs on all + * the super types of {@code enclosing}. + * + * @param mapping type variable to type argument map; side-effected by this method + * @param enclosing a type + */ + private void addTypeVarsFromEnclosingTypes( + AnnotatedDeclaredType enclosing, Map mapping) { + while (enclosing != null) { + addTypeVariablesToMapping(enclosing, mapping); + for (AnnotatedDeclaredType enclSuper : directSupertypes(enclosing)) { + addTypeVarsFromEnclosingTypes(enclSuper, mapping); + } + enclosing = enclosing.getEnclosingType(); + } } - } - return supertypes; - } - private List supertypesFromTree( - AnnotatedDeclaredType type, ClassTree classTree) { - List supertypes = new ArrayList<>(); - if (classTree.getExtendsClause() != null) { - AnnotatedDeclaredType adt = - (AnnotatedDeclaredType) - atypeFactory.getAnnotatedTypeFromTypeTree(classTree.getExtendsClause()); - supertypes.add(adt); - } else if (!ElementUtils.isObject(TreeUtils.elementFromDeclaration(classTree))) { - if (classTree.getKind().name().contentEquals("RECORD")) { - supertypes.add(AnnotatedTypeMirror.createTypeOfRecord(atypeFactory)); - } else { - supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); + private List supertypesFromElement( + AnnotatedDeclaredType type, TypeElement typeElement) { + List supertypes = new ArrayList<>(); + // Find the super types: Start with enums and superclass + if (typeElement.getKind() == ElementKind.ENUM) { + supertypes.add(createEnumSuperType(type, typeElement)); + } else if (typeElement.getSuperclass().getKind() != TypeKind.NONE + && typeElement.getSuperclass().getKind() != TypeKind.ERROR) { + DeclaredType superClass = (DeclaredType) typeElement.getSuperclass(); + AnnotatedDeclaredType dt = + (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(superClass, false); + supertypes.add(dt); + + } else if (!ElementUtils.isObject(typeElement)) { + supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); + } + + for (TypeMirror st : typeElement.getInterfaces()) { + if (st.getKind() == TypeKind.ERROR) { + // This can happen while parsing the JDK. + continue; + } + if (type.isUnderlyingTypeRaw()) { + st = types.erasure(st); + } + AnnotatedDeclaredType ast = + (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(st, false); + supertypes.add(ast); + if (type.isUnderlyingTypeRaw()) { + if (st.getKind() == TypeKind.DECLARED) { + List typeArgs = + ((DeclaredType) st).getTypeArguments(); + List annotatedTypeArgs = ast.getTypeArguments(); + for (int i = 0; i < typeArgs.size(); i++) { + atypeFactory.addComputedTypeAnnotations( + types.asElement(typeArgs.get(i)), annotatedTypeArgs.get(i)); + } + } + } + } + ElementAnnotationApplier.annotateSupers(supertypes, typeElement); + + if (type.isUnderlyingTypeRaw()) { + for (AnnotatedDeclaredType adt : supertypes) { + adt.setIsUnderlyingTypeRaw(); + } + } + return supertypes; } - } - - for (Tree implemented : classTree.getImplementsClause()) { - AnnotatedDeclaredType adt = - (AnnotatedDeclaredType) atypeFactory.getAnnotatedTypeFromTypeTree(implemented); - supertypes.add(adt); - } - - TypeElement elem = TreeUtils.elementFromDeclaration(classTree); - if (elem.getKind() == ElementKind.ENUM) { - supertypes.add(createEnumSuperType(type, elem)); - } - if (type.isUnderlyingTypeRaw()) { - for (AnnotatedDeclaredType adt : supertypes) { - adt.setIsUnderlyingTypeRaw(); + + private List supertypesFromTree( + AnnotatedDeclaredType type, ClassTree classTree) { + List supertypes = new ArrayList<>(); + if (classTree.getExtendsClause() != null) { + AnnotatedDeclaredType adt = + (AnnotatedDeclaredType) + atypeFactory.getAnnotatedTypeFromTypeTree( + classTree.getExtendsClause()); + supertypes.add(adt); + } else if (!ElementUtils.isObject(TreeUtils.elementFromDeclaration(classTree))) { + if (classTree.getKind().name().contentEquals("RECORD")) { + supertypes.add(AnnotatedTypeMirror.createTypeOfRecord(atypeFactory)); + } else { + supertypes.add(AnnotatedTypeMirror.createTypeOfObject(atypeFactory)); + } + } + + for (Tree implemented : classTree.getImplementsClause()) { + AnnotatedDeclaredType adt = + (AnnotatedDeclaredType) + atypeFactory.getAnnotatedTypeFromTypeTree(implemented); + supertypes.add(adt); + } + + TypeElement elem = TreeUtils.elementFromDeclaration(classTree); + if (elem.getKind() == ElementKind.ENUM) { + supertypes.add(createEnumSuperType(type, elem)); + } + if (type.isUnderlyingTypeRaw()) { + for (AnnotatedDeclaredType adt : supertypes) { + adt.setIsUnderlyingTypeRaw(); + } + } + return supertypes; } - } - return supertypes; - } - /** - * All enums implicitly extend {@code Enum}, where {@code MyEnum} is the type of the - * enum. This method creates the AnnotatedTypeMirror for {@code Enum} where the - * annotation on {@code MyEnum} is copied from the annotation on the upper bound of the type - * argument to Enum. For example, {@code class Enum>}. - * - * @param type annotated type of an enum - * @param elem element corresponding to {@code type} - * @return enum super type - */ - private AnnotatedDeclaredType createEnumSuperType( - AnnotatedDeclaredType type, TypeElement elem) { - DeclaredType dt = (DeclaredType) elem.getSuperclass(); - AnnotatedDeclaredType adt = (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(dt, false); - for (AnnotatedTypeMirror t : adt.getTypeArguments()) { - // If the type argument of super is the same as the input type - if (atypeFactory.types.isSameType(t.getUnderlyingType(), type.getUnderlyingType())) { - t.addAnnotations(type.primaryAnnotations); + /** + * All enums implicitly extend {@code Enum}, where {@code MyEnum} is the type of the + * enum. This method creates the AnnotatedTypeMirror for {@code Enum} where the + * annotation on {@code MyEnum} is copied from the annotation on the upper bound of the type + * argument to Enum. For example, {@code class Enum>}. + * + * @param type annotated type of an enum + * @param elem element corresponding to {@code type} + * @return enum super type + */ + private AnnotatedDeclaredType createEnumSuperType( + AnnotatedDeclaredType type, TypeElement elem) { + DeclaredType dt = (DeclaredType) elem.getSuperclass(); + AnnotatedDeclaredType adt = + (AnnotatedDeclaredType) atypeFactory.toAnnotatedType(dt, false); + for (AnnotatedTypeMirror t : adt.getTypeArguments()) { + // If the type argument of super is the same as the input type + if (atypeFactory.types.isSameType( + t.getUnderlyingType(), type.getUnderlyingType())) { + t.addAnnotations(type.primaryAnnotations); + } + } + adt.addAnnotations(type.getAnnotations()); + return adt; } - } - adt.addAnnotations(type.getAnnotations()); - return adt; - } - /** - * - * - *

          {@code
          -     * For type = A[ ] ==>
          -     *  Object >: A[ ]
          -     *  Clonable >: A[ ]
          -     *  java.io.Serializable >: A[ ]
          -     *
          -     * if A is reference type, then also
          -     *  B[ ] >: A[ ] for any B[ ] >: A[ ]
          -     * }
          - */ - @Override - public List visitArray(AnnotatedArrayType type, Void p) { - List superTypes = new ArrayList<>(); - AnnotationMirrorSet annotations = type.getAnnotations(); - AnnotatedTypeMirror objectType = atypeFactory.getAnnotatedType(Object.class); - objectType.addAnnotations(annotations); - superTypes.add(objectType); - - AnnotatedTypeMirror cloneableType = atypeFactory.getAnnotatedType(Cloneable.class); - cloneableType.addAnnotations(annotations); - superTypes.add(cloneableType); - - AnnotatedTypeMirror serializableType = atypeFactory.getAnnotatedType(Serializable.class); - serializableType.addAnnotations(annotations); - superTypes.add(serializableType); - - for (AnnotatedTypeMirror sup : type.getComponentType().directSupertypes()) { - ArrayType arrType = atypeFactory.types.getArrayType(sup.getUnderlyingType()); - AnnotatedArrayType aarrType = - (AnnotatedArrayType) atypeFactory.toAnnotatedType(arrType, false); - aarrType.setComponentType(sup); - aarrType.addAnnotations(annotations); - superTypes.add(aarrType); - } - - return superTypes; - } + /** + * + * + *
          {@code
          +         * For type = A[ ] ==>
          +         *  Object >: A[ ]
          +         *  Clonable >: A[ ]
          +         *  java.io.Serializable >: A[ ]
          +         *
          +         * if A is reference type, then also
          +         *  B[ ] >: A[ ] for any B[ ] >: A[ ]
          +         * }
          + */ + @Override + public List visitArray(AnnotatedArrayType type, Void p) { + List superTypes = new ArrayList<>(); + AnnotationMirrorSet annotations = type.getAnnotations(); + AnnotatedTypeMirror objectType = atypeFactory.getAnnotatedType(Object.class); + objectType.addAnnotations(annotations); + superTypes.add(objectType); + + AnnotatedTypeMirror cloneableType = atypeFactory.getAnnotatedType(Cloneable.class); + cloneableType.addAnnotations(annotations); + superTypes.add(cloneableType); + + AnnotatedTypeMirror serializableType = + atypeFactory.getAnnotatedType(Serializable.class); + serializableType.addAnnotations(annotations); + superTypes.add(serializableType); + + for (AnnotatedTypeMirror sup : type.getComponentType().directSupertypes()) { + ArrayType arrType = atypeFactory.types.getArrayType(sup.getUnderlyingType()); + AnnotatedArrayType aarrType = + (AnnotatedArrayType) atypeFactory.toAnnotatedType(arrType, false); + aarrType.setComponentType(sup); + aarrType.addAnnotations(annotations); + superTypes.add(aarrType); + } - @Override - public List visitTypeVariable(AnnotatedTypeVariable type, Void p) { - return Collections.singletonList(type.getUpperBound().deepCopy()); - } + return superTypes; + } - @Override - public List visitWildcard(AnnotatedWildcardType type, Void p) { - return Collections.singletonList(type.getExtendsBound().deepCopy()); + @Override + public List visitTypeVariable(AnnotatedTypeVariable type, Void p) { + return Collections.singletonList(type.getUpperBound().deepCopy()); + } + + @Override + public List visitWildcard(AnnotatedWildcardType type, Void p) { + return Collections.singletonList(type.getExtendsBound().deepCopy()); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java b/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java index 179b0174c99..1f7a11a7df9 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SyntheticArrays.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.type; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; + import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.type.TypeKind; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; /** * SyntheticArrays exists solely to fix AnnotatedTypeMirrors that need to be adapted from Array type @@ -13,36 +14,37 @@ */ public final class SyntheticArrays { - /** Do not instantiate. */ - private SyntheticArrays() { - throw new AssertionError("Class SyntheticArrays cannot be instantiated."); - } + /** Do not instantiate. */ + private SyntheticArrays() { + throw new AssertionError("Class SyntheticArrays cannot be instantiated."); + } - /** - * Returns true if this combination of type/elem represents an array.clone. - * - * @param type a type with a method/field of elem - * @param elem an element which is a member of type - * @return true if this combination of type/elem represents an array.clone - */ - public static boolean isArrayClone(AnnotatedTypeMirror type, Element elem) { - return type.getKind() == TypeKind.ARRAY - && elem.getKind() == ElementKind.METHOD - && elem.getSimpleName().contentEquals("clone"); - } + /** + * Returns true if this combination of type/elem represents an array.clone. + * + * @param type a type with a method/field of elem + * @param elem an element which is a member of type + * @return true if this combination of type/elem represents an array.clone + */ + public static boolean isArrayClone(AnnotatedTypeMirror type, Element elem) { + return type.getKind() == TypeKind.ARRAY + && elem.getKind() == ElementKind.METHOD + && elem.getSimpleName().contentEquals("clone"); + } - /** - * Returns the annotated type of methodElem with its return type replaced by newReturnType. - * - * @param methodElem identifies a method that should have an AnnotatedArrayType as its return type - * @param newReturnType identifies a type that should replace methodElem's return type - * @return the annotated type of methodElem with its return type replaced by newReturnType - */ - public static AnnotatedExecutableType replaceReturnType( - Element methodElem, AnnotatedArrayType newReturnType) { - AnnotatedExecutableType method = - (AnnotatedExecutableType) newReturnType.atypeFactory.getAnnotatedType(methodElem); - method.setReturnType(newReturnType); - return method; - } + /** + * Returns the annotated type of methodElem with its return type replaced by newReturnType. + * + * @param methodElem identifies a method that should have an AnnotatedArrayType as its return + * type + * @param newReturnType identifies a type that should replace methodElem's return type + * @return the annotated type of methodElem with its return type replaced by newReturnType + */ + public static AnnotatedExecutableType replaceReturnType( + Element methodElem, AnnotatedArrayType newReturnType) { + AnnotatedExecutableType method = + (AnnotatedExecutableType) newReturnType.atypeFactory.getAnnotatedType(methodElem); + method.setReturnType(newReturnType); + return method; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java index afe6b1fb263..42330dc568e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromClassVisitor.java @@ -1,9 +1,11 @@ package org.checkerframework.framework.type; import com.sun.source.tree.ClassTree; -import javax.lang.model.element.TypeElement; + import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.TypeElement; + /** * Converts ClassTrees into AnnotatedDeclaredType. * @@ -11,13 +13,13 @@ */ class TypeFromClassVisitor extends TypeFromTreeVisitor { - @Override - public AnnotatedTypeMirror visitClass(ClassTree tree, AnnotatedTypeFactory f) { - TypeElement elt = TreeUtils.elementFromDeclaration(tree); - AnnotatedTypeMirror result = f.toAnnotatedType(elt.asType(), true); + @Override + public AnnotatedTypeMirror visitClass(ClassTree tree, AnnotatedTypeFactory f) { + TypeElement elt = TreeUtils.elementFromDeclaration(tree); + AnnotatedTypeMirror result = f.toAnnotatedType(elt.asType(), true); - ElementAnnotationApplier.apply(result, elt, f); + ElementAnnotationApplier.apply(result, elt, f); - return result; - } + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java index b47aa998392..b9f8fcb672a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromExpressionVisitor.java @@ -25,12 +25,7 @@ import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.WildcardTree; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -46,6 +41,14 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * Converts ExpressionTrees into AnnotatedTypeMirrors. * @@ -77,364 +80,366 @@ /*package-private*/ class TypeFromExpressionVisitor extends TypeFromTreeVisitor { - /** Creates a new TypeFromTreeVisitor. */ - /*package-private*/ TypeFromExpressionVisitor() { - // nothing to do - } - - @Override - public AnnotatedTypeMirror visitAnnotation(AnnotationTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitBinary(BinaryTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitCompoundAssignment( - CompoundAssignmentTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitInstanceOf(InstanceOfTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitLiteral(LiteralTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitUnary(UnaryTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { - return f.fromTypeTree(tree); - } - - @Override - public AnnotatedTypeMirror visitTypeCast(TypeCastTree tree, AnnotatedTypeFactory f) { - // Use the annotated type of the type in the cast. - return f.fromTypeTree(tree.getType()); - } - - @Override - public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { - // for e.g. "int.class" - return f.fromTypeTree(tree); - } - - @Override - public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { - // for e.g. "int[].class" - return f.fromTypeTree(tree); - } - - @Override - public AnnotatedTypeMirror visitParameterizedType( - ParameterizedTypeTree tree, AnnotatedTypeFactory f) { - return f.fromTypeTree(tree); - } - - @Override - public AnnotatedTypeMirror visitIntersectionType( - IntersectionTypeTree tree, AnnotatedTypeFactory f) { - return f.fromTypeTree(tree); - } - - @Override - public AnnotatedTypeMirror visitMemberReference( - MemberReferenceTree tree, AnnotatedTypeFactory f) { - return f.toAnnotatedType(TreeUtils.typeOf(tree), false); - } - - @Override - public AnnotatedTypeMirror visitLambdaExpression( - LambdaExpressionTree tree, AnnotatedTypeFactory f) { - return f.toAnnotatedType(TreeUtils.typeOf(tree), false); - } - - @Override - public AnnotatedTypeMirror visitAssignment(AssignmentTree tree, AnnotatedTypeFactory f) { - // Recurse on the type of the variable. - return visit(tree.getVariable(), f); - } - - @Override - public AnnotatedTypeMirror visitConditionalExpression( - ConditionalExpressionTree tree, AnnotatedTypeFactory f) { - // The Java type of a conditional expression is generally the LUB of the boxed types - // of the true and false expressions, but with a few exceptions. See JLS 15.25. - // So, use the type of the ConditionalExpressionTree instead of - // InternalUtils#leastUpperBound - TypeMirror alub = TreeUtils.typeOf(tree); - - AnnotatedTypeMirror trueType = f.getAnnotatedType(tree.getTrueExpression()); - AnnotatedTypeMirror falseType = f.getAnnotatedType(tree.getFalseExpression()); - - return AnnotatedTypes.leastUpperBound(f, trueType, falseType, alub); - } - - // TODO: remove method and instead use JCP to add version-specific methods - // Switch expressions first appeared in 12, standard in 14, so don't use 17. - @Override - public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { - if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { - return visitSwitchExpressionTree17(tree, f); + /** Creates a new TypeFromTreeVisitor. */ + /*package-private*/ TypeFromExpressionVisitor() { + // nothing to do + } + + @Override + public AnnotatedTypeMirror visitAnnotation(AnnotationTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitBinary(BinaryTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitCompoundAssignment( + CompoundAssignmentTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitInstanceOf(InstanceOfTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitLiteral(LiteralTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitUnary(UnaryTree tree, AnnotatedTypeFactory f) { + return f.type(tree); + } + + @Override + public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { + return f.fromTypeTree(tree); + } + + @Override + public AnnotatedTypeMirror visitTypeCast(TypeCastTree tree, AnnotatedTypeFactory f) { + // Use the annotated type of the type in the cast. + return f.fromTypeTree(tree.getType()); + } + + @Override + public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { + // for e.g. "int.class" + return f.fromTypeTree(tree); + } + + @Override + public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { + // for e.g. "int[].class" + return f.fromTypeTree(tree); } - return super.defaultAction(tree, f); - } - - /** - * Compute the type of the switch expression tree. - * - * @param switchExpressionTree a SwitchExpressionTree; typed as Tree so method signature is - * backward-compatible - * @param f an AnnotatedTypeFactory - * @return the type of the switch expression - */ - public AnnotatedTypeMirror visitSwitchExpressionTree17( - Tree switchExpressionTree, AnnotatedTypeFactory f) { - TypeMirror switchTypeMirror = TreeUtils.typeOf(switchExpressionTree); - SwitchExpressionScanner luber = - new FunctionalSwitchExpressionScanner<>( - // Function applied to each result expression of the switch expression. - (valueTree, unused) -> f.getAnnotatedType(valueTree), - // Function used to combine the types of each result expression. - (type1, type2) -> { - if (type1 == null) { - return type2; - } else if (type2 == null) { - return type1; - } else { - return AnnotatedTypes.leastUpperBound(f, type1, type2, switchTypeMirror); - } - }); - return luber.scanSwitchExpression(switchExpressionTree, null); - } - - @Override - public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { - if (tree.getName().contentEquals("this") || tree.getName().contentEquals("super")) { - AnnotatedDeclaredType res = f.getSelfType(tree); - return res; + + @Override + public AnnotatedTypeMirror visitParameterizedType( + ParameterizedTypeTree tree, AnnotatedTypeFactory f) { + return f.fromTypeTree(tree); } - Element elt = TreeUtils.elementFromUse(tree); - AnnotatedTypeMirror selfType = f.getImplicitReceiverType(tree); - if (selfType != null) { - AnnotatedTypeMirror type = AnnotatedTypes.asMemberOf(f.types, f, selfType, elt).asUse(); - return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); + @Override + public AnnotatedTypeMirror visitIntersectionType( + IntersectionTypeTree tree, AnnotatedTypeFactory f) { + return f.fromTypeTree(tree); } - AnnotatedTypeMirror type = f.getAnnotatedType(elt); + @Override + public AnnotatedTypeMirror visitMemberReference( + MemberReferenceTree tree, AnnotatedTypeFactory f) { + return f.toAnnotatedType(TreeUtils.typeOf(tree), false); + } - return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); - } + @Override + public AnnotatedTypeMirror visitLambdaExpression( + LambdaExpressionTree tree, AnnotatedTypeFactory f) { + return f.toAnnotatedType(TreeUtils.typeOf(tree), false); + } - @Override - public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { - Element elt = TreeUtils.elementFromUse(tree); + @Override + public AnnotatedTypeMirror visitAssignment(AssignmentTree tree, AnnotatedTypeFactory f) { + // Recurse on the type of the variable. + return visit(tree.getVariable(), f); + } - if (TreeUtils.isClassLiteral(tree)) { - // the type of a class literal is the type of the "class" element. - return f.getAnnotatedType(elt); + @Override + public AnnotatedTypeMirror visitConditionalExpression( + ConditionalExpressionTree tree, AnnotatedTypeFactory f) { + // The Java type of a conditional expression is generally the LUB of the boxed types + // of the true and false expressions, but with a few exceptions. See JLS 15.25. + // So, use the type of the ConditionalExpressionTree instead of + // InternalUtils#leastUpperBound + TypeMirror alub = TreeUtils.typeOf(tree); + + AnnotatedTypeMirror trueType = f.getAnnotatedType(tree.getTrueExpression()); + AnnotatedTypeMirror falseType = f.getAnnotatedType(tree.getFalseExpression()); + + return AnnotatedTypes.leastUpperBound(f, trueType, falseType, alub); } - switch (ElementUtils.getKindRecordAsClass(elt)) { - case METHOD: - case CONSTRUCTOR: // x0.super() in anoymous classes - case PACKAGE: // "java.lang" in new java.lang.Short("2") - case CLASS: // o instanceof MyClass.InnerClass - case ENUM: - case INTERFACE: // o instanceof MyClass.InnerInterface - case ANNOTATION_TYPE: - return f.fromElement(elt); - default: - // Fall-through. + + // TODO: remove method and instead use JCP to add version-specific methods + // Switch expressions first appeared in 12, standard in 14, so don't use 17. + @Override + public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { + if (SystemUtil.jreVersion >= 14 && tree.getKind().name().equals("SWITCH_EXPRESSION")) { + return visitSwitchExpressionTree17(tree, f); + } + return super.defaultAction(tree, f); } - if (tree.getIdentifier().contentEquals("this")) { - // Tree is "MyClass.this", where "MyClass" may be the innermost enclosing type or any - // outer type. - return f.getEnclosingType(TypesUtils.getTypeElement(TreeUtils.typeOf(tree)), tree); - } else if (tree.getIdentifier().contentEquals("super")) { - // Tree is "MyClass.super", where "MyClass" may be the innermost enclosing type or any - // outer type. - TypeMirror superTypeMirror = TreeUtils.typeOf(tree); - TypeElement superTypeElement = TypesUtils.getTypeElement(superTypeMirror); - AnnotatedDeclaredType thisType = f.getEnclosingSubType(superTypeElement, tree); - return AnnotatedTypes.asSuper( - f, thisType, AnnotatedTypeMirror.createType(superTypeMirror, f, false)); - } else { - // tree must be a field access, so get the type of the expression, and then call - // asMemberOf. - AnnotatedTypeMirror t; - if (f instanceof GenericAnnotatedTypeFactory) { - // If calling GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree lhsTree) to - // get the type of this MemberSelectTree, flow refinement is disabled. However, - // we want the receiver to have the refined type because type - // systems can use receiver-dependent qualifiers for viewpoint adaptation. - // Thus, we re-enable the flow refinement for a while just for the receiver - // expression. - // See framework/tests/viewpointtest/TestGetAnnotatedLhs.java for a concrete - // example. - t = - ((GenericAnnotatedTypeFactory) f) - .getAnnotatedTypeWithReceiverRefinement(tree.getExpression()); - } else { - t = f.getAnnotatedType(tree.getExpression()); - } - t = f.applyCaptureConversion(t); - return AnnotatedTypes.asMemberOf(f.types, f, t, elt).asUse(); + /** + * Compute the type of the switch expression tree. + * + * @param switchExpressionTree a SwitchExpressionTree; typed as Tree so method signature is + * backward-compatible + * @param f an AnnotatedTypeFactory + * @return the type of the switch expression + */ + public AnnotatedTypeMirror visitSwitchExpressionTree17( + Tree switchExpressionTree, AnnotatedTypeFactory f) { + TypeMirror switchTypeMirror = TreeUtils.typeOf(switchExpressionTree); + SwitchExpressionScanner luber = + new FunctionalSwitchExpressionScanner<>( + // Function applied to each result expression of the switch expression. + (valueTree, unused) -> f.getAnnotatedType(valueTree), + // Function used to combine the types of each result expression. + (type1, type2) -> { + if (type1 == null) { + return type2; + } else if (type2 == null) { + return type1; + } else { + return AnnotatedTypes.leastUpperBound( + f, type1, type2, switchTypeMirror); + } + }); + return luber.scanSwitchExpression(switchExpressionTree, null); } - } - - @Override - public AnnotatedTypeMirror visitArrayAccess(ArrayAccessTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.getAnnotatedType(tree.getExpression()); - if (type.getKind() == TypeKind.ARRAY) { - AnnotatedTypeMirror t = ((AnnotatedArrayType) type).getComponentType(); - t = f.applyCaptureConversion(t); - return t; + + @Override + public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { + if (tree.getName().contentEquals("this") || tree.getName().contentEquals("super")) { + AnnotatedDeclaredType res = f.getSelfType(tree); + return res; + } + + Element elt = TreeUtils.elementFromUse(tree); + AnnotatedTypeMirror selfType = f.getImplicitReceiverType(tree); + if (selfType != null) { + AnnotatedTypeMirror type = AnnotatedTypes.asMemberOf(f.types, f, selfType, elt).asUse(); + return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); + } + + AnnotatedTypeMirror type = f.getAnnotatedType(elt); + + return f.applyCaptureConversion(type, TreeUtils.typeOf(tree)); } - throw new BugInCF("Unexpected type: " + type); - } - @Override - public AnnotatedTypeMirror visitNewArray(NewArrayTree tree, AnnotatedTypeFactory f) { - // Don't use fromTypeTree here, because tree.getType() is not an array type! - AnnotatedArrayType result = (AnnotatedArrayType) f.type(tree); + @Override + public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { + Element elt = TreeUtils.elementFromUse(tree); + + if (TreeUtils.isClassLiteral(tree)) { + // the type of a class literal is the type of the "class" element. + return f.getAnnotatedType(elt); + } + switch (ElementUtils.getKindRecordAsClass(elt)) { + case METHOD: + case CONSTRUCTOR: // x0.super() in anoymous classes + case PACKAGE: // "java.lang" in new java.lang.Short("2") + case CLASS: // o instanceof MyClass.InnerClass + case ENUM: + case INTERFACE: // o instanceof MyClass.InnerInterface + case ANNOTATION_TYPE: + return f.fromElement(elt); + default: + // Fall-through. + } + + if (tree.getIdentifier().contentEquals("this")) { + // Tree is "MyClass.this", where "MyClass" may be the innermost enclosing type or any + // outer type. + return f.getEnclosingType(TypesUtils.getTypeElement(TreeUtils.typeOf(tree)), tree); + } else if (tree.getIdentifier().contentEquals("super")) { + // Tree is "MyClass.super", where "MyClass" may be the innermost enclosing type or any + // outer type. + TypeMirror superTypeMirror = TreeUtils.typeOf(tree); + TypeElement superTypeElement = TypesUtils.getTypeElement(superTypeMirror); + AnnotatedDeclaredType thisType = f.getEnclosingSubType(superTypeElement, tree); + return AnnotatedTypes.asSuper( + f, thisType, AnnotatedTypeMirror.createType(superTypeMirror, f, false)); + } else { + // tree must be a field access, so get the type of the expression, and then call + // asMemberOf. + AnnotatedTypeMirror t; + if (f instanceof GenericAnnotatedTypeFactory) { + // If calling GenericAnnotatedTypeFactory#getAnnotatedTypeLhs(Tree lhsTree) to + // get the type of this MemberSelectTree, flow refinement is disabled. However, + // we want the receiver to have the refined type because type + // systems can use receiver-dependent qualifiers for viewpoint adaptation. + // Thus, we re-enable the flow refinement for a while just for the receiver + // expression. + // See framework/tests/viewpointtest/TestGetAnnotatedLhs.java for a concrete + // example. + t = + ((GenericAnnotatedTypeFactory) f) + .getAnnotatedTypeWithReceiverRefinement(tree.getExpression()); + } else { + t = f.getAnnotatedType(tree.getExpression()); + } + t = f.applyCaptureConversion(t); + return AnnotatedTypes.asMemberOf(f.types, f, t, elt).asUse(); + } + } - if (tree.getType() == null) { // e.g., byte[] b = {(byte)1, (byte)2}; - return result; + @Override + public AnnotatedTypeMirror visitArrayAccess(ArrayAccessTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.getAnnotatedType(tree.getExpression()); + if (type.getKind() == TypeKind.ARRAY) { + AnnotatedTypeMirror t = ((AnnotatedArrayType) type).getComponentType(); + t = f.applyCaptureConversion(t); + return t; + } + throw new BugInCF("Unexpected type: " + type); } - annotateArrayAsArray(result, tree, f); + @Override + public AnnotatedTypeMirror visitNewArray(NewArrayTree tree, AnnotatedTypeFactory f) { + // Don't use fromTypeTree here, because tree.getType() is not an array type! + AnnotatedArrayType result = (AnnotatedArrayType) f.type(tree); + + if (tree.getType() == null) { // e.g., byte[] b = {(byte)1, (byte)2}; + return result; + } + + annotateArrayAsArray(result, tree, f); - return result; - } + return result; + } - private AnnotatedTypeMirror descendBy(AnnotatedTypeMirror type, int depth) { - AnnotatedTypeMirror result = type; - while (depth > 0) { - result = ((AnnotatedArrayType) result).getComponentType(); - depth--; + private AnnotatedTypeMirror descendBy(AnnotatedTypeMirror type, int depth) { + AnnotatedTypeMirror result = type; + while (depth > 0) { + result = ((AnnotatedArrayType) result).getComponentType(); + depth--; + } + return result; } - return result; - } - - /** - * Add annotations to an array type. - * - * @param result an array type; is side-effected by this method - * @param tree an array construction expression from which to obtain annotations - * @param f the type factory - */ - private void annotateArrayAsArray( - AnnotatedArrayType result, NewArrayTree tree, AnnotatedTypeFactory f) { - // Copy annotations from the type. - AnnotatedTypeMirror treeElem = f.fromTypeTree(tree.getType()); - boolean hasInit = tree.getInitializers() != null; - AnnotatedTypeMirror typeElem = descendBy(result, hasInit ? 1 : tree.getDimensions().size()); - while (true) { - typeElem.addAnnotations(treeElem.getAnnotations()); - if (!(treeElem instanceof AnnotatedArrayType)) { - break; - } - assert typeElem instanceof AnnotatedArrayType; - treeElem = ((AnnotatedArrayType) treeElem).getComponentType(); - typeElem = ((AnnotatedArrayType) typeElem).getComponentType(); + + /** + * Add annotations to an array type. + * + * @param result an array type; is side-effected by this method + * @param tree an array construction expression from which to obtain annotations + * @param f the type factory + */ + private void annotateArrayAsArray( + AnnotatedArrayType result, NewArrayTree tree, AnnotatedTypeFactory f) { + // Copy annotations from the type. + AnnotatedTypeMirror treeElem = f.fromTypeTree(tree.getType()); + boolean hasInit = tree.getInitializers() != null; + AnnotatedTypeMirror typeElem = descendBy(result, hasInit ? 1 : tree.getDimensions().size()); + while (true) { + typeElem.addAnnotations(treeElem.getAnnotations()); + if (!(treeElem instanceof AnnotatedArrayType)) { + break; + } + assert typeElem instanceof AnnotatedArrayType; + treeElem = ((AnnotatedArrayType) treeElem).getComponentType(); + typeElem = ((AnnotatedArrayType) typeElem).getComponentType(); + } + // Add all dimension annotations. + int idx = 0; + AnnotatedTypeMirror level = result; + while (level.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType array = (AnnotatedArrayType) level; + List annos = + TreeUtils.annotationsFromArrayCreation(tree, idx++); + array.addAnnotations(annos); + level = array.getComponentType(); + } + + // Add top-level annotations. + result.addAnnotations(TreeUtils.annotationsFromArrayCreation(tree, -1)); } - // Add all dimension annotations. - int idx = 0; - AnnotatedTypeMirror level = result; - while (level.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType array = (AnnotatedArrayType) level; - List annos = TreeUtils.annotationsFromArrayCreation(tree, idx++); - array.addAnnotations(annos); - level = array.getComponentType(); + + /** + * Creates an AnnotatedDeclaredType for the NewClassTree and adds, for each hierarchy, one of: + * + *
            + *
          • an explicit annotation on the new class expression ({@code new @HERE MyClass()}), or + *
          • an explicit annotation on the declaration of the class ({@code @HERE class MyClass + * {}}), or + *
          • an explicit or default annotation on the declaration of the constructor ({@code @HERE + * public MyClass() {}}). + *
          + * + * @param tree a NewClassTree + * @param f the type factory + * @return AnnotatedDeclaredType of {@code tree} + */ + @Override + public AnnotatedTypeMirror visitNewClass(NewClassTree tree, AnnotatedTypeFactory f) { + // Add annotations that are on the constructor declaration. + AnnotatedDeclaredType returnType = + (AnnotatedDeclaredType) f.constructorFromUse(tree).executableType.getReturnType(); + // Clear the annotations on the return type, so that the explicit annotations can be added + // first, then the annotations from the return type are added as needed. + AnnotationMirrorSet fromReturn = new AnnotationMirrorSet(returnType.getAnnotations()); + returnType.clearAnnotations(); + returnType.addAnnotations(f.getExplicitNewClassAnnos(tree)); + returnType.addMissingAnnotations(fromReturn); + return returnType; } - // Add top-level annotations. - result.addAnnotations(TreeUtils.annotationsFromArrayCreation(tree, -1)); - } - - /** - * Creates an AnnotatedDeclaredType for the NewClassTree and adds, for each hierarchy, one of: - * - *
            - *
          • an explicit annotation on the new class expression ({@code new @HERE MyClass()}), or - *
          • an explicit annotation on the declaration of the class ({@code @HERE class MyClass {}}), - * or - *
          • an explicit or default annotation on the declaration of the constructor ({@code @HERE - * public MyClass() {}}). - *
          - * - * @param tree a NewClassTree - * @param f the type factory - * @return AnnotatedDeclaredType of {@code tree} - */ - @Override - public AnnotatedTypeMirror visitNewClass(NewClassTree tree, AnnotatedTypeFactory f) { - // Add annotations that are on the constructor declaration. - AnnotatedDeclaredType returnType = - (AnnotatedDeclaredType) f.constructorFromUse(tree).executableType.getReturnType(); - // Clear the annotations on the return type, so that the explicit annotations can be added - // first, then the annotations from the return type are added as needed. - AnnotationMirrorSet fromReturn = new AnnotationMirrorSet(returnType.getAnnotations()); - returnType.clearAnnotations(); - returnType.addAnnotations(f.getExplicitNewClassAnnos(tree)); - returnType.addMissingAnnotations(fromReturn); - return returnType; - } - - @Override - public AnnotatedTypeMirror visitMethodInvocation( - MethodInvocationTree tree, AnnotatedTypeFactory f) { - AnnotatedExecutableType ex = f.methodFromUse(tree).executableType; - AnnotatedTypeMirror returnT = ex.getReturnType().asUse(); - if (TypesUtils.isCapturedTypeVariable(returnT.getUnderlyingType()) - && !TypesUtils.isCapturedTypeVariable(TreeUtils.typeOf(tree))) { - // Sometimes javac types an expression as the upper bound of a captured type variable - // instead of the captured type variable itself. This seems to be a bug in javac. Detect - // this case and match the annotated type to the Java type. - returnT = ((AnnotatedTypeVariable) returnT).getUpperBound(); + @Override + public AnnotatedTypeMirror visitMethodInvocation( + MethodInvocationTree tree, AnnotatedTypeFactory f) { + AnnotatedExecutableType ex = f.methodFromUse(tree).executableType; + AnnotatedTypeMirror returnT = ex.getReturnType().asUse(); + if (TypesUtils.isCapturedTypeVariable(returnT.getUnderlyingType()) + && !TypesUtils.isCapturedTypeVariable(TreeUtils.typeOf(tree))) { + // Sometimes javac types an expression as the upper bound of a captured type variable + // instead of the captured type variable itself. This seems to be a bug in javac. Detect + // this case and match the annotated type to the Java type. + returnT = ((AnnotatedTypeVariable) returnT).getUpperBound(); + } + + if (TypesUtils.isRaw(TreeUtils.typeOf(tree))) { + return returnT.getErased(); + } + return f.applyCaptureConversion(returnT); } - if (TypesUtils.isRaw(TreeUtils.typeOf(tree))) { - return returnT.getErased(); + @Override + public AnnotatedTypeMirror visitParenthesized(ParenthesizedTree tree, AnnotatedTypeFactory f) { + // Recurse on the expression inside the parens. + return visit(tree.getExpression(), f); } - return f.applyCaptureConversion(returnT); - } - - @Override - public AnnotatedTypeMirror visitParenthesized(ParenthesizedTree tree, AnnotatedTypeFactory f) { - // Recurse on the expression inside the parens. - return visit(tree.getExpression(), f); - } - - @Override - public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror bound = visit(tree.getBound(), f); - - AnnotatedTypeMirror result = f.type(tree); - assert result instanceof AnnotatedWildcardType; - - // Instead of directly overwriting the bound, replace each annotation - // to ensure that the structure of the wildcard will match that created by - // BoundsInitializer/createType. - if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { - f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getSuperBound()); - - } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { - f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getExtendsBound()); + + @Override + public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror bound = visit(tree.getBound(), f); + + AnnotatedTypeMirror result = f.type(tree); + assert result instanceof AnnotatedWildcardType; + + // Instead of directly overwriting the bound, replace each annotation + // to ensure that the structure of the wildcard will match that created by + // BoundsInitializer/createType. + if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { + f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getSuperBound()); + + } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { + f.replaceAnnotations(bound, ((AnnotatedWildcardType) result).getExtendsBound()); + } + return result; } - return result; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java index 7598ed49326..0e17e9607b2 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java @@ -5,21 +5,24 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; + import java.util.Collections; import java.util.List; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; /** * Converts a field or methods tree into an AnnotatedTypeMirror. Using addAnnotations for innerType @@ -30,162 +33,164 @@ */ class TypeFromMemberVisitor extends TypeFromTreeVisitor { - @Override - public AnnotatedTypeMirror visitVariable(VariableTree variableTree, AnnotatedTypeFactory f) { - Element elt = TreeUtils.elementFromDeclaration(variableTree); - - // Create the ATM and add non-primary annotations - AnnotatedTypeMirror result; - // Propagate initializer annotated type to variable if declared using var. - // Skip propagation of annotations when initializer can be null. - // E.g. - // for (var i : list) {} - if (variableTree.getInitializer() != null - && TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { - // BaseTypeVisitor#visitVariable does not need to check the type of `var` declarations. - result = f.getAnnotatedType(variableTree.getInitializer()); - // Let normal defaulting happen for the primary annotation. - result.clearAnnotations(); - } else if (variableTree.getType() == null - || TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { - // VariableTree#getType returns null for binding variables from a - // DeconstructionPatternTree. - result = f.type(variableTree); - } else { - // (variableTree.getType() does not include the annotation before the type, so those - // are added to the type below). - result = TypeFromTree.fromTypeTree(f, variableTree.getType()); - } + @Override + public AnnotatedTypeMirror visitVariable(VariableTree variableTree, AnnotatedTypeFactory f) { + Element elt = TreeUtils.elementFromDeclaration(variableTree); - // Handle any annotations in variableTree.getModifiers(). - List modifierAnnos; - List annoTrees = variableTree.getModifiers().getAnnotations(); - if (annoTrees != null && !annoTrees.isEmpty()) { - modifierAnnos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); - } else { - modifierAnnos = Collections.emptyList(); - } + // Create the ATM and add non-primary annotations + AnnotatedTypeMirror result; + // Propagate initializer annotated type to variable if declared using var. + // Skip propagation of annotations when initializer can be null. + // E.g. + // for (var i : list) {} + if (variableTree.getInitializer() != null + && TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { + // BaseTypeVisitor#visitVariable does not need to check the type of `var` declarations. + result = f.getAnnotatedType(variableTree.getInitializer()); + // Let normal defaulting happen for the primary annotation. + result.clearAnnotations(); + } else if (variableTree.getType() == null + || TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { + // VariableTree#getType returns null for binding variables from a + // DeconstructionPatternTree. + result = f.type(variableTree); + } else { + // (variableTree.getType() does not include the annotation before the type, so those + // are added to the type below). + result = TypeFromTree.fromTypeTree(f, variableTree.getType()); + } - if (result.getKind() != TypeKind.TYPEVAR || modifierAnnos.isEmpty()) { - // See comment in visitMethod - ElementAnnotationApplier.apply(result, elt, f); - } + // Handle any annotations in variableTree.getModifiers(). + List modifierAnnos; + List annoTrees = variableTree.getModifiers().getAnnotations(); + if (annoTrees != null && !annoTrees.isEmpty()) { + modifierAnnos = TreeUtils.annotationsFromTypeAnnotationTrees(annoTrees); + } else { + modifierAnnos = Collections.emptyList(); + } - if (result.getKind() == TypeKind.DECLARED - && - // Annotations on enum constants are not in the TypeMirror and always apply to the - // innermost type, so handle them in the else block. - elt.getKind() != ElementKind.ENUM_CONSTANT) { - // Decode the annotations from the type mirror because the annotations are already in - // the correct place for enclosing types. The annotations in - // variableTree.getModifiers() might apply to the enclosing type or the type itself. - // For example, @Tainted Outer.Inner y and @Tainted Inner x. - // @Tainted is stored in variableTree.getModifiers() of the variable tree corresponding - // to both x and y, but @Tainted applies to different types. - AnnotatedDeclaredType annotatedDeclaredType = (AnnotatedDeclaredType) result; - // The underlying type of result does not have all annotations, but the TypeMirror of - // variableTree.getType() does. - // VariableTree#getType returns null for binding variables from a - // DeconstructionPatternTree. - if (variableTree.getType() != null - && !TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { - DeclaredType declaredType = (DeclaredType) TreeUtils.typeOf(variableTree.getType()); - AnnotatedTypes.applyAnnotationsFromDeclaredType(annotatedDeclaredType, declaredType); - } - - // Handle declaration annotations - for (AnnotationMirror anno : modifierAnnos) { - if (!AnnotationUtils.isTypeUseAnnotation(anno)) { - // This does not treat Checker Framework compatqual annotations differently, - // because it's not clear whether the annotation should apply to the outermost - // enclosing type or the innermost. - result.addAnnotation(anno); + if (result.getKind() != TypeKind.TYPEVAR || modifierAnnos.isEmpty()) { + // See comment in visitMethod + ElementAnnotationApplier.apply(result, elt, f); } - // If anno is not a declaration annotation, it should have been applied in the call - // to applyAnnotationsFromDeclaredType above. - } - } else { - // Add the primary annotation from the variableTree.getModifiers(); - AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(result); - for (AnnotationMirror anno : modifierAnnos) { - // The code here is similar to - // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. - if (AnnotationUtils.isTypeUseAnnotation(anno) - // Always treat Checker Framework annotations as type annotations. - || AnnotationUtils.annotationName(anno).startsWith("org.checkerframework")) { - // Type annotations apply to the innermost type. - innerType.addAnnotation(anno); + + if (result.getKind() == TypeKind.DECLARED + && + // Annotations on enum constants are not in the TypeMirror and always apply to the + // innermost type, so handle them in the else block. + elt.getKind() != ElementKind.ENUM_CONSTANT) { + // Decode the annotations from the type mirror because the annotations are already in + // the correct place for enclosing types. The annotations in + // variableTree.getModifiers() might apply to the enclosing type or the type itself. + // For example, @Tainted Outer.Inner y and @Tainted Inner x. + // @Tainted is stored in variableTree.getModifiers() of the variable tree corresponding + // to both x and y, but @Tainted applies to different types. + AnnotatedDeclaredType annotatedDeclaredType = (AnnotatedDeclaredType) result; + // The underlying type of result does not have all annotations, but the TypeMirror of + // variableTree.getType() does. + // VariableTree#getType returns null for binding variables from a + // DeconstructionPatternTree. + if (variableTree.getType() != null + && !TreeUtils.isVariableTreeDeclaredUsingVar(variableTree)) { + DeclaredType declaredType = (DeclaredType) TreeUtils.typeOf(variableTree.getType()); + AnnotatedTypes.applyAnnotationsFromDeclaredType( + annotatedDeclaredType, declaredType); + } + + // Handle declaration annotations + for (AnnotationMirror anno : modifierAnnos) { + if (!AnnotationUtils.isTypeUseAnnotation(anno)) { + // This does not treat Checker Framework compatqual annotations differently, + // because it's not clear whether the annotation should apply to the outermost + // enclosing type or the innermost. + result.addAnnotation(anno); + } + // If anno is not a declaration annotation, it should have been applied in the call + // to applyAnnotationsFromDeclaredType above. + } } else { - // Declaration annotations apply to the outer type. - result.addAnnotation(anno); + // Add the primary annotation from the variableTree.getModifiers(); + AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(result); + for (AnnotationMirror anno : modifierAnnos) { + // The code here is similar to + // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. + if (AnnotationUtils.isTypeUseAnnotation(anno) + // Always treat Checker Framework annotations as type annotations. + || AnnotationUtils.annotationName(anno) + .startsWith("org.checkerframework")) { + // Type annotations apply to the innermost type. + innerType.addAnnotation(anno); + } else { + // Declaration annotations apply to the outer type. + result.addAnnotation(anno); + } + } } - } - } - AnnotatedTypeMirror lambdaParamType = inferLambdaParamAnnotations(f, result, elt); - if (lambdaParamType != null) { - return lambdaParamType; + AnnotatedTypeMirror lambdaParamType = inferLambdaParamAnnotations(f, result, elt); + if (lambdaParamType != null) { + return lambdaParamType; + } + return result; } - return result; - } - - @Override - public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) { - ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); - - AnnotatedExecutableType result = - (AnnotatedExecutableType) f.toAnnotatedType(elt.asType(), false); - result.setElement(elt); - f.initializeAtm(result); - - // Make sure the return type field gets initialized... otherwise - // some code throws NPE. This should be cleaned up. - result.getReturnType(); - - // TODO: Needed to visit parameter types, etc. - // It would be nicer if this didn't decode the information from the Element and - // instead also used the Tree. If this is implemented, then care needs to be taken to put - // any alias declaration annotations in the correct place for return types that are arrays. - // This would be similar to - // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. - ElementAnnotationApplier.apply(result, elt, f); - return result; - } - - /** - * Returns the type of the lambda parameter, or null if paramElement is not a lambda parameter. - * - * @return the type of the lambda parameter, or null if paramElement is not a lambda parameter - */ - private static @Nullable AnnotatedTypeMirror inferLambdaParamAnnotations( - AnnotatedTypeFactory f, AnnotatedTypeMirror lambdaParam, Element paramElement) { - if (paramElement.getKind() != ElementKind.PARAMETER - || f.declarationFromElement(paramElement) == null - || f.getPath(f.declarationFromElement(paramElement)) == null - || f.getPath(f.declarationFromElement(paramElement)).getParentPath() == null) { - - return null; + + @Override + public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) { + ExecutableElement elt = TreeUtils.elementFromDeclaration(tree); + + AnnotatedExecutableType result = + (AnnotatedExecutableType) f.toAnnotatedType(elt.asType(), false); + result.setElement(elt); + f.initializeAtm(result); + + // Make sure the return type field gets initialized... otherwise + // some code throws NPE. This should be cleaned up. + result.getReturnType(); + + // TODO: Needed to visit parameter types, etc. + // It would be nicer if this didn't decode the information from the Element and + // instead also used the Tree. If this is implemented, then care needs to be taken to put + // any alias declaration annotations in the correct place for return types that are arrays. + // This would be similar to + // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. + ElementAnnotationApplier.apply(result, elt, f); + return result; } - Tree declaredInTree = - f.getPath(f.declarationFromElement(paramElement)).getParentPath().getLeaf(); - if (declaredInTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION - && TreeUtils.isImplicitlyTypedLambda(declaredInTree)) { - LambdaExpressionTree lambdaDecl = (LambdaExpressionTree) declaredInTree; - int index = lambdaDecl.getParameters().indexOf(f.declarationFromElement(paramElement)); - AnnotatedExecutableType functionType = f.getFunctionTypeFromTree(lambdaDecl); - AnnotatedTypeMirror funcTypeParam = functionType.getParameterTypes().get(index); - // During type argument inference, the type of the parameters is assumed to be the - // same as the function parameter. - // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-18.html#jls-18.2.1). So if - // the underlying types are not the same type, then assume the lambda parameter is the - // same as the function type. (Use the erased types because the type arguments are not - // substituted when the annotated type arguments are.) - if (TypesUtils.isErasedSubtype( - funcTypeParam.underlyingType, lambdaParam.underlyingType, f.types)) { - return AnnotatedTypes.asSuper(f, funcTypeParam, lambdaParam); - } - return funcTypeParam; + + /** + * Returns the type of the lambda parameter, or null if paramElement is not a lambda parameter. + * + * @return the type of the lambda parameter, or null if paramElement is not a lambda parameter + */ + private static @Nullable AnnotatedTypeMirror inferLambdaParamAnnotations( + AnnotatedTypeFactory f, AnnotatedTypeMirror lambdaParam, Element paramElement) { + if (paramElement.getKind() != ElementKind.PARAMETER + || f.declarationFromElement(paramElement) == null + || f.getPath(f.declarationFromElement(paramElement)) == null + || f.getPath(f.declarationFromElement(paramElement)).getParentPath() == null) { + + return null; + } + Tree declaredInTree = + f.getPath(f.declarationFromElement(paramElement)).getParentPath().getLeaf(); + if (declaredInTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION + && TreeUtils.isImplicitlyTypedLambda(declaredInTree)) { + LambdaExpressionTree lambdaDecl = (LambdaExpressionTree) declaredInTree; + int index = lambdaDecl.getParameters().indexOf(f.declarationFromElement(paramElement)); + AnnotatedExecutableType functionType = f.getFunctionTypeFromTree(lambdaDecl); + AnnotatedTypeMirror funcTypeParam = functionType.getParameterTypes().get(index); + // During type argument inference, the type of the parameters is assumed to be the + // same as the function parameter. + // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-18.html#jls-18.2.1). So if + // the underlying types are not the same type, then assume the lambda parameter is the + // same as the function type. (Use the erased types because the type arguments are not + // substituted when the annotated type arguments are.) + if (TypesUtils.isErasedSubtype( + funcTypeParam.underlyingType, lambdaParam.underlyingType, f.types)) { + return AnnotatedTypes.asSuper(f, funcTypeParam, lambdaParam); + } + return funcTypeParam; + } + return null; } - return null; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java index 9a5e58a6c5c..e32615daefa 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTree.java @@ -3,11 +3,13 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; -import javax.lang.model.type.TypeKind; + import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.javacutil.BugInCF; +import javax.lang.model.type.TypeKind; + /** * A utility class to convert trees into corresponding AnnotatedTypeMirrors. This class should be * used ONLY from AnnotatedTypeFactory. @@ -19,115 +21,118 @@ */ class TypeFromTree { - private static final TypeFromTypeTreeVisitor typeTreeVisitor = new TypeFromTypeTreeVisitor(); - private static final TypeFromMemberVisitor memberVisitor = new TypeFromMemberVisitor(); - private static final TypeFromClassVisitor classVisitor = new TypeFromClassVisitor(); - private static final TypeFromExpressionVisitor expressionVisitor = - new TypeFromExpressionVisitor(); - - /** - * Returns an AnnotatedTypeMirror representing the input expression tree. - * - * @param tree must be an ExpressionTree - * @return an AnnotatedTypeMirror representing the input expression tree - */ - public static AnnotatedTypeMirror fromExpression( - AnnotatedTypeFactory typeFactory, ExpressionTree tree) { - abortIfTreeIsNull(typeFactory, tree); - - final AnnotatedTypeMirror type; - try { - type = expressionVisitor.visit(tree, typeFactory); - } catch (Throwable t) { - throw new BugInCF( - t, - "Error in AnnotatedTypeMirror.fromExpression(%s, %s): %s", - typeFactory.getClass().getSimpleName(), - tree, - t.getMessage()); + private static final TypeFromTypeTreeVisitor typeTreeVisitor = new TypeFromTypeTreeVisitor(); + private static final TypeFromMemberVisitor memberVisitor = new TypeFromMemberVisitor(); + private static final TypeFromClassVisitor classVisitor = new TypeFromClassVisitor(); + private static final TypeFromExpressionVisitor expressionVisitor = + new TypeFromExpressionVisitor(); + + /** + * Returns an AnnotatedTypeMirror representing the input expression tree. + * + * @param tree must be an ExpressionTree + * @return an AnnotatedTypeMirror representing the input expression tree + */ + public static AnnotatedTypeMirror fromExpression( + AnnotatedTypeFactory typeFactory, ExpressionTree tree) { + abortIfTreeIsNull(typeFactory, tree); + + final AnnotatedTypeMirror type; + try { + type = expressionVisitor.visit(tree, typeFactory); + } catch (Throwable t) { + throw new BugInCF( + t, + "Error in AnnotatedTypeMirror.fromExpression(%s, %s): %s", + typeFactory.getClass().getSimpleName(), + tree, + t.getMessage()); + } + ifExecutableCheckElement(typeFactory, tree, type); + + return type; + } + + /** + * Returns an AnnotatedTypeMirror representing the input tree. + * + * @param tree must represent a class member + * @return an AnnotatedTypeMirror representing the input tree + */ + public static AnnotatedTypeMirror fromMember(AnnotatedTypeFactory typeFactory, Tree tree) { + abortIfTreeIsNull(typeFactory, tree); + + AnnotatedTypeMirror type = memberVisitor.visit(tree, typeFactory); + ifExecutableCheckElement(typeFactory, tree, type); + return type; + } + + /** + * Returns an AnnotatedTypeMirror representing the input type tree. + * + * @param tree must be a type tree + * @return an AnnotatedTypeMirror representing the input type tree + */ + public static AnnotatedTypeMirror fromTypeTree(AnnotatedTypeFactory typeFactory, Tree tree) { + abortIfTreeIsNull(typeFactory, tree); + + AnnotatedTypeMirror type = typeTreeVisitor.visit(tree, typeFactory); + abortIfTypeIsExecutable(typeFactory, tree, type); + return type; } - ifExecutableCheckElement(typeFactory, tree, type); - - return type; - } - - /** - * Returns an AnnotatedTypeMirror representing the input tree. - * - * @param tree must represent a class member - * @return an AnnotatedTypeMirror representing the input tree - */ - public static AnnotatedTypeMirror fromMember(AnnotatedTypeFactory typeFactory, Tree tree) { - abortIfTreeIsNull(typeFactory, tree); - - AnnotatedTypeMirror type = memberVisitor.visit(tree, typeFactory); - ifExecutableCheckElement(typeFactory, tree, type); - return type; - } - - /** - * Returns an AnnotatedTypeMirror representing the input type tree. - * - * @param tree must be a type tree - * @return an AnnotatedTypeMirror representing the input type tree - */ - public static AnnotatedTypeMirror fromTypeTree(AnnotatedTypeFactory typeFactory, Tree tree) { - abortIfTreeIsNull(typeFactory, tree); - - AnnotatedTypeMirror type = typeTreeVisitor.visit(tree, typeFactory); - abortIfTypeIsExecutable(typeFactory, tree, type); - return type; - } - - /** - * Returns an AnnotatedDeclaredType representing the input ClassTree. - * - * @return an AnnotatedDeclaredType representing the input ClassTree - */ - public static AnnotatedDeclaredType fromClassTree( - AnnotatedTypeFactory typeFactory, ClassTree tree) { - abortIfTreeIsNull(typeFactory, tree); - - AnnotatedDeclaredType type = (AnnotatedDeclaredType) classVisitor.visit(tree, typeFactory); - abortIfTypeIsExecutable(typeFactory, tree, type); - return type; - } - - protected static void abortIfTreeIsNull(AnnotatedTypeFactory typeFactory, Tree tree) { - if (tree == null) { - throw new BugInCF("Encountered null tree" + summarize(typeFactory, tree)); + + /** + * Returns an AnnotatedDeclaredType representing the input ClassTree. + * + * @return an AnnotatedDeclaredType representing the input ClassTree + */ + public static AnnotatedDeclaredType fromClassTree( + AnnotatedTypeFactory typeFactory, ClassTree tree) { + abortIfTreeIsNull(typeFactory, tree); + + AnnotatedDeclaredType type = (AnnotatedDeclaredType) classVisitor.visit(tree, typeFactory); + abortIfTypeIsExecutable(typeFactory, tree, type); + return type; + } + + protected static void abortIfTreeIsNull(AnnotatedTypeFactory typeFactory, Tree tree) { + if (tree == null) { + throw new BugInCF("Encountered null tree" + summarize(typeFactory, tree)); + } + } + + protected static void ifExecutableCheckElement( + AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.EXECUTABLE) { + if (((AnnotatedExecutableType) type).getElement() == null) { + throw new BugInCF( + "Executable has no element:%n%s", summarize(typeFactory, tree, type)); + } + } + } + + protected static void abortIfTypeIsExecutable( + AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.EXECUTABLE) { + throw new BugInCF( + "Unexpected Executable typekind:%n%s", summarize(typeFactory, tree, type)); + } } - } - - protected static void ifExecutableCheckElement( - AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.EXECUTABLE) { - if (((AnnotatedExecutableType) type).getElement() == null) { - throw new BugInCF("Executable has no element:%n%s", summarize(typeFactory, tree, type)); - } + + /** + * Return a string with the two arguments, for diagnostics. + * + * @param typeFactory a type factory + * @param tree a tree + * @return a string with the two arguments + */ + protected static String summarize(AnnotatedTypeFactory typeFactory, Tree tree) { + return String.format( + "tree=%s%ntypeFactory=%s", tree, typeFactory.getClass().getSimpleName()); } - } - protected static void abortIfTypeIsExecutable( - AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.EXECUTABLE) { - throw new BugInCF("Unexpected Executable typekind:%n%s", summarize(typeFactory, tree, type)); + protected static String summarize( + AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { + return "type=" + type + System.lineSeparator() + summarize(typeFactory, tree); } - } - - /** - * Return a string with the two arguments, for diagnostics. - * - * @param typeFactory a type factory - * @param tree a tree - * @return a string with the two arguments - */ - protected static String summarize(AnnotatedTypeFactory typeFactory, Tree tree) { - return String.format("tree=%s%ntypeFactory=%s", tree, typeFactory.getClass().getSimpleName()); - } - - protected static String summarize( - AnnotatedTypeFactory typeFactory, Tree tree, AnnotatedTypeMirror type) { - return "type=" + type + System.lineSeparator() + summarize(typeFactory, tree); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java index f11e5b9d7a9..63bf425ff9c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTreeVisitor.java @@ -2,6 +2,7 @@ import com.sun.source.tree.Tree; import com.sun.source.util.SimpleTreeVisitor; + import org.checkerframework.javacutil.BugInCF; /** @@ -17,18 +18,18 @@ * @see org.checkerframework.framework.type.TypeFromTree */ abstract class TypeFromTreeVisitor - extends SimpleTreeVisitor { + extends SimpleTreeVisitor { - TypeFromTreeVisitor() {} + TypeFromTreeVisitor() {} - @Override - public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { - if (tree == null) { - throw new BugInCF("TypeFromTree.defaultAction: null tree"); + @Override + public AnnotatedTypeMirror defaultAction(Tree tree, AnnotatedTypeFactory f) { + if (tree == null) { + throw new BugInCF("TypeFromTree.defaultAction: null tree"); + } + throw new BugInCF( + this.getClass().getCanonicalName() + + ": conversion undefined for tree type " + + tree.getKind()); } - throw new BugInCF( - this.getClass().getCanonicalName() - + ": conversion undefined for tree type " - + tree.getKind()); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java index 099691dc7d3..0ba5ad3ca3b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java @@ -19,11 +19,24 @@ import com.sun.tools.javac.code.Type.TypeVar; import com.sun.tools.javac.code.Type.WildcardType; import com.sun.tools.javac.tree.JCTree.JCWildcard; + +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -31,16 +44,6 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeVariable; -import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; /** * Converts type trees into AnnotatedTypeMirrors. @@ -49,331 +52,333 @@ */ class TypeFromTypeTreeVisitor extends TypeFromTreeVisitor { - /** Creates a TypeFromTypeTreeVisitor. */ - public TypeFromTypeTreeVisitor() {} - - /** - * A mapping from TypeParameterTree to its type. This is used to correctly initialize recursive - * type variables. - */ - private final Map visitedTypeParameter = - new HashMap<>(); - - @Override - public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = visit(tree.getUnderlyingType(), f); - if (type == null) { // e.g., for receiver type - type = f.toAnnotatedType(f.types.getNoType(TypeKind.NONE), false); - } - assert AnnotatedTypeFactory.validAnnotatedType(type); - List annos = TreeUtils.annotationsFromTree(tree); - - if (type.getKind() == TypeKind.WILDCARD) { - // Work-around for https://github.com/eisop/checker-framework/issues/17 - // For an annotated wildcard tree tree, the type attached to the - // tree is a WildcardType with a correct bound (set to the type - // variable which the wildcard instantiates). The underlying type is - // also a WildcardType but with a bound of null. Here we update the - // bound of the underlying WildcardType to be consistent. - WildcardType wildcardAttachedToNode = (WildcardType) TreeUtils.typeOf(tree); - WildcardType underlyingWildcard = (WildcardType) type.getUnderlyingType(); - underlyingWildcard.withTypeVar(wildcardAttachedToNode.bound); - // End of work-around - - AnnotatedWildcardType wctype = ((AnnotatedWildcardType) type); - ExpressionTree underlyingTree = tree.getUnderlyingType(); - - if (underlyingTree.getKind() == Tree.Kind.UNBOUNDED_WILDCARD) { - // primary annotations on unbounded wildcard types apply to both bounds - wctype.getExtendsBound().addAnnotations(annos); - wctype.getSuperBound().addAnnotations(annos); - } else if (underlyingTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { - wctype.getSuperBound().addAnnotations(annos); - } else if (underlyingTree.getKind() == Tree.Kind.SUPER_WILDCARD) { - wctype.getExtendsBound().addAnnotations(annos); - } else { - throw new BugInCF( - "Unexpected kind for type. tree=" - + tree - + " type=" - + type - + " kind=" - + underlyingTree.getKind()); - } - } else { - type.addAnnotations(annos); - } + /** Creates a TypeFromTypeTreeVisitor. */ + public TypeFromTypeTreeVisitor() {} + + /** + * A mapping from TypeParameterTree to its type. This is used to correctly initialize recursive + * type variables. + */ + private final Map visitedTypeParameter = + new HashMap<>(); + + @Override + public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = visit(tree.getUnderlyingType(), f); + if (type == null) { // e.g., for receiver type + type = f.toAnnotatedType(f.types.getNoType(TypeKind.NONE), false); + } + assert AnnotatedTypeFactory.validAnnotatedType(type); + List annos = TreeUtils.annotationsFromTree(tree); + + if (type.getKind() == TypeKind.WILDCARD) { + // Work-around for https://github.com/eisop/checker-framework/issues/17 + // For an annotated wildcard tree tree, the type attached to the + // tree is a WildcardType with a correct bound (set to the type + // variable which the wildcard instantiates). The underlying type is + // also a WildcardType but with a bound of null. Here we update the + // bound of the underlying WildcardType to be consistent. + WildcardType wildcardAttachedToNode = (WildcardType) TreeUtils.typeOf(tree); + WildcardType underlyingWildcard = (WildcardType) type.getUnderlyingType(); + underlyingWildcard.withTypeVar(wildcardAttachedToNode.bound); + // End of work-around + + AnnotatedWildcardType wctype = ((AnnotatedWildcardType) type); + ExpressionTree underlyingTree = tree.getUnderlyingType(); + + if (underlyingTree.getKind() == Tree.Kind.UNBOUNDED_WILDCARD) { + // primary annotations on unbounded wildcard types apply to both bounds + wctype.getExtendsBound().addAnnotations(annos); + wctype.getSuperBound().addAnnotations(annos); + } else if (underlyingTree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { + wctype.getSuperBound().addAnnotations(annos); + } else if (underlyingTree.getKind() == Tree.Kind.SUPER_WILDCARD) { + wctype.getExtendsBound().addAnnotations(annos); + } else { + throw new BugInCF( + "Unexpected kind for type. tree=" + + tree + + " type=" + + type + + " kind=" + + underlyingTree.getKind()); + } + } else { + type.addAnnotations(annos); + } - return type; - } - - @Override - public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror component = visit(tree.getType(), f); - - AnnotatedTypeMirror result = f.type(tree); - assert result instanceof AnnotatedArrayType; - ((AnnotatedArrayType) result).setComponentType(component); - return result; - } - - @Override - public AnnotatedTypeMirror visitParameterizedType( - ParameterizedTypeTree tree, AnnotatedTypeFactory f) { - ClassSymbol baseType = (ClassSymbol) TreeUtils.elementFromTree(tree.getType()); - updateWildcardBounds(tree.getTypeArguments(), baseType.getTypeParameters()); - - List args = - CollectionsPlume.mapList((Tree t) -> visit(t, f), tree.getTypeArguments()); - - AnnotatedTypeMirror result = f.type(tree); // use creator? - AnnotatedTypeMirror atype = visit(tree.getType(), f); - result.addAnnotations(atype.getAnnotations()); - // new ArrayList<>() type is AnnotatedExecutableType for some reason - - // Don't initialize the type arguments if they are empty. The type arguments might be a - // diamond which should be inferred. - if (result instanceof AnnotatedDeclaredType && !args.isEmpty()) { - assert result instanceof AnnotatedDeclaredType : tree + " --> " + result; - ((AnnotatedDeclaredType) result).setTypeArguments(args); + return type; } - return result; - } - - /** - * Work around a bug in javac 9 where sometimes the bound field is set to the transitive - * supertype's type parameter instead of the type parameter which the wildcard directly - * instantiates. See https://github.com/eisop/checker-framework/issues/18 - * - *

          Sets each wildcard type argument's bound from typeArgs to the corresponding type parameter - * from typeParams. - * - *

          If typeArgs.size() == 0 the method does nothing and returns. Otherwise, typeArgs.size() has - * to be equal to typeParams.size(). - * - *

          For each wildcard type argument and corresponding type parameter, sets the - * WildcardType.bound field to the corresponding type parameter, if and only if the owners of the - * existing bound and the type parameter are different. - * - *

          In scenarios where the bound's owner is the same, we don't want to replace a - * capture-converted bound in the wildcard type with a non-capture-converted bound given by the - * type parameter declaration. - * - * @param typeArgs the type of the arguments at (e.g., at the call side) - * @param typeParams the type of the formal parameters (e.g., at the method declaration) - */ - @SuppressWarnings("interning:not.interned") // workaround for javac bug - private void updateWildcardBounds( - List typeArgs, List typeParams) { - if (typeArgs.isEmpty()) { - // Nothing to do for empty type arguments. - return; + + @Override + public AnnotatedTypeMirror visitArrayType(ArrayTypeTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror component = visit(tree.getType(), f); + + AnnotatedTypeMirror result = f.type(tree); + assert result instanceof AnnotatedArrayType; + ((AnnotatedArrayType) result).setComponentType(component); + return result; } - assert typeArgs.size() == typeParams.size(); - - Iterator typeArgsItr = typeArgs.iterator(); - Iterator typeParamsItr = typeParams.iterator(); - while (typeArgsItr.hasNext()) { - Tree typeArg = typeArgsItr.next(); - TypeVariableSymbol typeParam = typeParamsItr.next(); - if (typeArg instanceof WildcardTree) { - TypeVar typeVar = (TypeVar) typeParam.asType(); - WildcardType wcType = (WildcardType) ((JCWildcard) typeArg).type; - if (wcType.bound != null - && wcType.bound.tsym != null - && typeVar.tsym != null - && wcType.bound.tsym.owner != typeVar.tsym.owner) { - wcType.withTypeVar(typeVar); + + @Override + public AnnotatedTypeMirror visitParameterizedType( + ParameterizedTypeTree tree, AnnotatedTypeFactory f) { + ClassSymbol baseType = (ClassSymbol) TreeUtils.elementFromTree(tree.getType()); + updateWildcardBounds(tree.getTypeArguments(), baseType.getTypeParameters()); + + List args = + CollectionsPlume.mapList((Tree t) -> visit(t, f), tree.getTypeArguments()); + + AnnotatedTypeMirror result = f.type(tree); // use creator? + AnnotatedTypeMirror atype = visit(tree.getType(), f); + result.addAnnotations(atype.getAnnotations()); + // new ArrayList<>() type is AnnotatedExecutableType for some reason + + // Don't initialize the type arguments if they are empty. The type arguments might be a + // diamond which should be inferred. + if (result instanceof AnnotatedDeclaredType && !args.isEmpty()) { + assert result instanceof AnnotatedDeclaredType : tree + " --> " + result; + ((AnnotatedDeclaredType) result).setTypeArguments(args); } - } - } - } - - @Override - public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { - return f.type(tree); - } - - @Override - public AnnotatedTypeVariable visitTypeParameter( - TypeParameterTree tree, @FindDistinct AnnotatedTypeFactory f) { - if (visitedTypeParameter.containsKey(tree)) { - return visitedTypeParameter.get(tree); + return result; } - AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(tree); - // If this type parameter is recursive and it is found again while visiting the bounds, then - // use the same AnnotateTypeVariable object. - visitedTypeParameter.put(tree, result); - - List bounds = new ArrayList<>(tree.getBounds().size()); - for (Tree t : tree.getBounds()) { - bounds.add(visit(t, f)); + /** + * Work around a bug in javac 9 where sometimes the bound field is set to the transitive + * supertype's type parameter instead of the type parameter which the wildcard directly + * instantiates. See https://github.com/eisop/checker-framework/issues/18 + * + *

          Sets each wildcard type argument's bound from typeArgs to the corresponding type parameter + * from typeParams. + * + *

          If typeArgs.size() == 0 the method does nothing and returns. Otherwise, typeArgs.size() + * has to be equal to typeParams.size(). + * + *

          For each wildcard type argument and corresponding type parameter, sets the + * WildcardType.bound field to the corresponding type parameter, if and only if the owners of + * the existing bound and the type parameter are different. + * + *

          In scenarios where the bound's owner is the same, we don't want to replace a + * capture-converted bound in the wildcard type with a non-capture-converted bound given by the + * type parameter declaration. + * + * @param typeArgs the type of the arguments at (e.g., at the call side) + * @param typeParams the type of the formal parameters (e.g., at the method declaration) + */ + @SuppressWarnings("interning:not.interned") // workaround for javac bug + private void updateWildcardBounds( + List typeArgs, List typeParams) { + if (typeArgs.isEmpty()) { + // Nothing to do for empty type arguments. + return; + } + assert typeArgs.size() == typeParams.size(); + + Iterator typeArgsItr = typeArgs.iterator(); + Iterator typeParamsItr = typeParams.iterator(); + while (typeArgsItr.hasNext()) { + Tree typeArg = typeArgsItr.next(); + TypeVariableSymbol typeParam = typeParamsItr.next(); + if (typeArg instanceof WildcardTree) { + TypeVar typeVar = (TypeVar) typeParam.asType(); + WildcardType wcType = (WildcardType) ((JCWildcard) typeArg).type; + if (wcType.bound != null + && wcType.bound.tsym != null + && typeVar.tsym != null + && wcType.bound.tsym.owner != typeVar.tsym.owner) { + wcType.withTypeVar(typeVar); + } + } + } } - visitedTypeParameter.remove(tree); - - List annotations = TreeUtils.annotationsFromTree(tree); - result.getLowerBound().addAnnotations(annotations); - - switch (bounds.size()) { - case 0: - break; - case 1: - result.setUpperBound(bounds.get(0)); - break; - default: - AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) result.getUpperBound(); - intersection.setBounds(bounds); - intersection.copyIntersectionBoundAnnotations(); + + @Override + public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedTypeFactory f) { + return f.type(tree); } - return result; - } + @Override + public AnnotatedTypeVariable visitTypeParameter( + TypeParameterTree tree, @FindDistinct AnnotatedTypeFactory f) { + if (visitedTypeParameter.containsKey(tree)) { + return visitedTypeParameter.get(tree); + } - @Override - public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror bound = visit(tree.getBound(), f); - AnnotatedTypeMirror result = f.type(tree); - assert result instanceof AnnotatedWildcardType; - f.initializeAtm(result); + AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(tree); + // If this type parameter is recursive and it is found again while visiting the bounds, then + // use the same AnnotateTypeVariable object. + visitedTypeParameter.put(tree, result); - // for wildcards unlike type variables there are bounds that differ in type from - // result. These occur for RAW types. In this case, use the newly created bound - // rather than merging into result - if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { - ((AnnotatedWildcardType) result).setSuperBound(bound); + List bounds = new ArrayList<>(tree.getBounds().size()); + for (Tree t : tree.getBounds()) { + bounds.add(visit(t, f)); + } + visitedTypeParameter.remove(tree); + + List annotations = TreeUtils.annotationsFromTree(tree); + result.getLowerBound().addAnnotations(annotations); + + switch (bounds.size()) { + case 0: + break; + case 1: + result.setUpperBound(bounds.get(0)); + break; + default: + AnnotatedIntersectionType intersection = + (AnnotatedIntersectionType) result.getUpperBound(); + intersection.setBounds(bounds); + intersection.copyIntersectionBoundAnnotations(); + } - } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { - ((AnnotatedWildcardType) result).setExtendsBound(bound); + return result; } - return result; - } - - /** - * If a tree is can be found for the declaration of the type variable {@code type}, then a {@link - * AnnotatedTypeVariable} is returned with explicit annotations from the type variables declared - * bounds. If a tree cannot be found, then {@code type}, converted to a use, is returned. - * - * @param type type variable used to find declaration tree - * @param f annotated type factory - * @return the AnnotatedTypeVariable from the declaration of {@code type} or {@code type} if no - * tree is found. - */ - private AnnotatedTypeVariable getTypeVariableFromDeclaration( - AnnotatedTypeVariable type, AnnotatedTypeFactory f) { - TypeVariable typeVar = type.getUnderlyingType(); - TypeParameterElement tpe = (TypeParameterElement) typeVar.asElement(); - Element elt = tpe.getGenericElement(); - if (elt instanceof TypeElement) { - TypeElement typeElt = (TypeElement) elt; - int idx = typeElt.getTypeParameters().indexOf(tpe); - if (idx == -1) { - idx = findIndex(typeElt.getTypeParameters(), tpe); - } - ClassTree cls = (ClassTree) f.declarationFromElement(typeElt); - if (cls == null || cls.getTypeParameters().isEmpty()) { - // The type parameters in the source tree were already erased. The element already - // contains all necessary information and we can return that. - return type.asUse(); - } - - // `forTypeVariable` is called for Identifier, MemberSelect and UnionType trees, - // none of which are declarations. But `cls.getTypeParameters()` returns a list - // of type parameter declarations (`TypeParameterTree`), so this call - // will return a declaration ATV. So change it to a use. - return visitTypeParameter(cls.getTypeParameters().get(idx), f).asUse(); - } else if (elt instanceof ExecutableElement) { - ExecutableElement exElt = (ExecutableElement) elt; - int idx = exElt.getTypeParameters().indexOf(tpe); - if (idx == -1) { - idx = findIndex(exElt.getTypeParameters(), tpe); - } - MethodTree meth = (MethodTree) f.declarationFromElement(exElt); - if (meth == null) { - // meth can be null when no source code was found for it. - return type.asUse(); - } - // This works the same as the case above. Even though `meth` itself is not a - // type declaration tree, the elements of `meth.getTypeParameters()` still are. - AnnotatedTypeVariable result = - visitTypeParameter(meth.getTypeParameters().get(idx), f).shallowCopy(); - result.setDeclaration(false); - return result; - } else if (TypesUtils.isCapturedTypeVariable(typeVar)) { - // Captured type variables can have a generic element (owner) that is - // not an element at all, namely Symtab.noSymbol. - return type.asUse(); - } else { - throw new BugInCF("TypeFromTree.forTypeVariable: not a supported element: " + elt); + + @Override + public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror bound = visit(tree.getBound(), f); + AnnotatedTypeMirror result = f.type(tree); + assert result instanceof AnnotatedWildcardType; + f.initializeAtm(result); + + // for wildcards unlike type variables there are bounds that differ in type from + // result. These occur for RAW types. In this case, use the newly created bound + // rather than merging into result + if (tree.getKind() == Tree.Kind.SUPER_WILDCARD) { + ((AnnotatedWildcardType) result).setSuperBound(bound); + + } else if (tree.getKind() == Tree.Kind.EXTENDS_WILDCARD) { + ((AnnotatedWildcardType) result).setExtendsBound(bound); + } + return result; + } + + /** + * If a tree is can be found for the declaration of the type variable {@code type}, then a + * {@link AnnotatedTypeVariable} is returned with explicit annotations from the type variables + * declared bounds. If a tree cannot be found, then {@code type}, converted to a use, is + * returned. + * + * @param type type variable used to find declaration tree + * @param f annotated type factory + * @return the AnnotatedTypeVariable from the declaration of {@code type} or {@code type} if no + * tree is found. + */ + private AnnotatedTypeVariable getTypeVariableFromDeclaration( + AnnotatedTypeVariable type, AnnotatedTypeFactory f) { + TypeVariable typeVar = type.getUnderlyingType(); + TypeParameterElement tpe = (TypeParameterElement) typeVar.asElement(); + Element elt = tpe.getGenericElement(); + if (elt instanceof TypeElement) { + TypeElement typeElt = (TypeElement) elt; + int idx = typeElt.getTypeParameters().indexOf(tpe); + if (idx == -1) { + idx = findIndex(typeElt.getTypeParameters(), tpe); + } + ClassTree cls = (ClassTree) f.declarationFromElement(typeElt); + if (cls == null || cls.getTypeParameters().isEmpty()) { + // The type parameters in the source tree were already erased. The element already + // contains all necessary information and we can return that. + return type.asUse(); + } + + // `forTypeVariable` is called for Identifier, MemberSelect and UnionType trees, + // none of which are declarations. But `cls.getTypeParameters()` returns a list + // of type parameter declarations (`TypeParameterTree`), so this call + // will return a declaration ATV. So change it to a use. + return visitTypeParameter(cls.getTypeParameters().get(idx), f).asUse(); + } else if (elt instanceof ExecutableElement) { + ExecutableElement exElt = (ExecutableElement) elt; + int idx = exElt.getTypeParameters().indexOf(tpe); + if (idx == -1) { + idx = findIndex(exElt.getTypeParameters(), tpe); + } + MethodTree meth = (MethodTree) f.declarationFromElement(exElt); + if (meth == null) { + // meth can be null when no source code was found for it. + return type.asUse(); + } + // This works the same as the case above. Even though `meth` itself is not a + // type declaration tree, the elements of `meth.getTypeParameters()` still are. + AnnotatedTypeVariable result = + visitTypeParameter(meth.getTypeParameters().get(idx), f).shallowCopy(); + result.setDeclaration(false); + return result; + } else if (TypesUtils.isCapturedTypeVariable(typeVar)) { + // Captured type variables can have a generic element (owner) that is + // not an element at all, namely Symtab.noSymbol. + return type.asUse(); + } else { + throw new BugInCF("TypeFromTree.forTypeVariable: not a supported element: " + elt); + } } - } - - /** - * Finds the index of {@code type} in {@code typeParameters} using {@link - * TypesUtils#areSame(TypeVariable, TypeVariable)} instead of {@link Object#equals(Object)}. - * - * @param typeParameters a list of type parameters - * @param type a type parameter - * @return the index of {@code type} in {@code typeParameters} using {@link - * TypesUtils#areSame(TypeVariable, TypeVariable)} or -1 if it does not exist - */ - private int findIndex( - List typeParameters, TypeParameterElement type) { - TypeVariable typeVariable = (TypeVariable) type.asType(); - - for (int i = 0; i < typeParameters.size(); i++) { - TypeVariable typeVariable1 = (TypeVariable) typeParameters.get(i).asType(); - if (TypesUtils.areSame(typeVariable1, typeVariable)) { - return i; - } + + /** + * Finds the index of {@code type} in {@code typeParameters} using {@link + * TypesUtils#areSame(TypeVariable, TypeVariable)} instead of {@link Object#equals(Object)}. + * + * @param typeParameters a list of type parameters + * @param type a type parameter + * @return the index of {@code type} in {@code typeParameters} using {@link + * TypesUtils#areSame(TypeVariable, TypeVariable)} or -1 if it does not exist + */ + private int findIndex( + List typeParameters, TypeParameterElement type) { + TypeVariable typeVariable = (TypeVariable) type.asType(); + + for (int i = 0; i < typeParameters.size(); i++) { + TypeVariable typeVariable1 = (TypeVariable) typeParameters.get(i).asType(); + if (TypesUtils.areSame(typeVariable1, typeVariable)) { + return i; + } + } + return -1; } - return -1; - } - @Override - public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.type(tree); + @Override + public AnnotatedTypeMirror visitIdentifier(IdentifierTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.type(tree); + + if (type.getKind() == TypeKind.TYPEVAR) { + return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); + } - if (type.getKind() == TypeKind.TYPEVAR) { - return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); + return type; } - return type; - } + @Override + public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.type(tree); - @Override - public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.type(tree); + if (type.getKind() == TypeKind.TYPEVAR) { + return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); + } - if (type.getKind() == TypeKind.TYPEVAR) { - return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); + return type; } - return type; - } + @Override + public AnnotatedTypeMirror visitUnionType(UnionTypeTree tree, AnnotatedTypeFactory f) { + AnnotatedTypeMirror type = f.type(tree); - @Override - public AnnotatedTypeMirror visitUnionType(UnionTypeTree tree, AnnotatedTypeFactory f) { - AnnotatedTypeMirror type = f.type(tree); + if (type.getKind() == TypeKind.TYPEVAR) { + return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); + } - if (type.getKind() == TypeKind.TYPEVAR) { - return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f); + return type; } - return type; - } - - @Override - public AnnotatedTypeMirror visitIntersectionType( - IntersectionTypeTree tree, AnnotatedTypeFactory f) { - // This method is only called for IntersectionTypes in casts. There is no - // IntersectionTypeTree - // for a type variable bound that is an intersection. See #visitTypeParameter. - AnnotatedIntersectionType type = (AnnotatedIntersectionType) f.type(tree); - List bounds = - CollectionsPlume.mapList((Tree boundTree) -> visit(boundTree, f), tree.getBounds()); - type.setBounds(bounds); - type.copyIntersectionBoundAnnotations(); - return type; - } + @Override + public AnnotatedTypeMirror visitIntersectionType( + IntersectionTypeTree tree, AnnotatedTypeFactory f) { + // This method is only called for IntersectionTypes in casts. There is no + // IntersectionTypeTree + // for a type variable bound that is an intersection. See #visitTypeParameter. + AnnotatedIntersectionType type = (AnnotatedIntersectionType) f.type(tree); + List bounds = + CollectionsPlume.mapList((Tree boundTree) -> visit(boundTree, f), tree.getBounds()); + type.setBounds(bounds); + type.copyIntersectionBoundAnnotations(); + return type; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java index 0d86838a8b9..f34d1999a09 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java @@ -1,174 +1,176 @@ package org.checkerframework.framework.type; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.util.AnnotatedTypes; + import java.util.Collection; import java.util.List; + import javax.lang.model.element.AnnotationMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.util.AnnotatedTypes; /** Compares AnnotatedTypeMirrors for subtype relationships. See also {@link QualifierHierarchy}. */ public interface TypeHierarchy { - // This can be used if: - // * the type is fully annotated, - // * the basetypes are Java subtypes, and - // * you want to check the full type - // Otherwise, call QualifierHierarchy. + // This can be used if: + // * the type is fully annotated, + // * the basetypes are Java subtypes, and + // * you want to check the full type + // Otherwise, call QualifierHierarchy. - // `TypeHierarchy` is an interface because the only implementation, DefaultTypeHierarchy, has - // public visitor methods that clients should never call. + // `TypeHierarchy` is an interface because the only implementation, DefaultTypeHierarchy, has + // public visitor methods that clients should never call. - /** - * Returns true if {@code subtype} is a subtype of or convertible to {@code supertype} for all - * hierarchies present. If the underlying Java type of {@code subtype} is not a subtype of or - * convertible to the underlying Java type of {@code supertype}, then the behavior of this method - * is undefined. - * - *

          Ideally, types that require conversions would be converted before isSubtype is called, but - * instead, isSubtype performs some of these conversions. - * - *

          JLS 5.1 specifies 13 categories of conversions. - * - *

          3 categories are converted in isSubtype: - * - *

            - *
          • Boxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getBoxedType} - *
          • Unboxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getUnboxedType} - *
          • String conversions: Any type to String. isSubtype calls {@link AnnotatedTypes#asSuper} - * which calls {@link AnnotatedTypeFactory#getStringType(AnnotatedTypeMirror)} - *
          - * - * 2 happen elsewhere: - * - *
            - *
          • Unchecked conversions: Generic type to raw type. Raw types are instantiated with bounds - * in AnnotatedTypeFactory#fromTypeTree before is subtype is called - *
          • Capture conversions: Wildcards are captured in {@link - * AnnotatedTypeFactory#applyCaptureConversion(AnnotatedTypeMirror)} - *
          - * - * 7 are not explicitly converted and are treated as though the types are actually subtypes. - * - *
            - *
          • Identity conversions: type to same type - *
          • Widening primitive conversions: primitive to primitive (no loss of information, byte to - * short for example) - *
          • Narrowing primitive conversions: primitive to primitive (possibly loss of information, - * short to byte for example) - *
          • Widening and Narrowing Primitive Conversion: byte to char - *
          • Widening reference conversions: Upcast - *
          • Narrowing reference conversions: Downcast - *
          • Value set conversions: floating-point value from one value set to another without - * changing its type. - *
          - * - * @param subtype possible subtype - * @param supertype possible supertype - * @return true if {@code subtype} is a subtype of {@code supertype} for all hierarchies present - */ - boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); + /** + * Returns true if {@code subtype} is a subtype of or convertible to {@code supertype} for all + * hierarchies present. If the underlying Java type of {@code subtype} is not a subtype of or + * convertible to the underlying Java type of {@code supertype}, then the behavior of this + * method is undefined. + * + *

          Ideally, types that require conversions would be converted before isSubtype is called, but + * instead, isSubtype performs some of these conversions. + * + *

          JLS 5.1 specifies 13 categories of conversions. + * + *

          3 categories are converted in isSubtype: + * + *

            + *
          • Boxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getBoxedType} + *
          • Unboxing conversions: isSubtype calls {@link AnnotatedTypeFactory#getUnboxedType} + *
          • String conversions: Any type to String. isSubtype calls {@link AnnotatedTypes#asSuper} + * which calls {@link AnnotatedTypeFactory#getStringType(AnnotatedTypeMirror)} + *
          + * + * 2 happen elsewhere: + * + *
            + *
          • Unchecked conversions: Generic type to raw type. Raw types are instantiated with bounds + * in AnnotatedTypeFactory#fromTypeTree before is subtype is called + *
          • Capture conversions: Wildcards are captured in {@link + * AnnotatedTypeFactory#applyCaptureConversion(AnnotatedTypeMirror)} + *
          + * + * 7 are not explicitly converted and are treated as though the types are actually subtypes. + * + *
            + *
          • Identity conversions: type to same type + *
          • Widening primitive conversions: primitive to primitive (no loss of information, byte to + * short for example) + *
          • Narrowing primitive conversions: primitive to primitive (possibly loss of information, + * short to byte for example) + *
          • Widening and Narrowing Primitive Conversion: byte to char + *
          • Widening reference conversions: Upcast + *
          • Narrowing reference conversions: Downcast + *
          • Value set conversions: floating-point value from one value set to another without + * changing its type. + *
          + * + * @param subtype possible subtype + * @param supertype possible supertype + * @return true if {@code subtype} is a subtype of {@code supertype} for all hierarchies present + */ + boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - /** - * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers - * of the effective annotations of {@code supertype}, according to the type qualifier hierarchy. - * - *

          The underlying types of {@code subtype} and {@code supertype} are not necessarily in a Java - * subtyping relationship with one another and are only used by this method for special cases when - * qualifier subtyping depends on the Java basetype. - * - * @param subtype possible subtype - * @param supertype possible supertype - * @return true iff the effective annotations of {@code subtype} are equal to or are - * sub-qualifiers of the effective annotations of {@code supertype} - */ - boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); + /** + * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers + * of the effective annotations of {@code supertype}, according to the type qualifier hierarchy. + * + *

          The underlying types of {@code subtype} and {@code supertype} are not necessarily in a + * Java subtyping relationship with one another and are only used by this method for special + * cases when qualifier subtyping depends on the Java basetype. + * + * @param subtype possible subtype + * @param supertype possible supertype + * @return true iff the effective annotations of {@code subtype} are equal to or are + * sub-qualifiers of the effective annotations of {@code supertype} + */ + boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype); - /** - * Tests whether the effective annotation in the same hierarchy as {@code hierarchy} of {@code - * subtype} are equal to or are sub-qualifiers of the effective annotation of {@code supertype} in - * the same hierarchy as {@code hierarchy}, according to the type qualifier hierarchy. Other - * annotations in {@code subtype} and {@code supertype} are ignored. - * - *

          The underlying types of {@code subtype} and {@code supertype} are not necessarily in a Java - * subtyping relationship with one another and are only used by this method for special cases when - * qualifier subtyping depends on the Java basetype. - * - * @param subtype possible subtype - * @param supertype possible supertype - * @param hierarchy an annotation whose hierarchy is used to compare {@code subtype} and {@code - * supertype} - * @return true iff the effective annotation in the same hierarchy as {@code hierarchy} of {@code - * subtype} are equal to or are sub-qualifiers of the effective annotation of {@code - * supertype} in the same hierarchy as {@code hierarchy} - */ - boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror hierarchy); + /** + * Tests whether the effective annotation in the same hierarchy as {@code hierarchy} of {@code + * subtype} are equal to or are sub-qualifiers of the effective annotation of {@code supertype} + * in the same hierarchy as {@code hierarchy}, according to the type qualifier hierarchy. Other + * annotations in {@code subtype} and {@code supertype} are ignored. + * + *

          The underlying types of {@code subtype} and {@code supertype} are not necessarily in a + * Java subtyping relationship with one another and are only used by this method for special + * cases when qualifier subtyping depends on the Java basetype. + * + * @param subtype possible subtype + * @param supertype possible supertype + * @param hierarchy an annotation whose hierarchy is used to compare {@code subtype} and {@code + * supertype} + * @return true iff the effective annotation in the same hierarchy as {@code hierarchy} of + * {@code subtype} are equal to or are sub-qualifiers of the effective annotation of {@code + * supertype} in the same hierarchy as {@code hierarchy} + */ + boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror hierarchy); - /** - * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers - * of {@code superQualifiers}, according to the type qualifier hierarchy. Other annotations in - * {@code subtype} are ignored. - * - *

          The underlying type of {@code subtype} is only used by this method for special cases when - * qualifier subtyping depends on the Java basetype. - * - * @param subtype possible subtype - * @param superQualifiers possible superQualifiers - * @return true iff the effective annotations of {@code subtype} are equal to or are - * sub-qualifiers of {@code superQualifiers} - */ - boolean isSubtypeShallowEffective( - AnnotatedTypeMirror subtype, Collection superQualifiers); + /** + * Tests whether the effective annotations of {@code subtype} are equal to or are sub-qualifiers + * of {@code superQualifiers}, according to the type qualifier hierarchy. Other annotations in + * {@code subtype} are ignored. + * + *

          The underlying type of {@code subtype} is only used by this method for special cases when + * qualifier subtyping depends on the Java basetype. + * + * @param subtype possible subtype + * @param superQualifiers possible superQualifiers + * @return true iff the effective annotations of {@code subtype} are equal to or are + * sub-qualifiers of {@code superQualifiers} + */ + boolean isSubtypeShallowEffective( + AnnotatedTypeMirror subtype, Collection superQualifiers); - /** - * Tests whether {@code subQualifiers} are equal to or are sub-qualifiers of the effective - * annotations of {@code supertype}, according to the type qualifier hierarchy. Other annotations - * in {@code supertype} are ignored. - * - *

          The underlying type of {@code supertype} is used by this method for special cases when - * qualifier subtyping depends on the Java basetype. - * - * @param subQualifiers possible subQualifiers - * @param supertype possible supertype - * @return true iff {@code subQualifiers} are equal to or are sub-qualifiers of the effective - * annotations of {@code supertype} - */ - boolean isSubtypeShallowEffective( - Collection subQualifiers, AnnotatedTypeMirror supertype); + /** + * Tests whether {@code subQualifiers} are equal to or are sub-qualifiers of the effective + * annotations of {@code supertype}, according to the type qualifier hierarchy. Other + * annotations in {@code supertype} are ignored. + * + *

          The underlying type of {@code supertype} is used by this method for special cases when + * qualifier subtyping depends on the Java basetype. + * + * @param subQualifiers possible subQualifiers + * @param supertype possible supertype + * @return true iff {@code subQualifiers} are equal to or are sub-qualifiers of the effective + * annotations of {@code supertype} + */ + boolean isSubtypeShallowEffective( + Collection subQualifiers, AnnotatedTypeMirror supertype); - /** - * Tests whether the effective annotation of {@code subtype} in the same hierarchy as {@code - * superQualifier} is equal to or sub-qualifier of {@code superQualifier}, according to the type - * qualifier hierarchy. The underlying types of {@code subtype} is only used by this method for - * special cases when qualifier subtyping depends on the Java basetype. Other annotations in - * {@code subtype} are ignored. - * - * @param subtype possible subtype - * @param superQualifier possible super qualifier - * @return true iffhe effective annotation of {@code subtype} in the same hierarchy as {@code - * superQualifier} is equal to or sub-qualifier of {@code superQualifier} - */ - boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotationMirror superQualifier); + /** + * Tests whether the effective annotation of {@code subtype} in the same hierarchy as {@code + * superQualifier} is equal to or sub-qualifier of {@code superQualifier}, according to the type + * qualifier hierarchy. The underlying types of {@code subtype} is only used by this method for + * special cases when qualifier subtyping depends on the Java basetype. Other annotations in + * {@code subtype} are ignored. + * + * @param subtype possible subtype + * @param superQualifier possible super qualifier + * @return true iffhe effective annotation of {@code subtype} in the same hierarchy as {@code + * superQualifier} is equal to or sub-qualifier of {@code superQualifier} + */ + boolean isSubtypeShallowEffective(AnnotatedTypeMirror subtype, AnnotationMirror superQualifier); - /** - * Tests whether {@code subQualifier} is equal to or sub-qualifier of the effective annotation of - * {@code supertype} in the same hierarchy as {@code subQualifier} according to the type qualifier - * hierarchy. The underlying types of {@code supertype} is only used by this method for special - * cases when qualifier subtyping depends on the Java basetype. Other annotations in {@code - * supertype} are ignored. - * - * @param subQualifier possible subQualifier - * @param supertype possible supertype - * @return true {@code subQualifier} is equal to or sub-qualifier of the effective annotation of - * {@code supertype} in the same hierarchy as {@code subQualifier} - */ - boolean isSubtypeShallowEffective(AnnotationMirror subQualifier, AnnotatedTypeMirror supertype); + /** + * Tests whether {@code subQualifier} is equal to or sub-qualifier of the effective annotation + * of {@code supertype} in the same hierarchy as {@code subQualifier} according to the type + * qualifier hierarchy. The underlying types of {@code supertype} is only used by this method + * for special cases when qualifier subtyping depends on the Java basetype. Other annotations in + * {@code supertype} are ignored. + * + * @param subQualifier possible subQualifier + * @param supertype possible supertype + * @return true {@code subQualifier} is equal to or sub-qualifier of the effective annotation of + * {@code supertype} in the same hierarchy as {@code subQualifier} + */ + boolean isSubtypeShallowEffective(AnnotationMirror subQualifier, AnnotatedTypeMirror supertype); - /** - * Returns a list of the indices of the type arguments that are covariant. - * - * @param type a type - * @return a list of the indices of the type arguments that are covariant - */ - List getCovariantArgIndexes(AnnotatedDeclaredType type); + /** + * Returns a list of the indices of the type arguments that are covariant. + * + * @param type a type + * @return a list of the indices of the type arguments that are covariant + */ + List getCovariantArgIndexes(AnnotatedDeclaredType type); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java index 349113a5e0c..7c7df75fbf8 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java @@ -1,172 +1,181 @@ package org.checkerframework.framework.type; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.javacutil.TypesUtils; + import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.Element; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.javacutil.TypesUtils; /** TypeVariableSubstitutor replaces type variables from a declaration with arguments to its use. */ public class TypeVariableSubstitutor { - /** Create a TypeVariableSubstitutor. */ - public TypeVariableSubstitutor() {} - - /** - * Given a mapping from type variable to its type argument, replace each instance of a type - * variable with a copy of type argument. - * - * @see #substituteTypeVariable(AnnotatedTypeMirror, - * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) - * @param typeVarToTypeArgument a mapping from type variable to its type argument - * @param type the type to substitute - * @return a copy of type with its type variables substituted - */ - public AnnotatedTypeMirror substitute( - Map typeVarToTypeArgument, AnnotatedTypeMirror type) { - return new Visitor(typeVarToTypeArgument, true).visit(type); - } - - /** - * Given a mapping from type variable to its type argument, replace each instance of a type - * variable with the given type argument. - * - * @see #substituteTypeVariable(AnnotatedTypeMirror, - * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) - * @param typeVarToTypeArgument a mapping from type variable to its type argument - * @param type the type to substitute - * @return a copy of type with its type variables substituted - */ - public AnnotatedTypeMirror substituteWithoutCopyingTypeArguments( - Map typeVarToTypeArgument, AnnotatedTypeMirror type) { - return new Visitor(typeVarToTypeArgument, false).visit(type); - } - - /** - * Given the types of a type parameter declaration, the argument to that type parameter - * declaration, and a given use of that declaration, return a substitute for the use with the - * correct annotations. - * - *

          To determine what primary annotations are correct for the substitute the following rules are - * used: If the type variable use has a primary annotation then apply that primary annotation to - * the substitute. Otherwise, use the annotations of the argument. - * - * @param argument the argument to declaration (this will be a value in typeParamToArg) - * @param use the use that is being replaced - * @return a deep copy of argument with the appropriate annotations applied - */ - protected AnnotatedTypeMirror substituteTypeVariable( - AnnotatedTypeMirror argument, AnnotatedTypeVariable use) { - AnnotatedTypeMirror substitute = argument.deepCopy(true); - if (!use.getAnnotationsField().isEmpty()) { - substitute.replaceAnnotations(use.getAnnotations()); - } - return substitute; - } - - /** - * Visitor that makes the substitution. This is an inner class so that its methods cannot be - * called by clients of {@link TypeVariableSubstitutor}. - */ - protected class Visitor extends AnnotatedTypeCopier { - - /** - * A mapping from {@link TypeParameterElement} to the {@link AnnotatedTypeMirror} that should - * replace its uses. - */ - private final Map elementToArgMap; + /** Create a TypeVariableSubstitutor. */ + public TypeVariableSubstitutor() {} /** - * A list of type variables that should be replaced by the type mirror at the same index in - * {@code typeMirrors} + * Given a mapping from type variable to its type argument, replace each instance of a type + * variable with a copy of type argument. + * + * @see #substituteTypeVariable(AnnotatedTypeMirror, + * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) + * @param typeVarToTypeArgument a mapping from type variable to its type argument + * @param type the type to substitute + * @return a copy of type with its type variables substituted */ - private final List typeVars; + public AnnotatedTypeMirror substitute( + Map typeVarToTypeArgument, + AnnotatedTypeMirror type) { + return new Visitor(typeVarToTypeArgument, true).visit(type); + } /** - * A list of TypeMirrors that should replace the type variable at the same index in {@code - * typeVars} + * Given a mapping from type variable to its type argument, replace each instance of a type + * variable with the given type argument. + * + * @see #substituteTypeVariable(AnnotatedTypeMirror, + * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) + * @param typeVarToTypeArgument a mapping from type variable to its type argument + * @param type the type to substitute + * @return a copy of type with its type variables substituted */ - private final List typeMirrors; - - /** Whether or not a copy of type argument should be substituted. */ - private final boolean copyArgument; + public AnnotatedTypeMirror substituteWithoutCopyingTypeArguments( + Map typeVarToTypeArgument, + AnnotatedTypeMirror type) { + return new Visitor(typeVarToTypeArgument, false).visit(type); + } /** - * Creates the Visitor. + * Given the types of a type parameter declaration, the argument to that type parameter + * declaration, and a given use of that declaration, return a substitute for the use with the + * correct annotations. + * + *

          To determine what primary annotations are correct for the substitute the following rules + * are used: If the type variable use has a primary annotation then apply that primary + * annotation to the substitute. Otherwise, use the annotations of the argument. * - * @param typeParamToArg mapping from TypeVariable to the AnnotatedTypeMirror that will replace - * it - * @param copyArgument whether or not a copy of type argument should be substituted + * @param argument the argument to declaration (this will be a value in typeParamToArg) + * @param use the use that is being replaced + * @return a deep copy of argument with the appropriate annotations applied */ - public Visitor(Map typeParamToArg, boolean copyArgument) { - int size = typeParamToArg.size(); - elementToArgMap = new HashMap<>(size); - typeVars = new ArrayList<>(size); - typeMirrors = new ArrayList<>(size); - - for (Map.Entry paramToArg : typeParamToArg.entrySet()) { - elementToArgMap.put( - (TypeParameterElement) paramToArg.getKey().asElement(), paramToArg.getValue()); - typeVars.add(paramToArg.getKey()); - typeMirrors.add(paramToArg.getValue().getUnderlyingType()); - } - this.copyArgument = copyArgument; + protected AnnotatedTypeMirror substituteTypeVariable( + AnnotatedTypeMirror argument, AnnotatedTypeVariable use) { + AnnotatedTypeMirror substitute = argument.deepCopy(true); + if (!use.getAnnotationsField().isEmpty()) { + substitute.replaceAnnotations(use.getAnnotations()); + } + return substitute; } - @Override - protected T makeCopy(T original) { - if (original.getKind() == TypeKind.TYPEVAR) { - return super.makeCopy(original); - } - TypeMirror s = - TypesUtils.substitute( - original.getUnderlyingType(), - typeVars, - typeMirrors, - original.atypeFactory.processingEnv); - - @SuppressWarnings("unchecked") - T copy = - (T) AnnotatedTypeMirror.createType(s, original.atypeFactory, original.isDeclaration()); - maybeCopyPrimaryAnnotations(original, copy); - - return copy; - } + /** + * Visitor that makes the substitution. This is an inner class so that its methods cannot be + * called by clients of {@link TypeVariableSubstitutor}. + */ + protected class Visitor extends AnnotatedTypeCopier { + + /** + * A mapping from {@link TypeParameterElement} to the {@link AnnotatedTypeMirror} that + * should replace its uses. + */ + private final Map elementToArgMap; + + /** + * A list of type variables that should be replaced by the type mirror at the same index in + * {@code typeMirrors} + */ + private final List typeVars; + + /** + * A list of TypeMirrors that should replace the type variable at the same index in {@code + * typeVars} + */ + private final List typeMirrors; + + /** Whether or not a copy of type argument should be substituted. */ + private final boolean copyArgument; + + /** + * Creates the Visitor. + * + * @param typeParamToArg mapping from TypeVariable to the AnnotatedTypeMirror that will + * replace it + * @param copyArgument whether or not a copy of type argument should be substituted + */ + public Visitor( + Map typeParamToArg, boolean copyArgument) { + int size = typeParamToArg.size(); + elementToArgMap = new HashMap<>(size); + typeVars = new ArrayList<>(size); + typeMirrors = new ArrayList<>(size); + + for (Map.Entry paramToArg : + typeParamToArg.entrySet()) { + elementToArgMap.put( + (TypeParameterElement) paramToArg.getKey().asElement(), + paramToArg.getValue()); + typeVars.add(paramToArg.getKey()); + typeMirrors.add(paramToArg.getValue().getUnderlyingType()); + } + this.copyArgument = copyArgument; + } - @Override - public AnnotatedTypeMirror visitTypeVariable( - AnnotatedTypeVariable original, - IdentityHashMap originalToCopy) { - if (visitingExecutableTypeParam) { - // AnnotatedExecutableType differs from AnnotatedDeclaredType in that its list of - // type parameters cannot be adapted in place since the - // AnnotatedExecutable.typeVarTypes field is of type AnnotatedTypeVariable and not - // AnnotatedTypeMirror. When substituting, all component types that contain a use - // of the executable's type parameters will be substituted. The executable's type - // parameters will have their bounds substituted but the top-level - // AnnotatedTypeVariable's will remain - visitingExecutableTypeParam = false; - return super.visitTypeVariable(original, originalToCopy); - } else { - Element typeVarElem = original.getUnderlyingType().asElement(); - if (elementToArgMap.containsKey(typeVarElem)) { - AnnotatedTypeMirror argument = elementToArgMap.get(typeVarElem); - if (copyArgument) { - return substituteTypeVariable(argument, original); - } else { - return argument; - } + @Override + protected T makeCopy(T original) { + if (original.getKind() == TypeKind.TYPEVAR) { + return super.makeCopy(original); + } + TypeMirror s = + TypesUtils.substitute( + original.getUnderlyingType(), + typeVars, + typeMirrors, + original.atypeFactory.processingEnv); + + @SuppressWarnings("unchecked") + T copy = + (T) + AnnotatedTypeMirror.createType( + s, original.atypeFactory, original.isDeclaration()); + maybeCopyPrimaryAnnotations(original, copy); + + return copy; } - } - return super.visitTypeVariable(original, originalToCopy); + @Override + public AnnotatedTypeMirror visitTypeVariable( + AnnotatedTypeVariable original, + IdentityHashMap originalToCopy) { + if (visitingExecutableTypeParam) { + // AnnotatedExecutableType differs from AnnotatedDeclaredType in that its list of + // type parameters cannot be adapted in place since the + // AnnotatedExecutable.typeVarTypes field is of type AnnotatedTypeVariable and not + // AnnotatedTypeMirror. When substituting, all component types that contain a use + // of the executable's type parameters will be substituted. The executable's type + // parameters will have their bounds substituted but the top-level + // AnnotatedTypeVariable's will remain + visitingExecutableTypeParam = false; + return super.visitTypeVariable(original, originalToCopy); + } else { + Element typeVarElem = original.getUnderlyingType().asElement(); + if (elementToArgMap.containsKey(typeVarElem)) { + AnnotatedTypeMirror argument = elementToArgMap.get(typeVarElem); + if (copyArgument) { + return substituteTypeVariable(argument, original); + } else { + return argument; + } + } + } + + return super.visitTypeVariable(original, originalToCopy); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java b/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java index fd15c5cf9f5..ad96366af5e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypesIntoElements.java @@ -17,10 +17,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Types; + import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -34,6 +31,11 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypeAnnotationUtils; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; + /** * A helper class that puts the annotations from an AnnotatedTypeMirrors back into the corresponding * Elements, so that they get stored in the bytecode by the compiler. @@ -44,459 +46,481 @@ */ public final class TypesIntoElements { - /** Do not instantiate. */ - private TypesIntoElements() { - throw new AssertionError("Class TypesIntoElements cannot be instantiated."); - } - - /** - * The entry point. - * - * @param processingEnv the environment - * @param atypeFactory the type factory - * @param tree the ClassTree to process - */ - public static void store( - ProcessingEnvironment processingEnv, AnnotatedTypeFactory atypeFactory, ClassTree tree) { - Symbol.ClassSymbol csym = (Symbol.ClassSymbol) TreeUtils.elementFromDeclaration(tree); - Types types = processingEnv.getTypeUtils(); - - storeTypeParameters(processingEnv, types, atypeFactory, tree.getTypeParameters(), csym); - - /* TODO: storing extends/implements types results in - * a strange error e.g. from the Nullness Checker. - * I think somewhere we take the annotations on extends/implements as - * the receiver annotation on a constructor, breaking logic there. - * I assume that the problem is the default that we use for these locations. - * Once we've decided the defaulting, enable this. - * See example of code that fails when this is enabled in - * checker/jtreg/nullness/annotationsOnExtends. Also, see - * https://github.com/typetools/checker-framework/pull/876 for - * a better implementation (though it also causes the error). - storeClassExtends(processingEnv, types, atypeFactory, tree.getExtendsClause(), csym, -1); - { - int implidx = 0; - for (Tree imp : tree.getImplementsClause()) { - storeClassExtends(processingEnv, types, atypeFactory, imp, csym, implidx); - ++implidx; - } - } - */ - - for (Tree mem : tree.getMembers()) { - if (mem.getKind() == Tree.Kind.METHOD) { - storeMethod(processingEnv, types, atypeFactory, (MethodTree) mem); - } else if (mem.getKind() == Tree.Kind.VARIABLE) { - storeVariable(processingEnv, types, atypeFactory, (VariableTree) mem); - } else { - // System.out.println("Unhandled member tree: " + mem); - } - } - } - - private static void storeMethod( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - MethodTree meth) { - AnnotatedExecutableType mtype = atypeFactory.getAnnotatedType(meth); - MethodSymbol sym = (MethodSymbol) TreeUtils.elementFromDeclaration(meth); - TypeAnnotationPosition tapos; - List tcs = List.nil(); - - storeTypeParameters(processingEnv, types, atypeFactory, meth.getTypeParameters(), sym); - - { - // return type - JCTree ret = ((JCTree.JCMethodDecl) meth).getReturnType(); - if (ret != null) { - tapos = TypeAnnotationUtils.methodReturnTAPosition(ret.pos); - tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReturnType(), tapos)); - } - } - { - // receiver - JCTree receiverTree = ((JCTree.JCMethodDecl) meth).getReceiverParameter(); - if (receiverTree != null) { - tapos = TypeAnnotationUtils.methodReceiverTAPosition(receiverTree.pos); - tcs = tcs.appendList(generateTypeCompounds(processingEnv, mtype.getReceiverType(), tapos)); - } - } - { - // parameters - int pidx = 0; - java.util.List ptypes = mtype.getParameterTypes(); - for (JCTree param : ((JCTree.JCMethodDecl) meth).getParameters()) { - tapos = TypeAnnotationUtils.methodParameterTAPosition(pidx, param.pos); - tcs = tcs.appendList(generateTypeCompounds(processingEnv, ptypes.get(pidx), tapos)); - ++pidx; - } - } - { - // throws clauses - int tidx = 0; - java.util.List ttypes = mtype.getThrownTypes(); - for (JCTree thr : ((JCTree.JCMethodDecl) meth).getThrows()) { - tapos = TypeAnnotationUtils.methodThrowsTAPosition(tidx, thr.pos); - tcs = tcs.appendList(generateTypeCompounds(processingEnv, ttypes.get(tidx), tapos)); - ++tidx; - } + /** Do not instantiate. */ + private TypesIntoElements() { + throw new AssertionError("Class TypesIntoElements cannot be instantiated."); } - addUniqueTypeCompounds(types, sym, tcs); - } - - private static void storeVariable( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - VariableTree var) { - VarSymbol sym = (VarSymbol) TreeUtils.elementFromDeclaration(var); - AnnotatedTypeMirror type; - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - // TODO: this is rather ugly: we do not want refinement from the - // initializer of the field. We need a general way to get - // the "defaulted" type of a variable. - type = ((GenericAnnotatedTypeFactory) atypeFactory).getAnnotatedTypeLhs(var); - } else { - type = atypeFactory.getAnnotatedType(var); - } - - TypeAnnotationPosition tapos = TypeAnnotationUtils.fieldTAPosition(((JCTree) var).pos); - - List tcs; - tcs = generateTypeCompounds(processingEnv, type, tapos); - addUniqueTypeCompounds(types, sym, tcs); - } - - @SuppressWarnings("unused") // TODO: see usage in comments above - private static void storeClassExtends( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - Tree ext, - Symbol.ClassSymbol csym, - int implidx) { - - AnnotatedTypeMirror type; - int pos; - if (ext == null) { - // The implicit superclass is always java.lang.Object. - // TODO: is this a good way to get the type? - type = atypeFactory.fromElement(csym.getSuperclass().asElement()); - pos = -1; - } else { - type = atypeFactory.getAnnotatedTypeFromTypeTree(ext); - pos = ((JCTree) ext).pos; + /** + * The entry point. + * + * @param processingEnv the environment + * @param atypeFactory the type factory + * @param tree the ClassTree to process + */ + public static void store( + ProcessingEnvironment processingEnv, + AnnotatedTypeFactory atypeFactory, + ClassTree tree) { + Symbol.ClassSymbol csym = (Symbol.ClassSymbol) TreeUtils.elementFromDeclaration(tree); + Types types = processingEnv.getTypeUtils(); + + storeTypeParameters(processingEnv, types, atypeFactory, tree.getTypeParameters(), csym); + + /* TODO: storing extends/implements types results in + * a strange error e.g. from the Nullness Checker. + * I think somewhere we take the annotations on extends/implements as + * the receiver annotation on a constructor, breaking logic there. + * I assume that the problem is the default that we use for these locations. + * Once we've decided the defaulting, enable this. + * See example of code that fails when this is enabled in + * checker/jtreg/nullness/annotationsOnExtends. Also, see + * https://github.com/typetools/checker-framework/pull/876 for + * a better implementation (though it also causes the error). + storeClassExtends(processingEnv, types, atypeFactory, tree.getExtendsClause(), csym, -1); + { + int implidx = 0; + for (Tree imp : tree.getImplementsClause()) { + storeClassExtends(processingEnv, types, atypeFactory, imp, csym, implidx); + ++implidx; + } + } + */ + + for (Tree mem : tree.getMembers()) { + if (mem.getKind() == Tree.Kind.METHOD) { + storeMethod(processingEnv, types, atypeFactory, (MethodTree) mem); + } else if (mem.getKind() == Tree.Kind.VARIABLE) { + storeVariable(processingEnv, types, atypeFactory, (VariableTree) mem); + } else { + // System.out.println("Unhandled member tree: " + mem); + } + } } - TypeAnnotationPosition tapos = TypeAnnotationUtils.classExtendsTAPosition(implidx, pos); - - List tcs; - tcs = generateTypeCompounds(processingEnv, type, tapos); - addUniqueTypeCompounds(types, csym, tcs); - } - - private static void storeTypeParameters( - ProcessingEnvironment processingEnv, - Types types, - AnnotatedTypeFactory atypeFactory, - java.util.List tps, - Symbol sym) { - boolean isClassOrInterface = sym.getKind().isClass() || sym.getKind().isInterface(); - List tcs = List.nil(); - - int tpidx = 0; - for (TypeParameterTree tp : tps) { - AnnotatedTypeVariable typeVar = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tp); - // System.out.println("The Type for type parameter " + tp + " is " + type); - - TypeAnnotationPosition tapos; - // Note: we use the type parameter pos also for the bounds; - // the bounds may not be explicit and we couldn't look up separate pos. - if (isClassOrInterface) { - tapos = TypeAnnotationUtils.typeParameterTAPosition(tpidx, ((JCTree) tp).pos); - } else { - tapos = TypeAnnotationUtils.methodTypeParameterTAPosition(tpidx, ((JCTree) tp).pos); - } - - { // This block is essentially direct annotations, perhaps we should refactor that - // method out - List res = List.nil(); - for (AnnotationMirror am : typeVar.getLowerBound().getAnnotations()) { - Attribute.TypeCompound tc = - TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv); - res = res.prepend(tc); + private static void storeMethod( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + MethodTree meth) { + AnnotatedExecutableType mtype = atypeFactory.getAnnotatedType(meth); + MethodSymbol sym = (MethodSymbol) TreeUtils.elementFromDeclaration(meth); + TypeAnnotationPosition tapos; + List tcs = List.nil(); + + storeTypeParameters(processingEnv, types, atypeFactory, meth.getTypeParameters(), sym); + + { + // return type + JCTree ret = ((JCTree.JCMethodDecl) meth).getReturnType(); + if (ret != null) { + tapos = TypeAnnotationUtils.methodReturnTAPosition(ret.pos); + tcs = + tcs.appendList( + generateTypeCompounds(processingEnv, mtype.getReturnType(), tapos)); + } } - tcs = tcs.appendList(res); - } - - AnnotatedTypeMirror tpbound = typeVar.getUpperBound(); - java.util.List bounds; - if (tpbound.getKind() == TypeKind.INTERSECTION) { - bounds = ((AnnotatedIntersectionType) tpbound).getBounds(); - } else { - bounds = List.of(tpbound); - } - - int bndidx = 0; - for (AnnotatedTypeMirror bound : bounds) { - if (bndidx == 0 && ((Type) bound.getUnderlyingType()).isInterface()) { - // If the first bound is an interface, there is an implicit java.lang.Object - ++bndidx; + { + // receiver + JCTree receiverTree = ((JCTree.JCMethodDecl) meth).getReceiverParameter(); + if (receiverTree != null) { + tapos = TypeAnnotationUtils.methodReceiverTAPosition(receiverTree.pos); + tcs = + tcs.appendList( + generateTypeCompounds( + processingEnv, mtype.getReceiverType(), tapos)); + } } + { + // parameters + int pidx = 0; + java.util.List ptypes = mtype.getParameterTypes(); + for (JCTree param : ((JCTree.JCMethodDecl) meth).getParameters()) { + tapos = TypeAnnotationUtils.methodParameterTAPosition(pidx, param.pos); + tcs = tcs.appendList(generateTypeCompounds(processingEnv, ptypes.get(pidx), tapos)); + ++pidx; + } + } + { + // throws clauses + int tidx = 0; + java.util.List ttypes = mtype.getThrownTypes(); + for (JCTree thr : ((JCTree.JCMethodDecl) meth).getThrows()) { + tapos = TypeAnnotationUtils.methodThrowsTAPosition(tidx, thr.pos); + tcs = tcs.appendList(generateTypeCompounds(processingEnv, ttypes.get(tidx), tapos)); + ++tidx; + } + } + + addUniqueTypeCompounds(types, sym, tcs); + } - if (isClassOrInterface) { - tapos = - TypeAnnotationUtils.typeParameterBoundTAPosition(tpidx, bndidx, ((JCTree) tp).pos); + private static void storeVariable( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + VariableTree var) { + VarSymbol sym = (VarSymbol) TreeUtils.elementFromDeclaration(var); + AnnotatedTypeMirror type; + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + // TODO: this is rather ugly: we do not want refinement from the + // initializer of the field. We need a general way to get + // the "defaulted" type of a variable. + type = + ((GenericAnnotatedTypeFactory) atypeFactory) + .getAnnotatedTypeLhs(var); } else { - tapos = - TypeAnnotationUtils.methodTypeParameterBoundTAPosition( - tpidx, bndidx, ((JCTree) tp).pos); + type = atypeFactory.getAnnotatedType(var); } - tcs = tcs.appendList(generateTypeCompounds(processingEnv, bound, tapos)); - ++bndidx; - } - ++tpidx; + TypeAnnotationPosition tapos = TypeAnnotationUtils.fieldTAPosition(((JCTree) var).pos); + + List tcs; + tcs = generateTypeCompounds(processingEnv, type, tapos); + addUniqueTypeCompounds(types, sym, tcs); } - // System.out.println("Adding " + tcs + " to " + sym); - addUniqueTypeCompounds(types, sym, tcs); - } + @SuppressWarnings("unused") // TODO: see usage in comments above + private static void storeClassExtends( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + Tree ext, + Symbol.ClassSymbol csym, + int implidx) { + + AnnotatedTypeMirror type; + int pos; + if (ext == null) { + // The implicit superclass is always java.lang.Object. + // TODO: is this a good way to get the type? + type = atypeFactory.fromElement(csym.getSuperclass().asElement()); + pos = -1; + } else { + type = atypeFactory.getAnnotatedTypeFromTypeTree(ext); + pos = ((JCTree) ext).pos; + } - private static void addUniqueTypeCompounds(Types types, Symbol sym, List tcs) { - List raw = sym.getRawTypeAttributes(); - List res = List.nil(); + TypeAnnotationPosition tapos = TypeAnnotationUtils.classExtendsTAPosition(implidx, pos); - for (Attribute.TypeCompound tc : tcs) { - if (!TypeAnnotationUtils.isTypeCompoundContained(raw, tc, types)) { - res = res.append(tc); - } + List tcs; + tcs = generateTypeCompounds(processingEnv, type, tapos); + addUniqueTypeCompounds(types, csym, tcs); } - // That method only uses reference equality. isTypeCompoundContained does a deep comparison. - sym.appendUniqueTypeAttributes(res); - } - - // Do not return null. Return List.nil() if there are no TypeCompounds to return. - private static List generateTypeCompounds( - ProcessingEnvironment processingEnv, AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { - return new TCConvert(processingEnv).scan(type, tapos); - } - - /** - * Convert an AnnotatedTypeMirror and a TypeAnnotationPosition into the corresponding - * TypeCompounds. - */ - private static class TCConvert - extends AnnotatedTypeScanner, TypeAnnotationPosition> { - - /** The processing environment. */ - private final ProcessingEnvironment processingEnv; - /** - * Creates a {@link TCConvert}. - * - * @param processingEnv the processing environment - */ - TCConvert(ProcessingEnvironment processingEnv) { - super(List.nil()); - this.processingEnv = processingEnv; - } + private static void storeTypeParameters( + ProcessingEnvironment processingEnv, + Types types, + AnnotatedTypeFactory atypeFactory, + java.util.List tps, + Symbol sym) { + boolean isClassOrInterface = sym.getKind().isClass() || sym.getKind().isInterface(); + List tcs = List.nil(); + + int tpidx = 0; + for (TypeParameterTree tp : tps) { + AnnotatedTypeVariable typeVar = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(tp); + // System.out.println("The Type for type parameter " + tp + " is " + type); + + TypeAnnotationPosition tapos; + // Note: we use the type parameter pos also for the bounds; + // the bounds may not be explicit and we couldn't look up separate pos. + if (isClassOrInterface) { + tapos = TypeAnnotationUtils.typeParameterTAPosition(tpidx, ((JCTree) tp).pos); + } else { + tapos = TypeAnnotationUtils.methodTypeParameterTAPosition(tpidx, ((JCTree) tp).pos); + } + + { // This block is essentially direct annotations, perhaps we should refactor that + // method out + List res = List.nil(); + for (AnnotationMirror am : typeVar.getLowerBound().getAnnotations()) { + Attribute.TypeCompound tc = + TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( + am, tapos, processingEnv); + res = res.prepend(tc); + } + tcs = tcs.appendList(res); + } + + AnnotatedTypeMirror tpbound = typeVar.getUpperBound(); + java.util.List bounds; + if (tpbound.getKind() == TypeKind.INTERSECTION) { + bounds = ((AnnotatedIntersectionType) tpbound).getBounds(); + } else { + bounds = List.of(tpbound); + } + + int bndidx = 0; + for (AnnotatedTypeMirror bound : bounds) { + if (bndidx == 0 && ((Type) bound.getUnderlyingType()).isInterface()) { + // If the first bound is an interface, there is an implicit java.lang.Object + ++bndidx; + } + + if (isClassOrInterface) { + tapos = + TypeAnnotationUtils.typeParameterBoundTAPosition( + tpidx, bndidx, ((JCTree) tp).pos); + } else { + tapos = + TypeAnnotationUtils.methodTypeParameterBoundTAPosition( + tpidx, bndidx, ((JCTree) tp).pos); + } + + tcs = tcs.appendList(generateTypeCompounds(processingEnv, bound, tapos)); + ++bndidx; + } + ++tpidx; + } - @Override - public List scan(AnnotatedTypeMirror type, TypeAnnotationPosition pos) { - if (pos == null) { - throw new BugInCF("TypesIntoElements: invalid usage, null pos with type: " + type); - } - List res = super.scan(type, pos); - return res; + // System.out.println("Adding " + tcs + " to " + sym); + addUniqueTypeCompounds(types, sym, tcs); } - @Override - public List reduce(List r1, List r2) { - if (r1 == null) { - return r2; - } - if (r2 == null) { - return r1; - } - return r1.appendList(r2); - } + private static void addUniqueTypeCompounds(Types types, Symbol sym, List tcs) { + List raw = sym.getRawTypeAttributes(); + List res = List.nil(); - private List directAnnotations( - AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { - List res = List.nil(); - - for (AnnotationMirror am : type.getAnnotations()) { - // TODO: I BELIEVE THIS ISN'T TRUE BECAUSE PARAMETERS MAY HAVE ANNOTATIONS THAT CAME - // FROM THE ELEMENT OF THE CLASS WHICH PREVIOUSLY WAS WRITTEN OUT BY - // TYPESINTOELEMENT. - // if (am instanceof Attribute.TypeCompound) { - // // If it is a TypeCompound it was already present in source - // (right?), - // // so there is nothing to do. - // // System.out.println(" found TypeComound: " + am + " pos: " - // + ((Attribute.TypeCompound)am).position); - // } else { - // TODO: DOES THIS LEAD TO DOUBLING UP ON THE SAME ANNOTATION IN THE ELEMENT? - Attribute.TypeCompound tc = - TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror(am, tapos, processingEnv); - res = res.prepend(tc); - // } - } - return res; + for (Attribute.TypeCompound tc : tcs) { + if (!TypeAnnotationUtils.isTypeCompoundContained(raw, tc, types)) { + res = res.append(tc); + } + } + // That method only uses reference equality. isTypeCompoundContained does a deep comparison. + sym.appendUniqueTypeAttributes(res); } - @Override - public List visitDeclared( - AnnotatedDeclaredType type, TypeAnnotationPosition tapos) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - // Hack for termination - visitedNodes.put(type, List.nil()); - List res; - - TypeAnnotationPosition oldpos = TypeAnnotationUtils.copyTAPosition(tapos); - locateNestedTypes(type, tapos); - - res = directAnnotations(type, tapos); - - // We sometimes fix-up raw types with wildcards. Do not write these into the bytecode - // as there are no corresponding type arguments and therefore no location to actually - // add them to. - if (!type.isUnderlyingTypeRaw()) { - int arg = 0; - for (AnnotatedTypeMirror ta : type.getTypeArguments()) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = - tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); - res = scanAndReduce(ta, newpos, res); - ++arg; - } - } - - AnnotatedTypeMirror encl = type.getEnclosingType(); - if (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) { - // use original tapos - res = scanAndReduce(encl, oldpos, res); - } - visitedNodes.put(type, res); - return res; + // Do not return null. Return List.nil() if there are no TypeCompounds to return. + private static List generateTypeCompounds( + ProcessingEnvironment processingEnv, + AnnotatedTypeMirror type, + TypeAnnotationPosition tapos) { + return new TCConvert(processingEnv).scan(type, tapos); } - /* Modeled after - * {@link com.sun.tools.javac.code.TypeAnnotations.TypeAnnotationPositions#locateNestedTypes(Type, TypeAnnotationPosition)} + /** + * Convert an AnnotatedTypeMirror and a TypeAnnotationPosition into the corresponding + * TypeCompounds. */ - private void locateNestedTypes(AnnotatedDeclaredType type, TypeAnnotationPosition p) { - // The number of "steps" to get from the full type to the - // left-most outer type. - ListBuffer depth = new ListBuffer<>(); - - Type encl = (Type) type.getUnderlyingType().getEnclosingType(); - while (encl != null && encl.getKind() != TypeKind.NONE && encl.getKind() != TypeKind.ERROR) { - depth = depth.append(TypePathEntry.INNER_TYPE); - encl = encl.getEnclosingType(); - } - - if (depth.nonEmpty()) { - p.location = p.location.appendList(depth.toList()); - } - } + private static class TCConvert + extends AnnotatedTypeScanner, TypeAnnotationPosition> { + + /** The processing environment. */ + private final ProcessingEnvironment processingEnv; + + /** + * Creates a {@link TCConvert}. + * + * @param processingEnv the processing environment + */ + TCConvert(ProcessingEnvironment processingEnv) { + super(List.nil()); + this.processingEnv = processingEnv; + } - @Override - public List visitIntersection( - AnnotatedIntersectionType type, TypeAnnotationPosition tapos) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, List.nil()); - List res; - res = directAnnotations(type, tapos); - - int arg = 0; - for (AnnotatedTypeMirror bound : type.getBounds()) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = - tapos.location.append(new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); - res = scanAndReduce(bound, newpos, res); - ++arg; - } - visitedNodes.put(type, res); - return res; - } + @Override + public List scan(AnnotatedTypeMirror type, TypeAnnotationPosition pos) { + if (pos == null) { + throw new BugInCF("TypesIntoElements: invalid usage, null pos with type: " + type); + } + List res = super.scan(type, pos); + return res; + } - @Override - public List visitUnion(AnnotatedUnionType type, TypeAnnotationPosition tapos) { - // We should never need to write a union type, so raise an error. - throw new BugInCF( - "TypesIntoElement: encountered union type: " + type + " at position: " + tapos); - } + @Override + public List reduce(List r1, List r2) { + if (r1 == null) { + return r2; + } + if (r2 == null) { + return r1; + } + return r1.appendList(r2); + } - @Override - public List visitArray(AnnotatedArrayType type, TypeAnnotationPosition tapos) { - List res; - res = directAnnotations(type, tapos); + private List directAnnotations( + AnnotatedTypeMirror type, TypeAnnotationPosition tapos) { + List res = List.nil(); + + for (AnnotationMirror am : type.getAnnotations()) { + // TODO: I BELIEVE THIS ISN'T TRUE BECAUSE PARAMETERS MAY HAVE ANNOTATIONS THAT CAME + // FROM THE ELEMENT OF THE CLASS WHICH PREVIOUSLY WAS WRITTEN OUT BY + // TYPESINTOELEMENT. + // if (am instanceof Attribute.TypeCompound) { + // // If it is a TypeCompound it was already present in source + // (right?), + // // so there is nothing to do. + // // System.out.println(" found TypeComound: " + am + " pos: " + // + ((Attribute.TypeCompound)am).position); + // } else { + // TODO: DOES THIS LEAD TO DOUBLING UP ON THE SAME ANNOTATION IN THE ELEMENT? + Attribute.TypeCompound tc = + TypeAnnotationUtils.createTypeCompoundFromAnnotationMirror( + am, tapos, processingEnv); + res = res.prepend(tc); + // } + } + return res; + } - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = tapos.location.append(TypePathEntry.ARRAY); + @Override + public List visitDeclared( + AnnotatedDeclaredType type, TypeAnnotationPosition tapos) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + // Hack for termination + visitedNodes.put(type, List.nil()); + List res; + + TypeAnnotationPosition oldpos = TypeAnnotationUtils.copyTAPosition(tapos); + locateNestedTypes(type, tapos); + + res = directAnnotations(type, tapos); + + // We sometimes fix-up raw types with wildcards. Do not write these into the bytecode + // as there are no corresponding type arguments and therefore no location to actually + // add them to. + if (!type.isUnderlyingTypeRaw()) { + int arg = 0; + for (AnnotatedTypeMirror ta : type.getTypeArguments()) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = + tapos.location.append( + new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); + res = scanAndReduce(ta, newpos, res); + ++arg; + } + } + + AnnotatedTypeMirror encl = type.getEnclosingType(); + if (encl != null + && encl.getKind() != TypeKind.NONE + && encl.getKind() != TypeKind.ERROR) { + // use original tapos + res = scanAndReduce(encl, oldpos, res); + } + visitedNodes.put(type, res); + return res; + } - return reduce(super.visitArray(type, newpos), res); - } + /* Modeled after + * {@link com.sun.tools.javac.code.TypeAnnotations.TypeAnnotationPositions#locateNestedTypes(Type, TypeAnnotationPosition)} + */ + private void locateNestedTypes(AnnotatedDeclaredType type, TypeAnnotationPosition p) { + // The number of "steps" to get from the full type to the + // left-most outer type. + ListBuffer depth = new ListBuffer<>(); + + Type encl = (Type) type.getUnderlyingType().getEnclosingType(); + while (encl != null + && encl.getKind() != TypeKind.NONE + && encl.getKind() != TypeKind.ERROR) { + depth = depth.append(TypePathEntry.INNER_TYPE); + encl = encl.getEnclosingType(); + } + + if (depth.nonEmpty()) { + p.location = p.location.appendList(depth.toList()); + } + } - @Override - public List visitPrimitive( - AnnotatedPrimitiveType type, TypeAnnotationPosition tapos) { - List res; - res = directAnnotations(type, tapos); - return res; - } + @Override + public List visitIntersection( + AnnotatedIntersectionType type, TypeAnnotationPosition tapos) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, List.nil()); + List res; + res = directAnnotations(type, tapos); + + int arg = 0; + for (AnnotatedTypeMirror bound : type.getBounds()) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = + tapos.location.append( + new TypePathEntry(TypePathEntryKind.TYPE_ARGUMENT, arg)); + res = scanAndReduce(bound, newpos, res); + ++arg; + } + visitedNodes.put(type, res); + return res; + } - @Override - public List visitTypeVariable( - AnnotatedTypeVariable type, TypeAnnotationPosition tapos) { - List res; - res = directAnnotations(type, tapos); - // Do not call super. The bound will be visited separately. - return res; - } + @Override + public List visitUnion( + AnnotatedUnionType type, TypeAnnotationPosition tapos) { + // We should never need to write a union type, so raise an error. + throw new BugInCF( + "TypesIntoElement: encountered union type: " + type + " at position: " + tapos); + } + + @Override + public List visitArray( + AnnotatedArrayType type, TypeAnnotationPosition tapos) { + List res; + res = directAnnotations(type, tapos); + + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = tapos.location.append(TypePathEntry.ARRAY); + + return reduce(super.visitArray(type, newpos), res); + } + + @Override + public List visitPrimitive( + AnnotatedPrimitiveType type, TypeAnnotationPosition tapos) { + List res; + res = directAnnotations(type, tapos); + return res; + } - @Override - public List visitWildcard( - AnnotatedWildcardType type, TypeAnnotationPosition tapos) { - if (this.visitedNodes.containsKey(type)) { - return List.nil(); - } - // Hack for termination, otherwise we'll visit one type too far (the same recursive - // wildcard twice and generate extra type annos) - visitedNodes.put(type, List.nil()); - List res; - - // Note: By default, an Unbound wildcard will return true for both isExtendsBound and - // isSuperBound - if (((Type.WildcardType) type.getUnderlyingType()).isExtendsBound()) { - res = directAnnotations(type.getSuperBound(), tapos); - - AnnotatedTypeMirror ext = type.getExtendsBound(); - if (ext != null) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = tapos.location.append(TypePathEntry.WILDCARD); - res = scanAndReduce(ext, newpos, res); + @Override + public List visitTypeVariable( + AnnotatedTypeVariable type, TypeAnnotationPosition tapos) { + List res; + res = directAnnotations(type, tapos); + // Do not call super. The bound will be visited separately. + return res; } - } else { - res = directAnnotations(type.getExtendsBound(), tapos); - AnnotatedTypeMirror sup = type.getSuperBoundField(); - if (sup != null) { - TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); - newpos.location = tapos.location.append(TypePathEntry.WILDCARD); - res = scanAndReduce(sup, newpos, res); + @Override + public List visitWildcard( + AnnotatedWildcardType type, TypeAnnotationPosition tapos) { + if (this.visitedNodes.containsKey(type)) { + return List.nil(); + } + // Hack for termination, otherwise we'll visit one type too far (the same recursive + // wildcard twice and generate extra type annos) + visitedNodes.put(type, List.nil()); + List res; + + // Note: By default, an Unbound wildcard will return true for both isExtendsBound and + // isSuperBound + if (((Type.WildcardType) type.getUnderlyingType()).isExtendsBound()) { + res = directAnnotations(type.getSuperBound(), tapos); + + AnnotatedTypeMirror ext = type.getExtendsBound(); + if (ext != null) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = tapos.location.append(TypePathEntry.WILDCARD); + res = scanAndReduce(ext, newpos, res); + } + + } else { + res = directAnnotations(type.getExtendsBound(), tapos); + AnnotatedTypeMirror sup = type.getSuperBoundField(); + if (sup != null) { + TypeAnnotationPosition newpos = TypeAnnotationUtils.copyTAPosition(tapos); + newpos.location = tapos.location.append(TypePathEntry.WILDCARD); + res = scanAndReduce(sup, newpos, res); + } + } + visitedNodes.put(type, res); + return res; } - } - visitedNodes.put(type, res); - return res; } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java index ceddec41c2c..97f1279dff9 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/ViewpointAdapter.java @@ -1,9 +1,11 @@ package org.checkerframework.framework.type; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; + import java.util.List; + import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; /** * A viewpoint adapter. @@ -13,57 +15,60 @@ */ public interface ViewpointAdapter { - /** - * Viewpoint adapts a member/field access. - * - *

          Developer notes: When this method is invoked on a member/field with a type given by a type - * parameter, the type arguments are correctly substituted, and memberType is already in a good - * shape. Only annotations on the memberType should be replaced by the viewpoint adapted ones. - * - * @param receiverType receiver type through which the member/field is accessed. - * @param memberElement element of the accessed member/field. - * @param memberType accessed type of the member/field. After the method returns, it will be - * mutated to the viewpoint adapted result. - */ - void viewpointAdaptMember( - AnnotatedTypeMirror receiverType, Element memberElement, AnnotatedTypeMirror memberType); + /** + * Viewpoint adapts a member/field access. + * + *

          Developer notes: When this method is invoked on a member/field with a type given by a type + * parameter, the type arguments are correctly substituted, and memberType is already in a good + * shape. Only annotations on the memberType should be replaced by the viewpoint adapted ones. + * + * @param receiverType receiver type through which the member/field is accessed. + * @param memberElement element of the accessed member/field. + * @param memberType accessed type of the member/field. After the method returns, it will be + * mutated to the viewpoint adapted result. + */ + void viewpointAdaptMember( + AnnotatedTypeMirror receiverType, + Element memberElement, + AnnotatedTypeMirror memberType); - /** - * Viewpoint adapts a constructor invocation. Takes an unsubstituted method invocation type and - * performs the viewpoint adaption in place, modifying the parameter. - * - * @param receiverType receiver type through which a constructor is invoked. - * @param constructorElt element of the invoked constructor. - * @param constructorType invoked type of the constructor with type variables not substituted. - * After the method returns, it will be mutated to the viewpoint adapted constructor type. - */ - void viewpointAdaptConstructor( - AnnotatedTypeMirror receiverType, - ExecutableElement constructorElt, - AnnotatedExecutableType constructorType); + /** + * Viewpoint adapts a constructor invocation. Takes an unsubstituted method invocation type and + * performs the viewpoint adaption in place, modifying the parameter. + * + * @param receiverType receiver type through which a constructor is invoked. + * @param constructorElt element of the invoked constructor. + * @param constructorType invoked type of the constructor with type variables not substituted. + * After the method returns, it will be mutated to the viewpoint adapted constructor type. + */ + void viewpointAdaptConstructor( + AnnotatedTypeMirror receiverType, + ExecutableElement constructorElt, + AnnotatedExecutableType constructorType); - /** - * Viewpoint adapts a method invocation. Takes an unsubstituted method invocation type and - * performs the viewpoint adaption in place, modifying the parameter. - * - * @param receiverType receiver type through which a method is invoked. - * @param methodElt element of the invoked method. Only used to determine whether this type should - * be viewpoint adapted - * @param methodType invoked type of the method with type variables not substituted. After the - * method returns, it will be mutated to the viewpoint adapted method type. - */ - void viewpointAdaptMethod( - AnnotatedTypeMirror receiverType, - ExecutableElement methodElt, - AnnotatedExecutableType methodType); + /** + * Viewpoint adapts a method invocation. Takes an unsubstituted method invocation type and + * performs the viewpoint adaption in place, modifying the parameter. + * + * @param receiverType receiver type through which a method is invoked. + * @param methodElt element of the invoked method. Only used to determine whether this type + * should be viewpoint adapted + * @param methodType invoked type of the method with type variables not substituted. After the + * method returns, it will be mutated to the viewpoint adapted method type. + */ + void viewpointAdaptMethod( + AnnotatedTypeMirror receiverType, + ExecutableElement methodElt, + AnnotatedExecutableType methodType); - /** - * Viewpoint adapts a type parameter bound when being instantiated. - * - * @param receiverType receiver type through which the type parameter is instantiated. - * @param typeParameterBounds a list of type parameter bounds. After the method returns, it will - * be mutated to the viewpoint adapted type parameter bounds. - */ - void viewpointAdaptTypeParameterBounds( - AnnotatedTypeMirror receiverType, List typeParameterBounds); + /** + * Viewpoint adapts a type parameter bound when being instantiated. + * + * @param receiverType receiver type through which the type parameter is instantiated. + * @param typeParameterBounds a list of type parameter bounds. After the method returns, it will + * be mutated to the viewpoint adapted type parameter bounds. + */ + void viewpointAdaptTypeParameterBounds( + AnnotatedTypeMirror receiverType, + List typeParameterBounds); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java index 93c63e2a85a..1d40c703190 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java @@ -2,17 +2,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -35,6 +25,19 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.CollectionsPlume; +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; + /** * Implements framework support for qualifier polymorphism. * @@ -56,542 +59,552 @@ */ public abstract class AbstractQualifierPolymorphism implements QualifierPolymorphism { - /** Annotated type factory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy to use. */ - protected final QualifierHierarchy qualHierarchy; - - /** - * The polymorphic qualifiers: mapping from a polymorphic qualifier of {@code qualHierarchy} to - * the top qualifier of that hierarchy. - */ - protected final AnnotationMirrorMap polyQuals = new AnnotationMirrorMap<>(); - - /** - * The qualifiers at the top of {@code qualHierarchy}. These are the values in {@code polyQuals}. - */ - protected final AnnotationMirrorSet topQuals; - - /** Determines the instantiations for each polymorphic qualifier. */ - private final PolyCollector collector = new PolyCollector(); - - /** Resolves each polymorphic qualifier by replacing it with its instantiation. */ - private final SimpleAnnotatedTypeScanner> replacer; - - /** - * Completes a type by removing any unresolved polymorphic qualifiers, replacing them with the - * bottom qualifiers. - */ - private final SimpleAnnotatedTypeScanner completer; - - /** Mapping from poly qualifier to its instantiation for types with a qualifier parameter. */ - protected final AnnotationMirrorMap polyInstantiationForQualifierParameter = - new AnnotationMirrorMap<>(); - - /** The visit method returns true if the passed type has any polymorphic qualifiers. */ - protected final SimpleAnnotatedTypeScanner hasPolyScanner; - - /** - * Creates an {@link AbstractQualifierPolymorphism} instance that uses the given checker for - * querying type qualifiers and the given factory for getting annotated types. Subclasses need to - * add polymorphic qualifiers to {@code this.polyQuals}. - * - * @param env the processing environment - * @param factory the factory for the current checker - */ - protected AbstractQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { - this.atypeFactory = factory; - this.qualHierarchy = factory.getQualifierHierarchy(); - this.topQuals = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); - - this.completer = - new SimpleAnnotatedTypeScanner<>( - (type, p) -> { - for (Map.Entry entry : polyQuals.entrySet()) { - AnnotationMirror poly = entry.getKey(); - AnnotationMirror top = entry.getValue(); - if (type.hasAnnotation(poly)) { - type.removeAnnotation(poly); - if (type.getKind() != TypeKind.TYPEVAR && type.getKind() != TypeKind.WILDCARD) { - // Do not add qualifiers to type variables and - // wildcards - type.addAnnotation(this.qualHierarchy.getBottomAnnotation(top)); - } - } - } - return null; - }); - - this.replacer = - new SimpleAnnotatedTypeScanner<>( - (type, map) -> { - replace(type, map); - return null; - }); - - this.hasPolyScanner = - new SimpleAnnotatedTypeScanner<>( - (type, notused) -> { - for (AnnotationMirror a : type.getAnnotations()) { - if (qualHierarchy.isPolymorphicQualifier(a)) { - return true; - } - } - return false; - }, - Boolean::logicalOr, - false); - } - - /** - * Reset to allow reuse of the same instance. Subclasses should override this method. The - * overriding implementation should clear its additional state and then call the super - * implementation. - */ - protected void reset() { - collector.reset(); - replacer.reset(); - completer.reset(); - polyInstantiationForQualifierParameter.clear(); - } - - @Override - public boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type) { - return hasPolyScanner.visit(type); - } - - /** - * Resolves polymorphism annotations for the given type. - * - * @param tree the tree associated with the type - * @param type the type to annotate - */ - @Override - public void resolve(MethodInvocationTree tree, AnnotatedExecutableType type) { - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { - return; - } + /** Annotated type factory. */ + protected final AnnotatedTypeFactory atypeFactory; - // javac produces enum super calls with zero arguments even though the - // method element requires two. - // See also BaseTypeVisitor.visitMethodInvocation and - // CFGBuilder.CFGTranslationPhaseOne.visitMethodInvocation. - if (TreeUtils.isEnumSuperCall(tree)) { - return; - } - List parameters = - AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments(), null); - List arguments = - CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); - - AnnotationMirrorMap instantiationMapping = - collector.visit(arguments, parameters); - - // For super() and this() method calls, getReceiverType(tree) does not return the correct - // type. So, just skip those. This is consistent with skipping receivers of constructors - // below. - if (type.getReceiverType() != null - && !TreeUtils.isSuperConstructorCall(tree) - && !TreeUtils.isThisConstructorCall(tree)) { - instantiationMapping = - collector.reduce( - instantiationMapping, - collector.visit(atypeFactory.getReceiverType(tree), type.getReceiverType())); - } + /** The qualifier hierarchy to use. */ + protected final QualifierHierarchy qualHierarchy; - if ((instantiationMapping != null && !instantiationMapping.isEmpty()) - || TreeUtils.isCallToVarArgsMethodWithZeroVarargsActuals(tree)) { - replacer.visit(type, instantiationMapping); - } else { - completer.visit(type); - } - reset(); - } + /** + * The polymorphic qualifiers: mapping from a polymorphic qualifier of {@code qualHierarchy} to + * the top qualifier of that hierarchy. + */ + protected final AnnotationMirrorMap polyQuals = new AnnotationMirrorMap<>(); - @Override - public void resolve(NewClassTree tree, AnnotatedExecutableType type) { - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { - return; - } - List parameters = - AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments(), tree); - List arguments = - CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); - - AnnotationMirrorMap instantiationMapping = - collector.visit(arguments, parameters); - // TODO: poly on receiver for constructors? - // instantiationMapping = collector.reduce(instantiationMapping, - // collector.visit(factory.getReceiverType(tree), type.getReceiverType())); - - AnnotatedTypeMirror newClassType = type.getReturnType().deepCopy(); - newClassType.clearAnnotations(); - newClassType.replaceAnnotations(atypeFactory.getExplicitNewClassAnnos(tree)); - - instantiationMapping = - collector.reduce( - instantiationMapping, mapQualifierToPoly(newClassType, type.getReturnType())); - - if (instantiationMapping != null && !instantiationMapping.isEmpty()) { - replacer.visit(type, instantiationMapping); - } else { - completer.visit(type); - } - reset(); - } + /** + * The qualifiers at the top of {@code qualHierarchy}. These are the values in {@code + * polyQuals}. + */ + protected final AnnotationMirrorSet topQuals; - @Override - public void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type) { - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { - return; - } - AnnotationMirrorMap matchingMapping = new AnnotationMirrorMap<>(); - polyQuals.forEach( - (polyAnnotation, topAnno) -> { - AnnotationMirror annoOnOwner = owner.getAnnotationInHierarchy(topAnno); - if (annoOnOwner != null) { - matchingMapping.put(polyAnnotation, annoOnOwner); - } - }); - if (!matchingMapping.isEmpty()) { - replacer.visit(type, matchingMapping); - } else { - completer.visit(type); - } - reset(); - } - - @Override - public void resolve( - AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) { - if (hasPolymorphicQualifiers(functionalInterface.getReturnType())) { - // functional interface has a polymorphic qualifier, so they should not be resolved - // on memberReference. - return; - } - if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(memberReference)) { - return; - } - AnnotationMirrorMap instantiationMapping; - - List parameters = memberReference.getParameterTypes(); - List args = functionalInterface.getParameterTypes(); - if (args.size() == parameters.size() + 1) { - // If the member reference is a reference to an instance method of an arbitrary - // object, then first parameter of the functional interface corresponds to the - // receiver of the member reference. - List newParameters = new ArrayList<>(parameters.size() + 1); - newParameters.add(memberReference.getReceiverType()); - newParameters.addAll(parameters); - parameters = newParameters; - instantiationMapping = new AnnotationMirrorMap<>(); - } else { - if (memberReference.getReceiverType() != null - && functionalInterface.getReceiverType() != null) { - instantiationMapping = - mapQualifierToPoly( - functionalInterface.getReceiverType(), memberReference.getReceiverType()); - } else { - instantiationMapping = new AnnotationMirrorMap<>(); - } - } - // Deal with varargs - if (memberReference.isVarArgs() && !functionalInterface.isVarArgs()) { - parameters = AnnotatedTypes.expandVarArgsParametersFromTypes(memberReference, args); - } + /** Determines the instantiations for each polymorphic qualifier. */ + private final PolyCollector collector = new PolyCollector(); - instantiationMapping = - collector.reduce(instantiationMapping, collector.visit(args, parameters)); + /** Resolves each polymorphic qualifier by replacing it with its instantiation. */ + private final SimpleAnnotatedTypeScanner> replacer; - if (instantiationMapping != null && !instantiationMapping.isEmpty()) { - replacer.visit(memberReference, instantiationMapping); - } else { - // TODO: Do we need this (return type?) - completer.visit(memberReference); - } - reset(); - } - - /** - * If the primary annotation of {@code polyType} is a polymorphic qualifier, then it is mapped to - * the primary annotation of {@code type} and the map is returned. Otherwise, an empty map is - * returned. - * - * @param type type with qualifier to us in the map - * @param polyType type that may have polymorphic qualifiers - * @return a mapping from the polymorphic qualifiers in {@code polyType} to the qualifiers in - * {@code type} - */ - private AnnotationMirrorMap mapQualifierToPoly( - AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { - AnnotationMirrorMap result = new AnnotationMirrorMap<>(); - - for (Map.Entry kv : polyQuals.entrySet()) { - AnnotationMirror top = kv.getValue(); - AnnotationMirror poly = kv.getKey(); - if (polyType.hasAnnotation(poly)) { - AnnotationMirror typeQual = type.getAnnotationInHierarchy(top); - if (typeQual != null) { - if (atypeFactory.hasQualifierParameterInHierarchy(type, top)) { - polyInstantiationForQualifierParameter.put(poly, typeQual); - } - result.put(poly, typeQual); - } - } - } - return result; - } - - /** - * Returns annotation that is the combination of the two annotations. The annotations are - * instantiations for {@code polyQual}. - * - *

          The combination is typically their least upper bound. (It could be the GLB in the case that - * all arguments to a polymorphic method must have the same annotation.) - * - * @param polyQual polymorphic qualifier for which {@code a1} and {@code a2} are instantiations - * @param a1 an annotation that is an instantiation of {@code polyQual} - * @param a2 an annotation that is an instantiation of {@code polyQual} - * @return an annotation that is the combination of the two annotations - */ - protected abstract AnnotationMirror combine( - AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2); - - /** - * Replaces the top-level polymorphic annotations in {@code type} with the instantiations in - * {@code replacements}. - * - *

          This method is called on all parts of a type. - * - * @param type the AnnotatedTypeMirror whose poly annotations are replaced; it is side-effected by - * this method - * @param replacements a mapping from polymorphic annotation to instantiation - */ - protected abstract void replace( - AnnotatedTypeMirror type, AnnotationMirrorMap replacements); - - /** - * A helper class that resolves the polymorphic qualifiers with the most restrictive qualifier. It - * returns a mapping from the polymorphic qualifier to the substitution for that qualifier. - */ - private class PolyCollector - extends EquivalentAtmComboScanner, Void> { + /** + * Completes a type by removing any unresolved polymorphic qualifiers, replacing them with the + * bottom qualifiers. + */ + private final SimpleAnnotatedTypeScanner completer; + + /** Mapping from poly qualifier to its instantiation for types with a qualifier parameter. */ + protected final AnnotationMirrorMap polyInstantiationForQualifierParameter = + new AnnotationMirrorMap<>(); + + /** The visit method returns true if the passed type has any polymorphic qualifiers. */ + protected final SimpleAnnotatedTypeScanner hasPolyScanner; /** - * Set of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been visited. - * Used to prevent infinite recursion on recursive types. + * Creates an {@link AbstractQualifierPolymorphism} instance that uses the given checker for + * querying type qualifiers and the given factory for getting annotated types. Subclasses need + * to add polymorphic qualifiers to {@code this.polyQuals}. * - *

          Uses reference equality rather than equals because the visitor may visit two types that - * are structurally equal, but not actually the same. For example, the wildcards in {@code - * IPair} may be equal, but they both should be visited. + * @param env the processing environment + * @param factory the factory for the current checker */ - private final Set visitedTypes = - Collections.newSetFromMap(new IdentityHashMap()); + protected AbstractQualifierPolymorphism( + ProcessingEnvironment env, AnnotatedTypeFactory factory) { + this.atypeFactory = factory; + this.qualHierarchy = factory.getQualifierHierarchy(); + this.topQuals = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); + + this.completer = + new SimpleAnnotatedTypeScanner<>( + (type, p) -> { + for (Map.Entry entry : + polyQuals.entrySet()) { + AnnotationMirror poly = entry.getKey(); + AnnotationMirror top = entry.getValue(); + if (type.hasAnnotation(poly)) { + type.removeAnnotation(poly); + if (type.getKind() != TypeKind.TYPEVAR + && type.getKind() != TypeKind.WILDCARD) { + // Do not add qualifiers to type variables and + // wildcards + type.addAnnotation( + this.qualHierarchy.getBottomAnnotation(top)); + } + } + } + return null; + }); + + this.replacer = + new SimpleAnnotatedTypeScanner<>( + (type, map) -> { + replace(type, map); + return null; + }); + + this.hasPolyScanner = + new SimpleAnnotatedTypeScanner<>( + (type, notused) -> { + for (AnnotationMirror a : type.getAnnotations()) { + if (qualHierarchy.isPolymorphicQualifier(a)) { + return true; + } + } + return false; + }, + Boolean::logicalOr, + false); + } /** - * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is - * added to the list of visited AnnotatedTypeMirrors. + * Reset to allow reuse of the same instance. Subclasses should override this method. The + * overriding implementation should clear its additional state and then call the super + * implementation. */ - private boolean visited(AnnotatedTypeMirror atm) { - return !visitedTypes.add(atm); + protected void reset() { + collector.reset(); + replacer.reset(); + completer.reset(); + polyInstantiationForQualifierParameter.clear(); } @Override - protected AnnotationMirrorMap scanWithNull( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { - return new AnnotationMirrorMap<>(); + public boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type) { + return hasPolyScanner.visit(type); } + /** + * Resolves polymorphism annotations for the given type. + * + * @param tree the tree associated with the type + * @param type the type to annotate + */ @Override - public AnnotationMirrorMap reduce( - AnnotationMirrorMap r1, AnnotationMirrorMap r2) { - - if (r1 == null || r1.isEmpty()) { - return r2; - } - if (r2 == null || r2.isEmpty()) { - return r1; - } - - AnnotationMirrorMap res = new AnnotationMirrorMap<>(); - // Ensure that all qualifiers from r1 and r2 are visited. - AnnotationMirrorSet r2remain = new AnnotationMirrorSet(); - r2remain.addAll(r2.keySet()); - for (Map.Entry entry : r1.entrySet()) { - AnnotationMirror polyQual = entry.getKey(); - AnnotationMirror a1Annos = entry.getValue(); - AnnotationMirror a2Annos = r2.get(polyQual); - if (a2Annos == null) { - res.put(polyQual, a1Annos); + public void resolve(MethodInvocationTree tree, AnnotatedExecutableType type) { + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { + return; + } + + // javac produces enum super calls with zero arguments even though the + // method element requires two. + // See also BaseTypeVisitor.visitMethodInvocation and + // CFGBuilder.CFGTranslationPhaseOne.visitMethodInvocation. + if (TreeUtils.isEnumSuperCall(tree)) { + return; + } + List parameters = + AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments(), null); + List arguments = + CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); + + AnnotationMirrorMap instantiationMapping = + collector.visit(arguments, parameters); + + // For super() and this() method calls, getReceiverType(tree) does not return the correct + // type. So, just skip those. This is consistent with skipping receivers of constructors + // below. + if (type.getReceiverType() != null + && !TreeUtils.isSuperConstructorCall(tree) + && !TreeUtils.isThisConstructorCall(tree)) { + instantiationMapping = + collector.reduce( + instantiationMapping, + collector.visit( + atypeFactory.getReceiverType(tree), type.getReceiverType())); + } + + if ((instantiationMapping != null && !instantiationMapping.isEmpty()) + || TreeUtils.isCallToVarArgsMethodWithZeroVarargsActuals(tree)) { + replacer.visit(type, instantiationMapping); } else { - res.put(polyQual, combine(polyQual, a1Annos, a2Annos)); + completer.visit(type); } - r2remain.remove(polyQual); - } - for (AnnotationMirror key2 : r2remain) { - res.put(key2, r2.get(key2)); - } - return res; + reset(); + } + + @Override + public void resolve(NewClassTree tree, AnnotatedExecutableType type) { + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { + return; + } + List parameters = + AnnotatedTypes.adaptParameters(atypeFactory, type, tree.getArguments(), tree); + List arguments = + CollectionsPlume.mapList(atypeFactory::getAnnotatedType, tree.getArguments()); + + AnnotationMirrorMap instantiationMapping = + collector.visit(arguments, parameters); + // TODO: poly on receiver for constructors? + // instantiationMapping = collector.reduce(instantiationMapping, + // collector.visit(factory.getReceiverType(tree), type.getReceiverType())); + + AnnotatedTypeMirror newClassType = type.getReturnType().deepCopy(); + newClassType.clearAnnotations(); + newClassType.replaceAnnotations(atypeFactory.getExplicitNewClassAnnos(tree)); + + instantiationMapping = + collector.reduce( + instantiationMapping, + mapQualifierToPoly(newClassType, type.getReturnType())); + + if (instantiationMapping != null && !instantiationMapping.isEmpty()) { + replacer.visit(type, instantiationMapping); + } else { + completer.visit(type); + } + reset(); + } + + @Override + public void resolve( + VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type) { + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(type)) { + return; + } + AnnotationMirrorMap matchingMapping = new AnnotationMirrorMap<>(); + polyQuals.forEach( + (polyAnnotation, topAnno) -> { + AnnotationMirror annoOnOwner = owner.getAnnotationInHierarchy(topAnno); + if (annoOnOwner != null) { + matchingMapping.put(polyAnnotation, annoOnOwner); + } + }); + if (!matchingMapping.isEmpty()) { + replacer.visit(type, matchingMapping); + } else { + completer.visit(type); + } + reset(); + } + + @Override + public void resolve( + AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) { + if (hasPolymorphicQualifiers(functionalInterface.getReturnType())) { + // functional interface has a polymorphic qualifier, so they should not be resolved + // on memberReference. + return; + } + if (polyQuals.isEmpty() || !hasPolymorphicQualifiers(memberReference)) { + return; + } + AnnotationMirrorMap instantiationMapping; + + List parameters = memberReference.getParameterTypes(); + List args = functionalInterface.getParameterTypes(); + if (args.size() == parameters.size() + 1) { + // If the member reference is a reference to an instance method of an arbitrary + // object, then first parameter of the functional interface corresponds to the + // receiver of the member reference. + List newParameters = new ArrayList<>(parameters.size() + 1); + newParameters.add(memberReference.getReceiverType()); + newParameters.addAll(parameters); + parameters = newParameters; + instantiationMapping = new AnnotationMirrorMap<>(); + } else { + if (memberReference.getReceiverType() != null + && functionalInterface.getReceiverType() != null) { + instantiationMapping = + mapQualifierToPoly( + functionalInterface.getReceiverType(), + memberReference.getReceiverType()); + } else { + instantiationMapping = new AnnotationMirrorMap<>(); + } + } + // Deal with varargs + if (memberReference.isVarArgs() && !functionalInterface.isVarArgs()) { + parameters = AnnotatedTypes.expandVarArgsParametersFromTypes(memberReference, args); + } + + instantiationMapping = + collector.reduce(instantiationMapping, collector.visit(args, parameters)); + + if (instantiationMapping != null && !instantiationMapping.isEmpty()) { + replacer.visit(memberReference, instantiationMapping); + } else { + // TODO: Do we need this (return type?) + completer.visit(memberReference); + } + reset(); } /** - * Calls {@link #visit(AnnotatedTypeMirror, AnnotatedTypeMirror)} for each type in {@code - * types}. + * If the primary annotation of {@code polyType} is a polymorphic qualifier, then it is mapped + * to the primary annotation of {@code type} and the map is returned. Otherwise, an empty map is + * returned. * - * @param types the AnnotateTypeMirrors used to find instantiations - * @param polyTypes the AnnotatedTypeMirrors that may have polymorphic qualifiers - * @return a mapping of polymorphic qualifiers to their instantiations + * @param type type with qualifier to us in the map + * @param polyType type that may have polymorphic qualifiers + * @return a mapping from the polymorphic qualifiers in {@code polyType} to the qualifiers in + * {@code type} */ - private AnnotationMirrorMap visit( - Iterable types, - Iterable polyTypes) { - AnnotationMirrorMap result = new AnnotationMirrorMap<>(); - - Iterator itert = types.iterator(); - Iterator itera = polyTypes.iterator(); - - while (itert.hasNext() && itera.hasNext()) { - AnnotatedTypeMirror type = itert.next(); - AnnotatedTypeMirror actualType = itera.next(); - result = reduce(result, visit(type, actualType)); - } - if (itert.hasNext()) { - throw new BugInCF( - "PolyCollector.visit: types is longer than polyTypes:%n" - + " types = %s%n polyTypes = %s%n", - types, polyTypes); - } - if (itera.hasNext()) { - throw new BugInCF( - "PolyCollector.visit: types is shorter than polyTypes:%n" - + " types = %s%n polyTypes = %s%n", - types, polyTypes); - } - return result; + private AnnotationMirrorMap mapQualifierToPoly( + AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { + AnnotationMirrorMap result = new AnnotationMirrorMap<>(); + + for (Map.Entry kv : polyQuals.entrySet()) { + AnnotationMirror top = kv.getValue(); + AnnotationMirror poly = kv.getKey(); + if (polyType.hasAnnotation(poly)) { + AnnotationMirror typeQual = type.getAnnotationInHierarchy(top); + if (typeQual != null) { + if (atypeFactory.hasQualifierParameterInHierarchy(type, top)) { + polyInstantiationForQualifierParameter.put(poly, typeQual); + } + result.put(poly, typeQual); + } + } + } + return result; } /** - * Creates a mapping of polymorphic qualifiers to their instantiations by visiting each - * composite type in {@code type}. + * Returns annotation that is the combination of the two annotations. The annotations are + * instantiations for {@code polyQual}. + * + *

          The combination is typically their least upper bound. (It could be the GLB in the case + * that all arguments to a polymorphic method must have the same annotation.) * - * @param type the AnnotateTypeMirror used to find instantiations - * @param polyType the AnnotatedTypeMirror that may have polymorphic qualifiers - * @return a mapping of polymorphic qualifiers to their instantiations + * @param polyQual polymorphic qualifier for which {@code a1} and {@code a2} are instantiations + * @param a1 an annotation that is an instantiation of {@code polyQual} + * @param a2 an annotation that is an instantiation of {@code polyQual} + * @return an annotation that is the combination of the two annotations */ - private AnnotationMirrorMap visit( - AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { - if (type.getKind() == TypeKind.NULL) { - return mapQualifierToPoly(type, polyType); - } - - if (type.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) type; - if (wildcardType.getExtendsBound().getKind() == TypeKind.WILDCARD) { - wildcardType = (AnnotatedWildcardType) wildcardType.getExtendsBound(); + protected abstract AnnotationMirror combine( + AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2); + + /** + * Replaces the top-level polymorphic annotations in {@code type} with the instantiations in + * {@code replacements}. + * + *

          This method is called on all parts of a type. + * + * @param type the AnnotatedTypeMirror whose poly annotations are replaced; it is side-effected + * by this method + * @param replacements a mapping from polymorphic annotation to instantiation + */ + protected abstract void replace( + AnnotatedTypeMirror type, AnnotationMirrorMap replacements); + + /** + * A helper class that resolves the polymorphic qualifiers with the most restrictive qualifier. + * It returns a mapping from the polymorphic qualifier to the substitution for that qualifier. + */ + private class PolyCollector + extends EquivalentAtmComboScanner, Void> { + + /** + * Set of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been + * visited. Used to prevent infinite recursion on recursive types. + * + *

          Uses reference equality rather than equals because the visitor may visit two types + * that are structurally equal, but not actually the same. For example, the wildcards in + * {@code IPair} may be equal, but they both should be visited. + */ + private final Set visitedTypes = + Collections.newSetFromMap(new IdentityHashMap()); + + /** + * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it + * is added to the list of visited AnnotatedTypeMirrors. + */ + private boolean visited(AnnotatedTypeMirror atm) { + return !visitedTypes.add(atm); } - if (wildcardType.isTypeArgOfRawType()) { - return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); + + @Override + protected AnnotationMirrorMap scanWithNull( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { + return new AnnotationMirrorMap<>(); } - switch (polyType.getKind()) { - case WILDCARD: - AnnotatedTypeMirror asSuper = - AnnotatedTypes.asSuper(atypeFactory, wildcardType, polyType); - return visit(asSuper, polyType, null); - case TYPEVAR: - return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); - default: - return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); + @Override + public AnnotationMirrorMap reduce( + AnnotationMirrorMap r1, + AnnotationMirrorMap r2) { + + if (r1 == null || r1.isEmpty()) { + return r2; + } + if (r2 == null || r2.isEmpty()) { + return r1; + } + + AnnotationMirrorMap res = new AnnotationMirrorMap<>(); + // Ensure that all qualifiers from r1 and r2 are visited. + AnnotationMirrorSet r2remain = new AnnotationMirrorSet(); + r2remain.addAll(r2.keySet()); + for (Map.Entry entry : r1.entrySet()) { + AnnotationMirror polyQual = entry.getKey(); + AnnotationMirror a1Annos = entry.getValue(); + AnnotationMirror a2Annos = r2.get(polyQual); + if (a2Annos == null) { + res.put(polyQual, a1Annos); + } else { + res.put(polyQual, combine(polyQual, a1Annos, a2Annos)); + } + r2remain.remove(polyQual); + } + for (AnnotationMirror key2 : r2remain) { + res.put(key2, r2.get(key2)); + } + return res; } - } - AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(atypeFactory, type, polyType); + /** + * Calls {@link #visit(AnnotatedTypeMirror, AnnotatedTypeMirror)} for each type in {@code + * types}. + * + * @param types the AnnotateTypeMirrors used to find instantiations + * @param polyTypes the AnnotatedTypeMirrors that may have polymorphic qualifiers + * @return a mapping of polymorphic qualifiers to their instantiations + */ + private AnnotationMirrorMap visit( + Iterable types, + Iterable polyTypes) { + AnnotationMirrorMap result = new AnnotationMirrorMap<>(); + + Iterator itert = types.iterator(); + Iterator itera = polyTypes.iterator(); + + while (itert.hasNext() && itera.hasNext()) { + AnnotatedTypeMirror type = itert.next(); + AnnotatedTypeMirror actualType = itera.next(); + result = reduce(result, visit(type, actualType)); + } + if (itert.hasNext()) { + throw new BugInCF( + "PolyCollector.visit: types is longer than polyTypes:%n" + + " types = %s%n polyTypes = %s%n", + types, polyTypes); + } + if (itera.hasNext()) { + throw new BugInCF( + "PolyCollector.visit: types is shorter than polyTypes:%n" + + " types = %s%n polyTypes = %s%n", + types, polyTypes); + } + return result; + } - return visit(asSuper, polyType, null); - } + /** + * Creates a mapping of polymorphic qualifiers to their instantiations by visiting each + * composite type in {@code type}. + * + * @param type the AnnotateTypeMirror used to find instantiations + * @param polyType the AnnotatedTypeMirror that may have polymorphic qualifiers + * @return a mapping of polymorphic qualifiers to their instantiations + */ + private AnnotationMirrorMap visit( + AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) { + if (type.getKind() == TypeKind.NULL) { + return mapQualifierToPoly(type, polyType); + } + + if (type.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) type; + if (wildcardType.getExtendsBound().getKind() == TypeKind.WILDCARD) { + wildcardType = (AnnotatedWildcardType) wildcardType.getExtendsBound(); + } + if (wildcardType.isTypeArgOfRawType()) { + return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); + } - @Override - public AnnotationMirrorMap visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, Void aVoid) { - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitArray_Array(type1, type2, aVoid)); - } + switch (polyType.getKind()) { + case WILDCARD: + AnnotatedTypeMirror asSuper = + AnnotatedTypes.asSuper(atypeFactory, wildcardType, polyType); + return visit(asSuper, polyType, null); + case TYPEVAR: + return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); + default: + return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType); + } + } - @Override - public AnnotationMirrorMap visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void aVoid) { - // Don't call super because asSuper has to be called on each type argument. - if (visited(type2)) { - return new AnnotationMirrorMap<>(); - } - - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - - Iterator type2Args = type2.getTypeArguments().iterator(); - for (AnnotatedTypeMirror type1Arg : type1.getTypeArguments()) { - AnnotatedTypeMirror type2Arg = type2Args.next(); - if (TypesUtils.isErasedSubtype( - type1Arg.getUnderlyingType(), - type2Arg.getUnderlyingType(), - atypeFactory.getChecker().getTypeUtils())) { - result = reduce(result, visit(type1Arg, type2Arg)); - } // else an unchecked warning was issued by Java, ignore this part of the type. - } - - return result; - } + AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(atypeFactory, type, polyType); - @Override - public AnnotationMirrorMap visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void aVoid) { - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitIntersection_Intersection(type1, type2, aVoid)); - } + return visit(asSuper, polyType, null); + } - @Override - public AnnotationMirrorMap visitNull_Null( - AnnotatedNullType type1, AnnotatedNullType type2, Void aVoid) { - return mapQualifierToPoly(type1, type2); - } + @Override + public AnnotationMirrorMap visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, Void aVoid) { + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitArray_Array(type1, type2, aVoid)); + } - @Override - public AnnotationMirrorMap visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void aVoid) { - return mapQualifierToPoly(type1, type2); - } + @Override + public AnnotationMirrorMap visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void aVoid) { + // Don't call super because asSuper has to be called on each type argument. + if (visited(type2)) { + return new AnnotationMirrorMap<>(); + } + + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + + Iterator type2Args = type2.getTypeArguments().iterator(); + for (AnnotatedTypeMirror type1Arg : type1.getTypeArguments()) { + AnnotatedTypeMirror type2Arg = type2Args.next(); + if (TypesUtils.isErasedSubtype( + type1Arg.getUnderlyingType(), + type2Arg.getUnderlyingType(), + atypeFactory.getChecker().getTypeUtils())) { + result = reduce(result, visit(type1Arg, type2Arg)); + } // else an unchecked warning was issued by Java, ignore this part of the type. + } + + return result; + } - @Override - public AnnotationMirrorMap visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void aVoid) { - if (visited(type2)) { - return new AnnotationMirrorMap<>(); - } - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitTypevar_Typevar(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void aVoid) { + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitIntersection_Intersection(type1, type2, aVoid)); + } - @Override - public AnnotationMirrorMap visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, Void aVoid) { - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitUnion_Union(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitNull_Null( + AnnotatedNullType type1, AnnotatedNullType type2, Void aVoid) { + return mapQualifierToPoly(type1, type2); + } - @Override - public AnnotationMirrorMap visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void aVoid) { - if (visited(type2)) { - return new AnnotationMirrorMap<>(); - } - AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); - return reduce(result, super.visitWildcard_Wildcard(type1, type2, aVoid)); - } + @Override + public AnnotationMirrorMap visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void aVoid) { + return mapQualifierToPoly(type1, type2); + } + + @Override + public AnnotationMirrorMap visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void aVoid) { + if (visited(type2)) { + return new AnnotationMirrorMap<>(); + } + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitTypevar_Typevar(type1, type2, aVoid)); + } + + @Override + public AnnotationMirrorMap visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, Void aVoid) { + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitUnion_Union(type1, type2, aVoid)); + } + + @Override + public AnnotationMirrorMap visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void aVoid) { + if (visited(type2)) { + return new AnnotationMirrorMap<>(); + } + AnnotationMirrorMap result = mapQualifierToPoly(type1, type2); + return reduce(result, super.visitWildcard_Wildcard(type1, type2, aVoid)); + } - /** Resets the state. */ - public void reset() { - this.visitedTypes.clear(); - this.visited.clear(); + /** Resets the state. */ + public void reset() { + this.visitedTypes.clear(); + this.visited.clear(); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java index ef69e019e15..febd2da4ef5 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java @@ -1,12 +1,14 @@ package org.checkerframework.framework.type.poly; -import java.util.Map; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationMirrorMap; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; + /** * Default implementation of {@link AbstractQualifierPolymorphism}. The polymorphic qualifiers for a * checker that uses this class are found by searching all supported qualifiers. Instantiations of a @@ -14,67 +16,67 @@ */ public class DefaultQualifierPolymorphism extends AbstractQualifierPolymorphism { - /** - * Creates a {@link DefaultQualifierPolymorphism} instance that uses {@code factory} for querying - * type qualifiers and for getting annotated types. - * - * @param env the processing environment - * @param factory the factory for the current checker - */ - public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { - super(env, factory); + /** + * Creates a {@link DefaultQualifierPolymorphism} instance that uses {@code factory} for + * querying type qualifiers and for getting annotated types. + * + * @param env the processing environment + * @param factory the factory for the current checker + */ + public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) { + super(env, factory); - for (AnnotationMirror top : topQuals) { - AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); - if (poly != null) { - polyQuals.put(poly, top); - } + for (AnnotationMirror top : topQuals) { + AnnotationMirror poly = qualHierarchy.getPolymorphicAnnotation(top); + if (poly != null) { + polyQuals.put(poly, top); + } + } } - } - @Override - protected void replace( - AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { - if (replacements.isEmpty()) { - // If the 'replacements' map is empty, it is likely a case where a method with - // a varargs parameter was invoked with zero varargs actuals. - // In this case, the polymorphic qualifiers should be replaced with the top type in - // the qualifier hierarchy, since there is no further information to deduce. - for (AnnotationMirror top : topQuals) { - AnnotationMirror effectiveAnno = type.getEffectiveAnnotationInHierarchy(top); - if (effectiveAnno != null && qualHierarchy.isPolymorphicQualifier(effectiveAnno)) { - replacements.put(effectiveAnno, top); + @Override + protected void replace( + AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { + if (replacements.isEmpty()) { + // If the 'replacements' map is empty, it is likely a case where a method with + // a varargs parameter was invoked with zero varargs actuals. + // In this case, the polymorphic qualifiers should be replaced with the top type in + // the qualifier hierarchy, since there is no further information to deduce. + for (AnnotationMirror top : topQuals) { + AnnotationMirror effectiveAnno = type.getEffectiveAnnotationInHierarchy(top); + if (effectiveAnno != null && qualHierarchy.isPolymorphicQualifier(effectiveAnno)) { + replacements.put(effectiveAnno, top); + } + } } - } - } - for (Map.Entry pqentry : replacements.entrySet()) { - AnnotationMirror poly = pqentry.getKey(); - if (type.hasAnnotation(poly)) { - type.removeAnnotation(poly); - AnnotationMirror qual; - if (polyInstantiationForQualifierParameter.containsKey(poly)) { - qual = polyInstantiationForQualifierParameter.get(poly); - } else { - qual = pqentry.getValue(); + for (Map.Entry pqentry : replacements.entrySet()) { + AnnotationMirror poly = pqentry.getKey(); + if (type.hasAnnotation(poly)) { + type.removeAnnotation(poly); + AnnotationMirror qual; + if (polyInstantiationForQualifierParameter.containsKey(poly)) { + qual = polyInstantiationForQualifierParameter.get(poly); + } else { + qual = pqentry.getValue(); + } + type.replaceAnnotation(qual); + } } - type.replaceAnnotation(qual); - } } - } - /** - * This implementation combines the two annotations using the least upper bound. - * - *

          {@inheritDoc} - */ - @Override - protected AnnotationMirror combine( - AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == null) { - return a2; - } else if (a2 == null) { - return a1; + /** + * This implementation combines the two annotations using the least upper bound. + * + *

          {@inheritDoc} + */ + @Override + protected AnnotationMirror combine( + AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == null) { + return a2; + } else if (a2 == null) { + return a1; + } + return qualHierarchy.leastUpperBoundQualifiersOnly(a1, a2); } - return qualHierarchy.leastUpperBoundQualifiersOnly(a1, a2); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java index 4283cc75909..a2b8805b09e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java @@ -2,11 +2,13 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; -import javax.lang.model.element.VariableElement; + import org.checkerframework.framework.qual.PolymorphicQualifier; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import javax.lang.model.element.VariableElement; + /** * Interface to implement qualifier polymorphism. * @@ -16,45 +18,45 @@ */ public interface QualifierPolymorphism { - /** - * Returns true if {@code type} has any polymorphic qualifiers - * - * @param type a type that might have polymorphic qualifiers - * @return true if {@code type} has any polymorphic qualifiers - */ - boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type); - - /** - * Resolves polymorphism annotations for the given type. - * - * @param tree the tree associated with the type - * @param type the type to annotate; is side-effected by this method - */ - void resolve(MethodInvocationTree tree, AnnotatedExecutableType type); - - /** - * Resolves polymorphism annotations for the given type. - * - * @param tree the tree associated with the type - * @param type the type to annotate; is side-effected by this method - */ - void resolve(NewClassTree tree, AnnotatedExecutableType type); - - /** - * Resolves polymorphism annotations for the given type. - * - * @param functionalInterface the function type of {@code memberReference} - * @param memberReference the type of a member reference; is side-effected by this method - */ - void resolve( - AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference); - - /** - * Resolves polymorphism annotations for the given field type. - * - * @param field field element to whose poly annotation must be resolved - * @param owner the type of the object whose field is being typed - * @param type type of the field which still has poly annotations - */ - void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type); + /** + * Returns true if {@code type} has any polymorphic qualifiers + * + * @param type a type that might have polymorphic qualifiers + * @return true if {@code type} has any polymorphic qualifiers + */ + boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type); + + /** + * Resolves polymorphism annotations for the given type. + * + * @param tree the tree associated with the type + * @param type the type to annotate; is side-effected by this method + */ + void resolve(MethodInvocationTree tree, AnnotatedExecutableType type); + + /** + * Resolves polymorphism annotations for the given type. + * + * @param tree the tree associated with the type + * @param type the type to annotate; is side-effected by this method + */ + void resolve(NewClassTree tree, AnnotatedExecutableType type); + + /** + * Resolves polymorphism annotations for the given type. + * + * @param functionalInterface the function type of {@code memberReference} + * @param memberReference the type of a member reference; is side-effected by this method + */ + void resolve( + AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference); + + /** + * Resolves polymorphism annotations for the given field type. + * + * @param field field element to whose poly annotation must be resolved + * @param owner the type of the object whose field is being typed + * @param type type of the field which still has poly annotations + */ + void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java index 75d9dfd457f..a622fe80978 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/DebugListTreeAnnotator.java @@ -1,52 +1,54 @@ package org.checkerframework.framework.type.treeannotator; import com.sun.source.tree.Tree; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; + import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import org.checkerframework.framework.type.AnnotatedTypeMirror; /** A ListTreeAnnotator implementation that additionally outputs debugging information. */ public class DebugListTreeAnnotator extends ListTreeAnnotator { - /** The tree kinds to debug. */ - private final Set kinds; - - /** - * Constructs a DebugListTreeAnnotator that does not output any debug information. - * - * @param annotators the annotators for ListTreeAnnotator - */ - public DebugListTreeAnnotator(TreeAnnotator... annotators) { - super(annotators); - kinds = Collections.emptySet(); - } - - /** - * Constructs a DebugListTreeAnnotator that outputs debug for the given tree kinds. - * - * @param kinds the tree kinds to output debug info for - * @param annotators the annotators for ListTreeAnnotator - */ - public DebugListTreeAnnotator(Tree.Kind[] kinds, TreeAnnotator... annotators) { - super(annotators); - this.kinds = new HashSet<>(Arrays.asList(kinds)); - } - - @Override - public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { - if (kinds.contains(tree.getKind())) { - System.out.println("DebugListTreeAnnotator input tree: " + tree); - System.out.println(" Initial type: " + type); - for (TreeAnnotator annotator : annotators) { - System.out.println(" Running annotator: " + annotator.getClass()); - annotator.visit(tree, type); - System.out.println(" Current type: " + type); - } - } else { - super.defaultAction(tree, type); + /** The tree kinds to debug. */ + private final Set kinds; + + /** + * Constructs a DebugListTreeAnnotator that does not output any debug information. + * + * @param annotators the annotators for ListTreeAnnotator + */ + public DebugListTreeAnnotator(TreeAnnotator... annotators) { + super(annotators); + kinds = Collections.emptySet(); } - return null; - } + /** + * Constructs a DebugListTreeAnnotator that outputs debug for the given tree kinds. + * + * @param kinds the tree kinds to output debug info for + * @param annotators the annotators for ListTreeAnnotator + */ + public DebugListTreeAnnotator(Tree.Kind[] kinds, TreeAnnotator... annotators) { + super(annotators); + this.kinds = new HashSet<>(Arrays.asList(kinds)); + } + + @Override + public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { + if (kinds.contains(tree.getKind())) { + System.out.println("DebugListTreeAnnotator input tree: " + tree); + System.out.println(" Initial type: " + type); + for (TreeAnnotator annotator : annotators) { + System.out.println(" Running annotator: " + annotator.getClass()); + annotator.visit(tree, type); + System.out.println(" Current type: " + type); + } + } else { + super.defaultAction(tree, type); + } + + return null; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java index 9fea8110876..b8ff8473e69 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/ListTreeAnnotator.java @@ -1,11 +1,13 @@ package org.checkerframework.framework.type.treeannotator; import com.sun.source.tree.Tree; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.checkerframework.framework.type.AnnotatedTypeMirror; /** * ListTreeAnnotator is a TreeVisitor that executes a list of {@link TreeAnnotator} for each tree @@ -19,44 +21,44 @@ */ public class ListTreeAnnotator extends TreeAnnotator { - protected final List annotators; - - /** - * @param annotators the annotators that will be executed for each tree scanned by this - * TreeAnnotator. They are executed in the order passed in. - */ - public ListTreeAnnotator(TreeAnnotator... annotators) { - this(Arrays.asList(annotators)); - } - - /** - * @param annotators the annotators that will be executed for each tree scanned by this - * TreeAnnotator. They are executed in the order passed in. - */ - public ListTreeAnnotator(List annotators) { - super(null); - List annotatorList = new ArrayList<>(annotators.size()); - for (TreeAnnotator annotator : annotators) { - if (annotator instanceof ListTreeAnnotator) { - annotatorList.addAll(((ListTreeAnnotator) annotator).annotators); - } else { - annotatorList.add(annotator); - } + protected final List annotators; + + /** + * @param annotators the annotators that will be executed for each tree scanned by this + * TreeAnnotator. They are executed in the order passed in. + */ + public ListTreeAnnotator(TreeAnnotator... annotators) { + this(Arrays.asList(annotators)); } - this.annotators = Collections.unmodifiableList(annotatorList); - } - @Override - public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { - for (TreeAnnotator annotator : annotators) { - annotator.visit(tree, type); + /** + * @param annotators the annotators that will be executed for each tree scanned by this + * TreeAnnotator. They are executed in the order passed in. + */ + public ListTreeAnnotator(List annotators) { + super(null); + List annotatorList = new ArrayList<>(annotators.size()); + for (TreeAnnotator annotator : annotators) { + if (annotator instanceof ListTreeAnnotator) { + annotatorList.addAll(((ListTreeAnnotator) annotator).annotators); + } else { + annotatorList.add(annotator); + } + } + this.annotators = Collections.unmodifiableList(annotatorList); } - return null; - } + @Override + public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { + for (TreeAnnotator annotator : annotators) { + annotator.visit(tree, type); + } + + return null; + } - @Override - public String toString() { - return "ListTreeAnnotator(" + annotators + ")"; - } + @Override + public String toString() { + return "ListTreeAnnotator(" + annotators + ")"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java index 9d738da21bf..edc7b58a646 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/LiteralTreeAnnotator.java @@ -2,17 +2,7 @@ import com.sun.source.tree.LiteralTree; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.framework.qual.LiteralKind; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -25,6 +15,19 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; + /** * Adds annotations to a type based on the contents of a tree. This class applies annotations * specified by {@link org.checkerframework.framework.qual.QualifierForLiterals}; it is designed to @@ -37,244 +40,247 @@ */ public class LiteralTreeAnnotator extends TreeAnnotator { - /* The following three fields are mappings from a particular AST kind, - * AST Class, or String literal pattern to the set of AnnotationMirrors - * that should be defaulted. - * There can be at most one qualifier per qualifier hierarchy. - * For type systems with single top qualifiers, the sets will always contain - * at most one element. - */ - /** Maps AST kind to the set of AnnotationMirrors that should be defaulted. */ - private final Map treeKinds; + /* The following three fields are mappings from a particular AST kind, + * AST Class, or String literal pattern to the set of AnnotationMirrors + * that should be defaulted. + * There can be at most one qualifier per qualifier hierarchy. + * For type systems with single top qualifiers, the sets will always contain + * at most one element. + */ + /** Maps AST kind to the set of AnnotationMirrors that should be defaulted. */ + private final Map treeKinds; - /** Maps AST class to the set of AnnotationMirrors that should be defaulted. */ - private final Map, AnnotationMirrorSet> treeClasses; + /** Maps AST class to the set of AnnotationMirrors that should be defaulted. */ + private final Map, AnnotationMirrorSet> treeClasses; - /** Maps String literal pattern to the set of AnnotationMirrors that should be defaulted. */ - private final IdentityHashMap stringPatterns; + /** Maps String literal pattern to the set of AnnotationMirrors that should be defaulted. */ + private final IdentityHashMap stringPatterns; - /** The qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; + /** The qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; - /** - * Map of {@link LiteralKind}s to {@link Tree.Kind}s. This is here and not in LiteralKinds because - * LiteralKind is in the checker-qual.jar which cannot depend on classes, such as Tree.Kind, that - * are in the tools.jar - */ - private static final Map literalKindToTreeKind = - new EnumMap<>(LiteralKind.class); + /** + * Map of {@link LiteralKind}s to {@link Tree.Kind}s. This is here and not in LiteralKinds + * because LiteralKind is in the checker-qual.jar which cannot depend on classes, such as + * Tree.Kind, that are in the tools.jar + */ + private static final Map literalKindToTreeKind = + new EnumMap<>(LiteralKind.class); - static { - literalKindToTreeKind.put(LiteralKind.BOOLEAN, Tree.Kind.BOOLEAN_LITERAL); - literalKindToTreeKind.put(LiteralKind.CHAR, Tree.Kind.CHAR_LITERAL); - literalKindToTreeKind.put(LiteralKind.DOUBLE, Tree.Kind.DOUBLE_LITERAL); - literalKindToTreeKind.put(LiteralKind.FLOAT, Tree.Kind.FLOAT_LITERAL); - literalKindToTreeKind.put(LiteralKind.INT, Tree.Kind.INT_LITERAL); - literalKindToTreeKind.put(LiteralKind.LONG, Tree.Kind.LONG_LITERAL); - literalKindToTreeKind.put(LiteralKind.NULL, Tree.Kind.NULL_LITERAL); - literalKindToTreeKind.put(LiteralKind.STRING, Tree.Kind.STRING_LITERAL); - } + static { + literalKindToTreeKind.put(LiteralKind.BOOLEAN, Tree.Kind.BOOLEAN_LITERAL); + literalKindToTreeKind.put(LiteralKind.CHAR, Tree.Kind.CHAR_LITERAL); + literalKindToTreeKind.put(LiteralKind.DOUBLE, Tree.Kind.DOUBLE_LITERAL); + literalKindToTreeKind.put(LiteralKind.FLOAT, Tree.Kind.FLOAT_LITERAL); + literalKindToTreeKind.put(LiteralKind.INT, Tree.Kind.INT_LITERAL); + literalKindToTreeKind.put(LiteralKind.LONG, Tree.Kind.LONG_LITERAL); + literalKindToTreeKind.put(LiteralKind.NULL, Tree.Kind.NULL_LITERAL); + literalKindToTreeKind.put(LiteralKind.STRING, Tree.Kind.STRING_LITERAL); + } - /** - * Creates a {@link LiteralTreeAnnotator} for the given {@code atypeFactory}. - * - * @param atypeFactory the type factory to make an annotator for - */ - public LiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - this.treeKinds = new EnumMap<>(Tree.Kind.class); - this.treeClasses = new HashMap<>(); - this.stringPatterns = new IdentityHashMap<>(); + /** + * Creates a {@link LiteralTreeAnnotator} for the given {@code atypeFactory}. + * + * @param atypeFactory the type factory to make an annotator for + */ + public LiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + this.treeKinds = new EnumMap<>(Tree.Kind.class); + this.treeClasses = new HashMap<>(); + this.stringPatterns = new IdentityHashMap<>(); - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - // Get type qualifiers from the checker. - Set> quals = atypeFactory.getSupportedTypeQualifiers(); + // Get type qualifiers from the checker. + Set> quals = atypeFactory.getSupportedTypeQualifiers(); - // For each qualifier, read the @QualifierForLiterals annotation and put its contents into - // maps. - for (Class qual : quals) { - QualifierForLiterals forLiterals = qual.getAnnotation(QualifierForLiterals.class); - if (forLiterals == null) { - continue; - } + // For each qualifier, read the @QualifierForLiterals annotation and put its contents into + // maps. + for (Class qual : quals) { + QualifierForLiterals forLiterals = qual.getAnnotation(QualifierForLiterals.class); + if (forLiterals == null) { + continue; + } - AnnotationMirror theQual = AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), qual); - for (LiteralKind literalKind : forLiterals.value()) { - addLiteralKind(literalKind, theQual); - } + AnnotationMirror theQual = + AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), qual); + for (LiteralKind literalKind : forLiterals.value()) { + addLiteralKind(literalKind, theQual); + } - for (String pattern : forLiterals.stringPatterns()) { - addStringPattern(pattern, theQual); - } + for (String pattern : forLiterals.stringPatterns()) { + addStringPattern(pattern, theQual); + } - if (forLiterals.value().length == 0 && forLiterals.stringPatterns().length == 0) { - addLiteralKind(LiteralKind.ALL, theQual); - } + if (forLiterals.value().length == 0 && forLiterals.stringPatterns().length == 0) { + addLiteralKind(LiteralKind.ALL, theQual); + } + } } - } - /** - * Adds standard qualifiers for literals. Currently sets the null literal to bottom if no other - * default is set for null literals. Also, see {@link - * DefaultForTypeAnnotator#addStandardDefaults()}. - * - * @return this - */ - public LiteralTreeAnnotator addStandardLiteralQualifiers() { - // Set null to bottom if no other qualifier is given. - if (!treeKinds.containsKey(Tree.Kind.NULL_LITERAL)) { - for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { - addLiteralKind(LiteralKind.NULL, bottom); - } - return this; - } - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet defaultForNull = treeKinds.get(Tree.Kind.NULL_LITERAL); - if (tops.size() == defaultForNull.size()) { - return this; - } - for (AnnotationMirror top : tops) { - if (qualHierarchy.findAnnotationInHierarchy(defaultForNull, top) == null) { - defaultForNull.add(qualHierarchy.getBottomAnnotation(top)); - } + /** + * Adds standard qualifiers for literals. Currently sets the null literal to bottom if no other + * default is set for null literals. Also, see {@link + * DefaultForTypeAnnotator#addStandardDefaults()}. + * + * @return this + */ + public LiteralTreeAnnotator addStandardLiteralQualifiers() { + // Set null to bottom if no other qualifier is given. + if (!treeKinds.containsKey(Tree.Kind.NULL_LITERAL)) { + for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { + addLiteralKind(LiteralKind.NULL, bottom); + } + return this; + } + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet defaultForNull = treeKinds.get(Tree.Kind.NULL_LITERAL); + if (tops.size() == defaultForNull.size()) { + return this; + } + for (AnnotationMirror top : tops) { + if (qualHierarchy.findAnnotationInHierarchy(defaultForNull, top) == null) { + defaultForNull.add(qualHierarchy.getBottomAnnotation(top)); + } + } + return this; } - return this; - } - /** - * Added a rule for a particular {@link LiteralKind} - * - * @param literalKind {@code LiteralKind} that should be defaulted to {@code theQual} - * @param theQual the {@code AnnotationMirror} that should be applied to the {@code literalKind} - */ - public void addLiteralKind(LiteralKind literalKind, AnnotationMirror theQual) { - if (literalKind == LiteralKind.ALL) { - for (LiteralKind iterLiteralKind : LiteralKind.allLiteralKinds()) { - addLiteralKind(iterLiteralKind, theQual); - } - } else if (literalKind == LiteralKind.PRIMITIVE) { - for (LiteralKind iterLiteralKind : LiteralKind.primitiveLiteralKinds()) { - addLiteralKind(iterLiteralKind, theQual); - } - } else { - Tree.Kind treeKind = literalKindToTreeKind.get(literalKind); - if (treeKind != null) { - addTreeKind(treeKind, theQual); - } else { - throw new BugInCF("LiteralKind " + literalKind + " is not mapped to a Tree.Kind."); - } + /** + * Added a rule for a particular {@link LiteralKind} + * + * @param literalKind {@code LiteralKind} that should be defaulted to {@code theQual} + * @param theQual the {@code AnnotationMirror} that should be applied to the {@code literalKind} + */ + public void addLiteralKind(LiteralKind literalKind, AnnotationMirror theQual) { + if (literalKind == LiteralKind.ALL) { + for (LiteralKind iterLiteralKind : LiteralKind.allLiteralKinds()) { + addLiteralKind(iterLiteralKind, theQual); + } + } else if (literalKind == LiteralKind.PRIMITIVE) { + for (LiteralKind iterLiteralKind : LiteralKind.primitiveLiteralKinds()) { + addLiteralKind(iterLiteralKind, theQual); + } + } else { + Tree.Kind treeKind = literalKindToTreeKind.get(literalKind); + if (treeKind != null) { + addTreeKind(treeKind, theQual); + } else { + throw new BugInCF("LiteralKind " + literalKind + " is not mapped to a Tree.Kind."); + } + } } - } - /** - * Added a rule for a particular {@link com.sun.source.tree.Tree.Kind} - * - * @param treeKind {@code Tree.Kind} that should be implicited to {@code theQual} - * @param theQual the {@code AnnotationMirror} that should be applied to the {@code treeKind} - */ - private void addTreeKind(Tree.Kind treeKind, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(treeKinds, treeKind, theQual); - if (!res) { - throw new BugInCF( - "LiteralTreeAnnotator: tried to add mapping %s=%s to %s", treeKind, theQual, treeKinds); + /** + * Added a rule for a particular {@link com.sun.source.tree.Tree.Kind} + * + * @param treeKind {@code Tree.Kind} that should be implicited to {@code theQual} + * @param theQual the {@code AnnotationMirror} that should be applied to the {@code treeKind} + */ + private void addTreeKind(Tree.Kind treeKind, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(treeKinds, treeKind, theQual); + if (!res) { + throw new BugInCF( + "LiteralTreeAnnotator: tried to add mapping %s=%s to %s", + treeKind, theQual, treeKinds); + } } - } - /** - * Added a rule for all String literals that match the given pattern. - * - * @param pattern pattern to match Strings against - * @param theQual {@code AnnotationMirror} to apply to Strings that match the pattern - */ - public void addStringPattern(String pattern, AnnotationMirror theQual) { - boolean res = - qualHierarchy.updateMappingToMutableSet(stringPatterns, Pattern.compile(pattern), theQual); - if (!res) { - throw new BugInCF( - "LiteralTreeAnnotator: invalid update of stringPatterns " - + stringPatterns - + " at " - + pattern - + " with " - + theQual); + /** + * Added a rule for all String literals that match the given pattern. + * + * @param pattern pattern to match Strings against + * @param theQual {@code AnnotationMirror} to apply to Strings that match the pattern + */ + public void addStringPattern(String pattern, AnnotationMirror theQual) { + boolean res = + qualHierarchy.updateMappingToMutableSet( + stringPatterns, Pattern.compile(pattern), theQual); + if (!res) { + throw new BugInCF( + "LiteralTreeAnnotator: invalid update of stringPatterns " + + stringPatterns + + " at " + + pattern + + " with " + + theQual); + } } - } - @Override - public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { - if (tree == null || type == null) { - return null; - } + @Override + public Void defaultAction(Tree tree, AnnotatedTypeMirror type) { + if (tree == null || type == null) { + return null; + } - // If this tree's kind is in treeKinds, annotate the type. + // If this tree's kind is in treeKinds, annotate the type. - // If this tree's class or any of its interfaces are in treeClasses, annotate the type, and - // if it was an interface add a mapping for it to treeClasses. - if (treeKinds.containsKey(tree.getKind())) { - AnnotationMirrorSet fnd = treeKinds.get(tree.getKind()); - type.addMissingAnnotations(fnd); - } else if (!treeClasses.isEmpty()) { - Class t = tree.getClass(); - if (treeClasses.containsKey(t)) { - AnnotationMirrorSet fnd = treeClasses.get(t); - type.addMissingAnnotations(fnd); - } - for (Class c : t.getInterfaces()) { - if (treeClasses.containsKey(c)) { - AnnotationMirrorSet fnd = treeClasses.get(c); - type.addMissingAnnotations(fnd); - treeClasses.put(t, treeClasses.get(c)); + // If this tree's class or any of its interfaces are in treeClasses, annotate the type, and + // if it was an interface add a mapping for it to treeClasses. + if (treeKinds.containsKey(tree.getKind())) { + AnnotationMirrorSet fnd = treeKinds.get(tree.getKind()); + type.addMissingAnnotations(fnd); + } else if (!treeClasses.isEmpty()) { + Class t = tree.getClass(); + if (treeClasses.containsKey(t)) { + AnnotationMirrorSet fnd = treeClasses.get(t); + type.addMissingAnnotations(fnd); + } + for (Class c : t.getInterfaces()) { + if (treeClasses.containsKey(c)) { + AnnotationMirrorSet fnd = treeClasses.get(c); + type.addMissingAnnotations(fnd); + treeClasses.put(t, treeClasses.get(c)); + } + } } - } + return null; } - return null; - } - /** Go through the string patterns and add the greatest lower bound of all matching patterns. */ - @Override - public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { - if (!stringPatterns.isEmpty() && tree.getKind() == Tree.Kind.STRING_LITERAL) { - List> matches = new ArrayList<>(); - List> nonMatches = new ArrayList<>(); + /** Go through the string patterns and add the greatest lower bound of all matching patterns. */ + @Override + public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { + if (!stringPatterns.isEmpty() && tree.getKind() == Tree.Kind.STRING_LITERAL) { + List> matches = new ArrayList<>(); + List> nonMatches = new ArrayList<>(); - String string = (String) tree.getValue(); - for (Map.Entry entry : stringPatterns.entrySet()) { - Pattern pattern = entry.getKey(); - AnnotationMirrorSet sam = entry.getValue(); - if (pattern.matcher(string).matches()) { - matches.add(sam); - } else { - nonMatches.add(sam); - } - } - if (!matches.isEmpty()) { - TypeMirror tm = type.getUnderlyingType(); - Set res = matches.get(0); - for (Set sam : matches) { - res = qualHierarchy.greatestLowerBoundsShallow(res, tm, sam, tm); - } - // Verify that res is not a subtype of any type in nonMatches - for (Set sam : nonMatches) { - if (qualHierarchy.isSubtypeShallow(res, sam, tm)) { - String matchesOnePerLine = ""; - for (Set match : matches) { - matchesOnePerLine += System.lineSeparator() + " " + match; + String string = (String) tree.getValue(); + for (Map.Entry entry : stringPatterns.entrySet()) { + Pattern pattern = entry.getKey(); + AnnotationMirrorSet sam = entry.getValue(); + if (pattern.matcher(string).matches()) { + matches.add(sam); + } else { + nonMatches.add(sam); + } + } + if (!matches.isEmpty()) { + TypeMirror tm = type.getUnderlyingType(); + Set res = matches.get(0); + for (Set sam : matches) { + res = qualHierarchy.greatestLowerBoundsShallow(res, tm, sam, tm); + } + // Verify that res is not a subtype of any type in nonMatches + for (Set sam : nonMatches) { + if (qualHierarchy.isSubtypeShallow(res, sam, tm)) { + String matchesOnePerLine = ""; + for (Set match : matches) { + matchesOnePerLine += System.lineSeparator() + " " + match; + } + throw new BugInCF( + StringsPlume.joinLines( + "Bug in @QualifierForLiterals(stringpatterns=...) in type" + + " hierarchy definition:", + " the glb of `matches` for \"" + string + "\" is " + res, + " which is a subtype of " + sam, + " whose pattern does not match \"" + string + "\".", + " matches = " + matchesOnePerLine, + " nonMatches = " + nonMatches)); + } + } + type.addAnnotations(res); } - throw new BugInCF( - StringsPlume.joinLines( - "Bug in @QualifierForLiterals(stringpatterns=...) in type" - + " hierarchy definition:", - " the glb of `matches` for \"" + string + "\" is " + res, - " which is a subtype of " + sam, - " whose pattern does not match \"" + string + "\".", - " matches = " + matchesOnePerLine, - " nonMatches = " + nonMatches)); - } } - type.addAnnotations(res); - } + return super.visitLiteral(tree, type); } - return super.visitLiteral(tree, type); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java index 3e125f95f34..238b0e662ba 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java @@ -12,11 +12,7 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -30,6 +26,13 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * {@link PropagationTreeAnnotator} adds qualifiers to types where the resulting type is a function * of an input type, e.g. the result of a binary operation is a LUB of the type of expressions in @@ -44,340 +47,348 @@ */ public class PropagationTreeAnnotator extends TreeAnnotator { - private final QualifierHierarchy qualHierarchy; - - /** Creates a {@link PropagationTreeAnnotator} for the given {@code atypeFactory}. */ - public PropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - } - - /** - * Whether to use the assignment context when computing the type of a new array expression. This - * is a hack to prevent infinite recursion if computing the type of the assignment context - * includes computing the type of the right-hand side of the assignment. This happens when the - * assignment is the pseudo-assignment of a method argument to a formal parameter. - */ - private boolean useAssignmentContext = true; - - /** - * A mapping from {@code MethodInvocationTree} to the type of its declaration adapted to the call - * site. This is a cache used when getting the type of a new array expression that is an argument - * to a method. Getting the call-site-adapted type of a method also gets the type of all the - * arguments at the call site. (This happens both for resolving polymorphic methods and for method - * type argument inference.) {@link #useAssignmentContext} is used to prevent infinite recursion - * and this cache is used to improve performance. - */ - private final Map methodInvocationToType = - CollectionsPlume.createLruCache(300); - - @Override - public Void visitNewArray(NewArrayTree arrayTree, AnnotatedTypeMirror arrayType) { - assert arrayType.getKind() == TypeKind.ARRAY - : "PropagationTreeAnnotator.visitNewArray: should be an array type"; - - AnnotatedTypeMirror componentType = ((AnnotatedArrayType) arrayType).getComponentType(); - TypeMirror componentTM = componentType.getUnderlyingType(); - - // prev is the lub of the initializers if they exist, otherwise the current component type. - Set prev = null; - if (arrayTree.getInitializers() != null && !arrayTree.getInitializers().isEmpty()) { - // We have initializers, either with or without an array type. - - // TODO (issue #599): This only works at the top level. It should work at all levels of - // the array. - for (ExpressionTree init : arrayTree.getInitializers()) { - AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); - // initType might be a typeVariable, so use effectiveAnnotations. - AnnotationMirrorSet annos = initType.getEffectiveAnnotations(); - - prev = - (prev == null) - ? annos - : qualHierarchy.leastUpperBoundsShallow( - prev, componentTM, annos, initType.getUnderlyingType()); - } - } else { - prev = componentType.getAnnotations(); + private final QualifierHierarchy qualHierarchy; + + /** Creates a {@link PropagationTreeAnnotator} for the given {@code atypeFactory}. */ + public PropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); } - assert prev != null - : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers"; - - TreePath path = atypeFactory.getPath(arrayTree); - AnnotatedTypeMirror contextType = null; - if (path != null && path.getParentPath() != null) { - Tree parentTree = path.getParentPath().getLeaf(); - if (parentTree.getKind() == Tree.Kind.ASSIGNMENT) { - Tree var = ((AssignmentTree) parentTree).getVariable(); - contextType = atypeFactory.getAnnotatedType(var); - } else if (parentTree.getKind() == Tree.Kind.VARIABLE) { - if (!TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) parentTree)) { - contextType = atypeFactory.getAnnotatedType(parentTree); - } - } else if (parentTree instanceof CompoundAssignmentTree) { - Tree var = ((CompoundAssignmentTree) parentTree).getVariable(); - contextType = atypeFactory.getAnnotatedType(var); - } else if (parentTree.getKind() == Tree.Kind.RETURN) { - Tree methodTree = TreePathUtil.enclosingMethodOrLambda(path.getParentPath()); - if (methodTree.getKind() == Tree.Kind.METHOD) { - AnnotatedExecutableType methodType = - atypeFactory.getAnnotatedType((MethodTree) methodTree); - contextType = methodType.getReturnType(); - } - } else if (parentTree.getKind() == Tree.Kind.METHOD_INVOCATION && useAssignmentContext) { - MethodInvocationTree methodInvocationTree = (MethodInvocationTree) parentTree; - useAssignmentContext = false; - AnnotatedExecutableType m; - try { - if (atypeFactory.shouldCache - && methodInvocationToType.containsKey(methodInvocationTree)) { - m = methodInvocationToType.get(methodInvocationTree); - } else { - m = atypeFactory.methodFromUse(methodInvocationTree).executableType; - if (atypeFactory.shouldCache) { - methodInvocationToType.put(methodInvocationTree, m); + /** + * Whether to use the assignment context when computing the type of a new array expression. This + * is a hack to prevent infinite recursion if computing the type of the assignment context + * includes computing the type of the right-hand side of the assignment. This happens when the + * assignment is the pseudo-assignment of a method argument to a formal parameter. + */ + private boolean useAssignmentContext = true; + + /** + * A mapping from {@code MethodInvocationTree} to the type of its declaration adapted to the + * call site. This is a cache used when getting the type of a new array expression that is an + * argument to a method. Getting the call-site-adapted type of a method also gets the type of + * all the arguments at the call site. (This happens both for resolving polymorphic methods and + * for method type argument inference.) {@link #useAssignmentContext} is used to prevent + * infinite recursion and this cache is used to improve performance. + */ + private final Map methodInvocationToType = + CollectionsPlume.createLruCache(300); + + @Override + public Void visitNewArray(NewArrayTree arrayTree, AnnotatedTypeMirror arrayType) { + assert arrayType.getKind() == TypeKind.ARRAY + : "PropagationTreeAnnotator.visitNewArray: should be an array type"; + + AnnotatedTypeMirror componentType = ((AnnotatedArrayType) arrayType).getComponentType(); + TypeMirror componentTM = componentType.getUnderlyingType(); + + // prev is the lub of the initializers if they exist, otherwise the current component type. + Set prev = null; + if (arrayTree.getInitializers() != null && !arrayTree.getInitializers().isEmpty()) { + // We have initializers, either with or without an array type. + + // TODO (issue #599): This only works at the top level. It should work at all levels of + // the array. + for (ExpressionTree init : arrayTree.getInitializers()) { + AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); + // initType might be a typeVariable, so use effectiveAnnotations. + AnnotationMirrorSet annos = initType.getEffectiveAnnotations(); + + prev = + (prev == null) + ? annos + : qualHierarchy.leastUpperBoundsShallow( + prev, componentTM, annos, initType.getUnderlyingType()); } - } - } finally { - useAssignmentContext = true; + } else { + prev = componentType.getAnnotations(); } - int parametersCount = m.getParameterTypes().size(); - boolean isVarargs = m.getElement().isVarArgs(); - - // If the method accepts varargs, we handle the vararg parameter after this - // for-loop. - for (int i = 0; i < parametersCount; i++) { - @SuppressWarnings("interning") // Tree must be exactly the same. - boolean foundArgument = methodInvocationTree.getArguments().get(i) == arrayTree; - if (foundArgument) { - contextType = m.getParameterTypes().get(i); - break; - } + assert prev != null + : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers"; + + TreePath path = atypeFactory.getPath(arrayTree); + AnnotatedTypeMirror contextType = null; + if (path != null && path.getParentPath() != null) { + Tree parentTree = path.getParentPath().getLeaf(); + if (parentTree.getKind() == Tree.Kind.ASSIGNMENT) { + Tree var = ((AssignmentTree) parentTree).getVariable(); + contextType = atypeFactory.getAnnotatedType(var); + } else if (parentTree.getKind() == Tree.Kind.VARIABLE) { + if (!TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) parentTree)) { + contextType = atypeFactory.getAnnotatedType(parentTree); + } + } else if (parentTree instanceof CompoundAssignmentTree) { + Tree var = ((CompoundAssignmentTree) parentTree).getVariable(); + contextType = atypeFactory.getAnnotatedType(var); + } else if (parentTree.getKind() == Tree.Kind.RETURN) { + Tree methodTree = TreePathUtil.enclosingMethodOrLambda(path.getParentPath()); + if (methodTree.getKind() == Tree.Kind.METHOD) { + AnnotatedExecutableType methodType = + atypeFactory.getAnnotatedType((MethodTree) methodTree); + contextType = methodType.getReturnType(); + } + } else if (parentTree.getKind() == Tree.Kind.METHOD_INVOCATION + && useAssignmentContext) { + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) parentTree; + useAssignmentContext = false; + AnnotatedExecutableType m; + try { + if (atypeFactory.shouldCache + && methodInvocationToType.containsKey(methodInvocationTree)) { + m = methodInvocationToType.get(methodInvocationTree); + } else { + m = atypeFactory.methodFromUse(methodInvocationTree).executableType; + if (atypeFactory.shouldCache) { + methodInvocationToType.put(methodInvocationTree, m); + } + } + } finally { + useAssignmentContext = true; + } + + int parametersCount = m.getParameterTypes().size(); + boolean isVarargs = m.getElement().isVarArgs(); + + // If the method accepts varargs, we handle the vararg parameter after this + // for-loop. + for (int i = 0; i < parametersCount; i++) { + @SuppressWarnings("interning") // Tree must be exactly the same. + boolean foundArgument = methodInvocationTree.getArguments().get(i) == arrayTree; + if (foundArgument) { + contextType = m.getParameterTypes().get(i); + break; + } + } + + if (isVarargs && contextType == null) { + // The previous for-loop did not find any arguments matching the + // new array tree, thus the tree has to be an argument for varargs. + // + // The tree could be an artificial tree when the code doesn't provide + // any explicit arguments for the varargs (i.e., an empty array). + // So we don't try getting the argument from methodInvocationTree + // to avoid out-of-bound exception. + contextType = m.getVarargType(); + } + } } - - if (isVarargs && contextType == null) { - // The previous for-loop did not find any arguments matching the - // new array tree, thus the tree has to be an argument for varargs. - // - // The tree could be an artificial tree when the code doesn't provide - // any explicit arguments for the varargs (i.e., an empty array). - // So we don't try getting the argument from methodInvocationTree - // to avoid out-of-bound exception. - contextType = m.getVarargType(); + Set post; + + if (contextType instanceof AnnotatedArrayType) { + AnnotatedTypeMirror contextComponentType = + ((AnnotatedArrayType) contextType).getComponentType(); + // Only compare the qualifiers that existed in the array type. + // Defaulting wasn't performed yet, so prev might have fewer qualifiers than + // contextComponentType, which would cause a failure. + // TODO: better solution? + TypeMirror contextCTM = contextComponentType.getUnderlyingType(); + boolean prevIsSubtype = true; + for (AnnotationMirror am : prev) { + if (contextComponentType.hasAnnotationInHierarchy(am) + && !this.qualHierarchy.isSubtypeShallow( + am, + contextCTM, + contextComponentType.getAnnotationInHierarchy(am), + contextCTM)) { + prevIsSubtype = false; + } + } + // TODO: checking conformance of component kinds is a basic sanity check + // It fails for array initializer expressions. Those should be handled nicer. + if (contextComponentType.getKind() == componentType.getKind() + && (prev.isEmpty() + || (!contextComponentType.getAnnotations().isEmpty() + && prevIsSubtype))) { + post = contextComponentType.getAnnotations(); + } else { + // The type of the array initializers is incompatible with the context type! + // Somebody else will complain. + post = prev; + } + } else { + // No context is available - simply use what we have. + post = prev; } - } + // TODO (issue #599): This only works at the top level. It should work at all levels of + // the array. + addAnnoOrBound(componentType, post); + + return null; } - Set post; - - if (contextType instanceof AnnotatedArrayType) { - AnnotatedTypeMirror contextComponentType = - ((AnnotatedArrayType) contextType).getComponentType(); - // Only compare the qualifiers that existed in the array type. - // Defaulting wasn't performed yet, so prev might have fewer qualifiers than - // contextComponentType, which would cause a failure. - // TODO: better solution? - TypeMirror contextCTM = contextComponentType.getUnderlyingType(); - boolean prevIsSubtype = true; - for (AnnotationMirror am : prev) { - if (contextComponentType.hasAnnotationInHierarchy(am) - && !this.qualHierarchy.isSubtypeShallow( - am, contextCTM, contextComponentType.getAnnotationInHierarchy(am), contextCTM)) { - prevIsSubtype = false; + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + return null; } - } - // TODO: checking conformance of component kinds is a basic sanity check - // It fails for array initializer expressions. Those should be handled nicer. - if (contextComponentType.getKind() == componentType.getKind() - && (prev.isEmpty() - || (!contextComponentType.getAnnotations().isEmpty() && prevIsSubtype))) { - post = contextComponentType.getAnnotations(); - } else { - // The type of the array initializers is incompatible with the context type! - // Somebody else will complain. - post = prev; - } - } else { - // No context is available - simply use what we have. - post = prev; - } - // TODO (issue #599): This only works at the top level. It should work at all levels of - // the array. - addAnnoOrBound(componentType, post); - - return null; - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) { - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - return null; - } - IPair argTypes = - atypeFactory.compoundAssignmentTreeArgTypes(tree); - AnnotatedTypeMirror rhs = argTypes.first; - AnnotatedTypeMirror lhs = argTypes.second; - Set lubs = - qualHierarchy.leastUpperBoundsShallow( - rhs.getEffectiveAnnotations(), - rhs.getUnderlyingType(), - lhs.getEffectiveAnnotations(), - lhs.getUnderlyingType()); - type.addMissingAnnotations(lubs); - - return null; - } - - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - // Also, calling getAnnotatedType on the left and right operands is potentially - // expensive. - return null; + IPair argTypes = + atypeFactory.compoundAssignmentTreeArgTypes(tree); + AnnotatedTypeMirror rhs = argTypes.first; + AnnotatedTypeMirror lhs = argTypes.second; + Set lubs = + qualHierarchy.leastUpperBoundsShallow( + rhs.getEffectiveAnnotations(), + rhs.getUnderlyingType(), + lhs.getEffectiveAnnotations(), + lhs.getUnderlyingType()); + type.addMissingAnnotations(lubs); + + return null; } - if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { - return null; - } + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + // Also, calling getAnnotatedType on the left and right operands is potentially + // expensive. + return null; + } - IPair argTypes = - atypeFactory.binaryTreeArgTypes(tree); - AnnotatedTypeMirror type1 = argTypes.first; - AnnotatedTypeMirror type2 = argTypes.second; - Set lubs = - qualHierarchy.leastUpperBoundsShallow( - type1.getEffectiveAnnotations(), - type1.getUnderlyingType(), - type2.getEffectiveAnnotations(), - type2.getUnderlyingType()); - - if (TreeUtils.isBinaryComparison(tree)) { - // When we have binary comparison, the result type (boolean) can be different - // from the operands' types. So we need to apply the bounds of boolean to the - // lubs. - lubs = atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), lubs); - } + if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { + return null; + } - log( - "%s PTA.visitBinary(%s, %s)%n argTypes=%s%n lubs=%s%n", - atypeFactory.getClass().getSimpleName(), tree, type, argTypes, lubs); + IPair argTypes = + atypeFactory.binaryTreeArgTypes(tree); + AnnotatedTypeMirror type1 = argTypes.first; + AnnotatedTypeMirror type2 = argTypes.second; + Set lubs = + qualHierarchy.leastUpperBoundsShallow( + type1.getEffectiveAnnotations(), + type1.getUnderlyingType(), + type2.getEffectiveAnnotations(), + type2.getUnderlyingType()); + + if (TreeUtils.isBinaryComparison(tree)) { + // When we have binary comparison, the result type (boolean) can be different + // from the operands' types. So we need to apply the bounds of boolean to the + // lubs. + lubs = atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), lubs); + } - type.addMissingAnnotations(lubs); - log("PTA.visitBinary(%s, ...): final type = %s%n", tree, type); + log( + "%s PTA.visitBinary(%s, %s)%n argTypes=%s%n lubs=%s%n", + atypeFactory.getClass().getSimpleName(), tree, type, argTypes, lubs); - return null; - } + type.addMissingAnnotations(lubs); + log("PTA.visitBinary(%s, ...): final type = %s%n", tree, type); - @Override - public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - return null; + return null; } - AnnotatedTypeMirror exp = atypeFactory.getAnnotatedType(tree.getExpression()); - type.addMissingAnnotations(exp.getAnnotations()); - return null; - } - - /* - * TODO: would this make sense in general? - @Override - public Void visitConditionalExpression(ConditionalExpressionTree tree, AnnotatedTypeMirror type) { - if (!type.isAnnotated()) { - AnnotatedTypeMirror a = typeFactory.getAnnotatedType(tree.getTrueExpression()); - AnnotatedTypeMirror b = typeFactory.getAnnotatedType(tree.getFalseExpression()); - AnnotationMirrorSet lubs = qualHierarchy.leastUpperBounds(a.getEffectiveAnnotations(), b.getEffectiveAnnotations()); - type.replaceAnnotations(lubs); - } - return super.visitConditionalExpression(tree, type); - }*/ - - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { - if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { - return null; - } + @Override + public Void visitUnary(UnaryTree tree, AnnotatedTypeMirror type) { + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + return null; + } - if (hasPrimaryAnnotationInAllHierarchies(type)) { - // If the type is already has a primary annotation in all hierarchies, then the - // propagated annotations won't be applied. So don't compute them. - log("PTA.visitTypeCast(%s, %s): hasPrimaryAnnotationInAllHierarchies%n", tree, type); - return null; + AnnotatedTypeMirror exp = atypeFactory.getAnnotatedType(tree.getExpression()); + type.addMissingAnnotations(exp.getAnnotations()); + return null; } - AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); - if (type.getKind() == TypeKind.TYPEVAR) { - if (exprType.getKind() == TypeKind.TYPEVAR) { - // If both types are type variables, take the direct annotations. - type.addMissingAnnotations(exprType.getAnnotations()); - } - // else do nothing. - } else { - // Use effective annotations from the expression, to get upper bound of type variables. - AnnotationMirrorSet expressionAnnos = exprType.getEffectiveAnnotations(); - log( - "PTA.visitTypeCast(%s, %s): getEffectiveAnnotations(%s) = %s%n", - tree, type, exprType, expressionAnnos); - - TypeKind castKind = type.getPrimitiveKind(); - if (castKind != null) { - TypeKind exprKind = exprType.getPrimitiveKind(); - if (exprKind != null) { - switch (TypeKindUtils.getPrimitiveConversionKind(exprKind, castKind)) { - case WIDENING: - expressionAnnos = - atypeFactory.getWidenedAnnotations(expressionAnnos, exprKind, castKind); - break; - case NARROWING: - atypeFactory.getNarrowedAnnotations(expressionAnnos, exprKind, castKind); - break; - case SAME: - // Nothing to do - break; - } + /* + * TODO: would this make sense in general? + @Override + public Void visitConditionalExpression(ConditionalExpressionTree tree, AnnotatedTypeMirror type) { + if (!type.isAnnotated()) { + AnnotatedTypeMirror a = typeFactory.getAnnotatedType(tree.getTrueExpression()); + AnnotatedTypeMirror b = typeFactory.getAnnotatedType(tree.getFalseExpression()); + AnnotationMirrorSet lubs = qualHierarchy.leastUpperBounds(a.getEffectiveAnnotations(), b.getEffectiveAnnotations()); + type.replaceAnnotations(lubs); + } + return super.visitConditionalExpression(tree, type); + }*/ + + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror type) { + if (!((GenericAnnotatedTypeFactory) atypeFactory).isRelevant(type)) { + return null; } - } - // If the qualifier on the expression type is a supertype of the qualifier upper bound - // of the cast type, then apply the bound as the default qualifier rather than the - // expression qualifier. - addAnnoOrBound(type, expressionAnnos); + if (hasPrimaryAnnotationInAllHierarchies(type)) { + // If the type is already has a primary annotation in all hierarchies, then the + // propagated annotations won't be applied. So don't compute them. + log("PTA.visitTypeCast(%s, %s): hasPrimaryAnnotationInAllHierarchies%n", tree, type); + return null; + } + + AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(tree.getExpression()); + if (type.getKind() == TypeKind.TYPEVAR) { + if (exprType.getKind() == TypeKind.TYPEVAR) { + // If both types are type variables, take the direct annotations. + type.addMissingAnnotations(exprType.getAnnotations()); + } + // else do nothing. + } else { + // Use effective annotations from the expression, to get upper bound of type variables. + AnnotationMirrorSet expressionAnnos = exprType.getEffectiveAnnotations(); + log( + "PTA.visitTypeCast(%s, %s): getEffectiveAnnotations(%s) = %s%n", + tree, type, exprType, expressionAnnos); + + TypeKind castKind = type.getPrimitiveKind(); + if (castKind != null) { + TypeKind exprKind = exprType.getPrimitiveKind(); + if (exprKind != null) { + switch (TypeKindUtils.getPrimitiveConversionKind(exprKind, castKind)) { + case WIDENING: + expressionAnnos = + atypeFactory.getWidenedAnnotations( + expressionAnnos, exprKind, castKind); + break; + case NARROWING: + atypeFactory.getNarrowedAnnotations( + expressionAnnos, exprKind, castKind); + break; + case SAME: + // Nothing to do + break; + } + } + } + + // If the qualifier on the expression type is a supertype of the qualifier upper bound + // of the cast type, then apply the bound as the default qualifier rather than the + // expression qualifier. + addAnnoOrBound(type, expressionAnnos); + } + + return null; } - return null; - } + private boolean hasPrimaryAnnotationInAllHierarchies(AnnotatedTypeMirror type) { + boolean annotated = true; + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (type.getEffectiveAnnotationInHierarchy(top) == null) { + annotated = false; + } + } + return annotated; + } - private boolean hasPrimaryAnnotationInAllHierarchies(AnnotatedTypeMirror type) { - boolean annotated = true; - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (type.getEffectiveAnnotationInHierarchy(top) == null) { - annotated = false; - } + /** + * Adds the qualifiers in {@code annos} to {@code type} that are below the qualifier upper bound + * of type and for which type does not already have annotation in the same hierarchy. If a + * qualifier in {@code annos} is above the bound, then the bound is added to {@code type} + * instead. + * + * @param type annotations are added to this type + * @param annos annotations to add to type + */ + private void addAnnoOrBound(AnnotatedTypeMirror type, Set annos) { + log("addAnnoOrBound(%s, %s)%n", type, annos); + AnnotationMirrorSet annosToAdd = + atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), annos); + type.addMissingAnnotations(annosToAdd); + log("addAnnoOrBound#2(%s, %s)%n", type, annos); } - return annotated; - } - - /** - * Adds the qualifiers in {@code annos} to {@code type} that are below the qualifier upper bound - * of type and for which type does not already have annotation in the same hierarchy. If a - * qualifier in {@code annos} is above the bound, then the bound is added to {@code type} instead. - * - * @param type annotations are added to this type - * @param annos annotations to add to type - */ - private void addAnnoOrBound(AnnotatedTypeMirror type, Set annos) { - log("addAnnoOrBound(%s, %s)%n", type, annos); - AnnotationMirrorSet annosToAdd = - atypeFactory.getAnnotationOrTypeDeclarationBound(type.getUnderlyingType(), annos); - type.addMissingAnnotations(annosToAdd); - log("addAnnoOrBound#2(%s, %s)%n", type, annos); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java index 93e140d7bc8..a3f8506ff4c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java @@ -4,6 +4,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.util.SimpleTreeVisitor; + import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -29,64 +30,64 @@ */ public abstract class TreeAnnotator extends SimpleTreeVisitor { - /** - * Whether to output verbose, low-level debugging messages. Also see {@code - * GenericAnnotatedTypeFactory.debug}. - */ - private static final boolean debug = false; + /** + * Whether to output verbose, low-level debugging messages. Also see {@code + * GenericAnnotatedTypeFactory.debug}. + */ + private static final boolean debug = false; - /** The type factory. */ - protected final AnnotatedTypeFactory atypeFactory; + /** The type factory. */ + protected final AnnotatedTypeFactory atypeFactory; - /** - * Create a new TreeAnnotator. - * - * @param atypeFactory the type factory - */ - protected TreeAnnotator(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** + * Create a new TreeAnnotator. + * + * @param atypeFactory the type factory + */ + protected TreeAnnotator(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - /** - * Output a message, if logging is on. - * - * @param format a format string - * @param args arguments to the format string - */ - @FormatMethod - protected void log(String format, Object... args) { - if (debug) { - SystemPlume.sleep(1); // logging can interleave with typechecker output - System.out.printf(format, args); + /** + * Output a message, if logging is on. + * + * @param format a format string + * @param args arguments to the format string + */ + @FormatMethod + protected void log(String format, Object... args) { + if (debug) { + SystemPlume.sleep(1); // logging can interleave with typechecker output + System.out.printf(format, args); + } } - } - /** - * This method is not called when checking a method invocation against its declaration. So, - * instead of overriding this method, override TypeAnnotator.visitExecutable. - * TypeAnnotator.visitExecutable is called both when checking method declarations and method - * invocations. - * - * @see org.checkerframework.framework.type.typeannotator.TypeAnnotator - */ - @Override - public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { - return super.visitMethod(tree, p); - } + /** + * This method is not called when checking a method invocation against its declaration. So, + * instead of overriding this method, override TypeAnnotator.visitExecutable. + * TypeAnnotator.visitExecutable is called both when checking method declarations and method + * invocations. + * + * @see org.checkerframework.framework.type.typeannotator.TypeAnnotator + */ + @Override + public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) { + return super.visitMethod(tree, p); + } - /** - * When overriding this method, getAnnotatedType on the left and right operands should only be - * called when absolutely necessary. Otherwise, the checker will be very slow on heavily nested - * binary trees. (For example, a + b + c + d + e + f + g + h.) - * - *

          If a checker's performance is still too slow, the types of binary trees could be computed in - * a subclass of {@link org.checkerframework.framework.flow.CFTransfer}. When computing the types - * in a transfer, look up the value in the store rather than the AnnotatedTypeFactory. Then this - * method should annotate binary trees with top so that the type applied in the transfer is always - * a subtype of the type the AnnotatedTypeFactory computes. - */ - @Override - public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror mirror) { - return super.visitBinary(tree, mirror); - } + /** + * When overriding this method, getAnnotatedType on the left and right operands should only be + * called when absolutely necessary. Otherwise, the checker will be very slow on heavily nested + * binary trees. (For example, a + b + c + d + e + f + g + h.) + * + *

          If a checker's performance is still too slow, the types of binary trees could be computed + * in a subclass of {@link org.checkerframework.framework.flow.CFTransfer}. When computing the + * types in a transfer, look up the value in the store rather than the AnnotatedTypeFactory. + * Then this method should annotate binary trees with top so that the type applied in the + * transfer is always a subtype of the type the AnnotatedTypeFactory computes. + */ + @Override + public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror mirror) { + return super.visitBinary(tree, mirror); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java index 60865e96593..ba3e69a0fef 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultForTypeAnnotator.java @@ -1,5 +1,20 @@ package org.checkerframework.framework.type.typeannotator; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypeAnnotationUtils; +import org.checkerframework.javacutil.TypeSystemError; +import org.checkerframework.javacutil.TypesUtils; + import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.EnumMap; @@ -10,26 +25,13 @@ import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; -import org.checkerframework.javacutil.AnnotationBuilder; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TypeAnnotationUtils; -import org.checkerframework.javacutil.TypeSystemError; -import org.checkerframework.javacutil.TypesUtils; /** * Adds annotations to a type based on the use of a type. This class applies annotations specified @@ -45,320 +47,329 @@ */ public class DefaultForTypeAnnotator extends TypeAnnotator { - /** Map from {@link TypeKind} to annotations. */ - private final Map typeKinds; - - /** Map from {@link AnnotatedTypeMirror} classes to annotations. */ - private final Map, AnnotationMirrorSet> atmClasses; - - /** Map from fully qualified class name strings to annotations. */ - private final Map types; - - /** - * A list where each element associates an annotation with name regexes and name exception - * regexes. - */ - private final ListOfNameRegexes listOfNameRegexes; - - /** {@link QualifierHierarchy} */ - private final QualifierHierarchy qualHierarchy; - - /** - * Creates a {@link DefaultForTypeAnnotator} from the given checker, using that checker to - * determine the annotations that are in the type hierarchy. - */ - public DefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - this.typeKinds = new EnumMap<>(TypeKind.class); - this.atmClasses = new HashMap<>(); - this.types = new HashMap<>(); - this.listOfNameRegexes = new ListOfNameRegexes(); - - this.qualHierarchy = typeFactory.getQualifierHierarchy(); + /** Map from {@link TypeKind} to annotations. */ + private final Map typeKinds; - // Get type qualifiers from the checker. - Set> quals = typeFactory.getSupportedTypeQualifiers(); + /** Map from {@link AnnotatedTypeMirror} classes to annotations. */ + private final Map, AnnotationMirrorSet> atmClasses; - // For each qualifier, read the @DefaultFor annotation and put its types, kinds, and names - // into maps. - for (Class qual : quals) { - DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); - if (defaultFor == null) { - continue; - } + /** Map from fully qualified class name strings to annotations. */ + private final Map types; - AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); - - for (org.checkerframework.framework.qual.TypeKind typeKind : defaultFor.typeKinds()) { - TypeKind mappedTk = mapTypeKinds(typeKind); - addTypeKind(mappedTk, theQual); - } - - for (Class typeName : defaultFor.types()) { - addTypes(typeName, theQual); - } + /** + * A list where each element associates an annotation with name regexes and name exception + * regexes. + */ + private final ListOfNameRegexes listOfNameRegexes; - listOfNameRegexes.add(theQual, defaultFor); - } - } - - /** - * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link - * javax.lang.model.type.TypeKind}. - * - * @param typeKind the Checker Framework TypeKind - * @return the javax TypeKind - */ - private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { - return TypeKind.valueOf(typeKind.name()); - } - - /** Add default qualifier, {@code theQual}, for the given TypeKind. */ - public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); - if (!res) { - throw new BugInCF( - "TypeAnnotator: invalid update of typeKinds " - + typeKinds - + " at " - + typeKind - + " with " - + theQual); - } - } - - /** Add default qualifier, {@code theQual}, for the given {@link AnnotatedTypeMirror} class. */ - public void addAtmClass( - Class typeClass, AnnotationMirror theQual) { - boolean res = qualHierarchy.updateMappingToMutableSet(atmClasses, typeClass, theQual); - if (!res) { - throw new BugInCF( - "TypeAnnotator: invalid update of atmClasses " - + atmClasses - + " at " - + typeClass - + " with " - + theQual); - } - } - - /** Add default qualifier, {@code theQual}, for the given type. */ - public void addTypes(Class clazz, AnnotationMirror theQual) { - String typeNameString = clazz.getCanonicalName(); - boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); - if (!res) { - throw new BugInCF( - "TypeAnnotator: invalid update of types " + types + " at " + clazz + " with " + theQual); - } - } - - @Override - protected Void scan(AnnotatedTypeMirror type, Void p) { - // If the type's fully-qualified name is in the appropriate map, annotate the type. Do this - // before looking at kind or class, as this information is more specific. - - String qname; - // We have to use the type name without annotations for the lookup. - TypeMirror unannotatedType = TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()); - if (type.getKind() == TypeKind.DECLARED) { - qname = TypesUtils.getQualifiedName((DeclaredType) unannotatedType); - } else if (type.getKind().isPrimitive()) { - qname = unannotatedType.toString(); - } else { - qname = null; - } + /** {@link QualifierHierarchy} */ + private final QualifierHierarchy qualHierarchy; - // Perform the lookup. - if (qname != null) { - AnnotationMirrorSet fromQname = types.get(qname); - if (fromQname != null) { - type.addMissingAnnotations(fromQname); - } + /** + * Creates a {@link DefaultForTypeAnnotator} from the given checker, using that checker to + * determine the annotations that are in the type hierarchy. + */ + public DefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + this.typeKinds = new EnumMap<>(TypeKind.class); + this.atmClasses = new HashMap<>(); + this.types = new HashMap<>(); + this.listOfNameRegexes = new ListOfNameRegexes(); + + this.qualHierarchy = typeFactory.getQualifierHierarchy(); + + // Get type qualifiers from the checker. + Set> quals = typeFactory.getSupportedTypeQualifiers(); + + // For each qualifier, read the @DefaultFor annotation and put its types, kinds, and names + // into maps. + for (Class qual : quals) { + DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); + if (defaultFor == null) { + continue; + } + + AnnotationMirror theQual = + AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); + + for (org.checkerframework.framework.qual.TypeKind typeKind : defaultFor.typeKinds()) { + TypeKind mappedTk = mapTypeKinds(typeKind); + addTypeKind(mappedTk, theQual); + } + + for (Class typeName : defaultFor.types()) { + addTypes(typeName, theQual); + } + + listOfNameRegexes.add(theQual, defaultFor); + } } - // If the type's kind or class is in the appropriate map, annotate the type. - AnnotationMirrorSet fromKind = typeKinds.get(type.getKind()); - if (fromKind != null) { - type.addMissingAnnotations(fromKind); - } else if (!atmClasses.isEmpty()) { - Class t = type.getClass(); - AnnotationMirrorSet fromClass = atmClasses.get(t); - if (fromClass != null) { - type.addMissingAnnotations(fromClass); - } + /** + * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link + * javax.lang.model.type.TypeKind}. + * + * @param typeKind the Checker Framework TypeKind + * @return the javax TypeKind + */ + private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { + return TypeKind.valueOf(typeKind.name()); } - return super.scan(type, p); - } - - /** - * Adds standard rules. Currently, sets Void to bottom if no other qualifier is set for Void. - * Also, see {@link LiteralTreeAnnotator#addStandardLiteralQualifiers()}. - * - * @return this - */ - public DefaultForTypeAnnotator addStandardDefaults() { - if (!types.containsKey(Void.class.getCanonicalName())) { - for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { - addTypes(Void.class, bottom); - } - } else { - AnnotationMirrorSet annos = types.get(Void.class.getCanonicalName()); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - if (qualHierarchy.findAnnotationInHierarchy(annos, top) == null) { - addTypes(Void.class, qualHierarchy.getBottomAnnotation(top)); + /** Add default qualifier, {@code theQual}, for the given TypeKind. */ + public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); + if (!res) { + throw new BugInCF( + "TypeAnnotator: invalid update of typeKinds " + + typeKinds + + " at " + + typeKind + + " with " + + theQual); } - } } - return this; - } - - /** - * Apply defaults based on a variable name to a type. - * - * @param type a type to apply defaults to - * @param name the name of the variable that has type {@code type}, or the name of the method - * whose return type is {@code type} - */ - public void defaultTypeFromName(AnnotatedTypeMirror type, String name) { - // TODO: Check whether the annotation is applicable to this Java type? - AnnotationMirror defaultAnno = listOfNameRegexes.getDefaultAnno(name); - if (defaultAnno != null) { - if (atypeFactory - .getQualifierHierarchy() - .findAnnotationInHierarchy(type.getAnnotations(), defaultAnno) - == null) { - type.addAnnotation(defaultAnno); - } + /** Add default qualifier, {@code theQual}, for the given {@link AnnotatedTypeMirror} class. */ + public void addAtmClass( + Class typeClass, AnnotationMirror theQual) { + boolean res = qualHierarchy.updateMappingToMutableSet(atmClasses, typeClass, theQual); + if (!res) { + throw new BugInCF( + "TypeAnnotator: invalid update of atmClasses " + + atmClasses + + " at " + + typeClass + + " with " + + theQual); + } } - } - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - ExecutableElement element = type.getElement(); - - Iterator paramTypes = type.getParameterTypes().iterator(); - for (VariableElement paramElt : element.getParameters()) { - String paramName = paramElt.getSimpleName().toString(); - AnnotatedTypeMirror paramType = paramTypes.next(); - defaultTypeFromName(paramType, paramName); + /** Add default qualifier, {@code theQual}, for the given type. */ + public void addTypes(Class clazz, AnnotationMirror theQual) { + String typeNameString = clazz.getCanonicalName(); + boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); + if (!res) { + throw new BugInCF( + "TypeAnnotator: invalid update of types " + + types + + " at " + + clazz + + " with " + + theQual); + } } - String methodName = element.getSimpleName().toString(); - AnnotatedTypeMirror returnType = type.getReturnType(); - defaultTypeFromName(returnType, methodName); + @Override + protected Void scan(AnnotatedTypeMirror type, Void p) { + // If the type's fully-qualified name is in the appropriate map, annotate the type. Do this + // before looking at kind or class, as this information is more specific. + + String qname; + // We have to use the type name without annotations for the lookup. + TypeMirror unannotatedType = TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()); + if (type.getKind() == TypeKind.DECLARED) { + qname = TypesUtils.getQualifiedName((DeclaredType) unannotatedType); + } else if (type.getKind().isPrimitive()) { + qname = unannotatedType.toString(); + } else { + qname = null; + } - return super.visitExecutable(type, aVoid); - } + // Perform the lookup. + if (qname != null) { + AnnotationMirrorSet fromQname = types.get(qname); + if (fromQname != null) { + type.addMissingAnnotations(fromQname); + } + } - /** - * A list where each element associates an annotation with name regexes and name exception - * regexes. - */ - private static class ListOfNameRegexes extends ArrayList { + // If the type's kind or class is in the appropriate map, annotate the type. + AnnotationMirrorSet fromKind = typeKinds.get(type.getKind()); + if (fromKind != null) { + type.addMissingAnnotations(fromKind); + } else if (!atmClasses.isEmpty()) { + Class t = type.getClass(); + AnnotationMirrorSet fromClass = atmClasses.get(t); + if (fromClass != null) { + type.addMissingAnnotations(fromClass); + } + } - static final long serialVersionUID = 20200218L; + return super.scan(type, p); + } /** - * Update this list from the {@code names} and {@code namesExceptions} fields of a @DefaultFor - * annotation. + * Adds standard rules. Currently, sets Void to bottom if no other qualifier is set for Void. + * Also, see {@link LiteralTreeAnnotator#addStandardLiteralQualifiers()}. * - * @param theQual the qualifier that a @DefaultFor annotation is written on - * @param defaultFor the @DefaultFor annotation written on {@code theQual} + * @return this */ - void add(AnnotationMirror theQual, DefaultFor defaultFor) { - if (defaultFor.names().length != 0) { - NameRegexes thisName = new NameRegexes(theQual); - for (String nameRegex : defaultFor.names()) { - try { - thisName.names.add(Pattern.compile(nameRegex)); - } catch (PatternSyntaxException e) { - throw new TypeSystemError( - "In annotation %s, names() value \"%s\" is not a regular" + " expression", - theQual, nameRegex); - } - } - for (String namesExceptionsRegex : defaultFor.namesExceptions()) { - try { - thisName.namesExceptions.add(Pattern.compile(namesExceptionsRegex)); - } catch (PatternSyntaxException e) { - throw new TypeSystemError( - "In annotation %s, namesExceptions() value \"%s\" is not a regular" + " expression", - theQual, namesExceptionsRegex); - } + public DefaultForTypeAnnotator addStandardDefaults() { + if (!types.containsKey(Void.class.getCanonicalName())) { + for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { + addTypes(Void.class, bottom); + } + } else { + AnnotationMirrorSet annos = types.get(Void.class.getCanonicalName()); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + if (qualHierarchy.findAnnotationInHierarchy(annos, top) == null) { + addTypes(Void.class, qualHierarchy.getBottomAnnotation(top)); + } + } } - add(thisName); - } else if (defaultFor.namesExceptions().length != 0) { - throw new TypeSystemError( - "On annotation %s, %s has empty names() but nonempty namesExceptions()", - theQual, defaultFor); - } + + return this; } /** - * Returns the annotation that should be the default for a variable of the given name, or for - * the return type of a method of the given name. + * Apply defaults based on a variable name to a type. * - * @param name a variable name - * @return the annotation that should be the default for a variable named {@code name}, or null - * if none + * @param type a type to apply defaults to + * @param name the name of the variable that has type {@code type}, or the name of the method + * whose return type is {@code type} */ - @Nullable AnnotationMirror getDefaultAnno(String name) { - if (this.isEmpty()) { - return null; - } - AnnotationMirror result = null; - for (NameRegexes nameRegexes : this) { - if (nameRegexes.matches(name)) { - if (result == null) { - result = nameRegexes.anno; - } else { - // This could combine the annotations instead, but I think doing so - // silently would confuse users. - throw new TypeSystemError( - "Multiple annotations are applicable to the name \"%s\"", name); - } + public void defaultTypeFromName(AnnotatedTypeMirror type, String name) { + // TODO: Check whether the annotation is applicable to this Java type? + AnnotationMirror defaultAnno = listOfNameRegexes.getDefaultAnno(name); + if (defaultAnno != null) { + if (atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(type.getAnnotations(), defaultAnno) + == null) { + type.addAnnotation(defaultAnno); + } } - } - return result; } - } - /** - * Associates an annotation with the variable names that cause the annotation to be chosen as a - * default. - */ - private static class NameRegexes { - /** The annotation. */ - final AnnotationMirror anno; + @Override + public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { + ExecutableElement element = type.getElement(); + + Iterator paramTypes = type.getParameterTypes().iterator(); + for (VariableElement paramElt : element.getParameters()) { + String paramName = paramElt.getSimpleName().toString(); + AnnotatedTypeMirror paramType = paramTypes.next(); + defaultTypeFromName(paramType, paramName); + } - /** The name regexes. */ - final List names = new ArrayList<>(0); + String methodName = element.getSimpleName().toString(); + AnnotatedTypeMirror returnType = type.getReturnType(); + defaultTypeFromName(returnType, methodName); - /** The name exception regexes. */ - final List namesExceptions = new ArrayList<>(0); + return super.visitExecutable(type, aVoid); + } /** - * Constructs a NameRegexes from a @DefaultFor annotation. - * - * @param theQual the qualifier that {@code defaultFor} is written on + * A list where each element associates an annotation with name regexes and name exception + * regexes. */ - NameRegexes(AnnotationMirror theQual) { - this.anno = theQual; + private static class ListOfNameRegexes extends ArrayList { + + static final long serialVersionUID = 20200218L; + + /** + * Update this list from the {@code names} and {@code namesExceptions} fields of + * a @DefaultFor annotation. + * + * @param theQual the qualifier that a @DefaultFor annotation is written on + * @param defaultFor the @DefaultFor annotation written on {@code theQual} + */ + void add(AnnotationMirror theQual, DefaultFor defaultFor) { + if (defaultFor.names().length != 0) { + NameRegexes thisName = new NameRegexes(theQual); + for (String nameRegex : defaultFor.names()) { + try { + thisName.names.add(Pattern.compile(nameRegex)); + } catch (PatternSyntaxException e) { + throw new TypeSystemError( + "In annotation %s, names() value \"%s\" is not a regular" + + " expression", + theQual, nameRegex); + } + } + for (String namesExceptionsRegex : defaultFor.namesExceptions()) { + try { + thisName.namesExceptions.add(Pattern.compile(namesExceptionsRegex)); + } catch (PatternSyntaxException e) { + throw new TypeSystemError( + "In annotation %s, namesExceptions() value \"%s\" is not a regular" + + " expression", + theQual, namesExceptionsRegex); + } + } + add(thisName); + } else if (defaultFor.namesExceptions().length != 0) { + throw new TypeSystemError( + "On annotation %s, %s has empty names() but nonempty namesExceptions()", + theQual, defaultFor); + } + } + + /** + * Returns the annotation that should be the default for a variable of the given name, or + * for the return type of a method of the given name. + * + * @param name a variable name + * @return the annotation that should be the default for a variable named {@code name}, or + * null if none + */ + @Nullable AnnotationMirror getDefaultAnno(String name) { + if (this.isEmpty()) { + return null; + } + AnnotationMirror result = null; + for (NameRegexes nameRegexes : this) { + if (nameRegexes.matches(name)) { + if (result == null) { + result = nameRegexes.anno; + } else { + // This could combine the annotations instead, but I think doing so + // silently would confuse users. + throw new TypeSystemError( + "Multiple annotations are applicable to the name \"%s\"", name); + } + } + } + return result; + } } /** - * Returns true if the regular expressions match the given name -- that is, if {@link #anno} - * should be used as the default type for a variable named {@code name}, or for the return type - * of a method named {@code name}. - * - * @param name a variable or method name - * @return true if {@link #anno} should be used as the default for a variable named {@code name} + * Associates an annotation with the variable names that cause the annotation to be chosen as a + * default. */ - public boolean matches(String name) { - return names.stream().anyMatch(p -> p.matcher(name).matches()) - && namesExceptions.stream().noneMatch(p -> p.matcher(name).matches()); + private static class NameRegexes { + /** The annotation. */ + final AnnotationMirror anno; + + /** The name regexes. */ + final List names = new ArrayList<>(0); + + /** The name exception regexes. */ + final List namesExceptions = new ArrayList<>(0); + + /** + * Constructs a NameRegexes from a @DefaultFor annotation. + * + * @param theQual the qualifier that {@code defaultFor} is written on + */ + NameRegexes(AnnotationMirror theQual) { + this.anno = theQual; + } + + /** + * Returns true if the regular expressions match the given name -- that is, if {@link #anno} + * should be used as the default type for a variable named {@code name}, or for the return + * type of a method named {@code name}. + * + * @param name a variable or method name + * @return true if {@link #anno} should be used as the default for a variable named {@code + * name} + */ + public boolean matches(String name) { + return names.stream().anyMatch(p -> p.matcher(name).matches()) + && namesExceptions.stream().noneMatch(p -> p.matcher(name).matches()); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java index 3a6e6bc6a80..29cae3aa003 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/DefaultQualifierForUseTypeAnnotator.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.type.typeannotator; -import java.util.List; -import java.util.Map; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.framework.qual.DefaultQualifierForUse; import org.checkerframework.framework.qual.NoDefaultQualifierForUse; @@ -19,161 +12,176 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.CollectionsPlume; +import java.util.List; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; + /** * Implements support for {@link DefaultQualifierForUse} and {@link NoDefaultQualifierForUse}. Adds * default annotations on types that have no annotation. */ public class DefaultQualifierForUseTypeAnnotator extends TypeAnnotator { - /** The DefaultQualifierForUse.value field/element. */ - private final ExecutableElement defaultQualifierForUseValueElement; - - /** The NoDefaultQualifierForUse.value field/element. */ - private final ExecutableElement noDefaultQualifierForUseValueElement; - - /** - * Creates an DefaultQualifierForUseTypeAnnotator for {@code typeFactory}. - * - * @param typeFactory the type factory - */ - public DefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - ProcessingEnvironment processingEnv = typeFactory.getProcessingEnv(); - defaultQualifierForUseValueElement = - TreeUtils.getMethod(DefaultQualifierForUse.class, "value", 0, processingEnv); - noDefaultQualifierForUseValueElement = - TreeUtils.getMethod(NoDefaultQualifierForUse.class, "value", 0, processingEnv); - } - - // There is no `visitPrimitive()` because `@DefaultQualifierForUse` is an annotation the goes on - // a type declaration. Defaults for primitives are add via the meta-annotation @DefaultFor, - // which is handled elsewhere. - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - Element element = type.getUnderlyingType().asElement(); - AnnotationMirrorSet annosToApply = getDefaultAnnosForUses(element); - type.addMissingAnnotations(annosToApply); - return super.visitDeclared(type, aVoid); - } - - /** - * Cache of elements to the set of annotations that should be applied to unannotated uses of the - * element. - */ - protected final Map elementToDefaults = - CollectionsPlume.createLruCache(100); - - /** Clears all caches. */ - public void clearCache() { - elementToDefaults.clear(); - } - - /** - * Returns the set of qualifiers that should be applied to unannotated uses of the given element - * - * @param element the element for which to determine default qualifiers - * @return the set of qualifiers that should be applied to unannotated uses of {@code element} - */ - protected AnnotationMirrorSet getDefaultAnnosForUses(Element element) { - if (atypeFactory.shouldCache && elementToDefaults.containsKey(element)) { - return elementToDefaults.get(element); + /** The DefaultQualifierForUse.value field/element. */ + private final ExecutableElement defaultQualifierForUseValueElement; + + /** The NoDefaultQualifierForUse.value field/element. */ + private final ExecutableElement noDefaultQualifierForUseValueElement; + + /** + * Creates an DefaultQualifierForUseTypeAnnotator for {@code typeFactory}. + * + * @param typeFactory the type factory + */ + public DefaultQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + ProcessingEnvironment processingEnv = typeFactory.getProcessingEnv(); + defaultQualifierForUseValueElement = + TreeUtils.getMethod(DefaultQualifierForUse.class, "value", 0, processingEnv); + noDefaultQualifierForUseValueElement = + TreeUtils.getMethod(NoDefaultQualifierForUse.class, "value", 0, processingEnv); } - AnnotationMirrorSet explictAnnos = getExplicitAnnos(element); - AnnotationMirrorSet defaultAnnos = getDefaultQualifierForUses(element); - AnnotationMirrorSet noDefaultAnnos = getHierarchiesNoDefault(element); - AnnotationMirrorSet annosToApply = new AnnotationMirrorSet(); - - for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) { - if (AnnotationUtils.containsSame(noDefaultAnnos, top)) { - continue; - } - AnnotationMirror defaultAnno = - atypeFactory.getQualifierHierarchy().findAnnotationInHierarchy(defaultAnnos, top); - if (defaultAnno != null) { - annosToApply.add(defaultAnno); - } else { - AnnotationMirror explict = - atypeFactory.getQualifierHierarchy().findAnnotationInHierarchy(explictAnnos, top); - if (explict != null) { - annosToApply.add(explict); + + // There is no `visitPrimitive()` because `@DefaultQualifierForUse` is an annotation the goes on + // a type declaration. Defaults for primitives are add via the meta-annotation @DefaultFor, + // which is handled elsewhere. + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { + Element element = type.getUnderlyingType().asElement(); + AnnotationMirrorSet annosToApply = getDefaultAnnosForUses(element); + type.addMissingAnnotations(annosToApply); + return super.visitDeclared(type, aVoid); + } + + /** + * Cache of elements to the set of annotations that should be applied to unannotated uses of the + * element. + */ + protected final Map elementToDefaults = + CollectionsPlume.createLruCache(100); + + /** Clears all caches. */ + public void clearCache() { + elementToDefaults.clear(); + } + + /** + * Returns the set of qualifiers that should be applied to unannotated uses of the given element + * + * @param element the element for which to determine default qualifiers + * @return the set of qualifiers that should be applied to unannotated uses of {@code element} + */ + protected AnnotationMirrorSet getDefaultAnnosForUses(Element element) { + if (atypeFactory.shouldCache && elementToDefaults.containsKey(element)) { + return elementToDefaults.get(element); + } + AnnotationMirrorSet explictAnnos = getExplicitAnnos(element); + AnnotationMirrorSet defaultAnnos = getDefaultQualifierForUses(element); + AnnotationMirrorSet noDefaultAnnos = getHierarchiesNoDefault(element); + AnnotationMirrorSet annosToApply = new AnnotationMirrorSet(); + + for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) { + if (AnnotationUtils.containsSame(noDefaultAnnos, top)) { + continue; + } + AnnotationMirror defaultAnno = + atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(defaultAnnos, top); + if (defaultAnno != null) { + annosToApply.add(defaultAnno); + } else { + AnnotationMirror explict = + atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy(explictAnnos, top); + if (explict != null) { + annosToApply.add(explict); + } + } } - } + // If parsing stub files, then the annosToApply is incomplete, so don't cache them. + if (atypeFactory.shouldCache + && !atypeFactory.stubTypes.isParsing() + && !atypeFactory.ajavaTypes.isParsing()) { + elementToDefaults.put(element, annosToApply); + } + return annosToApply; } - // If parsing stub files, then the annosToApply is incomplete, so don't cache them. - if (atypeFactory.shouldCache - && !atypeFactory.stubTypes.isParsing() - && !atypeFactory.ajavaTypes.isParsing()) { - elementToDefaults.put(element, annosToApply); + + /** + * Return the annotations explicitly written on the element. + * + * @param element an element + * @return the annotations explicitly written on the element + */ + protected AnnotationMirrorSet getExplicitAnnos(Element element) { + AnnotatedTypeMirror explicitAnnoOnDecl = atypeFactory.fromElement(element); + return explicitAnnoOnDecl.getAnnotations(); } - return annosToApply; - } - - /** - * Return the annotations explicitly written on the element. - * - * @param element an element - * @return the annotations explicitly written on the element - */ - protected AnnotationMirrorSet getExplicitAnnos(Element element) { - AnnotatedTypeMirror explicitAnnoOnDecl = atypeFactory.fromElement(element); - return explicitAnnoOnDecl.getAnnotations(); - } - - /** - * Return the default qualifiers for uses of {@code element} as specified by a {@link - * DefaultQualifierForUse} annotation. - * - *

          Subclasses may override to use an annotation other than {@link DefaultQualifierForUse}. - * - * @param element an element - * @return the default qualifiers for uses of {@code element} - */ - protected AnnotationMirrorSet getDefaultQualifierForUses(Element element) { - AnnotationMirror defaultQualifier = - atypeFactory.getDeclAnnotation(element, DefaultQualifierForUse.class); - if (defaultQualifier == null) { - return AnnotationMirrorSet.emptySet(); + + /** + * Return the default qualifiers for uses of {@code element} as specified by a {@link + * DefaultQualifierForUse} annotation. + * + *

          Subclasses may override to use an annotation other than {@link DefaultQualifierForUse}. + * + * @param element an element + * @return the default qualifiers for uses of {@code element} + */ + protected AnnotationMirrorSet getDefaultQualifierForUses(Element element) { + AnnotationMirror defaultQualifier = + atypeFactory.getDeclAnnotation(element, DefaultQualifierForUse.class); + if (defaultQualifier == null) { + return AnnotationMirrorSet.emptySet(); + } + return supportedAnnosFromAnnotationMirror( + AnnotationUtils.getElementValueClassNames( + defaultQualifier, defaultQualifierForUseValueElement)); } - return supportedAnnosFromAnnotationMirror( - AnnotationUtils.getElementValueClassNames( - defaultQualifier, defaultQualifierForUseValueElement)); - } - - /** - * Returns top annotations in hierarchies for which no default for use qualifier should be added. - * - * @param element an element - * @return top annotations in hierarchies for which no default for use qualifier should be added - */ - protected AnnotationMirrorSet getHierarchiesNoDefault(Element element) { - AnnotationMirror noDefaultQualifier = - atypeFactory.getDeclAnnotation(element, NoDefaultQualifierForUse.class); - if (noDefaultQualifier == null) { - return AnnotationMirrorSet.emptySet(); + + /** + * Returns top annotations in hierarchies for which no default for use qualifier should be + * added. + * + * @param element an element + * @return top annotations in hierarchies for which no default for use qualifier should be added + */ + protected AnnotationMirrorSet getHierarchiesNoDefault(Element element) { + AnnotationMirror noDefaultQualifier = + atypeFactory.getDeclAnnotation(element, NoDefaultQualifierForUse.class); + if (noDefaultQualifier == null) { + return AnnotationMirrorSet.emptySet(); + } + return supportedAnnosFromAnnotationMirror( + AnnotationUtils.getElementValueClassNames( + noDefaultQualifier, noDefaultQualifierForUseValueElement)); } - return supportedAnnosFromAnnotationMirror( - AnnotationUtils.getElementValueClassNames( - noDefaultQualifier, noDefaultQualifierForUseValueElement)); - } - - /** - * Returns the set of qualifiers supported by this type system from the value element of {@code - * annotationMirror}. - * - * @param annoClassNames a list of annotation class names - * @return the set of qualifiers supported by this type system from the value element of {@code - * annotationMirror} - */ - protected final AnnotationMirrorSet supportedAnnosFromAnnotationMirror( - List<@CanonicalName Name> annoClassNames) { - AnnotationMirrorSet supportAnnos = new AnnotationMirrorSet(); - for (Name annoName : annoClassNames) { - AnnotationMirror anno = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); - if (atypeFactory.isSupportedQualifier(anno)) { - supportAnnos.add(anno); - } + + /** + * Returns the set of qualifiers supported by this type system from the value element of {@code + * annotationMirror}. + * + * @param annoClassNames a list of annotation class names + * @return the set of qualifiers supported by this type system from the value element of {@code + * annotationMirror} + */ + protected final AnnotationMirrorSet supportedAnnosFromAnnotationMirror( + List<@CanonicalName Name> annoClassNames) { + AnnotationMirrorSet supportAnnos = new AnnotationMirrorSet(); + for (Name annoName : annoClassNames) { + AnnotationMirror anno = + AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); + if (atypeFactory.isSupportedQualifier(anno)) { + supportAnnos.add(anno); + } + } + return supportAnnos; } - return supportAnnos; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java index 311c341beaa..877f9797af4 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java @@ -1,83 +1,85 @@ package org.checkerframework.framework.type.typeannotator; -import javax.lang.model.type.TypeMirror; import org.checkerframework.framework.qual.RelevantJavaTypes; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.javacutil.BugInCF; +import javax.lang.model.type.TypeMirror; + /** * Adds annotations to types that are not relevant specified by the {@link RelevantJavaTypes} on a * checker. */ public class IrrelevantTypeAnnotator extends TypeAnnotator { - /** - * Annotate every type except for those whose underlying Java type is one of (or a subtype or - * supertype of) a class in relevantClasses. (Only adds annotationMirror if no annotation in the - * hierarchy are already on the type.) If relevantClasses includes Object[].class, then all arrays - * are considered relevant. - * - * @param atypeFactory a GenericAnnotatedTypeFactory - */ - @SuppressWarnings("rawtypes") - public IrrelevantTypeAnnotator(GenericAnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + /** + * Annotate every type except for those whose underlying Java type is one of (or a subtype or + * supertype of) a class in relevantClasses. (Only adds annotationMirror if no annotation in the + * hierarchy are already on the type.) If relevantClasses includes Object[].class, then all + * arrays are considered relevant. + * + * @param atypeFactory a GenericAnnotatedTypeFactory + */ + @SuppressWarnings("rawtypes") + public IrrelevantTypeAnnotator(GenericAnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - GenericAnnotatedTypeFactory gatf = (GenericAnnotatedTypeFactory) atypeFactory; + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + GenericAnnotatedTypeFactory gatf = (GenericAnnotatedTypeFactory) atypeFactory; - TypeMirror tm = type.getUnderlyingType(); - if (shouldAddPrimaryAnnotation(tm) && !gatf.isRelevant(tm)) { - type.addMissingAnnotations(gatf.annotationsForIrrelevantJavaType(type.getUnderlyingType())); - } + TypeMirror tm = type.getUnderlyingType(); + if (shouldAddPrimaryAnnotation(tm) && !gatf.isRelevant(tm)) { + type.addMissingAnnotations( + gatf.annotationsForIrrelevantJavaType(type.getUnderlyingType())); + } - return super.scan(type, aVoid); - } + return super.scan(type, aVoid); + } - /** - * Returns true if IrrelevantTypeAnnotator should add a primary annotation. - * - * @param tm a type mirror - * @return true if IrrelevantTypeAnnotator should add a primary annotation - */ - boolean shouldAddPrimaryAnnotation(TypeMirror tm) { - switch (tm.getKind()) { - case BOOLEAN: - case BYTE: - case CHAR: - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - return true; + /** + * Returns true if IrrelevantTypeAnnotator should add a primary annotation. + * + * @param tm a type mirror + * @return true if IrrelevantTypeAnnotator should add a primary annotation + */ + boolean shouldAddPrimaryAnnotation(TypeMirror tm) { + switch (tm.getKind()) { + case BOOLEAN: + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + return true; - case DECLARED: - return true; + case DECLARED: + return true; - case ARRAY: - return true; - case TYPEVAR: - case WILDCARD: - return false; + case ARRAY: + return true; + case TYPEVAR: + case WILDCARD: + return false; - case ERROR: - case EXECUTABLE: - case INTERSECTION: - case MODULE: - case NONE: - case NULL: - case OTHER: - case PACKAGE: - case UNION: - case VOID: - return false; + case ERROR: + case EXECUTABLE: + case INTERSECTION: + case MODULE: + case NONE: + case NULL: + case OTHER: + case PACKAGE: + case UNION: + case VOID: + return false; - default: - throw new BugInCF("Unknown type kind %s for %s", tm.getKind(), tm); + default: + throw new BugInCF("Unknown type kind %s for %s", tm.getKind(), tm); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java index 50e693bea4a..0de2e13c462 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/ListTypeAnnotator.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.type.typeannotator; +import org.checkerframework.framework.type.AnnotatedTypeMirror; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.checkerframework.framework.type.AnnotatedTypeMirror; /** * ListTypeAnnotator is a TypeAnnotator that executes a list of {@link TypeAnnotator} for each type @@ -18,50 +19,50 @@ */ public final class ListTypeAnnotator extends TypeAnnotator { - /** - * The annotators that will be executed for each type scanned by this TypeAnnotator. They are - * executed in order. - */ - private final List annotators; - - /** - * Create a new ListTypeAnnotator. - * - * @param annotators the annotators that will be executed for each type scanned by this - * TypeAnnotator. They are executed in the order passed in. - */ - public ListTypeAnnotator(TypeAnnotator... annotators) { - this(Arrays.asList(annotators)); - } + /** + * The annotators that will be executed for each type scanned by this TypeAnnotator. They are + * executed in order. + */ + private final List annotators; - /** - * @param annotators the annotators that will be executed for each type scanned by this - * TypeAnnotator. They are executed in the order passed in. - */ - public ListTypeAnnotator(List annotators) { - super(null); - List annotatorList = new ArrayList<>(annotators.size()); - for (TypeAnnotator annotator : annotators) { - if (annotator instanceof ListTypeAnnotator) { - annotatorList.addAll(((ListTypeAnnotator) annotator).annotators); - } else { - annotatorList.add(annotator); - } + /** + * Create a new ListTypeAnnotator. + * + * @param annotators the annotators that will be executed for each type scanned by this + * TypeAnnotator. They are executed in the order passed in. + */ + public ListTypeAnnotator(TypeAnnotator... annotators) { + this(Arrays.asList(annotators)); } - this.annotators = Collections.unmodifiableList(annotatorList); - } - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - for (TypeAnnotator annotator : annotators) { - annotator.visit(type, aVoid); + /** + * @param annotators the annotators that will be executed for each type scanned by this + * TypeAnnotator. They are executed in the order passed in. + */ + public ListTypeAnnotator(List annotators) { + super(null); + List annotatorList = new ArrayList<>(annotators.size()); + for (TypeAnnotator annotator : annotators) { + if (annotator instanceof ListTypeAnnotator) { + annotatorList.addAll(((ListTypeAnnotator) annotator).annotators); + } else { + annotatorList.add(annotator); + } + } + this.annotators = Collections.unmodifiableList(annotatorList); } - return null; - } + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + for (TypeAnnotator annotator : annotators) { + annotator.visit(type, aVoid); + } - @Override - public String toString() { - return "ListTypeAnnotator" + annotators; - } + return null; + } + + @Override + public String toString() { + return "ListTypeAnnotator" + annotators; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java index 37bcd489197..b432c3ae96b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.type.typeannotator; -import java.util.ArrayDeque; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -19,6 +12,15 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.StringsPlume; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; + /** * {@link PropagationTypeAnnotator} adds qualifiers to types where the qualifier to add should be * transferred from one or more other types. @@ -33,191 +35,194 @@ */ public class PropagationTypeAnnotator extends TypeAnnotator { - /** - * The PropagationTypeAnnotator is called recursively via - * TypeAnnotatorUtil.eraseBoundsThenAnnotate. This flag prevents infinite recursion. - */ - private boolean pause = false; - - /** The parents. */ - private final ArrayDeque parents = new ArrayDeque<>(); - - /** - * Creates a new PropagationTypeAnnotator. - * - * @param typeFactory the type factory - */ - public PropagationTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - } - - @Override - public void reset() { - if (!pause) { - // when the PropagationTypeAnnotator is called recursively we don't - // want the visit method to reset the list of visited types - super.reset(); - } - } - - /* - * When pause == true, the PropagationTypeAnnotator caused a recursive call - * and there is no need to execute the PropagationTypeAnnotator - */ - @Override - protected Void scan(AnnotatedTypeMirror type, Void aVoid) { - if (pause) { - return null; + /** + * The PropagationTypeAnnotator is called recursively via + * TypeAnnotatorUtil.eraseBoundsThenAnnotate. This flag prevents infinite recursion. + */ + private boolean pause = false; + + /** The parents. */ + private final ArrayDeque parents = new ArrayDeque<>(); + + /** + * Creates a new PropagationTypeAnnotator. + * + * @param typeFactory the type factory + */ + public PropagationTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); } - return super.scan(type, aVoid); - } - - /** - * Sometimes the underlying type parameters of AnnotatedWildcardTypes are not available on the - * wildcards themselves. Instead, record enclosing class to find the type parameter to use as a - * backup in visitWildcards. - * - * @param declaredType type to record - */ - @Override - public Void visitDeclared(AnnotatedDeclaredType declaredType, Void aVoid) { - if (pause) { - return null; + @Override + public void reset() { + if (!pause) { + // when the PropagationTypeAnnotator is called recursively we don't + // want the visit method to reset the list of visited types + super.reset(); + } } - if (declaredType.isUnderlyingTypeRaw()) { - // Copy annotations from the declaration to the wildcards. - AnnotatedDeclaredType declaration = - (AnnotatedDeclaredType) - atypeFactory.fromElement(declaredType.getUnderlyingType().asElement()); - List typeArgs = declaredType.getTypeArguments(); - for (int i = 0; i < typeArgs.size(); i++) { - if (!AnnotatedTypes.isTypeArgOfRawType(typeArgs.get(i))) { - // Sometimes the framework infers a more precise type argument, so just use it. - continue; + + /* + * When pause == true, the PropagationTypeAnnotator caused a recursive call + * and there is no need to execute the PropagationTypeAnnotator + */ + @Override + protected Void scan(AnnotatedTypeMirror type, Void aVoid) { + if (pause) { + return null; } - AnnotatedTypeVariable typeParam = - (AnnotatedTypeVariable) declaration.getTypeArguments().get(i); - AnnotatedWildcardType wct = (AnnotatedWildcardType) typeArgs.get(i); - wct.getExtendsBound().replaceAnnotations(typeParam.getUpperBound().getAnnotations()); - wct.getSuperBound().replaceAnnotations(typeParam.getLowerBound().getAnnotations()); - wct.replaceAnnotations(typeParam.getAnnotations()); - } + + return super.scan(type, aVoid); } - parents.addFirst(declaredType); - super.visitDeclared(declaredType, aVoid); - parents.removeFirst(); - return null; - } - - /** - * Rather than defaulting the missing bounds of a wildcard, find the bound annotations on the type - * parameter it replaced. Place those annotations on the wildcard. - * - * @param wildcard type to annotate - */ - @Override - public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { - if (visitedNodes.containsKey(wildcard) || pause) { - return null; + /** + * Sometimes the underlying type parameters of AnnotatedWildcardTypes are not available on the + * wildcards themselves. Instead, record enclosing class to find the type parameter to use as a + * backup in visitWildcards. + * + * @param declaredType type to record + */ + @Override + public Void visitDeclared(AnnotatedDeclaredType declaredType, Void aVoid) { + if (pause) { + return null; + } + if (declaredType.isUnderlyingTypeRaw()) { + // Copy annotations from the declaration to the wildcards. + AnnotatedDeclaredType declaration = + (AnnotatedDeclaredType) + atypeFactory.fromElement(declaredType.getUnderlyingType().asElement()); + List typeArgs = declaredType.getTypeArguments(); + for (int i = 0; i < typeArgs.size(); i++) { + if (!AnnotatedTypes.isTypeArgOfRawType(typeArgs.get(i))) { + // Sometimes the framework infers a more precise type argument, so just use it. + continue; + } + AnnotatedTypeVariable typeParam = + (AnnotatedTypeVariable) declaration.getTypeArguments().get(i); + AnnotatedWildcardType wct = (AnnotatedWildcardType) typeArgs.get(i); + wct.getExtendsBound() + .replaceAnnotations(typeParam.getUpperBound().getAnnotations()); + wct.getSuperBound().replaceAnnotations(typeParam.getLowerBound().getAnnotations()); + wct.replaceAnnotations(typeParam.getAnnotations()); + } + } + + parents.addFirst(declaredType); + super.visitDeclared(declaredType, aVoid); + parents.removeFirst(); + return null; } - visitedNodes.put(wildcard, null); - Element typeParamElement = TypesUtils.wildcardToTypeParam(wildcard.getUnderlyingType()); - if (typeParamElement == null && !parents.isEmpty()) { - typeParamElement = getTypeParameterElement(wildcard, parents.peekFirst()); + /** + * Rather than defaulting the missing bounds of a wildcard, find the bound annotations on the + * type parameter it replaced. Place those annotations on the wildcard. + * + * @param wildcard type to annotate + */ + @Override + public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { + if (visitedNodes.containsKey(wildcard) || pause) { + return null; + } + visitedNodes.put(wildcard, null); + + Element typeParamElement = TypesUtils.wildcardToTypeParam(wildcard.getUnderlyingType()); + if (typeParamElement == null && !parents.isEmpty()) { + typeParamElement = getTypeParameterElement(wildcard, parents.peekFirst()); + } + + if (typeParamElement != null) { + pause = true; + AnnotatedTypeVariable typeParam = + (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeParamElement); + pause = false; + + AnnotationMirrorSet tops = atypeFactory.getQualifierHierarchy().getTopAnnotations(); + + if (AnnotatedTypes.hasNoExplicitBound(wildcard)) { + propagateExtendsBound(wildcard, typeParam, tops); + propagateSuperBound(wildcard, typeParam, tops); + } else if (AnnotatedTypes.hasExplicitExtendsBound(wildcard)) { + propagateSuperBound(wildcard, typeParam, tops); + } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { + propagateExtendsBound(wildcard, typeParam, tops); + } else { + // If this is thrown, then it means that there's a bug in one of the + // AnnotatedTypes.hasNoExplicit*Bound methods. Probably something changed in the + // javac implementation. + throw new BugInCF( + "Wildcard is neither unbound nor does it have an explicit bound."); + } + } + scan(wildcard.getExtendsBound(), null); + scan(wildcard.getSuperBound(), null); + return null; } - if (typeParamElement != null) { - pause = true; - AnnotatedTypeVariable typeParam = - (AnnotatedTypeVariable) atypeFactory.getAnnotatedType(typeParamElement); - pause = false; - - AnnotationMirrorSet tops = atypeFactory.getQualifierHierarchy().getTopAnnotations(); - - if (AnnotatedTypes.hasNoExplicitBound(wildcard)) { - propagateExtendsBound(wildcard, typeParam, tops); - propagateSuperBound(wildcard, typeParam, tops); - } else if (AnnotatedTypes.hasExplicitExtendsBound(wildcard)) { - propagateSuperBound(wildcard, typeParam, tops); - } else if (AnnotatedTypes.hasExplicitSuperBound(wildcard)) { - propagateExtendsBound(wildcard, typeParam, tops); - } else { - // If this is thrown, then it means that there's a bug in one of the - // AnnotatedTypes.hasNoExplicit*Bound methods. Probably something changed in the - // javac implementation. - throw new BugInCF("Wildcard is neither unbound nor does it have an explicit bound."); - } + private void propagateSuperBound( + AnnotatedWildcardType wildcard, + AnnotatedTypeVariable typeParam, + Set tops) { + applyAnnosFromBound(wildcard.getSuperBound(), typeParam.getLowerBound(), tops); } - scan(wildcard.getExtendsBound(), null); - scan(wildcard.getSuperBound(), null); - return null; - } - - private void propagateSuperBound( - AnnotatedWildcardType wildcard, - AnnotatedTypeVariable typeParam, - Set tops) { - applyAnnosFromBound(wildcard.getSuperBound(), typeParam.getLowerBound(), tops); - } - - private void propagateExtendsBound( - AnnotatedWildcardType wildcard, - AnnotatedTypeVariable typeParam, - Set tops) { - applyAnnosFromBound(wildcard.getExtendsBound(), typeParam.getUpperBound(), tops); - } - - /** - * Take the primary annotations from typeParamBound and place them as primary annotations on - * wildcard bound. - */ - private void applyAnnosFromBound( - AnnotatedTypeMirror wildcardBound, - AnnotatedTypeMirror typeParamBound, - Set tops) { - // Type variables do not need primary annotations. - // The type variable will have annotations placed on its - // bounds via its declaration or defaulting rules - if (wildcardBound.getKind() == TypeKind.TYPEVAR - || typeParamBound.getKind() == TypeKind.TYPEVAR) { - return; + + private void propagateExtendsBound( + AnnotatedWildcardType wildcard, + AnnotatedTypeVariable typeParam, + Set tops) { + applyAnnosFromBound(wildcard.getExtendsBound(), typeParam.getUpperBound(), tops); } - for (AnnotationMirror top : tops) { - if (wildcardBound.getAnnotationInHierarchy(top) == null) { - AnnotationMirror typeParamAnno = typeParamBound.getAnnotationInHierarchy(top); - if (typeParamAnno == null) { - throw new BugInCF( - StringsPlume.joinLines( - "Missing annotation on type parameter", - "top=" + top, - "wildcardBound=" + wildcardBound, - "typeParamBound=" + typeParamBound)); - } // else - wildcardBound.addAnnotation(typeParamAnno); - } + /** + * Take the primary annotations from typeParamBound and place them as primary annotations on + * wildcard bound. + */ + private void applyAnnosFromBound( + AnnotatedTypeMirror wildcardBound, + AnnotatedTypeMirror typeParamBound, + Set tops) { + // Type variables do not need primary annotations. + // The type variable will have annotations placed on its + // bounds via its declaration or defaulting rules + if (wildcardBound.getKind() == TypeKind.TYPEVAR + || typeParamBound.getKind() == TypeKind.TYPEVAR) { + return; + } + + for (AnnotationMirror top : tops) { + if (wildcardBound.getAnnotationInHierarchy(top) == null) { + AnnotationMirror typeParamAnno = typeParamBound.getAnnotationInHierarchy(top); + if (typeParamAnno == null) { + throw new BugInCF( + StringsPlume.joinLines( + "Missing annotation on type parameter", + "top=" + top, + "wildcardBound=" + wildcardBound, + "typeParamBound=" + typeParamBound)); + } // else + wildcardBound.addAnnotation(typeParamAnno); + } + } } - } - - /** - * Search {@code declaredType}'s type arguments for {@code typeArg}. Using the index of {@code - * typeArg}, find the corresponding type parameter element and return it. - * - * @param typeArg a typeArg of {@code declaredType} - * @param declaredType the type in which {@code typeArg} is a type argument - * @return the type parameter in {@code declaredType} that corresponds to {@code typeArg} - */ - private Element getTypeParameterElement( - @FindDistinct AnnotatedTypeMirror typeArg, AnnotatedDeclaredType declaredType) { - for (int i = 0; i < declaredType.getTypeArguments().size(); i++) { - if (declaredType.getTypeArguments().get(i) == typeArg) { - TypeElement typeElement = TypesUtils.getTypeElement(declaredType.getUnderlyingType()); - return typeElement.getTypeParameters().get(i); - } + + /** + * Search {@code declaredType}'s type arguments for {@code typeArg}. Using the index of {@code + * typeArg}, find the corresponding type parameter element and return it. + * + * @param typeArg a typeArg of {@code declaredType} + * @param declaredType the type in which {@code typeArg} is a type argument + * @return the type parameter in {@code declaredType} that corresponds to {@code typeArg} + */ + private Element getTypeParameterElement( + @FindDistinct AnnotatedTypeMirror typeArg, AnnotatedDeclaredType declaredType) { + for (int i = 0; i < declaredType.getTypeArguments().size(); i++) { + if (declaredType.getTypeArguments().get(i) == typeArg) { + TypeElement typeElement = + TypesUtils.getTypeElement(declaredType.getUnderlyingType()); + return typeElement.getTypeParameters().get(i); + } + } + throw new BugInCF("Wildcard %s is not a type argument of %s", typeArg, declaredType); } - throw new BugInCF("Wildcard %s is not a type argument of %s", typeArg, declaredType); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java index 37d29ef448a..d9cd5f1db99 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/TypeAnnotator.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.type.typeannotator; -import javax.lang.model.element.Element; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; +import javax.lang.model.element.Element; + /** * {@link TypeAnnotator} is an abstract AnnotatedTypeScanner to be used with {@link * ListTypeAnnotator}. @@ -16,29 +17,29 @@ */ public abstract class TypeAnnotator extends AnnotatedTypeScanner { - /** The type factory. */ - protected final AnnotatedTypeFactory atypeFactory; + /** The type factory. */ + protected final AnnotatedTypeFactory atypeFactory; - /** - * Creates a new TypeAnnotator. - * - * @param atypeFactory the type factory - */ - protected TypeAnnotator(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - } + /** + * Creates a new TypeAnnotator. + * + * @param atypeFactory the type factory + */ + protected TypeAnnotator(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + } - /** - * {@inheritDoc} - * - *

          If this method adds annotations to the type of method parameters, then {@link - * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Element, - * AnnotatedTypeMirror)} should be overridden and the same annotations added to the type of - * elements with kind {@link javax.lang.model.element.ElementKind#PARAMETER}. Likewise for return - * types. - */ - @Override - public Void visitExecutable(AnnotatedExecutableType method, Void aVoid) { - return super.visitExecutable(method, aVoid); - } + /** + * {@inheritDoc} + * + *

          If this method adds annotations to the type of method parameters, then {@link + * org.checkerframework.framework.type.GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Element, + * AnnotatedTypeMirror)} should be overridden and the same annotations added to the type of + * elements with kind {@link javax.lang.model.element.ElementKind#PARAMETER}. Likewise for + * return types. + */ + @Override + public Void visitExecutable(AnnotatedExecutableType method, Void aVoid) { + return super.visitExecutable(method, aVoid); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java index cd278c12e2e..843c9570408 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AbstractAtmComboVisitor.java @@ -20,603 +20,608 @@ *

          This class does no traversal. */ public abstract class AbstractAtmComboVisitor - implements AtmComboVisitor { - - /** - * Dispatches to a more specific {@code visit*} method. - * - * @param type1 the first type to visit - * @param type2 the second type to visit - * @param param a value passed to every visit method - * @return the result of calling the more specific {@code visit*} method - */ - public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - return AtmCombo.accept(type1, type2, param, this); - } - - @Override - public RETURN_TYPE visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Declared( - AnnotatedArrayType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Executable( - AnnotatedArrayType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Intersection( - AnnotatedArrayType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_None(AnnotatedArrayType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Null( - AnnotatedArrayType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Primitive( - AnnotatedArrayType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Typevar( - AnnotatedArrayType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Union( - AnnotatedArrayType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitArray_Wildcard( - AnnotatedArrayType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Array( - AnnotatedDeclaredType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Executable( - AnnotatedDeclaredType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Intersection( - AnnotatedDeclaredType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_None( - AnnotatedDeclaredType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Null( - AnnotatedDeclaredType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Primitive( - AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Typevar( - AnnotatedDeclaredType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Union( - AnnotatedDeclaredType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitDeclared_Wildcard( - AnnotatedDeclaredType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Array( - AnnotatedExecutableType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Declared( - AnnotatedExecutableType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Executable( - AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Intersection( - AnnotatedExecutableType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_None( - AnnotatedExecutableType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Null( - AnnotatedExecutableType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Primitive( - AnnotatedExecutableType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Typevar( - AnnotatedExecutableType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Union( - AnnotatedExecutableType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitExecutable_Wildcard( - AnnotatedExecutableType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Array( - AnnotatedIntersectionType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Declared( - AnnotatedIntersectionType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Executable( - AnnotatedIntersectionType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_None( - AnnotatedIntersectionType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Null( - AnnotatedIntersectionType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Primitive( - AnnotatedIntersectionType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Typevar( - AnnotatedIntersectionType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Union( - AnnotatedIntersectionType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitIntersection_Wildcard( - AnnotatedIntersectionType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Array(AnnotatedNoType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Declared( - AnnotatedNoType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Executable( - AnnotatedNoType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Intersection( - AnnotatedNoType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_None(AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Null(AnnotatedNoType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Primitive( - AnnotatedNoType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Union(AnnotatedNoType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNone_Wildcard( - AnnotatedNoType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Array( - AnnotatedNullType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Declared( - AnnotatedNullType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Executable( - AnnotatedNullType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Intersection( - AnnotatedNullType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_None(AnnotatedNullType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Null(AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Primitive( - AnnotatedNullType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Typevar( - AnnotatedNullType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Union( - AnnotatedNullType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitNull_Wildcard( - AnnotatedNullType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Array( - AnnotatedPrimitiveType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Declared( - AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Executable( - AnnotatedPrimitiveType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Intersection( - AnnotatedPrimitiveType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_None( - AnnotatedPrimitiveType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Null( - AnnotatedPrimitiveType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Typevar( - AnnotatedPrimitiveType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Union( - AnnotatedPrimitiveType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitPrimitive_Wildcard( - AnnotatedPrimitiveType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Array( - AnnotatedUnionType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Declared( - AnnotatedUnionType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Executable( - AnnotatedUnionType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Intersection( - AnnotatedUnionType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_None(AnnotatedUnionType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Null( - AnnotatedUnionType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Primitive( - AnnotatedUnionType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Typevar( - AnnotatedUnionType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitUnion_Wildcard( - AnnotatedUnionType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Array( - AnnotatedTypeVariable type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Declared( - AnnotatedTypeVariable type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Executable( - AnnotatedTypeVariable type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Intersection( - AnnotatedTypeVariable type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_None( - AnnotatedTypeVariable type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Null( - AnnotatedTypeVariable type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Primitive( - AnnotatedTypeVariable type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Union( - AnnotatedTypeVariable type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitTypevar_Wildcard( - AnnotatedTypeVariable type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Array( - AnnotatedWildcardType type1, AnnotatedArrayType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Declared( - AnnotatedWildcardType type1, AnnotatedDeclaredType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Executable( - AnnotatedWildcardType type1, AnnotatedExecutableType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Intersection( - AnnotatedWildcardType type1, AnnotatedIntersectionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_None( - AnnotatedWildcardType type1, AnnotatedNoType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Null( - AnnotatedWildcardType type1, AnnotatedNullType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Primitive( - AnnotatedWildcardType type1, AnnotatedPrimitiveType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Typevar( - AnnotatedWildcardType type1, AnnotatedTypeVariable type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Union( - AnnotatedWildcardType type1, AnnotatedUnionType type2, PARAM param) { - return defaultAction(type1, type2, param); - } - - @Override - public RETURN_TYPE visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { - return defaultAction(type1, type2, param); - } + implements AtmComboVisitor { + + /** + * Dispatches to a more specific {@code visit*} method. + * + * @param type1 the first type to visit + * @param type2 the second type to visit + * @param param a value passed to every visit method + * @return the result of calling the more specific {@code visit*} method + */ + public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + return AtmCombo.accept(type1, type2, param, this); + } + + @Override + public RETURN_TYPE visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Declared( + AnnotatedArrayType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Executable( + AnnotatedArrayType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Intersection( + AnnotatedArrayType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_None( + AnnotatedArrayType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Null( + AnnotatedArrayType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Primitive( + AnnotatedArrayType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Typevar( + AnnotatedArrayType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Union( + AnnotatedArrayType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitArray_Wildcard( + AnnotatedArrayType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Array( + AnnotatedDeclaredType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Executable( + AnnotatedDeclaredType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Intersection( + AnnotatedDeclaredType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_None( + AnnotatedDeclaredType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Null( + AnnotatedDeclaredType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Primitive( + AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Typevar( + AnnotatedDeclaredType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Union( + AnnotatedDeclaredType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitDeclared_Wildcard( + AnnotatedDeclaredType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Array( + AnnotatedExecutableType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Declared( + AnnotatedExecutableType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Executable( + AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Intersection( + AnnotatedExecutableType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_None( + AnnotatedExecutableType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Null( + AnnotatedExecutableType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Primitive( + AnnotatedExecutableType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Typevar( + AnnotatedExecutableType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Union( + AnnotatedExecutableType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitExecutable_Wildcard( + AnnotatedExecutableType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Array( + AnnotatedIntersectionType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Declared( + AnnotatedIntersectionType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Executable( + AnnotatedIntersectionType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_None( + AnnotatedIntersectionType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Null( + AnnotatedIntersectionType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Primitive( + AnnotatedIntersectionType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Typevar( + AnnotatedIntersectionType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Union( + AnnotatedIntersectionType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitIntersection_Wildcard( + AnnotatedIntersectionType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Array( + AnnotatedNoType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Declared( + AnnotatedNoType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Executable( + AnnotatedNoType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Intersection( + AnnotatedNoType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_None(AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Null(AnnotatedNoType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Primitive( + AnnotatedNoType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Union( + AnnotatedNoType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNone_Wildcard( + AnnotatedNoType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Array( + AnnotatedNullType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Declared( + AnnotatedNullType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Executable( + AnnotatedNullType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Intersection( + AnnotatedNullType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_None(AnnotatedNullType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Null( + AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Primitive( + AnnotatedNullType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Typevar( + AnnotatedNullType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Union( + AnnotatedNullType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitNull_Wildcard( + AnnotatedNullType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Array( + AnnotatedPrimitiveType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Declared( + AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Executable( + AnnotatedPrimitiveType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Intersection( + AnnotatedPrimitiveType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_None( + AnnotatedPrimitiveType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Null( + AnnotatedPrimitiveType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Typevar( + AnnotatedPrimitiveType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Union( + AnnotatedPrimitiveType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitPrimitive_Wildcard( + AnnotatedPrimitiveType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Array( + AnnotatedUnionType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Declared( + AnnotatedUnionType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Executable( + AnnotatedUnionType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Intersection( + AnnotatedUnionType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_None( + AnnotatedUnionType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Null( + AnnotatedUnionType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Primitive( + AnnotatedUnionType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Typevar( + AnnotatedUnionType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitUnion_Wildcard( + AnnotatedUnionType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Array( + AnnotatedTypeVariable type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Declared( + AnnotatedTypeVariable type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Executable( + AnnotatedTypeVariable type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Intersection( + AnnotatedTypeVariable type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_None( + AnnotatedTypeVariable type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Null( + AnnotatedTypeVariable type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Primitive( + AnnotatedTypeVariable type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Union( + AnnotatedTypeVariable type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitTypevar_Wildcard( + AnnotatedTypeVariable type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Array( + AnnotatedWildcardType type1, AnnotatedArrayType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Declared( + AnnotatedWildcardType type1, AnnotatedDeclaredType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Executable( + AnnotatedWildcardType type1, AnnotatedExecutableType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Intersection( + AnnotatedWildcardType type1, AnnotatedIntersectionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_None( + AnnotatedWildcardType type1, AnnotatedNoType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Null( + AnnotatedWildcardType type1, AnnotatedNullType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Primitive( + AnnotatedWildcardType type1, AnnotatedPrimitiveType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Typevar( + AnnotatedWildcardType type1, AnnotatedTypeVariable type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Union( + AnnotatedWildcardType type1, AnnotatedUnionType type2, PARAM param) { + return defaultAction(type1, type2, param); + } + + @Override + public RETURN_TYPE visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { + return defaultAction(type1, type2, param); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java index 699f7fa87f6..5cbaeb9674f 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java @@ -1,76 +1,77 @@ package org.checkerframework.framework.type.visitor; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.BugInCF; +import javax.lang.model.element.AnnotationMirror; + /** Changes each parameter type to be the GLB of the parameter type and visited type. */ public class AnnotatedTypeCombiner extends DoubleAnnotatedTypeScanner { - /** - * Combines all annotations from {@code from} and {@code to} into {@code to} using the GLB. - * - * @param from the annotated type mirror from which to take annotations - * @param to the annotated type mirror into which annotations should be combined - * @param hierarchy the top type of the hierarchy whose annotations should be combined - */ - @SuppressWarnings("interning:not.interned") // assertion - public static void combine( - AnnotatedTypeMirror from, AnnotatedTypeMirror to, QualifierHierarchy hierarchy) { - if (from == to) { - throw new BugInCF("from == to: %s", from); + /** + * Combines all annotations from {@code from} and {@code to} into {@code to} using the GLB. + * + * @param from the annotated type mirror from which to take annotations + * @param to the annotated type mirror into which annotations should be combined + * @param hierarchy the top type of the hierarchy whose annotations should be combined + */ + @SuppressWarnings("interning:not.interned") // assertion + public static void combine( + AnnotatedTypeMirror from, AnnotatedTypeMirror to, QualifierHierarchy hierarchy) { + if (from == to) { + throw new BugInCF("from == to: %s", from); + } + new AnnotatedTypeCombiner(hierarchy).visit(from, to); } - new AnnotatedTypeCombiner(hierarchy).visit(from, to); - } - /** The hierarchy used to compute the GLB. */ - private final QualifierHierarchy hierarchy; + /** The hierarchy used to compute the GLB. */ + private final QualifierHierarchy hierarchy; - /** - * Create an AnnotatedTypeCombiner. - * - * @param hierarchy the hierarchy used to the compute the GLB - */ - public AnnotatedTypeCombiner(QualifierHierarchy hierarchy) { - this.hierarchy = hierarchy; - } + /** + * Create an AnnotatedTypeCombiner. + * + * @param hierarchy the hierarchy used to the compute the GLB + */ + public AnnotatedTypeCombiner(QualifierHierarchy hierarchy) { + this.hierarchy = hierarchy; + } - @Override - @SuppressWarnings("interning:not.interned") // assertion - protected Void defaultAction(AnnotatedTypeMirror one, AnnotatedTypeMirror two) { - assert one != two; - if (one != null && two != null) { - combineAnnotations(one, two); + @Override + @SuppressWarnings("interning:not.interned") // assertion + protected Void defaultAction(AnnotatedTypeMirror one, AnnotatedTypeMirror two) { + assert one != two; + if (one != null && two != null) { + combineAnnotations(one, two); + } + return null; } - return null; - } - /** - * Computes the greatest lower bound of each set of annotations shared by from and to, and - * replaces the annotations in to with the result. - * - * @param from the first set of annotations - * @param to the second set of annotations. This is modified by side-effect to hold the result. - */ - protected void combineAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + /** + * Computes the greatest lower bound of each set of annotations shared by from and to, and + * replaces the annotations in to with the result. + * + * @param from the first set of annotations + * @param to the second set of annotations. This is modified by side-effect to hold the result. + */ + protected void combineAnnotations(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - AnnotationMirrorSet combinedAnnotations = new AnnotationMirrorSet(); + AnnotationMirrorSet combinedAnnotations = new AnnotationMirrorSet(); - for (AnnotationMirror top : hierarchy.getTopAnnotations()) { - AnnotationMirror aFrom = from.getAnnotationInHierarchy(top); - AnnotationMirror aTo = to.getAnnotationInHierarchy(top); - if (aFrom != null && aTo != null) { - combinedAnnotations.add( - hierarchy.greatestLowerBoundShallow( - aFrom, from.getUnderlyingType(), aTo, to.getUnderlyingType())); - } else if (aFrom != null) { - combinedAnnotations.add(aFrom); - } else if (aTo != null) { - combinedAnnotations.add(aTo); - } + for (AnnotationMirror top : hierarchy.getTopAnnotations()) { + AnnotationMirror aFrom = from.getAnnotationInHierarchy(top); + AnnotationMirror aTo = to.getAnnotationInHierarchy(top); + if (aFrom != null && aTo != null) { + combinedAnnotations.add( + hierarchy.greatestLowerBoundShallow( + aFrom, from.getUnderlyingType(), aTo, to.getUnderlyingType())); + } else if (aFrom != null) { + combinedAnnotations.add(aFrom); + } else if (aTo != null) { + combinedAnnotations.add(aTo); + } + } + to.replaceAnnotations(combinedAnnotations); } - to.replaceAnnotations(combinedAnnotations); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java index c29fdb75bbb..8d7300c7b20 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeScanner.java @@ -1,6 +1,5 @@ package org.checkerframework.framework.type.visitor; -import java.util.IdentityHashMap; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -14,6 +13,8 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import java.util.IdentityHashMap; + /** * An {@code AnnotatedTypeScanner} visits an {@link AnnotatedTypeMirror} and all of its child {@link * AnnotatedTypeMirror}s and performs some function depending on the kind of type. (By contrast, a @@ -87,277 +88,277 @@ */ public abstract class AnnotatedTypeScanner implements AnnotatedTypeVisitor { - /** - * Reduces two results into a single result. - * - * @param the result type - */ - @FunctionalInterface - public interface Reduce { - /** - * Returns the combination of two results. + * Reduces two results into a single result. * - * @param r1 the first result - * @param r2 the second result - * @return the combination of the two results + * @param the result type */ - R reduce(R r1, R r2); - } + @FunctionalInterface + public interface Reduce { - /** The reduce function to use. */ - protected final Reduce reduceFunction; - - /** The result to return if no other result is provided. It should be immutable. */ - protected final R defaultResult; - - /** - * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is - * null, then the reduce function returns the first result if it is nonnull; otherwise the second - * result is returned. - * - * @param reduceFunction function used to combine two results - * @param defaultResult the result to return if a visit type method is not overridden; it should - * be immutable - */ - protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction, R defaultResult) { - if (reduceFunction == null) { - this.reduceFunction = (r1, r2) -> r1 == null ? r2 : r1; - } else { - this.reduceFunction = reduceFunction; + /** + * Returns the combination of two results. + * + * @param r1 the first result + * @param r2 the second result + * @return the combination of the two results + */ + R reduce(R r1, R r2); } - this.defaultResult = defaultResult; - } - /** - * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is - * null, then the reduce function returns the first result if it is nonnull; otherwise the second - * result is returned. The default result is {@code null} - * - * @param reduceFunction function used to combine two results - */ - protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction) { - this(reduceFunction, null); - } + /** The reduce function to use. */ + protected final Reduce reduceFunction; - /** - * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is - * nonnull; otherwise the second result is returned. - * - * @param defaultResult the result to return if a visit type method is not overridden; it should - * be immutable - */ - protected AnnotatedTypeScanner(R defaultResult) { - this(null, defaultResult); - } + /** The result to return if no other result is provided. It should be immutable. */ + protected final R defaultResult; - /** - * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is - * nonnull; otherwise the second result is returned. The default result is {@code null}. - */ - protected AnnotatedTypeScanner() { - this(null, null); - } - - // To prevent infinite loops - protected final IdentityHashMap visitedNodes = new IdentityHashMap<>(); + /** + * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} + * is null, then the reduce function returns the first result if it is nonnull; otherwise the + * second result is returned. + * + * @param reduceFunction function used to combine two results + * @param defaultResult the result to return if a visit type method is not overridden; it should + * be immutable + */ + protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction, R defaultResult) { + if (reduceFunction == null) { + this.reduceFunction = (r1, r2) -> r1 == null ? r2 : r1; + } else { + this.reduceFunction = reduceFunction; + } + this.defaultResult = defaultResult; + } - /** - * Reset the scanner to allow reuse of the same instance. Subclasses should override this method - * to clear their additional state; they must call the super implementation. - */ - public void reset() { - visitedNodes.clear(); - } + /** + * Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} + * is null, then the reduce function returns the first result if it is nonnull; otherwise the + * second result is returned. The default result is {@code null} + * + * @param reduceFunction function used to combine two results + */ + protected AnnotatedTypeScanner(@Nullable Reduce reduceFunction) { + this(reduceFunction, null); + } - /** - * Calls {@link #reset()} and then scans {@code type} using null as the parameter. - * - * @param type type to scan - * @return result of scanning {@code type} - */ - @Override - public final R visit(AnnotatedTypeMirror type) { - return visit(type, null); - } + /** + * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it + * is nonnull; otherwise the second result is returned. + * + * @param defaultResult the result to return if a visit type method is not overridden; it should + * be immutable + */ + protected AnnotatedTypeScanner(R defaultResult) { + this(null, defaultResult); + } - /** - * Calls {@link #reset()} and then scans {@code type} using {@code p} as the parameter. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return result of scanning {@code type} - */ - @Override - public final R visit(AnnotatedTypeMirror type, P p) { - reset(); - return scan(type, p); - } + /** + * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it + * is nonnull; otherwise the second result is returned. The default result is {@code null}. + */ + protected AnnotatedTypeScanner() { + this(null, null); + } - /** - * Scan {@code type} by calling {@code type.accept(this, p)}; this method may be overridden by - * subclasses. - * - * @param type type to scan - * @param p the parameter to use - * @return the result of visiting {@code type} - */ - protected R scan(AnnotatedTypeMirror type, P p) { - return type.accept(this, p); - } + // To prevent infinite loops + protected final IdentityHashMap visitedNodes = new IdentityHashMap<>(); - /** - * Scan all the types and returns the reduced result. - * - * @param types types to scan - * @param p the parameter to use - * @return the reduced result of scanning all the types - */ - protected R scan(@Nullable Iterable types, P p) { - if (types == null) { - return defaultResult; + /** + * Reset the scanner to allow reuse of the same instance. Subclasses should override this method + * to clear their additional state; they must call the super implementation. + */ + public void reset() { + visitedNodes.clear(); } - R r = defaultResult; - boolean first = true; - for (AnnotatedTypeMirror type : types) { - r = (first ? scan(type, p) : scanAndReduce(type, p, r)); - first = false; + + /** + * Calls {@link #reset()} and then scans {@code type} using null as the parameter. + * + * @param type type to scan + * @return result of scanning {@code type} + */ + @Override + public final R visit(AnnotatedTypeMirror type) { + return visit(type, null); } - return r; - } - protected R scanAndReduce(Iterable types, P p, R r) { - return reduce(scan(types, p), r); - } + /** + * Calls {@link #reset()} and then scans {@code type} using {@code p} as the parameter. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return result of scanning {@code type} + */ + @Override + public final R visit(AnnotatedTypeMirror type, P p) { + reset(); + return scan(type, p); + } - /** - * Scans {@code type} with the parameter {@code p} and reduces the result with {@code r}. - * - * @param type type to scan - * @param p parameter to use for when scanning {@code type} - * @param r result to combine with the result of scanning {@code type} - * @return the combination of {@code r} with the result of scanning {@code type} - */ - protected R scanAndReduce(AnnotatedTypeMirror type, P p, R r) { - return reduce(scan(type, p), r); - } + /** + * Scan {@code type} by calling {@code type.accept(this, p)}; this method may be overridden by + * subclasses. + * + * @param type type to scan + * @param p the parameter to use + * @return the result of visiting {@code type} + */ + protected R scan(AnnotatedTypeMirror type, P p) { + return type.accept(this, p); + } - /** - * Combines {@code r1} and {@code r2} and returns the result. The default implementation returns - * {@code r1} if it is not null; otherwise, it returns {@code r2}. - * - * @param r1 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never - * returns null - * @param r2 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never - * returns null - * @return the combination of {@code r1} and {@code r2} - */ - protected R reduce(R r1, R r2) { - return reduceFunction.reduce(r1, r2); - } + /** + * Scan all the types and returns the reduced result. + * + * @param types types to scan + * @param p the parameter to use + * @return the reduced result of scanning all the types + */ + protected R scan(@Nullable Iterable types, P p) { + if (types == null) { + return defaultResult; + } + R r = defaultResult; + boolean first = true; + for (AnnotatedTypeMirror type : types) { + r = (first ? scan(type, p) : scanAndReduce(type, p, r)); + first = false; + } + return r; + } - @Override - public R visitDeclared(AnnotatedDeclaredType type, P p) { - // Only declared types with type arguments might be recursive, so only store those. - boolean shouldStoreType = !type.getTypeArguments().isEmpty(); - if (shouldStoreType && visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + protected R scanAndReduce(Iterable types, P p, R r) { + return reduce(scan(types, p), r); } - if (shouldStoreType) { - visitedNodes.put(type, defaultResult); + + /** + * Scans {@code type} with the parameter {@code p} and reduces the result with {@code r}. + * + * @param type type to scan + * @param p parameter to use for when scanning {@code type} + * @param r result to combine with the result of scanning {@code type} + * @return the combination of {@code r} with the result of scanning {@code type} + */ + protected R scanAndReduce(AnnotatedTypeMirror type, P p, R r) { + return reduce(scan(type, p), r); } - R r = defaultResult; - if (type.getEnclosingType() != null) { - r = scan(type.getEnclosingType(), p); - if (shouldStoreType) { - visitedNodes.put(type, r); - } + + /** + * Combines {@code r1} and {@code r2} and returns the result. The default implementation returns + * {@code r1} if it is not null; otherwise, it returns {@code r2}. + * + * @param r1 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method + * never returns null + * @param r2 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method + * never returns null + * @return the combination of {@code r1} and {@code r2} + */ + protected R reduce(R r1, R r2) { + return reduceFunction.reduce(r1, r2); } - r = scanAndReduce(type.getTypeArguments(), p, r); - if (shouldStoreType) { - visitedNodes.put(type, r); + + @Override + public R visitDeclared(AnnotatedDeclaredType type, P p) { + // Only declared types with type arguments might be recursive, so only store those. + boolean shouldStoreType = !type.getTypeArguments().isEmpty(); + if (shouldStoreType && visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + if (shouldStoreType) { + visitedNodes.put(type, defaultResult); + } + R r = defaultResult; + if (type.getEnclosingType() != null) { + r = scan(type.getEnclosingType(), p); + if (shouldStoreType) { + visitedNodes.put(type, r); + } + } + r = scanAndReduce(type.getTypeArguments(), p, r); + if (shouldStoreType) { + visitedNodes.put(type, r); + } + return r; } - return r; - } - @Override - public R visitIntersection(AnnotatedIntersectionType type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + @Override + public R visitIntersection(AnnotatedIntersectionType type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, defaultResult); + R r = scan(type.getBounds(), p); + visitedNodes.put(type, r); + return r; } - visitedNodes.put(type, defaultResult); - R r = scan(type.getBounds(), p); - visitedNodes.put(type, r); - return r; - } - @Override - public R visitUnion(AnnotatedUnionType type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + @Override + public R visitUnion(AnnotatedUnionType type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, defaultResult); + R r = scan(type.getAlternatives(), p); + visitedNodes.put(type, r); + return r; } - visitedNodes.put(type, defaultResult); - R r = scan(type.getAlternatives(), p); - visitedNodes.put(type, r); - return r; - } - @Override - public R visitArray(AnnotatedArrayType type, P p) { - R r = scan(type.getComponentType(), p); - return r; - } + @Override + public R visitArray(AnnotatedArrayType type, P p) { + R r = scan(type.getComponentType(), p); + return r; + } - @Override - public R visitExecutable(AnnotatedExecutableType type, P p) { - R r = scan(type.getReturnType(), p); - if (type.getReceiverType() != null) { - r = scanAndReduce(type.getReceiverType(), p, r); + @Override + public R visitExecutable(AnnotatedExecutableType type, P p) { + R r = scan(type.getReturnType(), p); + if (type.getReceiverType() != null) { + r = scanAndReduce(type.getReceiverType(), p, r); + } + r = scanAndReduce(type.getParameterTypes(), p, r); + r = scanAndReduce(type.getThrownTypes(), p, r); + r = scanAndReduce(type.getTypeVariables(), p, r); + return r; } - r = scanAndReduce(type.getParameterTypes(), p, r); - r = scanAndReduce(type.getThrownTypes(), p, r); - r = scanAndReduce(type.getTypeVariables(), p, r); - return r; - } - @Override - public R visitTypeVariable(AnnotatedTypeVariable type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + @Override + public R visitTypeVariable(AnnotatedTypeVariable type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, defaultResult); + R r = scan(type.getLowerBound(), p); + visitedNodes.put(type, r); + r = scanAndReduce(type.getUpperBound(), p, r); + visitedNodes.put(type, r); + return r; } - visitedNodes.put(type, defaultResult); - R r = scan(type.getLowerBound(), p); - visitedNodes.put(type, r); - r = scanAndReduce(type.getUpperBound(), p, r); - visitedNodes.put(type, r); - return r; - } - @Override - public R visitNoType(AnnotatedNoType type, P p) { - return defaultResult; - } + @Override + public R visitNoType(AnnotatedNoType type, P p) { + return defaultResult; + } - @Override - public R visitNull(AnnotatedNullType type, P p) { - return defaultResult; - } + @Override + public R visitNull(AnnotatedNullType type, P p) { + return defaultResult; + } - @Override - public R visitPrimitive(AnnotatedPrimitiveType type, P p) { - return defaultResult; - } + @Override + public R visitPrimitive(AnnotatedPrimitiveType type, P p) { + return defaultResult; + } - @Override - public R visitWildcard(AnnotatedWildcardType type, P p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + @Override + public R visitWildcard(AnnotatedWildcardType type, P p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, defaultResult); + R r = scan(type.getExtendsBound(), p); + visitedNodes.put(type, r); + r = scanAndReduce(type.getSuperBound(), p, r); + visitedNodes.put(type, r); + return r; } - visitedNodes.put(type, defaultResult); - R r = scan(type.getExtendsBound(), p); - visitedNodes.put(type, r); - r = scanAndReduce(type.getSuperBound(), p, r); - visitedNodes.put(type, r); - return r; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java index 76564e765b9..5de4912cf58 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeVisitor.java @@ -29,119 +29,119 @@ */ public interface AnnotatedTypeVisitor { - /** - * A convenience method equivalent to {@code v.visit(t, null)}. - * - * @param type the type to visit - * @return a visitor-specified result - */ - public R visit(AnnotatedTypeMirror type); + /** + * A convenience method equivalent to {@code v.visit(t, null)}. + * + * @param type the type to visit + * @return a visitor-specified result + */ + public R visit(AnnotatedTypeMirror type); - /** - * Visits a type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visit(AnnotatedTypeMirror type, P p); + /** + * Visits a type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visit(AnnotatedTypeMirror type, P p); - /** - * Visits a declared type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - // public R visitType(AnnotatedTypeMirror type, P p); + /** + * Visits a declared type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + // public R visitType(AnnotatedTypeMirror type, P p); - /** - * Visits a declared type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitDeclared(AnnotatedDeclaredType type, P p); + /** + * Visits a declared type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitDeclared(AnnotatedDeclaredType type, P p); - /** - * Visits an intersection type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitIntersection(AnnotatedIntersectionType type, P p); + /** + * Visits an intersection type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitIntersection(AnnotatedIntersectionType type, P p); - /** - * Visits an union type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitUnion(AnnotatedUnionType type, P p); + /** + * Visits an union type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitUnion(AnnotatedUnionType type, P p); - /** - * Visits an executable type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitExecutable(AnnotatedExecutableType type, P p); + /** + * Visits an executable type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitExecutable(AnnotatedExecutableType type, P p); - /** - * Visits an array type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitArray(AnnotatedArrayType type, P p); + /** + * Visits an array type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitArray(AnnotatedArrayType type, P p); - /** - * Visits a type variable. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitTypeVariable(AnnotatedTypeVariable type, P p); + /** + * Visits a type variable. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitTypeVariable(AnnotatedTypeVariable type, P p); - /** - * Visits a primitive type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitPrimitive(AnnotatedPrimitiveType type, P p); + /** + * Visits a primitive type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitPrimitive(AnnotatedPrimitiveType type, P p); - /** - * Visits NoType type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitNoType(AnnotatedNoType type, P p); + /** + * Visits NoType type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitNoType(AnnotatedNoType type, P p); - /** - * Visits a {@code null} type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitNull(AnnotatedNullType type, P p); + /** + * Visits a {@code null} type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitNull(AnnotatedNullType type, P p); - /** - * Visits a wildcard type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - public R visitWildcard(AnnotatedWildcardType type, P p); + /** + * Visits a wildcard type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + public R visitWildcard(AnnotatedWildcardType type, P p); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java index 6334d03aee4..cbd264125c7 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java @@ -24,342 +24,342 @@ */ public interface AtmComboVisitor { - /** - * Formats type1, type2 and param into an error message used by all methods of - * AbstractAtmComboVisitor that are not overridden. Normally, this method should indicate that the - * given method (and therefore the given pair of type mirror classes) is not supported by this - * class. - * - * @param type1 the first AnnotatedTypeMirror parameter to the visit method called - * @param type2 the second AnnotatedTypeMirror parameter to the visit method called - * @param param subtype specific parameter passed to every visit method - * @return an error message - */ - default String defaultErrorMessage( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - // Message is on one line, without line breaks, because in a stack trace only the first line - // of the message may be shown. - return String.format( - "%s: unexpected combination: type: [%s %s] %s supertype: [%s %s] %s", - this.getClass().getSimpleName(), - type1.getKind(), - type1.getClass(), - type1, - type2.getKind(), - type2.getClass(), - type2); - } - - /** - * Called by the default implementation of every AbstractAtmComboVisitor visit method. This method - * issues a runtime exception by default. In general, it should handle the case where a visit - * method has been called with a pair of type mirrors that should never be passed to this - * particular visitor. - * - * @param type1 the first AnnotatedTypeMirror parameter to the visit method called - * @param type2 the second AnnotatedTypeMirror parameter to the visit method called - * @param param subtype specific parameter passed to every visit method - * @return a value of type RETURN_TYPE, if no exception is thrown - */ - default RETURN_TYPE defaultAction( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - throw new BugInCF(defaultErrorMessage(type1, type2, param)); - } + /** + * Formats type1, type2 and param into an error message used by all methods of + * AbstractAtmComboVisitor that are not overridden. Normally, this method should indicate that + * the given method (and therefore the given pair of type mirror classes) is not supported by + * this class. + * + * @param type1 the first AnnotatedTypeMirror parameter to the visit method called + * @param type2 the second AnnotatedTypeMirror parameter to the visit method called + * @param param subtype specific parameter passed to every visit method + * @return an error message + */ + default String defaultErrorMessage( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + // Message is on one line, without line breaks, because in a stack trace only the first line + // of the message may be shown. + return String.format( + "%s: unexpected combination: type: [%s %s] %s supertype: [%s %s] %s", + this.getClass().getSimpleName(), + type1.getKind(), + type1.getClass(), + type1, + type2.getKind(), + type2.getClass(), + type2); + } + + /** + * Called by the default implementation of every AbstractAtmComboVisitor visit method. This + * method issues a runtime exception by default. In general, it should handle the case where a + * visit method has been called with a pair of type mirrors that should never be passed to this + * particular visitor. + * + * @param type1 the first AnnotatedTypeMirror parameter to the visit method called + * @param type2 the second AnnotatedTypeMirror parameter to the visit method called + * @param param subtype specific parameter passed to every visit method + * @return a value of type RETURN_TYPE, if no exception is thrown + */ + default RETURN_TYPE defaultAction( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + throw new BugInCF(defaultErrorMessage(type1, type2, param)); + } - public RETURN_TYPE visitArray_Array( - AnnotatedArrayType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitArray_Array( + AnnotatedArrayType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitArray_Declared( - AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitArray_Declared( + AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitArray_Executable( - AnnotatedArrayType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitArray_Executable( + AnnotatedArrayType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitArray_Intersection( - AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitArray_Intersection( + AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitArray_None( - AnnotatedArrayType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitArray_None( + AnnotatedArrayType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitArray_Null( - AnnotatedArrayType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitArray_Null( + AnnotatedArrayType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitArray_Primitive( - AnnotatedArrayType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitArray_Primitive( + AnnotatedArrayType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitArray_Typevar( - AnnotatedArrayType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitArray_Typevar( + AnnotatedArrayType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitArray_Union( - AnnotatedArrayType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitArray_Union( + AnnotatedArrayType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitArray_Wildcard( - AnnotatedArrayType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitArray_Wildcard( + AnnotatedArrayType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Array( - AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Array( + AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Declared( - AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Declared( + AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Executable( - AnnotatedDeclaredType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Executable( + AnnotatedDeclaredType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Intersection( - AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Intersection( + AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitDeclared_None( - AnnotatedDeclaredType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitDeclared_None( + AnnotatedDeclaredType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Null( - AnnotatedDeclaredType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Null( + AnnotatedDeclaredType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Primitive( - AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Primitive( + AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Typevar( - AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitDeclared_Typevar( + AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitDeclared_Union( - AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Union( + AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitDeclared_Wildcard( - AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitDeclared_Wildcard( + AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Array( - AnnotatedExecutableType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Array( + AnnotatedExecutableType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Declared( - AnnotatedExecutableType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Declared( + AnnotatedExecutableType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Executable( - AnnotatedExecutableType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Executable( + AnnotatedExecutableType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Intersection( - AnnotatedExecutableType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Intersection( + AnnotatedExecutableType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitExecutable_None( - AnnotatedExecutableType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitExecutable_None( + AnnotatedExecutableType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Null( - AnnotatedExecutableType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Null( + AnnotatedExecutableType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Primitive( - AnnotatedExecutableType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Primitive( + AnnotatedExecutableType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Typevar( - AnnotatedExecutableType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitExecutable_Typevar( + AnnotatedExecutableType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitExecutable_Union( - AnnotatedExecutableType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Union( + AnnotatedExecutableType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitExecutable_Wildcard( - AnnotatedExecutableType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitExecutable_Wildcard( + AnnotatedExecutableType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Array( - AnnotatedIntersectionType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Array( + AnnotatedIntersectionType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Declared( - AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Declared( + AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Executable( - AnnotatedIntersectionType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Executable( + AnnotatedIntersectionType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Intersection( - AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Intersection( + AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitIntersection_None( - AnnotatedIntersectionType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitIntersection_None( + AnnotatedIntersectionType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Null( - AnnotatedIntersectionType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Null( + AnnotatedIntersectionType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Primitive( - AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Primitive( + AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Typevar( - AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitIntersection_Typevar( + AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitIntersection_Union( - AnnotatedIntersectionType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Union( + AnnotatedIntersectionType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitIntersection_Wildcard( - AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitIntersection_Wildcard( + AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitNone_Array( - AnnotatedNoType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitNone_Array( + AnnotatedNoType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitNone_Declared( - AnnotatedNoType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitNone_Declared( + AnnotatedNoType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitNone_Executable( - AnnotatedNoType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitNone_Executable( + AnnotatedNoType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitNone_Intersection( - AnnotatedNoType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitNone_Intersection( + AnnotatedNoType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitNone_None( - AnnotatedNoType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitNone_None( + AnnotatedNoType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitNone_Null( - AnnotatedNoType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitNone_Null( + AnnotatedNoType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitNone_Primitive( - AnnotatedNoType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitNone_Primitive( + AnnotatedNoType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitNone_Union( - AnnotatedNoType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitNone_Union( + AnnotatedNoType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitNone_Wildcard( - AnnotatedNoType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitNone_Wildcard( + AnnotatedNoType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitNull_Array( - AnnotatedNullType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitNull_Array( + AnnotatedNullType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitNull_Declared( - AnnotatedNullType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitNull_Declared( + AnnotatedNullType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitNull_Executable( - AnnotatedNullType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitNull_Executable( + AnnotatedNullType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitNull_Intersection( - AnnotatedNullType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitNull_Intersection( + AnnotatedNullType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitNull_None( - AnnotatedNullType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitNull_None( + AnnotatedNullType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitNull_Null( - AnnotatedNullType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitNull_Null( + AnnotatedNullType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitNull_Primitive( - AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitNull_Primitive( + AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitNull_Typevar( - AnnotatedNullType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitNull_Typevar( + AnnotatedNullType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitNull_Union( - AnnotatedNullType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitNull_Union( + AnnotatedNullType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitNull_Wildcard( - AnnotatedNullType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitNull_Wildcard( + AnnotatedNullType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Array( - AnnotatedPrimitiveType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Array( + AnnotatedPrimitiveType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Declared( - AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Declared( + AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Executable( - AnnotatedPrimitiveType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Executable( + AnnotatedPrimitiveType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Intersection( - AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Intersection( + AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_None( - AnnotatedPrimitiveType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_None( + AnnotatedPrimitiveType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Null( - AnnotatedPrimitiveType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Null( + AnnotatedPrimitiveType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Primitive( - AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Primitive( + AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Typevar( - AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Typevar( + AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Union( - AnnotatedPrimitiveType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Union( + AnnotatedPrimitiveType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitPrimitive_Wildcard( - AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitPrimitive_Wildcard( + AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitUnion_Array( - AnnotatedUnionType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitUnion_Array( + AnnotatedUnionType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitUnion_Declared( - AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitUnion_Declared( + AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitUnion_Executable( - AnnotatedUnionType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitUnion_Executable( + AnnotatedUnionType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitUnion_Intersection( - AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitUnion_Intersection( + AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitUnion_None( - AnnotatedUnionType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitUnion_None( + AnnotatedUnionType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitUnion_Null( - AnnotatedUnionType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitUnion_Null( + AnnotatedUnionType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitUnion_Primitive( - AnnotatedUnionType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitUnion_Primitive( + AnnotatedUnionType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitUnion_Typevar( - AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitUnion_Typevar( + AnnotatedUnionType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitUnion_Union( - AnnotatedUnionType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitUnion_Union( + AnnotatedUnionType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitUnion_Wildcard( - AnnotatedUnionType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitUnion_Wildcard( + AnnotatedUnionType subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Array( - AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Array( + AnnotatedTypeVariable subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Declared( - AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Declared( + AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Executable( - AnnotatedTypeVariable subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Executable( + AnnotatedTypeVariable subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Intersection( - AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Intersection( + AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitTypevar_None( - AnnotatedTypeVariable subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitTypevar_None( + AnnotatedTypeVariable subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Null( - AnnotatedTypeVariable subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Null( + AnnotatedTypeVariable subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Primitive( - AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Primitive( + AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Typevar( - AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitTypevar_Typevar( + AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitTypevar_Union( - AnnotatedTypeVariable subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Union( + AnnotatedTypeVariable subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitTypevar_Wildcard( - AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitTypevar_Wildcard( + AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Array( - AnnotatedWildcardType subtype, AnnotatedArrayType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Array( + AnnotatedWildcardType subtype, AnnotatedArrayType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Declared( - AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Declared( + AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Executable( - AnnotatedWildcardType subtype, AnnotatedExecutableType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Executable( + AnnotatedWildcardType subtype, AnnotatedExecutableType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Intersection( - AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Intersection( + AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, PARAM param); - public RETURN_TYPE visitWildcard_None( - AnnotatedWildcardType subtype, AnnotatedNoType supertype, PARAM param); + public RETURN_TYPE visitWildcard_None( + AnnotatedWildcardType subtype, AnnotatedNoType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Null( - AnnotatedWildcardType subtype, AnnotatedNullType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Null( + AnnotatedWildcardType subtype, AnnotatedNullType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Primitive( - AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Primitive( + AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Typevar( - AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, PARAM param); + public RETURN_TYPE visitWildcard_Typevar( + AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, PARAM param); - public RETURN_TYPE visitWildcard_Union( - AnnotatedWildcardType subtype, AnnotatedUnionType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Union( + AnnotatedWildcardType subtype, AnnotatedUnionType supertype, PARAM param); - public RETURN_TYPE visitWildcard_Wildcard( - AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, PARAM param); + public RETURN_TYPE visitWildcard_Wildcard( + AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, PARAM param); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java index bfc3773c874..2bc0564f692 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/DoubleAnnotatedTypeScanner.java @@ -1,6 +1,5 @@ package org.checkerframework.framework.type.visitor; -import java.util.Iterator; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -11,6 +10,8 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.javacutil.BugInCF; +import java.util.Iterator; + /** * An {@link AnnotatedTypeScanner} that scans two {@link AnnotatedTypeMirror}s simultaneously and * performs {@link #defaultAction(AnnotatedTypeMirror, AnnotatedTypeMirror)} on the pair. Both @@ -26,183 +27,190 @@ * @param the result of scanning the two {@code AnnotatedTypeMirror}s */ public abstract class DoubleAnnotatedTypeScanner - extends AnnotatedTypeScanner { - - /** - * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is - * nonnull; otherwise the second result is returned. The default result is {@code null}. - */ - protected DoubleAnnotatedTypeScanner() { - super(); - } - - /** - * Creates a scanner with the given {@code reduce} function and {@code defaultResult}. - * - * @param reduce function used to combine the results of scan - * @param defaultResult result to use by default - */ - protected DoubleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { - super(reduce, defaultResult); - } - - /** - * Called by default for any visit method that is not overridden. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - protected abstract R defaultAction(AnnotatedTypeMirror type, AnnotatedTypeMirror p); - - /** - * Scans {@code types1} and {@code types2}. If they are empty, then {@link #defaultResult} is - * returned. - * - * @param types1 types - * @param types2 types - * @return the result of scanning and reducing all the types in {@code types1} and {@code types2} - * or {@link #defaultResult} if they are empty - */ - protected R scan( - Iterable types1, - Iterable types2) { - if (types1 == null || types2 == null) { - return defaultResult; + extends AnnotatedTypeScanner { + + /** + * Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it + * is nonnull; otherwise the second result is returned. The default result is {@code null}. + */ + protected DoubleAnnotatedTypeScanner() { + super(); + } + + /** + * Creates a scanner with the given {@code reduce} function and {@code defaultResult}. + * + * @param reduce function used to combine the results of scan + * @param defaultResult result to use by default + */ + protected DoubleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { + super(reduce, defaultResult); + } + + /** + * Called by default for any visit method that is not overridden. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + protected abstract R defaultAction(AnnotatedTypeMirror type, AnnotatedTypeMirror p); + + /** + * Scans {@code types1} and {@code types2}. If they are empty, then {@link #defaultResult} is + * returned. + * + * @param types1 types + * @param types2 types + * @return the result of scanning and reducing all the types in {@code types1} and {@code + * types2} or {@link #defaultResult} if they are empty + */ + protected R scan( + Iterable types1, + Iterable types2) { + if (types1 == null || types2 == null) { + return defaultResult; + } + R r = defaultResult; + boolean first = true; + Iterator iter1 = types1.iterator(); + Iterator iter2 = types2.iterator(); + while (iter1.hasNext() && iter2.hasNext()) { + r = + (first + ? scan(iter1.next(), iter2.next()) + : scanAndReduce(iter1.next(), iter2.next(), r)); + first = false; + } + return r; + } + + /** + * Run {@link #scan} on types and p, then run {@link #reduce} on the result (plus r) to return a + * single element. + */ + protected R scanAndReduce( + Iterable types, + Iterable p, + R r) { + return reduce(scan(types, p), r); } - R r = defaultResult; - boolean first = true; - Iterator iter1 = types1.iterator(); - Iterator iter2 = types2.iterator(); - while (iter1.hasNext() && iter2.hasNext()) { - r = (first ? scan(iter1.next(), iter2.next()) : scanAndReduce(iter1.next(), iter2.next(), r)); - first = false; + + @Override + protected final R scanAndReduce( + Iterable types, AnnotatedTypeMirror p, R r) { + throw new BugInCF( + "DoubleAnnotatedTypeScanner.scanAndReduce: " + + p + + " is not Iterable"); } - return r; - } - - /** - * Run {@link #scan} on types and p, then run {@link #reduce} on the result (plus r) to return a - * single element. - */ - protected R scanAndReduce( - Iterable types, - Iterable p, - R r) { - return reduce(scan(types, p), r); - } - - @Override - protected final R scanAndReduce( - Iterable types, AnnotatedTypeMirror p, R r) { - throw new BugInCF( - "DoubleAnnotatedTypeScanner.scanAndReduce: " - + p - + " is not Iterable"); - } - - @Override - protected R scan(AnnotatedTypeMirror type, AnnotatedTypeMirror p) { - return reduce(super.scan(type, p), defaultAction(type, p)); - } - - @Override - public final R visitDeclared(AnnotatedDeclaredType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedDeclaredType : p; - R r = scan(type.getTypeArguments(), ((AnnotatedDeclaredType) p).getTypeArguments()); - if (type.getEnclosingType() != null) { - r = scanAndReduce(type.getEnclosingType(), ((AnnotatedDeclaredType) p).getEnclosingType(), r); + + @Override + protected R scan(AnnotatedTypeMirror type, AnnotatedTypeMirror p) { + return reduce(super.scan(type, p), defaultAction(type, p)); } - return r; - } - - @Override - public final R visitArray(AnnotatedArrayType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedArrayType : p; - R r = scan(type.getComponentType(), ((AnnotatedArrayType) p).getComponentType()); - return r; - } - - @Override - public final R visitExecutable(AnnotatedExecutableType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedExecutableType : p; - AnnotatedExecutableType ex = (AnnotatedExecutableType) p; - R r = scan(type.getReturnType(), ex.getReturnType()); - if (type.getReceiverType() != null) { - r = scanAndReduce(type.getReceiverType(), ex.getReceiverType(), r); + + @Override + public final R visitDeclared(AnnotatedDeclaredType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedDeclaredType : p; + R r = scan(type.getTypeArguments(), ((AnnotatedDeclaredType) p).getTypeArguments()); + if (type.getEnclosingType() != null) { + r = + scanAndReduce( + type.getEnclosingType(), + ((AnnotatedDeclaredType) p).getEnclosingType(), + r); + } + return r; } - r = scanAndReduce(type.getParameterTypes(), ex.getParameterTypes(), r); - r = scanAndReduce(type.getThrownTypes(), ex.getThrownTypes(), r); - r = scanAndReduce(type.getTypeVariables(), ex.getTypeVariables(), r); - return r; - } - - @Override - public R visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeMirror p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + + @Override + public final R visitArray(AnnotatedArrayType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedArrayType : p; + R r = scan(type.getComponentType(), ((AnnotatedArrayType) p).getComponentType()); + return r; } - visitedNodes.put(type, null); - - R r; - if (p instanceof AnnotatedTypeVariable) { - AnnotatedTypeVariable tv = (AnnotatedTypeVariable) p; - r = scan(type.getLowerBound(), tv.getLowerBound()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getUpperBound(), tv.getUpperBound(), r); - visitedNodes.put(type, r); - } else { - r = scan(type.getLowerBound(), p.getErased()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getUpperBound(), p.getErased(), r); - visitedNodes.put(type, r); + + @Override + public final R visitExecutable(AnnotatedExecutableType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedExecutableType : p; + AnnotatedExecutableType ex = (AnnotatedExecutableType) p; + R r = scan(type.getReturnType(), ex.getReturnType()); + if (type.getReceiverType() != null) { + r = scanAndReduce(type.getReceiverType(), ex.getReceiverType(), r); + } + r = scanAndReduce(type.getParameterTypes(), ex.getParameterTypes(), r); + r = scanAndReduce(type.getThrownTypes(), ex.getThrownTypes(), r); + r = scanAndReduce(type.getTypeVariables(), ex.getTypeVariables(), r); + return r; } - return r; - } - @Override - public R visitWildcard(AnnotatedWildcardType type, AnnotatedTypeMirror p) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + @Override + public R visitTypeVariable(AnnotatedTypeVariable type, AnnotatedTypeMirror p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, null); + + R r; + if (p instanceof AnnotatedTypeVariable) { + AnnotatedTypeVariable tv = (AnnotatedTypeVariable) p; + r = scan(type.getLowerBound(), tv.getLowerBound()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getUpperBound(), tv.getUpperBound(), r); + visitedNodes.put(type, r); + } else { + r = scan(type.getLowerBound(), p.getErased()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getUpperBound(), p.getErased(), r); + visitedNodes.put(type, r); + } + return r; } - visitedNodes.put(type, null); - - R r; - if (p instanceof AnnotatedWildcardType) { - AnnotatedWildcardType w = (AnnotatedWildcardType) p; - r = scan(type.getExtendsBound(), w.getExtendsBound()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getSuperBound(), w.getSuperBound(), r); - visitedNodes.put(type, r); - } else { - r = scan(type.getExtendsBound(), p.getErased()); - visitedNodes.put(type, r); - r = scanAndReduce(type.getSuperBound(), p.getErased(), r); - visitedNodes.put(type, r); + + @Override + public R visitWildcard(AnnotatedWildcardType type, AnnotatedTypeMirror p) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, null); + + R r; + if (p instanceof AnnotatedWildcardType) { + AnnotatedWildcardType w = (AnnotatedWildcardType) p; + r = scan(type.getExtendsBound(), w.getExtendsBound()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getSuperBound(), w.getSuperBound(), r); + visitedNodes.put(type, r); + } else { + r = scan(type.getExtendsBound(), p.getErased()); + visitedNodes.put(type, r); + r = scanAndReduce(type.getSuperBound(), p.getErased(), r); + visitedNodes.put(type, r); + } + return r; } - return r; - } - @Override - public R visitIntersection(AnnotatedIntersectionType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedIntersectionType : p; + @Override + public R visitIntersection(AnnotatedIntersectionType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedIntersectionType : p; - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, null); + R r = scan(type.getBounds(), ((AnnotatedIntersectionType) p).getBounds()); + return r; } - visitedNodes.put(type, null); - R r = scan(type.getBounds(), ((AnnotatedIntersectionType) p).getBounds()); - return r; - } - - @Override - public R visitUnion(AnnotatedUnionType type, AnnotatedTypeMirror p) { - assert p instanceof AnnotatedUnionType : p; - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + + @Override + public R visitUnion(AnnotatedUnionType type, AnnotatedTypeMirror p) { + assert p instanceof AnnotatedUnionType : p; + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, null); + R r = scan(type.getAlternatives(), ((AnnotatedUnionType) p).getAlternatives()); + return r; } - visitedNodes.put(type, null); - R r = scan(type.getAlternatives(), ((AnnotatedUnionType) p).getAlternatives()); - return r; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java index 26d93af991a..647d5e3d83e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/EquivalentAtmComboScanner.java @@ -1,7 +1,5 @@ package org.checkerframework.framework.type.visitor; -import java.util.IdentityHashMap; -import java.util.Iterator; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -16,227 +14,235 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.util.AtmCombo; +import java.util.IdentityHashMap; +import java.util.Iterator; + /** * EquivalentAtmComboScanner is an AtmComboVisitor that accepts combinations that are identical in * TypeMirror structure but might differ in contained AnnotationMirrors. This method will scan the * individual components of the visited type pairs together. */ public abstract class EquivalentAtmComboScanner - extends AbstractAtmComboVisitor { + extends AbstractAtmComboVisitor { - /** A history of type pairs that have already been visited and the return type of their visit. */ - protected final Visited visited = new Visited(); - - /** Entry point for this scanner. */ - @Override - public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - visited.clear(); - return scan(type1, type2, param); - } - - /** - * In an AnnotatedTypeScanner a null type is encounter than null is returned. A user may want to - * customize the behavior of this scanner depending on whether or not one or both types is null. - * - * @param type1 a nullable AnnotatedTypeMirror - * @param type2 a nullable AnnotatedTypeMirror - * @param param the visitor param - * @return a subclass specific return type/value - */ - protected abstract RETURN_TYPE scanWithNull( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param); - - protected RETURN_TYPE scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { - if (type1 == null || type2 == null) { - return scanWithNull(type1, type2, param); - } - - return AtmCombo.accept(type1, type2, param, this); - } - - protected RETURN_TYPE scan( - Iterable types1, - Iterable types2, - PARAM param) { - RETURN_TYPE r = null; - boolean first = true; - - Iterator tIter1 = types1.iterator(); - Iterator tIter2 = types2.iterator(); - - while (tIter1.hasNext() && tIter2.hasNext()) { - AnnotatedTypeMirror type1 = tIter1.next(); - AnnotatedTypeMirror type2 = tIter2.next(); - - r = first ? scan(type1, type2, param) : scanAndReduce(type1, type2, param, r); - } - - return r; - } - - protected RETURN_TYPE scanAndReduce( - Iterable types1, - Iterable types2, - PARAM param, - RETURN_TYPE r) { - return reduce(scan(types1, types2, param), r); - } - - protected RETURN_TYPE scanAndReduce( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param, RETURN_TYPE r) { - return reduce(scan(type1, type2, param), r); - } - - protected RETURN_TYPE reduce(RETURN_TYPE r1, RETURN_TYPE r2) { - if (r1 == null) { - return r2; - } - return r1; - } - - @Override - public RETURN_TYPE visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); - } - visited.add(type1, type2, null); - - return scan(type1.getComponentType(), type2.getComponentType(), param); - } + /** + * A history of type pairs that have already been visited and the return type of their visit. + */ + protected final Visited visited = new Visited(); - @Override - public RETURN_TYPE visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); + /** Entry point for this scanner. */ + @Override + public RETURN_TYPE visit(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + visited.clear(); + return scan(type1, type2, param); } - visited.add(type1, type2, null); - return scan(type1.getTypeArguments(), type2.getTypeArguments(), param); - } + /** + * In an AnnotatedTypeScanner a null type is encounter than null is returned. A user may want to + * customize the behavior of this scanner depending on whether or not one or both types is null. + * + * @param type1 a nullable AnnotatedTypeMirror + * @param type2 a nullable AnnotatedTypeMirror + * @param param the visitor param + * @return a subclass specific return type/value + */ + protected abstract RETURN_TYPE scanWithNull( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param); - @Override - public RETURN_TYPE visitExecutable_Executable( - AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); + protected RETURN_TYPE scan(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param) { + if (type1 == null || type2 == null) { + return scanWithNull(type1, type2, param); + } + + return AtmCombo.accept(type1, type2, param, this); } - visited.add(type1, type2, null); - RETURN_TYPE r = scan(type1.getReturnType(), type2.getReturnType(), param); - r = scanAndReduce(type1.getReceiverType(), type2.getReceiverType(), param, r); - r = scanAndReduce(type1.getParameterTypes(), type2.getParameterTypes(), param, r); - r = scanAndReduce(type1.getThrownTypes(), type2.getThrownTypes(), param, r); - r = scanAndReduce(type1.getTypeVariables(), type2.getTypeVariables(), param, r); - return r; - } + protected RETURN_TYPE scan( + Iterable types1, + Iterable types2, + PARAM param) { + RETURN_TYPE r = null; + boolean first = true; + + Iterator tIter1 = types1.iterator(); + Iterator tIter2 = types2.iterator(); - @Override - public RETURN_TYPE visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); + while (tIter1.hasNext() && tIter2.hasNext()) { + AnnotatedTypeMirror type1 = tIter1.next(); + AnnotatedTypeMirror type2 = tIter2.next(); + + r = first ? scan(type1, type2, param) : scanAndReduce(type1, type2, param, r); + } + + return r; } - visited.add(type1, type2, null); - return scan(type1.getBounds(), type2.getBounds(), param); - } + protected RETURN_TYPE scanAndReduce( + Iterable types1, + Iterable types2, + PARAM param, + RETURN_TYPE r) { + return reduce(scan(types1, types2, param), r); + } - @Override - public @Nullable RETURN_TYPE visitNone_None( - AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { - return null; - } + protected RETURN_TYPE scanAndReduce( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, PARAM param, RETURN_TYPE r) { + return reduce(scan(type1, type2, param), r); + } - @Override - public @Nullable RETURN_TYPE visitNull_Null( - AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { - return null; - } + protected RETURN_TYPE reduce(RETURN_TYPE r1, RETURN_TYPE r2) { + if (r1 == null) { + return r2; + } + return r1; + } - @Override - public @Nullable RETURN_TYPE visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { - return null; - } + @Override + public RETURN_TYPE visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } + visited.add(type1, type2, null); - @Override - public RETURN_TYPE visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); + return scan(type1.getComponentType(), type2.getComponentType(), param); } - visited.add(type1, type2, null); + @Override + public RETURN_TYPE visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } + visited.add(type1, type2, null); - return scan(type1.getAlternatives(), type2.getAlternatives(), param); - } + return scan(type1.getTypeArguments(), type2.getTypeArguments(), param); + } - @Override - public RETURN_TYPE visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); + @Override + public RETURN_TYPE visitExecutable_Executable( + AnnotatedExecutableType type1, AnnotatedExecutableType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } + visited.add(type1, type2, null); + + RETURN_TYPE r = scan(type1.getReturnType(), type2.getReturnType(), param); + r = scanAndReduce(type1.getReceiverType(), type2.getReceiverType(), param, r); + r = scanAndReduce(type1.getParameterTypes(), type2.getParameterTypes(), param, r); + r = scanAndReduce(type1.getThrownTypes(), type2.getThrownTypes(), param, r); + r = scanAndReduce(type1.getTypeVariables(), type2.getTypeVariables(), param, r); + return r; } - visited.add(type1, type2, null); + @Override + public RETURN_TYPE visitIntersection_Intersection( + AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } + visited.add(type1, type2, null); - RETURN_TYPE r = scan(type1.getUpperBound(), type2.getUpperBound(), param); - r = scanAndReduce(type1.getLowerBound(), type2.getLowerBound(), param, r); - return r; - } + return scan(type1.getBounds(), type2.getBounds(), param); + } - @Override - public RETURN_TYPE visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { - if (visited.contains(type1, type2)) { - return visited.getResult(type1, type2); + @Override + public @Nullable RETURN_TYPE visitNone_None( + AnnotatedNoType type1, AnnotatedNoType type2, PARAM param) { + return null; } - visited.add(type1, type2, null); + @Override + public @Nullable RETURN_TYPE visitNull_Null( + AnnotatedNullType type1, AnnotatedNullType type2, PARAM param) { + return null; + } - RETURN_TYPE r = scan(type1.getExtendsBound(), type2.getExtendsBound(), param); - r = scanAndReduce(type1.getSuperBound(), type2.getSuperBound(), param, r); - return r; - } + @Override + public @Nullable RETURN_TYPE visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, PARAM param) { + return null; + } - /** A history of type pairs that have already been visited and the return type of their visit. */ - protected class Visited { + @Override + public RETURN_TYPE visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } - private final IdentityHashMap< - AnnotatedTypeMirror, IdentityHashMap> - visits = new IdentityHashMap<>(); + visited.add(type1, type2, null); - public void clear() { - visits.clear(); + return scan(type1.getAlternatives(), type2.getAlternatives(), param); } - public boolean contains(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - IdentityHashMap recordFor1 = visits.get(type1); - return recordFor1 != null && recordFor1.containsKey(type2); + @Override + public RETURN_TYPE visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } + + visited.add(type1, type2, null); + + RETURN_TYPE r = scan(type1.getUpperBound(), type2.getUpperBound(), param); + r = scanAndReduce(type1.getLowerBound(), type2.getLowerBound(), param, r); + return r; } - public @Nullable RETURN_TYPE getResult(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - IdentityHashMap recordFor1 = visits.get(type1); - if (recordFor1 == null) { - return null; - } + @Override + public RETURN_TYPE visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, PARAM param) { + if (visited.contains(type1, type2)) { + return visited.getResult(type1, type2); + } + + visited.add(type1, type2, null); - return recordFor1.get(type2); + RETURN_TYPE r = scan(type1.getExtendsBound(), type2.getExtendsBound(), param); + r = scanAndReduce(type1.getSuperBound(), type2.getSuperBound(), param, r); + return r; } /** - * Add a new pair to the history. - * - * @param type1 the first type - * @param type2 the second type - * @param ret the result + * A history of type pairs that have already been visited and the return type of their visit. */ - public void add(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, RETURN_TYPE ret) { - IdentityHashMap recordFor1 = - visits.computeIfAbsent(type1, __ -> new IdentityHashMap<>()); - recordFor1.put(type2, ret); + protected class Visited { + + private final IdentityHashMap< + AnnotatedTypeMirror, IdentityHashMap> + visits = new IdentityHashMap<>(); + + public void clear() { + visits.clear(); + } + + public boolean contains(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + IdentityHashMap recordFor1 = visits.get(type1); + return recordFor1 != null && recordFor1.containsKey(type2); + } + + public @Nullable RETURN_TYPE getResult( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + IdentityHashMap recordFor1 = visits.get(type1); + if (recordFor1 == null) { + return null; + } + + return recordFor1.get(type2); + } + + /** + * Add a new pair to the history. + * + * @param type1 the first type + * @param type2 the second type + * @param ret the result + */ + public void add(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, RETURN_TYPE ret) { + IdentityHashMap recordFor1 = + visits.computeIfAbsent(type1, __ -> new IdentityHashMap<>()); + recordFor1.put(type2, ret); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java index a57be68b6b9..a06280e6440 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeScanner.java @@ -27,188 +27,189 @@ */ public class SimpleAnnotatedTypeScanner extends AnnotatedTypeScanner { - /** - * Represents an action to perform on every type. - * - * @param the type of the result of the action - * @param

          the type of the parameter of action - */ - @FunctionalInterface - public interface DefaultAction { + /** + * Represents an action to perform on every type. + * + * @param the type of the result of the action + * @param

          the type of the parameter of action + */ + @FunctionalInterface + public interface DefaultAction { + + /** + * The action to perform on every type. + * + * @param type a type on which to perform some action + * @param p argument to pass to the action + * @return result of the action + */ + R defaultAction(AnnotatedTypeMirror type, P p); + } + + /** The action to perform on every type. */ + protected final DefaultAction defaultAction; + + /** + * Creates a scanner that performs {@code defaultAction} on every type. + * + *

          Use this constructor if the type of result of the default action is {@link Void}. + * + * @param defaultAction action to perform on every type + */ + public SimpleAnnotatedTypeScanner(DefaultAction defaultAction) { + this(defaultAction, null, null); + } + + /** + * Creates a scanner that performs {@code defaultAction} on every type and use {@code reduce} to + * combine the results. + * + *

          Use this constructor if the default action returns a result. + * + * @param defaultAction action to perform on every type + * @param reduce function used to combine results + * @param defaultResult result to use by default + */ + public SimpleAnnotatedTypeScanner( + DefaultAction defaultAction, Reduce reduce, R defaultResult) { + super(reduce, defaultResult); + this.defaultAction = defaultAction; + } + + /** + * Creates a scanner without specifying the default action. Subclasses may only use this + * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. + */ + protected SimpleAnnotatedTypeScanner() { + this(null, null, null); + } + + /** + * Creates a scanner without specifying the default action. Subclasses may only use this + * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. + * + * @param reduce function used to combine results + * @param defaultResult result to use by default + */ + protected SimpleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { + this(null, reduce, defaultResult); + } + + /** + * Called by default for any visit method that is not overridden. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + protected R defaultAction(AnnotatedTypeMirror type, P p) { + if (defaultAction == null) { + // The no argument constructor sets default action to null. + throw new BugInCF( + "%s did not provide a default action. Please override #defaultAction", + this.getClass()); + } + return defaultAction.defaultAction(type, p); + } + + /** + * Visits a declared type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitDeclared(AnnotatedDeclaredType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitDeclared(type, p), r); + } + + /** + * Visits an executable type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitExecutable(AnnotatedExecutableType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitExecutable(type, p), r); + } + + /** + * Visits an array type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitArray(AnnotatedArrayType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitArray(type, p), r); + } + + /** + * Visits a type variable. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitTypeVariable(AnnotatedTypeVariable type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitTypeVariable(type, p), r); + } + + /** + * Visits a primitive type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitPrimitive(AnnotatedPrimitiveType type, P p) { + return defaultAction(type, p); + } + + /** + * Visits NoType type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitNoType(AnnotatedNoType type, P p) { + return defaultAction(type, p); + } + + /** + * Visits a {@code null} type. + * + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + */ + @Override + public final R visitNull(AnnotatedNullType type, P p) { + return defaultAction(type, p); + } /** - * The action to perform on every type. + * Visits a wildcard type. * - * @param type a type on which to perform some action - * @param p argument to pass to the action - * @return result of the action + * @param type the type to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result */ - R defaultAction(AnnotatedTypeMirror type, P p); - } - - /** The action to perform on every type. */ - protected final DefaultAction defaultAction; - - /** - * Creates a scanner that performs {@code defaultAction} on every type. - * - *

          Use this constructor if the type of result of the default action is {@link Void}. - * - * @param defaultAction action to perform on every type - */ - public SimpleAnnotatedTypeScanner(DefaultAction defaultAction) { - this(defaultAction, null, null); - } - - /** - * Creates a scanner that performs {@code defaultAction} on every type and use {@code reduce} to - * combine the results. - * - *

          Use this constructor if the default action returns a result. - * - * @param defaultAction action to perform on every type - * @param reduce function used to combine results - * @param defaultResult result to use by default - */ - public SimpleAnnotatedTypeScanner( - DefaultAction defaultAction, Reduce reduce, R defaultResult) { - super(reduce, defaultResult); - this.defaultAction = defaultAction; - } - - /** - * Creates a scanner without specifying the default action. Subclasses may only use this - * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. - */ - protected SimpleAnnotatedTypeScanner() { - this(null, null, null); - } - - /** - * Creates a scanner without specifying the default action. Subclasses may only use this - * constructor if they also override {@link #defaultAction(AnnotatedTypeMirror, Object)}. - * - * @param reduce function used to combine results - * @param defaultResult result to use by default - */ - protected SimpleAnnotatedTypeScanner(Reduce reduce, R defaultResult) { - this(null, reduce, defaultResult); - } - - /** - * Called by default for any visit method that is not overridden. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - protected R defaultAction(AnnotatedTypeMirror type, P p) { - if (defaultAction == null) { - // The no argument constructor sets default action to null. - throw new BugInCF( - "%s did not provide a default action. Please override #defaultAction", this.getClass()); + @Override + public final R visitWildcard(AnnotatedWildcardType type, P p) { + R r = defaultAction(type, p); + return reduce(super.visitWildcard(type, p), r); } - return defaultAction.defaultAction(type, p); - } - - /** - * Visits a declared type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitDeclared(AnnotatedDeclaredType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitDeclared(type, p), r); - } - - /** - * Visits an executable type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitExecutable(AnnotatedExecutableType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitExecutable(type, p), r); - } - - /** - * Visits an array type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitArray(AnnotatedArrayType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitArray(type, p), r); - } - - /** - * Visits a type variable. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitTypeVariable(AnnotatedTypeVariable type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitTypeVariable(type, p), r); - } - - /** - * Visits a primitive type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitPrimitive(AnnotatedPrimitiveType type, P p) { - return defaultAction(type, p); - } - - /** - * Visits NoType type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitNoType(AnnotatedNoType type, P p) { - return defaultAction(type, p); - } - - /** - * Visits a {@code null} type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitNull(AnnotatedNullType type, P p) { - return defaultAction(type, p); - } - - /** - * Visits a wildcard type. - * - * @param type the type to visit - * @param p a visitor-specified parameter - * @return a visitor-specified result - */ - @Override - public final R visitWildcard(AnnotatedWildcardType type, P p) { - R r = defaultAction(type, p); - return reduce(super.visitWildcard(type, p), r); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java index b5ec2184999..ef2f396f6a2 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/SimpleAnnotatedTypeVisitor.java @@ -22,95 +22,95 @@ */ public abstract class SimpleAnnotatedTypeVisitor implements AnnotatedTypeVisitor { - /** The default value to return as a default action. */ - protected final R DEFAULT_VALUE; - - /** - * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with default value being {@code - * null}. - */ - protected SimpleAnnotatedTypeVisitor() { - this(null); - } - - /** - * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with the default value being the - * passed defaultValue. - * - * @param defaultValue the default value this class should return - */ - protected SimpleAnnotatedTypeVisitor(R defaultValue) { - this.DEFAULT_VALUE = defaultValue; - } - - /** - * Performs the default action for visiting trees, if subclasses do not override the visitFOO - * node. - * - *

          This implementation merely returns the default value (as specified by the protected field - * {@code DEFAULT_VALUE}). - */ - protected R defaultAction(AnnotatedTypeMirror type, P p) { - return DEFAULT_VALUE; - } - - @Override - public R visit(AnnotatedTypeMirror type) { - return visit(type, null); - } - - @Override - public R visit(AnnotatedTypeMirror type, P p) { - return (type == null) ? null : type.accept(this, p); - } - - @Override - public R visitDeclared(AnnotatedDeclaredType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitIntersection(AnnotatedIntersectionType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitUnion(AnnotatedUnionType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitArray(AnnotatedArrayType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitExecutable(AnnotatedExecutableType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitTypeVariable(AnnotatedTypeVariable type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitWildcard(AnnotatedWildcardType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitPrimitive(AnnotatedPrimitiveType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitNull(AnnotatedNullType type, P p) { - return defaultAction(type, p); - } - - @Override - public R visitNoType(AnnotatedNoType type, P p) { - return defaultAction(type, p); - } + /** The default value to return as a default action. */ + protected final R DEFAULT_VALUE; + + /** + * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with default value being {@code + * null}. + */ + protected SimpleAnnotatedTypeVisitor() { + this(null); + } + + /** + * Creates an instance of {@link SimpleAnnotatedTypeVisitor} with the default value being the + * passed defaultValue. + * + * @param defaultValue the default value this class should return + */ + protected SimpleAnnotatedTypeVisitor(R defaultValue) { + this.DEFAULT_VALUE = defaultValue; + } + + /** + * Performs the default action for visiting trees, if subclasses do not override the visitFOO + * node. + * + *

          This implementation merely returns the default value (as specified by the protected field + * {@code DEFAULT_VALUE}). + */ + protected R defaultAction(AnnotatedTypeMirror type, P p) { + return DEFAULT_VALUE; + } + + @Override + public R visit(AnnotatedTypeMirror type) { + return visit(type, null); + } + + @Override + public R visit(AnnotatedTypeMirror type, P p) { + return (type == null) ? null : type.accept(this, p); + } + + @Override + public R visitDeclared(AnnotatedDeclaredType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitIntersection(AnnotatedIntersectionType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitUnion(AnnotatedUnionType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitArray(AnnotatedArrayType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitExecutable(AnnotatedExecutableType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitTypeVariable(AnnotatedTypeVariable type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitWildcard(AnnotatedWildcardType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitPrimitive(AnnotatedPrimitiveType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitNull(AnnotatedNullType type, P p) { + return defaultAction(type, p); + } + + @Override + public R visitNoType(AnnotatedNoType type, P p) { + return defaultAction(type, p); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index 6346c04bd6a..abc43076f4e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -9,30 +9,7 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Deque; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.CanonicalName; @@ -59,1624 +36,1702 @@ import org.plumelib.util.IPair; import org.plumelib.util.StringsPlume; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + /** * Utility methods for operating on {@code AnnotatedTypeMirror}. This class mimics the class {@link * Types}. */ public class AnnotatedTypes { - /** Class cannot be instantiated. */ - private AnnotatedTypes() { - throw new AssertionError("Class AnnotatedTypes cannot be instantiated."); - } - - /** Implements {@code asSuper}. */ - private static @MonotonicNonNull AsSuperVisitor asSuperVisitor; - - /** - * Copies annotations from {@code type} to a copy of {@code superType} where the type variables of - * {@code superType} have been substituted. How the annotations are copied depends on the kinds of - * AnnotatedTypeMirrors given. Generally, if {@code type} and {@code superType} are both declared - * types, asSuper is called recursively on the direct super types, see {@link - * AnnotatedTypeMirror#directSupertypes()}, of {@code type} until {@code type}'s erased Java type - * is the same as {@code superType}'s erased super type. Then {@code type is returned}. For - * compound types, asSuper is called recursively on components. - * - *

          Preconditions:
          - * {@code superType} may have annotations, but they are ignored.
          - * {@code type} may not be an instanceof AnnotatedNullType, because if {@code superType} is a - * compound type, the annotations on the component types are undefined.
          - * The underlying {@code type} (ie the Java type) of {@code type} should be a subtype (or the same - * type) of the underlying type of {@code superType}. Except for these cases: - * - *

            - *
          • If {@code type} is a primitive, then the boxed type of {@code type} must be subtype of - * {@code superType}. - *
          • If {@code superType} is a primitive, then {@code type} must be convertible to {@code - * superType}. - *
          • If {@code superType} is a type variable or wildcard without a lower bound, then {@code - * type} must be a subtype of the upper bound of {@code superType}. (This relaxed rule is - * used during type argument inference where the type variable or wildcard is the type - * argument that was inferred.) - *
          • If {@code superType} is a wildcard with a lower bound, then {@code type} must be a - * subtype of the lower bound of {@code superType}. - *
          - * - *

          Postconditions: {@code type} and {@code superType} are not modified. - * - * @param atypeFactory {@link AnnotatedTypeFactory} - * @param type type from which to copy annotations - * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java - * type. - * @return {@code superType} with annotations copied from {@code type} and type variables - * substituted from {@code type}. - */ - public static T asSuper( - AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type, T superType) { - if (asSuperVisitor == null || !asSuperVisitor.sameAnnotatedTypeFactory(atypeFactory)) { - asSuperVisitor = new AsSuperVisitor(atypeFactory); - } - return asSuperVisitor.asSuper(type, superType); - } - - /** - * Calls asSuper and casts the result to the same type as the input supertype. - * - * @param the type of supertype and return type - * @param atypeFactory the type factory - * @param subtype subtype to be transformed to supertype - * @param supertype supertype that subtype is transformed to - * @return subtype as an instance of supertype - */ - public static T castedAsSuper( - AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror subtype, T supertype) { - Types types = atypeFactory.getProcessingEnv().getTypeUtils(); - - if (subtype.getKind() == TypeKind.NULL) { - // Make a copy of the supertype so that if supertype is a composite type, the - // returned type will be fully annotated. (For example, if sub is @C null and super is - // @A List<@B String>, then the returned type is @C List<@B String>.) - @SuppressWarnings("unchecked") - T copy = (T) supertype.deepCopy(); - copy.replaceAnnotations(subtype.getAnnotations()); - return copy; + /** Class cannot be instantiated. */ + private AnnotatedTypes() { + throw new AssertionError("Class AnnotatedTypes cannot be instantiated."); } - Elements elements = atypeFactory.getProcessingEnv().getElementUtils(); - if (supertype != null - && AnnotatedTypes.isEnum(supertype) - && AnnotatedTypes.isDeclarationOfJavaLangEnum(types, elements, supertype)) { - // Don't return the asSuper result because it causes an infinite loop. - @SuppressWarnings("unchecked") - T result = (T) supertype.deepCopy(); - return result; + /** Implements {@code asSuper}. */ + private static @MonotonicNonNull AsSuperVisitor asSuperVisitor; + + /** + * Copies annotations from {@code type} to a copy of {@code superType} where the type variables + * of {@code superType} have been substituted. How the annotations are copied depends on the + * kinds of AnnotatedTypeMirrors given. Generally, if {@code type} and {@code superType} are + * both declared types, asSuper is called recursively on the direct super types, see {@link + * AnnotatedTypeMirror#directSupertypes()}, of {@code type} until {@code type}'s erased Java + * type is the same as {@code superType}'s erased super type. Then {@code type is returned}. For + * compound types, asSuper is called recursively on components. + * + *

          Preconditions:
          + * {@code superType} may have annotations, but they are ignored.
          + * {@code type} may not be an instanceof AnnotatedNullType, because if {@code superType} is a + * compound type, the annotations on the component types are undefined.
          + * The underlying {@code type} (ie the Java type) of {@code type} should be a subtype (or the + * same type) of the underlying type of {@code superType}. Except for these cases: + * + *

            + *
          • If {@code type} is a primitive, then the boxed type of {@code type} must be subtype of + * {@code superType}. + *
          • If {@code superType} is a primitive, then {@code type} must be convertible to {@code + * superType}. + *
          • If {@code superType} is a type variable or wildcard without a lower bound, then {@code + * type} must be a subtype of the upper bound of {@code superType}. (This relaxed rule is + * used during type argument inference where the type variable or wildcard is the type + * argument that was inferred.) + *
          • If {@code superType} is a wildcard with a lower bound, then {@code type} must be a + * subtype of the lower bound of {@code superType}. + *
          + * + *

          Postconditions: {@code type} and {@code superType} are not modified. + * + * @param atypeFactory {@link AnnotatedTypeFactory} + * @param type type from which to copy annotations + * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java + * type. + * @return {@code superType} with annotations copied from {@code type} and type variables + * substituted from {@code type}. + */ + public static T asSuper( + AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type, T superType) { + if (asSuperVisitor == null || !asSuperVisitor.sameAnnotatedTypeFactory(atypeFactory)) { + asSuperVisitor = new AsSuperVisitor(atypeFactory); + } + return asSuperVisitor.asSuper(type, superType); } - T asSuperType = AnnotatedTypes.asSuper(atypeFactory, subtype, supertype); - - fixUpRawTypes(subtype, asSuperType, supertype, types); - - return asSuperType; - } - - /** - * Some times we create type arguments for types that were raw. When we do an asSuper we lose - * these arguments. If in the converted type (i.e. the subtype as super) is missing type arguments - * AND those type arguments should come from the original subtype's type arguments then we copy - * the original type arguments to the converted type. e.g. We have a type W, that "wasRaw" {@code - * ArrayList} When W is converted to type A, List, using asSuper it no longer - * has its type argument. But since the type argument to List should be the same as that to - * ArrayList we copy over the type argument of W to A. A becomes {@code List} - * - * @param originalSubtype the subtype before being converted by asSuper - * @param asSuperType he subtype after being converted by asSuper - * @param supertype the supertype for which asSuperType should have the same underlying type - * @param types the types utility - */ - private static void fixUpRawTypes( - AnnotatedTypeMirror originalSubtype, - AnnotatedTypeMirror asSuperType, - AnnotatedTypeMirror supertype, - Types types) { - if (asSuperType == null - || asSuperType.getKind() != TypeKind.DECLARED - || originalSubtype.getKind() != TypeKind.DECLARED) { - return; - } + /** + * Calls asSuper and casts the result to the same type as the input supertype. + * + * @param the type of supertype and return type + * @param atypeFactory the type factory + * @param subtype subtype to be transformed to supertype + * @param supertype supertype that subtype is transformed to + * @return subtype as an instance of supertype + */ + public static T castedAsSuper( + AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror subtype, T supertype) { + Types types = atypeFactory.getProcessingEnv().getTypeUtils(); + + if (subtype.getKind() == TypeKind.NULL) { + // Make a copy of the supertype so that if supertype is a composite type, the + // returned type will be fully annotated. (For example, if sub is @C null and super is + // @A List<@B String>, then the returned type is @C List<@B String>.) + @SuppressWarnings("unchecked") + T copy = (T) supertype.deepCopy(); + copy.replaceAnnotations(subtype.getAnnotations()); + return copy; + } - AnnotatedDeclaredType declaredAsSuper = (AnnotatedDeclaredType) asSuperType; - AnnotatedDeclaredType declaredSubtype = (AnnotatedDeclaredType) originalSubtype; + Elements elements = atypeFactory.getProcessingEnv().getElementUtils(); + if (supertype != null + && AnnotatedTypes.isEnum(supertype) + && AnnotatedTypes.isDeclarationOfJavaLangEnum(types, elements, supertype)) { + // Don't return the asSuper result because it causes an infinite loop. + @SuppressWarnings("unchecked") + T result = (T) supertype.deepCopy(); + return result; + } - if (!declaredAsSuper.isUnderlyingTypeRaw() - || !declaredAsSuper.getTypeArguments().isEmpty() - || declaredSubtype.getTypeArguments().isEmpty()) { - return; - } + T asSuperType = AnnotatedTypes.asSuper(atypeFactory, subtype, supertype); - Set> typeArgMap = - TypeArgumentMapper.mapTypeArgumentIndices( - (TypeElement) declaredSubtype.getUnderlyingType().asElement(), - (TypeElement) declaredAsSuper.getUnderlyingType().asElement(), - types); + fixUpRawTypes(subtype, asSuperType, supertype, types); - if (typeArgMap.size() != declaredSubtype.getTypeArguments().size()) { - return; + return asSuperType; } - List> orderedByDestination = new ArrayList<>(typeArgMap); - orderedByDestination.sort(Comparator.comparingInt(o -> o.second)); - - if (typeArgMap.size() == ((AnnotatedDeclaredType) supertype).getTypeArguments().size()) { - List subTypeArgs = declaredSubtype.getTypeArguments(); - List newTypeArgs = - CollectionsPlume.mapList( - mapping -> subTypeArgs.get(mapping.first).deepCopy(), orderedByDestination); - declaredAsSuper.setTypeArguments(newTypeArgs); - } else { - declaredAsSuper.setTypeArguments(Collections.emptyList()); - } - } - - /** - * Returns the result of calling {@link #asSuper(AnnotatedTypeFactory, AnnotatedTypeMirror, - * AnnotatedTypeMirror)} on {@code type} and {@code superType} or an enclosing type of {@code - * type}. - * - *

          If the underlying type of {@code type} is a subtype of the underlying type of {@code - * superType}, then this method returns the result of calling {@code asSuper(atypeFactory, type, - * superType)}. - * - *

          If the underlying type of an enclosing of {@code type} is a subtype of the underlying type - * of {@code superType}, then this method returns the result of calling {@code - * asSuper(atypeFactory, type.getEnclosingType(), superType)}. - * - *

          Otherwise, throws {@link BugInCF}. - * - * @param types types utils - * @param atypeFactory the type factory - * @param type a type - * @param superType a supertype of {@code type} or a supertype of an enclosing type of {@code - * type} - * @return {@code type} or an enclosing type of {@code type} as {@code superType} - */ - private static AnnotatedTypeMirror asOuterSuper( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror type, - AnnotatedTypeMirror superType) { - if (type.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType dt = (AnnotatedDeclaredType) type; - AnnotatedDeclaredType enclosingType = dt; - TypeMirror superTypeMirror = types.erasure(superType.getUnderlyingType()); - while (enclosingType != null) { - TypeMirror enclosingTypeMirror = types.erasure(enclosingType.getUnderlyingType()); - if (types.isSubtype(enclosingTypeMirror, superTypeMirror)) { - dt = enclosingType; - break; - } - enclosingType = enclosingType.getEnclosingType(); - } - if (enclosingType == null) { - throw new BugInCF("Enclosing type not found: type: %s supertype: %s", dt, superType); - } - return asSuper(atypeFactory, dt, superType); - } - return asSuper(atypeFactory, type, superType); - } - - /** - * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, - * Element)} with more precise return type. - * - * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @return the type of elem as member of t - */ - public static AnnotatedExecutableType asMemberOf( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror t, - ExecutableElement elem) { - return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem); - } - - /** - * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element, - * AnnotatedTypeMirror)} with more precise return type. - * - * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element, - * AnnotatedTypeMirror) - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @param type unsubstituted type of member - * @return the type of member as member of {@code t}, with initial type memberType; can be an - * alias to memberType - */ - public static AnnotatedExecutableType asMemberOf( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror t, - ExecutableElement elem, - AnnotatedExecutableType type) { - return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem, type); - } - - /** - * Returns the type of an element when that element is viewed as a member of, or otherwise - * directly contained by, a given type. - * - *

          For example, when viewed as a member of the parameterized type {@code Set<@NonNull String>}, - * the {@code Set.add} method is an {@code ExecutableType} whose parameter is of type - * {@code @NonNull String}. - * - *

          Before returning the result, this method adjusts it by calling {@link - * AnnotatedTypeFactory#postAsMemberOf(AnnotatedTypeMirror, AnnotatedTypeMirror, Element)}. - * - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @return the type of elem as member of t - */ - public static AnnotatedTypeMirror asMemberOf( - Types types, AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror t, Element elem) { - AnnotatedTypeMirror memberType = atypeFactory.getAnnotatedType(elem); - return asMemberOf(types, atypeFactory, t, elem, memberType); - } - - /** - * Returns the type of an element when that element is viewed as a member of, or otherwise - * directly contained by, a given type. An initial type for the member is provided, to allow for - * earlier changes to the declared type of elem. For example, polymorphic qualifiers must be - * substituted before type variables are substituted. - * - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param t the receiver type - * @param elem the element that should be viewed as member of t - * @param elemType unsubstituted type of elem - * @return the type of elem as member of t - * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) - */ - public static AnnotatedTypeMirror asMemberOf( - Types types, - AnnotatedTypeFactory atypeFactory, - @Nullable AnnotatedTypeMirror t, - Element elem, - AnnotatedTypeMirror elemType) { - // asMemberOf is only for fields, variables, and methods! - // Otherwise, simply use fromElement. - switch (elem.getKind()) { - case PACKAGE: - case INSTANCE_INIT: - case OTHER: - case STATIC_INIT: - case TYPE_PARAMETER: - return elemType; - default: - if (t == null || ElementUtils.isStatic(elem)) { - return elemType; - } - AnnotatedTypeMirror res = asMemberOfImpl(types, atypeFactory, t, elem, elemType); - atypeFactory.postAsMemberOf(res, t, elem); - return res; - } - } - - /** - * Helper for {@link AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, - * Element)}. - * - * @param types the Types instance to use - * @param atypeFactory the type factory to use - * @param receiverType the receiver type - * @param member the element that should be viewed as member of receiverType - * @param memberType unsubstituted type of member - * @return the type of member as a member of receiverType; can be an alias to memberType - */ - private static AnnotatedTypeMirror asMemberOfImpl( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror receiverType, - Element member, - AnnotatedTypeMirror memberType) { - switch (receiverType.getKind()) { - case ARRAY: - // Method references like String[]::clone should have a return type of String[] - // rather than Object. - if (SyntheticArrays.isArrayClone(receiverType, member)) { - return SyntheticArrays.replaceReturnType(member, (AnnotatedArrayType) receiverType); + /** + * Some times we create type arguments for types that were raw. When we do an asSuper we lose + * these arguments. If in the converted type (i.e. the subtype as super) is missing type + * arguments AND those type arguments should come from the original subtype's type arguments + * then we copy the original type arguments to the converted type. e.g. We have a type W, that + * "wasRaw" {@code ArrayList} When W is converted to type A, List, using + * asSuper it no longer has its type argument. But since the type argument to List should be the + * same as that to ArrayList we copy over the type argument of W to A. A becomes {@code List} + * + * @param originalSubtype the subtype before being converted by asSuper + * @param asSuperType he subtype after being converted by asSuper + * @param supertype the supertype for which asSuperType should have the same underlying type + * @param types the types utility + */ + private static void fixUpRawTypes( + AnnotatedTypeMirror originalSubtype, + AnnotatedTypeMirror asSuperType, + AnnotatedTypeMirror supertype, + Types types) { + if (asSuperType == null + || asSuperType.getKind() != TypeKind.DECLARED + || originalSubtype.getKind() != TypeKind.DECLARED) { + return; } - return memberType; - case TYPEVAR: - return asMemberOf( - types, - atypeFactory, - atypeFactory.applyCaptureConversion( - ((AnnotatedTypeVariable) receiverType).getUpperBound()), - member, - memberType); - case WILDCARD: - if (AnnotatedTypes.isTypeArgOfRawType(receiverType)) { - return substituteTypeArgsFromRawTypes(atypeFactory, member, memberType); + + AnnotatedDeclaredType declaredAsSuper = (AnnotatedDeclaredType) asSuperType; + AnnotatedDeclaredType declaredSubtype = (AnnotatedDeclaredType) originalSubtype; + + if (!declaredAsSuper.isUnderlyingTypeRaw() + || !declaredAsSuper.getTypeArguments().isEmpty() + || declaredSubtype.getTypeArguments().isEmpty()) { + return; } - return asMemberOf( - types, - atypeFactory, - ((AnnotatedWildcardType) receiverType).getExtendsBound().deepCopy(), - member, - memberType); - case INTERSECTION: - AnnotatedTypeMirror result = memberType; - TypeMirror enclosingElementType = member.getEnclosingElement().asType(); - for (AnnotatedTypeMirror bound : ((AnnotatedIntersectionType) receiverType).getBounds()) { - if (TypesUtils.isErasedSubtype(bound.getUnderlyingType(), enclosingElementType, types)) { - result = - substituteTypeVariables( - types, - atypeFactory, - atypeFactory.applyCaptureConversion(bound), - member, - result); - } + + Set> typeArgMap = + TypeArgumentMapper.mapTypeArgumentIndices( + (TypeElement) declaredSubtype.getUnderlyingType().asElement(), + (TypeElement) declaredAsSuper.getUnderlyingType().asElement(), + types); + + if (typeArgMap.size() != declaredSubtype.getTypeArguments().size()) { + return; } - return result; - case UNION: - return substituteTypeVariables(types, atypeFactory, receiverType, member, memberType); - case DECLARED: - AnnotatedDeclaredType receiverTypeDT = (AnnotatedDeclaredType) receiverType; - if (isRawCall(receiverTypeDT, member, types)) { - return memberType.getErased(); + + List> orderedByDestination = new ArrayList<>(typeArgMap); + orderedByDestination.sort(Comparator.comparingInt(o -> o.second)); + + if (typeArgMap.size() == ((AnnotatedDeclaredType) supertype).getTypeArguments().size()) { + List subTypeArgs = declaredSubtype.getTypeArguments(); + List newTypeArgs = + CollectionsPlume.mapList( + mapping -> subTypeArgs.get(mapping.first).deepCopy(), + orderedByDestination); + declaredAsSuper.setTypeArguments(newTypeArgs); + } else { + declaredAsSuper.setTypeArguments(Collections.emptyList()); } - return substituteTypeVariables(types, atypeFactory, receiverType, member, memberType); - default: - throw new BugInCF("asMemberOf called on unexpected type.%nt: %s", receiverType); - } - } - - /** - * Is the call to {@code method} with {@code receiver} raw? - * - * @param receiver type of the receiver of the call - * @param method the element of a method or constructor - * @param types type utilities - * @return whether the call to {@code method} with {@code receiver} raw - */ - private static boolean isRawCall(AnnotatedDeclaredType receiver, Element method, Types types) { - // Section 4.8, "Raw Types". - // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.8) - // - // The type of a constructor (§8.8), instance method (8.4, 9.4), or non-static field - // (8.3) of a raw type C that is not inherited from its superclasses or superinterfaces - // is the raw type that corresponds to the erasure of its type in the generic declaration - // corresponding to C. - if (method.getEnclosingElement().equals(receiver.getUnderlyingType().asElement())) { - return receiver.isUnderlyingTypeRaw(); } - // The below is checking for a super() call where the super type is a raw type. - // See framework/tests/all-systems/RawSuper.java for an example. - if ("".contentEquals(method.getSimpleName())) { - ExecutableElement constructor = (ExecutableElement) method; - TypeMirror constructorClass = types.erasure(constructor.getEnclosingElement().asType()); - TypeMirror directSuper = types.directSupertypes(receiver.getUnderlyingType()).get(0); - while (!types.isSameType(types.erasure(directSuper), constructorClass) - && !TypesUtils.isObject(directSuper)) { - directSuper = types.directSupertypes(directSuper).get(0); - } - if (directSuper.getKind() == TypeKind.DECLARED) { - DeclaredType declaredType = (DeclaredType) directSuper; - TypeElement typeelem = (TypeElement) declaredType.asElement(); - DeclaredType declty = (DeclaredType) typeelem.asType(); - return !declty.getTypeArguments().isEmpty() && declaredType.getTypeArguments().isEmpty(); - } + /** + * Returns the result of calling {@link #asSuper(AnnotatedTypeFactory, AnnotatedTypeMirror, + * AnnotatedTypeMirror)} on {@code type} and {@code superType} or an enclosing type of {@code + * type}. + * + *

          If the underlying type of {@code type} is a subtype of the underlying type of {@code + * superType}, then this method returns the result of calling {@code asSuper(atypeFactory, type, + * superType)}. + * + *

          If the underlying type of an enclosing of {@code type} is a subtype of the underlying type + * of {@code superType}, then this method returns the result of calling {@code + * asSuper(atypeFactory, type.getEnclosingType(), superType)}. + * + *

          Otherwise, throws {@link BugInCF}. + * + * @param types types utils + * @param atypeFactory the type factory + * @param type a type + * @param superType a supertype of {@code type} or a supertype of an enclosing type of {@code + * type} + * @return {@code type} or an enclosing type of {@code type} as {@code superType} + */ + private static AnnotatedTypeMirror asOuterSuper( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror type, + AnnotatedTypeMirror superType) { + if (type.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType dt = (AnnotatedDeclaredType) type; + AnnotatedDeclaredType enclosingType = dt; + TypeMirror superTypeMirror = types.erasure(superType.getUnderlyingType()); + while (enclosingType != null) { + TypeMirror enclosingTypeMirror = types.erasure(enclosingType.getUnderlyingType()); + if (types.isSubtype(enclosingTypeMirror, superTypeMirror)) { + dt = enclosingType; + break; + } + enclosingType = enclosingType.getEnclosingType(); + } + if (enclosingType == null) { + throw new BugInCF( + "Enclosing type not found: type: %s supertype: %s", dt, superType); + } + return asSuper(atypeFactory, dt, superType); + } + return asSuper(atypeFactory, type, superType); } - return false; - } - - /** - * Substitute type variables. - * - * @param types type utilities - * @param atypeFactory the type factory - * @param receiverType the type of the class that contains member (or a subtype of it) - * @param member a type member, such as a method or field - * @param memberType the type of {@code member} - * @return {@code memberType}, substituted - */ - private static AnnotatedTypeMirror substituteTypeVariables( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror receiverType, - Element member, - AnnotatedTypeMirror memberType) { - - // Basic Algorithm: - // 1. Find the enclosingClassOfMember of the element - // 2. Find the base type of enclosingClassOfMember (e.g. type of enclosingClassOfMember as - // supertype of passed type) - // 3. Substitute for type variables if any exist - TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); - DeclaredType enclosingType = (DeclaredType) enclosingClassOfMember.asType(); - Map mappings = new HashMap<>(); - - // Look for all enclosing types that have type variables - // and collect type to be substituted for those type variables - while (enclosingType != null) { - TypeElement enclosingTypeElement = (TypeElement) enclosingType.asElement(); - addTypeVarMappings(types, atypeFactory, receiverType, enclosingTypeElement, mappings); - if (enclosingType.getEnclosingType() != null - && enclosingType.getEnclosingType().getKind() == TypeKind.DECLARED) { - enclosingType = (DeclaredType) enclosingType.getEnclosingType(); - } else { - enclosingType = null; - } + /** + * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, + * Element)} with more precise return type. + * + * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @return the type of elem as member of t + */ + public static AnnotatedExecutableType asMemberOf( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror t, + ExecutableElement elem) { + return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem); } - if (!mappings.isEmpty()) { - memberType = atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); + /** + * Specialization of {@link #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, + * Element, AnnotatedTypeMirror)} with more precise return type. + * + * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element, + * AnnotatedTypeMirror) + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @param type unsubstituted type of member + * @return the type of member as member of {@code t}, with initial type memberType; can be an + * alias to memberType + */ + public static AnnotatedExecutableType asMemberOf( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror t, + ExecutableElement elem, + AnnotatedExecutableType type) { + return (AnnotatedExecutableType) asMemberOf(types, atypeFactory, t, (Element) elem, type); } - return memberType; - } - - private static void addTypeVarMappings( - Types types, - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror t, - TypeElement enclosingClassOfElem, - Map mappings) { - if (enclosingClassOfElem.getTypeParameters().isEmpty()) { - return; - } - AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfElem); - AnnotatedDeclaredType base = - (AnnotatedDeclaredType) asOuterSuper(types, atypeFactory, t, enclosingType); - base = (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(base); - - List ownerParams = - new ArrayList<>(enclosingType.getTypeArguments().size()); - for (AnnotatedTypeMirror typeParam : enclosingType.getTypeArguments()) { - if (typeParam.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF( - StringsPlume.joinLines( - "Type arguments of a declaration should be type variables.", - " enclosingClassOfElem=" + enclosingClassOfElem, - " enclosingType=" + enclosingType, - " typeMirror=" + t)); - } - ownerParams.add((AnnotatedTypeVariable) typeParam); + /** + * Returns the type of an element when that element is viewed as a member of, or otherwise + * directly contained by, a given type. + * + *

          For example, when viewed as a member of the parameterized type {@code Set<@NonNull + * String>}, the {@code Set.add} method is an {@code ExecutableType} whose parameter is of type + * {@code @NonNull String}. + * + *

          Before returning the result, this method adjusts it by calling {@link + * AnnotatedTypeFactory#postAsMemberOf(AnnotatedTypeMirror, AnnotatedTypeMirror, Element)}. + * + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @return the type of elem as member of t + */ + public static AnnotatedTypeMirror asMemberOf( + Types types, AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror t, Element elem) { + AnnotatedTypeMirror memberType = atypeFactory.getAnnotatedType(elem); + return asMemberOf(types, atypeFactory, t, elem, memberType); } - List baseParams = base.getTypeArguments(); - if (ownerParams.size() != baseParams.size() && !base.isUnderlyingTypeRaw()) { - throw new BugInCF( - StringsPlume.joinLines( - "Unexpected number of parameters.", - "enclosingType=" + enclosingType, - "baseType=" + base)); - } - if (!ownerParams.isEmpty() && baseParams.isEmpty() && base.isUnderlyingTypeRaw()) { - // If base type was raw and the type arguments are missing, set them to the erased - // type of the type variable (which is the erased type of the upper bound). - baseParams = CollectionsPlume.mapList(AnnotatedTypeVariable::getErased, ownerParams); + /** + * Returns the type of an element when that element is viewed as a member of, or otherwise + * directly contained by, a given type. An initial type for the member is provided, to allow for + * earlier changes to the declared type of elem. For example, polymorphic qualifiers must be + * substituted before type variables are substituted. + * + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param t the receiver type + * @param elem the element that should be viewed as member of t + * @param elemType unsubstituted type of elem + * @return the type of elem as member of t + * @see #asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, Element) + */ + public static AnnotatedTypeMirror asMemberOf( + Types types, + AnnotatedTypeFactory atypeFactory, + @Nullable AnnotatedTypeMirror t, + Element elem, + AnnotatedTypeMirror elemType) { + // asMemberOf is only for fields, variables, and methods! + // Otherwise, simply use fromElement. + switch (elem.getKind()) { + case PACKAGE: + case INSTANCE_INIT: + case OTHER: + case STATIC_INIT: + case TYPE_PARAMETER: + return elemType; + default: + if (t == null || ElementUtils.isStatic(elem)) { + return elemType; + } + AnnotatedTypeMirror res = asMemberOfImpl(types, atypeFactory, t, elem, elemType); + atypeFactory.postAsMemberOf(res, t, elem); + return res; + } } - for (int i = 0; i < ownerParams.size(); ++i) { - mappings.put(ownerParams.get(i).getUnderlyingType(), baseParams.get(i).asUse()); - } - } - - /** - * Substitutes type arguments from raw types for type variables in {@code memberType}. - * - * @param atypeFactory the type factory - * @param member the element with type {@code memberType}; used to obtain the enclosing type - * @param memberType the type to side-effect - * @return memberType, with type arguments substituted for type variables - */ - private static AnnotatedTypeMirror substituteTypeArgsFromRawTypes( - AnnotatedTypeFactory atypeFactory, Element member, AnnotatedTypeMirror memberType) { - TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); - Map mappings = new HashMap<>(); - - while (enclosingClassOfMember != null) { - if (!enclosingClassOfMember.getTypeParameters().isEmpty()) { - AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfMember); - AnnotatedDeclaredType erasedEnclosingType = - atypeFactory.getAnnotatedType(enclosingClassOfMember); - List typeArguments = enclosingType.getTypeArguments(); - for (int i = 0; i < typeArguments.size(); i++) { - AnnotatedTypeMirror type = typeArguments.get(i); - AnnotatedTypeMirror enclosedTypeArg = erasedEnclosingType.getTypeArguments().get(i); - AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type; - mappings.put(typeParameter.getUnderlyingType(), enclosedTypeArg); + /** + * Helper for {@link AnnotatedTypes#asMemberOf(Types, AnnotatedTypeFactory, AnnotatedTypeMirror, + * Element)}. + * + * @param types the Types instance to use + * @param atypeFactory the type factory to use + * @param receiverType the receiver type + * @param member the element that should be viewed as member of receiverType + * @param memberType unsubstituted type of member + * @return the type of member as a member of receiverType; can be an alias to memberType + */ + private static AnnotatedTypeMirror asMemberOfImpl( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror receiverType, + Element member, + AnnotatedTypeMirror memberType) { + switch (receiverType.getKind()) { + case ARRAY: + // Method references like String[]::clone should have a return type of String[] + // rather than Object. + if (SyntheticArrays.isArrayClone(receiverType, member)) { + return SyntheticArrays.replaceReturnType( + member, (AnnotatedArrayType) receiverType); + } + return memberType; + case TYPEVAR: + return asMemberOf( + types, + atypeFactory, + atypeFactory.applyCaptureConversion( + ((AnnotatedTypeVariable) receiverType).getUpperBound()), + member, + memberType); + case WILDCARD: + if (AnnotatedTypes.isTypeArgOfRawType(receiverType)) { + return substituteTypeArgsFromRawTypes(atypeFactory, member, memberType); + } + return asMemberOf( + types, + atypeFactory, + ((AnnotatedWildcardType) receiverType).getExtendsBound().deepCopy(), + member, + memberType); + case INTERSECTION: + AnnotatedTypeMirror result = memberType; + TypeMirror enclosingElementType = member.getEnclosingElement().asType(); + for (AnnotatedTypeMirror bound : + ((AnnotatedIntersectionType) receiverType).getBounds()) { + if (TypesUtils.isErasedSubtype( + bound.getUnderlyingType(), enclosingElementType, types)) { + result = + substituteTypeVariables( + types, + atypeFactory, + atypeFactory.applyCaptureConversion(bound), + member, + result); + } + } + return result; + case UNION: + return substituteTypeVariables( + types, atypeFactory, receiverType, member, memberType); + case DECLARED: + AnnotatedDeclaredType receiverTypeDT = (AnnotatedDeclaredType) receiverType; + if (isRawCall(receiverTypeDT, member, types)) { + return memberType.getErased(); + } + return substituteTypeVariables( + types, atypeFactory, receiverType, member, memberType); + default: + throw new BugInCF("asMemberOf called on unexpected type.%nt: %s", receiverType); } - } - enclosingClassOfMember = - ElementUtils.enclosingTypeElement(enclosingClassOfMember.getEnclosingElement()); } - if (!mappings.isEmpty()) { - return atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); + /** + * Is the call to {@code method} with {@code receiver} raw? + * + * @param receiver type of the receiver of the call + * @param method the element of a method or constructor + * @param types type utilities + * @return whether the call to {@code method} with {@code receiver} raw + */ + private static boolean isRawCall(AnnotatedDeclaredType receiver, Element method, Types types) { + // Section 4.8, "Raw Types". + // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.8) + // + // The type of a constructor (§8.8), instance method (8.4, 9.4), or non-static field + // (8.3) of a raw type C that is not inherited from its superclasses or superinterfaces + // is the raw type that corresponds to the erasure of its type in the generic declaration + // corresponding to C. + if (method.getEnclosingElement().equals(receiver.getUnderlyingType().asElement())) { + return receiver.isUnderlyingTypeRaw(); + } + + // The below is checking for a super() call where the super type is a raw type. + // See framework/tests/all-systems/RawSuper.java for an example. + if ("".contentEquals(method.getSimpleName())) { + ExecutableElement constructor = (ExecutableElement) method; + TypeMirror constructorClass = types.erasure(constructor.getEnclosingElement().asType()); + TypeMirror directSuper = types.directSupertypes(receiver.getUnderlyingType()).get(0); + while (!types.isSameType(types.erasure(directSuper), constructorClass) + && !TypesUtils.isObject(directSuper)) { + directSuper = types.directSupertypes(directSuper).get(0); + } + if (directSuper.getKind() == TypeKind.DECLARED) { + DeclaredType declaredType = (DeclaredType) directSuper; + TypeElement typeelem = (TypeElement) declaredType.asElement(); + DeclaredType declty = (DeclaredType) typeelem.asType(); + return !declty.getTypeArguments().isEmpty() + && declaredType.getTypeArguments().isEmpty(); + } + } + + return false; } - return memberType; - } + /** + * Substitute type variables. + * + * @param types type utilities + * @param atypeFactory the type factory + * @param receiverType the type of the class that contains member (or a subtype of it) + * @param member a type member, such as a method or field + * @param memberType the type of {@code member} + * @return {@code memberType}, substituted + */ + private static AnnotatedTypeMirror substituteTypeVariables( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror receiverType, + Element member, + AnnotatedTypeMirror memberType) { + + // Basic Algorithm: + // 1. Find the enclosingClassOfMember of the element + // 2. Find the base type of enclosingClassOfMember (e.g. type of enclosingClassOfMember as + // supertype of passed type) + // 3. Substitute for type variables if any exist + TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); + DeclaredType enclosingType = (DeclaredType) enclosingClassOfMember.asType(); + Map mappings = new HashMap<>(); + + // Look for all enclosing types that have type variables + // and collect type to be substituted for those type variables + while (enclosingType != null) { + TypeElement enclosingTypeElement = (TypeElement) enclosingType.asElement(); + addTypeVarMappings(types, atypeFactory, receiverType, enclosingTypeElement, mappings); + if (enclosingType.getEnclosingType() != null + && enclosingType.getEnclosingType().getKind() == TypeKind.DECLARED) { + enclosingType = (DeclaredType) enclosingType.getEnclosingType(); + } else { + enclosingType = null; + } + } - /** - * Returns all the supertypes (direct or indirect) of the given declared type. - * - * @param type a declared type - * @return all the supertypes of the given type - */ - public static Set getSuperTypes(AnnotatedDeclaredType type) { + if (!mappings.isEmpty()) { + memberType = atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); + } - Set supertypes = new LinkedHashSet<>(); - if (type == null) { - return supertypes; + return memberType; } - // Set up a stack containing the type mirror of subtype, which - // is our starting point. - Deque stack = new ArrayDeque<>(); - stack.push(type); - - while (!stack.isEmpty()) { - AnnotatedDeclaredType current = stack.pop(); - - // For each direct supertype of the current type, if it - // hasn't already been visited, push it onto the stack and - // add it to our supertypes set. - for (AnnotatedDeclaredType supertype : current.directSupertypes()) { - if (!supertypes.contains(supertype)) { - stack.push(supertype); - supertypes.add(supertype); + private static void addTypeVarMappings( + Types types, + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror t, + TypeElement enclosingClassOfElem, + Map mappings) { + if (enclosingClassOfElem.getTypeParameters().isEmpty()) { + return; + } + AnnotatedDeclaredType enclosingType = atypeFactory.getAnnotatedType(enclosingClassOfElem); + AnnotatedDeclaredType base = + (AnnotatedDeclaredType) asOuterSuper(types, atypeFactory, t, enclosingType); + base = (AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(base); + + List ownerParams = + new ArrayList<>(enclosingType.getTypeArguments().size()); + for (AnnotatedTypeMirror typeParam : enclosingType.getTypeArguments()) { + if (typeParam.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF( + StringsPlume.joinLines( + "Type arguments of a declaration should be type variables.", + " enclosingClassOfElem=" + enclosingClassOfElem, + " enclosingType=" + enclosingType, + " typeMirror=" + t)); + } + ownerParams.add((AnnotatedTypeVariable) typeParam); + } + + List baseParams = base.getTypeArguments(); + if (ownerParams.size() != baseParams.size() && !base.isUnderlyingTypeRaw()) { + throw new BugInCF( + StringsPlume.joinLines( + "Unexpected number of parameters.", + "enclosingType=" + enclosingType, + "baseType=" + base)); + } + if (!ownerParams.isEmpty() && baseParams.isEmpty() && base.isUnderlyingTypeRaw()) { + // If base type was raw and the type arguments are missing, set them to the erased + // type of the type variable (which is the erased type of the upper bound). + baseParams = CollectionsPlume.mapList(AnnotatedTypeVariable::getErased, ownerParams); } - } - } - return Collections.unmodifiableSet(supertypes); - } - - /** - * Given a method, return the methods that it overrides. - * - * @param method the overriding method - * @return a map from types to methods that {@code method} overrides - */ - public static Map overriddenMethods( - Elements elements, AnnotatedTypeFactory atypeFactory, ExecutableElement method) { - TypeElement elem = (TypeElement) method.getEnclosingElement(); - AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(elem); - Collection supertypes = getSuperTypes(type); - return overriddenMethods(elements, method, supertypes); - } - - /** - * Given a method and all supertypes (recursively) of the method's containing class, returns the - * methods that the method overrides. - * - * @param method the overriding method - * @param supertypes the set of supertypes to check for methods that are overridden by {@code - * method} - * @return a map from types to methods that {@code method} overrides - */ - public static Map overriddenMethods( - Elements elements, ExecutableElement method, Collection supertypes) { - - Map overrides = new LinkedHashMap<>(); - - for (AnnotatedDeclaredType supertype : supertypes) { - TypeElement superElement = (TypeElement) supertype.getUnderlyingType().asElement(); - assert superElement != null; - // For all method in the supertype, add it to the set if - // it overrides the given method. - for (ExecutableElement supermethod : - ElementFilter.methodsIn(superElement.getEnclosedElements())) { - if (elements.overrides(method, supermethod, superElement)) { - overrides.put(supertype, supermethod); - break; + for (int i = 0; i < ownerParams.size(); ++i) { + mappings.put(ownerParams.get(i).getUnderlyingType(), baseParams.get(i).asUse()); } - } } - return Collections.unmodifiableMap(overrides); - } - - /** - * A pair of an empty map and false. Used in {@link #findTypeArguments(AnnotatedTypeFactory, - * ExpressionTree, ExecutableElement, AnnotatedExecutableType, boolean)}. - */ - private static final IPair, Boolean> emptyFalsePair = - IPair.of(Collections.emptyMap(), false); - ; - - /** - * Given a method or constructor invocation, return a mapping of the type variables to their type - * arguments, if any exist. - * - *

          It uses the method or constructor invocation type arguments if they were specified and - * otherwise it infers them based on the passed arguments or the return type context, according to - * JLS 15.12.2. - * - * @param atypeFactory the annotated type factory - * @param expr the method or constructor invocation tree; the passed argument has to be a subtype - * of MethodInvocationTree or NewClassTree - * @param elt the element corresponding to the tree - * @param preType the (partially annotated) type corresponding to the tree - the result of - * AnnotatedTypes.asMemberOf with the receiver and elt - * @param inferTypeArgs whether the type argument should be inferred - * @return the mapping of type variables to type arguments for this method or constructor - * invocation, and whether unchecked conversion was required to infer the type arguments - */ - public static IPair, Boolean> findTypeArguments( - AnnotatedTypeFactory atypeFactory, - ExpressionTree expr, - ExecutableElement elt, - AnnotatedExecutableType preType, - boolean inferTypeArgs) { - if (expr.getKind() != Kind.MEMBER_REFERENCE - && elt.getTypeParameters().isEmpty() - && !TreeUtils.isDiamondTree(expr)) { - return emptyFalsePair; + /** + * Substitutes type arguments from raw types for type variables in {@code memberType}. + * + * @param atypeFactory the type factory + * @param member the element with type {@code memberType}; used to obtain the enclosing type + * @param memberType the type to side-effect + * @return memberType, with type arguments substituted for type variables + */ + private static AnnotatedTypeMirror substituteTypeArgsFromRawTypes( + AnnotatedTypeFactory atypeFactory, Element member, AnnotatedTypeMirror memberType) { + TypeElement enclosingClassOfMember = ElementUtils.enclosingTypeElement(member); + Map mappings = new HashMap<>(); + + while (enclosingClassOfMember != null) { + if (!enclosingClassOfMember.getTypeParameters().isEmpty()) { + AnnotatedDeclaredType enclosingType = + atypeFactory.getAnnotatedType(enclosingClassOfMember); + AnnotatedDeclaredType erasedEnclosingType = + atypeFactory.getAnnotatedType(enclosingClassOfMember); + List typeArguments = enclosingType.getTypeArguments(); + for (int i = 0; i < typeArguments.size(); i++) { + AnnotatedTypeMirror type = typeArguments.get(i); + AnnotatedTypeMirror enclosedTypeArg = + erasedEnclosingType.getTypeArguments().get(i); + AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type; + mappings.put(typeParameter.getUnderlyingType(), enclosedTypeArg); + } + } + enclosingClassOfMember = + ElementUtils.enclosingTypeElement(enclosingClassOfMember.getEnclosingElement()); + } + + if (!mappings.isEmpty()) { + return atypeFactory.getTypeVarSubstitutor().substitute(mappings, memberType); + } + + return memberType; } - List targs; - if (expr instanceof MethodInvocationTree) { - targs = ((MethodInvocationTree) expr).getTypeArguments(); - } else if (expr instanceof NewClassTree) { - targs = ((NewClassTree) expr).getTypeArguments(); - } else if (expr instanceof MemberReferenceTree) { - MemberReferenceTree memRef = ((MemberReferenceTree) expr); - if (inferTypeArgs && TreeUtils.needsTypeArgInference(memRef)) { - InferenceResult inferenceResult = - atypeFactory.getTypeArgumentInference().inferTypeArgs(atypeFactory, expr, preType); - return IPair.of( - inferenceResult.getTypeArgumentsForExpression(expr), - inferenceResult.isUncheckedConversion()); - } - targs = memRef.getTypeArguments(); - if (memRef.getTypeArguments() == null) { - return emptyFalsePair; - } - } else { - // This case should never happen. - throw new BugInCF("AnnotatedTypes.findTypeArguments: unexpected tree: " + expr); + /** + * Returns all the supertypes (direct or indirect) of the given declared type. + * + * @param type a declared type + * @return all the supertypes of the given type + */ + public static Set getSuperTypes(AnnotatedDeclaredType type) { + + Set supertypes = new LinkedHashSet<>(); + if (type == null) { + return supertypes; + } + + // Set up a stack containing the type mirror of subtype, which + // is our starting point. + Deque stack = new ArrayDeque<>(); + stack.push(type); + + while (!stack.isEmpty()) { + AnnotatedDeclaredType current = stack.pop(); + + // For each direct supertype of the current type, if it + // hasn't already been visited, push it onto the stack and + // add it to our supertypes set. + for (AnnotatedDeclaredType supertype : current.directSupertypes()) { + if (!supertypes.contains(supertype)) { + stack.push(supertype); + supertypes.add(supertype); + } + } + } + + return Collections.unmodifiableSet(supertypes); } - if (preType.getReceiverType() != null) { - DeclaredType receiverTypeMirror = preType.getReceiverType().getUnderlyingType(); - if (TypesUtils.isRaw(receiverTypeMirror) - && elt.getEnclosingElement().equals(receiverTypeMirror.asElement())) { - return emptyFalsePair; - } + /** + * Given a method, return the methods that it overrides. + * + * @param method the overriding method + * @return a map from types to methods that {@code method} overrides + */ + public static Map overriddenMethods( + Elements elements, AnnotatedTypeFactory atypeFactory, ExecutableElement method) { + TypeElement elem = (TypeElement) method.getEnclosingElement(); + AnnotatedDeclaredType type = atypeFactory.getAnnotatedType(elem); + Collection supertypes = getSuperTypes(type); + return overriddenMethods(elements, method, supertypes); } - // Has the user supplied type arguments? - if (!targs.isEmpty() && !TreeUtils.isDiamondTree(expr)) { - List tvars = preType.getTypeVariables(); - if (tvars.isEmpty()) { - // This happens when the method is invoked with a raw receiver. - return emptyFalsePair; - } - - Map typeArguments = new HashMap<>(); - for (int i = 0; i < elt.getTypeParameters().size(); ++i) { - AnnotatedTypeVariable typeVar = tvars.get(i); - AnnotatedTypeMirror typeArg = atypeFactory.getAnnotatedTypeFromTypeTree(targs.get(i)); - // TODO: the call to getTypeParameterDeclaration shouldn't be necessary - typeVar - // already should be a declaration. - typeArguments.put(typeVar.getUnderlyingType(), typeArg); - } - return IPair.of(typeArguments, false); - } else { - if (inferTypeArgs) { - InferenceResult inferenceResult = - atypeFactory.getTypeArgumentInference().inferTypeArgs(atypeFactory, expr, preType); - return IPair.of( - inferenceResult.getTypeArgumentsForExpression(expr), - inferenceResult.isUncheckedConversion()); - } else { - return emptyFalsePair; - } + /** + * Given a method and all supertypes (recursively) of the method's containing class, returns the + * methods that the method overrides. + * + * @param method the overriding method + * @param supertypes the set of supertypes to check for methods that are overridden by {@code + * method} + * @return a map from types to methods that {@code method} overrides + */ + public static Map overriddenMethods( + Elements elements, + ExecutableElement method, + Collection supertypes) { + + Map overrides = new LinkedHashMap<>(); + + for (AnnotatedDeclaredType supertype : supertypes) { + TypeElement superElement = (TypeElement) supertype.getUnderlyingType().asElement(); + assert superElement != null; + // For all method in the supertype, add it to the set if + // it overrides the given method. + for (ExecutableElement supermethod : + ElementFilter.methodsIn(superElement.getEnclosedElements())) { + if (elements.overrides(method, supermethod, superElement)) { + overrides.put(supertype, supermethod); + break; + } + } + } + + return Collections.unmodifiableMap(overrides); } - } - - /** - * Returns the lub of two annotated types. - * - * @param atypeFactory the type factory - * @param type1 a type - * @param type2 another type - * @return the lub of {@code type1} and {@code type2} - */ - public static AnnotatedTypeMirror leastUpperBound( - AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - TypeMirror lub = - TypesUtils.leastUpperBound( - type1.getUnderlyingType(), type2.getUnderlyingType(), atypeFactory.getProcessingEnv()); - return leastUpperBound(atypeFactory, type1, type2, lub); - } - - /** - * Returns the lub, whose underlying type is {@code lubTypeMirror} of two annotated types. - * - * @param atypeFactory a type factory - * @param type1 annotated type whose underlying type must be a subtype or convertible to - * lubTypeMirror - * @param type2 annotated type whose underlying type must be a subtype or convertible to - * lubTypeMirror - * @param lubTypeMirror underlying type of the returned lub - * @return the lub of type1 and type2 with underlying type lubTypeMirror - */ - public static AnnotatedTypeMirror leastUpperBound( - AnnotatedTypeFactory atypeFactory, - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - TypeMirror lubTypeMirror) { - return new AtmLubVisitor(atypeFactory).lub(type1, type2, lubTypeMirror); - } - - /** - * Returns the "annotated greatest lower bound" of {@code type1} and {@code type2}. - * - *

          Suppose that there is an expression e with annotated type T. The underlying type of T must - * be the same as javac's type for e. (This is a requirement of the Checker Framework.) As a - * corollary, when computing a glb of atype1 and atype2, it is required that - * underlyingType(cfGLB(atype1, atype2) == glb(javacGLB(underlyingType(atype1), - * underlyingType(atype2)). Because of this requirement, the return value of this method (the - * "annotated GLB") may not be a subtype of one of the types. - * - *

          The "annotated greatest lower bound" is defined as follows: - * - *

            - *
          1. If the underlying type of {@code type1} and {@code type2} are the same, then return a - * copy of {@code type1} whose primary annotations are the greatest lower bound of the - * primary annotations on {@code type1} and {@code type2}. - *
          2. If the underlying type of {@code type1} is a subtype of the underlying type of {@code - * type2}, then return a copy of {@code type1} whose primary annotations are the greatest - * lower bound of the primary annotations on {@code type1} and {@code type2}. - *
          3. If the underlying type of {@code type1} is a supertype of the underlying type of {@code - * type2}, then return a copy of {@code type2} whose primary annotations are the greatest - * lower bound of the primary annotations on {@code type1} and {@code type2}. - *
          4. If the underlying type of {@code type1} and {@code type2} are not in a subtyping - * relationship, then return an annotated intersection type whose bounds are {@code type1} - * and {@code type2}. - *
          - * - * @param atypeFactory the AnnotatedTypeFactory - * @param type1 annotated type - * @param type2 annotated type - * @return the annotated glb of type1 and type2 - */ - public static AnnotatedTypeMirror annotatedGLB( - AnnotatedTypeFactory atypeFactory, AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - TypeMirror tm1 = type1.getUnderlyingType(); - TypeMirror tm2 = type2.getUnderlyingType(); - TypeMirror glbJava = TypesUtils.greatestLowerBound(tm1, tm2, atypeFactory.getProcessingEnv()); - Types types = atypeFactory.types; - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - if (types.isSubtype(tm1, tm2)) { - return glbSubtype(qualHierarchy, type1, type2); - } else if (types.isSubtype(tm2, tm1)) { - return glbSubtype(qualHierarchy, type2, type1); + + /** + * A pair of an empty map and false. Used in {@link #findTypeArguments(AnnotatedTypeFactory, + * ExpressionTree, ExecutableElement, AnnotatedExecutableType, boolean)}. + */ + private static final IPair, Boolean> emptyFalsePair = + IPair.of(Collections.emptyMap(), false); + ; + + /** + * Given a method or constructor invocation, return a mapping of the type variables to their + * type arguments, if any exist. + * + *

          It uses the method or constructor invocation type arguments if they were specified and + * otherwise it infers them based on the passed arguments or the return type context, according + * to JLS 15.12.2. + * + * @param atypeFactory the annotated type factory + * @param expr the method or constructor invocation tree; the passed argument has to be a + * subtype of MethodInvocationTree or NewClassTree + * @param elt the element corresponding to the tree + * @param preType the (partially annotated) type corresponding to the tree - the result of + * AnnotatedTypes.asMemberOf with the receiver and elt + * @param inferTypeArgs whether the type argument should be inferred + * @return the mapping of type variables to type arguments for this method or constructor + * invocation, and whether unchecked conversion was required to infer the type arguments + */ + public static IPair, Boolean> findTypeArguments( + AnnotatedTypeFactory atypeFactory, + ExpressionTree expr, + ExecutableElement elt, + AnnotatedExecutableType preType, + boolean inferTypeArgs) { + if (expr.getKind() != Kind.MEMBER_REFERENCE + && elt.getTypeParameters().isEmpty() + && !TreeUtils.isDiamondTree(expr)) { + return emptyFalsePair; + } + + List targs; + if (expr instanceof MethodInvocationTree) { + targs = ((MethodInvocationTree) expr).getTypeArguments(); + } else if (expr instanceof NewClassTree) { + targs = ((NewClassTree) expr).getTypeArguments(); + } else if (expr instanceof MemberReferenceTree) { + MemberReferenceTree memRef = ((MemberReferenceTree) expr); + if (inferTypeArgs && TreeUtils.needsTypeArgInference(memRef)) { + InferenceResult inferenceResult = + atypeFactory + .getTypeArgumentInference() + .inferTypeArgs(atypeFactory, expr, preType); + return IPair.of( + inferenceResult.getTypeArgumentsForExpression(expr), + inferenceResult.isUncheckedConversion()); + } + targs = memRef.getTypeArguments(); + if (memRef.getTypeArguments() == null) { + return emptyFalsePair; + } + } else { + // This case should never happen. + throw new BugInCF("AnnotatedTypes.findTypeArguments: unexpected tree: " + expr); + } + + if (preType.getReceiverType() != null) { + DeclaredType receiverTypeMirror = preType.getReceiverType().getUnderlyingType(); + if (TypesUtils.isRaw(receiverTypeMirror) + && elt.getEnclosingElement().equals(receiverTypeMirror.asElement())) { + return emptyFalsePair; + } + } + + // Has the user supplied type arguments? + if (!targs.isEmpty() && !TreeUtils.isDiamondTree(expr)) { + List tvars = preType.getTypeVariables(); + if (tvars.isEmpty()) { + // This happens when the method is invoked with a raw receiver. + return emptyFalsePair; + } + + Map typeArguments = new HashMap<>(); + for (int i = 0; i < elt.getTypeParameters().size(); ++i) { + AnnotatedTypeVariable typeVar = tvars.get(i); + AnnotatedTypeMirror typeArg = + atypeFactory.getAnnotatedTypeFromTypeTree(targs.get(i)); + // TODO: the call to getTypeParameterDeclaration shouldn't be necessary - typeVar + // already should be a declaration. + typeArguments.put(typeVar.getUnderlyingType(), typeArg); + } + return IPair.of(typeArguments, false); + } else { + if (inferTypeArgs) { + InferenceResult inferenceResult = + atypeFactory + .getTypeArgumentInference() + .inferTypeArgs(atypeFactory, expr, preType); + return IPair.of( + inferenceResult.getTypeArgumentsForExpression(expr), + inferenceResult.isUncheckedConversion()); + } else { + return emptyFalsePair; + } + } } - if (types.isSameType(tm1, glbJava)) { - return glbSubtype(qualHierarchy, type1, type2); - } else if (types.isSameType(tm2, glbJava)) { - return glbSubtype(qualHierarchy, type2, type1); + /** + * Returns the lub of two annotated types. + * + * @param atypeFactory the type factory + * @param type1 a type + * @param type2 another type + * @return the lub of {@code type1} and {@code type2} + */ + public static AnnotatedTypeMirror leastUpperBound( + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2) { + TypeMirror lub = + TypesUtils.leastUpperBound( + type1.getUnderlyingType(), + type2.getUnderlyingType(), + atypeFactory.getProcessingEnv()); + return leastUpperBound(atypeFactory, type1, type2, lub); } - if (glbJava.getKind() != TypeKind.INTERSECTION) { - // If one type isn't a subtype of the other, then GLB must be an intersection. - throw new BugInCF( - "AnnotatedTypes#annotatedGLB: expected intersection, got [%s] %s. " - + "type1: %s, type2: %s", - glbJava.getKind(), glbJava, type1, type2); + /** + * Returns the lub, whose underlying type is {@code lubTypeMirror} of two annotated types. + * + * @param atypeFactory a type factory + * @param type1 annotated type whose underlying type must be a subtype or convertible to + * lubTypeMirror + * @param type2 annotated type whose underlying type must be a subtype or convertible to + * lubTypeMirror + * @param lubTypeMirror underlying type of the returned lub + * @return the lub of type1 and type2 with underlying type lubTypeMirror + */ + public static AnnotatedTypeMirror leastUpperBound( + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + TypeMirror lubTypeMirror) { + return new AtmLubVisitor(atypeFactory).lub(type1, type2, lubTypeMirror); } - AnnotationMirrorSet set1 = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type1); - AnnotationMirrorSet set2 = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type2); - Set glbAnnos = - qualHierarchy.greatestLowerBoundsShallow(set1, tm1, set2, tm2); - - AnnotatedIntersectionType glb = - (AnnotatedIntersectionType) AnnotatedTypeMirror.createType(glbJava, atypeFactory, false); - - List newBounds = new ArrayList<>(2); - for (AnnotatedTypeMirror bound : glb.getBounds()) { - if (types.isSameType(bound.getUnderlyingType(), tm1)) { - newBounds.add(type1.deepCopy()); - } else if (types.isSameType(bound.getUnderlyingType(), tm2)) { - newBounds.add(type2.deepCopy()); - } else if (type1.getKind() == TypeKind.INTERSECTION) { - AnnotatedIntersectionType intertype1 = (AnnotatedIntersectionType) type1; - for (AnnotatedTypeMirror otherBound : intertype1.getBounds()) { - if (types.isSameType(bound.getUnderlyingType(), otherBound.getUnderlyingType())) { - newBounds.add(otherBound.deepCopy()); - } - } - } else if (type2.getKind() == TypeKind.INTERSECTION) { - AnnotatedIntersectionType intertype2 = (AnnotatedIntersectionType) type2; - for (AnnotatedTypeMirror otherBound : intertype2.getBounds()) { - if (types.isSameType(bound.getUnderlyingType(), otherBound.getUnderlyingType())) { - newBounds.add(otherBound.deepCopy()); - } + + /** + * Returns the "annotated greatest lower bound" of {@code type1} and {@code type2}. + * + *

          Suppose that there is an expression e with annotated type T. The underlying type of T must + * be the same as javac's type for e. (This is a requirement of the Checker Framework.) As a + * corollary, when computing a glb of atype1 and atype2, it is required that + * underlyingType(cfGLB(atype1, atype2) == glb(javacGLB(underlyingType(atype1), + * underlyingType(atype2)). Because of this requirement, the return value of this method (the + * "annotated GLB") may not be a subtype of one of the types. + * + *

          The "annotated greatest lower bound" is defined as follows: + * + *

            + *
          1. If the underlying type of {@code type1} and {@code type2} are the same, then return a + * copy of {@code type1} whose primary annotations are the greatest lower bound of the + * primary annotations on {@code type1} and {@code type2}. + *
          2. If the underlying type of {@code type1} is a subtype of the underlying type of {@code + * type2}, then return a copy of {@code type1} whose primary annotations are the greatest + * lower bound of the primary annotations on {@code type1} and {@code type2}. + *
          3. If the underlying type of {@code type1} is a supertype of the underlying type of {@code + * type2}, then return a copy of {@code type2} whose primary annotations are the greatest + * lower bound of the primary annotations on {@code type1} and {@code type2}. + *
          4. If the underlying type of {@code type1} and {@code type2} are not in a subtyping + * relationship, then return an annotated intersection type whose bounds are {@code type1} + * and {@code type2}. + *
          + * + * @param atypeFactory the AnnotatedTypeFactory + * @param type1 annotated type + * @param type2 annotated type + * @return the annotated glb of type1 and type2 + */ + public static AnnotatedTypeMirror annotatedGLB( + AnnotatedTypeFactory atypeFactory, + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2) { + TypeMirror tm1 = type1.getUnderlyingType(); + TypeMirror tm2 = type2.getUnderlyingType(); + TypeMirror glbJava = + TypesUtils.greatestLowerBound(tm1, tm2, atypeFactory.getProcessingEnv()); + Types types = atypeFactory.types; + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + if (types.isSubtype(tm1, tm2)) { + return glbSubtype(qualHierarchy, type1, type2); + } else if (types.isSubtype(tm2, tm1)) { + return glbSubtype(qualHierarchy, type2, type1); } - } else { - throw new BugInCF( - "Neither %s nor %s is one of the intersection bounds in %s. Bound: %s", - type1, type2, bound, glb); - } - } - glb.setBounds(newBounds); - glb.addAnnotations(glbAnnos); - return glb; - } - - /** - * Returns the annotated greatest lower bound of {@code subtype} and {@code supertype}, where the - * underlying Java types are in a subtyping relationship. - * - *

          This handles cases 1, 2, and 3 mentioned in the Javadoc of {@link - * #annotatedGLB(AnnotatedTypeFactory, AnnotatedTypeMirror, AnnotatedTypeMirror)}. - * - * @param qualHierarchy the qualifier hierarchy - * @param subtype annotated type whose underlying type is a subtype of {@code supertype} - * @param supertype annotated type whose underlying type is a supertype of {@code subtype} - * @return the annotated greatest lower bound of {@code subtype} and {@code supertype} - */ - private static AnnotatedTypeMirror glbSubtype( - QualifierHierarchy qualHierarchy, - AnnotatedTypeMirror subtype, - AnnotatedTypeMirror supertype) { - AnnotatedTypeMirror glb = subtype.deepCopy(); - glb.clearAnnotations(); - - TypeMirror subTM = subtype.getUnderlyingType(); - TypeMirror superTM = supertype.getUnderlyingType(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror subAnno = subtype.getAnnotationInHierarchy(top); - AnnotationMirror superAnno = supertype.getAnnotationInHierarchy(top); - if (subAnno != null && superAnno != null) { - glb.addAnnotation( - qualHierarchy.greatestLowerBoundShallow(subAnno, subTM, superAnno, superTM)); - } else if (subAnno == null && superAnno == null) { - if (subtype.getKind() != TypeKind.TYPEVAR || supertype.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF( - "Missing primary annotations: subtype: %s, supertype: %s", subtype, supertype); + if (types.isSameType(tm1, glbJava)) { + return glbSubtype(qualHierarchy, type1, type2); + } else if (types.isSameType(tm2, glbJava)) { + return glbSubtype(qualHierarchy, type2, type1); } - } else if (subAnno == null) { - if (subtype.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF("Missing primary annotations: subtype: %s", subtype); + + if (glbJava.getKind() != TypeKind.INTERSECTION) { + // If one type isn't a subtype of the other, then GLB must be an intersection. + throw new BugInCF( + "AnnotatedTypes#annotatedGLB: expected intersection, got [%s] %s. " + + "type1: %s, type2: %s", + glbJava.getKind(), glbJava, type1, type2); } - AnnotationMirror ubAnno = subtype.getEffectiveAnnotationInHierarchy(top); - if (!qualHierarchy.isSubtypeQualifiersOnly(ubAnno, superAnno)) { - // Instead of superAnno <: ubAnno check for ubAnno glbAnnos = + qualHierarchy.greatestLowerBoundsShallow(set1, tm1, set2, tm2); + + AnnotatedIntersectionType glb = + (AnnotatedIntersectionType) + AnnotatedTypeMirror.createType(glbJava, atypeFactory, false); + + List newBounds = new ArrayList<>(2); + for (AnnotatedTypeMirror bound : glb.getBounds()) { + if (types.isSameType(bound.getUnderlyingType(), tm1)) { + newBounds.add(type1.deepCopy()); + } else if (types.isSameType(bound.getUnderlyingType(), tm2)) { + newBounds.add(type2.deepCopy()); + } else if (type1.getKind() == TypeKind.INTERSECTION) { + AnnotatedIntersectionType intertype1 = (AnnotatedIntersectionType) type1; + for (AnnotatedTypeMirror otherBound : intertype1.getBounds()) { + if (types.isSameType( + bound.getUnderlyingType(), otherBound.getUnderlyingType())) { + newBounds.add(otherBound.deepCopy()); + } + } + } else if (type2.getKind() == TypeKind.INTERSECTION) { + AnnotatedIntersectionType intertype2 = (AnnotatedIntersectionType) type2; + for (AnnotatedTypeMirror otherBound : intertype2.getBounds()) { + if (types.isSameType( + bound.getUnderlyingType(), otherBound.getUnderlyingType())) { + newBounds.add(otherBound.deepCopy()); + } + } + } else { + throw new BugInCF( + "Neither %s nor %s is one of the intersection bounds in %s. Bound: %s", + type1, type2, bound, glb); + } } - } else { - throw new BugInCF("GLB: subtype: %s, supertype: %s", subtype, supertype); - } - } - return glb; - } - - /** - * Returns the method parameters for the invoked method (or constructor), with the same number of - * arguments as passed to the invocation tree. - * - *

          This expands the parameters if the call uses varargs or contracts the parameters if the call - * is to an anonymous class that extends a class with an enclosing type. If the call is neither of - * these, then the parameters are returned unchanged. - * - * @param atypeFactory the type factory to use for fetching annotated types - * @param method the method or constructor's type - * @param args the arguments to the method or constructor invocation - * @param tree the NewClassTree if method is a constructor - * @return a list of the types that the invocation arguments need to be subtype of; has the same - * length as {@code args} - */ - public static List adaptParameters( - AnnotatedTypeFactory atypeFactory, - AnnotatedExecutableType method, - List args, - @Nullable NewClassTree tree) { - List parameters = method.getParameterTypes(); - // Handle anonymous constructors that extend a class with an enclosing type. - // There is a mismatch between the number of parameters and arguments when - // the following conditions are met: - // 1. Java version >= 11 - // 2. the method is an anonymous constructor - // 3. the constructor is invoked with an explicit enclosing expression - // In the case, we should remove the first parameter. - if (SystemUtil.jreVersion >= 11 - && tree != null - && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( - method.getElement(), tree)) { - if (parameters.size() != args.size() || args.isEmpty()) { - List p = new ArrayList<>(parameters.size()); - p.addAll(parameters.subList(1, parameters.size())); - parameters = p; - } + + glb.setBounds(newBounds); + glb.addAnnotations(glbAnnos); + return glb; } - // Handle vararg methods. - if (!method.getElement().isVarArgs()) { - return parameters; + /** + * Returns the annotated greatest lower bound of {@code subtype} and {@code supertype}, where + * the underlying Java types are in a subtyping relationship. + * + *

          This handles cases 1, 2, and 3 mentioned in the Javadoc of {@link + * #annotatedGLB(AnnotatedTypeFactory, AnnotatedTypeMirror, AnnotatedTypeMirror)}. + * + * @param qualHierarchy the qualifier hierarchy + * @param subtype annotated type whose underlying type is a subtype of {@code supertype} + * @param supertype annotated type whose underlying type is a supertype of {@code subtype} + * @return the annotated greatest lower bound of {@code subtype} and {@code supertype} + */ + private static AnnotatedTypeMirror glbSubtype( + QualifierHierarchy qualHierarchy, + AnnotatedTypeMirror subtype, + AnnotatedTypeMirror supertype) { + AnnotatedTypeMirror glb = subtype.deepCopy(); + glb.clearAnnotations(); + + TypeMirror subTM = subtype.getUnderlyingType(); + TypeMirror superTM = supertype.getUnderlyingType(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror subAnno = subtype.getAnnotationInHierarchy(top); + AnnotationMirror superAnno = supertype.getAnnotationInHierarchy(top); + if (subAnno != null && superAnno != null) { + glb.addAnnotation( + qualHierarchy.greatestLowerBoundShallow( + subAnno, subTM, superAnno, superTM)); + } else if (subAnno == null && superAnno == null) { + if (subtype.getKind() != TypeKind.TYPEVAR + || supertype.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF( + "Missing primary annotations: subtype: %s, supertype: %s", + subtype, supertype); + } + } else if (subAnno == null) { + if (subtype.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Missing primary annotations: subtype: %s", subtype); + } + AnnotationMirror ubAnno = subtype.getEffectiveAnnotationInHierarchy(top); + if (!qualHierarchy.isSubtypeQualifiersOnly(ubAnno, superAnno)) { + // Instead of superAnno <: ubAnno check for ubAnno This expands the parameters if the call uses varargs or contracts the parameters if the + * call is to an anonymous class that extends a class with an enclosing type. If the call is + * neither of these, then the parameters are returned unchanged. + * + * @param atypeFactory the type factory to use for fetching annotated types + * @param method the method or constructor's type + * @param args the arguments to the method or constructor invocation + * @param tree the NewClassTree if method is a constructor + * @return a list of the types that the invocation arguments need to be subtype of; has the same + * length as {@code args} + */ + public static List adaptParameters( + AnnotatedTypeFactory atypeFactory, + AnnotatedExecutableType method, + List args, + @Nullable NewClassTree tree) { + List parameters = method.getParameterTypes(); + // Handle anonymous constructors that extend a class with an enclosing type. + // There is a mismatch between the number of parameters and arguments when + // the following conditions are met: + // 1. Java version >= 11 + // 2. the method is an anonymous constructor + // 3. the constructor is invoked with an explicit enclosing expression + // In the case, we should remove the first parameter. + if (SystemUtil.jreVersion >= 11 + && tree != null + && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( + method.getElement(), tree)) { + if (parameters.size() != args.size() || args.isEmpty()) { + List p = new ArrayList<>(parameters.size()); + p.addAll(parameters.subList(1, parameters.size())); + parameters = p; + } + } + + // Handle vararg methods. + if (!method.getElement().isVarArgs()) { + return parameters; + } + + AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1); + + if (parameters.size() == args.size()) { + // Check if one sent an element or an array + AnnotatedTypeMirror lastArg = atypeFactory.getAnnotatedType(args.get(args.size() - 1)); + if (lastArg.getKind() == TypeKind.NULL + || (lastArg.getKind() == TypeKind.ARRAY + && getArrayDepth(varargs) + == getArrayDepth((AnnotatedArrayType) lastArg))) { + return parameters; + } + } + + parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); + for (int i = args.size() - parameters.size(); i > 0; --i) { + parameters.add(varargs.getComponentType().deepCopy()); + } - if (parameters.size() == args.size()) { - // Check if one sent an element or an array - AnnotatedTypeMirror lastArg = atypeFactory.getAnnotatedType(args.get(args.size() - 1)); - if (lastArg.getKind() == TypeKind.NULL - || (lastArg.getKind() == TypeKind.ARRAY - && getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg))) { return parameters; - } } - parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); - for (int i = args.size() - parameters.size(); i > 0; --i) { - parameters.add(varargs.getComponentType().deepCopy()); + /** + * Returns the method parameters for the invoked method, with the same number of formal + * parameters as the arguments in the given list. + * + * @param method the method's type + * @param args the types of the arguments at the call site + * @return the method parameters, with varargs replaced by instances of its component type + */ + public static List expandVarArgsParametersFromTypes( + AnnotatedExecutableType method, List args) { + List parameters = method.getParameterTypes(); + if (!method.getElement().isVarArgs()) { + return parameters; + } + + AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1); + + if (parameters.size() == args.size()) { + // Check if one sent an element or an array + AnnotatedTypeMirror lastArg = args.get(args.size() - 1); + if (lastArg.getKind() == TypeKind.ARRAY + && (getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg) + // If the array depths don't match, but the component type of the vararg + // is a type variable, then that type variable might later be + // substituted for an array. + || varargs.getComponentType().getKind() == TypeKind.TYPEVAR)) { + return parameters; + } + } + + parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); + for (int i = args.size() - parameters.size(); i > 0; --i) { + parameters.add(varargs.getComponentType()); + } + + return parameters; } - return parameters; - } - - /** - * Returns the method parameters for the invoked method, with the same number of formal parameters - * as the arguments in the given list. - * - * @param method the method's type - * @param args the types of the arguments at the call site - * @return the method parameters, with varargs replaced by instances of its component type - */ - public static List expandVarArgsParametersFromTypes( - AnnotatedExecutableType method, List args) { - List parameters = method.getParameterTypes(); - if (!method.getElement().isVarArgs()) { - return parameters; + /** + * Given an AnnotatedExecutableType of a method or constructor declaration, get the parameter + * type expected at the indexth position (unwrapping varargs if necessary). + * + * @param methodType the type of a method or constructor containing the parameter to return + * @param index position of the parameter type to return + * @return the type of the parameter in the index position. If that parameter is a varArgs, + * return the component type of the varargs and NOT the array type. + */ + public static AnnotatedTypeMirror getAnnotatedTypeMirrorOfParameter( + AnnotatedExecutableType methodType, int index) { + List parameterTypes = methodType.getParameterTypes(); + boolean hasVarArg = methodType.getElement().isVarArgs(); + + int lastIndex = parameterTypes.size() - 1; + AnnotatedTypeMirror lastType = parameterTypes.get(lastIndex); + boolean parameterBeforeVarargs = index < lastIndex; + if (!parameterBeforeVarargs && lastType instanceof AnnotatedArrayType) { + AnnotatedArrayType arrayType = (AnnotatedArrayType) lastType; + if (hasVarArg) { + return arrayType.getComponentType(); + } + } + return parameterTypes.get(index); } - AnnotatedArrayType varargs = (AnnotatedArrayType) parameters.get(parameters.size() - 1); - - if (parameters.size() == args.size()) { - // Check if one sent an element or an array - AnnotatedTypeMirror lastArg = args.get(args.size() - 1); - if (lastArg.getKind() == TypeKind.ARRAY - && (getArrayDepth(varargs) == getArrayDepth((AnnotatedArrayType) lastArg) - // If the array depths don't match, but the component type of the vararg - // is a type variable, then that type variable might later be - // substituted for an array. - || varargs.getComponentType().getKind() == TypeKind.TYPEVAR)) { - return parameters; - } + /** + * Returns the depth of the array type of the provided array. + * + * @param array the type of the array + * @return the depth of the provided array + */ + public static int getArrayDepth(AnnotatedArrayType array) { + int counter = 0; + AnnotatedTypeMirror type = array; + while (type.getKind() == TypeKind.ARRAY) { + counter++; + type = ((AnnotatedArrayType) type).getComponentType(); + } + return counter; } - parameters = new ArrayList<>(parameters.subList(0, parameters.size() - 1)); - for (int i = args.size() - parameters.size(); i > 0; --i) { - parameters.add(varargs.getComponentType()); + // The innermost *array* type. + public static AnnotatedTypeMirror innerMostType(AnnotatedTypeMirror t) { + AnnotatedTypeMirror inner = t; + while (inner.getKind() == TypeKind.ARRAY) { + inner = ((AnnotatedArrayType) inner).getComponentType(); + } + return inner; } - return parameters; - } - - /** - * Given an AnnotatedExecutableType of a method or constructor declaration, get the parameter type - * expected at the indexth position (unwrapping varargs if necessary). - * - * @param methodType the type of a method or constructor containing the parameter to return - * @param index position of the parameter type to return - * @return the type of the parameter in the index position. If that parameter is a varArgs, return - * the component type of the varargs and NOT the array type. - */ - public static AnnotatedTypeMirror getAnnotatedTypeMirrorOfParameter( - AnnotatedExecutableType methodType, int index) { - List parameterTypes = methodType.getParameterTypes(); - boolean hasVarArg = methodType.getElement().isVarArgs(); - - int lastIndex = parameterTypes.size() - 1; - AnnotatedTypeMirror lastType = parameterTypes.get(lastIndex); - boolean parameterBeforeVarargs = index < lastIndex; - if (!parameterBeforeVarargs && lastType instanceof AnnotatedArrayType) { - AnnotatedArrayType arrayType = (AnnotatedArrayType) lastType; - if (hasVarArg) { - return arrayType.getComponentType(); - } + /** + * Checks whether type contains the given modifier, also recursively in type arguments and + * arrays. This method might be easier to implement directly as instance method in + * AnnotatedTypeMirror; it corresponds to a "deep" version of {@link + * AnnotatedTypeMirror#hasAnnotation(AnnotationMirror)}. + * + * @param type the type to search + * @param modifier the modifier to search for + * @return whether the type contains the modifier + */ + public static boolean containsModifier(AnnotatedTypeMirror type, AnnotationMirror modifier) { + return containsModifierImpl(type, modifier, new ArrayList<>()); } - return parameterTypes.get(index); - } - - /** - * Returns the depth of the array type of the provided array. - * - * @param array the type of the array - * @return the depth of the provided array - */ - public static int getArrayDepth(AnnotatedArrayType array) { - int counter = 0; - AnnotatedTypeMirror type = array; - while (type.getKind() == TypeKind.ARRAY) { - counter++; - type = ((AnnotatedArrayType) type).getComponentType(); + + /* + * For type variables we might hit the same type again. We keep a list of visited types. + */ + private static boolean containsModifierImpl( + AnnotatedTypeMirror type, + AnnotationMirror modifier, + List visited) { + boolean found = type.hasAnnotation(modifier); + boolean vis = visited.contains(type); + visited.add(type); + + if (!found && !vis) { + if (type.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; + for (AnnotatedTypeMirror typeMirror : declaredType.getTypeArguments()) { + found |= containsModifierImpl(typeMirror, modifier, visited); + if (found) { + break; + } + } + } else if (type.getKind() == TypeKind.ARRAY) { + AnnotatedArrayType arrayType = (AnnotatedArrayType) type; + found = containsModifierImpl(arrayType.getComponentType(), modifier, visited); + } else if (type.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) type; + if (atv.getUpperBound() != null) { + found = containsModifierImpl(atv.getUpperBound(), modifier, visited); + } + if (!found && atv.getLowerBound() != null) { + found = containsModifierImpl(atv.getLowerBound(), modifier, visited); + } + } else if (type.getKind() == TypeKind.WILDCARD) { + AnnotatedWildcardType awc = (AnnotatedWildcardType) type; + if (awc.getExtendsBound() != null) { + found = containsModifierImpl(awc.getExtendsBound(), modifier, visited); + } + if (!found && awc.getSuperBound() != null) { + found = containsModifierImpl(awc.getSuperBound(), modifier, visited); + } + } + } + + return found; } - return counter; - } - - // The innermost *array* type. - public static AnnotatedTypeMirror innerMostType(AnnotatedTypeMirror t) { - AnnotatedTypeMirror inner = t; - while (inner.getKind() == TypeKind.ARRAY) { - inner = ((AnnotatedArrayType) inner).getComponentType(); + + /** java.lang.annotation.Annotation.class canonical name. */ + private static final @CanonicalName String annotationClassName = + java.lang.annotation.Annotation.class.getCanonicalName(); + + /** + * Returns true if the underlying type of this atm is a java.lang.annotation.Annotation. + * + * @return true if the underlying type of this atm is a java.lang.annotation.Annotation + */ + public static boolean isJavaLangAnnotation(AnnotatedTypeMirror atm) { + return TypesUtils.isDeclaredOfName(atm.getUnderlyingType(), annotationClassName); } - return inner; - } - - /** - * Checks whether type contains the given modifier, also recursively in type arguments and arrays. - * This method might be easier to implement directly as instance method in AnnotatedTypeMirror; it - * corresponds to a "deep" version of {@link AnnotatedTypeMirror#hasAnnotation(AnnotationMirror)}. - * - * @param type the type to search - * @param modifier the modifier to search for - * @return whether the type contains the modifier - */ - public static boolean containsModifier(AnnotatedTypeMirror type, AnnotationMirror modifier) { - return containsModifierImpl(type, modifier, new ArrayList<>()); - } - - /* - * For type variables we might hit the same type again. We keep a list of visited types. - */ - private static boolean containsModifierImpl( - AnnotatedTypeMirror type, AnnotationMirror modifier, List visited) { - boolean found = type.hasAnnotation(modifier); - boolean vis = visited.contains(type); - visited.add(type); - - if (!found && !vis) { - if (type.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; - for (AnnotatedTypeMirror typeMirror : declaredType.getTypeArguments()) { - found |= containsModifierImpl(typeMirror, modifier, visited); - if (found) { - break; - } + + /** + * Returns true if atm is an Annotation interface, i.e., an implementation of + * java.lang.annotation.Annotation. Given {@code @interface MyAnno}, a call to {@code + * implementsAnnotation} returns true when called on an AnnotatedDeclaredType representing a use + * of MyAnno. + * + * @return true if atm is an Annotation interface + */ + public static boolean implementsAnnotation(AnnotatedTypeMirror atm) { + if (atm.getKind() != TypeKind.DECLARED) { + return false; } - } else if (type.getKind() == TypeKind.ARRAY) { - AnnotatedArrayType arrayType = (AnnotatedArrayType) type; - found = containsModifierImpl(arrayType.getComponentType(), modifier, visited); - } else if (type.getKind() == TypeKind.TYPEVAR) { - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) type; - if (atv.getUpperBound() != null) { - found = containsModifierImpl(atv.getUpperBound(), modifier, visited); + AnnotatedTypeMirror.AnnotatedDeclaredType declaredType = + (AnnotatedTypeMirror.AnnotatedDeclaredType) atm; + + Symbol.ClassSymbol classSymbol = + (Symbol.ClassSymbol) declaredType.getUnderlyingType().asElement(); + for (Type iface : classSymbol.getInterfaces()) { + if (TypesUtils.isDeclaredOfName(iface, annotationClassName)) { + return true; + } } - if (!found && atv.getLowerBound() != null) { - found = containsModifierImpl(atv.getLowerBound(), modifier, visited); + + return false; + } + + public static boolean isEnum(AnnotatedTypeMirror typeMirror) { + if (typeMirror.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) typeMirror; + return TypesUtils.isDeclaredOfName( + adt.getUnderlyingType(), java.lang.Enum.class.getName()); } - } else if (type.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType awc = (AnnotatedWildcardType) type; - if (awc.getExtendsBound() != null) { - found = containsModifierImpl(awc.getExtendsBound(), modifier, visited); + + return false; + } + + public static boolean isDeclarationOfJavaLangEnum( + Types types, Elements elements, AnnotatedTypeMirror typeMirror) { + if (isEnum(typeMirror)) { + return elements.getTypeElement(Enum.class.getCanonicalName()) + .equals(((AnnotatedDeclaredType) typeMirror).getUnderlyingType().asElement()); } - if (!found && awc.getSuperBound() != null) { - found = containsModifierImpl(awc.getSuperBound(), modifier, visited); + + return false; + } + + /** + * Returns true if the typeVar1 and typeVar2 are two uses of the same type variable. + * + * @param types type utils + * @param typeVar1 a type variable + * @param typeVar2 a type variable + * @return true if the typeVar1 and typeVar2 are two uses of the same type variable + */ + @SuppressWarnings( + "interning:not.interned" // This is an equals method but @EqualsMethod can't be used + // because this method has 3 arguments. + ) + public static boolean haveSameDeclaration( + Types types, AnnotatedTypeVariable typeVar1, AnnotatedTypeVariable typeVar2) { + + if (typeVar1.getUnderlyingType() == typeVar2.getUnderlyingType()) { + return true; } - } + return types.isSameType(typeVar1.getUnderlyingType(), typeVar2.getUnderlyingType()); } - return found; - } - - /** java.lang.annotation.Annotation.class canonical name. */ - private static final @CanonicalName String annotationClassName = - java.lang.annotation.Annotation.class.getCanonicalName(); - - /** - * Returns true if the underlying type of this atm is a java.lang.annotation.Annotation. - * - * @return true if the underlying type of this atm is a java.lang.annotation.Annotation - */ - public static boolean isJavaLangAnnotation(AnnotatedTypeMirror atm) { - return TypesUtils.isDeclaredOfName(atm.getUnderlyingType(), annotationClassName); - } - - /** - * Returns true if atm is an Annotation interface, i.e., an implementation of - * java.lang.annotation.Annotation. Given {@code @interface MyAnno}, a call to {@code - * implementsAnnotation} returns true when called on an AnnotatedDeclaredType representing a use - * of MyAnno. - * - * @return true if atm is an Annotation interface - */ - public static boolean implementsAnnotation(AnnotatedTypeMirror atm) { - if (atm.getKind() != TypeKind.DECLARED) { - return false; + /** + * When overriding a method, you must include the same number of type parameters as the base + * method. By index, these parameters are considered equivalent to the type parameters of the + * overridden method. + * + *

          Necessary conditions: + * + *

            + *
          • Both type variables are defined in methods. + *
          • One of the two methods overrides the other. + *
          • Within their method declaration, both types have the same type parameter index. + *
          + * + * @return true if type1 and type2 are corresponding type variables (that is, either one + * "overrides" the other) + */ + public static boolean areCorrespondingTypeVariables( + Elements elements, AnnotatedTypeVariable type1, AnnotatedTypeVariable type2) { + TypeParameterElement type1ParamElem = + (TypeParameterElement) type1.getUnderlyingType().asElement(); + TypeParameterElement type2ParamElem = + (TypeParameterElement) type2.getUnderlyingType().asElement(); + + if (type1ParamElem.getGenericElement() instanceof ExecutableElement + && type2ParamElem.getGenericElement() instanceof ExecutableElement) { + ExecutableElement type1Executable = + (ExecutableElement) type1ParamElem.getGenericElement(); + ExecutableElement type2Executable = + (ExecutableElement) type2ParamElem.getGenericElement(); + + TypeElement type1Class = (TypeElement) type1Executable.getEnclosingElement(); + TypeElement type2Class = (TypeElement) type2Executable.getEnclosingElement(); + + boolean methodIsOverridden = + elements.overrides(type1Executable, type2Executable, type1Class) + || elements.overrides(type2Executable, type1Executable, type2Class); + if (methodIsOverridden) { + boolean haveSameIndex = + type1Executable.getTypeParameters().indexOf(type1ParamElem) + == type2Executable.getTypeParameters().indexOf(type2ParamElem); + return haveSameIndex; + } + } + + return false; } - AnnotatedTypeMirror.AnnotatedDeclaredType declaredType = - (AnnotatedTypeMirror.AnnotatedDeclaredType) atm; - - Symbol.ClassSymbol classSymbol = - (Symbol.ClassSymbol) declaredType.getUnderlyingType().asElement(); - for (Type iface : classSymbol.getInterfaces()) { - if (TypesUtils.isDeclaredOfName(iface, annotationClassName)) { - return true; - } + + /** + * When comparing types against the bounds of a type variable, we may encounter other type + * variables, wildcards, and intersections in those bounds. This method traverses the bounds + * until it finds a concrete type from which it can pull an annotation. + * + * @param top the top of the hierarchy for which you are searching + * @return the AnnotationMirror that represents the type of toSearch in the hierarchy of top + */ + public static AnnotationMirror findEffectiveAnnotationInHierarchy( + QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch, AnnotationMirror top) { + return findEffectiveAnnotationInHierarchy(qualHierarchy, toSearch, top, false); } - return false; - } + /** + * When comparing types against the bounds of a type variable, we may encounter other type + * variables, wildcards, and intersections in those bounds. This method traverses the bounds + * until it finds a concrete type from which it can pull an annotation. + * + * @param top the top of the hierarchy for which you are searching + * @param canBeEmpty whether or not the effective type can have NO annotation in the hierarchy + * specified by top. If this param is false, an exception will be thrown if no annotation is + * found. Otherwise the result is null. + * @return the AnnotationMirror that represents the type of {@code toSearch} in the hierarchy of + * {@code top} + */ + public static @Nullable AnnotationMirror findEffectiveAnnotationInHierarchy( + QualifierHierarchy qualHierarchy, + AnnotatedTypeMirror toSearch, + AnnotationMirror top, + boolean canBeEmpty) { + AnnotatedTypeMirror source = toSearch; + while (source.getAnnotationInHierarchy(top) == null) { + + switch (source.getKind()) { + case TYPEVAR: + source = ((AnnotatedTypeVariable) source).getUpperBound(); + break; + + case WILDCARD: + source = ((AnnotatedWildcardType) source).getExtendsBound(); + break; + + case INTERSECTION: + // if there are multiple conflicting annotations, choose the lowest + AnnotationMirror glb = + glbOfBoundsInHierarchy( + (AnnotatedIntersectionType) source, top, qualHierarchy); + + if (glb == null) { + throw new BugInCF( + "AnnotatedIntersectionType has no annotation in hierarchy " + + "on any of its supertypes." + + System.lineSeparator() + + "intersectionType=" + + source); + } + return glb; + + default: + if (canBeEmpty) { + return null; + } + + throw new BugInCF( + StringsPlume.joinLines( + "Unexpected AnnotatedTypeMirror with no primary annotation.", + "toSearch=" + toSearch, + "top=" + top, + "source=" + source)); + } + } - public static boolean isEnum(AnnotatedTypeMirror typeMirror) { - if (typeMirror.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType adt = (AnnotatedDeclaredType) typeMirror; - return TypesUtils.isDeclaredOfName(adt.getUnderlyingType(), java.lang.Enum.class.getName()); + return source.getAnnotationInHierarchy(top); } - return false; - } + /** + * This method returns the effective annotation on the lower bound of a type, or on the type + * itself if the type has no lower bound (it is not a type variable, wildcard, or intersection). + * + * @param qualHierarchy the qualifier hierarchy + * @param toSearch the type whose lower bound to examine + * @return the set of effective annotation mirrors in all hierarchies + */ + public static AnnotationMirrorSet findEffectiveLowerBoundAnnotations( + QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { + AnnotatedTypeMirror source = toSearch; + TypeKind kind = source.getKind(); + while (kind == TypeKind.TYPEVAR + || kind == TypeKind.WILDCARD + || kind == TypeKind.INTERSECTION) { + + switch (source.getKind()) { + case TYPEVAR: + source = ((AnnotatedTypeVariable) source).getLowerBound(); + break; + + case WILDCARD: + source = ((AnnotatedWildcardType) source).getSuperBound(); + break; + + case INTERSECTION: + // if there are multiple conflicting annotations, choose the lowest + AnnotationMirrorSet glb = + glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); + return glb; + + default: + throw new BugInCF( + "Unexpected AnnotatedTypeMirror with no primary annotation;" + + " toSearch=" + + toSearch + + " source=" + + source); + } + + kind = source.getKind(); + } - public static boolean isDeclarationOfJavaLangEnum( - Types types, Elements elements, AnnotatedTypeMirror typeMirror) { - if (isEnum(typeMirror)) { - return elements - .getTypeElement(Enum.class.getCanonicalName()) - .equals(((AnnotatedDeclaredType) typeMirror).getUnderlyingType().asElement()); + return source.getAnnotations(); } - return false; - } - - /** - * Returns true if the typeVar1 and typeVar2 are two uses of the same type variable. - * - * @param types type utils - * @param typeVar1 a type variable - * @param typeVar2 a type variable - * @return true if the typeVar1 and typeVar2 are two uses of the same type variable - */ - @SuppressWarnings( - "interning:not.interned" // This is an equals method but @EqualsMethod can't be used - // because this method has 3 arguments. - ) - public static boolean haveSameDeclaration( - Types types, AnnotatedTypeVariable typeVar1, AnnotatedTypeVariable typeVar2) { - - if (typeVar1.getUnderlyingType() == typeVar2.getUnderlyingType()) { - return true; + /** + * When comparing types against the bounds of a type variable, we may encounter other type + * variables, wildcards, and intersections in those bounds. This method traverses the bounds + * until it finds a concrete type from which it can pull an annotation. This occurs for every + * hierarchy in QualifierHierarchy. + * + * @param qualHierarchy the qualifier hierarchy + * @param toSearch the type whose effective annotations to determine + * @return the set of effective annotation mirrors in all hierarchies + */ + public static AnnotationMirrorSet findEffectiveAnnotations( + QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { + AnnotatedTypeMirror source = toSearch; + TypeKind kind = source.getKind(); + while (kind == TypeKind.TYPEVAR + || kind == TypeKind.WILDCARD + || kind == TypeKind.INTERSECTION) { + + switch (source.getKind()) { + case TYPEVAR: + source = ((AnnotatedTypeVariable) source).getUpperBound(); + break; + + case WILDCARD: + source = ((AnnotatedWildcardType) source).getExtendsBound(); + break; + + case INTERSECTION: + // if there are multiple conflicting annotations, choose the lowest + AnnotationMirrorSet glb = + glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); + return glb; + + default: + throw new BugInCF( + "Unexpected AnnotatedTypeMirror with no primary annotation;" + + " toSearch=" + + toSearch + + " source=" + + source); + } + + kind = source.getKind(); + } + + return source.getAnnotations(); } - return types.isSameType(typeVar1.getUnderlyingType(), typeVar2.getUnderlyingType()); - } - - /** - * When overriding a method, you must include the same number of type parameters as the base - * method. By index, these parameters are considered equivalent to the type parameters of the - * overridden method. - * - *

          Necessary conditions: - * - *

            - *
          • Both type variables are defined in methods. - *
          • One of the two methods overrides the other. - *
          • Within their method declaration, both types have the same type parameter index. - *
          - * - * @return true if type1 and type2 are corresponding type variables (that is, either one - * "overrides" the other) - */ - public static boolean areCorrespondingTypeVariables( - Elements elements, AnnotatedTypeVariable type1, AnnotatedTypeVariable type2) { - TypeParameterElement type1ParamElem = - (TypeParameterElement) type1.getUnderlyingType().asElement(); - TypeParameterElement type2ParamElem = - (TypeParameterElement) type2.getUnderlyingType().asElement(); - - if (type1ParamElem.getGenericElement() instanceof ExecutableElement - && type2ParamElem.getGenericElement() instanceof ExecutableElement) { - ExecutableElement type1Executable = (ExecutableElement) type1ParamElem.getGenericElement(); - ExecutableElement type2Executable = (ExecutableElement) type2ParamElem.getGenericElement(); - - TypeElement type1Class = (TypeElement) type1Executable.getEnclosingElement(); - TypeElement type2Class = (TypeElement) type2Executable.getEnclosingElement(); - - boolean methodIsOverridden = - elements.overrides(type1Executable, type2Executable, type1Class) - || elements.overrides(type2Executable, type1Executable, type2Class); - if (methodIsOverridden) { - boolean haveSameIndex = - type1Executable.getTypeParameters().indexOf(type1ParamElem) - == type2Executable.getTypeParameters().indexOf(type2ParamElem); - return haveSameIndex; - } + + private static AnnotationMirror glbOfBoundsInHierarchy( + AnnotatedIntersectionType isect, + AnnotationMirror top, + QualifierHierarchy qualHierarchy) { + AnnotationMirror anno = isect.getAnnotationInHierarchy(top); + for (AnnotatedTypeMirror bound : isect.getBounds()) { + AnnotationMirror boundAnno = bound.getAnnotationInHierarchy(top); + if (boundAnno != null + && (anno == null + || qualHierarchy.isSubtypeShallow( + boundAnno, + bound.getUnderlyingType(), + anno, + isect.getUnderlyingType()))) { + anno = boundAnno; + } + } + + return anno; } - return false; - } - - /** - * When comparing types against the bounds of a type variable, we may encounter other type - * variables, wildcards, and intersections in those bounds. This method traverses the bounds until - * it finds a concrete type from which it can pull an annotation. - * - * @param top the top of the hierarchy for which you are searching - * @return the AnnotationMirror that represents the type of toSearch in the hierarchy of top - */ - public static AnnotationMirror findEffectiveAnnotationInHierarchy( - QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch, AnnotationMirror top) { - return findEffectiveAnnotationInHierarchy(qualHierarchy, toSearch, top, false); - } - - /** - * When comparing types against the bounds of a type variable, we may encounter other type - * variables, wildcards, and intersections in those bounds. This method traverses the bounds until - * it finds a concrete type from which it can pull an annotation. - * - * @param top the top of the hierarchy for which you are searching - * @param canBeEmpty whether or not the effective type can have NO annotation in the hierarchy - * specified by top. If this param is false, an exception will be thrown if no annotation is - * found. Otherwise the result is null. - * @return the AnnotationMirror that represents the type of {@code toSearch} in the hierarchy of - * {@code top} - */ - public static @Nullable AnnotationMirror findEffectiveAnnotationInHierarchy( - QualifierHierarchy qualHierarchy, - AnnotatedTypeMirror toSearch, - AnnotationMirror top, - boolean canBeEmpty) { - AnnotatedTypeMirror source = toSearch; - while (source.getAnnotationInHierarchy(top) == null) { - - switch (source.getKind()) { - case TYPEVAR: - source = ((AnnotatedTypeVariable) source).getUpperBound(); - break; - - case WILDCARD: - source = ((AnnotatedWildcardType) source).getExtendsBound(); - break; - - case INTERSECTION: - // if there are multiple conflicting annotations, choose the lowest - AnnotationMirror glb = - glbOfBoundsInHierarchy((AnnotatedIntersectionType) source, top, qualHierarchy); - - if (glb == null) { - throw new BugInCF( - "AnnotatedIntersectionType has no annotation in hierarchy " - + "on any of its supertypes." - + System.lineSeparator() - + "intersectionType=" - + source); - } - return glb; - - default: - if (canBeEmpty) { - return null; - } - - throw new BugInCF( - StringsPlume.joinLines( - "Unexpected AnnotatedTypeMirror with no primary annotation.", - "toSearch=" + toSearch, - "top=" + top, - "source=" + source)); - } + /** + * Gets the lowest primary annotation of all bounds in the intersection. + * + * @param isect the intersection for which we are glbing bounds + * @param qualHierarchy the qualifier used to get the hierarchies in which to glb + * @return a set of annotations representing the glb of the intersection's bounds + */ + public static AnnotationMirrorSet glbOfBounds( + AnnotatedIntersectionType isect, QualifierHierarchy qualHierarchy) { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror glbAnno = glbOfBoundsInHierarchy(isect, top, qualHierarchy); + if (glbAnno != null) { + result.add(glbAnno); + } + } + + return result; } - return source.getAnnotationInHierarchy(top); - } - - /** - * This method returns the effective annotation on the lower bound of a type, or on the type - * itself if the type has no lower bound (it is not a type variable, wildcard, or intersection). - * - * @param qualHierarchy the qualifier hierarchy - * @param toSearch the type whose lower bound to examine - * @return the set of effective annotation mirrors in all hierarchies - */ - public static AnnotationMirrorSet findEffectiveLowerBoundAnnotations( - QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { - AnnotatedTypeMirror source = toSearch; - TypeKind kind = source.getKind(); - while (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD || kind == TypeKind.INTERSECTION) { - - switch (source.getKind()) { - case TYPEVAR: - source = ((AnnotatedTypeVariable) source).getLowerBound(); - break; - - case WILDCARD: - source = ((AnnotatedWildcardType) source).getSuperBound(); - break; - - case INTERSECTION: - // if there are multiple conflicting annotations, choose the lowest - AnnotationMirrorSet glb = glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); - return glb; - - default: - throw new BugInCF( - "Unexpected AnnotatedTypeMirror with no primary annotation;" - + " toSearch=" - + toSearch - + " source=" - + source); - } - - kind = source.getKind(); + // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. + // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". + + /** + * This method identifies wildcard types that are unbound. + * + * @param wildcardType the type to check + * @return true if the given card is an unbounded wildcard + */ + public static boolean hasNoExplicitBound(AnnotatedTypeMirror wildcardType) { + return TypesUtils.hasNoExplicitBound(wildcardType.getUnderlyingType()); } - return source.getAnnotations(); - } - - /** - * When comparing types against the bounds of a type variable, we may encounter other type - * variables, wildcards, and intersections in those bounds. This method traverses the bounds until - * it finds a concrete type from which it can pull an annotation. This occurs for every hierarchy - * in QualifierHierarchy. - * - * @param qualHierarchy the qualifier hierarchy - * @param toSearch the type whose effective annotations to determine - * @return the set of effective annotation mirrors in all hierarchies - */ - public static AnnotationMirrorSet findEffectiveAnnotations( - QualifierHierarchy qualHierarchy, AnnotatedTypeMirror toSearch) { - AnnotatedTypeMirror source = toSearch; - TypeKind kind = source.getKind(); - while (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD || kind == TypeKind.INTERSECTION) { - - switch (source.getKind()) { - case TYPEVAR: - source = ((AnnotatedTypeVariable) source).getUpperBound(); - break; - - case WILDCARD: - source = ((AnnotatedWildcardType) source).getExtendsBound(); - break; - - case INTERSECTION: - // if there are multiple conflicting annotations, choose the lowest - AnnotationMirrorSet glb = glbOfBounds((AnnotatedIntersectionType) source, qualHierarchy); - return glb; - - default: - throw new BugInCF( - "Unexpected AnnotatedTypeMirror with no primary annotation;" - + " toSearch=" - + toSearch - + " source=" - + source); - } - - kind = source.getKind(); + /** + * Returns true if wildcard type is explicitly super bounded. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly super bounded + * @deprecated Use {@link #hasExplicitSuperBound(AnnotatedTypeMirror)} + */ + @Deprecated // 2023-02-28 + public static boolean isExplicitlySuperBounded(AnnotatedWildcardType wildcardType) { + return hasExplicitSuperBound(wildcardType); } - return source.getAnnotations(); - } - - private static AnnotationMirror glbOfBoundsInHierarchy( - AnnotatedIntersectionType isect, AnnotationMirror top, QualifierHierarchy qualHierarchy) { - AnnotationMirror anno = isect.getAnnotationInHierarchy(top); - for (AnnotatedTypeMirror bound : isect.getBounds()) { - AnnotationMirror boundAnno = bound.getAnnotationInHierarchy(top); - if (boundAnno != null - && (anno == null - || qualHierarchy.isSubtypeShallow( - boundAnno, bound.getUnderlyingType(), anno, isect.getUnderlyingType()))) { - anno = boundAnno; - } + /** + * Returns true if wildcard type has an explicit super bound. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly super bounded + */ + public static boolean hasExplicitSuperBound(AnnotatedTypeMirror wildcardType) { + return TypesUtils.hasExplicitSuperBound(wildcardType.getUnderlyingType()); } - return anno; - } - - /** - * Gets the lowest primary annotation of all bounds in the intersection. - * - * @param isect the intersection for which we are glbing bounds - * @param qualHierarchy the qualifier used to get the hierarchies in which to glb - * @return a set of annotations representing the glb of the intersection's bounds - */ - public static AnnotationMirrorSet glbOfBounds( - AnnotatedIntersectionType isect, QualifierHierarchy qualHierarchy) { - AnnotationMirrorSet result = new AnnotationMirrorSet(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror glbAnno = glbOfBoundsInHierarchy(isect, top, qualHierarchy); - if (glbAnno != null) { - result.add(glbAnno); - } + /** + * Returns true if wildcard type is explicitly extends bounded. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly extends bounded + * @deprecated Use {@link #hasExplicitExtendsBound(AnnotatedTypeMirror)}. + */ + @Deprecated // 2023-02-28 + public static boolean isExplicitlyExtendsBounded(AnnotatedWildcardType wildcardType) { + return hasExplicitExtendsBound(wildcardType); } - return result; - } - - // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. - // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". - - /** - * This method identifies wildcard types that are unbound. - * - * @param wildcardType the type to check - * @return true if the given card is an unbounded wildcard - */ - public static boolean hasNoExplicitBound(AnnotatedTypeMirror wildcardType) { - return TypesUtils.hasNoExplicitBound(wildcardType.getUnderlyingType()); - } - - /** - * Returns true if wildcard type is explicitly super bounded. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly super bounded - * @deprecated Use {@link #hasExplicitSuperBound(AnnotatedTypeMirror)} - */ - @Deprecated // 2023-02-28 - public static boolean isExplicitlySuperBounded(AnnotatedWildcardType wildcardType) { - return hasExplicitSuperBound(wildcardType); - } - - /** - * Returns true if wildcard type has an explicit super bound. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly super bounded - */ - public static boolean hasExplicitSuperBound(AnnotatedTypeMirror wildcardType) { - return TypesUtils.hasExplicitSuperBound(wildcardType.getUnderlyingType()); - } - - /** - * Returns true if wildcard type is explicitly extends bounded. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly extends bounded - * @deprecated Use {@link #hasExplicitExtendsBound(AnnotatedTypeMirror)}. - */ - @Deprecated // 2023-02-28 - public static boolean isExplicitlyExtendsBounded(AnnotatedWildcardType wildcardType) { - return hasExplicitExtendsBound(wildcardType); - } - - /** - * Returns true if wildcard type has an explicit extends bound. - * - * @param wildcardType the wildcard type to test - * @return true if wildcard type is explicitly extends bounded - */ - public static boolean hasExplicitExtendsBound(AnnotatedTypeMirror wildcardType) { - return TypesUtils.hasExplicitExtendsBound(wildcardType.getUnderlyingType()); - } - - /** - * Returns true if this type is super bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is super bounded or unbounded - */ - public static boolean isUnboundedOrSuperBounded(AnnotatedWildcardType wildcardType) { - return TypesUtils.isUnboundedOrSuperBounded(wildcardType.getUnderlyingType()); - } - - /** - * Returns true if this type is extends bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is extends bounded or unbounded - */ - public static boolean isUnboundedOrExtendsBounded(AnnotatedWildcardType wildcardType) { - return TypesUtils.isUnboundedOrExtendsBounded(wildcardType.getUnderlyingType()); - } - - /** - * Copies explicit annotations and annotations resulting from resolution of polymorphic qualifiers - * from {@code constructor} to {@code returnType}. If {@code returnType} has an annotation in the - * same hierarchy of an annotation to be copied, that annotation is not copied. - * - * @param atypeFactory type factory - * @param returnType return type to copy annotations to - * @param constructorType the ATM for the constructor - */ - public static void copyOnlyExplicitConstructorAnnotations( - AnnotatedTypeFactory atypeFactory, - AnnotatedDeclaredType returnType, - AnnotatedExecutableType constructorType) { - - // TODO: There will be a nicer way to access this in 308 soon. - List decall = - ((Symbol) constructorType.getElement()).getRawTypeAttributes(); - AnnotationMirrorSet decret = new AnnotationMirrorSet(); - for (Attribute.TypeCompound da : decall) { - if (da.position.type == com.sun.tools.javac.code.TargetType.METHOD_RETURN) { - decret.add(da); - } + /** + * Returns true if wildcard type has an explicit extends bound. + * + * @param wildcardType the wildcard type to test + * @return true if wildcard type is explicitly extends bounded + */ + public static boolean hasExplicitExtendsBound(AnnotatedTypeMirror wildcardType) { + return TypesUtils.hasExplicitExtendsBound(wildcardType.getUnderlyingType()); } - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + /** + * Returns true if this type is super bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is super bounded or unbounded + */ + public static boolean isUnboundedOrSuperBounded(AnnotatedWildcardType wildcardType) { + return TypesUtils.isUnboundedOrSuperBounded(wildcardType.getUnderlyingType()); + } - // Collect all polymorphic qualifiers; we should substitute them. - AnnotationMirrorSet polys = new AnnotationMirrorSet(); - for (AnnotationMirror anno : returnType.getAnnotations()) { - if (qualHierarchy.isPolymorphicQualifier(anno)) { - polys.add(anno); - } + /** + * Returns true if this type is extends bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is extends bounded or unbounded + */ + public static boolean isUnboundedOrExtendsBounded(AnnotatedWildcardType wildcardType) { + return TypesUtils.isUnboundedOrExtendsBounded(wildcardType.getUnderlyingType()); } - for (AnnotationMirror cta : constructorType.getReturnType().getAnnotations()) { - AnnotationMirror ctatop = qualHierarchy.getTopAnnotation(cta); - if (returnType.hasAnnotationInHierarchy(cta)) { - continue; - } - if (atypeFactory.isSupportedQualifier(cta) && !returnType.hasAnnotationInHierarchy(cta)) { - for (AnnotationMirror fromDecl : decret) { - if (atypeFactory.isSupportedQualifier(fromDecl) - && AnnotationUtils.areSame(ctatop, qualHierarchy.getTopAnnotation(fromDecl))) { - returnType.addAnnotation(cta); - break; - } + /** + * Copies explicit annotations and annotations resulting from resolution of polymorphic + * qualifiers from {@code constructor} to {@code returnType}. If {@code returnType} has an + * annotation in the same hierarchy of an annotation to be copied, that annotation is not + * copied. + * + * @param atypeFactory type factory + * @param returnType return type to copy annotations to + * @param constructorType the ATM for the constructor + */ + public static void copyOnlyExplicitConstructorAnnotations( + AnnotatedTypeFactory atypeFactory, + AnnotatedDeclaredType returnType, + AnnotatedExecutableType constructorType) { + + // TODO: There will be a nicer way to access this in 308 soon. + List decall = + ((Symbol) constructorType.getElement()).getRawTypeAttributes(); + AnnotationMirrorSet decret = new AnnotationMirrorSet(); + for (Attribute.TypeCompound da : decall) { + if (da.position.type == com.sun.tools.javac.code.TargetType.METHOD_RETURN) { + decret.add(da); + } + } + + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + + // Collect all polymorphic qualifiers; we should substitute them. + AnnotationMirrorSet polys = new AnnotationMirrorSet(); + for (AnnotationMirror anno : returnType.getAnnotations()) { + if (qualHierarchy.isPolymorphicQualifier(anno)) { + polys.add(anno); + } } - } - - // Go through the polymorphic qualifiers and see whether - // there is anything left to replace. - for (AnnotationMirror pa : polys) { - if (AnnotationUtils.areSame(ctatop, qualHierarchy.getTopAnnotation(pa))) { - returnType.replaceAnnotation(cta); - break; + + for (AnnotationMirror cta : constructorType.getReturnType().getAnnotations()) { + AnnotationMirror ctatop = qualHierarchy.getTopAnnotation(cta); + if (returnType.hasAnnotationInHierarchy(cta)) { + continue; + } + if (atypeFactory.isSupportedQualifier(cta) + && !returnType.hasAnnotationInHierarchy(cta)) { + for (AnnotationMirror fromDecl : decret) { + if (atypeFactory.isSupportedQualifier(fromDecl) + && AnnotationUtils.areSame( + ctatop, qualHierarchy.getTopAnnotation(fromDecl))) { + returnType.addAnnotation(cta); + break; + } + } + } + + // Go through the polymorphic qualifiers and see whether + // there is anything left to replace. + for (AnnotationMirror pa : polys) { + if (AnnotationUtils.areSame(ctatop, qualHierarchy.getTopAnnotation(pa))) { + returnType.replaceAnnotation(cta); + break; + } + } } - } } - } - - /** - * Add all the annotations in {@code declaredType} to {@code annotatedDeclaredType}. - * - *

          (The {@code TypeMirror} returned by {@code annotatedDeclaredType#getUnderlyingType} may not - * have all the annotations on the type, so allow the user to specify a different one.) - * - * @param annotatedDeclaredType annotated type to which annotations are added - * @param declaredType a type that may have annotations - */ - public static void applyAnnotationsFromDeclaredType( - AnnotatedDeclaredType annotatedDeclaredType, DeclaredType declaredType) { - TypeMirror underlyingTypeMirror = declaredType; - while (annotatedDeclaredType != null) { - List annosOnTypeMirror = - underlyingTypeMirror.getAnnotationMirrors(); - annotatedDeclaredType.addAnnotations(annosOnTypeMirror); - annotatedDeclaredType = annotatedDeclaredType.getEnclosingType(); - underlyingTypeMirror = ((DeclaredType) underlyingTypeMirror).getEnclosingType(); + + /** + * Add all the annotations in {@code declaredType} to {@code annotatedDeclaredType}. + * + *

          (The {@code TypeMirror} returned by {@code annotatedDeclaredType#getUnderlyingType} may + * not have all the annotations on the type, so allow the user to specify a different one.) + * + * @param annotatedDeclaredType annotated type to which annotations are added + * @param declaredType a type that may have annotations + */ + public static void applyAnnotationsFromDeclaredType( + AnnotatedDeclaredType annotatedDeclaredType, DeclaredType declaredType) { + TypeMirror underlyingTypeMirror = declaredType; + while (annotatedDeclaredType != null) { + List annosOnTypeMirror = + underlyingTypeMirror.getAnnotationMirrors(); + annotatedDeclaredType.addAnnotations(annosOnTypeMirror); + annotatedDeclaredType = annotatedDeclaredType.getEnclosingType(); + underlyingTypeMirror = ((DeclaredType) underlyingTypeMirror).getEnclosingType(); + } + } + + /** + * Returns whether {@code type} is a type argument to a type whose {@code #underlyingType} is + * raw. The Checker Framework gives raw types wildcard type arguments so that the annotated type + * can be used as if the annotated type was not raw. + * + * @param type an annotated type + * @return whether this is a type argument to a type whose {@code #underlyingType} is raw + */ + public static boolean isTypeArgOfRawType(AnnotatedTypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type).isTypeArgOfRawType(); } - } - - /** - * Returns whether {@code type} is a type argument to a type whose {@code #underlyingType} is raw. - * The Checker Framework gives raw types wildcard type arguments so that the annotated type can be - * used as if the annotated type was not raw. - * - * @param type an annotated type - * @return whether this is a type argument to a type whose {@code #underlyingType} is raw - */ - public static boolean isTypeArgOfRawType(AnnotatedTypeMirror type) { - return type.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type).isTypeArgOfRawType(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java index b07670f389f..c526d392c5b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotationFormatter.java @@ -1,30 +1,32 @@ package org.checkerframework.framework.util; +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.Collection; + import javax.lang.model.element.AnnotationMirror; -import org.checkerframework.dataflow.qual.SideEffectFree; /** Converts AnnotationMirrors to Strings. Used when converting AnnotatedTypeMirrors to Strings. */ public interface AnnotationFormatter { - /** - * Converts a collection of annotation mirrors into a String. - * - * @param annos a collection of annotations to print - * @param printInvisible whether or not to print "invisible" annotation mirrors - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @return a string representation of annos - */ - @SideEffectFree - public String formatAnnotationString( - Collection annos, boolean printInvisible); + /** + * Converts a collection of annotation mirrors into a String. + * + * @param annos a collection of annotations to print + * @param printInvisible whether or not to print "invisible" annotation mirrors + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @return a string representation of annos + */ + @SideEffectFree + public String formatAnnotationString( + Collection annos, boolean printInvisible); - /** - * Converts an individual annotation mirror into a String. - * - * @param anno the annotation mirror to convert - * @return a String representation of anno - */ - @SideEffectFree - public String formatAnnotationMirror(AnnotationMirror anno); + /** + * Converts an individual annotation mirror into a String. + * + * @param anno the annotation mirror to convert + * @return a String representation of anno + */ + @SideEffectFree + public String formatAnnotationMirror(AnnotationMirror anno); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java index ea19e66a529..5d4008ee4d9 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java @@ -22,41 +22,41 @@ * AtmKind.ordinal(). See AtmCombo.comboMap */ enum AtmKind { - ARRAY(AnnotatedArrayType.class), - DECLARED(AnnotatedDeclaredType.class), - EXECUTABLE(AnnotatedExecutableType.class), - INTERSECTION(AnnotatedIntersectionType.class), - NONE(AnnotatedNoType.class), - NULL(AnnotatedNullType.class), - PRIMITIVE(AnnotatedPrimitiveType.class), - TYPEVAR(AnnotatedTypeVariable.class), - UNION(AnnotatedUnionType.class), - WILDCARD(AnnotatedWildcardType.class); - - // The AnnotatedTypeMirror subclass that represents types of this kind - public final Class atmClass; - - AtmKind(Class atmClass) { - this.atmClass = atmClass; - } - - /** - * Returns the AtmKind corresponding to the class of atm. - * - * @return the AtmKind corresponding to the class of atm - */ - public static AtmKind valueOf(AnnotatedTypeMirror atm) { - Class argClass = atm.getClass(); - - for (AtmKind atmKind : AtmKind.values()) { - Class kindClass = atmKind.atmClass; - if (argClass == kindClass) { - return atmKind; - } + ARRAY(AnnotatedArrayType.class), + DECLARED(AnnotatedDeclaredType.class), + EXECUTABLE(AnnotatedExecutableType.class), + INTERSECTION(AnnotatedIntersectionType.class), + NONE(AnnotatedNoType.class), + NULL(AnnotatedNullType.class), + PRIMITIVE(AnnotatedPrimitiveType.class), + TYPEVAR(AnnotatedTypeVariable.class), + UNION(AnnotatedUnionType.class), + WILDCARD(AnnotatedWildcardType.class); + + // The AnnotatedTypeMirror subclass that represents types of this kind + public final Class atmClass; + + AtmKind(Class atmClass) { + this.atmClass = atmClass; } - throw new BugInCF("Unhandled AnnotatedTypeMirror ( " + atm.getClass() + " )"); - } + /** + * Returns the AtmKind corresponding to the class of atm. + * + * @return the AtmKind corresponding to the class of atm + */ + public static AtmKind valueOf(AnnotatedTypeMirror atm) { + Class argClass = atm.getClass(); + + for (AtmKind atmKind : AtmKind.values()) { + Class kindClass = atmKind.atmClass; + if (argClass == kindClass) { + return atmKind; + } + } + + throw new BugInCF("Unhandled AnnotatedTypeMirror ( " + atm.getClass() + " )"); + } } /** @@ -81,589 +81,651 @@ public static AtmKind valueOf(AnnotatedTypeMirror atm) { */ @SuppressWarnings("EnumOrdinal") // Uses arrays instead of maps for access public enum AtmCombo { - ARRAY_ARRAY(AtmKind.ARRAY, AtmKind.ARRAY), - ARRAY_DECLARED(AtmKind.ARRAY, AtmKind.DECLARED), - ARRAY_EXECUTABLE(AtmKind.ARRAY, AtmKind.EXECUTABLE), - ARRAY_INTERSECTION(AtmKind.ARRAY, AtmKind.INTERSECTION), - ARRAY_NONE(AtmKind.ARRAY, AtmKind.NONE), - ARRAY_NULL(AtmKind.ARRAY, AtmKind.NULL), - ARRAY_PRIMITIVE(AtmKind.ARRAY, AtmKind.PRIMITIVE), - ARRAY_UNION(AtmKind.ARRAY, AtmKind.UNION), - ARRAY_TYPEVAR(AtmKind.ARRAY, AtmKind.TYPEVAR), - ARRAY_WILDCARD(AtmKind.ARRAY, AtmKind.WILDCARD), - - DECLARED_ARRAY(AtmKind.DECLARED, AtmKind.ARRAY), - DECLARED_DECLARED(AtmKind.DECLARED, AtmKind.DECLARED), - DECLARED_EXECUTABLE(AtmKind.DECLARED, AtmKind.EXECUTABLE), - DECLARED_INTERSECTION(AtmKind.DECLARED, AtmKind.INTERSECTION), - DECLARED_NONE(AtmKind.DECLARED, AtmKind.NONE), - DECLARED_NULL(AtmKind.DECLARED, AtmKind.NULL), - DECLARED_PRIMITIVE(AtmKind.DECLARED, AtmKind.PRIMITIVE), - DECLARED_TYPEVAR(AtmKind.DECLARED, AtmKind.TYPEVAR), - DECLARED_UNION(AtmKind.DECLARED, AtmKind.UNION), - DECLARED_WILDCARD(AtmKind.DECLARED, AtmKind.WILDCARD), - - EXECUTABLE_ARRAY(AtmKind.EXECUTABLE, AtmKind.ARRAY), - EXECUTABLE_DECLARED(AtmKind.EXECUTABLE, AtmKind.DECLARED), - EXECUTABLE_EXECUTABLE(AtmKind.EXECUTABLE, AtmKind.EXECUTABLE), - EXECUTABLE_INTERSECTION(AtmKind.EXECUTABLE, AtmKind.INTERSECTION), - EXECUTABLE_NONE(AtmKind.EXECUTABLE, AtmKind.NONE), - EXECUTABLE_NULL(AtmKind.EXECUTABLE, AtmKind.NULL), - EXECUTABLE_PRIMITIVE(AtmKind.EXECUTABLE, AtmKind.PRIMITIVE), - EXECUTABLE_TYPEVAR(AtmKind.EXECUTABLE, AtmKind.TYPEVAR), - EXECUTABLE_UNION(AtmKind.EXECUTABLE, AtmKind.UNION), - EXECUTABLE_WILDCARD(AtmKind.EXECUTABLE, AtmKind.WILDCARD), - - INTERSECTION_ARRAY(AtmKind.INTERSECTION, AtmKind.ARRAY), - INTERSECTION_DECLARED(AtmKind.INTERSECTION, AtmKind.DECLARED), - INTERSECTION_EXECUTABLE(AtmKind.INTERSECTION, AtmKind.EXECUTABLE), - INTERSECTION_INTERSECTION(AtmKind.INTERSECTION, AtmKind.INTERSECTION), - INTERSECTION_NONE(AtmKind.INTERSECTION, AtmKind.NONE), - INTERSECTION_NULL(AtmKind.INTERSECTION, AtmKind.NULL), - INTERSECTION_PRIMITIVE(AtmKind.INTERSECTION, AtmKind.PRIMITIVE), - INTERSECTION_TYPEVAR(AtmKind.INTERSECTION, AtmKind.TYPEVAR), - INTERSECTION_UNION(AtmKind.INTERSECTION, AtmKind.UNION), - INTERSECTION_WILDCARD(AtmKind.INTERSECTION, AtmKind.WILDCARD), - - NONE_ARRAY(AtmKind.NONE, AtmKind.ARRAY), - NONE_DECLARED(AtmKind.NONE, AtmKind.DECLARED), - NONE_EXECUTABLE(AtmKind.NONE, AtmKind.EXECUTABLE), - NONE_INTERSECTION(AtmKind.NONE, AtmKind.INTERSECTION), - NONE_NONE(AtmKind.NONE, AtmKind.NONE), - NONE_NULL(AtmKind.NONE, AtmKind.NULL), - NONE_PRIMITIVE(AtmKind.NONE, AtmKind.PRIMITIVE), - NONE_TYPEVAR(AtmKind.NONE, AtmKind.TYPEVAR), - NONE_UNION(AtmKind.NONE, AtmKind.UNION), - NONE_WILDCARD(AtmKind.NONE, AtmKind.WILDCARD), - - NULL_ARRAY(AtmKind.NULL, AtmKind.ARRAY), - NULL_DECLARED(AtmKind.NULL, AtmKind.DECLARED), - NULL_EXECUTABLE(AtmKind.NULL, AtmKind.EXECUTABLE), - NULL_INTERSECTION(AtmKind.NULL, AtmKind.INTERSECTION), - NULL_NONE(AtmKind.NULL, AtmKind.NONE), - NULL_NULL(AtmKind.NULL, AtmKind.NULL), - NULL_PRIMITIVE(AtmKind.NULL, AtmKind.PRIMITIVE), - NULL_TYPEVAR(AtmKind.NULL, AtmKind.TYPEVAR), - NULL_UNION(AtmKind.NULL, AtmKind.UNION), - NULL_WILDCARD(AtmKind.NULL, AtmKind.WILDCARD), - - PRIMITIVE_ARRAY(AtmKind.PRIMITIVE, AtmKind.ARRAY), - PRIMITIVE_DECLARED(AtmKind.PRIMITIVE, AtmKind.DECLARED), - PRIMITIVE_EXECUTABLE(AtmKind.PRIMITIVE, AtmKind.EXECUTABLE), - PRIMITIVE_INTERSECTION(AtmKind.PRIMITIVE, AtmKind.INTERSECTION), - PRIMITIVE_NONE(AtmKind.PRIMITIVE, AtmKind.NONE), - PRIMITIVE_NULL(AtmKind.PRIMITIVE, AtmKind.NULL), - PRIMITIVE_PRIMITIVE(AtmKind.PRIMITIVE, AtmKind.PRIMITIVE), - PRIMITIVE_TYPEVAR(AtmKind.PRIMITIVE, AtmKind.TYPEVAR), - PRIMITIVE_UNION(AtmKind.PRIMITIVE, AtmKind.UNION), - PRIMITIVE_WILDCARD(AtmKind.PRIMITIVE, AtmKind.WILDCARD), - - TYPEVAR_ARRAY(AtmKind.TYPEVAR, AtmKind.ARRAY), - TYPEVAR_DECLARED(AtmKind.TYPEVAR, AtmKind.DECLARED), - TYPEVAR_EXECUTABLE(AtmKind.TYPEVAR, AtmKind.EXECUTABLE), - TYPEVAR_INTERSECTION(AtmKind.TYPEVAR, AtmKind.INTERSECTION), - TYPEVAR_NONE(AtmKind.TYPEVAR, AtmKind.NONE), - TYPEVAR_NULL(AtmKind.TYPEVAR, AtmKind.NULL), - TYPEVAR_PRIMITIVE(AtmKind.TYPEVAR, AtmKind.PRIMITIVE), - TYPEVAR_TYPEVAR(AtmKind.TYPEVAR, AtmKind.TYPEVAR), - TYPEVAR_UNION(AtmKind.TYPEVAR, AtmKind.UNION), - TYPEVAR_WILDCARD(AtmKind.TYPEVAR, AtmKind.WILDCARD), - - UNION_ARRAY(AtmKind.UNION, AtmKind.ARRAY), - UNION_DECLARED(AtmKind.UNION, AtmKind.DECLARED), - UNION_EXECUTABLE(AtmKind.UNION, AtmKind.EXECUTABLE), - UNION_INTERSECTION(AtmKind.UNION, AtmKind.INTERSECTION), - UNION_NONE(AtmKind.UNION, AtmKind.NONE), - UNION_NULL(AtmKind.UNION, AtmKind.NULL), - UNION_PRIMITIVE(AtmKind.UNION, AtmKind.PRIMITIVE), - UNION_TYPEVAR(AtmKind.UNION, AtmKind.TYPEVAR), - UNION_UNION(AtmKind.UNION, AtmKind.UNION), - UNION_WILDCARD(AtmKind.UNION, AtmKind.WILDCARD), - - WILDCARD_ARRAY(AtmKind.WILDCARD, AtmKind.ARRAY), - WILDCARD_DECLARED(AtmKind.WILDCARD, AtmKind.DECLARED), - WILDCARD_EXECUTABLE(AtmKind.WILDCARD, AtmKind.EXECUTABLE), - WILDCARD_INTERSECTION(AtmKind.WILDCARD, AtmKind.INTERSECTION), - WILDCARD_NONE(AtmKind.WILDCARD, AtmKind.NONE), - WILDCARD_NULL(AtmKind.WILDCARD, AtmKind.NULL), - WILDCARD_PRIMITIVE(AtmKind.WILDCARD, AtmKind.PRIMITIVE), - WILDCARD_TYPEVAR(AtmKind.WILDCARD, AtmKind.TYPEVAR), - WILDCARD_UNION(AtmKind.WILDCARD, AtmKind.UNION), - WILDCARD_WILDCARD(AtmKind.WILDCARD, AtmKind.WILDCARD); - - /** First AtmKind. */ - public final AtmKind type1Kind; - - /** Second AtmKind. */ - public final AtmKind type2Kind; - - /** - * Creates an AtmCombo. - * - * @param type1Kind first kind - * @param type2Kind second kind - */ - AtmCombo(AtmKind type1Kind, AtmKind type2Kind) { - this.type1Kind = type1Kind; - this.type2Kind = type2Kind; - } - - /** - * Used to locate AtmCombo pairs using AtmKinds as indices into a two-dimensional array. This - * ensures that all pairs are included. - */ - private static final AtmCombo[][] comboMap = - new AtmCombo[AtmKind.values().length][AtmKind.values().length]; - - static { - for (AtmCombo atmCombo : AtmCombo.values()) { - comboMap[atmCombo.type1Kind.ordinal()][atmCombo.type2Kind.ordinal()] = atmCombo; + ARRAY_ARRAY(AtmKind.ARRAY, AtmKind.ARRAY), + ARRAY_DECLARED(AtmKind.ARRAY, AtmKind.DECLARED), + ARRAY_EXECUTABLE(AtmKind.ARRAY, AtmKind.EXECUTABLE), + ARRAY_INTERSECTION(AtmKind.ARRAY, AtmKind.INTERSECTION), + ARRAY_NONE(AtmKind.ARRAY, AtmKind.NONE), + ARRAY_NULL(AtmKind.ARRAY, AtmKind.NULL), + ARRAY_PRIMITIVE(AtmKind.ARRAY, AtmKind.PRIMITIVE), + ARRAY_UNION(AtmKind.ARRAY, AtmKind.UNION), + ARRAY_TYPEVAR(AtmKind.ARRAY, AtmKind.TYPEVAR), + ARRAY_WILDCARD(AtmKind.ARRAY, AtmKind.WILDCARD), + + DECLARED_ARRAY(AtmKind.DECLARED, AtmKind.ARRAY), + DECLARED_DECLARED(AtmKind.DECLARED, AtmKind.DECLARED), + DECLARED_EXECUTABLE(AtmKind.DECLARED, AtmKind.EXECUTABLE), + DECLARED_INTERSECTION(AtmKind.DECLARED, AtmKind.INTERSECTION), + DECLARED_NONE(AtmKind.DECLARED, AtmKind.NONE), + DECLARED_NULL(AtmKind.DECLARED, AtmKind.NULL), + DECLARED_PRIMITIVE(AtmKind.DECLARED, AtmKind.PRIMITIVE), + DECLARED_TYPEVAR(AtmKind.DECLARED, AtmKind.TYPEVAR), + DECLARED_UNION(AtmKind.DECLARED, AtmKind.UNION), + DECLARED_WILDCARD(AtmKind.DECLARED, AtmKind.WILDCARD), + + EXECUTABLE_ARRAY(AtmKind.EXECUTABLE, AtmKind.ARRAY), + EXECUTABLE_DECLARED(AtmKind.EXECUTABLE, AtmKind.DECLARED), + EXECUTABLE_EXECUTABLE(AtmKind.EXECUTABLE, AtmKind.EXECUTABLE), + EXECUTABLE_INTERSECTION(AtmKind.EXECUTABLE, AtmKind.INTERSECTION), + EXECUTABLE_NONE(AtmKind.EXECUTABLE, AtmKind.NONE), + EXECUTABLE_NULL(AtmKind.EXECUTABLE, AtmKind.NULL), + EXECUTABLE_PRIMITIVE(AtmKind.EXECUTABLE, AtmKind.PRIMITIVE), + EXECUTABLE_TYPEVAR(AtmKind.EXECUTABLE, AtmKind.TYPEVAR), + EXECUTABLE_UNION(AtmKind.EXECUTABLE, AtmKind.UNION), + EXECUTABLE_WILDCARD(AtmKind.EXECUTABLE, AtmKind.WILDCARD), + + INTERSECTION_ARRAY(AtmKind.INTERSECTION, AtmKind.ARRAY), + INTERSECTION_DECLARED(AtmKind.INTERSECTION, AtmKind.DECLARED), + INTERSECTION_EXECUTABLE(AtmKind.INTERSECTION, AtmKind.EXECUTABLE), + INTERSECTION_INTERSECTION(AtmKind.INTERSECTION, AtmKind.INTERSECTION), + INTERSECTION_NONE(AtmKind.INTERSECTION, AtmKind.NONE), + INTERSECTION_NULL(AtmKind.INTERSECTION, AtmKind.NULL), + INTERSECTION_PRIMITIVE(AtmKind.INTERSECTION, AtmKind.PRIMITIVE), + INTERSECTION_TYPEVAR(AtmKind.INTERSECTION, AtmKind.TYPEVAR), + INTERSECTION_UNION(AtmKind.INTERSECTION, AtmKind.UNION), + INTERSECTION_WILDCARD(AtmKind.INTERSECTION, AtmKind.WILDCARD), + + NONE_ARRAY(AtmKind.NONE, AtmKind.ARRAY), + NONE_DECLARED(AtmKind.NONE, AtmKind.DECLARED), + NONE_EXECUTABLE(AtmKind.NONE, AtmKind.EXECUTABLE), + NONE_INTERSECTION(AtmKind.NONE, AtmKind.INTERSECTION), + NONE_NONE(AtmKind.NONE, AtmKind.NONE), + NONE_NULL(AtmKind.NONE, AtmKind.NULL), + NONE_PRIMITIVE(AtmKind.NONE, AtmKind.PRIMITIVE), + NONE_TYPEVAR(AtmKind.NONE, AtmKind.TYPEVAR), + NONE_UNION(AtmKind.NONE, AtmKind.UNION), + NONE_WILDCARD(AtmKind.NONE, AtmKind.WILDCARD), + + NULL_ARRAY(AtmKind.NULL, AtmKind.ARRAY), + NULL_DECLARED(AtmKind.NULL, AtmKind.DECLARED), + NULL_EXECUTABLE(AtmKind.NULL, AtmKind.EXECUTABLE), + NULL_INTERSECTION(AtmKind.NULL, AtmKind.INTERSECTION), + NULL_NONE(AtmKind.NULL, AtmKind.NONE), + NULL_NULL(AtmKind.NULL, AtmKind.NULL), + NULL_PRIMITIVE(AtmKind.NULL, AtmKind.PRIMITIVE), + NULL_TYPEVAR(AtmKind.NULL, AtmKind.TYPEVAR), + NULL_UNION(AtmKind.NULL, AtmKind.UNION), + NULL_WILDCARD(AtmKind.NULL, AtmKind.WILDCARD), + + PRIMITIVE_ARRAY(AtmKind.PRIMITIVE, AtmKind.ARRAY), + PRIMITIVE_DECLARED(AtmKind.PRIMITIVE, AtmKind.DECLARED), + PRIMITIVE_EXECUTABLE(AtmKind.PRIMITIVE, AtmKind.EXECUTABLE), + PRIMITIVE_INTERSECTION(AtmKind.PRIMITIVE, AtmKind.INTERSECTION), + PRIMITIVE_NONE(AtmKind.PRIMITIVE, AtmKind.NONE), + PRIMITIVE_NULL(AtmKind.PRIMITIVE, AtmKind.NULL), + PRIMITIVE_PRIMITIVE(AtmKind.PRIMITIVE, AtmKind.PRIMITIVE), + PRIMITIVE_TYPEVAR(AtmKind.PRIMITIVE, AtmKind.TYPEVAR), + PRIMITIVE_UNION(AtmKind.PRIMITIVE, AtmKind.UNION), + PRIMITIVE_WILDCARD(AtmKind.PRIMITIVE, AtmKind.WILDCARD), + + TYPEVAR_ARRAY(AtmKind.TYPEVAR, AtmKind.ARRAY), + TYPEVAR_DECLARED(AtmKind.TYPEVAR, AtmKind.DECLARED), + TYPEVAR_EXECUTABLE(AtmKind.TYPEVAR, AtmKind.EXECUTABLE), + TYPEVAR_INTERSECTION(AtmKind.TYPEVAR, AtmKind.INTERSECTION), + TYPEVAR_NONE(AtmKind.TYPEVAR, AtmKind.NONE), + TYPEVAR_NULL(AtmKind.TYPEVAR, AtmKind.NULL), + TYPEVAR_PRIMITIVE(AtmKind.TYPEVAR, AtmKind.PRIMITIVE), + TYPEVAR_TYPEVAR(AtmKind.TYPEVAR, AtmKind.TYPEVAR), + TYPEVAR_UNION(AtmKind.TYPEVAR, AtmKind.UNION), + TYPEVAR_WILDCARD(AtmKind.TYPEVAR, AtmKind.WILDCARD), + + UNION_ARRAY(AtmKind.UNION, AtmKind.ARRAY), + UNION_DECLARED(AtmKind.UNION, AtmKind.DECLARED), + UNION_EXECUTABLE(AtmKind.UNION, AtmKind.EXECUTABLE), + UNION_INTERSECTION(AtmKind.UNION, AtmKind.INTERSECTION), + UNION_NONE(AtmKind.UNION, AtmKind.NONE), + UNION_NULL(AtmKind.UNION, AtmKind.NULL), + UNION_PRIMITIVE(AtmKind.UNION, AtmKind.PRIMITIVE), + UNION_TYPEVAR(AtmKind.UNION, AtmKind.TYPEVAR), + UNION_UNION(AtmKind.UNION, AtmKind.UNION), + UNION_WILDCARD(AtmKind.UNION, AtmKind.WILDCARD), + + WILDCARD_ARRAY(AtmKind.WILDCARD, AtmKind.ARRAY), + WILDCARD_DECLARED(AtmKind.WILDCARD, AtmKind.DECLARED), + WILDCARD_EXECUTABLE(AtmKind.WILDCARD, AtmKind.EXECUTABLE), + WILDCARD_INTERSECTION(AtmKind.WILDCARD, AtmKind.INTERSECTION), + WILDCARD_NONE(AtmKind.WILDCARD, AtmKind.NONE), + WILDCARD_NULL(AtmKind.WILDCARD, AtmKind.NULL), + WILDCARD_PRIMITIVE(AtmKind.WILDCARD, AtmKind.PRIMITIVE), + WILDCARD_TYPEVAR(AtmKind.WILDCARD, AtmKind.TYPEVAR), + WILDCARD_UNION(AtmKind.WILDCARD, AtmKind.UNION), + WILDCARD_WILDCARD(AtmKind.WILDCARD, AtmKind.WILDCARD); + + /** First AtmKind. */ + public final AtmKind type1Kind; + + /** Second AtmKind. */ + public final AtmKind type2Kind; + + /** + * Creates an AtmCombo. + * + * @param type1Kind first kind + * @param type2Kind second kind + */ + AtmCombo(AtmKind type1Kind, AtmKind type2Kind) { + this.type1Kind = type1Kind; + this.type2Kind = type2Kind; } - } - - /** - * Returns the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. {@literal - * (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE}. - * - * @return the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. {@literal - * (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE} - */ - public static AtmCombo valueOf(AtmKind type1, AtmKind type2) { - return comboMap[type1.ordinal()][type2.ordinal()]; - } - - /** - * Returns the AtmCombo corresponding to the pair of the classes for the given - * AnnotatedTypeMirrors. e.g. {@literal (AnnotatedPrimitiveType, AnnotatedDeclaredType) => - * AtmCombo.PRIMITIVE_DECLARED} - * - * @return the AtmCombo corresponding to the pair of the classes for the given - * AnnotatedTypeMirrors - */ - public static AtmCombo valueOf(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - return valueOf(AtmKind.valueOf(type1), AtmKind.valueOf(type2)); - } - - /** - * Call the visit method that corresponds to the AtmCombo that represents the classes of type1 and - * type2. That is, get the combo for type1 and type 2, use it to identify the correct visitor - * method, and call that method with type1, type2, and initialParam as arguments to the visit - * method. - * - * @param type1 first argument to the called visit method - * @param type2 second argument to the called visit method - * @param initialParam the parameter passed to the called visit method - * @param visitor the visitor that is visiting the given types - * @param the return type of the visitor's visit methods - * @param the parameter type of the visitor's visit methods - * @return the return value of the visit method called - */ - public static RETURN_TYPE accept( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - PARAM initialParam, - AtmComboVisitor visitor) { - AtmCombo combo = valueOf(type1, type2); - switch (combo) { - case ARRAY_ARRAY: - return visitor.visitArray_Array( - (AnnotatedArrayType) type1, (AnnotatedArrayType) type2, initialParam); - - case ARRAY_DECLARED: - return visitor.visitArray_Declared( - (AnnotatedArrayType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case ARRAY_EXECUTABLE: - return visitor.visitArray_Executable( - (AnnotatedArrayType) type1, (AnnotatedExecutableType) type2, initialParam); - - case ARRAY_INTERSECTION: - return visitor.visitArray_Intersection( - (AnnotatedArrayType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case ARRAY_NONE: - return visitor.visitArray_None( - (AnnotatedArrayType) type1, (AnnotatedNoType) type2, initialParam); - - case ARRAY_NULL: - return visitor.visitArray_Null( - (AnnotatedArrayType) type1, (AnnotatedNullType) type2, initialParam); - - case ARRAY_PRIMITIVE: - return visitor.visitArray_Primitive( - (AnnotatedArrayType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case ARRAY_TYPEVAR: - return visitor.visitArray_Typevar( - (AnnotatedArrayType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case ARRAY_UNION: - return visitor.visitArray_Union( - (AnnotatedArrayType) type1, (AnnotatedUnionType) type2, initialParam); - - case ARRAY_WILDCARD: - return visitor.visitArray_Wildcard( - (AnnotatedArrayType) type1, (AnnotatedWildcardType) type2, initialParam); - - case DECLARED_ARRAY: - return visitor.visitDeclared_Array( - (AnnotatedDeclaredType) type1, (AnnotatedArrayType) type2, initialParam); - - case DECLARED_DECLARED: - return visitor.visitDeclared_Declared( - (AnnotatedDeclaredType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case DECLARED_EXECUTABLE: - return visitor.visitDeclared_Executable( - (AnnotatedDeclaredType) type1, (AnnotatedExecutableType) type2, initialParam); - - case DECLARED_INTERSECTION: - return visitor.visitDeclared_Intersection( - (AnnotatedDeclaredType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case DECLARED_NONE: - return visitor.visitDeclared_None( - (AnnotatedDeclaredType) type1, (AnnotatedNoType) type2, initialParam); - - case DECLARED_NULL: - return visitor.visitDeclared_Null( - (AnnotatedDeclaredType) type1, (AnnotatedNullType) type2, initialParam); - - case DECLARED_PRIMITIVE: - return visitor.visitDeclared_Primitive( - (AnnotatedDeclaredType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case DECLARED_TYPEVAR: - return visitor.visitDeclared_Typevar( - (AnnotatedDeclaredType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case DECLARED_UNION: - return visitor.visitDeclared_Union( - (AnnotatedDeclaredType) type1, (AnnotatedUnionType) type2, initialParam); - - case DECLARED_WILDCARD: - return visitor.visitDeclared_Wildcard( - (AnnotatedDeclaredType) type1, (AnnotatedWildcardType) type2, initialParam); - - case EXECUTABLE_ARRAY: - return visitor.visitExecutable_Array( - (AnnotatedExecutableType) type1, (AnnotatedArrayType) type2, initialParam); - - case EXECUTABLE_DECLARED: - return visitor.visitExecutable_Declared( - (AnnotatedExecutableType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case EXECUTABLE_EXECUTABLE: - return visitor.visitExecutable_Executable( - (AnnotatedExecutableType) type1, (AnnotatedExecutableType) type2, initialParam); - - case EXECUTABLE_INTERSECTION: - return visitor.visitExecutable_Intersection( - (AnnotatedExecutableType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case EXECUTABLE_NONE: - return visitor.visitExecutable_None( - (AnnotatedExecutableType) type1, (AnnotatedNoType) type2, initialParam); - - case EXECUTABLE_NULL: - return visitor.visitExecutable_Null( - (AnnotatedExecutableType) type1, (AnnotatedNullType) type2, initialParam); - - case EXECUTABLE_PRIMITIVE: - return visitor.visitExecutable_Primitive( - (AnnotatedExecutableType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case EXECUTABLE_TYPEVAR: - return visitor.visitExecutable_Typevar( - (AnnotatedExecutableType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case EXECUTABLE_UNION: - return visitor.visitExecutable_Union( - (AnnotatedExecutableType) type1, (AnnotatedUnionType) type2, initialParam); - - case EXECUTABLE_WILDCARD: - return visitor.visitExecutable_Wildcard( - (AnnotatedExecutableType) type1, (AnnotatedWildcardType) type2, initialParam); - - case INTERSECTION_ARRAY: - return visitor.visitIntersection_Array( - (AnnotatedIntersectionType) type1, (AnnotatedArrayType) type2, initialParam); - - case INTERSECTION_DECLARED: - return visitor.visitIntersection_Declared( - (AnnotatedIntersectionType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case INTERSECTION_EXECUTABLE: - return visitor.visitIntersection_Executable( - (AnnotatedIntersectionType) type1, (AnnotatedExecutableType) type2, initialParam); - - case INTERSECTION_INTERSECTION: - return visitor.visitIntersection_Intersection( - (AnnotatedIntersectionType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case INTERSECTION_NONE: - return visitor.visitIntersection_None( - (AnnotatedIntersectionType) type1, (AnnotatedNoType) type2, initialParam); - - case INTERSECTION_NULL: - return visitor.visitIntersection_Null( - (AnnotatedIntersectionType) type1, (AnnotatedNullType) type2, initialParam); - - case INTERSECTION_PRIMITIVE: - return visitor.visitIntersection_Primitive( - (AnnotatedIntersectionType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case INTERSECTION_TYPEVAR: - return visitor.visitIntersection_Typevar( - (AnnotatedIntersectionType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case INTERSECTION_UNION: - return visitor.visitIntersection_Union( - (AnnotatedIntersectionType) type1, (AnnotatedUnionType) type2, initialParam); - - case INTERSECTION_WILDCARD: - return visitor.visitIntersection_Wildcard( - (AnnotatedIntersectionType) type1, (AnnotatedWildcardType) type2, initialParam); - - case NONE_ARRAY: - return visitor.visitNone_Array( - (AnnotatedNoType) type1, (AnnotatedArrayType) type2, initialParam); - - case NONE_DECLARED: - return visitor.visitNone_Declared( - (AnnotatedNoType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case NONE_EXECUTABLE: - return visitor.visitNone_Executable( - (AnnotatedNoType) type1, (AnnotatedExecutableType) type2, initialParam); - - case NONE_INTERSECTION: - return visitor.visitNone_Intersection( - (AnnotatedNoType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case NONE_NONE: - return visitor.visitNone_None( - (AnnotatedNoType) type1, (AnnotatedNoType) type2, initialParam); - - case NONE_NULL: - return visitor.visitNone_Null( - (AnnotatedNoType) type1, (AnnotatedNullType) type2, initialParam); - - case NONE_PRIMITIVE: - return visitor.visitNone_Primitive( - (AnnotatedNoType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case NONE_UNION: - return visitor.visitNone_Union( - (AnnotatedNoType) type1, (AnnotatedUnionType) type2, initialParam); - - case NONE_WILDCARD: - return visitor.visitNone_Wildcard( - (AnnotatedNoType) type1, (AnnotatedWildcardType) type2, initialParam); - - case NULL_ARRAY: - return visitor.visitNull_Array( - (AnnotatedNullType) type1, (AnnotatedArrayType) type2, initialParam); - - case NULL_DECLARED: - return visitor.visitNull_Declared( - (AnnotatedNullType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case NULL_EXECUTABLE: - return visitor.visitNull_Executable( - (AnnotatedNullType) type1, (AnnotatedExecutableType) type2, initialParam); - - case NULL_INTERSECTION: - return visitor.visitNull_Intersection( - (AnnotatedNullType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case NULL_NONE: - return visitor.visitNull_None( - (AnnotatedNullType) type1, (AnnotatedNoType) type2, initialParam); - - case NULL_NULL: - return visitor.visitNull_Null( - (AnnotatedNullType) type1, (AnnotatedNullType) type2, initialParam); - - case NULL_PRIMITIVE: - return visitor.visitNull_Primitive( - (AnnotatedNullType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case NULL_TYPEVAR: - return visitor.visitNull_Typevar( - (AnnotatedNullType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case NULL_UNION: - return visitor.visitNull_Union( - (AnnotatedNullType) type1, (AnnotatedUnionType) type2, initialParam); - - case NULL_WILDCARD: - return visitor.visitNull_Wildcard( - (AnnotatedNullType) type1, (AnnotatedWildcardType) type2, initialParam); - - case PRIMITIVE_ARRAY: - return visitor.visitPrimitive_Array( - (AnnotatedPrimitiveType) type1, (AnnotatedArrayType) type2, initialParam); - - case PRIMITIVE_DECLARED: - return visitor.visitPrimitive_Declared( - (AnnotatedPrimitiveType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case PRIMITIVE_EXECUTABLE: - return visitor.visitPrimitive_Executable( - (AnnotatedPrimitiveType) type1, (AnnotatedExecutableType) type2, initialParam); - case PRIMITIVE_INTERSECTION: - return visitor.visitPrimitive_Intersection( - (AnnotatedPrimitiveType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case PRIMITIVE_NONE: - return visitor.visitPrimitive_None( - (AnnotatedPrimitiveType) type1, (AnnotatedNoType) type2, initialParam); - - case PRIMITIVE_NULL: - return visitor.visitPrimitive_Null( - (AnnotatedPrimitiveType) type1, (AnnotatedNullType) type2, initialParam); - - case PRIMITIVE_PRIMITIVE: - return visitor.visitPrimitive_Primitive( - (AnnotatedPrimitiveType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case PRIMITIVE_TYPEVAR: - return visitor.visitPrimitive_Typevar( - (AnnotatedPrimitiveType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case PRIMITIVE_UNION: - return visitor.visitPrimitive_Union( - (AnnotatedPrimitiveType) type1, (AnnotatedUnionType) type2, initialParam); - - case PRIMITIVE_WILDCARD: - return visitor.visitPrimitive_Wildcard( - (AnnotatedPrimitiveType) type1, (AnnotatedWildcardType) type2, initialParam); - - case UNION_ARRAY: - return visitor.visitUnion_Array( - (AnnotatedUnionType) type1, (AnnotatedArrayType) type2, initialParam); - - case UNION_DECLARED: - return visitor.visitUnion_Declared( - (AnnotatedUnionType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case UNION_EXECUTABLE: - return visitor.visitUnion_Executable( - (AnnotatedUnionType) type1, (AnnotatedExecutableType) type2, initialParam); - - case UNION_INTERSECTION: - return visitor.visitUnion_Intersection( - (AnnotatedUnionType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case UNION_NONE: - return visitor.visitUnion_None( - (AnnotatedUnionType) type1, (AnnotatedNoType) type2, initialParam); - - case UNION_NULL: - return visitor.visitUnion_Null( - (AnnotatedUnionType) type1, (AnnotatedNullType) type2, initialParam); - - case UNION_PRIMITIVE: - return visitor.visitUnion_Primitive( - (AnnotatedUnionType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case UNION_TYPEVAR: - return visitor.visitUnion_Typevar( - (AnnotatedUnionType) type1, (AnnotatedTypeVariable) type2, initialParam); - - case UNION_UNION: - return visitor.visitUnion_Union( - (AnnotatedUnionType) type1, (AnnotatedUnionType) type2, initialParam); - - case UNION_WILDCARD: - return visitor.visitUnion_Wildcard( - (AnnotatedUnionType) type1, (AnnotatedWildcardType) type2, initialParam); - - case TYPEVAR_ARRAY: - return visitor.visitTypevar_Array( - (AnnotatedTypeVariable) type1, (AnnotatedArrayType) type2, initialParam); - - case TYPEVAR_DECLARED: - return visitor.visitTypevar_Declared( - (AnnotatedTypeVariable) type1, (AnnotatedDeclaredType) type2, initialParam); - - case TYPEVAR_EXECUTABLE: - return visitor.visitTypevar_Executable( - (AnnotatedTypeVariable) type1, (AnnotatedExecutableType) type2, initialParam); - - case TYPEVAR_INTERSECTION: - return visitor.visitTypevar_Intersection( - (AnnotatedTypeVariable) type1, (AnnotatedIntersectionType) type2, initialParam); - - case TYPEVAR_NONE: - return visitor.visitTypevar_None( - (AnnotatedTypeVariable) type1, (AnnotatedNoType) type2, initialParam); - - case TYPEVAR_NULL: - return visitor.visitTypevar_Null( - (AnnotatedTypeVariable) type1, (AnnotatedNullType) type2, initialParam); - - case TYPEVAR_PRIMITIVE: - return visitor.visitTypevar_Primitive( - (AnnotatedTypeVariable) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case TYPEVAR_TYPEVAR: - return visitor.visitTypevar_Typevar( - (AnnotatedTypeVariable) type1, (AnnotatedTypeVariable) type2, initialParam); - - case TYPEVAR_UNION: - return visitor.visitTypevar_Union( - (AnnotatedTypeVariable) type1, (AnnotatedUnionType) type2, initialParam); - - case TYPEVAR_WILDCARD: - return visitor.visitTypevar_Wildcard( - (AnnotatedTypeVariable) type1, (AnnotatedWildcardType) type2, initialParam); - - case WILDCARD_ARRAY: - return visitor.visitWildcard_Array( - (AnnotatedWildcardType) type1, (AnnotatedArrayType) type2, initialParam); - - case WILDCARD_DECLARED: - return visitor.visitWildcard_Declared( - (AnnotatedWildcardType) type1, (AnnotatedDeclaredType) type2, initialParam); - - case WILDCARD_EXECUTABLE: - return visitor.visitWildcard_Executable( - (AnnotatedWildcardType) type1, (AnnotatedExecutableType) type2, initialParam); - - case WILDCARD_INTERSECTION: - return visitor.visitWildcard_Intersection( - (AnnotatedWildcardType) type1, (AnnotatedIntersectionType) type2, initialParam); - - case WILDCARD_NONE: - return visitor.visitWildcard_None( - (AnnotatedWildcardType) type1, (AnnotatedNoType) type2, initialParam); - - case WILDCARD_NULL: - return visitor.visitWildcard_Null( - (AnnotatedWildcardType) type1, (AnnotatedNullType) type2, initialParam); - - case WILDCARD_PRIMITIVE: - return visitor.visitWildcard_Primitive( - (AnnotatedWildcardType) type1, (AnnotatedPrimitiveType) type2, initialParam); - - case WILDCARD_TYPEVAR: - return visitor.visitWildcard_Typevar( - (AnnotatedWildcardType) type1, (AnnotatedTypeVariable) type2, initialParam); + /** + * Used to locate AtmCombo pairs using AtmKinds as indices into a two-dimensional array. This + * ensures that all pairs are included. + */ + private static final AtmCombo[][] comboMap = + new AtmCombo[AtmKind.values().length][AtmKind.values().length]; + + static { + for (AtmCombo atmCombo : AtmCombo.values()) { + comboMap[atmCombo.type1Kind.ordinal()][atmCombo.type2Kind.ordinal()] = atmCombo; + } + } - case WILDCARD_UNION: - return visitor.visitWildcard_Union( - (AnnotatedWildcardType) type1, (AnnotatedUnionType) type2, initialParam); + /** + * Returns the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. + * {@literal (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE}. + * + * @return the AtmCombo corresponding to the given ATM pair of the given ATMKinds. e.g. + * {@literal (AtmKind.NULL, AtmKind.EXECUTABLE) => AtmCombo.NULL_EXECUTABLE} + */ + public static AtmCombo valueOf(AtmKind type1, AtmKind type2) { + return comboMap[type1.ordinal()][type2.ordinal()]; + } - case WILDCARD_WILDCARD: - return visitor.visitWildcard_Wildcard( - (AnnotatedWildcardType) type1, (AnnotatedWildcardType) type2, initialParam); + /** + * Returns the AtmCombo corresponding to the pair of the classes for the given + * AnnotatedTypeMirrors. e.g. {@literal (AnnotatedPrimitiveType, AnnotatedDeclaredType) => + * AtmCombo.PRIMITIVE_DECLARED} + * + * @return the AtmCombo corresponding to the pair of the classes for the given + * AnnotatedTypeMirrors + */ + public static AtmCombo valueOf(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + return valueOf(AtmKind.valueOf(type1), AtmKind.valueOf(type2)); + } - default: - // Reaching this point indicates that there is an AtmCombo missing - throw new BugInCF("Unhandled AtmCombo ( " + combo + " ) "); + /** + * Call the visit method that corresponds to the AtmCombo that represents the classes of type1 + * and type2. That is, get the combo for type1 and type 2, use it to identify the correct + * visitor method, and call that method with type1, type2, and initialParam as arguments to the + * visit method. + * + * @param type1 first argument to the called visit method + * @param type2 second argument to the called visit method + * @param initialParam the parameter passed to the called visit method + * @param visitor the visitor that is visiting the given types + * @param the return type of the visitor's visit methods + * @param the parameter type of the visitor's visit methods + * @return the return value of the visit method called + */ + public static RETURN_TYPE accept( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + PARAM initialParam, + AtmComboVisitor visitor) { + AtmCombo combo = valueOf(type1, type2); + switch (combo) { + case ARRAY_ARRAY: + return visitor.visitArray_Array( + (AnnotatedArrayType) type1, (AnnotatedArrayType) type2, initialParam); + + case ARRAY_DECLARED: + return visitor.visitArray_Declared( + (AnnotatedArrayType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case ARRAY_EXECUTABLE: + return visitor.visitArray_Executable( + (AnnotatedArrayType) type1, (AnnotatedExecutableType) type2, initialParam); + + case ARRAY_INTERSECTION: + return visitor.visitArray_Intersection( + (AnnotatedArrayType) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case ARRAY_NONE: + return visitor.visitArray_None( + (AnnotatedArrayType) type1, (AnnotatedNoType) type2, initialParam); + + case ARRAY_NULL: + return visitor.visitArray_Null( + (AnnotatedArrayType) type1, (AnnotatedNullType) type2, initialParam); + + case ARRAY_PRIMITIVE: + return visitor.visitArray_Primitive( + (AnnotatedArrayType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case ARRAY_TYPEVAR: + return visitor.visitArray_Typevar( + (AnnotatedArrayType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case ARRAY_UNION: + return visitor.visitArray_Union( + (AnnotatedArrayType) type1, (AnnotatedUnionType) type2, initialParam); + + case ARRAY_WILDCARD: + return visitor.visitArray_Wildcard( + (AnnotatedArrayType) type1, (AnnotatedWildcardType) type2, initialParam); + + case DECLARED_ARRAY: + return visitor.visitDeclared_Array( + (AnnotatedDeclaredType) type1, (AnnotatedArrayType) type2, initialParam); + + case DECLARED_DECLARED: + return visitor.visitDeclared_Declared( + (AnnotatedDeclaredType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case DECLARED_EXECUTABLE: + return visitor.visitDeclared_Executable( + (AnnotatedDeclaredType) type1, + (AnnotatedExecutableType) type2, + initialParam); + + case DECLARED_INTERSECTION: + return visitor.visitDeclared_Intersection( + (AnnotatedDeclaredType) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case DECLARED_NONE: + return visitor.visitDeclared_None( + (AnnotatedDeclaredType) type1, (AnnotatedNoType) type2, initialParam); + + case DECLARED_NULL: + return visitor.visitDeclared_Null( + (AnnotatedDeclaredType) type1, (AnnotatedNullType) type2, initialParam); + + case DECLARED_PRIMITIVE: + return visitor.visitDeclared_Primitive( + (AnnotatedDeclaredType) type1, + (AnnotatedPrimitiveType) type2, + initialParam); + + case DECLARED_TYPEVAR: + return visitor.visitDeclared_Typevar( + (AnnotatedDeclaredType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case DECLARED_UNION: + return visitor.visitDeclared_Union( + (AnnotatedDeclaredType) type1, (AnnotatedUnionType) type2, initialParam); + + case DECLARED_WILDCARD: + return visitor.visitDeclared_Wildcard( + (AnnotatedDeclaredType) type1, (AnnotatedWildcardType) type2, initialParam); + + case EXECUTABLE_ARRAY: + return visitor.visitExecutable_Array( + (AnnotatedExecutableType) type1, (AnnotatedArrayType) type2, initialParam); + + case EXECUTABLE_DECLARED: + return visitor.visitExecutable_Declared( + (AnnotatedExecutableType) type1, + (AnnotatedDeclaredType) type2, + initialParam); + + case EXECUTABLE_EXECUTABLE: + return visitor.visitExecutable_Executable( + (AnnotatedExecutableType) type1, + (AnnotatedExecutableType) type2, + initialParam); + + case EXECUTABLE_INTERSECTION: + return visitor.visitExecutable_Intersection( + (AnnotatedExecutableType) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case EXECUTABLE_NONE: + return visitor.visitExecutable_None( + (AnnotatedExecutableType) type1, (AnnotatedNoType) type2, initialParam); + + case EXECUTABLE_NULL: + return visitor.visitExecutable_Null( + (AnnotatedExecutableType) type1, (AnnotatedNullType) type2, initialParam); + + case EXECUTABLE_PRIMITIVE: + return visitor.visitExecutable_Primitive( + (AnnotatedExecutableType) type1, + (AnnotatedPrimitiveType) type2, + initialParam); + + case EXECUTABLE_TYPEVAR: + return visitor.visitExecutable_Typevar( + (AnnotatedExecutableType) type1, + (AnnotatedTypeVariable) type2, + initialParam); + + case EXECUTABLE_UNION: + return visitor.visitExecutable_Union( + (AnnotatedExecutableType) type1, (AnnotatedUnionType) type2, initialParam); + + case EXECUTABLE_WILDCARD: + return visitor.visitExecutable_Wildcard( + (AnnotatedExecutableType) type1, + (AnnotatedWildcardType) type2, + initialParam); + + case INTERSECTION_ARRAY: + return visitor.visitIntersection_Array( + (AnnotatedIntersectionType) type1, + (AnnotatedArrayType) type2, + initialParam); + + case INTERSECTION_DECLARED: + return visitor.visitIntersection_Declared( + (AnnotatedIntersectionType) type1, + (AnnotatedDeclaredType) type2, + initialParam); + + case INTERSECTION_EXECUTABLE: + return visitor.visitIntersection_Executable( + (AnnotatedIntersectionType) type1, + (AnnotatedExecutableType) type2, + initialParam); + + case INTERSECTION_INTERSECTION: + return visitor.visitIntersection_Intersection( + (AnnotatedIntersectionType) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case INTERSECTION_NONE: + return visitor.visitIntersection_None( + (AnnotatedIntersectionType) type1, (AnnotatedNoType) type2, initialParam); + + case INTERSECTION_NULL: + return visitor.visitIntersection_Null( + (AnnotatedIntersectionType) type1, (AnnotatedNullType) type2, initialParam); + + case INTERSECTION_PRIMITIVE: + return visitor.visitIntersection_Primitive( + (AnnotatedIntersectionType) type1, + (AnnotatedPrimitiveType) type2, + initialParam); + + case INTERSECTION_TYPEVAR: + return visitor.visitIntersection_Typevar( + (AnnotatedIntersectionType) type1, + (AnnotatedTypeVariable) type2, + initialParam); + + case INTERSECTION_UNION: + return visitor.visitIntersection_Union( + (AnnotatedIntersectionType) type1, + (AnnotatedUnionType) type2, + initialParam); + + case INTERSECTION_WILDCARD: + return visitor.visitIntersection_Wildcard( + (AnnotatedIntersectionType) type1, + (AnnotatedWildcardType) type2, + initialParam); + + case NONE_ARRAY: + return visitor.visitNone_Array( + (AnnotatedNoType) type1, (AnnotatedArrayType) type2, initialParam); + + case NONE_DECLARED: + return visitor.visitNone_Declared( + (AnnotatedNoType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case NONE_EXECUTABLE: + return visitor.visitNone_Executable( + (AnnotatedNoType) type1, (AnnotatedExecutableType) type2, initialParam); + + case NONE_INTERSECTION: + return visitor.visitNone_Intersection( + (AnnotatedNoType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case NONE_NONE: + return visitor.visitNone_None( + (AnnotatedNoType) type1, (AnnotatedNoType) type2, initialParam); + + case NONE_NULL: + return visitor.visitNone_Null( + (AnnotatedNoType) type1, (AnnotatedNullType) type2, initialParam); + + case NONE_PRIMITIVE: + return visitor.visitNone_Primitive( + (AnnotatedNoType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case NONE_UNION: + return visitor.visitNone_Union( + (AnnotatedNoType) type1, (AnnotatedUnionType) type2, initialParam); + + case NONE_WILDCARD: + return visitor.visitNone_Wildcard( + (AnnotatedNoType) type1, (AnnotatedWildcardType) type2, initialParam); + + case NULL_ARRAY: + return visitor.visitNull_Array( + (AnnotatedNullType) type1, (AnnotatedArrayType) type2, initialParam); + + case NULL_DECLARED: + return visitor.visitNull_Declared( + (AnnotatedNullType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case NULL_EXECUTABLE: + return visitor.visitNull_Executable( + (AnnotatedNullType) type1, (AnnotatedExecutableType) type2, initialParam); + + case NULL_INTERSECTION: + return visitor.visitNull_Intersection( + (AnnotatedNullType) type1, (AnnotatedIntersectionType) type2, initialParam); + + case NULL_NONE: + return visitor.visitNull_None( + (AnnotatedNullType) type1, (AnnotatedNoType) type2, initialParam); + + case NULL_NULL: + return visitor.visitNull_Null( + (AnnotatedNullType) type1, (AnnotatedNullType) type2, initialParam); + + case NULL_PRIMITIVE: + return visitor.visitNull_Primitive( + (AnnotatedNullType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case NULL_TYPEVAR: + return visitor.visitNull_Typevar( + (AnnotatedNullType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case NULL_UNION: + return visitor.visitNull_Union( + (AnnotatedNullType) type1, (AnnotatedUnionType) type2, initialParam); + + case NULL_WILDCARD: + return visitor.visitNull_Wildcard( + (AnnotatedNullType) type1, (AnnotatedWildcardType) type2, initialParam); + + case PRIMITIVE_ARRAY: + return visitor.visitPrimitive_Array( + (AnnotatedPrimitiveType) type1, (AnnotatedArrayType) type2, initialParam); + + case PRIMITIVE_DECLARED: + return visitor.visitPrimitive_Declared( + (AnnotatedPrimitiveType) type1, + (AnnotatedDeclaredType) type2, + initialParam); + + case PRIMITIVE_EXECUTABLE: + return visitor.visitPrimitive_Executable( + (AnnotatedPrimitiveType) type1, + (AnnotatedExecutableType) type2, + initialParam); + + case PRIMITIVE_INTERSECTION: + return visitor.visitPrimitive_Intersection( + (AnnotatedPrimitiveType) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case PRIMITIVE_NONE: + return visitor.visitPrimitive_None( + (AnnotatedPrimitiveType) type1, (AnnotatedNoType) type2, initialParam); + + case PRIMITIVE_NULL: + return visitor.visitPrimitive_Null( + (AnnotatedPrimitiveType) type1, (AnnotatedNullType) type2, initialParam); + + case PRIMITIVE_PRIMITIVE: + return visitor.visitPrimitive_Primitive( + (AnnotatedPrimitiveType) type1, + (AnnotatedPrimitiveType) type2, + initialParam); + + case PRIMITIVE_TYPEVAR: + return visitor.visitPrimitive_Typevar( + (AnnotatedPrimitiveType) type1, + (AnnotatedTypeVariable) type2, + initialParam); + + case PRIMITIVE_UNION: + return visitor.visitPrimitive_Union( + (AnnotatedPrimitiveType) type1, (AnnotatedUnionType) type2, initialParam); + + case PRIMITIVE_WILDCARD: + return visitor.visitPrimitive_Wildcard( + (AnnotatedPrimitiveType) type1, + (AnnotatedWildcardType) type2, + initialParam); + + case UNION_ARRAY: + return visitor.visitUnion_Array( + (AnnotatedUnionType) type1, (AnnotatedArrayType) type2, initialParam); + + case UNION_DECLARED: + return visitor.visitUnion_Declared( + (AnnotatedUnionType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case UNION_EXECUTABLE: + return visitor.visitUnion_Executable( + (AnnotatedUnionType) type1, (AnnotatedExecutableType) type2, initialParam); + + case UNION_INTERSECTION: + return visitor.visitUnion_Intersection( + (AnnotatedUnionType) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case UNION_NONE: + return visitor.visitUnion_None( + (AnnotatedUnionType) type1, (AnnotatedNoType) type2, initialParam); + + case UNION_NULL: + return visitor.visitUnion_Null( + (AnnotatedUnionType) type1, (AnnotatedNullType) type2, initialParam); + + case UNION_PRIMITIVE: + return visitor.visitUnion_Primitive( + (AnnotatedUnionType) type1, (AnnotatedPrimitiveType) type2, initialParam); + + case UNION_TYPEVAR: + return visitor.visitUnion_Typevar( + (AnnotatedUnionType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case UNION_UNION: + return visitor.visitUnion_Union( + (AnnotatedUnionType) type1, (AnnotatedUnionType) type2, initialParam); + + case UNION_WILDCARD: + return visitor.visitUnion_Wildcard( + (AnnotatedUnionType) type1, (AnnotatedWildcardType) type2, initialParam); + + case TYPEVAR_ARRAY: + return visitor.visitTypevar_Array( + (AnnotatedTypeVariable) type1, (AnnotatedArrayType) type2, initialParam); + + case TYPEVAR_DECLARED: + return visitor.visitTypevar_Declared( + (AnnotatedTypeVariable) type1, (AnnotatedDeclaredType) type2, initialParam); + + case TYPEVAR_EXECUTABLE: + return visitor.visitTypevar_Executable( + (AnnotatedTypeVariable) type1, + (AnnotatedExecutableType) type2, + initialParam); + + case TYPEVAR_INTERSECTION: + return visitor.visitTypevar_Intersection( + (AnnotatedTypeVariable) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case TYPEVAR_NONE: + return visitor.visitTypevar_None( + (AnnotatedTypeVariable) type1, (AnnotatedNoType) type2, initialParam); + + case TYPEVAR_NULL: + return visitor.visitTypevar_Null( + (AnnotatedTypeVariable) type1, (AnnotatedNullType) type2, initialParam); + + case TYPEVAR_PRIMITIVE: + return visitor.visitTypevar_Primitive( + (AnnotatedTypeVariable) type1, + (AnnotatedPrimitiveType) type2, + initialParam); + + case TYPEVAR_TYPEVAR: + return visitor.visitTypevar_Typevar( + (AnnotatedTypeVariable) type1, (AnnotatedTypeVariable) type2, initialParam); + + case TYPEVAR_UNION: + return visitor.visitTypevar_Union( + (AnnotatedTypeVariable) type1, (AnnotatedUnionType) type2, initialParam); + + case TYPEVAR_WILDCARD: + return visitor.visitTypevar_Wildcard( + (AnnotatedTypeVariable) type1, (AnnotatedWildcardType) type2, initialParam); + + case WILDCARD_ARRAY: + return visitor.visitWildcard_Array( + (AnnotatedWildcardType) type1, (AnnotatedArrayType) type2, initialParam); + + case WILDCARD_DECLARED: + return visitor.visitWildcard_Declared( + (AnnotatedWildcardType) type1, (AnnotatedDeclaredType) type2, initialParam); + + case WILDCARD_EXECUTABLE: + return visitor.visitWildcard_Executable( + (AnnotatedWildcardType) type1, + (AnnotatedExecutableType) type2, + initialParam); + + case WILDCARD_INTERSECTION: + return visitor.visitWildcard_Intersection( + (AnnotatedWildcardType) type1, + (AnnotatedIntersectionType) type2, + initialParam); + + case WILDCARD_NONE: + return visitor.visitWildcard_None( + (AnnotatedWildcardType) type1, (AnnotatedNoType) type2, initialParam); + + case WILDCARD_NULL: + return visitor.visitWildcard_Null( + (AnnotatedWildcardType) type1, (AnnotatedNullType) type2, initialParam); + + case WILDCARD_PRIMITIVE: + return visitor.visitWildcard_Primitive( + (AnnotatedWildcardType) type1, + (AnnotatedPrimitiveType) type2, + initialParam); + + case WILDCARD_TYPEVAR: + return visitor.visitWildcard_Typevar( + (AnnotatedWildcardType) type1, (AnnotatedTypeVariable) type2, initialParam); + + case WILDCARD_UNION: + return visitor.visitWildcard_Union( + (AnnotatedWildcardType) type1, (AnnotatedUnionType) type2, initialParam); + + case WILDCARD_WILDCARD: + return visitor.visitWildcard_Wildcard( + (AnnotatedWildcardType) type1, (AnnotatedWildcardType) type2, initialParam); + + default: + // Reaching this point indicates that there is an AtmCombo missing + throw new BugInCF("Unhandled AtmCombo ( " + combo + " ) "); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java index caed91dcc0c..314e4702d5e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.util; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -24,6 +17,15 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; + /** * Helper class to compute the least upper bound of two AnnotatedTypeMirrors. * @@ -32,411 +34,417 @@ */ class AtmLubVisitor extends AbstractAtmComboVisitor { - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy. */ - private final QualifierHierarchy qualHierarchy; - - /** - * List of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been visited. - * Call {@link #visited(AnnotatedTypeMirror)} to check if the type have been visited, so that - * reference equality is used rather than {@link #equals(Object)}. - */ - private final List visited = new ArrayList<>(); - - AtmLubVisitor(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - } - - /** - * Returns an ATM that is the least upper bound of type1 and type2 and whose Java type is - * lubJavaType. lubJavaType must be a super type or convertible to the Java types of type1 and - * type2. - */ - AnnotatedTypeMirror lub( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, TypeMirror lubJavaType) { - AnnotatedTypeMirror lub = AnnotatedTypeMirror.createType(lubJavaType, atypeFactory, false); - - if (type1.getKind() == TypeKind.NULL) { - return lubWithNull((AnnotatedNullType) type1, type2, lub); - } - if (type2.getKind() == TypeKind.NULL) { - return lubWithNull((AnnotatedNullType) type2, type1, lub); - } + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; - AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); - AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); - - visit(type1AsLub, type2AsLub, lub); - visited.clear(); - return lub; - } - - /** - * Lub a type with the nulltype. - * - * @param nullType an annotated null type - * @param otherType the other type to lub - * @param lub a type mirror that will be copied, side-effected, and returned - * @return the lub - */ - private AnnotatedTypeMirror lubWithNull( - AnnotatedNullType nullType, AnnotatedTypeMirror otherType, AnnotatedTypeMirror lub) { - TypeMirror nullTM = nullType.getUnderlyingType(); - AnnotatedTypeMirror otherAsLub; - if (otherType.getKind() == TypeKind.NULL) { - otherAsLub = otherType.deepCopy(); - } else { - otherAsLub = AnnotatedTypes.asSuper(atypeFactory, otherType, lub); + /** The qualifier hierarchy. */ + private final QualifierHierarchy qualHierarchy; + + /** + * List of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been + * visited. Call {@link #visited(AnnotatedTypeMirror)} to check if the type have been visited, + * so that reference equality is used rather than {@link #equals(Object)}. + */ + private final List visited = new ArrayList<>(); + + AtmLubVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); } - TypeMirror otherTM = otherAsLub.getUnderlyingType(); - - lub = otherAsLub.deepCopy(); - - if (otherAsLub.getKind() != TypeKind.TYPEVAR && otherAsLub.getKind() != TypeKind.WILDCARD) { - for (AnnotationMirror nullAnno : nullType.getAnnotations()) { - AnnotationMirror otherAnno = otherAsLub.getAnnotationInHierarchy(nullAnno); - AnnotationMirror lubAnno = - qualHierarchy.leastUpperBoundShallow(nullAnno, nullTM, otherAnno, otherTM); - lub.replaceAnnotation(lubAnno); - } - return lub; + + /** + * Returns an ATM that is the least upper bound of type1 and type2 and whose Java type is + * lubJavaType. lubJavaType must be a super type or convertible to the Java types of type1 and + * type2. + */ + AnnotatedTypeMirror lub( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, TypeMirror lubJavaType) { + AnnotatedTypeMirror lub = AnnotatedTypeMirror.createType(lubJavaType, atypeFactory, false); + + if (type1.getKind() == TypeKind.NULL) { + return lubWithNull((AnnotatedNullType) type1, type2, lub); + } + if (type2.getKind() == TypeKind.NULL) { + return lubWithNull((AnnotatedNullType) type2, type1, lub); + } + + AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); + AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); + + visit(type1AsLub, type2AsLub, lub); + visited.clear(); + return lub; } - // LUB(@N null, T), where T's upper bound is @U and T's lower bound is @L - // if @L <: @U <: @N then LUB(@N null, T) = @N T - // if @L <: @N <:@U && @N != @L then LUB(@N null, T) = @U T - // if @N <: @L <: @U then LUB(@N null, T) = T - AnnotationMirrorSet lowerBounds = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, otherAsLub); - for (AnnotationMirror lowerBound : lowerBounds) { - AnnotationMirror nullAnno = nullType.getAnnotationInHierarchy(lowerBound); - AnnotationMirror upperBound = otherAsLub.getEffectiveAnnotationInHierarchy(lowerBound); - if (qualHierarchy.isSubtypeShallow(upperBound, otherTM, nullAnno, nullTM)) { - // @L <: @U <: @N - lub.replaceAnnotation(nullAnno); - } else if (qualHierarchy.isSubtypeShallow(lowerBound, otherTM, nullAnno, nullTM) - && !qualHierarchy.isSubtypeShallow(nullAnno, nullTM, lowerBound, otherTM)) { - // @L <: @N <:@U && @N != @L - lub.replaceAnnotation(upperBound); - } // else @N <: @L <: @U + /** + * Lub a type with the nulltype. + * + * @param nullType an annotated null type + * @param otherType the other type to lub + * @param lub a type mirror that will be copied, side-effected, and returned + * @return the lub + */ + private AnnotatedTypeMirror lubWithNull( + AnnotatedNullType nullType, AnnotatedTypeMirror otherType, AnnotatedTypeMirror lub) { + TypeMirror nullTM = nullType.getUnderlyingType(); + AnnotatedTypeMirror otherAsLub; + if (otherType.getKind() == TypeKind.NULL) { + otherAsLub = otherType.deepCopy(); + } else { + otherAsLub = AnnotatedTypes.asSuper(atypeFactory, otherType, lub); + } + TypeMirror otherTM = otherAsLub.getUnderlyingType(); + + lub = otherAsLub.deepCopy(); + + if (otherAsLub.getKind() != TypeKind.TYPEVAR && otherAsLub.getKind() != TypeKind.WILDCARD) { + for (AnnotationMirror nullAnno : nullType.getAnnotations()) { + AnnotationMirror otherAnno = otherAsLub.getAnnotationInHierarchy(nullAnno); + AnnotationMirror lubAnno = + qualHierarchy.leastUpperBoundShallow(nullAnno, nullTM, otherAnno, otherTM); + lub.replaceAnnotation(lubAnno); + } + return lub; + } + + // LUB(@N null, T), where T's upper bound is @U and T's lower bound is @L + // if @L <: @U <: @N then LUB(@N null, T) = @N T + // if @L <: @N <:@U && @N != @L then LUB(@N null, T) = @U T + // if @N <: @L <: @U then LUB(@N null, T) = T + AnnotationMirrorSet lowerBounds = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, otherAsLub); + for (AnnotationMirror lowerBound : lowerBounds) { + AnnotationMirror nullAnno = nullType.getAnnotationInHierarchy(lowerBound); + AnnotationMirror upperBound = otherAsLub.getEffectiveAnnotationInHierarchy(lowerBound); + if (qualHierarchy.isSubtypeShallow(upperBound, otherTM, nullAnno, nullTM)) { + // @L <: @U <: @N + lub.replaceAnnotation(nullAnno); + } else if (qualHierarchy.isSubtypeShallow(lowerBound, otherTM, nullAnno, nullTM) + && !qualHierarchy.isSubtypeShallow(nullAnno, nullTM, lowerBound, otherTM)) { + // @L <: @N <:@U && @N != @L + lub.replaceAnnotation(upperBound); + } // else @N <: @L <: @U + } + return lub; } - return lub; - } - - /** - * Replaces the primary annotations of lub with the lub of the primary annotations of type1 and - * type2. - */ - private void lubPrimaryAnnotations( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - Set lubSet; - if (type1.getAnnotations().isEmpty()) { - lubSet = type2.getAnnotations(); - } else if (type2.getAnnotations().isEmpty()) { - lubSet = type1.getAnnotations(); - } else { - lubSet = - qualHierarchy.leastUpperBoundsShallow( - type1.getAnnotations(), - type1.getUnderlyingType(), - type2.getAnnotations(), - type2.getUnderlyingType()); + + /** + * Replaces the primary annotations of lub with the lub of the primary annotations of type1 and + * type2. + */ + private void lubPrimaryAnnotations( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + Set lubSet; + if (type1.getAnnotations().isEmpty()) { + lubSet = type2.getAnnotations(); + } else if (type2.getAnnotations().isEmpty()) { + lubSet = type1.getAnnotations(); + } else { + lubSet = + qualHierarchy.leastUpperBoundsShallow( + type1.getAnnotations(), + type1.getUnderlyingType(), + type2.getAnnotations(), + type2.getUnderlyingType()); + } + lub.replaceAnnotations(lubSet); } - lub.replaceAnnotations(lubSet); - } - - /** - * Casts lub to the type of type, or issues an error if type and lub are not the same kind. - * - * @param the type to cast to - * @param type a values of the type to cast to - * @param lub the value to cast to {@code T} - * @return {@code lub}, casted to {@code T} - */ - private T castLub(T type, AnnotatedTypeMirror lub) { - if (type.getKind() != lub.getKind()) { - throw new BugInCF( - "AtmLubVisitor: unexpected type. Found: %s Required %s", lub.getKind(), type.getKind()); + + /** + * Casts lub to the type of type, or issues an error if type and lub are not the same kind. + * + * @param the type to cast to + * @param type a values of the type to cast to + * @param lub the value to cast to {@code T} + * @return {@code lub}, casted to {@code T} + */ + private T castLub(T type, AnnotatedTypeMirror lub) { + if (type.getKind() != lub.getKind()) { + throw new BugInCF( + "AtmLubVisitor: unexpected type. Found: %s Required %s", + lub.getKind(), type.getKind()); + } + @SuppressWarnings("unchecked") + T castedLub = (T) lub; + return castedLub; } - @SuppressWarnings("unchecked") - T castedLub = (T) lub; - return castedLub; - } - - @Override - public Void visitNull_Null( - AnnotatedNullType type1, AnnotatedNullType type2, AnnotatedTypeMirror lub) { - // Called to issue warning - castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); - return null; - } - - @Override - public Void visitArray_Array( - AnnotatedArrayType type1, AnnotatedArrayType type2, AnnotatedTypeMirror lub) { - AnnotatedArrayType lubArray = castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lubArray); - - visit(type1.getComponentType(), type2.getComponentType(), lubArray.getComponentType()); - return null; - } - - @Override - public Void visitDeclared_Declared( - AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, AnnotatedTypeMirror lub) { - AnnotatedDeclaredType castedLub = castLub(type1, lub); - - lubPrimaryAnnotations(type1, type2, lub); - - if (lub.getKind() == TypeKind.DECLARED) { - AnnotatedDeclaredType enclosingLub = ((AnnotatedDeclaredType) lub).getEnclosingType(); - AnnotatedDeclaredType enclosing1 = type1.getEnclosingType(); - AnnotatedDeclaredType enclosing2 = type2.getEnclosingType(); - if (enclosingLub != null && enclosing1 != null && enclosing2 != null) { - visitDeclared_Declared(enclosing1, enclosing2, enclosingLub); - } + + @Override + public Void visitNull_Null( + AnnotatedNullType type1, AnnotatedNullType type2, AnnotatedTypeMirror lub) { + // Called to issue warning + castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); + return null; } - for (int i = 0; i < type1.getTypeArguments().size(); i++) { - AnnotatedTypeMirror type1TypeArg = type1.getTypeArguments().get(i); - AnnotatedTypeMirror type2TypeArg = type2.getTypeArguments().get(i); - AnnotatedTypeMirror lubTypeArg = castedLub.getTypeArguments().get(i); - lubTypeArgument(type1TypeArg, type2TypeArg, lubTypeArg); + @Override + public Void visitArray_Array( + AnnotatedArrayType type1, AnnotatedArrayType type2, AnnotatedTypeMirror lub) { + AnnotatedArrayType lubArray = castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lubArray); + + visit(type1.getComponentType(), type2.getComponentType(), lubArray.getComponentType()); + return null; } - return null; - } - - /** - * Annotate the least upper bound of two type arguments. - * - * @param type1 the first type argument - * @param type2 the second type argument - * @param lub the least upper bound - */ - private void lubTypeArgument( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - if ((type1.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type1).isTypeArgOfRawType()) - || (type2.getKind() == TypeKind.WILDCARD - && ((AnnotatedWildcardType) type2).isTypeArgOfRawType())) { - // The asSuper calls below don't seem to retain if a type variable was uninferred. - // There is a similar check in the wildcards branch below, not sure when that is - // actually hit. - // TODO: see whether anything else should be done. See typetools issue 6438 and eisop - // issue 703. - if (lub.getKind() == TypeKind.WILDCARD) { - ((AnnotatedWildcardType) lub).setTypeArgOfRawType(); - } - return; + + @Override + public Void visitDeclared_Declared( + AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, AnnotatedTypeMirror lub) { + AnnotatedDeclaredType castedLub = castLub(type1, lub); + + lubPrimaryAnnotations(type1, type2, lub); + + if (lub.getKind() == TypeKind.DECLARED) { + AnnotatedDeclaredType enclosingLub = ((AnnotatedDeclaredType) lub).getEnclosingType(); + AnnotatedDeclaredType enclosing1 = type1.getEnclosingType(); + AnnotatedDeclaredType enclosing2 = type2.getEnclosingType(); + if (enclosingLub != null && enclosing1 != null && enclosing2 != null) { + visitDeclared_Declared(enclosing1, enclosing2, enclosingLub); + } + } + + for (int i = 0; i < type1.getTypeArguments().size(); i++) { + AnnotatedTypeMirror type1TypeArg = type1.getTypeArguments().get(i); + AnnotatedTypeMirror type2TypeArg = type2.getTypeArguments().get(i); + AnnotatedTypeMirror lubTypeArg = castedLub.getTypeArguments().get(i); + lubTypeArgument(type1TypeArg, type2TypeArg, lubTypeArg); + } + return null; } - // In lub(), asSuper is called on type1 and type2, but asSuper does not recur into type - // arguments, so call asSuper on the type arguments so that they have the same underlying - // type. - AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); - AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); - - // If the type argument is a wildcard or captured type argument, then the lub computation is - // slightly different. The primary annotation on the lower bound is the glb of lower bounds - // of the type types. This is because the lub of Gen<@A ? extends @A Object> and Gen<@B ? - // extends @A Object> is Gen<@B ? extends @A Object>. If visit(type1AsLub, type2AsLub, lub) - // was called instead of the below code, then the lub would be Gen<@A ? extends @A Object>. - // (Note the lub of Gen<@A ? super @A Object> and Gen<@A ? super @B Object> does not exist, - // but Gen<@A ? super @B Object> is returned.) - if (lub.getKind() == TypeKind.WILDCARD) { - if (visited(lub)) { - return; - } - AnnotatedWildcardType type1Wildcard = (AnnotatedWildcardType) type1AsLub; - AnnotatedWildcardType type2Wildcard = (AnnotatedWildcardType) type2AsLub; - AnnotatedWildcardType lubWildcard = (AnnotatedWildcardType) lub; - if (type1Wildcard.isTypeArgOfRawType() || type2Wildcard.isTypeArgOfRawType()) { - lubWildcard.setTypeArgOfRawType(); - } - lubWildcard( - type1Wildcard.getSuperBound(), - type1Wildcard.getExtendsBound(), - type2Wildcard.getSuperBound(), - type2Wildcard.getExtendsBound(), - lubWildcard.getSuperBound(), - lubWildcard.getExtendsBound()); - } else if (lub.getKind() == TypeKind.TYPEVAR - && TypesUtils.isCapturedTypeVariable((TypeVariable) lub.getUnderlyingType())) { - if (visited(lub)) { - return; - } - AnnotatedTypeVariable type1typevar = (AnnotatedTypeVariable) type1AsLub; - AnnotatedTypeVariable type2typevar = (AnnotatedTypeVariable) type2AsLub; - AnnotatedTypeVariable lubTypevar = (AnnotatedTypeVariable) lub; - lubWildcard( - type1typevar.getLowerBound(), - type1typevar.getUpperBound(), - type2typevar.getLowerBound(), - type2typevar.getUpperBound(), - lubTypevar.getLowerBound(), - lubTypevar.getUpperBound()); - } else { - // Don't add to visit history because that will happen in visitTypevar_Typevar or - // visitWildcard_Wildcard if needed. - visit(type1AsLub, type2AsLub, lub); + /** + * Annotate the least upper bound of two type arguments. + * + * @param type1 the first type argument + * @param type2 the second type argument + * @param lub the least upper bound + */ + private void lubTypeArgument( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + if ((type1.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type1).isTypeArgOfRawType()) + || (type2.getKind() == TypeKind.WILDCARD + && ((AnnotatedWildcardType) type2).isTypeArgOfRawType())) { + // The asSuper calls below don't seem to retain if a type variable was uninferred. + // There is a similar check in the wildcards branch below, not sure when that is + // actually hit. + // TODO: see whether anything else should be done. See typetools issue 6438 and eisop + // issue 703. + if (lub.getKind() == TypeKind.WILDCARD) { + ((AnnotatedWildcardType) lub).setTypeArgOfRawType(); + } + return; + } + + // In lub(), asSuper is called on type1 and type2, but asSuper does not recur into type + // arguments, so call asSuper on the type arguments so that they have the same underlying + // type. + AnnotatedTypeMirror type1AsLub = AnnotatedTypes.asSuper(atypeFactory, type1, lub); + AnnotatedTypeMirror type2AsLub = AnnotatedTypes.asSuper(atypeFactory, type2, lub); + + // If the type argument is a wildcard or captured type argument, then the lub computation is + // slightly different. The primary annotation on the lower bound is the glb of lower bounds + // of the type types. This is because the lub of Gen<@A ? extends @A Object> and Gen<@B ? + // extends @A Object> is Gen<@B ? extends @A Object>. If visit(type1AsLub, type2AsLub, lub) + // was called instead of the below code, then the lub would be Gen<@A ? extends @A Object>. + // (Note the lub of Gen<@A ? super @A Object> and Gen<@A ? super @B Object> does not exist, + // but Gen<@A ? super @B Object> is returned.) + if (lub.getKind() == TypeKind.WILDCARD) { + if (visited(lub)) { + return; + } + AnnotatedWildcardType type1Wildcard = (AnnotatedWildcardType) type1AsLub; + AnnotatedWildcardType type2Wildcard = (AnnotatedWildcardType) type2AsLub; + AnnotatedWildcardType lubWildcard = (AnnotatedWildcardType) lub; + if (type1Wildcard.isTypeArgOfRawType() || type2Wildcard.isTypeArgOfRawType()) { + lubWildcard.setTypeArgOfRawType(); + } + lubWildcard( + type1Wildcard.getSuperBound(), + type1Wildcard.getExtendsBound(), + type2Wildcard.getSuperBound(), + type2Wildcard.getExtendsBound(), + lubWildcard.getSuperBound(), + lubWildcard.getExtendsBound()); + } else if (lub.getKind() == TypeKind.TYPEVAR + && TypesUtils.isCapturedTypeVariable((TypeVariable) lub.getUnderlyingType())) { + if (visited(lub)) { + return; + } + AnnotatedTypeVariable type1typevar = (AnnotatedTypeVariable) type1AsLub; + AnnotatedTypeVariable type2typevar = (AnnotatedTypeVariable) type2AsLub; + AnnotatedTypeVariable lubTypevar = (AnnotatedTypeVariable) lub; + lubWildcard( + type1typevar.getLowerBound(), + type1typevar.getUpperBound(), + type2typevar.getLowerBound(), + type2typevar.getUpperBound(), + lubTypevar.getLowerBound(), + lubTypevar.getUpperBound()); + } else { + // Don't add to visit history because that will happen in visitTypevar_Typevar or + // visitWildcard_Wildcard if needed. + visit(type1AsLub, type2AsLub, lub); + } } - } - - private void lubWildcard( - AnnotatedTypeMirror type1LowerBound, - AnnotatedTypeMirror type1UpperBound, - AnnotatedTypeMirror type2LowerBound, - AnnotatedTypeMirror type2UpperBound, - AnnotatedTypeMirror lubLowerBound, - AnnotatedTypeMirror lubUpperBound) { - visit(type1UpperBound, type2UpperBound, lubUpperBound); - visit(type1LowerBound, type2LowerBound, lubLowerBound); - - TypeMirror tm1 = type1LowerBound.getUnderlyingType(); - TypeMirror tm2 = type2LowerBound.getUnderlyingType(); - for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { - AnnotationMirror anno1 = type1LowerBound.getAnnotationInHierarchy(top); - AnnotationMirror anno2 = type2LowerBound.getAnnotationInHierarchy(top); - - if (anno1 != null && anno2 != null) { - AnnotationMirror glb = qualHierarchy.greatestLowerBoundShallow(anno1, tm1, anno2, tm2); - lubLowerBound.replaceAnnotation(glb); - } + + private void lubWildcard( + AnnotatedTypeMirror type1LowerBound, + AnnotatedTypeMirror type1UpperBound, + AnnotatedTypeMirror type2LowerBound, + AnnotatedTypeMirror type2UpperBound, + AnnotatedTypeMirror lubLowerBound, + AnnotatedTypeMirror lubUpperBound) { + visit(type1UpperBound, type2UpperBound, lubUpperBound); + visit(type1LowerBound, type2LowerBound, lubLowerBound); + + TypeMirror tm1 = type1LowerBound.getUnderlyingType(); + TypeMirror tm2 = type2LowerBound.getUnderlyingType(); + for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { + AnnotationMirror anno1 = type1LowerBound.getAnnotationInHierarchy(top); + AnnotationMirror anno2 = type2LowerBound.getAnnotationInHierarchy(top); + + if (anno1 != null && anno2 != null) { + AnnotationMirror glb = + qualHierarchy.greatestLowerBoundShallow(anno1, tm1, anno2, tm2); + lubLowerBound.replaceAnnotation(glb); + } + } } - } - - @Override - public Void visitPrimitive_Primitive( - AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, AnnotatedTypeMirror lub) { - // Called to issue warning - castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); - return null; - } - - @Override - public Void visitTypevar_Typevar( - AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, AnnotatedTypeMirror lub1) { - if (visited(lub1)) { - return null; + + @Override + public Void visitPrimitive_Primitive( + AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, AnnotatedTypeMirror lub) { + // Called to issue warning + castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); + return null; } - AnnotatedTypeVariable lub = castLub(type1, lub1); - visit(type1.getUpperBound(), type2.getUpperBound(), lub.getUpperBound()); - visit(type1.getLowerBound(), type2.getLowerBound(), lub.getLowerBound()); + @Override + public Void visitTypevar_Typevar( + AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, AnnotatedTypeMirror lub1) { + if (visited(lub1)) { + return null; + } - lubPrimaryOnBoundedType(type1, type2, lub); + AnnotatedTypeVariable lub = castLub(type1, lub1); + visit(type1.getUpperBound(), type2.getUpperBound(), lub.getUpperBound()); + visit(type1.getLowerBound(), type2.getLowerBound(), lub.getLowerBound()); - return null; - } + lubPrimaryOnBoundedType(type1, type2, lub); - @Override - public Void visitWildcard_Wildcard( - AnnotatedWildcardType type1, AnnotatedWildcardType type2, AnnotatedTypeMirror lub1) { - if (visited(lub1)) { - return null; - } - AnnotatedWildcardType lub = castLub(type1, lub1); - visit(type1.getExtendsBound(), type2.getExtendsBound(), lub.getExtendsBound()); - visit(type1.getSuperBound(), type2.getSuperBound(), lub.getSuperBound()); - lubPrimaryOnBoundedType(type1, type2, lub); - - return null; - } - - private void lubPrimaryOnBoundedType( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - // For each hierarchy, if type1 is not a subtype of type2 and type2 is not a - // subtype of type1, then the primary annotation on lub must be the effective upper - // bound of type1 or type2, whichever is higher. - AnnotationMirrorSet type1LowerBoundAnnos = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type1); - AnnotationMirrorSet type2LowerBoundAnnos = - AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type2); - - TypeMirror typeMirror1 = type1.getUnderlyingType(); - TypeMirror typeMirror2 = type2.getUnderlyingType(); - - for (AnnotationMirror lower1 : type1LowerBoundAnnos) { - AnnotationMirror top = qualHierarchy.getTopAnnotation(lower1); - - // Can't just call isSubtype because it will return false if bounds have - // different annotations on component types - AnnotationMirror lower2 = qualHierarchy.findAnnotationInHierarchy(type2LowerBoundAnnos, top); - AnnotationMirror upper1 = type1.getEffectiveAnnotationInHierarchy(lower1); - AnnotationMirror upper2 = type2.getEffectiveAnnotationInHierarchy(lower1); - - if (qualHierarchy.isSubtypeShallow(upper2, typeMirror2, upper1, typeMirror1) - && qualHierarchy.isSubtypeShallow(upper1, typeMirror1, upper2, typeMirror2) - && qualHierarchy.isSubtypeShallow(lower1, typeMirror1, lower2, typeMirror2) - && qualHierarchy.isSubtypeShallow(lower2, typeMirror2, lower1, typeMirror1)) { - continue; - } - - if (!qualHierarchy.isSubtypeShallow(upper2, typeMirror2, lower1, typeMirror1) - && !qualHierarchy.isSubtypeShallow(upper1, typeMirror1, lower2, typeMirror2)) { - lub.replaceAnnotation( - qualHierarchy.leastUpperBoundShallow(upper1, typeMirror1, upper2, typeMirror2)); - } + return null; } - } - @Override - public Void visitIntersection_Intersection( - AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, AnnotatedTypeMirror lub) { - AnnotatedIntersectionType castedLub = castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); + @Override + public Void visitWildcard_Wildcard( + AnnotatedWildcardType type1, AnnotatedWildcardType type2, AnnotatedTypeMirror lub1) { + if (visited(lub1)) { + return null; + } + AnnotatedWildcardType lub = castLub(type1, lub1); + visit(type1.getExtendsBound(), type2.getExtendsBound(), lub.getExtendsBound()); + visit(type1.getSuperBound(), type2.getSuperBound(), lub.getSuperBound()); + lubPrimaryOnBoundedType(type1, type2, lub); + + return null; + } - for (int i = 0; i < castedLub.getBounds().size(); i++) { - AnnotatedTypeMirror lubST = castedLub.getBounds().get(i); - visit(type1.getBounds().get(i), type2.getBounds().get(i), lubST); + private void lubPrimaryOnBoundedType( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + // For each hierarchy, if type1 is not a subtype of type2 and type2 is not a + // subtype of type1, then the primary annotation on lub must be the effective upper + // bound of type1 or type2, whichever is higher. + AnnotationMirrorSet type1LowerBoundAnnos = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type1); + AnnotationMirrorSet type2LowerBoundAnnos = + AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, type2); + + TypeMirror typeMirror1 = type1.getUnderlyingType(); + TypeMirror typeMirror2 = type2.getUnderlyingType(); + + for (AnnotationMirror lower1 : type1LowerBoundAnnos) { + AnnotationMirror top = qualHierarchy.getTopAnnotation(lower1); + + // Can't just call isSubtype because it will return false if bounds have + // different annotations on component types + AnnotationMirror lower2 = + qualHierarchy.findAnnotationInHierarchy(type2LowerBoundAnnos, top); + AnnotationMirror upper1 = type1.getEffectiveAnnotationInHierarchy(lower1); + AnnotationMirror upper2 = type2.getEffectiveAnnotationInHierarchy(lower1); + + if (qualHierarchy.isSubtypeShallow(upper2, typeMirror2, upper1, typeMirror1) + && qualHierarchy.isSubtypeShallow(upper1, typeMirror1, upper2, typeMirror2) + && qualHierarchy.isSubtypeShallow(lower1, typeMirror1, lower2, typeMirror2) + && qualHierarchy.isSubtypeShallow(lower2, typeMirror2, lower1, typeMirror1)) { + continue; + } + + if (!qualHierarchy.isSubtypeShallow(upper2, typeMirror2, lower1, typeMirror1) + && !qualHierarchy.isSubtypeShallow(upper1, typeMirror1, lower2, typeMirror2)) { + lub.replaceAnnotation( + qualHierarchy.leastUpperBoundShallow( + upper1, typeMirror1, upper2, typeMirror2)); + } + } } - return null; - } + @Override + public Void visitIntersection_Intersection( + AnnotatedIntersectionType type1, + AnnotatedIntersectionType type2, + AnnotatedTypeMirror lub) { + AnnotatedIntersectionType castedLub = castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); + + for (int i = 0; i < castedLub.getBounds().size(); i++) { + AnnotatedTypeMirror lubST = castedLub.getBounds().get(i); + visit(type1.getBounds().get(i), type2.getBounds().get(i), lubST); + } - @Override - public Void visitUnion_Union( - AnnotatedUnionType type1, AnnotatedUnionType type2, AnnotatedTypeMirror lub) { - AnnotatedUnionType castedLub = castLub(type1, lub); - lubPrimaryAnnotations(type1, type2, lub); + return null; + } + + @Override + public Void visitUnion_Union( + AnnotatedUnionType type1, AnnotatedUnionType type2, AnnotatedTypeMirror lub) { + AnnotatedUnionType castedLub = castLub(type1, lub); + lubPrimaryAnnotations(type1, type2, lub); + + for (int i = 0; i < castedLub.getAlternatives().size(); i++) { + AnnotatedDeclaredType lubAltern = castedLub.getAlternatives().get(i); + visit(type1.getAlternatives().get(i), type2.getAlternatives().get(i), lubAltern); + } + return null; + } - for (int i = 0; i < castedLub.getAlternatives().size(); i++) { - AnnotatedDeclaredType lubAltern = castedLub.getAlternatives().get(i); - visit(type1.getAlternatives().get(i), type2.getAlternatives().get(i), lubAltern); + @Override + public String defaultErrorMessage( + AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { + return super.defaultErrorMessage(type1, type2, lub) + + String.format("%n lub: %s %s", lub.getKind(), lub); } - return null; - } - - @Override - public String defaultErrorMessage( - AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotatedTypeMirror lub) { - return super.defaultErrorMessage(type1, type2, lub) - + String.format("%n lub: %s %s", lub.getKind(), lub); - } - - /** - * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is - * added to the list of visited AnnotatedTypeMirrors. This prevents infinite recursion on - * recursive types. - * - * @param atm the type that might have been visited - * @return true if the given type has been visited - */ - private boolean visited(@FindDistinct AnnotatedTypeMirror atm) { - for (AnnotatedTypeMirror atmVisit : visited) { - // Use reference equality rather than equals because the visitor may visit two types - // that are structurally equal, but not actually the same. For example, the - // wildcards in IPair may be equal, but they both should be visited. - if (atmVisit == atm) { - return true; - } + + /** + * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is + * added to the list of visited AnnotatedTypeMirrors. This prevents infinite recursion on + * recursive types. + * + * @param atm the type that might have been visited + * @return true if the given type has been visited + */ + private boolean visited(@FindDistinct AnnotatedTypeMirror atm) { + for (AnnotatedTypeMirror atmVisit : visited) { + // Use reference equality rather than equals because the visitor may visit two types + // that are structurally equal, but not actually the same. For example, the + // wildcards in IPair may be equal, but they both should be visited. + if (atmVisit == atm) { + return true; + } + } + visited.add(atm); + return false; } - visited.add(atm); - return false; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java index e76c4323a55..342c62e80fe 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java +++ b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java @@ -1,5 +1,14 @@ package org.checkerframework.framework.util; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; +import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.SystemUtil; +import org.checkerframework.javacutil.UserError; +import org.plumelib.util.CollectionsPlume; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -23,14 +32,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.PolyNull; -import org.checkerframework.checker.regex.qual.Regex; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.SystemUtil; -import org.checkerframework.javacutil.UserError; -import org.plumelib.util.CollectionsPlume; /** * This class behaves similarly to javac. CheckerMain does the following: @@ -58,917 +59,939 @@ */ public class CheckerMain { - /** - * Any exception thrown by the Checker Framework escapes to the command line. - * - * @param args command-line arguments - */ - public static void main(String[] args) { - File pathToThisJar = new File(findPathTo(CheckerMain.class, false)); - ArrayList alargs = new ArrayList<>(Arrays.asList(args)); - CheckerMain program = new CheckerMain(pathToThisJar, alargs); - int exitStatus = program.invokeCompiler(); - System.exit(exitStatus); - } + /** + * Any exception thrown by the Checker Framework escapes to the command line. + * + * @param args command-line arguments + */ + public static void main(String[] args) { + File pathToThisJar = new File(findPathTo(CheckerMain.class, false)); + ArrayList alargs = new ArrayList<>(Arrays.asList(args)); + CheckerMain program = new CheckerMain(pathToThisJar, alargs); + int exitStatus = program.invokeCompiler(); + System.exit(exitStatus); + } + + /** The path to the javacJar to use. */ + protected final File javacJar; + + /** The path to the jar containing CheckerMain.class (i.e. checker.jar). */ + protected final File checkerJar; + + /** The path to checker-qual.jar. */ + protected final File checkerQualJar; + + /** The path to checker-util.jar. */ + protected final File checkerUtilJar; + + /** Compilation bootclasspath. */ + private final List compilationBootclasspath; + + private final List runtimeClasspath; + + private final List jvmOpts; + + /** + * Each element is either a classpath element (a directory or jar file) or is a classpath + * (containing elements separated by File.pathSeparator). To produce the final classpath, + * concatenate them all (separated by File.pathSeparator). + */ + private final List cpOpts; + + /** Processor path options. */ + private final List ppOpts; + + /** Arguments to the Checker Framework. */ + private final List toolOpts; + + /** Command-line argument files (specified with @ on the command line). */ + private final List argListFiles; + + /** + * Option name for specifying an alternative checker-qual.jar location. The accompanying value + * MUST be the path to the jar file (NOT the path to its encompassing directory) + */ + public static final String CHECKER_QUAL_PATH_OPT = "-checkerQualJar"; + + /** + * Option name for specifying an alternative checker-util.jar location. The accompanying value + * MUST be the path to the jar file (NOT the path to its encompassing directory) + */ + public static final String CHECKER_UTIL_PATH_OPT = "-checkerUtilJar"; + + /** + * Option name for specifying an alternative javac.jar location. The accompanying value MUST be + * the path to the jar file (NOT the path to its encompassing directory) + */ + public static final String JAVAC_PATH_OPT = "-javacJar"; + + /** + * Option name for specifying an alternative jdk.jar location. The accompanying value MUST be + * the path to the jar file (NOT the path to its encompassing directory) + */ + public static final String JDK_PATH_OPT = "-jdkJar"; + + /** + * Construct all the relevant file locations and Java version given the path to this jar and a + * set of directories in which to search for jars. + */ + public CheckerMain(File checkerJar, List args) { + + this.checkerJar = checkerJar; + File searchPath = checkerJar.getParentFile(); + + replaceShorthandProcessor(args); + argListFiles = collectArgFiles(args); + + this.checkerQualJar = + extractFileArg( + CHECKER_QUAL_PATH_OPT, new File(searchPath, "checker-qual.jar"), args); + + this.checkerUtilJar = + extractFileArg( + CHECKER_UTIL_PATH_OPT, new File(searchPath, "checker-util.jar"), args); + + this.javacJar = extractFileArg(JAVAC_PATH_OPT, new File(searchPath, "javac.jar"), args); + + this.compilationBootclasspath = createCompilationBootclasspath(args); + this.runtimeClasspath = createRuntimeClasspath(args); + this.jvmOpts = extractJvmOpts(args); + + this.cpOpts = createCpOpts(args); + this.ppOpts = createPpOpts(args); + this.toolOpts = args; + + assertValidState(); + } + + /** Assert that required jars exist. */ + protected void assertValidState() { + if (SystemUtil.jreVersion == 8) { + assertFilesExist(javacJar, checkerJar, checkerQualJar, checkerUtilJar); + } else { + assertFilesExist(checkerJar, checkerQualJar, checkerUtilJar); + } + } + + public void addToClasspath(List cpOpts) { + this.cpOpts.addAll(cpOpts); + } + + public void addToProcessorpath(List ppOpts) { + this.ppOpts.addAll(ppOpts); + } + + public void addToRuntimeClasspath(List runtimeClasspathOpts) { + this.runtimeClasspath.addAll(runtimeClasspathOpts); + } + + protected List createRuntimeClasspath(List argsList) { + return new ArrayList<>(Arrays.asList(javacJar.getAbsolutePath())); + } + + /** + * Returns the compilation bootclasspath from {@code argsList}. + * + * @param argsList args to add + * @return the compilation bootclasspath from {@code argsList} + */ + protected List createCompilationBootclasspath(List argsList) { + return extractBootClassPath(argsList); + } + + protected List createCpOpts(List argsList) { + List extractedOpts = extractCpOpts(argsList); + extractedOpts.add(0, this.checkerQualJar.getAbsolutePath()); + extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); + + return extractedOpts; + } + + /** + * Returns processor path options. + * + *

          This method assumes that createCpOpts has already been run. + * + * @param argsList arguments + * @return processor path options + */ + protected List createPpOpts(List argsList) { + List extractedOpts = new ArrayList<>(extractPpOpts(argsList)); + if (extractedOpts.isEmpty()) { + // If processorpath is not provided, then javac uses the classpath. + // CheckerMain always supplies a processorpath, so if the user + // didn't specify a processorpath, then use the classpath. + extractedOpts.addAll(this.cpOpts); + } + extractedOpts.add(0, this.checkerJar.getAbsolutePath()); + extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); + + return extractedOpts; + } + + /** + * Return the arguments that start with @ and therefore are files that contain javac arguments. + * + * @param args a list of command-line arguments; is not modified + * @return a List of files representing all arguments that started with @ + */ + protected List collectArgFiles(List args) { + List argListFiles = new ArrayList<>(); + for (String arg : args) { + if (arg.startsWith("@")) { + argListFiles.add(new File(arg.substring(1))); + } + } + + return argListFiles; + } + + /** + * Remove the argument given by argumentName and the subsequent value from the list args if + * present. Return the subsequent value. + * + * @param argumentName a command-line option name whose argument to extract + * @param alternative default value to return if argumentName does not appear in args + * @param args the current list of arguments + * @return the string that follows argumentName if argumentName is in args, or alternative if + * argumentName is not present in args + */ + protected static @PolyNull String extractArg( + String argumentName, @PolyNull String alternative, List args) { + int i = args.indexOf(argumentName); + if (i == -1) { + return alternative; + } else if (i == args.size() - 1) { + throw new BugInCF( + "Command line contains " + argumentName + " but no value following it"); + } else { + args.remove(i); + return args.remove(i); + } + } + + /** + * Remove the argument given by argumentName and the subsequent value from the list args if + * present. Return the subsequent value wrapped as a File. + * + * @param argumentName argument to extract + * @param alternative file to return if argumentName is not found in args + * @param args the current list of arguments + * @return the string that follows argumentName wrapped as a File if argumentName is in args or + * alternative if argumentName is not present in args + */ + protected static File extractFileArg(String argumentName, File alternative, List args) { + String filePath = extractArg(argumentName, null, args); + if (filePath == null) { + return alternative; + } else { + return new File(filePath); + } + } + + /** + * Find all args that match the given pattern and extract their index 1 group. Add all the index + * 1 groups to the returned list. Remove all matching args from the input args list. + * + * @param pattern a pattern with at least one matching group + * @param allowEmpties whether or not to add empty group(1) matches to the returned list + * @param args the arguments to extract from + * @return a list of arguments from the first group that matched the pattern for each input args + * or the empty list if there were none + */ + protected static List extractOptWithPattern( + @Regex(1) Pattern pattern, boolean allowEmpties, List args) { + List matchedArgs = new ArrayList<>(); + + int i = 0; + while (i < args.size()) { + Matcher matcher = pattern.matcher(args.get(i)); + if (matcher.matches()) { + String group1 = matcher.group(1); + if (group1 == null) { + throw new BugInCF("Regex didn't capture group 1: " + pattern); + } + String arg = group1.trim(); + + if (!arg.isEmpty() || allowEmpties) { + matchedArgs.add(arg); + } + + args.remove(i); + } else { + i++; + } + } + + return matchedArgs; + } + + /** + * A pattern to match bootclasspath prepend entries, used to construct one {@code + * -Xbootclasspath/p:} command-line argument. + */ + protected static final Pattern BOOT_CLASS_PATH_REGEX = + Pattern.compile("^(?:-J)?-Xbootclasspath/p:(.*)$"); + + // TODO: Why does this treat -J and -J-X the same? They have different semantics, don't they? + /** + * Remove all {@code -Xbootclasspath/p:} or {@code -J-Xbootclasspath/p:} arguments from args and + * add them to the returned list. + * + * @param args the arguments to extract from + * @return all non-empty arguments matching BOOT_CLASS_PATH_REGEX or an empty list if there were + * none + */ + protected static List extractBootClassPath(List args) { + return extractOptWithPattern(BOOT_CLASS_PATH_REGEX, false, args); + } + + /** Matches all {@code -J} arguments. */ + protected static final Pattern JVM_OPTS_REGEX = Pattern.compile("^(?:-J)(.*)$"); + + /** + * Remove all {@code -J} arguments from {@code args} and add them to the returned list (without + * the {@code -J} prefix). + * + * @param args the arguments to extract from + * @return all {@code -J} arguments (without the {@code -J} prefix) or an empty list if there + * were none + */ + protected static List extractJvmOpts(List args) { + return extractOptWithPattern(JVM_OPTS_REGEX, false, args); + } + + /** + * Return the last {@code -cp} or {@code -classpath} option. If no {@code -cp} or {@code + * -classpath} arguments were present, then return the CLASSPATH environment variable (if set) + * followed by the current directory. + * + *

          Also removes all {@code -cp} and {@code -classpath} options from args. + * + * @param args a list of arguments to extract from; is side-effected by this + * @return collection of classpaths to concatenate to use when calling javac.jar + */ + protected static List extractCpOpts(List args) { + List actualArgs = new ArrayList<>(); + + String lastCpArg = null; + + for (int i = 0; i < args.size(); i++) { + if ((args.get(i).equals("-cp") || args.get(i).equals("-classpath")) + && (i + 1 < args.size())) { + args.remove(i); + // Every classpath entry overrides the one before it. + lastCpArg = args.remove(i); + // re-process whatever is currently at element i + i--; + } + } + + // The logic below is exactly what the javac script does. If no command-line classpath is + // specified, use the "CLASSPATH" environment variable followed by the current directory. + if (lastCpArg == null) { + String systemClassPath = System.getenv("CLASSPATH"); + if (systemClassPath != null && !systemClassPath.trim().isEmpty()) { + actualArgs.add(systemClassPath.trim()); + } + + actualArgs.add("."); + } else { + actualArgs.add(lastCpArg); + } + + return actualArgs; + } + + /** + * Remove the {@code -processorpath} options and their arguments from args. Return the last + * argument. + * + * @param args a list of arguments to extract from + * @return the arguments that should be put on the processorpath when calling javac.jar + */ + protected static List extractPpOpts(List args) { + String path = null; + + for (int i = 0; i < args.size(); i++) { + if (args.get(i).equals("-processorpath") && (i + 1 < args.size())) { + args.remove(i); + path = args.remove(i); + // re-process whatever is currently at element i + i--; + } + } + + if (path != null) { + return Collections.singletonList(path); + } else { + return Collections.emptyList(); + } + } + + protected void addMainToArgs(List args) { + args.add("com.sun.tools.javac.Main"); + } + + /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ + public List getExecArguments() { + List args = new ArrayList<>(jvmOpts.size() + cpOpts.size() + toolOpts.size() + 7); + + // TODO: do we need java.exe on Windows? + String java = "java"; + args.add(java); + + if (SystemUtil.jreVersion == 8) { + args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, runtimeClasspath)); + } else { + // See comments in build.gradle + args.addAll( + // Keep this list in sync with the lists in checker-framework/build.gradle in + // compilerArgsForRunningCFs, the sections with labels + // "javac-jdk11-non-modularized", "maven", and "sbt" in the manual, and in the + // checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject + Arrays.asList( + // These are required in Java 17+ because the --illegal-access option is + // set to deny by default. None of these packages are accessed via + // reflection, so the module only needs to be exported, but not opened. + "--add-exports", + "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports", + "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + // Required because the Checker Framework reflectively accesses private + // members in com.sun.tools.javac.comp. + "--add-opens", + "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED")); + } + + args.add("-classpath"); + args.add(String.join(File.pathSeparator, runtimeClasspath)); + args.add("-ea"); + // com.sun.tools needs to be enabled separately + args.add("-ea:com.sun.tools..."); + + args.addAll(jvmOpts); + + addMainToArgs(args); - /** The path to the javacJar to use. */ - protected final File javacJar; + if (!argsListHasClassPath(argListFiles)) { + args.add("-classpath"); + args.add(quote(concatenatePaths(cpOpts))); + } + if (!argsListHasProcessorPath(argListFiles)) { + args.add("-processorpath"); + args.add(quote(concatenatePaths(ppOpts))); + } - /** The path to the jar containing CheckerMain.class (i.e. checker.jar). */ - protected final File checkerJar; - - /** The path to checker-qual.jar. */ - protected final File checkerQualJar; + if (SystemUtil.jreVersion == 8) { + // No classes on the compilation bootclasspath will be loaded + // during compilation, but the classes are read by the compiler + // without loading them. The compiler assumes that any class on + // this bootclasspath will be on the bootclasspath of the JVM used + // to later run the classfiles that Javac produces. + args.add( + "-Xbootclasspath/p:" + + String.join(File.pathSeparator, compilationBootclasspath)); + } - /** The path to checker-util.jar. */ - protected final File checkerUtilJar; + args.addAll(toolOpts); + return args; + } - /** Compilation bootclasspath. */ - private final List compilationBootclasspath; + /** Given a list of paths, concatenate them to form a single path. Also expand wildcards. */ + private String concatenatePaths(List paths) { + List elements = new ArrayList<>(); + for (String path : paths) { + for (String element : SystemUtil.PATH_SEPARATOR_SPLITTER.split(path)) { + elements.addAll(expandWildcards(element)); + } + } + return String.join(File.pathSeparator, elements); + } + + /** The string "/*" (on Unix). */ + private static final String FILESEP_STAR = File.separator + "*"; + + /** + * Given a path element that might be a wildcard, return a list of the elements it expands to. + * If the element isn't a wildcard, return a singleton list containing the argument. Since the + * original argument list is placed after 'com.sun.tools.javac.Main' in the new command line, + * the JVM doesn't do wildcard expansion of jar files in any classpaths in the original argument + * list. + * + * @param pathElement an element of a classpath + * @return all elements of a classpath with wildcards expanded + */ + private List expandWildcards(String pathElement) { + if (pathElement.equals("*")) { + return jarFiles("."); + } else if (pathElement.endsWith(FILESEP_STAR)) { + return jarFiles(pathElement.substring(0, pathElement.length() - 1)); + } else if (pathElement.isEmpty()) { + return Collections.emptyList(); + } else { + return Collections.singletonList(pathElement); + } + } - private final List runtimeClasspath; + /** + * Returns all the .jar and .JAR files in the given directory. + * + * @param directory a directory + * @return all the .jar and .JAR files in the given directory + */ + private List jarFiles(String directory) { + File dir = new File(directory); + String[] jarFiles = dir.list((d, name) -> name.endsWith(".jar") || name.endsWith(".JAR")); + if (jarFiles == null) { + return Collections.emptyList(); + } + // concat directory with jar file path to give full path + for (int i = 0; i < jarFiles.length; i++) { + jarFiles[i] = directory + jarFiles[i]; + } + return Arrays.asList(jarFiles); + } + + /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ + public int invokeCompiler() { + List args = getExecArguments(); - private final List jvmOpts; - - /** - * Each element is either a classpath element (a directory or jar file) or is a classpath - * (containing elements separated by File.pathSeparator). To produce the final classpath, - * concatenate them all (separated by File.pathSeparator). - */ - private final List cpOpts; - - /** Processor path options. */ - private final List ppOpts; - - /** Arguments to the Checker Framework. */ - private final List toolOpts; - - /** Command-line argument files (specified with @ on the command line). */ - private final List argListFiles; - - /** - * Option name for specifying an alternative checker-qual.jar location. The accompanying value - * MUST be the path to the jar file (NOT the path to its encompassing directory) - */ - public static final String CHECKER_QUAL_PATH_OPT = "-checkerQualJar"; - - /** - * Option name for specifying an alternative checker-util.jar location. The accompanying value - * MUST be the path to the jar file (NOT the path to its encompassing directory) - */ - public static final String CHECKER_UTIL_PATH_OPT = "-checkerUtilJar"; - - /** - * Option name for specifying an alternative javac.jar location. The accompanying value MUST be - * the path to the jar file (NOT the path to its encompassing directory) - */ - public static final String JAVAC_PATH_OPT = "-javacJar"; - - /** - * Option name for specifying an alternative jdk.jar location. The accompanying value MUST be the - * path to the jar file (NOT the path to its encompassing directory) - */ - public static final String JDK_PATH_OPT = "-jdkJar"; - - /** - * Construct all the relevant file locations and Java version given the path to this jar and a set - * of directories in which to search for jars. - */ - public CheckerMain(File checkerJar, List args) { - - this.checkerJar = checkerJar; - File searchPath = checkerJar.getParentFile(); - - replaceShorthandProcessor(args); - argListFiles = collectArgFiles(args); - - this.checkerQualJar = - extractFileArg(CHECKER_QUAL_PATH_OPT, new File(searchPath, "checker-qual.jar"), args); - - this.checkerUtilJar = - extractFileArg(CHECKER_UTIL_PATH_OPT, new File(searchPath, "checker-util.jar"), args); - - this.javacJar = extractFileArg(JAVAC_PATH_OPT, new File(searchPath, "javac.jar"), args); - - this.compilationBootclasspath = createCompilationBootclasspath(args); - this.runtimeClasspath = createRuntimeClasspath(args); - this.jvmOpts = extractJvmOpts(args); - - this.cpOpts = createCpOpts(args); - this.ppOpts = createPpOpts(args); - this.toolOpts = args; - - assertValidState(); - } - - /** Assert that required jars exist. */ - protected void assertValidState() { - if (SystemUtil.jreVersion == 8) { - assertFilesExist(javacJar, checkerJar, checkerQualJar, checkerUtilJar); - } else { - assertFilesExist(checkerJar, checkerQualJar, checkerUtilJar); - } - } - - public void addToClasspath(List cpOpts) { - this.cpOpts.addAll(cpOpts); - } - - public void addToProcessorpath(List ppOpts) { - this.ppOpts.addAll(ppOpts); - } - - public void addToRuntimeClasspath(List runtimeClasspathOpts) { - this.runtimeClasspath.addAll(runtimeClasspathOpts); - } - - protected List createRuntimeClasspath(List argsList) { - return new ArrayList<>(Arrays.asList(javacJar.getAbsolutePath())); - } - - /** - * Returns the compilation bootclasspath from {@code argsList}. - * - * @param argsList args to add - * @return the compilation bootclasspath from {@code argsList} - */ - protected List createCompilationBootclasspath(List argsList) { - return extractBootClassPath(argsList); - } - - protected List createCpOpts(List argsList) { - List extractedOpts = extractCpOpts(argsList); - extractedOpts.add(0, this.checkerQualJar.getAbsolutePath()); - extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); - - return extractedOpts; - } - - /** - * Returns processor path options. - * - *

          This method assumes that createCpOpts has already been run. - * - * @param argsList arguments - * @return processor path options - */ - protected List createPpOpts(List argsList) { - List extractedOpts = new ArrayList<>(extractPpOpts(argsList)); - if (extractedOpts.isEmpty()) { - // If processorpath is not provided, then javac uses the classpath. - // CheckerMain always supplies a processorpath, so if the user - // didn't specify a processorpath, then use the classpath. - extractedOpts.addAll(this.cpOpts); - } - extractedOpts.add(0, this.checkerJar.getAbsolutePath()); - extractedOpts.add(0, this.checkerUtilJar.getAbsolutePath()); - - return extractedOpts; - } - - /** - * Return the arguments that start with @ and therefore are files that contain javac arguments. - * - * @param args a list of command-line arguments; is not modified - * @return a List of files representing all arguments that started with @ - */ - protected List collectArgFiles(List args) { - List argListFiles = new ArrayList<>(); - for (String arg : args) { - if (arg.startsWith("@")) { - argListFiles.add(new File(arg.substring(1))); - } - } - - return argListFiles; - } - - /** - * Remove the argument given by argumentName and the subsequent value from the list args if - * present. Return the subsequent value. - * - * @param argumentName a command-line option name whose argument to extract - * @param alternative default value to return if argumentName does not appear in args - * @param args the current list of arguments - * @return the string that follows argumentName if argumentName is in args, or alternative if - * argumentName is not present in args - */ - protected static @PolyNull String extractArg( - String argumentName, @PolyNull String alternative, List args) { - int i = args.indexOf(argumentName); - if (i == -1) { - return alternative; - } else if (i == args.size() - 1) { - throw new BugInCF("Command line contains " + argumentName + " but no value following it"); - } else { - args.remove(i); - return args.remove(i); - } - } - - /** - * Remove the argument given by argumentName and the subsequent value from the list args if - * present. Return the subsequent value wrapped as a File. - * - * @param argumentName argument to extract - * @param alternative file to return if argumentName is not found in args - * @param args the current list of arguments - * @return the string that follows argumentName wrapped as a File if argumentName is in args or - * alternative if argumentName is not present in args - */ - protected static File extractFileArg(String argumentName, File alternative, List args) { - String filePath = extractArg(argumentName, null, args); - if (filePath == null) { - return alternative; - } else { - return new File(filePath); - } - } - - /** - * Find all args that match the given pattern and extract their index 1 group. Add all the index 1 - * groups to the returned list. Remove all matching args from the input args list. - * - * @param pattern a pattern with at least one matching group - * @param allowEmpties whether or not to add empty group(1) matches to the returned list - * @param args the arguments to extract from - * @return a list of arguments from the first group that matched the pattern for each input args - * or the empty list if there were none - */ - protected static List extractOptWithPattern( - @Regex(1) Pattern pattern, boolean allowEmpties, List args) { - List matchedArgs = new ArrayList<>(); - - int i = 0; - while (i < args.size()) { - Matcher matcher = pattern.matcher(args.get(i)); - if (matcher.matches()) { - String group1 = matcher.group(1); - if (group1 == null) { - throw new BugInCF("Regex didn't capture group 1: " + pattern); - } - String arg = group1.trim(); - - if (!arg.isEmpty() || allowEmpties) { - matchedArgs.add(arg); - } - - args.remove(i); - } else { - i++; - } - } - - return matchedArgs; - } - - /** - * A pattern to match bootclasspath prepend entries, used to construct one {@code - * -Xbootclasspath/p:} command-line argument. - */ - protected static final Pattern BOOT_CLASS_PATH_REGEX = - Pattern.compile("^(?:-J)?-Xbootclasspath/p:(.*)$"); - - // TODO: Why does this treat -J and -J-X the same? They have different semantics, don't they? - /** - * Remove all {@code -Xbootclasspath/p:} or {@code -J-Xbootclasspath/p:} arguments from args and - * add them to the returned list. - * - * @param args the arguments to extract from - * @return all non-empty arguments matching BOOT_CLASS_PATH_REGEX or an empty list if there were - * none - */ - protected static List extractBootClassPath(List args) { - return extractOptWithPattern(BOOT_CLASS_PATH_REGEX, false, args); - } - - /** Matches all {@code -J} arguments. */ - protected static final Pattern JVM_OPTS_REGEX = Pattern.compile("^(?:-J)(.*)$"); - - /** - * Remove all {@code -J} arguments from {@code args} and add them to the returned list (without - * the {@code -J} prefix). - * - * @param args the arguments to extract from - * @return all {@code -J} arguments (without the {@code -J} prefix) or an empty list if there were - * none - */ - protected static List extractJvmOpts(List args) { - return extractOptWithPattern(JVM_OPTS_REGEX, false, args); - } - - /** - * Return the last {@code -cp} or {@code -classpath} option. If no {@code -cp} or {@code - * -classpath} arguments were present, then return the CLASSPATH environment variable (if set) - * followed by the current directory. - * - *

          Also removes all {@code -cp} and {@code -classpath} options from args. - * - * @param args a list of arguments to extract from; is side-effected by this - * @return collection of classpaths to concatenate to use when calling javac.jar - */ - protected static List extractCpOpts(List args) { - List actualArgs = new ArrayList<>(); - - String lastCpArg = null; - - for (int i = 0; i < args.size(); i++) { - if ((args.get(i).equals("-cp") || args.get(i).equals("-classpath")) - && (i + 1 < args.size())) { - args.remove(i); - // Every classpath entry overrides the one before it. - lastCpArg = args.remove(i); - // re-process whatever is currently at element i - i--; - } - } - - // The logic below is exactly what the javac script does. If no command-line classpath is - // specified, use the "CLASSPATH" environment variable followed by the current directory. - if (lastCpArg == null) { - String systemClassPath = System.getenv("CLASSPATH"); - if (systemClassPath != null && !systemClassPath.trim().isEmpty()) { - actualArgs.add(systemClassPath.trim()); - } - - actualArgs.add("."); - } else { - actualArgs.add(lastCpArg); - } - - return actualArgs; - } - - /** - * Remove the {@code -processorpath} options and their arguments from args. Return the last - * argument. - * - * @param args a list of arguments to extract from - * @return the arguments that should be put on the processorpath when calling javac.jar - */ - protected static List extractPpOpts(List args) { - String path = null; - - for (int i = 0; i < args.size(); i++) { - if (args.get(i).equals("-processorpath") && (i + 1 < args.size())) { - args.remove(i); - path = args.remove(i); - // re-process whatever is currently at element i - i--; - } - } - - if (path != null) { - return Collections.singletonList(path); - } else { - return Collections.emptyList(); - } - } - - protected void addMainToArgs(List args) { - args.add("com.sun.tools.javac.Main"); - } - - /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ - public List getExecArguments() { - List args = new ArrayList<>(jvmOpts.size() + cpOpts.size() + toolOpts.size() + 7); - - // TODO: do we need java.exe on Windows? - String java = "java"; - args.add(java); - - if (SystemUtil.jreVersion == 8) { - args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, runtimeClasspath)); - } else { - // See comments in build.gradle - args.addAll( - // Keep this list in sync with the lists in checker-framework/build.gradle in - // compilerArgsForRunningCFs, the sections with labels - // "javac-jdk11-non-modularized", "maven", and "sbt" in the manual, and in the - // checker-framework-gradle-plugin, CheckerFrameworkPlugin#applyToProject - Arrays.asList( - // These are required in Java 17+ because the --illegal-access option is - // set to deny by default. None of these packages are accessed via - // reflection, so the module only needs to be exported, but not opened. - "--add-exports", - "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports", - "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - // Required because the Checker Framework reflectively accesses private - // members in com.sun.tools.javac.comp. - "--add-opens", - "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED")); - } - - args.add("-classpath"); - args.add(String.join(File.pathSeparator, runtimeClasspath)); - args.add("-ea"); - // com.sun.tools needs to be enabled separately - args.add("-ea:com.sun.tools..."); - - args.addAll(jvmOpts); - - addMainToArgs(args); - - if (!argsListHasClassPath(argListFiles)) { - args.add("-classpath"); - args.add(quote(concatenatePaths(cpOpts))); - } - if (!argsListHasProcessorPath(argListFiles)) { - args.add("-processorpath"); - args.add(quote(concatenatePaths(ppOpts))); - } - - if (SystemUtil.jreVersion == 8) { - // No classes on the compilation bootclasspath will be loaded - // during compilation, but the classes are read by the compiler - // without loading them. The compiler assumes that any class on - // this bootclasspath will be on the bootclasspath of the JVM used - // to later run the classfiles that Javac produces. - args.add("-Xbootclasspath/p:" + String.join(File.pathSeparator, compilationBootclasspath)); - } - - args.addAll(toolOpts); - return args; - } - - /** Given a list of paths, concatenate them to form a single path. Also expand wildcards. */ - private String concatenatePaths(List paths) { - List elements = new ArrayList<>(); - for (String path : paths) { - for (String element : SystemUtil.PATH_SEPARATOR_SPLITTER.split(path)) { - elements.addAll(expandWildcards(element)); - } - } - return String.join(File.pathSeparator, elements); - } - - /** The string "/*" (on Unix). */ - private static final String FILESEP_STAR = File.separator + "*"; - - /** - * Given a path element that might be a wildcard, return a list of the elements it expands to. If - * the element isn't a wildcard, return a singleton list containing the argument. Since the - * original argument list is placed after 'com.sun.tools.javac.Main' in the new command line, the - * JVM doesn't do wildcard expansion of jar files in any classpaths in the original argument list. - * - * @param pathElement an element of a classpath - * @return all elements of a classpath with wildcards expanded - */ - private List expandWildcards(String pathElement) { - if (pathElement.equals("*")) { - return jarFiles("."); - } else if (pathElement.endsWith(FILESEP_STAR)) { - return jarFiles(pathElement.substring(0, pathElement.length() - 1)); - } else if (pathElement.isEmpty()) { - return Collections.emptyList(); - } else { - return Collections.singletonList(pathElement); - } - } - - /** - * Returns all the .jar and .JAR files in the given directory. - * - * @param directory a directory - * @return all the .jar and .JAR files in the given directory - */ - private List jarFiles(String directory) { - File dir = new File(directory); - String[] jarFiles = dir.list((d, name) -> name.endsWith(".jar") || name.endsWith(".JAR")); - if (jarFiles == null) { - return Collections.emptyList(); - } - // concat directory with jar file path to give full path - for (int i = 0; i < jarFiles.length; i++) { - jarFiles[i] = directory + jarFiles[i]; - } - return Arrays.asList(jarFiles); - } - - /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ - public int invokeCompiler() { - List args = getExecArguments(); - - for (int i = 0; i < args.size(); i++) { - String arg = args.get(i); - - if (arg.startsWith("-AoutputArgsToFile=")) { - String fileName = arg.substring(19); - args.remove(i); - outputArgumentsToFile(fileName, args); - break; - } - } - - // Actually invoke the compiler - return ExecUtil.execute(args.toArray(new String[0]), System.out, System.err); - } - - private static void outputArgumentsToFile(String outputFilename, List args) { - if (outputFilename != null) { - String errorMessage = null; - - try { - @SuppressWarnings("builder:required.method.not.called") // called when needed - PrintWriter writer = - (outputFilename.equals("-") - ? new PrintWriter( - new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8))) - : new PrintWriter(outputFilename, "UTF-8")); for (int i = 0; i < args.size(); i++) { - String arg = args.get(i); - - // We would like to include the filename of the argfile instead of its contents. - // The problem is that the file will sometimes disappear by the time the user - // can look at or run the resulting script. Maven deletes the argfile very - // shortly after it has been handed off to javac, for example. Ideally we would - // print the argfile filename as a comment but the resulting file couldn't then - // be run as a script on Unix or Windows. - if (arg.startsWith("@")) { - // Read argfile and include its parameters in the output file. - String inputFilename = arg.substring(1); - - try (BufferedReader br = - Files.newBufferedReader(Paths.get(inputFilename), StandardCharsets.UTF_8)) { - String line; - while ((line = br.readLine()) != null) { - writer.print(line); - writer.print(" "); - } + String arg = args.get(i); + + if (arg.startsWith("-AoutputArgsToFile=")) { + String fileName = arg.substring(19); + args.remove(i); + outputArgumentsToFile(fileName, args); + break; + } + } + + // Actually invoke the compiler + return ExecUtil.execute(args.toArray(new String[0]), System.out, System.err); + } + + private static void outputArgumentsToFile(String outputFilename, List args) { + if (outputFilename != null) { + String errorMessage = null; + + try { + @SuppressWarnings("builder:required.method.not.called") // called when needed + PrintWriter writer = + (outputFilename.equals("-") + ? new PrintWriter( + new BufferedWriter( + new OutputStreamWriter( + System.out, StandardCharsets.UTF_8))) + : new PrintWriter(outputFilename, "UTF-8")); + for (int i = 0; i < args.size(); i++) { + String arg = args.get(i); + + // We would like to include the filename of the argfile instead of its contents. + // The problem is that the file will sometimes disappear by the time the user + // can look at or run the resulting script. Maven deletes the argfile very + // shortly after it has been handed off to javac, for example. Ideally we would + // print the argfile filename as a comment but the resulting file couldn't then + // be run as a script on Unix or Windows. + if (arg.startsWith("@")) { + // Read argfile and include its parameters in the output file. + String inputFilename = arg.substring(1); + + try (BufferedReader br = + Files.newBufferedReader( + Paths.get(inputFilename), StandardCharsets.UTF_8)) { + String line; + while ((line = br.readLine()) != null) { + writer.print(line); + writer.print(" "); + } + } + } else { + writer.print(arg); + writer.print(" "); + } + writer.flush(); + } + if (!outputFilename.equals("-")) { + writer.close(); + } + } catch (IOException e) { + errorMessage = e.toString(); + } + + if (errorMessage != null) { + System.err.println( + "Failed to output command-line arguments to file " + + outputFilename + + " due to exception: " + + errorMessage); + } + } + } + + /** + * Returns true if some @arglist file sets the classpath. + * + * @param argListFiles command-line argument files (specified with @ on the command line) + */ + private static boolean argsListHasClassPath(List argListFiles) { + for (String arg : expandArgFiles(argListFiles)) { + if (arg.contains("-classpath") || arg.contains("-cp")) { + return true; } - } else { - writer.print(arg); - writer.print(" "); - } - writer.flush(); - } - if (!outputFilename.equals("-")) { - writer.close(); - } - } catch (IOException e) { - errorMessage = e.toString(); - } - - if (errorMessage != null) { - System.err.println( - "Failed to output command-line arguments to file " - + outputFilename - + " due to exception: " - + errorMessage); - } - } - } - - /** - * Returns true if some @arglist file sets the classpath. - * - * @param argListFiles command-line argument files (specified with @ on the command line) - */ - private static boolean argsListHasClassPath(List argListFiles) { - for (String arg : expandArgFiles(argListFiles)) { - if (arg.contains("-classpath") || arg.contains("-cp")) { - return true; - } - } - - return false; - } - - /** - * Returns true if some @arglist file sets the processorpath. - * - * @param argListFiles command-line argument files (specified with @ on the command line) - */ - private static boolean argsListHasProcessorPath(List argListFiles) { - for (String arg : expandArgFiles(argListFiles)) { - if (arg.contains("-processorpath")) { - return true; - } - } - - return false; - } - - /** - * Return all the lines in all the files. - * - * @param files a list of files - * @return a list of all the lines in all the files - */ - protected static List expandArgFiles(List files) { - List content = new ArrayList<>(); - for (File file : files) { - try { - content.addAll(Files.readAllLines(file.toPath())); - } catch (IOException exc) { - throw new RuntimeException("Could not open file: " + file.getAbsolutePath(), exc); - } - } - return content; - } - - /** - * Find the jar file or directory containing the .class file from which cls was loaded. - * - * @param cls the class whose .class file we wish to locate; if null, CheckerMain.class - * @param errIfFromDirectory if false, throw an exception if the file was loaded from a directory - */ - public static String findPathTo(@Nullable Class cls, boolean errIfFromDirectory) - throws IllegalStateException { - if (cls == null) { - cls = CheckerMain.class; - } - String name = cls.getName(); - String classFileName; - /* name is something like pakkage.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ - { - int idx = name.lastIndexOf('.'); - classFileName = (idx == -1 ? name : name.substring(idx + 1)) + ".class"; - } - - URL classFileUrl = cls.getResource(classFileName); - if (classFileUrl == null) { - throw new BugInCF("Cannot find resource " + classFileName); - } - if (classFileUrl.getProtocol().equals("file")) { - if (errIfFromDirectory) { - return classFileUrl.toString(); - } else { - throw new IllegalStateException( - "This class has been loaded from a directory and not from a jar file."); - } - } - String uri = classFileUrl.toString(); - if (!uri.startsWith("jar:file:")) { - int idx = uri.indexOf(':'); - String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx); - throw new IllegalStateException( - "This class has been loaded remotely via the " - + protocol - + " protocol. Only loading from a jar on the local file system is" - + " supported."); - } - - int idx = uri.indexOf('!'); - // Sanity check - if (idx == -1) { - throw new IllegalStateException( - "You appear to have loaded this class from a local jar file, but URI has no" - + " \"!\": " - + uri); - } - - try { - String fileName = - URLDecoder.decode( - uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name()); - return new File(fileName).getAbsolutePath(); - } catch (UnsupportedEncodingException e) { - throw new BugInCF("Default charset doesn't exist. Your VM is borked."); - } - } - - /** - * Assert that all files in the list exist and if they don't, throw a RuntimeException with a list - * of the files that do not exist. - * - * @param expectedFiles files that must exist - */ - private static void assertFilesExist(File... expectedFiles) { - List missingFiles = new ArrayList<>(); - for (File file : expectedFiles) { - if (file == null) { - throw new RuntimeException("Null passed to assertFilesExist"); - } - if (!file.exists()) { - missingFiles.add(file); - } - } - - if (!missingFiles.isEmpty()) { - if (missingFiles.size() == 1) { - File missingFile = missingFiles.get(0); - if (missingFile.getName().equals("javac.jar")) { - throw new UserError( - "Could not find " - + missingFile.getAbsolutePath() - + ". This may be because you built the Checker Framework under" - + " Java 11 but are running it under Java 8."); - } - } - List missingAbsoluteFilenames = - CollectionsPlume.mapList(File::getAbsolutePath, missingFiles); - throw new RuntimeException( - "The following files could not be located: " - + String.join(", ", missingAbsoluteFilenames)); - } - } - - private static String quote(String str) { - if (str.contains(" ")) { - if (str.contains("\"")) { - throw new BugInCF( - "Don't know how to quote a string containing a double-quote character " + str); - } - return "\"" + str + "\""; - } - return str; - } - - /////////////////////////////////////////////////////////////////////////// - /// Shorthand checker names - /// - - /** Processor shorthand is enabled for processors in this directory in checker.jar. */ - protected static final String CHECKER_BASE_DIR_NAME = "org/checkerframework/checker/"; - - /** Processor shorthand is enabled for processors in this directory in checker.jar. */ - protected static final String COMMON_BASE_DIR_NAME = "org/checkerframework/common/"; - - /** - * Returns true if processorString, once transformed into fully-qualified form, is present in - * fullyQualifiedCheckerNames. Used by SourceChecker to determine whether a class is annotated for - * any processor that is being run. - * - * @param processorString the name of a single processor, not a comma-separated list of processors - * @param fullyQualifiedCheckerNames a list of fully-qualified checker names - * @return true if the fully-qualified version of {@code processorString} is in {@code - * fullyQualifiedCheckerNames} - */ - public static boolean matchesCheckerOrSubcheckerFromList( - String processorString, List<@FullyQualifiedName String> fullyQualifiedCheckerNames) { - if (processorString.contains(",")) { - return false; // Do not process strings containing multiple processors. - } - - return fullyQualifiedCheckerNames.contains( - unshorthandProcessorNames(processorString, fullyQualifiedCheckerNames, true)); - } - - /** - * For every "-processor" argument in args, replace its immediate successor argument using - * unabbreviateProcessorNames. - */ - protected void replaceShorthandProcessor(List args) { - for (int i = 0; i < args.size(); i++) { - int nextIndex = i + 1; - if (args.size() > nextIndex) { - if (args.get(i).equals("-processor")) { - String replacement = - unshorthandProcessorNames(args.get(nextIndex), getAllCheckerClassNames(), false); - args.remove(nextIndex); - args.add(nextIndex, replacement); - } - } - } - } - - /** - * Returns the list of fully qualified names of the checkers found in checker.jar. This covers - * only checkers with the name ending in "Checker". Checkers with a name ending in "Subchecker" - * are not included in the returned list. Note however that it is possible for a checker with the - * name ending in "Checker" to be used as a subchecker. - * - * @return fully qualified names of the checkers found in checker.jar - */ - private List<@FullyQualifiedName String> getAllCheckerClassNames() { - ArrayList<@FullyQualifiedName String> checkerClassNames = new ArrayList<>(); - try (FileInputStream fis = new FileInputStream(checkerJar); - JarInputStream checkerJarIs = new JarInputStream(fis)) { - ZipEntry entry; - while ((entry = checkerJarIs.getNextEntry()) != null) { - String name = entry.getName(); - // Checkers ending in "Subchecker" are not included in this list used by - // CheckerMain. - if ((name.startsWith(CHECKER_BASE_DIR_NAME) || name.startsWith(COMMON_BASE_DIR_NAME)) - && name.endsWith("Checker.class")) { - // Forward slash is used instead of File.separator because checker.jar uses / as - // the separator. - @SuppressWarnings("signature") // string manipulation - @FullyQualifiedName String fqName = - String.join(".", name.substring(0, name.length() - ".class".length()).split("/")); - checkerClassNames.add(fqName); - } - } - } catch (IOException e) { - // Issue a warning instead of aborting execution. - System.err.printf( - "Could not read %s. Shorthand processor names will not work.%n", checkerJar); - } - - return checkerClassNames; - } - - /** - * Takes a string of comma-separated processor names, and expands any shorthands to - * fully-qualified names from the fullyQualifiedCheckerNames list. For example: - * - *

          -   * NullnessChecker → org.checkerframework.checker.nullness.NullnessChecker
          -   * nullness → org.checkerframework.checker.nullness.NullnessChecker
          -   * NullnessChecker,RegexChecker → org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.regex.RegexChecker
          -   * 
          - * - * Note, a processor entry only gets replaced if it contains NO "." (i.e., it is not qualified by - * a package name) and can be found under the package org.checkerframework.checker in checker.jar. - * - * @param processorsString a comma-separated string identifying processors; often just one - * processor - * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match - * processorsString against - * @param allowSubcheckers whether to match against fully qualified checker names ending with - * "Subchecker" - * @return processorsString where all shorthand references to Checker Framework built-in checkers - * are replaced with fully-qualified references - */ - protected static String unshorthandProcessorNames( - String processorsString, - List<@FullyQualifiedName String> fullyQualifiedCheckerNames, - boolean allowSubcheckers) { - StringJoiner result = new StringJoiner(","); - for (String processor : SystemUtil.COMMA_SPLITTER.split(processorsString)) { - if (!processor.contains(".")) { // Not already fully qualified - processor = - unshorthandProcessorName(processor, fullyQualifiedCheckerNames, allowSubcheckers); - } - result.add(processor); - } - return result.toString(); - } - - /** - * Given a processor name, tries to expand it to a checker in the fullyQualifiedCheckerNames list. - * Returns that expansion, or the argument itself if the expansion fails. - * - * @param processorName a processor name, possibly in shorthand - * @param fullyQualifiedCheckerNames all checker names - * @param allowSubcheckers whether to match subcheckers as well as checkers - * @return the fully-qualified version of {@code processorName} in {@code - * fullyQualifiedCheckerNames}, or else {@code processorName} itself - */ - private static String unshorthandProcessorName( - String processorName, - List<@FullyQualifiedName String> fullyQualifiedCheckerNames, - boolean allowSubcheckers) { - for (String name : fullyQualifiedCheckerNames) { - boolean tryMatch = false; - String[] checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); - String checkerNameShort = checkerPath[checkerPath.length - 1]; - String checkerName = checkerNameShort + "Checker"; - - if (name.endsWith("Checker")) { - checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); - checkerNameShort = checkerPath[checkerPath.length - 1]; - checkerName = checkerNameShort + "Checker"; - tryMatch = true; - } else if (allowSubcheckers && name.endsWith("Subchecker")) { - checkerPath = name.substring(0, name.length() - "Subchecker".length()).split("\\."); - checkerNameShort = checkerPath[checkerPath.length - 1]; - checkerName = checkerNameShort + "Subchecker"; - tryMatch = true; - } - - if (tryMatch) { - if (processorName.equalsIgnoreCase(checkerName) - || processorName.equalsIgnoreCase(checkerNameShort)) { - return name; - } - } - } - - return processorName; // If not matched, return the input string. - } - - /** - * Given a shorthand processor name, returns true if it can be expanded to a checker in the - * fullyQualifiedCheckerNames list. - * - * @param processorName a string identifying one processor - * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match - * processorName against - * @param allowSubcheckers whether to match against fully qualified checker names ending with - * "Subchecker" - * @return true if the shorthand processor name can be expanded to a checker in {@code - * fullyQualifiedCheckerNames} - */ - public static boolean matchesFullyQualifiedProcessor( - String processorName, - List<@FullyQualifiedName String> fullyQualifiedCheckerNames, - boolean allowSubcheckers) { - return !processorName.equals( - unshorthandProcessorName(processorName, fullyQualifiedCheckerNames, allowSubcheckers)); - } + } + + return false; + } + + /** + * Returns true if some @arglist file sets the processorpath. + * + * @param argListFiles command-line argument files (specified with @ on the command line) + */ + private static boolean argsListHasProcessorPath(List argListFiles) { + for (String arg : expandArgFiles(argListFiles)) { + if (arg.contains("-processorpath")) { + return true; + } + } + + return false; + } + + /** + * Return all the lines in all the files. + * + * @param files a list of files + * @return a list of all the lines in all the files + */ + protected static List expandArgFiles(List files) { + List content = new ArrayList<>(); + for (File file : files) { + try { + content.addAll(Files.readAllLines(file.toPath())); + } catch (IOException exc) { + throw new RuntimeException("Could not open file: " + file.getAbsolutePath(), exc); + } + } + return content; + } + + /** + * Find the jar file or directory containing the .class file from which cls was loaded. + * + * @param cls the class whose .class file we wish to locate; if null, CheckerMain.class + * @param errIfFromDirectory if false, throw an exception if the file was loaded from a + * directory + */ + public static String findPathTo(@Nullable Class cls, boolean errIfFromDirectory) + throws IllegalStateException { + if (cls == null) { + cls = CheckerMain.class; + } + String name = cls.getName(); + String classFileName; + /* name is something like pakkage.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ + { + int idx = name.lastIndexOf('.'); + classFileName = (idx == -1 ? name : name.substring(idx + 1)) + ".class"; + } + + URL classFileUrl = cls.getResource(classFileName); + if (classFileUrl == null) { + throw new BugInCF("Cannot find resource " + classFileName); + } + if (classFileUrl.getProtocol().equals("file")) { + if (errIfFromDirectory) { + return classFileUrl.toString(); + } else { + throw new IllegalStateException( + "This class has been loaded from a directory and not from a jar file."); + } + } + String uri = classFileUrl.toString(); + if (!uri.startsWith("jar:file:")) { + int idx = uri.indexOf(':'); + String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx); + throw new IllegalStateException( + "This class has been loaded remotely via the " + + protocol + + " protocol. Only loading from a jar on the local file system is" + + " supported."); + } + + int idx = uri.indexOf('!'); + // Sanity check + if (idx == -1) { + throw new IllegalStateException( + "You appear to have loaded this class from a local jar file, but URI has no" + + " \"!\": " + + uri); + } + + try { + String fileName = + URLDecoder.decode( + uri.substring("jar:file:".length(), idx), + Charset.defaultCharset().name()); + return new File(fileName).getAbsolutePath(); + } catch (UnsupportedEncodingException e) { + throw new BugInCF("Default charset doesn't exist. Your VM is borked."); + } + } + + /** + * Assert that all files in the list exist and if they don't, throw a RuntimeException with a + * list of the files that do not exist. + * + * @param expectedFiles files that must exist + */ + private static void assertFilesExist(File... expectedFiles) { + List missingFiles = new ArrayList<>(); + for (File file : expectedFiles) { + if (file == null) { + throw new RuntimeException("Null passed to assertFilesExist"); + } + if (!file.exists()) { + missingFiles.add(file); + } + } + + if (!missingFiles.isEmpty()) { + if (missingFiles.size() == 1) { + File missingFile = missingFiles.get(0); + if (missingFile.getName().equals("javac.jar")) { + throw new UserError( + "Could not find " + + missingFile.getAbsolutePath() + + ". This may be because you built the Checker Framework under" + + " Java 11 but are running it under Java 8."); + } + } + List missingAbsoluteFilenames = + CollectionsPlume.mapList(File::getAbsolutePath, missingFiles); + throw new RuntimeException( + "The following files could not be located: " + + String.join(", ", missingAbsoluteFilenames)); + } + } + + private static String quote(String str) { + if (str.contains(" ")) { + if (str.contains("\"")) { + throw new BugInCF( + "Don't know how to quote a string containing a double-quote character " + + str); + } + return "\"" + str + "\""; + } + return str; + } + + /////////////////////////////////////////////////////////////////////////// + /// Shorthand checker names + /// + + /** Processor shorthand is enabled for processors in this directory in checker.jar. */ + protected static final String CHECKER_BASE_DIR_NAME = "org/checkerframework/checker/"; + + /** Processor shorthand is enabled for processors in this directory in checker.jar. */ + protected static final String COMMON_BASE_DIR_NAME = "org/checkerframework/common/"; + + /** + * Returns true if processorString, once transformed into fully-qualified form, is present in + * fullyQualifiedCheckerNames. Used by SourceChecker to determine whether a class is annotated + * for any processor that is being run. + * + * @param processorString the name of a single processor, not a comma-separated list of + * processors + * @param fullyQualifiedCheckerNames a list of fully-qualified checker names + * @return true if the fully-qualified version of {@code processorString} is in {@code + * fullyQualifiedCheckerNames} + */ + public static boolean matchesCheckerOrSubcheckerFromList( + String processorString, List<@FullyQualifiedName String> fullyQualifiedCheckerNames) { + if (processorString.contains(",")) { + return false; // Do not process strings containing multiple processors. + } + + return fullyQualifiedCheckerNames.contains( + unshorthandProcessorNames(processorString, fullyQualifiedCheckerNames, true)); + } + + /** + * For every "-processor" argument in args, replace its immediate successor argument using + * unabbreviateProcessorNames. + */ + protected void replaceShorthandProcessor(List args) { + for (int i = 0; i < args.size(); i++) { + int nextIndex = i + 1; + if (args.size() > nextIndex) { + if (args.get(i).equals("-processor")) { + String replacement = + unshorthandProcessorNames( + args.get(nextIndex), getAllCheckerClassNames(), false); + args.remove(nextIndex); + args.add(nextIndex, replacement); + } + } + } + } + + /** + * Returns the list of fully qualified names of the checkers found in checker.jar. This covers + * only checkers with the name ending in "Checker". Checkers with a name ending in "Subchecker" + * are not included in the returned list. Note however that it is possible for a checker with + * the name ending in "Checker" to be used as a subchecker. + * + * @return fully qualified names of the checkers found in checker.jar + */ + private List<@FullyQualifiedName String> getAllCheckerClassNames() { + ArrayList<@FullyQualifiedName String> checkerClassNames = new ArrayList<>(); + try (FileInputStream fis = new FileInputStream(checkerJar); + JarInputStream checkerJarIs = new JarInputStream(fis)) { + ZipEntry entry; + while ((entry = checkerJarIs.getNextEntry()) != null) { + String name = entry.getName(); + // Checkers ending in "Subchecker" are not included in this list used by + // CheckerMain. + if ((name.startsWith(CHECKER_BASE_DIR_NAME) + || name.startsWith(COMMON_BASE_DIR_NAME)) + && name.endsWith("Checker.class")) { + // Forward slash is used instead of File.separator because checker.jar uses / as + // the separator. + @SuppressWarnings("signature") // string manipulation + @FullyQualifiedName String fqName = + String.join( + ".", + name.substring(0, name.length() - ".class".length()) + .split("/")); + checkerClassNames.add(fqName); + } + } + } catch (IOException e) { + // Issue a warning instead of aborting execution. + System.err.printf( + "Could not read %s. Shorthand processor names will not work.%n", checkerJar); + } + + return checkerClassNames; + } + + /** + * Takes a string of comma-separated processor names, and expands any shorthands to + * fully-qualified names from the fullyQualifiedCheckerNames list. For example: + * + *
          +     * NullnessChecker → org.checkerframework.checker.nullness.NullnessChecker
          +     * nullness → org.checkerframework.checker.nullness.NullnessChecker
          +     * NullnessChecker,RegexChecker → org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.regex.RegexChecker
          +     * 
          + * + * Note, a processor entry only gets replaced if it contains NO "." (i.e., it is not qualified + * by a package name) and can be found under the package org.checkerframework.checker in + * checker.jar. + * + * @param processorsString a comma-separated string identifying processors; often just one + * processor + * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match + * processorsString against + * @param allowSubcheckers whether to match against fully qualified checker names ending with + * "Subchecker" + * @return processorsString where all shorthand references to Checker Framework built-in + * checkers are replaced with fully-qualified references + */ + protected static String unshorthandProcessorNames( + String processorsString, + List<@FullyQualifiedName String> fullyQualifiedCheckerNames, + boolean allowSubcheckers) { + StringJoiner result = new StringJoiner(","); + for (String processor : SystemUtil.COMMA_SPLITTER.split(processorsString)) { + if (!processor.contains(".")) { // Not already fully qualified + processor = + unshorthandProcessorName( + processor, fullyQualifiedCheckerNames, allowSubcheckers); + } + result.add(processor); + } + return result.toString(); + } + + /** + * Given a processor name, tries to expand it to a checker in the fullyQualifiedCheckerNames + * list. Returns that expansion, or the argument itself if the expansion fails. + * + * @param processorName a processor name, possibly in shorthand + * @param fullyQualifiedCheckerNames all checker names + * @param allowSubcheckers whether to match subcheckers as well as checkers + * @return the fully-qualified version of {@code processorName} in {@code + * fullyQualifiedCheckerNames}, or else {@code processorName} itself + */ + private static String unshorthandProcessorName( + String processorName, + List<@FullyQualifiedName String> fullyQualifiedCheckerNames, + boolean allowSubcheckers) { + for (String name : fullyQualifiedCheckerNames) { + boolean tryMatch = false; + String[] checkerPath = + name.substring(0, name.length() - "Checker".length()).split("\\."); + String checkerNameShort = checkerPath[checkerPath.length - 1]; + String checkerName = checkerNameShort + "Checker"; + + if (name.endsWith("Checker")) { + checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); + checkerNameShort = checkerPath[checkerPath.length - 1]; + checkerName = checkerNameShort + "Checker"; + tryMatch = true; + } else if (allowSubcheckers && name.endsWith("Subchecker")) { + checkerPath = name.substring(0, name.length() - "Subchecker".length()).split("\\."); + checkerNameShort = checkerPath[checkerPath.length - 1]; + checkerName = checkerNameShort + "Subchecker"; + tryMatch = true; + } + + if (tryMatch) { + if (processorName.equalsIgnoreCase(checkerName) + || processorName.equalsIgnoreCase(checkerNameShort)) { + return name; + } + } + } + + return processorName; // If not matched, return the input string. + } + + /** + * Given a shorthand processor name, returns true if it can be expanded to a checker in the + * fullyQualifiedCheckerNames list. + * + * @param processorName a string identifying one processor + * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match + * processorName against + * @param allowSubcheckers whether to match against fully qualified checker names ending with + * "Subchecker" + * @return true if the shorthand processor name can be expanded to a checker in {@code + * fullyQualifiedCheckerNames} + */ + public static boolean matchesFullyQualifiedProcessor( + String processorName, + List<@FullyQualifiedName String> fullyQualifiedCheckerNames, + boolean allowSubcheckers) { + return !processorName.equals( + unshorthandProcessorName( + processorName, fullyQualifiedCheckerNames, allowSubcheckers)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/Contract.java b/framework/src/main/java/org/checkerframework/framework/util/Contract.java index 3883adc4ecd..849e7bd2fe4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Contract.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Contract.java @@ -1,9 +1,7 @@ package org.checkerframework.framework.util; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Objects; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; @@ -16,6 +14,11 @@ import org.checkerframework.framework.util.dependenttypes.DependentTypesHelper; import org.checkerframework.javacutil.BugInCF; +import java.lang.annotation.Annotation; +import java.util.Objects; + +import javax.lang.model.element.AnnotationMirror; + /** * A contract represents an annotation on an expression. It is a precondition, postcondition, or * conditional postcondition. @@ -26,298 +29,302 @@ */ public abstract class Contract { - /** - * The expression for which the condition must hold, such as {@code "foo"} in - * {@code @RequiresNonNull("foo")}. - * - *

          An annotation like {@code @RequiresNonNull({"a", "b", "c"})} would be represented by - * multiple Contracts. - */ - public final String expressionString; - - /** The annotation on the type of expression, according to this contract. */ - public final AnnotationMirror annotation; - - /** - * The annotation that expressed this contract; used for diagnostic messages, but not for the - * location of the diagnostic message. - */ - public final AnnotationMirror contractAnnotation; - - // This is redundant with the contract's class and is not used in this file, but the field - // is used by clients, for its fields. - /** The kind of contract: precondition, postcondition, or conditional postcondition. */ - public final Kind kind; - - /** Enumerates the kinds of contracts. */ - public enum Kind { - /** A precondition. */ - PRECONDITION( - "precondition", - PreconditionAnnotation.class, - RequiresQualifier.class, - RequiresQualifier.List.class), - /** A postcondition. */ - POSTCONDITION( - "postcondition", - PostconditionAnnotation.class, - EnsuresQualifier.class, - EnsuresQualifier.List.class), - /** A conditional postcondition. */ - CONDITIONALPOSTCONDITION( - "conditional postcondition", - ConditionalPostconditionAnnotation.class, - EnsuresQualifierIf.class, - EnsuresQualifierIf.List.class); - - /** Used for constructing error messages. */ - public final String errorKey; - /** - * The meta-annotation identifying annotations of this kind: PreconditionAnnotation, - * PostconditionAnnotation, or ConditionalPostconditionAnnotation. - */ - public final Class metaAnnotation; - - /** - * The built-in framework qualifier for this contract: RequiresQualifier, EnsuresQualifier, or - * EnsuresQualifierIf. + * The expression for which the condition must hold, such as {@code "foo"} in + * {@code @RequiresNonNull("foo")}. + * + *

          An annotation like {@code @RequiresNonNull({"a", "b", "c"})} would be represented by + * multiple Contracts. */ - public final Class frameworkContractClass; + public final String expressionString; - /** - * The built-in framework qualifier for repeated occurrences of this contract: - * RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. - */ - public final Class frameworkContractListClass; + /** The annotation on the type of expression, according to this contract. */ + public final AnnotationMirror annotation; /** - * Create a new Kind. - * - * @param errorKey used for constructing error messages - * @param metaAnnotation the meta-annotation identifying annotations of this kind - * @param frameworkContractClass the built-in framework qualifier for this contract - * @param frameworkContractListClass the built-in framework qualifier for repeated occurrences - * of this contract + * The annotation that expressed this contract; used for diagnostic messages, but not for the + * location of the diagnostic message. */ - Kind( - String errorKey, - Class metaAnnotation, - Class frameworkContractClass, - Class frameworkContractListClass) { - this.errorKey = errorKey; - this.metaAnnotation = metaAnnotation; - this.frameworkContractClass = frameworkContractClass; - this.frameworkContractListClass = frameworkContractListClass; - } - } + public final AnnotationMirror contractAnnotation; - /** - * Creates a new Contract. This should be called only by the constructors for {@link - * Precondition}, {@link Postcondition}, and {@link ConditionalPostcondition}. - * - * @param kind precondition, postcondition, or conditional postcondition - * @param expressionString the Java expression that should have a type qualifier - * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; used - * for diagnostic messages - */ - private Contract( - Kind kind, - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation) { - this.expressionString = expressionString; - this.annotation = annotation; - this.contractAnnotation = contractAnnotation; - this.kind = kind; - } + // This is redundant with the contract's class and is not used in this file, but the field + // is used by clients, for its fields. + /** The kind of contract: precondition, postcondition, or conditional postcondition. */ + public final Kind kind; - /** - * Creates a new {@code Contract}. - * - * @param kind precondition, postcondition, or conditional postcondition - * @param expressionString the Java expression that should have a type qualifier - * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; used - * for diagnostic messages - * @param ensuresQualifierIf the ensuresQualifierIf field, for a conditional postcondition - * @return a new contract - */ - public static Contract create( - Kind kind, - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation, - Boolean ensuresQualifierIf) { - if ((ensuresQualifierIf != null) != (kind == Kind.CONDITIONALPOSTCONDITION)) { - throw new BugInCF( - "Mismatch: Contract.create(%s, %s, %s, %s, %s)", - kind, expressionString, annotation, contractAnnotation, ensuresQualifierIf); - } - switch (kind) { - case PRECONDITION: - return new Precondition(expressionString, annotation, contractAnnotation); - case POSTCONDITION: - return new Postcondition(expressionString, annotation, contractAnnotation); - case CONDITIONALPOSTCONDITION: - return new ConditionalPostcondition( - expressionString, annotation, contractAnnotation, ensuresQualifierIf); - default: - throw new BugInCF("Unrecognized kind: " + kind); - } - } + /** Enumerates the kinds of contracts. */ + public enum Kind { + /** A precondition. */ + PRECONDITION( + "precondition", + PreconditionAnnotation.class, + RequiresQualifier.class, + RequiresQualifier.List.class), + /** A postcondition. */ + POSTCONDITION( + "postcondition", + PostconditionAnnotation.class, + EnsuresQualifier.class, + EnsuresQualifier.List.class), + /** A conditional postcondition. */ + CONDITIONALPOSTCONDITION( + "conditional postcondition", + ConditionalPostconditionAnnotation.class, + EnsuresQualifierIf.class, + EnsuresQualifierIf.List.class); - // Note that equality requires exact match of the run-time class and that it ignores the - // `contractAnnotation` field. - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null) { - return false; - } - if (getClass() != o.getClass()) { - return false; - } + /** Used for constructing error messages. */ + public final String errorKey; - Contract otherContract = (Contract) o; + /** + * The meta-annotation identifying annotations of this kind: PreconditionAnnotation, + * PostconditionAnnotation, or ConditionalPostconditionAnnotation. + */ + public final Class metaAnnotation; - return kind == otherContract.kind - && Objects.equals(expressionString, otherContract.expressionString) - && Objects.equals(annotation, otherContract.annotation); - } + /** + * The built-in framework qualifier for this contract: RequiresQualifier, EnsuresQualifier, + * or EnsuresQualifierIf. + */ + public final Class frameworkContractClass; - @Override - public int hashCode() { - return Objects.hash(kind, expressionString, annotation); - } + /** + * The built-in framework qualifier for repeated occurrences of this contract: + * RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. + */ + public final Class frameworkContractListClass; - @Override - public String toString() { - return String.format( - "%s{expressionString=%s, annotation=%s, contractAnnotation=%s}", - getClass().getSimpleName(), expressionString, annotation, contractAnnotation); - } - - /** - * Viewpoint-adapt {@link #annotation} using {@code stringToJavaExpr}. - * - *

          For example, if the contract is {@code @EnsuresKeyFor(value = "this.field", map = "map")}, - * {@code annotation} is {@code @KeyFor("map")}. This method applies {@code stringToJava} to "map" - * and returns a new {@code KeyFor} annotation with the result. - * - * @param factory used to get {@link DependentTypesHelper} - * @param stringToJavaExpr function used to convert strings to {@link JavaExpression}s - * @param errorTree if non-null, where to report any errors that occur when parsing the dependent - * type annotation; if null, report no errors - * @return the viewpoint-adapted annotation, or {@link #annotation} if it is not a dependent type - * annotation - */ - public AnnotationMirror viewpointAdaptDependentTypeAnnotation( - GenericAnnotatedTypeFactory factory, - StringToJavaExpression stringToJavaExpr, - @Nullable Tree errorTree) { - DependentTypesHelper dependentTypesHelper = factory.getDependentTypesHelper(); - AnnotationMirror standardized = - dependentTypesHelper.convertAnnotationMirror(stringToJavaExpr, annotation); - if (standardized == null) { - return annotation; - } - if (errorTree != null) { - dependentTypesHelper.checkAnnotationForErrorExpressions(standardized, errorTree); + /** + * Create a new Kind. + * + * @param errorKey used for constructing error messages + * @param metaAnnotation the meta-annotation identifying annotations of this kind + * @param frameworkContractClass the built-in framework qualifier for this contract + * @param frameworkContractListClass the built-in framework qualifier for repeated + * occurrences of this contract + */ + Kind( + String errorKey, + Class metaAnnotation, + Class frameworkContractClass, + Class frameworkContractListClass) { + this.errorKey = errorKey; + this.metaAnnotation = metaAnnotation; + this.frameworkContractClass = frameworkContractClass; + this.frameworkContractListClass = frameworkContractListClass; + } } - return standardized; - } - /** A precondition contract. */ - public static class Precondition extends Contract { /** - * Create a precondition contract. + * Creates a new Contract. This should be called only by the constructors for {@link + * Precondition}, {@link Postcondition}, and {@link ConditionalPostcondition}. * + * @param kind precondition, postcondition, or conditional postcondition * @param expressionString the Java expression that should have a type qualifier * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the precondition annotation that the programmer wrote; used for - * diagnostic messages + * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; + * used for diagnostic messages */ - public Precondition( - String expressionString, AnnotationMirror annotation, AnnotationMirror contractAnnotation) { - super(Kind.PRECONDITION, expressionString, annotation, contractAnnotation); + private Contract( + Kind kind, + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation) { + this.expressionString = expressionString; + this.annotation = annotation; + this.contractAnnotation = contractAnnotation; + this.kind = kind; } - } - /** A postcondition contract. */ - public static class Postcondition extends Contract { /** - * Create a postcondition contract. + * Creates a new {@code Contract}. * + * @param kind precondition, postcondition, or conditional postcondition * @param expressionString the Java expression that should have a type qualifier * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the postcondition annotation that the programmer wrote; used for - * diagnostic messages + * @param contractAnnotation the pre- or post-condition annotation that the programmer wrote; + * used for diagnostic messages + * @param ensuresQualifierIf the ensuresQualifierIf field, for a conditional postcondition + * @return a new contract */ - public Postcondition( - String expressionString, AnnotationMirror annotation, AnnotationMirror contractAnnotation) { - super(Kind.POSTCONDITION, expressionString, annotation, contractAnnotation); + public static Contract create( + Kind kind, + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation, + Boolean ensuresQualifierIf) { + if ((ensuresQualifierIf != null) != (kind == Kind.CONDITIONALPOSTCONDITION)) { + throw new BugInCF( + "Mismatch: Contract.create(%s, %s, %s, %s, %s)", + kind, expressionString, annotation, contractAnnotation, ensuresQualifierIf); + } + switch (kind) { + case PRECONDITION: + return new Precondition(expressionString, annotation, contractAnnotation); + case POSTCONDITION: + return new Postcondition(expressionString, annotation, contractAnnotation); + case CONDITIONALPOSTCONDITION: + return new ConditionalPostcondition( + expressionString, annotation, contractAnnotation, ensuresQualifierIf); + default: + throw new BugInCF("Unrecognized kind: " + kind); + } + } + + // Note that equality requires exact match of the run-time class and that it ignores the + // `contractAnnotation` field. + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + if (getClass() != o.getClass()) { + return false; + } + + Contract otherContract = (Contract) o; + + return kind == otherContract.kind + && Objects.equals(expressionString, otherContract.expressionString) + && Objects.equals(annotation, otherContract.annotation); } - } - /** - * Represents a conditional postcondition that must be verified by {@code BaseTypeVisitor} or one - * of its subclasses. Automatically extracted from annotations with meta-annotation - * {@code @ConditionalPostconditionAnnotation}, such as {@code EnsuresNonNullIf}. - */ - public static class ConditionalPostcondition extends Contract { + @Override + public int hashCode() { + return Objects.hash(kind, expressionString, annotation); + } - /** - * The return value for the annotated method that ensures that the conditional postcondition - * holds. For example, given - * - *

          -     * {@code @EnsuresNonNullIf(expression="foo", result=false) boolean method()}
          -     * 
          - * - * {@code foo} is guaranteed to be {@code @NonNull} after a call to {@code method()} that - * returns {@code false}. - */ - public final boolean resultValue; + @Override + public String toString() { + return String.format( + "%s{expressionString=%s, annotation=%s, contractAnnotation=%s}", + getClass().getSimpleName(), expressionString, annotation, contractAnnotation); + } /** - * Create a new conditional postcondition. + * Viewpoint-adapt {@link #annotation} using {@code stringToJavaExpr}. * - * @param expressionString the Java expression that should have a type qualifier - * @param annotation the type qualifier that {@code expressionString} should have - * @param contractAnnotation the postcondition annotation that the programmer wrote; used for - * diagnostic messages - * @param resultValue whether the condition is the method returning true or false + *

          For example, if the contract is {@code @EnsuresKeyFor(value = "this.field", map = "map")}, + * {@code annotation} is {@code @KeyFor("map")}. This method applies {@code stringToJava} to + * "map" and returns a new {@code KeyFor} annotation with the result. + * + * @param factory used to get {@link DependentTypesHelper} + * @param stringToJavaExpr function used to convert strings to {@link JavaExpression}s + * @param errorTree if non-null, where to report any errors that occur when parsing the + * dependent type annotation; if null, report no errors + * @return the viewpoint-adapted annotation, or {@link #annotation} if it is not a dependent + * type annotation */ - public ConditionalPostcondition( - String expressionString, - AnnotationMirror annotation, - AnnotationMirror contractAnnotation, - boolean resultValue) { - super(Kind.CONDITIONALPOSTCONDITION, expressionString, annotation, contractAnnotation); - this.resultValue = resultValue; + public AnnotationMirror viewpointAdaptDependentTypeAnnotation( + GenericAnnotatedTypeFactory factory, + StringToJavaExpression stringToJavaExpr, + @Nullable Tree errorTree) { + DependentTypesHelper dependentTypesHelper = factory.getDependentTypesHelper(); + AnnotationMirror standardized = + dependentTypesHelper.convertAnnotationMirror(stringToJavaExpr, annotation); + if (standardized == null) { + return annotation; + } + if (errorTree != null) { + dependentTypesHelper.checkAnnotationForErrorExpressions(standardized, errorTree); + } + return standardized; } - @Override - public boolean equals(@Nullable Object o) { - return super.equals(o) && resultValue == ((ConditionalPostcondition) o).resultValue; + /** A precondition contract. */ + public static class Precondition extends Contract { + /** + * Create a precondition contract. + * + * @param expressionString the Java expression that should have a type qualifier + * @param annotation the type qualifier that {@code expressionString} should have + * @param contractAnnotation the precondition annotation that the programmer wrote; used for + * diagnostic messages + */ + public Precondition( + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation) { + super(Kind.PRECONDITION, expressionString, annotation, contractAnnotation); + } } - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), resultValue); + /** A postcondition contract. */ + public static class Postcondition extends Contract { + /** + * Create a postcondition contract. + * + * @param expressionString the Java expression that should have a type qualifier + * @param annotation the type qualifier that {@code expressionString} should have + * @param contractAnnotation the postcondition annotation that the programmer wrote; used + * for diagnostic messages + */ + public Postcondition( + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation) { + super(Kind.POSTCONDITION, expressionString, annotation, contractAnnotation); + } } - @Override - public String toString() { - String superToString = super.toString(); - return superToString.substring(0, superToString.length() - 1) - + ", annoResult=" - + resultValue - + "}"; + /** + * Represents a conditional postcondition that must be verified by {@code BaseTypeVisitor} or + * one of its subclasses. Automatically extracted from annotations with meta-annotation + * {@code @ConditionalPostconditionAnnotation}, such as {@code EnsuresNonNullIf}. + */ + public static class ConditionalPostcondition extends Contract { + + /** + * The return value for the annotated method that ensures that the conditional postcondition + * holds. For example, given + * + *

          +         * {@code @EnsuresNonNullIf(expression="foo", result=false) boolean method()}
          +         * 
          + * + * {@code foo} is guaranteed to be {@code @NonNull} after a call to {@code method()} that + * returns {@code false}. + */ + public final boolean resultValue; + + /** + * Create a new conditional postcondition. + * + * @param expressionString the Java expression that should have a type qualifier + * @param annotation the type qualifier that {@code expressionString} should have + * @param contractAnnotation the postcondition annotation that the programmer wrote; used + * for diagnostic messages + * @param resultValue whether the condition is the method returning true or false + */ + public ConditionalPostcondition( + String expressionString, + AnnotationMirror annotation, + AnnotationMirror contractAnnotation, + boolean resultValue) { + super(Kind.CONDITIONALPOSTCONDITION, expressionString, annotation, contractAnnotation); + this.resultValue = resultValue; + } + + @Override + public boolean equals(@Nullable Object o) { + return super.equals(o) && resultValue == ((ConditionalPostcondition) o).resultValue; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), resultValue); + } + + @Override + public String toString() { + String superToString = super.toString(); + return superToString.substring(0, superToString.length() - 1) + + ", annoResult=" + + resultValue + + "}"; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java index 5ce98c58e65..144882d44de 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ContractsFromMethod.java @@ -1,7 +1,5 @@ package org.checkerframework.framework.util; -import java.util.Set; -import javax.lang.model.element.ExecutableElement; import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; import org.checkerframework.framework.qual.EnsuresQualifier; import org.checkerframework.framework.qual.EnsuresQualifierIf; @@ -9,6 +7,10 @@ import org.checkerframework.framework.qual.PreconditionAnnotation; import org.checkerframework.framework.qual.RequiresQualifier; +import java.util.Set; + +import javax.lang.model.element.ExecutableElement; + /** * Interface to retrieve pre- and postconditions from a method. * @@ -21,36 +23,36 @@ */ public interface ContractsFromMethod { - /** - * Returns all the contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method or constructor whose contracts to retrieve - * @return the contracts on {@code executableElement} - */ - Set getContracts(ExecutableElement executableElement); - - /** - * Returns the precondition contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the precondition contracts on {@code executableElement} - */ - Set getPreconditions(ExecutableElement executableElement); - - /** - * Returns the postcondition contracts on {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the postcondition contracts on {@code executableElement} - */ - Set getPostconditions(ExecutableElement executableElement); - - /** - * Returns the conditional postcondition contracts on method {@code methodElement}. - * - * @param methodElement the method whose contracts to return - * @return the conditional postcondition contracts on {@code methodElement} - */ - Set getConditionalPostconditions( - ExecutableElement methodElement); + /** + * Returns all the contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method or constructor whose contracts to retrieve + * @return the contracts on {@code executableElement} + */ + Set getContracts(ExecutableElement executableElement); + + /** + * Returns the precondition contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the precondition contracts on {@code executableElement} + */ + Set getPreconditions(ExecutableElement executableElement); + + /** + * Returns the postcondition contracts on {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the postcondition contracts on {@code executableElement} + */ + Set getPostconditions(ExecutableElement executableElement); + + /** + * Returns the conditional postcondition contracts on method {@code methodElement}. + * + * @param methodElement the method whose contracts to return + * @return the conditional postcondition contracts on {@code methodElement} + */ + Set getConditionalPostconditions( + ExecutableElement methodElement); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java index 9a534ad6410..b6b8b155416 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java @@ -1,66 +1,68 @@ package org.checkerframework.framework.util; -import java.util.Collection; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; +import java.util.Collection; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; + /** A utility for converting AnnotationMirrors to Strings. It omits full package names. */ public class DefaultAnnotationFormatter implements AnnotationFormatter { - /** - * Returns true if, by default, anno should not be printed. - * - * @see org.checkerframework.framework.qual.InvisibleQualifier - * @param anno the annotation mirror to test - * @return true if anno's declaration was qualified by InvisibleQualifier - */ - public static boolean isInvisibleQualified(AnnotationMirror anno) { - TypeElement annoElement = (TypeElement) anno.getAnnotationType().asElement(); - return annoElement.getAnnotation(InvisibleQualifier.class) != null; - } + /** + * Returns true if, by default, anno should not be printed. + * + * @see org.checkerframework.framework.qual.InvisibleQualifier + * @param anno the annotation mirror to test + * @return true if anno's declaration was qualified by InvisibleQualifier + */ + public static boolean isInvisibleQualified(AnnotationMirror anno) { + TypeElement annoElement = (TypeElement) anno.getAnnotationType().asElement(); + return annoElement.getAnnotation(InvisibleQualifier.class) != null; + } - /** - * Creates a String of each annotation in annos separated by a single space character and - * terminated by a space character, obeying the printInvisible parameter. - * - * @param annos a collection of annotations to print - * @param printInvisible whether or not to print "invisible" annotation mirrors - * @return the list of annotations converted to a String - */ - @Override - @SideEffectFree - public String formatAnnotationString( - Collection annos, boolean printInvisible) { - StringBuilder sb = new StringBuilder(); - for (AnnotationMirror obj : annos) { - if (obj == null) { - throw new BugInCF( - "AnnotatedTypeMirror.formatAnnotationString: found null AnnotationMirror"); - } - if (isInvisibleQualified(obj) && !printInvisible) { - continue; - } - AnnotationUtils.toStringSimple(obj, sb); - sb.append(" "); + /** + * Creates a String of each annotation in annos separated by a single space character and + * terminated by a space character, obeying the printInvisible parameter. + * + * @param annos a collection of annotations to print + * @param printInvisible whether or not to print "invisible" annotation mirrors + * @return the list of annotations converted to a String + */ + @Override + @SideEffectFree + public String formatAnnotationString( + Collection annos, boolean printInvisible) { + StringBuilder sb = new StringBuilder(); + for (AnnotationMirror obj : annos) { + if (obj == null) { + throw new BugInCF( + "AnnotatedTypeMirror.formatAnnotationString: found null AnnotationMirror"); + } + if (isInvisibleQualified(obj) && !printInvisible) { + continue; + } + AnnotationUtils.toStringSimple(obj, sb); + sb.append(" "); + } + return sb.toString(); } - return sb.toString(); - } - /** - * Returns the string representation of a single AnnotationMirror, without showing full package - * names. - * - * @param anno the annotation mirror to convert - * @return the string representation of a single AnnotationMirror, without showing full package - * names - */ - @Override - @SideEffectFree - public String formatAnnotationMirror(AnnotationMirror anno) { - return AnnotationUtils.toStringSimple(anno); - } + /** + * Returns the string representation of a single AnnotationMirror, without showing full package + * names. + * + * @param anno the annotation mirror to convert + * @return the string representation of a single AnnotationMirror, without showing full package + * names + */ + @Override + @SideEffectFree + public String formatAnnotationMirror(AnnotationMirror anno) { + return AnnotationUtils.toStringSimple(anno); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java index 5e8582387b8..5089c6b7c43 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java @@ -1,16 +1,5 @@ package org.checkerframework.framework.util; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.util.ElementFilter; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; import org.checkerframework.framework.qual.EnsuresQualifier; @@ -25,6 +14,19 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.util.ElementFilter; + /** * A utility class to retrieve pre- and postconditions from a method. * @@ -39,287 +41,300 @@ // more helpful error message. public class DefaultContractsFromMethod implements ContractsFromMethod { - /** The QualifierArgument.value field/element. */ - protected final ExecutableElement qualifierArgumentValueElement; + /** The QualifierArgument.value field/element. */ + protected final ExecutableElement qualifierArgumentValueElement; - /** The factory that this ContractsFromMethod is associated with. */ - protected final GenericAnnotatedTypeFactory atypeFactory; + /** The factory that this ContractsFromMethod is associated with. */ + protected final GenericAnnotatedTypeFactory atypeFactory; - /** - * Creates a ContractsFromMethod for the given factory. - * - * @param atypeFactory the type factory associated with the newly-created ContractsFromMethod - */ - public DefaultContractsFromMethod(GenericAnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - qualifierArgumentValueElement = - TreeUtils.getMethod(QualifierArgument.class, "value", 0, atypeFactory.getProcessingEnv()); - } + /** + * Creates a ContractsFromMethod for the given factory. + * + * @param atypeFactory the type factory associated with the newly-created ContractsFromMethod + */ + public DefaultContractsFromMethod(GenericAnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + qualifierArgumentValueElement = + TreeUtils.getMethod( + QualifierArgument.class, "value", 0, atypeFactory.getProcessingEnv()); + } - /** - * Returns all the contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method or constructor whose contracts to retrieve - * @return the contracts on {@code executableElement} - */ - @Override - public Set getContracts(ExecutableElement executableElement) { - Set contracts = new LinkedHashSet<>(); - contracts.addAll(getPreconditions(executableElement)); - contracts.addAll(getPostconditions(executableElement)); - contracts.addAll(getConditionalPostconditions(executableElement)); - return contracts; - } + /** + * Returns all the contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method or constructor whose contracts to retrieve + * @return the contracts on {@code executableElement} + */ + @Override + public Set getContracts(ExecutableElement executableElement) { + Set contracts = new LinkedHashSet<>(); + contracts.addAll(getPreconditions(executableElement)); + contracts.addAll(getPostconditions(executableElement)); + contracts.addAll(getConditionalPostconditions(executableElement)); + return contracts; + } - /** - * Returns the precondition contracts on method or constructor {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the precondition contracts on {@code executableElement} - */ - @Override - public Set getPreconditions(ExecutableElement executableElement) { - return getContractsOfKind( - executableElement, Contract.Kind.PRECONDITION, Contract.Precondition.class); - } + /** + * Returns the precondition contracts on method or constructor {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the precondition contracts on {@code executableElement} + */ + @Override + public Set getPreconditions(ExecutableElement executableElement) { + return getContractsOfKind( + executableElement, Contract.Kind.PRECONDITION, Contract.Precondition.class); + } - /** - * Returns the postcondition contracts on {@code executableElement}. - * - * @param executableElement the method whose contracts to return - * @return the postcondition contracts on {@code executableElement} - */ - @Override - public Set getPostconditions(ExecutableElement executableElement) { - return getContractsOfKind( - executableElement, Contract.Kind.POSTCONDITION, Contract.Postcondition.class); - } + /** + * Returns the postcondition contracts on {@code executableElement}. + * + * @param executableElement the method whose contracts to return + * @return the postcondition contracts on {@code executableElement} + */ + @Override + public Set getPostconditions(ExecutableElement executableElement) { + return getContractsOfKind( + executableElement, Contract.Kind.POSTCONDITION, Contract.Postcondition.class); + } - /** - * Returns the conditional postcondition contracts on method {@code methodElement}. - * - * @param methodElement the method whose contracts to return - * @return the conditional postcondition contracts on {@code methodElement} - */ - @Override - public Set getConditionalPostconditions( - ExecutableElement methodElement) { - return getContractsOfKind( - methodElement, - Contract.Kind.CONDITIONALPOSTCONDITION, - Contract.ConditionalPostcondition.class); - } + /** + * Returns the conditional postcondition contracts on method {@code methodElement}. + * + * @param methodElement the method whose contracts to return + * @return the conditional postcondition contracts on {@code methodElement} + */ + @Override + public Set getConditionalPostconditions( + ExecutableElement methodElement) { + return getContractsOfKind( + methodElement, + Contract.Kind.CONDITIONALPOSTCONDITION, + Contract.ConditionalPostcondition.class); + } - /// Helper methods + /// Helper methods - /** - * Returns the contracts (of a particular kind) on method or constructor {@code - * executableElement}. - * - * @param the type of {@link Contract} to return - * @param executableElement the method whose contracts to return - * @param kind the kind of contracts to retrieve - * @param clazz the class to determine the return type - * @return the contracts on {@code executableElement} - */ - private Set getContractsOfKind( - ExecutableElement executableElement, Contract.Kind kind, Class clazz) { - Set result = new LinkedHashSet<>(); - // Check for a single framework-defined contract annotation. - // The result is RequiresQualifier, EnsuresQualifier, EnsuresQualifierIf, or null. - AnnotationMirror frameworkContractAnno = - atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractClass); - if (frameworkContractAnno != null) { - result.addAll(getContract(kind, frameworkContractAnno, clazz)); - } + /** + * Returns the contracts (of a particular kind) on method or constructor {@code + * executableElement}. + * + * @param the type of {@link Contract} to return + * @param executableElement the method whose contracts to return + * @param kind the kind of contracts to retrieve + * @param clazz the class to determine the return type + * @return the contracts on {@code executableElement} + */ + private Set getContractsOfKind( + ExecutableElement executableElement, Contract.Kind kind, Class clazz) { + Set result = new LinkedHashSet<>(); + // Check for a single framework-defined contract annotation. + // The result is RequiresQualifier, EnsuresQualifier, EnsuresQualifierIf, or null. + AnnotationMirror frameworkContractAnno = + atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractClass); + if (frameworkContractAnno != null) { + result.addAll(getContract(kind, frameworkContractAnno, clazz)); + } - // Check for a framework-defined wrapper around contract annotations. - // The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. - // Add its elements to `result`. - AnnotationMirror frameworkContractListAnno = - atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractListClass); - if (frameworkContractListAnno != null) { - List frameworkContractAnnoList = - atypeFactory.getContractListValues(frameworkContractListAnno); - for (AnnotationMirror a : frameworkContractAnnoList) { - result.addAll(getContract(kind, a, clazz)); - } - } + // Check for a framework-defined wrapper around contract annotations. + // The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. + // Add its elements to `result`. + AnnotationMirror frameworkContractListAnno = + atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractListClass); + if (frameworkContractListAnno != null) { + List frameworkContractAnnoList = + atypeFactory.getContractListValues(frameworkContractListAnno); + for (AnnotationMirror a : frameworkContractAnnoList) { + result.addAll(getContract(kind, a, clazz)); + } + } - // Check for type-system specific annotations. These are the annotations that are - // meta-annotated by `kind.metaAnnotation`, which is PreconditionAnnotation, - // PostconditionAnnotation, or ConditionalPostconditionAnnotation. - List> declAnnotations = - atypeFactory.getDeclAnnotationWithMetaAnnotation(executableElement, kind.metaAnnotation); - for (IPair r : declAnnotations) { - AnnotationMirror anno = r.first; - // contractAnno is the meta-annotation on anno, such as PreconditionAnnotation, - // PostconditionAnnotation, or ConditionalPostconditionAnnotation. - AnnotationMirror contractAnno = r.second; - AnnotationMirror enforcedQualifier = - getQualifierEnforcedByContractAnnotation(contractAnno, anno); - if (enforcedQualifier == null) { - continue; - } - List expressions = atypeFactory.getContractExpressions(kind, anno); - Collections.sort(expressions); - Boolean ensuresQualifierIfResult = atypeFactory.getEnsuresQualifierIfResult(kind, anno); + // Check for type-system specific annotations. These are the annotations that are + // meta-annotated by `kind.metaAnnotation`, which is PreconditionAnnotation, + // PostconditionAnnotation, or ConditionalPostconditionAnnotation. + List> declAnnotations = + atypeFactory.getDeclAnnotationWithMetaAnnotation( + executableElement, kind.metaAnnotation); + for (IPair r : declAnnotations) { + AnnotationMirror anno = r.first; + // contractAnno is the meta-annotation on anno, such as PreconditionAnnotation, + // PostconditionAnnotation, or ConditionalPostconditionAnnotation. + AnnotationMirror contractAnno = r.second; + AnnotationMirror enforcedQualifier = + getQualifierEnforcedByContractAnnotation(contractAnno, anno); + if (enforcedQualifier == null) { + continue; + } + List expressions = atypeFactory.getContractExpressions(kind, anno); + Collections.sort(expressions); + Boolean ensuresQualifierIfResult = atypeFactory.getEnsuresQualifierIfResult(kind, anno); - for (String expr : expressions) { - T contract = - clazz.cast( - Contract.create(kind, expr, enforcedQualifier, anno, ensuresQualifierIfResult)); - result.add(contract); - } + for (String expr : expressions) { + T contract = + clazz.cast( + Contract.create( + kind, + expr, + enforcedQualifier, + anno, + ensuresQualifierIfResult)); + result.add(contract); + } + } + return result; } - return result; - } - /** - * Returns the contracts expressed by the given framework contract annotation. - * - * @param the type of {@link Contract} to return - * @param kind the kind of {@code contractAnnotation} - * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link - * EnsuresQualifierIf} - * @param clazz the class to determine the return type - * @return the contracts expressed by the given annotation, or the empty set if the argument is - * null - */ - private Set getContract( - Contract.Kind kind, AnnotationMirror contractAnnotation, Class clazz) { - if (contractAnnotation == null) { - return Collections.emptySet(); - } + /** + * Returns the contracts expressed by the given framework contract annotation. + * + * @param the type of {@link Contract} to return + * @param kind the kind of {@code contractAnnotation} + * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifierIf} + * @param clazz the class to determine the return type + * @return the contracts expressed by the given annotation, or the empty set if the argument is + * null + */ + private Set getContract( + Contract.Kind kind, AnnotationMirror contractAnnotation, Class clazz) { + if (contractAnnotation == null) { + return Collections.emptySet(); + } - AnnotationMirror enforcedQualifier = - getQualifierEnforcedByContractAnnotation(contractAnnotation); - if (enforcedQualifier == null) { - return Collections.emptySet(); - } + AnnotationMirror enforcedQualifier = + getQualifierEnforcedByContractAnnotation(contractAnnotation); + if (enforcedQualifier == null) { + return Collections.emptySet(); + } - List expressions = atypeFactory.getContractExpressions(contractAnnotation); - Collections.sort(expressions); + List expressions = atypeFactory.getContractExpressions(contractAnnotation); + Collections.sort(expressions); - Boolean ensuresQualifierIfResult = - atypeFactory.getEnsuresQualifierIfResult(kind, contractAnnotation); + Boolean ensuresQualifierIfResult = + atypeFactory.getEnsuresQualifierIfResult(kind, contractAnnotation); - Set result = new LinkedHashSet<>(); - for (String expr : expressions) { - T contract = - clazz.cast( - Contract.create( - kind, expr, enforcedQualifier, contractAnnotation, ensuresQualifierIfResult)); - result.add(contract); + Set result = new LinkedHashSet<>(); + for (String expr : expressions) { + T contract = + clazz.cast( + Contract.create( + kind, + expr, + enforcedQualifier, + contractAnnotation, + ensuresQualifierIfResult)); + result.add(contract); + } + return result; } - return result; - } - /** - * Returns the annotation mirror as specified by the {@code qualifier} element in {@code - * contractAnno}. May return null. - * - * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} - * @return the type annotation specified in {@code contractAnno.qualifier} - */ - private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( - AnnotationMirror contractAnno) { - return getQualifierEnforcedByContractAnnotation(contractAnno, null, null); - } + /** + * Returns the annotation mirror as specified by the {@code qualifier} element in {@code + * contractAnno}. May return null. + * + * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} + * @return the type annotation specified in {@code contractAnno.qualifier} + */ + private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( + AnnotationMirror contractAnno) { + return getQualifierEnforcedByContractAnnotation(contractAnno, null, null); + } - /** - * Returns the annotation mirror as specified by the {@code qualifier} element in {@code - * contractAnno}, with elements/arguments taken from {@code argumentAnno}. May return null. - * - * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} - * @param argumentAnno supplies the elements/fields in the return value - * @return the type annotation specified in {@code contractAnno.qualifier} - */ - private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( - AnnotationMirror contractAnno, AnnotationMirror argumentAnno) { + /** + * Returns the annotation mirror as specified by the {@code qualifier} element in {@code + * contractAnno}, with elements/arguments taken from {@code argumentAnno}. May return null. + * + * @param contractAnno a pre- or post-condition annotation, such as {@code @RequiresQualifier} + * @param argumentAnno supplies the elements/fields in the return value + * @return the type annotation specified in {@code contractAnno.qualifier} + */ + private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( + AnnotationMirror contractAnno, AnnotationMirror argumentAnno) { - Map argumentRenaming = - makeArgumentRenaming(argumentAnno.getAnnotationType().asElement()); - return getQualifierEnforcedByContractAnnotation(contractAnno, argumentAnno, argumentRenaming); - } + Map argumentRenaming = + makeArgumentRenaming(argumentAnno.getAnnotationType().asElement()); + return getQualifierEnforcedByContractAnnotation( + contractAnno, argumentAnno, argumentRenaming); + } - /** - * Returns the annotation mirror as specified by the "qualifier" element in {@code contractAnno}. - * If {@code argumentAnno} is specified, then elements/arguments are copied from {@code - * argumentAnno} to the returned annotation, renamed according to {@code argumentRenaming}. If - * {@code argumentAnno} is not specified, the result has no elements/arguments; this may make it - * invalid. - * - *

          This is a helper method. Use one of its overloads if possible. - * - * @param contractAnno a contract annotation, such as {@code @RequiresQualifier}, which has a - * {@code qualifier} element/field - * @param argumentAnno annotation containing the element {@code values}, or {@code null} - * @param argumentRenaming renaming of argument names, which maps from names in {@code - * argumentAnno} to names used in the returned annotation, or {@code null} - * @return a qualifier whose type is that of {@code contractAnno.qualifier}, or an alias for it, - * or null if it is not a supported qualifier of the type system - */ - private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( - AnnotationMirror contractAnno, - @Nullable AnnotationMirror argumentAnno, - @Nullable Map argumentRenaming) { + /** + * Returns the annotation mirror as specified by the "qualifier" element in {@code + * contractAnno}. If {@code argumentAnno} is specified, then elements/arguments are copied from + * {@code argumentAnno} to the returned annotation, renamed according to {@code + * argumentRenaming}. If {@code argumentAnno} is not specified, the result has no + * elements/arguments; this may make it invalid. + * + *

          This is a helper method. Use one of its overloads if possible. + * + * @param contractAnno a contract annotation, such as {@code @RequiresQualifier}, which has a + * {@code qualifier} element/field + * @param argumentAnno annotation containing the element {@code values}, or {@code null} + * @param argumentRenaming renaming of argument names, which maps from names in {@code + * argumentAnno} to names used in the returned annotation, or {@code null} + * @return a qualifier whose type is that of {@code contractAnno.qualifier}, or an alias for it, + * or null if it is not a supported qualifier of the type system + */ + private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( + AnnotationMirror contractAnno, + @Nullable AnnotationMirror argumentAnno, + @Nullable Map argumentRenaming) { - @SuppressWarnings("deprecation") // permitted for use in the framework - Name c = AnnotationUtils.getElementValueClassName(contractAnno, "qualifier", false); + @SuppressWarnings("deprecation") // permitted for use in the framework + Name c = AnnotationUtils.getElementValueClassName(contractAnno, "qualifier", false); - AnnotationMirror anno; - if (argumentAnno == null || argumentRenaming.isEmpty()) { - // If there are no arguments, use factory method that allows caching - anno = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), c); - } else { - AnnotationBuilder builder = new AnnotationBuilder(atypeFactory.getProcessingEnv(), c); - builder.copyRenameElementValuesFromAnnotation(argumentAnno, argumentRenaming); - anno = builder.build(); - } + AnnotationMirror anno; + if (argumentAnno == null || argumentRenaming.isEmpty()) { + // If there are no arguments, use factory method that allows caching + anno = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), c); + } else { + AnnotationBuilder builder = new AnnotationBuilder(atypeFactory.getProcessingEnv(), c); + builder.copyRenameElementValuesFromAnnotation(argumentAnno, argumentRenaming); + anno = builder.build(); + } - if (atypeFactory.isSupportedQualifier(anno)) { - return anno; - } else { - AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); - if (atypeFactory.isSupportedQualifier(aliasedAnno)) { - return aliasedAnno; - } else { - return null; - } + if (atypeFactory.isSupportedQualifier(anno)) { + return anno; + } else { + AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); + if (atypeFactory.isSupportedQualifier(aliasedAnno)) { + return aliasedAnno; + } else { + return null; + } + } } - } - /** - * Makes a map from element names of a contract annotation to qualifier argument names, as defined - * by {@link QualifierArgument}. - * - *

          Each element of {@code contractAnnoElement} that is annotated by {@link QualifierArgument} - * is mapped to the name specified by the value of {@link QualifierArgument}. If the value is not - * specified or is an empty string, then the element is mapped to an argument of the same name. - * - * @param contractAnnoElement the declaration of the contract annotation containing the elements - * @return map from the names of elements of {@code sourceArgumentNames} to the corresponding - * qualifier argument names - * @see QualifierArgument - */ - private Map makeArgumentRenaming(Element contractAnnoElement) { - HashMap argumentRenaming = new HashMap<>(); - for (ExecutableElement meth : - ElementFilter.methodsIn(contractAnnoElement.getEnclosedElements())) { - AnnotationMirror argumentAnnotation = - atypeFactory.getDeclAnnotation(meth, QualifierArgument.class); - if (argumentAnnotation != null) { - String sourceName = meth.getSimpleName().toString(); - String targetName = - AnnotationUtils.getElementValue( - argumentAnnotation, qualifierArgumentValueElement, String.class); - if (targetName == null || targetName.isEmpty()) { - targetName = sourceName; + /** + * Makes a map from element names of a contract annotation to qualifier argument names, as + * defined by {@link QualifierArgument}. + * + *

          Each element of {@code contractAnnoElement} that is annotated by {@link QualifierArgument} + * is mapped to the name specified by the value of {@link QualifierArgument}. If the value is + * not specified or is an empty string, then the element is mapped to an argument of the same + * name. + * + * @param contractAnnoElement the declaration of the contract annotation containing the elements + * @return map from the names of elements of {@code sourceArgumentNames} to the corresponding + * qualifier argument names + * @see QualifierArgument + */ + private Map makeArgumentRenaming(Element contractAnnoElement) { + HashMap argumentRenaming = new HashMap<>(); + for (ExecutableElement meth : + ElementFilter.methodsIn(contractAnnoElement.getEnclosedElements())) { + AnnotationMirror argumentAnnotation = + atypeFactory.getDeclAnnotation(meth, QualifierArgument.class); + if (argumentAnnotation != null) { + String sourceName = meth.getSimpleName().toString(); + String targetName = + AnnotationUtils.getElementValue( + argumentAnnotation, qualifierArgumentValueElement, String.class); + if (targetName == null || targetName.isEmpty()) { + targetName = sourceName; + } + argumentRenaming.put(sourceName, targetName); + } } - argumentRenaming.put(sourceName, targetName); - } + return argumentRenaming; } - return argumentRenaming; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java index 88223829a8d..ccb58e04cd7 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultQualifierKindHierarchy.java @@ -1,18 +1,5 @@ package org.checkerframework.framework.util; -import java.lang.annotation.Annotation; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.interning.qual.Interned; @@ -29,6 +16,20 @@ import org.checkerframework.javacutil.TypeSystemError; import org.plumelib.util.StringsPlume; +import java.lang.annotation.Annotation; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + /** * This is the default implementation of {@link QualifierKindHierarchy}. * @@ -52,785 +53,795 @@ @AnnotatedFor("nullness") public class DefaultQualifierKindHierarchy implements QualifierKindHierarchy { - /** - * A mapping from canonical name of a qualifier class to the QualifierKind object representing - * that class. - */ - protected final Map<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind; - - /** - * A list of all {@link QualifierKind}s for this DefaultQualifierKindHierarchy, sorted in - * ascending order. - */ - protected final List qualifierKinds; - - /** All the qualifier kinds that are the top qualifier in their hierarchy. */ - private final Set tops; - - /** All the qualifier kinds that are the bottom qualifier in their hierarchy. */ - private final Set bottoms; - - /** - * Holds the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 - * and kind2. - */ - private final Map> lubs; - - /** - * Holds the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 - * and kind2. - */ - private final Map> glbs; - - @Override - public Set getTops() { - return tops; - } - - @Override - public Set getBottoms() { - return bottoms; - } - - @Override - public @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2) { - @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in lubs. - QualifierKind result = lubs.get(q1).get(q2); - return result; - } - - @Override - public @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2) { - @SuppressWarnings("nullness:dereference.of.nullable") // All QualifierKinds are keys in glbs. - QualifierKind result = glbs.get(q1).get(q2); - return result; - } - - @Override - public List allQualifierKinds() { - return qualifierKinds; - } - - @Override - public QualifierKind getQualifierKind( - @UnknownInitialization(DefaultQualifierKindHierarchy.class) DefaultQualifierKindHierarchy this, - @CanonicalName String name) { - QualifierKind result = nameToQualifierKind.get(name); - if (result == null) { - throw new BugInCF("getQualifierKind(%s) => null", name); - } - return result; - } - - /** - * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its - * qualifier kinds. - * - * @param qualifierClasses all the classes of qualifiers supported by this hierarchy - */ - public DefaultQualifierKindHierarchy(Collection> qualifierClasses) { - this(qualifierClasses, null, null); - } - - /** - * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its - * qualifier kinds. - * - *

          For some type systems, qualifiers may be added at run time, so the {@link SubtypeOf} - * meta-annotation on the bottom qualifier class cannot specify all other qualifiers. For those - * type systems, use this constructor. Otherwise, use {@link - * #DefaultQualifierKindHierarchy(Collection)}. - * - * @param qualifierClasses all the classes of qualifiers supported by this hierarchy - * @param bottom the bottom qualifier of this hierarchy - */ - public DefaultQualifierKindHierarchy( - Collection> qualifierClasses, - Class bottom) { - this(qualifierClasses, bottom, null); - } - - /** - * Private constructor that sets the bottom qualifier if {@code bottom} is nonnull. - * - * @param qualifierClasses all the classes of qualifiers supported by this hierarchy - * @param bottom the bottom qualifier of this hierarchy or null if bottom can be inferred from the - * meta-annotations - * @param voidParam void parameter to differentiate from {@link - * #DefaultQualifierKindHierarchy(Collection, Class)} - */ - private DefaultQualifierKindHierarchy( - Collection> qualifierClasses, - @Nullable Class bottom, - @SuppressWarnings("UnusedVariable") Void voidParam) { - this.nameToQualifierKind = createQualifierKinds(qualifierClasses); - this.qualifierKinds = new ArrayList<>(nameToQualifierKind.values()); - Collections.sort(qualifierKinds); - - Map> directSuperMap = createDirectSuperMap(); - if (bottom != null) { - setBottom(bottom, directSuperMap); - } - this.tops = createTopsSet(directSuperMap); - this.bottoms = createBottomsSet(directSuperMap); - initializePolymorphicQualifiers(); - initializeQualifierKindFields(directSuperMap); - this.lubs = createLubsMap(); - this.glbs = createGlbsMap(); - - verifyHierarchy(directSuperMap); - } - - /** - * Verifies that the {@link DefaultQualifierKindHierarchy} is a valid hierarchy. - * - * @param directSuperMap mapping from qualifier to its direct supertypes; used to verify that a - * polymorphic annotation does not have a {@link SubtypeOf} meta-annotation - * @throws TypeSystemError if the hierarchy isn't valid - */ - @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) - protected void verifyHierarchy( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - boolean isPoly = qualifierKind.isPoly(); - boolean hasSubtypeOfAnno = directSuperMap.containsKey(qualifierKind); - if (isPoly && hasSubtypeOfAnno) { - // Polymorphic qualifiers with upper and lower bounds are currently not supported. - throw new TypeSystemError( - "AnnotatedTypeFactory: " - + qualifierKind - + " is polymorphic and specifies super qualifiers.%nRemove the" - + " @PolymorphicQualifier or @SubtypeOf annotation from it."); - } else if (!isPoly && !hasSubtypeOfAnno) { - throw new TypeSystemError( - "AnnotatedTypeFactory: %s does not specify its super qualifiers.%nAdd an" - + " @SubtypeOf or @PolymorphicQualifier annotation to it,%nor if it is" - + " an alias, exclude it from `createSupportedTypeQualifiers()`.", - qualifierKind); - } else if (isPoly) { - if (qualifierKind.top == null) { - throw new TypeSystemError( - "PolymorphicQualifier, %s, has to specify a type hierarchy in its" - + " @PolymorphicQualifier meta-annotation, if more than one exists;" - + " top types: [%s].", - qualifierKind, StringsPlume.join(", ", tops)); - } else if (!tops.contains(qualifierKind.top)) { - throw new TypeSystemError( - "Polymorphic qualifier %s has invalid top %s. Top qualifiers: %s", - qualifierKind, qualifierKind.top, StringsPlume.join(", ", tops)); - } - } - } + /** + * A mapping from canonical name of a qualifier class to the QualifierKind object representing + * that class. + */ + protected final Map<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind; - if (bottoms.size() != tops.size()) { - throw new TypeSystemError( - "Number of tops not equal to number of bottoms: Tops: [%s] Bottoms: [%s]", - StringsPlume.join(", ", tops), StringsPlume.join(", ", bottoms)); - } - } - - /** - * Creates all QualifierKind objects for the given qualifier classes and adds them to - * qualifierClassMap. This method does not initialize all fields in the {@link QualifierKind}; - * that is done by {@link #initializeQualifierKindFields(Map)}. - * - * @param qualifierClasses classes of annotations that are type qualifiers - * @return a mapping from the canonical name of an annotation class to {@link QualifierKind} - */ - protected Map<@Interned @CanonicalName String, DefaultQualifierKind> createQualifierKinds( - @UnderInitialization DefaultQualifierKindHierarchy this, - Collection> qualifierClasses) { - TreeMap<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind = - new TreeMap<>(); - for (Class clazz : qualifierClasses) { - @SuppressWarnings("interning") // uniqueness is tested immediately below - @Interned DefaultQualifierKind qualifierKind = new DefaultQualifierKind(clazz); - if (nameToQualifierKind.containsKey(qualifierKind.getName())) { - throw new TypeSystemError("Duplicate QualifierKind " + qualifierKind.getName()); - } - nameToQualifierKind.put(qualifierKind.getName(), qualifierKind); - } - return Collections.unmodifiableMap(nameToQualifierKind); - } - - /** - * Creates a mapping from a {@link QualifierKind} to a set of its direct super qualifier kinds. - * The direct super qualifier kinds do not contain the qualifier itself. This mapping is used to - * create the bottom set, to create the top set, and by {@link - * #initializeQualifierKindFields(Map)}. - * - *

          This implementation uses the {@link SubtypeOf} meta-annotation. Subclasses may override this - * method to create the direct super map some other way. - * - *

          Note that this method is called from the constructor when {@link #nameToQualifierKind} and - * {@link #qualifierKinds} are the only fields that have non-null values. This method is not - * static, so it can be overridden by subclasses. - * - * @return a mapping from each {@link QualifierKind} to a set of its direct super qualifiers - */ - @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) - protected Map> createDirectSuperMap( - @UnderInitialization DefaultQualifierKindHierarchy this) { - Map> directSuperMap = new TreeMap<>(); - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - SubtypeOf subtypeOfMetaAnno = - qualifierKind.getAnnotationClass().getAnnotation(SubtypeOf.class); - if (subtypeOfMetaAnno == null) { - // qualifierKind has no @SubtypeOf: it must be top or polymorphic - continue; - } - Set directSupers = new TreeSet<>(); - for (Class superClazz : subtypeOfMetaAnno.value()) { - String superName = QualifierKindHierarchy.annotationClassName(superClazz); - DefaultQualifierKind superQualifier = nameToQualifierKind.get(superName); - if (superQualifier == null) { - throw new TypeSystemError( - "In %s, @SubtypeOf(%s) argument isn't in the hierarchy." - + " Have you mis-defined createSupportedTypeQualifiers()? Qualifiers: [%s]", - qualifierKind, superName, StringsPlume.join(", ", qualifierKinds)); - } - directSupers.add(superQualifier); - } - directSuperMap.put(qualifierKind, directSupers); - } - return directSuperMap; - } - - /** - * This method sets bottom to the given class and modifies {@code directSuperMap} to add all - * leaves to its super qualifier kinds. Leaves are qualifier kinds that are not super qualifier - * kinds of another qualifier kind and are not polymorphic. - * - * @param bottom the class of the bottom qualifier in the hierarchy - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifiers; side-effected by this method - */ - @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) - private void setBottom( - @UnderInitialization DefaultQualifierKindHierarchy this, - Class bottom, - Map> directSuperMap) { - DefaultQualifierKind bottomKind = - nameToQualifierKind.get(QualifierKindHierarchy.annotationClassName(bottom)); - if (bottomKind == null) { - throw new TypeSystemError( - "QualifierKindHierarchy#setBottom: %s is not in the hierarchy", - bottom.getCanonicalName()); - } + /** + * A list of all {@link QualifierKind}s for this DefaultQualifierKindHierarchy, sorted in + * ascending order. + */ + protected final List qualifierKinds; - Set leaves = new TreeSet<>(qualifierKinds); - leaves.remove(bottomKind); - directSuperMap.forEach((sub, supers) -> leaves.removeAll(supers)); - Set bottomDirectSuperQuals = directSuperMap.get(bottomKind); - if (bottomDirectSuperQuals == null) { - directSuperMap.put(bottomKind, leaves); - } else { - bottomDirectSuperQuals.addAll(leaves); - } - } - - /** - * Creates the set of top {@link QualifierKind}s by searching {@code directSuperMap} for qualifier - * kinds without any direct super qualifier kinds. - * - *

          Subclasses should override {@link #createDirectSuperMap} to change the tops and not this - * method, because other methods expect the directSuperMap to be complete. - * - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - * @return the set of top {@link QualifierKind}s - */ - private Set createTopsSet( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - Set tops = new TreeSet<>(); - directSuperMap.forEach( - (qualifierKind, superQuals) -> { - if (superQuals.isEmpty()) { - tops.add(qualifierKind); - } - }); - return tops; - } - - /** - * Creates the set of bottom {@link QualifierKind}s by searching {@code directSuperMap} for - * qualifiers that are not a direct super qualifier kind of another qualifier kind. - * - *

          Subclasses should override {@link #createDirectSuperMap} or {@link #setBottom} to change the - * bottoms and not this method, because other methods expect the directSuperMap to be complete. - * - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - * @return the set of bottom {@link QualifierKind}s - */ - private Set createBottomsSet( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - Set bottoms = new HashSet<>(directSuperMap.keySet()); - for (Set superKinds : directSuperMap.values()) { - bottoms.removeAll(superKinds); - } - return bottoms; - } - - /** - * Iterates over all the qualifier kinds and adds all polymorphic qualifier kinds to - * polymorphicQualifiers. Also sets {@link DefaultQualifierKind#poly} and {@link - * DefaultQualifierKind#top} for the polymorphic qualifiers, and sets {@link - * DefaultQualifierKind#poly} for the top qualifiers. - * - *

          Requires that tops has been initialized. - */ - @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds", "this.tops"}) - protected void initializePolymorphicQualifiers( - @UnderInitialization DefaultQualifierKindHierarchy this) { - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - Class clazz = qualifierKind.getAnnotationClass(); - PolymorphicQualifier polyMetaAnno = clazz.getAnnotation(PolymorphicQualifier.class); - if (polyMetaAnno == null) { - continue; - } - qualifierKind.poly = qualifierKind; - String topName = QualifierKindHierarchy.annotationClassName(polyMetaAnno.value()); - if (nameToQualifierKind.containsKey(topName)) { - qualifierKind.top = nameToQualifierKind.get(topName); - } else if (topName.equals(Annotation.class.getCanonicalName())) { - // Annotation.class is the default value of PolymorphicQualifier. If it is used, - // then there must be exactly one top. - if (tops.size() == 1) { - qualifierKind.top = tops.iterator().next(); - } else { - throw new TypeSystemError( - "Polymorphic qualifier %s did not specify a top annotation class. Tops:" + " [%s]", - qualifierKind, StringsPlume.join(", ", tops)); - } - } else { - throw new TypeSystemError( - "Polymorphic qualifier %s's top, %s, is not a qualifier.", qualifierKind, topName); - } - qualifierKind.strictSuperTypes = Collections.singleton(qualifierKind.top); - qualifierKind.top.poly = qualifierKind; - } - } - - /** - * For each qualifier kind in {@code directSuperMap}, initializes {@link - * DefaultQualifierKind#strictSuperTypes}, {@link DefaultQualifierKind#top}, {@link - * DefaultQualifierKind#bottom}, and {@link DefaultQualifierKind#poly}. - * - *

          Requires tops, bottoms, and polymorphicQualifiers to be initialized. - * - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - */ - @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) - protected void initializeQualifierKindFields( - @UnderInitialization DefaultQualifierKindHierarchy this, - Map> directSuperMap) { - for (DefaultQualifierKind qualifierKind : directSuperMap.keySet()) { - if (!qualifierKind.isPoly()) { - qualifierKind.strictSuperTypes = findAllTheSupers(qualifierKind, directSuperMap); - } - } - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - for (DefaultQualifierKind top : tops) { - if (qualifierKind.isSubtypeOf(top)) { - if (qualifierKind.top == null) { - qualifierKind.top = top; - } else if (qualifierKind.top != top) { - throw new TypeSystemError( - "Multiple tops found for qualifier %s. Tops: %s and %s.", - qualifierKind, top, qualifierKind.top); - } - } - } - if (qualifierKind.top == null) { - throw new TypeSystemError( - "Qualifier %s isn't a subtype of any top. tops = %s", qualifierKind, tops); - } - qualifierKind.poly = qualifierKind.top.poly; - } - for (DefaultQualifierKind qualifierKind : qualifierKinds) { - for (DefaultQualifierKind bot : bottoms) { - if (bot.top != qualifierKind.top) { - continue; - } - if (qualifierKind.bottom == null) { - qualifierKind.bottom = bot; - } else if (qualifierKind.top != bot) { - throw new TypeSystemError( - "Multiple bottoms found for qualifier %s. Bottoms: %s and %s.", - qualifierKind, bot, qualifierKind.bottom); - } - if (qualifierKind.isPoly()) { - assert bot.strictSuperTypes != null - : "@AssumeAssertion(nullness): strictSuperTypes should be nonnull."; - bot.strictSuperTypes.add(qualifierKind); - } - } - if (qualifierKind.bottom == null) { - throw new TypeSystemError( - "Cannot find a bottom qualifier for %s. bottoms = %s", qualifierKind, bottoms); - } - } - } - - /** - * Returns the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}. - * - * @param qualifierKind the qualifier kind whose super types should be returned - * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super - * qualifier kinds; created by {@link #createDirectSuperMap()} - * @return the set of all qualifier kinds that are a strict supertype of {@code qualifierKind} - */ - private Set findAllTheSupers( - @UnderInitialization DefaultQualifierKindHierarchy this, - @KeyFor("#2") QualifierKind qualifierKind, - Map> directSuperMap) { - - Set allSupers = new TreeSet<>(directSuperMap.get(qualifierKind)); - - // Visit every super qualifier kind and add its super qualifier kinds to allSupers. - Queue toVisit = new ArrayDeque<>(directSuperMap.get(qualifierKind)); - Set visited = new HashSet<>(); - while (!toVisit.isEmpty()) { - DefaultQualifierKind superQualKind = toVisit.remove(); - if (superQualKind == qualifierKind) { - throw new TypeSystemError("Cycle in hierarchy: %s", qualifierKind); - } - - if (!visited.add(superQualKind) || superQualKind.isPoly()) { - continue; - } - - Set superSuperQuals = directSuperMap.get(superQualKind); - if (superSuperQuals == null) { - throw new TypeSystemError( - superQualKind - + " is not a key in the directSuperMap." - + " Does it have a @SubtypeOf annotation?"); - } - toVisit.addAll(superSuperQuals); - allSupers.addAll(superSuperQuals); - } - return allSupers; - } - - /** - * Creates the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 - * and kind2. - * - * @return a mapping of lubs - */ - @RequiresNonNull("this.qualifierKinds") - protected Map> createLubsMap( - @UnderInitialization DefaultQualifierKindHierarchy this) { - Map> lubs = new HashMap<>(); - for (QualifierKind qual1 : qualifierKinds) { - for (QualifierKind qual2 : qualifierKinds) { - if (qual1.getTop() != qual2.getTop()) { - continue; - } - QualifierKind lub = findLub(qual1, qual2); - addToMapOfMap(lubs, qual1, qual2, lub, "lub"); - addToMapOfMap(lubs, qual2, qual1, lub, "lub"); - } - } - return lubs; - } - - /** - * Returns the least upper bound of {@code qual1} and {@code qual2}. - * - * @param qual1 a qualifier kind - * @param qual2 a qualifier kind - * @return the least upper bound of {@code qual1} and {@code qual2} - */ - private QualifierKind findLub( - @UnderInitialization DefaultQualifierKindHierarchy this, - QualifierKind qual1, - QualifierKind qual2) { - if (qual1 == qual2) { - return qual1; - } else if (qual1.isSubtypeOf(qual2)) { - return qual2; - } else if (qual2.isSubtypeOf(qual1)) { - return qual1; - } - Set allSuperTypes = new TreeSet<>(qual1.getStrictSuperTypes()); - Set qual2StrictSuperTypes = qual2.getStrictSuperTypes(); - allSuperTypes.retainAll(qual2StrictSuperTypes); - Set lubs = findLowestQualifiers(allSuperTypes); - if (lubs.size() != 1) { - throw new TypeSystemError( - "lub(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", lubs)); - } - QualifierKind lub = lubs.iterator().next(); - if (lub.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { - throw new TypeSystemError("lub(%s, %s) can't be poly: %s", qual1, qual2, lub); - } - return lub; - } - - /** - * Returns the lowest qualifiers in the passed set. - * - * @param qualifierKinds a set of qualifiers - * @return the lowest qualifiers in the passed set - */ - protected static Set findLowestQualifiers(Set qualifierKinds) { - Set lowestQualifiers = new TreeSet<>(qualifierKinds); - for (QualifierKind a1 : qualifierKinds) { - lowestQualifiers.removeIf(a2 -> a1 != a2 && a1.isSubtypeOf(a2)); - } - return lowestQualifiers; - } - - /** - * Creates the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 - * and kind2. - * - * @return a mapping of glb - */ - @RequiresNonNull("this.qualifierKinds") - protected Map> createGlbsMap( - @UnderInitialization DefaultQualifierKindHierarchy this) { - Map> glbs = new TreeMap<>(); - for (QualifierKind qual1 : qualifierKinds) { - for (QualifierKind qual2 : qualifierKinds) { - if (qual1.getTop() != qual2.getTop()) { - continue; - } - QualifierKind glb = findGlb(qual1, qual2); - addToMapOfMap(glbs, qual1, qual2, glb, "glb"); - addToMapOfMap(glbs, qual2, qual1, glb, "glb"); - } - } - return glbs; - } - - /** - * Returns the greatest lower bound of {@code qual1} and {@code qual2}. - * - * @param qual1 a qualifier kind - * @param qual2 a qualifier kind - * @return the greatest lower bound of {@code qual1} and {@code qual2} - */ - @RequiresNonNull("this.qualifierKinds") - private QualifierKind findGlb( - @UnderInitialization DefaultQualifierKindHierarchy this, - QualifierKind qual1, - QualifierKind qual2) { - if (qual1 == qual2) { - return qual1; - } else if (qual1.isSubtypeOf(qual2)) { - return qual1; - } else if (qual2.isSubtypeOf(qual1)) { - return qual2; + /** All the qualifier kinds that are the top qualifier in their hierarchy. */ + private final Set tops; + + /** All the qualifier kinds that are the bottom qualifier in their hierarchy. */ + private final Set bottoms; + + /** + * Holds the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of kind1 + * and kind2. + */ + private final Map> lubs; + + /** + * Holds the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of kind1 + * and kind2. + */ + private final Map> glbs; + + @Override + public Set getTops() { + return tops; } - Set allSubTypes = new TreeSet<>(); - for (QualifierKind qualifierKind : qualifierKinds) { - if (qualifierKind.isSubtypeOf(qual1) && qualifierKind.isSubtypeOf(qual2)) { - allSubTypes.add(qualifierKind); - } + + @Override + public Set getBottoms() { + return bottoms; } - Set glbs = findHighestQualifiers(allSubTypes); - if (glbs.size() != 1) { - throw new TypeSystemError( - "glb(%s, %s) should have size 1: [%s]", qual1, qual2, StringsPlume.join(", ", glbs)); + + @Override + public @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2) { + @SuppressWarnings( + "nullness:dereference.of.nullable") // All QualifierKinds are keys in lubs. + QualifierKind result = lubs.get(q1).get(q2); + return result; } - QualifierKind glb = glbs.iterator().next(); - if (glb.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { - throw new TypeSystemError("glb(%s, %s) can't be poly: %s", qual1, qual2, glb); + + @Override + public @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2) { + @SuppressWarnings( + "nullness:dereference.of.nullable") // All QualifierKinds are keys in glbs. + QualifierKind result = glbs.get(q1).get(q2); + return result; } - return glb; - } - - /** - * Returns the highest qualifiers in the passed set. - * - * @param qualifierKinds a set of qualifiers - * @return the highest qualifiers in the passed set - */ - protected static Set findHighestQualifiers(Set qualifierKinds) { - Set highestQualifiers = new TreeSet<>(qualifierKinds); - for (QualifierKind a1 : qualifierKinds) { - highestQualifiers.removeIf(a2 -> a1 != a2 && a2.isSubtypeOf(a1)); + + @Override + public List allQualifierKinds() { + return qualifierKinds; } - return highestQualifiers; - } - - /** - * Add Key: qual1, Value: (Key: qual2, Value: value) to {@code map}. If already in map, throw an - * exception if value is different. - * - * @param map mapping to side-effect - * @param qual1 the first qualifier kind - * @param qual2 the second qualifier kind - * @param value the value to add - * @param operationName "lub" or "glb"; used only for error messages - */ - private static void addToMapOfMap( - Map> map, - QualifierKind qual1, - QualifierKind qual2, - QualifierKind value, - String operationName) { - Map qual1Map = map.computeIfAbsent(qual1, k -> new HashMap<>()); - QualifierKind existingValue = qual1Map.get(qual2); - if (existingValue == null) { - qual1Map.put(qual2, value); - } else { - if (existingValue != value) { - throw new TypeSystemError( - "Multiple %ss for qualifiers %s and %s. Found map %s and %s", - operationName, qual1, qual2, value, existingValue); - } + + @Override + public QualifierKind getQualifierKind( + @UnknownInitialization(DefaultQualifierKindHierarchy.class) DefaultQualifierKindHierarchy this, + @CanonicalName String name) { + QualifierKind result = nameToQualifierKind.get(name); + if (result == null) { + throw new BugInCF("getQualifierKind(%s) => null", name); + } + return result; } - } - /** - * The default implementation of {@link QualifierKind}. - * - *

          The fields in this class that refer to {@link QualifierKind}s are set when creating the - * {@link DefaultQualifierKindHierarchy}. So the getter methods for these fields should not be - * called until after the {@code DefaultQualifierKindHierarchy} is created. - */ - @AnnotatedFor("nullness") - public @Interned static class DefaultQualifierKind implements QualifierKind { + /** + * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its + * qualifier kinds. + * + * @param qualifierClasses all the classes of qualifiers supported by this hierarchy + */ + public DefaultQualifierKindHierarchy(Collection> qualifierClasses) { + this(qualifierClasses, null, null); + } - /** The canonical name of the annotation class of this. */ - private final @Interned @CanonicalName String name; + /** + * Creates a {@link DefaultQualifierKindHierarchy}. Also, creates and initializes all its + * qualifier kinds. + * + *

          For some type systems, qualifiers may be added at run time, so the {@link SubtypeOf} + * meta-annotation on the bottom qualifier class cannot specify all other qualifiers. For those + * type systems, use this constructor. Otherwise, use {@link + * #DefaultQualifierKindHierarchy(Collection)}. + * + * @param qualifierClasses all the classes of qualifiers supported by this hierarchy + * @param bottom the bottom qualifier of this hierarchy + */ + public DefaultQualifierKindHierarchy( + Collection> qualifierClasses, + Class bottom) { + this(qualifierClasses, bottom, null); + } - /** The annotation class for this. */ - private final Class clazz; + /** + * Private constructor that sets the bottom qualifier if {@code bottom} is nonnull. + * + * @param qualifierClasses all the classes of qualifiers supported by this hierarchy + * @param bottom the bottom qualifier of this hierarchy or null if bottom can be inferred from + * the meta-annotations + * @param voidParam void parameter to differentiate from {@link + * #DefaultQualifierKindHierarchy(Collection, Class)} + */ + private DefaultQualifierKindHierarchy( + Collection> qualifierClasses, + @Nullable Class bottom, + @SuppressWarnings("UnusedVariable") Void voidParam) { + this.nameToQualifierKind = createQualifierKinds(qualifierClasses); + this.qualifierKinds = new ArrayList<>(nameToQualifierKind.values()); + Collections.sort(qualifierKinds); + + Map> directSuperMap = + createDirectSuperMap(); + if (bottom != null) { + setBottom(bottom, directSuperMap); + } + this.tops = createTopsSet(directSuperMap); + this.bottoms = createBottomsSet(directSuperMap); + initializePolymorphicQualifiers(); + initializeQualifierKindFields(directSuperMap); + this.lubs = createLubsMap(); + this.glbs = createGlbsMap(); - /** True if the annotation class of this has annotation elements/arguments. */ - private final boolean hasElements; + verifyHierarchy(directSuperMap); + } - /** The top of the hierarchy to which this belongs. */ - // Set while creating the QualifierKindHierarchy. - protected @MonotonicNonNull DefaultQualifierKind top; + /** + * Verifies that the {@link DefaultQualifierKindHierarchy} is a valid hierarchy. + * + * @param directSuperMap mapping from qualifier to its direct supertypes; used to verify that a + * polymorphic annotation does not have a {@link SubtypeOf} meta-annotation + * @throws TypeSystemError if the hierarchy isn't valid + */ + @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) + protected void verifyHierarchy( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + boolean isPoly = qualifierKind.isPoly(); + boolean hasSubtypeOfAnno = directSuperMap.containsKey(qualifierKind); + if (isPoly && hasSubtypeOfAnno) { + // Polymorphic qualifiers with upper and lower bounds are currently not supported. + throw new TypeSystemError( + "AnnotatedTypeFactory: " + + qualifierKind + + " is polymorphic and specifies super qualifiers.%nRemove the" + + " @PolymorphicQualifier or @SubtypeOf annotation from it."); + } else if (!isPoly && !hasSubtypeOfAnno) { + throw new TypeSystemError( + "AnnotatedTypeFactory: %s does not specify its super qualifiers.%nAdd an" + + " @SubtypeOf or @PolymorphicQualifier annotation to it,%nor if it is" + + " an alias, exclude it from `createSupportedTypeQualifiers()`.", + qualifierKind); + } else if (isPoly) { + if (qualifierKind.top == null) { + throw new TypeSystemError( + "PolymorphicQualifier, %s, has to specify a type hierarchy in its" + + " @PolymorphicQualifier meta-annotation, if more than one exists;" + + " top types: [%s].", + qualifierKind, StringsPlume.join(", ", tops)); + } else if (!tops.contains(qualifierKind.top)) { + throw new TypeSystemError( + "Polymorphic qualifier %s has invalid top %s. Top qualifiers: %s", + qualifierKind, qualifierKind.top, StringsPlume.join(", ", tops)); + } + } + } - /** The bottom of the hierarchy to which this belongs. */ - // Set while creating the QualifierKindHierarchy. - protected @MonotonicNonNull DefaultQualifierKind bottom; + if (bottoms.size() != tops.size()) { + throw new TypeSystemError( + "Number of tops not equal to number of bottoms: Tops: [%s] Bottoms: [%s]", + StringsPlume.join(", ", tops), StringsPlume.join(", ", bottoms)); + } + } - /** The polymorphic qualifier of the hierarchy to which this belongs. */ - // Set while creating the QualifierKindHierarchy. - protected @Nullable DefaultQualifierKind poly; + /** + * Creates all QualifierKind objects for the given qualifier classes and adds them to + * qualifierClassMap. This method does not initialize all fields in the {@link QualifierKind}; + * that is done by {@link #initializeQualifierKindFields(Map)}. + * + * @param qualifierClasses classes of annotations that are type qualifiers + * @return a mapping from the canonical name of an annotation class to {@link QualifierKind} + */ + protected Map<@Interned @CanonicalName String, DefaultQualifierKind> createQualifierKinds( + @UnderInitialization DefaultQualifierKindHierarchy this, + Collection> qualifierClasses) { + TreeMap<@Interned @CanonicalName String, DefaultQualifierKind> nameToQualifierKind = + new TreeMap<>(); + for (Class clazz : qualifierClasses) { + @SuppressWarnings("interning") // uniqueness is tested immediately below + @Interned DefaultQualifierKind qualifierKind = new DefaultQualifierKind(clazz); + if (nameToQualifierKind.containsKey(qualifierKind.getName())) { + throw new TypeSystemError("Duplicate QualifierKind " + qualifierKind.getName()); + } + nameToQualifierKind.put(qualifierKind.getName(), qualifierKind); + } + return Collections.unmodifiableMap(nameToQualifierKind); + } /** - * All the qualifier kinds that are a strict super qualifier kind of this. Does not include this - * qualifier kind itself. + * Creates a mapping from a {@link QualifierKind} to a set of its direct super qualifier kinds. + * The direct super qualifier kinds do not contain the qualifier itself. This mapping is used to + * create the bottom set, to create the top set, and by {@link + * #initializeQualifierKindFields(Map)}. + * + *

          This implementation uses the {@link SubtypeOf} meta-annotation. Subclasses may override + * this method to create the direct super map some other way. + * + *

          Note that this method is called from the constructor when {@link #nameToQualifierKind} and + * {@link #qualifierKinds} are the only fields that have non-null values. This method is not + * static, so it can be overridden by subclasses. + * + * @return a mapping from each {@link QualifierKind} to a set of its direct super qualifiers */ - // Set while creating the QualifierKindHierarchy. - protected @MonotonicNonNull Set strictSuperTypes; + @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) + protected Map> createDirectSuperMap( + @UnderInitialization DefaultQualifierKindHierarchy this) { + Map> directSuperMap = new TreeMap<>(); + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + SubtypeOf subtypeOfMetaAnno = + qualifierKind.getAnnotationClass().getAnnotation(SubtypeOf.class); + if (subtypeOfMetaAnno == null) { + // qualifierKind has no @SubtypeOf: it must be top or polymorphic + continue; + } + Set directSupers = new TreeSet<>(); + for (Class superClazz : subtypeOfMetaAnno.value()) { + String superName = QualifierKindHierarchy.annotationClassName(superClazz); + DefaultQualifierKind superQualifier = nameToQualifierKind.get(superName); + if (superQualifier == null) { + throw new TypeSystemError( + "In %s, @SubtypeOf(%s) argument isn't in the hierarchy." + + " Have you mis-defined createSupportedTypeQualifiers()? Qualifiers: [%s]", + qualifierKind, superName, StringsPlume.join(", ", qualifierKinds)); + } + directSupers.add(superQualifier); + } + directSuperMap.put(qualifierKind, directSupers); + } + return directSuperMap; + } /** - * Creates a {@link DefaultQualifierKind} for the given annotation class. + * This method sets bottom to the given class and modifies {@code directSuperMap} to add all + * leaves to its super qualifier kinds. Leaves are qualifier kinds that are not super qualifier + * kinds of another qualifier kind and are not polymorphic. * - * @param clazz annotation class for a qualifier + * @param bottom the class of the bottom qualifier in the hierarchy + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifiers; side-effected by this method */ - DefaultQualifierKind(Class clazz) { - this.clazz = clazz; - this.hasElements = clazz.getDeclaredMethods().length != 0; - this.name = QualifierKindHierarchy.annotationClassName(clazz).intern(); - this.poly = null; + @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds"}) + private void setBottom( + @UnderInitialization DefaultQualifierKindHierarchy this, + Class bottom, + Map> directSuperMap) { + DefaultQualifierKind bottomKind = + nameToQualifierKind.get(QualifierKindHierarchy.annotationClassName(bottom)); + if (bottomKind == null) { + throw new TypeSystemError( + "QualifierKindHierarchy#setBottom: %s is not in the hierarchy", + bottom.getCanonicalName()); + } + + Set leaves = new TreeSet<>(qualifierKinds); + leaves.remove(bottomKind); + directSuperMap.forEach((sub, supers) -> leaves.removeAll(supers)); + Set bottomDirectSuperQuals = directSuperMap.get(bottomKind); + if (bottomDirectSuperQuals == null) { + directSuperMap.put(bottomKind, leaves); + } else { + bottomDirectSuperQuals.addAll(leaves); + } } - @Override - public @Interned @CanonicalName String getName() { - return name; + /** + * Creates the set of top {@link QualifierKind}s by searching {@code directSuperMap} for + * qualifier kinds without any direct super qualifier kinds. + * + *

          Subclasses should override {@link #createDirectSuperMap} to change the tops and not this + * method, because other methods expect the directSuperMap to be complete. + * + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + * @return the set of top {@link QualifierKind}s + */ + private Set createTopsSet( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + Set tops = new TreeSet<>(); + directSuperMap.forEach( + (qualifierKind, superQuals) -> { + if (superQuals.isEmpty()) { + tops.add(qualifierKind); + } + }); + return tops; } - @Override - public Class getAnnotationClass() { - return clazz; + /** + * Creates the set of bottom {@link QualifierKind}s by searching {@code directSuperMap} for + * qualifiers that are not a direct super qualifier kind of another qualifier kind. + * + *

          Subclasses should override {@link #createDirectSuperMap} or {@link #setBottom} to change + * the bottoms and not this method, because other methods expect the directSuperMap to be + * complete. + * + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + * @return the set of bottom {@link QualifierKind}s + */ + private Set createBottomsSet( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + Set bottoms = new HashSet<>(directSuperMap.keySet()); + for (Set superKinds : directSuperMap.values()) { + bottoms.removeAll(superKinds); + } + return bottoms; } - @Override - public QualifierKind getTop() { - if (top == null) { - throw new BugInCF( - "DefaultQualifierKindHierarchy#getTop: Top is null for QualifierKind %s." - + " Don't call this method during initialization of" - + " DefaultQualifierKindHierarchy.", - name); - } - return top; + /** + * Iterates over all the qualifier kinds and adds all polymorphic qualifier kinds to + * polymorphicQualifiers. Also sets {@link DefaultQualifierKind#poly} and {@link + * DefaultQualifierKind#top} for the polymorphic qualifiers, and sets {@link + * DefaultQualifierKind#poly} for the top qualifiers. + * + *

          Requires that tops has been initialized. + */ + @RequiresNonNull({"this.nameToQualifierKind", "this.qualifierKinds", "this.tops"}) + protected void initializePolymorphicQualifiers( + @UnderInitialization DefaultQualifierKindHierarchy this) { + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + Class clazz = qualifierKind.getAnnotationClass(); + PolymorphicQualifier polyMetaAnno = clazz.getAnnotation(PolymorphicQualifier.class); + if (polyMetaAnno == null) { + continue; + } + qualifierKind.poly = qualifierKind; + String topName = QualifierKindHierarchy.annotationClassName(polyMetaAnno.value()); + if (nameToQualifierKind.containsKey(topName)) { + qualifierKind.top = nameToQualifierKind.get(topName); + } else if (topName.equals(Annotation.class.getCanonicalName())) { + // Annotation.class is the default value of PolymorphicQualifier. If it is used, + // then there must be exactly one top. + if (tops.size() == 1) { + qualifierKind.top = tops.iterator().next(); + } else { + throw new TypeSystemError( + "Polymorphic qualifier %s did not specify a top annotation class. Tops:" + + " [%s]", + qualifierKind, StringsPlume.join(", ", tops)); + } + } else { + throw new TypeSystemError( + "Polymorphic qualifier %s's top, %s, is not a qualifier.", + qualifierKind, topName); + } + qualifierKind.strictSuperTypes = Collections.singleton(qualifierKind.top); + qualifierKind.top.poly = qualifierKind; + } } - @Override - public boolean isTop() { - return this.top == this; + /** + * For each qualifier kind in {@code directSuperMap}, initializes {@link + * DefaultQualifierKind#strictSuperTypes}, {@link DefaultQualifierKind#top}, {@link + * DefaultQualifierKind#bottom}, and {@link DefaultQualifierKind#poly}. + * + *

          Requires tops, bottoms, and polymorphicQualifiers to be initialized. + * + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + */ + @RequiresNonNull({"this.qualifierKinds", "this.tops", "this.bottoms"}) + protected void initializeQualifierKindFields( + @UnderInitialization DefaultQualifierKindHierarchy this, + Map> directSuperMap) { + for (DefaultQualifierKind qualifierKind : directSuperMap.keySet()) { + if (!qualifierKind.isPoly()) { + qualifierKind.strictSuperTypes = findAllTheSupers(qualifierKind, directSuperMap); + } + } + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + for (DefaultQualifierKind top : tops) { + if (qualifierKind.isSubtypeOf(top)) { + if (qualifierKind.top == null) { + qualifierKind.top = top; + } else if (qualifierKind.top != top) { + throw new TypeSystemError( + "Multiple tops found for qualifier %s. Tops: %s and %s.", + qualifierKind, top, qualifierKind.top); + } + } + } + if (qualifierKind.top == null) { + throw new TypeSystemError( + "Qualifier %s isn't a subtype of any top. tops = %s", qualifierKind, tops); + } + qualifierKind.poly = qualifierKind.top.poly; + } + for (DefaultQualifierKind qualifierKind : qualifierKinds) { + for (DefaultQualifierKind bot : bottoms) { + if (bot.top != qualifierKind.top) { + continue; + } + if (qualifierKind.bottom == null) { + qualifierKind.bottom = bot; + } else if (qualifierKind.top != bot) { + throw new TypeSystemError( + "Multiple bottoms found for qualifier %s. Bottoms: %s and %s.", + qualifierKind, bot, qualifierKind.bottom); + } + if (qualifierKind.isPoly()) { + assert bot.strictSuperTypes != null + : "@AssumeAssertion(nullness): strictSuperTypes should be nonnull."; + bot.strictSuperTypes.add(qualifierKind); + } + } + if (qualifierKind.bottom == null) { + throw new TypeSystemError( + "Cannot find a bottom qualifier for %s. bottoms = %s", + qualifierKind, bottoms); + } + } } - @Override - public QualifierKind getBottom() { - if (bottom == null) { - throw new BugInCF( - "DefaultQualifierKind#getBottom:Bottom is null for QualifierKind %s. Don't" - + " call this method during initialization of" - + " DefaultQualifierKindHierarchy.", - name); - } - return bottom; + /** + * Returns the set of all qualifier kinds that are a strict supertype of {@code qualifierKind}. + * + * @param qualifierKind the qualifier kind whose super types should be returned + * @param directSuperMap a mapping from a {@link QualifierKind} to a set of its direct super + * qualifier kinds; created by {@link #createDirectSuperMap()} + * @return the set of all qualifier kinds that are a strict supertype of {@code qualifierKind} + */ + private Set findAllTheSupers( + @UnderInitialization DefaultQualifierKindHierarchy this, + @KeyFor("#2") QualifierKind qualifierKind, + Map> directSuperMap) { + + Set allSupers = new TreeSet<>(directSuperMap.get(qualifierKind)); + + // Visit every super qualifier kind and add its super qualifier kinds to allSupers. + Queue toVisit = new ArrayDeque<>(directSuperMap.get(qualifierKind)); + Set visited = new HashSet<>(); + while (!toVisit.isEmpty()) { + DefaultQualifierKind superQualKind = toVisit.remove(); + if (superQualKind == qualifierKind) { + throw new TypeSystemError("Cycle in hierarchy: %s", qualifierKind); + } + + if (!visited.add(superQualKind) || superQualKind.isPoly()) { + continue; + } + + Set superSuperQuals = directSuperMap.get(superQualKind); + if (superSuperQuals == null) { + throw new TypeSystemError( + superQualKind + + " is not a key in the directSuperMap." + + " Does it have a @SubtypeOf annotation?"); + } + toVisit.addAll(superSuperQuals); + allSupers.addAll(superSuperQuals); + } + return allSupers; } - @Override - public boolean isBottom() { - return this.bottom == this; + /** + * Creates the lub of qualifier kinds. {@code lubs.get(kind1).get(kind2)} returns the lub of + * kind1 and kind2. + * + * @return a mapping of lubs + */ + @RequiresNonNull("this.qualifierKinds") + protected Map> createLubsMap( + @UnderInitialization DefaultQualifierKindHierarchy this) { + Map> lubs = new HashMap<>(); + for (QualifierKind qual1 : qualifierKinds) { + for (QualifierKind qual2 : qualifierKinds) { + if (qual1.getTop() != qual2.getTop()) { + continue; + } + QualifierKind lub = findLub(qual1, qual2); + addToMapOfMap(lubs, qual1, qual2, lub, "lub"); + addToMapOfMap(lubs, qual2, qual1, lub, "lub"); + } + } + return lubs; } - @Override - public @Nullable QualifierKind getPolymorphic() { - return poly; + /** + * Returns the least upper bound of {@code qual1} and {@code qual2}. + * + * @param qual1 a qualifier kind + * @param qual2 a qualifier kind + * @return the least upper bound of {@code qual1} and {@code qual2} + */ + private QualifierKind findLub( + @UnderInitialization DefaultQualifierKindHierarchy this, + QualifierKind qual1, + QualifierKind qual2) { + if (qual1 == qual2) { + return qual1; + } else if (qual1.isSubtypeOf(qual2)) { + return qual2; + } else if (qual2.isSubtypeOf(qual1)) { + return qual1; + } + Set allSuperTypes = new TreeSet<>(qual1.getStrictSuperTypes()); + Set qual2StrictSuperTypes = qual2.getStrictSuperTypes(); + allSuperTypes.retainAll(qual2StrictSuperTypes); + Set lubs = findLowestQualifiers(allSuperTypes); + if (lubs.size() != 1) { + throw new TypeSystemError( + "lub(%s, %s) should have size 1: [%s]", + qual1, qual2, StringsPlume.join(", ", lubs)); + } + QualifierKind lub = lubs.iterator().next(); + if (lub.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { + throw new TypeSystemError("lub(%s, %s) can't be poly: %s", qual1, qual2, lub); + } + return lub; } - @Pure - @Override - public boolean isPoly() { - return this.poly == this; + /** + * Returns the lowest qualifiers in the passed set. + * + * @param qualifierKinds a set of qualifiers + * @return the lowest qualifiers in the passed set + */ + protected static Set findLowestQualifiers(Set qualifierKinds) { + Set lowestQualifiers = new TreeSet<>(qualifierKinds); + for (QualifierKind a1 : qualifierKinds) { + lowestQualifiers.removeIf(a2 -> a1 != a2 && a1.isSubtypeOf(a2)); + } + return lowestQualifiers; } - @Override - public boolean hasElements() { - return hasElements; + /** + * Creates the glb of qualifier kinds. {@code glbs.get(kind1).get(kind2)} returns the glb of + * kind1 and kind2. + * + * @return a mapping of glb + */ + @RequiresNonNull("this.qualifierKinds") + protected Map> createGlbsMap( + @UnderInitialization DefaultQualifierKindHierarchy this) { + Map> glbs = new TreeMap<>(); + for (QualifierKind qual1 : qualifierKinds) { + for (QualifierKind qual2 : qualifierKinds) { + if (qual1.getTop() != qual2.getTop()) { + continue; + } + QualifierKind glb = findGlb(qual1, qual2); + addToMapOfMap(glbs, qual1, qual2, glb, "glb"); + addToMapOfMap(glbs, qual2, qual1, glb, "glb"); + } + } + return glbs; } - @Override - public Set getStrictSuperTypes() { - if (strictSuperTypes == null) { - throw new BugInCF( - "DefaultQualifierKind#getStrictSuperTypes: strictSuperTypes was null. Don't" - + " call this method during initialization of" - + " DefaultQualifierKindHierarchy."); - } - return strictSuperTypes; + /** + * Returns the greatest lower bound of {@code qual1} and {@code qual2}. + * + * @param qual1 a qualifier kind + * @param qual2 a qualifier kind + * @return the greatest lower bound of {@code qual1} and {@code qual2} + */ + @RequiresNonNull("this.qualifierKinds") + private QualifierKind findGlb( + @UnderInitialization DefaultQualifierKindHierarchy this, + QualifierKind qual1, + QualifierKind qual2) { + if (qual1 == qual2) { + return qual1; + } else if (qual1.isSubtypeOf(qual2)) { + return qual1; + } else if (qual2.isSubtypeOf(qual1)) { + return qual2; + } + Set allSubTypes = new TreeSet<>(); + for (QualifierKind qualifierKind : qualifierKinds) { + if (qualifierKind.isSubtypeOf(qual1) && qualifierKind.isSubtypeOf(qual2)) { + allSubTypes.add(qualifierKind); + } + } + Set glbs = findHighestQualifiers(allSubTypes); + if (glbs.size() != 1) { + throw new TypeSystemError( + "glb(%s, %s) should have size 1: [%s]", + qual1, qual2, StringsPlume.join(", ", glbs)); + } + QualifierKind glb = glbs.iterator().next(); + if (glb.isPoly() && !qual1.isPoly() && !qual2.isPoly()) { + throw new TypeSystemError("glb(%s, %s) can't be poly: %s", qual1, qual2, glb); + } + return glb; } - @Override - public boolean isInSameHierarchyAs(QualifierKind other) { - return this.top == other.getTop(); + /** + * Returns the highest qualifiers in the passed set. + * + * @param qualifierKinds a set of qualifiers + * @return the highest qualifiers in the passed set + */ + protected static Set findHighestQualifiers(Set qualifierKinds) { + Set highestQualifiers = new TreeSet<>(qualifierKinds); + for (QualifierKind a1 : qualifierKinds) { + highestQualifiers.removeIf(a2 -> a1 != a2 && a2.isSubtypeOf(a1)); + } + return highestQualifiers; } - @Override - public boolean isSubtypeOf(QualifierKind superQualKind) { - if (strictSuperTypes == null) { - throw new BugInCF( - "DefaultQualifierKind#isSubtypeOf: strictSuperTypes was null. Don't call" - + " this method during initialization of" - + " DefaultQualifierKindHierarchy."); - } - return this == superQualKind || strictSuperTypes.contains(superQualKind); + /** + * Add Key: qual1, Value: (Key: qual2, Value: value) to {@code map}. If already in map, throw an + * exception if value is different. + * + * @param map mapping to side-effect + * @param qual1 the first qualifier kind + * @param qual2 the second qualifier kind + * @param value the value to add + * @param operationName "lub" or "glb"; used only for error messages + */ + private static void addToMapOfMap( + Map> map, + QualifierKind qual1, + QualifierKind qual2, + QualifierKind value, + String operationName) { + Map qual1Map = + map.computeIfAbsent(qual1, k -> new HashMap<>()); + QualifierKind existingValue = qual1Map.get(qual2); + if (existingValue == null) { + qual1Map.put(qual2, value); + } else { + if (existingValue != value) { + throw new TypeSystemError( + "Multiple %ss for qualifiers %s and %s. Found map %s and %s", + operationName, qual1, qual2, value, existingValue); + } + } } - @Override - public String toString() { - return clazz.getSimpleName(); + /** + * The default implementation of {@link QualifierKind}. + * + *

          The fields in this class that refer to {@link QualifierKind}s are set when creating the + * {@link DefaultQualifierKindHierarchy}. So the getter methods for these fields should not be + * called until after the {@code DefaultQualifierKindHierarchy} is created. + */ + @AnnotatedFor("nullness") + public @Interned static class DefaultQualifierKind implements QualifierKind { + + /** The canonical name of the annotation class of this. */ + private final @Interned @CanonicalName String name; + + /** The annotation class for this. */ + private final Class clazz; + + /** True if the annotation class of this has annotation elements/arguments. */ + private final boolean hasElements; + + /** The top of the hierarchy to which this belongs. */ + // Set while creating the QualifierKindHierarchy. + protected @MonotonicNonNull DefaultQualifierKind top; + + /** The bottom of the hierarchy to which this belongs. */ + // Set while creating the QualifierKindHierarchy. + protected @MonotonicNonNull DefaultQualifierKind bottom; + + /** The polymorphic qualifier of the hierarchy to which this belongs. */ + // Set while creating the QualifierKindHierarchy. + protected @Nullable DefaultQualifierKind poly; + + /** + * All the qualifier kinds that are a strict super qualifier kind of this. Does not include + * this qualifier kind itself. + */ + // Set while creating the QualifierKindHierarchy. + protected @MonotonicNonNull Set strictSuperTypes; + + /** + * Creates a {@link DefaultQualifierKind} for the given annotation class. + * + * @param clazz annotation class for a qualifier + */ + DefaultQualifierKind(Class clazz) { + this.clazz = clazz; + this.hasElements = clazz.getDeclaredMethods().length != 0; + this.name = QualifierKindHierarchy.annotationClassName(clazz).intern(); + this.poly = null; + } + + @Override + public @Interned @CanonicalName String getName() { + return name; + } + + @Override + public Class getAnnotationClass() { + return clazz; + } + + @Override + public QualifierKind getTop() { + if (top == null) { + throw new BugInCF( + "DefaultQualifierKindHierarchy#getTop: Top is null for QualifierKind %s." + + " Don't call this method during initialization of" + + " DefaultQualifierKindHierarchy.", + name); + } + return top; + } + + @Override + public boolean isTop() { + return this.top == this; + } + + @Override + public QualifierKind getBottom() { + if (bottom == null) { + throw new BugInCF( + "DefaultQualifierKind#getBottom:Bottom is null for QualifierKind %s. Don't" + + " call this method during initialization of" + + " DefaultQualifierKindHierarchy.", + name); + } + return bottom; + } + + @Override + public boolean isBottom() { + return this.bottom == this; + } + + @Override + public @Nullable QualifierKind getPolymorphic() { + return poly; + } + + @Pure + @Override + public boolean isPoly() { + return this.poly == this; + } + + @Override + public boolean hasElements() { + return hasElements; + } + + @Override + public Set getStrictSuperTypes() { + if (strictSuperTypes == null) { + throw new BugInCF( + "DefaultQualifierKind#getStrictSuperTypes: strictSuperTypes was null. Don't" + + " call this method during initialization of" + + " DefaultQualifierKindHierarchy."); + } + return strictSuperTypes; + } + + @Override + public boolean isInSameHierarchyAs(QualifierKind other) { + return this.top == other.getTop(); + } + + @Override + public boolean isSubtypeOf(QualifierKind superQualKind) { + if (strictSuperTypes == null) { + throw new BugInCF( + "DefaultQualifierKind#isSubtypeOf: strictSuperTypes was null. Don't call" + + " this method during initialization of" + + " DefaultQualifierKindHierarchy."); + } + return this == superQualKind || strictSuperTypes.contains(superQualKind); + } + + @Override + public String toString() { + return clazz.getSimpleName(); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java index 21f890cbdeb..0e128e79729 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java @@ -10,78 +10,78 @@ /** Utilities for executing external processes. */ public class ExecUtil { - public static int execute(String[] cmd, OutputStream std, OutputStream err) { + public static int execute(String[] cmd, OutputStream std, OutputStream err) { - Redirection outRedirect = new Redirection(std, BLOCK_SIZE); - Redirection errRedirect = new Redirection(err, BLOCK_SIZE); + Redirection outRedirect = new Redirection(std, BLOCK_SIZE); + Redirection errRedirect = new Redirection(err, BLOCK_SIZE); - try { - Process proc = Runtime.getRuntime().exec(cmd); - outRedirect.redirect(proc.getInputStream()); - errRedirect.redirect(proc.getErrorStream()); + try { + Process proc = Runtime.getRuntime().exec(cmd); + outRedirect.redirect(proc.getInputStream()); + errRedirect.redirect(proc.getErrorStream()); - IOException stdExc = outRedirect.join(); - IOException errExc = errRedirect.join(); - int exitStatus = proc.waitFor(); + IOException stdExc = outRedirect.join(); + IOException errExc = errRedirect.join(); + int exitStatus = proc.waitFor(); - if (stdExc != null) { - throw stdExc; - } + if (stdExc != null) { + throw stdExc; + } - if (errExc != null) { - throw errExc; - } + if (errExc != null) { + throw errExc; + } - return exitStatus; + return exitStatus; - } catch (InterruptedException e) { - throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); - } catch (IOException e) { - throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); + } catch (InterruptedException e) { + throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); + } catch (IOException e) { + throw new RuntimeException("Exception executing command: " + String.join(" ", cmd), e); + } } - } - public static final int BLOCK_SIZE = 1024; - - public static class Redirection { - private final char[] buffer; - private final OutputStreamWriter out; - - private Thread thread; - private IOException exception; - - public Redirection(OutputStream out, int bufferSize) { - this.buffer = new char[bufferSize]; - this.out = new OutputStreamWriter(out, StandardCharsets.UTF_8); - } - - public void redirect(InputStream inStream) { - - exception = null; - - this.thread = - new Thread( - () -> { - try (InputStreamReader in = - new InputStreamReader(inStream, StandardCharsets.UTF_8)) { - int read = 0; - while (read > -1) { - read = in.read(buffer); - if (read > 0) { - out.write(buffer, 0, read); - } - } - out.flush(); - } catch (IOException exc) { - exception = exc; - } - }); - thread.start(); - } - - public IOException join() throws InterruptedException { - thread.join(); - return exception; + public static final int BLOCK_SIZE = 1024; + + public static class Redirection { + private final char[] buffer; + private final OutputStreamWriter out; + + private Thread thread; + private IOException exception; + + public Redirection(OutputStream out, int bufferSize) { + this.buffer = new char[bufferSize]; + this.out = new OutputStreamWriter(out, StandardCharsets.UTF_8); + } + + public void redirect(InputStream inStream) { + + exception = null; + + this.thread = + new Thread( + () -> { + try (InputStreamReader in = + new InputStreamReader(inStream, StandardCharsets.UTF_8)) { + int read = 0; + while (read > -1) { + read = in.read(buffer); + if (read > 0) { + out.write(buffer, 0, read); + } + } + out.flush(); + } catch (IOException exc) { + exception = exc; + } + }); + thread.start(); + } + + public IOException join() throws InterruptedException { + thread.join(); + return exception; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java b/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java index 655ea3a81ca..779326267f9 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java +++ b/framework/src/main/java/org/checkerframework/framework/util/FieldInvariants.java @@ -1,15 +1,17 @@ package org.checkerframework.framework.util; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.source.DiagMessage; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.javacutil.BugInCF; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; + /** * Represents field invariants, which the user states by writing {@code @FieldInvariant}. Think of * this as a set of (field name, qualifier) pairs. @@ -19,130 +21,134 @@ */ public class FieldInvariants { - /** - * A list of simple field names. A field may appear more than once in this list. This list has the - * same length as {@link #qualifiers}. - */ - private final List fields; - - /** - * A list of qualifiers that apply to the field at the same index in {@link #fields}. In a - * well-formed FieldInvariants, has the same length as {@link #fields}. - */ - private final List qualifiers; + /** + * A list of simple field names. A field may appear more than once in this list. This list has + * the same length as {@link #qualifiers}. + */ + private final List fields; - /** The type factory associated with this. */ - private final AnnotatedTypeFactory atypeFactory; + /** + * A list of qualifiers that apply to the field at the same index in {@link #fields}. In a + * well-formed FieldInvariants, has the same length as {@link #fields}. + */ + private final List qualifiers; - /** - * Creates a new FieldInvariants object. The result is well-formed if the length of qualifiers is - * either 1 or equal to length of {@code fields}. - * - * @param fields list of fields - * @param qualifiers list of qualifiers, or a single qualifier that applies to all fields - * @param atypeFactory the type factory - */ - public FieldInvariants( - List fields, List qualifiers, AnnotatedTypeFactory atypeFactory) { - this(null, fields, qualifiers, atypeFactory); - } + /** The type factory associated with this. */ + private final AnnotatedTypeFactory atypeFactory; - /** - * Creates a new object with all the invariants in {@code other}, plus those specified by {@code - * fields} and {@code qualifiers}. The result is well-formed if the length of qualifiers is either - * 1 or equal to length of {@code fields}. - * - * @param other other invariant object, may be null - * @param fields list of fields - * @param qualifiers list of qualifiers - * @param atypeFactory the type factory - */ - public FieldInvariants( - @Nullable FieldInvariants other, - List fields, - List qualifiers, - AnnotatedTypeFactory atypeFactory) { - if (qualifiers.size() == 1) { - while (fields.size() > qualifiers.size()) { - qualifiers.add(qualifiers.get(0)); - } - } - if (other != null) { - fields.addAll(other.fields); - qualifiers.addAll(other.qualifiers); + /** + * Creates a new FieldInvariants object. The result is well-formed if the length of qualifiers + * is either 1 or equal to length of {@code fields}. + * + * @param fields list of fields + * @param qualifiers list of qualifiers, or a single qualifier that applies to all fields + * @param atypeFactory the type factory + */ + public FieldInvariants( + List fields, + List qualifiers, + AnnotatedTypeFactory atypeFactory) { + this(null, fields, qualifiers, atypeFactory); } - this.fields = Collections.unmodifiableList(fields); - this.qualifiers = qualifiers; - this.atypeFactory = atypeFactory; - } - - /** The simple names of the fields that have a qualifier. May contain duplicates. */ - public List getFields() { - return fields; - } + /** + * Creates a new object with all the invariants in {@code other}, plus those specified by {@code + * fields} and {@code qualifiers}. The result is well-formed if the length of qualifiers is + * either 1 or equal to length of {@code fields}. + * + * @param other other invariant object, may be null + * @param fields list of fields + * @param qualifiers list of qualifiers + * @param atypeFactory the type factory + */ + public FieldInvariants( + @Nullable FieldInvariants other, + List fields, + List qualifiers, + AnnotatedTypeFactory atypeFactory) { + if (qualifiers.size() == 1) { + while (fields.size() > qualifiers.size()) { + qualifiers.add(qualifiers.get(0)); + } + } + if (other != null) { + fields.addAll(other.fields); + qualifiers.addAll(other.qualifiers); + } - /** - * Returns a list of qualifiers for {@code field}. If {@code field} has no qualifiers, returns an - * empty list. - * - * @param field simple field name - * @return a list of qualifiers for {@code field}, possibly empty - */ - public List getQualifiersFor(CharSequence field) { - if (!isWellFormed()) { - throw new BugInCF("malformed FieldInvariants"); + this.fields = Collections.unmodifiableList(fields); + this.qualifiers = qualifiers; + this.atypeFactory = atypeFactory; } - String fieldString = field.toString(); - int index = fields.indexOf(fieldString); - if (index == -1) { - return Collections.emptyList(); - } - List list = new ArrayList<>(); - for (int i = 0; i < fields.size(); i++) { - if (fields.get(i).equals(fieldString)) { - list.add(qualifiers.get(i)); - } + + /** The simple names of the fields that have a qualifier. May contain duplicates. */ + public List getFields() { + return fields; } - return list; - } - /** - * Returns true if there is a qualifier for each field in {@code fields}. - * - * @return true if there is a qualifier for each field in {@code fields} - */ - public boolean isWellFormed() { - return qualifiers.size() == fields.size(); - } + /** + * Returns a list of qualifiers for {@code field}. If {@code field} has no qualifiers, returns + * an empty list. + * + * @param field simple field name + * @return a list of qualifiers for {@code field}, possibly empty + */ + public List getQualifiersFor(CharSequence field) { + if (!isWellFormed()) { + throw new BugInCF("malformed FieldInvariants"); + } + String fieldString = field.toString(); + int index = fields.indexOf(fieldString); + if (index == -1) { + return Collections.emptyList(); + } + List list = new ArrayList<>(); + for (int i = 0; i < fields.size(); i++) { + if (fields.get(i).equals(fieldString)) { + list.add(qualifiers.get(i)); + } + } + return list; + } - /** - * Returns null if this is stronger than the given FieldInvariants, otherwise returns the error - * message. This is stronger if each of its qualifiers is a subtype of (or equal to) the - * respective qualfier in the given FieldInvariants. - * - * @param superInvar the value to check for being a weaker invariant - * @return null if this is stronger, otherwise returns an error message - */ - public @Nullable DiagMessage isStrongerThan(FieldInvariants superInvar) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - if (!this.fields.containsAll(superInvar.fields)) { - List missingFields = new ArrayList<>(superInvar.fields); - missingFields.removeAll(fields); - return DiagMessage.error( - "field.invariant.not.found.superclass", String.join(", ", missingFields)); + /** + * Returns true if there is a qualifier for each field in {@code fields}. + * + * @return true if there is a qualifier for each field in {@code fields} + */ + public boolean isWellFormed() { + return qualifiers.size() == fields.size(); } - for (String field : superInvar.fields) { - List superQualifiers = superInvar.getQualifiersFor(field); - List subQualifiers = this.getQualifiersFor(field); - for (AnnotationMirror superA : superQualifiers) { - AnnotationMirror sub = qualHierarchy.findAnnotationInSameHierarchy(subQualifiers, superA); - if (sub == null || !qualHierarchy.isSubtypeQualifiersOnly(sub, superA)) { - return DiagMessage.error("field.invariant.not.subtype.superclass", field, sub, superA); + /** + * Returns null if this is stronger than the given FieldInvariants, otherwise returns the error + * message. This is stronger if each of its qualifiers is a subtype of (or equal to) the + * respective qualfier in the given FieldInvariants. + * + * @param superInvar the value to check for being a weaker invariant + * @return null if this is stronger, otherwise returns an error message + */ + public @Nullable DiagMessage isStrongerThan(FieldInvariants superInvar) { + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + if (!this.fields.containsAll(superInvar.fields)) { + List missingFields = new ArrayList<>(superInvar.fields); + missingFields.removeAll(fields); + return DiagMessage.error( + "field.invariant.not.found.superclass", String.join(", ", missingFields)); + } + + for (String field : superInvar.fields) { + List superQualifiers = superInvar.getQualifiersFor(field); + List subQualifiers = this.getQualifiersFor(field); + for (AnnotationMirror superA : superQualifiers) { + AnnotationMirror sub = + qualHierarchy.findAnnotationInSameHierarchy(subQualifiers, superA); + if (sub == null || !qualHierarchy.isSubtypeQualifiersOnly(sub, superA)) { + return DiagMessage.error( + "field.invariant.not.subtype.superclass", field, sub, superA); + } + } } - } + return null; } - return null; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java index d3253672b31..001d5c68689 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java @@ -9,11 +9,13 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.util.SimpleTreeVisitor; import com.sun.source.util.TreePath; -import java.util.ArrayDeque; -import java.util.Arrays; + import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayDeque; +import java.util.Arrays; + /** * Utilities for determining tree-based heuristics. * @@ -21,223 +23,227 @@ */ public class Heuristics { - /** - * Returns true if a tree has a particular set of direct parents, ignoring blocks and parentheses. - * - *

          For example, to test whether an expression (specified by {@code path}) is immediately - * contained by an if statement which is immediately contained in a method, one would invoke: - * - *

          -   * matchParents(path, Kind.IF, Tree.Kind.METHOD)
          -   * 
          - * - * @param path the path to match - * @param kinds the tree kinds to match against, in ascending order starting from the desired kind - * of the parent - * @return true if the tree path matches the desired kinds, skipping blocks and parentheses, for - * as many kinds as specified - */ - public static boolean matchParents(TreePath path, Tree.Kind... kinds) { - - TreePath parentPath = path.getParentPath(); - boolean result = true; - - ArrayDeque queue = new ArrayDeque<>(Arrays.asList(kinds)); - - Tree tree; - while ((tree = parentPath.getLeaf()) != null) { - - if (queue.isEmpty()) { - break; - } - - if (tree.getKind() == Tree.Kind.BLOCK || tree.getKind() == Tree.Kind.PARENTHESIZED) { - parentPath = parentPath.getParentPath(); - continue; - } - - result &= queue.removeFirst() == parentPath.getLeaf().getKind(); - parentPath = parentPath.getParentPath(); - } - - return result; - } - - /** A base class for tree-matching algorithms. Skips parentheses by default. */ - public static class Matcher extends SimpleTreeVisitor { - - @Override - protected Boolean defaultAction(Tree tree, Void p) { - return false; - } - - @Override - public Boolean visitParenthesized(ParenthesizedTree tree, Void p) { - return visit(tree.getExpression(), p); - } - /** - * Returns true if the given path matches this Matcher. + * Returns true if a tree has a particular set of direct parents, ignoring blocks and + * parentheses. * - * @param path the path to test - * @return true if the given path matches this Matcher + *

          For example, to test whether an expression (specified by {@code path}) is immediately + * contained by an if statement which is immediately contained in a method, one would invoke: + * + *

          +     * matchParents(path, Kind.IF, Tree.Kind.METHOD)
          +     * 
          + * + * @param path the path to match + * @param kinds the tree kinds to match against, in ascending order starting from the desired + * kind of the parent + * @return true if the tree path matches the desired kinds, skipping blocks and parentheses, for + * as many kinds as specified */ - public boolean match(TreePath path) { - return visit(path.getLeaf(), null); - } - } + public static boolean matchParents(TreePath path, Tree.Kind... kinds) { - public static class PreceededBy extends Matcher { - private final Matcher matcher; + TreePath parentPath = path.getParentPath(); + boolean result = true; - public PreceededBy(Matcher matcher) { - this.matcher = matcher; - } + ArrayDeque queue = new ArrayDeque<>(Arrays.asList(kinds)); - @SuppressWarnings("interning:not.interned") - @Override - public boolean match(TreePath path) { - StatementTree stmt = TreePathUtil.enclosingOfClass(path, StatementTree.class); - if (stmt == null) { - return false; - } - TreePath p = path; - while (p.getLeaf() != stmt) { - p = p.getParentPath(); - } - assert p.getLeaf() == stmt; - - while (p != null && p.getLeaf() instanceof StatementTree) { - if (p.getParentPath().getLeaf() instanceof BlockTree) { - BlockTree block = (BlockTree) p.getParentPath().getLeaf(); - for (StatementTree st : block.getStatements()) { - if (st == p.getLeaf()) { - break; + Tree tree; + while ((tree = parentPath.getLeaf()) != null) { + + if (queue.isEmpty()) { + break; } - if (matcher.match(new TreePath(p, st))) { - return true; + if (tree.getKind() == Tree.Kind.BLOCK || tree.getKind() == Tree.Kind.PARENTHESIZED) { + parentPath = parentPath.getParentPath(); + continue; } - } + + result &= queue.removeFirst() == parentPath.getLeaf().getKind(); + parentPath = parentPath.getParentPath(); } - p = p.getParentPath(); - } - return false; + return result; } - } - /** - * {@code match()} returns true if called on a path, any element of which matches the given - * matcher (supplied at object initialization). That matcher is usually one that matches only the - * leaf of a path, ignoring all other parts of it. - */ - public static class Within extends Matcher { - /** The matcher that {@code Within.match} will try, on every parent of the path it is given. */ - private final Matcher matcher; + /** A base class for tree-matching algorithms. Skips parentheses by default. */ + public static class Matcher extends SimpleTreeVisitor { - /** - * Create a new Within matcher. - * - * @param matcher the matcher that {@code Within.match} will try, on every parent of the path it - * is given - */ - public Within(Matcher matcher) { - this.matcher = matcher; - } + @Override + protected Boolean defaultAction(Tree tree, Void p) { + return false; + } - @Override - public boolean match(TreePath path) { - TreePath p = path; - while (p != null) { - if (matcher.match(p)) { - return true; + @Override + public Boolean visitParenthesized(ParenthesizedTree tree, Void p) { + return visit(tree.getExpression(), p); } - p = p.getParentPath(); - } - return false; + /** + * Returns true if the given path matches this Matcher. + * + * @param path the path to test + * @return true if the given path matches this Matcher + */ + public boolean match(TreePath path) { + return visit(path.getLeaf(), null); + } } - } - /** - * {@code match()} returns true if called on a path whose leaf is within the "then" clause of an - * if whose condition matches the matcher (supplied at object initialization). Also returns true - * if the leaf is within the "else" of a negated condition that matches the supplied matcher. - */ - public static class WithinTrueBranch extends Matcher { - /** conditionMatcher for the condition */ - private final Matcher matcher; + public static class PreceededBy extends Matcher { + private final Matcher matcher; + + public PreceededBy(Matcher matcher) { + this.matcher = matcher; + } + + @SuppressWarnings("interning:not.interned") + @Override + public boolean match(TreePath path) { + StatementTree stmt = TreePathUtil.enclosingOfClass(path, StatementTree.class); + if (stmt == null) { + return false; + } + TreePath p = path; + while (p.getLeaf() != stmt) { + p = p.getParentPath(); + } + assert p.getLeaf() == stmt; + + while (p != null && p.getLeaf() instanceof StatementTree) { + if (p.getParentPath().getLeaf() instanceof BlockTree) { + BlockTree block = (BlockTree) p.getParentPath().getLeaf(); + for (StatementTree st : block.getStatements()) { + if (st == p.getLeaf()) { + break; + } + + if (matcher.match(new TreePath(p, st))) { + return true; + } + } + } + p = p.getParentPath(); + } + + return false; + } + } /** - * @param conditionMatcher for the condition + * {@code match()} returns true if called on a path, any element of which matches the given + * matcher (supplied at object initialization). That matcher is usually one that matches only + * the leaf of a path, ignoring all other parts of it. */ - public WithinTrueBranch(Matcher conditionMatcher) { - this.matcher = conditionMatcher; + public static class Within extends Matcher { + /** + * The matcher that {@code Within.match} will try, on every parent of the path it is given. + */ + private final Matcher matcher; + + /** + * Create a new Within matcher. + * + * @param matcher the matcher that {@code Within.match} will try, on every parent of the + * path it is given + */ + public Within(Matcher matcher) { + this.matcher = matcher; + } + + @Override + public boolean match(TreePath path) { + TreePath p = path; + while (p != null) { + if (matcher.match(p)) { + return true; + } + p = p.getParentPath(); + } + + return false; + } } - @SuppressWarnings("interning:not.interned") - @Override - public boolean match(TreePath path) { - TreePath prev = path, p = path.getParentPath(); - while (p != null) { - if (p.getLeaf().getKind() == Tree.Kind.IF) { - IfTree ifTree = (IfTree) p.getLeaf(); - ExpressionTree cond = TreeUtils.withoutParens(ifTree.getCondition()); - if (ifTree.getThenStatement() == prev.getLeaf() && matcher.match(new TreePath(p, cond))) { - return true; - } - if (cond.getKind() == Tree.Kind.LOGICAL_COMPLEMENT - && matcher.match(new TreePath(p, ((UnaryTree) cond).getExpression()))) { - return true; - } + /** + * {@code match()} returns true if called on a path whose leaf is within the "then" clause of an + * if whose condition matches the matcher (supplied at object initialization). Also returns true + * if the leaf is within the "else" of a negated condition that matches the supplied matcher. + */ + public static class WithinTrueBranch extends Matcher { + /** conditionMatcher for the condition */ + private final Matcher matcher; + + /** + * @param conditionMatcher for the condition + */ + public WithinTrueBranch(Matcher conditionMatcher) { + this.matcher = conditionMatcher; } - prev = p; - p = p.getParentPath(); - } - return false; - } - } - - /** - * {@code match()} returns true if called on a path whose leaf has the given kind (supplied at - * object initialization). - */ - public static class OfKind extends Matcher { - private final Tree.Kind kind; - private final Matcher matcher; - - public OfKind(Tree.Kind kind, Matcher matcher) { - this.kind = kind; - this.matcher = matcher; - } + @SuppressWarnings("interning:not.interned") + @Override + public boolean match(TreePath path) { + TreePath prev = path, p = path.getParentPath(); + while (p != null) { + if (p.getLeaf().getKind() == Tree.Kind.IF) { + IfTree ifTree = (IfTree) p.getLeaf(); + ExpressionTree cond = TreeUtils.withoutParens(ifTree.getCondition()); + if (ifTree.getThenStatement() == prev.getLeaf() + && matcher.match(new TreePath(p, cond))) { + return true; + } + if (cond.getKind() == Tree.Kind.LOGICAL_COMPLEMENT + && matcher.match(new TreePath(p, ((UnaryTree) cond).getExpression()))) { + return true; + } + } + prev = p; + p = p.getParentPath(); + } - @Override - public boolean match(TreePath path) { - if (path.getLeaf().getKind() == kind) { - return matcher.match(path); - } - return false; + return false; + } } - } - /** {@code match()} returns true if any of the given matchers returns true. */ - public static class OrMatcher extends Matcher { - private final Matcher[] matchers; + /** + * {@code match()} returns true if called on a path whose leaf has the given kind (supplied at + * object initialization). + */ + public static class OfKind extends Matcher { + private final Tree.Kind kind; + private final Matcher matcher; + + public OfKind(Tree.Kind kind, Matcher matcher) { + this.kind = kind; + this.matcher = matcher; + } - public OrMatcher(Matcher... matchers) { - this.matchers = matchers; + @Override + public boolean match(TreePath path) { + if (path.getLeaf().getKind() == kind) { + return matcher.match(path); + } + return false; + } } - @Override - public boolean match(TreePath path) { - for (Matcher matcher : matchers) { - if (matcher.match(path)) { - return true; + /** {@code match()} returns true if any of the given matchers returns true. */ + public static class OrMatcher extends Matcher { + private final Matcher[] matchers; + + public OrMatcher(Matcher... matchers) { + this.matchers = matchers; + } + + @Override + public boolean match(TreePath path) { + for (Matcher matcher : matchers) { + if (matcher.match(path)) { + return true; + } + } + return false; } - } - return false; } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java index 0d1020af27d..ea07b06621e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java @@ -32,22 +32,7 @@ import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.ClassType; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; + import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -76,6 +61,24 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; + /** * Helper methods to parse a string that represents a restricted Java expression. * @@ -86,1125 +89,1179 @@ */ public class JavaExpressionParseUtil { - /** Regular expression for a formal parameter use. */ - protected static final String PARAMETER_REGEX = "#([1-9][0-9]*)"; - - /** - * Anchored pattern for a formal parameter use; matches a string that is exactly a formal - * parameter use. - */ - protected static final @Regex(1) Pattern ANCHORED_PARAMETER_PATTERN = - Pattern.compile("^" + PARAMETER_REGEX + "$"); - - /** - * Unanchored pattern for a formal parameter use; can be used to find all formal parameter uses. - */ - protected static final @Regex(1) Pattern UNANCHORED_PARAMETER_PATTERN = - Pattern.compile(PARAMETER_REGEX); - - /** - * Parsable replacement for formal parameter references. It is parsable because it is a Java - * identifier. - */ - private static final String PARAMETER_PREFIX = "_param_"; - - /** The length of {@link #PARAMETER_PREFIX}. */ - private static final int PARAMETER_PREFIX_LENGTH = PARAMETER_PREFIX.length(); - - /** A pattern that matches the start of a formal parameter in "#2" syntax. */ - private static final Pattern FORMAL_PARAMETER = Pattern.compile("#(\\d)"); - - /** The replacement for a formal parameter in "#2" syntax. */ - private static final String PARAMETER_REPLACEMENT = PARAMETER_PREFIX + "$1"; - - /** - * Parses a string to a {@link JavaExpression}. - * - *

          For most uses, clients should call one of the static methods in {@link - * StringToJavaExpression} rather than calling this method directly. - * - * @param expression the string expression to parse - * @param enclosingType type of the class that encloses the JavaExpression - * @param thisReference the JavaExpression to which to parse "this", or null if "this" should not - * appear in the expression - * @param parameters list of JavaExpressions to which to parse formal parameter references such as - * "#2", or null if formal parameter references should not appear in the expression - * @param localVarPath if non-null, the expression is parsed as if it were written at this - * location; affects only parsing of local variables - * @param pathToCompilationUnit required to use the underlying Javac API - * @param env the processing environment - * @return {@code expression} as a {@code JavaExpression} - * @throws JavaExpressionParseException if the string cannot be parsed - */ - public static JavaExpression parse( - String expression, - TypeMirror enclosingType, - @Nullable ThisReference thisReference, - @Nullable List parameters, - @Nullable TreePath localVarPath, - TreePath pathToCompilationUnit, - ProcessingEnvironment env) - throws JavaExpressionParseException { - - // Use the current source version to parse with because a JavaExpression could refer to a - // variable named "var", which is a keyword in Java 10 and later. - LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); - String expressionWithParameterNames = - StringsPlume.replaceAll(expression, FORMAL_PARAMETER, PARAMETER_REPLACEMENT); - Expression expr; - try { - expr = JavaParserUtil.parseExpression(expressionWithParameterNames, currentSourceVersion); - } catch (ParseProblemException e) { - String extra = "."; - if (!e.getProblems().isEmpty()) { - String message = e.getProblems().get(0).getMessage(); - int newLine = message.indexOf(System.lineSeparator()); - if (newLine != -1) { - message = message.substring(0, newLine); - } - extra = ". Error message: " + message; - } - throw constructJavaExpressionParseError(expression, "the expression did not parse" + extra); - } - - JavaExpression result = - ExpressionToJavaExpressionVisitor.convert( - expr, - enclosingType, - thisReference, - parameters, - localVarPath, - pathToCompilationUnit, - env); - - if (result instanceof ClassName && !expression.endsWith(".class")) { - throw constructJavaExpressionParseError( - expression, - String.format( - "a class name cannot terminate a Java expression string, where" + " result=%s [%s]", - result, result.getClass())); - } - return result; - } - - /** - * A visitor class that converts a JavaParser {@link Expression} to a {@link JavaExpression}. This - * class does not viewpoint-adapt the expression. - */ - private static class ExpressionToJavaExpressionVisitor - extends GenericVisitorWithDefaults { + /** Regular expression for a formal parameter use. */ + protected static final String PARAMETER_REGEX = "#([1-9][0-9]*)"; /** - * The underlying javac API used to convert from Strings to Elements requires a tree path even - * when the information could be deduced from elements alone. So use the path to the current - * CompilationUnit. + * Anchored pattern for a formal parameter use; matches a string that is exactly a formal + * parameter use. */ - private final TreePath pathToCompilationUnit; - - /** If non-null, the expression is parsed as if it were written at this location. */ - private final @Nullable TreePath localVarPath; - - /** The processing environment. */ - private final ProcessingEnvironment env; - - /** The resolver. Computed from the environment, but lazily initialized. */ - private @MonotonicNonNull Resolver resolver = null; - - /** The type utilities. */ - private final Types types; - - /** The java.lang.String type. */ - private final TypeMirror stringTypeMirror; - - /** The enclosing type. Used to look up unqualified method, field, and class names. */ - private final TypeMirror enclosingType; + protected static final @Regex(1) Pattern ANCHORED_PARAMETER_PATTERN = + Pattern.compile("^" + PARAMETER_REGEX + "$"); /** - * The expression to use for "this". If {@code null}, a parse error will be thrown if "this" - * appears in the expression. + * Unanchored pattern for a formal parameter use; can be used to find all formal parameter uses. */ - private final @Nullable ThisReference thisReference; + protected static final @Regex(1) Pattern UNANCHORED_PARAMETER_PATTERN = + Pattern.compile(PARAMETER_REGEX); /** - * For each formal parameter, the expression to which to parse it. For example, the second - * (index 1) element of the list is what "#2" parses to. If this field is {@code null}, a parse - * error will be thrown if "#2" appears in the expression. + * Parsable replacement for formal parameter references. It is parsable because it is a Java + * identifier. */ - private final @Nullable List parameters; + private static final String PARAMETER_PREFIX = "_param_"; - /** - * Create a new ExpressionToJavaExpressionVisitor. - * - * @param enclosingType type of the class that encloses the JavaExpression - * @param thisReference a JavaExpression to which to parse "this", or null if "this" should not - * appear in the expression - * @param parameters list of JavaExpressions to which to parse a formal parameter reference such - * as "#2", or null if parameters should not appear in the expression - * @param localVarPath if non-null, the expression is parsed as if it were written at this - * location - * @param pathToCompilationUnit required to use the underlying Javac API - * @param env the processing environment - */ - private ExpressionToJavaExpressionVisitor( - TypeMirror enclosingType, - @Nullable ThisReference thisReference, - @Nullable List parameters, - @Nullable TreePath localVarPath, - TreePath pathToCompilationUnit, - ProcessingEnvironment env) { - this.pathToCompilationUnit = pathToCompilationUnit; - this.localVarPath = localVarPath; - this.env = env; - this.types = env.getTypeUtils(); - this.stringTypeMirror = env.getElementUtils().getTypeElement("java.lang.String").asType(); - this.enclosingType = enclosingType; - this.thisReference = thisReference; - this.parameters = parameters; - } + /** The length of {@link #PARAMETER_PREFIX}. */ + private static final int PARAMETER_PREFIX_LENGTH = PARAMETER_PREFIX.length(); + + /** A pattern that matches the start of a formal parameter in "#2" syntax. */ + private static final Pattern FORMAL_PARAMETER = Pattern.compile("#(\\d)"); + + /** The replacement for a formal parameter in "#2" syntax. */ + private static final String PARAMETER_REPLACEMENT = PARAMETER_PREFIX + "$1"; /** - * Converts a JavaParser {@link Expression} to a {@link JavaExpression}. + * Parses a string to a {@link JavaExpression}. + * + *

          For most uses, clients should call one of the static methods in {@link + * StringToJavaExpression} rather than calling this method directly. * - * @param expr the JavaParser {@link Expression} to convert + * @param expression the string expression to parse * @param enclosingType type of the class that encloses the JavaExpression - * @param thisReference a JavaExpression to which to parse "this", or null if "this" should not - * appear in the expression - * @param parameters list of JavaExpressions to which to parse parameters, or null if parameters - * should not appear in the expression + * @param thisReference the JavaExpression to which to parse "this", or null if "this" should + * not appear in the expression + * @param parameters list of JavaExpressions to which to parse formal parameter references such + * as "#2", or null if formal parameter references should not appear in the expression * @param localVarPath if non-null, the expression is parsed as if it were written at this - * location + * location; affects only parsing of local variables * @param pathToCompilationUnit required to use the underlying Javac API * @param env the processing environment - * @return {@code expr} as a {@code JavaExpression} - * @throws JavaExpressionParseException if {@code expr} cannot be converted to a {@code - * JavaExpression} + * @return {@code expression} as a {@code JavaExpression} + * @throws JavaExpressionParseException if the string cannot be parsed */ - public static JavaExpression convert( - Expression expr, - TypeMirror enclosingType, - @Nullable ThisReference thisReference, - @Nullable List parameters, - @Nullable TreePath localVarPath, - TreePath pathToCompilationUnit, - ProcessingEnvironment env) - throws JavaExpressionParseException { - try { - return expr.accept( - new ExpressionToJavaExpressionVisitor( - enclosingType, thisReference, parameters, localVarPath, pathToCompilationUnit, env), - null); - } catch (ParseRuntimeException e) { - // Convert unchecked to checked exception. Visitor methods can't throw checked - // exceptions. They override the methods in the superclass, and a checked exception - // would change the method signature. - throw e.getCheckedException(); - } + public static JavaExpression parse( + String expression, + TypeMirror enclosingType, + @Nullable ThisReference thisReference, + @Nullable List parameters, + @Nullable TreePath localVarPath, + TreePath pathToCompilationUnit, + ProcessingEnvironment env) + throws JavaExpressionParseException { + + // Use the current source version to parse with because a JavaExpression could refer to a + // variable named "var", which is a keyword in Java 10 and later. + LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); + String expressionWithParameterNames = + StringsPlume.replaceAll(expression, FORMAL_PARAMETER, PARAMETER_REPLACEMENT); + Expression expr; + try { + expr = + JavaParserUtil.parseExpression( + expressionWithParameterNames, currentSourceVersion); + } catch (ParseProblemException e) { + String extra = "."; + if (!e.getProblems().isEmpty()) { + String message = e.getProblems().get(0).getMessage(); + int newLine = message.indexOf(System.lineSeparator()); + if (newLine != -1) { + message = message.substring(0, newLine); + } + extra = ". Error message: " + message; + } + throw constructJavaExpressionParseError( + expression, "the expression did not parse" + extra); + } + + JavaExpression result = + ExpressionToJavaExpressionVisitor.convert( + expr, + enclosingType, + thisReference, + parameters, + localVarPath, + pathToCompilationUnit, + env); + + if (result instanceof ClassName && !expression.endsWith(".class")) { + throw constructJavaExpressionParseError( + expression, + String.format( + "a class name cannot terminate a Java expression string, where" + + " result=%s [%s]", + result, result.getClass())); + } + return result; } /** - * Initializes the {@code resolver} field if necessary. Does nothing on invocations after the - * first. + * A visitor class that converts a JavaParser {@link Expression} to a {@link JavaExpression}. + * This class does not viewpoint-adapt the expression. */ - @EnsuresNonNull("resolver") - private void setResolverField() { - if (resolver == null) { - resolver = new Resolver(env); - } - } - - /** If the expression is not supported, throw a {@link ParseRuntimeException} by default. */ - @Override - public JavaExpression defaultAction(com.github.javaparser.ast.Node n, Void aVoid) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - n.toString(), n.getClass() + " is not a supported expression")); - } + private static class ExpressionToJavaExpressionVisitor + extends GenericVisitorWithDefaults { + + /** + * The underlying javac API used to convert from Strings to Elements requires a tree path + * even when the information could be deduced from elements alone. So use the path to the + * current CompilationUnit. + */ + private final TreePath pathToCompilationUnit; + + /** If non-null, the expression is parsed as if it were written at this location. */ + private final @Nullable TreePath localVarPath; + + /** The processing environment. */ + private final ProcessingEnvironment env; + + /** The resolver. Computed from the environment, but lazily initialized. */ + private @MonotonicNonNull Resolver resolver = null; + + /** The type utilities. */ + private final Types types; + + /** The java.lang.String type. */ + private final TypeMirror stringTypeMirror; + + /** The enclosing type. Used to look up unqualified method, field, and class names. */ + private final TypeMirror enclosingType; + + /** + * The expression to use for "this". If {@code null}, a parse error will be thrown if "this" + * appears in the expression. + */ + private final @Nullable ThisReference thisReference; + + /** + * For each formal parameter, the expression to which to parse it. For example, the second + * (index 1) element of the list is what "#2" parses to. If this field is {@code null}, a + * parse error will be thrown if "#2" appears in the expression. + */ + private final @Nullable List parameters; + + /** + * Create a new ExpressionToJavaExpressionVisitor. + * + * @param enclosingType type of the class that encloses the JavaExpression + * @param thisReference a JavaExpression to which to parse "this", or null if "this" should + * not appear in the expression + * @param parameters list of JavaExpressions to which to parse a formal parameter reference + * such as "#2", or null if parameters should not appear in the expression + * @param localVarPath if non-null, the expression is parsed as if it were written at this + * location + * @param pathToCompilationUnit required to use the underlying Javac API + * @param env the processing environment + */ + private ExpressionToJavaExpressionVisitor( + TypeMirror enclosingType, + @Nullable ThisReference thisReference, + @Nullable List parameters, + @Nullable TreePath localVarPath, + TreePath pathToCompilationUnit, + ProcessingEnvironment env) { + this.pathToCompilationUnit = pathToCompilationUnit; + this.localVarPath = localVarPath; + this.env = env; + this.types = env.getTypeUtils(); + this.stringTypeMirror = + env.getElementUtils().getTypeElement("java.lang.String").asType(); + this.enclosingType = enclosingType; + this.thisReference = thisReference; + this.parameters = parameters; + } - @Override - public JavaExpression visit(NullLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getNullType(), (Object) null); - } + /** + * Converts a JavaParser {@link Expression} to a {@link JavaExpression}. + * + * @param expr the JavaParser {@link Expression} to convert + * @param enclosingType type of the class that encloses the JavaExpression + * @param thisReference a JavaExpression to which to parse "this", or null if "this" should + * not appear in the expression + * @param parameters list of JavaExpressions to which to parse parameters, or null if + * parameters should not appear in the expression + * @param localVarPath if non-null, the expression is parsed as if it were written at this + * location + * @param pathToCompilationUnit required to use the underlying Javac API + * @param env the processing environment + * @return {@code expr} as a {@code JavaExpression} + * @throws JavaExpressionParseException if {@code expr} cannot be converted to a {@code + * JavaExpression} + */ + public static JavaExpression convert( + Expression expr, + TypeMirror enclosingType, + @Nullable ThisReference thisReference, + @Nullable List parameters, + @Nullable TreePath localVarPath, + TreePath pathToCompilationUnit, + ProcessingEnvironment env) + throws JavaExpressionParseException { + try { + return expr.accept( + new ExpressionToJavaExpressionVisitor( + enclosingType, + thisReference, + parameters, + localVarPath, + pathToCompilationUnit, + env), + null); + } catch (ParseRuntimeException e) { + // Convert unchecked to checked exception. Visitor methods can't throw checked + // exceptions. They override the methods in the superclass, and a checked exception + // would change the method signature. + throw e.getCheckedException(); + } + } - @Override - public JavaExpression visit(IntegerLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.INT), expr.asNumber()); - } + /** + * Initializes the {@code resolver} field if necessary. Does nothing on invocations after + * the first. + */ + @EnsuresNonNull("resolver") + private void setResolverField() { + if (resolver == null) { + resolver = new Resolver(env); + } + } - @Override - public JavaExpression visit(LongLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.LONG), expr.asNumber()); - } + /** If the expression is not supported, throw a {@link ParseRuntimeException} by default. */ + @Override + public JavaExpression defaultAction(com.github.javaparser.ast.Node n, Void aVoid) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + n.toString(), n.getClass() + " is not a supported expression")); + } - @Override - public JavaExpression visit(CharLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.CHAR), expr.asChar()); - } + @Override + public JavaExpression visit(NullLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getNullType(), (Object) null); + } - @Override - public JavaExpression visit(DoubleLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.DOUBLE), expr.asDouble()); - } + @Override + public JavaExpression visit(IntegerLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.INT), expr.asNumber()); + } - @Override - public JavaExpression visit(StringLiteralExpr expr, Void aVoid) { - return new ValueLiteral(stringTypeMirror, expr.asString()); - } + @Override + public JavaExpression visit(LongLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.LONG), expr.asNumber()); + } - @Override - public JavaExpression visit(BooleanLiteralExpr expr, Void aVoid) { - return new ValueLiteral(types.getPrimitiveType(TypeKind.BOOLEAN), expr.getValue()); - } + @Override + public JavaExpression visit(CharLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.CHAR), expr.asChar()); + } - @Override - public JavaExpression visit(ThisExpr n, Void aVoid) { - if (thisReference == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError("this", "\"this\" isn't allowed here")); - } - return thisReference; - } + @Override + public JavaExpression visit(DoubleLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.DOUBLE), expr.asDouble()); + } - @Override - public JavaExpression visit(SuperExpr n, Void aVoid) { - // super literal - TypeMirror superclass = TypesUtils.getSuperclass(enclosingType, types); - if (superclass == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError("super", enclosingType + " has no superclass")); - } - return new ThisReference(superclass); - } + @Override + public JavaExpression visit(StringLiteralExpr expr, Void aVoid) { + return new ValueLiteral(stringTypeMirror, expr.asString()); + } - // expr is an expression in parentheses. - @Override - public JavaExpression visit(EnclosedExpr expr, Void aVoid) { - return expr.getInner().accept(this, null); - } + @Override + public JavaExpression visit(BooleanLiteralExpr expr, Void aVoid) { + return new ValueLiteral(types.getPrimitiveType(TypeKind.BOOLEAN), expr.getValue()); + } - @Override - public JavaExpression visit(ArrayAccessExpr expr, Void aVoid) { - JavaExpression array = expr.getName().accept(this, null); - TypeMirror arrayType = array.getType(); - if (arrayType.getKind() != TypeKind.ARRAY) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - String.format( - "expected an array, found %s of type %s [%s]", - array, arrayType, arrayType.getKind()))); - } - TypeMirror componentType = ((ArrayType) arrayType).getComponentType(); - - JavaExpression index = expr.getIndex().accept(this, null); - - return new ArrayAccess(componentType, array, index); - } + @Override + public JavaExpression visit(ThisExpr n, Void aVoid) { + if (thisReference == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError("this", "\"this\" isn't allowed here")); + } + return thisReference; + } - // expr is an identifier with no dots in its name. - @Override - public JavaExpression visit(NameExpr expr, Void aVoid) { - String s = expr.getNameAsString(); - setResolverField(); - - // Formal parameter, using "#2" syntax. - JavaExpression parameter = getParameterJavaExpression(s); - if (parameter != null) { - // A parameter is a local variable, but it can be referenced outside of local scope - // (at the method scope) using the special #NN syntax. - return parameter; - } - - // Local variable or parameter. - if (localVarPath != null) { - // Attempt to match a local variable within the scope of the - // given path before attempting to match a field. - VariableElement varElem = resolver.findLocalVariableOrParameter(s, localVarPath); - if (varElem != null) { - return new LocalVariable(varElem); + @Override + public JavaExpression visit(SuperExpr n, Void aVoid) { + // super literal + TypeMirror superclass = TypesUtils.getSuperclass(enclosingType, types); + if (superclass == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + "super", enclosingType + " has no superclass")); + } + return new ThisReference(superclass); } - } - - // Field access - JavaExpression fieldAccessReceiver; - if (thisReference != null) { - fieldAccessReceiver = thisReference; - } else { - fieldAccessReceiver = new ClassName(enclosingType); - } - FieldAccess fieldAccess = getIdentifierAsFieldAccess(fieldAccessReceiver, s); - if (fieldAccess != null) { - return fieldAccess; - } - - if (localVarPath != null) { - Element classElem = resolver.findClass(s, localVarPath); - TypeMirror classType = ElementUtils.getType(classElem); - if (classType != null) { - return new ClassName(classType); + + // expr is an expression in parentheses. + @Override + public JavaExpression visit(EnclosedExpr expr, Void aVoid) { + return expr.getInner().accept(this, null); } - } - - ClassName classType = getIdentifierAsUnqualifiedClassName(s); - if (classType != null) { - return classType; - } - - // Err if a formal parameter name is used, instead of the "#2" syntax. - if (parameters != null) { - for (int i = 0; i < parameters.size(); i++) { - Element varElt = parameters.get(i).getElement(); - if (varElt.getSimpleName().contentEquals(s)) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - s, String.format(DependentTypesError.FORMAL_PARAM_NAME_STRING, i + 1, s))); - } + + @Override + public JavaExpression visit(ArrayAccessExpr expr, Void aVoid) { + JavaExpression array = expr.getName().accept(this, null); + TypeMirror arrayType = array.getType(); + if (arrayType.getKind() != TypeKind.ARRAY) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + String.format( + "expected an array, found %s of type %s [%s]", + array, arrayType, arrayType.getKind()))); + } + TypeMirror componentType = ((ArrayType) arrayType).getComponentType(); + + JavaExpression index = expr.getIndex().accept(this, null); + + return new ArrayAccess(componentType, array, index); } - } - throw new ParseRuntimeException(constructJavaExpressionParseError(s, "identifier not found")); - } + // expr is an identifier with no dots in its name. + @Override + public JavaExpression visit(NameExpr expr, Void aVoid) { + String s = expr.getNameAsString(); + setResolverField(); + + // Formal parameter, using "#2" syntax. + JavaExpression parameter = getParameterJavaExpression(s); + if (parameter != null) { + // A parameter is a local variable, but it can be referenced outside of local scope + // (at the method scope) using the special #NN syntax. + return parameter; + } + + // Local variable or parameter. + if (localVarPath != null) { + // Attempt to match a local variable within the scope of the + // given path before attempting to match a field. + VariableElement varElem = resolver.findLocalVariableOrParameter(s, localVarPath); + if (varElem != null) { + return new LocalVariable(varElem); + } + } + + // Field access + JavaExpression fieldAccessReceiver; + if (thisReference != null) { + fieldAccessReceiver = thisReference; + } else { + fieldAccessReceiver = new ClassName(enclosingType); + } + FieldAccess fieldAccess = getIdentifierAsFieldAccess(fieldAccessReceiver, s); + if (fieldAccess != null) { + return fieldAccess; + } + + if (localVarPath != null) { + Element classElem = resolver.findClass(s, localVarPath); + TypeMirror classType = ElementUtils.getType(classElem); + if (classType != null) { + return new ClassName(classType); + } + } + + ClassName classType = getIdentifierAsUnqualifiedClassName(s); + if (classType != null) { + return classType; + } + + // Err if a formal parameter name is used, instead of the "#2" syntax. + if (parameters != null) { + for (int i = 0; i < parameters.size(); i++) { + Element varElt = parameters.get(i).getElement(); + if (varElt.getSimpleName().contentEquals(s)) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + s, + String.format( + DependentTypesError.FORMAL_PARAM_NAME_STRING, + i + 1, + s))); + } + } + } - /** - * If {@code s} a parameter expressed using the {@code #NN} syntax, then returns a - * JavaExpression for the given parameter; that is, returns an element of {@code parameters}. - * Otherwise, returns {@code null}. - * - * @param s a String that starts with PARAMETER_PREFIX - * @return the JavaExpression for the given parameter or {@code null} if {@code s} is not a - * parameter - */ - private @Nullable JavaExpression getParameterJavaExpression(String s) { - if (!s.startsWith(PARAMETER_PREFIX)) { - return null; - } - if (parameters == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError(s, "no parameters found")); - } - int idx = Integer.parseInt(s.substring(PARAMETER_PREFIX_LENGTH)); - - if (idx == 0) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - "#0", - "Use \"this\" for the receiver or \"#1\" for the first formal" + " parameter")); - } - if (idx > parameters.size()) { - throw new ParseRuntimeException( - new JavaExpressionParseException( - "flowexpr.parse.index.too.big", Integer.toString(idx))); - } - return parameters.get(idx - 1); - } + throw new ParseRuntimeException( + constructJavaExpressionParseError(s, "identifier not found")); + } - /** - * If {@code identifier} is the simple class name of any inner class of {@code type}, return the - * {@link ClassName} for the inner class. If not, return null. - * - * @param type type to search for {@code identifier} - * @param identifier possible simple class name - * @return the {@code ClassName} for {@code identifier}, or null if it is not a simple class - * name - */ - protected @Nullable ClassName getIdentifierAsInnerClassName( - TypeMirror type, String identifier) { - if (type.getKind() != TypeKind.DECLARED) { - return null; - } - - Element outerClass = ((DeclaredType) type).asElement(); - for (Element memberElement : outerClass.getEnclosedElements()) { - if (!(memberElement.getKind().isClass() || memberElement.getKind().isInterface())) { - continue; + /** + * If {@code s} a parameter expressed using the {@code #NN} syntax, then returns a + * JavaExpression for the given parameter; that is, returns an element of {@code + * parameters}. Otherwise, returns {@code null}. + * + * @param s a String that starts with PARAMETER_PREFIX + * @return the JavaExpression for the given parameter or {@code null} if {@code s} is not a + * parameter + */ + private @Nullable JavaExpression getParameterJavaExpression(String s) { + if (!s.startsWith(PARAMETER_PREFIX)) { + return null; + } + if (parameters == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError(s, "no parameters found")); + } + int idx = Integer.parseInt(s.substring(PARAMETER_PREFIX_LENGTH)); + + if (idx == 0) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + "#0", + "Use \"this\" for the receiver or \"#1\" for the first formal" + + " parameter")); + } + if (idx > parameters.size()) { + throw new ParseRuntimeException( + new JavaExpressionParseException( + "flowexpr.parse.index.too.big", Integer.toString(idx))); + } + return parameters.get(idx - 1); } - if (memberElement.getSimpleName().contentEquals(identifier)) { - return new ClassName(ElementUtils.getType(memberElement)); + + /** + * If {@code identifier} is the simple class name of any inner class of {@code type}, return + * the {@link ClassName} for the inner class. If not, return null. + * + * @param type type to search for {@code identifier} + * @param identifier possible simple class name + * @return the {@code ClassName} for {@code identifier}, or null if it is not a simple class + * name + */ + protected @Nullable ClassName getIdentifierAsInnerClassName( + TypeMirror type, String identifier) { + if (type.getKind() != TypeKind.DECLARED) { + return null; + } + + Element outerClass = ((DeclaredType) type).asElement(); + for (Element memberElement : outerClass.getEnclosedElements()) { + if (!(memberElement.getKind().isClass() || memberElement.getKind().isInterface())) { + continue; + } + if (memberElement.getSimpleName().contentEquals(identifier)) { + return new ClassName(ElementUtils.getType(memberElement)); + } + } + return null; } - } - return null; - } - /** - * If {@code identifier} is a class name with that can be referenced using only its simple name - * within {@code enclosingType}, return the {@link ClassName} for the class. If not, return - * null. - * - *

          {@code identifier} may be - * - *

            - *
          1. the simple name of {@code type}. - *
          2. the simple name of a class declared in {@code type} or in an enclosing type of {@code - * type}. - *
          3. the simple name of a class in the java.lang package. - *
          4. the simple name of a class in the unnamed package. - *
          - * - * @param identifier possible class name - * @return the {@code ClassName} for {@code identifier}, or null if it is not a class name - */ - protected @Nullable ClassName getIdentifierAsUnqualifiedClassName(String identifier) { - // Is identifier an inner class of enclosingType or of any enclosing class of - // enclosingType? - TypeMirror searchType = enclosingType; - while (searchType.getKind() == TypeKind.DECLARED) { - DeclaredType searchDeclaredType = (DeclaredType) searchType; - if (searchDeclaredType.asElement().getSimpleName().contentEquals(identifier)) { - return new ClassName(searchType); + /** + * If {@code identifier} is a class name with that can be referenced using only its simple + * name within {@code enclosingType}, return the {@link ClassName} for the class. If not, + * return null. + * + *

          {@code identifier} may be + * + *

            + *
          1. the simple name of {@code type}. + *
          2. the simple name of a class declared in {@code type} or in an enclosing type of + * {@code type}. + *
          3. the simple name of a class in the java.lang package. + *
          4. the simple name of a class in the unnamed package. + *
          + * + * @param identifier possible class name + * @return the {@code ClassName} for {@code identifier}, or null if it is not a class name + */ + protected @Nullable ClassName getIdentifierAsUnqualifiedClassName(String identifier) { + // Is identifier an inner class of enclosingType or of any enclosing class of + // enclosingType? + TypeMirror searchType = enclosingType; + while (searchType.getKind() == TypeKind.DECLARED) { + DeclaredType searchDeclaredType = (DeclaredType) searchType; + if (searchDeclaredType.asElement().getSimpleName().contentEquals(identifier)) { + return new ClassName(searchType); + } + ClassName className = getIdentifierAsInnerClassName(searchType, identifier); + if (className != null) { + return className; + } + searchType = getTypeOfEnclosingClass(searchDeclaredType); + } + + setResolverField(); + + if (enclosingType.getKind() == TypeKind.DECLARED) { + // Is identifier in the same package as this? + PackageSymbol packageSymbol = + (PackageSymbol) + ElementUtils.enclosingPackage( + ((DeclaredType) enclosingType).asElement()); + ClassSymbol classSymbol = + resolver.findClassInPackage( + identifier, packageSymbol, pathToCompilationUnit); + if (classSymbol != null) { + return new ClassName(classSymbol.asType()); + } + } + // Is identifier a simple name for a class in java.lang? + Symbol.PackageSymbol packageSymbol = + resolver.findPackage("java.lang", pathToCompilationUnit); + if (packageSymbol == null) { + throw new BugInCF("Can't find java.lang package."); + } + ClassSymbol classSymbol = + resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); + if (classSymbol != null) { + return new ClassName(classSymbol.asType()); + } + + // Is identifier a class in the unnamed package? + Element classElem = resolver.findClass(identifier, pathToCompilationUnit); + if (classElem != null) { + PackageElement pkg = ElementUtils.enclosingPackage(classElem); + if (pkg != null && pkg.isUnnamed()) { + TypeMirror classType = ElementUtils.getType(classElem); + if (classType != null) { + return new ClassName(classType); + } + } + } + + return null; } - ClassName className = getIdentifierAsInnerClassName(searchType, identifier); - if (className != null) { - return className; + + /** + * Return the {@link FieldAccess} expression for the field with name {@code identifier} + * accessed via {@code receiverExpr}. If no such field exists, then {@code null} is + * returned. + * + * @param receiverExpr the receiver of the field access; the expression used to access the + * field + * @param identifier possibly a field name + * @return a field access, or null if {@code identifier} is not a field that can be accessed + * via {@code receiverExpr} + */ + protected @Nullable FieldAccess getIdentifierAsFieldAccess( + JavaExpression receiverExpr, String identifier) { + setResolverField(); + // Find the field element. + TypeMirror enclosingTypeOfField = receiverExpr.getType(); + VariableElement fieldElem; + if (identifier.equals("length") && enclosingTypeOfField.getKind() == TypeKind.ARRAY) { + fieldElem = + resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); + if (fieldElem == null) { + throw new BugInCF("length field not found for type %s", enclosingTypeOfField); + } + } else { + fieldElem = null; + // Search for field in each enclosing class. + while (enclosingTypeOfField.getKind() == TypeKind.DECLARED) { + fieldElem = + resolver.findField( + identifier, enclosingTypeOfField, pathToCompilationUnit); + if (fieldElem != null) { + break; + } + enclosingTypeOfField = + getTypeOfEnclosingClass((DeclaredType) enclosingTypeOfField); + } + if (fieldElem == null) { + // field not found. + return null; + } + } + + // `fieldElem` is now set. Construct a FieldAccess expression. + + if (ElementUtils.isStatic(fieldElem)) { + Element classElem = fieldElem.getEnclosingElement(); + JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); + return new FieldAccess(staticClassReceiver, fieldElem); + } + + // fieldElem is an instance field. + + if (receiverExpr instanceof ClassName) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + fieldElem.getSimpleName().toString(), + "a non-static field cannot have a class name as a receiver.")); + } + + // There are two possibilities, captured by local variable fieldDeclaredInReceiverType: + // * true: it's an instance field declared in the type (or supertype) of receiverExpr. + // * false: it's an instance field declared in an enclosing type of receiverExpr. + + @SuppressWarnings("interning:not.interned") // Checking for exact object + boolean fieldDeclaredInReceiverType = enclosingTypeOfField == receiverExpr.getType(); + if (fieldDeclaredInReceiverType) { + TypeMirror fieldType = ElementUtils.getType(fieldElem); + return new FieldAccess(receiverExpr, fieldType, fieldElem); + } else { + if (!(receiverExpr instanceof ThisReference)) { + String msg = + String.format( + "%s is declared in an outer type of the type of the receiver" + + " expression, %s.", + identifier, receiverExpr); + throw new ParseRuntimeException( + constructJavaExpressionParseError(identifier, msg)); + } + TypeElement receiverTypeElement = TypesUtils.getTypeElement(receiverExpr.getType()); + if (receiverTypeElement == null || ElementUtils.isStatic(receiverTypeElement)) { + String msg = + String.format( + "%s is a non-static field declared in an outer type this.", + identifier); + throw new ParseRuntimeException( + constructJavaExpressionParseError(identifier, msg)); + } + JavaExpression locationOfField = new ThisReference(enclosingTypeOfField); + return new FieldAccess(locationOfField, fieldElem); + } } - searchType = getTypeOfEnclosingClass(searchDeclaredType); - } - - setResolverField(); - - if (enclosingType.getKind() == TypeKind.DECLARED) { - // Is identifier in the same package as this? - PackageSymbol packageSymbol = - (PackageSymbol) - ElementUtils.enclosingPackage(((DeclaredType) enclosingType).asElement()); - ClassSymbol classSymbol = - resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); - if (classSymbol != null) { - return new ClassName(classSymbol.asType()); + + @Override + public JavaExpression visit(MethodCallExpr expr, Void aVoid) { + setResolverField(); + + JavaExpression receiverExpr; + if (expr.getScope().isPresent()) { + receiverExpr = expr.getScope().get().accept(this, null); + expr = expr.removeScope(); + } else if (thisReference != null) { + receiverExpr = thisReference; + } else { + receiverExpr = new ClassName(enclosingType); + } + + String methodName = expr.getNameAsString(); + + // parse argument list + List arguments = + CollectionsPlume.mapList( + argument -> argument.accept(this, null), expr.getArguments()); + + ExecutableElement methodElement; + try { + methodElement = + getMethodElement( + methodName, + receiverExpr.getType(), + pathToCompilationUnit, + arguments, + resolver); + } catch (JavaExpressionParseException e) { + throw new ParseRuntimeException(e); + } + + // Box any arguments that require it. + for (int i = 0; i < arguments.size(); i++) { + VariableElement parameter = methodElement.getParameters().get(i); + TypeMirror parameterType = parameter.asType(); + JavaExpression argument = arguments.get(i); + TypeMirror argumentType = argument.getType(); + // is boxing necessary? + if (TypesUtils.isBoxedPrimitive(parameterType) + && TypesUtils.isPrimitive(argumentType)) { + // boxing is necessary + MethodSymbol valueOfMethod = TreeBuilder.getValueOfMethod(env, parameterType); + JavaExpression boxedParam = + new MethodCall( + parameterType, + valueOfMethod, + new ClassName(parameterType), + Collections.singletonList(argument)); + arguments.set(i, boxedParam); + } + } + + // Build the MethodCall expression object. + if (ElementUtils.isStatic(methodElement)) { + Element classElem = methodElement.getEnclosingElement(); + JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); + return new MethodCall( + ElementUtils.getType(methodElement), + methodElement, + staticClassReceiver, + arguments); + } else { + if (receiverExpr instanceof ClassName) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + "a non-static method call cannot have a class name as a" + + " receiver")); + } + TypeMirror methodType = + TypesUtils.substituteMethodReturnType( + methodElement, receiverExpr.getType(), env); + return new MethodCall(methodType, methodElement, receiverExpr, arguments); + } } - } - // Is identifier a simple name for a class in java.lang? - Symbol.PackageSymbol packageSymbol = resolver.findPackage("java.lang", pathToCompilationUnit); - if (packageSymbol == null) { - throw new BugInCF("Can't find java.lang package."); - } - ClassSymbol classSymbol = - resolver.findClassInPackage(identifier, packageSymbol, pathToCompilationUnit); - if (classSymbol != null) { - return new ClassName(classSymbol.asType()); - } - - // Is identifier a class in the unnamed package? - Element classElem = resolver.findClass(identifier, pathToCompilationUnit); - if (classElem != null) { - PackageElement pkg = ElementUtils.enclosingPackage(classElem); - if (pkg != null && pkg.isUnnamed()) { - TypeMirror classType = ElementUtils.getType(classElem); - if (classType != null) { - return new ClassName(classType); - } + + /** + * Returns the ExecutableElement for a method, or throws an exception. + * + *

          (This method takes into account autoboxing.) + * + * @param methodName the method name + * @param receiverType the receiver type + * @param pathToCompilationUnit the path to the compilation unit + * @param arguments the arguments + * @param resolver the resolver + * @return the ExecutableElement for a method, or throws an exception + * @throws JavaExpressionParseException if the string cannot be parsed as a method name + */ + private ExecutableElement getMethodElement( + String methodName, + TypeMirror receiverType, + TreePath pathToCompilationUnit, + List arguments, + Resolver resolver) + throws JavaExpressionParseException { + + List argumentTypes = + CollectionsPlume.mapList(JavaExpression::getType, arguments); + + if (receiverType.getKind() == TypeKind.ARRAY) { + ExecutableElement element = + resolver.findMethod( + methodName, receiverType, pathToCompilationUnit, argumentTypes); + if (element == null) { + throw constructJavaExpressionParseError(methodName, "no such method"); + } + return element; + } + + // Search for method in each enclosing class. + while (receiverType.getKind() == TypeKind.DECLARED) { + ExecutableElement element = + resolver.findMethod( + methodName, receiverType, pathToCompilationUnit, argumentTypes); + if (element != null) { + return element; + } + receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType); + } + + // Method not found. + throw constructJavaExpressionParseError(methodName, "no such method"); } - } - return null; - } + // `expr` should be a field access, a fully qualified class name, or a class name qualified + // with another class name (e.g. {@code OuterClass.InnerClass}). If the expression refers + // to a class that is not available to the resolver (the class wasn't passed to javac on + // the command line), then the argument can be "outerpackage.innerpackage", which will lead + // to a confusing error message. + @Override + public JavaExpression visit(FieldAccessExpr expr, Void aVoid) { + setResolverField(); + + Expression scope = expr.getScope(); + String name = expr.getNameAsString(); + + // Check for fully qualified class name. + Symbol.PackageSymbol packageSymbol = + resolver.findPackage(scope.toString(), pathToCompilationUnit); + if (packageSymbol != null) { + ClassSymbol classSymbol = + resolver.findClassInPackage(name, packageSymbol, pathToCompilationUnit); + if (classSymbol != null) { + return new ClassName(classSymbol.asType()); + } + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + "could not find class " + + expr.getNameAsString() + + " in package " + + scope.toString())); + } + + JavaExpression receiver = scope.accept(this, null); + + // Check for field access expression. + FieldAccess fieldAccess = getIdentifierAsFieldAccess(receiver, name); + if (fieldAccess != null) { + return fieldAccess; + } + + // Check for inner class. + ClassName classType = getIdentifierAsInnerClassName(receiver.getType(), name); + if (classType != null) { + return classType; + } - /** - * Return the {@link FieldAccess} expression for the field with name {@code identifier} accessed - * via {@code receiverExpr}. If no such field exists, then {@code null} is returned. - * - * @param receiverExpr the receiver of the field access; the expression used to access the field - * @param identifier possibly a field name - * @return a field access, or null if {@code identifier} is not a field that can be accessed via - * {@code receiverExpr} - */ - protected @Nullable FieldAccess getIdentifierAsFieldAccess( - JavaExpression receiverExpr, String identifier) { - setResolverField(); - // Find the field element. - TypeMirror enclosingTypeOfField = receiverExpr.getType(); - VariableElement fieldElem; - if (identifier.equals("length") && enclosingTypeOfField.getKind() == TypeKind.ARRAY) { - fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); - if (fieldElem == null) { - throw new BugInCF("length field not found for type %s", enclosingTypeOfField); - } - } else { - fieldElem = null; - // Search for field in each enclosing class. - while (enclosingTypeOfField.getKind() == TypeKind.DECLARED) { - fieldElem = resolver.findField(identifier, enclosingTypeOfField, pathToCompilationUnit); - if (fieldElem != null) { - break; - } - enclosingTypeOfField = getTypeOfEnclosingClass((DeclaredType) enclosingTypeOfField); - } - if (fieldElem == null) { - // field not found. - return null; - } - } - - // `fieldElem` is now set. Construct a FieldAccess expression. - - if (ElementUtils.isStatic(fieldElem)) { - Element classElem = fieldElem.getEnclosingElement(); - JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); - return new FieldAccess(staticClassReceiver, fieldElem); - } - - // fieldElem is an instance field. - - if (receiverExpr instanceof ClassName) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - fieldElem.getSimpleName().toString(), - "a non-static field cannot have a class name as a receiver.")); - } - - // There are two possibilities, captured by local variable fieldDeclaredInReceiverType: - // * true: it's an instance field declared in the type (or supertype) of receiverExpr. - // * false: it's an instance field declared in an enclosing type of receiverExpr. - - @SuppressWarnings("interning:not.interned") // Checking for exact object - boolean fieldDeclaredInReceiverType = enclosingTypeOfField == receiverExpr.getType(); - if (fieldDeclaredInReceiverType) { - TypeMirror fieldType = ElementUtils.getType(fieldElem); - return new FieldAccess(receiverExpr, fieldType, fieldElem); - } else { - if (!(receiverExpr instanceof ThisReference)) { - String msg = - String.format( - "%s is declared in an outer type of the type of the receiver" - + " expression, %s.", - identifier, receiverExpr); - throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg)); - } - TypeElement receiverTypeElement = TypesUtils.getTypeElement(receiverExpr.getType()); - if (receiverTypeElement == null || ElementUtils.isStatic(receiverTypeElement)) { - String msg = - String.format("%s is a non-static field declared in an outer type this.", identifier); - throw new ParseRuntimeException(constructJavaExpressionParseError(identifier, msg)); + throw new ParseRuntimeException( + constructJavaExpressionParseError( + name, + String.format("field or class %s not found in %s", name, receiver))); } - JavaExpression locationOfField = new ThisReference(enclosingTypeOfField); - return new FieldAccess(locationOfField, fieldElem); - } - } - @Override - public JavaExpression visit(MethodCallExpr expr, Void aVoid) { - setResolverField(); - - JavaExpression receiverExpr; - if (expr.getScope().isPresent()) { - receiverExpr = expr.getScope().get().accept(this, null); - expr = expr.removeScope(); - } else if (thisReference != null) { - receiverExpr = thisReference; - } else { - receiverExpr = new ClassName(enclosingType); - } - - String methodName = expr.getNameAsString(); - - // parse argument list - List arguments = - CollectionsPlume.mapList(argument -> argument.accept(this, null), expr.getArguments()); - - ExecutableElement methodElement; - try { - methodElement = - getMethodElement( - methodName, receiverExpr.getType(), pathToCompilationUnit, arguments, resolver); - } catch (JavaExpressionParseException e) { - throw new ParseRuntimeException(e); - } - - // Box any arguments that require it. - for (int i = 0; i < arguments.size(); i++) { - VariableElement parameter = methodElement.getParameters().get(i); - TypeMirror parameterType = parameter.asType(); - JavaExpression argument = arguments.get(i); - TypeMirror argumentType = argument.getType(); - // is boxing necessary? - if (TypesUtils.isBoxedPrimitive(parameterType) && TypesUtils.isPrimitive(argumentType)) { - // boxing is necessary - MethodSymbol valueOfMethod = TreeBuilder.getValueOfMethod(env, parameterType); - JavaExpression boxedParam = - new MethodCall( - parameterType, - valueOfMethod, - new ClassName(parameterType), - Collections.singletonList(argument)); - arguments.set(i, boxedParam); - } - } - - // Build the MethodCall expression object. - if (ElementUtils.isStatic(methodElement)) { - Element classElem = methodElement.getEnclosingElement(); - JavaExpression staticClassReceiver = new ClassName(ElementUtils.getType(classElem)); - return new MethodCall( - ElementUtils.getType(methodElement), methodElement, staticClassReceiver, arguments); - } else { - if (receiverExpr instanceof ClassName) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - "a non-static method call cannot have a class name as a" + " receiver")); + // expr is a Class literal + @Override + public JavaExpression visit(ClassExpr expr, Void aVoid) { + TypeMirror result = convertTypeToTypeMirror(expr.getType()); + if (result == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), "it is an unparsable class literal")); + } + return new ClassName(result); } - TypeMirror methodType = - TypesUtils.substituteMethodReturnType(methodElement, receiverExpr.getType(), env); - return new MethodCall(methodType, methodElement, receiverExpr, arguments); - } - } - /** - * Returns the ExecutableElement for a method, or throws an exception. - * - *

          (This method takes into account autoboxing.) - * - * @param methodName the method name - * @param receiverType the receiver type - * @param pathToCompilationUnit the path to the compilation unit - * @param arguments the arguments - * @param resolver the resolver - * @return the ExecutableElement for a method, or throws an exception - * @throws JavaExpressionParseException if the string cannot be parsed as a method name - */ - private ExecutableElement getMethodElement( - String methodName, - TypeMirror receiverType, - TreePath pathToCompilationUnit, - List arguments, - Resolver resolver) - throws JavaExpressionParseException { - - List argumentTypes = CollectionsPlume.mapList(JavaExpression::getType, arguments); - - if (receiverType.getKind() == TypeKind.ARRAY) { - ExecutableElement element = - resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes); - if (element == null) { - throw constructJavaExpressionParseError(methodName, "no such method"); + @Override + public JavaExpression visit(ArrayCreationExpr expr, Void aVoid) { + List<@Nullable JavaExpression> dimensions = + CollectionsPlume.mapList( + (ArrayCreationLevel dimension) -> + dimension + .getDimension() + .map(dim -> dim.accept(this, aVoid)) + .orElse(null), + expr.getLevels()); + + List initializers; + if (expr.getInitializer().isPresent()) { + initializers = + CollectionsPlume.mapList( + (Expression initializer) -> initializer.accept(this, null), + expr.getInitializer().get().getValues()); + } else { + initializers = Collections.emptyList(); + } + TypeMirror arrayType = convertTypeToTypeMirror(expr.getElementType()); + if (arrayType == null) { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.getElementType().asString(), "type not parsable")); + } + for (int i = 0; i < dimensions.size(); i++) { + arrayType = TypesUtils.createArrayType(arrayType, env.getTypeUtils()); + } + return new ArrayCreation(arrayType, dimensions, initializers); } - return element; - } - - // Search for method in each enclosing class. - while (receiverType.getKind() == TypeKind.DECLARED) { - ExecutableElement element = - resolver.findMethod(methodName, receiverType, pathToCompilationUnit, argumentTypes); - if (element != null) { - return element; - } - receiverType = getTypeOfEnclosingClass((DeclaredType) receiverType); - } - // Method not found. - throw constructJavaExpressionParseError(methodName, "no such method"); - } + @Override + public JavaExpression visit(UnaryExpr expr, Void aVoid) { + Tree.Kind treeKind = javaParserUnaryOperatorToTreeKind(expr.getOperator()); + JavaExpression operand = expr.getExpression().accept(this, null); + // This eliminates + and performs constant-folding for -; it could also do so for other + // operations. + switch (treeKind) { + case UNARY_PLUS: + return operand; + case UNARY_MINUS: + if (operand instanceof ValueLiteral) { + return ((ValueLiteral) operand).negate(); + } + break; + default: + // Not optimization for this operand + break; + } + return new UnaryOperation(operand.getType(), treeKind, operand); + } - // `expr` should be a field access, a fully qualified class name, or a class name qualified - // with another class name (e.g. {@code OuterClass.InnerClass}). If the expression refers - // to a class that is not available to the resolver (the class wasn't passed to javac on - // the command line), then the argument can be "outerpackage.innerpackage", which will lead - // to a confusing error message. - @Override - public JavaExpression visit(FieldAccessExpr expr, Void aVoid) { - setResolverField(); - - Expression scope = expr.getScope(); - String name = expr.getNameAsString(); - - // Check for fully qualified class name. - Symbol.PackageSymbol packageSymbol = - resolver.findPackage(scope.toString(), pathToCompilationUnit); - if (packageSymbol != null) { - ClassSymbol classSymbol = - resolver.findClassInPackage(name, packageSymbol, pathToCompilationUnit); - if (classSymbol != null) { - return new ClassName(classSymbol.asType()); + /** + * Convert a JavaParser unary operator to a TreeKind. + * + * @param op a JavaParser unary operator + * @return a TreeKind for the unary operator + */ + private Tree.Kind javaParserUnaryOperatorToTreeKind(UnaryExpr.Operator op) { + switch (op) { + case BITWISE_COMPLEMENT: + return Tree.Kind.BITWISE_COMPLEMENT; + case LOGICAL_COMPLEMENT: + return Tree.Kind.LOGICAL_COMPLEMENT; + case MINUS: + return Tree.Kind.UNARY_MINUS; + case PLUS: + return Tree.Kind.UNARY_PLUS; + case POSTFIX_DECREMENT: + return Tree.Kind.POSTFIX_DECREMENT; + case POSTFIX_INCREMENT: + return Tree.Kind.POSTFIX_INCREMENT; + case PREFIX_DECREMENT: + return Tree.Kind.PREFIX_DECREMENT; + case PREFIX_INCREMENT: + return Tree.Kind.PREFIX_INCREMENT; + default: + throw new BugInCF("unhandled " + op); + } } - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - "could not find class " - + expr.getNameAsString() - + " in package " - + scope.toString())); - } - - JavaExpression receiver = scope.accept(this, null); - - // Check for field access expression. - FieldAccess fieldAccess = getIdentifierAsFieldAccess(receiver, name); - if (fieldAccess != null) { - return fieldAccess; - } - - // Check for inner class. - ClassName classType = getIdentifierAsInnerClassName(receiver.getType(), name); - if (classType != null) { - return classType; - } - - throw new ParseRuntimeException( - constructJavaExpressionParseError( - name, String.format("field or class %s not found in %s", name, receiver))); - } - // expr is a Class literal - @Override - public JavaExpression visit(ClassExpr expr, Void aVoid) { - TypeMirror result = convertTypeToTypeMirror(expr.getType()); - if (result == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), "it is an unparsable class literal")); - } - return new ClassName(result); - } + @Override + public JavaExpression visit(BinaryExpr expr, Void aVoid) { + JavaExpression leftJe = expr.getLeft().accept(this, null); + JavaExpression rightJe = expr.getRight().accept(this, null); + TypeMirror leftType = leftJe.getType(); + TypeMirror rightType = rightJe.getType(); + TypeMirror type; + // isSubtype() first does the cheaper test isSameType(), so no need to do it here. + if (types.isSubtype(leftType, rightType)) { + type = rightType; + } else if (types.isSubtype(rightType, leftType)) { + type = leftType; + } else if (expr.getOperator() == BinaryExpr.Operator.PLUS + && (TypesUtils.isString(leftType) || TypesUtils.isString(rightType))) { + type = stringTypeMirror; + } else { + throw new ParseRuntimeException( + constructJavaExpressionParseError( + expr.toString(), + String.format( + "inconsistent types %s %s for %s", + leftType, rightType, expr))); + } + return new BinaryOperation( + type, javaParserBinaryOperatorToTreeKind(expr.getOperator()), leftJe, rightJe); + } - @Override - public JavaExpression visit(ArrayCreationExpr expr, Void aVoid) { - List<@Nullable JavaExpression> dimensions = - CollectionsPlume.mapList( - (ArrayCreationLevel dimension) -> - dimension.getDimension().map(dim -> dim.accept(this, aVoid)).orElse(null), - expr.getLevels()); - - List initializers; - if (expr.getInitializer().isPresent()) { - initializers = - CollectionsPlume.mapList( - (Expression initializer) -> initializer.accept(this, null), - expr.getInitializer().get().getValues()); - } else { - initializers = Collections.emptyList(); - } - TypeMirror arrayType = convertTypeToTypeMirror(expr.getElementType()); - if (arrayType == null) { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.getElementType().asString(), "type not parsable")); - } - for (int i = 0; i < dimensions.size(); i++) { - arrayType = TypesUtils.createArrayType(arrayType, env.getTypeUtils()); - } - return new ArrayCreation(arrayType, dimensions, initializers); - } + /** + * Convert a JavaParser binary operator to a TreeKind. + * + * @param op a JavaParser binary operator + * @return a TreeKind for the binary operator + */ + private Tree.Kind javaParserBinaryOperatorToTreeKind(BinaryExpr.Operator op) { + switch (op) { + case AND: + return Tree.Kind.CONDITIONAL_AND; + case BINARY_AND: + return Tree.Kind.AND; + case BINARY_OR: + return Tree.Kind.OR; + case DIVIDE: + return Tree.Kind.DIVIDE; + case EQUALS: + return Tree.Kind.EQUAL_TO; + case GREATER: + return Tree.Kind.GREATER_THAN; + case GREATER_EQUALS: + return Tree.Kind.GREATER_THAN_EQUAL; + case LEFT_SHIFT: + return Tree.Kind.LEFT_SHIFT; + case LESS: + return Tree.Kind.LESS_THAN; + case LESS_EQUALS: + return Tree.Kind.LESS_THAN_EQUAL; + case MINUS: + return Tree.Kind.MINUS; + case MULTIPLY: + return Tree.Kind.MULTIPLY; + case NOT_EQUALS: + return Tree.Kind.NOT_EQUAL_TO; + case OR: + return Tree.Kind.CONDITIONAL_OR; + case PLUS: + return Tree.Kind.PLUS; + case REMAINDER: + return Tree.Kind.REMAINDER; + case SIGNED_RIGHT_SHIFT: + return Tree.Kind.RIGHT_SHIFT; + case UNSIGNED_RIGHT_SHIFT: + return Tree.Kind.UNSIGNED_RIGHT_SHIFT; + case XOR: + return Tree.Kind.XOR; + default: + throw new BugInCF("unhandled " + op); + } + } - @Override - public JavaExpression visit(UnaryExpr expr, Void aVoid) { - Tree.Kind treeKind = javaParserUnaryOperatorToTreeKind(expr.getOperator()); - JavaExpression operand = expr.getExpression().accept(this, null); - // This eliminates + and performs constant-folding for -; it could also do so for other - // operations. - switch (treeKind) { - case UNARY_PLUS: - return operand; - case UNARY_MINUS: - if (operand instanceof ValueLiteral) { - return ((ValueLiteral) operand).negate(); - } - break; - default: - // Not optimization for this operand - break; - } - return new UnaryOperation(operand.getType(), treeKind, operand); + /** + * Converts the JavaParser type to a TypeMirror. Returns null if {@code type} is not + * handled; this method does not handle type variables, union types, or intersection types. + * + * @param type a JavaParser type + * @return a TypeMirror corresponding to {@code type}, or null if {@code type} isn't handled + */ + private @Nullable TypeMirror convertTypeToTypeMirror(Type type) { + if (type.isClassOrInterfaceType()) { + LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); + try { + return JavaParserUtil.parseExpression(type.asString(), currentSourceVersion) + .accept(this, null) + .getType(); + } catch (ParseProblemException e) { + return null; + } + } else if (type.isPrimitiveType()) { + switch (type.asPrimitiveType().getType()) { + case BOOLEAN: + return types.getPrimitiveType(TypeKind.BOOLEAN); + case BYTE: + return types.getPrimitiveType(TypeKind.BYTE); + case SHORT: + return types.getPrimitiveType(TypeKind.SHORT); + case INT: + return types.getPrimitiveType(TypeKind.INT); + case CHAR: + return types.getPrimitiveType(TypeKind.CHAR); + case FLOAT: + return types.getPrimitiveType(TypeKind.FLOAT); + case LONG: + return types.getPrimitiveType(TypeKind.LONG); + case DOUBLE: + return types.getPrimitiveType(TypeKind.DOUBLE); + } + } else if (type.isVoidType()) { + return types.getNoType(TypeKind.VOID); + } else if (type.isArrayType()) { + TypeMirror componentType = + convertTypeToTypeMirror(type.asArrayType().getComponentType()); + if (componentType == null) { + return null; + } + return types.getArrayType(componentType); + } + return null; + } } /** - * Convert a JavaParser unary operator to a TreeKind. + * If {@code s} is exactly a formal parameter, return its 1-based index. Returns -1 otherwise. * - * @param op a JavaParser unary operator - * @return a TreeKind for the unary operator + * @param s a Java expression + * @return the 1-based index of the formal parameter that {@code s} represents, or -1 */ - private Tree.Kind javaParserUnaryOperatorToTreeKind(UnaryExpr.Operator op) { - switch (op) { - case BITWISE_COMPLEMENT: - return Tree.Kind.BITWISE_COMPLEMENT; - case LOGICAL_COMPLEMENT: - return Tree.Kind.LOGICAL_COMPLEMENT; - case MINUS: - return Tree.Kind.UNARY_MINUS; - case PLUS: - return Tree.Kind.UNARY_PLUS; - case POSTFIX_DECREMENT: - return Tree.Kind.POSTFIX_DECREMENT; - case POSTFIX_INCREMENT: - return Tree.Kind.POSTFIX_INCREMENT; - case PREFIX_DECREMENT: - return Tree.Kind.PREFIX_DECREMENT; - case PREFIX_INCREMENT: - return Tree.Kind.PREFIX_INCREMENT; - default: - throw new BugInCF("unhandled " + op); - } + public static int parameterIndex(String s) { + Matcher matcher = ANCHORED_PARAMETER_PATTERN.matcher(s); + if (matcher.find()) { + @SuppressWarnings( + "nullness:assignment") // group 1 is non-null due to the structure of the regex + @NonNull String group1 = matcher.group(1); + return Integer.parseInt(group1); + } + return -1; } - @Override - public JavaExpression visit(BinaryExpr expr, Void aVoid) { - JavaExpression leftJe = expr.getLeft().accept(this, null); - JavaExpression rightJe = expr.getRight().accept(this, null); - TypeMirror leftType = leftJe.getType(); - TypeMirror rightType = rightJe.getType(); - TypeMirror type; - // isSubtype() first does the cheaper test isSameType(), so no need to do it here. - if (types.isSubtype(leftType, rightType)) { - type = rightType; - } else if (types.isSubtype(rightType, leftType)) { - type = leftType; - } else if (expr.getOperator() == BinaryExpr.Operator.PLUS - && (TypesUtils.isString(leftType) || TypesUtils.isString(rightType))) { - type = stringTypeMirror; - } else { - throw new ParseRuntimeException( - constructJavaExpressionParseError( - expr.toString(), - String.format("inconsistent types %s %s for %s", leftType, rightType, expr))); - } - return new BinaryOperation( - type, javaParserBinaryOperatorToTreeKind(expr.getOperator()), leftJe, rightJe); - } + /////////////////////////////////////////////////////////////////////////// + /// Contexts + /// /** - * Convert a JavaParser binary operator to a TreeKind. + * Returns the type of the innermost enclosing class. Returns Type.noType if the type is a + * top-level class. * - * @param op a JavaParser binary operator - * @return a TreeKind for the binary operator + *

          If the innermost enclosing class is static, this method returns the type of that class. By + * contrast, {@link DeclaredType#getEnclosingType()} returns the type of the innermost enclosing + * class that is not static. + * + * @param type a DeclaredType + * @return the type of the innermost enclosing class or Type.noType */ - private Tree.Kind javaParserBinaryOperatorToTreeKind(BinaryExpr.Operator op) { - switch (op) { - case AND: - return Tree.Kind.CONDITIONAL_AND; - case BINARY_AND: - return Tree.Kind.AND; - case BINARY_OR: - return Tree.Kind.OR; - case DIVIDE: - return Tree.Kind.DIVIDE; - case EQUALS: - return Tree.Kind.EQUAL_TO; - case GREATER: - return Tree.Kind.GREATER_THAN; - case GREATER_EQUALS: - return Tree.Kind.GREATER_THAN_EQUAL; - case LEFT_SHIFT: - return Tree.Kind.LEFT_SHIFT; - case LESS: - return Tree.Kind.LESS_THAN; - case LESS_EQUALS: - return Tree.Kind.LESS_THAN_EQUAL; - case MINUS: - return Tree.Kind.MINUS; - case MULTIPLY: - return Tree.Kind.MULTIPLY; - case NOT_EQUALS: - return Tree.Kind.NOT_EQUAL_TO; - case OR: - return Tree.Kind.CONDITIONAL_OR; - case PLUS: - return Tree.Kind.PLUS; - case REMAINDER: - return Tree.Kind.REMAINDER; - case SIGNED_RIGHT_SHIFT: - return Tree.Kind.RIGHT_SHIFT; - case UNSIGNED_RIGHT_SHIFT: - return Tree.Kind.UNSIGNED_RIGHT_SHIFT; - case XOR: - return Tree.Kind.XOR; - default: - throw new BugInCF("unhandled " + op); - } + private static TypeMirror getTypeOfEnclosingClass(DeclaredType type) { + if (type instanceof ClassType) { + // enclClass() needs to be called on tsym.owner, because tsym.enclClass() == tsym. + Symbol sym = ((ClassType) type).tsym.owner; + if (sym == null) { + return com.sun.tools.javac.code.Type.noType; + } + + ClassSymbol cs = sym.enclClass(); + if (cs == null) { + return com.sun.tools.javac.code.Type.noType; + } + + return cs.asType(); + } else { + return type.getEnclosingType(); + } } + /////////////////////////////////////////////////////////////////////////// + /// Exceptions + /// + /** - * Converts the JavaParser type to a TypeMirror. Returns null if {@code type} is not handled; - * this method does not handle type variables, union types, or intersection types. - * - * @param type a JavaParser type - * @return a TypeMirror corresponding to {@code type}, or null if {@code type} isn't handled + * An exception that indicates a parse error. Call {@link #getDiagMessage} to obtain a {@link + * DiagMessage} that can be used for error reporting. */ - private @Nullable TypeMirror convertTypeToTypeMirror(Type type) { - if (type.isClassOrInterfaceType()) { - LanguageLevel currentSourceVersion = JavaParserUtil.getCurrentSourceVersion(env); - try { - return JavaParserUtil.parseExpression(type.asString(), currentSourceVersion) - .accept(this, null) - .getType(); - } catch (ParseProblemException e) { - return null; - } - } else if (type.isPrimitiveType()) { - switch (type.asPrimitiveType().getType()) { - case BOOLEAN: - return types.getPrimitiveType(TypeKind.BOOLEAN); - case BYTE: - return types.getPrimitiveType(TypeKind.BYTE); - case SHORT: - return types.getPrimitiveType(TypeKind.SHORT); - case INT: - return types.getPrimitiveType(TypeKind.INT); - case CHAR: - return types.getPrimitiveType(TypeKind.CHAR); - case FLOAT: - return types.getPrimitiveType(TypeKind.FLOAT); - case LONG: - return types.getPrimitiveType(TypeKind.LONG); - case DOUBLE: - return types.getPrimitiveType(TypeKind.DOUBLE); - } - } else if (type.isVoidType()) { - return types.getNoType(TypeKind.VOID); - } else if (type.isArrayType()) { - TypeMirror componentType = convertTypeToTypeMirror(type.asArrayType().getComponentType()); - if (componentType == null) { - return null; + public static class JavaExpressionParseException extends Exception { + /** The serial version identifier. */ + private static final long serialVersionUID = 2L; + + /** The error message key. */ + private final @CompilerMessageKey String errorKey; + + /** The arguments to the error message key. */ + @SuppressWarnings( + "serial") // I do not intend to serialize JavaExpressionParseException objects + public final Object[] args; + + /** + * Create a new JavaExpressionParseException. + * + * @param errorKey the error message key + * @param args the arguments to the error message key + */ + public JavaExpressionParseException(@CompilerMessageKey String errorKey, Object... args) { + this(null, errorKey, args); } - return types.getArrayType(componentType); - } - return null; - } - } - - /** - * If {@code s} is exactly a formal parameter, return its 1-based index. Returns -1 otherwise. - * - * @param s a Java expression - * @return the 1-based index of the formal parameter that {@code s} represents, or -1 - */ - public static int parameterIndex(String s) { - Matcher matcher = ANCHORED_PARAMETER_PATTERN.matcher(s); - if (matcher.find()) { - @SuppressWarnings( - "nullness:assignment") // group 1 is non-null due to the structure of the regex - @NonNull String group1 = matcher.group(1); - return Integer.parseInt(group1); - } - return -1; - } - - /////////////////////////////////////////////////////////////////////////// - /// Contexts - /// - - /** - * Returns the type of the innermost enclosing class. Returns Type.noType if the type is a - * top-level class. - * - *

          If the innermost enclosing class is static, this method returns the type of that class. By - * contrast, {@link DeclaredType#getEnclosingType()} returns the type of the innermost enclosing - * class that is not static. - * - * @param type a DeclaredType - * @return the type of the innermost enclosing class or Type.noType - */ - private static TypeMirror getTypeOfEnclosingClass(DeclaredType type) { - if (type instanceof ClassType) { - // enclClass() needs to be called on tsym.owner, because tsym.enclClass() == tsym. - Symbol sym = ((ClassType) type).tsym.owner; - if (sym == null) { - return com.sun.tools.javac.code.Type.noType; - } - - ClassSymbol cs = sym.enclClass(); - if (cs == null) { - return com.sun.tools.javac.code.Type.noType; - } - - return cs.asType(); - } else { - return type.getEnclosingType(); - } - } - - /////////////////////////////////////////////////////////////////////////// - /// Exceptions - /// - /** - * An exception that indicates a parse error. Call {@link #getDiagMessage} to obtain a {@link - * DiagMessage} that can be used for error reporting. - */ - public static class JavaExpressionParseException extends Exception { - /** The serial version identifier. */ - private static final long serialVersionUID = 2L; + /** + * Create a new JavaExpressionParseException. + * + * @param cause cause + * @param errorKey the error message key + * @param args the arguments to the error message key + */ + public JavaExpressionParseException( + @Nullable Throwable cause, @CompilerMessageKey String errorKey, Object... args) { + super(cause); + this.errorKey = errorKey; + this.args = args; + } - /** The error message key. */ - private final @CompilerMessageKey String errorKey; + @Override + public String getMessage() { + return errorKey + " " + Arrays.toString(args); + } - /** The arguments to the error message key. */ - @SuppressWarnings("serial") // I do not intend to serialize JavaExpressionParseException objects - public final Object[] args; + /** + * Return a DiagMessage that can be used for error reporting. + * + * @return a DiagMessage that can be used for error reporting + */ + public DiagMessage getDiagMessage() { + return new DiagMessage(Diagnostic.Kind.ERROR, errorKey, args); + } - /** - * Create a new JavaExpressionParseException. - * - * @param errorKey the error message key - * @param args the arguments to the error message key - */ - public JavaExpressionParseException(@CompilerMessageKey String errorKey, Object... args) { - this(null, errorKey, args); + public boolean isFlowParseError() { + return errorKey.endsWith("flowexpr.parse.error"); + } } /** - * Create a new JavaExpressionParseException. + * Returns a {@link JavaExpressionParseException} with error key "flowexpr.parse.error" for the + * expression {@code expr} with explanation {@code explanation}. * - * @param cause cause - * @param errorKey the error message key - * @param args the arguments to the error message key + * @param expr the string that could not be parsed + * @param explanation an explanation of the parse failure + * @return a {@link JavaExpressionParseException} for the expression {@code expr} with + * explanation {@code explanation}. */ - public JavaExpressionParseException( - @Nullable Throwable cause, @CompilerMessageKey String errorKey, Object... args) { - super(cause); - this.errorKey = errorKey; - this.args = args; - } - - @Override - public String getMessage() { - return errorKey + " " + Arrays.toString(args); + private static JavaExpressionParseException constructJavaExpressionParseError( + String expr, String explanation) { + if (expr == null) { + throw new BugInCF("Must have an expression."); + } + if (explanation == null) { + throw new BugInCF("Must have an explanation."); + } + return new JavaExpressionParseException( + (Throwable) null, + "flowexpr.parse.error", + "Invalid '" + expr + "' because " + explanation); } /** - * Return a DiagMessage that can be used for error reporting. - * - * @return a DiagMessage that can be used for error reporting + * The unchecked exception equivalent of checked exception {@link JavaExpressionParseException}. */ - public DiagMessage getDiagMessage() { - return new DiagMessage(Diagnostic.Kind.ERROR, errorKey, args); - } + private static class ParseRuntimeException extends RuntimeException { + private static final long serialVersionUID = 2L; + private final JavaExpressionParseException exception; - public boolean isFlowParseError() { - return errorKey.endsWith("flowexpr.parse.error"); - } - } - - /** - * Returns a {@link JavaExpressionParseException} with error key "flowexpr.parse.error" for the - * expression {@code expr} with explanation {@code explanation}. - * - * @param expr the string that could not be parsed - * @param explanation an explanation of the parse failure - * @return a {@link JavaExpressionParseException} for the expression {@code expr} with explanation - * {@code explanation}. - */ - private static JavaExpressionParseException constructJavaExpressionParseError( - String expr, String explanation) { - if (expr == null) { - throw new BugInCF("Must have an expression."); - } - if (explanation == null) { - throw new BugInCF("Must have an explanation."); - } - return new JavaExpressionParseException( - (Throwable) null, "flowexpr.parse.error", "Invalid '" + expr + "' because " + explanation); - } - - /** - * The unchecked exception equivalent of checked exception {@link JavaExpressionParseException}. - */ - private static class ParseRuntimeException extends RuntimeException { - private static final long serialVersionUID = 2L; - private final JavaExpressionParseException exception; - - private ParseRuntimeException(JavaExpressionParseException exception) { - this.exception = exception; - } + private ParseRuntimeException(JavaExpressionParseException exception) { + this.exception = exception; + } - private JavaExpressionParseException getCheckedException() { - return exception; + private JavaExpressionParseException getCheckedException() { + return exception; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java b/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java index 162bf377f75..870018a637e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/JavaParserUtil.java @@ -19,13 +19,16 @@ import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.checkerframework.javacutil.BugInCF; + import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; import java.util.Optional; + import javax.annotation.processing.ProcessingEnvironment; -import org.checkerframework.javacutil.BugInCF; /** * Utility methods for working with JavaParser. It is a replacement for StaticJavaParser that does @@ -33,389 +36,391 @@ */ public class JavaParserUtil { - /** - * The Language Level to use when parsing if a specific level isn't applied. This should be the - * highest version of Java that the Checker Framework can process. - */ - // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as of version - // 3.25.1 (2023-02-28). See - // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . - public static final LanguageLevel DEFAULT_LANGUAGE_LEVEL = LanguageLevel.JAVA_17; + /** + * The Language Level to use when parsing if a specific level isn't applied. This should be the + * highest version of Java that the Checker Framework can process. + */ + // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as of version + // 3.25.1 (2023-02-28). See + // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . + public static final LanguageLevel DEFAULT_LANGUAGE_LEVEL = LanguageLevel.JAVA_17; - /// - /// Replacements for StaticJavaParser - /// + /// + /// Replacements for StaticJavaParser + /// - /** - * Parses the Java code contained in the {@code InputStream} and returns a {@code CompilationUnit} - * that represents it. - * - *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it - * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} - * causes memory problems because it retains too much memory. - * - * @param inputStream the Java source code - * @return CompilationUnit representing the Java source code - * @throws ParseProblemException if the source code has parser errors - */ - public static CompilationUnit parseCompilationUnit(InputStream inputStream) { - ParserConfiguration parserConfiguration = new ParserConfiguration(); - parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - JavaParser javaParser = new JavaParser(parserConfiguration); - ParseResult parseResult = javaParser.parse(inputStream); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); + /** + * Parses the Java code contained in the {@code InputStream} and returns a {@code + * CompilationUnit} that represents it. + * + *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because + * it creates a new instance of JavaParser each time it is invoked. Re-using {@code + * StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param inputStream the Java source code + * @return CompilationUnit representing the Java source code + * @throws ParseProblemException if the source code has parser errors + */ + public static CompilationUnit parseCompilationUnit(InputStream inputStream) { + ParserConfiguration parserConfiguration = new ParserConfiguration(); + parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + JavaParser javaParser = new JavaParser(parserConfiguration); + ParseResult parseResult = javaParser.parse(inputStream); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); + } } - } - /** - * Parses the Java code contained in the {@code File} and returns a {@code CompilationUnit} that - * represents it. - * - *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it - * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} - * causes memory problems because it retains too much memory. - * - * @param file the Java source code - * @return CompilationUnit representing the Java source code - * @throws ParseProblemException if the source code has parser errors - * @throws FileNotFoundException if the file was not found - */ - public static CompilationUnit parseCompilationUnit(File file) throws FileNotFoundException { - ParserConfiguration configuration = new ParserConfiguration(); - configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - JavaParser javaParser = new JavaParser(configuration); - ParseResult parseResult = javaParser.parse(file); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); + /** + * Parses the Java code contained in the {@code File} and returns a {@code CompilationUnit} that + * represents it. + * + *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because + * it creates a new instance of JavaParser each time it is invoked. Re-using {@code + * StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param file the Java source code + * @return CompilationUnit representing the Java source code + * @throws ParseProblemException if the source code has parser errors + * @throws FileNotFoundException if the file was not found + */ + public static CompilationUnit parseCompilationUnit(File file) throws FileNotFoundException { + ParserConfiguration configuration = new ParserConfiguration(); + configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + JavaParser javaParser = new JavaParser(configuration); + ParseResult parseResult = javaParser.parse(file); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); + } } - } - /** - * Parses the Java code contained in the {@code String} and returns a {@code CompilationUnit} that - * represents it. - * - *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it - * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} - * causes memory problems because it retains too much memory. - * - * @param javaSource the Java source code - * @return CompilationUnit representing the Java source code - * @throws ParseProblemException if the source code has parser errors - */ - public static CompilationUnit parseCompilationUnit(String javaSource) { - ParserConfiguration parserConfiguration = new ParserConfiguration(); - parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - JavaParser javaParser = new JavaParser(parserConfiguration); - ParseResult parseResult = javaParser.parse(javaSource); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); + /** + * Parses the Java code contained in the {@code String} and returns a {@code CompilationUnit} + * that represents it. + * + *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because + * it creates a new instance of JavaParser each time it is invoked. Re-using {@code + * StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param javaSource the Java source code + * @return CompilationUnit representing the Java source code + * @throws ParseProblemException if the source code has parser errors + */ + public static CompilationUnit parseCompilationUnit(String javaSource) { + ParserConfiguration parserConfiguration = new ParserConfiguration(); + parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + JavaParser javaParser = new JavaParser(parserConfiguration); + ParseResult parseResult = javaParser.parse(javaSource); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); + } } - } - /** - * Parses the stub file contained in the {@code InputStream} and returns a {@code StubUnit} that - * represents it. - * - *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because it - * creates a new instance of JavaParser each time it is invoked. Re-using {@code StaticJavaParser} - * causes memory problems because it retains too much memory. - * - * @param inputStream the stub file - * @return StubUnit representing the stub file - * @throws ParseProblemException if the source code has parser errors - */ - public static StubUnit parseStubUnit(InputStream inputStream) { - // The ParserConfiguration accumulates data each time parse is called, so create a new one - // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a - // JavaParser has to be created each time. - ParserConfiguration configuration = new ParserConfiguration(); - configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); - // Store the tokens so that errors have line and column numbers. - // configuration.setStoreTokens(false); - configuration.setLexicalPreservationEnabled(false); - configuration.setAttributeComments(false); - configuration.setDetectOriginalLineSeparator(false); - JavaParser javaParser = new JavaParser(configuration); - ParseResult parseResult = javaParser.parseStubUnit(inputStream); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); + /** + * Parses the stub file contained in the {@code InputStream} and returns a {@code StubUnit} that + * represents it. + * + *

          This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because + * it creates a new instance of JavaParser each time it is invoked. Re-using {@code + * StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param inputStream the stub file + * @return StubUnit representing the stub file + * @throws ParseProblemException if the source code has parser errors + */ + public static StubUnit parseStubUnit(InputStream inputStream) { + // The ParserConfiguration accumulates data each time parse is called, so create a new one + // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a + // JavaParser has to be created each time. + ParserConfiguration configuration = new ParserConfiguration(); + configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); + // Store the tokens so that errors have line and column numbers. + // configuration.setStoreTokens(false); + configuration.setLexicalPreservationEnabled(false); + configuration.setAttributeComments(false); + configuration.setDetectOriginalLineSeparator(false); + JavaParser javaParser = new JavaParser(configuration); + ParseResult parseResult = javaParser.parseStubUnit(inputStream); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); + } } - } - /** - * Parses the {@code expression} and returns an {@code Expression} that represents it. - * - *

          This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory leaks - * because it creates a new instance of JavaParser each time it is invoked. Re-using {@code - * StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param expression the expression string - * @return the parsed expression - * @throws ParseProblemException if the expression has parser errors - */ - public static Expression parseExpression(String expression) { - return parseExpression(expression, DEFAULT_LANGUAGE_LEVEL); - } + /** + * Parses the {@code expression} and returns an {@code Expression} that represents it. + * + *

          This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory + * leaks because it creates a new instance of JavaParser each time it is invoked. Re-using + * {@code StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param expression the expression string + * @return the parsed expression + * @throws ParseProblemException if the expression has parser errors + */ + public static Expression parseExpression(String expression) { + return parseExpression(expression, DEFAULT_LANGUAGE_LEVEL); + } - /** - * Parses the {@code expression} and returns an {@code Expression} that represents it. - * - *

          This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory leaks - * because it creates a new instance of JavaParser each time it is invoked. Re-using {@code - * StaticJavaParser} causes memory problems because it retains too much memory. - * - * @param expression the expression string - * @param languageLevel the language level to use when parsing the Java source - * @return the parsed expression - * @throws ParseProblemException if the expression has parser errors - */ - public static Expression parseExpression(String expression, LanguageLevel languageLevel) { - // The ParserConfiguration accumulates data each time parse is called, so create a new one - // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a - // JavaParser has to be created each time. - ParserConfiguration configuration = new ParserConfiguration(); - configuration.setLanguageLevel(languageLevel); - configuration.setStoreTokens(false); - configuration.setLexicalPreservationEnabled(false); - configuration.setAttributeComments(false); - configuration.setDetectOriginalLineSeparator(false); - JavaParser javaParser = new JavaParser(configuration); - ParseResult parseResult = javaParser.parseExpression(expression); - if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { - return parseResult.getResult().get(); - } else { - throw new ParseProblemException(parseResult.getProblems()); + /** + * Parses the {@code expression} and returns an {@code Expression} that represents it. + * + *

          This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory + * leaks because it creates a new instance of JavaParser each time it is invoked. Re-using + * {@code StaticJavaParser} causes memory problems because it retains too much memory. + * + * @param expression the expression string + * @param languageLevel the language level to use when parsing the Java source + * @return the parsed expression + * @throws ParseProblemException if the expression has parser errors + */ + public static Expression parseExpression(String expression, LanguageLevel languageLevel) { + // The ParserConfiguration accumulates data each time parse is called, so create a new one + // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a + // JavaParser has to be created each time. + ParserConfiguration configuration = new ParserConfiguration(); + configuration.setLanguageLevel(languageLevel); + configuration.setStoreTokens(false); + configuration.setLexicalPreservationEnabled(false); + configuration.setAttributeComments(false); + configuration.setDetectOriginalLineSeparator(false); + JavaParser javaParser = new JavaParser(configuration); + ParseResult parseResult = javaParser.parseExpression(expression); + if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { + return parseResult.getResult().get(); + } else { + throw new ParseProblemException(parseResult.getProblems()); + } } - } - /// - /// Other methods - /// + /// + /// Other methods + /// - /** - * Given the compilation unit node for a source file, returns the top level type definition with - * the given name. - * - * @param root compilation unit to search - * @param name name of a top level type declaration in {@code root} - * @return a top level type declaration in {@code root} named {@code name} - */ - public static TypeDeclaration getTypeDeclarationByName(CompilationUnit root, String name) { - Optional classDecl = root.getClassByName(name); - if (classDecl.isPresent()) { - return classDecl.get(); - } + /** + * Given the compilation unit node for a source file, returns the top level type definition with + * the given name. + * + * @param root compilation unit to search + * @param name name of a top level type declaration in {@code root} + * @return a top level type declaration in {@code root} named {@code name} + */ + public static TypeDeclaration getTypeDeclarationByName(CompilationUnit root, String name) { + Optional classDecl = root.getClassByName(name); + if (classDecl.isPresent()) { + return classDecl.get(); + } - Optional interfaceDecl = root.getInterfaceByName(name); - if (interfaceDecl.isPresent()) { - return interfaceDecl.get(); - } + Optional interfaceDecl = root.getInterfaceByName(name); + if (interfaceDecl.isPresent()) { + return interfaceDecl.get(); + } - Optional enumDecl = root.getEnumByName(name); - if (enumDecl.isPresent()) { - return enumDecl.get(); - } + Optional enumDecl = root.getEnumByName(name); + if (enumDecl.isPresent()) { + return enumDecl.get(); + } - Optional annoDecl = root.getAnnotationDeclarationByName(name); - if (annoDecl.isPresent()) { - return annoDecl.get(); - } + Optional annoDecl = root.getAnnotationDeclarationByName(name); + if (annoDecl.isPresent()) { + return annoDecl.get(); + } + + Optional recordDecl = getRecordByName(root, name); + if (recordDecl.isPresent()) { + return recordDecl.get(); + } - Optional recordDecl = getRecordByName(root, name); - if (recordDecl.isPresent()) { - return recordDecl.get(); + Optional storage = root.getStorage(); + if (storage.isPresent()) { + throw new BugInCF("Type " + name + " not found in " + storage.get().getPath()); + } else { + throw new BugInCF("Type " + name + " not found in " + root); + } } - Optional storage = root.getStorage(); - if (storage.isPresent()) { - throw new BugInCF("Type " + name + " not found in " + storage.get().getPath()); - } else { - throw new BugInCF("Type " + name + " not found in " + root); + /** + * JavaParser's {@link CompilationUnit} class has methods like this for every other kind of + * class-like structure (e.g., classes, enums, annotation declarations, etc.), but not for + * records. This implementation is based on the implementation of {@link + * CompilationUnit#getClassByName(String)}, and has the same interface as the other, similar + * JavaParser methods (except that it is static and takes the CompilationUnit as a parameter, + * rather than being an instance method on the CompilationUnit). + * + * @param cu the CompilationUnit to search + * @param recordName the name of the record + * @return the record declaration in the compilation unit with the given name, or an empty + * Optional if no such record declaration exists + */ + private static Optional getRecordByName( + CompilationUnit cu, String recordName) { + return cu.getTypes().stream() + .filter( + (type) -> { + return type.getNameAsString().equals(recordName) + && type instanceof RecordDeclaration; + }) + .findFirst() + .map( + (t) -> { + return (RecordDeclaration) t; + }); } - } - /** - * JavaParser's {@link CompilationUnit} class has methods like this for every other kind of - * class-like structure (e.g., classes, enums, annotation declarations, etc.), but not for - * records. This implementation is based on the implementation of {@link - * CompilationUnit#getClassByName(String)}, and has the same interface as the other, similar - * JavaParser methods (except that it is static and takes the CompilationUnit as a parameter, - * rather than being an instance method on the CompilationUnit). - * - * @param cu the CompilationUnit to search - * @param recordName the name of the record - * @return the record declaration in the compilation unit with the given name, or an empty - * Optional if no such record declaration exists - */ - private static Optional getRecordByName( - CompilationUnit cu, String recordName) { - return cu.getTypes().stream() - .filter( - (type) -> { - return type.getNameAsString().equals(recordName) && type instanceof RecordDeclaration; - }) - .findFirst() - .map( - (t) -> { - return (RecordDeclaration) t; - }); - } + /** + * Returns the fully qualified name of a type appearing in a given compilation unit. + * + * @param type a type declaration + * @param compilationUnit the compilation unit containing {@code type} + * @return the fully qualified name of {@code type} if {@code compilationUnit} contains a + * package declaration, or just the name of {@code type} otherwise + */ + public static String getFullyQualifiedName( + TypeDeclaration type, CompilationUnit compilationUnit) { + if (compilationUnit.getPackageDeclaration().isPresent()) { + return compilationUnit.getPackageDeclaration().get().getNameAsString() + + "." + + type.getNameAsString(); + } else { + return type.getNameAsString(); + } + } - /** - * Returns the fully qualified name of a type appearing in a given compilation unit. - * - * @param type a type declaration - * @param compilationUnit the compilation unit containing {@code type} - * @return the fully qualified name of {@code type} if {@code compilationUnit} contains a package - * declaration, or just the name of {@code type} otherwise - */ - public static String getFullyQualifiedName( - TypeDeclaration type, CompilationUnit compilationUnit) { - if (compilationUnit.getPackageDeclaration().isPresent()) { - return compilationUnit.getPackageDeclaration().get().getNameAsString() - + "." - + type.getNameAsString(); - } else { - return type.getNameAsString(); + /** + * Side-effects {@code node} by removing all annotations from anywhere inside its subtree. + * + * @param node a JavaParser Node + */ + public static void clearAnnotations(Node node) { + node.accept(new ClearAnnotationsVisitor(), null); } - } - /** - * Side-effects {@code node} by removing all annotations from anywhere inside its subtree. - * - * @param node a JavaParser Node - */ - public static void clearAnnotations(Node node) { - node.accept(new ClearAnnotationsVisitor(), null); - } + /** A visitor that clears all annotations from a JavaParser AST. */ + private static class ClearAnnotationsVisitor extends VoidVisitorWithDefaultAction { + @Override + public void defaultAction(Node node) { + for (Node child : new ArrayList<>(node.getChildNodes())) { + if (child instanceof AnnotationExpr) { + node.remove(child); + } + } + } - /** A visitor that clears all annotations from a JavaParser AST. */ - private static class ClearAnnotationsVisitor extends VoidVisitorWithDefaultAction { - @Override - public void defaultAction(Node node) { - for (Node child : new ArrayList<>(node.getChildNodes())) { - if (child instanceof AnnotationExpr) { - node.remove(child); + @Override + public void visit(ArrayInitializerExpr node, Void p) { + // Do not remove annotations that are array elements. } - } } - @Override - public void visit(ArrayInitializerExpr node, Void p) { - // Do not remove annotations that are array elements. + /** + * Side-effects node by combining any added String literals in node's subtree into their + * concatenation. For example, the expression {@code "a" + "b"} becomes {@code "ab"}. This + * occurs even if, when reading from left to right, the two string literals are not added + * directly. For example, the expression {@code 1 + "a" + "b"} parses as {@code (1 + "a") + + * "b"}}, but it is transformed into {@code 1 + "ab"}. + * + *

          This is the same transformation performed by javac automatically. Javac seems to ignore + * string literals surrounded in parentheses, so this method does as well. + * + * @param node a JavaParser Node + */ + public static void concatenateAddedStringLiterals(Node node) { + node.accept(new StringLiteralConcatenateVisitor(), null); } - } - - /** - * Side-effects node by combining any added String literals in node's subtree into their - * concatenation. For example, the expression {@code "a" + "b"} becomes {@code "ab"}. This occurs - * even if, when reading from left to right, the two string literals are not added directly. For - * example, the expression {@code 1 + "a" + "b"} parses as {@code (1 + "a") + "b"}}, but it is - * transformed into {@code 1 + "ab"}. - * - *

          This is the same transformation performed by javac automatically. Javac seems to ignore - * string literals surrounded in parentheses, so this method does as well. - * - * @param node a JavaParser Node - */ - public static void concatenateAddedStringLiterals(Node node) { - node.accept(new StringLiteralConcatenateVisitor(), null); - } - /** Visitor that combines added String literals, see {@link #concatenateAddedStringLiterals}. */ - public static class StringLiteralConcatenateVisitor extends VoidVisitorAdapter { - @Override - public void visit(BinaryExpr node, Void p) { - super.visit(node, p); - if (node.getOperator() == BinaryExpr.Operator.PLUS && node.getRight().isStringLiteralExpr()) { - String right = node.getRight().asStringLiteralExpr().getValue(); - if (node.getLeft().isStringLiteralExpr()) { - String left = node.getLeft().asStringLiteralExpr().getValue(); - node.replace(new StringLiteralExpr(left + right)); - } else if (node.getLeft().isBinaryExpr()) { - BinaryExpr leftExpr = node.getLeft().asBinaryExpr(); - if (leftExpr.getOperator() == BinaryExpr.Operator.PLUS - && leftExpr.getRight().isStringLiteralExpr()) { - String left = leftExpr.getRight().asStringLiteralExpr().getValue(); - node.replace( - new BinaryExpr( - leftExpr.getLeft(), - new StringLiteralExpr(left + right), - BinaryExpr.Operator.PLUS)); - } + /** Visitor that combines added String literals, see {@link #concatenateAddedStringLiterals}. */ + public static class StringLiteralConcatenateVisitor extends VoidVisitorAdapter { + @Override + public void visit(BinaryExpr node, Void p) { + super.visit(node, p); + if (node.getOperator() == BinaryExpr.Operator.PLUS + && node.getRight().isStringLiteralExpr()) { + String right = node.getRight().asStringLiteralExpr().getValue(); + if (node.getLeft().isStringLiteralExpr()) { + String left = node.getLeft().asStringLiteralExpr().getValue(); + node.replace(new StringLiteralExpr(left + right)); + } else if (node.getLeft().isBinaryExpr()) { + BinaryExpr leftExpr = node.getLeft().asBinaryExpr(); + if (leftExpr.getOperator() == BinaryExpr.Operator.PLUS + && leftExpr.getRight().isStringLiteralExpr()) { + String left = leftExpr.getRight().asStringLiteralExpr().getValue(); + node.replace( + new BinaryExpr( + leftExpr.getLeft(), + new StringLiteralExpr(left + right), + BinaryExpr.Operator.PLUS)); + } + } + } } - } } - } - /** - * Initialized by {@link #getCurrentSourceVersion(ProcessingEnvironment)}. Use that method to - * access. - */ - private static LanguageLevel currentSourceVersion = null; + /** + * Initialized by {@link #getCurrentSourceVersion(ProcessingEnvironment)}. Use that method to + * access. + */ + private static LanguageLevel currentSourceVersion = null; - /** - * Returns the {@link com.github.javaparser.ParserConfiguration.LanguageLevel} corresponding to - * the current source version. - * - * @param env processing environment used to get source version - * @return the current source version - */ - public static ParserConfiguration.LanguageLevel getCurrentSourceVersion( - ProcessingEnvironment env) { - if (currentSourceVersion == null) { - // Use String comparison so we can compile on older JDKs which - // don't have all the latest SourceVersion constants: - switch (env.getSourceVersion().name()) { - case "RELEASE_8": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_8; - break; - case "RELEASE_9": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_9; - break; - case "RELEASE_10": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_10; - break; - case "RELEASE_11": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_11; - break; - case "RELEASE_12": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_12; - break; - case "RELEASE_13": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_13; - break; - case "RELEASE_14": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_14; - break; - case "RELEASE_15": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_15; - break; - case "RELEASE_16": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_16; - break; - case "RELEASE_17": - currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_17; - break; - // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as - // of version 3.25.1 (2023-02-28). See - // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . - // case "RELEASE_18": - // currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_18; - // break; - default: - currentSourceVersion = DEFAULT_LANGUAGE_LEVEL; - } + /** + * Returns the {@link com.github.javaparser.ParserConfiguration.LanguageLevel} corresponding to + * the current source version. + * + * @param env processing environment used to get source version + * @return the current source version + */ + public static ParserConfiguration.LanguageLevel getCurrentSourceVersion( + ProcessingEnvironment env) { + if (currentSourceVersion == null) { + // Use String comparison so we can compile on older JDKs which + // don't have all the latest SourceVersion constants: + switch (env.getSourceVersion().name()) { + case "RELEASE_8": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_8; + break; + case "RELEASE_9": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_9; + break; + case "RELEASE_10": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_10; + break; + case "RELEASE_11": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_11; + break; + case "RELEASE_12": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_12; + break; + case "RELEASE_13": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_13; + break; + case "RELEASE_14": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_14; + break; + case "RELEASE_15": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_15; + break; + case "RELEASE_16": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_16; + break; + case "RELEASE_17": + currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_17; + break; + // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as + // of version 3.25.1 (2023-02-28). See + // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . + // case "RELEASE_18": + // currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_18; + // break; + default: + currentSourceVersion = DEFAULT_LANGUAGE_LEVEL; + } + } + return currentSourceVersion; } - return currentSourceVersion; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java index 270d948b8a3..21c66caf9ef 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/NoContractsFromMethod.java @@ -2,56 +2,57 @@ import java.util.Collections; import java.util.Set; + import javax.lang.model.element.ExecutableElement; /** Dummy implementation of {@link ContractsFromMethod} that only returns empty sets. */ public class NoContractsFromMethod implements ContractsFromMethod { - /** Creates a NoContractsFromMethod object. */ - public NoContractsFromMethod() {} - - /** - * Returns an empty set. - * - * @param executableElement the method or constructor whose contracts to retrieve - * @return an empty set - */ - @Override - public Set getContracts(ExecutableElement executableElement) { - return Collections.emptySet(); - } - - /** - * Returns an empty set - * - * @param executableElement the method whose contracts to return - * @return an empty set - */ - @Override - public Set getPreconditions(ExecutableElement executableElement) { - return Collections.emptySet(); - } - - /** - * Returns an empty set - * - * @param executableElement the method whose contracts to return - * @return an empty set - */ - @Override - public Set getPostconditions(ExecutableElement executableElement) { - return Collections.emptySet(); - } - - /** - * Returns an empty set. - * - * @param methodElement the method whose contracts to return - * @return an empty set - */ - @Override - public Set getConditionalPostconditions( - ExecutableElement methodElement) { - return Collections.emptySet(); - } + /** Creates a NoContractsFromMethod object. */ + public NoContractsFromMethod() {} + + /** + * Returns an empty set. + * + * @param executableElement the method or constructor whose contracts to retrieve + * @return an empty set + */ + @Override + public Set getContracts(ExecutableElement executableElement) { + return Collections.emptySet(); + } + + /** + * Returns an empty set + * + * @param executableElement the method whose contracts to return + * @return an empty set + */ + @Override + public Set getPreconditions(ExecutableElement executableElement) { + return Collections.emptySet(); + } + + /** + * Returns an empty set + * + * @param executableElement the method whose contracts to return + * @return an empty set + */ + @Override + public Set getPostconditions(ExecutableElement executableElement) { + return Collections.emptySet(); + } + + /** + * Returns an empty set. + * + * @param methodElement the method whose contracts to return + * @return an empty set + */ + @Override + public Set getConditionalPostconditions( + ExecutableElement methodElement) { + return Collections.emptySet(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java b/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java index 6d24142e38e..d941f910cb1 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java +++ b/framework/src/main/java/org/checkerframework/framework/util/OptionConfiguration.java @@ -1,126 +1,127 @@ package org.checkerframework.framework.util; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.source.SupportedOptions; + import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.source.SupportedOptions; /** Provides methods for querying the Checker's options. */ public interface OptionConfiguration { - /** - * Return all active options for this checker. - * - * @return all active options for this checker - */ - Map getOptions(); + /** + * Return all active options for this checker. + * + * @return all active options for this checker + */ + Map getOptions(); - /** - * Check whether the given option is provided. - * - *

          Note that {@link #getOption} can still return null even if {@code hasOption} returns true: - * this happens e.g. for {@code -Amyopt} - * - * @param name the name of the option to check - * @return true if the option name was provided, false otherwise - */ - boolean hasOption(String name); + /** + * Check whether the given option is provided. + * + *

          Note that {@link #getOption} can still return null even if {@code hasOption} returns true: + * this happens e.g. for {@code -Amyopt} + * + * @param name the name of the option to check + * @return true if the option name was provided, false otherwise + */ + boolean hasOption(String name); - /** - * Determines the value of the option with the given name. - * - *

          Note that {@code getOption} can still return null even if {@link #hasOption} returns true: - * this happens e.g. for {@code -Amyopt} - * - * @param name the name of the option to check - * @return the value of the option with the given name - * @see #getOption(String,String) - */ - @Nullable String getOption(String name); + /** + * Determines the value of the option with the given name. + * + *

          Note that {@code getOption} can still return null even if {@link #hasOption} returns true: + * this happens e.g. for {@code -Amyopt} + * + * @param name the name of the option to check + * @return the value of the option with the given name + * @see #getOption(String,String) + */ + @Nullable String getOption(String name); - /** - * Determines the boolean value of the option with the given name. Returns {@code defaultValue} if - * the option is not set. - * - * @param name the name of the option to check - * @param defaultValue the default value to return if the option is not set - * @return the value of the option with the given name, or {@code defaultValue} - * @see #getOption(String) - */ - String getOption(String name, String defaultValue); + /** + * Determines the boolean value of the option with the given name. Returns {@code defaultValue} + * if the option is not set. + * + * @param name the name of the option to check + * @param defaultValue the default value to return if the option is not set + * @return the value of the option with the given name, or {@code defaultValue} + * @see #getOption(String) + */ + String getOption(String name, String defaultValue); - /** - * Determines the boolean value of the option with the given name. Returns false if the option is - * not set. - * - * @param name the name of the option to check - * @return the boolean value of the option - */ - boolean getBooleanOption(String name); + /** + * Determines the boolean value of the option with the given name. Returns false if the option + * is not set. + * + * @param name the name of the option to check + * @return the boolean value of the option + */ + boolean getBooleanOption(String name); - /** - * Determines the boolean value of the option with the given name. Returns the given default value - * if the option is not set. - * - * @param name the name of the option to check - * @param defaultValue the default value to use if the option is not set - * @return the boolean value of the option - */ - boolean getBooleanOption(String name, boolean defaultValue); + /** + * Determines the boolean value of the option with the given name. Returns the given default + * value if the option is not set. + * + * @param name the name of the option to check + * @param defaultValue the default value to use if the option is not set + * @return the boolean value of the option + */ + boolean getBooleanOption(String name, boolean defaultValue); - /** - * Determines the string list value of the option with the given name. The option's value is split - * on the given separator. Returns an empty list if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @return the list of options - */ - default List getStringsOption(String name, char separator) { - return getStringsOption(name, separator, Collections.emptyList()); - } + /** + * Determines the string list value of the option with the given name. The option's value is + * split on the given separator. Returns an empty list if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @return the list of options + */ + default List getStringsOption(String name, char separator) { + return getStringsOption(name, separator, Collections.emptyList()); + } - /** - * Determines the string list value of the option with the given name. The option's value is split - * on the given separator. Returns the given default value if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @param defaultValue the default value to use if the option is not set - * @return the list of options - */ - public List getStringsOption(String name, char separator, List defaultValue); + /** + * Determines the string list value of the option with the given name. The option's value is + * split on the given separator. Returns the given default value if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @param defaultValue the default value to use if the option is not set + * @return the list of options + */ + public List getStringsOption(String name, char separator, List defaultValue); - /** - * Determines the string list value of the option with the given name. The option's value is split - * on the given separator. Returns an empty list if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @return the list of options - */ - default List getStringsOption(String name, String separator) { - return getStringsOption(name, separator, Collections.emptyList()); - } + /** + * Determines the string list value of the option with the given name. The option's value is + * split on the given separator. Returns an empty list if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @return the list of options + */ + default List getStringsOption(String name, String separator) { + return getStringsOption(name, separator, Collections.emptyList()); + } - /** - * Determines the string list value of the option with the given name. The option's value is split - * on the given separator. Returns the given default value if the option is not set. - * - * @param name the name of the option to check - * @param separator the separator for list elements - * @param defaultValue the default value to use if the option is not set - * @return the list of options - */ - public List getStringsOption(String name, String separator, List defaultValue); + /** + * Determines the string list value of the option with the given name. The option's value is + * split on the given separator. Returns the given default value if the option is not set. + * + * @param name the name of the option to check + * @param separator the separator for list elements + * @param defaultValue the default value to use if the option is not set + * @return the list of options + */ + public List getStringsOption(String name, String separator, List defaultValue); - /** - * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation - * provided version {@link javax.annotation.processing.SupportedOptions}. - * - * @return the supported options - */ - Set getSupportedOptions(); + /** + * Map the Checker Framework version of {@link SupportedOptions} to the standard annotation + * provided version {@link javax.annotation.processing.SupportedOptions}. + * + * @return the supported options + */ + Set getSupportedOptions(); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java index ff62ab479cc..8716f307c99 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/util/PurityAnnotatedTypeFactory.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.util; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.PurityUnqualified; + import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.qual.PurityUnqualified; /** AnnotatedTypeFactory for the {@link PurityChecker}. */ public class PurityAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public PurityAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public PurityAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>(Arrays.asList(PurityUnqualified.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>(Arrays.asList(PurityUnqualified.class)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java b/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java index 6d50face48b..0a23d711767 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java @@ -9,7 +9,7 @@ * flow-sensitive analysis */ public class PurityChecker extends BaseTypeChecker { - // There is no implementation here. - // It uses functionality from BaseTypeChecker, which itself calls - // dataflow's purity implementation. + // There is no implementation here. + // It uses functionality from BaseTypeChecker, which itself calls + // dataflow's purity implementation. } diff --git a/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java b/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java index e06e9f35fa4..e85a899a8a2 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java +++ b/framework/src/main/java/org/checkerframework/framework/util/QualifierKind.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.util; -import java.lang.annotation.Annotation; -import java.util.Set; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.framework.qual.AnnotatedFor; +import java.lang.annotation.Annotation; +import java.util.Set; + /** * Represents a kind of qualifier, which is an annotation class. Does not represent annotation * elements. If two qualifiers use the same annotation class, then they have the same qualifier @@ -26,99 +27,99 @@ @AnnotatedFor("nullness") public @Interned interface QualifierKind extends Comparable { - /** - * Returns the canonical name of the annotation class of this. - * - * @return the canonical name of the annotation class of this - */ - @Interned @CanonicalName String getName(); - - /** - * Returns the annotation class for this. - * - * @return the annotation class for this - */ - Class getAnnotationClass(); - - /** - * Returns the top qualifier kind of the hierarchy to which this qualifier kind belongs. - * - * @return the top qualifier kind of the hierarchy to which this qualifier kind belongs - */ - QualifierKind getTop(); - - /** - * Returns true if this is the top qualifier of its hierarchy. - * - * @return true if this is the top qualifier of its hierarchy - */ - boolean isTop(); - - /** - * Returns the bottom qualifier kind of the hierarchy to which this qualifier kind belongs. - * - * @return the bottom qualifier kind of the hierarchy to which this qualifier kind belongs - */ - QualifierKind getBottom(); - - /** - * Returns true if this is the bottom qualifier of its hierarchy. - * - * @return true if this is the bottom qualifier of its hierarchy - */ - boolean isBottom(); - - /** - * Returns the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, - * or null if one does not exist. - * - * @return the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, - * or null if one does not exist - */ - @Nullable QualifierKind getPolymorphic(); - - /** - * Returns true if this is polymorphic. - * - * @return true if this is polymorphic - */ - @Pure - boolean isPoly(); - - /** - * Returns true if the annotation class this qualifier kind represents has annotation - * elements/arguments. - * - * @return true if the annotation class this qualifier kind represents has elements/arguments - */ - boolean hasElements(); - - /** - * All the qualifier kinds that are a strict super qualifier of this qualifier. Does not include - * this qualifier kind itself. - * - * @return all the qualifier kinds that are a strict super qualifier of this qualifier - */ - Set getStrictSuperTypes(); - - /** - * Returns true if this and {@code other} are in the same hierarchy. - * - * @param other a qualifier kind - * @return true if this and {@code other} are in the same hierarchy - */ - boolean isInSameHierarchyAs(QualifierKind other); - - /** - * Returns true if this qualifier kind is a subtype of or equal to {@code superQualKind}. - * - * @param superQualKind other qualifier kind - * @return true if this qualifier kind is a subtype of or equal to {@code superQualKind} - */ - boolean isSubtypeOf(QualifierKind superQualKind); - - @Override - default int compareTo(QualifierKind o) { - return this.getName().compareTo(o.getName()); - } + /** + * Returns the canonical name of the annotation class of this. + * + * @return the canonical name of the annotation class of this + */ + @Interned @CanonicalName String getName(); + + /** + * Returns the annotation class for this. + * + * @return the annotation class for this + */ + Class getAnnotationClass(); + + /** + * Returns the top qualifier kind of the hierarchy to which this qualifier kind belongs. + * + * @return the top qualifier kind of the hierarchy to which this qualifier kind belongs + */ + QualifierKind getTop(); + + /** + * Returns true if this is the top qualifier of its hierarchy. + * + * @return true if this is the top qualifier of its hierarchy + */ + boolean isTop(); + + /** + * Returns the bottom qualifier kind of the hierarchy to which this qualifier kind belongs. + * + * @return the bottom qualifier kind of the hierarchy to which this qualifier kind belongs + */ + QualifierKind getBottom(); + + /** + * Returns true if this is the bottom qualifier of its hierarchy. + * + * @return true if this is the bottom qualifier of its hierarchy + */ + boolean isBottom(); + + /** + * Returns the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, + * or null if one does not exist. + * + * @return the polymorphic qualifier kind of the hierarchy to which this qualifier kind belongs, + * or null if one does not exist + */ + @Nullable QualifierKind getPolymorphic(); + + /** + * Returns true if this is polymorphic. + * + * @return true if this is polymorphic + */ + @Pure + boolean isPoly(); + + /** + * Returns true if the annotation class this qualifier kind represents has annotation + * elements/arguments. + * + * @return true if the annotation class this qualifier kind represents has elements/arguments + */ + boolean hasElements(); + + /** + * All the qualifier kinds that are a strict super qualifier of this qualifier. Does not include + * this qualifier kind itself. + * + * @return all the qualifier kinds that are a strict super qualifier of this qualifier + */ + Set getStrictSuperTypes(); + + /** + * Returns true if this and {@code other} are in the same hierarchy. + * + * @param other a qualifier kind + * @return true if this and {@code other} are in the same hierarchy + */ + boolean isInSameHierarchyAs(QualifierKind other); + + /** + * Returns true if this qualifier kind is a subtype of or equal to {@code superQualKind}. + * + * @param superQualKind other qualifier kind + * @return true if this qualifier kind is a subtype of or equal to {@code superQualKind} + */ + boolean isSubtypeOf(QualifierKind superQualKind); + + @Override + default int compareTo(QualifierKind o) { + return this.getName().compareTo(o.getName()); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java index 1ab64cddd14..fab9d20f63e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.util; -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.CanonicalName; import org.checkerframework.framework.qual.AnnotatedFor; @@ -12,6 +8,12 @@ import org.checkerframework.framework.type.NoElementQualifierHierarchy; import org.checkerframework.javacutil.TypeSystemError; +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; + /** * This interface holds information about the subtyping relationships between kinds of qualifiers. A * "kind" of qualifier is its annotation class and is represented by the {@link QualifierKind} @@ -36,70 +38,72 @@ @AnnotatedFor("nullness") public interface QualifierKindHierarchy { - /** - * Returns the qualifier kinds that are the top qualifier in their hierarchies. - * - * @return the qualifier kinds that are the top qualifier in their hierarchies - */ - Set getTops(); + /** + * Returns the qualifier kinds that are the top qualifier in their hierarchies. + * + * @return the qualifier kinds that are the top qualifier in their hierarchies + */ + Set getTops(); - /** - * Returns the qualifier kinds that are the bottom qualifier in their hierarchies. - * - * @return the qualifier kinds that are the bottom qualifier in their hierarchies - */ - Set getBottoms(); + /** + * Returns the qualifier kinds that are the bottom qualifier in their hierarchies. + * + * @return the qualifier kinds that are the bottom qualifier in their hierarchies + */ + Set getBottoms(); - /** - * Returns the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier - * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does). - * - * @param q1 a qualifier kind - * @param q2 a qualifier kind - * @return the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier - * kinds are not in the same hierarchy - */ - @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2); + /** + * Returns the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier + * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always + * does). + * + * @param q1 a qualifier kind + * @param q2 a qualifier kind + * @return the least upper bound of {@code q1} and {@code q2}, or {@code null} if the qualifier + * kinds are not in the same hierarchy + */ + @Nullable QualifierKind leastUpperBound(QualifierKind q1, QualifierKind q2); - /** - * Returns the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier - * kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind always does). - * - * @param q1 a qualifier kind - * @param q2 a qualifier kind - * @return the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the qualifier - * kinds are not in the same hierarchy - */ - @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2); + /** + * Returns the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the + * qualifier kinds are not in the same hierarchy. Ignores elements/arguments (as QualifierKind + * always does). + * + * @param q1 a qualifier kind + * @param q2 a qualifier kind + * @return the greatest lower bound of {@code q1} and {@code q2}, or {@code null} if the + * qualifier kinds are not in the same hierarchy + */ + @Nullable QualifierKind greatestLowerBound(QualifierKind q1, QualifierKind q2); - /** - * Returns a list of all {@link QualifierKind}s sorted in ascending order. - * - * @return a list of all {@link QualifierKind}s sorted in ascending order - */ - List allQualifierKinds(); + /** + * Returns a list of all {@link QualifierKind}s sorted in ascending order. + * + * @return a list of all {@link QualifierKind}s sorted in ascending order + */ + List allQualifierKinds(); - /** - * Returns the {@link QualifierKind} for the given annotation class name. Throws an exception if - * one does not exist. - * - * @param name canonical name of an annotation class - * @return the {@link QualifierKind} for the given annotation class name - */ - QualifierKind getQualifierKind(@CanonicalName String name); + /** + * Returns the {@link QualifierKind} for the given annotation class name. Throws an exception if + * one does not exist. + * + * @param name canonical name of an annotation class + * @return the {@link QualifierKind} for the given annotation class name + */ + QualifierKind getQualifierKind(@CanonicalName String name); - /** - * Returns the canonical name of {@code clazz}. Throws a {@link TypeSystemError} if {@code clazz} - * is anonymous or otherwise does not have a name. - * - * @param clazz annotation class - * @return the canonical name of {@code clazz} - */ - static @CanonicalName String annotationClassName(Class clazz) { - String name = clazz.getCanonicalName(); - if (name == null) { - throw new TypeSystemError("Qualifier classes must not be anonymous."); + /** + * Returns the canonical name of {@code clazz}. Throws a {@link TypeSystemError} if {@code + * clazz} is anonymous or otherwise does not have a name. + * + * @param clazz annotation class + * @return the canonical name of {@code clazz} + */ + static @CanonicalName String annotationClassName(Class clazz) { + String name = clazz.getCanonicalName(); + if (name == null) { + throw new TypeSystemError("Qualifier classes must not be anonymous."); + } + return name; } - return name; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java b/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java index 6278db973b9..42db91ab672 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java +++ b/framework/src/main/java/org/checkerframework/framework/util/StringToJavaExpression.java @@ -7,12 +7,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.expression.FormalParameter; @@ -26,6 +21,14 @@ import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.List; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + /** * This interface is both a functional interface, see {@link #toJavaExpression(String)}, and also a * collection of static methods that convert a string to a JavaExpression at common locations. @@ -40,311 +43,315 @@ @FunctionalInterface public interface StringToJavaExpression { - /** - * Convert a string to a {@link JavaExpression}. Returns {@code null} if no conversion exists. - * - *

          Conversion includes parsing {@code stringExpr} to a {@code JavaExpression} and optionally - * transforming the result of parsing into another {@code JavaExpression}. An example of - * transformation is viewpoint adaptation. - * - * @param stringExpr a Java expression - * @return a {@code JavaExpression} or {@code null} if no conversion from {@code stringExpr} - * exists - * @throws JavaExpressionParseException if {@code stringExpr} cannot be parsed to a {@code - * JavaExpression} - */ - @Nullable JavaExpression toJavaExpression(String stringExpr) throws JavaExpressionParseException; + /** + * Convert a string to a {@link JavaExpression}. Returns {@code null} if no conversion exists. + * + *

          Conversion includes parsing {@code stringExpr} to a {@code JavaExpression} and optionally + * transforming the result of parsing into another {@code JavaExpression}. An example of + * transformation is viewpoint adaptation. + * + * @param stringExpr a Java expression + * @return a {@code JavaExpression} or {@code null} if no conversion from {@code stringExpr} + * exists + * @throws JavaExpressionParseException if {@code stringExpr} cannot be parsed to a {@code + * JavaExpression} + */ + @Nullable JavaExpression toJavaExpression(String stringExpr) + throws JavaExpressionParseException; - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code typeElement}. - * - * @param expression a Java expression to parse - * @param typeElement type element at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atTypeDecl( - String expression, TypeElement typeElement, SourceChecker checker) - throws JavaExpressionParseException { - ThisReference thisReference = new ThisReference(typeElement.asType()); - List parameters = null; - return JavaExpressionParseUtil.parse( - expression, - typeElement.asType(), - thisReference, - parameters, - null, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - } + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code typeElement}. + * + * @param expression a Java expression to parse + * @param typeElement type element at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atTypeDecl( + String expression, TypeElement typeElement, SourceChecker checker) + throws JavaExpressionParseException { + ThisReference thisReference = new ThisReference(typeElement.asType()); + List parameters = null; + return JavaExpressionParseUtil.parse( + expression, + typeElement.asType(), + thisReference, + parameters, + null, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + } - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code fieldElement}. - * - * @param expression a Java expression to parse - * @param fieldElement variable element at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atFieldDecl( - String expression, VariableElement fieldElement, SourceChecker checker) - throws JavaExpressionParseException { - TypeMirror enclosingType = ElementUtils.enclosingTypeElement(fieldElement).asType(); - ThisReference thisReference; - if (ElementUtils.isStatic(fieldElement)) { - // Can't use "this" on a static fieldElement - thisReference = null; - } else { - thisReference = new ThisReference(enclosingType); + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code fieldElement}. + * + * @param expression a Java expression to parse + * @param fieldElement variable element at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atFieldDecl( + String expression, VariableElement fieldElement, SourceChecker checker) + throws JavaExpressionParseException { + TypeMirror enclosingType = ElementUtils.enclosingTypeElement(fieldElement).asType(); + ThisReference thisReference; + if (ElementUtils.isStatic(fieldElement)) { + // Can't use "this" on a static fieldElement + thisReference = null; + } else { + thisReference = new ThisReference(enclosingType); + } + List parameters = null; + return JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + null, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); } - List parameters = null; - return JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - null, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - } - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code method}. The - * returned {@code JavaExpression} uses {@link FormalParameter}s to represent parameters. Use - * {@link #atMethodBody(String, MethodTree, SourceChecker)} if parameters should be {@link - * LocalVariable}s instead. - * - * @param expression a Java expression to parse - * @param method method element at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodDecl( - String expression, ExecutableElement method, SourceChecker checker) - throws JavaExpressionParseException { - TypeMirror enclosingType = ElementUtils.enclosingTypeElement(method).asType(); - ThisReference thisReference; - if (ElementUtils.isStatic(method)) { - // Can't use "this" on a static method - thisReference = null; - } else { - thisReference = new ThisReference(enclosingType); + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code method}. The + * returned {@code JavaExpression} uses {@link FormalParameter}s to represent parameters. Use + * {@link #atMethodBody(String, MethodTree, SourceChecker)} if parameters should be {@link + * LocalVariable}s instead. + * + * @param expression a Java expression to parse + * @param method method element at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodDecl( + String expression, ExecutableElement method, SourceChecker checker) + throws JavaExpressionParseException { + TypeMirror enclosingType = ElementUtils.enclosingTypeElement(method).asType(); + ThisReference thisReference; + if (ElementUtils.isStatic(method)) { + // Can't use "this" on a static method + thisReference = null; + } else { + thisReference = new ThisReference(enclosingType); + } + List parameters = JavaExpression.getFormalParameters(method); + return JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + null, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); } - List parameters = JavaExpression.getFormalParameters(method); - return JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - null, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - } - /** - * Parses a string to a {@link JavaExpression} as if it were written at {@code methodTree}. The - * returned {@code JavaExpression} uses {@link LocalVariable}s to represent parameters. Use {@link - * #atMethodDecl(String, ExecutableElement, SourceChecker)} if parameters should be {@link - * FormalParameter}s instead. - * - * @param expression a Java expression to parse - * @param methodTree method declaration tree at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodBody( - String expression, MethodTree methodTree, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromDeclaration(methodTree); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atMethodBody(methodTree); - } + /** + * Parses a string to a {@link JavaExpression} as if it were written at {@code methodTree}. The + * returned {@code JavaExpression} uses {@link LocalVariable}s to represent parameters. Use + * {@link #atMethodDecl(String, ExecutableElement, SourceChecker)} if parameters should be + * {@link FormalParameter}s instead. + * + * @param expression a Java expression to parse + * @param methodTree method declaration tree at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodBody( + String expression, MethodTree methodTree, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromDeclaration(methodTree); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atMethodBody(methodTree); + } - /** - * Parses a string as if it were written at the declaration of the invoked method and then - * viewpoint-adapts the result to the call site. - * - * @param expression a Java expression to parse - * @param methodInvocationTree method invocation tree - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodInvocation( - String expression, MethodInvocationTree methodInvocationTree, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationTree); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atMethodInvocation(methodInvocationTree); - } + /** + * Parses a string as if it were written at the declaration of the invoked method and then + * viewpoint-adapts the result to the call site. + * + * @param expression a Java expression to parse + * @param methodInvocationTree method invocation tree + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodInvocation( + String expression, MethodInvocationTree methodInvocationTree, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationTree); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atMethodInvocation(methodInvocationTree); + } - /** - * Parses a string as if it were written at the declaration of the invoked method and then - * viewpoint-adapts the result to the call site. - * - * @param expression a Java expression to parse - * @param methodInvocationNode method invocation node - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atMethodInvocation( - String expression, MethodInvocationNode methodInvocationNode, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationNode.getTree()); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atMethodInvocation(methodInvocationNode); - } + /** + * Parses a string as if it were written at the declaration of the invoked method and then + * viewpoint-adapts the result to the call site. + * + * @param expression a Java expression to parse + * @param methodInvocationNode method invocation node + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atMethodInvocation( + String expression, MethodInvocationNode methodInvocationNode, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromUse(methodInvocationNode.getTree()); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atMethodInvocation(methodInvocationNode); + } - /** - * Parses a string as if it were written at the declaration of the invoked constructor and then - * viewpoint-adapts the result to the call site. - * - * @param expression a Java expression to parse - * @param newClassTree constructor invocation - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atConstructorInvocation( - String expression, NewClassTree newClassTree, SourceChecker checker) - throws JavaExpressionParseException { - ExecutableElement ee = TreeUtils.elementFromUse(newClassTree); - JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); - return javaExpr.atConstructorInvocation(newClassTree); - } + /** + * Parses a string as if it were written at the declaration of the invoked constructor and then + * viewpoint-adapts the result to the call site. + * + * @param expression a Java expression to parse + * @param newClassTree constructor invocation + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atConstructorInvocation( + String expression, NewClassTree newClassTree, SourceChecker checker) + throws JavaExpressionParseException { + ExecutableElement ee = TreeUtils.elementFromUse(newClassTree); + JavaExpression javaExpr = StringToJavaExpression.atMethodDecl(expression, ee, checker); + return javaExpr.atConstructorInvocation(newClassTree); + } - /** - * uf found Parses a string as if it were written at the declaration of the field and then - * viewpoint-adapts the result to the use. - * - * @param expression a Java expression to parse - * @param fieldAccess the field access tree - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atFieldAccess( - String expression, MemberSelectTree fieldAccess, SourceChecker checker) - throws JavaExpressionParseException { + /** + * uf found Parses a string as if it were written at the declaration of the field and then + * viewpoint-adapts the result to the use. + * + * @param expression a Java expression to parse + * @param fieldAccess the field access tree + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atFieldAccess( + String expression, MemberSelectTree fieldAccess, SourceChecker checker) + throws JavaExpressionParseException { - VariableElement fieldEle = TreeUtils.variableElementFromUse(fieldAccess); - JavaExpression receiver = JavaExpression.fromTree(fieldAccess.getExpression()); - JavaExpression javaExpr = StringToJavaExpression.atFieldDecl(expression, fieldEle, checker); - return javaExpr.atFieldAccess(receiver); - } + VariableElement fieldEle = TreeUtils.variableElementFromUse(fieldAccess); + JavaExpression receiver = JavaExpression.fromTree(fieldAccess.getExpression()); + JavaExpression javaExpr = StringToJavaExpression.atFieldDecl(expression, fieldEle, checker); + return javaExpr.atFieldAccess(receiver); + } - /** - * Parses a string as if it were written at one of the parameters of {@code lambdaTree}. - * Parameters of the lambda are expressed as {@link LocalVariable}s. - * - * @param expression a Java expression to parse - * @param lambdaTree the lambda tree - * @param parentPath path to the parent of {@code lambdaTree}; required because the expression can - * reference final local variables of the enclosing method - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atLambdaParameter( - String expression, - LambdaExpressionTree lambdaTree, - TreePath parentPath, - SourceChecker checker) - throws JavaExpressionParseException { + /** + * Parses a string as if it were written at one of the parameters of {@code lambdaTree}. + * Parameters of the lambda are expressed as {@link LocalVariable}s. + * + * @param expression a Java expression to parse + * @param lambdaTree the lambda tree + * @param parentPath path to the parent of {@code lambdaTree}; required because the expression + * can reference final local variables of the enclosing method + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atLambdaParameter( + String expression, + LambdaExpressionTree lambdaTree, + TreePath parentPath, + SourceChecker checker) + throws JavaExpressionParseException { - TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(parentPath)); - JavaExpression receiver = JavaExpression.getPseudoReceiver(parentPath, enclosingType); - // If receiver isn't a ThisReference, then the lambda is in a static context and "this" - // cannot be referenced in the expression. - ThisReference thisReference = - receiver instanceof ThisReference ? (ThisReference) receiver : null; - List paramsAsLocals = new ArrayList<>(lambdaTree.getParameters().size()); - List parameters = new ArrayList<>(lambdaTree.getParameters().size()); - int oneBasedIndex = 1; - for (VariableTree arg : lambdaTree.getParameters()) { - LocalVariable param = (LocalVariable) JavaExpression.fromVariableTree(arg); - paramsAsLocals.add(param); - parameters.add(new FormalParameter(oneBasedIndex, param.getElement())); - oneBasedIndex++; + TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(parentPath)); + JavaExpression receiver = JavaExpression.getPseudoReceiver(parentPath, enclosingType); + // If receiver isn't a ThisReference, then the lambda is in a static context and "this" + // cannot be referenced in the expression. + ThisReference thisReference = + receiver instanceof ThisReference ? (ThisReference) receiver : null; + List paramsAsLocals = new ArrayList<>(lambdaTree.getParameters().size()); + List parameters = new ArrayList<>(lambdaTree.getParameters().size()); + int oneBasedIndex = 1; + for (VariableTree arg : lambdaTree.getParameters()) { + LocalVariable param = (LocalVariable) JavaExpression.fromVariableTree(arg); + paramsAsLocals.add(param); + parameters.add(new FormalParameter(oneBasedIndex, param.getElement())); + oneBasedIndex++; + } + + JavaExpression javaExpr = + JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + parentPath, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); } - JavaExpression javaExpr = - JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - parentPath, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); - } + /** + * Parses a string as if it were written at {@code localVarPath}. + * + * @param expression a Java expression to parse + * @param localVarPath location at which {@code expression} is parsed + * @param checker checker used to get the {@link + * javax.annotation.processing.ProcessingEnvironment} and current {@link + * com.sun.source.tree.CompilationUnitTree} + * @return a {@code JavaExpression} for {@code expression} + * @throws JavaExpressionParseException if {@code expression} cannot be parsed + */ + static JavaExpression atPath(String expression, TreePath localVarPath, SourceChecker checker) + throws JavaExpressionParseException { - /** - * Parses a string as if it were written at {@code localVarPath}. - * - * @param expression a Java expression to parse - * @param localVarPath location at which {@code expression} is parsed - * @param checker checker used to get the {@link - * javax.annotation.processing.ProcessingEnvironment} and current {@link - * com.sun.source.tree.CompilationUnitTree} - * @return a {@code JavaExpression} for {@code expression} - * @throws JavaExpressionParseException if {@code expression} cannot be parsed - */ - static JavaExpression atPath(String expression, TreePath localVarPath, SourceChecker checker) - throws JavaExpressionParseException { + TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(localVarPath)); + ThisReference thisReference = + TreePathUtil.isTreeInStaticScope(localVarPath) + ? null + : new ThisReference(enclosingType); - TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(localVarPath)); - ThisReference thisReference = - TreePathUtil.isTreeInStaticScope(localVarPath) ? null : new ThisReference(enclosingType); + MethodTree methodTree = TreePathUtil.enclosingMethod(localVarPath); + if (methodTree == null) { + return JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + null, + localVarPath, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + } - MethodTree methodTree = TreePathUtil.enclosingMethod(localVarPath); - if (methodTree == null) { - return JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - null, - localVarPath, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); + ExecutableElement methodEle = TreeUtils.elementFromDeclaration(methodTree); + List parameters = JavaExpression.getFormalParameters(methodEle); + JavaExpression javaExpr = + JavaExpressionParseUtil.parse( + expression, + enclosingType, + thisReference, + parameters, + localVarPath, + checker.getPathToCompilationUnit(), + checker.getProcessingEnvironment()); + List paramsAsLocals = + JavaExpression.getParametersAsLocalVariables(methodEle); + return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); } - - ExecutableElement methodEle = TreeUtils.elementFromDeclaration(methodTree); - List parameters = JavaExpression.getFormalParameters(methodEle); - JavaExpression javaExpr = - JavaExpressionParseUtil.parse( - expression, - enclosingType, - thisReference, - parameters, - localVarPath, - checker.getPathToCompilationUnit(), - checker.getProcessingEnvironment()); - List paramsAsLocals = JavaExpression.getParametersAsLocalVariables(methodEle); - return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java index 55ba73a5e63..dc2c554b11c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java +++ b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java @@ -4,11 +4,13 @@ import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import com.sun.source.util.TreeScanner; -import java.util.HashMap; -import java.util.Map; + import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.HashMap; +import java.util.Map; + /** * TreePathCacher is a TreeScanner that creates and caches a TreePath for a target Tree. * @@ -18,109 +20,109 @@ */ public class TreePathCacher extends TreeScanner { - private final Map foundPaths = new HashMap<>(32); - - /** - * The TreePath of the previous tree scanned. It is always set back to null after a scan has - * completed. - */ - private @Nullable TreePath path; - - /** - * Returns true if the tree is cached. - * - * @param target the tree to search for - * @return true if the tree is cached - */ - public boolean isCached(Tree target) { - return foundPaths.containsKey(target); - } - - /** - * Adds the given key and value to the cache. - * - * @param target the tree to add - * @param path the path to cache - */ - public void addPath(Tree target, TreePath path) { - foundPaths.put(target, path); - } - - /** - * Return the TreePath for a Tree. - * - * @param root the compilation unit to search in - * @param target the target tree to look for - * @return the TreePath corresponding to target, or null if target is not found in the compilation - * root - */ - public @Nullable TreePath getPath(CompilationUnitTree root, @FindDistinct Tree target) { - // This method uses try/catch and the private {@code Result} exception for control flow to - // stop the superclass from scanning other subtrees when target is found. - - if (foundPaths.containsKey(target)) { - return foundPaths.get(target); - } + private final Map foundPaths = new HashMap<>(32); - TreePath path = new TreePath(root); - if (path.getLeaf() == target) { - return path; - } + /** + * The TreePath of the previous tree scanned. It is always set back to null after a scan has + * completed. + */ + private @Nullable TreePath path; - try { - this.scan(path, target); - } catch (Result result) { - return result.path; + /** + * Returns true if the tree is cached. + * + * @param target the tree to search for + * @return true if the tree is cached + */ + public boolean isCached(Tree target) { + return foundPaths.containsKey(target); } - // If a path wasn't found, cache null so the whole compilation unit isn't searched again. - foundPaths.put(target, null); - return null; - } - /** The result of {@link #getPath}. This exception is used for control flow. */ - private static class Result extends Error { - /** Unique identifier for serialization. */ - private static final long serialVersionUID = 4948452207518392627L; - - /** The result of {@link #getPath}. */ - @SuppressWarnings("serial") // I do not intend to serialize Result objects - private final TreePath path; + /** + * Adds the given key and value to the cache. + * + * @param target the tree to add + * @param path the path to cache + */ + public void addPath(Tree target, TreePath path) { + foundPaths.put(target, path); + } /** - * Create a {@link #getPath} result. + * Return the TreePath for a Tree. * - * @param path the result of {@link #getPath} + * @param root the compilation unit to search in + * @param target the target tree to look for + * @return the TreePath corresponding to target, or null if target is not found in the + * compilation root */ - Result(TreePath path) { - this.path = path; + public @Nullable TreePath getPath(CompilationUnitTree root, @FindDistinct Tree target) { + // This method uses try/catch and the private {@code Result} exception for control flow to + // stop the superclass from scanning other subtrees when target is found. + + if (foundPaths.containsKey(target)) { + return foundPaths.get(target); + } + + TreePath path = new TreePath(root); + if (path.getLeaf() == target) { + return path; + } + + try { + this.scan(path, target); + } catch (Result result) { + return result.path; + } + // If a path wasn't found, cache null so the whole compilation unit isn't searched again. + foundPaths.put(target, null); + return null; } - } - - public void clear() { - foundPaths.clear(); - } - - /** Scan a single node. The current path is updated for the duration of the scan. */ - @SuppressWarnings("interning:not.interned") // assertion - @Override - public TreePath scan(Tree tree, Tree target) { - TreePath prev = path; - if (tree != null) { - TreePath foundPath = foundPaths.get(tree); - if (foundPath == null) { - foundPath = new TreePath(path, tree); - foundPaths.put(tree, foundPath); - } - this.path = foundPath; + + /** The result of {@link #getPath}. This exception is used for control flow. */ + private static class Result extends Error { + /** Unique identifier for serialization. */ + private static final long serialVersionUID = 4948452207518392627L; + + /** The result of {@link #getPath}. */ + @SuppressWarnings("serial") // I do not intend to serialize Result objects + private final TreePath path; + + /** + * Create a {@link #getPath} result. + * + * @param path the result of {@link #getPath} + */ + Result(TreePath path) { + this.path = path; + } } - if (tree == target) { - throw new Result(path); + public void clear() { + foundPaths.clear(); } - try { - return super.scan(tree, target); - } finally { - this.path = prev; + + /** Scan a single node. The current path is updated for the duration of the scan. */ + @SuppressWarnings("interning:not.interned") // assertion + @Override + public TreePath scan(Tree tree, Tree target) { + TreePath prev = path; + if (tree != null) { + TreePath foundPath = foundPaths.get(tree); + if (foundPath == null) { + foundPath = new TreePath(path, tree); + foundPaths.put(tree, foundPath); + } + this.path = foundPath; + } + + if (tree == target) { + throw new Result(path); + } + try { + return super.scan(tree, target); + } finally { + this.path = prev; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java b/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java index 2f3cda91657..c530be918fd 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java +++ b/framework/src/main/java/org/checkerframework/framework/util/TypeArgumentMapper.java @@ -1,5 +1,8 @@ package org.checkerframework.framework.util; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.plumelib.util.IPair; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -9,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; @@ -16,8 +20,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.plumelib.util.IPair; /** * Records any mapping between the type parameters of a subtype to the corresponding type parameters @@ -61,280 +63,289 @@ */ public class TypeArgumentMapper { - /** - * Returns a mapping from subtype's type parameter indices to the indices of corresponding type - * parameters in supertype. - */ - public static Set> mapTypeArgumentIndices( - TypeElement subtype, TypeElement supertype, Types types) { - Set> result = new HashSet<>(); - if (subtype.equals(supertype)) { - for (int i = 0; i < subtype.getTypeParameters().size(); i++) { - result.add(IPair.of(Integer.valueOf(i), Integer.valueOf(i))); - } - - } else { - Map> subToSuperElements = - mapTypeArguments(subtype, supertype, types); - Map supertypeIndexes = getElementToIndex(supertype); - - List subtypeParams = subtype.getTypeParameters(); - for (int subtypeIndex = 0; subtypeIndex < subtypeParams.size(); subtypeIndex++) { - TypeParameterElement subtypeParam = subtypeParams.get(subtypeIndex); - - Set correspondingSuperArgs = subToSuperElements.get(subtypeParam); - if (correspondingSuperArgs != null) { - for (TypeParameterElement supertypeParam : subToSuperElements.get(subtypeParam)) { - result.add(IPair.of(subtypeIndex, supertypeIndexes.get(supertypeParam))); - } + /** + * Returns a mapping from subtype's type parameter indices to the indices of corresponding type + * parameters in supertype. + */ + public static Set> mapTypeArgumentIndices( + TypeElement subtype, TypeElement supertype, Types types) { + Set> result = new HashSet<>(); + if (subtype.equals(supertype)) { + for (int i = 0; i < subtype.getTypeParameters().size(); i++) { + result.add(IPair.of(Integer.valueOf(i), Integer.valueOf(i))); + } + + } else { + Map> subToSuperElements = + mapTypeArguments(subtype, supertype, types); + Map supertypeIndexes = getElementToIndex(supertype); + + List subtypeParams = subtype.getTypeParameters(); + for (int subtypeIndex = 0; subtypeIndex < subtypeParams.size(); subtypeIndex++) { + TypeParameterElement subtypeParam = subtypeParams.get(subtypeIndex); + + Set correspondingSuperArgs = + subToSuperElements.get(subtypeParam); + if (correspondingSuperArgs != null) { + for (TypeParameterElement supertypeParam : + subToSuperElements.get(subtypeParam)) { + result.add(IPair.of(subtypeIndex, supertypeIndexes.get(supertypeParam))); + } + } + } } - } - } - return result; - } - - /** - * Returns a Map(type parameter symbol → index in type parameter list). - * - * @param typeElement a type whose type parameters to summarize - * @return a Map(type parameter symbol → index in type parameter list) - */ - private static Map getElementToIndex(TypeElement typeElement) { - Map result = new LinkedHashMap<>(); - - List params = typeElement.getTypeParameters(); - for (int i = 0; i < params.size(); i++) { - result.put(params.get(i), Integer.valueOf(i)); + return result; } - return result; - } - - /** - * Returns a mapping from the type parameters of subtype to a list of the type parameters in - * supertype that must be the same type as subtype. - * - *

          e.g., - * - *

          {@code
          -   * class A
          -   * class B extends A {}
          -   * }
          - * - * results in a {@code Map(B1 => [A1,A2], B2 => [], B3 => [A3], B4 => [])} - * - * @return a mapping from the type parameters of subtype to the supertype type parameter's that to - * which they are a type argument - */ - public static Map> mapTypeArguments( - TypeElement subtype, TypeElement supertype, Types types) { - - List pathToSupertype = depthFirstSearchForSupertype(subtype, supertype, types); - - if (pathToSupertype == null || pathToSupertype.isEmpty()) { - return new LinkedHashMap<>(); + /** + * Returns a Map(type parameter symbol → index in type parameter list). + * + * @param typeElement a type whose type parameters to summarize + * @return a Map(type parameter symbol → index in type parameter list) + */ + private static Map getElementToIndex(TypeElement typeElement) { + Map result = new LinkedHashMap<>(); + + List params = typeElement.getTypeParameters(); + for (int i = 0; i < params.size(); i++) { + result.put(params.get(i), Integer.valueOf(i)); + } + + return result; } - Map> intermediate = new LinkedHashMap<>(); - Set currentTypeParams = new HashSet<>(); - - // takes a type records of the form: - // TypeRecord(element = MyMap, type = null) - // TypeRecord(element = AbstractMap, type = AbstractMap) - // TypeRecord(element = Map, type = AbstractMap) - // And makes a map: - // Map(Y1 -> [A1], Y2 -> [A2], A1 -> [M1], A2 -> M2] - Iterator path = pathToSupertype.iterator(); - TypeRecord current = path.next(); - while (path.hasNext()) { - TypeRecord next = path.next(); - - List nextTypeParameter = next.element.getTypeParameters(); - List nextTypeArgs = - next.type != null ? next.type.getTypeArguments() : Collections.emptyList(); - currentTypeParams.clear(); - currentTypeParams.addAll(current.element.getTypeParameters()); - - for (int i = 0; i < nextTypeArgs.size(); i++) { - TypeParameterElement correspondingParameter = nextTypeParameter.get(i); - TypeMirror typeArg = nextTypeArgs.get(i); - Element typeArgEle = types.asElement(typeArg); - - if (currentTypeParams.contains(typeArgEle)) { - addToSetMap(intermediate, (TypeParameterElement) typeArgEle, correspondingParameter); + /** + * Returns a mapping from the type parameters of subtype to a list of the type parameters in + * supertype that must be the same type as subtype. + * + *

          e.g., + * + *

          {@code
          +     * class A
          +     * class B extends A {}
          +     * }
          + * + * results in a {@code Map(B1 => [A1,A2], B2 => [], B3 => [A3], B4 => [])} + * + * @return a mapping from the type parameters of subtype to the supertype type parameter's that + * to which they are a type argument + */ + public static Map> mapTypeArguments( + TypeElement subtype, TypeElement supertype, Types types) { + + List pathToSupertype = depthFirstSearchForSupertype(subtype, supertype, types); + + if (pathToSupertype == null || pathToSupertype.isEmpty()) { + return new LinkedHashMap<>(); } - } - } - List supertypeParams = supertype.getTypeParameters(); - Map> result = - new LinkedHashMap<>(subtype.getTypeParameters().size()); - - // You can think of the map above as a set of links from SubtypeParameter -> Supertype - // Parameter - for (TypeParameterElement subtypeParam : subtype.getTypeParameters()) { - Set subtypePath = - flattenPath(intermediate.get(subtypeParam), intermediate); - subtypePath.retainAll(supertypeParams); - result.put(subtypeParam, subtypePath); - } + Map> intermediate = new LinkedHashMap<>(); + Set currentTypeParams = new HashSet<>(); + + // takes a type records of the form: + // TypeRecord(element = MyMap, type = null) + // TypeRecord(element = AbstractMap, type = AbstractMap) + // TypeRecord(element = Map, type = AbstractMap) + // And makes a map: + // Map(Y1 -> [A1], Y2 -> [A2], A1 -> [M1], A2 -> M2] + Iterator path = pathToSupertype.iterator(); + TypeRecord current = path.next(); + while (path.hasNext()) { + TypeRecord next = path.next(); + + List nextTypeParameter = + next.element.getTypeParameters(); + List nextTypeArgs = + next.type != null ? next.type.getTypeArguments() : Collections.emptyList(); + currentTypeParams.clear(); + currentTypeParams.addAll(current.element.getTypeParameters()); + + for (int i = 0; i < nextTypeArgs.size(); i++) { + TypeParameterElement correspondingParameter = nextTypeParameter.get(i); + TypeMirror typeArg = nextTypeArgs.get(i); + Element typeArgEle = types.asElement(typeArg); + + if (currentTypeParams.contains(typeArgEle)) { + addToSetMap( + intermediate, + (TypeParameterElement) typeArgEle, + correspondingParameter); + } + } + } - return result; - } + List supertypeParams = supertype.getTypeParameters(); + Map> result = + new LinkedHashMap<>(subtype.getTypeParameters().size()); + + // You can think of the map above as a set of links from SubtypeParameter -> Supertype + // Parameter + for (TypeParameterElement subtypeParam : subtype.getTypeParameters()) { + Set subtypePath = + flattenPath(intermediate.get(subtypeParam), intermediate); + subtypePath.retainAll(supertypeParams); + result.put(subtypeParam, subtypePath); + } - private static Set flattenPath( - Set elements, - Map> map) { - Set result = new HashSet<>(); - if (elements == null) { - return result; - } - for (TypeParameterElement oldElement : elements) { - Set substitutions = map.get(oldElement); - if (substitutions != null) { - result.addAll(flattenPath(elements, map)); - } else { - result.add(oldElement); - } + return result; } - return result; - } - - private static void addToSetMap( - Map> setMap, - TypeParameterElement element, - TypeParameterElement typeParam) { - Set set = setMap.computeIfAbsent(element, __ -> new HashSet<>()); - set.add(typeParam); - } - - /** - * Create a list of TypeRecord's that form a "path" to target from subtype. e.g. Suppose I have - * the types - * - *
          {@code
          -   * interface Map
          -   * class AbstractMap implements Map, Iterable>
          -   * class MyMap extends AbstractMap implements List>
          -   * }
          - * - * The path from MyMap to Map would be: - * - *
          {@code
          -   * TypeRecord(element = MyMap, type = null)
          -   * TypeRecord(element = AbstractMap, type = AbstractMap)
          -   * TypeRecord(element = Map, type = AbstractMap)
          -   * }
          - * - * Note: You can have an implementation of the same interface inherited multiple times as long as - * the parameterization of that interface remains the same e.g. - * - *
          {@code
          -   * interface List
          -   * class AbstractList implements List
          -   * class ArrayList extends AbstractList implements List
          -   * }
          - * - * Notice how ArrayList implements list both by inheriting from AbstractList and from explicitly - * listing it in the implements clause. We prioritize finding a path through the list of - * interfaces first since this will be the shorter path. - * - * @param subtype the start of the resulting sequence - * @param target the end of the resulting sequence - * @param types utility methods for operating on types - * @return a list of type records that represents the sequence of directSupertypes between subtype - * and target - */ - private static List depthFirstSearchForSupertype( - TypeElement subtype, TypeElement target, Types types) { - ArrayDeque pathFromRoot = new ArrayDeque<>(); - TypeRecord pathStart = new TypeRecord(subtype, null); - pathFromRoot.push(pathStart); - List result = recursiveDepthFirstSearch(pathFromRoot, target, types); - return result; - } - - /** - * Computes one level for depthFirstSearchForSupertype then recurses. - * - * @param pathFromRoot the path so far - * @param target the end of the resulting path - * @param types utility methods for operating on types - * @return a list of type records that extends pathFromRoot (a sequence of directSupertypes) to - * target - */ - private static @Nullable List recursiveDepthFirstSearch( - ArrayDeque pathFromRoot, TypeElement target, Types types) { - if (pathFromRoot.isEmpty()) { - return null; + + private static Set flattenPath( + Set elements, + Map> map) { + Set result = new HashSet<>(); + if (elements == null) { + return result; + } + for (TypeParameterElement oldElement : elements) { + Set substitutions = map.get(oldElement); + if (substitutions != null) { + result.addAll(flattenPath(elements, map)); + } else { + result.add(oldElement); + } + } + return result; } - TypeRecord currentRecord = pathFromRoot.peekLast(); - TypeElement currentElement = currentRecord.element; + private static void addToSetMap( + Map> setMap, + TypeParameterElement element, + TypeParameterElement typeParam) { + Set set = setMap.computeIfAbsent(element, __ -> new HashSet<>()); + set.add(typeParam); + } - if (currentElement.equals(target)) { - return new ArrayList<>(pathFromRoot); + /** + * Create a list of TypeRecord's that form a "path" to target from subtype. e.g. Suppose I have + * the types + * + *
          {@code
          +     * interface Map
          +     * class AbstractMap implements Map, Iterable>
          +     * class MyMap extends AbstractMap implements List>
          +     * }
          + * + * The path from MyMap to Map would be: + * + *
          {@code
          +     * TypeRecord(element = MyMap, type = null)
          +     * TypeRecord(element = AbstractMap, type = AbstractMap)
          +     * TypeRecord(element = Map, type = AbstractMap)
          +     * }
          + * + * Note: You can have an implementation of the same interface inherited multiple times as long + * as the parameterization of that interface remains the same e.g. + * + *
          {@code
          +     * interface List
          +     * class AbstractList implements List
          +     * class ArrayList extends AbstractList implements List
          +     * }
          + * + * Notice how ArrayList implements list both by inheriting from AbstractList and from explicitly + * listing it in the implements clause. We prioritize finding a path through the list of + * interfaces first since this will be the shorter path. + * + * @param subtype the start of the resulting sequence + * @param target the end of the resulting sequence + * @param types utility methods for operating on types + * @return a list of type records that represents the sequence of directSupertypes between + * subtype and target + */ + private static List depthFirstSearchForSupertype( + TypeElement subtype, TypeElement target, Types types) { + ArrayDeque pathFromRoot = new ArrayDeque<>(); + TypeRecord pathStart = new TypeRecord(subtype, null); + pathFromRoot.push(pathStart); + List result = recursiveDepthFirstSearch(pathFromRoot, target, types); + return result; } - Iterator interfaces = currentElement.getInterfaces().iterator(); - TypeMirror superclassType = currentElement.getSuperclass(); + /** + * Computes one level for depthFirstSearchForSupertype then recurses. + * + * @param pathFromRoot the path so far + * @param target the end of the resulting path + * @param types utility methods for operating on types + * @return a list of type records that extends pathFromRoot (a sequence of directSupertypes) to + * target + */ + private static @Nullable List recursiveDepthFirstSearch( + ArrayDeque pathFromRoot, TypeElement target, Types types) { + if (pathFromRoot.isEmpty()) { + return null; + } - List path = null; + TypeRecord currentRecord = pathFromRoot.peekLast(); + TypeElement currentElement = currentRecord.element; - while (path == null && interfaces.hasNext()) { - TypeMirror intface = interfaces.next(); - if (intface.getKind() != TypeKind.NONE) { - DeclaredType interfaceDeclared = (DeclaredType) intface; - pathFromRoot.addLast( - new TypeRecord((TypeElement) types.asElement(interfaceDeclared), interfaceDeclared)); - path = recursiveDepthFirstSearch(pathFromRoot, target, types); - pathFromRoot.removeLast(); - } - } + if (currentElement.equals(target)) { + return new ArrayList<>(pathFromRoot); + } - if (path == null && superclassType.getKind() != TypeKind.NONE) { - DeclaredType superclass = (DeclaredType) superclassType; + Iterator interfaces = currentElement.getInterfaces().iterator(); + TypeMirror superclassType = currentElement.getSuperclass(); + + List path = null; + + while (path == null && interfaces.hasNext()) { + TypeMirror intface = interfaces.next(); + if (intface.getKind() != TypeKind.NONE) { + DeclaredType interfaceDeclared = (DeclaredType) intface; + pathFromRoot.addLast( + new TypeRecord( + (TypeElement) types.asElement(interfaceDeclared), + interfaceDeclared)); + path = recursiveDepthFirstSearch(pathFromRoot, target, types); + pathFromRoot.removeLast(); + } + } - pathFromRoot.addLast(new TypeRecord((TypeElement) types.asElement(superclass), superclass)); - path = recursiveDepthFirstSearch(pathFromRoot, target, types); - pathFromRoot.removeLast(); - } + if (path == null && superclassType.getKind() != TypeKind.NONE) { + DeclaredType superclass = (DeclaredType) superclassType; + + pathFromRoot.addLast( + new TypeRecord((TypeElement) types.asElement(superclass), superclass)); + path = recursiveDepthFirstSearch(pathFromRoot, target, types); + pathFromRoot.removeLast(); + } - return path; - } - - /** - * Maps a class or interface's declaration element to the type it would be if viewed from a - * subtype class or interface. - * - *

          e.g. suppose we have the elements for the declarations: - * - *

          {@code
          -   * class A
          -   * class B extends A
          -   * }
          - * - * The type record of B if it is viewed as class A would bed: - * - *
          {@code
          -   * TypeRecord( element = A, type = A )
          -   * }
          - * - * That is, B can be viewed as an object of type A with an type argument of type parameter Tb - */ - private static class TypeRecord { - public final TypeElement element; - public final DeclaredType type; - - TypeRecord(TypeElement element, DeclaredType type) { - this.element = element; - this.type = type; + return path; } - @Override - public String toString() { - return String.format("[%s => %s]", element, type); + /** + * Maps a class or interface's declaration element to the type it would be if viewed from a + * subtype class or interface. + * + *

          e.g. suppose we have the elements for the declarations: + * + *

          {@code
          +     * class A
          +     * class B extends A
          +     * }
          + * + * The type record of B if it is viewed as class A would bed: + * + *
          {@code
          +     * TypeRecord( element = A, type = A )
          +     * }
          + * + * That is, B can be viewed as an object of type A with an type argument of type parameter Tb + */ + private static class TypeRecord { + public final TypeElement element; + public final DeclaredType type; + + TypeRecord(TypeElement element, DeclaredType type) { + this.element = element; + this.type = type; + } + + @Override + public String toString() { + return String.format("[%s => %s]", element, type); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java b/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java index 683b2706c30..a3404ffc0e9 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java +++ b/framework/src/main/java/org/checkerframework/framework/util/VoidVisitorWithDefaultAction.java @@ -111,604 +111,604 @@ * performing an action on each node of an AST. */ public abstract class VoidVisitorWithDefaultAction extends VoidVisitorAdapter { - /** - * Action performed on each visited node. - * - * @param node node to perform action on - */ - public abstract void defaultAction(Node node); - - @Override - public void visit(AnnotationDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(AnnotationMemberDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayAccessExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayCreationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayCreationLevel n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayInitializerExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ArrayType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(AssertStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(AssignExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BinaryExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BlockComment n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BlockStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BooleanLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(BreakStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CastExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CatchClause n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CharLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ClassExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ClassOrInterfaceDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ClassOrInterfaceType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CompilationUnit n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(StubUnit n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ConditionalExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ConstructorDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ContinueStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(DoStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(DoubleLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EmptyStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EnclosedExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EnumConstantDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(EnumDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ExplicitConstructorInvocationStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ExpressionStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(FieldAccessExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(FieldDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ForStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ForEachStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(IfStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ImportDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(InitializerDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(InstanceOfExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(IntegerLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(IntersectionType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(JavadocComment n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LabeledStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LambdaExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LineComment n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LocalClassDeclarationStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LongLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MarkerAnnotationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MemberValuePair n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MethodCallExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MethodDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(MethodReferenceExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(NameExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(Name n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(NormalAnnotationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(NullLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ObjectCreationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(PackageDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(Parameter n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(PrimitiveType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ReturnStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SimpleName n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SingleMemberAnnotationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(StringLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SuperExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SwitchEntry n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SwitchStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SynchronizedStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ThisExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ThrowStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TryStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TypeExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TypeParameter n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnaryExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnionType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnknownType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VariableDeclarationExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VariableDeclarator n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VoidType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(WhileStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(WildcardType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleRequiresDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleExportsDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleProvidesDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleUsesDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ModuleOpensDirective n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(UnparsableStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(ReceiverParameter n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(VarType n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(Modifier n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(SwitchExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(TextBlockLiteralExpr n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(YieldStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(RecordDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(LocalRecordDeclarationStmt n, Void p) { - super.visit(n, p); - defaultAction(n); - } - - @Override - public void visit(CompactConstructorDeclaration n, Void p) { - super.visit(n, p); - defaultAction(n); - } + /** + * Action performed on each visited node. + * + * @param node node to perform action on + */ + public abstract void defaultAction(Node node); + + @Override + public void visit(AnnotationDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(AnnotationMemberDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayAccessExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayCreationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayCreationLevel n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayInitializerExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ArrayType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(AssertStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(AssignExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BinaryExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BlockComment n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BlockStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BooleanLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(BreakStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CastExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CatchClause n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CharLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ClassExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ClassOrInterfaceDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ClassOrInterfaceType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CompilationUnit n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(StubUnit n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ConditionalExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ConstructorDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ContinueStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(DoStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(DoubleLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EmptyStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EnclosedExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EnumConstantDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(EnumDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ExplicitConstructorInvocationStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ExpressionStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(FieldAccessExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(FieldDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ForStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ForEachStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(IfStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ImportDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(InitializerDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(InstanceOfExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(IntegerLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(IntersectionType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(JavadocComment n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LabeledStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LambdaExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LineComment n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LocalClassDeclarationStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LongLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MarkerAnnotationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MemberValuePair n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MethodCallExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MethodDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(MethodReferenceExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(NameExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(Name n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(NormalAnnotationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(NullLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ObjectCreationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(PackageDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(Parameter n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(PrimitiveType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ReturnStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SimpleName n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SingleMemberAnnotationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(StringLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SuperExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SwitchEntry n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SwitchStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SynchronizedStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ThisExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ThrowStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TryStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TypeExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TypeParameter n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnaryExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnionType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnknownType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VariableDeclarationExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VariableDeclarator n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VoidType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(WhileStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(WildcardType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleRequiresDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleExportsDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleProvidesDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleUsesDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ModuleOpensDirective n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(UnparsableStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(ReceiverParameter n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(VarType n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(Modifier n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(SwitchExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(TextBlockLiteralExpr n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(YieldStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(RecordDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(LocalRecordDeclarationStmt n, Void p) { + super.visit(n, p); + defaultAction(n); + } + + @Override + public void visit(CompactConstructorDeclaration n, Void p) { + super.visit(n, p); + defaultAction(n); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java index bc02c881efb..dd42d8ad91f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/Default.java @@ -1,11 +1,13 @@ package org.checkerframework.framework.util.defaults; -import java.util.Objects; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.javacutil.AnnotationUtils; +import java.util.Objects; + +import javax.lang.model.element.AnnotationMirror; + /** * Represents a mapping from an Annotation to a TypeUseLocation it should be applied to during * defaulting. The Comparable ordering of this class first tests location then tests annotation @@ -14,73 +16,73 @@ *

          It also has a handy toString method that is useful for debugging. */ public class Default implements Comparable { - // please remember to add any fields to the hashcode calculation - /** The default annotation mirror. */ - public final AnnotationMirror anno; + // please remember to add any fields to the hashcode calculation + /** The default annotation mirror. */ + public final AnnotationMirror anno; - /** The type use location. */ - public final TypeUseLocation location; + /** The type use location. */ + public final TypeUseLocation location; - /** Whether the default should be inherited by subpackages. */ - public final boolean applyToSubpackages; + /** Whether the default should be inherited by subpackages. */ + public final boolean applyToSubpackages; - /** - * Construct a Default object. - * - * @param anno the default annotation mirror - * @param location the type use location - * @param applyToSubpackages whether the default should be inherited by subpackages - */ - public Default(AnnotationMirror anno, TypeUseLocation location, boolean applyToSubpackages) { - this.anno = anno; - this.location = location; - this.applyToSubpackages = applyToSubpackages; - } + /** + * Construct a Default object. + * + * @param anno the default annotation mirror + * @param location the type use location + * @param applyToSubpackages whether the default should be inherited by subpackages + */ + public Default(AnnotationMirror anno, TypeUseLocation location, boolean applyToSubpackages) { + this.anno = anno; + this.location = location; + this.applyToSubpackages = applyToSubpackages; + } - @Override - public int compareTo(Default other) { - int locationOrder = location.compareTo(other.location); - if (locationOrder == 0) { - int annoOrder = AnnotationUtils.compareAnnotationMirrors(anno, other.anno); - if (annoOrder == 0) { - if (applyToSubpackages == other.applyToSubpackages) { - return 0; + @Override + public int compareTo(Default other) { + int locationOrder = location.compareTo(other.location); + if (locationOrder == 0) { + int annoOrder = AnnotationUtils.compareAnnotationMirrors(anno, other.anno); + if (annoOrder == 0) { + if (applyToSubpackages == other.applyToSubpackages) { + return 0; + } else { + return applyToSubpackages ? 1 : -1; + } + } else { + return annoOrder; + } } else { - return applyToSubpackages ? 1 : -1; + return locationOrder; } - } else { - return annoOrder; - } - } else { - return locationOrder; } - } - @Override - public boolean equals(@Nullable Object thatObj) { - if (thatObj == this) { - return true; - } + @Override + public boolean equals(@Nullable Object thatObj) { + if (thatObj == this) { + return true; + } - if (thatObj == null || thatObj.getClass() != Default.class) { - return false; - } + if (thatObj == null || thatObj.getClass() != Default.class) { + return false; + } - return compareTo((Default) thatObj) == 0; - } + return compareTo((Default) thatObj) == 0; + } - @Override - public int hashCode() { - return Objects.hash(anno, location, applyToSubpackages); - } + @Override + public int hashCode() { + return Objects.hash(anno, location, applyToSubpackages); + } - @Override - public String toString() { - return "( " - + location.name() - + " => " - + anno - + (applyToSubpackages ? " applies to subpackages" : "") - + " )"; - } + @Override + public String toString() { + return "( " + + location.name() + + " => " + + anno + + (applyToSubpackages ? " applies to subpackages" : "") + + " )"; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java index 465bc253b25..8ba438bd1a8 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/DefaultSet.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.util.defaults; -import java.util.TreeSet; import org.plumelib.util.StringsPlume; +import java.util.TreeSet; + /** * An ordered set of Defaults (see {@link org.checkerframework.framework.util.defaults.Default}). * This class provides a little syntactic sugar and a better toString over TreeSet. @@ -10,15 +11,15 @@ @SuppressWarnings("serial") class DefaultSet extends TreeSet { - /** Creates a DefaultSet. */ - public DefaultSet() { - super(Default::compareTo); - } + /** Creates a DefaultSet. */ + public DefaultSet() { + super(Default::compareTo); + } - @Override - public String toString() { - return "DefaultSet( " + StringsPlume.join(", ", this) + " )"; - } + @Override + public String toString() { + return "DefaultSet( " + StringsPlume.join(", ", this) + " )"; + } - public static final DefaultSet EMPTY = new DefaultSet(); + public static final DefaultSet EMPTY = new DefaultSet(); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 14ebde49909..fced09455fd 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -10,22 +10,7 @@ import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import java.util.Arrays; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; + import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.AnnotatedFor; @@ -53,6 +38,24 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.StringsPlume; +import java.util.Arrays; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Elements; + /** * Determines the default qualifiers on a type. Default qualifiers are specified via the {@link * org.checkerframework.framework.qual.DefaultQualifier} annotation. @@ -75,1328 +78,1359 @@ */ public class QualifierDefaults { - // TODO add visitor state to get the default annotations from the top down? - // TODO apply from package elements also - // TODO try to remove some dependencies (e.g. on factory) - - /** Element utilities to use. */ - private final Elements elements; - - /** The value() element/field of a @DefaultQualifier annotation. */ - protected final ExecutableElement defaultQualifierValueElement; - - /** The locations() element/field of a @DefaultQualifier annotation. */ - protected final ExecutableElement defaultQualifierLocationsElement; - - /** The applyToSubpackages() element/field of a @DefaultQualifier annotation. */ - protected final ExecutableElement defaultQualifierApplyToSubpackagesElement; - - /** The value() element/field of a @DefaultQualifier.List annotation. */ - protected final ExecutableElement defaultQualifierListValueElement; - - /** AnnotatedTypeFactory to use. */ - private final AnnotatedTypeFactory atypeFactory; - - /** Defaults for checked code. */ - private final DefaultSet checkedCodeDefaults = new DefaultSet(); - - /** Defaults for unchecked code. */ - private final DefaultSet uncheckedCodeDefaults = new DefaultSet(); - - /** Size for caches. */ - private static final int CACHE_SIZE = 300; - - /** Mapping from an Element to the bound type. */ - protected final Map elementToBoundType = - CollectionsPlume.createLruCache(CACHE_SIZE); - - /** - * Defaults that apply for a certain Element. On the one hand this is used for caching (an earlier - * name for the field was "qualifierCache"). It can also be used by type systems to set defaults - * for certain Elements. - */ - private final IdentityHashMap elementDefaults = new IdentityHashMap<>(); - - /** A mapping of Element → Whether or not that element is AnnotatedFor this type system. */ - private final IdentityHashMap elementAnnotatedFors = new IdentityHashMap<>(); - - /** CLIMB locations whose standard default is top for a given type system. */ - public static final List STANDARD_CLIMB_DEFAULTS_TOP = - Collections.unmodifiableList( - Arrays.asList( - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.RESOURCE_VARIABLE, - TypeUseLocation.EXCEPTION_PARAMETER, - TypeUseLocation.IMPLICIT_UPPER_BOUND)); - - /** CLIMB locations whose standard default is bottom for a given type system. */ - public static final List STANDARD_CLIMB_DEFAULTS_BOTTOM = - Collections.unmodifiableList(Arrays.asList(TypeUseLocation.IMPLICIT_LOWER_BOUND)); - - /** List of TypeUseLocations that are valid for unchecked code defaults. */ - private static final List validUncheckedCodeDefaultLocations = - Collections.unmodifiableList( - Arrays.asList( - TypeUseLocation.FIELD, - TypeUseLocation.PARAMETER, - TypeUseLocation.RETURN, - TypeUseLocation.RECEIVER, - TypeUseLocation.UPPER_BOUND, - TypeUseLocation.LOWER_BOUND, - TypeUseLocation.OTHERWISE, - TypeUseLocation.ALL)); - - /** Standard unchecked default locations that should be top. */ - // Fields are defaulted to top so that warnings are issued at field reads, which we believe are - // more common than field writes. Future work is to specify different defaults for field reads - // and field writes. (When a field is written to, its type should be bottom.) - public static final List STANDARD_UNCHECKED_DEFAULTS_TOP = - Collections.unmodifiableList( - Arrays.asList( - TypeUseLocation.RETURN, TypeUseLocation.FIELD, TypeUseLocation.UPPER_BOUND)); - - /** Standard unchecked default locations that should be bottom. */ - public static final List STANDARD_UNCHECKED_DEFAULTS_BOTTOM = - Collections.unmodifiableList( - Arrays.asList(TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND)); - - /** True if conservative defaults should be used in unannotated source code. */ - private final boolean useConservativeDefaultsSource; - - /** True if conservative defaults should be used for bytecode. */ - private final boolean useConservativeDefaultsBytecode; - - /** - * Returns an array of locations that are valid for the unchecked value defaults. These are simply - * by syntax, since an entire file is typechecked, it is not possible for local variables to be - * unchecked. - */ - public static List validLocationsForUncheckedCodeDefaults() { - return validUncheckedCodeDefaultLocations; - } - - /** - * @param elements interface to Element data in the current processing environment - * @param atypeFactory an annotation factory, used to get annotations by name - */ - public QualifierDefaults(Elements elements, AnnotatedTypeFactory atypeFactory) { - this.elements = elements; - this.atypeFactory = atypeFactory; - this.useConservativeDefaultsBytecode = - atypeFactory.getChecker().useConservativeDefault("bytecode"); - this.useConservativeDefaultsSource = atypeFactory.getChecker().useConservativeDefault("source"); - ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); - this.defaultQualifierValueElement = - TreeUtils.getMethod(DefaultQualifier.class, "value", 0, processingEnv); - this.defaultQualifierLocationsElement = - TreeUtils.getMethod(DefaultQualifier.class, "locations", 0, processingEnv); - this.defaultQualifierApplyToSubpackagesElement = - TreeUtils.getMethod(DefaultQualifier.class, "applyToSubpackages", 0, processingEnv); - this.defaultQualifierListValueElement = - TreeUtils.getMethod(DefaultQualifier.List.class, "value", 0, processingEnv); - } - - @Override - public String toString() { - // displays the checked and unchecked code defaults - return StringsPlume.joinLines( - "Checked code defaults: ", - StringsPlume.joinLines(checkedCodeDefaults), - "Unchecked code defaults: ", - StringsPlume.joinLines(uncheckedCodeDefaults), - "useConservativeDefaultsSource: " + useConservativeDefaultsSource, - "useConservativeDefaultsBytecode: " + useConservativeDefaultsBytecode); - } - - /** - * Check that a default with TypeUseLocation OTHERWISE or ALL is specified. - * - * @return whether we found a Default with location OTHERWISE or ALL - */ - public boolean hasDefaultsForCheckedCode() { - for (Default def : checkedCodeDefaults) { - if (def.location == TypeUseLocation.OTHERWISE || def.location == TypeUseLocation.ALL) { - return true; - } + // TODO add visitor state to get the default annotations from the top down? + // TODO apply from package elements also + // TODO try to remove some dependencies (e.g. on factory) + + /** Element utilities to use. */ + private final Elements elements; + + /** The value() element/field of a @DefaultQualifier annotation. */ + protected final ExecutableElement defaultQualifierValueElement; + + /** The locations() element/field of a @DefaultQualifier annotation. */ + protected final ExecutableElement defaultQualifierLocationsElement; + + /** The applyToSubpackages() element/field of a @DefaultQualifier annotation. */ + protected final ExecutableElement defaultQualifierApplyToSubpackagesElement; + + /** The value() element/field of a @DefaultQualifier.List annotation. */ + protected final ExecutableElement defaultQualifierListValueElement; + + /** AnnotatedTypeFactory to use. */ + private final AnnotatedTypeFactory atypeFactory; + + /** Defaults for checked code. */ + private final DefaultSet checkedCodeDefaults = new DefaultSet(); + + /** Defaults for unchecked code. */ + private final DefaultSet uncheckedCodeDefaults = new DefaultSet(); + + /** Size for caches. */ + private static final int CACHE_SIZE = 300; + + /** Mapping from an Element to the bound type. */ + protected final Map elementToBoundType = + CollectionsPlume.createLruCache(CACHE_SIZE); + + /** + * Defaults that apply for a certain Element. On the one hand this is used for caching (an + * earlier name for the field was "qualifierCache"). It can also be used by type systems to set + * defaults for certain Elements. + */ + private final IdentityHashMap elementDefaults = new IdentityHashMap<>(); + + /** A mapping of Element → Whether or not that element is AnnotatedFor this type system. */ + private final IdentityHashMap elementAnnotatedFors = new IdentityHashMap<>(); + + /** CLIMB locations whose standard default is top for a given type system. */ + public static final List STANDARD_CLIMB_DEFAULTS_TOP = + Collections.unmodifiableList( + Arrays.asList( + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.RESOURCE_VARIABLE, + TypeUseLocation.EXCEPTION_PARAMETER, + TypeUseLocation.IMPLICIT_UPPER_BOUND)); + + /** CLIMB locations whose standard default is bottom for a given type system. */ + public static final List STANDARD_CLIMB_DEFAULTS_BOTTOM = + Collections.unmodifiableList(Arrays.asList(TypeUseLocation.IMPLICIT_LOWER_BOUND)); + + /** List of TypeUseLocations that are valid for unchecked code defaults. */ + private static final List validUncheckedCodeDefaultLocations = + Collections.unmodifiableList( + Arrays.asList( + TypeUseLocation.FIELD, + TypeUseLocation.PARAMETER, + TypeUseLocation.RETURN, + TypeUseLocation.RECEIVER, + TypeUseLocation.UPPER_BOUND, + TypeUseLocation.LOWER_BOUND, + TypeUseLocation.OTHERWISE, + TypeUseLocation.ALL)); + + /** Standard unchecked default locations that should be top. */ + // Fields are defaulted to top so that warnings are issued at field reads, which we believe are + // more common than field writes. Future work is to specify different defaults for field reads + // and field writes. (When a field is written to, its type should be bottom.) + public static final List STANDARD_UNCHECKED_DEFAULTS_TOP = + Collections.unmodifiableList( + Arrays.asList( + TypeUseLocation.RETURN, + TypeUseLocation.FIELD, + TypeUseLocation.UPPER_BOUND)); + + /** Standard unchecked default locations that should be bottom. */ + public static final List STANDARD_UNCHECKED_DEFAULTS_BOTTOM = + Collections.unmodifiableList( + Arrays.asList(TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND)); + + /** True if conservative defaults should be used in unannotated source code. */ + private final boolean useConservativeDefaultsSource; + + /** True if conservative defaults should be used for bytecode. */ + private final boolean useConservativeDefaultsBytecode; + + /** + * Returns an array of locations that are valid for the unchecked value defaults. These are + * simply by syntax, since an entire file is typechecked, it is not possible for local variables + * to be unchecked. + */ + public static List validLocationsForUncheckedCodeDefaults() { + return validUncheckedCodeDefaultLocations; } - return false; - } - - /** Add standard unchecked defaults that do not conflict with previously added defaults. */ - public void addUncheckedStandardDefaults() { - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); - - for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_TOP) { - // Only add standard defaults in locations where a default has not be specified. - for (AnnotationMirror top : tops) { - if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, top, loc)) { - addUncheckedCodeDefault(top, loc); - } - } + + /** + * @param elements interface to Element data in the current processing environment + * @param atypeFactory an annotation factory, used to get annotations by name + */ + public QualifierDefaults(Elements elements, AnnotatedTypeFactory atypeFactory) { + this.elements = elements; + this.atypeFactory = atypeFactory; + this.useConservativeDefaultsBytecode = + atypeFactory.getChecker().useConservativeDefault("bytecode"); + this.useConservativeDefaultsSource = + atypeFactory.getChecker().useConservativeDefault("source"); + ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv(); + this.defaultQualifierValueElement = + TreeUtils.getMethod(DefaultQualifier.class, "value", 0, processingEnv); + this.defaultQualifierLocationsElement = + TreeUtils.getMethod(DefaultQualifier.class, "locations", 0, processingEnv); + this.defaultQualifierApplyToSubpackagesElement = + TreeUtils.getMethod(DefaultQualifier.class, "applyToSubpackages", 0, processingEnv); + this.defaultQualifierListValueElement = + TreeUtils.getMethod(DefaultQualifier.List.class, "value", 0, processingEnv); } - for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_BOTTOM) { - for (AnnotationMirror bottom : bottoms) { - // Only add standard defaults in locations where a default has not be specified. - if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, bottom, loc)) { - addUncheckedCodeDefault(bottom, loc); - } - } + @Override + public String toString() { + // displays the checked and unchecked code defaults + return StringsPlume.joinLines( + "Checked code defaults: ", + StringsPlume.joinLines(checkedCodeDefaults), + "Unchecked code defaults: ", + StringsPlume.joinLines(uncheckedCodeDefaults), + "useConservativeDefaultsSource: " + useConservativeDefaultsSource, + "useConservativeDefaultsBytecode: " + useConservativeDefaultsBytecode); } - } - - /** Add standard CLIMB defaults that do not conflict with previously added defaults. */ - public void addClimbStandardDefaults() { - QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); - AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); - AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); - - for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_TOP) { - for (AnnotationMirror top : tops) { - if (!conflictsWithExistingDefaults(checkedCodeDefaults, top, loc)) { - // Only add standard defaults in locations where a default has not been - // specified. - addCheckedCodeDefault(top, loc); + + /** + * Check that a default with TypeUseLocation OTHERWISE or ALL is specified. + * + * @return whether we found a Default with location OTHERWISE or ALL + */ + public boolean hasDefaultsForCheckedCode() { + for (Default def : checkedCodeDefaults) { + if (def.location == TypeUseLocation.OTHERWISE || def.location == TypeUseLocation.ALL) { + return true; + } } - } + return false; } - for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_BOTTOM) { - for (AnnotationMirror bottom : bottoms) { - if (!conflictsWithExistingDefaults(checkedCodeDefaults, bottom, loc)) { - // Only add standard defaults in locations where a default has not been - // specified. - addCheckedCodeDefault(bottom, loc); + /** Add standard unchecked defaults that do not conflict with previously added defaults. */ + public void addUncheckedStandardDefaults() { + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); + + for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_TOP) { + // Only add standard defaults in locations where a default has not be specified. + for (AnnotationMirror top : tops) { + if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, top, loc)) { + addUncheckedCodeDefault(top, loc); + } + } + } + + for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_BOTTOM) { + for (AnnotationMirror bottom : bottoms) { + // Only add standard defaults in locations where a default has not be specified. + if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, bottom, loc)) { + addUncheckedCodeDefault(bottom, loc); + } + } } - } - } - } - - /** - * Adds a default annotation. A programmer may override this by writing the @DefaultQualifier - * annotation on an element. - * - * @param absoluteDefaultAnno the default annotation mirror - * @param location the type use location - * @param applyToSubpackages whether the default should be inherited by subpackages - */ - public void addCheckedCodeDefault( - AnnotationMirror absoluteDefaultAnno, TypeUseLocation location, boolean applyToSubpackages) { - checkDuplicates(checkedCodeDefaults, absoluteDefaultAnno, location); - checkedCodeDefaults.add(new Default(absoluteDefaultAnno, location, applyToSubpackages)); - } - - /** - * Adds a default annotation that also applies to subpackages, if applicable. A programmer may - * override this by writing the @DefaultQualifier annotation on an element. - * - * @param absoluteDefaultAnno the default annotation mirror - * @param location the type use location - */ - public void addCheckedCodeDefault( - AnnotationMirror absoluteDefaultAnno, TypeUseLocation location) { - addCheckedCodeDefault(absoluteDefaultAnno, location, true); - } - - /** - * Add a default annotation for unchecked elements. - * - * @param uncheckedDefaultAnno the default annotation mirror - * @param location the type use location - * @param applyToSubpackages whether the default should be inherited by subpackages - */ - public void addUncheckedCodeDefault( - AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location, boolean applyToSubpackages) { - checkDuplicates(uncheckedCodeDefaults, uncheckedDefaultAnno, location); - checkIsValidUncheckedCodeLocation(uncheckedDefaultAnno, location); - - uncheckedCodeDefaults.add(new Default(uncheckedDefaultAnno, location, applyToSubpackages)); - } - - /** - * Add a default annotation for unchecked elements that also applies to subpackages, if - * applicable. - * - * @param uncheckedDefaultAnno the default annotation mirror - * @param location the type use location - */ - public void addUncheckedCodeDefault( - AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { - addUncheckedCodeDefault(uncheckedDefaultAnno, location, true); - } - - /** Sets the default annotation for unchecked elements, with specific locations. */ - public void addUncheckedCodeDefaults( - AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { - for (TypeUseLocation location : locations) { - addUncheckedCodeDefault(absoluteDefaultAnno, location); } - } - public void addCheckedCodeDefaults( - AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { - for (TypeUseLocation location : locations) { - addCheckedCodeDefault(absoluteDefaultAnno, location); + /** Add standard CLIMB defaults that do not conflict with previously added defaults. */ + public void addClimbStandardDefaults() { + QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy(); + AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations(); + AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations(); + + for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_TOP) { + for (AnnotationMirror top : tops) { + if (!conflictsWithExistingDefaults(checkedCodeDefaults, top, loc)) { + // Only add standard defaults in locations where a default has not been + // specified. + addCheckedCodeDefault(top, loc); + } + } + } + + for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_BOTTOM) { + for (AnnotationMirror bottom : bottoms) { + if (!conflictsWithExistingDefaults(checkedCodeDefaults, bottom, loc)) { + // Only add standard defaults in locations where a default has not been + // specified. + addCheckedCodeDefault(bottom, loc); + } + } + } } - } - - /** - * Sets the default annotations for a certain Element. - * - * @param elem the scope to set the default within - * @param elementDefaultAnno the default to set - * @param location the location to apply the default to - */ - /* - * TODO(cpovirk): This method looks dangerous for a type system to call early: If it "adds" a - * default for an Element before defaultsAt runs for that Element, that looks like it would - * prevent any @DefaultQualifier or similar annotation from having any effect (because - * defaultsAt would short-circuit after discovering that an entry already exists for the - * Element). Maybe this method should run defaultsAt before inserting its own entry? Or maybe - * it's too early to run defaultsAt? Or maybe we'd see new problems in existing code because - * we'd start running checkDuplicates to look for overlap between the @DefaultQualifier defaults - * and addElementDefault defaults? - */ - public void addElementDefault( - Element elem, AnnotationMirror elementDefaultAnno, TypeUseLocation location) { - DefaultSet prevset = elementDefaults.get(elem); - if (prevset != null) { - checkDuplicates(prevset, elementDefaultAnno, location); - } else { - prevset = new DefaultSet(); + + /** + * Adds a default annotation. A programmer may override this by writing the @DefaultQualifier + * annotation on an element. + * + * @param absoluteDefaultAnno the default annotation mirror + * @param location the type use location + * @param applyToSubpackages whether the default should be inherited by subpackages + */ + public void addCheckedCodeDefault( + AnnotationMirror absoluteDefaultAnno, + TypeUseLocation location, + boolean applyToSubpackages) { + checkDuplicates(checkedCodeDefaults, absoluteDefaultAnno, location); + checkedCodeDefaults.add(new Default(absoluteDefaultAnno, location, applyToSubpackages)); } - // TODO: expose applyToSubpackages - prevset.add(new Default(elementDefaultAnno, location, true)); - elementDefaults.put(elem, prevset); - } - - private void checkIsValidUncheckedCodeLocation( - AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { - boolean isValidUntypeLocation = false; - for (TypeUseLocation validLoc : validLocationsForUncheckedCodeDefaults()) { - if (location == validLoc) { - isValidUntypeLocation = true; - break; - } + + /** + * Adds a default annotation that also applies to subpackages, if applicable. A programmer may + * override this by writing the @DefaultQualifier annotation on an element. + * + * @param absoluteDefaultAnno the default annotation mirror + * @param location the type use location + */ + public void addCheckedCodeDefault( + AnnotationMirror absoluteDefaultAnno, TypeUseLocation location) { + addCheckedCodeDefault(absoluteDefaultAnno, location, true); } - if (!isValidUntypeLocation) { - throw new BugInCF( - "Invalid unchecked code default location: " + location + " -> " + uncheckedDefaultAnno); + /** + * Add a default annotation for unchecked elements. + * + * @param uncheckedDefaultAnno the default annotation mirror + * @param location the type use location + * @param applyToSubpackages whether the default should be inherited by subpackages + */ + public void addUncheckedCodeDefault( + AnnotationMirror uncheckedDefaultAnno, + TypeUseLocation location, + boolean applyToSubpackages) { + checkDuplicates(uncheckedCodeDefaults, uncheckedDefaultAnno, location); + checkIsValidUncheckedCodeLocation(uncheckedDefaultAnno, location); + + uncheckedCodeDefaults.add(new Default(uncheckedDefaultAnno, location, applyToSubpackages)); } - } - - private void checkDuplicates( - DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { - if (conflictsWithExistingDefaults(previousDefaults, newAnno, newLoc)) { - throw new BugInCF( - "Only one qualifier from a hierarchy can be the default. Existing: " - + previousDefaults - + " and new: " - // TODO: expose applyToSubpackages - + new Default(newAnno, newLoc, true)); + + /** + * Add a default annotation for unchecked elements that also applies to subpackages, if + * applicable. + * + * @param uncheckedDefaultAnno the default annotation mirror + * @param location the type use location + */ + public void addUncheckedCodeDefault( + AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { + addUncheckedCodeDefault(uncheckedDefaultAnno, location, true); } - } - - /** - * Returns true if there are conflicts with existing defaults. - * - * @param previousDefaults the previous defaults - * @param newAnno the new annotation - * @param newLoc the location of the type use - * @return true if there are conflicts with existing defaults - */ - private boolean conflictsWithExistingDefaults( - DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { - QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); - - for (Default previous : previousDefaults) { - if (!AnnotationUtils.areSame(newAnno, previous.anno) && previous.location == newLoc) { - AnnotationMirror previousTop = qualHierarchy.getTopAnnotation(previous.anno); - if (qualHierarchy.isSubtypeQualifiersOnly(newAnno, previousTop)) { - return true; + + /** Sets the default annotation for unchecked elements, with specific locations. */ + public void addUncheckedCodeDefaults( + AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { + for (TypeUseLocation location : locations) { + addUncheckedCodeDefault(absoluteDefaultAnno, location); } - } - } - return false; - } - - /** - * Applies default annotations to a type obtained from an {@link - * javax.lang.model.element.Element}. - * - * @param elt the element from which the type was obtained - * @param type the type to annotate - */ - public void annotate(Element elt, AnnotatedTypeMirror type) { - if (elt != null) { - switch (elt.getKind()) { - case FIELD: - case LOCAL_VARIABLE: - case PARAMETER: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - case ENUM_CONSTANT: - String varName = elt.getSimpleName().toString(); - ((GenericAnnotatedTypeFactory) atypeFactory) - .getDefaultForTypeAnnotator() - .defaultTypeFromName(type, varName); - break; - - case METHOD: - String methodName = elt.getSimpleName().toString(); - AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) type).getReturnType(); - ((GenericAnnotatedTypeFactory) atypeFactory) - .getDefaultForTypeAnnotator() - .defaultTypeFromName(returnType, methodName); - break; - - default: - break; - } } - applyDefaultsElement(elt, type, false); - } - - /** - * Applies default annotations to a type given a {@link com.sun.source.tree.Tree}. - * - * @param tree the tree from which the type was obtained - * @param type the type to annotate - */ - public void annotate(Tree tree, AnnotatedTypeMirror type) { - applyDefaults(tree, type); - } - - /** - * Determines the nearest enclosing element for a tree by climbing the tree toward the root and - * obtaining the element for the first declaration (variable, method, or class) that encloses the - * tree. Initializers of local variables are handled in a special way: within an initializer we - * look for the DefaultQualifier(s) annotation and keep track of the previously visited tree. - * TODO: explain the behavior better. - * - * @param tree the tree - * @return the nearest enclosing element for a tree - */ - private @Nullable Element nearestEnclosingExceptLocal(Tree tree) { - TreePath path = atypeFactory.getPath(tree); - if (path == null) { - return TreeUtils.elementFromTree(tree); + public void addCheckedCodeDefaults( + AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) { + for (TypeUseLocation location : locations) { + addCheckedCodeDefault(absoluteDefaultAnno, location); + } } - Tree prev = null; - - for (Tree t : path) { - switch (TreeUtils.getKindRecordAsClass(t)) { - case ANNOTATED_TYPE: - case ANNOTATION: - // If the tree is in an annotation, then there is no relevant scope. - return null; - case VARIABLE: - VariableTree vtree = (VariableTree) t; - ExpressionTree vtreeInit = vtree.getInitializer(); - @SuppressWarnings("interning:not.interned") // check cached value - boolean sameAsPrev = (vtreeInit != null && prev == vtreeInit); - if (sameAsPrev) { - Element elt = TreeUtils.elementFromDeclaration((VariableTree) t); - AnnotationMirror d = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); - AnnotationMirror ds = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); - - if (d == null && ds == null) { - break; - } - } - if (prev != null && prev.getKind() == Tree.Kind.MODIFIERS) { - // Annotations are modifiers. We do not want to apply the local variable - // default to annotations. Without this, test fenum/TestSwitch failed, - // because the default for an argument became incompatible with the declared - // type. - break; - } - return TreeUtils.elementFromDeclaration((VariableTree) t); - case METHOD: - return TreeUtils.elementFromDeclaration((MethodTree) t); - case CLASS: // Including RECORD - case ENUM: - case INTERFACE: - case ANNOTATION_TYPE: - return TreeUtils.elementFromDeclaration((ClassTree) t); - default: // Do nothing. - } - prev = t; + /** + * Sets the default annotations for a certain Element. + * + * @param elem the scope to set the default within + * @param elementDefaultAnno the default to set + * @param location the location to apply the default to + */ + /* + * TODO(cpovirk): This method looks dangerous for a type system to call early: If it "adds" a + * default for an Element before defaultsAt runs for that Element, that looks like it would + * prevent any @DefaultQualifier or similar annotation from having any effect (because + * defaultsAt would short-circuit after discovering that an entry already exists for the + * Element). Maybe this method should run defaultsAt before inserting its own entry? Or maybe + * it's too early to run defaultsAt? Or maybe we'd see new problems in existing code because + * we'd start running checkDuplicates to look for overlap between the @DefaultQualifier defaults + * and addElementDefault defaults? + */ + public void addElementDefault( + Element elem, AnnotationMirror elementDefaultAnno, TypeUseLocation location) { + DefaultSet prevset = elementDefaults.get(elem); + if (prevset != null) { + checkDuplicates(prevset, elementDefaultAnno, location); + } else { + prevset = new DefaultSet(); + } + // TODO: expose applyToSubpackages + prevset.add(new Default(elementDefaultAnno, location, true)); + elementDefaults.put(elem, prevset); } - return null; - } - - /** - * Applies default annotations to a type. A {@link com.sun.source.tree.Tree} determines the - * appropriate scope for defaults. - * - *

          For instance, if the tree is associated with a declaration (e.g., it's the use of a field, - * or a method invocation), defaults in the scope of the declaration are used; if the tree - * is not associated with a declaration (e.g., a typecast), defaults in the scope of the tree are - * used. - * - * @param tree the tree associated with the type - * @param type the type to which defaults will be applied - * @see #applyDefaultsElement(javax.lang.model.element.Element, - * org.checkerframework.framework.type.AnnotatedTypeMirror,boolean) - */ - private void applyDefaults(Tree tree, AnnotatedTypeMirror type) { - // The location to take defaults from. - Element elt; - switch (tree.getKind()) { - case MEMBER_SELECT: - elt = TreeUtils.elementFromUse((MemberSelectTree) tree); - break; - - case IDENTIFIER: - elt = TreeUtils.elementFromUse((IdentifierTree) tree); - if (ElementUtils.isTypeDeclaration(elt)) { - // If the identifier is a type, then use the scope of the tree. - elt = nearestEnclosingExceptLocal(tree); + private void checkIsValidUncheckedCodeLocation( + AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) { + boolean isValidUntypeLocation = false; + for (TypeUseLocation validLoc : validLocationsForUncheckedCodeDefaults()) { + if (location == validLoc) { + isValidUntypeLocation = true; + break; + } } - break; - - case METHOD_INVOCATION: - elt = TreeUtils.elementFromUse((MethodInvocationTree) tree); - break; - - // TODO cases for array access, etc. -- every expression tree - // (The above probably means that we should use defaults in the - // scope of the declaration of the array. Is that right? -MDE) - default: - // If no associated symbol was found, use the tree's (lexical) scope. - elt = nearestEnclosingExceptLocal(tree); - // elt = nearestEnclosing(tree); - } - // System.out.println("applyDefaults on tree " + tree + - // " gives elt: " + elt + "(" + elt.getKind() + ")"); - - applyDefaultsElement(elt, type, true); - } - - /** The default {@code value} element for a @DefaultQualifier annotation. */ - private static final TypeUseLocation[] defaultQualifierValueDefault = - new TypeUseLocation[] {org.checkerframework.framework.qual.TypeUseLocation.ALL}; - - /** - * Create a DefaultSet from a @DefaultQualifier annotation. - * - * @param dq a @DefaultQualifier annotation - * @return a DefaultSet corresponding to the @DefaultQualifier annotation - */ - private @Nullable DefaultSet fromDefaultQualifier(AnnotationMirror dq) { - @SuppressWarnings("unchecked") - Name cls = AnnotationUtils.getElementValueClassName(dq, defaultQualifierValueElement); - AnnotationMirror anno = AnnotationBuilder.fromName(elements, cls); - - if (anno == null) { - return null; + if (!isValidUntypeLocation) { + throw new BugInCF( + "Invalid unchecked code default location: " + + location + + " -> " + + uncheckedDefaultAnno); + } } - if (!atypeFactory.isSupportedQualifier(anno)) { - anno = atypeFactory.canonicalAnnotation(anno); + private void checkDuplicates( + DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { + if (conflictsWithExistingDefaults(previousDefaults, newAnno, newLoc)) { + throw new BugInCF( + "Only one qualifier from a hierarchy can be the default. Existing: " + + previousDefaults + + " and new: " + // TODO: expose applyToSubpackages + + new Default(newAnno, newLoc, true)); + } } - if (atypeFactory.isSupportedQualifier(anno)) { - TypeUseLocation[] locations = - AnnotationUtils.getElementValueEnumArray( - dq, - defaultQualifierLocationsElement, - TypeUseLocation.class, - defaultQualifierValueDefault); - boolean applyToSubpackages = - AnnotationUtils.getElementValue( - dq, defaultQualifierApplyToSubpackagesElement, Boolean.class, true); - - DefaultSet ret = new DefaultSet(); - for (TypeUseLocation loc : locations) { - ret.add(new Default(anno, loc, applyToSubpackages)); - } - return ret; - } else { - return null; + /** + * Returns true if there are conflicts with existing defaults. + * + * @param previousDefaults the previous defaults + * @param newAnno the new annotation + * @param newLoc the location of the type use + * @return true if there are conflicts with existing defaults + */ + private boolean conflictsWithExistingDefaults( + DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) { + QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy(); + + for (Default previous : previousDefaults) { + if (!AnnotationUtils.areSame(newAnno, previous.anno) && previous.location == newLoc) { + AnnotationMirror previousTop = qualHierarchy.getTopAnnotation(previous.anno); + if (qualHierarchy.isSubtypeQualifiersOnly(newAnno, previousTop)) { + return true; + } + } + } + return false; } - } - private boolean isElementAnnotatedForThisChecker(Element elt) { - boolean elementAnnotatedForThisChecker = false; + /** + * Applies default annotations to a type obtained from an {@link + * javax.lang.model.element.Element}. + * + * @param elt the element from which the type was obtained + * @param type the type to annotate + */ + public void annotate(Element elt, AnnotatedTypeMirror type) { + if (elt != null) { + switch (elt.getKind()) { + case FIELD: + case LOCAL_VARIABLE: + case PARAMETER: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + case ENUM_CONSTANT: + String varName = elt.getSimpleName().toString(); + ((GenericAnnotatedTypeFactory) atypeFactory) + .getDefaultForTypeAnnotator() + .defaultTypeFromName(type, varName); + break; + + case METHOD: + String methodName = elt.getSimpleName().toString(); + AnnotatedTypeMirror returnType = + ((AnnotatedExecutableType) type).getReturnType(); + ((GenericAnnotatedTypeFactory) atypeFactory) + .getDefaultForTypeAnnotator() + .defaultTypeFromName(returnType, methodName); + break; + + default: + break; + } + } - if (elt == null) { - throw new BugInCF("Call of QualifierDefaults.isElementAnnotatedForThisChecker with null"); + applyDefaultsElement(elt, type, false); } - if (elementAnnotatedFors.containsKey(elt)) { - return elementAnnotatedFors.get(elt); + /** + * Applies default annotations to a type given a {@link com.sun.source.tree.Tree}. + * + * @param tree the tree from which the type was obtained + * @param type the type to annotate + */ + public void annotate(Tree tree, AnnotatedTypeMirror type) { + applyDefaults(tree, type); } - AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class); + /** + * Determines the nearest enclosing element for a tree by climbing the tree toward the root and + * obtaining the element for the first declaration (variable, method, or class) that encloses + * the tree. Initializers of local variables are handled in a special way: within an initializer + * we look for the DefaultQualifier(s) annotation and keep track of the previously visited tree. + * TODO: explain the behavior better. + * + * @param tree the tree + * @return the nearest enclosing element for a tree + */ + private @Nullable Element nearestEnclosingExceptLocal(Tree tree) { + TreePath path = atypeFactory.getPath(tree); + if (path == null) { + return TreeUtils.elementFromTree(tree); + } - if (annotatedFor != null) { - elementAnnotatedForThisChecker = - atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor); - } + Tree prev = null; + + for (Tree t : path) { + switch (TreeUtils.getKindRecordAsClass(t)) { + case ANNOTATED_TYPE: + case ANNOTATION: + // If the tree is in an annotation, then there is no relevant scope. + return null; + case VARIABLE: + VariableTree vtree = (VariableTree) t; + ExpressionTree vtreeInit = vtree.getInitializer(); + @SuppressWarnings("interning:not.interned") // check cached value + boolean sameAsPrev = (vtreeInit != null && prev == vtreeInit); + if (sameAsPrev) { + Element elt = TreeUtils.elementFromDeclaration((VariableTree) t); + AnnotationMirror d = + atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); + AnnotationMirror ds = + atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); + + if (d == null && ds == null) { + break; + } + } + if (prev != null && prev.getKind() == Tree.Kind.MODIFIERS) { + // Annotations are modifiers. We do not want to apply the local variable + // default to annotations. Without this, test fenum/TestSwitch failed, + // because the default for an argument became incompatible with the declared + // type. + break; + } + return TreeUtils.elementFromDeclaration((VariableTree) t); + case METHOD: + return TreeUtils.elementFromDeclaration((MethodTree) t); + case CLASS: // Including RECORD + case ENUM: + case INTERFACE: + case ANNOTATION_TYPE: + return TreeUtils.elementFromDeclaration((ClassTree) t); + default: // Do nothing. + } + prev = t; + } - if (!elementAnnotatedForThisChecker) { - Element parent; - if (elt.getKind() == ElementKind.PACKAGE) { - // TODO: should AnnotatedFor apply to subpackages?? - // elt.getEnclosingElement() on a package is null; therefore, - // use the dedicated method. - parent = ElementUtils.parentPackage((PackageElement) elt, elements); - } else { - parent = elt.getEnclosingElement(); - } - - if (parent != null && isElementAnnotatedForThisChecker(parent)) { - elementAnnotatedForThisChecker = true; - } + return null; } - elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker); - - return elementAnnotatedForThisChecker; - } - - /** - * Returns the defaults that apply to the given Element, considering defaults from enclosing - * Elements. - * - * @param elt the element - * @return the defaults - */ - private DefaultSet defaultsAt(Element elt) { - if (elt == null) { - return DefaultSet.EMPTY; - } + /** + * Applies default annotations to a type. A {@link com.sun.source.tree.Tree} determines the + * appropriate scope for defaults. + * + *

          For instance, if the tree is associated with a declaration (e.g., it's the use of a field, + * or a method invocation), defaults in the scope of the declaration are used; if the + * tree is not associated with a declaration (e.g., a typecast), defaults in the scope of the + * tree are used. + * + * @param tree the tree associated with the type + * @param type the type to which defaults will be applied + * @see #applyDefaultsElement(javax.lang.model.element.Element, + * org.checkerframework.framework.type.AnnotatedTypeMirror,boolean) + */ + private void applyDefaults(Tree tree, AnnotatedTypeMirror type) { + // The location to take defaults from. + Element elt; + switch (tree.getKind()) { + case MEMBER_SELECT: + elt = TreeUtils.elementFromUse((MemberSelectTree) tree); + break; + + case IDENTIFIER: + elt = TreeUtils.elementFromUse((IdentifierTree) tree); + if (ElementUtils.isTypeDeclaration(elt)) { + // If the identifier is a type, then use the scope of the tree. + elt = nearestEnclosingExceptLocal(tree); + } + break; + + case METHOD_INVOCATION: + elt = TreeUtils.elementFromUse((MethodInvocationTree) tree); + break; + + // TODO cases for array access, etc. -- every expression tree + // (The above probably means that we should use defaults in the + // scope of the declaration of the array. Is that right? -MDE) + + default: + // If no associated symbol was found, use the tree's (lexical) scope. + elt = nearestEnclosingExceptLocal(tree); + // elt = nearestEnclosing(tree); + } + // System.out.println("applyDefaults on tree " + tree + + // " gives elt: " + elt + "(" + elt.getKind() + ")"); - if (elementDefaults.containsKey(elt)) { - return elementDefaults.get(elt); + applyDefaultsElement(elt, type, true); } - DefaultSet qualifiers = defaultsAtDirect(elt); - DefaultSet parentDefaults; + /** The default {@code value} element for a @DefaultQualifier annotation. */ + private static final TypeUseLocation[] defaultQualifierValueDefault = + new TypeUseLocation[] {org.checkerframework.framework.qual.TypeUseLocation.ALL}; + + /** + * Create a DefaultSet from a @DefaultQualifier annotation. + * + * @param dq a @DefaultQualifier annotation + * @return a DefaultSet corresponding to the @DefaultQualifier annotation + */ + private @Nullable DefaultSet fromDefaultQualifier(AnnotationMirror dq) { + @SuppressWarnings("unchecked") + Name cls = AnnotationUtils.getElementValueClassName(dq, defaultQualifierValueElement); + AnnotationMirror anno = AnnotationBuilder.fromName(elements, cls); - if (elt.getKind() == ElementKind.PACKAGE) { - Element parent = ElementUtils.parentPackage((PackageElement) elt, elements); - DefaultSet origParentDefaults = defaultsAt(parent); - parentDefaults = new DefaultSet(); - for (Default d : origParentDefaults) { - if (d.applyToSubpackages) { - parentDefaults.add(d); + if (anno == null) { + return null; } - } - } else { - Element parent = elt.getEnclosingElement(); - parentDefaults = defaultsAt(parent); - } - if (qualifiers == null || qualifiers.isEmpty()) { - qualifiers = parentDefaults; - } else { - // TODO(cpovirk): What should happen with conflicts? - qualifiers.addAll(parentDefaults); - } + if (!atypeFactory.isSupportedQualifier(anno)) { + anno = atypeFactory.canonicalAnnotation(anno); + } - /* TODO: it would seem more efficient to also cache null/empty as the result. - * However, doing so causes KeyFor tests to fail. - if (qualifiers == null) { - qualifiers = DefaultSet.EMPTY; - } - - elementDefaults.put(elt, qualifiers); - return qualifiers; - */ - if (qualifiers != null && !qualifiers.isEmpty()) { - elementDefaults.put(elt, qualifiers); - return qualifiers; - } else { - return DefaultSet.EMPTY; - } - } - - /** - * Returns the defaults that apply directly to the given Element, without considering enclosing - * Elements. - * - * @param elt the element - * @return the defaults - */ - private DefaultSet defaultsAtDirect(Element elt) { - DefaultSet qualifiers = null; - - // Handle DefaultQualifier - AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); - - if (dqAnno != null) { - Set p = fromDefaultQualifier(dqAnno); - - if (p != null) { - qualifiers = new DefaultSet(); - qualifiers.addAll(p); - } + if (atypeFactory.isSupportedQualifier(anno)) { + TypeUseLocation[] locations = + AnnotationUtils.getElementValueEnumArray( + dq, + defaultQualifierLocationsElement, + TypeUseLocation.class, + defaultQualifierValueDefault); + boolean applyToSubpackages = + AnnotationUtils.getElementValue( + dq, defaultQualifierApplyToSubpackagesElement, Boolean.class, true); + + DefaultSet ret = new DefaultSet(); + for (TypeUseLocation loc : locations) { + ret.add(new Default(anno, loc, applyToSubpackages)); + } + return ret; + } else { + return null; + } } - // Handle DefaultQualifier.List - AnnotationMirror dqListAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); - if (dqListAnno != null) { - if (qualifiers == null) { - qualifiers = new DefaultSet(); - } - List values = - AnnotationUtils.getElementValueArray( - dqListAnno, defaultQualifierListValueElement, AnnotationMirror.class); - for (AnnotationMirror dqlAnno : values) { - Set p = fromDefaultQualifier(dqlAnno); - if (p != null) { - // TODO(cpovirk): What should happen with conflicts? - qualifiers.addAll(p); + private boolean isElementAnnotatedForThisChecker(Element elt) { + boolean elementAnnotatedForThisChecker = false; + + if (elt == null) { + throw new BugInCF( + "Call of QualifierDefaults.isElementAnnotatedForThisChecker with null"); } - } - } - return qualifiers; - } - - /** - * Given an element, returns whether the conservative default should be applied for it. Handles - * elements from bytecode or source code. - * - * @param annotationScope the element that the conservative default might apply to - * @return whether the conservative default applies to the given element - */ - public boolean applyConservativeDefaults(Element annotationScope) { - if (annotationScope == null) { - return false; - } - if (uncheckedCodeDefaults.isEmpty()) { - return false; - } + if (elementAnnotatedFors.containsKey(elt)) { + return elementAnnotatedFors.get(elt); + } - // TODO: I would expect this: - // atypeFactory.isFromByteCode(annotationScope)) { - // to work instead of the - // isElementFromByteCode/declarationFromElement/isFromStubFile calls, - // but it doesn't work correctly and tests fail. - - boolean isFromStubFile = atypeFactory.isFromStubFile(annotationScope); - boolean isBytecode = - ElementUtils.isElementFromByteCode(annotationScope) - && atypeFactory.declarationFromElement(annotationScope) == null - && !isFromStubFile; - if (isBytecode) { - return useConservativeDefaultsBytecode && !isElementAnnotatedForThisChecker(annotationScope); - } else if (isFromStubFile) { - // TODO: Types in stub files not annotated for a particular checker should be - // treated as unchecked bytecode. For now, all types in stub files are treated as - // checked code. Eventually, @AnnotatedFor("checker") will be programmatically added - // to methods in stub files supplied via the @StubFiles annotation. Stub files will - // be treated like unchecked code except for methods in the scope of an @AnnotatedFor. - return false; - } else if (useConservativeDefaultsSource) { - return !isElementAnnotatedForThisChecker(annotationScope); - } - return false; - } - - /** - * Applies default annotations to a type. Conservative defaults are applied first as appropriate, - * followed by source code defaults. - * - *

          For a discussion on the rules for application of source code and conservative defaults, - * please see the linked manual sections. - * - * @param annotationScope the element representing the nearest enclosing default annotation scope - * for the type - * @param type the type to which defaults will be applied - * @param fromTree whether the element came from a tree - * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults and - * inference) - * @checker_framework.manual #annotating-libraries Annotating libraries - */ - private void applyDefaultsElement( - Element annotationScope, AnnotatedTypeMirror type, boolean fromTree) { - DefaultApplierElement applier = - createDefaultApplierElement(atypeFactory, annotationScope, type, fromTree); - - DefaultSet defaults = defaultsAt(annotationScope); - - // If there is a default for type variable uses, do not also apply checked/unchecked code - // defaults to type variables. Otherwise, the default in scope could decide not to annotate - // the type variable use, whereas the checked/unchecked code default could add an - // annotation. - // TODO: the checked/unchecked defaults should be added to `defaults` and then only one - // iteration through the defaults should be necessary. - boolean typeVarUseDef = false; - - for (Default def : defaults) { - applier.applyDefault(def); - typeVarUseDef |= (def.location == TypeUseLocation.TYPE_VARIABLE_USE); - } + AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class); - if (applyConservativeDefaults(annotationScope)) { - for (Default def : uncheckedCodeDefaults) { - if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { - applier.applyDefault(def); + if (annotatedFor != null) { + elementAnnotatedForThisChecker = + atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor); + } + + if (!elementAnnotatedForThisChecker) { + Element parent; + if (elt.getKind() == ElementKind.PACKAGE) { + // TODO: should AnnotatedFor apply to subpackages?? + // elt.getEnclosingElement() on a package is null; therefore, + // use the dedicated method. + parent = ElementUtils.parentPackage((PackageElement) elt, elements); + } else { + parent = elt.getEnclosingElement(); + } + + if (parent != null && isElementAnnotatedForThisChecker(parent)) { + elementAnnotatedForThisChecker = true; + } } - } - } - for (Default def : checkedCodeDefaults) { - if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { - applier.applyDefault(def); - } + elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker); + + return elementAnnotatedForThisChecker; } - } - - /** - * Create the default applier element. - * - * @param atypeFactory the annotated type factory - * @param annotationScope the scope of the default - * @param type the type to which to apply the default - * @param fromTree whether the element came from a tree - * @return the default applier element - */ - protected DefaultApplierElement createDefaultApplierElement( - AnnotatedTypeFactory atypeFactory, - Element annotationScope, - AnnotatedTypeMirror type, - boolean fromTree) { - return new DefaultApplierElement(atypeFactory, annotationScope, type, fromTree); - } - - /** A default applier element. */ - protected class DefaultApplierElement { - - /** The annotated type factory. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** The qualifier hierarchy. */ - protected final QualifierHierarchy qualHierarchy; - - /** The scope of the default. */ - protected final Element scope; - - /** The type to which to apply the default. */ - protected final AnnotatedTypeMirror type; - - /** Whether the element came from a tree. */ - protected final boolean fromTree; /** - * True if type variable uses as top-level type of local variables should be defaulted. + * Returns the defaults that apply to the given Element, considering defaults from enclosing + * Elements. * - * @see GenericAnnotatedTypeFactory#getShouldDefaultTypeVarLocals() + * @param elt the element + * @return the defaults */ - private final boolean shouldDefaultTypeVarLocals; + private DefaultSet defaultsAt(Element elt) { + if (elt == null) { + return DefaultSet.EMPTY; + } + + if (elementDefaults.containsKey(elt)) { + return elementDefaults.get(elt); + } + + DefaultSet qualifiers = defaultsAtDirect(elt); + DefaultSet parentDefaults; + + if (elt.getKind() == ElementKind.PACKAGE) { + Element parent = ElementUtils.parentPackage((PackageElement) elt, elements); + DefaultSet origParentDefaults = defaultsAt(parent); + parentDefaults = new DefaultSet(); + for (Default d : origParentDefaults) { + if (d.applyToSubpackages) { + parentDefaults.add(d); + } + } + } else { + Element parent = elt.getEnclosingElement(); + parentDefaults = defaultsAt(parent); + } - /** Location to which to apply the default. (Should only be set by the applyDefault method.) */ - protected TypeUseLocation location; + if (qualifiers == null || qualifiers.isEmpty()) { + qualifiers = parentDefaults; + } else { + // TODO(cpovirk): What should happen with conflicts? + qualifiers.addAll(parentDefaults); + } - /** The default element applier implementation. */ - protected final DefaultApplierElementImpl impl; + /* TODO: it would seem more efficient to also cache null/empty as the result. + * However, doing so causes KeyFor tests to fail. + if (qualifiers == null) { + qualifiers = DefaultSet.EMPTY; + } + + elementDefaults.put(elt, qualifiers); + return qualifiers; + */ + if (qualifiers != null && !qualifiers.isEmpty()) { + elementDefaults.put(elt, qualifiers); + return qualifiers; + } else { + return DefaultSet.EMPTY; + } + } /** - * Create an instance. + * Returns the defaults that apply directly to the given Element, without considering enclosing + * Elements. * - * @param atypeFactory the type factory - * @param scope the scope for the defaults - * @param type the type to default - * @param fromTree whether the element came from a tree + * @param elt the element + * @return the defaults */ - public DefaultApplierElement( - AnnotatedTypeFactory atypeFactory, - Element scope, - AnnotatedTypeMirror type, - boolean fromTree) { - this.atypeFactory = atypeFactory; - this.qualHierarchy = atypeFactory.getQualifierHierarchy(); - this.scope = scope; - this.type = type; - this.fromTree = fromTree; - this.shouldDefaultTypeVarLocals = - (atypeFactory instanceof GenericAnnotatedTypeFactory) - && ((GenericAnnotatedTypeFactory) atypeFactory) - .getShouldDefaultTypeVarLocals(); - this.impl = new DefaultApplierElementImpl(this); + private DefaultSet defaultsAtDirect(Element elt) { + DefaultSet qualifiers = null; + + // Handle DefaultQualifier + AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); + + if (dqAnno != null) { + Set p = fromDefaultQualifier(dqAnno); + + if (p != null) { + qualifiers = new DefaultSet(); + qualifiers.addAll(p); + } + } + + // Handle DefaultQualifier.List + AnnotationMirror dqListAnno = + atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class); + if (dqListAnno != null) { + if (qualifiers == null) { + qualifiers = new DefaultSet(); + } + List values = + AnnotationUtils.getElementValueArray( + dqListAnno, defaultQualifierListValueElement, AnnotationMirror.class); + for (AnnotationMirror dqlAnno : values) { + Set p = fromDefaultQualifier(dqlAnno); + if (p != null) { + // TODO(cpovirk): What should happen with conflicts? + qualifiers.addAll(p); + } + } + } + return qualifiers; } /** - * Apply default to the type. + * Given an element, returns whether the conservative default should be applied for it. Handles + * elements from bytecode or source code. * - * @param def default to apply + * @param annotationScope the element that the conservative default might apply to + * @return whether the conservative default applies to the given element */ - public void applyDefault(Default def) { - this.location = def.location; - impl.visit(type, def.anno); + public boolean applyConservativeDefaults(Element annotationScope) { + if (annotationScope == null) { + return false; + } + + if (uncheckedCodeDefaults.isEmpty()) { + return false; + } + + // TODO: I would expect this: + // atypeFactory.isFromByteCode(annotationScope)) { + // to work instead of the + // isElementFromByteCode/declarationFromElement/isFromStubFile calls, + // but it doesn't work correctly and tests fail. + + boolean isFromStubFile = atypeFactory.isFromStubFile(annotationScope); + boolean isBytecode = + ElementUtils.isElementFromByteCode(annotationScope) + && atypeFactory.declarationFromElement(annotationScope) == null + && !isFromStubFile; + if (isBytecode) { + return useConservativeDefaultsBytecode + && !isElementAnnotatedForThisChecker(annotationScope); + } else if (isFromStubFile) { + // TODO: Types in stub files not annotated for a particular checker should be + // treated as unchecked bytecode. For now, all types in stub files are treated as + // checked code. Eventually, @AnnotatedFor("checker") will be programmatically added + // to methods in stub files supplied via the @StubFiles annotation. Stub files will + // be treated like unchecked code except for methods in the scope of an @AnnotatedFor. + return false; + } else if (useConservativeDefaultsSource) { + return !isElementAnnotatedForThisChecker(annotationScope); + } + return false; } /** - * Returns true if the given qualifier should be applied to the given type. Currently we do not - * apply defaults to void types, packages, wildcards, and type variables. + * Applies default annotations to a type. Conservative defaults are applied first as + * appropriate, followed by source code defaults. * - * @param type type to which qual would be applied - * @return true if this application should proceed + *

          For a discussion on the rules for application of source code and conservative defaults, + * please see the linked manual sections. + * + * @param annotationScope the element representing the nearest enclosing default annotation + * scope for the type + * @param type the type to which defaults will be applied + * @param fromTree whether the element came from a tree + * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults + * and inference) + * @checker_framework.manual #annotating-libraries Annotating libraries */ - protected boolean shouldBeAnnotated(AnnotatedTypeMirror type) { - return type != null - // TODO: executables themselves should not be annotated - // For some reason h1h2checker-tests fails with this. - // || type.getKind() == TypeKind.EXECUTABLE - && type.getKind() != TypeKind.NONE - && type.getKind() != TypeKind.WILDCARD - && type.getKind() != TypeKind.TYPEVAR - && !(type instanceof AnnotatedNoType); + private void applyDefaultsElement( + Element annotationScope, AnnotatedTypeMirror type, boolean fromTree) { + DefaultApplierElement applier = + createDefaultApplierElement(atypeFactory, annotationScope, type, fromTree); + + DefaultSet defaults = defaultsAt(annotationScope); + + // If there is a default for type variable uses, do not also apply checked/unchecked code + // defaults to type variables. Otherwise, the default in scope could decide not to annotate + // the type variable use, whereas the checked/unchecked code default could add an + // annotation. + // TODO: the checked/unchecked defaults should be added to `defaults` and then only one + // iteration through the defaults should be necessary. + boolean typeVarUseDef = false; + + for (Default def : defaults) { + applier.applyDefault(def); + typeVarUseDef |= (def.location == TypeUseLocation.TYPE_VARIABLE_USE); + } + + if (applyConservativeDefaults(annotationScope)) { + for (Default def : uncheckedCodeDefaults) { + if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { + applier.applyDefault(def); + } + } + } + + for (Default def : checkedCodeDefaults) { + if (!typeVarUseDef || def.location != TypeUseLocation.TYPE_VARIABLE_USE) { + applier.applyDefault(def); + } + } } /** - * Add the qualifier to the type if it does not already have an annotation in the same hierarchy - * as qual. + * Create the default applier element. * - * @param type type to add qual - * @param qual annotation to add + * @param atypeFactory the annotated type factory + * @param annotationScope the scope of the default + * @param type the type to which to apply the default + * @param fromTree whether the element came from a tree + * @return the default applier element */ - protected void addAnnotation(AnnotatedTypeMirror type, AnnotationMirror qual) { - // Add the default annotation, but only if no other annotation is present. - if (type.getKind() != TypeKind.EXECUTABLE) { - type.addMissingAnnotation(qual); - } + protected DefaultApplierElement createDefaultApplierElement( + AnnotatedTypeFactory atypeFactory, + Element annotationScope, + AnnotatedTypeMirror type, + boolean fromTree) { + return new DefaultApplierElement(atypeFactory, annotationScope, type, fromTree); } - } - // Only reason this cannot be `static` is call to `getBoundType`. - protected class DefaultApplierElementImpl extends AnnotatedTypeScanner { - private final DefaultApplierElement outer; + /** A default applier element. */ + protected class DefaultApplierElement { + + /** The annotated type factory. */ + protected final AnnotatedTypeFactory atypeFactory; + + /** The qualifier hierarchy. */ + protected final QualifierHierarchy qualHierarchy; + + /** The scope of the default. */ + protected final Element scope; + + /** The type to which to apply the default. */ + protected final AnnotatedTypeMirror type; + + /** Whether the element came from a tree. */ + protected final boolean fromTree; + + /** + * True if type variable uses as top-level type of local variables should be defaulted. + * + * @see GenericAnnotatedTypeFactory#getShouldDefaultTypeVarLocals() + */ + private final boolean shouldDefaultTypeVarLocals; + + /** + * Location to which to apply the default. (Should only be set by the applyDefault method.) + */ + protected TypeUseLocation location; + + /** The default element applier implementation. */ + protected final DefaultApplierElementImpl impl; + + /** + * Create an instance. + * + * @param atypeFactory the type factory + * @param scope the scope for the defaults + * @param type the type to default + * @param fromTree whether the element came from a tree + */ + public DefaultApplierElement( + AnnotatedTypeFactory atypeFactory, + Element scope, + AnnotatedTypeMirror type, + boolean fromTree) { + this.atypeFactory = atypeFactory; + this.qualHierarchy = atypeFactory.getQualifierHierarchy(); + this.scope = scope; + this.type = type; + this.fromTree = fromTree; + this.shouldDefaultTypeVarLocals = + (atypeFactory instanceof GenericAnnotatedTypeFactory) + && ((GenericAnnotatedTypeFactory) atypeFactory) + .getShouldDefaultTypeVarLocals(); + this.impl = new DefaultApplierElementImpl(this); + } - protected DefaultApplierElementImpl(DefaultApplierElement outer) { - this.outer = outer; - } + /** + * Apply default to the type. + * + * @param def default to apply + */ + public void applyDefault(Default def) { + this.location = def.location; + impl.visit(type, def.anno); + } - @Override - public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { - if (!outer.shouldBeAnnotated(t)) { - // Type variables and wildcards are separately handled in the corresponding visitors - // below. - return super.scan(t, qual); - } - - // Some defaults only apply to the top level type. - boolean isTopLevelType = t == outer.type; - switch (outer.location) { - case FIELD: - if (outer.scope != null && outer.scope.getKind() == ElementKind.FIELD && isTopLevelType) { - outer.addAnnotation(t, qual); - } - break; - case LOCAL_VARIABLE: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.LOCAL_VARIABLE - && isTopLevelType) { - // TODO: how do we determine that we are in a cast or instanceof type? - outer.addAnnotation(t, qual); - } - break; - case RESOURCE_VARIABLE: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.RESOURCE_VARIABLE - && isTopLevelType) { - outer.addAnnotation(t, qual); - } - break; - case EXCEPTION_PARAMETER: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.EXCEPTION_PARAMETER - && isTopLevelType) { - outer.addAnnotation(t, qual); - if (t.getKind() == TypeKind.UNION) { - AnnotatedUnionType aut = (AnnotatedUnionType) t; - // Also apply the default to the alternative types - for (AnnotatedDeclaredType anno : aut.getAlternatives()) { - outer.addAnnotation(anno, qual); - } - } - } - break; - case PARAMETER: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.PARAMETER - && isTopLevelType) { - outer.addAnnotation(t, qual); - } else if (outer.scope != null - && (outer.scope.getKind() == ElementKind.METHOD - || outer.scope.getKind() == ElementKind.CONSTRUCTOR) - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - for (AnnotatedTypeMirror atm : ((AnnotatedExecutableType) t).getParameterTypes()) { - if (outer.shouldBeAnnotated(atm)) { - outer.addAnnotation(atm, qual); - } - } - } - break; - case RECEIVER: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.PARAMETER - && isTopLevelType - && outer.scope.getSimpleName().contentEquals("this")) { - // TODO: comparison against "this" is ugly, won't work - // for all possible names for receiver parameter. - // Comparison to Names._this might be a bit faster. - outer.addAnnotation(t, qual); - } else if (outer.scope != null - && (outer.scope.getKind() == ElementKind.METHOD) - // TODO: Constructors can also have receivers. - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - AnnotatedDeclaredType receiver = ((AnnotatedExecutableType) t).getReceiverType(); - if (outer.shouldBeAnnotated(receiver)) { - outer.addAnnotation(receiver, qual); + /** + * Returns true if the given qualifier should be applied to the given type. Currently we do + * not apply defaults to void types, packages, wildcards, and type variables. + * + * @param type type to which qual would be applied + * @return true if this application should proceed + */ + protected boolean shouldBeAnnotated(AnnotatedTypeMirror type) { + return type != null + // TODO: executables themselves should not be annotated + // For some reason h1h2checker-tests fails with this. + // || type.getKind() == TypeKind.EXECUTABLE + && type.getKind() != TypeKind.NONE + && type.getKind() != TypeKind.WILDCARD + && type.getKind() != TypeKind.TYPEVAR + && !(type instanceof AnnotatedNoType); + } + + /** + * Add the qualifier to the type if it does not already have an annotation in the same + * hierarchy as qual. + * + * @param type type to add qual + * @param qual annotation to add + */ + protected void addAnnotation(AnnotatedTypeMirror type, AnnotationMirror qual) { + // Add the default annotation, but only if no other annotation is present. + if (type.getKind() != TypeKind.EXECUTABLE) { + type.addMissingAnnotation(qual); } - } - break; - case RETURN: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.METHOD - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType(); - if (outer.shouldBeAnnotated(returnType)) { - outer.addAnnotation(returnType, qual); + } + } + + // Only reason this cannot be `static` is call to `getBoundType`. + protected class DefaultApplierElementImpl extends AnnotatedTypeScanner { + private final DefaultApplierElement outer; + + protected DefaultApplierElementImpl(DefaultApplierElement outer) { + this.outer = outer; + } + + @Override + public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { + if (!outer.shouldBeAnnotated(t)) { + // Type variables and wildcards are separately handled in the corresponding visitors + // below. + return super.scan(t, qual); } - } - break; - case CONSTRUCTOR_RESULT: - if (outer.scope != null - && outer.scope.getKind() == ElementKind.CONSTRUCTOR - && t.getKind() == TypeKind.EXECUTABLE - && isTopLevelType) { - // This is the return type of a constructor declaration (not a - // constructor invocation). - AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType(); - if (outer.shouldBeAnnotated(returnType)) { - outer.addAnnotation(returnType, qual); + + // Some defaults only apply to the top level type. + boolean isTopLevelType = t == outer.type; + switch (outer.location) { + case FIELD: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.FIELD + && isTopLevelType) { + outer.addAnnotation(t, qual); + } + break; + case LOCAL_VARIABLE: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.LOCAL_VARIABLE + && isTopLevelType) { + // TODO: how do we determine that we are in a cast or instanceof type? + outer.addAnnotation(t, qual); + } + break; + case RESOURCE_VARIABLE: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.RESOURCE_VARIABLE + && isTopLevelType) { + outer.addAnnotation(t, qual); + } + break; + case EXCEPTION_PARAMETER: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.EXCEPTION_PARAMETER + && isTopLevelType) { + outer.addAnnotation(t, qual); + if (t.getKind() == TypeKind.UNION) { + AnnotatedUnionType aut = (AnnotatedUnionType) t; + // Also apply the default to the alternative types + for (AnnotatedDeclaredType anno : aut.getAlternatives()) { + outer.addAnnotation(anno, qual); + } + } + } + break; + case PARAMETER: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.PARAMETER + && isTopLevelType) { + outer.addAnnotation(t, qual); + } else if (outer.scope != null + && (outer.scope.getKind() == ElementKind.METHOD + || outer.scope.getKind() == ElementKind.CONSTRUCTOR) + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + for (AnnotatedTypeMirror atm : + ((AnnotatedExecutableType) t).getParameterTypes()) { + if (outer.shouldBeAnnotated(atm)) { + outer.addAnnotation(atm, qual); + } + } + } + break; + case RECEIVER: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.PARAMETER + && isTopLevelType + && outer.scope.getSimpleName().contentEquals("this")) { + // TODO: comparison against "this" is ugly, won't work + // for all possible names for receiver parameter. + // Comparison to Names._this might be a bit faster. + outer.addAnnotation(t, qual); + } else if (outer.scope != null + && (outer.scope.getKind() == ElementKind.METHOD) + // TODO: Constructors can also have receivers. + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + AnnotatedDeclaredType receiver = + ((AnnotatedExecutableType) t).getReceiverType(); + if (outer.shouldBeAnnotated(receiver)) { + outer.addAnnotation(receiver, qual); + } + } + break; + case RETURN: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.METHOD + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + AnnotatedTypeMirror returnType = + ((AnnotatedExecutableType) t).getReturnType(); + if (outer.shouldBeAnnotated(returnType)) { + outer.addAnnotation(returnType, qual); + } + } + break; + case CONSTRUCTOR_RESULT: + if (outer.scope != null + && outer.scope.getKind() == ElementKind.CONSTRUCTOR + && t.getKind() == TypeKind.EXECUTABLE + && isTopLevelType) { + // This is the return type of a constructor declaration (not a + // constructor invocation). + AnnotatedTypeMirror returnType = + ((AnnotatedExecutableType) t).getReturnType(); + if (outer.shouldBeAnnotated(returnType)) { + outer.addAnnotation(returnType, qual); + } + } + break; + case IMPLICIT_LOWER_BOUND: + if (isLowerBound + && (boundType == BoundType.TYPEVAR_UNBOUNDED + || boundType == BoundType.TYPEVAR_UPPER + || boundType == BoundType.WILDCARD_UNBOUNDED + || boundType == BoundType.WILDCARD_UPPER)) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_LOWER_BOUND: + if (isLowerBound && boundType == BoundType.WILDCARD_LOWER) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case LOWER_BOUND: + if (isLowerBound) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_UPPER_BOUND: + if (isUpperBound + && (boundType == BoundType.TYPEVAR_UNBOUNDED + || boundType == BoundType.WILDCARD_UNBOUNDED + || boundType == BoundType.WILDCARD_LOWER)) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_TYPE_PARAMETER_UPPER_BOUND: + if (isUpperBound && boundType == BoundType.TYPEVAR_UNBOUNDED) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER: + if (isUpperBound && boundType == BoundType.WILDCARD_UNBOUNDED) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_WILDCARD_UPPER_BOUND_SUPER: + if (isUpperBound && boundType == BoundType.WILDCARD_LOWER) { + outer.addAnnotation(t, qual); + } + break; + case IMPLICIT_WILDCARD_UPPER_BOUND: + if (isUpperBound + && (boundType == BoundType.WILDCARD_UNBOUNDED + || boundType == BoundType.WILDCARD_LOWER)) { + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_UPPER_BOUND: + if (isUpperBound + && (boundType == BoundType.TYPEVAR_UPPER + || boundType == BoundType.WILDCARD_UPPER)) { + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_TYPE_PARAMETER_UPPER_BOUND: + if (isUpperBound && boundType == BoundType.TYPEVAR_UPPER) { + outer.addAnnotation(t, qual); + } + break; + case EXPLICIT_WILDCARD_UPPER_BOUND: + if (isUpperBound && boundType == BoundType.WILDCARD_UPPER) { + outer.addAnnotation(t, qual); + } + break; + case UPPER_BOUND: + if (isUpperBound) { + // TODO: split type variables and wildcards? + outer.addAnnotation(t, qual); + } + break; + case OTHERWISE: + case ALL: + // TODO: forbid ALL if anything else was given. + outer.addAnnotation(t, qual); + break; + case TYPE_VARIABLE_USE: + // This location is handled in visitTypeVariable below. Do nothing here. + break; + default: + throw new BugInCF( + "QualifierDefaults.DefaultApplierElement: unhandled location: " + + outer.location); } - } - break; - case IMPLICIT_LOWER_BOUND: - if (isLowerBound - && (boundType == BoundType.TYPEVAR_UNBOUNDED - || boundType == BoundType.TYPEVAR_UPPER - || boundType == BoundType.WILDCARD_UNBOUNDED - || boundType == BoundType.WILDCARD_UPPER)) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_LOWER_BOUND: - if (isLowerBound && boundType == BoundType.WILDCARD_LOWER) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case LOWER_BOUND: - if (isLowerBound) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_UPPER_BOUND: - if (isUpperBound - && (boundType == BoundType.TYPEVAR_UNBOUNDED - || boundType == BoundType.WILDCARD_UNBOUNDED - || boundType == BoundType.WILDCARD_LOWER)) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_TYPE_PARAMETER_UPPER_BOUND: - if (isUpperBound && boundType == BoundType.TYPEVAR_UNBOUNDED) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER: - if (isUpperBound && boundType == BoundType.WILDCARD_UNBOUNDED) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_WILDCARD_UPPER_BOUND_SUPER: - if (isUpperBound && boundType == BoundType.WILDCARD_LOWER) { - outer.addAnnotation(t, qual); - } - break; - case IMPLICIT_WILDCARD_UPPER_BOUND: - if (isUpperBound - && (boundType == BoundType.WILDCARD_UNBOUNDED - || boundType == BoundType.WILDCARD_LOWER)) { - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_UPPER_BOUND: - if (isUpperBound - && (boundType == BoundType.TYPEVAR_UPPER || boundType == BoundType.WILDCARD_UPPER)) { - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_TYPE_PARAMETER_UPPER_BOUND: - if (isUpperBound && boundType == BoundType.TYPEVAR_UPPER) { - outer.addAnnotation(t, qual); - } - break; - case EXPLICIT_WILDCARD_UPPER_BOUND: - if (isUpperBound && boundType == BoundType.WILDCARD_UPPER) { - outer.addAnnotation(t, qual); - } - break; - case UPPER_BOUND: - if (isUpperBound) { - // TODO: split type variables and wildcards? - outer.addAnnotation(t, qual); - } - break; - case OTHERWISE: - case ALL: - // TODO: forbid ALL if anything else was given. - outer.addAnnotation(t, qual); - break; - case TYPE_VARIABLE_USE: - // This location is handled in visitTypeVariable below. Do nothing here. - break; - default: - throw new BugInCF( - "QualifierDefaults.DefaultApplierElement: unhandled location: " + outer.location); - } - - return super.scan(t, qual); - } - @Override - public void reset() { - super.reset(); - isLowerBound = false; - isUpperBound = false; - boundType = BoundType.TYPEVAR_UNBOUNDED; - } + return super.scan(t, qual); + } - /** Are we currently defaulting the lower bound of a type variable or wildcard? */ - private boolean isLowerBound = false; + @Override + public void reset() { + super.reset(); + isLowerBound = false; + isUpperBound = false; + boundType = BoundType.TYPEVAR_UNBOUNDED; + } - /** Are we currently defaulting the upper bound of a type variable or wildcard? */ - private boolean isUpperBound = false; + /** Are we currently defaulting the lower bound of a type variable or wildcard? */ + private boolean isLowerBound = false; - /** The bound type of the current wildcard or type variable being defaulted. */ - private BoundType boundType = BoundType.TYPEVAR_UNBOUNDED; + /** Are we currently defaulting the upper bound of a type variable or wildcard? */ + private boolean isUpperBound = false; - @Override - public Void visitTypeVariable(@FindDistinct AnnotatedTypeVariable type, AnnotationMirror qual) { - if (visitedNodes.containsKey(type)) { - return null; - } - if (outer.qualHierarchy.isParametricQualifier(qual)) { - // Parametric qualifiers are only applicable to type variables and have no effect on - // their type. Therefore, do nothing. - return null; - } - if (type.isDeclaration()) { - // For a type variable declaration, apply the defaults to the bounds. Do not apply - // `TYPE_VARIALBE_USE` defaults. - visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); - return null; - } + /** The bound type of the current wildcard or type variable being defaulted. */ + private BoundType boundType = BoundType.TYPEVAR_UNBOUNDED; - boolean isTopLevelType = type == outer.type; - boolean isLocalVariable = outer.scope != null && ElementUtils.isLocalVariable(outer.scope); + @Override + public Void visitTypeVariable( + @FindDistinct AnnotatedTypeVariable type, AnnotationMirror qual) { + if (visitedNodes.containsKey(type)) { + return null; + } + if (outer.qualHierarchy.isParametricQualifier(qual)) { + // Parametric qualifiers are only applicable to type variables and have no effect on + // their type. Therefore, do nothing. + return null; + } + if (type.isDeclaration()) { + // For a type variable declaration, apply the defaults to the bounds. Do not apply + // `TYPE_VARIALBE_USE` defaults. + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + return null; + } - if (isTopLevelType && isLocalVariable) { - if (outer.shouldDefaultTypeVarLocals - && outer.fromTree - && outer.location == TypeUseLocation.LOCAL_VARIABLE) { - outer.addAnnotation(type, qual); - } else { - // TODO: Should `TYPE_VARIABLE_USE` default apply to top-level local variables, - // if they should not be defaulted according to `shouldDefaultTypeVarLocals`? - visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + boolean isTopLevelType = type == outer.type; + boolean isLocalVariable = + outer.scope != null && ElementUtils.isLocalVariable(outer.scope); + + if (isTopLevelType && isLocalVariable) { + if (outer.shouldDefaultTypeVarLocals + && outer.fromTree + && outer.location == TypeUseLocation.LOCAL_VARIABLE) { + outer.addAnnotation(type, qual); + } else { + // TODO: Should `TYPE_VARIABLE_USE` default apply to top-level local variables, + // if they should not be defaulted according to `shouldDefaultTypeVarLocals`? + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + } + } else { + if (outer.location == TypeUseLocation.TYPE_VARIABLE_USE) { + outer.addAnnotation(type, qual); + } else { + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + } + } + return null; } - } else { - if (outer.location == TypeUseLocation.TYPE_VARIABLE_USE) { - outer.addAnnotation(type, qual); - } else { - visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + + @Override + public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror qual) { + if (visitedNodes.containsKey(type)) { + return null; + } + visitBounds(type, type.getExtendsBound(), type.getSuperBound(), qual); + return null; } - } - return null; - } - @Override - public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror qual) { - if (visitedNodes.containsKey(type)) { - return null; - } - visitBounds(type, type.getExtendsBound(), type.getSuperBound(), qual); - return null; + /** + * Visit the bounds of a type variable or a wildcard and potentially apply qual to those + * bounds. This method will also update the boundType, isLowerBound, and isUpperbound + * fields. + */ + protected void visitBounds( + AnnotatedTypeMirror boundedType, + AnnotatedTypeMirror upperBound, + AnnotatedTypeMirror lowerBound, + AnnotationMirror qual) { + boolean prevIsUpperBound = isUpperBound; + boolean prevIsLowerBound = isLowerBound; + BoundType prevBoundType = boundType; + + boundType = getBoundType(boundedType); + + try { + isLowerBound = true; + isUpperBound = false; + scanAndReduce(lowerBound, qual, null); + + visitedNodes.put(boundedType, null); + + isLowerBound = false; + isUpperBound = true; + scanAndReduce(upperBound, qual, null); + + visitedNodes.put(boundedType, null); + } finally { + isUpperBound = prevIsUpperBound; + isLowerBound = prevIsLowerBound; + boundType = prevBoundType; + } + } } /** - * Visit the bounds of a type variable or a wildcard and potentially apply qual to those bounds. - * This method will also update the boundType, isLowerBound, and isUpperbound fields. + * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an + * explicit lower bound (LOWER), or no explicit bounds (UNBOUNDED). */ - protected void visitBounds( - AnnotatedTypeMirror boundedType, - AnnotatedTypeMirror upperBound, - AnnotatedTypeMirror lowerBound, - AnnotationMirror qual) { - boolean prevIsUpperBound = isUpperBound; - boolean prevIsLowerBound = isLowerBound; - BoundType prevBoundType = boundType; - - boundType = getBoundType(boundedType); - - try { - isLowerBound = true; - isUpperBound = false; - scanAndReduce(lowerBound, qual, null); - - visitedNodes.put(boundedType, null); - - isLowerBound = false; - isUpperBound = true; - scanAndReduce(upperBound, qual, null); - - visitedNodes.put(boundedType, null); - } finally { - isUpperBound = prevIsUpperBound; - isLowerBound = prevIsLowerBound; - boundType = prevBoundType; - } - } - } + protected enum BoundType { + + /** Indicates an upper-bounded type variable. */ + TYPEVAR_UPPER, + + /** + * Neither bound is specified, BOTH are implicit. (If a type variable is declared in + * bytecode and the type of the upper bound is Object, then the checker assumes that the + * bound was not explicitly written in source code.) + */ + TYPEVAR_UNBOUNDED, - /** - * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an - * explicit lower bound (LOWER), or no explicit bounds (UNBOUNDED). - */ - protected enum BoundType { + /** Indicates an upper-bounded wildcard. */ + WILDCARD_UPPER, - /** Indicates an upper-bounded type variable. */ - TYPEVAR_UPPER, + /** Indicates a lower-bounded wildcard. */ + WILDCARD_LOWER, + + /** Neither bound is specified, BOTH are implicit. */ + WILDCARD_UNBOUNDED; + } /** - * Neither bound is specified, BOTH are implicit. (If a type variable is declared in bytecode - * and the type of the upper bound is Object, then the checker assumes that the bound was not - * explicitly written in source code.) + * Returns the boundType for type. + * + * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or + * AnnotatedTypeVariable. + * @return the boundType for type */ - TYPEVAR_UNBOUNDED, - - /** Indicates an upper-bounded wildcard. */ - WILDCARD_UPPER, - - /** Indicates a lower-bounded wildcard. */ - WILDCARD_LOWER, - - /** Neither bound is specified, BOTH are implicit. */ - WILDCARD_UNBOUNDED; - } - - /** - * Returns the boundType for type. - * - * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or - * AnnotatedTypeVariable. - * @return the boundType for type - */ - private BoundType getBoundType(AnnotatedTypeMirror type) { - if (type instanceof AnnotatedTypeVariable) { - return getTypeVarBoundType((AnnotatedTypeVariable) type); - } + private BoundType getBoundType(AnnotatedTypeMirror type) { + if (type instanceof AnnotatedTypeVariable) { + return getTypeVarBoundType((AnnotatedTypeVariable) type); + } + + if (type instanceof AnnotatedWildcardType) { + return getWildcardBoundType((AnnotatedWildcardType) type); + } - if (type instanceof AnnotatedWildcardType) { - return getWildcardBoundType((AnnotatedWildcardType) type); + throw new BugInCF("Unexpected type kind: type=" + type); } - throw new BugInCF("Unexpected type kind: type=" + type); - } - - /** - * Returns the bound type of the input typeVar. - * - * @param typeVar the type variable - * @return the bound type of the input typeVar - */ - private BoundType getTypeVarBoundType(AnnotatedTypeVariable typeVar) { - return getTypeVarBoundType((TypeParameterElement) typeVar.getUnderlyingType().asElement()); - } - - /** - * Returns the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of typeParamElem. - * - * @param typeParamElem the type parameter element - * @return the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of typeParamElem - */ - // Results are cached in {@link elementToBoundType}. - private BoundType getTypeVarBoundType(TypeParameterElement typeParamElem) { - BoundType prev = elementToBoundType.get(typeParamElem); - if (prev != null) { - return prev; + /** + * Returns the bound type of the input typeVar. + * + * @param typeVar the type variable + * @return the bound type of the input typeVar + */ + private BoundType getTypeVarBoundType(AnnotatedTypeVariable typeVar) { + return getTypeVarBoundType((TypeParameterElement) typeVar.getUnderlyingType().asElement()); } - TreePath declaredTypeVarEle = atypeFactory.getTreeUtils().getPath(typeParamElem); - Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf(); - - final BoundType boundType; - if (typeParamDecl == null) { - // This is not only for elements from binaries, but also - // when the compilation unit is no-longer available. - if (typeParamElem.getBounds().size() == 1 - && TypesUtils.isObject(typeParamElem.getBounds().get(0))) { - // If the bound was Object, then it may or may not have been explicitly written. - // Assume that it was not. - boundType = BoundType.TYPEVAR_UNBOUNDED; - } else { - // The bound is not Object, so it must have been explicitly written and thus the - // type variable has an upper bound. - boundType = BoundType.TYPEVAR_UPPER; - } - } else { - if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) { - TypeParameterTree tptree = (TypeParameterTree) typeParamDecl; - - List bnds = tptree.getBounds(); - if (bnds != null && !bnds.isEmpty()) { - boundType = BoundType.TYPEVAR_UPPER; + /** + * Returns the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of + * typeParamElem. + * + * @param typeParamElem the type parameter element + * @return the boundType (TYPEVAR_UPPER or TYPEVAR_UNBOUNDED) of the declaration of + * typeParamElem + */ + // Results are cached in {@link elementToBoundType}. + private BoundType getTypeVarBoundType(TypeParameterElement typeParamElem) { + BoundType prev = elementToBoundType.get(typeParamElem); + if (prev != null) { + return prev; + } + + TreePath declaredTypeVarEle = atypeFactory.getTreeUtils().getPath(typeParamElem); + Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf(); + + final BoundType boundType; + if (typeParamDecl == null) { + // This is not only for elements from binaries, but also + // when the compilation unit is no-longer available. + if (typeParamElem.getBounds().size() == 1 + && TypesUtils.isObject(typeParamElem.getBounds().get(0))) { + // If the bound was Object, then it may or may not have been explicitly written. + // Assume that it was not. + boundType = BoundType.TYPEVAR_UNBOUNDED; + } else { + // The bound is not Object, so it must have been explicitly written and thus the + // type variable has an upper bound. + boundType = BoundType.TYPEVAR_UPPER; + } } else { - boundType = BoundType.TYPEVAR_UNBOUNDED; + if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) { + TypeParameterTree tptree = (TypeParameterTree) typeParamDecl; + + List bnds = tptree.getBounds(); + if (bnds != null && !bnds.isEmpty()) { + boundType = BoundType.TYPEVAR_UPPER; + } else { + boundType = BoundType.TYPEVAR_UNBOUNDED; + } + } else { + throw new BugInCF( + StringsPlume.joinLines( + "Unexpected tree type for typeVar Element:", + "typeParamElem=" + typeParamElem, + typeParamDecl)); + } } - } else { - throw new BugInCF( - StringsPlume.joinLines( - "Unexpected tree type for typeVar Element:", - "typeParamElem=" + typeParamElem, - typeParamDecl)); - } + + elementToBoundType.put(typeParamElem, boundType); + return boundType; } - elementToBoundType.put(typeParamElem, boundType); - return boundType; - } - - /** - * Returns the BoundType of wildcardType. - * - * @param wildcardType the annotated wildcard type - * @return the BoundType of annotatedWildcard - */ - private BoundType getWildcardBoundType(AnnotatedWildcardType wildcardType) { - if (AnnotatedTypes.hasNoExplicitBound(wildcardType)) { - return BoundType.WILDCARD_UNBOUNDED; - } else if (AnnotatedTypes.hasExplicitSuperBound(wildcardType)) { - return BoundType.WILDCARD_LOWER; - } else { - return BoundType.WILDCARD_UPPER; + /** + * Returns the BoundType of wildcardType. + * + * @param wildcardType the annotated wildcard type + * @return the BoundType of annotatedWildcard + */ + private BoundType getWildcardBoundType(AnnotatedWildcardType wildcardType) { + if (AnnotatedTypes.hasNoExplicitBound(wildcardType)) { + return BoundType.WILDCARD_UNBOUNDED; + } else if (AnnotatedTypes.hasExplicitSuperBound(wildcardType)) { + return BoundType.WILDCARD_LOWER; + } else { + return BoundType.WILDCARD_UPPER; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java index 528f4189b2e..3a48107b3df 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java @@ -1,14 +1,15 @@ package org.checkerframework.framework.util.dependenttypes; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.checkerframework.checker.formatter.qual.ConversionCategory; import org.checkerframework.checker.formatter.qual.Format; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; import org.checkerframework.javacutil.BugInCF; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Helper class for creating dependent type annotation error strings. * @@ -18,116 +19,117 @@ */ public class DependentTypesError { - /// Static fields - - /** How elements of this class are formatted. */ - @SuppressWarnings("InlineFormatString") // https://github.com/google/error-prone/issues/1650 - private static final String FORMAT_STRING = "[error for expression: %s; error: %s]"; - - /** Regular expression for unparsing string representations of this class (gross). */ - private static final Pattern ERROR_PATTERN = - Pattern.compile("\\[error for expression: (.*); error: ([\\s\\S]*)\\]"); - - /** - * Returns whether or not the given expression string is an error. That is, whether it is a string - * that was generated by this class. - * - * @param expression expression string to test - * @return whether or not the given expressions string is an error - */ - public static boolean isExpressionError(String expression) { - return expression.startsWith("[error"); - } - - /** How to format warnings about use of formal parameter name. */ - public static final @Format({ConversionCategory.INT, ConversionCategory.GENERAL}) String - FORMAL_PARAM_NAME_STRING = "Use \"#%d\" rather than \"%s\""; - - /** Matches warnings about use of formal parameter name. */ - private static final Pattern FORMAL_PARAM_NAME_PATTERN = - Pattern.compile("^'([a-zA-Z_$][a-zA-Z0-9_$]*)' because (Use \"#\\d+\" rather than \"\\1\")$"); - - /// Instance fields - - /** The expression that is unparsable or otherwise problematic. */ - public final String expression; - - /** An error message about that expression. */ - public final String error; - - /// Constructors and methods - - /** - * Create a DependentTypesError for the given expression and error message. - * - * @param expression the incorrect Java expression - * @param error an error message about the expression - */ - public DependentTypesError(String expression, String error) { - this.expression = expression; - this.error = error; - } - - /** - * Create a DependentTypesError for the given expression and exception. - * - * @param expression the incorrect Java expression - * @param e wraps an error message about the expression - */ - public DependentTypesError(String expression, JavaExpressionParseException e) { - this.expression = expression; - this.error = e.getDiagMessage().getArgs()[0].toString(); - } - - /** - * Create a DependentTypesError by parsing a printed one. - * - * @param formattedError the toString() representation of a DependentTypesError - */ - public static DependentTypesError unparse(String formattedError) { - Matcher matcher = ERROR_PATTERN.matcher(formattedError); - if (matcher.matches()) { - assert matcher.groupCount() == 2; - return new DependentTypesError(matcher.group(1), matcher.group(2)); - } else { - throw new BugInCF("Cannot unparse: " + formattedError); + /// Static fields + + /** How elements of this class are formatted. */ + @SuppressWarnings("InlineFormatString") // https://github.com/google/error-prone/issues/1650 + private static final String FORMAT_STRING = "[error for expression: %s; error: %s]"; + + /** Regular expression for unparsing string representations of this class (gross). */ + private static final Pattern ERROR_PATTERN = + Pattern.compile("\\[error for expression: (.*); error: ([\\s\\S]*)\\]"); + + /** + * Returns whether or not the given expression string is an error. That is, whether it is a + * string that was generated by this class. + * + * @param expression expression string to test + * @return whether or not the given expressions string is an error + */ + public static boolean isExpressionError(String expression) { + return expression.startsWith("[error"); + } + + /** How to format warnings about use of formal parameter name. */ + public static final @Format({ConversionCategory.INT, ConversionCategory.GENERAL}) String + FORMAL_PARAM_NAME_STRING = "Use \"#%d\" rather than \"%s\""; + + /** Matches warnings about use of formal parameter name. */ + private static final Pattern FORMAL_PARAM_NAME_PATTERN = + Pattern.compile( + "^'([a-zA-Z_$][a-zA-Z0-9_$]*)' because (Use \"#\\d+\" rather than \"\\1\")$"); + + /// Instance fields + + /** The expression that is unparsable or otherwise problematic. */ + public final String expression; + + /** An error message about that expression. */ + public final String error; + + /// Constructors and methods + + /** + * Create a DependentTypesError for the given expression and error message. + * + * @param expression the incorrect Java expression + * @param error an error message about the expression + */ + public DependentTypesError(String expression, String error) { + this.expression = expression; + this.error = error; + } + + /** + * Create a DependentTypesError for the given expression and exception. + * + * @param expression the incorrect Java expression + * @param e wraps an error message about the expression + */ + public DependentTypesError(String expression, JavaExpressionParseException e) { + this.expression = expression; + this.error = e.getDiagMessage().getArgs()[0].toString(); + } + + /** + * Create a DependentTypesError by parsing a printed one. + * + * @param formattedError the toString() representation of a DependentTypesError + */ + public static DependentTypesError unparse(String formattedError) { + Matcher matcher = ERROR_PATTERN.matcher(formattedError); + if (matcher.matches()) { + assert matcher.groupCount() == 2; + return new DependentTypesError(matcher.group(1), matcher.group(2)); + } else { + throw new BugInCF("Cannot unparse: " + formattedError); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DependentTypesError that = (DependentTypesError) o; + + return expression.equals(that.expression) && error.equals(that.error); } - } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; + @Override + public int hashCode() { + return Objects.hash(expression, error); } - if (o == null || getClass() != o.getClass()) { - return false; + + @Override + public String toString() { + return String.format(FORMAT_STRING, expression, error); } - DependentTypesError that = (DependentTypesError) o; - - return expression.equals(that.expression) && error.equals(that.error); - } - - @Override - public int hashCode() { - return Objects.hash(expression, error); - } - - @Override - public String toString() { - return String.format(FORMAT_STRING, expression, error); - } - - /** - * Like toString, but uses better formatting sometimes. Use this only for the final output, - * because of the design that hides error messages in toString(). - */ - @SuppressWarnings("nullness:return") // regex groups always match text - public String format() { - Matcher m = FORMAL_PARAM_NAME_PATTERN.matcher(error); - if (m.matches()) { - return m.group(2); + /** + * Like toString, but uses better formatting sometimes. Use this only for the final output, + * because of the design that hides error messages in toString(). + */ + @SuppressWarnings("nullness:return") // regex groups always match text + public String format() { + Matcher m = FORMAL_PARAM_NAME_PATTERN.matcher(error); + if (m.matches()) { + return m.group(2); + } + return toString(); } - return toString(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java index 42d8b989727..45037b5844d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java @@ -13,25 +13,7 @@ import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.JCTree; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.expression.FormalParameter; @@ -63,6 +45,27 @@ import org.checkerframework.javacutil.trees.DetachedVarSymbol; import org.plumelib.util.CollectionsPlume; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + /** * A class that helps checkers use qualifiers that are represented by annotations with Java * expression strings. This class performs the following main functions: @@ -107,1222 +110,1257 @@ */ public class DependentTypesHelper { - /** AnnotatedTypeFactory */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Maps from an annotation name, the fully-qualified name of its class, to its elements that are - * Java expressions. - */ - private final Map> annoToElements; - - /** This scans an annotated type and returns a list of {@link DependentTypesError}. */ - private final ExpressionErrorCollector expressionErrorCollector = new ExpressionErrorCollector(); - - /** - * A scanner that applies a function to each {@link AnnotationMirror} and replaces it in the given - * {@code AnnotatedTypeMirror}. (This side-effects the {@code AnnotatedTypeMirror}.) - */ - private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); - - /** - * Copies annotations that might have been viewpoint adapted from the visited type (the first - * formal parameter of {@code ViewpointAdaptedCopier#visit}) to the second formal parameter. - */ - private final ViewpointAdaptedCopier viewpointAdaptedCopier = new ViewpointAdaptedCopier(); - - /** The type mirror for java.lang.Object. */ - protected final TypeMirror objectTM; - - /** - * Creates a {@code DependentTypesHelper}. - * - * @param atypeFactory annotated type factory - */ - public DependentTypesHelper(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - - this.annoToElements = new HashMap<>(); - for (Class expressionAnno : atypeFactory.getSupportedTypeQualifiers()) { - List elementList = - getExpressionElements(expressionAnno, atypeFactory.getProcessingEnv()); - if (!elementList.isEmpty()) { - annoToElements.put(expressionAnno.getCanonicalName(), elementList); - } - } + /** AnnotatedTypeFactory */ + protected final AnnotatedTypeFactory atypeFactory; - this.objectTM = - TypesUtils.typeFromClass(Object.class, atypeFactory.types, atypeFactory.getElementUtils()); - } - - /** - * Returns true if any qualifier in the type system is a dependent type annotation. - * - * @return true if any qualifier in the type system is a dependent type annotation - */ - public boolean hasDependentAnnotations() { - return !annoToElements.isEmpty(); - } - - /** - * Returns a list of the elements in the annotation class that should be interpreted as Java - * expressions, namely those annotated with {@code @}{@link JavaExpression}. - * - * @param clazz annotation class - * @param env processing environment for getting the ExecutableElement - * @return a list of the elements in the annotation class that should be interpreted as Java - * expressions - */ - private static List getExpressionElements( - Class clazz, ProcessingEnvironment env) { - Method[] methods = clazz.getMethods(); - if (methods == null) { - return Collections.emptyList(); - } - List elements = new ArrayList<>(); - for (Method method : methods) { - org.checkerframework.framework.qual.JavaExpression javaExpressionAnno = - method.getAnnotation(org.checkerframework.framework.qual.JavaExpression.class); - if (javaExpressionAnno != null) { - elements.add(TreeUtils.getMethod(clazz, method.getName(), method.getParameterCount(), env)); - } - } - return elements; - } - - /** - * Returns the elements of the annotation that are Java expressions. - * - * @param am an annotation - * @return the elements of the annotation that are Java expressions - */ - private List getListOfExpressionElements(AnnotationMirror am) { - return annoToElements.getOrDefault(AnnotationUtils.annotationName(am), Collections.emptyList()); - } - - /** - * Creates a TreeAnnotator that viewpoint-adapts dependent type annotations. - * - * @return a new TreeAnnotator that viewpoint-adapts dependent type annotations - */ - public TreeAnnotator createDependentTypesTreeAnnotator() { - assert hasDependentAnnotations(); - return new DependentTypesTreeAnnotator(atypeFactory, this); - } - - /// - /// Methods that convert annotations - /// - - /** If true, log information about where lambdas are created. */ - // This variable is only set here; edit the source code to modify it. - private static final boolean debugStringToJavaExpression = false; - - /** - * Viewpoint-adapts the dependent type annotations on the bounds of the type parameters of the - * declaration of {@code typeUse} to {@code typeUse}. - * - * @param bounds annotated types of the bounds of the type parameters; its elements are - * side-effected by this method (but the list itself is not side-effected) - * @param typeUse a use of a type with type parameter bounds {@code bounds} - */ - public void atParameterizedTypeUse( - List bounds, TypeElement typeUse) { - if (!hasDependentAnnotations()) { - return; - } + /** + * Maps from an annotation name, the fully-qualified name of its class, to its elements that are + * Java expressions. + */ + private final Map> annoToElements; - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atTypeDecl(stringExpr, typeUse, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atParameterizedTypeUse(%s, %s) created %s%n", bounds, typeUse, stringToJavaExpr); - } - for (AnnotatedTypeParameterBounds bound : bounds) { - convertAnnotatedTypeMirror(stringToJavaExpr, bound.getUpperBound()); - convertAnnotatedTypeMirror(stringToJavaExpr, bound.getLowerBound()); - } - } - - /** - * Viewpoint-adapts the dependent type annotations in the methodType to the methodInvocationTree. - * - *

          {@code methodType} has been viewpoint-adapted to the call site, except for any dependent - * type annotations. This method viewpoint-adapts the dependent type annotations. - * - * @param methodType type of the method invocation; is side-effected by this method - * @param methodInvocationTree use of the method - */ - public void atMethodInvocation( - AnnotatedExecutableType methodType, MethodInvocationTree methodInvocationTree) { - if (!hasDependentAnnotations()) { - return; - } - atInvocation(methodType, methodInvocationTree); - } - - /** - * Viewpoint-adapts the dependent type annotations in the constructorType to the newClassTree. - * - *

          {@code constructorType} has been viewpoint-adapted to the call site, except for any - * dependent type annotations. This method viewpoint-adapts the dependent type annotations. - * - * @param constructorType type of the constructor invocation; is side-effected by this method - * @param newClassTree invocation of the constructor - */ - public void atConstructorInvocation( - AnnotatedExecutableType constructorType, NewClassTree newClassTree) { - if (!hasDependentAnnotations()) { - return; - } - atInvocation(constructorType, newClassTree); - } - - /** - * Viewpoint-adapts dependent type annotations in a method or constructor type. - * - *

          {@code methodType} has been viewpoint-adapted to the call site, except for any dependent - * type annotations. (For example, type variables have been substituted and polymorphic qualifiers - * have been resolved.) This method viewpoint-adapts the dependent type annotations. - * - * @param methodType type of the method or constructor invocation; is side-effected by this method - * @param tree invocation of the method or constructor - */ - private void atInvocation(AnnotatedExecutableType methodType, ExpressionTree tree) { - assert hasDependentAnnotations(); - Element methodElt = TreeUtils.elementFromUse(tree); - // Because methodType is the type post type variable substitution, it has annotations from - // both the method declaration and the type arguments at the use of the method. Annotations - // from type arguments must not be viewpoint-adapted to the call site. For example: - // Map map = ...; - // List<@KeyFor("this.map") String> list = ...; - // list.get(0) - // - // methodType is @KeyFor("this.map") String get(int) - // "this.map" must not be viewpoint-adapted to the invocation because it is not from - // the method declaration, but added during type variable substitution. - // - // So this implementation gets the declared type of the method, declaredMethodType, - // viewpoint-adapts all dependent type annotations in declaredMethodType to the call site, - // and then copies the viewpoint-adapted annotations from methodType except for types that - // are replaced by type variable substitution. (Those annotations are viewpoint-adapted - // before type variable substitution.) - - // The annotations on `declaredMethodType` will be copied to `methodType`. - AnnotatedExecutableType declaredMethodType = - (AnnotatedExecutableType) atypeFactory.getAnnotatedType(methodElt); - if (!hasDependentType(declaredMethodType)) { - return; - } + /** This scans an annotated type and returns a list of {@link DependentTypesError}. */ + private final ExpressionErrorCollector expressionErrorCollector = + new ExpressionErrorCollector(); - StringToJavaExpression stringToJavaExpr; - if (tree instanceof MethodInvocationTree) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodInvocation( - stringExpr, (MethodInvocationTree) tree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atInvocation(%s, %s) 1 created %s%n", - methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); - } - } else if (tree instanceof NewClassTree) { - stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atConstructorInvocation( - stringExpr, (NewClassTree) tree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atInvocation(%s, %s) 2 created %s%n", - methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); - } - } else { - throw new BugInCF("Unexpected tree: %s kind: %s", tree, tree.getKind()); - } - convertAnnotatedTypeMirror(stringToJavaExpr, declaredMethodType); - this.viewpointAdaptedCopier.visit(declaredMethodType, methodType); - } - - /** - * Viewpoint-adapts the Java expressions in annotations written on a field declaration to the use - * at {@code fieldAccess}. - * - * @param type its type; is side-effected by this method - * @param fieldAccess a field access - */ - public void atFieldAccess(AnnotatedTypeMirror type, MemberSelectTree fieldAccess) { - if (!hasDependentType(type)) { - return; - } + /** + * A scanner that applies a function to each {@link AnnotationMirror} and replaces it in the + * given {@code AnnotatedTypeMirror}. (This side-effects the {@code AnnotatedTypeMirror}.) + */ + private final AnnotatedTypeReplacer annotatedTypeReplacer = new AnnotatedTypeReplacer(); + + /** + * Copies annotations that might have been viewpoint adapted from the visited type (the first + * formal parameter of {@code ViewpointAdaptedCopier#visit}) to the second formal parameter. + */ + private final ViewpointAdaptedCopier viewpointAdaptedCopier = new ViewpointAdaptedCopier(); + + /** The type mirror for java.lang.Object. */ + protected final TypeMirror objectTM; + + /** + * Creates a {@code DependentTypesHelper}. + * + * @param atypeFactory annotated type factory + */ + public DependentTypesHelper(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + + this.annoToElements = new HashMap<>(); + for (Class expressionAnno : + atypeFactory.getSupportedTypeQualifiers()) { + List elementList = + getExpressionElements(expressionAnno, atypeFactory.getProcessingEnv()); + if (!elementList.isEmpty()) { + annoToElements.put(expressionAnno.getCanonicalName(), elementList); + } + } - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atFieldAccess( - stringExpr, fieldAccess, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atFieldAccess(%s, %s) created %s%n", - type, TreeUtils.toStringTruncated(fieldAccess, 65), stringToJavaExpr); + this.objectTM = + TypesUtils.typeFromClass( + Object.class, atypeFactory.types, atypeFactory.getElementUtils()); } - convertAnnotatedTypeMirror(stringToJavaExpr, type); - } - - /** - * Viewpoint-adapts the Java expressions in annotations written on the signature of the method - * declaration (for example, a return type) to the body of the method. This means the parameter - * syntax, e.g. "#2", is converted to the names of the parameter. - * - * @param atm a type at the method signature; is side-effected by this method - * @param methodDeclTree a method declaration - */ - public void atMethodBody(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { - if (!hasDependentType(atm)) { - return; + + /** + * Returns true if any qualifier in the type system is a dependent type annotation. + * + * @return true if any qualifier in the type system is a dependent type annotation + */ + public boolean hasDependentAnnotations() { + return !annoToElements.isEmpty(); } - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody( - stringExpr, methodDeclTree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atMethodBody(%s, %s) 1 created %s%n", - atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); + /** + * Returns a list of the elements in the annotation class that should be interpreted as Java + * expressions, namely those annotated with {@code @}{@link JavaExpression}. + * + * @param clazz annotation class + * @param env processing environment for getting the ExecutableElement + * @return a list of the elements in the annotation class that should be interpreted as Java + * expressions + */ + private static List getExpressionElements( + Class clazz, ProcessingEnvironment env) { + Method[] methods = clazz.getMethods(); + if (methods == null) { + return Collections.emptyList(); + } + List elements = new ArrayList<>(); + for (Method method : methods) { + org.checkerframework.framework.qual.JavaExpression javaExpressionAnno = + method.getAnnotation(org.checkerframework.framework.qual.JavaExpression.class); + if (javaExpressionAnno != null) { + elements.add( + TreeUtils.getMethod( + clazz, method.getName(), method.getParameterCount(), env)); + } + } + return elements; } - convertAnnotatedTypeMirror(stringToJavaExpr, atm); - } - - /** - * Standardizes the Java expressions in annotations to a type declaration. - * - * @param type the type of the type declaration; is side-effected by this method - * @param typeElt the element of the type declaration - */ - public void atTypeDecl(AnnotatedTypeMirror type, TypeElement typeElt) { - if (!hasDependentType(type)) { - return; + + /** + * Returns the elements of the annotation that are Java expressions. + * + * @param am an annotation + * @return the elements of the annotation that are Java expressions + */ + private List getListOfExpressionElements(AnnotationMirror am) { + return annoToElements.getOrDefault( + AnnotationUtils.annotationName(am), Collections.emptyList()); } - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atTypeDecl(stringExpr, typeElt, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf("atTypeDecl(%s, %s) created %s%n", type, typeElt, stringToJavaExpr); + /** + * Creates a TreeAnnotator that viewpoint-adapts dependent type annotations. + * + * @return a new TreeAnnotator that viewpoint-adapts dependent type annotations + */ + public TreeAnnotator createDependentTypesTreeAnnotator() { + assert hasDependentAnnotations(); + return new DependentTypesTreeAnnotator(atypeFactory, this); } - convertAnnotatedTypeMirror(stringToJavaExpr, type); - } - - /** A set containing {@link Tree.Kind#METHOD} and {@link Tree.Kind#LAMBDA_EXPRESSION}. */ - private static final Set METHOD_OR_LAMBDA = - EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION); - - /** - * Standardize the Java expressions in annotations in a variable declaration. Converts the - * parameter syntax, e.g "#1", to the parameter name. - * - * @param type the type of the variable declaration; is side-effected by this method - * @param declarationTree the variable declaration - * @param variableElt the element of the variable declaration - */ - public void atVariableDeclaration( - AnnotatedTypeMirror type, Tree declarationTree, VariableElement variableElt) { - if (!hasDependentType(type)) { - return; + + /// + /// Methods that convert annotations + /// + + /** If true, log information about where lambdas are created. */ + // This variable is only set here; edit the source code to modify it. + private static final boolean debugStringToJavaExpression = false; + + /** + * Viewpoint-adapts the dependent type annotations on the bounds of the type parameters of the + * declaration of {@code typeUse} to {@code typeUse}. + * + * @param bounds annotated types of the bounds of the type parameters; its elements are + * side-effected by this method (but the list itself is not side-effected) + * @param typeUse a use of a type with type parameter bounds {@code bounds} + */ + public void atParameterizedTypeUse( + List bounds, TypeElement typeUse) { + if (!hasDependentAnnotations()) { + return; + } + + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atTypeDecl( + stringExpr, typeUse, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atParameterizedTypeUse(%s, %s) created %s%n", + bounds, typeUse, stringToJavaExpr); + } + for (AnnotatedTypeParameterBounds bound : bounds) { + convertAnnotatedTypeMirror(stringToJavaExpr, bound.getUpperBound()); + convertAnnotatedTypeMirror(stringToJavaExpr, bound.getLowerBound()); + } } - TreePath pathToVariableDecl = atypeFactory.getPath(declarationTree); - assert pathToVariableDecl != null; - - if (variableElt instanceof DetachedVarSymbol) { - // Skip standardizing the Java expression in annotations of artificial variables, - // because artificial variables are created and initialized with standardized Java - // expressions. - // For example, for the following code fragment from - // Nullness test case checker/tests/nullness-asserts/NonNullMapValue.java - // - // ... - // static HashMap call_hashmap = new HashMap<>(); - // - // public foo() { - // for (Integer i : call_hashmap.keySet()) { - // @NonNull Date d = call_hashmap.get(i); - // } - // } - // ... - // - // - // the CFGBuilder creates the initialized artificial iterator as follows: - // iter#num0 = NonNullMapValue.call_hashmap.keySet().iterator() - // Therefore, for the Map Key Checker, the type of the variable "i" is - // @KeyFor({"NonNullMapValue.call_hashmap"}), the string in which is already - // standardized. - return; + /** + * Viewpoint-adapts the dependent type annotations in the methodType to the + * methodInvocationTree. + * + *

          {@code methodType} has been viewpoint-adapted to the call site, except for any dependent + * type annotations. This method viewpoint-adapts the dependent type annotations. + * + * @param methodType type of the method invocation; is side-effected by this method + * @param methodInvocationTree use of the method + */ + public void atMethodInvocation( + AnnotatedExecutableType methodType, MethodInvocationTree methodInvocationTree) { + if (!hasDependentAnnotations()) { + return; + } + atInvocation(methodType, methodInvocationTree); } - ElementKind variableKind = variableElt.getKind(); - if (ElementUtils.isBindingVariable(variableElt)) { - // Treat binding variables the same as local variables. - variableKind = ElementKind.LOCAL_VARIABLE; + + /** + * Viewpoint-adapts the dependent type annotations in the constructorType to the newClassTree. + * + *

          {@code constructorType} has been viewpoint-adapted to the call site, except for any + * dependent type annotations. This method viewpoint-adapts the dependent type annotations. + * + * @param constructorType type of the constructor invocation; is side-effected by this method + * @param newClassTree invocation of the constructor + */ + public void atConstructorInvocation( + AnnotatedExecutableType constructorType, NewClassTree newClassTree) { + if (!hasDependentAnnotations()) { + return; + } + atInvocation(constructorType, newClassTree); } - switch (variableKind) { - case PARAMETER: - TreePath pathTillEnclTree = - TreePathUtil.pathTillOfKind(pathToVariableDecl, METHOD_OR_LAMBDA); - if (pathTillEnclTree == null) { - throw new BugInCF("no enclosing method or lambda found for " + variableElt); + + /** + * Viewpoint-adapts dependent type annotations in a method or constructor type. + * + *

          {@code methodType} has been viewpoint-adapted to the call site, except for any dependent + * type annotations. (For example, type variables have been substituted and polymorphic + * qualifiers have been resolved.) This method viewpoint-adapts the dependent type annotations. + * + * @param methodType type of the method or constructor invocation; is side-effected by this + * method + * @param tree invocation of the method or constructor + */ + private void atInvocation(AnnotatedExecutableType methodType, ExpressionTree tree) { + assert hasDependentAnnotations(); + Element methodElt = TreeUtils.elementFromUse(tree); + // Because methodType is the type post type variable substitution, it has annotations from + // both the method declaration and the type arguments at the use of the method. Annotations + // from type arguments must not be viewpoint-adapted to the call site. For example: + // Map map = ...; + // List<@KeyFor("this.map") String> list = ...; + // list.get(0) + // + // methodType is @KeyFor("this.map") String get(int) + // "this.map" must not be viewpoint-adapted to the invocation because it is not from + // the method declaration, but added during type variable substitution. + // + // So this implementation gets the declared type of the method, declaredMethodType, + // viewpoint-adapts all dependent type annotations in declaredMethodType to the call site, + // and then copies the viewpoint-adapted annotations from methodType except for types that + // are replaced by type variable substitution. (Those annotations are viewpoint-adapted + // before type variable substitution.) + + // The annotations on `declaredMethodType` will be copied to `methodType`. + AnnotatedExecutableType declaredMethodType = + (AnnotatedExecutableType) atypeFactory.getAnnotatedType(methodElt); + if (!hasDependentType(declaredMethodType)) { + return; } - Tree enclTree = pathTillEnclTree.getLeaf(); - - if (enclTree.getKind() == Tree.Kind.METHOD) { - MethodTree methodDeclTree = (MethodTree) enclTree; - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody( - stringExpr, methodDeclTree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 1 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, type); + + StringToJavaExpression stringToJavaExpr; + if (tree instanceof MethodInvocationTree) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodInvocation( + stringExpr, + (MethodInvocationTree) tree, + atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atInvocation(%s, %s) 1 created %s%n", + methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); + } + } else if (tree instanceof NewClassTree) { + stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atConstructorInvocation( + stringExpr, (NewClassTree) tree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atInvocation(%s, %s) 2 created %s%n", + methodType, TreeUtils.toStringTruncated(tree, 65), stringToJavaExpr); + } } else { - // Lambdas can use local variables defined in the enclosing method, so allow - // identifiers to be locals in scope at the location of the lambda. - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atLambdaParameter( - stringExpr, - (LambdaExpressionTree) enclTree, - pathToVariableDecl.getParentPath(), - atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 2 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, type); + throw new BugInCF("Unexpected tree: %s kind: %s", tree, tree.getKind()); } - break; - - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - StringToJavaExpression stringToJavaExprVar = - stringExpr -> - StringToJavaExpression.atPath( - stringExpr, pathToVariableDecl, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 3 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExprVar); + convertAnnotatedTypeMirror(stringToJavaExpr, declaredMethodType); + this.viewpointAdaptedCopier.visit(declaredMethodType, methodType); + } + + /** + * Viewpoint-adapts the Java expressions in annotations written on a field declaration to the + * use at {@code fieldAccess}. + * + * @param type its type; is side-effected by this method + * @param fieldAccess a field access + */ + public void atFieldAccess(AnnotatedTypeMirror type, MemberSelectTree fieldAccess) { + if (!hasDependentType(type)) { + return; } - convertAnnotatedTypeMirror(stringToJavaExprVar, type); - break; - - case FIELD: - case ENUM_CONSTANT: - StringToJavaExpression stringToJavaExprField = - stringExpr -> - StringToJavaExpression.atFieldDecl( - stringExpr, variableElt, atypeFactory.getChecker()); + + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atFieldAccess( + stringExpr, fieldAccess, atypeFactory.getChecker()); if (debugStringToJavaExpression) { - System.out.printf( - "atVariableDeclaration(%s, %s, %s) 4 created %s%n", - type, - TreeUtils.toStringTruncated(declarationTree, 65), - variableElt, - stringToJavaExprField); + System.out.printf( + "atFieldAccess(%s, %s) created %s%n", + type, TreeUtils.toStringTruncated(fieldAccess, 65), stringToJavaExpr); } - convertAnnotatedTypeMirror(stringToJavaExprField, type); - break; - - default: - throw new BugInCF( - "unexpected element kind " + variableElt.getKind() + " for " + variableElt); - } - } - - /** - * Standardize the Java expressions in annotations in written in the {@code expressionTree}. Also, - * converts the parameter syntax, e.g. "#1", to the parameter name. - * - *

          {@code expressionTree} must be an expressions which can contain explicitly written - * annotations, namely a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or {@link - * com.sun.source.tree.TypeCastTree}. For example, this method standardizes the {@code KeyFor} - * annotation in {@code (@KeyFor("map") String) key }. - * - * @param annotatedType its type; is side-effected by this method - * @param expressionTree a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or - * {@link com.sun.source.tree.TypeCastTree} - */ - public void atExpression(AnnotatedTypeMirror annotatedType, ExpressionTree expressionTree) { - if (!hasDependentType(annotatedType)) { - return; + convertAnnotatedTypeMirror(stringToJavaExpr, type); } - TreePath path = atypeFactory.getPath(expressionTree); - if (path == null) { - return; - } - StringToJavaExpression stringToJavaExpr = - stringExpr -> StringToJavaExpression.atPath(stringExpr, path, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "atExpression(%s, %s) created %s%n", - annotatedType, TreeUtils.toStringTruncated(expressionTree, 65), stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, annotatedType); - } - - /** - * Standardize the Java expressions in annotations in a type. Converts the parameter syntax, e.g. - * "#2", to the parameter name. - * - * @param type the type to standardize; is side-effected by this method - * @param elt the element whose type is {@code type} - */ - public void atLocalVariable(AnnotatedTypeMirror type, Element elt) { - if (!hasDependentType(type)) { - return; + /** + * Viewpoint-adapts the Java expressions in annotations written on the signature of the method + * declaration (for example, a return type) to the body of the method. This means the parameter + * syntax, e.g. "#2", is converted to the names of the parameter. + * + * @param atm a type at the method signature; is side-effected by this method + * @param methodDeclTree a method declaration + */ + public void atMethodBody(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { + if (!hasDependentType(atm)) { + return; + } + + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody( + stringExpr, methodDeclTree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atMethodBody(%s, %s) 1 created %s%n", + atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, atm); } - switch (elt.getKind()) { - case PARAMETER: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - Tree declarationTree = atypeFactory.declarationFromElement(elt); - if (declarationTree == null) { - if (elt.getKind() == ElementKind.PARAMETER) { - // The tree might be null when - // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the - // assignment context for a pseudo assignment of an argument to a method - // parameter. + /** + * Standardizes the Java expressions in annotations to a type declaration. + * + * @param type the type of the type declaration; is side-effected by this method + * @param typeElt the element of the type declaration + */ + public void atTypeDecl(AnnotatedTypeMirror type, TypeElement typeElt) { + if (!hasDependentType(type)) { return; - } - throw new BugInCF(this.getClass() + ": tree not found"); - } else if (TreeUtils.typeOf(declarationTree) == null) { - // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() - // gets the assignment context for a pseudo assignment of an argument to a - // method parameter. - return; } - atVariableDeclaration(type, declarationTree, (VariableElement) elt); - return; - - default: - // It's not a local variable (it might be METHOD, CONSTRUCTOR, CLASS, or INTERFACE, - // for example), so there is nothing to do. - break; - } - } - - /** Thrown when a non-parameter local variable is found. */ - @SuppressWarnings("serial") - private static class FoundLocalException extends RuntimeException {} - - /** - * Viewpoint-adapt all dependent type annotations to the method declaration, {@code - * methodDeclTree}. This method changes occurrences of formal parameter names to the "#2" syntax, - * and it removes expressions that contain other local variables. - * - *

          If a Java expression in {@code atm} references local variables (other than formal - * parameters), the expression is removed from the annotation. This could result in dependent type - * annotations with empty lists of expressions. If this is a problem, a subclass can override - * {@link #buildAnnotation(AnnotationMirror, Map)} to do something besides creating an annotation - * with a empty list. - * - * @param atm type to viewpoint-adapt; is side-effected by this method - * @param methodDeclTree the method declaration to which the annotations are viewpoint-adapted - */ - public void delocalize(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { - if (!hasDependentType(atm)) { - return; + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atTypeDecl( + stringExpr, typeElt, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf("atTypeDecl(%s, %s) created %s%n", type, typeElt, stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, type); } - TreePath pathToMethodDecl = atypeFactory.getPath(methodDeclTree); - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodDeclTree); - List parameters = JavaExpression.getFormalParameters(methodElement); - List paramsAsLocals = - JavaExpression.getParametersAsLocalVariables(methodElement); - - StringToJavaExpression stringToJavaExpr = - expression -> { - JavaExpression javaExpr; - try { - javaExpr = - StringToJavaExpression.atPath( - expression, pathToMethodDecl, atypeFactory.getChecker()); - } catch (JavaExpressionParseException ex) { - return null; - } - JavaExpressionConverter jec = - new JavaExpressionConverter() { - @Override - protected JavaExpression visitLocalVariable( - LocalVariable localVarExpr, Void unused) { - int index = paramsAsLocals.indexOf(localVarExpr); - if (index == -1) { - throw new FoundLocalException(); - } - return parameters.get(index); + /** A set containing {@link Tree.Kind#METHOD} and {@link Tree.Kind#LAMBDA_EXPRESSION}. */ + private static final Set METHOD_OR_LAMBDA = + EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION); + + /** + * Standardize the Java expressions in annotations in a variable declaration. Converts the + * parameter syntax, e.g "#1", to the parameter name. + * + * @param type the type of the variable declaration; is side-effected by this method + * @param declarationTree the variable declaration + * @param variableElt the element of the variable declaration + */ + public void atVariableDeclaration( + AnnotatedTypeMirror type, Tree declarationTree, VariableElement variableElt) { + if (!hasDependentType(type)) { + return; + } + + TreePath pathToVariableDecl = atypeFactory.getPath(declarationTree); + assert pathToVariableDecl != null; + + if (variableElt instanceof DetachedVarSymbol) { + // Skip standardizing the Java expression in annotations of artificial variables, + // because artificial variables are created and initialized with standardized Java + // expressions. + // For example, for the following code fragment from + // Nullness test case checker/tests/nullness-asserts/NonNullMapValue.java + // + // ... + // static HashMap call_hashmap = new HashMap<>(); + // + // public foo() { + // for (Integer i : call_hashmap.keySet()) { + // @NonNull Date d = call_hashmap.get(i); + // } + // } + // ... + // + // + // the CFGBuilder creates the initialized artificial iterator as follows: + // iter#num0 = NonNullMapValue.call_hashmap.keySet().iterator() + // Therefore, for the Map Key Checker, the type of the variable "i" is + // @KeyFor({"NonNullMapValue.call_hashmap"}), the string in which is already + // standardized. + return; + } + ElementKind variableKind = variableElt.getKind(); + if (ElementUtils.isBindingVariable(variableElt)) { + // Treat binding variables the same as local variables. + variableKind = ElementKind.LOCAL_VARIABLE; + } + switch (variableKind) { + case PARAMETER: + TreePath pathTillEnclTree = + TreePathUtil.pathTillOfKind(pathToVariableDecl, METHOD_OR_LAMBDA); + if (pathTillEnclTree == null) { + throw new BugInCF("no enclosing method or lambda found for " + variableElt); } - }; - try { - return jec.convert(javaExpr); - } catch (FoundLocalException ex) { - return null; - } - }; - if (debugStringToJavaExpression) { - System.out.printf( - "delocalize(%s, %s) created %s%n", - atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); + Tree enclTree = pathTillEnclTree.getLeaf(); + + if (enclTree.getKind() == Tree.Kind.METHOD) { + MethodTree methodDeclTree = (MethodTree) enclTree; + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody( + stringExpr, methodDeclTree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atVariableDeclaration(%s, %s, %s) 1 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, type); + } else { + // Lambdas can use local variables defined in the enclosing method, so allow + // identifiers to be locals in scope at the location of the lambda. + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atLambdaParameter( + stringExpr, + (LambdaExpressionTree) enclTree, + pathToVariableDecl.getParentPath(), + atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atVariableDeclaration(%s, %s, %s) 2 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, type); + } + break; + + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + StringToJavaExpression stringToJavaExprVar = + stringExpr -> + StringToJavaExpression.atPath( + stringExpr, pathToVariableDecl, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atVariableDeclaration(%s, %s, %s) 3 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExprVar); + } + convertAnnotatedTypeMirror(stringToJavaExprVar, type); + break; + + case FIELD: + case ENUM_CONSTANT: + StringToJavaExpression stringToJavaExprField = + stringExpr -> + StringToJavaExpression.atFieldDecl( + stringExpr, variableElt, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atVariableDeclaration(%s, %s, %s) 4 created %s%n", + type, + TreeUtils.toStringTruncated(declarationTree, 65), + variableElt, + stringToJavaExprField); + } + convertAnnotatedTypeMirror(stringToJavaExprField, type); + break; + + default: + throw new BugInCF( + "unexpected element kind " + variableElt.getKind() + " for " + variableElt); + } } - convertAnnotatedTypeMirror(stringToJavaExpr, atm); - } - - /** - * Delocalizes dependent type annotations in {@code atm} so that they can be placed on the - * declaration of the given method or constructor being invoked. Used by whole program inference - * to infer dependent types for method/constructor parameters based on the actual arguments used - * at call sites. - * - * @param atm the annotated type mirror to delocalize - * @param invocationTree the method or constructor invocation - * @param arguments the actual arguments to the method or constructor - * @param receiver the actual receiver, if there was one; null if not - * @param methodElt the declaration of the method or constructor being invoked - */ - public void delocalizeAtCallsite( - AnnotatedTypeMirror atm, - Tree invocationTree, - List arguments, - @Nullable Node receiver, - ExecutableElement methodElt) { - - // TODO: this method should also take the receiver parameter, if there was one at the - // callsite, as an argument. Before it does, WPI needs to infer receiver types from - // callsites. - - if (!hasDependentType(atm)) { - return; + + /** + * Standardize the Java expressions in annotations in written in the {@code expressionTree}. + * Also, converts the parameter syntax, e.g. "#1", to the parameter name. + * + *

          {@code expressionTree} must be an expressions which can contain explicitly written + * annotations, namely a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or + * {@link com.sun.source.tree.TypeCastTree}. For example, this method standardizes the {@code + * KeyFor} annotation in {@code (@KeyFor("map") String) key }. + * + * @param annotatedType its type; is side-effected by this method + * @param expressionTree a {@link NewClassTree}, {@link com.sun.source.tree.NewArrayTree}, or + * {@link com.sun.source.tree.TypeCastTree} + */ + public void atExpression(AnnotatedTypeMirror annotatedType, ExpressionTree expressionTree) { + if (!hasDependentType(annotatedType)) { + return; + } + + TreePath path = atypeFactory.getPath(expressionTree); + if (path == null) { + return; + } + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atPath(stringExpr, path, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "atExpression(%s, %s) created %s%n", + annotatedType, + TreeUtils.toStringTruncated(expressionTree, 65), + stringToJavaExpr); + } + convertAnnotatedTypeMirror(stringToJavaExpr, annotatedType); } - // For use in stringToJavaExpr below, to avoid re-computation. Especially - // important for the TreePath, which is expensive to compute. - List argsAsExprs = CollectionsPlume.mapList(LocalVariable::fromNode, arguments); - JavaExpression receiverAsExpr = receiver == null ? null : LocalVariable.fromNode(receiver); - TreePath path = atypeFactory.getPath(invocationTree); - - StringToJavaExpression stringToJavaExpr = - stringExpr -> { - JavaExpression expr = - StringToJavaExpression.atPath(stringExpr, path, atypeFactory.getChecker()); - JavaExpressionConverter jec = - new JavaExpressionConverter() { - @Override - public JavaExpression convert(JavaExpression javaExpr) { - // if javaExpr is an argument to the method, - // then return formal parameter expression. - int index = argsAsExprs.indexOf(javaExpr); - if (index != -1) { - return FormalParameter.getFormalParameters(methodElt).get(index); - } - if (javaExpr.equals(receiverAsExpr)) { - return new ThisReference(ElementUtils.enclosingTypeElement(methodElt).asType()); - } - return super.convert(javaExpr); - } + /** + * Standardize the Java expressions in annotations in a type. Converts the parameter syntax, + * e.g. "#2", to the parameter name. + * + * @param type the type to standardize; is side-effected by this method + * @param elt the element whose type is {@code type} + */ + public void atLocalVariable(AnnotatedTypeMirror type, Element elt) { + if (!hasDependentType(type)) { + return; + } - // Local variables and this references at the call site that do not - // correspond to any parameter need to be removed from the dependent - // type annotation, which returning null from these methods - // accomplishes. - @Override - public JavaExpression visitLocalVariable(LocalVariable local, Void unused) { - throw new FoundLocalException(); + switch (elt.getKind()) { + case PARAMETER: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + Tree declarationTree = atypeFactory.declarationFromElement(elt); + if (declarationTree == null) { + if (elt.getKind() == ElementKind.PARAMETER) { + // The tree might be null when + // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() gets the + // assignment context for a pseudo assignment of an argument to a method + // parameter. + return; + } + throw new BugInCF(this.getClass() + ": tree not found"); + } else if (TreeUtils.typeOf(declarationTree) == null) { + // org.checkerframework.framework.flow.CFAbstractTransfer.getValueFromFactory() + // gets the assignment context for a pseudo assignment of an argument to a + // method parameter. + return; } - @Override - public JavaExpression visitThisReference(ThisReference thisRef, Void unused) { - throw new FoundLocalException(); - } - }; + atVariableDeclaration(type, declarationTree, (VariableElement) elt); + return; - try { - return jec.convert(expr); - } catch (FoundLocalException ex) { - return null; - } - }; - - convertAnnotatedTypeMirror(stringToJavaExpr, atm); - } - - /** - * Calls {@link #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} on each - * annotation mirror on type with {@code stringToJavaExpr}. And replaces the annotation with the - * one created by {@code convertAnnotationMirror}, if it's not null. If it is null, the original - * annotation is used. See {@link #convertAnnotationMirror(StringToJavaExpression, - * AnnotationMirror)} for more details. - * - * @param stringToJavaExpr function to convert a string to a {@link JavaExpression} - * @param type the type that is side-effected by this method - */ - protected void convertAnnotatedTypeMirror( - StringToJavaExpression stringToJavaExpr, AnnotatedTypeMirror type) { - this.annotatedTypeReplacer.visit(type, anno -> convertAnnotationMirror(stringToJavaExpr, anno)); - } - - /** - * Given an annotation {@code anno}, this method builds a new annotation with the Java expressions - * transformed according to {@code stringToJavaExpr}. If {@code anno} is not a dependent type - * annotation, {@code null} is returned. - * - *

          If {@code stringToJavaExpr} returns {@code null}, then that expression is removed from the - * returned annotation. - * - *

          Instead of overriding this method, subclasses can override the following methods to change - * the behavior of this class: - * - *

            - *
          • {@link #shouldPassThroughExpression(String)}: to control which expressions are skipped. - * If this method returns true, then the expression string is not parsed and is included in - * the new annotation unchanged. - *
          • {@link #transform(JavaExpression)}: make changes to the JavaExpression produced by {@code - * stringToJavaExpr}. - *
          • {@link #buildAnnotation(AnnotationMirror, Map)}: to change the annotation returned by - * this method. - *
          - * - * @param stringToJavaExpr function that converts strings to {@code JavaExpression}s - * @param anno annotation mirror - * @return an annotation created by applying {@code stringToJavaExpr} to all expression strings in - * {@code anno}, or null if there would be no effect - */ - public @Nullable AnnotationMirror convertAnnotationMirror( - StringToJavaExpression stringToJavaExpr, AnnotationMirror anno) { - if (!isExpressionAnno(anno)) { - return null; + default: + // It's not a local variable (it might be METHOD, CONSTRUCTOR, CLASS, or INTERFACE, + // for example), so there is nothing to do. + break; + } } - Map> newElements = new HashMap<>(); - for (ExecutableElement element : getListOfExpressionElements(anno)) { - List expressionStrings = - AnnotationUtils.getElementValueArray( - anno, element, String.class, Collections.emptyList()); - List javaExprs = new ArrayList<>(expressionStrings.size()); - newElements.put(element, javaExprs); - for (String expression : expressionStrings) { - JavaExpression result; - if (shouldPassThroughExpression(expression)) { - result = new PassThroughExpression(objectTM, expression); - } else { - try { - result = stringToJavaExpr.toJavaExpression(expression); - } catch (JavaExpressionParseException e) { - result = createError(expression, e); - } + /** Thrown when a non-parameter local variable is found. */ + @SuppressWarnings("serial") + private static class FoundLocalException extends RuntimeException {} + + /** + * Viewpoint-adapt all dependent type annotations to the method declaration, {@code + * methodDeclTree}. This method changes occurrences of formal parameter names to the "#2" + * syntax, and it removes expressions that contain other local variables. + * + *

          If a Java expression in {@code atm} references local variables (other than formal + * parameters), the expression is removed from the annotation. This could result in dependent + * type annotations with empty lists of expressions. If this is a problem, a subclass can + * override {@link #buildAnnotation(AnnotationMirror, Map)} to do something besides creating an + * annotation with a empty list. + * + * @param atm type to viewpoint-adapt; is side-effected by this method + * @param methodDeclTree the method declaration to which the annotations are viewpoint-adapted + */ + public void delocalize(AnnotatedTypeMirror atm, MethodTree methodDeclTree) { + if (!hasDependentType(atm)) { + return; } - if (result != null) { - result = transform(result); - javaExprs.add(result); + TreePath pathToMethodDecl = atypeFactory.getPath(methodDeclTree); + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(methodDeclTree); + List parameters = JavaExpression.getFormalParameters(methodElement); + List paramsAsLocals = + JavaExpression.getParametersAsLocalVariables(methodElement); + + StringToJavaExpression stringToJavaExpr = + expression -> { + JavaExpression javaExpr; + try { + javaExpr = + StringToJavaExpression.atPath( + expression, pathToMethodDecl, atypeFactory.getChecker()); + } catch (JavaExpressionParseException ex) { + return null; + } + JavaExpressionConverter jec = + new JavaExpressionConverter() { + @Override + protected JavaExpression visitLocalVariable( + LocalVariable localVarExpr, Void unused) { + int index = paramsAsLocals.indexOf(localVarExpr); + if (index == -1) { + throw new FoundLocalException(); + } + return parameters.get(index); + } + }; + try { + return jec.convert(javaExpr); + } catch (FoundLocalException ex) { + return null; + } + }; + if (debugStringToJavaExpression) { + System.out.printf( + "delocalize(%s, %s) created %s%n", + atm, TreeUtils.toStringTruncated(methodDeclTree, 65), stringToJavaExpr); } - } + convertAnnotatedTypeMirror(stringToJavaExpr, atm); } - return buildAnnotation(anno, newElements); - } - - /** - * This method is for subclasses to override to change JavaExpressions in some way before they are - * inserted into new annotations. This method is called after parsing and viewpoint-adaptation - * have occurred. {@code javaExpr} may be a {@link DependentTypesHelper.PassThroughExpression}. - * - *

          If {@code null} is returned then the expression is not added to the new annotation. - * - *

          The default implementation returns the argument, but subclasses may override it. - * - * @param javaExpr a JavaExpression - * @return a transformed JavaExpression or {@code null} if no transformation exists - */ - protected @Nullable JavaExpression transform(JavaExpression javaExpr) { - return javaExpr; - } - - /** - * Whether or not {@code expression} should be passed to the new annotation unchanged. If this - * method returns true, the {@code expression} is not parsed. - * - *

          The default implementation returns true if the {@code expression} is an expression error - * according to {@link DependentTypesError#isExpressionError(String)}. Subclasses may override - * this method to add additional logic. - * - * @param expression an expression string in a dependent types annotation - * @return whether or not {@code expression} should be passed through unchanged to the new - * annotation - */ - protected boolean shouldPassThroughExpression(String expression) { - return DependentTypesError.isExpressionError(expression); - } - - /** - * Create a new annotation of the same type as {@code originalAnno} using the provided {@code - * elementMap}. - * - * @param originalAnno the annotation passed to {@link - * #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} (this method is a - * helper method for {@link #convertAnnotationMirror(StringToJavaExpression, - * AnnotationMirror)}) - * @param elementMap a mapping from element of {@code originalAnno} to {@code JavaExpression}s - * @return an annotation created from {@code elementMap} - */ - protected AnnotationMirror buildAnnotation( - AnnotationMirror originalAnno, Map> elementMap) { - AnnotationBuilder builder = - new AnnotationBuilder( - atypeFactory.getProcessingEnv(), AnnotationUtils.annotationName(originalAnno)); - builder.copyElementValuesFromAnnotation(originalAnno, elementMap.keySet()); - for (Map.Entry> entry : elementMap.entrySet()) { - List strings = CollectionsPlume.mapList(JavaExpression::toString, entry.getValue()); - builder.setValue(entry.getKey(), strings); + + /** + * Delocalizes dependent type annotations in {@code atm} so that they can be placed on the + * declaration of the given method or constructor being invoked. Used by whole program inference + * to infer dependent types for method/constructor parameters based on the actual arguments used + * at call sites. + * + * @param atm the annotated type mirror to delocalize + * @param invocationTree the method or constructor invocation + * @param arguments the actual arguments to the method or constructor + * @param receiver the actual receiver, if there was one; null if not + * @param methodElt the declaration of the method or constructor being invoked + */ + public void delocalizeAtCallsite( + AnnotatedTypeMirror atm, + Tree invocationTree, + List arguments, + @Nullable Node receiver, + ExecutableElement methodElt) { + + // TODO: this method should also take the receiver parameter, if there was one at the + // callsite, as an argument. Before it does, WPI needs to infer receiver types from + // callsites. + + if (!hasDependentType(atm)) { + return; + } + + // For use in stringToJavaExpr below, to avoid re-computation. Especially + // important for the TreePath, which is expensive to compute. + List argsAsExprs = + CollectionsPlume.mapList(LocalVariable::fromNode, arguments); + JavaExpression receiverAsExpr = receiver == null ? null : LocalVariable.fromNode(receiver); + TreePath path = atypeFactory.getPath(invocationTree); + + StringToJavaExpression stringToJavaExpr = + stringExpr -> { + JavaExpression expr = + StringToJavaExpression.atPath( + stringExpr, path, atypeFactory.getChecker()); + JavaExpressionConverter jec = + new JavaExpressionConverter() { + @Override + public JavaExpression convert(JavaExpression javaExpr) { + // if javaExpr is an argument to the method, + // then return formal parameter expression. + int index = argsAsExprs.indexOf(javaExpr); + if (index != -1) { + return FormalParameter.getFormalParameters(methodElt) + .get(index); + } + if (javaExpr.equals(receiverAsExpr)) { + return new ThisReference( + ElementUtils.enclosingTypeElement(methodElt) + .asType()); + } + return super.convert(javaExpr); + } + + // Local variables and this references at the call site that do not + // correspond to any parameter need to be removed from the dependent + // type annotation, which returning null from these methods + // accomplishes. + @Override + public JavaExpression visitLocalVariable( + LocalVariable local, Void unused) { + throw new FoundLocalException(); + } + + @Override + public JavaExpression visitThisReference( + ThisReference thisRef, Void unused) { + throw new FoundLocalException(); + } + }; + + try { + return jec.convert(expr); + } catch (FoundLocalException ex) { + return null; + } + }; + + convertAnnotatedTypeMirror(stringToJavaExpr, atm); } - return builder.build(); - } - /** - * A {@link JavaExpression} that does not represent a {@link JavaExpression}, but rather allows an - * expression string to be converted to a JavaExpression and then to a string without parsing. - */ - static class PassThroughExpression extends Unknown { - /** Some string. */ - public final String string; + /** + * Calls {@link #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} on each + * annotation mirror on type with {@code stringToJavaExpr}. And replaces the annotation with the + * one created by {@code convertAnnotationMirror}, if it's not null. If it is null, the original + * annotation is used. See {@link #convertAnnotationMirror(StringToJavaExpression, + * AnnotationMirror)} for more details. + * + * @param stringToJavaExpr function to convert a string to a {@link JavaExpression} + * @param type the type that is side-effected by this method + */ + protected void convertAnnotatedTypeMirror( + StringToJavaExpression stringToJavaExpr, AnnotatedTypeMirror type) { + this.annotatedTypeReplacer.visit( + type, anno -> convertAnnotationMirror(stringToJavaExpr, anno)); + } + + /** + * Given an annotation {@code anno}, this method builds a new annotation with the Java + * expressions transformed according to {@code stringToJavaExpr}. If {@code anno} is not a + * dependent type annotation, {@code null} is returned. + * + *

          If {@code stringToJavaExpr} returns {@code null}, then that expression is removed from the + * returned annotation. + * + *

          Instead of overriding this method, subclasses can override the following methods to change + * the behavior of this class: + * + *

            + *
          • {@link #shouldPassThroughExpression(String)}: to control which expressions are skipped. + * If this method returns true, then the expression string is not parsed and is included + * in the new annotation unchanged. + *
          • {@link #transform(JavaExpression)}: make changes to the JavaExpression produced by + * {@code stringToJavaExpr}. + *
          • {@link #buildAnnotation(AnnotationMirror, Map)}: to change the annotation returned by + * this method. + *
          + * + * @param stringToJavaExpr function that converts strings to {@code JavaExpression}s + * @param anno annotation mirror + * @return an annotation created by applying {@code stringToJavaExpr} to all expression strings + * in {@code anno}, or null if there would be no effect + */ + public @Nullable AnnotationMirror convertAnnotationMirror( + StringToJavaExpression stringToJavaExpr, AnnotationMirror anno) { + if (!isExpressionAnno(anno)) { + return null; + } + + Map> newElements = new HashMap<>(); + for (ExecutableElement element : getListOfExpressionElements(anno)) { + List expressionStrings = + AnnotationUtils.getElementValueArray( + anno, element, String.class, Collections.emptyList()); + List javaExprs = new ArrayList<>(expressionStrings.size()); + newElements.put(element, javaExprs); + for (String expression : expressionStrings) { + JavaExpression result; + if (shouldPassThroughExpression(expression)) { + result = new PassThroughExpression(objectTM, expression); + } else { + try { + result = stringToJavaExpr.toJavaExpression(expression); + } catch (JavaExpressionParseException e) { + result = createError(expression, e); + } + } + + if (result != null) { + result = transform(result); + javaExprs.add(result); + } + } + } + return buildAnnotation(anno, newElements); + } /** - * Creates a PassThroughExpression. + * This method is for subclasses to override to change JavaExpressions in some way before they + * are inserted into new annotations. This method is called after parsing and + * viewpoint-adaptation have occurred. {@code javaExpr} may be a {@link + * DependentTypesHelper.PassThroughExpression}. + * + *

          If {@code null} is returned then the expression is not added to the new annotation. * - * @param type some type - * @param string the string to convert to a JavaExpression + *

          The default implementation returns the argument, but subclasses may override it. + * + * @param javaExpr a JavaExpression + * @return a transformed JavaExpression or {@code null} if no transformation exists */ - public PassThroughExpression(TypeMirror type, String string) { - super(type); - this.string = string; + protected @Nullable JavaExpression transform(JavaExpression javaExpr) { + return javaExpr; } - @Override - public String toString() { - return string; + /** + * Whether or not {@code expression} should be passed to the new annotation unchanged. If this + * method returns true, the {@code expression} is not parsed. + * + *

          The default implementation returns true if the {@code expression} is an expression error + * according to {@link DependentTypesError#isExpressionError(String)}. Subclasses may override + * this method to add additional logic. + * + * @param expression an expression string in a dependent types annotation + * @return whether or not {@code expression} should be passed through unchanged to the new + * annotation + */ + protected boolean shouldPassThroughExpression(String expression) { + return DependentTypesError.isExpressionError(expression); } - } - - /** - * Creates a {@link JavaExpression} representing the exception thrown when parsing {@code - * expression}. - * - * @param expression an expression that caused {@code e} when parsed - * @param e the exception thrown when parsing {@code expression} - * @return a Java expression - */ - protected PassThroughExpression createError(String expression, JavaExpressionParseException e) { - return new PassThroughExpression(objectTM, new DependentTypesError(expression, e).toString()); - } - - /** - * Creates a {@link JavaExpression} representing the error caused when parsing {@code expression} - * - * @param expression an expression that caused {@code error} when parsed - * @param error the error message caused by {@code expression} - * @return a Java expression - */ - protected PassThroughExpression createError(String expression, String error) { - return new PassThroughExpression( - objectTM, new DependentTypesError(expression, error).toString()); - } - - /** - * Applies the passed function to each annotation in the given {@link AnnotatedTypeMirror}. If the - * function returns a non-null annotation, then the original annotation is replaced with the - * result. If the function returns null, the original annotation is retained. - */ - private static class AnnotatedTypeReplacer - extends AnnotatedTypeScanner> { - - @Override - public Void visitTypeVariable( - AnnotatedTypeMirror.AnnotatedTypeVariable type, - Function func) { - if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); - } - visitedNodes.put(type, null); - - // If the type variable has a primary annotation, then it is viewpoint-adapted before - // this method is called. The viewpoint-adapted primary annotation was already copied - // to the upper and lower bounds. These annotations cannot be viewpoint-adapted again, - // so remove them, viewpoint-adapt any other annotations in the bound, and then add them - // back. - AnnotationMirrorSet primarys = type.getAnnotations(); - type.getLowerBound().removeAnnotations(primarys); - Void r = scan(type.getLowerBound(), func); - type.getLowerBound().addAnnotations(primarys); - visitedNodes.put(type, r); - - type.getUpperBound().removeAnnotations(primarys); - r = scanAndReduce(type.getUpperBound(), func, r); - type.getUpperBound().addAnnotations(primarys); - visitedNodes.put(type, r); - return r; + + /** + * Create a new annotation of the same type as {@code originalAnno} using the provided {@code + * elementMap}. + * + * @param originalAnno the annotation passed to {@link + * #convertAnnotationMirror(StringToJavaExpression, AnnotationMirror)} (this method is a + * helper method for {@link #convertAnnotationMirror(StringToJavaExpression, + * AnnotationMirror)}) + * @param elementMap a mapping from element of {@code originalAnno} to {@code JavaExpression}s + * @return an annotation created from {@code elementMap} + */ + protected AnnotationMirror buildAnnotation( + AnnotationMirror originalAnno, + Map> elementMap) { + AnnotationBuilder builder = + new AnnotationBuilder( + atypeFactory.getProcessingEnv(), + AnnotationUtils.annotationName(originalAnno)); + builder.copyElementValuesFromAnnotation(originalAnno, elementMap.keySet()); + for (Map.Entry> entry : elementMap.entrySet()) { + List strings = + CollectionsPlume.mapList(JavaExpression::toString, entry.getValue()); + builder.setValue(entry.getKey(), strings); + } + return builder.build(); } - @Override - protected Void scan( - AnnotatedTypeMirror type, Function func) { - if (visitedNodes.containsKey(type)) { - return null; - } - for (AnnotationMirror anno : new AnnotationMirrorSet(type.getAnnotations())) { - AnnotationMirror newAnno = func.apply(anno); - if (newAnno != null) { - // This code must remove and then add, rather than call `replace`, because a - // type may have multiple annotations with the same class, but different - // elements. (This is a bug; see - // https://github.com/typetools/checker-framework/issues/4451 .) - // AnnotatedTypeMirror#replace only removes one annotation that is in the same - // hierarchy as the passed argument. - type.removeAnnotation(anno); - type.addAnnotation(newAnno); + /** + * A {@link JavaExpression} that does not represent a {@link JavaExpression}, but rather allows + * an expression string to be converted to a JavaExpression and then to a string without + * parsing. + */ + static class PassThroughExpression extends Unknown { + /** Some string. */ + public final String string; + + /** + * Creates a PassThroughExpression. + * + * @param type some type + * @param string the string to convert to a JavaExpression + */ + public PassThroughExpression(TypeMirror type, String string) { + super(type); + this.string = string; + } + + @Override + public String toString() { + return string; } - } - return super.scan(type, func); } - } - - /// - /// Methods that check and report errors - /// - - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the given type - * that is an expression error string. - * - * @param atm annotated type to check for expression errors - * @param errorTree the tree at which to report any found errors - */ - public void checkTypeForErrorExpressions(AnnotatedTypeMirror atm, Tree errorTree) { - if (!hasDependentAnnotations()) { - return; + + /** + * Creates a {@link JavaExpression} representing the exception thrown when parsing {@code + * expression}. + * + * @param expression an expression that caused {@code e} when parsed + * @param e the exception thrown when parsing {@code expression} + * @return a Java expression + */ + protected PassThroughExpression createError(String expression, JavaExpressionParseException e) { + return new PassThroughExpression( + objectTM, new DependentTypesError(expression, e).toString()); } - List errors = expressionErrorCollector.visit(atm); - if (errors.isEmpty()) { - return; + /** + * Creates a {@link JavaExpression} representing the error caused when parsing {@code + * expression} + * + * @param expression an expression that caused {@code error} when parsed + * @param error the error message caused by {@code expression} + * @return a Java expression + */ + protected PassThroughExpression createError(String expression, String error) { + return new PassThroughExpression( + objectTM, new DependentTypesError(expression, error).toString()); } - // Report the error at the type rather than at the variable. - if (errorTree.getKind() == Tree.Kind.VARIABLE) { - Tree typeTree = ((VariableTree) errorTree).getType(); - // Don't report the error at the type if the type is not present in source code. - if (((JCTree) typeTree).getPreferredPosition() != -1) { - ModifiersTree modifiers = ((VariableTree) errorTree).getModifiers(); - errorTree = typeTree; - for (AnnotationTree annoTree : modifiers.getAnnotations()) { - String annoString = annoTree.toString(); - for (String annoName : annoToElements.keySet()) { - // TODO: Simple string containment seems too simplistic. At least check for - // a word boundary. - if (annoString.contains(annoName)) { - errorTree = annoTree; - break; + /** + * Applies the passed function to each annotation in the given {@link AnnotatedTypeMirror}. If + * the function returns a non-null annotation, then the original annotation is replaced with the + * result. If the function returns null, the original annotation is retained. + */ + private static class AnnotatedTypeReplacer + extends AnnotatedTypeScanner> { + + @Override + public Void visitTypeVariable( + AnnotatedTypeMirror.AnnotatedTypeVariable type, + Function func) { + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); } - } + visitedNodes.put(type, null); + + // If the type variable has a primary annotation, then it is viewpoint-adapted before + // this method is called. The viewpoint-adapted primary annotation was already copied + // to the upper and lower bounds. These annotations cannot be viewpoint-adapted again, + // so remove them, viewpoint-adapt any other annotations in the bound, and then add them + // back. + AnnotationMirrorSet primarys = type.getAnnotations(); + type.getLowerBound().removeAnnotations(primarys); + Void r = scan(type.getLowerBound(), func); + type.getLowerBound().addAnnotations(primarys); + visitedNodes.put(type, r); + + type.getUpperBound().removeAnnotations(primarys); + r = scanAndReduce(type.getUpperBound(), func, r); + type.getUpperBound().addAnnotations(primarys); + visitedNodes.put(type, r); + return r; } - } - } - reportErrors(errorTree, errors); - } - - /** - * Report the given errors as "expression.unparsable.type.invalid". - * - * @param errorTree where to report the errors - * @param errors the errors to report - */ - protected void reportErrors(Tree errorTree, List errors) { - SourceChecker checker = atypeFactory.getChecker(); - for (DependentTypesError dte : errors) { - checker.reportError(errorTree, "expression.unparsable.type.invalid", dte.format()); - } - } - - /** - * Returns a list of {@link DependentTypesError}s for all the Java expression elements of the - * annotation that are an error string as specified by DependentTypesError#isExpressionError. - * - * @param am an annotation - * @return a list of {@link DependentTypesError}s for the error strings in the given annotation - */ - private List errorElements(AnnotationMirror am) { - assert hasDependentAnnotations(); - - List errors = new ArrayList<>(); - - for (ExecutableElement element : getListOfExpressionElements(am)) { - // It's always an array, not a single value, because @JavaExpression may only be written - // on an annotation element of type String[]. - List value = - AnnotationUtils.getElementValueArray(am, element, String.class, Collections.emptyList()); - for (String v : value) { - if (DependentTypesError.isExpressionError(v)) { - errors.add(DependentTypesError.unparse(v)); + + @Override + protected Void scan( + AnnotatedTypeMirror type, Function func) { + if (visitedNodes.containsKey(type)) { + return null; + } + for (AnnotationMirror anno : new AnnotationMirrorSet(type.getAnnotations())) { + AnnotationMirror newAnno = func.apply(anno); + if (newAnno != null) { + // This code must remove and then add, rather than call `replace`, because a + // type may have multiple annotations with the same class, but different + // elements. (This is a bug; see + // https://github.com/typetools/checker-framework/issues/4451 .) + // AnnotatedTypeMirror#replace only removes one annotation that is in the same + // hierarchy as the passed argument. + type.removeAnnotation(anno); + type.addAnnotation(newAnno); + } + } + return super.scan(type, func); } - } - } - return errors; - } - - /** - * Reports a flowexpr.parse.error error for each Java expression in the given annotation that is - * an expression error string. - * - * @param annotation annotation to check - * @param errorTree location at which to issue errors - */ - public void checkAnnotationForErrorExpressions(AnnotationMirror annotation, Tree errorTree) { - if (!hasDependentAnnotations()) { - return; } - List errors = errorElements(annotation); - if (errors.isEmpty()) { - return; - } - SourceChecker checker = atypeFactory.getChecker(); - for (DependentTypesError error : errors) { - checker.reportError(errorTree, "flowexpr.parse.error", error); + /// + /// Methods that check and report errors + /// + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the given + * type that is an expression error string. + * + * @param atm annotated type to check for expression errors + * @param errorTree the tree at which to report any found errors + */ + public void checkTypeForErrorExpressions(AnnotatedTypeMirror atm, Tree errorTree) { + if (!hasDependentAnnotations()) { + return; + } + + List errors = expressionErrorCollector.visit(atm); + if (errors.isEmpty()) { + return; + } + + // Report the error at the type rather than at the variable. + if (errorTree.getKind() == Tree.Kind.VARIABLE) { + Tree typeTree = ((VariableTree) errorTree).getType(); + // Don't report the error at the type if the type is not present in source code. + if (((JCTree) typeTree).getPreferredPosition() != -1) { + ModifiersTree modifiers = ((VariableTree) errorTree).getModifiers(); + errorTree = typeTree; + for (AnnotationTree annoTree : modifiers.getAnnotations()) { + String annoString = annoTree.toString(); + for (String annoName : annoToElements.keySet()) { + // TODO: Simple string containment seems too simplistic. At least check for + // a word boundary. + if (annoString.contains(annoName)) { + errorTree = annoTree; + break; + } + } + } + } + } + reportErrors(errorTree, errors); } - } - - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the given class - * declaration AnnotatedTypeMirror that is an expression error string. Note that this reports - * errors in the class declaration itself, not the body or extends/implements clauses. - * - * @param classTree class to check - * @param type annotated type of the class - */ - public void checkClassForErrorExpressions(ClassTree classTree, AnnotatedDeclaredType type) { - if (!hasDependentAnnotations()) { - return; + + /** + * Report the given errors as "expression.unparsable.type.invalid". + * + * @param errorTree where to report the errors + * @param errors the errors to report + */ + protected void reportErrors(Tree errorTree, List errors) { + SourceChecker checker = atypeFactory.getChecker(); + for (DependentTypesError dte : errors) { + checker.reportError(errorTree, "expression.unparsable.type.invalid", dte.format()); + } } - // TODO: check that invalid annotations in type variable bounds are properly - // formatted. They are part of the type, but the output isn't nicely formatted. - checkTypeForErrorExpressions(type, classTree); - } - - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the method - * declaration AnnotatedTypeMirror that is an expression error string. - * - * @param methodDeclTree method to check - * @param type annotated type of the method - */ - public void checkMethodForErrorExpressions( - MethodTree methodDeclTree, AnnotatedExecutableType type) { - if (!hasDependentAnnotations()) { - return; + /** + * Returns a list of {@link DependentTypesError}s for all the Java expression elements of the + * annotation that are an error string as specified by DependentTypesError#isExpressionError. + * + * @param am an annotation + * @return a list of {@link DependentTypesError}s for the error strings in the given annotation + */ + private List errorElements(AnnotationMirror am) { + assert hasDependentAnnotations(); + + List errors = new ArrayList<>(); + + for (ExecutableElement element : getListOfExpressionElements(am)) { + // It's always an array, not a single value, because @JavaExpression may only be written + // on an annotation element of type String[]. + List value = + AnnotationUtils.getElementValueArray( + am, element, String.class, Collections.emptyList()); + for (String v : value) { + if (DependentTypesError.isExpressionError(v)) { + errors.add(DependentTypesError.unparse(v)); + } + } + } + return errors; } - // Parameters and receivers are checked by visitVariable - // So only type parameters and return type need to be checked here. + /** + * Reports a flowexpr.parse.error error for each Java expression in the given annotation that is + * an expression error string. + * + * @param annotation annotation to check + * @param errorTree location at which to issue errors + */ + public void checkAnnotationForErrorExpressions(AnnotationMirror annotation, Tree errorTree) { + if (!hasDependentAnnotations()) { + return; + } - checkTypeVariablesForErrorExpressions(methodDeclTree, type); - // Check return type - if (type.getReturnType().getKind() != TypeKind.VOID) { - AnnotatedTypeMirror returnType = atypeFactory.getMethodReturnType(methodDeclTree); - Tree treeForError = - TreeUtils.isConstructor(methodDeclTree) ? methodDeclTree : methodDeclTree.getReturnType(); - checkTypeForErrorExpressions(returnType, treeForError); + List errors = errorElements(annotation); + if (errors.isEmpty()) { + return; + } + SourceChecker checker = atypeFactory.getChecker(); + for (DependentTypesError error : errors) { + checker.reportError(errorTree, "flowexpr.parse.error", error); + } } - } - - /** - * Reports an expression.unparsable.type.invalid error for each Java expression in the given type - * variables that is an expression error string. - * - * @param tree a method declaration - * @param methodType annotated type of the method - */ - private void checkTypeVariablesForErrorExpressions( - MethodTree tree, AnnotatedExecutableType methodType) { - for (int i = 0; i < methodType.getTypeVariables().size(); i++) { - AnnotatedTypeMirror atm = methodType.getTypeVariables().get(i); - StringToJavaExpression stringToJavaExpr = - stringExpr -> - StringToJavaExpression.atMethodBody(stringExpr, tree, atypeFactory.getChecker()); - if (debugStringToJavaExpression) { - System.out.printf( - "checkTypeVariablesForErrorExpressions(%s, %s) created %s%n", - tree, methodType, stringToJavaExpr); - } - convertAnnotatedTypeMirror(stringToJavaExpr, atm); - checkTypeForErrorExpressions(atm, tree.getTypeParameters().get(i)); + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the given + * class declaration AnnotatedTypeMirror that is an expression error string. Note that this + * reports errors in the class declaration itself, not the body or extends/implements clauses. + * + * @param classTree class to check + * @param type annotated type of the class + */ + public void checkClassForErrorExpressions(ClassTree classTree, AnnotatedDeclaredType type) { + if (!hasDependentAnnotations()) { + return; + } + + // TODO: check that invalid annotations in type variable bounds are properly + // formatted. They are part of the type, but the output isn't nicely formatted. + checkTypeForErrorExpressions(type, classTree); } - } - - /** - * Returns true if {@code am} is an expression annotation, that is, an annotation whose element is - * a Java expression. - * - * @param am an annotation - * @return true if {@code am} is an expression annotation - */ - private boolean isExpressionAnno(AnnotationMirror am) { - if (!hasDependentAnnotations()) { - return false; + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the method + * declaration AnnotatedTypeMirror that is an expression error string. + * + * @param methodDeclTree method to check + * @param type annotated type of the method + */ + public void checkMethodForErrorExpressions( + MethodTree methodDeclTree, AnnotatedExecutableType type) { + if (!hasDependentAnnotations()) { + return; + } + + // Parameters and receivers are checked by visitVariable + // So only type parameters and return type need to be checked here. + + checkTypeVariablesForErrorExpressions(methodDeclTree, type); + // Check return type + if (type.getReturnType().getKind() != TypeKind.VOID) { + AnnotatedTypeMirror returnType = atypeFactory.getMethodReturnType(methodDeclTree); + Tree treeForError = + TreeUtils.isConstructor(methodDeclTree) + ? methodDeclTree + : methodDeclTree.getReturnType(); + checkTypeForErrorExpressions(returnType, treeForError); + } } - return annoToElements.containsKey(AnnotationUtils.annotationName(am)); - } - - /** - * Checks all dependent type annotations in the given annotated type to see if the expression - * string is an error string as specified by DependentTypesError#isExpressionError. If the - * annotated type has any errors, then a non-empty list of {@link DependentTypesError} is - * returned. - */ - private class ExpressionErrorCollector - extends SimpleAnnotatedTypeScanner, Void> { - - /** Create ExpressionErrorCollector. */ - private ExpressionErrorCollector() { - super( - (AnnotatedTypeMirror type, Void aVoid) -> { - List errors = new ArrayList<>(); - for (AnnotationMirror am : type.getAnnotations()) { - if (isExpressionAnno(am)) { - errors.addAll(errorElements(am)); - } + + /** + * Reports an expression.unparsable.type.invalid error for each Java expression in the given + * type variables that is an expression error string. + * + * @param tree a method declaration + * @param methodType annotated type of the method + */ + private void checkTypeVariablesForErrorExpressions( + MethodTree tree, AnnotatedExecutableType methodType) { + for (int i = 0; i < methodType.getTypeVariables().size(); i++) { + AnnotatedTypeMirror atm = methodType.getTypeVariables().get(i); + StringToJavaExpression stringToJavaExpr = + stringExpr -> + StringToJavaExpression.atMethodBody( + stringExpr, tree, atypeFactory.getChecker()); + if (debugStringToJavaExpression) { + System.out.printf( + "checkTypeVariablesForErrorExpressions(%s, %s) created %s%n", + tree, methodType, stringToJavaExpr); } - return errors; - }, - DependentTypesHelper::concatenate, - Collections.emptyList()); - } - } - - /** - * Appends list2 to list1 in a new list. If either list is empty, returns the other. Thus, the - * result may be aliased to one of the arguments and the client should only read, not write into, - * the result. - * - * @param list1 a list - * @param list2 a list - * @return the lists, concatenated - */ - private static List concatenate( - List list1, List list2) { - if (list1.isEmpty()) { - return list2; - } else if (list2.isEmpty()) { - return list1; - } - List newList = new ArrayList<>(list1.size() + list2.size()); - newList.addAll(list1); - newList.addAll(list2); - return newList; - } - - /** - * The underlying type of the second parameter is the result of applying type variable - * substitution to the visited type (the first parameter). This class copies annotations from the - * visited type to the second formal parameter except for annotations on types that have been - * substituted. - */ - private class ViewpointAdaptedCopier extends DoubleAnnotatedTypeScanner { - @Override - protected Void scan(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { - if (from == null || to == null) { - return null; - } - AnnotationMirrorSet replacements = new AnnotationMirrorSet(); - for (String vpa : annoToElements.keySet()) { - AnnotationMirror anno = from.getAnnotation(vpa); - if (anno != null) { - // Only replace annotations that might have been changed. - replacements.add(anno); + convertAnnotatedTypeMirror(stringToJavaExpr, atm); + checkTypeForErrorExpressions(atm, tree.getTypeParameters().get(i)); } - } - to.replaceAnnotations(replacements); - if (from.getKind() != to.getKind() - || (from.getKind() == TypeKind.TYPEVAR - && TypesUtils.isCapturedTypeVariable(to.getUnderlyingType()))) { - // If the underlying types don't match, then from has been substituted for a - // from variable, so don't recur. The primary annotation was copied because - // the from variable might have had a primary annotation at a use. - // For example: - // void method(@KeyFor("a") T t) {...} - // void use(@KeyFor("b") String s) { - // method(s); // the from of the parameter should be @KeyFor("a") String - // } - return null; - } - return super.scan(from, to); } - @Override - protected Void defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if (type1 == null || type2 == null) { - return null; - } - if (type1.getKind() != type2.getKind()) { - throw new BugInCF("Should be the same. type: %s p: %s ", type1, type2); - } - return null; + /** + * Returns true if {@code am} is an expression annotation, that is, an annotation whose element + * is a Java expression. + * + * @param am an annotation + * @return true if {@code am} is an expression annotation + */ + private boolean isExpressionAnno(AnnotationMirror am) { + if (!hasDependentAnnotations()) { + return false; + } + return annoToElements.containsKey(AnnotationUtils.annotationName(am)); } - } - - /** - * Returns true if {@code atm} has any dependent type annotations. If an annotated type does not - * have a dependent type annotation, then no standardization or viewpoint adaption is performed. - * (This check avoids calling time-intensive methods unless required.) - * - * @param atm a type - * @return true if {@code atm} has any dependent type annotations - */ - private boolean hasDependentType(AnnotatedTypeMirror atm) { - if (atm == null) { - return false; + + /** + * Checks all dependent type annotations in the given annotated type to see if the expression + * string is an error string as specified by DependentTypesError#isExpressionError. If the + * annotated type has any errors, then a non-empty list of {@link DependentTypesError} is + * returned. + */ + private class ExpressionErrorCollector + extends SimpleAnnotatedTypeScanner, Void> { + + /** Create ExpressionErrorCollector. */ + private ExpressionErrorCollector() { + super( + (AnnotatedTypeMirror type, Void aVoid) -> { + List errors = new ArrayList<>(); + for (AnnotationMirror am : type.getAnnotations()) { + if (isExpressionAnno(am)) { + errors.addAll(errorElements(am)); + } + } + return errors; + }, + DependentTypesHelper::concatenate, + Collections.emptyList()); + } } - // This is a test about the type system. - if (!hasDependentAnnotations()) { - return false; + + /** + * Appends list2 to list1 in a new list. If either list is empty, returns the other. Thus, the + * result may be aliased to one of the arguments and the client should only read, not write + * into, the result. + * + * @param list1 a list + * @param list2 a list + * @return the lists, concatenated + */ + private static List concatenate( + List list1, List list2) { + if (list1.isEmpty()) { + return list2; + } else if (list2.isEmpty()) { + return list1; + } + List newList = new ArrayList<>(list1.size() + list2.size()); + newList.addAll(list1); + newList.addAll(list2); + return newList; } - // This is a test about this specific type. - return hasDependentTypeScanner.visit(atm); - } - - /** Returns true if the passed AnnotatedTypeMirror has any dependent type annotations. */ - private final AnnotatedTypeScanner hasDependentTypeScanner = - new SimpleAnnotatedTypeScanner<>( - (type, __) -> { - for (AnnotationMirror annotationMirror : type.getAnnotations()) { - if (isExpressionAnno(annotationMirror)) { - return true; - } + + /** + * The underlying type of the second parameter is the result of applying type variable + * substitution to the visited type (the first parameter). This class copies annotations from + * the visited type to the second formal parameter except for annotations on types that have + * been substituted. + */ + private class ViewpointAdaptedCopier extends DoubleAnnotatedTypeScanner { + @Override + protected Void scan(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { + if (from == null || to == null) { + return null; + } + AnnotationMirrorSet replacements = new AnnotationMirrorSet(); + for (String vpa : annoToElements.keySet()) { + AnnotationMirror anno = from.getAnnotation(vpa); + if (anno != null) { + // Only replace annotations that might have been changed. + replacements.add(anno); + } + } + to.replaceAnnotations(replacements); + if (from.getKind() != to.getKind() + || (from.getKind() == TypeKind.TYPEVAR + && TypesUtils.isCapturedTypeVariable(to.getUnderlyingType()))) { + // If the underlying types don't match, then from has been substituted for a + // from variable, so don't recur. The primary annotation was copied because + // the from variable might have had a primary annotation at a use. + // For example: + // void method(@KeyFor("a") T t) {...} + // void use(@KeyFor("b") String s) { + // method(s); // the from of the parameter should be @KeyFor("a") String + // } + return null; } + return super.scan(from, to); + } + + @Override + protected Void defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { + if (type1 == null || type2 == null) { + return null; + } + if (type1.getKind() != type2.getKind()) { + throw new BugInCF("Should be the same. type: %s p: %s ", type1, type2); + } + return null; + } + } + + /** + * Returns true if {@code atm} has any dependent type annotations. If an annotated type does not + * have a dependent type annotation, then no standardization or viewpoint adaption is performed. + * (This check avoids calling time-intensive methods unless required.) + * + * @param atm a type + * @return true if {@code atm} has any dependent type annotations + */ + private boolean hasDependentType(AnnotatedTypeMirror atm) { + if (atm == null) { return false; - }, - Boolean::logicalOr, - false); + } + // This is a test about the type system. + if (!hasDependentAnnotations()) { + return false; + } + // This is a test about this specific type. + return hasDependentTypeScanner.visit(atm); + } + + /** Returns true if the passed AnnotatedTypeMirror has any dependent type annotations. */ + private final AnnotatedTypeScanner hasDependentTypeScanner = + new SimpleAnnotatedTypeScanner<>( + (type, __) -> { + for (AnnotationMirror annotationMirror : type.getAnnotations()) { + if (isExpressionAnno(annotationMirror)) { + return true; + } + } + return false; + }, + Boolean::logicalOr, + false); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java index 95537cf2a42..cc001dde6a1 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesTreeAnnotator.java @@ -6,71 +6,75 @@ import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.VariableTree; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + /** * Standardizes Java expressions in annotations and also viewpoint-adapts field accesses. Other * viewpoint adaption is handled in {@link DependentTypesHelper}. */ public class DependentTypesTreeAnnotator extends TreeAnnotator { - private final DependentTypesHelper helper; + private final DependentTypesHelper helper; - public DependentTypesTreeAnnotator( - AnnotatedTypeFactory atypeFactory, DependentTypesHelper helper) { - super(atypeFactory); - this.helper = helper; - } + public DependentTypesTreeAnnotator( + AnnotatedTypeFactory atypeFactory, DependentTypesHelper helper) { + super(atypeFactory); + this.helper = helper; + } - @Override - public Void visitClass(ClassTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - TypeElement ele = TreeUtils.elementFromDeclaration(tree); - helper.atTypeDecl(annotatedTypeMirror, ele); - return super.visitClass(tree, annotatedTypeMirror); - } + @Override + public Void visitClass(ClassTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + TypeElement ele = TreeUtils.elementFromDeclaration(tree); + helper.atTypeDecl(annotatedTypeMirror, ele); + return super.visitClass(tree, annotatedTypeMirror); + } - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror annotatedType) { - helper.atExpression(annotatedType, tree); - return super.visitNewArray(tree, annotatedType); - } + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror annotatedType) { + helper.atExpression(annotatedType, tree); + return super.visitNewArray(tree, annotatedType); + } - @Override - public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror annotatedType) { - log("DTTA.visitTypeCast(%s, %s)%n", tree, annotatedType); - helper.atExpression(annotatedType, tree); - log("DTTA.visitTypeCast(%s, ...) annotatedType=%s; about to call super%n", tree, annotatedType); - return super.visitTypeCast(tree, annotatedType); - } + @Override + public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror annotatedType) { + log("DTTA.visitTypeCast(%s, %s)%n", tree, annotatedType); + helper.atExpression(annotatedType, tree); + log( + "DTTA.visitTypeCast(%s, ...) annotatedType=%s; about to call super%n", + tree, annotatedType); + return super.visitTypeCast(tree, annotatedType); + } - @Override - public Void visitVariable(VariableTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - VariableElement ele = TreeUtils.elementFromDeclaration(tree); - helper.atVariableDeclaration(annotatedTypeMirror, tree, ele); - return super.visitVariable(tree, annotatedTypeMirror); - } + @Override + public Void visitVariable(VariableTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + VariableElement ele = TreeUtils.elementFromDeclaration(tree); + helper.atVariableDeclaration(annotatedTypeMirror, tree, ele); + return super.visitVariable(tree, annotatedTypeMirror); + } - @Override - public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - Element ele = TreeUtils.elementFromUse(tree); - if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { - helper.atVariableDeclaration(annotatedTypeMirror, tree, (VariableElement) ele); + @Override + public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror annotatedTypeMirror) { + Element ele = TreeUtils.elementFromUse(tree); + if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { + helper.atVariableDeclaration(annotatedTypeMirror, tree, (VariableElement) ele); + } + return super.visitIdentifier(tree, annotatedTypeMirror); } - return super.visitIdentifier(tree, annotatedTypeMirror); - } - @Override - public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { - Element ele = TreeUtils.elementFromUse(tree); - if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { - helper.atFieldAccess(type, tree); + @Override + public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { + Element ele = TreeUtils.elementFromUse(tree); + if (ele.getKind() == ElementKind.FIELD || ele.getKind() == ElementKind.ENUM_CONSTANT) { + helper.atFieldAccess(type, tree); + } + return super.visitMemberSelect(tree, type); } - return super.visitMemberSelect(tree, type); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java index 8e10cbbe15b..5538421a44d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ClassTypeParamApplier.java @@ -3,123 +3,125 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; + /** * Applies the annotations present for a class type parameter onto an AnnotatedTypeVariable. See * {@link TypeParamElementAnnotationApplier} for details. */ public class ClassTypeParamApplier extends TypeParamElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new ClassTypeParamApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * Returns true if element represents a type parameter for a class. - * - * @param type ignored - * @param element the element that might be a type parameter - * @return true if element represents a type parameter for a class - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return element.getKind() == ElementKind.TYPE_PARAMETER - && element.getEnclosingElement() instanceof Symbol.ClassSymbol; - } - - /** The class that holds the type parameter element. */ - private final Symbol.ClassSymbol enclosingClass; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ ClassTypeParamApplier( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element, atypeFactory); - - if (!(element.getEnclosingElement() instanceof Symbol.ClassSymbol)) { - throw new BugInCF( - "TypeParameter not enclosed by class? Type( " - + type - + " ) " - + "Element ( " - + element - + " ) "); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new ClassTypeParamApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * Returns true if element represents a type parameter for a class. + * + * @param type ignored + * @param element the element that might be a type parameter + * @return true if element represents a type parameter for a class + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return element.getKind() == ElementKind.TYPE_PARAMETER + && element.getEnclosingElement() instanceof Symbol.ClassSymbol; + } + + /** The class that holds the type parameter element. */ + private final Symbol.ClassSymbol enclosingClass; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ ClassTypeParamApplier( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element, atypeFactory); + + if (!(element.getEnclosingElement() instanceof Symbol.ClassSymbol)) { + throw new BugInCF( + "TypeParameter not enclosed by class? Type( " + + type + + " ) " + + "Element ( " + + element + + " ) "); + } + + enclosingClass = (Symbol.ClassSymbol) element.getEnclosingElement(); } - enclosingClass = (Symbol.ClassSymbol) element.getEnclosingElement(); - } - - /** - * Returns TargetType.CLASS_TYPE_PARAMETER. - * - * @return TargetType.CLASS_TYPE_PARAMETER - */ - @Override - protected TargetType lowerBoundTarget() { - return TargetType.CLASS_TYPE_PARAMETER; - } - - /** - * Returns TargetType.CLASS_TYPE_PARAMETER_BOUND. - * - * @return TargetType.CLASS_TYPE_PARAMETER_BOUND - */ - @Override - protected TargetType upperBoundTarget() { - return TargetType.CLASS_TYPE_PARAMETER_BOUND; - } - - /** - * Returns the index of element in the type parameter list of its enclosing class. - * - * @return the index of element in the type parameter list of its enclosing class - */ - @Override - public int getElementIndex() { - return enclosingClass.getTypeParameters().indexOf(element); - } - - /** The valid targets. */ - private static final TargetType[] validTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the raw type attributes of the enclosing class. - * - * @return the raw type attributes of the enclosing class - */ - @Override - protected Iterable getRawTypeAttributes() { - return enclosingClass.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } + /** + * Returns TargetType.CLASS_TYPE_PARAMETER. + * + * @return TargetType.CLASS_TYPE_PARAMETER + */ + @Override + protected TargetType lowerBoundTarget() { + return TargetType.CLASS_TYPE_PARAMETER; + } + + /** + * Returns TargetType.CLASS_TYPE_PARAMETER_BOUND. + * + * @return TargetType.CLASS_TYPE_PARAMETER_BOUND + */ + @Override + protected TargetType upperBoundTarget() { + return TargetType.CLASS_TYPE_PARAMETER_BOUND; + } + + /** + * Returns the index of element in the type parameter list of its enclosing class. + * + * @return the index of element in the type parameter list of its enclosing class + */ + @Override + public int getElementIndex() { + return enclosingClass.getTypeParameters().indexOf(element); + } + + /** The valid targets. */ + private static final TargetType[] validTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the raw type attributes of the enclosing class. + * + * @return the raw type attributes of the enclosing class + */ + @Override + protected Iterable getRawTypeAttributes() { + return enclosingClass.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java index c06c2e0e435..b1bc405f9e3 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java @@ -7,17 +7,7 @@ import com.sun.tools.javac.code.TypeAnnotationPosition; import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.formatter.qual.FormatMethod; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -35,6 +25,19 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; + /** * Utility methods for adding the annotations that are stored in an Element to the type that * represents that element (or a use of that Element). This class also contains package private @@ -42,578 +45,591 @@ */ public class ElementAnnotationUtil { - /** - * For each type/element pair, add all of the annotations stored in Element to type. See apply for - * more details. - * - * @param types the types to which we wish to apply element annotations - * @param elements the elements that may contain annotations to apply. elements.size must == - * types.size - * @param typeFactory the type factory used to create the AnnotatedTypeMirrors contained by types - */ - public static void applyAllElementAnnotations( - List types, - List elements, - AnnotatedTypeFactory typeFactory) { - if (types.size() != elements.size()) { - throw new BugInCF( - "Number of types and elements don't match. " - + "types ( " - + StringsPlume.join(", ", types) - + " ) " - + "element ( " - + StringsPlume.join(", ", elements) - + " ) "); - } + /** + * For each type/element pair, add all of the annotations stored in Element to type. See apply + * for more details. + * + * @param types the types to which we wish to apply element annotations + * @param elements the elements that may contain annotations to apply. elements.size must == + * types.size + * @param typeFactory the type factory used to create the AnnotatedTypeMirrors contained by + * types + */ + public static void applyAllElementAnnotations( + List types, + List elements, + AnnotatedTypeFactory typeFactory) { + if (types.size() != elements.size()) { + throw new BugInCF( + "Number of types and elements don't match. " + + "types ( " + + StringsPlume.join(", ", types) + + " ) " + + "element ( " + + StringsPlume.join(", ", elements) + + " ) "); + } - for (int i = 0; i < types.size(); i++) { - ElementAnnotationApplier.apply(types.get(i), elements.get(i), typeFactory); + for (int i = 0; i < types.size(); i++) { + ElementAnnotationApplier.apply(types.get(i), elements.get(i), typeFactory); + } } - } - - /** - * When a declaration annotation is an alias for a type annotation, then the Checker Framework may - * move the annotation before replacing it by the canonical version. - * - *

          If the annotation is one of the Checker Framework compatibility annotations, for example - * org.checkerframework.checker.nullness.compatqual.NonNullDecl, then it is interpreted as a type - * annotation in the same location. - * - * @param type the type to annotate - * @param annotations the annotations to add - */ - @SuppressWarnings("interning:not.interned") // AST node comparison - static void addDeclarationAnnotationsFromElement( - AnnotatedTypeMirror type, List annotations) { - // The code here should be similar to - // org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable - // However, in this version, note that there is no check whether the annotation is a type - // use annotation, using `AnnotationUtils.isTypeUseAnnotation`. - // The declaration annotation could also be a type use annotation, but it appears as a - // declaration annotation on the element, so treat it as a declaration annotation, unless it - // is a Checker Framework annotation. - AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type); - if (innerType != type) { - for (AnnotationMirror anno : annotations) { - if (AnnotationUtils.annotationName(anno).startsWith("org.checkerframework")) { - // Always treat Checker Framework annotations as type annotations, even if they - // are declaration annotations. - innerType.addAnnotation(anno); + + /** + * When a declaration annotation is an alias for a type annotation, then the Checker Framework + * may move the annotation before replacing it by the canonical version. + * + *

          If the annotation is one of the Checker Framework compatibility annotations, for example + * org.checkerframework.checker.nullness.compatqual.NonNullDecl, then it is interpreted as a + * type annotation in the same location. + * + * @param type the type to annotate + * @param annotations the annotations to add + */ + @SuppressWarnings("interning:not.interned") // AST node comparison + static void addDeclarationAnnotationsFromElement( + AnnotatedTypeMirror type, List annotations) { + // The code here should be similar to + // org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable + // However, in this version, note that there is no check whether the annotation is a type + // use annotation, using `AnnotationUtils.isTypeUseAnnotation`. + // The declaration annotation could also be a type use annotation, but it appears as a + // declaration annotation on the element, so treat it as a declaration annotation, unless it + // is a Checker Framework annotation. + AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type); + if (innerType != type) { + for (AnnotationMirror anno : annotations) { + if (AnnotationUtils.annotationName(anno).startsWith("org.checkerframework")) { + // Always treat Checker Framework annotations as type annotations, even if they + // are declaration annotations. + innerType.addAnnotation(anno); + } else { + // Declaration annotations apply to the outer type. + type.addAnnotation(anno); + } + } } else { - // Declaration annotations apply to the outer type. - type.addAnnotation(anno); + type.addAnnotations(annotations); } - } - } else { - type.addAnnotations(annotations); - } - } - - /** - * Does expectedValues contain enumValue. This is just a linear search. - * - * @param enumValue value to search for, a needle - * @param expectedValues values to search through, a haystack - * @return true if enumValue is in expectedValues, false otherwise - */ - static boolean contains(Object enumValue, Object[] expectedValues) { - for (Object expected : expectedValues) { - if (enumValue.equals(expected)) { - return true; - } } - return false; - } - - /** - * Use a map to partition annotations with the given TargetTypes into Lists, where each target - * type is a key in the output map. Any annotation that does not have one of these target types - * will be added to unmatched - * - * @param annos the collection of annotations to partition - * @param unmatched a list to add annotations with unmatched target types to - * @param targetTypes a list of target types to partition annos with - * @return a map from targetType → List of Annotations that have that targetType - */ - static Map> partitionByTargetType( - Collection annos, List unmatched, TargetType... targetTypes) { - Map> targetTypeToAnnos = new HashMap<>(); - for (TargetType targetType : targetTypes) { - targetTypeToAnnos.put(targetType, new ArrayList<>()); - } + /** + * Does expectedValues contain enumValue. This is just a linear search. + * + * @param enumValue value to search for, a needle + * @param expectedValues values to search through, a haystack + * @return true if enumValue is in expectedValues, false otherwise + */ + static boolean contains(Object enumValue, Object[] expectedValues) { + for (Object expected : expectedValues) { + if (enumValue.equals(expected)) { + return true; + } + } - for (TypeCompound anno : annos) { - List annoSet = targetTypeToAnnos.get(anno.getPosition().type); - if (annoSet != null) { - annoSet.add(anno); - } else if (unmatched != null) { - unmatched.add(anno); - } + return false; } - return targetTypeToAnnos; - } - - /** - * A class used solely to annotate wildcards from Element annotations. Instances of - * WildcardBoundAnnos are used to aggregate ALL annotations for a given Wildcard and then apply - * them all at once in order to resolve the annotations in front of unbound wildcards. - * - *

          Wildcard annotations are applied as follows: - * - *

            - *
          • a) If an Annotation is in front of a extends or super bounded wildcard, it applies to the - * bound that is NOT explicitly present. e.g. - *
            {@code
            -   * <@A ? extends Object> -- @A is placed on the super bound (Void)
            -   * <@B ? super CharSequence> -- @B is placed on the extends bound (probably Object)
            -   * }
            - *
          • b) If an Annotation is on a bound, it applies to that bound. E.g. - *
            {@code
            -   *  -- @A is placed on the extends bound (Object)
            -   *  -- @B is placed on the super bound (CharSequence)
            -   * }
            - *
          • c) If an Annotation is on an unbounded wildcard there are two subcases. - *
              - *
            • c.1 The user wrote the annotation explicitly -- these annotations apply to both - * bounds e.g. the user wrote - *
              {@code
              -   * <@C ?> -- the annotation is placed on the extends/super bounds
              -   * }
              - *
            • c.2 Previous calls to getAnnotatedType have annotated this wildcard with BOTH - * bounds e.g. the user wrote {@code } but the checker framework added {@code <@C ? - * extends @D Object>} to the corresponding element. - *
              -   *             {@code  -- @C is placed on the lower bound and @D is placed on the upper bound
              -   *          This case is treated just like annotations in cases a/b.
              -   * }
              - *
            - *
          - */ - private static final class WildcardBoundAnnos { - /** The wildcard type. */ - public final AnnotatedWildcardType wildcard; - - /** The upper bound annotations. */ - public final AnnotationMirrorSet upperBoundAnnos; - - /** The lower bound annotations. */ - public final AnnotationMirrorSet lowerBoundAnnos; - - // indicates that this is an annotation in front of an unbounded wildcard - // e.g. < @A ? > - // For each annotation in this set, if there is no annotation in upperBoundAnnos - // that is in the same hierarchy then the annotation will be applied to both bounds - // otherwise the annotation applies to the lower bound only - public final AnnotationMirrorSet possiblyBoth; - - /** Whether or not wildcard has an explicit super bound. */ - private final boolean isSuperBounded; - - /** Whether or not wildcard has NO explicit bound whatsoever. */ - private final boolean isUnbounded; - /** - * Creates a new WildcardBoundAnnos from the given wildcard type, with no upper- or lower-bound - * annotations. + * Use a map to partition annotations with the given TargetTypes into Lists, where each target + * type is a key in the output map. Any annotation that does not have one of these target types + * will be added to unmatched * - * @param wildcard the wildcard type + * @param annos the collection of annotations to partition + * @param unmatched a list to add annotations with unmatched target types to + * @param targetTypes a list of target types to partition annos with + * @return a map from targetType → List of Annotations that have that targetType */ - WildcardBoundAnnos(AnnotatedWildcardType wildcard) { - this.wildcard = wildcard; - this.upperBoundAnnos = new AnnotationMirrorSet(); - this.lowerBoundAnnos = new AnnotationMirrorSet(); - this.possiblyBoth = new AnnotationMirrorSet(); - - this.isSuperBounded = AnnotatedTypes.hasExplicitSuperBound(wildcard); - this.isUnbounded = AnnotatedTypes.hasNoExplicitBound(wildcard); - } + static Map> partitionByTargetType( + Collection annos, + List unmatched, + TargetType... targetTypes) { + Map> targetTypeToAnnos = new HashMap<>(); + for (TargetType targetType : targetTypes) { + targetTypeToAnnos.put(targetType, new ArrayList<>()); + } - void addAnnotation(TypeCompound anno) { - // if the typepath entry ends in Wildcard then the annotation should go on a bound - // otherwise, the annotation is in front of the wildcard - // e.g. @HERE ? extends Object - boolean isInFrontOfWildcard = anno.getPosition().location.last() != TypePathEntry.WILDCARD; - if (isInFrontOfWildcard && isUnbounded) { - possiblyBoth.add(anno); - } else { - // A TypePathEntry of WILDCARD indicates that it is placed on the bound - // use the type of the wildcard bound to determine which set to put it in - - if (isInFrontOfWildcard) { - if (isSuperBounded) { - upperBoundAnnos.add(anno); - } else { - lowerBoundAnnos.add(anno); - } - } else { // it's on the bound - if (isSuperBounded) { - lowerBoundAnnos.add(anno); - } else { - upperBoundAnnos.add(anno); - } + for (TypeCompound anno : annos) { + List annoSet = targetTypeToAnnos.get(anno.getPosition().type); + if (annoSet != null) { + annoSet.add(anno); + } else if (unmatched != null) { + unmatched.add(anno); + } } - } + + return targetTypeToAnnos; } /** - * Apply the annotations to wildcard according to the rules outlined in the comment at the - * beginning of this class. + * A class used solely to annotate wildcards from Element annotations. Instances of + * WildcardBoundAnnos are used to aggregate ALL annotations for a given Wildcard and then apply + * them all at once in order to resolve the annotations in front of unbound wildcards. + * + *

          Wildcard annotations are applied as follows: + * + *

            + *
          • a) If an Annotation is in front of a extends or super bounded wildcard, it applies to + * the bound that is NOT explicitly present. e.g. + *
            {@code
            +     * <@A ? extends Object> -- @A is placed on the super bound (Void)
            +     * <@B ? super CharSequence> -- @B is placed on the extends bound (probably Object)
            +     * }
            + *
          • b) If an Annotation is on a bound, it applies to that bound. E.g. + *
            {@code
            +     *  -- @A is placed on the extends bound (Object)
            +     *  -- @B is placed on the super bound (CharSequence)
            +     * }
            + *
          • c) If an Annotation is on an unbounded wildcard there are two subcases. + *
              + *
            • c.1 The user wrote the annotation explicitly -- these annotations apply to both + * bounds e.g. the user wrote + *
              {@code
              +     * <@C ?> -- the annotation is placed on the extends/super bounds
              +     * }
              + *
            • c.2 Previous calls to getAnnotatedType have annotated this wildcard with BOTH + * bounds e.g. the user wrote {@code } but the checker framework added {@code <@C + * ? extends @D Object>} to the corresponding element. + *
              +     *             {@code  -- @C is placed on the lower bound and @D is placed on the upper bound
              +     *          This case is treated just like annotations in cases a/b.
              +     * }
              + *
            + *
          */ - void apply() { - AnnotatedTypeMirror extendsBound = wildcard.getExtendsBound(); - AnnotatedTypeMirror superBound = wildcard.getSuperBound(); - - for (AnnotationMirror extAnno : upperBoundAnnos) { - extendsBound.addAnnotation(extAnno); - } - for (AnnotationMirror supAnno : lowerBoundAnnos) { - superBound.addAnnotation(supAnno); - } - - for (AnnotationMirror anno : possiblyBoth) { - superBound.addAnnotation(anno); - - // This will be false if we've defaulted the bounds and are reading them again. - // In that case, we will have already created an annotation for the extends bound - // that should be honored and NOT overwritten. - extendsBound.addMissingAnnotation(anno); - } - } - } - - /** - * TypeCompounds are implementations of AnnotationMirror that are stored on Elements. Each type - * compound has a TypeAnnotationPosition which identifies, relative to the "root" of a type, where - * an annotation should be placed. This method adds all of the given TypeCompounds to the correct - * location on type by interpreting the TypeAnnotationPosition. - * - *

          Note: We handle all of the Element annotations on a type at once because we need to identify - * whether or not the element annotation in front of an unbound wildcard (e.g. {@code <@HERE ?>}) - * should apply to only the super bound or both the super bound and the extends bound. - * - * @see org.checkerframework.framework.util.element.ElementAnnotationUtil.WildcardBoundAnnos - * @param type the type in which annos should be placed - * @param annos all of the element annotations, TypeCompounds, for type - */ - static void annotateViaTypeAnnoPosition(AnnotatedTypeMirror type, Collection annos) - throws UnexpectedAnnotationLocationException { - IdentityHashMap wildcardToAnnos = - new IdentityHashMap<>(); - for (TypeCompound anno : annos) { - AnnotatedTypeMirror target = getTypeAtLocation(type, anno.position.location, anno, false); - if (target.getKind() == TypeKind.WILDCARD) { - addWildcardToBoundMap((AnnotatedWildcardType) target, anno, wildcardToAnnos); - } else { - target.addAnnotation(anno); - } - } + private static final class WildcardBoundAnnos { + /** The wildcard type. */ + public final AnnotatedWildcardType wildcard; + + /** The upper bound annotations. */ + public final AnnotationMirrorSet upperBoundAnnos; + + /** The lower bound annotations. */ + public final AnnotationMirrorSet lowerBoundAnnos; + + // indicates that this is an annotation in front of an unbounded wildcard + // e.g. < @A ? > + // For each annotation in this set, if there is no annotation in upperBoundAnnos + // that is in the same hierarchy then the annotation will be applied to both bounds + // otherwise the annotation applies to the lower bound only + public final AnnotationMirrorSet possiblyBoth; + + /** Whether or not wildcard has an explicit super bound. */ + private final boolean isSuperBounded; + + /** Whether or not wildcard has NO explicit bound whatsoever. */ + private final boolean isUnbounded; + + /** + * Creates a new WildcardBoundAnnos from the given wildcard type, with no upper- or + * lower-bound annotations. + * + * @param wildcard the wildcard type + */ + WildcardBoundAnnos(AnnotatedWildcardType wildcard) { + this.wildcard = wildcard; + this.upperBoundAnnos = new AnnotationMirrorSet(); + this.lowerBoundAnnos = new AnnotationMirrorSet(); + this.possiblyBoth = new AnnotationMirrorSet(); + + this.isSuperBounded = AnnotatedTypes.hasExplicitSuperBound(wildcard); + this.isUnbounded = AnnotatedTypes.hasNoExplicitBound(wildcard); + } - for (WildcardBoundAnnos wildcardAnnos : wildcardToAnnos.values()) { - wildcardAnnos.apply(); + void addAnnotation(TypeCompound anno) { + // if the typepath entry ends in Wildcard then the annotation should go on a bound + // otherwise, the annotation is in front of the wildcard + // e.g. @HERE ? extends Object + boolean isInFrontOfWildcard = + anno.getPosition().location.last() != TypePathEntry.WILDCARD; + if (isInFrontOfWildcard && isUnbounded) { + possiblyBoth.add(anno); + } else { + // A TypePathEntry of WILDCARD indicates that it is placed on the bound + // use the type of the wildcard bound to determine which set to put it in + + if (isInFrontOfWildcard) { + if (isSuperBounded) { + upperBoundAnnos.add(anno); + } else { + lowerBoundAnnos.add(anno); + } + } else { // it's on the bound + if (isSuperBounded) { + lowerBoundAnnos.add(anno); + } else { + upperBoundAnnos.add(anno); + } + } + } + } + + /** + * Apply the annotations to wildcard according to the rules outlined in the comment at the + * beginning of this class. + */ + void apply() { + AnnotatedTypeMirror extendsBound = wildcard.getExtendsBound(); + AnnotatedTypeMirror superBound = wildcard.getSuperBound(); + + for (AnnotationMirror extAnno : upperBoundAnnos) { + extendsBound.addAnnotation(extAnno); + } + for (AnnotationMirror supAnno : lowerBoundAnnos) { + superBound.addAnnotation(supAnno); + } + + for (AnnotationMirror anno : possiblyBoth) { + superBound.addAnnotation(anno); + + // This will be false if we've defaulted the bounds and are reading them again. + // In that case, we will have already created an annotation for the extends bound + // that should be honored and NOT overwritten. + extendsBound.addMissingAnnotation(anno); + } + } } - } - - /** - * Creates an entry in wildcardToAnnos for wildcard if one does not already exists. Adds anno to - * the WildcardBoundAnnos object for wildcard. - */ - private static void addWildcardToBoundMap( - AnnotatedWildcardType wildcard, - TypeCompound anno, - Map wildcardToAnnos) { - WildcardBoundAnnos boundAnnos = - wildcardToAnnos.computeIfAbsent(wildcard, WildcardBoundAnnos::new); - boundAnnos.addAnnotation(anno); - } - - /** - * Returns true if the typeCompound is a primary annotation for the type it targets (or lower - * bound if this is a type variable or wildcard ). If you think of a type as a tree-like structure - * then a nested type any type that is not the root. E.g. {@code @T List< @N String>}, @T is on a - * top-level NON-nested type where as the annotation @N is on a nested type. - * - * @param typeCompound the type compound to inspect - * @return true if typeCompound is placed on a nested type, false otherwise - */ - static boolean isOnComponentType(Attribute.TypeCompound typeCompound) { - return !typeCompound.position.location.isEmpty(); - } - - /** - * See the Type Annotation Specification on bounds - * (https://checkerframework.org/jsr308/specification/java-annotation-design.html). - * - *

          TypeAnnotationPositions have bound indices when they represent an upper bound on a - * TypeVariable. The index 0 ALWAYS refers to the superclass type. If that supertype is implied to - * be Object (because we didn't specify an extends) then the actual types will be offset by 1 - * (because index 0 is ALWAYS a class. - * - *

          Therefore, These indices will be offset by -1 if the first type in the bound is an interface - * which implies the specified type itself is an interface. - * - *

          Reminder: There will only be multiple bound types if the upperBound is an intersection. - * - * @param upperBoundTypes the list of upperBounds for the type with bound positions you wish to - * offset - * @return the bound offset for all TypeAnnotationPositions of TypeCompounds targeting these - * bounds - */ - static int getBoundIndexOffset(List upperBoundTypes) { - final int boundIndexOffset; - if (((Type) upperBoundTypes.get(0).getUnderlyingType()).isInterface()) { - boundIndexOffset = -1; - } else { - boundIndexOffset = 0; + + /** + * TypeCompounds are implementations of AnnotationMirror that are stored on Elements. Each type + * compound has a TypeAnnotationPosition which identifies, relative to the "root" of a type, + * where an annotation should be placed. This method adds all of the given TypeCompounds to the + * correct location on type by interpreting the TypeAnnotationPosition. + * + *

          Note: We handle all of the Element annotations on a type at once because we need to + * identify whether or not the element annotation in front of an unbound wildcard (e.g. {@code + * <@HERE ?>}) should apply to only the super bound or both the super bound and the extends + * bound. + * + * @see org.checkerframework.framework.util.element.ElementAnnotationUtil.WildcardBoundAnnos + * @param type the type in which annos should be placed + * @param annos all of the element annotations, TypeCompounds, for type + */ + static void annotateViaTypeAnnoPosition( + AnnotatedTypeMirror type, Collection annos) + throws UnexpectedAnnotationLocationException { + IdentityHashMap wildcardToAnnos = + new IdentityHashMap<>(); + for (TypeCompound anno : annos) { + AnnotatedTypeMirror target = + getTypeAtLocation(type, anno.position.location, anno, false); + if (target.getKind() == TypeKind.WILDCARD) { + addWildcardToBoundMap((AnnotatedWildcardType) target, anno, wildcardToAnnos); + } else { + target.addAnnotation(anno); + } + } + + for (WildcardBoundAnnos wildcardAnnos : wildcardToAnnos.values()) { + wildcardAnnos.apply(); + } } - return boundIndexOffset; - } - - /** - * Overload of getTypeAtLocation with default values null/false for the annotation and array - * component flag, to make usage easier. Default visibility to allow usage within package. - */ - static AnnotatedTypeMirror getTypeAtLocation( - AnnotatedTypeMirror type, List location) - throws UnexpectedAnnotationLocationException { - return getTypeAtLocation(type, location, null, false); - } - - /** - * Given a TypePath into a type, return the component type that is located at the end of the - * TypePath. - * - * @param type a type containing the type specified by location - * @param location a type path into type - * @param anno an annotation to be applied to the inner types of a declared type if the declared - * type is itself a component type of an array - * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of - * some array type - * @return the type specified by location - */ - private static AnnotatedTypeMirror getTypeAtLocation( - AnnotatedTypeMirror type, - List location, - @Nullable TypeCompound anno, - boolean isComponentTypeOfArray) - throws UnexpectedAnnotationLocationException { - if (location.isEmpty() && type.getKind() != TypeKind.DECLARED) { - // An annotation with an empty type path on a declared type applies to the outermost - // enclosing type. This logic is handled together with non-empty type paths in - // getLocationTypeADT. - // For other kinds of types, no work is required for an empty type path. - return type; + /** + * Creates an entry in wildcardToAnnos for wildcard if one does not already exists. Adds anno to + * the WildcardBoundAnnos object for wildcard. + */ + private static void addWildcardToBoundMap( + AnnotatedWildcardType wildcard, + TypeCompound anno, + Map wildcardToAnnos) { + WildcardBoundAnnos boundAnnos = + wildcardToAnnos.computeIfAbsent(wildcard, WildcardBoundAnnos::new); + boundAnnos.addAnnotation(anno); } - switch (type.getKind()) { - case NULL: - return getLocationTypeANT((AnnotatedNullType) type, location); - case DECLARED: - return getLocationTypeADT( - (AnnotatedDeclaredType) type, location, anno, isComponentTypeOfArray); - case WILDCARD: - return getLocationTypeAWT((AnnotatedWildcardType) type, location); - case ARRAY: - return getLocationTypeAAT((AnnotatedArrayType) type, location, anno); - case UNION: - return getLocationTypeAUT((AnnotatedUnionType) type, location); - case INTERSECTION: - return getLocationTypeAIT((AnnotatedIntersectionType) type, location); - default: - // Raise an error for all other types below. + + /** + * Returns true if the typeCompound is a primary annotation for the type it targets (or lower + * bound if this is a type variable or wildcard ). If you think of a type as a tree-like + * structure then a nested type any type that is not the root. E.g. {@code @T List< @N + * String>}, @T is on a top-level NON-nested type where as the annotation @N is on a nested + * type. + * + * @param typeCompound the type compound to inspect + * @return true if typeCompound is placed on a nested type, false otherwise + */ + static boolean isOnComponentType(Attribute.TypeCompound typeCompound) { + return !typeCompound.position.location.isEmpty(); } - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getTypeAtLocation: " - + "unexpected annotation with location found for type: %s (kind: %s) location: ", - type, type.getKind(), location); - } - - /** - * Given a TypePath into a declared type, return the component type that is located at the end of - * the TypePath. - * - * @param type a type containing the type specified by location - * @param location a type path into type - * @param anno an annotation to be applied to the inner types of the declared type if the declared - * type is itself a component type of an array - * @param isComponentTypeOfArray indicates whether the type under analysis is a component type of - * some array type - * @return the type specified by location - */ - @SuppressWarnings("JdkObsolete") // error is issued on every operation, must suppress here - private static AnnotatedTypeMirror getLocationTypeADT( - AnnotatedDeclaredType type, - List location, - TypeCompound anno, - boolean isComponentTypeOfArray) - throws UnexpectedAnnotationLocationException { - // List order by outermost type to innermost type. - ArrayDeque outerToInner = new ArrayDeque<>(); - AnnotatedDeclaredType enclosing = type; - while (enclosing != null) { - outerToInner.addFirst(enclosing); - enclosing = enclosing.getEnclosingType(); + + /** + * See the Type Annotation Specification on bounds + * (https://checkerframework.org/jsr308/specification/java-annotation-design.html). + * + *

          TypeAnnotationPositions have bound indices when they represent an upper bound on a + * TypeVariable. The index 0 ALWAYS refers to the superclass type. If that supertype is implied + * to be Object (because we didn't specify an extends) then the actual types will be offset by 1 + * (because index 0 is ALWAYS a class. + * + *

          Therefore, These indices will be offset by -1 if the first type in the bound is an + * interface which implies the specified type itself is an interface. + * + *

          Reminder: There will only be multiple bound types if the upperBound is an intersection. + * + * @param upperBoundTypes the list of upperBounds for the type with bound positions you wish to + * offset + * @return the bound offset for all TypeAnnotationPositions of TypeCompounds targeting these + * bounds + */ + static int getBoundIndexOffset(List upperBoundTypes) { + final int boundIndexOffset; + if (((Type) upperBoundTypes.get(0).getUnderlyingType()).isInterface()) { + boundIndexOffset = -1; + } else { + boundIndexOffset = 0; + } + + return boundIndexOffset; } - // If the AnnotatedDeclaredType is a component of an array type, then apply anno to all - // possible inner types. - // NOTE: This workaround can be removed once - // https://bugs.openjdk.org/browse/JDK-8208470 is fixed - // The number of enclosing types is outerToInner.size() - 1; there only is - // work to do if outerToInner contains more than one element. - if (anno != null && isComponentTypeOfArray && location.isEmpty() && outerToInner.size() > 1) { - ArrayDeque innerTypes = new ArrayDeque<>(outerToInner); - innerTypes.removeFirst(); - while (!innerTypes.isEmpty()) { - innerTypes.removeFirst().addAnnotation(anno); - } + /** + * Overload of getTypeAtLocation with default values null/false for the annotation and array + * component flag, to make usage easier. Default visibility to allow usage within package. + */ + static AnnotatedTypeMirror getTypeAtLocation( + AnnotatedTypeMirror type, List location) + throws UnexpectedAnnotationLocationException { + return getTypeAtLocation(type, location, null, false); } - // Create a linked list of the location, so removing the first element is easier. - // Also, the tail() operation wouldn't work with a Deque. - @SuppressWarnings("JdkObsolete") - LinkedList tailOfLocations = new LinkedList<>(location); - boolean error = false; - while (!tailOfLocations.isEmpty()) { - TypePathEntry currentLocation = tailOfLocations.removeFirst(); - switch (currentLocation.tag) { - case INNER_TYPE: - outerToInner.removeFirst(); - break; - case TYPE_ARGUMENT: - AnnotatedDeclaredType innerType = outerToInner.getFirst(); - if (currentLocation.arg < innerType.getTypeArguments().size()) { - AnnotatedTypeMirror typeArg = innerType.getTypeArguments().get(currentLocation.arg); - return getTypeAtLocation(typeArg, tailOfLocations); - } else { - error = true; - break; - } - default: - error = true; - } - if (error) { - break; - } + /** + * Given a TypePath into a type, return the component type that is located at the end of the + * TypePath. + * + * @param type a type containing the type specified by location + * @param location a type path into type + * @param anno an annotation to be applied to the inner types of a declared type if the declared + * type is itself a component type of an array + * @param isComponentTypeOfArray indicates whether the type under analysis is a component type + * of some array type + * @return the type specified by location + */ + private static AnnotatedTypeMirror getTypeAtLocation( + AnnotatedTypeMirror type, + List location, + @Nullable TypeCompound anno, + boolean isComponentTypeOfArray) + throws UnexpectedAnnotationLocationException { + if (location.isEmpty() && type.getKind() != TypeKind.DECLARED) { + // An annotation with an empty type path on a declared type applies to the outermost + // enclosing type. This logic is handled together with non-empty type paths in + // getLocationTypeADT. + // For other kinds of types, no work is required for an empty type path. + return type; + } + switch (type.getKind()) { + case NULL: + return getLocationTypeANT((AnnotatedNullType) type, location); + case DECLARED: + return getLocationTypeADT( + (AnnotatedDeclaredType) type, location, anno, isComponentTypeOfArray); + case WILDCARD: + return getLocationTypeAWT((AnnotatedWildcardType) type, location); + case ARRAY: + return getLocationTypeAAT((AnnotatedArrayType) type, location, anno); + case UNION: + return getLocationTypeAUT((AnnotatedUnionType) type, location); + case INTERSECTION: + return getLocationTypeAIT((AnnotatedIntersectionType) type, location); + default: + // Raise an error for all other types below. + } + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getTypeAtLocation: " + + "unexpected annotation with location found for type: %s (kind: %s) location: ", + type, type.getKind(), location); } - if (outerToInner.isEmpty() || error) { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocationTypeADT: invalid location %s for type: %s", - location, type); + /** + * Given a TypePath into a declared type, return the component type that is located at the end + * of the TypePath. + * + * @param type a type containing the type specified by location + * @param location a type path into type + * @param anno an annotation to be applied to the inner types of the declared type if the + * declared type is itself a component type of an array + * @param isComponentTypeOfArray indicates whether the type under analysis is a component type + * of some array type + * @return the type specified by location + */ + @SuppressWarnings("JdkObsolete") // error is issued on every operation, must suppress here + private static AnnotatedTypeMirror getLocationTypeADT( + AnnotatedDeclaredType type, + List location, + TypeCompound anno, + boolean isComponentTypeOfArray) + throws UnexpectedAnnotationLocationException { + // List order by outermost type to innermost type. + ArrayDeque outerToInner = new ArrayDeque<>(); + AnnotatedDeclaredType enclosing = type; + while (enclosing != null) { + outerToInner.addFirst(enclosing); + enclosing = enclosing.getEnclosingType(); + } + + // If the AnnotatedDeclaredType is a component of an array type, then apply anno to all + // possible inner types. + // NOTE: This workaround can be removed once + // https://bugs.openjdk.org/browse/JDK-8208470 is fixed + // The number of enclosing types is outerToInner.size() - 1; there only is + // work to do if outerToInner contains more than one element. + if (anno != null + && isComponentTypeOfArray + && location.isEmpty() + && outerToInner.size() > 1) { + ArrayDeque innerTypes = new ArrayDeque<>(outerToInner); + innerTypes.removeFirst(); + while (!innerTypes.isEmpty()) { + innerTypes.removeFirst().addAnnotation(anno); + } + } + + // Create a linked list of the location, so removing the first element is easier. + // Also, the tail() operation wouldn't work with a Deque. + @SuppressWarnings("JdkObsolete") + LinkedList tailOfLocations = new LinkedList<>(location); + boolean error = false; + while (!tailOfLocations.isEmpty()) { + TypePathEntry currentLocation = tailOfLocations.removeFirst(); + switch (currentLocation.tag) { + case INNER_TYPE: + outerToInner.removeFirst(); + break; + case TYPE_ARGUMENT: + AnnotatedDeclaredType innerType = outerToInner.getFirst(); + if (currentLocation.arg < innerType.getTypeArguments().size()) { + AnnotatedTypeMirror typeArg = + innerType.getTypeArguments().get(currentLocation.arg); + return getTypeAtLocation(typeArg, tailOfLocations); + } else { + error = true; + break; + } + default: + error = true; + } + if (error) { + break; + } + } + + if (outerToInner.isEmpty() || error) { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocationTypeADT: invalid location %s for type: %s", + location, type); + } + + return outerToInner.getFirst(); } - return outerToInner.getFirst(); - } + private static AnnotatedTypeMirror getLocationTypeANT( + AnnotatedNullType type, List location) + throws UnexpectedAnnotationLocationException { + if (location.size() == 1 && location.get(0).tag == TypePathEntryKind.TYPE_ARGUMENT) { + return type; + } - private static AnnotatedTypeMirror getLocationTypeANT( - AnnotatedNullType type, List location) - throws UnexpectedAnnotationLocationException { - if (location.size() == 1 && location.get(0).tag == TypePathEntryKind.TYPE_ARGUMENT) { - return type; + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocationTypeANT: " + "invalid location %s for type: %s ", + location, type); } - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocationTypeANT: " + "invalid location %s for type: %s ", - location, type); - } + private static AnnotatedTypeMirror getLocationTypeAWT( + final AnnotatedWildcardType type, List location) + throws UnexpectedAnnotationLocationException { - private static AnnotatedTypeMirror getLocationTypeAWT( - final AnnotatedWildcardType type, List location) - throws UnexpectedAnnotationLocationException { + // the last step into the Wildcard type is handled in WildcardToBoundAnnos.addAnnotation + if (location.size() == 1) { + return type; + } - // the last step into the Wildcard type is handled in WildcardToBoundAnnos.addAnnotation - if (location.size() == 1) { - return type; + if (!location.isEmpty() + && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.WILDCARD) { + if (AnnotatedTypes.hasExplicitExtendsBound(type)) { + return getTypeAtLocation(type.getExtendsBound(), tail(location)); + } else if (AnnotatedTypes.hasExplicitSuperBound(type)) { + return getTypeAtLocation(type.getSuperBound(), tail(location)); + } else { + return getTypeAtLocation(type.getExtendsBound(), tail(location)); + } + } else { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocationTypeAWT: " + + "invalid location %s for type: %s ", + location, type); + } } - if (!location.isEmpty() - && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.WILDCARD) { - if (AnnotatedTypes.hasExplicitExtendsBound(type)) { - return getTypeAtLocation(type.getExtendsBound(), tail(location)); - } else if (AnnotatedTypes.hasExplicitSuperBound(type)) { - return getTypeAtLocation(type.getSuperBound(), tail(location)); - } else { - return getTypeAtLocation(type.getExtendsBound(), tail(location)); - } - } else { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocationTypeAWT: " + "invalid location %s for type: %s ", - location, type); - } - } - - /** - * When we have an (e.g. @Odd int @NonNull []) the type-annotation position of the array - * annotation (@NonNull) is really the outermost type in the TypeAnnotationPosition and it will - * NOT have TypePathEntryKind.ARRAY at the end of its position. The position of the component type - * (@Odd) is considered deeper in the type and therefore has the TypePathEntryKind.ARRAY in its - * position. - */ - private static AnnotatedTypeMirror getLocationTypeAAT( - AnnotatedArrayType type, - List location, - TypeCompound anno) - throws UnexpectedAnnotationLocationException { - if (location.size() >= 1 - && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) { - AnnotatedTypeMirror comptype = type.getComponentType(); - return getTypeAtLocation(comptype, tail(location), anno, true); - } else { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.annotateAAT: " + "invalid location %s for type: %s ", - location, type); + /** + * When we have an (e.g. @Odd int @NonNull []) the type-annotation position of the array + * annotation (@NonNull) is really the outermost type in the TypeAnnotationPosition and it will + * NOT have TypePathEntryKind.ARRAY at the end of its position. The position of the component + * type (@Odd) is considered deeper in the type and therefore has the TypePathEntryKind.ARRAY in + * its position. + */ + private static AnnotatedTypeMirror getLocationTypeAAT( + AnnotatedArrayType type, + List location, + TypeCompound anno) + throws UnexpectedAnnotationLocationException { + if (location.size() >= 1 + && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) { + AnnotatedTypeMirror comptype = type.getComponentType(); + return getTypeAtLocation(comptype, tail(location), anno, true); + } else { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.annotateAAT: " + "invalid location %s for type: %s ", + location, type); + } } - } - - /* - * TODO: this case should never occur! - * A union type can only occur in special locations, e.g. for exception - * parameters. The EXCEPTION_PARAMETER TartetType should be used to - * decide which of the alternatives in the union to annotate. - * Only the TypePathEntry is not enough. - * As a hack, always annotate the first alternative. - */ - private static AnnotatedTypeMirror getLocationTypeAUT( - AnnotatedUnionType type, List location) - throws UnexpectedAnnotationLocationException { - AnnotatedTypeMirror comptype = type.getAlternatives().get(0); - return getTypeAtLocation(comptype, location); - } - - /** Intersection types use the TYPE_ARGUMENT index to separate the individual types. */ - private static AnnotatedTypeMirror getLocationTypeAIT( - AnnotatedIntersectionType type, List location) - throws UnexpectedAnnotationLocationException { - if (location.size() >= 1 - && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT) { - AnnotatedTypeMirror bound = type.getBounds().get(location.get(0).arg); - return getTypeAtLocation(bound, tail(location)); - } else { - throw new UnexpectedAnnotationLocationException( - "ElementAnnotationUtil.getLocatonTypeAIT: invalid location %s for type: %s ", - location, type); + + /* + * TODO: this case should never occur! + * A union type can only occur in special locations, e.g. for exception + * parameters. The EXCEPTION_PARAMETER TartetType should be used to + * decide which of the alternatives in the union to annotate. + * Only the TypePathEntry is not enough. + * As a hack, always annotate the first alternative. + */ + private static AnnotatedTypeMirror getLocationTypeAUT( + AnnotatedUnionType type, List location) + throws UnexpectedAnnotationLocationException { + AnnotatedTypeMirror comptype = type.getAlternatives().get(0); + return getTypeAtLocation(comptype, location); } - } - private static List tail(List list) { - return list.subList(1, list.size()); - } + /** Intersection types use the TYPE_ARGUMENT index to separate the individual types. */ + private static AnnotatedTypeMirror getLocationTypeAIT( + AnnotatedIntersectionType type, List location) + throws UnexpectedAnnotationLocationException { + if (location.size() >= 1 + && location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT) { + AnnotatedTypeMirror bound = type.getBounds().get(location.get(0).arg); + return getTypeAtLocation(bound, tail(location)); + } else { + throw new UnexpectedAnnotationLocationException( + "ElementAnnotationUtil.getLocatonTypeAIT: invalid location %s for type: %s ", + location, type); + } + } - /** Exception indicating an invalid location for an annotation was found. */ - @SuppressWarnings("serial") - public static class UnexpectedAnnotationLocationException extends Exception { + private static List tail(List list) { + return list.subList(1, list.size()); + } - /** - * Creates an UnexpectedAnnotationLocationException. - * - * @param format format string - * @param args arguments to the format string - */ - @FormatMethod - private UnexpectedAnnotationLocationException(String format, Object... args) { - super(String.format(format, args)); + /** Exception indicating an invalid location for an annotation was found. */ + @SuppressWarnings("serial") + public static class UnexpectedAnnotationLocationException extends Exception { + + /** + * Creates an UnexpectedAnnotationLocationException. + * + * @param format format string + * @param args arguments to the format string + */ + @FormatMethod + private UnexpectedAnnotationLocationException(String format, Object... args) { + super(String.format(format, args)); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java index f98e6770900..1bbb6d73c22 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/IndexedElementAnnotationApplier.java @@ -2,10 +2,13 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.TargetType; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; + import java.util.List; import java.util.Map; + import javax.lang.model.element.Element; -import org.checkerframework.framework.type.AnnotatedTypeMirror; /** * Some Elements are members of a list (formal method parameters and type parameters). This class @@ -15,48 +18,50 @@ */ abstract class IndexedElementAnnotationApplier extends TargetedElementAnnotationApplier { - protected IndexedElementAnnotationApplier(AnnotatedTypeMirror type, Element element) { - super(type, element); - } - - /** The index of element in the list of elements that contains it. */ - public abstract int getElementIndex(); - - /** - * A TypeAnnotationPosition has a number of different indexes (type_index, bound_index, - * param_index) Return the index we are interested in. If offsetting needs to be done it should be - * done in getElementIndex not here. (see ElementAnnotationUtils.getBoundIndexOffset ) - * - * @param anno an annotation we might wish to apply - * @return the index value this applier compares against the getElementIndex - */ - public abstract int getTypeCompoundIndex(Attribute.TypeCompound anno); - - @Override - protected Map> sift( - Iterable typeCompounds) { - Map> targetClassToAnnos = super.sift(typeCompounds); - - List targeted = targetClassToAnnos.get(TargetClass.TARGETED); - List valid = targetClassToAnnos.get(TargetClass.VALID); - - int paramIndex = getElementIndex(); - - // filter out annotations in targeted that don't have the correct parameter index. (i.e the - // one's that are on the same method but don't pertain to the parameter element being - // processed, see class comments ). Place these annotations into the valid list. - int i = 0; - while (i < targeted.size()) { - Attribute.TypeCompound target = targeted.get(i); - // Annotations on parameters to record constructors are marked as fields so - // getTypeCompoundIndex does not return paramIndex. - if (target.position.type != TargetType.FIELD && getTypeCompoundIndex(target) != paramIndex) { - valid.add(targeted.remove(i)); - } else { - ++i; - } + protected IndexedElementAnnotationApplier(AnnotatedTypeMirror type, Element element) { + super(type, element); } - return targetClassToAnnos; - } + /** The index of element in the list of elements that contains it. */ + public abstract int getElementIndex(); + + /** + * A TypeAnnotationPosition has a number of different indexes (type_index, bound_index, + * param_index) Return the index we are interested in. If offsetting needs to be done it should + * be done in getElementIndex not here. (see ElementAnnotationUtils.getBoundIndexOffset ) + * + * @param anno an annotation we might wish to apply + * @return the index value this applier compares against the getElementIndex + */ + public abstract int getTypeCompoundIndex(Attribute.TypeCompound anno); + + @Override + protected Map> sift( + Iterable typeCompounds) { + Map> targetClassToAnnos = + super.sift(typeCompounds); + + List targeted = targetClassToAnnos.get(TargetClass.TARGETED); + List valid = targetClassToAnnos.get(TargetClass.VALID); + + int paramIndex = getElementIndex(); + + // filter out annotations in targeted that don't have the correct parameter index. (i.e the + // one's that are on the same method but don't pertain to the parameter element being + // processed, see class comments ). Place these annotations into the valid list. + int i = 0; + while (i < targeted.size()) { + Attribute.TypeCompound target = targeted.get(i); + // Annotations on parameters to record constructors are marked as fields so + // getTypeCompoundIndex does not return paramIndex. + if (target.position.type != TargetType.FIELD + && getTypeCompoundIndex(target) != paramIndex) { + valid.add(targeted.remove(i)); + } else { + ++i; + } + } + + return targetClassToAnnos; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java index 82f7cff81cf..14bd8b248e5 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java @@ -5,11 +5,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; import com.sun.tools.javac.code.TypeAnnotationPosition; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.lang.model.element.Element; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -19,236 +15,249 @@ import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.StringsPlume; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.Element; + /** * Adds annotations from element to the return type, formal parameter types, type parameters, and * throws clauses of the AnnotatedExecutableType type. */ public class MethodApplier extends TargetedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new MethodApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * Returns true if typeMirror represents an {@link AnnotatedExecutableType} and element represents - * a {@link Symbol.MethodSymbol}. - * - * @param typeMirror the type to test - * @param element the corresponding element - * @return true if the MethodApplier accepts the type and element - */ - public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { - return element instanceof Symbol.MethodSymbol && typeMirror instanceof AnnotatedExecutableType; - } - - /** The type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** Method being annotated, this symbol contains all relevant annotations. */ - private final Symbol.MethodSymbol methodSymbol; - - /** Method being annotated. */ - private final AnnotatedExecutableType methodType; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ MethodApplier( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - this.atypeFactory = atypeFactory; - this.methodSymbol = (Symbol.MethodSymbol) element; - this.methodType = (AnnotatedExecutableType) type; - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] {TargetType.METHOD_RECEIVER, TargetType.METHOD_RETURN, TargetType.THROWS}; - - /** - * Returns receiver, returns, and throws. See extract and apply as we also annotate type params. - * - * @return receiver, returns, and throws - */ - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - TargetType.METHOD_TYPE_PARAMETER, - TargetType.METHOD_TYPE_PARAMETER_BOUND, - TargetType.METHOD_FORMAL_PARAMETER, - // TODO: from generic anonymous classes; remove when - // we can depend on only seeing classfiles that were - // generated by a javac that contains a fix for: - // https://bugs.openjdk.org/browse/JDK-8198945 - TargetType.CLASS_EXTENDS, - // TODO: Test case from Issue 3277 produces invalid position. - // Ignore until this javac bug is fixed: - // https://bugs.openjdk.org/browse/JDK-8233945 - TargetType.UNKNOWN, - // Annotations on parameters to record constructors are marked as fields. - TargetType.FIELD - }; - - /** - * Returns all possible annotation positions for a method except those in annotatedTargets. - * - * @return all possible annotation positions for a method except those in annotatedTargets - */ - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the annotations on the method symbol (element). - * - * @return the annotations on the method symbol (element) - */ - @Override - protected Iterable getRawTypeAttributes() { - return methodSymbol.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return MethodApplier.accepts(type, element); - } - - /** - * Sets the method's element, annotates its return type, parameters, type parameters, and throws - * annotations. - */ - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - methodType.setElement(methodSymbol); // Preserves previous behavior - - // Add declaration annotations to the return type if - if (methodType.getReturnType() instanceof AnnotatedTypeVariable) { - applyTypeVarUseOnReturnType(); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new MethodApplier(type, element, atypeFactory).extractAndApply(); } - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - methodType.getReturnType(), methodSymbol.getAnnotationMirrors()); - - List params = methodType.getParameterTypes(); - for (int i = 0; i < params.size(); ++i) { - // Add declaration annotations to the parameter type - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - params.get(i), methodSymbol.getParameters().get(i).getAnnotationMirrors()); + + /** + * Returns true if typeMirror represents an {@link AnnotatedExecutableType} and element + * represents a {@link Symbol.MethodSymbol}. + * + * @param typeMirror the type to test + * @param element the corresponding element + * @return true if the MethodApplier accepts the type and element + */ + public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { + return element instanceof Symbol.MethodSymbol + && typeMirror instanceof AnnotatedExecutableType; } - // ensures that we check that there are only valid target types on this class, there are no - // "invalid" locations - super.extractAndApply(); - - ElementAnnotationUtil.applyAllElementAnnotations( - methodType.getParameterTypes(), methodSymbol.getParameters(), atypeFactory); - ElementAnnotationUtil.applyAllElementAnnotations( - methodType.getTypeVariables(), methodSymbol.getTypeParameters(), atypeFactory); - } - - // NOTE that these are the only locations not handled elsewhere, otherwise we call apply - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - List unmatched = new ArrayList<>(); - Map> targetTypeToAnno = - ElementAnnotationUtil.partitionByTargetType( - targeted, - unmatched, - TargetType.METHOD_RECEIVER, - TargetType.METHOD_RETURN, - TargetType.THROWS); - - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - methodType.getReceiverType(), targetTypeToAnno.get(TargetType.METHOD_RECEIVER)); - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - methodType.getReturnType(), targetTypeToAnno.get(TargetType.METHOD_RETURN)); - applyThrowsAnnotations(targetTypeToAnno.get(TargetType.THROWS)); - - if (!unmatched.isEmpty()) { - throw new BugInCF( - "Unexpected annotations ( " - + StringsPlume.join(",", unmatched) - + " ) for" - + "type ( " - + type - + " ) and element ( " - + element - + " ) "); + /** The type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** Method being annotated, this symbol contains all relevant annotations. */ + private final Symbol.MethodSymbol methodSymbol; + + /** Method being annotated. */ + private final AnnotatedExecutableType methodType; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ MethodApplier( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + this.atypeFactory = atypeFactory; + this.methodSymbol = (Symbol.MethodSymbol) element; + this.methodType = (AnnotatedExecutableType) type; } - } - - /** For each thrown type, collect all the annotations for that type and apply them. */ - private void applyThrowsAnnotations(List annos) - throws UnexpectedAnnotationLocationException { - List thrown = methodType.getThrownTypes(); - if (thrown.isEmpty()) { - return; + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] { + TargetType.METHOD_RECEIVER, TargetType.METHOD_RETURN, TargetType.THROWS + }; + + /** + * Returns receiver, returns, and throws. See extract and apply as we also annotate type params. + * + * @return receiver, returns, and throws + */ + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; } - Map> typeToAnnos = new LinkedHashMap<>(); - for (AnnotatedTypeMirror thrownType : thrown) { - typeToAnnos.put(thrownType, new ArrayList<>()); + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + TargetType.METHOD_TYPE_PARAMETER, + TargetType.METHOD_TYPE_PARAMETER_BOUND, + TargetType.METHOD_FORMAL_PARAMETER, + // TODO: from generic anonymous classes; remove when + // we can depend on only seeing classfiles that were + // generated by a javac that contains a fix for: + // https://bugs.openjdk.org/browse/JDK-8198945 + TargetType.CLASS_EXTENDS, + // TODO: Test case from Issue 3277 produces invalid position. + // Ignore until this javac bug is fixed: + // https://bugs.openjdk.org/browse/JDK-8233945 + TargetType.UNKNOWN, + // Annotations on parameters to record constructors are marked as fields. + TargetType.FIELD + }; + + /** + * Returns all possible annotation positions for a method except those in annotatedTargets. + * + * @return all possible annotation positions for a method except those in annotatedTargets + */ + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the annotations on the method symbol (element). + * + * @return the annotations on the method symbol (element) + */ + @Override + protected Iterable getRawTypeAttributes() { + return methodSymbol.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return MethodApplier.accepts(type, element); + } + + /** + * Sets the method's element, annotates its return type, parameters, type parameters, and throws + * annotations. + */ + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + methodType.setElement(methodSymbol); // Preserves previous behavior + + // Add declaration annotations to the return type if + if (methodType.getReturnType() instanceof AnnotatedTypeVariable) { + applyTypeVarUseOnReturnType(); + } + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + methodType.getReturnType(), methodSymbol.getAnnotationMirrors()); + + List params = methodType.getParameterTypes(); + for (int i = 0; i < params.size(); ++i) { + // Add declaration annotations to the parameter type + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + params.get(i), methodSymbol.getParameters().get(i).getAnnotationMirrors()); + } + + // ensures that we check that there are only valid target types on this class, there are no + // "invalid" locations + super.extractAndApply(); + + ElementAnnotationUtil.applyAllElementAnnotations( + methodType.getParameterTypes(), methodSymbol.getParameters(), atypeFactory); + ElementAnnotationUtil.applyAllElementAnnotations( + methodType.getTypeVariables(), methodSymbol.getTypeParameters(), atypeFactory); } - for (TypeCompound anno : annos) { - TypeAnnotationPosition annoPos = anno.position; - if (annoPos.type_index >= 0 && annoPos.type_index < thrown.size()) { - AnnotatedTypeMirror thrownType = thrown.get(annoPos.type_index); - typeToAnnos.get(thrownType).add(anno); - } else { - throw new BugInCF( - "MethodApplier.applyThrowsAnnotation: " - + "invalid throws index " - + annoPos.type_index - + " for annotation: " - + anno - + " for element: " - + ElementUtils.getQualifiedName(element)); - } + // NOTE that these are the only locations not handled elsewhere, otherwise we call apply + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + List unmatched = new ArrayList<>(); + Map> targetTypeToAnno = + ElementAnnotationUtil.partitionByTargetType( + targeted, + unmatched, + TargetType.METHOD_RECEIVER, + TargetType.METHOD_RETURN, + TargetType.THROWS); + + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + methodType.getReceiverType(), targetTypeToAnno.get(TargetType.METHOD_RECEIVER)); + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + methodType.getReturnType(), targetTypeToAnno.get(TargetType.METHOD_RETURN)); + applyThrowsAnnotations(targetTypeToAnno.get(TargetType.THROWS)); + + if (!unmatched.isEmpty()) { + throw new BugInCF( + "Unexpected annotations ( " + + StringsPlume.join(",", unmatched) + + " ) for" + + "type ( " + + type + + " ) and element ( " + + element + + " ) "); + } + } + + /** For each thrown type, collect all the annotations for that type and apply them. */ + private void applyThrowsAnnotations(List annos) + throws UnexpectedAnnotationLocationException { + List thrown = methodType.getThrownTypes(); + if (thrown.isEmpty()) { + return; + } + + Map> typeToAnnos = new LinkedHashMap<>(); + for (AnnotatedTypeMirror thrownType : thrown) { + typeToAnnos.put(thrownType, new ArrayList<>()); + } + + for (TypeCompound anno : annos) { + TypeAnnotationPosition annoPos = anno.position; + if (annoPos.type_index >= 0 && annoPos.type_index < thrown.size()) { + AnnotatedTypeMirror thrownType = thrown.get(annoPos.type_index); + typeToAnnos.get(thrownType).add(anno); + } else { + throw new BugInCF( + "MethodApplier.applyThrowsAnnotation: " + + "invalid throws index " + + annoPos.type_index + + " for annotation: " + + anno + + " for element: " + + ElementUtils.getQualifiedName(element)); + } + } + + for (Map.Entry> typeToAnno : + typeToAnnos.entrySet()) { + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + typeToAnno.getKey(), typeToAnno.getValue()); + } } - for (Map.Entry> typeToAnno : typeToAnnos.entrySet()) { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(typeToAnno.getKey(), typeToAnno.getValue()); + /** + * If the return type is a use of a type variable first apply the bound annotations from the + * type variables declaration. + */ + private void applyTypeVarUseOnReturnType() throws UnexpectedAnnotationLocationException { + new TypeVarUseApplier(methodType.getReturnType(), methodSymbol, atypeFactory) + .extractAndApply(); } - } - - /** - * If the return type is a use of a type variable first apply the bound annotations from the type - * variables declaration. - */ - private void applyTypeVarUseOnReturnType() throws UnexpectedAnnotationLocationException { - new TypeVarUseApplier(methodType.getReturnType(), methodSymbol, atypeFactory).extractAndApply(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java index 16e5034e8d6..79cf2f9c9db 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/MethodTypeParamApplier.java @@ -3,147 +3,149 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; + /** Applies the annotations present for a method type parameter onto an AnnotatedTypeVariable. */ public class MethodTypeParamApplier extends TypeParamElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new MethodTypeParamApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * Returns true if element represents a type parameter for a method. - * - * @param type ignored - * @param element the element that might be a type parameter for a method - * @return true if element represents a type parameter for a method - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return element.getKind() == ElementKind.TYPE_PARAMETER - && element.getEnclosingElement() instanceof Symbol.MethodSymbol; - } - - /** The enclosing method. */ - private final Symbol.MethodSymbol enclosingMethod; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory /*package-private - */ - /*package-private*/ MethodTypeParamApplier( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element, atypeFactory); - - if (!(element.getEnclosingElement() instanceof Symbol.MethodSymbol)) { - throw new BugInCF( - "TypeParameter not enclosed by method? Type( " - + type - + " ) " - + "Element ( " - + element - + " ) "); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new MethodTypeParamApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * Returns true if element represents a type parameter for a method. + * + * @param type ignored + * @param element the element that might be a type parameter for a method + * @return true if element represents a type parameter for a method + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return element.getKind() == ElementKind.TYPE_PARAMETER + && element.getEnclosingElement() instanceof Symbol.MethodSymbol; + } + + /** The enclosing method. */ + private final Symbol.MethodSymbol enclosingMethod; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory /*package-private + */ + /*package-private*/ MethodTypeParamApplier( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element, atypeFactory); + + if (!(element.getEnclosingElement() instanceof Symbol.MethodSymbol)) { + throw new BugInCF( + "TypeParameter not enclosed by method? Type( " + + type + + " ) " + + "Element ( " + + element + + " ) "); + } + + enclosingMethod = (Symbol.MethodSymbol) element.getEnclosingElement(); } - enclosingMethod = (Symbol.MethodSymbol) element.getEnclosingElement(); - } - - /** - * Returns TargetType.METHOD_TYPE_PARAMETER. - * - * @return TargetType.METHOD_TYPE_PARAMETER - */ - @Override - protected TargetType lowerBoundTarget() { - return TargetType.METHOD_TYPE_PARAMETER; - } - - /** - * Returns TargetType.METHOD_TYPE_PARAMETER_BOUND. - * - * @return TargetType.METHOD_TYPE_PARAMETER_BOUND - */ - @Override - protected TargetType upperBoundTarget() { - return TargetType.METHOD_TYPE_PARAMETER_BOUND; - } - - /** - * Returns the index of element in the type parameter list of its enclosing method. - * - * @return the index of element in the type parameter list of its enclosing method - */ - @Override - public int getElementIndex() { - return enclosingMethod.getTypeParameters().indexOf(element); - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.METHOD_RETURN, - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.METHOD_RECEIVER, - TargetType.THROWS, - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - // TODO: from generic anonymous classes; remove when - // we can depend on only seeing classfiles that were - // generated by a javac that contains a fix for: - // https://bugs.openjdk.org/browse/JDK-8198945 - TargetType.CLASS_EXTENDS, - // TODO: Test case from Issue 3277 produces invalid position. - // Ignore until this javac bug is fixed: - // https://bugs.openjdk.org/browse/JDK-8233945 - TargetType.UNKNOWN - }; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the TypeCompounds (annotations) of the declaring element. - * - * @return the TypeCompounds (annotations) of the declaring element - */ - @Override - protected Iterable getRawTypeAttributes() { - return enclosingMethod.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } + /** + * Returns TargetType.METHOD_TYPE_PARAMETER. + * + * @return TargetType.METHOD_TYPE_PARAMETER + */ + @Override + protected TargetType lowerBoundTarget() { + return TargetType.METHOD_TYPE_PARAMETER; + } + + /** + * Returns TargetType.METHOD_TYPE_PARAMETER_BOUND. + * + * @return TargetType.METHOD_TYPE_PARAMETER_BOUND + */ + @Override + protected TargetType upperBoundTarget() { + return TargetType.METHOD_TYPE_PARAMETER_BOUND; + } + + /** + * Returns the index of element in the type parameter list of its enclosing method. + * + * @return the index of element in the type parameter list of its enclosing method + */ + @Override + public int getElementIndex() { + return enclosingMethod.getTypeParameters().indexOf(element); + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.METHOD_RETURN, + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.METHOD_RECEIVER, + TargetType.THROWS, + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + // TODO: from generic anonymous classes; remove when + // we can depend on only seeing classfiles that were + // generated by a javac that contains a fix for: + // https://bugs.openjdk.org/browse/JDK-8198945 + TargetType.CLASS_EXTENDS, + // TODO: Test case from Issue 3277 produces invalid position. + // Ignore until this javac bug is fixed: + // https://bugs.openjdk.org/browse/JDK-8233945 + TargetType.UNKNOWN + }; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the TypeCompounds (annotations) of the declaring element. + * + * @return the TypeCompounds (annotations) of the declaring element + */ + @Override + protected Iterable getRawTypeAttributes() { + return enclosingMethod.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java index bdcc218ef8f..e5825069fa7 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ParamApplier.java @@ -7,12 +7,7 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.VariableElement; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -21,281 +16,290 @@ import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.IPair; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.VariableElement; + /** Adds annotations to one formal parameter of a method or lambda within a method. */ public class ParamApplier extends IndexedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type whose annotations to change - * @param element where to get annotations from - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new ParamApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * Returns true if element represents a parameter. - * - * @param type ignored - * @param element the element to test - * @return if the element represents a parameter - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return element.getKind() == ElementKind.PARAMETER; - } - - /** The enclosing method. */ - private final Symbol.MethodSymbol enclosingMethod; - - /** Whether this is a parameter to a lambda expression. */ - private final boolean isLambdaParam; - - /** The index of the lambda parameter, or null if isLambdaParam is false. */ - private final @Nullable Integer lambdaParamIndex; - - /** The corresponding lambda expression tree, or null if isLambdaParam is false. */ - private final @Nullable LambdaExpressionTree lambdaTree; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ ParamApplier( - AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - enclosingMethod = getParentMethod(element); - - if (enclosingMethod.getKind() != ElementKind.INSTANCE_INIT - && enclosingMethod.getKind() != ElementKind.STATIC_INIT - && enclosingMethod.getParameters().contains(element)) { - lambdaTree = null; - isLambdaParam = false; - lambdaParamIndex = null; - } else { - IPair paramToEnclosingLambda = - ElementAnnotationApplier.getParamAndLambdaTree(element, atypeFactory); - - if (paramToEnclosingLambda != null) { - VariableTree paramDecl = paramToEnclosingLambda.first; - lambdaTree = paramToEnclosingLambda.second; - isLambdaParam = true; - lambdaParamIndex = lambdaTree.getParameters().indexOf(paramDecl); - } else { - lambdaTree = null; - isLambdaParam = false; - lambdaParamIndex = null; - } + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type whose annotations to change + * @param element where to get annotations from + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new ParamApplier(type, element, atypeFactory).extractAndApply(); } - } - - /** - * Returns the index of element its parent method's parameter list. Integer.MIN_VALUE if the - * element is the receiver parameter. - * - * @return the index of element its parent method's parameter list. Integer.MIN_VALUE if the - * element is the receiver parameter - */ - @Override - public int getElementIndex() { - if (isLambdaParam) { - return lambdaParamIndex; + + /** + * Returns true if element represents a parameter. + * + * @param type ignored + * @param element the element to test + * @return if the element represents a parameter + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return element.getKind() == ElementKind.PARAMETER; } - if (isReceiver(element)) { - return Integer.MIN_VALUE; + /** The enclosing method. */ + private final Symbol.MethodSymbol enclosingMethod; + + /** Whether this is a parameter to a lambda expression. */ + private final boolean isLambdaParam; + + /** The index of the lambda parameter, or null if isLambdaParam is false. */ + private final @Nullable Integer lambdaParamIndex; + + /** The corresponding lambda expression tree, or null if isLambdaParam is false. */ + private final @Nullable LambdaExpressionTree lambdaTree; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ ParamApplier( + AnnotatedTypeMirror type, VariableElement element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + enclosingMethod = getParentMethod(element); + + if (enclosingMethod.getKind() != ElementKind.INSTANCE_INIT + && enclosingMethod.getKind() != ElementKind.STATIC_INIT + && enclosingMethod.getParameters().contains(element)) { + lambdaTree = null; + isLambdaParam = false; + lambdaParamIndex = null; + } else { + IPair paramToEnclosingLambda = + ElementAnnotationApplier.getParamAndLambdaTree(element, atypeFactory); + + if (paramToEnclosingLambda != null) { + VariableTree paramDecl = paramToEnclosingLambda.first; + lambdaTree = paramToEnclosingLambda.second; + isLambdaParam = true; + lambdaParamIndex = lambdaTree.getParameters().indexOf(paramDecl); + } else { + lambdaTree = null; + isLambdaParam = false; + lambdaParamIndex = null; + } + } } - int paramIndex = enclosingMethod.getParameters().indexOf(element); - if (paramIndex == -1) { - throw new BugInCF( - "Could not find parameter Element in parameter list. " - + "Parameter( " - + element - + " ) Parent ( " - + enclosingMethod - + " ) "); + /** + * Returns the index of element its parent method's parameter list. Integer.MIN_VALUE if the + * element is the receiver parameter. + * + * @return the index of element its parent method's parameter list. Integer.MIN_VALUE if the + * element is the receiver parameter + */ + @Override + public int getElementIndex() { + if (isLambdaParam) { + return lambdaParamIndex; + } + + if (isReceiver(element)) { + return Integer.MIN_VALUE; + } + + int paramIndex = enclosingMethod.getParameters().indexOf(element); + if (paramIndex == -1) { + throw new BugInCF( + "Could not find parameter Element in parameter list. " + + "Parameter( " + + element + + " ) Parent ( " + + enclosingMethod + + " ) "); + } + + return paramIndex; } - return paramIndex; - } - - /** - * Returns the parameter index of anno's TypeAnnotationPosition. - * - * @return the parameter index of anno's TypeAnnotationPosition - */ - @Override - public int getTypeCompoundIndex(Attribute.TypeCompound anno) { - return anno.getPosition().parameter_index; - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] { - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.METHOD_RECEIVER, - // Annotations on parameters to record constructors are marked as fields. - TargetType.FIELD - }; - - /** - * Returns {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER}. - * - * @return {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER} - */ - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.METHOD_RETURN, - TargetType.THROWS, - TargetType.METHOD_TYPE_PARAMETER, - TargetType.METHOD_TYPE_PARAMETER_BOUND, - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - // TODO: from generic anonymous classes; remove when - // we can depend on only seeing classfiles that were - // generated by a javac that contains a fix for: - // https://bugs.openjdk.org/browse/JDK-8198945 - TargetType.CLASS_EXTENDS - }; - - /** - * Returns any annotation TargetType that can be found on a method. - * - * @return any annotation TargetType that can be found on a method - */ - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the TypeCompounds (annotations) of the enclosing method for this parameter. - * - * @return the TypeCompounds (annotations) of the enclosing method for this parameter - */ - @Override - protected Iterable getRawTypeAttributes() { - return enclosingMethod.getRawTypeAttributes(); - } - - @Override - protected Map> sift( - Iterable typeCompounds) { - // this will sift out the annotations that do not have the right position index - Map> targetClassToAnnos = super.sift(typeCompounds); - - List targeted = targetClassToAnnos.get(TargetClass.TARGETED); - List valid = targetClassToAnnos.get(TargetClass.VALID); - - // if this is a lambdaParam, filter out from targeted those annos that apply to method - // formal parameters if this is a method formal param, filter out from targeted those annos - // that apply to lambdas - int i = 0; - while (i < targeted.size()) { - Tree onLambda = targeted.get(i).position.onLambda; - if (onLambda == null) { - if (!isLambdaParam) { - ++i; - } else { - valid.add(targeted.remove(i)); + /** + * Returns the parameter index of anno's TypeAnnotationPosition. + * + * @return the parameter index of anno's TypeAnnotationPosition + */ + @Override + public int getTypeCompoundIndex(Attribute.TypeCompound anno) { + return anno.getPosition().parameter_index; + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] { + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.METHOD_RECEIVER, + // Annotations on parameters to record constructors are marked as fields. + TargetType.FIELD + }; + + /** + * Returns {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER}. + * + * @return {TargetType.METHOD_FORMAL_PARAMETER, TargetType.METHOD_RECEIVER} + */ + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.METHOD_RETURN, + TargetType.THROWS, + TargetType.METHOD_TYPE_PARAMETER, + TargetType.METHOD_TYPE_PARAMETER_BOUND, + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + // TODO: from generic anonymous classes; remove when + // we can depend on only seeing classfiles that were + // generated by a javac that contains a fix for: + // https://bugs.openjdk.org/browse/JDK-8198945 + TargetType.CLASS_EXTENDS + }; + + /** + * Returns any annotation TargetType that can be found on a method. + * + * @return any annotation TargetType that can be found on a method + */ + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the TypeCompounds (annotations) of the enclosing method for this parameter. + * + * @return the TypeCompounds (annotations) of the enclosing method for this parameter + */ + @Override + protected Iterable getRawTypeAttributes() { + return enclosingMethod.getRawTypeAttributes(); + } + + @Override + protected Map> sift( + Iterable typeCompounds) { + // this will sift out the annotations that do not have the right position index + Map> targetClassToAnnos = + super.sift(typeCompounds); + + List targeted = targetClassToAnnos.get(TargetClass.TARGETED); + List valid = targetClassToAnnos.get(TargetClass.VALID); + + // if this is a lambdaParam, filter out from targeted those annos that apply to method + // formal parameters if this is a method formal param, filter out from targeted those annos + // that apply to lambdas + int i = 0; + while (i < targeted.size()) { + Tree onLambda = targeted.get(i).position.onLambda; + if (onLambda == null) { + if (!isLambdaParam) { + ++i; + } else { + valid.add(targeted.remove(i)); + } + } else { + if (onLambda.equals(this.lambdaTree)) { + ++i; + } else { + valid.add(targeted.remove(i)); + } + } } - } else { - if (onLambda.equals(this.lambdaTree)) { - ++i; + + return targetClassToAnnos; + } + + /** + * @param targeted type compounds with formal method parameter target types with parameter_index + * == getIndex + */ + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + List formalParams = new ArrayList<>(); + Map> targetToAnnos = + ElementAnnotationUtil.partitionByTargetType( + targeted, formalParams, TargetType.METHOD_RECEIVER); + + if (isReceiver(element)) { + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + type, targetToAnnos.get(TargetType.METHOD_RECEIVER)); } else { - valid.add(targeted.remove(i)); + ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, formalParams); } - } } - return targetClassToAnnos; - } - - /** - * @param targeted type compounds with formal method parameter target types with parameter_index - * == getIndex - */ - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - List formalParams = new ArrayList<>(); - Map> targetToAnnos = - ElementAnnotationUtil.partitionByTargetType( - targeted, formalParams, TargetType.METHOD_RECEIVER); - - if (isReceiver(element)) { - ElementAnnotationUtil.annotateViaTypeAnnoPosition( - type, targetToAnnos.get(TargetType.METHOD_RECEIVER)); - } else { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, formalParams); + /** + * Returns true if element represents the receiver parameter of a method. + * + * @param element an element + * @return true if element represents the receiver parameter of a method + */ + private boolean isReceiver(Element element) { + return element.getKind() == ElementKind.PARAMETER + && element.getSimpleName().contentEquals("this"); } - } - - /** - * Returns true if element represents the receiver parameter of a method. - * - * @param element an element - * @return true if element represents the receiver parameter of a method - */ - private boolean isReceiver(Element element) { - return element.getKind() == ElementKind.PARAMETER - && element.getSimpleName().contentEquals("this"); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } - - /** - * Return the enclosing MethodSymbol of the given element, throwing an exception if the symbol's - * enclosing element is not a MethodSymbol. - * - * @param methodChildElem some element that is a child of a method typeDeclaration (e.g. a - * parameter or return type) - * @return the MethodSymbol of the method containing methodChildElem - */ - public static Symbol.MethodSymbol getParentMethod(Element methodChildElem) { - if (!(methodChildElem.getEnclosingElement() instanceof Symbol.MethodSymbol)) { - throw new BugInCF( - "Element is not a direct child of a MethodSymbol. Element ( " - + methodChildElem - + " parent ( " - + methodChildElem.getEnclosingElement() - + " ) "); + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } + + /** + * Return the enclosing MethodSymbol of the given element, throwing an exception if the symbol's + * enclosing element is not a MethodSymbol. + * + * @param methodChildElem some element that is a child of a method typeDeclaration (e.g. a + * parameter or return type) + * @return the MethodSymbol of the method containing methodChildElem + */ + public static Symbol.MethodSymbol getParentMethod(Element methodChildElem) { + if (!(methodChildElem.getEnclosingElement() instanceof Symbol.MethodSymbol)) { + throw new BugInCF( + "Element is not a direct child of a MethodSymbol. Element ( " + + methodChildElem + + " parent ( " + + methodChildElem.getEnclosingElement() + + " ) "); + } + return (Symbol.MethodSymbol) methodChildElem.getEnclosingElement(); + } + + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + type, element.getAnnotationMirrors()); + super.extractAndApply(); } - return (Symbol.MethodSymbol) methodChildElem.getEnclosingElement(); - } - - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - type, element.getAnnotationMirrors()); - super.extractAndApply(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java index 0e459ca5f3e..d890056db76 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/SuperTypeApplier.java @@ -4,11 +4,14 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; -import java.util.List; -import javax.lang.model.element.TypeElement; + import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; +import java.util.List; + +import javax.lang.model.element.TypeElement; + /** * When discovering supertypes of an AnnotatedTypeMirror we want to annotate each supertype with the * annotations of the subtypes class declaration. This class provides static methods to do this for @@ -16,132 +19,136 @@ */ public class SuperTypeApplier extends IndexedElementAnnotationApplier { - /** - * Annotates each supertype with annotations from subtypeElement's extends/implements clauses. - * - * @param supertypes supertypes to annotate - * @param subtypeElement element that may have annotations to apply to supertypes - */ - public static void annotateSupers( - List supertypes, TypeElement subtypeElement) - throws UnexpectedAnnotationLocationException { - for (int i = 0; i < supertypes.size(); i++) { - AnnotatedTypeMirror supertype = supertypes.get(i); - // Offset i by -1 since typeIndex should start from -1. - // -1 represents the (implicit) extends clause class. - // 0 and greater represent the implements clause interfaces. - // For details see the JSR 308 specification: - // http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html#class-file%3Aext%3Ari%3Aextends - int typeIndex = i - 1; - new SuperTypeApplier(supertype, subtypeElement, typeIndex).extractAndApply(); + /** + * Annotates each supertype with annotations from subtypeElement's extends/implements clauses. + * + * @param supertypes supertypes to annotate + * @param subtypeElement element that may have annotations to apply to supertypes + */ + public static void annotateSupers( + List supertypes, TypeElement subtypeElement) + throws UnexpectedAnnotationLocationException { + for (int i = 0; i < supertypes.size(); i++) { + AnnotatedTypeMirror supertype = supertypes.get(i); + // Offset i by -1 since typeIndex should start from -1. + // -1 represents the (implicit) extends clause class. + // 0 and greater represent the implements clause interfaces. + // For details see the JSR 308 specification: + // http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html#class-file%3Aext%3Ari%3Aextends + int typeIndex = i - 1; + new SuperTypeApplier(supertype, subtypeElement, typeIndex).extractAndApply(); + } + } + + /** The subclass symbol. */ + private final Symbol.ClassSymbol subclassSymbol; + + /** + * The type_index of the supertype being annotated. + * + *

          Note: Due to the semantics of TypeAnnotationPosition, type_index/index numbering works as + * follows: + * + *

          If subtypeElement represents a class and not an interface: + * + *

          then the first member of supertypes represents the object and the relevant type_index = + * -1; interface indices are offset by 1. + * + *

          else all members of supertypes represent interfaces and their indices == their index in + * the supertypes list + */ + private final int index; + + /** + * Constructor. + * + *

          Note: This is not meant to be used in apply explicitly unlike all other AnnotationAppliers + * it is intended to be used for annotate super types via the static annotateSuper method, hence + * the private constructor. + * + * @param supertype the supertype + * @param subclassElement the corresponding subclass element + * @param index the type index of the supertype being annotated + */ + private SuperTypeApplier( + AnnotatedTypeMirror supertype, TypeElement subclassElement, int index) { + super(supertype, subclassElement); + this.subclassSymbol = (Symbol.ClassSymbol) subclassElement; + this.index = index; + } + + /** + * Returns the type_index that should represent supertype. + * + * @return the type_index that should represent supertype + */ + @Override + public int getElementIndex() { + return index; + } + + /** + * Returns the type_index of anno's TypeAnnotationPosition. + * + * @return the type_index of anno's TypeAnnotationPosition + */ + @Override + public int getTypeCompoundIndex(Attribute.TypeCompound anno) { + int typeIndex = anno.getPosition().type_index; + // TODO: this is a workaround of a bug in langtools + // https://bugs.openjdk.org/browse/JDK-8164519 + // This bug is fixed in Java 9. + return typeIndex == 0xffff ? -1 : typeIndex; + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] {TargetType.CLASS_EXTENDS}; + + /** + * Returns TargetType.CLASS_EXTENDS. + * + * @return TargetType.CLASS_EXTENDS + */ + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND + }; + + /** + * Returns TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND. + * + * @return TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND + */ + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** + * Returns the TypeCompounds (annotations) of the subclass. + * + * @return the TypeCompounds (annotations) of the subclass + */ + @Override + protected Iterable getRawTypeAttributes() { + return subclassSymbol.getRawTypeAttributes(); + } + + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); + } + + @Override + protected boolean isAccepted() { + return true; } - } - - /** The subclass symbol. */ - private final Symbol.ClassSymbol subclassSymbol; - - /** - * The type_index of the supertype being annotated. - * - *

          Note: Due to the semantics of TypeAnnotationPosition, type_index/index numbering works as - * follows: - * - *

          If subtypeElement represents a class and not an interface: - * - *

          then the first member of supertypes represents the object and the relevant type_index = -1; - * interface indices are offset by 1. - * - *

          else all members of supertypes represent interfaces and their indices == their index in the - * supertypes list - */ - private final int index; - - /** - * Constructor. - * - *

          Note: This is not meant to be used in apply explicitly unlike all other AnnotationAppliers - * it is intended to be used for annotate super types via the static annotateSuper method, hence - * the private constructor. - * - * @param supertype the supertype - * @param subclassElement the corresponding subclass element - * @param index the type index of the supertype being annotated - */ - private SuperTypeApplier(AnnotatedTypeMirror supertype, TypeElement subclassElement, int index) { - super(supertype, subclassElement); - this.subclassSymbol = (Symbol.ClassSymbol) subclassElement; - this.index = index; - } - - /** - * Returns the type_index that should represent supertype. - * - * @return the type_index that should represent supertype - */ - @Override - public int getElementIndex() { - return index; - } - - /** - * Returns the type_index of anno's TypeAnnotationPosition. - * - * @return the type_index of anno's TypeAnnotationPosition - */ - @Override - public int getTypeCompoundIndex(Attribute.TypeCompound anno) { - int typeIndex = anno.getPosition().type_index; - // TODO: this is a workaround of a bug in langtools - // https://bugs.openjdk.org/browse/JDK-8164519 - // This bug is fixed in Java 9. - return typeIndex == 0xffff ? -1 : typeIndex; - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; - - /** - * Returns TargetType.CLASS_EXTENDS. - * - * @return TargetType.CLASS_EXTENDS - */ - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] {TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND}; - - /** - * Returns TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND. - * - * @return TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND - */ - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** - * Returns the TypeCompounds (annotations) of the subclass. - * - * @return the TypeCompounds (annotations) of the subclass - */ - @Override - protected Iterable getRawTypeAttributes() { - return subclassSymbol.getRawTypeAttributes(); - } - - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); - } - - @Override - protected boolean isAccepted() { - return true; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java index 5df10dfd16d..815be4283bc 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TargetedElementAnnotationApplier.java @@ -3,17 +3,20 @@ import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.TargetType; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; +import org.checkerframework.javacutil.BugInCF; +import org.plumelib.util.StringsPlume; + import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.StringJoiner; + import javax.lang.model.element.Element; import javax.lang.model.type.TypeKind; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; -import org.checkerframework.javacutil.BugInCF; -import org.plumelib.util.StringsPlume; /** * TargetedElementAnnotationApplier filters annotations for an element into 3 groups. TARGETED @@ -28,180 +31,188 @@ * methods though they have default empty implementations for brevity. */ abstract class TargetedElementAnnotationApplier { - /** - * Three annotation types that may be encountered when calling getRawTypeAttributes. see sift(). - */ - enum TargetClass { - TARGETED, - VALID, - INVALID - } - - /** The type to which we wish to apply annotations. */ - protected final AnnotatedTypeMirror type; - - /** An Element that type represents. */ - protected final Element element; - - /** - * Returns the TargetTypes that identify annotations we wish to apply with this object. Any - * annotations that have these target types will be passed to handleTargeted. - * - * @return the TargetTypes that identify annotations we wish to apply with this object. Any - * annotations that have these target types will be passed to handleTargeted - */ - protected abstract TargetType[] annotatedTargets(); - - /** - * Returns the TargetTypes that identify annotations that are valid but we wish to ignore. Any - * annotations that have these target types will be passed to handleValid, providing they aren't - * also in annotatedTargets. - * - * @return the TargetTypes that identify annotations that are valid but we wish to ignore - */ - protected abstract TargetType[] validTargets(); - - /** - * Annotations on elements are represented as Attribute.TypeCompounds ( a subtype of - * AnnotationMirror) that are usually accessed through a getRawTypeAttributes method on the - * element. - * - *

          In Java 8 and later these annotations are generally contained by elements to which they - * apply. However, in earlier versions of Java many of these annotations are handled by either the - * enclosing method, e.g. parameters and method type parameters, or enclosing class, e.g. class - * type parameters. Therefore, many annotations are addressed by first getting all annotations on - * a method or class and the picking out only the ones we wish to target (see extractAndApply). - * - * @return the annotations that we MAY wish to apply to the given type - */ - protected abstract Iterable getRawTypeAttributes(); - - /** - * Tests element/type fields to ensure that this TargetedElementAnnotationApplier is valid for - * this element/type pair. - * - * @return true if the type/element members are handled by this class false otherwise - */ - protected abstract boolean isAccepted(); - - /** - * Constructor. - * - * @param type the type to annotate - * @param element an element identifying type - */ - /*package-private*/ TargetedElementAnnotationApplier(AnnotatedTypeMirror type, Element element) { - this.type = type; - this.element = element; - } - - /** - * This method should apply all annotations that are handled by this object. - * - * @param targeted the list of annotations that were returned by getRawTypeAttributes and had a - * TargetType contained by annotatedTargets - */ - protected abstract void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException; - - /** - * The default implementation of this method does nothing. - * - * @param valid the list of annotations that were returned by getRawTypeAttributes and had a - * TargetType contained by valid and NOT annotatedTargets - */ - protected void handleValid(List valid) {} - - /** - * This implementation reports all invalid annotations as errors. - * - * @param invalid the list of annotations that were returned by getRawTypeAttributes and were not - * handled by handleTargeted or handleValid - */ - protected void handleInvalid(List invalid) { - List remaining = new ArrayList<>(invalid.size()); - for (Attribute.TypeCompound tc : invalid) { - if (tc.getAnnotationType().getKind() != TypeKind.ERROR) { - // Filter out annotations that have an error type. javac will - // already have raised an error for them. - remaining.add(tc); - } - } - if (!remaining.isEmpty()) { - StringJoiner msg = new StringJoiner(System.lineSeparator()); - msg.add("handleInvalid(this=" + this.getClass().getName() + "):"); - msg.add("Invalid variable and element passed to extractAndApply; type: " + type); - String elementInfoPrefix = - " element: " + element + " (kind: " + element.getKind() + "), invalid annotations: "; - StringJoiner remainingInfo = new StringJoiner(", ", elementInfoPrefix, ""); - for (Attribute.TypeCompound r : remaining) { - remainingInfo.add(r.toString() + " (" + r.position + ")"); - } - msg.add(remainingInfo.toString()); - msg.add("Targeted annotations: " + StringsPlume.join(", ", annotatedTargets())); - msg.add("Valid annotations: " + StringsPlume.join(", ", validTargets())); - - throw new BugInCF(msg.toString()); - } - } - - /** - * Separate the input annotations into a Map of TargetClass (TARGETED, VALID, INVALID) to the - * annotations that fall into each of those categories. - * - * @param typeCompounds annotations to sift through, should be those returned by - * getRawTypeAttributes - * @return a {@literal Map Annotations>.} - */ - protected Map> sift( - Iterable typeCompounds) { - Map> targetClassToCompound = - new EnumMap<>(TargetClass.class); - for (TargetClass targetClass : TargetClass.values()) { - targetClassToCompound.put(targetClass, new ArrayList<>()); + /** + * Three annotation types that may be encountered when calling getRawTypeAttributes. see sift(). + */ + enum TargetClass { + TARGETED, + VALID, + INVALID } - for (Attribute.TypeCompound typeCompound : typeCompounds) { - TargetType typeCompoundTarget = typeCompound.position.type; - List destList; - - if (ElementAnnotationUtil.contains(typeCompoundTarget, annotatedTargets())) { - destList = targetClassToCompound.get(TargetClass.TARGETED); - } else if (ElementAnnotationUtil.contains(typeCompoundTarget, validTargets())) { - destList = targetClassToCompound.get(TargetClass.VALID); - } else { - destList = targetClassToCompound.get(TargetClass.INVALID); - } - - destList.add(typeCompound); + /** The type to which we wish to apply annotations. */ + protected final AnnotatedTypeMirror type; + + /** An Element that type represents. */ + protected final Element element; + + /** + * Returns the TargetTypes that identify annotations we wish to apply with this object. Any + * annotations that have these target types will be passed to handleTargeted. + * + * @return the TargetTypes that identify annotations we wish to apply with this object. Any + * annotations that have these target types will be passed to handleTargeted + */ + protected abstract TargetType[] annotatedTargets(); + + /** + * Returns the TargetTypes that identify annotations that are valid but we wish to ignore. Any + * annotations that have these target types will be passed to handleValid, providing they aren't + * also in annotatedTargets. + * + * @return the TargetTypes that identify annotations that are valid but we wish to ignore + */ + protected abstract TargetType[] validTargets(); + + /** + * Annotations on elements are represented as Attribute.TypeCompounds ( a subtype of + * AnnotationMirror) that are usually accessed through a getRawTypeAttributes method on the + * element. + * + *

          In Java 8 and later these annotations are generally contained by elements to which they + * apply. However, in earlier versions of Java many of these annotations are handled by either + * the enclosing method, e.g. parameters and method type parameters, or enclosing class, e.g. + * class type parameters. Therefore, many annotations are addressed by first getting all + * annotations on a method or class and the picking out only the ones we wish to target (see + * extractAndApply). + * + * @return the annotations that we MAY wish to apply to the given type + */ + protected abstract Iterable getRawTypeAttributes(); + + /** + * Tests element/type fields to ensure that this TargetedElementAnnotationApplier is valid for + * this element/type pair. + * + * @return true if the type/element members are handled by this class false otherwise + */ + protected abstract boolean isAccepted(); + + /** + * Constructor. + * + * @param type the type to annotate + * @param element an element identifying type + */ + /*package-private*/ TargetedElementAnnotationApplier( + AnnotatedTypeMirror type, Element element) { + this.type = type; + this.element = element; } - return targetClassToCompound; - } - - /** - * Reads the list of annotations that apply to this element (see getRawTypeAttributes). Sifts them - * into three groups (TARGETED, INVALID, VALID) and then calls the appropriate handle method on - * them. The handleTargeted method should apply all annotations that are handled by this object. - * - *

          This method will throw a runtime exception if isAccepted returns false. - */ - public void extractAndApply() throws UnexpectedAnnotationLocationException { - if (!isAccepted()) { - throw new BugInCF( - "LocalVariableExtractor.extractAndApply: " - + "Invalid variable and element passed to " - + this.getClass().getName() - + "::extractAndApply (" - + type - + ", " - + element); + /** + * This method should apply all annotations that are handled by this object. + * + * @param targeted the list of annotations that were returned by getRawTypeAttributes and had a + * TargetType contained by annotatedTargets + */ + protected abstract void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException; + + /** + * The default implementation of this method does nothing. + * + * @param valid the list of annotations that were returned by getRawTypeAttributes and had a + * TargetType contained by valid and NOT annotatedTargets + */ + protected void handleValid(List valid) {} + + /** + * This implementation reports all invalid annotations as errors. + * + * @param invalid the list of annotations that were returned by getRawTypeAttributes and were + * not handled by handleTargeted or handleValid + */ + protected void handleInvalid(List invalid) { + List remaining = new ArrayList<>(invalid.size()); + for (Attribute.TypeCompound tc : invalid) { + if (tc.getAnnotationType().getKind() != TypeKind.ERROR) { + // Filter out annotations that have an error type. javac will + // already have raised an error for them. + remaining.add(tc); + } + } + if (!remaining.isEmpty()) { + StringJoiner msg = new StringJoiner(System.lineSeparator()); + msg.add("handleInvalid(this=" + this.getClass().getName() + "):"); + msg.add("Invalid variable and element passed to extractAndApply; type: " + type); + String elementInfoPrefix = + " element: " + + element + + " (kind: " + + element.getKind() + + "), invalid annotations: "; + StringJoiner remainingInfo = new StringJoiner(", ", elementInfoPrefix, ""); + for (Attribute.TypeCompound r : remaining) { + remainingInfo.add(r.toString() + " (" + r.position + ")"); + } + msg.add(remainingInfo.toString()); + msg.add("Targeted annotations: " + StringsPlume.join(", ", annotatedTargets())); + msg.add("Valid annotations: " + StringsPlume.join(", ", validTargets())); + + throw new BugInCF(msg.toString()); + } } - Map> targetClassToAnno = sift(getRawTypeAttributes()); + /** + * Separate the input annotations into a Map of TargetClass (TARGETED, VALID, INVALID) to the + * annotations that fall into each of those categories. + * + * @param typeCompounds annotations to sift through, should be those returned by + * getRawTypeAttributes + * @return a {@literal Map Annotations>.} + */ + protected Map> sift( + Iterable typeCompounds) { + Map> targetClassToCompound = + new EnumMap<>(TargetClass.class); + for (TargetClass targetClass : TargetClass.values()) { + targetClassToCompound.put(targetClass, new ArrayList<>()); + } + + for (Attribute.TypeCompound typeCompound : typeCompounds) { + TargetType typeCompoundTarget = typeCompound.position.type; + List destList; + + if (ElementAnnotationUtil.contains(typeCompoundTarget, annotatedTargets())) { + destList = targetClassToCompound.get(TargetClass.TARGETED); + } else if (ElementAnnotationUtil.contains(typeCompoundTarget, validTargets())) { + destList = targetClassToCompound.get(TargetClass.VALID); + } else { + destList = targetClassToCompound.get(TargetClass.INVALID); + } + + destList.add(typeCompound); + } + + return targetClassToCompound; + } - handleInvalid(targetClassToAnno.get(TargetClass.INVALID)); - handleValid(targetClassToAnno.get(TargetClass.VALID)); - handleTargeted(targetClassToAnno.get(TargetClass.TARGETED)); - } + /** + * Reads the list of annotations that apply to this element (see getRawTypeAttributes). Sifts + * them into three groups (TARGETED, INVALID, VALID) and then calls the appropriate handle + * method on them. The handleTargeted method should apply all annotations that are handled by + * this object. + * + *

          This method will throw a runtime exception if isAccepted returns false. + */ + public void extractAndApply() throws UnexpectedAnnotationLocationException { + if (!isAccepted()) { + throw new BugInCF( + "LocalVariableExtractor.extractAndApply: " + + "Invalid variable and element passed to " + + this.getClass().getName() + + "::extractAndApply (" + + type + + ", " + + element); + } + + Map> targetClassToAnno = + sift(getRawTypeAttributes()); + + handleInvalid(targetClassToAnno.get(TargetClass.INVALID)); + handleValid(targetClassToAnno.get(TargetClass.VALID)); + handleTargeted(targetClassToAnno.get(TargetClass.TARGETED)); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java index 8e238518e92..0899b3776a2 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeDeclarationApplier.java @@ -4,146 +4,151 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; -import java.util.List; -import javax.lang.model.element.Element; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.TypesUtils; +import java.util.List; + +import javax.lang.model.element.Element; + /** Apply annotations to a declared type based on its declaration. */ public class TypeDeclarationApplier extends TargetedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new TypeDeclarationApplier(type, element, atypeFactory).extractAndApply(); - } - - /** - * If a type_index == -1 it means that the index refers to the immediate supertype class of the - * declaration. There is only ever one of these since Java has no multiple inheritance - */ - public static final int SUPERCLASS_INDEX = -1; - - /** - * Returns true if type is an annotated declared type and element is a ClassSymbol. - * - * @param type a type - * @param element an element - * @return true if type is an annotated declared type and element is a ClassSymbol - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return type instanceof AnnotatedDeclaredType && element instanceof Symbol.ClassSymbol; - } - - /** The type factory to use. */ - private final AnnotatedTypeFactory atypeFactory; - - /** The type symbol. */ - private final Symbol.ClassSymbol typeSymbol; - - /** The declared type. */ - private final AnnotatedDeclaredType declaredType; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ TypeDeclarationApplier( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - this.atypeFactory = atypeFactory; - this.typeSymbol = (Symbol.ClassSymbol) element; - this.declaredType = (AnnotatedDeclaredType) type; - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = new TargetType[] {TargetType.CLASS_EXTENDS}; - - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - TargetType.CLASS_TYPE_PARAMETER, - TargetType.CLASS_TYPE_PARAMETER_BOUND - }; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - /** All TypeCompounds (annotations) on the ClassSymbol. */ - @Override - protected Iterable getRawTypeAttributes() { - return typeSymbol.getRawTypeAttributes(); - } - - /** - * While more than just annotations on extends or implements clause are annotated by this class, - * only these annotations are passed to handleTargeted (as they are the only in the - * annotatedTargets list). See extractAndApply for type parameters - * - * @param extendsAndImplementsAnnos annotations with a TargetType of CLASS_EXTENDS - */ - @Override - protected void handleTargeted(List extendsAndImplementsAnnos) - throws UnexpectedAnnotationLocationException { - if (TypesUtils.isAnonymous(typeSymbol.type)) { - // If this is an anonymous class, then the annotations after "new" but before the class - // name are stored as super class annotations. Treat them as annotations on the class. - for (Attribute.TypeCompound anno : extendsAndImplementsAnnos) { - if (anno.position.type_index >= SUPERCLASS_INDEX && anno.position.location.isEmpty()) { - type.addAnnotation(anno); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new TypeDeclarationApplier(type, element, atypeFactory).extractAndApply(); + } + + /** + * If a type_index == -1 it means that the index refers to the immediate supertype class of the + * declaration. There is only ever one of these since Java has no multiple inheritance + */ + public static final int SUPERCLASS_INDEX = -1; + + /** + * Returns true if type is an annotated declared type and element is a ClassSymbol. + * + * @param type a type + * @param element an element + * @return true if type is an annotated declared type and element is a ClassSymbol + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return type instanceof AnnotatedDeclaredType && element instanceof Symbol.ClassSymbol; + } + + /** The type factory to use. */ + private final AnnotatedTypeFactory atypeFactory; + + /** The type symbol. */ + private final Symbol.ClassSymbol typeSymbol; + + /** The declared type. */ + private final AnnotatedDeclaredType declaredType; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ TypeDeclarationApplier( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + this.atypeFactory = atypeFactory; + this.typeSymbol = (Symbol.ClassSymbol) element; + this.declaredType = (AnnotatedDeclaredType) type; + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] {TargetType.CLASS_EXTENDS}; + + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + TargetType.CLASS_TYPE_PARAMETER, + TargetType.CLASS_TYPE_PARAMETER_BOUND + }; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + /** All TypeCompounds (annotations) on the ClassSymbol. */ + @Override + protected Iterable getRawTypeAttributes() { + return typeSymbol.getRawTypeAttributes(); + } + + /** + * While more than just annotations on extends or implements clause are annotated by this class, + * only these annotations are passed to handleTargeted (as they are the only in the + * annotatedTargets list). See extractAndApply for type parameters + * + * @param extendsAndImplementsAnnos annotations with a TargetType of CLASS_EXTENDS + */ + @Override + protected void handleTargeted(List extendsAndImplementsAnnos) + throws UnexpectedAnnotationLocationException { + if (TypesUtils.isAnonymous(typeSymbol.type)) { + // If this is an anonymous class, then the annotations after "new" but before the class + // name are stored as super class annotations. Treat them as annotations on the class. + for (Attribute.TypeCompound anno : extendsAndImplementsAnnos) { + if (anno.position.type_index >= SUPERCLASS_INDEX + && anno.position.location.isEmpty()) { + type.addAnnotation(anno); + } + } } - } } - } - - /** Adds extends/implements and class annotations to type. Annotates type parameters. */ - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - // ensures that we check that there only valid target types on this class, there are no - // "targeted" locations - super.extractAndApply(); - - // Annotate raw types // TODO: ASK WERNER WHAT THIS MIGHT MEAN? WHAT ACTUALLY GOES HERE? - type.addAnnotations(typeSymbol.getAnnotationMirrors()); - - ElementAnnotationUtil.applyAllElementAnnotations( - declaredType.getTypeArguments(), typeSymbol.getTypeParameters(), atypeFactory); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } + + /** Adds extends/implements and class annotations to type. Annotates type parameters. */ + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + // ensures that we check that there only valid target types on this class, there are no + // "targeted" locations + super.extractAndApply(); + + // Annotate raw types // TODO: ASK WERNER WHAT THIS MIGHT MEAN? WHAT ACTUALLY GOES HERE? + type.addAnnotations(typeSymbol.getAnnotationMirrors()); + + ElementAnnotationUtil.applyAllElementAnnotations( + declaredType.getTypeArguments(), typeSymbol.getTypeParameters(), atypeFactory); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java index 19693cc7643..0dd2247c1b8 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeParamElementAnnotationApplier.java @@ -2,20 +2,23 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.TargetType; + +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; +import org.checkerframework.javacutil.BugInCF; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.type.TypeKind; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; -import org.checkerframework.javacutil.BugInCF; /** * Applies Element annotations to a single AnnotatedTypeVariable representing a type parameter. @@ -24,208 +27,212 @@ */ abstract class TypeParamElementAnnotationApplier extends IndexedElementAnnotationApplier { - /** - * Returns true if element is a TYPE_PARAMETER. - * - * @param typeMirror ignored - * @param element the element that might be a TYPE_PARAMETER - * @return true if element is a TYPE_PARAMETER - */ - public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { - return element.getKind() == ElementKind.TYPE_PARAMETER; - } - - protected final AnnotatedTypeVariable typeParam; - protected final AnnotatedTypeFactory atypeFactory; - - /** - * Returns target type that represents the location of the lower bound of element. - * - * @return target type that represents the location of the lower bound of element - */ - protected abstract TargetType lowerBoundTarget(); - - /** - * Returns target type that represents the location of the upper bound of element. - * - * @return target type that represents the location of the upper bound of element - */ - protected abstract TargetType upperBoundTarget(); - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - */ - /*package-private*/ TypeParamElementAnnotationApplier( - AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { - super(type, element); - this.typeParam = type; - this.atypeFactory = atypeFactory; - } - - /** - * Returns the lower bound and upper bound targets. - * - * @return the lower bound and upper bound targets - */ - @Override - protected TargetType[] annotatedTargets() { - return new TargetType[] {lowerBoundTarget(), upperBoundTarget()}; - } - - /** - * Returns the parameter_index of anno's TypeAnnotationPosition which will actually point to the - * type parameter's index in its enclosing type parameter list. - * - * @return the parameter_index of anno's TypeAnnotationPosition which will actually point to the - * type parameter's index in its enclosing type parameter list - */ - @Override - public int getTypeCompoundIndex(TypeCompound anno) { - return anno.getPosition().parameter_index; - } - - /** - * @param targeted the list of annotations that were on the lower/upper bounds of the type - * parameter - *

          Note: When handling type parameters we NEVER add primary annotations to the type - * parameter. Primary annotations are reserved for the use of a type parameter (e.g. @Nullable - * T t; ) - *

          If an annotation is present on the type parameter itself, it represents the lower-bound - * annotation of that type parameter. Any annotation on the extends bound of a type parameter - * is placed on that bound. - */ - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - int paramIndex = getElementIndex(); - List upperBoundAnnos = new ArrayList<>(); - List lowerBoundAnnos = new ArrayList<>(); - - for (TypeCompound anno : targeted) { - AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); - AnnotationMirror canonicalAnno = (aliasedAnno != null) ? aliasedAnno : anno; - - if (anno.position.parameter_index != paramIndex - || !atypeFactory.isSupportedQualifier(canonicalAnno)) { - continue; - } - - if (ElementAnnotationUtil.isOnComponentType(anno)) { - applyComponentAnnotation(anno); - } else if (anno.position.type == upperBoundTarget()) { - upperBoundAnnos.add(anno); - } else { - lowerBoundAnnos.add(anno); - } + /** + * Returns true if element is a TYPE_PARAMETER. + * + * @param typeMirror ignored + * @param element the element that might be a TYPE_PARAMETER + * @return true if element is a TYPE_PARAMETER + */ + public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { + return element.getKind() == ElementKind.TYPE_PARAMETER; } - applyLowerBounds(lowerBoundAnnos); - applyUpperBounds(upperBoundAnnos); - } - - /** - * Applies a list of annotations to the upperBound of the type parameter. If the type of the upper - * bound is an intersection we must first find the correct location for each annotation. - */ - private void applyUpperBounds(List upperBounds) { - if (!upperBounds.isEmpty()) { - AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); - - if (upperBoundType.getKind() == TypeKind.INTERSECTION) { - List bounds = ((AnnotatedIntersectionType) upperBoundType).getBounds(); - int boundIndexOffset = ElementAnnotationUtil.getBoundIndexOffset(bounds); - - for (TypeCompound anno : upperBounds) { - int boundIndex = anno.position.bound_index + boundIndexOffset; - - if (boundIndex < 0 || boundIndex > bounds.size()) { - throw new BugInCF( - "Invalid bound index on element annotation ( " - + anno - + " ) " - + "for type ( " - + typeParam - + " ) with " - + "upper bound ( " - + typeParam.getUpperBound() - + " ) " - + "and boundIndex( " - + boundIndex - + " ) "); - } - - bounds.get(boundIndex).replaceAnnotation(anno); // TODO: WHY NOT ADD? + protected final AnnotatedTypeVariable typeParam; + protected final AnnotatedTypeFactory atypeFactory; + + /** + * Returns target type that represents the location of the lower bound of element. + * + * @return target type that represents the location of the lower bound of element + */ + protected abstract TargetType lowerBoundTarget(); + + /** + * Returns target type that represents the location of the upper bound of element. + * + * @return target type that represents the location of the upper bound of element + */ + protected abstract TargetType upperBoundTarget(); + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + */ + /*package-private*/ TypeParamElementAnnotationApplier( + AnnotatedTypeVariable type, Element element, AnnotatedTypeFactory atypeFactory) { + super(type, element); + this.typeParam = type; + this.atypeFactory = atypeFactory; + } + + /** + * Returns the lower bound and upper bound targets. + * + * @return the lower bound and upper bound targets + */ + @Override + protected TargetType[] annotatedTargets() { + return new TargetType[] {lowerBoundTarget(), upperBoundTarget()}; + } + + /** + * Returns the parameter_index of anno's TypeAnnotationPosition which will actually point to the + * type parameter's index in its enclosing type parameter list. + * + * @return the parameter_index of anno's TypeAnnotationPosition which will actually point to the + * type parameter's index in its enclosing type parameter list + */ + @Override + public int getTypeCompoundIndex(TypeCompound anno) { + return anno.getPosition().parameter_index; + } + + /** + * @param targeted the list of annotations that were on the lower/upper bounds of the type + * parameter + *

          Note: When handling type parameters we NEVER add primary annotations to the type + * parameter. Primary annotations are reserved for the use of a type parameter + * (e.g. @Nullable T t; ) + *

          If an annotation is present on the type parameter itself, it represents the + * lower-bound annotation of that type parameter. Any annotation on the extends bound of a + * type parameter is placed on that bound. + */ + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + int paramIndex = getElementIndex(); + List upperBoundAnnos = new ArrayList<>(); + List lowerBoundAnnos = new ArrayList<>(); + + for (TypeCompound anno : targeted) { + AnnotationMirror aliasedAnno = atypeFactory.canonicalAnnotation(anno); + AnnotationMirror canonicalAnno = (aliasedAnno != null) ? aliasedAnno : anno; + + if (anno.position.parameter_index != paramIndex + || !atypeFactory.isSupportedQualifier(canonicalAnno)) { + continue; + } + + if (ElementAnnotationUtil.isOnComponentType(anno)) { + applyComponentAnnotation(anno); + } else if (anno.position.type == upperBoundTarget()) { + upperBoundAnnos.add(anno); + } else { + lowerBoundAnnos.add(anno); + } } - ((AnnotatedIntersectionType) upperBoundType).copyIntersectionBoundAnnotations(); - } else { - upperBoundType.addAnnotations(upperBounds); - } + applyLowerBounds(lowerBoundAnnos); + applyUpperBounds(upperBoundAnnos); } - } - - /** - * In the event of multiple annotations on an AnnotatedNullType lower bound we want to preserve - * the multiple annotations so that a type.invalid error is issued later. - * - * @param annos the annotations to add to the lower bound - */ - private void applyLowerBounds(List annos) { - if (!annos.isEmpty()) { - AnnotatedTypeMirror lowerBound = typeParam.getLowerBound(); - - for (AnnotationMirror anno : annos) { - lowerBound.addAnnotation(anno); - } + + /** + * Applies a list of annotations to the upperBound of the type parameter. If the type of the + * upper bound is an intersection we must first find the correct location for each annotation. + */ + private void applyUpperBounds(List upperBounds) { + if (!upperBounds.isEmpty()) { + AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); + + if (upperBoundType.getKind() == TypeKind.INTERSECTION) { + List bounds = + ((AnnotatedIntersectionType) upperBoundType).getBounds(); + int boundIndexOffset = ElementAnnotationUtil.getBoundIndexOffset(bounds); + + for (TypeCompound anno : upperBounds) { + int boundIndex = anno.position.bound_index + boundIndexOffset; + + if (boundIndex < 0 || boundIndex > bounds.size()) { + throw new BugInCF( + "Invalid bound index on element annotation ( " + + anno + + " ) " + + "for type ( " + + typeParam + + " ) with " + + "upper bound ( " + + typeParam.getUpperBound() + + " ) " + + "and boundIndex( " + + boundIndex + + " ) "); + } + + bounds.get(boundIndex).replaceAnnotation(anno); // TODO: WHY NOT ADD? + } + ((AnnotatedIntersectionType) upperBoundType).copyIntersectionBoundAnnotations(); + + } else { + upperBoundType.addAnnotations(upperBounds); + } + } } - } - - private void addAnnotationToMap( - AnnotatedTypeMirror type, - TypeCompound anno, - Map> typeToAnnos) { - List annoList = typeToAnnos.computeIfAbsent(type, __ -> new ArrayList<>()); - annoList.add(anno); - } - - private void applyComponentAnnotation(TypeCompound anno) - throws UnexpectedAnnotationLocationException { - AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); - - Map> typeToAnnotations = new HashMap<>(); - - if (anno.position.type == upperBoundTarget()) { - if (upperBoundType.getKind() == TypeKind.INTERSECTION) { - List bounds = ((AnnotatedIntersectionType) upperBoundType).getBounds(); - int boundIndex = - anno.position.bound_index + ElementAnnotationUtil.getBoundIndexOffset(bounds); - - if (boundIndex < 0 || boundIndex > bounds.size()) { - throw new BugInCF( - "Invalid bound index on element annotation ( " - + anno - + " ) " - + "for type ( " - + typeParam - + " ) with upper bound ( " - + typeParam.getUpperBound() - + " )"); + + /** + * In the event of multiple annotations on an AnnotatedNullType lower bound we want to preserve + * the multiple annotations so that a type.invalid error is issued later. + * + * @param annos the annotations to add to the lower bound + */ + private void applyLowerBounds(List annos) { + if (!annos.isEmpty()) { + AnnotatedTypeMirror lowerBound = typeParam.getLowerBound(); + + for (AnnotationMirror anno : annos) { + lowerBound.addAnnotation(anno); + } } - addAnnotationToMap(bounds.get(boundIndex), anno, typeToAnnotations); - } else { - addAnnotationToMap(upperBoundType, anno, typeToAnnotations); - } - } else { - addAnnotationToMap(typeParam.getLowerBound(), anno, typeToAnnotations); } - for (Map.Entry> typeToAnno : - typeToAnnotations.entrySet()) { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(typeToAnno.getKey(), typeToAnno.getValue()); + private void addAnnotationToMap( + AnnotatedTypeMirror type, + TypeCompound anno, + Map> typeToAnnos) { + List annoList = typeToAnnos.computeIfAbsent(type, __ -> new ArrayList<>()); + annoList.add(anno); + } + + private void applyComponentAnnotation(TypeCompound anno) + throws UnexpectedAnnotationLocationException { + AnnotatedTypeMirror upperBoundType = typeParam.getUpperBound(); + + Map> typeToAnnotations = new HashMap<>(); + + if (anno.position.type == upperBoundTarget()) { + if (upperBoundType.getKind() == TypeKind.INTERSECTION) { + List bounds = + ((AnnotatedIntersectionType) upperBoundType).getBounds(); + int boundIndex = + anno.position.bound_index + + ElementAnnotationUtil.getBoundIndexOffset(bounds); + + if (boundIndex < 0 || boundIndex > bounds.size()) { + throw new BugInCF( + "Invalid bound index on element annotation ( " + + anno + + " ) " + + "for type ( " + + typeParam + + " ) with upper bound ( " + + typeParam.getUpperBound() + + " )"); + } + addAnnotationToMap(bounds.get(boundIndex), anno, typeToAnnotations); + } else { + addAnnotationToMap(upperBoundType, anno, typeToAnnotations); + } + } else { + addAnnotationToMap(typeParam.getLowerBound(), anno, typeToAnnotations); + } + + for (Map.Entry> typeToAnno : + typeToAnnotations.entrySet()) { + ElementAnnotationUtil.annotateViaTypeAnnoPosition( + typeToAnno.getKey(), typeToAnno.getValue()); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java index ade9e6b67a3..50ba7969fbe 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java @@ -5,14 +5,7 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.TargetType; import com.sun.tools.javac.code.TypeAnnotationPosition; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.TypeKind; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -23,319 +16,332 @@ import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.TypeKind; + /** Apply annotations to the use of a type parameter declaration. */ public class TypeVarUseApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @param atypeFactory the type factory - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply( - final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) - throws UnexpectedAnnotationLocationException { - new TypeVarUseApplier(type, element, atypeFactory).extractAndApply(); - } - - /** The ElementKinds that are accepted by this. */ - private static final ElementKind[] acceptedKinds = { - ElementKind.PARAMETER, - ElementKind.FIELD, - ElementKind.LOCAL_VARIABLE, - ElementKind.RESOURCE_VARIABLE, - ElementKind.METHOD - }; - - /** - * Returns true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable - * component, and the element is not a TYPE_PARAMETER. - * - * @param type the type to test - * @param element the corresponding element - * @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable - * component, and the element is not a TYPE_PARAMETER - */ - public static boolean accepts(AnnotatedTypeMirror type, Element element) { - return (type instanceof AnnotatedTypeVariable || isGenericArrayType(type)) - && ElementAnnotationUtil.contains(element.getKind(), acceptedKinds); - } - - /** - * Returns true if type is an array type whose innermost component type is a type variable. - * - * @param type the type to test - * @return true if type is an array type whose innermost component type is a type variable - */ - private static boolean isGenericArrayType(AnnotatedTypeMirror type) { - return type instanceof AnnotatedArrayType - && AnnotatedTypes.innerMostType(type) instanceof AnnotatedTypeVariable; - } - - /** The generic array type, if any. */ - // In order to avoid sprinkling code for type parameter uses all over the various locations - // uses can show up, we also handle generic array types. T [] myTArr; - private final @Nullable AnnotatedArrayType arrayType; - - /** The type variable. */ - private final AnnotatedTypeVariable typeVariable; - - /** The element for the declaration. */ - private final TypeParameterElement declarationElem; - - /** The element for the use. */ - private final Element useElem; - - /** The annotated type factory. */ - private final AnnotatedTypeFactory atypeFactory; - - /** - * Create a new TypeVarUseApplier. - * - * @param type the type of the variable use - * @param element the element for the variable use - * @param atypeFactory the type factory - */ - /*package-private*/ TypeVarUseApplier( - AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { - if (!accepts(type, element)) { - throw new BugInCF( - "TypeParamUseApplier does not accept type/element combination (" - + " type ( " - + type - + " ) element ( " - + element - + " ) "); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @param atypeFactory the type factory + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply( + final AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) + throws UnexpectedAnnotationLocationException { + new TypeVarUseApplier(type, element, atypeFactory).extractAndApply(); } - if (isGenericArrayType(type)) { - this.arrayType = (AnnotatedArrayType) type; - this.typeVariable = (AnnotatedTypeVariable) AnnotatedTypes.innerMostType(type); - this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); - this.useElem = element; - this.atypeFactory = atypeFactory; - } else { - this.arrayType = null; - this.typeVariable = (AnnotatedTypeVariable) type; - this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); - this.useElem = element; - this.atypeFactory = atypeFactory; - } - } - - /** - * Applies the bound annotations from the declaration of the type parameter and then applies the - * explicit annotations written on the type variable. - * - * @throws UnexpectedAnnotationLocationException if invalid location for an annotation was found - */ - public void extractAndApply() throws UnexpectedAnnotationLocationException { - if (arrayType != null) { - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - arrayType, useElem.getAnnotationMirrors()); - } else { - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - typeVariable, useElem.getAnnotationMirrors()); + /** The ElementKinds that are accepted by this. */ + private static final ElementKind[] acceptedKinds = { + ElementKind.PARAMETER, + ElementKind.FIELD, + ElementKind.LOCAL_VARIABLE, + ElementKind.RESOURCE_VARIABLE, + ElementKind.METHOD + }; + + /** + * Returns true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type + * variable component, and the element is not a TYPE_PARAMETER. + * + * @param type the type to test + * @param element the corresponding element + * @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type + * variable component, and the element is not a TYPE_PARAMETER + */ + public static boolean accepts(AnnotatedTypeMirror type, Element element) { + return (type instanceof AnnotatedTypeVariable || isGenericArrayType(type)) + && ElementAnnotationUtil.contains(element.getKind(), acceptedKinds); } - // apply annotations from the type parameter declaration - ElementAnnotationApplier.apply(typeVariable, declarationElem, atypeFactory); + /** + * Returns true if type is an array type whose innermost component type is a type variable. + * + * @param type the type to test + * @return true if type is an array type whose innermost component type is a type variable + */ + private static boolean isGenericArrayType(AnnotatedTypeMirror type) { + return type instanceof AnnotatedArrayType + && AnnotatedTypes.innerMostType(type) instanceof AnnotatedTypeVariable; + } - List annotations = getAnnotations(useElem, declarationElem); + /** The generic array type, if any. */ + // In order to avoid sprinkling code for type parameter uses all over the various locations + // uses can show up, we also handle generic array types. T [] myTArr; + private final @Nullable AnnotatedArrayType arrayType; + + /** The type variable. */ + private final AnnotatedTypeVariable typeVariable; + + /** The element for the declaration. */ + private final TypeParameterElement declarationElem; + + /** The element for the use. */ + private final Element useElem; + + /** The annotated type factory. */ + private final AnnotatedTypeFactory atypeFactory; + + /** + * Create a new TypeVarUseApplier. + * + * @param type the type of the variable use + * @param element the element for the variable use + * @param atypeFactory the type factory + */ + /*package-private*/ TypeVarUseApplier( + AnnotatedTypeMirror type, Element element, AnnotatedTypeFactory atypeFactory) { + if (!accepts(type, element)) { + throw new BugInCF( + "TypeParamUseApplier does not accept type/element combination (" + + " type ( " + + type + + " ) element ( " + + element + + " ) "); + } - List typeVarAnnotations; - if (arrayType != null) { - // if the outer-most type is an array type then we want to ensure the outer annotations - // are not applied as the type variables primary annotation - typeVarAnnotations = removeComponentAnnotations(arrayType, annotations); - ElementAnnotationUtil.annotateViaTypeAnnoPosition(arrayType, annotations); - } else { - typeVarAnnotations = annotations; + if (isGenericArrayType(type)) { + this.arrayType = (AnnotatedArrayType) type; + this.typeVariable = (AnnotatedTypeVariable) AnnotatedTypes.innerMostType(type); + this.declarationElem = + (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); + this.useElem = element; + this.atypeFactory = atypeFactory; + } else { + this.arrayType = null; + this.typeVariable = (AnnotatedTypeVariable) type; + this.declarationElem = + (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); + this.useElem = element; + this.atypeFactory = atypeFactory; + } } - for (Attribute.TypeCompound annotation : typeVarAnnotations) { - typeVariable.replaceAnnotation(annotation); - } - } - - /** - * Return the annotations that apply to the base component of the array and remove these - * annotations from the parameter. - * - * @param arrayType the array type - * @param annotations the annotations to inspect and modify - * @return the annotations that apply to the base component of the array - */ - private static List removeComponentAnnotations( - AnnotatedArrayType arrayType, List annotations) { - List componentAnnotations = new ArrayList<>(); - - for (int i = 0; i < annotations.size(); ) { - Attribute.TypeCompound anno = annotations.get(i); - if (isBaseComponent(arrayType, anno)) { - componentAnnotations.add(anno); - annotations.remove(anno); - } else { - i++; - } - } + /** + * Applies the bound annotations from the declaration of the type parameter and then applies the + * explicit annotations written on the type variable. + * + * @throws UnexpectedAnnotationLocationException if invalid location for an annotation was found + */ + public void extractAndApply() throws UnexpectedAnnotationLocationException { + if (arrayType != null) { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + arrayType, useElem.getAnnotationMirrors()); + } else { + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + typeVariable, useElem.getAnnotationMirrors()); + } + + // apply annotations from the type parameter declaration + ElementAnnotationApplier.apply(typeVariable, declarationElem, atypeFactory); + + List annotations = getAnnotations(useElem, declarationElem); - return componentAnnotations; - } - - /** - * Return true if anno applies to the base component of arrayType. - * - * @param arrayType the array type - * @param anno the annotation to inspect - * @return true if anno applies to the base component of arrayType - */ - private static boolean isBaseComponent( - AnnotatedArrayType arrayType, Attribute.TypeCompound anno) { - try { - return ElementAnnotationUtil.getTypeAtLocation(arrayType, anno.getPosition().location) - .getKind() - == TypeKind.TYPEVAR; - } catch (UnexpectedAnnotationLocationException ex) { - return false; + List typeVarAnnotations; + if (arrayType != null) { + // if the outer-most type is an array type then we want to ensure the outer annotations + // are not applied as the type variables primary annotation + typeVarAnnotations = removeComponentAnnotations(arrayType, annotations); + ElementAnnotationUtil.annotateViaTypeAnnoPosition(arrayType, annotations); + } else { + typeVarAnnotations = annotations; + } + + for (Attribute.TypeCompound annotation : typeVarAnnotations) { + typeVariable.replaceAnnotation(annotation); + } } - } - - /** - * Depending on what element type the annotations are stored on, the relevant annotations might be - * stored with different annotation positions. getAnnotations finds the correct annotations by - * annotation position and element kind and returns them - */ - private static List getAnnotations( - Element useElem, Element declarationElem) { - final List annotations; - switch (useElem.getKind()) { - case METHOD: - annotations = getReturnAnnos(useElem); - break; - - case PARAMETER: - annotations = getParameterAnnos(useElem); - break; - - case FIELD: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - annotations = getVariableAnnos(useElem); - break; - - default: - throw new BugInCF( - "TypeVarUseApplier::extractAndApply : " - + "Unhandled element kind " - + useElem.getKind() - + "useElem ( " - + useElem - + " ) " - + "declarationElem ( " - + declarationElem - + " ) "); + + /** + * Return the annotations that apply to the base component of the array and remove these + * annotations from the parameter. + * + * @param arrayType the array type + * @param annotations the annotations to inspect and modify + * @return the annotations that apply to the base component of the array + */ + private static List removeComponentAnnotations( + AnnotatedArrayType arrayType, List annotations) { + List componentAnnotations = new ArrayList<>(); + + for (int i = 0; i < annotations.size(); ) { + Attribute.TypeCompound anno = annotations.get(i); + if (isBaseComponent(arrayType, anno)) { + componentAnnotations.add(anno); + annotations.remove(anno); + } else { + i++; + } + } + + return componentAnnotations; } - return annotations; - } - - /** - * Returns annotations on an element that apply to variable declarations. - * - * @param variableElem the element whose annotations to check - * @return annotations on an element that apply to variable declarations - */ - private static List getVariableAnnos(Element variableElem) { - VarSymbol varSymbol = (VarSymbol) variableElem; - List annotations = new ArrayList<>(); - - for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) { - TypeAnnotationPosition pos = anno.position; - switch (pos.type) { - case FIELD: - case LOCAL_VARIABLE: - case RESOURCE_VARIABLE: - case EXCEPTION_PARAMETER: - annotations.add(anno); - break; - - default: - } + /** + * Return true if anno applies to the base component of arrayType. + * + * @param arrayType the array type + * @param anno the annotation to inspect + * @return true if anno applies to the base component of arrayType + */ + private static boolean isBaseComponent( + AnnotatedArrayType arrayType, Attribute.TypeCompound anno) { + try { + return ElementAnnotationUtil.getTypeAtLocation(arrayType, anno.getPosition().location) + .getKind() + == TypeKind.TYPEVAR; + } catch (UnexpectedAnnotationLocationException ex) { + return false; + } } - return annotations; - } - - /** - * Currently, the metadata for storing annotations (i.e. the Attribute.TypeCompounds) is null for - * binary-only parameters and type parameters. However, it is present on the method. So in order - * to ensure that we correctly retrieve the annotations we need to index from the method and - * retrieve the annotations from its metadata. - * - * @return a list of annotations that were found on METHOD_FORMAL_PARAMETERS that match the - * parameter index of the input element in the parent methods formal parameter list - */ - private static List getParameterAnnos(Element paramElem) { - Element enclosingElement = paramElem.getEnclosingElement(); - if (!(enclosingElement instanceof ExecutableElement)) { - throw new BugInCF( - "Bad element passed to TypeFromElement.getTypeParameterAnnotationAttributes: " - + "element: " - + paramElem - + " not found in enclosing executable: " - + enclosingElement); + /** + * Depending on what element type the annotations are stored on, the relevant annotations might + * be stored with different annotation positions. getAnnotations finds the correct annotations + * by annotation position and element kind and returns them + */ + private static List getAnnotations( + Element useElem, Element declarationElem) { + final List annotations; + switch (useElem.getKind()) { + case METHOD: + annotations = getReturnAnnos(useElem); + break; + + case PARAMETER: + annotations = getParameterAnnos(useElem); + break; + + case FIELD: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + annotations = getVariableAnnos(useElem); + break; + + default: + throw new BugInCF( + "TypeVarUseApplier::extractAndApply : " + + "Unhandled element kind " + + useElem.getKind() + + "useElem ( " + + useElem + + " ) " + + "declarationElem ( " + + declarationElem + + " ) "); + } + + return annotations; } - MethodSymbol enclosingMethod = (MethodSymbol) enclosingElement; + /** + * Returns annotations on an element that apply to variable declarations. + * + * @param variableElem the element whose annotations to check + * @return annotations on an element that apply to variable declarations + */ + private static List getVariableAnnos(Element variableElem) { + VarSymbol varSymbol = (VarSymbol) variableElem; + List annotations = new ArrayList<>(); + + for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) { + TypeAnnotationPosition pos = anno.position; + switch (pos.type) { + case FIELD: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case EXCEPTION_PARAMETER: + annotations.add(anno); + break; + + default: + } + } - if (enclosingMethod.getKind() != ElementKind.CONSTRUCTOR - && enclosingMethod.getKind() != ElementKind.METHOD) { - // Initializer blocks don't have parameters, so there is nothing to do. - return Collections.emptyList(); + return annotations; } - // TODO: for the parameter in a lambda expression, the enclosingMethod isn't - // the lambda expression. Does this read the correct annotations? + /** + * Currently, the metadata for storing annotations (i.e. the Attribute.TypeCompounds) is null + * for binary-only parameters and type parameters. However, it is present on the method. So in + * order to ensure that we correctly retrieve the annotations we need to index from the method + * and retrieve the annotations from its metadata. + * + * @return a list of annotations that were found on METHOD_FORMAL_PARAMETERS that match the + * parameter index of the input element in the parent methods formal parameter list + */ + private static List getParameterAnnos(Element paramElem) { + Element enclosingElement = paramElem.getEnclosingElement(); + if (!(enclosingElement instanceof ExecutableElement)) { + throw new BugInCF( + "Bad element passed to TypeFromElement.getTypeParameterAnnotationAttributes: " + + "element: " + + paramElem + + " not found in enclosing executable: " + + enclosingElement); + } - int paramIndex = enclosingMethod.getParameters().indexOf(paramElem); - List annotations = enclosingMethod.getRawTypeAttributes(); + MethodSymbol enclosingMethod = (MethodSymbol) enclosingElement; - List result = new ArrayList<>(); - for (Attribute.TypeCompound typeAnno : annotations) { - if (typeAnno.position.type == TargetType.METHOD_FORMAL_PARAMETER) { - if (typeAnno.position.parameter_index == paramIndex) { - result.add(typeAnno); + if (enclosingMethod.getKind() != ElementKind.CONSTRUCTOR + && enclosingMethod.getKind() != ElementKind.METHOD) { + // Initializer blocks don't have parameters, so there is nothing to do. + return Collections.emptyList(); } - } - } - return result; - } - - /** - * Returns the annotations on the return type of the input ExecutableElement. - * - * @param methodElem the method whose return type annotations to return - * @return the annotations on the return type of the input ExecutableElement - */ - private static List getReturnAnnos(Element methodElem) { - if (!(methodElem instanceof ExecutableElement)) { - throw new BugInCF("Bad element passed to TypeVarUseApplier.getReturnAnnos:" + methodElem); - } + // TODO: for the parameter in a lambda expression, the enclosingMethod isn't + // the lambda expression. Does this read the correct annotations? + + int paramIndex = enclosingMethod.getParameters().indexOf(paramElem); + List annotations = enclosingMethod.getRawTypeAttributes(); - MethodSymbol enclosingMethod = (MethodSymbol) methodElem; + List result = new ArrayList<>(); + for (Attribute.TypeCompound typeAnno : annotations) { + if (typeAnno.position.type == TargetType.METHOD_FORMAL_PARAMETER) { + if (typeAnno.position.parameter_index == paramIndex) { + result.add(typeAnno); + } + } + } - List annotations = enclosingMethod.getRawTypeAttributes(); - List result = new ArrayList<>(); - for (Attribute.TypeCompound typeAnno : annotations) { - if (typeAnno.position.type == TargetType.METHOD_RETURN) { - result.add(typeAnno); - } + return result; } - return result; - } + /** + * Returns the annotations on the return type of the input ExecutableElement. + * + * @param methodElem the method whose return type annotations to return + * @return the annotations on the return type of the input ExecutableElement + */ + private static List getReturnAnnos(Element methodElem) { + if (!(methodElem instanceof ExecutableElement)) { + throw new BugInCF( + "Bad element passed to TypeVarUseApplier.getReturnAnnos:" + methodElem); + } + + MethodSymbol enclosingMethod = (MethodSymbol) methodElem; + + List annotations = enclosingMethod.getRawTypeAttributes(); + List result = new ArrayList<>(); + for (Attribute.TypeCompound typeAnno : annotations) { + if (typeAnno.position.type == TargetType.METHOD_RETURN) { + result.add(typeAnno); + } + } + + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java index 9b17202be37..27012830236 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java @@ -4,129 +4,133 @@ import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.TargetType; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; +import org.checkerframework.javacutil.BugInCF; + import java.util.List; + import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.type.TypeKind; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; -import org.checkerframework.javacutil.BugInCF; /** * Applies annotations to variable declaration (providing they are not the use of a TYPE_PARAMETER). */ public class VariableApplier extends TargetedElementAnnotationApplier { - /** - * Apply annotations from {@code element} to {@code type}. - * - * @param type the type to annotate - * @param element the corresponding element - * @throws UnexpectedAnnotationLocationException if there is trouble - */ - public static void apply(AnnotatedTypeMirror type, Element element) - throws UnexpectedAnnotationLocationException { - new VariableApplier(type, element).extractAndApply(); - } - - /** The accepted element kinds. */ - private static final ElementKind[] acceptedKinds = { - ElementKind.LOCAL_VARIABLE, ElementKind.RESOURCE_VARIABLE, ElementKind.EXCEPTION_PARAMETER - }; - - /** - * Returns true if this is a variable declaration including fields an enum constants. - * - * @param typeMirror ignored - * @return true if this is a variable declaration including fields an enum constants - */ - public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { - return ElementAnnotationUtil.contains(element.getKind(), acceptedKinds) - || element.getKind().isField(); - } - - /** The variable symbol. */ - private final Symbol.VarSymbol varSymbol; - - /** - * Constructor. - * - * @param type the type to annotate - * @param element the corresponding element - */ - /*package-private*/ VariableApplier(AnnotatedTypeMirror type, Element element) { - super(type, element); - varSymbol = (Symbol.VarSymbol) element; - - if (type.getKind() == TypeKind.UNION && element.getKind() != ElementKind.EXCEPTION_PARAMETER) { - throw new BugInCF( - "Union types only allowed for exception parameters. " - + "Type: " - + type - + " for element: " - + element); + /** + * Apply annotations from {@code element} to {@code type}. + * + * @param type the type to annotate + * @param element the corresponding element + * @throws UnexpectedAnnotationLocationException if there is trouble + */ + public static void apply(AnnotatedTypeMirror type, Element element) + throws UnexpectedAnnotationLocationException { + new VariableApplier(type, element).extractAndApply(); + } + + /** The accepted element kinds. */ + private static final ElementKind[] acceptedKinds = { + ElementKind.LOCAL_VARIABLE, ElementKind.RESOURCE_VARIABLE, ElementKind.EXCEPTION_PARAMETER + }; + + /** + * Returns true if this is a variable declaration including fields an enum constants. + * + * @param typeMirror ignored + * @return true if this is a variable declaration including fields an enum constants + */ + public static boolean accepts(AnnotatedTypeMirror typeMirror, Element element) { + return ElementAnnotationUtil.contains(element.getKind(), acceptedKinds) + || element.getKind().isField(); + } + + /** The variable symbol. */ + private final Symbol.VarSymbol varSymbol; + + /** + * Constructor. + * + * @param type the type to annotate + * @param element the corresponding element + */ + /*package-private*/ VariableApplier(AnnotatedTypeMirror type, Element element) { + super(type, element); + varSymbol = (Symbol.VarSymbol) element; + + if (type.getKind() == TypeKind.UNION + && element.getKind() != ElementKind.EXCEPTION_PARAMETER) { + throw new BugInCF( + "Union types only allowed for exception parameters. " + + "Type: " + + type + + " for element: " + + element); + } + // TODO: need a way to split the union types into the right alternative + // to use for the annotation. The exception_index is probably what we + // need to look at, but it might not be set at this point. + } + + /** The annotated targets. */ + private static final TargetType[] annotatedTargets = + new TargetType[] { + TargetType.LOCAL_VARIABLE, + TargetType.RESOURCE_VARIABLE, + TargetType.EXCEPTION_PARAMETER, + TargetType.FIELD + }; + + @Override + protected TargetType[] annotatedTargets() { + return annotatedTargets; + } + + /** The valid targets. */ + private static final TargetType[] validTargets = + new TargetType[] { + TargetType.NEW, + TargetType.CAST, + TargetType.INSTANCEOF, + TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, + TargetType.METHOD_REFERENCE, + TargetType.CONSTRUCTOR_REFERENCE, + TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, + TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, + TargetType.METHOD_FORMAL_PARAMETER, + TargetType.CLASS_EXTENDS + }; + + @Override + protected TargetType[] validTargets() { + return validTargets; + } + + @Override + protected Iterable getRawTypeAttributes() { + return varSymbol.getRawTypeAttributes(); + } + + @Override + protected boolean isAccepted() { + return accepts(type, element); + } + + @Override + protected void handleTargeted(List targeted) + throws UnexpectedAnnotationLocationException { + ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); + } + + @Override + public void extractAndApply() throws UnexpectedAnnotationLocationException { + // Add declaration annotations to the local variable type + ElementAnnotationUtil.addDeclarationAnnotationsFromElement( + type, varSymbol.getAnnotationMirrors()); + super.extractAndApply(); } - // TODO: need a way to split the union types into the right alternative - // to use for the annotation. The exception_index is probably what we - // need to look at, but it might not be set at this point. - } - - /** The annotated targets. */ - private static final TargetType[] annotatedTargets = - new TargetType[] { - TargetType.LOCAL_VARIABLE, - TargetType.RESOURCE_VARIABLE, - TargetType.EXCEPTION_PARAMETER, - TargetType.FIELD - }; - - @Override - protected TargetType[] annotatedTargets() { - return annotatedTargets; - } - - /** The valid targets. */ - private static final TargetType[] validTargets = - new TargetType[] { - TargetType.NEW, - TargetType.CAST, - TargetType.INSTANCEOF, - TargetType.METHOD_INVOCATION_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT, - TargetType.METHOD_REFERENCE, - TargetType.CONSTRUCTOR_REFERENCE, - TargetType.METHOD_REFERENCE_TYPE_ARGUMENT, - TargetType.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT, - TargetType.METHOD_FORMAL_PARAMETER, - TargetType.CLASS_EXTENDS - }; - - @Override - protected TargetType[] validTargets() { - return validTargets; - } - - @Override - protected Iterable getRawTypeAttributes() { - return varSymbol.getRawTypeAttributes(); - } - - @Override - protected boolean isAccepted() { - return accepts(type, element); - } - - @Override - protected void handleTargeted(List targeted) - throws UnexpectedAnnotationLocationException { - ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); - } - - @Override - public void extractAndApply() throws UnexpectedAnnotationLocationException { - // Add declaration annotations to the local variable type - ElementAnnotationUtil.addDeclarationAnnotationsFromElement( - type, varSymbol.getAnnotationMirrors()); - super.extractAndApply(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java index 0def0d9fcdb..1c63a0958c3 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java @@ -7,258 +7,273 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.util.TreePath; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.util.typeinference8.types.ContainsInferenceVariable; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.List; + import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.util.typeinference8.types.ContainsInferenceVariable; -import org.checkerframework.framework.util.typeinference8.types.Variable; -import org.checkerframework.framework.util.typeinference8.util.Theta; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; /** Implementation of type argument inference. */ public class DefaultTypeArgumentInference implements TypeArgumentInference { - /** Current inference problem that is being solved. */ - private InvocationTypeInference java8Inference = null; + /** Current inference problem that is being solved. */ + private InvocationTypeInference java8Inference = null; - /** Stack of all inference problems currently being solved. */ - private final ArrayDeque java8InferenceStack = new ArrayDeque<>(); + /** Stack of all inference problems currently being solved. */ + private final ArrayDeque java8InferenceStack = new ArrayDeque<>(); - /** Creates a DefaultTypeArgumentInference. */ - public DefaultTypeArgumentInference() {} + /** Creates a DefaultTypeArgumentInference. */ + public DefaultTypeArgumentInference() {} - @SuppressWarnings("interning:not.interned") - @Override - public InferenceResult inferTypeArgs( - AnnotatedTypeFactory typeFactory, - ExpressionTree expressionTree, - AnnotatedExecutableType methodType) { - TreePath pathToExpression = typeFactory.getPath(expressionTree); + @SuppressWarnings("interning:not.interned") + @Override + public InferenceResult inferTypeArgs( + AnnotatedTypeFactory typeFactory, + ExpressionTree expressionTree, + AnnotatedExecutableType methodType) { + TreePath pathToExpression = typeFactory.getPath(expressionTree); - // In order to find the type arguments for expressionTree, type arguments for outer method - // calls may need be inferred, too. - // So, first find the outermost tree that is required to infer the type arguments for - // expressionTree - ExpressionTree outerTree = outerInference(expressionTree, pathToExpression.getParentPath()); + // In order to find the type arguments for expressionTree, type arguments for outer method + // calls may need be inferred, too. + // So, first find the outermost tree that is required to infer the type arguments for + // expressionTree + ExpressionTree outerTree = outerInference(expressionTree, pathToExpression.getParentPath()); - for (InvocationTypeInference i : java8InferenceStack) { - if (i.getInferenceExpression() == outerTree) { - // Inference is running and is asking for the type of the method before type - // arguments are substituted. So don't infer any type arguments. This happens when - // getting the type of a lambda's returned expression. - List instantiated = new ArrayList<>(); - Theta m = i.context.maps.get(expressionTree); - if (m == null) { - return InferenceResult.emptyResult(); + for (InvocationTypeInference i : java8InferenceStack) { + if (i.getInferenceExpression() == outerTree) { + // Inference is running and is asking for the type of the method before type + // arguments are substituted. So don't infer any type arguments. This happens when + // getting the type of a lambda's returned expression. + List instantiated = new ArrayList<>(); + Theta m = i.context.maps.get(expressionTree); + if (m == null) { + return InferenceResult.emptyResult(); + } + m.values() + .forEach( + var -> { + if (var.getInstantiation() != null) { + instantiated.add(var); + } + }); + if (instantiated.isEmpty()) { + return InferenceResult.emptyResult(); + } + return new InferenceResult(instantiated, false, false, ""); + } } - m.values() - .forEach( - var -> { - if (var.getInstantiation() != null) { - instantiated.add(var); - } - }); - if (instantiated.isEmpty()) { - return InferenceResult.emptyResult(); + AnnotatedExecutableType outerMethodType; + if (outerTree != expressionTree) { + if (outerTree.getKind() == Tree.Kind.METHOD_INVOCATION) { + pathToExpression = typeFactory.getPath(outerTree); + outerMethodType = + typeFactory.methodFromUseWithoutTypeArgInference( + (MethodInvocationTree) outerTree) + .executableType; + } else if (outerTree.getKind() == Tree.Kind.NEW_CLASS) { + pathToExpression = typeFactory.getPath(outerTree); + outerMethodType = + typeFactory.constructorFromUseWithoutTypeArgInference( + (NewClassTree) outerTree) + .executableType; + } else if (outerTree.getKind() == Kind.MEMBER_REFERENCE) { + pathToExpression = typeFactory.getPath(outerTree); + outerMethodType = null; + } else { + throw new BugInCF( + "Unexpected kind of outer expression to infer type arguments: %s", + outerTree.getKind()); + } + } else { + outerMethodType = methodType; } - return new InferenceResult(instantiated, false, false, ""); - } - } - AnnotatedExecutableType outerMethodType; - if (outerTree != expressionTree) { - if (outerTree.getKind() == Tree.Kind.METHOD_INVOCATION) { - pathToExpression = typeFactory.getPath(outerTree); - outerMethodType = - typeFactory.methodFromUseWithoutTypeArgInference((MethodInvocationTree) outerTree) - .executableType; - } else if (outerTree.getKind() == Tree.Kind.NEW_CLASS) { - pathToExpression = typeFactory.getPath(outerTree); - outerMethodType = - typeFactory.constructorFromUseWithoutTypeArgInference((NewClassTree) outerTree) - .executableType; - } else if (outerTree.getKind() == Kind.MEMBER_REFERENCE) { - pathToExpression = typeFactory.getPath(outerTree); - outerMethodType = null; - } else { - throw new BugInCF( - "Unexpected kind of outer expression to infer type arguments: %s", outerTree.getKind()); - } - } else { - outerMethodType = methodType; - } - if (java8Inference != null) { - java8InferenceStack.push(java8Inference); - } - try { - java8Inference = new InvocationTypeInference(typeFactory, pathToExpression); - if (outerTree.getKind() == Kind.MEMBER_REFERENCE) { - return java8Inference.infer((MemberReferenceTree) outerTree); - } else { - InferenceResult result = java8Inference.infer(outerTree, outerMethodType); - if (!result.getResults().containsKey(expressionTree) - && expressionTree.getKind() == Kind.MEMBER_REFERENCE) { - java8Inference.context.pathToExpression = typeFactory.getPath(expressionTree); - return java8Inference.infer((MemberReferenceTree) expressionTree); + if (java8Inference != null) { + java8InferenceStack.push(java8Inference); + } + try { + java8Inference = new InvocationTypeInference(typeFactory, pathToExpression); + if (outerTree.getKind() == Kind.MEMBER_REFERENCE) { + return java8Inference.infer((MemberReferenceTree) outerTree); + } else { + InferenceResult result = java8Inference.infer(outerTree, outerMethodType); + if (!result.getResults().containsKey(expressionTree) + && expressionTree.getKind() == Kind.MEMBER_REFERENCE) { + java8Inference.context.pathToExpression = typeFactory.getPath(expressionTree); + return java8Inference.infer((MemberReferenceTree) expressionTree); + } + return result.swapTypeVariables(methodType, expressionTree); + } + } catch (Exception ex) { + if (typeFactory + .getChecker() + .getBooleanOption("convertTypeArgInferenceCrashToWarning", true)) { + // This should never happen, if javac infers type arguments so should the Checker + // Framework. However, given how buggy javac inference is, this probably will, so + // deal + // with it gracefully. + return new InferenceResult( + Collections.emptyList(), + false, + true, + "An exception occurred: " + ex.getLocalizedMessage()); + } + throw ex; + } finally { + if (!java8InferenceStack.isEmpty()) { + java8Inference = java8InferenceStack.pop(); + } else { + java8Inference = null; + } } - return result.swapTypeVariables(methodType, expressionTree); - } - } catch (Exception ex) { - if (typeFactory - .getChecker() - .getBooleanOption("convertTypeArgInferenceCrashToWarning", true)) { - // This should never happen, if javac infers type arguments so should the Checker - // Framework. However, given how buggy javac inference is, this probably will, so deal - // with it gracefully. - return new InferenceResult( - Collections.emptyList(), - false, - true, - "An exception occurred: " + ex.getLocalizedMessage()); - } - throw ex; - } finally { - if (!java8InferenceStack.isEmpty()) { - java8Inference = java8InferenceStack.pop(); - } else { - java8Inference = null; - } - } - } - - /** - * Returns the outermost tree required to find the type of {@code tree}. - * - * @param tree tree that may need an outer tree to find the type - * @param parentPath path to the parent of {@code tree} or null if no such parent exists - * @return the outermost tree required to find the type of {@code tree} - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - public static ExpressionTree outerInference(ExpressionTree tree, @Nullable TreePath parentPath) { - if (parentPath == null) { - return tree; - } - if (!TreeUtils.isPolyExpression(tree)) { - return tree; } - Tree parentTree = parentPath.getLeaf(); - switch (parentTree.getKind()) { - case PARENTHESIZED: - case CONDITIONAL_EXPRESSION: - ExpressionTree outer = - outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); - if (outer == parentTree) { - return tree; - } - return outer; - case METHOD_INVOCATION: - MethodInvocationTree methodInvocationTree = (MethodInvocationTree) parentTree; - if (!methodInvocationTree.getTypeArguments().isEmpty()) { - return tree; - } - ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); - if (methodElement.getTypeParameters().isEmpty()) { - return tree; - } - if (argumentNeedsInference( - methodElement, methodInvocationTree.getArguments(), tree, null)) { - return outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); - } - return tree; - case NEW_CLASS: - NewClassTree newClassTree = (NewClassTree) parentTree; - if (!newClassTree.getTypeArguments().isEmpty()) { - return tree; - } - ExecutableElement constructor = TreeUtils.elementFromUse(newClassTree); - if (argumentNeedsInference(constructor, newClassTree.getArguments(), tree, newClassTree)) { - return outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); - } - return tree; - case RETURN: - TreePath parentParentPath = parentPath.getParentPath(); - if (parentParentPath.getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - return outerInference( - (ExpressionTree) parentParentPath.getLeaf(), parentParentPath.getParentPath()); - } - return tree; - default: - if (TreeUtils.isYield(parentTree)) { - parentPath = parentPath.getParentPath(); - // The first parent is a case statement. - parentPath = parentPath.getParentPath(); - parentTree = parentPath.getLeaf(); + /** + * Returns the outermost tree required to find the type of {@code tree}. + * + * @param tree tree that may need an outer tree to find the type + * @param parentPath path to the parent of {@code tree} or null if no such parent exists + * @return the outermost tree required to find the type of {@code tree} + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public static ExpressionTree outerInference( + ExpressionTree tree, @Nullable TreePath parentPath) { + if (parentPath == null) { + return tree; } - if (TreeUtils.isSwitchExpression(parentTree)) { - // case SWITCH_EXPRESSION: - ExpressionTree outerTree = - outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); - if (outerTree == parentTree) { + if (!TreeUtils.isPolyExpression(tree)) { return tree; - } - return outerTree; } - return tree; - } - } - /** - * Returns true if {@code argTree} is pseudo-assigned to a parameter in {@code executableElement} - * that contains a type variable that needs to be inferred. - * - * @param executableElement symbol of method or constructor - * @param argTrees all the arguments of the method or constructor call - * @param argTree the argument of interest - * @param newClassTree the new class tree or {@code null} if {@code executableElement} is a method - * @return true if {@code argTree} is pseudo-assigned to a parameter in {@code executableElement} - * that contains a type variable that needs to be inferred. - */ - private static boolean argumentNeedsInference( - ExecutableElement executableElement, - List argTrees, - Tree argTree, - @Nullable NewClassTree newClassTree) { - int index = -1; - for (int i = 0; i < argTrees.size(); i++) { - @SuppressWarnings("interning") // looking for exact argTree. - boolean found = argTrees.get(i) == argTree; - if (found) { - index = i; - } - } - if (index == -1) { - throw new BugInCF("Argument argTree not found in list of arguments."); + Tree parentTree = parentPath.getLeaf(); + switch (parentTree.getKind()) { + case PARENTHESIZED: + case CONDITIONAL_EXPRESSION: + ExpressionTree outer = + outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + if (outer == parentTree) { + return tree; + } + return outer; + case METHOD_INVOCATION: + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) parentTree; + if (!methodInvocationTree.getTypeArguments().isEmpty()) { + return tree; + } + ExecutableElement methodElement = TreeUtils.elementFromUse(methodInvocationTree); + if (methodElement.getTypeParameters().isEmpty()) { + return tree; + } + if (argumentNeedsInference( + methodElement, methodInvocationTree.getArguments(), tree, null)) { + return outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + } + return tree; + case NEW_CLASS: + NewClassTree newClassTree = (NewClassTree) parentTree; + if (!newClassTree.getTypeArguments().isEmpty()) { + return tree; + } + ExecutableElement constructor = TreeUtils.elementFromUse(newClassTree); + if (argumentNeedsInference( + constructor, newClassTree.getArguments(), tree, newClassTree)) { + return outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + } + return tree; + case RETURN: + TreePath parentParentPath = parentPath.getParentPath(); + if (parentParentPath.getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + return outerInference( + (ExpressionTree) parentParentPath.getLeaf(), + parentParentPath.getParentPath()); + } + return tree; + default: + if (TreeUtils.isYield(parentTree)) { + parentPath = parentPath.getParentPath(); + // The first parent is a case statement. + parentPath = parentPath.getParentPath(); + parentTree = parentPath.getLeaf(); + } + if (TreeUtils.isSwitchExpression(parentTree)) { + // case SWITCH_EXPRESSION: + ExpressionTree outerTree = + outerInference((ExpressionTree) parentTree, parentPath.getParentPath()); + if (outerTree == parentTree) { + return tree; + } + return outerTree; + } + return tree; + } } - ExecutableType executableType = (ExecutableType) executableElement.asType(); - // There are fewer parameters than arguments if this is a var args method. - if (executableType.getParameterTypes().size() <= index) { - index = executableType.getParameterTypes().size() - 1; - } - TypeMirror param = executableType.getParameterTypes().get(index); + /** + * Returns true if {@code argTree} is pseudo-assigned to a parameter in {@code + * executableElement} that contains a type variable that needs to be inferred. + * + * @param executableElement symbol of method or constructor + * @param argTrees all the arguments of the method or constructor call + * @param argTree the argument of interest + * @param newClassTree the new class tree or {@code null} if {@code executableElement} is a + * method + * @return true if {@code argTree} is pseudo-assigned to a parameter in {@code + * executableElement} that contains a type variable that needs to be inferred. + */ + private static boolean argumentNeedsInference( + ExecutableElement executableElement, + List argTrees, + Tree argTree, + @Nullable NewClassTree newClassTree) { + int index = -1; + for (int i = 0; i < argTrees.size(); i++) { + @SuppressWarnings("interning") // looking for exact argTree. + boolean found = argTrees.get(i) == argTree; + if (found) { + index = i; + } + } + if (index == -1) { + throw new BugInCF("Argument argTree not found in list of arguments."); + } + + ExecutableType executableType = (ExecutableType) executableElement.asType(); + // There are fewer parameters than arguments if this is a var args method. + if (executableType.getParameterTypes().size() <= index) { + index = executableType.getParameterTypes().size() - 1; + } + TypeMirror param = executableType.getParameterTypes().get(index); - if (executableElement.getKind() == ElementKind.CONSTRUCTOR) { - List list = new ArrayList<>(executableType.getTypeVariables()); - if (newClassTree != null && TreeUtils.isDiamondTree(newClassTree)) { - DeclaredType declaredType = - (DeclaredType) ((DeclaredType) TreeUtils.typeOf(newClassTree)).asElement().asType(); - for (TypeMirror typeVar : declaredType.getTypeArguments()) { - list.add((TypeVariable) typeVar); + if (executableElement.getKind() == ElementKind.CONSTRUCTOR) { + List list = new ArrayList<>(executableType.getTypeVariables()); + if (newClassTree != null && TreeUtils.isDiamondTree(newClassTree)) { + DeclaredType declaredType = + (DeclaredType) + ((DeclaredType) TreeUtils.typeOf(newClassTree)) + .asElement() + .asType(); + for (TypeMirror typeVar : declaredType.getTypeArguments()) { + list.add((TypeVariable) typeVar); + } + } + return ContainsInferenceVariable.hasAnyTypeVariable(list, param); } - } - return ContainsInferenceVariable.hasAnyTypeVariable(list, param); + return ContainsInferenceVariable.hasAnyTypeVariable( + executableType.getTypeVariables(), param); } - return ContainsInferenceVariable.hasAnyTypeVariable(executableType.getTypeVariables(), param); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java index 59d2259cd9c..eaf950113d4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InferenceResult.java @@ -2,12 +2,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -15,149 +10,160 @@ import org.checkerframework.framework.util.typeinference8.types.Variable; import org.checkerframework.javacutil.TypesUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import javax.lang.model.type.TypeVariable; + /** The result of type argument inferrece. */ public class InferenceResult { - /** An empty inference result. */ - @SuppressWarnings("interning:assignment") - private static final @InternedDistinct InferenceResult emptyResult = - new InferenceResult(Collections.emptyList(), false, false, ""); - - /** - * Returns an empty inference result. - * - * @return an empty inference result - */ - public static InferenceResult emptyResult() { - return emptyResult; - } - - /** - * A mapping from a tree that needs type argument inference to a map from type parameter to its - * inferred annotated type argument. If inference failed, this map will be empty. - */ - private final Map> results; - - /** - * If true, then type argument inference failed because an annotated type could not be inferred. - */ - private final boolean annoInferenceFailed; - - /** Whether unchecked conversion was necessary to infer the type arguments. */ - private final boolean uncheckedConversion; - - /** If {@code annoInferenceFailed}, then this is the error message to report to the user. */ - private final String errorMsg; - - /** - * Creates an inference result. - * - * @param variables instantiated variables - * @param uncheckedConversion where unchecked conversion was required to infer the type arguments - * @param annoInferenceFailed whether inference failed because of annotations - * @param errorMsg message to report to users if inference failed - */ - public InferenceResult( - Collection variables, - boolean uncheckedConversion, - boolean annoInferenceFailed, - String errorMsg) { - this.results = convert(variables); - this.uncheckedConversion = uncheckedConversion; - this.annoInferenceFailed = annoInferenceFailed; - this.errorMsg = errorMsg; - } - - /** - * A mapping from a tree that needs type argument inference to a map from type parameter to its - * inferred annotated type argument. If inference failed, this map will be empty. - * - * @return mapping from a tree that needs type argument inference to a map from type parameter to - * its inferred annotated type argument - */ - public Map> getResults() { - return results; - } - - /** - * Whether unchecked conversion was necessary to infer the type arguments. - * - * @return whether unchecked conversion was necessary to infer the type arguments - */ - public boolean isUncheckedConversion() { - return uncheckedConversion; - } - - /** - * Whether type argument inference failed because an annotated type could not be inferred. - * - * @return Whether type argument inference failed because an annotated type could not be inferred - */ - public boolean inferenceFailed() { - return annoInferenceFailed; - } - - /** - * Convert the instantiated variables to a map from expression tree to a map from type variable to - * its type argument. - * - * @param variables instantiated variables - * @return a map from expression tree to a map from type variable to its type argument - */ - private static Map> convert( - Collection variables) { - Map> map = new HashMap<>(); - for (Variable variable : variables) { - Map typeMap = - map.computeIfAbsent(variable.getInvocation(), k -> new HashMap<>()); - typeMap.put(variable.getJavaType(), variable.getInstantiation().getAnnotatedType()); + /** An empty inference result. */ + @SuppressWarnings("interning:assignment") + private static final @InternedDistinct InferenceResult emptyResult = + new InferenceResult(Collections.emptyList(), false, false, ""); + + /** + * Returns an empty inference result. + * + * @return an empty inference result + */ + public static InferenceResult emptyResult() { + return emptyResult; } - return map; - } - - /** - * Returns a mapping from type variable to its type argument for the {@code expressionTree}. - * - * @param expressionTree a tree for which type arguments were inferred - * @return a mapping from type variable to its type argument for the {@code expressionTree} - */ - public Map getTypeArgumentsForExpression( - ExpressionTree expressionTree) { - if (this == emptyResult || results.isEmpty()) { - return Collections.emptyMap(); + + /** + * A mapping from a tree that needs type argument inference to a map from type parameter to its + * inferred annotated type argument. If inference failed, this map will be empty. + */ + private final Map> results; + + /** + * If true, then type argument inference failed because an annotated type could not be inferred. + */ + private final boolean annoInferenceFailed; + + /** Whether unchecked conversion was necessary to infer the type arguments. */ + private final boolean uncheckedConversion; + + /** If {@code annoInferenceFailed}, then this is the error message to report to the user. */ + private final String errorMsg; + + /** + * Creates an inference result. + * + * @param variables instantiated variables + * @param uncheckedConversion where unchecked conversion was required to infer the type + * arguments + * @param annoInferenceFailed whether inference failed because of annotations + * @param errorMsg message to report to users if inference failed + */ + public InferenceResult( + Collection variables, + boolean uncheckedConversion, + boolean annoInferenceFailed, + String errorMsg) { + this.results = convert(variables); + this.uncheckedConversion = uncheckedConversion; + this.annoInferenceFailed = annoInferenceFailed; + this.errorMsg = errorMsg; } - return results.get(expressionTree); - } - - /** - * An error message to report to the user. - * - * @return an error message to report to the user - */ - public String getErrorMsg() { - return errorMsg; - } - - /** - * Switch the {@link TypeVariable}s in {@code results} with the {@code TypeVariable}s in {@code - * methodType} so that the {@code TypeVariable}s in the result are {@code .equals}. {@link - * TypesUtils#areSame(TypeVariable, TypeVariable)} is used to decide which type variables to swap. - * - * @param methodType annotated method type - * @param tree method invocation tree - * @return this - */ - /* package-private */ InferenceResult swapTypeVariables( - AnnotatedExecutableType methodType, ExpressionTree tree) { - Map map = results.get(tree); - for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { - TypeVariable typeVariable = tv.getUnderlyingType(); - for (TypeVariable t : new HashSet<>(map.keySet())) { - if (TypesUtils.areSame(t, typeVariable)) { - map.put(typeVariable, map.remove(t)); + + /** + * A mapping from a tree that needs type argument inference to a map from type parameter to its + * inferred annotated type argument. If inference failed, this map will be empty. + * + * @return mapping from a tree that needs type argument inference to a map from type parameter + * to its inferred annotated type argument + */ + public Map> getResults() { + return results; + } + + /** + * Whether unchecked conversion was necessary to infer the type arguments. + * + * @return whether unchecked conversion was necessary to infer the type arguments + */ + public boolean isUncheckedConversion() { + return uncheckedConversion; + } + + /** + * Whether type argument inference failed because an annotated type could not be inferred. + * + * @return Whether type argument inference failed because an annotated type could not be + * inferred + */ + public boolean inferenceFailed() { + return annoInferenceFailed; + } + + /** + * Convert the instantiated variables to a map from expression tree to a map from type variable + * to its type argument. + * + * @param variables instantiated variables + * @return a map from expression tree to a map from type variable to its type argument + */ + private static Map> convert( + Collection variables) { + Map> map = new HashMap<>(); + for (Variable variable : variables) { + Map typeMap = + map.computeIfAbsent(variable.getInvocation(), k -> new HashMap<>()); + typeMap.put(variable.getJavaType(), variable.getInstantiation().getAnnotatedType()); + } + return map; + } + + /** + * Returns a mapping from type variable to its type argument for the {@code expressionTree}. + * + * @param expressionTree a tree for which type arguments were inferred + * @return a mapping from type variable to its type argument for the {@code expressionTree} + */ + public Map getTypeArgumentsForExpression( + ExpressionTree expressionTree) { + if (this == emptyResult || results.isEmpty()) { + return Collections.emptyMap(); + } + return results.get(expressionTree); + } + + /** + * An error message to report to the user. + * + * @return an error message to report to the user + */ + public String getErrorMsg() { + return errorMsg; + } + + /** + * Switch the {@link TypeVariable}s in {@code results} with the {@code TypeVariable}s in {@code + * methodType} so that the {@code TypeVariable}s in the result are {@code .equals}. {@link + * TypesUtils#areSame(TypeVariable, TypeVariable)} is used to decide which type variables to + * swap. + * + * @param methodType annotated method type + * @param tree method invocation tree + * @return this + */ + /* package-private */ InferenceResult swapTypeVariables( + AnnotatedExecutableType methodType, ExpressionTree tree) { + Map map = results.get(tree); + for (AnnotatedTypeVariable tv : methodType.getTypeVariables()) { + TypeVariable typeVariable = tv.getUnderlyingType(); + for (TypeVariable t : new HashSet<>(map.keySet())) { + if (TypesUtils.areSame(t, typeVariable)) { + map.put(typeVariable, map.remove(t)); + } + } } - } + return this; } - return this; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java index 8a933fd6c51..258b93469ac 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java @@ -8,12 +8,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeKind; + import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -41,6 +36,14 @@ import org.checkerframework.javacutil.SwitchExpressionScanner.FunctionalSwitchExpressionScanner; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; + /** * Performs invocation type inference as described in JLS Section @@ -103,574 +106,585 @@ */ public class InvocationTypeInference { - /** Checker used to issue errors/warnings. */ - protected final SourceChecker checker; - - /** Stores information about the current inference problem being solved. */ - protected final Java8InferenceContext context; - - /** Tree for which type arguments are being inferred. */ - protected final Tree inferenceExpression; - - /** - * Creates an inference problem. - * - * @param factory the annotated type factory to use - * @param pathToExpression path to the expression for which inference is preformed - */ - public InvocationTypeInference(AnnotatedTypeFactory factory, TreePath pathToExpression) { - this.checker = factory.getChecker(); - this.context = new Java8InferenceContext(factory, pathToExpression, this); - this.inferenceExpression = pathToExpression.getLeaf(); - } - - /** - * Returns the tree for which inference is being inferred. - * - * @return the tree for which inference is being inferred - */ - public Tree getInferenceExpression() { - return inferenceExpression; - } - - /** - * Perform invocation type inference on {@code invocation}. See JLS - * 18.5.2. - * - * @param invocation invocation which needs inference - * @param methodType type of the method invocation - * @return the result of inference - * @throws FalseBoundException if inference fails because of the java types - */ - public InferenceResult infer(ExpressionTree invocation, AnnotatedExecutableType methodType) - throws FalseBoundException { - ExecutableType e = methodType.getUnderlyingType(); - InvocationType invocationType = new InvocationType(methodType, e, invocation, context); - ProperType target = context.inferenceTypeFactory.getTargetType(); - List args; - if (invocation.getKind() == Tree.Kind.METHOD_INVOCATION) { - args = ((MethodInvocationTree) invocation).getArguments(); - } else { - args = ((NewClassTree) invocation).getArguments(); + /** Checker used to issue errors/warnings. */ + protected final SourceChecker checker; + + /** Stores information about the current inference problem being solved. */ + protected final Java8InferenceContext context; + + /** Tree for which type arguments are being inferred. */ + protected final Tree inferenceExpression; + + /** + * Creates an inference problem. + * + * @param factory the annotated type factory to use + * @param pathToExpression path to the expression for which inference is preformed + */ + public InvocationTypeInference(AnnotatedTypeFactory factory, TreePath pathToExpression) { + this.checker = factory.getChecker(); + this.context = new Java8InferenceContext(factory, pathToExpression, this); + this.inferenceExpression = pathToExpression.getLeaf(); } - Theta map = - context.inferenceTypeFactory.createThetaForInvocation(invocation, invocationType, context); - BoundSet b2 = createB2(invocationType, args, map); - BoundSet b3; - if (target != null && TreeUtils.isPolyExpression(invocation)) { - b3 = createB3(b2, invocation, invocationType, target, map); - } else { - b3 = b2; - } - ConstraintSet c = createC(invocationType, args, map); - - BoundSet b4 = getB4(b3, c); - b4.resolve(); - return new InferenceResult( - b4.getInstantiatedVariables(), - b4.isUncheckedConversion(), - b4.annoInferenceFailed, - b4.errorMsg); - } - - /** - * Perform invocation type inference on {@code invocation}. See JLS - * 18.5.2. - * - * @param invocation member reference tree - * @return the result of inference - * @throws FalseBoundException if inference fails because of the java types - */ - public InferenceResult infer(MemberReferenceTree invocation) throws FalseBoundException { - - ProperType target = context.inferenceTypeFactory.getTargetType(); - AbstractType target1 = - InferenceType.create( - target.getAnnotatedType(), - target.getJavaType(), - context.maps.get(context.pathToExpression.getParentPath().getLeaf()), - context); - target = (ProperType) target1.applyInstantiations(); - if (target == null) { - throw new BugInCF("Target of method reference should not be null: %s", invocation); + /** + * Returns the tree for which inference is being inferred. + * + * @return the tree for which inference is being inferred + */ + public Tree getInferenceExpression() { + return inferenceExpression; } - InvocationType compileTimeDecl = - context.inferenceTypeFactory.compileTimeDeclarationType(invocation); - Theta map = - context.inferenceTypeFactory.createThetaForMethodReference( - invocation, compileTimeDecl, context); - BoundSet b2 = createB2MethodRef(compileTimeDecl, target.getFunctionTypeParameterTypes(), map); - AbstractType r = target.getFunctionTypeReturnType(); - BoundSet b3; - if (r == null || r.getTypeKind() == TypeKind.VOID) { - b3 = b2; - } else { - b3 = createB3(b2, invocation, compileTimeDecl, r, map); - } + /** + * Perform invocation type inference on {@code invocation}. See JLS + * 18.5.2. + * + * @param invocation invocation which needs inference + * @param methodType type of the method invocation + * @return the result of inference + * @throws FalseBoundException if inference fails because of the java types + */ + public InferenceResult infer(ExpressionTree invocation, AnnotatedExecutableType methodType) + throws FalseBoundException { + ExecutableType e = methodType.getUnderlyingType(); + InvocationType invocationType = new InvocationType(methodType, e, invocation, context); + ProperType target = context.inferenceTypeFactory.getTargetType(); + List args; + if (invocation.getKind() == Tree.Kind.METHOD_INVOCATION) { + args = ((MethodInvocationTree) invocation).getArguments(); + } else { + args = ((NewClassTree) invocation).getArguments(); + } - List thetaPrime = b3.resolve(); - - return new InferenceResult( - thetaPrime, b3.isUncheckedConversion(), b3.annoInferenceFailed, b3.errorMsg); - } - - /** - * Creates the bound set used to determine whether a method is applicable. This method is called - * B2 in JLS - * Section 18.5.1. - * - *

          It does this by: - * - *

            - *
          1. Creating the inference variables and initializing their bounds based on the - * type parameter declaration. - *
          2. Adding any bounds implied by the throws clause of {@code methodType}. - *
          3. Constructing constraints between formal parameters and arguments that are - * "pertinent to applicability" (See JLS - * Section 15.12.2.2). Generally, all arguments are applicable except: inexact method - * reference, implicitly typed lambdas, or explicitly typed lambda whose return - * expression(s) are not pertinent. - *
          4. Reducing and incorporating those constraints which finally produces B2. - *
          - * - * @param methodType the type of the method or constructor invoked - * @param args argument expression tress - * @param map map of type variables to (inference) variables - * @return bound set used to determine whether a method is applicable - */ - public BoundSet createB2( - InvocationType methodType, List args, Theta map) { - BoundSet b0 = BoundSet.initialBounds(map, context); - - // For all i (1 <= i <= p), if Pi appears in the throws clause of m, then the bound throws - // alphai is implied. These bounds, if any, are incorporated with B0 to produce a new bound - // set, B1. - for (AbstractType thrownType : methodType.getThrownTypes(map)) { - if (thrownType.isUseOfVariable()) { - ((UseOfVariable) thrownType).setHasThrowsBound(true); - } + Theta map = + context.inferenceTypeFactory.createThetaForInvocation( + invocation, invocationType, context); + BoundSet b2 = createB2(invocationType, args, map); + BoundSet b3; + if (target != null && TreeUtils.isPolyExpression(invocation)) { + b3 = createB3(b2, invocation, invocationType, target, map); + } else { + b3 = b2; + } + ConstraintSet c = createC(invocationType, args, map); + + BoundSet b4 = getB4(b3, c); + b4.resolve(); + return new InferenceResult( + b4.getInstantiatedVariables(), + b4.isUncheckedConversion(), + b4.annoInferenceFailed, + b4.errorMsg); } - BoundSet b1 = b0; - ConstraintSet c = new ConstraintSet(); - List formals = methodType.getParameterTypes(map, args.size()); + /** + * Perform invocation type inference on {@code invocation}. See JLS + * 18.5.2. + * + * @param invocation member reference tree + * @return the result of inference + * @throws FalseBoundException if inference fails because of the java types + */ + public InferenceResult infer(MemberReferenceTree invocation) throws FalseBoundException { + + ProperType target = context.inferenceTypeFactory.getTargetType(); + AbstractType target1 = + InferenceType.create( + target.getAnnotatedType(), + target.getJavaType(), + context.maps.get(context.pathToExpression.getParentPath().getLeaf()), + context); + target = (ProperType) target1.applyInstantiations(); + if (target == null) { + throw new BugInCF("Target of method reference should not be null: %s", invocation); + } + + InvocationType compileTimeDecl = + context.inferenceTypeFactory.compileTimeDeclarationType(invocation); + Theta map = + context.inferenceTypeFactory.createThetaForMethodReference( + invocation, compileTimeDecl, context); + BoundSet b2 = + createB2MethodRef(compileTimeDecl, target.getFunctionTypeParameterTypes(), map); + AbstractType r = target.getFunctionTypeReturnType(); + BoundSet b3; + if (r == null || r.getTypeKind() == TypeKind.VOID) { + b3 = b2; + } else { + b3 = createB3(b2, invocation, compileTimeDecl, r, map); + } - for (int i = 0; i < formals.size(); i++) { - ExpressionTree ei = args.get(i); - AbstractType fi = formals.get(i); + List thetaPrime = b3.resolve(); - if (!notPertinentToApplicability(ei, fi.isUseOfVariable())) { - c.add(new Expression(ei, fi)); - } + return new InferenceResult( + thetaPrime, b3.isUncheckedConversion(), b3.annoInferenceFailed, b3.errorMsg); } - BoundSet newBounds = c.reduce(context); - assert !newBounds.containsFalse(); - b1.incorporateToFixedPoint(newBounds); - - return b1; - } - - /** - * Same as {@link #createB2(InvocationType, List, Theta)}, but for method references. A list of - * types is used instead of a list of arguments. These types are the types of the formal - * parameters of function type of target type of the method reference. - * - * @param methodType the type of the method or constructor invoked - * @param args types to use as arguments - * @param map map of type variables to (inference) variables - * @return bound set used to determine whether a method is applicable - */ - public BoundSet createB2MethodRef(InvocationType methodType, List args, Theta map) { - BoundSet b0 = BoundSet.initialBounds(map, context); - - // For all i (1 <= i <= p), if Pi appears in the throws clause of m, then the bound throws - // alphai is implied. These bounds, if any, are incorporated with B0 to produce a new bound - // set, B1. - for (AbstractType thrownType : methodType.getThrownTypes(map)) { - if (thrownType.isUseOfVariable()) { - ((UseOfVariable) thrownType).setHasThrowsBound(true); - } - } + /** + * Creates the bound set used to determine whether a method is applicable. This method is called + * B2 in JLS + * Section 18.5.1. + * + *

          It does this by: + * + *

            + *
          1. Creating the inference variables and initializing their bounds based on the + * type parameter declaration. + *
          2. Adding any bounds implied by the throws clause of {@code methodType}. + *
          3. Constructing constraints between formal parameters and arguments that are + * "pertinent to applicability" (See JLS + * Section 15.12.2.2). Generally, all arguments are applicable except: inexact method + * reference, implicitly typed lambdas, or explicitly typed lambda whose return + * expression(s) are not pertinent. + *
          4. Reducing and incorporating those constraints which finally produces B2. + *
          + * + * @param methodType the type of the method or constructor invoked + * @param args argument expression tress + * @param map map of type variables to (inference) variables + * @return bound set used to determine whether a method is applicable + */ + public BoundSet createB2( + InvocationType methodType, List args, Theta map) { + BoundSet b0 = BoundSet.initialBounds(map, context); + + // For all i (1 <= i <= p), if Pi appears in the throws clause of m, then the bound throws + // alphai is implied. These bounds, if any, are incorporated with B0 to produce a new bound + // set, B1. + for (AbstractType thrownType : methodType.getThrownTypes(map)) { + if (thrownType.isUseOfVariable()) { + ((UseOfVariable) thrownType).setHasThrowsBound(true); + } + } - BoundSet b1 = b0; - ConstraintSet c = new ConstraintSet(); - List formals = methodType.getParameterTypes(map, args.size()); - if (TreeUtils.isLikeDiamondMemberReference(methodType.getInvocation())) { - // https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.13.1 - // If ReferenceType is a raw type, and there exists a parameterization of this type, - // G<...>, that is a supertype of P1, the type to search is the result of capture - // conversion (§5.1.10) applied to G<...>; otherwise, the type to search is the same - // as the type of the first search. Type arguments, if any, are given by the method - // reference expression. - AbstractType receiver = args.remove(0); - args.add(0, receiver.capture(context)); + BoundSet b1 = b0; + ConstraintSet c = new ConstraintSet(); + List formals = methodType.getParameterTypes(map, args.size()); + + for (int i = 0; i < formals.size(); i++) { + ExpressionTree ei = args.get(i); + AbstractType fi = formals.get(i); + + if (!notPertinentToApplicability(ei, fi.isUseOfVariable())) { + c.add(new Expression(ei, fi)); + } + } + + BoundSet newBounds = c.reduce(context); + assert !newBounds.containsFalse(); + b1.incorporateToFixedPoint(newBounds); + + return b1; } - for (int i = 0; i < formals.size(); i++) { - AbstractType ei = args.get(i); - AbstractType fi = formals.get(i); - c.add(new Typing(ei, fi, Kind.TYPE_COMPATIBILITY)); + /** + * Same as {@link #createB2(InvocationType, List, Theta)}, but for method references. A list of + * types is used instead of a list of arguments. These types are the types of the formal + * parameters of function type of target type of the method reference. + * + * @param methodType the type of the method or constructor invoked + * @param args types to use as arguments + * @param map map of type variables to (inference) variables + * @return bound set used to determine whether a method is applicable + */ + public BoundSet createB2MethodRef( + InvocationType methodType, List args, Theta map) { + BoundSet b0 = BoundSet.initialBounds(map, context); + + // For all i (1 <= i <= p), if Pi appears in the throws clause of m, then the bound throws + // alphai is implied. These bounds, if any, are incorporated with B0 to produce a new bound + // set, B1. + for (AbstractType thrownType : methodType.getThrownTypes(map)) { + if (thrownType.isUseOfVariable()) { + ((UseOfVariable) thrownType).setHasThrowsBound(true); + } + } + + BoundSet b1 = b0; + ConstraintSet c = new ConstraintSet(); + List formals = methodType.getParameterTypes(map, args.size()); + if (TreeUtils.isLikeDiamondMemberReference(methodType.getInvocation())) { + // https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.13.1 + // If ReferenceType is a raw type, and there exists a parameterization of this type, + // G<...>, that is a supertype of P1, the type to search is the result of capture + // conversion (§5.1.10) applied to G<...>; otherwise, the type to search is the same + // as the type of the first search. Type arguments, if any, are given by the method + // reference expression. + AbstractType receiver = args.remove(0); + args.add(0, receiver.capture(context)); + } + + for (int i = 0; i < formals.size(); i++) { + AbstractType ei = args.get(i); + AbstractType fi = formals.get(i); + c.add(new Typing(ei, fi, Kind.TYPE_COMPATIBILITY)); + } + + BoundSet newBounds = c.reduce(context); + assert !newBounds.containsFalse(); + b1.incorporateToFixedPoint(newBounds); + + return b1; } - BoundSet newBounds = c.reduce(context); - assert !newBounds.containsFalse(); - b1.incorporateToFixedPoint(newBounds); - - return b1; - } - - /** - * Creates constraints against the target type of {@code invocation} and then reduces and - * incorporates those constraints with {@code b2}. (See JLS - * 18.5.2.1.) - * - * @param b2 BoundSet created by {@link #createB2(InvocationType, List, Theta)} - * @param invocation a method or constructor invocation - * @param methodType the type of the method or constructor invoked by expression - * @param target target type of the invocation - * @param map map of type variables to (inference) variables - * @return bound set created by constraints against the target type of the invocation - */ - public BoundSet createB3( - BoundSet b2, - ExpressionTree invocation, - InvocationType methodType, - AbstractType target, - Theta map) { - AbstractType r = methodType.getReturnType(map); - if (b2.isUncheckedConversion()) { - // If unchecked conversion was necessary for the method to be applicable during - // constraint set reduction in 18.5.1, the constraint formula <|R| -> T> is reduced and - // incorporated with B2. - BoundSet b = - new ConstraintSet(new Typing(r.getErased(), target, Kind.TYPE_COMPATIBILITY)) - .reduce(context); - b2.incorporateToFixedPoint(b); - return b2; - - } else if (r.isWildcardParameterizedType()) { - // Otherwise, if r is a parameterized type, G, and one of A1, ..., - // An is a wildcard, then, for fresh inference variables B1, ..., Bn, the constraint - // formula -> T> is reduced and incorporated, along with the bound - // G = capture(G), with B2. - BoundSet b = - CaptureBound.createAndIncorporateCaptureConstraint(r, target, invocation, context); - b2.incorporateToFixedPoint(b); - return b2; - } else if (r.isUseOfVariable()) { - Variable alpha = ((UseOfVariable) r).getVariable(); - // Should a type compatibility constraint be added? - boolean compatibility = false; - // If the target type is a reference type, but is not a wildcard-parameterized type. - if (!target.isWildcardParameterizedType()) { - // i) B2 contains a bound of one of the forms alpha = S or S <: alpha, where S is a - // wildcard-parameterized type, or - compatibility = alpha.getBounds().hasWildcardParameterizedLowerOrEqualBound(); - // ii) B2 contains two bounds of the forms S1 <: alpha and S2 <: alpha, where S1 - // and S2 have supertypes that are two different parameterizations of the same - // generic class or interface. - compatibility |= alpha.getBounds().hasLowerBoundDifferentParam(); - } else if (target.isParameterizedType()) { - // The target type is a parameterization of a generic class or interface, G, and B2 - // contains a - // bound of one of the forms alpha = S or S <: alpha, where there exists no type of - // the form G<...> that is a supertype of S, but the raw type |G<...>| is a - // supertype of S. - compatibility = alpha.getBounds().hasRawTypeLowerOrEqualBound(target); - } else if (target.getTypeKind().isPrimitive()) { - // The target is a primitive type, and one of the primitive wrapper classes - // mentioned in - // 5.1.7 is an instantiation, upper bound, or lower bound for alpha in B2. - compatibility = alpha.getBounds().hasPrimitiveWrapperBound(); - } - if (compatibility) { - BoundSet resolve = Resolution.resolve(alpha, b2, context); - ProperType u = (ProperType) alpha.getBounds().getInstantiation().capture(context); + /** + * Creates constraints against the target type of {@code invocation} and then reduces and + * incorporates those constraints with {@code b2}. (See JLS + * 18.5.2.1.) + * + * @param b2 BoundSet created by {@link #createB2(InvocationType, List, Theta)} + * @param invocation a method or constructor invocation + * @param methodType the type of the method or constructor invoked by expression + * @param target target type of the invocation + * @param map map of type variables to (inference) variables + * @return bound set created by constraints against the target type of the invocation + */ + public BoundSet createB3( + BoundSet b2, + ExpressionTree invocation, + InvocationType methodType, + AbstractType target, + Theta map) { + AbstractType r = methodType.getReturnType(map); + if (b2.isUncheckedConversion()) { + // If unchecked conversion was necessary for the method to be applicable during + // constraint set reduction in 18.5.1, the constraint formula <|R| -> T> is reduced and + // incorporated with B2. + BoundSet b = + new ConstraintSet(new Typing(r.getErased(), target, Kind.TYPE_COMPATIBILITY)) + .reduce(context); + b2.incorporateToFixedPoint(b); + return b2; + + } else if (r.isWildcardParameterizedType()) { + // Otherwise, if r is a parameterized type, G, and one of A1, ..., + // An is a wildcard, then, for fresh inference variables B1, ..., Bn, the constraint + // formula -> T> is reduced and incorporated, along with the bound + // G = capture(G), with B2. + BoundSet b = + CaptureBound.createAndIncorporateCaptureConstraint( + r, target, invocation, context); + b2.incorporateToFixedPoint(b); + return b2; + } else if (r.isUseOfVariable()) { + Variable alpha = ((UseOfVariable) r).getVariable(); + // Should a type compatibility constraint be added? + boolean compatibility = false; + // If the target type is a reference type, but is not a wildcard-parameterized type. + if (!target.isWildcardParameterizedType()) { + // i) B2 contains a bound of one of the forms alpha = S or S <: alpha, where S is a + // wildcard-parameterized type, or + compatibility = alpha.getBounds().hasWildcardParameterizedLowerOrEqualBound(); + // ii) B2 contains two bounds of the forms S1 <: alpha and S2 <: alpha, where S1 + // and S2 have supertypes that are two different parameterizations of the same + // generic class or interface. + compatibility |= alpha.getBounds().hasLowerBoundDifferentParam(); + } else if (target.isParameterizedType()) { + // The target type is a parameterization of a generic class or interface, G, and B2 + // contains a + // bound of one of the forms alpha = S or S <: alpha, where there exists no type of + // the form G<...> that is a supertype of S, but the raw type |G<...>| is a + // supertype of S. + compatibility = alpha.getBounds().hasRawTypeLowerOrEqualBound(target); + } else if (target.getTypeKind().isPrimitive()) { + // The target is a primitive type, and one of the primitive wrapper classes + // mentioned in + // 5.1.7 is an instantiation, upper bound, or lower bound for alpha in B2. + compatibility = alpha.getBounds().hasPrimitiveWrapperBound(); + } + if (compatibility) { + BoundSet resolve = Resolution.resolve(alpha, b2, context); + ProperType u = (ProperType) alpha.getBounds().getInstantiation().capture(context); + ConstraintSet constraintSet = + new ConstraintSet(new Typing(u, target, Kind.TYPE_COMPATIBILITY)); + BoundSet newBounds = constraintSet.reduce(context); + resolve.incorporateToFixedPoint(newBounds); + return resolve; + } + if (target.isProper() && target.getJavaType().getKind().isPrimitive()) { + // From the JLS: + // "T is a primitive type, and one of the primitive wrapper classes mentioned in + // 5.1.7 is an instantiation, upper bound, or lower bound for [the variable] in B2." + ConstraintSet constraintSet = + new ConstraintSet(new Typing(r, target, Kind.SUBTYPE)); + BoundSet newBounds = constraintSet.reduce(context); + b2.incorporateToFixedPoint(newBounds); + return b2; + } + } + ConstraintSet constraintSet = - new ConstraintSet(new Typing(u, target, Kind.TYPE_COMPATIBILITY)); - BoundSet newBounds = constraintSet.reduce(context); - resolve.incorporateToFixedPoint(newBounds); - return resolve; - } - if (target.isProper() && target.getJavaType().getKind().isPrimitive()) { - // From the JLS: - // "T is a primitive type, and one of the primitive wrapper classes mentioned in - // 5.1.7 is an instantiation, upper bound, or lower bound for [the variable] in B2." - ConstraintSet constraintSet = new ConstraintSet(new Typing(r, target, Kind.SUBTYPE)); + new ConstraintSet(new Typing(r, target, Kind.TYPE_COMPATIBILITY)); BoundSet newBounds = constraintSet.reduce(context); b2.incorporateToFixedPoint(newBounds); return b2; - } } - ConstraintSet constraintSet = new ConstraintSet(new Typing(r, target, Kind.TYPE_COMPATIBILITY)); - BoundSet newBounds = constraintSet.reduce(context); - b2.incorporateToFixedPoint(newBounds); - return b2; - } - - /** - * Creates the constraints between the formal parameters and arguments that are not pertinent to - * applicability. (See JLS - * 18.5.2.2.) - * - * @param methodType type of method invoked - * @param args argument expression trees - * @param map map from type variable to inference variable - * @return the constraints between the formal parameters and arguments that are not pertinent to - * applicability - */ - public ConstraintSet createC( - InvocationType methodType, List args, Theta map) { - ConstraintSet c = new ConstraintSet(); - List formals = methodType.getParameterTypes(map, args.size()); - - for (int i = 0; i < formals.size(); i++) { - ExpressionTree ei = args.get(i); - AbstractType fi = formals.get(i); - if (notPertinentToApplicability(ei, fi.isUseOfVariable())) { - c.add(new Expression(ei, fi)); - } - if (ei.getKind() == Tree.Kind.METHOD_INVOCATION || ei.getKind() == Tree.Kind.NEW_CLASS) { - if (TreeUtils.isPolyExpression(ei)) { - AdditionalArgument aa = new AdditionalArgument(ei); - c.addAll(aa.reduce(context)); + /** + * Creates the constraints between the formal parameters and arguments that are not pertinent to + * applicability. (See JLS + * 18.5.2.2.) + * + * @param methodType type of method invoked + * @param args argument expression trees + * @param map map from type variable to inference variable + * @return the constraints between the formal parameters and arguments that are not pertinent to + * applicability + */ + public ConstraintSet createC( + InvocationType methodType, List args, Theta map) { + ConstraintSet c = new ConstraintSet(); + List formals = methodType.getParameterTypes(map, args.size()); + + for (int i = 0; i < formals.size(); i++) { + ExpressionTree ei = args.get(i); + AbstractType fi = formals.get(i); + if (notPertinentToApplicability(ei, fi.isUseOfVariable())) { + c.add(new Expression(ei, fi)); + } + if (ei.getKind() == Tree.Kind.METHOD_INVOCATION + || ei.getKind() == Tree.Kind.NEW_CLASS) { + if (TreeUtils.isPolyExpression(ei)) { + AdditionalArgument aa = new AdditionalArgument(ei); + c.addAll(aa.reduce(context)); + } + } else { + // Wait to reduce additional argument constraints from lambdas and method references + // because the additional constraints might require other inference variables to be + // resolved before the constraint can be created. + c.addAll(createAdditionalArgConstraints(ei, fi, map)); + } } - } else { - // Wait to reduce additional argument constraints from lambdas and method references - // because the additional constraints might require other inference variables to be - // resolved before the constraint can be created. - c.addAll(createAdditionalArgConstraints(ei, fi, map)); - } + + return c; } - return c; - } - - /** - * Adds argument constraints for the argument {@code ei} and its subexpressions. These are in - * addition to the constraints added in {@link #createC(InvocationType, List, Theta)}. - * - *

          It does this by traversing {@code ei} if it is a method reference, lambda, method - * invocation, new class tree, conditional expression, switch expression, or parenthesized - * expression. - * - *

          If {@code ei} is a method invocation or new class tree, that expression might require type - * argument inference. In that case the additional variables, bounds, and constraints are added - * here. - * - *

          (See JLS - * 18.5.2.2) - * - * @param ei expression that is an argument to a method that corresponds to the formal parameter - * whose type is {@code fi} - * @param fi type that is the formal parameter to a method whose corresponding argument is {@code - * ei} - * @param map map from type variable to inference variable - * @return the additional argument constraints - */ - private ConstraintSet createAdditionalArgConstraints( - ExpressionTree ei, AbstractType fi, Theta map) { - ConstraintSet c = new ConstraintSet(); - - switch (ei.getKind()) { - case MEMBER_REFERENCE: - c.add(new CheckedExceptionConstraint(ei, fi, map)); - break; - case LAMBDA_EXPRESSION: - c.add(new CheckedExceptionConstraint(ei, fi, map)); - LambdaExpressionTree lambda = (LambdaExpressionTree) ei; - for (ExpressionTree expression : TreeUtils.getReturnedExpressions(lambda)) { - c.addAll(createAdditionalArgConstraintsNoLambda(expression)); - } - break; - case METHOD_INVOCATION: - case NEW_CLASS: - if (TreeUtils.isPolyExpression(ei)) { - c.add(new AdditionalArgument(ei)); + /** + * Adds argument constraints for the argument {@code ei} and its subexpressions. These are in + * addition to the constraints added in {@link #createC(InvocationType, List, Theta)}. + * + *

          It does this by traversing {@code ei} if it is a method reference, lambda, method + * invocation, new class tree, conditional expression, switch expression, or parenthesized + * expression. + * + *

          If {@code ei} is a method invocation or new class tree, that expression might require type + * argument inference. In that case the additional variables, bounds, and constraints are added + * here. + * + *

          (See JLS + * 18.5.2.2) + * + * @param ei expression that is an argument to a method that corresponds to the formal parameter + * whose type is {@code fi} + * @param fi type that is the formal parameter to a method whose corresponding argument is + * {@code ei} + * @param map map from type variable to inference variable + * @return the additional argument constraints + */ + private ConstraintSet createAdditionalArgConstraints( + ExpressionTree ei, AbstractType fi, Theta map) { + ConstraintSet c = new ConstraintSet(); + + switch (ei.getKind()) { + case MEMBER_REFERENCE: + c.add(new CheckedExceptionConstraint(ei, fi, map)); + break; + case LAMBDA_EXPRESSION: + c.add(new CheckedExceptionConstraint(ei, fi, map)); + LambdaExpressionTree lambda = (LambdaExpressionTree) ei; + for (ExpressionTree expression : TreeUtils.getReturnedExpressions(lambda)) { + c.addAll(createAdditionalArgConstraintsNoLambda(expression)); + } + break; + case METHOD_INVOCATION: + case NEW_CLASS: + if (TreeUtils.isPolyExpression(ei)) { + c.add(new AdditionalArgument(ei)); + } + break; + case PARENTHESIZED: + c.addAll(createAdditionalArgConstraints(TreeUtils.withoutParens(ei), fi, map)); + break; + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) ei; + c.addAll(createAdditionalArgConstraints(conditional.getTrueExpression(), fi, map)); + c.addAll(createAdditionalArgConstraints(conditional.getFalseExpression(), fi, map)); + break; + default: + if (TreeUtils.isSwitchExpression(ei)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree tree, Void unused) -> { + c.addAll(createAdditionalArgConstraints(tree, fi, map)); + return null; + }, + (c1, c2) -> null); + scanner.scanSwitchExpression(ei, null); + } + // no constraints } - break; - case PARENTHESIZED: - c.addAll(createAdditionalArgConstraints(TreeUtils.withoutParens(ei), fi, map)); - break; - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree conditional = (ConditionalExpressionTree) ei; - c.addAll(createAdditionalArgConstraints(conditional.getTrueExpression(), fi, map)); - c.addAll(createAdditionalArgConstraints(conditional.getFalseExpression(), fi, map)); - break; - default: - if (TreeUtils.isSwitchExpression(ei)) { - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (ExpressionTree tree, Void unused) -> { - c.addAll(createAdditionalArgConstraints(tree, fi, map)); - return null; - }, - (c1, c2) -> null); - scanner.scanSwitchExpression(ei, null); - } - // no constraints + + return c; } - return c; - } - - /** - * Recursively search for method invocations and new class trees. If any are found, the additional - * variables, bounds, and constraints are returned. This method is called by {@link - * #createAdditionalArgConstraints(ExpressionTree, AbstractType, Theta)} when that method - * encounters a lambda. This method is different because it does not add checked exception - * constraints for lambdas or method references. - * - * @param expression expression to search - * @return additional constraints - */ - private ConstraintSet createAdditionalArgConstraintsNoLambda(ExpressionTree expression) { - ConstraintSet c = new ConstraintSet(); - - switch (expression.getKind()) { - case LAMBDA_EXPRESSION: - LambdaExpressionTree lambda = (LambdaExpressionTree) expression; - for (ExpressionTree returnedExpression : TreeUtils.getReturnedExpressions(lambda)) { - c.addAll(createAdditionalArgConstraintsNoLambda(returnedExpression)); - } - break; - case METHOD_INVOCATION: - case NEW_CLASS: - if (TreeUtils.isPolyExpression(expression)) { - c.add(new AdditionalArgument(expression)); - } - break; - case PARENTHESIZED: - c.addAll(createAdditionalArgConstraintsNoLambda(TreeUtils.withoutParens(expression))); - break; - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree conditional = (ConditionalExpressionTree) expression; - c.addAll(createAdditionalArgConstraintsNoLambda(conditional.getTrueExpression())); - c.addAll(createAdditionalArgConstraintsNoLambda(conditional.getFalseExpression())); - break; - default: - if (TreeUtils.isSwitchExpression(expression)) { - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (ExpressionTree tree, Void unused) -> { - c.addAll(createAdditionalArgConstraintsNoLambda(tree)); - return null; - }, - (c1, c2) -> null); - scanner.scanSwitchExpression(expression, null); + /** + * Recursively search for method invocations and new class trees. If any are found, the + * additional variables, bounds, and constraints are returned. This method is called by {@link + * #createAdditionalArgConstraints(ExpressionTree, AbstractType, Theta)} when that method + * encounters a lambda. This method is different because it does not add checked exception + * constraints for lambdas or method references. + * + * @param expression expression to search + * @return additional constraints + */ + private ConstraintSet createAdditionalArgConstraintsNoLambda(ExpressionTree expression) { + ConstraintSet c = new ConstraintSet(); + + switch (expression.getKind()) { + case LAMBDA_EXPRESSION: + LambdaExpressionTree lambda = (LambdaExpressionTree) expression; + for (ExpressionTree returnedExpression : TreeUtils.getReturnedExpressions(lambda)) { + c.addAll(createAdditionalArgConstraintsNoLambda(returnedExpression)); + } + break; + case METHOD_INVOCATION: + case NEW_CLASS: + if (TreeUtils.isPolyExpression(expression)) { + c.add(new AdditionalArgument(expression)); + } + break; + case PARENTHESIZED: + c.addAll( + createAdditionalArgConstraintsNoLambda( + TreeUtils.withoutParens(expression))); + break; + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) expression; + c.addAll(createAdditionalArgConstraintsNoLambda(conditional.getTrueExpression())); + c.addAll(createAdditionalArgConstraintsNoLambda(conditional.getFalseExpression())); + break; + default: + if (TreeUtils.isSwitchExpression(expression)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree tree, Void unused) -> { + c.addAll(createAdditionalArgConstraintsNoLambda(tree)); + return null; + }, + (c1, c2) -> null); + scanner.scanSwitchExpression(expression, null); + } + // no constraints } - // no constraints + + return c; } - return c; - } - - /** - * JLS - * 15.12.2.2 (Assuming the method is a generic method and the method invocation does not - * provide explicit type arguments) - * - * @param expressionTree expression tree - * @param isTargetVariable whether the corresponding target type (as derived from the signature of - * m) is a type parameter of m and therefore a variable - * @return whether {@code expressionTree} is pertinent to applicability - */ - private boolean notPertinentToApplicability( - ExpressionTree expressionTree, boolean isTargetVariable) { - switch (expressionTree.getKind()) { - case LAMBDA_EXPRESSION: - LambdaExpressionTree lambda = (LambdaExpressionTree) expressionTree; - if (TreeUtils.isImplicitlyTypedLambda(lambda) || isTargetVariable) { - // An implicitly typed lambda expression. - return true; - } else { - // An explicitly typed lambda expression whose body is a block, - // where at least one result expression is not pertinent to applicability. - // An explicitly typed lambda expression whose body is an expression that is - // not pertinent to applicability. - for (ExpressionTree result : TreeUtils.getReturnedExpressions(lambda)) { - if (notPertinentToApplicability(result, isTargetVariable)) { - return true; - } - } - return false; - } - case MEMBER_REFERENCE: - // An inexact method reference expression. - return isTargetVariable - || !TreeUtils.isExactMethodReference((MemberReferenceTree) expressionTree); - case PARENTHESIZED: - // A parenthesized expression whose contained expression is not pertinent to - // applicability. - return notPertinentToApplicability( - TreeUtils.withoutParens(expressionTree), isTargetVariable); - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree conditional = (ConditionalExpressionTree) expressionTree; - // A conditional expression whose second or third operand is not pertinent to - // applicability. - return notPertinentToApplicability(conditional.getTrueExpression(), isTargetVariable) - || notPertinentToApplicability(conditional.getFalseExpression(), isTargetVariable); - default: - if (TreeUtils.isSwitchExpression(expressionTree)) { - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (ExpressionTree tree, Void unused) -> - notPertinentToApplicability(tree, isTargetVariable), - (r1, r2) -> (r1 != null && r1) || (r2 != null && r2)); - ; - return scanner.scanSwitchExpression(expressionTree, null); + /** + * JLS + * 15.12.2.2 (Assuming the method is a generic method and the method invocation does not + * provide explicit type arguments) + * + * @param expressionTree expression tree + * @param isTargetVariable whether the corresponding target type (as derived from the signature + * of m) is a type parameter of m and therefore a variable + * @return whether {@code expressionTree} is pertinent to applicability + */ + private boolean notPertinentToApplicability( + ExpressionTree expressionTree, boolean isTargetVariable) { + switch (expressionTree.getKind()) { + case LAMBDA_EXPRESSION: + LambdaExpressionTree lambda = (LambdaExpressionTree) expressionTree; + if (TreeUtils.isImplicitlyTypedLambda(lambda) || isTargetVariable) { + // An implicitly typed lambda expression. + return true; + } else { + // An explicitly typed lambda expression whose body is a block, + // where at least one result expression is not pertinent to applicability. + // An explicitly typed lambda expression whose body is an expression that is + // not pertinent to applicability. + for (ExpressionTree result : TreeUtils.getReturnedExpressions(lambda)) { + if (notPertinentToApplicability(result, isTargetVariable)) { + return true; + } + } + return false; + } + case MEMBER_REFERENCE: + // An inexact method reference expression. + return isTargetVariable + || !TreeUtils.isExactMethodReference((MemberReferenceTree) expressionTree); + case PARENTHESIZED: + // A parenthesized expression whose contained expression is not pertinent to + // applicability. + return notPertinentToApplicability( + TreeUtils.withoutParens(expressionTree), isTargetVariable); + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) expressionTree; + // A conditional expression whose second or third operand is not pertinent to + // applicability. + return notPertinentToApplicability( + conditional.getTrueExpression(), isTargetVariable) + || notPertinentToApplicability( + conditional.getFalseExpression(), isTargetVariable); + default: + if (TreeUtils.isSwitchExpression(expressionTree)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree tree, Void unused) -> + notPertinentToApplicability(tree, isTargetVariable), + (r1, r2) -> (r1 != null && r1) || (r2 != null && r2)); + ; + return scanner.scanSwitchExpression(expressionTree, null); + } + return false; } - return false; } - } - - /** - * Returns the result of reducing and incorporating the set of constraints, {@code c}. The - * constraints must be reduced in a particular order. See JLS - * 18.5.2.2. - * - * @param b3 bound set created by previous inference step that is sideeffect and returned - * @param c constraints that are reduced and incorporated - * @return the result of reducing and incorporating the set of constraints - */ - private BoundSet getB4(BoundSet b3, ConstraintSet c) { - // C might contain new variables that have not yet been added to the b3 bound set. - Set newVariables = c.getAllInferenceVariables(); - while (!c.isEmpty()) { - - ConstraintSet subset = c.getClosedSubset(b3.getDependencies(newVariables)); - Set alphas = subset.getAllInputVariables(); - if (!alphas.isEmpty()) { - // First resolve only the variables with proper bounds. - for (Variable alpha : new ArrayList<>(alphas)) { - if (alpha.getBounds().onlyProperBounds()) { - Resolution.resolve(alpha, b3, context); - alphas.remove(alpha); - } + + /** + * Returns the result of reducing and incorporating the set of constraints, {@code c}. The + * constraints must be reduced in a particular order. See JLS + * 18.5.2.2. + * + * @param b3 bound set created by previous inference step that is sideeffect and returned + * @param c constraints that are reduced and incorporated + * @return the result of reducing and incorporating the set of constraints + */ + private BoundSet getB4(BoundSet b3, ConstraintSet c) { + // C might contain new variables that have not yet been added to the b3 bound set. + Set newVariables = c.getAllInferenceVariables(); + while (!c.isEmpty()) { + + ConstraintSet subset = c.getClosedSubset(b3.getDependencies(newVariables)); + Set alphas = subset.getAllInputVariables(); + if (!alphas.isEmpty()) { + // First resolve only the variables with proper bounds. + for (Variable alpha : new ArrayList<>(alphas)) { + if (alpha.getBounds().onlyProperBounds()) { + Resolution.resolve(alpha, b3, context); + alphas.remove(alpha); + } + } + c.applyInstantiations(); + } + if (!alphas.isEmpty()) { + // Resolve any remaining variables that have bounds that are variable or inference + // types. + Resolution.resolve(alphas, b3, context); + c.applyInstantiations(); + } + c.remove(subset); + BoundSet newBounds = subset.reduce(context); + b3.incorporateToFixedPoint(newBounds); } - c.applyInstantiations(); - } - if (!alphas.isEmpty()) { - // Resolve any remaining variables that have bounds that are variable or inference - // types. - Resolution.resolve(alphas, b3, context); - c.applyInstantiations(); - } - c.remove(subset); - BoundSet newBounds = subset.reduce(context); - b3.incorporateToFixedPoint(newBounds); + return b3; } - return b3; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/TypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/TypeArgumentInference.java index 80eb2c8d43b..95bd3dade79 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/TypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/TypeArgumentInference.java @@ -1,6 +1,7 @@ package org.checkerframework.framework.util.typeinference8; import com.sun.source.tree.ExpressionTree; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -29,18 +30,18 @@ */ public interface TypeArgumentInference { - /** - * Infer the type arguments for the method or constructor invocation given by invocation. - * - * @param typeFactory the type factory used to create methodType - * @param invocation a tree representing the method or constructor invocation for which we are - * inferring type arguments - * @param methodType the declaration type of method elem - * @return the result which includes the inferred type arguments or an error message if they were - * not inferred. - */ - InferenceResult inferTypeArgs( - AnnotatedTypeFactory typeFactory, - ExpressionTree invocation, - AnnotatedExecutableType methodType); + /** + * Infer the type arguments for the method or constructor invocation given by invocation. + * + * @param typeFactory the type factory used to create methodType + * @param invocation a tree representing the method or constructor invocation for which we are + * inferring type arguments + * @param methodType the declaration type of method elem + * @return the result which includes the inferred type arguments or an error message if they + * were not inferred. + */ + InferenceResult inferTypeArgs( + AnnotatedTypeFactory typeFactory, + ExpressionTree invocation, + AnnotatedExecutableType methodType); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java index a07d0b71c55..8b176da3acc 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/BoundSet.java @@ -1,10 +1,5 @@ package org.checkerframework.framework.util.typeinference8.bound; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; import org.checkerframework.framework.util.typeinference8.constraint.ReductionResult; import org.checkerframework.framework.util.typeinference8.types.CaptureVariable; import org.checkerframework.framework.util.typeinference8.types.Dependencies; @@ -14,360 +9,366 @@ import org.checkerframework.framework.util.typeinference8.util.Theta; import org.plumelib.util.StringsPlume; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + /** * Manages a set of bounds. Bounds are stored in the variable to which they apply, except for * capture bounds which are stored in this class. */ public class BoundSet implements ReductionResult { - /** - * Max number of incorporation loops. Use same constant as {@link - * com.sun.tools.javac.comp.Infer#MAX_INCORPORATION_STEPS} - */ - // TODO: revert to com.sun.tools.javac.comp.Infer#MAX_INCORPORATION_STEPS - public static final int MAX_INCORPORATION_STEPS = 1000; - - /** All inference variables in this bound set. */ - private final LinkedHashSet variables; - - /** All capture bounds. */ - private final LinkedHashSet captures; - - /** The context. */ - private final Java8InferenceContext context; - - /** - * If true, then type argument inference failed because an annotated type could not be inferred. - */ - public boolean annoInferenceFailed = false; - - /** The error message to report to users. */ - public String errorMsg = ""; - - /** Whether this bounds set contains the false bound. */ - private boolean containsFalse; - - /** Whether unchecked conversion was necessary to reduce and incorporate this bound set. */ - private boolean uncheckedConversion; - - /** - * Creates a bound set. - * - * @param context the context - */ - public BoundSet(Java8InferenceContext context) { - assert context != null; - this.variables = new LinkedHashSet<>(); - this.captures = new LinkedHashSet<>(); - this.context = context; - this.containsFalse = false; - this.uncheckedConversion = false; - } - - /** - * Copy constructor. - * - * @param toCopy bound set to copy - */ - public BoundSet(BoundSet toCopy) { - this.context = toCopy.context; - this.containsFalse = toCopy.containsFalse; - this.captures = new LinkedHashSet<>(toCopy.captures); - this.variables = new LinkedHashSet<>(toCopy.variables); - this.uncheckedConversion = toCopy.uncheckedConversion; - } - - /** - * Save the current state of the variables so they can be restored if the first attempt at - * resolution fails. - */ - public void saveBounds() { - for (Variable v : variables) { - v.save(); + /** + * Max number of incorporation loops. Use same constant as {@link + * com.sun.tools.javac.comp.Infer#MAX_INCORPORATION_STEPS} + */ + // TODO: revert to com.sun.tools.javac.comp.Infer#MAX_INCORPORATION_STEPS + public static final int MAX_INCORPORATION_STEPS = 1000; + + /** All inference variables in this bound set. */ + private final LinkedHashSet variables; + + /** All capture bounds. */ + private final LinkedHashSet captures; + + /** The context. */ + private final Java8InferenceContext context; + + /** + * If true, then type argument inference failed because an annotated type could not be inferred. + */ + public boolean annoInferenceFailed = false; + + /** The error message to report to users. */ + public String errorMsg = ""; + + /** Whether this bounds set contains the false bound. */ + private boolean containsFalse; + + /** Whether unchecked conversion was necessary to reduce and incorporate this bound set. */ + private boolean uncheckedConversion; + + /** + * Creates a bound set. + * + * @param context the context + */ + public BoundSet(Java8InferenceContext context) { + assert context != null; + this.variables = new LinkedHashSet<>(); + this.captures = new LinkedHashSet<>(); + this.context = context; + this.containsFalse = false; + this.uncheckedConversion = false; + } + + /** + * Copy constructor. + * + * @param toCopy bound set to copy + */ + public BoundSet(BoundSet toCopy) { + this.context = toCopy.context; + this.containsFalse = toCopy.containsFalse; + this.captures = new LinkedHashSet<>(toCopy.captures); + this.variables = new LinkedHashSet<>(toCopy.variables); + this.uncheckedConversion = toCopy.uncheckedConversion; } - } - - /** - * Restore the bounds to the last saved state. This method is called if the first attempt at - * resolution fails. - */ - public void restore() { - for (Variable v : variables) { - v.restore(); + + /** + * Save the current state of the variables so they can be restored if the first attempt at + * resolution fails. + */ + public void saveBounds() { + for (Variable v : variables) { + v.save(); + } } - } - - /** - * Creates a new bound set for the variables in theta. (The initial bounds for the variables were - * added to the variables when theta was created.) - * - * @param theta a Map from type variable to inference variable - * @param context inference context - * @return initial bounds - */ - public static BoundSet initialBounds(Theta theta, Java8InferenceContext context) { - BoundSet boundSet = new BoundSet(context); - boundSet.variables.addAll(theta.values()); - return boundSet; - } - - /** - * Merges {@code newSet} into this bound set. - * - * @param newSet bound set to merge - * @return whether the merge changed this bound set - */ - public boolean merge(BoundSet newSet) { - boolean changed = captures.addAll(newSet.captures); - changed |= variables.addAll(newSet.variables); - containsFalse |= newSet.containsFalse; - uncheckedConversion |= newSet.uncheckedConversion; - annoInferenceFailed |= newSet.annoInferenceFailed; - if (this.errorMsg.isEmpty()) { - this.errorMsg = newSet.errorMsg; - } else if (!newSet.errorMsg.isEmpty()) { - this.errorMsg += " " + newSet.errorMsg; + + /** + * Restore the bounds to the last saved state. This method is called if the first attempt at + * resolution fails. + */ + public void restore() { + for (Variable v : variables) { + v.restore(); + } } - return changed; - } - - /** Adds the false bound to this bound set. */ - public void addFalse() { - containsFalse = true; - } - - /** - * Return whether this bound set contains false. - * - * @return whether this bound set contains false - */ - public boolean containsFalse() { - return containsFalse; - } - - /** - * Return whether unchecked conversion was necessary to reduce and incorporate this bound set - * - * @return whether unchecked conversion was necessary to reduce and incorporate this bound set - */ - public boolean isUncheckedConversion() { - return uncheckedConversion; - } - - /** - * Sets whether unchecked conversion was necessary to reduce and incorporate this bound set. - * - * @param uncheckedConversion whether unchecked conversion was necessary to reduce and incorporate - * this bound set - */ - public void setUncheckedConversion(boolean uncheckedConversion) { - this.uncheckedConversion = uncheckedConversion; - } - - /** - * Adds {@code capture} to this bound set. - * - * @param capture a capture bound - */ - public void addCapture(CaptureBound capture) { - captures.add(capture); - variables.addAll(capture.getAllVariablesOnLHS()); - } - - /** - * Does the bound set contain a bound of the form {@code G<..., ai, ...> = capture(G<...>)} for - * any variable in {@code as}? - * - * @param as a collection of varialbes - * @return whether the bound set contain a bound of the form {@code G<..., ai, ...> = - * capture(G<...>)} for any variable in {@code as} - */ - public boolean containsCapture(Collection as) { - List list = new ArrayList<>(); - for (CaptureBound c : captures) { - list.addAll(c.getAllVariablesOnLHS()); + + /** + * Creates a new bound set for the variables in theta. (The initial bounds for the variables + * were added to the variables when theta was created.) + * + * @param theta a Map from type variable to inference variable + * @param context inference context + * @return initial bounds + */ + public static BoundSet initialBounds(Theta theta, Java8InferenceContext context) { + BoundSet boundSet = new BoundSet(context); + boundSet.variables.addAll(theta.values()); + return boundSet; } - for (Variable ai : as) { - if (list.contains(ai)) { - return true; - } + + /** + * Merges {@code newSet} into this bound set. + * + * @param newSet bound set to merge + * @return whether the merge changed this bound set + */ + public boolean merge(BoundSet newSet) { + boolean changed = captures.addAll(newSet.captures); + changed |= variables.addAll(newSet.variables); + containsFalse |= newSet.containsFalse; + uncheckedConversion |= newSet.uncheckedConversion; + annoInferenceFailed |= newSet.annoInferenceFailed; + if (this.errorMsg.isEmpty()) { + this.errorMsg = newSet.errorMsg; + } else if (!newSet.errorMsg.isEmpty()) { + this.errorMsg += " " + newSet.errorMsg; + } + return changed; } - return false; - } - - /** - * Returns a list of variables in {@code alphas} that are instantiated. - * - * @param alphas a list of variables - * @return a list of variables in {@code alphas} that are instantiated - */ - public List getInstantiationsInAlphas(Collection alphas) { - List list = new ArrayList<>(); - for (Variable var : alphas) { - if (var.getBounds().hasInstantiation()) { - list.add(var); - } + + /** Adds the false bound to this bound set. */ + public void addFalse() { + containsFalse = true; } - return list; - } - - /** - * Returns a list of all variables in this bound set that are instantiated. - * - * @return a list of all variables in this bound set that are instantiated - */ - public List getInstantiatedVariables() { - List list = new ArrayList<>(); - for (Variable var : variables) { - if (var.getBounds().hasInstantiation()) { - list.add(var); - } + + /** + * Return whether this bound set contains false. + * + * @return whether this bound set contains false + */ + public boolean containsFalse() { + return containsFalse; } - return list; - } - - /** - * Resolve all inference variables mentioned in any bound. - * - * @return a list of resolved variables in this bounds set - */ - public List resolve() { - BoundSet b = Resolution.resolve(new ArrayList<>(variables), this, context); - return b.getInstantiationsInAlphas(variables); - } - - /** - * Returns the dependencies between variables. - * - * @return the dependencies between variables - */ - public Dependencies getDependencies() { - return getDependencies(new ArrayList<>()); - } - - /** - * Adds the {@code additionalVars} to this bound set and returns the dependencies between all - * variables in this bound set. - * - * @param additionalVars variables to add to this bound set - * @return the dependencies between all variables in this bound set - */ - public Dependencies getDependencies(Collection additionalVars) { - variables.addAll(additionalVars); - Dependencies dependencies = new Dependencies(); - - for (CaptureBound capture : captures) { - List lhsVars = capture.getAllVariablesOnLHS(); - Set rhsVars = capture.getAllVariablesOnRHS(); - for (Variable var : lhsVars) { - // An inference variable alpha appearing on the left-hand side of a bound of the - // form G<..., alpha, ...> = capture(G<...>) depends on the resolution of every - // other inference variable mentioned in this bound (on both sides of the = sign). - dependencies.putOrAddAll(var, rhsVars); - dependencies.putOrAddAll(var, lhsVars); - } + + /** + * Return whether unchecked conversion was necessary to reduce and incorporate this bound set + * + * @return whether unchecked conversion was necessary to reduce and incorporate this bound set + */ + public boolean isUncheckedConversion() { + return uncheckedConversion; } - Set allVariables = new LinkedHashSet<>(variables); - allVariables.addAll(additionalVars); - for (Variable alpha : allVariables) { - LinkedHashSet alphaDependencies = new LinkedHashSet<>(); - // An inference variable alpha depends on the resolution of itself. - alphaDependencies.add(alpha); - alphaDependencies.addAll(alpha.getBounds().getVariablesMentionedInBounds()); - - if (alpha.isCaptureVariable()) { - // If alpha appears on the left-hand side of another bound of the form - // G<..., alpha, ...> = capture(G<...>), then beta depends on the resolution of - // alpha. - for (Variable beta : alphaDependencies) { - dependencies.putOrAdd(beta, alpha); + + /** + * Sets whether unchecked conversion was necessary to reduce and incorporate this bound set. + * + * @param uncheckedConversion whether unchecked conversion was necessary to reduce and + * incorporate this bound set + */ + public void setUncheckedConversion(boolean uncheckedConversion) { + this.uncheckedConversion = uncheckedConversion; + } + + /** + * Adds {@code capture} to this bound set. + * + * @param capture a capture bound + */ + public void addCapture(CaptureBound capture) { + captures.add(capture); + variables.addAll(capture.getAllVariablesOnLHS()); + } + + /** + * Does the bound set contain a bound of the form {@code G<..., ai, ...> = capture(G<...>)} for + * any variable in {@code as}? + * + * @param as a collection of varialbes + * @return whether the bound set contain a bound of the form {@code G<..., ai, ...> = + * capture(G<...>)} for any variable in {@code as} + */ + public boolean containsCapture(Collection as) { + List list = new ArrayList<>(); + for (CaptureBound c : captures) { + list.addAll(c.getAllVariablesOnLHS()); } - } else { - for (Variable beta : alphaDependencies) { - if (!beta.isCaptureVariable()) { - // Otherwise, alpha depends on the resolution of beta. - dependencies.putOrAdd(alpha, beta); - } + for (Variable ai : as) { + if (list.contains(ai)) { + return true; + } } - } + return false; } - // Add transitive dependencies - dependencies.calculateTransitiveDependencies(); - - return dependencies; - } - - /** - * Incorporates {@code newBounds} into this bounds set. - * - *

          Incorporation creates new constraints that are then reduced to a bound set which is further - * incorporated into this bound set. Incorporation terminates when the bounds set has reached a - * fixed point. JLS 18 .1 - * defines this fixed point and further explains incorporation. - * - * @param newBounds bounds to incorporate - */ - public void incorporateToFixedPoint(final BoundSet newBounds) { - this.containsFalse |= newBounds.containsFalse; - if (this.containsFalse()) { - return; + /** + * Returns a list of variables in {@code alphas} that are instantiated. + * + * @param alphas a list of variables + * @return a list of variables in {@code alphas} that are instantiated + */ + public List getInstantiationsInAlphas(Collection alphas) { + List list = new ArrayList<>(); + for (Variable var : alphas) { + if (var.getBounds().hasInstantiation()) { + list.add(var); + } + } + return list; } - merge(newBounds); - int count = 0; - do { - count++; - List instantiations = getInstantiatedVariables(); - boolean boundsChangeInst = false; - if (!instantiations.isEmpty()) { + + /** + * Returns a list of all variables in this bound set that are instantiated. + * + * @return a list of all variables in this bound set that are instantiated + */ + public List getInstantiatedVariables() { + List list = new ArrayList<>(); for (Variable var : variables) { - boundsChangeInst = var.getBounds().applyInstantiationsToBounds(); + if (var.getBounds().hasInstantiation()) { + list.add(var); + } } - } - boundsChangeInst |= captures.addAll(newBounds.captures); - for (Variable alpha : variables) { - boundsChangeInst = alpha.getBounds().applyInstantiationsToBounds(); - - while (!alpha.getBounds().constraints.isEmpty()) { - boundsChangeInst = true; - merge(alpha.getBounds().constraints.reduceOneStep(context)); - alpha.getBounds().applyInstantiationsToBounds(); + return list; + } + + /** + * Resolve all inference variables mentioned in any bound. + * + * @return a list of resolved variables in this bounds set + */ + public List resolve() { + BoundSet b = Resolution.resolve(new ArrayList<>(variables), this, context); + return b.getInstantiationsInAlphas(variables); + } + + /** + * Returns the dependencies between variables. + * + * @return the dependencies between variables + */ + public Dependencies getDependencies() { + return getDependencies(new ArrayList<>()); + } + + /** + * Adds the {@code additionalVars} to this bound set and returns the dependencies between all + * variables in this bound set. + * + * @param additionalVars variables to add to this bound set + * @return the dependencies between all variables in this bound set + */ + public Dependencies getDependencies(Collection additionalVars) { + variables.addAll(additionalVars); + Dependencies dependencies = new Dependencies(); + + for (CaptureBound capture : captures) { + List lhsVars = capture.getAllVariablesOnLHS(); + Set rhsVars = capture.getAllVariablesOnRHS(); + for (Variable var : lhsVars) { + // An inference variable alpha appearing on the left-hand side of a bound of the + // form G<..., alpha, ...> = capture(G<...>) depends on the resolution of every + // other inference variable mentioned in this bound (on both sides of the = sign). + dependencies.putOrAddAll(var, rhsVars); + dependencies.putOrAddAll(var, lhsVars); + } } - } - if (newBounds.isUncheckedConversion()) { - this.setUncheckedConversion(true); - } - - if (!boundsChangeInst) { - return; - } - - containsFalse |= newBounds.containsFalse; - assert count < MAX_INCORPORATION_STEPS : "Max incorporation steps reached."; - } while (!containsFalse && count < MAX_INCORPORATION_STEPS); - } - - /** - * Remove any capture bound that mentions any variable in {@code as}. - * - * @param as a set of variables - */ - public void removeCaptures(Set as) { - captures.removeIf((CaptureBound c) -> c.isCaptureMentionsAny(as)); - } - - @Override - public String toString() { - if (containsFalse) { - return "FALSE"; - } else if (variables.isEmpty()) { - return "EMPTY"; + Set allVariables = new LinkedHashSet<>(variables); + allVariables.addAll(additionalVars); + for (Variable alpha : allVariables) { + LinkedHashSet alphaDependencies = new LinkedHashSet<>(); + // An inference variable alpha depends on the resolution of itself. + alphaDependencies.add(alpha); + alphaDependencies.addAll(alpha.getBounds().getVariablesMentionedInBounds()); + + if (alpha.isCaptureVariable()) { + // If alpha appears on the left-hand side of another bound of the form + // G<..., alpha, ...> = capture(G<...>), then beta depends on the resolution of + // alpha. + for (Variable beta : alphaDependencies) { + dependencies.putOrAdd(beta, alpha); + } + } else { + for (Variable beta : alphaDependencies) { + if (!beta.isCaptureVariable()) { + // Otherwise, alpha depends on the resolution of beta. + dependencies.putOrAdd(alpha, beta); + } + } + } + } + + // Add transitive dependencies + dependencies.calculateTransitiveDependencies(); + + return dependencies; + } + + /** + * Incorporates {@code newBounds} into this bounds set. + * + *

          Incorporation creates new constraints that are then reduced to a bound set which is + * further incorporated into this bound set. Incorporation terminates when the bounds set has + * reached a fixed point. JLS 18 .1 + * defines this fixed point and further explains incorporation. + * + * @param newBounds bounds to incorporate + */ + public void incorporateToFixedPoint(final BoundSet newBounds) { + this.containsFalse |= newBounds.containsFalse; + if (this.containsFalse()) { + return; + } + merge(newBounds); + int count = 0; + do { + count++; + List instantiations = getInstantiatedVariables(); + boolean boundsChangeInst = false; + if (!instantiations.isEmpty()) { + for (Variable var : variables) { + boundsChangeInst = var.getBounds().applyInstantiationsToBounds(); + } + } + boundsChangeInst |= captures.addAll(newBounds.captures); + for (Variable alpha : variables) { + boundsChangeInst = alpha.getBounds().applyInstantiationsToBounds(); + + while (!alpha.getBounds().constraints.isEmpty()) { + boundsChangeInst = true; + merge(alpha.getBounds().constraints.reduceOneStep(context)); + alpha.getBounds().applyInstantiationsToBounds(); + } + } + if (newBounds.isUncheckedConversion()) { + this.setUncheckedConversion(true); + } + + if (!boundsChangeInst) { + return; + } + + containsFalse |= newBounds.containsFalse; + assert count < MAX_INCORPORATION_STEPS : "Max incorporation steps reached."; + } while (!containsFalse && count < MAX_INCORPORATION_STEPS); } - String vars = StringsPlume.join(", ", getInstantiatedVariables()); - if (vars.isEmpty()) { - return "No instantiated variables"; - } else { - return "Instantiated variables: " + vars; + + /** + * Remove any capture bound that mentions any variable in {@code as}. + * + * @param as a set of variables + */ + public void removeCaptures(Set as) { + captures.removeIf((CaptureBound c) -> c.isCaptureMentionsAny(as)); + } + + @Override + public String toString() { + if (containsFalse) { + return "FALSE"; + } else if (variables.isEmpty()) { + return "EMPTY"; + } + String vars = StringsPlume.join(", ", getInstantiatedVariables()); + if (vars.isEmpty()) { + return "No instantiated variables"; + } else { + return "Instantiated variables: " + vars; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java index 55ee4e7ded0..57b8e82e011 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/bound/CaptureBound.java @@ -1,16 +1,7 @@ package org.checkerframework.framework.util.typeinference8.bound; import com.sun.source.tree.ExpressionTree; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; + import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; import org.checkerframework.framework.util.typeinference8.constraint.Typing; @@ -23,216 +14,229 @@ import org.checkerframework.framework.util.typeinference8.util.Theta; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; + /** * A bound of the form: {@code G = capture(G)}. The variables a1, ..., an * represent the result of capture conversion applied to {@code G} (where A1, ..., An * may be types or wildcards and may mention inference variables). */ public class CaptureBound { - /** {@code G} sometimes called the right hand side */ - private final AbstractType capturedType; - - /** - * The substitution [P1 := alpha1, ..., Pn := alphan] where P1, ..., Pn are the type parameters of - * the underlying type, G. - */ - private final Theta map; - - /** {@code G} */ - private final InferenceType lhs; - - /** A list of {@link CaptureTuple}s. */ - private final List tuples = new ArrayList<>(); - - /** - * All capture variables in this capture. For example, a1 in {@code G = capture(G)}. - */ - private final List captureVariables = new ArrayList<>(); - - /** - * Creates a captured bound. - * - * @param capturedType a capture type - * @param invocation invocation a method or constructor invocation; used to create fresh inference - * variables - * @param context the context - */ - private CaptureBound( - AbstractType capturedType, ExpressionTree invocation, Java8InferenceContext context) { - this.capturedType = capturedType; - DeclaredType underlying = (DeclaredType) capturedType.getJavaType(); - TypeElement ele = TypesUtils.getTypeElement(underlying); - this.map = context.inferenceTypeFactory.createThetaForCapture(invocation, capturedType); - - lhs = (InferenceType) context.inferenceTypeFactory.getTypeOfElement(ele, map); - - Iterator alphas = this.map.values().iterator(); - Iterator args = capturedType.getTypeArguments().iterator(); - for (TypeParameterElement pEle : ele.getTypeParameters()) { - AbstractType Bi = context.inferenceTypeFactory.getTypeOfBound(pEle, map); - AbstractType Ai = args.next(); - - CaptureVariable alphai = (CaptureVariable) alphas.next(); - captureVariables.add(alphai); - alphai.initialBounds(map); - - tuples.add(CaptureTuple.of(alphai, Ai, Bi)); - } - } - - /** - * Given {@code r}, a parameterized type, {@code G}}, and one of {@code A1, ..., An} - * is a wildcard, then, for fresh inference variables {@code B1, ..., Bn}, the constraint formula - * {@code -> T>} is reduced and incorporated, along with the bound {@code G = capture(G)}, with B2. - * - * @param r a parameterized type, {@code G}, and one of {@code A1, ..., An} is a - * wildcard - * @param target target of the constraint - * @param invocation invocation a method or constructor invocation; used to create fresh inference - * variables - * @param context the context - * @return the result of incorporating the created capture constraint - */ - public static BoundSet createAndIncorporateCaptureConstraint( - AbstractType r, - AbstractType target, - ExpressionTree invocation, - Java8InferenceContext context) { - CaptureBound capture = new CaptureBound(r, invocation, context); - return capture.incorporate(target, context); - } - - /** - * Incorporate this capture bound. See JLS 18.3.1. - * - *

          Also, reduces and incorporates the constraint {@code G -> target}. See JLS - * 18.5.2.1. - * - * @param target the target type of - * @param context the context - * @return the result of incorporation - */ - private BoundSet incorporate(AbstractType target, Java8InferenceContext context) { - // First add the non-wildcard bounds. - for (CaptureTuple t : tuples) { - if (t.capturedTypeArg.getTypeKind() != TypeKind.WILDCARD) { - // If Ai is not a wildcard, then the bound alphai = Ai is implied. - t.alpha.getBounds().addBound(VariableBounds.BoundKind.EQUAL, t.capturedTypeArg); - } - } + /** {@code G} sometimes called the right hand side */ + private final AbstractType capturedType; + + /** + * The substitution [P1 := alpha1, ..., Pn := alphan] where P1, ..., Pn are the type parameters + * of the underlying type, G. + */ + private final Theta map; + + /** {@code G} */ + private final InferenceType lhs; - ConstraintSet set = new ConstraintSet(new Typing(lhs, target, Kind.TYPE_COMPATIBILITY)); - // Reduce and incorporate so that the capture variables bounds are set. - BoundSet b1 = set.reduce(context); - b1.incorporateToFixedPoint(new BoundSet(context)); - - // Then create constraints implied by captured type args that are wildcards. - boolean containsFalse = false; - for (CaptureTuple t : tuples) { - if (t.capturedTypeArg.getTypeKind() == TypeKind.WILDCARD) { - ConstraintSet newCon = t.alpha.getWildcardConstraints(t.capturedTypeArg, t.bound); - if (newCon == null) { - containsFalse = true; - } else { - set.addAll(newCon); + /** A list of {@link CaptureTuple}s. */ + private final List tuples = new ArrayList<>(); + + /** + * All capture variables in this capture. For example, a1 in {@code G = + * capture(G)}. + */ + private final List captureVariables = new ArrayList<>(); + + /** + * Creates a captured bound. + * + * @param capturedType a capture type + * @param invocation invocation a method or constructor invocation; used to create fresh + * inference variables + * @param context the context + */ + private CaptureBound( + AbstractType capturedType, ExpressionTree invocation, Java8InferenceContext context) { + this.capturedType = capturedType; + DeclaredType underlying = (DeclaredType) capturedType.getJavaType(); + TypeElement ele = TypesUtils.getTypeElement(underlying); + this.map = context.inferenceTypeFactory.createThetaForCapture(invocation, capturedType); + + lhs = (InferenceType) context.inferenceTypeFactory.getTypeOfElement(ele, map); + + Iterator alphas = this.map.values().iterator(); + Iterator args = capturedType.getTypeArguments().iterator(); + for (TypeParameterElement pEle : ele.getTypeParameters()) { + AbstractType Bi = context.inferenceTypeFactory.getTypeOfBound(pEle, map); + AbstractType Ai = args.next(); + + CaptureVariable alphai = (CaptureVariable) alphas.next(); + captureVariables.add(alphai); + alphai.initialBounds(map); + + tuples.add(CaptureTuple.of(alphai, Ai, Bi)); } - } } - // Reduce and incorporate again. - BoundSet b2 = set.reduce(context); - b2.addCapture(this); - if (containsFalse) { - b2.addFalse(); - } - b1.incorporateToFixedPoint(b2); - return b1; - } - - /** - * Return all variables on the left-hand side of this capture. - * - * @return all variables on the left-hand side of this capture - */ - public List getAllVariablesOnLHS() { - return captureVariables; - } - - /** - * Return all variables on the right-hand side of this capture. - * - * @return all variables on the right-hand side of this capture - */ - public Set getAllVariablesOnRHS() { - return new LinkedHashSet<>(capturedType.getInferenceVariables()); - } - - /** - * Returns whether this bound contains any {@code variables}. - * - * @param variables inference variables - * @return whether this bound contains any {@code variables} - */ - public boolean isCaptureMentionsAny(Collection variables) { - for (Variable a : variables) { - if (map.containsValue(a)) { - return true; - } + /** + * Given {@code r}, a parameterized type, {@code G}}, and one of {@code A1, ..., + * An} is a wildcard, then, for fresh inference variables {@code B1, ..., Bn}, the constraint + * formula {@code -> T>} is reduced and incorporated, along with the bound + * {@code G = capture(G)}, with B2. + * + * @param r a parameterized type, {@code G}, and one of {@code A1, ..., An} is a + * wildcard + * @param target target of the constraint + * @param invocation invocation a method or constructor invocation; used to create fresh + * inference variables + * @param context the context + * @return the result of incorporating the created capture constraint + */ + public static BoundSet createAndIncorporateCaptureConstraint( + AbstractType r, + AbstractType target, + ExpressionTree invocation, + Java8InferenceContext context) { + CaptureBound capture = new CaptureBound(r, invocation, context); + return capture.incorporate(target, context); } - return false; - } - - /** - * For a capture of the form: {@code G = capture(G)}, a capture tuple - * groups ai, Ai, and the upper bound of the corresponding type variable. - */ - private static class CaptureTuple { /** - * Fresh inference variable (in the left hand side of the capture). (Also referred to as beta in - * the some places in the JLS.) For example {@code a1} in {@code G = capture(G)}. + * Incorporate this capture bound. See JLS 18.3.1. + * + *

          Also, reduces and incorporates the constraint {@code G -> target}. See JLS + * 18.5.2.1. + * + * @param target the target type of + * @param context the context + * @return the result of incorporation */ - public final CaptureVariable alpha; + private BoundSet incorporate(AbstractType target, Java8InferenceContext context) { + // First add the non-wildcard bounds. + for (CaptureTuple t : tuples) { + if (t.capturedTypeArg.getTypeKind() != TypeKind.WILDCARD) { + // If Ai is not a wildcard, then the bound alphai = Ai is implied. + t.alpha.getBounds().addBound(VariableBounds.BoundKind.EQUAL, t.capturedTypeArg); + } + } + + ConstraintSet set = new ConstraintSet(new Typing(lhs, target, Kind.TYPE_COMPATIBILITY)); + // Reduce and incorporate so that the capture variables bounds are set. + BoundSet b1 = set.reduce(context); + b1.incorporateToFixedPoint(new BoundSet(context)); + + // Then create constraints implied by captured type args that are wildcards. + boolean containsFalse = false; + for (CaptureTuple t : tuples) { + if (t.capturedTypeArg.getTypeKind() == TypeKind.WILDCARD) { + ConstraintSet newCon = t.alpha.getWildcardConstraints(t.capturedTypeArg, t.bound); + if (newCon == null) { + containsFalse = true; + } else { + set.addAll(newCon); + } + } + } + + // Reduce and incorporate again. + BoundSet b2 = set.reduce(context); + b2.addCapture(this); + if (containsFalse) { + b2.addFalse(); + } + b1.incorporateToFixedPoint(b2); + return b1; + } /** - * Type argument in the right hand side for the capture. For example {@code A1} in {@code G = capture(G)}. + * Return all variables on the left-hand side of this capture. + * + * @return all variables on the left-hand side of this capture */ - public final AbstractType capturedTypeArg; + public List getAllVariablesOnLHS() { + return captureVariables; + } /** - * Upper bound of one of the type parameters of G that has been substituted using the fresh - * inference variables. + * Return all variables on the right-hand side of this capture. + * + * @return all variables on the right-hand side of this capture */ - public final AbstractType bound; + public Set getAllVariablesOnRHS() { + return new LinkedHashSet<>(capturedType.getInferenceVariables()); + } /** - * Creates a tuple. + * Returns whether this bound contains any {@code variables}. * - * @param alpha capture variable - * @param capturedTypeArg captured type argument - * @param bound the bound of the type parameter + * @param variables inference variables + * @return whether this bound contains any {@code variables} */ - private CaptureTuple(CaptureVariable alpha, AbstractType capturedTypeArg, AbstractType bound) { - this.alpha = alpha; - this.capturedTypeArg = capturedTypeArg; - this.bound = bound; + public boolean isCaptureMentionsAny(Collection variables) { + for (Variable a : variables) { + if (map.containsValue(a)) { + return true; + } + } + return false; } /** - * Creates a tuple. - * - * @param alpha capture variable - * @param capturedTypeArg captured type argument - * @param bound the bound of the type parameter - * @return a tuple + * For a capture of the form: {@code G = capture(G)}, a capture tuple + * groups ai, Ai, and the upper bound of the corresponding type variable. */ - public static CaptureTuple of( - CaptureVariable alpha, AbstractType capturedTypeArg, AbstractType bound) { - return new CaptureTuple(alpha, capturedTypeArg, bound); + private static class CaptureTuple { + + /** + * Fresh inference variable (in the left hand side of the capture). (Also referred to as + * beta in the some places in the JLS.) For example {@code a1} in {@code G = + * capture(G)}. + */ + public final CaptureVariable alpha; + + /** + * Type argument in the right hand side for the capture. For example {@code A1} in {@code + * G = capture(G)}. + */ + public final AbstractType capturedTypeArg; + + /** + * Upper bound of one of the type parameters of G that has been substituted using the fresh + * inference variables. + */ + public final AbstractType bound; + + /** + * Creates a tuple. + * + * @param alpha capture variable + * @param capturedTypeArg captured type argument + * @param bound the bound of the type parameter + */ + private CaptureTuple( + CaptureVariable alpha, AbstractType capturedTypeArg, AbstractType bound) { + this.alpha = alpha; + this.capturedTypeArg = capturedTypeArg; + this.bound = bound; + } + + /** + * Creates a tuple. + * + * @param alpha capture variable + * @param capturedTypeArg captured type argument + * @param bound the bound of the type parameter + * @return a tuple + */ + public static CaptureTuple of( + CaptureVariable alpha, AbstractType capturedTypeArg, AbstractType bound) { + return new CaptureTuple(alpha, capturedTypeArg, bound); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java index 9275a9a99d2..c908981a86d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/AdditionalArgument.java @@ -5,6 +5,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; + import org.checkerframework.framework.util.typeinference8.types.InvocationType; import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; import org.checkerframework.framework.util.typeinference8.util.Theta; @@ -19,48 +20,50 @@ */ public class AdditionalArgument implements Constraint { - /** The tree for the method or constructor invocation for this constraint. */ - private ExpressionTree methodOrConstructorInvocation; + /** The tree for the method or constructor invocation for this constraint. */ + private ExpressionTree methodOrConstructorInvocation; - /** - * Creates a new constraint. - * - * @param methodOrConstructorInvocation tree for the method or constructor invocation for this - * constraint - */ - public AdditionalArgument(ExpressionTree methodOrConstructorInvocation) { - this.methodOrConstructorInvocation = methodOrConstructorInvocation; - } + /** + * Creates a new constraint. + * + * @param methodOrConstructorInvocation tree for the method or constructor invocation for this + * constraint + */ + public AdditionalArgument(ExpressionTree methodOrConstructorInvocation) { + this.methodOrConstructorInvocation = methodOrConstructorInvocation; + } - @Override - public Kind getKind() { - return Kind.ADDITIONAL_ARG; - } + @Override + public Kind getKind() { + return Kind.ADDITIONAL_ARG; + } - @Override - public ConstraintSet reduce(Java8InferenceContext context) { - if (methodOrConstructorInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { - MethodInvocationTree methodInvocation = (MethodInvocationTree) methodOrConstructorInvocation; - InvocationType methodType = - context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(methodInvocation); - Theta newMap = - context.inferenceTypeFactory.createThetaForInvocation( - methodInvocation, methodType, context); - ConstraintSet set = - context.inference.createC(methodType, methodInvocation.getArguments(), newMap); - set.applyInstantiations(); - return set; - } else { - NewClassTree newClassTree = (NewClassTree) methodOrConstructorInvocation; - InvocationType methodType = - context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(newClassTree); + @Override + public ConstraintSet reduce(Java8InferenceContext context) { + if (methodOrConstructorInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { + MethodInvocationTree methodInvocation = + (MethodInvocationTree) methodOrConstructorInvocation; + InvocationType methodType = + context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(methodInvocation); + Theta newMap = + context.inferenceTypeFactory.createThetaForInvocation( + methodInvocation, methodType, context); + ConstraintSet set = + context.inference.createC(methodType, methodInvocation.getArguments(), newMap); + set.applyInstantiations(); + return set; + } else { + NewClassTree newClassTree = (NewClassTree) methodOrConstructorInvocation; + InvocationType methodType = + context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(newClassTree); - Theta newMap = - context.inferenceTypeFactory.createThetaForInvocation(newClassTree, methodType, context); - ConstraintSet set = - context.inference.createC(methodType, newClassTree.getArguments(), newMap); - set.applyInstantiations(); - return set; + Theta newMap = + context.inferenceTypeFactory.createThetaForInvocation( + newClassTree, methodType, context); + ConstraintSet set = + context.inference.createC(methodType, newClassTree.getArguments(), newMap); + set.applyInstantiations(); + return set; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java index d82bd82a565..b2c20db80e6 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/CheckedExceptionConstraint.java @@ -2,14 +2,16 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; + import org.checkerframework.framework.util.typeinference8.types.AbstractType; import org.checkerframework.framework.util.typeinference8.types.Variable; import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; import org.checkerframework.framework.util.typeinference8.util.Theta; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + /** * <LambdaExpression →throws T>: The checked exceptions thrown by the body of the * LambdaExpression are declared by the throws clause of the function type derived from T. @@ -19,78 +21,78 @@ */ public class CheckedExceptionConstraint extends TypeConstraint { - /** - * {@link com.sun.source.tree.LambdaExpressionTree} or {@link - * com.sun.source.tree.MemberReferenceTree} for this constraint. - */ - protected final ExpressionTree expression; - - /** The mapping from type variable to inference variable to use with this constraint. */ - protected final Theta map; + /** + * {@link com.sun.source.tree.LambdaExpressionTree} or {@link + * com.sun.source.tree.MemberReferenceTree} for this constraint. + */ + protected final ExpressionTree expression; - /** - * Creates a {@code CheckedExceptionConstraint}. - * - * @param expression {@link com.sun.source.tree.LambdaExpressionTree} or {@link - * com.sun.source.tree.MemberReferenceTree} for this constraint - * @param t a function type - * @param map The mapping from type variable to inference variable to use with this constraint - */ - public CheckedExceptionConstraint(ExpressionTree expression, AbstractType t, Theta map) { - super(t); - assert expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION - || expression.getKind() == Tree.Kind.MEMBER_REFERENCE; - this.expression = expression; - this.map = map; - } + /** The mapping from type variable to inference variable to use with this constraint. */ + protected final Theta map; - @Override - public Kind getKind() { - return expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION - ? Kind.LAMBDA_EXCEPTION - : Kind.METHOD_REF_EXCEPTION; - } - - @Override - public List getInputVariables() { - return getInputVariablesForExpression(expression, getT()); - } - - @Override - public List getOutputVariables() { - List input = getInputVariables(); - List output = new ArrayList<>(getT().getInferenceVariables()); - output.removeAll(input); - return output; - } + /** + * Creates a {@code CheckedExceptionConstraint}. + * + * @param expression {@link com.sun.source.tree.LambdaExpressionTree} or {@link + * com.sun.source.tree.MemberReferenceTree} for this constraint + * @param t a function type + * @param map The mapping from type variable to inference variable to use with this constraint + */ + public CheckedExceptionConstraint(ExpressionTree expression, AbstractType t, Theta map) { + super(t); + assert expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION + || expression.getKind() == Tree.Kind.MEMBER_REFERENCE; + this.expression = expression; + this.map = map; + } - @Override - public ReductionResult reduce(Java8InferenceContext context) { - // See JLS 18.2.5 - return context.inferenceTypeFactory.getCheckedExceptionConstraints(expression, T, map); - } + @Override + public Kind getKind() { + return expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION + ? Kind.LAMBDA_EXCEPTION + : Kind.METHOD_REF_EXCEPTION; + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + @Override + public List getInputVariables() { + return getInputVariablesForExpression(expression, getT()); } - if (o == null || getClass() != o.getClass()) { - return false; + + @Override + public List getOutputVariables() { + List input = getInputVariables(); + List output = new ArrayList<>(getT().getInferenceVariables()); + output.removeAll(input); + return output; } - if (!super.equals(o)) { - return false; + + @Override + public ReductionResult reduce(Java8InferenceContext context) { + // See JLS 18.2.5 + return context.inferenceTypeFactory.getCheckedExceptionConstraints(expression, T, map); } - CheckedExceptionConstraint that = (CheckedExceptionConstraint) o; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + CheckedExceptionConstraint that = (CheckedExceptionConstraint) o; - return Objects.equals(expression, that.expression); - } + return Objects.equals(expression, that.expression); + } - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (expression != null ? expression.hashCode() : 0); - return result; - } + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (expression != null ? expression.hashCode() : 0); + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java index cdc63ecc94c..a5b9ad0816f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Constraint.java @@ -8,62 +8,62 @@ */ public interface Constraint extends ReductionResult { - /** - * Return the kind of constraint. - * - * @return the kind of constraint - */ - Kind getKind(); - - /** - * Reduce this constraint what this means depends on the kind of constraint. Reduction can produce - * new bounds and/or new constraints. - * - *

          Reduction is documented in JLS section - * 18.2 - * - * @param context Java8InferenceContext - * @return the result of reducing this constraint - */ - ReductionResult reduce(Java8InferenceContext context); - - /** A kind of Constraint. */ - enum Kind { - /** - * {@code < Expression -> T >}: An expression is compatible in a loose invocation context with - * type T - */ - EXPRESSION, - /** {@code < S -> T >}: A type S is compatible in a loose invocation context with type T */ - TYPE_COMPATIBILITY, - /** {@code < S <: T >}: A reference type S is a subtype of a reference type T */ - SUBTYPE, - /** {@code < S <= T >}: A type argument S is contained by a type argument T. */ - CONTAINED, /** - * {@code < S = T >}: A type S is the same as a type T, or a type argument S is the same as type - * argument T. + * Return the kind of constraint. + * + * @return the kind of constraint */ - TYPE_EQUALITY, - /** - * {@code < LambdaExpression -> throws T>}: The checked exceptions thrown by the body of the - * LambdaExpression are declared by the throws clause of the function type derived from T. - */ - LAMBDA_EXCEPTION, + Kind getKind(); + /** - * {@code < MethodReferenceExpression -> throws T>}: The checked exceptions thrown by the - * referenced method are declared by the throws clause of the function type derived from T. + * Reduce this constraint what this means depends on the kind of constraint. Reduction can + * produce new bounds and/or new constraints. + * + *

          Reduction is documented in JLS section + * 18.2 + * + * @param context Java8InferenceContext + * @return the result of reducing this constraint */ - METHOD_REF_EXCEPTION, + ReductionResult reduce(Java8InferenceContext context); + + /** A kind of Constraint. */ + enum Kind { + /** + * {@code < Expression -> T >}: An expression is compatible in a loose invocation context + * with type T + */ + EXPRESSION, + /** {@code < S -> T >}: A type S is compatible in a loose invocation context with type T */ + TYPE_COMPATIBILITY, + /** {@code < S <: T >}: A reference type S is a subtype of a reference type T */ + SUBTYPE, + /** {@code < S <= T >}: A type argument S is contained by a type argument T. */ + CONTAINED, + /** + * {@code < S = T >}: A type S is the same as a type T, or a type argument S is the same as + * type argument T. + */ + TYPE_EQUALITY, + /** + * {@code < LambdaExpression -> throws T>}: The checked exceptions thrown by the body of the + * LambdaExpression are declared by the throws clause of the function type derived from T. + */ + LAMBDA_EXCEPTION, + /** + * {@code < MethodReferenceExpression -> throws T>}: The checked exceptions thrown by the + * referenced method are declared by the throws clause of the function type derived from T. + */ + METHOD_REF_EXCEPTION, - /** {@code < Q <: R >}: A qualifier Q is a subtype of a qualifier R. */ - QUALIFIER_SUBTYPE, + /** {@code < Q <: R >}: A qualifier Q is a subtype of a qualifier R. */ + QUALIFIER_SUBTYPE, - /** {@code < Q = R >}: A qualifier R is the same as a qualifier R. */ - QUALIFIER_EQUALITY, + /** {@code < Q = R >}: A qualifier R is the same as a qualifier R. */ + QUALIFIER_EQUALITY, - /** A single constraint, that when reduced, generates additional argument constraints. */ - ADDITIONAL_ARG, - } + /** A single constraint, that when reduced, generates additional argument constraints. */ + ADDITIONAL_ARG, + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java index 2ef8416be3c..6d215605a92 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ConstraintSet.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.util.typeinference8.constraint; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.framework.util.typeinference8.bound.BoundSet; import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; @@ -16,322 +9,330 @@ import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; import org.checkerframework.javacutil.BugInCF; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + /** A set of constraints and the operations that can be performed on them. */ public class ConstraintSet implements ReductionResult { - /** The result given when a constraint set reduces to true. */ - @SuppressWarnings("interning:assignment") - public static final @InternedDistinct ConstraintSet TRUE = - new ConstraintSet() { - @Override - public String toString() { - return "TRUE"; + /** The result given when a constraint set reduces to true. */ + @SuppressWarnings("interning:assignment") + public static final @InternedDistinct ConstraintSet TRUE = + new ConstraintSet() { + @Override + public String toString() { + return "TRUE"; + } + }; + + /** + * The Java types are correct, but the qualifiers are not in the correct relationship. Return + * this rather than throwing an exception so that type arguments with the correct Java type are + * still inferred. + */ + @SuppressWarnings("interning:assignment") + public static final @InternedDistinct ConstraintSet TRUE_ANNO_FAIL = + new ConstraintSet(true) { + @Override + public String toString() { + return "TRUE_ANNO_FAIL"; + } + }; + + /** The result given when a constraint set reduces to false. */ + @SuppressWarnings("interning:assignment") + public static final @InternedDistinct ReductionResult FALSE = + new ReductionResult() { + @Override + public String toString() { + return "FALSE"; + } + }; + + /** + * A list of constraints in this set. It does not contain constraints that are equal. This needs + * to be kept in the order created, which should be lexically left to right. This is so the + * {@link #getClosedSubset(Dependencies)} is computed correctly. + */ + private final List list; + + /** Whether inference failed because the qualifiers where not in the correct relationship. */ + private boolean annotationFailure = false; + + /** + * Creates a new constraint set. + * + * @param annotationFailure inference failed because the qualifiers where not in the correct + * relationship + */ + private ConstraintSet(boolean annotationFailure) { + this(); + this.annotationFailure = annotationFailure; + } + + /** + * Creates a constraint set with {@code constraints}. + * + * @param constraints constraints to add to the newly created set + */ + public ConstraintSet(Constraint... constraints) { + if (constraints != null) { + list = new ArrayList<>(constraints.length); + list.addAll(Arrays.asList(constraints)); + } else { + list = new ArrayList<>(); } - }; - - /** - * The Java types are correct, but the qualifiers are not in the correct relationship. Return this - * rather than throwing an exception so that type arguments with the correct Java type are still - * inferred. - */ - @SuppressWarnings("interning:assignment") - public static final @InternedDistinct ConstraintSet TRUE_ANNO_FAIL = - new ConstraintSet(true) { - @Override - public String toString() { - return "TRUE_ANNO_FAIL"; + } + + /** + * Adds {@code c} to this set, if c isn't already in the list. + * + * @param c a constraint to add to this set + */ + public void add(Constraint c) { + if (c != null && !list.contains(c)) { + list.add(c); } - }; - - /** The result given when a constraint set reduces to false. */ - @SuppressWarnings("interning:assignment") - public static final @InternedDistinct ReductionResult FALSE = - new ReductionResult() { - @Override - public String toString() { - return "FALSE"; + } + + /** + * Adds all constraints in {@code constraintSet} to this constraint set. + * + * @param constraintSet a set of constraints to add to this set + */ + public void addAll(ConstraintSet constraintSet) { + if (constraintSet.annotationFailure) { + this.annotationFailure = true; } - }; - - /** - * A list of constraints in this set. It does not contain constraints that are equal. This needs - * to be kept in the order created, which should be lexically left to right. This is so the {@link - * #getClosedSubset(Dependencies)} is computed correctly. - */ - private final List list; - - /** Whether inference failed because the qualifiers where not in the correct relationship. */ - private boolean annotationFailure = false; - - /** - * Creates a new constraint set. - * - * @param annotationFailure inference failed because the qualifiers where not in the correct - * relationship - */ - private ConstraintSet(boolean annotationFailure) { - this(); - this.annotationFailure = annotationFailure; - } - - /** - * Creates a constraint set with {@code constraints}. - * - * @param constraints constraints to add to the newly created set - */ - public ConstraintSet(Constraint... constraints) { - if (constraints != null) { - list = new ArrayList<>(constraints.length); - list.addAll(Arrays.asList(constraints)); - } else { - list = new ArrayList<>(); + constraintSet.list.forEach(this::add); } - } - - /** - * Adds {@code c} to this set, if c isn't already in the list. - * - * @param c a constraint to add to this set - */ - public void add(Constraint c) { - if (c != null && !list.contains(c)) { - list.add(c); + + /** + * Adds all constraints in {@code constraintSet} to this constraint set. + * + * @param constraintSet a collection of constraints to add to this set + */ + public void addAll(Collection constraintSet) { + list.addAll(constraintSet); } - } - - /** - * Adds all constraints in {@code constraintSet} to this constraint set. - * - * @param constraintSet a set of constraints to add to this set - */ - public void addAll(ConstraintSet constraintSet) { - if (constraintSet.annotationFailure) { - this.annotationFailure = true; + + /** + * Return whether or not this constraint set is empty. + * + * @return whether or not this constraint set is empty + */ + public boolean isEmpty() { + return list.isEmpty(); } - constraintSet.list.forEach(this::add); - } - - /** - * Adds all constraints in {@code constraintSet} to this constraint set. - * - * @param constraintSet a collection of constraints to add to this set - */ - public void addAll(Collection constraintSet) { - list.addAll(constraintSet); - } - - /** - * Return whether or not this constraint set is empty. - * - * @return whether or not this constraint set is empty - */ - public boolean isEmpty() { - return list.isEmpty(); - } - - /** - * Removes and returns the first constraint that was added to this set. - * - * @return first constraint that was added to this set - */ - public Constraint pop() { - assert !isEmpty(); - return list.remove(0); - } - - /** - * Remove all constraints in {@code subset} from this constraint set. - * - * @param subset the set of constraints to remove from this set - */ - @SuppressWarnings("interning:not.interned") - public void remove(ConstraintSet subset) { - if (this == subset) { - list.clear(); + + /** + * Removes and returns the first constraint that was added to this set. + * + * @return first constraint that was added to this set + */ + public Constraint pop() { + assert !isEmpty(); + return list.remove(0); } - list.removeAll(subset.list); - } - - /** - * A subset of constraints is selected in this constraint set, satisfying the property that, for - * each constraint, no input variable can influence an output variable of another constraint in - * this constraint set. (See JLS 18.5.2.2) - * - * @param dependencies an object describing the dependencies of inference variables - * @return s a subset of constraints is this constraint set - */ - public ConstraintSet getClosedSubset(Dependencies dependencies) { - ConstraintSet subset = new ConstraintSet(); - Set inputDependencies = new LinkedHashSet<>(); - Set outDependencies = new LinkedHashSet<>(); - for (Constraint constraint : list) { - if (constraint.getKind() == Kind.EXPRESSION - || constraint.getKind() == Kind.LAMBDA_EXCEPTION - || constraint.getKind() == Kind.METHOD_REF_EXCEPTION) { - TypeConstraint c = (TypeConstraint) constraint; - Set newInputs = dependencies.get(c.getInputVariables()); - Set newOutputs = dependencies.get(c.getOutputVariables()); - if (Collections.disjoint(newInputs, outDependencies) - && Collections.disjoint(newOutputs, inputDependencies)) { - inputDependencies.addAll(newInputs); - outDependencies.addAll(newOutputs); - subset.add(c); - } else { - // A cycle (or cycles) in the graph of dependencies between constraints exists. - subset = new ConstraintSet(); - break; + + /** + * Remove all constraints in {@code subset} from this constraint set. + * + * @param subset the set of constraints to remove from this set + */ + @SuppressWarnings("interning:not.interned") + public void remove(ConstraintSet subset) { + if (this == subset) { + list.clear(); } - } else { - subset.add(constraint); - } + list.removeAll(subset.list); } - if (!subset.isEmpty()) { - return subset; - } + /** + * A subset of constraints is selected in this constraint set, satisfying the property that, for + * each constraint, no input variable can influence an output variable of another constraint in + * this constraint set. (See JLS 18.5.2.2) + * + * @param dependencies an object describing the dependencies of inference variables + * @return s a subset of constraints is this constraint set + */ + public ConstraintSet getClosedSubset(Dependencies dependencies) { + ConstraintSet subset = new ConstraintSet(); + Set inputDependencies = new LinkedHashSet<>(); + Set outDependencies = new LinkedHashSet<>(); + for (Constraint constraint : list) { + if (constraint.getKind() == Kind.EXPRESSION + || constraint.getKind() == Kind.LAMBDA_EXCEPTION + || constraint.getKind() == Kind.METHOD_REF_EXCEPTION) { + TypeConstraint c = (TypeConstraint) constraint; + Set newInputs = dependencies.get(c.getInputVariables()); + Set newOutputs = dependencies.get(c.getOutputVariables()); + if (Collections.disjoint(newInputs, outDependencies) + && Collections.disjoint(newOutputs, inputDependencies)) { + inputDependencies.addAll(newInputs); + outDependencies.addAll(newOutputs); + subset.add(c); + } else { + // A cycle (or cycles) in the graph of dependencies between constraints exists. + subset = new ConstraintSet(); + break; + } + } else { + subset.add(constraint); + } + } - outDependencies.clear(); - inputDependencies.clear(); - // If this subset is empty, then there is a cycle (or cycles) in the graph of dependencies - // between constraints. - List consideredConstraints = new ArrayList<>(); - for (Constraint constraint : list) { - if (!(constraint instanceof TypeConstraint)) { - continue; - } - TypeConstraint c = (TypeConstraint) constraint; - Set newInputs = dependencies.get(c.getInputVariables()); - Set newOutputs = dependencies.get(c.getOutputVariables()); - if (inputDependencies.isEmpty() - || !Collections.disjoint(newInputs, outDependencies) - || !Collections.disjoint(newOutputs, inputDependencies)) { - inputDependencies.addAll(newInputs); - outDependencies.addAll(newOutputs); - consideredConstraints.add(c); - } - } + if (!subset.isEmpty()) { + return subset; + } + + outDependencies.clear(); + inputDependencies.clear(); + // If this subset is empty, then there is a cycle (or cycles) in the graph of dependencies + // between constraints. + List consideredConstraints = new ArrayList<>(); + for (Constraint constraint : list) { + if (!(constraint instanceof TypeConstraint)) { + continue; + } + TypeConstraint c = (TypeConstraint) constraint; + Set newInputs = dependencies.get(c.getInputVariables()); + Set newOutputs = dependencies.get(c.getOutputVariables()); + if (inputDependencies.isEmpty() + || !Collections.disjoint(newInputs, outDependencies) + || !Collections.disjoint(newOutputs, inputDependencies)) { + inputDependencies.addAll(newInputs); + outDependencies.addAll(newOutputs); + consideredConstraints.add(c); + } + } + + // A single constraint is selected from the considered constraints, as follows: - // A single constraint is selected from the considered constraints, as follows: + // If any of the considered constraints have the form T>, then the selected + // constraint is the considered constraint of this form that contains the expression to the + // left (3.5) of the expression of every other considered constraint of this form. - // If any of the considered constraints have the form T>, then the selected - // constraint is the considered constraint of this form that contains the expression to the - // left (3.5) of the expression of every other considered constraint of this form. + // If no considered constraint has the form T>, then the selected constraint + // is the considered constraint that contains the expression to the left of the expression + // of every other considered constraint. - // If no considered constraint has the form T>, then the selected constraint - // is the considered constraint that contains the expression to the left of the expression - // of every other considered constraint. + for (Constraint c : consideredConstraints) { + if (c.getKind() == Kind.EXPRESSION) { + return new ConstraintSet(c); + } + } - for (Constraint c : consideredConstraints) { - if (c.getKind() == Kind.EXPRESSION) { - return new ConstraintSet(c); - } + return new ConstraintSet(consideredConstraints.get(0)); } - return new ConstraintSet(consideredConstraints.get(0)); - } - - /** - * Return all variables mentioned by any constraint in this set. - * - * @return all variables mentioned by any constraint in this set - */ - public Set getAllInferenceVariables() { - Set vars = new LinkedHashSet<>(); - for (Constraint c : list) { - if (c instanceof TypeConstraint) { - vars.addAll(((TypeConstraint) c).getInferenceVariables()); - } + /** + * Return all variables mentioned by any constraint in this set. + * + * @return all variables mentioned by any constraint in this set + */ + public Set getAllInferenceVariables() { + Set vars = new LinkedHashSet<>(); + for (Constraint c : list) { + if (c instanceof TypeConstraint) { + vars.addAll(((TypeConstraint) c).getInferenceVariables()); + } + } + return vars; } - return vars; - } - - /** - * Return all input variables for all constraints in this set. - * - * @return all input variables for all constraints in this set - */ - public Set getAllInputVariables() { - Set vars = new LinkedHashSet<>(); - for (Constraint constraint : list) { - if (constraint instanceof TypeConstraint) { - vars.addAll(((TypeConstraint) constraint).getInputVariables()); - } + + /** + * Return all input variables for all constraints in this set. + * + * @return all input variables for all constraints in this set + */ + public Set getAllInputVariables() { + Set vars = new LinkedHashSet<>(); + for (Constraint constraint : list) { + if (constraint instanceof TypeConstraint) { + vars.addAll(((TypeConstraint) constraint).getInputVariables()); + } + } + return vars; } - return vars; - } - - /** Applies the instantiations to all the constraints in this set. */ - public void applyInstantiations() { - for (Constraint constraint : list) { - if (constraint instanceof TypeConstraint) { - ((TypeConstraint) constraint).applyInstantiations(); - } + + /** Applies the instantiations to all the constraints in this set. */ + public void applyInstantiations() { + for (Constraint constraint : list) { + if (constraint instanceof TypeConstraint) { + ((TypeConstraint) constraint).applyInstantiations(); + } + } } - } - - @Override - public String toString() { - return "Size: " + list.size(); - } - - /** - * Reduces all the constraints in this set. (See JLS 18.2) - * - * @param context the context - * @return the bound set produced by reducing this constraint set - */ - public BoundSet reduce(Java8InferenceContext context) { - BoundSet boundSet = new BoundSet(context); - while (!this.isEmpty()) { - if (this.list.size() > BoundSet.MAX_INCORPORATION_STEPS) { - throw new BugInCF("TO MANY CONSTRAINTS: %s", context.pathToExpression.getLeaf()); - } - boundSet.merge(reduceOneStep(context)); + + @Override + public String toString() { + return "Size: " + list.size(); } - return boundSet; - } - - /** - * Reduce one constraint in this set. - * - * @param context the context - * @return the result of reducing one constraint in this set - */ - public BoundSet reduceOneStep(Java8InferenceContext context) { - boolean alreadyFailed = this.annotationFailure; - BoundSet boundSet = new BoundSet(context); - - Constraint constraint = this.pop(); - ReductionResult result = constraint.reduce(context); - if (result instanceof ReductionResultPair) { - boundSet.merge(((ReductionResultPair) result).boundSet); - if (boundSet.containsFalse()) { - throw new FalseBoundException(constraint, result); - } - this.addAll(((ReductionResultPair) result).constraintSet); - } else if (result instanceof TypeConstraint) { - this.add((Constraint) result); - } else if (result instanceof ConstraintSet) { - this.addAll((ConstraintSet) result); - } else if (result instanceof BoundSet) { - boundSet.merge((BoundSet) result); - if (boundSet.containsFalse()) { - throw new FalseBoundException(constraint, result); - } - } else if (result == null || result == ConstraintSet.FALSE) { - throw new FalseBoundException(constraint, result); - } else if (result == UNCHECKED_CONVERSION) { - boundSet.setUncheckedConversion(true); - } else { - throw new RuntimeException("Not found " + result); + /** + * Reduces all the constraints in this set. (See JLS 18.2) + * + * @param context the context + * @return the bound set produced by reducing this constraint set + */ + public BoundSet reduce(Java8InferenceContext context) { + BoundSet boundSet = new BoundSet(context); + while (!this.isEmpty()) { + if (this.list.size() > BoundSet.MAX_INCORPORATION_STEPS) { + throw new BugInCF("TO MANY CONSTRAINTS: %s", context.pathToExpression.getLeaf()); + } + boundSet.merge(reduceOneStep(context)); + } + + return boundSet; } - if (this.annotationFailure) { - boundSet.annoInferenceFailed = true; - if (!alreadyFailed && boundSet.errorMsg.isEmpty()) { - boundSet.errorMsg = constraint.toString(); - } + + /** + * Reduce one constraint in this set. + * + * @param context the context + * @return the result of reducing one constraint in this set + */ + public BoundSet reduceOneStep(Java8InferenceContext context) { + boolean alreadyFailed = this.annotationFailure; + BoundSet boundSet = new BoundSet(context); + + Constraint constraint = this.pop(); + ReductionResult result = constraint.reduce(context); + if (result instanceof ReductionResultPair) { + boundSet.merge(((ReductionResultPair) result).boundSet); + if (boundSet.containsFalse()) { + throw new FalseBoundException(constraint, result); + } + this.addAll(((ReductionResultPair) result).constraintSet); + } else if (result instanceof TypeConstraint) { + this.add((Constraint) result); + } else if (result instanceof ConstraintSet) { + this.addAll((ConstraintSet) result); + } else if (result instanceof BoundSet) { + boundSet.merge((BoundSet) result); + if (boundSet.containsFalse()) { + throw new FalseBoundException(constraint, result); + } + } else if (result == null || result == ConstraintSet.FALSE) { + throw new FalseBoundException(constraint, result); + } else if (result == UNCHECKED_CONVERSION) { + boundSet.setUncheckedConversion(true); + } else { + throw new RuntimeException("Not found " + result); + } + if (this.annotationFailure) { + boundSet.annoInferenceFailed = true; + if (!alreadyFailed && boundSet.errorMsg.isEmpty()) { + boundSet.errorMsg = constraint.toString(); + } + } + return boundSet; } - return boundSet; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java index 69b4fa1d7ff..09e462186df 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java @@ -9,10 +9,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import javax.lang.model.type.TypeKind; + import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.util.typeinference8.bound.BoundSet; import org.checkerframework.framework.util.typeinference8.types.AbstractType; @@ -29,434 +26,451 @@ import org.checkerframework.javacutil.TreeUtils.MemberReferenceKind; import org.plumelib.util.IPair; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.lang.model.type.TypeKind; + /** * <Expression → T> An expression is compatible in a loose invocation context with type T */ public class Expression extends TypeConstraint { - /** Expression that is compatible in a loose invocation context with {@link #T}. */ - private final ExpressionTree expression; - - /** - * Creates an expression constraint. - * - * @param expressionTree the expression for the constraint - * @param t the type that the expression is compatible in a loose invocation context - */ - public Expression(ExpressionTree expressionTree, AbstractType t) { - super(t); - this.expression = expressionTree; - assert expression != null; - } - - @Override - public Kind getKind() { - return Kind.EXPRESSION; - } - - @Override - public List getInputVariables() { - return getInputVariablesForExpression(expression, getT()); - } - - @Override - public List getOutputVariables() { - List input = getInputVariables(); - List output = new ArrayList<>(getT().getInferenceVariables()); - output.removeAll(input); - return output; - } - - @Override - public ReductionResult reduce(Java8InferenceContext context) { - // See JLS 18.2.1 - if (getT().isProper()) { - return reduceProperType(); - } else if (TreeUtils.isStandaloneExpression(expression)) { - AbstractType s; - if (!context.isLambdaParam(expression)) { - s = new ProperType(expression, context); - } else { - AnnotatedTypeMirror atm = context.typeFactory.getAnnotatedType(expression); - s = getT().create(atm, atm.getUnderlyingType()); - } - return new Typing(s, T, TypeConstraint.Kind.TYPE_COMPATIBILITY); - } - switch (expression.getKind()) { - case PARENTHESIZED: - return new Expression(TreeUtils.withoutParens(expression), T); - case NEW_CLASS: - case METHOD_INVOCATION: - return reduceMethodInvocation(context); - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree conditional = (ConditionalExpressionTree) expression; - TypeConstraint trueConstraint = new Expression(conditional.getTrueExpression(), T); - Constraint falseConstraint = new Expression(conditional.getFalseExpression(), T); - return new ConstraintSet(trueConstraint, falseConstraint); - case LAMBDA_EXPRESSION: - return reduceLambda(context); - case MEMBER_REFERENCE: - return reduceMethodRef(context); - default: - if (TreeUtils.isSwitchExpression(expression)) { - ConstraintSet set = new ConstraintSet(); - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (ExpressionTree valueTree, Void unused) -> { - Constraint c = new Expression(valueTree, T); - set.add(c); - return null; - }, - (c1, c2) -> null); - scanner.scanSwitchExpression(expression, null); - return set; - } - throw new BugInCF( - "Unexpected expression kind: %s, Expression: %s", expression.getKind(), expression); - } - } - - /** - * JSL 18.2.1: "If T is a proper type, the constraint reduces to true if the expression is - * compatible in a loose invocation context with T (5.3), and false otherwise." - * - * @return the result of reducing a proper type - */ - private ReductionResult reduceProperType() { - // Assume the constraint reduces to TRUE, if it did not the code wouldn't compile with - // javac. - - // TODO: This should return false in some cases. - // com.sun.tools.javac.code.Types.isConvertible(com.sun.tools.javac.code.Type, - // com.sun.tools.javac.code.Type) - return new ConstraintSet(); - } - - /** - * Text from JLS 18.2.1: If the expression is a class instance creation expression or a method - * invocation expression, the constraint reduces to the bound set B3 which would be used to - * determine the expression's invocation type when targeting T, as defined in 18.5.2. (For a class - * instance creation expression, the corresponding "method" used for inference is defined in - * 15.9.3). - * - *

          This bound set may contain new inference variables, as well as dependencies between these - * new variables and the inference variables in T. - * - * @param context the context - * @return the result of reducing this constraint - */ - private BoundSet reduceMethodInvocation(Java8InferenceContext context) { - ExpressionTree expressionTree = expression; - List args; - if (expressionTree.getKind() == Tree.Kind.NEW_CLASS) { - NewClassTree newClassTree = (NewClassTree) expressionTree; - args = newClassTree.getArguments(); - } else { - MethodInvocationTree methodInvocationTree = (MethodInvocationTree) expressionTree; - args = methodInvocationTree.getArguments(); + /** Expression that is compatible in a loose invocation context with {@link #T}. */ + private final ExpressionTree expression; + + /** + * Creates an expression constraint. + * + * @param expressionTree the expression for the constraint + * @param t the type that the expression is compatible in a loose invocation context + */ + public Expression(ExpressionTree expressionTree, AbstractType t) { + super(t); + this.expression = expressionTree; + assert expression != null; } - InvocationType methodType = - context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(expressionTree); - Theta map = - context.inferenceTypeFactory.createThetaForInvocation(expressionTree, methodType, context); - BoundSet b2 = context.inference.createB2(methodType, args, map); - return context.inference.createB3(b2, expressionTree, methodType, T, map); - } - - /** - * Reduce this constraint - * - * @param context the context - * @return the result of reducing this constraint - */ - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300 - private ReductionResult reduceMethodRef(Java8InferenceContext context) { - MemberReferenceTree memRef = (MemberReferenceTree) expression; - if (TreeUtils.isExactMethodReference(memRef)) { - InvocationType typeOfPoAppMethod = - context.inferenceTypeFactory.compileTimeDeclarationType(memRef); - - ConstraintSet constraintSet = new ConstraintSet(); - List ps = T.getFunctionTypeParameterTypes(); - List fs = typeOfPoAppMethod.getParameterTypes(null); - - if (ps.size() == fs.size() + 1) { - AbstractType targetReference = ps.remove(0); - ExpressionTree preColonTree = memRef.getQualifierExpression(); - AbstractType referenceType; - if (context.isLambdaParam(preColonTree)) { - AnnotatedTypeMirror atm = context.typeFactory.getAnnotatedType(preColonTree); - referenceType = T.create(atm, atm.getUnderlyingType()); - } else { - if (MemberReferenceKind.getMemberReferenceKind(memRef).isUnbound()) { - AnnotatedTypeMirror atm = - context.typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); - referenceType = new ProperType(atm, atm.getUnderlyingType(), context); - } else { - referenceType = new ProperType(preColonTree, context); - } - } - constraintSet.add(new Typing(targetReference, referenceType, TypeConstraint.Kind.SUBTYPE)); - } - for (int i = 0; i < ps.size(); i++) { - constraintSet.add(new Typing(ps.get(i), fs.get(i), TypeConstraint.Kind.SUBTYPE)); - } - AbstractType r = T.getFunctionTypeReturnType(); - if (r != null && r.getTypeKind() != TypeKind.VOID) { - AbstractType rPrime = typeOfPoAppMethod.getReturnType(null).capture(context); - constraintSet.add(new Typing(rPrime, r, TypeConstraint.Kind.TYPE_COMPATIBILITY)); - } - return constraintSet; + @Override + public Kind getKind() { + return Kind.EXPRESSION; } - // else the method reference is inexact. - // Compile-time declaration of the member reference expression - InvocationType compileTimeDecl = - context.inferenceTypeFactory.compileTimeDeclarationType(memRef); - if (compileTimeDecl.isVoid()) { - return ConstraintSet.TRUE; + @Override + public List getInputVariables() { + return getInputVariablesForExpression(expression, getT()); } - AbstractType r = T.getFunctionTypeReturnType(); - if (r.getTypeKind() == TypeKind.VOID) { - return ConstraintSet.TRUE; + + @Override + public List getOutputVariables() { + List input = getInputVariables(); + List output = new ArrayList<>(getT().getInferenceVariables()); + output.removeAll(input); + return output; } - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300-D-B-BC - // Otherwise, if the method reference expression elides TypeArguments, and the - // compile-time declaration is a generic method, and - // the return type of the compile-time declaration mentions at least one of the method's - // type parameters, the constraint reduces to the bound set B3 which would be used to - // determine the method reference's invocation type when targeting the return type of the - // function type, as defined in 18.5.2. B3 may contain new inference variables, as well as - // dependencies between these new variables and the inference variables in T. - Theta map = - context.inferenceTypeFactory.createThetaForMethodReference( - memRef, compileTimeDecl, context); - AbstractType compileTimeReturn = compileTimeDecl.getReturnType(map); - if (TreeUtils.needsTypeArgInference(memRef) && !compileTimeReturn.isProper()) { - BoundSet b2 = - context.inference.createB2MethodRef( - compileTimeDecl, T.getFunctionTypeParameterTypes(), map); - return context.inference.createB3(b2, memRef, compileTimeDecl, r, map); + @Override + public ReductionResult reduce(Java8InferenceContext context) { + // See JLS 18.2.1 + if (getT().isProper()) { + return reduceProperType(); + } else if (TreeUtils.isStandaloneExpression(expression)) { + AbstractType s; + if (!context.isLambdaParam(expression)) { + s = new ProperType(expression, context); + } else { + AnnotatedTypeMirror atm = context.typeFactory.getAnnotatedType(expression); + s = getT().create(atm, atm.getUnderlyingType()); + } + return new Typing(s, T, TypeConstraint.Kind.TYPE_COMPATIBILITY); + } + switch (expression.getKind()) { + case PARENTHESIZED: + return new Expression(TreeUtils.withoutParens(expression), T); + case NEW_CLASS: + case METHOD_INVOCATION: + return reduceMethodInvocation(context); + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) expression; + TypeConstraint trueConstraint = new Expression(conditional.getTrueExpression(), T); + Constraint falseConstraint = new Expression(conditional.getFalseExpression(), T); + return new ConstraintSet(trueConstraint, falseConstraint); + case LAMBDA_EXPRESSION: + return reduceLambda(context); + case MEMBER_REFERENCE: + return reduceMethodRef(context); + default: + if (TreeUtils.isSwitchExpression(expression)) { + ConstraintSet set = new ConstraintSet(); + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree valueTree, Void unused) -> { + Constraint c = new Expression(valueTree, T); + set.add(c); + return null; + }, + (c1, c2) -> null); + scanner.scanSwitchExpression(expression, null); + return set; + } + throw new BugInCF( + "Unexpected expression kind: %s, Expression: %s", + expression.getKind(), expression); + } } - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300-D-B-C - // Otherwise, let R be the return type of the function type, and let R' be the result - // of applying capture conversion (5.1.10) to the return type of the invocation type - // (15.12.2.6) of the compile-time declaration. If R' is void, the constraint reduces - // to false; otherwise, the constraint reduces to R>. - return ReductionResultPair.of( - new ConstraintSet( - new Typing( - compileTimeReturn.capture(context), r, TypeConstraint.Kind.TYPE_COMPATIBILITY)), - new BoundSet(context)); - } - - /** - * Reduce this constraint - * - * @param context the context - * @return the result of reducing this constraint - */ - // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-200 - private ReductionResultPair reduceLambda(Java8InferenceContext context) { - LambdaExpressionTree lambda = (LambdaExpressionTree) expression; - IPair pair = getGroundTargetType(T, lambda, context); - AbstractType tPrime = pair.first; - BoundSet boundSet = pair.second == null ? new BoundSet(context) : pair.second; - - ConstraintSet constraintSet = new ConstraintSet(); - - if (!TreeUtils.isImplicitlyTypedLambda(lambda)) { - // Explicitly typed lambda - List parameters = lambda.getParameters(); - List gs = T.getFunctionTypeParameterTypes(); - assert parameters.size() == gs.size(); - - for (int i = 0; i < gs.size(); i++) { - VariableTree parameter = parameters.get(i); - AbstractType fi = new ProperType(parameter, context); - AbstractType gi = gs.get(i); - constraintSet.add(new Typing(fi, gi, TypeConstraint.Kind.TYPE_EQUALITY)); - } - constraintSet.add(new Typing(tPrime, T, TypeConstraint.Kind.SUBTYPE)); - } else { - context.addLambdaParms(lambda.getParameters()); + /** + * JSL 18.2.1: "If T is a proper type, the constraint reduces to true if the expression is + * compatible in a loose invocation context with T (5.3), and false otherwise." + * + * @return the result of reducing a proper type + */ + private ReductionResult reduceProperType() { + // Assume the constraint reduces to TRUE, if it did not the code wouldn't compile with + // javac. + + // TODO: This should return false in some cases. + // com.sun.tools.javac.code.Types.isConvertible(com.sun.tools.javac.code.Type, + // com.sun.tools.javac.code.Type) + return new ConstraintSet(); } - AbstractType R = tPrime.getFunctionTypeReturnType(); - if (R != null && R.getTypeKind() != TypeKind.VOID) { - for (ExpressionTree e : TreeUtils.getReturnedExpressions(lambda)) { - if (R.isProper()) { - if (!context.env.getTypeUtils().isAssignable(TreeUtils.typeOf(e), R.getJavaType())) { - boundSet.addFalse(); - return ReductionResultPair.of(constraintSet, boundSet); - } + /** + * Text from JLS 18.2.1: If the expression is a class instance creation expression or a method + * invocation expression, the constraint reduces to the bound set B3 which would be used to + * determine the expression's invocation type when targeting T, as defined in 18.5.2. (For a + * class instance creation expression, the corresponding "method" used for inference is defined + * in 15.9.3). + * + *

          This bound set may contain new inference variables, as well as dependencies between these + * new variables and the inference variables in T. + * + * @param context the context + * @return the result of reducing this constraint + */ + private BoundSet reduceMethodInvocation(Java8InferenceContext context) { + ExpressionTree expressionTree = expression; + List args; + if (expressionTree.getKind() == Tree.Kind.NEW_CLASS) { + NewClassTree newClassTree = (NewClassTree) expressionTree; + args = newClassTree.getArguments(); } else { - constraintSet.add(new Expression(e, R)); + MethodInvocationTree methodInvocationTree = (MethodInvocationTree) expressionTree; + args = methodInvocationTree.getArguments(); } - } - } - return ReductionResultPair.of(constraintSet, boundSet); - } - - /** - * This method sets up functional interface parameterization inference for {@code lambda} as - * defined in JLS 18.5.3. - * - *

          Computes the ground target type of {@code t}. Returned as the first in the pair. This - * process might create additional bounds, if so the second in the returned pair will be non-null. - * - * @param t the target type of {@code lambda} - * @param lambda a lambda to infer functional interface parameterization - * @param context the context - * @return the ground target type - */ - private IPair getGroundTargetType( - AbstractType t, LambdaExpressionTree lambda, Java8InferenceContext context) { - if (!t.isWildcardParameterizedType()) { - return IPair.of(t, null); - } - // 15.27.3: - // If T is a wildcard-parameterized functional interface type and the lambda expression is - // explicitly typed, then the ground target type is inferred as described in 18.5.3. - if (TreeUtils.isExplicitlyTypeLambda(lambda)) { - return explicitlyTypedLambdaWithWildcard(t, lambda, context); - } else { - // If T is a wildcard-parameterized functional interface type and the lambda expression - // is implicitly typed, then the ground target type is the non-wildcard parameterization - // (9.9) of T. - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.9-200-C - return IPair.of(nonWildcardParameterization(t, context), null); + + InvocationType methodType = + context.inferenceTypeFactory.getTypeOfMethodAdaptedToUse(expressionTree); + Theta map = + context.inferenceTypeFactory.createThetaForInvocation( + expressionTree, methodType, context); + BoundSet b2 = context.inference.createB2(methodType, args, map); + return context.inference.createB3(b2, expressionTree, methodType, T, map); } - } - - /** - * Returns the non-wildcard parameterization of {@code t} as defined in JLS 9.9. - * - * @param t a type - * @param context the context - * @return the non-wildcard parameterization of {@code t} - */ - private AbstractType nonWildcardParameterization(AbstractType t, Java8InferenceContext context) { - List As = t.getTypeArguments(); - Iterator Bs = t.getTypeParameterBounds().iterator(); - List Ts = new ArrayList<>(); - for (AbstractType Ai : As) { - ProperType bi = Bs.next(); - if (Ai.getTypeKind() != TypeKind.WILDCARD) { - Ts.add(Ai); - } else if (Ai.isUnboundWildcard()) { - Ts.add(bi); - } else if (Ai.isUpperBoundedWildcard()) { - AbstractType Ui = Ai.getWildcardUpperBound(); - AbstractType glb = context.inferenceTypeFactory.glb(Ui, bi); - Ts.add(glb); - } else { - // Lower bounded wildcard - Ts.add(Ai.getWildcardLowerBound()); - } + + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300 + private ReductionResult reduceMethodRef(Java8InferenceContext context) { + MemberReferenceTree memRef = (MemberReferenceTree) expression; + if (TreeUtils.isExactMethodReference(memRef)) { + InvocationType typeOfPoAppMethod = + context.inferenceTypeFactory.compileTimeDeclarationType(memRef); + + ConstraintSet constraintSet = new ConstraintSet(); + List ps = T.getFunctionTypeParameterTypes(); + List fs = typeOfPoAppMethod.getParameterTypes(null); + + if (ps.size() == fs.size() + 1) { + AbstractType targetReference = ps.remove(0); + ExpressionTree preColonTree = memRef.getQualifierExpression(); + AbstractType referenceType; + if (context.isLambdaParam(preColonTree)) { + AnnotatedTypeMirror atm = context.typeFactory.getAnnotatedType(preColonTree); + referenceType = T.create(atm, atm.getUnderlyingType()); + } else { + if (MemberReferenceKind.getMemberReferenceKind(memRef).isUnbound()) { + AnnotatedTypeMirror atm = + context.typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + referenceType = new ProperType(atm, atm.getUnderlyingType(), context); + } else { + referenceType = new ProperType(preColonTree, context); + } + } + constraintSet.add( + new Typing(targetReference, referenceType, TypeConstraint.Kind.SUBTYPE)); + } + for (int i = 0; i < ps.size(); i++) { + constraintSet.add(new Typing(ps.get(i), fs.get(i), TypeConstraint.Kind.SUBTYPE)); + } + AbstractType r = T.getFunctionTypeReturnType(); + if (r != null && r.getTypeKind() != TypeKind.VOID) { + AbstractType rPrime = typeOfPoAppMethod.getReturnType(null).capture(context); + constraintSet.add(new Typing(rPrime, r, TypeConstraint.Kind.TYPE_COMPATIBILITY)); + } + return constraintSet; + } + // else the method reference is inexact. + + // Compile-time declaration of the member reference expression + InvocationType compileTimeDecl = + context.inferenceTypeFactory.compileTimeDeclarationType(memRef); + if (compileTimeDecl.isVoid()) { + return ConstraintSet.TRUE; + } + AbstractType r = T.getFunctionTypeReturnType(); + if (r.getTypeKind() == TypeKind.VOID) { + return ConstraintSet.TRUE; + } + + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300-D-B-BC + // Otherwise, if the method reference expression elides TypeArguments, and the + // compile-time declaration is a generic method, and + // the return type of the compile-time declaration mentions at least one of the method's + // type parameters, the constraint reduces to the bound set B3 which would be used to + // determine the method reference's invocation type when targeting the return type of the + // function type, as defined in 18.5.2. B3 may contain new inference variables, as well as + // dependencies between these new variables and the inference variables in T. + Theta map = + context.inferenceTypeFactory.createThetaForMethodReference( + memRef, compileTimeDecl, context); + AbstractType compileTimeReturn = compileTimeDecl.getReturnType(map); + if (TreeUtils.needsTypeArgInference(memRef) && !compileTimeReturn.isProper()) { + BoundSet b2 = + context.inference.createB2MethodRef( + compileTimeDecl, T.getFunctionTypeParameterTypes(), map); + return context.inference.createB3(b2, memRef, compileTimeDecl, r, map); + } + + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-300-D-B-C + // Otherwise, let R be the return type of the function type, and let R' be the result + // of applying capture conversion (5.1.10) to the return type of the invocation type + // (15.12.2.6) of the compile-time declaration. If R' is void, the constraint reduces + // to false; otherwise, the constraint reduces to R>. + return ReductionResultPair.of( + new ConstraintSet( + new Typing( + compileTimeReturn.capture(context), + r, + TypeConstraint.Kind.TYPE_COMPATIBILITY)), + new BoundSet(context)); } - return t.replaceTypeArgs(Ts); - } - - /** - * Infers the type of {@code lambda} which may create a bounds set that needs to be resolved as - * part of a larger inference problem. See 18.5.3: Functional Interface Parameterization Inference - * - * @param t the target type of the lambda - * @param lambda a lambda expression - * @param context the context - * @return a pair of the type of the lambda and the bound set that needs to be resolved - */ - private IPair explicitlyTypedLambdaWithWildcard( - AbstractType t, LambdaExpressionTree lambda, Java8InferenceContext context) { - // Where a lambda expression with explicit parameter types P1, ..., Pn targets a functional - // interface type F with at least one wildcard type argument, then a - // parameterization of F may be derived as the ground target type of the lambda expression - // as follows. - List ps = new ArrayList<>(); - for (VariableTree paramTree : lambda.getParameters()) { - ps.add(new ProperType(paramTree, context)); + + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.2.1-200 + private ReductionResultPair reduceLambda(Java8InferenceContext context) { + LambdaExpressionTree lambda = (LambdaExpressionTree) expression; + IPair pair = getGroundTargetType(T, lambda, context); + AbstractType tPrime = pair.first; + BoundSet boundSet = pair.second == null ? new BoundSet(context) : pair.second; + + ConstraintSet constraintSet = new ConstraintSet(); + + if (!TreeUtils.isImplicitlyTypedLambda(lambda)) { + // Explicitly typed lambda + List parameters = lambda.getParameters(); + List gs = T.getFunctionTypeParameterTypes(); + assert parameters.size() == gs.size(); + + for (int i = 0; i < gs.size(); i++) { + VariableTree parameter = parameters.get(i); + AbstractType fi = new ProperType(parameter, context); + AbstractType gi = gs.get(i); + constraintSet.add(new Typing(fi, gi, TypeConstraint.Kind.TYPE_EQUALITY)); + } + constraintSet.add(new Typing(tPrime, T, TypeConstraint.Kind.SUBTYPE)); + } else { + context.addLambdaParms(lambda.getParameters()); + } + + AbstractType R = tPrime.getFunctionTypeReturnType(); + if (R != null && R.getTypeKind() != TypeKind.VOID) { + for (ExpressionTree e : TreeUtils.getReturnedExpressions(lambda)) { + if (R.isProper()) { + if (!context.env + .getTypeUtils() + .isAssignable(TreeUtils.typeOf(e), R.getJavaType())) { + boundSet.addFalse(); + return ReductionResultPair.of(constraintSet, boundSet); + } + } else { + constraintSet.add(new Expression(e, R)); + } + } + } + return ReductionResultPair.of(constraintSet, boundSet); } - // Let Q1, ..., Qk be the parameter types of the function type of the type F, where alpha1, ..., alpham are fresh inference variables. - Theta map = context.inferenceTypeFactory.createThetaForLambda(lambda, t); - List alphas = new ArrayList<>(map.values()); - AbstractType tprime = InferenceType.create(t.getAnnotatedType(), t.getJavaType(), map, context); - - List qs = tprime.getFunctionTypeParameterTypes(); - assert qs.size() == ps.size(); - - // A set of constraint formulas is formed with, for all i (1 <= i <= n), . - ConstraintSet constraintSet = new ConstraintSet(); - for (int i = 0; i < ps.size(); i++) { - ProperType pi = ps.get(i); - AbstractType qi = qs.get(i); - constraintSet.add(new Typing(pi, qi, TypeConstraint.Kind.TYPE_EQUALITY)); + /** + * This method sets up functional interface parameterization inference for {@code lambda} as + * defined in JLS 18.5.3. + * + *

          Computes the ground target type of {@code t}. Returned as the first in the pair. This + * process might create additional bounds, if so the second in the returned pair will be + * non-null. + * + * @param t the target type of {@code lambda} + * @param lambda a lambda to infer functional interface parameterization + * @param context the context + * @return the ground target type + */ + private IPair getGroundTargetType( + AbstractType t, LambdaExpressionTree lambda, Java8InferenceContext context) { + if (!t.isWildcardParameterizedType()) { + return IPair.of(t, null); + } + // 15.27.3: + // If T is a wildcard-parameterized functional interface type and the lambda expression is + // explicitly typed, then the ground target type is inferred as described in 18.5.3. + if (TreeUtils.isExplicitlyTypeLambda(lambda)) { + return explicitlyTypedLambdaWithWildcard(t, lambda, context); + } else { + // If T is a wildcard-parameterized functional interface type and the lambda expression + // is implicitly typed, then the ground target type is the non-wildcard parameterization + // (9.9) of T. + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.9-200-C + return IPair.of(nonWildcardParameterization(t, context), null); + } } - // This constraint formula set is reduced to form the bound set B. - BoundSet b = constraintSet.reduce(context); - assert !b.containsFalse() - : "Bound set contains false during Functional Interface Parameterization Inference"; - - // A new parameterization of the functional interface type, F, is constructed - // as follows, for 1 <= i <= m: - List APrimes = new ArrayList<>(); - Iterator alphaIter = alphas.iterator(); - boolean hasWildcard = false; - for (AbstractType Ai : t.getTypeArguments()) { - Variable alphaI = alphaIter.next(); - // If B contains an instantiation (18.1.3) for alphai, T, then A'i = T. - AbstractType AiPrime = alphaI.getBounds().getInstantiation(); - if (AiPrime == null) { - AiPrime = Ai; - } - APrimes.add(AiPrime); - if (AiPrime.getTypeKind() == TypeKind.WILDCARD) { - hasWildcard = true; - } + + /** + * Returns the non-wildcard parameterization of {@code t} as defined in JLS 9.9. + * + * @param t a type + * @param context the context + * @return the non-wildcard parameterization of {@code t} + */ + private AbstractType nonWildcardParameterization( + AbstractType t, Java8InferenceContext context) { + List As = t.getTypeArguments(); + Iterator Bs = t.getTypeParameterBounds().iterator(); + List Ts = new ArrayList<>(); + for (AbstractType Ai : As) { + ProperType bi = Bs.next(); + if (Ai.getTypeKind() != TypeKind.WILDCARD) { + Ts.add(Ai); + } else if (Ai.isUnboundWildcard()) { + Ts.add(bi); + } else if (Ai.isUpperBoundedWildcard()) { + AbstractType Ui = Ai.getWildcardUpperBound(); + AbstractType glb = context.inferenceTypeFactory.glb(Ui, bi); + Ts.add(glb); + } else { + // Lower bounded wildcard + Ts.add(Ai.getWildcardLowerBound()); + } + } + return t.replaceTypeArgs(Ts); } - // The inferred parameterization is either F, if all the type arguments - // are types, or the non-wildcard parameterization (9.9) of F, if one or more - // type arguments are still wildcards. + /** + * Infers the type of {@code lambda} which may create a bounds set that needs to be resolved as + * part of a larger inference problem. See 18.5.3: Functional Interface Parameterization + * Inference + * + * @param t the target type of the lambda + * @param lambda a lambda expression + * @param context the context + * @return a pair of the type of the lambda and the bound set that needs to be resolved + */ + private IPair explicitlyTypedLambdaWithWildcard( + AbstractType t, LambdaExpressionTree lambda, Java8InferenceContext context) { + // Where a lambda expression with explicit parameter types P1, ..., Pn targets a functional + // interface type F with at least one wildcard type argument, then a + // parameterization of F may be derived as the ground target type of the lambda expression + // as follows. + List ps = new ArrayList<>(); + for (VariableTree paramTree : lambda.getParameters()) { + ps.add(new ProperType(paramTree, context)); + } - AbstractType target = t.replaceTypeArgs(APrimes); - if (hasWildcard) { - return IPair.of(nonWildcardParameterization(target, context), b); - } - return IPair.of(target, b); - } - - @Override - public String toString() { - return expression + " -> " + T; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; + // Let Q1, ..., Qk be the parameter types of the function type of the type F, where alpha1, ..., alpham are fresh inference variables. + Theta map = context.inferenceTypeFactory.createThetaForLambda(lambda, t); + List alphas = new ArrayList<>(map.values()); + AbstractType tprime = + InferenceType.create(t.getAnnotatedType(), t.getJavaType(), map, context); + + List qs = tprime.getFunctionTypeParameterTypes(); + assert qs.size() == ps.size(); + + // A set of constraint formulas is formed with, for all i (1 <= i <= n), . + ConstraintSet constraintSet = new ConstraintSet(); + for (int i = 0; i < ps.size(); i++) { + ProperType pi = ps.get(i); + AbstractType qi = qs.get(i); + constraintSet.add(new Typing(pi, qi, TypeConstraint.Kind.TYPE_EQUALITY)); + } + // This constraint formula set is reduced to form the bound set B. + BoundSet b = constraintSet.reduce(context); + assert !b.containsFalse() + : "Bound set contains false during Functional Interface Parameterization Inference"; + + // A new parameterization of the functional interface type, F, is constructed + // as follows, for 1 <= i <= m: + List APrimes = new ArrayList<>(); + Iterator alphaIter = alphas.iterator(); + boolean hasWildcard = false; + for (AbstractType Ai : t.getTypeArguments()) { + Variable alphaI = alphaIter.next(); + // If B contains an instantiation (18.1.3) for alphai, T, then A'i = T. + AbstractType AiPrime = alphaI.getBounds().getInstantiation(); + if (AiPrime == null) { + AiPrime = Ai; + } + APrimes.add(AiPrime); + if (AiPrime.getTypeKind() == TypeKind.WILDCARD) { + hasWildcard = true; + } + } + + // The inferred parameterization is either F, if all the type arguments + // are types, or the non-wildcard parameterization (9.9) of F, if one or more + // type arguments are still wildcards. + + AbstractType target = t.replaceTypeArgs(APrimes); + if (hasWildcard) { + return IPair.of(nonWildcardParameterization(target, context), b); + } + return IPair.of(target, b); } - if (!super.equals(o)) { - return false; + + @Override + public String toString() { + return expression + " -> " + T; } - Expression that = (Expression) o; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } - return expression.equals(that.expression); - } + Expression that = (Expression) o; - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + expression.hashCode(); - return result; - } + return expression.equals(that.expression); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + expression.hashCode(); + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java index a3dd38ca19a..0563d256b00 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/QualifierTyping.java @@ -1,6 +1,5 @@ package org.checkerframework.framework.util.typeinference8.constraint; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.framework.util.typeinference8.types.AbstractQualifier; import org.checkerframework.framework.util.typeinference8.types.AbstractType; import org.checkerframework.framework.util.typeinference8.types.Qualifier; @@ -9,6 +8,8 @@ import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; import org.checkerframework.javacutil.BugInCF; +import javax.lang.model.element.AnnotationMirror; + /** * Represents a constraint between two {@link AbstractType}. One of: * @@ -21,126 +22,129 @@ */ public class QualifierTyping implements Constraint { - /** The qualifier on the left hand side of the constraint. */ - private final AbstractQualifier Q; + /** The qualifier on the left hand side of the constraint. */ + private final AbstractQualifier Q; - /** The qualifier on the right hand side of the constraint. */ - private final AbstractQualifier R; + /** The qualifier on the right hand side of the constraint. */ + private final AbstractQualifier R; - /** - * Kind of constraint. One of: {@link Kind#QUALIFIER_SUBTYPE} or {@link Kind#QUALIFIER_EQUALITY}. - */ - private final Kind kind; + /** + * Kind of constraint. One of: {@link Kind#QUALIFIER_SUBTYPE} or {@link + * Kind#QUALIFIER_EQUALITY}. + */ + private final Kind kind; - /** - * Creates a qualifier typing constraint. - * - * @param Q the qualifiers on the left hand side of the constraint - * @param R the qualifiers on the right hand side of the constraint - * @param kind the kind of qualifier constraint - */ - public QualifierTyping(AbstractQualifier Q, AbstractQualifier R, Kind kind) { - assert Q != null && R != null; - switch (kind) { - case QUALIFIER_SUBTYPE: - case QUALIFIER_EQUALITY: - break; - default: - throw new BugInCF("Unexpected kind: " + kind); + /** + * Creates a qualifier typing constraint. + * + * @param Q the qualifiers on the left hand side of the constraint + * @param R the qualifiers on the right hand side of the constraint + * @param kind the kind of qualifier constraint + */ + public QualifierTyping(AbstractQualifier Q, AbstractQualifier R, Kind kind) { + assert Q != null && R != null; + switch (kind) { + case QUALIFIER_SUBTYPE: + case QUALIFIER_EQUALITY: + break; + default: + throw new BugInCF("Unexpected kind: " + kind); + } + this.R = R; + this.Q = Q; + this.kind = kind; } - this.R = R; - this.Q = Q; - this.kind = kind; - } - - @Override - public Kind getKind() { - return kind; - } - @Override - public ReductionResult reduce(Java8InferenceContext context) { - switch (getKind()) { - case QUALIFIER_EQUALITY: - return reduceEquality(context); - case QUALIFIER_SUBTYPE: - return reduceSubtyping(context); - default: - throw new BugInCF("Unexpected kind: " + getKind()); + @Override + public Kind getKind() { + return kind; } - } - /** - * Reduce this constraint - * - * @param context the context - * @return the result of reducing this constraint - */ - private ReductionResult reduceSubtyping(Java8InferenceContext context) { - if (Q instanceof Qualifier && R instanceof Qualifier) { - AnnotationMirror qAnno = ((Qualifier) Q).getAnnotation(); - AnnotationMirror rAnno = ((Qualifier) R).getAnnotation(); - if (context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(qAnno, rAnno)) { - return ConstraintSet.TRUE; - } - return ConstraintSet.TRUE_ANNO_FAIL; + @Override + public ReductionResult reduce(Java8InferenceContext context) { + switch (getKind()) { + case QUALIFIER_EQUALITY: + return reduceEquality(context); + case QUALIFIER_SUBTYPE: + return reduceSubtyping(context); + default: + throw new BugInCF("Unexpected kind: " + getKind()); + } } - ConstraintSet constraintSet = new ConstraintSet(); - if (Q instanceof QualifierVar) { - // Q <: R - QualifierVar var = (QualifierVar) Q; - constraintSet.addAll(var.addBound(BoundKind.UPPER, R)); - } - if (R instanceof QualifierVar) { - // Q <: R - QualifierVar var = (QualifierVar) R; - constraintSet.addAll(var.addBound(BoundKind.LOWER, Q)); - } - return constraintSet; - } + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + private ReductionResult reduceSubtyping(Java8InferenceContext context) { + if (Q instanceof Qualifier && R instanceof Qualifier) { + AnnotationMirror qAnno = ((Qualifier) Q).getAnnotation(); + AnnotationMirror rAnno = ((Qualifier) R).getAnnotation(); + if (context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(qAnno, rAnno)) { + return ConstraintSet.TRUE; + } + return ConstraintSet.TRUE_ANNO_FAIL; + } - /** - * Reduce this constraint - * - * @param context the context - * @return the result of reducing this constraint - */ - private ReductionResult reduceEquality(Java8InferenceContext context) { - if (Q instanceof Qualifier && R instanceof Qualifier) { - AnnotationMirror qAnno = ((Qualifier) Q).getAnnotation(); - AnnotationMirror rAnno = ((Qualifier) R).getAnnotation(); - if (context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(qAnno, rAnno) - && context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(rAnno, qAnno)) { - return ConstraintSet.TRUE; - } - return ConstraintSet.TRUE_ANNO_FAIL; + ConstraintSet constraintSet = new ConstraintSet(); + if (Q instanceof QualifierVar) { + // Q <: R + QualifierVar var = (QualifierVar) Q; + constraintSet.addAll(var.addBound(BoundKind.UPPER, R)); + } + if (R instanceof QualifierVar) { + // Q <: R + QualifierVar var = (QualifierVar) R; + constraintSet.addAll(var.addBound(BoundKind.LOWER, Q)); + } + return constraintSet; } - ConstraintSet constraintSet = new ConstraintSet(); - if (Q instanceof QualifierVar) { - // Q == R - QualifierVar var = (QualifierVar) Q; - constraintSet.addAll(var.addBound(BoundKind.EQUAL, R)); - } - if (R instanceof QualifierVar) { - // Q == R - QualifierVar var = (QualifierVar) R; - constraintSet.addAll(var.addBound(BoundKind.EQUAL, Q)); + + /** + * Reduce this constraint + * + * @param context the context + * @return the result of reducing this constraint + */ + private ReductionResult reduceEquality(Java8InferenceContext context) { + if (Q instanceof Qualifier && R instanceof Qualifier) { + AnnotationMirror qAnno = ((Qualifier) Q).getAnnotation(); + AnnotationMirror rAnno = ((Qualifier) R).getAnnotation(); + if (context.typeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(qAnno, rAnno) + && context.typeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly(rAnno, qAnno)) { + return ConstraintSet.TRUE; + } + return ConstraintSet.TRUE_ANNO_FAIL; + } + ConstraintSet constraintSet = new ConstraintSet(); + if (Q instanceof QualifierVar) { + // Q == R + QualifierVar var = (QualifierVar) Q; + constraintSet.addAll(var.addBound(BoundKind.EQUAL, R)); + } + if (R instanceof QualifierVar) { + // Q == R + QualifierVar var = (QualifierVar) R; + constraintSet.addAll(var.addBound(BoundKind.EQUAL, Q)); + } + return constraintSet; } - return constraintSet; - } - @Override - public String toString() { - switch (kind) { - case QUALIFIER_SUBTYPE: - return Q + " <: " + R; + @Override + public String toString() { + switch (kind) { + case QUALIFIER_SUBTYPE: + return Q + " <: " + R; - case QUALIFIER_EQUALITY: - return Q + " = " + R; - default: - assert false; - return super.toString(); + case QUALIFIER_EQUALITY: + return Q + " = " + R; + default: + assert false; + return super.toString(); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java index c6e3ef481d1..16972a788af 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/ReductionResult.java @@ -9,49 +9,49 @@ */ public interface ReductionResult { - /** - * Indicates that the constraint reduced to true, but unchecked conversion is required for the - * method to be applicable. - */ - @SuppressWarnings("interning:assignment") - @InternedDistinct ReductionResult UNCHECKED_CONVERSION = - new ReductionResult() { - @Override - public String toString() { - return "UNCHECKED_CONVERSION"; - } - }; - - /** A reduction result that contains a bound set and a constraint set. */ - class ReductionResultPair implements ReductionResult { - - /** A constraint set. */ - public final ConstraintSet constraintSet; - - /** A bound set. */ - public final BoundSet boundSet; - /** - * Creates a reduction result pair. - * - * @param constraintSet a constraint set - * @param boundSet a bound set + * Indicates that the constraint reduced to true, but unchecked conversion is required for the + * method to be applicable. */ - private ReductionResultPair(ConstraintSet constraintSet, BoundSet boundSet) { - this.constraintSet = constraintSet; - this.boundSet = boundSet; - } + @SuppressWarnings("interning:assignment") + @InternedDistinct ReductionResult UNCHECKED_CONVERSION = + new ReductionResult() { + @Override + public String toString() { + return "UNCHECKED_CONVERSION"; + } + }; + + /** A reduction result that contains a bound set and a constraint set. */ + class ReductionResultPair implements ReductionResult { + + /** A constraint set. */ + public final ConstraintSet constraintSet; + + /** A bound set. */ + public final BoundSet boundSet; + + /** + * Creates a reduction result pair. + * + * @param constraintSet a constraint set + * @param boundSet a bound set + */ + private ReductionResultPair(ConstraintSet constraintSet, BoundSet boundSet) { + this.constraintSet = constraintSet; + this.boundSet = boundSet; + } - /** - * Creates a reduction result pair. - * - * @param constraintSet a constraint set - * @param boundSet a bound set - * @return a reduction result pair - */ - public static ReductionResultPair of(ConstraintSet constraintSet, BoundSet boundSet) { - ReductionResultPair pair = new ReductionResultPair(constraintSet, boundSet); - return pair; + /** + * Creates a reduction result pair. + * + * @param constraintSet a constraint set + * @param boundSet a bound set + * @return a reduction result pair + */ + public static ReductionResultPair of(ConstraintSet constraintSet, BoundSet boundSet) { + ReductionResultPair pair = new ReductionResultPair(constraintSet, boundSet); + return pair; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java index 6d6de5b7373..0ef49a2fb8d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/TypeConstraint.java @@ -4,11 +4,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.MemberReferenceTree; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import javax.lang.model.type.TypeKind; + import org.checkerframework.framework.util.typeinference8.types.AbstractType; import org.checkerframework.framework.util.typeinference8.types.UseOfVariable; import org.checkerframework.framework.util.typeinference8.types.Variable; @@ -16,6 +12,13 @@ import org.checkerframework.javacutil.SwitchExpressionScanner.FunctionalSwitchExpressionScanner; import org.checkerframework.javacutil.TreeUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.type.TypeKind; + /** * Constraints are between either an expression and a type, two types, or an expression and a thrown * type. Defined in getInferenceVariables() { - return T.getInferenceVariables(); - } - - /** - * For lambda and method references constraints, input variables are roughly the inference - * variables mentioned by they function type's parameter types and return types. For conditional - * expression constraints and switch expression constraints, input variables are the union of the - * input variables of its subexpressions. For all other constraints, no input variables exist. - * - *

          Defined in JLS section - * 18.5.2.2 - * - * @return input variables for this constraint - */ - public abstract List getInputVariables(); - - /** - * "The output variables of [expression] constraints are all inference variables mentioned by the - * type on the right-hand side of the constraint, T, that are not input variables." - * - *

          As defined in JLS section - * 18.5.2.2 - * - * @return output variables for this constraint - */ - public abstract List getOutputVariables(); - - /** - * Implementation of {@link #getInputVariables()} that is used both by expressions constraints and - * checked exception constraints - * https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.5.2-200 - * - * @param tree an expression tree - * @param T the type of the right hand side of the constraint - * @return the input variables for this constraint - */ - protected List getInputVariablesForExpression(ExpressionTree tree, AbstractType T) { - switch (tree.getKind()) { - case LAMBDA_EXPRESSION: - if (T.isUseOfVariable()) { - return Collections.singletonList(((UseOfVariable) T).getVariable()); - } else { - LambdaExpressionTree lambdaTree = (LambdaExpressionTree) tree; - List inputs = new ArrayList<>(); - if (TreeUtils.isImplicitlyTypedLambda(lambdaTree)) { - List params = this.T.getFunctionTypeParameterTypes(); - if (params == null) { - // T is not a function type. - return Collections.emptyList(); - } - for (AbstractType param : params) { - inputs.addAll(param.getInferenceVariables()); - } - } - AbstractType R = this.T.getFunctionTypeReturnType(); - if (R == null || R.getTypeKind() == TypeKind.NONE) { - return inputs; - } - for (ExpressionTree e : TreeUtils.getReturnedExpressions(lambdaTree)) { - TypeConstraint c = new Expression(e, R); - inputs.addAll(c.getInputVariables()); - } - return inputs; - } - case MEMBER_REFERENCE: - if (T.isUseOfVariable()) { - return Collections.singletonList(((UseOfVariable) T).getVariable()); - } else if (TreeUtils.isExactMethodReference((MemberReferenceTree) tree)) { - return Collections.emptyList(); - } else { - List params = this.T.getFunctionTypeParameterTypes(); - if (params == null) { - // T is not a function type. - return Collections.emptyList(); - } - List inputs = new ArrayList<>(); - for (AbstractType param : params) { - inputs.addAll(param.getInferenceVariables()); - } - return inputs; - } - case PARENTHESIZED: - return getInputVariablesForExpression(TreeUtils.withoutParens(tree), T); - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree conditional = (ConditionalExpressionTree) tree; - List inputs = new ArrayList<>(); - inputs.addAll(getInputVariablesForExpression(conditional.getTrueExpression(), T)); - inputs.addAll(getInputVariablesForExpression(conditional.getFalseExpression(), T)); - return inputs; - default: - if (TreeUtils.isSwitchExpression(tree)) { - List inputs2 = new ArrayList<>(); - - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (ExpressionTree exTree, Void unused) -> - inputs2.addAll(getInputVariablesForExpression(exTree, T)), - (r1, r2) -> null); - scanner.scanSwitchExpression(tree, null); - return inputs2; - } - return Collections.emptyList(); + /** T, the type on the right hand side of the constraint; may contain inference variables. */ + protected AbstractType T; + + /** + * Creates a type constraint + * + * @param T the type of the right hand side of the constraint + */ + protected TypeConstraint(AbstractType T) { + assert T != null : "Can't create a constraint with a null type."; + this.T = T; + } + + /** + * Returns T which is the type on the right hand side of the constraint. + * + * @return T, that is the type on the right hand side of the constraint + */ + public AbstractType getT() { + return T; + } + + /** + * Returns a collection of all inference variables mentioned by this constraint. + * + * @return a collection of all inference variables mentioned by this constraint + */ + public Collection getInferenceVariables() { + return T.getInferenceVariables(); } - } - - /** - * Apply the given instantiations to any type mentioned in this constraint -- meaning replace any - * mention of a variable in {@code instantiations} with its proper type. - */ - public void applyInstantiations() { - T = T.applyInstantiations(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + + /** + * For lambda and method references constraints, input variables are roughly the inference + * variables mentioned by they function type's parameter types and return types. For conditional + * expression constraints and switch expression constraints, input variables are the union of + * the input variables of its subexpressions. For all other constraints, no input variables + * exist. + * + *

          Defined in JLS + * section 18.5.2.2 + * + * @return input variables for this constraint + */ + public abstract List getInputVariables(); + + /** + * "The output variables of [expression] constraints are all inference variables mentioned by + * the type on the right-hand side of the constraint, T, that are not input variables." + * + *

          As defined in JLS + * section 18.5.2.2 + * + * @return output variables for this constraint + */ + public abstract List getOutputVariables(); + + /** + * Implementation of {@link #getInputVariables()} that is used both by expressions constraints + * and checked exception constraints + * https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.5.2-200 + * + * @param tree an expression tree + * @param T the type of the right hand side of the constraint + * @return the input variables for this constraint + */ + protected List getInputVariablesForExpression(ExpressionTree tree, AbstractType T) { + switch (tree.getKind()) { + case LAMBDA_EXPRESSION: + if (T.isUseOfVariable()) { + return Collections.singletonList(((UseOfVariable) T).getVariable()); + } else { + LambdaExpressionTree lambdaTree = (LambdaExpressionTree) tree; + List inputs = new ArrayList<>(); + if (TreeUtils.isImplicitlyTypedLambda(lambdaTree)) { + List params = this.T.getFunctionTypeParameterTypes(); + if (params == null) { + // T is not a function type. + return Collections.emptyList(); + } + for (AbstractType param : params) { + inputs.addAll(param.getInferenceVariables()); + } + } + AbstractType R = this.T.getFunctionTypeReturnType(); + if (R == null || R.getTypeKind() == TypeKind.NONE) { + return inputs; + } + for (ExpressionTree e : TreeUtils.getReturnedExpressions(lambdaTree)) { + TypeConstraint c = new Expression(e, R); + inputs.addAll(c.getInputVariables()); + } + return inputs; + } + case MEMBER_REFERENCE: + if (T.isUseOfVariable()) { + return Collections.singletonList(((UseOfVariable) T).getVariable()); + } else if (TreeUtils.isExactMethodReference((MemberReferenceTree) tree)) { + return Collections.emptyList(); + } else { + List params = this.T.getFunctionTypeParameterTypes(); + if (params == null) { + // T is not a function type. + return Collections.emptyList(); + } + List inputs = new ArrayList<>(); + for (AbstractType param : params) { + inputs.addAll(param.getInferenceVariables()); + } + return inputs; + } + case PARENTHESIZED: + return getInputVariablesForExpression(TreeUtils.withoutParens(tree), T); + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) tree; + List inputs = new ArrayList<>(); + inputs.addAll(getInputVariablesForExpression(conditional.getTrueExpression(), T)); + inputs.addAll(getInputVariablesForExpression(conditional.getFalseExpression(), T)); + return inputs; + default: + if (TreeUtils.isSwitchExpression(tree)) { + List inputs2 = new ArrayList<>(); + + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree exTree, Void unused) -> + inputs2.addAll( + getInputVariablesForExpression(exTree, T)), + (r1, r2) -> null); + scanner.scanSwitchExpression(tree, null); + return inputs2; + } + return Collections.emptyList(); + } } - if (o == null || getClass() != o.getClass()) { - return false; + + /** + * Apply the given instantiations to any type mentioned in this constraint -- meaning replace + * any mention of a variable in {@code instantiations} with its proper type. + */ + public void applyInstantiations() { + T = T.applyInstantiations(); } - TypeConstraint that = (TypeConstraint) o; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TypeConstraint that = (TypeConstraint) o; - return T.equals(that.T); - } + return T.equals(that.T); + } - @Override - public int hashCode() { - return T.hashCode(); - } + @Override + public int hashCode() { + return T.hashCode(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java index 4dc62859ddd..356cbbd404d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.util.typeinference8.constraint; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import javax.lang.model.type.TypeKind; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.util.typeinference8.types.AbstractType; import org.checkerframework.framework.util.typeinference8.types.InferenceType; @@ -19,6 +12,15 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.lang.model.type.TypeKind; + /** * Represents a constraint between two {@link AbstractType}. One of: * @@ -35,449 +37,454 @@ */ public class Typing extends TypeConstraint { - /** One of the abstract types in this constraint. {@link #T} is the other. */ - private AbstractType S; - - /** - * Kind of constraint. One of: {@link Kind#TYPE_COMPATIBILITY}, {@link Kind#SUBTYPE}, {@link - * Kind#CONTAINED}, or {@link Kind#TYPE_EQUALITY} - */ - private final Kind kind; - - /** Whether this constraint is for a covariant type argument. */ - private boolean isCovarTypeArg; - - /** - * Creates a typing constraint. - * - * @param S left hand side type - * @param t right hand side type - * @param kind the kind of constraint - */ - public Typing(AbstractType S, AbstractType t, Kind kind) { - this(S, t, kind, false); - } - - /** - * Creates a typing constraint. - * - * @param S left hand side type - * @param t right hand side type - * @param kind the kind of constraint - * @param covarTypeArg whether the constraint is for a covariant type argument - */ - public Typing(AbstractType S, AbstractType t, Kind kind, boolean covarTypeArg) { - super(t); - assert S != null; - switch (kind) { - case TYPE_COMPATIBILITY: - case SUBTYPE: - case CONTAINED: - case TYPE_EQUALITY: - break; - default: - throw new BugInCF("Unexpected kind: " + kind); - } - this.S = S; - this.kind = kind; - this.isCovarTypeArg = covarTypeArg; - } - - /** - * Return one of the abstract types in this constraint. - * - * @return one of the abstract types in this constraint - */ - public AbstractType getS() { - return S; - } - - @Override - public Kind getKind() { - return kind; - } - - @Override - public List getInputVariables() { - return Collections.emptyList(); - } - - @Override - public List getOutputVariables() { - return Collections.emptyList(); - } - - @Override - public List getInferenceVariables() { - Set vars = new HashSet<>(); - vars.addAll(T.getInferenceVariables()); - vars.addAll(S.getInferenceVariables()); - return new ArrayList<>(vars); - } - - @Override - public void applyInstantiations() { - super.applyInstantiations(); - S = S.applyInstantiations(); - } - - @Override - public ReductionResult reduce(Java8InferenceContext context) { - - switch (getKind()) { - case TYPE_COMPATIBILITY: - return reduceCompatible(); - case SUBTYPE: - return reduceSubtyping(context); - case CONTAINED: - return reduceContained(); - case TYPE_EQUALITY: - return reduceEquality(); - default: - throw new BugInCF("Unexpected kind: " + getKind()); - } - } - - /** - * Returns the result of reducing this constraint, assuming it is a subtyping constraint. See JLS - * 18.2.3. - * - * @param context the context - * @return the result of reducing the constraint - */ - private ReductionResult reduceSubtyping(Java8InferenceContext context) { - if (S.isProper() && T.isProper()) { - ReductionResult isSubtype = ((ProperType) S).isSubType((ProperType) T); - if (isSubtype == ConstraintSet.TRUE) { - return ConstraintSet.TRUE; - } else if (((ProperType) S).isSubTypeUnchecked((ProperType) T) == ConstraintSet.TRUE) { - return ReductionResult.UNCHECKED_CONVERSION; - } - return isSubtype; - } else if (S.getTypeKind() == TypeKind.NULL) { - if (T.isUseOfVariable()) { - UseOfVariable tUseOf = (UseOfVariable) T; - tUseOf.addQualifierBound(BoundKind.LOWER, S.getQualifiers()); - } - return ConstraintSet.TRUE; - } else if (T.getTypeKind() == TypeKind.NULL) { - return ConstraintSet.FALSE; + /** One of the abstract types in this constraint. {@link #T} is the other. */ + private AbstractType S; + + /** + * Kind of constraint. One of: {@link Kind#TYPE_COMPATIBILITY}, {@link Kind#SUBTYPE}, {@link + * Kind#CONTAINED}, or {@link Kind#TYPE_EQUALITY} + */ + private final Kind kind; + + /** Whether this constraint is for a covariant type argument. */ + private boolean isCovarTypeArg; + + /** + * Creates a typing constraint. + * + * @param S left hand side type + * @param t right hand side type + * @param kind the kind of constraint + */ + public Typing(AbstractType S, AbstractType t, Kind kind) { + this(S, t, kind, false); } - if (S.isUseOfVariable() || T.isUseOfVariable()) { - if (S.isUseOfVariable()) { - if (T.getTypeKind() == TypeKind.TYPEVAR && T.isLowerBoundTypeVariable()) { - ((UseOfVariable) S).addBound(VariableBounds.BoundKind.UPPER, T.getTypeVarLowerBound()); - } else { - ((UseOfVariable) S).addBound(VariableBounds.BoundKind.UPPER, T); - } - } - if (T.isUseOfVariable()) { - if (TypesUtils.isCapturedTypeVariable(S.getJavaType())) { - ((UseOfVariable) T).addBound(VariableBounds.BoundKind.LOWER, S.getTypeVarUpperBound()); + /** + * Creates a typing constraint. + * + * @param S left hand side type + * @param t right hand side type + * @param kind the kind of constraint + * @param covarTypeArg whether the constraint is for a covariant type argument + */ + public Typing(AbstractType S, AbstractType t, Kind kind, boolean covarTypeArg) { + super(t); + assert S != null; + switch (kind) { + case TYPE_COMPATIBILITY: + case SUBTYPE: + case CONTAINED: + case TYPE_EQUALITY: + break; + default: + throw new BugInCF("Unexpected kind: " + kind); } - ((UseOfVariable) T).addBound(VariableBounds.BoundKind.LOWER, S); - } - return ConstraintSet.TRUE; + this.S = S; + this.kind = kind; + this.isCovarTypeArg = covarTypeArg; } - switch (T.getTypeKind()) { - case DECLARED: - return reduceSubtypeClass(context); - case ARRAY: - return reduceSubtypeArray(); - case WILDCARD: - case TYPEVAR: - return reduceSubtypeTypeVariable(); - case INTERSECTION: - return reduceSubtypingIntersection(); - default: - return ConstraintSet.FALSE; + /** + * Return one of the abstract types in this constraint. + * + * @return one of the abstract types in this constraint + */ + public AbstractType getS() { + return S; } - } - - /** - * Returns the result of reducing this constraint, assuming it is a subtyping constraint where - * {@code T} is a class type. See JLS 18.2.3. - * - * @param context the context - * @return the result of reducing the constraint - */ - private ReductionResult reduceSubtypeClass(Java8InferenceContext context) { - if (T.isParameterizedType()) { - // let A1, ..., An be the type arguments of T. Among the supertypes of S, a - // corresponding class or interface type is identified, with type arguments B1, ..., - // Bn. If no such type exists, the constraint reduces to false. Otherwise, the - // constraint reduces to the following new constraints: - // for all i (1 <= i <= n), . - - AbstractType sAsSuper = S.asSuper(T.getJavaType()); - if (sAsSuper == null) { - return ConstraintSet.FALSE; - } else if (sAsSuper.isRaw() || T.isRaw()) { - return ReductionResult.UNCHECKED_CONVERSION; - } - - List Bs = sAsSuper.getTypeArguments(); - Iterator As = T.getTypeArguments().iterator(); - List covariantArgIndexes = - context - .typeFactory - .getTypeHierarchy() - .getCovariantArgIndexes((AnnotatedDeclaredType) T.getAnnotatedType()); - ConstraintSet set = new ConstraintSet(); - int index = 0; - for (AbstractType b : Bs) { - AbstractType a = As.next(); - boolean convarArg = covariantArgIndexes.contains(index); - set.add(new Typing(b, a, Kind.CONTAINED, convarArg)); - index++; - } - - return set; - } else { - // The constraint reduces to true if T is among the supertypes of S, and false - // otherwise. - return ((InferenceType) S).isSubType((ProperType) T); + + @Override + public Kind getKind() { + return kind; } - } - - /** - * Returns the result of reducing this constraint, assuming it is a subtyping constraint where - * {@code T} is an array type. See JLS 18.2.3. - * - * @return the result of reducing the constraint - */ - private ReductionResult reduceSubtypeArray() { - AbstractType msArrayType = S.getMostSpecificArrayType(); - if (msArrayType == null) { - return ConstraintSet.FALSE; + + @Override + public List getInputVariables() { + return Collections.emptyList(); } - if (msArrayType.isPrimitiveArray() && T.isPrimitiveArray()) { - return ConstraintSet.TRUE; - } else { - return new Typing(msArrayType.getComponentType(), T.getComponentType(), Kind.SUBTYPE); + + @Override + public List getOutputVariables() { + return Collections.emptyList(); } - } - - /** - * Returns the result of reducing this constraint, assuming it is a subtyping constraint where - * {@code T} is a type variable. See JLS 18.2.3. - * - * @return the result of reducing the constraint - */ - private ReductionResult reduceSubtypeTypeVariable() { - if (S.getTypeKind() == TypeKind.INTERSECTION) { - return ConstraintSet.TRUE; - } else if (T.getTypeKind() == TypeKind.TYPEVAR && T.isLowerBoundTypeVariable()) { - return new Typing(S, T.getTypeVarLowerBound(), Kind.SUBTYPE); - } else if (T.getTypeKind() == TypeKind.WILDCARD && T.isLowerBoundedWildcard()) { - return new Typing(S, T.getWildcardLowerBound(), Kind.SUBTYPE); - } else { - return ConstraintSet.FALSE; + + @Override + public List getInferenceVariables() { + Set vars = new HashSet<>(); + vars.addAll(T.getInferenceVariables()); + vars.addAll(S.getInferenceVariables()); + return new ArrayList<>(vars); } - } - - /** - * Returns the result of reducing this constraint, assuming it is a subtyping constraint where - * {@code T} is an intersection type. See JLS 18.2.3. - * - * @return the result of reducing the constraint - */ - private ReductionResult reduceSubtypingIntersection() { - ConstraintSet constraintSet = new ConstraintSet(); - for (AbstractType bound : T.getIntersectionBounds()) { - constraintSet.add(new Typing(S, bound, Kind.SUBTYPE)); + + @Override + public void applyInstantiations() { + super.applyInstantiations(); + S = S.applyInstantiations(); } - return constraintSet; - } - - /** - * Returns the result of reducing this constraint, assuming it is a containment constraint. See - * JLS 18.2.3. - * - * @return the result of reducing the constraint - */ - private ReductionResult reduceContained() { - if (T.getTypeKind() != TypeKind.WILDCARD) { - if (S.getTypeKind() != TypeKind.WILDCARD) { - if (isCovarTypeArg) { - return new Typing(S, T, Kind.SUBTYPE); - } - return new Typing(S, T, Kind.TYPE_EQUALITY); - } else { - return ConstraintSet.FALSE; - } - } else if (T.isUnboundWildcard()) { - return ConstraintSet.TRUE; - } else if (T.isUpperBoundedWildcard()) { - AbstractType bound = T.getWildcardUpperBound(); - if (S.getTypeKind() == TypeKind.WILDCARD) { - if (S.isUnboundWildcard() || S.isUpperBoundedWildcard()) { - return new Typing(S.getWildcardUpperBound(), bound, Kind.SUBTYPE); - } else { - return new Typing(S.getWildcardLowerBound(), bound, Kind.TYPE_EQUALITY); + + @Override + public ReductionResult reduce(Java8InferenceContext context) { + + switch (getKind()) { + case TYPE_COMPATIBILITY: + return reduceCompatible(); + case SUBTYPE: + return reduceSubtyping(context); + case CONTAINED: + return reduceContained(); + case TYPE_EQUALITY: + return reduceEquality(); + default: + throw new BugInCF("Unexpected kind: " + getKind()); } - } else { - return new Typing(S, bound, Kind.SUBTYPE); - } - } else { // T is lower bounded wildcard - AbstractType tPrime = T.getWildcardLowerBound(); - if (S.getTypeKind() != TypeKind.WILDCARD) { - return new Typing(tPrime, S, Kind.SUBTYPE); - } else if (S.isLowerBoundedWildcard()) { - return new Typing(tPrime, S.getWildcardLowerBound(), Kind.SUBTYPE); - } else { - return ConstraintSet.FALSE; - } - } - } - - /** - * Returns the result of reducing this constraint, assume it is a type compatibility constraint. - * See JLS 18.2.2 - * - * @return the result of reducing the constraint - */ - private ReductionResult reduceCompatible() { - if (T.isProper() && S.isProper()) { - // the constraint reduces to true if S is compatible in a loose invocation context - // with T (5.3), and false otherwise. - ReductionResult r = ((ProperType) S).isSubTypeUnchecked((ProperType) T); - if (ConstraintSet.TRUE == r) { - return ConstraintSet.TRUE; - } - return ((ProperType) S).isAssignable((ProperType) T); - } else if (S.isProper() && S.getTypeKind().isPrimitive()) { - return new Typing(((ProperType) S).boxType(), T, Kind.TYPE_COMPATIBILITY); - } else if (T.isProper() && T.getTypeKind().isPrimitive()) { - return new Typing(S, ((ProperType) T).boxType(), Kind.TYPE_EQUALITY); - } else if (T.isParameterizedType() && !S.isUseOfVariable()) { - // Otherwise, if T is a parameterized type of the form G, - // and there exists no type of the form G<...> that is a supertype of S, - // but the raw type G is a supertype of S, then the constraint reduces to true. - AbstractType superS = S.asSuper(T.getJavaType()); - if (superS != null && superS.isRaw()) { - return ReductionResult.UNCHECKED_CONVERSION; - } - } else if (T.getTypeKind() == TypeKind.ARRAY && T.getComponentType().isParameterizedType()) { - AbstractType superS = S.asSuper(T.getJavaType()); - if (superS != null && superS.getComponentType().isRaw()) { - return ReductionResult.UNCHECKED_CONVERSION; - } } - return new Typing(S, T, Kind.SUBTYPE); - } - - /** - * Returns the result of reducing this constraint, assume it is an equality constraint. See JLS - * 18.2.4 - * - * @return the result of reducing the constraint - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - private ReductionResult reduceEquality() { - if (S.isProper()) { - if (T.isProper()) { - // If S and T are proper types, the constraint reduces to true if S is the same - // as T (4.3.4), and false otherwise. - return ConstraintSet.TRUE; - } - ProperType sProper = (ProperType) S; - if (sProper.getTypeKind() == TypeKind.NULL || sProper.getTypeKind().isPrimitive()) { - // if S or T is the null type, the constraint reduces to false. - return ConstraintSet.FALSE; - } - } else if (T.isProper()) { - ProperType tProper = (ProperType) T; - if (tProper.getTypeKind() == TypeKind.NULL || tProper.getTypeKind().isPrimitive()) { - // if S or T is the null type, the constraint reduces to false. - return ConstraintSet.FALSE; - } + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint. See + * JLS 18.2.3. + * + * @param context the context + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtyping(Java8InferenceContext context) { + if (S.isProper() && T.isProper()) { + ReductionResult isSubtype = ((ProperType) S).isSubType((ProperType) T); + if (isSubtype == ConstraintSet.TRUE) { + return ConstraintSet.TRUE; + } else if (((ProperType) S).isSubTypeUnchecked((ProperType) T) == ConstraintSet.TRUE) { + return ReductionResult.UNCHECKED_CONVERSION; + } + return isSubtype; + } else if (S.getTypeKind() == TypeKind.NULL) { + if (T.isUseOfVariable()) { + UseOfVariable tUseOf = (UseOfVariable) T; + tUseOf.addQualifierBound(BoundKind.LOWER, S.getQualifiers()); + } + return ConstraintSet.TRUE; + } else if (T.getTypeKind() == TypeKind.NULL) { + return ConstraintSet.FALSE; + } + + if (S.isUseOfVariable() || T.isUseOfVariable()) { + if (S.isUseOfVariable()) { + if (T.getTypeKind() == TypeKind.TYPEVAR && T.isLowerBoundTypeVariable()) { + ((UseOfVariable) S) + .addBound(VariableBounds.BoundKind.UPPER, T.getTypeVarLowerBound()); + } else { + ((UseOfVariable) S).addBound(VariableBounds.BoundKind.UPPER, T); + } + } + if (T.isUseOfVariable()) { + if (TypesUtils.isCapturedTypeVariable(S.getJavaType())) { + ((UseOfVariable) T) + .addBound(VariableBounds.BoundKind.LOWER, S.getTypeVarUpperBound()); + } + ((UseOfVariable) T).addBound(VariableBounds.BoundKind.LOWER, S); + } + return ConstraintSet.TRUE; + } + + switch (T.getTypeKind()) { + case DECLARED: + return reduceSubtypeClass(context); + case ARRAY: + return reduceSubtypeArray(); + case WILDCARD: + case TYPEVAR: + return reduceSubtypeTypeVariable(); + case INTERSECTION: + return reduceSubtypingIntersection(); + default: + return ConstraintSet.FALSE; + } } - if (S.isUseOfVariable() || T.isUseOfVariable()) { - if (S.isUseOfVariable()) { - ((UseOfVariable) S).addBound(VariableBounds.BoundKind.EQUAL, T); - } - if (T.isUseOfVariable()) { - ((UseOfVariable) T).addBound(VariableBounds.BoundKind.EQUAL, S); - } - return ConstraintSet.TRUE; + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is a class type. See JLS 18.2.3. + * + * @param context the context + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypeClass(Java8InferenceContext context) { + if (T.isParameterizedType()) { + // let A1, ..., An be the type arguments of T. Among the supertypes of S, a + // corresponding class or interface type is identified, with type arguments B1, ..., + // Bn. If no such type exists, the constraint reduces to false. Otherwise, the + // constraint reduces to the following new constraints: + // for all i (1 <= i <= n), . + + AbstractType sAsSuper = S.asSuper(T.getJavaType()); + if (sAsSuper == null) { + return ConstraintSet.FALSE; + } else if (sAsSuper.isRaw() || T.isRaw()) { + return ReductionResult.UNCHECKED_CONVERSION; + } + + List Bs = sAsSuper.getTypeArguments(); + Iterator As = T.getTypeArguments().iterator(); + List covariantArgIndexes = + context.typeFactory + .getTypeHierarchy() + .getCovariantArgIndexes((AnnotatedDeclaredType) T.getAnnotatedType()); + ConstraintSet set = new ConstraintSet(); + int index = 0; + for (AbstractType b : Bs) { + AbstractType a = As.next(); + boolean convarArg = covariantArgIndexes.contains(index); + set.add(new Typing(b, a, Kind.CONTAINED, convarArg)); + index++; + } + + return set; + } else { + // The constraint reduces to true if T is among the supertypes of S, and false + // otherwise. + return ((InferenceType) S).isSubType((ProperType) T); + } } - List sTypeArgs = S.getTypeArguments(); - List tTypeArgs = T.getTypeArguments(); - if (sTypeArgs != null && tTypeArgs != null && sTypeArgs.size() == tTypeArgs.size()) { - // Assume if both have type arguments, then S and T are class or interface types with - // the same erasure - ConstraintSet constraintSet = new ConstraintSet(); - for (int i = 0; i < tTypeArgs.size(); i++) { - if (tTypeArgs.get(i) != sTypeArgs.get(i)) { - constraintSet.add(new Typing(tTypeArgs.get(i), sTypeArgs.get(i), Kind.TYPE_EQUALITY)); + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is an array type. See JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypeArray() { + AbstractType msArrayType = S.getMostSpecificArrayType(); + if (msArrayType == null) { + return ConstraintSet.FALSE; + } + if (msArrayType.isPrimitiveArray() && T.isPrimitiveArray()) { + return ConstraintSet.TRUE; + } else { + return new Typing(msArrayType.getComponentType(), T.getComponentType(), Kind.SUBTYPE); } - } - return constraintSet; } - AbstractType sComponentType = S.getComponentType(); - AbstractType tComponentType = T.getComponentType(); - if (sComponentType != null && tComponentType != null) { - return new Typing(sComponentType, tComponentType, Kind.TYPE_EQUALITY); + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is a type variable. See JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypeTypeVariable() { + if (S.getTypeKind() == TypeKind.INTERSECTION) { + return ConstraintSet.TRUE; + } else if (T.getTypeKind() == TypeKind.TYPEVAR && T.isLowerBoundTypeVariable()) { + return new Typing(S, T.getTypeVarLowerBound(), Kind.SUBTYPE); + } else if (T.getTypeKind() == TypeKind.WILDCARD && T.isLowerBoundedWildcard()) { + return new Typing(S, T.getWildcardLowerBound(), Kind.SUBTYPE); + } else { + return ConstraintSet.FALSE; + } } - if (T.getTypeKind() == TypeKind.WILDCARD && S.getTypeKind() == TypeKind.WILDCARD) { - if (T.isUnboundWildcard() && S.isUnboundWildcard()) { - return ConstraintSet.TRUE; - } else if (!S.isLowerBoundedWildcard() && !T.isLowerBoundedWildcard()) { - return new Typing(S.getWildcardUpperBound(), T.getWildcardUpperBound(), Kind.TYPE_EQUALITY); - } else if (T.isLowerBoundedWildcard() && S.isLowerBoundedWildcard()) { - return new Typing(T.getWildcardLowerBound(), S.getWildcardLowerBound(), Kind.TYPE_EQUALITY); - } + /** + * Returns the result of reducing this constraint, assuming it is a subtyping constraint where + * {@code T} is an intersection type. See JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceSubtypingIntersection() { + ConstraintSet constraintSet = new ConstraintSet(); + for (AbstractType bound : T.getIntersectionBounds()) { + constraintSet.add(new Typing(S, bound, Kind.SUBTYPE)); + } + return constraintSet; } - return ConstraintSet.FALSE; - } - - @Override - public String toString() { - switch (kind) { - case TYPE_COMPATIBILITY: - return S + " -> " + T; - case SUBTYPE: - return S + " <: " + T; - case CONTAINED: - return S + " <= " + T; - case TYPE_EQUALITY: - return S + " = " + T; - default: - assert false; - return super.toString(); + + /** + * Returns the result of reducing this constraint, assuming it is a containment constraint. See + * JLS 18.2.3. + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceContained() { + if (T.getTypeKind() != TypeKind.WILDCARD) { + if (S.getTypeKind() != TypeKind.WILDCARD) { + if (isCovarTypeArg) { + return new Typing(S, T, Kind.SUBTYPE); + } + return new Typing(S, T, Kind.TYPE_EQUALITY); + } else { + return ConstraintSet.FALSE; + } + } else if (T.isUnboundWildcard()) { + return ConstraintSet.TRUE; + } else if (T.isUpperBoundedWildcard()) { + AbstractType bound = T.getWildcardUpperBound(); + if (S.getTypeKind() == TypeKind.WILDCARD) { + if (S.isUnboundWildcard() || S.isUpperBoundedWildcard()) { + return new Typing(S.getWildcardUpperBound(), bound, Kind.SUBTYPE); + } else { + return new Typing(S.getWildcardLowerBound(), bound, Kind.TYPE_EQUALITY); + } + } else { + return new Typing(S, bound, Kind.SUBTYPE); + } + } else { // T is lower bounded wildcard + AbstractType tPrime = T.getWildcardLowerBound(); + if (S.getTypeKind() != TypeKind.WILDCARD) { + return new Typing(tPrime, S, Kind.SUBTYPE); + } else if (S.isLowerBoundedWildcard()) { + return new Typing(tPrime, S.getWildcardLowerBound(), Kind.SUBTYPE); + } else { + return ConstraintSet.FALSE; + } + } } - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + /** + * Returns the result of reducing this constraint, assume it is a type compatibility constraint. + * See JLS 18.2.2 + * + * @return the result of reducing the constraint + */ + private ReductionResult reduceCompatible() { + if (T.isProper() && S.isProper()) { + // the constraint reduces to true if S is compatible in a loose invocation context + // with T (5.3), and false otherwise. + ReductionResult r = ((ProperType) S).isSubTypeUnchecked((ProperType) T); + if (ConstraintSet.TRUE == r) { + return ConstraintSet.TRUE; + } + return ((ProperType) S).isAssignable((ProperType) T); + } else if (S.isProper() && S.getTypeKind().isPrimitive()) { + return new Typing(((ProperType) S).boxType(), T, Kind.TYPE_COMPATIBILITY); + } else if (T.isProper() && T.getTypeKind().isPrimitive()) { + return new Typing(S, ((ProperType) T).boxType(), Kind.TYPE_EQUALITY); + } else if (T.isParameterizedType() && !S.isUseOfVariable()) { + // Otherwise, if T is a parameterized type of the form G, + // and there exists no type of the form G<...> that is a supertype of S, + // but the raw type G is a supertype of S, then the constraint reduces to true. + AbstractType superS = S.asSuper(T.getJavaType()); + if (superS != null && superS.isRaw()) { + return ReductionResult.UNCHECKED_CONVERSION; + } + } else if (T.getTypeKind() == TypeKind.ARRAY + && T.getComponentType().isParameterizedType()) { + AbstractType superS = S.asSuper(T.getJavaType()); + if (superS != null && superS.getComponentType().isRaw()) { + return ReductionResult.UNCHECKED_CONVERSION; + } + } + + return new Typing(S, T, Kind.SUBTYPE); } - if (o == null || getClass() != o.getClass()) { - return false; + + /** + * Returns the result of reducing this constraint, assume it is an equality constraint. See JLS + * 18.2.4 + * + * @return the result of reducing the constraint + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private ReductionResult reduceEquality() { + if (S.isProper()) { + if (T.isProper()) { + // If S and T are proper types, the constraint reduces to true if S is the same + // as T (4.3.4), and false otherwise. + return ConstraintSet.TRUE; + } + ProperType sProper = (ProperType) S; + if (sProper.getTypeKind() == TypeKind.NULL || sProper.getTypeKind().isPrimitive()) { + // if S or T is the null type, the constraint reduces to false. + return ConstraintSet.FALSE; + } + } else if (T.isProper()) { + ProperType tProper = (ProperType) T; + if (tProper.getTypeKind() == TypeKind.NULL || tProper.getTypeKind().isPrimitive()) { + // if S or T is the null type, the constraint reduces to false. + return ConstraintSet.FALSE; + } + } + + if (S.isUseOfVariable() || T.isUseOfVariable()) { + if (S.isUseOfVariable()) { + ((UseOfVariable) S).addBound(VariableBounds.BoundKind.EQUAL, T); + } + if (T.isUseOfVariable()) { + ((UseOfVariable) T).addBound(VariableBounds.BoundKind.EQUAL, S); + } + return ConstraintSet.TRUE; + } + + List sTypeArgs = S.getTypeArguments(); + List tTypeArgs = T.getTypeArguments(); + if (sTypeArgs != null && tTypeArgs != null && sTypeArgs.size() == tTypeArgs.size()) { + // Assume if both have type arguments, then S and T are class or interface types with + // the same erasure + ConstraintSet constraintSet = new ConstraintSet(); + for (int i = 0; i < tTypeArgs.size(); i++) { + if (tTypeArgs.get(i) != sTypeArgs.get(i)) { + constraintSet.add( + new Typing(tTypeArgs.get(i), sTypeArgs.get(i), Kind.TYPE_EQUALITY)); + } + } + return constraintSet; + } + + AbstractType sComponentType = S.getComponentType(); + AbstractType tComponentType = T.getComponentType(); + if (sComponentType != null && tComponentType != null) { + return new Typing(sComponentType, tComponentType, Kind.TYPE_EQUALITY); + } + + if (T.getTypeKind() == TypeKind.WILDCARD && S.getTypeKind() == TypeKind.WILDCARD) { + if (T.isUnboundWildcard() && S.isUnboundWildcard()) { + return ConstraintSet.TRUE; + } else if (!S.isLowerBoundedWildcard() && !T.isLowerBoundedWildcard()) { + return new Typing( + S.getWildcardUpperBound(), T.getWildcardUpperBound(), Kind.TYPE_EQUALITY); + } else if (T.isLowerBoundedWildcard() && S.isLowerBoundedWildcard()) { + return new Typing( + T.getWildcardLowerBound(), S.getWildcardLowerBound(), Kind.TYPE_EQUALITY); + } + } + return ConstraintSet.FALSE; } - if (!super.equals(o)) { - return false; + + @Override + public String toString() { + switch (kind) { + case TYPE_COMPATIBILITY: + return S + " -> " + T; + case SUBTYPE: + return S + " <: " + T; + case CONTAINED: + return S + " <= " + T; + case TYPE_EQUALITY: + return S + " = " + T; + default: + assert false; + return super.toString(); + } } - Typing typing = (Typing) o; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + Typing typing = (Typing) o; - return S.equals(typing.S) && kind == typing.kind; - } + return S.equals(typing.S) && kind == typing.kind; + } - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + S.hashCode(); - result = 31 * result + kind.hashCode(); - return result; - } + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + S.hashCode(); + result = 31 * result + kind.hashCode(); + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java index be405b73202..f226bb7077f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractQualifier.java @@ -1,16 +1,18 @@ package org.checkerframework.framework.util.typeinference8.types; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.function.BinaryOperator; + import javax.lang.model.element.AnnotationMirror; -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.AnnotationMirrorSet; -import org.checkerframework.javacutil.AnnotationUtils; /** * This is the super class for a qualifier, {@link Qualifier} or a qualifier variable, {@link @@ -20,138 +22,139 @@ */ public abstract class AbstractQualifier { - /** The (interned) name of the top qualifier in the same hierarchy as the qualifier. */ - protected final @Interned String hierarchyName; - - /** The context. */ - protected final Java8InferenceContext context; - - /** - * Creates an {@code AbstractQualifier}. - * - * @param anno an annotation mirror - * @param context the context - */ - AbstractQualifier(AnnotationMirror anno, Java8InferenceContext context) { - AnnotationMirror top = context.typeFactory.getQualifierHierarchy().getTopAnnotation(anno); - hierarchyName = AnnotationUtils.annotationNameInterned(top); - this.context = context; - } - - /** - * Returns whether {@code other} is in the same hierarchy as this. - * - * @param other another abstract qualifier - * @return whether {@code other} is in the same hierarchy as this. - */ - public boolean sameHierarchy(AbstractQualifier other) { - return this.hierarchyName == other.hierarchyName; - } - - /** - * Returns the instantiation of this. - * - * @return the instantiation of this. - */ - abstract AnnotationMirror getInstantiation(); - - /** - * Returns the least upper bounds of {@code quals}. - * - * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies and - * multiple qualifiers for a hierarchy - * @param context a context - * @return the least upper bounds of {@code quals} - */ - public static Set lub( - Set quals, Java8InferenceContext context) { - return combine( - quals, context.typeFactory.getQualifierHierarchy()::leastUpperBoundQualifiersOnly); - } - - /** - * Returns the greatest lower bounds of {@code quals}. - * - * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies and - * multiple qualifiers for a hierarchy - * @param context a context - * @return the greatest lowest bounds of {@code quals} - */ - public static Set glb( - Set quals, Java8InferenceContext context) { - return combine( - quals, context.typeFactory.getQualifierHierarchy()::greatestLowerBoundQualifiersOnly); - } - - /** - * Returns the result of applying the {@code combine} function on {@code quals}. - * - * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies and - * multiple qualifiers for a hierarchy - * @param combine a functions that combines two {@code AnnotationMirror}s and returns a single - * {@code AnnotationMirror} - * @return the result of applying the {@code combine} function on {@code quals} - */ - private static Set combine( - Set quals, BinaryOperator combine) { - Map m = new HashMap<>(); - - for (AbstractQualifier qual : quals) { - AnnotationMirror lub = m.get(qual.hierarchyName); - if (lub != null) { - lub = combine.apply(lub, qual.getInstantiation()); - } else { - lub = qual.getInstantiation(); - } - m.put(qual.hierarchyName, lub); + /** The (interned) name of the top qualifier in the same hierarchy as the qualifier. */ + protected final @Interned String hierarchyName; + + /** The context. */ + protected final Java8InferenceContext context; + + /** + * Creates an {@code AbstractQualifier}. + * + * @param anno an annotation mirror + * @param context the context + */ + AbstractQualifier(AnnotationMirror anno, Java8InferenceContext context) { + AnnotationMirror top = context.typeFactory.getQualifierHierarchy().getTopAnnotation(anno); + hierarchyName = AnnotationUtils.annotationNameInterned(top); + this.context = context; + } + + /** + * Returns whether {@code other} is in the same hierarchy as this. + * + * @param other another abstract qualifier + * @return whether {@code other} is in the same hierarchy as this. + */ + public boolean sameHierarchy(AbstractQualifier other) { + return this.hierarchyName == other.hierarchyName; + } + + /** + * Returns the instantiation of this. + * + * @return the instantiation of this. + */ + abstract AnnotationMirror getInstantiation(); + + /** + * Returns the least upper bounds of {@code quals}. + * + * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies + * and multiple qualifiers for a hierarchy + * @param context a context + * @return the least upper bounds of {@code quals} + */ + public static Set lub( + Set quals, Java8InferenceContext context) { + return combine( + quals, context.typeFactory.getQualifierHierarchy()::leastUpperBoundQualifiersOnly); } - return new AnnotationMirrorSet(m.values()); - } - - /** - * Creates an {@code AbstractQualifier} for each {@code AnnotationMirror} in {@code annos}. If an - * annotation mirror is a polymorphic qualifier in {@code qualifierVars}, the {@code QualifierVar} - * it maps to in {@code qualifierVars} is added to the returned set. Otherwise, a new {@code - * Qualifier} is added. - * - * @param annos a set of annotation mirrors - * @param qualifierVars a map from polymorphic qualifiers to {@link QualifierVar} - * @param context a context - * @return a set containing an {@code AbstractQualifier} for each annotation in {@code - * qualifierVars} - */ - public static Set create( - Set annos, - AnnotationMirrorMap qualifierVars, - Java8InferenceContext context) { - if (qualifierVars.isEmpty()) { - return create(annos, context); + + /** + * Returns the greatest lower bounds of {@code quals}. + * + * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies + * and multiple qualifiers for a hierarchy + * @param context a context + * @return the greatest lowest bounds of {@code quals} + */ + public static Set glb( + Set quals, Java8InferenceContext context) { + return combine( + quals, + context.typeFactory.getQualifierHierarchy()::greatestLowerBoundQualifiersOnly); + } + + /** + * Returns the result of applying the {@code combine} function on {@code quals}. + * + * @param quals a set of qualifiers; can contain multiple qualifiers for multiple hierarchies + * and multiple qualifiers for a hierarchy + * @param combine a functions that combines two {@code AnnotationMirror}s and returns a single + * {@code AnnotationMirror} + * @return the result of applying the {@code combine} function on {@code quals} + */ + private static Set combine( + Set quals, BinaryOperator combine) { + Map m = new HashMap<>(); + + for (AbstractQualifier qual : quals) { + AnnotationMirror lub = m.get(qual.hierarchyName); + if (lub != null) { + lub = combine.apply(lub, qual.getInstantiation()); + } else { + lub = qual.getInstantiation(); + } + m.put(qual.hierarchyName, lub); + } + return new AnnotationMirrorSet(m.values()); } - Set quals = new HashSet<>(); - for (AnnotationMirror anno : annos) { - if (qualifierVars.containsKey(anno)) { - quals.add(qualifierVars.get(anno)); - } else { - quals.add(new Qualifier(anno, context)); - } + /** + * Creates an {@code AbstractQualifier} for each {@code AnnotationMirror} in {@code annos}. If + * an annotation mirror is a polymorphic qualifier in {@code qualifierVars}, the {@code + * QualifierVar} it maps to in {@code qualifierVars} is added to the returned set. Otherwise, a + * new {@code Qualifier} is added. + * + * @param annos a set of annotation mirrors + * @param qualifierVars a map from polymorphic qualifiers to {@link QualifierVar} + * @param context a context + * @return a set containing an {@code AbstractQualifier} for each annotation in {@code + * qualifierVars} + */ + public static Set create( + Set annos, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + if (qualifierVars.isEmpty()) { + return create(annos, context); + } + + Set quals = new HashSet<>(); + for (AnnotationMirror anno : annos) { + if (qualifierVars.containsKey(anno)) { + quals.add(qualifierVars.get(anno)); + } else { + quals.add(new Qualifier(anno, context)); + } + } + return quals; } - return quals; - } - - /** - * Creates new {@code Qualifier} is added for each annotation in {@code annos}. - * - * @param annos a set of annotation mirrors - * @param context a context - * @return new {@code Qualifier} is added for each annotation in {@code annos} - */ - private static Set create( - Set annos, Java8InferenceContext context) { - Set quals = new HashSet<>(); - for (AnnotationMirror anno : annos) { - quals.add(new Qualifier(anno, context)); + + /** + * Creates new {@code Qualifier} is added for each annotation in {@code annos}. + * + * @param annos a set of annotation mirrors + * @param context a context + * @return new {@code Qualifier} is added for each annotation in {@code annos} + */ + private static Set create( + Set annos, Java8InferenceContext context) { + Set quals = new HashSet<>(); + for (AnnotationMirror anno : annos) { + quals.add(new Qualifier(anno, context)); + } + return quals; } - return quals; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java index a077e55b535..5242410e811 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java @@ -2,6 +2,21 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.WildcardType; + +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.framework.type.AnnotatedTypeParameterBounds; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.IPair; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -10,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; @@ -21,19 +37,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.AnnotatedTypeParameterBounds; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.IPair; /** * As explained in getInferenceVariables(); - - /** - * Return a new type that is the same as this one except the variables in {@code instantiations} - * have been replaced by their instantiation. - * - * @return a new type that is the same as this one except the variables in {@code instantiations} - * have been replaced by their instantiation - */ - public abstract AbstractType applyInstantiations(); - - /** - * Return true if this type is java.lang.Object. - * - * @return true if this type is java.lang.Object - */ - public abstract boolean isObject(); - - /** - * Assuming the type is a declared type, this method returns the upper bounds of its type - * parameters. (A type parameter of a declared type, can't refer to any type being inferred, so - * they are proper types.) - * - * @return the upper bounds of the type parameter of this type - */ - public List getTypeParameterBounds() { - TypeElement typeelem = (TypeElement) ((DeclaredType) getJavaType()).asElement(); - List bounds = new ArrayList<>(); - List typeVars = - typeFactory.typeVariablesFromUse((AnnotatedDeclaredType) getAnnotatedType(), typeelem); - Iterator javaEle = typeelem.getTypeParameters().iterator(); - - for (AnnotatedTypeParameterBounds bound : typeVars) { - TypeVariable typeVariable = (TypeVariable) javaEle.next().asType(); - bounds.add(new ProperType(bound.getUpperBound(), typeVariable.getUpperBound(), context)); - } - return bounds; - } - - /** - * Return a new type that is the capture of this type. - * - * @param context the context object - * @return a new type that is the capture of this type - */ - public AbstractType capture(Java8InferenceContext context) { - AnnotatedTypeMirror capturedType = - context.typeFactory.applyCaptureConversion(getAnnotatedType()); - return create(capturedType, capturedType.getUnderlyingType()); - } - - /** - * If {@code superType} is a super type of this type, then this method returns the super type of - * this type that is the same class as {@code superType}. Otherwise, it returns null - * - * @param superType a type, need not be a super type of this type - * @return super type of this type that is the same class as {@code superType} or null if one - * doesn't exist - */ - public AbstractType asSuper(TypeMirror superType) { - TypeMirror typeJava = getJavaType(); - if (typeJava.getKind() == TypeKind.WILDCARD) { - typeJava = ((WildcardType) typeJava).getExtendsBound(); - } - TypeMirror asSuperJava = context.types.asSuper((Type) typeJava, ((Type) superType).asElement()); - if (asSuperJava == null) { - return null; - } - - AnnotatedTypeMirror type = getAnnotatedType(); - - if (type.getKind() == TypeKind.WILDCARD) { - type = ((AnnotatedWildcardType) type).getExtendsBound(); - } - - AnnotatedTypeMirror superAnnotatedType = - AnnotatedTypeMirror.createType(superType, typeFactory, type.isDeclaration()); - typeFactory.initializeAtm(superAnnotatedType); - AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(typeFactory, type, superAnnotatedType); - return create(asSuper, asSuper.getUnderlyingType()); - } - - /** - * If this {@link AbstractType} is a functional interface type, then {@code functionType} is its - * function type. Otherwise, {@code functionType} is null. Initialized by {@link - * #getFunctionType()}. - */ - private IPair functionType = null; - - /** - * If this {@link AbstractType} is a functional interface type, then its function type is - * returned. Otherwise, returns null. - * - * @return this {@link AbstractType} is a functional interface type, then its function type is - * returned; otherwise, returns null - */ - IPair getFunctionType() { - if (functionType == null) { - ExecutableElement element = TypesUtils.findFunction(getJavaType(), context.env); - AnnotatedDeclaredType groundType = - makeGround((AnnotatedDeclaredType) getAnnotatedType(), typeFactory); - AnnotatedExecutableType aet = - AnnotatedTypes.asMemberOf(context.modelTypes, typeFactory, groundType, element); - functionType = IPair.of(aet, aet.getUnderlyingType()); - } - return functionType; - } - - /** - * If this type is a functional interface, then this method returns the return type of the - * function type of that functional interface. Otherwise, returns null. - * - * @return the return type of the function type of this type or null if one doesn't exist - */ - public AbstractType getFunctionTypeReturnType() { - if (TypesUtils.isFunctionalInterface(getJavaType(), context.env)) { - IPair pair = getFunctionType(); - ExecutableType elementType = pair.second; - TypeMirror returnTypeJava = elementType.getReturnType(); - if (returnTypeJava.getKind() == TypeKind.VOID) { - return null; - } + /** The kind of {@link AbstractType}. */ + public enum Kind { + /** {@link ProperType},a type that contains no inference variables* */ + PROPER, + /** {@link UseOfVariable}, a use of an inference variable. */ + USE_OF_VARIABLE, + /** + * {@link InferenceType}, a type that contains inference variables, but is not an inference + * variable. + */ + INFERENCE_TYPE + } + + /** The context object. */ + protected final Java8InferenceContext context; + + /** The {@link AnnotatedTypeFactory}. */ + protected final AnnotatedTypeFactory typeFactory; + + /** + * Creates an {@link AbstractType}. + * + * @param context the context object + */ + protected AbstractType(Java8InferenceContext context) { + this.context = context; + this.typeFactory = context.typeFactory; + } + + /** + * Returns the kind of {@link AbstractType}. + * + * @return the kind of {@link AbstractType} + */ + public abstract Kind getKind(); + + /** + * Return true if this type is a proper type. + * + * @return true if this type is a proper type + */ + public boolean isProper() { + return getKind() == Kind.PROPER; + } + + /** + * Return true if this type is a use of an inference variable. + * + * @return true if this type is a use of an inference variable + */ + public boolean isUseOfVariable() { + return getKind() == Kind.USE_OF_VARIABLE; + } + + /** + * Return true if this type contains inference variables, but is not an inference variable. + * + * @return true if this type contains inference variables, but is not an inference variable + */ + public boolean isInferenceType() { + return getKind() == Kind.INFERENCE_TYPE; + } + + /** + * Returns the TypeKind of the underlying Java type. + * + * @return the TypeKind of the underlying Java type + */ + public final TypeKind getTypeKind() { + return getJavaType().getKind(); + } + + /** + * Creates a type using the given types. + * + * @param atm annotated type mirror + * @param type type mirror + * @return the new type + */ + public abstract AbstractType create(AnnotatedTypeMirror atm, TypeMirror type); + + /** + * Return the underlying Java type without inference variables. + * + * @return the underlying Java type without inference variables + */ + public abstract TypeMirror getJavaType(); + + /** + * Return the underlying Java type without inference variables. + * + * @return the underlying Java type without inference variables + */ + public abstract AnnotatedTypeMirror getAnnotatedType(); + + /** + * Return a collection of all inference variables referenced by this type. + * + * @return a collection of all inference variables referenced by this type + */ + public abstract Collection getInferenceVariables(); + + /** + * Return a new type that is the same as this one except the variables in {@code instantiations} + * have been replaced by their instantiation. + * + * @return a new type that is the same as this one except the variables in {@code + * instantiations} have been replaced by their instantiation + */ + public abstract AbstractType applyInstantiations(); + + /** + * Return true if this type is java.lang.Object. + * + * @return true if this type is java.lang.Object + */ + public abstract boolean isObject(); + + /** + * Assuming the type is a declared type, this method returns the upper bounds of its type + * parameters. (A type parameter of a declared type, can't refer to any type being inferred, so + * they are proper types.) + * + * @return the upper bounds of the type parameter of this type + */ + public List getTypeParameterBounds() { + TypeElement typeelem = (TypeElement) ((DeclaredType) getJavaType()).asElement(); + List bounds = new ArrayList<>(); + List typeVars = + typeFactory.typeVariablesFromUse( + (AnnotatedDeclaredType) getAnnotatedType(), typeelem); + Iterator javaEle = typeelem.getTypeParameters().iterator(); + + for (AnnotatedTypeParameterBounds bound : typeVars) { + TypeVariable typeVariable = (TypeVariable) javaEle.next().asType(); + bounds.add( + new ProperType(bound.getUpperBound(), typeVariable.getUpperBound(), context)); + } + return bounds; + } + + /** + * Return a new type that is the capture of this type. + * + * @param context the context object + * @return a new type that is the capture of this type + */ + public AbstractType capture(Java8InferenceContext context) { + AnnotatedTypeMirror capturedType = + context.typeFactory.applyCaptureConversion(getAnnotatedType()); + return create(capturedType, capturedType.getUnderlyingType()); + } + + /** + * If {@code superType} is a super type of this type, then this method returns the super type of + * this type that is the same class as {@code superType}. Otherwise, it returns null + * + * @param superType a type, need not be a super type of this type + * @return super type of this type that is the same class as {@code superType} or null if one + * doesn't exist + */ + public AbstractType asSuper(TypeMirror superType) { + TypeMirror typeJava = getJavaType(); + if (typeJava.getKind() == TypeKind.WILDCARD) { + typeJava = ((WildcardType) typeJava).getExtendsBound(); + } + TypeMirror asSuperJava = + context.types.asSuper((Type) typeJava, ((Type) superType).asElement()); + if (asSuperJava == null) { + return null; + } + + AnnotatedTypeMirror type = getAnnotatedType(); + + if (type.getKind() == TypeKind.WILDCARD) { + type = ((AnnotatedWildcardType) type).getExtendsBound(); + } + + AnnotatedTypeMirror superAnnotatedType = + AnnotatedTypeMirror.createType(superType, typeFactory, type.isDeclaration()); + typeFactory.initializeAtm(superAnnotatedType); + AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(typeFactory, type, superAnnotatedType); + return create(asSuper, asSuper.getUnderlyingType()); + } + + /** + * If this {@link AbstractType} is a functional interface type, then {@code functionType} is its + * function type. Otherwise, {@code functionType} is null. Initialized by {@link + * #getFunctionType()}. + */ + private IPair functionType = null; + + /** + * If this {@link AbstractType} is a functional interface type, then its function type is + * returned. Otherwise, returns null. + * + * @return this {@link AbstractType} is a functional interface type, then its function type is + * returned; otherwise, returns null + */ + IPair getFunctionType() { + if (functionType == null) { + ExecutableElement element = TypesUtils.findFunction(getJavaType(), context.env); + AnnotatedDeclaredType groundType = + makeGround((AnnotatedDeclaredType) getAnnotatedType(), typeFactory); + AnnotatedExecutableType aet = + AnnotatedTypes.asMemberOf(context.modelTypes, typeFactory, groundType, element); + functionType = IPair.of(aet, aet.getUnderlyingType()); + } + return functionType; + } + + /** + * If this type is a functional interface, then this method returns the return type of the + * function type of that functional interface. Otherwise, returns null. + * + * @return the return type of the function type of this type or null if one doesn't exist + */ + public AbstractType getFunctionTypeReturnType() { + if (TypesUtils.isFunctionalInterface(getJavaType(), context.env)) { + IPair pair = getFunctionType(); + ExecutableType elementType = pair.second; + TypeMirror returnTypeJava = elementType.getReturnType(); + if (returnTypeJava.getKind() == TypeKind.VOID) { + return null; + } + + AnnotatedExecutableType aet = pair.first; + AnnotatedTypeMirror returnType = aet.getReturnType(); + if (returnType.getKind() == TypeKind.VOID) { + return null; + } + return create(returnType, returnTypeJava); + } else { + return null; + } + } + + /** + * If this type is a functional interface, then this method returns the parameter types of the + * function type of that functional interface. Otherwise, it returns null. + * + * @return the parameter types of the function type of this type or null if no function type + * exists + */ + public List getFunctionTypeParameterTypes() { + if (TypesUtils.isFunctionalInterface(getJavaType(), context.env)) { + IPair pair = getFunctionType(); + List paramsTypeMirror = pair.second.getParameterTypes(); + List params = new ArrayList<>(); + Iterator iter = paramsTypeMirror.iterator(); + for (AnnotatedTypeMirror param : pair.first.getParameterTypes()) { + params.add(create(param, iter.next())); + } + return params; + } else { + return null; + } + } + + /** + * Make {@code type} ground, which is basically changing any wildcards to their bounds. JLS section + * 15.27.3 + * + * @param type a type to ground + * @param typeFactory type factory + * @return the ground type + */ + // TODO: This method is named make ground, but is actually implements non-wildcard + // parameterization as defined in + // https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.9 + // https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.13.2 + static AnnotatedDeclaredType makeGround( + AnnotatedDeclaredType type, AnnotatedTypeFactory typeFactory) { + Element e = type.getUnderlyingType().asElement(); + AnnotatedDeclaredType decl = typeFactory.getAnnotatedType((TypeElement) e); + Iterator bounds = decl.getTypeArguments().iterator(); + + // typeFactory.getTypeVarSubstitutor().substitute() + Map typeVarToTypeArg = new HashMap<>(); + for (AnnotatedTypeMirror pn : type.getTypeArguments()) { + AnnotatedTypeVariable typeVariable = (AnnotatedTypeVariable) bounds.next(); + if (pn.getKind() != TypeKind.WILDCARD) { + typeVarToTypeArg.put(typeVariable.getUnderlyingType(), pn); + continue; + } + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) pn; + if (wildcardType.getSuperBound().getKind() == TypeKind.NULL) { + // › If Ai is a upper-bounded wildcard ? extends Ui, then Ti = glb(Ui, Bi) + typeVarToTypeArg.put( + typeVariable.getUnderlyingType(), + AnnotatedTypes.annotatedGLB( + typeFactory, + typeVariable.getUpperBound(), + wildcardType.getExtendsBound())); + } else { + typeVarToTypeArg.put( + typeVariable.getUnderlyingType(), wildcardType.getSuperBound()); + } + } + return (AnnotatedDeclaredType) + typeFactory.getTypeVarSubstitutor().substitute(typeVarToTypeArg, decl.asUse()); + } + + /** + * Return true if the type is a raw type. + * + * @return true if the type is a raw type + */ + public boolean isRaw() { + if (getAnnotatedType().getKind() == TypeKind.DECLARED) { + return ((AnnotatedDeclaredType) getAnnotatedType()).isUnderlyingTypeRaw(); + } + return false; + } + + /** + * Return a new type that is the same type as this one, but whose type arguments are {@code + * args}. + * + * @param args a list of type arguments + * @return a new type that is the same type as this one, but whose type arguments are {@code + * args} + */ + public AbstractType replaceTypeArgs(List args) { + DeclaredType declaredType = (DeclaredType) getJavaType(); + TypeMirror[] newArgs = new TypeMirror[args.size()]; + int i = 0; + for (AbstractType t : args) { + newArgs[i++] = t.getJavaType(); + } + TypeMirror newTypeJava = + context.env + .getTypeUtils() + .getDeclaredType((TypeElement) declaredType.asElement(), newArgs); + + AnnotatedDeclaredType newType = + (AnnotatedDeclaredType) + AnnotatedTypeMirror.createType( + newTypeJava, typeFactory, getAnnotatedType().isDeclaration()); + List argTypes = new ArrayList<>(); + for (AbstractType arg : args) { + argTypes.add(arg.getAnnotatedType()); + } + newType.setTypeArguments(argTypes); + newType.replaceAnnotations(getAnnotatedType().getAnnotations()); + return create(newType, newTypeJava); + } + + /** + * Whether the proper type is a parameterized class or interface type, or an inner class type of + * a parameterized class or interface type (directly or indirectly) + * + * @return whether T is a parameterized type + */ + public boolean isParameterizedType() { + // TODO this isn't matching the JavaDoc. + return ((Type) getJavaType()).isParameterized() || ((Type) getJavaType()).isRaw(); + } + + /** + * Return the most specific array type that is a super type of this type or null if one doesn't + * exist. + * + * @return the most specific array type that is a super type of this type or null if one doesn't + * exist + */ + public AbstractType getMostSpecificArrayType() { + if (getTypeKind() == TypeKind.ARRAY) { + return this; + } else if (TypesUtils.isObject(getJavaType())) { + return null; + } else { + AnnotatedTypeMirror msat = mostSpecificArrayType(getAnnotatedType()); + TypeMirror typeMirror = + TypesUtils.getMostSpecificArrayType(getJavaType(), context.modelTypes); + if (msat != null) { + return create(msat, typeMirror); + } + return null; + } + } + + /** + * Return the most specific array type, that is the first super type of {@code type} that is not + * an array. + * + * @param type annotated type mirror + * @return the first supertype of {@code type} that is an array + */ + private static AnnotatedTypeMirror mostSpecificArrayType(AnnotatedTypeMirror type) { + if (type.getKind() == TypeKind.ARRAY) { + return type; + } else if (TypesUtils.isObject(type.getUnderlyingType())) { + return null; + } else { + for (AnnotatedTypeMirror superType : type.directSupertypes()) { + AnnotatedTypeMirror arrayType = mostSpecificArrayType(superType); + if (arrayType != null) { + return arrayType; + } + } + return null; + } + } - AnnotatedExecutableType aet = pair.first; - AnnotatedTypeMirror returnType = aet.getReturnType(); - if (returnType.getKind() == TypeKind.VOID) { + /** + * Return true if this type is a primitive array. + * + * @return true if this type is a primitive array + */ + public boolean isPrimitiveArray() { + return getJavaType().getKind() == TypeKind.ARRAY + && ((ArrayType) getJavaType()).getComponentType().getKind().isPrimitive(); + } + + /** + * Return assuming type is an intersection type, this method returns the bounds in this type. + * + * @return assuming type is an intersection type, this method returns the bounds in this type + */ + public List getIntersectionBounds() { + List boundsJava = ((IntersectionType) getJavaType()).getBounds(); + Iterator iter = boundsJava.iterator(); + List bounds = new ArrayList<>(); + for (AnnotatedTypeMirror bound : + ((AnnotatedIntersectionType) getAnnotatedType()).directSupertypes()) { + bounds.add(create(bound, iter.next())); + } + return bounds; + } + + /** + * Return assuming this type is a type variable, this method returns the upper bound of this + * type. + * + * @return assuming this type is a type variable, this method returns the upper bound of this + * type + */ + public AbstractType getTypeVarUpperBound() { + TypeMirror javaUpperBound = ((TypeVariable) getJavaType()).getUpperBound(); + return create(((AnnotatedTypeVariable) getAnnotatedType()).getUpperBound(), javaUpperBound); + } + + /** + * Return assuming this type is a type variable that has a lower bound, this method returns the + * lower bound of this type. + * + * @return assuming this type is a type variable that has a lower bound, this method returns the + * lower bound of this type + */ + public AbstractType getTypeVarLowerBound() { + TypeMirror lowerBound = ((TypeVariable) getJavaType()).getLowerBound(); + return create(((AnnotatedTypeVariable) getAnnotatedType()).getLowerBound(), lowerBound); + } + + /** + * Return true if this type is a type variable with a lower bound. + * + * @return true if this type is a type variable with a lower bound + */ + public boolean isLowerBoundTypeVariable() { + return ((TypeVariable) getJavaType()).getLowerBound().getKind() != TypeKind.NULL; + } + + /** + * Return true if this type is a parameterized type whose has at least one wildcard as a type + * argument. + * + * @return true if this type is a parameterized type whose has at least one wildcard as a type + * argument + */ + public boolean isWildcardParameterizedType() { + return TypesUtils.isWildcardParameterized(getJavaType()); + } + + /** + * Return this type's type arguments or null if this type isn't a declared type. + * + * @return this type's type arguments or null this type isn't a declared type + */ + public List getTypeArguments() { + if (getJavaType().getKind() != TypeKind.DECLARED) { + return null; + } + if (((AnnotatedDeclaredType) getAnnotatedType()).isUnderlyingTypeRaw()) { + return Collections.emptyList(); + } + List javaTypeArgs = ((DeclaredType) getJavaType()).getTypeArguments(); + Iterator iter = javaTypeArgs.iterator(); + List list = new ArrayList<>(); + for (AnnotatedTypeMirror typeArg : + ((AnnotatedDeclaredType) getAnnotatedType()).getTypeArguments()) { + list.add(create(typeArg, iter.next())); + } + return list; + } + + /** + * Return true if the type is an unbound wildcard. + * + * @return true if the type is an unbound wildcard + */ + public boolean isUnboundWildcard() { + return TypesUtils.hasNoExplicitBound(getJavaType()); + } + + /** + * Return true if the type is a wildcard with an upper bound. + * + * @return true if the type is a wildcard with an upper bound + */ + public boolean isUpperBoundedWildcard() { + return TypesUtils.hasExplicitExtendsBound(getJavaType()); + } + + /** + * Return true if the type is a wildcard with a lower bound. + * + * @return true if the type is a wildcard with a lower bound + */ + public boolean isLowerBoundedWildcard() { + return TypesUtils.hasExplicitSuperBound(getJavaType()); + } + + /** + * Return if this type is a wildcard return its lower bound; otherwise, return null. + * + * @return if this type is a wildcard return its lower bound; otherwise, return null + */ + public AbstractType getWildcardLowerBound() { + if (getJavaType().getKind() == TypeKind.WILDCARD) { + WildcardType wild = (WildcardType) getJavaType(); + return create( + ((AnnotatedWildcardType) getAnnotatedType()).getSuperBound(), + wild.getSuperBound()); + } return null; - } - return create(returnType, returnTypeJava); - } else { - return null; - } - } - - /** - * If this type is a functional interface, then this method returns the parameter types of the - * function type of that functional interface. Otherwise, it returns null. - * - * @return the parameter types of the function type of this type or null if no function type - * exists - */ - public List getFunctionTypeParameterTypes() { - if (TypesUtils.isFunctionalInterface(getJavaType(), context.env)) { - IPair pair = getFunctionType(); - List paramsTypeMirror = pair.second.getParameterTypes(); - List params = new ArrayList<>(); - Iterator iter = paramsTypeMirror.iterator(); - for (AnnotatedTypeMirror param : pair.first.getParameterTypes()) { - params.add(create(param, iter.next())); - } - return params; - } else { - return null; - } - } - - /** - * Make {@code type} ground, which is basically changing any wildcards to their bounds. JLS section - * 15.27.3 - * - * @param type a type to ground - * @param typeFactory type factory - * @return the ground type - */ - // TODO: This method is named make ground, but is actually implements non-wildcard - // parameterization as defined in - // https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.9 - // https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.13.2 - static AnnotatedDeclaredType makeGround( - AnnotatedDeclaredType type, AnnotatedTypeFactory typeFactory) { - Element e = type.getUnderlyingType().asElement(); - AnnotatedDeclaredType decl = typeFactory.getAnnotatedType((TypeElement) e); - Iterator bounds = decl.getTypeArguments().iterator(); - - // typeFactory.getTypeVarSubstitutor().substitute() - Map typeVarToTypeArg = new HashMap<>(); - for (AnnotatedTypeMirror pn : type.getTypeArguments()) { - AnnotatedTypeVariable typeVariable = (AnnotatedTypeVariable) bounds.next(); - if (pn.getKind() != TypeKind.WILDCARD) { - typeVarToTypeArg.put(typeVariable.getUnderlyingType(), pn); - continue; - } - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) pn; - if (wildcardType.getSuperBound().getKind() == TypeKind.NULL) { - // › If Ai is a upper-bounded wildcard ? extends Ui, then Ti = glb(Ui, Bi) - typeVarToTypeArg.put( - typeVariable.getUnderlyingType(), - AnnotatedTypes.annotatedGLB( - typeFactory, typeVariable.getUpperBound(), wildcardType.getExtendsBound())); - } else { - typeVarToTypeArg.put(typeVariable.getUnderlyingType(), wildcardType.getSuperBound()); - } - } - return (AnnotatedDeclaredType) - typeFactory.getTypeVarSubstitutor().substitute(typeVarToTypeArg, decl.asUse()); - } - - /** - * Return true if the type is a raw type. - * - * @return true if the type is a raw type - */ - public boolean isRaw() { - if (getAnnotatedType().getKind() == TypeKind.DECLARED) { - return ((AnnotatedDeclaredType) getAnnotatedType()).isUnderlyingTypeRaw(); - } - return false; - } - - /** - * Return a new type that is the same type as this one, but whose type arguments are {@code args}. - * - * @param args a list of type arguments - * @return a new type that is the same type as this one, but whose type arguments are {@code args} - */ - public AbstractType replaceTypeArgs(List args) { - DeclaredType declaredType = (DeclaredType) getJavaType(); - TypeMirror[] newArgs = new TypeMirror[args.size()]; - int i = 0; - for (AbstractType t : args) { - newArgs[i++] = t.getJavaType(); - } - TypeMirror newTypeJava = - context.env.getTypeUtils().getDeclaredType((TypeElement) declaredType.asElement(), newArgs); - - AnnotatedDeclaredType newType = - (AnnotatedDeclaredType) - AnnotatedTypeMirror.createType( - newTypeJava, typeFactory, getAnnotatedType().isDeclaration()); - List argTypes = new ArrayList<>(); - for (AbstractType arg : args) { - argTypes.add(arg.getAnnotatedType()); - } - newType.setTypeArguments(argTypes); - newType.replaceAnnotations(getAnnotatedType().getAnnotations()); - return create(newType, newTypeJava); - } - - /** - * Whether the proper type is a parameterized class or interface type, or an inner class type of a - * parameterized class or interface type (directly or indirectly) - * - * @return whether T is a parameterized type - */ - public boolean isParameterizedType() { - // TODO this isn't matching the JavaDoc. - return ((Type) getJavaType()).isParameterized() || ((Type) getJavaType()).isRaw(); - } - - /** - * Return the most specific array type that is a super type of this type or null if one doesn't - * exist. - * - * @return the most specific array type that is a super type of this type or null if one doesn't - * exist - */ - public AbstractType getMostSpecificArrayType() { - if (getTypeKind() == TypeKind.ARRAY) { - return this; - } else if (TypesUtils.isObject(getJavaType())) { - return null; - } else { - AnnotatedTypeMirror msat = mostSpecificArrayType(getAnnotatedType()); - TypeMirror typeMirror = - TypesUtils.getMostSpecificArrayType(getJavaType(), context.modelTypes); - if (msat != null) { - return create(msat, typeMirror); - } - return null; - } - } - - /** - * Return the most specific array type, that is the first super type of {@code type} that is not - * an array. - * - * @param type annotated type mirror - * @return the first supertype of {@code type} that is an array - */ - private static AnnotatedTypeMirror mostSpecificArrayType(AnnotatedTypeMirror type) { - if (type.getKind() == TypeKind.ARRAY) { - return type; - } else if (TypesUtils.isObject(type.getUnderlyingType())) { - return null; - } else { - for (AnnotatedTypeMirror superType : type.directSupertypes()) { - AnnotatedTypeMirror arrayType = mostSpecificArrayType(superType); - if (arrayType != null) { - return arrayType; + } + + /** + * Return if this type is a wildcard return its upper bound; otherwise, return null. + * + * @return if this type is a wildcard return its upper bound; otherwise, return null + */ + public AbstractType getWildcardUpperBound() { + if (getJavaType().getKind() == TypeKind.WILDCARD) { + TypeMirror upperBoundJava = ((WildcardType) getJavaType()).getExtendsBound(); + if (upperBoundJava == null) { + upperBoundJava = context.object.getJavaType(); + } + return create( + ((AnnotatedWildcardType) getAnnotatedType()).getExtendsBound(), upperBoundJava); + } else { + return null; + } + } + + /** + * Return new type whose Java type is the erasure of this type. + * + * @return a new type whose Java type is the erasure of this type + */ + public AbstractType getErased() { + TypeMirror typeMirror = context.env.getTypeUtils().erasure(getJavaType()); + return create(getAnnotatedType().getErased(), typeMirror); + } + + /** + * Return the array component type fo this type or null if on does not exist. + * + * @return the array component type of this type or null if one does not exist + */ + public final AbstractType getComponentType() { + if (getJavaType().getKind() == TypeKind.ARRAY) { + TypeMirror javaType = ((ArrayType) getJavaType()).getComponentType(); + return create(((AnnotatedArrayType) getAnnotatedType()).getComponentType(), javaType); + } else { + return null; } - } - return null; - } - } - - /** - * Return true if this type is a primitive array. - * - * @return true if this type is a primitive array - */ - public boolean isPrimitiveArray() { - return getJavaType().getKind() == TypeKind.ARRAY - && ((ArrayType) getJavaType()).getComponentType().getKind().isPrimitive(); - } - - /** - * Return assuming type is an intersection type, this method returns the bounds in this type. - * - * @return assuming type is an intersection type, this method returns the bounds in this type - */ - public List getIntersectionBounds() { - List boundsJava = ((IntersectionType) getJavaType()).getBounds(); - Iterator iter = boundsJava.iterator(); - List bounds = new ArrayList<>(); - for (AnnotatedTypeMirror bound : - ((AnnotatedIntersectionType) getAnnotatedType()).directSupertypes()) { - bounds.add(create(bound, iter.next())); - } - return bounds; - } - - /** - * Return assuming this type is a type variable, this method returns the upper bound of this type. - * - * @return assuming this type is a type variable, this method returns the upper bound of this type - */ - public AbstractType getTypeVarUpperBound() { - TypeMirror javaUpperBound = ((TypeVariable) getJavaType()).getUpperBound(); - return create(((AnnotatedTypeVariable) getAnnotatedType()).getUpperBound(), javaUpperBound); - } - - /** - * Return assuming this type is a type variable that has a lower bound, this method returns the - * lower bound of this type. - * - * @return assuming this type is a type variable that has a lower bound, this method returns the - * lower bound of this type - */ - public AbstractType getTypeVarLowerBound() { - TypeMirror lowerBound = ((TypeVariable) getJavaType()).getLowerBound(); - return create(((AnnotatedTypeVariable) getAnnotatedType()).getLowerBound(), lowerBound); - } - - /** - * Return true if this type is a type variable with a lower bound. - * - * @return true if this type is a type variable with a lower bound - */ - public boolean isLowerBoundTypeVariable() { - return ((TypeVariable) getJavaType()).getLowerBound().getKind() != TypeKind.NULL; - } - - /** - * Return true if this type is a parameterized type whose has at least one wildcard as a type - * argument. - * - * @return true if this type is a parameterized type whose has at least one wildcard as a type - * argument - */ - public boolean isWildcardParameterizedType() { - return TypesUtils.isWildcardParameterized(getJavaType()); - } - - /** - * Return this type's type arguments or null if this type isn't a declared type. - * - * @return this type's type arguments or null this type isn't a declared type - */ - public List getTypeArguments() { - if (getJavaType().getKind() != TypeKind.DECLARED) { - return null; - } - if (((AnnotatedDeclaredType) getAnnotatedType()).isUnderlyingTypeRaw()) { - return Collections.emptyList(); - } - List javaTypeArgs = ((DeclaredType) getJavaType()).getTypeArguments(); - Iterator iter = javaTypeArgs.iterator(); - List list = new ArrayList<>(); - for (AnnotatedTypeMirror typeArg : - ((AnnotatedDeclaredType) getAnnotatedType()).getTypeArguments()) { - list.add(create(typeArg, iter.next())); - } - return list; - } - - /** - * Return true if the type is an unbound wildcard. - * - * @return true if the type is an unbound wildcard - */ - public boolean isUnboundWildcard() { - return TypesUtils.hasNoExplicitBound(getJavaType()); - } - - /** - * Return true if the type is a wildcard with an upper bound. - * - * @return true if the type is a wildcard with an upper bound - */ - public boolean isUpperBoundedWildcard() { - return TypesUtils.hasExplicitExtendsBound(getJavaType()); - } - - /** - * Return true if the type is a wildcard with a lower bound. - * - * @return true if the type is a wildcard with a lower bound - */ - public boolean isLowerBoundedWildcard() { - return TypesUtils.hasExplicitSuperBound(getJavaType()); - } - - /** - * Return if this type is a wildcard return its lower bound; otherwise, return null. - * - * @return if this type is a wildcard return its lower bound; otherwise, return null - */ - public AbstractType getWildcardLowerBound() { - if (getJavaType().getKind() == TypeKind.WILDCARD) { - WildcardType wild = (WildcardType) getJavaType(); - return create( - ((AnnotatedWildcardType) getAnnotatedType()).getSuperBound(), wild.getSuperBound()); - } - return null; - } - - /** - * Return if this type is a wildcard return its upper bound; otherwise, return null. - * - * @return if this type is a wildcard return its upper bound; otherwise, return null - */ - public AbstractType getWildcardUpperBound() { - if (getJavaType().getKind() == TypeKind.WILDCARD) { - TypeMirror upperBoundJava = ((WildcardType) getJavaType()).getExtendsBound(); - if (upperBoundJava == null) { - upperBoundJava = context.object.getJavaType(); - } - return create(((AnnotatedWildcardType) getAnnotatedType()).getExtendsBound(), upperBoundJava); - } else { - return null; - } - } - - /** - * Return new type whose Java type is the erasure of this type. - * - * @return a new type whose Java type is the erasure of this type - */ - public AbstractType getErased() { - TypeMirror typeMirror = context.env.getTypeUtils().erasure(getJavaType()); - return create(getAnnotatedType().getErased(), typeMirror); - } - - /** - * Return the array component type fo this type or null if on does not exist. - * - * @return the array component type of this type or null if one does not exist - */ - public final AbstractType getComponentType() { - if (getJavaType().getKind() == TypeKind.ARRAY) { - TypeMirror javaType = ((ArrayType) getJavaType()).getComponentType(); - return create(((AnnotatedArrayType) getAnnotatedType()).getComponentType(), javaType); - } else { - return null; - } - } - - /** - * Returns the primary qualifiers on this type. - * - * @return the primary qualifiers on this type - */ - public abstract Set getQualifiers(); - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - AbstractType that = (AbstractType) o; - - if (!context.equals(that.context)) { - return false; - } - return typeFactory.equals(that.typeFactory); - } - - @Override - public int hashCode() { - int result = context.hashCode(); - result = 31 * result + typeFactory.hashCode(); - return result; - } + } + + /** + * Returns the primary qualifiers on this type. + * + * @return the primary qualifiers on this type + */ + public abstract Set getQualifiers(); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AbstractType that = (AbstractType) o; + + if (!context.equals(that.context)) { + return false; + } + return typeFactory.equals(that.typeFactory); + } + + @Override + public int hashCode() { + int result = context.hashCode(); + result = 31 * result + typeFactory.hashCode(); + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java index 59faa24cb00..feef3ca3ddb 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AnnotatedContainsInferenceVariable.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.util.typeinference8.types; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.type.TypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -17,146 +13,153 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; -/** Helper class for determining if a type contains an inference variable. */ -public class AnnotatedContainsInferenceVariable { - - /** Creates an AnnotatedContainsInferenceVariable. */ - public AnnotatedContainsInferenceVariable() {} - - /** - * Returns true if {@code type} contains any of the type variables in {@code typeVariables}. - * - * @param typeVariables a collection of type variables - * @param type a type to check - * @return true if {@code type} contains any of the type variables in {@code typeVariables} - */ - public static boolean hasAnyTypeVariable( - Collection typeVariables, AnnotatedTypeMirror type) { - return new Visitor(typeVariables).visit(type); - } +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; - /** A helper class to find type variables mentioned by a type. */ - private static class Visitor implements AnnotatedTypeVisitor { +import javax.lang.model.type.TypeVariable; - /** Type variables for which to search. */ - private final Collection typeVariables; +/** Helper class for determining if a type contains an inference variable. */ +public class AnnotatedContainsInferenceVariable { - /** A set of types that have been visited. Used to prevent infinite recursion. */ - private final Set visitedTypes = new HashSet<>(); + /** Creates an AnnotatedContainsInferenceVariable. */ + public AnnotatedContainsInferenceVariable() {} /** - * Creates the visitor. + * Returns true if {@code type} contains any of the type variables in {@code typeVariables}. * - * @param variables a collection of type variables that should be treated as inference variables + * @param typeVariables a collection of type variables + * @param type a type to check + * @return true if {@code type} contains any of the type variables in {@code typeVariables} */ - Visitor(Collection variables) { - typeVariables = variables; + public static boolean hasAnyTypeVariable( + Collection typeVariables, AnnotatedTypeMirror type) { + return new Visitor(typeVariables).visit(type); } - /** - * Returns true if {@code typeVar} is a type variable in {@code typeVariables} - * - * @param typeVar a type variable - * @return true if {@code typeVar} is a type variable in {@code typeVariables} - */ - private boolean isTypeVariableOfInterest(AnnotatedTypeVariable typeVar) { - return typeVariables.contains(typeVar.getUnderlyingType()); - } + /** A helper class to find type variables mentioned by a type. */ + private static class Visitor implements AnnotatedTypeVisitor { - @Override - public Boolean visit(AnnotatedTypeMirror t, Void aVoid) { - return t != null && t.accept(this, aVoid); - } + /** Type variables for which to search. */ + private final Collection typeVariables; - @Override - public Boolean visit(AnnotatedTypeMirror t) { - return t != null && t.accept(this, null); - } + /** A set of types that have been visited. Used to prevent infinite recursion. */ + private final Set visitedTypes = new HashSet<>(); - @Override - public Boolean visitPrimitive(AnnotatedPrimitiveType t, Void aVoid) { - return false; - } + /** + * Creates the visitor. + * + * @param variables a collection of type variables that should be treated as inference + * variables + */ + Visitor(Collection variables) { + typeVariables = variables; + } - @Override - public Boolean visitNoType(AnnotatedNoType type, Void unused) { - return false; - } + /** + * Returns true if {@code typeVar} is a type variable in {@code typeVariables} + * + * @param typeVar a type variable + * @return true if {@code typeVar} is a type variable in {@code typeVariables} + */ + private boolean isTypeVariableOfInterest(AnnotatedTypeVariable typeVar) { + return typeVariables.contains(typeVar.getUnderlyingType()); + } - @Override - public Boolean visitNull(AnnotatedNullType t, Void aVoid) { - return false; - } + @Override + public Boolean visit(AnnotatedTypeMirror t, Void aVoid) { + return t != null && t.accept(this, aVoid); + } - @Override - public Boolean visitArray(AnnotatedArrayType t, Void aVoid) { - return visit(t.getComponentType()); - } + @Override + public Boolean visit(AnnotatedTypeMirror t) { + return t != null && t.accept(this, null); + } - @Override - public Boolean visitDeclared(AnnotatedDeclaredType t, Void aVoid) { - boolean found = false; - for (AnnotatedTypeMirror typeArg : t.getTypeArguments()) { - if (visit(typeArg)) { - found = true; + @Override + public Boolean visitPrimitive(AnnotatedPrimitiveType t, Void aVoid) { + return false; } - } - return found; - } - @Override - public Boolean visitTypeVariable(AnnotatedTypeVariable t, Void aVoid) { - if (visitedTypes.contains(t)) { - // t has visited before. If it contained an inference variable, - // then true would have been returned, so it must not contain an inference variable. - return false; - } - visitedTypes.add(t); - if (isTypeVariableOfInterest(t)) { - return true; - } - if (visit(t.getLowerBound())) { - return true; - } - return visit(t.getUpperBound()); - } + @Override + public Boolean visitNoType(AnnotatedNoType type, Void unused) { + return false; + } - @Override - public Boolean visitWildcard(AnnotatedWildcardType t, Void aVoid) { - if (visitedTypes.contains(t)) { - return false; - } - visitedTypes.add(t); - - if (visit(t.getSuperBound())) { - return true; - } - return visit(t.getExtendsBound()); - } + @Override + public Boolean visitNull(AnnotatedNullType t, Void aVoid) { + return false; + } - @Override - public Boolean visitExecutable(AnnotatedExecutableType t, Void aVoid) { - return false; - } + @Override + public Boolean visitArray(AnnotatedArrayType t, Void aVoid) { + return visit(t.getComponentType()); + } - @Override - public Boolean visitUnion(AnnotatedUnionType t, Void aVoid) { - for (AnnotatedTypeMirror altern : t.getAlternatives()) { - if (visit(altern)) { - return true; + @Override + public Boolean visitDeclared(AnnotatedDeclaredType t, Void aVoid) { + boolean found = false; + for (AnnotatedTypeMirror typeArg : t.getTypeArguments()) { + if (visit(typeArg)) { + found = true; + } + } + return found; + } + + @Override + public Boolean visitTypeVariable(AnnotatedTypeVariable t, Void aVoid) { + if (visitedTypes.contains(t)) { + // t has visited before. If it contained an inference variable, + // then true would have been returned, so it must not contain an inference variable. + return false; + } + visitedTypes.add(t); + if (isTypeVariableOfInterest(t)) { + return true; + } + if (visit(t.getLowerBound())) { + return true; + } + return visit(t.getUpperBound()); + } + + @Override + public Boolean visitWildcard(AnnotatedWildcardType t, Void aVoid) { + if (visitedTypes.contains(t)) { + return false; + } + visitedTypes.add(t); + + if (visit(t.getSuperBound())) { + return true; + } + return visit(t.getExtendsBound()); + } + + @Override + public Boolean visitExecutable(AnnotatedExecutableType t, Void aVoid) { + return false; + } + + @Override + public Boolean visitUnion(AnnotatedUnionType t, Void aVoid) { + for (AnnotatedTypeMirror altern : t.getAlternatives()) { + if (visit(altern)) { + return true; + } + } + return false; } - } - return false; - } - @Override - public Boolean visitIntersection(AnnotatedIntersectionType t, Void aVoid) { - for (AnnotatedTypeMirror bound : t.getBounds()) { - if (visit(bound)) { - return true; + @Override + public Boolean visitIntersection(AnnotatedIntersectionType t, Void aVoid) { + for (AnnotatedTypeMirror bound : t.getBounds()) { + if (visit(bound)) { + return true; + } + } + return false; } - } - return false; } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java index 04acd7ddc42..942f5c645ae 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CaptureVariable.java @@ -1,56 +1,58 @@ package org.checkerframework.framework.util.typeinference8.types; import com.sun.source.tree.ExpressionTree; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; import org.checkerframework.framework.util.typeinference8.util.Theta; +import javax.lang.model.type.TypeVariable; + /** Variables created as a part of a capture bound. */ @Interned public class CaptureVariable extends Variable { - /** - * Creates a captured variable - * - * @param type the annotated type variable that is captured - * @param typeVariableJava the type variable that is captured - * @param invocation invocation expression for the variable - * @param context the context - * @param map a mapping from type variable to inference variable - */ - CaptureVariable( - AnnotatedTypeVariable type, - TypeVariable typeVariableJava, - ExpressionTree invocation, - Java8InferenceContext context, - Theta map) { - super(type, typeVariableJava, invocation, context, map, context.getNextCaptureVariableId()); - } - - @Override - public String toString() { - // Use "b" instead of "a" like super so it is apparent that this is a capture variable. - if (variableBounds.hasInstantiation()) { - return "b" + id + " := " + variableBounds.getInstantiation(); + /** + * Creates a captured variable + * + * @param type the annotated type variable that is captured + * @param typeVariableJava the type variable that is captured + * @param invocation invocation expression for the variable + * @param context the context + * @param map a mapping from type variable to inference variable + */ + CaptureVariable( + AnnotatedTypeVariable type, + TypeVariable typeVariableJava, + ExpressionTree invocation, + Java8InferenceContext context, + Theta map) { + super(type, typeVariableJava, invocation, context, map, context.getNextCaptureVariableId()); + } + + @Override + public String toString() { + // Use "b" instead of "a" like super so it is apparent that this is a capture variable. + if (variableBounds.hasInstantiation()) { + return "b" + id + " := " + variableBounds.getInstantiation(); + } + return "b" + id; + } + + /** + * Returns the constraints generated when incorporating a capture bound. See JLS 18.3.2. + * + * @param Ai the captured type argument + * @param Bi the bound of the type variable + * @return constraints generated when incorporating a capture bound + */ + public ConstraintSet getWildcardConstraints(AbstractType Ai, AbstractType Bi) { + return variableBounds.getWildcardConstraints(Ai, Bi); + } + + @Override + public boolean isCaptureVariable() { + return true; } - return "b" + id; - } - - /** - * Returns the constraints generated when incorporating a capture bound. See JLS 18.3.2. - * - * @param Ai the captured type argument - * @param Bi the bound of the type variable - * @return constraints generated when incorporating a capture bound - */ - public ConstraintSet getWildcardConstraints(AbstractType Ai, AbstractType Bi) { - return variableBounds.getWildcardConstraints(Ai, Bi); - } - - @Override - public boolean isCaptureVariable() { - return true; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java index c171def5edb..1fecceb8ade 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ContainsInferenceVariable.java @@ -1,9 +1,12 @@ package org.checkerframework.framework.util.typeinference8.types; +import org.checkerframework.javacutil.TypesUtils; + import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; + import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ErrorType; @@ -17,182 +20,182 @@ import javax.lang.model.type.TypeVisitor; import javax.lang.model.type.UnionType; import javax.lang.model.type.WildcardType; -import org.checkerframework.javacutil.TypesUtils; /** Helper class for determining if a type contains an inference variable. */ public class ContainsInferenceVariable { - /** Don't use. */ - private ContainsInferenceVariable() {} - - /** - * Returns true if {@code type} contains any of the type variables in {@code typeVariables}. - * - * @param typeVariables a collection of type variables - * @param type a type to check - * @return true if {@code type} contains any of the type variables in {@code typeVariables} - */ - public static boolean hasAnyTypeVariable( - Collection typeVariables, TypeMirror type) { - return new Visitor(typeVariables).visit(type); - } - - /** - * Returns the type variables in {@code typeVariables} that appear in {@code type}. - * - * @param typeVariables a collection of type variables - * @param type a type to check - * @return the type variables in {@code typeVariables} that appear in {@code type} - */ - public static Set getMentionedTypeVariables( - Collection typeVariables, TypeMirror type) { - Visitor visitor = new Visitor(typeVariables); - visitor.visit(type); - return visitor.foundVariables; - } - - /** A helper class to find type variables mentioned by a type. */ - static class Visitor implements TypeVisitor { - - /** Type variables for which to search. */ - private final Collection typeVariables; - - /** Type variables in {@code typeVariables} that have been found. */ - // default visibility to allow direct access from getMentionedTypeVariables - final LinkedHashSet foundVariables = new LinkedHashSet<>(); - - /** A set of types that have been visited. Used to prevent infinite recursion. */ - private final Set visitedTypes = new HashSet<>(); + /** Don't use. */ + private ContainsInferenceVariable() {} /** - * Creates the visitor. + * Returns true if {@code type} contains any of the type variables in {@code typeVariables}. * - * @param variables a collection of type variables that should be treated as inference variables + * @param typeVariables a collection of type variables + * @param type a type to check + * @return true if {@code type} contains any of the type variables in {@code typeVariables} */ - Visitor(Collection variables) { - typeVariables = variables; + public static boolean hasAnyTypeVariable( + Collection typeVariables, TypeMirror type) { + return new Visitor(typeVariables).visit(type); } /** - * Returns true if {@code typeVar} is a type variable in {@code typeVariables}. + * Returns the type variables in {@code typeVariables} that appear in {@code type}. * - * @param typeVar a type variable - * @return true if {@code typeVar} is a type variable in {@code typeVariables} + * @param typeVariables a collection of type variables + * @param type a type to check + * @return the type variables in {@code typeVariables} that appear in {@code type} */ - private boolean isTypeVariableOfInterest(TypeVariable typeVar) { - for (TypeVariable tv : typeVariables) { - if (TypesUtils.areSame(tv, typeVar)) { - foundVariables.add(tv); - return true; - } - } - return false; - } + public static Set getMentionedTypeVariables( + Collection typeVariables, TypeMirror type) { + Visitor visitor = new Visitor(typeVariables); + visitor.visit(type); + return visitor.foundVariables; + } + + /** A helper class to find type variables mentioned by a type. */ + static class Visitor implements TypeVisitor { + + /** Type variables for which to search. */ + private final Collection typeVariables; + + /** Type variables in {@code typeVariables} that have been found. */ + // default visibility to allow direct access from getMentionedTypeVariables + final LinkedHashSet foundVariables = new LinkedHashSet<>(); + + /** A set of types that have been visited. Used to prevent infinite recursion. */ + private final Set visitedTypes = new HashSet<>(); + + /** + * Creates the visitor. + * + * @param variables a collection of type variables that should be treated as inference + * variables + */ + Visitor(Collection variables) { + typeVariables = variables; + } - @Override - public Boolean visit(TypeMirror t, Void aVoid) { - return t != null && t.accept(this, aVoid); - } + /** + * Returns true if {@code typeVar} is a type variable in {@code typeVariables}. + * + * @param typeVar a type variable + * @return true if {@code typeVar} is a type variable in {@code typeVariables} + */ + private boolean isTypeVariableOfInterest(TypeVariable typeVar) { + for (TypeVariable tv : typeVariables) { + if (TypesUtils.areSame(tv, typeVar)) { + foundVariables.add(tv); + return true; + } + } + return false; + } - @Override - public Boolean visit(TypeMirror t) { - return t != null && t.accept(this, null); - } + @Override + public Boolean visit(TypeMirror t, Void aVoid) { + return t != null && t.accept(this, aVoid); + } - @Override - public Boolean visitPrimitive(PrimitiveType t, Void aVoid) { - return false; - } + @Override + public Boolean visit(TypeMirror t) { + return t != null && t.accept(this, null); + } - @Override - public Boolean visitNull(NullType t, Void aVoid) { - return false; - } + @Override + public Boolean visitPrimitive(PrimitiveType t, Void aVoid) { + return false; + } - @Override - public Boolean visitArray(ArrayType t, Void aVoid) { - return visit(t.getComponentType()); - } + @Override + public Boolean visitNull(NullType t, Void aVoid) { + return false; + } - @Override - public Boolean visitDeclared(DeclaredType t, Void aVoid) { - boolean found = false; - for (TypeMirror typeArg : t.getTypeArguments()) { - if (visit(typeArg)) { - found = true; + @Override + public Boolean visitArray(ArrayType t, Void aVoid) { + return visit(t.getComponentType()); } - } - return found; - } - @Override - public Boolean visitError(ErrorType t, Void aVoid) { - return null; - } + @Override + public Boolean visitDeclared(DeclaredType t, Void aVoid) { + boolean found = false; + for (TypeMirror typeArg : t.getTypeArguments()) { + if (visit(typeArg)) { + found = true; + } + } + return found; + } - @Override - public Boolean visitTypeVariable(TypeVariable t, Void aVoid) { - if (visitedTypes.contains(t)) { - // t has visited before. If it contained an inference variable, - // then true would have been returned, so it must not contain an inference variable. - return false; - } - visitedTypes.add(t); - if (isTypeVariableOfInterest(t)) { - return true; - } - if (visit(t.getLowerBound())) { - return true; - } - return visit(t.getUpperBound()); - } + @Override + public Boolean visitError(ErrorType t, Void aVoid) { + return null; + } - @Override - public Boolean visitWildcard(WildcardType t, Void aVoid) { - if (visitedTypes.contains(t)) { - return false; - } - visitedTypes.add(t); - - if (visit(t.getSuperBound())) { - return true; - } - return visit(t.getExtendsBound()); - } + @Override + public Boolean visitTypeVariable(TypeVariable t, Void aVoid) { + if (visitedTypes.contains(t)) { + // t has visited before. If it contained an inference variable, + // then true would have been returned, so it must not contain an inference variable. + return false; + } + visitedTypes.add(t); + if (isTypeVariableOfInterest(t)) { + return true; + } + if (visit(t.getLowerBound())) { + return true; + } + return visit(t.getUpperBound()); + } - @Override - public Boolean visitExecutable(ExecutableType t, Void aVoid) { - return false; - } + @Override + public Boolean visitWildcard(WildcardType t, Void aVoid) { + if (visitedTypes.contains(t)) { + return false; + } + visitedTypes.add(t); + + if (visit(t.getSuperBound())) { + return true; + } + return visit(t.getExtendsBound()); + } - @Override - public Boolean visitNoType(NoType t, Void aVoid) { - return false; - } + @Override + public Boolean visitExecutable(ExecutableType t, Void aVoid) { + return false; + } - @Override - public Boolean visitUnknown(TypeMirror t, Void aVoid) { - return false; - } + @Override + public Boolean visitNoType(NoType t, Void aVoid) { + return false; + } - @Override - public Boolean visitUnion(UnionType t, Void aVoid) { - for (TypeMirror altern : t.getAlternatives()) { - if (visit(altern)) { - return true; + @Override + public Boolean visitUnknown(TypeMirror t, Void aVoid) { + return false; + } + + @Override + public Boolean visitUnion(UnionType t, Void aVoid) { + for (TypeMirror altern : t.getAlternatives()) { + if (visit(altern)) { + return true; + } + } + return false; } - } - return false; - } - @Override - public Boolean visitIntersection(IntersectionType t, Void aVoid) { - for (TypeMirror bound : t.getBounds()) { - if (visit(bound)) { - return true; + @Override + public Boolean visitIntersection(IntersectionType t, Void aVoid) { + for (TypeMirror bound : t.getBounds()) { + if (visit(bound)) { + return true; + } + } + return false; } - } - return false; } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java index 8ab2b743777..43c1b11d74b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Dependencies.java @@ -14,82 +14,82 @@ */ public class Dependencies { - /** Creates Dependencies. */ - public Dependencies() {} + /** Creates Dependencies. */ + public Dependencies() {} - /** A map from a variable to the variables, including itself, on which it depends. */ - private final Map> map = new LinkedHashMap<>(); + /** A map from a variable to the variables, including itself, on which it depends. */ + private final Map> map = new LinkedHashMap<>(); - /** - * Add {@code value} as a dependency of {@code key}. - * - * @param key a key to add - * @param value a value to add - */ - public void putOrAdd(Variable key, Variable value) { - LinkedHashSet set = map.computeIfAbsent(key, k -> new LinkedHashSet<>()); - set.add(value); - } + /** + * Add {@code value} as a dependency of {@code key}. + * + * @param key a key to add + * @param value a value to add + */ + public void putOrAdd(Variable key, Variable value) { + LinkedHashSet set = map.computeIfAbsent(key, k -> new LinkedHashSet<>()); + set.add(value); + } - /** - * Add {@code values} as dependencies of {@code key}. - * - * @param key a key to add - * @param values values to add - */ - public void putOrAddAll(Variable key, Collection values) { - LinkedHashSet set = map.computeIfAbsent(key, k -> new LinkedHashSet<>()); - set.addAll(values); - } + /** + * Add {@code values} as dependencies of {@code key}. + * + * @param key a key to add + * @param values values to add + */ + public void putOrAddAll(Variable key, Collection values) { + LinkedHashSet set = map.computeIfAbsent(key, k -> new LinkedHashSet<>()); + set.addAll(values); + } - /** - * Calculate and add transitive dependencies. - * - *

          JLS 18.4 "An inference variable alpha depends on the resolution of an inference variable - * beta if there exists an inference variable gamma such that alpha depends on the resolution of - * gamma and gamma depends on the resolution of beta." - */ - public void calculateTransitiveDependencies() { - boolean changed = true; - while (changed) { - changed = false; - for (Map.Entry> entry : map.entrySet()) { - Variable alpha = entry.getKey(); - LinkedHashSet gammas = entry.getValue(); - LinkedHashSet betas = new LinkedHashSet<>(); - for (Variable gamma : gammas) { - if (gamma == alpha) { - continue; - } - betas.addAll(map.get(gamma)); + /** + * Calculate and add transitive dependencies. + * + *

          JLS 18.4 "An inference variable alpha depends on the resolution of an inference variable + * beta if there exists an inference variable gamma such that alpha depends on the resolution of + * gamma and gamma depends on the resolution of beta." + */ + public void calculateTransitiveDependencies() { + boolean changed = true; + while (changed) { + changed = false; + for (Map.Entry> entry : map.entrySet()) { + Variable alpha = entry.getKey(); + LinkedHashSet gammas = entry.getValue(); + LinkedHashSet betas = new LinkedHashSet<>(); + for (Variable gamma : gammas) { + if (gamma == alpha) { + continue; + } + betas.addAll(map.get(gamma)); + } + changed |= gammas.addAll(betas); + } } - changed |= gammas.addAll(betas); - } } - } - /** - * Returns the set of dependencies of {@code alpha}. - * - * @param alpha a variable - * @return the set of dependencies of {@code alpha} - */ - public Set get(Variable alpha) { - return new LinkedHashSet<>(map.get(alpha)); - } + /** + * Returns the set of dependencies of {@code alpha}. + * + * @param alpha a variable + * @return the set of dependencies of {@code alpha} + */ + public Set get(Variable alpha) { + return new LinkedHashSet<>(map.get(alpha)); + } - /** - * Returns the set of dependencies for all variables in {@code variables}. - * - * @param variables list of variables - * @return the set of dependencies for all variables in {@code variables} - */ - public Set get(List variables) { - LinkedHashSet set = new LinkedHashSet<>(); - for (Variable v : variables) { - Set get = get(v); - set.addAll(get); + /** + * Returns the set of dependencies for all variables in {@code variables}. + * + * @param variables list of variables + * @return the set of dependencies for all variables in {@code variables} + */ + public Set get(List variables) { + LinkedHashSet set = new LinkedHashSet<>(); + for (Variable v : variables) { + Set get = get(v); + set.addAll(get); + } + return set; } - return set; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java index 982a019726e..03170063a49 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java @@ -20,27 +20,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.processing.JavacProcessingEnvironment; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -70,1111 +50,1179 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.IPair; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; + /** Factory that creates AbstractTypes. */ public class InferenceFactory { - /** AnnotatedTypeFactory used to get annotated types. */ - private final AnnotatedTypeFactory typeFactory; - - /** Stores information about the current inference problem being solved. */ - private Java8InferenceContext context; - - /** - * Creates an inference factory - * - * @param context the context - */ - public InferenceFactory(Java8InferenceContext context) { - this.context = context; - this.typeFactory = context.typeFactory; - } - - /** - * Gets the target type for the expression for which type arguments are being inferred. - * - * @return target type for the expression for which type arguments are being inferred - */ - public @Nullable ProperType getTargetType() { - GenericAnnotatedTypeFactory factory = - (GenericAnnotatedTypeFactory) context.typeFactory; - TreePath path = context.pathToExpression; - Tree context = TreePathUtil.getContextForPolyExpression(path); - if (context == null) { - AnnotatedTypeMirror dummy = factory.getDummyAssignedTo((ExpressionTree) path.getLeaf()); - if (dummy == null || dummy.containsCapturedTypes()) { - return null; - } - return new ProperType(dummy, dummy.getUnderlyingType(), this.context); + /** AnnotatedTypeFactory used to get annotated types. */ + private final AnnotatedTypeFactory typeFactory; + + /** Stores information about the current inference problem being solved. */ + private Java8InferenceContext context; + + /** + * Creates an inference factory + * + * @param context the context + */ + public InferenceFactory(Java8InferenceContext context) { + this.context = context; + this.typeFactory = context.typeFactory; } - switch (context.getKind()) { - case ASSIGNMENT: - ExpressionTree variable = ((AssignmentTree) context).getVariable(); - AnnotatedTypeMirror atm = factory.getAnnotatedTypeLhs(variable); - return new ProperType(atm, TreeUtils.typeOf(variable), this.context); - case TYPE_CAST: - Tree cast = ((TypeCastTree) context).getType(); - AnnotatedTypeMirror castType = factory.getAnnotatedTypeFromTypeTree(cast); - return new ProperType(castType, TreeUtils.typeOf(cast), this.context); - case VARIABLE: - VariableTree variableTree = (VariableTree) context; - AnnotatedTypeMirror variableAtm = assignedToVariable(factory, context); - return new ProperType(variableAtm, TreeUtils.typeOf(variableTree.getType()), this.context); - case METHOD_INVOCATION: - MethodInvocationTree methodInvocation = (MethodInvocationTree) context; - - AnnotatedExecutableType methodType = - factory.methodFromUseWithoutTypeArgInference(methodInvocation).executableType; - - AnnotatedTypeMirror paramType = - assignedToExecutable( - path, methodInvocation, methodInvocation.getArguments(), methodType); - return new ProperType( - paramType, - assignedToExecutable( - path, methodInvocation, methodInvocation.getArguments(), this.context), - this.context); - case NEW_CLASS: - NewClassTree newClassTree = (NewClassTree) context; - AnnotatedExecutableType constructorType = - factory.constructorFromUseWithoutTypeArgInference(newClassTree).executableType; - AnnotatedTypeMirror constATM = - assignedToExecutable(path, newClassTree, newClassTree.getArguments(), constructorType); - return new ProperType( - constATM, - assignedToExecutable(path, newClassTree, newClassTree.getArguments(), this.context), - this.context); - case NEW_ARRAY: - NewArrayTree newArrayTree = (NewArrayTree) context; - ArrayType arrayType = (ArrayType) TreeUtils.typeOf(newArrayTree); - AnnotatedArrayType type = factory.getAnnotatedType((NewArrayTree) context); - AnnotatedTypeMirror component = type.getComponentType(); - return new ProperType(component, arrayType.getComponentType(), this.context); - case LAMBDA_EXPRESSION: - { - LambdaExpressionTree lambdaTree = (LambdaExpressionTree) context; - AnnotatedExecutableType fninf = factory.getFunctionTypeFromTree(lambdaTree); - AnnotatedTypeMirror res = fninf.getReturnType(); - if (res.getKind() == TypeKind.VOID) { - return null; - } - return new ProperType(res, res.getUnderlyingType(), this.context); + /** + * Gets the target type for the expression for which type arguments are being inferred. + * + * @return target type for the expression for which type arguments are being inferred + */ + public @Nullable ProperType getTargetType() { + GenericAnnotatedTypeFactory factory = + (GenericAnnotatedTypeFactory) context.typeFactory; + TreePath path = context.pathToExpression; + Tree context = TreePathUtil.getContextForPolyExpression(path); + if (context == null) { + AnnotatedTypeMirror dummy = factory.getDummyAssignedTo((ExpressionTree) path.getLeaf()); + if (dummy == null || dummy.containsCapturedTypes()) { + return null; + } + return new ProperType(dummy, dummy.getUnderlyingType(), this.context); } - case RETURN: - HashSet kinds = - new HashSet<>(Arrays.asList(Tree.Kind.LAMBDA_EXPRESSION, Tree.Kind.METHOD)); - Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds); - if (enclosing.getKind() == Tree.Kind.METHOD) { - MethodTree methodTree = (MethodTree) enclosing; - AnnotatedTypeMirror res = factory.getMethodReturnType(methodTree); - return new ProperType(res, TreeUtils.typeOf(methodTree.getReturnType()), this.context); - } else { - LambdaExpressionTree lambdaTree = (LambdaExpressionTree) enclosing; - AnnotatedExecutableType fninf = factory.getFunctionTypeFromTree(lambdaTree); - AnnotatedTypeMirror res = fninf.getReturnType(); - return new ProperType(res, res.getUnderlyingType(), this.context); + + switch (context.getKind()) { + case ASSIGNMENT: + ExpressionTree variable = ((AssignmentTree) context).getVariable(); + AnnotatedTypeMirror atm = factory.getAnnotatedTypeLhs(variable); + return new ProperType(atm, TreeUtils.typeOf(variable), this.context); + case TYPE_CAST: + Tree cast = ((TypeCastTree) context).getType(); + AnnotatedTypeMirror castType = factory.getAnnotatedTypeFromTypeTree(cast); + return new ProperType(castType, TreeUtils.typeOf(cast), this.context); + case VARIABLE: + VariableTree variableTree = (VariableTree) context; + AnnotatedTypeMirror variableAtm = assignedToVariable(factory, context); + return new ProperType( + variableAtm, TreeUtils.typeOf(variableTree.getType()), this.context); + case METHOD_INVOCATION: + MethodInvocationTree methodInvocation = (MethodInvocationTree) context; + + AnnotatedExecutableType methodType = + factory.methodFromUseWithoutTypeArgInference(methodInvocation) + .executableType; + + AnnotatedTypeMirror paramType = + assignedToExecutable( + path, + methodInvocation, + methodInvocation.getArguments(), + methodType); + return new ProperType( + paramType, + assignedToExecutable( + path, + methodInvocation, + methodInvocation.getArguments(), + this.context), + this.context); + case NEW_CLASS: + NewClassTree newClassTree = (NewClassTree) context; + AnnotatedExecutableType constructorType = + factory.constructorFromUseWithoutTypeArgInference(newClassTree) + .executableType; + AnnotatedTypeMirror constATM = + assignedToExecutable( + path, newClassTree, newClassTree.getArguments(), constructorType); + return new ProperType( + constATM, + assignedToExecutable( + path, newClassTree, newClassTree.getArguments(), this.context), + this.context); + case NEW_ARRAY: + NewArrayTree newArrayTree = (NewArrayTree) context; + ArrayType arrayType = (ArrayType) TreeUtils.typeOf(newArrayTree); + AnnotatedArrayType type = factory.getAnnotatedType((NewArrayTree) context); + AnnotatedTypeMirror component = type.getComponentType(); + return new ProperType(component, arrayType.getComponentType(), this.context); + case LAMBDA_EXPRESSION: + { + LambdaExpressionTree lambdaTree = (LambdaExpressionTree) context; + AnnotatedExecutableType fninf = factory.getFunctionTypeFromTree(lambdaTree); + AnnotatedTypeMirror res = fninf.getReturnType(); + if (res.getKind() == TypeKind.VOID) { + return null; + } + return new ProperType(res, res.getUnderlyingType(), this.context); + } + case RETURN: + HashSet kinds = + new HashSet<>(Arrays.asList(Tree.Kind.LAMBDA_EXPRESSION, Tree.Kind.METHOD)); + Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds); + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) enclosing; + AnnotatedTypeMirror res = factory.getMethodReturnType(methodTree); + return new ProperType( + res, TreeUtils.typeOf(methodTree.getReturnType()), this.context); + } else { + LambdaExpressionTree lambdaTree = (LambdaExpressionTree) enclosing; + AnnotatedExecutableType fninf = factory.getFunctionTypeFromTree(lambdaTree); + AnnotatedTypeMirror res = fninf.getReturnType(); + return new ProperType(res, res.getUnderlyingType(), this.context); + } + default: + if (context.getKind().asInterface() == CompoundAssignmentTree.class) { + // 11 Tree kinds are compound assignments, so don't use it in the switch + ExpressionTree var = ((CompoundAssignmentTree) context).getVariable(); + AnnotatedTypeMirror res = factory.getAnnotatedTypeLhs(var); + + return new ProperType(res, TreeUtils.typeOf(var), this.context); + } else { + throw new BugInCF( + "Unexpected assignment context.\nKind: %s\nTree: %s", + context.getKind(), context); + } } - default: - if (context.getKind().asInterface() == CompoundAssignmentTree.class) { - // 11 Tree kinds are compound assignments, so don't use it in the switch - ExpressionTree var = ((CompoundAssignmentTree) context).getVariable(); - AnnotatedTypeMirror res = factory.getAnnotatedTypeLhs(var); + } - return new ProperType(res, TreeUtils.typeOf(var), this.context); + /** + * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree). + * Rational: + * + *

          For example: + * + *

          {@code
          +     *  S bar () {...}
          +     *
          +     *  T foo(T p) {
          +     *     T local = bar();
          +     *     return local;
          +     *   }
          +     * }
          + * + * During type argument inference of {@code bar}, the assignment context is {@code local}. If + * the local variable default is used, then the type of assignment context type is + * {@code @Nullable T} and the type argument inferred for {@code bar()} is {@code @Nullable T}. + * And an incompatible types in return error is issued. + * + *

          If instead, the local variable default is not applied, then the assignment context type is + * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) + * and the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of + * {@code local} is refined to {@code T} and the return is legal. + * + *

          If the assignment context type was a declared type, for example: + * + *

          {@code
          +     *  S bar () {...}
          +     * Object foo() {
          +     *     Object local = bar();
          +     *     return local;
          +     * }
          +     * }
          + * + * The local variable default must be used or else the assignment context type is missing an + * annotation. So, an incompatible types in return error is issued in the above code. We could + * improve type argument inference in this case and by using the lower bound of {@code S} + * instead of the local variable default. + * + * @param atypeFactory AnnotatedTypeFactory + * @param assignmentContext VariableTree + * @return AnnotatedTypeMirror of Assignment context + */ + public static AnnotatedTypeMirror assignedToVariable( + AnnotatedTypeFactory atypeFactory, Tree assignmentContext) { + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + final GenericAnnotatedTypeFactory gatf = + ((GenericAnnotatedTypeFactory) atypeFactory); + return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext); } else { - throw new BugInCF( - "Unexpected assignment context.\nKind: %s\nTree: %s", context.getKind(), context); + return atypeFactory.getAnnotatedType(assignmentContext); } } - } - - /** - * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree). - * Rational: - * - *

          For example: - * - *

          {@code
          -   *  S bar () {...}
          -   *
          -   *  T foo(T p) {
          -   *     T local = bar();
          -   *     return local;
          -   *   }
          -   * }
          - * - * During type argument inference of {@code bar}, the assignment context is {@code local}. If the - * local variable default is used, then the type of assignment context type is {@code @Nullable T} - * and the type argument inferred for {@code bar()} is {@code @Nullable T}. And an incompatible - * types in return error is issued. - * - *

          If instead, the local variable default is not applied, then the assignment context type is - * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) and - * the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of {@code - * local} is refined to {@code T} and the return is legal. - * - *

          If the assignment context type was a declared type, for example: - * - *

          {@code
          -   *  S bar () {...}
          -   * Object foo() {
          -   *     Object local = bar();
          -   *     return local;
          -   * }
          -   * }
          - * - * The local variable default must be used or else the assignment context type is missing an - * annotation. So, an incompatible types in return error is issued in the above code. We could - * improve type argument inference in this case and by using the lower bound of {@code S} instead - * of the local variable default. - * - * @param atypeFactory AnnotatedTypeFactory - * @param assignmentContext VariableTree - * @return AnnotatedTypeMirror of Assignment context - */ - public static AnnotatedTypeMirror assignedToVariable( - AnnotatedTypeFactory atypeFactory, Tree assignmentContext) { - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - final GenericAnnotatedTypeFactory gatf = - ((GenericAnnotatedTypeFactory) atypeFactory); - return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext); - } else { - return atypeFactory.getAnnotatedType(assignmentContext); - } - } - - /** - * Return the rhs of the assignment of an argument and its formal parameter. - * - * @param path path to the argument - * @param invocation a method or constructor invocation - * @param arguments the argument expression tress - * @param context the context - * @return the rhs of the assignment of an argument and its formal parameter - */ - private static TypeMirror assignedToExecutable( - TreePath path, - ExpressionTree invocation, - List arguments, - Java8InferenceContext context) { - int treeIndex = -1; - for (int i = 0; i < arguments.size(); ++i) { - ExpressionTree argumentTree = arguments.get(i); - if (isArgument(path, argumentTree)) { - treeIndex = i; - break; - } - } - ExecutableType methodType = getTypeOfMethodAdaptedToUse(invocation, context); - if (treeIndex >= methodType.getParameterTypes().size() - 1 - && TreeUtils.isVarArgMethodCall(invocation)) { - treeIndex = methodType.getParameterTypes().size() - 1; - TypeMirror typeMirror = methodType.getParameterTypes().get(treeIndex); - return ((ArrayType) typeMirror).getComponentType(); - } + /** + * Return the rhs of the assignment of an argument and its formal parameter. + * + * @param path path to the argument + * @param invocation a method or constructor invocation + * @param arguments the argument expression tress + * @param context the context + * @return the rhs of the assignment of an argument and its formal parameter + */ + private static TypeMirror assignedToExecutable( + TreePath path, + ExpressionTree invocation, + List arguments, + Java8InferenceContext context) { + int treeIndex = -1; + for (int i = 0; i < arguments.size(); ++i) { + ExpressionTree argumentTree = arguments.get(i); + if (isArgument(path, argumentTree)) { + treeIndex = i; + break; + } + } - return methodType.getParameterTypes().get(treeIndex); - } - - /** - * Return the rhs of the assignment of an argument and its formal parameter. - * - * @param path path to the argument - * @param invocation a method or constructor invocation - * @param arguments the argument expression tress - * @param methodType the type of the method or constructor - * @return the rhs of the assignment of an argument and its formal parameter - */ - private static AnnotatedTypeMirror assignedToExecutable( - TreePath path, - ExpressionTree invocation, - List arguments, - AnnotatedExecutableType methodType) { - int treeIndex = -1; - for (int i = 0; i < arguments.size(); ++i) { - ExpressionTree argumentTree = arguments.get(i); - if (isArgument(path, argumentTree)) { - treeIndex = i; - break; - } - } + ExecutableType methodType = getTypeOfMethodAdaptedToUse(invocation, context); + if (treeIndex >= methodType.getParameterTypes().size() - 1 + && TreeUtils.isVarArgMethodCall(invocation)) { + treeIndex = methodType.getParameterTypes().size() - 1; + TypeMirror typeMirror = methodType.getParameterTypes().get(treeIndex); + return ((ArrayType) typeMirror).getComponentType(); + } - if (treeIndex >= methodType.getParameterTypes().size() - 1 - && TreeUtils.isVarArgMethodCall(invocation)) { - treeIndex = methodType.getParameterTypes().size() - 1; - AnnotatedTypeMirror typeMirror = methodType.getParameterTypes().get(treeIndex); - return ((AnnotatedArrayType) typeMirror).getComponentType(); + return methodType.getParameterTypes().get(treeIndex); } - return methodType.getParameterTypes().get(treeIndex); - } - - /** - * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional - * expression, isArgument is called recursively on the true and false expressions. If tree is a - * switch expression isArgument is called recursively on all yielded expressions. - * - * @param path tree path might contain {@code argumentTree} - * @param argumentTree an expression tree that might be in {@code path} - * @return whether argumentTree is the tree at the leaf of path - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - private static boolean isArgument(TreePath path, ExpressionTree argumentTree) { - argumentTree = TreeUtils.withoutParens(argumentTree); - if (argumentTree == path.getLeaf()) { - return true; - } else if (argumentTree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { - ConditionalExpressionTree conditionalExpressionTree = - (ConditionalExpressionTree) argumentTree; - return isArgument(path, conditionalExpressionTree.getTrueExpression()) - || isArgument(path, conditionalExpressionTree.getFalseExpression()); - } else if (TreeUtils.isSwitchExpression(argumentTree)) { - SwitchExpressionScanner scanner = - new FunctionalSwitchExpressionScanner<>( - (tree, unused) -> isArgument(path, tree), - (r1, r2) -> (r1 != null && r1) || (r2 != null && r2)); - return scanner.scanSwitchExpression(argumentTree, null); - } - return false; - } - - /** - * Returns the type of the receiver of {@code tree} or null if {@code tree} does not have a - * receiver. - * - * @param tree an expression tree - * @return the type of the receiver of {@code tree} or null if {@code tree} does not have a - * receiver - */ - private static @Nullable DeclaredType getReceiverType(ExpressionTree tree) { - Tree receiverTree; - if (tree.getKind() == Tree.Kind.NEW_CLASS) { - receiverTree = ((NewClassTree) tree).getEnclosingExpression(); - if (receiverTree == null && ((NewClassTree) tree).getClassBody() == null) { - TypeMirror t = TreeUtils.elementFromUse((NewClassTree) tree).getReceiverType(); - if (t instanceof DeclaredType) { - return (DeclaredType) t; + /** + * Return the rhs of the assignment of an argument and its formal parameter. + * + * @param path path to the argument + * @param invocation a method or constructor invocation + * @param arguments the argument expression tress + * @param methodType the type of the method or constructor + * @return the rhs of the assignment of an argument and its formal parameter + */ + private static AnnotatedTypeMirror assignedToExecutable( + TreePath path, + ExpressionTree invocation, + List arguments, + AnnotatedExecutableType methodType) { + int treeIndex = -1; + for (int i = 0; i < arguments.size(); ++i) { + ExpressionTree argumentTree = arguments.get(i); + if (isArgument(path, argumentTree)) { + treeIndex = i; + break; + } + } + + if (treeIndex >= methodType.getParameterTypes().size() - 1 + && TreeUtils.isVarArgMethodCall(invocation)) { + treeIndex = methodType.getParameterTypes().size() - 1; + AnnotatedTypeMirror typeMirror = methodType.getParameterTypes().get(treeIndex); + return ((AnnotatedArrayType) typeMirror).getComponentType(); } - return null; - } - } else { - receiverTree = TreeUtils.getReceiverTree(tree); - } - if (receiverTree == null) { - return null; + return methodType.getParameterTypes().get(treeIndex); } - TypeMirror type = TreeUtils.typeOf(receiverTree); - if (type.getKind() == TypeKind.TYPEVAR) { - return (DeclaredType) ((TypeVariable) type).getUpperBound(); + + /** + * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional + * expression, isArgument is called recursively on the true and false expressions. If tree is a + * switch expression isArgument is called recursively on all yielded expressions. + * + * @param path tree path might contain {@code argumentTree} + * @param argumentTree an expression tree that might be in {@code path} + * @return whether argumentTree is the tree at the leaf of path + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private static boolean isArgument(TreePath path, ExpressionTree argumentTree) { + argumentTree = TreeUtils.withoutParens(argumentTree); + if (argumentTree == path.getLeaf()) { + return true; + } else if (argumentTree.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) { + ConditionalExpressionTree conditionalExpressionTree = + (ConditionalExpressionTree) argumentTree; + return isArgument(path, conditionalExpressionTree.getTrueExpression()) + || isArgument(path, conditionalExpressionTree.getFalseExpression()); + } else if (TreeUtils.isSwitchExpression(argumentTree)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (tree, unused) -> isArgument(path, tree), + (r1, r2) -> (r1 != null && r1) || (r2 != null && r2)); + return scanner.scanSwitchExpression(argumentTree, null); + } + return false; } - return type.getKind() == TypeKind.DECLARED ? (DeclaredType) type : null; - } - - /** - * Return ExecutableType of the method invocation or new class tree adapted to the call site. - * - * @param expressionTree a method invocation or new class tree - * @param context the context - * @return ExecutableType of the method invocation or new class tree adapted to the call site - */ - public static ExecutableType getTypeOfMethodAdaptedToUse( - ExpressionTree expressionTree, Java8InferenceContext context) { - assert expressionTree.getKind() == Kind.NEW_CLASS - || expressionTree.getKind() == Kind.METHOD_INVOCATION; - - ExecutableElement ele = (ExecutableElement) TreeUtils.elementFromUse(expressionTree); - ExecutableType executableType = null; - // First adapt to receiver - if (!ElementUtils.isStatic(ele)) { - DeclaredType receiverType = getReceiverType(expressionTree); - if (receiverType == null && expressionTree.getKind() == Kind.METHOD_INVOCATION) { - receiverType = context.enclosingType; - } else if (receiverType != null) { - receiverType = (DeclaredType) context.types.capture((Type) receiverType); - } - - while (receiverType != null - && context.types.asSuper((Type) receiverType, (Symbol) ele.getEnclosingElement()) - == null) { - TypeMirror enclosing = receiverType.getEnclosingType(); - if (enclosing == null || enclosing.getKind() != TypeKind.DECLARED) { - if (expressionTree.getKind() == Tree.Kind.NEW_CLASS) { - // No receiver for the constructor. - executableType = (ExecutableType) ele.asType(); - } else { - throw new BugInCF("Method not found"); - } + + /** + * Returns the type of the receiver of {@code tree} or null if {@code tree} does not have a + * receiver. + * + * @param tree an expression tree + * @return the type of the receiver of {@code tree} or null if {@code tree} does not have a + * receiver + */ + private static @Nullable DeclaredType getReceiverType(ExpressionTree tree) { + Tree receiverTree; + if (tree.getKind() == Tree.Kind.NEW_CLASS) { + receiverTree = ((NewClassTree) tree).getEnclosingExpression(); + if (receiverTree == null && ((NewClassTree) tree).getClassBody() == null) { + TypeMirror t = TreeUtils.elementFromUse((NewClassTree) tree).getReceiverType(); + if (t instanceof DeclaredType) { + return (DeclaredType) t; + } + return null; + } + } else { + receiverTree = TreeUtils.getReceiverTree(tree); + } + + if (receiverTree == null) { + return null; } - receiverType = (DeclaredType) enclosing; - } - if (receiverType == null) { - executableType = (ExecutableType) ele.asType(); - } - if (executableType == null) { - javax.lang.model.util.Types types = context.env.getTypeUtils(); - executableType = (ExecutableType) types.asMemberOf(receiverType, ele); - } - } else { - executableType = (ExecutableType) TreeUtils.elementFromUse(expressionTree).asType(); + TypeMirror type = TreeUtils.typeOf(receiverTree); + if (type.getKind() == TypeKind.TYPEVAR) { + return (DeclaredType) ((TypeVariable) type).getUpperBound(); + } + return type.getKind() == TypeKind.DECLARED ? (DeclaredType) type : null; } - // Adapt to class type arguments. - if (expressionTree.getKind() == Tree.Kind.NEW_CLASS - && !TreeUtils.isDiamondTree(expressionTree)) { - NewClassTree newClassTree = (NewClassTree) expressionTree; - List typeArgs = TreeUtils.getTypeArgumentsToNewClassTree(newClassTree); - if (!typeArgs.isEmpty()) { - ExecutableElement e = TreeUtils.elementFromUse(newClassTree); - List typeParams = - ElementUtils.enclosingTypeElement(e).getTypeParameters(); + /** + * Return ExecutableType of the method invocation or new class tree adapted to the call site. + * + * @param expressionTree a method invocation or new class tree + * @param context the context + * @return ExecutableType of the method invocation or new class tree adapted to the call site + */ + public static ExecutableType getTypeOfMethodAdaptedToUse( + ExpressionTree expressionTree, Java8InferenceContext context) { + assert expressionTree.getKind() == Kind.NEW_CLASS + || expressionTree.getKind() == Kind.METHOD_INVOCATION; + + ExecutableElement ele = (ExecutableElement) TreeUtils.elementFromUse(expressionTree); + ExecutableType executableType = null; + // First adapt to receiver + if (!ElementUtils.isStatic(ele)) { + DeclaredType receiverType = getReceiverType(expressionTree); + if (receiverType == null && expressionTree.getKind() == Kind.METHOD_INVOCATION) { + receiverType = context.enclosingType; + } else if (receiverType != null) { + receiverType = (DeclaredType) context.types.capture((Type) receiverType); + } + + while (receiverType != null + && context.types.asSuper( + (Type) receiverType, (Symbol) ele.getEnclosingElement()) + == null) { + TypeMirror enclosing = receiverType.getEnclosingType(); + if (enclosing == null || enclosing.getKind() != TypeKind.DECLARED) { + if (expressionTree.getKind() == Tree.Kind.NEW_CLASS) { + // No receiver for the constructor. + executableType = (ExecutableType) ele.asType(); + } else { + throw new BugInCF("Method not found"); + } + } + receiverType = (DeclaredType) enclosing; + } + if (receiverType == null) { + executableType = (ExecutableType) ele.asType(); + } + if (executableType == null) { + javax.lang.model.util.Types types = context.env.getTypeUtils(); + executableType = (ExecutableType) types.asMemberOf(receiverType, ele); + } + } else { + executableType = (ExecutableType) TreeUtils.elementFromUse(expressionTree).asType(); + } + + // Adapt to class type arguments. + if (expressionTree.getKind() == Tree.Kind.NEW_CLASS + && !TreeUtils.isDiamondTree(expressionTree)) { + NewClassTree newClassTree = (NewClassTree) expressionTree; + List typeArgs = TreeUtils.getTypeArgumentsToNewClassTree(newClassTree); + if (!typeArgs.isEmpty()) { + ExecutableElement e = TreeUtils.elementFromUse(newClassTree); + List typeParams = + ElementUtils.enclosingTypeElement(e).getTypeParameters(); + List typeVariables = new ArrayList<>(); + for (TypeParameterElement typeParam : typeParams) { + typeVariables.add((TypeVariable) typeParam.asType()); + } + List args = new ArrayList<>(); + for (Tree arg : typeArgs) { + args.add(TreeUtils.typeOf(arg)); + } + executableType = + (ExecutableType) + TypesUtils.substitute( + executableType, typeVariables, args, context.env); + } else if (TypesUtils.isRaw(TreeUtils.typeOf(newClassTree))) { + executableType = (ExecutableType) context.types.erasure((Type) executableType); + } + } + // Adapt to explicit method type arguments. + List typeArgs; + if (expressionTree.getKind() == Kind.METHOD_INVOCATION) { + typeArgs = ((MethodInvocationTree) expressionTree).getTypeArguments(); + } else { + typeArgs = ((NewClassTree) expressionTree).getTypeArguments(); + } + if (typeArgs.isEmpty()) { + return executableType; + } + List typeParams = ele.getTypeParameters(); List typeVariables = new ArrayList<>(); for (TypeParameterElement typeParam : typeParams) { - typeVariables.add((TypeVariable) typeParam.asType()); + typeVariables.add((TypeVariable) typeParam.asType()); } + List args = new ArrayList<>(); for (Tree arg : typeArgs) { - args.add(TreeUtils.typeOf(arg)); + args.add(TreeUtils.typeOf(arg)); } - executableType = - (ExecutableType) + + return (ExecutableType) TypesUtils.substitute(executableType, typeVariables, args, context.env); - } else if (TypesUtils.isRaw(TreeUtils.typeOf(newClassTree))) { - executableType = (ExecutableType) context.types.erasure((Type) executableType); - } - } - // Adapt to explicit method type arguments. - List typeArgs; - if (expressionTree.getKind() == Kind.METHOD_INVOCATION) { - typeArgs = ((MethodInvocationTree) expressionTree).getTypeArguments(); - } else { - typeArgs = ((NewClassTree) expressionTree).getTypeArguments(); - } - if (typeArgs.isEmpty()) { - return executableType; - } - List typeParams = ele.getTypeParameters(); - List typeVariables = new ArrayList<>(); - for (TypeParameterElement typeParam : typeParams) { - typeVariables.add((TypeVariable) typeParam.asType()); } - List args = new ArrayList<>(); - for (Tree arg : typeArgs) { - args.add(TreeUtils.typeOf(arg)); + /** + * Returns the least upper bound of {@code tm1} and {@code tm2}. + * + * @param processingEnv the processing environment + * @param tm1 a type + * @param tm2 a type + * @return the least upper bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror lub( + ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + Types types = Types.instance(javacEnv.getContext()); + + return types.lub(t1, t2); } - return (ExecutableType) TypesUtils.substitute(executableType, typeVariables, args, context.env); - } - - /** - * Returns the least upper bound of {@code tm1} and {@code tm2}. - * - * @param processingEnv the processing environment - * @param tm1 a type - * @param tm2 a type - * @return the least upper bound of {@code tm1} and {@code tm2} - */ - public static TypeMirror lub( - ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { - Type t1 = TypeAnnotationUtils.unannotatedType(tm1); - Type t2 = TypeAnnotationUtils.unannotatedType(tm2); - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - Types types = Types.instance(javacEnv.getContext()); - - return types.lub(t1, t2); - } - - /** - * Returns the greatest lower bound of {@code tm1} and {@code tm2}. - * - * @param processingEnv the processing environment - * @param tm1 a type - * @param tm2 a type - * @return the greatest lower bound of {@code tm1} and {@code tm2} - */ - public static TypeMirror glb( - ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { - Type t1 = TypeAnnotationUtils.unannotatedType(tm1); - Type t2 = TypeAnnotationUtils.unannotatedType(tm2); - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - Types types = Types.instance(javacEnv.getContext()); - - return types.glb(t1, t2); - } - - /** - * If a mapping, theta, for {@code invocation} doesn't exist create it by: - * - *

          Creates inference variables for the type parameters to {@code methodType} for a particular - * {@code invocation}. Initializes the bounds of the variables. Returns a mapping from type - * variables to newly created variables. - * - *

          Otherwise, returns the previously created mapping. - * - * @param invocation method or constructor invocation - * @param methodType type of generic method - * @param context Java8InferenceContext - * @return a mapping of the type variables of {@code methodType} to inference variables - */ - public Theta createThetaForInvocation( - ExpressionTree invocation, InvocationType methodType, Java8InferenceContext context) { - if (context.maps.containsKey(invocation)) { - return context.maps.get(invocation); + /** + * Returns the greatest lower bound of {@code tm1} and {@code tm2}. + * + * @param processingEnv the processing environment + * @param tm1 a type + * @param tm2 a type + * @return the greatest lower bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror glb( + ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + Types types = Types.instance(javacEnv.getContext()); + + return types.glb(t1, t2); } - Theta map = new Theta(); - // Create inference variables for the type parameters to methodType + /** + * If a mapping, theta, for {@code invocation} doesn't exist create it by: + * + *

          Creates inference variables for the type parameters to {@code methodType} for a particular + * {@code invocation}. Initializes the bounds of the variables. Returns a mapping from type + * variables to newly created variables. + * + *

          Otherwise, returns the previously created mapping. + * + * @param invocation method or constructor invocation + * @param methodType type of generic method + * @param context Java8InferenceContext + * @return a mapping of the type variables of {@code methodType} to inference variables + */ + public Theta createThetaForInvocation( + ExpressionTree invocation, InvocationType methodType, Java8InferenceContext context) { + if (context.maps.containsKey(invocation)) { + return context.maps.get(invocation); + } + Theta map = new Theta(); + + // Create inference variables for the type parameters to methodType - for (AnnotatedTypeVariable pl : methodType.getAnnotatedTypeVariables()) { - @SuppressWarnings("interning:interned.object.creation") - Variable al = new @Interned Variable(pl, pl.getUnderlyingType(), invocation, context, map); - map.put(pl.getUnderlyingType(), al); - } - if (TreeUtils.isDiamondTree(invocation)) { - // If the invocation is a diamondTree, such as new List<>(...), then create variables - // for the class type parameters, too. - Element classEle = - ElementUtils.enclosingTypeElement(TreeUtils.elementFromUse((NewClassTree) invocation)); - if (classEle.getSimpleName().contentEquals("")) { - classEle = - ((DeclaredType) TreeUtils.typeOf(((NewClassTree) invocation).getIdentifier())) - .asElement(); - } - DeclaredType classTypeMirror = (DeclaredType) classEle.asType(); - - AnnotatedDeclaredType classType = - (AnnotatedDeclaredType) typeFactory.getAnnotatedType(classEle); - - Iterator iter = classType.getTypeArguments().iterator(); - - for (TypeMirror typeMirror : classTypeMirror.getTypeArguments()) { - if (typeMirror.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF("Expected type variable, found: %s", typeMirror); + for (AnnotatedTypeVariable pl : methodType.getAnnotatedTypeVariables()) { + @SuppressWarnings("interning:interned.object.creation") + Variable al = + new @Interned Variable(pl, pl.getUnderlyingType(), invocation, context, map); + map.put(pl.getUnderlyingType(), al); + } + if (TreeUtils.isDiamondTree(invocation)) { + // If the invocation is a diamondTree, such as new List<>(...), then create variables + // for the class type parameters, too. + Element classEle = + ElementUtils.enclosingTypeElement( + TreeUtils.elementFromUse((NewClassTree) invocation)); + if (classEle.getSimpleName().contentEquals("")) { + classEle = + ((DeclaredType) + TreeUtils.typeOf( + ((NewClassTree) invocation).getIdentifier())) + .asElement(); + } + DeclaredType classTypeMirror = (DeclaredType) classEle.asType(); + + AnnotatedDeclaredType classType = + (AnnotatedDeclaredType) typeFactory.getAnnotatedType(classEle); + + Iterator iter = classType.getTypeArguments().iterator(); + + for (TypeMirror typeMirror : classTypeMirror.getTypeArguments()) { + if (typeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Expected type variable, found: %s", typeMirror); + } + TypeVariable pl = (TypeVariable) typeMirror; + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + Variable al = new @Interned Variable(atv, pl, invocation, context, map); + map.put(pl, al); + } } - TypeVariable pl = (TypeVariable) typeMirror; - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); - @SuppressWarnings("interning:interned.object.creation") - Variable al = new @Interned Variable(atv, pl, invocation, context, map); - map.put(pl, al); - } - } - // Initialize variable bounds. - for (Variable v : map.values()) { - v.initialBounds(map); - } - context.maps.put(invocation, map); - return map; - } - - /** - * If a mapping, theta, for {@code memRef} doesn't exist create it by: - * - *

          Creates inference variables for the type parameters to {@code compileTimeDecl} for a - * particular method reference. Initializes the bounds of the variables. Returns a mapping from - * type variables to newly created variables. - * - *

          Otherwise, returns the previously created mapping. - * - * @param memRef method reference tree - * @param compileTimeDecl type of generic method - * @param context Java8InferenceContext - * @return a mapping of the type variables of {@code compileTimeDecl} to inference variables - */ - public Theta createThetaForMethodReference( - MemberReferenceTree memRef, InvocationType compileTimeDecl, Java8InferenceContext context) { - if (context.maps.containsKey(memRef)) { - return context.maps.get(memRef); + // Initialize variable bounds. + for (Variable v : map.values()) { + v.initialBounds(map); + } + context.maps.put(invocation, map); + return map; } - Theta map = new Theta(); - TypeMirror preColonTreeType = TreeUtils.typeOf(memRef.getQualifierExpression()); - if (TreeUtils.isDiamondMemberReference(memRef) - || TreeUtils.isLikeDiamondMemberReference(memRef)) { - // If memRef is a constructor or method of a generic class whose type argument isn't - // specified such as HashSet::new or HashSet::put - // then add variables for the type arguments to the class. - TypeElement classEle = (TypeElement) ((Type) preColonTreeType).asElement(); - DeclaredType classTypeMirror = (DeclaredType) classEle.asType(); + /** + * If a mapping, theta, for {@code memRef} doesn't exist create it by: + * + *

          Creates inference variables for the type parameters to {@code compileTimeDecl} for a + * particular method reference. Initializes the bounds of the variables. Returns a mapping from + * type variables to newly created variables. + * + *

          Otherwise, returns the previously created mapping. + * + * @param memRef method reference tree + * @param compileTimeDecl type of generic method + * @param context Java8InferenceContext + * @return a mapping of the type variables of {@code compileTimeDecl} to inference variables + */ + public Theta createThetaForMethodReference( + MemberReferenceTree memRef, + InvocationType compileTimeDecl, + Java8InferenceContext context) { + if (context.maps.containsKey(memRef)) { + return context.maps.get(memRef); + } - AnnotatedDeclaredType classType = - (AnnotatedDeclaredType) typeFactory.getAnnotatedType(classTypeMirror.asElement()); + Theta map = new Theta(); + TypeMirror preColonTreeType = TreeUtils.typeOf(memRef.getQualifierExpression()); + if (TreeUtils.isDiamondMemberReference(memRef) + || TreeUtils.isLikeDiamondMemberReference(memRef)) { + // If memRef is a constructor or method of a generic class whose type argument isn't + // specified such as HashSet::new or HashSet::put + // then add variables for the type arguments to the class. + TypeElement classEle = (TypeElement) ((Type) preColonTreeType).asElement(); + DeclaredType classTypeMirror = (DeclaredType) classEle.asType(); + + AnnotatedDeclaredType classType = + (AnnotatedDeclaredType) + typeFactory.getAnnotatedType(classTypeMirror.asElement()); + + if (((Type) preColonTreeType).getTypeArguments().isEmpty()) { + Iterator iter = classType.getTypeArguments().iterator(); + for (TypeMirror typeMirror : classTypeMirror.getTypeArguments()) { + if (typeMirror.getKind() != TypeKind.TYPEVAR) { + throw new BugInCF("Expected type variable, found: %s", typeMirror); + } + TypeVariable pl = (TypeVariable) typeMirror; + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + Variable al = new @Interned Variable(atv, pl, memRef, context, map); + map.put(pl, al); + } + } + } - if (((Type) preColonTreeType).getTypeArguments().isEmpty()) { - Iterator iter = classType.getTypeArguments().iterator(); - for (TypeMirror typeMirror : classTypeMirror.getTypeArguments()) { - if (typeMirror.getKind() != TypeKind.TYPEVAR) { - throw new BugInCF("Expected type variable, found: %s", typeMirror); - } - TypeVariable pl = (TypeVariable) typeMirror; - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); - @SuppressWarnings("interning:interned.object.creation") - Variable al = new @Interned Variable(atv, pl, memRef, context, map); - map.put(pl, al); + // Create inference variables for the type parameters to compileTypeDecl + if (memRef.getTypeArguments() == null && compileTimeDecl.hasTypeVariables()) { + Iterator iter1 = + compileTimeDecl.getAnnotatedTypeVariables().iterator(); + for (TypeVariable pl : compileTimeDecl.getTypeVariables()) { + @SuppressWarnings("interning:interned.object.creation") + Variable al = new @Interned Variable(iter1.next(), pl, memRef, context, map); + map.put(pl, al); + } + } + for (Variable v : map.values()) { + v.initialBounds(map); } - } + context.maps.put(memRef, map); + return map; } - // Create inference variables for the type parameters to compileTypeDecl - if (memRef.getTypeArguments() == null && compileTimeDecl.hasTypeVariables()) { - Iterator iter1 = - compileTimeDecl.getAnnotatedTypeVariables().iterator(); - for (TypeVariable pl : compileTimeDecl.getTypeVariables()) { - @SuppressWarnings("interning:interned.object.creation") - Variable al = new @Interned Variable(iter1.next(), pl, memRef, context, map); - map.put(pl, al); - } - } - for (Variable v : map.values()) { - v.initialBounds(map); - } - context.maps.put(memRef, map); - return map; - } - - /** - * If a mapping, theta, for {@code lambda} doesn't exist create it by: - * - *

          Creates inference variables for the type parameters to the functional inference of the - * lambda. Initializes the bounds of the variables. Returns a mapping from type variables to newly - * created variables. - * - *

          Otherwise, returns the previously created mapping. - * - * @param lambda lambda expression tree - * @param functionalInterface functional interface of the lambda - * @return a mapping of the type variables of {@code compileTimeDecl} to inference variables - */ - public Theta createThetaForLambda(LambdaExpressionTree lambda, AbstractType functionalInterface) { - if (context.maps.containsKey(lambda)) { - return context.maps.get(lambda); - } - TypeElement typeEle = - (TypeElement) ((DeclaredType) functionalInterface.getJavaType()).asElement(); - AnnotatedDeclaredType classType = typeFactory.getAnnotatedType(typeEle); - - Iterator iter = classType.getTypeArguments().iterator(); - Theta map = new Theta(); - for (TypeParameterElement param : typeEle.getTypeParameters()) { - TypeVariable typeVar = (TypeVariable) param.asType(); - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); - @SuppressWarnings("interning:interned.object.creation") - Variable ai = new @Interned Variable(atv, typeVar, lambda, context, map); - map.put(typeVar, ai); - } - for (Variable v : map.values()) { - v.initialBounds(map); - } - context.maps.put(lambda, map); - return map; - } - - /** - * Creates capture variables for variables introduced by a capture bounds. The new variables - * correspond to the type parameters of {@code capturedType}. - * - * @param tree invocation tree that created the capture bound - * @param capturedType type that should be captured - * @return a mapping of the type variables of {@code capturedType} to capture inference variables - */ - public Theta createThetaForCapture(ExpressionTree tree, AbstractType capturedType) { - // Don't save this theta, because there is also a noncapture theta for this tree. - DeclaredType underlying = (DeclaredType) capturedType.getJavaType(); - TypeElement ele = TypesUtils.getTypeElement(underlying); - AnnotatedDeclaredType classType = typeFactory.getAnnotatedType(ele); - Iterator iter = classType.getTypeArguments().iterator(); - Theta map = new Theta(); - for (TypeParameterElement pEle : ele.getTypeParameters()) { - TypeVariable pl = (TypeVariable) pEle.asType(); - AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); - @SuppressWarnings("interning:interned.object.creation") - CaptureVariable al = new @Interned CaptureVariable(atv, pl, tree, context, map); - map.put(pl, al); - } - for (Variable v : map.values()) { - v.initialBounds(map); + /** + * If a mapping, theta, for {@code lambda} doesn't exist create it by: + * + *

          Creates inference variables for the type parameters to the functional inference of the + * lambda. Initializes the bounds of the variables. Returns a mapping from type variables to + * newly created variables. + * + *

          Otherwise, returns the previously created mapping. + * + * @param lambda lambda expression tree + * @param functionalInterface functional interface of the lambda + * @return a mapping of the type variables of {@code compileTimeDecl} to inference variables + */ + public Theta createThetaForLambda( + LambdaExpressionTree lambda, AbstractType functionalInterface) { + if (context.maps.containsKey(lambda)) { + return context.maps.get(lambda); + } + TypeElement typeEle = + (TypeElement) ((DeclaredType) functionalInterface.getJavaType()).asElement(); + AnnotatedDeclaredType classType = typeFactory.getAnnotatedType(typeEle); + + Iterator iter = classType.getTypeArguments().iterator(); + Theta map = new Theta(); + for (TypeParameterElement param : typeEle.getTypeParameters()) { + TypeVariable typeVar = (TypeVariable) param.asType(); + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + Variable ai = new @Interned Variable(atv, typeVar, lambda, context, map); + map.put(typeVar, ai); + } + for (Variable v : map.values()) { + v.initialBounds(map); + } + context.maps.put(lambda, map); + return map; } - return map; - } - - /** - * Returns the type of the method or constructor invocation adapted to its arguments. This type - * may include inference variables. - * - * @param invocation method or constructor invocation - * @return the type of the method or constructor invocation adapted to its arguments - */ - public InvocationType getTypeOfMethodAdaptedToUse(ExpressionTree invocation) { - AnnotatedExecutableType executableType; - if (invocation.getKind() == Kind.METHOD_INVOCATION) { - executableType = - typeFactory.methodFromUseWithoutTypeArgInference((MethodInvocationTree) invocation) - .executableType; - } else { - executableType = - typeFactory.constructorFromUseWithoutTypeArgInference((NewClassTree) invocation) - .executableType; + + /** + * Creates capture variables for variables introduced by a capture bounds. The new variables + * correspond to the type parameters of {@code capturedType}. + * + * @param tree invocation tree that created the capture bound + * @param capturedType type that should be captured + * @return a mapping of the type variables of {@code capturedType} to capture inference + * variables + */ + public Theta createThetaForCapture(ExpressionTree tree, AbstractType capturedType) { + // Don't save this theta, because there is also a noncapture theta for this tree. + DeclaredType underlying = (DeclaredType) capturedType.getJavaType(); + TypeElement ele = TypesUtils.getTypeElement(underlying); + AnnotatedDeclaredType classType = typeFactory.getAnnotatedType(ele); + Iterator iter = classType.getTypeArguments().iterator(); + Theta map = new Theta(); + for (TypeParameterElement pEle : ele.getTypeParameters()) { + TypeVariable pl = (TypeVariable) pEle.asType(); + AnnotatedTypeVariable atv = (AnnotatedTypeVariable) iter.next(); + @SuppressWarnings("interning:interned.object.creation") + CaptureVariable al = new @Interned CaptureVariable(atv, pl, tree, context, map); + map.put(pl, al); + } + for (Variable v : map.values()) { + v.initialBounds(map); + } + return map; } - return new InvocationType( - executableType, getTypeOfMethodAdaptedToUse(invocation, context), invocation, context); - } - - /** - * Returns the compile-time declaration of the method reference that is the method to which the - * expression refers. See JLS section - * 15.13.1 for a complete definition. - * - *

          The type of a member reference is a functional interface. The function type of a member - * reference is the type of the single abstract method declared by the functional interface. The - * compile-time declaration type is the type of the actual method referenced by the method - * reference, i.e. the method that is actually being referenced. - * - *

          For example, - * - *

          {@code
          -   * static class MyClass {
          -   *   String field;
          -   *   public static int compareByField(MyClass a, MyClass b) { ... }
          -   * }
          -   * Comparator func = MyClass::compareByField;
          -   * }
          - * - *

          The function type is {@code compare(Comparator this, MyClass o1, MyClass o2)} where - * as the compile-time declaration type is {@code compareByField(MyClass a, MyClass b)}. - * - * @param memRef method reference tree - * @return the compile-time declaration of the method reference - */ - public InvocationType compileTimeDeclarationType(MemberReferenceTree memRef) { - // The tree before :: is an expression or type use. - final ExpressionTree preColonTree = memRef.getQualifierExpression(); - final MemberReferenceKind memRefKind = MemberReferenceKind.getMemberReferenceKind(memRef); - AnnotatedTypeMirror enclosingType; - - if (memRef.getMode() == ReferenceMode.NEW) { - enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); - if (TreeUtils.isDiamondMemberReference(memRef)) { - // The member reference is HashMap::new so the type arguments for HashMap must be - // inferred. - // So use the type declared type. - TypeElement typeEle = TypesUtils.getTypeElement(enclosingType.getUnderlyingType()); - enclosingType = typeFactory.getAnnotatedType(typeEle); - } - } else if (memRefKind == MemberReferenceKind.UNBOUND) { - enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); - if (enclosingType.getKind() == TypeKind.DECLARED - && ((AnnotatedDeclaredType) enclosingType).isUnderlyingTypeRaw()) { - TypeElement typeEle = TypesUtils.getTypeElement(enclosingType.getUnderlyingType()); - enclosingType = typeFactory.getAnnotatedType(typeEle); - } - } else if (memRefKind == MemberReferenceKind.STATIC) { - // The tree before :: is a type tree. - enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); - } else { // memRefKind == MemberReferenceKind.BOUND - // The tree before :: is an expression. - enclosingType = typeFactory.getAnnotatedType(preColonTree); + + /** + * Returns the type of the method or constructor invocation adapted to its arguments. This type + * may include inference variables. + * + * @param invocation method or constructor invocation + * @return the type of the method or constructor invocation adapted to its arguments + */ + public InvocationType getTypeOfMethodAdaptedToUse(ExpressionTree invocation) { + AnnotatedExecutableType executableType; + if (invocation.getKind() == Kind.METHOD_INVOCATION) { + executableType = + typeFactory.methodFromUseWithoutTypeArgInference( + (MethodInvocationTree) invocation) + .executableType; + } else { + executableType = + typeFactory.constructorFromUseWithoutTypeArgInference((NewClassTree) invocation) + .executableType; + } + return new InvocationType( + executableType, + getTypeOfMethodAdaptedToUse(invocation, context), + invocation, + context); } - // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference - ExecutableElement compileTimeDeclaration = - (ExecutableElement) TreeUtils.elementFromTree(memRef); + /** + * Returns the compile-time declaration of the method reference that is the method to which the + * expression refers. See JLS section + * 15.13.1 for a complete definition. + * + *

          The type of a member reference is a functional interface. The function type of a member + * reference is the type of the single abstract method declared by the functional interface. The + * compile-time declaration type is the type of the actual method referenced by the method + * reference, i.e. the method that is actually being referenced. + * + *

          For example, + * + *

          {@code
          +     * static class MyClass {
          +     *   String field;
          +     *   public static int compareByField(MyClass a, MyClass b) { ... }
          +     * }
          +     * Comparator func = MyClass::compareByField;
          +     * }
          + * + *

          The function type is {@code compare(Comparator this, MyClass o1, MyClass o2)} + * where as the compile-time declaration type is {@code compareByField(MyClass a, MyClass b)}. + * + * @param memRef method reference tree + * @return the compile-time declaration of the method reference + */ + public InvocationType compileTimeDeclarationType(MemberReferenceTree memRef) { + // The tree before :: is an expression or type use. + final ExpressionTree preColonTree = memRef.getQualifierExpression(); + final MemberReferenceKind memRefKind = MemberReferenceKind.getMemberReferenceKind(memRef); + AnnotatedTypeMirror enclosingType; + + if (memRef.getMode() == ReferenceMode.NEW) { + enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + if (TreeUtils.isDiamondMemberReference(memRef)) { + // The member reference is HashMap::new so the type arguments for HashMap must be + // inferred. + // So use the type declared type. + TypeElement typeEle = TypesUtils.getTypeElement(enclosingType.getUnderlyingType()); + enclosingType = typeFactory.getAnnotatedType(typeEle); + } + } else if (memRefKind == MemberReferenceKind.UNBOUND) { + enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + if (enclosingType.getKind() == TypeKind.DECLARED + && ((AnnotatedDeclaredType) enclosingType).isUnderlyingTypeRaw()) { + TypeElement typeEle = TypesUtils.getTypeElement(enclosingType.getUnderlyingType()); + enclosingType = typeFactory.getAnnotatedType(typeEle); + } + } else if (memRefKind == MemberReferenceKind.STATIC) { + // The tree before :: is a type tree. + enclosingType = typeFactory.getAnnotatedTypeFromTypeTree(preColonTree); + } else { // memRefKind == MemberReferenceKind.BOUND + // The tree before :: is an expression. + enclosingType = typeFactory.getAnnotatedType(preColonTree); + } - if (enclosingType.getKind() == TypeKind.DECLARED) { - enclosingType = AbstractType.makeGround((AnnotatedDeclaredType) enclosingType, typeFactory); - } - // The type of the compileTimeDeclaration if it were invoked with a receiver expression - // of type {@code type} - AnnotatedExecutableType compileTimeType = - typeFactory.methodFromUseWithoutTypeArgInference( - memRef, compileTimeDeclaration, enclosingType) - .executableType; - - return new InvocationType( - compileTimeType, compileTimeType.getUnderlyingType(), memRef, context); - } - - /** - * Returns the pair of {@code a} as the least upper bound of {@code a} and {@code b} and {@code b} - * as the least upper bound of {@code a} and {@code b}. - * - * @param a type - * @param b type - * @return the pair of {@code a} as the least upper bound of {@code a} and {@code b} and * {@code - * b} as the least upper bound of {@code a} and {@code b} - */ - public IPair getParameterizedSupers(AbstractType a, AbstractType b) { - TypeMirror aTypeMirror = a.getJavaType(); - TypeMirror bTypeMirror = b.getJavaType(); - // com.sun.tools.javac.comp.Infer#getParameterizedSupers - TypeMirror lubResult = lub(context.env, aTypeMirror, bTypeMirror); - if (!TypesUtils.isParameterizedType(lubResult) || lubResult.getKind() == TypeKind.ARRAY) { - return null; - } + // The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference + ExecutableElement compileTimeDeclaration = + (ExecutableElement) TreeUtils.elementFromTree(memRef); - Type asSuperOfA = context.types.asSuper((Type) aTypeMirror, ((Type) lubResult).asElement()); - Type asSuperOfB = context.types.asSuper((Type) bTypeMirror, ((Type) lubResult).asElement()); - - return IPair.of(a.asSuper(asSuperOfA), b.asSuper(asSuperOfB)); - } - - /** - * Returns the type of {@code element} using the type variable to inference variable mapping, - * {@code map}. - * - * @param element some element - * @param map type parameter to inference variables to use - * @return the type of {@code element} - */ - public AbstractType getTypeOfElement(Element element, Theta map) { - AnnotatedTypeMirror atm = typeFactory.getAnnotatedType(element).asUse(); - return InferenceType.create(atm, element.asType(), map, context); - } - - /** - * Returns the type of {@code pEle} using the type variable to inference variable mapping, {@code - * map}. - * - * @param pEle some element - * @param map type parameter to inference variables to use - * @return the type of {@code pEle} - */ - public AbstractType getTypeOfBound(TypeParameterElement pEle, Theta map) { - AnnotatedTypeVariable atm = (AnnotatedTypeVariable) typeFactory.getAnnotatedType(pEle); - return InferenceType.create( - atm.getUpperBound(), ((TypeVariable) pEle.asType()).getUpperBound(), map, context); - } - - /** - * Return the proper type for object. - * - * @return the proper type for object - */ - public ProperType getObject() { - TypeMirror objectTypeMirror = - TypesUtils.typeFromClass(Object.class, context.modelTypes, context.env.getElementUtils()); - AnnotatedTypeMirror object = - AnnotatedTypeMirror.createType(objectTypeMirror, typeFactory, false); - object.addMissingAnnotations(typeFactory.getQualifierHierarchy().getTopAnnotations()); - return new ProperType(object, objectTypeMirror, context); - } - - /** - * Return the least upper bounds of {@code properTypes}. - * - * @param properTypes types to lub - * @return the least upper bounds of {@code properTypes} - */ - public ProperType lub(Set properTypes) { - if (properTypes.isEmpty()) { - return null; - } - TypeMirror tiTypeMirror = null; - AnnotatedTypeMirror ti = null; - for (ProperType liProperType : properTypes) { - AnnotatedTypeMirror li = liProperType.getAnnotatedType(); - TypeMirror liTypeMirror = liProperType.getJavaType(); - if (ti == null) { - ti = li; - tiTypeMirror = liTypeMirror; - } else { - tiTypeMirror = lub(context.env, tiTypeMirror, liTypeMirror); - ti = AnnotatedTypes.leastUpperBound(typeFactory, ti, li, tiTypeMirror); - } - } - return new ProperType(ti, tiTypeMirror, context); - } - - /** - * Returns the greatest lower bound of {@code abstractTypes}. - * - * @param abstractTypes types to glb - * @return the greatest upper bounds of {@code abstractTypes} - */ - public AbstractType glb(Set abstractTypes) { - AbstractType ti = null; - for (AbstractType liProperType : abstractTypes) { - AbstractType li = liProperType; - if (ti == null) { - ti = li; - } else { - ti = glb(ti, li); - } + if (enclosingType.getKind() == TypeKind.DECLARED) { + enclosingType = + AbstractType.makeGround((AnnotatedDeclaredType) enclosingType, typeFactory); + } + // The type of the compileTimeDeclaration if it were invoked with a receiver expression + // of type {@code type} + AnnotatedExecutableType compileTimeType = + typeFactory.methodFromUseWithoutTypeArgInference( + memRef, compileTimeDeclaration, enclosingType) + .executableType; + + return new InvocationType( + compileTimeType, compileTimeType.getUnderlyingType(), memRef, context); } - return ti; - } - - /** - * Returns the greatest lower bound of {@code a} and {@code b}. - * - * @param a type to glb - * @param b type to glb - * @return the greatest lower bound of {@code a} and {@code b} - */ - public AbstractType glb(AbstractType a, AbstractType b) { - Type aJavaType = (Type) a.getJavaType(); - Type bJavaType = (Type) b.getJavaType(); - TypeMirror glb = TypesUtils.greatestLowerBound(aJavaType, bJavaType, context.env); - - AnnotatedTypeMirror aAtm = a.getAnnotatedType(); - AnnotatedTypeMirror bAtm = b.getAnnotatedType(); - AnnotatedTypeMirror glbATM = AnnotatedTypes.annotatedGLB(typeFactory, aAtm, bAtm); - if (context.types.isSameType(aJavaType, (Type) glb)) { - return a.create(glbATM, glb); + + /** + * Returns the pair of {@code a} as the least upper bound of {@code a} and {@code b} and {@code + * b} as the least upper bound of {@code a} and {@code b}. + * + * @param a type + * @param b type + * @return the pair of {@code a} as the least upper bound of {@code a} and {@code b} and * + * {@code b} as the least upper bound of {@code a} and {@code b} + */ + public IPair getParameterizedSupers( + AbstractType a, AbstractType b) { + TypeMirror aTypeMirror = a.getJavaType(); + TypeMirror bTypeMirror = b.getJavaType(); + // com.sun.tools.javac.comp.Infer#getParameterizedSupers + TypeMirror lubResult = lub(context.env, aTypeMirror, bTypeMirror); + if (!TypesUtils.isParameterizedType(lubResult) || lubResult.getKind() == TypeKind.ARRAY) { + return null; + } + + Type asSuperOfA = context.types.asSuper((Type) aTypeMirror, ((Type) lubResult).asElement()); + Type asSuperOfB = context.types.asSuper((Type) bTypeMirror, ((Type) lubResult).asElement()); + + return IPair.of(a.asSuper(asSuperOfA), b.asSuper(asSuperOfB)); } - if (context.types.isSameType(bJavaType, (Type) glb)) { - return b.create(glbATM, glb); + /** + * Returns the type of {@code element} using the type variable to inference variable mapping, + * {@code map}. + * + * @param element some element + * @param map type parameter to inference variables to use + * @return the type of {@code element} + */ + public AbstractType getTypeOfElement(Element element, Theta map) { + AnnotatedTypeMirror atm = typeFactory.getAnnotatedType(element).asUse(); + return InferenceType.create(atm, element.asType(), map, context); } - if (a.isInferenceType()) { - return a.create(glbATM, glb); - } else if (b.isInferenceType()) { - return b.create(glbATM, glb); + /** + * Returns the type of {@code pEle} using the type variable to inference variable mapping, + * {@code map}. + * + * @param pEle some element + * @param map type parameter to inference variables to use + * @return the type of {@code pEle} + */ + public AbstractType getTypeOfBound(TypeParameterElement pEle, Theta map) { + AnnotatedTypeVariable atm = (AnnotatedTypeVariable) typeFactory.getAnnotatedType(pEle); + return InferenceType.create( + atm.getUpperBound(), ((TypeVariable) pEle.asType()).getUpperBound(), map, context); } - assert a.isProper() && b.isProper(); - return new ProperType(glbATM, glb, context); - } - - /** - * Return the proper type for RuntimeException. - * - * @return the proper type for RuntimeException - */ - public ProperType getRuntimeException() { - AnnotatedTypeMirror runtimeEx = - AnnotatedTypeMirror.createType(context.runtimeEx, typeFactory, false); - runtimeEx.addMissingAnnotations(typeFactory.getQualifierHierarchy().getTopAnnotations()); - return new ProperType(runtimeEx, context.runtimeEx, context); - } - - /** - * Creates and returns the set of checked exception constraints for the given lambda or method - * reference. - * - * @param expression a lambda or method reference expression - * @param targetType the target type of {@code expression} - * @param map theta - * @return the set of checked exception constraints for the given lambda or method reference - */ - public ConstraintSet getCheckedExceptionConstraints( - ExpressionTree expression, AbstractType targetType, Theta map) { - ConstraintSet constraintSet = new ConstraintSet(); - ExecutableElement ele = TreeUtils.findFunction(expression, context.env); - // The types in the function type's throws clause that are not proper types. - List es = new ArrayList<>(); - List properTypes = new ArrayList<>(); - - AnnotatedExecutableType functionType = - AnnotatedTypes.asMemberOf( - context.modelTypes, context.typeFactory, targetType.getAnnotatedType(), ele); - Iterator iter = functionType.getThrownTypes().iterator(); - for (TypeMirror thrownType : ele.getThrownTypes()) { - AbstractType ei = InferenceType.create(iter.next(), thrownType, map, context); - if (ei.isProper()) { - properTypes.add((ProperType) ei); - } else { - es.add((UseOfVariable) ei); - } + /** + * Return the proper type for object. + * + * @return the proper type for object + */ + public ProperType getObject() { + TypeMirror objectTypeMirror = + TypesUtils.typeFromClass( + Object.class, context.modelTypes, context.env.getElementUtils()); + AnnotatedTypeMirror object = + AnnotatedTypeMirror.createType(objectTypeMirror, typeFactory, false); + object.addMissingAnnotations(typeFactory.getQualifierHierarchy().getTopAnnotations()); + return new ProperType(object, objectTypeMirror, context); } - if (es.isEmpty()) { - return ConstraintSet.TRUE; + + /** + * Return the least upper bounds of {@code properTypes}. + * + * @param properTypes types to lub + * @return the least upper bounds of {@code properTypes} + */ + public ProperType lub(Set properTypes) { + if (properTypes.isEmpty()) { + return null; + } + TypeMirror tiTypeMirror = null; + AnnotatedTypeMirror ti = null; + for (ProperType liProperType : properTypes) { + AnnotatedTypeMirror li = liProperType.getAnnotatedType(); + TypeMirror liTypeMirror = liProperType.getJavaType(); + if (ti == null) { + ti = li; + tiTypeMirror = liTypeMirror; + } else { + tiTypeMirror = lub(context.env, tiTypeMirror, liTypeMirror); + ti = AnnotatedTypes.leastUpperBound(typeFactory, ti, li, tiTypeMirror); + } + } + return new ProperType(ti, tiTypeMirror, context); } - List thrownTypes; - List thrownTypeMirrors; - if (expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { - thrownTypeMirrors = - CheckedExceptionsUtil.thrownCheckedExceptions((LambdaExpressionTree) expression, context); - thrownTypes = - CheckedExceptionsUtil.thrownCheckedExceptionsATM( - (LambdaExpressionTree) expression, context); - } else { - thrownTypeMirrors = - TypesUtils.findFunctionType(TreeUtils.typeOf(expression), context.env).getThrownTypes(); - thrownTypes = - compileTimeDeclarationType((MemberReferenceTree) expression) - .getAnnotatedType() - .getThrownTypes(); - if (thrownTypes.size() != thrownTypeMirrors.size()) { - // TODO: the thrown types are not stored in the ExecutableElements, so the above method - // doesn't find any thrown types. Below gets the types thrown type from the ExecutableType - // and just adds default annotations. This is just a work around for this problem. We need - // to figure out how to get the type with the correct annotations. - List thrownTypesNew = new ArrayList<>(thrownTypeMirrors.size()); - for (TypeMirror thrown : thrownTypeMirrors) { - AnnotatedTypeMirror thrownATM = - AnnotatedTypeMirror.createType(thrown, context.typeFactory, false); - context.typeFactory.addDefaultAnnotations(thrownATM); - thrownTypesNew.add(thrownATM); + + /** + * Returns the greatest lower bound of {@code abstractTypes}. + * + * @param abstractTypes types to glb + * @return the greatest upper bounds of {@code abstractTypes} + */ + public AbstractType glb(Set abstractTypes) { + AbstractType ti = null; + for (AbstractType liProperType : abstractTypes) { + AbstractType li = liProperType; + if (ti == null) { + ti = li; + } else { + ti = glb(ti, li); + } } - thrownTypes = thrownTypesNew; - } + return ti; } - Iterator iter2 = thrownTypes.iterator(); - for (TypeMirror xi : thrownTypeMirrors) { - boolean isSubtypeOfProper = false; - for (ProperType properType : properTypes) { - if (context.env.getTypeUtils().isSubtype(xi, properType.getJavaType())) { - isSubtypeOfProper = true; + /** + * Returns the greatest lower bound of {@code a} and {@code b}. + * + * @param a type to glb + * @param b type to glb + * @return the greatest lower bound of {@code a} and {@code b} + */ + public AbstractType glb(AbstractType a, AbstractType b) { + Type aJavaType = (Type) a.getJavaType(); + Type bJavaType = (Type) b.getJavaType(); + TypeMirror glb = TypesUtils.greatestLowerBound(aJavaType, bJavaType, context.env); + + AnnotatedTypeMirror aAtm = a.getAnnotatedType(); + AnnotatedTypeMirror bAtm = b.getAnnotatedType(); + AnnotatedTypeMirror glbATM = AnnotatedTypes.annotatedGLB(typeFactory, aAtm, bAtm); + if (context.types.isSameType(aJavaType, (Type) glb)) { + return a.create(glbATM, glb); } - } - if (!isSubtypeOfProper) { - for (UseOfVariable ei : es) { - constraintSet.add( - new Typing( - new ProperType(iter2.next(), xi, context), ei, TypeConstraint.Kind.SUBTYPE)); - ei.setHasThrowsBound(true); + + if (context.types.isSameType(bJavaType, (Type) glb)) { + return b.create(glbATM, glb); } - } - } - return constraintSet; - } - - /** - * Creates a wildcard using the upper and lower bounds provided. - * - * @param lowerBound a proper type or null - * @param upperBound an abstract type or null - * @return a wildcard with the provided upper and lower bounds - */ - public ProperType createWildcard(ProperType lowerBound, AbstractType upperBound) { - TypeMirror wildcard = - TypesUtils.createWildcard( - lowerBound == null ? null : lowerBound.getJavaType(), - upperBound == null ? null : upperBound.getJavaType(), - context.env.getTypeUtils()); - AnnotatedWildcardType wildcardAtm = - (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wildcard, typeFactory, false); - if (lowerBound != null) { - wildcardAtm.setSuperBound(lowerBound.getAnnotatedType()); - } - if (upperBound != null) { - wildcardAtm.setExtendsBound(upperBound.getAnnotatedType()); + if (a.isInferenceType()) { + return a.create(glbATM, glb); + } else if (b.isInferenceType()) { + return b.create(glbATM, glb); + } + + assert a.isProper() && b.isProper(); + return new ProperType(glbATM, glb, context); } - return new ProperType(wildcardAtm, wildcard, context); - } - - /** - * Creates a fresh type variable using the upper and lower bounds provided. - * - * @param lowerBound a proper type or null - * @param lowerBoundAnnos annotations to use if {@code lowerBound} is null - * @param upperBound an abstract type or null - * @param upperBoundAnnos annotations to use if {@code upperBound} is null - * @return a fresh type variable with the provided upper and lower bounds - */ - public AbstractType createFreshTypeVariable( - ProperType lowerBound, - Set lowerBoundAnnos, - AbstractType upperBound, - Set upperBoundAnnos) { - TypeMirror freshTypeVariable = - TypesUtils.freshTypeVariable( - upperBound == null ? null : upperBound.getJavaType(), - lowerBound == null ? null : lowerBound.getJavaType(), - context.env); - AnnotatedTypeVariable typeVariable = - (AnnotatedTypeVariable) - AnnotatedTypeMirror.createType(freshTypeVariable, typeFactory, false); - // Initialize bounds. - typeVariable.getUpperBound(); - typeVariable.getLowerBound(); - if (lowerBound != null) { - typeVariable.setLowerBound(lowerBound.getAnnotatedType()); - } else { - typeVariable.getLowerBound().addAnnotations(lowerBoundAnnos); + + /** + * Return the proper type for RuntimeException. + * + * @return the proper type for RuntimeException + */ + public ProperType getRuntimeException() { + AnnotatedTypeMirror runtimeEx = + AnnotatedTypeMirror.createType(context.runtimeEx, typeFactory, false); + runtimeEx.addMissingAnnotations(typeFactory.getQualifierHierarchy().getTopAnnotations()); + return new ProperType(runtimeEx, context.runtimeEx, context); } - if (upperBound != null) { - typeVariable.setUpperBound(upperBound.getAnnotatedType()); - } else { - typeVariable.getUpperBound().addAnnotations(upperBoundAnnos); + + /** + * Creates and returns the set of checked exception constraints for the given lambda or method + * reference. + * + * @param expression a lambda or method reference expression + * @param targetType the target type of {@code expression} + * @param map theta + * @return the set of checked exception constraints for the given lambda or method reference + */ + public ConstraintSet getCheckedExceptionConstraints( + ExpressionTree expression, AbstractType targetType, Theta map) { + ConstraintSet constraintSet = new ConstraintSet(); + ExecutableElement ele = TreeUtils.findFunction(expression, context.env); + // The types in the function type's throws clause that are not proper types. + List es = new ArrayList<>(); + List properTypes = new ArrayList<>(); + + AnnotatedExecutableType functionType = + AnnotatedTypes.asMemberOf( + context.modelTypes, + context.typeFactory, + targetType.getAnnotatedType(), + ele); + Iterator iter = functionType.getThrownTypes().iterator(); + for (TypeMirror thrownType : ele.getThrownTypes()) { + AbstractType ei = InferenceType.create(iter.next(), thrownType, map, context); + if (ei.isProper()) { + properTypes.add((ProperType) ei); + } else { + es.add((UseOfVariable) ei); + } + } + if (es.isEmpty()) { + return ConstraintSet.TRUE; + } + List thrownTypes; + List thrownTypeMirrors; + if (expression.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { + thrownTypeMirrors = + CheckedExceptionsUtil.thrownCheckedExceptions( + (LambdaExpressionTree) expression, context); + thrownTypes = + CheckedExceptionsUtil.thrownCheckedExceptionsATM( + (LambdaExpressionTree) expression, context); + } else { + thrownTypeMirrors = + TypesUtils.findFunctionType(TreeUtils.typeOf(expression), context.env) + .getThrownTypes(); + thrownTypes = + compileTimeDeclarationType((MemberReferenceTree) expression) + .getAnnotatedType() + .getThrownTypes(); + if (thrownTypes.size() != thrownTypeMirrors.size()) { + // TODO: the thrown types are not stored in the ExecutableElements, so the above + // method + // doesn't find any thrown types. Below gets the types thrown type from the + // ExecutableType + // and just adds default annotations. This is just a work around for this problem. + // We need + // to figure out how to get the type with the correct annotations. + List thrownTypesNew = + new ArrayList<>(thrownTypeMirrors.size()); + for (TypeMirror thrown : thrownTypeMirrors) { + AnnotatedTypeMirror thrownATM = + AnnotatedTypeMirror.createType(thrown, context.typeFactory, false); + context.typeFactory.addDefaultAnnotations(thrownATM); + thrownTypesNew.add(thrownATM); + } + thrownTypes = thrownTypesNew; + } + } + + Iterator iter2 = thrownTypes.iterator(); + for (TypeMirror xi : thrownTypeMirrors) { + boolean isSubtypeOfProper = false; + for (ProperType properType : properTypes) { + if (context.env.getTypeUtils().isSubtype(xi, properType.getJavaType())) { + isSubtypeOfProper = true; + } + } + if (!isSubtypeOfProper) { + for (UseOfVariable ei : es) { + constraintSet.add( + new Typing( + new ProperType(iter2.next(), xi, context), + ei, + TypeConstraint.Kind.SUBTYPE)); + ei.setHasThrowsBound(true); + } + } + } + + return constraintSet; } - context.typeFactory.capturedTypeVarSubstitutor.substitute( - typeVariable, Collections.singletonMap(typeVariable.getUnderlyingType(), typeVariable)); - return upperBound.create(typeVariable, freshTypeVariable); - } - - /** - * Returns the result of substituting {@code typeArg} for {@code typeVar} in {@code types}. - * - * @param typeVar type variables - * @param typeArg type arguments - * @param types types - * @return the result of substituting {@code typeArg} for {@code typeVar} in {@code types} - */ - public List getSubsTypeArgs( - List typeVar, List typeArg, List types) { - List javaTypeArgs = new ArrayList<>(); - // Recursive types: - for (int i = 0; i < typeArg.size(); i++) { - Variable ai = types.get(i); - TypeMirror inst = typeArg.get(i).getJavaType(); - TypeVariable typeVariableI = ai.getJavaType(); - if (ContainsInferenceVariable.hasAnyTypeVariable( - Collections.singleton(typeVariableI), inst)) { - // If the instantiation of ai includes a reference to ai, - // then substitute ai with an unbound wildcard. This isn't quite right but I'm not - // sure how to make recursive types Java types. - // TODO: This causes problems when incorporating the bounds. - TypeMirror unbound = context.env.getTypeUtils().getWildcardType(null, null); - inst = - TypesUtils.substitute( - inst, - Collections.singletonList(typeVariableI), - Collections.singletonList(unbound), - context.env); - javaTypeArgs.add(inst); - } else { - javaTypeArgs.add(inst); - } + + /** + * Creates a wildcard using the upper and lower bounds provided. + * + * @param lowerBound a proper type or null + * @param upperBound an abstract type or null + * @return a wildcard with the provided upper and lower bounds + */ + public ProperType createWildcard(ProperType lowerBound, AbstractType upperBound) { + TypeMirror wildcard = + TypesUtils.createWildcard( + lowerBound == null ? null : lowerBound.getJavaType(), + upperBound == null ? null : upperBound.getJavaType(), + context.env.getTypeUtils()); + AnnotatedWildcardType wildcardAtm = + (AnnotatedWildcardType) + AnnotatedTypeMirror.createType(wildcard, typeFactory, false); + if (lowerBound != null) { + wildcardAtm.setSuperBound(lowerBound.getAnnotatedType()); + } + if (upperBound != null) { + wildcardAtm.setExtendsBound(upperBound.getAnnotatedType()); + } + return new ProperType(wildcardAtm, wildcard, context); } - for (int i = 0; i < typeVar.size(); i++) { - TypeMirror javaTypeArg = javaTypeArgs.get(i); - TypeMirror x = TypesUtils.substitute(javaTypeArg, typeVar, javaTypeArgs, context.env); - javaTypeArgs.remove(i); - javaTypeArgs.add(i, x); + /** + * Creates a fresh type variable using the upper and lower bounds provided. + * + * @param lowerBound a proper type or null + * @param lowerBoundAnnos annotations to use if {@code lowerBound} is null + * @param upperBound an abstract type or null + * @param upperBoundAnnos annotations to use if {@code upperBound} is null + * @return a fresh type variable with the provided upper and lower bounds + */ + public AbstractType createFreshTypeVariable( + ProperType lowerBound, + Set lowerBoundAnnos, + AbstractType upperBound, + Set upperBoundAnnos) { + TypeMirror freshTypeVariable = + TypesUtils.freshTypeVariable( + upperBound == null ? null : upperBound.getJavaType(), + lowerBound == null ? null : lowerBound.getJavaType(), + context.env); + AnnotatedTypeVariable typeVariable = + (AnnotatedTypeVariable) + AnnotatedTypeMirror.createType(freshTypeVariable, typeFactory, false); + // Initialize bounds. + typeVariable.getUpperBound(); + typeVariable.getLowerBound(); + if (lowerBound != null) { + typeVariable.setLowerBound(lowerBound.getAnnotatedType()); + } else { + typeVariable.getLowerBound().addAnnotations(lowerBoundAnnos); + } + if (upperBound != null) { + typeVariable.setUpperBound(upperBound.getAnnotatedType()); + } else { + typeVariable.getUpperBound().addAnnotations(upperBoundAnnos); + } + context.typeFactory.capturedTypeVarSubstitutor.substitute( + typeVariable, + Collections.singletonMap(typeVariable.getUnderlyingType(), typeVariable)); + return upperBound.create(typeVariable, freshTypeVariable); } - Map map = new HashMap<>(); + /** + * Returns the result of substituting {@code typeArg} for {@code typeVar} in {@code types}. + * + * @param typeVar type variables + * @param typeArg type arguments + * @param types types + * @return the result of substituting {@code typeArg} for {@code typeVar} in {@code types} + */ + public List getSubsTypeArgs( + List typeVar, List typeArg, List types) { + List javaTypeArgs = new ArrayList<>(); + // Recursive types: + for (int i = 0; i < typeArg.size(); i++) { + Variable ai = types.get(i); + TypeMirror inst = typeArg.get(i).getJavaType(); + TypeVariable typeVariableI = ai.getJavaType(); + if (ContainsInferenceVariable.hasAnyTypeVariable( + Collections.singleton(typeVariableI), inst)) { + // If the instantiation of ai includes a reference to ai, + // then substitute ai with an unbound wildcard. This isn't quite right but I'm not + // sure how to make recursive types Java types. + // TODO: This causes problems when incorporating the bounds. + TypeMirror unbound = context.env.getTypeUtils().getWildcardType(null, null); + inst = + TypesUtils.substitute( + inst, + Collections.singletonList(typeVariableI), + Collections.singletonList(unbound), + context.env); + javaTypeArgs.add(inst); + } else { + javaTypeArgs.add(inst); + } + } - List typeArgsATM = new ArrayList<>(); - // Recursive types: - for (int i = 0; i < typeArg.size(); i++) { - Variable ai = types.get(i); - AbstractType inst = typeArg.get(i); - typeArgsATM.add(inst.getAnnotatedType()); - TypeVariable typeVariableI = ai.getJavaType(); - map.put(typeVariableI, inst.getAnnotatedType()); - } + for (int i = 0; i < typeVar.size(); i++) { + TypeMirror javaTypeArg = javaTypeArgs.get(i); + TypeMirror x = TypesUtils.substitute(javaTypeArg, typeVar, javaTypeArgs, context.env); + javaTypeArgs.remove(i); + javaTypeArgs.add(i, x); + } - Iterator iter = javaTypeArgs.iterator(); - // Instantiations that refer to another variable - List subsTypeArg = new ArrayList<>(); - for (AnnotatedTypeMirror type : typeArgsATM) { - TypeVariableSubstitutor typeVarSubstitutor = typeFactory.getTypeVarSubstitutor(); - AnnotatedTypeMirror subs; - if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { - AnnotatedTypeVariable capTypeVar = (AnnotatedTypeVariable) type; - AnnotatedTypeMirror upperBound = - typeVarSubstitutor.substituteWithoutCopyingTypeArguments( - map, capTypeVar.getUpperBound()); - AnnotatedTypeMirror lowerBound = - typeVarSubstitutor.substituteWithoutCopyingTypeArguments( - map, capTypeVar.getLowerBound()); - capTypeVar.setUpperBound(upperBound); - capTypeVar.setLowerBound(lowerBound); - subs = capTypeVar; - } else { - subs = typeVarSubstitutor.substituteWithoutCopyingTypeArguments(map, type); - } - subsTypeArg.add(new ProperType(subs, iter.next(), context)); + Map map = new HashMap<>(); + + List typeArgsATM = new ArrayList<>(); + // Recursive types: + for (int i = 0; i < typeArg.size(); i++) { + Variable ai = types.get(i); + AbstractType inst = typeArg.get(i); + typeArgsATM.add(inst.getAnnotatedType()); + TypeVariable typeVariableI = ai.getJavaType(); + map.put(typeVariableI, inst.getAnnotatedType()); + } + + Iterator iter = javaTypeArgs.iterator(); + // Instantiations that refer to another variable + List subsTypeArg = new ArrayList<>(); + for (AnnotatedTypeMirror type : typeArgsATM) { + TypeVariableSubstitutor typeVarSubstitutor = typeFactory.getTypeVarSubstitutor(); + AnnotatedTypeMirror subs; + if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { + AnnotatedTypeVariable capTypeVar = (AnnotatedTypeVariable) type; + AnnotatedTypeMirror upperBound = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + map, capTypeVar.getUpperBound()); + AnnotatedTypeMirror lowerBound = + typeVarSubstitutor.substituteWithoutCopyingTypeArguments( + map, capTypeVar.getLowerBound()); + capTypeVar.setUpperBound(upperBound); + capTypeVar.setLowerBound(lowerBound); + subs = capTypeVar; + } else { + subs = typeVarSubstitutor.substituteWithoutCopyingTypeArguments(map, type); + } + subsTypeArg.add(new ProperType(subs, iter.next(), context)); + } + return subsTypeArg; } - return subsTypeArg; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java index 6450a922744..0c18602be75 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java @@ -1,6 +1,17 @@ package org.checkerframework.framework.util.typeinference8.types; import com.sun.tools.javac.code.Type; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; +import org.checkerframework.framework.util.typeinference8.constraint.ReductionResult; +import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import org.checkerframework.framework.util.typeinference8.util.Theta; +import org.checkerframework.javacutil.AnnotationMirrorMap; +import org.checkerframework.javacutil.TypesUtils; + import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -9,19 +20,11 @@ import java.util.List; import java.util.Map; import java.util.Set; + import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; -import org.checkerframework.framework.util.typeinference8.constraint.ReductionResult; -import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; -import org.checkerframework.framework.util.typeinference8.util.Theta; -import org.checkerframework.javacutil.AnnotationMirrorMap; -import org.checkerframework.javacutil.TypesUtils; /** * A type-like structure that contains at least one inference variable, but is not an inference @@ -29,313 +32,322 @@ */ public class InferenceType extends AbstractType { - /** - * The underlying Java type. It contains type variables that are mapped to inference variables in - * {@code map}. - */ - private final TypeMirror typeMirror; - - /** - * The AnnotatedTypeMirror. It contains type variables that are mapped to inference variables in - * {@code map}. - */ - private final AnnotatedTypeMirror type; - - /** A mapping of type variables to inference variables. */ - private final Theta map; - - /** A mapping from polymorphic annotation to {@link QualifierVar}. */ - private final AnnotationMirrorMap qualifierVars; - - /** - * Creates an inference type. - * - * @param type the annotated type mirror - * @param typeMirror the type mirror - * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} - * @param map a mapping from type variable to inference variablef - * @param context the context - */ - private InferenceType( - AnnotatedTypeMirror type, - TypeMirror typeMirror, - Theta map, - AnnotationMirrorMap qualifierVars, - Java8InferenceContext context) { - super(context); - assert type.getKind() == typeMirror.getKind(); - this.type = type.asUse(); - this.typeMirror = typeMirror; - this.qualifierVars = qualifierVars; - this.map = map; - } - - @Override - public Kind getKind() { - return Kind.INFERENCE_TYPE; - } - - /** - * Creates an abstract type for the given TypeMirror. The created type is an {@link InferenceType} - * if {@code type} contains any type variables that are mapped to inference variables as specified - * by {@code map}. Or if {@code type} is a type variable that is mapped to an inference variable, - * that {@link Variable} is returned. Or if {@code type} contains no type variables that are - * mapped in an inference variable, a {@link ProperType} is returned. - * - * @param type the annotated type mirror - * @param typeMirror the java type - * @param map a mapping from type variable to inference variable - * @param context the context - * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror - */ - public static AbstractType create( - AnnotatedTypeMirror type, - TypeMirror typeMirror, - @Nullable Theta map, - Java8InferenceContext context) { - - return create(type, typeMirror, map, AnnotationMirrorMap.emptyMap(), context); - } - - /** - * Creates an abstract type for the given TypeMirror. The created type is an {@link InferenceType} - * if {@code type} contains any type variables that are mapped to inference variables as specified - * by {@code map}. Or if {@code type} is a type variable that is mapped to an inference variable, - * that {@link Variable} is returned. Or if {@code type} contains no type variables that are - * mapped in an inference variable, a {@link ProperType} is returned. - * - * @param type the annotated type mirror - * @param typeMirror the java type - * @param map a mapping from type variable to inference variable - * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} - * @param context the context - * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror - */ - public static AbstractType create( - AnnotatedTypeMirror type, - TypeMirror typeMirror, - @Nullable Theta map, - AnnotationMirrorMap qualifierVars, - Java8InferenceContext context) { - assert type != null; - if (map == null) { - return new ProperType(type, typeMirror, qualifierVars, context); + /** + * The underlying Java type. It contains type variables that are mapped to inference variables + * in {@code map}. + */ + private final TypeMirror typeMirror; + + /** + * The AnnotatedTypeMirror. It contains type variables that are mapped to inference variables in + * {@code map}. + */ + private final AnnotatedTypeMirror type; + + /** A mapping of type variables to inference variables. */ + private final Theta map; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates an inference type. + * + * @param type the annotated type mirror + * @param typeMirror the type mirror + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param map a mapping from type variable to inference variablef + * @param context the context + */ + private InferenceType( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + super(context); + assert type.getKind() == typeMirror.getKind(); + this.type = type.asUse(); + this.typeMirror = typeMirror; + this.qualifierVars = qualifierVars; + this.map = map; } - if (typeMirror.getKind() == TypeKind.TYPEVAR && map.containsKey(type.getUnderlyingType())) { - return new UseOfVariable( - (AnnotatedTypeVariable) type, map.get(type.getUnderlyingType()), qualifierVars, context); - } else if (AnnotatedContainsInferenceVariable.hasAnyTypeVariable(map.keySet(), type)) { - return new InferenceType(type, typeMirror, map, qualifierVars, context); - } else { - return new ProperType(type, typeMirror, qualifierVars, context); - } - } - - /** - * Same as {@link #create(AnnotatedTypeMirror, TypeMirror, Theta, AnnotationMirrorMap, - * Java8InferenceContext)}, but if {@code type} contains any type variables that are in {@code - * map}, but already have an instantiation, they are treated as proper types. - * - * @param type the annotated type mirror - * @param typeMirror the java type - * @param map a mapping from type variable to inference variable - * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} - * @param context the context - * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror - */ - public static AbstractType createIgnoreInstantiated( - AnnotatedTypeMirror type, - TypeMirror typeMirror, - @Nullable Theta map, - AnnotationMirrorMap qualifierVars, - Java8InferenceContext context) { - assert type != null; - if (map == null) { - return new ProperType(type, typeMirror, qualifierVars, context); + @Override + public Kind getKind() { + return Kind.INFERENCE_TYPE; } - if (typeMirror.getKind() == TypeKind.TYPEVAR && map.containsKey(type.getUnderlyingType())) { - return new UseOfVariable( - (AnnotatedTypeVariable) type, map.get(type.getUnderlyingType()), qualifierVars, context); - } else if (AnnotatedContainsInferenceVariable.hasAnyTypeVariable( - map.getNotInstantiated(), type)) { - return new InferenceType(type, typeMirror, map, qualifierVars, context); - } else { - return new ProperType(type, typeMirror, qualifierVars, context); - } - } - - /** - * Creates abstract types for each TypeMirror. The created type is an {@link InferenceType} if it - * contains any type variables that are mapped to inference variables as specified by {@code map}. - * Or if the type is a type variable that is mapped to an inference variable, it will return that - * {@link Variable}. Or if the type contains no type variables that are mapped in an inference - * variable, a {@link ProperType} is returned. - * - * @param types the annotated type mirrors - * @param typeMirrors the java types - * @param map a mapping from type variable to inference variable - * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} - * @param context the context - * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror - */ - public static List create( - List types, - List typeMirrors, - Theta map, - AnnotationMirrorMap qualifierVars, - Java8InferenceContext context) { - List abstractTypes = new ArrayList<>(); - Iterator iter = typeMirrors.iterator(); - for (AnnotatedTypeMirror type : types) { - abstractTypes.add(create(type, iter.next(), map, qualifierVars, context)); + /** + * Creates an abstract type for the given TypeMirror. The created type is an {@link + * InferenceType} if {@code type} contains any type variables that are mapped to inference + * variables as specified by {@code map}. Or if {@code type} is a type variable that is mapped + * to an inference variable, that {@link Variable} is returned. Or if {@code type} contains no + * type variables that are mapped in an inference variable, a {@link ProperType} is returned. + * + * @param type the annotated type mirror + * @param typeMirror the java type + * @param map a mapping from type variable to inference variable + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static AbstractType create( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + @Nullable Theta map, + Java8InferenceContext context) { + + return create(type, typeMirror, map, AnnotationMirrorMap.emptyMap(), context); } - return abstractTypes; - } - - @Override - public AbstractType create(AnnotatedTypeMirror type, TypeMirror typeMirror) { - return create(type, typeMirror, map, qualifierVars, context); - } - - @Override - @SuppressWarnings("interning:not.interned") // maps should be == - public boolean equals(Object o) { - if (this == o) { - return true; + + /** + * Creates an abstract type for the given TypeMirror. The created type is an {@link + * InferenceType} if {@code type} contains any type variables that are mapped to inference + * variables as specified by {@code map}. Or if {@code type} is a type variable that is mapped + * to an inference variable, that {@link Variable} is returned. Or if {@code type} contains no + * type variables that are mapped in an inference variable, a {@link ProperType} is returned. + * + * @param type the annotated type mirror + * @param typeMirror the java type + * @param map a mapping from type variable to inference variable + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static AbstractType create( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + @Nullable Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + assert type != null; + if (map == null) { + return new ProperType(type, typeMirror, qualifierVars, context); + } + + if (typeMirror.getKind() == TypeKind.TYPEVAR && map.containsKey(type.getUnderlyingType())) { + return new UseOfVariable( + (AnnotatedTypeVariable) type, + map.get(type.getUnderlyingType()), + qualifierVars, + context); + } else if (AnnotatedContainsInferenceVariable.hasAnyTypeVariable(map.keySet(), type)) { + return new InferenceType(type, typeMirror, map, qualifierVars, context); + } else { + return new ProperType(type, typeMirror, qualifierVars, context); + } } - if (o == null || getClass() != o.getClass()) { - return false; + + /** + * Same as {@link #create(AnnotatedTypeMirror, TypeMirror, Theta, AnnotationMirrorMap, + * Java8InferenceContext)}, but if {@code type} contains any type variables that are in {@code + * map}, but already have an instantiation, they are treated as proper types. + * + * @param type the annotated type mirror + * @param typeMirror the java type + * @param map a mapping from type variable to inference variable + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static AbstractType createIgnoreInstantiated( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + @Nullable Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + assert type != null; + if (map == null) { + return new ProperType(type, typeMirror, qualifierVars, context); + } + + if (typeMirror.getKind() == TypeKind.TYPEVAR && map.containsKey(type.getUnderlyingType())) { + return new UseOfVariable( + (AnnotatedTypeVariable) type, + map.get(type.getUnderlyingType()), + qualifierVars, + context); + } else if (AnnotatedContainsInferenceVariable.hasAnyTypeVariable( + map.getNotInstantiated(), type)) { + return new InferenceType(type, typeMirror, map, qualifierVars, context); + } else { + return new ProperType(type, typeMirror, qualifierVars, context); + } } - InferenceType variable = (InferenceType) o; - if (map != variable.map) { - return false; + /** + * Creates abstract types for each TypeMirror. The created type is an {@link InferenceType} if + * it contains any type variables that are mapped to inference variables as specified by {@code + * map}. Or if the type is a type variable that is mapped to an inference variable, it will + * return that {@link Variable}. Or if the type contains no type variables that are mapped in an + * inference variable, a {@link ProperType} is returned. + * + * @param types the annotated type mirrors + * @param typeMirrors the java types + * @param map a mapping from type variable to inference variable + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static List create( + List types, + List typeMirrors, + Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + List abstractTypes = new ArrayList<>(); + Iterator iter = typeMirrors.iterator(); + for (AnnotatedTypeMirror type : types) { + abstractTypes.add(create(type, iter.next(), map, qualifierVars, context)); + } + return abstractTypes; } - if (!type.equals(variable.type)) { - return false; + + @Override + public AbstractType create(AnnotatedTypeMirror type, TypeMirror typeMirror) { + return create(type, typeMirror, map, qualifierVars, context); } - if (typeMirror.getKind() == TypeKind.TYPEVAR) { - if (variable.typeMirror.getKind() == TypeKind.TYPEVAR) { - return TypesUtils.areSame((TypeVariable) typeMirror, (TypeVariable) variable.typeMirror); - } - return false; + + @Override + @SuppressWarnings("interning:not.interned") // maps should be == + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InferenceType variable = (InferenceType) o; + if (map != variable.map) { + return false; + } + if (!type.equals(variable.type)) { + return false; + } + if (typeMirror.getKind() == TypeKind.TYPEVAR) { + if (variable.typeMirror.getKind() == TypeKind.TYPEVAR) { + return TypesUtils.areSame( + (TypeVariable) typeMirror, (TypeVariable) variable.typeMirror); + } + return false; + } + return context.modelTypes.isSameType(typeMirror, variable.typeMirror); } - return context.modelTypes.isSameType(typeMirror, variable.typeMirror); - } - - @Override - public int hashCode() { - int result = type.hashCode(); - result = 31 * result + Kind.INFERENCE_TYPE.hashCode(); - return result; - } - - @Override - public TypeMirror getJavaType() { - return typeMirror; - } - - @Override - public AnnotatedTypeMirror getAnnotatedType() { - return type; - } - - @Override - public boolean isObject() { - return false; - } - - /** - * Returns all inference variables mentioned in this type. - * - * @return all inference variables mentioned in this type - */ - @Override - public Collection getInferenceVariables() { - LinkedHashSet variables = new LinkedHashSet<>(); - for (TypeVariable typeVar : - ContainsInferenceVariable.getMentionedTypeVariables(map.keySet(), typeMirror)) { - variables.add(map.get(typeVar)); + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + Kind.INFERENCE_TYPE.hashCode(); + return result; } - return variables; - } - - @Override - public AbstractType applyInstantiations() { - List typeVariables = new ArrayList<>(); - List arguments = new ArrayList<>(); - List instantiations = new ArrayList<>(); - - for (Variable alpha : map.values()) { - if (alpha.getInstantiation() != null) { - typeVariables.add(alpha.getJavaType()); - arguments.add(alpha.getBounds().getInstantiation().getJavaType()); - instantiations.add(alpha); - } + + @Override + public TypeMirror getJavaType() { + return typeMirror; } - if (typeVariables.isEmpty()) { - return this; + + @Override + public AnnotatedTypeMirror getAnnotatedType() { + return type; } - TypeMirror newTypeJava = - TypesUtils.substitute(typeMirror, typeVariables, arguments, context.env); + @Override + public boolean isObject() { + return false; + } - Map mapping = new LinkedHashMap<>(); + /** + * Returns all inference variables mentioned in this type. + * + * @return all inference variables mentioned in this type + */ + @Override + public Collection getInferenceVariables() { + LinkedHashSet variables = new LinkedHashSet<>(); + for (TypeVariable typeVar : + ContainsInferenceVariable.getMentionedTypeVariables(map.keySet(), typeMirror)) { + variables.add(map.get(typeVar)); + } + return variables; + } - for (Variable alpha : instantiations) { - AnnotatedTypeMirror instantiation = alpha.getBounds().getInstantiation().getAnnotatedType(); - context.typeFactory.initializeAtm(instantiation); - mapping.put(alpha.getJavaType(), instantiation); + @Override + public AbstractType applyInstantiations() { + List typeVariables = new ArrayList<>(); + List arguments = new ArrayList<>(); + List instantiations = new ArrayList<>(); + + for (Variable alpha : map.values()) { + if (alpha.getInstantiation() != null) { + typeVariables.add(alpha.getJavaType()); + arguments.add(alpha.getBounds().getInstantiation().getJavaType()); + instantiations.add(alpha); + } + } + if (typeVariables.isEmpty()) { + return this; + } + + TypeMirror newTypeJava = + TypesUtils.substitute(typeMirror, typeVariables, arguments, context.env); + + Map mapping = new LinkedHashMap<>(); + + for (Variable alpha : instantiations) { + AnnotatedTypeMirror instantiation = + alpha.getBounds().getInstantiation().getAnnotatedType(); + context.typeFactory.initializeAtm(instantiation); + mapping.put(alpha.getJavaType(), instantiation); + } + if (map.isEmpty()) { + return this; + } + + AnnotatedTypeMirror newType = typeFactory.getTypeVarSubstitutor().substitute(mapping, type); + return createIgnoreInstantiated( + newType, newTypeJava, map, AnnotationMirrorMap.emptyMap(), context); } - if (map.isEmpty()) { - return this; + + @Override + public String toString() { + return "inference type: " + typeMirror; } - AnnotatedTypeMirror newType = typeFactory.getTypeVarSubstitutor().substitute(mapping, type); - return createIgnoreInstantiated( - newType, newTypeJava, map, AnnotationMirrorMap.emptyMap(), context); - } - - @Override - public String toString() { - return "inference type: " + typeMirror; - } - - @Override - public Set getQualifiers() { - return AbstractQualifier.create(getAnnotatedType().getAnnotations(), qualifierVars, context); - } - - /** - * Is {@code this} a subtype of {@code superType}? - * - * @param superType the potential supertype; is a declared type with no parameters - * @return if {@code this} is a subtype of {@code superType}, then return {@link - * ConstraintSet#TRUE}; otherwise, a false bound is returned - */ - public ReductionResult isSubType(ProperType superType) { - TypeMirror subType = getJavaType(); - TypeMirror superJavaType = superType.getJavaType(); - if (subType.getKind() == TypeKind.WILDCARD) { - if (((WildcardType) subType).getExtendsBound() != null) { - subType = ((WildcardType) subType).getExtendsBound(); - } else { - subType = context.types.erasure((Type) subType); - } + @Override + public Set getQualifiers() { + return AbstractQualifier.create( + getAnnotatedType().getAnnotations(), qualifierVars, context); } - if (context.types.isSubtype((Type) subType, (Type) superJavaType)) { - AnnotatedTypeMirror superATM = superType.getAnnotatedType(); - AnnotatedTypeMirror subATM = this.getAnnotatedType(); - if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { - return ConstraintSet.TRUE; - } else { - return ConstraintSet.TRUE_ANNO_FAIL; - } - } else { - return ConstraintSet.FALSE; + /** + * Is {@code this} a subtype of {@code superType}? + * + * @param superType the potential supertype; is a declared type with no parameters + * @return if {@code this} is a subtype of {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isSubType(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); + if (subType.getKind() == TypeKind.WILDCARD) { + if (((WildcardType) subType).getExtendsBound() != null) { + subType = ((WildcardType) subType).getExtendsBound(); + } else { + subType = context.types.erasure((Type) subType); + } + } + + if (context.types.isSubtype((Type) subType, (Type) superJavaType)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java index bc194c0065a..bce06de7951 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java @@ -5,17 +5,7 @@ import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -30,225 +20,239 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TreeUtils.MemberReferenceKind; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; + /** A method type for an invocation of a method or constructor. */ public class InvocationType { - /** A method or constructor invocation. */ - private final ExpressionTree invocation; - - /** The annotated method type. */ - private final AnnotatedExecutableType annotatedExecutableType; - - /** The Java method type. */ - private final ExecutableType methodType; - - /** The context. */ - private final Java8InferenceContext context; - - /** The annotated type factory. */ - private final AnnotatedTypeFactory typeFactory; - - /** A mapping from polymorphic annotation to {@link QualifierVar}. */ - private final AnnotationMirrorMap qualifierVars; - - /** - * Creates an invocation type. - * - * @param annotatedExecutableType annotated method type - * @param methodType java method type - * @param invocation a method or constructor invocation - * @param context the context - */ - public InvocationType( - AnnotatedExecutableType annotatedExecutableType, - ExecutableType methodType, - ExpressionTree invocation, - Java8InferenceContext context) { - assert annotatedExecutableType != null && methodType != null; - this.annotatedExecutableType = annotatedExecutableType; - this.methodType = methodType; - this.invocation = invocation; - this.context = context; - this.typeFactory = context.typeFactory; - - SimpleAnnotatedTypeScanner> s = - new SimpleAnnotatedTypeScanner<>( - (type, polys) -> { - for (AnnotationMirror a : type.getAnnotations()) { - if (typeFactory.getQualifierHierarchy().isPolymorphicQualifier(a)) { - polys.add(a); - } - } - return null; - }); - Set polys = new AnnotationMirrorSet(); - s.visit(annotatedExecutableType, polys); - AnnotationMirrorMap qualifierVars = new AnnotationMirrorMap<>(); - for (AnnotationMirror poly : polys) { - qualifierVars.put(poly, new QualifierVar(invocation, poly, context)); + /** A method or constructor invocation. */ + private final ExpressionTree invocation; + + /** The annotated method type. */ + private final AnnotatedExecutableType annotatedExecutableType; + + /** The Java method type. */ + private final ExecutableType methodType; + + /** The context. */ + private final Java8InferenceContext context; + + /** The annotated type factory. */ + private final AnnotatedTypeFactory typeFactory; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates an invocation type. + * + * @param annotatedExecutableType annotated method type + * @param methodType java method type + * @param invocation a method or constructor invocation + * @param context the context + */ + public InvocationType( + AnnotatedExecutableType annotatedExecutableType, + ExecutableType methodType, + ExpressionTree invocation, + Java8InferenceContext context) { + assert annotatedExecutableType != null && methodType != null; + this.annotatedExecutableType = annotatedExecutableType; + this.methodType = methodType; + this.invocation = invocation; + this.context = context; + this.typeFactory = context.typeFactory; + + SimpleAnnotatedTypeScanner> s = + new SimpleAnnotatedTypeScanner<>( + (type, polys) -> { + for (AnnotationMirror a : type.getAnnotations()) { + if (typeFactory.getQualifierHierarchy().isPolymorphicQualifier(a)) { + polys.add(a); + } + } + return null; + }); + Set polys = new AnnotationMirrorSet(); + s.visit(annotatedExecutableType, polys); + AnnotationMirrorMap qualifierVars = new AnnotationMirrorMap<>(); + for (AnnotationMirror poly : polys) { + qualifierVars.put(poly, new QualifierVar(invocation, poly, context)); + } + this.qualifierVars = qualifierVars; } - this.qualifierVars = qualifierVars; - } - - /** - * Returns the method or constructor invocation. - * - * @return the method or constructor invocation - */ - public ExpressionTree getInvocation() { - return invocation; - } - - /** - * Returns the java method type. - * - * @return the java method type - */ - public ExecutableType getJavaType() { - return annotatedExecutableType.getUnderlyingType(); - } - - /** - * Returns the thrown types. - * - * @param map a mapping from type variable to inference variable - * @return the thrown types - */ - public List getThrownTypes(Theta map) { - List thrown = new ArrayList<>(); - Iterator iter = methodType.getThrownTypes().iterator(); - for (AnnotatedTypeMirror t : annotatedExecutableType.getThrownTypes()) { - thrown.add(InferenceType.create(t, iter.next(), map, context)); + + /** + * Returns the method or constructor invocation. + * + * @return the method or constructor invocation + */ + public ExpressionTree getInvocation() { + return invocation; } - return thrown; - } - - /** - * Returns the return type. - * - * @param map a mapping from type variable to inference variable - * @return the return type - */ - public AbstractType getReturnType(Theta map) { - TypeMirror returnTypeJava; - AnnotatedTypeMirror returnType; - - if (TreeUtils.isDiamondTree(invocation)) { - Element e = ElementUtils.enclosingTypeElement(TreeUtils.elementFromUse(invocation)); - returnTypeJava = e.asType(); - returnType = typeFactory.getAnnotatedType(e); - } else if (invocation.getKind() == Tree.Kind.METHOD_INVOCATION - || invocation.getKind() == Tree.Kind.MEMBER_REFERENCE) { - if (invocation.getKind() == Kind.MEMBER_REFERENCE - && ((MemberReferenceTree) invocation).getMode() == ReferenceMode.NEW) { - returnType = - context.typeFactory.getResultingTypeOfConstructorMemberReference( - (MemberReferenceTree) invocation, annotatedExecutableType); - returnTypeJava = returnType.getUnderlyingType(); - } else { - returnTypeJava = methodType.getReturnType(); - returnType = annotatedExecutableType.getReturnType(); - } - - } else { - returnTypeJava = TreeUtils.typeOf(invocation); - returnType = typeFactory.getAnnotatedType(invocation); + + /** + * Returns the java method type. + * + * @return the java method type + */ + public ExecutableType getJavaType() { + return annotatedExecutableType.getUnderlyingType(); } - if (map == null) { - return new ProperType(returnType, returnTypeJava, context); + /** + * Returns the thrown types. + * + * @param map a mapping from type variable to inference variable + * @return the thrown types + */ + public List getThrownTypes(Theta map) { + List thrown = new ArrayList<>(); + Iterator iter = methodType.getThrownTypes().iterator(); + for (AnnotatedTypeMirror t : annotatedExecutableType.getThrownTypes()) { + thrown.add(InferenceType.create(t, iter.next(), map, context)); + } + return thrown; } - return InferenceType.create(returnType, returnTypeJava, map, context); - } - - /** - * Returns a list of the parameter types of {@code InvocationType} where the vararg parameter has - * been modified to match the arguments in {@code expression}. - * - * @param map a mapping from type variable to inference variable - * @param size the number of parameters to return; used to expand the vararg - * @return a list of the parameter types of {@code InvocationType} where the vararg parameter has - * been modified to match the arguments in {@code expression} - */ - public List getParameterTypes(Theta map, int size) { - List params = new ArrayList<>(annotatedExecutableType.getParameterTypes()); - - if (TreeUtils.isVarArgMethodCall(invocation)) { - AnnotatedArrayType vararg = (AnnotatedArrayType) params.remove(params.size() - 1); - for (int i = params.size(); i < size; i++) { - params.add(vararg.getComponentType()); - } + + /** + * Returns the return type. + * + * @param map a mapping from type variable to inference variable + * @return the return type + */ + public AbstractType getReturnType(Theta map) { + TypeMirror returnTypeJava; + AnnotatedTypeMirror returnType; + + if (TreeUtils.isDiamondTree(invocation)) { + Element e = ElementUtils.enclosingTypeElement(TreeUtils.elementFromUse(invocation)); + returnTypeJava = e.asType(); + returnType = typeFactory.getAnnotatedType(e); + } else if (invocation.getKind() == Tree.Kind.METHOD_INVOCATION + || invocation.getKind() == Tree.Kind.MEMBER_REFERENCE) { + if (invocation.getKind() == Kind.MEMBER_REFERENCE + && ((MemberReferenceTree) invocation).getMode() == ReferenceMode.NEW) { + returnType = + context.typeFactory.getResultingTypeOfConstructorMemberReference( + (MemberReferenceTree) invocation, annotatedExecutableType); + returnTypeJava = returnType.getUnderlyingType(); + } else { + returnTypeJava = methodType.getReturnType(); + returnType = annotatedExecutableType.getReturnType(); + } + + } else { + returnTypeJava = TreeUtils.typeOf(invocation); + returnType = typeFactory.getAnnotatedType(invocation); + } + + if (map == null) { + return new ProperType(returnType, returnTypeJava, context); + } + return InferenceType.create(returnType, returnTypeJava, map, context); } - List paramsJava = new ArrayList<>(methodType.getParameterTypes()); + /** + * Returns a list of the parameter types of {@code InvocationType} where the vararg parameter + * has been modified to match the arguments in {@code expression}. + * + * @param map a mapping from type variable to inference variable + * @param size the number of parameters to return; used to expand the vararg + * @return a list of the parameter types of {@code InvocationType} where the vararg parameter + * has been modified to match the arguments in {@code expression} + */ + public List getParameterTypes(Theta map, int size) { + List params = + new ArrayList<>(annotatedExecutableType.getParameterTypes()); + + if (TreeUtils.isVarArgMethodCall(invocation)) { + AnnotatedArrayType vararg = (AnnotatedArrayType) params.remove(params.size() - 1); + for (int i = params.size(); i < size; i++) { + params.add(vararg.getComponentType()); + } + } - if (TreeUtils.isVarArgMethodCall(invocation)) { - ArrayType vararg = (ArrayType) paramsJava.remove(paramsJava.size() - 1); - for (int i = paramsJava.size(); i < size; i++) { - paramsJava.add(vararg.getComponentType()); - } + List paramsJava = new ArrayList<>(methodType.getParameterTypes()); + + if (TreeUtils.isVarArgMethodCall(invocation)) { + ArrayType vararg = (ArrayType) paramsJava.remove(paramsJava.size() - 1); + for (int i = paramsJava.size(); i < size; i++) { + paramsJava.add(vararg.getComponentType()); + } + } + if (invocation.getKind() == Kind.MEMBER_REFERENCE + && MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) invocation) + .isUnbound()) { + params.add(0, annotatedExecutableType.getReceiverType()); + paramsJava.add(0, annotatedExecutableType.getReceiverType().getUnderlyingType()); + } + return InferenceType.create(params, paramsJava, map, qualifierVars, context); + } + + /** + * Returns the parameter types. (Varags are not expanded.) + * + * @param map a mapping from type variable to inference variable + * @return the parameter types + */ + public List getParameterTypes(Theta map) { + return getParameterTypes(map, annotatedExecutableType.getParameterTypes().size()); + } + + /** + * Whether this method has type variables. + * + * @return whether this method has type variables. + */ + public boolean hasTypeVariables() { + return !annotatedExecutableType.getTypeVariables().isEmpty(); + } + + /** + * Returns the annotated type variables. + * + * @return the annotated type variables + */ + public List getAnnotatedTypeVariables() { + return annotatedExecutableType.getTypeVariables(); + } + + /** + * Returns the type variables. + * + * @return the type variables + */ + public List getTypeVariables() { + return methodType.getTypeVariables(); } - if (invocation.getKind() == Kind.MEMBER_REFERENCE - && MemberReferenceKind.getMemberReferenceKind((MemberReferenceTree) invocation) - .isUnbound()) { - params.add(0, annotatedExecutableType.getReceiverType()); - paramsJava.add(0, annotatedExecutableType.getReceiverType().getUnderlyingType()); + + /** + * Whether this method is void. + * + * @return whether this method is void + */ + public boolean isVoid() { + return annotatedExecutableType.getReturnType().getKind() == TypeKind.VOID; + } + + /** + * Returns the annotated method type. + * + * @return the annotated method type + */ + public AnnotatedExecutableType getAnnotatedType() { + return annotatedExecutableType; } - return InferenceType.create(params, paramsJava, map, qualifierVars, context); - } - - /** - * Returns the parameter types. (Varags are not expanded.) - * - * @param map a mapping from type variable to inference variable - * @return the parameter types - */ - public List getParameterTypes(Theta map) { - return getParameterTypes(map, annotatedExecutableType.getParameterTypes().size()); - } - - /** - * Whether this method has type variables. - * - * @return whether this method has type variables. - */ - public boolean hasTypeVariables() { - return !annotatedExecutableType.getTypeVariables().isEmpty(); - } - - /** - * Returns the annotated type variables. - * - * @return the annotated type variables - */ - public List getAnnotatedTypeVariables() { - return annotatedExecutableType.getTypeVariables(); - } - - /** - * Returns the type variables. - * - * @return the type variables - */ - public List getTypeVariables() { - return methodType.getTypeVariables(); - } - - /** - * Whether this method is void. - * - * @return whether this method is void - */ - public boolean isVoid() { - return annotatedExecutableType.getReturnType().getKind() == TypeKind.VOID; - } - - /** - * Returns the annotated method type. - * - * @return the annotated method type - */ - public AnnotatedExecutableType getAnnotatedType() { - return annotatedExecutableType; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java index 7952418044a..008d7db9e6b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java @@ -3,12 +3,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Type; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; @@ -18,258 +13,269 @@ import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; + /** A type that does not contain any inference variables. */ public class ProperType extends AbstractType { - /** The annotated type mirror. */ - private final AnnotatedTypeMirror type; - - /** The Java type. */ - private final TypeMirror properType; - - /** A mapping from polymorphic annotation to {@link QualifierVar}. */ - private final AnnotationMirrorMap qualifierVars; - - /** - * Creates a proper type. - * - * @param type the annotated type - * @param properType the java type - * @param context the context - */ - public ProperType( - AnnotatedTypeMirror type, TypeMirror properType, Java8InferenceContext context) { - this(type, properType, AnnotationMirrorMap.emptyMap(), context); - } - - /** - * Creates a proper type. - * - * @param type the annotated type - * @param properType the java type - * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} - * @param context the context - */ - public ProperType( - AnnotatedTypeMirror type, - TypeMirror properType, - AnnotationMirrorMap qualifierVars, - Java8InferenceContext context) { - super(context); - this.properType = properType; - this.type = type; - this.qualifierVars = qualifierVars; - verifyTypeKinds(type, properType); - } - - /** - * Creates a proper type from the type of the expression. - * - * @param tree an expression tree - * @param context the context - */ - public ProperType(ExpressionTree tree, Java8InferenceContext context) { - super(context); - this.type = context.typeFactory.getAnnotatedType(tree); - this.properType = type.getUnderlyingType(); - this.qualifierVars = AnnotationMirrorMap.emptyMap(); - verifyTypeKinds(type, properType); - } - - /** - * Creates a proper type from the type of the variable. - * - * @param varTree a variable tree - * @param context the context - */ - public ProperType(VariableTree varTree, Java8InferenceContext context) { - super(context); - this.type = context.typeFactory.getAnnotatedType(varTree); - this.properType = TreeUtils.typeOf(varTree); - this.qualifierVars = AnnotationMirrorMap.emptyMap(); - verifyTypeKinds(type, properType); - } - - /** - * Asserts that the underlying type of {@code atm} is the same kind as {@code typeMirror}. - * - * @param atm annotated type mirror - * @param typeMirror java type - */ - private static void verifyTypeKinds(AnnotatedTypeMirror atm, TypeMirror typeMirror) { - assert typeMirror != null && typeMirror.getKind() != TypeKind.VOID && atm != null; - - if (typeMirror.getKind() != atm.getKind()) { - // throw new BugInCF("type: %s annotated type: %s", typeMirror, - // atm.getUnderlyingType()); + /** The annotated type mirror. */ + private final AnnotatedTypeMirror type; + + /** The Java type. */ + private final TypeMirror properType; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates a proper type. + * + * @param type the annotated type + * @param properType the java type + * @param context the context + */ + public ProperType( + AnnotatedTypeMirror type, TypeMirror properType, Java8InferenceContext context) { + this(type, properType, AnnotationMirrorMap.emptyMap(), context); } - } - - @Override - public Kind getKind() { - return Kind.PROPER; - } - - @Override - public AbstractType create(AnnotatedTypeMirror atm, TypeMirror type) { - return new ProperType(atm, type, qualifierVars, context); - } - - /** - * If this is a primitive type, then the proper type corresponding to its wrapper is returned. - * Otherwise, this object is return. - * - * @return the proper type that is the wrapper type for this type or this if no such wrapper - * exists - */ - public ProperType boxType() { - if (properType.getKind().isPrimitive()) { - return new ProperType( - typeFactory.getBoxedType((AnnotatedPrimitiveType) getAnnotatedType()), - context.types.boxedClass((Type) properType).asType(), - context); + + /** + * Creates a proper type. + * + * @param type the annotated type + * @param properType the java type + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + */ + public ProperType( + AnnotatedTypeMirror type, + TypeMirror properType, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + super(context); + this.properType = properType; + this.type = type; + this.qualifierVars = qualifierVars; + verifyTypeKinds(type, properType); } - return this; - } - - /** - * Is {@code this} a subtype of {@code superType}? - * - * @param superType super type - * @return if {@code this} is a subtype of {@code superType}, then return {@link - * ConstraintSet#TRUE}; otherwise, a false bound is returned - */ - public ReductionResult isSubType(ProperType superType) { - TypeMirror subType = getJavaType(); - TypeMirror superJavaType = superType.getJavaType(); - - if (context.typeFactory.types.isAssignable(subType, superJavaType) - || TypesUtils.isErasedSubtype(subType, superJavaType, context.typeFactory.types)) { - AnnotatedTypeMirror superATM = superType.getAnnotatedType(); - AnnotatedTypeMirror subATM = this.getAnnotatedType(); - if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { - return ConstraintSet.TRUE; - } else { - return ConstraintSet.TRUE_ANNO_FAIL; - } - } else { - return ConstraintSet.FALSE; + + /** + * Creates a proper type from the type of the expression. + * + * @param tree an expression tree + * @param context the context + */ + public ProperType(ExpressionTree tree, Java8InferenceContext context) { + super(context); + this.type = context.typeFactory.getAnnotatedType(tree); + this.properType = type.getUnderlyingType(); + this.qualifierVars = AnnotationMirrorMap.emptyMap(); + verifyTypeKinds(type, properType); } - } - - /** - * Is {@code this} an unchecked subtype of {@code superType}? - * - * @param superType super type - * @return if {@code this} is an unchecked subtype of {@code superType}, then return {@link - * ConstraintSet#TRUE}; otherwise, a false bound is returned - */ - public ReductionResult isSubTypeUnchecked(ProperType superType) { - TypeMirror subType = getJavaType(); - TypeMirror superJavaType = superType.getJavaType(); - - if (context.types.isSubtypeUnchecked((Type) subType, (Type) superJavaType)) { - AnnotatedTypeMirror superATM = superType.getAnnotatedType(); - AnnotatedTypeMirror subATM = this.getAnnotatedType(); - if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { - return ConstraintSet.TRUE; - } else { - return ConstraintSet.TRUE_ANNO_FAIL; - } - } else { - return ConstraintSet.FALSE; + + /** + * Creates a proper type from the type of the variable. + * + * @param varTree a variable tree + * @param context the context + */ + public ProperType(VariableTree varTree, Java8InferenceContext context) { + super(context); + this.type = context.typeFactory.getAnnotatedType(varTree); + this.properType = TreeUtils.typeOf(varTree); + this.qualifierVars = AnnotationMirrorMap.emptyMap(); + verifyTypeKinds(type, properType); + } + + /** + * Asserts that the underlying type of {@code atm} is the same kind as {@code typeMirror}. + * + * @param atm annotated type mirror + * @param typeMirror java type + */ + private static void verifyTypeKinds(AnnotatedTypeMirror atm, TypeMirror typeMirror) { + assert typeMirror != null && typeMirror.getKind() != TypeKind.VOID && atm != null; + + if (typeMirror.getKind() != atm.getKind()) { + // throw new BugInCF("type: %s annotated type: %s", typeMirror, + // atm.getUnderlyingType()); + } } - } - - /** - * Is {@code this} assignable to {@code superType}? - * - * @param superType super type - * @return if {@code this} assignable to {@code superType}, then return {@link - * ConstraintSet#TRUE}; otherwise, a false bound is returned - */ - public ReductionResult isAssignable(ProperType superType) { - TypeMirror subType = getJavaType(); - TypeMirror superJavaType = superType.getJavaType(); - - if (context.types.isAssignable((Type) subType, (Type) superJavaType)) { - AnnotatedTypeMirror superATM = superType.getAnnotatedType(); - AnnotatedTypeMirror subATM = this.getAnnotatedType(); - if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { - return ConstraintSet.TRUE; - } else { - return ConstraintSet.TRUE_ANNO_FAIL; - } - } else { - return ConstraintSet.FALSE; + + @Override + public Kind getKind() { + return Kind.PROPER; } - } - @SuppressWarnings("interning:not.interned") // Checking for exact object. - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + @Override + public AbstractType create(AnnotatedTypeMirror atm, TypeMirror type) { + return new ProperType(atm, type, qualifierVars, context); } - if (o == null || getClass() != o.getClass()) { - return false; + + /** + * If this is a primitive type, then the proper type corresponding to its wrapper is returned. + * Otherwise, this object is return. + * + * @return the proper type that is the wrapper type for this type or this if no such wrapper + * exists + */ + public ProperType boxType() { + if (properType.getKind().isPrimitive()) { + return new ProperType( + typeFactory.getBoxedType((AnnotatedPrimitiveType) getAnnotatedType()), + context.types.boxedClass((Type) properType).asType(), + context); + } + return this; } - ProperType otherProperType = (ProperType) o; + /** + * Is {@code this} a subtype of {@code superType}? + * + * @param superType super type + * @return if {@code this} is a subtype of {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isSubType(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); - if (!type.equals(otherProperType.type)) { - return false; + if (context.typeFactory.types.isAssignable(subType, superJavaType) + || TypesUtils.isErasedSubtype(subType, superJavaType, context.typeFactory.types)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } } - if (properType.getKind() == TypeKind.TYPEVAR) { - if (otherProperType.properType.getKind() == TypeKind.TYPEVAR) { - return TypesUtils.areSame( - (TypeVariable) properType, (TypeVariable) otherProperType.properType); - } - return false; + + /** + * Is {@code this} an unchecked subtype of {@code superType}? + * + * @param superType super type + * @return if {@code this} is an unchecked subtype of {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isSubTypeUnchecked(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); + + if (context.types.isSubtypeUnchecked((Type) subType, (Type) superJavaType)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } + } + + /** + * Is {@code this} assignable to {@code superType}? + * + * @param superType super type + * @return if {@code this} assignable to {@code superType}, then return {@link + * ConstraintSet#TRUE}; otherwise, a false bound is returned + */ + public ReductionResult isAssignable(ProperType superType) { + TypeMirror subType = getJavaType(); + TypeMirror superJavaType = superType.getJavaType(); + + if (context.types.isAssignable((Type) subType, (Type) superJavaType)) { + AnnotatedTypeMirror superATM = superType.getAnnotatedType(); + AnnotatedTypeMirror subATM = this.getAnnotatedType(); + if (typeFactory.getTypeHierarchy().isSubtype(subATM, superATM)) { + return ConstraintSet.TRUE; + } else { + return ConstraintSet.TRUE_ANNO_FAIL; + } + } else { + return ConstraintSet.FALSE; + } + } + + @SuppressWarnings("interning:not.interned") // Checking for exact object. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ProperType otherProperType = (ProperType) o; + + if (!type.equals(otherProperType.type)) { + return false; + } + if (properType.getKind() == TypeKind.TYPEVAR) { + if (otherProperType.properType.getKind() == TypeKind.TYPEVAR) { + return TypesUtils.areSame( + (TypeVariable) properType, (TypeVariable) otherProperType.properType); + } + return false; + } + return properType == otherProperType.properType // faster + || context.env + .getTypeUtils() + .isSameType(properType, otherProperType.properType); // slower + } + + @Override + public int hashCode() { + int result = properType.toString().hashCode(); + result = 31 * result + Kind.PROPER.hashCode(); + return result; + } + + @Override + public TypeMirror getJavaType() { + return properType; + } + + @Override + public AnnotatedTypeMirror getAnnotatedType() { + return type; + } + + @Override + public boolean isObject() { + return TypesUtils.isObject(properType); + } + + @Override + public Collection getInferenceVariables() { + return Collections.emptyList(); + } + + @Override + public AbstractType applyInstantiations() { + return this; + } + + @Override + public Set getQualifiers() { + return AbstractQualifier.create( + getAnnotatedType().getAnnotations(), qualifierVars, context); + } + + @Override + public String toString() { + return type.toString(); } - return properType == otherProperType.properType // faster - || context.env.getTypeUtils().isSameType(properType, otherProperType.properType); // slower - } - - @Override - public int hashCode() { - int result = properType.toString().hashCode(); - result = 31 * result + Kind.PROPER.hashCode(); - return result; - } - - @Override - public TypeMirror getJavaType() { - return properType; - } - - @Override - public AnnotatedTypeMirror getAnnotatedType() { - return type; - } - - @Override - public boolean isObject() { - return TypesUtils.isObject(properType); - } - - @Override - public Collection getInferenceVariables() { - return Collections.emptyList(); - } - - @Override - public AbstractType applyInstantiations() { - return this; - } - - @Override - public Set getQualifiers() { - return AbstractQualifier.create(getAnnotatedType().getAnnotations(), qualifierVars, context); - } - - @Override - public String toString() { - return type.toString(); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java index 58af2bd5eb9..d79506c855b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Qualifier.java @@ -1,41 +1,42 @@ package org.checkerframework.framework.util.typeinference8.types; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import javax.lang.model.element.AnnotationMirror; + /** A wrapper around an {@link AnnotationMirror}. */ public class Qualifier extends AbstractQualifier { - /** The annotation. */ - private final AnnotationMirror annotation; - - /** - * A wrapper around an {@link AnnotationMirror}. - * - * @param annotation the annotation - * @param context the context - */ - protected Qualifier(AnnotationMirror annotation, Java8InferenceContext context) { - super(annotation, context); - this.annotation = annotation; - } - - /** - * Returns the annotation - * - * @return the annotation - */ - public AnnotationMirror getAnnotation() { - return annotation; - } - - @Override - public AnnotationMirror getInstantiation() { - return annotation; - } - - @Override - public String toString() { - return annotation.toString(); - } + /** The annotation. */ + private final AnnotationMirror annotation; + + /** + * A wrapper around an {@link AnnotationMirror}. + * + * @param annotation the annotation + * @param context the context + */ + protected Qualifier(AnnotationMirror annotation, Java8InferenceContext context) { + super(annotation, context); + this.annotation = annotation; + } + + /** + * Returns the annotation + * + * @return the annotation + */ + public AnnotationMirror getAnnotation() { + return annotation; + } + + @Override + public AnnotationMirror getInstantiation() { + return annotation; + } + + @Override + public String toString() { + return annotation.toString(); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java index 27b4ff6b0e5..4edbc824e03 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/QualifierVar.java @@ -1,194 +1,200 @@ package org.checkerframework.framework.util.typeinference8.types; import com.sun.source.tree.ExpressionTree; -import java.util.EnumMap; -import java.util.LinkedHashSet; -import java.util.Objects; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; import org.checkerframework.framework.util.typeinference8.constraint.QualifierTyping; import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; import org.checkerframework.framework.util.typeinference8.util.Java8InferenceContext; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; + /** * A {@code QualifierVar} is a variable for a polymorphic qualifier that needs to be viewpoint * adapted at a call site. */ public class QualifierVar extends AbstractQualifier { - /** Identification number. Used only to make debugging easier. */ - protected final int id; - - /** - * The expression for which this variable is being solved. Used to differentiate qualifier - * variables for two different invocations of the same method or constructor. - */ - protected final ExpressionTree invocation; - - /** The polymorphic qualifier associated with this var. */ - protected final AnnotationMirror polyQualifier; - - /** A mapping from a {@link BoundKind} to a set of abstract qualifiers. */ - public final EnumMap> qualifierBounds = - new EnumMap<>(BoundKind.class); - - /** The instantiation of this variable. This is set during inference. */ - protected AnnotationMirror instantiation; - - /** - * Creates a {@link QualifierVar}. - * - * @param invocation the expression for which this variable is being solved - * @param polyQualifier polymorphic qualifier associated with this var - * @param context the context - */ - public QualifierVar( - ExpressionTree invocation, AnnotationMirror polyQualifier, Java8InferenceContext context) { - super(polyQualifier, context); - this.id = context.getNextVariableId(); - this.invocation = invocation; - this.polyQualifier = polyQualifier; - qualifierBounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); - qualifierBounds.put(BoundKind.UPPER, new LinkedHashSet<>()); - qualifierBounds.put(BoundKind.LOWER, new LinkedHashSet<>()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + /** Identification number. Used only to make debugging easier. */ + protected final int id; + + /** + * The expression for which this variable is being solved. Used to differentiate qualifier + * variables for two different invocations of the same method or constructor. + */ + protected final ExpressionTree invocation; + + /** The polymorphic qualifier associated with this var. */ + protected final AnnotationMirror polyQualifier; + + /** A mapping from a {@link BoundKind} to a set of abstract qualifiers. */ + public final EnumMap> qualifierBounds = + new EnumMap<>(BoundKind.class); + + /** The instantiation of this variable. This is set during inference. */ + protected AnnotationMirror instantiation; + + /** + * Creates a {@link QualifierVar}. + * + * @param invocation the expression for which this variable is being solved + * @param polyQualifier polymorphic qualifier associated with this var + * @param context the context + */ + public QualifierVar( + ExpressionTree invocation, + AnnotationMirror polyQualifier, + Java8InferenceContext context) { + super(polyQualifier, context); + this.id = context.getNextVariableId(); + this.invocation = invocation; + this.polyQualifier = polyQualifier; + qualifierBounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.UPPER, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.LOWER, new LinkedHashSet<>()); } - if (o == null || getClass() != o.getClass()) { - return false; - } - QualifierVar that = (QualifierVar) o; - return id == that.id - && Objects.equals(invocation, that.invocation) - && Objects.equals(polyQualifier, that.polyQualifier); - } - - @Override - public int hashCode() { - return Objects.hash(id, invocation, polyQualifier); - } - - @Override - public String toString() { - return "@QV" + id; - } - - /** - * Add a bound for this qualifier variable - * - * @param kind a bound kind - * @param otherQual the bound to add - * @return a set of constraints generated from adding this bound. - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - public ConstraintSet addBound(BoundKind kind, AbstractQualifier otherQual) { - if (otherQual == this) { - return ConstraintSet.TRUE; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QualifierVar that = (QualifierVar) o; + return id == that.id + && Objects.equals(invocation, that.invocation) + && Objects.equals(polyQualifier, that.polyQualifier); } - if (kind == BoundKind.EQUAL && otherQual instanceof Qualifier) { - instantiation = ((Qualifier) otherQual).getAnnotation(); + + @Override + public int hashCode() { + return Objects.hash(id, invocation, polyQualifier); } - if (qualifierBounds.get(kind).add(otherQual)) { - return addConstraintsFromComplementaryBounds(kind, otherQual); + + @Override + public String toString() { + return "@QV" + id; } - return ConstraintSet.TRUE; - } - - /** - * Returns the constraints generated by adding the given bound. - * - * @param kind bound kind - * @param s other abstract qualifier - * @return the constraints - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - private ConstraintSet addConstraintsFromComplementaryBounds(BoundKind kind, AbstractQualifier s) { - ConstraintSet constraints = new ConstraintSet(); - switch (kind) { - case EQUAL: - for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { - if (s != t) { - constraints.add(new QualifierTyping(s, t, Kind.TYPE_EQUALITY)); - } + + /** + * Add a bound for this qualifier variable + * + * @param kind a bound kind + * @param otherQual the bound to add + * @return a set of constraints generated from adding this bound. + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public ConstraintSet addBound(BoundKind kind, AbstractQualifier otherQual) { + if (otherQual == this) { + return ConstraintSet.TRUE; } - break; - case LOWER: - for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { - if (s != t) { - constraints.add(new QualifierTyping(s, t, Kind.SUBTYPE)); - } + if (kind == BoundKind.EQUAL && otherQual instanceof Qualifier) { + instantiation = ((Qualifier) otherQual).getAnnotation(); } - break; - case UPPER: - for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { - if (s != t) { - constraints.add(new QualifierTyping(t, s, Kind.SUBTYPE)); - } + if (qualifierBounds.get(kind).add(otherQual)) { + return addConstraintsFromComplementaryBounds(kind, otherQual); } - break; + return ConstraintSet.TRUE; } - if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { - for (AbstractQualifier t : qualifierBounds.get(BoundKind.LOWER)) { - if (s != t) { - constraints.add(new QualifierTyping(t, s, Kind.SUBTYPE)); + /** + * Returns the constraints generated by adding the given bound. + * + * @param kind bound kind + * @param s other abstract qualifier + * @return the constraints + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private ConstraintSet addConstraintsFromComplementaryBounds( + BoundKind kind, AbstractQualifier s) { + ConstraintSet constraints = new ConstraintSet(); + switch (kind) { + case EQUAL: + for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new QualifierTyping(s, t, Kind.TYPE_EQUALITY)); + } + } + break; + case LOWER: + for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new QualifierTyping(s, t, Kind.SUBTYPE)); + } + } + break; + case UPPER: + for (AbstractQualifier t : qualifierBounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new QualifierTyping(t, s, Kind.SUBTYPE)); + } + } + break; + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + for (AbstractQualifier t : qualifierBounds.get(BoundKind.LOWER)) { + if (s != t) { + constraints.add(new QualifierTyping(t, s, Kind.SUBTYPE)); + } + } } - } - } - if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { - for (AbstractQualifier t : qualifierBounds.get(BoundKind.UPPER)) { - if (s != t) { - constraints.add(new QualifierTyping(s, t, Kind.SUBTYPE)); + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + for (AbstractQualifier t : qualifierBounds.get(BoundKind.UPPER)) { + if (s != t) { + constraints.add(new QualifierTyping(s, t, Kind.SUBTYPE)); + } + } } - } + return constraints; } - return constraints; - } - - @Override - AnnotationMirror getInstantiation() { - if (instantiation == null) { - AnnotationMirror lub = null; - for (AbstractQualifier lower : qualifierBounds.get(BoundKind.LOWER)) { - if (lower instanceof Qualifier) { - if (lub != null) { - lub = - context - .typeFactory - .getQualifierHierarchy() - .leastUpperBoundQualifiersOnly(lub, ((Qualifier) lower).getAnnotation()); - } else { - lub = ((Qualifier) lower).getAnnotation(); - } + + @Override + AnnotationMirror getInstantiation() { + if (instantiation == null) { + AnnotationMirror lub = null; + for (AbstractQualifier lower : qualifierBounds.get(BoundKind.LOWER)) { + if (lower instanceof Qualifier) { + if (lub != null) { + lub = + context.typeFactory + .getQualifierHierarchy() + .leastUpperBoundQualifiersOnly( + lub, ((Qualifier) lower).getAnnotation()); + } else { + lub = ((Qualifier) lower).getAnnotation(); + } + } + } + if (lub != null) { + instantiation = lub; + return instantiation; + } + AnnotationMirror glb = null; + for (AbstractQualifier upper : qualifierBounds.get(BoundKind.UPPER)) { + if (upper instanceof Qualifier) { + if (glb != null) { + glb = + context.typeFactory + .getQualifierHierarchy() + .greatestLowerBoundQualifiersOnly( + glb, ((Qualifier) upper).getAnnotation()); + } else { + glb = ((Qualifier) upper).getAnnotation(); + } + } + } + return glb; } - } - if (lub != null) { - instantiation = lub; return instantiation; - } - AnnotationMirror glb = null; - for (AbstractQualifier upper : qualifierBounds.get(BoundKind.UPPER)) { - if (upper instanceof Qualifier) { - if (glb != null) { - glb = - context - .typeFactory - .getQualifierHierarchy() - .greatestLowerBoundQualifiersOnly(glb, ((Qualifier) upper).getAnnotation()); - } else { - glb = ((Qualifier) upper).getAnnotation(); - } - } - } - return glb; } - return instantiation; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java index 9cff553d79c..63aa8dca76c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.util.typeinference8.types; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.QualifierHierarchy; @@ -15,227 +8,237 @@ import org.checkerframework.javacutil.AnnotationMirrorMap; import org.checkerframework.javacutil.AnnotationMirrorSet; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; + /** * A use of an inference variable. This class keeps track of whether the use of this variable has a * primary annotation. */ public class UseOfVariable extends AbstractType { - /** The variable that this is a use of. */ - private final Variable variable; - - /** Whether this use has a primary annotation. */ - private final boolean hasPrimaryAnno; - - /** The bottom annotations for each hierarchy that has a primary annotation on this use. */ - private final Set bots; - - /** The top annotations for each hierarchy that has a primary annotation on this use. */ - private final Set tops; - - /** The annotated type variable for this use. */ - private final AnnotatedTypeVariable type; - - /** A mapping from polymorphic annotation to {@link QualifierVar}. */ - private final AnnotationMirrorMap qualifierVars; - - /** - * Creates a use of a variable. - * - * @param type annotated type variable for this use - * @param variable variable that this is a use of - * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} - * @param context the context - */ - public UseOfVariable( - AnnotatedTypeVariable type, - Variable variable, - AnnotationMirrorMap qualifierVars, - Java8InferenceContext context) { - super(context); - QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); - this.qualifierVars = qualifierVars; - this.variable = variable; - this.type = type.deepCopy(); - this.hasPrimaryAnno = !type.getAnnotations().isEmpty(); - this.bots = new AnnotationMirrorSet(); - this.tops = new AnnotationMirrorSet(); - if (hasPrimaryAnno) { - for (AnnotationMirror anno : type.getAnnotations()) { - bots.add(qh.getBottomAnnotation(anno)); - tops.add(qh.getTopAnnotation(anno)); - } - } - } - - @Override - public AbstractType create(AnnotatedTypeMirror atm, TypeMirror type) { - return InferenceType.create(atm, type, variable.map, qualifierVars, context); - } - - @Override - public boolean isObject() { - return false; - } - - @Override - public List getTypeParameterBounds() { - return null; - } - - @Override - public UseOfVariable capture(Java8InferenceContext context) { - return this; - } - - @Override - public UseOfVariable getErased() { - return this; - } - - @Override - public TypeVariable getJavaType() { - return variable.typeVariableJava; - } - - @Override - public AnnotatedTypeVariable getAnnotatedType() { - return type; - } - - @Override - public Kind getKind() { - return Kind.USE_OF_VARIABLE; - } - - @Override - public Collection getInferenceVariables() { - return Collections.singleton(variable); - } - - @Override - public AbstractType applyInstantiations() { - if (this.variable.getInstantiation() != null) { - return this.variable.getInstantiation(); - } - - return this; - } - - /** - * Returns the variable that this is a use of. - * - * @return the variable that this is a use of - */ - public Variable getVariable() { - return variable; - } - - /** - * Set whether this use has a throws bound. - * - * @param hasThrowsBound whether this use has a throws bound - */ - public void setHasThrowsBound(boolean hasThrowsBound) { - variable.getBounds().setHasThrowsBound(hasThrowsBound); - } - - /** - * Adds a qualifier bound for this variable, is this use does not have a primary annotation. - * - * @param kind the kind of bound - * @param annotations the qualifiers to add - */ - public void addQualifierBound(BoundKind kind, Set annotations) { - if (!hasPrimaryAnno) { - variable.getBounds().addQualifierBound(kind, annotations); - } - } - - /** - * Adds a bound for this variable, is this use does not have a primary annotation. - * - * @param kind the kind of bound - * @param bound the type of the bound - */ - public void addBound(BoundKind kind, AbstractType bound) { - if (!hasPrimaryAnno) { - variable.getBounds().addBound(kind, bound); - } else { - // If the use has a primary annotation, then add the bound but with that annotations - // set to bottom or top. This makes it so that the java type is still a bound, but - // the qualifiers do not change the results of inference. - if (kind == BoundKind.LOWER) { - bound.getAnnotatedType().replaceAnnotations(bots); - variable.getBounds().addBound(kind, bound); - } else if (kind == BoundKind.UPPER) { - bound.getAnnotatedType().replaceAnnotations(tops); - variable.getBounds().addBound(kind, bound); - } else { - AnnotatedTypeMirror copyATM = bound.getAnnotatedType().deepCopy(); - AbstractType boundCopy = bound.create(copyATM, bound.getJavaType()); - - bound.getAnnotatedType().replaceAnnotations(tops); - variable.getBounds().addBound(BoundKind.UPPER, bound); - - boundCopy.getAnnotatedType().replaceAnnotations(bots); - variable.getBounds().addBound(BoundKind.LOWER, boundCopy); - } - } - } - - @Override - public Set getQualifiers() { - if (hasPrimaryAnno) { - return AbstractQualifier.create(getAnnotatedType().getAnnotations(), qualifierVars, context); - } else { - return Collections.emptySet(); - } - } - - @Override - public String toString() { - return "use of " + variable + (hasPrimaryAnno ? " with primary" : ""); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - - UseOfVariable that = (UseOfVariable) o; - - if (hasPrimaryAnno != that.hasPrimaryAnno) { - return false; - } - if (variable != that.variable) { - return false; - } - if (!bots.equals(that.bots)) { - return false; - } - if (!tops.equals(that.tops)) { - return false; - } - - return type.equals(that.type); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + variable.hashCode(); - result = 31 * result + (hasPrimaryAnno ? 1 : 0); - result = 31 * result + bots.hashCode(); - result = 31 * result + tops.hashCode(); - result = 31 * result + type.hashCode(); - return result; - } + /** The variable that this is a use of. */ + private final Variable variable; + + /** Whether this use has a primary annotation. */ + private final boolean hasPrimaryAnno; + + /** The bottom annotations for each hierarchy that has a primary annotation on this use. */ + private final Set bots; + + /** The top annotations for each hierarchy that has a primary annotation on this use. */ + private final Set tops; + + /** The annotated type variable for this use. */ + private final AnnotatedTypeVariable type; + + /** A mapping from polymorphic annotation to {@link QualifierVar}. */ + private final AnnotationMirrorMap qualifierVars; + + /** + * Creates a use of a variable. + * + * @param type annotated type variable for this use + * @param variable variable that this is a use of + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + */ + public UseOfVariable( + AnnotatedTypeVariable type, + Variable variable, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + super(context); + QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); + this.qualifierVars = qualifierVars; + this.variable = variable; + this.type = type.deepCopy(); + this.hasPrimaryAnno = !type.getAnnotations().isEmpty(); + this.bots = new AnnotationMirrorSet(); + this.tops = new AnnotationMirrorSet(); + if (hasPrimaryAnno) { + for (AnnotationMirror anno : type.getAnnotations()) { + bots.add(qh.getBottomAnnotation(anno)); + tops.add(qh.getTopAnnotation(anno)); + } + } + } + + @Override + public AbstractType create(AnnotatedTypeMirror atm, TypeMirror type) { + return InferenceType.create(atm, type, variable.map, qualifierVars, context); + } + + @Override + public boolean isObject() { + return false; + } + + @Override + public List getTypeParameterBounds() { + return null; + } + + @Override + public UseOfVariable capture(Java8InferenceContext context) { + return this; + } + + @Override + public UseOfVariable getErased() { + return this; + } + + @Override + public TypeVariable getJavaType() { + return variable.typeVariableJava; + } + + @Override + public AnnotatedTypeVariable getAnnotatedType() { + return type; + } + + @Override + public Kind getKind() { + return Kind.USE_OF_VARIABLE; + } + + @Override + public Collection getInferenceVariables() { + return Collections.singleton(variable); + } + + @Override + public AbstractType applyInstantiations() { + if (this.variable.getInstantiation() != null) { + return this.variable.getInstantiation(); + } + + return this; + } + + /** + * Returns the variable that this is a use of. + * + * @return the variable that this is a use of + */ + public Variable getVariable() { + return variable; + } + + /** + * Set whether this use has a throws bound. + * + * @param hasThrowsBound whether this use has a throws bound + */ + public void setHasThrowsBound(boolean hasThrowsBound) { + variable.getBounds().setHasThrowsBound(hasThrowsBound); + } + + /** + * Adds a qualifier bound for this variable, is this use does not have a primary annotation. + * + * @param kind the kind of bound + * @param annotations the qualifiers to add + */ + public void addQualifierBound(BoundKind kind, Set annotations) { + if (!hasPrimaryAnno) { + variable.getBounds().addQualifierBound(kind, annotations); + } + } + + /** + * Adds a bound for this variable, is this use does not have a primary annotation. + * + * @param kind the kind of bound + * @param bound the type of the bound + */ + public void addBound(BoundKind kind, AbstractType bound) { + if (!hasPrimaryAnno) { + variable.getBounds().addBound(kind, bound); + } else { + // If the use has a primary annotation, then add the bound but with that annotations + // set to bottom or top. This makes it so that the java type is still a bound, but + // the qualifiers do not change the results of inference. + if (kind == BoundKind.LOWER) { + bound.getAnnotatedType().replaceAnnotations(bots); + variable.getBounds().addBound(kind, bound); + } else if (kind == BoundKind.UPPER) { + bound.getAnnotatedType().replaceAnnotations(tops); + variable.getBounds().addBound(kind, bound); + } else { + AnnotatedTypeMirror copyATM = bound.getAnnotatedType().deepCopy(); + AbstractType boundCopy = bound.create(copyATM, bound.getJavaType()); + + bound.getAnnotatedType().replaceAnnotations(tops); + variable.getBounds().addBound(BoundKind.UPPER, bound); + + boundCopy.getAnnotatedType().replaceAnnotations(bots); + variable.getBounds().addBound(BoundKind.LOWER, boundCopy); + } + } + } + + @Override + public Set getQualifiers() { + if (hasPrimaryAnno) { + return AbstractQualifier.create( + getAnnotatedType().getAnnotations(), qualifierVars, context); + } else { + return Collections.emptySet(); + } + } + + @Override + public String toString() { + return "use of " + variable + (hasPrimaryAnno ? " with primary" : ""); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + UseOfVariable that = (UseOfVariable) o; + + if (hasPrimaryAnno != that.hasPrimaryAnno) { + return false; + } + if (variable != that.variable) { + return false; + } + if (!bots.equals(that.bots)) { + return false; + } + if (!tops.equals(that.tops)) { + return false; + } + + return type.equals(that.type); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + variable.hashCode(); + result = 31 * result + (hasPrimaryAnno ? 1 : 0); + result = 31 * result + bots.hashCode(); + result = 31 * result + tops.hashCode(); + result = 31 * result + type.hashCode(); + return result; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java index 95aa1279d6a..e110e7021e1 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/Variable.java @@ -1,11 +1,7 @@ package org.checkerframework.framework.util.typeinference8.types; import com.sun.source.tree.ExpressionTree; -import java.util.Iterator; -import java.util.Set; -import javax.lang.model.type.IntersectionType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; + import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; @@ -16,203 +12,213 @@ import org.checkerframework.javacutil.AnnotationMirrorMap; import org.checkerframework.javacutil.TypesUtils; +import java.util.Iterator; +import java.util.Set; + +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; + /** * An inference variable. It corresponds to a type argument for a particular method invocation, new * class tree or method reference that needs to be inferred. */ @Interned public class Variable { - /** Bounds of this variable. */ - protected final VariableBounds variableBounds; - - /** Identification number. Used only to make debugging easier. */ - protected final int id; - - /** - * The expression for which this variable is being solved. Used to differentiate inference - * variables for two different invocations of the same method or constructor. This is set during - * inference. - */ - protected final ExpressionTree invocation; - - /** Type variable for which the instantiation of this variable is a type argument, */ - protected final TypeVariable typeVariableJava; - - /** Type variable for which the instantiation of this variable is a type argument, */ - protected final AnnotatedTypeVariable typeVariable; - - /** A mapping from type variable to inference variable. */ - protected final Theta map; - - /** The context. */ - protected final Java8InferenceContext context; - - /** - * Creates a variable. - * - * @param typeVariable an annotated type variable - * @param typeVariableJava a java type variable - * @param invocation the invocation for which this variable is a type argument for - * @param context the context - * @param map a mapping from type variable to inference variable - */ - Variable( - AnnotatedTypeVariable typeVariable, - TypeVariable typeVariableJava, - ExpressionTree invocation, - Java8InferenceContext context, - Theta map) { - this(typeVariable, typeVariableJava, invocation, context, map, context.getNextVariableId()); - } - - /** - * Creates a variable. - * - * @param typeVariable an annotated type variable - * @param typeVariableJava a java type variable - * @param invocation the invocation for which this variable is a type argument for - * @param context the context - * @param map a mapping from type variable to inference variable - * @param id a unique number for this variable - */ - @SuppressWarnings("interning:argument") // "this" is interned - protected Variable( - AnnotatedTypeVariable typeVariable, - TypeVariable typeVariableJava, - ExpressionTree invocation, - Java8InferenceContext context, - Theta map, - int id) { - this.context = context; - assert typeVariable != null; - this.variableBounds = new VariableBounds(this, context); - this.typeVariableJava = typeVariableJava; - this.typeVariable = typeVariable; - this.invocation = invocation; - this.map = map; - this.id = id; - } - - /** - * Return this variable's current bounds. - * - * @return this variable's current bounds - */ - public VariableBounds getBounds() { - return variableBounds; - } - - /** - * Adds the initial bounds to this variable. These are the bounds implied by the upper bounds of - * the type variable. See end of JLS 18.1.3. - * - * @param map used to determine if the bounds refer to another variable - */ - public void initialBounds(Theta map) { - TypeMirror upperBound = typeVariable.getUpperBound().getUnderlyingType(); - // If Pl has no TypeBound, the bound {@literal al <: Object} appears in the set. Otherwise, - // for each type T delimited by & in the TypeBound, the bound {@literal al <: T[P1:=a1,..., - // Pp:=ap]} appears in the set; if this results in no proper upper bounds for al (only - // dependencies), then the bound {@literal al <: Object} also appears in the set. - switch (upperBound.getKind()) { - case INTERSECTION: - Iterator iter = - ((IntersectionType) upperBound).getBounds().iterator(); - for (AnnotatedTypeMirror bound : typeVariable.getUpperBound().directSupertypes()) { - AbstractType t1 = InferenceType.create(bound, iter.next(), map, context); - variableBounds.addBound(BoundKind.UPPER, t1); + /** Bounds of this variable. */ + protected final VariableBounds variableBounds; + + /** Identification number. Used only to make debugging easier. */ + protected final int id; + + /** + * The expression for which this variable is being solved. Used to differentiate inference + * variables for two different invocations of the same method or constructor. This is set during + * inference. + */ + protected final ExpressionTree invocation; + + /** Type variable for which the instantiation of this variable is a type argument, */ + protected final TypeVariable typeVariableJava; + + /** Type variable for which the instantiation of this variable is a type argument, */ + protected final AnnotatedTypeVariable typeVariable; + + /** A mapping from type variable to inference variable. */ + protected final Theta map; + + /** The context. */ + protected final Java8InferenceContext context; + + /** + * Creates a variable. + * + * @param typeVariable an annotated type variable + * @param typeVariableJava a java type variable + * @param invocation the invocation for which this variable is a type argument for + * @param context the context + * @param map a mapping from type variable to inference variable + */ + Variable( + AnnotatedTypeVariable typeVariable, + TypeVariable typeVariableJava, + ExpressionTree invocation, + Java8InferenceContext context, + Theta map) { + this(typeVariable, typeVariableJava, invocation, context, map, context.getNextVariableId()); + } + + /** + * Creates a variable. + * + * @param typeVariable an annotated type variable + * @param typeVariableJava a java type variable + * @param invocation the invocation for which this variable is a type argument for + * @param context the context + * @param map a mapping from type variable to inference variable + * @param id a unique number for this variable + */ + @SuppressWarnings("interning:argument") // "this" is interned + protected Variable( + AnnotatedTypeVariable typeVariable, + TypeVariable typeVariableJava, + ExpressionTree invocation, + Java8InferenceContext context, + Theta map, + int id) { + this.context = context; + assert typeVariable != null; + this.variableBounds = new VariableBounds(this, context); + this.typeVariableJava = typeVariableJava; + this.typeVariable = typeVariable; + this.invocation = invocation; + this.map = map; + this.id = id; + } + + /** + * Return this variable's current bounds. + * + * @return this variable's current bounds + */ + public VariableBounds getBounds() { + return variableBounds; + } + + /** + * Adds the initial bounds to this variable. These are the bounds implied by the upper bounds of + * the type variable. See end of JLS 18.1.3. + * + * @param map used to determine if the bounds refer to another variable + */ + public void initialBounds(Theta map) { + TypeMirror upperBound = typeVariable.getUpperBound().getUnderlyingType(); + // If Pl has no TypeBound, the bound {@literal al <: Object} appears in the set. Otherwise, + // for each type T delimited by & in the TypeBound, the bound {@literal al <: T[P1:=a1,..., + // Pp:=ap]} appears in the set; if this results in no proper upper bounds for al (only + // dependencies), then the bound {@literal al <: Object} also appears in the set. + switch (upperBound.getKind()) { + case INTERSECTION: + Iterator iter = + ((IntersectionType) upperBound).getBounds().iterator(); + for (AnnotatedTypeMirror bound : typeVariable.getUpperBound().directSupertypes()) { + AbstractType t1 = InferenceType.create(bound, iter.next(), map, context); + variableBounds.addBound(BoundKind.UPPER, t1); + } + break; + default: + AbstractType t1 = + InferenceType.create( + typeVariable.getUpperBound(), upperBound, map, context); + variableBounds.addBound(BoundKind.UPPER, t1); + break; } - break; - default: - AbstractType t1 = - InferenceType.create(typeVariable.getUpperBound(), upperBound, map, context); - variableBounds.addBound(BoundKind.UPPER, t1); - break; + + Set quals = + AbstractQualifier.create( + typeVariable.getLowerBound().getAnnotations(), + AnnotationMirrorMap.emptyMap(), + context); + variableBounds.addQualifierBound(BoundKind.LOWER, quals); + } + + /** + * Returns the invocation tree. + * + * @return the invocation tree + */ + public ExpressionTree getInvocation() { + return invocation; + } + + @SuppressWarnings("interning:not.interned") // Checking for exact object. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Variable variable = (Variable) o; + return TypesUtils.areSame(typeVariableJava, variable.typeVariableJava) + && invocation == variable.invocation; + } + + @Override + public int hashCode() { + int result = typeVariableJava.toString().hashCode(); + result = 31 * result + Kind.USE_OF_VARIABLE.hashCode(); + result = 31 * result + invocation.hashCode(); + return result; } - Set quals = - AbstractQualifier.create( - typeVariable.getLowerBound().getAnnotations(), AnnotationMirrorMap.emptyMap(), context); - variableBounds.addQualifierBound(BoundKind.LOWER, quals); - } - - /** - * Returns the invocation tree. - * - * @return the invocation tree - */ - public ExpressionTree getInvocation() { - return invocation; - } - - @SuppressWarnings("interning:not.interned") // Checking for exact object. - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + @Override + public String toString() { + if (variableBounds.hasInstantiation()) { + return "a" + id + " := " + variableBounds.getInstantiation(); + } + return "a" + id; } - if (o == null || getClass() != o.getClass()) { - return false; + + /** + * Returns the instantiation for this variable. + * + * @return the instantiation for this variable + */ + public ProperType getInstantiation() { + return variableBounds.getInstantiation(); + } + + /** in case the first attempt at resolution fails. */ + public void save() { + variableBounds.save(); + } + + /** + * Restore the bounds to the state previously saved. This method is called if the first attempt + * at resolution fails. + */ + public void restore() { + variableBounds.restore(); + } + + /** + * Returns whether this variable was created for a capture bound. + * + * @return whether this variable was created for a capture bound + */ + public boolean isCaptureVariable() { + return false; } - Variable variable = (Variable) o; - return TypesUtils.areSame(typeVariableJava, variable.typeVariableJava) - && invocation == variable.invocation; - } - - @Override - public int hashCode() { - int result = typeVariableJava.toString().hashCode(); - result = 31 * result + Kind.USE_OF_VARIABLE.hashCode(); - result = 31 * result + invocation.hashCode(); - return result; - } - - @Override - public String toString() { - if (variableBounds.hasInstantiation()) { - return "a" + id + " := " + variableBounds.getInstantiation(); + /** + * The Java type variable. + * + * @return the Java type variable + */ + public TypeVariable getJavaType() { + return typeVariableJava; } - return "a" + id; - } - - /** - * Returns the instantiation for this variable. - * - * @return the instantiation for this variable - */ - public ProperType getInstantiation() { - return variableBounds.getInstantiation(); - } - - /** in case the first attempt at resolution fails. */ - public void save() { - variableBounds.save(); - } - - /** - * Restore the bounds to the state previously saved. This method is called if the first attempt at - * resolution fails. - */ - public void restore() { - variableBounds.restore(); - } - - /** - * Returns whether this variable was created for a capture bound. - * - * @return whether this variable was created for a capture bound - */ - public boolean isCaptureVariable() { - return false; - } - - /** - * The Java type variable. - * - * @return the Java type variable - */ - public TypeVariable getJavaType() { - return typeVariableJava; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java index c36dd4494a3..01fb43400e0 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/VariableBounds.java @@ -1,13 +1,5 @@ package org.checkerframework.framework.util.typeinference8.types; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; import org.checkerframework.framework.util.typeinference8.constraint.Constraint.Kind; import org.checkerframework.framework.util.typeinference8.constraint.ConstraintSet; import org.checkerframework.framework.util.typeinference8.constraint.QualifierTyping; @@ -17,650 +9,662 @@ import org.checkerframework.javacutil.TypesUtils; import org.plumelib.util.IPair; -/** Data structure to stores the bounds of a variable. */ -public class VariableBounds { +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; - /** Kind of bound. */ - public enum BoundKind { - /** {@code other type <: this } */ - LOWER, - /** {@code this <: other type } */ - UPPER, - /** {@code this = other type } */ - EQUAL; - } - - /** The variable whose bounds this class represents. */ - private final Variable variable; - - /** The context. */ - private final Java8InferenceContext context; - - /** The type to which this variable is instantiated. */ - private ProperType instantiation = null; - - /** - * Bounds on this variable. Stored as a map from kind of bound (upper, lower, equal) to a set of - * {@link AbstractType}s. - */ - public final EnumMap> bounds = new EnumMap<>(BoundKind.class); - - /** - * Qualifier bounds on this variable. Stored as a map from kind of bound (upper, lower, equal) to - * a set of {@link AnnotationMirror}s. A qualifier bound is a bound on the primary annotation of - * this variable. - */ - public final EnumMap> qualifierBounds = - new EnumMap<>(BoundKind.class); - - /** Constraints implied by complementary pairs of bounds found during incorporation. */ - public final ConstraintSet constraints = new ConstraintSet(); - - /** Whether this variable has a throws bounds. */ - public boolean hasThrowsBound = false; - - /** Saved bounds used in the event the first attempt at resolution fails. */ - public EnumMap> savedBounds = null; - - /** Saved qualifier bounds used in the event the first attempt at resolution fails. */ - public EnumMap> savedQualifierBounds = null; - - /** - * Creates bounds for {@code variable}. - * - * @param variable a variable - * @param context the context - */ - public VariableBounds(Variable variable, Java8InferenceContext context) { - this.variable = variable; - this.context = context; - bounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); - bounds.put(BoundKind.UPPER, new LinkedHashSet<>()); - bounds.put(BoundKind.LOWER, new LinkedHashSet<>()); - - qualifierBounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); - qualifierBounds.put(BoundKind.UPPER, new LinkedHashSet<>()); - qualifierBounds.put(BoundKind.LOWER, new LinkedHashSet<>()); - } - - /** Save the current bounds in case the first attempt at resolution fails. */ - public void save() { - savedBounds = new EnumMap<>(BoundKind.class); - savedBounds.put(BoundKind.EQUAL, new LinkedHashSet<>(bounds.get(BoundKind.EQUAL))); - savedBounds.put(BoundKind.UPPER, new LinkedHashSet<>(bounds.get(BoundKind.UPPER))); - savedBounds.put(BoundKind.LOWER, new LinkedHashSet<>(bounds.get(BoundKind.LOWER))); - - savedQualifierBounds = new EnumMap<>(BoundKind.class); - savedQualifierBounds.put( - BoundKind.EQUAL, new LinkedHashSet<>(qualifierBounds.get(BoundKind.EQUAL))); - savedQualifierBounds.put( - BoundKind.UPPER, new LinkedHashSet<>(qualifierBounds.get(BoundKind.UPPER))); - savedQualifierBounds.put( - BoundKind.LOWER, new LinkedHashSet<>(qualifierBounds.get(BoundKind.LOWER))); - } - - /** - * Restore the bounds to the state previously saved. This method is called if the first attempt at - * resolution fails. - */ - public void restore() { - assert savedBounds != null; - instantiation = null; - bounds.clear(); - bounds.put(BoundKind.EQUAL, new LinkedHashSet<>(savedBounds.get(BoundKind.EQUAL))); - bounds.put(BoundKind.UPPER, new LinkedHashSet<>(savedBounds.get(BoundKind.UPPER))); - bounds.put(BoundKind.LOWER, new LinkedHashSet<>(savedBounds.get(BoundKind.LOWER))); - for (AbstractType t : bounds.get(BoundKind.EQUAL)) { - if (t.isProper()) { - instantiation = (ProperType) t; - } - } - qualifierBounds.clear(); - qualifierBounds.put( - BoundKind.EQUAL, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.EQUAL))); - qualifierBounds.put( - BoundKind.UPPER, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.UPPER))); - qualifierBounds.put( - BoundKind.LOWER, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.LOWER))); - } - - /** - * Return true if this has a throws bound. - * - * @return true if this has a throws bound - */ - public boolean hasThrowsBound() { - return hasThrowsBound; - } - - /** - * Set has throws bound - * - * @param b has thrown bound - */ - public void setHasThrowsBound(boolean b) { - hasThrowsBound = b; - } - - /** - * Adds {@code otherType} as bound against this variable. - * - * @param kind the kind of bound - * @param otherType the bound type - * @return if a new bound was added - */ - public boolean addBound(BoundKind kind, AbstractType otherType) { - if (otherType.isUseOfVariable() && ((UseOfVariable) otherType).getVariable() == variable) { - return false; - } - if (kind == BoundKind.EQUAL && otherType.isProper()) { - instantiation = ((ProperType) otherType).boxType(); - } - if (bounds.get(kind).add(otherType)) { - addConstraintsFromComplementaryBounds(kind, otherType); - Set aQuals = otherType.getQualifiers(); - addConstraintsFromComplementaryQualifierBounds(kind, aQuals); - return true; - } - return false; - } - - /** - * Adds {@code qualifiers} as a qualifier bound against this variable. - * - * @param kind the kind of bound - * @param qualifiers the qualifiers - */ - public void addQualifierBound(BoundKind kind, Set qualifiers) { - addConstraintsFromComplementaryQualifierBounds(kind, qualifiers); - addConstraintsFromComplementaryBounds(kind, qualifiers); - qualifierBounds.get(kind).addAll(qualifiers); - } - - /** - * Add constraints created via incorporation of the bound. See JLS 18.3.1. - * - * @param kind the kind of bound - * @param qualifiers the qualifiers - */ - public void addConstraintsFromComplementaryQualifierBounds( - BoundKind kind, Set qualifiers) { - Set equalBounds = qualifierBounds.get(BoundKind.EQUAL); - if (kind == BoundKind.EQUAL) { - addQualifierConstraint(qualifiers, equalBounds, Kind.QUALIFIER_EQUALITY); - } else if (kind == BoundKind.LOWER) { - addQualifierConstraint(qualifiers, equalBounds, Kind.QUALIFIER_SUBTYPE); - } else { // UPPER - addQualifierConstraint(equalBounds, qualifiers, Kind.QUALIFIER_SUBTYPE); - } +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeKind; - if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { - addQualifierConstraint( - qualifierBounds.get(BoundKind.LOWER), qualifiers, Kind.QUALIFIER_SUBTYPE); - } +/** Data structure to stores the bounds of a variable. */ +public class VariableBounds { - if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { - addQualifierConstraint( - qualifiers, qualifierBounds.get(BoundKind.UPPER), Kind.QUALIFIER_SUBTYPE); - } - } - - /** - * Add a {@link QualifierTyping} constraint for a qualifier in {@code setT} and the qualifier in - * {@code setS} in the same hierarchy. - * - * @param setT a set of abstract qualifiers on the left side of the constraint - * @param setS a set of abstract qualifiers on the right side of the constraint - * @param kind the kind of constraint - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - private void addQualifierConstraint( - Set setT, Set setS, Kind kind) { - for (AbstractQualifier t : setT) { - for (AbstractQualifier s : setS) { - if (s != t && s.sameHierarchy(t)) { - constraints.add(new QualifierTyping(t, s, kind)); - } - } - } - } - - /** - * Add constraints created via incorporation of the bound. See JLS 18.3.1. - * - * @param kind the kind of bound - * @param s the type of the bound - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - public void addConstraintsFromComplementaryBounds(BoundKind kind, AbstractType s) { - switch (kind) { - case EQUAL: + /** Kind of bound. */ + public enum BoundKind { + /** {@code other type <: this } */ + LOWER, + /** {@code this <: other type } */ + UPPER, + /** {@code this = other type } */ + EQUAL; + } + + /** The variable whose bounds this class represents. */ + private final Variable variable; + + /** The context. */ + private final Java8InferenceContext context; + + /** The type to which this variable is instantiated. */ + private ProperType instantiation = null; + + /** + * Bounds on this variable. Stored as a map from kind of bound (upper, lower, equal) to a set of + * {@link AbstractType}s. + */ + public final EnumMap> bounds = new EnumMap<>(BoundKind.class); + + /** + * Qualifier bounds on this variable. Stored as a map from kind of bound (upper, lower, equal) + * to a set of {@link AnnotationMirror}s. A qualifier bound is a bound on the primary annotation + * of this variable. + */ + public final EnumMap> qualifierBounds = + new EnumMap<>(BoundKind.class); + + /** Constraints implied by complementary pairs of bounds found during incorporation. */ + public final ConstraintSet constraints = new ConstraintSet(); + + /** Whether this variable has a throws bounds. */ + public boolean hasThrowsBound = false; + + /** Saved bounds used in the event the first attempt at resolution fails. */ + public EnumMap> savedBounds = null; + + /** Saved qualifier bounds used in the event the first attempt at resolution fails. */ + public EnumMap> savedQualifierBounds = null; + + /** + * Creates bounds for {@code variable}. + * + * @param variable a variable + * @param context the context + */ + public VariableBounds(Variable variable, Java8InferenceContext context) { + this.variable = variable; + this.context = context; + bounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); + bounds.put(BoundKind.UPPER, new LinkedHashSet<>()); + bounds.put(BoundKind.LOWER, new LinkedHashSet<>()); + + qualifierBounds.put(BoundKind.EQUAL, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.UPPER, new LinkedHashSet<>()); + qualifierBounds.put(BoundKind.LOWER, new LinkedHashSet<>()); + } + + /** Save the current bounds in case the first attempt at resolution fails. */ + public void save() { + savedBounds = new EnumMap<>(BoundKind.class); + savedBounds.put(BoundKind.EQUAL, new LinkedHashSet<>(bounds.get(BoundKind.EQUAL))); + savedBounds.put(BoundKind.UPPER, new LinkedHashSet<>(bounds.get(BoundKind.UPPER))); + savedBounds.put(BoundKind.LOWER, new LinkedHashSet<>(bounds.get(BoundKind.LOWER))); + + savedQualifierBounds = new EnumMap<>(BoundKind.class); + savedQualifierBounds.put( + BoundKind.EQUAL, new LinkedHashSet<>(qualifierBounds.get(BoundKind.EQUAL))); + savedQualifierBounds.put( + BoundKind.UPPER, new LinkedHashSet<>(qualifierBounds.get(BoundKind.UPPER))); + savedQualifierBounds.put( + BoundKind.LOWER, new LinkedHashSet<>(qualifierBounds.get(BoundKind.LOWER))); + } + + /** + * Restore the bounds to the state previously saved. This method is called if the first attempt + * at resolution fails. + */ + public void restore() { + assert savedBounds != null; + instantiation = null; + bounds.clear(); + bounds.put(BoundKind.EQUAL, new LinkedHashSet<>(savedBounds.get(BoundKind.EQUAL))); + bounds.put(BoundKind.UPPER, new LinkedHashSet<>(savedBounds.get(BoundKind.UPPER))); + bounds.put(BoundKind.LOWER, new LinkedHashSet<>(savedBounds.get(BoundKind.LOWER))); for (AbstractType t : bounds.get(BoundKind.EQUAL)) { - if (s != t) { - constraints.add(new Typing(s, t, Kind.TYPE_EQUALITY)); - } + if (t.isProper()) { + instantiation = (ProperType) t; + } } - break; - case LOWER: - for (AbstractType t : bounds.get(BoundKind.EQUAL)) { - if (s != t) { - constraints.add(new Typing(s, t, Kind.SUBTYPE)); - } + qualifierBounds.clear(); + qualifierBounds.put( + BoundKind.EQUAL, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.EQUAL))); + qualifierBounds.put( + BoundKind.UPPER, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.UPPER))); + qualifierBounds.put( + BoundKind.LOWER, new LinkedHashSet<>(savedQualifierBounds.get(BoundKind.LOWER))); + } + + /** + * Return true if this has a throws bound. + * + * @return true if this has a throws bound + */ + public boolean hasThrowsBound() { + return hasThrowsBound; + } + + /** + * Set has throws bound + * + * @param b has thrown bound + */ + public void setHasThrowsBound(boolean b) { + hasThrowsBound = b; + } + + /** + * Adds {@code otherType} as bound against this variable. + * + * @param kind the kind of bound + * @param otherType the bound type + * @return if a new bound was added + */ + public boolean addBound(BoundKind kind, AbstractType otherType) { + if (otherType.isUseOfVariable() && ((UseOfVariable) otherType).getVariable() == variable) { + return false; } - break; - case UPPER: - for (AbstractType t : bounds.get(BoundKind.EQUAL)) { - if (s != t) { - constraints.add(new Typing(t, s, Kind.SUBTYPE)); - } + if (kind == BoundKind.EQUAL && otherType.isProper()) { + instantiation = ((ProperType) otherType).boxType(); + } + if (bounds.get(kind).add(otherType)) { + addConstraintsFromComplementaryBounds(kind, otherType); + Set aQuals = otherType.getQualifiers(); + addConstraintsFromComplementaryQualifierBounds(kind, aQuals); + return true; + } + return false; + } + + /** + * Adds {@code qualifiers} as a qualifier bound against this variable. + * + * @param kind the kind of bound + * @param qualifiers the qualifiers + */ + public void addQualifierBound(BoundKind kind, Set qualifiers) { + addConstraintsFromComplementaryQualifierBounds(kind, qualifiers); + addConstraintsFromComplementaryBounds(kind, qualifiers); + qualifierBounds.get(kind).addAll(qualifiers); + } + + /** + * Add constraints created via incorporation of the bound. See JLS 18.3.1. + * + * @param kind the kind of bound + * @param qualifiers the qualifiers + */ + public void addConstraintsFromComplementaryQualifierBounds( + BoundKind kind, Set qualifiers) { + Set equalBounds = qualifierBounds.get(BoundKind.EQUAL); + if (kind == BoundKind.EQUAL) { + addQualifierConstraint(qualifiers, equalBounds, Kind.QUALIFIER_EQUALITY); + } else if (kind == BoundKind.LOWER) { + addQualifierConstraint(qualifiers, equalBounds, Kind.QUALIFIER_SUBTYPE); + } else { // UPPER + addQualifierConstraint(equalBounds, qualifiers, Kind.QUALIFIER_SUBTYPE); } - break; - } - if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { - for (AbstractType t : bounds.get(BoundKind.LOWER)) { - if (s != t) { - constraints.add(new Typing(t, s, Kind.SUBTYPE)); + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + addQualifierConstraint( + qualifierBounds.get(BoundKind.LOWER), qualifiers, Kind.QUALIFIER_SUBTYPE); } - } - } - if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { - for (AbstractType t : bounds.get(BoundKind.UPPER)) { - if (s != t) { - constraints.add(new Typing(s, t, Kind.SUBTYPE)); + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + addQualifierConstraint( + qualifiers, qualifierBounds.get(BoundKind.UPPER), Kind.QUALIFIER_SUBTYPE); } - } } - if (kind == BoundKind.UPPER) { - // When a bound set contains a pair of bounds var <: S and var <: T, and there exists - // a supertype of S of the form G and - // a supertype of T of the form G (for some generic class or interface, G), - // then for all i (1 <= i <= n), if Si and Ti are types (not wildcards), - // the constraint formula is implied. - if (s.isInferenceType() || s.isProper()) { - for (AbstractType t : bounds.get(BoundKind.LOWER)) { - if (t.isProper() || t.isInferenceType()) { - constraints.addAll(getConstraintsFromParameterized(s, t)); - } - } - } - } - } - - /** - * Adds constraints from complementary bounds. - * - * @param kind kind of bound - * @param s qualifiers - */ - public void addConstraintsFromComplementaryBounds( - BoundKind kind, Set s) { - // Copy bound to equal variables - for (AbstractType t : bounds.get(BoundKind.EQUAL)) { - if (t.isUseOfVariable()) { - VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); - otherBounds.qualifierBounds.get(kind).addAll(s); - } + /** + * Add a {@link QualifierTyping} constraint for a qualifier in {@code setT} and the qualifier in + * {@code setS} in the same hierarchy. + * + * @param setT a set of abstract qualifiers on the left side of the constraint + * @param setS a set of abstract qualifiers on the right side of the constraint + * @param kind the kind of constraint + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + private void addQualifierConstraint( + Set setT, + Set setS, + Kind kind) { + for (AbstractQualifier t : setT) { + for (AbstractQualifier s : setS) { + if (s != t && s.sameHierarchy(t)) { + constraints.add(new QualifierTyping(t, s, kind)); + } + } + } } - if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { - for (AbstractType t : bounds.get(BoundKind.LOWER)) { - if (t.isUseOfVariable()) { - VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); - otherBounds.qualifierBounds.get(BoundKind.UPPER).addAll(s); + /** + * Add constraints created via incorporation of the bound. See JLS 18.3.1. + * + * @param kind the kind of bound + * @param s the type of the bound + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public void addConstraintsFromComplementaryBounds(BoundKind kind, AbstractType s) { + switch (kind) { + case EQUAL: + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new Typing(s, t, Kind.TYPE_EQUALITY)); + } + } + break; + case LOWER: + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new Typing(s, t, Kind.SUBTYPE)); + } + } + break; + case UPPER: + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (s != t) { + constraints.add(new Typing(t, s, Kind.SUBTYPE)); + } + } + break; } - } - } - if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { - for (AbstractType t : bounds.get(BoundKind.UPPER)) { - if (t.isUseOfVariable()) { - VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); - otherBounds.qualifierBounds.get(BoundKind.LOWER).addAll(s); + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + for (AbstractType t : bounds.get(BoundKind.LOWER)) { + if (s != t) { + constraints.add(new Typing(t, s, Kind.SUBTYPE)); + } + } } - } - } - } - - /** - * Returns the constraints between the type arguments to {@code s} and {@code t}. - * - *

          If the there exists a supertype of S of the form {@code G} and a supertype of T - * of the form {@code G} (for some generic class or interface, G), then for all i - * ({@code 1 <= i <= n}), if Si and Ti are types (not wildcards), the constraint formula {@code - * } is implied. - * - * @param s a type argument - * @param t a type argument - * @return the constraints between the type arguments to {@code s} and {@code t} - */ - private List getConstraintsFromParameterized(AbstractType s, AbstractType t) { - IPair pair = - context.inferenceTypeFactory.getParameterizedSupers(s, t); - - if (pair == null) { - return new ArrayList<>(); - } - List ss = pair.first.getTypeArguments(); - List ts = pair.second.getTypeArguments(); - assert ss.size() == ts.size(); - - List constraints = new ArrayList<>(); - for (int i = 0; i < ss.size(); i++) { - AbstractType si = ss.get(i); - AbstractType ti = ts.get(i); - if (si.getTypeKind() != TypeKind.WILDCARD && ti.getTypeKind() != TypeKind.WILDCARD) { - constraints.add(new Typing(si, ti, Kind.TYPE_EQUALITY)); - } - } - return constraints; - } - - /** - * Returns whether this variable only has bounds against proper types. - * - * @return whether this variable only has bounds against proper types. - */ - public boolean onlyProperBounds() { - for (BoundKind k : BoundKind.values()) { - for (AbstractType bound : bounds.get(k)) { - if (!bound.isProper()) { - return false; - } - } - } - return true; - } - - /** - * Return all lower bounds that are proper types. - * - * @return all lower bounds that are proper types - */ - public Set findProperLowerBounds() { - LinkedHashSet set = new LinkedHashSet<>(); - for (AbstractType bound : bounds.get(BoundKind.LOWER)) { - if (bound.isProper()) { - set.add((ProperType) bound); - } - } - return set; - } - - /** - * Returns all upper bounds that proper types. - * - * @return all upper bounds that are proper types - */ - public Set findProperUpperBounds() { - LinkedHashSet set = new LinkedHashSet<>(); - for (AbstractType bound : bounds.get(BoundKind.UPPER)) { - if (bound.isProper()) { - set.add((ProperType) bound); - } - } - return set; - } - - /** - * Returns all upper bounds. - * - * @return all upper bounds - */ - public Set upperBounds() { - LinkedHashSet set = new LinkedHashSet<>(); - for (AbstractType bound : bounds.get(BoundKind.UPPER)) { - if (!bound.isUseOfVariable()) { - set.add(bound); - } - } - return set; - } - - /** - * Apply instantiations to all bounds and constraints of this variable. - * - * @return whether any of the bounds changed - */ - @SuppressWarnings("interning:not.interned") // Checking for exact object. - public boolean applyInstantiationsToBounds() { - boolean changed = false; - for (Set boundList : bounds.values()) { - LinkedHashSet newBounds = new LinkedHashSet<>(boundList.size()); - for (AbstractType bound : boundList) { - AbstractType newBound = bound.applyInstantiations(); - if (newBound != bound && !boundList.contains(newBound)) { - changed = true; - } - newBounds.add(newBound); - } - boundList.clear(); - boundList.addAll(newBounds); - } - constraints.applyInstantiations(); + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + for (AbstractType t : bounds.get(BoundKind.UPPER)) { + if (s != t) { + constraints.add(new Typing(s, t, Kind.SUBTYPE)); + } + } + } - if (changed && instantiation == null) { - for (AbstractType bound : bounds.get(BoundKind.EQUAL)) { - if (bound.isProper()) { - instantiation = ((ProperType) bound).boxType(); + if (kind == BoundKind.UPPER) { + // When a bound set contains a pair of bounds var <: S and var <: T, and there exists + // a supertype of S of the form G and + // a supertype of T of the form G (for some generic class or interface, G), + // then for all i (1 <= i <= n), if Si and Ti are types (not wildcards), + // the constraint formula is implied. + if (s.isInferenceType() || s.isProper()) { + for (AbstractType t : bounds.get(BoundKind.LOWER)) { + if (t.isProper() || t.isInferenceType()) { + constraints.addAll(getConstraintsFromParameterized(s, t)); + } + } + } } - } - } - return changed; - } - - /** - * Return all variables mentioned in a bound against this variable. - * - * @return all variables mentioned in a bound against this variable - */ - public Collection getVariablesMentionedInBounds() { - List mentioned = new ArrayList<>(); - for (Set boundList : bounds.values()) { - for (AbstractType bound : boundList) { - mentioned.addAll(bound.getInferenceVariables()); - } - } - return mentioned; - } - - /** - * Returns the instantiation of this variable. - * - * @return the instantiation of this variable - */ - public ProperType getInstantiation() { - return instantiation; - } - - /** - * Return true if this has an instantiation. - * - * @return true if this has an instantiation - */ - public boolean hasInstantiation() { - return instantiation != null; - } - - /** - * Returns true if any bound mentions a primitive wrapper type. - * - * @return true if any bound mentions a primitive wrapper type - */ - public boolean hasPrimitiveWrapperBound() { - for (Set boundList : bounds.values()) { - for (AbstractType bound : boundList) { - if (bound.isProper() && TypesUtils.isBoxedPrimitive(bound.getJavaType())) { - return true; - } - } - } - return false; - } - - /** - * Returns true if any lower or equal bound is a parameterized type with at least one wildcard as - * a type argument. - * - * @return true if any lower or equal bound is a parameterized type with at least one wildcard for - * a type argument - */ - public boolean hasWildcardParameterizedLowerOrEqualBound() { - for (AbstractType type : bounds.get(BoundKind.EQUAL)) { - if (!type.isUseOfVariable() && type.isWildcardParameterizedType()) { - return true; - } - } - for (AbstractType type : bounds.get(BoundKind.LOWER)) { - if (!type.isUseOfVariable() && type.isWildcardParameterizedType()) { - return true; - } - } - return false; - } - - /** - * Does this bound set contain two bounds of the forms {@code S1 <: var} and {@code S2 <: var}, - * where S1 and S2 have supertypes that are two different parameterizations of the same generic - * class or interface? - * - * @return whether this bound set contain two bounds of the forms {@code S1 <: var} and {@code S2 - * <: var}, where S1 and S2 have supertypes that are two different parameterizations of the - * same generic class or interface - */ - public boolean hasLowerBoundDifferentParam() { - List parameteredTypes = new ArrayList<>(); - for (AbstractType type : bounds.get(BoundKind.LOWER)) { - if (!type.isUseOfVariable() && type.isParameterizedType()) { - parameteredTypes.add(type); - } - } - for (int i = 0; i < parameteredTypes.size(); i++) { - AbstractType s1 = parameteredTypes.get(i); - for (int j = i + 1; j < parameteredTypes.size(); j++) { - AbstractType s2 = parameteredTypes.get(j); - IPair supers = - context.inferenceTypeFactory.getParameterizedSupers(s1, s2); - if (supers == null) { - continue; - } - List s1TypeArgs = supers.first.getTypeArguments(); - List s2TypeArgs = supers.second.getTypeArguments(); - if (!s1TypeArgs.equals(s2TypeArgs)) { - return true; - } - } } - return false; - } - - /** - * Returns true if there exists an equal or lower bound against a type, S, such that S is not a - * subtype of {@code G<...>}, but S is a subtype of the raw type {@code |G<...>|}, where {@code G} - * a generic class or interface for which the parameter of this method, {@code t}, is a - * parameterization. - * - * @param t a parameterization of a generic class or interface, {@code G} - * @return true if there exists an equal or lower bound against a type, S, such that S is not a - * subtype of {@code G<...>}, but S is a subtype of the raw type {@code |G<...>|}, where - * {@code G} a generic class or interface for which the parameter of this method, {@code t}, - * is a parameterization. - */ - public boolean hasRawTypeLowerOrEqualBound(AbstractType t) { - for (AbstractType type : bounds.get(BoundKind.LOWER)) { - if (type.isUseOfVariable()) { - continue; - } - AbstractType superTypeOfS = type.asSuper(t.getJavaType()); - if (superTypeOfS != null && superTypeOfS.isRaw()) { - return true; - } + /** + * Adds constraints from complementary bounds. + * + * @param kind kind of bound + * @param s qualifiers + */ + public void addConstraintsFromComplementaryBounds( + BoundKind kind, Set s) { + // Copy bound to equal variables + for (AbstractType t : bounds.get(BoundKind.EQUAL)) { + if (t.isUseOfVariable()) { + VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); + otherBounds.qualifierBounds.get(kind).addAll(s); + } + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.UPPER) { + for (AbstractType t : bounds.get(BoundKind.LOWER)) { + if (t.isUseOfVariable()) { + VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); + otherBounds.qualifierBounds.get(BoundKind.UPPER).addAll(s); + } + } + } + + if (kind == BoundKind.EQUAL || kind == BoundKind.LOWER) { + for (AbstractType t : bounds.get(BoundKind.UPPER)) { + if (t.isUseOfVariable()) { + VariableBounds otherBounds = ((UseOfVariable) t).getVariable().getBounds(); + otherBounds.qualifierBounds.get(BoundKind.LOWER).addAll(s); + } + } + } } - for (AbstractType type : bounds.get(BoundKind.EQUAL)) { - if (type.isUseOfVariable()) { - continue; - } - AbstractType superTypeOfS = type.asSuper(t.getJavaType()); - if (superTypeOfS != null && superTypeOfS.isRaw()) { + /** + * Returns the constraints between the type arguments to {@code s} and {@code t}. + * + *

          If the there exists a supertype of S of the form {@code G} and a supertype of + * T of the form {@code G} (for some generic class or interface, G), then for all i + * ({@code 1 <= i <= n}), if Si and Ti are types (not wildcards), the constraint formula {@code + * } is implied. + * + * @param s a type argument + * @param t a type argument + * @return the constraints between the type arguments to {@code s} and {@code t} + */ + private List getConstraintsFromParameterized(AbstractType s, AbstractType t) { + IPair pair = + context.inferenceTypeFactory.getParameterizedSupers(s, t); + + if (pair == null) { + return new ArrayList<>(); + } + + List ss = pair.first.getTypeArguments(); + List ts = pair.second.getTypeArguments(); + assert ss.size() == ts.size(); + + List constraints = new ArrayList<>(); + for (int i = 0; i < ss.size(); i++) { + AbstractType si = ss.get(i); + AbstractType ti = ts.get(i); + if (si.getTypeKind() != TypeKind.WILDCARD && ti.getTypeKind() != TypeKind.WILDCARD) { + constraints.add(new Typing(si, ti, Kind.TYPE_EQUALITY)); + } + } + return constraints; + } + + /** + * Returns whether this variable only has bounds against proper types. + * + * @return whether this variable only has bounds against proper types. + */ + public boolean onlyProperBounds() { + for (BoundKind k : BoundKind.values()) { + for (AbstractType bound : bounds.get(k)) { + if (!bound.isProper()) { + return false; + } + } + } return true; - } - } - return false; - } - - /** - * Returns the constraints generated when incorporating a capture bound. See JLS 18.3.2. - * - * @param Ai the captured type argument - * @param Bi the bound of the type variable - * @return constraints generated when incorporating a capture bound - */ - public ConstraintSet getWildcardConstraints(AbstractType Ai, AbstractType Bi) { - ConstraintSet constraintSet = new ConstraintSet(); - - // Only concerned with bounds against proper types or inference types. - List upperBoundsNonVar = new ArrayList<>(); - for (AbstractType bound : bounds.get(VariableBounds.BoundKind.UPPER)) { - if (bound.isProper() || bound.isInferenceType()) { - upperBoundsNonVar.add(bound); - } - } - List lowerBoundsNonVar = new ArrayList<>(); - for (AbstractType bound : bounds.get(VariableBounds.BoundKind.LOWER)) { - if (bound.isProper() || bound.isInferenceType()) { - lowerBoundsNonVar.add(bound); - } } - for (AbstractType bound : bounds.get(VariableBounds.BoundKind.EQUAL)) { - if (bound.isProper() || bound.isInferenceType()) { - // var = R implies the bound false - return null; - } - } + /** + * Return all lower bounds that are proper types. + * + * @return all lower bounds that are proper types + */ + public Set findProperLowerBounds() { + LinkedHashSet set = new LinkedHashSet<>(); + for (AbstractType bound : bounds.get(BoundKind.LOWER)) { + if (bound.isProper()) { + set.add((ProperType) bound); + } + } + return set; + } + + /** + * Returns all upper bounds that proper types. + * + * @return all upper bounds that are proper types + */ + public Set findProperUpperBounds() { + LinkedHashSet set = new LinkedHashSet<>(); + for (AbstractType bound : bounds.get(BoundKind.UPPER)) { + if (bound.isProper()) { + set.add((ProperType) bound); + } + } + return set; + } + + /** + * Returns all upper bounds. + * + * @return all upper bounds + */ + public Set upperBounds() { + LinkedHashSet set = new LinkedHashSet<>(); + for (AbstractType bound : bounds.get(BoundKind.UPPER)) { + if (!bound.isUseOfVariable()) { + set.add(bound); + } + } + return set; + } + + /** + * Apply instantiations to all bounds and constraints of this variable. + * + * @return whether any of the bounds changed + */ + @SuppressWarnings("interning:not.interned") // Checking for exact object. + public boolean applyInstantiationsToBounds() { + boolean changed = false; + for (Set boundList : bounds.values()) { + LinkedHashSet newBounds = new LinkedHashSet<>(boundList.size()); + for (AbstractType bound : boundList) { + AbstractType newBound = bound.applyInstantiations(); + if (newBound != bound && !boundList.contains(newBound)) { + changed = true; + } + newBounds.add(newBound); + } + boundList.clear(); + boundList.addAll(newBounds); + } + constraints.applyInstantiations(); + + if (changed && instantiation == null) { + for (AbstractType bound : bounds.get(BoundKind.EQUAL)) { + if (bound.isProper()) { + instantiation = ((ProperType) bound).boxType(); + } + } + } + return changed; + } + + /** + * Return all variables mentioned in a bound against this variable. + * + * @return all variables mentioned in a bound against this variable + */ + public Collection getVariablesMentionedInBounds() { + List mentioned = new ArrayList<>(); + for (Set boundList : bounds.values()) { + for (AbstractType bound : boundList) { + mentioned.addAll(bound.getInferenceVariables()); + } + } + return mentioned; + } + + /** + * Returns the instantiation of this variable. + * + * @return the instantiation of this variable + */ + public ProperType getInstantiation() { + return instantiation; + } + + /** + * Return true if this has an instantiation. + * + * @return true if this has an instantiation + */ + public boolean hasInstantiation() { + return instantiation != null; + } + + /** + * Returns true if any bound mentions a primitive wrapper type. + * + * @return true if any bound mentions a primitive wrapper type + */ + public boolean hasPrimitiveWrapperBound() { + for (Set boundList : bounds.values()) { + for (AbstractType bound : boundList) { + if (bound.isProper() && TypesUtils.isBoxedPrimitive(bound.getJavaType())) { + return true; + } + } + } + return false; + } + + /** + * Returns true if any lower or equal bound is a parameterized type with at least one wildcard + * as a type argument. + * + * @return true if any lower or equal bound is a parameterized type with at least one wildcard + * for a type argument + */ + public boolean hasWildcardParameterizedLowerOrEqualBound() { + for (AbstractType type : bounds.get(BoundKind.EQUAL)) { + if (!type.isUseOfVariable() && type.isWildcardParameterizedType()) { + return true; + } + } + for (AbstractType type : bounds.get(BoundKind.LOWER)) { + if (!type.isUseOfVariable() && type.isWildcardParameterizedType()) { + return true; + } + } + return false; + } + + /** + * Does this bound set contain two bounds of the forms {@code S1 <: var} and {@code S2 <: var}, + * where S1 and S2 have supertypes that are two different parameterizations of the same generic + * class or interface? + * + * @return whether this bound set contain two bounds of the forms {@code S1 <: var} and {@code + * S2 <: var}, where S1 and S2 have supertypes that are two different parameterizations of + * the same generic class or interface + */ + public boolean hasLowerBoundDifferentParam() { + List parameteredTypes = new ArrayList<>(); + for (AbstractType type : bounds.get(BoundKind.LOWER)) { + if (!type.isUseOfVariable() && type.isParameterizedType()) { + parameteredTypes.add(type); + } + } + for (int i = 0; i < parameteredTypes.size(); i++) { + AbstractType s1 = parameteredTypes.get(i); + for (int j = i + 1; j < parameteredTypes.size(); j++) { + AbstractType s2 = parameteredTypes.get(j); + IPair supers = + context.inferenceTypeFactory.getParameterizedSupers(s1, s2); + if (supers == null) { + continue; + } + List s1TypeArgs = supers.first.getTypeArguments(); + List s2TypeArgs = supers.second.getTypeArguments(); + if (!s1TypeArgs.equals(s2TypeArgs)) { + return true; + } + } + } + + return false; + } + + /** + * Returns true if there exists an equal or lower bound against a type, S, such that S is not a + * subtype of {@code G<...>}, but S is a subtype of the raw type {@code |G<...>|}, where {@code + * G} a generic class or interface for which the parameter of this method, {@code t}, is a + * parameterization. + * + * @param t a parameterization of a generic class or interface, {@code G} + * @return true if there exists an equal or lower bound against a type, S, such that S is not a + * subtype of {@code G<...>}, but S is a subtype of the raw type {@code |G<...>|}, where + * {@code G} a generic class or interface for which the parameter of this method, {@code t}, + * is a parameterization. + */ + public boolean hasRawTypeLowerOrEqualBound(AbstractType t) { + for (AbstractType type : bounds.get(BoundKind.LOWER)) { + if (type.isUseOfVariable()) { + continue; + } + AbstractType superTypeOfS = type.asSuper(t.getJavaType()); + if (superTypeOfS != null && superTypeOfS.isRaw()) { + return true; + } + } - if (Ai.isUnboundWildcard()) { - // R <: var implies the bound false - if (!lowerBoundsNonVar.isEmpty()) { - return null; - } - // var <: R implies the constraint formula - } else if (Ai.isUpperBoundedWildcard()) { - // R <: var implies the bound false - if (!lowerBoundsNonVar.isEmpty()) { - return null; - } - AbstractType T = Ai.getWildcardUpperBound(); - if (Bi.isObject()) { - // If Bi is Object, then var <: R implies the constraint formula - for (AbstractType r : upperBoundsNonVar) { - constraintSet.add(new Typing(T, r, TypeConstraint.Kind.SUBTYPE)); - } - } else if (T.isObject()) { - // If T is Object, then var <: R implies the constraint formula - for (AbstractType r : upperBoundsNonVar) { - constraintSet.add(new Typing(Bi, r, TypeConstraint.Kind.SUBTYPE)); - } - } - // else no constraint - } else { - // Super bounded wildcard - // var <: R implies the constraint formula - for (AbstractType r : upperBoundsNonVar) { - constraintSet.add(new Typing(Bi, r, TypeConstraint.Kind.SUBTYPE)); - } - - // R <: var implies the constraint formula - AbstractType T = Ai.getWildcardLowerBound(); - for (AbstractType r : lowerBoundsNonVar) { - constraintSet.add(new Typing(r, T, TypeConstraint.Kind.SUBTYPE)); - } + for (AbstractType type : bounds.get(BoundKind.EQUAL)) { + if (type.isUseOfVariable()) { + continue; + } + AbstractType superTypeOfS = type.asSuper(t.getJavaType()); + if (superTypeOfS != null && superTypeOfS.isRaw()) { + return true; + } + } + return false; + } + + /** + * Returns the constraints generated when incorporating a capture bound. See JLS 18.3.2. + * + * @param Ai the captured type argument + * @param Bi the bound of the type variable + * @return constraints generated when incorporating a capture bound + */ + public ConstraintSet getWildcardConstraints(AbstractType Ai, AbstractType Bi) { + ConstraintSet constraintSet = new ConstraintSet(); + + // Only concerned with bounds against proper types or inference types. + List upperBoundsNonVar = new ArrayList<>(); + for (AbstractType bound : bounds.get(VariableBounds.BoundKind.UPPER)) { + if (bound.isProper() || bound.isInferenceType()) { + upperBoundsNonVar.add(bound); + } + } + List lowerBoundsNonVar = new ArrayList<>(); + for (AbstractType bound : bounds.get(VariableBounds.BoundKind.LOWER)) { + if (bound.isProper() || bound.isInferenceType()) { + lowerBoundsNonVar.add(bound); + } + } + + for (AbstractType bound : bounds.get(VariableBounds.BoundKind.EQUAL)) { + if (bound.isProper() || bound.isInferenceType()) { + // var = R implies the bound false + return null; + } + } + + if (Ai.isUnboundWildcard()) { + // R <: var implies the bound false + if (!lowerBoundsNonVar.isEmpty()) { + return null; + } + // var <: R implies the constraint formula + } else if (Ai.isUpperBoundedWildcard()) { + // R <: var implies the bound false + if (!lowerBoundsNonVar.isEmpty()) { + return null; + } + AbstractType T = Ai.getWildcardUpperBound(); + if (Bi.isObject()) { + // If Bi is Object, then var <: R implies the constraint formula + for (AbstractType r : upperBoundsNonVar) { + constraintSet.add(new Typing(T, r, TypeConstraint.Kind.SUBTYPE)); + } + } else if (T.isObject()) { + // If T is Object, then var <: R implies the constraint formula + for (AbstractType r : upperBoundsNonVar) { + constraintSet.add(new Typing(Bi, r, TypeConstraint.Kind.SUBTYPE)); + } + } + // else no constraint + } else { + // Super bounded wildcard + // var <: R implies the constraint formula + for (AbstractType r : upperBoundsNonVar) { + constraintSet.add(new Typing(Bi, r, TypeConstraint.Kind.SUBTYPE)); + } + + // R <: var implies the constraint formula + AbstractType T = Ai.getWildcardLowerBound(); + for (AbstractType r : lowerBoundsNonVar) { + constraintSet.add(new Typing(r, T, TypeConstraint.Kind.SUBTYPE)); + } + } + return constraintSet; } - return constraintSet; - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java index cd8226b4f72..6eb4fb6a4ac 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/CheckedExceptionsUtil.java @@ -7,304 +7,313 @@ import com.sun.source.tree.ThrowTree; import com.sun.source.tree.TryTree; import com.sun.source.util.TreeScanner; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.TreeUtils; + import java.util.ArrayList; import java.util.List; + import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.UnionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.TreeUtils; /** Util for checked exception constraints. */ public class CheckedExceptionsUtil { - /** Don't use. */ - private CheckedExceptionsUtil() {} - - /** - * Returns a list of checked exception types that can be thrown by the lambda. - * - * @param lambda an expression - * @param context inference context - * @return a list of types of checked exceptions that can be thrown by the lambda - */ - public static List thrownCheckedExceptions( - LambdaExpressionTree lambda, Java8InferenceContext context) { - return new CheckedExceptionVisitor(context).scan(lambda, null); - } - - /** - * Helper class for gathering the types of checked exceptions in a lambda. See - * https://docs.oracle.com/javase/specs/jls/se9/html/jls-11.html#jls-11.2.2 - */ - private static class CheckedExceptionVisitor extends TreeScanner, Void> { - - /** the context. */ - private final Java8InferenceContext context; + /** Don't use. */ + private CheckedExceptionsUtil() {} /** - * Creates the visitor. + * Returns a list of checked exception types that can be thrown by the lambda. * - * @param context the context + * @param lambda an expression + * @param context inference context + * @return a list of types of checked exceptions that can be thrown by the lambda */ - private CheckedExceptionVisitor(Java8InferenceContext context) { - this.context = context; + public static List thrownCheckedExceptions( + LambdaExpressionTree lambda, Java8InferenceContext context) { + return new CheckedExceptionVisitor(context).scan(lambda, null); } - @Override - public List reduce(List r1, List r2) { - if (r1 == null) { - return r2; - } - if (r2 == null) { - return r1; - } - r1.addAll(r2); - return r1; - } + /** + * Helper class for gathering the types of checked exceptions in a lambda. See + * https://docs.oracle.com/javase/specs/jls/se9/html/jls-11.html#jls-11.2.2 + */ + private static class CheckedExceptionVisitor extends TreeScanner, Void> { - @Override - public List visitThrow(ThrowTree node, Void aVoid) { - List result = super.visitThrow(node, aVoid); - if (result == null) { - result = new ArrayList<>(); - } - TypeMirror type = TreeUtils.typeOf(node); - if (isCheckedException(type, context)) { - result.add(type); - } - return result; - } + /** the context. */ + private final Java8InferenceContext context; - @Override - public List visitMethodInvocation(MethodInvocationTree node, Void aVoid) { - List result = super.visitMethodInvocation(node, aVoid); - if (result == null) { - result = new ArrayList<>(); - } - for (TypeMirror type : TreeUtils.elementFromUse(node).getThrownTypes()) { - if (isCheckedException(type, context)) { - result.add(type); + /** + * Creates the visitor. + * + * @param context the context + */ + private CheckedExceptionVisitor(Java8InferenceContext context) { + this.context = context; } - } - return result; - } - @Override - public List visitNewClass(NewClassTree node, Void aVoid) { - List result = super.visitNewClass(node, aVoid); - if (result == null) { - result = new ArrayList<>(); - } - for (TypeMirror type : TreeUtils.elementFromUse(node).getThrownTypes()) { - if (isCheckedException(type, context)) { - result.add(type); + @Override + public List reduce(List r1, List r2) { + if (r1 == null) { + return r2; + } + if (r2 == null) { + return r1; + } + r1.addAll(r2); + return r1; } - } - return result; - } - @Override - public List visitTry(TryTree node, Void aVoid) { - List results = scan(node.getBlock(), aVoid); - if (results == null) { - results = new ArrayList<>(); - } - - if (!results.isEmpty()) { - for (CatchTree catchTree : node.getCatches()) { - // Remove any type that would be caught. - removeAssignable(TreeUtils.typeOf(catchTree.getParameter()), results); + @Override + public List visitThrow(ThrowTree node, Void aVoid) { + List result = super.visitThrow(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + TypeMirror type = TreeUtils.typeOf(node); + if (isCheckedException(type, context)) { + result.add(type); + } + return result; } - } - results.addAll(scan(node.getResources(), aVoid)); - results.addAll(scan(node.getCatches(), aVoid)); - results.addAll(scan(node.getFinallyBlock(), aVoid)); - return results; - } + @Override + public List visitMethodInvocation(MethodInvocationTree node, Void aVoid) { + List result = super.visitMethodInvocation(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + for (TypeMirror type : TreeUtils.elementFromUse(node).getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; + } - /** - * If any type in {@code thrownExceptionTypes} is assignable to {@code type}, then remove it - * from the list. - * - * @param type a type - * @param thrownExceptionTypes the type of the exceptions - */ - private void removeAssignable(TypeMirror type, List thrownExceptionTypes) { - if (thrownExceptionTypes.isEmpty()) { - return; - } - if (type.getKind() == TypeKind.UNION) { - for (TypeMirror altern : ((UnionType) type).getAlternatives()) { - removeAssignable(altern, thrownExceptionTypes); + @Override + public List visitNewClass(NewClassTree node, Void aVoid) { + List result = super.visitNewClass(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + for (TypeMirror type : TreeUtils.elementFromUse(node).getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; + } + + @Override + public List visitTry(TryTree node, Void aVoid) { + List results = scan(node.getBlock(), aVoid); + if (results == null) { + results = new ArrayList<>(); + } + + if (!results.isEmpty()) { + for (CatchTree catchTree : node.getCatches()) { + // Remove any type that would be caught. + removeAssignable(TreeUtils.typeOf(catchTree.getParameter()), results); + } + } + results.addAll(scan(node.getResources(), aVoid)); + results.addAll(scan(node.getCatches(), aVoid)); + results.addAll(scan(node.getFinallyBlock(), aVoid)); + + return results; } - } else { - for (TypeMirror thrownType : new ArrayList<>(thrownExceptionTypes)) { - if (context.env.getTypeUtils().isAssignable(thrownType, type)) { - thrownExceptionTypes.remove(thrownType); - } + + /** + * If any type in {@code thrownExceptionTypes} is assignable to {@code type}, then remove it + * from the list. + * + * @param type a type + * @param thrownExceptionTypes the type of the exceptions + */ + private void removeAssignable(TypeMirror type, List thrownExceptionTypes) { + if (thrownExceptionTypes.isEmpty()) { + return; + } + if (type.getKind() == TypeKind.UNION) { + for (TypeMirror altern : ((UnionType) type).getAlternatives()) { + removeAssignable(altern, thrownExceptionTypes); + } + } else { + for (TypeMirror thrownType : new ArrayList<>(thrownExceptionTypes)) { + if (context.env.getTypeUtils().isAssignable(thrownType, type)) { + thrownExceptionTypes.remove(thrownType); + } + } + } } - } } - } - - /** - * Returns true iff {@code type} is a checked exception. - * - * @param type at ype to check - * @param context the context - * @return true iff {@code type} is a checked exception - */ - private static boolean isCheckedException(TypeMirror type, Java8InferenceContext context) { - TypeMirror runtimeEx = context.runtimeEx; - return context.env.getTypeUtils().isSubtype(type, runtimeEx); - } - - /** - * Returns a list of checked exception types that can be thrown by the lambda. - * - * @param lambda an expression - * @param context inference context - * @return a list of types of checked exceptions that can be thrown by the lambda - */ - public static List thrownCheckedExceptionsATM( - LambdaExpressionTree lambda, Java8InferenceContext context) { - return new CheckedExceptionATMVisitor(context).scan(lambda, null); - } - - /** - * Helper class for gathering the types of checked exceptions in a lambda. See - * https://docs.oracle.com/javase/specs/jls/se9/html/jls-11.html#jls-11.2.2 - */ - private static class CheckedExceptionATMVisitor - extends TreeScanner, Void> { - - /** The context. */ - private final Java8InferenceContext context; /** - * Creates the visitor. + * Returns true iff {@code type} is a checked exception. * + * @param type at ype to check * @param context the context + * @return true iff {@code type} is a checked exception */ - private CheckedExceptionATMVisitor(Java8InferenceContext context) { - this.context = context; + private static boolean isCheckedException(TypeMirror type, Java8InferenceContext context) { + TypeMirror runtimeEx = context.runtimeEx; + return context.env.getTypeUtils().isSubtype(type, runtimeEx); } - @Override - public List reduce( - List r1, List r2) { - if (r1 == null) { - return r2; - } - if (r2 == null) { - return r1; - } - r1.addAll(r2); - return r1; + /** + * Returns a list of checked exception types that can be thrown by the lambda. + * + * @param lambda an expression + * @param context inference context + * @return a list of types of checked exceptions that can be thrown by the lambda + */ + public static List thrownCheckedExceptionsATM( + LambdaExpressionTree lambda, Java8InferenceContext context) { + return new CheckedExceptionATMVisitor(context).scan(lambda, null); } - @Override - public List visitThrow(ThrowTree node, Void aVoid) { - List result = super.visitThrow(node, aVoid); - if (result == null) { - result = new ArrayList<>(); - } - AnnotatedTypeMirror type = context.typeFactory.getAnnotatedType(node); - if (isCheckedException(type, context)) { - result.add(type); - } - return result; - } + /** + * Helper class for gathering the types of checked exceptions in a lambda. See + * https://docs.oracle.com/javase/specs/jls/se9/html/jls-11.html#jls-11.2.2 + */ + private static class CheckedExceptionATMVisitor + extends TreeScanner, Void> { + + /** The context. */ + private final Java8InferenceContext context; - @Override - public List visitMethodInvocation(MethodInvocationTree node, Void aVoid) { - List result = super.visitMethodInvocation(node, aVoid); - if (result == null) { - result = new ArrayList<>(); - } - AnnotatedExecutableType method = context.typeFactory.methodFromUse(node).executableType; - for (AnnotatedTypeMirror type : method.getThrownTypes()) { - if (isCheckedException(type, context)) { - result.add(type); + /** + * Creates the visitor. + * + * @param context the context + */ + private CheckedExceptionATMVisitor(Java8InferenceContext context) { + this.context = context; } - } - return result; - } - @Override - public List visitNewClass(NewClassTree node, Void aVoid) { - List result = super.visitNewClass(node, aVoid); - if (result == null) { - result = new ArrayList<>(); - } - AnnotatedExecutableType method = context.typeFactory.constructorFromUse(node).executableType; - - for (AnnotatedTypeMirror type : method.getThrownTypes()) { - if (isCheckedException(type, context)) { - result.add(type); + @Override + public List reduce( + List r1, List r2) { + if (r1 == null) { + return r2; + } + if (r2 == null) { + return r1; + } + r1.addAll(r2); + return r1; + } + + @Override + public List visitThrow(ThrowTree node, Void aVoid) { + List result = super.visitThrow(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + AnnotatedTypeMirror type = context.typeFactory.getAnnotatedType(node); + if (isCheckedException(type, context)) { + result.add(type); + } + return result; + } + + @Override + public List visitMethodInvocation( + MethodInvocationTree node, Void aVoid) { + List result = super.visitMethodInvocation(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + AnnotatedExecutableType method = context.typeFactory.methodFromUse(node).executableType; + for (AnnotatedTypeMirror type : method.getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; + } + + @Override + public List visitNewClass(NewClassTree node, Void aVoid) { + List result = super.visitNewClass(node, aVoid); + if (result == null) { + result = new ArrayList<>(); + } + AnnotatedExecutableType method = + context.typeFactory.constructorFromUse(node).executableType; + + for (AnnotatedTypeMirror type : method.getThrownTypes()) { + if (isCheckedException(type, context)) { + result.add(type); + } + } + return result; } - } - return result; - } - @Override - public List visitTry(TryTree node, Void aVoid) { - List results = scan(node.getBlock(), aVoid); - if (results == null) { - results = new ArrayList<>(); - } - - if (!results.isEmpty()) { - for (CatchTree catchTree : node.getCatches()) { - // Remove any type that would be caught. - removeAssignable(TreeUtils.typeOf(catchTree.getParameter()), results); + @Override + public List visitTry(TryTree node, Void aVoid) { + List results = scan(node.getBlock(), aVoid); + if (results == null) { + results = new ArrayList<>(); + } + + if (!results.isEmpty()) { + for (CatchTree catchTree : node.getCatches()) { + // Remove any type that would be caught. + removeAssignable(TreeUtils.typeOf(catchTree.getParameter()), results); + } + } + results.addAll(scan(node.getResources(), aVoid)); + results.addAll(scan(node.getCatches(), aVoid)); + results.addAll(scan(node.getFinallyBlock(), aVoid)); + + return results; } - } - results.addAll(scan(node.getResources(), aVoid)); - results.addAll(scan(node.getCatches(), aVoid)); - results.addAll(scan(node.getFinallyBlock(), aVoid)); - return results; + /** + * If any type in {@code thrownExceptionTypes} is assignable to {@code type}, then remove it + * from the list. + * + * @param type an exception type + * @param thrownExceptionTypes a list of thrown exception types; side-effected by this + * method + */ + private void removeAssignable( + TypeMirror type, List thrownExceptionTypes) { + if (thrownExceptionTypes.isEmpty()) { + return; + } + if (type.getKind() == TypeKind.UNION) { + for (TypeMirror altern : ((UnionType) type).getAlternatives()) { + removeAssignable(altern, thrownExceptionTypes); + } + } else { + for (AnnotatedTypeMirror thrownType : new ArrayList<>(thrownExceptionTypes)) { + if (context.env + .getTypeUtils() + .isAssignable(thrownType.getUnderlyingType(), type)) { + thrownExceptionTypes.remove(thrownType); + } + } + } + } } /** - * If any type in {@code thrownExceptionTypes} is assignable to {@code type}, then remove it - * from the list. + * Returns true iff {@code type} is a checked exception. * - * @param type an exception type - * @param thrownExceptionTypes a list of thrown exception types; side-effected by this method + * @param type a type to check + * @param context the context + * @return true iff {@code type} is a checked exception */ - private void removeAssignable(TypeMirror type, List thrownExceptionTypes) { - if (thrownExceptionTypes.isEmpty()) { - return; - } - if (type.getKind() == TypeKind.UNION) { - for (TypeMirror altern : ((UnionType) type).getAlternatives()) { - removeAssignable(altern, thrownExceptionTypes); - } - } else { - for (AnnotatedTypeMirror thrownType : new ArrayList<>(thrownExceptionTypes)) { - if (context.env.getTypeUtils().isAssignable(thrownType.getUnderlyingType(), type)) { - thrownExceptionTypes.remove(thrownType); - } - } - } + private static boolean isCheckedException( + AnnotatedTypeMirror type, Java8InferenceContext context) { + TypeMirror runtimeEx = context.runtimeEx; + return context.env.getTypeUtils().isSubtype(type.getUnderlyingType(), runtimeEx); } - } - - /** - * Returns true iff {@code type} is a checked exception. - * - * @param type a type to check - * @param context the context - * @return true iff {@code type} is a checked exception - */ - private static boolean isCheckedException( - AnnotatedTypeMirror type, Java8InferenceContext context) { - TypeMirror runtimeEx = context.runtimeEx; - return context.env.getTypeUtils().isSubtype(type.getUnderlyingType(), runtimeEx); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java index be468d38568..5c40de60776 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/FalseBoundException.java @@ -6,16 +6,16 @@ /** Exception thrown when the Java types make it so that false is inferred. */ public class FalseBoundException extends RuntimeException { - /** serialVersionUID */ - private static final long serialVersionUID = 1; + /** serialVersionUID */ + private static final long serialVersionUID = 1; - /** - * Creates a false bound exception - * - * @param constraint the constraint the was not resolved - * @param result the result of reduction - */ - public FalseBoundException(Constraint constraint, ReductionResult result) { - super(" False bound for: Constraint: " + constraint + " Result: " + result); - } + /** + * Creates a false bound exception + * + * @param constraint the constraint the was not resolved + * @param result the result of reduction + */ + public FalseBoundException(Constraint constraint, ReductionResult result) { + super(" False bound for: Constraint: " + constraint + " Result: " + result); + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java index 6652415fb6f..6f81ce2f134 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Java8InferenceContext.java @@ -6,24 +6,27 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.processing.JavacProcessingEnvironment; + +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.util.typeinference8.InvocationTypeInference; +import org.checkerframework.framework.util.typeinference8.types.InferenceFactory; +import org.checkerframework.framework.util.typeinference8.types.ProperType; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; + import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.util.typeinference8.InvocationTypeInference; -import org.checkerframework.framework.util.typeinference8.types.InferenceFactory; -import org.checkerframework.framework.util.typeinference8.types.ProperType; -import org.checkerframework.javacutil.TreePathUtil; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; /** * An object to pass around for use during invocation type inference. One context is created per @@ -31,133 +34,138 @@ */ public class Java8InferenceContext { - /** Path to the top level expression whose type arguments are inferred. */ - public TreePath pathToExpression; - - /** javax.annotation.processing.ProcessingEnvironment */ - public final ProcessingEnvironment env; - - /** ProperType for java.lang.Object. */ - public final ProperType object; - - /** Invocation type inference object. */ - public final InvocationTypeInference inference; - - /** com.sun.tools.javac.code.Types */ - public final Types types; - - /** javax.lang.model.util.Types */ - public final javax.lang.model.util.Types modelTypes; - - /** The type of class that encloses the top level expression whose type arguments are inferred. */ - public final DeclaredType enclosingType; - - /** - * Store previously created type variable to inference variable maps as a map from invocation - * expression to Theta. - */ - public final Map maps; - - /** Number of non-capture variables in this inference problem. */ - private int variableCount = 1; - - /** Number of capture variables in this inference problem. */ - private int captureVariableCount = 1; - - /** Number of qualifier variables in this inference problem. */ - private int qualifierVarCount = 1; - - /** TypeMirror for java.lang.RuntimeException. */ - public final TypeMirror runtimeEx; - - /** The inference factory. */ - public final InferenceFactory inferenceTypeFactory; - - /** The annotated type factory. */ - public final AnnotatedTypeFactory typeFactory; - - /** There's no way to tell if an element is a parameter of a lambda, so keep track of them. */ - public final Set lambdaParms = new HashSet<>(); - - /** - * Creates a context - * - * @param factory type factory - * @param pathToExpression path to the expression whose type arguments are inferred - * @param inference inference object - */ - public Java8InferenceContext( - AnnotatedTypeFactory factory, TreePath pathToExpression, InvocationTypeInference inference) { - this.typeFactory = factory; - this.pathToExpression = pathToExpression; - this.env = factory.getProcessingEnv(); - this.inference = inference; - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; - this.types = Types.instance(javacEnv.getContext()); - this.modelTypes = factory.getProcessingEnv().getTypeUtils(); - ClassTree clazz = TreePathUtil.enclosingClass(pathToExpression); - this.enclosingType = (DeclaredType) TreeUtils.typeOf(clazz); - this.maps = new HashMap<>(); - this.runtimeEx = - TypesUtils.typeFromClass(RuntimeException.class, env.getTypeUtils(), env.getElementUtils()); - this.inferenceTypeFactory = new InferenceFactory(this); - this.object = inferenceTypeFactory.getObject(); - } - - /** - * Returns the next number to use as the id for a non-capture variable. This id is only unique for - * this inference problem. - * - * @return the next number to use as the id for a non-capture variable - */ - public int getNextVariableId() { - return variableCount++; - } - - /** - * Return the next number to use as the id for a capture variable. This id is only unique for this - * inference problem. - * - * @return the next number to use as the id for a capture variable - */ - public int getNextCaptureVariableId() { - return captureVariableCount++; - } - - /** - * Returns the next number to use as the id for a qualifier variable. This id is only unique for - * this inference problem. - * - * @return the next number to use as the id for a qualifier variable - */ - public int getNextQualifierVariableId() { - return qualifierVarCount++; - } - - /** - * Adds the parameters to the list of trees that are lambda parameters. - * - *

          There's no way to tell if a tree is a parameter of a lambda, so keep track of them. - * - * @param parameters list of lambda parameters - */ - public void addLambdaParms(List parameters) { - for (VariableTree tree : parameters) { - lambdaParms.add(TreeUtils.elementFromDeclaration(tree)); + /** Path to the top level expression whose type arguments are inferred. */ + public TreePath pathToExpression; + + /** javax.annotation.processing.ProcessingEnvironment */ + public final ProcessingEnvironment env; + + /** ProperType for java.lang.Object. */ + public final ProperType object; + + /** Invocation type inference object. */ + public final InvocationTypeInference inference; + + /** com.sun.tools.javac.code.Types */ + public final Types types; + + /** javax.lang.model.util.Types */ + public final javax.lang.model.util.Types modelTypes; + + /** + * The type of class that encloses the top level expression whose type arguments are inferred. + */ + public final DeclaredType enclosingType; + + /** + * Store previously created type variable to inference variable maps as a map from invocation + * expression to Theta. + */ + public final Map maps; + + /** Number of non-capture variables in this inference problem. */ + private int variableCount = 1; + + /** Number of capture variables in this inference problem. */ + private int captureVariableCount = 1; + + /** Number of qualifier variables in this inference problem. */ + private int qualifierVarCount = 1; + + /** TypeMirror for java.lang.RuntimeException. */ + public final TypeMirror runtimeEx; + + /** The inference factory. */ + public final InferenceFactory inferenceTypeFactory; + + /** The annotated type factory. */ + public final AnnotatedTypeFactory typeFactory; + + /** There's no way to tell if an element is a parameter of a lambda, so keep track of them. */ + public final Set lambdaParms = new HashSet<>(); + + /** + * Creates a context + * + * @param factory type factory + * @param pathToExpression path to the expression whose type arguments are inferred + * @param inference inference object + */ + public Java8InferenceContext( + AnnotatedTypeFactory factory, + TreePath pathToExpression, + InvocationTypeInference inference) { + this.typeFactory = factory; + this.pathToExpression = pathToExpression; + this.env = factory.getProcessingEnv(); + this.inference = inference; + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + this.types = Types.instance(javacEnv.getContext()); + this.modelTypes = factory.getProcessingEnv().getTypeUtils(); + ClassTree clazz = TreePathUtil.enclosingClass(pathToExpression); + this.enclosingType = (DeclaredType) TreeUtils.typeOf(clazz); + this.maps = new HashMap<>(); + this.runtimeEx = + TypesUtils.typeFromClass( + RuntimeException.class, env.getTypeUtils(), env.getElementUtils()); + this.inferenceTypeFactory = new InferenceFactory(this); + this.object = inferenceTypeFactory.getObject(); } - } - - /** - * Return whether the {@code expression} is a lambda parameter. - * - * @param expression an expression - * @return whether the {@code expression} is a lambda parameter - */ - public boolean isLambdaParam(ExpressionTree expression) { - Element element = TreeUtils.elementFromTree(expression); - if (element == null || element.getKind() != ElementKind.PARAMETER) { - return false; + + /** + * Returns the next number to use as the id for a non-capture variable. This id is only unique + * for this inference problem. + * + * @return the next number to use as the id for a non-capture variable + */ + public int getNextVariableId() { + return variableCount++; + } + + /** + * Return the next number to use as the id for a capture variable. This id is only unique for + * this inference problem. + * + * @return the next number to use as the id for a capture variable + */ + public int getNextCaptureVariableId() { + return captureVariableCount++; + } + + /** + * Returns the next number to use as the id for a qualifier variable. This id is only unique for + * this inference problem. + * + * @return the next number to use as the id for a qualifier variable + */ + public int getNextQualifierVariableId() { + return qualifierVarCount++; + } + + /** + * Adds the parameters to the list of trees that are lambda parameters. + * + *

          There's no way to tell if a tree is a parameter of a lambda, so keep track of them. + * + * @param parameters list of lambda parameters + */ + public void addLambdaParms(List parameters) { + for (VariableTree tree : parameters) { + lambdaParms.add(TreeUtils.elementFromDeclaration(tree)); + } + } + + /** + * Return whether the {@code expression} is a lambda parameter. + * + * @param expression an expression + * @return whether the {@code expression} is a lambda parameter + */ + public boolean isLambdaParam(ExpressionTree expression) { + Element element = TreeUtils.elementFromTree(expression); + if (element == null || element.getKind() != ElementKind.PARAMETER) { + return false; + } + return lambdaParms.contains((VariableElement) element); } - return lambdaParms.contains((VariableElement) element); - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java index e09587a5512..fbad3e533e5 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Resolution.java @@ -1,5 +1,16 @@ package org.checkerframework.framework.util.typeinference8.util; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.util.typeinference8.bound.BoundSet; +import org.checkerframework.framework.util.typeinference8.types.AbstractQualifier; +import org.checkerframework.framework.util.typeinference8.types.AbstractType; +import org.checkerframework.framework.util.typeinference8.types.Dependencies; +import org.checkerframework.framework.util.typeinference8.types.ProperType; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds; +import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -8,20 +19,11 @@ import java.util.List; import java.util.Queue; import java.util.Set; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.util.typeinference8.bound.BoundSet; -import org.checkerframework.framework.util.typeinference8.types.AbstractQualifier; -import org.checkerframework.framework.util.typeinference8.types.AbstractType; -import org.checkerframework.framework.util.typeinference8.types.Dependencies; -import org.checkerframework.framework.util.typeinference8.types.ProperType; -import org.checkerframework.framework.util.typeinference8.types.Variable; -import org.checkerframework.framework.util.typeinference8.types.VariableBounds; -import org.checkerframework.framework.util.typeinference8.types.VariableBounds.BoundKind; /** * Resolution finds an instantiation for each variable in a given set of variables. It does this @@ -41,383 +43,391 @@ */ public class Resolution { - /** - * Instantiates a set of variables, {@code as}. - * - * @param as the set of variables to resolve - * @param boundSet the bound set that includes {@code as} - * @param context Java8InferenceContext - * @return bound set where {@code as} have instantiations - */ - public static BoundSet resolve( - Collection as, BoundSet boundSet, Java8InferenceContext context) { - - // Remove any variables that already have instantiations - List resolvedVars = boundSet.getInstantiatedVariables(); - as.removeAll(resolvedVars); - if (as.isEmpty()) { - return boundSet; - } - // Calculate the dependencies between variables. (A variable depends on another if it is - // included in one of its bounds.) - Dependencies dependencies = boundSet.getDependencies(); - Queue unresolvedVars = new ArrayDeque<>(as); - for (Variable var : as) { - for (Variable dep : dependencies.get(var)) { - if (!unresolvedVars.contains(dep)) { - unresolvedVars.add(dep); + /** + * Instantiates a set of variables, {@code as}. + * + * @param as the set of variables to resolve + * @param boundSet the bound set that includes {@code as} + * @param context Java8InferenceContext + * @return bound set where {@code as} have instantiations + */ + public static BoundSet resolve( + Collection as, BoundSet boundSet, Java8InferenceContext context) { + + // Remove any variables that already have instantiations + List resolvedVars = boundSet.getInstantiatedVariables(); + as.removeAll(resolvedVars); + if (as.isEmpty()) { + return boundSet; + } + // Calculate the dependencies between variables. (A variable depends on another if it is + // included in one of its bounds.) + Dependencies dependencies = boundSet.getDependencies(); + Queue unresolvedVars = new ArrayDeque<>(as); + for (Variable var : as) { + for (Variable dep : dependencies.get(var)) { + if (!unresolvedVars.contains(dep)) { + unresolvedVars.add(dep); + } + } } - } - } - // Remove any variables that already have instantiations - unresolvedVars.removeAll(resolvedVars); - if (unresolvedVars.isEmpty()) { - return boundSet; - } + // Remove any variables that already have instantiations + unresolvedVars.removeAll(resolvedVars); + if (unresolvedVars.isEmpty()) { + return boundSet; + } - // Resolve the variables - Resolution resolution = new Resolution(context, dependencies); - boundSet = resolution.resolve(boundSet, unresolvedVars); - assert !boundSet.containsFalse(); - return boundSet; - } - - /** - * Instantiates the variable {@code a}. - * - * @param a the variable to resolve - * @param boundSet the bound set that includes {@code a} - * @param context Java8InferenceContext - * @return bound set where {@code a} is instantiated - */ - public static BoundSet resolve(Variable a, BoundSet boundSet, Java8InferenceContext context) { - if (a.getBounds().hasInstantiation()) { - return boundSet; + // Resolve the variables + Resolution resolution = new Resolution(context, dependencies); + boundSet = resolution.resolve(boundSet, unresolvedVars); + assert !boundSet.containsFalse(); + return boundSet; } - Dependencies dependencies = boundSet.getDependencies(); - - LinkedHashSet unresolvedVars = new LinkedHashSet<>(); - unresolvedVars.add(a); - Resolution resolution = new Resolution(context, dependencies); - boundSet = resolution.resolveSmallestSet(unresolvedVars, boundSet); - assert !boundSet.containsFalse(); - return boundSet; - } - - /** The context. */ - private final Java8InferenceContext context; - - /** The set of dependencies between the variables. */ - private final Dependencies dependencies; - - /** - * Creates a resolution. - * - * @param context the context - * @param dependencies the dependencies - */ - private Resolution(Java8InferenceContext context, Dependencies dependencies) { - this.context = context; - this.dependencies = dependencies; - } - - /** - * Resolve all the variables in {@code unresolvedVars}. - * - * @param boundSet current bound set - * @param unresolvedVars a set of unresolved variables that includes all dependencies - * @return the bounds set with the resolved bounds - */ - private BoundSet resolve(BoundSet boundSet, Queue unresolvedVars) { - List resolvedVars = boundSet.getInstantiatedVariables(); - - while (!unresolvedVars.isEmpty()) { - assert !boundSet.containsFalse(); - - Set smallestDependencySet = getSmallestDependecySet(resolvedVars, unresolvedVars); - - // Resolve the smallest unresolved dependency set. - boundSet = resolveSmallestSet(smallestDependencySet, boundSet); - - resolvedVars = boundSet.getInstantiatedVariables(); - unresolvedVars.removeAll(resolvedVars); + + /** + * Instantiates the variable {@code a}. + * + * @param a the variable to resolve + * @param boundSet the bound set that includes {@code a} + * @param context Java8InferenceContext + * @return bound set where {@code a} is instantiated + */ + public static BoundSet resolve(Variable a, BoundSet boundSet, Java8InferenceContext context) { + if (a.getBounds().hasInstantiation()) { + return boundSet; + } + Dependencies dependencies = boundSet.getDependencies(); + + LinkedHashSet unresolvedVars = new LinkedHashSet<>(); + unresolvedVars.add(a); + Resolution resolution = new Resolution(context, dependencies); + boundSet = resolution.resolveSmallestSet(unresolvedVars, boundSet); + assert !boundSet.containsFalse(); + return boundSet; } - return boundSet; - } - - /** - * Returns the smallest set of unresolved variables that includes any variable on which a variable - * in the set depends. - * - * @param resolvedVars variables that have been resolved - * @param unresolvedVars variables that have not been resolved - * @return the smallest set of unresolved variable - */ - private Set getSmallestDependecySet( - List resolvedVars, Queue unresolvedVars) { - Set smallestDependencySet = null; - // This loop is looking for the smallest set of dependencies that have not been resolved. - for (Variable alpha : unresolvedVars) { - Set alphasDependencySet = dependencies.get(alpha); - alphasDependencySet.removeAll(resolvedVars); - - if (smallestDependencySet == null - || alphasDependencySet.size() < smallestDependencySet.size()) { - smallestDependencySet = alphasDependencySet; - } - - if (smallestDependencySet.size() == 1) { - // If the size is 1, then alpha has the smallest possible set of unresolved - // dependencies. - // (A variable is always dependent on itself.) So, stop looking for smaller ones. - break; - } + + /** The context. */ + private final Java8InferenceContext context; + + /** The set of dependencies between the variables. */ + private final Dependencies dependencies; + + /** + * Creates a resolution. + * + * @param context the context + * @param dependencies the dependencies + */ + private Resolution(Java8InferenceContext context, Dependencies dependencies) { + this.context = context; + this.dependencies = dependencies; } - return smallestDependencySet; - } - - /** - * Resolves {@code as} - * - * @param as the smallest set of unresolved variables that includes all any variable on which a - * variable in the set depends - * @param boundSet current bounds set - * @return current bound set - */ - private BoundSet resolveSmallestSet(Set as, BoundSet boundSet) { - assert !boundSet.containsFalse(); - - if (boundSet.containsCapture(as)) { - resolveNoCapturesFirst(new ArrayList<>(as)); - boundSet.getInstantiatedVariables().forEach(as::remove); - // Then resolve the capture variables - return resolveWithCapture(as, boundSet, context); - } else { - BoundSet copy = new BoundSet(boundSet); - // Save the current bounds in case the first attempt at resolution fails. - copy.saveBounds(); - try { - BoundSet resolvedBounds = resolveNoCapture(as, boundSet); - if (!resolvedBounds.containsFalse()) { - return resolvedBounds; + + /** + * Resolve all the variables in {@code unresolvedVars}. + * + * @param boundSet current bound set + * @param unresolvedVars a set of unresolved variables that includes all dependencies + * @return the bounds set with the resolved bounds + */ + private BoundSet resolve(BoundSet boundSet, Queue unresolvedVars) { + List resolvedVars = boundSet.getInstantiatedVariables(); + + while (!unresolvedVars.isEmpty()) { + assert !boundSet.containsFalse(); + + Set smallestDependencySet = + getSmallestDependecySet(resolvedVars, unresolvedVars); + + // Resolve the smallest unresolved dependency set. + boundSet = resolveSmallestSet(smallestDependencySet, boundSet); + + resolvedVars = boundSet.getInstantiatedVariables(); + unresolvedVars.removeAll(resolvedVars); } - } catch (FalseBoundException ex) { - // Try with capture. - } - boundSet = copy; - // If resolveNoCapture fails, then undo all resolved variables from the failed attempt. - boundSet.restore(); - return resolveWithCapture(as, boundSet, context); + return boundSet; } - } - - /** - * Resolves all the non-capture variables in {@code variables}. - * - * @param variables the variables - */ - private void resolveNoCapturesFirst(List variables) { - Variable smallV; - do { - smallV = null; - int smallest = Integer.MAX_VALUE; - for (Variable v : variables) { - v.getBounds().applyInstantiationsToBounds(); - if (v.getBounds().hasInstantiation()) { - variables.remove(v); - // loop again because a new instantiation has been found. - // (Also avoids concurrent modification exception.) - break; - } - if (!v.isCaptureVariable()) { - int size = v.getBounds().getVariablesMentionedInBounds().size(); - if (size < smallest) { - smallest = size; - smallV = v; - } + + /** + * Returns the smallest set of unresolved variables that includes any variable on which a + * variable in the set depends. + * + * @param resolvedVars variables that have been resolved + * @param unresolvedVars variables that have not been resolved + * @return the smallest set of unresolved variable + */ + private Set getSmallestDependecySet( + List resolvedVars, Queue unresolvedVars) { + Set smallestDependencySet = null; + // This loop is looking for the smallest set of dependencies that have not been resolved. + for (Variable alpha : unresolvedVars) { + Set alphasDependencySet = dependencies.get(alpha); + alphasDependencySet.removeAll(resolvedVars); + + if (smallestDependencySet == null + || alphasDependencySet.size() < smallestDependencySet.size()) { + smallestDependencySet = alphasDependencySet; + } + + if (smallestDependencySet.size() == 1) { + // If the size is 1, then alpha has the smallest possible set of unresolved + // dependencies. + // (A variable is always dependent on itself.) So, stop looking for smaller ones. + break; + } } - } - if (smallV != null) { - resolveNoCapture(smallV); - variables.remove(smallV); - } - } while (smallV != null); - } - - /** - * Resolves all variables in {@code as} by instantiating each to the greatest lower bound of its - * proper upper bounds. This may fail and resolveWithCapture will need to be used instead. - * - * @param as variables to resolve - * @param boundSet the bound set to use - * @return the resolved bound st - */ - private BoundSet resolveNoCapture(Set as, BoundSet boundSet) { - BoundSet resolvedBoundSet = new BoundSet(context); - for (Variable ai : as) { - assert !ai.getBounds().hasInstantiation(); - resolveNoCapture(ai); - if (!ai.getBounds().hasInstantiation()) { - resolvedBoundSet.addFalse(); - break; - } + return smallestDependencySet; } - boundSet.incorporateToFixedPoint(resolvedBoundSet); - return boundSet; - } - - /** - * Resolves {@code ai} by instantiating it to the greatest lower bound of its proper upper bounds. - * - * @param ai variable to resolve - */ - private void resolveNoCapture(Variable ai) { - assert !ai.getBounds().hasInstantiation(); - Set lowerBounds = ai.getBounds().findProperLowerBounds(); - - if (!lowerBounds.isEmpty()) { - ProperType lubProperType = context.inferenceTypeFactory.lub(lowerBounds); - Set qualifierLowerBounds = - ai.getBounds().qualifierBounds.get(BoundKind.LOWER); - if (!qualifierLowerBounds.isEmpty()) { - QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); - Set lubAnnos = AbstractQualifier.lub(qualifierLowerBounds, context); - if (lubProperType.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { - Set newLubAnnos = - qh.leastUpperBoundsQualifiersOnly( - lubAnnos, lubProperType.getAnnotatedType().getAnnotations()); - lubProperType.getAnnotatedType().replaceAnnotations(newLubAnnos); - } else { - AnnotatedTypeVariable lubTV = (AnnotatedTypeVariable) lubProperType.getAnnotatedType(); - Set newLubAnnos = - qh.leastUpperBoundsQualifiersOnly(lubAnnos, lubTV.getLowerBound().getAnnotations()); - lubTV.getLowerBound().replaceAnnotations(newLubAnnos); + /** + * Resolves {@code as} + * + * @param as the smallest set of unresolved variables that includes all any variable on which a + * variable in the set depends + * @param boundSet current bounds set + * @return current bound set + */ + private BoundSet resolveSmallestSet(Set as, BoundSet boundSet) { + assert !boundSet.containsFalse(); + + if (boundSet.containsCapture(as)) { + resolveNoCapturesFirst(new ArrayList<>(as)); + boundSet.getInstantiatedVariables().forEach(as::remove); + // Then resolve the capture variables + return resolveWithCapture(as, boundSet, context); + } else { + BoundSet copy = new BoundSet(boundSet); + // Save the current bounds in case the first attempt at resolution fails. + copy.saveBounds(); + try { + BoundSet resolvedBounds = resolveNoCapture(as, boundSet); + if (!resolvedBounds.containsFalse()) { + return resolvedBounds; + } + } catch (FalseBoundException ex) { + // Try with capture. + } + boundSet = copy; + // If resolveNoCapture fails, then undo all resolved variables from the failed attempt. + boundSet.restore(); + return resolveWithCapture(as, boundSet, context); } - } - ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, lubProperType); - return; } - Set upperBounds = ai.getBounds().findProperUpperBounds(); - if (!upperBounds.isEmpty()) { - ProperType ti = null; - boolean useRuntimeEx = false; - for (ProperType liProperType : upperBounds) { - TypeMirror li = liProperType.getJavaType(); - if (ai.getBounds().hasThrowsBound() - && context.env.getTypeUtils().isSubtype(context.runtimeEx, li)) { - useRuntimeEx = true; - } - if (ti == null) { - ti = liProperType; - } else { - ti = (ProperType) context.inferenceTypeFactory.glb(ti, liProperType); + /** + * Resolves all the non-capture variables in {@code variables}. + * + * @param variables the variables + */ + private void resolveNoCapturesFirst(List variables) { + Variable smallV; + do { + smallV = null; + int smallest = Integer.MAX_VALUE; + for (Variable v : variables) { + v.getBounds().applyInstantiationsToBounds(); + if (v.getBounds().hasInstantiation()) { + variables.remove(v); + // loop again because a new instantiation has been found. + // (Also avoids concurrent modification exception.) + break; + } + if (!v.isCaptureVariable()) { + int size = v.getBounds().getVariablesMentionedInBounds().size(); + if (size < smallest) { + smallest = size; + smallV = v; + } + } + } + if (smallV != null) { + resolveNoCapture(smallV); + variables.remove(smallV); + } + } while (smallV != null); + } + + /** + * Resolves all variables in {@code as} by instantiating each to the greatest lower bound of its + * proper upper bounds. This may fail and resolveWithCapture will need to be used instead. + * + * @param as variables to resolve + * @param boundSet the bound set to use + * @return the resolved bound st + */ + private BoundSet resolveNoCapture(Set as, BoundSet boundSet) { + BoundSet resolvedBoundSet = new BoundSet(context); + for (Variable ai : as) { + assert !ai.getBounds().hasInstantiation(); + resolveNoCapture(ai); + if (!ai.getBounds().hasInstantiation()) { + resolvedBoundSet.addFalse(); + break; + } } - } - if (useRuntimeEx) { - ai.getBounds() - .addBound( - VariableBounds.BoundKind.EQUAL, context.inferenceTypeFactory.getRuntimeException()); - } else { - ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, ti); - } + boundSet.incorporateToFixedPoint(resolvedBoundSet); + return boundSet; } - } - - /** - * Instantiates the variables in {@code as} by creating fresh type variables using the bounds of - * the variables. - * - * @param as a set of variables to resolve - * @param boundSet the bounds set to use - * @param context the contest - * @return the resolved bound set - */ - private static BoundSet resolveWithCapture( - Set as, BoundSet boundSet, Java8InferenceContext context) { - assert !boundSet.containsFalse(); - boundSet.removeCaptures(as); - BoundSet resolvedBoundSet = new BoundSet(context); - List asList = new ArrayList<>(); - List typeVar = new ArrayList<>(); - List typeArg = new ArrayList<>(); - - for (Variable ai : as) { - ai.getBounds().applyInstantiationsToBounds(); - if (ai.getBounds().hasInstantiation()) { - // If ai is equal to a variable that was resolved previously, - // ai would now have an instantiation. - continue; - } - asList.add(ai); - Set lowerBounds = ai.getBounds().findProperLowerBounds(); - ProperType lowerBound = context.inferenceTypeFactory.lub(lowerBounds); - - Set lowerBoundAnnos; - Set qualifierLowerBounds = - ai.getBounds().qualifierBounds.get(BoundKind.LOWER); - if (!qualifierLowerBounds.isEmpty()) { - QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); - lowerBoundAnnos = AbstractQualifier.lub(qualifierLowerBounds, context); - if (lowerBound != null) { - if (lowerBound.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { - Set newLubAnnos = - qh.leastUpperBoundsQualifiersOnly( - lowerBoundAnnos, lowerBound.getAnnotatedType().getAnnotations()); - lowerBound.getAnnotatedType().replaceAnnotations(newLubAnnos); - lowerBoundAnnos = newLubAnnos; - } else { - AnnotatedTypeVariable lubTV = (AnnotatedTypeVariable) lowerBound.getAnnotatedType(); - Set newLubAnnos = - qh.leastUpperBoundsQualifiersOnly( - lowerBoundAnnos, lubTV.getLowerBound().getAnnotations()); - lubTV.getLowerBound().replaceAnnotations(newLubAnnos); - lowerBoundAnnos = newLubAnnos; - } + + /** + * Resolves {@code ai} by instantiating it to the greatest lower bound of its proper upper + * bounds. + * + * @param ai variable to resolve + */ + private void resolveNoCapture(Variable ai) { + assert !ai.getBounds().hasInstantiation(); + Set lowerBounds = ai.getBounds().findProperLowerBounds(); + + if (!lowerBounds.isEmpty()) { + ProperType lubProperType = context.inferenceTypeFactory.lub(lowerBounds); + Set qualifierLowerBounds = + ai.getBounds().qualifierBounds.get(BoundKind.LOWER); + if (!qualifierLowerBounds.isEmpty()) { + QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); + Set lubAnnos = + AbstractQualifier.lub(qualifierLowerBounds, context); + if (lubProperType.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lubAnnos, lubProperType.getAnnotatedType().getAnnotations()); + lubProperType.getAnnotatedType().replaceAnnotations(newLubAnnos); + } else { + + AnnotatedTypeVariable lubTV = + (AnnotatedTypeVariable) lubProperType.getAnnotatedType(); + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lubAnnos, lubTV.getLowerBound().getAnnotations()); + lubTV.getLowerBound().replaceAnnotations(newLubAnnos); + } + } + ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, lubProperType); + return; } - } else { - lowerBoundAnnos = Collections.emptySet(); - } - - Set upperBounds = ai.getBounds().upperBounds(); - AbstractType upperBound = context.inferenceTypeFactory.glb(upperBounds); - Set upperBoundAnnos; - Set qualifierUpperBounds = - ai.getBounds().qualifierBounds.get(BoundKind.UPPER); - if (!qualifierUpperBounds.isEmpty()) { - upperBoundAnnos = AbstractQualifier.glb(qualifierUpperBounds, context); - if (upperBound != null) { - upperBoundAnnos = - context - .typeFactory - .getQualifierHierarchy() - .greatestLowerBoundsQualifiersOnly( - upperBoundAnnos, upperBound.getAnnotatedType().getAnnotations()); - upperBound.getAnnotatedType().replaceAnnotations(upperBoundAnnos); + + Set upperBounds = ai.getBounds().findProperUpperBounds(); + if (!upperBounds.isEmpty()) { + ProperType ti = null; + boolean useRuntimeEx = false; + for (ProperType liProperType : upperBounds) { + TypeMirror li = liProperType.getJavaType(); + if (ai.getBounds().hasThrowsBound() + && context.env.getTypeUtils().isSubtype(context.runtimeEx, li)) { + useRuntimeEx = true; + } + if (ti == null) { + ti = liProperType; + } else { + ti = (ProperType) context.inferenceTypeFactory.glb(ti, liProperType); + } + } + if (useRuntimeEx) { + ai.getBounds() + .addBound( + VariableBounds.BoundKind.EQUAL, + context.inferenceTypeFactory.getRuntimeException()); + } else { + ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, ti); + } } - } else { - upperBoundAnnos = Collections.emptySet(); - } - - typeVar.add(ai.getJavaType()); - AbstractType freshTypeVar = - context.inferenceTypeFactory.createFreshTypeVariable( - lowerBound, lowerBoundAnnos, upperBound, upperBoundAnnos); - typeArg.add(freshTypeVar); } - List subsTypeArg = - context.inferenceTypeFactory.getSubsTypeArgs(typeVar, typeArg, asList); + /** + * Instantiates the variables in {@code as} by creating fresh type variables using the bounds of + * the variables. + * + * @param as a set of variables to resolve + * @param boundSet the bounds set to use + * @param context the contest + * @return the resolved bound set + */ + private static BoundSet resolveWithCapture( + Set as, BoundSet boundSet, Java8InferenceContext context) { + assert !boundSet.containsFalse(); + boundSet.removeCaptures(as); + BoundSet resolvedBoundSet = new BoundSet(context); + List asList = new ArrayList<>(); + List typeVar = new ArrayList<>(); + List typeArg = new ArrayList<>(); + + for (Variable ai : as) { + ai.getBounds().applyInstantiationsToBounds(); + if (ai.getBounds().hasInstantiation()) { + // If ai is equal to a variable that was resolved previously, + // ai would now have an instantiation. + continue; + } + asList.add(ai); + Set lowerBounds = ai.getBounds().findProperLowerBounds(); + ProperType lowerBound = context.inferenceTypeFactory.lub(lowerBounds); + + Set lowerBoundAnnos; + Set qualifierLowerBounds = + ai.getBounds().qualifierBounds.get(BoundKind.LOWER); + if (!qualifierLowerBounds.isEmpty()) { + QualifierHierarchy qh = context.typeFactory.getQualifierHierarchy(); + lowerBoundAnnos = AbstractQualifier.lub(qualifierLowerBounds, context); + if (lowerBound != null) { + if (lowerBound.getAnnotatedType().getKind() != TypeKind.TYPEVAR) { + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lowerBoundAnnos, + lowerBound.getAnnotatedType().getAnnotations()); + lowerBound.getAnnotatedType().replaceAnnotations(newLubAnnos); + lowerBoundAnnos = newLubAnnos; + } else { + AnnotatedTypeVariable lubTV = + (AnnotatedTypeVariable) lowerBound.getAnnotatedType(); + Set newLubAnnos = + qh.leastUpperBoundsQualifiersOnly( + lowerBoundAnnos, lubTV.getLowerBound().getAnnotations()); + lubTV.getLowerBound().replaceAnnotations(newLubAnnos); + lowerBoundAnnos = newLubAnnos; + } + } + } else { + lowerBoundAnnos = Collections.emptySet(); + } + + Set upperBounds = ai.getBounds().upperBounds(); + AbstractType upperBound = context.inferenceTypeFactory.glb(upperBounds); + Set upperBoundAnnos; + Set qualifierUpperBounds = + ai.getBounds().qualifierBounds.get(BoundKind.UPPER); + if (!qualifierUpperBounds.isEmpty()) { + upperBoundAnnos = AbstractQualifier.glb(qualifierUpperBounds, context); + if (upperBound != null) { + upperBoundAnnos = + context.typeFactory + .getQualifierHierarchy() + .greatestLowerBoundsQualifiersOnly( + upperBoundAnnos, + upperBound.getAnnotatedType().getAnnotations()); + upperBound.getAnnotatedType().replaceAnnotations(upperBoundAnnos); + } + } else { + upperBoundAnnos = Collections.emptySet(); + } + + typeVar.add(ai.getJavaType()); + AbstractType freshTypeVar = + context.inferenceTypeFactory.createFreshTypeVariable( + lowerBound, lowerBoundAnnos, upperBound, upperBoundAnnos); + typeArg.add(freshTypeVar); + } - // Create the new bounds. - for (int i = 0; i < asList.size(); i++) { - Variable ai = asList.get(i); - ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, subsTypeArg.get(i)); - } + List subsTypeArg = + context.inferenceTypeFactory.getSubsTypeArgs(typeVar, typeArg, asList); - boundSet.incorporateToFixedPoint(resolvedBoundSet); - return boundSet; - } + // Create the new bounds. + for (int i = 0; i < asList.size(); i++) { + Variable ai = asList.get(i); + ai.getBounds().addBound(VariableBounds.BoundKind.EQUAL, subsTypeArg.get(i)); + } + + boundSet.incorporateToFixedPoint(resolvedBoundSet); + return boundSet; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java index 3ae9182408b..5538217b4c0 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java @@ -1,68 +1,70 @@ package org.checkerframework.framework.util.typeinference8.util; +import org.checkerframework.framework.util.typeinference8.types.Variable; +import org.checkerframework.javacutil.TypesUtils; + import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; + import javax.lang.model.type.TypeVariable; -import org.checkerframework.framework.util.typeinference8.types.Variable; -import org.checkerframework.javacutil.TypesUtils; /** A mapping from type variables to inference variables. */ public class Theta extends LinkedHashMap { - /** serialVersionUID */ - private static final long serialVersionUID = 42L; + /** serialVersionUID */ + private static final long serialVersionUID = 42L; - /** Creates Theta. */ - public Theta() {} + /** Creates Theta. */ + public Theta() {} - /** - * Returns the type variable in the key set that is {@link TypesUtils#areSame(TypeVariable, - * TypeVariable)} as {@code typeVariable}. - * - * @param typeVariable a type variable - * @return the type variable in the key set that is {@link TypesUtils#areSame(TypeVariable, - * TypeVariable)} as {@code typeVariable} - */ - private TypeVariable getTypeVariable(TypeVariable typeVariable) { - for (TypeVariable key : keySet()) { - if (TypesUtils.areSame(key, typeVariable)) { - return key; - } + /** + * Returns the type variable in the key set that is {@link TypesUtils#areSame(TypeVariable, + * TypeVariable)} as {@code typeVariable}. + * + * @param typeVariable a type variable + * @return the type variable in the key set that is {@link TypesUtils#areSame(TypeVariable, + * TypeVariable)} as {@code typeVariable} + */ + private TypeVariable getTypeVariable(TypeVariable typeVariable) { + for (TypeVariable key : keySet()) { + if (TypesUtils.areSame(key, typeVariable)) { + return key; + } + } + return typeVariable; } - return typeVariable; - } - @Override - public boolean containsKey(Object key) { - if (key instanceof TypeVariable) { - return super.containsKey(getTypeVariable((TypeVariable) key)); + @Override + public boolean containsKey(Object key) { + if (key instanceof TypeVariable) { + return super.containsKey(getTypeVariable((TypeVariable) key)); + } + return false; } - return false; - } - @Override - public Variable get(Object key) { - if (key instanceof TypeVariable) { - return super.get(getTypeVariable((TypeVariable) key)); + @Override + public Variable get(Object key) { + if (key instanceof TypeVariable) { + return super.get(getTypeVariable((TypeVariable) key)); + } + return super.get(key); } - return super.get(key); - } - /** - * Returns a list of type variables that do not yet have a value. - * - * @return a list of type variables that do not yet have a value - */ - public Collection getNotInstantiated() { - List list = new ArrayList<>(); - forEach( - (typevar, var) -> { - if (var.getInstantiation() == null) { - list.add(typevar); - } - }); - return list; - } + /** + * Returns a list of type variables that do not yet have a value. + * + * @return a list of type variables that do not yet have a value + */ + public Collection getNotInstantiated() { + List list = new ArrayList<>(); + forEach( + (typevar, var) -> { + if (var.getInstantiation() == null) { + list.add(typevar); + } + }); + return list; + } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java index 154a5ada6df..213b7324928 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/AbstractTypeInformationPresenter.java @@ -19,7 +19,7 @@ import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; -import java.util.Arrays; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.flow.CFAbstractAnalysis; @@ -37,296 +37,318 @@ import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.ArraySet; +import java.util.Arrays; + /** Presents formatted type information for various AST trees in a class. */ public abstract class AbstractTypeInformationPresenter implements TypeInformationPresenter { - /** The {@link AnnotatedTypeFactory} for the current analysis. */ - protected final AnnotatedTypeFactory atypeFactory; - - /** - * The {@link GenericAnnotatedTypeFactory} for the current analysis. null if the factory is not an - * instance of {@link GenericAnnotatedTypeFactory}; otherwise, {@code factory} and {@code - * genFactory} refer to the same object. - */ - protected final @Nullable GenericAnnotatedTypeFactory< - ? extends CFAbstractValue, - ? extends CFAbstractStore, ?>, - ? extends CFAbstractTransfer, - ? extends CFAbstractAnalysis> - genFactory; - - /** This formats the ATMs that the presenter is going to present. */ - protected final AnnotatedTypeFormatter typeFormatter; - - /** - * Constructs a presenter for the given factory. - * - * @param atypeFactory the {@link AnnotatedTypeFactory} for the current analysis - */ - public AbstractTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { - this.atypeFactory = atypeFactory; - if (atypeFactory instanceof GenericAnnotatedTypeFactory) { - this.genFactory = (GenericAnnotatedTypeFactory) atypeFactory; - } else { - this.genFactory = null; - } - this.typeFormatter = createTypeFormatter(); - } - - /** - * The entry point for presenting type information of trees in the given class. - * - * @param tree a {@link ClassTree} that has been annotated by the factory - * @param treePath a {@link TreePath} to {@code tree} - */ - @Override - public void process(ClassTree tree, TreePath treePath) { - TypeInformationReporter visitor = createTypeInformationReporter(tree); - visitor.scan(treePath, null); - } - - /** - * Creates the {@link TypeInformationReporter} to use. - * - * @param tree a {@link ClassTree} that has been annotated by the factory - * @return the {@link TypeInformationReporter} to use - */ - protected abstract TypeInformationReporter createTypeInformationReporter(ClassTree tree); - - /** - * Creates the {@link AnnotatedTypeFormatter} to use for output. - * - * @return the {@link AnnotatedTypeFormatter} to use for output - */ - protected AnnotatedTypeFormatter createTypeFormatter() { - return new DefaultAnnotatedTypeFormatter(true, true); - } - - /** - * A visitor which traverses a class tree and reports type information of various sub-trees. - * - *

          Note: Since nested class trees will be type-checked separately, this visitor does not dive - * into any nested class trees. - */ - protected abstract class TypeInformationReporter extends TreePathScanner { - - /** The class tree in which it traverses and reports type information. */ - protected final ClassTree classTree; - - /** Root of the current class tree. This is a helper for computing positions of a sub-tree. */ - protected final CompilationUnitTree currentRoot; - - /** The checker that's currently running. */ - protected final BaseTypeChecker checker; + /** The {@link AnnotatedTypeFactory} for the current analysis. */ + protected final AnnotatedTypeFactory atypeFactory; /** - * Constructs a new reporter for the given class tree. - * - * @param classTree the {@link ClassTree} + * The {@link GenericAnnotatedTypeFactory} for the current analysis. null if the factory is not + * an instance of {@link GenericAnnotatedTypeFactory}; otherwise, {@code factory} and {@code + * genFactory} refer to the same object. */ - public TypeInformationReporter(ClassTree classTree) { - this.classTree = classTree; - this.checker = atypeFactory.getChecker(); - this.currentRoot = this.checker.getPathToCompilationUnit().getCompilationUnit(); - } + protected final @Nullable GenericAnnotatedTypeFactory< + ? extends CFAbstractValue, + ? extends CFAbstractStore, ?>, + ? extends CFAbstractTransfer, + ? extends CFAbstractAnalysis> + genFactory; + + /** This formats the ATMs that the presenter is going to present. */ + protected final AnnotatedTypeFormatter typeFormatter; /** - * Report the {@code type} of {@code tree} in a particular {@code occurrenceKind}. + * Constructs a presenter for the given factory. * - * @param tree the tree - * @param type the type - * @param occurrenceKind the occurrence kind + * @param atypeFactory the {@link AnnotatedTypeFactory} for the current analysis */ - protected abstract void reportTreeType( - Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind); - - @Override - public Void visitClass(ClassTree tree, Void unused) { - @SuppressWarnings("interning:not.interned") - boolean isNestedClass = tree != classTree; - if (isNestedClass) { - // Since nested class trees will be type-checked separately, this visitor does - // not dive into any nested class trees. - return null; - } - return super.visitClass(tree, unused); - } - - @Override - public Void visitTypeParameter(TypeParameterTree tree, Void unused) { - reportTreeType( - tree, atypeFactory.getAnnotatedTypeFromTypeTree(tree), TypeOccurrenceKind.DECLARED_TYPE); - return super.visitTypeParameter(tree, unused); - } - - @Override - public Void visitVariable(VariableTree tree, Void unused) { - // TODO: "int x = 1" is a VariableTree, but there is no AssignmentTree and it - // TODO: is difficult to locate the "=" symbol. - AnnotatedTypeMirror varType = - genFactory != null - ? genFactory.getAnnotatedTypeLhs(tree) - : atypeFactory.getAnnotatedType(tree); - reportTreeType(tree, varType, TypeOccurrenceKind.DECLARED_TYPE); - return super.visitVariable(tree, unused); - } - - @Override - public Void visitMethod(MethodTree tree, Void unused) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); - return super.visitMethod(tree, unused); + public AbstractTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; + if (atypeFactory instanceof GenericAnnotatedTypeFactory) { + this.genFactory = (GenericAnnotatedTypeFactory) atypeFactory; + } else { + this.genFactory = null; + } + this.typeFormatter = createTypeFormatter(); } + /** + * The entry point for presenting type information of trees in the given class. + * + * @param tree a {@link ClassTree} that has been annotated by the factory + * @param treePath a {@link TreePath} to {@code tree} + */ @Override - public Void visitMethodInvocation(MethodInvocationTree tree, Void unused) { - reportTreeType( - tree, atypeFactory.methodFromUse(tree).executableType, TypeOccurrenceKind.USE_TYPE); - return super.visitMethodInvocation(tree, unused); + public void process(ClassTree tree, TreePath treePath) { + TypeInformationReporter visitor = createTypeInformationReporter(tree); + visitor.scan(treePath, null); } - @Override - public Void visitAssignment(AssignmentTree tree, Void unused) { - AnnotatedTypeMirror varType = - genFactory != null - ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) - : atypeFactory.getAnnotatedType(tree.getVariable()); - reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); - reportTreeType( - tree, - atypeFactory.getAnnotatedType(tree.getExpression()), - TypeOccurrenceKind.ASSIGN_RHS_TYPE); - return super.visitAssignment(tree, unused); - } + /** + * Creates the {@link TypeInformationReporter} to use. + * + * @param tree a {@link ClassTree} that has been annotated by the factory + * @return the {@link TypeInformationReporter} to use + */ + protected abstract TypeInformationReporter createTypeInformationReporter(ClassTree tree); - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void unused) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - AnnotatedTypeMirror varType = - genFactory != null - ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) - : atypeFactory.getAnnotatedType(tree.getVariable()); - reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); - reportTreeType( - tree, - atypeFactory.getAnnotatedType(tree.getExpression()), - TypeOccurrenceKind.ASSIGN_RHS_TYPE); - return super.visitCompoundAssignment(tree, unused); + /** + * Creates the {@link AnnotatedTypeFormatter} to use for output. + * + * @return the {@link AnnotatedTypeFormatter} to use for output + */ + protected AnnotatedTypeFormatter createTypeFormatter() { + return new DefaultAnnotatedTypeFormatter(true, true); } - @Override - public Void visitUnary(UnaryTree tree, Void unused) { - Tree.Kind treeKind = tree.getKind(); - switch (treeKind) { - case UNARY_PLUS: - case UNARY_MINUS: - case BITWISE_COMPLEMENT: - case LOGICAL_COMPLEMENT: - case PREFIX_INCREMENT: - case PREFIX_DECREMENT: - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - break; - case POSTFIX_INCREMENT: - case POSTFIX_DECREMENT: - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - if (genFactory != null) { + /** + * A visitor which traverses a class tree and reports type information of various sub-trees. + * + *

          Note: Since nested class trees will be type-checked separately, this visitor does not dive + * into any nested class trees. + */ + protected abstract class TypeInformationReporter extends TreePathScanner { + + /** The class tree in which it traverses and reports type information. */ + protected final ClassTree classTree; + + /** + * Root of the current class tree. This is a helper for computing positions of a sub-tree. + */ + protected final CompilationUnitTree currentRoot; + + /** The checker that's currently running. */ + protected final BaseTypeChecker checker; + + /** + * Constructs a new reporter for the given class tree. + * + * @param classTree the {@link ClassTree} + */ + public TypeInformationReporter(ClassTree classTree) { + this.classTree = classTree; + this.checker = atypeFactory.getChecker(); + this.currentRoot = this.checker.getPathToCompilationUnit().getCompilationUnit(); + } + + /** + * Report the {@code type} of {@code tree} in a particular {@code occurrenceKind}. + * + * @param tree the tree + * @param type the type + * @param occurrenceKind the occurrence kind + */ + protected abstract void reportTreeType( + Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind); + + @Override + public Void visitClass(ClassTree tree, Void unused) { + @SuppressWarnings("interning:not.interned") + boolean isNestedClass = tree != classTree; + if (isNestedClass) { + // Since nested class trees will be type-checked separately, this visitor does + // not dive into any nested class trees. + return null; + } + return super.visitClass(tree, unused); + } + + @Override + public Void visitTypeParameter(TypeParameterTree tree, Void unused) { reportTreeType( - tree, - genFactory.getAnnotatedTypeRhsUnaryAssign(tree), - TypeOccurrenceKind.ASSIGN_RHS_TYPE); - } - break; - default: - throw new BugInCF( - "Unsupported unary tree type " - + treeKind - + " for " - + TypeInformationPresenter.class.getCanonicalName()); - } - return super.visitUnary(tree, unused); - } - - @Override - public Void visitBinary(BinaryTree tree, Void unused) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - return super.visitBinary(tree, unused); - } - - @Override - public Void visitMemberSelect(MemberSelectTree tree, Void unused) { - if (TreeUtils.isFieldAccess(tree)) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - } else if (TreeUtils.isMethodAccess(tree)) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); - } - - return super.visitMemberSelect(tree, unused); - } - - @Override - public Void visitMemberReference(MemberReferenceTree tree, Void unused) { - // the declared type of the functional interface - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); - // the use type of the functional interface - reportTreeType( - tree, atypeFactory.getFnInterfaceFromTree(tree).first, TypeOccurrenceKind.USE_TYPE); - return super.visitMemberReference(tree, unused); - } - - @Override - public Void visitIdentifier(IdentifierTree tree, Void unused) { - switch (TreeUtils.elementFromUse(tree).getKind()) { - case ENUM_CONSTANT: - case FIELD: - case PARAMETER: - case LOCAL_VARIABLE: - case EXCEPTION_PARAMETER: - case RESOURCE_VARIABLE: - case CONSTRUCTOR: - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - break; - case METHOD: - reportTreeType( - tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); - break; - default: - break; - } - return super.visitIdentifier(tree, unused); - } - - @Override - public Void visitLiteral(LiteralTree tree, Void unused) { - reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); - return super.visitLiteral(tree, unused); - } - - /** The tree kinds for methods and lambda expressions. */ - private final ArraySet methodAndLambdaExpression = - new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); + tree, + atypeFactory.getAnnotatedTypeFromTypeTree(tree), + TypeOccurrenceKind.DECLARED_TYPE); + return super.visitTypeParameter(tree, unused); + } + + @Override + public Void visitVariable(VariableTree tree, Void unused) { + // TODO: "int x = 1" is a VariableTree, but there is no AssignmentTree and it + // TODO: is difficult to locate the "=" symbol. + AnnotatedTypeMirror varType = + genFactory != null + ? genFactory.getAnnotatedTypeLhs(tree) + : atypeFactory.getAnnotatedType(tree); + reportTreeType(tree, varType, TypeOccurrenceKind.DECLARED_TYPE); + return super.visitVariable(tree, unused); + } + + @Override + public Void visitMethod(MethodTree tree, Void unused) { + reportTreeType( + tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); + return super.visitMethod(tree, unused); + } - @Override - public Void visitReturn(ReturnTree tree, Void unused) { - // No output for void methods. - if (tree.getExpression() == null) { - return super.visitReturn(tree, unused); - } - - Tree enclosing = TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); - - AnnotatedTypeMirror ret = null; - if (enclosing.getKind() == Tree.Kind.METHOD) { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); - } else { - AnnotatedExecutableType result = - atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); - ret = result.getReturnType(); - } - - if (ret != null) { - reportTreeType(tree, ret, TypeOccurrenceKind.DECLARED_TYPE); - } - return super.visitReturn(tree, unused); + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, Void unused) { + reportTreeType( + tree, + atypeFactory.methodFromUse(tree).executableType, + TypeOccurrenceKind.USE_TYPE); + return super.visitMethodInvocation(tree, unused); + } + + @Override + public Void visitAssignment(AssignmentTree tree, Void unused) { + AnnotatedTypeMirror varType = + genFactory != null + ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) + : atypeFactory.getAnnotatedType(tree.getVariable()); + reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); + reportTreeType( + tree, + atypeFactory.getAnnotatedType(tree.getExpression()), + TypeOccurrenceKind.ASSIGN_RHS_TYPE); + return super.visitAssignment(tree, unused); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void unused) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + AnnotatedTypeMirror varType = + genFactory != null + ? genFactory.getAnnotatedTypeLhs(tree.getVariable()) + : atypeFactory.getAnnotatedType(tree.getVariable()); + reportTreeType(tree, varType, TypeOccurrenceKind.ASSIGN_LHS_DECLARED_TYPE); + reportTreeType( + tree, + atypeFactory.getAnnotatedType(tree.getExpression()), + TypeOccurrenceKind.ASSIGN_RHS_TYPE); + return super.visitCompoundAssignment(tree, unused); + } + + @Override + public Void visitUnary(UnaryTree tree, Void unused) { + Tree.Kind treeKind = tree.getKind(); + switch (treeKind) { + case UNARY_PLUS: + case UNARY_MINUS: + case BITWISE_COMPLEMENT: + case LOGICAL_COMPLEMENT: + case PREFIX_INCREMENT: + case PREFIX_DECREMENT: + reportTreeType( + tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + break; + case POSTFIX_INCREMENT: + case POSTFIX_DECREMENT: + reportTreeType( + tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + if (genFactory != null) { + reportTreeType( + tree, + genFactory.getAnnotatedTypeRhsUnaryAssign(tree), + TypeOccurrenceKind.ASSIGN_RHS_TYPE); + } + break; + default: + throw new BugInCF( + "Unsupported unary tree type " + + treeKind + + " for " + + TypeInformationPresenter.class.getCanonicalName()); + } + return super.visitUnary(tree, unused); + } + + @Override + public Void visitBinary(BinaryTree tree, Void unused) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + return super.visitBinary(tree, unused); + } + + @Override + public Void visitMemberSelect(MemberSelectTree tree, Void unused) { + if (TreeUtils.isFieldAccess(tree)) { + reportTreeType( + tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + } else if (TreeUtils.isMethodAccess(tree)) { + reportTreeType( + tree, + atypeFactory.getAnnotatedType(tree), + TypeOccurrenceKind.DECLARED_TYPE); + } + + return super.visitMemberSelect(tree, unused); + } + + @Override + public Void visitMemberReference(MemberReferenceTree tree, Void unused) { + // the declared type of the functional interface + reportTreeType( + tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.DECLARED_TYPE); + // the use type of the functional interface + reportTreeType( + tree, + atypeFactory.getFnInterfaceFromTree(tree).first, + TypeOccurrenceKind.USE_TYPE); + return super.visitMemberReference(tree, unused); + } + + @Override + public Void visitIdentifier(IdentifierTree tree, Void unused) { + switch (TreeUtils.elementFromUse(tree).getKind()) { + case ENUM_CONSTANT: + case FIELD: + case PARAMETER: + case LOCAL_VARIABLE: + case EXCEPTION_PARAMETER: + case RESOURCE_VARIABLE: + case CONSTRUCTOR: + reportTreeType( + tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + break; + case METHOD: + reportTreeType( + tree, + atypeFactory.getAnnotatedType(tree), + TypeOccurrenceKind.DECLARED_TYPE); + break; + default: + break; + } + return super.visitIdentifier(tree, unused); + } + + @Override + public Void visitLiteral(LiteralTree tree, Void unused) { + reportTreeType(tree, atypeFactory.getAnnotatedType(tree), TypeOccurrenceKind.USE_TYPE); + return super.visitLiteral(tree, unused); + } + + /** The tree kinds for methods and lambda expressions. */ + private final ArraySet methodAndLambdaExpression = + new ArraySet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); + + @Override + public Void visitReturn(ReturnTree tree, Void unused) { + // No output for void methods. + if (tree.getExpression() == null) { + return super.visitReturn(tree, unused); + } + + Tree enclosing = + TreePathUtil.enclosingOfKind(getCurrentPath(), methodAndLambdaExpression); + + AnnotatedTypeMirror ret = null; + if (enclosing.getKind() == Tree.Kind.METHOD) { + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + ret = atypeFactory.getMethodReturnType(enclosingMethod, tree); + } else { + AnnotatedExecutableType result = + atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); + ret = result.getReturnType(); + } + + if (ret != null) { + reportTreeType(tree, ret, TypeOccurrenceKind.DECLARED_TYPE); + } + return super.visitReturn(tree, unused); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java index a27faebdf1f..d7d2c425317 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/LspTypeInformationPresenter.java @@ -12,11 +12,13 @@ import com.sun.source.tree.VariableTree; import com.sun.source.util.SourcePositions; import com.sun.tools.javac.tree.JCTree; -import javax.tools.Diagnostic; + import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; +import javax.tools.Diagnostic; + /** * Presents formatted type information for various AST trees in a class. * @@ -25,195 +27,197 @@ */ public class LspTypeInformationPresenter extends AbstractTypeInformationPresenter { - /** - * Constructs a presenter for the given factory. - * - * @param atypeFactory the AnnotatedTypeFactory for the current analysis - */ - public LspTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - protected TypeInformationReporter createTypeInformationReporter(ClassTree tree) { - return new LspTypeInformationReporter(tree); - } - - /** Type information reporter that uses a format suitable for the LSP server. */ - protected class LspTypeInformationReporter extends TypeInformationReporter { - /** Computes positions of a sub-tree. */ - protected final SourcePositions sourcePositions; - /** - * Constructor. + * Constructs a presenter for the given factory. * - * @param classTree the {@link ClassTree} + * @param atypeFactory the AnnotatedTypeFactory for the current analysis */ - LspTypeInformationReporter(ClassTree classTree) { - super(classTree); - this.sourcePositions = atypeFactory.getTreeUtils().getSourcePositions(); + public LspTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); } - /** - * Reports a diagnostic message indicating the range corresponding to the given tree has the - * given type. Specifically, the message has key "lsp.type.information", and it contains the - * name of the checker, the given occurrenceKind, the given type, and the computed message range - * for the tree. If the tree is an artificial tree, this does not report anything. - * - * @param tree the tree that is used to find the corresponding range to report - * @param type the type that we are going to display - * @param occurrenceKind the kind of the given type - */ @Override - protected void reportTreeType( - Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind) { - TypeOccurrenceRange messageRange = computeTypeOccurrenceRange(tree); - if (messageRange == null) { - // Don't report if the tree can't be found in the source file. - // Please check the implementation of computeTypeOccurrenceRange for - // more details. - return; - } - - checker.reportError( - tree, - "lsp.type.information", - checker.getClass().getSimpleName(), - occurrenceKind, - typeFormatter.format(type), - messageRange); + protected TypeInformationReporter createTypeInformationReporter(ClassTree tree) { + return new LspTypeInformationReporter(tree); } - /** - * Computes the 0-based inclusive message range for the given tree. - * - *

          Note that the range sometimes don't cover the entire source code of the tree. For example, - * in "int a = 0", we have a variable tree "int a", but we only want to report the range of the - * identifier "a". This customizes the positions where we want the type information to show. - * - * @param tree the tree for which we want to compute the message range - * @return a message range corresponds to the tree - */ - protected @Nullable TypeOccurrenceRange computeTypeOccurrenceRange(Tree tree) { - long startPos = sourcePositions.getStartPosition(currentRoot, tree); - long endPos = sourcePositions.getEndPosition(currentRoot, tree); - if (startPos == Diagnostic.NOPOS || endPos == Diagnostic.NOPOS) { - // The tree doesn't exist in the source file. - // For example, a class tree may contain a child that represents - // a default constructor which is not explicitly written out in - // the source file. - // For this kind of trees, there's no way to compute their range - // in the source file. - return null; - } - - LineMap lineMap = currentRoot.getLineMap(); - startPos = ((JCTree) tree).getPreferredPosition(); - long startLine = lineMap.getLineNumber(startPos); - long startCol = lineMap.getColumnNumber(startPos); - long endLine = startLine; - long endCol; - - // We are decreasing endCol by 1 because we want it to be inclusive - switch (tree.getKind()) { - case UNARY_PLUS: - case UNARY_MINUS: - case BITWISE_COMPLEMENT: - case LOGICAL_COMPLEMENT: - case MULTIPLY: - case DIVIDE: - case REMAINDER: - case PLUS: - case MINUS: - case AND: - case XOR: - case OR: - case ASSIGNMENT: - case LESS_THAN: - case GREATER_THAN: - // 1-character operators - endCol = startCol; - break; - case PREFIX_INCREMENT: - case PREFIX_DECREMENT: - case POSTFIX_INCREMENT: - case POSTFIX_DECREMENT: - case LEFT_SHIFT: - case RIGHT_SHIFT: - case CONDITIONAL_AND: - case CONDITIONAL_OR: - case MULTIPLY_ASSIGNMENT: - case DIVIDE_ASSIGNMENT: - case REMAINDER_ASSIGNMENT: - case PLUS_ASSIGNMENT: - case MINUS_ASSIGNMENT: - case AND_ASSIGNMENT: - case XOR_ASSIGNMENT: - case OR_ASSIGNMENT: - case LESS_THAN_EQUAL: - case GREATER_THAN_EQUAL: - case EQUAL_TO: - case NOT_EQUAL_TO: - // 2-character operators - endCol = startCol + 1; - break; - case UNSIGNED_RIGHT_SHIFT: - case LEFT_SHIFT_ASSIGNMENT: - case RIGHT_SHIFT_ASSIGNMENT: - // 3-character operators - endCol = startCol + 2; - break; - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - // 4-character operators - endCol = startCol + 3; - break; - case IDENTIFIER: - endCol = startCol + ((IdentifierTree) tree).getName().length() - 1; - break; - case VARIABLE: - endCol = startCol + ((VariableTree) tree).getName().length() - 1; - break; - case MEMBER_SELECT: - // The preferred start column of MemberSelectTree locates the "." - // character before the member identifier. So we increase startCol - // by 1 to point to the start of the member identifier. - startCol += 1; - endCol = startCol + ((MemberSelectTree) tree).getIdentifier().length() - 1; - break; - case MEMBER_REFERENCE: - MemberReferenceTree memberReferenceTree = (MemberReferenceTree) tree; - - final int identifierLength; - if (memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW) { - identifierLength = 3; - } else { - identifierLength = memberReferenceTree.getName().length(); - } - - // The preferred position of a MemberReferenceTree is the head of - // its expression, which is not ideal. Here we compute the range of - // its identifier using the end position and the length of the identifier. - endLine = lineMap.getLineNumber(endPos); - endCol = lineMap.getColumnNumber(endPos) - 1; - startLine = endLine; - startCol = endCol - identifierLength + 1; - break; - case TYPE_PARAMETER: - endCol = startCol + ((TypeParameterTree) tree).getName().length() - 1; - break; - case METHOD: - endCol = startCol + ((MethodTree) tree).getName().length() - 1; - break; - case METHOD_INVOCATION: - return computeTypeOccurrenceRange(((MethodInvocationTree) tree).getMethodSelect()); - default: - endLine = lineMap.getLineNumber(endPos); - endCol = lineMap.getColumnNumber(endPos) - 1; - break; - } - - // convert 1-based positions to 0-based positions - return TypeOccurrenceRange.of(startLine - 1, startCol - 1, endLine - 1, endCol - 1); + /** Type information reporter that uses a format suitable for the LSP server. */ + protected class LspTypeInformationReporter extends TypeInformationReporter { + /** Computes positions of a sub-tree. */ + protected final SourcePositions sourcePositions; + + /** + * Constructor. + * + * @param classTree the {@link ClassTree} + */ + LspTypeInformationReporter(ClassTree classTree) { + super(classTree); + this.sourcePositions = atypeFactory.getTreeUtils().getSourcePositions(); + } + + /** + * Reports a diagnostic message indicating the range corresponding to the given tree has the + * given type. Specifically, the message has key "lsp.type.information", and it contains the + * name of the checker, the given occurrenceKind, the given type, and the computed message + * range for the tree. If the tree is an artificial tree, this does not report anything. + * + * @param tree the tree that is used to find the corresponding range to report + * @param type the type that we are going to display + * @param occurrenceKind the kind of the given type + */ + @Override + protected void reportTreeType( + Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind) { + TypeOccurrenceRange messageRange = computeTypeOccurrenceRange(tree); + if (messageRange == null) { + // Don't report if the tree can't be found in the source file. + // Please check the implementation of computeTypeOccurrenceRange for + // more details. + return; + } + + checker.reportError( + tree, + "lsp.type.information", + checker.getClass().getSimpleName(), + occurrenceKind, + typeFormatter.format(type), + messageRange); + } + + /** + * Computes the 0-based inclusive message range for the given tree. + * + *

          Note that the range sometimes don't cover the entire source code of the tree. For + * example, in "int a = 0", we have a variable tree "int a", but we only want to report the + * range of the identifier "a". This customizes the positions where we want the type + * information to show. + * + * @param tree the tree for which we want to compute the message range + * @return a message range corresponds to the tree + */ + protected @Nullable TypeOccurrenceRange computeTypeOccurrenceRange(Tree tree) { + long startPos = sourcePositions.getStartPosition(currentRoot, tree); + long endPos = sourcePositions.getEndPosition(currentRoot, tree); + if (startPos == Diagnostic.NOPOS || endPos == Diagnostic.NOPOS) { + // The tree doesn't exist in the source file. + // For example, a class tree may contain a child that represents + // a default constructor which is not explicitly written out in + // the source file. + // For this kind of trees, there's no way to compute their range + // in the source file. + return null; + } + + LineMap lineMap = currentRoot.getLineMap(); + startPos = ((JCTree) tree).getPreferredPosition(); + long startLine = lineMap.getLineNumber(startPos); + long startCol = lineMap.getColumnNumber(startPos); + long endLine = startLine; + long endCol; + + // We are decreasing endCol by 1 because we want it to be inclusive + switch (tree.getKind()) { + case UNARY_PLUS: + case UNARY_MINUS: + case BITWISE_COMPLEMENT: + case LOGICAL_COMPLEMENT: + case MULTIPLY: + case DIVIDE: + case REMAINDER: + case PLUS: + case MINUS: + case AND: + case XOR: + case OR: + case ASSIGNMENT: + case LESS_THAN: + case GREATER_THAN: + // 1-character operators + endCol = startCol; + break; + case PREFIX_INCREMENT: + case PREFIX_DECREMENT: + case POSTFIX_INCREMENT: + case POSTFIX_DECREMENT: + case LEFT_SHIFT: + case RIGHT_SHIFT: + case CONDITIONAL_AND: + case CONDITIONAL_OR: + case MULTIPLY_ASSIGNMENT: + case DIVIDE_ASSIGNMENT: + case REMAINDER_ASSIGNMENT: + case PLUS_ASSIGNMENT: + case MINUS_ASSIGNMENT: + case AND_ASSIGNMENT: + case XOR_ASSIGNMENT: + case OR_ASSIGNMENT: + case LESS_THAN_EQUAL: + case GREATER_THAN_EQUAL: + case EQUAL_TO: + case NOT_EQUAL_TO: + // 2-character operators + endCol = startCol + 1; + break; + case UNSIGNED_RIGHT_SHIFT: + case LEFT_SHIFT_ASSIGNMENT: + case RIGHT_SHIFT_ASSIGNMENT: + // 3-character operators + endCol = startCol + 2; + break; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + // 4-character operators + endCol = startCol + 3; + break; + case IDENTIFIER: + endCol = startCol + ((IdentifierTree) tree).getName().length() - 1; + break; + case VARIABLE: + endCol = startCol + ((VariableTree) tree).getName().length() - 1; + break; + case MEMBER_SELECT: + // The preferred start column of MemberSelectTree locates the "." + // character before the member identifier. So we increase startCol + // by 1 to point to the start of the member identifier. + startCol += 1; + endCol = startCol + ((MemberSelectTree) tree).getIdentifier().length() - 1; + break; + case MEMBER_REFERENCE: + MemberReferenceTree memberReferenceTree = (MemberReferenceTree) tree; + + final int identifierLength; + if (memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW) { + identifierLength = 3; + } else { + identifierLength = memberReferenceTree.getName().length(); + } + + // The preferred position of a MemberReferenceTree is the head of + // its expression, which is not ideal. Here we compute the range of + // its identifier using the end position and the length of the identifier. + endLine = lineMap.getLineNumber(endPos); + endCol = lineMap.getColumnNumber(endPos) - 1; + startLine = endLine; + startCol = endCol - identifierLength + 1; + break; + case TYPE_PARAMETER: + endCol = startCol + ((TypeParameterTree) tree).getName().length() - 1; + break; + case METHOD: + endCol = startCol + ((MethodTree) tree).getName().length() - 1; + break; + case METHOD_INVOCATION: + return computeTypeOccurrenceRange( + ((MethodInvocationTree) tree).getMethodSelect()); + default: + endLine = lineMap.getLineNumber(endPos); + endCol = lineMap.getColumnNumber(endPos) - 1; + break; + } + + // convert 1-based positions to 0-based positions + return TypeOccurrenceRange.of(startLine - 1, startCol - 1, endLine - 1, endCol - 1); + } } - } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java index 68bb4de61a2..13a80ac85f0 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeInformationPresenter.java @@ -11,11 +11,11 @@ * . */ public interface TypeInformationPresenter { - /** - * The entry point for presenting type information of trees in the given class. - * - * @param tree a ClassTree that has been type-checked by the factory - * @param treePath a {@link TreePath} to {@code tree} - */ - void process(ClassTree tree, TreePath treePath); + /** + * The entry point for presenting type information of trees in the given class. + * + * @param tree a ClassTree that has been type-checked by the factory + * @param treePath a {@link TreePath} to {@code tree} + */ + void process(ClassTree tree, TreePath treePath); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java index 60f96e5aa95..6fcf0d960d2 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceKind.java @@ -2,21 +2,21 @@ /** Types can occurr in different kinds of positions. */ public enum TypeOccurrenceKind { - /** The type of the tree at its use site. */ - USE_TYPE, - /** - * The declared type of the tree. For a method, it should be the method's signature. For a field, - * it should be the type of the field in its declaration. - */ - DECLARED_TYPE, - /** The declared type of the LHS of an assignment or compound assignment tree. */ - ASSIGN_LHS_DECLARED_TYPE, - /** - * The type of the RHS of an assignment or compound assignment tree. - * - *

          For a postfix operation, it can be considered as a special assignment tree, in which the LHS - * is returned and the RHS is the new value of the variable. In this situation, this message kind - * means the type of the new value of the variable. - */ - ASSIGN_RHS_TYPE, + /** The type of the tree at its use site. */ + USE_TYPE, + /** + * The declared type of the tree. For a method, it should be the method's signature. For a + * field, it should be the type of the field in its declaration. + */ + DECLARED_TYPE, + /** The declared type of the LHS of an assignment or compound assignment tree. */ + ASSIGN_LHS_DECLARED_TYPE, + /** + * The type of the RHS of an assignment or compound assignment tree. + * + *

          For a postfix operation, it can be considered as a special assignment tree, in which the + * LHS is returned and the RHS is the new value of the variable. In this situation, this message + * kind means the type of the new value of the variable. + */ + ASSIGN_RHS_TYPE, } diff --git a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java index d6c15a23c25..297fd44b54c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java +++ b/framework/src/main/java/org/checkerframework/framework/util/visualize/TypeOccurrenceRange.java @@ -5,48 +5,48 @@ * a piece of type information refers. All indices are 0-based since LSP uses 0-based positions. */ public class TypeOccurrenceRange { - /** 0-based line number of the start position. */ - private final long startLine; + /** 0-based line number of the start position. */ + private final long startLine; - /** 0-based column number of the start position. */ - private final long startCol; + /** 0-based column number of the start position. */ + private final long startCol; - /** 0-based line number of the end position. */ - private final long endLine; + /** 0-based line number of the end position. */ + private final long endLine; - /** 0-based column number of the end position. */ - private final long endCol; + /** 0-based column number of the end position. */ + private final long endCol; - /** - * Constructs a new {@link TypeOccurrenceRange} with the given position information. - * - * @param startLine 0-based line number of the start position - * @param startCol 0-based column number of the start position - * @param endLine 0-based line number of the end position - * @param endCol 0-based column number of the end position - */ - private TypeOccurrenceRange(long startLine, long startCol, long endLine, long endCol) { - this.startLine = startLine; - this.startCol = startCol; - this.endLine = endLine; - this.endCol = endCol; - } + /** + * Constructs a new {@link TypeOccurrenceRange} with the given position information. + * + * @param startLine 0-based line number of the start position + * @param startCol 0-based column number of the start position + * @param endLine 0-based line number of the end position + * @param endCol 0-based column number of the end position + */ + private TypeOccurrenceRange(long startLine, long startCol, long endLine, long endCol) { + this.startLine = startLine; + this.startCol = startCol; + this.endLine = endLine; + this.endCol = endCol; + } - /** - * Constructs a new {@link TypeOccurrenceRange} with the given position information. - * - * @param startLine 0-based line number of the start position - * @param startCol 0-based column number of the start position - * @param endLine 0-based line number of the end position - * @param endCol 0-based column number of the end position - * @return a new {@link TypeOccurrenceRange} with the given position information - */ - public static TypeOccurrenceRange of(long startLine, long startCol, long endLine, long endCol) { - return new TypeOccurrenceRange(startLine, startCol, endLine, endCol); - } + /** + * Constructs a new {@link TypeOccurrenceRange} with the given position information. + * + * @param startLine 0-based line number of the start position + * @param startCol 0-based column number of the start position + * @param endLine 0-based line number of the end position + * @param endCol 0-based column number of the end position + * @return a new {@link TypeOccurrenceRange} with the given position information + */ + public static TypeOccurrenceRange of(long startLine, long startCol, long endLine, long endCol) { + return new TypeOccurrenceRange(startLine, startCol, endLine, endCol); + } - @Override - public String toString() { - return String.format("(%d, %d, %d, %d)", startLine, startCol, endLine, endCol); - } + @Override + public String toString() { + return String.format("(%d, %d, %d, %d)", startLine, startCol, endLine, endCol); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java index f96ccd38706..09481342248 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationNoReturnsReceiverTest.java @@ -1,31 +1,32 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.testaccumulation.TestAccumulationNoReturnsReceiverChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * A test that the accumulation abstract checker is working correctly, using a simple accumulation * checker without a returns-receiver analysis. */ public class AccumulationNoReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AccumulationNoReturnsReceiverTest(List testFiles) { - super( - testFiles, - TestAccumulationNoReturnsReceiverChecker.class, - "accumulation-norr", - "-encoding", - "UTF-8"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AccumulationNoReturnsReceiverTest(List testFiles) { + super( + testFiles, + TestAccumulationNoReturnsReceiverChecker.class, + "accumulation-norr", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"accumulation-norr", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"accumulation-norr", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java index 870d7b48096..273290e5074 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AccumulationTest.java @@ -1,33 +1,34 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.testaccumulation.TestAccumulationChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * A test that the accumulation abstract checker is working correctly, using a simple accumulation * checker. */ public class AccumulationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AccumulationTest(List testFiles) { - super( - testFiles, - TestAccumulationChecker.class, - "accumulation", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-encoding", - "UTF-8"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AccumulationTest(List testFiles) { + super( + testFiles, + TestAccumulationChecker.class, + "accumulation", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-encoding", + "UTF-8"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"accumulation", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"accumulation", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java index ce05ae7b286..7a6ad6f1bfc 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AggregateTest.java @@ -1,22 +1,23 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.aggregate.AggregateOfCompoundChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class AggregateTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AggregateTest(List testFiles) { - super(testFiles, AggregateOfCompoundChecker.class, "aggregate", "-AresolveReflection"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AggregateTest(List testFiles) { + super(testFiles, AggregateOfCompoundChecker.class, "aggregate", "-AresolveReflection"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"aggregate"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"aggregate"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java index 9975c10744f..2207ccb24bd 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AliasingTest.java @@ -1,21 +1,22 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class AliasingTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AliasingTest(List testFiles) { - super(testFiles, org.checkerframework.common.aliasing.AliasingChecker.class, "aliasing"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AliasingTest(List testFiles) { + super(testFiles, org.checkerframework.common.aliasing.AliasingChecker.class, "aliasing"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"aliasing", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"aliasing", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java index 332d6c5ebcc..016af8b7719 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotatedForTest.java @@ -1,32 +1,33 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Perform tests of the {@code @AnnotatedFor} annotation with the Subtyping Checker. */ public class AnnotatedForTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public AnnotatedForTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.subtyping.SubtypingChecker.class, - "subtyping", - "-Aquals=org.checkerframework.framework.testchecker.util.SubQual,org.checkerframework.framework.testchecker.util.SuperQual", - "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public AnnotatedForTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.subtyping.SubtypingChecker.class, + "subtyping", + "-Aquals=org.checkerframework.framework.testchecker.util.SubQual,org.checkerframework.framework.testchecker.util.SuperQual", + "-AuseConservativeDefaultsForUncheckedCode=source,bytecode"); + } - /** - * Define the test directories for this test. - * - * @return the test directories - */ - @Parameters - public static String[] getTestDirs() { - return new String[] {"conservative-defaults/annotatedfor"}; - } + /** + * Define the test directories for this test. + * + * @return the test directories + */ + @Parameters + public static String[] getTestDirs() { + return new String[] {"conservative-defaults/annotatedfor"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java index 7e583b813d3..f6c919ecca3 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/AnnotationBuilderTest.java @@ -6,9 +6,7 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Options; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.framework.testchecker.util.AnnoWithStringArg; import org.checkerframework.framework.testchecker.util.Encrypted; import org.checkerframework.javacutil.AnnotationBuilder; @@ -17,305 +15,310 @@ import org.junit.Ignore; import org.junit.Test; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; + public class AnnotationBuilderTest { - private final ProcessingEnvironment env; - - public AnnotationBuilderTest() { - Context context = new Context(); - // Set source and target to 8 - Options options = Options.instance(context); - options.put(Option.SOURCE, "8"); - options.put(Option.TARGET, "8"); - - env = JavacProcessingEnvironment.instance(context); - JavaCompiler javac = JavaCompiler.instance(context); - // Even though source/target are set to 8, the modules in the JavaCompiler - // need to be initialized by setting the list of modules to nil. - javac.initModules(List.nil()); - javac.enterDone(); - } - - @Test - public void createAnnoWithoutValues() { - AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); - // AnnotationMirror anno = - builder.build(); - } - - @Test - public void createAnnoWithoutValues1() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - AnnotationMirror anno = builder.build(); - Assert.assertEquals(0, anno.getElementValues().size()); - } - - @Test - public void createAnnoWithValues0() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("value", "m"); - AnnotationMirror anno = builder.build(); - Assert.assertEquals(1, anno.getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void buildingTwice() { - AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); - builder.build(); - builder.build(); - } - - @Test(expected = BugInCF.class) - public void addingValuesAfterBuilding() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("value", "m"); - // AnnotationMirror anno = - builder.build(); - builder.setValue("value", "n"); - } - - @Test(expected = BugInCF.class) - public void notFoundElements() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("n", "m"); - } - - @Test(expected = BugInCF.class) - public void illegalValue() { - AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); - builder.setValue("value", 1); - } - - public static @interface A { - int[] numbers(); - } - - public static @interface B { - String[] strings(); - } - - @Test - public void listArrayPrimitive() { - AnnotationBuilder builder = new AnnotationBuilder(env, A.class); - builder.setValue("numbers", new Integer[] {34, 32, 43}); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test - public void listArrayObject() { - AnnotationBuilder builder = new AnnotationBuilder(env, B.class); - builder.setValue("strings", new String[] {"m", "n"}); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void listArrayObjectWrongType() { - AnnotationBuilder builder = new AnnotationBuilder(env, B.class); - builder.setValue("strings", new Object[] {"m", "n", 1}); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void listArrayObjectWrongType1() { - AnnotationBuilder builder = new AnnotationBuilder(env, B.class); - builder.setValue("strings", 1); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - public static @interface Prim { - int a(); - } - - @Test - public void primitiveValue() { - AnnotationBuilder builder = new AnnotationBuilder(env, Prim.class); - builder.setValue("a", 3); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void primitiveValueWithException() { - AnnotationBuilder builder = new AnnotationBuilder(env, A.class); - builder.setValue("a", 3.0); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - // Multiple values - public static @interface Mult { - int a(); - - String b(); - } - - @Test - public void multiple1() { - AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); - builder.setValue("a", 2); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test(expected = BugInCF.class) - public void multiple2() { - AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); - builder.setValue("a", "m"); - Assert.assertEquals(1, builder.build().getElementValues().size()); - } - - @Test - public void multiple3() { - AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); - builder.setValue("a", 1); - builder.setValue("b", "mark"); - Assert.assertEquals(2, builder.build().getElementValues().size()); - } - - public static @interface ClassElt { - Class value(); - } - - @Test - public void testClassPositive() { - AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); - builder.setValue("value", String.class); - builder.setValue("value", int.class); - builder.setValue("value", int[].class); - builder.setValue("value", void.class); - Object storedValue = builder.build().getElementValues().values().iterator().next().getValue(); - Assert.assertTrue( - "storedValue is " + storedValue.getClass(), storedValue instanceof TypeMirror); - } - - @Test(expected = BugInCF.class) - public void testClassNegative() { - AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); - builder.setValue("value", 2); - } - - public static @interface RestrictedClassElt { - Class value(); - } - - @Test - public void testRestClassPositive() { - AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); - builder.setValue("value", Integer.class); - } - - // Failing test for now. AnnotationBuilder is a bit permissive - // It doesn't not check type argument subtyping - @Test(expected = BugInCF.class) - @Ignore // bug for now - public void testRetClassNegative() { - AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); - builder.setValue("value", String.class); - } - - enum MyEnum { - OK, - NOT; - } - - enum OtherEnum { - TEST; - } - - public static @interface EnumElt { - MyEnum value(); - } - - @Test - public void testEnumPositive() { - AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); - builder.setValue("value", MyEnum.OK); - builder.setValue("value", MyEnum.NOT); - } - - @Test(expected = BugInCF.class) - public void testEnumNegative() { - AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); - builder.setValue("value", 2); - } - - @Test(expected = BugInCF.class) - public void testEnumNegative2() { - AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); - builder.setValue("value", OtherEnum.TEST); - } - - public static @interface Anno { - String value(); - - int[] can(); - } - - @Test - public void testToString1() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno", - builder.build().toString()); - } - - @Test - public void testToString2() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("value", "string"); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(\"string\")", - builder.build().toString()); - } - - @Test - public void testToString3() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("can", new Object[] {1}); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(can={1})", - builder.build().toString()); - } - - @Test - public void testToString4() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("value", "m"); - builder.setValue("can", new Object[] {1}); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" - + "(value=\"m\", can={1})", - builder.build().toString()); - } - - @Test - public void testToString5() { - AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); - builder.setValue("can", new Object[] {1}); - builder.setValue("value", "m"); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" - + "(can={1}, value=\"m\")", - builder.build().toString()); - } - - public static @interface MyAnno {} - - public static @interface ContainingAnno { - MyAnno value(); - } - - @Test - public void testAnnoAsArgPositive() { - AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), MyAnno.class); - AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); - builder.setValue("value", anno); - Assert.assertEquals( - "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.ContainingAnno(@org.checkerframework.framework.test.junit.AnnotationBuilderTest.MyAnno)", - builder.build().toString()); - } - - @Test(expected = BugInCF.class) - public void testAnnoAsArgNegative() { - AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), Anno.class); - AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); - builder.setValue("value", anno); - } + private final ProcessingEnvironment env; + + public AnnotationBuilderTest() { + Context context = new Context(); + // Set source and target to 8 + Options options = Options.instance(context); + options.put(Option.SOURCE, "8"); + options.put(Option.TARGET, "8"); + + env = JavacProcessingEnvironment.instance(context); + JavaCompiler javac = JavaCompiler.instance(context); + // Even though source/target are set to 8, the modules in the JavaCompiler + // need to be initialized by setting the list of modules to nil. + javac.initModules(List.nil()); + javac.enterDone(); + } + + @Test + public void createAnnoWithoutValues() { + AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); + // AnnotationMirror anno = + builder.build(); + } + + @Test + public void createAnnoWithoutValues1() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + AnnotationMirror anno = builder.build(); + Assert.assertEquals(0, anno.getElementValues().size()); + } + + @Test + public void createAnnoWithValues0() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("value", "m"); + AnnotationMirror anno = builder.build(); + Assert.assertEquals(1, anno.getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void buildingTwice() { + AnnotationBuilder builder = new AnnotationBuilder(env, Encrypted.class); + builder.build(); + builder.build(); + } + + @Test(expected = BugInCF.class) + public void addingValuesAfterBuilding() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("value", "m"); + // AnnotationMirror anno = + builder.build(); + builder.setValue("value", "n"); + } + + @Test(expected = BugInCF.class) + public void notFoundElements() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("n", "m"); + } + + @Test(expected = BugInCF.class) + public void illegalValue() { + AnnotationBuilder builder = new AnnotationBuilder(env, AnnoWithStringArg.class); + builder.setValue("value", 1); + } + + public static @interface A { + int[] numbers(); + } + + public static @interface B { + String[] strings(); + } + + @Test + public void listArrayPrimitive() { + AnnotationBuilder builder = new AnnotationBuilder(env, A.class); + builder.setValue("numbers", new Integer[] {34, 32, 43}); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test + public void listArrayObject() { + AnnotationBuilder builder = new AnnotationBuilder(env, B.class); + builder.setValue("strings", new String[] {"m", "n"}); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void listArrayObjectWrongType() { + AnnotationBuilder builder = new AnnotationBuilder(env, B.class); + builder.setValue("strings", new Object[] {"m", "n", 1}); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void listArrayObjectWrongType1() { + AnnotationBuilder builder = new AnnotationBuilder(env, B.class); + builder.setValue("strings", 1); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + public static @interface Prim { + int a(); + } + + @Test + public void primitiveValue() { + AnnotationBuilder builder = new AnnotationBuilder(env, Prim.class); + builder.setValue("a", 3); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void primitiveValueWithException() { + AnnotationBuilder builder = new AnnotationBuilder(env, A.class); + builder.setValue("a", 3.0); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + // Multiple values + public static @interface Mult { + int a(); + + String b(); + } + + @Test + public void multiple1() { + AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); + builder.setValue("a", 2); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test(expected = BugInCF.class) + public void multiple2() { + AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); + builder.setValue("a", "m"); + Assert.assertEquals(1, builder.build().getElementValues().size()); + } + + @Test + public void multiple3() { + AnnotationBuilder builder = new AnnotationBuilder(env, Mult.class); + builder.setValue("a", 1); + builder.setValue("b", "mark"); + Assert.assertEquals(2, builder.build().getElementValues().size()); + } + + public static @interface ClassElt { + Class value(); + } + + @Test + public void testClassPositive() { + AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); + builder.setValue("value", String.class); + builder.setValue("value", int.class); + builder.setValue("value", int[].class); + builder.setValue("value", void.class); + Object storedValue = + builder.build().getElementValues().values().iterator().next().getValue(); + Assert.assertTrue( + "storedValue is " + storedValue.getClass(), storedValue instanceof TypeMirror); + } + + @Test(expected = BugInCF.class) + public void testClassNegative() { + AnnotationBuilder builder = new AnnotationBuilder(env, ClassElt.class); + builder.setValue("value", 2); + } + + public static @interface RestrictedClassElt { + Class value(); + } + + @Test + public void testRestClassPositive() { + AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); + builder.setValue("value", Integer.class); + } + + // Failing test for now. AnnotationBuilder is a bit permissive + // It doesn't not check type argument subtyping + @Test(expected = BugInCF.class) + @Ignore // bug for now + public void testRetClassNegative() { + AnnotationBuilder builder = new AnnotationBuilder(env, RestrictedClassElt.class); + builder.setValue("value", String.class); + } + + enum MyEnum { + OK, + NOT; + } + + enum OtherEnum { + TEST; + } + + public static @interface EnumElt { + MyEnum value(); + } + + @Test + public void testEnumPositive() { + AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); + builder.setValue("value", MyEnum.OK); + builder.setValue("value", MyEnum.NOT); + } + + @Test(expected = BugInCF.class) + public void testEnumNegative() { + AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); + builder.setValue("value", 2); + } + + @Test(expected = BugInCF.class) + public void testEnumNegative2() { + AnnotationBuilder builder = new AnnotationBuilder(env, EnumElt.class); + builder.setValue("value", OtherEnum.TEST); + } + + public static @interface Anno { + String value(); + + int[] can(); + } + + @Test + public void testToString1() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno", + builder.build().toString()); + } + + @Test + public void testToString2() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("value", "string"); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(\"string\")", + builder.build().toString()); + } + + @Test + public void testToString3() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("can", new Object[] {1}); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno(can={1})", + builder.build().toString()); + } + + @Test + public void testToString4() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("value", "m"); + builder.setValue("can", new Object[] {1}); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" + + "(value=\"m\", can={1})", + builder.build().toString()); + } + + @Test + public void testToString5() { + AnnotationBuilder builder = new AnnotationBuilder(env, Anno.class); + builder.setValue("can", new Object[] {1}); + builder.setValue("value", "m"); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.Anno" + + "(can={1}, value=\"m\")", + builder.build().toString()); + } + + public static @interface MyAnno {} + + public static @interface ContainingAnno { + MyAnno value(); + } + + @Test + public void testAnnoAsArgPositive() { + AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), MyAnno.class); + AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); + builder.setValue("value", anno); + Assert.assertEquals( + "@org.checkerframework.framework.test.junit.AnnotationBuilderTest.ContainingAnno(@org.checkerframework.framework.test.junit.AnnotationBuilderTest.MyAnno)", + builder.build().toString()); + } + + @Test(expected = BugInCF.class) + public void testAnnoAsArgNegative() { + AnnotationMirror anno = AnnotationBuilder.fromClass(env.getElementUtils(), Anno.class); + AnnotationBuilder builder = new AnnotationBuilder(env, ContainingAnno.class); + builder.setValue("value", anno); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java index 90953ebca3f..1417468174b 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ClassValTest.java @@ -1,22 +1,23 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests the ClassVal Checker. */ public class ClassValTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ClassValTest(List testFiles) { - super(testFiles, org.checkerframework.common.reflection.ClassValChecker.class, "classval"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ClassValTest(List testFiles) { + super(testFiles, org.checkerframework.common.reflection.ClassValChecker.class, "classval"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"classval"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"classval"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java index b287eefe2e9..7f669d9a293 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/CompoundCheckerTest.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.compound.CompoundChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the compound checker design pattern. */ public class CompoundCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public CompoundCheckerTest(List testFiles) { - super(testFiles, CompoundChecker.class, "compound-checker"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public CompoundCheckerTest(List testFiles) { + super(testFiles, CompoundChecker.class, "compound-checker"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"compound-checker"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"compound-checker"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java index 541335aea85..aa0018635d9 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingLowerBoundTest.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.defaulting.DefaultingLowerBoundChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Created by jburke on 9/29/14. */ public class DefaultingLowerBoundTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public DefaultingLowerBoundTest(List testFiles) { - super(testFiles, DefaultingLowerBoundChecker.class, "defaulting"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public DefaultingLowerBoundTest(List testFiles) { + super(testFiles, DefaultingLowerBoundChecker.class, "defaulting"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"defaulting/lowerbound"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"defaulting/lowerbound"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java index c0a00feaa5c..a5fb56c473a 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/DefaultingUpperBoundTest.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.defaulting.DefaultingUpperBoundChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Created by jburke on 9/29/14. */ public class DefaultingUpperBoundTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public DefaultingUpperBoundTest(List testFiles) { - super(testFiles, DefaultingUpperBoundChecker.class, "defaulting"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public DefaultingUpperBoundTest(List testFiles) { + super(testFiles, DefaultingUpperBoundChecker.class, "defaulting"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"defaulting/upperbound"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"defaulting/upperbound"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java index df6cf4e0408..f269c5bf3b2 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowExpressionCheckerTest.java @@ -1,22 +1,23 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.flowexpression.FlowExpressionChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class FlowExpressionCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public FlowExpressionCheckerTest(List testFiles) { - super(testFiles, FlowExpressionChecker.class, "flowexpression"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public FlowExpressionCheckerTest(List testFiles) { + super(testFiles, FlowExpressionChecker.class, "flowexpression"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"flowexpression", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"flowexpression", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java index 92f32fad51b..adcb32215b0 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FlowTest.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.FlowTestChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** */ public class FlowTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public FlowTest(List testFiles) { - super(testFiles, FlowTestChecker.class, "flow", "-AcheckPurityAnnotations"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public FlowTest(List testFiles) { + super(testFiles, FlowTestChecker.class, "flow", "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"flow", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"flow", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java index 53b37a0f222..aff43610c89 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkJavacErrorsTest.java @@ -1,22 +1,23 @@ package org.checkerframework.framework.test.junit; -import java.io.File; import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.checkerframework.framework.testchecker.util.EvenOddChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; + /** JUnit tests for the Checker Framework, using the {@link EvenOddChecker}. */ // See framework/tests/framework/README for why this is a CFPerFileTest. // See FrameworkTest for tests that do not contain Java errors. public class FrameworkJavacErrorsTest extends CheckerFrameworkPerFileTest { - public FrameworkJavacErrorsTest(File testFile) { - super(testFile, EvenOddChecker.class, "framework"); - } + public FrameworkJavacErrorsTest(File testFile) { + super(testFile, EvenOddChecker.class, "framework"); + } - @Parameters - public static String[] getTestDirs() { - // Do not include all-systems! - return new String[] {"framework-javac-errors"}; - } + @Parameters + public static String[] getTestDirs() { + // Do not include all-systems! + return new String[] {"framework-javac-errors"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java index 37b57e80601..066b2391612 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/FrameworkTest.java @@ -1,21 +1,22 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.EvenOddChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** JUnit tests for the Checker Framework, using the {@link EvenOddChecker}. */ // See FrameworkJavacErrorsTest for tests that can contain Java errors. public class FrameworkTest extends CheckerFrameworkPerDirectoryTest { - public FrameworkTest(List testFiles) { - super(testFiles, EvenOddChecker.class, "framework"); - } + public FrameworkTest(List testFiles) { + super(testFiles, EvenOddChecker.class, "framework"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"framework", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"framework", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java index aa912eac64f..0d10232d123 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/H1H2CheckerTest.java @@ -1,27 +1,28 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.h1h2checker.H1H2Checker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class H1H2CheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public H1H2CheckerTest(List testFiles) { - super( - testFiles, - H1H2Checker.class, - "h1h2checker", - "-Astubs=tests/h1h2checker/h1h2checker.astub", - "-AcheckEnclosingExpr"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public H1H2CheckerTest(List testFiles) { + super( + testFiles, + H1H2Checker.class, + "h1h2checker", + "-Astubs=tests/h1h2checker/h1h2checker.astub", + "-AcheckEnclosingExpr"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"h1h2checker"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"h1h2checker"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java index e1ee14a77a1..048e87ed96b 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsTest.java @@ -1,24 +1,25 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.initializedfields.InitializedFieldsChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class InitializedFieldsTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a InitializedFieldsTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InitializedFieldsTest(List testFiles) { - super(testFiles, InitializedFieldsChecker.class, "initialized-fields"); - } + /** + * Create a InitializedFieldsTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InitializedFieldsTest(List testFiles) { + super(testFiles, InitializedFieldsChecker.class, "initialized-fields"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"initialized-fields", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"initialized-fields", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java index 5a3d4431c20..1bf1bc032ea 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/InitializedFieldsValueTest.java @@ -1,32 +1,33 @@ package org.checkerframework.framework.test.junit; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.junit.runners.Parameterized.Parameters; public class InitializedFieldsValueTest extends CheckerFrameworkPerDirectoryTest { - /** - * Create a InitializedFieldsValueTest. - * - * @param testFiles the files containing test code, which will be type-checked - */ - public InitializedFieldsValueTest(List testFiles) { - super( - testFiles, - Arrays.asList( - "org.checkerframework.common.initializedfields.InitializedFieldsChecker", - "org.checkerframework.common.value.ValueChecker"), - "initialized-fields-value", - Collections.emptyList() // classpathextra - ); - } + /** + * Create a InitializedFieldsValueTest. + * + * @param testFiles the files containing test code, which will be type-checked + */ + public InitializedFieldsValueTest(List testFiles) { + super( + testFiles, + Arrays.asList( + "org.checkerframework.common.initializedfields.InitializedFieldsChecker", + "org.checkerframework.common.value.ValueChecker"), + "initialized-fields-value", + Collections.emptyList() // classpathextra + ); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"initialized-fields-value", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"initialized-fields-value", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java index 0c813472bd8..b559f8b03a4 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/LubGlbTest.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.lubglb.LubGlbChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** */ public class LubGlbTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public LubGlbTest(List testFiles) { - super(testFiles, LubGlbChecker.class, "lubglb"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public LubGlbTest(List testFiles) { + super(testFiles, LubGlbChecker.class, "lubglb"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"lubglb"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"lubglb"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java index 30e871cc79c..76278b69798 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/MethodValTest.java @@ -1,22 +1,26 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests the MethodVal Checker. */ public class MethodValTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public MethodValTest(List testFiles) { - super(testFiles, org.checkerframework.common.reflection.MethodValChecker.class, "methodval"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public MethodValTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.reflection.MethodValChecker.class, + "methodval"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"methodval"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"methodval"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java index 63737a0f26f..cfd2b025a3f 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/NonTopDefaultTest.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.nontopdefault.NTDChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests the NonTopDefault Checker. */ public class NonTopDefaultTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public NonTopDefaultTest(List testFiles) { - super(testFiles, NTDChecker.class, "nontopdefault"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public NonTopDefaultTest(List testFiles) { + super(testFiles, NTDChecker.class, "nontopdefault"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"nontopdefault"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"nontopdefault"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java index fcef4d729ec..bd3161418cd 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/PuritySuggestionsTest.java @@ -1,28 +1,29 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.FlowTestChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests for the {@code -AsuggestPureMethods} command-line argument. */ public class PuritySuggestionsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public PuritySuggestionsTest(List testFiles) { - super( - testFiles, - FlowTestChecker.class, - "flow", - "-AsuggestPureMethods", - "-AcheckPurityAnnotations"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public PuritySuggestionsTest(List testFiles) { + super( + testFiles, + FlowTestChecker.class, + "flow", + "-AsuggestPureMethods", + "-AcheckPurityAnnotations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"purity-suggestions"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"purity-suggestions"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java index 0982ff1f6ed..4c10e309ce5 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/RangeTest.java @@ -1,668 +1,709 @@ package org.checkerframework.framework.test.junit; +import org.checkerframework.common.value.util.Range; +import org.junit.Assert; +import org.junit.Test; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; + import javax.lang.model.type.TypeKind; -import org.checkerframework.common.value.util.Range; -import org.junit.Assert; -import org.junit.Test; /** This class tests the Range class, independent of the Value Checker. */ public class RangeTest { - // These sets of values may be excessively long; perhaps trim them down later. - - long[] rangeBounds = { - Long.MIN_VALUE, - Long.MIN_VALUE + 1, - Integer.MIN_VALUE - 1000L, - Integer.MIN_VALUE - 10L, - Integer.MIN_VALUE, - Integer.MIN_VALUE + 1L, - Short.MIN_VALUE - 1000L, - Short.MIN_VALUE - 10L, - Short.MIN_VALUE, - Short.MIN_VALUE + 1L, - Character.MIN_VALUE - 1000L, - Character.MIN_VALUE - 10L, - Character.MIN_VALUE, // 0L - Character.MIN_VALUE + 1L, - Byte.MIN_VALUE - 1000L, - Byte.MIN_VALUE - 10L, - Byte.MIN_VALUE, - Byte.MIN_VALUE + 1L, - Byte.MAX_VALUE - 1L, - Byte.MAX_VALUE, - Byte.MAX_VALUE + 10L, - Byte.MAX_VALUE + 1000L, - Short.MAX_VALUE - 1L, - Short.MAX_VALUE, - Short.MAX_VALUE + 10L, - Short.MAX_VALUE + 1000L, - Character.MAX_VALUE - 1L, - Character.MAX_VALUE, - Character.MAX_VALUE + 10L, - Character.MAX_VALUE + 1000L, - Integer.MAX_VALUE - 1, - Integer.MAX_VALUE, - Integer.MAX_VALUE + 10L, - Integer.MAX_VALUE + 1000L, - Long.MAX_VALUE - 1, - Long.MAX_VALUE - }; - long[] values = { - Long.MIN_VALUE, - Long.MIN_VALUE + 1L, - Integer.MIN_VALUE, - Integer.MIN_VALUE + 1L, - Short.MIN_VALUE, - Short.MIN_VALUE + 1L, - Byte.MIN_VALUE, - Byte.MIN_VALUE + 1L, - -8L, - -4L, - -2L, - -1L, - Character.MIN_VALUE, // 0L - Character.MIN_VALUE + 1L, // 1L - 2L, - 4L, - 8L, - Byte.MAX_VALUE - 1L, - Byte.MAX_VALUE, - Short.MAX_VALUE - 1L, - Short.MAX_VALUE, - Character.MAX_VALUE - 1L, - Character.MAX_VALUE, - Integer.MAX_VALUE - 1L, - Integer.MAX_VALUE, - Long.MAX_VALUE - 1L, - Long.MAX_VALUE - }; - - /** Contains a Range for every combination of values in rangeBounds. */ - Range[] ranges; - - static final long INT_WIDTH = (long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE + 1; - - static final long SHORT_WIDTH = Short.MAX_VALUE - Short.MIN_VALUE + 1; - - static final long BYTE_WIDTH = Byte.MAX_VALUE - Byte.MIN_VALUE + 1; - - static final long CHAR_WIDTH = Character.MAX_VALUE - Character.MIN_VALUE + 1; - - public RangeTest() { - // Initialize the ranges list to every combination of values in rangeBounds. - List rangesList = new ArrayList<>(); - for (long lowerbound : rangeBounds) { - for (long upperbound : rangeBounds) { - if (lowerbound <= upperbound) { - rangesList.add(Range.create(lowerbound, upperbound)); - } - } - } - ranges = rangesList.toArray(new Range[0]); - } - - /** The element is a member of the range. */ - static class RangeAndElement { - Range range; - long element; - - RangeAndElement(Range range, long element) { - if (!range.contains(element)) { - throw new IllegalArgumentException(); - } - this.range = range; - this.element = element; - } - } - - static class RangeAndTwoElements { - Range range; - long a; - long b; - } - - ValuesInRangeIterator valuesInRange(Range r) { - return new ValuesInRangeIterator(r); - } - - RangeAndElementIterator rangeAndElements() { - return new RangeAndElementIterator(); - } - - @SuppressWarnings("IterableAndIterator") // TODO - class RangeAndElementIterator implements Iterator, Iterable { - // This is the index of the range that is currently being examined. - // It is in [0..ranges.length]. - int ri; - Range range; - ValuesInRangeIterator vi; - RangeAndElement nextValue; - boolean nextValueValid = false; - - public RangeAndElementIterator() { - ri = 0; - range = ranges[ri]; - vi = new ValuesInRangeIterator(range); - } - - @Override - public RangeAndElement next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - nextValueValid = false; - return nextValue; - } - - @Override - public boolean hasNext() { - if (nextValueValid) { - return true; - } - while (!vi.hasNext()) { - ri++; - if (ri == ranges.length) { - return false; - } - range = ranges[ri]; - vi = new ValuesInRangeIterator(range); - } - nextValue = new RangeAndElement(range, vi.next()); - nextValueValid = true; - return true; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator iterator() { - return this; - } - } - - @SuppressWarnings("IterableAndIterator") // TODO - class ValuesInRangeIterator implements Iterator, Iterable { - - Range range; - // This is the first index that has NOT been examined. It is in [0..values.length]. - int i = 0; - long nextValue; - boolean nextValueValid = false; - - @SuppressWarnings("StaticAssignmentInConstructor") - public ValuesInRangeIterator(Range range) { - this.range = range; - Range.ignoreOverflow = false; - } - - @Override - public Long next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - nextValueValid = false; - return nextValue; - } - - @Override - public boolean hasNext() { - if (nextValueValid) { - return true; - } - while (i < values.length) { - nextValue = values[i]; - i++; - if (range.contains(nextValue)) { - nextValueValid = true; - break; - } - } - return nextValueValid; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator iterator() { - return this; - } - } - - @Test - public void testIntRange() { - for (Range range : ranges) { - Range result = range.intRange(); - for (long value : values) { - if (value < range.from + INT_WIDTH - && value > range.to - INT_WIDTH - && (Math.abs(range.from) - 1) / Integer.MIN_VALUE - == (Math.abs(range.to) - 1) / Integer.MIN_VALUE) { - // filter out test data that would cause Range.intRange to return INT_EVERYTHING - int intValue = (int) value; - assert (range.contains(value) && result.contains(intValue)) - || (!range.contains(value) && !result.contains(intValue)) - : String.format( - "Range.intRange failure: %s => %s; witness = %s", range, result, intValue); - } - } - } - } - - @Test - public void testShortRange() { - for (Range range : ranges) { - Range result = range.shortRange(); - for (long value : values) { - if (value < range.from + SHORT_WIDTH - && value > range.to - SHORT_WIDTH - && (Math.abs(range.from) - 1) / Short.MIN_VALUE - == (Math.abs(range.to) - 1) / Short.MIN_VALUE) { - // filter out test data that would cause Range.shortRange to return - // SHORT_EVERYTHING - short shortValue = (short) value; - assert (range.contains(value) && result.contains(shortValue)) - || (!range.contains(value) && !result.contains(shortValue)) - : String.format( - "Range.shortRange failure: %s => %s; witness = %s", range, result, shortValue); - } - } - } - } - - @Test - public void testCharRange() { - Range.ignoreOverflow = false; - for (Range range : ranges) { - Range result = range.charRange(); - for (long value : values) { - if (value < range.from + CHAR_WIDTH - && value > range.to - CHAR_WIDTH - && (Math.abs(range.from + Short.MIN_VALUE) - 1) / Short.MIN_VALUE - == (Math.abs(range.to + Short.MIN_VALUE) - 1) / Short.MIN_VALUE) { - // filter out test data that would cause Range.CharRange to return - // CHAR_EVERYTHING - // char range interval is a right shift of the short range interval - char charValue = (char) value; - assert (range.contains(value) && result.contains(charValue)) - || (!range.contains(value) && !result.contains(charValue)) - : String.format( - "Range.byteRange failure: %s => %s; witness = %s", range, result, charValue); - } - } - } - } - - @Test - public void testByteRange() { - for (Range range : ranges) { - Range result = range.byteRange(); - for (long value : values) { - if (value < range.from + BYTE_WIDTH - && value > range.to - BYTE_WIDTH - && (Math.abs(range.from) - 1) / Byte.MIN_VALUE - == (Math.abs(range.to) - 1) / Byte.MIN_VALUE) { - // filter out test data that would cause Range.ByteRange to return - // BYTE_EVERYTHING - byte byteValue = (byte) value; - assert (range.contains(value) && result.contains(byteValue)) - || (!range.contains(value) && !result.contains(byteValue)) - : String.format( - "Range.byteRange failure: %s => %s; witness = %s", range, result, byteValue); - } - } - } - - Range r1 = Range.create(5, 1000); - Range r2 = Range.create(1024 + 17, 1024 + 22); - Range r3 = Range.create(5, Byte.MAX_VALUE + 2); - - Range.ignoreOverflow = true; - - assert r1.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); - assert r2.byteRange().equals(Range.create(Byte.MAX_VALUE, Byte.MAX_VALUE)); - assert r3.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); - - Range.ignoreOverflow = false; - - assert r1.byteRange().equals(Range.BYTE_EVERYTHING); - assert r2.byteRange().equals(Range.create(17, 22)); - assert r3.byteRange().equals(Range.BYTE_EVERYTHING); - } - - @Test - public void testUnion() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - Range result = range1.union(range2); - for (long value : values) { - if (range1.contains(value) || range2.contains(value)) { - assert result.contains(value) - : String.format( - "Range.union failure: %s %s %s; witness = %s", range1, range2, result, value); - } - } - } - } - } - - @Test - public void testIntersect() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - Range result = range1.intersect(range2); - for (long value : values) { - assert ((range1.contains(value) && range2.contains(value)) == result.contains(value)) - : String.format( - "Range.intersect failure: %s %s => %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testPlus() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.plus(re2.range); - assert result.contains(re1.element + re2.element) - : String.format( - "Range.plus failure: %s %s => %s; witnesses %s + %s => %s", - re1.range, re2.range, result, re1.element, re2.element, re1.element + re2.element); - } - } - } - - @Test - public void testMinus() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.minus(re2.range); - assert result.contains(re1.element - re2.element) - : String.format( - "Range.minus failure: %s %s => %s; witnesses %s - %s => %s", - re1.range, re2.range, result, re1.element, re2.element, re1.element - re2.element); - } - } - } - - @Test - public void testTimes() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.times(re2.range); - assert result.contains(re1.element * re2.element) - : String.format( - "Range.times failure: %s %s => %s; witnesses %s * %s => %s", - re1.range, re2.range, result, re1.element, re2.element, re1.element * re2.element); - } - } - } - - @Test - public void testDivide() { - assert Range.create(1, 2).divide(Range.create(0, 0)) == Range.NOTHING; - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - if (re2.element == 0) { - continue; - } - Range result = re1.range.divide(re2.range); - Long witness = re1.element / re2.element; - assert result.contains(witness) - : String.format( - "Range.divide failure: %s %s => %s; witnesses %s / %s => %s", - re1.range, re2.range, result, re1.element, re2.element, witness); - } - } - } - - @Test - public void testRemainder() { - assert Range.create(1, 2).remainder(Range.create(0, 0)) == Range.NOTHING; - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - if (re2.element == 0) { - continue; - } - Range result = re1.range.remainder(re2.range); - Long witness = re1.element % re2.element; - assert result.contains(witness) - : String.format( - "Range.remainder failure: %s %s => %s; witnesses %s %% %s => %s", - re1.range, re2.range, result, re1.element, re2.element, witness); - } - } - } - - @Test - public void testShiftLeft() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.shiftLeft(re2.range); - assert result.contains(re1.element << re2.element) - : String.format( - "Range.shiftLeft failure: %s %s => %s; witnesses %s << %s => %s", - re1.range, re2.range, result, re1.element, re2.element, re1.element << re2.element); - } - } - } - - @Test - public void testSignedShiftRight() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.signedShiftRight(re2.range); - assert result.contains(re1.element >> re2.element) - : String.format( - "Range.signedShiftRight failure: %s %s => %s; witnesses %s >> %s =>" + " %s", - re1.range, re2.range, result, re1.element, re2.element, re1.element >> re2.element); - } - } - } - - @Test - public void testUnsignedShiftRight() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result = re1.range.unsignedShiftRight(re2.range); - if (re1.range.from >= 0) { - assert result.contains(re1.element >>> re2.element) - : String.format( - "Range.unsignedShiftRight failure: %s %s => %s; witnesses %s >>" + " %s => %s", - re1.range, - re2.range, - result, - re1.element, - re2.element, - re1.element >> re2.element); - } else { - assert result == Range.EVERYTHING; - } - } - } - } - - @Test - public void testUnaryPlus() { - for (RangeAndElement re : rangeAndElements()) { - Range result = re.range.unaryPlus(); - assert result.contains(+re.element) - : String.format( - "Range.unaryPlus failure: %s => %s; witness %s => %s", - re.range, result, re.element, +re.element); - } - } - - @Test - public void testUnaryMinus() { - for (RangeAndElement re : rangeAndElements()) { - Range result = re.range.unaryMinus(); - assert result.contains(-re.element) - : String.format( - "Range.unaryMinus failure: %s => %s; witness %s => %s", - re.range, result, re.element, -re.element); - } - } - - @Test - public void testBitwiseComplement() { - for (RangeAndElement re : rangeAndElements()) { - Range result = re.range.bitwiseComplement(); - assert result.contains(~re.element) - : String.format( - "Range.bitwiseComplement failure: %s => %s; witness %s => %s", - re.range, result, re.element, ~re.element); - } - } - - @Test - public void testBitwiseAnd() { - for (RangeAndElement re1 : rangeAndElements()) { - for (RangeAndElement re2 : rangeAndElements()) { - Range result1 = re1.range.bitwiseAnd(re2.range); - Range result2 = re2.range.bitwiseAnd(re1.range); - if (re1.range.isConstant() || re2.range.isConstant()) { - Long witness = re1.element & re2.element; - assert result1.from == result2.from; - assert result1.to == result2.to; - assert result1.contains(witness) - : String.format( - "Range.bitwiseAnd failure: %s %s => %s; witnesses %s & %s =>" + " %s", - re1.range, re2.range, result1, re1.element, re2.element, witness); - } else { - assert result1 == Range.EVERYTHING; - assert result2 == Range.EVERYTHING; - } - } - } - } - - @Test - public void testLessThan() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineLessThan(range2); - assert (value >= range2.to - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineLessThan failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testLessThanEq() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineLessThanEq(range2); - assert (value > range2.to - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineLessThanEq failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testGreaterThan() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineGreaterThan(range2); - assert (value <= range2.from - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineGreaterThan failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testGreaterThanEq() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineGreaterThanEq(range2); - assert (value < range2.from - ? !result.contains(value) - : range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineGreaterThanEq failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testEqualTo() { - for (Range range1 : ranges) { - for (Range range2 : ranges) { - for (long value : values) { - Range result = range1.refineEqualTo(range2); - assert (value < range2.from || value > range2.to) - ? !result.contains(value) - : (range1.contains(value) == result.contains(value)) - : String.format( - "Range.refineEqualTo failure: %s %s %s; witness = %s", - range1, range2, result, value); - } - } - } - } - - @Test - public void testFactoryLongLong() { - Assert.assertEquals((long) 1, Range.create(1, 2).from); - Assert.assertEquals((long) 2, Range.create(1, 2).to); - } - - @Test - public void testFactoryList() { - Assert.assertEquals((long) 1, Range.create(Arrays.asList(1, 2, 3)).from); - Assert.assertEquals((long) 3, Range.create(Arrays.asList(1, 2, 3)).to); - Assert.assertEquals((long) 1, Range.create(Arrays.asList(3, 2, 1)).from); - Assert.assertEquals((long) 3, Range.create(Arrays.asList(3, 2, 1)).to); - Assert.assertEquals(Range.NOTHING, Range.create(Collections.emptyList())); - Assert.assertTrue(Range.NOTHING == Range.create(Collections.emptyList())); - } - - @Test - public void testFactoryTypeKind() { - Assert.assertEquals(Range.BYTE_EVERYTHING, Range.create(TypeKind.BYTE)); - Assert.assertEquals(Range.INT_EVERYTHING, Range.create(TypeKind.INT)); - Assert.assertEquals(Range.SHORT_EVERYTHING, Range.create(TypeKind.SHORT)); - Assert.assertEquals(Range.CHAR_EVERYTHING, Range.create(TypeKind.CHAR)); - Assert.assertEquals(Range.LONG_EVERYTHING, Range.create(TypeKind.LONG)); - } - - @Test(expected = IllegalArgumentException.class) - public void testFactoryTypeKindFailure() { - Range.create(TypeKind.FLOAT); - } + // These sets of values may be excessively long; perhaps trim them down later. + + long[] rangeBounds = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1, + Integer.MIN_VALUE - 1000L, + Integer.MIN_VALUE - 10L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L, + Short.MIN_VALUE - 1000L, + Short.MIN_VALUE - 10L, + Short.MIN_VALUE, + Short.MIN_VALUE + 1L, + Character.MIN_VALUE - 1000L, + Character.MIN_VALUE - 10L, + Character.MIN_VALUE, // 0L + Character.MIN_VALUE + 1L, + Byte.MIN_VALUE - 1000L, + Byte.MIN_VALUE - 10L, + Byte.MIN_VALUE, + Byte.MIN_VALUE + 1L, + Byte.MAX_VALUE - 1L, + Byte.MAX_VALUE, + Byte.MAX_VALUE + 10L, + Byte.MAX_VALUE + 1000L, + Short.MAX_VALUE - 1L, + Short.MAX_VALUE, + Short.MAX_VALUE + 10L, + Short.MAX_VALUE + 1000L, + Character.MAX_VALUE - 1L, + Character.MAX_VALUE, + Character.MAX_VALUE + 10L, + Character.MAX_VALUE + 1000L, + Integer.MAX_VALUE - 1, + Integer.MAX_VALUE, + Integer.MAX_VALUE + 10L, + Integer.MAX_VALUE + 1000L, + Long.MAX_VALUE - 1, + Long.MAX_VALUE + }; + long[] values = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L, + Short.MIN_VALUE, + Short.MIN_VALUE + 1L, + Byte.MIN_VALUE, + Byte.MIN_VALUE + 1L, + -8L, + -4L, + -2L, + -1L, + Character.MIN_VALUE, // 0L + Character.MIN_VALUE + 1L, // 1L + 2L, + 4L, + 8L, + Byte.MAX_VALUE - 1L, + Byte.MAX_VALUE, + Short.MAX_VALUE - 1L, + Short.MAX_VALUE, + Character.MAX_VALUE - 1L, + Character.MAX_VALUE, + Integer.MAX_VALUE - 1L, + Integer.MAX_VALUE, + Long.MAX_VALUE - 1L, + Long.MAX_VALUE + }; + + /** Contains a Range for every combination of values in rangeBounds. */ + Range[] ranges; + + static final long INT_WIDTH = (long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE + 1; + + static final long SHORT_WIDTH = Short.MAX_VALUE - Short.MIN_VALUE + 1; + + static final long BYTE_WIDTH = Byte.MAX_VALUE - Byte.MIN_VALUE + 1; + + static final long CHAR_WIDTH = Character.MAX_VALUE - Character.MIN_VALUE + 1; + + public RangeTest() { + // Initialize the ranges list to every combination of values in rangeBounds. + List rangesList = new ArrayList<>(); + for (long lowerbound : rangeBounds) { + for (long upperbound : rangeBounds) { + if (lowerbound <= upperbound) { + rangesList.add(Range.create(lowerbound, upperbound)); + } + } + } + ranges = rangesList.toArray(new Range[0]); + } + + /** The element is a member of the range. */ + static class RangeAndElement { + Range range; + long element; + + RangeAndElement(Range range, long element) { + if (!range.contains(element)) { + throw new IllegalArgumentException(); + } + this.range = range; + this.element = element; + } + } + + static class RangeAndTwoElements { + Range range; + long a; + long b; + } + + ValuesInRangeIterator valuesInRange(Range r) { + return new ValuesInRangeIterator(r); + } + + RangeAndElementIterator rangeAndElements() { + return new RangeAndElementIterator(); + } + + @SuppressWarnings("IterableAndIterator") // TODO + class RangeAndElementIterator implements Iterator, Iterable { + // This is the index of the range that is currently being examined. + // It is in [0..ranges.length]. + int ri; + Range range; + ValuesInRangeIterator vi; + RangeAndElement nextValue; + boolean nextValueValid = false; + + public RangeAndElementIterator() { + ri = 0; + range = ranges[ri]; + vi = new ValuesInRangeIterator(range); + } + + @Override + public RangeAndElement next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + nextValueValid = false; + return nextValue; + } + + @Override + public boolean hasNext() { + if (nextValueValid) { + return true; + } + while (!vi.hasNext()) { + ri++; + if (ri == ranges.length) { + return false; + } + range = ranges[ri]; + vi = new ValuesInRangeIterator(range); + } + nextValue = new RangeAndElement(range, vi.next()); + nextValueValid = true; + return true; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return this; + } + } + + @SuppressWarnings("IterableAndIterator") // TODO + class ValuesInRangeIterator implements Iterator, Iterable { + + Range range; + // This is the first index that has NOT been examined. It is in [0..values.length]. + int i = 0; + long nextValue; + boolean nextValueValid = false; + + @SuppressWarnings("StaticAssignmentInConstructor") + public ValuesInRangeIterator(Range range) { + this.range = range; + Range.ignoreOverflow = false; + } + + @Override + public Long next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + nextValueValid = false; + return nextValue; + } + + @Override + public boolean hasNext() { + if (nextValueValid) { + return true; + } + while (i < values.length) { + nextValue = values[i]; + i++; + if (range.contains(nextValue)) { + nextValueValid = true; + break; + } + } + return nextValueValid; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return this; + } + } + + @Test + public void testIntRange() { + for (Range range : ranges) { + Range result = range.intRange(); + for (long value : values) { + if (value < range.from + INT_WIDTH + && value > range.to - INT_WIDTH + && (Math.abs(range.from) - 1) / Integer.MIN_VALUE + == (Math.abs(range.to) - 1) / Integer.MIN_VALUE) { + // filter out test data that would cause Range.intRange to return INT_EVERYTHING + int intValue = (int) value; + assert (range.contains(value) && result.contains(intValue)) + || (!range.contains(value) && !result.contains(intValue)) + : String.format( + "Range.intRange failure: %s => %s; witness = %s", + range, result, intValue); + } + } + } + } + + @Test + public void testShortRange() { + for (Range range : ranges) { + Range result = range.shortRange(); + for (long value : values) { + if (value < range.from + SHORT_WIDTH + && value > range.to - SHORT_WIDTH + && (Math.abs(range.from) - 1) / Short.MIN_VALUE + == (Math.abs(range.to) - 1) / Short.MIN_VALUE) { + // filter out test data that would cause Range.shortRange to return + // SHORT_EVERYTHING + short shortValue = (short) value; + assert (range.contains(value) && result.contains(shortValue)) + || (!range.contains(value) && !result.contains(shortValue)) + : String.format( + "Range.shortRange failure: %s => %s; witness = %s", + range, result, shortValue); + } + } + } + } + + @Test + public void testCharRange() { + Range.ignoreOverflow = false; + for (Range range : ranges) { + Range result = range.charRange(); + for (long value : values) { + if (value < range.from + CHAR_WIDTH + && value > range.to - CHAR_WIDTH + && (Math.abs(range.from + Short.MIN_VALUE) - 1) / Short.MIN_VALUE + == (Math.abs(range.to + Short.MIN_VALUE) - 1) / Short.MIN_VALUE) { + // filter out test data that would cause Range.CharRange to return + // CHAR_EVERYTHING + // char range interval is a right shift of the short range interval + char charValue = (char) value; + assert (range.contains(value) && result.contains(charValue)) + || (!range.contains(value) && !result.contains(charValue)) + : String.format( + "Range.byteRange failure: %s => %s; witness = %s", + range, result, charValue); + } + } + } + } + + @Test + public void testByteRange() { + for (Range range : ranges) { + Range result = range.byteRange(); + for (long value : values) { + if (value < range.from + BYTE_WIDTH + && value > range.to - BYTE_WIDTH + && (Math.abs(range.from) - 1) / Byte.MIN_VALUE + == (Math.abs(range.to) - 1) / Byte.MIN_VALUE) { + // filter out test data that would cause Range.ByteRange to return + // BYTE_EVERYTHING + byte byteValue = (byte) value; + assert (range.contains(value) && result.contains(byteValue)) + || (!range.contains(value) && !result.contains(byteValue)) + : String.format( + "Range.byteRange failure: %s => %s; witness = %s", + range, result, byteValue); + } + } + } + + Range r1 = Range.create(5, 1000); + Range r2 = Range.create(1024 + 17, 1024 + 22); + Range r3 = Range.create(5, Byte.MAX_VALUE + 2); + + Range.ignoreOverflow = true; + + assert r1.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); + assert r2.byteRange().equals(Range.create(Byte.MAX_VALUE, Byte.MAX_VALUE)); + assert r3.byteRange().equals(Range.create(5, Byte.MAX_VALUE)); + + Range.ignoreOverflow = false; + + assert r1.byteRange().equals(Range.BYTE_EVERYTHING); + assert r2.byteRange().equals(Range.create(17, 22)); + assert r3.byteRange().equals(Range.BYTE_EVERYTHING); + } + + @Test + public void testUnion() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + Range result = range1.union(range2); + for (long value : values) { + if (range1.contains(value) || range2.contains(value)) { + assert result.contains(value) + : String.format( + "Range.union failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + } + + @Test + public void testIntersect() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + Range result = range1.intersect(range2); + for (long value : values) { + assert ((range1.contains(value) && range2.contains(value)) + == result.contains(value)) + : String.format( + "Range.intersect failure: %s %s => %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testPlus() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.plus(re2.range); + assert result.contains(re1.element + re2.element) + : String.format( + "Range.plus failure: %s %s => %s; witnesses %s + %s => %s", + re1.range, + re2.range, + result, + re1.element, + re2.element, + re1.element + re2.element); + } + } + } + + @Test + public void testMinus() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.minus(re2.range); + assert result.contains(re1.element - re2.element) + : String.format( + "Range.minus failure: %s %s => %s; witnesses %s - %s => %s", + re1.range, + re2.range, + result, + re1.element, + re2.element, + re1.element - re2.element); + } + } + } + + @Test + public void testTimes() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.times(re2.range); + assert result.contains(re1.element * re2.element) + : String.format( + "Range.times failure: %s %s => %s; witnesses %s * %s => %s", + re1.range, + re2.range, + result, + re1.element, + re2.element, + re1.element * re2.element); + } + } + } + + @Test + public void testDivide() { + assert Range.create(1, 2).divide(Range.create(0, 0)) == Range.NOTHING; + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + if (re2.element == 0) { + continue; + } + Range result = re1.range.divide(re2.range); + Long witness = re1.element / re2.element; + assert result.contains(witness) + : String.format( + "Range.divide failure: %s %s => %s; witnesses %s / %s => %s", + re1.range, re2.range, result, re1.element, re2.element, witness); + } + } + } + + @Test + public void testRemainder() { + assert Range.create(1, 2).remainder(Range.create(0, 0)) == Range.NOTHING; + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + if (re2.element == 0) { + continue; + } + Range result = re1.range.remainder(re2.range); + Long witness = re1.element % re2.element; + assert result.contains(witness) + : String.format( + "Range.remainder failure: %s %s => %s; witnesses %s %% %s => %s", + re1.range, re2.range, result, re1.element, re2.element, witness); + } + } + } + + @Test + public void testShiftLeft() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.shiftLeft(re2.range); + assert result.contains(re1.element << re2.element) + : String.format( + "Range.shiftLeft failure: %s %s => %s; witnesses %s << %s => %s", + re1.range, + re2.range, + result, + re1.element, + re2.element, + re1.element << re2.element); + } + } + } + + @Test + public void testSignedShiftRight() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.signedShiftRight(re2.range); + assert result.contains(re1.element >> re2.element) + : String.format( + "Range.signedShiftRight failure: %s %s => %s; witnesses %s >> %s =>" + + " %s", + re1.range, + re2.range, + result, + re1.element, + re2.element, + re1.element >> re2.element); + } + } + } + + @Test + public void testUnsignedShiftRight() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result = re1.range.unsignedShiftRight(re2.range); + if (re1.range.from >= 0) { + assert result.contains(re1.element >>> re2.element) + : String.format( + "Range.unsignedShiftRight failure: %s %s => %s; witnesses %s >>" + + " %s => %s", + re1.range, + re2.range, + result, + re1.element, + re2.element, + re1.element >> re2.element); + } else { + assert result == Range.EVERYTHING; + } + } + } + } + + @Test + public void testUnaryPlus() { + for (RangeAndElement re : rangeAndElements()) { + Range result = re.range.unaryPlus(); + assert result.contains(+re.element) + : String.format( + "Range.unaryPlus failure: %s => %s; witness %s => %s", + re.range, result, re.element, +re.element); + } + } + + @Test + public void testUnaryMinus() { + for (RangeAndElement re : rangeAndElements()) { + Range result = re.range.unaryMinus(); + assert result.contains(-re.element) + : String.format( + "Range.unaryMinus failure: %s => %s; witness %s => %s", + re.range, result, re.element, -re.element); + } + } + + @Test + public void testBitwiseComplement() { + for (RangeAndElement re : rangeAndElements()) { + Range result = re.range.bitwiseComplement(); + assert result.contains(~re.element) + : String.format( + "Range.bitwiseComplement failure: %s => %s; witness %s => %s", + re.range, result, re.element, ~re.element); + } + } + + @Test + public void testBitwiseAnd() { + for (RangeAndElement re1 : rangeAndElements()) { + for (RangeAndElement re2 : rangeAndElements()) { + Range result1 = re1.range.bitwiseAnd(re2.range); + Range result2 = re2.range.bitwiseAnd(re1.range); + if (re1.range.isConstant() || re2.range.isConstant()) { + Long witness = re1.element & re2.element; + assert result1.from == result2.from; + assert result1.to == result2.to; + assert result1.contains(witness) + : String.format( + "Range.bitwiseAnd failure: %s %s => %s; witnesses %s & %s =>" + + " %s", + re1.range, + re2.range, + result1, + re1.element, + re2.element, + witness); + } else { + assert result1 == Range.EVERYTHING; + assert result2 == Range.EVERYTHING; + } + } + } + } + + @Test + public void testLessThan() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineLessThan(range2); + assert (value >= range2.to + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineLessThan failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testLessThanEq() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineLessThanEq(range2); + assert (value > range2.to + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineLessThanEq failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testGreaterThan() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineGreaterThan(range2); + assert (value <= range2.from + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineGreaterThan failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testGreaterThanEq() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineGreaterThanEq(range2); + assert (value < range2.from + ? !result.contains(value) + : range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineGreaterThanEq failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testEqualTo() { + for (Range range1 : ranges) { + for (Range range2 : ranges) { + for (long value : values) { + Range result = range1.refineEqualTo(range2); + assert (value < range2.from || value > range2.to) + ? !result.contains(value) + : (range1.contains(value) == result.contains(value)) + : String.format( + "Range.refineEqualTo failure: %s %s %s; witness = %s", + range1, range2, result, value); + } + } + } + } + + @Test + public void testFactoryLongLong() { + Assert.assertEquals((long) 1, Range.create(1, 2).from); + Assert.assertEquals((long) 2, Range.create(1, 2).to); + } + + @Test + public void testFactoryList() { + Assert.assertEquals((long) 1, Range.create(Arrays.asList(1, 2, 3)).from); + Assert.assertEquals((long) 3, Range.create(Arrays.asList(1, 2, 3)).to); + Assert.assertEquals((long) 1, Range.create(Arrays.asList(3, 2, 1)).from); + Assert.assertEquals((long) 3, Range.create(Arrays.asList(3, 2, 1)).to); + Assert.assertEquals(Range.NOTHING, Range.create(Collections.emptyList())); + Assert.assertTrue(Range.NOTHING == Range.create(Collections.emptyList())); + } + + @Test + public void testFactoryTypeKind() { + Assert.assertEquals(Range.BYTE_EVERYTHING, Range.create(TypeKind.BYTE)); + Assert.assertEquals(Range.INT_EVERYTHING, Range.create(TypeKind.INT)); + Assert.assertEquals(Range.SHORT_EVERYTHING, Range.create(TypeKind.SHORT)); + Assert.assertEquals(Range.CHAR_EVERYTHING, Range.create(TypeKind.CHAR)); + Assert.assertEquals(Range.LONG_EVERYTHING, Range.create(TypeKind.LONG)); + } + + @Test(expected = IllegalArgumentException.class) + public void testFactoryTypeKindFailure() { + Range.create(TypeKind.FLOAT); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java index 7ab27b0b972..ec2ac305b21 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReflectionTest.java @@ -1,42 +1,43 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.ArrayList; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.reflection.ReflectionTestChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + /** Tests the reflection resolution using a simple type system. */ public class ReflectionTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReflectionTest(List testFiles) { - super( - testFiles, - ReflectionTestChecker.class, - "reflection", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReflectionTest(List testFiles) { + super( + testFiles, + ReflectionTestChecker.class, + "reflection", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"reflection"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"reflection"}; + } - @Override - public List customizeOptions(List previousOptions) { - List optionsWithStub = new ArrayList<>(checkerOptions); - optionsWithStub.add("-Astubs=" + getFullPath(testFiles.get(0), "reflection.astub")); - optionsWithStub.add("-AresolveReflection"); - return optionsWithStub; - } + @Override + public List customizeOptions(List previousOptions) { + List optionsWithStub = new ArrayList<>(checkerOptions); + optionsWithStub.add("-Astubs=" + getFullPath(testFiles.get(0), "reflection.astub")); + optionsWithStub.add("-AresolveReflection"); + return optionsWithStub; + } - protected String getFullPath(File javaFile, String filename) { - String dirname = javaFile.getParentFile().getAbsolutePath(); - return dirname + File.separator + filename; - } + protected String getFullPath(File javaFile, String filename) { + String dirname = javaFile.getParentFile().getAbsolutePath(); + return dirname + File.separator + filename; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java index d8d95d617d2..86b6cb24063 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportModifiersTest.java @@ -1,25 +1,26 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class ReportModifiersTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReportModifiersTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.util.report.ReportChecker.class, - "report", - "-AreportModifiers=native"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReportModifiersTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.util.report.ReportChecker.class, + "report", + "-AreportModifiers=native"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"reportmodifiers"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"reportmodifiers"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java index deffc3bad2a..a8d88352186 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTest.java @@ -1,25 +1,26 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class ReportTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReportTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.util.report.ReportChecker.class, - "report", - "-Astubs=tests/report/reporttest.astub"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReportTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.util.report.ReportChecker.class, + "report", + "-Astubs=tests/report/reporttest.astub"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"report"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"report"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java index 10fdf5f69bb..c4ac4a353db 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReportTreeKindsTest.java @@ -1,25 +1,26 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class ReportTreeKindsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ReportTreeKindsTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.util.report.ReportChecker.class, - "report", - "-AreportTreeKinds=WHILE_LOOP,CONDITIONAL_AND"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ReportTreeKindsTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.util.report.ReportChecker.class, + "report", + "-AreportTreeKinds=WHILE_LOOP,CONDITIONAL_AND"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"reporttreekinds"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"reporttreekinds"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java index 8588b4901f4..67d60380a1a 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverAutoValueTest.java @@ -1,33 +1,35 @@ package org.checkerframework.framework.test.junit; import com.google.common.collect.ImmutableList; -import java.io.File; -import java.util.Collections; -import java.util.List; + import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.Collections; +import java.util.List; + /** tests the returns receiver checker's AutoValue integration. */ public class ReturnsReceiverAutoValueTest extends CheckerFrameworkPerDirectoryTest { - public ReturnsReceiverAutoValueTest(List testFiles) { - super( - testFiles, - ImmutableList.of( - "com.google.auto.value.extension.memoized.processor.MemoizedValidator", - "com.google.auto.value.processor.AutoAnnotationProcessor", - "com.google.auto.value.processor.AutoOneOfProcessor", - "com.google.auto.value.processor.AutoValueBuilderProcessor", - "com.google.auto.value.processor.AutoValueProcessor", - ReturnsReceiverChecker.class.getName()), - "basic", - Collections.emptyList(), - "-nowarn"); - } + public ReturnsReceiverAutoValueTest(List testFiles) { + super( + testFiles, + ImmutableList.of( + "com.google.auto.value.extension.memoized.processor.MemoizedValidator", + "com.google.auto.value.processor.AutoAnnotationProcessor", + "com.google.auto.value.processor.AutoOneOfProcessor", + "com.google.auto.value.processor.AutoValueBuilderProcessor", + "com.google.auto.value.processor.AutoValueProcessor", + ReturnsReceiverChecker.class.getName()), + "basic", + Collections.emptyList(), + "-nowarn"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"returnsreceiverautovalue"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"returnsreceiverautovalue"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java index da97b4b45f9..d90e07f4360 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverLombokTest.java @@ -1,37 +1,38 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests the returns receiver checker's lombok integration, the test files in * tests/returnsreceiverlombok package will be delomboked into tests/returnsreceiverdelomboked * package before running the test and the returns receiver checker will run on the generated codes. */ public class ReturnsReceiverLombokTest extends CheckerFrameworkPerDirectoryTest { - public ReturnsReceiverLombokTest(List testFiles) { - super( - testFiles, - ReturnsReceiverChecker.class, - "returnsreceiverdelomboked", - "-nowarn", - "-AsuppressWarnings=type.anno.before.modifier"); - } + public ReturnsReceiverLombokTest(List testFiles) { + super( + testFiles, + ReturnsReceiverChecker.class, + "returnsreceiverdelomboked", + "-nowarn", + "-AsuppressWarnings=type.anno.before.modifier"); + } - @Override - public void run() { - // Only run if delomboked codes have been created. - if (!new File("tests/returnsreceiverdelomboked/").exists()) { - throw new RuntimeException("delombok task must be run before this test."); + @Override + public void run() { + // Only run if delomboked codes have been created. + if (!new File("tests/returnsreceiverdelomboked/").exists()) { + throw new RuntimeException("delombok task must be run before this test."); + } + super.run(); } - super.run(); - } - @Parameters - public static String[] getTestDirs() { - return new String[] {"returnsreceiverdelomboked"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"returnsreceiverdelomboked"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java index 97900425455..a4526de8246 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ReturnsReceiverTest.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Test runner for tests of the Returns Receiver Checker. * @@ -13,19 +14,19 @@ * case, create a Java file in that directory. */ public class ReturnsReceiverTest extends CheckerFrameworkPerDirectoryTest { - public ReturnsReceiverTest(List testFiles) { - super( - testFiles, - ReturnsReceiverChecker.class, - "returnsreceiver", - "-Astubs=stubs/", - "-nowarn", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations"); - } + public ReturnsReceiverTest(List testFiles) { + super( + testFiles, + ReturnsReceiverChecker.class, + "returnsreceiver", + "-Astubs=stubs/", + "-nowarn", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"returnsreceiver", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"returnsreceiver", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java index 59d5f66f659..447d29c5e7d 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingEncryptedTest.java @@ -1,27 +1,28 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.util.Encrypted; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Test suite for the Subtyping Checker, using a simple {@link Encrypted} annotation. */ public class SubtypingEncryptedTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public SubtypingEncryptedTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.subtyping.SubtypingChecker.class, - "subtyping", - "-Aquals=org.checkerframework.framework.testchecker.util.Encrypted,org.checkerframework.framework.testchecker.util.PolyEncrypted,org.checkerframework.common.subtyping.qual.Unqualified"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public SubtypingEncryptedTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.subtyping.SubtypingChecker.class, + "subtyping", + "-Aquals=org.checkerframework.framework.testchecker.util.Encrypted,org.checkerframework.framework.testchecker.util.PolyEncrypted,org.checkerframework.common.subtyping.qual.Unqualified"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"subtyping", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"subtyping", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java index b05db500b65..67c5e36311e 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SubtypingStringPatternsFullTest.java @@ -1,26 +1,27 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Test suite for the Subtyping Checker, using {@code @QualifierForLiterals}. */ public class SubtypingStringPatternsFullTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public SubtypingStringPatternsFullTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.subtyping.SubtypingChecker.class, - "stringpatterns/stringpatterns-full", - "-Aquals=org.checkerframework.framework.testchecker.util.PatternUnknown,org.checkerframework.framework.testchecker.util.PatternAB,org.checkerframework.framework.testchecker.util.PatternBC,org.checkerframework.framework.testchecker.util.PatternAC,org.checkerframework.framework.testchecker.util.PatternA,org.checkerframework.framework.testchecker.util.PatternB,org.checkerframework.framework.testchecker.util.PatternC,org.checkerframework.framework.testchecker.util.PatternBottomFull"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public SubtypingStringPatternsFullTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.subtyping.SubtypingChecker.class, + "stringpatterns/stringpatterns-full", + "-Aquals=org.checkerframework.framework.testchecker.util.PatternUnknown,org.checkerframework.framework.testchecker.util.PatternAB,org.checkerframework.framework.testchecker.util.PatternBC,org.checkerframework.framework.testchecker.util.PatternAC,org.checkerframework.framework.testchecker.util.PatternA,org.checkerframework.framework.testchecker.util.PatternB,org.checkerframework.framework.testchecker.util.PatternC,org.checkerframework.framework.testchecker.util.PatternBottomFull"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"stringpatterns/stringpatterns-full", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"stringpatterns/stringpatterns-full", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java index 8a7c60a6e0a..d068dc54546 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SupportedQualsTest.java @@ -1,22 +1,23 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.supportedquals.SupportedQualsChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class SupportedQualsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public SupportedQualsTest(List testFiles) { - super(testFiles, SupportedQualsChecker.class, "simple"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public SupportedQualsTest(List testFiles) { + super(testFiles, SupportedQualsChecker.class, "simple"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"simple"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"simple"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java index 6d30c363b00..d0a03d5be2a 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TreeParserTest.java @@ -8,87 +8,90 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; -import javax.annotation.processing.ProcessingEnvironment; + import org.checkerframework.javacutil.trees.TreeParser; import org.junit.Assert; import org.junit.Test; +import javax.annotation.processing.ProcessingEnvironment; + public class TreeParserTest { - private final ProcessingEnvironment env; - private final TreeParser parser; - - public TreeParserTest() { - env = JavacProcessingEnvironment.instance(new Context()); - parser = new TreeParser(env); - } - - @Test - public void parsesIdentifiers() { - String value = "id"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof IdentifierTree); - } - - @Test - public void parsesNumbers() { - String value = "23"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof LiteralTree); - } - - @Test - public void parsesMethodInvocations() { - String value = "test()"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof MethodInvocationTree); - MethodInvocationTree invocation = (MethodInvocationTree) parsed; - Assert.assertTrue(invocation.getMethodSelect() instanceof IdentifierTree); - Assert.assertEquals( - "test", ((IdentifierTree) invocation.getMethodSelect()).getName().toString()); - } - - @Test - public void parsesMethodInvocationsWithSelect() { - String value = "Class.test()"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof MethodInvocationTree); - MethodInvocationTree invocation = (MethodInvocationTree) parsed; - Assert.assertTrue(invocation.getMethodSelect() instanceof MemberSelectTree); - MemberSelectTree select = (MemberSelectTree) invocation.getMethodSelect(); - Assert.assertEquals("test", select.getIdentifier().toString()); - Assert.assertEquals("Class", select.getExpression().toString()); - } - - @Test - public void parsesIndex() { - String value = "array[2]"; - ExpressionTree parsed = parser.parseTree(value); - - Assert.assertTrue(parsed instanceof ArrayAccessTree); - ArrayAccessTree access = (ArrayAccessTree) parsed; - - Assert.assertEquals(2, ((LiteralTree) access.getIndex()).getValue()); - Assert.assertEquals("array", ((IdentifierTree) access.getExpression()).getName().toString()); - } - - @Test - public void randomParses() { - ExpressionTree parsed = parser.parseTree("Class.method()[4].field[3]"); - - Assert.assertTrue(parsed instanceof ArrayAccessTree); - MemberSelectTree array = (MemberSelectTree) ((ArrayAccessTree) parsed).getExpression(); - Assert.assertEquals("field", array.getIdentifier().toString()); - Assert.assertTrue(array.getExpression() instanceof ArrayAccessTree); - } - - @Test - public void parsesMethodArguments() { - parser.parseTree("method()"); - parser.parseTree("method(1)"); - parser.parseTree("method(1,2)"); - } + private final ProcessingEnvironment env; + private final TreeParser parser; + + public TreeParserTest() { + env = JavacProcessingEnvironment.instance(new Context()); + parser = new TreeParser(env); + } + + @Test + public void parsesIdentifiers() { + String value = "id"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof IdentifierTree); + } + + @Test + public void parsesNumbers() { + String value = "23"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof LiteralTree); + } + + @Test + public void parsesMethodInvocations() { + String value = "test()"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof MethodInvocationTree); + MethodInvocationTree invocation = (MethodInvocationTree) parsed; + Assert.assertTrue(invocation.getMethodSelect() instanceof IdentifierTree); + Assert.assertEquals( + "test", ((IdentifierTree) invocation.getMethodSelect()).getName().toString()); + } + + @Test + public void parsesMethodInvocationsWithSelect() { + String value = "Class.test()"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof MethodInvocationTree); + MethodInvocationTree invocation = (MethodInvocationTree) parsed; + Assert.assertTrue(invocation.getMethodSelect() instanceof MemberSelectTree); + MemberSelectTree select = (MemberSelectTree) invocation.getMethodSelect(); + Assert.assertEquals("test", select.getIdentifier().toString()); + Assert.assertEquals("Class", select.getExpression().toString()); + } + + @Test + public void parsesIndex() { + String value = "array[2]"; + ExpressionTree parsed = parser.parseTree(value); + + Assert.assertTrue(parsed instanceof ArrayAccessTree); + ArrayAccessTree access = (ArrayAccessTree) parsed; + + Assert.assertEquals(2, ((LiteralTree) access.getIndex()).getValue()); + Assert.assertEquals( + "array", ((IdentifierTree) access.getExpression()).getName().toString()); + } + + @Test + public void randomParses() { + ExpressionTree parsed = parser.parseTree("Class.method()[4].field[3]"); + + Assert.assertTrue(parsed instanceof ArrayAccessTree); + MemberSelectTree array = (MemberSelectTree) ((ArrayAccessTree) parsed).getExpression(); + Assert.assertEquals("field", array.getIdentifier().toString()); + Assert.assertTrue(array.getExpression() instanceof ArrayAccessTree); + } + + @Test + public void parsesMethodArguments() { + parser.parseTree("method()"); + parser.parseTree("method(1)"); + parser.parseTree("method(1,2)"); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java index 525f41c3a07..bf9d7835d0f 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclBoundsTest.java @@ -1,19 +1,20 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.typedeclbounds.TypeDeclBoundsChecker; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + public class TypeDeclBoundsTest extends CheckerFrameworkPerDirectoryTest { - public TypeDeclBoundsTest(List testFiles) { - super(testFiles, TypeDeclBoundsChecker.class, "typedeclbounds"); - } + public TypeDeclBoundsTest(List testFiles) { + super(testFiles, TypeDeclBoundsChecker.class, "typedeclbounds"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"typedeclbounds"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"typedeclbounds"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java index 1cb24876638..262aea10f9a 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/TypeDeclDefaultTest.java @@ -1,27 +1,28 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.typedecldefault.TypeDeclDefaultChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Create the TypeDeclDefault test. */ public class TypeDeclDefaultTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public TypeDeclDefaultTest(List testFiles) { - super( - testFiles, - TypeDeclDefaultChecker.class, - "typedecldefault", - "-Astubs=tests/typedecldefault/jdk.astub"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public TypeDeclDefaultTest(List testFiles) { + super( + testFiles, + TypeDeclDefaultChecker.class, + "typedecldefault", + "-Astubs=tests/typedecldefault/jdk.astub"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"typedecldefault"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"typedecldefault"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java index 72990bd1496..afd06a04138 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueIgnoreRangeOverflowTest.java @@ -1,30 +1,31 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests the constant value propagation type system without overflow. */ public class ValueIgnoreRangeOverflowTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueIgnoreRangeOverflowTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-A" + ValueChecker.REPORT_EVAL_WARNS, - "-A" + ValueChecker.IGNORE_RANGE_OVERFLOW); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueIgnoreRangeOverflowTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-A" + ValueChecker.REPORT_EVAL_WARNS, + "-A" + ValueChecker.IGNORE_RANGE_OVERFLOW); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"value", "all-systems", "value-ignore-range-overflow"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"value", "all-systems", "value-ignore-range-overflow"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java index 9d3e1c8298d..f6d75d9e364 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueNonNullStringsConcatenationTest.java @@ -1,29 +1,30 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + public class ValueNonNullStringsConcatenationTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueNonNullStringsConcatenationTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value-non-null-strings-concatenation", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-A" + ValueChecker.REPORT_EVAL_WARNS, - "-A" + ValueChecker.NON_NULL_STRINGS_CONCATENATION); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueNonNullStringsConcatenationTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value-non-null-strings-concatenation", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-A" + ValueChecker.REPORT_EVAL_WARNS, + "-A" + ValueChecker.NON_NULL_STRINGS_CONCATENATION); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"all-systems", "value-non-null-strings-concatenation"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"all-systems", "value-non-null-strings-concatenation"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java index e9c1a5b18b2..01579f71a5e 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.test.TestUtilities; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** * Tests the constant value propagation type system. * @@ -16,22 +17,23 @@ */ public class ValueTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueTest(List testFiles) { - super( - testFiles, - org.checkerframework.common.value.ValueChecker.class, - "value", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - TestUtilities.adapt("-Astubs=tests/value/minints-stub.astub:tests/value/lowercase.astub"), - "-A" + ValueChecker.REPORT_EVAL_WARNS); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + TestUtilities.adapt( + "-Astubs=tests/value/minints-stub.astub:tests/value/lowercase.astub"), + "-A" + ValueChecker.REPORT_EVAL_WARNS); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"value", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"value", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java index e8bd5912e0c..d33bdf51dd8 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueUncheckedDefaultsTest.java @@ -1,32 +1,33 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Tests conservative defaults for the constant value propagation type system. */ public class ValueUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ValueUncheckedDefaultsTest(List testFiles) { - super( - testFiles, - ValueChecker.class, - "value", - // Ignore the test suite's usage of qualifiers in illegal locations. - "-AignoreTargetLocations", - "-AuseConservativeDefaultsForUncheckedCode=btyecode", - "-A" + ValueChecker.REPORT_EVAL_WARNS); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ValueUncheckedDefaultsTest(List testFiles) { + super( + testFiles, + ValueChecker.class, + "value", + // Ignore the test suite's usage of qualifiers in illegal locations. + "-AignoreTargetLocations", + "-AuseConservativeDefaultsForUncheckedCode=btyecode", + "-A" + ValueChecker.REPORT_EVAL_WARNS); + } - @Parameters - public static String[] getTestDirs() { - // The defaults for unchecked code should be the same as checked code, so use the same - // tests. - return new String[] {"value", "all-systems"}; - } + @Parameters + public static String[] getTestDirs() { + // The defaults for unchecked code should be the same as checked code, so use the same + // tests. + return new String[] {"value", "all-systems"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java index ca2d9645fd0..a3e6fb05b77 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/VariableNameDefaultTest.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.checkerframework.framework.testchecker.variablenamedefault.VariableNameDefaultChecker; import org.junit.runners.Parameterized.Parameters; +import java.io.File; +import java.util.List; + /** Create the VariableNameDefault test. */ public class VariableNameDefaultTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public VariableNameDefaultTest(List testFiles) { - super(testFiles, VariableNameDefaultChecker.class, "variablenamedefault"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public VariableNameDefaultTest(List testFiles) { + super(testFiles, VariableNameDefaultChecker.class, "variablenamedefault"); + } - @Parameters - public static String[] getTestDirs() { - return new String[] {"variablenamedefault"}; - } + @Parameters + public static String[] getTestDirs() { + return new String[] {"variablenamedefault"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java index 241f1db806e..81479836a19 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java @@ -1,21 +1,22 @@ package org.checkerframework.framework.test.junit; -import java.io.File; -import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; +import java.io.File; +import java.util.List; + public class ViewpointTestCheckerTest extends CheckerFrameworkPerDirectoryTest { - /** - * @param testFiles the files containing test code, which will be type-checked - */ - public ViewpointTestCheckerTest(List testFiles) { - super(testFiles, viewpointtest.ViewpointTestChecker.class, "viewpointtest"); - } + /** + * @param testFiles the files containing test code, which will be type-checked + */ + public ViewpointTestCheckerTest(List testFiles) { + super(testFiles, viewpointtest.ViewpointTestChecker.class, "viewpointtest"); + } - @Parameterized.Parameters - public static String[] getTestDirs() { - return new String[] {"viewpointtest"}; - } + @Parameterized.Parameters + public static String[] getTestDirs() { + return new String[] {"viewpointtest"}; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java index 853afd89d41..a78adc169e6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/AggregateOfCompoundChecker.java @@ -1,17 +1,18 @@ package org.checkerframework.framework.testchecker.aggregate; -import java.util.Arrays; -import java.util.Collection; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.source.AggregateChecker; import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.testchecker.compound.CompoundChecker; +import java.util.Arrays; +import java.util.Collection; + /** An aggregate checker where one of the checkers is a compound checker. */ public class AggregateOfCompoundChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - return Arrays.asList(ValueChecker.class, CompoundChecker.class); - } + @Override + protected Collection> getSupportedCheckers() { + return Arrays.asList(ValueChecker.class, CompoundChecker.class); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java index 90bea58adb0..2e159cc59a5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/aggregate/TestAggregateChecker.java @@ -1,17 +1,18 @@ package org.checkerframework.framework.testchecker.aggregate; -import java.util.Arrays; -import java.util.Collection; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.source.AggregateChecker; import org.checkerframework.framework.source.SourceChecker; +import java.util.Arrays; +import java.util.Collection; + /** Basic aggregate checker. */ public class TestAggregateChecker extends AggregateChecker { - @Override - protected Collection> getSupportedCheckers() { - return Arrays.asList(ValueChecker.class, AliasingChecker.class); - } + @Override + protected Collection> getSupportedCheckers() { + return Arrays.asList(ValueChecker.class, AliasingChecker.class); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java index e34bc0aeb92..6846df5df4c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundChecker.java @@ -1,33 +1,34 @@ package org.checkerframework.framework.testchecker.compound; -import java.util.LinkedHashSet; -import java.util.Set; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.value.ValueChecker; +import java.util.LinkedHashSet; +import java.util.Set; + public class AnotherCompoundChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - // Make sure that options can be accessed by sub-checkers to determine - // which subcheckers to run. - @SuppressWarnings("unused") - String option = super.getOption("nomsgtext"); - LinkedHashSet> subcheckers = new LinkedHashSet<>(); - subcheckers.addAll(super.getImmediateSubcheckerClasses()); - subcheckers.add(AliasingChecker.class); - subcheckers.add(ValueChecker.class); - return subcheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + // Make sure that options can be accessed by sub-checkers to determine + // which subcheckers to run. + @SuppressWarnings("unused") + String option = super.getOption("nomsgtext"); + LinkedHashSet> subcheckers = new LinkedHashSet<>(); + subcheckers.addAll(super.getImmediateSubcheckerClasses()); + subcheckers.add(AliasingChecker.class); + subcheckers.add(ValueChecker.class); + return subcheckers; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new BaseTypeVisitor(this) { - @Override - protected AnotherCompoundCheckerAnnotatedTypeFactory createTypeFactory() { - return new AnotherCompoundCheckerAnnotatedTypeFactory(checker); - } - }; - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new BaseTypeVisitor(this) { + @Override + protected AnotherCompoundCheckerAnnotatedTypeFactory createTypeFactory() { + return new AnotherCompoundCheckerAnnotatedTypeFactory(checker); + } + }; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java index 59b37023d5d..c79aa94af07 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/AnotherCompoundCheckerAnnotatedTypeFactory.java @@ -1,10 +1,7 @@ package org.checkerframework.framework.testchecker.compound; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; + import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -16,42 +13,48 @@ import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class AnotherCompoundCheckerAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** - * Creates a new AnotherCompoundCheckerAnnotatedTypeFactory. - * - * @param checker the checker - */ - public AnotherCompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + /** + * Creates a new AnotherCompoundCheckerAnnotatedTypeFactory. + * + * @param checker the checker + */ + public AnotherCompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>(Arrays.asList(ACCTop.class, ACCBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList(ACCTop.class, ACCBottom.class)); + } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), - new TreeAnnotator(this) { - @Override - protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { - // Just access the subchecker type factories to make - // sure they were created properly - GenericAnnotatedTypeFactory aliasingATF = - getTypeFactoryOfSubchecker(AliasingChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); - GenericAnnotatedTypeFactory valueATF = - getTypeFactoryOfSubchecker(ValueChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror value = valueATF.getAnnotatedType(tree); - return super.defaultAction(tree, p); - } - }); - } + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), + new TreeAnnotator(this) { + @Override + protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { + // Just access the subchecker type factories to make + // sure they were created properly + GenericAnnotatedTypeFactory aliasingATF = + getTypeFactoryOfSubchecker(AliasingChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); + GenericAnnotatedTypeFactory valueATF = + getTypeFactoryOfSubchecker(ValueChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror value = valueATF.getAnnotatedType(tree); + return super.defaultAction(tree, p); + } + }); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java index d617ea0a102..4c7d6ab9e68 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundChecker.java @@ -1,33 +1,34 @@ package org.checkerframework.framework.testchecker.compound; -import java.util.LinkedHashSet; -import java.util.Set; import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; +import java.util.LinkedHashSet; +import java.util.Set; + /** * Used to test the compound checker design pattern. AliasingChecker and AnotherCompoundChecker are * subcheckers of this checker AnotherCompoundChecker relies on the Aliasing Checker, too. This is * so that the order of subcheckers is tested. */ public class CompoundChecker extends BaseTypeChecker { - @Override - protected Set> getImmediateSubcheckerClasses() { - LinkedHashSet> subcheckers = new LinkedHashSet<>(); - subcheckers.addAll(super.getImmediateSubcheckerClasses()); - subcheckers.add(AliasingChecker.class); - subcheckers.add(AnotherCompoundChecker.class); - return subcheckers; - } + @Override + protected Set> getImmediateSubcheckerClasses() { + LinkedHashSet> subcheckers = new LinkedHashSet<>(); + subcheckers.addAll(super.getImmediateSubcheckerClasses()); + subcheckers.add(AliasingChecker.class); + subcheckers.add(AnotherCompoundChecker.class); + return subcheckers; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new BaseTypeVisitor(this) { - @Override - protected CompoundCheckerAnnotatedTypeFactory createTypeFactory() { - return new CompoundCheckerAnnotatedTypeFactory(checker); - } - }; - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new BaseTypeVisitor(this) { + @Override + protected CompoundCheckerAnnotatedTypeFactory createTypeFactory() { + return new CompoundCheckerAnnotatedTypeFactory(checker); + } + }; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java index ece07ad4c76..be4e34717f5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/CompoundCheckerAnnotatedTypeFactory.java @@ -1,10 +1,7 @@ package org.checkerframework.framework.testchecker.compound; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; + import org.checkerframework.common.aliasing.AliasingChecker; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -16,40 +13,46 @@ import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class CompoundCheckerAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public CompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public CompoundCheckerAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>(Arrays.asList(CCTop.class, CCBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>(Arrays.asList(CCTop.class, CCBottom.class)); + } - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), - new TreeAnnotator(this) { - @Override - protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { - // Just access the subchecker type factories to make - // sure they were created properly - GenericAnnotatedTypeFactory accATF = - getTypeFactoryOfSubchecker(AnotherCompoundChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror another = accATF.getAnnotatedType(tree); - GenericAnnotatedTypeFactory aliasingATF = - getTypeFactoryOfSubchecker(AliasingChecker.class); - @SuppressWarnings("unused") - AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); - GenericAnnotatedTypeFactory valueATF = - getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); - assert valueATF == null : "Should not be able to access the ValueChecker annotations."; - return super.defaultAction(tree, p); - } - }); - } + @Override + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), + new TreeAnnotator(this) { + @Override + protected Void defaultAction(Tree tree, AnnotatedTypeMirror p) { + // Just access the subchecker type factories to make + // sure they were created properly + GenericAnnotatedTypeFactory accATF = + getTypeFactoryOfSubchecker(AnotherCompoundChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror another = accATF.getAnnotatedType(tree); + GenericAnnotatedTypeFactory aliasingATF = + getTypeFactoryOfSubchecker(AliasingChecker.class); + @SuppressWarnings("unused") + AnnotatedTypeMirror aliasing = aliasingATF.getAnnotatedType(tree); + GenericAnnotatedTypeFactory valueATF = + getTypeFactoryOfSubcheckerOrNull(ValueChecker.class); + assert valueATF == null + : "Should not be able to access the ValueChecker annotations."; + return super.defaultAction(tree, p); + } + }); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java index c2a8d1550a5..f26f4af606f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCBottom.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.compound.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({ACCTop.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java index 19500df5419..2232818be1b 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/ACCTop.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.compound.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) @DefaultQualifierInHierarchy diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java index 54467f5ebea..7567920d933 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCBottom.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.compound.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({CCTop.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java index a998fa40ac3..8a87d7f4afa 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/compound/qual/CCTop.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.compound.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) @DefaultQualifierInHierarchy diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java index d6f6ea8ac2f..41006bd2a5a 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingLowerBoundAnnotatedTypeFactory.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.testchecker.defaulting; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbBottom; @@ -11,16 +7,21 @@ import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbImplicit; import org.checkerframework.framework.testchecker.defaulting.LowerBoundQual.LbTop; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class DefaultingLowerBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public DefaultingLowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public DefaultingLowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList(LbTop.class, LbExplicit.class, LbImplicit.class, LbBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList(LbTop.class, LbExplicit.class, LbImplicit.class, LbBottom.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java index 94bec6e43eb..909997e88c5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/DefaultingUpperBoundAnnotatedTypeFactory.java @@ -1,26 +1,27 @@ package org.checkerframework.framework.testchecker.defaulting; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; + import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.common.basetype.BaseTypeChecker; public class DefaultingUpperBoundAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public DefaultingUpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public DefaultingUpperBoundAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - UpperBoundQual.UbTop.class, - UpperBoundQual.UbExplicit.class, - UpperBoundQual.UbImplicit.class, - UpperBoundQual.UbBottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + UpperBoundQual.UbTop.class, + UpperBoundQual.UbExplicit.class, + UpperBoundQual.UbImplicit.class, + UpperBoundQual.UbBottom.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java index 9bf5bb1ee27..66430d261c7 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/LowerBoundQual.java @@ -1,41 +1,42 @@ package org.checkerframework.framework.testchecker.defaulting; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; public class LowerBoundQual { - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @SubtypeOf({}) - @DefaultQualifierInHierarchy - @Documented - @Retention(RetentionPolicy.RUNTIME) - public static @interface LbTop {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @SubtypeOf({}) + @DefaultQualifierInHierarchy + @Documented + @Retention(RetentionPolicy.RUNTIME) + public static @interface LbTop {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(LbTop.class) - @DefaultFor(TypeUseLocation.IMPLICIT_LOWER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface LbImplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(LbTop.class) + @DefaultFor(TypeUseLocation.IMPLICIT_LOWER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface LbImplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(LbTop.class) - @DefaultFor(TypeUseLocation.EXPLICIT_LOWER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface LbExplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(LbTop.class) + @DefaultFor(TypeUseLocation.EXPLICIT_LOWER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface LbExplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf({LbImplicit.class, LbExplicit.class}) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface LbBottom {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf({LbImplicit.class, LbExplicit.class}) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface LbBottom {} } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java index 705d42ec0d8..130247ef0bc 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/defaulting/UpperBoundQual.java @@ -1,43 +1,44 @@ package org.checkerframework.framework.testchecker.defaulting; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** Created by jburke on 9/29/14. */ public class UpperBoundQual { - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @SubtypeOf({}) - @DefaultQualifierInHierarchy - @Documented - @Retention(RetentionPolicy.RUNTIME) - public static @interface UbTop {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @SubtypeOf({}) + @DefaultQualifierInHierarchy + @Documented + @Retention(RetentionPolicy.RUNTIME) + public static @interface UbTop {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(UbTop.class) - @DefaultFor(TypeUseLocation.IMPLICIT_UPPER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface UbImplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(UbTop.class) + @DefaultFor(TypeUseLocation.IMPLICIT_UPPER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface UbImplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf(UbTop.class) - @DefaultFor(TypeUseLocation.EXPLICIT_UPPER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface UbExplicit {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf(UbTop.class) + @DefaultFor(TypeUseLocation.EXPLICIT_UPPER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface UbExplicit {} - @Documented - @Retention(RetentionPolicy.RUNTIME) - @SubtypeOf({UbImplicit.class, UbExplicit.class}) - @DefaultFor(TypeUseLocation.LOWER_BOUND) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public static @interface UbBottom {} + @Documented + @Retention(RetentionPolicy.RUNTIME) + @SubtypeOf({UbImplicit.class, UbExplicit.class}) + @DefaultFor(TypeUseLocation.LOWER_BOUND) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public static @interface UbBottom {} } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java index b653b269e30..cb113e53b5e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/FlowExpressionAnnotatedTypeFactory.java @@ -1,11 +1,5 @@ package org.checkerframework.framework.testchecker.flowexpression; -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.flowexpression.qual.FEBottom; @@ -18,104 +12,116 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; -public class FlowExpressionAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - private AnnotationMirror TOP, BOTTOM; - - /** The FlowExp.value field/element. */ - ExecutableElement flowExpValueElement = - TreeUtils.getMethod(FlowExp.class, "value", 0, processingEnv); - - /** - * Creates a new FlowExpressionAnnotatedTypeFactory. - * - * @param checker the checker - */ - public FlowExpressionAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - TOP = AnnotationBuilder.fromClass(elements, FETop.class); - BOTTOM = AnnotationBuilder.fromClass(elements, FEBottom.class); - postInit(); - } +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; - @Override - protected DependentTypesHelper createDependentTypesHelper() { - return new DependentTypesHelper(this); - } +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; - @Override - protected FlowExpressionQualifierHierarchy createQualifierHierarchy() { - return new FlowExpressionQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } +public class FlowExpressionAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + private AnnotationMirror TOP, BOTTOM; - private class FlowExpressionQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + /** The FlowExp.value field/element. */ + ExecutableElement flowExpValueElement = + TreeUtils.getMethod(FlowExp.class, "value", 0, processingEnv); /** - * Create a {@code FlowExpressionQualifierHierarchy}. + * Creates a new FlowExpressionAnnotatedTypeFactory. * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils + * @param checker the checker */ - public FlowExpressionQualifierHierarchy( - Set> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, FlowExpressionAnnotatedTypeFactory.this); + public FlowExpressionAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + TOP = AnnotationBuilder.fromClass(elements, FETop.class); + BOTTOM = AnnotationBuilder.fromClass(elements, FEBottom.class); + postInit(); } @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - List subtypeExpressions = - AnnotationUtils.getElementValueArray(subAnno, flowExpValueElement, String.class); - List supertypeExpressions = - AnnotationUtils.getElementValueArray(superAnno, flowExpValueElement, String.class); - return subtypeExpressions.containsAll(supertypeExpressions) - && supertypeExpressions.containsAll(subtypeExpressions); + protected DependentTypesHelper createDependentTypesHelper() { + return new DependentTypesHelper(this); } @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1.getName() == FEBottom.class.getCanonicalName()) { - return a2; - } else if (qualifierKind2.getName() == FEBottom.class.getCanonicalName()) { - return a1; - } - List a1Expressions = - AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); - List a2Expressions = - AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); - if (a1Expressions.containsAll(a2Expressions) && a2Expressions.containsAll(a1Expressions)) { - return a1; - } - return TOP; + protected FlowExpressionQualifierHierarchy createQualifierHierarchy() { + return new FlowExpressionQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1.getName() == FETop.class.getCanonicalName()) { - return a2; - } else if (qualifierKind2.getName() == FETop.class.getCanonicalName()) { - return a1; - } - List a1Expressions = - AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); - List a2Expressions = - AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); - if (a1Expressions.containsAll(a2Expressions) && a2Expressions.containsAll(a1Expressions)) { - return a1; - } - return BOTTOM; + private class FlowExpressionQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + + /** + * Create a {@code FlowExpressionQualifierHierarchy}. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + */ + public FlowExpressionQualifierHierarchy( + Set> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, FlowExpressionAnnotatedTypeFactory.this); + } + + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + List subtypeExpressions = + AnnotationUtils.getElementValueArray( + subAnno, flowExpValueElement, String.class); + List supertypeExpressions = + AnnotationUtils.getElementValueArray( + superAnno, flowExpValueElement, String.class); + return subtypeExpressions.containsAll(supertypeExpressions) + && supertypeExpressions.containsAll(subtypeExpressions); + } + + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1.getName() == FEBottom.class.getCanonicalName()) { + return a2; + } else if (qualifierKind2.getName() == FEBottom.class.getCanonicalName()) { + return a1; + } + List a1Expressions = + AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); + List a2Expressions = + AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); + if (a1Expressions.containsAll(a2Expressions) + && a2Expressions.containsAll(a1Expressions)) { + return a1; + } + return TOP; + } + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1.getName() == FETop.class.getCanonicalName()) { + return a2; + } else if (qualifierKind2.getName() == FETop.class.getCanonicalName()) { + return a1; + } + List a1Expressions = + AnnotationUtils.getElementValueArray(a1, flowExpValueElement, String.class); + List a2Expressions = + AnnotationUtils.getElementValueArray(a2, flowExpValueElement, String.class); + if (a1Expressions.containsAll(a2Expressions) + && a2Expressions.containsAll(a1Expressions)) { + return a1; + } + return BOTTOM; + } } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java index ddad660ef93..bf0692f5068 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FEBottom.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.flowexpression.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf({FlowExp.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java index f272e014b5a..5c70279a7e4 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FETop.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.flowexpression.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) @DefaultQualifierInHierarchy diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java index cd2a452dc89..8302a00f7e4 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/flowexpression/qual/FlowExp.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.testchecker.flowexpression.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.JavaExpression; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({FETop.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface FlowExp { - @JavaExpression - String[] value() default {}; + @JavaExpression + String[] value() default {}; } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java index f0eac24510e..8f33b5378dd 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java @@ -2,9 +2,7 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Bot; @@ -22,44 +20,49 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationBuilder; +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; + public class H1H2AnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - AnnotationMirror H1S2; + AnnotationMirror H1S2; - /** - * Creates a new H1H2AnnotatedTypeFactory. - * - * @param checker the checker - */ - public H1H2AnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - H1S2 = AnnotationBuilder.fromClass(elements, H1S2.class); - } + /** + * Creates a new H1H2AnnotatedTypeFactory. + * + * @param checker the checker + */ + public H1H2AnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + H1S2 = AnnotationBuilder.fromClass(elements, H1S2.class); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - H1Top.class, - H1S1.class, - H1S2.class, - H1Bot.class, - H2Top.class, - H2S1.class, - H2S2.class, - H2Bot.class, - H1Poly.class, - H2Poly.class, - H2OnlyOnLB.class, - H1Invalid.class); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + H1Top.class, + H1S1.class, + H1S2.class, + H1Bot.class, + H2Top.class, + H2S1.class, + H2S2.class, + H2Bot.class, + H1Poly.class, + H2Poly.class, + H2OnlyOnLB.class, + H1Invalid.class); + } - @Override - protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - super.addComputedTypeAnnotations(tree, type); - if (tree.getKind() == Tree.Kind.VARIABLE - && ((VariableTree) tree).getName().toString().contains("addH1S2")) { - type.replaceAnnotation(H1S2); + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); + if (tree.getKind() == Tree.Kind.VARIABLE + && ((VariableTree) tree).getName().toString().contains("addH1S2")) { + type.replaceAnnotation(H1S2); + } } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java index ab88878674e..492f6dc1ae7 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2Visitor.java @@ -1,7 +1,7 @@ package org.checkerframework.framework.testchecker.h1h2checker; import com.sun.source.tree.Tree; -import javax.lang.model.element.AnnotationMirror; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -11,37 +11,41 @@ import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.javacutil.AnnotationBuilder; +import javax.lang.model.element.AnnotationMirror; + public class H1H2Visitor extends BaseTypeVisitor { - public H1H2Visitor(BaseTypeChecker checker) { - super(checker); - } + public H1H2Visitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected BaseTypeValidator createTypeValidator() { - return new H1H2TypeValidator(checker, this, atypeFactory); - } + @Override + protected BaseTypeValidator createTypeValidator() { + return new H1H2TypeValidator(checker, this, atypeFactory); + } - private final class H1H2TypeValidator extends BaseTypeValidator { + private final class H1H2TypeValidator extends BaseTypeValidator { - public H1H2TypeValidator( - BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) { - super(checker, visitor, atypeFactory); - } + public H1H2TypeValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Tree p) { - AnnotationMirror h1Invalid = AnnotationBuilder.fromClass(elements, H1Invalid.class); - if (AnnotatedTypes.containsModifier(type, h1Invalid)) { - checker.reportError( - p, - // An error specific to this type system, with no corresponding text - // in a messages.properties file; this checker is just for testing. - "h1h2checker.h1invalid.forbidden", - type.getAnnotations(), - type.toString()); - } - return super.visitDeclared(type, p); + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree p) { + AnnotationMirror h1Invalid = AnnotationBuilder.fromClass(elements, H1Invalid.class); + if (AnnotatedTypes.containsModifier(type, h1Invalid)) { + checker.reportError( + p, + // An error specific to this type system, with no corresponding text + // in a messages.properties file; this checker is just for testing. + "h1h2checker.h1invalid.forbidden", + type.getAnnotations(), + type.toString()); + } + return super.visitDeclared(type, p); + } } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java index dcbc4ede956..fd18a09c40c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Bot.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java index a2e4a34e0a2..ce0f0b60dc7 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Invalid.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java index 9f4201b9542..83d5863695d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Poly.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java index 26798d88272..8d618242ddc 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S1.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java index 8608b206d98..635de6c9ddb 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1S2.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java index 89de2b45e08..ebffe288ae2 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H1Top.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java index ed939bffc2e..296bf34280b 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Bot.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java index 3b5da4a08eb..2f6daffbf55 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2OnlyOnLB.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java index 26e3a187e95..54ac91a8e05 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Poly.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java index e7794e83ae4..29276abd5ed 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S1.java @@ -1,23 +1,24 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({ - TypeUseLocation.FIELD, - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.PARAMETER, - TypeUseLocation.RETURN, - TypeUseLocation.CONSTRUCTOR_RESULT + TypeUseLocation.FIELD, + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.PARAMETER, + TypeUseLocation.RETURN, + TypeUseLocation.CONSTRUCTOR_RESULT }) @SubtypeOf({H2Top.class}) public @interface H2S1 {} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java index 0bc2a6a7a1d..c5dbfe61308 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2S2.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java index 54edce7bdf3..fba193b0c5d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/quals/H2Top.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.h1h2checker.quals; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java index 4939d4b6bf5..1ea34c06116 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/Issue3105Fields.java @@ -3,11 +3,11 @@ package org.checkerframework.framework.testchecker.lib; public class Issue3105Fields { - public static final String FIELD1 = "foo"; + public static final String FIELD1 = "foo"; - public static final String FIELD2; + public static final String FIELD2; - static { - FIELD2 = "bar"; - } + static { + FIELD2 = "bar"; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java index 96899a15063..1e899bafa14 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/UncheckedByteCode.java @@ -1,42 +1,42 @@ package org.checkerframework.framework.testchecker.lib; public class UncheckedByteCode { - public CT classTypeVariableField; - public static Object nonFinalPublicField; + public CT classTypeVariableField; + public static Object nonFinalPublicField; - public CT getCT() { - return classTypeVariableField; - } + public CT getCT() { + return classTypeVariableField; + } - public T identity(T t) { - return t; - } + public T identity(T t) { + return t; + } - public int getInt(int i) { - return i; - } + public int getInt(int i) { + return i; + } - public Integer getInteger(Integer i) { - return i; - } + public Integer getInteger(Integer i) { + return i; + } - public String getString(CharSequence charSequence) { - return ""; - } + public String getString(CharSequence charSequence) { + return ""; + } - public I getI(I i) { - return i; - } + public I getI(I i) { + return i; + } - public Object getObject(Object o) { - return o; - } + public Object getObject(Object o) { + return o; + } - public static void unboundedWildcardParam(UncheckedByteCode param) {} + public static void unboundedWildcardParam(UncheckedByteCode param) {} - public static void upperboundedWildcardParam(UncheckedByteCode param) {} + public static void upperboundedWildcardParam(UncheckedByteCode param) {} - public static void lowerboundedWildcardParam(UncheckedByteCode param) {} + public static void lowerboundedWildcardParam(UncheckedByteCode param) {} - public static void methodWithTypeVarBoundedByNumber(F param) {} + public static void methodWithTypeVarBoundedByNumber(F param) {} } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java index 32e905ff5a9..02b94e604fe 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lib/VarArgMethods.java @@ -4,30 +4,30 @@ /** Used by framework/tests/value/VarArgRe.java */ public class VarArgMethods { - @StaticallyExecutable - public static int test0(Object... objects) { - if (objects == null) { - return -1; - } else { - return objects.length; + @StaticallyExecutable + public static int test0(Object... objects) { + if (objects == null) { + return -1; + } else { + return objects.length; + } } - } - @StaticallyExecutable - public static int test1(String s, Object... objects) { - if (objects == null) { - return -1; - } else { - return objects.length; + @StaticallyExecutable + public static int test1(String s, Object... objects) { + if (objects == null) { + return -1; + } else { + return objects.length; + } } - } - @StaticallyExecutable - public static int test2(String s, String s2, Object... objects) { - if (objects == null) { - return -1; - } else { - return objects.length; + @StaticallyExecutable + public static int test2(String s, String s2, Object... objects) { + if (objects == null) { + return -1; + } else { + return objects.length; + } } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java index 74e6f82c6ab..f400cecbc0d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbAnnotatedTypeFactory.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.testchecker.lubglb; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.lubglb.quals.LubglbA; @@ -14,23 +10,28 @@ import org.checkerframework.framework.testchecker.lubglb.quals.LubglbF; import org.checkerframework.framework.testchecker.lubglb.quals.PolyLubglb; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class LubGlbAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public LubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public LubGlbAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - LubglbA.class, - LubglbB.class, - LubglbC.class, - LubglbD.class, - LubglbE.class, - LubglbF.class, - PolyLubglb.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + LubglbA.class, + LubglbB.class, + LubglbC.class, + LubglbD.class, + LubglbE.class, + LubglbF.class, + PolyLubglb.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java index 474e94cc3cd..0531afc6dcf 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/LubGlbChecker.java @@ -1,7 +1,5 @@ package org.checkerframework.framework.testchecker.lubglb; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.testchecker.lubglb.quals.LubglbA; @@ -15,6 +13,9 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + // Type hierarchy: // A <-- @DefaultQualifierInHierarchy // / \ @@ -26,68 +27,74 @@ public class LubGlbChecker extends BaseTypeChecker { - private AnnotationMirror A, B, C, D, E, F, POLY; + private AnnotationMirror A, B, C, D, E, F, POLY; - @Override - public void initChecker() { - super.initChecker(); + @Override + public void initChecker() { + super.initChecker(); - Elements elements = processingEnv.getElementUtils(); + Elements elements = processingEnv.getElementUtils(); - A = AnnotationBuilder.fromClass(elements, LubglbA.class); - B = AnnotationBuilder.fromClass(elements, LubglbB.class); - C = AnnotationBuilder.fromClass(elements, LubglbC.class); - D = AnnotationBuilder.fromClass(elements, LubglbD.class); - E = AnnotationBuilder.fromClass(elements, LubglbE.class); - F = AnnotationBuilder.fromClass(elements, LubglbF.class); - POLY = AnnotationBuilder.fromClass(elements, PolyLubglb.class); + A = AnnotationBuilder.fromClass(elements, LubglbA.class); + B = AnnotationBuilder.fromClass(elements, LubglbB.class); + C = AnnotationBuilder.fromClass(elements, LubglbC.class); + D = AnnotationBuilder.fromClass(elements, LubglbD.class); + E = AnnotationBuilder.fromClass(elements, LubglbE.class); + F = AnnotationBuilder.fromClass(elements, LubglbF.class); + POLY = AnnotationBuilder.fromClass(elements, PolyLubglb.class); - lubAssert(D, E, C); - lubAssert(E, D, C); + lubAssert(D, E, C); + lubAssert(E, D, C); - glbAssert(B, C, D); - glbAssert(C, B, D); + glbAssert(B, C, D); + glbAssert(C, B, D); - glbAssert(POLY, B, F); - glbAssert(POLY, F, F); - glbAssert(POLY, A, POLY); + glbAssert(POLY, B, F); + glbAssert(POLY, F, F); + glbAssert(POLY, A, POLY); - lubAssert(POLY, B, A); - lubAssert(POLY, F, POLY); - lubAssert(POLY, A, A); - } + lubAssert(POLY, B, A); + lubAssert(POLY, F, POLY); + lubAssert(POLY, A, A); + } - /** - * Throws an exception if glb(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void glbAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format("GLB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + /** + * Throws an exception if glb(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void glbAssert( + AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.greatestLowerBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format( + "GLB of %s and %s should be %s, but is %s", + arg1, arg2, expected, result)); + } } - } - /** - * Throws an exception if lub(arg1, arg2) != result. - * - * @param arg1 the first argument - * @param arg2 the second argument - * @param expected the expected result - */ - private void lubAssert(AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { - QualifierHierarchy qualHierarchy = - ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); - AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); - if (!AnnotationUtils.areSame(expected, result)) { - throw new AssertionError( - String.format("LUB of %s and %s should be %s, but is %s", arg1, arg2, expected, result)); + /** + * Throws an exception if lub(arg1, arg2) != result. + * + * @param arg1 the first argument + * @param arg2 the second argument + * @param expected the expected result + */ + private void lubAssert( + AnnotationMirror arg1, AnnotationMirror arg2, AnnotationMirror expected) { + QualifierHierarchy qualHierarchy = + ((BaseTypeVisitor) visitor).getTypeFactory().getQualifierHierarchy(); + AnnotationMirror result = qualHierarchy.leastUpperBoundQualifiersOnly(arg1, arg2); + if (!AnnotationUtils.areSame(expected, result)) { + throw new AssertionError( + String.format( + "LUB of %s and %s should be %s, but is %s", + arg1, arg2, expected, result)); + } } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java index fdb862dd9e2..cfeaec85d3c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbA.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.lubglb.quals; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java index 7b6d1cd1b58..0ce07415b66 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbB.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.lubglb.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java index a307181199c..4033410a678 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbC.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.lubglb.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java index 25b6760b394..7b98b12906f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbD.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.lubglb.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java index 3a103946808..8ed2e6fe892 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbE.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.lubglb.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java index 2c2abaa87c6..fe21be7cb1d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/LubglbF.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.testchecker.lubglb.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java index 9891e951496..73785e46140 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/lubglb/quals/PolyLubglb.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.lubglb.quals; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java index 4de4fc1844c..08983de7471 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDAnnotatedTypeFactory.java @@ -1,21 +1,22 @@ package org.checkerframework.framework.testchecker.nontopdefault; -import java.lang.annotation.Annotation; -import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; +import java.lang.annotation.Annotation; +import java.util.Set; + public class NTDAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public NTDAnnotatedTypeFactory(BaseTypeChecker checker) { - // use flow inference - super(checker, true); - this.postInit(); - } + public NTDAnnotatedTypeFactory(BaseTypeChecker checker) { + // use flow inference + super(checker, true); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - // there's no polymorphic qualifiers in NTD - return getBundledTypeQualifiers(); - } + @Override + protected Set> createSupportedTypeQualifiers() { + // there's no polymorphic qualifiers in NTD + return getBundledTypeQualifiers(); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java index 8b083e8c01c..9cc308e26fc 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/NTDVisitor.java @@ -1,6 +1,7 @@ package org.checkerframework.framework.testchecker.nontopdefault; import com.sun.source.tree.Tree; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.testchecker.nontopdefault.qual.NTDBottom; @@ -8,23 +9,23 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; public class NTDVisitor extends BaseTypeVisitor { - public NTDVisitor(BaseTypeChecker checker) { - super(checker); - } + public NTDVisitor(BaseTypeChecker checker) { + super(checker); + } - // Because classes and interfaces are by default NTDMiddle, an override is defined here which - // allows references to be declared using any NDT type except NTDBottom. - @Override - public boolean isValidUse( - AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - // eg for the statement "@NTDSide Double x;" the declarationType is @NTDMiddle - // Double, and the useType is @NTDSide Double - if (declarationType.getEffectiveAnnotation(NTDMiddle.class) != null - && useType.getEffectiveAnnotation(NTDBottom.class) == null) { - return true; - } else { - // otherwise check the usage using super - return super.isValidUse(declarationType, useType, tree); + // Because classes and interfaces are by default NTDMiddle, an override is defined here which + // allows references to be declared using any NDT type except NTDBottom. + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + // eg for the statement "@NTDSide Double x;" the declarationType is @NTDMiddle + // Double, and the useType is @NTDSide Double + if (declarationType.getEffectiveAnnotation(NTDMiddle.class) != null + && useType.getEffectiveAnnotation(NTDBottom.class) == null) { + return true; + } else { + // otherwise check the usage using super + return super.isValidUse(declarationType, useType, tree); + } } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java index 56a7697b057..7f69c5fb6fe 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java index cd48dc75840..61b7383e6bc 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDMiddle.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** Middle is the default type in hierarchy. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java index 269c9020dc3..fe4382b9426 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDSide.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java index b8ba586691c..26ae8d65f92 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/nontopdefault/qual/NTDTop.java @@ -1,21 +1,22 @@ package org.checkerframework.framework.testchecker.nontopdefault.qual; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) @DefaultFor({ - TypeUseLocation.LOCAL_VARIABLE, - TypeUseLocation.IMPLICIT_UPPER_BOUND, - TypeUseLocation.RECEIVER + TypeUseLocation.LOCAL_VARIABLE, + TypeUseLocation.IMPLICIT_UPPER_BOUND, + TypeUseLocation.RECEIVER }) public @interface NTDTop {} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java index 5a728249db0..4709fab47f0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestAnnotatedTypeFactory.java @@ -1,6 +1,5 @@ package org.checkerframework.framework.testchecker.reflection; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.LiteralKind; @@ -11,23 +10,25 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationBuilder; +import javax.lang.model.element.AnnotationMirror; + /** * AnnotatedTypeFactory with reflection resolution enabled. The used qualifier hierarchy is * straightforward and only intended for test purposes. */ public final class ReflectionTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public ReflectionTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + public ReflectionTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - public TreeAnnotator createTreeAnnotator() { - LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); - AnnotationMirror bottom = AnnotationBuilder.fromClass(elements, TestReflectBottom.class); - literalTreeAnnotator.addLiteralKind(LiteralKind.INT, bottom); - literalTreeAnnotator.addStandardLiteralQualifiers(); + @Override + public TreeAnnotator createTreeAnnotator() { + LiteralTreeAnnotator literalTreeAnnotator = new LiteralTreeAnnotator(this); + AnnotationMirror bottom = AnnotationBuilder.fromClass(elements, TestReflectBottom.class); + literalTreeAnnotator.addLiteralKind(LiteralKind.INT, bottom); + literalTreeAnnotator.addStandardLiteralQualifiers(); - return new ListTreeAnnotator(new PropagationTreeAnnotator(this), literalTreeAnnotator); - } + return new ListTreeAnnotator(new PropagationTreeAnnotator(this), literalTreeAnnotator); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java index 84bc8d8d431..41235aae8f0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestChecker.java @@ -6,8 +6,8 @@ /** Checker for a simple type system to test reflection resolution. */ public class ReflectionTestChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ReflectionTestVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ReflectionTestVisitor(this); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java index 75427bcbb73..8d535964571 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/ReflectionTestVisitor.java @@ -5,14 +5,14 @@ /** Visitor for a simple type system to test reflection resolution. */ public final class ReflectionTestVisitor - extends BaseTypeVisitor { + extends BaseTypeVisitor { - public ReflectionTestVisitor(BaseTypeChecker checker) { - super(checker); - } + public ReflectionTestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected ReflectionTestAnnotatedTypeFactory createTypeFactory() { - return new ReflectionTestAnnotatedTypeFactory(checker); - } + @Override + protected ReflectionTestAnnotatedTypeFactory createTypeFactory() { + return new ReflectionTestAnnotatedTypeFactory(checker); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java index b49de665391..9074b7ab177 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/PolyTestReflect.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.reflection.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** * Toy type system for testing reflection resolution. Uses diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java index e95a1559575..eb90ec4ad8c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectBottom.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.reflection.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** * Toy type system for testing reflection resolution. Uses * org.checkerframework.common.subtyping.qual.Bottom as bottom. diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java index 1bafddb9d55..727a693eb44 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling1.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.reflection.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing reflection resolution. Uses diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java index 831a4b814ad..f47f20616d3 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectSibling2.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.reflection.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing reflection resolution. Uses diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java index 744c64ac5b1..37689e8e18e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/reflection/qual/TestReflectTop.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.reflection.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** * Toy type system for testing reflection resolution. Uses * org.checkerframework.common.subtyping.qual.Bottom as bottom. diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java index f2245000b20..b0f4fcd5295 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/SupportedQualsChecker.java @@ -1,41 +1,42 @@ package org.checkerframework.framework.testchecker.supportedquals; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.testchecker.supportedquals.qual.BottomQualifier; import org.checkerframework.framework.testchecker.supportedquals.qual.Qualifier; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + /** * Tests that annotations that have @Target(TYPE_USE, OTHER) (where OTHER is not TYPE_PARAMETER) may * be in the qual package so long as {@link BaseAnnotatedTypeFactory#createSupportedTypeQualifiers} * is overridden. */ public class SupportedQualsChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new BaseTypeVisitor(this) { - @Override - protected SupportedQualsAnnotatedTypeFactory createTypeFactory() { - return new SupportedQualsAnnotatedTypeFactory(checker); - } - }; - } - - static class SupportedQualsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public SupportedQualsAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new BaseTypeVisitor(this) { + @Override + protected SupportedQualsAnnotatedTypeFactory createTypeFactory() { + return new SupportedQualsAnnotatedTypeFactory(checker); + } + }; } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList(Qualifier.class, BottomQualifier.class)); + static class SupportedQualsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + public SupportedQualsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList(Qualifier.class, BottomQualifier.class)); + } } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java index 4cfbe9280b5..5fdd37a143f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/BottomQualifier.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.supportedquals.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({Qualifier.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java index 4bedee90bcf..1caecc0926e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/supportedquals/qual/Qualifier.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.supportedquals.qual; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @Target(ElementType.TYPE_USE) @SubtypeOf({}) @DefaultQualifierInHierarchy diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java index 8c9e9199b46..8e0a324e382 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationAnnotatedTypeFactory.java @@ -1,8 +1,7 @@ package org.checkerframework.framework.testchecker.testaccumulation; import com.sun.source.tree.MethodInvocationTree; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; + import org.checkerframework.common.accumulation.AccumulationAnalysis; import org.checkerframework.common.accumulation.AccumulationAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; @@ -14,71 +13,74 @@ import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.TreeUtils; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.TypeMirror; + /** * The annotated type factory for a test accumulation checker, which implements a basic called * methods checker. */ public class TestAccumulationAnnotatedTypeFactory extends AccumulationAnnotatedTypeFactory { - /** - * Create a new accumulation checker's annotated type factory. - * - * @param checker the checker - */ - public TestAccumulationAnnotatedTypeFactory(BaseTypeChecker checker) { - super( - checker, - TestAccumulation.class, - TestAccumulationBottom.class, - TestAccumulationPredicate.class); - this.postInit(); - } - - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - super.createTreeAnnotator(), new TestAccumulationTreeAnnotator(this)); - } - - /** - * Necessary for the type rule for called methods described below. A new accumulation analysis - * might have other type rules here, or none at all. - */ - private class TestAccumulationTreeAnnotator extends AccumulationTreeAnnotator { /** - * Creates an instance of this tree annotator for the given type factory. + * Create a new accumulation checker's annotated type factory. * - * @param factory the type factory + * @param checker the checker */ - public TestAccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { - super(factory); + public TestAccumulationAnnotatedTypeFactory(BaseTypeChecker checker) { + super( + checker, + TestAccumulation.class, + TestAccumulationBottom.class, + TestAccumulationPredicate.class); + this.postInit(); } @Override - public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - // CalledMethods requires special treatment of the return values of methods that return - // their receiver: the default return type must include the method being invoked. - // - // The basic accumulation analysis cannot handle this case - it can use the RR checker - // to transfer an annotation from the receiver to the return type, but because - // accumulation (has to) happen in dataflow, the correct annotation may not yet be - // available. The basic accumulation analysis therefore only supports "pass-through" - // returns receiver methods; it does not support automatically accumulating at the same - // time. - if (returnsThis(tree)) { - TypeMirror tm = type.getUnderlyingType(); - String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); - AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); - type.replaceAnnotation( - qualHierarchy.greatestLowerBoundShallow( - oldAnno, tm, createAccumulatorAnnotation(methodName), tm)); - } - return super.visitMethodInvocation(tree, type); + protected TreeAnnotator createTreeAnnotator() { + return new ListTreeAnnotator( + super.createTreeAnnotator(), new TestAccumulationTreeAnnotator(this)); } - } - // Overridden because there is no TestAccumulationAnalysis. - @Override - protected AccumulationAnalysis createFlowAnalysis() { - return new AccumulationAnalysis(this.getChecker(), this); - } + /** + * Necessary for the type rule for called methods described below. A new accumulation analysis + * might have other type rules here, or none at all. + */ + private class TestAccumulationTreeAnnotator extends AccumulationTreeAnnotator { + /** + * Creates an instance of this tree annotator for the given type factory. + * + * @param factory the type factory + */ + public TestAccumulationTreeAnnotator(AccumulationAnnotatedTypeFactory factory) { + super(factory); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + // CalledMethods requires special treatment of the return values of methods that return + // their receiver: the default return type must include the method being invoked. + // + // The basic accumulation analysis cannot handle this case - it can use the RR checker + // to transfer an annotation from the receiver to the return type, but because + // accumulation (has to) happen in dataflow, the correct annotation may not yet be + // available. The basic accumulation analysis therefore only supports "pass-through" + // returns receiver methods; it does not support automatically accumulating at the same + // time. + if (returnsThis(tree)) { + TypeMirror tm = type.getUnderlyingType(); + String methodName = TreeUtils.getMethodName(tree.getMethodSelect()); + AnnotationMirror oldAnno = type.getAnnotationInHierarchy(top); + type.replaceAnnotation( + qualHierarchy.greatestLowerBoundShallow( + oldAnno, tm, createAccumulatorAnnotation(methodName), tm)); + } + return super.visitMethodInvocation(tree, type); + } + } + + // Overridden because there is no TestAccumulationAnalysis. + @Override + protected AccumulationAnalysis createFlowAnalysis() { + return new AccumulationAnalysis(this.getChecker(), this); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java index 2d7b4b053d5..cf5f81168c6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverAnnotatedTypeFactory.java @@ -7,13 +7,13 @@ * receiver support, to enable the checker's auto-discovery of its AnnotatedTypeFactory to succeed. */ public class TestAccumulationNoReturnsReceiverAnnotatedTypeFactory - extends TestAccumulationAnnotatedTypeFactory { - /** - * Create a new accumulation checker's annotated type factory. - * - * @param checker the checker - */ - public TestAccumulationNoReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - } + extends TestAccumulationAnnotatedTypeFactory { + /** + * Create a new accumulation checker's annotated type factory. + * + * @param checker the checker + */ + public TestAccumulationNoReturnsReceiverAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java index 7133fdc8f90..0e9e6b48b80 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverChecker.java @@ -1,21 +1,22 @@ package org.checkerframework.framework.testchecker.testaccumulation; -import java.util.EnumSet; import org.checkerframework.common.accumulation.AccumulationChecker; +import java.util.EnumSet; + /** * A test accumulation checker that implements a basic version of called-methods accumulation, * without returns receiver support, to test the pluggable alias analysis functionality. */ public class TestAccumulationNoReturnsReceiverChecker extends AccumulationChecker { - /** - * Get the alias analyses that this checker should employ. - * - * @return the alias analyses - */ - @Override - protected EnumSet createAliasAnalyses() { - return EnumSet.noneOf(AliasAnalysis.class); - } + /** + * Get the alias analyses that this checker should employ. + * + * @return the alias analyses + */ + @Override + protected EnumSet createAliasAnalyses() { + return EnumSet.noneOf(AliasAnalysis.class); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java index 7a3cfb38143..cf8fe75caf5 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationNoReturnsReceiverTransfer.java @@ -7,12 +7,12 @@ * checker without support for the Returns Receiver Checker. */ public class TestAccumulationNoReturnsReceiverTransfer extends TestAccumulationTransfer { - /** - * default constructor - * - * @param analysis the analysis - */ - public TestAccumulationNoReturnsReceiverTransfer(AccumulationAnalysis analysis) { - super(analysis); - } + /** + * default constructor + * + * @param analysis the analysis + */ + public TestAccumulationNoReturnsReceiverTransfer(AccumulationAnalysis analysis) { + super(analysis); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java index a7ad023d24a..e0d064da34c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/TestAccumulationTransfer.java @@ -12,25 +12,25 @@ /** A basic transfer function that accumulates the names of methods called. */ public class TestAccumulationTransfer extends AccumulationTransfer { - /** - * default constructor - * - * @param analysis the analysis - */ - public TestAccumulationTransfer(AccumulationAnalysis analysis) { - super(analysis); - } + /** + * default constructor + * + * @param analysis the analysis + */ + public TestAccumulationTransfer(AccumulationAnalysis analysis) { + super(analysis); + } - @Override - public TransferResult visitMethodInvocation( - MethodInvocationNode node, TransferInput input) { - TransferResult result = - super.visitMethodInvocation(node, input); - Node receiver = node.getTarget().getReceiver(); - if (receiver != null) { - String methodName = node.getTarget().getMethod().getSimpleName().toString(); - accumulate(receiver, result, methodName); + @Override + public TransferResult visitMethodInvocation( + MethodInvocationNode node, TransferInput input) { + TransferResult result = + super.visitMethodInvocation(node, input); + Node receiver = node.getTarget().getReceiver(); + if (receiver != null) { + String methodName = node.getTarget().getMethod().getSimpleName().toString(); + accumulate(receiver, result, methodName); + } + return result; } - return result; - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java index 4d9378a3b6c..e3dbdd314a9 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/PolyTestAccumulation.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** Polymorphic qualifier for the test accumulation type system. */ @PolymorphicQualifier(TestAccumulation.class) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java index 2e876a0fa35..26da670ab2d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulation.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** A test accumulation analysis qualifier. It accumulates generic strings. */ @Retention(RetentionPolicy.RUNTIME) @@ -13,10 +14,10 @@ @SubtypeOf({}) @DefaultQualifierInHierarchy public @interface TestAccumulation { - /** - * Accumulated strings. - * - * @return the strings - */ - public String[] value() default {}; + /** + * Accumulated strings. + * + * @return the strings + */ + public String[] value() default {}; } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java index 824e22a54ca..6df1ac88fd6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationBottom.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; /** A test bottom type for an accumulation type system. */ @SubtypeOf({TestAccumulation.class, TestAccumulationPredicate.class}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java index 2056217faa4..2dfcdac1596 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/testaccumulation/qual/TestAccumulationPredicate.java @@ -1,21 +1,22 @@ package org.checkerframework.framework.testchecker.testaccumulation.qual; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** A test accumulation predicate annotation. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({TestAccumulation.class}) public @interface TestAccumulationPredicate { - /** - * A boolean expression indicating which values have been accumulated. - * - * @return a boolean expression indicating which values have been accumulated - * @checker_framework.manual #accumulation-qualifiers - */ - String value(); + /** + * A boolean expression indicating which values have been accumulated. + * + * @return a boolean expression indicating which values have been accumulated + * @checker_framework.manual #accumulation-qualifiers + */ + String value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java index d859f5fdacd..9644b64ee0f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsAnnotatedTypeFactory.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.testchecker.typedeclbounds; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.typedeclbounds.quals.Bottom; @@ -11,15 +7,20 @@ import org.checkerframework.framework.testchecker.typedeclbounds.quals.S2; import org.checkerframework.framework.testchecker.typedeclbounds.quals.Top; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class TypeDeclBoundsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public TypeDeclBoundsAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - postInit(); - } + public TypeDeclBoundsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>(Arrays.asList(Top.class, Bottom.class, S1.class, S2.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>(Arrays.asList(Top.class, Bottom.class, S1.class, S2.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java index 22b647976c4..e9dbd293231 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/TypeDeclBoundsVisitor.java @@ -4,12 +4,12 @@ import org.checkerframework.common.basetype.BaseTypeVisitor; public class TypeDeclBoundsVisitor extends BaseTypeVisitor { - public TypeDeclBoundsVisitor(BaseTypeChecker checker) { - super(checker); - } + public TypeDeclBoundsVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected TypeDeclBoundsAnnotatedTypeFactory createTypeFactory() { - return new TypeDeclBoundsAnnotatedTypeFactory(checker); - } + @Override + protected TypeDeclBoundsAnnotatedTypeFactory createTypeFactory() { + return new TypeDeclBoundsAnnotatedTypeFactory(checker); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java index 1c38d5e6357..3368e84da73 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Bottom.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** * Toy type system for testing impact of implicit java type conversion. diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java index 5b94b9717b0..68508063e86 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S1.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.UpperBoundFor; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.UpperBoundFor; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java index 161fa85f701..6d82e552151 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/S2.java @@ -1,14 +1,15 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeKind; +import org.checkerframework.framework.qual.UpperBoundFor; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeKind; -import org.checkerframework.framework.qual.UpperBoundFor; @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java index eb27eadbec6..fa909669f56 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedeclbounds/quals/Top.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.typedeclbounds.quals; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** * Toy type system for testing impact of implicit java type conversion. diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java index f181548ee12..3a1e591d7a6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/TypeDeclDefaultAnnotatedTypeFactory.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.testchecker.typedecldefault; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.typedecldefault.quals.PolyTypeDeclDefault; @@ -11,19 +7,24 @@ import org.checkerframework.framework.testchecker.typedecldefault.quals.TypeDeclDefaultMiddle; import org.checkerframework.framework.testchecker.typedecldefault.quals.TypeDeclDefaultTop; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class TypeDeclDefaultAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public TypeDeclDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public TypeDeclDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - TypeDeclDefaultTop.class, - TypeDeclDefaultMiddle.class, - TypeDeclDefaultBottom.class, - PolyTypeDeclDefault.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + TypeDeclDefaultTop.class, + TypeDeclDefaultMiddle.class, + TypeDeclDefaultBottom.class, + PolyTypeDeclDefault.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java index 71784ca50e0..178f737b9fb 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/PolyTypeDeclDefault.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** A polymorphic qualifier for the TypeDeclDefault type system. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java index a9ebbf764f7..6427beba55b 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultBottom.java @@ -1,14 +1,15 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.LiteralKind; -import org.checkerframework.framework.qual.QualifierForLiterals; -import org.checkerframework.framework.qual.SubtypeOf; /** TypeDeclDefault bottom qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java index 7a9d17ccce5..574849132f0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultMiddle.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** TypeDeclDefault middle qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java index 40cde9d0392..a2372bef51f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/typedecldefault/quals/TypeDeclDefaultTop.java @@ -1,13 +1,14 @@ package org.checkerframework.framework.testchecker.typedecldefault.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** This is the top qualifier of the TypeDeclDefault type system. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java index c491909478e..a87f7541e97 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/AnnoWithStringArg.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface AnnoWithStringArg { - String value(); + String value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java index e452855cdc5..cd620b9aaa8 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Critical.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** Denotes an exception that is particularly important. */ @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java index 600cb2fdf2d..89e455aabc9 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Encrypted.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** Denotes an object with a representation that has been encrypted. */ @SubtypeOf(Unqualified.class) @DefaultFor({TypeUseLocation.LOWER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java index 0b0cd24cfea..b9efaf21700 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOdd.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.util; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.PostconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.PostconditionAnnotation; /** * A postcondition annotation to indicate that a method ensures certain expressions to be {@link @@ -18,5 +19,5 @@ @PostconditionAnnotation(qualifier = Odd.class) @InheritedAnnotation public @interface EnsuresOdd { - String[] value(); + String[] value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java index 96d67c75e5d..81421991c7e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EnsuresOddIf.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.util; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; /** * A conditional postcondition annotation to indicate that a method ensures certain expressions to @@ -18,7 +19,7 @@ @ConditionalPostconditionAnnotation(qualifier = Odd.class) @InheritedAnnotation public @interface EnsuresOddIf { - boolean result(); + boolean result(); - String[] expression(); + String[] expression(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java index f803f9841b5..14f416636ac 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Even.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface Even {} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java index 4efbd5595ce..ecf1e1eb4e8 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/EvenOddChecker.java @@ -1,13 +1,7 @@ package org.checkerframework.framework.testchecker.util; import com.sun.source.tree.Tree; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; + import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -22,6 +16,15 @@ import org.checkerframework.framework.util.defaults.QualifierDefaults; import org.checkerframework.javacutil.AnnotationBuilder; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + /** * A simple checker used for testing the Checker Framework. It treats the {@code @Odd} and * {@code @Even} annotations as a subtype-style qualifiers with no special semantics. @@ -29,62 +32,68 @@ *

          This checker should only be used for testing the framework. */ public final class EvenOddChecker extends BaseTypeChecker { - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new TestVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new TestVisitor(this); + } } class TestVisitor extends BaseTypeVisitor { - public TestVisitor(BaseTypeChecker checker) { - super(checker); - } + public TestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - protected TestAnnotatedTypeFactory createTypeFactory() { - return new TestAnnotatedTypeFactory(checker); - } + @Override + protected TestAnnotatedTypeFactory createTypeFactory() { + return new TestAnnotatedTypeFactory(checker); + } - @Override - public boolean isValidUse(AnnotatedDeclaredType type, AnnotatedDeclaredType useType, Tree tree) { - // TODO: super would result in error, because of default on classes. - return true; - } + @Override + public boolean isValidUse( + AnnotatedDeclaredType type, AnnotatedDeclaredType useType, Tree tree) { + // TODO: super would result in error, because of default on classes. + return true; + } } class TestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - protected AnnotationMirror BOTTOM; + protected AnnotationMirror BOTTOM; - public TestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - Elements elements = processingEnv.getElementUtils(); - BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); + public TestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + Elements elements = processingEnv.getElementUtils(); + BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); - this.postInit(); - } + this.postInit(); + } - @Override - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); - AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); - defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); - } + @Override + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); + AnnotationMirror unqualified = AnnotationBuilder.fromClass(elements, Unqualified.class); + defs.addCheckedCodeDefault(unqualified, TypeUseLocation.OTHERWISE); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList(Odd.class, MonotonicOdd.class, Even.class, Unqualified.class, Bottom.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + Odd.class, + MonotonicOdd.class, + Even.class, + Unqualified.class, + Bottom.class)); + } - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this) { - @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); - } - }; - } + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this) { + @Override + protected QualifierKindHierarchy createQualifierKindHierarchy( + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); + } + }; + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java index 33bdf4aec0d..8084faae6e0 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FactoryTestChecker.java @@ -4,6 +4,16 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; import com.sun.tools.javac.tree.JCTree; + +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; + import java.io.File; import java.io.FileReader; import java.io.IOException; @@ -15,18 +25,11 @@ import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; + import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.tools.JavaFileObject; -import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.common.basetype.BaseTypeVisitor; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; /** * A specialized checker for testing purposes. It compares an expression's annotated type to an @@ -73,210 +76,212 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedOptions({"checker"}) public class FactoryTestChecker extends BaseTypeChecker { - SourceChecker checker; + SourceChecker checker; - @Override - public void initChecker() { - super.initChecker(); + @Override + public void initChecker() { + super.initChecker(); - // Find factory constructor - String checkerClassName = getOption("checker"); - try { - if (checkerClassName != null) { - Class checkerClass = Class.forName(checkerClassName); - Constructor constructor = checkerClass.getConstructor(); - Object o = constructor.newInstance(); - if (o instanceof SourceChecker) { - checker = (SourceChecker) o; + // Find factory constructor + String checkerClassName = getOption("checker"); + try { + if (checkerClassName != null) { + Class checkerClass = Class.forName(checkerClassName); + Constructor constructor = checkerClass.getConstructor(); + Object o = constructor.newInstance(); + if (o instanceof SourceChecker) { + checker = (SourceChecker) o; + } + } + } catch (Exception e) { + throw new BugInCF("Couldn't load " + checkerClassName + " class."); } - } - } catch (Exception e) { - throw new BugInCF("Couldn't load " + checkerClassName + " class."); } - } - /* - @Override - public AnnotatedTypeFactory createTypeFactory() { - return checker.createTypeFactory(); - }*/ + /* + @Override + public AnnotatedTypeFactory createTypeFactory() { + return checker.createTypeFactory(); + }*/ - @Override - public Properties getMessagesProperties() { - // We don't have any properties - Properties prop = new Properties(); - prop.setProperty( - "type.unexpected", - "unexpected type for the given tree%n" - + "Tree : %s%n" - + "Found : %s%n" - + "Expected : %s%n"); - return prop; - } + @Override + public Properties getMessagesProperties() { + // We don't have any properties + Properties prop = new Properties(); + prop.setProperty( + "type.unexpected", + "unexpected type for the given tree%n" + + "Tree : %s%n" + + "Found : %s%n" + + "Expected : %s%n"); + return prop; + } - @Override - protected BaseTypeVisitor createSourceVisitor() { - return new ToStringVisitor(this); - } + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new ToStringVisitor(this); + } - /** Builds the expected type for the trees from the source file of the tree compilation unit. */ - // This method is extremely ugly - private Map buildExpected(CompilationUnitTree tree) { - Map expected = new HashMap<>(); - try { - JavaFileObject o = tree.getSourceFile(); - File sourceFile = new File(o.toUri()); - LineNumberReader reader = new LineNumberReader(new FileReader(sourceFile)); - String line = reader.readLine(); - Pattern prevsubtreePattern = Pattern.compile("\\s*///(.*)-:-(.*)"); - Pattern prevfulltreePattern = Pattern.compile("\\s*///(.*)"); - Pattern subtreePattern = Pattern.compile("(.*)///(.*)-:-(.*)"); - Pattern fulltreePattern = Pattern.compile("(.*)///(.*)"); - while (line != null) { - Matcher prevsubtreeMatcher = prevsubtreePattern.matcher(line); - Matcher prevfulltreeMatcher = prevfulltreePattern.matcher(line); - Matcher subtreeMatcher = subtreePattern.matcher(line); - Matcher fulltreeMatcher = fulltreePattern.matcher(line); - if (prevsubtreeMatcher.matches()) { - String treeString = prevsubtreeMatcher.group(1).trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber() + 1); - expected.put(treeSpec, canonizeTypeString(prevsubtreeMatcher.group(2))); - } else if (prevfulltreeMatcher.matches()) { - String treeString = reader.readLine().trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); - expected.put(treeSpec, canonizeTypeString(prevfulltreeMatcher.group(1))); - } else if (subtreeMatcher.matches()) { - String treeString = subtreeMatcher.group(2).trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); - expected.put(treeSpec, canonizeTypeString(subtreeMatcher.group(3))); - } else if (fulltreeMatcher.matches()) { - String treeString = fulltreeMatcher.group(1).trim(); - if (treeString.endsWith(";")) { - treeString = treeString.substring(0, treeString.length() - 1); - } - TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); - expected.put(treeSpec, canonizeTypeString(fulltreeMatcher.group(2))); + /** Builds the expected type for the trees from the source file of the tree compilation unit. */ + // This method is extremely ugly + private Map buildExpected(CompilationUnitTree tree) { + Map expected = new HashMap<>(); + try { + JavaFileObject o = tree.getSourceFile(); + File sourceFile = new File(o.toUri()); + LineNumberReader reader = new LineNumberReader(new FileReader(sourceFile)); + String line = reader.readLine(); + Pattern prevsubtreePattern = Pattern.compile("\\s*///(.*)-:-(.*)"); + Pattern prevfulltreePattern = Pattern.compile("\\s*///(.*)"); + Pattern subtreePattern = Pattern.compile("(.*)///(.*)-:-(.*)"); + Pattern fulltreePattern = Pattern.compile("(.*)///(.*)"); + while (line != null) { + Matcher prevsubtreeMatcher = prevsubtreePattern.matcher(line); + Matcher prevfulltreeMatcher = prevfulltreePattern.matcher(line); + Matcher subtreeMatcher = subtreePattern.matcher(line); + Matcher fulltreeMatcher = fulltreePattern.matcher(line); + if (prevsubtreeMatcher.matches()) { + String treeString = prevsubtreeMatcher.group(1).trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber() + 1); + expected.put(treeSpec, canonizeTypeString(prevsubtreeMatcher.group(2))); + } else if (prevfulltreeMatcher.matches()) { + String treeString = reader.readLine().trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); + expected.put(treeSpec, canonizeTypeString(prevfulltreeMatcher.group(1))); + } else if (subtreeMatcher.matches()) { + String treeString = subtreeMatcher.group(2).trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); + expected.put(treeSpec, canonizeTypeString(subtreeMatcher.group(3))); + } else if (fulltreeMatcher.matches()) { + String treeString = fulltreeMatcher.group(1).trim(); + if (treeString.endsWith(";")) { + treeString = treeString.substring(0, treeString.length() - 1); + } + TreeSpec treeSpec = new TreeSpec(treeString.trim(), reader.getLineNumber()); + expected.put(treeSpec, canonizeTypeString(fulltreeMatcher.group(2))); + } + line = reader.readLine(); + } + reader.close(); + } catch (IOException e) { + throw new BugInCF("Unexpected IOException!", e); } - line = reader.readLine(); - } - reader.close(); - } catch (IOException e) { - throw new BugInCF("Unexpected IOException!", e); + return expected; } - return expected; - } - /** A method to canonize the tree representation. */ - private static String canonizeTreeString(String str) { - String canon = str.trim(); - Pattern pattern = Pattern.compile("(@\\S+)\\(\\)"); - Matcher matcher = pattern.matcher(canon); - while (matcher.find()) { - canon = matcher.replaceFirst(matcher.group(1)); - matcher.reset(canon); + /** A method to canonize the tree representation. */ + private static String canonizeTreeString(String str) { + String canon = str.trim(); + Pattern pattern = Pattern.compile("(@\\S+)\\(\\)"); + Matcher matcher = pattern.matcher(canon); + while (matcher.find()) { + canon = matcher.replaceFirst(matcher.group(1)); + matcher.reset(canon); + } + return canon.trim(); } - return canon.trim(); - } - /** - * A method to canonize type string representation. It removes any unnecessary white spaces and - * finds the type simple name instead of the fully qualified name. - * - * @param str the type string representation - * @return a canonical representation of the type - */ - private static String canonizeTypeString(String str) { - String canon = str.trim(); - canon = canon.replaceAll("\\s+", " "); - // Remove spaces between [ ] - canon = canon.replaceAll("\\[\\s+", "["); - canon = canon.replaceAll("\\s+\\]", "]"); + /** + * A method to canonize type string representation. It removes any unnecessary white spaces and + * finds the type simple name instead of the fully qualified name. + * + * @param str the type string representation + * @return a canonical representation of the type + */ + private static String canonizeTypeString(String str) { + String canon = str.trim(); + canon = canon.replaceAll("\\s+", " "); + // Remove spaces between [ ] + canon = canon.replaceAll("\\[\\s+", "["); + canon = canon.replaceAll("\\s+\\]", "]"); - // Remove spaces between < > - canon = canon.replaceAll("<\\s+", "<"); - canon = canon.replaceAll("\\s+>", ">"); + // Remove spaces between < > + canon = canon.replaceAll("<\\s+", "<"); + canon = canon.replaceAll("\\s+>", ">"); - // Take simply names! - canon = canon.replaceAll("[^\\<]*\\.(?=\\w)", ""); - return canon; - } + // Take simply names! + canon = canon.replaceAll("[^\\<]*\\.(?=\\w)", ""); + return canon; + } - /** - * A data structure that encapsulate a string and the line number that string appears in the - * buffer - */ - private static class TreeSpec { - public final String treeString; - public final long lineNumber; + /** + * A data structure that encapsulate a string and the line number that string appears in the + * buffer + */ + private static class TreeSpec { + public final String treeString; + public final long lineNumber; - public TreeSpec(String treeString, long lineNumber) { - this.treeString = canonizeTreeString(treeString); - this.lineNumber = lineNumber; - } + public TreeSpec(String treeString, long lineNumber) { + this.treeString = canonizeTreeString(treeString); + this.lineNumber = lineNumber; + } - @Override - public int hashCode() { - return Objects.hash(treeString, lineNumber); - } + @Override + public int hashCode() { + return Objects.hash(treeString, lineNumber); + } - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof TreeSpec) { - TreeSpec other = (TreeSpec) o; - return treeString.equals(other.treeString) && lineNumber == other.lineNumber; - } - return false; - } + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof TreeSpec) { + TreeSpec other = (TreeSpec) o; + return treeString.equals(other.treeString) && lineNumber == other.lineNumber; + } + return false; + } - @Override - public String toString() { - return lineNumber + ":" + treeString; + @Override + public String toString() { + return lineNumber + ":" + treeString; + } } - } - /** - * A specialized visitor that compares the actual and expected types for the specified trees and - * report an error if they differ - */ - private class ToStringVisitor extends BaseTypeVisitor> { - Map expected; + /** + * A specialized visitor that compares the actual and expected types for the specified trees and + * report an error if they differ + */ + private class ToStringVisitor extends BaseTypeVisitor> { + Map expected; - public ToStringVisitor(BaseTypeChecker checker) { - super(checker); - this.expected = buildExpected(root); - } + public ToStringVisitor(BaseTypeChecker checker) { + super(checker); + this.expected = buildExpected(root); + } - @Override - public Void scan(Tree tree, Void p) { - if (TreeUtils.isExpressionTree(tree)) { - ExpressionTree expTree = (ExpressionTree) tree; - TreeSpec treeSpec = - new TreeSpec( - expTree.toString().trim(), root.getLineMap().getLineNumber(((JCTree) expTree).pos)); - if (expected.containsKey(treeSpec)) { - String actualType = canonizeTypeString(atypeFactory.getAnnotatedType(expTree).toString()); - String expectedType = expected.get(treeSpec); - if (!actualType.equals(expectedType)) { + @Override + public Void scan(Tree tree, Void p) { + if (TreeUtils.isExpressionTree(tree)) { + ExpressionTree expTree = (ExpressionTree) tree; + TreeSpec treeSpec = + new TreeSpec( + expTree.toString().trim(), + root.getLineMap().getLineNumber(((JCTree) expTree).pos)); + if (expected.containsKey(treeSpec)) { + String actualType = + canonizeTypeString(atypeFactory.getAnnotatedType(expTree).toString()); + String expectedType = expected.get(treeSpec); + if (!actualType.equals(expectedType)) { - // The key is added above using a setProperty call, which is not supported - // by the CompilerMessagesChecker. - @SuppressWarnings("compilermessages") - @CompilerMessageKey String key = "type.unexpected"; - FactoryTestChecker.this.reportError( - tree, key, tree.toString(), actualType, expectedType); - } + // The key is added above using a setProperty call, which is not supported + // by the CompilerMessagesChecker. + @SuppressWarnings("compilermessages") + @CompilerMessageKey String key = "type.unexpected"; + FactoryTestChecker.this.reportError( + tree, key, tree.toString(), actualType, expectedType); + } + } + } + return super.scan(tree, p); } - } - return super.scan(tree, p); } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java index 4c16d26e7aa..9930adf0690 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/FlowTestAnnotatedTypeFactory.java @@ -1,12 +1,5 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.subtyping.qual.Bottom; @@ -22,109 +15,124 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; -public class FlowTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - protected final AnnotationMirror VALUE, BOTTOM, TOP; - - public FlowTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - VALUE = AnnotationBuilder.fromClass(elements, ValueTypeAnno.class); - BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); - TOP = AnnotationBuilder.fromClass(elements, Unqualified.class); - - this.postInit(); - } - - @Override - protected void addCheckedCodeDefaults(QualifierDefaults defs) { - defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); - defs.addCheckedCodeDefault(TOP, TypeUseLocation.OTHERWISE); - } +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet>( - Arrays.asList( - ValueTypeAnno.class, Odd.class, MonotonicOdd.class, Unqualified.class, Bottom.class)); - } +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; - @Override - protected QualifierHierarchy createQualifierHierarchy() { - return new FlowQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); - } +public class FlowTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + protected final AnnotationMirror VALUE, BOTTOM, TOP; - /** FlowQualifierHierarchy: {@code @ValueTypeAnno(a) <: @ValueValueTypeAnno(b) iff a == b} */ - class FlowQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - final QualifierKind VALUE_KIND; + public FlowTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker, true); + VALUE = AnnotationBuilder.fromClass(elements, ValueTypeAnno.class); + BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); + TOP = AnnotationBuilder.fromClass(elements, Unqualified.class); - /** - * Creates a FlowQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy - * @param elements element utils - */ - public FlowQualifierHierarchy( - Collection> qualifierClasses, Elements elements) { - super(qualifierClasses, elements, FlowTestAnnotatedTypeFactory.this); - this.VALUE_KIND = getQualifierKind(VALUE); + this.postInit(); } @Override - protected QualifierKindHierarchy createQualifierKindHierarchy( - Collection> qualifierClasses) { - return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); + protected void addCheckedCodeDefaults(QualifierDefaults defs) { + defs.addCheckedCodeDefault(BOTTOM, TypeUseLocation.LOWER_BOUND); + defs.addCheckedCodeDefault(TOP, TypeUseLocation.OTHERWISE); } @Override - protected boolean isSubtypeWithElements( - AnnotationMirror subAnno, - QualifierKind subKind, - AnnotationMirror superAnno, - QualifierKind superKind) { - return AnnotationUtils.areSame(superAnno, subAnno); + protected Set> createSupportedTypeQualifiers() { + return new HashSet>( + Arrays.asList( + ValueTypeAnno.class, + Odd.class, + MonotonicOdd.class, + Unqualified.class, + Bottom.class)); } @Override - protected AnnotationMirror leastUpperBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind lubKind) { - if (qualifierKind1 == qualifierKind2) { - // Both are Value - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return TOP; - } - } else if (qualifierKind1 == VALUE_KIND) { - return a1; - } else if (qualifierKind2 == VALUE_KIND) { - return a2; - } - throw new BugInCF("Unexpected annotations: leastUpperBoundWithElements(%s, %s)", a1, a2); + protected QualifierHierarchy createQualifierHierarchy() { + return new FlowQualifierHierarchy(this.getSupportedTypeQualifiers(), elements); } - @Override - protected AnnotationMirror greatestLowerBoundWithElements( - AnnotationMirror a1, - QualifierKind qualifierKind1, - AnnotationMirror a2, - QualifierKind qualifierKind2, - QualifierKind glbKind) { - if (qualifierKind1 == qualifierKind2) { - // Both are Value - if (AnnotationUtils.areSame(a1, a2)) { - return a1; - } else { - return BOTTOM; + /** FlowQualifierHierarchy: {@code @ValueTypeAnno(a) <: @ValueValueTypeAnno(b) iff a == b} */ + class FlowQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + final QualifierKind VALUE_KIND; + + /** + * Creates a FlowQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy + * @param elements element utils + */ + public FlowQualifierHierarchy( + Collection> qualifierClasses, Elements elements) { + super(qualifierClasses, elements, FlowTestAnnotatedTypeFactory.this); + this.VALUE_KIND = getQualifierKind(VALUE); + } + + @Override + protected QualifierKindHierarchy createQualifierKindHierarchy( + Collection> qualifierClasses) { + return new DefaultQualifierKindHierarchy(qualifierClasses, Bottom.class); + } + + @Override + protected boolean isSubtypeWithElements( + AnnotationMirror subAnno, + QualifierKind subKind, + AnnotationMirror superAnno, + QualifierKind superKind) { + return AnnotationUtils.areSame(superAnno, subAnno); + } + + @Override + protected AnnotationMirror leastUpperBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind lubKind) { + if (qualifierKind1 == qualifierKind2) { + // Both are Value + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return TOP; + } + } else if (qualifierKind1 == VALUE_KIND) { + return a1; + } else if (qualifierKind2 == VALUE_KIND) { + return a2; + } + throw new BugInCF( + "Unexpected annotations: leastUpperBoundWithElements(%s, %s)", a1, a2); + } + + @Override + protected AnnotationMirror greatestLowerBoundWithElements( + AnnotationMirror a1, + QualifierKind qualifierKind1, + AnnotationMirror a2, + QualifierKind qualifierKind2, + QualifierKind glbKind) { + if (qualifierKind1 == qualifierKind2) { + // Both are Value + if (AnnotationUtils.areSame(a1, a2)) { + return a1; + } else { + return BOTTOM; + } + } else if (qualifierKind1 == VALUE_KIND) { + return a1; + } else if (qualifierKind2 == VALUE_KIND) { + return a2; + } + throw new BugInCF( + "Unexpected annotations: greatestLowerBoundWithElements(%s, %s)", a1, a2); } - } else if (qualifierKind1 == VALUE_KIND) { - return a1; - } else if (qualifierKind2 == VALUE_KIND) { - return a2; - } - throw new BugInCF("Unexpected annotations: greatestLowerBoundWithElements(%s, %s)", a1, a2); } - } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java index 36a8492989a..9abf5735d05 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/MonotonicOdd.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Target; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.qual.MonotonicQualifier; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; + @Inherited @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java index fe5407621b4..4f1b97c10e6 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/Odd.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.util; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; @SubtypeOf(MonotonicOdd.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java index 07d863bf16f..5fc6f75c472 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternA.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({PatternAB.class, PatternAC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @QualifierForLiterals(stringPatterns = "^[A]$") diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java index dc2768014bc..ebb8ff02fee 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAB.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf(PatternUnknown.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @QualifierForLiterals(stringPatterns = "^[AB]$") diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java index bdcef53aac1..44d5786391b 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternAC.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf(PatternUnknown.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @QualifierForLiterals(stringPatterns = "^[AC]$") diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java index 6b2b13d7cb4..9ac2275227d 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternB.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({PatternAB.class, PatternBC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @QualifierForLiterals(stringPatterns = "^[B]$") diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java index b68509fa218..8d62e419fb1 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBC.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf(PatternUnknown.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @QualifierForLiterals(stringPatterns = "^[BC]$") diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java index cd69141f598..fd016483cea 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternBottomFull.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({PatternA.class, PatternB.class, PatternC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.LOWER_BOUND, TypeUseLocation.UPPER_BOUND}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java index ef12d93f792..9b54506948b 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternC.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf({PatternBC.class, PatternAC.class}) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @QualifierForLiterals(stringPatterns = "^[C]$") diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java index 43c21ca264e..9468b2ee663 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PatternUnknown.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) @DefaultQualifierInHierarchy diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java index 05ec1d312c2..dce7b1ee0d1 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/PolyEncrypted.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.util; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; @PolymorphicQualifier @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java index 81dafd5df51..20945ecd28f 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/RequiresOdd.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.util; +import org.checkerframework.framework.qual.PreconditionAnnotation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PreconditionAnnotation; /** * A precondition annotation to indicate that a method requires certain expressions to be {@link @@ -16,5 +17,5 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @PreconditionAnnotation(qualifier = Odd.class) public @interface RequiresOdd { - String[] value(); + String[] value(); } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java index db0eab4ddd5..bfaf21a661e 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SubQual.java @@ -1,8 +1,9 @@ package org.checkerframework.framework.testchecker.util; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** A subtype of SuperQual. */ @SubtypeOf(SuperQual.class) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java index 15d29f6356e..8bb3ddd8b26 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/SuperQual.java @@ -1,10 +1,11 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + /** A supertype of SubQual. */ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java b/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java index 3d306397a52..5e14b48fcf2 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/util/ValueTypeAnno.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.util; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.common.subtyping.qual.Unqualified; import org.checkerframework.framework.qual.SubtypeOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + @SubtypeOf(Unqualified.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) public @interface ValueTypeAnno { - int value() default 0; + int value() default 0; } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java index 80ab054ecbe..8398ca71912 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/VariableNameDefaultAnnotatedTypeFactory.java @@ -1,9 +1,5 @@ package org.checkerframework.framework.testchecker.variablenamedefault; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.testchecker.variablenamedefault.quals.PolyVariableNameDefault; @@ -11,19 +7,24 @@ import org.checkerframework.framework.testchecker.variablenamedefault.quals.VariableNameDefaultMiddle; import org.checkerframework.framework.testchecker.variablenamedefault.quals.VariableNameDefaultTop; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class VariableNameDefaultAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - public VariableNameDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } + public VariableNameDefaultAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } - @Override - protected Set> createSupportedTypeQualifiers() { - return new HashSet<>( - Arrays.asList( - VariableNameDefaultTop.class, - VariableNameDefaultMiddle.class, - VariableNameDefaultBottom.class, - PolyVariableNameDefault.class)); - } + @Override + protected Set> createSupportedTypeQualifiers() { + return new HashSet<>( + Arrays.asList( + VariableNameDefaultTop.class, + VariableNameDefaultMiddle.class, + VariableNameDefaultBottom.class, + PolyVariableNameDefault.class)); + } } diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java index 7b148ede0e6..c7cb0147bb2 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/PolyVariableNameDefault.java @@ -1,11 +1,12 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** A polymorphic qualifier for the VariableNameDefault type system. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java index 436c04b92f5..ed75447ea2c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultBottom.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; /** VariableNameDefault bottom qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java index ddba0352fdf..79bde50fd5c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultMiddle.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; /** VariableNameDefault middle qualifier. */ @Documented diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java index b58a1c34f25..ddd3285d772 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/variablenamedefault/quals/VariableNameDefaultTop.java @@ -1,12 +1,13 @@ package org.checkerframework.framework.testchecker.variablenamedefault.quals; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** This is the top qualifier of the VariableNameDefault type system. */ @Documented diff --git a/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java b/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java index ffdfa8f0cfe..a17426e754a 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestAnnotatedTypeFactory.java @@ -1,13 +1,16 @@ package viewpointtest; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.type.AbstractViewpointAdapter; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.javacutil.AnnotationBuilder; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; + import viewpointtest.quals.A; import viewpointtest.quals.B; import viewpointtest.quals.Bottom; @@ -19,41 +22,42 @@ /** The annotated type factory for the Viewpoint Test Checker. */ public class ViewpointTestAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The {@link Top} annotation. */ - public final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, Top.class); - - /** The {@link Lost} annotation. */ - public final AnnotationMirror LOST = AnnotationBuilder.fromClass(elements, Lost.class); - - /** - * Create a new ViewpointTestAnnotatedTypeFactory. - * - * @param checker the checker to which this annotated type factory belongs - */ - public ViewpointTestAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker); - this.postInit(); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return getBundledTypeQualifiers( - A.class, - B.class, - Bottom.class, - PolyVP.class, - ReceiverDependentQual.class, - Lost.class, - Top.class); - } - - @Override - protected AbstractViewpointAdapter createViewpointAdapter() { - return new ViewpointTestViewpointAdapter(this); - } - - @Override - public QualifierHierarchy createQualifierHierarchy() { - return new ViewpointTestQualifierHierarchy(this.getSupportedTypeQualifiers(), elements, this); - } + /** The {@link Top} annotation. */ + public final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, Top.class); + + /** The {@link Lost} annotation. */ + public final AnnotationMirror LOST = AnnotationBuilder.fromClass(elements, Lost.class); + + /** + * Create a new ViewpointTestAnnotatedTypeFactory. + * + * @param checker the checker to which this annotated type factory belongs + */ + public ViewpointTestAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + this.postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return getBundledTypeQualifiers( + A.class, + B.class, + Bottom.class, + PolyVP.class, + ReceiverDependentQual.class, + Lost.class, + Top.class); + } + + @Override + protected AbstractViewpointAdapter createViewpointAdapter() { + return new ViewpointTestViewpointAdapter(this); + } + + @Override + public QualifierHierarchy createQualifierHierarchy() { + return new ViewpointTestQualifierHierarchy( + this.getSupportedTypeQualifiers(), elements, this); + } } diff --git a/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java b/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java index 6b1aa74740b..b5dfaed769a 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestQualifierHierarchy.java @@ -1,38 +1,41 @@ package viewpointtest; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; +import org.checkerframework.framework.type.NoElementQualifierHierarchy; +import org.checkerframework.framework.type.QualifierHierarchy; + import java.lang.annotation.Annotation; import java.util.Collection; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.util.Elements; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; -import org.checkerframework.framework.type.NoElementQualifierHierarchy; -import org.checkerframework.framework.type.QualifierHierarchy; + import viewpointtest.quals.Bottom; import viewpointtest.quals.Lost; /** The {@link QualifierHierarchy} for the Viewpoint Test Checker. */ public class ViewpointTestQualifierHierarchy extends NoElementQualifierHierarchy { - /** - * Creates a ViewpointTestQualifierHierarchy from the given classes. - * - * @param qualifierClasses classes of annotations that are the qualifiers - * @param elements element utils - * @param atypeFactory the associated type factory - */ - public ViewpointTestQualifierHierarchy( - Collection> qualifierClasses, - Elements elements, - GenericAnnotatedTypeFactory atypeFactory) { - super(qualifierClasses, elements, atypeFactory); - } + /** + * Creates a ViewpointTestQualifierHierarchy from the given classes. + * + * @param qualifierClasses classes of annotations that are the qualifiers + * @param elements element utils + * @param atypeFactory the associated type factory + */ + public ViewpointTestQualifierHierarchy( + Collection> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, elements, atypeFactory); + } - @Override - public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { - // Lost is not reflexive and the only subtype is Bottom. - if (atypeFactory.areSameByClass(superAnno, Lost.class) - && !atypeFactory.areSameByClass(subAnno, Bottom.class)) { - return false; + @Override + public boolean isSubtypeQualifiers(AnnotationMirror subAnno, AnnotationMirror superAnno) { + // Lost is not reflexive and the only subtype is Bottom. + if (atypeFactory.areSameByClass(superAnno, Lost.class) + && !atypeFactory.areSameByClass(subAnno, Bottom.class)) { + return false; + } + return super.isSubtypeQualifiers(subAnno, superAnno); } - return super.isSubtypeQualifiers(subAnno, superAnno); - } } diff --git a/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java b/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java index 3e0a7ec24ea..aae61833e10 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestViewpointAdapter.java @@ -1,11 +1,13 @@ package viewpointtest; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.framework.type.AbstractViewpointAdapter; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; + +import javax.lang.model.element.AnnotationMirror; + import viewpointtest.quals.Lost; import viewpointtest.quals.ReceiverDependentQual; import viewpointtest.quals.Top; @@ -13,38 +15,39 @@ /** The viewpoint adapter for the Viewpoint Test Checker. */ public class ViewpointTestViewpointAdapter extends AbstractViewpointAdapter { - /** The {@link Top}, {@link ReceiverDependentQual} and {@link Lost} annotation. */ - private final AnnotationMirror TOP, RECEIVERDEPENDENTQUAL, LOST; - - /** - * The class constructor. - * - * @param atypeFactory the type factory to use - */ - public ViewpointTestViewpointAdapter(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - TOP = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).TOP; - RECEIVERDEPENDENTQUAL = - AnnotationBuilder.fromClass(atypeFactory.getElementUtils(), ReceiverDependentQual.class); - LOST = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).LOST; - } - - @Override - protected AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm) { - return atm.getAnnotationInHierarchy(TOP); - } - - @Override - protected AnnotationMirror combineAnnotationWithAnnotation( - AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation) { - - if (AnnotationUtils.areSame(declaredAnnotation, RECEIVERDEPENDENTQUAL)) { - if (AnnotationUtils.areSame(receiverAnnotation, TOP)) { - return LOST; - } else { - return receiverAnnotation; - } + /** The {@link Top}, {@link ReceiverDependentQual} and {@link Lost} annotation. */ + private final AnnotationMirror TOP, RECEIVERDEPENDENTQUAL, LOST; + + /** + * The class constructor. + * + * @param atypeFactory the type factory to use + */ + public ViewpointTestViewpointAdapter(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + TOP = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).TOP; + RECEIVERDEPENDENTQUAL = + AnnotationBuilder.fromClass( + atypeFactory.getElementUtils(), ReceiverDependentQual.class); + LOST = ((ViewpointTestAnnotatedTypeFactory) atypeFactory).LOST; + } + + @Override + protected AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm) { + return atm.getAnnotationInHierarchy(TOP); + } + + @Override + protected AnnotationMirror combineAnnotationWithAnnotation( + AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation) { + + if (AnnotationUtils.areSame(declaredAnnotation, RECEIVERDEPENDENTQUAL)) { + if (AnnotationUtils.areSame(receiverAnnotation, TOP)) { + return LOST; + } else { + return receiverAnnotation; + } + } + return declaredAnnotation; } - return declaredAnnotation; - } } diff --git a/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java b/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java index 7435f376383..928860b199d 100644 --- a/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java +++ b/framework/src/test/java/viewpointtest/ViewpointTestVisitor.java @@ -1,27 +1,28 @@ package viewpointtest; import com.sun.source.tree.NewClassTree; + import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.framework.type.AnnotatedTypeMirror; /** The visitor for the Viewpoint Test Checker. */ public class ViewpointTestVisitor extends BaseTypeVisitor { - /** - * Create a new ViewpointTestVisitor. - * - * @param checker the checker to which this visitor belongs - */ - public ViewpointTestVisitor(BaseTypeChecker checker) { - super(checker); - } + /** + * Create a new ViewpointTestVisitor. + * + * @param checker the checker to which this visitor belongs + */ + public ViewpointTestVisitor(BaseTypeChecker checker) { + super(checker); + } - @Override - public Void visitNewClass(NewClassTree tree, Void p) { - AnnotatedTypeMirror Type = atypeFactory.getAnnotatedType(tree); - if (Type.hasAnnotation(atypeFactory.TOP) || Type.hasAnnotation(atypeFactory.LOST)) { - checker.reportError(tree, "new.class.type.invalid", Type.getAnnotations()); + @Override + public Void visitNewClass(NewClassTree tree, Void p) { + AnnotatedTypeMirror Type = atypeFactory.getAnnotatedType(tree); + if (Type.hasAnnotation(atypeFactory.TOP) || Type.hasAnnotation(atypeFactory.LOST)) { + checker.reportError(tree, "new.class.type.invalid", Type.getAnnotations()); + } + return super.visitNewClass(tree, p); } - return super.visitNewClass(tree, p); - } } diff --git a/framework/src/test/java/viewpointtest/quals/A.java b/framework/src/test/java/viewpointtest/quals/A.java index 45fe3e9b6ff..8bee242159e 100644 --- a/framework/src/test/java/viewpointtest/quals/A.java +++ b/framework/src/test/java/viewpointtest/quals/A.java @@ -1,11 +1,12 @@ package viewpointtest.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** The A qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/B.java b/framework/src/test/java/viewpointtest/quals/B.java index 5199ecee3c7..957096b3c9f 100644 --- a/framework/src/test/java/viewpointtest/quals/B.java +++ b/framework/src/test/java/viewpointtest/quals/B.java @@ -1,11 +1,12 @@ package viewpointtest.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** The B qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/Bottom.java b/framework/src/test/java/viewpointtest/quals/Bottom.java index 2970528116f..1cdcd149e9d 100644 --- a/framework/src/test/java/viewpointtest/quals/Bottom.java +++ b/framework/src/test/java/viewpointtest/quals/Bottom.java @@ -1,13 +1,14 @@ package viewpointtest.quals; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeUseLocation; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; /** The Bottom qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/Lost.java b/framework/src/test/java/viewpointtest/quals/Lost.java index f20c1363fdb..38e215c5293 100644 --- a/framework/src/test/java/viewpointtest/quals/Lost.java +++ b/framework/src/test/java/viewpointtest/quals/Lost.java @@ -1,11 +1,12 @@ package viewpointtest.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** * The Lost qualifier indicates that a relationship cannot be expressed. It is the result of diff --git a/framework/src/test/java/viewpointtest/quals/PolyVP.java b/framework/src/test/java/viewpointtest/quals/PolyVP.java index 3236c0b1694..c89a201cc80 100644 --- a/framework/src/test/java/viewpointtest/quals/PolyVP.java +++ b/framework/src/test/java/viewpointtest/quals/PolyVP.java @@ -1,11 +1,12 @@ package viewpointtest.quals; +import org.checkerframework.framework.qual.PolymorphicQualifier; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; /** The PolyVP qualifier is a polymorphic qualifier in this hierarchy. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java b/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java index 7b0df660fac..cb8adfc0635 100644 --- a/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java +++ b/framework/src/test/java/viewpointtest/quals/ReceiverDependentQual.java @@ -1,11 +1,12 @@ package viewpointtest.quals; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; /** The ReceiverDependentQual qualifier. */ @Documented diff --git a/framework/src/test/java/viewpointtest/quals/Top.java b/framework/src/test/java/viewpointtest/quals/Top.java index 22df4b2fdc5..e98647e51f1 100644 --- a/framework/src/test/java/viewpointtest/quals/Top.java +++ b/framework/src/test/java/viewpointtest/quals/Top.java @@ -1,12 +1,13 @@ package viewpointtest.quals; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.SubtypeOf; /** The Top qualifier. */ @Documented diff --git a/framework/src/testannotations/java/android/support/annotation/IntRange.java b/framework/src/testannotations/java/android/support/annotation/IntRange.java index 03f547e925b..ab7bb212f6a 100644 --- a/framework/src/testannotations/java/android/support/annotation/IntRange.java +++ b/framework/src/testannotations/java/android/support/annotation/IntRange.java @@ -7,7 +7,7 @@ @Documented @Retention(RetentionPolicy.CLASS) public @interface IntRange { - long from() default Long.MIN_VALUE; + long from() default Long.MIN_VALUE; - long to() default Long.MAX_VALUE; + long to() default Long.MAX_VALUE; } diff --git a/framework/tests/Issue6060.java b/framework/tests/Issue6060.java index d9ee30afd15..cdaf6e276ae 100644 --- a/framework/tests/Issue6060.java +++ b/framework/tests/Issue6060.java @@ -3,11 +3,11 @@ public interface Issue6060 extends Iterable { - default Spliterator spliterator() { - return Iterable.super.spliterator(); - } + default Spliterator spliterator() { + return Iterable.super.spliterator(); + } - default void forEach(Consumer action) { - Iterable.super.forEach(action); - } + default void forEach(Consumer action) { + Iterable.super.forEach(action); + } } diff --git a/framework/tests/accumulation-norr/SimpleFluent.java b/framework/tests/accumulation-norr/SimpleFluent.java index ac5a4c12551..aed02fa7c16 100644 --- a/framework/tests/accumulation-norr/SimpleFluent.java +++ b/framework/tests/accumulation-norr/SimpleFluent.java @@ -7,119 +7,118 @@ /* Simple inference of a fluent builder. */ public class SimpleFluent { - SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } - - @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } - - @This SimpleFluent a() { - return this; - } - - @This SimpleFluent b() { - return this; - } - - // intentionally does not have an @This annotation - SimpleFluent c() { - return this; - } - - static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().b().build(); - } - - static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { - s.a() + SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } + + @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } + + @This SimpleFluent a() { + return this; + } + + @This SimpleFluent b() { + return this; + } + + // intentionally does not have an @This annotation + SimpleFluent c() { + return this; + } + + static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().b().build(); + } + + static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { + s.a() + // :: error: (method.invocation.invalid) + .build(); + } + + static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { + s.a().b() + .c() + // :: error: (method.invocation.invalid) + .build(); + } + + static void mixFluentAndNonFluent(SimpleFluent s1) { + s1.a().b(); + // :: error: (method.invocation.invalid) + s1.build(); + } + + static void mixFluentAndNonFluentWrong(SimpleFluent s) { + s.a(); // .b() // :: error: (method.invocation.invalid) - .build(); - } + s.build(); + } + + static void fluentLoop(SimpleFluent t) { + SimpleFluent s = t.a(); + int i = 10; + while (i > 0) { + // :: error: (method.invocation.invalid) + s.b().build(); + i--; + s = new SimpleFluent(); + } + } + + static void m1(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.c().a().b().build(); + } - static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { - s.a() - .b() - .c() + static void m2(SimpleFluent s) { + s.c().a().b(); // :: error: (method.invocation.invalid) - .build(); - } - - static void mixFluentAndNonFluent(SimpleFluent s1) { - s1.a().b(); - // :: error: (method.invocation.invalid) - s1.build(); - } - - static void mixFluentAndNonFluentWrong(SimpleFluent s) { - s.a(); // .b() - // :: error: (method.invocation.invalid) - s.build(); - } - - static void fluentLoop(SimpleFluent t) { - SimpleFluent s = t.a(); - int i = 10; - while (i > 0) { - // :: error: (method.invocation.invalid) - s.b().build(); - i--; - s = new SimpleFluent(); - } - } - - static void m1(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.c().a().b().build(); - } - - static void m2(SimpleFluent s) { - s.c().a().b(); - // :: error: (method.invocation.invalid) - s.c().build(); - } - - static void m3(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.c().a().b().build(); - // :: error: (method.invocation.invalid) - s.c().a().build(); - } - - static void m4(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.c().a().b().build2().build(); - } - - static void m5(SimpleFluent s) { - s.a().c(); - // :: error: (method.invocation.invalid) - s.b().build(); - } - - static void m6(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().c().b().build(); - } - - static void m7(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().b().build().c().b().build(); - } - - static void m8(SimpleFluent s) { - // :: error: (method.invocation.invalid) - s.a().build().c().a().b().build(); - // :: error: (method.invocation.invalid) - s.build(); - } - - static void m9() { - // :: error: (method.invocation.invalid) - new SimpleFluent().a().b().build(); - // :: error: (method.invocation.invalid) - new SimpleFluent().a().build(); - } + s.c().build(); + } + + static void m3(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.c().a().b().build(); + // :: error: (method.invocation.invalid) + s.c().a().build(); + } + + static void m4(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.c().a().b().build2().build(); + } + + static void m5(SimpleFluent s) { + s.a().c(); + // :: error: (method.invocation.invalid) + s.b().build(); + } + + static void m6(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().c().b().build(); + } + + static void m7(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().b().build().c().b().build(); + } + + static void m8(SimpleFluent s) { + // :: error: (method.invocation.invalid) + s.a().build().c().a().b().build(); + // :: error: (method.invocation.invalid) + s.build(); + } + + static void m9() { + // :: error: (method.invocation.invalid) + new SimpleFluent().a().b().build(); + // :: error: (method.invocation.invalid) + new SimpleFluent().a().build(); + } } diff --git a/framework/tests/accumulation/Generics.java b/framework/tests/accumulation/Generics.java index 4fb90b5e5a3..0fe6454874c 100644 --- a/framework/tests/accumulation/Generics.java +++ b/framework/tests/accumulation/Generics.java @@ -1,43 +1,44 @@ -import java.util.ArrayList; -import java.util.List; import org.checkerframework.common.returnsreceiver.qual.*; import org.checkerframework.framework.testchecker.testaccumulation.qual.*; +import java.util.ArrayList; +import java.util.List; + public class Generics { - static interface Symbol { + static interface Symbol { - boolean isStatic(); + boolean isStatic(); - void finalize(@TestAccumulation("foo") Symbol this); - } + void finalize(@TestAccumulation("foo") Symbol this); + } - static List<@TestAccumulation("foo") Symbol> makeList(@TestAccumulation("foo") Symbol s) { - ArrayList<@TestAccumulation("foo") Symbol> l = new ArrayList<>(); - l.add(s); - return l; - } + static List<@TestAccumulation("foo") Symbol> makeList(@TestAccumulation("foo") Symbol s) { + ArrayList<@TestAccumulation("foo") Symbol> l = new ArrayList<>(); + l.add(s); + return l; + } - static void useList() { - Symbol s = null; - for (Symbol t : makeList(s)) { - t.finalize(); + static void useList() { + Symbol s = null; + for (Symbol t : makeList(s)) { + t.finalize(); + } } - } - - // reduced from real-world code - private <@TestAccumulation() T extends Symbol> T getMember(Class type, boolean b) { - if (b) { - T sym = getMember(type, !b); - if (sym != null && sym.isStatic()) { - return sym; - } - } else { - T sym = getMember(type, b); - if (sym != null) { - return sym; - } + + // reduced from real-world code + private <@TestAccumulation() T extends Symbol> T getMember(Class type, boolean b) { + if (b) { + T sym = getMember(type, !b); + if (sym != null && sym.isStatic()) { + return sym; + } + } else { + T sym = getMember(type, b); + if (sym != null) { + return sym; + } + } + return null; } - return null; - } } diff --git a/framework/tests/accumulation/Not.java b/framework/tests/accumulation/Not.java index 373f074cc1d..26e04a8b849 100644 --- a/framework/tests/accumulation/Not.java +++ b/framework/tests/accumulation/Not.java @@ -2,74 +2,74 @@ public class Not { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - void notA(@TestAccumulationPredicate("!a") Foo this) {} + void notA(@TestAccumulationPredicate("!a") Foo this) {} - void notB(@TestAccumulationPredicate("!b") Foo this) {} - } + void notB(@TestAccumulationPredicate("!b") Foo this) {} + } - void test1(Foo f) { - f.notA(); - f.notB(); - } + void test1(Foo f) { + f.notA(); + f.notB(); + } - void test2(Foo f) { - f.c(); - f.notA(); - f.notB(); - } + void test2(Foo f) { + f.c(); + f.notA(); + f.notB(); + } - void test3(Foo f) { - f.a(); - // :: error: method.invocation.invalid - f.notA(); - f.notB(); - } + void test3(Foo f) { + f.a(); + // :: error: method.invocation.invalid + f.notA(); + f.notB(); + } - void test4(Foo f) { - f.b(); - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test4(Foo f) { + f.b(); + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.notA(); - // :: error: method.invocation.invalid - f.notB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - // DEMONSTRATION OF "UNSOUNDNESS" - f.notA(); - } + void test6(Foo f) { + callA(f); + // DEMONSTRATION OF "UNSOUNDNESS" + f.notA(); + } - void test7(@TestAccumulation("a") Foo f) { - // :: error: method.invocation.invalid - f.notA(); - } + void test7(@TestAccumulation("a") Foo f) { + // :: error: method.invocation.invalid + f.notA(); + } - void test8(Foo f, boolean test) { - if (test) { - f.a(); - } else { - f.b(); + void test8(Foo f, boolean test) { + if (test) { + f.a(); + } else { + f.b(); + } + // DEMONSTRATION OF "UNSOUNDNESS" + f.notA(); } - // DEMONSTRATION OF "UNSOUNDNESS" - f.notA(); - } } diff --git a/framework/tests/accumulation/ObjectConstructionIssue20.java b/framework/tests/accumulation/ObjectConstructionIssue20.java index ee7d5e8e057..b7ff7e0fcf6 100644 --- a/framework/tests/accumulation/ObjectConstructionIssue20.java +++ b/framework/tests/accumulation/ObjectConstructionIssue20.java @@ -4,33 +4,33 @@ public class ObjectConstructionIssue20 { - private boolean enableProtoAnnotations; - - @SuppressWarnings({"unchecked"}) - private T getProtoExtension( - E element, GeneratedExtension extension) { - // Use this method as the chokepoint for all field annotations processing, so we can - // toggle on/off annotations processing in one place. - if (!enableProtoAnnotations) { - return null; + private boolean enableProtoAnnotations; + + @SuppressWarnings({"unchecked"}) + private T getProtoExtension( + E element, GeneratedExtension extension) { + // Use this method as the chokepoint for all field annotations processing, so we can + // toggle on/off annotations processing in one place. + if (!enableProtoAnnotations) { + return null; + } + return (T) element.getOptionFields().get(extension.getDescriptor()); } - return (T) element.getOptionFields().get(extension.getDescriptor()); - } - // stubs of relevant classes - private class Message {} + // stubs of relevant classes + private class Message {} - private class ProtoElement { - public Map getOptionFields() { - return null; + private class ProtoElement { + public Map getOptionFields() { + return null; + } } - } - private class FieldDescriptor {} + private class FieldDescriptor {} - private class GeneratedExtension { - public FieldDescriptor getDescriptor() { - return null; + private class GeneratedExtension { + public FieldDescriptor getDescriptor() { + return null; + } } - } } diff --git a/framework/tests/accumulation/Parens.java b/framework/tests/accumulation/Parens.java index 668e65a6272..2e896cf9d2f 100644 --- a/framework/tests/accumulation/Parens.java +++ b/framework/tests/accumulation/Parens.java @@ -1,5 +1,5 @@ public class Parens { - public synchronized void incrementPushed(long[] pushed, int operationType) { - ++(pushed[operationType]); - } + public synchronized void incrementPushed(long[] pushed, int operationType) { + ++(pushed[operationType]); + } } diff --git a/framework/tests/accumulation/Predicates.java b/framework/tests/accumulation/Predicates.java index 3b290a2f608..1aabe26775e 100644 --- a/framework/tests/accumulation/Predicates.java +++ b/framework/tests/accumulation/Predicates.java @@ -2,606 +2,606 @@ public class Predicates { - void testOr1() { - MyClass m1 = new MyClass(); + void testOr1() { + MyClass m1 = new MyClass(); - // :: error: method.invocation.invalid - m1.c(); - } - - void testOr2() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.c(); - } - - void testOr3() { - MyClass m1 = new MyClass(); - - m1.b(); - m1.c(); - } - - void testAnd1() { - MyClass m1 = new MyClass(); - - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd2() { - MyClass m1 = new MyClass(); - - m1.a(); - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd3() { - MyClass m1 = new MyClass(); - - m1.b(); - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd4() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.c(); - // :: error: method.invocation.invalid - m1.d(); - } - - void testAnd5() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.b(); - m1.d(); - } - - void testAnd6() { - MyClass m1 = new MyClass(); - - m1.a(); - m1.b(); - m1.c(); - m1.d(); - } - - void testAndOr1() { - MyClass m1 = new MyClass(); - - // :: error: method.invocation.invalid - m1.e(); - } - - void testAndOr2() { - MyClass m1 = new MyClass(); + // :: error: method.invocation.invalid + m1.c(); + } - m1.a(); - m1.e(); - } + void testOr2() { + MyClass m1 = new MyClass(); - void testAndOr3() { - MyClass m1 = new MyClass(); + m1.a(); + m1.c(); + } - m1.b(); - // :: error: method.invocation.invalid - m1.e(); - } + void testOr3() { + MyClass m1 = new MyClass(); - void testAndOr4() { - MyClass m1 = new MyClass(); + m1.b(); + m1.c(); + } - m1.b(); - m1.c(); - m1.e(); - } + void testAnd1() { + MyClass m1 = new MyClass(); - void testAndOr5() { - MyClass m1 = new MyClass(); + // :: error: method.invocation.invalid + m1.d(); + } - m1.a(); - m1.b(); - m1.c(); - m1.d(); - m1.e(); - } + void testAnd2() { + MyClass m1 = new MyClass(); - void testPrecedence1() { - MyClass m1 = new MyClass(); + m1.a(); + // :: error: method.invocation.invalid + m1.d(); + } - // :: error: method.invocation.invalid - m1.f(); - } + void testAnd3() { + MyClass m1 = new MyClass(); - void testPrecedence2() { - MyClass m1 = new MyClass(); + m1.b(); + // :: error: method.invocation.invalid + m1.d(); + } - m1.a(); - // :: error: method.invocation.invalid - m1.f(); - } + void testAnd4() { + MyClass m1 = new MyClass(); - void testPrecedence3() { - MyClass m1 = new MyClass(); + m1.a(); + m1.c(); + // :: error: method.invocation.invalid + m1.d(); + } - m1.b(); - // :: error: method.invocation.invalid - m1.f(); - } + void testAnd5() { + MyClass m1 = new MyClass(); - void testPrecedence4() { - MyClass m1 = new MyClass(); + m1.a(); + m1.b(); + m1.d(); + } - m1.a(); - m1.b(); - m1.f(); - } + void testAnd6() { + MyClass m1 = new MyClass(); - void testPrecedence5() { - MyClass m1 = new MyClass(); + m1.a(); + m1.b(); + m1.c(); + m1.d(); + } - m1.a(); - m1.c(); - m1.f(); - } + void testAndOr1() { + MyClass m1 = new MyClass(); - void testPrecedence6() { - MyClass m1 = new MyClass(); + // :: error: method.invocation.invalid + m1.e(); + } - m1.b(); - m1.c(); - m1.f(); - } + void testAndOr2() { + MyClass m1 = new MyClass(); - private static class MyClass { + m1.a(); + m1.e(); + } - @TestAccumulation("a") MyClass cmA; + void testAndOr3() { + MyClass m1 = new MyClass(); - @TestAccumulationPredicate("a") MyClass cmpA; + m1.b(); + // :: error: method.invocation.invalid + m1.e(); + } - @TestAccumulation({"a", "b"}) MyClass aB; + void testAndOr4() { + MyClass m1 = new MyClass(); - @TestAccumulationPredicate("a || b") MyClass aOrB; + m1.b(); + m1.c(); + m1.e(); + } - @TestAccumulationPredicate("a && b") MyClass aAndB; + void testAndOr5() { + MyClass m1 = new MyClass(); - @TestAccumulationPredicate("a || b && c") MyClass bAndCOrA; + m1.a(); + m1.b(); + m1.c(); + m1.d(); + m1.e(); + } - @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParens; + void testPrecedence1() { + MyClass m1 = new MyClass(); - @TestAccumulationPredicate("a && b || c") MyClass aAndBOrC; + // :: error: method.invocation.invalid + m1.f(); + } - @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParens; + void testPrecedence2() { + MyClass m1 = new MyClass(); - @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndC; + m1.a(); + // :: error: method.invocation.invalid + m1.f(); + } - @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndA; + void testPrecedence3() { + MyClass m1 = new MyClass(); - @TestAccumulationPredicate("b && c") MyClass bAndC; + m1.b(); + // :: error: method.invocation.invalid + m1.f(); + } - @TestAccumulationPredicate("(b && c)") MyClass bAndCParens; + void testPrecedence4() { + MyClass m1 = new MyClass(); - void a() {} + m1.a(); + m1.b(); + m1.f(); + } - void b() {} + void testPrecedence5() { + MyClass m1 = new MyClass(); - void c(@TestAccumulationPredicate("a || b") MyClass this) {} + m1.a(); + m1.c(); + m1.f(); + } - void d(@TestAccumulationPredicate("a && b") MyClass this) {} + void testPrecedence6() { + MyClass m1 = new MyClass(); - void e(@TestAccumulationPredicate("a || (b && c)") MyClass this) {} + m1.b(); + m1.c(); + m1.f(); + } - void f(@TestAccumulationPredicate("a && b || c") MyClass this) {} + private static class MyClass { - static void testAssignability1(@TestAccumulationPredicate("a || b") MyClass cAble) { - cAble.c(); - // :: error: method.invocation.invalid - cAble.d(); - // :: error: method.invocation.invalid - cAble.e(); - // :: error: method.invocation.invalid - cAble.f(); - } + @TestAccumulation("a") MyClass cmA; - static void testAssignability2(@TestAccumulationPredicate("a && b") MyClass dAble) { - // These calls would work if subtyping between predicates was by implication. They issue - // errors, because it is not. - // :: error: method.invocation.invalid - dAble.c(); - dAble.d(); - // :: error: method.invocation.invalid - dAble.e(); - // :: error: method.invocation.invalid - dAble.f(); - } + @TestAccumulationPredicate("a") MyClass cmpA; - void testAllAssignability() { - - @TestAccumulation("a") MyClass cmALocal; - @TestAccumulationPredicate("a") MyClass cmpALocal; - @TestAccumulationPredicate("a || b") MyClass aOrBLocal; - @TestAccumulation({"a", "b"}) MyClass aBLocal; - @TestAccumulationPredicate("a && b") MyClass aAndBLocal; - @TestAccumulationPredicate("a || b && c") MyClass bAndCOrALocal; - @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; - @TestAccumulationPredicate("a && b || c") MyClass aAndBOrCLocal; - @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; - @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndCLocal; - @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndALocal; - @TestAccumulationPredicate("b && c") MyClass bAndCLocal; - @TestAccumulationPredicate("(b && c)") MyClass bAndCParensLocal; - - cmALocal = cmA; - cmALocal = cmpA; - // :: error: assignment.type.incompatible - cmALocal = aOrB; - cmALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = aAndB; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmALocal = bAndC; - // :: error: assignment.type.incompatible - cmALocal = bAndCParens; - - cmpALocal = cmA; - cmpALocal = cmpA; - // :: error: assignment.type.incompatible - cmpALocal = aOrB; - cmpALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = aAndB; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrA; - // :: error: assignment.type.incompatible - cmpALocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrC; - // :: error: assignment.type.incompatible - cmpALocal = aAndBOrCParens; - // :: error: assignment.type.incompatible - cmpALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - cmpALocal = bOrCAndA; - // :: error: assignment.type.incompatible - cmpALocal = bAndC; - // :: error: assignment.type.incompatible - cmpALocal = bAndCParens; - - aOrBLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = cmpA; - aOrBLocal = aOrB; - aOrBLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCOrAParens; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrC; - // :: error: assignment.type.incompatible - aOrBLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aBLocal = cmA; - // :: error: (assignment.type.incompatible) - aBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aBLocal = aOrB; - aBLocal = aB; - aBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aBLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrB; - aAndBLocal = aB; - aAndBLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aAndBLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - aAndBLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndC; - // :: error: (assignment.type.incompatible) - aAndBLocal = bAndCParens; - - bAndCOrALocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aOrB; - bAndCOrALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aAndB; - bAndCOrALocal = bAndCOrA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrALocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrALocal = bAndCParens; - - bAndCOrAParensLocal = cmA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aOrB; - bAndCOrAParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aAndB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCOrA; - bAndCOrAParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCOrAParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCOrAParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = aOrB; - aAndBOrCLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCLocal = bAndCOrAParens; - aAndBOrCLocal = aAndBOrC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = aOrB; - aAndBOrCParensLocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aAndBOrCParensLocal = bAndCOrAParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aAndBOrC; - aAndBOrCParensLocal = aAndBOrCParens; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = aOrBAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aAndBOrCParensLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = aAndBOrCParens; - aOrBAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - aOrBAndCLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - aOrBAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = cmpA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrB; - bOrCAndALocal = aB; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bOrCAndALocal = aAndB; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = aOrBAndC; - bOrCAndALocal = bOrCAndA; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndC; - // :: error: (assignment.type.incompatible) - bOrCAndALocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCLocal = bOrCAndA; - bAndCLocal = bAndC; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCLocal = bAndCParens; - - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = cmpA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndB; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrA; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bAndCOrAParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aAndBOrCParens; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = aOrBAndC; - // :: error: (assignment.type.incompatible) - bAndCParensLocal = bOrCAndA; - // The next line would not fail if predicate subtyping was decided by implication. - // :: error: assignment.type.incompatible - bAndCParensLocal = bAndC; - bAndCParensLocal = bAndCParens; + @TestAccumulation({"a", "b"}) MyClass aB; + + @TestAccumulationPredicate("a || b") MyClass aOrB; + + @TestAccumulationPredicate("a && b") MyClass aAndB; + + @TestAccumulationPredicate("a || b && c") MyClass bAndCOrA; + + @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParens; + + @TestAccumulationPredicate("a && b || c") MyClass aAndBOrC; + + @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParens; + + @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndC; + + @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndA; + + @TestAccumulationPredicate("b && c") MyClass bAndC; + + @TestAccumulationPredicate("(b && c)") MyClass bAndCParens; + + void a() {} + + void b() {} + + void c(@TestAccumulationPredicate("a || b") MyClass this) {} + + void d(@TestAccumulationPredicate("a && b") MyClass this) {} + + void e(@TestAccumulationPredicate("a || (b && c)") MyClass this) {} + + void f(@TestAccumulationPredicate("a && b || c") MyClass this) {} + + static void testAssignability1(@TestAccumulationPredicate("a || b") MyClass cAble) { + cAble.c(); + // :: error: method.invocation.invalid + cAble.d(); + // :: error: method.invocation.invalid + cAble.e(); + // :: error: method.invocation.invalid + cAble.f(); + } + + static void testAssignability2(@TestAccumulationPredicate("a && b") MyClass dAble) { + // These calls would work if subtyping between predicates was by implication. They issue + // errors, because it is not. + // :: error: method.invocation.invalid + dAble.c(); + dAble.d(); + // :: error: method.invocation.invalid + dAble.e(); + // :: error: method.invocation.invalid + dAble.f(); + } + + void testAllAssignability() { + + @TestAccumulation("a") MyClass cmALocal; + @TestAccumulationPredicate("a") MyClass cmpALocal; + @TestAccumulationPredicate("a || b") MyClass aOrBLocal; + @TestAccumulation({"a", "b"}) MyClass aBLocal; + @TestAccumulationPredicate("a && b") MyClass aAndBLocal; + @TestAccumulationPredicate("a || b && c") MyClass bAndCOrALocal; + @TestAccumulationPredicate("a || (b && c)") MyClass bAndCOrAParensLocal; + @TestAccumulationPredicate("a && b || c") MyClass aAndBOrCLocal; + @TestAccumulationPredicate("(a && b) || c") MyClass aAndBOrCParensLocal; + @TestAccumulationPredicate("(a || b) && c") MyClass aOrBAndCLocal; + @TestAccumulationPredicate("a && (b || c)") MyClass bOrCAndALocal; + @TestAccumulationPredicate("b && c") MyClass bAndCLocal; + @TestAccumulationPredicate("(b && c)") MyClass bAndCParensLocal; + + cmALocal = cmA; + cmALocal = cmpA; + // :: error: assignment.type.incompatible + cmALocal = aOrB; + cmALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = aAndB; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmALocal = bAndC; + // :: error: assignment.type.incompatible + cmALocal = bAndCParens; + + cmpALocal = cmA; + cmpALocal = cmpA; + // :: error: assignment.type.incompatible + cmpALocal = aOrB; + cmpALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = aAndB; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmpALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmpALocal = bAndC; + // :: error: assignment.type.incompatible + cmpALocal = bAndCParens; + + aOrBLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = cmpA; + aOrBLocal = aOrB; + aOrBLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrC; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aBLocal = cmA; + // :: error: (assignment.type.incompatible) + aBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aBLocal = aOrB; + aBLocal = aB; + aBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrB; + aAndBLocal = aB; + aAndBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCParens; + + bAndCOrALocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aOrB; + bAndCOrALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aAndB; + bAndCOrALocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCParens; + + bAndCOrAParensLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aOrB; + bAndCOrAParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCOrA; + bAndCOrAParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = aOrB; + aAndBOrCLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrAParens; + aAndBOrCLocal = aAndBOrC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = aOrB; + aAndBOrCParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrAParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndBOrC; + aAndBOrCParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrCParens; + aOrBAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmpA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrB; + bOrCAndALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bOrCAndALocal = aAndB; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrBAndC; + bOrCAndALocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCLocal = bOrCAndA; + bAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCParensLocal = bAndC; + bAndCParensLocal = bAndCParens; + } } - } } diff --git a/framework/tests/accumulation/SimpleFluent.java b/framework/tests/accumulation/SimpleFluent.java index bad36328ea2..b031c53283b 100644 --- a/framework/tests/accumulation/SimpleFluent.java +++ b/framework/tests/accumulation/SimpleFluent.java @@ -5,112 +5,111 @@ /* Simple inference of a fluent builder. */ public class SimpleFluent { - SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } + SimpleFluent build(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } + + @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { + return this; + } + + @This SimpleFluent a() { + return this; + } + + @This SimpleFluent b() { + return this; + } + + // intentionally does not have an @This annotation + SimpleFluent c() { + return this; + } + + static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { + s.a().b().build(); + } + + static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { + s.a() + // :: error: method.invocation.invalid + .build(); + } + + static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { + s.a().b() + .c() + // :: error: method.invocation.invalid + .build(); + } + + static void mixFluentAndNonFluent(SimpleFluent s1) { + s1.a().b(); + s1.build(); + } + + static void mixFluentAndNonFluentWrong(SimpleFluent s) { + s.a(); // .b() + // :: error: method.invocation.invalid + s.build(); + } - @This SimpleFluent build2(@TestAccumulation({"a", "b"}) SimpleFluent this) { - return this; - } + static void fluentLoop(SimpleFluent t) { + SimpleFluent s = t.a(); + int i = 10; + while (i > 0) { + // :: error: method.invocation.invalid + s.b().build(); + i--; + s = new SimpleFluent(); + } + } - @This SimpleFluent a() { - return this; - } + static void m1(SimpleFluent s) { + s.c().a().b().build(); + } + + static void m2(SimpleFluent s) { + s.c().a().b(); + // :: error: method.invocation.invalid + s.c().build(); + } - @This SimpleFluent b() { - return this; - } + static void m3(SimpleFluent s) { + s.c().a().b().build(); + // :: error: method.invocation.invalid + s.c().a().build(); + } - // intentionally does not have an @This annotation - SimpleFluent c() { - return this; - } + static void m4(SimpleFluent s) { + s.c().a().b().build2().build(); + } - static void doStuffCorrect(@TestAccumulation({"a", "b"}) SimpleFluent s) { - s.a().b().build(); - } + static void m5(SimpleFluent s) { + s.a().c(); + s.b().build(); + } - static void doStuffWrong(@TestAccumulation({"a"}) SimpleFluent s) { - s.a() + static void m6(SimpleFluent s) { // :: error: method.invocation.invalid - .build(); - } + s.a().c().b().build(); + } - static void noReturnsReceiverAnno(@TestAccumulation({"a", "b"}) SimpleFluent s) { - s.a() - .b() - .c() + static void m7(SimpleFluent s) { + // :: error: method.invocation.invalid + s.a().b().build().c().b().build(); + } + + static void m8(SimpleFluent s) { + // :: error: method.invocation.invalid + s.a().build().c().a().b().build(); // :: error: method.invocation.invalid - .build(); - } - - static void mixFluentAndNonFluent(SimpleFluent s1) { - s1.a().b(); - s1.build(); - } - - static void mixFluentAndNonFluentWrong(SimpleFluent s) { - s.a(); // .b() - // :: error: method.invocation.invalid - s.build(); - } - - static void fluentLoop(SimpleFluent t) { - SimpleFluent s = t.a(); - int i = 10; - while (i > 0) { - // :: error: method.invocation.invalid - s.b().build(); - i--; - s = new SimpleFluent(); - } - } - - static void m1(SimpleFluent s) { - s.c().a().b().build(); - } - - static void m2(SimpleFluent s) { - s.c().a().b(); - // :: error: method.invocation.invalid - s.c().build(); - } - - static void m3(SimpleFluent s) { - s.c().a().b().build(); - // :: error: method.invocation.invalid - s.c().a().build(); - } - - static void m4(SimpleFluent s) { - s.c().a().b().build2().build(); - } - - static void m5(SimpleFluent s) { - s.a().c(); - s.b().build(); - } - - static void m6(SimpleFluent s) { - // :: error: method.invocation.invalid - s.a().c().b().build(); - } - - static void m7(SimpleFluent s) { - // :: error: method.invocation.invalid - s.a().b().build().c().b().build(); - } - - static void m8(SimpleFluent s) { - // :: error: method.invocation.invalid - s.a().build().c().a().b().build(); - // :: error: method.invocation.invalid - s.build(); - } - - static void m9() { - new SimpleFluent().a().b().build(); - // :: error: method.invocation.invalid - new SimpleFluent().a().build(); - } + s.build(); + } + + static void m9() { + new SimpleFluent().a().b().build(); + // :: error: method.invocation.invalid + new SimpleFluent().a().build(); + } } diff --git a/framework/tests/accumulation/SimpleInference.java b/framework/tests/accumulation/SimpleInference.java index 9fabe3e3b6d..698c238e44c 100644 --- a/framework/tests/accumulation/SimpleInference.java +++ b/framework/tests/accumulation/SimpleInference.java @@ -1,37 +1,37 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SimpleInference { - void build(@TestAccumulation({"a"}) SimpleInference this) {} - - void doublebuild(@TestAccumulation({"a", "b"}) SimpleInference this) {} - - void a() {} - - void b() {} - - static void doStuffCorrect() { - SimpleInference s = new SimpleInference(); - s.a(); - s.build(); - } - - static void doStuffCorrect2() { - SimpleInference s = new SimpleInference(); - s.a(); - s.b(); - s.doublebuild(); - } - - static void doStuffWrong() { - SimpleInference s = new SimpleInference(); - // :: error: method.invocation.invalid - s.build(); - } - - static void doStuffWrong2() { - SimpleInference s = new SimpleInference(); - s.a(); - // :: error: method.invocation.invalid - s.doublebuild(); - } + void build(@TestAccumulation({"a"}) SimpleInference this) {} + + void doublebuild(@TestAccumulation({"a", "b"}) SimpleInference this) {} + + void a() {} + + void b() {} + + static void doStuffCorrect() { + SimpleInference s = new SimpleInference(); + s.a(); + s.build(); + } + + static void doStuffCorrect2() { + SimpleInference s = new SimpleInference(); + s.a(); + s.b(); + s.doublebuild(); + } + + static void doStuffWrong() { + SimpleInference s = new SimpleInference(); + // :: error: method.invocation.invalid + s.build(); + } + + static void doStuffWrong2() { + SimpleInference s = new SimpleInference(); + s.a(); + // :: error: method.invocation.invalid + s.doublebuild(); + } } diff --git a/framework/tests/accumulation/SimpleInferenceMerge.java b/framework/tests/accumulation/SimpleInferenceMerge.java index a74765d3ac1..de9f93e4c80 100644 --- a/framework/tests/accumulation/SimpleInferenceMerge.java +++ b/framework/tests/accumulation/SimpleInferenceMerge.java @@ -1,37 +1,37 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SimpleInferenceMerge { - void build(@TestAccumulation({"a", "b"}) SimpleInferenceMerge this) {} + void build(@TestAccumulation({"a", "b"}) SimpleInferenceMerge this) {} - void a() {} + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - static void doStuffCorrectMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.a(); - s.c(); + static void doStuffCorrectMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.a(); + s.c(); + } + s.build(); } - s.build(); - } - static void doStuffWrongMerge(boolean b) { - SimpleInferenceMerge s = new SimpleInferenceMerge(); - if (b) { - s.a(); - s.b(); - } else { - s.b(); - s.c(); + static void doStuffWrongMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.c(); + } + // :: error: method.invocation.invalid + s.build(); } - // :: error: method.invocation.invalid - s.build(); - } } diff --git a/framework/tests/accumulation/SimplePolymorphic.java b/framework/tests/accumulation/SimplePolymorphic.java index 7d681d90f3c..fb48f988480 100644 --- a/framework/tests/accumulation/SimplePolymorphic.java +++ b/framework/tests/accumulation/SimplePolymorphic.java @@ -3,17 +3,17 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SimplePolymorphic { - @PolyTestAccumulation Object id(@PolyTestAccumulation Object obj) { - return obj; - } + @PolyTestAccumulation Object id(@PolyTestAccumulation Object obj) { + return obj; + } - @TestAccumulation("foo") Object usePoly(@TestAccumulation("foo") Object obj) { - return id(obj); - } + @TestAccumulation("foo") Object usePoly(@TestAccumulation("foo") Object obj) { + return id(obj); + } - // Check that polymorphic supertype with accumulator type doesn't cause a crash. - void noCrashOnPolySuper(@TestAccumulation("foo") Object obj) { - // :: error: assignment.type.incompatible - @PolyTestAccumulation Object obj2 = obj; - } + // Check that polymorphic supertype with accumulator type doesn't cause a crash. + void noCrashOnPolySuper(@TestAccumulation("foo") Object obj) { + // :: error: assignment.type.incompatible + @PolyTestAccumulation Object obj2 = obj; + } } diff --git a/framework/tests/accumulation/SmallPredicate.java b/framework/tests/accumulation/SmallPredicate.java index 39f1577a63f..d0e03a248f7 100644 --- a/framework/tests/accumulation/SmallPredicate.java +++ b/framework/tests/accumulation/SmallPredicate.java @@ -3,16 +3,16 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class SmallPredicate { - void a() {} + void a() {} - void b() {} + void b() {} - void d(@TestAccumulationPredicate("a && b") SmallPredicate this) {} + void d(@TestAccumulationPredicate("a && b") SmallPredicate this) {} - static void test(SmallPredicate smallPredicate) { - smallPredicate.a(); - smallPredicate.b(); - @TestAccumulation({"a", "b"}) SmallPredicate p2 = smallPredicate; - smallPredicate.d(); - } + static void test(SmallPredicate smallPredicate) { + smallPredicate.a(); + smallPredicate.b(); + @TestAccumulation({"a", "b"}) SmallPredicate p2 = smallPredicate; + smallPredicate.d(); + } } diff --git a/framework/tests/accumulation/Subtyping.java b/framework/tests/accumulation/Subtyping.java index 99ca343a1c7..a723e742040 100644 --- a/framework/tests/accumulation/Subtyping.java +++ b/framework/tests/accumulation/Subtyping.java @@ -4,70 +4,70 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class Subtyping { - void top(@TestAccumulation() Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") - // :: error: assignment.type.incompatible - Object o3 = o1; - @TestAccumulation("bar") - // :: error: assignment.type.incompatible - Object o4 = o1; - @TestAccumulation({"foo", "bar"}) - // :: error: assignment.type.incompatible - Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void top(@TestAccumulation() Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") + // :: error: assignment.type.incompatible + Object o3 = o1; + @TestAccumulation("bar") + // :: error: assignment.type.incompatible + Object o4 = o1; + @TestAccumulation({"foo", "bar"}) + // :: error: assignment.type.incompatible + Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void foo(@TestAccumulation("foo") Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") - // :: error: assignment.type.incompatible - Object o4 = o1; - @TestAccumulation({"foo", "bar"}) - // :: error: assignment.type.incompatible - Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void foo(@TestAccumulation("foo") Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") + // :: error: assignment.type.incompatible + Object o4 = o1; + @TestAccumulation({"foo", "bar"}) + // :: error: assignment.type.incompatible + Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void bar(@TestAccumulation("bar") Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") - // :: error: assignment.type.incompatible - Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) - // :: error: assignment.type.incompatible - Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void bar(@TestAccumulation("bar") Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") + // :: error: assignment.type.incompatible + Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) + // :: error: assignment.type.incompatible + Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void foobar(@TestAccumulation({"foo", "bar"}) Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void foobar(@TestAccumulation({"foo", "bar"}) Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void barfoo(@TestAccumulation({"bar", "foo"}) Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) Object o5 = o1; - // :: error: assignment.type.incompatible - @TestAccumulationBottom Object o6 = o1; - } + void barfoo(@TestAccumulation({"bar", "foo"}) Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) Object o5 = o1; + // :: error: assignment.type.incompatible + @TestAccumulationBottom Object o6 = o1; + } - void bot(@TestAccumulationBottom Object o1) { - @TestAccumulation() Object o2 = o1; - @TestAccumulation("foo") Object o3 = o1; - @TestAccumulation("bar") Object o4 = o1; - @TestAccumulation({"foo", "bar"}) Object o5 = o1; - @TestAccumulationBottom Object o6 = o1; - } + void bot(@TestAccumulationBottom Object o1) { + @TestAccumulation() Object o2 = o1; + @TestAccumulation("foo") Object o3 = o1; + @TestAccumulation("bar") Object o4 = o1; + @TestAccumulation({"foo", "bar"}) Object o5 = o1; + @TestAccumulationBottom Object o6 = o1; + } } diff --git a/framework/tests/accumulation/UnparsablePredicate.java b/framework/tests/accumulation/UnparsablePredicate.java index 0d7227d081a..33f4a428650 100644 --- a/framework/tests/accumulation/UnparsablePredicate.java +++ b/framework/tests/accumulation/UnparsablePredicate.java @@ -2,49 +2,49 @@ public class UnparsablePredicate { - // :: error: predicate.invalid - void unclosedOpen(@TestAccumulationPredicate("(foo && bar") Object x) {} + // :: error: predicate.invalid + void unclosedOpen(@TestAccumulationPredicate("(foo && bar") Object x) {} - // :: error: predicate.invalid - void unopenedClose(@TestAccumulationPredicate("foo || bar)") Object x) {} + // :: error: predicate.invalid + void unopenedClose(@TestAccumulationPredicate("foo || bar)") Object x) {} - // :: error: predicate.invalid - void badKeywords1(@TestAccumulationPredicate("foo OR bar") Object x) {} + // :: error: predicate.invalid + void badKeywords1(@TestAccumulationPredicate("foo OR bar") Object x) {} - // :: error: predicate.invalid - void badKeywords2(@TestAccumulationPredicate("foo AND bar") Object x) {} + // :: error: predicate.invalid + void badKeywords2(@TestAccumulationPredicate("foo AND bar") Object x) {} - // These tests check that valid Java identifiers don't cause problems - // when evaluating predicates. Examples of identifiers from - // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 + // These tests check that valid Java identifiers don't cause problems + // when evaluating predicates. Examples of identifiers from + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 - void jls0Example(@TestAccumulationPredicate("String") Object x) {} + void jls0Example(@TestAccumulationPredicate("String") Object x) {} - void callJls0Example(@TestAccumulation("String") Object y) { - jls0Example(y); - } + void callJls0Example(@TestAccumulation("String") Object y) { + jls0Example(y); + } - void jls1Example(@TestAccumulationPredicate("i3") Object x) {} + void jls1Example(@TestAccumulationPredicate("i3") Object x) {} - void callJls1Example(@TestAccumulation("i3") Object y) { - jls1Example(y); - } + void callJls1Example(@TestAccumulation("i3") Object y) { + jls1Example(y); + } - void jls2Example(@TestAccumulationPredicate("αρετη") Object x) {} + void jls2Example(@TestAccumulationPredicate("αρετη") Object x) {} - void callJls2Example(@TestAccumulation("αρετη") Object y) { - jls2Example(y); - } + void callJls2Example(@TestAccumulation("αρετη") Object y) { + jls2Example(y); + } - void jls3Example(@TestAccumulationPredicate("MAX_VALUE") Object x) {} + void jls3Example(@TestAccumulationPredicate("MAX_VALUE") Object x) {} - void callJls3Example(@TestAccumulation("MAX_VALUE") Object y) { - jls3Example(y); - } + void callJls3Example(@TestAccumulation("MAX_VALUE") Object y) { + jls3Example(y); + } - void jls4Example(@TestAccumulationPredicate("isLetterOrDigit") Object x) {} + void jls4Example(@TestAccumulationPredicate("isLetterOrDigit") Object x) {} - void callJls4Example(@TestAccumulation("isLetterOrDigit") Object y) { - jls4Example(y); - } + void callJls4Example(@TestAccumulation("isLetterOrDigit") Object y) { + jls4Example(y); + } } diff --git a/framework/tests/accumulation/UseSimpleFluent.java b/framework/tests/accumulation/UseSimpleFluent.java index 20d12968369..ddaab38a260 100644 --- a/framework/tests/accumulation/UseSimpleFluent.java +++ b/framework/tests/accumulation/UseSimpleFluent.java @@ -1,9 +1,9 @@ import org.checkerframework.framework.testchecker.testaccumulation.qual.*; public class UseSimpleFluent { - static void req(@TestAccumulation({"a", "b"}) SimpleFluent s) {} + static void req(@TestAccumulation({"a", "b"}) SimpleFluent s) {} - static void test() { - req(new SimpleFluent().a().b()); - } + static void test() { + req(new SimpleFluent().a().b()); + } } diff --git a/framework/tests/accumulation/Xor.java b/framework/tests/accumulation/Xor.java index 804d3e389c3..24c9223c3c3 100644 --- a/framework/tests/accumulation/Xor.java +++ b/framework/tests/accumulation/Xor.java @@ -2,64 +2,64 @@ public class Xor { - class Foo { - void a() {} + class Foo { + void a() {} - void b() {} + void b() {} - void c() {} + void c() {} - // use a standard gate encoding for xor - void aXorB(@TestAccumulationPredicate("(a || b) && !(a && b)") Foo this) {} - } + // use a standard gate encoding for xor + void aXorB(@TestAccumulationPredicate("(a || b) && !(a && b)") Foo this) {} + } - void test1(Foo f) { - // :: error: method.invocation.invalid - f.aXorB(); - } + void test1(Foo f) { + // :: error: method.invocation.invalid + f.aXorB(); + } - void test2(Foo f) { - f.c(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test2(Foo f) { + f.c(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void test3(Foo f) { - f.a(); - f.aXorB(); - } + void test3(Foo f) { + f.a(); + f.aXorB(); + } - void test4(Foo f) { - f.b(); - f.aXorB(); - } + void test4(Foo f) { + f.b(); + f.aXorB(); + } - void test5(Foo f) { - f.a(); - f.b(); - // :: error: method.invocation.invalid - f.aXorB(); - } + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.aXorB(); + } - void callA(Foo f) { - f.a(); - } + void callA(Foo f) { + f.a(); + } - void test6(Foo f) { - callA(f); - f.b(); - // DEMONSTRATION OF "UNSOUNDNESS" - f.aXorB(); - } + void test6(Foo f) { + callA(f); + f.b(); + // DEMONSTRATION OF "UNSOUNDNESS" + f.aXorB(); + } - void test7(@TestAccumulation("a") Foo f) { - f.aXorB(); - } + void test7(@TestAccumulation("a") Foo f) { + f.aXorB(); + } - void test8(Foo f) { - callA(f); - // THIS IS AN UNAVOIDABLE FALSE POSITIVE - // :: error: method.invocation.invalid - f.aXorB(); - } + void test8(Foo f) { + callA(f); + // THIS IS AN UNAVOIDABLE FALSE POSITIVE + // :: error: method.invocation.invalid + f.aXorB(); + } } diff --git a/framework/tests/aggregate/AggregateBasicTest.java b/framework/tests/aggregate/AggregateBasicTest.java index 0d68191182a..a00d5b1442b 100644 --- a/framework/tests/aggregate/AggregateBasicTest.java +++ b/framework/tests/aggregate/AggregateBasicTest.java @@ -1,12 +1,12 @@ public class AggregateBasicTest { - // Random code just to make sure that the aggregate design pattern - // does not throw any exceptions. - Object field = new Object(); - String[] array = {"hello", "world"}; + // Random code just to make sure that the aggregate design pattern + // does not throw any exceptions. + Object field = new Object(); + String[] array = {"hello", "world"}; - void foo(int arg) { - field = array[0]; - field.toString(); - int a = 1 + arg; - } + void foo(int arg) { + field = array[0]; + field.toString(); + int a = 1 + arg; + } } diff --git a/framework/tests/aggregate/JavaErrorTest.java b/framework/tests/aggregate/JavaErrorTest.java index eb8776fa3e5..478fe489666 100644 --- a/framework/tests/aggregate/JavaErrorTest.java +++ b/framework/tests/aggregate/JavaErrorTest.java @@ -1,12 +1,12 @@ public class JavaErrorTest { - // Checking that if one checker finds a Java error - // and therefor does not set a root, then - // checkers relying on that checker should also not run. - // otherwise, it might access a subchecker whose "root" has not been set. - // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) - void foo() { - Object a; - // :: error: variable a is already defined in method foo() - Object a; - } + // Checking that if one checker finds a Java error + // and therefor does not set a root, then + // checkers relying on that checker should also not run. + // otherwise, it might access a subchecker whose "root" has not been set. + // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) + void foo() { + Object a; + // :: error: variable a is already defined in method foo() + Object a; + } } diff --git a/framework/tests/aggregate/MultiError.java b/framework/tests/aggregate/MultiError.java index 71e9dce705d..c7e577ec9f9 100644 --- a/framework/tests/aggregate/MultiError.java +++ b/framework/tests/aggregate/MultiError.java @@ -3,18 +3,18 @@ import org.checkerframework.common.value.qual.StringVal; public class MultiError { - // Testing that errors from multiple checkers are issued - // on the same compilation unit - // :: error: (unique.location.forbidden) - @Unique String[] array; + // Testing that errors from multiple checkers are issued + // on the same compilation unit + // :: error: (unique.location.forbidden) + @Unique String[] array; - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "goodbye"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "goodbye"; - @MethodVal( - className = "c", - methodName = "m", - params = {0, 0}) - // :: error: (invalid.methodval) - Object o; + @MethodVal( + className = "c", + methodName = "m", + params = {0, 0}) + // :: error: (invalid.methodval) + Object o; } diff --git a/framework/tests/aliasing/AliasingConstructorTest.java b/framework/tests/aliasing/AliasingConstructorTest.java index 28d1d580324..a8a0e7b29b3 100644 --- a/framework/tests/aliasing/AliasingConstructorTest.java +++ b/framework/tests/aliasing/AliasingConstructorTest.java @@ -2,22 +2,22 @@ public class AliasingConstructorTest { - public AliasingConstructorTest(@NonLeaked Object o) {} + public AliasingConstructorTest(@NonLeaked Object o) {} - // int and String parameters on the constructors below are used only - // to make a distinction among constructors. - public AliasingConstructorTest(@LeakedToResult Object o, int i) {} + // int and String parameters on the constructors below are used only + // to make a distinction among constructors. + public AliasingConstructorTest(@LeakedToResult Object o, int i) {} - public AliasingConstructorTest(Object o, String s) {} + public AliasingConstructorTest(Object o, String s) {} - public void annosInAliasingConstructorTest() { - @Unique Object o = new Object(); - new AliasingConstructorTest(o); - Object o2 = new Object(); - new AliasingConstructorTest(o2, 1); - AliasingConstructorTest ct = new AliasingConstructorTest(o2, 1); - @Unique Object o3 = new Object(); - // ::error: (unique.leaked) - new AliasingConstructorTest(o3, "someString"); - } + public void annosInAliasingConstructorTest() { + @Unique Object o = new Object(); + new AliasingConstructorTest(o); + Object o2 = new Object(); + new AliasingConstructorTest(o2, 1); + AliasingConstructorTest ct = new AliasingConstructorTest(o2, 1); + @Unique Object o3 = new Object(); + // ::error: (unique.leaked) + new AliasingConstructorTest(o3, "someString"); + } } diff --git a/framework/tests/aliasing/ArrayInitializerTest.java b/framework/tests/aliasing/ArrayInitializerTest.java index e63fb40ebd0..deddd1de048 100644 --- a/framework/tests/aliasing/ArrayInitializerTest.java +++ b/framework/tests/aliasing/ArrayInitializerTest.java @@ -2,17 +2,17 @@ public class ArrayInitializerTest { - void foo() { - @Unique Object o = new Object(); - // :: error: (unique.leaked) - Object[] ar = new Object[] {o}; + void foo() { + @Unique Object o = new Object(); + // :: error: (unique.leaked) + Object[] ar = new Object[] {o}; - @Unique Object o2 = new Object(); - // :: error: (unique.leaked) - Object @Unique [] ar2 = new Object[] {o2}; + @Unique Object o2 = new Object(); + // :: error: (unique.leaked) + Object @Unique [] ar2 = new Object[] {o2}; - Object[] arr = new Object[] {new Object()}; + Object[] arr = new Object[] {new Object()}; - Object @Unique [] arrr = new Object[2]; - } + Object @Unique [] arrr = new Object[2]; + } } diff --git a/framework/tests/aliasing/CatchTest.java b/framework/tests/aliasing/CatchTest.java index 5543ece86eb..3868f683ab0 100644 --- a/framework/tests/aliasing/CatchTest.java +++ b/framework/tests/aliasing/CatchTest.java @@ -2,16 +2,16 @@ public class CatchTest { - void foo() { - @Unique Exception exVar = new Exception(); - try { - // :: error: (unique.leaked) - throw exVar; + void foo() { + @Unique Exception exVar = new Exception(); + try { + // :: error: (unique.leaked) + throw exVar; - // :: error: (exception.parameter.invalid) - } catch (@Unique Exception e) { - // exVar and e points to the same object, therefore catch clauses - // are not allowed to have a @Unique parameter. + // :: error: (exception.parameter.invalid) + } catch (@Unique Exception e) { + // exVar and e points to the same object, therefore catch clauses + // are not allowed to have a @Unique parameter. + } } - } } diff --git a/framework/tests/aliasing/EnumTest.java b/framework/tests/aliasing/EnumTest.java index d4d0e1c59b2..9571cf085c5 100644 --- a/framework/tests/aliasing/EnumTest.java +++ b/framework/tests/aliasing/EnumTest.java @@ -1,4 +1,4 @@ public enum EnumTest { - val1, - val2 + val1, + val2 } diff --git a/framework/tests/aliasing/ExplicitAnnotationTest.java b/framework/tests/aliasing/ExplicitAnnotationTest.java index b45989654f5..a3ae0bf779a 100644 --- a/framework/tests/aliasing/ExplicitAnnotationTest.java +++ b/framework/tests/aliasing/ExplicitAnnotationTest.java @@ -1,15 +1,15 @@ import org.checkerframework.common.aliasing.qual.Unique; @Unique class UniqueData { - @SuppressWarnings("unique.leaked") - UniqueData() {} // All objects of UniqueData are now @Unique + @SuppressWarnings("unique.leaked") + UniqueData() {} // All objects of UniqueData are now @Unique } public class ExplicitAnnotationTest { - void check(UniqueData p) { // p is @Unique UniqueData Object - // :: error: (unique.leaked) - UniqueData y = p; // @Unique p is leaked - // :: error: (unique.leaked) - Object z = p; // @Unique p is leaked - } + void check(UniqueData p) { // p is @Unique UniqueData Object + // :: error: (unique.leaked) + UniqueData y = p; // @Unique p is leaked + // :: error: (unique.leaked) + Object z = p; // @Unique p is leaked + } } diff --git a/framework/tests/aliasing/ForbiddenUniqueTest.java b/framework/tests/aliasing/ForbiddenUniqueTest.java index f0294ecc962..61098335473 100644 --- a/framework/tests/aliasing/ForbiddenUniqueTest.java +++ b/framework/tests/aliasing/ForbiddenUniqueTest.java @@ -1,20 +1,21 @@ -import java.util.List; import org.checkerframework.common.aliasing.qual.Unique; -public class ForbiddenUniqueTest { +import java.util.List; - // :: error: (unique.location.forbidden) - @Unique int field; +public class ForbiddenUniqueTest { - void notAllowed() { // :: error: (unique.location.forbidden) - @Unique Object[] arr; - // :: error: (type.argument.type.incompatible) :: error: (unique.location.forbidden) - List<@Unique Object> list; - } + @Unique int field; + + void notAllowed() { + // :: error: (unique.location.forbidden) + @Unique Object[] arr; + // :: error: (type.argument.type.incompatible) :: error: (unique.location.forbidden) + List<@Unique Object> list; + } - void allowed() { - Object @Unique [] ar; - @Unique List l; - } + void allowed() { + Object @Unique [] ar; + @Unique List l; + } } diff --git a/framework/tests/aliasing/ReceiverParameterTest.java b/framework/tests/aliasing/ReceiverParameterTest.java index 940266d5b9d..4e19d392922 100644 --- a/framework/tests/aliasing/ReceiverParameterTest.java +++ b/framework/tests/aliasing/ReceiverParameterTest.java @@ -2,59 +2,59 @@ public class ReceiverParameterTest { - public @Unique ReceiverParameterTest() { - nonLeaked(); - // :: error: (unique.leaked) - mayLeak(); - } - - public @Unique ReceiverParameterTest(int i) { - leakedToResult(); - // :: error: (unique.leaked) - ReceiverParameterTest b = leakedToResult(); - } - - public @Unique ReceiverParameterTest(String s) {} - - void receiverTest() { - ReceiverParameterTest rec = new ReceiverParameterTest("s"); // @Unique - isUnique(rec); - rec.leakedToResult(); - isUnique(rec); - ReceiverParameterTest other = rec.leakedToResult(); - // :: error: (argument.type.incompatible) - isUnique(rec); - // :: error: (argument.type.incompatible) - isUnique(other); - } - - void stubFileReceiverTest() { - // StringBuffer append(String s) @LeakedToResult; - StringBuffer sb = new StringBuffer(); - isUnique(sb); - sb.append("something"); - isUnique(sb); - StringBuffer sb2 = sb.append("something"); - // :: error: (argument.type.incompatible) - isUnique(sb); - // :: error: (argument.type.incompatible) - isUnique(sb2); - } - - ReceiverParameterTest leakedToResult(@LeakedToResult ReceiverParameterTest this) { - return this; - } - - void nonLeaked(@NonLeaked ReceiverParameterTest this) {} - - void mayLeak() {} - - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique ReceiverParameterTest s) {} - - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique String s) {} - - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique StringBuffer s) {} + public @Unique ReceiverParameterTest() { + nonLeaked(); + // :: error: (unique.leaked) + mayLeak(); + } + + public @Unique ReceiverParameterTest(int i) { + leakedToResult(); + // :: error: (unique.leaked) + ReceiverParameterTest b = leakedToResult(); + } + + public @Unique ReceiverParameterTest(String s) {} + + void receiverTest() { + ReceiverParameterTest rec = new ReceiverParameterTest("s"); // @Unique + isUnique(rec); + rec.leakedToResult(); + isUnique(rec); + ReceiverParameterTest other = rec.leakedToResult(); + // :: error: (argument.type.incompatible) + isUnique(rec); + // :: error: (argument.type.incompatible) + isUnique(other); + } + + void stubFileReceiverTest() { + // StringBuffer append(String s) @LeakedToResult; + StringBuffer sb = new StringBuffer(); + isUnique(sb); + sb.append("something"); + isUnique(sb); + StringBuffer sb2 = sb.append("something"); + // :: error: (argument.type.incompatible) + isUnique(sb); + // :: error: (argument.type.incompatible) + isUnique(sb2); + } + + ReceiverParameterTest leakedToResult(@LeakedToResult ReceiverParameterTest this) { + return this; + } + + void nonLeaked(@NonLeaked ReceiverParameterTest this) {} + + void mayLeak() {} + + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique ReceiverParameterTest s) {} + + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique String s) {} + + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique StringBuffer s) {} } diff --git a/framework/tests/aliasing/SuperTest.java b/framework/tests/aliasing/SuperTest.java index 16086f41b0a..4d6c3f93537 100644 --- a/framework/tests/aliasing/SuperTest.java +++ b/framework/tests/aliasing/SuperTest.java @@ -1,22 +1,22 @@ import org.checkerframework.common.aliasing.qual.Unique; class A { - static A lastA; + static A lastA; - public A() { - lastA = this; - } + public A() { + lastA = this; + } - public @Unique A(String s) {} + public @Unique A(String s) {} } class B extends A { - public @Unique B() { - // :: error: (unique.leaked) - super(); // "this" is aliased to A.lastA. - } + public @Unique B() { + // :: error: (unique.leaked) + super(); // "this" is aliased to A.lastA. + } - public @Unique B(String s) { - super(s); // no aliases created - } + public @Unique B(String s) { + super(s); // no aliases created + } } diff --git a/framework/tests/aliasing/ThrowTest.java b/framework/tests/aliasing/ThrowTest.java index 8dad69e615f..18dd55edc9a 100644 --- a/framework/tests/aliasing/ThrowTest.java +++ b/framework/tests/aliasing/ThrowTest.java @@ -2,13 +2,13 @@ public class ThrowTest { - void foo() throws Exception { - @Unique Exception e = new Exception(); - // :: error: (unique.leaked) - throw e; - } + void foo() throws Exception { + @Unique Exception e = new Exception(); + // :: error: (unique.leaked) + throw e; + } - void bar() throws Exception { - throw new Exception(); - } + void bar() throws Exception { + throw new Exception(); + } } diff --git a/framework/tests/aliasing/TypeRefinement.java b/framework/tests/aliasing/TypeRefinement.java index 03135df0289..40b8712c1d4 100644 --- a/framework/tests/aliasing/TypeRefinement.java +++ b/framework/tests/aliasing/TypeRefinement.java @@ -2,70 +2,70 @@ public class TypeRefinement { - /** - * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may - * lose its type refinement, before the LHS is type-refined. - * - *

          The RHS always loses its type refinement (it is widened to @MaybeAliased, and its declared - * type must have been @MaybeAliased) except in the following cases: - * - *

            - *
          1. The RHS is a fresh expression. - *
          2. The LHS is a @NonLeaked formal parameter and the RHS is an argument in a method call or - * constructor invocation. - *
          3. The LHS is a @LeakedToResult formal parameter, the RHS is an argument in a method call or - * constructor invocation, and the method's return value is discarded. - *
              - */ + /** + * Type refinement is treated in the usual way, except that at (pseudo-)assignments the RHS may + * lose its type refinement, before the LHS is type-refined. + * + *

              The RHS always loses its type refinement (it is widened to @MaybeAliased, and its declared + * type must have been @MaybeAliased) except in the following cases: + * + *

                + *
              1. The RHS is a fresh expression. + *
              2. The LHS is a @NonLeaked formal parameter and the RHS is an argument in a method call or + * constructor invocation. + *
              3. The LHS is a @LeakedToResult formal parameter, the RHS is an argument in a method call + * or constructor invocation, and the method's return value is discarded. + *
                  + */ - // Test cases for the Aliasing type refinement cases below. - // One method for each exception case. The usual case is tested in every method too. - // As annotated in stubfile.astub, String() has type @Unique @NonLeaked. + // Test cases for the Aliasing type refinement cases below. + // One method for each exception case. The usual case is tested in every method too. + // As annotated in stubfile.astub, String() has type @Unique @NonLeaked. - void rule1() { - String unique = new String(); - // unique is refined to @Unique here, according to the definition. - isUnique(unique); + void rule1() { + String unique = new String(); + // unique is refined to @Unique here, according to the definition. + isUnique(unique); - String notUnique = unique; // unique loses its refinement. + String notUnique = unique; // unique loses its refinement. - // :: error: (argument.type.incompatible) - isUnique(unique); - // :: error: (argument.type.incompatible) - isUnique(notUnique); - } + // :: error: (argument.type.incompatible) + isUnique(unique); + // :: error: (argument.type.incompatible) + isUnique(notUnique); + } - void rule2() { - String unique = new String(); + void rule2() { + String unique = new String(); - isUnique(unique); - nonLeaked(unique); - isUnique(unique); + isUnique(unique); + nonLeaked(unique); + isUnique(unique); - leaked(unique); - // :: error: (argument.type.incompatible) - isUnique(unique); - } + leaked(unique); + // :: error: (argument.type.incompatible) + isUnique(unique); + } - void rule3() { - String unique = new String(); - isUnique(unique); - leakedToResult(unique); - isUnique(unique); + void rule3() { + String unique = new String(); + isUnique(unique); + leakedToResult(unique); + isUnique(unique); - String notUnique = leakedToResult(unique); - // :: error: (argument.type.incompatible) - isUnique(unique); - } + String notUnique = leakedToResult(unique); + // :: error: (argument.type.incompatible) + isUnique(unique); + } - void nonLeaked(@NonLeaked String s) {} + void nonLeaked(@NonLeaked String s) {} - void leaked(String s) {} + void leaked(String s) {} - String leakedToResult(@LeakedToResult String s) { - return s; - } + String leakedToResult(@LeakedToResult String s) { + return s; + } - // @NonLeaked so it doesn't refine the type of the argument. - void isUnique(@NonLeaked @Unique String s) {} + // @NonLeaked so it doesn't refine the type of the argument. + void isUnique(@NonLeaked @Unique String s) {} } diff --git a/framework/tests/aliasing/UniqueAnnoTest.java b/framework/tests/aliasing/UniqueAnnoTest.java index 816ae0ebfa2..f324adae341 100644 --- a/framework/tests/aliasing/UniqueAnnoTest.java +++ b/framework/tests/aliasing/UniqueAnnoTest.java @@ -2,39 +2,39 @@ public class UniqueAnnoTest { - // @Unique constructor - public @Unique UniqueAnnoTest() {} - - // @Unique constructor leaking the "this" reference. - // Each unique.leaked error is a leak. - public @Unique UniqueAnnoTest(int i) { - notLeaked(this); - leakedToResult(this); - // :: error: (unique.leaked) - UniqueAnnoTest b = leakedToResult(this); - - UniqueAnnoTest other = new UniqueAnnoTest(); - // :: error: (unique.leaked) - other = this; - // :: error: (unique.leaked) - leaked(this); - // :: error: (unique.leaked) - leaked(other); // The receiver parameter is "this", so there is a leak. - } - - // Not @Unique constructor. No warnings. - public UniqueAnnoTest(int i1, int i2) { - UniqueAnnoTest other = new UniqueAnnoTest(); - other = this; - notLeaked(this); - } - - void leaked(UniqueAnnoTest a) {} - - void notLeaked(@NonLeaked UniqueAnnoTest this, @NonLeaked UniqueAnnoTest a) {} - - UniqueAnnoTest leakedToResult( - @LeakedToResult UniqueAnnoTest this, @LeakedToResult UniqueAnnoTest a) { - return a; - } + // @Unique constructor + public @Unique UniqueAnnoTest() {} + + // @Unique constructor leaking the "this" reference. + // Each unique.leaked error is a leak. + public @Unique UniqueAnnoTest(int i) { + notLeaked(this); + leakedToResult(this); + // :: error: (unique.leaked) + UniqueAnnoTest b = leakedToResult(this); + + UniqueAnnoTest other = new UniqueAnnoTest(); + // :: error: (unique.leaked) + other = this; + // :: error: (unique.leaked) + leaked(this); + // :: error: (unique.leaked) + leaked(other); // The receiver parameter is "this", so there is a leak. + } + + // Not @Unique constructor. No warnings. + public UniqueAnnoTest(int i1, int i2) { + UniqueAnnoTest other = new UniqueAnnoTest(); + other = this; + notLeaked(this); + } + + void leaked(UniqueAnnoTest a) {} + + void notLeaked(@NonLeaked UniqueAnnoTest this, @NonLeaked UniqueAnnoTest a) {} + + UniqueAnnoTest leakedToResult( + @LeakedToResult UniqueAnnoTest this, @LeakedToResult UniqueAnnoTest a) { + return a; + } } diff --git a/framework/tests/aliasing/UniqueConstructorTest.java b/framework/tests/aliasing/UniqueConstructorTest.java index 5cc3a63fe88..fc2a4b6d8fa 100644 --- a/framework/tests/aliasing/UniqueConstructorTest.java +++ b/framework/tests/aliasing/UniqueConstructorTest.java @@ -2,22 +2,22 @@ public class UniqueConstructorTest { - @Unique UniqueConstructorTest() { - // Does not raise unique.leaked error since the parent constructor (Object) is unique - } + @Unique UniqueConstructorTest() { + // Does not raise unique.leaked error since the parent constructor (Object) is unique + } - class ParentClass extends UniqueConstructorTest { + class ParentClass extends UniqueConstructorTest { - ParentClass() { - // Does not raise unique.leaked error since the parent constructor is unique + ParentClass() { + // Does not raise unique.leaked error since the parent constructor is unique + } } - } - class ChildUniqueClass extends ParentClass { + class ChildUniqueClass extends ParentClass { - // ::error: (unique.leaked) - @Unique ChildUniqueClass() { - // Raises unique.leaked error since the parent constructor is not unique + // ::error: (unique.leaked) + @Unique ChildUniqueClass() { + // Raises unique.leaked error since the parent constructor is not unique + } } - } } diff --git a/framework/tests/all-systems/Annotations.java b/framework/tests/all-systems/Annotations.java index da2404b48f4..67369607876 100644 --- a/framework/tests/all-systems/Annotations.java +++ b/framework/tests/all-systems/Annotations.java @@ -3,15 +3,15 @@ @interface Anno {} public class Annotations { - void takeAnnotation(Annotation a) {} + void takeAnnotation(Annotation a) {} - // test that a Tree works (source for Anno is in same compilation unit) - void takeTree(Anno a1) { - takeAnnotation(a1); - } + // test that a Tree works (source for Anno is in same compilation unit) + void takeTree(Anno a1) { + takeAnnotation(a1); + } - // test that an Element works (annotation only available from class file) - void takeElem(SuppressWarnings a2) { - takeAnnotation(a2); - } + // test that an Element works (annotation only available from class file) + void takeElem(SuppressWarnings a2) { + takeAnnotation(a2); + } } diff --git a/framework/tests/all-systems/AnonymousAndInnerClass.java b/framework/tests/all-systems/AnonymousAndInnerClass.java index 1aca2ccc8ed..5e124d0f85a 100644 --- a/framework/tests/all-systems/AnonymousAndInnerClass.java +++ b/framework/tests/all-systems/AnonymousAndInnerClass.java @@ -1,48 +1,48 @@ public class AnonymousAndInnerClass { - class MyInnerClass { - public MyInnerClass() {} - - public MyInnerClass(String s) {} - - public MyInnerClass(int... i) {} - - public MyInnerClass(AnonymousAndInnerClass c) {} - - public MyInnerClass(AnonymousAndInnerClass... o) {} - } - - static class MyClass { - public MyClass() {} - - public MyClass(String s) {} - - public MyClass(int... i) {} - } - - void test(AnonymousAndInnerClass outer, String tainted) { - new MyClass() {}; - new MyClass(tainted) {}; - new MyClass(1, 2, 3) {}; - new MyClass(1) {}; - new MyInnerClass() {}; - new MyInnerClass(tainted) {}; - new MyInnerClass(1) {}; - new MyInnerClass(1, 2, 3) {}; - new MyInnerClass(this) {}; - new MyInnerClass(this, this, this) {}; - this.new MyInnerClass() {}; - this.new MyInnerClass(tainted) {}; - this.new MyInnerClass(1) {}; - this.new MyInnerClass(1, 2, 3) {}; - this.new MyInnerClass(this) {}; - this.new MyInnerClass(outer, outer, outer) {}; - this.new MyInnerClass(this, this, this) {}; - outer.new MyInnerClass() {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(tainted) {}; - outer.new MyInnerClass(1) {}; - outer.new MyInnerClass(1, 2, 3) {}; - outer.new MyInnerClass(outer) {}; - outer.new MyInnerClass(outer, outer, outer) {}; - } + class MyInnerClass { + public MyInnerClass() {} + + public MyInnerClass(String s) {} + + public MyInnerClass(int... i) {} + + public MyInnerClass(AnonymousAndInnerClass c) {} + + public MyInnerClass(AnonymousAndInnerClass... o) {} + } + + static class MyClass { + public MyClass() {} + + public MyClass(String s) {} + + public MyClass(int... i) {} + } + + void test(AnonymousAndInnerClass outer, String tainted) { + new MyClass() {}; + new MyClass(tainted) {}; + new MyClass(1, 2, 3) {}; + new MyClass(1) {}; + new MyInnerClass() {}; + new MyInnerClass(tainted) {}; + new MyInnerClass(1) {}; + new MyInnerClass(1, 2, 3) {}; + new MyInnerClass(this) {}; + new MyInnerClass(this, this, this) {}; + this.new MyInnerClass() {}; + this.new MyInnerClass(tainted) {}; + this.new MyInnerClass(1) {}; + this.new MyInnerClass(1, 2, 3) {}; + this.new MyInnerClass(this) {}; + this.new MyInnerClass(outer, outer, outer) {}; + this.new MyInnerClass(this, this, this) {}; + outer.new MyInnerClass() {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(tainted) {}; + outer.new MyInnerClass(1) {}; + outer.new MyInnerClass(1, 2, 3) {}; + outer.new MyInnerClass(outer) {}; + outer.new MyInnerClass(outer, outer, outer) {}; + } } diff --git a/framework/tests/all-systems/AnonymousClasses.java b/framework/tests/all-systems/AnonymousClasses.java index a37f2da6197..2832fa144fc 100644 --- a/framework/tests/all-systems/AnonymousClasses.java +++ b/framework/tests/all-systems/AnonymousClasses.java @@ -4,28 +4,28 @@ // Checkers may issue type checking errors for this class, but they should not crash @SuppressWarnings("all") public class AnonymousClasses { - // test anonymous classes - private void testAnonymous() { - Foo x = new Foo() {}; - new Object() { - public boolean equals(Object o) { - return true; - } - }.equals(null); + // test anonymous classes + private void testAnonymous() { + Foo x = new Foo() {}; + new Object() { + public boolean equals(Object o) { + return true; + } + }.equals(null); - Date d = new Date() {}; - } + Date d = new Date() {}; + } - private > void testGenericAnonymous() { - Gen g = new Gen() {}; - GenInter gi = new GenInter() {}; - } + private > void testGenericAnonymous() { + Gen g = new Gen() {}; + GenInter gi = new GenInter() {}; + } - class Gen { - public Gen() {} - } + class Gen { + public Gen() {} + } - interface GenInter {} + interface GenInter {} - interface Foo {} + interface Foo {} } diff --git a/framework/tests/all-systems/AnonymousFieldAccess.java b/framework/tests/all-systems/AnonymousFieldAccess.java index 852c094eee7..04527b15229 100644 --- a/framework/tests/all-systems/AnonymousFieldAccess.java +++ b/framework/tests/all-systems/AnonymousFieldAccess.java @@ -1,12 +1,12 @@ @SuppressWarnings("all") // Check for crashes. public class AnonymousFieldAccess { - static class SomeClass { - Object fieldInSomeClass; - } + static class SomeClass { + Object fieldInSomeClass; + } - void createTreeAnnotator() { - new SomeClass() { - Object f = fieldInSomeClass; - }; - } + void createTreeAnnotator() { + new SomeClass() { + Object f = fieldInSomeClass; + }; + } } diff --git a/framework/tests/all-systems/ArrayComparator.java b/framework/tests/all-systems/ArrayComparator.java index 07c7dd81372..cd82a55e00e 100644 --- a/framework/tests/all-systems/ArrayComparator.java +++ b/framework/tests/all-systems/ArrayComparator.java @@ -5,9 +5,9 @@ @SuppressWarnings("allcheckers") // Only check for crashes class ArrayComparator { - private final SortedMap map = new TreeMap<>(); + private final SortedMap map = new TreeMap<>(); - public Comparator comparator() { - return map.comparator(); - } + public Comparator comparator() { + return map.comparator(); + } } diff --git a/framework/tests/all-systems/Arrays.java b/framework/tests/all-systems/Arrays.java index 89ba538ca51..2a8003cc2f7 100644 --- a/framework/tests/all-systems/Arrays.java +++ b/framework/tests/all-systems/Arrays.java @@ -1,27 +1,27 @@ public class Arrays { - public static final String[] RELATIONSHIP_LABELS = { - "SJJ", "SJU", "SUJ", "SUU", "DJJ", "DJU", "DUJ", "DUU", "JM", "UM", "MJ", "MU" - }; + public static final String[] RELATIONSHIP_LABELS = { + "SJJ", "SJU", "SUJ", "SUU", "DJJ", "DJU", "DUJ", "DUU", "JM", "UM", "MJ", "MU" + }; - public static final int[] ia = {1, -2, 3}; + public static final int[] ia = {1, -2, 3}; - // Note that "-1.0" is _NOT_ a double literal! It's a unary minus, - // that needs to be handled correctly. - public static final double[] elts_plus_minus_one_float = {-1.0, 1.0, +1.0, 1.0 / 2.0}; + // Note that "-1.0" is _NOT_ a double literal! It's a unary minus, + // that needs to be handled correctly. + public static final double[] elts_plus_minus_one_float = {-1.0, 1.0, +1.0, 1.0 / 2.0}; - String[] vis = new String[] {"a", "b"}; + String[] vis = new String[] {"a", "b"}; - @SuppressWarnings("nullness") // Don't want to depend on @Nullable - void m() { - class VarInfo {} - VarInfo v1 = null; - VarInfo v2 = null; - VarInfo[] vis = null; + @SuppressWarnings("nullness") // Don't want to depend on @Nullable + void m() { + class VarInfo {} + VarInfo v1 = null; + VarInfo v2 = null; + VarInfo[] vis = null; - if (v2 == null) { - vis = new VarInfo[] {v1}; - } else { - vis = new VarInfo[] {v1, v2}; + if (v2 == null) { + vis = new VarInfo[] {v1}; + } else { + vis = new VarInfo[] {v1, v2}; + } } - } } diff --git a/framework/tests/all-systems/AsSuperCrashes.java b/framework/tests/all-systems/AsSuperCrashes.java index eab7218e4f0..09343e440ad 100644 --- a/framework/tests/all-systems/AsSuperCrashes.java +++ b/framework/tests/all-systems/AsSuperCrashes.java @@ -1,91 +1,92 @@ package assuper; +import org.checkerframework.dataflow.qual.Pure; + import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.Date; import java.util.List; -import org.checkerframework.dataflow.qual.Pure; // This class has code that used to cause AsSuper to crash @SuppressWarnings("all") public class AsSuperCrashes { - // TODO: Value Checker crashes on this - /* void primitiveNarrowing() { - Byte b = 100; - Character c = 100; - Short s = 100; - - byte bb = 100; - char cc = 100; - short ss = 100; - } - */ - - // test anonymous classes - private void testAnonymous() { - new Object() { - public boolean equals(Object o) { - return true; - } - }.equals(null); - - Date d = new Date() {}; - } - - private void apply(Field field) { - Class type = field.getType(); - type.getSuperclass().getName().equals("java.lang.Enum"); - } - - void arrayAsMethodReceiver(Object[] array) { - array.clone(); - } - - T lowerBoundedWildcard(java.util.List> l) { - lowerBoundedWildcard(new java.util.ArrayList()); - throw new Error(); - } - - // Test super() and this() - class Inner { - public Inner() { - super(); + // TODO: Value Checker crashes on this + /* void primitiveNarrowing() { + Byte b = 100; + Character c = 100; + Short s = 100; + + byte bb = 100; + char cc = 100; + short ss = 100; + } + */ + + // test anonymous classes + private void testAnonymous() { + new Object() { + public boolean equals(Object o) { + return true; + } + }.equals(null); + + Date d = new Date() {}; } - public Inner(int i) { - this(); + private void apply(Field field) { + Class type = field.getType(); + type.getSuperclass().getName().equals("java.lang.Enum"); } - } - public static > void foo2(T a, T b) { - a.compareTo(b); - } + void arrayAsMethodReceiver(Object[] array) { + array.clone(); + } - public static > void foo(T a, T b) { - a.compareTo(b); - } + T lowerBoundedWildcard(java.util.List> l) { + lowerBoundedWildcard(new java.util.ArrayList()); + throw new Error(); + } - interface Interface { - void compareTo(F t); - } + // Test super() and this() + class Inner { + public Inner() { + super(); + } - public void m1(Class c) { - Class x = c.asSubclass(I2.class); - new WeakReference>(c.asSubclass(I2.class)); - } + public Inner(int i) { + this(); + } + } - interface I2 {} + public static > void foo2(T a, T b) { + a.compareTo(b); + } - @Pure - void bar() { - bar(); - } + public static > void foo(T a, T b) { + a.compareTo(b); + } - public static void copy(List dest, List src) { - dest.set(0, src.get(0)); - } + interface Interface { + void compareTo(F t); + } - public static void copy2(List dest, List src) { - dest.set(0, src.get(0)); - } + public void m1(Class c) { + Class x = c.asSubclass(I2.class); + new WeakReference>(c.asSubclass(I2.class)); + } + + interface I2 {} + + @Pure + void bar() { + bar(); + } + + public static void copy(List dest, List src) { + dest.set(0, src.get(0)); + } + + public static void copy2(List dest, List src) { + dest.set(0, src.get(0)); + } } diff --git a/framework/tests/all-systems/AssertWithSideEffect.java b/framework/tests/all-systems/AssertWithSideEffect.java index dfd0176263d..a25775c593b 100644 --- a/framework/tests/all-systems/AssertWithSideEffect.java +++ b/framework/tests/all-systems/AssertWithSideEffect.java @@ -2,8 +2,8 @@ * in conditional mode, such as in the condition of an assert statement. */ public class AssertWithSideEffect { - void CheckAssert() { - boolean assert_enabled = false; - assert (assert_enabled = true); - } + void CheckAssert() { + boolean assert_enabled = false; + assert (assert_enabled = true); + } } diff --git a/framework/tests/all-systems/AssignmentContext.java b/framework/tests/all-systems/AssignmentContext.java index 5010640706a..62ce3b024be 100644 --- a/framework/tests/all-systems/AssignmentContext.java +++ b/framework/tests/all-systems/AssignmentContext.java @@ -3,27 +3,27 @@ @SuppressWarnings("nullness") // Don't want to depend on @Nullable public class AssignmentContext { - void foo(String[] a) {} + void foo(String[] a) {} - void t1(boolean b) { - String[] s = b ? new String[] {""} : null; - } + void t1(boolean b) { + String[] s = b ? new String[] {""} : null; + } - void t2(boolean b) { - foo(b ? new String[] {""} : null); - } + void t2(boolean b) { + foo(b ? new String[] {""} : null); + } - String[] t3(boolean b) { - return b ? new String[] {""} : null; - } + String[] t3(boolean b) { + return b ? new String[] {""} : null; + } - void t4(boolean b) { - String[] s = null; - s = b ? new String[] {""} : null; - } + void t4(boolean b) { + String[] s = null; + s = b ? new String[] {""} : null; + } - void assignToCast(String @MinLen(4) [] @MinLen(5) [] currentSample) { - // This statement used to cause a null pointer exception. - ((String @MinLen(5) []) currentSample[3])[4] = currentSample[3][4]; - } + void assignToCast(String @MinLen(4) [] @MinLen(5) [] currentSample) { + // This statement used to cause a null pointer exception. + ((String @MinLen(5) []) currentSample[3])[4] = currentSample[3][4]; + } } diff --git a/framework/tests/all-systems/BeamCrash2Full.java b/framework/tests/all-systems/BeamCrash2Full.java index 61aee00eb55..69d84fb384b 100644 --- a/framework/tests/all-systems/BeamCrash2Full.java +++ b/framework/tests/all-systems/BeamCrash2Full.java @@ -10,44 +10,48 @@ @SuppressWarnings("all") // Just check for crashes. public class BeamCrash2Full { - private static void validateGettersHaveConsistentAnnotation( - List descriptors, - final AnnotationPredicates annotationPredicates, - SortedSet gettersWithTheAnnotation) { - for (final PropertyDescriptor descriptor : descriptors) { - throw new IllegalArgumentException( - String.format( - "Property [%s] is marked with contradictory annotations. Found [%s].", - descriptor.getName(), - gettersWithTheAnnotation.stream() - .flatMap( - method -> - Arrays.stream(method.getAnnotations()) - .filter(annotationPredicates.forAnnotation) - .map( - annotation -> - String.format( - "[%s on %s]", - formatAnnotation(annotation), - formatMethodWithClass(method)))) - .collect(Collectors.joining(", ")))); + private static void validateGettersHaveConsistentAnnotation( + List descriptors, + final AnnotationPredicates annotationPredicates, + SortedSet gettersWithTheAnnotation) { + for (final PropertyDescriptor descriptor : descriptors) { + throw new IllegalArgumentException( + String.format( + "Property [%s] is marked with contradictory annotations. Found [%s].", + descriptor.getName(), + gettersWithTheAnnotation.stream() + .flatMap( + method -> + Arrays.stream(method.getAnnotations()) + .filter( + annotationPredicates + .forAnnotation) + .map( + annotation -> + String.format( + "[%s on %s]", + formatAnnotation( + annotation), + formatMethodWithClass( + method)))) + .collect(Collectors.joining(", ")))); + } } - } - public static String formatAnnotation(Annotation annotation) { - return ""; - } + public static String formatAnnotation(Annotation annotation) { + return ""; + } - public static String formatMethodWithClass(Method input) { - return ""; - } + public static String formatMethodWithClass(Method input) { + return ""; + } - static class AnnotationPredicates { + static class AnnotationPredicates { - Predicate forAnnotation; + Predicate forAnnotation; - AnnotationPredicates(Predicate forAnnotation) { - this.forAnnotation = forAnnotation; + AnnotationPredicates(Predicate forAnnotation) { + this.forAnnotation = forAnnotation; + } } - } } diff --git a/framework/tests/all-systems/BigBinaryTrees.java b/framework/tests/all-systems/BigBinaryTrees.java index 6c63c533abf..3e3ce130552 100644 --- a/framework/tests/all-systems/BigBinaryTrees.java +++ b/framework/tests/all-systems/BigBinaryTrees.java @@ -4,267 +4,268 @@ // Checkers may correctly issue errors, so suppress them. @SuppressWarnings("all") public class BigBinaryTrees { - String string1; - String string2; - String string3; + String string1; + String string2; + String string3; - public void testStrings() { - String s = - getClass().getName() - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3 - + ",string1=" - + string1 - + ",string2=" - + string2 - + ",string3=" - + string3; - } + public void testStrings() { + String s = + getClass().getName() + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3 + + ",string1=" + + string1 + + ",string2=" + + string2 + + ",string3=" + + string3; + } - void test() { - int i0 = 163; - int i1 = 153; - int i2 = 75; - int i3 = -72; - int i4 = 61; - int i5 = 7; - int i6 = 83; - int i7 = -36; - int i8 = -90; - int i9 = -93; - int i10 = 187; - int i11 = -76; - int i12 = -16; - int i13 = -99; - int i14 = 113; - int i15 = 72; - int i16 = 58; - int i17 = -97; - int i18 = 115; - int i19 = -85; - int i20 = 156; - int i21 = -10; - int i22 = -85; - int i23 = 81; - int i24 = 63; - int i25 = -49; - int i26 = 158; - int i27 = 158; - int i28 = 25; - int i29 = 136; - int i30 = -90; - int i31 = 115; - int i32 = 179; - int i33 = 11; - int i34 = -100; - int i35 = 70; - int i36 = -46; - int i37 = -56; - int i38 = 108; - int i39 = -41; - int i40 = 124; - int i41 = -88; - int i42 = 54; - int i43 = 117; - int i44 = -92; - int i45 = 7; - int i46 = -94; - int i47 = 162; - int i48 = -34; - int i49 = 104; - int i50 = 111; - int i51 = -16; - int i52 = 197; - int i53 = -8; - int i54 = 101; - int i55 = 96; - int i56 = 132; - int i57 = -36; - int i58 = 148; - int i59 = 43; - int i60 = -59; - int i61 = 150; - int i62 = 48; - int i63 = 130; - int i64 = 74; - int i65 = -1; - int i66 = 79; - int i67 = 109; - int i68 = -70; - int i69 = 111; - int i70 = 78; - int i71 = 155; - int i72 = 176; - int i73 = 80; - int i74 = 181; - int i75 = 41; - int i76 = -85; - int i77 = 189; - int i78 = 97; - int i79 = 139; - int i80 = 9; - int i81 = 42; - int i82 = -50; - int i83 = 82; - int i84 = -70; - int i85 = 162; - int i86 = -20; - int i87 = 52; - int i88 = -94; - int i89 = 133; - int i90 = 136; - int i91 = 129; - int i92 = -55; - int i93 = 153; - int i94 = 6; - int i95 = -18; - int i96 = 132; - int i97 = 45; - int i98 = 120; - int i99 = 60; - int result = - i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 + i12 + i13 + i14 + i15 + i16 - + i17 + i18 + i19 + i20 + i21 + i22 + i23 + i24 + i25 + i26 + i27 + i28 + i29 + i30 - + i31 + i32 + i33 + i34 + i35 + i36 + i37 + i38 + i39 + i40 + i41 + i42 + i43 + i44 - + i45 + i46 + i47 + i48 + i49 + i50 + i51 + i52 + i53 + i54 + i55 + i56 + i57 + i58 - + i59 + i60 + i61 + i62 + i63 + i64 + i65 + i66 + i67 + i68 + i69 + i70 + i71 + i72 - + i73 + i74 + i75 + i76 + i77 + i78 + i79 + i80 + i81 + i82 + i83 + i84 + i85 + i86 - + i87 + i88 + i89 + i90 + i91 + i92 + i93 + i94 + i95 + i96 + i97 + i98 + i99; - } + void test() { + int i0 = 163; + int i1 = 153; + int i2 = 75; + int i3 = -72; + int i4 = 61; + int i5 = 7; + int i6 = 83; + int i7 = -36; + int i8 = -90; + int i9 = -93; + int i10 = 187; + int i11 = -76; + int i12 = -16; + int i13 = -99; + int i14 = 113; + int i15 = 72; + int i16 = 58; + int i17 = -97; + int i18 = 115; + int i19 = -85; + int i20 = 156; + int i21 = -10; + int i22 = -85; + int i23 = 81; + int i24 = 63; + int i25 = -49; + int i26 = 158; + int i27 = 158; + int i28 = 25; + int i29 = 136; + int i30 = -90; + int i31 = 115; + int i32 = 179; + int i33 = 11; + int i34 = -100; + int i35 = 70; + int i36 = -46; + int i37 = -56; + int i38 = 108; + int i39 = -41; + int i40 = 124; + int i41 = -88; + int i42 = 54; + int i43 = 117; + int i44 = -92; + int i45 = 7; + int i46 = -94; + int i47 = 162; + int i48 = -34; + int i49 = 104; + int i50 = 111; + int i51 = -16; + int i52 = 197; + int i53 = -8; + int i54 = 101; + int i55 = 96; + int i56 = 132; + int i57 = -36; + int i58 = 148; + int i59 = 43; + int i60 = -59; + int i61 = 150; + int i62 = 48; + int i63 = 130; + int i64 = 74; + int i65 = -1; + int i66 = 79; + int i67 = 109; + int i68 = -70; + int i69 = 111; + int i70 = 78; + int i71 = 155; + int i72 = 176; + int i73 = 80; + int i74 = 181; + int i75 = 41; + int i76 = -85; + int i77 = 189; + int i78 = 97; + int i79 = 139; + int i80 = 9; + int i81 = 42; + int i82 = -50; + int i83 = 82; + int i84 = -70; + int i85 = 162; + int i86 = -20; + int i87 = 52; + int i88 = -94; + int i89 = 133; + int i90 = 136; + int i91 = 129; + int i92 = -55; + int i93 = 153; + int i94 = 6; + int i95 = -18; + int i96 = 132; + int i97 = 45; + int i98 = 120; + int i99 = 60; + int result = + i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 + i12 + i13 + i14 + i15 + + i16 + i17 + i18 + i19 + i20 + i21 + i22 + i23 + i24 + i25 + i26 + i27 + + i28 + i29 + i30 + i31 + i32 + i33 + i34 + i35 + i36 + i37 + i38 + i39 + + i40 + i41 + i42 + i43 + i44 + i45 + i46 + i47 + i48 + i49 + i50 + i51 + + i52 + i53 + i54 + i55 + i56 + i57 + i58 + i59 + i60 + i61 + i62 + i63 + + i64 + i65 + i66 + i67 + i68 + i69 + i70 + i71 + i72 + i73 + i74 + i75 + + i76 + i77 + i78 + i79 + i80 + i81 + i82 + i83 + i84 + i85 + i86 + i87 + + i88 + i89 + i90 + i91 + i92 + i93 + i94 + i95 + i96 + i97 + i98 + i99; + } } diff --git a/framework/tests/all-systems/BigString.java b/framework/tests/all-systems/BigString.java index 35cdd4fa6e7..937096b1594 100644 --- a/framework/tests/all-systems/BigString.java +++ b/framework/tests/all-systems/BigString.java @@ -1,254 +1,254 @@ public class BigString { - public static final String big = - "\u00e7\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" - + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" - + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" - + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" - + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" - + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" - + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" - + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" - + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" - + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" - + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" - + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" - + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" - + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" - + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" - + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" - + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" - + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" - + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" - + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" - + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" - + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" - + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" - + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" - + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" - + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" - + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" - + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" - + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" - + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" - + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" - + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" - + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" - + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" - + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" - + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" - + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" - + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" - + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" - + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" - + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" - + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" - + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" - + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" - + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" - + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" - + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" - + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" - + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" - + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6"; - public static final String bigger = - "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" - + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" - + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" - + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" - + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" - + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" - + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" - + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" - + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" - + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" - + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" - + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" - + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" - + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" - + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" - + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" - + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" - + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" - + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" - + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" - + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" - + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" - + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" - + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" - + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" - + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" - + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" - + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" - + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" - + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" - + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" - + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" - + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" - + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" - + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" - + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" - + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" - + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" - + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" - + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" - + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" - + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" - + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" - + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" - + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" - + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" - + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" - + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" - + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" - + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" - + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" - + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" - + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" - + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" - + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" - + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" - + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" - + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" - + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" - + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" - + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" - + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" - + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" - + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" - + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" - + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" - + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" - + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" - + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" - + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" - + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" - + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" - + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" - + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" - + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" - + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" - + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" - + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" - + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" - + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" - + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" - + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" - + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" - + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" - + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" - + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" - + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" - + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" - + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" - + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" - + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" - + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" - + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" - + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" - + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" - + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" - + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" - + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" - + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" - + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" - + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" - + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" - + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" - + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" - + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" - + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" - + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" - + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" - + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" - + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" - + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" - + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" - + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" - + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" - + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" - + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" - + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" - + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" - + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" - + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" - + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" - + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" - + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" - + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" - + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" - + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" - + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" - + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" - + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" - + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" - + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" - + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" - + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" - + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" - + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" - + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" - + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" - + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" - + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" - + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" - + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" - + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" - + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" - + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" - + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" - + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" - + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" - + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" - + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" - + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" - + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" - + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" - + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" - + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" - + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" - + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" - + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" - + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" - + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" - + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" - + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" - + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" - + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" - + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" - + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" - + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" - + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" - + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" - + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" - + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" - + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" - + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" - + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" - + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" - + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" - + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" - + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" - + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" - + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" - + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" - + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" - + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" - + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" - + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" - + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" - + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" - + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" - + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" - + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" - + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" - + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" - + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" - + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" - + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" - + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" - + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" - + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" - + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" - + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" - + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; + public static final String big = + "\u00e7\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" + + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" + + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" + + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" + + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" + + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" + + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" + + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" + + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" + + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" + + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" + + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" + + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" + + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" + + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" + + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" + + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" + + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" + + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" + + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" + + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" + + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" + + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" + + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" + + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" + + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" + + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" + + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" + + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" + + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" + + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" + + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" + + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" + + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" + + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" + + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" + + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" + + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" + + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" + + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" + + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" + + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" + + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" + + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" + + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" + + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" + + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" + + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" + + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" + + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6"; + public static final String bigger = + "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" + + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" + + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" + + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" + + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" + + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" + + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" + + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" + + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" + + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" + + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" + + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" + + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" + + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" + + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" + + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" + + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" + + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" + + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" + + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" + + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" + + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" + + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" + + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" + + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" + + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" + + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" + + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" + + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" + + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" + + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" + + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" + + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" + + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" + + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" + + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" + + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" + + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" + + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" + + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" + + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" + + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" + + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" + + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" + + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" + + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" + + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" + + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" + + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" + + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" + + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" + + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" + + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" + + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" + + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" + + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" + + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" + + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" + + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" + + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" + + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" + + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" + + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" + + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" + + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" + + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" + + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" + + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" + + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" + + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" + + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" + + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" + + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" + + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" + + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" + + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" + + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" + + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" + + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" + + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" + + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" + + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" + + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" + + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" + + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" + + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" + + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" + + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" + + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" + + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" + + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" + + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" + + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" + + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" + + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" + + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" + + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" + + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" + + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" + + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" + + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" + + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" + + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" + + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" + + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" + + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" + + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" + + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" + + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" + + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" + + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" + + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" + + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" + + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" + + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" + + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" + + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" + + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" + + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" + + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" + + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" + + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" + + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" + + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" + + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" + + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" + + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" + + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" + + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" + + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" + + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" + + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" + + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" + + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" + + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" + + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" + + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" + + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" + + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" + + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" + + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" + + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" + + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" + + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" + + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" + + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" + + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" + + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" + + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" + + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" + + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" + + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" + + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" + + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" + + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" + + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" + + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" + + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" + + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" + + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" + + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" + + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" + + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" + + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" + + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" + + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" + + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" + + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" + + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" + + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" + + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" + + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" + + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" + + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" + + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" + + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" + + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" + + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" + + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" + + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" + + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" + + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" + + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" + + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" + + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" + + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" + + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" + + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" + + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" + + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" + + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" + + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" + + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" + + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" + + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" + + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" + + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" + + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" + + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" + + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; } diff --git a/framework/tests/all-systems/CaptureConversionCrash.java b/framework/tests/all-systems/CaptureConversionCrash.java index 3d9c65163de..f9c9f5b79ef 100644 --- a/framework/tests/all-systems/CaptureConversionCrash.java +++ b/framework/tests/all-systems/CaptureConversionCrash.java @@ -3,29 +3,29 @@ import wildcards.CaptureConversionCrash.GenericAnnotatedTypeFactoryCrash; public class CaptureConversionCrash> { - public abstract static class GenericAnnotatedTypeFactoryCrash< - Value extends CFAbstractValue, - Store extends CFAbstractStore, - TransferFunction extends CFAbstractTransfer, - FlowAnalysis extends CFAbstractAnalysis> { - abstract Store getRegularExitStore(); - } + public abstract static class GenericAnnotatedTypeFactoryCrash< + Value extends CFAbstractValue, + Store extends CFAbstractStore, + TransferFunction extends CFAbstractTransfer, + FlowAnalysis extends CFAbstractAnalysis> { + abstract Store getRegularExitStore(); + } - static class CFAbstractValue> {} + static class CFAbstractValue> {} - static class CFAbstractStore, S extends CFAbstractStore> {} + static class CFAbstractStore, S extends CFAbstractStore> {} - abstract static class CFAbstractTransfer< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> {} + abstract static class CFAbstractTransfer< + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> {} - public abstract static class CFAbstractAnalysis< - V extends CFAbstractValue, - S extends CFAbstractStore, - T extends CFAbstractTransfer> {} + public abstract static class CFAbstractAnalysis< + V extends CFAbstractValue, + S extends CFAbstractStore, + T extends CFAbstractTransfer> {} - void use(Factory atypeFactory) { - CFAbstractStore exitStore = atypeFactory.getRegularExitStore(); - } + void use(Factory atypeFactory) { + CFAbstractStore exitStore = atypeFactory.getRegularExitStore(); + } } diff --git a/framework/tests/all-systems/CaptureMethodTypeArgs.java b/framework/tests/all-systems/CaptureMethodTypeArgs.java index e4693d0e09d..3157a10b46c 100644 --- a/framework/tests/all-systems/CaptureMethodTypeArgs.java +++ b/framework/tests/all-systems/CaptureMethodTypeArgs.java @@ -2,15 +2,15 @@ @SuppressWarnings("all") // Just check for crashes. public class CaptureMethodTypeArgs { - static class MyClass {} + static class MyClass {} - private MyClass myClass; + private MyClass myClass; - void test(Class cls) { - Object o = method(cls.getComponentType()).myClass; - } + void test(Class cls) { + Object o = method(cls.getComponentType()).myClass; + } - static CaptureMethodTypeArgs method(Class cls) { - throw new RuntimeException(); - } + static CaptureMethodTypeArgs method(Class cls) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/Catch.java b/framework/tests/all-systems/Catch.java index 6fe33c22022..55fd9624825 100644 --- a/framework/tests/all-systems/Catch.java +++ b/framework/tests/all-systems/Catch.java @@ -1,17 +1,17 @@ public class Catch { - void defaultUnionType() throws Throwable { - try { - throw new Throwable(); - } catch (IndexOutOfBoundsException | NullPointerException ex) { + void defaultUnionType() throws Throwable { + try { + throw new Throwable(); + } catch (IndexOutOfBoundsException | NullPointerException ex) { + } } - } - void defaultDeclaredType() throws Throwable { - try { - throw new Throwable(); - } catch (RuntimeException ex) { + void defaultDeclaredType() throws Throwable { + try { + throw new Throwable(); + } catch (RuntimeException ex) { + } } - } } diff --git a/framework/tests/all-systems/ComplexPatternEscape.java b/framework/tests/all-systems/ComplexPatternEscape.java index 5aa37b0a6a5..48ff75b2eb7 100644 --- a/framework/tests/all-systems/ComplexPatternEscape.java +++ b/framework/tests/all-systems/ComplexPatternEscape.java @@ -4,10 +4,10 @@ public class ComplexPatternEscape { - private static final String DOT_DELIMITED_IDS = ""; + private static final String DOT_DELIMITED_IDS = ""; - // From - // https://github.com/randoop/randoop/blob/ffed1540721212adc55da179f1ae3b3df582d0d5/agent/replacecall/src/main/java/randoop/instrument/ReplacementFileReader.java#L58 - private static final String SIGNATURE_STRING = - DOT_DELIMITED_IDS + "(?:\\.)?" + "\\([^)]*\\)"; + // From + // https://github.com/randoop/randoop/blob/ffed1540721212adc55da179f1ae3b3df582d0d5/agent/replacecall/src/main/java/randoop/instrument/ReplacementFileReader.java#L58 + private static final String SIGNATURE_STRING = + DOT_DELIMITED_IDS + "(?:\\.)?" + "\\([^)]*\\)"; } diff --git a/framework/tests/all-systems/CompoundAssignments.java b/framework/tests/all-systems/CompoundAssignments.java index c769e3d2074..09efb7bab88 100644 --- a/framework/tests/all-systems/CompoundAssignments.java +++ b/framework/tests/all-systems/CompoundAssignments.java @@ -1,22 +1,22 @@ public class CompoundAssignments { - static final int SIZE = 4; + static final int SIZE = 4; - // There used to be a bug creating the LeftShiftAssignmentNode where the target (e.g. pow) was - // replaced by the shift amount (e.g. 1). - static void left_shift_assign() { - for (int i = 0, pow = 1; i <= SIZE; i++) { - pow <<= 1; + // There used to be a bug creating the LeftShiftAssignmentNode where the target (e.g. pow) was + // replaced by the shift amount (e.g. 1). + static void left_shift_assign() { + for (int i = 0, pow = 1; i <= SIZE; i++) { + pow <<= 1; + } } - } - // There used to be a bug computing the JavaExpression for a widening - // conversion, such as widening sum to a double below. - static int sum_with_widening() { - double[] freq = new double[SIZE]; - int sum = 0; - for (int i = 0; i < SIZE; i++) { - sum += freq[i]; + // There used to be a bug computing the JavaExpression for a widening + // conversion, such as widening sum to a double below. + static int sum_with_widening() { + double[] freq = new double[SIZE]; + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += freq[i]; + } + return sum; } - return sum; - } } diff --git a/framework/tests/all-systems/ConditionalExpressions.java b/framework/tests/all-systems/ConditionalExpressions.java index e2b91395f22..1ea0aa35688 100644 --- a/framework/tests/all-systems/ConditionalExpressions.java +++ b/framework/tests/all-systems/ConditionalExpressions.java @@ -6,51 +6,52 @@ import java.util.List; public class ConditionalExpressions { - public static boolean flag = true; + public static boolean flag = true; - class TypeVarTypeVar { - void foo1(T tExtendsNumber, S sExtendsT) { - T o = flag ? tExtendsNumber : sExtendsT; - } + class TypeVarTypeVar { + void foo1(T tExtendsNumber, S sExtendsT) { + T o = flag ? tExtendsNumber : sExtendsT; + } - void foo2(T tExtendsNumber, S sExtendsT) { - T o = flag ? tExtendsNumber : sExtendsT; - } + void foo2(T tExtendsNumber, S sExtendsT) { + T o = flag ? tExtendsNumber : sExtendsT; + } - void foo3(T tExtendsNumber, S sExtendsInteger) { - Number o3 = flag ? tExtendsNumber : sExtendsInteger; - } + void foo3(T tExtendsNumber, S sExtendsInteger) { + Number o3 = flag ? tExtendsNumber : sExtendsInteger; + } - void foo4(T tExtendsNumber, S sExtendsCharSequence) { - Object o2 = flag ? tExtendsNumber : sExtendsCharSequence; - } + void foo4( + T tExtendsNumber, S sExtendsCharSequence) { + Object o2 = flag ? tExtendsNumber : sExtendsCharSequence; + } - void foo5(T tExtendsLong, S sExtendsInt) { - Number o = flag ? tExtendsLong : sExtendsInt; + void foo5(T tExtendsLong, S sExtendsInt) { + Number o = flag ? tExtendsLong : sExtendsInt; + } } - } - class ArrayTypes { - void foo1(String string, String[] strings) { - Serializable o2 = (flag ? string : strings); - } + class ArrayTypes { + void foo1(String string, String[] strings) { + Serializable o2 = (flag ? string : strings); + } - void foo2(Integer[] integers, String[] strings) { - Object[] o = (flag ? integers : strings); - } + void foo2(Integer[] integers, String[] strings) { + Object[] o = (flag ? integers : strings); + } - void foo3(T ts, Number[] numbers) { - Cloneable o = flag ? ts : numbers; - } + void foo3(T ts, Number[] numbers) { + Cloneable o = flag ? ts : numbers; + } - void foo4(T ts, Number[] numbers) { - Object o = (flag ? ts : numbers); + void foo4(T ts, Number[] numbers) { + Object o = (flag ? ts : numbers); + } } - } - class Generics { - void foo1(List listS, List listI) { - Number s = (flag ? listI : listS).get(0); + class Generics { + void foo1(List listS, List listI) { + Number s = (flag ? listI : listS).get(0); + } } - } } diff --git a/framework/tests/all-systems/CrazyEnum.java b/framework/tests/all-systems/CrazyEnum.java index 492a2fd3edd..fd9a9064b0d 100644 --- a/framework/tests/all-systems/CrazyEnum.java +++ b/framework/tests/all-systems/CrazyEnum.java @@ -1,20 +1,20 @@ @SuppressWarnings("all") // Check for crashes. public class CrazyEnum { - private enum MyEnum { - ENUM_CONST1 { - private final String s = method(); + private enum MyEnum { + ENUM_CONST1 { + private final String s = method(); - private String method() { - return "hello"; - } - }, + private String method() { + return "hello"; + } + }, - ENUM_CONST2 { - private final String s = this.method(); + ENUM_CONST2 { + private final String s = this.method(); - private String method() { - return "hello"; - } + private String method() { + return "hello"; + } + } } - } } diff --git a/framework/tests/all-systems/DeepEquals.java b/framework/tests/all-systems/DeepEquals.java index d6167bf2b60..927a386cd1c 100644 --- a/framework/tests/all-systems/DeepEquals.java +++ b/framework/tests/all-systems/DeepEquals.java @@ -1,12 +1,12 @@ public class DeepEquals { - public static int deepEquals(Object o1) { - if (o1 instanceof boolean[]) { - return 1; - } - if (o1 instanceof byte[]) { - return 2; - } + public static int deepEquals(Object o1) { + if (o1 instanceof boolean[]) { + return 1; + } + if (o1 instanceof byte[]) { + return 2; + } - return 3; - } + return 3; + } } diff --git a/framework/tests/all-systems/DiamondLambda.java b/framework/tests/all-systems/DiamondLambda.java index e66dfceb910..c063128571f 100644 --- a/framework/tests/all-systems/DiamondLambda.java +++ b/framework/tests/all-systems/DiamondLambda.java @@ -3,7 +3,7 @@ import java.util.Set; public class DiamondLambda { - void foo(Map> map, String sequence) { - Set set = map.computeIfAbsent(sequence, __ -> new HashSet<>()); - } + void foo(Map> map, String sequence) { + Set set = map.computeIfAbsent(sequence, __ -> new HashSet<>()); + } } diff --git a/framework/tests/all-systems/EisopIssue612.java b/framework/tests/all-systems/EisopIssue612.java index 88abd9931d3..8693bda0aeb 100644 --- a/framework/tests/all-systems/EisopIssue612.java +++ b/framework/tests/all-systems/EisopIssue612.java @@ -1,7 +1,7 @@ interface Issue612A {} @interface Issue612B { - Class value(); + Class value(); } class Issue612C implements Issue612A {} @@ -9,12 +9,12 @@ class Issue612C implements Issue612A {} class Issue612E {} @interface Issue612Z { - abstract class Issue612D implements Issue612A { + abstract class Issue612D implements Issue612A { - public Issue612D() { - g(new Issue612E()); - } + public Issue612D() { + g(new Issue612E()); + } - static void g(Object... xs) {} - } + static void g(Object... xs) {} + } } diff --git a/framework/tests/all-systems/EisopIssue612Min.java b/framework/tests/all-systems/EisopIssue612Min.java index c30d9e1eacd..797a04874d1 100644 --- a/framework/tests/all-systems/EisopIssue612Min.java +++ b/framework/tests/all-systems/EisopIssue612Min.java @@ -1,9 +1,9 @@ @interface Issue612Min { - class D { - D() { - g(new Object()); - } + class D { + D() { + g(new Object()); + } - static void g(Object... xs) {} - } + static void g(Object... xs) {} + } } diff --git a/framework/tests/all-systems/EnumSwitch.java b/framework/tests/all-systems/EnumSwitch.java index ff20f02091c..4330494cf5b 100644 --- a/framework/tests/all-systems/EnumSwitch.java +++ b/framework/tests/all-systems/EnumSwitch.java @@ -3,21 +3,21 @@ // A similar test is in // checker/tests/nullness/Issue6260.java enum EnumSwitch { - FOO; + FOO; - EnumSwitch getIt() { - return FOO; - } + EnumSwitch getIt() { + return FOO; + } - String go() { - EnumSwitch e = getIt(); - switch (e) { - case FOO: - return "foo"; + String go() { + EnumSwitch e = getIt(); + switch (e) { + case FOO: + return "foo"; + } + // This location is reachable in general: the enum could evolve and add a new constant. + // This cannot be the case here, because this code is in the enum declaration. + // javac does not special case this and I do not see a reason to do so here. + throw new AssertionError(e); } - // This location is reachable in general: the enum could evolve and add a new constant. - // This cannot be the case here, because this code is in the enum declaration. - // javac does not special case this and I do not see a reason to do so here. - throw new AssertionError(e); - } } diff --git a/framework/tests/all-systems/Enums.java b/framework/tests/all-systems/Enums.java index 7f8bd397607..e0e0ab6799d 100644 --- a/framework/tests/all-systems/Enums.java +++ b/framework/tests/all-systems/Enums.java @@ -3,46 +3,46 @@ class MyEnumSet> {} public class Enums { - public enum VarFlags { - IS_PARAM, - NO_DUPS - }; + public enum VarFlags { + IS_PARAM, + NO_DUPS + }; - public MyEnumSet var_flags = new MyEnumSet<>(); + public MyEnumSet var_flags = new MyEnumSet<>(); - VarFlags f1 = VarFlags.IS_PARAM; + VarFlags f1 = VarFlags.IS_PARAM; - void foo1(MyEnumSet p) {} + void foo1(MyEnumSet p) {} - void foo2(MyEnumSet p) {} + void foo2(MyEnumSet p) {} - > void mtv(Class p) {} + > void mtv(Class p) {} - T checkNotNull(T ref) { - return ref; - } - - T checkNotNull2(T ref, S ref2) { - return ref; - } + T checkNotNull(T ref) { + return ref; + } - class Test> { - void m(Class p) { - checkNotNull(p); + T checkNotNull2(T ref, S ref2) { + return ref; } - public SSS firstNonNull(SSS first, SSS second) { - @SuppressWarnings("nullness:nulltest.redundant") - SSS res = first != null ? first : checkNotNull(second); - return res; + class Test> { + void m(Class p) { + checkNotNull(p); + } + + public SSS firstNonNull(SSS first, SSS second) { + @SuppressWarnings("nullness:nulltest.redundant") + SSS res = first != null ? first : checkNotNull(second); + return res; + } } - } - class Unbound {} + class Unbound {} - class Test2, S extends Unbound> { - void m(Class p, Class q) { - checkNotNull2(p, q); + class Test2, S extends Unbound> { + void m(Class p, Class q) { + checkNotNull2(p, q); + } } - } } diff --git a/framework/tests/all-systems/EqualityTests.java b/framework/tests/all-systems/EqualityTests.java index 3b33ca5dd9a..f184d8c8f14 100644 --- a/framework/tests/all-systems/EqualityTests.java +++ b/framework/tests/all-systems/EqualityTests.java @@ -1,24 +1,24 @@ public class EqualityTests { - // the Interning checker correctly issues an error below, but we would like to keep this test in - // all-systems. - @SuppressWarnings("interning") - public boolean compareLongs(Long v1, Long v2) { - // This expression used to cause an assertion - // failure in GLB computation. - return !(((v1 == 0) || (v2 == 0)) && (v1 != v2)); - } - - public int charEquals(boolean cond) { - char result = 'F'; - if (cond) { - result = 'T'; + // the Interning checker correctly issues an error below, but we would like to keep this test in + // all-systems. + @SuppressWarnings("interning") + public boolean compareLongs(Long v1, Long v2) { + // This expression used to cause an assertion + // failure in GLB computation. + return !(((v1 == 0) || (v2 == 0)) && (v1 != v2)); } - if (result == 'T') { - return 1; - } else { - assert result == '?'; + public int charEquals(boolean cond) { + char result = 'F'; + if (cond) { + result = 'T'; + } + + if (result == 'T') { + return 1; + } else { + assert result == '?'; + } + return 10; } - return 10; - } } diff --git a/framework/tests/all-systems/FieldAccessTest.java b/framework/tests/all-systems/FieldAccessTest.java index ec3cf454bfd..34a0d11a005 100644 --- a/framework/tests/all-systems/FieldAccessTest.java +++ b/framework/tests/all-systems/FieldAccessTest.java @@ -2,39 +2,39 @@ // See the comments at GenericNull for some tips about what might be wrong. public class FieldAccessTest { - class MyClass { + class MyClass { - Object field = new Object(); - } - - class MyException extends RuntimeException { - Object field = new Object(); - } + Object field = new Object(); + } - class MyExceptionA extends MyException {} + class MyException extends RuntimeException { + Object field = new Object(); + } - class MyExceptionB extends MyException {} + class MyExceptionA extends MyException {} - @SuppressWarnings("nullness") - class MyGen { - T myClass = null; - } + class MyExceptionB extends MyException {} - void test(Object o, MyGen raw) { - // Raw type field access: - raw.myClass.field = new Object(); + @SuppressWarnings("nullness") + class MyGen { + T myClass = null; + } - // Intersection type field access - Object a = ((MyClass & Cloneable) o).field; - try { - } catch (MyExceptionA | MyExceptionB ex) { - // Union type field access - ex.field = new Object(); + void test(Object o, MyGen raw) { + // Raw type field access: + raw.myClass.field = new Object(); + + // Intersection type field access + Object a = ((MyClass & Cloneable) o).field; + try { + } catch (MyExceptionA | MyExceptionB ex) { + // Union type field access + ex.field = new Object(); + } } - } - void classLiterals() { - Class c = byte.class; - Class d = void.class; - } + void classLiterals() { + Class c = byte.class; + Class d = void.class; + } } diff --git a/framework/tests/all-systems/FieldWithInit.java b/framework/tests/all-systems/FieldWithInit.java index 2355a066875..f4d629dec8d 100644 --- a/framework/tests/all-systems/FieldWithInit.java +++ b/framework/tests/all-systems/FieldWithInit.java @@ -2,9 +2,9 @@ public class FieldWithInit { - Object f = foo(); + Object f = foo(); - Object foo(@UnknownInitialization FieldWithInit this) { - return new Object(); - } + Object foo(@UnknownInitialization FieldWithInit this) { + return new Object(); + } } diff --git a/framework/tests/all-systems/ForEach.java b/framework/tests/all-systems/ForEach.java index 96d4e6e1063..b551fdf9150 100644 --- a/framework/tests/all-systems/ForEach.java +++ b/framework/tests/all-systems/ForEach.java @@ -4,43 +4,43 @@ import java.util.Set; public class ForEach { - void m1() { - Set s = new HashSet(); - for (CharSequence cs : s) { - cs.toString(); + void m1() { + Set s = new HashSet(); + for (CharSequence cs : s) { + cs.toString(); + } } - } - void m2() { - Set s = new HashSet<>(); - for (CharSequence cs : s) { - cs.toString(); + void m2() { + Set s = new HashSet<>(); + for (CharSequence cs : s) { + cs.toString(); + } } - } - void m3(T p) { - Set s = new HashSet<>(); - for (T cs : s) { - cs.toString(); + void m3(T p) { + Set s = new HashSet<>(); + for (T cs : s) { + cs.toString(); + } } - } - void m4(T p) { - Set s = new HashSet<>(); - for (Object cs : s) { - cs.toString(); + void m4(T p) { + Set s = new HashSet<>(); + for (Object cs : s) { + cs.toString(); + } } - } - public static List removeDuplicates(List l) { - // There are shorter solutions that do not maintain order. - HashSet hs = new HashSet<>(l.size()); - List result = new ArrayList<>(); - for (T t : l) { - if (hs.add(t)) { - result.add(t); - } + public static List removeDuplicates(List l) { + // There are shorter solutions that do not maintain order. + HashSet hs = new HashSet<>(l.size()); + List result = new ArrayList<>(); + for (T t : l) { + if (hs.add(t)) { + result.add(t); + } + } + return result; } - return result; - } } diff --git a/framework/tests/all-systems/GenericCrazyBounds.java b/framework/tests/all-systems/GenericCrazyBounds.java index e95a702c9cf..d5ef260665d 100644 --- a/framework/tests/all-systems/GenericCrazyBounds.java +++ b/framework/tests/all-systems/GenericCrazyBounds.java @@ -4,41 +4,41 @@ */ interface MyList { - ZZ getZZ(); + ZZ getZZ(); - void setZZ(ZZ ZZ); + void setZZ(ZZ ZZ); } interface MyMap { - K getK(); + K getK(); - V getV(); + V getV(); - void setK(K k); + void setK(K k); - void setV(V v); + void setV(V v); } class MyRec> {} class RecMyList extends MyRec implements MyList { - @SuppressWarnings("return.type.incompatible") - public RecMyList getZZ() { - return null; - } - - public void setZZ(RecMyList rl) { - return; - } + @SuppressWarnings("return.type.incompatible") + public RecMyList getZZ() { + return null; + } + + public void setZZ(RecMyList rl) { + return; + } } class Context2 { - & MyList> void main() {} + & MyList> void main() {} - void context() { - this.main(); - } + void context() { + this.main(); + } } interface Rec> {} @@ -50,38 +50,38 @@ class RecImpl implements Rec {} class SubRec extends RecImpl {} class CrazyGen2, EE extends MyMap> { - TT t2; - EE e2; - - public CrazyGen2(TT t2, EE e2) { - this.t2 = t2; - this.e2 = e2; - } - - public void context() { - t2.setZZ(e2); - e2.setK(t2.getZZ().getK()); - } + TT t2; + EE e2; + + public CrazyGen2(TT t2, EE e2) { + this.t2 = t2; + this.e2 = e2; + } + + public void context() { + t2.setZZ(e2); + e2.setK(t2.getZZ().getK()); + } } class CrazyGen3, EEE extends MyMap> { - TTT t3; - EEE e3; - - public CrazyGen3(TTT t3, EEE e3) { - this.t3 = t3; - this.e3 = e3; - } - - public void context() { - e3.setK(t3); - e3.setK(t3.getZZ()); - } + TTT t3; + EEE e3; + + public CrazyGen3(TTT t3, EEE e3) { + this.t3 = t3; + this.e3 = e3; + } + + public void context() { + e3.setK(t3); + e3.setK(t3.getZZ()); + } } class MyClass { - public > String methodToPrint(TV1 tv1, int intParam) { - return ""; - } + public > String methodToPrint(TV1 tv1, int intParam) { + return ""; + } } diff --git a/framework/tests/all-systems/GenericExtendsTypeVars.java b/framework/tests/all-systems/GenericExtendsTypeVars.java index c4655318788..1c7ed51218d 100644 --- a/framework/tests/all-systems/GenericExtendsTypeVars.java +++ b/framework/tests/all-systems/GenericExtendsTypeVars.java @@ -9,29 +9,29 @@ interface MMyMap {} class Tester> {} class WithWildcard> { - void context() { - ZZ zz = null; - QQ qq = null; - YY yy = null; - } + void context() { + ZZ zz = null; + QQ qq = null; + YY yy = null; + } } class Test> { - KK kk; - FF ff; + KK kk; + FF ff; - Test(KK kk, FF ff) { - this.kk = kk; - this.ff = ff; - } + Test(KK kk, FF ff) { + this.kk = kk; + this.ff = ff; + } } class RecursiveTypevarClass> { - T t; + T t; - RecursiveTypevarClass(T t) { - this.t = t; - } + RecursiveTypevarClass(T t) { + this.t = t; + } } class RecursiveImplements implements MMyList {} diff --git a/framework/tests/all-systems/GenericNull.java b/framework/tests/all-systems/GenericNull.java index f5bbf04355d..bf8000069b2 100644 --- a/framework/tests/all-systems/GenericNull.java +++ b/framework/tests/all-systems/GenericNull.java @@ -17,16 +17,16 @@ // public class GenericNull { - /** - * In most type systems, null's type is bottom and therefore the generic return type T is a - * supertype of null's type. - * - *

                  However, in the nullness and lock type systems, null's type is not bottom, so they exclude - * this test. For the Lock Checker, null's type is bottom for the @GuardedByUnknown hierarchy but - * not for the @LockPossiblyHeld hierarchy. - */ - @SuppressWarnings({"nullness", "lock"}) - T f() { - return null; - } + /** + * In most type systems, null's type is bottom and therefore the generic return type T is a + * supertype of null's type. + * + *

                  However, in the nullness and lock type systems, null's type is not bottom, so they exclude + * this test. For the Lock Checker, null's type is bottom for the @GuardedByUnknown hierarchy + * but not for the @LockPossiblyHeld hierarchy. + */ + @SuppressWarnings({"nullness", "lock"}) + T f() { + return null; + } } diff --git a/framework/tests/all-systems/GenericTest11full.java b/framework/tests/all-systems/GenericTest11full.java index 7e6744a8462..ab91a54ab04 100644 --- a/framework/tests/all-systems/GenericTest11full.java +++ b/framework/tests/all-systems/GenericTest11full.java @@ -1,23 +1,24 @@ public class GenericTest11full { - public void m(BeanManager beanManager) { - Bean bean = beanManager.getBeans(GenericTest11full.class).iterator().next(); - CreationalContext context = beanManager.createCreationalContext(bean); - GenericTest11full b = - (GenericTest11full) beanManager.getReference(bean, GenericTest11full.class, context); - } + public void m(BeanManager beanManager) { + Bean bean = beanManager.getBeans(GenericTest11full.class).iterator().next(); + CreationalContext context = beanManager.createCreationalContext(bean); + GenericTest11full b = + (GenericTest11full) + beanManager.getReference(bean, GenericTest11full.class, context); + } - static interface BeanManager { - java.util.Set> getBeans( - java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); + static interface BeanManager { + java.util.Set> getBeans( + java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); - CreationalContext createCreationalContext(Contextual arg0); + CreationalContext createCreationalContext(Contextual arg0); - Object getReference(Bean arg0, java.lang.reflect.Type arg1, CreationalContext arg2); - } + Object getReference(Bean arg0, java.lang.reflect.Type arg1, CreationalContext arg2); + } - static interface Contextual {} + static interface Contextual {} - static interface Bean extends Contextual {} + static interface Bean extends Contextual {} - static interface CreationalContext {} + static interface CreationalContext {} } diff --git a/framework/tests/all-systems/GenericTest12.java b/framework/tests/all-systems/GenericTest12.java index 24b4b63cde4..3c4445aaa26 100644 --- a/framework/tests/all-systems/GenericTest12.java +++ b/framework/tests/all-systems/GenericTest12.java @@ -1,10 +1,10 @@ // Test case from Issue 142 abstract class GenericTest12 { - interface Task {} + interface Task {} - abstract Task create(Runnable runnable, M result); + abstract Task create(Runnable runnable, M result); - void submit(Runnable runnable) { - Task task = create(runnable, null); - } + void submit(Runnable runnable) { + Task task = create(runnable, null); + } } diff --git a/framework/tests/all-systems/GenericTest12b.java b/framework/tests/all-systems/GenericTest12b.java index 92425a9df85..f2e3dc8c626 100644 --- a/framework/tests/all-systems/GenericTest12b.java +++ b/framework/tests/all-systems/GenericTest12b.java @@ -1,23 +1,23 @@ @SuppressWarnings({ - "initialization", - "nullness" + "initialization", + "nullness" }) // Don't want to depend on @Nullable or @UnderInitialization public class GenericTest12b { - class Cell {} + class Cell {} - class Node { - public Node(Cell userObject) {} + class Node { + public Node(Cell userObject) {} - void nodecall(Cell userObject) {} - } - - class RootNode extends Node { - public RootNode() { - super(new Cell()); - call(new Cell()); - nodecall(new Cell()); + void nodecall(Cell userObject) {} } - void call(Cell userObject) {} - } + class RootNode extends Node { + public RootNode() { + super(new Cell()); + call(new Cell()); + nodecall(new Cell()); + } + + void call(Cell userObject) {} + } } diff --git a/framework/tests/all-systems/GenericTest13.java b/framework/tests/all-systems/GenericTest13.java index 75955975090..90d43368be2 100644 --- a/framework/tests/all-systems/GenericTest13.java +++ b/framework/tests/all-systems/GenericTest13.java @@ -2,15 +2,15 @@ // https://github.com/typetools/checker-framework/issues/142 public class GenericTest13 { - interface Entry { - V getValue(); - } + interface Entry { + V getValue(); + } - interface Iterator { - E next(); - } + interface Iterator { + E next(); + } - S call(Iterator> entryIterator) { - return entryIterator.next().getValue(); - } + S call(Iterator> entryIterator) { + return entryIterator.next().getValue(); + } } diff --git a/framework/tests/all-systems/GenericsBounds.java b/framework/tests/all-systems/GenericsBounds.java index 34b6877ae78..8e0bfb018a2 100644 --- a/framework/tests/all-systems/GenericsBounds.java +++ b/framework/tests/all-systems/GenericsBounds.java @@ -14,7 +14,7 @@ class Upper, Y extends X> {} class Lower extends Upper {} class GenericsBounds { - Upper f = new Upper<>(); + Upper f = new Upper<>(); } class Upper1> {} @@ -27,7 +27,7 @@ class Lower2 extends Upper2, LinkedList> {} class GenericGetClass { - Class getClass(Class orig, Class cast) { - return orig.asSubclass(cast); - } + Class getClass(Class orig, Class cast) { + return orig.asSubclass(cast); + } } diff --git a/framework/tests/all-systems/GenericsBounds2.java b/framework/tests/all-systems/GenericsBounds2.java index f6990f408a5..6e8c185e94e 100644 --- a/framework/tests/all-systems/GenericsBounds2.java +++ b/framework/tests/all-systems/GenericsBounds2.java @@ -1,7 +1,7 @@ // Test for Issue 258 // https://github.com/typetools/checker-framework/issues/258 public class GenericsBounds2 { - void method(C arg) { - arg.toString(); - } + void method(C arg) { + arg.toString(); + } } diff --git a/framework/tests/all-systems/GenericsCasts.java b/framework/tests/all-systems/GenericsCasts.java index 4f43b5aeb54..abd33b1e056 100644 --- a/framework/tests/all-systems/GenericsCasts.java +++ b/framework/tests/all-systems/GenericsCasts.java @@ -3,51 +3,51 @@ import java.util.List; public class GenericsCasts { - // Cast from a raw type to a generic type - // :: warning: [unchecked] unchecked cast - List[] o = (List[]) new List[] {new ArrayList()}; - - class Data {} - - // Use our own dummy method as to avoid a complaint from the Signature Checker - Data forName(String p) { - throw new Error(""); - } - - void m() { - // Cast from a wildcard to a normal type argument. - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) + // Cast from a raw type to a generic type // :: warning: [unchecked] unchecked cast - Data c = (Data) forName("HaHa!"); - } + List[] o = (List[]) new List[] {new ArrayList()}; - // Casts from something with one type argument to two type arguments - // are currently problematic. - // TODO: try to find a problem with skipping this check. - class Test { - class Entry extends LinkedList {} + class Data {} - class Queue { - List poll() { + // Use our own dummy method as to avoid a complaint from the Signature Checker + Data forName(String p) { throw new Error(""); - } } - void trouble() { - Queue queue = new Queue<>(); - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - // :: warning: [unchecked] unchecked cast - Entry e = (Entry) queue.poll(); + void m() { + // Cast from a wildcard to a normal type argument. + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + Data c = (Data) forName("HaHa!"); } - } - public static int indexOf(T[] a) { - return indexOfEq(a); - } + // Casts from something with one type argument to two type arguments + // are currently problematic. + // TODO: try to find a problem with skipping this check. + class Test { + class Entry extends LinkedList {} + + class Queue { + List poll() { + throw new Error(""); + } + } + + void trouble() { + Queue queue = new Queue<>(); + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + // :: warning: [unchecked] unchecked cast + Entry e = (Entry) queue.poll(); + } + } - public static int indexOfEq(Object[] a) { - return 0; - } + public static int indexOf(T[] a) { + return indexOfEq(a); + } + + public static int indexOfEq(Object[] a) { + return 0; + } } diff --git a/framework/tests/all-systems/GenericsEnclosing.java b/framework/tests/all-systems/GenericsEnclosing.java index 62e8ab1bf5f..ea62fb5c05a 100644 --- a/framework/tests/all-systems/GenericsEnclosing.java +++ b/framework/tests/all-systems/GenericsEnclosing.java @@ -7,19 +7,19 @@ *

                  Also see regex/GenericsEnclosing for a test case for the Regex Checker. */ public class GenericsEnclosing extends TreeMap { - class Inner { - void foo() { - put("string", "string".toString()); - put("string", "string"); - GenericsEnclosing.this.put("string", "string".toString()); - GenericsEnclosing.this.put("string", "string"); + class Inner { + void foo() { + put("string", "string".toString()); + put("string", "string"); + GenericsEnclosing.this.put("string", "string".toString()); + GenericsEnclosing.this.put("string", "string"); + } } - } } class OtherUse { - void m(GenericsEnclosing p) { - p.put("string", "string".toString()); - p.put("string", "string"); - } + void m(GenericsEnclosing p) { + p.put("string", "string".toString()); + p.put("string", "string"); + } } diff --git a/framework/tests/all-systems/GetClassTest.java b/framework/tests/all-systems/GetClassTest.java index c36d56265f2..ba7e2fe7ea1 100644 --- a/framework/tests/all-systems/GetClassTest.java +++ b/framework/tests/all-systems/GetClassTest.java @@ -3,28 +3,28 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class GetClassTest { - // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver + // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver - void context() { - Integer i = 4; - i.getClass(); - Class a = i.getClass(); - // Type arguments don't match - @SuppressWarnings("fenum:assignment.type.incompatible") - Class b = i.getClass(); - @SuppressWarnings({ - "fenum:assignment.type.incompatible", // Type arguments don't match - "signedness:assignment.type.incompatible" // Type arguments don't match - }) - Class c = i.getClass(); + void context() { + Integer i = 4; + i.getClass(); + Class a = i.getClass(); + // Type arguments don't match + @SuppressWarnings("fenum:assignment.type.incompatible") + Class b = i.getClass(); + @SuppressWarnings({ + "fenum:assignment.type.incompatible", // Type arguments don't match + "signedness:assignment.type.incompatible" // Type arguments don't match + }) + Class c = i.getClass(); - Class d = i.getClass(); - // not legal Java; that is, does not type-check under Java rules - // Class e = i.getClass(); - } + Class d = i.getClass(); + // not legal Java; that is, does not type-check under Java rules + // Class e = i.getClass(); + } - void m(Date d) { - @SuppressWarnings("fenum:assignment.type.incompatible") - Class c = d.getClass(); - } + void m(Date d) { + @SuppressWarnings("fenum:assignment.type.incompatible") + Class c = d.getClass(); + } } diff --git a/framework/tests/all-systems/HiveCrash2.java b/framework/tests/all-systems/HiveCrash2.java index 259945201cc..c32b99a1412 100644 --- a/framework/tests/all-systems/HiveCrash2.java +++ b/framework/tests/all-systems/HiveCrash2.java @@ -1,17 +1,17 @@ @SuppressWarnings("all") // Just check for crashes. public class HiveCrash2 { - protected final MqttMessageEncoder[] encoders = new MqttMessageEncoder[16]; + protected final MqttMessageEncoder[] encoders = new MqttMessageEncoder[16]; - HiveCrash2(Mqtt3ConnectEncoder connectEncoder) { - encoders[0] = connectEncoder; - } + HiveCrash2(Mqtt3ConnectEncoder connectEncoder) { + encoders[0] = connectEncoder; + } - public static class Mqtt3ConnectEncoder extends MqttMessageEncoder {} + public static class Mqtt3ConnectEncoder extends MqttMessageEncoder {} - static class MqttStatefulConnect implements MqttMessage {} + static class MqttStatefulConnect implements MqttMessage {} - public abstract static class MqttMessageEncoder {} + public abstract static class MqttMessageEncoder {} - public interface MqttMessage {} + public interface MqttMessage {} } diff --git a/framework/tests/all-systems/InferAndIntersection.java b/framework/tests/all-systems/InferAndIntersection.java index d4105bbc802..5ae5b686c83 100644 --- a/framework/tests/all-systems/InferAndIntersection.java +++ b/framework/tests/all-systems/InferAndIntersection.java @@ -1,8 +1,8 @@ public class InferAndIntersection { - void toInfer(Iterable t) {} + void toInfer(Iterable t) {} - > void context(U u) { - toInfer(u); - } + > void context(U u) { + toInfer(u); + } } diff --git a/framework/tests/all-systems/InferAndWildcards.java b/framework/tests/all-systems/InferAndWildcards.java index fcb3f4b5066..aeb5dfa2787 100644 --- a/framework/tests/all-systems/InferAndWildcards.java +++ b/framework/tests/all-systems/InferAndWildcards.java @@ -1,10 +1,10 @@ @SuppressWarnings("interning") public class InferAndWildcards { - Class b(Class clazz) { - return clazz; - } + Class b(Class clazz) { + return clazz; + } - void a(Class clazz) { - Class v = b(clazz); - } + void a(Class clazz) { + Class v = b(clazz); + } } diff --git a/framework/tests/all-systems/InferNullType.java b/framework/tests/all-systems/InferNullType.java index 7b3bfb3d1e4..ad0727b602e 100644 --- a/framework/tests/all-systems/InferNullType.java +++ b/framework/tests/all-systems/InferNullType.java @@ -2,36 +2,36 @@ // errors public class InferNullType { - T toInfer(T input) { - return input; - } + T toInfer(T input) { + return input; + } - T toInfer2(T input) { - return input; - } + T toInfer2(T input) { + return input; + } - T toInfer3(T input, S p2) { - return input; - } + T toInfer3(T input, S p2) { + return input; + } - T toInfer4(T input, S p2) { - return input; - } + T toInfer4(T input, S p2) { + return input; + } - void x() { - @SuppressWarnings("nullness:type.arguments.not.inferred") - Object m = toInfer(null); - Object m2 = toInfer2(null); + void x() { + @SuppressWarnings("nullness:type.arguments.not.inferred") + Object m = toInfer(null); + Object m2 = toInfer2(null); - Object m3 = toInfer3(null, null); - Object m4 = toInfer3(1, null); - Object m5 = toInfer3(null, 1); + Object m3 = toInfer3(null, null); + Object m4 = toInfer3(1, null); + Object m5 = toInfer3(null, 1); - @SuppressWarnings("nullness:type.arguments.not.inferred") - Object m6 = toInfer4(null, null); - @SuppressWarnings("nullness:type.arguments.not.inferred") - Object m7 = toInfer4(1, null); - @SuppressWarnings("nullness:type.arguments.not.inferred") - Object m8 = toInfer4(null, 1); - } + @SuppressWarnings("nullness:type.arguments.not.inferred") + Object m6 = toInfer4(null, null); + @SuppressWarnings("nullness:type.arguments.not.inferred") + Object m7 = toInfer4(1, null); + @SuppressWarnings("nullness:type.arguments.not.inferred") + Object m8 = toInfer4(null, 1); + } } diff --git a/framework/tests/all-systems/InferTypeArgs.java b/framework/tests/all-systems/InferTypeArgs.java index a1fc726ec05..7e58392fb51 100644 --- a/framework/tests/all-systems/InferTypeArgs.java +++ b/framework/tests/all-systems/InferTypeArgs.java @@ -67,20 +67,20 @@ class CFAbstractValue> {} class CFAbstractAnalysis> {} class GenericAnnotatedTypeFactoryInferTypeArgs< - Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { + Value extends CFAbstractValue, FlowAnalysis extends CFAbstractAnalysis> { - @SuppressWarnings("immutability:type.argument.type.incompatible") - protected FlowAnalysis createFlowAnalysis() { - FlowAnalysis result = invokeConstructorFor(); - return result; - } + @SuppressWarnings("immutability:type.argument.type.incompatible") + protected FlowAnalysis createFlowAnalysis() { + FlowAnalysis result = invokeConstructorFor(); + return result; + } - @SuppressWarnings({ - "nullness:return.type.incompatible", - "lock:return.type.incompatible", - "immutabilitysub:type.argument.type.incompatible" - }) - public static T invokeConstructorFor() { - return null; - } + @SuppressWarnings({ + "nullness:return.type.incompatible", + "lock:return.type.incompatible", + "immutabilitysub:type.argument.type.incompatible" + }) + public static T invokeConstructorFor() { + return null; + } } diff --git a/framework/tests/all-systems/InferTypeArgs2.java b/framework/tests/all-systems/InferTypeArgs2.java index f7e6fc03bd9..241635f02e6 100644 --- a/framework/tests/all-systems/InferTypeArgs2.java +++ b/framework/tests/all-systems/InferTypeArgs2.java @@ -4,21 +4,21 @@ public class InferTypeArgs2> {} // InferTypeArgs1.java. In this case we end up comparing CFValue with V extends InferTypeArgs2 // which kicks off the DefaultRawnessComparer. Before I fixed it, it then blew the stack class CFValue extends InferTypeArgs2 { - public CFValue(InferTypeArgsAnalysis analysis) {} + public CFValue(InferTypeArgsAnalysis analysis) {} } class CFAbstractStore, S extends CFAbstractStore> {} class CFAbstractTransfer< - V extends InferTypeArgs2, - S extends CFAbstractStore, - T extends CFAbstractTransfer> {} + V extends InferTypeArgs2, + S extends CFAbstractStore, + T extends CFAbstractTransfer> {} class InferTypeArgsAnalysis< - V extends InferTypeArgs2, - S extends CFAbstractStore, - T extends CFAbstractTransfer> { - public CFValue defaultCreateAbstractValue(InferTypeArgsAnalysis analysis) { - return new CFValue(analysis); - } + V extends InferTypeArgs2, + S extends CFAbstractStore, + T extends CFAbstractTransfer> { + public CFValue defaultCreateAbstractValue(InferTypeArgsAnalysis analysis) { + return new CFValue(analysis); + } } diff --git a/framework/tests/all-systems/InferTypeArgs3.java b/framework/tests/all-systems/InferTypeArgs3.java index 3e5a51f26ee..6a4e46e6177 100644 --- a/framework/tests/all-systems/InferTypeArgs3.java +++ b/framework/tests/all-systems/InferTypeArgs3.java @@ -2,18 +2,18 @@ import java.util.HashSet; public class InferTypeArgs3 { - @SuppressWarnings({"deprecation", "removal", "cast.unsafe.constructor.invocation"}) - void test() { - java.util.Arrays.asList(new Integer(1), ""); - } + @SuppressWarnings({"deprecation", "removal", "cast.unsafe.constructor.invocation"}) + void test() { + java.util.Arrays.asList(new Integer(1), ""); + } - void test2() { - Integer i = Integer.valueOf(1); - java.util.Arrays.asList(i, ""); - } + void test2() { + Integer i = Integer.valueOf(1); + java.util.Arrays.asList(i, ""); + } - void foo() { - new HashSet<>(Arrays.asList(new Object())); - new HashSet(Arrays.asList(new Object())) {}; - } + void foo() { + new HashSet<>(Arrays.asList(new Object())); + new HashSet(Arrays.asList(new Object())) {}; + } } diff --git a/framework/tests/all-systems/InferTypeArgsConditionalExpression.java b/framework/tests/all-systems/InferTypeArgsConditionalExpression.java index 216a6806a8c..1d83873aac6 100644 --- a/framework/tests/all-systems/InferTypeArgsConditionalExpression.java +++ b/framework/tests/all-systems/InferTypeArgsConditionalExpression.java @@ -5,11 +5,11 @@ public class InferTypeArgsConditionalExpression { - public void foo(Generic real, Generic other, boolean flag) { - bar(flag ? real : other); - } + public void foo(Generic real, Generic other, boolean flag) { + bar(flag ? real : other); + } - void bar(Generic param) {} + void bar(Generic param) {} - interface Generic {} + interface Generic {} } diff --git a/framework/tests/all-systems/InitializationVisitor.java b/framework/tests/all-systems/InitializationVisitor.java index c574139e712..4fe9db658b0 100644 --- a/framework/tests/all-systems/InitializationVisitor.java +++ b/framework/tests/all-systems/InitializationVisitor.java @@ -1,11 +1,11 @@ // Minimized test case from InitializationVisitor. class IATF< - Value extends CFAV, - Store extends IS, - Transfer extends IT, - Flow extends CFAA> - extends GATF {} + Value extends CFAV, + Store extends IS, + Transfer extends IT, + Flow extends CFAA> + extends GATF {} class CFAV> {} @@ -20,15 +20,15 @@ class CFAT, S extends CFAS, T extends CFAT> {} class CFAS, S extends CFAS> {} class GATF< - Value extends CFAV, - Store extends CFAS, - TransferFunction extends CFAT, - FlowAnalysis extends CFAA> {} + Value extends CFAV, + Store extends CFAS, + TransferFunction extends CFAT, + FlowAnalysis extends CFAA> {} class BTV> {} class IV< - Factory extends IATF, - Value extends CFAV, - Store extends IS> - extends BTV {} + Factory extends IATF, + Value extends CFAV, + Store extends IS> + extends BTV {} diff --git a/framework/tests/all-systems/InstanceOf.java b/framework/tests/all-systems/InstanceOf.java index c0e79eb8c01..2f01b9935e4 100644 --- a/framework/tests/all-systems/InstanceOf.java +++ b/framework/tests/all-systems/InstanceOf.java @@ -8,23 +8,23 @@ class GETFIELD extends FieldInstruction {} class PUTFIELD extends FieldInstruction {} public class InstanceOf { - public void emptyGLB(FieldInstruction f) { - if (f instanceof GETFIELD || f instanceof PUTFIELD) { - if (f instanceof PUTFIELD) { - // During org.checkerframework.dataflow analysis, we can believe that f is both a - // GETFIELD and a PUTFIELD, which yields an empty GLB. Once - // org.checkerframework.dataflow converges, it will know that f is a PUTFIELD. - return; - } - return; + public void emptyGLB(FieldInstruction f) { + if (f instanceof GETFIELD || f instanceof PUTFIELD) { + if (f instanceof PUTFIELD) { + // During org.checkerframework.dataflow analysis, we can believe that f is both a + // GETFIELD and a PUTFIELD, which yields an empty GLB. Once + // org.checkerframework.dataflow converges, it will know that f is a PUTFIELD. + return; + } + return; + } } - } - public boolean assignInstanceOf(Object obj) { - // We fixed a bug where the type in an instanceof expression - // like Class was stored as the abstract value result of - // the expression. - boolean is_class = obj instanceof Class; - return is_class; - } + public boolean assignInstanceOf(Object obj) { + // We fixed a bug where the type in an instanceof expression + // like Class was stored as the abstract value result of + // the expression. + boolean is_class = obj instanceof Class; + return is_class; + } } diff --git a/framework/tests/all-systems/IntersectionTypes.java b/framework/tests/all-systems/IntersectionTypes.java index 3f2b4d30f41..610d33bd6df 100644 --- a/framework/tests/all-systems/IntersectionTypes.java +++ b/framework/tests/all-systems/IntersectionTypes.java @@ -8,10 +8,10 @@ interface Bar {} class Baz implements Foo, Bar {} public class IntersectionTypes { - void foo() { - Baz baz = new Baz(); - call(baz); - } + void foo() { + Baz baz = new Baz(); + call(baz); + } - void call(T p) {} + void call(T p) {} } diff --git a/framework/tests/all-systems/IsSubarrayEq.java b/framework/tests/all-systems/IsSubarrayEq.java index c2cc270f654..2d0d0700d50 100644 --- a/framework/tests/all-systems/IsSubarrayEq.java +++ b/framework/tests/all-systems/IsSubarrayEq.java @@ -1,14 +1,15 @@ -import java.util.List; import org.checkerframework.common.value.qual.MinLen; +import java.util.List; + @SuppressWarnings("ainfertest") // only check WPI for crashes public class IsSubarrayEq { - // the Interning checker correctly issues an error below, but we would like to keep this test in - // all-systems. - // Fenum Checker should not issue a warning. See issue 789 - // https://github.com/typetools/checker-framework/issues/789 - @SuppressWarnings({"interning", "fenum:return.type.incompatible"}) - public static boolean isSubarrayEq(Object @MinLen(1) [] a, List sub) { - return (sub.get(0) != a[0]); - } + // the Interning checker correctly issues an error below, but we would like to keep this test in + // all-systems. + // Fenum Checker should not issue a warning. See issue 789 + // https://github.com/typetools/checker-framework/issues/789 + @SuppressWarnings({"interning", "fenum:return.type.incompatible"}) + public static boolean isSubarrayEq(Object @MinLen(1) [] a, List sub) { + return (sub.get(0) != a[0]); + } } diff --git a/framework/tests/all-systems/Issue1003.java b/framework/tests/all-systems/Issue1003.java index d4642faa37e..d5562462a5b 100644 --- a/framework/tests/all-systems/Issue1003.java +++ b/framework/tests/all-systems/Issue1003.java @@ -4,18 +4,18 @@ // The fact that M extends Issue1003 is unimportant, // I'm just doing this to wrap it up into a single class example: public class Issue1003 { - public int field = 0; + public int field = 0; - public M getFoo() { - throw new RuntimeException(); - } + public M getFoo() { + throw new RuntimeException(); + } - public static void methodMemberAccess() { - // Use of raw generics: - Issue1003 m = new Issue1003(); - // This version causes error but not exception: - // Issue1003 m = new Issue1003<>(); - // Exception caused by this line: - int x = m.getFoo().field; - } + public static void methodMemberAccess() { + // Use of raw generics: + Issue1003 m = new Issue1003(); + // This version causes error but not exception: + // Issue1003 m = new Issue1003<>(); + // Exception caused by this line: + int x = m.getFoo().field; + } } diff --git a/framework/tests/all-systems/Issue1006.java b/framework/tests/all-systems/Issue1006.java index dfa7a9351da..5010b16c6e8 100644 --- a/framework/tests/all-systems/Issue1006.java +++ b/framework/tests/all-systems/Issue1006.java @@ -8,13 +8,13 @@ @SuppressWarnings("all") // Ignore type-checking errors. public class Issue1006 { - void foo(Stream m, Map im) { - Map l = m.collect(Collectors.toMap(Function.identity(), im::get)); - } + void foo(Stream m, Map im) { + Map l = m.collect(Collectors.toMap(Function.identity(), im::get)); + } - // alternative version with same crash - Map bar(String src) { - return Stream.of(src) - .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); - } + // alternative version with same crash + Map bar(String src) { + return Stream.of(src) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + } } diff --git a/framework/tests/all-systems/Issue1039.java b/framework/tests/all-systems/Issue1039.java index ee7c1c8e085..48207b606c7 100644 --- a/framework/tests/all-systems/Issue1039.java +++ b/framework/tests/all-systems/Issue1039.java @@ -2,7 +2,7 @@ // https://github.com/typetools/checker-framework/issues/1039 public class Issue1039> { - Issue1039 foo() { - return new Issue1039<>(); - } + Issue1039 foo() { + return new Issue1039<>(); + } } diff --git a/framework/tests/all-systems/Issue1043.java b/framework/tests/all-systems/Issue1043.java index 218ac117c08..a9f5deb67c6 100644 --- a/framework/tests/all-systems/Issue1043.java +++ b/framework/tests/all-systems/Issue1043.java @@ -2,14 +2,14 @@ // https://github.com/typetools/checker-framework/issues/1043 public class Issue1043 { - boolean foo(Class p) { - return true; - } + boolean foo(Class p) { + return true; + } - void bar(Object p) {} + void bar(Object p) {} - @SuppressWarnings("keyfor:type.argument") - void baz() { - bar(foo(this.getClass()) ? "a" : "b"); - } + @SuppressWarnings("keyfor:type.argument") + void baz() { + bar(foo(this.getClass()) ? "a" : "b"); + } } diff --git a/framework/tests/all-systems/Issue1049.java b/framework/tests/all-systems/Issue1049.java index 06bc82f7648..d2e128d0dce 100644 --- a/framework/tests/all-systems/Issue1049.java +++ b/framework/tests/all-systems/Issue1049.java @@ -1,12 +1,12 @@ // Test case for Issue #1049 // https://github.com/typetools/checker-framework/issues/1049 public class Issue1049 { - interface Gen> { - T get(); - } + interface Gen> { + T get(); + } - @SuppressWarnings("nulltest.redundant") - void bar(Gen g) { - Gen l = g.get() != null ? g.get() : g; - } + @SuppressWarnings("nulltest.redundant") + void bar(Gen g) { + Gen l = g.get() != null ? g.get() : g; + } } diff --git a/framework/tests/all-systems/Issue1102.java b/framework/tests/all-systems/Issue1102.java index f1e5dc52f0d..9ecdc1a501e 100644 --- a/framework/tests/all-systems/Issue1102.java +++ b/framework/tests/all-systems/Issue1102.java @@ -7,16 +7,17 @@ interface Issue1102Itf {} class Issue1102Base {} class Issue1102Decl extends Issue1102Base { - static Issue1102Decl newInstance(T s) { - return new Issue1102Decl(); - } + static Issue1102Decl newInstance( + T s) { + return new Issue1102Decl(); + } } @SuppressWarnings("all") // Only interested in possible crash class Issue1102Use { - U f; + U f; - void bar() { - Issue1102Decl d = Issue1102Decl.newInstance(f); - } + void bar() { + Issue1102Decl d = Issue1102Decl.newInstance(f); + } } diff --git a/framework/tests/all-systems/Issue1111.java b/framework/tests/all-systems/Issue1111.java index 122b9ecb9d6..ff7a9159bdc 100644 --- a/framework/tests/all-systems/Issue1111.java +++ b/framework/tests/all-systems/Issue1111.java @@ -6,11 +6,11 @@ @SuppressWarnings("all") // just check for crash public class Issue1111 { - void foo(Box box, List list) { - bar(box, list); - } + void foo(Box box, List list) { + bar(box, list); + } - void bar(Box box, Iterable list) {} + void bar(Box box, Iterable list) {} - class Box {} + class Box {} } diff --git a/framework/tests/all-systems/Issue116Graph.java b/framework/tests/all-systems/Issue116Graph.java index b8a836bfb6d..57cd09d1eb2 100644 --- a/framework/tests/all-systems/Issue116Graph.java +++ b/framework/tests/all-systems/Issue116Graph.java @@ -9,4 +9,4 @@ class Issue116Edge, GrEdgeType extends Issue116Edge> {} + GrNodeType extends Issue116Node, GrEdgeType extends Issue116Edge> {} diff --git a/framework/tests/all-systems/Issue1274.java b/framework/tests/all-systems/Issue1274.java index d7c5f90bd3f..ca3efcb14d4 100644 --- a/framework/tests/all-systems/Issue1274.java +++ b/framework/tests/all-systems/Issue1274.java @@ -3,21 +3,21 @@ @SuppressWarnings("all") // Just check for crashes public class Issue1274 { - static class Mine { - static Mine of(S p1, S p2) { - return null; + static class Mine { + static Mine of(S p1, S p2) { + return null; + } } - } - class Two {} + class Two {} - class C extends Two {} + class C extends Two {} - class D extends Two {} + class D extends Two {} - class Crash { - { - Mine.of(new C(), new D()); + class Crash { + { + Mine.of(new C(), new D()); + } } - } } diff --git a/framework/tests/all-systems/Issue1431.java b/framework/tests/all-systems/Issue1431.java index 400acac7c3d..d228e8d8736 100644 --- a/framework/tests/all-systems/Issue1431.java +++ b/framework/tests/all-systems/Issue1431.java @@ -1,13 +1,13 @@ // Test case for Issue 1431 // https://github.com/typetools/checker-framework/issues/1431 public class Issue1431 { - static class Outer { - class Inner {} - } + static class Outer { + class Inner {} + } - Outer.Inner ic; + Outer.Inner ic; - Issue1431(Outer.Inner ic) { - this.ic = ic; - } + Issue1431(Outer.Inner ic) { + this.ic = ic; + } } diff --git a/framework/tests/all-systems/Issue1442.java b/framework/tests/all-systems/Issue1442.java index 0ea99fcfeed..61519502c5c 100644 --- a/framework/tests/all-systems/Issue1442.java +++ b/framework/tests/all-systems/Issue1442.java @@ -2,33 +2,33 @@ // https://github.com/typetools/checker-framework/issues/1442 public class Issue1442 { - protected void configure(SubConfig.SubConfigInner x) { - SubMyClass subMyClass = x.getT().getSubConfigInner().outerClassTypeVar(); - } + protected void configure(SubConfig.SubConfigInner x) { + SubMyClass subMyClass = x.getT().getSubConfigInner().outerClassTypeVar(); + } - static class MyClass> {} + static class MyClass> {} - static class SubMyClass extends MyClass {} + static class SubMyClass extends MyClass {} - static class Config> { - class ConfigInner { - public T getT() { - throw new RuntimeException(); - } + static class Config> { + class ConfigInner { + public T getT() { + throw new RuntimeException(); + } + } } - } - static class SubConfig> extends Config { - public class SubConfigInner extends ConfigInner { - public C outerClassTypeVar() { - throw new RuntimeException(); - } - } + static class SubConfig> extends Config { + public class SubConfigInner extends ConfigInner { + public C outerClassTypeVar() { + throw new RuntimeException(); + } + } - class Thing { - public SubConfigInner getSubConfigInner() { - throw new RuntimeException(); - } + class Thing { + public SubConfigInner getSubConfigInner() { + throw new RuntimeException(); + } + } } - } } diff --git a/framework/tests/all-systems/Issue1506.java b/framework/tests/all-systems/Issue1506.java index 6c360a36bab..94898a09213 100644 --- a/framework/tests/all-systems/Issue1506.java +++ b/framework/tests/all-systems/Issue1506.java @@ -6,12 +6,12 @@ @SuppressWarnings("all") // just check for crashes. public class Issue1506 { - static void m() { - ArrayList l = new ArrayList<>(); - try { - throw new IOException(); - } catch (RuntimeException | IOException e) { - l.add(e); + static void m() { + ArrayList l = new ArrayList<>(); + try { + throw new IOException(); + } catch (RuntimeException | IOException e) { + l.add(e); + } } - } } diff --git a/framework/tests/all-systems/Issue1520.java b/framework/tests/all-systems/Issue1520.java index 387df8274d5..43f22806467 100644 --- a/framework/tests/all-systems/Issue1520.java +++ b/framework/tests/all-systems/Issue1520.java @@ -4,32 +4,32 @@ import java.io.IOException; public class Issue1520 { - void start() { - new Runnable() { - public void run() { - try { - _run(); - } finally { - signal(); // Evaluating this node type as member of implicit `this` will throw - // NPE - } - } - }; - } + void start() { + new Runnable() { + public void run() { + try { + _run(); + } finally { + signal(); // Evaluating this node type as member of implicit `this` will throw + // NPE + } + } + }; + } - void signal() {} + void signal() {} - void _run() {} + void _run() {} - static class Inner {} + static class Inner {} - void test2() throws IOException { - try { - throwIO(); - } finally { - Inner inner = new Inner(); + void test2() throws IOException { + try { + throwIO(); + } finally { + Inner inner = new Inner(); + } } - } - void throwIO() throws IOException {} + void throwIO() throws IOException {} } diff --git a/framework/tests/all-systems/Issue1526.java b/framework/tests/all-systems/Issue1526.java index 100c860fdfa..cc2c670e3c7 100644 --- a/framework/tests/all-systems/Issue1526.java +++ b/framework/tests/all-systems/Issue1526.java @@ -1,17 +1,17 @@ // Tet case for Issue 1526. // https://github.com/typetools/checker-framework/issues/1526 public class Issue1526 { - public T get(T t) { - return this.get(t); - } + public T get(T t) { + return this.get(t); + } - public T method(T[] t) { - return this.method(t); - } + public T method(T[] t) { + return this.method(t); + } - public T method2(Gen t) { - return this.method2(t); - } + public T method2(Gen t) { + return this.method2(t); + } - static class Gen {} + static class Gen {} } diff --git a/framework/tests/all-systems/Issue1543.java b/framework/tests/all-systems/Issue1543.java index 6fe00114304..c2a2f0d195c 100644 --- a/framework/tests/all-systems/Issue1543.java +++ b/framework/tests/all-systems/Issue1543.java @@ -2,14 +2,14 @@ // https://github.com/typetools/checker-framework/issues/1543 @SuppressWarnings("all") // check for crashes only public class Issue1543 { - static class BClass {} + static class BClass {} - interface AInterface {} + interface AInterface {} - static class GClass & AInterface> {} + static class GClass & AInterface> {} - static class Test { - GClass gClassRaw; - GClass gClassWC; - } + static class Test { + GClass gClassRaw; + GClass gClassWC; + } } diff --git a/framework/tests/all-systems/Issue1546.java b/framework/tests/all-systems/Issue1546.java index bf7c08d6306..4bbd03976df 100644 --- a/framework/tests/all-systems/Issue1546.java +++ b/framework/tests/all-systems/Issue1546.java @@ -3,15 +3,15 @@ @SuppressWarnings("all") // check for crashes public class Issue1546 { - void m(T t) {} + void m(T t) {} - { - try { - new Runnable() { - public void run() {} - }; - } finally { - m("Hi"); + { + try { + new Runnable() { + public void run() {} + }; + } finally { + m("Hi"); + } } - } } diff --git a/framework/tests/all-systems/Issue1586.java b/framework/tests/all-systems/Issue1586.java index 24235fa49ae..acb2430735a 100644 --- a/framework/tests/all-systems/Issue1586.java +++ b/framework/tests/all-systems/Issue1586.java @@ -4,19 +4,19 @@ import java.util.concurrent.ExecutorService; public class Issue1586 { - void f(ExecutorService es) { - es.execute( - () -> { - try { - System.err.println(); - } catch (Throwable throwable) { - System.err.println(); - } finally { - es.execute( + void f(ExecutorService es) { + es.execute( () -> { - System.err.println(); + try { + System.err.println(); + } catch (Throwable throwable) { + System.err.println(); + } finally { + es.execute( + () -> { + System.err.println(); + }); + } }); - } - }); - } + } } diff --git a/framework/tests/all-systems/Issue1587.java b/framework/tests/all-systems/Issue1587.java index f74977619b2..7f0cdf2cb48 100644 --- a/framework/tests/all-systems/Issue1587.java +++ b/framework/tests/all-systems/Issue1587.java @@ -1,28 +1,28 @@ abstract class Issue1587 { - static class MyObject {} + static class MyObject {} - interface Six, R> { - T d(); + interface Six, R> { + T d(); - Iterable q(); - } + Iterable q(); + } - abstract Six e(Object entity); + abstract Six e(Object entity); - public void method(MyObject one) { - g(e(one).d().q()); - } + public void method(MyObject one) { + g(e(one).d().q()); + } - abstract Iterable g(Iterable r); + abstract Iterable g(Iterable r); - interface Class2> {} + interface Class2> {} - static class Class1> { + static class Class1> { - static void test() {} + static void test() {} - void use() { - Class1.test(); + void use() { + Class1.test(); + } } - } } diff --git a/framework/tests/all-systems/Issue1587b.java b/framework/tests/all-systems/Issue1587b.java index ff3b1aa5245..8f50472f132 100644 --- a/framework/tests/all-systems/Issue1587b.java +++ b/framework/tests/all-systems/Issue1587b.java @@ -1,33 +1,33 @@ abstract class Issue1587b { - static class One implements Two {} + static class One implements Two {} - interface Two< - E extends Two, K extends Five, C extends Enum, I extends Enum> {} + interface Two< + E extends Two, K extends Five, C extends Enum, I extends Enum> {} - abstract static class Three implements Five {} + abstract static class Three implements Five {} - enum Four {} + enum Four {} - interface Five> extends Comparable {} + interface Five> extends Comparable {} - interface Six { - , I extends Enum> Seven e(Class entity); - } + interface Six { + , I extends Enum> Seven e(Class entity); + } - interface Seven, E extends Two> extends Eight {} + interface Seven, E extends Two> extends Eight {} - interface Eight, R, E extends Two> extends Nine {} + interface Eight, R, E extends Two> extends Nine {} - interface Nine, R> { - T d(); + interface Nine, R> { + T d(); - Iterable q(); - } + Iterable q(); + } - public Iterable f(Six e) { - return g(e.e(One.class).d().q()); - } + public Iterable f(Six e) { + return g(e.e(One.class).d().q()); + } - abstract Iterable g(Iterable r); + abstract Iterable g(Iterable r); } diff --git a/framework/tests/all-systems/Issue1690.java b/framework/tests/all-systems/Issue1690.java index bf7ffe90205..e275c8cc792 100644 --- a/framework/tests/all-systems/Issue1690.java +++ b/framework/tests/all-systems/Issue1690.java @@ -5,10 +5,10 @@ public class Issue1690 { - public Issue1690() {} + public Issue1690() {} - // Can be an inner type or in its own file, shouldn't matter. - public static interface Issue16902 { - public R getR(); - } + // Can be an inner type or in its own file, shouldn't matter. + public static interface Issue16902 { + public R getR(); + } } diff --git a/framework/tests/all-systems/Issue1696.java b/framework/tests/all-systems/Issue1696.java index 289f1ffc005..9c8aee47fee 100644 --- a/framework/tests/all-systems/Issue1696.java +++ b/framework/tests/all-systems/Issue1696.java @@ -2,7 +2,7 @@ // https://github.com/typetools/checker-framework/issues/1696 public class Issue1696 { - interface I, S> {} + interface I, S> {} - void f(I x) {} + void f(I x) {} } diff --git a/framework/tests/all-systems/Issue1697.java b/framework/tests/all-systems/Issue1697.java index 001bddf7915..a70d04f160a 100644 --- a/framework/tests/all-systems/Issue1697.java +++ b/framework/tests/all-systems/Issue1697.java @@ -3,29 +3,29 @@ public class Issue1697 { - interface G { - A h(byte[] l); - } + interface G { + A h(byte[] l); + } - interface F {} + interface F {} - interface E extends F { - interface M extends F {} - } + interface E extends F { + interface M extends F {} + } - abstract static class D, B extends D.M> implements E { - abstract static class M, B extends M> implements E.M {} - } + abstract static class D, B extends D.M> implements E { + abstract static class M, B extends M> implements E.M {} + } - abstract static class C, B extends C.M> extends D { - abstract static class M, B extends M> extends D.M {} - } + abstract static class C, B extends C.M> extends D { + abstract static class M, B extends M> extends D.M {} + } - static class W> { - private W(T proto) {} - } + static class W> { + private W(T proto) {} + } - > W i(G j, byte[] k) { - return new W<>(j.h(k)); - } + > W i(G j, byte[] k) { + return new W<>(j.h(k)); + } } diff --git a/framework/tests/all-systems/Issue1698.java b/framework/tests/all-systems/Issue1698.java index ac759342671..d0809bd6e51 100644 --- a/framework/tests/all-systems/Issue1698.java +++ b/framework/tests/all-systems/Issue1698.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/1698 public class Issue1698 { - static class B { - static C f() { - return new B().new C(); - } + static class B { + static C f() { + return new B().new C(); + } - class C {} - } + class C {} + } } diff --git a/framework/tests/all-systems/Issue1708.java b/framework/tests/all-systems/Issue1708.java index eea4466b511..e7b98be68a6 100644 --- a/framework/tests/all-systems/Issue1708.java +++ b/framework/tests/all-systems/Issue1708.java @@ -6,42 +6,42 @@ import java.util.List; @SuppressWarnings({ - "unchecked", - "ainfertest", - "value" + "unchecked", + "ainfertest", + "value" }) // Don't issue warnings during ainfer tests, because more than one round of inference is required // for the value checker public class Issue1708 { - static class A {} + static class A {} - static class B {} + static class B {} - static class C {} + static class C {} - static class D {} + static class D {} - static class E {} + static class E {} - static class F {} + static class F {} - static class B1 extends B {} + static class B1 extends B {} - static class B2 extends B {} + static class B2 extends B {} - static class B3 extends B {} + static class B3 extends B {} - public static class Example extends A> { - private final List> f; + public static class Example extends A> { + private final List> f; - public Example(B1 b1, B2 b2, B3 b3) { - f = ListUtil.of(b1, b2, b3); + public Example(B1 b1, B2 b2, B3 b3) { + f = ListUtil.of(b1, b2, b3); + } } - } - public static class ListUtil { - public static List of(T... elems) { - return new ArrayList<>(Arrays.asList(elems)); + public static class ListUtil { + public static List of(T... elems) { + return new ArrayList<>(Arrays.asList(elems)); + } } - } } diff --git a/framework/tests/all-systems/Issue1709.java b/framework/tests/all-systems/Issue1709.java index 807817b4821..dd90537e311 100644 --- a/framework/tests/all-systems/Issue1709.java +++ b/framework/tests/all-systems/Issue1709.java @@ -5,7 +5,7 @@ import java.util.List; public class Issue1709 { - public static void m(final List l) { - Iterator it = l.iterator(); - } + public static void m(final List l) { + Iterator it = l.iterator(); + } } diff --git a/framework/tests/all-systems/Issue1738.java b/framework/tests/all-systems/Issue1738.java index 2e63033a67d..2a6321f9895 100644 --- a/framework/tests/all-systems/Issue1738.java +++ b/framework/tests/all-systems/Issue1738.java @@ -5,29 +5,29 @@ @SuppressWarnings("all") // Only check for crashes public class Issue1738 { - static class TwoParamIterator implements Iterator { - @Override - public boolean hasNext() { - return false; - } + static class TwoParamIterator implements Iterator { + @Override + public boolean hasNext() { + return false; + } - @Override - public T next() { - return null; + @Override + public T next() { + return null; + } } - } - static class TwoParamCollection implements Iterable { - @Override - public TwoParamIterator iterator() { - return new TwoParamIterator(); + static class TwoParamCollection implements Iterable { + @Override + public TwoParamIterator iterator() { + return new TwoParamIterator(); + } } - } - static void test() { - TwoParamCollection c = new TwoParamCollection<>(); - for (String s : c) { - s.hashCode(); + static void test() { + TwoParamCollection c = new TwoParamCollection<>(); + for (String s : c) { + s.hashCode(); + } } - } } diff --git a/framework/tests/all-systems/Issue1749.java b/framework/tests/all-systems/Issue1749.java index 71efbc83517..bc9605f7d44 100644 --- a/framework/tests/all-systems/Issue1749.java +++ b/framework/tests/all-systems/Issue1749.java @@ -2,15 +2,15 @@ // https://github.com/typetools/checker-framework/issues/1749 abstract class Issue1749 { - public interface A {} + public interface A {} - interface B extends A {} + interface B extends A {} - public class I {} + public class I {} - abstract I f(Class x); + abstract I f(Class x); - void f() { - I x = f(A.class); - } + void f() { + I x = f(A.class); + } } diff --git a/framework/tests/all-systems/Issue1809.java b/framework/tests/all-systems/Issue1809.java index fc3f89d7403..e43737d4cc5 100644 --- a/framework/tests/all-systems/Issue1809.java +++ b/framework/tests/all-systems/Issue1809.java @@ -12,28 +12,28 @@ @SuppressWarnings("unchecked") abstract class Issue1809 { - abstract Stream concat(Stream... streams); + abstract Stream concat(Stream... streams); - abstract Optional f(); + abstract Optional f(); - private static class A {} + private static class A {} - interface B { - List g(); - } + interface B { + List g(); + } - interface C { - List h(); - } + interface C { + List h(); + } - interface S {} + interface S {} - // The Checker Framework does not refine the type of Stream#filter based on post conditions of - // the passed function. - @SuppressWarnings({"nullness", "optional"}) - private Stream xrefsFor(B b) { - return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) - .filter(Optional::isPresent) - .map(Optional::get); - } + // The Checker Framework does not refine the type of Stream#filter based on post conditions of + // the passed function. + @SuppressWarnings({"nullness", "optional"}) + private Stream xrefsFor(B b) { + return concat(b.g().stream().flatMap(a -> a.h().stream().map(c -> f()))) + .filter(Optional::isPresent) + .map(Optional::get); + } } diff --git a/framework/tests/all-systems/Issue1865.java b/framework/tests/all-systems/Issue1865.java index 701c8328ef6..f0a7e55f415 100644 --- a/framework/tests/all-systems/Issue1865.java +++ b/framework/tests/all-systems/Issue1865.java @@ -3,21 +3,21 @@ abstract class Issue1865 { - // Widening conversion + // Widening conversion - abstract int f(); + abstract int f(); - abstract int max(int... array); + abstract int max(int... array); - void g() { - long l = max(f(), f()); - } + void g() { + long l = max(f(), f()); + } - // String conversion + // String conversion - abstract Object h(Object... args); + abstract Object h(Object... args); - void i() { - Object o = "" + h(); - } + void i() { + Object o = "" + h(); + } } diff --git a/framework/tests/all-systems/Issue1867.java b/framework/tests/all-systems/Issue1867.java index 6fbc00f696c..9b93af43cf2 100644 --- a/framework/tests/all-systems/Issue1867.java +++ b/framework/tests/all-systems/Issue1867.java @@ -4,17 +4,17 @@ import java.util.List; public abstract class Issue1867 { - interface AInterface {} + interface AInterface {} - interface BInterface { - List g(); - } + interface BInterface { + List g(); + } - abstract List> h(); + abstract List> h(); - void f() { - for (BInterface x : h()) { - for (AInterface y : x.g()) {} + void f() { + for (BInterface x : h()) { + for (AInterface y : x.g()) {} + } } - } } diff --git a/framework/tests/all-systems/Issue1920.java b/framework/tests/all-systems/Issue1920.java index 3fa235fd729..0d8bb0f7a19 100644 --- a/framework/tests/all-systems/Issue1920.java +++ b/framework/tests/all-systems/Issue1920.java @@ -6,25 +6,25 @@ @SuppressWarnings("all") // Only check for crashes public class Issue1920 { - static class Foo implements Iterable { - public Iterator iterator() { - return new Iterator() { - @Override - public boolean hasNext() { - return false; - } + static class Foo implements Iterable { + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return false; + } - @Override - public Object next() { - throw new NoSuchElementException(); + @Override + public Object next() { + throw new NoSuchElementException(); + } + }; } - }; } - } - static void testErasedIterator(Foo foo) { - for (Object x : foo) { - x.hashCode(); + static void testErasedIterator(Foo foo) { + for (Object x : foo) { + x.hashCode(); + } } - } } diff --git a/framework/tests/all-systems/Issue1948.java b/framework/tests/all-systems/Issue1948.java index 24a453afe88..01640753f5a 100644 --- a/framework/tests/all-systems/Issue1948.java +++ b/framework/tests/all-systems/Issue1948.java @@ -11,149 +11,152 @@ @SuppressWarnings("all") // ensure no crash public class Issue1948< - K, V, E extends Issue1948.MyEntry, S extends Issue1948.MyClass> - implements ConcurrentMap { + K, V, E extends Issue1948.MyEntry, S extends Issue1948.MyClass> + implements ConcurrentMap { - private Issue1948(MapMaker builder, InternalEntryHelper entryHelper) {} + private Issue1948(MapMaker builder, InternalEntryHelper entryHelper) {} - /** Returns a fresh {@link Issue1948} as specified by the given {@code builder}. */ - static Issue1948, ?> create(MapMaker builder) { - return new Issue1948<>(builder, Helper.instance()); - } + /** Returns a fresh {@link Issue1948} as specified by the given {@code builder}. */ + static Issue1948, ?> create(MapMaker builder) { + return new Issue1948<>(builder, Helper.instance()); + } - interface MyEntry> {} + interface MyEntry> {} - abstract static class MyClass, S extends MyClass> {} + abstract static class MyClass< + K, V, E extends MyEntry, S extends MyClass> {} - static final class MapMaker {} + static final class MapMaker {} - static final class Helper - implements InternalEntryHelper< - K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueMyClass> { - static Helper instance() { - return null; + static final class Helper + implements InternalEntryHelper< + K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueMyClass> { + static Helper instance() { + return null; + } } - } - interface InternalEntryHelper, S> {} + interface InternalEntryHelper, S> {} + + abstract static class StrongKeyStrongValueEntry + extends AbstractStrongKeyEntry> + implements StrongValueEntry> {} + + abstract static class AbstractStrongKeyEntry> + implements MyEntry {} - abstract static class StrongKeyStrongValueEntry - extends AbstractStrongKeyEntry> - implements StrongValueEntry> {} + interface StrongValueEntry> extends MyEntry {} - abstract static class AbstractStrongKeyEntry> - implements MyEntry {} + abstract static class StrongKeyStrongValueMyClass + extends MyClass< + K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueMyClass> {} - interface StrongValueEntry> extends MyEntry {} + @Override + public int size() { + return 0; + } - abstract static class StrongKeyStrongValueMyClass - extends MyClass, StrongKeyStrongValueMyClass> {} + @Override + public boolean isEmpty() { + return false; + } - @Override - public int size() { - return 0; - } + @Override + public boolean containsKey(Object key) { + return false; + } - @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean containsKey(Object key) { - return false; - } - - @Override - public boolean containsValue(Object value) { - return false; - } - - @Override - public V get(Object key) { - return null; - } - - @Override - public V put(K key, V value) { - return null; - } - - @Override - public V remove(Object key) { - return null; - } - - @Override - public void putAll(Map m) {} - - @Override - public void clear() {} - - @Override - public Set keySet() { - return null; - } - - @Override - public Collection values() { - return null; - } - - @Override - public Set> entrySet() { - return null; - } - - @Override - public V getOrDefault(Object key, V defaultValue) { - return null; - } - - @Override - public void forEach(BiConsumer action) {} - - @Override - public V putIfAbsent(K key, V value) { - return null; - } - - @Override - public boolean remove(Object key, Object value) { - return false; - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - return false; - } - - @Override - public V replace(K key, V value) { - return null; - } - - @Override - public void replaceAll(BiFunction function) {} - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return null; - } - - @Override - public V computeIfPresent( - K key, BiFunction remappingFunction) { - return null; - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - return null; - } - - @Override - public V merge(K key, V value, BiFunction remappingFunction) { - return null; - } + @Override + public boolean containsValue(Object value) { + return false; + } + + @Override + public V get(Object key) { + return null; + } + + @Override + public V put(K key, V value) { + return null; + } + + @Override + public V remove(Object key) { + return null; + } + + @Override + public void putAll(Map m) {} + + @Override + public void clear() {} + + @Override + public Set keySet() { + return null; + } + + @Override + public Collection values() { + return null; + } + + @Override + public Set> entrySet() { + return null; + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + return null; + } + + @Override + public void forEach(BiConsumer action) {} + + @Override + public V putIfAbsent(K key, V value) { + return null; + } + + @Override + public boolean remove(Object key, Object value) { + return false; + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return false; + } + + @Override + public V replace(K key, V value) { + return null; + } + + @Override + public void replaceAll(BiFunction function) {} + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return null; + } + + @Override + public V computeIfPresent( + K key, BiFunction remappingFunction) { + return null; + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return null; + } + + @Override + public V merge( + K key, V value, BiFunction remappingFunction) { + return null; + } } diff --git a/framework/tests/all-systems/Issue1991.java b/framework/tests/all-systems/Issue1991.java index 32208aed70b..707a46b346b 100644 --- a/framework/tests/all-systems/Issue1991.java +++ b/framework/tests/all-systems/Issue1991.java @@ -3,13 +3,13 @@ @SuppressWarnings("all") // Check for crashes only public class Issue1991 { - interface Comp> {} + interface Comp> {} - interface C> {} + interface C> {} - class D implements Comp {} + class D implements Comp {} - void f(C p) { - C x = p; - } + void f(C p) { + C x = p; + } } diff --git a/framework/tests/all-systems/Issue1991Full.java b/framework/tests/all-systems/Issue1991Full.java index 9b89b24d88b..017235e9fab 100644 --- a/framework/tests/all-systems/Issue1991Full.java +++ b/framework/tests/all-systems/Issue1991Full.java @@ -6,21 +6,21 @@ @SuppressWarnings("all") // Check for crashes only abstract class Issue1991Full { - abstract void g(A obj); + abstract void g(A obj); - static class A { - A(C c) {} - } + static class A { + A(C c) {} + } - interface B extends C {} + interface B extends C {} - interface C, Y extends Serializable> {} + interface C, Y extends Serializable> {} - public class E implements Serializable {} + public class E implements Serializable {} - abstract static class D implements Comparable, Serializable {} + abstract static class D implements Comparable, Serializable {} - void f(B b) { - g(new A(b)); - } + void f(B b) { + g(new A(b)); + } } diff --git a/framework/tests/all-systems/Issue1992.java b/framework/tests/all-systems/Issue1992.java index 0673d75b760..5cc6b5d7ba2 100644 --- a/framework/tests/all-systems/Issue1992.java +++ b/framework/tests/all-systems/Issue1992.java @@ -7,23 +7,23 @@ @SuppressWarnings("all") // Check for crashes only public class Issue1992 { - interface A {} + interface A {} - static class B { - C a; - T b; - } + static class B { + C a; + T b; + } - static class C { - Function c; + static class C { + Function c; - enum E { - NONE + enum E { + NONE + } } - } - boolean f(List> x) { - B d = x.get(x.size() - 1); - return d.a.c.apply(d.b) != C.E.NONE; - } + boolean f(List> x) { + B d = x.get(x.size() - 1); + return d.a.c.apply(d.b) != C.E.NONE; + } } diff --git a/framework/tests/all-systems/Issue2048.java b/framework/tests/all-systems/Issue2048.java index 00aa40d853d..a0a7f6e7771 100644 --- a/framework/tests/all-systems/Issue2048.java +++ b/framework/tests/all-systems/Issue2048.java @@ -6,12 +6,12 @@ // checker/tests/nullness public class Issue2048 { - interface Foo {} + interface Foo {} - interface Fooer {} + interface Fooer {} - class Use { - @SuppressWarnings("all") // Check for crashes. - void foo(Fooer fooer) {} - } + class Use { + @SuppressWarnings("all") // Check for crashes. + void foo(Fooer fooer) {} + } } diff --git a/framework/tests/all-systems/Issue2082.java b/framework/tests/all-systems/Issue2082.java index 6374fb657c3..9ffdd4492fe 100644 --- a/framework/tests/all-systems/Issue2082.java +++ b/framework/tests/all-systems/Issue2082.java @@ -4,6 +4,6 @@ import java.util.concurrent.Callable; public class Issue2082 { - @SuppressWarnings("all") // Callable is a raw type. - Callable foo = () -> 0; + @SuppressWarnings("all") // Callable is a raw type. + Callable foo = () -> 0; } diff --git a/framework/tests/all-systems/Issue2088.java b/framework/tests/all-systems/Issue2088.java index 1a1859f0797..364fbbb90ec 100644 --- a/framework/tests/all-systems/Issue2088.java +++ b/framework/tests/all-systems/Issue2088.java @@ -7,25 +7,25 @@ @SuppressWarnings({"unchecked", "all"}) // Check for crashes only abstract class Issue2088 { - interface A> {} + interface A> {} - interface B extends A {} + interface B extends A {} - interface C

                  { - interface F> {} - } + interface C

                  { + interface F> {} + } - interface D {} + interface D {} - static class Key { - static Key get(Type type) { - return null; + static class Key { + static Key get(Type type) { + return null; + } } - } - abstract ParameterizedType n(Type o, Class r, Type... a); + abstract ParameterizedType n(Type o, Class r, Type... a); - , Z extends Y> void f(Class c) { - Key> f = (Key>) Key.get(n(C.class, C.F.class, c)); - } + , Z extends Y> void f(Class c) { + Key> f = (Key>) Key.get(n(C.class, C.F.class, c)); + } } diff --git a/framework/tests/all-systems/Issue2190.java b/framework/tests/all-systems/Issue2190.java index b75dc3517cf..3341136043e 100644 --- a/framework/tests/all-systems/Issue2190.java +++ b/framework/tests/all-systems/Issue2190.java @@ -1,24 +1,24 @@ // Test case for Issue 2190. public class Issue2190 { - interface A {} + interface A {} - abstract class B implements C {} + abstract class B implements C {} - interface C {} + interface C {} - class D {} + class D {} - interface I { - I to(D x); - } - - abstract class Z { - void f(I> x, D> y) { - x.to(y); + interface I { + I to(D x); } - void g(I> x, D> y) { - x.to(y); + abstract class Z { + void f(I> x, D> y) { + x.to(y); + } + + void g(I> x, D> y) { + x.to(y); + } } - } } diff --git a/framework/tests/all-systems/Issue2195.java b/framework/tests/all-systems/Issue2195.java index 4bee9a2c1d6..0e8f4f00e3e 100644 --- a/framework/tests/all-systems/Issue2195.java +++ b/framework/tests/all-systems/Issue2195.java @@ -1,17 +1,17 @@ // Test case for Issue 2195. @SuppressWarnings("unchecked") public class Issue2195 { - interface A {} + interface A {} - interface B {} + interface B {} - class C { - C(T t) {} - } + class C { + C(T t) {} + } - class X { - X(B b) { - new C(b); + class X { + X(B b) { + new C(b); + } } - } } diff --git a/framework/tests/all-systems/Issue2196.java b/framework/tests/all-systems/Issue2196.java index 4b45057c741..4344e7cccbf 100644 --- a/framework/tests/all-systems/Issue2196.java +++ b/framework/tests/all-systems/Issue2196.java @@ -1,20 +1,20 @@ // Test case for Issue 2196. @SuppressWarnings("unchecked") public class Issue2196 { - interface A {} + interface A {} - interface B {} + interface B {} - interface C {} + interface C {} - abstract class X { + abstract class X { - class D implements B {} + class D implements B {} - abstract void f(B b); + abstract void f(B b); - private void g() { - f(new D()); + private void g() { + f(new D()); + } } - } } diff --git a/framework/tests/all-systems/Issue2198.java b/framework/tests/all-systems/Issue2198.java index 6d6d20b97a2..59ff0f4c3fa 100644 --- a/framework/tests/all-systems/Issue2198.java +++ b/framework/tests/all-systems/Issue2198.java @@ -1,17 +1,17 @@ // Test case for Issue 2198. @SuppressWarnings("unchecked") public class Issue2198 { - interface A {} + interface A {} - class B {} + class B {} - class C { - C(T t) {} - } + class C { + C(T t) {} + } - class X { - X(B b) { - new C(b); + class X { + X(B b) { + new C(b); + } } - } } diff --git a/framework/tests/all-systems/Issue2199.java b/framework/tests/all-systems/Issue2199.java index bcddf4b0ed7..d97c94662ab 100644 --- a/framework/tests/all-systems/Issue2199.java +++ b/framework/tests/all-systems/Issue2199.java @@ -1,15 +1,15 @@ // Test case for Issue 2199. @SuppressWarnings("unchecked") public class Issue2199 { - static class StrangeConstructorTypeArgs { - public StrangeConstructorTypeArgs(Abstract abs) {} - } + static class StrangeConstructorTypeArgs { + public StrangeConstructorTypeArgs(Abstract abs) {} + } - abstract static class Abstract {} + abstract static class Abstract {} - static class Concrete extends Abstract {} + static class Concrete extends Abstract {} - static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { - return new StrangeConstructorTypeArgs(new Concrete<>()); - } + static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { + return new StrangeConstructorTypeArgs(new Concrete<>()); + } } diff --git a/framework/tests/all-systems/Issue2234.java b/framework/tests/all-systems/Issue2234.java index bc7c1bec225..1d06e3f76fc 100644 --- a/framework/tests/all-systems/Issue2234.java +++ b/framework/tests/all-systems/Issue2234.java @@ -5,12 +5,12 @@ import java.util.List; class Issue2234Super { - Issue2234Super(List p) {} + Issue2234Super(List p) {} } @SuppressWarnings("unchecked") // raw supertype class Issue2234Sub extends Issue2234Super { - Issue2234Sub() { - super(new LinkedList<>()); - } + Issue2234Sub() { + super(new LinkedList<>()); + } } diff --git a/framework/tests/all-systems/Issue2302.java b/framework/tests/all-systems/Issue2302.java index 081a12f2d1a..3ea4636d86f 100644 --- a/framework/tests/all-systems/Issue2302.java +++ b/framework/tests/all-systems/Issue2302.java @@ -3,23 +3,23 @@ @SuppressWarnings("unchecked") public class Issue2302 { - static class StrangeConstructorTypeArgs { - // The constructor does not use the type parameter V. - public StrangeConstructorTypeArgs(MyClass abs) {} - } + static class StrangeConstructorTypeArgs { + // The constructor does not use the type parameter V. + public StrangeConstructorTypeArgs(MyClass abs) {} + } - static class MyClass {} + static class MyClass {} - static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { - // Crash with the diamond operator. - // Type inference chooses `Object` as the type argument. - // That is a bug, since it should choose exactly `byte[]`. - return new StrangeConstructorTypeArgs(new MyClass<>()); + static StrangeConstructorTypeArgs getStrangeConstructorTypeArgs() { + // Crash with the diamond operator. + // Type inference chooses `Object` as the type argument. + // That is a bug, since it should choose exactly `byte[]`. + return new StrangeConstructorTypeArgs(new MyClass<>()); - // No crash with an explicit type argument (no diamond operator), no matter what it is. - // return new StrangeConstructorTypeArgs(new MyClass()); - // return new StrangeConstructorTypeArgs(new MyClass()); - // return new StrangeConstructorTypeArgs(new MyClass()); - // return new StrangeConstructorTypeArgs(new MyClass<@Tainted Object>()); - } + // No crash with an explicit type argument (no diamond operator), no matter what it is. + // return new StrangeConstructorTypeArgs(new MyClass()); + // return new StrangeConstructorTypeArgs(new MyClass()); + // return new StrangeConstructorTypeArgs(new MyClass()); + // return new StrangeConstructorTypeArgs(new MyClass<@Tainted Object>()); + } } diff --git a/framework/tests/all-systems/Issue2370.java b/framework/tests/all-systems/Issue2370.java index ff1cd063e8b..e5f775213c8 100644 --- a/framework/tests/all-systems/Issue2370.java +++ b/framework/tests/all-systems/Issue2370.java @@ -6,30 +6,30 @@ // framework. @SuppressWarnings("all") public class Issue2370 { - private Stream getAction2370s(final State2370 state) { - return Stream.of( - toStream(state.getOnExit()).flatMap(t -> t.getAction2370s().stream()), - toStream(state.getOnSignal()).flatMap(t -> t.getAction2370s().stream()), - toStream(state.getOnEnter()).flatMap(t -> t.getAction2370s().stream())) - .flatMap(actionStream -> actionStream); - } + private Stream getAction2370s(final State2370 state) { + return Stream.of( + toStream(state.getOnExit()).flatMap(t -> t.getAction2370s().stream()), + toStream(state.getOnSignal()).flatMap(t -> t.getAction2370s().stream()), + toStream(state.getOnEnter()).flatMap(t -> t.getAction2370s().stream())) + .flatMap(actionStream -> actionStream); + } - private Stream toStream(final Collection obj) { - return Optional.ofNullable(obj) - .map(Stream::of) - .orElseGet(Stream::empty) - .flatMap(Collection::stream); - } + private Stream toStream(final Collection obj) { + return Optional.ofNullable(obj) + .map(Stream::of) + .orElseGet(Stream::empty) + .flatMap(Collection::stream); + } } interface Action2370 { - public Collection getAction2370s(); + public Collection getAction2370s(); } interface State2370 { - public Collection getOnExit(); + public Collection getOnExit(); - public Collection getOnSignal(); + public Collection getOnSignal(); - public Collection getOnEnter(); + public Collection getOnEnter(); } diff --git a/framework/tests/all-systems/Issue2371.java b/framework/tests/all-systems/Issue2371.java index 9abe3188b72..da6ab66cde5 100644 --- a/framework/tests/all-systems/Issue2371.java +++ b/framework/tests/all-systems/Issue2371.java @@ -1,8 +1,8 @@ @SuppressWarnings("all") public class Issue2371> { - void method(Issue2371 i) { - other(i); - } + void method(Issue2371 i) { + other(i); + } - void other(Issue2371 e) {} + void other(Issue2371 e) {} } diff --git a/framework/tests/all-systems/Issue2446.java b/framework/tests/all-systems/Issue2446.java index 99ebd21743d..2375f562585 100644 --- a/framework/tests/all-systems/Issue2446.java +++ b/framework/tests/all-systems/Issue2446.java @@ -1,13 +1,13 @@ public class Issue2446 { - static class One {} + static class One {} - static class Two> extends One {} + static class Two> extends One {} - static class Three> extends Two {} + static class Three> extends Two {} - static > Three f() { - throw new AssertionError(); - } + static > Three f() { + throw new AssertionError(); + } - static final Three F = f(); + static final Three F = f(); } diff --git a/framework/tests/all-systems/Issue2480.java b/framework/tests/all-systems/Issue2480.java index c1b65d94e88..e78a90a726f 100644 --- a/framework/tests/all-systems/Issue2480.java +++ b/framework/tests/all-systems/Issue2480.java @@ -5,9 +5,9 @@ @SuppressWarnings({"unchecked", "all"}) // check for crashes only abstract class Issue2480 { - void testCase() { - for (Class wrapperType : of(Character.class, Boolean.class)) {} - } + void testCase() { + for (Class wrapperType : of(Character.class, Boolean.class)) {} + } - abstract List of(E... e1); + abstract List of(E... e1); } diff --git a/framework/tests/all-systems/Issue263.java b/framework/tests/all-systems/Issue263.java index 121f24bdedf..c873ce143c2 100644 --- a/framework/tests/all-systems/Issue263.java +++ b/framework/tests/all-systems/Issue263.java @@ -3,29 +3,29 @@ abstract class Outer { - public class Inner { - private T t; + public class Inner { + private T t; - public Inner(T t) { - this.t = t; - } + public Inner(T t) { + this.t = t; + } - T get() { - return t; + T get() { + return t; + } } - } - public abstract Inner getInner(); + public abstract Inner getInner(); } public class Issue263 { - public Issue263(Outer outer) { - this.outer = outer; - } + public Issue263(Outer outer) { + this.outer = outer; + } - Outer outer; + Outer outer; - public void context() { - String s = outer.getInner().get(); - } + public void context() { + String s = outer.getInner().get(); + } } diff --git a/framework/tests/all-systems/Issue2678.java b/framework/tests/all-systems/Issue2678.java index 2fffc3aaf04..c4cb96796f7 100644 --- a/framework/tests/all-systems/Issue2678.java +++ b/framework/tests/all-systems/Issue2678.java @@ -1,6 +1,6 @@ public class Issue2678 { - @SuppressWarnings({"index:array.access.unsafe.low", "index:array.access.unsafe.high"}) - public synchronized void incrementPushed(long[] pushed, int operationType) { - ++(pushed[operationType]); - } + @SuppressWarnings({"index:array.access.unsafe.low", "index:array.access.unsafe.high"}) + public synchronized void incrementPushed(long[] pushed, int operationType) { + ++(pushed[operationType]); + } } diff --git a/framework/tests/all-systems/Issue2717.java b/framework/tests/all-systems/Issue2717.java index 573faad77b8..c917d3c9ddd 100644 --- a/framework/tests/all-systems/Issue2717.java +++ b/framework/tests/all-systems/Issue2717.java @@ -1,26 +1,26 @@ @SuppressWarnings("all") public class Issue2717 { - interface Tree {} + interface Tree {} - interface ExpressionTree extends Tree {} + interface ExpressionTree extends Tree {} - interface BinaryTree extends ExpressionTree {} + interface BinaryTree extends ExpressionTree {} - interface Matcher {} + interface Matcher {} - public static void test(Matcher m) {} + public static void test(Matcher m) {} - public static void caller() { - test(toType(BinaryTree.class, Issue2717.allOf())); - } + public static void caller() { + test(toType(BinaryTree.class, Issue2717.allOf())); + } - public static Matcher allOf() { - return null; - } + public static Matcher allOf() { + return null; + } - public static Matcher toType( - Class type, Matcher matcher) { - return null; - } + public static Matcher toType( + Class type, Matcher matcher) { + return null; + } } diff --git a/framework/tests/all-systems/Issue2739.java b/framework/tests/all-systems/Issue2739.java index ddacf4361eb..4bf545de5e8 100644 --- a/framework/tests/all-systems/Issue2739.java +++ b/framework/tests/all-systems/Issue2739.java @@ -1,9 +1,9 @@ public class Issue2739 { - public interface EppEnum { - String getXmlName(); - } + public interface EppEnum { + String getXmlName(); + } - void method(E e) { - String s = e.getXmlName(); - } + void method(E e) { + String s = e.getXmlName(); + } } diff --git a/framework/tests/all-systems/Issue2779.java b/framework/tests/all-systems/Issue2779.java index e61cd02d312..9199a6ca59a 100644 --- a/framework/tests/all-systems/Issue2779.java +++ b/framework/tests/all-systems/Issue2779.java @@ -4,23 +4,23 @@ // @below-java9-jdk-skip-test @SuppressWarnings("all") // Just check for crashes. interface Issue2779 { - S get(); + S get(); - static Issue2779 wrap2(T val) { - return new Issue2779() { - @Override - public T get() { - return val; - } - }; - } + static Issue2779 wrap2(T val) { + return new Issue2779() { + @Override + public T get() { + return val; + } + }; + } - static Issue2779 wrap(T val) { - return new Issue2779<>() { - @Override - public T get() { - return val; - } - }; - } + static Issue2779 wrap(T val) { + return new Issue2779<>() { + @Override + public T get() { + return val; + } + }; + } } diff --git a/framework/tests/all-systems/Issue2781.java b/framework/tests/all-systems/Issue2781.java index 5213c82fd43..003652baa67 100644 --- a/framework/tests/all-systems/Issue2781.java +++ b/framework/tests/all-systems/Issue2781.java @@ -5,18 +5,18 @@ import java.util.stream.Stream; public class Issue2781 { - class Wrapper { - Wrapper(T t) {} - } + class Wrapper { + Wrapper(T t) {} + } - Stream>> getStreamOfWrappedFunctions1() { - // inferred type in new - return Stream.>>of(new Wrapper<>(e -> e)); - } + Stream>> getStreamOfWrappedFunctions1() { + // inferred type in new + return Stream.>>of(new Wrapper<>(e -> e)); + } - Stream>> getStreamOfWrappedFunctions2() { - // explicit type in new - return Stream.>>of( - new Wrapper>(e -> e)); - } + Stream>> getStreamOfWrappedFunctions2() { + // explicit type in new + return Stream.>>of( + new Wrapper>(e -> e)); + } } diff --git a/framework/tests/all-systems/Issue301.java b/framework/tests/all-systems/Issue301.java index f77023e4893..b8ed2243195 100644 --- a/framework/tests/all-systems/Issue301.java +++ b/framework/tests/all-systems/Issue301.java @@ -2,1462 +2,1462 @@ // https://github.com/typetools/checker-framework/issues/301 public class Issue301 { - { - java.util.Vector v = new java.util.Vector(); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - v.add(1.0); - } + { + java.util.Vector v = new java.util.Vector(); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + v.add(1.0); + } } diff --git a/framework/tests/all-systems/Issue3021.java b/framework/tests/all-systems/Issue3021.java index c8ad7b0198e..9b6428a956a 100644 --- a/framework/tests/all-systems/Issue3021.java +++ b/framework/tests/all-systems/Issue3021.java @@ -5,9 +5,9 @@ import org.checkerframework.common.aliasing.qual.MaybeAliased; public class Issue3021 { - void make() { - new Lib<@MaybeAliased T>() {}; - } + void make() { + new Lib<@MaybeAliased T>() {}; + } - class Lib {} + class Lib {} } diff --git a/framework/tests/all-systems/Issue3055.java b/framework/tests/all-systems/Issue3055.java index d3e0078b890..7d85929cffe 100644 --- a/framework/tests/all-systems/Issue3055.java +++ b/framework/tests/all-systems/Issue3055.java @@ -1,14 +1,14 @@ public class Issue3055 { - class C1.Bound> { - class Bound {} - } + class C1.Bound> { + class Bound {} + } - class C2 { - class Bound {} - } + class C2 { + class Bound {} + } - class C3.Bound> { - class Bound {} - } + class C3.Bound> { + class Bound {} + } } diff --git a/framework/tests/all-systems/Issue3120.java b/framework/tests/all-systems/Issue3120.java index 2a623d899a9..431bf1a9551 100644 --- a/framework/tests/all-systems/Issue3120.java +++ b/framework/tests/all-systems/Issue3120.java @@ -1,22 +1,22 @@ @SuppressWarnings("all") // Ensure no crashes public class Issue3120 { - CharSequence foo() { - return bar(); - } + CharSequence foo() { + return bar(); + } - > CharSequence bar() { - return null; - } + > CharSequence bar() { + return null; + } - CharSequence foo0() { - return bar0(null); - } + CharSequence foo0() { + return bar0(null); + } - & AnotherType> CharSequence bar0(SomeType type) { - return null; - } + & AnotherType> CharSequence bar0(SomeType type) { + return null; + } - class SomeType {} + class SomeType {} - interface AnotherType {} + interface AnotherType {} } diff --git a/framework/tests/all-systems/Issue3128.java b/framework/tests/all-systems/Issue3128.java index fd72ec17985..e9aa730e0d5 100644 --- a/framework/tests/all-systems/Issue3128.java +++ b/framework/tests/all-systems/Issue3128.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/3128 public class Issue3128 { - class One {} + class One {} - class Two> {} + class Two> {} - One> foo() { - return new One<>(); - } + One> foo() { + return new One<>(); + } } diff --git a/framework/tests/all-systems/Issue3232.java b/framework/tests/all-systems/Issue3232.java index be29842c912..22079628fa4 100644 --- a/framework/tests/all-systems/Issue3232.java +++ b/framework/tests/all-systems/Issue3232.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/3232 class Issue3232A { - @SuppressWarnings("unchecked") - void foo(B... values) {} + @SuppressWarnings("unchecked") + void foo(B... values) {} } class Issue3232C extends Issue3232A { - void bar(int value) { - foo(value); - } + void bar(int value) { + foo(value); + } } diff --git a/framework/tests/all-systems/Issue3277.java b/framework/tests/all-systems/Issue3277.java index 877eb24b63c..eb8fdcfa196 100644 --- a/framework/tests/all-systems/Issue3277.java +++ b/framework/tests/all-systems/Issue3277.java @@ -5,15 +5,15 @@ import org.checkerframework.common.aliasing.qual.MaybeAliased; public class Issue3277 { - void f() { - Object o = new @MaybeAliased Generic[0]; - o = new Generic<@MaybeAliased ?>[0]; - } + void f() { + Object o = new @MaybeAliased Generic[0]; + o = new Generic<@MaybeAliased ?>[0]; + } - // TODO: Having the same code in fields crashes javac - // with an AssertionError. - // Object o1 = new @MaybeAliased Generic[0]; - // Object o2 = new Generic<@MaybeAliased ?>[0]; + // TODO: Having the same code in fields crashes javac + // with an AssertionError. + // Object o1 = new @MaybeAliased Generic[0]; + // Object o2 = new Generic<@MaybeAliased ?>[0]; - class Generic {} + class Generic {} } diff --git a/framework/tests/all-systems/Issue3295.java b/framework/tests/all-systems/Issue3295.java index 991f8128ad3..3e2497f9229 100644 --- a/framework/tests/all-systems/Issue3295.java +++ b/framework/tests/all-systems/Issue3295.java @@ -1,17 +1,17 @@ import java.util.List; public class Issue3295 { - interface P { + interface P { - Class h(); + Class h(); - List> g(); - } + List> g(); + } - interface Q {} + interface Q {} - @SuppressWarnings("interning:unnecessary.equals") // This warning is expected. - static void f(P t) { - t.g().stream().filter(x -> x.h().equals(Q.class)); - } + @SuppressWarnings("interning:unnecessary.equals") // This warning is expected. + static void f(P t) { + t.g().stream().filter(x -> x.h().equals(Q.class)); + } } diff --git a/framework/tests/all-systems/Issue3302.java b/framework/tests/all-systems/Issue3302.java index fa1bbf4e27b..c11b2591d74 100644 --- a/framework/tests/all-systems/Issue3302.java +++ b/framework/tests/all-systems/Issue3302.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/3302 public class Issue3302 { - void foo(Bar b) {} + void foo(Bar b) {} - interface Bar & A> {} + interface Bar & A> {} - interface A {} + interface A {} - interface Box {} + interface Box {} } diff --git a/framework/tests/all-systems/Issue3377.java b/framework/tests/all-systems/Issue3377.java index 3022ae4e700..3265d19429c 100644 --- a/framework/tests/all-systems/Issue3377.java +++ b/framework/tests/all-systems/Issue3377.java @@ -1,15 +1,15 @@ // @below-java11-jdk-skip-test @SuppressWarnings("all") // Check for crashes. public class Issue3377 { - static class Box {} + static class Box {} - interface Unboxer { - T unbox(Box p); - } + interface Unboxer { + T unbox(Box p); + } - static class Crash { - Box crash(Unboxer ub) { - return ub.unbox(new Box<>() {}); + static class Crash { + Box crash(Unboxer ub) { + return ub.unbox(new Box<>() {}); + } } - } } diff --git a/framework/tests/all-systems/Issue3569.java b/framework/tests/all-systems/Issue3569.java index 211e75dc6eb..670ff161dea 100644 --- a/framework/tests/all-systems/Issue3569.java +++ b/framework/tests/all-systems/Issue3569.java @@ -1,9 +1,9 @@ public abstract class Issue3569 { - public interface MyInterface {} + public interface MyInterface {} - public abstract T getT(); + public abstract T getT(); - protected K getK(Issue3569 ab) { - return ab.getT(); - } + protected K getK(Issue3569 ab) { + return ab.getT(); + } } diff --git a/framework/tests/all-systems/Issue3570.java b/framework/tests/all-systems/Issue3570.java index c64cbe4db32..4bc9753ee5a 100644 --- a/framework/tests/all-systems/Issue3570.java +++ b/framework/tests/all-systems/Issue3570.java @@ -1,63 +1,64 @@ @SuppressWarnings("all") // Check for crashes. public class Issue3570 { - public interface Freezable extends Cloneable {} + public interface Freezable extends Cloneable {} - public static final class Key extends Iced> implements Comparable { - @Override - public int compareTo(Object o) { - return 0; + public static final class Key extends Iced> implements Comparable { + @Override + public int compareTo(Object o) { + return 0; + } } - } - public abstract static class Keyed extends Iced {} + public abstract static class Keyed extends Iced {} - public abstract static class Lockable> extends Keyed {} + public abstract static class Lockable> extends Keyed {} - public abstract static class Iced - implements Freezable, java.io.Externalizable { - @Override - public void readExternal(java.io.ObjectInput ois) - throws java.io.IOException, ClassNotFoundException {} + public abstract static class Iced + implements Freezable, java.io.Externalizable { + @Override + public void readExternal(java.io.ObjectInput ois) + throws java.io.IOException, ClassNotFoundException {} - @Override - public void writeExternal(java.io.ObjectOutput oos) throws java.io.IOException {} - } + @Override + public void writeExternal(java.io.ObjectOutput oos) throws java.io.IOException {} + } - public abstract static class Model< - M extends Model, P extends Model.Parameters, O extends Model.Output> - extends Lockable { - public P _parms; + public abstract static class Model< + M extends Model, P extends Model.Parameters, O extends Model.Output> + extends Lockable { + public P _parms; - public abstract static class Parameters extends Iced { - public Key _train; - } + public abstract static class Parameters extends Iced { + public Key _train; + } - public abstract static class Output extends Iced {} - } + public abstract static class Output extends Iced {} + } - public static class Frame extends Lockable {} + public static class Frame extends Lockable {} - public abstract static class Schema> extends Iced {} + public abstract static class Schema> extends Iced {} - public static class SchemaV3> extends Schema {} + public static class SchemaV3> extends Schema {} - public static class KeyV3, K extends Keyed> - extends SchemaV3> { - public KeyV3(Key key) {} + public static class KeyV3, K extends Keyed> + extends SchemaV3> { + public KeyV3(Key key) {} - public static class FrameKeyV3 extends KeyV3 { - public FrameKeyV3(Key key) { - super(key); - } + public static class FrameKeyV3 extends KeyV3 { + public FrameKeyV3(Key key) { + super(key); + } + } } - } - public static class ModelSchemaBaseV3, S extends ModelSchemaBaseV3> - extends SchemaV3 { - public KeyV3.FrameKeyV3 data_frame; + public static class ModelSchemaBaseV3< + M extends Model, S extends ModelSchemaBaseV3> + extends SchemaV3 { + public KeyV3.FrameKeyV3 data_frame; - public ModelSchemaBaseV3(M m) { - this.data_frame = new KeyV3.FrameKeyV3(m._parms._train); + public ModelSchemaBaseV3(M m) { + this.data_frame = new KeyV3.FrameKeyV3(m._parms._train); + } } - } } diff --git a/framework/tests/all-systems/Issue3598.java b/framework/tests/all-systems/Issue3598.java index e7e06d144b3..eeb4b20aaa3 100644 --- a/framework/tests/all-systems/Issue3598.java +++ b/framework/tests/all-systems/Issue3598.java @@ -2,24 +2,24 @@ public class Issue3598 { - static class DClass extends EClass {} + static class DClass extends EClass {} - static class EClass {} + static class EClass {} - // Must be Function, can't use interface defined in this class. - static class XClass

                  implements Function { + // Must be Function, can't use interface defined in this class. + static class XClass

                  implements Function { - @Override - public P apply(P protoT) { - return protoT; - } + @Override + public P apply(P protoT) { + return protoT; + } - // DClass extends a raw class. - static Function f(DClass k) { - // Crash on this line. - return new XClass<>(k); - } + // DClass extends a raw class. + static Function f(DClass k) { + // Crash on this line. + return new XClass<>(k); + } - XClass(P p) {} - } + XClass(P p) {} + } } diff --git a/framework/tests/all-systems/Issue3785.java b/framework/tests/all-systems/Issue3785.java index 3384d029b36..ad1742b009c 100644 --- a/framework/tests/all-systems/Issue3785.java +++ b/framework/tests/all-systems/Issue3785.java @@ -1,29 +1,29 @@ import java.util.List; public class Issue3785 { - public Issue3785(List l) {} + public Issue3785(List l) {} - void method(L l) {} + void method(L l) {} - @SuppressWarnings("unchecked") - void use(Issue3785 p, Object o, List list) { - // This type-checks in Java, but probably shouldn't. - p.method(o); - p.method(o); - new Issue3785(list); - } + @SuppressWarnings("unchecked") + void use(Issue3785 p, Object o, List list) { + // This type-checks in Java, but probably shouldn't. + p.method(o); + p.method(o); + new Issue3785(list); + } - /* - L method2(L l) { return l;} + /* + L method2(L l) { return l;} - @SuppressWarnings("unchecked") - void use2(Issue3785 p, Object o) { - // javac issues: "error: incompatible types: Object cannot be converted to Void" - p.method(o); - // javac issues: "error: incompatible types: Object cannot be converted to Void" - Void s = p.method2(o); - } - */ + @SuppressWarnings("unchecked") + void use2(Issue3785 p, Object o) { + // javac issues: "error: incompatible types: Object cannot be converted to Void" + p.method(o); + // javac issues: "error: incompatible types: Object cannot be converted to Void" + Void s = p.method2(o); + } + */ } /* diff --git a/framework/tests/all-systems/Issue3791.java b/framework/tests/all-systems/Issue3791.java index 046ee244b44..e66066f8b12 100644 --- a/framework/tests/all-systems/Issue3791.java +++ b/framework/tests/all-systems/Issue3791.java @@ -1,19 +1,19 @@ public class Issue3791 { - interface MyInterface {} + interface MyInterface {} - abstract static class MyClass {} + abstract static class MyClass {} - static class SubMyClass extends MyClass> {} + static class SubMyClass extends MyClass> {} - static class Generic implements MyInterface {} + static class Generic implements MyInterface {} - abstract static class MyInterfaceMyClass> {} + abstract static class MyInterfaceMyClass> {} - void method(MyInterfaceMyClass param) { - // TODO: Should we open an issue for this? - // This code is reject by Eclipse and should be reject by javac. - // See the javac bug report https://bugs.openjdk.org/browse/JDK-8265255. - @SuppressWarnings({"unchecked", "type.argument"}) - MyInterfaceMyClass> local = (MyInterfaceMyClass>) param; - } + void method(MyInterfaceMyClass param) { + // TODO: Should we open an issue for this? + // This code is reject by Eclipse and should be reject by javac. + // See the javac bug report https://bugs.openjdk.org/browse/JDK-8265255. + @SuppressWarnings({"unchecked", "type.argument"}) + MyInterfaceMyClass> local = (MyInterfaceMyClass>) param; + } } diff --git a/framework/tests/all-systems/Issue3826.java b/framework/tests/all-systems/Issue3826.java index 3f38c908a79..dd8cbf8e31b 100644 --- a/framework/tests/all-systems/Issue3826.java +++ b/framework/tests/all-systems/Issue3826.java @@ -1,27 +1,27 @@ public class Issue3826 { - public static void getOption(Class cls, B[] opts) {} + public static void getOption(Class cls, B[] opts) {} - public static class ClassA { - public static class InnerClassA { - interface InnerInnerClassA {} + public static class ClassA { + public static class InnerClassA { + interface InnerInnerClassA {} + } } - } - public static class ClassB { - public abstract static class InnerClassB {} - } + public static class ClassB { + public abstract static class InnerClassB {} + } - public static class ClassC { - interface InterfaceClassC extends ClassA.InnerClassA.InnerInnerClassA {} + public static class ClassC { + interface InterfaceClassC extends ClassA.InnerClassA.InnerInnerClassA {} - private static class InnerClassC extends ClassA.InnerClassA implements InterfaceClassC {} + private static class InnerClassC extends ClassA.InnerClassA implements InterfaceClassC {} - public ClassC(ClassA.InnerClassA.InnerInnerClassA... opts) { - // Does not crash - Issue3826.getOption( - InnerClassC.class, opts); - // Crashes - Issue3826.getOption(InnerClassC.class, opts); + public ClassC(ClassA.InnerClassA.InnerInnerClassA... opts) { + // Does not crash + Issue3826.getOption( + InnerClassC.class, opts); + // Crashes + Issue3826.getOption(InnerClassC.class, opts); + } } - } } diff --git a/framework/tests/all-systems/Issue392.java b/framework/tests/all-systems/Issue392.java index 385c526ccef..7639e4ec1bc 100644 --- a/framework/tests/all-systems/Issue392.java +++ b/framework/tests/all-systems/Issue392.java @@ -3,7 +3,7 @@ public class Issue392 { - public void getFields(T t) { - Object o = new Object[] {t, t}; - } + public void getFields(T t) { + Object o = new Object[] {t, t}; + } } diff --git a/framework/tests/all-systems/Issue3929.java b/framework/tests/all-systems/Issue3929.java index 9eec1ffb759..2042ab42b5e 100644 --- a/framework/tests/all-systems/Issue3929.java +++ b/framework/tests/all-systems/Issue3929.java @@ -3,13 +3,13 @@ public class Issue3929 { - public void endElement(DefaultKeyedValues3929 arg) { - for (Object o : arg.getKeys()) {} - } + public void endElement(DefaultKeyedValues3929 arg) { + for (Object o : arg.getKeys()) {} + } } class DefaultKeyedValues3929> { - public List getKeys() { - return new ArrayList<>(); - } + public List getKeys() { + return new ArrayList<>(); + } } diff --git a/framework/tests/all-systems/Issue393.java b/framework/tests/all-systems/Issue393.java index 436a033ff1a..262d50dd867 100644 --- a/framework/tests/all-systems/Issue393.java +++ b/framework/tests/all-systems/Issue393.java @@ -3,9 +3,9 @@ abstract class TypeVarTaintCheck { - void test() { - wrap(new Object()); - } + void test() { + wrap(new Object()); + } - abstract void wrap(U u); + abstract void wrap(U u); } diff --git a/framework/tests/all-systems/Issue395.java b/framework/tests/all-systems/Issue395.java index 9fa8bb5f3bf..8368a46c8ff 100644 --- a/framework/tests/all-systems/Issue395.java +++ b/framework/tests/all-systems/Issue395.java @@ -5,7 +5,7 @@ public class Issue395 { - Object[] testMethod() { - return new Object[] {new ArrayList<>()}; - } + Object[] testMethod() { + return new Object[] {new ArrayList<>()}; + } } diff --git a/framework/tests/all-systems/Issue396.java b/framework/tests/all-systems/Issue396.java index 64b1f981077..61dd18ea2e8 100644 --- a/framework/tests/all-systems/Issue396.java +++ b/framework/tests/all-systems/Issue396.java @@ -1,11 +1,11 @@ // Test case for Issue 396: // https://github.com/typetools/checker-framework/issues/396 public class Issue396 { - void b() { - try { + void b() { + try { - } catch (LinkageError | AssertionError e) { - throw e; + } catch (LinkageError | AssertionError e) { + throw e; + } } - } } diff --git a/framework/tests/all-systems/Issue3994.java b/framework/tests/all-systems/Issue3994.java index c91c07a2640..90396885ab7 100644 --- a/framework/tests/all-systems/Issue3994.java +++ b/framework/tests/all-systems/Issue3994.java @@ -1,7 +1,7 @@ public class Issue3994 { - interface MyInterface {} + interface MyInterface {} - interface OkRecursive {} + interface OkRecursive {} - interface Recursive {} + interface Recursive {} } diff --git a/framework/tests/all-systems/Issue4083.java b/framework/tests/all-systems/Issue4083.java index d5aaa2b77b6..2f346a5d3fe 100644 --- a/framework/tests/all-systems/Issue4083.java +++ b/framework/tests/all-systems/Issue4083.java @@ -1,16 +1,16 @@ @SuppressWarnings("all") // Just check for crashes. abstract class Issue4083 { - abstract void use(Predicate predicate); + abstract void use(Predicate predicate); - void go() { - use(Annotation::b); - } + void go() { + use(Annotation::b); + } - @interface Annotation { - boolean b() default false; - } + @interface Annotation { + boolean b() default false; + } - interface Predicate { - boolean apply(T t); - } + interface Predicate { + boolean apply(T t); + } } diff --git a/framework/tests/all-systems/Issue4115.java b/framework/tests/all-systems/Issue4115.java index ac21d3f7780..3d2e3c11b6c 100644 --- a/framework/tests/all-systems/Issue4115.java +++ b/framework/tests/all-systems/Issue4115.java @@ -3,13 +3,13 @@ abstract class Issue4115 { - interface V {} + interface V {} - abstract Iterable transform(Iterable i, Function f); + abstract Iterable transform(Iterable i, Function f); - abstract List copyOf(Iterable e); + abstract List copyOf(Iterable e); - List generateAppSuggestions(List xs) { - return copyOf(transform(xs, x -> new V() {})); - } + List generateAppSuggestions(List xs) { + return copyOf(transform(xs, x -> new V() {})); + } } diff --git a/framework/tests/all-systems/Issue4170.java b/framework/tests/all-systems/Issue4170.java index ab787799896..3d5432c5ddc 100644 --- a/framework/tests/all-systems/Issue4170.java +++ b/framework/tests/all-systems/Issue4170.java @@ -1,27 +1,28 @@ // @below-java10-jdk-skip-test +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; public class Issue4170 { - public void loadSequentially(Iterable keys) { - Map result = new LinkedHashMap<>(); - for (K key : keys) { - result.put(key, null); + public void loadSequentially(Iterable keys) { + Map result = new LinkedHashMap<>(); + for (K key : keys) { + result.put(key, null); + } + for (var iter = result.entrySet().iterator(); iter.hasNext(); ) { + var entry = iter.next(); + } } - for (var iter = result.entrySet().iterator(); iter.hasNext(); ) { - var entry = iter.next(); - } - } - public void method1() { - var s = new ArrayList<@Nullable String>(); - for (var str : s) {} - } + public void method1() { + var s = new ArrayList<@Nullable String>(); + for (var str : s) {} + } - public void method2() { - var s = new ArrayList<@Nullable ArrayList<@Nullable String>>(); - } + public void method2() { + var s = new ArrayList<@Nullable ArrayList<@Nullable String>>(); + } } diff --git a/framework/tests/all-systems/Issue437.java b/framework/tests/all-systems/Issue437.java index f0dd4d0495e..121994e445e 100644 --- a/framework/tests/all-systems/Issue437.java +++ b/framework/tests/all-systems/Issue437.java @@ -2,27 +2,27 @@ // https://github.com/typetools/checker-framework/issues/437 abstract class I437Bar { - private final T t; + private final T t; - class Norf { - T getT() { - return t; + class Norf { + T getT() { + return t; + } } - } - I437Bar(T t) { - this.t = t; - } + I437Bar(T t) { + this.t = t; + } - abstract void quux(Norf norf); + abstract void quux(Norf norf); } class I437Foo extends I437Bar { - I437Foo(Integer i) { - super(i); - } + I437Foo(Integer i) { + super(i); + } - void quux(Norf norf) { - Integer i = norf.getT(); - } + void quux(Norf norf) { + Integer i = norf.getT(); + } } diff --git a/framework/tests/all-systems/Issue438.java b/framework/tests/all-systems/Issue438.java index 9f7707e09f1..a104adf4f4b 100644 --- a/framework/tests/all-systems/Issue438.java +++ b/framework/tests/all-systems/Issue438.java @@ -5,15 +5,15 @@ import java.util.List; public class Issue438 { - boolean foo(List list) { - if (list.isEmpty()) { - return new HashSet<>(list).isEmpty(); - } else { - return new HashSet<>(list).contains("test"); + boolean foo(List list) { + if (list.isEmpty()) { + return new HashSet<>(list).isEmpty(); + } else { + return new HashSet<>(list).contains("test"); + } } - } - int bar(List list) { - return new HashSet<>(list).size(); - } + int bar(List list) { + return new HashSet<>(list).size(); + } } diff --git a/framework/tests/all-systems/Issue4384.java b/framework/tests/all-systems/Issue4384.java index 4abb436c465..b1cbf1f0e63 100644 --- a/framework/tests/all-systems/Issue4384.java +++ b/framework/tests/all-systems/Issue4384.java @@ -1,21 +1,21 @@ public abstract class Issue4384 { - interface A> extends C, D {} + interface A> extends C, D {} - interface C {} + interface C {} - interface D> { - void f(); - } + interface D> { + void f(); + } - interface B> extends E {} + interface B> extends E {} - interface E> {} + interface E> {} - void g(A t) { - t.f(); - h(t); - } + void g(A t) { + t.f(); + h(t); + } - abstract void h(A> t); + abstract void h(A> t); } diff --git a/framework/tests/all-systems/Issue457.java b/framework/tests/all-systems/Issue457.java index a1819160772..fac0e68ca05 100644 --- a/framework/tests/all-systems/Issue457.java +++ b/framework/tests/all-systems/Issue457.java @@ -2,14 +2,14 @@ // for more information on what was going wrong here public class Issue457 { - @SuppressWarnings("unused") - public void f(T t) { - final T obj = t; + @SuppressWarnings("unused") + public void f(T t) { + final T obj = t; - @SuppressWarnings("signedness:assignment.type.incompatible") // cast - Float objFloat = (obj instanceof Float) ? (Float) obj : null; + @SuppressWarnings("signedness:assignment.type.incompatible") // cast + Float objFloat = (obj instanceof Float) ? (Float) obj : null; - // An error will be emitted on this line before the fix for Issue457 - t = obj; - } + // An error will be emitted on this line before the fix for Issue457 + t = obj; + } } diff --git a/framework/tests/all-systems/Issue478.java b/framework/tests/all-systems/Issue478.java index 81c7ae424bc..162c7f9cf0a 100644 --- a/framework/tests/all-systems/Issue478.java +++ b/framework/tests/all-systems/Issue478.java @@ -5,7 +5,7 @@ import java.util.Comparator; public class Issue478 { - public static Comparator allTheSame() { - return (Comparator & Serializable) (c1, c2) -> 0; - } + public static Comparator allTheSame() { + return (Comparator & Serializable) (c1, c2) -> 0; + } } diff --git a/framework/tests/all-systems/Issue4829.java b/framework/tests/all-systems/Issue4829.java index 00b215349c4..6bf9e566253 100644 --- a/framework/tests/all-systems/Issue4829.java +++ b/framework/tests/all-systems/Issue4829.java @@ -1,18 +1,19 @@ -import java.util.concurrent.Future; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.concurrent.Future; + @SuppressWarnings("all") // Just check for crashes. public class Issue4829 { - interface ListenableFuture extends Future {} + interface ListenableFuture extends Future {} - enum E {} + enum E {} - ListenableFuture f(E e) { - return g(e); - } + ListenableFuture f(E e) { + return g(e); + } - ListenableFuture g(E e) { - throw new AssertionError(); - } + ListenableFuture g(E e) { + throw new AssertionError(); + } } diff --git a/framework/tests/all-systems/Issue4849.java b/framework/tests/all-systems/Issue4849.java index 972bb4dea11..2a0c04c12be 100644 --- a/framework/tests/all-systems/Issue4849.java +++ b/framework/tests/all-systems/Issue4849.java @@ -4,18 +4,18 @@ final class Issue4849F {} abstract class Issue4849C extends Issue4849G> {} abstract class Issue4849G> { - abstract M e(); + abstract M e(); } @SuppressWarnings("all") // Just check for crashes. abstract class Issue4849 { - enum MyEnum {} + enum MyEnum {} - abstract Issue4849C n(Issue4849F field1); + abstract Issue4849C n(Issue4849F field1); - abstract > Issue4849F of(Class e); + abstract > Issue4849F of(Class e); - void method() { - Issue4849C c = this.n(this.of(MyEnum.class)).e(); - } + void method() { + Issue4849C c = this.n(this.of(MyEnum.class)).e(); + } } diff --git a/framework/tests/all-systems/Issue4852.java b/framework/tests/all-systems/Issue4852.java index df423761342..75449d494b6 100644 --- a/framework/tests/all-systems/Issue4852.java +++ b/framework/tests/all-systems/Issue4852.java @@ -1,10 +1,10 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4852 { - interface Class1, B extends Class1.Class2> { - abstract class Class2, B extends Class2> {} - } + interface Class1, B extends Class1.Class2> { + abstract class Class2, B extends Class2> {} + } - class Class3 { - private void f(Class1 x) {} - } + class Class3 { + private void f(Class1 x) {} + } } diff --git a/framework/tests/all-systems/Issue4853.java b/framework/tests/all-systems/Issue4853.java index 7445725eeb1..b871cf91500 100644 --- a/framework/tests/all-systems/Issue4853.java +++ b/framework/tests/all-systems/Issue4853.java @@ -1,16 +1,16 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4853 { - interface Interface {} + interface Interface {} - static class MyClass { - class InnerMyClass implements Interface {} - } - - abstract static class SubMyClass extends MyClass { - protected void f() { - method(new InnerMyClass()); + static class MyClass { + class InnerMyClass implements Interface {} } - abstract void method(Interface callback); - } + abstract static class SubMyClass extends MyClass { + protected void f() { + method(new InnerMyClass()); + } + + abstract void method(Interface callback); + } } diff --git a/framework/tests/all-systems/Issue4876.java b/framework/tests/all-systems/Issue4876.java index dfb75a3f3f5..0cc2488878e 100644 --- a/framework/tests/all-systems/Issue4876.java +++ b/framework/tests/all-systems/Issue4876.java @@ -2,17 +2,17 @@ public class Issue4876 { - interface FFunction extends Function {} + interface FFunction extends Function {} - interface DInterface {} + interface DInterface {} - interface MInterface

                  {} + interface MInterface

                  {} - interface QInterface, P> {} + interface QInterface, P> {} - FFunction> r; + FFunction> r; - Issue4876(FFunction, DInterface>> r) { - this.r = r; - } + Issue4876(FFunction, DInterface>> r) { + this.r = r; + } } diff --git a/framework/tests/all-systems/Issue4877.java b/framework/tests/all-systems/Issue4877.java index 1d530c2ade9..1c60f8b0dbc 100644 --- a/framework/tests/all-systems/Issue4877.java +++ b/framework/tests/all-systems/Issue4877.java @@ -1,15 +1,15 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4877 { - interface NInterface> {} + interface NInterface> {} - interface OInterface> {} + interface OInterface> {} - interface XInterface {} + interface XInterface {} - static class QClass & XInterface> {} + static class QClass & XInterface> {} - static final class PClass> implements OInterface { - QClass f; - } + static final class PClass> implements OInterface { + QClass f; + } } diff --git a/framework/tests/all-systems/Issue4878.java b/framework/tests/all-systems/Issue4878.java index a9dfce4df39..3d062306bb0 100644 --- a/framework/tests/all-systems/Issue4878.java +++ b/framework/tests/all-systems/Issue4878.java @@ -1,13 +1,13 @@ import java.util.List; class Issue4878 { - void f(S4878 s, Q v) { - s.p().forEach(param -> {}); - } + void f(S4878 s, Q v) { + s.p().forEach(param -> {}); + } } class A4878 {} abstract class S4878 { - abstract List> p(); + abstract List> p(); } diff --git a/framework/tests/all-systems/Issue4878Orig.java b/framework/tests/all-systems/Issue4878Orig.java index eb83315ac52..173522bebf5 100644 --- a/framework/tests/all-systems/Issue4878Orig.java +++ b/framework/tests/all-systems/Issue4878Orig.java @@ -3,23 +3,22 @@ interface M4878Orig {} class A4878Orig { - void p(T v) {} + void p(T v) {} } abstract class S { - abstract List> p(); + abstract List> p(); } class Issue4878Orig { - @SuppressWarnings("unchecked") - void f(S s, T v) { - s.p() - .forEach( - field -> { - Object o = field; - A4878Orig typedField = (A4878Orig) field; - typedField.p(v); - }); - } + @SuppressWarnings("unchecked") + void f(S s, T v) { + s.p().forEach( + field -> { + Object o = field; + A4878Orig typedField = (A4878Orig) field; + typedField.p(v); + }); + } } diff --git a/framework/tests/all-systems/Issue4879.java b/framework/tests/all-systems/Issue4879.java index d1eb46f8235..60825d2a2eb 100644 --- a/framework/tests/all-systems/Issue4879.java +++ b/framework/tests/all-systems/Issue4879.java @@ -1,23 +1,23 @@ abstract class L4879 { - protected L4879(A4879 c) {} + protected L4879(A4879 c) {} } class A4879 { - static class B4879 { - public B4879() {} + static class B4879 { + public B4879() {} - A4879 build() { - throw new AssertionError(); + A4879 build() { + throw new AssertionError(); + } } - } } class Issue4879 { - private final class I4879 extends L4879 { - // Any non-top default checker will issue a true postive error. - @SuppressWarnings("argument") - private I4879() { - super(new A4879.B4879<>().build()); + private final class I4879 extends L4879 { + // Any non-top default checker will issue a true postive error. + @SuppressWarnings("argument") + private I4879() { + super(new A4879.B4879<>().build()); + } } - } } diff --git a/framework/tests/all-systems/Issue4890.java b/framework/tests/all-systems/Issue4890.java index 3f543d2f642..610c1f8b6a8 100644 --- a/framework/tests/all-systems/Issue4890.java +++ b/framework/tests/all-systems/Issue4890.java @@ -1,25 +1,26 @@ -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.function.Function; + @SuppressWarnings("all") // Just check for crashes. public class Issue4890 { - class R

                  , K> {} + class R

                  , K> {} - interface PK {} + interface PK {} - interface N {} + interface N {} - interface Q, P extends @NonNull Object> extends N {} + interface Q, P extends @NonNull Object> extends N {} - interface S

                  extends PhysicalPK {} + interface S

                  extends PhysicalPK {} - interface PhysicalPK extends PK {} + interface PhysicalPK extends PK {} - interface I extends Function {} + interface I extends Function {} - class T { + class T { - I, ? extends Q, ?>> reader; - } + I, ? extends Q, ?>> reader; + } } diff --git a/framework/tests/all-systems/Issue4890Interfaces.java b/framework/tests/all-systems/Issue4890Interfaces.java index dcf54ab6edf..f2ba3001653 100644 --- a/framework/tests/all-systems/Issue4890Interfaces.java +++ b/framework/tests/all-systems/Issue4890Interfaces.java @@ -1,31 +1,31 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue4890Interfaces { - // The capture of BigClass2> is BigClass - // where cap1 is a fresh type variable with upper bound of - // SubInterface2 & Interface2 and lower bound of nulltype. - BigClass2> r; - // The type of r.getI2() is cap1 extends SubInterface2 & Interface2 so - // both of the following assignments should be legal. - SubInterface2 s = r.getI2(); - // Interface2 s2 = r.getI2(); // javac error here, but no error with IntelliJ and - // Eclipse. + // The capture of BigClass2> is BigClass + // where cap1 is a fresh type variable with upper bound of + // SubInterface2 & Interface2 and lower bound of nulltype. + BigClass2> r; + // The type of r.getI2() is cap1 extends SubInterface2 & Interface2 so + // both of the following assignments should be legal. + SubInterface2 s = r.getI2(); + // Interface2 s2 = r.getI2(); // javac error here, but no error with IntelliJ and + // Eclipse. - BigClass2> t; - SubInterface2 s3 = t.getI2(); + BigClass2> t; + SubInterface2 s3 = t.getI2(); - // Interface2 s4 = t.getI2(); // javac error here, but no error with IntelliJ and - // Eclipse. + // Interface2 s4 = t.getI2(); // javac error here, but no error with IntelliJ and + // Eclipse. - abstract static class BigClass2> { - abstract I2 getI2(); - } + abstract static class BigClass2> { + abstract I2 getI2(); + } - interface Interface1 {} + interface Interface1 {} - interface SubInterface1 extends Interface1 {} + interface SubInterface1 extends Interface1 {} - interface Interface2 {} + interface Interface2 {} - interface SubInterface2 extends Interface2 {} + interface SubInterface2 extends Interface2 {} } diff --git a/framework/tests/all-systems/Issue4924.java b/framework/tests/all-systems/Issue4924.java index d6391c30dd8..0f25c91ffba 100644 --- a/framework/tests/all-systems/Issue4924.java +++ b/framework/tests/all-systems/Issue4924.java @@ -1,15 +1,15 @@ public class Issue4924 { - interface Callback {} + interface Callback {} - static class Template { - class Adapter implements Callback {} - } + static class Template { + class Adapter implements Callback {} + } - static class Super extends Template {} + static class Super extends Template {} - static class Issue extends Super { - void foo() { - Callback f = new Adapter(); + static class Issue extends Super { + void foo() { + Callback f = new Adapter(); + } } - } } diff --git a/framework/tests/all-systems/Issue4996.java b/framework/tests/all-systems/Issue4996.java index 6716c29fd86..67b48ef04ee 100644 --- a/framework/tests/all-systems/Issue4996.java +++ b/framework/tests/all-systems/Issue4996.java @@ -1,21 +1,21 @@ public class Issue4996 { - abstract static class CaptureOuter { - abstract T get(); + abstract static class CaptureOuter { + abstract T get(); - abstract class Inner { - abstract T get(); + abstract class Inner { + abstract T get(); + } } - } - static class Client { + static class Client { - CharSequence getFrom(CaptureOuter o) { - return o.get(); - } + CharSequence getFrom(CaptureOuter o) { + return o.get(); + } - CharSequence getFrom(CaptureOuter.Inner i) { - return i.get(); + CharSequence getFrom(CaptureOuter.Inner i) { + return i.get(); + } } - } } diff --git a/framework/tests/all-systems/Issue5008.java b/framework/tests/all-systems/Issue5008.java index 535ee02634c..e5231616e42 100644 --- a/framework/tests/all-systems/Issue5008.java +++ b/framework/tests/all-systems/Issue5008.java @@ -1,11 +1,11 @@ class Issue5008 { - Issue5008(L l) {} + Issue5008(L l) {} - static class B extends Issue5008 { - B() { - super(new L<>()); + static class B extends Issue5008 { + B() { + super(new L<>()); + } } - } - static class L {} + static class L {} } diff --git a/framework/tests/all-systems/Issue5042.java b/framework/tests/all-systems/Issue5042.java index e4d3a29b621..ce6ca709c7c 100644 --- a/framework/tests/all-systems/Issue5042.java +++ b/framework/tests/all-systems/Issue5042.java @@ -1,68 +1,69 @@ -import java.util.function.Function; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Function; + @SuppressWarnings("initializedfields") // The fields are initialized. public class Issue5042 { - interface PromptViewModel { - boolean isPending(); + interface PromptViewModel { + boolean isPending(); - @Nullable PromptButtonViewModel getConfirmationButton(); - } + @Nullable PromptButtonViewModel getConfirmationButton(); + } - interface PromptButtonViewModel { - @Nullable ConfirmationPopupViewModel getConfirmationPopup(); - } + interface PromptButtonViewModel { + @Nullable ConfirmationPopupViewModel getConfirmationPopup(); + } - interface ConfirmationPopupViewModel { - boolean isShowingConfirmation(); - } + interface ConfirmationPopupViewModel { + boolean isShowingConfirmation(); + } - boolean f(PromptViewModel viewModel) { - PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - } - - static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = - (viewModel) -> { - @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - promptLambda != null ? promptLambda.getConfirmationPopup() : null; + boolean f(PromptViewModel viewModel) { + PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + ConfirmationPopupViewModel popup = prompt != null ? prompt.getConfirmationPopup() : null; return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + } - final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = - (viewModel) -> { - @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); - @Nullable ConfirmationPopupViewModel popup = - prompt == null ? null : prompt.getConfirmationPopup(); - return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); - }; + static final Function IS_PENDING_OR_SHOWING_CONFIRMATION = + (viewModel) -> { + @Nullable PromptButtonViewModel promptLambda = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + promptLambda != null ? promptLambda.getConfirmationPopup() : null; + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; + + final Function IS_PENDING_OR_SHOWING_CONFIRMATION2 = + (viewModel) -> { + @Nullable PromptButtonViewModel prompt = viewModel.getConfirmationButton(); + @Nullable ConfirmationPopupViewModel popup = + prompt == null ? null : prompt.getConfirmationPopup(); + return viewModel.isPending() || (popup != null && popup.isShowingConfirmation()); + }; - @Nullable PromptButtonViewModel promptfield; - Producer o = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield == null ? null : promptfield.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + @Nullable PromptButtonViewModel promptfield; + Producer o = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield == null ? null : promptfield.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - static @Nullable PromptButtonViewModel promptfield2; + static @Nullable PromptButtonViewModel promptfield2; - static Producer o2 = - () -> { - @Nullable ConfirmationPopupViewModel popup = - promptfield2 == null ? null : promptfield2.getConfirmationPopup(); - return (popup != null && popup.isShowingConfirmation()); - }; + static Producer o2 = + () -> { + @Nullable ConfirmationPopupViewModel popup = + promptfield2 == null ? null : promptfield2.getConfirmationPopup(); + return (popup != null && popup.isShowingConfirmation()); + }; - interface Producer { - Object apply(); - } + interface Producer { + Object apply(); + } - Issue5042(int i, int i2) {} + Issue5042(int i, int i2) {} - Issue5042(int i) {} + Issue5042(int i) {} - Issue5042() {} + Issue5042() {} } diff --git a/framework/tests/all-systems/Issue5436.java b/framework/tests/all-systems/Issue5436.java index 72329658979..c369f7a579d 100644 --- a/framework/tests/all-systems/Issue5436.java +++ b/framework/tests/all-systems/Issue5436.java @@ -4,23 +4,23 @@ @SuppressWarnings({"unchecked", "all"}) // just check for crashes. public class Issue5436 { - static class BoundedWindow {} + static class BoundedWindow {} - static class Accessor { - public Accessor(Supplier s) {} + static class Accessor { + public Accessor(Supplier s) {} - Accessor() {} - } + Accessor() {} + } - private final Accessor stateAccessor; - private List currentWindows; + private final Accessor stateAccessor; + private List currentWindows; - Issue5436() { - currentWindows = new ArrayList<>(); - stateAccessor = new Accessor(() -> currentWindows); - } + Issue5436() { + currentWindows = new ArrayList<>(); + stateAccessor = new Accessor(() -> currentWindows); + } - private Object getCurrentKey() { - return new Object(); - } + private Object getCurrentKey() { + return new Object(); + } } diff --git a/framework/tests/all-systems/Issue577.java b/framework/tests/all-systems/Issue577.java index df27c3a9a6f..552ac760a15 100644 --- a/framework/tests/all-systems/Issue577.java +++ b/framework/tests/all-systems/Issue577.java @@ -2,75 +2,75 @@ // Test with expected errors: checker/tests/nullness/Issue577.java // https://github.com/typetools/checker-framework/issues/577 class Banana extends Apple { - @Override - void fooIssue577Outer(int[] array) {} - - class InnerBanana extends InnerApple { @Override - void foo(int[] array, long[] array2, F2 param3) {} - } + void fooIssue577Outer(int[] array) {} + + class InnerBanana extends InnerApple { + @Override + void foo(int[] array, long[] array2, F2 param3) {} + } } class Apple { - void fooIssue577Outer(T param) {} + void fooIssue577Outer(T param) {} - class InnerApple { - void foo(T param, E param2, F param3) {} - } + class InnerApple { + void foo(T param, E param2, F param3) {} + } } class Pineapple extends Apple { - @Override - void fooIssue577Outer(E array) {} + @Override + void fooIssue577Outer(E array) {} } class IntersectionAsMemberOf { - interface MyGenericInterface { - F getF(); - } + interface MyGenericInterface { + F getF(); + } - > void foo(T param) { - String s = param.getF(); - } + > void foo(T param) { + String s = param.getF(); + } } class UnionAsMemberOf { - interface MyInterface { - T getT(); - } + interface MyInterface { + T getT(); + } - class MyExceptionA extends Throwable implements Cloneable, MyInterface { - public String getT() { - return "t"; + class MyExceptionA extends Throwable implements Cloneable, MyInterface { + public String getT() { + return "t"; + } } - } - class MyExceptionB extends Throwable implements Cloneable, MyInterface { - public String getT() { - return "t"; + class MyExceptionB extends Throwable implements Cloneable, MyInterface { + public String getT() { + return "t"; + } } - } - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - @SuppressWarnings("ainfertest") // only check WPI for crashes - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - String s = ex1.getT(); + @SuppressWarnings("ainfertest") // only check WPI for crashes + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + String s = ex1.getT(); + } } - } } final class Issue577Outer { - private Inner createInner(ReferenceQueue q) { - return new Inner(q); - } + private Inner createInner(ReferenceQueue q) { + return new Inner(q); + } - private final class Inner { - private Inner(ReferenceQueue q) {} - } + private final class Inner { + private Inner(ReferenceQueue q) {} + } } class ReferenceQueue {} diff --git a/framework/tests/all-systems/Issue6019.java b/framework/tests/all-systems/Issue6019.java index e838650c060..4b0d7738d21 100644 --- a/framework/tests/all-systems/Issue6019.java +++ b/framework/tests/all-systems/Issue6019.java @@ -1,6 +1,6 @@ // @below-java10-jdk-skip-test public class Issue6019 { - void test() { - var magic = new byte[2]; - } + void test() { + var magic = new byte[2]; + } } diff --git a/framework/tests/all-systems/Issue6025.java b/framework/tests/all-systems/Issue6025.java index bbb9777fa67..18cb1a3c1b6 100644 --- a/framework/tests/all-systems/Issue6025.java +++ b/framework/tests/all-systems/Issue6025.java @@ -1,16 +1,16 @@ public class Issue6025 { - public interface A {} + public interface A {} - private static final class B {} + private static final class B {} - public interface C, T2 extends A> {} + public interface C, T2 extends A> {} - private final B, ? extends A>> one = new B<>(); - private final B, ? extends A>> two = new B<>(); + private final B, ? extends A>> one = new B<>(); + private final B, ? extends A>> two = new B<>(); - void f(boolean b) { - B> three1 = one; - B> three = b ? two : one; - } + void f(boolean b) { + B> three1 = one; + B> three = b ? two : one; + } } diff --git a/framework/tests/all-systems/Issue6076.java b/framework/tests/all-systems/Issue6076.java index 6a95b2b72c0..b0040874eb3 100644 --- a/framework/tests/all-systems/Issue6076.java +++ b/framework/tests/all-systems/Issue6076.java @@ -5,62 +5,64 @@ public class Issue6076 { - public SSTableReaderLoadingBuilder loadingBuilder( - Descriptor descriptor, TableMetadataRef tableMetadataRef, Set components) { - return new BtiTableReaderLoadingBuilder( - new SSTable.Builder<>(descriptor) - .setTableMetadataRef(tableMetadataRef) - .setComponents(components)); - } - - public static class Component {} - - public static class Descriptor {} - - public static final class TableMetadataRef {} - - public static class BtiTableReaderLoadingBuilder - extends SortedTableReaderLoadingBuilder { - public BtiTableReaderLoadingBuilder(SSTable.Builder builder) {} - } - - public static class BtiTableReader extends SSTableReaderWithFilter { - public static class Builder extends SSTableReaderWithFilter.Builder {} - } - - public abstract static class SSTableReaderWithFilter extends SSTableReader { - public abstract static class Builder> - extends SSTableReader.Builder {} - } - - @SuppressWarnings("all") // Just check for crashes. - public abstract static class SSTable { - public static class Builder> { - public Builder() {} - - public Builder(Descriptor descriptor) {} - - @SuppressWarnings("unchecked") - public B setTableMetadataRef(TableMetadataRef ref) { - return (B) this; - } - - @SuppressWarnings("unchecked") - public B setComponents(Collection components) { - return (B) this; - } + public SSTableReaderLoadingBuilder loadingBuilder( + Descriptor descriptor, TableMetadataRef tableMetadataRef, Set components) { + return new BtiTableReaderLoadingBuilder( + new SSTable.Builder<>(descriptor) + .setTableMetadataRef(tableMetadataRef) + .setComponents(components)); } - } - public abstract static class SSTableReaderLoadingBuilder< - R extends SSTableReader, B extends SSTableReader.Builder> {} + public static class Component {} - public abstract static class SortedTableReaderLoadingBuilder< - R extends SSTableReader, B extends SSTableReader.Builder> - extends SSTableReaderLoadingBuilder {} + public static class Descriptor {} - public abstract static class SSTableReader extends SSTable { - public abstract static class Builder> - extends SSTable.Builder {} - } + public static final class TableMetadataRef {} + + public static class BtiTableReaderLoadingBuilder + extends SortedTableReaderLoadingBuilder { + public BtiTableReaderLoadingBuilder(SSTable.Builder builder) {} + } + + public static class BtiTableReader extends SSTableReaderWithFilter { + public static class Builder + extends SSTableReaderWithFilter.Builder {} + } + + public abstract static class SSTableReaderWithFilter extends SSTableReader { + public abstract static class Builder< + R extends SSTableReaderWithFilter, B extends Builder> + extends SSTableReader.Builder {} + } + + @SuppressWarnings("all") // Just check for crashes. + public abstract static class SSTable { + public static class Builder> { + public Builder() {} + + public Builder(Descriptor descriptor) {} + + @SuppressWarnings("unchecked") + public B setTableMetadataRef(TableMetadataRef ref) { + return (B) this; + } + + @SuppressWarnings("unchecked") + public B setComponents(Collection components) { + return (B) this; + } + } + } + + public abstract static class SSTableReaderLoadingBuilder< + R extends SSTableReader, B extends SSTableReader.Builder> {} + + public abstract static class SortedTableReaderLoadingBuilder< + R extends SSTableReader, B extends SSTableReader.Builder> + extends SSTableReaderLoadingBuilder {} + + public abstract static class SSTableReader extends SSTable { + public abstract static class Builder> + extends SSTable.Builder {} + } } diff --git a/framework/tests/all-systems/Issue6078.java b/framework/tests/all-systems/Issue6078.java index 6e0d2c27b66..6f9df77907d 100644 --- a/framework/tests/all-systems/Issue6078.java +++ b/framework/tests/all-systems/Issue6078.java @@ -1,14 +1,14 @@ import java.lang.invoke.MethodHandle; public class Issue6078 { - static void call(MethodHandle methodHandle) throws Throwable { - methodHandle.invoke(); - } + static void call(MethodHandle methodHandle) throws Throwable { + methodHandle.invoke(); + } - void use() { - foo(); - } + void use() { + foo(); + } - @SafeVarargs - private final void foo(T... ts) {} + @SafeVarargs + private final void foo(T... ts) {} } diff --git a/framework/tests/all-systems/Issue6098.java b/framework/tests/all-systems/Issue6098.java index 9f64ad149ba..84d2f5661f8 100644 --- a/framework/tests/all-systems/Issue6098.java +++ b/framework/tests/all-systems/Issue6098.java @@ -1,11 +1,11 @@ // @below-java10-jdk-skip-test public class Issue6098 { - T method(T t) { - return t; - } + T method(T t) { + return t; + } - void use() { - var c = method(""); - } + void use() { + var c = method(""); + } } diff --git a/framework/tests/all-systems/Issue6104.java b/framework/tests/all-systems/Issue6104.java index 8f5dd26ec71..9b3c572e1cc 100644 --- a/framework/tests/all-systems/Issue6104.java +++ b/framework/tests/all-systems/Issue6104.java @@ -1,9 +1,9 @@ public class Issue6104 { - public void m() { - try { - } catch (Exception e) { - // Because the try block is empty, this lambda is unreachable. - Runnable r = () -> {}; + public void m() { + try { + } catch (Exception e) { + // Because the try block is empty, this lambda is unreachable. + Runnable r = () -> {}; + } } - } } diff --git a/framework/tests/all-systems/Issue6259.java b/framework/tests/all-systems/Issue6259.java index 7e53c2fe9c0..1603fede829 100644 --- a/framework/tests/all-systems/Issue6259.java +++ b/framework/tests/all-systems/Issue6259.java @@ -2,36 +2,40 @@ @SuppressWarnings("unchecked") public class Issue6259 { - public interface A { - interface Builder { - AT build(); + public interface A { + interface Builder { + AT build(); + } } - } - public abstract static class B implements C { - public abstract static class Builder implements C.Builder {} - } + public abstract static class B implements C { + public abstract static class Builder implements C.Builder {} + } - public interface I> {} + public interface I> {} - public interface C { - interface Builder {} - } + public interface C { + interface Builder {} + } - public static final class L implements I> {} + public static final class L implements I> {} - static class S> { + static class S> { - public static < - ET extends A, E2 extends A.Builder, CT, IT extends C, I2 extends C.Builder> - S assemble(I... xs) { - throw new AssertionError(); + public static < + ET extends A, + E2 extends A.Builder, + CT, + IT extends C, + I2 extends C.Builder> + S assemble(I... xs) { + throw new AssertionError(); + } } - } - void f(L f) { - S> storeStack = S.assemble(f); - var storeStackVar = S.assemble(f); - var storeStackVarExplicit = S., String, B, B.Builder>assemble(f); - } + void f(L f) { + S> storeStack = S.assemble(f); + var storeStackVar = S.assemble(f); + var storeStackVarExplicit = S., String, B, B.Builder>assemble(f); + } } diff --git a/framework/tests/all-systems/Issue6282.java b/framework/tests/all-systems/Issue6282.java index 96ebb03671a..8878bd29fe2 100644 --- a/framework/tests/all-systems/Issue6282.java +++ b/framework/tests/all-systems/Issue6282.java @@ -3,18 +3,18 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class Issue6282 { - public static final MethodHandle setAccessible0_Method = setAccessible0_Method(); + public static final MethodHandle setAccessible0_Method = setAccessible0_Method(); - public static final MethodHandle setAccessible0_Method() { - throw new RuntimeException(); - } + public static final MethodHandle setAccessible0_Method() { + throw new RuntimeException(); + } - public static void setAccessible(final AccessibleObject accessibleObject) { - try { - boolean newFlag = (boolean) setAccessible0_Method.invokeExact(accessibleObject, true); - assert newFlag; - } catch (Throwable throwable) { - throw new AssertionError(throwable); + public static void setAccessible(final AccessibleObject accessibleObject) { + try { + boolean newFlag = (boolean) setAccessible0_Method.invokeExact(accessibleObject, true); + assert newFlag; + } catch (Throwable throwable) { + throw new AssertionError(throwable); + } } - } } diff --git a/framework/tests/all-systems/Issue6319.java b/framework/tests/all-systems/Issue6319.java index e2453bb9738..b56dfb5d570 100644 --- a/framework/tests/all-systems/Issue6319.java +++ b/framework/tests/all-systems/Issue6319.java @@ -4,8 +4,10 @@ @SuppressWarnings("all") // just check for crashes. class Issue6319 { - void f(List> list) { - for (Enum value : - list.stream().sorted(Comparator.comparing(Enum::name)).collect(Collectors.toList())) {} - } + void f(List> list) { + for (Enum value : + list.stream() + .sorted(Comparator.comparing(Enum::name)) + .collect(Collectors.toList())) {} + } } diff --git a/framework/tests/all-systems/Issue6373.java b/framework/tests/all-systems/Issue6373.java index b21fbaf5e5d..875a1139269 100644 --- a/framework/tests/all-systems/Issue6373.java +++ b/framework/tests/all-systems/Issue6373.java @@ -1,89 +1,89 @@ public class Issue6373 { - abstract static class C1< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5> - extends C6 {} - - static class C6 {} - - abstract static class C2< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - RT extends C5> - implements C7 {} - - abstract static class C3< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - R extends C5> {} - - abstract static class C4< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - R extends C5> { - interface I {} - } - - abstract static class C5> implements C7 {} - - interface C7 {} - - abstract static class C8< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5, - RpT extends C5> { - - public static < - CM extends C1, - QM extends C2, - BM extends C3, - DM extends C4, - CRM extends C5, - RpTM extends C5> - Builder n(QM q) { - throw new AssertionError(); + abstract static class C1< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5> + extends C6 {} + + static class C6 {} + + abstract static class C2< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + RT extends C5> + implements C7 {} + + abstract static class C3< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> {} + + abstract static class C4< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> { + interface I {} } - abstract static class Builder< - C extends C1, - Q extends C2, - B extends C3, - D extends C4, - CR extends C5, - RpT extends C5> {} - } + abstract static class C5> implements C7 {} + + interface C7 {} + + abstract static class C8< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> { + + public static < + CM extends C1, + QM extends C2, + BM extends C3, + DM extends C4, + CRM extends C5, + RpTM extends C5> + Builder n(QM q) { + throw new AssertionError(); + } + + abstract static class Builder< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> {} + } - abstract static class C9> {} + abstract static class C9> {} - static class C13 { + static class C13 { - static final class C14 extends C1 {} + static final class C14 extends C1 {} - static final class C15 extends C2 {} + static final class C15 extends C2 {} - static final class C18 extends C5 {} + static final class C18 extends C5 {} - static class C17 extends C4 implements C4.I, C19 {} + static class C17 extends C4 implements C4.I, C19 {} - static final class C16 extends C3 {} - } + static final class C16 extends C3 {} + } - interface C19 {} + interface C19 {} - void f(C13.C15 c) { - C8.n(c); - } + void f(C13.C15 c) { + C8.n(c); + } } diff --git a/framework/tests/all-systems/Issue6421.java b/framework/tests/all-systems/Issue6421.java index 4b7f64553fa..f995d6e13b0 100644 --- a/framework/tests/all-systems/Issue6421.java +++ b/framework/tests/all-systems/Issue6421.java @@ -3,48 +3,50 @@ @SuppressWarnings("all") // Just check for crashes. public class Issue6421 { - public static final List> DEFAULT_ARBITRARY_INTROSPECTORS = - list( - MatcherOperator.exactTypeMatchOperator( - UnidentifiableType.class, NullArbitraryIntrospector.INSTANCE), - MatcherOperator.exactTypeMatchOperator( - GeneratingWildcardType.class, context -> new ArbitraryIntrospectorResult())); - - static List list(E e1, E e2) { - throw new RuntimeException(); - } + public static final List> + DEFAULT_ARBITRARY_INTROSPECTORS = + list( + MatcherOperator.exactTypeMatchOperator( + UnidentifiableType.class, NullArbitraryIntrospector.INSTANCE), + MatcherOperator.exactTypeMatchOperator( + GeneratingWildcardType.class, + context -> new ArbitraryIntrospectorResult())); + + static List list(E e1, E e2) { + throw new RuntimeException(); + } - public interface ArbitraryIntrospector { - ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context); - } + public interface ArbitraryIntrospector { + ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context); + } - public interface CombinableArbitrary {} + public interface CombinableArbitrary {} - public static final class ArbitraryGeneratorContext {} + public static final class ArbitraryGeneratorContext {} - public static final class MatcherOperator { - public static MatcherOperator exactTypeMatchOperator(Class type, T operator) { - throw new RuntimeException(); + public static final class MatcherOperator { + public static MatcherOperator exactTypeMatchOperator(Class type, T operator) { + throw new RuntimeException(); + } } - } - public static class GeneratingWildcardType {} + public static class GeneratingWildcardType {} - public static class UnidentifiableType {} + public static class UnidentifiableType {} - public static final class NullArbitraryIntrospector implements ArbitraryIntrospector { + public static final class NullArbitraryIntrospector implements ArbitraryIntrospector { - public static final NullArbitraryIntrospector INSTANCE = new NullArbitraryIntrospector(); + public static final NullArbitraryIntrospector INSTANCE = new NullArbitraryIntrospector(); - @Override - public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) { - return null; + @Override + public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) { + return null; + } } - } - public static final class ArbitraryIntrospectorResult { - public ArbitraryIntrospectorResult() {} + public static final class ArbitraryIntrospectorResult { + public ArbitraryIntrospectorResult() {} - public ArbitraryIntrospectorResult(CombinableArbitrary value) {} - } + public ArbitraryIntrospectorResult(CombinableArbitrary value) {} + } } diff --git a/framework/tests/all-systems/Issue6421C.java b/framework/tests/all-systems/Issue6421C.java index 3c5a08032a5..983a598c134 100644 --- a/framework/tests/all-systems/Issue6421C.java +++ b/framework/tests/all-systems/Issue6421C.java @@ -5,12 +5,12 @@ @SuppressWarnings("all") public class Issue6421C { - private static String[] getParameterNames(Constructor constructor) { - Parameter[] parameters = constructor.getParameters(); - return Arrays.stream(parameters).map(Parameter::getName).toArray(String[]::new); - } + private static String[] getParameterNames(Constructor constructor) { + Parameter[] parameters = constructor.getParameters(); + return Arrays.stream(parameters).map(Parameter::getName).toArray(String[]::new); + } - public void processConstructor(Stream> stream2) { - Class[] arguments = stream2.toArray(Class[]::new); - } + public void processConstructor(Stream> stream2) { + Class[] arguments = stream2.toArray(Class[]::new); + } } diff --git a/framework/tests/all-systems/Issue6421D.java b/framework/tests/all-systems/Issue6421D.java index e9e2063641a..678ef76bfd9 100644 --- a/framework/tests/all-systems/Issue6421D.java +++ b/framework/tests/all-systems/Issue6421D.java @@ -4,35 +4,37 @@ @SuppressWarnings("all") // Check for crashes. public class Issue6421D { - private Function - generateJavaTypeArbitrarySet = null; - private JavaTypeArbitraryGenerator javaTypeArbitraryGenerator = null; - private JavaArbitraryResolver javaArbitraryResolver = null; - - void method() { - this.generateJavaTypeArbitrarySet = - defaultIfNull( - this.generateJavaTypeArbitrarySet, - () -> - constraintGenerator -> - new JqwikJavaTypeArbitraryGeneratorSet( - this.javaTypeArbitraryGenerator, javaArbitraryResolver)); - } - - public interface JavaArbitraryResolver {} - - public interface JavaConstraintGenerator {} - - public interface JavaTypeArbitraryGeneratorSet {} - - public final class JqwikJavaTypeArbitraryGeneratorSet implements JavaTypeArbitraryGeneratorSet { - public JqwikJavaTypeArbitraryGeneratorSet( - JavaTypeArbitraryGenerator arbitraryGenerator, JavaArbitraryResolver arbitraryResolver) {} - } - - public interface JavaTypeArbitraryGenerator {} - - private static T defaultIfNull(T obj, Supplier defaultValue) { - return obj != null ? obj : defaultValue.get(); - } + private Function + generateJavaTypeArbitrarySet = null; + private JavaTypeArbitraryGenerator javaTypeArbitraryGenerator = null; + private JavaArbitraryResolver javaArbitraryResolver = null; + + void method() { + this.generateJavaTypeArbitrarySet = + defaultIfNull( + this.generateJavaTypeArbitrarySet, + () -> + constraintGenerator -> + new JqwikJavaTypeArbitraryGeneratorSet( + this.javaTypeArbitraryGenerator, + javaArbitraryResolver)); + } + + public interface JavaArbitraryResolver {} + + public interface JavaConstraintGenerator {} + + public interface JavaTypeArbitraryGeneratorSet {} + + public final class JqwikJavaTypeArbitraryGeneratorSet implements JavaTypeArbitraryGeneratorSet { + public JqwikJavaTypeArbitraryGeneratorSet( + JavaTypeArbitraryGenerator arbitraryGenerator, + JavaArbitraryResolver arbitraryResolver) {} + } + + public interface JavaTypeArbitraryGenerator {} + + private static T defaultIfNull(T obj, Supplier defaultValue) { + return obj != null ? obj : defaultValue.get(); + } } diff --git a/framework/tests/all-systems/Issue6433.java b/framework/tests/all-systems/Issue6433.java index 2ee8d30f6a4..df7f0be9757 100644 --- a/framework/tests/all-systems/Issue6433.java +++ b/framework/tests/all-systems/Issue6433.java @@ -1,17 +1,17 @@ // Test case for Issue 6433: // https://github.com/typetools/checker-framework/issues/6433 class Issue6433 { - void foo() {} + void foo() {} - void bar() { - foo(); - while (true) {} - } + void bar() { + foo(); + while (true) {} + } - void baz() { - foo(); - while (true) { - break; + void baz() { + foo(); + while (true) { + break; + } } - } } diff --git a/framework/tests/all-systems/Issue6438.java b/framework/tests/all-systems/Issue6438.java index 8950ff9eb00..00541197cd7 100644 --- a/framework/tests/all-systems/Issue6438.java +++ b/framework/tests/all-systems/Issue6438.java @@ -4,13 +4,13 @@ import java.util.Optional; abstract class Issue6438 { - Optional a(boolean b, Class clazz) { - return b ? Optional.empty() : Optional.of(b(clazz)); - } + Optional a(boolean b, Class clazz) { + return b ? Optional.empty() : Optional.of(b(clazz)); + } - abstract T b(Class clazz); + abstract T b(Class clazz); - interface First {} + interface First {} - interface Second {} + interface Second {} } diff --git a/framework/tests/all-systems/Issue6442.java b/framework/tests/all-systems/Issue6442.java index a25fb6cf8cd..7e71d5cf03f 100644 --- a/framework/tests/all-systems/Issue6442.java +++ b/framework/tests/all-systems/Issue6442.java @@ -2,33 +2,34 @@ public class Issue6442 { - public static class Crash - extends Issue6442.PTransform>, PCollection>>> { - - public PCollection, Iterable>> expand( - PCollection, I>> x) { - return x.apply(new Crash<>()); + public static class Crash + extends Issue6442.PTransform< + PCollection>, PCollection>>> { + + public PCollection, Iterable>> expand( + PCollection, I>> x) { + return x.apply(new Crash<>()); + } } - } - public interface PValue extends POutput, PInput {} + public interface PValue extends POutput, PInput {} - public interface POutput {} + public interface POutput {} - public interface PInput {} + public interface PInput {} - public static class Pair {} + public static class Pair {} - public static class PCollection extends PValueBase implements PValue { + public static class PCollection extends PValueBase implements PValue { - public O1 apply(PTransform, O1> t) { - throw new RuntimeException(); + public O1 apply(PTransform, O1> t) { + throw new RuntimeException(); + } } - } - public abstract static class PValueBase implements PValue {} + public abstract static class PValueBase implements PValue {} - public abstract static class PTransform {} + public abstract static class PTransform {} - public static class ShardedKey {} + public static class ShardedKey {} } diff --git a/framework/tests/all-systems/Issue671.java b/framework/tests/all-systems/Issue671.java index 2308aafb149..76f3e87967b 100644 --- a/framework/tests/all-systems/Issue671.java +++ b/framework/tests/all-systems/Issue671.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/671 public class Issue671 { - void foo() { - byte var = 0; - boolean f = (var == (method() ? 2 : 0)); - } + void foo() { + byte var = 0; + boolean f = (var == (method() ? 2 : 0)); + } - boolean method() { - return false; - } + boolean method() { + return false; + } } diff --git a/framework/tests/all-systems/Issue689.java b/framework/tests/all-systems/Issue689.java index cb713b1c8dd..eeeb9f44273 100644 --- a/framework/tests/all-systems/Issue689.java +++ b/framework/tests/all-systems/Issue689.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/689 public class Issue689 { - public int initializerFodder() { - return 3; - } + public int initializerFodder() { + return 3; + } - public Runnable trigger() { - return new Runnable() { - // Issue 689 triggers when examining a method invocation inside a field initializer of - // an anonymous inner class - public final int val = initializerFodder(); + public Runnable trigger() { + return new Runnable() { + // Issue 689 triggers when examining a method invocation inside a field initializer of + // an anonymous inner class + public final int val = initializerFodder(); - public void run() { - // do nothing - } - }; - } + public void run() { + // do nothing + } + }; + } } diff --git a/framework/tests/all-systems/Issue691.java b/framework/tests/all-systems/Issue691.java index 28a31431ee6..de789193412 100644 --- a/framework/tests/all-systems/Issue691.java +++ b/framework/tests/all-systems/Issue691.java @@ -8,5 +8,5 @@ interface MyInterface {} // issue a valid type checking error for this code, so suppress any warnings. @SuppressWarnings("all") public class Issue691 implements MyInterface { - MyInterface mi = new Issue691<>(); + MyInterface mi = new Issue691<>(); } diff --git a/framework/tests/all-systems/Issue692.java b/framework/tests/all-systems/Issue692.java index 0d5a2b106df..906242f73c8 100644 --- a/framework/tests/all-systems/Issue692.java +++ b/framework/tests/all-systems/Issue692.java @@ -2,8 +2,8 @@ // https://github.com/typetools/checker-framework/issues/692 public class Issue692> { - private boolean method(Object param, Class tClass) { - Class paramClass = param.getClass(); - return paramClass == tClass || paramClass.getSuperclass() == tClass; - } + private boolean method(Object param, Class tClass) { + Class paramClass = param.getClass(); + return paramClass == tClass || paramClass.getSuperclass() == tClass; + } } diff --git a/framework/tests/all-systems/Issue696.java b/framework/tests/all-systems/Issue696.java index a9529f5d4a9..8f6113eb72f 100644 --- a/framework/tests/all-systems/Issue696.java +++ b/framework/tests/all-systems/Issue696.java @@ -5,5 +5,5 @@ import java.util.Map; public class Issue696 { - public static void test(final List> input) {} + public static void test(final List> input) {} } diff --git a/framework/tests/all-systems/Issue703.java b/framework/tests/all-systems/Issue703.java index 36b6d4a7688..19b4bf472f2 100644 --- a/framework/tests/all-systems/Issue703.java +++ b/framework/tests/all-systems/Issue703.java @@ -1,17 +1,18 @@ // Test case for Issue 703: // https://github.com/eisop/checker-framework/issues/703 -import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.List; + abstract class Issue703 { - void foo() { - List attrs = or(x(), y()); - } + void foo() { + List attrs = or(x(), y()); + } - abstract @NonNull T or(T nullableValue, T defaultValue); + abstract @NonNull T or(T nullableValue, T defaultValue); - abstract List x(); + abstract List x(); - abstract List y(); + abstract List y(); } diff --git a/framework/tests/all-systems/Issue717.java b/framework/tests/all-systems/Issue717.java index fdb7f6dc20b..c58a1e8de64 100644 --- a/framework/tests/all-systems/Issue717.java +++ b/framework/tests/all-systems/Issue717.java @@ -2,17 +2,17 @@ // https://github.com/typetools/checker-framework/issues/717 public class Issue717 { - public static > void foo2(T a, T b) { - a.compareTo(b); - } + public static > void foo2(T a, T b) { + a.compareTo(b); + } - public static > void foo(T a, T b) { - // asSuper doesn't find Interface, so the type variable F is not substituted - // causing isSuptype to be called between Object & Interface and F. - a.compareTo(b); - } + public static > void foo(T a, T b) { + // asSuper doesn't find Interface, so the type variable F is not substituted + // causing isSuptype to be called between Object & Interface and F. + a.compareTo(b); + } - interface Interface { - void compareTo(F t); - } + interface Interface { + void compareTo(F t); + } } diff --git a/framework/tests/all-systems/Issue738.java b/framework/tests/all-systems/Issue738.java index 499a68a3f15..d820d1366d3 100644 --- a/framework/tests/all-systems/Issue738.java +++ b/framework/tests/all-systems/Issue738.java @@ -3,12 +3,12 @@ // Also, see checker/tests/nullness/Issue738.java @SuppressWarnings("all") // This testcase is checking for crashes. public class Issue738 { - public static void methodA() { - methodB(0, new Object()); // This compiles fine. - methodB(new int[0], new Object[0]); // This crashes. - } + public static void methodA() { + methodB(0, new Object()); // This compiles fine. + methodB(new int[0], new Object[0]); // This crashes. + } - private static void methodB(T paramA, T paramB) { - // Do nothing. - } + private static void methodB(T paramA, T paramB) { + // Do nothing. + } } diff --git a/framework/tests/all-systems/Issue759.java b/framework/tests/all-systems/Issue759.java index b6d23961e5b..df3eaec101e 100644 --- a/framework/tests/all-systems/Issue759.java +++ b/framework/tests/all-systems/Issue759.java @@ -1,38 +1,38 @@ // Testcase for Issue759 // https://github.com/typetools/checker-framework/issues/759 @SuppressWarnings({ - "nullness", - "unchecked", - "ainfertest", - "value" + "nullness", + "unchecked", + "ainfertest", + "value" }) // See checker/test/nullness/Issue759.java; ainfertest and value are suppressed because WPI // errors shouldn't be issued here, just checked for crashes public class Issue759 { - void possibleValues(final Class enumType) { - lowercase(enumType.getEnumConstants()); - lowercase2(enumType.getEnumConstants()); - lowercase3(enumType.getEnumConstants()); - } + void possibleValues(final Class enumType) { + lowercase(enumType.getEnumConstants()); + lowercase2(enumType.getEnumConstants()); + lowercase3(enumType.getEnumConstants()); + } - > void lowercase(final T... items) {} + > void lowercase(final T... items) {} - > void lowercase2(final T[] items) {} + > void lowercase2(final T[] items) {} - void lowercase3(final T items) {} + void lowercase3(final T items) {} } @SuppressWarnings("nullness") class Gen> { - T[] getConstants() { - return null; - } + T[] getConstants() { + return null; + } } @SuppressWarnings("nullness") class IncompatibleTypes { - void possibleValues(final Gen genType) { - lowercase(genType.getConstants()); - } + void possibleValues(final Gen genType) { + lowercase(genType.getConstants()); + } - void lowercase(final S items) {} + void lowercase(final S items) {} } diff --git a/framework/tests/all-systems/Issue807.java b/framework/tests/all-systems/Issue807.java index 376e3031b9a..8b6b487e905 100644 --- a/framework/tests/all-systems/Issue807.java +++ b/framework/tests/all-systems/Issue807.java @@ -5,16 +5,16 @@ public class Issue807 { - class MyEntry { - MyEntry(MyEntry e) {} - } + class MyEntry { + MyEntry(MyEntry e) {} + } - Consumer> entryConsumer(Consumer> action) { - // The "new MyEntry" isn't a subtype of "? super MyEntry" in - // most type systems. Suppress that error, as it's not the - // point of this test. - @SuppressWarnings("all") - Consumer> res = e -> action.accept(new MyEntry<>(e)); - return res; - } + Consumer> entryConsumer(Consumer> action) { + // The "new MyEntry" isn't a subtype of "? super MyEntry" in + // most type systems. Suppress that error, as it's not the + // point of this test. + @SuppressWarnings("all") + Consumer> res = e -> action.accept(new MyEntry<>(e)); + return res; + } } diff --git a/framework/tests/all-systems/Issue808.java b/framework/tests/all-systems/Issue808.java index 87142f723b0..187399f8d4b 100644 --- a/framework/tests/all-systems/Issue808.java +++ b/framework/tests/all-systems/Issue808.java @@ -5,19 +5,19 @@ import java.util.List; public class Issue808 { - void f() { - Arrays.asList(0, 0, "", Arrays.asList(new Object[0])); - foo(new Object(), bar()); - new Issue808(bar()); - foo(bar()); - List list = Arrays.asList(new Object[0]); - } + void f() { + Arrays.asList(0, 0, "", Arrays.asList(new Object[0])); + foo(new Object(), bar()); + new Issue808(bar()); + foo(bar()); + List list = Arrays.asList(new Object[0]); + } - T bar() { - throw new RuntimeException(); - } + T bar() { + throw new RuntimeException(); + } - void foo(Object... param) {} + void foo(Object... param) {} - Issue808(Object... param) {} + Issue808(Object... param) {} } diff --git a/framework/tests/all-systems/Issue810.java b/framework/tests/all-systems/Issue810.java index a0d2a3ecf1a..561b2879b61 100644 --- a/framework/tests/all-systems/Issue810.java +++ b/framework/tests/all-systems/Issue810.java @@ -6,6 +6,6 @@ import java.util.Set; public class Issue810 { - Map m = new HashMap<>(); - Set n = m.keySet(); + Map m = new HashMap<>(); + Set n = m.keySet(); } diff --git a/framework/tests/all-systems/Issue887.java b/framework/tests/all-systems/Issue887.java index 1a66012919c..4a567ad2cc0 100644 --- a/framework/tests/all-systems/Issue887.java +++ b/framework/tests/all-systems/Issue887.java @@ -5,12 +5,12 @@ import java.util.List; public abstract class Issue887 { - @SuppressWarnings("nullness") // See checker/tests/nullness/Issue887.java - void test() { - method(foo(null).get(0)); - } + @SuppressWarnings("nullness") // See checker/tests/nullness/Issue887.java + void test() { + method(foo(null).get(0)); + } - void method(Number o) {} + void method(Number o) {} - abstract List foo(T t); + abstract List foo(T t); } diff --git a/framework/tests/all-systems/Issue888.java b/framework/tests/all-systems/Issue888.java index c3a8dbbaa4d..216359ea732 100644 --- a/framework/tests/all-systems/Issue888.java +++ b/framework/tests/all-systems/Issue888.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/888 public class Issue888 { - T foo(T t) { - return t; - } + T foo(T t) { + return t; + } - void bar(int i) { - foo(i).toString(); - } + void bar(int i) { + foo(i).toString(); + } } diff --git a/framework/tests/all-systems/Issue913.java b/framework/tests/all-systems/Issue913.java index 4f5c441ad8a..165f4966b3b 100644 --- a/framework/tests/all-systems/Issue913.java +++ b/framework/tests/all-systems/Issue913.java @@ -2,15 +2,15 @@ // https://github.com/typetools/checker-framework/issues/913 public class Issue913 { - void test(Ordering o) { - Multimap newMap = create(o); - } + void test(Ordering o) { + Multimap newMap = create(o); + } - static Multimap create(Ordering valueComparator) { - throw new RuntimeException(); - } + static Multimap create(Ordering valueComparator) { + throw new RuntimeException(); + } - interface Multimap {} + interface Multimap {} - class Ordering {} + class Ordering {} } diff --git a/framework/tests/all-systems/Issue953.java b/framework/tests/all-systems/Issue953.java index 106e2570b50..1aebe8ec45b 100644 --- a/framework/tests/all-systems/Issue953.java +++ b/framework/tests/all-systems/Issue953.java @@ -4,21 +4,21 @@ import java.util.List; public class Issue953 { - class MyCollector {} + class MyCollector {} - class MyStream { - F collect(MyCollector param) { - throw new RuntimeException(); + class MyStream { + F collect(MyCollector param) { + throw new RuntimeException(); + } } - } - public static void test(MyStream y) { - // Type argument inference fails, so a checker may report a type checking error. - @SuppressWarnings("all") - List counts = y.collect(toList()); - } + public static void test(MyStream y) { + // Type argument inference fails, so a checker may report a type checking error. + @SuppressWarnings("all") + List counts = y.collect(toList()); + } - static MyCollector> toList() { - throw new RuntimeException(); - } + static MyCollector> toList() { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/Issue953b.java b/framework/tests/all-systems/Issue953b.java index 64a570b45d3..3b411dc0b4b 100644 --- a/framework/tests/all-systems/Issue953b.java +++ b/framework/tests/all-systems/Issue953b.java @@ -5,21 +5,21 @@ import java.util.List; public class Issue953b { - class MyCollector {} + class MyCollector {} - class MyStream { - F collect(MyCollector param) { - throw new RuntimeException(); + class MyStream { + F collect(MyCollector param) { + throw new RuntimeException(); + } } - } - public static void test(MyStream y) { - // Type argument inference fails, so a checker may report a type checking error. - @SuppressWarnings("all") - List counts = y.collect(toList()); - } + public static void test(MyStream y) { + // Type argument inference fails, so a checker may report a type checking error. + @SuppressWarnings("all") + List counts = y.collect(toList()); + } - static MyCollector> toList() { - throw new RuntimeException(); - } + static MyCollector> toList() { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/Issue988.java b/framework/tests/all-systems/Issue988.java index b9388524a2c..193dc92f5ea 100644 --- a/framework/tests/all-systems/Issue988.java +++ b/framework/tests/all-systems/Issue988.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/988 abstract class Issue988 { - abstract Class getRawClass(); + abstract Class getRawClass(); - abstract Class getGenericClass(); + abstract Class getGenericClass(); - Class getWithArg(boolean generic) { - return generic ? getGenericClass() : getRawClass(); - } + Class getWithArg(boolean generic) { + return generic ? getGenericClass() : getRawClass(); + } } diff --git a/framework/tests/all-systems/LambdaParam.java b/framework/tests/all-systems/LambdaParam.java index 37b234a39d2..d489ec80e18 100644 --- a/framework/tests/all-systems/LambdaParam.java +++ b/framework/tests/all-systems/LambdaParam.java @@ -2,34 +2,35 @@ @SuppressWarnings("all") // Just check for crashes. public class LambdaParam> { - void method(Range restriction, Range> lowerBoundWindow) { - Cut> upperBoundOnLowerBounds = - Ordering.natural().min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); - } - - abstract static class Cut implements Comparable>, Serializable { - static Cut belowValue(C endpoint) { - throw new RuntimeException(); + void method(Range restriction, Range> lowerBoundWindow) { + Cut> upperBoundOnLowerBounds = + Ordering.natural() + .min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); } - } - static class Ordering { - public static Ordering natural() { - throw new RuntimeException(); + abstract static class Cut implements Comparable>, Serializable { + static Cut belowValue(C endpoint) { + throw new RuntimeException(); + } } - public E min(E a, E b) { - throw new RuntimeException(); + static class Ordering { + public static Ordering natural() { + throw new RuntimeException(); + } + + public E min(E a, E b) { + throw new RuntimeException(); + } } - } - public static final class Range { - final Cut upperBound = - new Cut() { - @Override - public int compareTo(Cut o) { - return 0; - } - }; - } + public static final class Range { + final Cut upperBound = + new Cut() { + @Override + public int compareTo(Cut o) { + return 0; + } + }; + } } diff --git a/framework/tests/all-systems/LambdaParams.java b/framework/tests/all-systems/LambdaParams.java index 5e71bff34ff..021ad94b370 100644 --- a/framework/tests/all-systems/LambdaParams.java +++ b/framework/tests/all-systems/LambdaParams.java @@ -2,20 +2,20 @@ import java.util.function.Function; public class LambdaParams { - interface Stream { - Stream map(Function mapper); - } + interface Stream { + Stream map(Function mapper); + } - static Z identity(Z p) { - throw new RuntimeException(); - } + static Z identity(Z p) { + throw new RuntimeException(); + } - static Stream stream(Q[] array) { - throw new RuntimeException(); - } + static Stream stream(Q[] array) { + throw new RuntimeException(); + } - static void method() { - Function> mapper = - identity(f -> stream(f.getAnnotations()).map(annotation -> "")); - } + static void method() { + Function> mapper = + identity(f -> stream(f.getAnnotations()).map(annotation -> "")); + } } diff --git a/framework/tests/all-systems/LightWeightCache.java b/framework/tests/all-systems/LightWeightCache.java index f8b66c2ad2a..d42d9b54e65 100644 --- a/framework/tests/all-systems/LightWeightCache.java +++ b/framework/tests/all-systems/LightWeightCache.java @@ -6,58 +6,58 @@ @SuppressWarnings("allcheckers") // Only check for crashes class LightWeightCache extends LightWeightGSet { - @Override - public Iterator iterator() { - final Iterator iter = super.iterator(); - return new Iterator() { - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public E next() { - return iter.next(); - } - - @Override - public void remove() {} - }; - } + @Override + public Iterator iterator() { + final Iterator iter = super.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public E next() { + return iter.next(); + } + + @Override + public void remove() {} + }; + } } @SuppressWarnings("allcheckers") // Only check for crashes class LightWeightGSet implements GSet { - interface LinkedElement {} - - protected LinkedElement[] entries; - - @Override - public Iterator iterator() { - return new SetIterator(); - } + interface LinkedElement {} - private class SetIterator implements Iterator { - private int index = -1; - private LinkedElement next = nextNonemptyEntry(); - - private LinkedElement nextNonemptyEntry() { - for (index++; index < entries.length && entries[index] == null; index++) - ; - return index < entries.length ? entries[index] : null; - } + protected LinkedElement[] entries; @Override - public E next() { - return null; + public Iterator iterator() { + return new SetIterator(); } - @Override - public boolean hasNext() { - return false; + private class SetIterator implements Iterator { + private int index = -1; + private LinkedElement next = nextNonemptyEntry(); + + private LinkedElement nextNonemptyEntry() { + for (index++; index < entries.length && entries[index] == null; index++) + ; + return index < entries.length ? entries[index] : null; + } + + @Override + public E next() { + return null; + } + + @Override + public boolean hasNext() { + return false; + } } - } } interface GSet extends Iterable {} diff --git a/framework/tests/all-systems/LongDouble.java b/framework/tests/all-systems/LongDouble.java index 95f85d4fb5e..fb154df5b65 100644 --- a/framework/tests/all-systems/LongDouble.java +++ b/framework/tests/all-systems/LongDouble.java @@ -2,26 +2,26 @@ public abstract class LongDouble { - public abstract T getMaxValue(); + public abstract T getMaxValue(); - public class LongLongDouble extends LongDouble { - @java.lang.Override - public Long getMaxValue() { - return Long.MAX_VALUE; + public class LongLongDouble extends LongDouble { + @java.lang.Override + public Long getMaxValue() { + return Long.MAX_VALUE; + } } - } - public class DoubleLongDouble extends LongDouble { - @java.lang.Override - public Double getMaxValue() { - return Double.MAX_VALUE; + public class DoubleLongDouble extends LongDouble { + @java.lang.Override + public Double getMaxValue() { + return Double.MAX_VALUE; + } } - } - public class FloatLongDouble extends LongDouble { - @java.lang.Override - public Float getMaxValue() { - return Float.MAX_VALUE; + public class FloatLongDouble extends LongDouble { + @java.lang.Override + public Float getMaxValue() { + return Float.MAX_VALUE; + } } - } } diff --git a/framework/tests/all-systems/LubRawTypes.java b/framework/tests/all-systems/LubRawTypes.java index 913d8f013b9..5264b6e1ab3 100644 --- a/framework/tests/all-systems/LubRawTypes.java +++ b/framework/tests/all-systems/LubRawTypes.java @@ -1,18 +1,18 @@ @SuppressWarnings("unchecked") public class LubRawTypes { - public static boolean flag = false; + public static boolean flag = false; - class MyGen {} + class MyGen {} - MyGen>> test(MyGen myGen1, MyGen myGen2) { - return flag ? myGen1 : myGen2; - } + MyGen>> test(MyGen myGen1, MyGen myGen2) { + return flag ? myGen1 : myGen2; + } - MyGen>> test2(MyGen myGen1, MyGen>> myGen2) { - return flag ? myGen1 : myGen2; - } + MyGen>> test2(MyGen myGen1, MyGen>> myGen2) { + return flag ? myGen1 : myGen2; + } - MyGen>> test3(MyGen myGen1, MyGen>> myGen2) { - return flag ? myGen2 : myGen1; - } + MyGen>> test3(MyGen myGen1, MyGen>> myGen2) { + return flag ? myGen2 : myGen1; + } } diff --git a/framework/tests/all-systems/MapCrashTest.java b/framework/tests/all-systems/MapCrashTest.java index cb08d513367..05f8277c333 100644 --- a/framework/tests/all-systems/MapCrashTest.java +++ b/framework/tests/all-systems/MapCrashTest.java @@ -2,13 +2,13 @@ @SuppressWarnings("all") // Just check for crashes public class MapCrashTest { - public static class ImmutableSetMultimap { - public ImmutableSetMultimap(MapCrashTest> map) {} - } + public static class ImmutableSetMultimap { + public ImmutableSetMultimap(MapCrashTest> map) {} + } - private final class MapSet extends MapCrashTest> {} + private final class MapSet extends MapCrashTest> {} - public void asMultimap() { - new ImmutableSetMultimap(new MapSet()); - } + public void asMultimap() { + new ImmutableSetMultimap(new MapSet()); + } } diff --git a/framework/tests/all-systems/MethodTypeVars.java b/framework/tests/all-systems/MethodTypeVars.java index 4749f8feb4a..ad9b593c44f 100644 --- a/framework/tests/all-systems/MethodTypeVars.java +++ b/framework/tests/all-systems/MethodTypeVars.java @@ -3,19 +3,19 @@ import java.util.Map; public class MethodTypeVars { - private void addToBindingList(Map> map, T key, String value) {} + private void addToBindingList(Map> map, T key, String value) {} - void call1() { - LinkedHashMap> multiMap = new LinkedHashMap<>(); - String s = "s"; - Object o = new Object(); - addToBindingList(multiMap, o, s); - } + void call1() { + LinkedHashMap> multiMap = new LinkedHashMap<>(); + String s = "s"; + Object o = new Object(); + addToBindingList(multiMap, o, s); + } - void call2() { - LinkedHashMap> multiMap = new LinkedHashMap<>(); - String s = "s"; - Integer n = 5; - addToBindingList(multiMap, n, s); - } + void call2() { + LinkedHashMap> multiMap = new LinkedHashMap<>(); + String s = "s"; + Integer n = 5; + addToBindingList(multiMap, n, s); + } } diff --git a/framework/tests/all-systems/MissingBoundAnnotations.java b/framework/tests/all-systems/MissingBoundAnnotations.java index b0c85b75c6a..d0f90f649ea 100644 --- a/framework/tests/all-systems/MissingBoundAnnotations.java +++ b/framework/tests/all-systems/MissingBoundAnnotations.java @@ -4,10 +4,10 @@ import java.util.Map; public final class MissingBoundAnnotations { - @SuppressWarnings("nullness:type.argument.type.incompatible") - public static , V> Collection sortedKeySet(Map m) { - ArrayList theKeys = new ArrayList<>(m.keySet()); - Collections.sort(theKeys); - return theKeys; - } + @SuppressWarnings("nullness:type.argument.type.incompatible") + public static , V> Collection sortedKeySet(Map m) { + ArrayList theKeys = new ArrayList<>(m.keySet()); + Collections.sort(theKeys); + return theKeys; + } } diff --git a/framework/tests/all-systems/MultipleUnions.java b/framework/tests/all-systems/MultipleUnions.java index aafe839d92f..3de444b1f17 100644 --- a/framework/tests/all-systems/MultipleUnions.java +++ b/framework/tests/all-systems/MultipleUnions.java @@ -1,39 +1,39 @@ public class MultipleUnions { - public static boolean flag = false; - - @SuppressWarnings("ainfertest") // only check WPI for crashes - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - try { - bar(); - } catch (SubMyExceptionA | MyExceptionB ex2) { - - Throwable t = flag ? ex1 : ex2; - typeVar(ex1, ex1); - typeVar(ex2, ex2); - // See UnionCrash for version that crashes - // typeVar(ex1, ex2); - } + public static boolean flag = false; + + @SuppressWarnings("ainfertest") // only check WPI for crashes + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + try { + bar(); + } catch (SubMyExceptionA | MyExceptionB ex2) { + + Throwable t = flag ? ex1 : ex2; + typeVar(ex1, ex1); + typeVar(ex2, ex2); + // See UnionCrash for version that crashes + // typeVar(ex1, ex2); + } + } } - } - > void typeVarIntersection(T param) {} + > void typeVarIntersection(T param) {} - void typeVar(T param, T param2) {} + void typeVar(T param, T param2) {} - void typeVarWildcard(T param, MyInterface myInterface) {} + void typeVarWildcard(T param, MyInterface myInterface) {} - void typeVarWildcard2(T param, MyInterface myInterface) {} + void typeVarWildcard2(T param, MyInterface myInterface) {} - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - interface MyInterface {} + interface MyInterface {} - class MyExceptionA extends Throwable implements Cloneable, MyInterface {} + class MyExceptionA extends Throwable implements Cloneable, MyInterface {} - class MyExceptionB extends Throwable implements Cloneable, MyInterface {} + class MyExceptionB extends Throwable implements Cloneable, MyInterface {} - class SubMyExceptionA extends MyExceptionA {} + class SubMyExceptionA extends MyExceptionA {} } diff --git a/framework/tests/all-systems/MyMapCrash.java b/framework/tests/all-systems/MyMapCrash.java index 7eefe0e8b2e..30788c3444d 100644 --- a/framework/tests/all-systems/MyMapCrash.java +++ b/framework/tests/all-systems/MyMapCrash.java @@ -2,13 +2,13 @@ @SuppressWarnings("all") // Just check for crashes public class MyMapCrash { - public static class ImmutableSetMultimap { - public ImmutableSetMultimap(MyMapCrash> map) {} - } + public static class ImmutableSetMultimap { + public ImmutableSetMultimap(MyMapCrash> map) {} + } - private final class MapSet extends MyMapCrash> {} + private final class MapSet extends MyMapCrash> {} - public void asMultimap() { - new ImmutableSetMultimap(new MapSet()); - } + public void asMultimap() { + new ImmutableSetMultimap(new MapSet()); + } } diff --git a/framework/tests/all-systems/Options.java b/framework/tests/all-systems/Options.java index c203e94d31f..a9af59dc96a 100644 --- a/framework/tests/all-systems/Options.java +++ b/framework/tests/all-systems/Options.java @@ -1,8 +1,8 @@ public class Options { - private Class main_class; + private Class main_class; - public Options() { - throw new Error("" + main_class); - } + public Options() { + throw new Error("" + main_class); + } } diff --git a/framework/tests/all-systems/PCGen.java b/framework/tests/all-systems/PCGen.java index 5b97f00e6ea..a92056ea3b9 100644 --- a/framework/tests/all-systems/PCGen.java +++ b/framework/tests/all-systems/PCGen.java @@ -5,12 +5,14 @@ import java.util.stream.Collectors; public class PCGen { - private Map activeBonusMap = new HashMap<>(); + private Map activeBonusMap = new HashMap<>(); - void method(String prefix) { - final Set keys = - activeBonusMap.keySet().stream() - .filter(fullyQualifiedBonusType -> fullyQualifiedBonusType.startsWith(prefix)) - .collect(Collectors.toCollection(() -> new TreeSet<>())); - } + void method(String prefix) { + final Set keys = + activeBonusMap.keySet().stream() + .filter( + fullyQualifiedBonusType -> + fullyQualifiedBonusType.startsWith(prefix)) + .collect(Collectors.toCollection(() -> new TreeSet<>())); + } } diff --git a/framework/tests/all-systems/PQueue.java b/framework/tests/all-systems/PQueue.java index 81951b0c275..371f642bd7b 100644 --- a/framework/tests/all-systems/PQueue.java +++ b/framework/tests/all-systems/PQueue.java @@ -1,11 +1,11 @@ public class PQueue

                  { - public static PQueue create(Iterable p, Builder b) { - return b.create(p); - } + public static PQueue create(Iterable p, Builder b) { + return b.create(p); + } - public static final class Builder { - public PQueue create(Iterable p) { - throw new RuntimeException(); + public static final class Builder { + public PQueue create(Iterable p) { + throw new RuntimeException(); + } } - } } diff --git a/framework/tests/all-systems/PlaceholderCrash1.java b/framework/tests/all-systems/PlaceholderCrash1.java index d181d88cf80..a873f1a8481 100644 --- a/framework/tests/all-systems/PlaceholderCrash1.java +++ b/framework/tests/all-systems/PlaceholderCrash1.java @@ -8,57 +8,58 @@ @SuppressWarnings("all") // Just check for crashes. public class PlaceholderCrash1 { - void method(PlaceholderAPIPlugin plugin) { - final List expansions = new ArrayList<>(); - onMainThread( - plugin, - downloadAndDiscover(expansions, plugin), - (classes, exception) -> { - if (exception != null) { - return; - } - final String message = - classes.stream() - .filter(Objects::nonNull) - .map(plugin.getLocalExpansionManager()::register) - .filter(Optional::isPresent) - .map(Optional::get) - .map(expansion -> " &a" + expansion.getName()) - .collect(Collectors.joining("\n")); - return; - }); - } + void method(PlaceholderAPIPlugin plugin) { + final List expansions = new ArrayList<>(); + onMainThread( + plugin, + downloadAndDiscover(expansions, plugin), + (classes, exception) -> { + if (exception != null) { + return; + } + final String message = + classes.stream() + .filter(Objects::nonNull) + .map(plugin.getLocalExpansionManager()::register) + .filter(Optional::isPresent) + .map(Optional::get) + .map(expansion -> " &a" + expansion.getName()) + .collect(Collectors.joining("\n")); + return; + }); + } - public static void onMainThread( - final Plugin plugin, - final CompletableFuture future, - final BiConsumer consumer) {} + public static void onMainThread( + final Plugin plugin, + final CompletableFuture future, + final BiConsumer consumer) {} - private static CompletableFuture>> downloadAndDiscover( - final List expansions, final PlaceholderAPIPlugin plugin) { - throw new RuntimeException(); - } + private static CompletableFuture>> + downloadAndDiscover( + final List expansions, final PlaceholderAPIPlugin plugin) { + throw new RuntimeException(); + } - static class Plugin {} + static class Plugin {} - static class PlaceholderAPIPlugin extends Plugin { - LocalExpansionManager getLocalExpansionManager() { - throw new RuntimeException(); + static class PlaceholderAPIPlugin extends Plugin { + LocalExpansionManager getLocalExpansionManager() { + throw new RuntimeException(); + } } - } - static class LocalExpansionManager { - Optional register(Class p) { - throw new RuntimeException(); + static class LocalExpansionManager { + Optional register(Class p) { + throw new RuntimeException(); + } } - } - static class PlaceholderExpansion { + static class PlaceholderExpansion { - public String getName() { - return ""; + public String getName() { + return ""; + } } - } - static class CloudExpansion {} + static class CloudExpansion {} } diff --git a/framework/tests/all-systems/PolyCollectorTypeVars.java b/framework/tests/all-systems/PolyCollectorTypeVars.java index 4804af54399..f544232a267 100644 --- a/framework/tests/all-systems/PolyCollectorTypeVars.java +++ b/framework/tests/all-systems/PolyCollectorTypeVars.java @@ -3,27 +3,27 @@ class MyGen {} abstract class Ordering implements Comparator { - // Natural order + // Natural order - public static Ordering natural() { - return new Ordering() { - @Override - public int compare(C o1, C o2) { - return 0; - } - }; - } + public static Ordering natural() { + return new Ordering() { + @Override + public int compare(C o1, C o2) { + return 0; + } + }; + } } public class PolyCollectorTypeVars { - // Both of these come from the extends Comparable on line 9 - @SuppressWarnings({"rawtypes", "type.argument.type.incompatible"}) - public static MyGen treeKeys2() { - // See Limitation in DefaultTypeArgumentInference on interdependent methods - return treeKeys(Ordering.natural()); - } + // Both of these come from the extends Comparable on line 9 + @SuppressWarnings({"rawtypes", "type.argument.type.incompatible"}) + public static MyGen treeKeys2() { + // See Limitation in DefaultTypeArgumentInference on interdependent methods + return treeKeys(Ordering.natural()); + } - public static MyGen treeKeys(final Comparator comparator) { - return new MyGen(); - } + public static MyGen treeKeys(final Comparator comparator) { + return new MyGen(); + } } diff --git a/framework/tests/all-systems/PrintArray.java b/framework/tests/all-systems/PrintArray.java index 74d34ee2cdc..a31cdf2ea8b 100644 --- a/framework/tests/all-systems/PrintArray.java +++ b/framework/tests/all-systems/PrintArray.java @@ -1,14 +1,14 @@ public class PrintArray { - // the I18n checker correctly issues an error and Nullness org.checkerframework.checker - // correctly issue a warning below, but we would like to keep this test in all-systems. - @SuppressWarnings({"i18n", "nullness:nulltest.redundant"}) - public static final void print(java.io.PrintStream ps, Object[][] a) { - if (a == null) { - ps.println("null"); - return; + // the I18n checker correctly issues an error and Nullness org.checkerframework.checker + // correctly issue a warning below, but we would like to keep this test in all-systems. + @SuppressWarnings({"i18n", "nullness:nulltest.redundant"}) + public static final void print(java.io.PrintStream ps, Object[][] a) { + if (a == null) { + ps.println("null"); + return; + } + // When analyzing this call, we see an exception about taking the LUB + // of ATMs with different numbers of qualifiers. + ps.print('7'); } - // When analyzing this call, we see an exception about taking the LUB - // of ATMs with different numbers of qualifiers. - ps.print('7'); - } } diff --git a/framework/tests/all-systems/Quarkus1.java b/framework/tests/all-systems/Quarkus1.java index e5def3e8fcc..340129e6fb1 100644 --- a/framework/tests/all-systems/Quarkus1.java +++ b/framework/tests/all-systems/Quarkus1.java @@ -1,38 +1,38 @@ @SuppressWarnings("all") // Just check for crashes. public class Quarkus1 { - void method(ModifiableModelEx libraryModel) { - ApplicationManager.getApplication() - .invokeAndWait( - () -> { - WriteAction.run(libraryModel::commit); - }); - } + void method(ModifiableModelEx libraryModel) { + ApplicationManager.getApplication() + .invokeAndWait( + () -> { + WriteAction.run(libraryModel::commit); + }); + } - public static class ApplicationManager { - protected static Application ourApplication; + public static class ApplicationManager { + protected static Application ourApplication; - public static Application getApplication() { - return ourApplication; + public static Application getApplication() { + return ourApplication; + } } - } - public interface Application { // extends ComponentManager {} - void invokeAndWait(Runnable runnable); - } + public interface Application { // extends ComponentManager {} + void invokeAndWait(Runnable runnable); + } - public abstract static class WriteAction extends BaseActionRunnable { - public static void run(ThrowableRunnable action) throws E { - throw new RuntimeException(); + public abstract static class WriteAction extends BaseActionRunnable { + public static void run(ThrowableRunnable action) throws E { + throw new RuntimeException(); + } } - } - public abstract static class BaseActionRunnable {} + public abstract static class BaseActionRunnable {} - public interface ThrowableRunnable { - void run() throws T; - } + public interface ThrowableRunnable { + void run() throws T; + } - interface ModifiableModelEx { - void commit(); - } + interface ModifiableModelEx { + void commit(); + } } diff --git a/framework/tests/all-systems/Quarkus2.java b/framework/tests/all-systems/Quarkus2.java index c414fd8ecf5..792aa3c5a28 100644 --- a/framework/tests/all-systems/Quarkus2.java +++ b/framework/tests/all-systems/Quarkus2.java @@ -3,26 +3,26 @@ @SuppressWarnings("all") // Just check for crashes. public class Quarkus2 { - private void method( - CompletableFuture server, - CompletableFuture>> list, - CompletableFuture>> x) { - CompletableFuture>> q = - server.thenCompose( - ls -> { - x.thenApply( - y -> { - return y; - }); - return list; - }); - } + private void method( + CompletableFuture server, + CompletableFuture>> list, + CompletableFuture>> x) { + CompletableFuture>> q = + server.thenCompose( + ls -> { + x.thenApply( + y -> { + return y; + }); + return list; + }); + } - public interface InferfaceA {} + public interface InferfaceA {} - static class ClassB {} + static class ClassB {} - static class ClassC {} + static class ClassC {} - public static class ClassD {} + public static class ClassD {} } diff --git a/framework/tests/all-systems/Quarkus3.java b/framework/tests/all-systems/Quarkus3.java index b1172d8feed..bf6e8878753 100644 --- a/framework/tests/all-systems/Quarkus3.java +++ b/framework/tests/all-systems/Quarkus3.java @@ -2,16 +2,16 @@ import java.util.stream.Collectors; public class Quarkus3 { - void method(List commands) { - String text = - commands.stream() - .map( - param -> { - if (param.indexOf(' ') != -1) { - return "\"" + param + "\""; - } - return param; - }) - .collect(Collectors.joining(" ")); - } + void method(List commands) { + String text = + commands.stream() + .map( + param -> { + if (param.indexOf(' ') != -1) { + return "\"" + param + "\""; + } + return param; + }) + .collect(Collectors.joining(" ")); + } } diff --git a/framework/tests/all-systems/RawSuper.java b/framework/tests/all-systems/RawSuper.java index c5c544d77d8..41df28daee9 100644 --- a/framework/tests/all-systems/RawSuper.java +++ b/framework/tests/all-systems/RawSuper.java @@ -2,16 +2,16 @@ import java.util.List; public class RawSuper { - static class MySuper { - public MySuper(List p) {} + static class MySuper { + public MySuper(List p) {} - void method(List o) {} - } + void method(List o) {} + } - @SuppressWarnings("unchecked") // raw extends - static class SubRaw extends MySuper { - public SubRaw() { - super(new ArrayList>()); + @SuppressWarnings("unchecked") // raw extends + static class SubRaw extends MySuper { + public SubRaw() { + super(new ArrayList>()); + } } - } } diff --git a/framework/tests/all-systems/RawTypeAssignment.java b/framework/tests/all-systems/RawTypeAssignment.java index ba99804e584..176bc3bd782 100644 --- a/framework/tests/all-systems/RawTypeAssignment.java +++ b/framework/tests/all-systems/RawTypeAssignment.java @@ -9,15 +9,15 @@ class Components extends ArrayList {} // class Components extends ArrayList {} public class RawTypeAssignment { - static Components getComponents() { - return new Components(); - } + static Components getComponents() { + return new Components(); + } - static void addTimes(Calendar calendar) { - // Type systems may issue an error below because of a mismatch between the type arguments. - @SuppressWarnings("assignment.type.incompatible") - // :: warning: [unchecked] unchecked conversion - ArrayList clist = getComponents(); - clist.get(0); - } + static void addTimes(Calendar calendar) { + // Type systems may issue an error below because of a mismatch between the type arguments. + @SuppressWarnings("assignment.type.incompatible") + // :: warning: [unchecked] unchecked conversion + ArrayList clist = getComponents(); + clist.get(0); + } } diff --git a/framework/tests/all-systems/RawTypes.java b/framework/tests/all-systems/RawTypes.java index 0a3c8d2de03..c798988ec3f 100644 --- a/framework/tests/all-systems/RawTypes.java +++ b/framework/tests/all-systems/RawTypes.java @@ -1,15 +1,15 @@ import java.util.List; public class RawTypes { - public void m(ClassLoader cl) throws ClassNotFoundException { - Class clazz = cl.loadClass("java.lang.Object"); - } + public void m(ClassLoader cl) throws ClassNotFoundException { + Class clazz = cl.loadClass("java.lang.Object"); + } } interface I { - public void m(List l); + public void m(List l); } class OtherClass implements I { - public void m(List l) {} + public void m(List l) {} } diff --git a/framework/tests/all-systems/ResourceVariables.java b/framework/tests/all-systems/ResourceVariables.java index 53d33294c7d..add322044fa 100644 --- a/framework/tests/all-systems/ResourceVariables.java +++ b/framework/tests/all-systems/ResourceVariables.java @@ -3,9 +3,9 @@ // Tests related to resource variables in try-with-resources statements. public class ResourceVariables { - void foo(InputStream arg) { - try (InputStream in = arg) { - } catch (IOException e) { + void foo(InputStream arg) { + try (InputStream in = arg) { + } catch (IOException e) { + } } - } } diff --git a/framework/tests/all-systems/SameBoundsCrazy.java b/framework/tests/all-systems/SameBoundsCrazy.java index 975a527cb30..da9b652ec80 100644 --- a/framework/tests/all-systems/SameBoundsCrazy.java +++ b/framework/tests/all-systems/SameBoundsCrazy.java @@ -2,17 +2,17 @@ @SuppressWarnings("all") // the assignment is not legal in most checkers. public class SameBoundsCrazy { - static class Simple {} + static class Simple {} - static class Gen> {} + static class Gen> {} - void use(Gen, ? super Simple>> g) { - Gen, Simple>> s = g; - } + void use(Gen, ? super Simple>> g) { + Gen, Simple>> s = g; + } - static class Gen2 {} + static class Gen2 {} - void use2(Gen2 g) { - Gen2 f = g; - } + void use2(Gen2 g) { + Gen2 f = g; + } } diff --git a/framework/tests/all-systems/SelfRef.java b/framework/tests/all-systems/SelfRef.java index cbb5d139191..fac424abce6 100644 --- a/framework/tests/all-systems/SelfRef.java +++ b/framework/tests/all-systems/SelfRef.java @@ -1,8 +1,8 @@ @SuppressWarnings("allcheckers") // Only check for crashes. class SelfRef, V> { - public SelfRef parent, left, right; + public SelfRef parent, left, right; - public SelfRef(SelfRef parent) { - this.parent = parent; - } + public SelfRef(SelfRef parent) { + this.parent = parent; + } } diff --git a/framework/tests/all-systems/SetOfSet.java b/framework/tests/all-systems/SetOfSet.java index a6778907148..ffbcb83d4cf 100644 --- a/framework/tests/all-systems/SetOfSet.java +++ b/framework/tests/all-systems/SetOfSet.java @@ -1,14 +1,14 @@ package wildcards; @SuppressWarnings({ - "initialization", - "initializedfields:contracts.postcondition" + "initialization", + "initializedfields:contracts.postcondition" }) // field isn't set public class SetOfSet { - public void addAll(SetOfSet s) { - E[] svalues = s.values; - } + public void addAll(SetOfSet s) { + E[] svalues = s.values; + } - protected E[] values; + protected E[] values; } diff --git a/framework/tests/all-systems/SimpleLog.java b/framework/tests/all-systems/SimpleLog.java index c6fee147d51..155fad32b3a 100644 --- a/framework/tests/all-systems/SimpleLog.java +++ b/framework/tests/all-systems/SimpleLog.java @@ -1,10 +1,10 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class SimpleLog { - public SimpleLog() { - try { - int i = 0; - } catch (Exception e) { - throw new RuntimeException("", e); + public SimpleLog() { + try { + int i = 0; + } catch (Exception e) { + throw new RuntimeException("", e); + } } - } } diff --git a/framework/tests/all-systems/StateMatch.java b/framework/tests/all-systems/StateMatch.java index 94bdbdc0e15..0dbb14c1447 100644 --- a/framework/tests/all-systems/StateMatch.java +++ b/framework/tests/all-systems/StateMatch.java @@ -1,26 +1,26 @@ public class StateMatch { - private int num_elts = 0; + private int num_elts = 0; - @SuppressWarnings("nullness") - private double[][] elts = null; + @SuppressWarnings("nullness") + private double[][] elts = null; - @SuppressWarnings({ - "interning", - "index" - }) // This code is inherently unsafe for the index checker, but adding index annotations - // produces warnings for other checkers (fenum). - public boolean state_match(Object state) { - if (!(state instanceof double[][])) { - System.out.println(""); - } + @SuppressWarnings({ + "interning", + "index" + }) // This code is inherently unsafe for the index checker, but adding index annotations + // produces warnings for other checkers (fenum). + public boolean state_match(Object state) { + if (!(state instanceof double[][])) { + System.out.println(""); + } - double[][] e = (double[][]) state; - boolean match = false; - if (elts[0] == e[0]) { - // When analyzing this statement, we get an exception about taking - // the LUB of ATMs with empty sets of qualifiers. - match = true; + double[][] e = (double[][]) state; + boolean match = false; + if (elts[0] == e[0]) { + // When analyzing this statement, we get an exception about taking + // the LUB of ATMs with empty sets of qualifiers. + match = true; + } + return true; } - return true; - } } diff --git a/framework/tests/all-systems/SuperThis.java b/framework/tests/all-systems/SuperThis.java index 28e4176d8fc..247bb9ee954 100644 --- a/framework/tests/all-systems/SuperThis.java +++ b/framework/tests/all-systems/SuperThis.java @@ -1,15 +1,15 @@ @SuppressWarnings("all") public class SuperThis { - class Super {} + class Super {} - // Test super() and this() - class Inner extends Super { - public Inner() { - super(); - } + // Test super() and this() + class Inner extends Super { + public Inner() { + super(); + } - public Inner(int i) { - this(); + public Inner(int i) { + this(); + } } - } } diff --git a/framework/tests/all-systems/Ternary.java b/framework/tests/all-systems/Ternary.java index f9928555984..f5a4573a7cd 100644 --- a/framework/tests/all-systems/Ternary.java +++ b/framework/tests/all-systems/Ternary.java @@ -1,74 +1,74 @@ import java.lang.ref.WeakReference; public class Ternary { - void m1(boolean b) { - String s = b ? new String("foo") : null; - } + void m1(boolean b) { + String s = b ? new String("foo") : null; + } - void m2(boolean b) { - String s = b ? null : new String("foo"); - } + void m2(boolean b) { + String s = b ? null : new String("foo"); + } - @SuppressWarnings("nullness") // Don't want to depend on @Nullable - String m3(boolean b) { - return b ? new String("foo") : null; - } + @SuppressWarnings("nullness") // Don't want to depend on @Nullable + String m3(boolean b) { + return b ? new String("foo") : null; + } - void m4(boolean b) { - String[] s = b ? new String[] {""} : null; - } + void m4(boolean b) { + String[] s = b ? new String[] {""} : null; + } - void m5(boolean b) { - Object o = new Object(); - String s = b ? (String) o : null; - } + void m5(boolean b) { + Object o = new Object(); + String s = b ? (String) o : null; + } - void m6(boolean b) { - String p = "x*((("; - String s = b ? p : null; - } + void m6(boolean b) { + String p = "x*((("; + String s = b ? p : null; + } - class Generic { - void cond(boolean b, T p1, T p2) { - p1 = b ? p1 : p2; + class Generic { + void cond(boolean b, T p1, T p2) { + p1 = b ? p1 : p2; + } } - } - void array(boolean b) { - String[] s = b ? new String[] {""} : null; - } + void array(boolean b) { + String[] s = b ? new String[] {""} : null; + } - void generic(boolean b, Generic p) { - Generic s = b ? p : null; - } + void generic(boolean b, Generic p) { + Generic s = b ? p : null; + } - void primarray(boolean b) { - long[] result = b ? null : new long[10]; - } + void primarray(boolean b) { + long[] result = b ? null : new long[10]; + } - void vars() { - // String and Integer generate an intersection type. - String c = null; - Integer m = null; - Object s = (m != null) ? m : c; - } + void vars() { + // String and Integer generate an intersection type. + String c = null; + Integer m = null; + Object s = (m != null) ? m : c; + } - void vars2() { - // String and Integer generate an intersection type. - String c = null; - Integer m = null; - Object s = (m != null) ? m : c; - } + void vars2() { + // String and Integer generate an intersection type. + String c = null; + Integer m = null; + Object s = (m != null) ? m : c; + } - public void test(MyWeakRef existingRef) { - @SuppressWarnings("nulltest.redundant") - F existing = existingRef == null ? null : existingRef.get(); - } + public void test(MyWeakRef existingRef) { + @SuppressWarnings("nulltest.redundant") + F existing = existingRef == null ? null : existingRef.get(); + } - private static final class MyWeakRef extends WeakReference { + private static final class MyWeakRef extends WeakReference { - public MyWeakRef(L referent) { - super(referent); + public MyWeakRef(L referent) { + super(referent); + } } - } } diff --git a/framework/tests/all-systems/Throw.java b/framework/tests/all-systems/Throw.java index 0edcff67cb7..3e76a261e6b 100644 --- a/framework/tests/all-systems/Throw.java +++ b/framework/tests/all-systems/Throw.java @@ -1,18 +1,18 @@ import java.util.List; public class Throw { - void throwTypeVar(E ex) { - try { - throw ex; - } catch (Exception e) { + void throwTypeVar(E ex) { + try { + throw ex; + } catch (Exception e) { + } } - } - void throwWildcard(List list) { - try { - throw list.get(0); - } catch (Exception e) { + void throwWildcard(List list) { + try { + throw list.get(0); + } catch (Exception e) { + } } - } } diff --git a/framework/tests/all-systems/TypeVarAndArrayRefinement.java b/framework/tests/all-systems/TypeVarAndArrayRefinement.java index d162e44ff48..8a7e68e8515 100644 --- a/framework/tests/all-systems/TypeVarAndArrayRefinement.java +++ b/framework/tests/all-systems/TypeVarAndArrayRefinement.java @@ -1,20 +1,20 @@ public class TypeVarAndArrayRefinement { - private > T getEnumValue(Class enumType, String name) { - T[] constants = enumType.getEnumConstants(); - if (constants == null) { - throw new IllegalArgumentException(enumType.getName() + " is not an enum type"); + private > T getEnumValue(Class enumType, String name) { + T[] constants = enumType.getEnumConstants(); + if (constants == null) { + throw new IllegalArgumentException(enumType.getName() + " is not an enum type"); + } + // previously the constants method was considered nullable mainly because it was an invalid + // type because when lubbing type variables we didn't copy the declared types on the bounds + // over to the lub + for (T constant : constants) { + if (constant.name().equalsIgnoreCase(name.replace('-', '_'))) { + return constant; + } + } + // same error that's thrown by Enum.valueOf() + throw new IllegalArgumentException( + "No enum constant " + enumType.getCanonicalName() + "." + name); } - // previously the constants method was considered nullable mainly because it was an invalid - // type because when lubbing type variables we didn't copy the declared types on the bounds - // over to the lub - for (T constant : constants) { - if (constant.name().equalsIgnoreCase(name.replace('-', '_'))) { - return constant; - } - } - // same error that's thrown by Enum.valueOf() - throw new IllegalArgumentException( - "No enum constant " + enumType.getCanonicalName() + "." + name); - } } diff --git a/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java b/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java index 2fe1aeea64c..cba41782a9d 100644 --- a/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java +++ b/framework/tests/all-systems/TypeVarAndArrayRefinementSmall.java @@ -1,8 +1,8 @@ public class TypeVarAndArrayRefinementSmall { - private > T getEnumValue(T[] constants) { - for (T constant : constants) { - return constant; + private > T getEnumValue(T[] constants) { + for (T constant : constants) { + return constant; + } + throw new Error(); } - throw new Error(); - } } diff --git a/framework/tests/all-systems/TypeVarInstanceOf.java b/framework/tests/all-systems/TypeVarInstanceOf.java index 990e8074d91..4442b71fb74 100644 --- a/framework/tests/all-systems/TypeVarInstanceOf.java +++ b/framework/tests/all-systems/TypeVarInstanceOf.java @@ -1,5 +1,5 @@ public class TypeVarInstanceOf { - public static void clone(final T obj) { - if (obj instanceof Cloneable) {} - } + public static void clone(final T obj) { + if (obj instanceof Cloneable) {} + } } diff --git a/framework/tests/all-systems/TypeVarPrimitives.java b/framework/tests/all-systems/TypeVarPrimitives.java index 1ad25d008ba..02a82e6960c 100644 --- a/framework/tests/all-systems/TypeVarPrimitives.java +++ b/framework/tests/all-systems/TypeVarPrimitives.java @@ -2,11 +2,11 @@ // checker/tests/nullness/TypeVarPrimitivesNullness.java and // checker/tests/interning/TypeVarPrimitivesInterning.java public class TypeVarPrimitives { - void method(T tLong) { - long l = tLong; - } + void method(T tLong) { + long l = tLong; + } - void methodIntersection(T tLong) { - long l = tLong; - } + void methodIntersection(T tLong) { + long l = tLong; + } } diff --git a/framework/tests/all-systems/TypeVarVarargs.java b/framework/tests/all-systems/TypeVarVarargs.java index 9a928cccfd6..ffc6eceed53 100644 --- a/framework/tests/all-systems/TypeVarVarargs.java +++ b/framework/tests/all-systems/TypeVarVarargs.java @@ -1,9 +1,9 @@ abstract class TypeVarVarargs { - public abstract T foo(); + public abstract T foo(); - public abstract void bar(Integer... s); + public abstract void bar(Integer... s); - public void m() { - bar(foo(), foo()); - } + public void m() { + bar(foo(), foo()); + } } diff --git a/framework/tests/all-systems/TypeVars.java b/framework/tests/all-systems/TypeVars.java index ef748e5fc16..cc2442f17ab 100644 --- a/framework/tests/all-systems/TypeVars.java +++ b/framework/tests/all-systems/TypeVars.java @@ -1,22 +1,22 @@ public class TypeVars { - class Test1 { - void m() { - @SuppressWarnings("unchecked") - T x = (T) new Object(); + class Test1 { + void m() { + @SuppressWarnings("unchecked") + T x = (T) new Object(); - Object o = x; - } + Object o = x; + } - class Inner1 {} + class Inner1 {} - public Inner1 method1() { - return new Inner1<>(); + public Inner1 method1() { + return new Inner1<>(); + } } - } - // It's difficult to add more test cases that - // should work for all type systems. - // Ensure that for the different type systems, annotations - // on the type variable are propagated correctly. + // It's difficult to add more test cases that + // should work for all type systems. + // Ensure that for the different type systems, annotations + // on the type variable are propagated correctly. } diff --git a/framework/tests/all-systems/UncheckedCrash.java b/framework/tests/all-systems/UncheckedCrash.java index 433171d6540..1a10ef59b19 100644 --- a/framework/tests/all-systems/UncheckedCrash.java +++ b/framework/tests/all-systems/UncheckedCrash.java @@ -1,15 +1,16 @@ import java.util.List; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; public class UncheckedCrash { - void method(AnnotationMirror anno, ExecutableElement ele) { - @SuppressWarnings("unchecked") - List values = getElementValue(anno, ele, List.class); - } + void method(AnnotationMirror anno, ExecutableElement ele) { + @SuppressWarnings("unchecked") + List values = getElementValue(anno, ele, List.class); + } - public static T getElementValue( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - throw new RuntimeException(); - } + public static T getElementValue( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/UnionCrash.java b/framework/tests/all-systems/UnionCrash.java index 3ea9d253a66..3bb5c12e08d 100644 --- a/framework/tests/all-systems/UnionCrash.java +++ b/framework/tests/all-systems/UnionCrash.java @@ -3,28 +3,28 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class UnionCrash { - void foo(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex1) { - try { - bar(); - } catch (SubMyExceptionA | MyExceptionB ex2) { - // This call cause a crash - typeVar(ex1, ex2); - } + void foo(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex1) { + try { + bar(); + } catch (SubMyExceptionA | MyExceptionB ex2) { + // This call cause a crash + typeVar(ex1, ex2); + } + } } - } - void typeVar(T param, T param2) {} + void typeVar(T param, T param2) {} - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - interface MyInterface {} + interface MyInterface {} - class MyExceptionA extends Throwable implements Cloneable, MyInterface {} + class MyExceptionA extends Throwable implements Cloneable, MyInterface {} - class MyExceptionB extends Throwable implements Cloneable, MyInterface {} + class MyExceptionB extends Throwable implements Cloneable, MyInterface {} - class SubMyExceptionA extends MyExceptionA {} + class SubMyExceptionA extends MyExceptionA {} } diff --git a/framework/tests/all-systems/UnionTypes.java b/framework/tests/all-systems/UnionTypes.java index 4fc1717e433..ce3f3329aad 100644 --- a/framework/tests/all-systems/UnionTypes.java +++ b/framework/tests/all-systems/UnionTypes.java @@ -1,12 +1,12 @@ // Test case for Issue 145 // https://github.com/typetools/checker-framework/issues/145 public class UnionTypes { - public void TryCatch() { - try { - int[] arr = new int[10]; - arr[4] = 1; - } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException exc) { - Exception e = exc; + public void TryCatch() { + try { + int[] arr = new int[10]; + arr[4] = 1; + } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException exc) { + Exception e = exc; + } } - } } diff --git a/framework/tests/all-systems/Unions.java b/framework/tests/all-systems/Unions.java index 3837a84e49c..e9d133d99f3 100644 --- a/framework/tests/all-systems/Unions.java +++ b/framework/tests/all-systems/Unions.java @@ -1,50 +1,50 @@ @SuppressWarnings("ainfertest") // only check WPI for crashes public class Unions { - void foo1(MyInterface param) throws Throwable { - try { - bar(); - } catch (MyExceptionA | MyExceptionB ex) { - typeVar(ex); - typeVarIntersection(ex); - - typeVarWildcard(ex, param); - typeVarWildcard2(ex, param); + void foo1(MyInterface param) throws Throwable { + try { + bar(); + } catch (MyExceptionA | MyExceptionB ex) { + typeVar(ex); + typeVarIntersection(ex); + + typeVarWildcard(ex, param); + typeVarWildcard2(ex, param); + } } - } - void foo2(MyInterface param) throws Throwable { - try { - bar(); - } catch (SubMyExceptionA | SubMyExceptionA2 ex) { - typeVar(ex); - typeVar2(ex, ex); + void foo2(MyInterface param) throws Throwable { + try { + bar(); + } catch (SubMyExceptionA | SubMyExceptionA2 ex) { + typeVar(ex); + typeVar2(ex, ex); - typeVarIntersection(ex); + typeVarIntersection(ex); - typeVarWildcard(ex, param); - typeVarWildcard2(ex, param); + typeVarWildcard(ex, param); + typeVarWildcard2(ex, param); + } } - } - > void typeVarIntersection(T param) {} + > void typeVarIntersection(T param) {} - void typeVar(T param) {} + void typeVar(T param) {} - void typeVar2(T param, T param2) {} + void typeVar2(T param, T param2) {} - void typeVarWildcard(T param, MyInterface myInterface) {} + void typeVarWildcard(T param, MyInterface myInterface) {} - void typeVarWildcard2(T param, MyInterface myInterface) {} + void typeVarWildcard2(T param, MyInterface myInterface) {} - void bar() throws MyExceptionA, MyExceptionB {} + void bar() throws MyExceptionA, MyExceptionB {} - interface MyInterface {} + interface MyInterface {} - class MyExceptionA extends Throwable implements Cloneable, MyInterface {} + class MyExceptionA extends Throwable implements Cloneable, MyInterface {} - class MyExceptionB extends Throwable implements Cloneable, MyInterface {} + class MyExceptionB extends Throwable implements Cloneable, MyInterface {} - class SubMyExceptionA extends MyExceptionA {} + class SubMyExceptionA extends MyExceptionA {} - class SubMyExceptionA2 extends MyExceptionA {} + class SubMyExceptionA2 extends MyExceptionA {} } diff --git a/framework/tests/all-systems/VarKeyword.java b/framework/tests/all-systems/VarKeyword.java index 2d605a28a69..daf26ee405e 100644 --- a/framework/tests/all-systems/VarKeyword.java +++ b/framework/tests/all-systems/VarKeyword.java @@ -2,14 +2,14 @@ // @below-java11-jdk-skip-test public class VarKeyword { - @SuppressWarnings("dereference.of.nullable") - void method(List list) { - var s = "Hello!"; - s = null; - s.toString(); - for (var i : list) {} + @SuppressWarnings("dereference.of.nullable") + void method(List list) { + var s = "Hello!"; + s = null; + s.toString(); + for (var i : list) {} - var listVar = list; - method((listVar)); - } + var listVar = list; + method((listVar)); + } } diff --git a/framework/tests/all-systems/Viz.java b/framework/tests/all-systems/Viz.java index 417a1cc7770..e0af141697d 100644 --- a/framework/tests/all-systems/Viz.java +++ b/framework/tests/all-systems/Viz.java @@ -2,18 +2,18 @@ public class Viz { - static class AbstractValue> {} + static class AbstractValue> {} - public interface Store> {} + public interface Store> {} - public interface TransferFunction, D extends Store> {} + public interface TransferFunction, D extends Store> {} - public interface CFGVisualizer< - E extends AbstractValue, F extends Store, G extends TransferFunction> {} + public interface CFGVisualizer< + E extends AbstractValue, F extends Store, G extends TransferFunction> {} - static class CFAbstractStore, X extends CFAbstractStore> - implements Store { + static class CFAbstractStore, X extends CFAbstractStore> + implements Store { - void test(CFGVisualizer param) {} - } + void test(CFGVisualizer param) {} + } } diff --git a/framework/tests/all-systems/WildCardCrash.java b/framework/tests/all-systems/WildCardCrash.java index aefb31f5cda..615cab04761 100644 --- a/framework/tests/all-systems/WildCardCrash.java +++ b/framework/tests/all-systems/WildCardCrash.java @@ -1,40 +1,40 @@ public class WildCardCrash {} abstract class AbstractTransfer123< - IndexStore extends CFAbstractStore123, - MySelf extends AbstractTransfer123> - extends CFAbstractTransfer123 { - void method() { - // There was a crash when checking the assignment of analysis to the formal parameter. - SomeGen rfi = new SomeGen<>(analysis); - } + IndexStore extends CFAbstractStore123, + MySelf extends AbstractTransfer123> + extends CFAbstractTransfer123 { + void method() { + // There was a crash when checking the assignment of analysis to the formal parameter. + SomeGen rfi = new SomeGen<>(analysis); + } } class SomeGen> { - public SomeGen(CFAbstractAnalysis123 analysis) {} + public SomeGen(CFAbstractAnalysis123 analysis) {} } class CFValue123 extends CFAbstractValue123 {} @SuppressWarnings({"initialization", "initializedfields:contracts.postcondition.not.satisfied"}) class CFAbstractTransfer123< - V extends CFAbstractValue123, - S extends CFAbstractStore123, - T extends CFAbstractTransfer123> - implements TransferFunction123 { - protected CFAbstractAnalysis123 analysis; + V extends CFAbstractValue123, + S extends CFAbstractStore123, + T extends CFAbstractTransfer123> + implements TransferFunction123 { + protected CFAbstractAnalysis123 analysis; } class CFAbstractValue123> implements AbstractValue123 {} class CFAbstractStore123, S extends CFAbstractStore123> - implements Store123 {} + implements Store123 {} abstract class CFAbstractAnalysis123< - V extends CFAbstractValue123, - S extends CFAbstractStore123, - T extends CFAbstractTransfer123> - extends Analysis123 {} + V extends CFAbstractValue123, + S extends CFAbstractStore123, + T extends CFAbstractTransfer123> + extends Analysis123 {} interface AbstractValue123> {} @@ -43,4 +43,6 @@ interface Store123> {} interface TransferFunction123, S extends Store123> {} class Analysis123< - A extends AbstractValue123, S extends Store123, T extends TransferFunction123> {} + A extends AbstractValue123, + S extends Store123, + T extends TransferFunction123> {} diff --git a/framework/tests/all-systems/WildcardBounds.java b/framework/tests/all-systems/WildcardBounds.java index 2e648929cd5..9e31b3ce1a6 100644 --- a/framework/tests/all-systems/WildcardBounds.java +++ b/framework/tests/all-systems/WildcardBounds.java @@ -1,15 +1,15 @@ import java.io.Serializable; public class WildcardBounds { - class BoundedGeneric {} + class BoundedGeneric {} - class BoundedGeneric2 {} + class BoundedGeneric2 {} - class BoundedGeneric3 {} + class BoundedGeneric3 {} - void use() { - BoundedGeneric a; - BoundedGeneric b; - BoundedGeneric c; - } + void use() { + BoundedGeneric a; + BoundedGeneric b; + BoundedGeneric c; + } } diff --git a/framework/tests/all-systems/WildcardCharPrimitive.java b/framework/tests/all-systems/WildcardCharPrimitive.java index 1a053ef07ff..77cfbc93b80 100644 --- a/framework/tests/all-systems/WildcardCharPrimitive.java +++ b/framework/tests/all-systems/WildcardCharPrimitive.java @@ -1,24 +1,24 @@ public class WildcardCharPrimitive { - static interface Predicate { - boolean apply(T t); - } + static interface Predicate { + boolean apply(T t); + } - abstract static class Matcher { + abstract static class Matcher { - public abstract boolean matches(char character); - } + public abstract boolean matches(char character); + } - public static void forPredicate(final Predicate predicate) { + public static void forPredicate(final Predicate predicate) { - new Matcher() { - // this tests default type hierarchy visitPrimitive_Wildcard - public boolean matches(char c) { - // this will happen when a type system does not have an EXPLICIT_LOWER_BOUND - // that matches the default for char c - @SuppressWarnings("argument.type.incompatible") - boolean value = predicate.apply(c); - return value; - } - }; - } + new Matcher() { + // this tests default type hierarchy visitPrimitive_Wildcard + public boolean matches(char c) { + // this will happen when a type system does not have an EXPLICIT_LOWER_BOUND + // that matches the default for char c + @SuppressWarnings("argument.type.incompatible") + boolean value = predicate.apply(c); + return value; + } + }; + } } diff --git a/framework/tests/all-systems/WildcardCon.java b/framework/tests/all-systems/WildcardCon.java index c16b31682b5..ea139919ab1 100644 --- a/framework/tests/all-systems/WildcardCon.java +++ b/framework/tests/all-systems/WildcardCon.java @@ -2,15 +2,15 @@ @SuppressWarnings("all") // Only testing for crashes public class WildcardCon { - ComparatorClass> RANGE_LEX_ORDERING = null; + ComparatorClass> RANGE_LEX_ORDERING = null; - WildcardCon(Comparator comparator) {} + WildcardCon(Comparator comparator) {} - void use() { - new WildcardCon>(RANGE_LEX_ORDERING); - } + void use() { + new WildcardCon>(RANGE_LEX_ORDERING); + } - class ComparableClass {} + class ComparableClass {} - abstract class ComparatorClass implements Comparator {} + abstract class ComparatorClass implements Comparator {} } diff --git a/framework/tests/all-systems/WildcardForEach.java b/framework/tests/all-systems/WildcardForEach.java index 3db717b3695..69ad678452c 100644 --- a/framework/tests/all-systems/WildcardForEach.java +++ b/framework/tests/all-systems/WildcardForEach.java @@ -1,11 +1,11 @@ import java.util.List; public class WildcardForEach { - static class Gen> {} + static class Gen> {} - void test(List> x) { - // This used to cause a crash in - // org.checkerframework.framework.flow.CFTreeBuilder#buildAnnotatedType - for (Gen a : x) {} - } + void test(List> x) { + // This used to cause a crash in + // org.checkerframework.framework.flow.CFTreeBuilder#buildAnnotatedType + for (Gen a : x) {} + } } diff --git a/framework/tests/all-systems/WildcardInReturn.java b/framework/tests/all-systems/WildcardInReturn.java index c9327b1ef8a..7c9c785e820 100644 --- a/framework/tests/all-systems/WildcardInReturn.java +++ b/framework/tests/all-systems/WildcardInReturn.java @@ -12,14 +12,14 @@ abstract class WildcardInReturn { - abstract WildcardInReturn of(String key); + abstract WildcardInReturn of(String key); - abstract WildcardInReturn getSubtype(Class subclass); + abstract WildcardInReturn getSubtype(Class subclass); - WildcardInReturn getSubtypeFromLowerBounds(Class subclass, String key) { - @SuppressWarnings("unchecked") // T's lower bound is - WildcardInReturn bound = (WildcardInReturn) of(key); - // Java supports only one lowerbound anyway. - return bound.getSubtype(subclass); - } + WildcardInReturn getSubtypeFromLowerBounds(Class subclass, String key) { + @SuppressWarnings("unchecked") // T's lower bound is + WildcardInReturn bound = (WildcardInReturn) of(key); + // Java supports only one lowerbound anyway. + return bound.getSubtype(subclass); + } } diff --git a/framework/tests/all-systems/WildcardIterable.java b/framework/tests/all-systems/WildcardIterable.java index 1249a6a9348..2222863a26a 100644 --- a/framework/tests/all-systems/WildcardIterable.java +++ b/framework/tests/all-systems/WildcardIterable.java @@ -2,18 +2,18 @@ import java.util.List; public class WildcardIterable { - private static List catListAndIterable( - final List list, final Iterable iterable) { - final List newList = new ArrayList<>(); + private static List catListAndIterable( + final List list, final Iterable iterable) { + final List newList = new ArrayList<>(); - for (T listObject : list) { - newList.add(listObject); - } + for (T listObject : list) { + newList.add(listObject); + } - for (T iterObject : iterable) { - newList.add(iterObject); - } + for (T iterObject : iterable) { + newList.add(iterObject); + } - return newList; - } + return newList; + } } diff --git a/framework/tests/all-systems/WildcardSuper.java b/framework/tests/all-systems/WildcardSuper.java index 4fc25cd2274..4bb7386da9d 100644 --- a/framework/tests/all-systems/WildcardSuper.java +++ b/framework/tests/all-systems/WildcardSuper.java @@ -1,13 +1,13 @@ public class WildcardSuper { - interface Consumer { - void consume(T object); - } + interface Consumer { + void consume(T object); + } - Consumer testCast(Consumer consumer) { - return cast(consumer); - } + Consumer testCast(Consumer consumer) { + return cast(consumer); + } - private static Consumer cast(final Consumer consumer) { - throw new RuntimeException(); - } + private static Consumer cast(final Consumer consumer) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/WildcardSuper2.java b/framework/tests/all-systems/WildcardSuper2.java index 29a87bcbf55..4cead900274 100644 --- a/framework/tests/all-systems/WildcardSuper2.java +++ b/framework/tests/all-systems/WildcardSuper2.java @@ -3,14 +3,14 @@ // See also checker/nullness/generics/WildcardOverride.java interface AInterface { - public abstract int transform(List function); + public abstract int transform(List function); } class B implements AInterface { - // This shouldn't work for nullness as the function won't take possibly nullable values. - @SuppressWarnings({"nullness", "fenum:override.param.invalid", "aliasing"}) - @Override - public int transform(List function) { - return 0; - } + // This shouldn't work for nullness as the function won't take possibly nullable values. + @SuppressWarnings({"nullness", "fenum:override.param.invalid", "aliasing"}) + @Override + public int transform(List function) { + return 0; + } } diff --git a/framework/tests/all-systems/java17/BindingVariable.java b/framework/tests/all-systems/java17/BindingVariable.java index ffaa68ea6f1..ed082ab1e1d 100644 --- a/framework/tests/all-systems/java17/BindingVariable.java +++ b/framework/tests/all-systems/java17/BindingVariable.java @@ -2,11 +2,11 @@ // This is a test case for https://github.com/typetools/checker-framework/issues/5013. public class BindingVariable { - public static int bar(Object o) { - if (o instanceof String s) { - return s.length(); - } else { - return 0; + public static int bar(Object o) { + if (o instanceof String s) { + return s.length(); + } else { + return 0; + } } - } } diff --git a/framework/tests/all-systems/java17/Issue5749.java b/framework/tests/all-systems/java17/Issue5749.java index c756d3687f7..bef9f94f5f3 100644 --- a/framework/tests/all-systems/java17/Issue5749.java +++ b/framework/tests/all-systems/java17/Issue5749.java @@ -3,11 +3,11 @@ public record Issue5749() { - public enum Bar { - A, - B, - C - } + public enum Bar { + A, + B, + C + } - public static final Stream BAR = Stream.of(Bar.values()).map(b -> ""); + public static final Stream BAR = Stream.of(Bar.values()).map(b -> ""); } diff --git a/framework/tests/all-systems/java17/Issue5930.java b/framework/tests/all-systems/java17/Issue5930.java index 2d4f9a228a8..0f6ce3b2aa7 100644 --- a/framework/tests/all-systems/java17/Issue5930.java +++ b/framework/tests/all-systems/java17/Issue5930.java @@ -3,18 +3,18 @@ import java.util.function.Supplier; public final class Issue5930 { - enum TestEnum { - FIRST, - SECOND - }; + enum TestEnum { + FIRST, + SECOND + }; - public static void main(String[] args) { - TestEnum testEnum = TestEnum.FIRST; - Supplier supplier = - switch (testEnum) { - case FIRST -> () -> 1; - case SECOND -> () -> 2; - }; - System.out.println(supplier.get()); - } + public static void main(String[] args) { + TestEnum testEnum = TestEnum.FIRST; + Supplier supplier = + switch (testEnum) { + case FIRST -> () -> 1; + case SECOND -> () -> 2; + }; + System.out.println(supplier.get()); + } } diff --git a/framework/tests/all-systems/java17/Issue6069.java b/framework/tests/all-systems/java17/Issue6069.java index ade59f5717b..21422fad1c0 100644 --- a/framework/tests/all-systems/java17/Issue6069.java +++ b/framework/tests/all-systems/java17/Issue6069.java @@ -5,21 +5,21 @@ package issue6069; public class Issue6069 { - public interface MyInterface { - Record foo(); - } + public interface MyInterface { + Record foo(); + } - public static class MyClass implements MyInterface { + public static class MyClass implements MyInterface { - public MyRecord foo() { - return new MyRecord(42); - } + public MyRecord foo() { + return new MyRecord(42); + } - record MyRecord(int bar) {} + record MyRecord(int bar) {} - public static void main(String[] args) { - MyClass f = new MyClass(); - System.out.println(f.foo()); + public static void main(String[] args) { + MyClass f = new MyClass(); + System.out.println(f.foo()); + } } - } } diff --git a/framework/tests/all-systems/java17/Issue6100.java b/framework/tests/all-systems/java17/Issue6100.java index 82c5c7e5c55..88c9fcf9e57 100644 --- a/framework/tests/all-systems/java17/Issue6100.java +++ b/framework/tests/all-systems/java17/Issue6100.java @@ -1,15 +1,16 @@ // @below-java17-jdk-skip-test // @infer-jaifs-skip-test The AFU's JAIF reading/writing libraries don't support records. -import java.util.List; import org.checkerframework.checker.index.qual.NonNegative; +import java.util.List; + @SuppressWarnings("signedness") public record Issue6100(List<@NonNegative Integer> bar) { - public Issue6100 { - if (bar.size() < 0) { - throw new IllegalArgumentException(); + public Issue6100 { + if (bar.size() < 0) { + throw new IllegalArgumentException(); + } } - } } diff --git a/framework/tests/all-systems/java17/SimpleRecord.java b/framework/tests/all-systems/java17/SimpleRecord.java index f5996944945..b218f29f600 100644 --- a/framework/tests/all-systems/java17/SimpleRecord.java +++ b/framework/tests/all-systems/java17/SimpleRecord.java @@ -3,8 +3,8 @@ import java.util.function.Predicate; record SimpleRecord>(T root) implements Predicate { - @Override - public boolean test(T t) { - return root().compareTo(t) < 0; - } + @Override + public boolean test(T t) { + return root().compareTo(t) < 0; + } } diff --git a/framework/tests/all-systems/java17/SwitchTests.java b/framework/tests/all-systems/java17/SwitchTests.java index 08b6dde21c7..faad3e33300 100644 --- a/framework/tests/all-systems/java17/SwitchTests.java +++ b/framework/tests/all-systems/java17/SwitchTests.java @@ -9,163 +9,163 @@ @SuppressWarnings("all") // Just check for crashes. class SwitchTests { - String statementRule(DayOfWeek day) { - var today = ""; - switch (day) { - case SATURDAY, SUNDAY -> today = "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> today = "workday"; - default -> throw new IllegalArgumentException("Invalid day: " + day.name()); + String statementRule(DayOfWeek day) { + var today = ""; + switch (day) { + case SATURDAY, SUNDAY -> today = "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> today = "workday"; + default -> throw new IllegalArgumentException("Invalid day: " + day.name()); + } + return today; } - return today; - } - String expressionRule1(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY -> "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; - default -> throw new IllegalArgumentException("Invalid day: " + day.name()); - }; + String expressionRule1(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY -> "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; + default -> throw new IllegalArgumentException("Invalid day: " + day.name()); + }; - return today; - } + return today; + } - int expressionRule2(DayOfWeek day) { - int numLetters = - switch (day) { - case MONDAY, FRIDAY, SUNDAY -> 6; - case TUESDAY -> 7; - case THURSDAY, SATURDAY -> 8; - case WEDNESDAY -> 9; - }; - return numLetters; - } - - String expressionLabel(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY: - yield "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: - yield "workday"; - default: - throw new IllegalArgumentException("Invalid day: " + day.name()); - }; - - return today; - } - - String expressionLabelBlock(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY: - yield "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: - { - var kind = "workday"; - yield kind; - } - default: - { - var kind = day.name(); - System.out.println(kind); - throw new IllegalArgumentException("Invalid day: " + kind); - } - }; + int expressionRule2(DayOfWeek day) { + int numLetters = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> 6; + case TUESDAY -> 7; + case THURSDAY, SATURDAY -> 8; + case WEDNESDAY -> 9; + }; + return numLetters; + } - return today; - } + String expressionLabel(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY: + yield "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: + yield "workday"; + default: + throw new IllegalArgumentException("Invalid day: " + day.name()); + }; + + return today; + } - String expressionRuleYield(DayOfWeek day) { - var today = - switch (day) { - case SATURDAY, SUNDAY -> "weekend"; - case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; - default -> { - var len = day.name().length(); - yield len; - } - }; - return today.toString(); - } - - void fallthrough1(int myInt) { - switch (myInt) { - case 1 -> System.out.println("One"); - case 2 -> { - System.out.println("Two"); - System.out.println("No fall through"); - } - default -> System.out.println("Something else"); + String expressionLabelBlock(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY: + yield "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: + { + var kind = "workday"; + yield kind; + } + default: + { + var kind = day.name(); + System.out.println(kind); + throw new IllegalArgumentException("Invalid day: " + kind); + } + }; + + return today; } - } - - void fallthrough2(int myInt) { - switch (myInt) { - case 1: - System.out.println("One"); - break; - case 2: - System.out.println("Two"); - System.out.println("Falling through"); - default: - System.out.println("Something else"); + + String expressionRuleYield(DayOfWeek day) { + var today = + switch (day) { + case SATURDAY, SUNDAY -> "weekend"; + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "workday"; + default -> { + var len = day.name().length(); + yield len; + } + }; + return today.toString(); } - } - - void fallthrough3(int myInt) { - switch (myInt) { - case 1, 2, 3: - System.out.println("One, two or three"); - break; - default: - System.out.println("Something else"); + + void fallthrough1(int myInt) { + switch (myInt) { + case 1 -> System.out.println("One"); + case 2 -> { + System.out.println("Two"); + System.out.println("No fall through"); + } + default -> System.out.println("Something else"); + } } - } - - void fallthrough4(int myInt) { - switch (myInt) { - case 1: - System.out.print("Hello "); - // Fall through to next case - case 2: - System.out.println("World"); - break; - default: - System.out.println("Not executed"); + + void fallthrough2(int myInt) { + switch (myInt) { + case 1: + System.out.println("One"); + break; + case 2: + System.out.println("Two"); + System.out.println("Falling through"); + default: + System.out.println("Something else"); + } } - } - - int multipleLabels(int month, int year) { - int numDays = 0; - - switch (month) { - case 1, 3, 5, 7, 8, 10, 12 -> { - numDays = 31; - } - case 4, 6, 9, 11 -> { - numDays = 30; - } - case 2 -> { - if (((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0)) { - numDays = 29; - } else { - numDays = 28; + + void fallthrough3(int myInt) { + switch (myInt) { + case 1, 2, 3: + System.out.println("One, two or three"); + break; + default: + System.out.println("Something else"); } - } - default -> { - System.out.println("Invalid month."); - } } - return numDays; - } - void switchStatementsWithExpression(int selector) { - switch (selector) { - case 1 -> "hello".toString(); - default -> "goodbye".toString(); + void fallthrough4(int myInt) { + switch (myInt) { + case 1: + System.out.print("Hello "); + // Fall through to next case + case 2: + System.out.println("World"); + break; + default: + System.out.println("Not executed"); + } } - switch (selector) { + + int multipleLabels(int month, int year) { + int numDays = 0; + + switch (month) { + case 1, 3, 5, 7, 8, 10, 12 -> { + numDays = 31; + } + case 4, 6, 9, 11 -> { + numDays = 30; + } + case 2 -> { + if (((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0)) { + numDays = 29; + } else { + numDays = 28; + } + } + default -> { + System.out.println("Invalid month."); + } + } + return numDays; + } + + void switchStatementsWithExpression(int selector) { + switch (selector) { + case 1 -> "hello".toString(); + default -> "goodbye".toString(); + } + switch (selector) { + } } - } } diff --git a/framework/tests/all-systems/java21/Issue6173.java b/framework/tests/all-systems/java21/Issue6173.java index 687b90d0dd3..46e52e81f72 100644 --- a/framework/tests/all-systems/java21/Issue6173.java +++ b/framework/tests/all-systems/java21/Issue6173.java @@ -9,20 +9,20 @@ public class Issue6173 { - static Object toGroupByQueryWithExtractor2(@Nullable GroupBy groupBy) { - return switch (groupBy) { - case OWNER -> new Object(); - case CHANNEL -> new Object(); - case TOPIC -> new Object(); - case TEAM -> new Object(); - case null -> throw new IllegalArgumentException("GroupBy parameter is required"); - }; - } + static Object toGroupByQueryWithExtractor2(@Nullable GroupBy groupBy) { + return switch (groupBy) { + case OWNER -> new Object(); + case CHANNEL -> new Object(); + case TOPIC -> new Object(); + case TEAM -> new Object(); + case null -> throw new IllegalArgumentException("GroupBy parameter is required"); + }; + } - public enum GroupBy { - OWNER, - CHANNEL, - TOPIC, - TEAM; - } + public enum GroupBy { + OWNER, + CHANNEL, + TOPIC, + TEAM; + } } diff --git a/framework/tests/all-systems/java21/JEP440.java b/framework/tests/all-systems/java21/JEP440.java index 2c33031f0cd..acd2a109cf2 100644 --- a/framework/tests/all-systems/java21/JEP440.java +++ b/framework/tests/all-systems/java21/JEP440.java @@ -13,63 +13,63 @@ @SuppressWarnings("i18n") // true postives. public class JEP440 { - record Point(int x, int y) {} + record Point(int x, int y) {} - static void printSum(Object obj) { - if (obj instanceof Point(int x, int y)) { - System.out.println(x + y); + static void printSum(Object obj) { + if (obj instanceof Point(int x, int y)) { + System.out.println(x + y); + } } - } - enum Color { - RED, - GREEN, - BLUE - } + enum Color { + RED, + GREEN, + BLUE + } - record ColoredPoint(Point p, Color c) {} + record ColoredPoint(Point p, Color c) {} - record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} + record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} - static void printUpperLeftColoredPoint(Rectangle r) { - if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { - System.out.println(ul.c()); + static void printUpperLeftColoredPoint(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { + System.out.println(ul.c()); + } } - } - static void printColorOfUpperLeftPoint(Rectangle r) { - if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) { - System.out.println(c); + static void printColorOfUpperLeftPoint(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) { + System.out.println(c); + } } - } - static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) { - if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) { - System.out.println("Upper-left corner: " + x); + static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) { + System.out.println("Upper-left corner: " + x); + } } - } - void failToMatch() { - record Pair(Object x, Object y) {} - Pair p = new Pair(42, 42); - if (p instanceof Pair(String s, String t)) { - System.out.println(s + ", " + t); - } else { - System.out.println("Not a pair of strings"); + void failToMatch() { + record Pair(Object x, Object y) {} + Pair p = new Pair(42, 42); + if (p instanceof Pair(String s, String t)) { + System.out.println(s + ", " + t); + } else { + System.out.println("Not a pair of strings"); + } } - } - record Box(T t) {} + record Box(T t) {} - static void test1(Box> bbs) { - if (bbs instanceof Box>(Box(var s))) { - System.out.println("String " + s); + static void test1(Box> bbs) { + if (bbs instanceof Box>(Box(var s))) { + System.out.println("String " + s); + } } - } - static void test2(Box> bbs) { - if (bbs instanceof Box(Box(var s))) { - System.out.println("String " + s); + static void test2(Box> bbs) { + if (bbs instanceof Box(Box(var s))) { + System.out.println("String " + s); + } } - } } diff --git a/framework/tests/all-systems/java21/JEP441.java b/framework/tests/all-systems/java21/JEP441.java index 32116ad33e8..52b8974a71c 100644 --- a/framework/tests/all-systems/java21/JEP441.java +++ b/framework/tests/all-systems/java21/JEP441.java @@ -14,240 +14,240 @@ @SuppressWarnings({"i18n"}) // true postives. public class JEP441 { - // JEP 441 enhances switch statements and expressions in four ways: - // * Improve enum constant case labels - // * Extend case labels to include patterns and null in addition to constants - // * Broaden the range of types permitted for the selector expressions of both switch - // statements and switch expressions (along with the required richer analysis of - // exhaustiveness of switch blocks) - // * Allow optional when clauses to follow case labels. - - // Prior to Java 21 - static String formatter(Object obj) { - String formatted = "unknown"; - if (obj instanceof Integer i) { - formatted = String.format("int %d", i); - } else if (obj instanceof Long l) { - formatted = String.format("long %d", l); - } else if (obj instanceof Double d) { - formatted = String.format("double %f", d); - } else if (obj instanceof String s) { - formatted = String.format("String %s", s); - } - return formatted; - } - - static void formatterPatternSwitchStatement(Object obj) { - switch (obj) { - case Integer i: - String.format("int %d", i); - break; - case Long l: - String.format("long %d", l); - break; - case Double d: - String.format("double %f", d); - break; - case String s: - String.format("String %s", s); - break; - default: - obj.toString(); - } - } - - static String formatterPatternSwitch(Object obj) { - return switch (obj) { - case Integer i -> String.format("int %d", i); - case Long l -> String.format("long %d", l); - case Double d -> String.format("double %f", d); - case String s -> String.format("String %s", s); - default -> obj.toString(); - }; - } - - // As of Java 21 - static void testFooBarNew(@Nullable String s) { - switch (s) { - case null -> System.out.println("Oops"); - case "Foo", "Bar" -> System.out.println("Great"); - default -> System.out.println("Ok"); - } - } - - static void testStringOld(@Nullable String response) { - switch (response) { - case null -> {} - case String s -> { - if (s.equalsIgnoreCase("YES")) System.out.println("You got it"); - else if (s.equalsIgnoreCase("NO")) System.out.println("Shame"); - else System.out.println("Sorry?"); - } - } - } - - static void testStringNew(@Nullable String response) { - switch (response) { - case null -> {} - case String s when s.equalsIgnoreCase("YES") -> { - System.out.println("You got it"); - } - case String s when s.equalsIgnoreCase("NO") -> { - System.out.println("Shame"); - } - case String s -> { - System.out.println("Sorry?"); - } - } - } - - static void testStringEnhanced(@Nullable String response) { - switch (response) { - case null -> {} - case "y", "Y" -> { - System.out.println("You got it"); - } - case "n", "N" -> { - System.out.println("Shame"); - } - case String s when s.equalsIgnoreCase("YES") -> { - System.out.println("You got it"); - } - case String s when s.equalsIgnoreCase("NO") -> { - System.out.println("Shame"); - } - case String s -> { - System.out.println("Sorry?"); - } - } - } - - sealed interface CardClassification permits Suit, Tarot {} - - public enum Suit implements CardClassification { - CLUBS, - DIAMONDS, - HEARTS, - SPADES - } - - final class Tarot implements CardClassification {} - - static void exhaustiveSwitchWithoutEnumSupport(CardClassification c) { - switch (c) { - case Suit s when s == Suit.CLUBS -> { - System.out.println("It's clubs"); - } - case Suit s when s == Suit.DIAMONDS -> { - System.out.println("It's diamonds"); - } - case Suit s when s == Suit.HEARTS -> { - System.out.println("It's hearts"); - } - case Suit s -> { - System.out.println("It's spades"); - } - case Tarot t -> { - System.out.println("It's a tarot"); - } - } - } - - static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) { - switch (c) { - case Suit.CLUBS -> { - System.out.println("It's clubs"); - } - case Suit.DIAMONDS -> { - System.out.println("It's diamonds"); - } - case Suit.HEARTS -> { - System.out.println("It's hearts"); - } - case Suit.SPADES -> { - System.out.println("It's spades"); - } - case Tarot t -> { - System.out.println("It's a tarot"); - } - } - } - - sealed interface Currency permits Coin {} - - enum Coin implements Currency { - HEADS, - TAILS - } - - static void goodEnumSwitch1(Currency c) { - switch (c) { - case Coin.HEADS -> { // Qualified name of enum constant as a label - System.out.println("Heads"); - } - case Coin.TAILS -> { - System.out.println("Tails"); - } - } - } - - static void goodEnumSwitch2(Coin c) { - switch (c) { - case HEADS -> { - System.out.println("Heads"); - } - case Coin.TAILS -> { // Unnecessary qualification but allowed - System.out.println("Tails"); - } - } - } - - record Point(int i, int j) {} - - enum Color { - RED, - GREEN, - BLUE; - } - - static void typeTester(@Nullable Object obj) { - switch (obj) { - case null -> System.out.println("null"); - case String s -> System.out.println("String"); - case Color c -> System.out.println("Color: " + c.toString()); - case Point p -> System.out.println("Record class: " + p.toString()); - case int[] ia -> System.out.println("Array of ints of length" + ia.length); - default -> System.out.println("Something else"); - } - } - - static void first(Object obj) { - switch (obj) { - case String s -> System.out.println("A string: " + s); - case CharSequence cs -> System.out.println("A sequence of length " + cs.length()); - default -> { - break; - } - } - } - - record MyPair(S fst, T snd) {} - - static void recordInference(MyPair pair) { - switch (pair) { - case MyPair(var f, var s) -> { - String ff = f; - Integer ss = s; - } - } - } - - void fragment(Integer i) { - // TODO: This would be a good test case for the Value Checker. - // switch (i) { - // case -1, 1 -> ... // Special cases - // case Integer j when j > 0 -> ... // Positive integer cases - // case Integer j -> ... // All the remaining integers - // } - } + // JEP 441 enhances switch statements and expressions in four ways: + // * Improve enum constant case labels + // * Extend case labels to include patterns and null in addition to constants + // * Broaden the range of types permitted for the selector expressions of both switch + // statements and switch expressions (along with the required richer analysis of + // exhaustiveness of switch blocks) + // * Allow optional when clauses to follow case labels. + + // Prior to Java 21 + static String formatter(Object obj) { + String formatted = "unknown"; + if (obj instanceof Integer i) { + formatted = String.format("int %d", i); + } else if (obj instanceof Long l) { + formatted = String.format("long %d", l); + } else if (obj instanceof Double d) { + formatted = String.format("double %f", d); + } else if (obj instanceof String s) { + formatted = String.format("String %s", s); + } + return formatted; + } + + static void formatterPatternSwitchStatement(Object obj) { + switch (obj) { + case Integer i: + String.format("int %d", i); + break; + case Long l: + String.format("long %d", l); + break; + case Double d: + String.format("double %f", d); + break; + case String s: + String.format("String %s", s); + break; + default: + obj.toString(); + } + } + + static String formatterPatternSwitch(Object obj) { + return switch (obj) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + default -> obj.toString(); + }; + } + + // As of Java 21 + static void testFooBarNew(@Nullable String s) { + switch (s) { + case null -> System.out.println("Oops"); + case "Foo", "Bar" -> System.out.println("Great"); + default -> System.out.println("Ok"); + } + } + + static void testStringOld(@Nullable String response) { + switch (response) { + case null -> {} + case String s -> { + if (s.equalsIgnoreCase("YES")) System.out.println("You got it"); + else if (s.equalsIgnoreCase("NO")) System.out.println("Shame"); + else System.out.println("Sorry?"); + } + } + } + + static void testStringNew(@Nullable String response) { + switch (response) { + case null -> {} + case String s when s.equalsIgnoreCase("YES") -> { + System.out.println("You got it"); + } + case String s when s.equalsIgnoreCase("NO") -> { + System.out.println("Shame"); + } + case String s -> { + System.out.println("Sorry?"); + } + } + } + + static void testStringEnhanced(@Nullable String response) { + switch (response) { + case null -> {} + case "y", "Y" -> { + System.out.println("You got it"); + } + case "n", "N" -> { + System.out.println("Shame"); + } + case String s when s.equalsIgnoreCase("YES") -> { + System.out.println("You got it"); + } + case String s when s.equalsIgnoreCase("NO") -> { + System.out.println("Shame"); + } + case String s -> { + System.out.println("Sorry?"); + } + } + } + + sealed interface CardClassification permits Suit, Tarot {} + + public enum Suit implements CardClassification { + CLUBS, + DIAMONDS, + HEARTS, + SPADES + } + + final class Tarot implements CardClassification {} + + static void exhaustiveSwitchWithoutEnumSupport(CardClassification c) { + switch (c) { + case Suit s when s == Suit.CLUBS -> { + System.out.println("It's clubs"); + } + case Suit s when s == Suit.DIAMONDS -> { + System.out.println("It's diamonds"); + } + case Suit s when s == Suit.HEARTS -> { + System.out.println("It's hearts"); + } + case Suit s -> { + System.out.println("It's spades"); + } + case Tarot t -> { + System.out.println("It's a tarot"); + } + } + } + + static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) { + switch (c) { + case Suit.CLUBS -> { + System.out.println("It's clubs"); + } + case Suit.DIAMONDS -> { + System.out.println("It's diamonds"); + } + case Suit.HEARTS -> { + System.out.println("It's hearts"); + } + case Suit.SPADES -> { + System.out.println("It's spades"); + } + case Tarot t -> { + System.out.println("It's a tarot"); + } + } + } + + sealed interface Currency permits Coin {} + + enum Coin implements Currency { + HEADS, + TAILS + } + + static void goodEnumSwitch1(Currency c) { + switch (c) { + case Coin.HEADS -> { // Qualified name of enum constant as a label + System.out.println("Heads"); + } + case Coin.TAILS -> { + System.out.println("Tails"); + } + } + } + + static void goodEnumSwitch2(Coin c) { + switch (c) { + case HEADS -> { + System.out.println("Heads"); + } + case Coin.TAILS -> { // Unnecessary qualification but allowed + System.out.println("Tails"); + } + } + } + + record Point(int i, int j) {} + + enum Color { + RED, + GREEN, + BLUE; + } + + static void typeTester(@Nullable Object obj) { + switch (obj) { + case null -> System.out.println("null"); + case String s -> System.out.println("String"); + case Color c -> System.out.println("Color: " + c.toString()); + case Point p -> System.out.println("Record class: " + p.toString()); + case int[] ia -> System.out.println("Array of ints of length" + ia.length); + default -> System.out.println("Something else"); + } + } + + static void first(Object obj) { + switch (obj) { + case String s -> System.out.println("A string: " + s); + case CharSequence cs -> System.out.println("A sequence of length " + cs.length()); + default -> { + break; + } + } + } + + record MyPair(S fst, T snd) {} + + static void recordInference(MyPair pair) { + switch (pair) { + case MyPair(var f, var s) -> { + String ff = f; + Integer ss = s; + } + } + } + + void fragment(Integer i) { + // TODO: This would be a good test case for the Value Checker. + // switch (i) { + // case -1, 1 -> ... // Special cases + // case Integer j when j > 0 -> ... // Positive integer cases + // case Integer j -> ... // All the remaining integers + // } + } } diff --git a/framework/tests/all-systems/java8/DefaultMethods.java b/framework/tests/all-systems/java8/DefaultMethods.java index 5aab68f7fd1..916b868109d 100644 --- a/framework/tests/all-systems/java8/DefaultMethods.java +++ b/framework/tests/all-systems/java8/DefaultMethods.java @@ -1,17 +1,17 @@ interface DefaultMethods { - // Test that abstract methods are still ignored. - void abstractMethod(); + // Test that abstract methods are still ignored. + void abstractMethod(); - default String method(String s) { - return s.toString(); - } + default String method(String s) { + return s.toString(); + } } interface DefaultMethods2 extends DefaultMethods { - @Override - default String method(String s) { - return s; - } + @Override + default String method(String s) { + return s; + } } diff --git a/framework/tests/all-systems/java8/lambda/Issue1681.java b/framework/tests/all-systems/java8/lambda/Issue1681.java index fed088288aa..bcbf9a39382 100644 --- a/framework/tests/all-systems/java8/lambda/Issue1681.java +++ b/framework/tests/all-systems/java8/lambda/Issue1681.java @@ -2,18 +2,18 @@ // https://github.com/typetools/checker-framework/issues/1681 public class Issue1681 { - @FunctionalInterface - interface StrReturn { - String op(); - } + @FunctionalInterface + interface StrReturn { + String op(); + } - StrReturn[] v1 = new StrReturn[] {() -> "hello"}; - StrReturn[] v2 = new StrReturn[] {() -> "hello", () -> "world"}; - StrReturn[] v3 = {() -> "test"}; + StrReturn[] v1 = new StrReturn[] {() -> "hello"}; + StrReturn[] v2 = new StrReturn[] {() -> "hello", () -> "world"}; + StrReturn[] v3 = {() -> "test"}; - void foo(StrReturn[] p) {} + void foo(StrReturn[] p) {} - void bar() { - foo(new StrReturn[] {() -> "test", () -> "boo"}); - } + void bar() { + foo(new StrReturn[] {() -> "test", () -> "boo"}); + } } diff --git a/framework/tests/all-systems/java8/lambda/Issue1817.java b/framework/tests/all-systems/java8/lambda/Issue1817.java index 68d7d21bc1a..ac73fc903e2 100644 --- a/framework/tests/all-systems/java8/lambda/Issue1817.java +++ b/framework/tests/all-systems/java8/lambda/Issue1817.java @@ -6,9 +6,9 @@ @SuppressWarnings("all") // only check for crashes public class Issue1817 { - { - Consumer> c = values -> values.forEach(value -> f(value)); - } + { + Consumer> c = values -> values.forEach(value -> f(value)); + } - void f(Object o) {} + void f(Object o) {} } diff --git a/framework/tests/all-systems/java8/lambda/Issue436All.java b/framework/tests/all-systems/java8/lambda/Issue436All.java index 55e595053a9..bb122a8928d 100644 --- a/framework/tests/all-systems/java8/lambda/Issue436All.java +++ b/framework/tests/all-systems/java8/lambda/Issue436All.java @@ -4,16 +4,16 @@ import java.util.function.Supplier; public class Issue436All { - public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) { - // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the - // lambda return for those return statements below - Supplier> supplier = - () -> { - if (makeAll) { - return asList("beer", "peanuts"); - } else { - return asList("cheese", "wine"); - } - }; - } + public void makeALongFormConditionalLambdaReturningGenerics(boolean makeAll) { + // TypeArgInferenceUtil.assignedTo used to try to use the method return rather than the + // lambda return for those return statements below + Supplier> supplier = + () -> { + if (makeAll) { + return asList("beer", "peanuts"); + } else { + return asList("cheese", "wine"); + } + }; + } } diff --git a/framework/tests/all-systems/java8/lambda/Issue450.java b/framework/tests/all-systems/java8/lambda/Issue450.java index a73d3f85634..d6bf95927cc 100644 --- a/framework/tests/all-systems/java8/lambda/Issue450.java +++ b/framework/tests/all-systems/java8/lambda/Issue450.java @@ -1,37 +1,37 @@ public class Issue450 { - Issue450(int i, Runnable... runnables) {} + Issue450(int i, Runnable... runnables) {} - Issue450(Consumer consumer) { - consumer.consume("hello"); // Use lambda as a constructor argument - } + Issue450(Consumer consumer) { + consumer.consume("hello"); // Use lambda as a constructor argument + } - interface Top { - public void consume(String s); - } + interface Top { + public void consume(String s); + } - interface Sub extends Top { - public default void otherMethod() {} - } + interface Sub extends Top { + public default void otherMethod() {} + } - interface Consumer { - void consume(T t); - } + interface Consumer { + void consume(T t); + } - void varargs(Runnable... runnables) {} + void varargs(Runnable... runnables) {} - public static void consumeStr(String str) {} + public static void consumeStr(String str) {} - public static void consumeStr2(String str) {} + public static void consumeStr2(String str) {} - > void context(E e, Sub s) { - new Issue450(Issue450::consumeStr); + > void context(E e, Sub s) { + new Issue450(Issue450::consumeStr); - Consumer cs1 = (false) ? Issue450::consumeStr2 : Issue450::consumeStr; - Consumer cs2 = (false) ? e : Issue450::consumeStr; - Top t = (false) ? s : Issue450::consumeStr; + Consumer cs1 = (false) ? Issue450::consumeStr2 : Issue450::consumeStr; + Consumer cs2 = (false) ? e : Issue450::consumeStr; + Top t = (false) ? s : Issue450::consumeStr; - new Issue450(42, new Thread()::start); // Use lambda as a constructor argument - varargs(new Thread()::start, new Thread()::start); // Use lambda in a var arg list of method - } + new Issue450(42, new Thread()::start); // Use lambda as a constructor argument + varargs(new Thread()::start, new Thread()::start); // Use lambda in a var arg list of method + } } diff --git a/framework/tests/all-systems/java8/lambda/Issue573.java b/framework/tests/all-systems/java8/lambda/Issue573.java index c3572aed67f..f9952c51256 100644 --- a/framework/tests/all-systems/java8/lambda/Issue573.java +++ b/framework/tests/all-systems/java8/lambda/Issue573.java @@ -8,9 +8,9 @@ import java.util.Comparator; public abstract class Issue573 implements Chronology { - Object o = - (Comparator> & Serializable) - (dateTime1, dateTime2) -> { - return 0; - }; + Object o = + (Comparator> & Serializable) + (dateTime1, dateTime2) -> { + return 0; + }; } diff --git a/framework/tests/all-systems/java8/lambda/ManyLambda.java b/framework/tests/all-systems/java8/lambda/ManyLambda.java index 7d5a5517f15..4f72d86b2b5 100644 --- a/framework/tests/all-systems/java8/lambda/ManyLambda.java +++ b/framework/tests/all-systems/java8/lambda/ManyLambda.java @@ -2,95 +2,95 @@ // Test file for lambda syntax interface Supplier { - R supply(); + R supply(); } interface Function { - R apply(T t); + R apply(T t); } interface Consumer { - void consume(T t); + void consume(T t); } interface BiFunction { - R apply(T t, U u); + R apply(T t, U u); } interface Noop { - void noop(); + void noop(); } public class ManyLambda { - public static void consumeStr(String str) {} + public static void consumeStr(String str) {} - ManyLambda(Consumer consumer) { - consumer.consume("hello"); - } + ManyLambda(Consumer consumer) { + consumer.consume("hello"); + } - // No parameters; result is void - Noop f1 = () -> {}; - // No parameters, expression body - Supplier f2 = () -> 42; - // No parameters, expression body - // Supplier f3 = () -> null; - // No parameters, block body with return - Supplier f4 = - () -> { - return 42; - }; - Noop f5 = - () -> { - System.gc(); - }; // No parameters, void block body + // No parameters; result is void + Noop f1 = () -> {}; + // No parameters, expression body + Supplier f2 = () -> 42; + // No parameters, expression body + // Supplier f3 = () -> null; + // No parameters, block body with return + Supplier f4 = + () -> { + return 42; + }; + Noop f5 = + () -> { + System.gc(); + }; // No parameters, void block body - // Complex block body with returns - Supplier f6 = - () -> { - if (true) { - return 12; - } else { - int result = 15; - for (int i = 1; i < 10; i++) { - result *= i; - } - // conditional expression - Consumer consumer = - result > 100 ? ManyLambda::consumeStr : ManyLambda::consumeStr; - return result; - } - }; + // Complex block body with returns + Supplier f6 = + () -> { + if (true) { + return 12; + } else { + int result = 15; + for (int i = 1; i < 10; i++) { + result *= i; + } + // conditional expression + Consumer consumer = + result > 100 ? ManyLambda::consumeStr : ManyLambda::consumeStr; + return result; + } + }; - // Single declared-type parameter - Function f7 = (Integer x) -> x + 1; - // Single declared-type parameter - Function f9 = - (Integer x) -> { - return (Integer) x + 1; - }; - // Single inferred-type parameter - Function f10 = (x) -> x + 1; - // Parentheses optional for single inferred-type parameter - Function f11 = x -> x + 1; + // Single declared-type parameter + Function f7 = (Integer x) -> x + 1; + // Single declared-type parameter + Function f9 = + (Integer x) -> { + return (Integer) x + 1; + }; + // Single inferred-type parameter + Function f10 = (x) -> x + 1; + // Parentheses optional for single inferred-type parameter + Function f11 = x -> x + 1; - // Single declared-type parameter - Function f12 = (String s) -> s.length(); - // Single declared-type parameter - Consumer f13 = - (Thread t) -> { - t.start(); - }; - // Single inferred-type parameter - Consumer f14 = s -> s.length(); - // Single inferred-type parameter - Consumer f15 = - t -> { - t.start(); - }; + // Single declared-type parameter + Function f12 = (String s) -> s.length(); + // Single declared-type parameter + Consumer f13 = + (Thread t) -> { + t.start(); + }; + // Single inferred-type parameter + Consumer f14 = s -> s.length(); + // Single inferred-type parameter + Consumer f15 = + t -> { + t.start(); + }; - // Multiple declared-type parameters - BiFunction f16 = (Integer x, final Integer y) -> x + y; - // Multiple inferred-type parameters - BiFunction f18 = (x, y) -> x + y; + // Multiple declared-type parameters + BiFunction f16 = (Integer x, final Integer y) -> x + y; + // Multiple inferred-type parameters + BiFunction f18 = (x, y) -> x + y; } diff --git a/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java b/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java index 45c69bbbd68..c6256bb9de0 100644 --- a/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java +++ b/framework/tests/all-systems/java8/memberref/AssignmentContextFunction.java @@ -1,21 +1,21 @@ interface FunctionAC { - String apply(String s); + String apply(String s); } public class AssignmentContextFunction { - // Test assign - FunctionAC f1 = String::toString; + // Test assign + FunctionAC f1 = String::toString; - // Test casts - Object o1 = (Object) (FunctionAC) String::toString; + // Test casts + Object o1 = (Object) (FunctionAC) String::toString; - void take(FunctionAC f) { - // Test argument assingment - take(String::toString); - } + void take(FunctionAC f) { + // Test argument assingment + take(String::toString); + } - FunctionAC supply() { - // Test return assingment - return String::toString; - } + FunctionAC supply() { + // Test return assingment + return String::toString; + } } diff --git a/framework/tests/all-systems/java8/memberref/FromByteCode.java b/framework/tests/all-systems/java8/memberref/FromByteCode.java index 786bf433318..ec564389793 100644 --- a/framework/tests/all-systems/java8/memberref/FromByteCode.java +++ b/framework/tests/all-systems/java8/memberref/FromByteCode.java @@ -1,7 +1,7 @@ interface FunctionFromByteCode { - R apply(T t); + R apply(T t); } public class FromByteCode { - FunctionFromByteCode f1 = String::toString; + FunctionFromByteCode f1 = String::toString; } diff --git a/framework/tests/all-systems/java8/memberref/Issue871.java b/framework/tests/all-systems/java8/memberref/Issue871.java index b5c12de5f60..593ce4a80a3 100644 --- a/framework/tests/all-systems/java8/memberref/Issue871.java +++ b/framework/tests/all-systems/java8/memberref/Issue871.java @@ -5,9 +5,9 @@ import java.util.function.Predicate; interface Issue871 { - default Iterable a() { - return f(Files::isRegularFile); - } + default Iterable a() { + return f(Files::isRegularFile); + } - Iterable f(Predicate condition); + Iterable f(Predicate condition); } diff --git a/framework/tests/all-systems/java8/memberref/Issue946.java b/framework/tests/all-systems/java8/memberref/Issue946.java index 09417293d95..cdc5d352917 100644 --- a/framework/tests/all-systems/java8/memberref/Issue946.java +++ b/framework/tests/all-systems/java8/memberref/Issue946.java @@ -2,37 +2,37 @@ // https://github.com/typetools/checker-framework/issues/946 interface Supply946 { - R supply(); + R supply(); } public class Issue946 { - class MethodRefInnerA { - Supply946 constructorReferenceField = MethodRefInnerB::new; - - MethodRefInnerA(Issue946 Issue946.this) { - Supply946 constructorReference = MethodRefInnerB::new; + class MethodRefInnerA { + Supply946 constructorReferenceField = MethodRefInnerB::new; + + MethodRefInnerA(Issue946 Issue946.this) { + Supply946 constructorReference = MethodRefInnerB::new; + } + + void method() { + Supply946 constructorReference = MethodRefInnerB::new; + } + + class MethodRefInnerAInner { + void method() { + Supply946 constructorReference = MethodRefInnerB::new; + } + } } - void method() { - Supply946 constructorReference = MethodRefInnerB::new; - } + class MethodRefInnerB { + MethodRefInnerB(Issue946 Issue946.this) {} - class MethodRefInnerAInner { - void method() { - Supply946 constructorReference = MethodRefInnerB::new; - } + void method() { + Supply946 constructorReference = MethodRefInnerB::new; + } } - } - - class MethodRefInnerB { - MethodRefInnerB(Issue946 Issue946.this) {} void method() { - Supply946 constructorReference = MethodRefInnerB::new; + Supply946 constructorReference = MethodRefInnerB::new; } - } - - void method() { - Supply946 constructorReference = MethodRefInnerB::new; - } } diff --git a/framework/tests/all-systems/java8/memberref/MemberReferences.java b/framework/tests/all-systems/java8/memberref/MemberReferences.java index ba0271d1681..4058df08c7a 100644 --- a/framework/tests/all-systems/java8/memberref/MemberReferences.java +++ b/framework/tests/all-systems/java8/memberref/MemberReferences.java @@ -1,17 +1,17 @@ interface Supplier { - R supply(); + R supply(); } interface FunctionMR { - R apply(T t); + R apply(T t); } interface Consumer { - void consume(T t); + void consume(T t); } interface BiFunctionMR { - R apply(T t, U u); + R apply(T t, U u); } /** super # instMethod */ @@ -19,152 +19,152 @@ interface BiFunctionMR { @SuppressWarnings("all") class Super { - Object func1(Object o) { - return o; - } + Object func1(Object o) { + return o; + } - T func2(T o) { - return o; - } + T func2(T o) { + return o; + } - class Sub extends Super { - void context() { - FunctionMR f1 = super::func1; - FunctionMR f2 = super::func2; - // Top level wildcards are ignored when type checking - FunctionMR f3 = super::func2; + class Sub extends Super { + void context() { + FunctionMR f1 = super::func1; + FunctionMR f2 = super::func2; + // Top level wildcards are ignored when type checking + FunctionMR f3 = super::func2; + } } - } } @SuppressWarnings("all") class SuperWithArg { - void func1(U o) {} + void func1(U o) {} - class Sub extends SuperWithArg { - void context() { - Consumer f1 = super::func1; + class Sub extends SuperWithArg { + void context() { + Consumer f1 = super::func1; + } } - } } /** Type # instMethod. */ // UNBOUNDED(ReferenceMode.INVOKE, true), @SuppressWarnings("all") class Unbound { - T func1(T o) { - return o; - } + T func1(T o) { + return o; + } - void context() { - FunctionMR f1 = String::toString; - BiFunctionMR f2 = Unbound::func1; - BiFunctionMR f3 = - Unbound::func1; - } + void context() { + FunctionMR f1 = String::toString; + BiFunctionMR f2 = Unbound::func1; + BiFunctionMR f3 = + Unbound::func1; + } } @SuppressWarnings("all") abstract class UnboundWithArg { - abstract U func1(); + abstract U func1(); - void context() { - FunctionMR, String> f1 = UnboundWithArg::func1; - FunctionMR, String> f2 = UnboundWithArg::func1; - FunctionMR, String> f3 = UnboundWithArg::func1; - FunctionMR, String> f4 = UnboundWithArg::func1; - } + void context() { + FunctionMR, String> f1 = UnboundWithArg::func1; + FunctionMR, String> f2 = UnboundWithArg::func1; + FunctionMR, String> f3 = UnboundWithArg::func1; + FunctionMR, String> f4 = UnboundWithArg::func1; + } } /** Type # staticMethod. */ // STATIC(ReferenceMode.INVOKE, false), @SuppressWarnings("all") class Static { - static T func1(T o) { - return o; - } + static T func1(T o) { + return o; + } - void context() { - FunctionMR f1 = Static::func1; - FunctionMR f2 = Static::func1; - } + void context() { + FunctionMR f1 = Static::func1; + FunctionMR f2 = Static::func1; + } } /** Expr # instMethod. */ // BOUND(ReferenceMode.INVOKE, false), @SuppressWarnings("all") class Bound { - T func1(T o) { - return o; - } + T func1(T o) { + return o; + } - void context(Bound bound) { - FunctionMR f1 = bound::func1; - FunctionMR f2 = this::func1; - FunctionMR f3 = this::func1; - FunctionMR f4 = this::func1; - } + void context(Bound bound) { + FunctionMR f1 = bound::func1; + FunctionMR f2 = this::func1; + FunctionMR f3 = this::func1; + FunctionMR f4 = this::func1; + } } @SuppressWarnings("all") class BoundWithArg { - void func1(U param) {} + void func1(U param) {} - void context(BoundWithArg bound) { - Consumer f1 = bound::func1; - Consumer f2 = bound::func1; - } + void context(BoundWithArg bound) { + Consumer f1 = bound::func1; + Consumer f2 = bound::func1; + } } /** Inner # new. */ // IMPLICIT_INNER(ReferenceMode.NEW, false), @SuppressWarnings("all") class Outer { - void context(Outer other) { - Supplier f1 = Inner::new; - } + void context(Outer other) { + Supplier f1 = Inner::new; + } - class Inner extends Outer {} + class Inner extends Outer {} } @SuppressWarnings("all") class OuterWithArg { - void context() { - Supplier> f1 = Inner::new; - Supplier> f2 = Inner::new; - Supplier> f3 = Inner::new; - } + void context() { + Supplier> f1 = Inner::new; + Supplier> f2 = Inner::new; + Supplier> f3 = Inner::new; + } - class Inner extends OuterWithArg {} + class Inner extends OuterWithArg {} } /** Toplevel # new. */ // TOPLEVEL(ReferenceMode.NEW, false), @SuppressWarnings("all") class TopLevel { - TopLevel() {} + TopLevel() {} - TopLevel(T s) {} + TopLevel(T s) {} - void context() { - Supplier f1 = TopLevel::new; - FunctionMR f2 = TopLevel::new; - FunctionMR f3 = TopLevel::new; - } + void context() { + Supplier f1 = TopLevel::new; + FunctionMR f2 = TopLevel::new; + FunctionMR f3 = TopLevel::new; + } } @SuppressWarnings("all") class TopLevelWithArg { - TopLevelWithArg() {} + TopLevelWithArg() {} - TopLevelWithArg(U s) {} + TopLevelWithArg(U s) {} - void context() { - Supplier> f1 = TopLevelWithArg::new; - Supplier> f2 = TopLevelWithArg::new; - FunctionMR> f3 = TopLevelWithArg::new; - } + void context() { + Supplier> f1 = TopLevelWithArg::new; + Supplier> f2 = TopLevelWithArg::new; + FunctionMR> f3 = TopLevelWithArg::new; + } } /** ArrayType # new. */ @@ -172,9 +172,9 @@ void context() { @SuppressWarnings("all") class ArrayType { - void context() { - FunctionMR string = String[]::new; - FunctionMR clone = String[]::clone; - FunctionMR toString = String[]::toString; - } + void context() { + FunctionMR string = String[]::new; + FunctionMR clone = String[]::clone; + FunctionMR toString = String[]::toString; + } } diff --git a/framework/tests/all-systems/java8/memberref/Purity.java b/framework/tests/all-systems/java8/memberref/Purity.java index e7c7ef91134..c0182010c75 100644 --- a/framework/tests/all-systems/java8/memberref/Purity.java +++ b/framework/tests/all-systems/java8/memberref/Purity.java @@ -1,24 +1,24 @@ import org.checkerframework.dataflow.qual.*; interface PureFunc { - @Pure - String doNothing(); + @Pure + String doNothing(); } class TestPure { - static String myMethod() { - return ""; - } + static String myMethod() { + return ""; + } - @Pure - static String myPureMethod() { - return ""; - } + @Pure + static String myPureMethod() { + return ""; + } - void context() { - PureFunc f1 = TestPure::myPureMethod; - // :: error: (purity.invalid.methodref) - PureFunc f2 = TestPure::myMethod; - } + void context() { + PureFunc f1 = TestPure::myPureMethod; + // :: error: (purity.invalid.methodref) + PureFunc f2 = TestPure::myMethod; + } } diff --git a/framework/tests/all-systems/java8/memberref/Receivers.java b/framework/tests/all-systems/java8/memberref/Receivers.java index d20c39ccccd..53437bdc9f1 100644 --- a/framework/tests/all-systems/java8/memberref/Receivers.java +++ b/framework/tests/all-systems/java8/memberref/Receivers.java @@ -1,53 +1,53 @@ /** BoundR and unbound constraints. */ interface UnboundR { - void consume(/*1*/ UnboundR this, /*2*/ MyClass my, String s); + void consume(/*1*/ UnboundR this, /*2*/ MyClass my, String s); } interface BoundR { - void consume(/*4*/ BoundR this, String s); + void consume(/*4*/ BoundR this, String s); } interface SupplierR { - R supply(); + R supply(); } class MyClass { - void take(/*6*/ MyClass this, String s) {} + void take(/*6*/ MyClass this, String s) {} - void context(/*7*/ MyClass my) { - /*8*/ UnboundR u1 = /*9*/ MyClass::take; - // 2 <: 6 -- like an override - // No relation to 1 or 2 - // No relationship or check for 8 / 9? - // Need to check on this. + void context(/*7*/ MyClass my) { + /*8*/ UnboundR u1 = /*9*/ MyClass::take; + // 2 <: 6 -- like an override + // No relation to 1 or 2 + // No relationship or check for 8 / 9? + // Need to check on this. - u1.consume(my, ""); - // 7 <: 2 - // 8 <: 1 + u1.consume(my, ""); + // 7 <: 2 + // 8 <: 1 - /*10*/ BoundR b1 = /*11*/ my::take; - // 7 <: 6 -- like an invocation - // No Relationship for 10 / 11? + /*10*/ BoundR b1 = /*11*/ my::take; + // 7 <: 6 -- like an invocation + // No Relationship for 10 / 11? - b1.consume(""); - // 10 <: 4 - } + b1.consume(""); + // 10 <: 4 + } } /** Constraints for implicit inner constraints and super. */ @SuppressWarnings("lock") class OuterR { - class Inner { - Inner(/*1*/ OuterR OuterR.this) {} + class Inner { + Inner(/*1*/ OuterR OuterR.this) {} - void context() { - SupplierR o = OuterR.super::toString; + void context() { + SupplierR o = OuterR.super::toString; + } } - } - void context(/*2*/ OuterR this) { - // This one is unbound and needs an OuterR as a param - SupplierR f = /*4*/ Inner::new; - } + void context(/*2*/ OuterR this) { + // This one is unbound and needs an OuterR as a param + SupplierR f = /*4*/ Inner::new; + } } diff --git a/framework/tests/all-systems/java8/memberref/VarArgs.java b/framework/tests/all-systems/java8/memberref/VarArgs.java index 9ecc432b38f..4ed2022712e 100644 --- a/framework/tests/all-systems/java8/memberref/VarArgs.java +++ b/framework/tests/all-systems/java8/memberref/VarArgs.java @@ -1,40 +1,40 @@ interface VarArgsFunc { - void take(String... in); + void take(String... in); } interface ArrayFunc { - void take(String[] in); + void take(String[] in); } class VarArgsTest { - static void myMethod(String... in) {} + static void myMethod(String... in) {} - static void myMethodArray(String[] in) {} + static void myMethodArray(String[] in) {} - VarArgsFunc v1 = VarArgsTest::myMethod; - VarArgsFunc v2 = VarArgsTest::myMethodArray; + VarArgsFunc v1 = VarArgsTest::myMethod; + VarArgsFunc v2 = VarArgsTest::myMethodArray; - ArrayFunc v3 = VarArgsTest::myMethod; - ArrayFunc v4 = VarArgsTest::myMethodArray; + ArrayFunc v3 = VarArgsTest::myMethod; + ArrayFunc v4 = VarArgsTest::myMethodArray; } interface RegularFunc { - void take(Object o); + void take(Object o); } interface RegularFunc2 { - void take(Object o, String s); + void take(Object o, String s); } interface RegularFunc3 { - void take(Object o, String s, String s2); + void take(Object o, String s, String s2); } class MoreVarAgrgsTest { - static void myObjectArgArg(Object o, String... vararg) {} + static void myObjectArgArg(Object o, String... vararg) {} - RegularFunc v1 = MoreVarAgrgsTest::myObjectArgArg; - RegularFunc2 v2 = MoreVarAgrgsTest::myObjectArgArg; - RegularFunc3 v4 = MoreVarAgrgsTest::myObjectArgArg; + RegularFunc v1 = MoreVarAgrgsTest::myObjectArgArg; + RegularFunc2 v2 = MoreVarAgrgsTest::myObjectArgArg; + RegularFunc3 v4 = MoreVarAgrgsTest::myObjectArgArg; } diff --git a/framework/tests/all-systems/java8inference/ArrayInits.java b/framework/tests/all-systems/java8inference/ArrayInits.java index c3a0474a915..0893f416f76 100644 --- a/framework/tests/all-systems/java8inference/ArrayInits.java +++ b/framework/tests/all-systems/java8inference/ArrayInits.java @@ -3,7 +3,7 @@ import java.util.Arrays; public class ArrayInits { - void method() { - Object[] objects = new Object[] {Arrays.asList(1, 2, 3)}; - } + void method() { + Object[] objects = new Object[] {Arrays.asList(1, 2, 3)}; + } } diff --git a/framework/tests/all-systems/java8inference/BeamCrash1.java b/framework/tests/all-systems/java8inference/BeamCrash1.java index 97446ff8818..e111e6ae5d4 100644 --- a/framework/tests/all-systems/java8inference/BeamCrash1.java +++ b/framework/tests/all-systems/java8inference/BeamCrash1.java @@ -1,30 +1,31 @@ @SuppressWarnings("unchecked") public abstract class BeamCrash1 { - abstract Builder toBuilder(); + abstract Builder toBuilder(); - public void via( - Contextful> outputFn, - Contextful>> sinkFn) { - toBuilder().setSinkFn((Contextful) sinkFn).setOutputFn(outputFn); - } + public void via( + Contextful> outputFn, + Contextful>> sinkFn) { + toBuilder().setSinkFn((Contextful) sinkFn).setOutputFn(outputFn); + } - abstract static class Builder { + abstract static class Builder { - abstract Builder setSinkFn( - Contextful>> sink); + abstract Builder setSinkFn( + Contextful>> sink); - abstract Builder setOutputFn(Contextful> outputFn); + abstract Builder setOutputFn( + Contextful> outputFn); - abstract Write build(); - } + abstract Write build(); + } - public interface Sink {} + public interface Sink {} - public abstract static class Write {} + public abstract static class Write {} - public static final class Contextful { + public static final class Contextful { - public interface Fn {} - } + public interface Fn {} + } } diff --git a/framework/tests/all-systems/java8inference/BeamCrash3.java b/framework/tests/all-systems/java8inference/BeamCrash3.java index d13fb8e34e8..2e336932f51 100644 --- a/framework/tests/all-systems/java8inference/BeamCrash3.java +++ b/framework/tests/all-systems/java8inference/BeamCrash3.java @@ -1,25 +1,26 @@ @SuppressWarnings("all") // Just check for crashes. public class BeamCrash3 { - @SuppressWarnings({"unchecked", "rawtypes"}) - private CoderOrFailure inferCoderOrFail(PInput input, PTransform transform) { - return new CoderOrFailure<>(((PTransform) transform).getDefaultOutputCoder(input, this), null); - } + @SuppressWarnings({"unchecked", "rawtypes"}) + private CoderOrFailure inferCoderOrFail(PInput input, PTransform transform) { + return new CoderOrFailure<>( + ((PTransform) transform).getDefaultOutputCoder(input, this), null); + } - private static class CoderOrFailure { - public CoderOrFailure(Coder coder, String failure) {} - } + private static class CoderOrFailure { + public CoderOrFailure(Coder coder, String failure) {} + } - public interface PInput {} + public interface PInput {} - public abstract static class Coder {} + public abstract static class Coder {} - public abstract static class PTransform { + public abstract static class PTransform { - public Coder getDefaultOutputCoder(InputT input, BeamCrash3 output) { - throw new RuntimeException(); + public Coder getDefaultOutputCoder(InputT input, BeamCrash3 output) { + throw new RuntimeException(); + } } - } - public interface POutput {} + public interface POutput {} } diff --git a/framework/tests/all-systems/java8inference/BeamCrash4.java b/framework/tests/all-systems/java8inference/BeamCrash4.java index d030d2e608c..58d139fbc9c 100644 --- a/framework/tests/all-systems/java8inference/BeamCrash4.java +++ b/framework/tests/all-systems/java8inference/BeamCrash4.java @@ -1,52 +1,52 @@ @SuppressWarnings("unchecked") public class BeamCrash4 { - public abstract static class Write { - abstract static class Builder { - abstract Builder setSinkFn( - Contextful>> sink); - - abstract Builder setOutputFn( - Contextful> outputFn); - - abstract Write build(); + public abstract static class Write { + abstract static class Builder { + abstract Builder setSinkFn( + Contextful>> sink); + + abstract Builder setOutputFn( + Contextful> outputFn); + + abstract Write build(); + } + + abstract Builder toBuilder(); + + public Write via( + Contextful>> sinkFn) { + return toBuilder() + .setSinkFn((Contextful) sinkFn) + .setOutputFn(fn(BeamCrash4.identity())) + .build(); + } } - abstract Builder toBuilder(); + public interface SerializableFunction { - public Write via( - Contextful>> sinkFn) { - return toBuilder() - .setSinkFn((Contextful) sinkFn) - .setOutputFn(fn(BeamCrash4.identity())) - .build(); + OutputT apply(InputT input); } - } - - public interface SerializableFunction { - - OutputT apply(InputT input); - } - public static SerializableFunction identity() { - return new Identity<>(); - } + public static SerializableFunction identity() { + return new Identity<>(); + } - private static class Identity implements SerializableFunction { - @Override - public T apply(T input) { - return input; + private static class Identity implements SerializableFunction { + @Override + public T apply(T input) { + return input; + } } - } - public interface Sink {} + public interface Sink {} - public static final class Contextful { + public static final class Contextful { - public interface Fn {} - } + public interface Fn {} + } - public static Contextful> fn( - final SerializableFunction fn) { - throw new RuntimeException(); - } + public static Contextful> fn( + final SerializableFunction fn) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug1.java b/framework/tests/all-systems/java8inference/Bug1.java index 483e0795f00..3848beb7ac7 100644 --- a/framework/tests/all-systems/java8inference/Bug1.java +++ b/framework/tests/all-systems/java8inference/Bug1.java @@ -5,14 +5,14 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug1 { - public void method1(Map, ? extends B> map) { - Map, B> copy = new LinkedHashMap<>(map); - for (Map.Entry, B> entry : copy.entrySet()) { - cast(entry.getKey(), entry.getValue()); + public void method1(Map, ? extends B> map) { + Map, B> copy = new LinkedHashMap<>(map); + for (Map.Entry, B> entry : copy.entrySet()) { + cast(entry.getKey(), entry.getValue()); + } } - } - private static T cast(Class type, X value) { - throw new RuntimeException(); - } + private static T cast(Class type, X value) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug10.java b/framework/tests/all-systems/java8inference/Bug10.java index 6b9ad5053f0..cc48d706049 100644 --- a/framework/tests/all-systems/java8inference/Bug10.java +++ b/framework/tests/all-systems/java8inference/Bug10.java @@ -6,44 +6,44 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug10 { - public static Collector> least(int k, Comparator comparator) { - return Collector.of( - () -> TopKSelector.least(k, comparator), - TopKSelector::offer, - TopKSelector::combine, - TopKSelector::topK, - Collector.Characteristics.UNORDERED); - } - - static final class TopKSelector { - TopKSelector combine(TopKSelector other) { - throw new RuntimeException(); + public static Collector> least(int k, Comparator comparator) { + return Collector.of( + () -> TopKSelector.least(k, comparator), + TopKSelector::offer, + TopKSelector::combine, + TopKSelector::topK, + Collector.Characteristics.UNORDERED); } - public static > TopKSelector least(int k) { - throw new RuntimeException(); - } + static final class TopKSelector { + TopKSelector combine(TopKSelector other) { + throw new RuntimeException(); + } - public static > TopKSelector greatest(int k) { - throw new RuntimeException(); - } + public static > TopKSelector least(int k) { + throw new RuntimeException(); + } - public static TopKSelector least(int k, Comparator comparator) { - return new TopKSelector(comparator, k); - } + public static > TopKSelector greatest(int k) { + throw new RuntimeException(); + } - public static TopKSelector greatest(int k, Comparator comparator) { - throw new RuntimeException(); - } + public static TopKSelector least(int k, Comparator comparator) { + return new TopKSelector(comparator, k); + } - private TopKSelector(Comparator comparator, int k) {} + public static TopKSelector greatest(int k, Comparator comparator) { + throw new RuntimeException(); + } - public void offer(T elem) { - throw new RuntimeException(); - } + private TopKSelector(Comparator comparator, int k) {} + + public void offer(T elem) { + throw new RuntimeException(); + } - public List topK() { - throw new RuntimeException(); + public List topK() { + throw new RuntimeException(); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Bug11.java b/framework/tests/all-systems/java8inference/Bug11.java index ddc2bfb22af..f167c5d66b2 100644 --- a/framework/tests/all-systems/java8inference/Bug11.java +++ b/framework/tests/all-systems/java8inference/Bug11.java @@ -7,16 +7,16 @@ @SuppressWarnings("all") // Check for crashes. public class Bug11 { - public static MyMap copyOf(Map map) { - @SuppressWarnings("unchecked") - MyMap kvMap = (MyMap) copyOfEnumMap((EnumMap) map); - return kvMap; - } + public static MyMap copyOf(Map map) { + @SuppressWarnings("unchecked") + MyMap kvMap = (MyMap) copyOfEnumMap((EnumMap) map); + return kvMap; + } - private static , V2> MyMap copyOfEnumMap( - EnumMap original) { - throw new RuntimeException(); - } + private static , V2> MyMap copyOfEnumMap( + EnumMap original) { + throw new RuntimeException(); + } - public abstract static class MyMap implements Map, Serializable {} + public abstract static class MyMap implements Map, Serializable {} } diff --git a/framework/tests/all-systems/java8inference/Bug12.java b/framework/tests/all-systems/java8inference/Bug12.java index 69402ad5299..0f8fcb5924c 100644 --- a/framework/tests/all-systems/java8inference/Bug12.java +++ b/framework/tests/all-systems/java8inference/Bug12.java @@ -4,14 +4,15 @@ public class Bug12 { - private static Map getOrCreateNodes( - Map existing, Map created) { - return firstNonNull(existing, created); - } + private static Map getOrCreateNodes( + Map existing, + Map created) { + return firstNonNull(existing, created); + } - public static T firstNonNull(T first, T second) { - throw new RuntimeException(); - } + public static T firstNonNull(T first, T second) { + throw new RuntimeException(); + } - private static class LockGraphNode {} + private static class LockGraphNode {} } diff --git a/framework/tests/all-systems/java8inference/Bug13.java b/framework/tests/all-systems/java8inference/Bug13.java index 2f5c99417e8..4a8ac0aeb04 100644 --- a/framework/tests/all-systems/java8inference/Bug13.java +++ b/framework/tests/all-systems/java8inference/Bug13.java @@ -6,31 +6,31 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug13 { - public static class MyClass extends MySuperClass { - static Stream stream(MyClass s, Iterable iterable) { - throw new RuntimeException(); + public static class MyClass extends MySuperClass { + static Stream stream(MyClass s, Iterable iterable) { + throw new RuntimeException(); + } + + public void method(final Iterable iterable) { + Spliterator x = Stream.generate(() -> iterable).flatMap(super::stream).spliterator(); + } } - public void method(final Iterable iterable) { - Spliterator x = Stream.generate(() -> iterable).flatMap(super::stream).spliterator(); - } - } + public static class MySuperClass { + Stream stream(Iterable iterable) { + throw new RuntimeException(); + } - public static class MySuperClass { - Stream stream(Iterable iterable) { - throw new RuntimeException(); + static Stream stream(MySuperClass s, Iterable iterable) { + throw new RuntimeException(); + } } - static Stream stream(MySuperClass s, Iterable iterable) { - throw new RuntimeException(); + public static void method(final Iterable iterable, MyClass myClass) { + Spliterator x = Stream.generate(() -> iterable).flatMap(myClass::stream).spliterator(); } - } - - public static void method(final Iterable iterable, MyClass myClass) { - Spliterator x = Stream.generate(() -> iterable).flatMap(myClass::stream).spliterator(); - } - public static Stream stream(Iterable iterable) { - throw new RuntimeException(); - } + public static Stream stream(Iterable iterable) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug14.java b/framework/tests/all-systems/java8inference/Bug14.java index 04ac6bb26d6..5aeac3db099 100644 --- a/framework/tests/all-systems/java8inference/Bug14.java +++ b/framework/tests/all-systems/java8inference/Bug14.java @@ -7,43 +7,43 @@ @SuppressWarnings({"unchecked", "all"}) public class Bug14 { - private static final Collector> TO_IMMUTABLE_LIST = - Collector.of( - ImmutableList::builder, - ImmutableList.Builder::add, - ImmutableList.Builder::combine, - ImmutableList.Builder::build); - - public abstract static class ImmutableList extends ImmutableCollection - implements List, RandomAccess { - public static final class Builder { - - public Builder add(E element) { - return this; - } - - public Builder add(E... elements) { - return this; - } - - public Builder addAll(Iterator elements) { - return this; - } - - public ImmutableList build() { - throw new RuntimeException(); - } - - Builder combine(Builder builder) { - return this; - } + private static final Collector> TO_IMMUTABLE_LIST = + Collector.of( + ImmutableList::builder, + ImmutableList.Builder::add, + ImmutableList.Builder::combine, + ImmutableList.Builder::build); + + public abstract static class ImmutableList extends ImmutableCollection + implements List, RandomAccess { + public static final class Builder { + + public Builder add(E element) { + return this; + } + + public Builder add(E... elements) { + return this; + } + + public Builder addAll(Iterator elements) { + return this; + } + + public ImmutableList build() { + throw new RuntimeException(); + } + + Builder combine(Builder builder) { + return this; + } + } + + public static Builder builder() { + return new Builder(); + } } - public static Builder builder() { - return new Builder(); - } - } - - public abstract static class ImmutableCollection extends AbstractCollection - implements Serializable {} + public abstract static class ImmutableCollection extends AbstractCollection + implements Serializable {} } diff --git a/framework/tests/all-systems/java8inference/Bug15.java b/framework/tests/all-systems/java8inference/Bug15.java index 43484fabf2c..14a9be01312 100644 --- a/framework/tests/all-systems/java8inference/Bug15.java +++ b/framework/tests/all-systems/java8inference/Bug15.java @@ -4,11 +4,11 @@ @SuppressWarnings("all") // check for crashes public class Bug15 { - public void putAll(Entry, B> entry) { - cast(entry.getKey(), entry.getValue()); - } + public void putAll(Entry, B> entry) { + cast(entry.getKey(), entry.getValue()); + } - private static T cast(Class type, F value) { - throw new RuntimeException(); - } + private static T cast(Class type, F value) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug16.java b/framework/tests/all-systems/java8inference/Bug16.java index 6260616307f..d202049a99f 100644 --- a/framework/tests/all-systems/java8inference/Bug16.java +++ b/framework/tests/all-systems/java8inference/Bug16.java @@ -2,20 +2,20 @@ public class Bug16 { - public interface Interface {} + public interface Interface {} - private static class Implementation implements Interface { - Implementation(Interface delegate, Interface inverse) {} + private static class Implementation implements Interface { + Implementation(Interface delegate, Interface inverse) {} - Implementation(Interface inverse, int o) {} + Implementation(Interface inverse, int o) {} - Implementation(Interface delegate) {} + Implementation(Interface delegate) {} - void test(Interface param) { - Interface inverse1 = new Implementation<>(param, this); - Interface inverse1b = new Implementation(param, this); - Interface inverse2 = new Implementation<>(param); - Interface inverse3 = new Implementation<>(this, 1); + void test(Interface param) { + Interface inverse1 = new Implementation<>(param, this); + Interface inverse1b = new Implementation(param, this); + Interface inverse2 = new Implementation<>(param); + Interface inverse3 = new Implementation<>(this, 1); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Bug17.java b/framework/tests/all-systems/java8inference/Bug17.java index 8c7eec07ab6..1387f341046 100644 --- a/framework/tests/all-systems/java8inference/Bug17.java +++ b/framework/tests/all-systems/java8inference/Bug17.java @@ -5,84 +5,84 @@ @SuppressWarnings("all") // Just check for crashes. public abstract class Bug17 implements Map { - public MapBug17 asMultimap(MapBug17 multimapView) { - if (isEmpty()) { - return MapBug17.of(); + public MapBug17 asMultimap(MapBug17 multimapView) { + if (isEmpty()) { + return MapBug17.of(); + } + MapBug17 result = multimapView; + return (result == null) + ? (multimapView = new MapBug17<>(new SubSubBug17(), size(), null)) + : result; } - MapBug17 result = multimapView; - return (result == null) - ? (multimapView = new MapBug17<>(new SubSubBug17(), size(), null)) - : result; - } - public static class MapBug17 { - MapBug17(Bug17> map, int size, Comparator valueComparator) {} + public static class MapBug17 { + MapBug17(Bug17> map, int size, Comparator valueComparator) {} - public static MapBug17 of() { - throw new RuntimeException(); + public static MapBug17 of() { + throw new RuntimeException(); + } } - } - abstract static class SubBug17 extends Bug17 {} + abstract static class SubBug17 extends Bug17 {} - static class MyClass {} + static class MyClass {} - private class SubSubBug17 extends SubBug17> { + private class SubSubBug17 extends SubBug17> { - @Override - public int size() { - return 0; - } + @Override + public int size() { + return 0; + } - @Override - public boolean isEmpty() { - return false; - } + @Override + public boolean isEmpty() { + return false; + } - @Override - public boolean containsKey(Object key) { - return false; - } + @Override + public boolean containsKey(Object key) { + return false; + } - @Override - public boolean containsValue(Object value) { - return false; - } + @Override + public boolean containsValue(Object value) { + return false; + } - @Override - public MyClass get(Object key) { - return null; - } + @Override + public MyClass get(Object key) { + return null; + } - @Override - public MyClass put(KK key, MyClass value) { - return null; - } + @Override + public MyClass put(KK key, MyClass value) { + return null; + } - @Override - public MyClass remove(Object key) { - return null; - } + @Override + public MyClass remove(Object key) { + return null; + } - @Override - public void putAll(Map> m) {} + @Override + public void putAll(Map> m) {} - @Override - public void clear() {} + @Override + public void clear() {} - @Override - public Set keySet() { - return null; - } + @Override + public Set keySet() { + return null; + } - @Override - public Collection> values() { - return null; - } + @Override + public Collection> values() { + return null; + } - @Override - public Set>> entrySet() { - return null; + @Override + public Set>> entrySet() { + return null; + } } - } } diff --git a/framework/tests/all-systems/java8inference/Bug2.java b/framework/tests/all-systems/java8inference/Bug2.java index 933e3f74a1e..09146939724 100644 --- a/framework/tests/all-systems/java8inference/Bug2.java +++ b/framework/tests/all-systems/java8inference/Bug2.java @@ -6,21 +6,21 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug2 { - public ConcurrentMap makeMap() { - return Bug2.create(this); - } + public ConcurrentMap makeMap() { + return Bug2.create(this); + } - static MyMap, ?> create(Bug2 builder) { - throw new RuntimeException(); - } + static MyMap, ?> create(Bug2 builder) { + throw new RuntimeException(); + } - abstract static class MyMap< - E, F, G extends MyMap.MyEntry, H extends MyMap.MyLock> - extends AbstractMap implements ConcurrentMap, Serializable { + abstract static class MyMap< + E, F, G extends MyMap.MyEntry, H extends MyMap.MyLock> + extends AbstractMap implements ConcurrentMap, Serializable { - interface MyEntry> {} + interface MyEntry> {} - abstract static class MyLock, O extends MyLock> - extends ReentrantLock {} - } + abstract static class MyLock, O extends MyLock> + extends ReentrantLock {} + } } diff --git a/framework/tests/all-systems/java8inference/Bug3.java b/framework/tests/all-systems/java8inference/Bug3.java index 453da07b7eb..1051e12747c 100644 --- a/framework/tests/all-systems/java8inference/Bug3.java +++ b/framework/tests/all-systems/java8inference/Bug3.java @@ -6,23 +6,23 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug3 { - public abstract static class MySet implements Set { + public abstract static class MySet implements Set { - public static MySet of() { - throw new RuntimeException(""); - } + public static MySet of() { + throw new RuntimeException(""); + } - public static MySet of(E e) { - throw new RuntimeException(""); + public static MySet of(E e) { + throw new RuntimeException(""); + } } - } - @SuppressWarnings({"rawtypes", "unchecked"}) - static MySet asMySet(EnumSet set) { - return MySet.of(getElement(set)); - } + @SuppressWarnings({"rawtypes", "unchecked"}) + static MySet asMySet(EnumSet set) { + return MySet.of(getElement(set)); + } - public static T getElement(Iterable iterable) { - throw new RuntimeException(); - } + public static T getElement(Iterable iterable) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug4.java b/framework/tests/all-systems/java8inference/Bug4.java index 1f8d2442c36..828055c86ac 100644 --- a/framework/tests/all-systems/java8inference/Bug4.java +++ b/framework/tests/all-systems/java8inference/Bug4.java @@ -6,11 +6,11 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug4 { - Type resolveInternal(TypeVariable var, Type[] types) { - return method(var.getGenericDeclaration(), var.getName(), types); - } + Type resolveInternal(TypeVariable var, Type[] types) { + return method(var.getGenericDeclaration(), var.getName(), types); + } - static TypeVariable method(D d, String n, Type... bounds) { - throw new RuntimeException(); - } + static TypeVariable method(D d, String n, Type... bounds) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug5.java b/framework/tests/all-systems/java8inference/Bug5.java index e609304206b..8b242783f6c 100644 --- a/framework/tests/all-systems/java8inference/Bug5.java +++ b/framework/tests/all-systems/java8inference/Bug5.java @@ -6,17 +6,17 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug5 { - boolean apply(Object key, V value, MyPredicate> predicate) { - @SuppressWarnings("unchecked") - K k = (K) key; - return predicate.apply(immutableEntry(k, value)); - } + boolean apply(Object key, V value, MyPredicate> predicate) { + @SuppressWarnings("unchecked") + K k = (K) key; + return predicate.apply(immutableEntry(k, value)); + } - public static Map.Entry immutableEntry(K key, V value) { - throw new RuntimeException(); - } + public static Map.Entry immutableEntry(K key, V value) { + throw new RuntimeException(); + } - public interface MyPredicate extends Predicate { - boolean apply(T input); - } + public interface MyPredicate extends Predicate { + boolean apply(T input); + } } diff --git a/framework/tests/all-systems/java8inference/Bug6.java b/framework/tests/all-systems/java8inference/Bug6.java index 9a6e41d6c2f..473a8922924 100644 --- a/framework/tests/all-systems/java8inference/Bug6.java +++ b/framework/tests/all-systems/java8inference/Bug6.java @@ -7,26 +7,26 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug6 { - public static Iterable method(final Iterable iterable) { - return new Iterable() { - @Override - public Iterator iterator() { - throw new RuntimeException(); - } + public static Iterable method(final Iterable iterable) { + return new Iterable() { + @Override + public Iterator iterator() { + throw new RuntimeException(); + } - @Override - public void forEach(Consumer action) { - throw new RuntimeException(); - } + @Override + public void forEach(Consumer action) { + throw new RuntimeException(); + } - @Override - public Spliterator spliterator() { - return Stream.generate(() -> iterable).flatMap(Bug6::stream).spliterator(); - } - }; - } + @Override + public Spliterator spliterator() { + return Stream.generate(() -> iterable).flatMap(Bug6::stream).spliterator(); + } + }; + } - public static Stream stream(Iterable iterable) { - throw new RuntimeException(); - } + public static Stream stream(Iterable iterable) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Bug7.java b/framework/tests/all-systems/java8inference/Bug7.java index d646627a69d..496b516eca3 100644 --- a/framework/tests/all-systems/java8inference/Bug7.java +++ b/framework/tests/all-systems/java8inference/Bug7.java @@ -7,32 +7,33 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug7 { - static Collector> toMap( - Function keyFunction, - Function valueFunction) { - return Collector.of( - MyMap.Builder::new, - (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), - MyMap.Builder::combine, - MyMap.Builder::build); - } + static Collector> toMap( + Function keyFunction, + Function valueFunction) { + return Collector.of( + MyMap.Builder::new, + (builder, input) -> + builder.put(keyFunction.apply(input), valueFunction.apply(input)), + MyMap.Builder::combine, + MyMap.Builder::build); + } - public abstract static class MyMap implements Map, Serializable { - public static class Builder { + public abstract static class MyMap implements Map, Serializable { + public static class Builder { - public Builder() {} + public Builder() {} - public Builder put(K key, V value) { - throw new RuntimeException(); - } + public Builder put(K key, V value) { + throw new RuntimeException(); + } - Builder combine(Builder other) { - throw new RuntimeException(); - } + Builder combine(Builder other) { + throw new RuntimeException(); + } - public MyMap build() { - throw new RuntimeException(); - } + public MyMap build() { + throw new RuntimeException(); + } + } } - } } diff --git a/framework/tests/all-systems/java8inference/Bug8.java b/framework/tests/all-systems/java8inference/Bug8.java index 7b6104c0348..c60497acfa8 100644 --- a/framework/tests/all-systems/java8inference/Bug8.java +++ b/framework/tests/all-systems/java8inference/Bug8.java @@ -8,68 +8,70 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug8 { - static Collector> toImmutableMap( - Function keyFunction, - Function valueFunction) { - return Collector.of( - MyMap.Builder::new, - (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), - MyMap.Builder::combine, - MyMap.Builder::build); - } + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + return Collector.of( + MyMap.Builder::new, + (builder, input) -> + builder.put(keyFunction.apply(input), valueFunction.apply(input)), + MyMap.Builder::combine, + MyMap.Builder::build); + } - static Collector> toImmutableBiMap( - Function keyFunction, - Function valueFunction) { - return Collector.of( - MyBiMap.Builder::new, - (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), - MyBiMap.Builder::combine, - MyBiMap.Builder::build, - new Collector.Characteristics[0]); - } + static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + return Collector.of( + MyBiMap.Builder::new, + (builder, input) -> + builder.put(keyFunction.apply(input), valueFunction.apply(input)), + MyBiMap.Builder::combine, + MyBiMap.Builder::build, + new Collector.Characteristics[0]); + } - abstract static class ShimMap extends MyMap {} + abstract static class ShimMap extends MyMap {} - public interface BiMap extends Map {} + public interface BiMap extends Map {} - public abstract static class MyBiMap extends ShimMap implements BiMap { - public static final class Builder extends MyMap.Builder { - public Builder() {} + public abstract static class MyBiMap extends ShimMap implements BiMap { + public static final class Builder extends MyMap.Builder { + public Builder() {} - @Override - public MyBiMap.Builder put(K key, V value) { - throw new RuntimeException(); - } + @Override + public MyBiMap.Builder put(K key, V value) { + throw new RuntimeException(); + } - @Override - MyBiMap.Builder combine(MyMap.Builder builder) { - super.combine(builder); - return this; - } + @Override + MyBiMap.Builder combine(MyMap.Builder builder) { + super.combine(builder); + return this; + } - @Override - public MyBiMap build() { - throw new RuntimeException(); - } + @Override + public MyBiMap build() { + throw new RuntimeException(); + } + } } - } - public abstract static class MyMap implements Map, Serializable { - public static class Builder { - public Builder() {} + public abstract static class MyMap implements Map, Serializable { + public static class Builder { + public Builder() {} - public MyMap.Builder put(K key, V value) { - throw new RuntimeException(); - } + public MyMap.Builder put(K key, V value) { + throw new RuntimeException(); + } - MyMap.Builder combine(MyMap.Builder other) { - throw new RuntimeException(); - } + MyMap.Builder combine(MyMap.Builder other) { + throw new RuntimeException(); + } - public MyMap build() { - throw new RuntimeException(); - } + public MyMap build() { + throw new RuntimeException(); + } + } } - } } diff --git a/framework/tests/all-systems/java8inference/Bug9.java b/framework/tests/all-systems/java8inference/Bug9.java index 88f052359a2..9ef255ed81a 100644 --- a/framework/tests/all-systems/java8inference/Bug9.java +++ b/framework/tests/all-systems/java8inference/Bug9.java @@ -7,21 +7,22 @@ @SuppressWarnings("all") // Just check for crashes. public class Bug9 { - private transient Map> map; + private transient Map> map; - Spliterator valueSpliterator() { - return flatMap(map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size()); - } + Spliterator valueSpliterator() { + return flatMap( + map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size()); + } - static Spliterator flatMap( - Spliterator fromSpliterator, - Function> function, - int topCharacteristics, - long topSize) { - throw new RuntimeException(); - } + static Spliterator flatMap( + Spliterator fromSpliterator, + Function> function, + int topCharacteristics, + long topSize) { + throw new RuntimeException(); + } - public int size() { - throw new RuntimeException(); - } + public int size() { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/CollectorCollect.java b/framework/tests/all-systems/java8inference/CollectorCollect.java index 8cf91e61438..73f3e21a736 100644 --- a/framework/tests/all-systems/java8inference/CollectorCollect.java +++ b/framework/tests/all-systems/java8inference/CollectorCollect.java @@ -1,105 +1,106 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Collection; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("all") // just check for crashes. public class CollectorCollect { - static - Collector> flatteningToImmutableListMultimap( - Function keyFunction, - Function> valuesFunction) { - return Collectors.collectingAndThen( - flatteningToMultimap( - input -> checkNotNull(keyFunction.apply(input)), - input -> valuesFunction.apply(input).peek(CollectorCollect::checkNotNull), - MultimapBuilder.linkedHashKeys().arrayListValues()::build), - ImmutableListMultimap::copyOf); - } - - static < - T extends @Nullable Object, - K extends @Nullable Object, - V extends @Nullable Object, - M extends MultimapBuilder.Multimap> - Collector flatteningToMultimap( - Function keyFunction, - Function> valueFunction, - Supplier multimapSupplier) { - checkNotNull(keyFunction); - checkNotNull(valueFunction); - checkNotNull(multimapSupplier); - return Collector.of( - multimapSupplier, - (multimap, input) -> { - K key = keyFunction.apply(input); - Collection valuesForKey = multimap.get(key); - valueFunction.apply(input).forEachOrdered(valuesForKey::add); - }, - (multimap1, multimap2) -> { - multimap1.putAll(multimap2); - return multimap1; - }); - } - - public static T checkNotNull(T reference) { - if (reference == null) { - throw new NullPointerException(); + static + Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + return Collectors.collectingAndThen( + flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(CollectorCollect::checkNotNull), + MultimapBuilder.linkedHashKeys().arrayListValues()::build), + ImmutableListMultimap::copyOf); } - return reference; - } - public abstract static class MultimapBuilder< - K0 extends @Nullable Object, V0 extends @Nullable Object> { + static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends MultimapBuilder.Multimap> + Collector flatteningToMultimap( + Function keyFunction, + Function> valueFunction, + Supplier multimapSupplier) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> { + K key = keyFunction.apply(input); + Collection valuesForKey = multimap.get(key); + valueFunction.apply(input).forEachOrdered(valuesForKey::add); + }, + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); + } - public static MultimapBuilderWithKeys<@Nullable Object> linkedHashKeys() { - throw new RuntimeException(); + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; } - public abstract Multimap build(); + public abstract static class MultimapBuilder< + K0 extends @Nullable Object, V0 extends @Nullable Object> { - public interface ListMultimap - extends Multimap {} + public static MultimapBuilderWithKeys<@Nullable Object> linkedHashKeys() { + throw new RuntimeException(); + } - public interface Multimap { + public abstract Multimap build(); - Collection get(K key); + public interface ListMultimap + extends Multimap {} - void putAll(Multimap multimap2); - } + public interface Multimap { - public abstract static class ListMultimapBuilder< - K0 extends @Nullable Object, V0 extends @Nullable Object> - extends MultimapBuilder {} + Collection get(K key); - public abstract static class MultimapBuilderWithKeys { + void putAll(Multimap multimap2); + } - public ListMultimapBuilder arrayListValues() { - throw new RuntimeException(); - } - } - } + public abstract static class ListMultimapBuilder< + K0 extends @Nullable Object, V0 extends @Nullable Object> + extends MultimapBuilder {} - public static class ImmutableListMultimap extends ImmutableMultimap - implements CollectorCollect.MultimapBuilder.ListMultimap { + public abstract static class MultimapBuilderWithKeys { - public static ImmutableListMultimap copyOf( - CollectorCollect.MultimapBuilder.Multimap multimap) { - throw new RuntimeException(); + public ListMultimapBuilder arrayListValues() { + throw new RuntimeException(); + } + } } - @Override - public Collection get(K key) { - return null; - } + public static class ImmutableListMultimap extends ImmutableMultimap + implements CollectorCollect.MultimapBuilder.ListMultimap { + + public static ImmutableListMultimap copyOf( + CollectorCollect.MultimapBuilder.Multimap multimap) { + throw new RuntimeException(); + } - @Override - public void putAll(MultimapBuilder.Multimap multimap2) {} - } + @Override + public Collection get(K key) { + return null; + } + + @Override + public void putAll(MultimapBuilder.Multimap multimap2) {} + } - public abstract static class ImmutableMultimap {} + public abstract static class ImmutableMultimap {} } diff --git a/framework/tests/all-systems/java8inference/CollectorsToList.java b/framework/tests/all-systems/java8inference/CollectorsToList.java index ea8c8cee5c2..843b26fe562 100644 --- a/framework/tests/all-systems/java8inference/CollectorsToList.java +++ b/framework/tests/all-systems/java8inference/CollectorsToList.java @@ -1,33 +1,34 @@ // Test case for issue #979: // https://github.com/typetools/checker-framework/issues/979 +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.checkerframework.checker.nullness.qual.Nullable; public class CollectorsToList { - // See checker/tests/i18n-formatter/I18nFormatCollectorsToList.java - @SuppressWarnings({ - "i18n:methodref.param", // true postive, see - // checker/tests/i18n-formatter/I18nFormatCollectorsToList.java - "lock:methodref.receiver.bound" - }) - void m(List strings) { - Stream s = strings.stream(); + // See checker/tests/i18n-formatter/I18nFormatCollectorsToList.java + @SuppressWarnings({ + "i18n:methodref.param", // true postive, see + // checker/tests/i18n-formatter/I18nFormatCollectorsToList.java + "lock:methodref.receiver.bound" + }) + void m(List strings) { + Stream s = strings.stream(); - // This works: - List collectedStrings1 = s.collect(Collectors.toList()); - // This works: - List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); - // This works: - @SuppressWarnings("nullness") - List collectedStrings3 = s.collect(Collectors.toList()); + // This works: + List collectedStrings1 = s.collect(Collectors.toList()); + // This works: + List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList()); + // This works: + @SuppressWarnings("nullness") + List collectedStrings3 = s.collect(Collectors.toList()); - // This assignment issues a warning due to incompatible types: - List collectedStrings = s.collect(Collectors.toList()); + // This assignment issues a warning due to incompatible types: + List collectedStrings = s.collect(Collectors.toList()); - collectedStrings.forEach(System.out::println); - } + collectedStrings.forEach(System.out::println); + } } diff --git a/framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java b/framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java index 356efce9656..c86c02bc9a3 100644 --- a/framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java +++ b/framework/tests/all-systems/java8inference/CrashWithSuperWildcard.java @@ -3,55 +3,55 @@ import java.util.Set; public class CrashWithSuperWildcard { - ImmutableList method(Map map) { - return sortKeysByValue(map, Ordering1.natural().reverse()); - } + ImmutableList method(Map map) { + return sortKeysByValue(map, Ordering1.natural().reverse()); + } - private static ImmutableList sortKeysByValue( - Map map, Comparator valueComparator) { - throw new RuntimeException(); - } + private static ImmutableList sortKeysByValue( + Map map, Comparator valueComparator) { + throw new RuntimeException(); + } - static class ImmutableList {} + static class ImmutableList {} - static class Ordering1 implements Comparator { + static class Ordering1 implements Comparator { - public static Ordering1 natural() { - throw new RuntimeException(); - } + public static Ordering1 natural() { + throw new RuntimeException(); + } - public Ordering1 reverse() { - throw new RuntimeException(); - } + public Ordering1 reverse() { + throw new RuntimeException(); + } - @Override - public int compare(T o1, T o2) { - return 0; + @Override + public int compare(T o1, T o2) { + return 0; + } } - } - private static ImmutableList sortKeysByValue( - Set map, Comparator valueComparator) { - throw new RuntimeException(); - } + private static ImmutableList sortKeysByValue( + Set map, Comparator valueComparator) { + throw new RuntimeException(); + } - ImmutableList method2(Set map) { - return sortKeysByValue(map, Ordering2.natural().reverse()); - } + ImmutableList method2(Set map) { + return sortKeysByValue(map, Ordering2.natural().reverse()); + } - static class Ordering2 implements Comparator { + static class Ordering2 implements Comparator { - public static > Ordering2 natural() { - throw new RuntimeException(); - } + public static > Ordering2 natural() { + throw new RuntimeException(); + } - public Ordering2 reverse() { - throw new RuntimeException(); - } + public Ordering2 reverse() { + throw new RuntimeException(); + } - @Override - public int compare(T o1, T o2) { - return 0; + @Override + public int compare(T o1, T o2) { + return 0; + } } - } } diff --git a/framework/tests/all-systems/java8inference/GuavaCrash.java b/framework/tests/all-systems/java8inference/GuavaCrash.java index eea2067df16..0abe2579987 100644 --- a/framework/tests/all-systems/java8inference/GuavaCrash.java +++ b/framework/tests/all-systems/java8inference/GuavaCrash.java @@ -1,3 +1,5 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.lang.reflect.Method; import java.util.Comparator; import java.util.LinkedHashMap; @@ -7,119 +9,127 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; -import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("all") // Just check for crashes. public class GuavaCrash { - static class ImmutableMap { - public static ImmutableMap copyOf(Map map) { - throw new RuntimeException(); + static class ImmutableMap { + public static ImmutableMap copyOf(Map map) { + throw new RuntimeException(); + } + } + + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + Function, ImmutableMap> finisher = ImmutableMap::copyOf; + Supplier> mapFactory = LinkedHashMap::new; + return collectingAndThen( + toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), + ImmutableMap::copyOf); + } + + public static > Collector toMap( + Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapFactory) { + throw new RuntimeException(); + } + + public static Collector collectingAndThen( + Collector downstream, Function finisher) { + throw new RuntimeException(); + } + + static class Table {} + + private static < + R extends @Nullable Object, + C extends @Nullable Object, + V extends @Nullable Object> + void mergeTables( + Table table, + R row, + C column, + V value, + BinaryOperator mergeFunction) { + throw new RuntimeException(); + } + + static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V extends @Nullable Object, + I extends Table> + Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction, + java.util.function.Supplier tableSupplier) { + + return Collector.of( + tableSupplier, + (table, input) -> + mergeTables( + table, + rowFunction.apply(input), + columnFunction.apply(input), + valueFunction.apply(input), + mergeFunction), + (table1, table2) -> { + // for (Table.Cell cell2 : table2.cellSet()) { + // mergeTables( + // table1, cell2.getRowKey(), cell2.getColumnKey(), + // cell2.getValue(), mergeFunction); + // } + return table1; + }); + } + + void subcrash(Method method, Class p) { + + checkArgument( + !p.isPrimitive(), + "@Subscribe method %s's parameter is %s. " + + "Subscriber methods cannot accept primitives. " + + "Consider changing the parameter to %s.", + method, + p.getName(), + wrap(p).getSimpleName()); + } + + void crash(Method method) { + Class[] parameterTypes = method.getParameterTypes(); + checkArgument( + !parameterTypes[0].isPrimitive(), + "@Subscribe method %s's parameter is %s. " + + "Subscriber methods cannot accept primitives. " + + "Consider changing the parameter to %s.", + method, + parameterTypes[0].getName(), + wrap(parameterTypes[0]).getSimpleName()); + } + + public static Class wrap(Class type) { + throw new RuntimeException(); + } + + public static void checkArgument( + boolean expression, + String errorMessageTemplate, + @Nullable Object... errorMessageArgs) {} + + public static void difference( + SortedMap left, Map right) { + Comparator comparator = orNaturalOrder(left.comparator()); + } + + static Comparator orNaturalOrder( + Comparator comparator) { + throw new RuntimeException(); } - } - - public static - Collector> toImmutableMap( - Function keyFunction, - Function valueFunction, - BinaryOperator mergeFunction) { - Function, ImmutableMap> finisher = ImmutableMap::copyOf; - Supplier> mapFactory = LinkedHashMap::new; - return collectingAndThen( - toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), ImmutableMap::copyOf); - } - - public static > Collector toMap( - Function keyMapper, - Function valueMapper, - BinaryOperator mergeFunction, - Supplier mapFactory) { - throw new RuntimeException(); - } - - public static Collector collectingAndThen( - Collector downstream, Function finisher) { - throw new RuntimeException(); - } - - static class Table {} - - private static < - R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> - void mergeTables( - Table table, R row, C column, V value, BinaryOperator mergeFunction) { - throw new RuntimeException(); - } - - static < - T extends @Nullable Object, - R extends @Nullable Object, - C extends @Nullable Object, - V extends @Nullable Object, - I extends Table> - Collector toTable( - java.util.function.Function rowFunction, - java.util.function.Function columnFunction, - java.util.function.Function valueFunction, - BinaryOperator mergeFunction, - java.util.function.Supplier tableSupplier) { - - return Collector.of( - tableSupplier, - (table, input) -> - mergeTables( - table, - rowFunction.apply(input), - columnFunction.apply(input), - valueFunction.apply(input), - mergeFunction), - (table1, table2) -> { - // for (Table.Cell cell2 : table2.cellSet()) { - // mergeTables( - // table1, cell2.getRowKey(), cell2.getColumnKey(), - // cell2.getValue(), mergeFunction); - // } - return table1; - }); - } - - void subcrash(Method method, Class p) { - - checkArgument( - !p.isPrimitive(), - "@Subscribe method %s's parameter is %s. " - + "Subscriber methods cannot accept primitives. " - + "Consider changing the parameter to %s.", - method, - p.getName(), - wrap(p).getSimpleName()); - } - - void crash(Method method) { - Class[] parameterTypes = method.getParameterTypes(); - checkArgument( - !parameterTypes[0].isPrimitive(), - "@Subscribe method %s's parameter is %s. " - + "Subscriber methods cannot accept primitives. " - + "Consider changing the parameter to %s.", - method, - parameterTypes[0].getName(), - wrap(parameterTypes[0]).getSimpleName()); - } - - public static Class wrap(Class type) { - throw new RuntimeException(); - } - - public static void checkArgument( - boolean expression, String errorMessageTemplate, @Nullable Object... errorMessageArgs) {} - - public static void difference( - SortedMap left, Map right) { - Comparator comparator = orNaturalOrder(left.comparator()); - } - - static Comparator orNaturalOrder( - Comparator comparator) { - throw new RuntimeException(); - } } diff --git a/framework/tests/all-systems/java8inference/Issue1308.java b/framework/tests/all-systems/java8inference/Issue1308.java index 01fe0160467..20a6cdbfbf2 100644 --- a/framework/tests/all-systems/java8inference/Issue1308.java +++ b/framework/tests/all-systems/java8inference/Issue1308.java @@ -9,21 +9,21 @@ class Map1308 {} @SuppressWarnings("all") // check for crashes public class Issue1308 { - void bar(Stream stream) { - new Inner(stream.collect(transform(data -> convert(data), Function.identity()))); - } + void bar(Stream stream) { + new Inner(stream.collect(transform(data -> convert(data), Function.identity()))); + } - String convert(Number entry) { - return ""; - } + String convert(Number entry) { + return ""; + } - class Inner { - Inner(Map1308 data) {} - } + class Inner { + Inner(Map1308 data) {} + } - static Collector> transform( - Function keyFunction, - Function valueFunction) { - return null; - } + static Collector> transform( + Function keyFunction, + Function valueFunction) { + return null; + } } diff --git a/framework/tests/all-systems/java8inference/Issue1312.java b/framework/tests/all-systems/java8inference/Issue1312.java index 80daf7eeaee..c33dc644bfc 100644 --- a/framework/tests/all-systems/java8inference/Issue1312.java +++ b/framework/tests/all-systems/java8inference/Issue1312.java @@ -6,13 +6,13 @@ import java.util.stream.*; class SimpleEntry1312 { - SimpleEntry1312(K k, V v) {} + SimpleEntry1312(K k, V v) {} } @SuppressWarnings("all") // check for crashes public class Issue1312 { - Map> x = - Stream.of(Stream.of(new SimpleEntry1312<>("A", "B"))) - .flatMap(Function.identity()) - .collect(Collectors.groupingBy(e -> e)); + Map> x = + Stream.of(Stream.of(new SimpleEntry1312<>("A", "B"))) + .flatMap(Function.identity()) + .collect(Collectors.groupingBy(e -> e)); } diff --git a/framework/tests/all-systems/java8inference/Issue1313.java b/framework/tests/all-systems/java8inference/Issue1313.java index b9b46c1c04e..9396936ec07 100644 --- a/framework/tests/all-systems/java8inference/Issue1313.java +++ b/framework/tests/all-systems/java8inference/Issue1313.java @@ -8,10 +8,10 @@ interface MyList1313 extends Iterable {} @SuppressWarnings("all") // check for crashes public class Issue1313 { - Stream s; - Iterable i = s.collect(toMyList1313()); + Stream s; + Iterable i = s.collect(toMyList1313()); - Collector> toMyList1313() { - return null; - } + Collector> toMyList1313() { + return null; + } } diff --git a/framework/tests/all-systems/java8inference/Issue1331.java b/framework/tests/all-systems/java8inference/Issue1331.java index 1f201e592a5..34abd099f6e 100644 --- a/framework/tests/all-systems/java8inference/Issue1331.java +++ b/framework/tests/all-systems/java8inference/Issue1331.java @@ -6,10 +6,10 @@ @SuppressWarnings("all") // check for crashes public class Issue1331 { - List ll; - long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); + List ll; + long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); - static T getOnlyElement(Iterable iterable) { - return null; - } + static T getOnlyElement(Iterable iterable) { + return null; + } } diff --git a/framework/tests/all-systems/java8inference/Issue1332.java b/framework/tests/all-systems/java8inference/Issue1332.java index 41dffed8092..17c53502c11 100644 --- a/framework/tests/all-systems/java8inference/Issue1332.java +++ b/framework/tests/all-systems/java8inference/Issue1332.java @@ -8,22 +8,22 @@ @SuppressWarnings("all") // check for crashes abstract class Issue1332 { - void foo(List ll) { - Function test = - s -> { - long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); - return result; - }; - } + void foo(List ll) { + Function test = + s -> { + long result = getOnlyElement(ll.stream().collect(Collectors.toSet())); + return result; + }; + } - abstract T getOnlyElement(Iterable iterable); + abstract T getOnlyElement(Iterable iterable); - private void test1() { - byte[][] byteArrayArray = new byte[][] {}; - Stream stream = Stream.of(byteArrayArray); - } + private void test1() { + byte[][] byteArrayArray = new byte[][] {}; + Stream stream = Stream.of(byteArrayArray); + } - private void test2() { - Stream stream = Stream.of(new byte[][] {}); - } + private void test2() { + Stream stream = Stream.of(new byte[][] {}); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1334.java b/framework/tests/all-systems/java8inference/Issue1334.java index fbb9b537866..4c11b5d9efa 100644 --- a/framework/tests/all-systems/java8inference/Issue1334.java +++ b/framework/tests/all-systems/java8inference/Issue1334.java @@ -4,7 +4,7 @@ import java.util.stream.Stream; public class Issue1334 { - private void test() { - Stream stream = Stream.of(new byte[][] {}).map(b -> 1); - } + private void test() { + Stream stream = Stream.of(new byte[][] {}).map(b -> 1); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1377.java b/framework/tests/all-systems/java8inference/Issue1377.java index 3485978cf42..ea23c02e0ce 100644 --- a/framework/tests/all-systems/java8inference/Issue1377.java +++ b/framework/tests/all-systems/java8inference/Issue1377.java @@ -2,23 +2,23 @@ // https://github.com/typetools/checker-framework/issues/1377 interface Func1377 { - R apply(P p); + R apply(P p); } @SuppressWarnings("all") // just check for crashes interface Issue1377 { - static Issue1377 of(Issue1377 in) { - return in; - } + static Issue1377 of(Issue1377 in) { + return in; + } - Issue1377 m1(Func1377 f); + Issue1377 m1(Func1377 f); - Issue1377 m2(Func1377 f); + Issue1377 m2(Func1377 f); } @SuppressWarnings("all") // just check for crashes class Crash1377 { - void foo(Issue1377 p) { - Issue1377.of(p.m1(in -> p)).m2(empty -> 5); - } + void foo(Issue1377 p) { + Issue1377.of(p.m1(in -> p)).m2(empty -> 5); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1379.java b/framework/tests/all-systems/java8inference/Issue1379.java index dec2dc2264d..6c539160b14 100644 --- a/framework/tests/all-systems/java8inference/Issue1379.java +++ b/framework/tests/all-systems/java8inference/Issue1379.java @@ -4,18 +4,18 @@ interface Box1379 {} interface Trans1379 { - Box1379 apply(I in); + Box1379 apply(I in); } @SuppressWarnings("all") // just check for crashes abstract class Issue1379 { - abstract Box1379 app(Box1379 in, Trans1379 t); + abstract Box1379 app(Box1379 in, Trans1379 t); - abstract Trans1379 pass(Trans1379 t); + abstract Trans1379 pass(Trans1379 t); - abstract Box1379 box(Number p); + abstract Box1379 box(Number p); - void foo(Box1379 p) { - app(p, pass(this::box)); - } + void foo(Box1379 p) { + app(p, pass(this::box)); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1397.java b/framework/tests/all-systems/java8inference/Issue1397.java index 8ad4572e0cb..40fb3933dfb 100644 --- a/framework/tests/all-systems/java8inference/Issue1397.java +++ b/framework/tests/all-systems/java8inference/Issue1397.java @@ -3,17 +3,17 @@ public class Issue1397 { - class Box {} + class Box {} - abstract class CrashCompound { - abstract T chk(T in); + abstract class CrashCompound { + abstract T chk(T in); - abstract T unbox(Box p); + abstract T unbox(Box p); - @SuppressWarnings("units") - void foo(Box bb) { - boolean res = false; - res |= chk(unbox(bb)); + @SuppressWarnings("units") + void foo(Box bb) { + boolean res = false; + res |= chk(unbox(bb)); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue1398.java b/framework/tests/all-systems/java8inference/Issue1398.java index caf4e4cdb06..f80914458b5 100644 --- a/framework/tests/all-systems/java8inference/Issue1398.java +++ b/framework/tests/all-systems/java8inference/Issue1398.java @@ -3,29 +3,29 @@ public class Issue1398 { - interface Pair {} + interface Pair {} - interface Triple {} + interface Triple {} - interface Quadruple {} + interface Quadruple {} - interface Box { - Pair doTriple(Triple t); + interface Box { + Pair doTriple(Triple t); - > BA doPair(Pair p, BoxMaker bm); - } + > BA doPair(Pair p, BoxMaker bm); + } - class BoxMaker> {} + class BoxMaker> {} - abstract class Crash7 { - abstract Pair bar(Pair in); + abstract class Crash7 { + abstract Pair bar(Pair in); - void foo( - Box bs, - BoxMaker> bm, - Pair psn, - Triple t) { - bs.doPair(bar(psn), bm).doTriple(t); + void foo( + Box bs, + BoxMaker> bm, + Pair psn, + Triple t) { + bs.doPair(bar(psn), bm).doTriple(t); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue1399.java b/framework/tests/all-systems/java8inference/Issue1399.java index d4eb9032f91..021401ee778 100644 --- a/framework/tests/all-systems/java8inference/Issue1399.java +++ b/framework/tests/all-systems/java8inference/Issue1399.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/1399 public class Issue1399 { - static class Box { - static Box box(Class type) { - return new Box(); - } + static class Box { + static Box box(Class type) { + return new Box(); + } - void act(T instance) {} - } + void act(T instance) {} + } - abstract class Math { - abstract Box id(Box in); + abstract class Math { + abstract Box id(Box in); - void foo(Math m) { - m.id(Box.box(Long.class)).act(10L); + void foo(Math m) { + m.id(Box.box(Long.class)).act(10L); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue1407.java b/framework/tests/all-systems/java8inference/Issue1407.java index 375845d9bf3..b7ebcb1d7d7 100644 --- a/framework/tests/all-systems/java8inference/Issue1407.java +++ b/framework/tests/all-systems/java8inference/Issue1407.java @@ -2,12 +2,12 @@ // https://github.com/typetools/checker-framework/issues/1407 abstract class Issue1407 { - abstract T foo(T p1, T p2); + abstract T foo(T p1, T p2); - abstract T bar(int p1, T p2); + abstract T bar(int p1, T p2); - @SuppressWarnings({"interning", "signedness"}) - int demo() { - return foo(bar(5, 3), 3); - } + @SuppressWarnings({"interning", "signedness"}) + int demo() { + return foo(bar(5, 3), 3); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1408.java b/framework/tests/all-systems/java8inference/Issue1408.java index 5cfae7dd158..85aeb2d83a5 100644 --- a/framework/tests/all-systems/java8inference/Issue1408.java +++ b/framework/tests/all-systems/java8inference/Issue1408.java @@ -1,15 +1,15 @@ // Test case for Issue 1408. // https://github.com/typetools/checker-framework/issues/1408 abstract class Issue1408 { - interface Demo {} + interface Demo {} - interface SubDemo extends Demo {} + interface SubDemo extends Demo {} - abstract S foo(S p1, S p2); + abstract S foo(S p1, S p2); - abstract T bar(T p2); + abstract T bar(T p2); - SubDemo demo(SubDemo p) { - return foo(bar(p), p); - } + SubDemo demo(SubDemo p) { + return foo(bar(p), p); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1415.java b/framework/tests/all-systems/java8inference/Issue1415.java index 5af1832b7ad..fedc3b61679 100644 --- a/framework/tests/all-systems/java8inference/Issue1415.java +++ b/framework/tests/all-systems/java8inference/Issue1415.java @@ -3,24 +3,24 @@ @SuppressWarnings("all") // Check for crashes. public class Issue1415 { - static class Optional { - static Optional absent() { - return null; - } + static class Optional { + static Optional absent() { + return null; + } - static Optional of(T p) { - return null; + static Optional of(T p) { + return null; + } } - } - static class Box { - void box(T p) {} - } + static class Box { + void box(T p) {} + } - static class Crash9 { - > void foo(boolean b, Box> box, Class enumClass) { - box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); - box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); + static class Crash9 { + > void foo(boolean b, Box> box, Class enumClass) { + box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); + box.box(b ? Optional.absent() : Optional.of(Enum.valueOf(enumClass, "hi"))); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue1416.java b/framework/tests/all-systems/java8inference/Issue1416.java index c7cae68ff39..ca9c44bfe11 100644 --- a/framework/tests/all-systems/java8inference/Issue1416.java +++ b/framework/tests/all-systems/java8inference/Issue1416.java @@ -5,8 +5,8 @@ import java.util.stream.Stream; public class Issue1416 { - @SuppressWarnings("signedness") - long order(Stream sl) { - return sl.max(Comparator.naturalOrder()).orElse(0L); - } + @SuppressWarnings("signedness") + long order(Stream sl) { + return sl.max(Comparator.naturalOrder()).orElse(0L); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1417.java b/framework/tests/all-systems/java8inference/Issue1417.java index 52f86b0de5b..854ef83857d 100644 --- a/framework/tests/all-systems/java8inference/Issue1417.java +++ b/framework/tests/all-systems/java8inference/Issue1417.java @@ -2,19 +2,19 @@ // https://github.com/typetools/checker-framework/issues/1417 public class Issue1417 { - interface Bar {} + interface Bar {} - interface SubBar extends Bar {} + interface SubBar extends Bar {} - interface Barber { - S call(S s); - } + interface Barber { + S call(S s); + } - abstract class Crash12 { - abstract void foo(Barber b); + abstract class Crash12 { + abstract void foo(Barber b); - void crash() { - foo((SubBar p) -> p); + void crash() { + foo((SubBar p) -> p); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue1419.java b/framework/tests/all-systems/java8inference/Issue1419.java index 0bc996530d4..66216938ae3 100644 --- a/framework/tests/all-systems/java8inference/Issue1419.java +++ b/framework/tests/all-systems/java8inference/Issue1419.java @@ -2,14 +2,14 @@ // https://github.com/typetools/checker-framework/issues/1419 abstract class Issue1419 { - class Map {} + class Map {} - class EnumMap> extends Map {} + class EnumMap> extends Map {} - abstract > Map foo(Map map); + abstract > Map foo(Map map); - @SuppressWarnings({"unchecked", "all"}) // Just check for crashes. - Map bar(Map map) { - return foo((EnumMap) map); - } + @SuppressWarnings({"unchecked", "all"}) // Just check for crashes. + Map bar(Map map) { + return foo((EnumMap) map); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1424.java b/framework/tests/all-systems/java8inference/Issue1424.java index b79e0dbcec5..ed83d8b8507 100644 --- a/framework/tests/all-systems/java8inference/Issue1424.java +++ b/framework/tests/all-systems/java8inference/Issue1424.java @@ -3,23 +3,23 @@ @SuppressWarnings({"unchecked", "all"}) // Just check for crashes. abstract class Issue1424 { - class Box {} + class Box {} - interface Callable { - V call() throws Exception; - } + interface Callable { + V call() throws Exception; + } - class MyCallable implements Callable { - MyCallable(Callable delegate) {} + class MyCallable implements Callable { + MyCallable(Callable delegate) {} - public T call() throws Exception { - throw new RuntimeException(); + public T call() throws Exception { + throw new RuntimeException(); + } } - } - abstract Box submit(Callable t); + abstract Box submit(Callable t); - Box foo() { - return submit(new MyCallable(() -> true)); - } + Box foo() { + return submit(new MyCallable(() -> true)); + } } diff --git a/framework/tests/all-systems/java8inference/Issue1715.java b/framework/tests/all-systems/java8inference/Issue1715.java index 948329fb757..f5e1aed8478 100644 --- a/framework/tests/all-systems/java8inference/Issue1715.java +++ b/framework/tests/all-systems/java8inference/Issue1715.java @@ -3,30 +3,30 @@ public class Issue1715 { - static final class A { + static final class A { - public A() {} + public A() {} - public Object foo(Object o) { - return o; + public Object foo(Object o) { + return o; + } } - } - private Observable>> test(A a) { - return Observable.just(ImmutableList.of(a::foo)); - } + private Observable>> test(A a) { + return Observable.just(ImmutableList.of(a::foo)); + } - static class Observable { + static class Observable { - static Observable just(T param) { - throw new RuntimeException(); + static Observable just(T param) { + throw new RuntimeException(); + } } - } - public abstract static class ImmutableList implements List { + public abstract static class ImmutableList implements List { - public static ImmutableList of(E element) { - throw new RuntimeException(); + public static ImmutableList of(E element) { + throw new RuntimeException(); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue1775.java b/framework/tests/all-systems/java8inference/Issue1775.java index d4994a76d5d..faf56655108 100644 --- a/framework/tests/all-systems/java8inference/Issue1775.java +++ b/framework/tests/all-systems/java8inference/Issue1775.java @@ -3,15 +3,15 @@ @SuppressWarnings("all") // just check for crashes public class Issue1775 { - interface Box { - B get(); - } + interface Box { + B get(); + } - Box getBox() { - return null; - } + Box getBox() { + return null; + } - void m() { - for (String s : getBox().get()) {} - } + void m() { + for (String s : getBox().get()) {} + } } diff --git a/framework/tests/all-systems/java8inference/Issue1815.java b/framework/tests/all-systems/java8inference/Issue1815.java index 8b29216f524..e8f05e4bfd7 100644 --- a/framework/tests/all-systems/java8inference/Issue1815.java +++ b/framework/tests/all-systems/java8inference/Issue1815.java @@ -7,15 +7,15 @@ @SuppressWarnings("all") // just check for crashes abstract class Issue1815 { - class A extends B {} + class A extends B {} - static class B, Two extends B.I> { - static class I, Four extends I> {} - } + static class B, Two extends B.I> { + static class I, Four extends I> {} + } - abstract A f(Integer x); + abstract A f(Integer x); - void test(List xs) { - Stream.of(xs.stream().map(x -> f(x)), xs.stream().map(x -> f(x))).flatMap(stream -> stream); - } + void test(List xs) { + Stream.of(xs.stream().map(x -> f(x)), xs.stream().map(x -> f(x))).flatMap(stream -> stream); + } } diff --git a/framework/tests/all-systems/java8inference/Issue2975.java b/framework/tests/all-systems/java8inference/Issue2975.java index 570cfc4e0db..5e644ef8f7d 100644 --- a/framework/tests/all-systems/java8inference/Issue2975.java +++ b/framework/tests/all-systems/java8inference/Issue2975.java @@ -2,19 +2,19 @@ import java.util.function.Consumer; public class Issue2975 { - static class Child extends Issue2975 { - Wrapper y = new Wrapper(Child::takesCloseable); + static class Child extends Issue2975 { + Wrapper y = new Wrapper(Child::takesCloseable); - private static void takesCloseable(Closeable rhs) {} - } + private static void takesCloseable(Closeable rhs) {} + } - protected class Wrapper { - protected Wrapper() {} + protected class Wrapper { + protected Wrapper() {} - protected Wrapper(Consumer makeExpression) {} + protected Wrapper(Consumer makeExpression) {} - protected Wrapper method(Consumer makeExpression) { - throw new RuntimeException(); + protected Wrapper method(Consumer makeExpression) { + throw new RuntimeException(); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue3032.java b/framework/tests/all-systems/java8inference/Issue3032.java index b2054db6ccb..f8e49fe603f 100644 --- a/framework/tests/all-systems/java8inference/Issue3032.java +++ b/framework/tests/all-systems/java8inference/Issue3032.java @@ -1,46 +1,47 @@ import java.io.Serializable; public class Issue3032 { - public static class PCollection implements PValue { - public OutputT apply( - String name, PTransform, OutputT> t) { - throw new RuntimeException(); + public static class PCollection implements PValue { + public OutputT apply( + String name, PTransform, OutputT> t) { + throw new RuntimeException(); + } } - } - public abstract static class PTransform {} + public abstract static class PTransform {} - interface PInput {} + interface PInput {} - interface POutput {} + interface POutput {} - interface PValue extends PInput, POutput {} + interface PValue extends PInput, POutput {} - static class BillingEvent { - InvoiceGroupingKey getInvoiceGroupingKey() { - throw new RuntimeException(); + static class BillingEvent { + InvoiceGroupingKey getInvoiceGroupingKey() { + throw new RuntimeException(); + } } - } - public static class MapElements - extends PTransform, PCollection> { - public static MapElements via( - final ProcessFunction fn) { - throw new RuntimeException(); + public static class MapElements + extends PTransform, PCollection> { + public static MapElements via( + final ProcessFunction fn) { + throw new RuntimeException(); + } } - } - static class InvoiceGroupingKey {} + static class InvoiceGroupingKey {} - @FunctionalInterface - public interface ProcessFunction extends Serializable { - OutputT apply(InputT input) throws Exception; - } + @FunctionalInterface + public interface ProcessFunction extends Serializable { + OutputT apply(InputT input) throws Exception; + } - private static class GenerateInvoiceRows - extends PTransform, PCollection> { - public void expand(PCollection input) { - input.apply("Map to invoicing key", MapElements.via(BillingEvent::getInvoiceGroupingKey)); + private static class GenerateInvoiceRows + extends PTransform, PCollection> { + public void expand(PCollection input) { + input.apply( + "Map to invoicing key", MapElements.via(BillingEvent::getInvoiceGroupingKey)); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue3036.java b/framework/tests/all-systems/java8inference/Issue3036.java index 504f6aa8695..dfadd592b7d 100644 --- a/framework/tests/all-systems/java8inference/Issue3036.java +++ b/framework/tests/all-systems/java8inference/Issue3036.java @@ -9,42 +9,42 @@ public class Issue3036 { - public Set getDsData() { - throw new RuntimeException(); - } - - public static class MyInnerClass { - public int getKeyTag() { - return 5; + public Set getDsData() { + throw new RuntimeException(); } - public String getDigest() { - return ""; - } - } + public static class MyInnerClass { + public int getKeyTag() { + return 5; + } - private void write(Stream stream) { - Function> mapper = - dsData1 -> - ImmutableMap.of( - "keyTag", dsData1.getKeyTag(), - "digest", dsData1.getDigest()); + public String getDigest() { + return ""; + } + } - // Any checker where string literals are not top will error here. - @SuppressWarnings("type.arguments.not.inferred") - List> dsData = - getDsData().stream() - .map( + private void write(Stream stream) { + Function> mapper = dsData1 -> - ImmutableMap.of( - "keyTag", dsData1.getKeyTag(), - "digest", dsData1.getDigest())) - .collect(Collectors.toList()); - } + ImmutableMap.of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest()); + + // Any checker where string literals are not top will error here. + @SuppressWarnings("type.arguments.not.inferred") + List> dsData = + getDsData().stream() + .map( + dsData1 -> + ImmutableMap.of( + "keyTag", dsData1.getKeyTag(), + "digest", dsData1.getDigest())) + .collect(Collectors.toList()); + } - public static class ImmutableMap extends HashMap { - public static ImmutableMap of(K k1, V v1, K k2, V v2) { - throw new RuntimeException(); + public static class ImmutableMap extends HashMap { + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + throw new RuntimeException(); + } } - } } diff --git a/framework/tests/all-systems/java8inference/Issue404.java b/framework/tests/all-systems/java8inference/Issue404.java index b686bb80991..bc7aeeb1459 100644 --- a/framework/tests/all-systems/java8inference/Issue404.java +++ b/framework/tests/all-systems/java8inference/Issue404.java @@ -5,7 +5,7 @@ import java.util.stream.Collectors; public final class Issue404 { - public Set uniqueTrimmed(final Collection strings) { - return strings.stream().map(String::trim).collect(Collectors.toSet()); - } + public Set uniqueTrimmed(final Collection strings) { + return strings.stream().map(String::trim).collect(Collectors.toSet()); + } } diff --git a/framework/tests/all-systems/java8inference/Issue6046.java b/framework/tests/all-systems/java8inference/Issue6046.java index 55481d3e698..a6b9a49d0e0 100644 --- a/framework/tests/all-systems/java8inference/Issue6046.java +++ b/framework/tests/all-systems/java8inference/Issue6046.java @@ -9,23 +9,27 @@ @SuppressWarnings("all") public class Issue6046 { - public interface Record extends Comparable, Formattable {} + public interface Record extends Comparable, Formattable {} - public interface Result extends List, Formattable {} + public interface Result extends List, Formattable {} - @SuppressWarnings({"unchecked", "nullness:new.array", "index:array.access.unsafe.high.constant"}) - public static - Collector>> intoResultGroups( - Function keyMapper) { + @SuppressWarnings({ + "unchecked", + "nullness:new.array", + "index:array.access.unsafe.high.constant" + }) + public static + Collector>> intoResultGroups( + Function keyMapper) { - return Collectors.groupingBy( - keyMapper, - LinkedHashMap::new, - Collector.[], Result>of( - () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); - } + return Collectors.groupingBy( + keyMapper, + LinkedHashMap::new, + Collector.[], Result>of( + () -> new Result[1], (x, r) -> {}, (r1, r2) -> r1, r -> r[0])); + } - public static Result result(R record) { - throw new RuntimeException(); - } + public static Result result(R record) { + throw new RuntimeException(); + } } diff --git a/framework/tests/all-systems/java8inference/Issue6346.java b/framework/tests/all-systems/java8inference/Issue6346.java index 190583104f0..ff37cdf01c0 100644 --- a/framework/tests/all-systems/java8inference/Issue6346.java +++ b/framework/tests/all-systems/java8inference/Issue6346.java @@ -5,87 +5,89 @@ public class Issue6346 { - public abstract static class A, Y extends A.Builder> extends B { + public abstract static class A, Y extends A.Builder> extends B { - public abstract static class Builder, Y1 extends Builder> - extends B.Builder {} - } + public abstract static class Builder, Y1 extends Builder> + extends B.Builder {} + } - public abstract static class B, Y2 extends B.Builder> implements C { + public abstract static class B, Y2 extends B.Builder> + implements C { - public abstract static class Builder, Y3 extends Builder> - implements C.Builder {} - } + public abstract static class Builder, Y3 extends Builder> + implements C.Builder {} + } - public interface C extends D { - interface Builder extends D, Cloneable {} - } + public interface C extends D { + interface Builder extends D, Cloneable {} + } - public interface D {} + public interface D {} - abstract static class E, I, O> extends F { + abstract static class E, I, O> extends F { - public abstract static class Builder, I, O> { - public Builder g(Function x, BiConsumer y) { - throw new AssertionError(); - } + public abstract static class Builder, I, O> { + public Builder g(Function x, BiConsumer y) { + throw new AssertionError(); + } - public abstract Builder version(int version); - } + public abstract Builder version(int version); + } - public static < - L extends A, - B extends A.Builder, - I extends A, - O extends A.Builder> - Builder f(L x, Function> y, Supplier z, BiConsumer z1) { - throw new AssertionError(); + public static < + L extends A, + B extends A.Builder, + I extends A, + O extends A.Builder> + Builder f( + L x, Function> y, Supplier z, BiConsumer z1) { + throw new AssertionError(); + } } - } - public interface G extends D {} + public interface G extends D {} - abstract static class H extends A implements G { + abstract static class H extends A implements G { - public List h() { - throw new AssertionError(); - } + public List h() { + throw new AssertionError(); + } - public static final class Builder extends A.Builder implements G { + public static final class Builder extends A.Builder implements G { - private Builder i(I value) { - return this; - } - } + private Builder i(I value) { + return this; + } + } - public static H j() { - throw new AssertionError(); + public static H j() { + throw new AssertionError(); + } } - } - public interface J extends D {} + public interface J extends D {} - public static final class I extends A implements J { + public static final class I extends A implements J { - public long k() { - return 0L; - } + public long k() { + return 0L; + } - public static final class Builder extends A.Builder implements J { + public static final class Builder extends A.Builder implements J { - Builder l(long value) { - return this; - } - } + Builder l(long value) { + return this; + } + } - public static Builder n() { - throw new AssertionError(); + public static Builder n() { + throw new AssertionError(); + } } - } - abstract static class F> {} + abstract static class F> {} - void f() { - var x = E.f(H.j(), H::h, I::n, H.Builder::i).g(I::k, I.Builder::l); - } + void f() { + var x = E.f(H.j(), H::h, I::n, H.Builder::i).g(I::k, I.Builder::l); + } } diff --git a/framework/tests/all-systems/java8inference/Issue953Inference.java b/framework/tests/all-systems/java8inference/Issue953Inference.java index 677fe719059..23b09c0000d 100644 --- a/framework/tests/all-systems/java8inference/Issue953Inference.java +++ b/framework/tests/all-systems/java8inference/Issue953Inference.java @@ -6,13 +6,13 @@ import java.util.stream.Collectors; public class Issue953Inference { - public static void test() { - List initial = new ArrayList<>(); - List counts = - initial.stream().skip(1).map(l -> count("", l)).collect(Collectors.toList()); - } + public static void test() { + List initial = new ArrayList<>(); + List counts = + initial.stream().skip(1).map(l -> count("", l)).collect(Collectors.toList()); + } - private static int count(String s, String l) { - return 0; - } + private static int count(String s, String l) { + return 0; + } } diff --git a/framework/tests/all-systems/java8inference/IteratorInference.java b/framework/tests/all-systems/java8inference/IteratorInference.java index 2e8f636e3b3..261974a0671 100644 --- a/framework/tests/all-systems/java8inference/IteratorInference.java +++ b/framework/tests/all-systems/java8inference/IteratorInference.java @@ -1,13 +1,15 @@ -import java.util.Iterator; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Iterator; + @SuppressWarnings({"unchecked", "all"}) public class IteratorInference { - static void concatNoDefensiveCopy(Iterator... inputs) { - for (Iterator input : checkNotNull(inputs)) {} - } + static void concatNoDefensiveCopy( + Iterator... inputs) { + for (Iterator input : checkNotNull(inputs)) {} + } - public static T checkNotNull(T reference) { - return reference; - } + public static T checkNotNull(T reference) { + return reference; + } } diff --git a/framework/tests/all-systems/java8inference/MapEntryGetFails.java b/framework/tests/all-systems/java8inference/MapEntryGetFails.java index d28294f1fb4..8554f0913a6 100644 --- a/framework/tests/all-systems/java8inference/MapEntryGetFails.java +++ b/framework/tests/all-systems/java8inference/MapEntryGetFails.java @@ -4,8 +4,8 @@ import java.util.stream.Stream; public class MapEntryGetFails { - void test(Stream> listStream) { - listStream.collect(Collectors.groupingByConcurrent(l -> l.get(1))).entrySet().stream() - .sorted(Entry.comparingByKey()); - } + void test(Stream> listStream) { + listStream.collect(Collectors.groupingByConcurrent(l -> l.get(1))).entrySet().stream() + .sorted(Entry.comparingByKey()); + } } diff --git a/framework/tests/all-systems/java8inference/MemRefInfere.java b/framework/tests/all-systems/java8inference/MemRefInfere.java index 0ddd3bd9060..b57cb0dd9b3 100644 --- a/framework/tests/all-systems/java8inference/MemRefInfere.java +++ b/framework/tests/all-systems/java8inference/MemRefInfere.java @@ -9,18 +9,18 @@ import java.util.stream.Collectors; public abstract class MemRefInfere implements Map, Serializable { - public static MemRefInfere copyOf(Map map) { - throw new RuntimeException(); - } + public static MemRefInfere copyOf(Map map) { + throw new RuntimeException(); + } - @SuppressWarnings("lock:methodref.return") - public static Collector> toImmutableMap( - Function keyFunction, - Function valueFunction, - BinaryOperator mergeFunction) { + @SuppressWarnings("lock:methodref.return") + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { - return Collectors.collectingAndThen( - Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), - MemRefInfere::copyOf); - } + return Collectors.collectingAndThen( + Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), + MemRefInfere::copyOf); + } } diff --git a/framework/tests/all-systems/java8inference/Misc.java b/framework/tests/all-systems/java8inference/Misc.java index fc4d56efff9..f20b79eb05c 100644 --- a/framework/tests/all-systems/java8inference/Misc.java +++ b/framework/tests/all-systems/java8inference/Misc.java @@ -1,20 +1,20 @@ @SuppressWarnings({"unchecked", "all"}) // check for crashes public class Misc { - Misc forward; + Misc forward; - public E min(E a, E b) { - return forward.max(a, b); - } + public E min(E a, E b) { + return forward.max(a, b); + } - public F min(F a, F b, F c, F... rest) { - return forward.max(a, b, c, rest); - } + public F min(F a, F b, F c, F... rest) { + return forward.max(a, b, c, rest); + } - public G max(G a, G b) { - return forward.min(a, b); - } + public G max(G a, G b) { + return forward.min(a, b); + } - public H max(H a, H b, H c, H... rest) { - return forward.min(a, b, c, rest); - } + public H max(H a, H b, H c, H... rest) { + return forward.min(a, b, c, rest); + } } diff --git a/framework/tests/all-systems/java8inference/PrimitiveTarget.java b/framework/tests/all-systems/java8inference/PrimitiveTarget.java index 71712e1250c..86e84657dc8 100644 --- a/framework/tests/all-systems/java8inference/PrimitiveTarget.java +++ b/framework/tests/all-systems/java8inference/PrimitiveTarget.java @@ -2,7 +2,7 @@ import java.util.List; public class PrimitiveTarget { - void method(List list) { - long l = Collections.min(list); - } + void method(List list) { + long l = Collections.min(list); + } } diff --git a/framework/tests/all-systems/java8inference/TooManyConstraints.java b/framework/tests/all-systems/java8inference/TooManyConstraints.java index e12c49fa6e0..0d49991841e 100644 --- a/framework/tests/all-systems/java8inference/TooManyConstraints.java +++ b/framework/tests/all-systems/java8inference/TooManyConstraints.java @@ -3,27 +3,27 @@ @SuppressWarnings("all") // Just check for crashes. public class TooManyConstraints { - static final class ThisClass { - private final ReferenceQueue queue = new ReferenceQueue(); + static final class ThisClass { + private final ReferenceQueue queue = new ReferenceQueue(); - public InterfaceB> test(Entry e, V value) { - return new ClassA<>(queue, value, cast(e)); - } + public InterfaceB> test(Entry e, V value) { + return new ClassA<>(queue, value, cast(e)); + } - public EntryC cast(Entry entry) { - throw new RuntimeException(); + public EntryC cast(Entry entry) { + throw new RuntimeException(); + } } - } - static final class ClassA> implements InterfaceB { - ClassA(ReferenceQueue queue, V referent, E entry) {} - } + static final class ClassA> implements InterfaceB { + ClassA(ReferenceQueue queue, V referent, E entry) {} + } - interface InterfaceB> {} + interface InterfaceB> {} - static final class EntryC implements EntryB> {} + static final class EntryC implements EntryB> {} - interface EntryB> extends Entry {} + interface EntryB> extends Entry {} - interface Entry> {} + interface Entry> {} } diff --git a/framework/tests/all-systems/java8inference/UncheckedConversionInference.java b/framework/tests/all-systems/java8inference/UncheckedConversionInference.java index 8407acb5c51..6664ea09acd 100644 --- a/framework/tests/all-systems/java8inference/UncheckedConversionInference.java +++ b/framework/tests/all-systems/java8inference/UncheckedConversionInference.java @@ -1,57 +1,65 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Iterator; import java.util.Set; import java.util.function.Function; -import org.checkerframework.checker.nullness.qual.Nullable; public class UncheckedConversionInference { - private static class TransposeTable< - C extends @Nullable Object, R extends @Nullable Object, V extends @Nullable Object> - extends AbstractTable { + private static class TransposeTable< + C extends @Nullable Object, + R extends @Nullable Object, + V extends @Nullable Object> + extends AbstractTable { - private static final Function, Cell> TRANSPOSE_CELL = - new Function, Cell>() { - @Override - public Cell apply(Cell cell) { - throw new RuntimeException(); - } - }; - final Table original; + private static final Function, Cell> TRANSPOSE_CELL = + new Function, Cell>() { + @Override + public Cell apply(Cell cell) { + throw new RuntimeException(); + } + }; + final Table original; - TransposeTable(Table original) { - this.original = original; - } + TransposeTable(Table original) { + this.original = original; + } - @SuppressWarnings("unchecked") - @Override - Iterator> cellIterator() { - return transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); - } + @SuppressWarnings("unchecked") + @Override + Iterator> cellIterator() { + return transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); + } - public static Iterator transform( - Iterator fromIterator, Function function) { - throw new RuntimeException(); - } + public static + Iterator transform( + Iterator fromIterator, Function function) { + throw new RuntimeException(); + } - @Override - public Set> cellSet() { - throw new RuntimeException(); + @Override + public Set> cellSet() { + throw new RuntimeException(); + } } - } - abstract static class AbstractTable< - R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> - implements Table { + abstract static class AbstractTable< + R extends @Nullable Object, + C extends @Nullable Object, + V extends @Nullable Object> + implements Table { - abstract Iterator> cellIterator(); - } + abstract Iterator> cellIterator(); + } - public interface Table< - R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> { + public interface Table< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> { - Set> cellSet(); + Set> cellSet(); - interface Cell< - R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> {} - } + interface Cell< + R extends @Nullable Object, + C extends @Nullable Object, + V extends @Nullable Object> {} + } } diff --git a/framework/tests/annotationclassloader/LoaderTest.java b/framework/tests/annotationclassloader/LoaderTest.java index 69e50fdc585..02b4b8b6c91 100644 --- a/framework/tests/annotationclassloader/LoaderTest.java +++ b/framework/tests/annotationclassloader/LoaderTest.java @@ -1,9 +1,9 @@ import org.checkerframework.common.aliasing.qual.Unique; public class LoaderTest { - void foo() { - @Unique Object o = new Object(); - // :: error: (unique.leaked) - Object[] ar = new Object[] {o}; - } + void foo() { + @Unique Object o = new Object(); + // :: error: (unique.leaked) + Object[] ar = new Object[] {o}; + } } diff --git a/framework/tests/classval/ClassNameTest.java b/framework/tests/classval/ClassNameTest.java index a43c51f3236..f2a6ea5412c 100644 --- a/framework/tests/classval/ClassNameTest.java +++ b/framework/tests/classval/ClassNameTest.java @@ -1,22 +1,22 @@ import org.checkerframework.common.reflection.qual.ClassVal; public class ClassNameTest { - void test() throws Exception { - @ClassVal("Class$Inner") Object o; - @ClassVal("java.lang.String") Object o1; - @ClassVal("java.lang.String[]") Object o2; - @ClassVal("java.lang.String[][][]") Object o3; - @ClassVal("Class$Inner._") Object o8; + void test() throws Exception { + @ClassVal("Class$Inner") Object o; + @ClassVal("java.lang.String") Object o1; + @ClassVal("java.lang.String[]") Object o2; + @ClassVal("java.lang.String[][][]") Object o3; + @ClassVal("Class$Inner._") Object o8; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][]]") Object o4; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][][") Object o5; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][][]s") Object o6; - // :: error: (illegal.classname) - @ClassVal("java.lang.String[][][].") Object o7; - // :: error: (illegal.classname) - @ClassVal("java.lang..String") Object o9; - } + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][]]") Object o4; + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][][") Object o5; + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][][]s") Object o6; + // :: error: (illegal.classname) + @ClassVal("java.lang.String[][][].") Object o7; + // :: error: (illegal.classname) + @ClassVal("java.lang..String") Object o9; + } } diff --git a/framework/tests/classval/ClassValInferenceTest.java b/framework/tests/classval/ClassValInferenceTest.java index efa5aa68475..a7cd10d1484 100644 --- a/framework/tests/classval/ClassValInferenceTest.java +++ b/framework/tests/classval/ClassValInferenceTest.java @@ -1,66 +1,67 @@ -import java.util.ArrayList; -import java.util.List; import org.checkerframework.common.reflection.qual.ClassBound; import org.checkerframework.common.reflection.qual.ClassVal; +import java.util.ArrayList; +import java.util.List; + public class ClassValInferenceTest { - class Inner { - Inner() { - @ClassBound("ClassValInferenceTest$Inner") Class c1 = this.getClass(); - @ClassBound("ClassValInferenceTest") Class c2 = ClassValInferenceTest.this.getClass(); + class Inner { + Inner() { + @ClassBound("ClassValInferenceTest$Inner") Class c1 = this.getClass(); + @ClassBound("ClassValInferenceTest") Class c2 = ClassValInferenceTest.this.getClass(); + } } - } - public void classLiterals() { - @ClassVal("java.lang.Object") Class c1 = Object.class; - @ClassVal("java.lang.Object[]") Class c2 = Object[].class; - @ClassVal("java.lang.Object[][][]") Class c3 = Object[][][].class; - @ClassVal("ClassValInferenceTest$Inner") Class c4 = Inner.class; - @ClassVal("byte") Class c5 = byte.class; - } + public void classLiterals() { + @ClassVal("java.lang.Object") Class c1 = Object.class; + @ClassVal("java.lang.Object[]") Class c2 = Object[].class; + @ClassVal("java.lang.Object[][][]") Class c3 = Object[][][].class; + @ClassVal("ClassValInferenceTest$Inner") Class c4 = Inner.class; + @ClassVal("byte") Class c5 = byte.class; + } - public void classForName() throws ClassNotFoundException { - @ClassVal("ClassValInferenceTest$Inner") Class c = Class.forName("ClassValInferenceTest$Inner"); - @ClassVal("java.lang.Object") Class c1 = Class.forName("java.lang.Object"); - } + public void classForName() throws ClassNotFoundException { + @ClassVal("ClassValInferenceTest$Inner") Class c = Class.forName("ClassValInferenceTest$Inner"); + @ClassVal("java.lang.Object") Class c1 = Class.forName("java.lang.Object"); + } - boolean flag = true; + boolean flag = true; - public void classForNameStringVal() throws ClassNotFoundException { - Class c2; - if (flag) { - c2 = Class.forName("java.lang.Byte"); - } else { - c2 = Class.forName("java.lang.Integer"); + public void classForNameStringVal() throws ClassNotFoundException { + Class c2; + if (flag) { + c2 = Class.forName("java.lang.Byte"); + } else { + c2 = Class.forName("java.lang.Integer"); + } + @ClassVal({"java.lang.Byte", "java.lang.Integer"}) Class c3 = c2; } - @ClassVal({"java.lang.Byte", "java.lang.Integer"}) Class c3 = c2; - } - public > void testGetClass( - T typeVar, I intersect) { - @ClassBound("ClassValInferenceTest") Class c1 = this.getClass(); - @ClassBound("ClassValInferenceTest") Class c2 = getClass(); - String[] array = {"hello"}; - @ClassBound("java.lang.String[]") Class c3 = array.getClass(); - String[][][][] arrayMulti = null; - @ClassBound("java.lang.String[][][][]") Class c4 = arrayMulti.getClass(); - @ClassBound("java.lang.String") Class c5 = array[0].getClass(); - List list = null; - @ClassBound("java.util.List") Class c6 = list.getClass(); - @ClassBound("java.lang.Number") Class c7 = typeVar.getClass(); - @ClassBound("java.util.ArrayList") Class c8 = new ArrayList().getClass(); - List wildCardListLB = null; - List wildCardListUB = null; - @ClassBound("java.lang.Object") Class c9 = wildCardListLB.get(0).getClass(); - @ClassBound("java.lang.Number") Class c10 = wildCardListUB.get(0).getClass(); - Integer i = 0; - @ClassBound("java.lang.Integer") Class c11 = i.getClass(); - @ClassBound("java.lang.Object") Class c12 = intersect.getClass(); + public > void testGetClass( + T typeVar, I intersect) { + @ClassBound("ClassValInferenceTest") Class c1 = this.getClass(); + @ClassBound("ClassValInferenceTest") Class c2 = getClass(); + String[] array = {"hello"}; + @ClassBound("java.lang.String[]") Class c3 = array.getClass(); + String[][][][] arrayMulti = null; + @ClassBound("java.lang.String[][][][]") Class c4 = arrayMulti.getClass(); + @ClassBound("java.lang.String") Class c5 = array[0].getClass(); + List list = null; + @ClassBound("java.util.List") Class c6 = list.getClass(); + @ClassBound("java.lang.Number") Class c7 = typeVar.getClass(); + @ClassBound("java.util.ArrayList") Class c8 = new ArrayList().getClass(); + List wildCardListLB = null; + List wildCardListUB = null; + @ClassBound("java.lang.Object") Class c9 = wildCardListLB.get(0).getClass(); + @ClassBound("java.lang.Number") Class c10 = wildCardListUB.get(0).getClass(); + Integer i = 0; + @ClassBound("java.lang.Integer") Class c11 = i.getClass(); + @ClassBound("java.lang.Object") Class c12 = intersect.getClass(); - try { - } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) { - @ClassBound("java.lang.RuntimeException") Class c = ex.getClass(); + try { + } catch (NullPointerException | ArrayIndexOutOfBoundsException ex) { + @ClassBound("java.lang.RuntimeException") Class c = ex.getClass(); + } } - } } diff --git a/framework/tests/classval/ClassValSubtypingTest.java b/framework/tests/classval/ClassValSubtypingTest.java index 53d8ed3097c..1af41449e05 100644 --- a/framework/tests/classval/ClassValSubtypingTest.java +++ b/framework/tests/classval/ClassValSubtypingTest.java @@ -2,115 +2,115 @@ import org.checkerframework.common.reflection.qual.ClassVal; public class ClassValSubtypingTest { - @ClassVal("a") Object a = null; - - @ClassVal({"a", "b"}) Object ab = null; - - @ClassVal("c") Object c = null; - - @ClassVal({"c", "d"}) Object cd = null; - - Object unknown = null; - - void assignToUnknown() { - unknown = a; - unknown = ab; - unknown = c; - unknown = cd; - } - - void assignUnknown() { - // :: error: (assignment.type.incompatible) - a = unknown; - // :: error: (assignment.type.incompatible) - ab = unknown; - // :: error: (assignment.type.incompatible) - c = unknown; - // :: error: (assignment.type.incompatible) - cd = unknown; - } - - void assignments() { - // :: error: (assignment.type.incompatible) - a = ab; - ab = a; - // :: error: (assignment.type.incompatible) - a = c; - // :: error: (assignment.type.incompatible) - ab = c; - // :: error: (assignment.type.incompatible) - ab = cd; - } + @ClassVal("a") Object a = null; + + @ClassVal({"a", "b"}) Object ab = null; + + @ClassVal("c") Object c = null; + + @ClassVal({"c", "d"}) Object cd = null; + + Object unknown = null; + + void assignToUnknown() { + unknown = a; + unknown = ab; + unknown = c; + unknown = cd; + } + + void assignUnknown() { + // :: error: (assignment.type.incompatible) + a = unknown; + // :: error: (assignment.type.incompatible) + ab = unknown; + // :: error: (assignment.type.incompatible) + c = unknown; + // :: error: (assignment.type.incompatible) + cd = unknown; + } + + void assignments() { + // :: error: (assignment.type.incompatible) + a = ab; + ab = a; + // :: error: (assignment.type.incompatible) + a = c; + // :: error: (assignment.type.incompatible) + ab = c; + // :: error: (assignment.type.incompatible) + ab = cd; + } } class ClassBoundSubtypingTest { - @ClassBound("a") Object a = null; - - @ClassBound({"a", "b"}) Object ab = null; - - @ClassBound("c") Object c = null; - - @ClassBound({"c", "d"}) Object cd = null; - - Object unknown = null; - - void assignToUnknown() { - unknown = a; - unknown = ab; - unknown = c; - unknown = cd; - } - - void assignUnknown() { - // :: error: (assignment.type.incompatible) - a = unknown; - // :: error: (assignment.type.incompatible) - ab = unknown; - // :: error: (assignment.type.incompatible) - c = unknown; - // :: error: (assignment.type.incompatible) - cd = unknown; - } - - void assignments() { - // :: error: (assignment.type.incompatible) - a = ab; - ab = a; - // :: error: (assignment.type.incompatible) - a = c; - // :: error: (assignment.type.incompatible) - ab = c; - // :: error: (assignment.type.incompatible) - ab = cd; - } + @ClassBound("a") Object a = null; + + @ClassBound({"a", "b"}) Object ab = null; + + @ClassBound("c") Object c = null; + + @ClassBound({"c", "d"}) Object cd = null; + + Object unknown = null; + + void assignToUnknown() { + unknown = a; + unknown = ab; + unknown = c; + unknown = cd; + } + + void assignUnknown() { + // :: error: (assignment.type.incompatible) + a = unknown; + // :: error: (assignment.type.incompatible) + ab = unknown; + // :: error: (assignment.type.incompatible) + c = unknown; + // :: error: (assignment.type.incompatible) + cd = unknown; + } + + void assignments() { + // :: error: (assignment.type.incompatible) + a = ab; + ab = a; + // :: error: (assignment.type.incompatible) + a = c; + // :: error: (assignment.type.incompatible) + ab = c; + // :: error: (assignment.type.incompatible) + ab = cd; + } } class ClassValClassBoundSubtypingTest { - @ClassVal("a") Object a = null; + @ClassVal("a") Object a = null; - @ClassVal({"a", "b"}) Object ab = null; + @ClassVal({"a", "b"}) Object ab = null; - @ClassBound("a") Object aBound = null; + @ClassBound("a") Object aBound = null; - @ClassBound({"a", "b"}) Object abBound = null; + @ClassBound({"a", "b"}) Object abBound = null; - void assignments1() { - // :: error: (assignment.type.incompatible) - a = aBound; - // :: error: (assignment.type.incompatible) - ab = aBound; - // :: error: (assignment.type.incompatible) - a = abBound; - // :: error: (assignment.type.incompatible) - ab = abBound; - } + void assignments1() { + // :: error: (assignment.type.incompatible) + a = aBound; + // :: error: (assignment.type.incompatible) + ab = aBound; + // :: error: (assignment.type.incompatible) + a = abBound; + // :: error: (assignment.type.incompatible) + ab = abBound; + } - void assignments2() { - aBound = a; - // :: error: (assignment.type.incompatible) - aBound = ab; + void assignments2() { + aBound = a; + // :: error: (assignment.type.incompatible) + aBound = ab; - abBound = a; - abBound = ab; - } + abBound = a; + abBound = ab; + } } diff --git a/framework/tests/classval/GLBTest.java b/framework/tests/classval/GLBTest.java index 70fbb5aded2..a536ccfc3b9 100644 --- a/framework/tests/classval/GLBTest.java +++ b/framework/tests/classval/GLBTest.java @@ -2,24 +2,24 @@ import org.checkerframework.common.reflection.qual.ClassVal; public class GLBTest<@ClassVal({"A", "B"}) T extends Object> { - // This code is intented to cover the more complex branchs for the GLB calcuation. - // This only triggers the GLB calculation because of a hack in - // org.checkerframework.framework.util.AnnotatedTypes.addAnnotationsImpl() - // If that code changes, this code may not test GLB anymore. - // This code does not test correctness. Because no expresion is given the GLB as a type, - // it is impossible to test GLB for correctness. - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - GLBTest<@ClassVal({"A", "B", "C"}) ?> f1 = new GLBTest<@ClassVal({"A", "E"}) Object>(); - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - GLBTest<@ClassVal({"A", "B", "C"}) ?> f2 = new GLBTest<@ClassBound({"A", "E"}) Object>(); - // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) - GLBTest<@ClassBound({"A", "B", "C"}) ?> f3 = new GLBTest<@ClassBound({"A", "E"}) Object>(); + // This code is intented to cover the more complex branchs for the GLB calcuation. + // This only triggers the GLB calculation because of a hack in + // org.checkerframework.framework.util.AnnotatedTypes.addAnnotationsImpl() + // If that code changes, this code may not test GLB anymore. + // This code does not test correctness. Because no expresion is given the GLB as a type, + // it is impossible to test GLB for correctness. + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + GLBTest<@ClassVal({"A", "B", "C"}) ?> f1 = new GLBTest<@ClassVal({"A", "E"}) Object>(); + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + GLBTest<@ClassVal({"A", "B", "C"}) ?> f2 = new GLBTest<@ClassBound({"A", "E"}) Object>(); + // :: error: (type.argument.type.incompatible) :: error: (assignment.type.incompatible) + GLBTest<@ClassBound({"A", "B", "C"}) ?> f3 = new GLBTest<@ClassBound({"A", "E"}) Object>(); - < - @ClassVal({"A", "B", "C"}) CLASSVAL extends Object, - @ClassBound({"A", "B", "C"}) CLASSBOUND extends Object> - void test() { - GLBTest f1 = new GLBTest(); - GLBTest f2 = new GLBTest(); - } + < + @ClassVal({"A", "B", "C"}) CLASSVAL extends Object, + @ClassBound({"A", "B", "C"}) CLASSBOUND extends Object> + void test() { + GLBTest f1 = new GLBTest(); + GLBTest f2 = new GLBTest(); + } } diff --git a/framework/tests/compound-checker/CompoundBasicTest.java b/framework/tests/compound-checker/CompoundBasicTest.java index 807e13907b4..e396fdaea67 100644 --- a/framework/tests/compound-checker/CompoundBasicTest.java +++ b/framework/tests/compound-checker/CompoundBasicTest.java @@ -1,12 +1,12 @@ public class CompoundBasicTest { - // Random code just to make sure that the compound design pattern - // does not throw any exceptions. - Object field = new Object(); - String[] array = {"hello", "world"}; + // Random code just to make sure that the compound design pattern + // does not throw any exceptions. + Object field = new Object(); + String[] array = {"hello", "world"}; - void foo(int arg) { - field = array[0]; - field.toString(); - int a = 1 + arg; - } + void foo(int arg) { + field = array[0]; + field.toString(); + int a = 1 + arg; + } } diff --git a/framework/tests/compound-checker/MultiError.java b/framework/tests/compound-checker/MultiError.java index 6e35ac719c7..231846ac592 100644 --- a/framework/tests/compound-checker/MultiError.java +++ b/framework/tests/compound-checker/MultiError.java @@ -2,11 +2,11 @@ import org.checkerframework.common.value.qual.StringVal; public class MultiError { - // Testing that errors from multiple checkers are issued - // on the same compilation unit - // :: error: (unique.location.forbidden) - @Unique String[] array; + // Testing that errors from multiple checkers are issued + // on the same compilation unit + // :: error: (unique.location.forbidden) + @Unique String[] array; - // :: error: (assignment.type.incompatible) - @StringVal("hello") String s = "goodbye"; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String s = "goodbye"; } diff --git a/framework/tests/compound-checker/javac-error/JavaErrorTest.java b/framework/tests/compound-checker/javac-error/JavaErrorTest.java index eb8776fa3e5..478fe489666 100644 --- a/framework/tests/compound-checker/javac-error/JavaErrorTest.java +++ b/framework/tests/compound-checker/javac-error/JavaErrorTest.java @@ -1,12 +1,12 @@ public class JavaErrorTest { - // Checking that if one checker finds a Java error - // and therefor does not set a root, then - // checkers relying on that checker should also not run. - // otherwise, it might access a subchecker whose "root" has not been set. - // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) - void foo() { - Object a; - // :: error: variable a is already defined in method foo() - Object a; - } + // Checking that if one checker finds a Java error + // and therefor does not set a root, then + // checkers relying on that checker should also not run. + // otherwise, it might access a subchecker whose "root" has not been set. + // (See AnnotatedTypeFactory.setRoot(CompilationUnitTree) ) + void foo() { + Object a; + // :: error: variable a is already defined in method foo() + Object a; + } } diff --git a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java index 5172497a42b..b73ea47242b 100644 --- a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java +++ b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java @@ -3,248 +3,248 @@ import org.checkerframework.framework.testchecker.util.SuperQual; public class AnnotatedForTest { - /* - Test a mix of @SuppressWarnings with @AnnotatedFor. @SuppressWarnings should win, but only within the kinds of warnings it promises to suppress. It should win because - it is a specific intent of suppressing warnings, whereas NOT suppressing warnings using AnnotatedFor is a default behavior, and SW is a user-specified behavior. - */ - - // Test unannotated class initializer - no warnings should be issued - @SuperQual Object o1 = annotatedMethod(new Object()); - @SubQual Object o2 = annotatedMethod(new Object()); - Object o3 = unannotatedMethod(o2); - Object o4 = unannotatedMethod(o1); - - static @SuperQual Object so1; - static @SubQual Object so2; - static Object so3, so4; - - // Test unannotated static initializer block - no warnings should be issued - static { - so1 = staticAnnotatedMethod(new Object()); - so2 = staticAnnotatedMethod(new Object()); - so3 = staticUnannotatedMethod(so2); - so4 = staticUnannotatedMethod(so1); - } - - @SuperQual Object o5; - @SubQual Object o6; - Object o7, o8; - - // Test unannotated nonstatic initializer block - no warnings should be issued - { - o5 = annotatedMethod(new Object()); - o6 = annotatedMethod(new Object()); - o7 = unannotatedMethod(o6); - o8 = unannotatedMethod(o5); - } - - // This method is @AnnotatedFor("subtyping") so it can cause errors to be issued by calling - // other methods. - @AnnotatedFor("subtyping") - void method1() { - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. - @SuperQual Object o1 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = annotatedMethod(new Object()); + /* + Test a mix of @SuppressWarnings with @AnnotatedFor. @SuppressWarnings should win, but only within the kinds of warnings it promises to suppress. It should win because + it is a specific intent of suppressing warnings, whereas NOT suppressing warnings using AnnotatedFor is a default behavior, and SW is a user-specified behavior. + */ - // When calling unannotatedMethod, we expect the conservative defaults. - Object o3 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o4 = unannotatedMethod(o1); - - // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor - // annotation. - Object o5 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o6 = unannotatedMethod(o1); - - // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) - Object o7 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o8 = unannotatedMethod(o1); - } - - @SuppressWarnings("all") - @AnnotatedFor( - "subtyping") // Same as method1, but the @SuppressWarnings overrides the @AnnotatedFor. - void method2() { - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. + // Test unannotated class initializer - no warnings should be issued @SuperQual Object o1 = annotatedMethod(new Object()); @SubQual Object o2 = annotatedMethod(new Object()); - - // When calling unannotatedMethod, we expect the conservative defaults. Object o3 = unannotatedMethod(o2); Object o4 = unannotatedMethod(o1); - // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor - // annotation. - Object o5 = unannotatedMethod(o2); - Object o6 = unannotatedMethod(o1); - - // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) - Object o7 = unannotatedMethod(o2); - Object o8 = unannotatedMethod(o1); - } - - @SuppressWarnings("nullness") - @AnnotatedFor("subtyping") // Similar to method1. The @SuppressWarnings does not override the - // @AnnotatedFor because it suppressing warnings for a different typesystem. - void method3() { - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. - @SuperQual Object o1 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = annotatedMethod(new Object()); - } - - @AnnotatedFor("subtyping") - Object annotatedMethod(Object p) { - return new Object(); - } - - Object unannotatedMethod(Object p) { - return new Object(); - } - - @AnnotatedFor("subtyping") - static Object staticAnnotatedMethod(Object p) { - return new Object(); - } - - static Object staticUnannotatedMethod(Object p) { - return new Object(); - } - - @AnnotatedFor({}) - Object unannotatedMethod2(Object p) { - return new Object(); - } - - @AnnotatedFor("nullness") - Object annotatedForADifferentTypeSystemMethod(Object p) { - return new Object(); - } - - // Annotated for more than one type system - @AnnotatedFor({"nullness", "subtyping"}) - void method4() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); - } - - // Different way of writing the checker name - @AnnotatedFor("SubtypingChecker") - void method5() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); - } - - // Different way of writing the checker name - @AnnotatedFor("org.checkerframework.common.subtyping.SubtypingChecker") - void method6() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); - } - - // Every method in this class should issue warnings for subtyping even if it's not marked with - // @AnnotatedFor, unless it's marked with @SuppressWarnings. - @AnnotatedFor("subtyping") - class annotatedClass { - // Test annotated class initializer - // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since - // @SuperQual is annotated with @DefaultQualifierInHierarchy. - @SuperQual Object o1 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = annotatedMethod(new Object()); + static @SuperQual Object so1; + static @SubQual Object so2; + static Object so3, so4; - // When calling unannotatedMethod, we expect the conservative defaults. - Object o3 = unannotatedMethod(o2); - // :: error: (argument.type.incompatible) - Object o4 = unannotatedMethod(o1); + // Test unannotated static initializer block - no warnings should be issued + static { + so1 = staticAnnotatedMethod(new Object()); + so2 = staticAnnotatedMethod(new Object()); + so3 = staticUnannotatedMethod(so2); + so4 = staticUnannotatedMethod(so1); + } @SuperQual Object o5; @SubQual Object o6; Object o7, o8; - // Test annotated nonstatic initializer block + // Test unannotated nonstatic initializer block - no warnings should be issued { - o5 = annotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - o6 = annotatedMethod(new Object()); - o7 = unannotatedMethod(o6); - // :: error: (argument.type.incompatible) - o8 = unannotatedMethod(o5); + o5 = annotatedMethod(new Object()); + o6 = annotatedMethod(new Object()); + o7 = unannotatedMethod(o6); + o8 = unannotatedMethod(o5); } + // This method is @AnnotatedFor("subtyping") so it can cause errors to be issued by calling + // other methods. + @AnnotatedFor("subtyping") void method1() { - // :: error: (assignment.type.incompatible) - @SubQual Object o2 = new @SuperQual Object(); + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. + @SuperQual Object o1 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = annotatedMethod(new Object()); + + // When calling unannotatedMethod, we expect the conservative defaults. + Object o3 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o4 = unannotatedMethod(o1); + + // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor + // annotation. + Object o5 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o6 = unannotatedMethod(o1); + + // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) + Object o7 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o8 = unannotatedMethod(o1); } @SuppressWarnings("all") + @AnnotatedFor( + "subtyping") // Same as method1, but the @SuppressWarnings overrides the @AnnotatedFor. void method2() { - @SubQual Object o2 = new @SuperQual Object(); + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. + @SuperQual Object o1 = annotatedMethod(new Object()); + @SubQual Object o2 = annotatedMethod(new Object()); + + // When calling unannotatedMethod, we expect the conservative defaults. + Object o3 = unannotatedMethod(o2); + Object o4 = unannotatedMethod(o1); + + // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor + // annotation. + Object o5 = unannotatedMethod(o2); + Object o6 = unannotatedMethod(o1); + + // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({}) + Object o7 = unannotatedMethod(o2); + Object o8 = unannotatedMethod(o1); } - } - @AnnotatedFor("subtyping") - static class staticAnnotatedForClass { - static @SuperQual Object so1; - static @SubQual Object so2; - static Object so3, so4; + @SuppressWarnings("nullness") + @AnnotatedFor("subtyping") // Similar to method1. The @SuppressWarnings does not override the + // @AnnotatedFor because it suppressing warnings for a different typesystem. + void method3() { + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. + @SuperQual Object o1 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = annotatedMethod(new Object()); + } - // Test annotated static initializer block - static { - so1 = staticAnnotatedMethod(new Object()); - // :: error: (assignment.type.incompatible) - so2 = staticAnnotatedMethod(new Object()); - so3 = staticUnannotatedMethod(so2); - // :: error: (argument.type.incompatible) - so4 = staticUnannotatedMethod(so1); - } - } - - @SuppressWarnings("all") // @SuppressWarnings("all") overrides @AnnotatedFor("subtyping") - @AnnotatedFor("subtyping") - class annotatedAndWarningsSuppressedClass { - // Test annotated class initializer whose warnings are suppressed. - @SuperQual Object o1 = annotatedMethod(new Object()); - @SubQual Object o2 = annotatedMethod(new Object()); - Object o3 = unannotatedMethod(o2); - Object o4 = unannotatedMethod(o1); + @AnnotatedFor("subtyping") + Object annotatedMethod(Object p) { + return new Object(); + } - @SuperQual Object o5; - @SubQual Object o6; - Object o7, o8; + Object unannotatedMethod(Object p) { + return new Object(); + } - // Test annotated nonstatic initializer block whose warnings are suppressed. - { - o5 = annotatedMethod(new Object()); - o6 = annotatedMethod(new Object()); - o7 = unannotatedMethod(o6); - o8 = unannotatedMethod(o5); + @AnnotatedFor("subtyping") + static Object staticAnnotatedMethod(Object p) { + return new Object(); } - void method1() { - @SubQual Object o2 = new @SuperQual Object(); + static Object staticUnannotatedMethod(Object p) { + return new Object(); } - } - @SuppressWarnings("all") - @AnnotatedFor("subtyping") - static class staticAnnotatedAndWarningsSuppressedClass { - static @SuperQual Object so1; - static @SubQual Object so2; - static Object so3, so4; + @AnnotatedFor({}) + Object unannotatedMethod2(Object p) { + return new Object(); + } - // Test annotated static initializer block whose warnings are suppressed. - static { - so1 = staticAnnotatedMethod(new Object()); - so2 = staticAnnotatedMethod(new Object()); - so3 = staticUnannotatedMethod(so2); - so4 = staticUnannotatedMethod(so1); + @AnnotatedFor("nullness") + Object annotatedForADifferentTypeSystemMethod(Object p) { + return new Object(); + } + + // Annotated for more than one type system + @AnnotatedFor({"nullness", "subtyping"}) + void method4() { + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); + } + + // Different way of writing the checker name + @AnnotatedFor("SubtypingChecker") + void method5() { + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); + } + + // Different way of writing the checker name + @AnnotatedFor("org.checkerframework.common.subtyping.SubtypingChecker") + void method6() { + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); + } + + // Every method in this class should issue warnings for subtyping even if it's not marked with + // @AnnotatedFor, unless it's marked with @SuppressWarnings. + @AnnotatedFor("subtyping") + class annotatedClass { + // Test annotated class initializer + // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since + // @SuperQual is annotated with @DefaultQualifierInHierarchy. + @SuperQual Object o1 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = annotatedMethod(new Object()); + + // When calling unannotatedMethod, we expect the conservative defaults. + Object o3 = unannotatedMethod(o2); + // :: error: (argument.type.incompatible) + Object o4 = unannotatedMethod(o1); + + @SuperQual Object o5; + @SubQual Object o6; + Object o7, o8; + + // Test annotated nonstatic initializer block + { + o5 = annotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + o6 = annotatedMethod(new Object()); + o7 = unannotatedMethod(o6); + // :: error: (argument.type.incompatible) + o8 = unannotatedMethod(o5); + } + + void method1() { + // :: error: (assignment.type.incompatible) + @SubQual Object o2 = new @SuperQual Object(); + } + + @SuppressWarnings("all") + void method2() { + @SubQual Object o2 = new @SuperQual Object(); + } + } + + @AnnotatedFor("subtyping") + static class staticAnnotatedForClass { + static @SuperQual Object so1; + static @SubQual Object so2; + static Object so3, so4; + + // Test annotated static initializer block + static { + so1 = staticAnnotatedMethod(new Object()); + // :: error: (assignment.type.incompatible) + so2 = staticAnnotatedMethod(new Object()); + so3 = staticUnannotatedMethod(so2); + // :: error: (argument.type.incompatible) + so4 = staticUnannotatedMethod(so1); + } + } + + @SuppressWarnings("all") // @SuppressWarnings("all") overrides @AnnotatedFor("subtyping") + @AnnotatedFor("subtyping") + class annotatedAndWarningsSuppressedClass { + // Test annotated class initializer whose warnings are suppressed. + @SuperQual Object o1 = annotatedMethod(new Object()); + @SubQual Object o2 = annotatedMethod(new Object()); + Object o3 = unannotatedMethod(o2); + Object o4 = unannotatedMethod(o1); + + @SuperQual Object o5; + @SubQual Object o6; + Object o7, o8; + + // Test annotated nonstatic initializer block whose warnings are suppressed. + { + o5 = annotatedMethod(new Object()); + o6 = annotatedMethod(new Object()); + o7 = unannotatedMethod(o6); + o8 = unannotatedMethod(o5); + } + + void method1() { + @SubQual Object o2 = new @SuperQual Object(); + } + } + + @SuppressWarnings("all") + @AnnotatedFor("subtyping") + static class staticAnnotatedAndWarningsSuppressedClass { + static @SuperQual Object so1; + static @SubQual Object so2; + static Object so3, so4; + + // Test annotated static initializer block whose warnings are suppressed. + static { + so1 = staticAnnotatedMethod(new Object()); + so2 = staticAnnotatedMethod(new Object()); + so3 = staticUnannotatedMethod(so2); + so4 = staticUnannotatedMethod(so1); + } } - } } diff --git a/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java b/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java index 6c63f77d9a7..17ee82675c0 100644 --- a/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java +++ b/framework/tests/defaulting/lowerbound/LowerBoundDefaulting.java @@ -11,57 +11,57 @@ class MyExplicitArray {} public class LowerBoundDefaulting { - // IMP1 is of type IMP1 [extends @LbTop super @LbImplicit] - public void implicitsTypeVar() { + // IMP1 is of type IMP1 [extends @LbTop super @LbImplicit] + public void implicitsTypeVar() { - // should fail because @LbImplicit is below @LbTop - @LbTop MyArrayList<@LbTop ? extends @LbTop String> itLowerBoundIncompatible = - // :: error: (assignment.type.incompatible) - new MyArrayList(); + // should fail because @LbImplicit is below @LbTop + @LbTop MyArrayList<@LbTop ? extends @LbTop String> itLowerBoundIncompatible = + // :: error: (assignment.type.incompatible) + new MyArrayList(); - @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> itLowerBoundStillIncompatible = - // :: error: (assignment.type.incompatible) - new MyArrayList(); + @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> itLowerBoundStillIncompatible = + // :: error: (assignment.type.incompatible) + new MyArrayList(); - @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> itLowerBoundCompatible = - new MyArrayList(); - } + @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> itLowerBoundCompatible = + new MyArrayList(); + } - public void implicitsWildcard(MyArrayList myArrayList) { + public void implicitsWildcard(MyArrayList myArrayList) { - // should fail because @LbImplicit is below @LbTop - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbTop ? extends @LbTop CharSequence> iwLowerBoundIncompatible = myArrayList; + // should fail because @LbImplicit is below @LbTop + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbTop ? extends @LbTop CharSequence> iwLowerBoundIncompatible = myArrayList; - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbExplicit ? extends @LbTop CharSequence> iwLowerBoundCompatible = myArrayList; + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbExplicit ? extends @LbTop CharSequence> iwLowerBoundCompatible = myArrayList; - @LbTop MyArrayList<@LbImplicit ? extends @LbTop CharSequence> iwLowerBoundStillCompatible = - myArrayList; - } + @LbTop MyArrayList<@LbImplicit ? extends @LbTop CharSequence> iwLowerBoundStillCompatible = + myArrayList; + } - public void implicitExtendBoundedWildcard(MyArrayList iebList) { + public void implicitExtendBoundedWildcard(MyArrayList iebList) { - // should fail because @LbImplicit is below @LbTop - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbTop ? extends @LbTop String> iebLowerBoundIncompatible = iebList; + // should fail because @LbImplicit is below @LbTop + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbTop ? extends @LbTop String> iebLowerBoundIncompatible = iebList; - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> iebLowerBoundStillIncompatible = iebList; + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbExplicit ? extends @LbTop String> iebLowerBoundStillIncompatible = iebList; - @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> iebLowerBoundCompatible = iebList; - } + @LbTop MyArrayList<@LbImplicit ? extends @LbTop String> iebLowerBoundCompatible = iebList; + } - // MyArrayList<@LbTop MAL extends @LbTop CharSequence> - // elbLsit is MyArrayList<@LbTop ? super @LbExplicit String> - // capture is MyArrayList - // The super bound is the LUB of @LbTop and @LbExplicit. - public void explicitLowerBoundedWildcard(MyArrayList elbList) { - // :: error: (assignment.type.incompatible) - @LbTop MyArrayList<@LbBottom ? super @LbBottom String> iebLowerBoundIncompatible = elbList; + // MyArrayList<@LbTop MAL extends @LbTop CharSequence> + // elbLsit is MyArrayList<@LbTop ? super @LbExplicit String> + // capture is MyArrayList + // The super bound is the LUB of @LbTop and @LbExplicit. + public void explicitLowerBoundedWildcard(MyArrayList elbList) { + // :: error: (assignment.type.incompatible) + @LbTop MyArrayList<@LbBottom ? super @LbBottom String> iebLowerBoundIncompatible = elbList; - @LbTop MyArrayList<@LbTop ? super @LbExplicit String> iebLowerBoundStillIncompatible = elbList; + @LbTop MyArrayList<@LbTop ? super @LbExplicit String> iebLowerBoundStillIncompatible = elbList; - @LbTop MyArrayList<@LbTop ? super @LbImplicit String> iebLowerBoundCompatible = elbList; - } + @LbTop MyArrayList<@LbTop ? super @LbImplicit String> iebLowerBoundCompatible = elbList; + } } diff --git a/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java b/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java index 6e3767b96ac..3f46afe6594 100644 --- a/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java +++ b/framework/tests/defaulting/upperbound/UpperBoundDefaulting.java @@ -13,47 +13,48 @@ class MyExplicitArray {} public class UpperBoundDefaulting { - public void explicitUpperBoundTypeVar() { - MyArrayList<@UbBottom ? extends @UbBottom Object> eubBottomToBottom = - // :: error: (assignment.type.incompatible) - new MyArrayList(); + public void explicitUpperBoundTypeVar() { + MyArrayList<@UbBottom ? extends @UbBottom Object> eubBottomToBottom = + // :: error: (assignment.type.incompatible) + new MyArrayList(); - MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = - new MyArrayList(); + MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = + new MyArrayList(); - MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = - // :: error: (assignment.type.incompatible) - new MyArrayList(); - } + MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = + // :: error: (assignment.type.incompatible) + new MyArrayList(); + } - public void implicitsWildcard(MyArrayList myArrayList) { + public void implicitsWildcard(MyArrayList myArrayList) { - @UbTop MyArrayList<@UbBottom ? extends @UbTop CharSequence> iwLowerBoundIncompatible = myArrayList; + @UbTop MyArrayList<@UbBottom ? extends @UbTop CharSequence> iwLowerBoundIncompatible = myArrayList; - @UbTop MyArrayList<@UbBottom ? extends @UbExplicit CharSequence> iwLowerBoundCompatible = myArrayList; + @UbTop MyArrayList<@UbBottom ? extends @UbExplicit CharSequence> iwLowerBoundCompatible = + myArrayList; - @UbTop MyArrayList<@UbBottom ? extends @UbImplicit CharSequence> iwLowerBoundStillCompatible = - // :: error: (assignment.type.incompatible) - myArrayList; - } + @UbTop MyArrayList<@UbBottom ? extends @UbImplicit CharSequence> iwLowerBoundStillCompatible = + // :: error: (assignment.type.incompatible) + myArrayList; + } - public void implicitExtendBoundedWildcard(MyArrayList iebList) { + public void implicitExtendBoundedWildcard(MyArrayList iebList) { - @UbTop MyArrayList<@UbBottom ? extends @UbTop String> iebLowerBoundIncompatible = iebList; + @UbTop MyArrayList<@UbBottom ? extends @UbTop String> iebLowerBoundIncompatible = iebList; - MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = iebList; + MyArrayList<@UbBottom ? extends @UbExplicit Object> eubExplicitToBottom = iebList; - // :: error: (assignment.type.incompatible) - MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = iebList; - } + // :: error: (assignment.type.incompatible) + MyArrayList<@UbBottom ? extends @UbImplicit Object> eubImplicitToBottom = iebList; + } - public void explicitLowerBoundedWildcard(MyArrayList elbList) { - @UbTop MyArrayList<@UbTop ? super @UbBottom String> iebLowerBoundIncompatible = elbList; + public void explicitLowerBoundedWildcard(MyArrayList elbList) { + @UbTop MyArrayList<@UbTop ? super @UbBottom String> iebLowerBoundIncompatible = elbList; - // Upper bound: GLB(@UbExplicit, @UbImplicit), Lower bound: @UbBottom. - // :: error: (assignment.type.incompatible) - @UbTop MyArrayList<@UbImplicit ? super @UbBottom String> iebLowerBoundStillIncompatible = elbList; + // Upper bound: GLB(@UbExplicit, @UbImplicit), Lower bound: @UbBottom. + // :: error: (assignment.type.incompatible) + @UbTop MyArrayList<@UbImplicit ? super @UbBottom String> iebLowerBoundStillIncompatible = elbList; - @UbTop MyArrayList<@UbExplicit ? super @UbBottom String> iebLowerBoundCompatible = elbList; - } + @UbTop MyArrayList<@UbExplicit ? super @UbBottom String> iebLowerBoundCompatible = elbList; + } } diff --git a/framework/tests/flow/AnnotationAliasing.java b/framework/tests/flow/AnnotationAliasing.java index f75bda997e4..df32dc0b22f 100644 --- a/framework/tests/flow/AnnotationAliasing.java +++ b/framework/tests/flow/AnnotationAliasing.java @@ -5,42 +5,42 @@ /** Various tests for annotation aliasing. */ public class AnnotationAliasing { - String f1, f2, f3; - - @Pure - int pure1() { - return 1; - } - - @org.jmlspecs.annotation.Pure - int pure2() { - return 1; - } - - // a method that is not pure (no annotation) - void nonpure() {} - - @Pure - String t1() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - nonpure(); - return ""; - } - - @org.jmlspecs.annotation.Pure - String t2() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - nonpure(); - return ""; - } - - // check aliasing of Pure - void t1(@Odd String p1, String p2) { - f1 = p1; - @Odd String l2 = f1; - pure1(); - @Odd String l3 = f1; - pure2(); - @Odd String l4 = f1; - } + String f1, f2, f3; + + @Pure + int pure1() { + return 1; + } + + @org.jmlspecs.annotation.Pure + int pure2() { + return 1; + } + + // a method that is not pure (no annotation) + void nonpure() {} + + @Pure + String t1() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + nonpure(); + return ""; + } + + @org.jmlspecs.annotation.Pure + String t2() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + nonpure(); + return ""; + } + + // check aliasing of Pure + void t1(@Odd String p1, String p2) { + f1 = p1; + @Odd String l2 = f1; + pure1(); + @Odd String l3 = f1; + pure2(); + @Odd String l4 = f1; + } } diff --git a/framework/tests/flow/ArrayFlow.java b/framework/tests/flow/ArrayFlow.java index 321dc31872b..f4a7e8948f2 100644 --- a/framework/tests/flow/ArrayFlow.java +++ b/framework/tests/flow/ArrayFlow.java @@ -3,22 +3,22 @@ public class ArrayFlow { - // array accesses - void t1(@Odd String a1[], String a2[], @Odd String odd) { - String l1 = a1[0]; + // array accesses + void t1(@Odd String a1[], String a2[], @Odd String odd) { + String l1 = a1[0]; - // :: error: (assignment.type.incompatible) - @Odd String l2 = a2[0]; + // :: error: (assignment.type.incompatible) + @Odd String l2 = a2[0]; - if (a2[0] == odd) { - @Odd String l3 = a2[0]; - } + if (a2[0] == odd) { + @Odd String l3 = a2[0]; + } - int i = 1; - a2[i] = odd; - @Odd String l4 = a2[i]; - i = 2; - // :: error: (assignment.type.incompatible) - @Odd String l5 = a2[i]; - } + int i = 1; + a2[i] = odd; + @Odd String l4 = a2[i]; + i = 2; + // :: error: (assignment.type.incompatible) + @Odd String l5 = a2[i]; + } } diff --git a/framework/tests/flow/Basic.java b/framework/tests/flow/Basic.java index 87f1c1bd3ab..e04f30286d3 100644 --- a/framework/tests/flow/Basic.java +++ b/framework/tests/flow/Basic.java @@ -2,48 +2,48 @@ public class Basic { - @Odd String field; - - void test(@Odd String param) { - String local = ""; - local = param; - field = local; - - String r = field; - } - - void testIf(@Odd String ifParam) { - String local = ""; - if (field != null) { - local = ifParam; - } else { - local = ifParam; + @Odd String field; + + void test(@Odd String param) { + String local = ""; + local = param; + field = local; + + String r = field; } - String r = local; - } + void testIf(@Odd String ifParam) { + String local = ""; + if (field != null) { + local = ifParam; + } else { + local = ifParam; + } - void testWhile(@Odd String whileParam) { - String local = whileParam; - while (local != "foo") { - local = ""; + String r = local; } - String r = local; - } + void testWhile(@Odd String whileParam) { + String local = whileParam; + while (local != "foo") { + local = ""; + } - void testWhile2(@Odd String whileParam) { - String local = ""; - while (local != "foo") { - local = whileParam; + String r = local; } - String r = local; - } + void testWhile2(@Odd String whileParam) { + String local = ""; + while (local != "foo") { + local = whileParam; + } - void testCompountAssignment(@Odd String odd) { - String nonOdd = odd; - nonOdd += "kj"; // nonOdd as rValue is not Odd necessarily! - nonOdd = "m"; - } + String r = local; + } + + void testCompountAssignment(@Odd String odd) { + String nonOdd = odd; + nonOdd += "kj"; // nonOdd as rValue is not Odd necessarily! + nonOdd = "m"; + } } diff --git a/framework/tests/flow/Basic2.java b/framework/tests/flow/Basic2.java index 6b38304cabb..2827906bf00 100644 --- a/framework/tests/flow/Basic2.java +++ b/framework/tests/flow/Basic2.java @@ -1,234 +1,235 @@ -import java.util.List; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.framework.test.*; import org.checkerframework.framework.testchecker.util.*; +import java.util.List; + public class Basic2 { - // basic tests to make sure everything works - void t1(@Odd String p1, String p2) { - String l1 = p1; - // :: error: (assignment.type.incompatible) - @Odd String l2 = p2; - } - - // basic local variable flow sensitivity - void t2(@Odd String p1, String p2, boolean b1) { - String l1 = p1; - if (b1) { - l1 = p2; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = l1; - - l1 = p1; - while (b1) { - l1 = p2; - } - // :: error: (assignment.type.incompatible) - @Odd String l4 = l1; - } - - void t2b(@Odd String p1, String p2, @Odd String p3, boolean b1) { - String l1 = p1; - - if (b1) { - l1 = p3; - } - @Odd String l3 = l1; - - while (b1) { - l1 = p3; - } - @Odd String l4 = l1; - } - - // return statement - void t3(@Odd String p1, String p2, boolean b1) { - String l1 = p1; - if (b1) { - l1 = p2; - return; - } - @Odd String l3 = l1; - } - - // simple throw statement - void t4(@Odd String p1, String p2, boolean b1) { - String l1 = p1; - if (b1) { - l1 = p2; - throw new RuntimeException(); - } - @Odd String l3 = l1; - } - - class C { - C c; - String f1, f2, f3; - @Odd String g1, g2, g3; - } - - // fields - void t5(@Odd String p1, String p2, boolean b1, C c1, C c2) { - c1.f1 = p1; - @Odd String l1 = c1.f1; - c2.f2 = p2; // assignment to f2 does not change knowledge about f1 - c1.f2 = p2; - @Odd String l2 = c1.f1; - c2.f1 = p2; - // :: error: (assignment.type.incompatible) - @Odd String l3 = c1.f1; - } - - // fields - void t6(@Odd String p1, String p2, boolean b1, C c1, C c2) { - c1.f1 = p1; - c1.c.f1 = p1; - @Odd String l2 = c1.c.f1; - c1.f1 = p2; - // :: error: (assignment.type.incompatible) - @Odd String l3 = c1.f1; - - c1.f1 = p1; - c1.c.f1 = p1; - @Odd String l4 = c1.c.f1; - c2.c = c2; - // :: error: (assignment.type.incompatible) - @Odd String l5 = c1.f1; - // :: error: (assignment.type.incompatible) - @Odd String l6 = c1.c.f1; - } - - // fields - void t6b(@Odd String p1, String p2, boolean b1, C c1, C c2) { - if (b1) { - c1.f1 = p1; - } - // :: error: (assignment.type.incompatible) - @Odd String l1 = c1.f1; - - if (b1) { - c1.f1 = p1; - } else { - c1.f1 = p1; - } - @Odd String l2 = c1.f1; - } - - // method calls - void nonpure() {} - - @Pure - int pure() { - return 1; - } - - void t7(@Odd String p1, String p2, boolean b1, C c1, C c2) { - c1.f1 = p1; - nonpure(); - // :: error: (assignment.type.incompatible) - @Odd String l1 = c1.f1; - - c1.f1 = p1; - pure(); - @Odd String l2 = c1.f1; - } - - // array accesses - void t8(@Odd String a1[], String a2[], String p3) { - String l1 = a1[0]; - - // :: error: (assignment.type.incompatible) - @Odd String l2 = a2[0]; - - @Odd String l3 = l1; - - a1[0] = l1; - a1[1] = l3; - - // :: error: (assignment.type.incompatible) - a1[2] = p3; - - a2[0] = l1; - a2[1] = l3; - a2[2] = p3; - } - - // self type - void t9(@Odd Basic2 this) { - @Odd Basic2 l1 = this; - } - - // generics - public void t10a(T p1, @Odd T p2) { - T l1 = p1; - p1 = l1; - T l2 = p2; - p2 = l2; - // :: error: (assignment.type.incompatible) - @Odd T l3 = p1; - @Odd T l4 = p2; - } - - public void t10b(T p1, @Odd T p2) { - T l1 = p1; - p1 = l1; - T l2 = p2; - p2 = l2; - @Odd T l3 = p1; - @Odd T l4 = p2; - } - - // for-each loop - void t11(@Odd String p1, String p2, List list, List<@Odd String> oddList) { - // :: error: (enhancedfor.type.incompatible) - for (@Odd String i : list) {} - for (@Odd String i : oddList) { - @Odd String l1 = i; - } - for (@Odd String i : oddList) { - // :: error: (assignment.type.incompatible) - i = p2; - } - for (String i : oddList) { - // @Odd String l3 = i; - } - } - - // cast without annotations - void t12(@Odd String p1, String p2, boolean b1) { - @Odd String l1 = (String) p1; - } - - // final fields - class CF { - final String f1; - CF c; + // basic tests to make sure everything works + void t1(@Odd String p1, String p2) { + String l1 = p1; + // :: error: (assignment.type.incompatible) + @Odd String l2 = p2; + } + + // basic local variable flow sensitivity + void t2(@Odd String p1, String p2, boolean b1) { + String l1 = p1; + if (b1) { + l1 = p2; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = l1; + + l1 = p1; + while (b1) { + l1 = p2; + } + // :: error: (assignment.type.incompatible) + @Odd String l4 = l1; + } + + void t2b(@Odd String p1, String p2, @Odd String p3, boolean b1) { + String l1 = p1; + + if (b1) { + l1 = p3; + } + @Odd String l3 = l1; + + while (b1) { + l1 = p3; + } + @Odd String l4 = l1; + } + + // return statement + void t3(@Odd String p1, String p2, boolean b1) { + String l1 = p1; + if (b1) { + l1 = p2; + return; + } + @Odd String l3 = l1; + } + + // simple throw statement + void t4(@Odd String p1, String p2, boolean b1) { + String l1 = p1; + if (b1) { + l1 = p2; + throw new RuntimeException(); + } + @Odd String l3 = l1; + } + + class C { + C c; + String f1, f2, f3; + @Odd String g1, g2, g3; + } + + // fields + void t5(@Odd String p1, String p2, boolean b1, C c1, C c2) { + c1.f1 = p1; + @Odd String l1 = c1.f1; + c2.f2 = p2; // assignment to f2 does not change knowledge about f1 + c1.f2 = p2; + @Odd String l2 = c1.f1; + c2.f1 = p2; + // :: error: (assignment.type.incompatible) + @Odd String l3 = c1.f1; + } + + // fields + void t6(@Odd String p1, String p2, boolean b1, C c1, C c2) { + c1.f1 = p1; + c1.c.f1 = p1; + @Odd String l2 = c1.c.f1; + c1.f1 = p2; + // :: error: (assignment.type.incompatible) + @Odd String l3 = c1.f1; + + c1.f1 = p1; + c1.c.f1 = p1; + @Odd String l4 = c1.c.f1; + c2.c = c2; + // :: error: (assignment.type.incompatible) + @Odd String l5 = c1.f1; + // :: error: (assignment.type.incompatible) + @Odd String l6 = c1.c.f1; + } + + // fields + void t6b(@Odd String p1, String p2, boolean b1, C c1, C c2) { + if (b1) { + c1.f1 = p1; + } + // :: error: (assignment.type.incompatible) + @Odd String l1 = c1.f1; + + if (b1) { + c1.f1 = p1; + } else { + c1.f1 = p1; + } + @Odd String l2 = c1.f1; + } + // method calls void nonpure() {} - CF(@Odd String p1) { - f1 = p1; - nonpure(); - @Odd String l1 = f1; + @Pure + int pure() { + return 1; } - void CF_t1(@Odd String p1, CF o) { - if (f1 == p1) { + void t7(@Odd String p1, String p2, boolean b1, C c1, C c2) { + c1.f1 = p1; nonpure(); - @Odd String l1 = f1; - } + // :: error: (assignment.type.incompatible) + @Odd String l1 = c1.f1; + + c1.f1 = p1; + pure(); + @Odd String l2 = c1.f1; + } + + // array accesses + void t8(@Odd String a1[], String a2[], String p3) { + String l1 = a1[0]; + + // :: error: (assignment.type.incompatible) + @Odd String l2 = a2[0]; + + @Odd String l3 = l1; + + a1[0] = l1; + a1[1] = l3; + + // :: error: (assignment.type.incompatible) + a1[2] = p3; + + a2[0] = l1; + a2[1] = l3; + a2[2] = p3; + } + + // self type + void t9(@Odd Basic2 this) { + @Odd Basic2 l1 = this; + } + + // generics + public void t10a(T p1, @Odd T p2) { + T l1 = p1; + p1 = l1; + T l2 = p2; + p2 = l2; + // :: error: (assignment.type.incompatible) + @Odd T l3 = p1; + @Odd T l4 = p2; + } + + public void t10b(T p1, @Odd T p2) { + T l1 = p1; + p1 = l1; + T l2 = p2; + p2 = l2; + @Odd T l3 = p1; + @Odd T l4 = p2; + } + + // for-each loop + void t11(@Odd String p1, String p2, List list, List<@Odd String> oddList) { + // :: error: (enhancedfor.type.incompatible) + for (@Odd String i : list) {} + for (@Odd String i : oddList) { + @Odd String l1 = i; + } + for (@Odd String i : oddList) { + // :: error: (assignment.type.incompatible) + i = p2; + } + for (String i : oddList) { + // @Odd String l3 = i; + } + } + + // cast without annotations + void t12(@Odd String p1, String p2, boolean b1) { + @Odd String l1 = (String) p1; + } + + // final fields + class CF { + final String f1; + CF c; + + void nonpure() {} + + CF(@Odd String p1) { + f1 = p1; + nonpure(); + @Odd String l1 = f1; + } + + void CF_t1(@Odd String p1, CF o) { + if (f1 == p1) { + nonpure(); + @Odd String l1 = f1; + } + } } - } - // final fields with initializer - class A { - private final @Odd String f1 = null; - private final String f2 = f1; + // final fields with initializer + class A { + private final @Odd String f1 = null; + private final String f2 = f1; - void A_t1() { - @Odd String l1 = f2; + void A_t1() { + @Odd String l1 = f2; + } } - } } diff --git a/framework/tests/flow/ContractsOverriding.java b/framework/tests/flow/ContractsOverriding.java index d47363d2a7c..b3a94eec230 100644 --- a/framework/tests/flow/ContractsOverriding.java +++ b/framework/tests/flow/ContractsOverriding.java @@ -7,184 +7,184 @@ import org.checkerframework.framework.testchecker.util.RequiresOdd; public class ContractsOverriding { - static class Super { - String f, g; + static class Super { + String f, g; - void m1() {} + void m1() {} - void m2() {} + void m2() {} - @RequiresOdd("g") - void m3() {} + @RequiresOdd("g") + void m3() {} - @RequiresOdd("g") - void m3b() {} + @RequiresOdd("g") + void m3b() {} - @RequiresOdd("f") - void m4() {} - } - - static class Sub extends Super { - String g; - - @Override - @RequiresOdd("f") - // :: error: (contracts.precondition.override.invalid) - void m1() {} - - @Override - @RequiresQualifier(expression = "f", qualifier = Odd.class) - // :: error: (contracts.precondition.override.invalid) - void m2() {} - - @Override - // g is a different field than in the supertype - @RequiresOdd("g") - // :: error: (contracts.precondition.override.invalid) - void m3() {} - - @Override - @RequiresOdd("super.g") - void m3b() {} - - @Override - // use different string to refer to 'f' and RequiresQualifier instead of RequiresOdd - @RequiresQualifier(expression = "this.f", qualifier = Odd.class) - void m4() {} - } - - static class Sub2 extends Super2 { - String g; - - @Override - // :: error: (contracts.postcondition.not.satisfied) - void m1() {} - - @Override - // :: error: (contracts.postcondition.not.satisfied) - void m2() {} - - @Override - @EnsuresOdd("g") - // :: error: (contracts.postcondition.override.invalid) - void m3() { - g = odd; - } - - @Override - @EnsuresOdd("f") - void m4() { - super.m4(); - } - } - - static class Super2 { - String f, g; - @Odd String odd; - - @EnsuresOdd("f") - void m1() { - f = odd; + @RequiresOdd("f") + void m4() {} } - @EnsuresQualifier(expression = "f", qualifier = Odd.class) - void m2() { - f = odd; - } + static class Sub extends Super { + String g; - @EnsuresOdd("g") - void m3() { - g = odd; - } - - @EnsuresQualifier(expression = "this.f", qualifier = Odd.class) - void m4() { - f = odd; - } - } - - static class Sub3 extends Super3 { - String g; - - @Override - boolean m1() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @Override - boolean m2() { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } - - @Override - @EnsuresOddIf(expression = "g", result = true) - // :: error: (contracts.conditional.postcondition.true.override.invalid) - boolean m3() { - g = odd; - return true; - } - - @Override - @EnsuresOddIf(expression = "f", result = true) - boolean m4() { - return super.m4(); - } - - @Override - // invalid result - @EnsuresOddIf(expression = "f", result = false) - boolean m5() { - f = odd; - return true; - } - - @EnsuresOddIf(expression = "f", result = false) - boolean m6() { - f = odd; - return true; - } - - @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) - boolean m7() { - f = odd; - return true; - } - } - - static class Super3 { - String f, g; - @Odd String odd; - - @EnsuresOddIf(expression = "f", result = true) - boolean m1() { - f = odd; - return true; - } - - @EnsuresQualifierIf(expression = "f", qualifier = Odd.class, result = true) - boolean m2() { - f = odd; - return true; - } - - @EnsuresOddIf(expression = "g", result = true) - boolean m3() { - g = odd; - return true; - } - - @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) - boolean m4() { - f = odd; - return true; - } + @Override + @RequiresOdd("f") + // :: error: (contracts.precondition.override.invalid) + void m1() {} - @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) - boolean m5() { - f = odd; - return true; + @Override + @RequiresQualifier(expression = "f", qualifier = Odd.class) + // :: error: (contracts.precondition.override.invalid) + void m2() {} + + @Override + // g is a different field than in the supertype + @RequiresOdd("g") + // :: error: (contracts.precondition.override.invalid) + void m3() {} + + @Override + @RequiresOdd("super.g") + void m3b() {} + + @Override + // use different string to refer to 'f' and RequiresQualifier instead of RequiresOdd + @RequiresQualifier(expression = "this.f", qualifier = Odd.class) + void m4() {} + } + + static class Sub2 extends Super2 { + String g; + + @Override + // :: error: (contracts.postcondition.not.satisfied) + void m1() {} + + @Override + // :: error: (contracts.postcondition.not.satisfied) + void m2() {} + + @Override + @EnsuresOdd("g") + // :: error: (contracts.postcondition.override.invalid) + void m3() { + g = odd; + } + + @Override + @EnsuresOdd("f") + void m4() { + super.m4(); + } + } + + static class Super2 { + String f, g; + @Odd String odd; + + @EnsuresOdd("f") + void m1() { + f = odd; + } + + @EnsuresQualifier(expression = "f", qualifier = Odd.class) + void m2() { + f = odd; + } + + @EnsuresOdd("g") + void m3() { + g = odd; + } + + @EnsuresQualifier(expression = "this.f", qualifier = Odd.class) + void m4() { + f = odd; + } + } + + static class Sub3 extends Super3 { + String g; + + @Override + boolean m1() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @Override + boolean m2() { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + + @Override + @EnsuresOddIf(expression = "g", result = true) + // :: error: (contracts.conditional.postcondition.true.override.invalid) + boolean m3() { + g = odd; + return true; + } + + @Override + @EnsuresOddIf(expression = "f", result = true) + boolean m4() { + return super.m4(); + } + + @Override + // invalid result + @EnsuresOddIf(expression = "f", result = false) + boolean m5() { + f = odd; + return true; + } + + @EnsuresOddIf(expression = "f", result = false) + boolean m6() { + f = odd; + return true; + } + + @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) + boolean m7() { + f = odd; + return true; + } + } + + static class Super3 { + String f, g; + @Odd String odd; + + @EnsuresOddIf(expression = "f", result = true) + boolean m1() { + f = odd; + return true; + } + + @EnsuresQualifierIf(expression = "f", qualifier = Odd.class, result = true) + boolean m2() { + f = odd; + return true; + } + + @EnsuresOddIf(expression = "g", result = true) + boolean m3() { + g = odd; + return true; + } + + @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) + boolean m4() { + f = odd; + return true; + } + + @EnsuresQualifierIf(expression = "this.f", qualifier = Odd.class, result = true) + boolean m5() { + f = odd; + return true; + } } - } } diff --git a/framework/tests/flow/ContractsOverridingSubtyping.java b/framework/tests/flow/ContractsOverridingSubtyping.java index c9f9f5ebfcf..c520c3ffb20 100644 --- a/framework/tests/flow/ContractsOverridingSubtyping.java +++ b/framework/tests/flow/ContractsOverridingSubtyping.java @@ -5,73 +5,73 @@ import org.checkerframework.framework.testchecker.util.Odd; public class ContractsOverridingSubtyping { - static class Base { - String f; - @Odd String g; + static class Base { + String f; + @Odd String g; - @RequiresQualifier(expression = "f", qualifier = Odd.class) - void requiresOdd() {} + @RequiresQualifier(expression = "f", qualifier = Odd.class) + void requiresOdd() {} - @RequiresQualifier(expression = "f", qualifier = Unqualified.class) - void requiresUnqual() {} + @RequiresQualifier(expression = "f", qualifier = Unqualified.class) + void requiresUnqual() {} - @EnsuresQualifier(expression = "f", qualifier = Odd.class) - void ensuresOdd() { - f = g; - } + @EnsuresQualifier(expression = "f", qualifier = Odd.class) + void ensuresOdd() { + f = g; + } - @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) - void ensuresUnqual() {} + @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) + void ensuresUnqual() {} - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) - boolean ensuresIfOdd() { - f = g; - return true; - } + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) + boolean ensuresIfOdd() { + f = g; + return true; + } - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) - boolean ensuresIfUnqual() { - return true; + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) + boolean ensuresIfUnqual() { + return true; + } } - } - static class Derived extends Base { + static class Derived extends Base { - @Override - @RequiresQualifier(expression = "f", qualifier = Unqualified.class) - void requiresOdd() {} + @Override + @RequiresQualifier(expression = "f", qualifier = Unqualified.class) + void requiresOdd() {} - @Override - @RequiresQualifier(expression = "f", qualifier = Odd.class) - // :: error: (contracts.precondition.override.invalid) - void requiresUnqual() {} + @Override + @RequiresQualifier(expression = "f", qualifier = Odd.class) + // :: error: (contracts.precondition.override.invalid) + void requiresUnqual() {} - @Override - @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) - // :: error: (contracts.postcondition.override.invalid) - void ensuresOdd() { - f = g; - } + @Override + @EnsuresQualifier(expression = "f", qualifier = Unqualified.class) + // :: error: (contracts.postcondition.override.invalid) + void ensuresOdd() { + f = g; + } - @Override - @EnsuresQualifier(expression = "f", qualifier = Odd.class) - void ensuresUnqual() { - f = g; - } + @Override + @EnsuresQualifier(expression = "f", qualifier = Odd.class) + void ensuresUnqual() { + f = g; + } - @Override - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) - // :: error: (contracts.conditional.postcondition.true.override.invalid) - boolean ensuresIfOdd() { - f = g; - return true; - } + @Override + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Unqualified.class) + // :: error: (contracts.conditional.postcondition.true.override.invalid) + boolean ensuresIfOdd() { + f = g; + return true; + } - @Override - @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) - boolean ensuresIfUnqual() { - f = g; - return true; + @Override + @EnsuresQualifierIf(expression = "f", result = true, qualifier = Odd.class) + boolean ensuresIfUnqual() { + f = g; + return true; + } } - } } diff --git a/framework/tests/flow/CustomContractWithArgs.java b/framework/tests/flow/CustomContractWithArgs.java index c1d482a1c90..a22f00a42cf 100644 --- a/framework/tests/flow/CustomContractWithArgs.java +++ b/framework/tests/flow/CustomContractWithArgs.java @@ -5,80 +5,80 @@ import org.checkerframework.framework.testchecker.util.ValueTypeAnno; public class CustomContractWithArgs { - // Postcondition for Value - @PostconditionAnnotation(qualifier = ValueTypeAnno.class) - @interface EnsuresValue { - public String[] value(); + // Postcondition for Value + @PostconditionAnnotation(qualifier = ValueTypeAnno.class) + @interface EnsuresValue { + public String[] value(); - @QualifierArgument("value") - public int targetValue(); - } + @QualifierArgument("value") + public int targetValue(); + } - // Conditional postcondition for LTLengthOf - @ConditionalPostconditionAnnotation(qualifier = ValueTypeAnno.class) - @interface EnsuresValueIf { - public boolean result(); + // Conditional postcondition for LTLengthOf + @ConditionalPostconditionAnnotation(qualifier = ValueTypeAnno.class) + @interface EnsuresValueIf { + public boolean result(); - public String[] expression(); + public String[] expression(); - @QualifierArgument("value") - public int targetValue(); - } + @QualifierArgument("value") + public int targetValue(); + } - // Precondition for LTLengthOf - @PreconditionAnnotation(qualifier = ValueTypeAnno.class) - @interface RequiresValue { - public String[] value(); + // Precondition for LTLengthOf + @PreconditionAnnotation(qualifier = ValueTypeAnno.class) + @interface RequiresValue { + public String[] value(); - @QualifierArgument("value") - public int targetValue(); - } + @QualifierArgument("value") + public int targetValue(); + } - class Base { - Object o; + class Base { + Object o; - @ValueTypeAnno(10) Object o10; + @ValueTypeAnno(10) Object o10; - @EnsuresValue(value = "o", targetValue = 10) - void ensures() { - o = o10; - } + @EnsuresValue(value = "o", targetValue = 10) + void ensures() { + o = o10; + } - @EnsuresValue(value = "o", targetValue = 9) - // :: error: (contracts.postcondition.not.satisfied) - void ensuresWrong() { - o = o10; - } + @EnsuresValue(value = "o", targetValue = 9) + // :: error: (contracts.postcondition.not.satisfied) + void ensuresWrong() { + o = o10; + } - void ensuresUse() { - ensures(); - o10 = o; - } + void ensuresUse() { + ensures(); + o10 = o; + } - @EnsuresValueIf(expression = "o", targetValue = 10, result = true) - boolean ensuresIf(boolean b) { - if (b) { - o = o10; - return true; - } else { - return false; - } - } + @EnsuresValueIf(expression = "o", targetValue = 10, result = true) + boolean ensuresIf(boolean b) { + if (b) { + o = o10; + return true; + } else { + return false; + } + } - @RequiresValue(value = "o", targetValue = 10) - void requires() { - o10 = o; - } + @RequiresValue(value = "o", targetValue = 10) + void requires() { + o10 = o; + } - void use(boolean b) { - if (ensuresIf(b)) { - o10 = o; - requires(); - } - // :: error: (assignment.type.incompatible) - o10 = o; - // :: error: (contracts.precondition.not.satisfied) - requires(); + void use(boolean b) { + if (ensuresIf(b)) { + o10 = o; + requires(); + } + // :: error: (assignment.type.incompatible) + o10 = o; + // :: error: (contracts.precondition.not.satisfied) + requires(); + } } - } } diff --git a/framework/tests/flow/Equal.java b/framework/tests/flow/Equal.java index a73f74e9b36..049a70f57a9 100644 --- a/framework/tests/flow/Equal.java +++ b/framework/tests/flow/Equal.java @@ -2,42 +2,42 @@ public class Equal { - String f1, f2, f3; + String f1, f2, f3; - // annotation inference for equality - void t1(@Odd String p1, String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (f1 == p1) { - @Odd String l2 = f1; - } else { - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; + // annotation inference for equality + void t1(@Odd String p1, String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (f1 == p1) { + @Odd String l2 = f1; + } else { + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } } - } - // annotation inference for equality - void t2(@Odd String p1, String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (f1 != p1) { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } else { - @Odd String l3 = f1; + // annotation inference for equality + void t2(@Odd String p1, String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (f1 != p1) { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } else { + @Odd String l3 = f1; + } } - } - void t3(@Odd Long p1) { - // :: error: (assignment.type.incompatible) - @Odd Long l1 = Long.valueOf(2L); - if (Long.valueOf(2L) == p1) { - // The result of valueOf is not deterministic, so it can't be refined. - // :: error: (assignment.type.incompatible) - @Odd Long l2 = Long.valueOf(2L); - } else { - // :: error: (assignment.type.incompatible) - @Odd Long l3 = Long.valueOf(2L); + void t3(@Odd Long p1) { + // :: error: (assignment.type.incompatible) + @Odd Long l1 = Long.valueOf(2L); + if (Long.valueOf(2L) == p1) { + // The result of valueOf is not deterministic, so it can't be refined. + // :: error: (assignment.type.incompatible) + @Odd Long l2 = Long.valueOf(2L); + } else { + // :: error: (assignment.type.incompatible) + @Odd Long l3 = Long.valueOf(2L); + } } - } } diff --git a/framework/tests/flow/FieldShadowing.java b/framework/tests/flow/FieldShadowing.java index e9078bd4ff1..0d86164d906 100644 --- a/framework/tests/flow/FieldShadowing.java +++ b/framework/tests/flow/FieldShadowing.java @@ -6,43 +6,43 @@ // various tests for the precondition mechanism public class FieldShadowing { - String f; - - class Sub extends FieldShadowing { String f; - @Pure - @RequiresQualifier(expression = "f", qualifier = Odd.class) - int reqSub() { - @Odd String l2 = f; - // :: error: (assignment.type.incompatible) - @Odd String l1 = super.f; - int i; - i = 1; - return 1; - } + class Sub extends FieldShadowing { + String f; - @Pure - @RequiresQualifier(expression = "super.f", qualifier = Odd.class) - int reqSuper() { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f; - @Odd String l1 = super.f; - return 1; - } + @Pure + @RequiresQualifier(expression = "f", qualifier = Odd.class) + int reqSub() { + @Odd String l2 = f; + // :: error: (assignment.type.incompatible) + @Odd String l1 = super.f; + int i; + i = 1; + return 1; + } - void t1(@Odd String p1) { - f = p1; - // :: error: (contracts.precondition.not.satisfied) - reqSuper(); - reqSub(); - } + @Pure + @RequiresQualifier(expression = "super.f", qualifier = Odd.class) + int reqSuper() { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f; + @Odd String l1 = super.f; + return 1; + } + + void t1(@Odd String p1) { + f = p1; + // :: error: (contracts.precondition.not.satisfied) + reqSuper(); + reqSub(); + } - void t2(@Odd String p1) { - super.f = p1; - // :: error: (contracts.precondition.not.satisfied) - reqSub(); - reqSuper(); + void t2(@Odd String p1) { + super.f = p1; + // :: error: (contracts.precondition.not.satisfied) + reqSub(); + reqSuper(); + } } - } } diff --git a/framework/tests/flow/Fields.java b/framework/tests/flow/Fields.java index 1bc30a08f0b..ebd57af8afe 100644 --- a/framework/tests/flow/Fields.java +++ b/framework/tests/flow/Fields.java @@ -1,18 +1,18 @@ public class Fields { - // Not final field - String s = null; + // Not final field + String s = null; - void setString(String s) { - this.s = s; - } + void setString(String s) { + this.s = s; + } - // Test flow for fields but in different receivers - void others() { - Fields withOddField = null; - Fields notOddField = null; + // Test flow for fields but in different receivers + void others() { + Fields withOddField = null; + Fields notOddField = null; - withOddField.s = null; - notOddField.s = "m"; - } + withOddField.s = null; + notOddField.s = "m"; + } } diff --git a/framework/tests/flow/Issue4449.java b/framework/tests/flow/Issue4449.java index 32ad27dfe06..81b3447d1b6 100644 --- a/framework/tests/flow/Issue4449.java +++ b/framework/tests/flow/Issue4449.java @@ -2,32 +2,32 @@ public class Issue4449 { - @SideEffectFree - public void test1(long[] x) { - // :: error: (purity.not.sideeffectfree.assign.array) - x[0] = 1; + @SideEffectFree + public void test1(long[] x) { + // :: error: (purity.not.sideeffectfree.assign.array) + x[0] = 1; - long y; + long y; - // :: error: (purity.not.sideeffectfree.assign.array) - ++x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - --x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - x[0]++; - // :: error: (purity.not.sideeffectfree.assign.array) - x[0]--; + // :: error: (purity.not.sideeffectfree.assign.array) + ++x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + --x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + x[0]++; + // :: error: (purity.not.sideeffectfree.assign.array) + x[0]--; - // :: error: (purity.not.sideeffectfree.assign.array) - y = ++x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - y = --x[0]; - // :: error: (purity.not.sideeffectfree.assign.array) - y = x[0]++; - // :: error: (purity.not.sideeffectfree.assign.array) - y = x[0]--; + // :: error: (purity.not.sideeffectfree.assign.array) + y = ++x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + y = --x[0]; + // :: error: (purity.not.sideeffectfree.assign.array) + y = x[0]++; + // :: error: (purity.not.sideeffectfree.assign.array) + y = x[0]--; - y = +x[0]; - y = -x[0]; - } + y = +x[0]; + y = -x[0]; + } } diff --git a/framework/tests/flow/Issue951.java b/framework/tests/flow/Issue951.java index 55d1f4fcfd1..2c05b3ca9e8 100644 --- a/framework/tests/flow/Issue951.java +++ b/framework/tests/flow/Issue951.java @@ -7,126 +7,126 @@ public class Issue951 { - @Pure - public static int min(int[] a) { - if (a.length == 0) { - throw new MyExceptionSefConstructor("Empty array passed to min(int[])"); - } - int result = a[0]; - for (int i = 1; i < a.length; i++) { - result = min(result, a[i]); - } - return result; - } - - @Pure - public static int arbitraryExceptionArg1() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + arbitraryMethod()); - } - - @Pure - public static int arbitraryExceptionArg2() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - throw new MyExceptionSefConstructor("" + arbitraryMethod()); - } - - @Pure - public static int sefExceptionArg1() { - // The method is safe, so this is a false positive warning; - // in the future the Purity Checker may not issue this warning. - // :: error: (purity.not.deterministic.call) - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + sefMethod()); - } - - @Pure - public static int sefExceptionArg2() { - // The method is safe, so this is a false positive warning; - // in the future the Purity Checker may not issue this warning. - // :: error: (purity.not.deterministic.call) - throw new MyExceptionSefConstructor("" + sefMethod()); - } - - @Pure - public static int detExceptionArg1() { - // :: error: (purity.not.sideeffectfree.call) - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + detMethod()); - } - - @Pure - public static int detExceptionArg2() { - // :: error: (purity.not.sideeffectfree.call) - throw new MyExceptionSefConstructor("" + detMethod()); - } - - @Pure - public static int pureExceptionArg1(int a, int b) { - // :: error: (purity.not.sideeffectfree.call) - throw new MyException("" + min(a, b)); - } - - @Pure - public static int pureExceptionArg2(int a, int b) { - throw new MyExceptionSefConstructor("" + min(a, b)); - } - - @Pure - int throwNewWithinTry() { - for (int i = 0; i < 10; i++) { - try { - for (int j = 0; j < 10; j++) { - throw new MyExceptionSefConstructor("foo"); + @Pure + public static int min(int[] a) { + if (a.length == 0) { + throw new MyExceptionSefConstructor("Empty array passed to min(int[])"); } - // :: error: (purity.not.deterministic.catch) - } catch (MyExceptionSefConstructor e) { - return -1; - } + int result = a[0]; + for (int i = 1; i < a.length; i++) { + result = min(result, a[i]); + } + return result; } - return 22; - } - // Helper methods + @Pure + public static int arbitraryExceptionArg1() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + arbitraryMethod()); + } - // Not deterministic or side-effect-free - static int arbitraryMethod() { - return 22; - } + @Pure + public static int arbitraryExceptionArg2() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + throw new MyExceptionSefConstructor("" + arbitraryMethod()); + } - // Not deterministic - @SideEffectFree - static int sefMethod() { - return 22; - } + @Pure + public static int sefExceptionArg1() { + // The method is safe, so this is a false positive warning; + // in the future the Purity Checker may not issue this warning. + // :: error: (purity.not.deterministic.call) + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + sefMethod()); + } - @Deterministic - // Not side-effect-free - static int detMethod() { - return 22; - } + @Pure + public static int sefExceptionArg2() { + // The method is safe, so this is a false positive warning; + // in the future the Purity Checker may not issue this warning. + // :: error: (purity.not.deterministic.call) + throw new MyExceptionSefConstructor("" + sefMethod()); + } - @Pure - static int min(int a, int b) { - if (a < b) { - return a; - } else { - return b; + @Pure + public static int detExceptionArg1() { + // :: error: (purity.not.sideeffectfree.call) + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + detMethod()); } - } - // Constructors + @Pure + public static int detExceptionArg2() { + // :: error: (purity.not.sideeffectfree.call) + throw new MyExceptionSefConstructor("" + detMethod()); + } - static class MyException extends Error { - // Not side-effect-free - MyException(String message) {} - } + @Pure + public static int pureExceptionArg1(int a, int b) { + // :: error: (purity.not.sideeffectfree.call) + throw new MyException("" + min(a, b)); + } + + @Pure + public static int pureExceptionArg2(int a, int b) { + throw new MyExceptionSefConstructor("" + min(a, b)); + } + + @Pure + int throwNewWithinTry() { + for (int i = 0; i < 10; i++) { + try { + for (int j = 0; j < 10; j++) { + throw new MyExceptionSefConstructor("foo"); + } + // :: error: (purity.not.deterministic.catch) + } catch (MyExceptionSefConstructor e) { + return -1; + } + } + return 22; + } + + // Helper methods - static class MyExceptionSefConstructor extends Error { - // Side-effect-free - @SuppressWarnings("purity.not.sideeffectfree.call") // until java.util.Error is annotated + // Not deterministic or side-effect-free + static int arbitraryMethod() { + return 22; + } + + // Not deterministic @SideEffectFree - MyExceptionSefConstructor(String message) {} - } + static int sefMethod() { + return 22; + } + + @Deterministic + // Not side-effect-free + static int detMethod() { + return 22; + } + + @Pure + static int min(int a, int b) { + if (a < b) { + return a; + } else { + return b; + } + } + + // Constructors + + static class MyException extends Error { + // Not side-effect-free + MyException(String message) {} + } + + static class MyExceptionSefConstructor extends Error { + // Side-effect-free + @SuppressWarnings("purity.not.sideeffectfree.call") // until java.util.Error is annotated + @SideEffectFree + MyExceptionSefConstructor(String message) {} + } } diff --git a/framework/tests/flow/MetaPostcondition.java b/framework/tests/flow/MetaPostcondition.java index 4467a592e0d..9fb9a5951ae 100644 --- a/framework/tests/flow/MetaPostcondition.java +++ b/framework/tests/flow/MetaPostcondition.java @@ -3,170 +3,170 @@ public class MetaPostcondition { - String f1, f2, f3; - MetaPostcondition p; - - /** *** normal postcondition ***** */ - @EnsuresOdd("f1") - void oddF1() { - f1 = null; - } - - @EnsuresOdd("p.f1") - void oddF1_1() { - p.f1 = null; - } - - @EnsuresOdd("#1.f1") - void oddF1_2(final MetaPostcondition param) { - param.f1 = null; - } - - @EnsuresOdd("f1") - // :: error: (contracts.postcondition.not.satisfied) - void oddF1_error() {} - - @EnsuresOdd("---") - // :: error: (flowexpr.parse.error) - void error() {} - - @EnsuresOdd("#1.#2") - // :: error: (flowexpr.parse.error) - void error2(final String p1, final String p2) {} - - @EnsuresOdd("f1") - void exception() { - throw new RuntimeException(); - } - - @EnsuresOdd("#1") - void param1(final @Odd String f) {} - - @EnsuresOdd({"#1", "#2"}) - // :: error: (flowexpr.parameter.not.final) - void param2(@Odd String f, @Odd String g) { - f = g; - } - - @EnsuresOdd("#1") - // :: error: (flowexpr.parse.index.too.big) - void param3() {} - - // basic postcondition test - void t1(@Odd String p1, String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - oddF1(); - @Odd String l2 = f1; - - // :: error: (flowexpr.parse.error.postcondition) - error(); - } - - // test parameter syntax - void t2(@Odd String p1, String p2) { + String f1, f2, f3; + MetaPostcondition p; + + /** *** normal postcondition ***** */ + @EnsuresOdd("f1") + void oddF1() { + f1 = null; + } + + @EnsuresOdd("p.f1") + void oddF1_1() { + p.f1 = null; + } + + @EnsuresOdd("#1.f1") + void oddF1_2(final MetaPostcondition param) { + param.f1 = null; + } + + @EnsuresOdd("f1") + // :: error: (contracts.postcondition.not.satisfied) + void oddF1_error() {} + + @EnsuresOdd("---") + // :: error: (flowexpr.parse.error) + void error() {} + + @EnsuresOdd("#1.#2") + // :: error: (flowexpr.parse.error) + void error2(final String p1, final String p2) {} + + @EnsuresOdd("f1") + void exception() { + throw new RuntimeException(); + } + + @EnsuresOdd("#1") + void param1(final @Odd String f) {} + + @EnsuresOdd({"#1", "#2"}) + // :: error: (flowexpr.parameter.not.final) + void param2(@Odd String f, @Odd String g) { + f = g; + } + + @EnsuresOdd("#1") // :: error: (flowexpr.parse.index.too.big) - param3(); - } - - // postcondition with more complex expression - void tn1(boolean b) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p.f1; - oddF1_1(); - @Odd String l2 = p.f1; - } - - // postcondition with more complex expression - void tn2(boolean b) { - MetaPostcondition param = null; - // :: error: (assignment.type.incompatible) - @Odd String l1 = param.f1; - oddF1_2(param); - @Odd String l2 = param.f1; - } - - // basic postcondition test - void tnm1(@Odd String p1, @ValueTypeAnno String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = f2; - - // :: error: (flowexpr.parse.error.postcondition) - error2(p1, p2); - } - - /** conditional postcondition */ - @EnsuresOddIf(result = true, expression = "f1") - boolean condOddF1(boolean b) { - if (b) { - f1 = null; - return true; - } - return false; - } - - @EnsuresOddIf(result = false, expression = "f1") - boolean condOddF1False(boolean b) { - if (b) { - return true; - } - f1 = null; - return false; - } - - @EnsuresOddIf(result = false, expression = "f1") - boolean condOddF1Invalid(boolean b) { - if (b) { - f1 = null; - return true; - } - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - - @EnsuresOddIf(result = false, expression = "f1") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - void wrongReturnType() {} - - @EnsuresOddIf(result = false, expression = "f1") - // :: error: (contracts.conditional.postcondition.invalid.returntype) - String wrongReturnType2() { - f1 = null; - return ""; - } - - // basic conditional postcondition test - void t3(@Odd String p1, String p2) { - condOddF1(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (condOddF1(false)) { - @Odd String l2 = f1; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; - } - - // basic conditional postcondition test (inverted) - void t4(@Odd String p1, String p2) { - condOddF1False(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (!condOddF1False(false)) { - @Odd String l2 = f1; - } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; - } - - // basic conditional postcondition test 2 - void t5(boolean b) { - condOddF1(true); - if (b) { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } - } + void param3() {} + + // basic postcondition test + void t1(@Odd String p1, String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + oddF1(); + @Odd String l2 = f1; + + // :: error: (flowexpr.parse.error.postcondition) + error(); + } + + // test parameter syntax + void t2(@Odd String p1, String p2) { + // :: error: (flowexpr.parse.index.too.big) + param3(); + } + + // postcondition with more complex expression + void tn1(boolean b) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p.f1; + oddF1_1(); + @Odd String l2 = p.f1; + } + + // postcondition with more complex expression + void tn2(boolean b) { + MetaPostcondition param = null; + // :: error: (assignment.type.incompatible) + @Odd String l1 = param.f1; + oddF1_2(param); + @Odd String l2 = param.f1; + } + + // basic postcondition test + void tnm1(@Odd String p1, @ValueTypeAnno String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = f2; + + // :: error: (flowexpr.parse.error.postcondition) + error2(p1, p2); + } + + /** conditional postcondition */ + @EnsuresOddIf(result = true, expression = "f1") + boolean condOddF1(boolean b) { + if (b) { + f1 = null; + return true; + } + return false; + } + + @EnsuresOddIf(result = false, expression = "f1") + boolean condOddF1False(boolean b) { + if (b) { + return true; + } + f1 = null; + return false; + } + + @EnsuresOddIf(result = false, expression = "f1") + boolean condOddF1Invalid(boolean b) { + if (b) { + f1 = null; + return true; + } + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + + @EnsuresOddIf(result = false, expression = "f1") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + void wrongReturnType() {} + + @EnsuresOddIf(result = false, expression = "f1") + // :: error: (contracts.conditional.postcondition.invalid.returntype) + String wrongReturnType2() { + f1 = null; + return ""; + } + + // basic conditional postcondition test + void t3(@Odd String p1, String p2) { + condOddF1(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (condOddF1(false)) { + @Odd String l2 = f1; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test (inverted) + void t4(@Odd String p1, String p2) { + condOddF1False(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (!condOddF1False(false)) { + @Odd String l2 = f1; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test 2 + void t5(boolean b) { + condOddF1(true); + if (b) { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } + } } diff --git a/framework/tests/flow/MetaPrecondition.java b/framework/tests/flow/MetaPrecondition.java index ffaea8f5dd0..d04bb08461c 100644 --- a/framework/tests/flow/MetaPrecondition.java +++ b/framework/tests/flow/MetaPrecondition.java @@ -5,78 +5,78 @@ // Tests for the meta-annotations for contracts. public class MetaPrecondition { - String f1, f2, f3; - MetaPrecondition p; + String f1, f2, f3; + MetaPrecondition p; - @RequiresOdd("f1") - void requiresF1() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - } + @RequiresOdd("f1") + void requiresF1() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + } - @Pure - @RequiresOdd("f1") - int requiresF1AndPure() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - return 1; - } + @Pure + @RequiresOdd("f1") + int requiresF1AndPure() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + return 1; + } - @RequiresOdd("---") - // :: error: (flowexpr.parse.error) - void error() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } + @RequiresOdd("---") + // :: error: (flowexpr.parse.error) + void error() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } - @RequiresOdd("#1") - void requiresParam(String p) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p; - @Odd String l2 = p; - } + @RequiresOdd("#1") + void requiresParam(String p) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p; + @Odd String l2 = p; + } - @RequiresOdd({"#1", "#2"}) - void requiresParams(String p1, String p2) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = p2; - @Odd String l3 = p1; - @Odd String l4 = p2; - } + @RequiresOdd({"#1", "#2"}) + void requiresParams(String p1, String p2) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = p2; + @Odd String l3 = p1; + @Odd String l4 = p2; + } - @RequiresOdd("#1") - // :: error: (flowexpr.parse.index.too.big) - void param3() {} + @RequiresOdd("#1") + // :: error: (flowexpr.parse.index.too.big) + void param3() {} - void t1(@Odd String p1, String p2) { - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresParam(p2); - // :: error: (contracts.precondition.not.satisfied) - requiresParams(p1, p2); - } + void t1(@Odd String p1, String p2) { + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresParam(p2); + // :: error: (contracts.precondition.not.satisfied) + requiresParams(p1, p2); + } - void t2(@Odd String p1, String p2) { - f1 = p1; - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - } + void t2(@Odd String p1, String p2) { + f1 = p1; + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + } - void t3(@Odd String p1, String p2) { - f1 = p1; - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - } + void t3(@Odd String p1, String p2) { + f1 = p1; + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + } } diff --git a/framework/tests/flow/MethodCallFlowExpr.java b/framework/tests/flow/MethodCallFlowExpr.java index 0015ca90402..229f89163e5 100644 --- a/framework/tests/flow/MethodCallFlowExpr.java +++ b/framework/tests/flow/MethodCallFlowExpr.java @@ -4,194 +4,194 @@ public class MethodCallFlowExpr { - @Pure - String p1(int i) { - return ""; - } - - @Pure - String p1b(Long i) { - return ""; - } - - @Pure - String p1(String s) { - return ""; - } - - String nonpure() { - return ""; - } - - @Pure - String p2(String s, long l, String s2) { - return ""; - } - - @Pure - String p1c(T i) { - return ""; - } - - @Pure - static String p1d(int i) { - return ""; - } - - @EnsuresQualifier(expression = "p1c(\"abc\")", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e0() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e1() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1(\"abc\")", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e2() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p2(\"abc\", 2L, p1(1))", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e4() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1b(2L)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e5() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1b(null)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e6() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "p1d(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e7a() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "this.p1d(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e7b() { - // don't bother with implementation - } - - @EnsuresQualifier(expression = "MethodCallFlowExpr.p1d(1)", qualifier = Odd.class) - // :: error: (contracts.postcondition.not.satisfied) - void e7c() { - // don't bother with implementation - } - - void t1() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1(1); - e1(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1(2); - @Odd String l3 = p1(1); - } - - void t2() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1("abc"); - e2(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1("def"); - // :: error: (assignment.type.incompatible) - @Odd String l2b = p1(1); - @Odd String l3 = p1("abc"); - } - - void t3() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p2("abc", 2L, p1(1)); - e4(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p2("abc", 2L, p1(2)); - // :: error: (assignment.type.incompatible) - @Odd String l2b = p2("abc", 1L, p1(1)); - @Odd String l3 = p2("abc", 2L, p1(1)); - } - - void t4() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1b(2L); - e5(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1b(null); - // :: error: (assignment.type.incompatible) - @Odd String l2b = p1b(1L); - // FIXME: the following line shouldn't give an error - // (or at least it didn't give an error earlier) - // @Odd String l3 = p1b(2L); - } - - void t5() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1b(null); - e6(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1b(1L); - // FIXME: the following line shouldn't give an error - // (or at least it didn't give an error earlier) - // @Odd String l3 = p1b(null); - } - - void t6() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1c("abc"); - e0(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = p1c("def"); - @Odd String l3 = p1c("abc"); - } - - void t7() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1b = MethodCallFlowExpr.p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1c = this.p1d(1); - e7a(); - @Odd String l2 = p1d(1); - @Odd String l2b = MethodCallFlowExpr.p1d(1); - @Odd String l2c = this.p1d(1); - } - - void t8() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1b = MethodCallFlowExpr.p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1c = this.p1d(1); - e7b(); - @Odd String l2 = p1d(1); - @Odd String l2b = MethodCallFlowExpr.p1d(1); - @Odd String l2c = this.p1d(1); - } - - void t9() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1b = MethodCallFlowExpr.p1d(1); - // :: error: (assignment.type.incompatible) - @Odd String l1c = this.p1d(1); - e7c(); - @Odd String l2 = p1d(1); - @Odd String l2b = MethodCallFlowExpr.p1d(1); - @Odd String l2c = this.p1d(1); - } + @Pure + String p1(int i) { + return ""; + } + + @Pure + String p1b(Long i) { + return ""; + } + + @Pure + String p1(String s) { + return ""; + } + + String nonpure() { + return ""; + } + + @Pure + String p2(String s, long l, String s2) { + return ""; + } + + @Pure + String p1c(T i) { + return ""; + } + + @Pure + static String p1d(int i) { + return ""; + } + + @EnsuresQualifier(expression = "p1c(\"abc\")", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e0() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e1() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1(\"abc\")", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e2() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p2(\"abc\", 2L, p1(1))", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e4() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1b(2L)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e5() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1b(null)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e6() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "p1d(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e7a() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "this.p1d(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e7b() { + // don't bother with implementation + } + + @EnsuresQualifier(expression = "MethodCallFlowExpr.p1d(1)", qualifier = Odd.class) + // :: error: (contracts.postcondition.not.satisfied) + void e7c() { + // don't bother with implementation + } + + void t1() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1(1); + e1(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1(2); + @Odd String l3 = p1(1); + } + + void t2() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1("abc"); + e2(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1("def"); + // :: error: (assignment.type.incompatible) + @Odd String l2b = p1(1); + @Odd String l3 = p1("abc"); + } + + void t3() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p2("abc", 2L, p1(1)); + e4(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p2("abc", 2L, p1(2)); + // :: error: (assignment.type.incompatible) + @Odd String l2b = p2("abc", 1L, p1(1)); + @Odd String l3 = p2("abc", 2L, p1(1)); + } + + void t4() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1b(2L); + e5(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1b(null); + // :: error: (assignment.type.incompatible) + @Odd String l2b = p1b(1L); + // FIXME: the following line shouldn't give an error + // (or at least it didn't give an error earlier) + // @Odd String l3 = p1b(2L); + } + + void t5() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1b(null); + e6(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1b(1L); + // FIXME: the following line shouldn't give an error + // (or at least it didn't give an error earlier) + // @Odd String l3 = p1b(null); + } + + void t6() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1c("abc"); + e0(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = p1c("def"); + @Odd String l3 = p1c("abc"); + } + + void t7() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1b = MethodCallFlowExpr.p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1c = this.p1d(1); + e7a(); + @Odd String l2 = p1d(1); + @Odd String l2b = MethodCallFlowExpr.p1d(1); + @Odd String l2c = this.p1d(1); + } + + void t8() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1b = MethodCallFlowExpr.p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1c = this.p1d(1); + e7b(); + @Odd String l2 = p1d(1); + @Odd String l2b = MethodCallFlowExpr.p1d(1); + @Odd String l2c = this.p1d(1); + } + + void t9() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1b = MethodCallFlowExpr.p1d(1); + // :: error: (assignment.type.incompatible) + @Odd String l1c = this.p1d(1); + e7c(); + @Odd String l2 = p1d(1); + @Odd String l2b = MethodCallFlowExpr.p1d(1); + @Odd String l2c = this.p1d(1); + } } diff --git a/framework/tests/flow/Monotonic.java b/framework/tests/flow/Monotonic.java index dbe11546b92..6e45bbb7950 100644 --- a/framework/tests/flow/Monotonic.java +++ b/framework/tests/flow/Monotonic.java @@ -3,46 +3,46 @@ public class Monotonic { - String f1; - @MonotonicOdd String f2; - @MonotonicOdd String f2b; - @Odd String f3; - Monotonic[] ms; + String f1; + @MonotonicOdd String f2; + @MonotonicOdd String f2b; + @Odd String f3; + Monotonic[] ms; - void nonpure() {} + void nonpure() {} - void t1(@Odd String p1) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f2; - if (f2 == p1) { - @Odd String l2 = f2; - nonpure(); - @Odd String l3 = f2; + void t1(@Odd String p1) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f2; + if (f2 == p1) { + @Odd String l2 = f2; + nonpure(); + @Odd String l3 = f2; + } } - } - void t2(@Odd String p1) { - // :: error: (assignment.type.incompatible) - f2 = f1; - // :: error: (monotonic.type.incompatible) - f2 = f2b; // assigning @MonotonicOdd to @MonotonicOdd is not allowed - } + void t2(@Odd String p1) { + // :: error: (assignment.type.incompatible) + f2 = f1; + // :: error: (monotonic.type.incompatible) + f2 = f2b; // assigning @MonotonicOdd to @MonotonicOdd is not allowed + } - void t3(@Odd String p1) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f2; - f2 = p1; - @Odd String l2 = f2; - nonpure(); - @Odd String l3 = f2; - } + void t3(@Odd String p1) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f2; + f2 = p1; + @Odd String l2 = f2; + nonpure(); + @Odd String l3 = f2; + } - void t4(@Odd String p1) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f2; - f2 = p1; - @Odd String l2 = f2; - ms[0].f2 = p1; - @Odd String l3 = f2; - } + void t4(@Odd String p1) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f2; + f2 = p1; + @Odd String l2 = f2; + ms[0].f2 = p1; + @Odd String l3 = f2; + } } diff --git a/framework/tests/flow/MoreFields.java b/framework/tests/flow/MoreFields.java index b17f4694d93..2825ab82ce6 100644 --- a/framework/tests/flow/MoreFields.java +++ b/framework/tests/flow/MoreFields.java @@ -1,9 +1,9 @@ public class MoreFields { - void testOtherFieds(Test other, int f) { - other.f = f; - } + void testOtherFieds(Test other, int f) { + other.f = f; + } } class Test { - int f; + int f; } diff --git a/framework/tests/flow/NonMethodCode.java b/framework/tests/flow/NonMethodCode.java index 18fbca89827..5e2590015bd 100644 --- a/framework/tests/flow/NonMethodCode.java +++ b/framework/tests/flow/NonMethodCode.java @@ -3,34 +3,34 @@ public class NonMethodCode { - @Odd String f1 = null; - String g1 = "def"; + @Odd String f1 = null; + String g1 = "def"; - static @Odd String sf1 = null; - static String sg1 = "def"; + static @Odd String sf1 = null; + static String sg1 = "def"; - // test flow for field initializer - @Odd String f2 = g1 == f1 ? g1 : f1; + // test flow for field initializer + @Odd String f2 = g1 == f1 ? g1 : f1; - // test flow for initializer blocks - { - String l1 = f1; - @Odd String l2 = l1; - if (g1 == f1) { - @Odd String l3 = g1; + // test flow for initializer blocks + { + String l1 = f1; + @Odd String l2 = l1; + if (g1 == f1) { + @Odd String l3 = g1; + } + // :: error: (assignment.type.incompatible) + @Odd String l4 = g1; } - // :: error: (assignment.type.incompatible) - @Odd String l4 = g1; - } - // test flow for static initializer blocks - static { - String l1 = sf1; - @Odd String l2 = l1; - if (sg1 == sf1) { - @Odd String l3 = sg1; + // test flow for static initializer blocks + static { + String l1 = sf1; + @Odd String l2 = l1; + if (sg1 == sf1) { + @Odd String l3 = sg1; + } + // :: error: (assignment.type.incompatible) + @Odd String l4 = sg1; } - // :: error: (assignment.type.incompatible) - @Odd String l4 = sg1; - } } diff --git a/framework/tests/flow/ParamFlowExpr.java b/framework/tests/flow/ParamFlowExpr.java index 8cec0cb4621..61627213cf8 100644 --- a/framework/tests/flow/ParamFlowExpr.java +++ b/framework/tests/flow/ParamFlowExpr.java @@ -4,27 +4,27 @@ public class ParamFlowExpr { - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - void t1(String p1) { - String l1 = p1; - } - - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - // :: error: (flowexpr.parameter.not.final) - void t2(String p1) { - p1 = ""; - } + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + void t1(String p1) { + String l1 = p1; + } - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - public static boolean eltsNonNull(Object[] seq1) { - if (seq1 == null) { - return false; + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + // :: error: (flowexpr.parameter.not.final) + void t2(String p1) { + p1 = ""; } - for (int i = 0; i < seq1.length; i++) { - if (seq1[i] == null) { - return false; - } + + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + public static boolean eltsNonNull(Object[] seq1) { + if (seq1 == null) { + return false; + } + for (int i = 0; i < seq1.length; i++) { + if (seq1[i] == null) { + return false; + } + } + return true; } - return true; - } } diff --git a/framework/tests/flow/Postcondition.java b/framework/tests/flow/Postcondition.java index 74a3a52f5a7..f238750157f 100644 --- a/framework/tests/flow/Postcondition.java +++ b/framework/tests/flow/Postcondition.java @@ -6,310 +6,310 @@ public class Postcondition { - String f1, f2, f3; - Postcondition p; - - @Pure - String p1() { - return null; - } - - /** *** normal postcondition ***** */ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class) - void oddF1() { - f1 = null; - } - - @EnsuresQualifier(expression = "p.f1", qualifier = Odd.class) - void oddF1_1() { - p.f1 = null; - } - - @EnsuresQualifier(expression = "#1.f1", qualifier = Odd.class) - void oddF1_2(final Postcondition param) { - param.f1 = null; - } - - @EnsuresQualifier(expression = "p.p1()", qualifier = Odd.class) - void oddF1_3() { - if (p.p1() == null) { - return; + String f1, f2, f3; + Postcondition p; + + @Pure + String p1() { + return null; + } + + /** *** normal postcondition ***** */ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class) + void oddF1() { + f1 = null; + } + + @EnsuresQualifier(expression = "p.f1", qualifier = Odd.class) + void oddF1_1() { + p.f1 = null; + } + + @EnsuresQualifier(expression = "#1.f1", qualifier = Odd.class) + void oddF1_2(final Postcondition param) { + param.f1 = null; + } + + @EnsuresQualifier(expression = "p.p1()", qualifier = Odd.class) + void oddF1_3() { + if (p.p1() == null) { + return; + } + throw new RuntimeException(); + } + + @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + // :: error: (contracts.postcondition.not.satisfied) + void valueF1() {} + + @EnsuresQualifier(expression = "---", qualifier = ValueTypeAnno.class) + // :: error: (flowexpr.parse.error) + void error() {} + + @EnsuresQualifier(expression = "#1.#2", qualifier = ValueTypeAnno.class) + // :: error: (flowexpr.parse.error) + void error2(final String p1, final String p2) {} + + @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + void exception() { + throw new RuntimeException(); + } + + @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) + void param1(final @ValueTypeAnno String f) {} + + @EnsuresQualifier( + expression = {"#1", "#2"}, + qualifier = ValueTypeAnno.class) + // :: error: (flowexpr.parameter.not.final) + void param2(@ValueTypeAnno String f, @ValueTypeAnno String g) { + f = g; } - throw new RuntimeException(); - } - - @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) - // :: error: (contracts.postcondition.not.satisfied) - void valueF1() {} - - @EnsuresQualifier(expression = "---", qualifier = ValueTypeAnno.class) - // :: error: (flowexpr.parse.error) - void error() {} - - @EnsuresQualifier(expression = "#1.#2", qualifier = ValueTypeAnno.class) - // :: error: (flowexpr.parse.error) - void error2(final String p1, final String p2) {} - - @EnsuresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) - void exception() { - throw new RuntimeException(); - } - - @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) - void param1(final @ValueTypeAnno String f) {} - - @EnsuresQualifier( - expression = {"#1", "#2"}, - qualifier = ValueTypeAnno.class) - // :: error: (flowexpr.parameter.not.final) - void param2(@ValueTypeAnno String f, @ValueTypeAnno String g) { - f = g; - } - - @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) - // :: error: (flowexpr.parse.index.too.big) - void param3() {} - - // basic postcondition test - void t1(@Odd String p1, String p2) { - valueF1(); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - oddF1(); - @Odd String l2 = f1; - - // :: error: (flowexpr.parse.error.postcondition) - error(); - } - - // test parameter syntax - void t2(@Odd String p1, String p2) { + + @EnsuresQualifier(expression = "#1", qualifier = ValueTypeAnno.class) // :: error: (flowexpr.parse.index.too.big) - param3(); - } - - // postcondition with more complex expression - void tn1(boolean b) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p.f1; - oddF1_1(); - @Odd String l2 = p.f1; - } - - // postcondition with more complex expression - void tn2(boolean b) { - Postcondition param = null; - // :: error: (assignment.type.incompatible) - @Odd String l1 = param.f1; - oddF1_2(param); - @Odd String l2 = param.f1; - } - - // postcondition with more complex expression - void tn3(boolean b) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = p.p1(); - oddF1_3(); - @Odd String l2 = p.p1(); - } - - /** *** many postcondition ***** */ - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class), + void param3() {} + + // basic postcondition test + void t1(@Odd String p1, String p2) { + valueF1(); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + oddF1(); + @Odd String l2 = f1; + + // :: error: (flowexpr.parse.error.postcondition) + error(); + } + + // test parameter syntax + void t2(@Odd String p1, String p2) { + // :: error: (flowexpr.parse.index.too.big) + param3(); + } + + // postcondition with more complex expression + void tn1(boolean b) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p.f1; + oddF1_1(); + @Odd String l2 = p.f1; + } + + // postcondition with more complex expression + void tn2(boolean b) { + Postcondition param = null; + // :: error: (assignment.type.incompatible) + @Odd String l1 = param.f1; + oddF1_2(param); + @Odd String l2 = param.f1; + } + + // postcondition with more complex expression + void tn3(boolean b) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = p.p1(); + oddF1_3(); + @Odd String l2 = p.p1(); + } + + /** *** many postcondition ***** */ + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class), + @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) + }) + void oddValueF1(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; + } + + @EnsuresQualifier(expression = "f1", qualifier = Odd.class) @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - }) - void oddValueF1(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier(expression = "f1", qualifier = Odd.class) - @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - void oddValueF1_repeated1(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class), - }) - @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - void oddValueF1_repeated2(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier(expression = "f1", qualifier = Odd.class) - @EnsuresQualifier.List({@EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class)}) - void oddValueF1_repeated3(@ValueTypeAnno String p1) { - f1 = null; - f2 = p1; - } - - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "f1", qualifier = Odd.class), + void oddValueF1_repeated1(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; + } + + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class), + }) @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) - }) - // :: error: (contracts.postcondition.not.satisfied) - void oddValueF1_invalid(@ValueTypeAnno String p1) {} - - @EnsuresQualifier.List({ - @EnsuresQualifier(expression = "--", qualifier = Odd.class), - }) - // :: error: (flowexpr.parse.error) - void error2() {} - - // basic postcondition test - void tnm1(@Odd String p1, @ValueTypeAnno String p2) { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = f2; - oddValueF1(p2); - @Odd String l3 = f1; - @ValueTypeAnno String l4 = f2; - - // :: error: (flowexpr.parse.error.postcondition) - error2(); - } - - /** *** conditional postcondition ***** */ - @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class) - boolean condOddF1(boolean b) { - if (b) { - f1 = null; - return true; + void oddValueF1_repeated2(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; } - return false; - } - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - boolean condOddF1False(boolean b) { - if (b) { - return true; + @EnsuresQualifier(expression = "f1", qualifier = Odd.class) + @EnsuresQualifier.List({@EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class)}) + void oddValueF1_repeated3(@ValueTypeAnno String p1) { + f1 = null; + f2 = p1; } - f1 = null; - return false; - } - - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - boolean condOddF1Invalid(boolean b) { - if (b) { - f1 = null; - return true; + + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "f1", qualifier = Odd.class), + @EnsuresQualifier(expression = "f2", qualifier = ValueTypeAnno.class) + }) + // :: error: (contracts.postcondition.not.satisfied) + void oddValueF1_invalid(@ValueTypeAnno String p1) {} + + @EnsuresQualifier.List({ + @EnsuresQualifier(expression = "--", qualifier = Odd.class), + }) + // :: error: (flowexpr.parse.error) + void error2() {} + + // basic postcondition test + void tnm1(@Odd String p1, @ValueTypeAnno String p2) { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = f2; + oddValueF1(p2); + @Odd String l3 = f1; + @ValueTypeAnno String l4 = f2; + + // :: error: (flowexpr.parse.error.postcondition) + error2(); } - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - // :: error: (contracts.conditional.postcondition.invalid.returntype) - void wrongReturnType() {} - - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - // :: error: (contracts.conditional.postcondition.invalid.returntype) - String wrongReturnType2() { - f1 = null; - return ""; - } - - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) - boolean isOdd(final String p1) { - return isOdd(p1, 0); - } - - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) - boolean isOdd(final String p1, int p2) { - return p1 == null; - } - - @EnsuresQualifierIf(result = false, expression = "#1", qualifier = Odd.class) - boolean isNotOdd(final String p1) { - return !isOdd(p1); - } - - // basic conditional postcondition test - void t3(@Odd String p1, String p2) { - condOddF1(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (condOddF1(false)) { - @Odd String l2 = f1; + + /** *** conditional postcondition ***** */ + @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class) + boolean condOddF1(boolean b) { + if (b) { + f1 = null; + return true; + } + return false; } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; - } - - // basic conditional postcondition test (inverted) - void t4(@Odd String p1, String p2) { - condOddF1False(true); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - if (!condOddF1False(false)) { - @Odd String l2 = f1; + + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + boolean condOddF1False(boolean b) { + if (b) { + return true; + } + f1 = null; + return false; } - // :: error: (assignment.type.incompatible) - @Odd String l3 = f1; - } - - // basic conditional postcondition test 2 - void t5(boolean b) { - condOddF1(true); - if (b) { - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; + + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + boolean condOddF1Invalid(boolean b) { + if (b) { + f1 = null; + return true; + } + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; } - } - - /** *** many conditional postcondition ***** */ - @EnsuresQualifierIf.List({ - @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) - }) - boolean condsOddF1(boolean b, @ValueTypeAnno String p1) { - if (b) { - f1 = null; - return true; + + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + // :: error: (contracts.conditional.postcondition.invalid.returntype) + void wrongReturnType() {} + + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + // :: error: (contracts.conditional.postcondition.invalid.returntype) + String wrongReturnType2() { + f1 = null; + return ""; } - f1 = p1; - return false; - } - - @EnsuresQualifierIf.List({ - @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) - }) - boolean condsOddF1_invalid(boolean b, @ValueTypeAnno String p1) { - if (b) { - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; + + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) + boolean isOdd(final String p1) { + return isOdd(p1, 0); } - // :: error: (contracts.conditional.postcondition.not.satisfied) - return false; - } - @EnsuresQualifierIf.List({ - @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) - }) - // :: error: (contracts.conditional.postcondition.invalid.returntype) - String wrongReturnType3() { - return ""; - } - - void t6(@Odd String p1, @ValueTypeAnno String p2) { - condsOddF1(true, p2); - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = f1; - if (condsOddF1(false, p2)) { - @Odd String l3 = f1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l4 = f1; - } else { - @ValueTypeAnno String l5 = f1; - // :: error: (assignment.type.incompatible) - @Odd String l6 = f1; + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Odd.class) + boolean isOdd(final String p1, int p2) { + return p1 == null; + } + + @EnsuresQualifierIf(result = false, expression = "#1", qualifier = Odd.class) + boolean isNotOdd(final String p1) { + return !isOdd(p1); + } + + // basic conditional postcondition test + void t3(@Odd String p1, String p2) { + condOddF1(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (condOddF1(false)) { + @Odd String l2 = f1; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test (inverted) + void t4(@Odd String p1, String p2) { + condOddF1False(true); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + if (!condOddF1False(false)) { + @Odd String l2 = f1; + } + // :: error: (assignment.type.incompatible) + @Odd String l3 = f1; + } + + // basic conditional postcondition test 2 + void t5(boolean b) { + condOddF1(true); + if (b) { + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } + } + + /** *** many conditional postcondition ***** */ + @EnsuresQualifierIf.List({ + @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) + }) + boolean condsOddF1(boolean b, @ValueTypeAnno String p1) { + if (b) { + f1 = null; + return true; + } + f1 = p1; + return false; + } + + @EnsuresQualifierIf.List({ + @EnsuresQualifierIf(result = true, expression = "f1", qualifier = Odd.class), + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = ValueTypeAnno.class) + }) + boolean condsOddF1_invalid(boolean b, @ValueTypeAnno String p1) { + if (b) { + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } + // :: error: (contracts.conditional.postcondition.not.satisfied) + return false; + } + + @EnsuresQualifierIf.List({ + @EnsuresQualifierIf(result = false, expression = "f1", qualifier = Odd.class) + }) + // :: error: (contracts.conditional.postcondition.invalid.returntype) + String wrongReturnType3() { + return ""; + } + + void t6(@Odd String p1, @ValueTypeAnno String p2) { + condsOddF1(true, p2); + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = f1; + if (condsOddF1(false, p2)) { + @Odd String l3 = f1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l4 = f1; + } else { + @ValueTypeAnno String l5 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l6 = f1; + } } - } } diff --git a/framework/tests/flow/Precondition.java b/framework/tests/flow/Precondition.java index 572fc8a1330..7de4313ff7e 100644 --- a/framework/tests/flow/Precondition.java +++ b/framework/tests/flow/Precondition.java @@ -6,157 +6,159 @@ // various tests for the precondition mechanism public class Precondition { - String f1, f2, f3; - Precondition p; - - @RequiresQualifier(expression = "f1", qualifier = Odd.class) - void requiresF1() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - } - - @Pure - @RequiresQualifier(expression = "f1", qualifier = Odd.class) - int requiresF1AndPure() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f1; - return 1; - } - - @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) - void requiresF1Value() { - // :: error: (assignment.type.incompatible) - @Odd String l1 = f1; - @ValueTypeAnno String l2 = f1; - } - - @RequiresQualifier(expression = "---", qualifier = Odd.class) - // :: error: (flowexpr.parse.error) - void error() { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = f1; - // :: error: (assignment.type.incompatible) - @Odd String l2 = f1; - } - - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - void requiresParam(String p) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p; - @Odd String l2 = p; - } - - @RequiresQualifier( - expression = {"#1", "#2"}, - qualifier = Odd.class) - void requiresParams(String p1, String p2) { - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l1 = p1; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l2 = p2; - @Odd String l3 = p1; - @Odd String l4 = p2; - } - - @RequiresQualifier(expression = "#1", qualifier = Odd.class) - // :: error: (flowexpr.parse.index.too.big) - void param3() {} - - void t1(@Odd String p1, String p2) { - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1Value(); - // :: error: (contracts.precondition.not.satisfied) - requiresParam(p2); - // :: error: (contracts.precondition.not.satisfied) - requiresParams(p1, p2); - } - - void t2(@Odd String p1, String p2) { - f1 = p1; - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1Value(); - } - - void t3(@Odd String p1, String p2) { - f1 = p1; - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1AndPure(); - requiresF1(); - // :: error: (contracts.precondition.not.satisfied) - requiresF1(); - } - - void t4(@Odd String p1, String p2, @ValueTypeAnno String p3) { - f1 = p1; - requiresF1(); - f1 = p3; - requiresF1Value(); - requiresParam(p1); - requiresParams(p1, p1); - } - - class Inner { + String f1, f2, f3; + Precondition p; + @RequiresQualifier(expression = "f1", qualifier = Odd.class) - void foo() {} - } - - @Odd String f4; - - @RequiresQualifier(expression = "f4", qualifier = Odd.class) - void requiresF4() {} - - void tt1() { - requiresF4(); - } - - /** *** multiple preconditions ***** */ - @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) - @RequiresQualifier(expression = "f2", qualifier = Odd.class) - void multi() { - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f2; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l3 = f2; - // :: error: (assignment.type.incompatible) - @Odd String l4 = f1; - } - - @RequiresQualifier.List({ - @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class), - @RequiresQualifier(expression = "f2", qualifier = Odd.class) - }) - void multi_explicit_requiresqualifierlist() { - @ValueTypeAnno String l1 = f1; - @Odd String l2 = f2; - // :: error: (assignment.type.incompatible) - @ValueTypeAnno String l3 = f2; - // :: error: (assignment.type.incompatible) - @Odd String l4 = f1; - } - - @RequiresQualifier.List({@RequiresQualifier(expression = "--", qualifier = ValueTypeAnno.class)}) - // :: error: (flowexpr.parse.error) - void error2() {} - - void t5(@Odd String p1, String p2, @ValueTypeAnno String p3) { - // :: error: (contracts.precondition.not.satisfied) - multi(); - f1 = p3; - // :: error: (contracts.precondition.not.satisfied) - multi(); - f1 = p3; - f2 = p1; - multi(); + void requiresF1() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + } + @Pure + @RequiresQualifier(expression = "f1", qualifier = Odd.class) + int requiresF1AndPure() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f1; + return 1; + } + + @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + void requiresF1Value() { + // :: error: (assignment.type.incompatible) + @Odd String l1 = f1; + @ValueTypeAnno String l2 = f1; + } + + @RequiresQualifier(expression = "---", qualifier = Odd.class) + // :: error: (flowexpr.parse.error) + void error() { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = f1; + // :: error: (assignment.type.incompatible) + @Odd String l2 = f1; + } + + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + void requiresParam(String p) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p; + @Odd String l2 = p; + } + + @RequiresQualifier( + expression = {"#1", "#2"}, + qualifier = Odd.class) + void requiresParams(String p1, String p2) { + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l1 = p1; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l2 = p2; + @Odd String l3 = p1; + @Odd String l4 = p2; + } + + @RequiresQualifier(expression = "#1", qualifier = Odd.class) + // :: error: (flowexpr.parse.index.too.big) + void param3() {} + + void t1(@Odd String p1, String p2) { + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1Value(); + // :: error: (contracts.precondition.not.satisfied) + requiresParam(p2); + // :: error: (contracts.precondition.not.satisfied) + requiresParams(p1, p2); + } + + void t2(@Odd String p1, String p2) { + f1 = p1; + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1Value(); + } + + void t3(@Odd String p1, String p2) { + f1 = p1; + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1AndPure(); + requiresF1(); + // :: error: (contracts.precondition.not.satisfied) + requiresF1(); + } + + void t4(@Odd String p1, String p2, @ValueTypeAnno String p3) { + f1 = p1; + requiresF1(); + f1 = p3; + requiresF1Value(); + requiresParam(p1); + requiresParams(p1, p1); + } + + class Inner { + @RequiresQualifier(expression = "f1", qualifier = Odd.class) + void foo() {} + } + + @Odd String f4; + + @RequiresQualifier(expression = "f4", qualifier = Odd.class) + void requiresF4() {} + + void tt1() { + requiresF4(); + } + + /** *** multiple preconditions ***** */ + @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class) + @RequiresQualifier(expression = "f2", qualifier = Odd.class) + void multi() { + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f2; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l3 = f2; + // :: error: (assignment.type.incompatible) + @Odd String l4 = f1; + } + + @RequiresQualifier.List({ + @RequiresQualifier(expression = "f1", qualifier = ValueTypeAnno.class), + @RequiresQualifier(expression = "f2", qualifier = Odd.class) + }) + void multi_explicit_requiresqualifierlist() { + @ValueTypeAnno String l1 = f1; + @Odd String l2 = f2; + // :: error: (assignment.type.incompatible) + @ValueTypeAnno String l3 = f2; + // :: error: (assignment.type.incompatible) + @Odd String l4 = f1; + } + + @RequiresQualifier.List({ + @RequiresQualifier(expression = "--", qualifier = ValueTypeAnno.class) + }) // :: error: (flowexpr.parse.error) - error2(); - } + void error2() {} + + void t5(@Odd String p1, String p2, @ValueTypeAnno String p3) { + // :: error: (contracts.precondition.not.satisfied) + multi(); + f1 = p3; + // :: error: (contracts.precondition.not.satisfied) + multi(); + f1 = p3; + f2 = p1; + multi(); + + // :: error: (flowexpr.parse.error) + error2(); + } } diff --git a/framework/tests/flow/Purity.java b/framework/tests/flow/Purity.java index 731bc8cf627..bb6d5d1f9bd 100644 --- a/framework/tests/flow/Purity.java +++ b/framework/tests/flow/Purity.java @@ -7,280 +7,280 @@ // various tests for the @Pure annotation public class Purity { - String f1, f2, f3; - String[] a; + String f1, f2, f3; + String[] a; - // class with a (potentially) non-pure constructor - private static class NonPureClass {} + // class with a (potentially) non-pure constructor + private static class NonPureClass {} + + // class with a pure constructor + private static class PureClass { + @Pure + // :: warning: (purity.deterministic.constructor) + public PureClass() {} + } + + // class with a side-effect-free constructor + private static class SEClass { + @SideEffectFree + public SEClass() {} + } + + // a method that is not pure (no annotation) + void nonpure() {} + + @Pure + String pure() { + return ""; + } - // class with a pure constructor - private static class PureClass { @Pure - // :: warning: (purity.deterministic.constructor) - public PureClass() {} - } + // :: warning: (purity.deterministic.void.method) + void t1() {} - // class with a side-effect-free constructor - private static class SEClass { @SideEffectFree - public SEClass() {} - } - - // a method that is not pure (no annotation) - void nonpure() {} - - @Pure - String pure() { - return ""; - } - - @Pure - // :: warning: (purity.deterministic.void.method) - void t1() {} - - @SideEffectFree - void t1b() {} - - @Deterministic - // :: warning: (purity.deterministic.void.method) - void t1c() {} - - @Pure - String t2() { - return ""; - } - - @Pure - String t3() { - // :: error: (purity.not.deterministic.not.sideeffectfree.call) - nonpure(); - // :: error: (purity.not.deterministic.call) - t16b(); // Calling a @SideEffectFree method - // :: error: (purity.not.sideeffectfree.call) - t16c(); // Calling a @Deterministic method - return ""; - } - - @Pure - String t4() { - pure(); - return ""; - } - - @Pure - int t5() { - int i = 1; - return i; - } - - @Pure - int t6() { - int j = 0; - for (int i = 0; i < 10; i++) { - j = j - i; + void t1b() {} + + @Deterministic + // :: warning: (purity.deterministic.void.method) + void t1c() {} + + @Pure + String t2() { + return ""; + } + + @Pure + String t3() { + // :: error: (purity.not.deterministic.not.sideeffectfree.call) + nonpure(); + // :: error: (purity.not.deterministic.call) + t16b(); // Calling a @SideEffectFree method + // :: error: (purity.not.sideeffectfree.call) + t16c(); // Calling a @Deterministic method + return ""; } - return j; - } - @Pure - String t7() { - if (true) { - return "a"; + @Pure + String t4() { + pure(); + return ""; + } + + @Pure + int t5() { + int i = 1; + return i; + } + + @Pure + int t6() { + int j = 0; + for (int i = 0; i < 10; i++) { + j = j - i; + } + return j; } - return ""; - } - - @Pure - int t8() { - return 1 - 2 / 3 * 2 % 2; - } - - @Pure - String t9() { - return "b" + "a"; - } - - @Pure - String t10() { - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) - f1 = ""; - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) - f2 = ""; - return ""; - } - - @Pure - String t11(Purity l) { - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) - l.a[0] = ""; - return ""; - } - - @Pure - String t12(String[] s) { - // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) - s[0] = ""; - return ""; - } - - @Pure - String t13() { - // No "purity.not.deterministic.object.creation" error; an error was issued at the - // constructor. - PureClass p = new PureClass(); - return ""; - } - - @SideEffectFree - String t13b() { - PureClass p = new PureClass(); - return ""; - } - - @SideEffectFree - String t13d() { - SEClass p = new SEClass(); - return ""; - } - - @Deterministic - String t13c() { - // No "purity.not.deterministic.object.creation" error; an error was issued at the - // constructor. - PureClass p = new PureClass(); - return ""; - } - - @Pure - String t14() { - String i = ""; - i = "a"; - return i; - } - - @Pure - String t15() { - String[] s = new String[1]; - return s[0]; - } - - @Pure - String t16() { - try { - int i = 1 / 0; - // :: error: (purity.not.deterministic.catch) - } catch (Throwable t) { - // ... + + @Pure + String t7() { + if (true) { + return "a"; + } + return ""; + } + + @Pure + int t8() { + return 1 - 2 / 3 * 2 % 2; + } + + @Pure + String t9() { + return "b" + "a"; } - return ""; - } - - @SideEffectFree - String t16b() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... + + @Pure + String t10() { + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) + f1 = ""; + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.field) + f2 = ""; + return ""; } - return ""; - } - - @Deterministic - String t16c() { - try { - int i = 1 / 0; - // :: error: (purity.not.deterministic.catch) - } catch (Throwable t) { - // ... + + @Pure + String t11(Purity l) { + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) + l.a[0] = ""; + return ""; } - return ""; - } - - @Pure - String t12() { - // :: error: (purity.not.sideeffectfree.call) - // :: error: (purity.not.deterministic.object.creation) - NonPureClass p = new NonPureClass(); - return ""; - } - - @Deterministic - String t17a(Purity l) { - // :: error: (purity.not.deterministic.assign.field) - f1 = ""; - // :: error: (purity.not.deterministic.assign.array) - l.a[0] = ""; - // :: error: (purity.not.deterministic.call) - nonpure(); - // :: error: (purity.not.deterministic.call) - return t16b(); // Calling a @SideEffectFree method - } - - @SideEffectFree - String t17b() { - // :: error: (purity.not.sideeffectfree.assign.field) - f1 = ""; - // :: error: (purity.not.sideeffectfree.call) - NonPureClass p = new NonPureClass(); - // :: error: (purity.not.sideeffectfree.call) - nonpure(); - // :: error: (purity.not.sideeffectfree.call) - return t16c(); // Calling a @Deterministic method - } - - // @Pure annotations on the overridden implementation. - class Super { + @Pure - int m1(int arg) { - return 0; + String t12(String[] s) { + // :: error: (purity.not.deterministic.not.sideeffectfree.assign.array) + s[0] = ""; + return ""; } @Pure - int m2(int arg) { - return 0; + String t13() { + // No "purity.not.deterministic.object.creation" error; an error was issued at the + // constructor. + PureClass p = new PureClass(); + return ""; + } + + @SideEffectFree + String t13b() { + PureClass p = new PureClass(); + return ""; } - int m3(int arg) { - return 0; + @SideEffectFree + String t13d() { + SEClass p = new SEClass(); + return ""; } - int m4(int arg) { - return 0; + @Deterministic + String t13c() { + // No "purity.not.deterministic.object.creation" error; an error was issued at the + // constructor. + PureClass p = new PureClass(); + return ""; } - } - class Sub extends Super { @Pure - int m1(int arg) { - return 0; + String t14() { + String i = ""; + i = "a"; + return i; } - int m2(int arg) { - return 0; + @Pure + String t15() { + String[] s = new String[1]; + return s[0]; } @Pure - int m3(int arg) { - return 0; + String t16() { + try { + int i = 1 / 0; + // :: error: (purity.not.deterministic.catch) + } catch (Throwable t) { + // ... + } + return ""; + } + + @SideEffectFree + String t16b() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... + } + return ""; } - int m4(int arg) { - return 0; + @Deterministic + String t16c() { + try { + int i = 1 / 0; + // :: error: (purity.not.deterministic.catch) + } catch (Throwable t) { + // ... + } + return ""; } - } - class MyClass extends Object { - public int hashCode() { - return 42; + @Pure + String t12() { + // :: error: (purity.not.sideeffectfree.call) + // :: error: (purity.not.deterministic.object.creation) + NonPureClass p = new NonPureClass(); + return ""; } - } - class Wrapper { - Object key; + @Deterministic + String t17a(Purity l) { + // :: error: (purity.not.deterministic.assign.field) + f1 = ""; + // :: error: (purity.not.deterministic.assign.array) + l.a[0] = ""; + // :: error: (purity.not.deterministic.call) + nonpure(); + // :: error: (purity.not.deterministic.call) + return t16b(); // Calling a @SideEffectFree method + } @SideEffectFree - public Wrapper(Object key) { - this.key = key; + String t17b() { + // :: error: (purity.not.sideeffectfree.assign.field) + f1 = ""; + // :: error: (purity.not.sideeffectfree.call) + NonPureClass p = new NonPureClass(); + // :: error: (purity.not.sideeffectfree.call) + nonpure(); + // :: error: (purity.not.sideeffectfree.call) + return t16c(); // Calling a @Deterministic method + } + + // @Pure annotations on the overridden implementation. + class Super { + @Pure + int m1(int arg) { + return 0; + } + + @Pure + int m2(int arg) { + return 0; + } + + int m3(int arg) { + return 0; + } + + int m4(int arg) { + return 0; + } + } + + class Sub extends Super { + @Pure + int m1(int arg) { + return 0; + } + + int m2(int arg) { + return 0; + } + + @Pure + int m3(int arg) { + return 0; + } + + int m4(int arg) { + return 0; + } + } + + class MyClass extends Object { + public int hashCode() { + return 42; + } + } + + class Wrapper { + Object key; + + @SideEffectFree + public Wrapper(Object key) { + this.key = key; + } } - } } diff --git a/framework/tests/flow/StorePure.java b/framework/tests/flow/StorePure.java index 81795553119..22baa6d141d 100644 --- a/framework/tests/flow/StorePure.java +++ b/framework/tests/flow/StorePure.java @@ -6,143 +6,143 @@ // various tests about keeping information in the store about pure method calls public class StorePure { - String f1, f2; - - // various pure methods - - @Pure - String pure1() { - return null; - } - - @Pure - String pure1b() { - return null; - } - - @Deterministic - String pure1c() { - return null; - } - - @Pure - String pure2(int i) { - return null; - } - - @Pure - String pure3(boolean b) { - return null; - } - - @Pure - String pure4(String o) { - return null; - } - - void nonpure() {} - - void t1(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure1() == p1) { - @Odd String l1 = pure1(); - l0 = "a"; // does not remove information - @Odd String l1b = pure1(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = pure1b(); - nonpure(); // non-pure method call might change the return value of pure1 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure1(); + String f1, f2; + + // various pure methods + + @Pure + String pure1() { + return null; + } + + @Pure + String pure1b() { + return null; + } + + @Deterministic + String pure1c() { + return null; + } + + @Pure + String pure2(int i) { + return null; + } + + @Pure + String pure3(boolean b) { + return null; } - } - // check that it only works for deterministic methods - void t1b(@Odd String p1, String p2, boolean b1) { - if (pure1c() == p1) { - @Odd String l1 = pure1c(); + @Pure + String pure4(String o) { + return null; } - } - - void t2(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure1() == p1) { - @Odd String l1 = pure1(); - l0 = "a"; // does not remove information - @Odd String l1b = pure1(); - // :: error: (assignment.type.incompatible) - @Odd String l2 = pure1b(); - f1 = ""; // field update might change the return value of pure1 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure1(); + + void nonpure() {} + + void t1(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure1() == p1) { + @Odd String l1 = pure1(); + l0 = "a"; // does not remove information + @Odd String l1b = pure1(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = pure1b(); + nonpure(); // non-pure method call might change the return value of pure1 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure1(); + } + } + + // check that it only works for deterministic methods + void t1b(@Odd String p1, String p2, boolean b1) { + if (pure1c() == p1) { + @Odd String l1 = pure1c(); + } + } + + void t2(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure1() == p1) { + @Odd String l1 = pure1(); + l0 = "a"; // does not remove information + @Odd String l1b = pure1(); + // :: error: (assignment.type.incompatible) + @Odd String l2 = pure1b(); + f1 = ""; // field update might change the return value of pure1 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure1(); + } } - } - - void t3(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure2(1) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure2(0); - @Odd String l1 = pure2(1); - l0 = "a"; // does not remove information - @Odd String l1b = pure2(1); - nonpure(); // non-pure method call might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure2(1); + + void t3(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure2(1) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure2(0); + @Odd String l1 = pure2(1); + l0 = "a"; // does not remove information + @Odd String l1b = pure2(1); + nonpure(); // non-pure method call might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure2(1); + } } - } - - void t4(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure2(1) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure2(0); - @Odd String l1 = pure2(1); - l0 = "a"; // does not remove information - @Odd String l1b = pure2(1); - f1 = ""; // field update might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure2(1); + + void t4(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure2(1) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure2(0); + @Odd String l1 = pure2(1); + l0 = "a"; // does not remove information + @Odd String l1b = pure2(1); + f1 = ""; // field update might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure2(1); + } } - } - - void t5(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure3(true) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure3(false); - @Odd String l1 = pure3(true); - l0 = "a"; // does not remove information - @Odd String l1b = pure3(true); - nonpure(); // non-pure method call might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure3(true); + + void t5(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure3(true) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure3(false); + @Odd String l1 = pure3(true); + l0 = "a"; // does not remove information + @Odd String l1b = pure3(true); + nonpure(); // non-pure method call might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure3(true); + } } - } - - void t6(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure3(true) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure3(false); - @Odd String l1 = pure3(true); - l0 = "a"; // does not remove information - @Odd String l1b = pure3(true); - f1 = ""; // field update might change the return value of pure2 - // :: error: (assignment.type.incompatible) - @Odd String l3 = pure3(true); + + void t6(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure3(true) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure3(false); + @Odd String l1 = pure3(true); + l0 = "a"; // does not remove information + @Odd String l1b = pure3(true); + f1 = ""; // field update might change the return value of pure2 + // :: error: (assignment.type.incompatible) + @Odd String l3 = pure3(true); + } } - } - - // local variable as argument - void t7(@Odd String p1, String p2, boolean b1) { - String l0 = ""; - if (pure4(l0) == p1) { - // :: error: (assignment.type.incompatible) - @Odd String l4 = pure4("jk"); - @Odd String l1 = pure4(l0); - l0 = "a"; // remove information (!) - // :: error: (assignment.type.incompatible) - @Odd String l1b = pure4(l0); + + // local variable as argument + void t7(@Odd String p1, String p2, boolean b1) { + String l0 = ""; + if (pure4(l0) == p1) { + // :: error: (assignment.type.incompatible) + @Odd String l4 = pure4("jk"); + @Odd String l1 = pure4(l0); + l0 = "a"; // remove information (!) + // :: error: (assignment.type.incompatible) + @Odd String l1b = pure4(l0); + } } - } } diff --git a/framework/tests/flow/Termination.java b/framework/tests/flow/Termination.java index 51dc1334e00..76f92cf9707 100644 --- a/framework/tests/flow/Termination.java +++ b/framework/tests/flow/Termination.java @@ -5,16 +5,16 @@ // various tests for @TerminatesExecution public class Termination { - @TerminatesExecution - void exit() {} + @TerminatesExecution + void exit() {} - void t1(@Odd String p1, String p2, boolean b1) { - String l1 = p2; - if (b1) { - l1 = p1; - } else { - exit(); + void t1(@Odd String p1, String p2, boolean b1) { + String l1 = p2; + if (b1) { + l1 = p1; + } else { + exit(); + } + @Odd String l3 = l1; } - @Odd String l3 = l1; - } } diff --git a/framework/tests/flow/Values.java b/framework/tests/flow/Values.java index 0169d552aa8..244ec754cd3 100644 --- a/framework/tests/flow/Values.java +++ b/framework/tests/flow/Values.java @@ -1,69 +1,70 @@ -import java.util.Collection; import org.checkerframework.framework.testchecker.util.*; +import java.util.Collection; + public class Values { - void test() { - Object o = get(); - Object o1 = get1(); - Object o2 = get2(); - foo1(o1); - foo2(o2); + void test() { + Object o = get(); + Object o1 = get1(); + Object o2 = get2(); + foo1(o1); + foo2(o2); - // :: error: (argument.type.incompatible) - foo1(o); - // :: error: (argument.type.incompatible) - foo2(o1); - // :: error: (argument.type.incompatible) - foo1(o2); - // :: error: (argument.type.incompatible) - foo(o2); + // :: error: (argument.type.incompatible) + foo1(o); + // :: error: (argument.type.incompatible) + foo2(o1); + // :: error: (argument.type.incompatible) + foo1(o2); + // :: error: (argument.type.incompatible) + foo(o2); - o1 = o2; - foo2(o1); - // :: error: (argument.type.incompatible) - foo1(o1); + o1 = o2; + foo2(o1); + // :: error: (argument.type.incompatible) + foo1(o1); - o2 = get1(); - foo1(o2); - // :: error: (argument.type.incompatible) - foo2(o2); - } + o2 = get1(); + foo1(o2); + // :: error: (argument.type.incompatible) + foo2(o2); + } - void andlubTest(Collection c) { - for (Object obj : c) { - Object o = get1(); + void andlubTest(Collection c) { + for (Object obj : c) { + Object o = get1(); + } } - } - void orlubTest(boolean b1, boolean b2) { - if (b1) { - Object o = get1(); - return; - } else if (b2) { - Object o = get2(); - return; + void orlubTest(boolean b1, boolean b2) { + if (b1) { + Object o = get1(); + return; + } else if (b2) { + Object o = get2(); + return; + } } - } - void foo(@ValueTypeAnno Object o) {} + void foo(@ValueTypeAnno Object o) {} - void foo1(@ValueTypeAnno(1) Object o) {} + void foo1(@ValueTypeAnno(1) Object o) {} - void foo2(@ValueTypeAnno(2) Object o) {} + void foo2(@ValueTypeAnno(2) Object o) {} - @SuppressWarnings("flowtest:return.type.incompatible") - @ValueTypeAnno Object get() { - return null; - } + @SuppressWarnings("flowtest:return.type.incompatible") + @ValueTypeAnno Object get() { + return null; + } - @SuppressWarnings("flowtest:return.type.incompatible") - @ValueTypeAnno(1) Object get1() { - return null; - } + @SuppressWarnings("flowtest:return.type.incompatible") + @ValueTypeAnno(1) Object get1() { + return null; + } - @SuppressWarnings("flowtest:return.type.incompatible") - @ValueTypeAnno(2) Object get2() { - return null; - } + @SuppressWarnings("flowtest:return.type.incompatible") + @ValueTypeAnno(2) Object get2() { + return null; + } } diff --git a/framework/tests/flow/flowexpression-scope/Class1.java b/framework/tests/flow/flowexpression-scope/Class1.java index c7fe2ca8084..8292aeb6cf3 100644 --- a/framework/tests/flow/flowexpression-scope/Class1.java +++ b/framework/tests/flow/flowexpression-scope/Class1.java @@ -1,5 +1,5 @@ package pkg1; public class Class1 { - public static Object field; + public static Object field; } diff --git a/framework/tests/flow/flowexpression-scope/Class2.java b/framework/tests/flow/flowexpression-scope/Class2.java index 4b9f6ae2918..24e0473cb54 100644 --- a/framework/tests/flow/flowexpression-scope/Class2.java +++ b/framework/tests/flow/flowexpression-scope/Class2.java @@ -3,42 +3,43 @@ import org.checkerframework.framework.testchecker.util.EnsuresOdd; import org.checkerframework.framework.testchecker.util.Odd; import org.checkerframework.framework.testchecker.util.RequiresOdd; + import pkg1.Class1; public class Class2 { - @RequiresOdd("Class1.field") - // :: error: (flowexpr.parse.error) - public void requiresOddParseError() { - // :: error: (assignment.type.incompatible) - @Odd Object odd = Class1.field; - } - - @RequiresOdd("pkg1.Class1.field") - public void requiresOdd() { - @Odd Object odd = Class1.field; - } - - @EnsuresOdd("Class1.field") - // :: error: (flowexpr.parse.error) - public void ensuresOddParseError() { - // :: warning: (cast.unsafe.constructor.invocation) - Class1.field = new @Odd Object(); - } - - @EnsuresOdd("pkg1.Class1.field") - public void ensuresOdd() { - // :: warning: (cast.unsafe.constructor.invocation) - Class1.field = new @Odd Object(); - } - - void illegalUse() { - // :: error: (contracts.precondition.not.satisfied) - requiresOdd(); - } - - void legalUse() { - // :: warning: (cast.unsafe.constructor.invocation) - Class1.field = new @Odd Object(); - requiresOdd(); - } + @RequiresOdd("Class1.field") + // :: error: (flowexpr.parse.error) + public void requiresOddParseError() { + // :: error: (assignment.type.incompatible) + @Odd Object odd = Class1.field; + } + + @RequiresOdd("pkg1.Class1.field") + public void requiresOdd() { + @Odd Object odd = Class1.field; + } + + @EnsuresOdd("Class1.field") + // :: error: (flowexpr.parse.error) + public void ensuresOddParseError() { + // :: warning: (cast.unsafe.constructor.invocation) + Class1.field = new @Odd Object(); + } + + @EnsuresOdd("pkg1.Class1.field") + public void ensuresOdd() { + // :: warning: (cast.unsafe.constructor.invocation) + Class1.field = new @Odd Object(); + } + + void illegalUse() { + // :: error: (contracts.precondition.not.satisfied) + requiresOdd(); + } + + void legalUse() { + // :: warning: (cast.unsafe.constructor.invocation) + Class1.field = new @Odd Object(); + requiresOdd(); + } } diff --git a/framework/tests/flow/flowexpression-scope/Issue862.java b/framework/tests/flow/flowexpression-scope/Issue862.java index 7237cb643c4..86f0a3e5838 100644 --- a/framework/tests/flow/flowexpression-scope/Issue862.java +++ b/framework/tests/flow/flowexpression-scope/Issue862.java @@ -6,13 +6,13 @@ import pkg2.Class2; public class Issue862 { - void illegalUse(Class2 class2) { - // :: error: (contracts.precondition.not.satisfied) - class2.requiresOdd(); - } + void illegalUse(Class2 class2) { + // :: error: (contracts.precondition.not.satisfied) + class2.requiresOdd(); + } - void legalUse(Class2 class2) { - class2.ensuresOdd(); - class2.requiresOdd(); - } + void legalUse(Class2 class2) { + class2.ensuresOdd(); + class2.requiresOdd(); + } } diff --git a/framework/tests/flowexpression/ArrayCreationParsing.java b/framework/tests/flowexpression/ArrayCreationParsing.java index a46864a4bad..bc097b131b5 100644 --- a/framework/tests/flowexpression/ArrayCreationParsing.java +++ b/framework/tests/flowexpression/ArrayCreationParsing.java @@ -3,32 +3,32 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ArrayCreationParsing { - @FlowExp("new int[2]") Object value1; + @FlowExp("new int[2]") Object value1; - @FlowExp("new int[2][2]") Object value2; + @FlowExp("new int[2][2]") Object value2; - @FlowExp("new String[2]") Object value3; + @FlowExp("new String[2]") Object value3; - @FlowExp("new String[] {\"a\", \"b\"}") - Object value4; + @FlowExp("new String[] {\"a\", \"b\"}") + Object value4; - int i; + int i; - @FlowExp("new int[i]") Object value5; + @FlowExp("new int[i]") Object value5; - @FlowExp("new int[this.i]") Object value6; + @FlowExp("new int[this.i]") Object value6; - @FlowExp("new int[getI()]") Object value7; + @FlowExp("new int[getI()]") Object value7; - @FlowExp("new int[] {i, this.i, getI()}") Object value8; + @FlowExp("new int[] {i, this.i, getI()}") Object value8; - int getI() { - return i; - } + int getI() { + return i; + } - void method(@FlowExp("new java.lang.String[2]") Object param) { - value3 = param; - // :: error: (assignment.type.incompatible) - value1 = param; - } + void method(@FlowExp("new java.lang.String[2]") Object param) { + value3 = param; + // :: error: (assignment.type.incompatible) + value1 = param; + } } diff --git a/framework/tests/flowexpression/BinaryOperations.java b/framework/tests/flowexpression/BinaryOperations.java index 8b36de6977a..b43d64fd5c2 100644 --- a/framework/tests/flowexpression/BinaryOperations.java +++ b/framework/tests/flowexpression/BinaryOperations.java @@ -4,7 +4,7 @@ public class BinaryOperations { - void method(int i, int j, @FlowExp("#1+#2") String s) { - @FlowExp("i+j") String q = s; - } + void method(int i, int j, @FlowExp("#1+#2") String s) { + @FlowExp("i+j") String q = s; + } } diff --git a/framework/tests/flowexpression/Canonicalization.java b/framework/tests/flowexpression/Canonicalization.java index d476c002b26..06e7e7f1a1c 100644 --- a/framework/tests/flowexpression/Canonicalization.java +++ b/framework/tests/flowexpression/Canonicalization.java @@ -2,40 +2,40 @@ public class Canonicalization { - class LockExample { - protected final Object myLock = new Object(); - protected @FlowExp("myLock") Object locked; - protected @FlowExp("this.myLock") Object locked2; - - public @FlowExp("myLock") Object getLocked() { - return locked; + class LockExample { + protected final Object myLock = new Object(); + protected @FlowExp("myLock") Object locked; + protected @FlowExp("this.myLock") Object locked2; + + public @FlowExp("myLock") Object getLocked() { + return locked; + } } - } - class Use { - final LockExample lockExample1 = new LockExample(); - final Object myLock = new Object(); + class Use { + final LockExample lockExample1 = new LockExample(); + final Object myLock = new Object(); - @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; + @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; - @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; + @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object o3 = lockExample1.locked; + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object o3 = lockExample1.locked; - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object o4 = lockExample1.locked2; + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object o4 = lockExample1.locked2; - @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); + @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object oM2 = lockExample1.getLocked(); + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object oM2 = lockExample1.getLocked(); - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object oM3 = lockExample1.getLocked(); - } + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object oM3 = lockExample1.getLocked(); + } } diff --git a/framework/tests/flowexpression/CharAndDoubleParsing.java b/framework/tests/flowexpression/CharAndDoubleParsing.java index 01341ba1faf..a9a8226ef62 100644 --- a/framework/tests/flowexpression/CharAndDoubleParsing.java +++ b/framework/tests/flowexpression/CharAndDoubleParsing.java @@ -3,11 +3,11 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class CharAndDoubleParsing { - void doubleParsing(@FlowExp("1.0") Object doubleValue) { - @FlowExp("1.0") Object value = doubleValue; - } + void doubleParsing(@FlowExp("1.0") Object doubleValue) { + @FlowExp("1.0") Object value = doubleValue; + } - void CharParsing(@FlowExp("'c'") Object charValue) { - @FlowExp("'c'") Object value = charValue; - } + void CharParsing(@FlowExp("'c'") Object charValue) { + @FlowExp("'c'") Object value = charValue; + } } diff --git a/framework/tests/flowexpression/ClassLiterals.java b/framework/tests/flowexpression/ClassLiterals.java index 9324f0cc021..276407d856b 100644 --- a/framework/tests/flowexpression/ClassLiterals.java +++ b/framework/tests/flowexpression/ClassLiterals.java @@ -3,30 +3,30 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ClassLiterals { - static class String {} + static class String {} - void method( - @FlowExp("String.class") Object p1, - @FlowExp("String.class") Object p2, - @FlowExp("java.lang.String.class") Object p3) { - @FlowExp("String.class") Object l1 = p1; - @FlowExp("String.class") Object l2 = p2; - // :: error: (assignment.type.incompatible) - @FlowExp("String.class") Object l3 = p3; - // :: error: (assignment.type.incompatible) - @FlowExp("java.lang.String.class") Object l4 = p1; - // :: error: (assignment.type.incompatible) - @FlowExp("java.lang.String.class") Object l5 = p2; - @FlowExp("java.lang.String.class") Object l6 = p3; - } + void method( + @FlowExp("String.class") Object p1, + @FlowExp("String.class") Object p2, + @FlowExp("java.lang.String.class") Object p3) { + @FlowExp("String.class") Object l1 = p1; + @FlowExp("String.class") Object l2 = p2; + // :: error: (assignment.type.incompatible) + @FlowExp("String.class") Object l3 = p3; + // :: error: (assignment.type.incompatible) + @FlowExp("java.lang.String.class") Object l4 = p1; + // :: error: (assignment.type.incompatible) + @FlowExp("java.lang.String.class") Object l5 = p2; + @FlowExp("java.lang.String.class") Object l6 = p3; + } - @FlowExp("void.class") String s0; + @FlowExp("void.class") String s0; - @FlowExp("int.class") String s1; + @FlowExp("int.class") String s1; - @FlowExp("int[].class") String s2; + @FlowExp("int[].class") String s2; - @FlowExp("String[].class") String s3; + @FlowExp("String[].class") String s3; - @FlowExp("java.lang.String[].class") String s4; + @FlowExp("java.lang.String[].class") String s4; } diff --git a/framework/tests/flowexpression/Complex.java b/framework/tests/flowexpression/Complex.java index 576bd0d617e..71dedd56773 100644 --- a/framework/tests/flowexpression/Complex.java +++ b/framework/tests/flowexpression/Complex.java @@ -1,39 +1,40 @@ package flowexpression; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; + import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Complex { - class DocCategory { - public Map fields = new HashMap<>(); - } + class DocCategory { + public Map fields = new HashMap<>(); + } - protected DocCategory[] categories = new DocCategory[2]; + protected DocCategory[] categories = new DocCategory[2]; - void test() { - for (int c = 0; c < categories.length; c++) { + void test() { + for (int c = 0; c < categories.length; c++) { - for (@FlowExp("categories[c].fields") String field : sortedKeySet(categories[c].fields)) { - @FlowExp("categories[c].fields") String f = field; - } + for (@FlowExp("categories[c].fields") String field : sortedKeySet(categories[c].fields)) { + @FlowExp("categories[c].fields") String f = field; + } + } } - } - public static , V> Collection<@FlowExp("#1") K> sortedKeySet( - Map m) { - throw new RuntimeException(); - } + public static , V> Collection<@FlowExp("#1") K> sortedKeySet( + Map m) { + throw new RuntimeException(); + } - private static Map> succs1 = new HashMap<>(); + private static Map> succs1 = new HashMap<>(); - void method() { - Map> dom1post = dominators(succs1); - } + void method() { + Map> dom1post = dominators(succs1); + } - public static Map> dominators(Map> predecessors) { - throw new RuntimeException(); - } + public static Map> dominators(Map> predecessors) { + throw new RuntimeException(); + } } diff --git a/framework/tests/flowexpression/Constructor.java b/framework/tests/flowexpression/Constructor.java index d1d88ea00bb..0da30b2c530 100644 --- a/framework/tests/flowexpression/Constructor.java +++ b/framework/tests/flowexpression/Constructor.java @@ -2,26 +2,26 @@ public class Constructor { - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - static class MyClass { - String field; + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + static class MyClass { + String field; - @FlowExp("field") MyClass() {} - } + @FlowExp("field") MyClass() {} + } - static class MyClass2 { - String field; - String field2; + static class MyClass2 { + String field; + String field2; - @SuppressWarnings("cast.unsafe.constructor.invocation") - void method() { - // TODO: This should be an error. - MyClass c = new @FlowExp("field") MyClass(); - // :: error: (expression.unparsable.type.invalid) :: error: - // (constructor.invocation.invalid) - MyClass c2 = new @FlowExp("bad") MyClass(); - // :: error: (constructor.invocation.invalid) - MyClass c3 = new @FlowExp("field2") MyClass(); + @SuppressWarnings("cast.unsafe.constructor.invocation") + void method() { + // TODO: This should be an error. + MyClass c = new @FlowExp("field") MyClass(); + // :: error: (expression.unparsable.type.invalid) :: error: + // (constructor.invocation.invalid) + MyClass c2 = new @FlowExp("bad") MyClass(); + // :: error: (constructor.invocation.invalid) + MyClass c3 = new @FlowExp("field2") MyClass(); + } } - } } diff --git a/framework/tests/flowexpression/Fields.java b/framework/tests/flowexpression/Fields.java index 64896030aa9..e712db03ac6 100644 --- a/framework/tests/flowexpression/Fields.java +++ b/framework/tests/flowexpression/Fields.java @@ -3,16 +3,17 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Fields { - static class String { - public static final java.lang.String HELLO = "hello"; - } + static class String { + public static final java.lang.String HELLO = "hello"; + } - void method( - // :: error: (expression.unparsable.type.invalid) - @FlowExp("java.lang.String.HELLO") Object p1, @FlowExp("Fields.String.HELLO") Object p2) { - // :: error: (assignment.type.incompatible) - @FlowExp("String.HELLO") Object l1 = p1; - @FlowExp("String.HELLO") Object l2 = p2; - @FlowExp("flowexpression.Fields.String.HELLO") Object l3 = p2; - } + void method( + // :: error: (expression.unparsable.type.invalid) + @FlowExp("java.lang.String.HELLO") Object p1, + @FlowExp("Fields.String.HELLO") Object p2) { + // :: error: (assignment.type.incompatible) + @FlowExp("String.HELLO") Object l1 = p1; + @FlowExp("String.HELLO") Object l2 = p2; + @FlowExp("flowexpression.Fields.String.HELLO") Object l3 = p2; + } } diff --git a/framework/tests/flowexpression/InnerClasses.java b/framework/tests/flowexpression/InnerClasses.java index 383acfec05f..45585e3ff36 100644 --- a/framework/tests/flowexpression/InnerClasses.java +++ b/framework/tests/flowexpression/InnerClasses.java @@ -3,36 +3,36 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class InnerClasses { - public String outerInstanceField = ""; - public static String outerStaticField = ""; + public String outerInstanceField = ""; + public static String outerStaticField = ""; - static class InnerClass { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("outerInstanceField") Object o = null; + static class InnerClass { + // :: error: (expression.unparsable.type.invalid) + @FlowExp("outerInstanceField") Object o = null; - @FlowExp("outerStaticField") Object o2 = null; - } + @FlowExp("outerStaticField") Object o2 = null; + } - class NonStaticInnerClass { - @FlowExp("outerInstanceField") Object o = null; + class NonStaticInnerClass { + @FlowExp("outerInstanceField") Object o = null; - @FlowExp("outerStaticField") Object o2 = null; - } + @FlowExp("outerStaticField") Object o2 = null; + } - static class InnerClass2 { - public String outerInstanceField = ""; + static class InnerClass2 { + public String outerInstanceField = ""; - @FlowExp("outerInstanceField") Object o = null; - } + @FlowExp("outerInstanceField") Object o = null; + } - class TestUses { - void method(InnerClass innerClass, InnerClass2 innerClass2) { - // :: error: (expression.unparsable.type.invalid) :: error: - // (assignment.type.incompatible) - @FlowExp("innerClass.outerInstanceField") Object o = innerClass.o; - @FlowExp("InnerClasses.outerStaticField") Object o2 = innerClass.o2; + class TestUses { + void method(InnerClass innerClass, InnerClass2 innerClass2) { + // :: error: (expression.unparsable.type.invalid) :: error: + // (assignment.type.incompatible) + @FlowExp("innerClass.outerInstanceField") Object o = innerClass.o; + @FlowExp("InnerClasses.outerStaticField") Object o2 = innerClass.o2; - @FlowExp("innerClass2.outerInstanceField") Object o3 = innerClass2.o; + @FlowExp("innerClass2.outerInstanceField") Object o3 = innerClass2.o; + } } - } } diff --git a/framework/tests/flowexpression/Issue1609.java b/framework/tests/flowexpression/Issue1609.java index be71f4647b5..8e0607d602b 100644 --- a/framework/tests/flowexpression/Issue1609.java +++ b/framework/tests/flowexpression/Issue1609.java @@ -4,207 +4,207 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Issue1609 { - void method(@FlowExp("\"" + s + "\"") String x) {} + void method(@FlowExp("\"" + s + "\"") String x) {} - public static final String s = - "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" - + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" - + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" - + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" - + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" - + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" - + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" - + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" - + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" - + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" - + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" - + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" - + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" - + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" - + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" - + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" - + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" - + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" - + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" - + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" - + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" - + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" - + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" - + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" - + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" - + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" - + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" - + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" - + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" - + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" - + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" - + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" - + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" - + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" - + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" - + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" - + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" - + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" - + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" - + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" - + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" - + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" - + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" - + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" - + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" - + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" - + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" - + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" - + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" - + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" - + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" - + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" - + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" - + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" - + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" - + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" - + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" - + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" - + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" - + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" - + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" - + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" - + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" - + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" - + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" - + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" - + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" - + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" - + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" - + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" - + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" - + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" - + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" - + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" - + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" - + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" - + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" - + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" - + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" - + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" - + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" - + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" - + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" - + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" - + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" - + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" - + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" - + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" - + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" - + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" - + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" - + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" - + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" - + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" - + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" - + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" - + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" - + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" - + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" - + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" - + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" - + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" - + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" - + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" - + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" - + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" - + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" - + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" - + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" - + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" - + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" - + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" - + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" - + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" - + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" - + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" - + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" - + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" - + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" - + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" - + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" - + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" - + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" - + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" - + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" - + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" - + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" - + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" - + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" - + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" - + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" - + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" - + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" - + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" - + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" - + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" - + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" - + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" - + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" - + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" - + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" - + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" - + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" - + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" - + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" - + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" - + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" - + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" - + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" - + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" - + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" - + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" - + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" - + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" - + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" - + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" - + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" - + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" - + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" - + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" - + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" - + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" - + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" - + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" - + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" - + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" - + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" - + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" - + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" - + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" - + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" - + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" - + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" - + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" - + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" - + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" - + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" - + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" - + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" - + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" - + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" - + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" - + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" - + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" - + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" - + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" - + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" - + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" - + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" - + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" - + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" - + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" - + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" - + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" - + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" - + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" - + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" - + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" - + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" - + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; + public static final String s = + "\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa" + + "\u00fb\u00fc\u00fd\u00fe\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b\u010c\u010d\u010e" + + "\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b\u011c\u011d\u011e\u011f\u0120\u0121\u0122" + + "\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136" + + "\u0137\u0138\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014a" + + "\u014b\u014c\u014d\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d\u015e" + + "\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d\u016e\u016f\u0170\u0171\u0172" + + "\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d\u017e\u017f\u0180\u0181\u0182\u0183\u0184\u0185\u0186" + + "\u0187\u0188\u0189\u018a\u018b\u018c\u018d\u018e\u018f\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019a" + + "\u019b\u019c\u019d\u019e\u019f\u01a0\u01a1\u01a2\u01a3\u01a4\u01a5\u01a6\u01a7\u01a8\u01a9\u01aa\u01ab\u01ac\u01ad\u01ae" + + "\u01af\u01b0\u01b1\u01b2\u01b3\u01b4\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2" + + "\u01c3\u01c4\u01c5\u01c6\u01c7\u01c8\u01c9\u01ca\u01cb\u01cc\u01cd\u01ce\u01cf\u01d0\u01d1\u01d2\u01d3\u01d4\u01d5\u01d6" + + "\u01d7\u01d8\u01d9\u01da\u01db\u01dc\u01dd\u01de\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01e6\u01e7\u01e8\u01e9\u01ea" + + "\u01eb\u01ec\u01ed\u01ee\u01ef\u01f0\u01f1\u01f2\u01f3\u01f4\u01f5\u01f6\u01f7\u01f8\u01f9\u01fa\u01fb\u01fc\u01fd\u01fe" + + "\u01ff\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020a\u020b\u020c\u020d\u020e\u020f\u0210\u0211\u0212" + + "\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021a\u021b\u021c\u021d\u021e\u021f\u0220\u0221\u0222\u0223\u0224\u0225\u0226" + + "\u0227\u0228\u0229\u022a\u022b\u022c\u022d\u022e\u022f\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023a" + + "\u023b\u023c\u023d\u023e\u023f\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e" + + "\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262" + + "\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276" + + "\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a" + + "\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e" + + "\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u02b0\u02b1\u02b2" + + "\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c2\u02c3\u02c4\u02c5\u02c6" + + "\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da" + + "\u02db\u02dc\u02dd\u02de\u02df\u02e0\u02e1\u02e2\u02e3\u02e4\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ee" + + "\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0300\u0301\u0302" + + "\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316" + + "\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a" + + "\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e" + + "\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352" + + "\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366" + + "\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037a" + + "\u037b\u037c\u037d\u037e\u037f\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038a\u038b\u038c\u038d\u038e" + + "\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2" + + "\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6" + + "\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca" + + "\u03cb\u03cc\u03cd\u03ce\u03cf\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u03da\u03db\u03dc\u03dd\u03de" + + "\u03df\u03e0\u03e1\u03e2\u03e3\u03e4\u03e5\u03e6\u03e7\u03e8\u03e9\u03ea\u03eb\u03ec\u03ed\u03ee\u03ef\u03f0\u03f1\u03f2" + + "\u03f3\u03f4\u03f5\u03f6\u03f7\u03f8\u03f9\u03fa\u03fb\u03fc\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406" + + "\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a" + + "\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e" + + "\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442" + + "\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456" + + "\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0460\u0461\u0462\u0463\u0464\u0465\u0466\u0467\u0468\u0469\u046a" + + "\u046b\u046c\u046d\u046e\u046f\u0470\u0471\u0472\u0473\u0474\u0475\u0476\u0477\u0478\u0479\u047a\u047b\u047c\u047d\u047e" + + "\u047f\u0480\u0481\u0482\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048a\u048b\u048c\u048d\u048e\u048f\u0490\u0491\u0492" + + "\u0493\u0494\u0495\u0496\u0497\u0498\u0499\u049a\u049b\u049c\u049d\u049e\u049f\u04a0\u04a1\u04a2\u04a3\u04a4\u04a5\u04a6" + + "\u04a7\u04a8\u04a9\u04aa\u04ab\u04ac\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u04b5\u04b6\u04b7\u04b8\u04b9\u04ba" + + "\u04bb\u04bc\u04bd\u04be\u04bf\u04c0\u04c1\u04c2\u04c3\u04c4\u04c5\u04c6\u04c7\u04c8\u04c9\u04ca\u04cb\u04cc\u04cd\u04ce" + + "\u04cf\u04d0\u04d1\u04d2\u04d3\u04d4\u04d5\u04d6\u04d7\u04d8\u04d9\u04da\u04db\u04dc\u04dd\u04de\u04df\u04e0\u04e1\u04e2" + + "\u04e3\u04e4\u04e5\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u04f4\u04f5\u04f6" + + "\u04f7\u04f8\u04f9\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u0507\u0508\u0509\u050a" + + "\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e" + + "\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532" + + "\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546" + + "\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0557\u0558\u0559\u055a" + + "\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e" + + "\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582" + + "\u0583\u0584\u0585\u0586\u0587\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0590\u0591\u0592\u0593\u0594\u0595\u0596" + + "\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa" + + "\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be" + + "\u05bf\u05c0\u05c1\u05c2\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2" + + "\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6" + + "\u05e7\u05e8\u05e9\u05ea\u05eb\u05ec\u05ed\u05ee\u05ef\u05f0\u05f1\u05f2\u05f3\u05f4\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa" + + "\u05fb\u05fc\u05fd\u05fe\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e" + + "\u060f\u0610\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622" + + "\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636" + + "\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a" + + "\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e" + + "\u065f\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0670\u0671\u0672" + + "\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686" + + "\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a" + + "\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae" + + "\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2" + + "\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d4\u06d5\u06d6" + + "\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea" + + "\u06eb\u06ec\u06ed\u06ee\u06ef\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06fb\u06fc\u06fd\u06fe" + + "\u06ff\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u070e\u070f\u0710\u0711\u0712" + + "\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726" + + "\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a" + + "\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074e" + + "\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762" + + "\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776" + + "\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a" + + "\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e" + + "\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07b1\u07b2" + + "\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6" + + "\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da" + + "\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee" + + "\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802" + + "\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816" + + "\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a" + + "\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e" + + "\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852" + + "\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866" + + "\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a" + + "\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e" + + "\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2" + + "\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6" + + "\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca" + + "\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de" + + "\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2" + + "\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906" + + "\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a" + + "\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e" + + "\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942" + + "\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956" + + "\u0957\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a" + + "\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e" + + "\u097f\u0980\u0981\u0982\u0983\u0984\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992" + + "\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6" + + "\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8\u09b9\u09ba" + + "\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce" + + "\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2" + + "\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6" + + "\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a" + + "\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e" + + "\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a31\u0a32" + + "\u0a33\u0a34\u0a35\u0a36\u0a37\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46" + + "\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a" + + "\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e" + + "\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82" + + "\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96" + + "\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aa9\u0aaa" + + "\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe" + + "\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0ac9\u0aca\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2" + + "\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6" + + "\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa" + + "\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e" + + "\u0b0f\u0b10\u0b11\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22" + + "\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36" + + "\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a" + + "\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e" + + "\u0b5f\u0b60\u0b61\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71\u0b72" + + "\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86" + + "\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a" + + "\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae" + + "\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2" + + "\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6" + + "\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea" + + "\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1\u0bf2\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe" + + "\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12" + + "\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26" + + "\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a" + + "\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e" + + "\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c55\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u0c62" + + "\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76" + + "\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a" + + "\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e" + + "\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2" + + "\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6" + + "\u0cc7\u0cc8\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda" + + "\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee" + + "\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02" + + "\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16" + + "\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a" + + "\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e" + + "\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51\u0d52" + + "\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66" + + "\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a" + + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e" + + "\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2" + + "\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6" + + "\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dc7\u0dc8\u0dc9\u0dca" + + "\u0dcb\u0dcc\u0dcd\u0dce\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde" + + "\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2" + + "\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06" + + "\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a" + + "\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e" + + "\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42" + + "\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56" + + "\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a" + + "\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e" + + "\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92" + + "\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6" + + "\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba" + + "\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece" + + "\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2" + + "\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6" + + "\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a" + + "\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e" + + "\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32" + + "\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46" + + "\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a" + + "\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e" + + "\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82" + + "\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96" + + "\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa" + + "\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe" + + "\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2" + + "\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6" + + "\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa" + + "\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e" + + "\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1022" + + "\u1023\u1024\u1025\u1026\u1027\u1028\u1029\u102a\u102b\u102c\u102d\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u1035\u1036" + + "\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u104a" + + "\u104b\u104c\u104d\u104e\u104f\u1050\u1051\u1052\u1053\u1054\u1055\u1056\u1057\u1058\u1059\u105a\u105b\u105c\u105d\u105e" + + "\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072" + + "\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086"; } diff --git a/framework/tests/flowexpression/LambdaParameter.java b/framework/tests/flowexpression/LambdaParameter.java index c974e95d5d8..5736b799b20 100644 --- a/framework/tests/flowexpression/LambdaParameter.java +++ b/framework/tests/flowexpression/LambdaParameter.java @@ -1,66 +1,67 @@ -import java.util.function.Function; import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; +import java.util.function.Function; + public class LambdaParameter { - void method(String methodParam) { - Function func1 = - ( - // :: error: (lambda.param.type.incompatible) - @FlowExp("methodParam") String lambdaParam) -> { - return ""; - }; - Function func2 = - ( - // :: error: (lambda.param.type.incompatible) :: error: - // (expression.unparsable.type.invalid) - @FlowExp("lambdaParam") String lambdaParam) -> { - return ""; - }; - Function func3 = - ( - // :: error: (lambda.param.type.incompatible) - @FlowExp("#1") String lambdaParam) -> { - @FlowExp("lambdaParam") String s = lambdaParam; - return ""; - }; - Function<@FlowExp("methodParam") String, String> func4 = - (@FlowExp("methodParam") String lambdaParam) -> { - return ""; - }; - } + void method(String methodParam) { + Function func1 = + ( + // :: error: (lambda.param.type.incompatible) + @FlowExp("methodParam") String lambdaParam) -> { + return ""; + }; + Function func2 = + ( + // :: error: (lambda.param.type.incompatible) :: error: + // (expression.unparsable.type.invalid) + @FlowExp("lambdaParam") String lambdaParam) -> { + return ""; + }; + Function func3 = + ( + // :: error: (lambda.param.type.incompatible) + @FlowExp("#1") String lambdaParam) -> { + @FlowExp("lambdaParam") String s = lambdaParam; + return ""; + }; + Function<@FlowExp("methodParam") String, String> func4 = + (@FlowExp("methodParam") String lambdaParam) -> { + return ""; + }; + } - void method2(String methodParam, @FlowExp("#1") String methodParam2) { - Function<@FlowExp("methodParam") String, String> func1 = - (@FlowExp("methodParam") String lambdaParam) -> { - @FlowExp("methodParam") String a = methodParam2; - @FlowExp("methodParam") String b = lambdaParam; - return ""; - }; - } + void method2(String methodParam, @FlowExp("#1") String methodParam2) { + Function<@FlowExp("methodParam") String, String> func1 = + (@FlowExp("methodParam") String lambdaParam) -> { + @FlowExp("methodParam") String a = methodParam2; + @FlowExp("methodParam") String b = lambdaParam; + return ""; + }; + } - void method3() { - String local = ""; - Function func1 = - ( - // :: error: (lambda.param.type.incompatible) - @FlowExp("local") String lambdaParam) -> { - return ""; - }; - Function<@FlowExp("local") String, String> func2 = - (@FlowExp("local") String lambdaParam) -> { - return ""; - }; - } + void method3() { + String local = ""; + Function func1 = + ( + // :: error: (lambda.param.type.incompatible) + @FlowExp("local") String lambdaParam) -> { + return ""; + }; + Function<@FlowExp("local") String, String> func2 = + (@FlowExp("local") String lambdaParam) -> { + return ""; + }; + } - void method4() { - String local = ""; - @FlowExp("local") String otherLocal = null; - Function<@FlowExp("local") String, String> func1 = - (@FlowExp("local") String lambdaParam) -> { - @FlowExp("local") String a = otherLocal; - @FlowExp("local") String b = lambdaParam; - return ""; - }; - } + void method4() { + String local = ""; + @FlowExp("local") String otherLocal = null; + Function<@FlowExp("local") String, String> func1 = + (@FlowExp("local") String lambdaParam) -> { + @FlowExp("local") String a = otherLocal; + @FlowExp("local") String b = lambdaParam; + return ""; + }; + } } diff --git a/framework/tests/flowexpression/Private.java b/framework/tests/flowexpression/Private.java index 95c714b62a7..5f8161146ed 100644 --- a/framework/tests/flowexpression/Private.java +++ b/framework/tests/flowexpression/Private.java @@ -1,14 +1,15 @@ package flowexpression; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; + import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Private { - private final Map nameToPpt = new LinkedHashMap<>(); + private final Map nameToPpt = new LinkedHashMap<>(); - public Collection<@FlowExp("nameToPpt") String> nameStringSet() { - throw new RuntimeException(); - } + public Collection<@FlowExp("nameToPpt") String> nameStringSet() { + throw new RuntimeException(); + } } diff --git a/framework/tests/flowexpression/SimpleVPA.java b/framework/tests/flowexpression/SimpleVPA.java index de4e1e4c133..93fe2e262c6 100644 --- a/framework/tests/flowexpression/SimpleVPA.java +++ b/framework/tests/flowexpression/SimpleVPA.java @@ -4,17 +4,17 @@ public class SimpleVPA { - class MyClass { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("this.bad") Object field; - } + class MyClass { + // :: error: (expression.unparsable.type.invalid) + @FlowExp("this.bad") Object field; + } - class Use { - Object bad = new Object(); - MyClass myClass = new MyClass(); + class Use { + Object bad = new Object(); + MyClass myClass = new MyClass(); - @FlowExp("bad") - // :: error: (assignment.type.incompatible) - Object o = myClass.field; - } + @FlowExp("bad") + // :: error: (assignment.type.incompatible) + Object o = myClass.field; + } } diff --git a/framework/tests/flowexpression/Standardize.java b/framework/tests/flowexpression/Standardize.java index 0b6c65dfaed..f23c7a88453 100644 --- a/framework/tests/flowexpression/Standardize.java +++ b/framework/tests/flowexpression/Standardize.java @@ -1,104 +1,106 @@ package flowexpression; +import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; + import java.io.FileInputStream; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Standardize { - Object field; + Object field; + + @SuppressWarnings("assignment.type.incompatible") + @FlowExp("field") Object fieldField = null; - @SuppressWarnings("assignment.type.incompatible") - @FlowExp("field") Object fieldField = null; + void variableDecls(@FlowExp("field") Standardize this, @FlowExp("field") Object paramField) { - void variableDecls(@FlowExp("field") Standardize this, @FlowExp("field") Object paramField) { + @FlowExp("field") Object localField = fieldField; - @FlowExp("field") Object localField = fieldField; + @FlowExp("field") Object o1 = fieldField; + @FlowExp("this.field") Object o2 = fieldField; - @FlowExp("field") Object o1 = fieldField; - @FlowExp("this.field") Object o2 = fieldField; + @FlowExp("field") Object o3 = paramField; + @FlowExp("this.field") Object o4 = paramField; - @FlowExp("field") Object o3 = paramField; - @FlowExp("this.field") Object o4 = paramField; + @FlowExp("field") Object o5 = localField; + @FlowExp("this.field") Object o6 = localField; + + try (@SuppressWarnings("assignment.type.incompatible") + @FlowExp("field") FileInputStream in = new FileInputStream("")) { + in.read(); + @FlowExp("field") Object o7 = in; + @FlowExp("this.field") Object o8 = in; + } catch ( + @SuppressWarnings("exception.parameter.invalid") + @FlowExp("field") Exception ex) { + @FlowExp("field") Object o9 = ex; + @FlowExp("this.field") Object o10 = ex; + } + + @FlowExp("field") Object o11 = this; + @FlowExp("this.field") Object o12 = this; + } - @FlowExp("field") Object o5 = localField; - @FlowExp("this.field") Object o6 = localField; + class MyGen {} + + + void typeVariables(X x) { + MyGen o1; + MyGen o2; + MyGen<@FlowExp("this.field") String> o3; + MyGen<@FlowExp("field") String> o4; + + @FlowExp("field") Object o5 = x; + @FlowExp("this.field") Object o6 = x; + } + + void typeVariable2(A a, @FlowExp("#1") A a2) {} + + void callTypeVariable2(@FlowExp("field") Object param) { + typeVariable2(field, param); + typeVariable2(this.field, param); + } - try (@SuppressWarnings("assignment.type.incompatible") - @FlowExp("field") FileInputStream in = new FileInputStream("")) { - in.read(); - @FlowExp("field") Object o7 = in; - @FlowExp("this.field") Object o8 = in; - } catch ( - @SuppressWarnings("exception.parameter.invalid") - @FlowExp("field") Exception ex) { - @FlowExp("field") Object o9 = ex; - @FlowExp("this.field") Object o10 = ex; + @FlowExp("field") Object testReturn(@FlowExp("this.field") Object param) { + @FlowExp("this.field") Object o = testReturn(param); + return param; } - @FlowExp("field") Object o11 = this; - @FlowExp("this.field") Object o12 = this; - } - - class MyGen {} - - void typeVariables( - X x) { - MyGen o1; - MyGen o2; - MyGen<@FlowExp("this.field") String> o3; - MyGen<@FlowExp("field") String> o4; - - @FlowExp("field") Object o5 = x; - @FlowExp("this.field") Object o6 = x; - } - - void typeVariable2(A a, @FlowExp("#1") A a2) {} - - void callTypeVariable2(@FlowExp("field") Object param) { - typeVariable2(field, param); - typeVariable2(this.field, param); - } - - @FlowExp("field") Object testReturn(@FlowExp("this.field") Object param) { - @FlowExp("this.field") Object o = testReturn(param); - return param; - } - - void testCasts() { - @SuppressWarnings("cast.unsafe") - @FlowExp("this.field") Object o = (@FlowExp("field") Object) new Object(); - @FlowExp("this.field") Object o2 = (@FlowExp("field") Object) o; - } - - void testNewClassTree() { - // :: warning: (cast.unsafe.constructor.invocation) - @FlowExp("this.field") Object o = new @FlowExp("field") Object(); - } - - void list(List<@FlowExp("field") Object> list) { - Object field = new Object(); - // "field" is local variable, but list.get(1) type is @FlowExp("this.field") - @FlowExp("field") - // :: error: (assignment.type.incompatible) - Object o1 = list.get(1); - @FlowExp("this.field") Object o2 = list.get(1); - } - - Object dict = new Object(); - - void typvar() { - // TODO: Why doesn't the diamond operator work? - // Map<@FlowExp("this.dict") String, String> that = new HashMap<>(); - Map<@FlowExp("this.dict") String, String> that = new HashMap<@FlowExp("dict") String, String>(); - } - - void newArray() { - @FlowExp("this.dict") String[] s = new @FlowExp("dict") String[1]; - } - - void clasLiteral(@FlowExp("java.lang.String.class") String param) { - @FlowExp("String.class") String s = param; - } + void testCasts() { + @SuppressWarnings("cast.unsafe") + @FlowExp("this.field") Object o = (@FlowExp("field") Object) new Object(); + @FlowExp("this.field") Object o2 = (@FlowExp("field") Object) o; + } + + void testNewClassTree() { + // :: warning: (cast.unsafe.constructor.invocation) + @FlowExp("this.field") Object o = new @FlowExp("field") Object(); + } + + void list(List<@FlowExp("field") Object> list) { + Object field = new Object(); + // "field" is local variable, but list.get(1) type is @FlowExp("this.field") + @FlowExp("field") + // :: error: (assignment.type.incompatible) + Object o1 = list.get(1); + @FlowExp("this.field") Object o2 = list.get(1); + } + + Object dict = new Object(); + + void typvar() { + // TODO: Why doesn't the diamond operator work? + // Map<@FlowExp("this.dict") String, String> that = new HashMap<>(); + Map<@FlowExp("this.dict") String, String> that = + new HashMap<@FlowExp("dict") String, String>(); + } + + void newArray() { + @FlowExp("this.dict") String[] s = new @FlowExp("dict") String[1]; + } + + void clasLiteral(@FlowExp("java.lang.String.class") String param) { + @FlowExp("String.class") String s = param; + } } diff --git a/framework/tests/flowexpression/TestParsing.java b/framework/tests/flowexpression/TestParsing.java index b898ba3acb3..dd658c0bf67 100644 --- a/framework/tests/flowexpression/TestParsing.java +++ b/framework/tests/flowexpression/TestParsing.java @@ -1,12 +1,12 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class TestParsing { - int[] a = {1, 2}; + int[] a = {1, 2}; - void test(@FlowExp("a.length") Object a, @FlowExp("this.a.length") Object b) {} + void test(@FlowExp("a.length") Object a, @FlowExp("this.a.length") Object b) {} - void test2(@FlowExp("a.clone()") Object a, @FlowExp("this.a.clone()") Object b) {} + void test2(@FlowExp("a.clone()") Object a, @FlowExp("this.a.clone()") Object b) {} - // :: error: (expression.unparsable.type.invalid) - void test3(@FlowExp("a.leng") Object a) {} + // :: error: (expression.unparsable.type.invalid) + void test3(@FlowExp("a.leng") Object a) {} } diff --git a/framework/tests/flowexpression/ThisStaticContext.java b/framework/tests/flowexpression/ThisStaticContext.java index 77b941742db..409788f1db2 100644 --- a/framework/tests/flowexpression/ThisStaticContext.java +++ b/framework/tests/flowexpression/ThisStaticContext.java @@ -1,39 +1,40 @@ -import java.util.Map; import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; +import java.util.Map; + public class ThisStaticContext { - public static Map staticField; - public Map instanceField; + public static Map staticField; + public Map instanceField; - static void staticMethod1( - // :: error: (expression.unparsable.type.invalid) - @FlowExp("this.staticField") Object p1, - @FlowExp("ThisStaticContext.staticField") Object p2, - @FlowExp("staticField") Object p3) { - p2 = p3; - } + static void staticMethod1( + // :: error: (expression.unparsable.type.invalid) + @FlowExp("this.staticField") Object p1, + @FlowExp("ThisStaticContext.staticField") Object p2, + @FlowExp("staticField") Object p3) { + p2 = p3; + } - static void staticMethod2( - // :: error: (expression.unparsable.type.invalid) - @FlowExp("this.instanceField") Object p1, - // :: error: (expression.unparsable.type.invalid) - @FlowExp("ThisStaticContext.instanceField") Object p2, - // :: error: (expression.unparsable.type.invalid) - @FlowExp("instanceField") Object p3) {} + static void staticMethod2( + // :: error: (expression.unparsable.type.invalid) + @FlowExp("this.instanceField") Object p1, + // :: error: (expression.unparsable.type.invalid) + @FlowExp("ThisStaticContext.instanceField") Object p2, + // :: error: (expression.unparsable.type.invalid) + @FlowExp("instanceField") Object p3) {} - void instanceMethod1( - @FlowExp("this.staticField") Object p1, - @FlowExp("ThisStaticContext.staticField") Object p2, - @FlowExp("staticField") Object p3) { - p2 = p3; - p2 = p1; - } + void instanceMethod1( + @FlowExp("this.staticField") Object p1, + @FlowExp("ThisStaticContext.staticField") Object p2, + @FlowExp("staticField") Object p3) { + p2 = p3; + p2 = p1; + } - void instanceMethod2( - @FlowExp("this.instanceField") Object p1, - // :: error: (expression.unparsable.type.invalid) - @FlowExp("ThisStaticContext.instanceField") Object p2, - @FlowExp("instanceField") Object p3) { - p1 = p3; - } + void instanceMethod2( + @FlowExp("this.instanceField") Object p1, + // :: error: (expression.unparsable.type.invalid) + @FlowExp("ThisStaticContext.instanceField") Object p2, + @FlowExp("instanceField") Object p3) { + p1 = p3; + } } diff --git a/framework/tests/flowexpression/ThisSuper.java b/framework/tests/flowexpression/ThisSuper.java index 06018639688..5c2c940a790 100644 --- a/framework/tests/flowexpression/ThisSuper.java +++ b/framework/tests/flowexpression/ThisSuper.java @@ -4,41 +4,41 @@ // @skip-test public class ThisSuper { - static class SuperClass { - protected final Object field = new Object(); + static class SuperClass { + protected final Object field = new Object(); - private @FlowExp("field") Object superField; - } + private @FlowExp("field") Object superField; + } - static class SubClass extends SuperClass { - /* Hides SuperClass.field */ - private final Object field = new Object(); + static class SubClass extends SuperClass { + /* Hides SuperClass.field */ + private final Object field = new Object(); - private @FlowExp("field") Object subField; + private @FlowExp("field") Object subField; - void method() { - // super.superField : @FlowExp("super.field") - // this.subField : @FlowExp("this.field") - // :: error: (assignment.type.incompatible) - this.subField = super.superField; - // :: error: (assignment.type.incompatible) - super.superField = this.subField; + void method() { + // super.superField : @FlowExp("super.field") + // this.subField : @FlowExp("this.field") + // :: error: (assignment.type.incompatible) + this.subField = super.superField; + // :: error: (assignment.type.incompatible) + super.superField = this.subField; - @FlowExp("super.field") Object o1 = super.superField; - @FlowExp("this.field") Object o2 = this.subField; + @FlowExp("super.field") Object o1 = super.superField; + @FlowExp("this.field") Object o2 = this.subField; + } } - } - class OuterClass { - private final Object lock = new Object(); + class OuterClass { + private final Object lock = new Object(); - @FlowExp("this.lock") Object field; + @FlowExp("this.lock") Object field; - class InnerClass { - private final Object lock = new Object(); + class InnerClass { + private final Object lock = new Object(); - // :: error: (assignment.type.incompatible) - @FlowExp("this.lock") Object field2 = field; + // :: error: (assignment.type.incompatible) + @FlowExp("this.lock") Object field2 = field; + } } - } } diff --git a/framework/tests/flowexpression/UnaryOperations.java b/framework/tests/flowexpression/UnaryOperations.java index 9c36ecd52d4..ec9c038f584 100644 --- a/framework/tests/flowexpression/UnaryOperations.java +++ b/framework/tests/flowexpression/UnaryOperations.java @@ -4,8 +4,8 @@ public class UnaryOperations { - void method(int i, @FlowExp("+#1") String s) { - @FlowExp("i") String q = s; - @FlowExp("-9223372036854775808L") String s0; - } + void method(int i, @FlowExp("+#1") String s) { + @FlowExp("i") String q = s; + @FlowExp("-9223372036854775808L") String s0; + } } diff --git a/framework/tests/flowexpression/Unparsable.java b/framework/tests/flowexpression/Unparsable.java index dda2d443cb6..33cc368b8ee 100644 --- a/framework/tests/flowexpression/Unparsable.java +++ b/framework/tests/flowexpression/Unparsable.java @@ -3,13 +3,13 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class Unparsable { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("lsdjf") Object o3 = null; - - void method() { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("new Object()") Object o1 = null; // :: error: (expression.unparsable.type.invalid) - @FlowExp("int x = 0") Object o2 = null; - } + @FlowExp("lsdjf") Object o3 = null; + + void method() { + // :: error: (expression.unparsable.type.invalid) + @FlowExp("new Object()") Object o1 = null; + // :: error: (expression.unparsable.type.invalid) + @FlowExp("int x = 0") Object o2 = null; + } } diff --git a/framework/tests/flowexpression/UnsupportJavaCode.java b/framework/tests/flowexpression/UnsupportJavaCode.java index 08ea18c0590..3376197f313 100644 --- a/framework/tests/flowexpression/UnsupportJavaCode.java +++ b/framework/tests/flowexpression/UnsupportJavaCode.java @@ -4,12 +4,12 @@ public class UnsupportJavaCode { - void method() { + void method() { - // :: error: (expression.unparsable.type.invalid) - @FlowExp("new Object()") String s0; + // :: error: (expression.unparsable.type.invalid) + @FlowExp("new Object()") String s0; - // :: error: (expression.unparsable.type.invalid) - @FlowExp("List list;") String s1; - } + // :: error: (expression.unparsable.type.invalid) + @FlowExp("List list;") String s1; + } } diff --git a/framework/tests/flowexpression/UsePrivate.java b/framework/tests/flowexpression/UsePrivate.java index aef6f624be1..aae3113e392 100644 --- a/framework/tests/flowexpression/UsePrivate.java +++ b/framework/tests/flowexpression/UsePrivate.java @@ -1,12 +1,14 @@ package flowexpression; -import java.util.Collection; import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; +import java.util.Collection; + public class UsePrivate { - void test(Private app_ppts, Private test_ppts) { + void test(Private app_ppts, Private test_ppts) { - Collection<@FlowExp("app_ppts.nameToPpt") String> app_ppt_names = app_ppts.nameStringSet(); - Collection<@FlowExp("test_ppts.nameToPpt") String> test_ppt_names = test_ppts.nameStringSet(); - } + Collection<@FlowExp("app_ppts.nameToPpt") String> app_ppt_names = app_ppts.nameStringSet(); + Collection<@FlowExp("test_ppts.nameToPpt") String> test_ppt_names = + test_ppts.nameStringSet(); + } } diff --git a/framework/tests/flowexpression/ValueLiterals.java b/framework/tests/flowexpression/ValueLiterals.java index 5acc3b0d17f..44608e4a01c 100644 --- a/framework/tests/flowexpression/ValueLiterals.java +++ b/framework/tests/flowexpression/ValueLiterals.java @@ -1,13 +1,13 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ValueLiterals { - void test(@FlowExp("0") Object a, @FlowExp("0L") Object b) {} + void test(@FlowExp("0") Object a, @FlowExp("0L") Object b) {} - void test2(@FlowExp("1000") Object a, @FlowExp("100L") Object b) {} + void test2(@FlowExp("1000") Object a, @FlowExp("100L") Object b) {} - void test3(@FlowExp("01000") Object a) {} + void test3(@FlowExp("01000") Object a) {} - void test4(@FlowExp("0100L") Object b) {} + void test4(@FlowExp("0100L") Object b) {} - void test5(@FlowExp("0100l") Object b) {} + void test5(@FlowExp("0100l") Object b) {} } diff --git a/framework/tests/flowexpression/ViewPointAdaptMethods.java b/framework/tests/flowexpression/ViewPointAdaptMethods.java index 0594dcd2f04..f32635cd004 100644 --- a/framework/tests/flowexpression/ViewPointAdaptMethods.java +++ b/framework/tests/flowexpression/ViewPointAdaptMethods.java @@ -3,37 +3,37 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ViewPointAdaptMethods { - Object param1; + Object param1; - void method1(Object param1, @FlowExp("#1") Object param2) { - @FlowExp("param1") Object local = param2; - @FlowExp("this.param1") - // :: error: (assignment.type.incompatible) - Object local2 = param2; - @FlowExp("#1") Object local3 = param2; - } + void method1(Object param1, @FlowExp("#1") Object param2) { + @FlowExp("param1") Object local = param2; + @FlowExp("this.param1") + // :: error: (assignment.type.incompatible) + Object local2 = param2; + @FlowExp("#1") Object local3 = param2; + } - Object field; + Object field; - void callMethod1(@FlowExp("this.field") Object param, @FlowExp("#1") Object param2) { - method1(field, param); - // :: error: (argument.type.incompatible) - method1(field, param2); - } + void callMethod1(@FlowExp("this.field") Object param, @FlowExp("#1") Object param2) { + method1(field, param); + // :: error: (argument.type.incompatible) + method1(field, param2); + } - @FlowExp("#2") Object method2(@FlowExp("#2") Object param1, Object param2, boolean flag) { - if (flag) { - return param1; - } else if (param1 == param2) { - @FlowExp("#2") - // :: error: (assignment.type.incompatible) - Object o = new Object(); - return o; - } else { - @FlowExp("param2") - // :: error: (assignment.type.incompatible) - Object o = new Object(); - return o; + @FlowExp("#2") Object method2(@FlowExp("#2") Object param1, Object param2, boolean flag) { + if (flag) { + return param1; + } else if (param1 == param2) { + @FlowExp("#2") + // :: error: (assignment.type.incompatible) + Object o = new Object(); + return o; + } else { + @FlowExp("param2") + // :: error: (assignment.type.incompatible) + Object o = new Object(); + return o; + } } - } } diff --git a/framework/tests/flowexpression/ViewpointAdaptation.java b/framework/tests/flowexpression/ViewpointAdaptation.java index f0fb40f3919..3557e616770 100644 --- a/framework/tests/flowexpression/ViewpointAdaptation.java +++ b/framework/tests/flowexpression/ViewpointAdaptation.java @@ -2,40 +2,40 @@ public class ViewpointAdaptation { - class MyClass { - protected final Object field = new Object(); + class MyClass { + protected final Object field = new Object(); - protected @FlowExp("field") Object annotatedField1; + protected @FlowExp("field") Object annotatedField1; - protected @FlowExp("this.field") Object annotatedField2; + protected @FlowExp("this.field") Object annotatedField2; - public @FlowExp("field") Object getAnnotatedField1() { - return annotatedField1; + public @FlowExp("field") Object getAnnotatedField1() { + return annotatedField1; + } } - } - class Use { - final MyClass myClass1 = new MyClass(); - final Object field = new Object(); + class Use { + final MyClass myClass1 = new MyClass(); + final Object field = new Object(); - @FlowExp("this.myClass1.field") Object o1 = myClass1.annotatedField1; + @FlowExp("this.myClass1.field") Object o1 = myClass1.annotatedField1; - @FlowExp("this.myClass1.field") Object o2 = myClass1.annotatedField2; + @FlowExp("this.myClass1.field") Object o2 = myClass1.annotatedField2; - @FlowExp("field") - // :: error: (assignment.type.incompatible) - Object o3 = myClass1.annotatedField1; + @FlowExp("field") + // :: error: (assignment.type.incompatible) + Object o3 = myClass1.annotatedField1; - @FlowExp("this.field") - // :: error: (assignment.type.incompatible) - Object o4 = myClass1.annotatedField2; + @FlowExp("this.field") + // :: error: (assignment.type.incompatible) + Object o4 = myClass1.annotatedField2; - @FlowExp("field") - // :: error: (assignment.type.incompatible) - Object oM2 = myClass1.getAnnotatedField1(); + @FlowExp("field") + // :: error: (assignment.type.incompatible) + Object oM2 = myClass1.getAnnotatedField1(); - @FlowExp("this.field") - // :: error: (assignment.type.incompatible) - Object oM3 = myClass1.getAnnotatedField1(); - } + @FlowExp("this.field") + // :: error: (assignment.type.incompatible) + Object oM3 = myClass1.getAnnotatedField1(); + } } diff --git a/framework/tests/flowexpression/ViewpointAdaptation2.java b/framework/tests/flowexpression/ViewpointAdaptation2.java index c9f69ef4cc9..15442e0bd4f 100644 --- a/framework/tests/flowexpression/ViewpointAdaptation2.java +++ b/framework/tests/flowexpression/ViewpointAdaptation2.java @@ -1,48 +1,48 @@ import org.checkerframework.framework.testchecker.flowexpression.qual.FlowExp; public class ViewpointAdaptation2 { - class LockExample { - protected final Object myLock = new Object(); + class LockExample { + protected final Object myLock = new Object(); - protected @FlowExp("myLock") Object locked; + protected @FlowExp("myLock") Object locked; - protected @FlowExp("this.myLock") Object locked2; + protected @FlowExp("this.myLock") Object locked2; - public @FlowExp("myLock") Object getLocked() { - return locked; + public @FlowExp("myLock") Object getLocked() { + return locked; + } } - } - class Use { - final LockExample lockExample1 = new LockExample(); - final Object myLock = new Object(); + class Use { + final LockExample lockExample1 = new LockExample(); + final Object myLock = new Object(); - @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; + @FlowExp("lockExample1.myLock") Object o1 = lockExample1.locked; - @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; + @FlowExp("lockExample1.myLock") Object o2 = lockExample1.locked2; - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object o3 = lockExample1.locked; + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object o3 = lockExample1.locked; - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object o4 = lockExample1.locked2; + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object o4 = lockExample1.locked2; - @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); + @FlowExp("lockExample1.myLock") Object oM1 = lockExample1.getLocked(); - @FlowExp("myLock") - // :: error: (assignment.type.incompatible) - Object oM2 = lockExample1.getLocked(); + @FlowExp("myLock") + // :: error: (assignment.type.incompatible) + Object oM2 = lockExample1.getLocked(); - @FlowExp("this.myLock") - // :: error: (assignment.type.incompatible) - Object oM3 = lockExample1.getLocked(); + @FlowExp("this.myLock") + // :: error: (assignment.type.incompatible) + Object oM3 = lockExample1.getLocked(); - void uses() { - lockExample1.locked = o1; - // :: error: (assignment.type.incompatible) - lockExample1.locked = o3; + void uses() { + lockExample1.locked = o1; + // :: error: (assignment.type.incompatible) + lockExample1.locked = o3; + } } - } } diff --git a/framework/tests/framework-javac-errors/Issue346.java b/framework/tests/framework-javac-errors/Issue346.java index aae98334658..1f6df583e1f 100644 --- a/framework/tests/framework-javac-errors/Issue346.java +++ b/framework/tests/framework-javac-errors/Issue346.java @@ -4,6 +4,6 @@ class Before {} class Context { - // :: error: cannot find symbol - Unknown f; + // :: error: cannot find symbol + Unknown f; } diff --git a/framework/tests/framework-javac-errors/MissingSymbolCrash.java b/framework/tests/framework-javac-errors/MissingSymbolCrash.java index 8ff7d0d2993..5a99cb8521d 100644 --- a/framework/tests/framework-javac-errors/MissingSymbolCrash.java +++ b/framework/tests/framework-javac-errors/MissingSymbolCrash.java @@ -1,6 +1,6 @@ public class MissingSymbolCrash { - public void test() { - // :: error: cannot find symbol - lst.add(s); - } + public void test() { + // :: error: cannot find symbol + lst.add(s); + } } diff --git a/framework/tests/framework-javac-errors/ResolveError.java b/framework/tests/framework-javac-errors/ResolveError.java index 8199dd0ef0b..2b55b481016 100644 --- a/framework/tests/framework-javac-errors/ResolveError.java +++ b/framework/tests/framework-javac-errors/ResolveError.java @@ -1,6 +1,6 @@ public class ResolveError { - void m() { - // :: error: cannot find symbol - Unresolved.foo(); - } + void m() { + // :: error: cannot find symbol + Unresolved.foo(); + } } diff --git a/framework/tests/framework-javac-errors/UnimportedExtends2.java b/framework/tests/framework-javac-errors/UnimportedExtends2.java index 2ff426e76a5..f30b4883261 100644 --- a/framework/tests/framework-javac-errors/UnimportedExtends2.java +++ b/framework/tests/framework-javac-errors/UnimportedExtends2.java @@ -1,4 +1,4 @@ public class UnimportedExtends2 { - // :: error: cannot find symbol - class Inner extends UnimportedClass {} + // :: error: cannot find symbol + class Inner extends UnimportedClass {} } diff --git a/framework/tests/framework/AnnotatedAnnotation.java b/framework/tests/framework/AnnotatedAnnotation.java index 210f3886256..ebbab9d4d48 100644 --- a/framework/tests/framework/AnnotatedAnnotation.java +++ b/framework/tests/framework/AnnotatedAnnotation.java @@ -1,68 +1,69 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.testchecker.util.*; @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface OddInt { - @Odd int value(); + @Odd int value(); } @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface OddIntArr { - @Odd int[] value(); + @Odd int[] value(); } @interface OddRec { - OddIntArr[] value(); + OddIntArr[] value(); } class Const { - @SuppressWarnings("evenodd") - public static final @Odd int ok1 = 5; + @SuppressWarnings("evenodd") + public static final @Odd int ok1 = 5; - @SuppressWarnings("evenodd") - public static final @Odd int ok2 = 5; + @SuppressWarnings("evenodd") + public static final @Odd int ok2 = 5; - public static final int notodd = 4; + public static final int notodd = 4; } class Uses { - @OddInt(Const.ok1) - Object good1; + @OddInt(Const.ok1) + Object good1; - // :: error: (annotation.type.incompatible) - @OddInt(4) - Object bad1; + // :: error: (annotation.type.incompatible) + @OddInt(4) + Object bad1; - // :: error: (annotation.type.incompatible) - @OddInt(Const.notodd) - Object bad2; + // :: error: (annotation.type.incompatible) + @OddInt(Const.notodd) + Object bad2; - @OddIntArr(Const.ok1) - Object good2; + @OddIntArr(Const.ok1) + Object good2; - @OddIntArr({Const.ok1, Const.ok2}) - Object good3; + @OddIntArr({Const.ok1, Const.ok2}) + Object good3; - // :: error: (annotation.type.incompatible) - @OddIntArr(4) - Object bada1; + // :: error: (annotation.type.incompatible) + @OddIntArr(4) + Object bada1; - // :: error: (annotation.type.incompatible) - @OddIntArr({Const.ok1, 4}) - Object bada2; + // :: error: (annotation.type.incompatible) + @OddIntArr({Const.ok1, 4}) + Object bada2; - @OddRec(@OddIntArr({Const.ok1, Const.ok2})) - void goodrec1() {} + @OddRec(@OddIntArr({Const.ok1, Const.ok2})) + void goodrec1() {} - @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({Const.ok1, Const.ok2})}) - void goodrec2() {} + @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({Const.ok1, Const.ok2})}) + void goodrec2() {} - // :: error: (annotation.type.incompatible) - @OddRec(@OddIntArr({Const.ok1, 4})) - void badrec1() {} + // :: error: (annotation.type.incompatible) + @OddRec(@OddIntArr({Const.ok1, 4})) + void badrec1() {} - // :: error: (annotation.type.incompatible) - @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({3, Const.ok2})}) - void badrec2() {} + // :: error: (annotation.type.incompatible) + @OddRec({@OddIntArr({Const.ok1, Const.ok2}), @OddIntArr({3, Const.ok2})}) + void badrec2() {} } diff --git a/framework/tests/framework/AnnotatedGenerics.java b/framework/tests/framework/AnnotatedGenerics.java index b696cac5a34..bfd0207a2f9 100644 --- a/framework/tests/framework/AnnotatedGenerics.java +++ b/framework/tests/framework/AnnotatedGenerics.java @@ -2,134 +2,134 @@ public class AnnotatedGenerics { - public static void testNullableTypeVariable() { - class Test { - @Odd T get() { - return null; - } + public static void testNullableTypeVariable() { + class Test { + @Odd T get() { + return null; + } + } + Test l = null; + String l1 = l.get(); + @Odd String l2 = l.get(); + + Test<@Odd String> n = null; + String n1 = n.get(); + @Odd String n2 = n.get(); } - Test l = null; - String l1 = l.get(); - @Odd String l2 = l.get(); - - Test<@Odd String> n = null; - String n1 = n.get(); - @Odd String n2 = n.get(); - } - - // Tests the type of the constructed class is correctly inferred for generics. - public void testConstructors() { - // Variant without annotated type parameters - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>(); - - // Variant with annotated type parameters - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass innerClass3 = new @Odd MyClass(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass normal3 = new @Odd NormalClass(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass innerClass4 = new MyClass(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass normal4 = new NormalClass(); - } - - // Tests the type of the constructed class is correctly inferred when the - // diamond operator is used. - public void testConstructorsWithTypeParameterInferrence() { - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass<@Odd String> innerClass2 = new MyClass<>(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass<@Odd String> normal2 = new NormalClass<>(); - - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass innerClass3 = new @Odd MyClass<>(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass normal3 = new @Odd NormalClass<>(); - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass innerClass4 = new MyClass<>(); - // :: error: (assignment.type.incompatible) - @Odd NormalClass normal4 = new NormalClass<>(); - } - - // Tests the type of the constructor is appropriately inferred for anonymous classes - // N.B. This does not / cannot assert that the RHS is infact a subtype of the LHS. - public void testAnonymousConstructors() { - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>() {}; - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>() {}; - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>() {}; - // :: error: (assignment.type.incompatible) - @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>() {}; - - // :: warning: (cast.unsafe.constructor.invocation) - @Odd MyClass innerClass3 = new @Odd MyClass() {}; - // :: warning: (cast.unsafe.constructor.invocation) - @Odd NormalClass normal3 = new @Odd NormalClass() {}; - - // Should error because the RHS isn't annotated as '@Odd' - // :: error: (assignment.type.incompatible) - @Odd MyClass innerClass4 = new MyClass() {}; - // :: error: (assignment.type.incompatible) - @Odd NormalClass normal4 = new NormalClass() {}; - } - - // The following test cases are not included because the Java compiler currently does - // not seem to support the diamond operator in conjunction with anonymous classes. - // - // public void testAnonymousConstructorsWithTypeParameterInferrence() { - // @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>() {}; - // @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>() {}; - // - // // Should error because the RHS isn't annotated as '@Odd' - // @Odd MyClass<@Odd String> innerClass2 = new MyClass<>() {}; - // @Odd NormalClass<@Odd String> normal2 = new NormalClass<>() {}; - // - // @Odd MyClass innerClass3 = new @Odd MyClass<>() {}; - // @Odd NormalClass normal3 = new @Odd NormalClass<>() {}; - // - // // Should error because the RHS isn't annotated as '@Odd' - // @Odd MyClass innerClass4 = new MyClass<>() {}; - // @Odd NormalClass normal4 = new NormalClass<>() {}; - // } - - static class NormalClass { - @Odd T get() { - return null; + + // Tests the type of the constructed class is correctly inferred for generics. + public void testConstructors() { + // Variant without annotated type parameters + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>(); + + // Variant with annotated type parameters + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass innerClass3 = new @Odd MyClass(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass normal3 = new @Odd NormalClass(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass innerClass4 = new MyClass(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass normal4 = new NormalClass(); + } + + // Tests the type of the constructed class is correctly inferred when the + // diamond operator is used. + public void testConstructorsWithTypeParameterInferrence() { + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass<@Odd String> innerClass2 = new MyClass<>(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass<@Odd String> normal2 = new NormalClass<>(); + + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass innerClass3 = new @Odd MyClass<>(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass normal3 = new @Odd NormalClass<>(); + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass innerClass4 = new MyClass<>(); + // :: error: (assignment.type.incompatible) + @Odd NormalClass normal4 = new NormalClass<>(); } - } - class MyClass implements java.util.Iterator<@Odd T> { - public boolean hasNext() { - return true; + // Tests the type of the constructor is appropriately inferred for anonymous classes + // N.B. This does not / cannot assert that the RHS is infact a subtype of the LHS. + public void testAnonymousConstructors() { + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<@Odd String>() {}; + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<@Odd String>() {}; + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass<@Odd String> innerClass2 = new MyClass<@Odd String>() {}; + // :: error: (assignment.type.incompatible) + @Odd NormalClass<@Odd String> normal2 = new NormalClass<@Odd String>() {}; + + // :: warning: (cast.unsafe.constructor.invocation) + @Odd MyClass innerClass3 = new @Odd MyClass() {}; + // :: warning: (cast.unsafe.constructor.invocation) + @Odd NormalClass normal3 = new @Odd NormalClass() {}; + + // Should error because the RHS isn't annotated as '@Odd' + // :: error: (assignment.type.incompatible) + @Odd MyClass innerClass4 = new MyClass() {}; + // :: error: (assignment.type.incompatible) + @Odd NormalClass normal4 = new NormalClass() {}; } - public @Odd T next() { - return null; + // The following test cases are not included because the Java compiler currently does + // not seem to support the diamond operator in conjunction with anonymous classes. + // + // public void testAnonymousConstructorsWithTypeParameterInferrence() { + // @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>() {}; + // @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>() {}; + // + // // Should error because the RHS isn't annotated as '@Odd' + // @Odd MyClass<@Odd String> innerClass2 = new MyClass<>() {}; + // @Odd NormalClass<@Odd String> normal2 = new NormalClass<>() {}; + // + // @Odd MyClass innerClass3 = new @Odd MyClass<>() {}; + // @Odd NormalClass normal3 = new @Odd NormalClass<>() {}; + // + // // Should error because the RHS isn't annotated as '@Odd' + // @Odd MyClass innerClass4 = new MyClass<>() {}; + // @Odd NormalClass normal4 = new NormalClass<>() {}; + // } + + static class NormalClass { + @Odd T get() { + return null; + } } - public void remove() {} - } + class MyClass implements java.util.Iterator<@Odd T> { + public boolean hasNext() { + return true; + } + + public @Odd T next() { + return null; + } + + public void remove() {} + } } diff --git a/framework/tests/framework/AnnotationWithComponents.java b/framework/tests/framework/AnnotationWithComponents.java index a5c89e6b3cb..eb6da592e90 100644 --- a/framework/tests/framework/AnnotationWithComponents.java +++ b/framework/tests/framework/AnnotationWithComponents.java @@ -3,14 +3,14 @@ * classes and fields. */ @interface Anno { - class Inner {} + class Inner {} - int con = 5; + int con = 5; - int value(); + int value(); } class Use { - @Anno(0) - Object o; + @Anno(0) + Object o; } diff --git a/framework/tests/framework/AnonymousClasses.java b/framework/tests/framework/AnonymousClasses.java index 7d5e5633b3a..58c1b4c160a 100644 --- a/framework/tests/framework/AnonymousClasses.java +++ b/framework/tests/framework/AnonymousClasses.java @@ -2,19 +2,19 @@ public class AnonymousClasses { - void test() { - new Object() { - // TODO: the right hand side is - // @Unqualified @Unqualified Object - // We should make sure that the qualifier is only present once. + void test() { + new Object() { + // TODO: the right hand side is + // @Unqualified @Unqualified Object + // We should make sure that the qualifier is only present once. - // :: error: (assignment.type.incompatible) - @Odd Object o = this; // error - }; + // :: error: (assignment.type.incompatible) + @Odd Object o = this; // error + }; - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd Object() { - @Odd Object o = this; - }; - } + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd Object() { + @Odd Object o = this; + }; + } } diff --git a/framework/tests/framework/ArraySubtyping.java b/framework/tests/framework/ArraySubtyping.java index b3c087e9516..da5b87e8c46 100644 --- a/framework/tests/framework/ArraySubtyping.java +++ b/framework/tests/framework/ArraySubtyping.java @@ -2,29 +2,29 @@ // @skip-test public class ArraySubtyping { - Object[] obj1 = new Object[1]; - @Odd Object[] obj2 = new @Odd Object[1]; + Object[] obj1 = new Object[1]; + @Odd Object[] obj2 = new @Odd Object[1]; - String[] str1 = new String[1]; - @Odd String[] str2 = new @Odd String[1]; + String[] str1 = new String[1]; + @Odd String[] str2 = new @Odd String[1]; - void m() { - // :: error: (assignment.type.incompatible) - obj1 = obj2; - // :: error: (assignment.type.incompatible) - obj2 = obj1; + void m() { + // :: error: (assignment.type.incompatible) + obj1 = obj2; + // :: error: (assignment.type.incompatible) + obj2 = obj1; - // :: error: (assignment.type.incompatible) - str1 = str2; - // :: error: (assignment.type.incompatible) - str2 = str1; + // :: error: (assignment.type.incompatible) + str1 = str2; + // :: error: (assignment.type.incompatible) + str2 = str1; - obj1 = str1; - obj2 = str2; + obj1 = str1; + obj2 = str2; - // :: error: (assignment.type.incompatible) - obj1 = str2; - // :: error: (assignment.type.incompatible) - obj2 = str1; - } + // :: error: (assignment.type.incompatible) + obj1 = str2; + // :: error: (assignment.type.incompatible) + obj2 = str1; + } } diff --git a/framework/tests/framework/Arrays.java b/framework/tests/framework/Arrays.java index 1bf5f8dee1c..a2509c016bb 100644 --- a/framework/tests/framework/Arrays.java +++ b/framework/tests/framework/Arrays.java @@ -1,126 +1,127 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; -import org.checkerframework.framework.testchecker.util.*; public class Arrays { - Object[] @Odd [] objB1 = new Object[] @Odd [] {}; - Object[][] @Odd [] objB1a = new Object[][] @Odd [] {}; - Object @Odd [][][] objB1b = new Object @Odd [][][] {}; - @Odd Object[][][] objB1c = new @Odd Object[][][] {}; + Object[] @Odd [] objB1 = new Object[] @Odd [] {}; + Object[][] @Odd [] objB1a = new Object[][] @Odd [] {}; + Object @Odd [][][] objB1b = new Object @Odd [][][] {}; + @Odd Object[][][] objB1c = new @Odd Object[][][] {}; - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface A {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface A {} - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface B {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface B {} - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface C {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface C {} - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - @interface D {} + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @interface D {} - class Cell {} + class Cell {} - // (This part is actually for the parser, not the framework; it should - // be moved to the JSR 308 compiler test suite eventually.) - void test() { + // (This part is actually for the parser, not the framework; it should + // be moved to the JSR 308 compiler test suite eventually.) + void test() { - Object z = new @A String[] {}; + Object z = new @A String[] {}; - // 308 only: - Cell<@D Object @C [] @B [] @A []> o1; + // 308 only: + Cell<@D Object @C [] @B [] @A []> o1; - // w/new: - Object o2a = new @D Object @C [] @B [] @A [] {}; - Object o2b = new @D Object @C [1] @B [2] @A [3]; + // w/new: + Object o2a = new @D Object @C [] @B [] @A [] {}; + Object o2b = new @D Object @C [1] @B [2] @A [3]; - // w/175: - @D Object @C [] @B [] @A [] o3; - } + // w/175: + @D Object @C [] @B [] @A [] o3; + } - void moreTest() { - // Assignments: + void moreTest() { + // Assignments: - String[] s = null; - String[] t = null; + String[] s = null; + String[] t = null; - s[0] = null; - t[0] = null; + s[0] = null; + t[0] = null; - (new String[1])[0] = null; - (new String[1])[0] = null; + (new String[1])[0] = null; + (new String[1])[0] = null; - (new String[] {"foo"})[0] = null; - (new String[] {"foo"})[0] = null; - } + (new String[] {"foo"})[0] = null; + (new String[] {"foo"})[0] = null; + } - void test2() { + void test2() { - Object[][] objA1 = new Object[][] {}; - Object[][] objA2 = new Object[1][2]; - Object[][] objA3 = new Object[1][]; + Object[][] objA1 = new Object[][] {}; + Object[][] objA2 = new Object[1][2]; + Object[][] objA3 = new Object[1][]; - Object[] @Odd [] objB1 = new Object[] @Odd [] {}; - Object[] @Odd [] objB2 = new Object[1] @Odd [2]; - Object[] @Odd [] objB3 = new Object[1] @Odd []; - - Object @Odd [][] objC1 = new Object @Odd [][] {}; - Object @Odd [][] objC2 = new Object @Odd [1][2]; - Object @Odd [][] objC3 = new Object @Odd [1][]; - - @Odd Object[][] objD1 = new @Odd Object[][] {}; - @Odd Object[][] objD2 = new @Odd Object[1][2]; - @Odd Object[][] objD3 = new @Odd Object[1][]; - - Object @Odd [] @Odd [] objE1 = new Object @Odd [] @Odd [] {}; - Object @Odd [] @Odd [] objE2 = new Object @Odd [1] @Odd [2]; - Object @Odd [] @Odd [] objE3 = new Object @Odd [1] @Odd []; - - @Odd Object[] @Odd [] objF1 = new @Odd Object[] @Odd [] {}; - @Odd Object[] @Odd [] objF2 = new @Odd Object[1] @Odd [2]; - @Odd Object[] @Odd [] objF3 = new @Odd Object[1] @Odd []; - - @Odd Object @Odd [][] objG1 = new @Odd Object @Odd [][] {}; - @Odd Object @Odd [][] objG2 = new @Odd Object @Odd [1][2]; - @Odd Object @Odd [][] objG3 = new @Odd Object @Odd [1][]; - - @Odd Object @Odd [] @Odd [] objH1 = new @Odd Object @Odd [] @Odd [] {}; - @Odd Object @Odd [] @Odd [] objH2 = new @Odd Object @Odd [1] @Odd [2]; - @Odd Object @Odd [] @Odd [] objH3 = new @Odd Object @Odd [1] @Odd []; - } - - void test3() { - @Odd Object o1 = new @Odd Object @Odd [] @Odd [] {}; - // :: error: (assignment.type.incompatible) - @Odd Object o2 = new @Odd Object[] @Odd [] {}; // ERROR - - @Odd Object @Odd [] o3 = (new @Odd Object[] @Odd [] {})[0]; - // :: error: (assignment.type.incompatible) - @Odd Object @Odd [] o4 = (new Object @Odd [][] {})[0]; // ERROR - // :: error: (assignment.type.incompatible) - @Odd Object @Odd [] o5 = (new @Odd Object[][] {})[0]; // ERROR - - Object @Odd [] o6 = (new Object[] @Odd [] {})[0]; - @Odd Object[] o7 = (new @Odd Object[][] {})[0]; - - @Odd Object o8 = (new @Odd Object[][] {})[0][0]; - } - - void test4() { - @Odd Object @Odd [] @Odd [] o1 = new @Odd Object @Odd [] @Odd [] {}; - @Odd Object @Odd [] @Odd [] @Odd [] o2 = new @Odd Object @Odd [1] @Odd [2] @Odd [3]; - @Odd Object @Odd [] @Odd [] o3 = new @Odd Object @Odd [1] @Odd [2] @Odd []; - @Odd Object @Odd [] @Odd [] o4 = new @Odd Object @Odd [1] @Odd [] @Odd []; - } - - void testInitializers() { - // @Odd String [] ara1 = { null, null }; - @Odd String[] ara2 = new @Odd String[] {null, null}; - - // // xx:: error: (assignment.type.incompatible) - // @Odd String [] arb1 = { null, "m" }; - // :: error: (array.initializer.type.incompatible) - @Odd String[] arb2 = new @Odd String[] {null, "m"}; - } + Object[] @Odd [] objB1 = new Object[] @Odd [] {}; + Object[] @Odd [] objB2 = new Object[1] @Odd [2]; + Object[] @Odd [] objB3 = new Object[1] @Odd []; + + Object @Odd [][] objC1 = new Object @Odd [][] {}; + Object @Odd [][] objC2 = new Object @Odd [1][2]; + Object @Odd [][] objC3 = new Object @Odd [1][]; + + @Odd Object[][] objD1 = new @Odd Object[][] {}; + @Odd Object[][] objD2 = new @Odd Object[1][2]; + @Odd Object[][] objD3 = new @Odd Object[1][]; + + Object @Odd [] @Odd [] objE1 = new Object @Odd [] @Odd [] {}; + Object @Odd [] @Odd [] objE2 = new Object @Odd [1] @Odd [2]; + Object @Odd [] @Odd [] objE3 = new Object @Odd [1] @Odd []; + + @Odd Object[] @Odd [] objF1 = new @Odd Object[] @Odd [] {}; + @Odd Object[] @Odd [] objF2 = new @Odd Object[1] @Odd [2]; + @Odd Object[] @Odd [] objF3 = new @Odd Object[1] @Odd []; + + @Odd Object @Odd [][] objG1 = new @Odd Object @Odd [][] {}; + @Odd Object @Odd [][] objG2 = new @Odd Object @Odd [1][2]; + @Odd Object @Odd [][] objG3 = new @Odd Object @Odd [1][]; + + @Odd Object @Odd [] @Odd [] objH1 = new @Odd Object @Odd [] @Odd [] {}; + @Odd Object @Odd [] @Odd [] objH2 = new @Odd Object @Odd [1] @Odd [2]; + @Odd Object @Odd [] @Odd [] objH3 = new @Odd Object @Odd [1] @Odd []; + } + + void test3() { + @Odd Object o1 = new @Odd Object @Odd [] @Odd [] {}; + // :: error: (assignment.type.incompatible) + @Odd Object o2 = new @Odd Object[] @Odd [] {}; // ERROR + + @Odd Object @Odd [] o3 = (new @Odd Object[] @Odd [] {})[0]; + // :: error: (assignment.type.incompatible) + @Odd Object @Odd [] o4 = (new Object @Odd [][] {})[0]; // ERROR + // :: error: (assignment.type.incompatible) + @Odd Object @Odd [] o5 = (new @Odd Object[][] {})[0]; // ERROR + + Object @Odd [] o6 = (new Object[] @Odd [] {})[0]; + @Odd Object[] o7 = (new @Odd Object[][] {})[0]; + + @Odd Object o8 = (new @Odd Object[][] {})[0][0]; + } + + void test4() { + @Odd Object @Odd [] @Odd [] o1 = new @Odd Object @Odd [] @Odd [] {}; + @Odd Object @Odd [] @Odd [] @Odd [] o2 = new @Odd Object @Odd [1] @Odd [2] @Odd [3]; + @Odd Object @Odd [] @Odd [] o3 = new @Odd Object @Odd [1] @Odd [2] @Odd []; + @Odd Object @Odd [] @Odd [] o4 = new @Odd Object @Odd [1] @Odd [] @Odd []; + } + + void testInitializers() { + // @Odd String [] ara1 = { null, null }; + @Odd String[] ara2 = new @Odd String[] {null, null}; + + // // xx:: error: (assignment.type.incompatible) + // @Odd String [] arb1 = { null, "m" }; + // :: error: (array.initializer.type.incompatible) + @Odd String[] arb2 = new @Odd String[] {null, "m"}; + } } diff --git a/framework/tests/framework/Assignments.java b/framework/tests/framework/Assignments.java index bd55e33d131..a09de05f91f 100644 --- a/framework/tests/framework/Assignments.java +++ b/framework/tests/framework/Assignments.java @@ -2,65 +2,65 @@ public class Assignments { - public void testAssignment() { - @Odd String s; - @Odd String t; - s = null; - t = s; + public void testAssignment() { + @Odd String s; + @Odd String t; + s = null; + t = s; - String z = ""; - z = s; - } + String z = ""; + z = s; + } - public void testCompound() { - // :: warning: (cast.unsafe) - @Odd String s = (@Odd String) "foo"; - // :: warning: (cast.unsafe) - @Odd String t = (@Odd String) "bar"; - s += t; + public void testCompound() { + // :: warning: (cast.unsafe) + @Odd String s = (@Odd String) "foo"; + // :: warning: (cast.unsafe) + @Odd String t = (@Odd String) "bar"; + s += t; - String z = ""; - z += s; - } + String z = ""; + z += s; + } - public void testEnhancedForLoop() { - // TODO - } + public void testEnhancedForLoop() { + // TODO + } - public void testMethod() { - // Nothing to do here. - } + public void testMethod() { + // Nothing to do here. + } - public void testMethodInvocation() { - // TODO anonymous constructor - // TODO isEnumSuperCall - // @see Varargs + public void testMethodInvocation() { + // TODO anonymous constructor + // TODO isEnumSuperCall + // @see Varargs - @Odd String s = null; - methodA(s); - methodB(s); - } + @Odd String s = null; + methodA(s); + methodB(s); + } - public @Odd String testReturn() { - @Odd String s = null; - return s; - } + public @Odd String testReturn() { + @Odd String s = null; + return s; + } - public void testReturnVoid() { - return; - } + public void testReturnVoid() { + return; + } - public void testVariable() { - @Odd String s = null; - // :: warning: (cast.unsafe) - @Odd String t = (@Odd String) "foo"; - @Odd String u = s; - String v = s; - } + public void testVariable() { + @Odd String s = null; + // :: warning: (cast.unsafe) + @Odd String t = (@Odd String) "foo"; + @Odd String u = s; + String v = s; + } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ - public void methodA(@Odd String s) {} + public void methodA(@Odd String s) {} - public void methodB(String s) {} + public void methodB(String s) {} } diff --git a/framework/tests/framework/AssignmentsGeneric.java b/framework/tests/framework/AssignmentsGeneric.java index df4f7b65b22..c1df6727be4 100644 --- a/framework/tests/framework/AssignmentsGeneric.java +++ b/framework/tests/framework/AssignmentsGeneric.java @@ -1,54 +1,57 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.framework.testchecker.util.*; public class AssignmentsGeneric { - private static final Map< - @Odd List<@Odd String>, - @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>> - complex; - - static { - complex = - new HashMap< - @Odd List<@Odd String>, - @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>>(); - } + private static final Map< + @Odd List<@Odd String>, + @Odd Map<@Odd Set<@Odd List<@Odd String>>, @Odd List<@Odd Set<@Odd String>>>> + complex; + + static { + complex = + new HashMap< + @Odd List<@Odd String>, + @Odd Map< + @Odd Set<@Odd List<@Odd String>>, + @Odd List<@Odd Set<@Odd String>>>>(); + } - public void testAssignment() { - // :: warning: (cast.unsafe) - @Odd String s = (@Odd String) ""; + public void testAssignment() { + // :: warning: (cast.unsafe) + @Odd String s = (@Odd String) ""; - List<@Odd String> lst = new LinkedList<>(); - lst = new ArrayList<@Odd String>(); + List<@Odd String> lst = new LinkedList<>(); + lst = new ArrayList<@Odd String>(); - methodA(lst); - } + methodA(lst); + } - public void testEnhancedForLoop() { - List<@Odd String> lst = new LinkedList<>(); - for (@Odd String str : lst) { - System.out.println(str); + public void testEnhancedForLoop() { + List<@Odd String> lst = new LinkedList<>(); + for (@Odd String str : lst) { + System.out.println(str); + } } - } - public void testGenericInvocation() { - List<@Odd String> lst = new LinkedList<>(); - // :: warning: (cast.unsafe) - @Odd String s = (@Odd String) ""; - lst.add(s); - } + public void testGenericInvocation() { + List<@Odd String> lst = new LinkedList<>(); + // :: warning: (cast.unsafe) + @Odd String s = (@Odd String) ""; + lst.add(s); + } - public List<@Odd String> testReturn() { - return new LinkedList<@Odd String>(); - } + public List<@Odd String> testReturn() { + return new LinkedList<@Odd String>(); + } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ - public void methodA(List<@Odd String> lst) {} + public void methodA(List<@Odd String> lst) {} } diff --git a/framework/tests/framework/BridgeMethods.java b/framework/tests/framework/BridgeMethods.java index 5fb0c0e913f..9a53e6acd91 100644 --- a/framework/tests/framework/BridgeMethods.java +++ b/framework/tests/framework/BridgeMethods.java @@ -2,27 +2,27 @@ import org.checkerframework.framework.testchecker.util.Odd; abstract class C { - abstract T id(T x); + abstract T id(T x); } class D extends C<@Odd String> { - @Odd String id(@Odd String x) { - return x; - } + @Odd String id(@Odd String x) { + return x; + } } class Usage { - void use() { - C c = new D(); // C<@Odd String>(); - // Oddness is OK, will fail with ClassCastException - // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C - // :: warning: (cast.unsafe.constructor.invocation) - c.id(new @Odd Object()); + void use() { + C c = new D(); // C<@Odd String>(); + // Oddness is OK, will fail with ClassCastException + // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C + // :: warning: (cast.unsafe.constructor.invocation) + c.id(new @Odd Object()); - // Oddness is wrong! Would also fail with ClassCastException. - // :: error: (argument.type.incompatible) - // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C - // :: warning: (cast.unsafe.constructor.invocation) - c.id(new @Even Object()); - } + // Oddness is wrong! Would also fail with ClassCastException. + // :: error: (argument.type.incompatible) + // :: warning: [unchecked] unchecked call to id(T) as a member of the raw type C + // :: warning: (cast.unsafe.constructor.invocation) + c.id(new @Even Object()); + } } diff --git a/framework/tests/framework/ClassAnnotations.java b/framework/tests/framework/ClassAnnotations.java index b427c8fef9c..c0aa93a0ea9 100644 --- a/framework/tests/framework/ClassAnnotations.java +++ b/framework/tests/framework/ClassAnnotations.java @@ -3,9 +3,9 @@ // ::warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) public @Odd class ClassAnnotations { - ClassAnnotations c; + ClassAnnotations c; - public void test() { - @Odd ClassAnnotations d = c; - } + public void test() { + @Odd ClassAnnotations d = c; + } } diff --git a/framework/tests/framework/Compound.java b/framework/tests/framework/Compound.java index 7559f9f5b71..f83c544a596 100644 --- a/framework/tests/framework/Compound.java +++ b/framework/tests/framework/Compound.java @@ -6,13 +6,13 @@ * method type argument inference. */ public class Compound { - public static void one() { - long total = 0; - total = two(); - total += two(); - } + public static void one() { + long total = 0; + total = two(); + total += two(); + } - private static long two() { - return 0; - } + private static long two() { + return 0; + } } diff --git a/framework/tests/framework/Constructors.java b/framework/tests/framework/Constructors.java index 68eac5cd454..0f655c94866 100644 --- a/framework/tests/framework/Constructors.java +++ b/framework/tests/framework/Constructors.java @@ -1,50 +1,50 @@ import org.checkerframework.framework.testchecker.util.Odd; public class Constructors { - public Constructors(Constructors con) {} + public Constructors(Constructors con) {} - public void testConstructors() { - Constructors c = null; - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd Constructors(c); - } + public void testConstructors() { + Constructors c = null; + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd Constructors(c); + } + + // Test anonymous constructors + public Constructors(@Odd String s, int i) {} + + public void testStaticAnonymousConstructor() { + String notOdd = "m"; + + // :: error: (argument.type.incompatible) + new Constructors(notOdd, 0); // error + // :: error: (argument.type.incompatible) + new Constructors(notOdd, 0) {}; // error + } + + private class MyConstructors extends Constructors { + public MyConstructors(@Odd String s) { + super(s, 0); + } + } - // Test anonymous constructors - public Constructors(@Odd String s, int i) {} + public static void testAnonymousConstructor() { + Constructors m = new Constructors(null) {}; + String notOdd = "m"; + // :: error: (argument.type.incompatible) + m.new MyConstructors(notOdd); // error + // :: error: (argument.type.incompatible) + m.new MyConstructors(notOdd) {}; // error + } - public void testStaticAnonymousConstructor() { - String notOdd = "m"; + // Tests that should pass + public void testPassingTests() { + @Odd String odd = null; - // :: error: (argument.type.incompatible) - new Constructors(notOdd, 0); // error - // :: error: (argument.type.incompatible) - new Constructors(notOdd, 0) {}; // error - } + new Constructors(odd, 0); + new Constructors(odd, 0) {}; - private class MyConstructors extends Constructors { - public MyConstructors(@Odd String s) { - super(s, 0); + Constructors m = new Constructors(null) {}; + m.new MyConstructors(odd); + m.new MyConstructors(odd) {}; } - } - - public static void testAnonymousConstructor() { - Constructors m = new Constructors(null) {}; - String notOdd = "m"; - // :: error: (argument.type.incompatible) - m.new MyConstructors(notOdd); // error - // :: error: (argument.type.incompatible) - m.new MyConstructors(notOdd) {}; // error - } - - // Tests that should pass - public void testPassingTests() { - @Odd String odd = null; - - new Constructors(odd, 0); - new Constructors(odd, 0) {}; - - Constructors m = new Constructors(null) {}; - m.new MyConstructors(odd); - m.new MyConstructors(odd) {}; - } } diff --git a/framework/tests/framework/DeepOverride.java b/framework/tests/framework/DeepOverride.java index c08aaf2cf7b..0a5d0c992f0 100644 --- a/framework/tests/framework/DeepOverride.java +++ b/framework/tests/framework/DeepOverride.java @@ -1,19 +1,19 @@ import org.checkerframework.framework.testchecker.util.*; public class DeepOverride { - public static class A { - public @Odd String method() { - return null; + public static class A { + public @Odd String method() { + return null; + } } - } - public static class B extends A {} + public static class B extends A {} - public static class C extends B { - @Override - // :: error: (override.return.invalid) - public String method() { - return ""; + public static class C extends B { + @Override + // :: error: (override.return.invalid) + public String method() { + return ""; + } } - } } diff --git a/framework/tests/framework/DeepOverrideAbstract.java b/framework/tests/framework/DeepOverrideAbstract.java index e2dc12b50c9..d23cb047d48 100644 --- a/framework/tests/framework/DeepOverrideAbstract.java +++ b/framework/tests/framework/DeepOverrideAbstract.java @@ -2,24 +2,24 @@ public class DeepOverrideAbstract { - public static interface I { - @Odd String interfaceMethod(); - } + public static interface I { + @Odd String interfaceMethod(); + } - public abstract static class A { - public abstract @Odd String abstractMethod(); - } + public abstract static class A { + public abstract @Odd String abstractMethod(); + } - public abstract static class B extends A implements I {} + public abstract static class B extends A implements I {} - public static class C extends B { - public @Odd String interfaceMethod() { - return null; - } + public static class C extends B { + public @Odd String interfaceMethod() { + return null; + } - // :: error: (override.return.invalid) - public String abstractMethod() { - return ""; + // :: error: (override.return.invalid) + public String abstractMethod() { + return ""; + } } - } } diff --git a/framework/tests/framework/DeepOverrideBug.java b/framework/tests/framework/DeepOverrideBug.java index c0af5a64f5b..6610f760558 100644 --- a/framework/tests/framework/DeepOverrideBug.java +++ b/framework/tests/framework/DeepOverrideBug.java @@ -3,29 +3,29 @@ // TODO: the output have a "missing return statement"? public class DeepOverrideBug { - public static interface I { - @Odd String interfaceMethod(); + public static interface I { + @Odd String interfaceMethod(); - String abstractMethod(); - } + String abstractMethod(); + } - public abstract static class A { - public abstract @Odd String abstractMethod(); + public abstract static class A { + public abstract @Odd String abstractMethod(); - public abstract String interfaceMethod(); - } + public abstract String interfaceMethod(); + } - public abstract static class B extends A implements I {} + public abstract static class B extends A implements I {} - public static class C extends B { - // :: error: (override.return.invalid) - public String interfaceMethod() { // should emit error - return null; - } + public static class C extends B { + // :: error: (override.return.invalid) + public String interfaceMethod() { // should emit error + return null; + } - // :: error: (override.return.invalid) - public String abstractMethod() { // should emit error - return null; + // :: error: (override.return.invalid) + public String abstractMethod() { // should emit error + return null; + } } - } } diff --git a/framework/tests/framework/DeepOverrideInterface.java b/framework/tests/framework/DeepOverrideInterface.java index ff498df044c..aec5d9f64fc 100644 --- a/framework/tests/framework/DeepOverrideInterface.java +++ b/framework/tests/framework/DeepOverrideInterface.java @@ -2,24 +2,24 @@ public class DeepOverrideInterface { - public static interface I { - @Odd String interfaceMethod(); - } + public static interface I { + @Odd String interfaceMethod(); + } - public abstract static class A { - public abstract @Odd String abstractMethod(); - } + public abstract static class A { + public abstract @Odd String abstractMethod(); + } - public abstract static class B extends A implements I {} + public abstract static class B extends A implements I {} - public static class C extends B { - // :: error: (override.return.invalid) - public String interfaceMethod() { - return ""; - } + public static class C extends B { + // :: error: (override.return.invalid) + public String interfaceMethod() { + return ""; + } - public @Odd String abstractMethod() { - return null; + public @Odd String abstractMethod() { + return null; + } } - } } diff --git a/framework/tests/framework/ExtendsDefault.java b/framework/tests/framework/ExtendsDefault.java index ff98573036f..6f532f345c2 100644 --- a/framework/tests/framework/ExtendsDefault.java +++ b/framework/tests/framework/ExtendsDefault.java @@ -4,21 +4,21 @@ public class ExtendsDefault { - @DefaultQualifier( - value = Odd.class, - locations = {TypeUseLocation.UPPER_BOUND}) - class MyOddDefault {} + @DefaultQualifier( + value = Odd.class, + locations = {TypeUseLocation.UPPER_BOUND}) + class MyOddDefault {} - class MyNonOddDefault {} + class MyNonOddDefault {} - void testNonOdd() { - // :: error: (type.argument.type.incompatible) - MyOddDefault s1; - MyNonOddDefault s2; - } + void testNonOdd() { + // :: error: (type.argument.type.incompatible) + MyOddDefault s1; + MyNonOddDefault s2; + } - void testOdd() { - MyOddDefault<@Odd String> s1; - MyNonOddDefault<@Odd String> s2; - } + void testOdd() { + MyOddDefault<@Odd String> s1; + MyNonOddDefault<@Odd String> s2; + } } diff --git a/framework/tests/framework/GenericAlias.java b/framework/tests/framework/GenericAlias.java index 3c9538c1a60..c8423910835 100644 --- a/framework/tests/framework/GenericAlias.java +++ b/framework/tests/framework/GenericAlias.java @@ -1,24 +1,26 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.framework.testchecker.util.*; public class GenericAlias { - public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} + public static class SuperSetOne + extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} - public void test() { - Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); - @Odd Map<@Odd List<@Odd String>, @Odd String> mapA = - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd HashMap<@Odd List<@Odd String>, @Odd String>(); - s.add(mapA); - } + public void test() { + Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); + @Odd Map<@Odd List<@Odd String>, @Odd String> mapA = + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd HashMap<@Odd List<@Odd String>, @Odd String>(); + s.add(mapA); + } - public void regularGenerics() { - Set set = new HashSet<@Odd String>(); - Set set2 = new HashSet<@Odd String>(); - } + public void regularGenerics() { + Set set = new HashSet<@Odd String>(); + Set set2 = new HashSet<@Odd String>(); + } } diff --git a/framework/tests/framework/GenericAliasInvalid.java b/framework/tests/framework/GenericAliasInvalid.java index 2d35d47179b..983f832f2aa 100644 --- a/framework/tests/framework/GenericAliasInvalid.java +++ b/framework/tests/framework/GenericAliasInvalid.java @@ -1,15 +1,17 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.framework.testchecker.util.*; public class GenericAliasInvalid { - public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} + public static class SuperSetOne + extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} - public void test() { - // :: error: (assignment.type.incompatible) - Set, @Odd String>> t = new SuperSetOne(); - } + public void test() { + // :: error: (assignment.type.incompatible) + Set, @Odd String>> t = new SuperSetOne(); + } } diff --git a/framework/tests/framework/GenericAliasInvalidCall.java b/framework/tests/framework/GenericAliasInvalidCall.java index a3f9438eab3..3f3d229eb6a 100644 --- a/framework/tests/framework/GenericAliasInvalidCall.java +++ b/framework/tests/framework/GenericAliasInvalidCall.java @@ -1,20 +1,22 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.framework.testchecker.util.*; public class GenericAliasInvalidCall { - public static class SuperSetOne extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} + public static class SuperSetOne + extends HashSet<@Odd Map<@Odd List<@Odd String>, @Odd String>> {} - public void test() { - Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); - @Odd Map, @Odd String> mapA = - // :: warning: (cast.unsafe.constructor.invocation) - new @Odd HashMap, @Odd String>(); - // :: error: (argument.type.incompatible) - s.add(mapA); - } + public void test() { + Set<@Odd Map<@Odd List<@Odd String>, @Odd String>> s = new SuperSetOne(); + @Odd Map, @Odd String> mapA = + // :: warning: (cast.unsafe.constructor.invocation) + new @Odd HashMap, @Odd String>(); + // :: error: (argument.type.incompatible) + s.add(mapA); + } } diff --git a/framework/tests/framework/GenericEnum.java b/framework/tests/framework/GenericEnum.java index fd06e834a40..1512bde4ee1 100644 --- a/framework/tests/framework/GenericEnum.java +++ b/framework/tests/framework/GenericEnum.java @@ -2,7 +2,7 @@ public class GenericEnum { - void test() { - T.format(""); - } + void test() { + T.format(""); + } } diff --git a/framework/tests/framework/GenericTest1.java b/framework/tests/framework/GenericTest1.java index 91ac1a53545..134c3477e05 100644 --- a/framework/tests/framework/GenericTest1.java +++ b/framework/tests/framework/GenericTest1.java @@ -3,17 +3,17 @@ // Test case for Issue 131: // https://github.com/typetools/checker-framework/issues/131 public class GenericTest1 { - public interface Foo {} + public interface Foo {} - public interface Bar> extends Foo {} + public interface Bar> extends Foo {} - public void test(Foo foo) { - Bar bar = - foo instanceof Bar - // TODO flow: support instanceof / cast flow. - // Warning only with -AcheckCastElementType. - // TODO:: warning: (cast.unsafe) - ? (Bar) foo - : null; - } + public void test(Foo foo) { + Bar bar = + foo instanceof Bar + // TODO flow: support instanceof / cast flow. + // Warning only with -AcheckCastElementType. + // TODO:: warning: (cast.unsafe) + ? (Bar) foo + : null; + } } diff --git a/framework/tests/framework/GenericTest10.java b/framework/tests/framework/GenericTest10.java index c602ff2aeb2..60e8fded648 100644 --- a/framework/tests/framework/GenericTest10.java +++ b/framework/tests/framework/GenericTest10.java @@ -1,25 +1,25 @@ // Test case for (a part of) Issue 142: // https://github.com/typetools/checker-framework/issues/142 public class GenericTest10 { - abstract static class Bijection { - abstract B apply(A a); + abstract static class Bijection { + abstract B apply(A a); - abstract A invert(B b); + abstract A invert(B b); - Bijection inverse() { - return new Bijection() { - A apply(B b) { - return Bijection.this.invert(b); - } + Bijection inverse() { + return new Bijection() { + A apply(B b) { + return Bijection.this.invert(b); + } - B invert(A a) { - return Bijection.this.apply(a); - } + B invert(A a) { + return Bijection.this.apply(a); + } - Bijection inverse() { - return Bijection.this; + Bijection inverse() { + return Bijection.this; + } + }; } - }; } - } } diff --git a/framework/tests/framework/GenericTest11.java b/framework/tests/framework/GenericTest11.java index ac4341e9d85..a741d8bae4d 100644 --- a/framework/tests/framework/GenericTest11.java +++ b/framework/tests/framework/GenericTest11.java @@ -1,21 +1,21 @@ package com.example; public class GenericTest11 { - public void m(BeanManager beanManager) { - Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); - CreationalContext context = beanManager.createCreationalContext(bean); - } + public void m(BeanManager beanManager) { + Bean bean = beanManager.getBeans(GenericTest11.class).iterator().next(); + CreationalContext context = beanManager.createCreationalContext(bean); + } - static interface BeanManager { - java.util.Set> getBeans( - java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); + static interface BeanManager { + java.util.Set> getBeans( + java.lang.reflect.Type arg0, java.lang.annotation.Annotation... arg1); - CreationalContext createCreationalContext(Contextual arg0); - } + CreationalContext createCreationalContext(Contextual arg0); + } - static interface Contextual {} + static interface Contextual {} - static interface Bean extends Contextual {} + static interface Bean extends Contextual {} - static interface CreationalContext {} + static interface CreationalContext {} } diff --git a/framework/tests/framework/GenericTest12.java b/framework/tests/framework/GenericTest12.java index 1822abc743f..84592046c71 100644 --- a/framework/tests/framework/GenericTest12.java +++ b/framework/tests/framework/GenericTest12.java @@ -1,10 +1,10 @@ import java.lang.annotation.Annotation; public class GenericTest12 { - @interface Anno {} + @interface Anno {} - void foo(Class qual) { - Annotation a = qual.getAnnotation(Anno.class); - Anno an = qual.getAnnotation(Anno.class); - } + void foo(Class qual) { + Annotation a = qual.getAnnotation(Anno.class); + Anno an = qual.getAnnotation(Anno.class); + } } diff --git a/framework/tests/framework/GenericTest2.java b/framework/tests/framework/GenericTest2.java index 41cf2ff1136..a61402d0a45 100644 --- a/framework/tests/framework/GenericTest2.java +++ b/framework/tests/framework/GenericTest2.java @@ -2,13 +2,13 @@ // https://github.com/typetools/checker-framework/issues/132 // Method type argument inference test case. public class GenericTest2 { - public interface Data {} + public interface Data {} - public interface DataUtils { - Data makeData(T value); - } + public interface DataUtils { + Data makeData(T value); + } - public void test(U value, DataUtils utils) { - Data data = utils.makeData(value); - } + public void test(U value, DataUtils utils) { + Data data = utils.makeData(value); + } } diff --git a/framework/tests/framework/GenericTest3.java b/framework/tests/framework/GenericTest3.java index 94f0aefa863..94d30740ec3 100644 --- a/framework/tests/framework/GenericTest3.java +++ b/framework/tests/framework/GenericTest3.java @@ -2,13 +2,13 @@ // https://github.com/typetools/checker-framework/issues/133 // Upper bound of wildcard depends on declared bound of type variable. public class GenericTest3 { - interface Foo {} + interface Foo {} - interface Bar { - T get(); - } + interface Bar { + T get(); + } - public void test(Bar bar) { - Foo foo = bar.get(); - } + public void test(Bar bar) { + Foo foo = bar.get(); + } } diff --git a/framework/tests/framework/GenericTest4.java b/framework/tests/framework/GenericTest4.java index 6aec19a45b0..6b4cc9ec9d9 100644 --- a/framework/tests/framework/GenericTest4.java +++ b/framework/tests/framework/GenericTest4.java @@ -1,59 +1,60 @@ -import java.util.Map; import org.checkerframework.framework.testchecker.util.*; +import java.util.Map; + // Test case for Issue 134: // https://github.com/typetools/checker-framework/issues/134 // Handling of generics from different enclosing classes. public class GenericTest4 { - public interface Foo {} - - class Outer { - O getOuter() { - return null; - } - - class Inner { - O getInner() { - return null; - } - - I setter1(O p) { - return null; - } - - O setter2(I p) { - return null; - } - - Map wow(Map p) { - return null; - } - } - } - - class OuterImpl extends Outer { - void test() { - Foo foo = getOuter(); + public interface Foo {} + + class Outer { + O getOuter() { + return null; + } + + class Inner { + O getInner() { + return null; + } + + I setter1(O p) { + return null; + } + + O setter2(I p) { + return null; + } + + Map wow(Map p) { + return null; + } + } } - class InnerImpl extends Inner<@Odd String> { - void test() { - Foo foo = getInner(); - String s = setter1(foo); - foo = setter2(s); - } - - void testWow(Map p) { - p = wow(p); - } - - void testWow2(Map p) { - // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) - p = wow(p); - } + class OuterImpl extends Outer { + void test() { + Foo foo = getOuter(); + } + + class InnerImpl extends Inner<@Odd String> { + void test() { + Foo foo = getInner(); + String s = setter1(foo); + foo = setter2(s); + } + + void testWow(Map p) { + p = wow(p); + } + + void testWow2(Map p) { + // :: error: (assignment.type.incompatible) :: error: (argument.type.incompatible) + p = wow(p); + } + } } - } - // Add uses from outside of both classes. + // Add uses from outside of both classes. } diff --git a/framework/tests/framework/GenericTest5.java b/framework/tests/framework/GenericTest5.java index 4acd5229971..369607d848b 100644 --- a/framework/tests/framework/GenericTest5.java +++ b/framework/tests/framework/GenericTest5.java @@ -4,23 +4,23 @@ // https://github.com/typetools/checker-framework/issues/135 // Method type argument substitution needs to consider arrays correctly. public class GenericTest5 { - interface Foo { - T[] id(T[] a); - } + interface Foo { + T[] id(T[] a); + } - public void test(Foo foo, U[] a) { - U[] array = foo.id(a); - } + public void test(Foo foo, U[] a) { + U[] array = foo.id(a); + } - public void test1(Foo foo, T[] a) { - T[] array = foo.id(a); - } + public void test1(Foo foo, T[] a) { + T[] array = foo.id(a); + } - public void test2(Foo foo, S[] a) { - S[] array = foo.id(a); - } + public void test2(Foo foo, S[] a) { + S[] array = foo.id(a); + } - public void test3(Foo foo, T[] a) { - T[] array = foo.id(a); - } + public void test3(Foo foo, T[] a) { + T[] array = foo.id(a); + } } diff --git a/framework/tests/framework/GenericTest6.java b/framework/tests/framework/GenericTest6.java index 07f18df4fd8..f1ade30c343 100644 --- a/framework/tests/framework/GenericTest6.java +++ b/framework/tests/framework/GenericTest6.java @@ -3,32 +3,32 @@ // Test case for Issue 136: // https://github.com/typetools/checker-framework/issues/136 public class GenericTest6 { - interface Foo> {} + interface Foo> {} - class Strange implements Foo {} + class Strange implements Foo {} - void test(Foo p) {} + void test(Foo p) {} - void call(Foo p) { - test(p); - } + void call(Foo p) { + test(p); + } - void test2(Foo> p) {} + void test2(Foo> p) {} - void call2(Foo> p) { - test2(p); - } + void call2(Foo> p) { + test2(p); + } - void test3(Foo>> p) {} + void test3(Foo>> p) {} - void call3(Foo>> p) { - // :: error: (argument.type.incompatible) - test3(p); - } + void call3(Foo>> p) { + // :: error: (argument.type.incompatible) + test3(p); + } - void testRaw(Foo p) {} + void testRaw(Foo p) {} - void callRaw(Foo> p) { - testRaw(p); - } + void callRaw(Foo> p) { + testRaw(p); + } } diff --git a/framework/tests/framework/GenericTest7.java b/framework/tests/framework/GenericTest7.java index e53e0c0345f..7329cca7203 100644 --- a/framework/tests/framework/GenericTest7.java +++ b/framework/tests/framework/GenericTest7.java @@ -5,50 +5,50 @@ * https://github.com/typetools/checker-framework/issues/137 */ public class GenericTest7 { - interface A {} - - interface B {} - - interface C {} - - public & C> void one(I i) { - B i1 = i; - C i2 = i; - } - - public & C> void oneA(I i) { - // :: error: (assignment.type.incompatible) - @Odd B i1 = i; - // :: error: (assignment.type.incompatible) - @Odd C i2 = i; - } - - public & @Odd C> void oneB(I i) { - @Odd B i1 = i; - @Odd C i2 = i; - } - - public & C> void two(I i) { - B i1 = i; - C i2 = i; - } - - public & C> void twoA(I i) { - // :: error: (assignment.type.incompatible) - @Odd B i1 = i; - // :: error: (assignment.type.incompatible) - @Odd C i2 = i; - } - - public & @Odd C> void twoB(I i) { - @Odd B i1 = i; - @Odd C i2 = i; - } - - public & C> void twoC(I i) { - B i1 = i; - C i2 = i; - B i3 = i; - C i4 = i; - } + interface A {} + + interface B {} + + interface C {} + + public & C> void one(I i) { + B i1 = i; + C i2 = i; + } + + public & C> void oneA(I i) { + // :: error: (assignment.type.incompatible) + @Odd B i1 = i; + // :: error: (assignment.type.incompatible) + @Odd C i2 = i; + } + + public & @Odd C> void oneB(I i) { + @Odd B i1 = i; + @Odd C i2 = i; + } + + public & C> void two(I i) { + B i1 = i; + C i2 = i; + } + + public & C> void twoA(I i) { + // :: error: (assignment.type.incompatible) + @Odd B i1 = i; + // :: error: (assignment.type.incompatible) + @Odd C i2 = i; + } + + public & @Odd C> void twoB(I i) { + @Odd B i1 = i; + @Odd C i2 = i; + } + + public & C> void twoC(I i) { + B i1 = i; + C i2 = i; + B i3 = i; + C i4 = i; + } } diff --git a/framework/tests/framework/GenericTest8.java b/framework/tests/framework/GenericTest8.java index 059fcf99a6a..fe9f14379aa 100644 --- a/framework/tests/framework/GenericTest8.java +++ b/framework/tests/framework/GenericTest8.java @@ -1,17 +1,17 @@ // Test case for Issue 139: // https://github.com/typetools/checker-framework/issues/139 abstract class GenericTest8 { - interface A {} + interface A {} - void foo1(A a) { - foo2(a); - } + void foo1(A a) { + foo2(a); + } - abstract A foo2(A a); + abstract A foo2(A a); - void bar1(A> a) { - bar2(a); - } + void bar1(A> a) { + bar2(a); + } - abstract A> bar2(A> a); + abstract A> bar2(A> a); } diff --git a/framework/tests/framework/GenericTest9.java b/framework/tests/framework/GenericTest9.java index 28f21caacdb..8eefb7fe0aa 100644 --- a/framework/tests/framework/GenericTest9.java +++ b/framework/tests/framework/GenericTest9.java @@ -3,71 +3,71 @@ // Test case for Issue 140: // https://github.com/typetools/checker-framework/issues/140 public class GenericTest9 { - // Make sure that substitutions on classes work correctly - - class C {} - - C> f = new C<>(); - - interface MyEntry {} - - void testclass() { - // :: error: (type.argument.type.incompatible) - // :: error: (type.arguments.not.inferred) - C<@Odd Object, MyEntry> c1 = new C<>(); - C<@Odd Object, @Odd MyEntry> c2 = new C<>(); - } - - // Make sure that substitutions on method type variables work correctly - - interface Ordering1 { - U sort(U iterable); - } - - void test(Ordering1> o, MyEntry e) { - MyEntry e1 = o.sort(e); - MyEntry e2 = o.>sort(e); - } - - interface Ordering2 { - U sort(U iterable); - } - - void test(Ordering2<@Odd MyEntry> ord, MyEntry e, @Odd MyEntry o) { - // :: error: (type.arguments.not.inferred) - MyEntry e1 = ord.sort(e); - // :: error: (type.argument.type.incompatible) - MyEntry e2 = ord.>sort(e); - MyEntry e3 = ord.sort(o); - MyEntry e4 = ord.<@Odd MyEntry>sort(o); - } - - interface Ordering3<@Odd T> { - U sort(U iterable); - } - - void test(Ordering3<@Odd MyEntry> o, @Odd MyEntry e) { - MyEntry e1 = o.sort(e); - MyEntry e2 = o.<@Odd MyEntry>sort(e); - // :: error: (type.argument.type.incompatible) :: error: (argument.type.incompatible) - MyEntry e3 = o.<@Even MyEntry>sort(e); - } - - interface Comparator4 {} - - interface Ordering4 extends Comparator4 { - Ordering4 reverse(); - } - - Ordering4 from4(Comparator4 comparator) { - return null; - } - - Comparator4 reverseComparator4(Comparator4 comparator) { - // Making the method type argument explicit: - // from4(comparator).reverse(); - // has the same result. - from4(comparator).reverse(); - return from4(comparator).reverse(); - } + // Make sure that substitutions on classes work correctly + + class C {} + + C> f = new C<>(); + + interface MyEntry {} + + void testclass() { + // :: error: (type.argument.type.incompatible) + // :: error: (type.arguments.not.inferred) + C<@Odd Object, MyEntry> c1 = new C<>(); + C<@Odd Object, @Odd MyEntry> c2 = new C<>(); + } + + // Make sure that substitutions on method type variables work correctly + + interface Ordering1 { + U sort(U iterable); + } + + void test(Ordering1> o, MyEntry e) { + MyEntry e1 = o.sort(e); + MyEntry e2 = o.>sort(e); + } + + interface Ordering2 { + U sort(U iterable); + } + + void test(Ordering2<@Odd MyEntry> ord, MyEntry e, @Odd MyEntry o) { + // :: error: (type.arguments.not.inferred) + MyEntry e1 = ord.sort(e); + // :: error: (type.argument.type.incompatible) + MyEntry e2 = ord.>sort(e); + MyEntry e3 = ord.sort(o); + MyEntry e4 = ord.<@Odd MyEntry>sort(o); + } + + interface Ordering3<@Odd T> { + U sort(U iterable); + } + + void test(Ordering3<@Odd MyEntry> o, @Odd MyEntry e) { + MyEntry e1 = o.sort(e); + MyEntry e2 = o.<@Odd MyEntry>sort(e); + // :: error: (type.argument.type.incompatible) :: error: (argument.type.incompatible) + MyEntry e3 = o.<@Even MyEntry>sort(e); + } + + interface Comparator4 {} + + interface Ordering4 extends Comparator4 { + Ordering4 reverse(); + } + + Ordering4 from4(Comparator4 comparator) { + return null; + } + + Comparator4 reverseComparator4(Comparator4 comparator) { + // Making the method type argument explicit: + // from4(comparator).reverse(); + // has the same result. + from4(comparator).reverse(); + return from4(comparator).reverse(); + } } diff --git a/framework/tests/framework/GetReceiverLoop.java b/framework/tests/framework/GetReceiverLoop.java index 278a788a25b..3c953934f9c 100644 --- a/framework/tests/framework/GetReceiverLoop.java +++ b/framework/tests/framework/GetReceiverLoop.java @@ -2,21 +2,21 @@ public class GetReceiverLoop { - void test() { - String s = Collections.emptyList().toString(); - } + void test() { + String s = Collections.emptyList().toString(); + } - /* - * getAnnotatedType( emptyList().toString ) - * -> TypeFromExpression.visitMemberSelect( emptyList().toString ) - * -> TypeFromExpression.visitMethodInvocation( emptyList() ) - * -> AnnotatedTypes.findTypeParameters( emptyList() ) - * -> AnnotatedTypes.assignedTo( emptyList() ) - * [the assignment context is emptyList().toString(), so then:] - * -> AnnotatedTypeFactory.getReceiver( emptyList() ) - * -> getAnnotatedType( emtpyList() ) - * -> TypeFromExpression.visitMethodInvocation( emptyList() ) - * ... - */ + /* + * getAnnotatedType( emptyList().toString ) + * -> TypeFromExpression.visitMemberSelect( emptyList().toString ) + * -> TypeFromExpression.visitMethodInvocation( emptyList() ) + * -> AnnotatedTypes.findTypeParameters( emptyList() ) + * -> AnnotatedTypes.assignedTo( emptyList() ) + * [the assignment context is emptyList().toString(), so then:] + * -> AnnotatedTypeFactory.getReceiver( emptyList() ) + * -> getAnnotatedType( emtpyList() ) + * -> TypeFromExpression.visitMethodInvocation( emptyList() ) + * ... + */ } diff --git a/framework/tests/framework/InnerGenerics.java b/framework/tests/framework/InnerGenerics.java index d4677322555..d368cb16eec 100644 --- a/framework/tests/framework/InnerGenerics.java +++ b/framework/tests/framework/InnerGenerics.java @@ -3,31 +3,31 @@ class ListOuter {} public class InnerGenerics { - class ListInner {} + class ListInner {} - void testInner1() { - // :: warning: (cast.unsafe.constructor.invocation) - @Odd ListOuter o = new @Odd ListOuter(); - // :: warning: (cast.unsafe.constructor.invocation) - @Odd ListInner i = new @Odd ListInner(); - } + void testInner1() { + // :: warning: (cast.unsafe.constructor.invocation) + @Odd ListOuter o = new @Odd ListOuter(); + // :: warning: (cast.unsafe.constructor.invocation) + @Odd ListInner i = new @Odd ListInner(); + } - void testInner2() { - // :: error: (assignment.type.incompatible) - @Odd ListOuter o = new ListOuter<>(); - // :: error: (assignment.type.incompatible) - @Odd ListInner i = new ListInner<>(); - } + void testInner2() { + // :: error: (assignment.type.incompatible) + @Odd ListOuter o = new ListOuter<>(); + // :: error: (assignment.type.incompatible) + @Odd ListInner i = new ListInner<>(); + } - void testInner3() { - ListOuter<@Odd String> o = new ListOuter<>(); - ListInner<@Odd String> i = new ListInner<>(); - } + void testInner3() { + ListOuter<@Odd String> o = new ListOuter<>(); + ListInner<@Odd String> i = new ListInner<>(); + } - void testInner4() { - // :: error: (assignment.type.incompatible) - ListOuter<@Odd String> o = new ListOuter(); - // :: error: (assignment.type.incompatible) - ListInner<@Odd String> i = new ListInner(); - } + void testInner4() { + // :: error: (assignment.type.incompatible) + ListOuter<@Odd String> o = new ListOuter(); + // :: error: (assignment.type.incompatible) + ListInner<@Odd String> i = new ListInner(); + } } diff --git a/framework/tests/framework/MatrixBug.java b/framework/tests/framework/MatrixBug.java index 382b2dacdfb..91496fb886d 100644 --- a/framework/tests/framework/MatrixBug.java +++ b/framework/tests/framework/MatrixBug.java @@ -2,5 +2,5 @@ public class MatrixBug { - public char[][] chars = new char[][] {new char[] {'*', '*', '*'}, new char[] {'*', '*', '*'}}; + public char[][] chars = new char[][] {new char[] {'*', '*', '*'}, new char[] {'*', '*', '*'}}; } diff --git a/framework/tests/framework/MethodOverrideBadParam.java b/framework/tests/framework/MethodOverrideBadParam.java index e8fdee4d6ef..1db418c2f37 100644 --- a/framework/tests/framework/MethodOverrideBadParam.java +++ b/framework/tests/framework/MethodOverrideBadParam.java @@ -2,10 +2,10 @@ public abstract class MethodOverrideBadParam { - public abstract void method(String s); + public abstract void method(String s); - public static class SubclassA extends MethodOverrideBadParam { - // :: error: (override.param.invalid) - public void method(@Odd String s) {} - } + public static class SubclassA extends MethodOverrideBadParam { + // :: error: (override.param.invalid) + public void method(@Odd String s) {} + } } diff --git a/framework/tests/framework/MethodOverrideBadReceiver.java b/framework/tests/framework/MethodOverrideBadReceiver.java index bcaf41c365f..d595ba45e49 100644 --- a/framework/tests/framework/MethodOverrideBadReceiver.java +++ b/framework/tests/framework/MethodOverrideBadReceiver.java @@ -2,12 +2,12 @@ public abstract class MethodOverrideBadReceiver { - public abstract String method(); + public abstract String method(); - public static class SubclassA extends MethodOverrideBadReceiver { - // :: error: (override.receiver.invalid) - public String method(@Odd SubclassA this) { - return ""; + public static class SubclassA extends MethodOverrideBadReceiver { + // :: error: (override.receiver.invalid) + public String method(@Odd SubclassA this) { + return ""; + } } - } } diff --git a/framework/tests/framework/MethodOverrideBadReturn.java b/framework/tests/framework/MethodOverrideBadReturn.java index 2e48a373c4f..74aea948732 100644 --- a/framework/tests/framework/MethodOverrideBadReturn.java +++ b/framework/tests/framework/MethodOverrideBadReturn.java @@ -2,12 +2,12 @@ public abstract class MethodOverrideBadReturn { - public abstract @Odd String method(); + public abstract @Odd String method(); - public static class SubclassA extends MethodOverrideBadReturn { - // :: error: (override.return.invalid) - public String method() { - return ""; + public static class SubclassA extends MethodOverrideBadReturn { + // :: error: (override.return.invalid) + public String method() { + return ""; + } } - } } diff --git a/framework/tests/framework/MethodOverrides.java b/framework/tests/framework/MethodOverrides.java index 1318a6c26bd..96c26b021d4 100644 --- a/framework/tests/framework/MethodOverrides.java +++ b/framework/tests/framework/MethodOverrides.java @@ -2,91 +2,91 @@ public abstract class MethodOverrides { - public abstract @Odd String method(); + public abstract @Odd String method(); - public abstract String methodSub(); + public abstract String methodSub(); - public abstract void param(@Odd String s); + public abstract void param(@Odd String s); - public abstract void paramSup(@Odd String s); + public abstract void paramSup(@Odd String s); - public abstract void receiver(@Odd MethodOverrides this); + public abstract void receiver(@Odd MethodOverrides this); - public abstract void receiverSub(@Odd MethodOverrides this); + public abstract void receiverSub(@Odd MethodOverrides this); - public static class SubclassA extends MethodOverrides { + public static class SubclassA extends MethodOverrides { - public @Odd String method() { - // :: error: (assignment.type.incompatible) - @Odd String s = ""; - return s; - } + public @Odd String method() { + // :: error: (assignment.type.incompatible) + @Odd String s = ""; + return s; + } - public @Odd String methodSub() { - // :: error: (assignment.type.incompatible) - @Odd String s = ""; - return s; - } + public @Odd String methodSub() { + // :: error: (assignment.type.incompatible) + @Odd String s = ""; + return s; + } - public void param(@Odd String s) {} + public void param(@Odd String s) {} - public void paramSup(String s) {} + public void paramSup(String s) {} - public void receiver(@Odd SubclassA this) {} + public void receiver(@Odd SubclassA this) {} - public void receiverSub() {} - } + public void receiverSub() {} + } - static class X { - T @Odd [] method(T @Odd [] t) { - return null; + static class X { + T @Odd [] method(T @Odd [] t) { + return null; + } } - } - static class Y extends X { - @Override - S @Odd [] method(S @Odd [] s) { - return null; + static class Y extends X { + @Override + S @Odd [] method(S @Odd [] s) { + return null; + } } - } - - static class Z extends X { - @Override - // return type is an incorrect override, as it's a supertype - // :: error: (override.return.invalid) - A[] method(A[] s) { - return null; + + static class Z extends X { + @Override + // return type is an incorrect override, as it's a supertype + // :: error: (override.return.invalid) + A[] method(A[] s) { + return null; + } } - } - static class Z2 extends X { - @Override - // :: error: (override.return.invalid) :: error: (override.param.invalid) - @Odd A[] method(@Odd A[] s) { - return null; + static class Z2 extends X { + @Override + // :: error: (override.return.invalid) :: error: (override.param.invalid) + @Odd A[] method(@Odd A[] s) { + return null; + } } - } - static class ClX { - T @Odd [] method(T @Odd [] t) { - return null; + static class ClX { + T @Odd [] method(T @Odd [] t) { + return null; + } } - } - static class ClY extends ClX { - @Override - S @Odd [] method(S @Odd [] s) { - return null; + static class ClY extends ClX { + @Override + S @Odd [] method(S @Odd [] s) { + return null; + } } - } - static class ClZ extends ClX { - @Override - // :: error: (override.return.invalid) :: error: (override.param.invalid) - @Odd S[] method(@Odd S[] s) { - return null; + static class ClZ extends ClX { + @Override + // :: error: (override.return.invalid) :: error: (override.param.invalid) + @Odd S[] method(@Odd S[] s) { + return null; + } } - } - // TODO others... + // TODO others... } diff --git a/framework/tests/framework/MoreVarargs.java b/framework/tests/framework/MoreVarargs.java index 8f01d4e831d..9200417dd50 100644 --- a/framework/tests/framework/MoreVarargs.java +++ b/framework/tests/framework/MoreVarargs.java @@ -1,15 +1,15 @@ public class MoreVarargs { - // :: warning: [unchecked] Possible heap pollution from parameterized vararg type T - T[] genericVararg(T... args) { - return args; - } + // :: warning: [unchecked] Possible heap pollution from parameterized vararg type T + T[] genericVararg(T... args) { + return args; + } - void testGenericVararg() { - genericVararg("m"); - genericVararg(new String[] {}); - genericVararg(3); - genericVararg(new Integer[] {}); - genericVararg(new int[] {}); - } + void testGenericVararg() { + genericVararg("m"); + genericVararg(new String[] {}); + genericVararg(3); + genericVararg(new Integer[] {}); + genericVararg(new int[] {}); + } } diff --git a/framework/tests/framework/MultiBoundTypeVar.java b/framework/tests/framework/MultiBoundTypeVar.java index ef25cc5a2f1..32fe6aad90f 100644 --- a/framework/tests/framework/MultiBoundTypeVar.java +++ b/framework/tests/framework/MultiBoundTypeVar.java @@ -2,15 +2,15 @@ public class MultiBoundTypeVar { - void test(T t) { - Number n1 = t; - @Odd Number n2 = t; + void test(T t) { + Number n1 = t; + @Odd Number n2 = t; - Cloneable c1 = t; + Cloneable c1 = t; - @Odd Cloneable c2 = t; + @Odd Cloneable c2 = t; - Appendable d1 = t; - @Odd Appendable d2 = t; - } + Appendable d1 = t; + @Odd Appendable d2 = t; + } } diff --git a/framework/tests/framework/OverrideCrash.java b/framework/tests/framework/OverrideCrash.java index 751613b1418..77a99d44020 100644 --- a/framework/tests/framework/OverrideCrash.java +++ b/framework/tests/framework/OverrideCrash.java @@ -1,8 +1,8 @@ import java.util.ArrayList; public class OverrideCrash extends ArrayList { - @Override - public Object[] toArray(Object[] o) { - return null; - } + @Override + public Object[] toArray(Object[] o) { + return null; + } } diff --git a/framework/tests/framework/PrimitiveDotClass.java b/framework/tests/framework/PrimitiveDotClass.java index 658bd429cfc..1501249c9cf 100644 --- a/framework/tests/framework/PrimitiveDotClass.java +++ b/framework/tests/framework/PrimitiveDotClass.java @@ -1,9 +1,9 @@ public class PrimitiveDotClass { - void test() { - doStuff(int.class); - doStuff(int[].class); - } + void test() { + doStuff(int.class); + doStuff(int[].class); + } - void doStuff(Class cl) {} + void doStuff(Class cl) {} } diff --git a/framework/tests/framework/RandomTests.java b/framework/tests/framework/RandomTests.java index e688dae1698..1ccd5e58875 100644 --- a/framework/tests/framework/RandomTests.java +++ b/framework/tests/framework/RandomTests.java @@ -2,13 +2,13 @@ import java.util.List; public class RandomTests { - // Test that boxing occurs even if the varable (assigned to value) is not a declared type - void testBoxing() { - int i = 0; - Collections.singleton(i); - } + // Test that boxing occurs even if the varable (assigned to value) is not a declared type + void testBoxing() { + int i = 0; + Collections.singleton(i); + } - void testWildcards() { - List l = null; - } + void testWildcards() { + List l = null; + } } diff --git a/framework/tests/framework/RecursiveDef.java b/framework/tests/framework/RecursiveDef.java index d953b5eef66..fe3c4fc24c6 100644 --- a/framework/tests/framework/RecursiveDef.java +++ b/framework/tests/framework/RecursiveDef.java @@ -1,6 +1,6 @@ public class RecursiveDef implements Comparable { - @org.checkerframework.dataflow.qual.Pure - public int compareTo(T t) { - return 0; - } + @org.checkerframework.dataflow.qual.Pure + public int compareTo(T t) { + return 0; + } } diff --git a/framework/tests/framework/Supertypes.java b/framework/tests/framework/Supertypes.java index fef30cf0eda..736fdff5f03 100644 --- a/framework/tests/framework/Supertypes.java +++ b/framework/tests/framework/Supertypes.java @@ -1,86 +1,87 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.framework.testchecker.util.*; public class Supertypes { - static interface Inter {} + static interface Inter {} - static class A extends ArrayList implements Inter<@Odd String> {} + static class A extends ArrayList implements Inter<@Odd String> {} - static class B extends ArrayList<@Odd String> implements Inter {} + static class B extends ArrayList<@Odd String> implements Inter {} - A a1; - @Odd A a2; + A a1; + @Odd A a2; - B b1; - @Odd B b2; + B b1; + @Odd B b2; - void testSelf() { - // :: error: (assignment.type.incompatible) - @Odd A t1 = a1; // should emit error - @Odd A t2 = a2; - // :: error: (assignment.type.incompatible) - @Odd B t3 = b1; // should emit error - @Odd B t4 = b2; - } + void testSelf() { + // :: error: (assignment.type.incompatible) + @Odd A t1 = a1; // should emit error + @Odd A t2 = a2; + // :: error: (assignment.type.incompatible) + @Odd B t3 = b1; // should emit error + @Odd B t4 = b2; + } - void testList() { - List l1 = a1; - List l2 = a2; - // :: error: (assignment.type.incompatible) - List l3 = b1; // should emit error - // :: error: (assignment.type.incompatible) - List l4 = b2; // should emit error + void testList() { + List l1 = a1; + List l2 = a2; + // :: error: (assignment.type.incompatible) + List l3 = b1; // should emit error + // :: error: (assignment.type.incompatible) + List l4 = b2; // should emit error - // :: error: (assignment.type.incompatible) - List<@Odd String> l5 = a1; // should emit error - // :: error: (assignment.type.incompatible) - List<@Odd String> l6 = a2; // should emit error - List<@Odd String> l7 = b1; - List<@Odd String> l8 = b2; - } + // :: error: (assignment.type.incompatible) + List<@Odd String> l5 = a1; // should emit error + // :: error: (assignment.type.incompatible) + List<@Odd String> l6 = a2; // should emit error + List<@Odd String> l7 = b1; + List<@Odd String> l8 = b2; + } - void testInter() { - // :: error: (assignment.type.incompatible) - Inter l1 = a1; // should emit error - // :: error: (assignment.type.incompatible) - Inter l2 = a2; // should emit error - Inter l3 = b1; - Inter l4 = b2; + void testInter() { + // :: error: (assignment.type.incompatible) + Inter l1 = a1; // should emit error + // :: error: (assignment.type.incompatible) + Inter l2 = a2; // should emit error + Inter l3 = b1; + Inter l4 = b2; - Inter<@Odd String> l5 = a1; - Inter<@Odd String> l6 = a2; - // :: error: (assignment.type.incompatible) - Inter<@Odd String> l7 = b1; // should emit error - // :: error: (assignment.type.incompatible) - Inter<@Odd String> l8 = b2; // should emit error - } + Inter<@Odd String> l5 = a1; + Inter<@Odd String> l6 = a2; + // :: error: (assignment.type.incompatible) + Inter<@Odd String> l7 = b1; // should emit error + // :: error: (assignment.type.incompatible) + Inter<@Odd String> l8 = b2; // should emit error + } - void testListOp() { - String s1 = a1.get(0); - String s2 = a2.get(0); - String s3 = b1.get(0); - String s4 = b2.get(0); + void testListOp() { + String s1 = a1.get(0); + String s2 = a2.get(0); + String s3 = b1.get(0); + String s4 = b2.get(0); - // :: error: (assignment.type.incompatible) - @Odd String s5 = a1.get(0); // should emit error - // :: error: (assignment.type.incompatible) - @Odd String s6 = a2.get(0); // should emit error - @Odd String s7 = b1.get(0); - @Odd String s8 = b2.get(0); - } + // :: error: (assignment.type.incompatible) + @Odd String s5 = a1.get(0); // should emit error + // :: error: (assignment.type.incompatible) + @Odd String s6 = a2.get(0); // should emit error + @Odd String s7 = b1.get(0); + @Odd String s8 = b2.get(0); + } - void ListIterable() { - for (String s : a1) {} - for (String s : a2) {} - for (String s : b1) {} - for (String s : b2) {} + void ListIterable() { + for (String s : a1) {} + for (String s : a2) {} + for (String s : b1) {} + for (String s : b2) {} - // :: error: (enhancedfor.type.incompatible) - for (@Odd String s : a1) {} - // :: error: (enhancedfor.type.incompatible) - for (@Odd String s : a2) {} - for (@Odd String s : b1) {} - for (@Odd String s : b2) {} - } + // :: error: (enhancedfor.type.incompatible) + for (@Odd String s : a1) {} + // :: error: (enhancedfor.type.incompatible) + for (@Odd String s : a2) {} + for (@Odd String s : b1) {} + for (@Odd String s : b2) {} + } } diff --git a/framework/tests/framework/SymbolError.java b/framework/tests/framework/SymbolError.java index 469c4d3d3f1..48154a435df 100644 --- a/framework/tests/framework/SymbolError.java +++ b/framework/tests/framework/SymbolError.java @@ -3,7 +3,7 @@ public class SymbolError { - void test() { - List lst = new LinkedList(null) {}; - } + void test() { + List lst = new LinkedList(null) {}; + } } diff --git a/framework/tests/framework/TypeInference.java b/framework/tests/framework/TypeInference.java index 29e5d614212..e11961fbe51 100644 --- a/framework/tests/framework/TypeInference.java +++ b/framework/tests/framework/TypeInference.java @@ -1,36 +1,37 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.checkerframework.framework.testchecker.util.*; public class TypeInference { - void test() { - Collection<@Odd String> lst1 = Collections.<@Odd String>emptyList(); - // :: error: (assignment.type.incompatible) - Collection<@Odd String> lst2 = Collections.emptyList(); // should emit error - // :: error: (assignment.type.incompatible) - Collection lst3 = Collections.<@Odd String>emptyList(); // should emit error - Collection<@Odd String> lst4 = Collections.emptyList(); - Map lst5 = Collections.emptyMap(); - Map lst6 = Collections.emptyMap(); - } + void test() { + Collection<@Odd String> lst1 = Collections.<@Odd String>emptyList(); + // :: error: (assignment.type.incompatible) + Collection<@Odd String> lst2 = Collections.emptyList(); // should emit error + // :: error: (assignment.type.incompatible) + Collection lst3 = Collections.<@Odd String>emptyList(); // should emit error + Collection<@Odd String> lst4 = Collections.emptyList(); + Map lst5 = Collections.emptyMap(); + Map lst6 = Collections.emptyMap(); + } - static class MyMap extends HashMap {} + static class MyMap extends HashMap {} - static MyMap getMap() { - return null; - } + static MyMap getMap() { + return null; + } - void testSuper() { - MyMap<@Odd String> m1 = TypeInference.<@Odd String>getMap(); - MyMap<@Odd String> m2 = getMap(); - // :: error: (assignment.type.incompatible) - MyMap m3 = TypeInference.<@Odd String>getMap(); // should emit error - MyMap m4 = getMap(); + void testSuper() { + MyMap<@Odd String> m1 = TypeInference.<@Odd String>getMap(); + MyMap<@Odd String> m2 = getMap(); + // :: error: (assignment.type.incompatible) + MyMap m3 = TypeInference.<@Odd String>getMap(); // should emit error + MyMap m4 = getMap(); - Map m5 = getMap(); - Map m6 = getMap(); - } + Map m5 = getMap(); + Map m6 = getMap(); + } } diff --git a/framework/tests/framework/Unboxing.java b/framework/tests/framework/Unboxing.java index 8aa1169524f..a4ec7bd2c30 100644 --- a/framework/tests/framework/Unboxing.java +++ b/framework/tests/framework/Unboxing.java @@ -1,16 +1,16 @@ public class Unboxing { - boolean b = Boolean.TRUE; + boolean b = Boolean.TRUE; - T foo(Class expectedType) { - return null; - } + T foo(Class expectedType) { + return null; + } - boolean b2 = foo(Boolean.class); - boolean b3 = foo(null); + boolean b2 = foo(Boolean.class); + boolean b3 = foo(null); - T bar() { - return null; - } + T bar() { + return null; + } - boolean b4 = bar(); + boolean b4 = bar(); } diff --git a/framework/tests/framework/Varargs.java b/framework/tests/framework/Varargs.java index 299a2badb25..30b123648e1 100644 --- a/framework/tests/framework/Varargs.java +++ b/framework/tests/framework/Varargs.java @@ -1,43 +1,43 @@ import org.checkerframework.framework.testchecker.util.*; public class Varargs { - public void testVarargsInvocation() { - @Odd String s = null; - aVarargsMethod(s); - // :: error: (argument.type.incompatible) - aVarargsMethod(s, ""); - aVarargsMethod(s, s); - - moreVarargs(new @Odd String[1]); - // The assignment context infers @Odd for the component type. With invariant array - // subtyping, this will fail, as the main type is a subtype. - moreVarargs(new String @Odd [1]); - // :: warning: (cast.unsafe.constructor.invocation) - moreVarargs(new @Odd String(), new @Odd String()); - // :: error: (argument.type.incompatible) - // :: warning: (cast.unsafe.constructor.invocation) - moreVarargs(new String(), new @Odd String()); - moreVarargs( + public void testVarargsInvocation() { + @Odd String s = null; + aVarargsMethod(s); // :: error: (argument.type.incompatible) - new String(), + aVarargsMethod(s, ""); + aVarargsMethod(s, s); + + moreVarargs(new @Odd String[1]); + // The assignment context infers @Odd for the component type. With invariant array + // subtyping, this will fail, as the main type is a subtype. + moreVarargs(new String @Odd [1]); + // :: warning: (cast.unsafe.constructor.invocation) + moreVarargs(new @Odd String(), new @Odd String()); // :: error: (argument.type.incompatible) - new String()); - } + // :: warning: (cast.unsafe.constructor.invocation) + moreVarargs(new String(), new @Odd String()); + moreVarargs( + // :: error: (argument.type.incompatible) + new String(), + // :: error: (argument.type.incompatible) + new String()); + } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ - public void aVarargsMethod(@Odd String s, @Odd String... more) {} + public void aVarargsMethod(@Odd String s, @Odd String... more) {} - public void moreVarargs(@Odd String... args) {} + public void moreVarargs(@Odd String... args) {} - Varargs(String... args) {} + Varargs(String... args) {} - void test() { - new Varargs("m", "n"); - new Varargs(); - } + void test() { + new Varargs("m", "n"); + new Varargs(); + } - void testVarargsConstructor() { - new ProcessBuilder("hello"); - } + void testVarargsConstructor() { + new ProcessBuilder("hello"); + } } diff --git a/framework/tests/framework/WildcardSuper.java b/framework/tests/framework/WildcardSuper.java index 3feb54bb22f..afb2754f2f9 100644 --- a/framework/tests/framework/WildcardSuper.java +++ b/framework/tests/framework/WildcardSuper.java @@ -1,9 +1,10 @@ -import java.util.List; import org.checkerframework.framework.testchecker.util.*; +import java.util.List; + public class WildcardSuper { - void test(List list) { - // :: error: (assignment.type.incompatible) - @Odd Object odd = list.get(0); - } + void test(List list) { + // :: error: (assignment.type.incompatible) + @Odd Object odd = list.get(0); + } } diff --git a/framework/tests/framework/Wildcards.java b/framework/tests/framework/Wildcards.java index 10f34f89623..cdac316625f 100644 --- a/framework/tests/framework/Wildcards.java +++ b/framework/tests/framework/Wildcards.java @@ -1,12 +1,13 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.Date; import java.util.List; -import org.checkerframework.framework.testchecker.util.*; public class Wildcards { - void process(List arg) {} + void process(List arg) {} - void test() { - List myList = null; - process(myList); - } + void test() { + List myList = null; + process(myList); + } } diff --git a/framework/tests/h1h2checker/AnonymousClasses.java b/framework/tests/h1h2checker/AnonymousClasses.java index f328ff970d2..a3e511eb66a 100644 --- a/framework/tests/h1h2checker/AnonymousClasses.java +++ b/framework/tests/h1h2checker/AnonymousClasses.java @@ -1,20 +1,21 @@ -import java.util.Comparator; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S1; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S2; +import java.util.Comparator; + public class AnonymousClasses { - private <@H1S1 T extends @H1S1 Comparator> void testGenericAnonymous() { - // :: error: (type.argument.type.incompatible) :: error: (constructor.invocation.invalid) - new @H1S1 Gen() {}; - // :: error: (type.argument.type.incompatible) :: warning: - // (cast.unsafe.constructor.invocation) - new @H1S1 GenInter() {}; - } + private <@H1S1 T extends @H1S1 Comparator> void testGenericAnonymous() { + // :: error: (type.argument.type.incompatible) :: error: (constructor.invocation.invalid) + new @H1S1 Gen() {}; + // :: error: (type.argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + new @H1S1 GenInter() {}; + } } class Gen<@H1S2 F extends @H1S2 Object> { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - public @H1S2 Gen() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + public @H1S2 Gen() {} } interface GenInter<@H1S2 F extends @H1S2 Object> {} diff --git a/framework/tests/h1h2checker/Catch.java b/framework/tests/h1h2checker/Catch.java index 3a615264f8f..4cfc376a7c1 100644 --- a/framework/tests/h1h2checker/Catch.java +++ b/framework/tests/h1h2checker/Catch.java @@ -1,53 +1,53 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Catch { - void defaultUnionType() throws Throwable { - try { - throw new Throwable(); - } catch (IndexOutOfBoundsException | NullPointerException ex) { + void defaultUnionType() throws Throwable { + try { + throw new Throwable(); + } catch (IndexOutOfBoundsException | NullPointerException ex) { + } } - } - void defaultDeclaredType() throws Throwable { - try { - throw new Throwable(); - } catch (RuntimeException ex) { + void defaultDeclaredType() throws Throwable { + try { + throw new Throwable(); + } catch (RuntimeException ex) { + } } - } - void explictlyTopUnionType() throws Throwable { - try { - throw new Throwable(); - } catch (@H1Top @H2Top IndexOutOfBoundsException | @H1Top @H2Top NullPointerException ex) { + void explictlyTopUnionType() throws Throwable { + try { + throw new Throwable(); + } catch (@H1Top @H2Top IndexOutOfBoundsException | @H1Top @H2Top NullPointerException ex) { + } } - } - void explictlyNotTopUnionType() throws Throwable { - try { - throw new Throwable(); - // :: error: (exception.parameter.invalid) - } catch (@H1S1 @H2Top IndexOutOfBoundsException | @H1S1 @H2Top NullPointerException ex) { + void explictlyNotTopUnionType() throws Throwable { + try { + throw new Throwable(); + // :: error: (exception.parameter.invalid) + } catch (@H1S1 @H2Top IndexOutOfBoundsException | @H1S1 @H2Top NullPointerException ex) { + } } - } - void explictlyTopDeclaredType() throws Throwable { - try { - throw new Throwable(); - } catch (@H1Top @H2Top NullPointerException ex) { + void explictlyTopDeclaredType() throws Throwable { + try { + throw new Throwable(); + } catch (@H1Top @H2Top NullPointerException ex) { + } } - } - void explictlyNotTopDeclaredType() throws Throwable { - try { - throw new Throwable(); - // :: error: (exception.parameter.invalid) - } catch (@H1S1 @H2Top RuntimeException ex) { + void explictlyNotTopDeclaredType() throws Throwable { + try { + throw new Throwable(); + // :: error: (exception.parameter.invalid) + } catch (@H1S1 @H2Top RuntimeException ex) { + } } - } } diff --git a/framework/tests/h1h2checker/CompoundStringAssignment.java b/framework/tests/h1h2checker/CompoundStringAssignment.java index 4ecaddaa816..89edada1470 100644 --- a/framework/tests/h1h2checker/CompoundStringAssignment.java +++ b/framework/tests/h1h2checker/CompoundStringAssignment.java @@ -1,43 +1,43 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class CompoundStringAssignment { - @H1S1 @H2S1 String getSib1() { - return null; - } - - void test1() { - String local = null; - // There was a bug in data flow where - // the type of local was @H1Bot @H2Bot after the - // StringConcatenateNode and AssignmentNode, - // but only if the RHS was a method call. - local += getSib1(); - - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String isBot = local; - @H1S1 @H2S1 String isSib1 = local; - } - - @H1Top @H2Top String top; - - void test2() { - String local2 = top; - local2 += getSib1(); - - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String isBot2 = local2; - // :: error: (assignment.type.incompatible) - @H1S1 @H2S1 String isSib12 = local2; - } - - @H1S1 @H2S1 String sib1; - - void test3() { - String local3 = null; - local3 += sib1; - - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String isBot3 = local3; - @H1S1 @H2S1 String isSib13 = local3; - } + @H1S1 @H2S1 String getSib1() { + return null; + } + + void test1() { + String local = null; + // There was a bug in data flow where + // the type of local was @H1Bot @H2Bot after the + // StringConcatenateNode and AssignmentNode, + // but only if the RHS was a method call. + local += getSib1(); + + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String isBot = local; + @H1S1 @H2S1 String isSib1 = local; + } + + @H1Top @H2Top String top; + + void test2() { + String local2 = top; + local2 += getSib1(); + + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String isBot2 = local2; + // :: error: (assignment.type.incompatible) + @H1S1 @H2S1 String isSib12 = local2; + } + + @H1S1 @H2S1 String sib1; + + void test3() { + String local3 = null; + local3 += sib1; + + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String isBot3 = local3; + @H1S1 @H2S1 String isSib13 = local3; + } } diff --git a/framework/tests/h1h2checker/Constructors.java b/framework/tests/h1h2checker/Constructors.java index b608fd6dd82..a8abec327b1 100644 --- a/framework/tests/h1h2checker/Constructors.java +++ b/framework/tests/h1h2checker/Constructors.java @@ -1,67 +1,67 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Constructors { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S2 @H2S2 Constructors() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S2 @H2S2 Constructors() {} - void test1() { - // All quals from constructor - @H1S2 @H2S2 Constructors c1 = new Constructors(); - // Can still specify my own - @H1S2 @H2Top Constructors c2 = new @H1S2 @H2Top Constructors(); - // Can only specify some of the qualifiers, rest comes - // from constructor - @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(); + void test1() { + // All quals from constructor + @H1S2 @H2S2 Constructors c1 = new Constructors(); + // Can still specify my own + @H1S2 @H2Top Constructors c2 = new @H1S2 @H2Top Constructors(); + // Can only specify some of the qualifiers, rest comes + // from constructor + @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e1 = new Constructors(); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e2 = new @H1S2 Constructors(); - } + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e1 = new Constructors(); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e2 = new @H1S2 Constructors(); + } - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S2 @H2Poly Constructors(@H1S1 @H2Poly int i) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S2 @H2Poly Constructors(@H1S1 @H2Poly int i) {} - void test2(@H1S1 @H2S2 int p) { - @H1S2 @H2S2 Constructors c1 = new Constructors(p); - @H1S2 @H2S2 Constructors c2 = new @H1S2 @H2S2 Constructors(p); - @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(p); + void test2(@H1S1 @H2S2 int p) { + @H1S2 @H2S2 Constructors c1 = new Constructors(p); + @H1S2 @H2S2 Constructors c2 = new @H1S2 @H2S2 Constructors(p); + @H1S2 @H2S2 Constructors c3 = new @H1S2 Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e1 = new Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); - } + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e1 = new Constructors(p); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); + } - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s) {} - void test3(@H1S1 @H2S2 String p) { - @H1S1 @H2S2 Constructors c1 = new Constructors(p); - @H1S1 @H2S2 Constructors c2 = new @H1S1 @H2S2 Constructors(p); - @H1S1 @H2S2 Constructors c3 = new @H1S1 Constructors(p); + void test3(@H1S1 @H2S2 String p) { + @H1S1 @H2S2 Constructors c1 = new Constructors(p); + @H1S1 @H2S2 Constructors c2 = new @H1S1 @H2S2 Constructors(p); + @H1S1 @H2S2 Constructors c3 = new @H1S1 Constructors(p); - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 Constructors e1 = new Constructors(p); - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 Constructors e1 = new Constructors(p); + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S1 Constructors e2 = new @H1S2 @H2S2 Constructors(p); + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S1 Constructors e3 = new @H1S2 Constructors(p); - // :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S2 Constructors e4 = new @H1S2 @H2S2 Constructors(p); - // :: warning: (cast.unsafe.constructor.invocation) - @H1S2 @H2S2 Constructors e5 = new @H1S2 Constructors(p); - } + // :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S2 Constructors e4 = new @H1S2 @H2S2 Constructors(p); + // :: warning: (cast.unsafe.constructor.invocation) + @H1S2 @H2S2 Constructors e5 = new @H1S2 Constructors(p); + } - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @org.checkerframework.framework.testchecker.util.Encrypted @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s, int i) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @org.checkerframework.framework.testchecker.util.Encrypted @H1Poly @H2Poly Constructors(@H1Poly @H2Poly String s, int i) {} - void test4(@H1S1 @H2S2 String p) { - @H1S1 @H2S2 Constructors c1 = new Constructors(p, 4); - @H1S1 @H2S2 Constructors c2 = - new @org.checkerframework.framework.testchecker.util.Encrypted Constructors(p); - } + void test4(@H1S1 @H2S2 String p) { + @H1S1 @H2S2 Constructors c1 = new Constructors(p, 4); + @H1S1 @H2S2 Constructors c2 = + new @org.checkerframework.framework.testchecker.util.Encrypted Constructors(p); + } } diff --git a/framework/tests/h1h2checker/Defaulting.java b/framework/tests/h1h2checker/Defaulting.java index 653788ad3be..5c811144882 100644 --- a/framework/tests/h1h2checker/Defaulting.java +++ b/framework/tests/h1h2checker/Defaulting.java @@ -7,161 +7,161 @@ // are separately annotated. public class Defaulting { - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - class TestLocal { - void m(@H1S1 Object p1, @H1S2 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } - } - - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.UPPER_BOUND}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) - // Type of x is <@H1S2 X extends @H1S1 Object>, these annotations are siblings - // and should not be in the same bound - // :: warning: (inconsistent.constructor.type) :: error: (bound.type.incompatible) :: error: - // (super.invocation.invalid) - class TestUpperBound { - void m(X p) { - @H1S1 Object l1 = p; - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = p; - Object l3 = p; - } - } - - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.PARAMETER}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) - // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) - class TestParameter { - void m(Object p) { - @H1S1 Object l1 = p; - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = p; - Object l3 = p; + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + class TestLocal { + void m(@H1S1 Object p1, @H1S2 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } } - void call() { - // :: warning: (cast.unsafe.constructor.invocation) - m(new @H1S1 Object()); - // :: error: (argument.type.incompatible) :: warning: - // (cast.unsafe.constructor.invocation) - m(new @H1S2 Object()); - // :: error: (argument.type.incompatible) - m(new Object()); + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.UPPER_BOUND}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) + // Type of x is <@H1S2 X extends @H1S1 Object>, these annotations are siblings + // and should not be in the same bound + // :: warning: (inconsistent.constructor.type) :: error: (bound.type.incompatible) :: error: + // (super.invocation.invalid) + class TestUpperBound { + void m(X p) { + @H1S1 Object l1 = p; + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = p; + Object l3 = p; + } } - } - - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.PARAMETER}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) - class TestConstructorParameter { + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.PARAMETER}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) - TestConstructorParameter(Object p) { - @H1S1 Object l1 = p; - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = p; - Object l3 = p; + class TestParameter { + void m(Object p) { + @H1S1 Object l1 = p; + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = p; + Object l3 = p; + } + + void call() { + // :: warning: (cast.unsafe.constructor.invocation) + m(new @H1S1 Object()); + // :: error: (argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + m(new @H1S2 Object()); + // :: error: (argument.type.incompatible) + m(new Object()); + } } - void call() { - // :: warning: (cast.unsafe.constructor.invocation) - new TestConstructorParameter(new @H1S1 Object()); - // :: error: (argument.type.incompatible) :: warning: - // (cast.unsafe.constructor.invocation) - new TestConstructorParameter(new @H1S2 Object()); - // :: error: (argument.type.incompatible) - new TestConstructorParameter(new Object()); - } - } - - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.RETURN}) - @DefaultQualifier( - value = H1S2.class, - locations = {TypeUseLocation.OTHERWISE}) - // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) - class TestReturns { - Object res() { - // :: warning: (cast.unsafe.constructor.invocation) - return new @H1S1 Object(); + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.PARAMETER}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) + class TestConstructorParameter { + + // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) + TestConstructorParameter(Object p) { + @H1S1 Object l1 = p; + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = p; + Object l3 = p; + } + + void call() { + // :: warning: (cast.unsafe.constructor.invocation) + new TestConstructorParameter(new @H1S1 Object()); + // :: error: (argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + new TestConstructorParameter(new @H1S2 Object()); + // :: error: (argument.type.incompatible) + new TestConstructorParameter(new Object()); + } } - void m() { - @H1S1 Object l1 = res(); - // :: error: (assignment.type.incompatible) - @H1S2 Object l2 = res(); - Object l3 = res(); + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.RETURN}) + @DefaultQualifier( + value = H1S2.class, + locations = {TypeUseLocation.OTHERWISE}) + // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) + class TestReturns { + Object res() { + // :: warning: (cast.unsafe.constructor.invocation) + return new @H1S1 Object(); + } + + void m() { + @H1S1 Object l1 = res(); + // :: error: (assignment.type.incompatible) + @H1S2 Object l2 = res(); + Object l3 = res(); + } + + Object res2() { + // :: error: (return.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + return new @H1S2 Object(); + } + + Object res3() { + // :: error: (return.type.incompatible) + return new Object(); + } } - Object res2() { - // :: error: (return.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - return new @H1S2 Object(); - } + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + @DefaultQualifier( + value = H1S1.class, + locations = {TypeUseLocation.RECEIVER}) + public class ReceiverDefaulting { + public ReceiverDefaulting() {} - Object res3() { - // :: error: (return.type.incompatible) - return new Object(); + public void m() {} } - } - - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.RECEIVER}) - public class ReceiverDefaulting { - public ReceiverDefaulting() {} - - public void m() {} - } - - @DefaultQualifier( - value = H1Top.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}) - class TestReceiver { - - void call() { - // :: warning: (cast.unsafe.constructor.invocation) - @H1S1 ReceiverDefaulting r2 = new @H1S1 ReceiverDefaulting(); - // :: warning: (cast.unsafe.constructor.invocation) - @H1S2 ReceiverDefaulting r3 = new @H1S2 ReceiverDefaulting(); - ReceiverDefaulting r = new ReceiverDefaulting(); - - r2.m(); - // :: error: (method.invocation.invalid) - r3.m(); - // :: error: (method.invocation.invalid) - r.m(); + + @DefaultQualifier( + value = H1Top.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}) + class TestReceiver { + + void call() { + // :: warning: (cast.unsafe.constructor.invocation) + @H1S1 ReceiverDefaulting r2 = new @H1S1 ReceiverDefaulting(); + // :: warning: (cast.unsafe.constructor.invocation) + @H1S2 ReceiverDefaulting r3 = new @H1S2 ReceiverDefaulting(); + ReceiverDefaulting r = new ReceiverDefaulting(); + + r2.m(); + // :: error: (method.invocation.invalid) + r3.m(); + // :: error: (method.invocation.invalid) + r.m(); + } } - } } diff --git a/framework/tests/h1h2checker/EnforceTargetLocation.java b/framework/tests/h1h2checker/EnforceTargetLocation.java index 66db250652a..cfcf38117ea 100644 --- a/framework/tests/h1h2checker/EnforceTargetLocation.java +++ b/framework/tests/h1h2checker/EnforceTargetLocation.java @@ -1,29 +1,30 @@ -import java.util.List; import org.checkerframework.framework.testchecker.h1h2checker.quals.*; +import java.util.List; + // :: error: (type.invalid.annotations.on.location) public class EnforceTargetLocation { - @H2S1 Object right; + @H2S1 Object right; - // :: error: (type.invalid.annotations.on.location) - @H2OnlyOnLB Object wrong; + // :: error: (type.invalid.annotations.on.location) + @H2OnlyOnLB Object wrong; - @H2S1 Object correctUse(@H2S1 Object p1) { - // :: warning: (cast.unsafe.constructor.invocation) - @H2S1 Object o = new @H2S1 Object(); - List l; - return o; - } + @H2S1 Object correctUse(@H2S1 Object p1) { + // :: warning: (cast.unsafe.constructor.invocation) + @H2S1 Object o = new @H2S1 Object(); + List l; + return o; + } - @H2OnlyOnLB - // :: error: (type.invalid.annotations.on.location) - Object incorrect() { - // :: warning: (cast.unsafe.constructor.invocation) + @H2OnlyOnLB // :: error: (type.invalid.annotations.on.location) - @H2OnlyOnLB Object o = new @H2OnlyOnLB Object(); - return o; - } + Object incorrect() { + // :: warning: (cast.unsafe.constructor.invocation) + // :: error: (type.invalid.annotations.on.location) + @H2OnlyOnLB Object o = new @H2OnlyOnLB Object(); + return o; + } - // :: error: (type.invalid.annotations.on.location) - void incorrectUse2(@H2OnlyOnLB Object p1) {} + // :: error: (type.invalid.annotations.on.location) + void incorrectUse2(@H2OnlyOnLB Object p1) {} } diff --git a/framework/tests/h1h2checker/ForEach.java b/framework/tests/h1h2checker/ForEach.java index 6a04fbce39a..9de90c1b6e4 100644 --- a/framework/tests/h1h2checker/ForEach.java +++ b/framework/tests/h1h2checker/ForEach.java @@ -2,91 +2,91 @@ public class ForEach { - Object arrayAccess1(Object[] constants) { - Object constant = constants[0]; - return constant; - } + Object arrayAccess1(Object[] constants) { + Object constant = constants[0]; + return constant; + } - @H1S2 Object arrayAccessBad1(@H1S1 Object[] constants) { - Object constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + @H1S2 Object arrayAccessBad1(@H1S1 Object[] constants) { + Object constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - // Return type defaults to H1Top - @H2S1 Object arrayAccessBad2(@H1S1 @H2S2 Object[] constants) { - Object constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + // Return type defaults to H1Top + @H2S1 Object arrayAccessBad2(@H1S1 @H2S2 Object[] constants) { + Object constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - Object iterateFor(Object[] constants) { - for (int i = 0; i < constants.length; ++i) { - Object constant = constants[i]; - return constant; + Object iterateFor(Object[] constants) { + for (int i = 0; i < constants.length; ++i) { + Object constant = constants[i]; + return constant; + } + return null; } - return null; - } - Object iterateForEach(Object[] constants) { - for (Object constant : constants) { - return constant; + Object iterateForEach(Object[] constants) { + for (Object constant : constants) { + return constant; + } + return null; } - return null; - } - @H2S2 Object iterateForEachBad(@H2S1 Object[] constants) { - for (Object constant : constants) { - // :: error: (return.type.incompatible) - return constant; + @H2S2 Object iterateForEachBad(@H2S1 Object[] constants) { + for (Object constant : constants) { + // :: error: (return.type.incompatible) + return constant; + } + return null; } - return null; - } - // Now with a method type variable + // Now with a method type variable - T garrayAccess1(T[] constants) { - return constants[0]; - } + T garrayAccess1(T[] constants) { + return constants[0]; + } - T garrayAccess1(T p) { - T constant = p; - return constant; - } + T garrayAccess1(T p) { + T constant = p; + return constant; + } - @H1S2 T garrayAccessBad1(@H1S1 T[] constants) { - T constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + @H1S2 T garrayAccessBad1(@H1S1 T[] constants) { + T constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - // Return type defaults to H1Top - @H2S1 T garrayAccessBad2(@H1S1 @H2S2 T[] constants) { - T constant = constants[0]; - // :: error: (return.type.incompatible) - return constant; - } + // Return type defaults to H1Top + @H2S1 T garrayAccessBad2(@H1S1 @H2S2 T[] constants) { + T constant = constants[0]; + // :: error: (return.type.incompatible) + return constant; + } - T giterateFor(T[] constants) { - for (int i = 0; i < constants.length; ++i) { - T constant = constants[i]; - return constant; + T giterateFor(T[] constants) { + for (int i = 0; i < constants.length; ++i) { + T constant = constants[i]; + return constant; + } + return null; } - return null; - } - T giterateForEach(T[] constants) { - for (T constant : constants) { - return constant; + T giterateForEach(T[] constants) { + for (T constant : constants) { + return constant; + } + return null; } - return null; - } - @H2S2 T giterateForEachBad(@H2S1 T[] constants) { - for (T constant : constants) { - // :: error: (return.type.incompatible) - return constant; + @H2S2 T giterateForEachBad(@H2S1 T[] constants) { + for (T constant : constants) { + // :: error: (return.type.incompatible) + return constant; + } + return null; } - return null; - } } diff --git a/framework/tests/h1h2checker/Generics.java b/framework/tests/h1h2checker/Generics.java index b825978aaf6..44312a52f1f 100644 --- a/framework/tests/h1h2checker/Generics.java +++ b/framework/tests/h1h2checker/Generics.java @@ -2,38 +2,38 @@ public class Generics { - class Generics1 { + class Generics1 { - T m(@H1S2 @H2S2 T p) { - T l = p; - // :: error: (return.type.incompatible) - return l; - } + T m(@H1S2 @H2S2 T p) { + T l = p; + // :: error: (return.type.incompatible) + return l; + } - void unsound(Generics1<@H1S1 @H2S1 Object> p, @H1S2 @H2S2 Object p2) { - @H1S1 @H2S1 Object o = p.m(p2); + void unsound(Generics1<@H1S1 @H2S1 Object> p, @H1S2 @H2S2 Object p2) { + @H1S1 @H2S1 Object o = p.m(p2); + } } - } - class Generics2 { + class Generics2 { - T m(@H1S2 T p) { - T l = p; - // :: error: (return.type.incompatible) - return l; - } + T m(@H1S2 T p) { + T l = p; + // :: error: (return.type.incompatible) + return l; + } - void unsound(Generics2<@H1S1 Object> p, @H1S2 Object p2) { - @H1S1 Object o = p.m(p2); + void unsound(Generics2<@H1S1 Object> p, @H1S2 Object p2) { + @H1S1 Object o = p.m(p2); + } } - } - class Generics3 { + class Generics3 { - // See comments in BaseTypeVisitor about type variable checks. - // The currently desired behavior is that the annotation on the - // type variable overrides the bound. - // TODO?:: error: (type.invalid) - void m(@H1S2 T p) {} - } + // See comments in BaseTypeVisitor about type variable checks. + // The currently desired behavior is that the annotation on the + // type variable overrides the bound. + // TODO?:: error: (type.invalid) + void m(@H1S2 T p) {} + } } diff --git a/framework/tests/h1h2checker/GetClassStubTest.java b/framework/tests/h1h2checker/GetClassStubTest.java index 749631c8dfc..51568253f23 100644 --- a/framework/tests/h1h2checker/GetClassStubTest.java +++ b/framework/tests/h1h2checker/GetClassStubTest.java @@ -4,18 +4,18 @@ public class GetClassStubTest { - // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver - void context() { - Integer i = 4; - Class a = i.getClass(); + // See AnnotatedTypeFactory.adaptGetClassReturnTypeToReceiver + void context() { + Integer i = 4; + Class a = i.getClass(); - Class<@H1Bot ? extends @H1S1 Object> succeed1 = i.getClass(); - Class<@H1Bot ? extends @H1S1 Integer> succeed2 = i.getClass(); + Class<@H1Bot ? extends @H1S1 Object> succeed1 = i.getClass(); + Class<@H1Bot ? extends @H1S1 Integer> succeed2 = i.getClass(); - // :: error: (assignment.type.incompatible) - Class<@H1Bot ? extends @H1Bot Object> fail1 = i.getClass(); + // :: error: (assignment.type.incompatible) + Class<@H1Bot ? extends @H1Bot Object> fail1 = i.getClass(); - // :: error: (assignment.type.incompatible) - Class<@H1Bot ?> fail2 = i.getClass(); - } + // :: error: (assignment.type.incompatible) + Class<@H1Bot ?> fail2 = i.getClass(); + } } diff --git a/framework/tests/h1h2checker/IncompatibleBounds.java b/framework/tests/h1h2checker/IncompatibleBounds.java index 8f3b2528c28..ec086642f5c 100644 --- a/framework/tests/h1h2checker/IncompatibleBounds.java +++ b/framework/tests/h1h2checker/IncompatibleBounds.java @@ -13,50 +13,50 @@ // set the defaults in the H2 hierarchy such that do not report errors in this test @DefaultQualifier( - value = H2Top.class, - locations = {TypeUseLocation.UPPER_BOUND}) + value = H2Top.class, + locations = {TypeUseLocation.UPPER_BOUND}) @DefaultQualifier( - value = H2Bot.class, - locations = {TypeUseLocation.LOWER_BOUND}) + value = H2Bot.class, + locations = {TypeUseLocation.LOWER_BOUND}) public class IncompatibleBounds { - // The bounds below are valid - class TopToBottom<@H1Bot T extends @H1Top Object> {} + // The bounds below are valid + class TopToBottom<@H1Bot T extends @H1Top Object> {} - class TopToH1S1<@H1S1 TT extends @H1Top Object> {} + class TopToH1S1<@H1S1 TT extends @H1Top Object> {} - class H1S1ToBot<@H1Bot TTT extends @H1S1 Object> {} + class H1S1ToBot<@H1Bot TTT extends @H1S1 Object> {} - class H1S1ToH1S1<@H1S1 TTTT extends @H1S1 Object> {} + class H1S1ToH1S1<@H1S1 TTTT extends @H1S1 Object> {} - class ValidContext { - TopToBottom<@H1Bot ? extends @H1Top Object> topToBot; - TopToH1S1<@H1S1 ? extends @H1Top Object> topToH1S1; - H1S1ToBot<@H1Bot ? extends @H1S1 Object> h1S1ToBot; - H1S1ToH1S1<@H1S1 ? extends @H1S1 Object> h1S1ToH1S1; - } + class ValidContext { + TopToBottom<@H1Bot ? extends @H1Top Object> topToBot; + TopToH1S1<@H1S1 ? extends @H1Top Object> topToH1S1; + H1S1ToBot<@H1Bot ? extends @H1S1 Object> h1S1ToBot; + H1S1ToH1S1<@H1S1 ? extends @H1S1 Object> h1S1ToH1S1; + } - // invalid combinations - // :: error: (bound.type.incompatible) - class BottomToTop<@H1Top U extends @H1Bot Object> {} - - // :: error: (bound.type.incompatible) - class H1S1ToTop<@H1Top UU extends @H1S1 Object> {} - - // :: error: (bound.type.incompatible) - class BottomToH1S1<@H1S1 UUU extends @H1Bot Object> {} - - // :: error: (bound.type.incompatible) - class H1S2ToH1S1<@H1S1 UUUU extends @H1S2 Object> {} - - class InvalidContext { + // invalid combinations // :: error: (bound.type.incompatible) - BottomToTop<@H1Top ? extends @H1Bot Object> bottomToTop; + class BottomToTop<@H1Top U extends @H1Bot Object> {} + // :: error: (bound.type.incompatible) - H1S1ToTop<@H1Top ? extends @H1S1 Object> h1S1ToTop; + class H1S1ToTop<@H1Top UU extends @H1S1 Object> {} + // :: error: (bound.type.incompatible) - BottomToH1S1<@H1S1 ? extends @H1Bot Object> bottomToH1S1; + class BottomToH1S1<@H1S1 UUU extends @H1Bot Object> {} + // :: error: (bound.type.incompatible) - H1S2ToH1S1<@H1S1 ? extends @H1S2 Object> h1S2ToH1S1; - } + class H1S2ToH1S1<@H1S1 UUUU extends @H1S2 Object> {} + + class InvalidContext { + // :: error: (bound.type.incompatible) + BottomToTop<@H1Top ? extends @H1Bot Object> bottomToTop; + // :: error: (bound.type.incompatible) + H1S1ToTop<@H1Top ? extends @H1S1 Object> h1S1ToTop; + // :: error: (bound.type.incompatible) + BottomToH1S1<@H1S1 ? extends @H1Bot Object> bottomToH1S1; + // :: error: (bound.type.incompatible) + H1S2ToH1S1<@H1S1 ? extends @H1S2 Object> h1S2ToH1S1; + } } diff --git a/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java index 63b16651217..e1d3cc5c396 100644 --- a/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java +++ b/framework/tests/h1h2checker/InferTypeArgsPolyChecker.java @@ -1,176 +1,177 @@ +import org.checkerframework.framework.testchecker.h1h2checker.quals.*; + import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class InferTypeArgsPolyChecker { - // ---------------------------------------------------------- - // Test Case - A - A methodA(@H2Top A a1, @H2Top A a2) { - return null; - } - - void contextA(@H1S1 @H2Bot String str, @H1Bot @H2Bot List<@H1S2 String> s) { - @H2Bot Object a = methodA(str, s); - @H1Top @H2Bot Object aTester = a; - } - - // ---------------------------------------------------------- - // Test Case - B - B methodB(List<@H2S2 B> b1, List<@H1S2 B> b2) { - return null; - } - - void contextB(List<@H1S1 @H2S2 String> l1, List<@H1S2 @H2S1 String> l2) { - @H1S1 @H2S1 String str = methodB(l1, l2); - } - - // ---------------------------------------------------------- - // Test Case - C - > C methodC(C c1, C c2) { - return null; - } - - void contextC(List<@H1S1 @H2S2 ? extends @H1S1 @H2S2 String> l1, List<@H1S1 @H2S2 String> l2) { - List<@H1S1 @H2S2 ?> str = methodC(l1, l2); - } - - // ---------------------------------------------------------- - // Test Case - D - - D methodD(D d1, D d2, DD dd) { - return null; - } - - DD methodD2(D d1, D d2, DD dd) { - return null; - } - - void contextD(OUTER_SCOPE_TV os1, @H1S1 @H2S1 OUTER_SCOPE_TV os2) { - OUTER_SCOPE_TV osNaked1 = methodD(os1, os1, os2); - - // So for the next failure we correctly infer that for methodD to take both os1 and os2 as - // arguments D must be @H1Top @H2Top OUTER_SCOPE_TV. However, the UPPER_BOUND of D is - // <@H1Bottom @H2Bottom OUTER_SCOPE_TV extends @H1Top @H2Top Object> notice that our - // inferred type for D is above this bound. + // ---------------------------------------------------------- + // Test Case - A + A methodA(@H2Top A a1, @H2Top A a2) { + return null; + } + + void contextA(@H1S1 @H2Bot String str, @H1Bot @H2Bot List<@H1S2 String> s) { + @H2Bot Object a = methodA(str, s); + @H1Top @H2Bot Object aTester = a; + } + + // ---------------------------------------------------------- + // Test Case - B + B methodB(List<@H2S2 B> b1, List<@H1S2 B> b2) { + return null; + } + + void contextB(List<@H1S1 @H2S2 String> l1, List<@H1S2 @H2S1 String> l2) { + @H1S1 @H2S1 String str = methodB(l1, l2); + } + + // ---------------------------------------------------------- + // Test Case - C + > C methodC(C c1, C c2) { + return null; + } + + void contextC(List<@H1S1 @H2S2 ? extends @H1S1 @H2S2 String> l1, List<@H1S1 @H2S2 String> l2) { + List<@H1S1 @H2S2 ?> str = methodC(l1, l2); + } + + // ---------------------------------------------------------- + // Test Case - D + + D methodD(D d1, D d2, DD dd) { + return null; + } + + DD methodD2(D d1, D d2, DD dd) { + return null; + } + + void contextD(OUTER_SCOPE_TV os1, @H1S1 @H2S1 OUTER_SCOPE_TV os2) { + OUTER_SCOPE_TV osNaked1 = methodD(os1, os1, os2); + + // So for the next failure we correctly infer that for methodD to take both os1 and os2 as + // arguments D must be @H1Top @H2Top OUTER_SCOPE_TV. However, the UPPER_BOUND of D is + // <@H1Bottom @H2Bottom OUTER_SCOPE_TV extends @H1Top @H2Top Object> notice that our + // inferred type for D is above this bound. + // + // A similar, more useful example in the Nullness type system would be: + /* + class Gen { + public List listo; + + void addToListo(T t1, T T2) { + listo.add(t1); + listo.add(t2); + } + + void launder(@NonNull OUTER arg1, @Nullable OUTER arg2) { + addToListo(arg1, arg2); // T is inferred to be <@Nullable OUTER> + // if we did not mark this as type.argument.type.incompatible + // then we would have no idea that in the last + // line of this example we are putting a null value into + // a List of @NonNull Strings + } + + } + + Gen<@NonNull String> g = ...; + g.listo = new ArrayList<@NonNull String>(); + g.launder("", null); // during this method call null would be added to g.listo + */ + // :: error: (type.arguments.not.inferred) + OUTER_SCOPE_TV osNaked2 = methodD(os1, os2, ""); + + // :: error: (type.arguments.not.inferred) + OUTER_SCOPE_TV osAnnoed = methodD(os2, os2, ""); + + // :: error: (type.arguments.not.inferred) + String str = methodD2(os2, os1, ""); + // :: error: (type.arguments.not.inferred) + OUTER_SCOPE_TV osNaked3 = methodD2(os1, os1, os2); + } + + // ---------------------------------------------------------- + // Test Case - E + E methodE(E e1, E[] aos2) { + return null; + } // pass an array to one of these to cover A2FReducer.visitArray_Typevar + + void contextE(String[] strArr, String[][] strArrArr, OUTER_SCOPE_TV os, OUTER_SCOPE_TV[] aos) { + String[] strArrLocal = methodE(strArr, strArrArr); + OUTER_SCOPE_TV osLocal = methodE(os, aos); + } + + // ---------------------------------------------------------- + // Test Case - C + List methodF(List lExtF, List lSupF) { + return null; + } + + void contextF( + // :: error: (type.invalid.annotations.on.location) + List<@H1Bot @H2Bot ? extends @H1Top @H2S1 String> l1, + List l2, + List<@H1S1 @H2S2 ? extends @H1Top @H2Top String> l3) { + + // :: error: (type.arguments.not.inferred) + List lstr1 = methodF(l1, l2); + // :: error: (type.arguments.not.inferred) + List lstr2 = methodF(l3, l2); + } + + , H extends List> List methodG(G g1, G G2) { + return null; + } + + class NodeList extends ArrayList<@H1Top @H2Top NodeList> {} + + void contextG(NodeList l1, NodeList l2) { + List<@H1Top @H2Top NodeList> a = methodG(l1, l2); + } + + // add test case for Array vs. String[] + // add test case of E extends F, F extends G, H extends List and other craziness + + Map method() { + return null; + } + + void contextMN() { + + // so I am not exactly sure how to create a meaningful test for this case + // what occurs is the SubtypeSolver.propagateGlbs forces N to be a subtype of M + // and it works (at least while I was debugging) but I don't know how to create + // a check that fails or succeeds because of this + Map mnl = method(); + } + + // class Pair { + // PONE _1; + // PTWO _2; + // } + + @H1Top @H2Top P methodOP(@H1Top @H2Top P p, O o) { + return null; + } + + void contextOP(@H1S1 @H2S1 String s1, @H1Bot @H2Bot String s2) { + // This test is actually here to test that the constraint P :> O is implied on p + // :: error: (assignment.type.incompatible) + @H1Bot @H2Bot String loc = methodOP(s1, s2); + } + + // class Triplet { + // L l; + // M m; + // N n; + // } + // + // Triplet methodXYZ() { + // return null; + // } // - // A similar, more useful example in the Nullness type system would be: - /* - class Gen { - public List listo; - - void addToListo(T t1, T T2) { - listo.add(t1); - listo.add(t2); - } - - void launder(@NonNull OUTER arg1, @Nullable OUTER arg2) { - addToListo(arg1, arg2); // T is inferred to be <@Nullable OUTER> - // if we did not mark this as type.argument.type.incompatible - // then we would have no idea that in the last - // line of this example we are putting a null value into - // a List of @NonNull Strings - } - - } - - Gen<@NonNull String> g = ...; - g.listo = new ArrayList<@NonNull String>(); - g.launder("", null); // during this method call null would be added to g.listo - */ - // :: error: (type.arguments.not.inferred) - OUTER_SCOPE_TV osNaked2 = methodD(os1, os2, ""); - - // :: error: (type.arguments.not.inferred) - OUTER_SCOPE_TV osAnnoed = methodD(os2, os2, ""); - - // :: error: (type.arguments.not.inferred) - String str = methodD2(os2, os1, ""); - // :: error: (type.arguments.not.inferred) - OUTER_SCOPE_TV osNaked3 = methodD2(os1, os1, os2); - } - - // ---------------------------------------------------------- - // Test Case - E - E methodE(E e1, E[] aos2) { - return null; - } // pass an array to one of these to cover A2FReducer.visitArray_Typevar - - void contextE(String[] strArr, String[][] strArrArr, OUTER_SCOPE_TV os, OUTER_SCOPE_TV[] aos) { - String[] strArrLocal = methodE(strArr, strArrArr); - OUTER_SCOPE_TV osLocal = methodE(os, aos); - } - - // ---------------------------------------------------------- - // Test Case - C - List methodF(List lExtF, List lSupF) { - return null; - } - - void contextF( - // :: error: (type.invalid.annotations.on.location) - List<@H1Bot @H2Bot ? extends @H1Top @H2S1 String> l1, - List l2, - List<@H1S1 @H2S2 ? extends @H1Top @H2Top String> l3) { - - // :: error: (type.arguments.not.inferred) - List lstr1 = methodF(l1, l2); - // :: error: (type.arguments.not.inferred) - List lstr2 = methodF(l3, l2); - } - - , H extends List> List methodG(G g1, G G2) { - return null; - } - - class NodeList extends ArrayList<@H1Top @H2Top NodeList> {} - - void contextG(NodeList l1, NodeList l2) { - List<@H1Top @H2Top NodeList> a = methodG(l1, l2); - } - - // add test case for Array vs. String[] - // add test case of E extends F, F extends G, H extends List and other craziness - - Map method() { - return null; - } - - void contextMN() { - - // so I am not exactly sure how to create a meaningful test for this case - // what occurs is the SubtypeSolver.propagateGlbs forces N to be a subtype of M - // and it works (at least while I was debugging) but I don't know how to create - // a check that fails or succeeds because of this - Map mnl = method(); - } - - // class Pair { - // PONE _1; - // PTWO _2; - // } - - @H1Top @H2Top P methodOP(@H1Top @H2Top P p, O o) { - return null; - } - - void contextOP(@H1S1 @H2S1 String s1, @H1Bot @H2Bot String s2) { - // This test is actually here to test that the constraint P :> O is implied on p - // :: error: (assignment.type.incompatible) - @H1Bot @H2Bot String loc = methodOP(s1, s2); - } - - // class Triplet { - // L l; - // M m; - // N n; - // } - // - // Triplet methodXYZ() { - // return null; - // } - // - // void contextXYZ() { - // Triplet<@H1S> - // } + // void contextXYZ() { + // Triplet<@H1S> + // } } diff --git a/framework/tests/h1h2checker/Inheritance.java b/framework/tests/h1h2checker/Inheritance.java index 8c61a50f23d..26ff10171df 100644 --- a/framework/tests/h1h2checker/Inheritance.java +++ b/framework/tests/h1h2checker/Inheritance.java @@ -2,17 +2,17 @@ // :: warning: (inconsistent.constructor.type) :: error: (super.invocation.invalid) @H1S1 class Inheritance { - void bar1(@H1Bot Inheritance param) {} + void bar1(@H1Bot Inheritance param) {} - void bar2(@H1S1 Inheritance param) {} + void bar2(@H1S1 Inheritance param) {} - // :: error: (type.invalid.annotations.on.use) - void bar3(@H1Top Inheritance param) {} + // :: error: (type.invalid.annotations.on.use) + void bar3(@H1Top Inheritance param) {} - void foo1(@H1Bot Inheritance[] param) {} + void foo1(@H1Bot Inheritance[] param) {} - void foo2(@H1S1 Inheritance[] param) {} + void foo2(@H1S1 Inheritance[] param) {} - // :: error: (type.invalid.annotations.on.use) - void foo3(@H1Top Inheritance[] param) {} + // :: error: (type.invalid.annotations.on.use) + void foo3(@H1Top Inheritance[] param) {} } diff --git a/framework/tests/h1h2checker/Issue2163Final.java b/framework/tests/h1h2checker/Issue2163Final.java index 4bbf468e32f..17d5c6687da 100644 --- a/framework/tests/h1h2checker/Issue2163Final.java +++ b/framework/tests/h1h2checker/Issue2163Final.java @@ -6,64 +6,64 @@ // Testing Rule 1 (constructor declaration type <: class type) @H1Top class Issue2163FinalAA { - @H1Top Issue2163FinalAA() {} + @H1Top Issue2163FinalAA() {} } @H1Top class Issue2163FinalAB { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S1 Issue2163FinalAB() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S1 Issue2163FinalAB() {} } @H1Top class Issue2163FinalAC { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2163FinalAC() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2163FinalAC() {} } @H1S1 class Issue2163FinalBA { - // :: error: (type.invalid.annotations.on.use) - @H1Top Issue2163FinalBA() {} + // :: error: (type.invalid.annotations.on.use) + @H1Top Issue2163FinalBA() {} } @H1S1 class Issue2163FinalBB { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S1 Issue2163FinalBB() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S1 Issue2163FinalBB() {} } @H1S1 class Issue2163FinalBC { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2163FinalBC() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2163FinalBC() {} } @H1Bot class Issue2163FinalCA { - // :: error: (type.invalid.annotations.on.use) - @H1Top Issue2163FinalCA() {} + // :: error: (type.invalid.annotations.on.use) + @H1Top Issue2163FinalCA() {} } @H1Bot class Issue2163FinalCB { - // :: error: (type.invalid.annotations.on.use) :: warning: (inconsistent.constructor.type) - // :: error: (super.invocation.invalid) - @H1S1 Issue2163FinalCB() {} + // :: error: (type.invalid.annotations.on.use) :: warning: (inconsistent.constructor.type) + // :: error: (super.invocation.invalid) + @H1S1 Issue2163FinalCB() {} } @H1Bot class Issue2163FinalCC { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2163FinalCC() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2163FinalCC() {} } // Testing Rule 2 (Issue type cast warning if constructor declaration type <: invocation type) @SuppressWarnings("anno.on.irrelevant") class Issue2163FinalAAClient { - void test() { - new @H1Top Issue2163FinalAA(); - // :: warning: (cast.unsafe.constructor.invocation) - new @H1S1 Issue2163FinalAA(); - // :: warning: (cast.unsafe.constructor.invocation) - new @H1Bot Issue2163FinalAA(); - } + void test() { + new @H1Top Issue2163FinalAA(); + // :: warning: (cast.unsafe.constructor.invocation) + new @H1S1 Issue2163FinalAA(); + // :: warning: (cast.unsafe.constructor.invocation) + new @H1Bot Issue2163FinalAA(); + } } // Testing Default @SuppressWarnings("anno.on.irrelevant") class Issue2163FinalBCClient { - @H1Bot Issue2163FinalBC obj = new Issue2163FinalBC(); + @H1Bot Issue2163FinalBC obj = new Issue2163FinalBC(); } diff --git a/framework/tests/h1h2checker/Issue2186.java b/framework/tests/h1h2checker/Issue2186.java index ac6e51922d7..cd5af40f8f0 100644 --- a/framework/tests/h1h2checker/Issue2186.java +++ b/framework/tests/h1h2checker/Issue2186.java @@ -1,26 +1,27 @@ // Test case for issue #2186 // https://github.com/typetools/checker-framework/issues/2186 -import java.util.ArrayList; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Bot; import org.checkerframework.framework.testchecker.h1h2checker.quals.H1S1; +import java.util.ArrayList; + @SuppressWarnings("anno.on.irrelevant") @H1S1 class Issue2186 { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - Issue2186() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + Issue2186() {} - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1Bot Issue2186(int x) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1Bot Issue2186(int x) {} - void test() { - @H1S1 Issue2186 obj = new Issue2186(); - @H1Bot Issue2186 obj1 = new Issue2186(9); - } + void test() { + @H1S1 Issue2186 obj = new Issue2186(); + @H1Bot Issue2186 obj1 = new Issue2186(9); + } - void testDiamond() { - @H1Bot ArrayList<@H1Bot String> list = - // :: warning: (cast.unsafe.constructor.invocation) - new @H1Bot ArrayList<@H1Bot String>(); - } + void testDiamond() { + @H1Bot ArrayList<@H1Bot String> list = + // :: warning: (cast.unsafe.constructor.invocation) + new @H1Bot ArrayList<@H1Bot String>(); + } } diff --git a/framework/tests/h1h2checker/Issue2264.java b/framework/tests/h1h2checker/Issue2264.java index 81b9bc21a42..0ef2605d902 100644 --- a/framework/tests/h1h2checker/Issue2264.java +++ b/framework/tests/h1h2checker/Issue2264.java @@ -5,23 +5,23 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Top; public class Issue2264 extends SuperClass { - // :: warning: (inconsistent.constructor.type) - @H1S1 Issue2264() { - // :: error: (super.invocation.invalid) - super(9); - } + // :: warning: (inconsistent.constructor.type) + @H1S1 Issue2264() { + // :: error: (super.invocation.invalid) + super(9); + } } class ImplicitSuperCall { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @H1S1 ImplicitSuperCall() {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @H1S1 ImplicitSuperCall() {} } class SuperClass { - @H1Top SuperClass(int x) {} + @H1Top SuperClass(int x) {} } @H1S1 class TestClass { - // :: error: (type.invalid.annotations.on.use) - @H1Top TestClass() {} + // :: error: (type.invalid.annotations.on.use) + @H1Top TestClass() {} } diff --git a/framework/tests/h1h2checker/Issue282.java b/framework/tests/h1h2checker/Issue282.java index bb7c5540874..c699877351d 100644 --- a/framework/tests/h1h2checker/Issue282.java +++ b/framework/tests/h1h2checker/Issue282.java @@ -1,90 +1,90 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Issue282 { - // Declared constructor type is not consistent with default from class. - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @H1S1 Issue282() {} - - public class Inner { - Inner(@H2S2 Issue282 Issue282.this) {} - - // Test anonymous constructor without varargs - Inner(@H2S2 Issue282 Issue282.this, String s) {} - - Inner(@H2S2 Issue282 Issue282.this, int... i) {} - - Inner(@H2S2 Issue282 Issue282.this, Issue282 o) {} - - Inner(@H2S2 Issue282 Issue282.this, Issue282... o) {} - } - - @SuppressWarnings({"cast.unsafe.constructor.invocation"}) - public void test1() { - Inner inner1 = new @H2S2 Issue282().new Inner() {}; - Inner inner2 = new @H2S2 Issue282().new Inner(); - // The enclosing type is @H1S1 @H2Top, while the required type is @H1Top @H2S2 - // :: error: (enclosingexpr.type.incompatible) - Inner inner3 = new Issue282().new Inner() {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner4 = new Issue282().new Inner(); - - // test non-varargs - Inner inner5 = new @H2S2 Issue282().new Inner("s") {}; - Inner inner6 = new @H2S2 Issue282().new Inner("s"); - // :: error: (enclosingexpr.type.incompatible) - Inner inner7 = new Issue282().new Inner("s") {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner8 = new Issue282().new Inner("s"); - - // test varargs - Inner inner9 = new @H2S2 Issue282().new Inner(1, 2, 3) {}; - Inner inner10 = new @H2S2 Issue282().new Inner(1, 2, 3); - // :: error: (enclosingexpr.type.incompatible) - Inner inner11 = new Issue282().new Inner(1, 2, 3) {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner12 = new Issue282().new Inner(1, 2, 3); - - // test varargs with the same type of receiver - Inner inner13 = new @H2S2 Issue282().new Inner(this, this, this) {}; - Inner inner14 = new @H2S2 Issue282().new Inner(this, this, this); - // :: error: (enclosingexpr.type.incompatible) - Inner inner15 = new Issue282().new Inner(this, this, this) {}; - // :: error: (enclosingexpr.type.incompatible) - Inner inner16 = new Issue282().new Inner(this, this, this); - } - - class Issue282Sub extends Issue282 {} - - public void test2() { - // found: @H1Top @H2Top Issue282.@H1Top @H2Top Issue282Sub. required: @H1Top @H2S2 Issue282 - // :: error: (enclosingexpr.type.incompatible) - Inner inner = new Issue282Sub().new Inner(); - } - - public static void testStatic() { - // :: error: (enclosingexpr.type.incompatible) - new Issue282().new Inner() {}; - } - - class InnerGeneric { - @SuppressWarnings("unchecked") - InnerGeneric(T... t) {} - } - - public void test3(@H1S1 String a, @H1S1 String b, @H1S2 String c) { - new InnerGeneric<@H1S1 String>(a, b); - // found: @H1S2 @H2Top String. required: @H1S1 @H2Top String - // :: error: (argument.type.incompatible) - new InnerGeneric<@H1S1 String>(a, c); - } + // Declared constructor type is not consistent with default from class. + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @H1S1 Issue282() {} + + public class Inner { + Inner(@H2S2 Issue282 Issue282.this) {} + + // Test anonymous constructor without varargs + Inner(@H2S2 Issue282 Issue282.this, String s) {} + + Inner(@H2S2 Issue282 Issue282.this, int... i) {} + + Inner(@H2S2 Issue282 Issue282.this, Issue282 o) {} + + Inner(@H2S2 Issue282 Issue282.this, Issue282... o) {} + } + + @SuppressWarnings({"cast.unsafe.constructor.invocation"}) + public void test1() { + Inner inner1 = new @H2S2 Issue282().new Inner() {}; + Inner inner2 = new @H2S2 Issue282().new Inner(); + // The enclosing type is @H1S1 @H2Top, while the required type is @H1Top @H2S2 + // :: error: (enclosingexpr.type.incompatible) + Inner inner3 = new Issue282().new Inner() {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner4 = new Issue282().new Inner(); + + // test non-varargs + Inner inner5 = new @H2S2 Issue282().new Inner("s") {}; + Inner inner6 = new @H2S2 Issue282().new Inner("s"); + // :: error: (enclosingexpr.type.incompatible) + Inner inner7 = new Issue282().new Inner("s") {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner8 = new Issue282().new Inner("s"); + + // test varargs + Inner inner9 = new @H2S2 Issue282().new Inner(1, 2, 3) {}; + Inner inner10 = new @H2S2 Issue282().new Inner(1, 2, 3); + // :: error: (enclosingexpr.type.incompatible) + Inner inner11 = new Issue282().new Inner(1, 2, 3) {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner12 = new Issue282().new Inner(1, 2, 3); + + // test varargs with the same type of receiver + Inner inner13 = new @H2S2 Issue282().new Inner(this, this, this) {}; + Inner inner14 = new @H2S2 Issue282().new Inner(this, this, this); + // :: error: (enclosingexpr.type.incompatible) + Inner inner15 = new Issue282().new Inner(this, this, this) {}; + // :: error: (enclosingexpr.type.incompatible) + Inner inner16 = new Issue282().new Inner(this, this, this); + } + + class Issue282Sub extends Issue282 {} + + public void test2() { + // found: @H1Top @H2Top Issue282.@H1Top @H2Top Issue282Sub. required: @H1Top @H2S2 Issue282 + // :: error: (enclosingexpr.type.incompatible) + Inner inner = new Issue282Sub().new Inner(); + } + + public static void testStatic() { + // :: error: (enclosingexpr.type.incompatible) + new Issue282().new Inner() {}; + } + + class InnerGeneric { + @SuppressWarnings("unchecked") + InnerGeneric(T... t) {} + } + + public void test3(@H1S1 String a, @H1S1 String b, @H1S2 String c) { + new InnerGeneric<@H1S1 String>(a, b); + // found: @H1S2 @H2Top String. required: @H1S1 @H2Top String + // :: error: (argument.type.incompatible) + new InnerGeneric<@H1S1 String>(a, c); + } } class Top { - void test(@H1Top @H2S2 Issue282 outer) { - outer.new Inner() {}; - outer.new Inner("s") {}; - outer.new Inner(1, 2, 3) {}; - outer.new Inner(outer) {}; - outer.new Inner(outer, outer, outer) {}; - } + void test(@H1Top @H2S2 Issue282 outer) { + outer.new Inner() {}; + outer.new Inner("s") {}; + outer.new Inner(1, 2, 3) {}; + outer.new Inner(outer) {}; + outer.new Inner(outer, outer, outer) {}; + } } diff --git a/framework/tests/h1h2checker/Issue681.java b/framework/tests/h1h2checker/Issue681.java index 56ce83a938f..7cef99ccd0a 100644 --- a/framework/tests/h1h2checker/Issue681.java +++ b/framework/tests/h1h2checker/Issue681.java @@ -38,14 +38,14 @@ * */ public class Issue681 { - class Inner { - @H1S2 Inner explicitH1S2; - Issue681.@H1S2 Inner explicitNestedH1S2; - @H1S2 Issue681.Inner explicitOneOuterH1S2; - Inner addH1S2; + class Inner { + @H1S2 Inner explicitH1S2; + Issue681.@H1S2 Inner explicitNestedH1S2; + @H1S2 Issue681.Inner explicitOneOuterH1S2; + Inner addH1S2; - @H1S2 Inner method(@H1S2 Inner paramExplicit, Inner nonAnno) { - return paramExplicit; + @H1S2 Inner method(@H1S2 Inner paramExplicit, Inner nonAnno) { + return paramExplicit; + } } - } } diff --git a/framework/tests/h1h2checker/Issue798.java b/framework/tests/h1h2checker/Issue798.java index 2c7fc54849d..db9a6ce286f 100644 --- a/framework/tests/h1h2checker/Issue798.java +++ b/framework/tests/h1h2checker/Issue798.java @@ -4,17 +4,17 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Issue798 { - void test1(String format, @H1S1 Object @H1S2 ... args) { - String.format(format, args); - } + void test1(String format, @H1S1 Object @H1S2 ... args) { + String.format(format, args); + } - void test2(String format, @H1S1 Object @H1S1 ... args) { - // :: error: (argument.type.incompatible) - String.format(format, args); - } + void test2(String format, @H1S1 Object @H1S1 ... args) { + // :: error: (argument.type.incompatible) + String.format(format, args); + } - void test3(String format, @H1S2 Object @H1S2 ... args) { - // :: error: (argument.type.incompatible) - String.format(format, args); - } + void test3(String format, @H1S2 Object @H1S2 ... args) { + // :: error: (argument.type.incompatible) + String.format(format, args); + } } diff --git a/framework/tests/h1h2checker/Issue849.java b/framework/tests/h1h2checker/Issue849.java index 6695e72165c..37707ce1635 100644 --- a/framework/tests/h1h2checker/Issue849.java +++ b/framework/tests/h1h2checker/Issue849.java @@ -5,10 +5,10 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Top; public class Issue849 { - class Gen {} + class Gen {} - void polyAll(Gen> genGenNonNull) { - // :: error: (assignment.type.incompatible) - Gen<@H1Top ? extends @H1Top Gen<@H1Top Object>> a = genGenNonNull; - } + void polyAll(Gen> genGenNonNull) { + // :: error: (assignment.type.incompatible) + Gen<@H1Top ? extends @H1Top Gen<@H1Top Object>> a = genGenNonNull; + } } diff --git a/framework/tests/h1h2checker/Primitive.java b/framework/tests/h1h2checker/Primitive.java index a31f500fc27..c002cd43a21 100644 --- a/framework/tests/h1h2checker/Primitive.java +++ b/framework/tests/h1h2checker/Primitive.java @@ -1,19 +1,19 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.*; public class Primitive { - @SuppressWarnings("type.incompatible") - @H1S2 int o = 4; + @SuppressWarnings("type.incompatible") + @H1S2 int o = 4; - @H1S2 @H2Poly int m(@H1S2 @H2Poly int p) { - return p; - } + @H1S2 @H2Poly int m(@H1S2 @H2Poly int p) { + return p; + } - void use1(@H1S2 @H2S1 int p) { - @H1S2 @H2S1 int l = m(p); - } + void use1(@H1S2 @H2S1 int p) { + @H1S2 @H2S1 int l = m(p); + } - void use2(@H1S2 @H2S2 int p) { - // :: error: (assignment.type.incompatible) - @H1S2 @H2S1 int l = m(p); - } + void use2(@H1S2 @H2S2 int p) { + // :: error: (assignment.type.incompatible) + @H1S2 @H2S1 int l = m(p); + } } diff --git a/framework/tests/h1h2checker/TypeRefinement.java b/framework/tests/h1h2checker/TypeRefinement.java index 0fabe4ffaff..b821903d1bf 100644 --- a/framework/tests/h1h2checker/TypeRefinement.java +++ b/framework/tests/h1h2checker/TypeRefinement.java @@ -2,17 +2,17 @@ import org.checkerframework.framework.testchecker.h1h2checker.quals.H1Invalid; public class TypeRefinement { - // :: warning: (cast.unsafe.constructor.invocation) - @H1Top Object o = new @H1S1 Object(); - // :: error: (h1h2checker.h1invalid.forbidden) :: warning: (cast.unsafe.constructor.invocation) - @H1Top Object o2 = new @H1Invalid Object(); - // :: error: (h1h2checker.h1invalid.forbidden) - @H1Top Object o3 = getH1Invalid(); + // :: warning: (cast.unsafe.constructor.invocation) + @H1Top Object o = new @H1S1 Object(); + // :: error: (h1h2checker.h1invalid.forbidden) :: warning: (cast.unsafe.constructor.invocation) + @H1Top Object o2 = new @H1Invalid Object(); + // :: error: (h1h2checker.h1invalid.forbidden) + @H1Top Object o3 = getH1Invalid(); - // :: error: (h1h2checker.h1invalid.forbidden) - @H1Invalid Object getH1Invalid() { - // :: error: (h1h2checker.h1invalid.forbidden) :: warning: - // (cast.unsafe.constructor.invocation) - return new @H1Invalid Object(); - } + // :: error: (h1h2checker.h1invalid.forbidden) + @H1Invalid Object getH1Invalid() { + // :: error: (h1h2checker.h1invalid.forbidden) :: warning: + // (cast.unsafe.constructor.invocation) + return new @H1Invalid Object(); + } } diff --git a/framework/tests/h1h2checker/WildcardBounds.java b/framework/tests/h1h2checker/WildcardBounds.java index cbc0e62b6f0..e2fa5f62bb4 100644 --- a/framework/tests/h1h2checker/WildcardBounds.java +++ b/framework/tests/h1h2checker/WildcardBounds.java @@ -3,123 +3,126 @@ class WildcardBounds { - abstract class OuterTop { - abstract T get(); - - abstract class Inner { - abstract U get(); - - abstract class Chain { - abstract W get(); - } - - @H1S1 Object m0(Chain p) { - return p.get(); - } + abstract class OuterTop { + abstract T get(); + + abstract class Inner { + abstract U get(); + + abstract class Chain { + abstract W get(); + } + + @H1S1 Object m0(Chain p) { + return p.get(); + } + + @H1S1 Object m1(Chain p) { + return p.get(); + } + + @H1S1 Object m2(Chain p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m3(Chain p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m4(Chain<@H1S1 ?, @H1S1 ?> p) { + return p.get(); + } + + void callsS1( + OuterTop<@H1S1 Object>.Inner<@H1S1 Number> i, + OuterTop<@H1S1 Object>.Inner<@H1S1 Number>.Chain<@H1S1 Integer, @H1S1 Integer> + n) { + i.m0(n); + i.m1(n); + i.m2(n); + i.m3(n); + i.m4(n); + } + + void callsTop( + OuterTop<@H1Top Object>.Inner<@H1Top Number> i, + OuterTop<@H1Top Object>.Inner<@H1Top Number>.Chain< + @H1Top Integer, @H1Top Integer> + n) { + // :: error: (argument.type.incompatible) + i.m0(n); + // :: error: (argument.type.incompatible) + i.m1(n); + // OK + i.m2(n); + // OK + i.m3(n); + // :: error: (argument.type.incompatible) + i.m4(n); + } + } + + @H1S1 Object m0(Inner p) { + return p.get(); + } + + @H1S1 Object m1(Inner p) { + return p.get(); + } + + @H1S1 Object m2(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m3(Inner p) { + // :: error: (return.type.incompatible) + return p.get(); + } + + @H1S1 Object m4(Inner<@H1S1 ?> p) { + return p.get(); + } + + // We could add calls for these methods. + } - @H1S1 Object m1(Chain p) { + @H1S1 Object m0(OuterTop p) { return p.get(); - } + } - @H1S1 Object m2(Chain p) { + @H1S1 Object m1(OuterTop p) { // :: error: (return.type.incompatible) return p.get(); - } + } - @H1S1 Object m3(Chain p) { + @H1S1 Object m2(OuterTop p) { // :: error: (return.type.incompatible) return p.get(); - } + } - @H1S1 Object m4(Chain<@H1S1 ?, @H1S1 ?> p) { + @H1S1 Object m3(OuterTop<@H1S1 ?> p) { return p.get(); - } - - void callsS1( - OuterTop<@H1S1 Object>.Inner<@H1S1 Number> i, - OuterTop<@H1S1 Object>.Inner<@H1S1 Number>.Chain<@H1S1 Integer, @H1S1 Integer> n) { - i.m0(n); - i.m1(n); - i.m2(n); - i.m3(n); - i.m4(n); - } - - void callsTop( - OuterTop<@H1Top Object>.Inner<@H1Top Number> i, - OuterTop<@H1Top Object>.Inner<@H1Top Number>.Chain<@H1Top Integer, @H1Top Integer> n) { - // :: error: (argument.type.incompatible) - i.m0(n); + } + + void callsOuter(OuterTop<@H1S1 String> s, OuterTop<@H1Top String> ns) { + m0(s); + m1(s); + m2(s); + m3(s); + // :: error: (argument.type.incompatible) - i.m1(n); + m0(ns); // OK - i.m2(n); + m1(ns); // OK - i.m3(n); + m2(ns); // :: error: (argument.type.incompatible) - i.m4(n); - } - } - - @H1S1 Object m0(Inner p) { - return p.get(); - } - - @H1S1 Object m1(Inner p) { - return p.get(); - } - - @H1S1 Object m2(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m3(Inner p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m4(Inner<@H1S1 ?> p) { - return p.get(); + m3(ns); } - // We could add calls for these methods. - } - - @H1S1 Object m0(OuterTop p) { - return p.get(); - } - - @H1S1 Object m1(OuterTop p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m2(OuterTop p) { - // :: error: (return.type.incompatible) - return p.get(); - } - - @H1S1 Object m3(OuterTop<@H1S1 ?> p) { - return p.get(); - } - - void callsOuter(OuterTop<@H1S1 String> s, OuterTop<@H1Top String> ns) { - m0(s); - m1(s); - m2(s); - m3(s); - - // :: error: (argument.type.incompatible) - m0(ns); - // OK - m1(ns); - // OK - m2(ns); - // :: error: (argument.type.incompatible) - m3(ns); - } - - // We could add an OuterS1 to also test with a non-top upper bound. - // But we probably already test that enough. + // We could add an OuterS1 to also test with a non-top upper bound. + // But we probably already test that enough. } diff --git a/framework/tests/h1h2checker/pkg1/PackageDefaulting.java b/framework/tests/h1h2checker/pkg1/PackageDefaulting.java index 81c9bc75d79..33cf24071a5 100644 --- a/framework/tests/h1h2checker/pkg1/PackageDefaulting.java +++ b/framework/tests/h1h2checker/pkg1/PackageDefaulting.java @@ -7,17 +7,17 @@ /* Defaults from package pkg1 apply within the package. */ public class PackageDefaulting { - // Test H1 hierarchy - void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } + // Test H1 hierarchy + void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } - // Test H2 hierarchy - void m2(@H1S1 @H2S1 Object p1, @H1S1 @H2S2 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } + // Test H2 hierarchy + void m2(@H1S1 @H2S1 Object p1, @H1S1 @H2S2 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } } diff --git a/framework/tests/h1h2checker/pkg1/package-info.java b/framework/tests/h1h2checker/pkg1/package-info.java index 616b07aef5f..cdcca1c228a 100644 --- a/framework/tests/h1h2checker/pkg1/package-info.java +++ b/framework/tests/h1h2checker/pkg1/package-info.java @@ -1,11 +1,11 @@ @DefaultQualifier( - value = H1S1.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}, - applyToSubpackages = false) + value = H1S1.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}, + applyToSubpackages = false) @DefaultQualifier( - value = H2S1.class, - locations = {TypeUseLocation.LOCAL_VARIABLE}, - applyToSubpackages = true) + value = H2S1.class, + locations = {TypeUseLocation.LOCAL_VARIABLE}, + applyToSubpackages = true) package pkg1; import org.checkerframework.framework.qual.DefaultQualifier; diff --git a/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java b/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java index 8f0f50dba07..511f2aa0a70 100644 --- a/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java +++ b/framework/tests/h1h2checker/pkg1/pkg2/PackageDefaulting.java @@ -7,16 +7,16 @@ /* Default from package pkg1 for H1 is not applied to subpackages, whereas H2 is applied. */ public class PackageDefaulting { - // Test H1 hierarchy - void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { - Object l1 = p1; - Object l2 = p2; - } + // Test H1 hierarchy + void m(@H1S1 @H2S1 Object p1, @H1S2 @H2S1 Object p2) { + Object l1 = p1; + Object l2 = p2; + } - // Test H2 hierarchy - void m2(@H2S1 Object p1, @H2S2 Object p2) { - Object l1 = p1; - // :: error: (assignment.type.incompatible) - Object l2 = p2; - } + // Test H2 hierarchy + void m2(@H2S1 Object p1, @H2S2 Object p2) { + Object l1 = p1; + // :: error: (assignment.type.incompatible) + Object l2 = p2; + } } diff --git a/framework/tests/initialized-fields-value/ClassInitializer.java b/framework/tests/initialized-fields-value/ClassInitializer.java index 7f816813af7..3af64347d7d 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer.java +++ b/framework/tests/initialized-fields-value/ClassInitializer.java @@ -2,22 +2,22 @@ public class ClassInitializer { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - y = 2; - } + { + y = 2; + } - ClassInitializer() { - x = 1; - } + ClassInitializer() { + x = 1; + } - ClassInitializer(boolean ignore) { - x = 1; - z = 3; - } + ClassInitializer(boolean ignore) { + x = 1; + z = 3; + } } diff --git a/framework/tests/initialized-fields-value/ClassInitializer2.java b/framework/tests/initialized-fields-value/ClassInitializer2.java index a25c53506c9..ca5530c414e 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer2.java +++ b/framework/tests/initialized-fields-value/ClassInitializer2.java @@ -2,19 +2,19 @@ public class ClassInitializer2 { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - x = 1; - } + { + x = 1; + } - { - y = 2; - } + { + y = 2; + } - ClassInitializer2() {} + ClassInitializer2() {} } diff --git a/framework/tests/initialized-fields-value/ClassInitializer2a.java b/framework/tests/initialized-fields-value/ClassInitializer2a.java index f905f517726..1071109f163 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer2a.java +++ b/framework/tests/initialized-fields-value/ClassInitializer2a.java @@ -2,16 +2,16 @@ public class ClassInitializer2a { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - x = 1; - } + { + x = 1; + } - // :: error: (contracts.postcondition.not.satisfied) - ClassInitializer2a() {} + // :: error: (contracts.postcondition.not.satisfied) + ClassInitializer2a() {} } diff --git a/framework/tests/initialized-fields-value/ClassInitializer3.java b/framework/tests/initialized-fields-value/ClassInitializer3.java index bf9da2a22b7..d830451ff40 100644 --- a/framework/tests/initialized-fields-value/ClassInitializer3.java +++ b/framework/tests/initialized-fields-value/ClassInitializer3.java @@ -2,23 +2,23 @@ public class ClassInitializer3 { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - { - if (Math.random() < 0) { - x = 1; - } else { - x = 1; + { + if (Math.random() < 0) { + x = 1; + } else { + x = 1; + } } - } - { - y = 2; - } + { + y = 2; + } - ClassInitializer3() {} + ClassInitializer3() {} } diff --git a/framework/tests/initialized-fields-value/DeclaredType.java b/framework/tests/initialized-fields-value/DeclaredType.java index 41bf1a08e46..4a758cc6a78 100644 --- a/framework/tests/initialized-fields-value/DeclaredType.java +++ b/framework/tests/initialized-fields-value/DeclaredType.java @@ -1,6 +1,7 @@ -import java.util.List; import org.checkerframework.common.value.qual.IntVal; +import java.util.List; + public class DeclaredType { - List field; + List field; } diff --git a/framework/tests/initialized-fields-value/ManualConstructor.java b/framework/tests/initialized-fields-value/ManualConstructor.java index a041ddb4623..ee22d1da59f 100644 --- a/framework/tests/initialized-fields-value/ManualConstructor.java +++ b/framework/tests/initialized-fields-value/ManualConstructor.java @@ -2,31 +2,31 @@ public class ManualConstructor { - @IntVal(1) int x; + @IntVal(1) int x; - @IntVal(2) int y; + @IntVal(2) int y; - int z; + int z; - // :: error: (contracts.postcondition.not.satisfied) - ManualConstructor() { - x = 1; - } + // :: error: (contracts.postcondition.not.satisfied) + ManualConstructor() { + x = 1; + } - // :: error: (contracts.postcondition.not.satisfied) - ManualConstructor(boolean ignore) { - x = 1; - z = 3; - } + // :: error: (contracts.postcondition.not.satisfied) + ManualConstructor(boolean ignore) { + x = 1; + z = 3; + } - ManualConstructor(float ignore) { - x = 1; - y = 2; - } + ManualConstructor(float ignore) { + x = 1; + y = 2; + } - ManualConstructor(double ignore) { - x = 1; - y = 2; - z = 3; - } + ManualConstructor(double ignore) { + x = 1; + y = 2; + z = 3; + } } diff --git a/framework/tests/initialized-fields-value/PrimitiveField.java b/framework/tests/initialized-fields-value/PrimitiveField.java index d7e46e0f038..7759409a520 100644 --- a/framework/tests/initialized-fields-value/PrimitiveField.java +++ b/framework/tests/initialized-fields-value/PrimitiveField.java @@ -3,68 +3,68 @@ import org.checkerframework.common.value.qual.IntVal; public class PrimitiveField { - boolean b; - byte by; - char c; - int i; - short s; - float f; - double d; - long l; + boolean b; + byte by; + char c; + int i; + short s; + float f; + double d; + long l; - @BoolVal(false) boolean b2; + @BoolVal(false) boolean b2; - @IntVal(0) byte by2; + @IntVal(0) byte by2; - @IntVal(0) char c2; + @IntVal(0) char c2; - @IntVal(0) int i2; + @IntVal(0) int i2; - @IntVal(0) short s2; + @IntVal(0) short s2; - @DoubleVal(0.0) float f2; + @DoubleVal(0.0) float f2; - @DoubleVal(0.0) double d2; + @DoubleVal(0.0) double d2; - @IntVal(0) long l2; + @IntVal(0) long l2; - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk1 { - @BoolVal(true) boolean b2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk1 { + @BoolVal(true) boolean b2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk2 { - @IntVal(1) byte by2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk2 { + @IntVal(1) byte by2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk3 { - @IntVal(1) char c2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk3 { + @IntVal(1) char c2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk4 { - @IntVal(1) int i2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk4 { + @IntVal(1) int i2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk5 { - @IntVal(1) short s2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk5 { + @IntVal(1) short s2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk6 { - @DoubleVal(1.0) float f2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk6 { + @DoubleVal(1.0) float f2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk7 { - @DoubleVal(1.0) double d2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk7 { + @DoubleVal(1.0) double d2; + } - // :: error: (contracts.postcondition.not.satisfied) - static class InitValueNotOk8 { - @IntVal(2) long l2; - } + // :: error: (contracts.postcondition.not.satisfied) + static class InitValueNotOk8 { + @IntVal(2) long l2; + } } diff --git a/framework/tests/initialized-fields/ConstructorPostcondition.java b/framework/tests/initialized-fields/ConstructorPostcondition.java index 93d5446078a..740addef96b 100644 --- a/framework/tests/initialized-fields/ConstructorPostcondition.java +++ b/framework/tests/initialized-fields/ConstructorPostcondition.java @@ -2,19 +2,19 @@ public class ConstructorPostcondition { - int x; - int y; - int z; + int x; + int y; + int z; - ConstructorPostcondition() { - x = 1; - y = 2; - z = 3; - } + ConstructorPostcondition() { + x = 1; + y = 2; + z = 3; + } - // :: error: (contracts.postcondition.not.satisfied) - ConstructorPostcondition(int ignore) { - x = 1; - y = 2; - } + // :: error: (contracts.postcondition.not.satisfied) + ConstructorPostcondition(int ignore) { + x = 1; + y = 2; + } } diff --git a/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java b/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java index 8561f59162c..5619a96a081 100644 --- a/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java +++ b/framework/tests/initialized-fields/EnsuresInitializedFieldsTest.java @@ -4,76 +4,76 @@ public class EnsuresInitializedFieldsTest { - int x; - int y; - int z; + int x; + int y; + int z; - EnsuresInitializedFieldsTest() { - x = 1; - y = 2; - z = 3; - } + EnsuresInitializedFieldsTest() { + x = 1; + y = 2; + z = 3; + } - @EnsuresInitializedFields( - value = "this", - fields = {"x", "y"}) - // :: error: (contracts.postcondition.not.satisfied) - void setsX() { - x = 1; - } + @EnsuresInitializedFields( + value = "this", + fields = {"x", "y"}) + // :: error: (contracts.postcondition.not.satisfied) + void setsX() { + x = 1; + } - @EnsuresInitializedFields(fields = {"x", "y"}) - // :: error: (contracts.postcondition.not.satisfied) - void setsX2() { - x = 1; - } + @EnsuresInitializedFields(fields = {"x", "y"}) + // :: error: (contracts.postcondition.not.satisfied) + void setsX2() { + x = 1; + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - // :: error: (contracts.postcondition.not.satisfied) - void setsX(EnsuresInitializedFieldsTest eift) { - eift.x = 1; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + // :: error: (contracts.postcondition.not.satisfied) + void setsX(EnsuresInitializedFieldsTest eift) { + eift.x = 1; + } - @EnsuresInitializedFields( - value = "this", - fields = {"x", "y"}) - void setsXY() { - x = 1; - y = 2; - } + @EnsuresInitializedFields( + value = "this", + fields = {"x", "y"}) + void setsXY() { + x = 1; + y = 2; + } - @EnsuresInitializedFields(fields = {"x", "y"}) - void setsXY2() { - x = 1; - y = 2; - } + @EnsuresInitializedFields(fields = {"x", "y"}) + void setsXY2() { + x = 1; + y = 2; + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - void setsXY(EnsuresInitializedFieldsTest eift) { - eift.x = 1; - eift.y = 2; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + void setsXY(EnsuresInitializedFieldsTest eift) { + eift.x = 1; + eift.y = 2; + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - void setsXY2(EnsuresInitializedFieldsTest eift) { - setsXY(eift); - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + void setsXY2(EnsuresInitializedFieldsTest eift) { + setsXY(eift); + } - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - @EnsuresInitializedFields( - value = "#2", - fields = {"x", "z"}) - void setsXY2(EnsuresInitializedFieldsTest eift1, EnsuresInitializedFieldsTest eift2) { - setsXY(eift1); - setsX(eift2); - eift2.z = 3; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + @EnsuresInitializedFields( + value = "#2", + fields = {"x", "z"}) + void setsXY2(EnsuresInitializedFieldsTest eift1, EnsuresInitializedFieldsTest eift2) { + setsXY(eift1); + setsX(eift2); + eift2.z = 3; + } } diff --git a/framework/tests/initialized-fields/HelperMethodInitializesFields.java b/framework/tests/initialized-fields/HelperMethodInitializesFields.java index 608537b06e2..239bdfc9a2b 100644 --- a/framework/tests/initialized-fields/HelperMethodInitializesFields.java +++ b/framework/tests/initialized-fields/HelperMethodInitializesFields.java @@ -3,186 +3,186 @@ public class HelperMethodInitializesFields { - int x; - int y; - int z; - - HelperMethodInitializesFields(int ignore) { - helperMethodXY(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore, String ignore2) { - helperMethodXY2(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(long ignore) { - this.helperMethodXY(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(long ignore, String ignore2) { - this.helperMethodXY2(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(float ignore) { - staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(double ignore) { - this.staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(boolean ignore) { - new OtherClass().helperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(char ignore) { - new OtherClass().helperMethodXY2(0, this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, byte ignore2) { - new OtherClass().staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, short ignore2) { - new OtherClass().staticHelperMethodXY2(0, this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, int ignore2) { - OtherClass.staticHelperMethodXY(this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - HelperMethodInitializesFields(int ignore1, long ignore2) { - OtherClass.staticHelperMethodXY2(0, this); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - // Simple tests of LUB - - HelperMethodInitializesFields(boolean ignore1, int ignore) { - z = 3; - helperMethodXY(); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, char ignore) { - z = 3; - helperMethodXY2(); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, float ignore) { - z = 3; - staticHelperMethodXY(this); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, boolean ignore) { - z = 3; - new OtherClass().helperMethodXY(this); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(boolean ignore1, short ignore2) { - z = 3; - OtherClass.staticHelperMethodXY(this); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - // More complex tests of LUB - - HelperMethodInitializesFields(byte ignore1, int ignore) { - y = 2; - z = 3; - helperMethodXY(); - @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; - } - - HelperMethodInitializesFields(byte ignore1, long ignore) { - y = 2; - helperMethodXY(); - @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; - z = 3; - } - - // The helper methods - - @EnsuresInitializedFields( - value = "this", - fields = {"x", "y"}) - void helperMethodXY() { - x = 1; - this.y = 1; - } - - @EnsuresInitializedFields(fields = {"x", "y"}) - void helperMethodXY2() { - x = 1; - this.y = 1; - } - - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } + int x; + int y; + int z; + + HelperMethodInitializesFields(int ignore) { + helperMethodXY(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore, String ignore2) { + helperMethodXY2(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(long ignore) { + this.helperMethodXY(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(long ignore, String ignore2) { + this.helperMethodXY2(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(float ignore) { + staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(double ignore) { + this.staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(boolean ignore) { + new OtherClass().helperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(char ignore) { + new OtherClass().helperMethodXY2(0, this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, byte ignore2) { + new OtherClass().staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, short ignore2) { + new OtherClass().staticHelperMethodXY2(0, this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, int ignore2) { + OtherClass.staticHelperMethodXY(this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + HelperMethodInitializesFields(int ignore1, long ignore2) { + OtherClass.staticHelperMethodXY2(0, this); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + // Simple tests of LUB + + HelperMethodInitializesFields(boolean ignore1, int ignore) { + z = 3; + helperMethodXY(); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, char ignore) { + z = 3; + helperMethodXY2(); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, float ignore) { + z = 3; + staticHelperMethodXY(this); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, boolean ignore) { + z = 3; + new OtherClass().helperMethodXY(this); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(boolean ignore1, short ignore2) { + z = 3; + OtherClass.staticHelperMethodXY(this); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + // More complex tests of LUB + + HelperMethodInitializesFields(byte ignore1, int ignore) { + y = 2; + z = 3; + helperMethodXY(); + @InitializedFields({"x", "y", "z"}) HelperMethodInitializesFields hmif2 = this; + } + + HelperMethodInitializesFields(byte ignore1, long ignore) { + y = 2; + helperMethodXY(); + @InitializedFields({"x", "y"}) HelperMethodInitializesFields hmif2 = this; + z = 3; + } + + // The helper methods + + @EnsuresInitializedFields( + value = "this", + fields = {"x", "y"}) + void helperMethodXY() { + x = 1; + this.y = 1; + } + + @EnsuresInitializedFields(fields = {"x", "y"}) + void helperMethodXY2() { + x = 1; + this.y = 1; + } + + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } } class OtherClass { - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - void helperMethodXY(HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } - - @EnsuresInitializedFields( - value = "#2", - fields = {"x", "y"}) - void helperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } - - @EnsuresInitializedFields( - value = "#1", - fields = {"x", "y"}) - static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } - - @EnsuresInitializedFields( - value = "#2", - fields = {"x", "y"}) - static void staticHelperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { - hmif.x = 1; - hmif.y = 1; - } + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + void helperMethodXY(HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } + + @EnsuresInitializedFields( + value = "#2", + fields = {"x", "y"}) + void helperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } + + @EnsuresInitializedFields( + value = "#1", + fields = {"x", "y"}) + static void staticHelperMethodXY(HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } + + @EnsuresInitializedFields( + value = "#2", + fields = {"x", "y"}) + static void staticHelperMethodXY2(int ignore, HelperMethodInitializesFields hmif) { + hmif.x = 1; + hmif.y = 1; + } } diff --git a/framework/tests/initialized-fields/SimpleConstructor.java b/framework/tests/initialized-fields/SimpleConstructor.java index 7563549c594..b2caebacfec 100644 --- a/framework/tests/initialized-fields/SimpleConstructor.java +++ b/framework/tests/initialized-fields/SimpleConstructor.java @@ -2,28 +2,28 @@ public class SimpleConstructor { - int x; - int y; - int z; + int x; + int y; + int z; - SimpleConstructor() { - // :: error: (assignment.type.incompatible) - @InitializedFields({"x", "y", "z"}) SimpleConstructor sc1 = this; - @InitializedFields() SimpleConstructor sc2 = this; + SimpleConstructor() { + // :: error: (assignment.type.incompatible) + @InitializedFields({"x", "y", "z"}) SimpleConstructor sc1 = this; + @InitializedFields() SimpleConstructor sc2 = this; - x = 1; + x = 1; - // :: error: (assignment.type.incompatible) - @InitializedFields({"x", "y", "z"}) SimpleConstructor sc3 = this; - @InitializedFields({"x"}) SimpleConstructor sc4 = this; + // :: error: (assignment.type.incompatible) + @InitializedFields({"x", "y", "z"}) SimpleConstructor sc3 = this; + @InitializedFields({"x"}) SimpleConstructor sc4 = this; - this.y = 1; + this.y = 1; - // :: error: (assignment.type.incompatible) - @InitializedFields({"x", "y", "z"}) SimpleConstructor sc5 = this; - @InitializedFields({"x", "y"}) SimpleConstructor sc6 = this; - @InitializedFields({"y", "x"}) SimpleConstructor sc7 = this; + // :: error: (assignment.type.incompatible) + @InitializedFields({"x", "y", "z"}) SimpleConstructor sc5 = this; + @InitializedFields({"x", "y"}) SimpleConstructor sc6 = this; + @InitializedFields({"y", "x"}) SimpleConstructor sc7 = this; - z = 3; - } + z = 3; + } } diff --git a/framework/tests/lubglb/Dummy.java b/framework/tests/lubglb/Dummy.java index edd55f3231f..41aedead81c 100644 --- a/framework/tests/lubglb/Dummy.java +++ b/framework/tests/lubglb/Dummy.java @@ -1,4 +1,4 @@ public class Dummy { - // We don't need any code here. - // The actual tests are performed within the LubglbChecker using assert statements. + // We don't need any code here. + // The actual tests are performed within the LubglbChecker using assert statements. } diff --git a/framework/tests/lubglb/IntersectionTypes.java b/framework/tests/lubglb/IntersectionTypes.java index d4c3490c25b..57e166db584 100644 --- a/framework/tests/lubglb/IntersectionTypes.java +++ b/framework/tests/lubglb/IntersectionTypes.java @@ -7,25 +7,25 @@ interface Bar {} class Baz implements Foo, Bar {} public class IntersectionTypes { - // :: warning: (explicit.annotation.ignored) - void call1(S p) {} - - // :: warning: (explicit.annotation.ignored) - void call2(T p) {} - - void foo1(@LubglbD Baz baz1) { - call1(baz1); - call2(baz1); - } - - void foo2(@LubglbF Baz baz2) { - call1(baz2); - call2(baz2); - } - - void foo3(@LubglbB Baz baz3) { - call1(baz3); - // :: error: (type.arguments.not.inferred) - call2(baz3); - } + // :: warning: (explicit.annotation.ignored) + void call1(S p) {} + + // :: warning: (explicit.annotation.ignored) + void call2(T p) {} + + void foo1(@LubglbD Baz baz1) { + call1(baz1); + call2(baz1); + } + + void foo2(@LubglbF Baz baz2) { + call1(baz2); + call2(baz2); + } + + void foo3(@LubglbB Baz baz3) { + call1(baz3); + // :: error: (type.arguments.not.inferred) + call2(baz3); + } } diff --git a/framework/tests/lubglb/Issue2432Constructor.java b/framework/tests/lubglb/Issue2432Constructor.java index 8aefab6190a..33c8713fbd2 100644 --- a/framework/tests/lubglb/Issue2432Constructor.java +++ b/framework/tests/lubglb/Issue2432Constructor.java @@ -5,92 +5,93 @@ class Issue2432C { - // reason for suppressing: - // super.invocation: Object is @LubglbA by default and it is unreasonable to change jdk stub - // just because of this - // inconsistent.constructor.type: the qualifier on returning type is expected not to be top - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - Issue2432C(@PolyLubglb Object dummy) {} - - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - Issue2432C(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} - - // class for test cases using type parameter - static class TypeParamClass { - - // @PolyLubglb on T shouldn't be in the poly resolving process + // reason for suppressing: + // super.invocation: Object is @LubglbA by default and it is unreasonable to change jdk stub + // just because of this + // inconsistent.constructor.type: the qualifier on returning type is expected not to be top @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) @PolyLubglb - TypeParamClass(@PolyLubglb Object dummy, T t) {} + Issue2432C(@PolyLubglb Object dummy) {} - // 2 poly param for testing lub @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) @PolyLubglb - TypeParamClass(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2, T t) {} - } - - // class for test cases using type parameter - class ReceiverClass { - - // if the qualifier on receiver is @PolyLubglb, it should not be involved in poly resolve - // process - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - ReceiverClass(Issue2432C Issue2432C.this, @PolyLubglb Object dummy) {} - - // 2 poly param for testing lub - @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) - @PolyLubglb - ReceiverClass( - Issue2432C Issue2432C.this, @PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} - } - - void invokeConstructors(@LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { - // :: error: (assignment.type.incompatible) - @LubglbF Issue2432C bottomOuter = new Issue2432C(top); - @LubglbA Issue2432C topOuter = new Issue2432C(top); - - // lub test - @LubglbA Issue2432C bottomOuter2 = new Issue2432C(top, bottom); - // :: error: (assignment.type.incompatible) - @LubglbB Issue2432C bottomOuter3 = new Issue2432C(top, bottom); - - @LubglbF Issue2432C bottomOuter4 = new Issue2432C(bottom, bottom); - } - - // invoke constructors with a receiver to test poly resolving - // note: seems CF already works well on these before changes - void invokeReceiverConstructors( - @LubglbA Issue2432C topOuter, - @PolyLubglb Issue2432C polyOuter, - @LubglbF Object bottom, - @LubglbA Object top) { - Issue2432C.@LubglbF ReceiverClass ref1 = polyOuter.new ReceiverClass(bottom); - // :: error: (assignment.type.incompatible) - Issue2432C.@LubglbB ReceiverClass ref2 = polyOuter.new ReceiverClass(top); - - // lub tests - Issue2432C.@LubglbA ReceiverClass ref3 = polyOuter.new ReceiverClass(top, bottom); - // :: error: (assignment.type.incompatible) - Issue2432C.@LubglbB ReceiverClass ref4 = polyOuter.new ReceiverClass(top, bottom); - - Issue2432C.@LubglbF ReceiverClass ref5 = polyOuter.new ReceiverClass(bottom, bottom); - } - - // invoke constructors with a type parameter to test poly resolving - void invokeTypeVarConstructors( - @LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { - @LubglbF TypeParamClass<@PolyLubglb Object> ref1 = new TypeParamClass<>(bottom, poly); - // :: error: (assignment.type.incompatible) - @LubglbB TypeParamClass<@PolyLubglb Object> ref2 = new TypeParamClass<>(top, poly); - - // lub tests - @LubglbA TypeParamClass<@PolyLubglb Object> ref3 = new TypeParamClass<>(bottom, top, poly); - // :: error: (assignment.type.incompatible) - @LubglbB TypeParamClass<@PolyLubglb Object> ref4 = new TypeParamClass<>(bottom, top, poly); - - @LubglbF TypeParamClass<@PolyLubglb Object> ref5 = new TypeParamClass<>(bottom, bottom, poly); - } + Issue2432C(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} + + // class for test cases using type parameter + static class TypeParamClass { + + // @PolyLubglb on T shouldn't be in the poly resolving process + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + TypeParamClass(@PolyLubglb Object dummy, T t) {} + + // 2 poly param for testing lub + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + TypeParamClass(@PolyLubglb Object dummy1, @PolyLubglb Object dummy2, T t) {} + } + + // class for test cases using type parameter + class ReceiverClass { + + // if the qualifier on receiver is @PolyLubglb, it should not be involved in poly resolve + // process + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + ReceiverClass(Issue2432C Issue2432C.this, @PolyLubglb Object dummy) {} + + // 2 poly param for testing lub + @SuppressWarnings({"super.invocation.invalid", "inconsistent.constructor.type"}) + @PolyLubglb + ReceiverClass( + Issue2432C Issue2432C.this, @PolyLubglb Object dummy1, @PolyLubglb Object dummy2) {} + } + + void invokeConstructors(@LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { + // :: error: (assignment.type.incompatible) + @LubglbF Issue2432C bottomOuter = new Issue2432C(top); + @LubglbA Issue2432C topOuter = new Issue2432C(top); + + // lub test + @LubglbA Issue2432C bottomOuter2 = new Issue2432C(top, bottom); + // :: error: (assignment.type.incompatible) + @LubglbB Issue2432C bottomOuter3 = new Issue2432C(top, bottom); + + @LubglbF Issue2432C bottomOuter4 = new Issue2432C(bottom, bottom); + } + + // invoke constructors with a receiver to test poly resolving + // note: seems CF already works well on these before changes + void invokeReceiverConstructors( + @LubglbA Issue2432C topOuter, + @PolyLubglb Issue2432C polyOuter, + @LubglbF Object bottom, + @LubglbA Object top) { + Issue2432C.@LubglbF ReceiverClass ref1 = polyOuter.new ReceiverClass(bottom); + // :: error: (assignment.type.incompatible) + Issue2432C.@LubglbB ReceiverClass ref2 = polyOuter.new ReceiverClass(top); + + // lub tests + Issue2432C.@LubglbA ReceiverClass ref3 = polyOuter.new ReceiverClass(top, bottom); + // :: error: (assignment.type.incompatible) + Issue2432C.@LubglbB ReceiverClass ref4 = polyOuter.new ReceiverClass(top, bottom); + + Issue2432C.@LubglbF ReceiverClass ref5 = polyOuter.new ReceiverClass(bottom, bottom); + } + + // invoke constructors with a type parameter to test poly resolving + void invokeTypeVarConstructors( + @LubglbA Object top, @LubglbF Object bottom, @PolyLubglb Object poly) { + @LubglbF TypeParamClass<@PolyLubglb Object> ref1 = new TypeParamClass<>(bottom, poly); + // :: error: (assignment.type.incompatible) + @LubglbB TypeParamClass<@PolyLubglb Object> ref2 = new TypeParamClass<>(top, poly); + + // lub tests + @LubglbA TypeParamClass<@PolyLubglb Object> ref3 = new TypeParamClass<>(bottom, top, poly); + // :: error: (assignment.type.incompatible) + @LubglbB TypeParamClass<@PolyLubglb Object> ref4 = new TypeParamClass<>(bottom, top, poly); + + @LubglbF + TypeParamClass<@PolyLubglb Object> ref5 = new TypeParamClass<>(bottom, bottom, poly); + } } diff --git a/framework/tests/methodval/MethodNameTest.java b/framework/tests/methodval/MethodNameTest.java index 0c6ae9507d9..f1cb5eaa241 100644 --- a/framework/tests/methodval/MethodNameTest.java +++ b/framework/tests/methodval/MethodNameTest.java @@ -1,41 +1,41 @@ import org.checkerframework.common.reflection.qual.MethodVal; public class MethodNameTest { - @MethodVal(className = "", methodName = "_MethodName", params = 0) Object o; + @MethodVal(className = "", methodName = "_MethodName", params = 0) Object o; - @MethodVal(className = "", methodName = "$methname", params = 0) Object o1; + @MethodVal(className = "", methodName = "$methname", params = 0) Object o1; - @MethodVal(className = "", methodName = "Method_Name", params = 0) Object o2; + @MethodVal(className = "", methodName = "Method_Name", params = 0) Object o2; - @MethodVal(className = "", methodName = "", params = 0) Object o3; + @MethodVal(className = "", methodName = "", params = 0) Object o3; - // :: error: (illegal.methodname) - @MethodVal(className = "", methodName = "[]MethodName", params = 0) Object o4; + // :: error: (illegal.methodname) + @MethodVal(className = "", methodName = "[]MethodName", params = 0) Object o4; - // :: error: (illegal.methodname) - @MethodVal(className = "", methodName = "Meht.name", params = 0) Object o5; + // :: error: (illegal.methodname) + @MethodVal(className = "", methodName = "Meht.name", params = 0) Object o5; - // :: error: (illegal.methodname) - @MethodVal(className = "", methodName = ".emethos", params = 0) Object o6; + // :: error: (illegal.methodname) + @MethodVal(className = "", methodName = ".emethos", params = 0) Object o6; - @MethodVal( - className = "c", - methodName = "m", - params = {0, 0}) - // :: error: (invalid.methodval) - Object o7; + @MethodVal( + className = "c", + methodName = "m", + params = {0, 0}) + // :: error: (invalid.methodval) + Object o7; - @MethodVal( - className = "c", - methodName = {"m", "m"}, - params = {0, 0}) - // :: error: (invalid.methodval) - Object o8; + @MethodVal( + className = "c", + methodName = {"m", "m"}, + params = {0, 0}) + // :: error: (invalid.methodval) + Object o8; - @MethodVal( - className = "c", - methodName = {"m", "m"}, - params = {0}) - // :: error: (invalid.methodval) - Object o9; + @MethodVal( + className = "c", + methodName = {"m", "m"}, + params = {0}) + // :: error: (invalid.methodval) + Object o9; } diff --git a/framework/tests/methodval/MethodValInferenceTest.java b/framework/tests/methodval/MethodValInferenceTest.java index 56fdb17f936..60c8ea64221 100644 --- a/framework/tests/methodval/MethodValInferenceTest.java +++ b/framework/tests/methodval/MethodValInferenceTest.java @@ -1,103 +1,104 @@ -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import org.checkerframework.common.reflection.qual.ClassBound; import org.checkerframework.common.reflection.qual.ClassVal; import org.checkerframework.common.reflection.qual.MethodVal; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.StringVal; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + public class MethodValInferenceTest { - boolean flag = true; - - public void testGetMethodParamLen( - Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { - @StringVal("someMethod") String str = "someMethod"; - @ClassVal("java.lang.Object") Class c = Object.class; - - @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = c.getMethod("getA", new Class[] {}); - - @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m2 = c.getMethod("getA", (Class[]) null); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m3 = c.getMethod("someMethod", new Class[] {Integer.class}); - - @MethodVal(className = "java.lang.Object", methodName = "equals", params = 1) Method m4 = c.getMethod("equals", Object.class); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m5 = c.getMethod(str, int.class); - - @MethodVal(className = "java.lang.Object", methodName = "getB", params = 0) Method m6 = c.getMethod("getB", new Class[0]); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m7 = c.getMethod(str, new Class[] {int.class}); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m8 = c.getMethod(str, new Class[] {Integer.class, Integer.class}); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m10 = c.getMethod(str, int.class, int.class); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m11 = c.getMethod(str, classArray2); - - @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = -1) Method m12 = c.getMethod(str, classArrayUnknown); - } - - public void testGetMethodMultiClassAndMethodNames( - @ClassVal({"java.lang.Object", "java.lang.String"}) Class twoClasses, - @ClassVal({"java.lang.Object"}) Class oneClass, - @StringVal({"method1"}) String oneName, - @StringVal({"method1", "method2"}) String twoNames, - Class @ArrayLen(2) [] classArray2, - Class[] classArrayUnknown) - throws Exception { - - @MethodVal( - className = {"java.lang.Object"}, - methodName = {"method1"}, - params = -1) - Method m1 = oneClass.getMethod(oneName, classArrayUnknown); - @MethodVal( - className = {"java.lang.Object", "java.lang.Object"}, - methodName = {"method1", "method2"}, - params = {-1, -1}) - Method m2 = oneClass.getMethod(twoNames, classArrayUnknown); - @MethodVal( - className = {"java.lang.Object", "java.lang.String"}, - methodName = {"method1", "method1"}, - params = {-1, -1}) - Method m3 = twoClasses.getMethod(oneName, classArrayUnknown); - @MethodVal( - className = { - "java.lang.Object", - "java.lang.String", - "java.lang.Object", - "java.lang.String" - }, - methodName = {"method1", "method2", "method2", "method1"}, - params = {-1, -1, -1, -1}) - Method m4 = twoClasses.getMethod(twoNames, classArrayUnknown); - } - - @ClassBound("java.lang.Object") Class classBound = Object.class; - - public void testGetConstructorClassBound() throws Exception { - @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = classBound.getMethod("getA", new Class[] {}); - } - - public void testGetConstructorClassBoundFail() throws Exception { - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) - // :: error: (assignment.type.incompatible) - Constructor con1 = classBound.getConstructor(new Class[] {}); // Should be @UnknownMethod - } - - public void testGetConstructorParamLen( - Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { - @ClassVal("java.lang.Object") Class c = Object.class; - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con1 = c.getConstructor(new Class[] {}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con2 = c.getConstructor((Class[]) null); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con3 = c.getConstructor(new Class[] {Integer.class}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con4 = c.getConstructor(Object.class); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con5 = c.getConstructor(int.class); - @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con6 = c.getConstructor(new Class[0]); - @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con7 = c.getConstructor(new Class[] {int.class}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con8 = c.getConstructor(new Class[] {Integer.class, Integer.class}); - @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con9 = c.getConstructor(int.class, int.class); - @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con10 = c.getConstructor(classArray2); - @MethodVal(className = "java.lang.Object", methodName = "", params = -1) Constructor con11 = c.getConstructor(classArrayUnknown); - } + boolean flag = true; + + public void testGetMethodParamLen( + Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { + @StringVal("someMethod") String str = "someMethod"; + @ClassVal("java.lang.Object") Class c = Object.class; + + @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = c.getMethod("getA", new Class[] {}); + + @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m2 = c.getMethod("getA", (Class[]) null); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m3 = c.getMethod("someMethod", new Class[] {Integer.class}); + + @MethodVal(className = "java.lang.Object", methodName = "equals", params = 1) Method m4 = c.getMethod("equals", Object.class); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m5 = c.getMethod(str, int.class); + + @MethodVal(className = "java.lang.Object", methodName = "getB", params = 0) Method m6 = c.getMethod("getB", new Class[0]); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 1) Method m7 = c.getMethod(str, new Class[] {int.class}); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m8 = c.getMethod(str, new Class[] {Integer.class, Integer.class}); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m10 = c.getMethod(str, int.class, int.class); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = 2) Method m11 = c.getMethod(str, classArray2); + + @MethodVal(className = "java.lang.Object", methodName = "someMethod", params = -1) Method m12 = c.getMethod(str, classArrayUnknown); + } + + public void testGetMethodMultiClassAndMethodNames( + @ClassVal({"java.lang.Object", "java.lang.String"}) Class twoClasses, + @ClassVal({"java.lang.Object"}) Class oneClass, + @StringVal({"method1"}) String oneName, + @StringVal({"method1", "method2"}) String twoNames, + Class @ArrayLen(2) [] classArray2, + Class[] classArrayUnknown) + throws Exception { + + @MethodVal( + className = {"java.lang.Object"}, + methodName = {"method1"}, + params = -1) + Method m1 = oneClass.getMethod(oneName, classArrayUnknown); + @MethodVal( + className = {"java.lang.Object", "java.lang.Object"}, + methodName = {"method1", "method2"}, + params = {-1, -1}) + Method m2 = oneClass.getMethod(twoNames, classArrayUnknown); + @MethodVal( + className = {"java.lang.Object", "java.lang.String"}, + methodName = {"method1", "method1"}, + params = {-1, -1}) + Method m3 = twoClasses.getMethod(oneName, classArrayUnknown); + @MethodVal( + className = { + "java.lang.Object", + "java.lang.String", + "java.lang.Object", + "java.lang.String" + }, + methodName = {"method1", "method2", "method2", "method1"}, + params = {-1, -1, -1, -1}) + Method m4 = twoClasses.getMethod(twoNames, classArrayUnknown); + } + + @ClassBound("java.lang.Object") Class classBound = Object.class; + + public void testGetConstructorClassBound() throws Exception { + @MethodVal(className = "java.lang.Object", methodName = "getA", params = 0) Method m1 = classBound.getMethod("getA", new Class[] {}); + } + + public void testGetConstructorClassBoundFail() throws Exception { + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) + // :: error: (assignment.type.incompatible) + Constructor con1 = classBound.getConstructor(new Class[] {}); // Should be @UnknownMethod + } + + public void testGetConstructorParamLen( + Class @ArrayLen(2) [] classArray2, Class[] classArrayUnknown) throws Exception { + @ClassVal("java.lang.Object") Class c = Object.class; + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con1 = c.getConstructor(new Class[] {}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con2 = c.getConstructor((Class[]) null); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con3 = c.getConstructor(new Class[] {Integer.class}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con4 = c.getConstructor(Object.class); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con5 = c.getConstructor(int.class); + @MethodVal(className = "java.lang.Object", methodName = "", params = 0) Constructor con6 = c.getConstructor(new Class[0]); + @MethodVal(className = "java.lang.Object", methodName = "", params = 1) Constructor con7 = c.getConstructor(new Class[] {int.class}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con8 = c.getConstructor(new Class[] {Integer.class, Integer.class}); + @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con9 = c.getConstructor(int.class, int.class); + @MethodVal(className = "java.lang.Object", methodName = "", params = 2) Constructor con10 = c.getConstructor(classArray2); + @MethodVal(className = "java.lang.Object", methodName = "", params = -1) Constructor con11 = c.getConstructor(classArrayUnknown); + } } diff --git a/framework/tests/methodval/MethodValLUBTest.java b/framework/tests/methodval/MethodValLUBTest.java index 690403dc500..3f0a70328e6 100644 --- a/framework/tests/methodval/MethodValLUBTest.java +++ b/framework/tests/methodval/MethodValLUBTest.java @@ -1,102 +1,103 @@ -import java.lang.reflect.Method; import org.checkerframework.common.reflection.qual.MethodVal; +import java.lang.reflect.Method; + public class MethodValLUBTest { - Object unknown = null; - boolean flag = false; + Object unknown = null; + boolean flag = false; - @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10 = null; + @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10 = null; - @MethodVal(className = "c2", methodName = "m2", params = 1) Object c2m21 = null; + @MethodVal(className = "c2", methodName = "m2", params = 1) Object c2m21 = null; - void basicLub() { - if (flag) { - unknown = c1m10; - } else { - unknown = c2m21; + void basicLub() { + if (flag) { + unknown = c1m10; + } else { + unknown = c2m21; + } + @MethodVal( + className = {"c1", "c2"}, + methodName = {"m1", "m2"}, + params = {0, 1}) + Object lub = unknown; + // :: error: (assignment.type.incompatible) + c1m10 = unknown; + // :: error: (assignment.type.incompatible) + c2m21 = unknown; } - @MethodVal( - className = {"c1", "c2"}, - methodName = {"m1", "m2"}, - params = {0, 1}) - Object lub = unknown; - // :: error: (assignment.type.incompatible) - c1m10 = unknown; - // :: error: (assignment.type.incompatible) - c2m21 = unknown; - } - @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10duplicate = null; + @MethodVal(className = "c1", methodName = "m1", params = 0) Object c1m10duplicate = null; - void lubSameType() { - if (flag) { - unknown = c1m10; - } else { - unknown = c1m10duplicate; + void lubSameType() { + if (flag) { + unknown = c1m10; + } else { + unknown = c1m10duplicate; + } + @MethodVal(className = "c1", methodName = "m1", params = 0) Object lub = unknown; } - @MethodVal(className = "c1", methodName = "m1", params = 0) Object lub = unknown; - } - @MethodVal(className = "c1", methodName = "m1", params = 1) Object c1m11 = null; + @MethodVal(className = "c1", methodName = "m1", params = 1) Object c1m11 = null; - void simalarSigLub() { - if (flag) { - unknown = c1m10; - } else { - unknown = c1m11; + void simalarSigLub() { + if (flag) { + unknown = c1m10; + } else { + unknown = c1m11; + } + @MethodVal( + className = {"c1", "c1"}, + methodName = {"m1", "m1"}, + params = {0, 1}) + Object lub = unknown; + // :: error: (assignment.type.incompatible) + c1m10 = unknown; + // :: error: (assignment.type.incompatible) + c1m11 = unknown; } - @MethodVal( - className = {"c1", "c1"}, - methodName = {"m1", "m1"}, - params = {0, 1}) - Object lub = unknown; - // :: error: (assignment.type.incompatible) - c1m10 = unknown; - // :: error: (assignment.type.incompatible) - c1m11 = unknown; - } - @MethodVal( - className = {"class", "class2"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object classClass2Method0 = null; + @MethodVal( + className = {"class", "class2"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object classClass2Method0 = null; - @MethodVal( - className = {"class2", "class"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object class2classMethod0 = null; + @MethodVal( + className = {"class2", "class"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object class2classMethod0 = null; - void setsLub() { - if (flag) { - unknown = classClass2Method0; - } else { - unknown = class2classMethod0; + void setsLub() { + if (flag) { + unknown = classClass2Method0; + } else { + unknown = class2classMethod0; + } + @MethodVal( + className = {"class2", "class", "class", "class2"}, + methodName = {"method", "method2", "method", "method2"}, + params = {0, 1, 0, 1}) + Object lub = unknown; + // :: error: (assignment.type.incompatible) + classClass2Method0 = unknown; + // :: error: (assignment.type.incompatible) + class2classMethod0 = unknown; } - @MethodVal( - className = {"class2", "class", "class", "class2"}, - methodName = {"method", "method2", "method", "method2"}, - params = {0, 1, 0, 1}) - Object lub = unknown; - // :: error: (assignment.type.incompatible) - classClass2Method0 = unknown; - // :: error: (assignment.type.incompatible) - class2classMethod0 = unknown; - } - void inferedlubTest() throws Exception { - Class c = MethodValInferenceTest.class; - Method m; - if (flag) { - m = c.getMethod("getA", new Class[0]); - } else { - m = c.getMethod("getB", new Class[0]); + void inferedlubTest() throws Exception { + Class c = MethodValInferenceTest.class; + Method m; + if (flag) { + m = c.getMethod("getA", new Class[0]); + } else { + m = c.getMethod("getB", new Class[0]); + } + @MethodVal( + className = {"MethodValInferenceTest", "MethodValInferenceTest"}, + methodName = {"getA", "getB"}, + params = {0, 0}) + Method lub = m; } - @MethodVal( - className = {"MethodValInferenceTest", "MethodValInferenceTest"}, - methodName = {"getA", "getB"}, - params = {0, 0}) - Method lub = m; - } } diff --git a/framework/tests/methodval/MethodValSubtypingTest.java b/framework/tests/methodval/MethodValSubtypingTest.java index ebdafd802d1..3aeb24716b8 100644 --- a/framework/tests/methodval/MethodValSubtypingTest.java +++ b/framework/tests/methodval/MethodValSubtypingTest.java @@ -1,69 +1,69 @@ import org.checkerframework.common.reflection.qual.MethodVal; public class MethodValSubtypingTest { - @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0 = null; + @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0 = null; - @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0Dup = null; + @MethodVal(className = "class", methodName = "method", params = 0) Object classMethod0Dup = null; - @MethodVal( - className = {"class", "class2"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object classClass2Method0 = null; + @MethodVal( + className = {"class", "class2"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object classClass2Method0 = null; - @MethodVal( - className = {"class2", "class"}, - methodName = {"method", "method2"}, - params = {0, 1}) - Object class2classMethod0 = null; + @MethodVal( + className = {"class2", "class"}, + methodName = {"method", "method2"}, + params = {0, 1}) + Object class2classMethod0 = null; - Object unknown = null; + Object unknown = null; - void methodValSubtyping() { - classMethod0 = classMethod0Dup; - // :: error: (assignment.type.incompatible) - classMethod0 = classClass2Method0; - // :: error: (assignment.type.incompatible) - classClass2Method0 = class2classMethod0; - classClass2Method0 = classMethod0; - } + void methodValSubtyping() { + classMethod0 = classMethod0Dup; + // :: error: (assignment.type.incompatible) + classMethod0 = classClass2Method0; + // :: error: (assignment.type.incompatible) + classClass2Method0 = class2classMethod0; + classClass2Method0 = classMethod0; + } - void bottomMethodVal() { - classMethod0 = null; - classClass2Method0 = null; - } + void bottomMethodVal() { + classMethod0 = null; + classClass2Method0 = null; + } - void unknownMethodVal1() { - unknown = class2classMethod0; - } + void unknownMethodVal1() { + unknown = class2classMethod0; + } - void unknownMethodVal2() { - // :: error: (assignment.type.incompatible) - class2classMethod0 = unknown; - } + void unknownMethodVal2() { + // :: error: (assignment.type.incompatible) + class2classMethod0 = unknown; + } - @MethodVal( - className = {"aclass", "aclass", "aclass"}, - methodName = {"amethod", "amethod", "amethod"}, - params = {0, 1, 2}) - Object triple = null; + @MethodVal( + className = {"aclass", "aclass", "aclass"}, + methodName = {"amethod", "amethod", "amethod"}, + params = {0, 1, 2}) + Object triple = null; - @MethodVal( - className = {"aclass", "aclass", "aclass"}, - methodName = {"amethod", "amethod", "amethod"}, - params = {2, 1, 0}) - Object tripleAgain = null; + @MethodVal( + className = {"aclass", "aclass", "aclass"}, + methodName = {"amethod", "amethod", "amethod"}, + params = {2, 1, 0}) + Object tripleAgain = null; - @MethodVal( - className = {"aclass"}, - methodName = {"amethod"}, - params = {2}) - Object one = null; + @MethodVal( + className = {"aclass"}, + methodName = {"amethod"}, + params = {2}) + Object one = null; - void test() { - tripleAgain = triple; - // :: error: (assignment.type.incompatible) - one = triple; - triple = one; - } + void test() { + tripleAgain = triple; + // :: error: (assignment.type.incompatible) + one = triple; + triple = one; + } } diff --git a/framework/tests/nontopdefault/NTDTest.java b/framework/tests/nontopdefault/NTDTest.java index 1bec385d19e..420bee634ff 100644 --- a/framework/tests/nontopdefault/NTDTest.java +++ b/framework/tests/nontopdefault/NTDTest.java @@ -14,57 +14,57 @@ public class NTDConstructorReceiverTest { - // default method receiver is @NTDTop - void DefaultMethodReceiver() { + // default method receiver is @NTDTop + void DefaultMethodReceiver() { - // this line produces a methodref.receiver.bound.invalid error, but it shouldn't if the - // receiver for inner class constructors are properly applied - Demand constructorReference = InnerDefaultReceiver::new; + // this line produces a methodref.receiver.bound.invalid error, but it shouldn't if the + // receiver for inner class constructors are properly applied + Demand constructorReference = InnerDefaultReceiver::new; - // this line does not as the receiver is explicitly declared to be @NTDTop - Demand constructorReference2 = InnerExplicitReceiver::new; - } - - class InnerDefaultReceiver { - // takes on the default receiver for inner class constructor methods - InnerDefaultReceiver(NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { - // The type of NTDConstructorReceiverTest.this should be @NTDTop. - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; + // this line does not as the receiver is explicitly declared to be @NTDTop + Demand constructorReference2 = InnerExplicitReceiver::new; } - void method() { - // The TypeUseLocation.RECEIVER only applies to the outermost type, so - // NTDConstructorReceiverTest.this is given the - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - // :: error: (assignment.type.incompatible) - @NTDMiddle InnerDefaultReceiver thatInner = this; - } + class InnerDefaultReceiver { + // takes on the default receiver for inner class constructor methods + InnerDefaultReceiver(NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { + // The type of NTDConstructorReceiverTest.this should be @NTDTop. + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; + } - void explicit(@NTDTop NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver this) { - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; - } - } + void method() { + // The TypeUseLocation.RECEIVER only applies to the outermost type, so + // NTDConstructorReceiverTest.this is given the + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + // :: error: (assignment.type.incompatible) + @NTDMiddle InnerDefaultReceiver thatInner = this; + } - class InnerExplicitReceiver { - // explicitly set the receiver to be @NTDTop - InnerExplicitReceiver(@NTDTop NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { - // :: error: (assignment.type.incompatible) - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + void explicit(@NTDTop NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver this) { + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest.@NTDTop InnerDefaultReceiver thatInner = this; + } } - InnerExplicitReceiver( - @NTDMiddle NTDConstructorReceiverTest NTDConstructorReceiverTest.this, int i) { - @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + class InnerExplicitReceiver { + // explicitly set the receiver to be @NTDTop + InnerExplicitReceiver(@NTDTop NTDConstructorReceiverTest NTDConstructorReceiverTest.this) { + // :: error: (assignment.type.incompatible) + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + } + + InnerExplicitReceiver( + @NTDMiddle NTDConstructorReceiverTest NTDConstructorReceiverTest.this, int i) { + @NTDMiddle NTDConstructorReceiverTest that = NTDConstructorReceiverTest.this; + } } - } } interface Demand { - R supply(); + R supply(); } diff --git a/framework/tests/nontopdefault/TestCasting.java b/framework/tests/nontopdefault/TestCasting.java index d506e2b7aa6..06e659cfd55 100644 --- a/framework/tests/nontopdefault/TestCasting.java +++ b/framework/tests/nontopdefault/TestCasting.java @@ -2,19 +2,19 @@ @SuppressWarnings("inconsistent.constructor.type") // Not the point of this test public class TestCasting { - void repro(@NTDMiddle long startTime) { - try { - System.out.println("Inside try"); - return; - } catch (Exception ex) { - long timeTaken = startTime; - @NTDMiddle double dblTimeTaken = timeTaken; + void repro(@NTDMiddle long startTime) { + try { + System.out.println("Inside try"); + return; + } catch (Exception ex) { + long timeTaken = startTime; + @NTDMiddle double dblTimeTaken = timeTaken; - throw new IllegalArgumentException(); - } finally { - long timeTaken2 = startTime; - // This assignment used to fail. - @NTDMiddle double dblTimeTaken2 = timeTaken2; + throw new IllegalArgumentException(); + } finally { + long timeTaken2 = startTime; + // This assignment used to fail. + @NTDMiddle double dblTimeTaken2 = timeTaken2; + } } - } } diff --git a/framework/tests/purity-suggestions/PuritySuggestionsClass.java b/framework/tests/purity-suggestions/PuritySuggestionsClass.java index f1e1d70702b..f3f17b1b142 100644 --- a/framework/tests/purity-suggestions/PuritySuggestionsClass.java +++ b/framework/tests/purity-suggestions/PuritySuggestionsClass.java @@ -6,169 +6,169 @@ public class PuritySuggestionsClass { - String f1, f2, f3; - String[] a; - static String staticString; + String f1, f2, f3; + String[] a; + static String staticString; - // class with a (potentially) non-pure constructor - private static class NonPureClass { - String t; + // class with a (potentially) non-pure constructor + private static class NonPureClass { + String t; - public NonPureClass() { - staticString = ""; + public NonPureClass() { + staticString = ""; + } + } + + // class with a pure constructor + private static class PureClass { + // :: warning: (purity.more.sideeffectfree) + public PureClass() {} + } + + // class with a pure constructor + private static class PureClass2 { + String t; + + // :: warning: (purity.more.sideeffectfree) + public PureClass2() { + t = ""; + } + } + + // :: warning: (purity.more.sideeffectfree) + void nonpure() {} + + @Pure + String pure() { + return ""; + } + + String t3() { + nonpure(); + return ""; + } + + // :: warning: (purity.more.pure) + String t4() { + pure(); + return ""; + } + + // :: warning: (purity.more.pure) + int t5() { + int i = 1; + return i; + } + + // :: warning: (purity.more.pure) + int t6() { + int j = 0; + for (int i = 0; i < 10; i++) { + j = j - i; + } + return j; + } + + // :: warning: (purity.more.pure) + String t7() { + if (true) { + return "a"; + } + return ""; + } + + // :: warning: (purity.more.pure) + int t8() { + return 1 - 2 / 3 * 2 % 2; + } + + // :: warning: (purity.more.pure) + String t9() { + return "b" + "a"; + } + + String t10() { + f1 = ""; + f2 = ""; + return ""; + } + + String t11(PuritySuggestionsClass l) { + l.a[0] = ""; + return ""; + } + + String t12(String[] s) { + s[0] = ""; + return ""; + } + + String t13() { + PureClass p = new PureClass(); + return ""; + } + + // :: warning: (purity.more.pure) + String t14() { + String i = ""; + i = "a"; + return i; + } + + // :: warning: (purity.more.pure) + String t15() { + String[] s = new String[1]; + return s[0]; + } + + // :: warning: (purity.more.sideeffectfree) + String t16() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... + } + return ""; + } + + // :: warning: (purity.more.sideeffectfree) + String t16b() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... + } + return ""; } - } - // class with a pure constructor - private static class PureClass { // :: warning: (purity.more.sideeffectfree) - public PureClass() {} - } + String t16c() { + try { + int i = 1 / 0; + } catch (Throwable t) { + // ... + } + return ""; + } - // class with a pure constructor - private static class PureClass2 { - String t; + // :: warning: (purity.more.pure) + String t17() { + return ""; + } + @Deterministic // :: warning: (purity.more.sideeffectfree) - public PureClass2() { - t = ""; - } - } - - // :: warning: (purity.more.sideeffectfree) - void nonpure() {} - - @Pure - String pure() { - return ""; - } - - String t3() { - nonpure(); - return ""; - } - - // :: warning: (purity.more.pure) - String t4() { - pure(); - return ""; - } - - // :: warning: (purity.more.pure) - int t5() { - int i = 1; - return i; - } - - // :: warning: (purity.more.pure) - int t6() { - int j = 0; - for (int i = 0; i < 10; i++) { - j = j - i; - } - return j; - } - - // :: warning: (purity.more.pure) - String t7() { - if (true) { - return "a"; - } - return ""; - } - - // :: warning: (purity.more.pure) - int t8() { - return 1 - 2 / 3 * 2 % 2; - } - - // :: warning: (purity.more.pure) - String t9() { - return "b" + "a"; - } - - String t10() { - f1 = ""; - f2 = ""; - return ""; - } - - String t11(PuritySuggestionsClass l) { - l.a[0] = ""; - return ""; - } - - String t12(String[] s) { - s[0] = ""; - return ""; - } - - String t13() { - PureClass p = new PureClass(); - return ""; - } - - // :: warning: (purity.more.pure) - String t14() { - String i = ""; - i = "a"; - return i; - } - - // :: warning: (purity.more.pure) - String t15() { - String[] s = new String[1]; - return s[0]; - } - - // :: warning: (purity.more.sideeffectfree) - String t16() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... - } - return ""; - } - - // :: warning: (purity.more.sideeffectfree) - String t16b() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... - } - return ""; - } - - // :: warning: (purity.more.sideeffectfree) - String t16c() { - try { - int i = 1 / 0; - } catch (Throwable t) { - // ... - } - return ""; - } - - // :: warning: (purity.more.pure) - String t17() { - return ""; - } - - @Deterministic - // :: warning: (purity.more.sideeffectfree) - String t18() { - return ""; - } - - // :: warning: (purity.more.deterministic) - String t19() { - return t18(); - } - - String t12() { - NonPureClass p = new NonPureClass(); - return ""; - } + String t18() { + return ""; + } + + // :: warning: (purity.more.deterministic) + String t19() { + return t18(); + } + + String t12() { + NonPureClass p = new NonPureClass(); + return ""; + } } diff --git a/framework/tests/reflection/AnonymousClassTest.java b/framework/tests/reflection/AnonymousClassTest.java index c814fdad717..51eb0a3e2bc 100644 --- a/framework/tests/reflection/AnonymousClassTest.java +++ b/framework/tests/reflection/AnonymousClassTest.java @@ -1,128 +1,129 @@ -import java.lang.reflect.Method; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectBottom; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling1; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling2; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectTop; +import java.lang.reflect.Method; + public class AnonymousClassTest { - /** - * To build/run outside of the JUnit tests: - * - *

                  Build with $CHECKERFRAMEWOKR/framework/tests/build/ on the classpath. Need to either use - * Java 8 or the langtools compiler, because annotations on cast are used. - * - *

                  java AnonymousClassTest MyClass$1.getSib1() MyClass$1.setSib1() MyClass$1.setSib1() - * MyClass$1.setSib2() MyClass$1.setSib2() MyClass$1.getSib2() - */ - public static void main(String[] args) { - AnonymousClassTest act = new AnonymousClassTest(); - act.returnTypePass(); - act.argumentTypePass(); - act.argumentTypeFail(); - act.returnTypeFail(); - } - - @TestReflectSibling1 int sibling1; - @TestReflectSibling2 int sibling2; - - public void returnTypePass() { - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod("getSib1", new Class[] {}); - // TODO: Can we resolve anonymous classes? - // :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - ignore.printStackTrace(); - } - } - - public void argumentTypePass() { - String str = "setSib1"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod(str, new Class[] {int.class}); - // TODO: Can we resolve anonymous classes? - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val1); - // TODO: Can we resolve anonymous classes? - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val2); - } catch (Exception ignore) { - ignore.printStackTrace(); - } - } - - public void argumentTypeFail() { - String str = "setSib2"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod(str, new Class[] {int.class}); - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val1); - // :: error: (argument.type.incompatible) - m.invoke(anonymous, val2); - } catch (Exception ignore) { - ignore.printStackTrace(); - } - } - - public void returnTypeFail() { - try { - Class c = Class.forName("AnonymousClassTest$1"); - Method m = c.getMethod("getSib2", new Class[] {}); - // :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - ignore.printStackTrace(); + /** + * To build/run outside of the JUnit tests: + * + *

                  Build with $CHECKERFRAMEWOKR/framework/tests/build/ on the classpath. Need to either use + * Java 8 or the langtools compiler, because annotations on cast are used. + * + *

                  java AnonymousClassTest MyClass$1.getSib1() MyClass$1.setSib1() MyClass$1.setSib1() + * MyClass$1.setSib2() MyClass$1.setSib2() MyClass$1.getSib2() + */ + public static void main(String[] args) { + AnonymousClassTest act = new AnonymousClassTest(); + act.returnTypePass(); + act.argumentTypePass(); + act.argumentTypeFail(); + act.returnTypeFail(); } - } - public @TestReflectBottom MyClass anonymous = - // :: warning: (cast.unsafe.constructor.invocation) - new @TestReflectBottom MyClass() { - - public @TestReflectSibling1 int getSib1() { - System.out.println("MyClass$1.getSib1()"); - return 1; + @TestReflectSibling1 int sibling1; + @TestReflectSibling2 int sibling2; + + public void returnTypePass() { + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod("getSib1", new Class[] {}); + // TODO: Can we resolve anonymous classes? + // :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + ignore.printStackTrace(); } + } - public @TestReflectSibling2 int getSib2() { - System.out.println("MyClass$1.getSib2()"); - return 1; + public void argumentTypePass() { + String str = "setSib1"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod(str, new Class[] {int.class}); + // TODO: Can we resolve anonymous classes? + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val1); + // TODO: Can we resolve anonymous classes? + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val2); + } catch (Exception ignore) { + ignore.printStackTrace(); } + } - public void setSib1(@TestReflectSibling1 int a) { - System.out.println("MyClass$1.setSib1()"); + public void argumentTypeFail() { + String str = "setSib2"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod(str, new Class[] {int.class}); + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val1); + // :: error: (argument.type.incompatible) + m.invoke(anonymous, val2); + } catch (Exception ignore) { + ignore.printStackTrace(); } + } - public void setSib2(@TestReflectSibling2 int a) { - System.out.println("MyClass$1.setSib2()"); + public void returnTypeFail() { + try { + Class c = Class.forName("AnonymousClassTest$1"); + Method m = c.getMethod("getSib2", new Class[] {}); + // :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object a = m.invoke(anonymous, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + ignore.printStackTrace(); } - }; + } - class MyClass { + public @TestReflectBottom MyClass anonymous = + // :: warning: (cast.unsafe.constructor.invocation) + new @TestReflectBottom MyClass() { - public @TestReflectTop int getSib1() { - System.out.println("MyClass.getSib1()"); - return 1; - } + public @TestReflectSibling1 int getSib1() { + System.out.println("MyClass$1.getSib1()"); + return 1; + } - public @TestReflectTop int getSib2() { - System.out.println("MyClass.getSib1()"); - return 1; - } + public @TestReflectSibling2 int getSib2() { + System.out.println("MyClass$1.getSib2()"); + return 1; + } - public void setSib1(@TestReflectBottom int a) { - System.out.println("MyClass.setSib1()"); - } + public void setSib1(@TestReflectSibling1 int a) { + System.out.println("MyClass$1.setSib1()"); + } + + public void setSib2(@TestReflectSibling2 int a) { + System.out.println("MyClass$1.setSib2()"); + } + }; + + class MyClass { + + public @TestReflectTop int getSib1() { + System.out.println("MyClass.getSib1()"); + return 1; + } - public void setSib2(@TestReflectBottom int a) { - System.out.println("MyClass.setSib2()"); + public @TestReflectTop int getSib2() { + System.out.println("MyClass.getSib1()"); + return 1; + } + + public void setSib1(@TestReflectBottom int a) { + System.out.println("MyClass.setSib1()"); + } + + public void setSib2(@TestReflectBottom int a) { + System.out.println("MyClass.setSib2()"); + } } - } } diff --git a/framework/tests/reflection/MethodTest.java b/framework/tests/reflection/MethodTest.java index bb8c98fa0ab..9c3ae0cc441 100644 --- a/framework/tests/reflection/MethodTest.java +++ b/framework/tests/reflection/MethodTest.java @@ -1,409 +1,426 @@ -import java.lang.reflect.Method; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectBottom; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling1; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling2; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectTop; -public class MethodTest { - - @TestReflectSibling1 int sibling1; - @TestReflectSibling2 int sibling2; - @TestReflectBottom SuperClass superClass; - - public void real_class() { - try { - Class c = Object.class; - Method m = c.getMethod("equals", Object.class); - Object rec = new Object(); - Object param = new Object(); - Boolean other = (Boolean) rec.equals(param); - Boolean equals = (Boolean) m.invoke(rec, param); - } catch (Exception ignore) { - } - } - - public void pass1() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod("getA", new Class[] {}); - @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass1b() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod("getA", (Class[]) null); - @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass2() { - String str = "get" + "A"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {}); - @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass3() { - String str = "get"; - str += "A"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {}); - @TestReflectSibling1 Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass4() { - String str = "setA"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - m.invoke(superClass, val1); - m.invoke(superClass, val2); - } catch (Exception ignore) { - } - } - - public void pass4b() { - String str = "setA"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Integer val2 = val1; - try { - // - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, int.class); - m.invoke(superClass, val1); - m.invoke(superClass, val2); - } catch (Exception ignore) { - } - } - - @TestReflectBottom SubClass subClass; - - // Test resolution of methods declared in super class - public void pass5() { - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod("getB", new Class[0]); - @TestReflectSibling2 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - // Test resolution of static methods - public void pass6() { - try { - Class c = MethodTest.class; - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - // Test primitives - public void pass7() { - try { - Class c = MethodTest.class; - Method m = - c.getMethod("convertTestReflectSibling2ToTestReflectSibling1", new Class[] {int.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - public void pass8() { - String str = "setA"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - m.invoke(superClass, sibling1); - } catch (Exception ignore) { - } - } - - public void pass9() { - String str = "getA"; - if (true) { - str = "getB"; - } - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[0]); - @TestReflectTop Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - // Test getClass() - public void pass10() { - SuperClass inst = new SubClass(); - try { - Class c = inst.getClass(); - Method m = c.getMethod("getA", new Class[0]); - @TestReflectSibling1 Object o = m.invoke(inst, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void pass11() { - try { - Class c = this.getClass(); - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - public void pass11b() { - try { - Class c = getClass(); - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(null, sibling2); - } catch (Exception ignore) { - } - } - - // Test .class on inner class - public void pass12() { - try { - Class c = SuperClass.class; - Method m = c.getMethod("getA", new Class[0]); - @TestReflectSibling1 - Object o = m.invoke(new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); - } catch (Exception ignore) { - } - } - - boolean flag = false; - - // Test lub of return types - public void testLubReturnPass() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m; - if (flag) { - m = c.getMethod("getA", new Class[0]); - } else { - m = c.getMethod("getB", new Class[0]); - } - @TestReflectTop - Object o = m.invoke(new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); - } catch (Exception ignore) { - } - } - - public void testLubReturnFail() { - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m; - if (flag) { - m = c.getMethod("getA", new Class[0]); - } else { - m = c.getMethod("getB", new Class[0]); - } - @TestReflectBottom - Object o = - // :: error: (assignment.type.incompatible) - m.invoke(new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); - } catch (Exception ignore) { - } - } - - public void test() {} - - public void fail1() { - try { - Class c = MethodTest.class; - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - Object o = m.invoke(null, sibling1); - } catch (Exception ignore) { - } - } - - // Test unresolvable methods - public void fail2(String str) { - try { - Class c = Class.forName(str); - Method m = c.getMethod("getA", new Class[] {Integer.class}); - // :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); - } catch (Exception ignore) { - } - } - - public void fail3() { - String str = "setB"; - try { - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, sibling1); - } catch (Exception ignore) { - } - } - - public void fail4() { - String str = "setA"; - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, new Object[] {sibling2}); - } catch (Exception ignore) { - } - } - - public void fail5() { - String str = "setAB"; - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[] {Integer.class, Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, new Object[] {sibling1, sibling2}); - } catch (Exception ignore) { - } - } - - public void fail6() { - String str = "setA"; - if (true) { - str = "setB"; - } - try { - Class c = Class.forName("MethodTest$SubClass"); - Method m = c.getMethod(str, new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - m.invoke(this, new Object[] {sibling1}); - } catch (Exception ignore) { - } - } - - public void fail7() { - // :: warning: (cast.unsafe.constructor.invocation) - @TestReflectSibling2 MethodTest inst = new @TestReflectSibling2 MethodTest(); - try { - Class c = MethodTest.class; - Method m = - c.getMethod( - "convertTestReflectSibling2ToTestReflectSibling1", new Class[] {Integer.class}); - @TestReflectSibling1 Object o = m.invoke(inst, sibling2); - } catch (Exception ignore) { - } - } - - // Test method call that cannot be uniquely resolved - public void fail8() { - try { - Class c = SuperClass.class; - Method m = c.getMethod("setC", new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - Object o = m.invoke(new SuperClass(), new Object[] {sibling2}); - } catch (Exception ignore) { - } - } - - public void bug() { - String str = "setA"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling1 Object[] args = new Object[] {val1}; - try { - // - Class c = Class.forName("MethodTest$SuperClass"); - - Method m = c.getMethod(str, int.class); - // This error is a bug. - // See DefaultReflectionResolver.resolveMethodCall(...) - // for details. - // :: error: (argument.type.incompatible) - m.invoke(this, args); - } catch (Exception ignore) { - } - } - - public void bug2() { - String str = "setAB"; - @TestReflectSibling1 int val1 = sibling1; - @TestReflectSibling2 int val2 = sibling2; - - Object[] args = new Object[] {val1, val2}; - try { - // - Class c = Class.forName("MethodTest$SuperClass"); - Method m = c.getMethod(str, int.class, int.class); - // This error is a bug. - // See DefaultReflectionResolver.resolveMethodCall(...) - // for details. - // :: error: (argument.type.incompatible) - m.invoke(this, args); - } catch (Exception ignore) { - } - } - - public static @TestReflectSibling1 int convertTestReflectSibling2ToTestReflectSibling1( - @TestReflectSibling2 int a) { - return (@TestReflectSibling1 int) 1; - } - - // TODO: Does the testing framework somehow support the compilation of - // multiple files at the same time? - private class SubClass extends SuperClass {} - - private class SuperClass { - private @TestReflectSibling1 int a; - private @TestReflectSibling2 int b; - private @TestReflectSibling1 Integer c; - - public SuperClass() { - this.a = sibling1; - this.b = sibling2; - } - - public @TestReflectSibling1 int getA() { - return a; - } - - public void setA(@TestReflectSibling1 int a) { - this.a = a; - } - - public @TestReflectSibling2 int getB() { - return b; - } - - public void setB(@TestReflectSibling2 int b) { - this.b = b; - } - - public void setAB(@TestReflectSibling1 int a, @TestReflectSibling2 int b) { - this.a = a; - this.b = b; - } +import java.lang.reflect.Method; - public void setC(@TestReflectSibling1 int c) { - this.c = c; - } +public class MethodTest { - public void setC(@TestReflectSibling1 Integer c) { - this.c = c; + @TestReflectSibling1 int sibling1; + @TestReflectSibling2 int sibling2; + @TestReflectBottom SuperClass superClass; + + public void real_class() { + try { + Class c = Object.class; + Method m = c.getMethod("equals", Object.class); + Object rec = new Object(); + Object param = new Object(); + Boolean other = (Boolean) rec.equals(param); + Boolean equals = (Boolean) m.invoke(rec, param); + } catch (Exception ignore) { + } + } + + public void pass1() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod("getA", new Class[] {}); + @TestReflectSibling1 + Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass1b() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod("getA", (Class[]) null); + @TestReflectSibling1 + Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass2() { + String str = "get" + "A"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {}); + @TestReflectSibling1 + Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass3() { + String str = "get"; + str += "A"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {}); + @TestReflectSibling1 + Object a = m.invoke(superClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass4() { + String str = "setA"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + m.invoke(superClass, val1); + m.invoke(superClass, val2); + } catch (Exception ignore) { + } + } + + public void pass4b() { + String str = "setA"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Integer val2 = val1; + try { + // + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, int.class); + m.invoke(superClass, val1); + m.invoke(superClass, val2); + } catch (Exception ignore) { + } + } + + @TestReflectBottom SubClass subClass; + + // Test resolution of methods declared in super class + public void pass5() { + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod("getB", new Class[0]); + @TestReflectSibling2 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + // Test resolution of static methods + public void pass6() { + try { + Class c = MethodTest.class; + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", + new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + // Test primitives + public void pass7() { + try { + Class c = MethodTest.class; + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", + new Class[] {int.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + public void pass8() { + String str = "setA"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + m.invoke(superClass, sibling1); + } catch (Exception ignore) { + } + } + + public void pass9() { + String str = "getA"; + if (true) { + str = "getB"; + } + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[0]); + @TestReflectTop Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + // Test getClass() + public void pass10() { + SuperClass inst = new SubClass(); + try { + Class c = inst.getClass(); + Method m = c.getMethod("getA", new Class[0]); + @TestReflectSibling1 Object o = m.invoke(inst, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void pass11() { + try { + Class c = this.getClass(); + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", + new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + public void pass11b() { + try { + Class c = getClass(); + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", + new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(null, sibling2); + } catch (Exception ignore) { + } + } + + // Test .class on inner class + public void pass12() { + try { + Class c = SuperClass.class; + Method m = c.getMethod("getA", new Class[0]); + @TestReflectSibling1 + Object o = + m.invoke( + new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); + } catch (Exception ignore) { + } + } + + boolean flag = false; + + // Test lub of return types + public void testLubReturnPass() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m; + if (flag) { + m = c.getMethod("getA", new Class[0]); + } else { + m = c.getMethod("getB", new Class[0]); + } + @TestReflectTop + Object o = + m.invoke( + new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); + } catch (Exception ignore) { + } + } + + public void testLubReturnFail() { + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m; + if (flag) { + m = c.getMethod("getA", new Class[0]); + } else { + m = c.getMethod("getB", new Class[0]); + } + @TestReflectBottom + Object o = + // :: error: (assignment.type.incompatible) + m.invoke( + new SuperClass(), new @TestReflectBottom Object @TestReflectBottom [0]); + } catch (Exception ignore) { + } + } + + public void test() {} + + public void fail1() { + try { + Class c = MethodTest.class; + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", + new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + Object o = m.invoke(null, sibling1); + } catch (Exception ignore) { + } + } + + // Test unresolvable methods + public void fail2(String str) { + try { + Class c = Class.forName(str); + Method m = c.getMethod("getA", new Class[] {Integer.class}); + // :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object o = m.invoke(subClass, (@TestReflectBottom Object[]) null); + } catch (Exception ignore) { + } + } + + public void fail3() { + String str = "setB"; + try { + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, sibling1); + } catch (Exception ignore) { + } + } + + public void fail4() { + String str = "setA"; + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, new Object[] {sibling2}); + } catch (Exception ignore) { + } + } + + public void fail5() { + String str = "setAB"; + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[] {Integer.class, Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, new Object[] {sibling1, sibling2}); + } catch (Exception ignore) { + } + } + + public void fail6() { + String str = "setA"; + if (true) { + str = "setB"; + } + try { + Class c = Class.forName("MethodTest$SubClass"); + Method m = c.getMethod(str, new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + m.invoke(this, new Object[] {sibling1}); + } catch (Exception ignore) { + } + } + + public void fail7() { + // :: warning: (cast.unsafe.constructor.invocation) + @TestReflectSibling2 MethodTest inst = new @TestReflectSibling2 MethodTest(); + try { + Class c = MethodTest.class; + Method m = + c.getMethod( + "convertTestReflectSibling2ToTestReflectSibling1", + new Class[] {Integer.class}); + @TestReflectSibling1 Object o = m.invoke(inst, sibling2); + } catch (Exception ignore) { + } + } + + // Test method call that cannot be uniquely resolved + public void fail8() { + try { + Class c = SuperClass.class; + Method m = c.getMethod("setC", new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + Object o = m.invoke(new SuperClass(), new Object[] {sibling2}); + } catch (Exception ignore) { + } + } + + public void bug() { + String str = "setA"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling1 Object[] args = new Object[] {val1}; + try { + // + Class c = Class.forName("MethodTest$SuperClass"); + + Method m = c.getMethod(str, int.class); + // This error is a bug. + // See DefaultReflectionResolver.resolveMethodCall(...) + // for details. + // :: error: (argument.type.incompatible) + m.invoke(this, args); + } catch (Exception ignore) { + } + } + + public void bug2() { + String str = "setAB"; + @TestReflectSibling1 int val1 = sibling1; + @TestReflectSibling2 int val2 = sibling2; + + Object[] args = new Object[] {val1, val2}; + try { + // + Class c = Class.forName("MethodTest$SuperClass"); + Method m = c.getMethod(str, int.class, int.class); + // This error is a bug. + // See DefaultReflectionResolver.resolveMethodCall(...) + // for details. + // :: error: (argument.type.incompatible) + m.invoke(this, args); + } catch (Exception ignore) { + } + } + + public static @TestReflectSibling1 int convertTestReflectSibling2ToTestReflectSibling1( + @TestReflectSibling2 int a) { + return (@TestReflectSibling1 int) 1; + } + + // TODO: Does the testing framework somehow support the compilation of + // multiple files at the same time? + private class SubClass extends SuperClass {} + + private class SuperClass { + private @TestReflectSibling1 int a; + private @TestReflectSibling2 int b; + private @TestReflectSibling1 Integer c; + + public SuperClass() { + this.a = sibling1; + this.b = sibling2; + } + + public @TestReflectSibling1 int getA() { + return a; + } + + public void setA(@TestReflectSibling1 int a) { + this.a = a; + } + + public @TestReflectSibling2 int getB() { + return b; + } + + public void setB(@TestReflectSibling2 int b) { + this.b = b; + } + + public void setAB(@TestReflectSibling1 int a, @TestReflectSibling2 int b) { + this.a = a; + this.b = b; + } + + public void setC(@TestReflectSibling1 int c) { + this.c = c; + } + + public void setC(@TestReflectSibling1 Integer c) { + this.c = c; + } } - } } diff --git a/framework/tests/reflection/ReflectionConstructorTest.java b/framework/tests/reflection/ReflectionConstructorTest.java index e0e33f00bce..250283dc9d3 100644 --- a/framework/tests/reflection/ReflectionConstructorTest.java +++ b/framework/tests/reflection/ReflectionConstructorTest.java @@ -1,70 +1,71 @@ -import java.lang.reflect.Constructor; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling1; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectSibling2; import org.checkerframework.framework.testchecker.reflection.qual.TestReflectTop; +import java.lang.reflect.Constructor; + public class ReflectionConstructorTest { - @TestReflectSibling1 int sibling1; - @TestReflectSibling2 int sibling2; + @TestReflectSibling1 int sibling1; + @TestReflectSibling2 int sibling2; - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - public @TestReflectSibling1 ReflectionConstructorTest(@TestReflectSibling1 int a) {} + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + public @TestReflectSibling1 ReflectionConstructorTest(@TestReflectSibling1 int a) {} - // :: warning: (inconsistent.constructor.type) - public @TestReflectSibling2 ReflectionConstructorTest( - // :: error: (super.invocation.invalid) - @TestReflectSibling2 int a, @TestReflectSibling2 int b) {} + // :: warning: (inconsistent.constructor.type) + public @TestReflectSibling2 ReflectionConstructorTest( + // :: error: (super.invocation.invalid) + @TestReflectSibling2 int a, @TestReflectSibling2 int b) {} - public void pass1() { - try { - Class c = Class.forName("ReflectionConstructorTest"); - Constructor init = c.getConstructor(new Class[] {Integer.class}); - @TestReflectSibling1 int i = sibling1; - @TestReflectSibling1 Object o = init.newInstance(i); - } catch (Exception ignore) { + public void pass1() { + try { + Class c = Class.forName("ReflectionConstructorTest"); + Constructor init = c.getConstructor(new Class[] {Integer.class}); + @TestReflectSibling1 int i = sibling1; + @TestReflectSibling1 Object o = init.newInstance(i); + } catch (Exception ignore) { + } } - } - public void pass2() { - try { - Class c = Class.forName("ReflectionConstructorTest"); - Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); - @TestReflectSibling2 int a = sibling2; - int b = a; - @TestReflectTop Object inst = init.newInstance(a, b); - } catch (Exception ignore) { + public void pass2() { + try { + Class c = Class.forName("ReflectionConstructorTest"); + Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); + @TestReflectSibling2 int a = sibling2; + int b = a; + @TestReflectTop Object inst = init.newInstance(a, b); + } catch (Exception ignore) { + } } - } - public void fail1() { - try { - Class c = ReflectionConstructorTest.class; - Constructor init = c.getConstructor(new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) - Object o = init.newInstance(sibling2); - } catch (Exception ignore) { + public void fail1() { + try { + Class c = ReflectionConstructorTest.class; + Constructor init = c.getConstructor(new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) + Object o = init.newInstance(sibling2); + } catch (Exception ignore) { + } } - } - public void fail2() { - try { - Class c = ReflectionConstructorTest.class; - Constructor init = c.getConstructor(new Class[] {Integer.class}); - // :: error: (argument.type.incompatible) :: error: (assignment.type.incompatible) - @TestReflectSibling1 Object o = init.newInstance(new Object[] {sibling2}); - } catch (Exception ignore) { + public void fail2() { + try { + Class c = ReflectionConstructorTest.class; + Constructor init = c.getConstructor(new Class[] {Integer.class}); + // :: error: (argument.type.incompatible) :: error: (assignment.type.incompatible) + @TestReflectSibling1 Object o = init.newInstance(new Object[] {sibling2}); + } catch (Exception ignore) { + } } - } - public void fail3() { - try { - Class c = Class.forName("ReflectionConstructorTest"); - Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); - @TestReflectSibling2 int a = sibling2; - @TestReflectSibling1 int b = sibling1; - // :: error: (argument.type.incompatible) - @TestReflectSibling2 Object inst = init.newInstance(a, b); - } catch (Exception ignore) { + public void fail3() { + try { + Class c = Class.forName("ReflectionConstructorTest"); + Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); + @TestReflectSibling2 int a = sibling2; + @TestReflectSibling1 int b = sibling1; + // :: error: (argument.type.incompatible) + @TestReflectSibling2 Object inst = init.newInstance(a, b); + } catch (Exception ignore) { + } } - } } diff --git a/framework/tests/report/Accesses.java b/framework/tests/report/Accesses.java index 15385c603a1..38825288d48 100644 --- a/framework/tests/report/Accesses.java +++ b/framework/tests/report/Accesses.java @@ -1,68 +1,68 @@ import org.checkerframework.common.util.report.qual.*; public class Accesses { - class Demo { - @ReportReadWrite Object read; + class Demo { + @ReportReadWrite Object read; - @ReportWrite Object write; + @ReportWrite Object write; - @ReportCall - Object foo(Object p) { - return null; - } + @ReportCall + Object foo(Object p) { + return null; + } - void implicitRead() { - // :: error: (fieldreadwrite) - Object o = read; - // A read counts as access - // :: error: (fieldreadwrite) - read = null; - // :: error: (fieldreadwrite) - read.toString(); - } + void implicitRead() { + // :: error: (fieldreadwrite) + Object o = read; + // A read counts as access + // :: error: (fieldreadwrite) + read = null; + // :: error: (fieldreadwrite) + read.toString(); + } - void implicitWrite() { - Object o = write; - // :: error: (fieldwrite) - write = null; - write.toString(); - } + void implicitWrite() { + Object o = write; + // :: error: (fieldwrite) + write = null; + write.toString(); + } - void implicitMethod() { - // :: error: (methodcall) - foo(null); - // :: error: (methodcall) - equals(foo(null)); + void implicitMethod() { + // :: error: (methodcall) + foo(null); + // :: error: (methodcall) + equals(foo(null)); + } } - } - void accessesRead(Demo d) { - // :: error: (fieldreadwrite) - Object o = d.read; - // A read counts as access - // :: error: (fieldreadwrite) - d.read = null; - // :: error: (fieldreadwrite) - d.read.toString(); - } + void accessesRead(Demo d) { + // :: error: (fieldreadwrite) + Object o = d.read; + // A read counts as access + // :: error: (fieldreadwrite) + d.read = null; + // :: error: (fieldreadwrite) + d.read.toString(); + } - void accessesWrite(Demo d) { - Object o = d.write; - // :: error: (fieldwrite) - d.write = null; - d.write.toString(); - } + void accessesWrite(Demo d) { + Object o = d.write; + // :: error: (fieldwrite) + d.write = null; + d.write.toString(); + } - void accessesMethod(Demo d) { - // :: error: (methodcall) - d.foo(null); - // :: error: (methodcall) - d.equals(d.foo(null)); - } + void accessesMethod(Demo d) { + // :: error: (methodcall) + d.foo(null); + // :: error: (methodcall) + d.equals(d.foo(null)); + } - Object[] array = new Object[] {1, 2, 3}; + Object[] array = new Object[] {1, 2, 3}; - void accessArray() { - array[0] = 1; - } + void accessArray() { + array[0] = 1; + } } diff --git a/framework/tests/report/CallOverrides.java b/framework/tests/report/CallOverrides.java index d50e2891015..823c204d16d 100644 --- a/framework/tests/report/CallOverrides.java +++ b/framework/tests/report/CallOverrides.java @@ -1,33 +1,33 @@ import org.checkerframework.common.util.report.qual.*; public class CallOverrides { - class A { - void m() {} - } + class A { + void m() {} + } - class B extends A { - @ReportCall - void m() {} - } + class B extends A { + @ReportCall + void m() {} + } - class C extends B {} + class C extends B {} - void test() { - C c = new C(); + void test() { + C c = new C(); - // :: error: (methodcall) - c.m(); + // :: error: (methodcall) + c.m(); - B b = c; + B b = c; - // :: error: (methodcall) - b.m(); + // :: error: (methodcall) + b.m(); - A a = c; + A a = c; - // This call is not reported, because we statically - // don't know that one of the subtypes has the ReportCall - // annotation. - a.m(); - } + // This call is not reported, because we statically + // don't know that one of the subtypes has the ReportCall + // annotation. + a.m(); + } } diff --git a/framework/tests/report/Creation.java b/framework/tests/report/Creation.java index e5ce5f0d980..5aa9c816973 100644 --- a/framework/tests/report/Creation.java +++ b/framework/tests/report/Creation.java @@ -1,35 +1,35 @@ import org.checkerframework.common.util.report.qual.*; public class Creation { - class TestOne { - TestOne() {} + class TestOne { + TestOne() {} - @ReportCreation - TestOne(int i) {} - } + @ReportCreation + TestOne(int i) {} + } - @ReportCreation - class TestAll { - TestAll() {} + @ReportCreation + class TestAll { + TestAll() {} - TestAll(int i) {} - } + TestAll(int i) {} + } - void test() { - // :: error: (creation) - new TestAll(); - // :: error: (creation) - new TestAll(4); + void test() { + // :: error: (creation) + new TestAll(); + // :: error: (creation) + new TestAll(4); - new TestOne(); - // :: error: (creation) - new TestOne(4); - } + new TestOne(); + // :: error: (creation) + new TestOne(4); + } - class TestSub extends TestAll {} + class TestSub extends TestAll {} - void testSub() { - // :: error: (creation) - new TestSub(); - } + void testSub() { + // :: error: (creation) + new TestSub(); + } } diff --git a/framework/tests/report/Inherit.java b/framework/tests/report/Inherit.java index f413bb51c45..bdf8876267a 100644 --- a/framework/tests/report/Inherit.java +++ b/framework/tests/report/Inherit.java @@ -1,14 +1,14 @@ import org.checkerframework.common.util.report.qual.*; public class Inherit { - @ReportInherit - interface A {} + @ReportInherit + interface A {} - class B {} + class B {} - // :: error: (inherit) - class C extends B implements A {} + // :: error: (inherit) + class C extends B implements A {} - // :: error: (inherit) - class D extends C {} + // :: error: (inherit) + class D extends C {} } diff --git a/framework/tests/report/Interface.java b/framework/tests/report/Interface.java index 4475f986129..397450a8eff 100644 --- a/framework/tests/report/Interface.java +++ b/framework/tests/report/Interface.java @@ -5,38 +5,38 @@ import org.checkerframework.common.util.report.qual.*; public class Interface { - interface A { - @ReportCall - boolean equals(Object o); - - @ReportCall - void mine(); - } - - class B implements A { - public void mine() {} - } - - interface C extends A {} - - void foo(A a, B b, C c, Object o) { - // :: error: (methodcall) - if (a.equals(o)) {} - // :: error: (methodcall) - if (b.equals(o)) {} - // :: error: (methodcall) - if (c.equals(o)) {} - - // Don't report this call. - if (o.equals(a)) {} - } - - void bar(A a, B b, C c, Object o) { - // :: error: (methodcall) - a.mine(); - // :: error: (methodcall) - b.mine(); - // :: error: (methodcall) - c.mine(); - } + interface A { + @ReportCall + boolean equals(Object o); + + @ReportCall + void mine(); + } + + class B implements A { + public void mine() {} + } + + interface C extends A {} + + void foo(A a, B b, C c, Object o) { + // :: error: (methodcall) + if (a.equals(o)) {} + // :: error: (methodcall) + if (b.equals(o)) {} + // :: error: (methodcall) + if (c.equals(o)) {} + + // Don't report this call. + if (o.equals(a)) {} + } + + void bar(A a, B b, C c, Object o) { + // :: error: (methodcall) + a.mine(); + // :: error: (methodcall) + b.mine(); + // :: error: (methodcall) + c.mine(); + } } diff --git a/framework/tests/report/Overrides.java b/framework/tests/report/Overrides.java index e018c69e44b..eaaac3eeccc 100644 --- a/framework/tests/report/Overrides.java +++ b/framework/tests/report/Overrides.java @@ -1,26 +1,26 @@ import org.checkerframework.common.util.report.qual.*; public class Overrides { - class A { - void m() {} - } + class A { + void m() {} + } - class B extends A { - @ReportOverride - void m() {} - } + class B extends A { + @ReportOverride + void m() {} + } - class C extends B { - // :: error: (override) - void m() {} - } + class C extends B { + // :: error: (override) + void m() {} + } - // No explicit override -> no message. - class D extends B {} + // No explicit override -> no message. + class D extends B {} - class E extends A { - // Overrides method on same level as B.m - // -> no message. - void m() {} - } + class E extends A { + // Overrides method on same level as B.m + // -> no message. + void m() {} + } } diff --git a/framework/tests/report/Package.java b/framework/tests/report/Package.java index 3fdd14d92e7..801755f5b17 100644 --- a/framework/tests/report/Package.java +++ b/framework/tests/report/Package.java @@ -5,45 +5,45 @@ // :: error: (usage) public class Package extends PatternSyntaxException { - public Package(String desc, String regex, int index) { - // :: error: (usage) - super(desc, regex, index); - } + public Package(String desc, String regex, int index) { + // :: error: (usage) + super(desc, regex, index); + } - @Override - @org.checkerframework.dataflow.qual.Pure - public String getPattern() { - // :: error: (usage) - return super.getPattern(); - } + @Override + @org.checkerframework.dataflow.qual.Pure + public String getPattern() { + // :: error: (usage) + return super.getPattern(); + } - // :: error: (usage) - void m(Pattern p) { - // Access to a constant. // :: error: (usage) - int i = Pattern.CANON_EQ; + void m(Pattern p) { + // Access to a constant. + // :: error: (usage) + int i = Pattern.CANON_EQ; - // Use of inherited method. - // :: error: (usage) - String msg = getMessage(); + // Use of inherited method. + // :: error: (usage) + String msg = getMessage(); - // No report for use of overridden method - - // we get a message when we call super in the overriding method. - // TODO: Would we want "transitive" behavior? I.e. a few levels higher - // in the inheritance hierarchy we could see the class to report. - String pat = this.getPattern(); + // No report for use of overridden method - + // we get a message when we call super in the overriding method. + // TODO: Would we want "transitive" behavior? I.e. a few levels higher + // in the inheritance hierarchy we could see the class to report. + String pat = this.getPattern(); - try { - // :: error: (usage) - p.compile("test((("); - } catch (Package pe) { - // We don't look at supertypes of the types we analyze. - // TODO: Should we? - System.out.println("OK!"); - // :: error: (usage) - } catch (PatternSyntaxException pse) { - // We do get a report for direct uses. - System.out.println("Ha!"); + try { + // :: error: (usage) + p.compile("test((("); + } catch (Package pe) { + // We don't look at supertypes of the types we analyze. + // TODO: Should we? + System.out.println("OK!"); + // :: error: (usage) + } catch (PatternSyntaxException pse) { + // We do get a report for direct uses. + System.out.println("Ha!"); + } } - } } diff --git a/framework/tests/report/TestStub.java b/framework/tests/report/TestStub.java index 72493853557..452e40ab09f 100644 --- a/framework/tests/report/TestStub.java +++ b/framework/tests/report/TestStub.java @@ -1,9 +1,9 @@ public class TestStub { - void demo() { - try { - // :: error: (methodcall) - Class.forName("Evil"); - } catch (Exception e) { + void demo() { + try { + // :: error: (methodcall) + Class.forName("Evil"); + } catch (Exception e) { + } } - } } diff --git a/framework/tests/reportmodifiers/TestModifiers.java b/framework/tests/reportmodifiers/TestModifiers.java index f645645aa39..b253d9d7ebe 100644 --- a/framework/tests/reportmodifiers/TestModifiers.java +++ b/framework/tests/reportmodifiers/TestModifiers.java @@ -3,10 +3,10 @@ * org.checkerframework.checker/tests/src/tests/ReportModifiers.java */ public class TestModifiers { - void test() { - class Inner { - // :: error: (Modifier.native) - native void bad(); + void test() { + class Inner { + // :: error: (Modifier.native) + native void bad(); + } } - } } diff --git a/framework/tests/reporttreekinds/TestTreeKinds.java b/framework/tests/reporttreekinds/TestTreeKinds.java index dcf18aeb01f..c58e465b86b 100644 --- a/framework/tests/reporttreekinds/TestTreeKinds.java +++ b/framework/tests/reporttreekinds/TestTreeKinds.java @@ -3,9 +3,9 @@ * org.checkerframework.checker/tests/src/tests/ReportTreeKindsTest.java */ public class TestTreeKinds { - void test(boolean a, boolean b) { - // :: error: (Tree.Kind.WHILE_LOOP) :: error: (Tree.Kind.CONDITIONAL_AND) - while (a && b) {} - if (b) {} - } + void test(boolean a, boolean b) { + // :: error: (Tree.Kind.WHILE_LOOP) :: error: (Tree.Kind.CONDITIONAL_AND) + while (a && b) {} + if (b) {} + } } diff --git a/framework/tests/returnsreceiver/GenericReturn.java b/framework/tests/returnsreceiver/GenericReturn.java index 6262bd9672a..88325c3c277 100644 --- a/framework/tests/returnsreceiver/GenericReturn.java +++ b/framework/tests/returnsreceiver/GenericReturn.java @@ -2,32 +2,32 @@ public class GenericReturn { - abstract static class Builder> { - abstract @This B setFoo(String foo); - - @SuppressWarnings("unchecked") - @This B retThis() { - return (@This B) this; - } - - @This B dontRetThis() { - // :: error: return.type.incompatible - return null; + abstract static class Builder> { + abstract @This B setFoo(String foo); + + @SuppressWarnings("unchecked") + @This B retThis() { + return (@This B) this; + } + + @This B dontRetThis() { + // :: error: return.type.incompatible + return null; + } } - } - static class Builder1 extends Builder { + static class Builder1 extends Builder { - @This Builder1 setFoo(String foo) { - return this; + @This Builder1 setFoo(String foo) { + return this; + } } - } - static class Builder2 extends Builder { + static class Builder2 extends Builder { - @This Builder2 setFoo(String foo) { - // :: error: return.type.incompatible - return null; + @This Builder2 setFoo(String foo) { + // :: error: return.type.incompatible + return null; + } } - } } diff --git a/framework/tests/returnsreceiver/MethodRef.java b/framework/tests/returnsreceiver/MethodRef.java index 95b2a1871d5..1ca14bf6bdd 100644 --- a/framework/tests/returnsreceiver/MethodRef.java +++ b/framework/tests/returnsreceiver/MethodRef.java @@ -2,25 +2,25 @@ public class MethodRef { - @This MethodRef set(Object o) { - return this; - } + @This MethodRef set(Object o) { + return this; + } - interface Setter { - @This Object consume(Object p); - } + interface Setter { + @This Object consume(Object p); + } - // :: error: methodref.receiver.bound.invalid - Setter co = this::set; + // :: error: methodref.receiver.bound.invalid + Setter co = this::set; - void doNothing(@This MethodRef this) {} + void doNothing(@This MethodRef this) {} - interface Fun { - void run(@This Fun this); - } + interface Fun { + void run(@This Fun this); + } - // The error here is a false positive, due to - // https://github.com/typetools/checker-framework/issues/2931 - // :: error: methodref.receiver.bound.invalid - Fun f = this::doNothing; + // The error here is a false positive, due to + // https://github.com/typetools/checker-framework/issues/2931 + // :: error: methodref.receiver.bound.invalid + Fun f = this::doNothing; } diff --git a/framework/tests/returnsreceiver/NullsAndGenerics.java b/framework/tests/returnsreceiver/NullsAndGenerics.java index 6ddceec0288..ffe2ac22c7f 100644 --- a/framework/tests/returnsreceiver/NullsAndGenerics.java +++ b/framework/tests/returnsreceiver/NullsAndGenerics.java @@ -2,33 +2,33 @@ public class NullsAndGenerics { - private boolean enableProtoAnnotations; + private boolean enableProtoAnnotations; - @SuppressWarnings("unchecked") - private T getProtoExtension( - E element, GeneratedExtension extension) { - // Use this method as the chokepoint for all field annotations processing, so we can - // toggle on/off annotations processing in one place. - if (!enableProtoAnnotations) { - return null; + @SuppressWarnings("unchecked") + private T getProtoExtension( + E element, GeneratedExtension extension) { + // Use this method as the chokepoint for all field annotations processing, so we can + // toggle on/off annotations processing in one place. + if (!enableProtoAnnotations) { + return null; + } + return (T) element.getOptionFields().get(extension.getDescriptor()); } - return (T) element.getOptionFields().get(extension.getDescriptor()); - } - // stubs of relevant classes - private class Message {} + // stubs of relevant classes + private class Message {} - private class ProtoElement { - public Map getOptionFields() { - return null; + private class ProtoElement { + public Map getOptionFields() { + return null; + } } - } - private class FieldDescriptor {} + private class FieldDescriptor {} - private class GeneratedExtension { - public FieldDescriptor getDescriptor() { - return null; + private class GeneratedExtension { + public FieldDescriptor getDescriptor() { + return null; + } } - } } diff --git a/framework/tests/returnsreceiver/OverrideTest.java b/framework/tests/returnsreceiver/OverrideTest.java index 43b726072e8..7e7d01adcca 100644 --- a/framework/tests/returnsreceiver/OverrideTest.java +++ b/framework/tests/returnsreceiver/OverrideTest.java @@ -3,39 +3,39 @@ // Test basic subtyping relationships for the Returns Receiver Checker. public class OverrideTest { - static class Super { + static class Super { - @This Super retThis() { - return this; - } - - Super retWhatever() { - return null; - } - } - - static class Sub extends Super { + @This Super retThis() { + return this; + } - @Override - // :: error: override.return.invalid - Super retThis() { - return null; + Super retWhatever() { + return null; + } } - @Override - // we do not support this case for now; would need to write explicit @This on receiver in - // superclass - // :: error: override.receiver.invalid - @This Super retWhatever() { - return this; + static class Sub extends Super { + + @Override + // :: error: override.return.invalid + Super retThis() { + return null; + } + + @Override + // we do not support this case for now; would need to write explicit @This on receiver in + // superclass + // :: error: override.receiver.invalid + @This Super retWhatever() { + return this; + } } - } - static class Sub2 extends Super { + static class Sub2 extends Super { - @Override - @This Sub2 retThis() { - return this; + @Override + @This Sub2 retThis() { + return this; + } } - } } diff --git a/framework/tests/returnsreceiver/SimpleTest.java b/framework/tests/returnsreceiver/SimpleTest.java index 46eeb557131..29c448e9afb 100644 --- a/framework/tests/returnsreceiver/SimpleTest.java +++ b/framework/tests/returnsreceiver/SimpleTest.java @@ -3,66 +3,66 @@ // Test basic subtyping relationships for the Returns Receiver Checker. public class SimpleTest { - @This SimpleTest retNull() { - // :: error: return.type.incompatible - return null; - } - - @This SimpleTest retThis() { - return this; - } + @This SimpleTest retNull() { + // :: error: return.type.incompatible + return null; + } - @This SimpleTest retThisWrapper(@UnknownThis SimpleTest other, boolean flag) { - if (flag) { - // :: error: return.type.incompatible - return other.retThis(); - } else { - return this.retThis(); + @This SimpleTest retThis() { + return this; } - } - @This SimpleTest retLocalThis() { - SimpleTest x = this; - return x; - } + @This SimpleTest retThisWrapper(@UnknownThis SimpleTest other, boolean flag) { + if (flag) { + // :: error: return.type.incompatible + return other.retThis(); + } else { + return this.retThis(); + } + } - @This SimpleTest retNewLocal() { - SimpleTest x = new SimpleTest(); - // :: error: return.type.incompatible - return x; - } + @This SimpleTest retLocalThis() { + SimpleTest x = this; + return x; + } - // :: error: type.invalid.this.location - @This SimpleTest thisOnParam(@This SimpleTest x) { - return x; - } + @This SimpleTest retNewLocal() { + SimpleTest x = new SimpleTest(); + // :: error: return.type.incompatible + return x; + } - void thisOnLocal() { // :: error: type.invalid.this.location - // :: error: assignment.type.incompatible - @This SimpleTest x = new SimpleTest(); + @This SimpleTest thisOnParam(@This SimpleTest x) { + return x; + } - // :: error: type.invalid.this.location - // :: error: type.argument.type.incompatible - java.util.List<@This String> l = null; - } + void thisOnLocal() { + // :: error: type.invalid.this.location + // :: error: assignment.type.incompatible + @This SimpleTest x = new SimpleTest(); + + // :: error: type.invalid.this.location + // :: error: type.argument.type.incompatible + java.util.List<@This String> l = null; + } - // can write @This on receiver - void thisOnReceiver(@This SimpleTest this) {} + // can write @This on receiver + void thisOnReceiver(@This SimpleTest this) {} - // :: error: type.invalid.this.location :: error: invalid.polymorphic.qualifier.use - @This Object f; + // :: error: type.invalid.this.location :: error: invalid.polymorphic.qualifier.use + @This Object f; - interface I { + interface I { - Object foo(); + Object foo(); - SimpleTest.@This I setBar(); - } + SimpleTest.@This I setBar(); + } - // :: error: type.invalid.this.location - static @This Object thisOnStatic() { - // :: error: return.type.incompatible - return new Object(); - } + // :: error: type.invalid.this.location + static @This Object thisOnStatic() { + // :: error: return.type.incompatible + return new Object(); + } } diff --git a/framework/tests/returnsreceiver/SubtypingTest.java b/framework/tests/returnsreceiver/SubtypingTest.java index d22a371b7e3..37c92cc9bb9 100644 --- a/framework/tests/returnsreceiver/SubtypingTest.java +++ b/framework/tests/returnsreceiver/SubtypingTest.java @@ -2,11 +2,11 @@ // Test basic subtyping relationships for the Returns Receiver Checker. public class SubtypingTest { - void allSubtypingRelationships(@UnknownThis int x, @BottomThis int y) { - @UnknownThis int a = x; - @UnknownThis int b = y; - // :: error: assignment.type.incompatible - @BottomThis int c = x; // expected error on this line - @BottomThis int d = y; - } + void allSubtypingRelationships(@UnknownThis int x, @BottomThis int y) { + @UnknownThis int a = x; + @UnknownThis int b = y; + // :: error: assignment.type.incompatible + @BottomThis int c = x; // expected error on this line + @BottomThis int d = y; + } } diff --git a/framework/tests/returnsreceiverautovalue/Animal.java b/framework/tests/returnsreceiverautovalue/Animal.java index 806c5552354..7a6081a8298 100644 --- a/framework/tests/returnsreceiverautovalue/Animal.java +++ b/framework/tests/returnsreceiverautovalue/Animal.java @@ -1,4 +1,5 @@ import com.google.auto.value.AutoValue; + import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.common.returnsreceiver.qual.*; @@ -8,67 +9,67 @@ */ @AutoValue abstract class Animal { - abstract String name(); + abstract String name(); + + abstract @Nullable String habitat(); + + abstract int numberOfLegs(); + + static Builder builder() { + return new AutoValue_Animal.Builder(); + } - abstract @Nullable String habitat(); + @AutoValue.Builder + abstract static class Builder { - abstract int numberOfLegs(); + abstract Builder setName(String value); - static Builder builder() { - return new AutoValue_Animal.Builder(); - } + abstract Builder setNumberOfLegs(int value); - @AutoValue.Builder - abstract static class Builder { + abstract Builder setHabitat(String value); - abstract Builder setName(String value); + abstract Animal build(); - abstract Builder setNumberOfLegs(int value); + // wrapper methods to ensure @This annotations are getting added properly + @This Builder wrapperSetName() { + return setName("dummy"); + } - abstract Builder setHabitat(String value); + @This Builder wrapperSetNumberOfLegs() { + return setNumberOfLegs(3); + } - abstract Animal build(); + @This Builder wrapperSetHabitat() { + return setHabitat("dummy"); + } + } + + public static void buildSomethingWrong() { + Builder b = builder(); + b.setName("Frank"); + b.build(); + } + + public static void buildSomethingRight() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.build(); + } - // wrapper methods to ensure @This annotations are getting added properly - @This Builder wrapperSetName() { - return setName("dummy"); + public static void buildSomethingRightIncludeOptional() { + Builder b = builder(); + b.setName("Frank"); + b.setNumberOfLegs(4); + b.setHabitat("jungle"); + b.build(); } - @This Builder wrapperSetNumberOfLegs() { - return setNumberOfLegs(3); + public static void buildSomethingWrongFluent() { + builder().setName("Frank").build(); } - @This Builder wrapperSetHabitat() { - return setHabitat("dummy"); + public static void buildSomethingRightFluent() { + builder().setName("Jim").setNumberOfLegs(7).build(); } - } - - public static void buildSomethingWrong() { - Builder b = builder(); - b.setName("Frank"); - b.build(); - } - - public static void buildSomethingRight() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.build(); - } - - public static void buildSomethingRightIncludeOptional() { - Builder b = builder(); - b.setName("Frank"); - b.setNumberOfLegs(4); - b.setHabitat("jungle"); - b.build(); - } - - public static void buildSomethingWrongFluent() { - builder().setName("Frank").build(); - } - - public static void buildSomethingRightFluent() { - builder().setName("Jim").setNumberOfLegs(7).build(); - } } diff --git a/framework/tests/returnsreceiverlombok/BuilderMethodRef.java b/framework/tests/returnsreceiverlombok/BuilderMethodRef.java index ff3807d68f4..8cdfd4c28a3 100644 --- a/framework/tests/returnsreceiverlombok/BuilderMethodRef.java +++ b/framework/tests/returnsreceiverlombok/BuilderMethodRef.java @@ -1,30 +1,32 @@ -import java.util.Optional; import lombok.Builder; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; + import org.checkerframework.common.returnsreceiver.qual.*; +import java.util.Optional; + @Builder @Accessors(fluent = true) public class BuilderMethodRef { - @Getter @Setter @lombok.NonNull String foo; - @Getter @Setter Object bar; + @Getter @Setter @lombok.NonNull String foo; + @Getter @Setter Object bar; - public static void test(Optional opt) { - BuilderMethodRefBuilder b = builder().foo("Hello"); - opt.ifPresent(b::bar); - b.build(); - } + public static void test(Optional opt) { + BuilderMethodRefBuilder b = builder().foo("Hello"); + opt.ifPresent(b::bar); + b.build(); + } } class CustomBuilderMethodRefBuilder extends BuilderMethodRef.BuilderMethodRefBuilder { - // wrapper methods to ensure @This annotations are getting added properly - BuilderMethodRef.@This BuilderMethodRefBuilder wrapperFoo() { - return foo("dummy"); - } + // wrapper methods to ensure @This annotations are getting added properly + BuilderMethodRef.@This BuilderMethodRefBuilder wrapperFoo() { + return foo("dummy"); + } - BuilderMethodRef.@This BuilderMethodRefBuilder wrapperBar() { - return bar(new Object()); - } + BuilderMethodRef.@This BuilderMethodRefBuilder wrapperBar() { + return bar(new Object()); + } } diff --git a/framework/tests/returnsreceiverlombok/BuilderTest.java b/framework/tests/returnsreceiverlombok/BuilderTest.java index 4e18078386c..3368d7de93e 100644 --- a/framework/tests/returnsreceiverlombok/BuilderTest.java +++ b/framework/tests/returnsreceiverlombok/BuilderTest.java @@ -3,40 +3,41 @@ import lombok.NonNull; import lombok.Setter; import lombok.experimental.Accessors; + import org.checkerframework.common.returnsreceiver.qual.*; @Builder @Accessors(fluent = true) public class BuilderTest { - @Getter @Setter private Integer x; - @Getter @Setter @NonNull private Integer y; - @Getter @Setter @NonNull private Integer z; + @Getter @Setter private Integer x; + @Getter @Setter @NonNull private Integer y; + @Getter @Setter @NonNull private Integer z; - public static void test_simplePattern() { - BuilderTest.builder().x(0).y(0).build(); - BuilderTest.builder().y(0).build(); - BuilderTest.builder().y(0).z(5).build(); - } + public static void test_simplePattern() { + BuilderTest.builder().x(0).y(0).build(); + BuilderTest.builder().y(0).build(); + BuilderTest.builder().y(0).z(5).build(); + } - public static void test_builderVar() { - final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); - goodBuilder.x(0); - goodBuilder.y(0); - goodBuilder.build(); - } + public static void test_builderVar() { + final BuilderTest.BuilderTestBuilder goodBuilder = new BuilderTestBuilder(); + goodBuilder.x(0); + goodBuilder.y(0); + goodBuilder.build(); + } } class CustomBuilderTestBuilder extends BuilderTest.BuilderTestBuilder { - // wrapper methods to ensure @This annotations are getting added properly - BuilderTest.@This BuilderTestBuilder wrapperX() { - return x(0); - } + // wrapper methods to ensure @This annotations are getting added properly + BuilderTest.@This BuilderTestBuilder wrapperX() { + return x(0); + } - BuilderTest.@This BuilderTestBuilder wrapperY() { - return y(1); - } + BuilderTest.@This BuilderTestBuilder wrapperY() { + return y(1); + } - BuilderTest.@This BuilderTestBuilder wrapperZ() { - return z(2); - } + BuilderTest.@This BuilderTestBuilder wrapperZ() { + return z(2); + } } diff --git a/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java b/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java index 684317a590e..a222f9f4298 100644 --- a/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java +++ b/framework/tests/stringpatterns/stringpatterns-full/StringPatternsUsage.java @@ -2,87 +2,87 @@ public class StringPatternsUsage { - void requiresA(@PatternA String arg) {} + void requiresA(@PatternA String arg) {} - void requiresB(@PatternB String arg) {} + void requiresB(@PatternB String arg) {} - void requiresC(@PatternC String arg) {} + void requiresC(@PatternC String arg) {} - void requiresAB(@PatternAB String arg) {} + void requiresAB(@PatternAB String arg) {} - void requiresBC(@PatternBC String arg) {} + void requiresBC(@PatternBC String arg) {} - void requiresAC(@PatternAC String arg) {} + void requiresAC(@PatternAC String arg) {} - void requiresAny(String arg) {} + void requiresAny(String arg) {} - void m() { + void m() { - String a = "A"; - String b = "B"; - String c = "C"; - String d = "D"; - String e = ""; + String a = "A"; + String b = "B"; + String c = "C"; + String d = "D"; + String e = ""; - requiresA(a); - // :: error: (argument.type.incompatible) - requiresB(a); - // :: error: (argument.type.incompatible) - requiresC(a); - requiresAB(a); - // :: error: (argument.type.incompatible) - requiresBC(a); - requiresAC(a); - requiresAny(a); + requiresA(a); + // :: error: (argument.type.incompatible) + requiresB(a); + // :: error: (argument.type.incompatible) + requiresC(a); + requiresAB(a); + // :: error: (argument.type.incompatible) + requiresBC(a); + requiresAC(a); + requiresAny(a); - // :: error: (argument.type.incompatible) - requiresA(b); - requiresB(b); - // :: error: (argument.type.incompatible) - requiresC(b); - requiresAB(b); - requiresBC(b); - // :: error: (argument.type.incompatible) - requiresAC(b); - requiresAny(b); + // :: error: (argument.type.incompatible) + requiresA(b); + requiresB(b); + // :: error: (argument.type.incompatible) + requiresC(b); + requiresAB(b); + requiresBC(b); + // :: error: (argument.type.incompatible) + requiresAC(b); + requiresAny(b); - // :: error: (argument.type.incompatible) - requiresA(c); - // :: error: (argument.type.incompatible) - requiresB(c); - requiresC(c); - // :: error: (argument.type.incompatible) - requiresAB(c); - requiresBC(c); - requiresAC(c); - requiresAny(c); + // :: error: (argument.type.incompatible) + requiresA(c); + // :: error: (argument.type.incompatible) + requiresB(c); + requiresC(c); + // :: error: (argument.type.incompatible) + requiresAB(c); + requiresBC(c); + requiresAC(c); + requiresAny(c); - // :: error: (argument.type.incompatible) - requiresA(d); - // :: error: (argument.type.incompatible) - requiresB(d); - // :: error: (argument.type.incompatible) - requiresC(d); - // :: error: (argument.type.incompatible) - requiresAB(d); - // :: error: (argument.type.incompatible) - requiresBC(d); - // :: error: (argument.type.incompatible) - requiresAC(d); - requiresAny(d); + // :: error: (argument.type.incompatible) + requiresA(d); + // :: error: (argument.type.incompatible) + requiresB(d); + // :: error: (argument.type.incompatible) + requiresC(d); + // :: error: (argument.type.incompatible) + requiresAB(d); + // :: error: (argument.type.incompatible) + requiresBC(d); + // :: error: (argument.type.incompatible) + requiresAC(d); + requiresAny(d); - // :: error: (argument.type.incompatible) - requiresA(e); - // :: error: (argument.type.incompatible) - requiresB(e); - // :: error: (argument.type.incompatible) - requiresC(e); - // :: error: (argument.type.incompatible) - requiresAB(e); - // :: error: (argument.type.incompatible) - requiresBC(e); - // :: error: (argument.type.incompatible) - requiresAC(e); - requiresAny(e); - } + // :: error: (argument.type.incompatible) + requiresA(e); + // :: error: (argument.type.incompatible) + requiresB(e); + // :: error: (argument.type.incompatible) + requiresC(e); + // :: error: (argument.type.incompatible) + requiresAB(e); + // :: error: (argument.type.incompatible) + requiresBC(e); + // :: error: (argument.type.incompatible) + requiresAC(e); + requiresAny(e); + } } diff --git a/framework/tests/subtyping/InvariantArrays.java b/framework/tests/subtyping/InvariantArrays.java index 38cd7b42a6f..c652d3b9628 100644 --- a/framework/tests/subtyping/InvariantArrays.java +++ b/framework/tests/subtyping/InvariantArrays.java @@ -1,53 +1,54 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.LinkedList; import java.util.List; -import org.checkerframework.framework.testchecker.util.*; public class InvariantArrays { - Object[] oa; - @Encrypted Object[] eoa; + Object[] oa; + @Encrypted Object[] eoa; - String[] sa; - @Encrypted String[] esa; + String[] sa; + @Encrypted String[] esa; - void tests() { - // TODOINVARR:: error: (assignment.type.incompatible) - oa = eoa; - // This error only occurs with the Encrypted type system; - // other type systems don't suffer an error here. - // :: error: (assignment.type.incompatible) - eoa = oa; - // TODOINVARR:: error: (assignment.type.incompatible) - oa = esa; - // OK - oa = sa; - eoa = esa; - } + void tests() { + // TODOINVARR:: error: (assignment.type.incompatible) + oa = eoa; + // This error only occurs with the Encrypted type system; + // other type systems don't suffer an error here. + // :: error: (assignment.type.incompatible) + eoa = oa; + // TODOINVARR:: error: (assignment.type.incompatible) + oa = esa; + // OK + oa = sa; + eoa = esa; + } - List[] loa; - LinkedList[] llra; - List[] leoa; - LinkedList[] llera; - @Encrypted List[] eloa; - @Encrypted LinkedList[] ellra; - @Encrypted List[] eleoa; - @Encrypted LinkedList[] ellera; + List[] loa; + LinkedList[] llra; + List[] leoa; + LinkedList[] llera; + @Encrypted List[] eloa; + @Encrypted LinkedList[] ellra; + @Encrypted List[] eleoa; + @Encrypted LinkedList[] ellera; - void genericTests() { - // OK - loa = llra; - loa = leoa; - loa = llera; - eloa = ellra; - leoa = llera; - eloa = ellera; + void genericTests() { + // OK + loa = llra; + loa = leoa; + loa = llera; + eloa = ellra; + leoa = llera; + eloa = ellera; - // TODOINVARR:: error: (assignment.type.incompatible) - loa = eloa; - // TODOINVARR:: error: (assignment.type.incompatible) - loa = ellra; - // :: error: (assignment.type.incompatible) - eleoa = eloa; - // TODOINVARR:: error: (assignment.type.incompatible) - leoa = eleoa; - } + // TODOINVARR:: error: (assignment.type.incompatible) + loa = eloa; + // TODOINVARR:: error: (assignment.type.incompatible) + loa = ellra; + // :: error: (assignment.type.incompatible) + eleoa = eloa; + // TODOINVARR:: error: (assignment.type.incompatible) + leoa = eleoa; + } } diff --git a/framework/tests/subtyping/Poly.java b/framework/tests/subtyping/Poly.java index b369f9a181b..3b572cd24a5 100644 --- a/framework/tests/subtyping/Poly.java +++ b/framework/tests/subtyping/Poly.java @@ -1,88 +1,89 @@ +import org.checkerframework.framework.testchecker.util.*; + import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.framework.testchecker.util.*; public class Poly { - void test() { - - @Encrypted String s = encrypt("as0d78f9(*#4j"); - String t = "foo"; - - @Encrypted String x1 = id(s); // valid - // :: error: (assignment.type.incompatible) - @Encrypted String x2 = id(t); // error - String x3 = id(s); // valid - String x4 = id(t); // valid - - @Encrypted String y01 = combine(s, s); // valid - // :: error: (assignment.type.incompatible) - @Encrypted String y02 = combine(s, t); // error - // :: error: (assignment.type.incompatible) - @Encrypted String y03 = combine(t, t); // error - - String y11 = combine(s, s); // valid - String y12 = combine(s, t); // valid - String y13 = combine(t, t); // valid - } - - @PolyEncrypted String id(@PolyEncrypted String s) { - return s; - } - - @PolyEncrypted String combine(@PolyEncrypted String s, @PolyEncrypted String t) { - // :: error: (argument.type.incompatible) - sendOverNet(s); // error - return s; - } - - void sendOverNet(@Encrypted String msg) {} - - List<@PolyEncrypted String> duplicate(@PolyEncrypted String s) { - return null; - } - - @PolyEncrypted String[] duplicateAsArray(@PolyEncrypted String s) { - return null; - } - - void test2() { - @Encrypted String s = encrypt("p9aS*7dfa0w9e84r"); - List<@Encrypted String> lst = duplicate(s); - @Encrypted String[] arr = duplicateAsArray(s); - } - - @PolyEncrypted String substitute(Map map) { - return encrypt(null); - } - - @PolyEncrypted String substituteSuper(Map map) { - return encrypt(null); - } - - void test3() { - // :: error: (assignment.type.incompatible) - @Encrypted String s = substitute(new HashMap()); - @Encrypted String t = substitute(new HashMap()); - - // :: error: (assignment.type.incompatible) - @Encrypted String q = substituteSuper(new HashMap()); - @Encrypted String r = substituteSuper(new HashMap()); - } - - // Test assignment to poly - @PolyEncrypted String test4(@PolyEncrypted String s) { - if (s == null) { - return encrypt(null); // valid - } else { - // :: error: (return.type.incompatible) - return "m"; // invalid + void test() { + + @Encrypted String s = encrypt("as0d78f9(*#4j"); + String t = "foo"; + + @Encrypted String x1 = id(s); // valid + // :: error: (assignment.type.incompatible) + @Encrypted String x2 = id(t); // error + String x3 = id(s); // valid + String x4 = id(t); // valid + + @Encrypted String y01 = combine(s, s); // valid + // :: error: (assignment.type.incompatible) + @Encrypted String y02 = combine(s, t); // error + // :: error: (assignment.type.incompatible) + @Encrypted String y03 = combine(t, t); // error + + String y11 = combine(s, s); // valid + String y12 = combine(s, t); // valid + String y13 = combine(t, t); // valid + } + + @PolyEncrypted String id(@PolyEncrypted String s) { + return s; + } + + @PolyEncrypted String combine(@PolyEncrypted String s, @PolyEncrypted String t) { + // :: error: (argument.type.incompatible) + sendOverNet(s); // error + return s; + } + + void sendOverNet(@Encrypted String msg) {} + + List<@PolyEncrypted String> duplicate(@PolyEncrypted String s) { + return null; } - } - @SuppressWarnings("encrypted") - static @Encrypted String encrypt(String s) { - return (@Encrypted String) s; - } + @PolyEncrypted String[] duplicateAsArray(@PolyEncrypted String s) { + return null; + } + + void test2() { + @Encrypted String s = encrypt("p9aS*7dfa0w9e84r"); + List<@Encrypted String> lst = duplicate(s); + @Encrypted String[] arr = duplicateAsArray(s); + } + + @PolyEncrypted String substitute(Map map) { + return encrypt(null); + } + + @PolyEncrypted String substituteSuper(Map map) { + return encrypt(null); + } + + void test3() { + // :: error: (assignment.type.incompatible) + @Encrypted String s = substitute(new HashMap()); + @Encrypted String t = substitute(new HashMap()); + + // :: error: (assignment.type.incompatible) + @Encrypted String q = substituteSuper(new HashMap()); + @Encrypted String r = substituteSuper(new HashMap()); + } + + // Test assignment to poly + @PolyEncrypted String test4(@PolyEncrypted String s) { + if (s == null) { + return encrypt(null); // valid + } else { + // :: error: (return.type.incompatible) + return "m"; // invalid + } + } + + @SuppressWarnings("encrypted") + static @Encrypted String encrypt(String s) { + return (@Encrypted String) s; + } } diff --git a/framework/tests/subtyping/Simple.java b/framework/tests/subtyping/Simple.java index 47480a4c3dc..ac0a4249da6 100644 --- a/framework/tests/subtyping/Simple.java +++ b/framework/tests/subtyping/Simple.java @@ -1,34 +1,35 @@ +import org.checkerframework.framework.testchecker.util.Encrypted; + import java.util.LinkedList; import java.util.List; -import org.checkerframework.framework.testchecker.util.Encrypted; abstract class BasicFunctionality { - @Encrypted String encrypt(String s) { - byte[] b = s.getBytes(); - for (int i = 0; i < b.length; b[i++]++) {} - // :: warning: (cast.unsafe) - return (@Encrypted String) new String(b); - } + @Encrypted String encrypt(String s) { + byte[] b = s.getBytes(); + for (int i = 0; i < b.length; b[i++]++) {} + // :: warning: (cast.unsafe) + return (@Encrypted String) new String(b); + } - abstract void sendOverTheInternet(@Encrypted String s); + abstract void sendOverTheInternet(@Encrypted String s); - void test() { - @Encrypted String s = encrypt("foo"); // valid - sendOverTheInternet(s); // valid + void test() { + @Encrypted String s = encrypt("foo"); // valid + sendOverTheInternet(s); // valid - String t = encrypt("bar"); // valid (subtype) - sendOverTheInternet(t); // valid (flow) + String t = encrypt("bar"); // valid (subtype) + sendOverTheInternet(t); // valid (flow) - List<@Encrypted String> lst = new LinkedList<>(); - lst.add(s); - lst.add(t); + List<@Encrypted String> lst = new LinkedList<>(); + lst.add(s); + lst.add(t); - for (@Encrypted String str : lst) { - sendOverTheInternet(str); - } + for (@Encrypted String str : lst) { + sendOverTheInternet(str); + } - // for (String str : lst) - // sendOverTheInternet(str); // should be valid! - } + // for (String str : lst) + // sendOverTheInternet(str); // should be valid! + } } diff --git a/framework/tests/subtyping/ThisType.java b/framework/tests/subtyping/ThisType.java index 89cd2c5ad4c..a4e253ca36e 100644 --- a/framework/tests/subtyping/ThisType.java +++ b/framework/tests/subtyping/ThisType.java @@ -1,16 +1,16 @@ import org.checkerframework.framework.testchecker.util.*; public class ThisType { - void t1(@Encrypted ThisType this) { - @Encrypted ThisType l1 = this; - ThisType l2 = this; - // Type of l2 is refined by flow -> legal - l1 = l2; - } + void t1(@Encrypted ThisType this) { + @Encrypted ThisType l1 = this; + ThisType l2 = this; + // Type of l2 is refined by flow -> legal + l1 = l2; + } - void t2(ThisType this) { - ThisType l1 = this; - // :: error: (assignment.type.incompatible) - @Encrypted ThisType l2 = this; - } + void t2(ThisType this) { + ThisType l1 = this; + // :: error: (assignment.type.incompatible) + @Encrypted ThisType l2 = this; + } } diff --git a/framework/tests/subtyping/ThrowCatch.java b/framework/tests/subtyping/ThrowCatch.java index 71a0402b525..b5331d3f78b 100644 --- a/framework/tests/subtyping/ThrowCatch.java +++ b/framework/tests/subtyping/ThrowCatch.java @@ -8,34 +8,34 @@ */ abstract class ThrowCatch { - void throwsNoncritical() throws Exception { - throw new Exception(); - } - - void throwsCritical() throws @Critical Exception { - throw new @Critical Exception(); - } - - void catches() { - try { - throwsNoncritical(); - } catch (Exception e) { + void throwsNoncritical() throws Exception { + throw new Exception(); } - try { - throwsNoncritical(); - // :: error: (type.incompatible) - } catch (@Critical Exception e) { + void throwsCritical() throws @Critical Exception { + throw new @Critical Exception(); } - try { - throwsCritical(); - } catch (Exception e) { - } + void catches() { + try { + throwsNoncritical(); + } catch (Exception e) { + } + + try { + throwsNoncritical(); + // :: error: (type.incompatible) + } catch (@Critical Exception e) { + } + + try { + throwsCritical(); + } catch (Exception e) { + } - try { - throwsCritical(); - } catch (@Critical Exception e) { + try { + throwsCritical(); + } catch (@Critical Exception e) { + } } - } } diff --git a/framework/tests/subtyping/UnusedTypes.java b/framework/tests/subtyping/UnusedTypes.java index dee5a71e31a..640e6702a30 100644 --- a/framework/tests/subtyping/UnusedTypes.java +++ b/framework/tests/subtyping/UnusedTypes.java @@ -1,21 +1,22 @@ -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.Unused; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + // This test case is quite meaningless, as it's not run with the // Nullness Checker. See nullness/UnusedNullness.java instead. public class UnusedTypes { - @SubtypeOf({}) - @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) - public @interface Prototype {} + @SubtypeOf({}) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + public @interface Prototype {} - @Unused(when = Prototype.class) - public Object ppt; + @Unused(when = Prototype.class) + public Object ppt; - protected @Prototype UnusedTypes() { - // It should be legal to initialize an unused field to null in the constructor. - this.ppt = null; - } + protected @Prototype UnusedTypes() { + // It should be legal to initialize an unused field to null in the constructor. + this.ppt = null; + } } diff --git a/framework/tests/typedeclbounds/BooleanExpression.java b/framework/tests/typedeclbounds/BooleanExpression.java index a74c69881c2..60a85726054 100644 --- a/framework/tests/typedeclbounds/BooleanExpression.java +++ b/framework/tests/typedeclbounds/BooleanExpression.java @@ -3,51 +3,51 @@ // Test the @S2 upperbound for boolean expression class BooleanExpression { - boolean equal; - boolean notEqual; - boolean lessThan; - boolean greaterThan; - boolean lessThanEqual; - boolean greaterThanEqual; - - // ensures @Top is not within the bounds for boolean - // :: error: (type.invalid.annotations.on.use) - @Top boolean topBoolean; - - // ensures @S1 is not within the bounds for boolean - // :: error: (type.invalid.annotations.on.use) - @S1 boolean s1Boolean; - - @Bottom boolean bottomBoolean; - - // ensures the @S2 upperbound is applied to binary comparison - void compareTop(@Top Integer x, @Top Integer y) { - equal = x == y; - notEqual = x != y; - lessThan = x < y; - greaterThan = x > y; - lessThanEqual = x <= y; - greaterThanEqual = x >= y; - } - - // ensures the @S2 upperbound is applied to binary comparison - void compareBottom(@Bottom Integer x, @Bottom Integer y) { - equal = x == y; - notEqual = x != y; - lessThan = x < y; - greaterThan = x > y; - lessThanEqual = x <= y; - greaterThanEqual = x >= y; - } - - // ensures the default type is not @Bottom - void assignBottom() { - // :: error: (assignment.type.incompatible) - bottomBoolean = equal; - } - - // ensures the @S2 upperbound is applied to instanceof - static @S2 boolean isNumber(@Top Object obj) { - return obj instanceof Number; - } + boolean equal; + boolean notEqual; + boolean lessThan; + boolean greaterThan; + boolean lessThanEqual; + boolean greaterThanEqual; + + // ensures @Top is not within the bounds for boolean + // :: error: (type.invalid.annotations.on.use) + @Top boolean topBoolean; + + // ensures @S1 is not within the bounds for boolean + // :: error: (type.invalid.annotations.on.use) + @S1 boolean s1Boolean; + + @Bottom boolean bottomBoolean; + + // ensures the @S2 upperbound is applied to binary comparison + void compareTop(@Top Integer x, @Top Integer y) { + equal = x == y; + notEqual = x != y; + lessThan = x < y; + greaterThan = x > y; + lessThanEqual = x <= y; + greaterThanEqual = x >= y; + } + + // ensures the @S2 upperbound is applied to binary comparison + void compareBottom(@Bottom Integer x, @Bottom Integer y) { + equal = x == y; + notEqual = x != y; + lessThan = x < y; + greaterThan = x > y; + lessThanEqual = x <= y; + greaterThanEqual = x >= y; + } + + // ensures the default type is not @Bottom + void assignBottom() { + // :: error: (assignment.type.incompatible) + bottomBoolean = equal; + } + + // ensures the @S2 upperbound is applied to instanceof + static @S2 boolean isNumber(@Top Object obj) { + return obj instanceof Number; + } } diff --git a/framework/tests/typedeclbounds/StringConcatConversion.java b/framework/tests/typedeclbounds/StringConcatConversion.java index 8b59bed5869..3a411c6f97d 100644 --- a/framework/tests/typedeclbounds/StringConcatConversion.java +++ b/framework/tests/typedeclbounds/StringConcatConversion.java @@ -1,29 +1,30 @@ +import org.checkerframework.framework.testchecker.typedeclbounds.quals.*; + import java.util.ArrayList; import java.util.List; -import org.checkerframework.framework.testchecker.typedeclbounds.quals.*; // Test the @S1 upperbound applied to string conversion public class StringConcatConversion { - @Top List ts = new ArrayList<>(); + @Top List ts = new ArrayList<>(); - // :: error: (type.invalid.annotations.on.use) - @Top String topString; + // :: error: (type.invalid.annotations.on.use) + @Top String topString; - @Bottom String bottomString; + @Bottom String bottomString; - void foo(@Top T topT, @Bottom T bottomT) { - throwException("test normal top to bottom conversion" + ts); - throwException("test type variable" + topT); - throwException("test wildcard" + ts.get(0)); + void foo(@Top T topT, @Bottom T bottomT) { + throwException("test normal top to bottom conversion" + ts); + throwException("test type variable" + topT); + throwException("test wildcard" + ts.get(0)); - // the converted string of topT has type @S1 - // :: error: (compound.assignment.type.incompatible) - bottomString += topT; + // the converted string of topT has type @S1 + // :: error: (compound.assignment.type.incompatible) + bottomString += topT; - // the converted string of bottomT has type @Bottom - bottomString += bottomT; - } + // the converted string of bottomT has type @Bottom + bottomString += bottomT; + } - void throwException(@S1 String s) {} + void throwException(@S1 String s) {} } diff --git a/framework/tests/typedecldefault/BoundsAndDefaults.java b/framework/tests/typedecldefault/BoundsAndDefaults.java index 3bdc026516a..8efc5ab3846 100644 --- a/framework/tests/typedecldefault/BoundsAndDefaults.java +++ b/framework/tests/typedecldefault/BoundsAndDefaults.java @@ -4,27 +4,28 @@ // @TypeDeclDefaultBottom is the default qualifier in hierarchy. @SuppressWarnings("inconsistent.constructor.type") public class BoundsAndDefaults { - static @TypeDeclDefaultMiddle class MiddleClass {} + static @TypeDeclDefaultMiddle class MiddleClass {} - @TypeDeclDefaultBottom MiddleClass method(@TypeDeclDefaultMiddle MiddleClass middle, MiddleClass noAnno) { - noAnno = middle; - // :: error: (return.type.incompatible) - return noAnno; - } + @TypeDeclDefaultBottom MiddleClass method(@TypeDeclDefaultMiddle MiddleClass middle, MiddleClass noAnno) { + noAnno = middle; + // :: error: (return.type.incompatible) + return noAnno; + } - // :: error: (type.invalid.annotations.on.use) - void tops(@TypeDeclDefaultTop MiddleClass invalid) { - @TypeDeclDefaultTop MiddleClass local = null; - } + // :: error: (type.invalid.annotations.on.use) + void tops(@TypeDeclDefaultTop MiddleClass invalid) { + @TypeDeclDefaultTop MiddleClass local = null; + } - @NoDefaultQualifierForUse(TypeDeclDefaultTop.class) - static @TypeDeclDefaultMiddle class MiddleBoundClass { - @TypeDeclDefaultMiddle MiddleBoundClass() {} - } + @NoDefaultQualifierForUse(TypeDeclDefaultTop.class) + static @TypeDeclDefaultMiddle class MiddleBoundClass { + @TypeDeclDefaultMiddle MiddleBoundClass() {} + } - @TypeDeclDefaultBottom MiddleBoundClass method(@TypeDeclDefaultMiddle MiddleBoundClass middle, MiddleBoundClass noAnno) { - // :: error: (assignment.type.incompatible) - noAnno = middle; - return noAnno; - } + @TypeDeclDefaultBottom MiddleBoundClass method( + @TypeDeclDefaultMiddle MiddleBoundClass middle, MiddleBoundClass noAnno) { + // :: error: (assignment.type.incompatible) + noAnno = middle; + return noAnno; + } } diff --git a/framework/tests/typedecldefault/TestDefaultForTypeDecl.java b/framework/tests/typedecldefault/TestDefaultForTypeDecl.java index 1794da4bf89..35d198995e9 100644 --- a/framework/tests/typedecldefault/TestDefaultForTypeDecl.java +++ b/framework/tests/typedecldefault/TestDefaultForTypeDecl.java @@ -5,20 +5,20 @@ // @TypeDeclDefaultTop is the default for type declarations. @NoDefaultQualifierForUse(TypeDeclDefaultTop.class) public @TypeDeclDefaultTop class TestDefaultForTypeDecl { - void test(@TypeDeclDefaultTop TestDefaultForTypeDecl arg) {} + void test(@TypeDeclDefaultTop TestDefaultForTypeDecl arg) {} - void testUnannotated(TestDefaultForTypeDecl arg) {} + void testUnannotated(TestDefaultForTypeDecl arg) {} - void testOtherQual( - @TypeDeclDefaultBottom TestDefaultForTypeDecl arg, TestDefaultForTypeDecl arg1) { - arg = arg1; - } + void testOtherQual( + @TypeDeclDefaultBottom TestDefaultForTypeDecl arg, TestDefaultForTypeDecl arg1) { + arg = arg1; + } - void method() { - Object @TypeDeclDefaultBottom [] object = new Object[] {null}; - this.genericMethod(); - new TestDefaultForTypeDecl() {}; - } + void method() { + Object @TypeDeclDefaultBottom [] object = new Object[] {null}; + this.genericMethod(); + new TestDefaultForTypeDecl() {}; + } - <@TypeDeclDefaultBottom T extends @TypeDeclDefaultBottom Object> void genericMethod() {} + <@TypeDeclDefaultBottom T extends @TypeDeclDefaultBottom Object> void genericMethod() {} } diff --git a/framework/tests/value-ignore-range-overflow/Index117.java b/framework/tests/value-ignore-range-overflow/Index117.java index 5fc0dac2826..ace3ee9bf44 100644 --- a/framework/tests/value-ignore-range-overflow/Index117.java +++ b/framework/tests/value-ignore-range-overflow/Index117.java @@ -2,10 +2,10 @@ public class Index117 { - public static void foo(boolean includeIndex, String[] roots) { - @IntRange(from = 2, to = Integer.MAX_VALUE) int x = (includeIndex ? 2 : 1) * roots.length + 2; - @IntRange(from = 2, to = Integer.MAX_VALUE) int y = 2 * roots.length + 2; - @IntRange(from = 2, to = Integer.MAX_VALUE) int z = roots.length + 2; - @IntRange(from = 2, to = 2) int w = 0 + 2; - } + public static void foo(boolean includeIndex, String[] roots) { + @IntRange(from = 2, to = Integer.MAX_VALUE) int x = (includeIndex ? 2 : 1) * roots.length + 2; + @IntRange(from = 2, to = Integer.MAX_VALUE) int y = 2 * roots.length + 2; + @IntRange(from = 2, to = Integer.MAX_VALUE) int z = roots.length + 2; + @IntRange(from = 2, to = 2) int w = 0 + 2; + } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementEq.java b/framework/tests/value-ignore-range-overflow/RefinementEq.java index 451f9966787..73a533c1eed 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementEq.java +++ b/framework/tests/value-ignore-range-overflow/RefinementEq.java @@ -2,27 +2,27 @@ public class RefinementEq { - void test_equal(int a, int j, int s) { + void test_equal(int a, int j, int s) { - if (-1 == a) { - @IntRange(from = -1) int b = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int c = a; - } + if (-1 == a) { + @IntRange(from = -1) int b = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int c = a; + } - if (0 == j) { - @IntRange(from = 0) int k = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int l = j; - } + if (0 == j) { + @IntRange(from = 0) int k = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int l = j; + } - if (1 == s) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; + if (1 == s) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; + } } - } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementGT.java b/framework/tests/value-ignore-range-overflow/RefinementGT.java index 8ddd30c8819..79b7a6695a8 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementGT.java +++ b/framework/tests/value-ignore-range-overflow/RefinementGT.java @@ -2,64 +2,64 @@ public class RefinementGT { - void test_forward(int a, int j, int s) { - /** forwards greater than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (a > -1) { - /** a is NN now */ - @IntRange(from = 0) int b = a; - @IntRange(from = -1) int b1 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int b2 = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (a > -1) { + /** a is NN now */ + @IntRange(from = 0) int b = a; + @IntRange(from = -1) int b1 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int b2 = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int c = a; + } - if (j > 0) { - /** j is POS now */ - @IntRange(from = 1) int k = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = j; - } + if (j > 0) { + /** j is POS now */ + @IntRange(from = 1) int k = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = j; + } - if (s > 1) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; + if (s > 1) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; + } } - } - void test_backwards(int a, int j, int s) { - /** backwards greater than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 > a) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b = a; - } else { - @IntRange(from = -1) int c = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int c1 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int c2 = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 > a) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b = a; + } else { + @IntRange(from = -1) int c = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int c1 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int c2 = a; + } - if (0 > j) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = j; - } else { - @IntRange(from = 0) int l = j; - } + if (0 > j) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = j; + } else { + @IntRange(from = 0) int l = j; + } - if (1 > s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; + if (1 > s) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; + } } - } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementGTE.java b/framework/tests/value-ignore-range-overflow/RefinementGTE.java index 944ffa3e9b8..25a65d15f77 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementGTE.java +++ b/framework/tests/value-ignore-range-overflow/RefinementGTE.java @@ -2,53 +2,53 @@ public class RefinementGTE { - void test_forward(int a, int j, int s) { - /** forwards greater than or equals */ - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int aa = a; - if (a >= -1) { - @IntRange(from = -1) int b = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int c = a; - } + void test_forward(int a, int j, int s) { + /** forwards greater than or equals */ + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int aa = a; + if (a >= -1) { + @IntRange(from = -1) int b = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int c = a; + } - if (j >= 0) { - @IntRange(from = 0) int k = j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k1 = j; - @IntRange(from = -1) int k2 = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int l = j; + if (j >= 0) { + @IntRange(from = 0) int k = j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k1 = j; + @IntRange(from = -1) int k2 = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int l = j; + } } - } - void test_backwards(int a, int j, int s) { - /** backwards greater than or equal */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 >= a) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int b = a; - } else { - @IntRange(from = 0) int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards greater than or equal */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 >= a) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int b = a; + } else { + @IntRange(from = 0) int c = a; + } - if (0 >= j) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k = j; - } else { - @IntRange(from = 1) int l = j; - @IntRange(from = -1) int l1 = j; - @IntRange(from = 0) int l2 = j; - } + if (0 >= j) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k = j; + } else { + @IntRange(from = 1) int l = j; + @IntRange(from = -1) int l1 = j; + @IntRange(from = 0) int l2 = j; + } - if (1 >= s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; + if (1 >= s) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; + } } - } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementLT.java b/framework/tests/value-ignore-range-overflow/RefinementLT.java index 353b7957609..e0044d596dd 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementLT.java +++ b/framework/tests/value-ignore-range-overflow/RefinementLT.java @@ -2,61 +2,61 @@ public class RefinementLT { - void test_backwards(int a, int j, int s) { - /** backwards less than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 < a) { - @IntRange(from = 0) int b = a; - @IntRange(from = -1) int b1 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int b2 = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 < a) { + @IntRange(from = 0) int b = a; + @IntRange(from = -1) int b1 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int b2 = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int c = a; + } - if (0 < j) { - @IntRange(from = 1) int k = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = j; - } + if (0 < j) { + @IntRange(from = 1) int k = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = j; + } - if (1 < s) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; + if (1 < s) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; + } } - } - void test_forwards(int a, int j, int s) { - /** forwards less than */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (a < -1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b = a; - } else { - @IntRange(from = -1) int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (a < -1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b = a; + } else { + @IntRange(from = -1) int c = a; + } - if (j < 0) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = j; - } else { - @IntRange(from = 0) int l = j; - @IntRange(from = -1) int l1 = j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l2 = j; - } + if (j < 0) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = j; + } else { + @IntRange(from = 0) int l = j; + @IntRange(from = -1) int l1 = j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l2 = j; + } - if (s < 1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; + if (s < 1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; + } } - } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementLTE.java b/framework/tests/value-ignore-range-overflow/RefinementLTE.java index fac940caad3..fa23e930835 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementLTE.java +++ b/framework/tests/value-ignore-range-overflow/RefinementLTE.java @@ -2,62 +2,62 @@ public class RefinementLTE { - void test_backwards(int a, int j, int s) { - /** backwards less than or equals */ - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int aa = a; - if (-1 <= a) { - @IntRange(from = -1) int b = a; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int c = a; - } + void test_backwards(int a, int j, int s) { + /** backwards less than or equals */ + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int aa = a; + if (-1 <= a) { + @IntRange(from = -1) int b = a; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int c = a; + } - if (0 <= j) { - @IntRange(from = 0) int k = j; - @IntRange(from = -1) int k1 = j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k2 = j; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int l = j; - } + if (0 <= j) { + @IntRange(from = 0) int k = j; + @IntRange(from = -1) int k1 = j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k2 = j; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int l = j; + } - if (1 <= s) { - @IntRange(from = 1) int t = s; - } else { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = s; + if (1 <= s) { + @IntRange(from = 1) int t = s; + } else { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = s; + } } - } - void test_forwards(int a, int j, int s) { - /** forwards less than or equal */ - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (a <= -1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b0 = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int b = a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int b2 = a; - } else { - @IntRange(from = 0) int c = a; - } + void test_forwards(int a, int j, int s) { + /** forwards less than or equal */ + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (a <= -1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b0 = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int b = a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int b2 = a; + } else { + @IntRange(from = 0) int c = a; + } - if (j <= 0) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int k = j; - } else { - @IntRange(from = 1) int l = j; - } + if (j <= 0) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int k = j; + } else { + @IntRange(from = 1) int l = j; + } - if (s <= 1) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; + if (s <= 1) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; + } } - } } diff --git a/framework/tests/value-ignore-range-overflow/RefinementNEq.java b/framework/tests/value-ignore-range-overflow/RefinementNEq.java index 8e44cf6e54c..2535c55d3dd 100644 --- a/framework/tests/value-ignore-range-overflow/RefinementNEq.java +++ b/framework/tests/value-ignore-range-overflow/RefinementNEq.java @@ -2,29 +2,29 @@ public class RefinementNEq { - void test_not_equal(int a, int j, int s) { + void test_not_equal(int a, int j, int s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int aa = a; - if (-1 != a) { - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int b = a; - } else { - @IntRange(from = -1) int c = a; - } + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int aa = a; + if (-1 != a) { + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int b = a; + } else { + @IntRange(from = -1) int c = a; + } - if (0 != j) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = j; - } else { - @IntRange(from = 0) int l = j; - } + if (0 != j) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = j; + } else { + @IntRange(from = 0) int l = j; + } - if (1 != s) { - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = s; - } else { - @IntRange(from = 1) int u = s; + if (1 != s) { + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = s; + } else { + @IntRange(from = 1) int u = s; + } } - } } diff --git a/framework/tests/value-ignore-range-overflow/TransferAdd.java b/framework/tests/value-ignore-range-overflow/TransferAdd.java index a8d326ea571..2df432cb55a 100644 --- a/framework/tests/value-ignore-range-overflow/TransferAdd.java +++ b/framework/tests/value-ignore-range-overflow/TransferAdd.java @@ -2,81 +2,81 @@ public class TransferAdd { - void test() { + void test() { - // adding zero and one and two + // adding zero and one and two - int a = -1; + int a = -1; - @IntRange(from = 1) int a1 = a + 2; + @IntRange(from = 1) int a1 = a + 2; - @IntRange(from = 0) int b = a + 1; - @IntRange(from = 0) int c = 1 + a; + @IntRange(from = 0) int b = a + 1; + @IntRange(from = 0) int c = 1 + a; - @IntRange(from = -1) int d = a + 0; - @IntRange(from = -1) int e = 0 + a; + @IntRange(from = -1) int d = a + 0; + @IntRange(from = -1) int e = 0 + a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int f = a + 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int f = a + 1; - @IntRange(from = 0) int g = b + 0; + @IntRange(from = 0) int g = b + 0; - @IntRange(from = 1) int h = b + 1; + @IntRange(from = 1) int h = b + 1; - @IntRange(from = 1) int i = h + 1; - @IntRange(from = 1) int j = h + 0; + @IntRange(from = 1) int i = h + 1; + @IntRange(from = 1) int j = h + 0; - // adding values + // adding values - @IntRange(from = 1) int k = i + j; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = b + c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int m = d + c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int n = d + e; + @IntRange(from = 1) int k = i + j; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = b + c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int m = d + c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int n = d + e; - @IntRange(from = 1) int o = h + g; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int p = h + d; + @IntRange(from = 1) int o = h + g; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int p = h + d; - @IntRange(from = 0) int q = b + c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int r = q + d; + @IntRange(from = 0) int q = b + c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int r = q + d; - @IntRange(from = 0) int s = k + d; - @IntRange(from = -1) int t = s + d; + @IntRange(from = 0) int s = k + d; + @IntRange(from = -1) int t = s + d; - // increments + // increments - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int u = b++; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int u = b++; - @IntRange(from = 1) int u1 = b; + @IntRange(from = 1) int u1 = b; - @IntRange(from = 1) int v = ++c; + @IntRange(from = 1) int v = ++c; - @IntRange(from = 1) int v1 = c; + @IntRange(from = 1) int v1 = c; - int n1p1 = -1, n1p2 = -1; + int n1p1 = -1, n1p2 = -1; - @IntRange(from = 0) int w = ++n1p1; + @IntRange(from = 0) int w = ++n1p1; - @IntRange(from = 0) int w1 = n1p1; + @IntRange(from = 0) int w1 = n1p1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int w2 = n1p1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int w3 = n1p1++; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int w2 = n1p1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int w3 = n1p1++; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int x = n1p2++; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int x = n1p2++; - @IntRange(from = 0) int x1 = n1p2; + @IntRange(from = 0) int x1 = n1p2; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int y = ++d; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int z = e++; - } + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int y = ++d; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int z = e++; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferDivide.java b/framework/tests/value-ignore-range-overflow/TransferDivide.java index c6c2accc41a..81ee9b1500b 100644 --- a/framework/tests/value-ignore-range-overflow/TransferDivide.java +++ b/framework/tests/value-ignore-range-overflow/TransferDivide.java @@ -2,57 +2,57 @@ public class TransferDivide { - void test() { - int a = -1; - int b = 0; - int c = 1; - int d = 2; - - /** literals */ - @IntRange(from = 1) int e = -1 / -1; - - /** 0 / * -> NN */ - @IntRange(from = 0) int f = 0 / a; - @IntRange(from = 0) int g = 0 / d; - - /** * / 1 -> * */ - @IntRange(from = -1) int h = a / 1; - @IntRange(from = 0) int i = b / 1; - @IntRange(from = 1) int j = c / 1; - @IntRange(from = 1) int k = d / 1; - - /** pos / pos -> nn */ - @IntRange(from = 0) int l = d / c; - @IntRange(from = 0) int m = c / d; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int n = c / d; - - /** nn / pos -> nn */ - @IntRange(from = 0) int o = b / c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int p = b / d; - - /** pos / nn -> nn */ - @IntRange(from = 0) int q = d / l; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int r = c / l; - - /** nn / nn -> nn */ - @IntRange(from = 0) int s = b / q; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t = b / q; - - /** n1p / pos -> n1p */ - @IntRange(from = -1) int u = a / d; - @IntRange(from = -1) int v = a / c; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int w = a / c; - - /** n1p / nn -> n1p */ - @IntRange(from = -1) int x = a / l; - } - - void testDivideByTwo(@IntRange(from = 0) int x) { - @IntRange(from = 0) int y = x / 2; - } + void test() { + int a = -1; + int b = 0; + int c = 1; + int d = 2; + + /** literals */ + @IntRange(from = 1) int e = -1 / -1; + + /** 0 / * -> NN */ + @IntRange(from = 0) int f = 0 / a; + @IntRange(from = 0) int g = 0 / d; + + /** * / 1 -> * */ + @IntRange(from = -1) int h = a / 1; + @IntRange(from = 0) int i = b / 1; + @IntRange(from = 1) int j = c / 1; + @IntRange(from = 1) int k = d / 1; + + /** pos / pos -> nn */ + @IntRange(from = 0) int l = d / c; + @IntRange(from = 0) int m = c / d; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int n = c / d; + + /** nn / pos -> nn */ + @IntRange(from = 0) int o = b / c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int p = b / d; + + /** pos / nn -> nn */ + @IntRange(from = 0) int q = d / l; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int r = c / l; + + /** nn / nn -> nn */ + @IntRange(from = 0) int s = b / q; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t = b / q; + + /** n1p / pos -> n1p */ + @IntRange(from = -1) int u = a / d; + @IntRange(from = -1) int v = a / c; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int w = a / c; + + /** n1p / nn -> n1p */ + @IntRange(from = -1) int x = a / l; + } + + void testDivideByTwo(@IntRange(from = 0) int x) { + @IntRange(from = 0) int y = x / 2; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferMod.java b/framework/tests/value-ignore-range-overflow/TransferMod.java index 1f4f44cc798..6bad75f900f 100644 --- a/framework/tests/value-ignore-range-overflow/TransferMod.java +++ b/framework/tests/value-ignore-range-overflow/TransferMod.java @@ -2,30 +2,30 @@ public class TransferMod { - void test() { - int aa = -100; - int a = -1; - int b = 0; - int c = 1; - int d = 2; + void test() { + int aa = -100; + int a = -1; + int b = 0; + int c = 1; + int d = 2; - @IntRange(from = 1) int e = 5 % 3; - @IntRange(from = 0) int f = -100 % 1; + @IntRange(from = 1) int e = 5 % 3; + @IntRange(from = 0) int f = -100 % 1; - @IntRange(from = 0) int g = aa % -1; - @IntRange(from = 0) int h = aa % 1; - @IntRange(from = 0) int i = d % -1; - @IntRange(from = 0) int j = d % 1; + @IntRange(from = 0) int g = aa % -1; + @IntRange(from = 0) int h = aa % 1; + @IntRange(from = 0) int i = d % -1; + @IntRange(from = 0) int j = d % 1; - @IntRange(from = 0) int k = d % c; - @IntRange(from = 0) int l = b % c; - @IntRange(from = 0) int m = c % d; + @IntRange(from = 0) int k = d % c; + @IntRange(from = 0) int l = b % c; + @IntRange(from = 0) int m = c % d; - @IntRange(from = 0) int n = c % a; - @IntRange(from = 0) int o = b % a; + @IntRange(from = 0) int n = c % a; + @IntRange(from = 0) int o = b % a; - @IntRange(from = -1) int p = a % a; - @IntRange(from = -1) int q = a % d; - @IntRange(from = -1) int r = a % c; - } + @IntRange(from = -1) int p = a % a; + @IntRange(from = -1) int q = a % d; + @IntRange(from = -1) int r = a % c; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferSub.java b/framework/tests/value-ignore-range-overflow/TransferSub.java index b1bccc2887a..93b66f7290a 100644 --- a/framework/tests/value-ignore-range-overflow/TransferSub.java +++ b/framework/tests/value-ignore-range-overflow/TransferSub.java @@ -2,67 +2,67 @@ public class TransferSub { - void test() { - // zero, one, and two - int a = 1; + void test() { + // zero, one, and two + int a = 1; - @IntRange(from = 0) int b = a - 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int c = a - 1; - @IntRange(from = -1) int d = a - 2; + @IntRange(from = 0) int b = a - 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int c = a - 1; + @IntRange(from = -1) int d = a - 2; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int e = a - 2; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int e = a - 2; - @IntRange(from = -1) int f = b - 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int g = b - 1; + @IntRange(from = -1) int f = b - 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int g = b - 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int h = f - 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int h = f - 1; - @IntRange(from = -1) int i = f - 0; - @IntRange(from = 0) int j = b - 0; - @IntRange(from = 1) int k = a - 0; + @IntRange(from = -1) int i = f - 0; + @IntRange(from = 0) int j = b - 0; + @IntRange(from = 1) int k = a - 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int l = j - 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int m = i - 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int l = j - 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int m = i - 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int n = a - k; - // this would be an error if the values of b and j (both zero) weren't known at compile time - @IntRange(from = 0) int o = b - j; - /* i and d both have compile time value -1, so this is legal. - The general case of GTEN1 - GTEN1 is not, though. */ - @IntRange(from = -1) int p = i - d; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int n = a - k; + // this would be an error if the values of b and j (both zero) weren't known at compile time + @IntRange(from = 0) int o = b - j; + /* i and d both have compile time value -1, so this is legal. + The general case of GTEN1 - GTEN1 is not, though. */ + @IntRange(from = -1) int p = i - d; - // decrements + // decrements - // :: error: (unary.decrement.type.incompatible) - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int q = --k; // k = 0 + // :: error: (unary.decrement.type.incompatible) + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int q = --k; // k = 0 - // :: error: (unary.decrement.type.incompatible) - @IntRange(from = 0) int r = k--; // after this k = -1 + // :: error: (unary.decrement.type.incompatible) + @IntRange(from = 0) int r = k--; // after this k = -1 - int k1 = 0; - @IntRange(from = 0) int s = k1--; + int k1 = 0; + @IntRange(from = 0) int s = k1--; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int s1 = k1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int s1 = k1; - k1 = 1; - @IntRange(from = 0) int t = --k1; + k1 = 1; + @IntRange(from = 0) int t = --k1; - k1 = 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int t1 = --k1; + k1 = 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int t1 = --k1; - int u1 = -1; - @IntRange(from = -1) int x = u1--; - // :: error: (assignment.type.incompatible) - @IntRange(from = -1) int x1 = u1; - } + int u1 = -1; + @IntRange(from = -1) int x = u1--; + // :: error: (assignment.type.incompatible) + @IntRange(from = -1) int x1 = u1; + } } diff --git a/framework/tests/value-ignore-range-overflow/TransferTimes.java b/framework/tests/value-ignore-range-overflow/TransferTimes.java index e89ba40fab3..7654a1782e1 100644 --- a/framework/tests/value-ignore-range-overflow/TransferTimes.java +++ b/framework/tests/value-ignore-range-overflow/TransferTimes.java @@ -2,25 +2,25 @@ public class TransferTimes { - void test() { - int a = 1; - @IntRange(from = 1) int b = a * 1; - @IntRange(from = 1) int c = 1 * a; - @IntRange(from = 0) int d = 0 * a; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int e = -1 * a; + void test() { + int a = 1; + @IntRange(from = 1) int b = a * 1; + @IntRange(from = 1) int c = 1 * a; + @IntRange(from = 0) int d = 0 * a; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int e = -1 * a; - int g = -1; - @IntRange(from = 0) int h = g * 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int i = g * 0; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1) int j = g * a; + int g = -1; + @IntRange(from = 0) int h = g * 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int i = g * 0; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1) int j = g * a; - int k = 0; - int l = 1; - @IntRange(from = 1) int m = a * l; - @IntRange(from = 0) int n = k * l; - @IntRange(from = 0) int o = k * k; - } + int k = 0; + int l = 1; + @IntRange(from = 1) int m = a * l; + @IntRange(from = 0) int n = k * l; + @IntRange(from = 0) int o = k * k; + } } diff --git a/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java b/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java index 3406339bdc4..ef9cb5b3458 100644 --- a/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java +++ b/framework/tests/value-ignore-range-overflow/ValueNoOverflow.java @@ -2,94 +2,94 @@ public class ValueNoOverflow { - // This file contains a series of simple smoke tests for IntRange and ArrayLenRange with no - // overflow. - - void test_plus(@IntRange(from = 0) int x, @IntRange(from = -1) int z) { - @IntRange(from = 1) int y = x + 1; // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) int w = z + 1; // GTEN1 to NN - } - - void test_minus(@IntRange(to = 0) int x, @IntRange(to = 1) int z) { - @IntRange(to = -1) int y = x - 1; // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) int w = z - 1; // Pos to NN - } - - void test_mult(@IntRange(from = 0) int x, @IntRange(from = 1) int z) { - @IntRange(from = 0) int y = x * z; - @IntRange(from = 1) int w = z * z; - } - - void testLong_plus(@IntRange(from = 0) long x, @IntRange(from = -1) long z) { - @IntRange(from = 1) long y = x + 1; // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) long w = z + 1; // GTEN1 to NN - } - - void testLong_minus(@IntRange(to = 0) long x, @IntRange(to = 1) long z) { - @IntRange(to = -1) long y = x - 1; // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) long w = z - 1; // Pos to NN - } - - void testLong_mult(@IntRange(from = 0) long x, @IntRange(from = 1) long z) { - @IntRange(from = 0) long y = x * z; - @IntRange(from = 1) long w = z * z; - } - - void testShort_plus(@IntRange(from = 0) short x, @IntRange(from = -1) short z) { - @IntRange(from = 1) short y = (short) (x + 1); // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) short w = (short) (z + 1); // GTEN1 to NN - } - - void testShort_minus(@IntRange(to = 0) short x, @IntRange(to = 1) short z) { - @IntRange(to = -1) short y = (short) (x - 1); // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) short w = (short) (z - 1); // Pos to NN - } - - void testShort_mult(@IntRange(from = 0) short x, @IntRange(from = 1) short z) { - @IntRange(from = 0) short y = (short) (x * z); - @IntRange(from = 1) short w = (short) (z * z); - } - - void testChar_plus(@IntRange(from = 0) char x) { - @IntRange(from = 1) char y = (char) (x + 1); // IntRange(from = 0) to IntRange(from = 1) - } - - void testChar_minus(@IntRange(to = 65534) char z) { - @IntRange(to = 65533) char w = (char) (z - 1); // IntRange(to = 65535) to IntRange(to = 65535) - } - - void testChar_mult(@IntRange(from = 0) char x, @IntRange(from = 1) char z) { - @IntRange(from = 0) char y = (char) (x * z); - @IntRange(from = 1) char w = (char) (z * z); - } - - void testByte_plus(@IntRange(from = 0) byte x, @IntRange(from = -1) byte z) { - @IntRange(from = 1) byte y = (byte) (x + 1); // IntRange(from = 0) to IntRange(from = 1) - @IntRange(from = 0) byte w = (byte) (z + 1); // GTEN1 to NN - } - - void testByte_minus(@IntRange(to = 0) byte x, @IntRange(to = 1) byte z) { - @IntRange(to = -1) byte y = (byte) (x - 1); // IntRange(from = 0) to GTEN1 - @IntRange(to = 0) byte w = (byte) (z - 1); // Pos to NN - } - - void testByte_mult(@IntRange(from = 0) byte x, @IntRange(from = 1) byte z) { - @IntRange(from = 0) byte y = (byte) (x * z); - @IntRange(from = 1) byte w = (byte) (z * z); - } - - void test_casting(@IntRange(from = 0) int i) { - @IntRange(from = 1, to = ((long) Integer.MAX_VALUE) + 1) - long x = i + 1; - } - - // Include ArrayLenRange tests once ArrayLenRange is merged. - - void arraylenrange_test(int @ArrayLenRange(from = 5) [] a) { - int @ArrayLenRange(from = 7) [] b = new int[a.length + 2]; - } - - void arraylenrange_test2(int @ArrayLenRange(to = 5) [] a) { - int @ArrayLenRange(from = 0, to = 0) [] b = new int[a.length - 5]; - } + // This file contains a series of simple smoke tests for IntRange and ArrayLenRange with no + // overflow. + + void test_plus(@IntRange(from = 0) int x, @IntRange(from = -1) int z) { + @IntRange(from = 1) int y = x + 1; // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) int w = z + 1; // GTEN1 to NN + } + + void test_minus(@IntRange(to = 0) int x, @IntRange(to = 1) int z) { + @IntRange(to = -1) int y = x - 1; // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) int w = z - 1; // Pos to NN + } + + void test_mult(@IntRange(from = 0) int x, @IntRange(from = 1) int z) { + @IntRange(from = 0) int y = x * z; + @IntRange(from = 1) int w = z * z; + } + + void testLong_plus(@IntRange(from = 0) long x, @IntRange(from = -1) long z) { + @IntRange(from = 1) long y = x + 1; // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) long w = z + 1; // GTEN1 to NN + } + + void testLong_minus(@IntRange(to = 0) long x, @IntRange(to = 1) long z) { + @IntRange(to = -1) long y = x - 1; // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) long w = z - 1; // Pos to NN + } + + void testLong_mult(@IntRange(from = 0) long x, @IntRange(from = 1) long z) { + @IntRange(from = 0) long y = x * z; + @IntRange(from = 1) long w = z * z; + } + + void testShort_plus(@IntRange(from = 0) short x, @IntRange(from = -1) short z) { + @IntRange(from = 1) short y = (short) (x + 1); // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) short w = (short) (z + 1); // GTEN1 to NN + } + + void testShort_minus(@IntRange(to = 0) short x, @IntRange(to = 1) short z) { + @IntRange(to = -1) short y = (short) (x - 1); // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) short w = (short) (z - 1); // Pos to NN + } + + void testShort_mult(@IntRange(from = 0) short x, @IntRange(from = 1) short z) { + @IntRange(from = 0) short y = (short) (x * z); + @IntRange(from = 1) short w = (short) (z * z); + } + + void testChar_plus(@IntRange(from = 0) char x) { + @IntRange(from = 1) char y = (char) (x + 1); // IntRange(from = 0) to IntRange(from = 1) + } + + void testChar_minus(@IntRange(to = 65534) char z) { + @IntRange(to = 65533) char w = (char) (z - 1); // IntRange(to = 65535) to IntRange(to = 65535) + } + + void testChar_mult(@IntRange(from = 0) char x, @IntRange(from = 1) char z) { + @IntRange(from = 0) char y = (char) (x * z); + @IntRange(from = 1) char w = (char) (z * z); + } + + void testByte_plus(@IntRange(from = 0) byte x, @IntRange(from = -1) byte z) { + @IntRange(from = 1) byte y = (byte) (x + 1); // IntRange(from = 0) to IntRange(from = 1) + @IntRange(from = 0) byte w = (byte) (z + 1); // GTEN1 to NN + } + + void testByte_minus(@IntRange(to = 0) byte x, @IntRange(to = 1) byte z) { + @IntRange(to = -1) byte y = (byte) (x - 1); // IntRange(from = 0) to GTEN1 + @IntRange(to = 0) byte w = (byte) (z - 1); // Pos to NN + } + + void testByte_mult(@IntRange(from = 0) byte x, @IntRange(from = 1) byte z) { + @IntRange(from = 0) byte y = (byte) (x * z); + @IntRange(from = 1) byte w = (byte) (z * z); + } + + void test_casting(@IntRange(from = 0) int i) { + @IntRange(from = 1, to = ((long) Integer.MAX_VALUE) + 1) + long x = i + 1; + } + + // Include ArrayLenRange tests once ArrayLenRange is merged. + + void arraylenrange_test(int @ArrayLenRange(from = 5) [] a) { + int @ArrayLenRange(from = 7) [] b = new int[a.length + 2]; + } + + void arraylenrange_test2(int @ArrayLenRange(to = 5) [] a) { + int @ArrayLenRange(from = 0, to = 0) [] b = new int[a.length - 5]; + } } diff --git a/framework/tests/value-ignore-range-overflow/Widen.java b/framework/tests/value-ignore-range-overflow/Widen.java index 13bf9fcccec..024fbef066b 100644 --- a/framework/tests/value-ignore-range-overflow/Widen.java +++ b/framework/tests/value-ignore-range-overflow/Widen.java @@ -3,22 +3,22 @@ import org.checkerframework.common.value.qual.*; public class Widen { - public class Loops { - void test(int c, int max) { - int decexp = 0; - int seendot = 0; - int i = 0; - while (true) { - if (c == '.' && seendot == 0) { - seendot = 1; - } else if ('0' <= c && c <= '9') { - decexp += seendot; - } + public class Loops { + void test(int c, int max) { + int decexp = 0; + int seendot = 0; + int i = 0; + while (true) { + if (c == '.' && seendot == 0) { + seendot = 1; + } else if ('0' <= c && c <= '9') { + decexp += seendot; + } - if (max < i++) { - break; + if (max < i++) { + break; + } + } } - } } - } } diff --git a/framework/tests/value-non-null-strings-concatenation/Binaries.java b/framework/tests/value-non-null-strings-concatenation/Binaries.java index 906327a11ef..35546dc65d2 100644 --- a/framework/tests/value-non-null-strings-concatenation/Binaries.java +++ b/framework/tests/value-non-null-strings-concatenation/Binaries.java @@ -1,380 +1,381 @@ -import java.util.BitSet; import org.checkerframework.common.value.qual.*; +import java.util.BitSet; + public class Binaries { - private BitSet bitmap; - - public void test() { - int length = bitmap.length(); - for (int i = 0, t = 0; i < length; i++) { - t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); - if (i % 8 == 7 || i == length - 1) { - write(t); - t = 0; - } - } - } - - void write(int t) {} - - // Test widenedUpperBound is working. - public void loop(int c) { - double v = 0; - int decexp = 0; - int seendot = 0; - while (true) { - if (c == '.' && seendot == 0) seendot = 1; - else if ('0' <= c && c <= '9') { - v = v * 10 + (c - '0'); - decexp += seendot; - } else { - break; - } - } - } - - public void testIntRange( - @IntVal({1, 2}) int values, - @IntRange(from = 3, to = 4) int range1, - @IntRange(from = 5, to = 20) int range2, - @BottomVal int bottom, - @UnknownVal int top) { - - /* IntRange + IntRange */ - @IntRange(from = 8, to = 24) int a = range1 + range2; - - /* IntRange * IntVal */ - @IntRange(from = 3, to = 8) int b = values * range1; - - /* IntRange * BottomVal */ - int c = range1 * bottom; - - /* IntRange * UnknownVal */ - @IntRange(from = 0) - // :: error: (assignment.type.incompatible) - int d = range1 + top; - } - - public void add() { - int a = 1; - if (true) { - a = 2; + private BitSet bitmap; + + public void test() { + int length = bitmap.length(); + for (int i = 0, t = 0; i < length; i++) { + t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); + if (i % 8 == 7 || i == length - 1) { + write(t); + t = 0; + } + } } - @IntVal({3, 4}) int b = a + 2; - double c = 1.0; - if (true) { - c = 2.0; + void write(int t) {} + + // Test widenedUpperBound is working. + public void loop(int c) { + double v = 0; + int decexp = 0; + int seendot = 0; + while (true) { + if (c == '.' && seendot == 0) seendot = 1; + else if ('0' <= c && c <= '9') { + v = v * 10 + (c - '0'); + decexp += seendot; + } else { + break; + } + } } - @DoubleVal({3.0, 4.0}) double d = c + 2; - char e = '1'; - if (true) { - e = '2'; - } - @IntVal({'3', '4'}) char f = (char) (e + 2); + public void testIntRange( + @IntVal({1, 2}) int values, + @IntRange(from = 3, to = 4) int range1, + @IntRange(from = 5, to = 20) int range2, + @BottomVal int bottom, + @UnknownVal int top) { - String g = "A"; - if (true) { - g = "B"; - } - @StringVal({"AC", "BC"}) String h = g + "C"; - } + /* IntRange + IntRange */ + @IntRange(from = 8, to = 24) int a = range1 + range2; - public void subtract() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({-1, 0}) int b = a - 2; + /* IntRange * IntVal */ + @IntRange(from = 3, to = 8) int b = values * range1; - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({-1.0, 0.0}) double d = c - 2; + /* IntRange * BottomVal */ + int c = range1 * bottom; - char e = '2'; - if (true) { - e = '3'; + /* IntRange * UnknownVal */ + @IntRange(from = 0) + // :: error: (assignment.type.incompatible) + int d = range1 + top; } - @IntVal({'0', '1'}) char f = (char) (e - 2); - } + public void add() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({3, 4}) int b = a + 2; - public void multiply() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({2, 4}) int b = a * 2; + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({3.0, 4.0}) double d = c + 2; - double c = 1.0; - if (true) { - c = 2.0; + char e = '1'; + if (true) { + e = '2'; + } + @IntVal({'3', '4'}) char f = (char) (e + 2); + + String g = "A"; + if (true) { + g = "B"; + } + @StringVal({"AC", "BC"}) String h = g + "C"; } - @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - char e = (char) 25; - if (true) { + public void subtract() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({-1, 0}) int b = a - 2; + + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({-1.0, 0.0}) double d = c - 2; - e = (char) 26; + char e = '2'; + if (true) { + e = '3'; + } + + @IntVal({'0', '1'}) char f = (char) (e - 2); } - @IntVal({'2', '4'}) char f = (char) (e * 2); + public void multiply() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({2, 4}) int b = a * 2; - @DoubleVal(0.75) float g = 1 * 0.75f; - } + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - public void divide() { - int a = 2; - if (true) { - a = 4; - } - @IntVal({1, 2}) int b = a / 2; + char e = (char) 25; + if (true) { - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({0.5, 1.0}) double d = c / 2; + e = (char) 26; + } - char e = (char) 96; - if (true) { - e = (char) 98; + @IntVal({'2', '4'}) char f = (char) (e * 2); + + @DoubleVal(0.75) float g = 1 * 0.75f; } - @IntVal({'0', '1'}) char f = (char) (e / 2); + public void divide() { + int a = 2; + if (true) { + a = 4; + } + @IntVal({1, 2}) int b = a / 2; - @IntVal(0) int g = 2 / 3; - @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; - @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; - } + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({0.5, 1.0}) double d = c / 2; - public void remainder() { - int a = 4; - if (true) { - a = 5; - } - @IntVal({1, 2}) int b = a % 3; + char e = (char) 96; + if (true) { + e = (char) 98; + } - double c = 4.0; - if (true) { - c = 5.0; - } - @DoubleVal({1.0, 2.0}) double d = c % 3; + @IntVal({'0', '1'}) char f = (char) (e / 2); - char e = (char) 98; - if (true) { - e = (char) 99; + @IntVal(0) int g = 2 / 3; + @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; + @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; } - @IntVal({'0', '1'}) char f = (char) (e % 50); - } + public void remainder() { + int a = 4; + if (true) { + a = 5; + } + @IntVal({1, 2}) int b = a % 3; - public boolean flag = true; + double c = 4.0; + if (true) { + c = 5.0; + } + @DoubleVal({1.0, 2.0}) double d = c % 3; - public void and() { - boolean a = true; - if (flag) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a & true; + char e = (char) 98; + if (true) { + e = (char) 99; + } - int c = 4; - if (true) { - c = 5; + @IntVal({'0', '1'}) char f = (char) (e % 50); } - @IntVal({0, 1}) int d = c & 3; - char e = (char) 48; - if (true) { + public boolean flag = true; - e = (char) 51; - } + public void and() { + boolean a = true; + if (flag) { + a = false; + } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a & true; - @IntVal({'0', '2'}) char f = (char) (e & 50); - } + int c = 4; + if (true) { + c = 5; + } + @IntVal({0, 1}) int d = c & 3; - public void or() { - boolean a = true; - if (true) { - a = false; - } - // TODO: we could detect this case - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a | true; + char e = (char) 48; + if (true) { - int c = 4; - if (true) { - c = 5; - } - @IntVal({7}) int d = c | 3; + e = (char) 51; + } - char e = (char) 48; - if (true) { - e = (char) 51; + @IntVal({'0', '2'}) char f = (char) (e & 50); } - @IntVal({'1', '3'}) char f = (char) (e | 1); - } + public void or() { + boolean a = true; + if (true) { + a = false; + } + // TODO: we could detect this case + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a | true; - public void xor() { - boolean a = true; - if (true) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a ^ true; + int c = 4; + if (true) { + c = 5; + } + @IntVal({7}) int d = c | 3; + + char e = (char) 48; + if (true) { + e = (char) 51; + } - int c = 4; - if (true) { - c = 5; + @IntVal({'1', '3'}) char f = (char) (e | 1); } - @IntVal({7, 6}) int d = c ^ 3; - char e = (char) 48; - if (true) { + public void xor() { + boolean a = true; + if (true) { + a = false; + } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a ^ true; - e = (char) 51; - } + int c = 4; + if (true) { + c = 5; + } + @IntVal({7, 6}) int d = c ^ 3; + + char e = (char) 48; + if (true) { + + e = (char) 51; + } - @IntVal({'1', '2'}) char f = (char) (e ^ 1); - } - - public void boolAnd() { - @BoolVal({false}) boolean a = true && false; - @BoolVal({true}) boolean b = false || true; - } - - public void conditionals() { - @BoolVal({false}) boolean a = 1.0f == '1'; - @BoolVal({true}) boolean b = 1 != 2.0; - @BoolVal({true}) boolean c = 1 > 0.5; - @BoolVal({true}) boolean d = 1 >= 1.0; - @BoolVal({true}) boolean e = 1 < 1.1f; - @BoolVal({true}) boolean f = (char) 2 <= 2.0; - @IntVal('!') Character BANG = '!'; - @BoolVal(true) boolean g = (BANG == '!'); - char bangChar = '!'; - @BoolVal(true) boolean h = (BANG == bangChar); - - Character bang = '!'; - // Reference equalitiy is used - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean i = (BANG == bang); - } - - public void loop() throws InterruptedException { - int spurious_count = 0; - while (true) { - wait(); - if (System.currentTimeMillis() == 0) { - spurious_count++; - if (spurious_count > 1024) { - break; - } - } + @IntVal({'1', '2'}) char f = (char) (e ^ 1); + } + + public void boolAnd() { + @BoolVal({false}) boolean a = true && false; + @BoolVal({true}) boolean b = false || true; + } + + public void conditionals() { + @BoolVal({false}) boolean a = 1.0f == '1'; + @BoolVal({true}) boolean b = 1 != 2.0; + @BoolVal({true}) boolean c = 1 > 0.5; + @BoolVal({true}) boolean d = 1 >= 1.0; + @BoolVal({true}) boolean e = 1 < 1.1f; + @BoolVal({true}) boolean f = (char) 2 <= 2.0; + @IntVal('!') Character BANG = '!'; + @BoolVal(true) boolean g = (BANG == '!'); + char bangChar = '!'; + @BoolVal(true) boolean h = (BANG == bangChar); + + Character bang = '!'; + // Reference equalitiy is used + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean i = (BANG == bang); + } + + public void loop() throws InterruptedException { + int spurious_count = 0; + while (true) { + wait(); + if (System.currentTimeMillis() == 0) { + spurious_count++; + if (spurious_count > 1024) { + break; + } + } + } } - } - public void shifts() { - int a = -8; - if (true) { - a = 4; + public void shifts() { + int a = -8; + if (true) { + a = 4; + } + @IntVal({1, -2}) int b = a >> 2; + + int c = 1; + if (true) { + c = 2; + } + @IntVal({4, 8}) int d = c << 2; + + int e = -8; + if (true) { + e = 4; + } + @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; + + char g = (char) 24; + if (true) { + g = (char) 25; + } + + @IntVal({'0', '2'}) char h = (char) (g << 1); } - @IntVal({1, -2}) int b = a >> 2; - int c = 1; - if (true) { - c = 2; + public void chains() { + char a = 2; + int b = 3; + double c = 5; + + @DoubleVal({1}) double d = a * b - c; + + @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; } - @IntVal({4, 8}) int d = c << 2; - int e = -8; - if (true) { - e = 4; + public void compareWithNull() { + String s = "1"; + // TODO + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean b = (s != null); } - @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; - char g = (char) 24; - if (true) { - g = (char) 25; + public void nullConcatenation(@StringVal({"a", "b"}) String arg) { + String n1 = null; + String n2 = "null"; + String k = "const"; + + // @StringVal("nullnull") String a1 = n1 + null; + @StringVal("nullnull") String a2 = n1 + "null"; + // @StringVal("nullnull") String a3 = n1 + n1; + @StringVal("nullnull") String a4 = n1 + n2; + @StringVal("nullconst") String a5 = n1 + k; + @StringVal("nullconst") String a6 = n1 + "const"; + + @StringVal("nullnull") String b1 = n2 + null; + @StringVal("nullnull") String b2 = n2 + "null"; + @StringVal("null") String b3 = n2 + n1; + @StringVal("nullnull") String b4 = n2 + n2; + @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; + @StringVal("nullconst") String b6 = n2 + "const"; + + @StringVal({"anull", "bnull"}) String c1 = arg + null; + @StringVal({"anull", "bnull"}) String c2 = arg + "null"; + @StringVal({"anull", "bnull"}) String c3 = arg + n1; + @StringVal({"anull", "bnull"}) String c4 = arg + n2; + @StringVal({"aconst", "bconst"}) String c5 = arg + k; + @StringVal({"aconst", "bconst"}) String c6 = arg + "const"; + @StringVal({"a2147483647", "b2147483647"}) String c7 = arg + Integer.MAX_VALUE; } - @IntVal({'0', '2'}) char h = (char) (g << 1); - } - - public void chains() { - char a = 2; - int b = 3; - double c = 5; - - @DoubleVal({1}) double d = a * b - c; - - @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; - } - - public void compareWithNull() { - String s = "1"; - // TODO - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean b = (s != null); - } - - public void nullConcatenation(@StringVal({"a", "b"}) String arg) { - String n1 = null; - String n2 = "null"; - String k = "const"; - - // @StringVal("nullnull") String a1 = n1 + null; - @StringVal("nullnull") String a2 = n1 + "null"; - // @StringVal("nullnull") String a3 = n1 + n1; - @StringVal("nullnull") String a4 = n1 + n2; - @StringVal("nullconst") String a5 = n1 + k; - @StringVal("nullconst") String a6 = n1 + "const"; - - @StringVal("nullnull") String b1 = n2 + null; - @StringVal("nullnull") String b2 = n2 + "null"; - @StringVal("null") String b3 = n2 + n1; - @StringVal("nullnull") String b4 = n2 + n2; - @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; - @StringVal("nullconst") String b6 = n2 + "const"; - - @StringVal({"anull", "bnull"}) String c1 = arg + null; - @StringVal({"anull", "bnull"}) String c2 = arg + "null"; - @StringVal({"anull", "bnull"}) String c3 = arg + n1; - @StringVal({"anull", "bnull"}) String c4 = arg + n2; - @StringVal({"aconst", "bconst"}) String c5 = arg + k; - @StringVal({"aconst", "bconst"}) String c6 = arg + "const"; - @StringVal({"a2147483647", "b2147483647"}) String c7 = arg + Integer.MAX_VALUE; - } - - public void conditionalComparisions() { - @BoolVal(true) boolean a1 = true || false; - @BoolVal(true) boolean a2 = true || true; - @BoolVal(false) boolean a3 = false || false; - @BoolVal(true) boolean a4 = false || true; - - @BoolVal(false) boolean a5 = true && false; - @BoolVal(true) boolean a6 = true && true; - @BoolVal(false) boolean a7 = false && false; - @BoolVal(false) boolean a8 = false && true; - - boolean unknown = flag ? true : false; - @BoolVal(true) boolean a9 = true || unknown; - @BoolVal(true) boolean a11 = unknown || true; - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean a12 = unknown || false; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a13 = false || unknown; - - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a14 = true && unknown; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a15 = unknown && true; - @BoolVal(false) boolean a16 = unknown && false; - @BoolVal(false) boolean a17 = false && unknown; - } + public void conditionalComparisions() { + @BoolVal(true) boolean a1 = true || false; + @BoolVal(true) boolean a2 = true || true; + @BoolVal(false) boolean a3 = false || false; + @BoolVal(true) boolean a4 = false || true; + + @BoolVal(false) boolean a5 = true && false; + @BoolVal(true) boolean a6 = true && true; + @BoolVal(false) boolean a7 = false && false; + @BoolVal(false) boolean a8 = false && true; + + boolean unknown = flag ? true : false; + @BoolVal(true) boolean a9 = true || unknown; + @BoolVal(true) boolean a11 = unknown || true; + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean a12 = unknown || false; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a13 = false || unknown; + + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a14 = true && unknown; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a15 = unknown && true; + @BoolVal(false) boolean a16 = unknown && false; + @BoolVal(false) boolean a17 = false && unknown; + } } diff --git a/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java b/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java index e3ef8b28a68..2f83443dcb6 100644 --- a/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java +++ b/framework/tests/value-non-null-strings-concatenation/CompoundAssignment.java @@ -7,60 +7,60 @@ public class CompoundAssignment { - @StringVal("hello") String field; + @StringVal("hello") String field; - public void refinements() { - field = "hello"; - // :: error: (compound.assignment.type.incompatible) - field += method(); - // :: error: (assignment.type.incompatible) - // :: error: (compound.assignment.type.incompatible) - @StringVal("hellohellohello") String test = field += method(); - } + public void refinements() { + field = "hello"; + // :: error: (compound.assignment.type.incompatible) + field += method(); + // :: error: (assignment.type.incompatible) + // :: error: (compound.assignment.type.incompatible) + @StringVal("hellohellohello") String test = field += method(); + } - @StringVal("hello") String method() { - // :: error: (assignment.type.incompatible) - field = "goodbye"; - return "hello"; - } + @StringVal("hello") String method() { + // :: error: (assignment.type.incompatible) + field = "goodbye"; + return "hello"; + } - void value() { - @StringVal("hello") String s = "hello"; - // :: error: (compound.assignment.type.incompatible) - s += "hello"; + void value() { + @StringVal("hello") String s = "hello"; + // :: error: (compound.assignment.type.incompatible) + s += "hello"; - @IntVal(1) int i = 1; - // :: error: (compound.assignment.type.incompatible) - i += 1; + @IntVal(1) int i = 1; + // :: error: (compound.assignment.type.incompatible) + i += 1; - @IntVal(2) int j = 2; - // :: error: (compound.assignment.type.incompatible) - j += 2; + @IntVal(2) int j = 2; + // :: error: (compound.assignment.type.incompatible) + j += 2; - // :: error: (assignment.type.incompatible) - @IntVal(4) int four = j; - } + // :: error: (assignment.type.incompatible) + @IntVal(4) int four = j; + } - void value2() { - @StringVal("hello") String s = "hello"; - // :: error: (assignment.type.incompatible) - s = s + "hello"; + void value2() { + @StringVal("hello") String s = "hello"; + // :: error: (assignment.type.incompatible) + s = s + "hello"; - @IntVal(1) int i = 1; - // :: error: (assignment.type.incompatible) - i = i + 1; - } + @IntVal(1) int i = 1; + // :: error: (assignment.type.incompatible) + i = i + 1; + } - void noErrorCompoundAssignments() { - @IntVal(0) int zero = 0; - zero *= 12; + void noErrorCompoundAssignments() { + @IntVal(0) int zero = 0; + zero *= 12; - @StringVal("null") String s = "null"; - s += ""; - } + @StringVal("null") String s = "null"; + s += ""; + } - void errorCompundAssignments() { - @StringVal("hello") String s = "hello"; - s += ""; - } + void errorCompundAssignments() { + @StringVal("hello") String s = "hello"; + s += ""; + } } diff --git a/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java b/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java index 88afcb0c5bb..f2741832dc4 100644 --- a/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java +++ b/framework/tests/value-non-null-strings-concatenation/StringLenConcats.java @@ -7,112 +7,113 @@ public class StringLenConcats { - void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { - @ArrayLen(8) String ab = a + b; - @ArrayLen(7) String bxx = b + "xx"; - } - - void stringLenRangeConcat( - @ArrayLenRange(from = 3, to = 5) String a, @ArrayLenRange(from = 11, to = 19) String b) { - @ArrayLenRange(from = 14, to = 24) String ab = a + b; - @ArrayLenRange(from = 13, to = 21) String bxx = b + "xx"; - } - - void stringLenLenRangeConcat( - @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { - @ArrayLenRange(from = 13, to = 105) String ab = a + b; - } - - void stringValLenConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLen(11) String d) { - - @ArrayLen(19) String ad = a + d; - @ArrayLen(12) String bd = b + d; - @ArrayLenRange(from = 12, to = 14) String cd = c + d; - } - - void stringValLenRangeConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLenRange(from = 11, to = 19) String d) { - - @ArrayLenRange(from = 19, to = 27) String ad = a + d; - @ArrayLenRange(from = 12, to = 20) String bd = b + d; - @ArrayLenRange(from = 12, to = 22) String cd = c + d; - } - - void tooManyStringValConcat( - @StringVal({"a", "b", "c", "d"}) String a, - @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { - @ArrayLen(2) String aa = a + a; - @ArrayLen(3) String ab = a + b; - } - - void charConversions( - char c, - @IntVal({1, 100, 10000}) char d, - @ArrayLen({100, 200}) String s, - @ArrayLenRange(from = 100, to = 200) String t, - @StringVal({"a", "bb", "ccc", "dddd"}) String u) { - @ArrayLen({101, 201}) String sc = s + c; - @ArrayLen({101, 201}) String sd = s + d; - - @ArrayLenRange(from = 101, to = 201) String tc = t + c; - - @ArrayLen({2, 3, 4, 5}) String uc = u + c; - @ArrayLen({2, 3, 4, 5}) String ud = u + d; - } - - void intConversions( - @IntVal(123) int intConst, - @IntRange(from = -100000, to = 100) int intRange, - @IntRange(from = 100, to = 100000) int positiveRange, - int unknownInt, - @ArrayLen(10) String a, - @ArrayLenRange(from = 10, to = 20) String b, - @StringVal({"aaa", "bbbbb"}) String c) { - @ArrayLen(13) String aConst = a + intConst; - @ArrayLen({11, 12, 13, 14, 15, 16, 17}) String aRange = a + intRange; - @ArrayLen({13, 14, 15, 16}) String aPositive = a + positiveRange; - @ArrayLenRange(from = 11, to = 21) String aUnknown = a + unknownInt; - - @ArrayLenRange(from = 13, to = 23) String bConst = b + intConst; - @ArrayLenRange(from = 11, to = 27) String bRange = b + intRange; - @ArrayLenRange(from = 13, to = 26) String bPositive = b + positiveRange; - @ArrayLenRange(from = 11, to = 31) String bUnknown = b + unknownInt; - - @StringVal({"aaa123", "bbbbb123"}) String cConst = c + intConst; - @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; - @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; - } - - void longConversions( - @IntVal(1000000000000l) long longConst, - @IntRange(from = 10, to = 1000000000000l) long longRange, - long unknownLong, - @ArrayLen(10) String a) { - - @ArrayLen(23) String aConst = a + longConst; - @ArrayLenRange(from = 12, to = 23) String aRange = a + longRange; - @ArrayLenRange(from = 11, to = 30) String aUnknown = a + unknownLong; - } - - void byteConversions( - @IntVal(100) byte byteConst, - @IntRange(from = 2, to = 10) byte byteRange, - byte unknownByte, - @ArrayLen(10) String a) { - - @ArrayLen(13) String aConst = a + byteConst; - @ArrayLenRange(from = 11, to = 12) String aRange = a + byteRange; - @ArrayLenRange(from = 11, to = 14) String aUnknown = a + unknownByte; - } - - void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { - @MinLen(12) String st = s + t; - } + void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { + @ArrayLen(8) String ab = a + b; + @ArrayLen(7) String bxx = b + "xx"; + } + + void stringLenRangeConcat( + @ArrayLenRange(from = 3, to = 5) String a, + @ArrayLenRange(from = 11, to = 19) String b) { + @ArrayLenRange(from = 14, to = 24) String ab = a + b; + @ArrayLenRange(from = 13, to = 21) String bxx = b + "xx"; + } + + void stringLenLenRangeConcat( + @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { + @ArrayLenRange(from = 13, to = 105) String ab = a + b; + } + + void stringValLenConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLen(11) String d) { + + @ArrayLen(19) String ad = a + d; + @ArrayLen(12) String bd = b + d; + @ArrayLenRange(from = 12, to = 14) String cd = c + d; + } + + void stringValLenRangeConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLenRange(from = 11, to = 19) String d) { + + @ArrayLenRange(from = 19, to = 27) String ad = a + d; + @ArrayLenRange(from = 12, to = 20) String bd = b + d; + @ArrayLenRange(from = 12, to = 22) String cd = c + d; + } + + void tooManyStringValConcat( + @StringVal({"a", "b", "c", "d"}) String a, + @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { + @ArrayLen(2) String aa = a + a; + @ArrayLen(3) String ab = a + b; + } + + void charConversions( + char c, + @IntVal({1, 100, 10000}) char d, + @ArrayLen({100, 200}) String s, + @ArrayLenRange(from = 100, to = 200) String t, + @StringVal({"a", "bb", "ccc", "dddd"}) String u) { + @ArrayLen({101, 201}) String sc = s + c; + @ArrayLen({101, 201}) String sd = s + d; + + @ArrayLenRange(from = 101, to = 201) String tc = t + c; + + @ArrayLen({2, 3, 4, 5}) String uc = u + c; + @ArrayLen({2, 3, 4, 5}) String ud = u + d; + } + + void intConversions( + @IntVal(123) int intConst, + @IntRange(from = -100000, to = 100) int intRange, + @IntRange(from = 100, to = 100000) int positiveRange, + int unknownInt, + @ArrayLen(10) String a, + @ArrayLenRange(from = 10, to = 20) String b, + @StringVal({"aaa", "bbbbb"}) String c) { + @ArrayLen(13) String aConst = a + intConst; + @ArrayLen({11, 12, 13, 14, 15, 16, 17}) String aRange = a + intRange; + @ArrayLen({13, 14, 15, 16}) String aPositive = a + positiveRange; + @ArrayLenRange(from = 11, to = 21) String aUnknown = a + unknownInt; + + @ArrayLenRange(from = 13, to = 23) String bConst = b + intConst; + @ArrayLenRange(from = 11, to = 27) String bRange = b + intRange; + @ArrayLenRange(from = 13, to = 26) String bPositive = b + positiveRange; + @ArrayLenRange(from = 11, to = 31) String bUnknown = b + unknownInt; + + @StringVal({"aaa123", "bbbbb123"}) String cConst = c + intConst; + @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; + @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; + } + + void longConversions( + @IntVal(1000000000000l) long longConst, + @IntRange(from = 10, to = 1000000000000l) long longRange, + long unknownLong, + @ArrayLen(10) String a) { + + @ArrayLen(23) String aConst = a + longConst; + @ArrayLenRange(from = 12, to = 23) String aRange = a + longRange; + @ArrayLenRange(from = 11, to = 30) String aUnknown = a + unknownLong; + } + + void byteConversions( + @IntVal(100) byte byteConst, + @IntRange(from = 2, to = 10) byte byteRange, + byte unknownByte, + @ArrayLen(10) String a) { + + @ArrayLen(13) String aConst = a + byteConst; + @ArrayLenRange(from = 11, to = 12) String aRange = a + byteRange; + @ArrayLenRange(from = 11, to = 14) String aUnknown = a + unknownByte; + } + + void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { + @MinLen(12) String st = s + t; + } } diff --git a/framework/tests/value/Alias.java b/framework/tests/value/Alias.java index 0979baf94b0..3258700ef9c 100644 --- a/framework/tests/value/Alias.java +++ b/framework/tests/value/Alias.java @@ -1,10 +1,10 @@ import android.support.annotation.IntRange; public class Alias { - public void androidIntRange() { - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = 10) int j = 13; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0) int k = -1; - } + public void androidIntRange() { + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = 10) int j = 13; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0) int k = -1; + } } diff --git a/framework/tests/value/AnnotationUse.java b/framework/tests/value/AnnotationUse.java index a243a5af2d3..f26af8509b0 100644 --- a/framework/tests/value/AnnotationUse.java +++ b/framework/tests/value/AnnotationUse.java @@ -1,12 +1,12 @@ import org.checkerframework.common.value.qual.IntRange; public class AnnotationUse { - // :: error: (annotation.intrange.on.noninteger) - @IntRange(to = 0) String s1; + // :: error: (annotation.intrange.on.noninteger) + @IntRange(to = 0) String s1; - // :: error: (annotation.intrange.on.noninteger) - @IntRange(from = 0) String s2; + // :: error: (annotation.intrange.on.noninteger) + @IntRange(from = 0) String s2; - // Allowed on j.l.Object, because of possible boxing - @IntRange(to = 0) Object o; + // Allowed on j.l.Object, because of possible boxing + @IntRange(to = 0) Object o; } diff --git a/framework/tests/value/ArrayInit.java b/framework/tests/value/ArrayInit.java index b4bce068bec..85ac91390fb 100644 --- a/framework/tests/value/ArrayInit.java +++ b/framework/tests/value/ArrayInit.java @@ -1,186 +1,188 @@ import org.checkerframework.common.value.qual.*; public class ArrayInit { - public void raggedArrays() { - int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] alpha = - new int[][][] { - {{1, 1}, {1, 1, 1}, {1}, {1}}, - {{1}, {1}, {1}, {1, 1}}, - {{1, 2, 3, 4, 5, 6, 7}}, - {{1}, {1}, {1, 1, 1, 1}} + public void raggedArrays() { + int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] alpha = + new int[][][] { + {{1, 1}, {1, 1, 1}, {1}, {1}}, + {{1}, {1}, {1}, {1, 1}}, + {{1, 2, 3, 4, 5, 6, 7}}, + {{1}, {1}, {1, 1, 1, 1}} + }; + + int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLenRange(from = 1, to = 12) [] gamma = + new int[][][] { + {{1, 1}, {1, 1, 1}, {1}, {1}}, + {{1}, {1}, {1}, {1, 1}}, + {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {{1}, {1}, {1, 1, 1, 1}} + }; + + int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a = {{1, 1}, {1, 1, 1}, {1}, {1}}; + int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b = {{1}, {1}, {1}, {1, 1}}; + int @ArrayLen(1) [] @ArrayLen(7) [] c = {{1, 2, 3, 4, 5, 6, 7}}; + int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d = {{1}, {1}, {1, 1, 1, 1}}; + + int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] beta = { + a, b, c, d }; - int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLenRange(from = 1, to = 12) [] gamma = - new int[][][] { - {{1, 1}, {1, 1, 1}, {1}, {1}}, - {{1}, {1}, {1}, {1, 1}}, - {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, - {{1}, {1}, {1, 1, 1, 1}} - }; + int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a1 = {{1, 1}, {1, 1, 1}, {1}, {1}}; + int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b1 = {{1}, {1}, {1}, {1, 1}}; + int @ArrayLen(1) [] @ArrayLen(11) [] c1 = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}; + int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d1 = {{1}, {1}, {1, 1, 1, 1}}; + + int @ArrayLen(4) [] @ArrayLenRange(from = 1, to = 4) [] @ArrayLenRange(from = 1, to = 11) [] + delta = {a1, b1, c1, d1}; + } + + public void numberInit() { + int @ArrayLen({1}) [] a = new int[1]; + } + + public void listInit() { + int @ArrayLen({1}) [] a = new int[] {4}; + } + + public void varInit() { + int i = 1; + int @ArrayLen({1}) [] a = new int[i]; + } + + public void rangeInit( + @IntRange(from = 1, to = 2) int shortLength, + @IntRange(from = 1, to = 20) int longLength, + @BottomVal int bottom) { + int @ArrayLen({1, 2}) [] a = new int[shortLength]; + // :: error: (assignment.type.incompatible) + int @ArrayLen({1, 2}) [] b = new int[longLength]; + int @ArrayLenRange(from = 1, to = 20) [] d = new int[longLength]; + int @ArrayLen({0}) [] c = new int[bottom]; + } + + public void multiDim() { + int i = 2; + int j = 3; + int @ArrayLen({2}) [] @ArrayLen({3}) [] a = new int[2][3]; + int @ArrayLen({2}) [] @ArrayLen({3}) [] b = new int[i][j]; + + int @ArrayLen({2}) [][] c = new int[][] {{2}, {3}}; + } + + public void initilizer() { + int @ArrayLen(3) [] ints = new int[] {2, 2, 2}; + char @StringVal("-A%") [] chars = new char[] {45, 'A', '%'}; + int @ArrayLen(3) [] ints2 = {2, 2, 2}; + } + + public void initializerString() { + byte @ArrayLen(2) [] bytes = new byte[] {100, '%'}; + char @ArrayLen(3) [] chars = new char[] {45, 'A', '%'}; + } + + public void vargsTest() { + // type of arg should be @UnknownVal Object @BottomVal[] + vargs((Object[]) null); + + // type of arg should be @UnknownVal int @BottomVal[] + vargs((int[]) null); + + // type of arg should be @UnknownVal byte @BottomVal[] + vargs((byte[]) null); + } + + public void vargs(Object @ArrayLen(0) ... objs) {} - int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a = {{1, 1}, {1, 1, 1}, {1}, {1}}; - int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b = {{1}, {1}, {1}, {1, 1}}; - int @ArrayLen(1) [] @ArrayLen(7) [] c = {{1, 2, 3, 4, 5, 6, 7}}; - int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d = {{1}, {1}, {1, 1, 1, 1}}; - - int @ArrayLen(4) [] @ArrayLen({1, 3, 4}) [] @ArrayLen({1, 2, 3, 4, 7}) [] beta = {a, b, c, d}; - - int @ArrayLen(4) [] @ArrayLen({1, 2, 3}) [] a1 = {{1, 1}, {1, 1, 1}, {1}, {1}}; - int @ArrayLen(4) [] @ArrayLen({1, 2}) [] b1 = {{1}, {1}, {1}, {1, 1}}; - int @ArrayLen(1) [] @ArrayLen(11) [] c1 = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}; - int @ArrayLen(3) [] @ArrayLen({1, 4}) [] d1 = {{1}, {1}, {1, 1, 1, 1}}; - - int @ArrayLen(4) [] @ArrayLenRange(from = 1, to = 4) [] @ArrayLenRange(from = 1, to = 11) [] - delta = {a1, b1, c1, d1}; - } - - public void numberInit() { - int @ArrayLen({1}) [] a = new int[1]; - } - - public void listInit() { - int @ArrayLen({1}) [] a = new int[] {4}; - } - - public void varInit() { - int i = 1; - int @ArrayLen({1}) [] a = new int[i]; - } - - public void rangeInit( - @IntRange(from = 1, to = 2) int shortLength, - @IntRange(from = 1, to = 20) int longLength, - @BottomVal int bottom) { - int @ArrayLen({1, 2}) [] a = new int[shortLength]; - // :: error: (assignment.type.incompatible) - int @ArrayLen({1, 2}) [] b = new int[longLength]; - int @ArrayLenRange(from = 1, to = 20) [] d = new int[longLength]; - int @ArrayLen({0}) [] c = new int[bottom]; - } - - public void multiDim() { - int i = 2; - int j = 3; - int @ArrayLen({2}) [] @ArrayLen({3}) [] a = new int[2][3]; - int @ArrayLen({2}) [] @ArrayLen({3}) [] b = new int[i][j]; - - int @ArrayLen({2}) [][] c = new int[][] {{2}, {3}}; - } - - public void initilizer() { - int @ArrayLen(3) [] ints = new int[] {2, 2, 2}; - char @StringVal("-A%") [] chars = new char[] {45, 'A', '%'}; - int @ArrayLen(3) [] ints2 = {2, 2, 2}; - } - - public void initializerString() { - byte @ArrayLen(2) [] bytes = new byte[] {100, '%'}; - char @ArrayLen(3) [] chars = new char[] {45, 'A', '%'}; - } - - public void vargsTest() { - // type of arg should be @UnknownVal Object @BottomVal[] - vargs((Object[]) null); - - // type of arg should be @UnknownVal int @BottomVal[] - vargs((int[]) null); - - // type of arg should be @UnknownVal byte @BottomVal[] - vargs((byte[]) null); - } - - public void vargs(Object @ArrayLen(0) ... objs) {} - - public void vargs(int @ArrayLen(0) ... ints) {} - - public void vargs(byte @ArrayLen(0) ... bytes) {} - - public void nullableArrays() { - Object @ArrayLen(2) [] @ArrayLen(1) [] o = new Object[][] {new Object[] {null}, null}; - Object @ArrayLen(1) [][] o2 = new Object[][] {null}; - Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; - } - - public void subtyping1(int @ArrayLen({1, 5}) [] a) { - int @ArrayLenRange(from = 1, to = 5) [] b = a; - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 2, to = 5) [] c = a; - } - - public void subtyping2(int @ArrayLenRange(from = 1, to = 5) [] a) { - int @ArrayLen({1, 2, 3, 4, 5}) [] b = a; - // :: error: (assignment.type.incompatible) - int @ArrayLen({1, 5}) [] c = a; - } - - public void subtyping3(int @ArrayLenRange(from = 1, to = 17) [] a) { - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 1, to = 12) [] b = a; - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 5, to = 18) [] c = a; - int @ArrayLenRange(from = 0, to = 20) [] d = a; - } - - public void lub1(boolean flag, @IntRange(from = 1, to = 200) int x) { - int[] a; - if (flag) { - a = new int[x]; - } else { - a = new int[250]; - } - - int @ArrayLenRange(from = 1, to = 250) [] b = a; - } - - public void lub2( - boolean flag, @IntRange(from = 1, to = 20) int x, @IntRange(from = 50, to = 75) int y) { - int[] a; - if (flag) { - a = new int[x]; - } else { - a = new int[y]; - } - - int @ArrayLenRange(from = 1, to = 75) [] b = a; - } - - public void lub3( - boolean flag, @IntRange(from = 1, to = 5) int x, @IntRange(from = 3, to = 7) int y) { - int[] a; - if (flag) { - a = new int[x]; - } else { - a = new int[y]; - } - - int @ArrayLenRange(from = 1, to = 7) [] b = a; - int @ArrayLen({1, 2, 3, 4, 5, 6, 7}) [] c = a; - } - - public void refine(int[] q) { - if (q.length < 20) { - @IntRange(from = 0, to = 19) int x = q.length; - int @ArrayLenRange(from = 0, to = 19) [] b = q; - if (q.length < 5) { - int @ArrayLen({0, 1, 2, 3, 4}) [] c = q; - } - } - } - - // The argument is an arraylen with too many values. - // :: warning: (too.many.values.given) - public void coerce(int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 36}) [] a) { - int @ArrayLenRange(from = 1, to = 36) [] b = a; - if (a.length < 15) { - // :: error: (assignment.type.incompatible) - int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) [] c = a; - } - } - - public void warnings() { - // :: warning: (negative.arraylen) - int @ArrayLenRange(from = -1, to = 5) [] a; - // :: error: (from.greater.than.to) - int @ArrayLenRange(from = 10, to = 3) [] b; - } + public void vargs(int @ArrayLen(0) ... ints) {} + + public void vargs(byte @ArrayLen(0) ... bytes) {} + + public void nullableArrays() { + Object @ArrayLen(2) [] @ArrayLen(1) [] o = new Object[][] {new Object[] {null}, null}; + Object @ArrayLen(1) [][] o2 = new Object[][] {null}; + Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; + } + + public void subtyping1(int @ArrayLen({1, 5}) [] a) { + int @ArrayLenRange(from = 1, to = 5) [] b = a; + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 2, to = 5) [] c = a; + } + + public void subtyping2(int @ArrayLenRange(from = 1, to = 5) [] a) { + int @ArrayLen({1, 2, 3, 4, 5}) [] b = a; + // :: error: (assignment.type.incompatible) + int @ArrayLen({1, 5}) [] c = a; + } + + public void subtyping3(int @ArrayLenRange(from = 1, to = 17) [] a) { + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 1, to = 12) [] b = a; + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 5, to = 18) [] c = a; + int @ArrayLenRange(from = 0, to = 20) [] d = a; + } + + public void lub1(boolean flag, @IntRange(from = 1, to = 200) int x) { + int[] a; + if (flag) { + a = new int[x]; + } else { + a = new int[250]; + } + + int @ArrayLenRange(from = 1, to = 250) [] b = a; + } + + public void lub2( + boolean flag, @IntRange(from = 1, to = 20) int x, @IntRange(from = 50, to = 75) int y) { + int[] a; + if (flag) { + a = new int[x]; + } else { + a = new int[y]; + } + + int @ArrayLenRange(from = 1, to = 75) [] b = a; + } + + public void lub3( + boolean flag, @IntRange(from = 1, to = 5) int x, @IntRange(from = 3, to = 7) int y) { + int[] a; + if (flag) { + a = new int[x]; + } else { + a = new int[y]; + } + + int @ArrayLenRange(from = 1, to = 7) [] b = a; + int @ArrayLen({1, 2, 3, 4, 5, 6, 7}) [] c = a; + } + + public void refine(int[] q) { + if (q.length < 20) { + @IntRange(from = 0, to = 19) int x = q.length; + int @ArrayLenRange(from = 0, to = 19) [] b = q; + if (q.length < 5) { + int @ArrayLen({0, 1, 2, 3, 4}) [] c = q; + } + } + } + + // The argument is an arraylen with too many values. + // :: warning: (too.many.values.given) + public void coerce(int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 36}) [] a) { + int @ArrayLenRange(from = 1, to = 36) [] b = a; + if (a.length < 15) { + // :: error: (assignment.type.incompatible) + int @ArrayLen({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) [] c = a; + } + } + + public void warnings() { + // :: warning: (negative.arraylen) + int @ArrayLenRange(from = -1, to = 5) [] a; + // :: error: (from.greater.than.to) + int @ArrayLenRange(from = 10, to = 3) [] b; + } } diff --git a/framework/tests/value/ArrayIntro.java b/framework/tests/value/ArrayIntro.java index 03e8b8c2572..ab1216b541d 100644 --- a/framework/tests/value/ArrayIntro.java +++ b/framework/tests/value/ArrayIntro.java @@ -1,18 +1,18 @@ import org.checkerframework.common.value.qual.*; public class ArrayIntro { - void test() { - int @MinLen(5) [] arr = new int[5]; - int a = 9; - a += 5; - a -= 2; - int @MinLen(12) [] arr1 = new int[a]; - int @MinLen(3) [] arr2 = {1, 2, 3}; - // :: error: (assignment.type.incompatible) - int @MinLen(4) [] arr3 = {4, 5, 6}; - // :: error: (assignment.type.incompatible) - int @MinLen(7) [] arr4 = new int[4]; - // :: error: (assignment.type.incompatible) - int @MinLen(16) [] arr5 = new int[a]; - } + void test() { + int @MinLen(5) [] arr = new int[5]; + int a = 9; + a += 5; + a -= 2; + int @MinLen(12) [] arr1 = new int[a]; + int @MinLen(3) [] arr2 = {1, 2, 3}; + // :: error: (assignment.type.incompatible) + int @MinLen(4) [] arr3 = {4, 5, 6}; + // :: error: (assignment.type.incompatible) + int @MinLen(7) [] arr4 = new int[4]; + // :: error: (assignment.type.incompatible) + int @MinLen(16) [] arr5 = new int[a]; + } } diff --git a/framework/tests/value/Basics.java b/framework/tests/value/Basics.java index ff0898dbd04..05787674e97 100644 --- a/framework/tests/value/Basics.java +++ b/framework/tests/value/Basics.java @@ -4,291 +4,291 @@ @SuppressWarnings({"deprecation", "removal"}) public class Basics { - public void boolTest() { - boolean a = false; - if (true) { - a = true; + public void boolTest() { + boolean a = false; + if (true) { + a = true; + } + @BoolVal({true, false}) boolean b = a; + + // :: error: (assignment.type.incompatible) + @BoolVal({false}) boolean c = a; } - @BoolVal({true, false}) boolean b = a; - // :: error: (assignment.type.incompatible) - @BoolVal({false}) boolean c = a; - } + public void CharacterTest() { + Character a = 'a'; + if (true) { + a = 'b'; + } + @IntVal({'a', 'b'}) Character b = a; - public void CharacterTest() { - Character a = 'a'; - if (true) { - a = 'b'; + // :: error: (assignment.type.incompatible) + @IntVal({'a'}) Character c = a; } - @IntVal({'a', 'b'}) Character b = a; - // :: error: (assignment.type.incompatible) - @IntVal({'a'}) Character c = a; - } + public void charTest() { + char a = 'a'; + if (true) { + a = 'b'; + } + @IntVal({'a', 'b'}) char b = a; - public void charTest() { - char a = 'a'; - if (true) { - a = 'b'; + // :: error: (assignment.type.incompatible) + @IntVal({'a'}) char c = a; } - @IntVal({'a', 'b'}) char b = a; - // :: error: (assignment.type.incompatible) - @IntVal({'a'}) char c = a; - } + public void DoubleTest() { + Double a = new Double(0.0); + if (true) { + a = 2.0; + } + @DoubleVal({0, 2}) Double b = a; - public void DoubleTest() { - Double a = new Double(0.0); - if (true) { - a = 2.0; + // :: error: (assignment.type.incompatible) + @DoubleVal({0}) Double c = a; } - @DoubleVal({0, 2}) Double b = a; - // :: error: (assignment.type.incompatible) - @DoubleVal({0}) Double c = a; - } + public void doubleTest() { + double a = 0.0; + if (true) { + a = 2.0; + } + @DoubleVal({0, 2}) double b = a; - public void doubleTest() { - double a = 0.0; - if (true) { - a = 2.0; + // :: error: (assignment.type.incompatible) + @DoubleVal({0}) double c = a; } - @DoubleVal({0, 2}) double b = a; - // :: error: (assignment.type.incompatible) - @DoubleVal({0}) double c = a; - } + public void FloatTest() { + Float a = new Float(0.0f); + if (true) { + a = 2.0f; + } + @DoubleVal({0, 2}) Float b = a; - public void FloatTest() { - Float a = new Float(0.0f); - if (true) { - a = 2.0f; + // :: error: (assignment.type.incompatible) + @DoubleVal({0}) Float c = a; } - @DoubleVal({0, 2}) Float b = a; - // :: error: (assignment.type.incompatible) - @DoubleVal({0}) Float c = a; - } + public void floatTest() { + float a = 0.0f; + if (true) { + a = 2.0f; + } + @DoubleVal({0, 2}) float b = a; - public void floatTest() { - float a = 0.0f; - if (true) { - a = 2.0f; + // :: error: (assignment.type.incompatible) + @DoubleVal({'a'}) float c = a; } - @DoubleVal({0, 2}) float b = a; - // :: error: (assignment.type.incompatible) - @DoubleVal({'a'}) float c = a; - } - - public void IntegerTest( - @IntRange(from = 3, to = 4) Integer x, @IntRange(from = 20, to = 30) Integer y) { - Integer a; - - /* IntVal + IntVal */ - a = Integer.valueOf(0); - if (true) { - a = 2; - } - // :: error: (assignment.type.incompatible) - @IntVal({0}) Integer test1 = a; - @IntVal({0, 2}) Integer test2 = a; - - /* IntRange + IntRange */ - a = x; - @IntVal({3, 4}) Integer test3 = a; - if (true) { - a = y; + public void IntegerTest( + @IntRange(from = 3, to = 4) Integer x, @IntRange(from = 20, to = 30) Integer y) { + Integer a; + + /* IntVal + IntVal */ + a = Integer.valueOf(0); + if (true) { + a = 2; + } + // :: error: (assignment.type.incompatible) + @IntVal({0}) Integer test1 = a; + @IntVal({0, 2}) Integer test2 = a; + + /* IntRange + IntRange */ + a = x; + @IntVal({3, 4}) Integer test3 = a; + if (true) { + a = y; + } + @IntRange(from = 15, to = 30) + // :: error: (assignment.type.incompatible) + Integer test4 = a; + @IntRange(from = 3, to = 30) Integer test5 = a; + + /* IntRange + IntVal */ + a = Integer.valueOf(0); + if (true) { + a = x; + } + @IntRange(from = 0, to = 4) Integer test7 = a; + + /* IntRange (Wider than 10) + IntVal */ + a = Integer.valueOf(0); + if (true) { + a = y; + } + @IntRange(from = 1, to = 30) + // :: error: (assignment.type.incompatible) + Integer test8 = a; + @IntRange(from = 0, to = 30) Integer test9 = a; } - @IntRange(from = 15, to = 30) - // :: error: (assignment.type.incompatible) - Integer test4 = a; - @IntRange(from = 3, to = 30) Integer test5 = a; - - /* IntRange + IntVal */ - a = Integer.valueOf(0); - if (true) { - a = x; - } - @IntRange(from = 0, to = 4) Integer test7 = a; - /* IntRange (Wider than 10) + IntVal */ - a = Integer.valueOf(0); - if (true) { - a = y; - } - @IntRange(from = 1, to = 30) - // :: error: (assignment.type.incompatible) - Integer test8 = a; - @IntRange(from = 0, to = 30) Integer test9 = a; - } - - public void intTest(@IntRange(from = 3, to = 4) int x, @IntRange(from = 20, to = 30) int y) { - int a; - - /* IntVal + IntVal */ - a = 0; - if (true) { - a = 2; + public void intTest(@IntRange(from = 3, to = 4) int x, @IntRange(from = 20, to = 30) int y) { + int a; + + /* IntVal + IntVal */ + a = 0; + if (true) { + a = 2; + } + // :: error: (assignment.type.incompatible) + @IntVal({0}) int test1 = a; + @IntVal({0, 2}) int test2 = a; + + /* IntRange + IntRange */ + a = x; + @IntVal({3, 4}) int test3 = a; + if (true) { + a = y; + } + @IntRange(from = 15, to = 30) + // :: error: (assignment.type.incompatible) + int test4 = a; + @IntRange(from = 3, to = 30) int test5 = a; + + /* IntRange + IntVal */ + a = 0; + if (true) { + a = x; + } + @IntRange(from = 0, to = 4) int test7 = a; + + /* IntRange (Wider than 10) + IntVal */ + a = 0; + if (true) { + a = y; + } + @IntRange(from = 1, to = 30) + // :: error: (assignment.type.incompatible) + int test8 = a; + @IntRange(from = 0, to = 30) int test9 = a; } - // :: error: (assignment.type.incompatible) - @IntVal({0}) int test1 = a; - @IntVal({0, 2}) int test2 = a; - - /* IntRange + IntRange */ - a = x; - @IntVal({3, 4}) int test3 = a; - if (true) { - a = y; - } - @IntRange(from = 15, to = 30) - // :: error: (assignment.type.incompatible) - int test4 = a; - @IntRange(from = 3, to = 30) int test5 = a; - - /* IntRange + IntVal */ - a = 0; - if (true) { - a = x; - } - @IntRange(from = 0, to = 4) int test7 = a; - /* IntRange (Wider than 10) + IntVal */ - a = 0; - if (true) { - a = y; - } - @IntRange(from = 1, to = 30) - // :: error: (assignment.type.incompatible) - int test8 = a; - @IntRange(from = 0, to = 30) int test9 = a; - } - - public void intCastTest(@IntVal({0, 1}) int input) { - @IntVal({0, 1}) int c = (int) input; - @IntVal({0, 1}) int ac = (@IntVal({0, 1}) int) input; - @IntVal({0, 1, 2}) int sc = (@IntVal({0, 1, 2}) int) input; - // :: warning: (cast.unsafe) - @IntVal({1}) int uc = (@IntVal({1}) int) input; - // :: warning: (cast.unsafe) - @IntVal({2}) int bc = (@IntVal({2}) int) input; - } - - public void IntDoubleTest( - @IntVal({0, 1}) int iv, - @IntRange(from = 2, to = 3) int ir, - @IntRange(from = 2, to = 20) int irw, - @DoubleVal({4.0, 5.0}) double dv1) { - double a; - - /* IntVal + DoubleVal */ - a = iv; - if (true) { - a = dv1; - } - // :: error: (assignment.type.incompatible) - @DoubleVal({4.0, 5.0}) double test1 = a; - @DoubleVal({0.0, 1.0, 2.0, 3.0, 4.0, 5.0}) double test2 = a; - - /* IntRange + DoubleVal */ - a = ir; - // :: error: (assignment.type.incompatible) - @DoubleVal({2.0}) double test3 = a; - @DoubleVal({2.0, 3.0}) double test4 = a; - if (true) { - a = dv1; + public void intCastTest(@IntVal({0, 1}) int input) { + @IntVal({0, 1}) int c = (int) input; + @IntVal({0, 1}) int ac = (@IntVal({0, 1}) int) input; + @IntVal({0, 1, 2}) int sc = (@IntVal({0, 1, 2}) int) input; + // :: warning: (cast.unsafe) + @IntVal({1}) int uc = (@IntVal({1}) int) input; + // :: warning: (cast.unsafe) + @IntVal({2}) int bc = (@IntVal({2}) int) input; } - // :: error: (assignment.type.incompatible) - test1 = a; - test2 = a; - - /* IntRange (Wider than 10) + DoubleVal */ - a = irw; - if (true) { - a = dv1; - } - // :: error: (assignment.type.incompatible) - @DoubleVal({4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double test5 = a; - @UnknownVal double test6 = a; - } - - public void stringTest() { - String a = "test1"; - if (true) { - a = "test2"; + + public void IntDoubleTest( + @IntVal({0, 1}) int iv, + @IntRange(from = 2, to = 3) int ir, + @IntRange(from = 2, to = 20) int irw, + @DoubleVal({4.0, 5.0}) double dv1) { + double a; + + /* IntVal + DoubleVal */ + a = iv; + if (true) { + a = dv1; + } + // :: error: (assignment.type.incompatible) + @DoubleVal({4.0, 5.0}) double test1 = a; + @DoubleVal({0.0, 1.0, 2.0, 3.0, 4.0, 5.0}) double test2 = a; + + /* IntRange + DoubleVal */ + a = ir; + // :: error: (assignment.type.incompatible) + @DoubleVal({2.0}) double test3 = a; + @DoubleVal({2.0, 3.0}) double test4 = a; + if (true) { + a = dv1; + } + // :: error: (assignment.type.incompatible) + test1 = a; + test2 = a; + + /* IntRange (Wider than 10) + DoubleVal */ + a = irw; + if (true) { + a = dv1; + } + // :: error: (assignment.type.incompatible) + @DoubleVal({4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double test5 = a; + @UnknownVal double test6 = a; } - @StringVal({"test1", "test2"}) String b = a; - // :: error: (assignment.type.incompatible) - @StringVal({"test1"}) String c = a; - } + public void stringTest() { + String a = "test1"; + if (true) { + a = "test2"; + } + @StringVal({"test1", "test2"}) String b = a; - public void stringCastTest() { - Object a = "test1"; - @StringVal({"test1"}) String b = (String) a; - @StringVal({"test1"}) String c = (java.lang.String) b; - } + // :: error: (assignment.type.incompatible) + @StringVal({"test1"}) String c = a; + } - void tooManyValuesInt() { - // :: warning: (too.many.values.given.int) - @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 100}) int a = 20; // This should succeed if a is treated as @IntRange(from=1, to=100) + public void stringCastTest() { + Object a = "test1"; + @StringVal({"test1"}) String b = (String) a; + @StringVal({"test1"}) String c = (java.lang.String) b; + } - // :: warning: (too.many.values.given.int) - @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) - // :: error: (assignment.type.incompatible) - int b = 20; // d is @IntRange(from=1, to=12) + void tooManyValuesInt() { + // :: warning: (too.many.values.given.int) + @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 100}) int a = 20; // This should succeed if a is treated as @IntRange(from=1, to=100) - @UnknownVal int c = a; // This should always succeed - } + // :: warning: (too.many.values.given.int) + @IntVal({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) + // :: error: (assignment.type.incompatible) + int b = 20; // d is @IntRange(from=1, to=12) - void fromGreaterThanTo() { - // :: error: (from.greater.than.to) - @IntRange(from = 2, to = 0) - // :: error: (assignment.type.incompatible) - int a = 1; // a should be @BottomVal + @UnknownVal int c = a; // This should always succeed + } - @IntRange(from = 1) int b = 2; + void fromGreaterThanTo() { + // :: error: (from.greater.than.to) + @IntRange(from = 2, to = 0) + // :: error: (assignment.type.incompatible) + int a = 1; // a should be @BottomVal - @IntRange(to = 2) int c = 1; + @IntRange(from = 1) int b = 2; - @IntRange(to = 2, from = 0) int d = 1; - } + @IntRange(to = 2) int c = 1; - void tooManyValuesDouble() { - // :: warning: (too.many.values.given) - @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double a = 8.0; + @IntRange(to = 2, from = 0) int d = 1; + } + + void tooManyValuesDouble() { + // :: warning: (too.many.values.given) + @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double a = 8.0; - @UnknownVal double b = a; // This should always succeed + @UnknownVal double b = a; // This should always succeed - @UnknownVal double c = 0; + @UnknownVal double c = 0; - a = c; // This should succeed if a is treated as @UnknownVal + a = c; // This should succeed if a is treated as @UnknownVal - // :: warning: (too.many.values.given) - @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double d = 8.0; + // :: warning: (too.many.values.given) + @DoubleVal({1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}) double d = 8.0; - d = 2.0 * d; // This should succeed since d is @UnknownVal - } + d = 2.0 * d; // This should succeed since d is @UnknownVal + } - void tooManyValuesString() { - // :: warning: (too.many.values.given) - @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String a = "h"; + void tooManyValuesString() { + // :: warning: (too.many.values.given) + @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String a = "h"; - @UnknownVal String b = a; // This should always succeed + @UnknownVal String b = a; // This should always succeed - @UnknownVal String c = ""; + @UnknownVal String c = ""; - // :: error: (assignment.type.incompatible) - a = c; // This should not succeed if a is treated as @ArrayLen(1) + // :: error: (assignment.type.incompatible) + a = c; // This should not succeed if a is treated as @ArrayLen(1) - @ArrayLen(1) String al = a; // a is @ArrayLen(1) + @ArrayLen(1) String al = a; // a is @ArrayLen(1) - // :: warning: (too.many.values.given) - @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String d = "h"; + // :: warning: (too.many.values.given) + @StringVal({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}) String d = "h"; - // :: error: (assignment.type.incompatible) - d = "b" + d; // This should not succeed since d is @ArrayLen(1) + // :: error: (assignment.type.incompatible) + d = "b" + d; // This should not succeed since d is @ArrayLen(1) - @ArrayLen(1) String dl = d; // d is @ArrayLen(1) - } + @ArrayLen(1) String dl = d; // d is @ArrayLen(1) + } } diff --git a/framework/tests/value/BigIntegerTest.java b/framework/tests/value/BigIntegerTest.java index 19ce9666f8e..4bc59641569 100644 --- a/framework/tests/value/BigIntegerTest.java +++ b/framework/tests/value/BigIntegerTest.java @@ -1,37 +1,38 @@ -import java.math.BigInteger; import org.checkerframework.common.value.qual.IntRange; import org.checkerframework.common.value.qual.PolyValue; +import java.math.BigInteger; + public class BigIntegerTest { - void construct1(@IntRange(from = -1, to = 1) int signum, byte[] magnitude) { - BigInteger val = new BigInteger(signum, magnitude); - } + void construct1(@IntRange(from = -1, to = 1) int signum, byte[] magnitude) { + BigInteger val = new BigInteger(signum, magnitude); + } - void construct2(String val, @IntRange(from = 2, to = 36) int radix) { - BigInteger value = new BigInteger(val, radix); - } + void construct2(String val, @IntRange(from = 2, to = 36) int radix) { + BigInteger value = new BigInteger(val, radix); + } - @PolyValue double getDoubleVal(@PolyValue BigInteger val) { - return val.doubleValue(); - } + @PolyValue double getDoubleVal(@PolyValue BigInteger val) { + return val.doubleValue(); + } - @PolyValue int getIntVal(@PolyValue BigInteger val) { - return val.intValue(); - } + @PolyValue int getIntVal(@PolyValue BigInteger val) { + return val.intValue(); + } - @PolyValue float getFloatVal(@PolyValue BigInteger val) { - return val.floatValue(); - } + @PolyValue float getFloatVal(@PolyValue BigInteger val) { + return val.floatValue(); + } - @PolyValue long getLongVal(@PolyValue BigInteger val) { - return val.longValue(); - } + @PolyValue long getLongVal(@PolyValue BigInteger val) { + return val.longValue(); + } - void compareTo(BigInteger val, BigInteger to) { - @IntRange(from = -1, to = 1) int compared = val.compareTo(to); - } + void compareTo(BigInteger val, BigInteger to) { + @IntRange(from = -1, to = 1) int compared = val.compareTo(to); + } - void signum(BigInteger val) { - @IntRange(from = -1, to = 1) int signum = val.signum(); - } + void signum(BigInteger val) { + @IntRange(from = -1, to = 1) int signum = val.signum(); + } } diff --git a/framework/tests/value/Binaries.java b/framework/tests/value/Binaries.java index 8fc57dfa6b7..9b1eec7f28f 100644 --- a/framework/tests/value/Binaries.java +++ b/framework/tests/value/Binaries.java @@ -1,380 +1,381 @@ -import java.util.BitSet; import org.checkerframework.common.value.qual.*; +import java.util.BitSet; + public class Binaries { - private BitSet bitmap; - - public void test() { - int length = bitmap.length(); - for (int i = 0, t = 0; i < length; i++) { - t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); - if (i % 8 == 7 || i == length - 1) { - write(t); - t = 0; - } - } - } - - void write(int t) {} - - // Test widenedUpperBound is working. - public void loop(int c) { - double v = 0; - int decexp = 0; - int seendot = 0; - while (true) { - if (c == '.' && seendot == 0) seendot = 1; - else if ('0' <= c && c <= '9') { - v = v * 10 + (c - '0'); - decexp += seendot; - } else { - break; - } - } - } - - public void testIntRange( - @IntVal({1, 2}) int values, - @IntRange(from = 3, to = 4) int range1, - @IntRange(from = 5, to = 20) int range2, - @BottomVal int bottom, - @UnknownVal int top) { - - /* IntRange + IntRange */ - @IntRange(from = 8, to = 24) int a = range1 + range2; - - /* IntRange * IntVal */ - @IntRange(from = 3, to = 8) int b = values * range1; - - /* IntRange * BottomVal */ - int c = range1 * bottom; - - /* IntRange * UnknownVal */ - @IntRange(from = 0) - // :: error: (assignment.type.incompatible) - int d = range1 + top; - } - - public void add() { - int a = 1; - if (true) { - a = 2; + private BitSet bitmap; + + public void test() { + int length = bitmap.length(); + for (int i = 0, t = 0; i < length; i++) { + t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); + if (i % 8 == 7 || i == length - 1) { + write(t); + t = 0; + } + } } - @IntVal({3, 4}) int b = a + 2; - double c = 1.0; - if (true) { - c = 2.0; + void write(int t) {} + + // Test widenedUpperBound is working. + public void loop(int c) { + double v = 0; + int decexp = 0; + int seendot = 0; + while (true) { + if (c == '.' && seendot == 0) seendot = 1; + else if ('0' <= c && c <= '9') { + v = v * 10 + (c - '0'); + decexp += seendot; + } else { + break; + } + } } - @DoubleVal({3.0, 4.0}) double d = c + 2; - char e = '1'; - if (true) { - e = '2'; - } - @IntVal({'3', '4'}) char f = (char) (e + 2); + public void testIntRange( + @IntVal({1, 2}) int values, + @IntRange(from = 3, to = 4) int range1, + @IntRange(from = 5, to = 20) int range2, + @BottomVal int bottom, + @UnknownVal int top) { - String g = "A"; - if (true) { - g = "B"; - } - @StringVal({"nullC", "AC", "BC"}) String h = g + "C"; - } + /* IntRange + IntRange */ + @IntRange(from = 8, to = 24) int a = range1 + range2; - public void subtract() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({-1, 0}) int b = a - 2; + /* IntRange * IntVal */ + @IntRange(from = 3, to = 8) int b = values * range1; - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({-1.0, 0.0}) double d = c - 2; + /* IntRange * BottomVal */ + int c = range1 * bottom; - char e = '2'; - if (true) { - e = '3'; + /* IntRange * UnknownVal */ + @IntRange(from = 0) + // :: error: (assignment.type.incompatible) + int d = range1 + top; } - @IntVal({'0', '1'}) char f = (char) (e - 2); - } + public void add() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({3, 4}) int b = a + 2; - public void multiply() { - int a = 1; - if (true) { - a = 2; - } - @IntVal({2, 4}) int b = a * 2; + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({3.0, 4.0}) double d = c + 2; - double c = 1.0; - if (true) { - c = 2.0; + char e = '1'; + if (true) { + e = '2'; + } + @IntVal({'3', '4'}) char f = (char) (e + 2); + + String g = "A"; + if (true) { + g = "B"; + } + @StringVal({"nullC", "AC", "BC"}) String h = g + "C"; } - @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - char e = (char) 25; - if (true) { + public void subtract() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({-1, 0}) int b = a - 2; + + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({-1.0, 0.0}) double d = c - 2; - e = (char) 26; + char e = '2'; + if (true) { + e = '3'; + } + + @IntVal({'0', '1'}) char f = (char) (e - 2); } - @IntVal({'2', '4'}) char f = (char) (e * 2); + public void multiply() { + int a = 1; + if (true) { + a = 2; + } + @IntVal({2, 4}) int b = a * 2; - @DoubleVal(0.75) float g = 1 * 0.75f; - } + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({2.0, 4.0}) double d = (double) (c * 2); - public void divide() { - int a = 2; - if (true) { - a = 4; - } - @IntVal({1, 2}) int b = a / 2; + char e = (char) 25; + if (true) { - double c = 1.0; - if (true) { - c = 2.0; - } - @DoubleVal({0.5, 1.0}) double d = c / 2; + e = (char) 26; + } - char e = (char) 96; - if (true) { - e = (char) 98; + @IntVal({'2', '4'}) char f = (char) (e * 2); + + @DoubleVal(0.75) float g = 1 * 0.75f; } - @IntVal({'0', '1'}) char f = (char) (e / 2); + public void divide() { + int a = 2; + if (true) { + a = 4; + } + @IntVal({1, 2}) int b = a / 2; - @IntVal(0) int g = 2 / 3; - @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; - @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; - } + double c = 1.0; + if (true) { + c = 2.0; + } + @DoubleVal({0.5, 1.0}) double d = c / 2; - public void remainder() { - int a = 4; - if (true) { - a = 5; - } - @IntVal({1, 2}) int b = a % 3; + char e = (char) 96; + if (true) { + e = (char) 98; + } - double c = 4.0; - if (true) { - c = 5.0; - } - @DoubleVal({1.0, 2.0}) double d = c % 3; + @IntVal({'0', '1'}) char f = (char) (e / 2); - char e = (char) 98; - if (true) { - e = (char) 99; + @IntVal(0) int g = 2 / 3; + @IntVal(0) int h = (Integer.MAX_VALUE - 1) / Integer.MAX_VALUE; + @IntVal(0) long l = (Long.MAX_VALUE - 1) / Long.MAX_VALUE; } - @IntVal({'0', '1'}) char f = (char) (e % 50); - } + public void remainder() { + int a = 4; + if (true) { + a = 5; + } + @IntVal({1, 2}) int b = a % 3; - public boolean flag = true; + double c = 4.0; + if (true) { + c = 5.0; + } + @DoubleVal({1.0, 2.0}) double d = c % 3; - public void and() { - boolean a = true; - if (flag) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a & true; + char e = (char) 98; + if (true) { + e = (char) 99; + } - int c = 4; - if (true) { - c = 5; + @IntVal({'0', '1'}) char f = (char) (e % 50); } - @IntVal({0, 1}) int d = c & 3; - char e = (char) 48; - if (true) { + public boolean flag = true; - e = (char) 51; - } + public void and() { + boolean a = true; + if (flag) { + a = false; + } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a & true; - @IntVal({'0', '2'}) char f = (char) (e & 50); - } + int c = 4; + if (true) { + c = 5; + } + @IntVal({0, 1}) int d = c & 3; - public void or() { - boolean a = true; - if (true) { - a = false; - } - // TODO: we could detect this case - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a | true; + char e = (char) 48; + if (true) { - int c = 4; - if (true) { - c = 5; - } - @IntVal({7}) int d = c | 3; + e = (char) 51; + } - char e = (char) 48; - if (true) { - e = (char) 51; + @IntVal({'0', '2'}) char f = (char) (e & 50); } - @IntVal({'1', '3'}) char f = (char) (e | 1); - } + public void or() { + boolean a = true; + if (true) { + a = false; + } + // TODO: we could detect this case + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a | true; - public void xor() { - boolean a = true; - if (true) { - a = false; - } - // :: error: (assignment.type.incompatible) - @BoolVal({true}) boolean b = a ^ true; + int c = 4; + if (true) { + c = 5; + } + @IntVal({7}) int d = c | 3; + + char e = (char) 48; + if (true) { + e = (char) 51; + } - int c = 4; - if (true) { - c = 5; + @IntVal({'1', '3'}) char f = (char) (e | 1); } - @IntVal({7, 6}) int d = c ^ 3; - char e = (char) 48; - if (true) { + public void xor() { + boolean a = true; + if (true) { + a = false; + } + // :: error: (assignment.type.incompatible) + @BoolVal({true}) boolean b = a ^ true; - e = (char) 51; - } + int c = 4; + if (true) { + c = 5; + } + @IntVal({7, 6}) int d = c ^ 3; + + char e = (char) 48; + if (true) { + + e = (char) 51; + } - @IntVal({'1', '2'}) char f = (char) (e ^ 1); - } - - public void boolAnd() { - @BoolVal({false}) boolean a = true && false; - @BoolVal({true}) boolean b = false || true; - } - - public void conditionals() { - @BoolVal({false}) boolean a = 1.0f == '1'; - @BoolVal({true}) boolean b = 1 != 2.0; - @BoolVal({true}) boolean c = 1 > 0.5; - @BoolVal({true}) boolean d = 1 >= 1.0; - @BoolVal({true}) boolean e = 1 < 1.1f; - @BoolVal({true}) boolean f = (char) 2 <= 2.0; - @IntVal('!') Character BANG = '!'; - @BoolVal(true) boolean g = (BANG == '!'); - char bangChar = '!'; - @BoolVal(true) boolean h = (BANG == bangChar); - - Character bang = '!'; - // Reference equalitiy is used - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean i = (BANG == bang); - } - - public void loop() throws InterruptedException { - int spurious_count = 0; - while (true) { - wait(); - if (System.currentTimeMillis() == 0) { - spurious_count++; - if (spurious_count > 1024) { - break; - } - } + @IntVal({'1', '2'}) char f = (char) (e ^ 1); + } + + public void boolAnd() { + @BoolVal({false}) boolean a = true && false; + @BoolVal({true}) boolean b = false || true; + } + + public void conditionals() { + @BoolVal({false}) boolean a = 1.0f == '1'; + @BoolVal({true}) boolean b = 1 != 2.0; + @BoolVal({true}) boolean c = 1 > 0.5; + @BoolVal({true}) boolean d = 1 >= 1.0; + @BoolVal({true}) boolean e = 1 < 1.1f; + @BoolVal({true}) boolean f = (char) 2 <= 2.0; + @IntVal('!') Character BANG = '!'; + @BoolVal(true) boolean g = (BANG == '!'); + char bangChar = '!'; + @BoolVal(true) boolean h = (BANG == bangChar); + + Character bang = '!'; + // Reference equalitiy is used + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean i = (BANG == bang); + } + + public void loop() throws InterruptedException { + int spurious_count = 0; + while (true) { + wait(); + if (System.currentTimeMillis() == 0) { + spurious_count++; + if (spurious_count > 1024) { + break; + } + } + } } - } - public void shifts() { - int a = -8; - if (true) { - a = 4; + public void shifts() { + int a = -8; + if (true) { + a = 4; + } + @IntVal({1, -2}) int b = a >> 2; + + int c = 1; + if (true) { + c = 2; + } + @IntVal({4, 8}) int d = c << 2; + + int e = -8; + if (true) { + e = 4; + } + @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; + + char g = (char) 24; + if (true) { + g = (char) 25; + } + + @IntVal({'0', '2'}) char h = (char) (g << 1); } - @IntVal({1, -2}) int b = a >> 2; - int c = 1; - if (true) { - c = 2; + public void chains() { + char a = 2; + int b = 3; + double c = 5; + + @DoubleVal({1}) double d = a * b - c; + + @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; } - @IntVal({4, 8}) int d = c << 2; - int e = -8; - if (true) { - e = 4; + public void compareWithNull() { + String s = "1"; + // TODO + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean b = (s != null); } - @IntVal({Integer.MAX_VALUE / 2 - 1, 1}) int f = e >>> 2; - char g = (char) 24; - if (true) { - g = (char) 25; + public void nullConcatenation(@StringVal({"a", "b"}) String arg) { + String n1 = null; + String n2 = "null"; + String k = "const"; + + // @StringVal("nullnull") String a1 = n1 + null; + @StringVal("nullnull") String a2 = n1 + "null"; + // @StringVal("nullnull") String a3 = n1 + n1; + @StringVal("nullnull") String a4 = n1 + n2; + @StringVal({"nullconst", "nullnull"}) String a5 = n1 + k; + @StringVal("nullconst") String a6 = n1 + "const"; + + @StringVal("nullnull") String b1 = n2 + null; + @StringVal("nullnull") String b2 = n2 + "null"; + @StringVal("nullnull") String b3 = n2 + n1; + @StringVal("nullnull") String b4 = n2 + n2; + @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; + @StringVal("nullconst") String b6 = n2 + "const"; + + @StringVal({"anull", "bnull", "nullnull"}) String c1 = arg + null; + @StringVal({"anull", "bnull", "nullnull"}) String c2 = arg + "null"; + @StringVal({"anull", "bnull", "nullnull"}) String c3 = arg + n1; + @StringVal({"anull", "bnull", "nullnull"}) String c4 = arg + n2; + @StringVal({"aconst", "anull", "bconst", "bnull", "nullconst", "nullnull"}) String c5 = arg + k; + @StringVal({"aconst", "bconst", "nullconst"}) String c6 = arg + "const"; + @StringVal({"a2147483647", "b2147483647", "null2147483647"}) String c7 = arg + Integer.MAX_VALUE; } - @IntVal({'0', '2'}) char h = (char) (g << 1); - } - - public void chains() { - char a = 2; - int b = 3; - double c = 5; - - @DoubleVal({1}) double d = a * b - c; - - @DoubleVal({3}) double e = a * c - 2 * b - (char) 1; - } - - public void compareWithNull() { - String s = "1"; - // TODO - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean b = (s != null); - } - - public void nullConcatenation(@StringVal({"a", "b"}) String arg) { - String n1 = null; - String n2 = "null"; - String k = "const"; - - // @StringVal("nullnull") String a1 = n1 + null; - @StringVal("nullnull") String a2 = n1 + "null"; - // @StringVal("nullnull") String a3 = n1 + n1; - @StringVal("nullnull") String a4 = n1 + n2; - @StringVal({"nullconst", "nullnull"}) String a5 = n1 + k; - @StringVal("nullconst") String a6 = n1 + "const"; - - @StringVal("nullnull") String b1 = n2 + null; - @StringVal("nullnull") String b2 = n2 + "null"; - @StringVal("nullnull") String b3 = n2 + n1; - @StringVal("nullnull") String b4 = n2 + n2; - @StringVal({"nullconst", "nullnull"}) String b5 = n2 + k; - @StringVal("nullconst") String b6 = n2 + "const"; - - @StringVal({"anull", "bnull", "nullnull"}) String c1 = arg + null; - @StringVal({"anull", "bnull", "nullnull"}) String c2 = arg + "null"; - @StringVal({"anull", "bnull", "nullnull"}) String c3 = arg + n1; - @StringVal({"anull", "bnull", "nullnull"}) String c4 = arg + n2; - @StringVal({"aconst", "anull", "bconst", "bnull", "nullconst", "nullnull"}) String c5 = arg + k; - @StringVal({"aconst", "bconst", "nullconst"}) String c6 = arg + "const"; - @StringVal({"a2147483647", "b2147483647", "null2147483647"}) String c7 = arg + Integer.MAX_VALUE; - } - - public void conditionalComparisions() { - @BoolVal(true) boolean a1 = true || false; - @BoolVal(true) boolean a2 = true || true; - @BoolVal(false) boolean a3 = false || false; - @BoolVal(true) boolean a4 = false || true; - - @BoolVal(false) boolean a5 = true && false; - @BoolVal(true) boolean a6 = true && true; - @BoolVal(false) boolean a7 = false && false; - @BoolVal(false) boolean a8 = false && true; - - boolean unknown = flag ? true : false; - @BoolVal(true) boolean a9 = true || unknown; - @BoolVal(true) boolean a11 = unknown || true; - // :: error: (assignment.type.incompatible) - @BoolVal(false) boolean a12 = unknown || false; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a13 = false || unknown; - - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a14 = true && unknown; - // :: error: (assignment.type.incompatible) - @BoolVal(true) boolean a15 = unknown && true; - @BoolVal(false) boolean a16 = unknown && false; - @BoolVal(false) boolean a17 = false && unknown; - } + public void conditionalComparisions() { + @BoolVal(true) boolean a1 = true || false; + @BoolVal(true) boolean a2 = true || true; + @BoolVal(false) boolean a3 = false || false; + @BoolVal(true) boolean a4 = false || true; + + @BoolVal(false) boolean a5 = true && false; + @BoolVal(true) boolean a6 = true && true; + @BoolVal(false) boolean a7 = false && false; + @BoolVal(false) boolean a8 = false && true; + + boolean unknown = flag ? true : false; + @BoolVal(true) boolean a9 = true || unknown; + @BoolVal(true) boolean a11 = unknown || true; + // :: error: (assignment.type.incompatible) + @BoolVal(false) boolean a12 = unknown || false; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a13 = false || unknown; + + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a14 = true && unknown; + // :: error: (assignment.type.incompatible) + @BoolVal(true) boolean a15 = unknown && true; + @BoolVal(false) boolean a16 = unknown && false; + @BoolVal(false) boolean a17 = false && unknown; + } } diff --git a/framework/tests/value/BitsMethodsIntRange.java b/framework/tests/value/BitsMethodsIntRange.java index 6929a7e82c1..547ee314b96 100644 --- a/framework/tests/value/BitsMethodsIntRange.java +++ b/framework/tests/value/BitsMethodsIntRange.java @@ -1,13 +1,13 @@ import org.checkerframework.common.value.qual.IntRange; public class BitsMethodsIntRange { - void caseInteger(int integerIndex) { - @IntRange(from = 0, to = 32) int leadingZeros = Integer.numberOfLeadingZeros(integerIndex); - @IntRange(from = 0, to = 32) int trailingZeros = Integer.numberOfLeadingZeros(integerIndex); - } + void caseInteger(int integerIndex) { + @IntRange(from = 0, to = 32) int leadingZeros = Integer.numberOfLeadingZeros(integerIndex); + @IntRange(from = 0, to = 32) int trailingZeros = Integer.numberOfLeadingZeros(integerIndex); + } - void caseLong(long longIndex) { - @IntRange(from = 0, to = 64) int leadingZeros = Long.numberOfLeadingZeros(longIndex); - @IntRange(from = 0, to = 64) int trailingZeros = Long.numberOfLeadingZeros(longIndex); - } + void caseLong(long longIndex) { + @IntRange(from = 0, to = 64) int leadingZeros = Long.numberOfLeadingZeros(longIndex); + @IntRange(from = 0, to = 64) int trailingZeros = Long.numberOfLeadingZeros(longIndex); + } } diff --git a/framework/tests/value/BitwiseAnd.java b/framework/tests/value/BitwiseAnd.java index f40c5272772..888f9ac5931 100644 --- a/framework/tests/value/BitwiseAnd.java +++ b/framework/tests/value/BitwiseAnd.java @@ -2,35 +2,35 @@ public class BitwiseAnd { - public static void Case11(@IntRange(from = 0) byte b) { - @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; - } - - public static void Case12(@IntRange(to = -1) byte b) { - @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; - } - - public static void Case13(byte b) { - @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; - } - - public static void Case21(@IntRange(from = 0) byte b) { - @IntRange(from = 0, to = 0x0f) long i1 = b & 0x800000000000000fL; - } - - public static void Case22(@IntRange(to = -1) byte b) { - @IntRange(from = 0x8000000000000000L, to = 0x80000000ffffffffL) long i1 = b & 0x80000000ffffffffL; - } - - public static void Case23(byte b) { - @IntRange(from = 0x8000000000000000L, to = 0xf0) long i1 = b & 0x80000000000000f0L; - } - - public static void Issue1623(byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - byte b = bytes[i]; - @IntRange(from = 0, to = 15) int i1 = (b & 0xf0) >> 4; - @IntRange(from = 0, to = 15) int i2 = b & 0x0f; + public static void Case11(@IntRange(from = 0) byte b) { + @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; + } + + public static void Case12(@IntRange(to = -1) byte b) { + @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; + } + + public static void Case13(byte b) { + @IntRange(from = 0, to = 0xf0) int i1 = b & 0xf0; + } + + public static void Case21(@IntRange(from = 0) byte b) { + @IntRange(from = 0, to = 0x0f) long i1 = b & 0x800000000000000fL; + } + + public static void Case22(@IntRange(to = -1) byte b) { + @IntRange(from = 0x8000000000000000L, to = 0x80000000ffffffffL) long i1 = b & 0x80000000ffffffffL; + } + + public static void Case23(byte b) { + @IntRange(from = 0x8000000000000000L, to = 0xf0) long i1 = b & 0x80000000000000f0L; + } + + public static void Issue1623(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + @IntRange(from = 0, to = 15) int i1 = (b & 0xf0) >> 4; + @IntRange(from = 0, to = 15) int i2 = b & 0x0f; + } } - } } diff --git a/framework/tests/value/Boxing.java b/framework/tests/value/Boxing.java index 05121e91b4f..cdefae6530e 100644 --- a/framework/tests/value/Boxing.java +++ b/framework/tests/value/Boxing.java @@ -2,22 +2,22 @@ public class Boxing { - void simpleTest1(@BottomVal Integer x, int y) { - @BottomVal int f = x.intValue(); - if (x.intValue() == y) { - @BottomVal int z = y; + void simpleTest1(@BottomVal Integer x, int y) { + @BottomVal int f = x.intValue(); + if (x.intValue() == y) { + @BottomVal int z = y; + } } - } - void simpleTest2(@BottomVal Integer x, int y) { - if (x == y) { - @BottomVal int z = y; + void simpleTest2(@BottomVal Integer x, int y) { + if (x == y) { + @BottomVal int z = y; + } } - } - void simpleTest3(@BottomVal Integer x, Integer y) { - if (x == y) { - @BottomVal int z = y; + void simpleTest3(@BottomVal Integer x, Integer y) { + if (x == y) { + @BottomVal int z = y; + } } - } } diff --git a/framework/tests/value/CharArrayWithNonLiteralConstants.java b/framework/tests/value/CharArrayWithNonLiteralConstants.java index d0af0fb48d7..fba9d8da2a1 100644 --- a/framework/tests/value/CharArrayWithNonLiteralConstants.java +++ b/framework/tests/value/CharArrayWithNonLiteralConstants.java @@ -3,9 +3,9 @@ public class CharArrayWithNonLiteralConstants { - public static void main(String[] args) { - char @StringVal("hello") [] greeting1 = {'h', 'e', 'l', 'l', 'o'}; - @IntVal('e') char e = 'e'; - char @StringVal("hello") [] greeting2 = {'h', e, 'l', 'l', 'o'}; - } + public static void main(String[] args) { + char @StringVal("hello") [] greeting1 = {'h', 'e', 'l', 'l', 'o'}; + @IntVal('e') char e = 'e'; + char @StringVal("hello") [] greeting2 = {'h', e, 'l', 'l', 'o'}; + } } diff --git a/framework/tests/value/CharacterToString.java b/framework/tests/value/CharacterToString.java index a0f34bc99de..2dd0c8f0be9 100644 --- a/framework/tests/value/CharacterToString.java +++ b/framework/tests/value/CharacterToString.java @@ -2,7 +2,7 @@ // two annotations from the same hierarchy. // https://github.com/typetools/checker-framework/issues/1356 public class CharacterToString { - void m() { - String s = Character.toString('a'); - } + void m() { + String s = Character.toString('a'); + } } diff --git a/framework/tests/value/ClassNotFound.java b/framework/tests/value/ClassNotFound.java index 86c3d827806..ebca93c6bc7 100644 --- a/framework/tests/value/ClassNotFound.java +++ b/framework/tests/value/ClassNotFound.java @@ -3,15 +3,15 @@ public class ClassNotFound { - @StaticallyExecutable - @Pure - public static int foo(int a) { - return a + 2; - } + @StaticallyExecutable + @Pure + public static int foo(int a) { + return a + 2; + } - public void bar() { - int a = 0; - // :: warning: (class.find.failed) - foo(a); - } + public void bar() { + int a = 0; + // :: warning: (class.find.failed) + foo(a); + } } diff --git a/framework/tests/value/CompoundAssignment.java b/framework/tests/value/CompoundAssignment.java index 406e9d47311..0d7eb533fee 100644 --- a/framework/tests/value/CompoundAssignment.java +++ b/framework/tests/value/CompoundAssignment.java @@ -8,94 +8,94 @@ public class CompoundAssignment { - @StringVal("hello") String field; - - public void refinements() { - field = "hello"; - // :: error: (compound.assignment.type.incompatible) - field += method(); - // :: error: (assignment.type.incompatible) - // :: error: (compound.assignment.type.incompatible) - @StringVal("hellohellohello") String test = field += method(); - } - - @StringVal("hello") String method() { - // :: error: (assignment.type.incompatible) - field = "goodbye"; - return "hello"; - } - - void value() { - @StringVal("hello") String s = "hello"; - // :: error: (compound.assignment.type.incompatible) - s += "hello"; - - @IntVal(1) int i = 1; - // :: error: (compound.assignment.type.incompatible) - i += 1; - - @IntVal(2) int j = 2; - // :: error: (compound.assignment.type.incompatible) - j += 2; - - // :: error: (assignment.type.incompatible) - @IntVal(4) int four = j; - } - - void value2() { - @StringVal("hello") String s = "hello"; - // :: error: (assignment.type.incompatible) - s = s + "hello"; - - @IntVal(1) int i = 1; - // :: error: (assignment.type.incompatible) - i = i + 1; - } - - @IntRange(from = 5, to = 10) int afield; - - void afield() { - if (afield == 5) { - afield += 5; + @StringVal("hello") String field; + + public void refinements() { + field = "hello"; + // :: error: (compound.assignment.type.incompatible) + field += method(); + // :: error: (assignment.type.incompatible) + // :: error: (compound.assignment.type.incompatible) + @StringVal("hellohellohello") String test = field += method(); } - // :: error: (compound.assignment.type.incompatible) - afield += 2; - } - void aparam(@IntRange(from = 5, to = 10) int aparam) { - if (aparam == 5) { - aparam += 5; + @StringVal("hello") String method() { + // :: error: (assignment.type.incompatible) + field = "goodbye"; + return "hello"; } - // :: error: (compound.assignment.type.incompatible) - aparam += 2; - } - - void alocal() { - @IntRange(from = 5, to = 10) int alocal; - if (this.hashCode() > 100) { - alocal = 5; - } else { - alocal = 10; + + void value() { + @StringVal("hello") String s = "hello"; + // :: error: (compound.assignment.type.incompatible) + s += "hello"; + + @IntVal(1) int i = 1; + // :: error: (compound.assignment.type.incompatible) + i += 1; + + @IntVal(2) int j = 2; + // :: error: (compound.assignment.type.incompatible) + j += 2; + + // :: error: (assignment.type.incompatible) + @IntVal(4) int four = j; + } + + void value2() { + @StringVal("hello") String s = "hello"; + // :: error: (assignment.type.incompatible) + s = s + "hello"; + + @IntVal(1) int i = 1; + // :: error: (assignment.type.incompatible) + i = i + 1; + } + + @IntRange(from = 5, to = 10) int afield; + + void afield() { + if (afield == 5) { + afield += 5; + } + // :: error: (compound.assignment.type.incompatible) + afield += 2; + } + + void aparam(@IntRange(from = 5, to = 10) int aparam) { + if (aparam == 5) { + aparam += 5; + } + // :: error: (compound.assignment.type.incompatible) + aparam += 2; + } + + void alocal() { + @IntRange(from = 5, to = 10) int alocal; + if (this.hashCode() > 100) { + alocal = 5; + } else { + alocal = 10; + } + + if (alocal == 5) { + alocal += 5; + } + // :: error: (compound.assignment.type.incompatible) + alocal += 2; + } + + void noErrorCompoundAssignments() { + @IntVal(0) int zero = 0; + zero *= 12; + + @StringVal("null") String s = "null"; + s += ""; } - if (alocal == 5) { - alocal += 5; + void errorCompundAssignments() { + @StringVal("hello") String s = "hello"; + // :: error: (compound.assignment.type.incompatible) + s += ""; } - // :: error: (compound.assignment.type.incompatible) - alocal += 2; - } - - void noErrorCompoundAssignments() { - @IntVal(0) int zero = 0; - zero *= 12; - - @StringVal("null") String s = "null"; - s += ""; - } - - void errorCompundAssignments() { - @StringVal("hello") String s = "hello"; - // :: error: (compound.assignment.type.incompatible) - s += ""; - } } diff --git a/framework/tests/value/DivideByZero.java b/framework/tests/value/DivideByZero.java index e26901f0b7f..20cf9115c2e 100644 --- a/framework/tests/value/DivideByZero.java +++ b/framework/tests/value/DivideByZero.java @@ -5,71 +5,71 @@ import org.checkerframework.common.value.qual.IntVal; public class DivideByZero { - void divideNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { - @DoubleVal(Float.POSITIVE_INFINITY) float a = f / 0; - @DoubleVal(Double.POSITIVE_INFINITY) double b = d / 0l; - @DoubleVal(Double.POSITIVE_INFINITY) double c = i / 0.0; - @DoubleVal(Float.POSITIVE_INFINITY) float e = i / 0.0f; + void divideNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { + @DoubleVal(Float.POSITIVE_INFINITY) float a = f / 0; + @DoubleVal(Double.POSITIVE_INFINITY) double b = d / 0l; + @DoubleVal(Double.POSITIVE_INFINITY) double c = i / 0.0; + @DoubleVal(Float.POSITIVE_INFINITY) float e = i / 0.0f; - // :: error: (assignment.type.incompatible) - @BottomVal float a2 = f / 0; - // :: error: (assignment.type.incompatible) - @BottomVal double b2 = d / 0L; - // :: error: (assignment.type.incompatible) - @BottomVal double c2 = i / 0.0; - // :: error: (assignment.type.incompatible) - @BottomVal float e2 = i / 0.0f; - } + // :: error: (assignment.type.incompatible) + @BottomVal float a2 = f / 0; + // :: error: (assignment.type.incompatible) + @BottomVal double b2 = d / 0L; + // :: error: (assignment.type.incompatible) + @BottomVal double c2 = i / 0.0; + // :: error: (assignment.type.incompatible) + @BottomVal float e2 = i / 0.0f; + } - void remainderNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { - @DoubleVal(Float.NaN) float a = f % 0; - @DoubleVal(Double.NaN) double b = d % 0L; - @DoubleVal(Double.NaN) double c = i % 0.0; - @DoubleVal(Float.NaN) float e = i % 0.0f; + void remainderNoException(@DoubleVal(1.0) float f, @DoubleVal(1.0) double d, @IntVal(1) int i) { + @DoubleVal(Float.NaN) float a = f % 0; + @DoubleVal(Double.NaN) double b = d % 0L; + @DoubleVal(Double.NaN) double c = i % 0.0; + @DoubleVal(Float.NaN) float e = i % 0.0f; - // :: error: (assignment.type.incompatible) - @BottomVal float a2 = f % 0; - // :: error: (assignment.type.incompatible) - @BottomVal double b2 = d % 0l; - // :: error: (assignment.type.incompatible) - @BottomVal double c2 = i % 0.0; - // :: error: (assignment.type.incompatible) - @BottomVal float e2 = i % 0.0f; - } + // :: error: (assignment.type.incompatible) + @BottomVal float a2 = f % 0; + // :: error: (assignment.type.incompatible) + @BottomVal double b2 = d % 0l; + // :: error: (assignment.type.incompatible) + @BottomVal double c2 = i % 0.0; + // :: error: (assignment.type.incompatible) + @BottomVal float e2 = i % 0.0f; + } - void integerDivision( - @IntVal(1) long l, - @IntVal(1) int i, - @IntVal(0) byte bZero, - @IntVal(0) short sZero, - @IntVal(0L) long lZero, - @IntVal(0) int iZero) { - @BottomVal long a = l / bZero; - @BottomVal long b = l / sZero; - @BottomVal long c = l / iZero; - @BottomVal long d = l / lZero; + void integerDivision( + @IntVal(1) long l, + @IntVal(1) int i, + @IntVal(0) byte bZero, + @IntVal(0) short sZero, + @IntVal(0L) long lZero, + @IntVal(0) int iZero) { + @BottomVal long a = l / bZero; + @BottomVal long b = l / sZero; + @BottomVal long c = l / iZero; + @BottomVal long d = l / lZero; - @BottomVal long e = i / bZero; - @BottomVal long f = i / sZero; - @BottomVal long g = i / iZero; - @BottomVal long h = i / lZero; - } + @BottomVal long e = i / bZero; + @BottomVal long f = i / sZero; + @BottomVal long g = i / iZero; + @BottomVal long h = i / lZero; + } - void integerRemainder( - @IntVal(1) long l, - @IntVal(1) int i, - @IntVal(0) byte bZero, - @IntVal(0) short sZero, - @IntVal(0L) long lZero, - @IntVal(0) int iZero) { - @BottomVal long a = l % bZero; - @BottomVal long b = l % sZero; - @BottomVal long c = l % iZero; - @BottomVal long d = l % lZero; + void integerRemainder( + @IntVal(1) long l, + @IntVal(1) int i, + @IntVal(0) byte bZero, + @IntVal(0) short sZero, + @IntVal(0L) long lZero, + @IntVal(0) int iZero) { + @BottomVal long a = l % bZero; + @BottomVal long b = l % sZero; + @BottomVal long c = l % iZero; + @BottomVal long d = l % lZero; - @BottomVal long e = i % bZero; - @BottomVal long f = i % sZero; - @BottomVal long g = i % iZero; - @BottomVal long h = i % lZero; - } + @BottomVal long e = i % bZero; + @BottomVal long f = i % sZero; + @BottomVal long g = i % iZero; + @BottomVal long h = i % lZero; + } } diff --git a/framework/tests/value/DoubleRounding.java b/framework/tests/value/DoubleRounding.java index 81bb57a2a39..1baff0c41ac 100644 --- a/framework/tests/value/DoubleRounding.java +++ b/framework/tests/value/DoubleRounding.java @@ -3,9 +3,9 @@ public class DoubleRounding { - final double FLOATING_POINT_DELTA = 1e-15; + final double FLOATING_POINT_DELTA = 1e-15; - void round() { - float f = (float) FLOATING_POINT_DELTA; - } + void round() { + float f = (float) FLOATING_POINT_DELTA; + } } diff --git a/framework/tests/value/EmptyAnnotationArgument.java b/framework/tests/value/EmptyAnnotationArgument.java index 4cc53bc49a6..9df39c20b1c 100644 --- a/framework/tests/value/EmptyAnnotationArgument.java +++ b/framework/tests/value/EmptyAnnotationArgument.java @@ -6,18 +6,18 @@ public class EmptyAnnotationArgument { - // :: warning: (no.values.given) - void mArray(int @ArrayLen({}) [] a) {} + // :: warning: (no.values.given) + void mArray(int @ArrayLen({}) [] a) {} - // :: warning: (no.values.given) - void mBool(@BoolVal({}) boolean arg) {} + // :: warning: (no.values.given) + void mBool(@BoolVal({}) boolean arg) {} - // :: warning: (no.values.given) - void mDouble(@DoubleVal({}) double arg) {} + // :: warning: (no.values.given) + void mDouble(@DoubleVal({}) double arg) {} - // :: warning: (no.values.given) - void mInt(@IntVal({}) int arg) {} + // :: warning: (no.values.given) + void mInt(@IntVal({}) int arg) {} - // :: warning: (no.values.given) - void mString(@StringVal({}) String arg) {} + // :: warning: (no.values.given) + void mString(@StringVal({}) String arg) {} } diff --git a/framework/tests/value/EnclosingClass.java b/framework/tests/value/EnclosingClass.java index 4efea86ea28..a6a0bf576e1 100644 --- a/framework/tests/value/EnclosingClass.java +++ b/framework/tests/value/EnclosingClass.java @@ -4,38 +4,38 @@ import java.util.concurrent.Future; public class EnclosingClass { - private MyMap myMap = null; + private MyMap myMap = null; - // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with - // V. - private EnclosingClass catchingMoreGeneric( - final MyFunction fallback) { - AFunction applyFallback = - new AFunction() { - @Override - public MyFuture apply(B exception) throws Exception { - return myMap.apply(fallback, exception); - } + // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with + // V. + private EnclosingClass catchingMoreGeneric( + final MyFunction fallback) { + AFunction applyFallback = + new AFunction() { + @Override + public MyFuture apply(B exception) throws Exception { + return myMap.apply(fallback, exception); + } - @Override - public String toString() { - return fallback.toString(); - } - }; - return null; - } + @Override + public String toString() { + return fallback.toString(); + } + }; + return null; + } - private abstract class MyMap extends IdentityHashMap - implements Closeable { - abstract MyFuture apply(MyFunction transformation, D input) - throws Exception; - } + private abstract class MyMap extends IdentityHashMap + implements Closeable { + abstract MyFuture apply(MyFunction transformation, D input) + throws Exception; + } - public interface MyFunction {} + public interface MyFunction {} - interface AFunction { - MyFuture apply(H input) throws Exception; - } + interface AFunction { + MyFuture apply(H input) throws Exception; + } - interface MyFuture extends Future {} + interface MyFuture extends Future {} } diff --git a/framework/tests/value/EnumConstants.java b/framework/tests/value/EnumConstants.java index 10307f3c905..748ea0a8487 100644 --- a/framework/tests/value/EnumConstants.java +++ b/framework/tests/value/EnumConstants.java @@ -4,169 +4,172 @@ import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.CREATE; +import org.checkerframework.common.value.qual.*; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import org.checkerframework.common.value.qual.*; public class EnumConstants { - enum MyEnum { - VALUE, - OTHER_VALUE, - THIRD_VALUE - } - - static void subtyping1(@EnumVal("VALUE") MyEnum value) { - @EnumVal("VALUE") MyEnum value2 = value; - // :: error: (assignment.type.incompatible) - @EnumVal("OTHER_VALUE") MyEnum value3 = value; - @UnknownVal MyEnum value4 = value; - @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; - } - - static void subtyping2(@EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value) { - // :: error: (assignment.type.incompatible) - @EnumVal("VALUE") MyEnum value2 = value; - // :: error: (assignment.type.incompatible) - @EnumVal("OTHER_VALUE") MyEnum value3 = value; - @UnknownVal MyEnum value4 = value; - @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; - @EnumVal({"VALUE", "OTHER_VALUE", "THIRD_VALUE"}) MyEnum value6 = value; - } - - static void enumConstants() { - @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; - @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum v2 = MyEnum.VALUE; - // :: error: (assignment.type.incompatible) - @EnumVal("OTHER_VALUE") MyEnum v3 = MyEnum.VALUE; - } - - static void enumToString() { - @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; - // NOT toString(), because programmers can override that. .name() is final. - @StringVal("VALUE") String s1 = v1.name(); - } - - // These are just paranoia based on the implementation strategy for enum constant defaulting. - static void nonConstantEnum(MyEnum m) { - // :: error: (assignment.type.incompatible) - @EnumVal("m") MyEnum m2 = m; - // :: error: (assignment.type.incompatible) - @EnumVal("m3") MyEnum m3 = m; - } - - static void enums(@EnumVal("VALUE") MyEnum... enums) {} - - static void testEnums() { - enums(); - enums(MyEnum.VALUE); - // :: error: (argument.type.incompatible) - enums(MyEnum.OTHER_VALUE); - } - - static void testEnumArraysInConditional(boolean append, String filename) throws IOException { - Files.newBufferedWriter( - Paths.get(filename), - UTF_8, - append ? new StandardOpenOption[] {CREATE, APPEND} : new StandardOpenOption[] {CREATE}); - } - - public static String unescapeJava(String orig, char c) { - StringBuilder sb = new StringBuilder(); - // The previous escape character was seen just before this position. - int postEsc = 0; - int thisEsc = 0; // orig.indexOf('\\'); - while (thisEsc != -1) { - switch (c) { - case 'n': - sb.append(orig.substring(postEsc, thisEsc)); - sb.append('\n'); // not lineSep - postEsc = thisEsc + 2; - break; - case 'r': - sb.append(orig.substring(postEsc, thisEsc)); - sb.append('\r'); - postEsc = thisEsc + 2; - break; - case 't': - sb.append(orig.substring(postEsc, thisEsc)); - sb.append('\t'); - postEsc = thisEsc + 2; - break; - case '\\': - // This is not in the default case because the search would find - // the quoted backslash. Here we include the first backslash in - // the output, but not the first. - sb.append(orig.substring(postEsc, thisEsc + 1)); - postEsc = thisEsc + 2; - break; - - case 'u': - // Unescape Unicode characters. - sb.append(orig.substring(postEsc, thisEsc)); - char unicodeChar = 0; - int ii = thisEsc + 2; - // The specification permits one or more 'u' characters. - while (ii < orig.length() && orig.charAt(ii) == 'u') { - ii++; - } - // The specification requires exactly 4 hexadecimal characters. - // This is more liberal. (Should it be?) - int limit = Math.min(ii + 4, orig.length()); - while (ii < limit) { - int thisDigit = Character.digit(orig.charAt(ii), 16); - if (thisDigit == -1) { - break; - } - unicodeChar = (char) ((unicodeChar * 16) + thisDigit); - ii++; - } - sb.append(unicodeChar); - postEsc = ii; - break; - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - // Unescape octal characters. - sb.append(orig.substring(postEsc, thisEsc)); - char octalChar = 0; - int iii = thisEsc + 1; - while (iii < Math.min(thisEsc + 4, orig.length())) { - int thisDigit = Character.digit(orig.charAt(iii), 8); - if (thisDigit == -1) { - break; - } - int newValue = (octalChar * 8) + thisDigit; - if (newValue > 0377) { - break; - } - octalChar = (char) newValue; - iii++; - } - sb.append(octalChar); - postEsc = iii; - break; - - default: - // In the default case, retain the character following the backslash, - // but discard the backslash itself. "\*" is just a one-character string. - sb.append(orig.substring(postEsc, thisEsc)); - postEsc = thisEsc + 1; - break; - } - thisEsc = orig.indexOf('\\', postEsc); + enum MyEnum { + VALUE, + OTHER_VALUE, + THIRD_VALUE + } + + static void subtyping1(@EnumVal("VALUE") MyEnum value) { + @EnumVal("VALUE") MyEnum value2 = value; + // :: error: (assignment.type.incompatible) + @EnumVal("OTHER_VALUE") MyEnum value3 = value; + @UnknownVal MyEnum value4 = value; + @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; + } + + static void subtyping2(@EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value) { + // :: error: (assignment.type.incompatible) + @EnumVal("VALUE") MyEnum value2 = value; + // :: error: (assignment.type.incompatible) + @EnumVal("OTHER_VALUE") MyEnum value3 = value; + @UnknownVal MyEnum value4 = value; + @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum value5 = value; + @EnumVal({"VALUE", "OTHER_VALUE", "THIRD_VALUE"}) MyEnum value6 = value; + } + + static void enumConstants() { + @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; + @EnumVal({"VALUE", "OTHER_VALUE"}) MyEnum v2 = MyEnum.VALUE; + // :: error: (assignment.type.incompatible) + @EnumVal("OTHER_VALUE") MyEnum v3 = MyEnum.VALUE; + } + + static void enumToString() { + @EnumVal("VALUE") MyEnum v1 = MyEnum.VALUE; + // NOT toString(), because programmers can override that. .name() is final. + @StringVal("VALUE") String s1 = v1.name(); + } + + // These are just paranoia based on the implementation strategy for enum constant defaulting. + static void nonConstantEnum(MyEnum m) { + // :: error: (assignment.type.incompatible) + @EnumVal("m") MyEnum m2 = m; + // :: error: (assignment.type.incompatible) + @EnumVal("m3") MyEnum m3 = m; } - if (postEsc == 0) { - return orig; + + static void enums(@EnumVal("VALUE") MyEnum... enums) {} + + static void testEnums() { + enums(); + enums(MyEnum.VALUE); + // :: error: (argument.type.incompatible) + enums(MyEnum.OTHER_VALUE); + } + + static void testEnumArraysInConditional(boolean append, String filename) throws IOException { + Files.newBufferedWriter( + Paths.get(filename), + UTF_8, + append + ? new StandardOpenOption[] {CREATE, APPEND} + : new StandardOpenOption[] {CREATE}); + } + + public static String unescapeJava(String orig, char c) { + StringBuilder sb = new StringBuilder(); + // The previous escape character was seen just before this position. + int postEsc = 0; + int thisEsc = 0; // orig.indexOf('\\'); + while (thisEsc != -1) { + switch (c) { + case 'n': + sb.append(orig.substring(postEsc, thisEsc)); + sb.append('\n'); // not lineSep + postEsc = thisEsc + 2; + break; + case 'r': + sb.append(orig.substring(postEsc, thisEsc)); + sb.append('\r'); + postEsc = thisEsc + 2; + break; + case 't': + sb.append(orig.substring(postEsc, thisEsc)); + sb.append('\t'); + postEsc = thisEsc + 2; + break; + case '\\': + // This is not in the default case because the search would find + // the quoted backslash. Here we include the first backslash in + // the output, but not the first. + sb.append(orig.substring(postEsc, thisEsc + 1)); + postEsc = thisEsc + 2; + break; + + case 'u': + // Unescape Unicode characters. + sb.append(orig.substring(postEsc, thisEsc)); + char unicodeChar = 0; + int ii = thisEsc + 2; + // The specification permits one or more 'u' characters. + while (ii < orig.length() && orig.charAt(ii) == 'u') { + ii++; + } + // The specification requires exactly 4 hexadecimal characters. + // This is more liberal. (Should it be?) + int limit = Math.min(ii + 4, orig.length()); + while (ii < limit) { + int thisDigit = Character.digit(orig.charAt(ii), 16); + if (thisDigit == -1) { + break; + } + unicodeChar = (char) ((unicodeChar * 16) + thisDigit); + ii++; + } + sb.append(unicodeChar); + postEsc = ii; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + // Unescape octal characters. + sb.append(orig.substring(postEsc, thisEsc)); + char octalChar = 0; + int iii = thisEsc + 1; + while (iii < Math.min(thisEsc + 4, orig.length())) { + int thisDigit = Character.digit(orig.charAt(iii), 8); + if (thisDigit == -1) { + break; + } + int newValue = (octalChar * 8) + thisDigit; + if (newValue > 0377) { + break; + } + octalChar = (char) newValue; + iii++; + } + sb.append(octalChar); + postEsc = iii; + break; + + default: + // In the default case, retain the character following the backslash, + // but discard the backslash itself. "\*" is just a one-character string. + sb.append(orig.substring(postEsc, thisEsc)); + postEsc = thisEsc + 1; + break; + } + thisEsc = orig.indexOf('\\', postEsc); + } + if (postEsc == 0) { + return orig; + } + sb.append(orig.substring(postEsc)); + return sb.toString(); } - sb.append(orig.substring(postEsc)); - return sb.toString(); - } } diff --git a/framework/tests/value/EnumValue.java b/framework/tests/value/EnumValue.java index eefab78314a..823986e232d 100644 --- a/framework/tests/value/EnumValue.java +++ b/framework/tests/value/EnumValue.java @@ -2,70 +2,70 @@ public class EnumValue { - enum Direction { - NORTH, - WEST, - SOUTH, - EAST - }; + enum Direction { + NORTH, + WEST, + SOUTH, + EAST + }; - public enum Color { - BLUE, - RED, - GREEN - }; + public enum Color { + BLUE, + RED, + GREEN + }; - private enum Fruit { - APPLE, - ORANGE, - PEAR - }; + private enum Fruit { + APPLE, + ORANGE, + PEAR + }; - void simpleTest() { - Direction @ArrayLen(4) [] myCompass = Direction.values(); - Color @ArrayLen(3) [] myColors = Color.values(); - Fruit @ArrayLen(3) [] myFruitBasket = Fruit.values(); + void simpleTest() { + Direction @ArrayLen(4) [] myCompass = Direction.values(); + Color @ArrayLen(3) [] myColors = Color.values(); + Fruit @ArrayLen(3) [] myFruitBasket = Fruit.values(); - // :: error: (assignment.type.incompatible) - Direction @ArrayLen(7) [] badCompass = Direction.values(); + // :: error: (assignment.type.incompatible) + Direction @ArrayLen(7) [] badCompass = Direction.values(); - // :: error: (assignment.type.incompatible) - Color @ArrayLen(4) [] badColors = Color.values(); + // :: error: (assignment.type.incompatible) + Color @ArrayLen(4) [] badColors = Color.values(); - // :: error: (assignment.type.incompatible) - Fruit @ArrayLen(2) [] badFruit = Fruit.values(); - } + // :: error: (assignment.type.incompatible) + Fruit @ArrayLen(2) [] badFruit = Fruit.values(); + } - public enum AdvDirection { - ANORTH { - public AdvDirection getOpposite() { - return ASOUTH; - } - }, - AEAST { - public AdvDirection getOpposite() { - return AWEST; - } - }, - ASOUTH { - public AdvDirection getOpposite() { - return ANORTH; - } - }, - AWEST { - public AdvDirection getOpposite() { - return AEAST; - } - }; + public enum AdvDirection { + ANORTH { + public AdvDirection getOpposite() { + return ASOUTH; + } + }, + AEAST { + public AdvDirection getOpposite() { + return AWEST; + } + }, + ASOUTH { + public AdvDirection getOpposite() { + return ANORTH; + } + }, + AWEST { + public AdvDirection getOpposite() { + return AEAST; + } + }; - public abstract AdvDirection getOpposite(); - } + public abstract AdvDirection getOpposite(); + } - void advTest() { - AdvDirection @ArrayLen(4) [] myCompass = AdvDirection.values(); - // :: error: (assignment.type.incompatible) - AdvDirection @ArrayLen(3) [] badCompass = AdvDirection.values(); - // :: error: (assignment.type.incompatible) - AdvDirection @ArrayLen(5) [] badCompass2 = AdvDirection.values(); - } + void advTest() { + AdvDirection @ArrayLen(4) [] myCompass = AdvDirection.values(); + // :: error: (assignment.type.incompatible) + AdvDirection @ArrayLen(3) [] badCompass = AdvDirection.values(); + // :: error: (assignment.type.incompatible) + AdvDirection @ArrayLen(5) [] badCompass2 = AdvDirection.values(); + } } diff --git a/framework/tests/value/ExceptionTest.java b/framework/tests/value/ExceptionTest.java index 6771207a6c2..5a7cdf29b27 100644 --- a/framework/tests/value/ExceptionTest.java +++ b/framework/tests/value/ExceptionTest.java @@ -2,10 +2,10 @@ public class ExceptionTest { - public void foo() { - int indexTooBig = 5; - String s = "hello"; - // :: warning: (method.evaluation.exception) - char c = s.charAt(indexTooBig); - } + public void foo() { + int indexTooBig = 5; + String s = "hello"; + // :: warning: (method.evaluation.exception) + char c = s.charAt(indexTooBig); + } } diff --git a/framework/tests/value/Fields.java b/framework/tests/value/Fields.java index 4de154bdd0d..c88492978e4 100644 --- a/framework/tests/value/Fields.java +++ b/framework/tests/value/Fields.java @@ -1,54 +1,55 @@ -import javax.swing.plaf.BorderUIResource; import org.checkerframework.common.value.qual.*; +import javax.swing.plaf.BorderUIResource; + public class Fields { - static final int field = 1; + static final int field = 1; - public void innerClassFields() { - @IntVal({9}) int x = java.util.zip.Deflater.BEST_COMPRESSION; - @IntVal({4}) int a = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; - // :: error: (assignment.type.incompatible) - @IntVal({0}) int b = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; - } + public void innerClassFields() { + @IntVal({9}) int x = java.util.zip.Deflater.BEST_COMPRESSION; + @IntVal({4}) int a = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; + // :: error: (assignment.type.incompatible) + @IntVal({0}) int b = BorderUIResource.TitledBorderUIResource.ABOVE_BOTTOM; + } - public void inClassFields() { - @IntVal({1}) int a = field; - // :: error: (assignment.type.incompatible) - @IntVal({0}) int b = field; - } + public void inClassFields() { + @IntVal({1}) int a = field; + // :: error: (assignment.type.incompatible) + @IntVal({0}) int b = field; + } - public void otherClassFields() { - @IntVal({56319}) char x = Character.MAX_HIGH_SURROGATE; - @IntVal({16}) byte y = Character.FORMAT; + public void otherClassFields() { + @IntVal({56319}) char x = Character.MAX_HIGH_SURROGATE; + @IntVal({16}) byte y = Character.FORMAT; - @BoolVal({false}) boolean a = Boolean.FALSE; - // :: error: (assignment.type.incompatible) - a = Boolean.TRUE; + @BoolVal({false}) boolean a = Boolean.FALSE; + // :: error: (assignment.type.incompatible) + a = Boolean.TRUE; - @IntVal({4}) int b = java.util.Calendar.MAY; - // :: error: (assignment.type.incompatible) - b = java.util.Calendar.APRIL; + @IntVal({4}) int b = java.util.Calendar.MAY; + // :: error: (assignment.type.incompatible) + b = java.util.Calendar.APRIL; - @IntVal({9}) int c = java.util.zip.Deflater.BEST_COMPRESSION; - // :: error: (assignment.type.incompatible) - c = java.util.zip.Deflater.BEST_SPEED; + @IntVal({9}) int c = java.util.zip.Deflater.BEST_COMPRESSION; + // :: error: (assignment.type.incompatible) + c = java.util.zip.Deflater.BEST_SPEED; - @IntVal({1024}) int d = java.awt.GridBagConstraints.ABOVE_BASELINE; - // :: error: (assignment.type.incompatible) - d = java.awt.GridBagConstraints.LAST_LINE_END; - } + @IntVal({1024}) int d = java.awt.GridBagConstraints.ABOVE_BASELINE; + // :: error: (assignment.type.incompatible) + d = java.awt.GridBagConstraints.LAST_LINE_END; + } - void innerFieldTest() { - @StringVal("section_number") String a = InnerStaticClass.INNER_STATIC_FIELD; + void innerFieldTest() { + @StringVal("section_number") String a = InnerStaticClass.INNER_STATIC_FIELD; - // :: error: (assignment.type.incompatible) - @StringVal("") String b = InnerStaticClass.INNER_STATIC_FIELD; - } + // :: error: (assignment.type.incompatible) + @StringVal("") String b = InnerStaticClass.INNER_STATIC_FIELD; + } - static final int fieldDeclAtBottom = 1; + static final int fieldDeclAtBottom = 1; - public static class InnerStaticClass { - public static final String INNER_STATIC_FIELD = "section_number"; - } + public static class InnerStaticClass { + public static final String INNER_STATIC_FIELD = "section_number"; + } } diff --git a/framework/tests/value/GTETransferBug.java b/framework/tests/value/GTETransferBug.java index e9de46bd58e..8a62d0bb961 100644 --- a/framework/tests/value/GTETransferBug.java +++ b/framework/tests/value/GTETransferBug.java @@ -1,12 +1,12 @@ import org.checkerframework.common.value.qual.*; public class GTETransferBug { - void gte_bad_check(int[] a) { - if (a.length >= 1) { - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 2) [] b = a; + void gte_bad_check(int[] a) { + if (a.length >= 1) { + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 2) [] b = a; - int @ArrayLenRange(from = 1) [] c = a; + int @ArrayLenRange(from = 1) [] c = a; + } } - } } diff --git a/framework/tests/value/Issue1214.java b/framework/tests/value/Issue1214.java index 13ef11b3fd6..bbe2d39bb2e 100644 --- a/framework/tests/value/Issue1214.java +++ b/framework/tests/value/Issue1214.java @@ -5,66 +5,66 @@ public class Issue1214 { - static void noException() { - int n = 0; - try { - } catch (Exception e) { - n = 1; + static void noException() { + int n = 0; + try { + } catch (Exception e) { + n = 1; + } + @IntVal(0) int ok = n; } - @IntVal(0) int ok = n; - } - static void arrayAccess(String[] array) { - int n = 0; - try { - String s = array[0]; - } catch (NullPointerException e) { - n = 1; - } catch (ArrayIndexOutOfBoundsException e) { - n = 2; - } catch (Exception e) { - n = 3; + static void arrayAccess(String[] array) { + int n = 0; + try { + String s = array[0]; + } catch (NullPointerException e) { + n = 1; + } catch (ArrayIndexOutOfBoundsException e) { + n = 2; + } catch (Exception e) { + n = 3; + } + @IntVal({0, 1, 2}) int ok = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 1}) int ng1 = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 2}) int ng2 = n; + // :: error: (assignment.type.incompatible) + @IntVal(0) int ng3 = n; } - @IntVal({0, 1, 2}) int ok = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 1}) int ng1 = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 2}) int ng2 = n; - // :: error: (assignment.type.incompatible) - @IntVal(0) int ng3 = n; - } - static void forArray(String[] array) { - int n = 0; - try { - for (String s : array) {} - } catch (NullPointerException e) { - n = 1; - } catch (ArrayIndexOutOfBoundsException e) { - n = 2; - } catch (Exception e) { - n = 3; + static void forArray(String[] array) { + int n = 0; + try { + for (String s : array) {} + } catch (NullPointerException e) { + n = 1; + } catch (ArrayIndexOutOfBoundsException e) { + n = 2; + } catch (Exception e) { + n = 3; + } + @IntVal({0, 1}) int ok = n; + // :: error: (assignment.type.incompatible) + @IntVal(0) int ng = n; } - @IntVal({0, 1}) int ok = n; - // :: error: (assignment.type.incompatible) - @IntVal(0) int ng = n; - } - static void forIterable(Iterable itr) { - int n = 0; - try { - for (String s : itr) {} - } catch (NullPointerException e) { - n = 1; - } catch (Exception e) { - n = 2; + static void forIterable(Iterable itr) { + int n = 0; + try { + for (String s : itr) {} + } catch (NullPointerException e) { + n = 1; + } catch (Exception e) { + n = 2; + } + @IntVal({0, 1, 2}) int ok = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 1}) int ng1 = n; + // :: error: (assignment.type.incompatible) + @IntVal({0, 2}) int ng2 = n; + // :: error: (assignment.type.incompatible) + @IntVal(0) int ng3 = n; } - @IntVal({0, 1, 2}) int ok = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 1}) int ng1 = n; - // :: error: (assignment.type.incompatible) - @IntVal({0, 2}) int ng2 = n; - // :: error: (assignment.type.incompatible) - @IntVal(0) int ng3 = n; - } } diff --git a/framework/tests/value/Issue1218.java b/framework/tests/value/Issue1218.java index 54e421070b4..111def83191 100644 --- a/framework/tests/value/Issue1218.java +++ b/framework/tests/value/Issue1218.java @@ -1,144 +1,145 @@ // Test case for Issue 1218: // https://github.com/typetools/checker-framework/issues/1218 -import java.io.Serializable; import org.checkerframework.common.value.qual.*; +import java.io.Serializable; + public class Issue1218 { - enum MyEnum { - A, - B, - C; - } + enum MyEnum { + A, + B, + C; + } + + class ForString { + ForString(String @MinLen(2) ... strs) {} + } + + class ForInt { + ForInt(@IntVal({1, 2, 3}) int @MinLen(2) ... strs) {} + } + + class ForEnum> { + @SafeVarargs + ForEnum(E @MinLen(2) ... enums) {} + } + + class ForAny { + @SafeVarargs + ForAny(T @MinLen(3) ... anys) {} + } - class ForString { - ForString(String @MinLen(2) ... strs) {} - } + void strs(String @MinLen(2) ... strs) {} - class ForInt { - ForInt(@IntVal({1, 2, 3}) int @MinLen(2) ... strs) {} - } + void ints(@IntVal({1, 2, 3}) int @MinLen(2) ... ints) {} - class ForEnum> { @SafeVarargs - ForEnum(E @MinLen(2) ... enums) {} - } + final > void enums(E @MinLen(2) ... enums) {} - class ForAny { @SafeVarargs - ForAny(T @MinLen(3) ... anys) {} - } - - void strs(String @MinLen(2) ... strs) {} - - void ints(@IntVal({1, 2, 3}) int @MinLen(2) ... ints) {} - - @SafeVarargs - final > void enums(E @MinLen(2) ... enums) {} - - @SafeVarargs - final void anys(T @MinLen(3) ... anys) {} - - void testMethodCall() { - // :: error: (varargs.type.incompatible) - strs(); - // :: error: (varargs.type.incompatible) - strs(""); - strs("", ""); - // type of arg should be @UnknownVal String @BottomVal [] - strs((String[]) null); - - String[] args0 = {""}; - String[] args1 = {""}; - String[] args2 = {"", ""}; - - // :: error: (argument.type.incompatible) - strs(args0); - // :: error: (argument.type.incompatible) - strs(args1); - strs(args2); - - ints(1, 2); - // :: error: (argument.type.incompatible) - ints(0, 0, 0); - // :: error: (varargs.type.incompatible) - ints(3); - // type of arg should be @IntVal(1) int @BottomVal [] - ints((@IntVal(1) int[]) null); - } - - // Inferred enumval types are incompatible with >. Similar code - // works if the type is a specific enum; see the test file Enums.java for an example. - @SuppressWarnings("type.argument.type.incompatible") - void testMethodCallTypeInferred() { - // :: error: (varargs.type.incompatible) - enums(); - // :: error: (varargs.type.incompatible) - enums(MyEnum.A); - enums(MyEnum.A, MyEnum.B); - enums(MyEnum.A, MyEnum.B, MyEnum.C); - } - - & Serializable> void testMethodCallTypeInferredIntersection() { - T t = null; - - // :: error: (varargs.type.incompatible) - anys(1, 1.0); - // :: error: (varargs.type.incompatible) - anys(1, ""); - anys(1, 1.0, ""); - // :: error: (varargs.type.incompatible) - anys(1, t); - anys(1, t, ""); - } - - void testConstructorCall() { - // :: error: (varargs.type.incompatible) - new ForString(); - // :: error: (varargs.type.incompatible) - new ForString(""); - new ForString("", ""); - // type of arg should be @UnknownVal String @BottomVal [] - new ForString((String[]) null); - - String[] args0 = {""}; - String[] args1 = {""}; - String[] args2 = {"", ""}; - - // :: error: (argument.type.incompatible) - new ForString(args0); - // :: error: (argument.type.incompatible) - new ForString(args1); - new ForString(args2); - - new ForInt(1, 2); - // :: error: (argument.type.incompatible) - new ForInt(0, 0, 0); - // :: error: (varargs.type.incompatible) - new ForInt(3); - // type of arg should be @IntVal(1) int @BottomVal [] - ints((@IntVal(1) int[]) null); - } - - void testConstructorCallTypeInferred() { - // :: error: (varargs.type.incompatible) - new ForEnum<>(MyEnum.A); - new ForEnum<>(MyEnum.A, MyEnum.B); - new ForEnum<>(MyEnum.A, MyEnum.B, MyEnum.C); - } - - @SuppressWarnings("unchecked") - & Serializable> void testConstructorCallTypeInferredIntersection() { - T t = null; - - // :: error: (varargs.type.incompatible) - new ForAny<>(1, 1.0); - // :: error: (varargs.type.incompatible) - new ForAny<>(1, ""); - new ForAny<>(1, 1.0, ""); - // :: error: (varargs.type.incompatible) - new ForAny<>(1, t); - new ForAny<>(1, t, ""); - } + final void anys(T @MinLen(3) ... anys) {} + + void testMethodCall() { + // :: error: (varargs.type.incompatible) + strs(); + // :: error: (varargs.type.incompatible) + strs(""); + strs("", ""); + // type of arg should be @UnknownVal String @BottomVal [] + strs((String[]) null); + + String[] args0 = {""}; + String[] args1 = {""}; + String[] args2 = {"", ""}; + + // :: error: (argument.type.incompatible) + strs(args0); + // :: error: (argument.type.incompatible) + strs(args1); + strs(args2); + + ints(1, 2); + // :: error: (argument.type.incompatible) + ints(0, 0, 0); + // :: error: (varargs.type.incompatible) + ints(3); + // type of arg should be @IntVal(1) int @BottomVal [] + ints((@IntVal(1) int[]) null); + } + + // Inferred enumval types are incompatible with >. Similar code + // works if the type is a specific enum; see the test file Enums.java for an example. + @SuppressWarnings("type.argument.type.incompatible") + void testMethodCallTypeInferred() { + // :: error: (varargs.type.incompatible) + enums(); + // :: error: (varargs.type.incompatible) + enums(MyEnum.A); + enums(MyEnum.A, MyEnum.B); + enums(MyEnum.A, MyEnum.B, MyEnum.C); + } + + & Serializable> void testMethodCallTypeInferredIntersection() { + T t = null; + + // :: error: (varargs.type.incompatible) + anys(1, 1.0); + // :: error: (varargs.type.incompatible) + anys(1, ""); + anys(1, 1.0, ""); + // :: error: (varargs.type.incompatible) + anys(1, t); + anys(1, t, ""); + } + + void testConstructorCall() { + // :: error: (varargs.type.incompatible) + new ForString(); + // :: error: (varargs.type.incompatible) + new ForString(""); + new ForString("", ""); + // type of arg should be @UnknownVal String @BottomVal [] + new ForString((String[]) null); + + String[] args0 = {""}; + String[] args1 = {""}; + String[] args2 = {"", ""}; + + // :: error: (argument.type.incompatible) + new ForString(args0); + // :: error: (argument.type.incompatible) + new ForString(args1); + new ForString(args2); + + new ForInt(1, 2); + // :: error: (argument.type.incompatible) + new ForInt(0, 0, 0); + // :: error: (varargs.type.incompatible) + new ForInt(3); + // type of arg should be @IntVal(1) int @BottomVal [] + ints((@IntVal(1) int[]) null); + } + + void testConstructorCallTypeInferred() { + // :: error: (varargs.type.incompatible) + new ForEnum<>(MyEnum.A); + new ForEnum<>(MyEnum.A, MyEnum.B); + new ForEnum<>(MyEnum.A, MyEnum.B, MyEnum.C); + } + + @SuppressWarnings("unchecked") + & Serializable> void testConstructorCallTypeInferredIntersection() { + T t = null; + + // :: error: (varargs.type.incompatible) + new ForAny<>(1, 1.0); + // :: error: (varargs.type.incompatible) + new ForAny<>(1, ""); + new ForAny<>(1, 1.0, ""); + // :: error: (varargs.type.incompatible) + new ForAny<>(1, t); + new ForAny<>(1, t, ""); + } } diff --git a/framework/tests/value/Issue1229.java b/framework/tests/value/Issue1229.java index 93371938a96..6069c91464b 100644 --- a/framework/tests/value/Issue1229.java +++ b/framework/tests/value/Issue1229.java @@ -2,18 +2,18 @@ public class Issue1229 { - Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; + Object @ArrayLen(1) [] @ArrayLen(1) [] o3 = new Object[][] {null}; - @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; + @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; - void test() { + void test() { - @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; - @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; - } + @IntVal({0, 1, 2, 3}) int[] a1 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + int[] a2 = new @IntVal({0, 1, 2, 3}) int[] {0, 1, 2, 3}; + @IntVal({0, 1, 2, 3}) int[] a3 = new int[] {0, 1, 2, 3}; + } } diff --git a/framework/tests/value/Issue1423.java b/framework/tests/value/Issue1423.java index 571d39045ff..f56ffe71f37 100644 --- a/framework/tests/value/Issue1423.java +++ b/framework/tests/value/Issue1423.java @@ -4,11 +4,11 @@ import org.checkerframework.common.value.qual.IntRange; public class Issue1423 { - void loop(int i) { - int a = 0; - while (i >= 2) { - @IntRange(from = 2) int i2 = i; - ++a; + void loop(int i) { + int a = 0; + while (i >= 2) { + @IntRange(from = 2) int i2 = i; + ++a; + } } - } } diff --git a/framework/tests/value/Issue1579.java b/framework/tests/value/Issue1579.java index fda84f1c533..f0b01b64370 100644 --- a/framework/tests/value/Issue1579.java +++ b/framework/tests/value/Issue1579.java @@ -2,9 +2,9 @@ // https://github.com/typetools/checker-framework/issues/1579 public class Issue1579 { - public int[][] method(int[] array1, int[] array2) { - // Required for crash - for (int i = 0; i < array1.length; i++) {} - return new int[][] {array1, array2}; - } + public int[][] method(int[] array1, int[] array2) { + // Required for crash + for (int i = 0; i < array1.length; i++) {} + return new int[][] {array1, array2}; + } } diff --git a/framework/tests/value/Issue1580.java b/framework/tests/value/Issue1580.java index c1d5de029d0..e522d877969 100644 --- a/framework/tests/value/Issue1580.java +++ b/framework/tests/value/Issue1580.java @@ -2,11 +2,11 @@ // https://github.com/typetools/checker-framework/issues/1580 public class Issue1580> { - protected final Gen field; + protected final Gen field; - protected Issue1580(K parent) { - field = parent.field; - } + protected Issue1580(K parent) { + field = parent.field; + } - static class Gen {} + static class Gen {} } diff --git a/framework/tests/value/Issue1655.java b/framework/tests/value/Issue1655.java index 6bc027c3ce8..162dc79354e 100644 --- a/framework/tests/value/Issue1655.java +++ b/framework/tests/value/Issue1655.java @@ -5,9 +5,9 @@ public class Issue1655 { - public void test(int a) { - @IntRange(from = 0, to = 255) int b = a & 0xff; - @IntRange(from = 0, to = 15) int c1 = b >> 4; - @IntRange(from = 0, to = 15) int c2 = b >>> 4; - } + public void test(int a) { + @IntRange(from = 0, to = 255) int b = a & 0xff; + @IntRange(from = 0, to = 15) int c1 = b >> 4; + @IntRange(from = 0, to = 15) int c2 = b >>> 4; + } } diff --git a/framework/tests/value/Issue2353.java b/framework/tests/value/Issue2353.java index bcc53c046a6..bc66be2763c 100644 --- a/framework/tests/value/Issue2353.java +++ b/framework/tests/value/Issue2353.java @@ -1,7 +1,7 @@ @SuppressWarnings({"deprecation", "removal"}) // `new Integer() is deprecated in Java 9. public class Issue2353 { - public static void play() { - Integer a = new Integer("2"); - } + public static void play() { + Integer a = new Integer("2"); + } } diff --git a/framework/tests/value/Issue2367.java b/framework/tests/value/Issue2367.java index b165f6ddb15..8ab6c16d8d0 100644 --- a/framework/tests/value/Issue2367.java +++ b/framework/tests/value/Issue2367.java @@ -2,23 +2,23 @@ public class Issue2367 { - // Within the signed byte range + // Within the signed byte range - byte b1 = 75; - byte b2 = (byte) 75; - byte b3 = (byte) -120; + byte b1 = 75; + byte b2 = (byte) 75; + byte b3 = (byte) -120; - // Outside the signed byte range + // Outside the signed byte range - // Without the `(byte)` cast, all of these produce the following javac error: - // error: incompatible types: possible lossy conversion from int to byte - // The Value Checker's `cast.unsafe` error is analogous and is desirable. + // Without the `(byte)` cast, all of these produce the following javac error: + // error: incompatible types: possible lossy conversion from int to byte + // The Value Checker's `cast.unsafe` error is analogous and is desirable. - byte b4 = (byte) 139; // b4 == -117 - byte b5 = (byte) -240; - byte b6 = (byte) 251; + byte b4 = (byte) 139; // b4 == -117 + byte b5 = (byte) -240; + byte b6 = (byte) 251; - // Outside the signed byte range, but written as a hexadecimal literal. + // Outside the signed byte range, but written as a hexadecimal literal. - byte b7 = (byte) 0x8B; // 0x8B == 137, and b4 == -117 + byte b7 = (byte) 0x8B; // 0x8B == 137, and b4 == -117 } diff --git a/framework/tests/value/Issue3001.java b/framework/tests/value/Issue3001.java index ea1090af28a..2b3257af62b 100644 --- a/framework/tests/value/Issue3001.java +++ b/framework/tests/value/Issue3001.java @@ -1,7 +1,7 @@ public class Issue3001 { - private T getMember(Class type) { - T sym = getMember(type); - return sym; - } + private T getMember(Class type) { + T sym = getMember(type); + return sym; + } } diff --git a/framework/tests/value/Issue3105.java b/framework/tests/value/Issue3105.java index fe68f84f848..fb6119a5b6c 100644 --- a/framework/tests/value/Issue3105.java +++ b/framework/tests/value/Issue3105.java @@ -6,27 +6,27 @@ import org.checkerframework.framework.testchecker.lib.Issue3105Fields; public class Issue3105 { - class Demo1 { - @StringVal("foo") String m() { - return Issue3105Fields.FIELD1; + class Demo1 { + @StringVal("foo") String m() { + return Issue3105Fields.FIELD1; + } } - } - class Demo2 extends Issue3105Fields { - @StringVal("foo") String m() { - return FIELD1; + class Demo2 extends Issue3105Fields { + @StringVal("foo") String m() { + return FIELD1; + } } - } - class Demo3 { - @StringVal("bar") String m() { - return Issue3105Fields.FIELD2; + class Demo3 { + @StringVal("bar") String m() { + return Issue3105Fields.FIELD2; + } } - } - class Demo4 extends Issue3105Fields { - @StringVal("bar") String m() { - return FIELD2; + class Demo4 extends Issue3105Fields { + @StringVal("bar") String m() { + return FIELD2; + } } - } } diff --git a/framework/tests/value/Issue3105FieldInSameClass.java b/framework/tests/value/Issue3105FieldInSameClass.java index b2c01f82bd3..ac8ab24fa58 100644 --- a/framework/tests/value/Issue3105FieldInSameClass.java +++ b/framework/tests/value/Issue3105FieldInSameClass.java @@ -3,17 +3,17 @@ import org.checkerframework.common.value.qual.StringVal; public class Issue3105FieldInSameClass { - public static final String FIELD1 = "foo"; + public static final String FIELD1 = "foo"; } class Demo1 { - @StringVal("foo") String m() { - return Issue3105FieldInSameClass.FIELD1; - } + @StringVal("foo") String m() { + return Issue3105FieldInSameClass.FIELD1; + } } class Demo2 extends Issue3105FieldInSameClass { - @StringVal("foo") String m() { - return FIELD1; - } + @StringVal("foo") String m() { + return FIELD1; + } } diff --git a/framework/tests/value/Issue3105StaticImport.java b/framework/tests/value/Issue3105StaticImport.java index a750c488534..782d7daa52f 100644 --- a/framework/tests/value/Issue3105StaticImport.java +++ b/framework/tests/value/Issue3105StaticImport.java @@ -6,7 +6,7 @@ public class Issue3105StaticImport { - @StringVal("bar") String m2() { - return FIELD2; - } + @StringVal("bar") String m2() { + return FIELD2; + } } diff --git a/framework/tests/value/Issue3307.java b/framework/tests/value/Issue3307.java index ab007bbef22..6446a2e42d7 100644 --- a/framework/tests/value/Issue3307.java +++ b/framework/tests/value/Issue3307.java @@ -1,17 +1,17 @@ // Test case for https://github.com/typetools/checker-framework/issues/3307 public class Issue3307 extends A { - private int a = 0; + private int a = 0; - void bar(int value) { - if (a != value) { - a = value; - foo(value); + void bar(int value) { + if (a != value) { + a = value; + foo(value); + } } - } } @SuppressWarnings("unchecked") abstract class A { - protected final void foo(B... values) {} + protected final void foo(B... values) {} } diff --git a/framework/tests/value/Issue867.java b/framework/tests/value/Issue867.java index 7e11b028cd8..3732c1568a6 100644 --- a/framework/tests/value/Issue867.java +++ b/framework/tests/value/Issue867.java @@ -4,65 +4,65 @@ import org.checkerframework.common.value.qual.*; public class Issue867 { - void test1() { - @IntVal({0, 1}) int x = 0; - @IntVal(0) int zero = x++; - @IntVal(1) int one = x; - // :: error: (unary.increment.type.incompatible) - x++; + void test1() { + @IntVal({0, 1}) int x = 0; + @IntVal(0) int zero = x++; + @IntVal(1) int one = x; + // :: error: (unary.increment.type.incompatible) + x++; - x = 1; - one = x--; - zero = x; - // :: error: (unary.decrement.type.incompatible) - x--; - } + x = 1; + one = x--; + zero = x; + // :: error: (unary.decrement.type.incompatible) + x--; + } - void test2() { - @IntVal({0, 1, 2}) int x = 0; - @IntVal(1) int one = x++ + x++; - @IntVal(2) int two = x; - // :: error: (unary.increment.type.incompatible) - x++; + void test2() { + @IntVal({0, 1, 2}) int x = 0; + @IntVal(1) int one = x++ + x++; + @IntVal(2) int two = x; + // :: error: (unary.increment.type.incompatible) + x++; - x = 2; - @IntVal(3) int three = x-- + x--; - @IntVal(0) int zero = x; - // :: error: (unary.decrement.type.incompatible) - x--; - } + x = 2; + @IntVal(3) int three = x-- + x--; + @IntVal(0) int zero = x; + // :: error: (unary.decrement.type.incompatible) + x--; + } - void test3() { - @IntVal({0, 1, 2}) int x = 0; - @IntVal(2) int two = x++ + ++x; - two = x; - // :: error: (unary.increment.type.incompatible) - x++; + void test3() { + @IntVal({0, 1, 2}) int x = 0; + @IntVal(2) int two = x++ + ++x; + two = x; + // :: error: (unary.increment.type.incompatible) + x++; - x = 2; - two = x-- + --x; - @IntVal(0) int zero = x; - // :: error: (unary.decrement.type.incompatible) - x--; - } + x = 2; + two = x-- + --x; + @IntVal(0) int zero = x; + // :: error: (unary.decrement.type.incompatible) + x--; + } - void test4() { - @IntVal({0, 1}) int x = 0; - m0(x++); - // :: error: (argument.type.incompatible) - m0(x); - // :: error: (unary.increment.type.incompatible) - m1(x++); + void test4() { + @IntVal({0, 1}) int x = 0; + m0(x++); + // :: error: (argument.type.incompatible) + m0(x); + // :: error: (unary.increment.type.incompatible) + m1(x++); - x = 1; - m1(x--); - // :: error: (argument.type.incompatible) - m1(x); - // :: error: (unary.decrement.type.incompatible) - m0(x--); - } + x = 1; + m1(x--); + // :: error: (argument.type.incompatible) + m1(x); + // :: error: (unary.decrement.type.incompatible) + m0(x--); + } - void m0(@IntVal(0) int x) {} + void m0(@IntVal(0) int x) {} - void m1(@IntVal(1) int x) {} + void m1(@IntVal(1) int x) {} } diff --git a/framework/tests/value/LengthTransferForMinLen.java b/framework/tests/value/LengthTransferForMinLen.java index 00066a408eb..172200e510b 100644 --- a/framework/tests/value/LengthTransferForMinLen.java +++ b/framework/tests/value/LengthTransferForMinLen.java @@ -1,23 +1,23 @@ import org.checkerframework.common.value.qual.*; public class LengthTransferForMinLen { - void exceptional_control_flow(int[] a) { - if (a.length == 0) { - throw new IllegalArgumentException(); + void exceptional_control_flow(int[] a) { + if (a.length == 0) { + throw new IllegalArgumentException(); + } + int @MinLen(1) [] b = a; } - int @MinLen(1) [] b = a; - } - void equal_to_return(int[] a) { - if (a.length == 0) { - return; + void equal_to_return(int[] a) { + if (a.length == 0) { + return; + } + int @MinLen(1) [] b = a; } - int @MinLen(1) [] b = a; - } - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; + } } - } } diff --git a/framework/tests/value/LiteralArray.java b/framework/tests/value/LiteralArray.java index 7251f4420a4..71d6ae75a93 100644 --- a/framework/tests/value/LiteralArray.java +++ b/framework/tests/value/LiteralArray.java @@ -2,11 +2,11 @@ public class LiteralArray { - private static final String[] timeFormat = { - ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), - }; + private static final String[] timeFormat = { + ("#.#"), ("#.#"), ("#.#"), ("#.#"), ("#.#"), + }; - public void test() { - String @ArrayLen(5) [] array = timeFormat; - } + public void test() { + String @ArrayLen(5) [] array = timeFormat; + } } diff --git a/framework/tests/value/LongMax.java b/framework/tests/value/LongMax.java index d0ad214fe25..be4a68edb71 100644 --- a/framework/tests/value/LongMax.java +++ b/framework/tests/value/LongMax.java @@ -8,8 +8,8 @@ // infinitely growing the list of values. public class LongMax { - public void longMaxRange() { - @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long i = 9223372036854775807l; - @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long j = i; - } + public void longMaxRange() { + @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long i = 9223372036854775807l; + @IntRange(from = 9223372036854775807l, to = 9223372036854775807l) long j = i; + } } diff --git a/framework/tests/value/Loop.java b/framework/tests/value/Loop.java index ded95548684..8f81799c0a1 100644 --- a/framework/tests/value/Loop.java +++ b/framework/tests/value/Loop.java @@ -1,36 +1,37 @@ -import java.util.Iterator; -import java.util.Set; import org.checkerframework.common.value.qual.ArrayLen; import org.checkerframework.common.value.qual.BottomVal; import org.checkerframework.common.value.qual.IntVal; +import java.util.Iterator; +import java.util.Set; + public class Loop { - void test1(final double @ArrayLen(20) [] f2) { - int x; - for (int g = 0; g < f2.length; g++) { - x = g; + void test1(final double @ArrayLen(20) [] f2) { + int x; + for (int g = 0; g < f2.length; g++) { + x = g; + } + // :: error: (assignment.type.incompatible) + double @BottomVal [] test = f2; + // :: error: (assignment.type.incompatible) + @BottomVal int q = f2.length; } - // :: error: (assignment.type.incompatible) - double @BottomVal [] test = f2; - // :: error: (assignment.type.incompatible) - @BottomVal int q = f2.length; - } - void test2(final @IntVal(20) int param) { - int x; - for (int g = 0; g < param; g++) { - x = g; + void test2(final @IntVal(20) int param) { + int x; + for (int g = 0; g < param; g++) { + x = g; + } + // :: error: (assignment.type.incompatible) + @BottomVal int q = param; } - // :: error: (assignment.type.incompatible) - @BottomVal int q = param; - } - private void test3(Set set) { - String[] array = new String[set.size()]; - int i = 0; - for (Iterator iter = set.iterator(); iter.hasNext(); ) { - String key = iter.next(); - array[i++] = key; + private void test3(Set set) { + String[] array = new String[set.size()]; + int i = 0; + for (Iterator iter = set.iterator(); iter.hasNext(); ) { + String key = iter.next(); + array[i++] = key; + } } - } } diff --git a/framework/tests/value/LubToRange.java b/framework/tests/value/LubToRange.java index d83c7647617..dcbb697626c 100644 --- a/framework/tests/value/LubToRange.java +++ b/framework/tests/value/LubToRange.java @@ -6,13 +6,13 @@ import org.checkerframework.common.value.qual.IntVal; public class LubToRange { - public static boolean flag = false; + public static boolean flag = false; - void test(@IntVal({1, 2, 3, 4, 5}) int x, @IntVal({6, 7, 8, 9, 10, 11}) int y) { - @IntRange(from = 1, to = 11) int z = flag ? x : y; - } + void test(@IntVal({1, 2, 3, 4, 5}) int x, @IntVal({6, 7, 8, 9, 10, 11}) int y) { + @IntRange(from = 1, to = 11) int z = flag ? x : y; + } - void test2(int @ArrayLen({1, 2, 3, 4, 5}) [] x, int @ArrayLen({6, 7, 8, 9, 10, 11}) [] y) { - int @ArrayLenRange(from = 1, to = 11) [] z = flag ? x : y; - } + void test2(int @ArrayLen({1, 2, 3, 4, 5}) [] x, int @ArrayLen({6, 7, 8, 9, 10, 11}) [] y) { + int @ArrayLenRange(from = 1, to = 11) [] z = flag ? x : y; + } } diff --git a/framework/tests/value/MLEqualTo.java b/framework/tests/value/MLEqualTo.java index abfd04d8d4b..27463b19d80 100644 --- a/framework/tests/value/MLEqualTo.java +++ b/framework/tests/value/MLEqualTo.java @@ -2,9 +2,9 @@ public class MLEqualTo { - public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { - if (r == m) { - int @MinLen(2) [] j = r; + public static void equalToMinLen(int @MinLen(2) [] m, int @MinLen(0) [] r) { + if (r == m) { + int @MinLen(2) [] j = r; + } } - } } diff --git a/framework/tests/value/MathMinMax.java b/framework/tests/value/MathMinMax.java index 917df83a35d..11cb5303a14 100644 --- a/framework/tests/value/MathMinMax.java +++ b/framework/tests/value/MathMinMax.java @@ -2,34 +2,34 @@ import org.checkerframework.common.value.qual.IntVal; public class MathMinMax { - void mathTest1(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 15) int y) { - @IntRange(from = 0, to = 10) int min = Math.min(x, y); - @IntRange(from = 5, to = 15) int max = Math.max(x, y); - } + void mathTest1(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 15) int y) { + @IntRange(from = 0, to = 10) int min = Math.min(x, y); + @IntRange(from = 5, to = 15) int max = Math.max(x, y); + } - void mathTest2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 11, to = 15) int y) { - @IntRange(from = 0, to = 10) int min = Math.min(x, y); - @IntRange(from = 11, to = 15) int max = Math.max(x, y); - } + void mathTest2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 11, to = 15) int y) { + @IntRange(from = 0, to = 10) int min = Math.min(x, y); + @IntRange(from = 11, to = 15) int max = Math.max(x, y); + } - void mathTest3(@IntRange(from = 0, to = 20) int x, @IntRange(from = 5, to = 15) int y) { - @IntRange(from = 0, to = 15) int min = Math.min(x, y); - @IntRange(from = 5, to = 20) int max = Math.max(x, y); - @IntVal(1) int minConst = Math.min(1, 2); - @IntVal(2) int maxConst = Math.max(-1, 2); - } + void mathTest3(@IntRange(from = 0, to = 20) int x, @IntRange(from = 5, to = 15) int y) { + @IntRange(from = 0, to = 15) int min = Math.min(x, y); + @IntRange(from = 5, to = 20) int max = Math.max(x, y); + @IntVal(1) int minConst = Math.min(1, 2); + @IntVal(2) int maxConst = Math.max(-1, 2); + } - void mathTest(long x, long y) { - long min = Math.min(x, y); - long max = Math.max(x, y); - } + void mathTest(long x, long y) { + long min = Math.min(x, y); + long max = Math.max(x, y); + } - void mathTest(double x, double y) { - double min = Math.min(x, y); - double max = Math.max(x, y); - } + void mathTest(double x, double y) { + double min = Math.min(x, y); + double max = Math.max(x, y); + } - void mathTetMax(@IntRange int x, @IntRange int y) { - @IntRange int z = Math.min(x, y); - } + void mathTetMax(@IntRange int x, @IntRange int y) { + @IntRange int z = Math.min(x, y); + } } diff --git a/framework/tests/value/Methods.java b/framework/tests/value/Methods.java index feaad1cb2cd..7b7dd378052 100644 --- a/framework/tests/value/Methods.java +++ b/framework/tests/value/Methods.java @@ -2,83 +2,83 @@ public class Methods { - static int i = 3; - static final int k = 3; - - public static void Length() { - String a = "hello"; - @IntVal({5}) int b = a.length(); - - String e = "hello"; - int f = 2; - if (true) { - f = 1; - e = "world"; + static int i = 3; + static final int k = 3; + + public static void Length() { + String a = "hello"; + @IntVal({5}) int b = a.length(); + + String e = "hello"; + int f = 2; + if (true) { + f = 1; + e = "world"; + } + @IntVal({'e', 'l', 'o', 'r'}) char g = e.charAt(f); + + // :: error: (assignment.type.incompatible) + @IntVal({'l'}) char h = e.charAt(i); + + @IntVal({'l'}) char j = e.charAt(k); + } + + public static void Boolean() { + String a = "true"; + @BoolVal({true}) boolean b = Boolean.valueOf(a); + } + + public static void Byte() { + @IntVal({127}) byte a = Byte.MAX_VALUE; + @IntVal({-128}) byte b = Byte.MIN_VALUE; + + String c = "59"; + int d = 10; + @IntVal({59}) byte e = Byte.valueOf(c, d); + } + + public static void Character() { + @IntVal({'c'}) char a = Character.toLowerCase('C'); + + // :: error: (assignment.type.incompatible) + @BoolVal({false}) boolean b = Character.isWhitespace('\t'); + } + + public static void Double() { + @DoubleVal({Double.MAX_VALUE}) double a = Double.MAX_VALUE; + String b = "59.32"; + @DoubleVal({59.32}) double c = Double.valueOf(b); + } + + public static void Float() { + @IntVal({Float.MIN_EXPONENT}) int a = Float.MIN_EXPONENT; + String b = "59.32"; + @DoubleVal({59.32f}) float c = Float.valueOf(b); + } + + public static void Integer() { + @IntVal({Integer.SIZE}) int a = Integer.SIZE; + String b = "0"; + @IntVal({0}) int c = Integer.valueOf(b); + } + + public static void Long() { + @IntVal({Long.MAX_VALUE}) long a = Long.MAX_VALUE; + String b = "53"; + @IntVal({53L}) long c = Long.valueOf(53L); + } + + public static void Short() { + @IntVal({Short.MIN_VALUE}) short a = Short.MIN_VALUE; + + String b = "53"; + @IntVal({53}) short c = Short.valueOf(b); + } + + public static void String() { + + @StringVal({"herro"}) String a = "hello".replace('l', 'r'); + // :: error: (assignment.type.incompatible) + @StringVal({"hello"}) String b = "hello".replace('l', 'r'); } - @IntVal({'e', 'l', 'o', 'r'}) char g = e.charAt(f); - - // :: error: (assignment.type.incompatible) - @IntVal({'l'}) char h = e.charAt(i); - - @IntVal({'l'}) char j = e.charAt(k); - } - - public static void Boolean() { - String a = "true"; - @BoolVal({true}) boolean b = Boolean.valueOf(a); - } - - public static void Byte() { - @IntVal({127}) byte a = Byte.MAX_VALUE; - @IntVal({-128}) byte b = Byte.MIN_VALUE; - - String c = "59"; - int d = 10; - @IntVal({59}) byte e = Byte.valueOf(c, d); - } - - public static void Character() { - @IntVal({'c'}) char a = Character.toLowerCase('C'); - - // :: error: (assignment.type.incompatible) - @BoolVal({false}) boolean b = Character.isWhitespace('\t'); - } - - public static void Double() { - @DoubleVal({Double.MAX_VALUE}) double a = Double.MAX_VALUE; - String b = "59.32"; - @DoubleVal({59.32}) double c = Double.valueOf(b); - } - - public static void Float() { - @IntVal({Float.MIN_EXPONENT}) int a = Float.MIN_EXPONENT; - String b = "59.32"; - @DoubleVal({59.32f}) float c = Float.valueOf(b); - } - - public static void Integer() { - @IntVal({Integer.SIZE}) int a = Integer.SIZE; - String b = "0"; - @IntVal({0}) int c = Integer.valueOf(b); - } - - public static void Long() { - @IntVal({Long.MAX_VALUE}) long a = Long.MAX_VALUE; - String b = "53"; - @IntVal({53L}) long c = Long.valueOf(53L); - } - - public static void Short() { - @IntVal({Short.MIN_VALUE}) short a = Short.MIN_VALUE; - - String b = "53"; - @IntVal({53}) short c = Short.valueOf(b); - } - - public static void String() { - - @StringVal({"herro"}) String a = "hello".replace('l', 'r'); - // :: error: (assignment.type.incompatible) - @StringVal({"hello"}) String b = "hello".replace('l', 'r'); - } } diff --git a/framework/tests/value/MinLenConstants.java b/framework/tests/value/MinLenConstants.java index 1a277633089..2b2f103041e 100644 --- a/framework/tests/value/MinLenConstants.java +++ b/framework/tests/value/MinLenConstants.java @@ -2,7 +2,7 @@ public class MinLenConstants { - void test() { - int @MinLen(3) [] arr = {1, 2, 3}; - } + void test() { + int @MinLen(3) [] arr = {1, 2, 3}; + } } diff --git a/framework/tests/value/MinLenEqTransfer.java b/framework/tests/value/MinLenEqTransfer.java index 4ba8b4a28af..b7318503587 100644 --- a/framework/tests/value/MinLenEqTransfer.java +++ b/framework/tests/value/MinLenEqTransfer.java @@ -1,31 +1,31 @@ import org.checkerframework.common.value.qual.*; public class MinLenEqTransfer { - void eq_check(int[] a) { - if (1 == a.length) { - int @MinLen(1) [] b = a; + void eq_check(int[] a) { + if (1 == a.length) { + int @MinLen(1) [] b = a; + } + if (a.length == 1) { + int @MinLen(1) [] b = a; + } } - if (a.length == 1) { - int @MinLen(1) [] b = a; - } - } - void eq_bad_check(int[] a) { - if (1 == a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void eq_bad_check(int[] a) { + if (1 == a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } - int @MinLen(2) [] test(int[] a) { - if (a.length == 100 || a.length == 3) { - int x = a.length; - return a; - } else if (a.length == 0 || a.length == 1) { - int x = a.length; - // :: error: (return.type.incompatible) - return a; + int @MinLen(2) [] test(int[] a) { + if (a.length == 100 || a.length == 3) { + int x = a.length; + return a; + } else if (a.length == 0 || a.length == 1) { + int x = a.length; + // :: error: (return.type.incompatible) + return a; + } + return new int[] {1, 2}; } - return new int[] {1, 2}; - } } diff --git a/framework/tests/value/MinLenFieldInvar.java b/framework/tests/value/MinLenFieldInvar.java index 66e8420ed98..5fca71a606f 100644 --- a/framework/tests/value/MinLenFieldInvar.java +++ b/framework/tests/value/MinLenFieldInvar.java @@ -2,58 +2,58 @@ import org.checkerframework.framework.qual.FieldInvariant; public class MinLenFieldInvar { - class Super { - public final int @MinLen(2) [] minlen2; + class Super { + public final int @MinLen(2) [] minlen2; - public Super(int @MinLen(2) [] minlen2) { - this.minlen2 = minlen2; + public Super(int @MinLen(2) [] minlen2) { + this.minlen2 = minlen2; + } } - } - // :: error: (field.invariant.not.subtype) - @MinLenFieldInvariant(field = "minlen2", minLen = 1) - class InvalidSub extends Super { - public InvalidSub() { - super(new int[] {1, 2}); + // :: error: (field.invariant.not.subtype) + @MinLenFieldInvariant(field = "minlen2", minLen = 1) + class InvalidSub extends Super { + public InvalidSub() { + super(new int[] {1, 2}); + } } - } - @MinLenFieldInvariant(field = "minlen2", minLen = 4) - class ValidSub extends Super { - public final int[] validSubField; + @MinLenFieldInvariant(field = "minlen2", minLen = 4) + class ValidSub extends Super { + public final int[] validSubField; - public ValidSub(int[] validSubField) { - super(new int[] {1, 2, 3, 4}); - this.validSubField = validSubField; + public ValidSub(int[] validSubField) { + super(new int[] {1, 2, 3, 4}); + this.validSubField = validSubField; + } } - } - // :: error: (field.invariant.not.found.superclass) - @MinLenFieldInvariant(field = "validSubField", minLen = 3) - class InvalidSubSub1 extends ValidSub { - public InvalidSubSub1() { - super(new int[] {1, 2}); + // :: error: (field.invariant.not.found.superclass) + @MinLenFieldInvariant(field = "validSubField", minLen = 3) + class InvalidSubSub1 extends ValidSub { + public InvalidSubSub1() { + super(new int[] {1, 2}); + } } - } - // :: error: (field.invariant.not.subtype.superclass) - @MinLenFieldInvariant(field = "minlen2", minLen = 3) - class InvalidSubSub2 extends ValidSub { - public InvalidSubSub2() { - super(new int[] {1, 2}); + // :: error: (field.invariant.not.subtype.superclass) + @MinLenFieldInvariant(field = "minlen2", minLen = 3) + class InvalidSubSub2 extends ValidSub { + public InvalidSubSub2() { + super(new int[] {1, 2}); + } } - } - @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) - @MinLenFieldInvariant(field = "validSubField", minLen = 4) - class ValidSubSub extends ValidSub { - public ValidSubSub() { - super(null); + @FieldInvariant(field = "minlen2", qualifier = BottomVal.class) + @MinLenFieldInvariant(field = "validSubField", minLen = 4) + class ValidSubSub extends ValidSub { + public ValidSubSub() { + super(null); + } + + void test() { + int @BottomVal [] bot = minlen2; + int @MinLen(4) [] four = validSubField; + } } - - void test() { - int @BottomVal [] bot = minlen2; - int @MinLen(4) [] four = validSubField; - } - } } diff --git a/framework/tests/value/MinLenFieldInvar2.java b/framework/tests/value/MinLenFieldInvar2.java index 6e58400fd41..75600466ee9 100644 --- a/framework/tests/value/MinLenFieldInvar2.java +++ b/framework/tests/value/MinLenFieldInvar2.java @@ -6,26 +6,26 @@ import org.checkerframework.common.value.qual.MinLenFieldInvariant; public class MinLenFieldInvar2 { - class Super { - public final int @MinLen(2) [] minlen2; + class Super { + public final int @MinLen(2) [] minlen2; - public Super(int @MinLen(2) [] minlen2) { - this.minlen2 = minlen2; + public Super(int @MinLen(2) [] minlen2) { + this.minlen2 = minlen2; + } } - } - @MinLenFieldInvariant(field = "minlen2", minLen = 4) - class ValidSub extends Super { - public ValidSub(int[] validSubField) { - super(new int[] {1, 2, 3, 4}); + @MinLenFieldInvariant(field = "minlen2", minLen = 4) + class ValidSub extends Super { + public ValidSub(int[] validSubField) { + super(new int[] {1, 2, 3, 4}); + } } - } - void useAtValidSub(Super s) { - if (s instanceof ValidSub) { - System.out.println(s.minlen2[3]); - ValidSub vs = (ValidSub) s; - System.out.println(vs.minlen2[3]); + void useAtValidSub(Super s) { + if (s instanceof ValidSub) { + System.out.println(s.minlen2[3]); + ValidSub vs = (ValidSub) s; + System.out.println(vs.minlen2[3]); + } } - } } diff --git a/framework/tests/value/MinLenGTETransfer.java b/framework/tests/value/MinLenGTETransfer.java index e58a367a687..7347cefdfb6 100644 --- a/framework/tests/value/MinLenGTETransfer.java +++ b/framework/tests/value/MinLenGTETransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenGTETransfer { - void gte_check(int[] a) { - if (a.length >= 1) { - int @MinLen(1) [] b = a; + void gte_check(int[] a) { + if (a.length >= 1) { + int @MinLen(1) [] b = a; + } } - } - void gte_bad_check(int[] a) { - if (a.length >= 1) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void gte_bad_check(int[] a) { + if (a.length >= 1) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/framework/tests/value/MinLenGTTransfer.java b/framework/tests/value/MinLenGTTransfer.java index af419fd4fc9..95293af59f4 100644 --- a/framework/tests/value/MinLenGTTransfer.java +++ b/framework/tests/value/MinLenGTTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenGTTransfer { - void gt_check(int[] a) { - if (a.length > 0) { - int @MinLen(1) [] b = a; + void gt_check(int[] a) { + if (a.length > 0) { + int @MinLen(1) [] b = a; + } } - } - void gt_bad_check(int[] a) { - if (a.length > 0) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void gt_bad_check(int[] a) { + if (a.length > 0) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/framework/tests/value/MinLenLTETransfer.java b/framework/tests/value/MinLenLTETransfer.java index f9ead3a4f10..d09f0b6d520 100644 --- a/framework/tests/value/MinLenLTETransfer.java +++ b/framework/tests/value/MinLenLTETransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenLTETransfer { - void lte_check(int[] a) { - if (1 <= a.length) { - int @MinLen(1) [] b = a; + void lte_check(int[] a) { + if (1 <= a.length) { + int @MinLen(1) [] b = a; + } } - } - void lte_bad_check(int[] a) { - if (1 <= a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void lte_bad_check(int[] a) { + if (1 <= a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/framework/tests/value/MinLenLTTransfer.java b/framework/tests/value/MinLenLTTransfer.java index fe2d7cf2b29..e47c766f9e5 100644 --- a/framework/tests/value/MinLenLTTransfer.java +++ b/framework/tests/value/MinLenLTTransfer.java @@ -1,16 +1,16 @@ import org.checkerframework.common.value.qual.*; public class MinLenLTTransfer { - void lt_check(int[] a) { - if (0 < a.length) { - int @MinLen(1) [] b = a; + void lt_check(int[] a) { + if (0 < a.length) { + int @MinLen(1) [] b = a; + } } - } - void lt_bad_check(int[] a) { - if (0 < a.length) { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void lt_bad_check(int[] a) { + if (0 < a.length) { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } } diff --git a/framework/tests/value/MinLenLUB.java b/framework/tests/value/MinLenLUB.java index 2b8e575d149..c8e71267682 100644 --- a/framework/tests/value/MinLenLUB.java +++ b/framework/tests/value/MinLenLUB.java @@ -2,42 +2,42 @@ public class MinLenLUB { - public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; + public static void MinLen(int @MinLen(10) [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; + } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; - } - public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; + public static void Bottom(int @BottomVal [] arg, int @MinLen(4) [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; + } + // :: error: (assignment.type.incompatible) + int @MinLen(10) [] res = arr; + int @MinLen(4) [] res2 = arr; + // :: error: (assignment.type.incompatible) + int @BottomVal [] res3 = arr; } - // :: error: (assignment.type.incompatible) - int @MinLen(10) [] res = arr; - int @MinLen(4) [] res2 = arr; - // :: error: (assignment.type.incompatible) - int @BottomVal [] res3 = arr; - } - public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { - int[] arr; - if (true) { - arr = arg; - } else { - arr = arg2; + public static void BothBottom(int @BottomVal [] arg, int @BottomVal [] arg2) { + int[] arr; + if (true) { + arr = arg; + } else { + arr = arg2; + } + int @MinLen(10) [] res = arr; + int @BottomVal [] res2 = arr; } - int @MinLen(10) [] res = arr; - int @BottomVal [] res2 = arr; - } } diff --git a/framework/tests/value/MinLenNEqTransfer.java b/framework/tests/value/MinLenNEqTransfer.java index 15677e020fe..938246afcde 100644 --- a/framework/tests/value/MinLenNEqTransfer.java +++ b/framework/tests/value/MinLenNEqTransfer.java @@ -1,26 +1,26 @@ import org.checkerframework.common.value.qual.*; public class MinLenNEqTransfer { - void neq_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - int @MinLen(1) [] b = a; + void neq_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + int @MinLen(1) [] b = a; + } } - } - void neq_bad_check(int[] a) { - if (1 != a.length) { - int x = 1; // do nothing. - } else { - // :: error: (assignment.type.incompatible) - int @MinLen(2) [] b = a; + void neq_bad_check(int[] a) { + if (1 != a.length) { + int x = 1; // do nothing. + } else { + // :: error: (assignment.type.incompatible) + int @MinLen(2) [] b = a; + } } - } - void neq_zero_special_case(int[] a) { - if (a.length != 0) { - int @MinLen(1) [] b = a; + void neq_zero_special_case(int[] a) { + if (a.length != 0) { + int @MinLen(1) [] b = a; + } } - } } diff --git a/framework/tests/value/MinLenPostcondition.java b/framework/tests/value/MinLenPostcondition.java index b38f75f5a68..a3d36f3df5d 100644 --- a/framework/tests/value/MinLenPostcondition.java +++ b/framework/tests/value/MinLenPostcondition.java @@ -1,9 +1,9 @@ import org.checkerframework.common.value.qual.*; public class MinLenPostcondition { - public void m(String a) { - if (!a.isEmpty()) { - char c = a.charAt(0); + public void m(String a) { + if (!a.isEmpty()) { + char c = a.charAt(0); + } } - } } diff --git a/framework/tests/value/MinLenVarargs.java b/framework/tests/value/MinLenVarargs.java index e9501b83ebd..14f3baa7c36 100644 --- a/framework/tests/value/MinLenVarargs.java +++ b/framework/tests/value/MinLenVarargs.java @@ -3,18 +3,18 @@ // @skip-test public class MinLenVarargs { - static void check(String @MinLen(3) ... var) { - System.out.println(var[0] + " " + var[1]); - } + static void check(String @MinLen(3) ... var) { + System.out.println(var[0] + " " + var[1]); + } - public static void main(String[] args) { - // :: error: (argument.type.incompatible) - check(new String[] {"goodbye"}); - // :: error: (argument.type.incompatible) - check("goodbye"); - // :: error: (argument.type.incompatible) - check(); - // :: error: (argument.type.incompatible) - check("hello", "goodbye"); - } + public static void main(String[] args) { + // :: error: (argument.type.incompatible) + check(new String[] {"goodbye"}); + // :: error: (argument.type.incompatible) + check("goodbye"); + // :: error: (argument.type.incompatible) + check(); + // :: error: (argument.type.incompatible) + check("hello", "goodbye"); + } } diff --git a/framework/tests/value/MultipleBinaryExpressions.java b/framework/tests/value/MultipleBinaryExpressions.java index e9f1f294d3a..31af8b203dd 100644 --- a/framework/tests/value/MultipleBinaryExpressions.java +++ b/framework/tests/value/MultipleBinaryExpressions.java @@ -3,70 +3,70 @@ public class MultipleBinaryExpressions { - private final String ONE_STRING = "1"; - private final String TWO_STRING = "2"; - private final String THREE_STRING = "3"; - private final String FOUR_STRING = "4"; - private final String FIVE_STRING = "5"; - private final String SIX_STRING = "6"; - private final String SEVEN_STRING = "7"; - private final String EIGHT_STRING = "8"; - private final String NINE_STRING = "9"; + private final String ONE_STRING = "1"; + private final String TWO_STRING = "2"; + private final String THREE_STRING = "3"; + private final String FOUR_STRING = "4"; + private final String FIVE_STRING = "5"; + private final String SIX_STRING = "6"; + private final String SEVEN_STRING = "7"; + private final String EIGHT_STRING = "8"; + private final String NINE_STRING = "9"; - public final @StringVal("123456789123456789") String concat1 = - ONE_STRING - + TWO_STRING - + THREE_STRING - + FOUR_STRING - + FIVE_STRING - + SIX_STRING - + SEVEN_STRING - + EIGHT_STRING - + NINE_STRING - + ONE_STRING - + TWO_STRING - + THREE_STRING - + FOUR_STRING - + FIVE_STRING - + SIX_STRING - + SEVEN_STRING - + EIGHT_STRING - + NINE_STRING; + public final @StringVal("123456789123456789") String concat1 = + ONE_STRING + + TWO_STRING + + THREE_STRING + + FOUR_STRING + + FIVE_STRING + + SIX_STRING + + SEVEN_STRING + + EIGHT_STRING + + NINE_STRING + + ONE_STRING + + TWO_STRING + + THREE_STRING + + FOUR_STRING + + FIVE_STRING + + SIX_STRING + + SEVEN_STRING + + EIGHT_STRING + + NINE_STRING; - public final @StringVal("112233445566778899") String concat2 = - ONE_STRING - + "1" - + TWO_STRING - + "2" - + THREE_STRING - + "3" - + FOUR_STRING - + "4" - + FIVE_STRING - + "5" - + "6" - + SIX_STRING - + "7" - + SEVEN_STRING - + "8" - + EIGHT_STRING - + "9" - + NINE_STRING; + public final @StringVal("112233445566778899") String concat2 = + ONE_STRING + + "1" + + TWO_STRING + + "2" + + THREE_STRING + + "3" + + FOUR_STRING + + "4" + + FIVE_STRING + + "5" + + "6" + + SIX_STRING + + "7" + + SEVEN_STRING + + "8" + + EIGHT_STRING + + "9" + + NINE_STRING; - private final int ONE = 1; - private final int TWO = 2; - private final int THREE = 3; - private final int FOUR = 4; - private final int FIVE = 5; - private final int SIX = 6; - private final int SEVEN = 7; - private final int EIGHT = 8; - private final int NINE = 9; + private final int ONE = 1; + private final int TWO = 2; + private final int THREE = 3; + private final int FOUR = 4; + private final int FIVE = 5; + private final int SIX = 6; + private final int SEVEN = 7; + private final int EIGHT = 8; + private final int NINE = 9; - public final @IntVal(90) int plus1 = - ONE + TWO + THREE + FOUR + FIVE + SIX + SEVEN + EIGHT + NINE + ONE + TWO + THREE + FOUR + FIVE - + SIX + SEVEN + EIGHT + NINE; - public final @IntVal(90) int plus2 = - ONE + 1 + TWO + 2 + THREE + 3 + FOUR + 4 + FIVE + 5 + SIX + 6 + SEVEN + 7 + EIGHT + 8 + NINE - + 9; + public final @IntVal(90) int plus1 = + ONE + TWO + THREE + FOUR + FIVE + SIX + SEVEN + EIGHT + NINE + ONE + TWO + THREE + FOUR + + FIVE + SIX + SEVEN + EIGHT + NINE; + public final @IntVal(90) int plus2 = + ONE + 1 + TWO + 2 + THREE + 3 + FOUR + 4 + FIVE + 5 + SIX + 6 + SEVEN + 7 + EIGHT + 8 + + NINE + 9; } diff --git a/framework/tests/value/MyTree.java b/framework/tests/value/MyTree.java index 1440766b54f..8d9ee790ceb 100644 --- a/framework/tests/value/MyTree.java +++ b/framework/tests/value/MyTree.java @@ -8,35 +8,35 @@ public class MyTree { - public static MyTree newTree(V value) { - throw new Error("body doesn't matter"); - } - - public MyTree put(Value newValue) { - throw new Error("body doesn't matter"); - } - - void uses() { - newTree("hello").put("bye"); - - MyTree<@UnknownVal String> myTree1 = newTree("hello").put("bye"); - // :: error: (assignment.type.incompatible) - MyTree<@StringVal("hello") String> myTree1b = newTree("hello").put("bye"); - - // Note: This is a false positive: the type of newTree("hello").put("hello") should be - // inferred as MyTree<@StringVal("hello") String> and the assignment should therefore pass. - // :: error: (assignment.type.incompatible) - MyTree<@StringVal("hello") String> myTree2 = newTree("hello").put("hello"); - MyTree<@StringVal("hello") String> myTree2b = - MyTree.<@StringVal("hello") String>newTree("hello").put("hello"); - // :: error: (assignment.type.incompatible) - MyTree<@StringVal("error") String> myTree2c = newTree("hello").put("hello"); - - MyTree<@UnknownVal String> myTree3 = newTree("hello"); - myTree3.put("bye"); - - MyTree<@StringVal("hello") String> myTree4 = newTree("hello"); - // :: error: (argument.type.incompatible) - myTree4.put("bye"); - } + public static MyTree newTree(V value) { + throw new Error("body doesn't matter"); + } + + public MyTree put(Value newValue) { + throw new Error("body doesn't matter"); + } + + void uses() { + newTree("hello").put("bye"); + + MyTree<@UnknownVal String> myTree1 = newTree("hello").put("bye"); + // :: error: (assignment.type.incompatible) + MyTree<@StringVal("hello") String> myTree1b = newTree("hello").put("bye"); + + // Note: This is a false positive: the type of newTree("hello").put("hello") should be + // inferred as MyTree<@StringVal("hello") String> and the assignment should therefore pass. + // :: error: (assignment.type.incompatible) + MyTree<@StringVal("hello") String> myTree2 = newTree("hello").put("hello"); + MyTree<@StringVal("hello") String> myTree2b = + MyTree.<@StringVal("hello") String>newTree("hello").put("hello"); + // :: error: (assignment.type.incompatible) + MyTree<@StringVal("error") String> myTree2c = newTree("hello").put("hello"); + + MyTree<@UnknownVal String> myTree3 = newTree("hello"); + myTree3.put("bye"); + + MyTree<@StringVal("hello") String> myTree4 = newTree("hello"); + // :: error: (argument.type.incompatible) + myTree4.put("bye"); + } } diff --git a/framework/tests/value/MyTree2.java b/framework/tests/value/MyTree2.java index 9a79bd2184a..bfbfb6bfadc 100644 --- a/framework/tests/value/MyTree2.java +++ b/framework/tests/value/MyTree2.java @@ -5,25 +5,26 @@ public class MyTree2 { - public static MyTree2 newTree(V value) { - throw new Error("body doesn't matter"); - } + public static MyTree2 newTree(V value) { + throw new Error("body doesn't matter"); + } - public MyTree2 put(String newKey, Value newValue) { - throw new Error("body doesn't matter"); - } + public MyTree2 put(String newKey, Value newValue) { + throw new Error("body doesn't matter"); + } - public void client1() { - MyTree2 root2 = MyTree2.newTree("constructorarg").put("key1", "value1"); - } + public void client1() { + MyTree2 root2 = MyTree2.newTree("constructorarg").put("key1", "value1"); + } - public void client2() { - MyTree2 root2 = - MyTree2.newTree((@UnknownVal String) "constructorarg").put("key1", "value1"); - } + public void client2() { + MyTree2 root2 = + MyTree2.newTree((@UnknownVal String) "constructorarg").put("key1", "value1"); + } - public void client3() { - MyTree2 root2 = - MyTree2.newTree((@UnknownVal String) "constructorarg").put("key1", "value1"); - } + public void client3() { + MyTree2 root2 = + MyTree2.newTree((@UnknownVal String) "constructorarg") + .put("key1", "value1"); + } } diff --git a/framework/tests/value/NegativeArrayLen.java b/framework/tests/value/NegativeArrayLen.java index 3994776f539..76e9b63b4de 100644 --- a/framework/tests/value/NegativeArrayLen.java +++ b/framework/tests/value/NegativeArrayLen.java @@ -2,19 +2,19 @@ import org.checkerframework.common.value.qual.BottomVal; public class NegativeArrayLen { - void test1() { - int @BottomVal [] a = new int[-1]; - int @BottomVal [] b = new int[Integer.MAX_VALUE + 1]; - // :: warning: (negative.arraylen) - int @ArrayLen(-1) [] c = new int[-1]; - } + void test1() { + int @BottomVal [] a = new int[-1]; + int @BottomVal [] b = new int[Integer.MAX_VALUE + 1]; + // :: warning: (negative.arraylen) + int @ArrayLen(-1) [] c = new int[-1]; + } - void test2( - // :: warning: (negative.arraylen) - int @ArrayLen(Integer.MIN_VALUE) [] a, - // :: warning: (negative.arraylen) - int @ArrayLen({-1, 2, 0}) [] b) { - int @BottomVal [] y = a; - int @BottomVal [] x = b; - } + void test2( + // :: warning: (negative.arraylen) + int @ArrayLen(Integer.MIN_VALUE) [] a, + // :: warning: (negative.arraylen) + int @ArrayLen({-1, 2, 0}) [] b) { + int @BottomVal [] y = a; + int @BottomVal [] x = b; + } } diff --git a/framework/tests/value/NestedArrayLengthInference.java b/framework/tests/value/NestedArrayLengthInference.java index c17f2f05c79..964029c9da6 100644 --- a/framework/tests/value/NestedArrayLengthInference.java +++ b/framework/tests/value/NestedArrayLengthInference.java @@ -1,12 +1,12 @@ public class NestedArrayLengthInference { - public void doStuff(int r) { + public void doStuff(int r) { - int[] length16array = new int[16]; + int[] length16array = new int[16]; - int[] unknownLengthArray = new int[r]; + int[] unknownLengthArray = new int[r]; - // CF seems to think that if one array has constant length, all do?? - int[][] myNewArray = new int[][] {unknownLengthArray, length16array}; - int[][] myNewArray2 = new int[][] {length16array, unknownLengthArray}; - } + // CF seems to think that if one array has constant length, all do?? + int[][] myNewArray = new int[][] {unknownLengthArray, length16array}; + int[][] myNewArray2 = new int[][] {length16array, unknownLengthArray}; + } } diff --git a/framework/tests/value/Overflows.java b/framework/tests/value/Overflows.java index 94b7928fd75..ae18c26bb92 100644 --- a/framework/tests/value/Overflows.java +++ b/framework/tests/value/Overflows.java @@ -2,43 +2,43 @@ public class Overflows { - static void bytes() { - byte max = Byte.MAX_VALUE; - @IntVal(-128) byte maxPlus1 = (byte) (max + 1); - // :: error: (assignment.type.incompatible) - @IntVal(-128) short maxPlus1Short = (short) (max + 1); - } - - static void chars() { - char max = Character.MAX_VALUE; - // :: warning: (cast.unsafe) - @IntVal(0) char maxPlus1 = (char) (max + 1); - } - - static void shorts() { - short max = Short.MAX_VALUE; - @IntVal(-32768) short maxPlus1 = (short) (max + 1); - // :: error: (assignment.type.incompatible) - @IntVal(-32768) int maxPlus1Int = (int) (max + 1); - } - - static void ints() { - int max = Integer.MAX_VALUE; - @IntVal(-2147483648) int maxPlus1 = max + 1; - } - - static void longs() { - long max = Long.MAX_VALUE; - @IntVal(-9223372036854775808L) long maxPlus1 = max + 1; - } - - static void doubles() { - double max = Double.MAX_VALUE; - @DoubleVal(1.7976931348623157E308) double maxPlus1 = max + 1.0; - } - - static void floats() { - float max = Float.MAX_VALUE; - @DoubleVal(3.4028235E38f) float maxPlus1 = max + 1.0f; - } + static void bytes() { + byte max = Byte.MAX_VALUE; + @IntVal(-128) byte maxPlus1 = (byte) (max + 1); + // :: error: (assignment.type.incompatible) + @IntVal(-128) short maxPlus1Short = (short) (max + 1); + } + + static void chars() { + char max = Character.MAX_VALUE; + // :: warning: (cast.unsafe) + @IntVal(0) char maxPlus1 = (char) (max + 1); + } + + static void shorts() { + short max = Short.MAX_VALUE; + @IntVal(-32768) short maxPlus1 = (short) (max + 1); + // :: error: (assignment.type.incompatible) + @IntVal(-32768) int maxPlus1Int = (int) (max + 1); + } + + static void ints() { + int max = Integer.MAX_VALUE; + @IntVal(-2147483648) int maxPlus1 = max + 1; + } + + static void longs() { + long max = Long.MAX_VALUE; + @IntVal(-9223372036854775808L) long maxPlus1 = max + 1; + } + + static void doubles() { + double max = Double.MAX_VALUE; + @DoubleVal(1.7976931348623157E308) double maxPlus1 = max + 1.0; + } + + static void floats() { + float max = Float.MAX_VALUE; + @DoubleVal(3.4028235E38f) float maxPlus1 = max + 1.0f; + } } diff --git a/framework/tests/value/Polymorphic.java b/framework/tests/value/Polymorphic.java index 17ba3eb3de2..c85c9b247f0 100644 --- a/framework/tests/value/Polymorphic.java +++ b/framework/tests/value/Polymorphic.java @@ -2,31 +2,31 @@ public class Polymorphic { - // Identity functions + // Identity functions - int @PolyValue [] identity_array(int @PolyValue [] a) { - return a; - } + int @PolyValue [] identity_array(int @PolyValue [] a) { + return a; + } - @PolyValue int identity_int(@PolyValue int a) { - return a; - } + @PolyValue int identity_int(@PolyValue int a) { + return a; + } - void minlen_id(int @MinLen(5) [] a) { - int @MinLen(5) [] b = identity_array(a); - // :: error: (assignment.type.incompatible) - int @MinLen(6) [] c = identity_array(b); - } + void minlen_id(int @MinLen(5) [] a) { + int @MinLen(5) [] b = identity_array(a); + // :: error: (assignment.type.incompatible) + int @MinLen(6) [] c = identity_array(b); + } - void use(int @ArrayLenRange(from = 5, to = 25) [] a) { - int @ArrayLenRange(from = 5, to = 25) [] b = identity_array(a); - // :: error: (assignment.type.incompatible) - int @ArrayLenRange(from = 1, to = 13) [] c = identity_array(a); - } + void use(int @ArrayLenRange(from = 5, to = 25) [] a) { + int @ArrayLenRange(from = 5, to = 25) [] b = identity_array(a); + // :: error: (assignment.type.incompatible) + int @ArrayLenRange(from = 1, to = 13) [] c = identity_array(a); + } - void use2(@IntRange(from = 5, to = 25) int a) { - @IntRange(from = 5, to = 25) int b = identity_int(a); - // :: error: (assignment.type.incompatible) - @IntVal(3) int x = identity_int(a); - } + void use2(@IntRange(from = 5, to = 25) int a) { + @IntRange(from = 5, to = 25) int b = identity_int(a); + // :: error: (assignment.type.incompatible) + @IntVal(3) int x = identity_int(a); + } } diff --git a/framework/tests/value/Polymorphic2.java b/framework/tests/value/Polymorphic2.java index 932ba383b7a..58a5f2205bb 100644 --- a/framework/tests/value/Polymorphic2.java +++ b/framework/tests/value/Polymorphic2.java @@ -1,37 +1,37 @@ import org.checkerframework.common.value.qual.*; public class Polymorphic2 { - public static boolean flag = false; - - @PolyValue int mergeValue(@PolyValue int a, @PolyValue int b) { - return flag ? a : b; - } - - int @PolyValue [] mergeValue(int @PolyValue [] a, int @PolyValue [] b) { - return flag ? a : b; - } - - void testMinLen(int @MinLen(2) [] a, int @MinLen(5) [] b) { - int @MinLen(2) [] z = mergeValue(a, b); - // :: error: (assignment.type.incompatible) - int @MinLen(5) [] zz = mergeValue(a, b); - } - - void testArrayLen(int @ArrayLen(2) [] a, int @ArrayLen(5) [] b) { - // :: error: (assignment.type.incompatible) - int @ArrayLen(2) [] z = mergeValue(a, b); - // :: error: (assignment.type.incompatible) - int @ArrayLen(5) [] zz = mergeValue(a, b); - - int @ArrayLen({2, 5}) [] zzz = mergeValue(a, b); - } - - void testIntVal(@IntVal(2) int a, @IntVal(5) int b) { - // :: error: (assignment.type.incompatible) - @IntVal(2) int z = mergeValue(a, b); - // :: error: (assignment.type.incompatible) - @IntVal(5) int zz = mergeValue(a, b); - - @IntVal({2, 5}) int zzz = mergeValue(a, b); - } + public static boolean flag = false; + + @PolyValue int mergeValue(@PolyValue int a, @PolyValue int b) { + return flag ? a : b; + } + + int @PolyValue [] mergeValue(int @PolyValue [] a, int @PolyValue [] b) { + return flag ? a : b; + } + + void testMinLen(int @MinLen(2) [] a, int @MinLen(5) [] b) { + int @MinLen(2) [] z = mergeValue(a, b); + // :: error: (assignment.type.incompatible) + int @MinLen(5) [] zz = mergeValue(a, b); + } + + void testArrayLen(int @ArrayLen(2) [] a, int @ArrayLen(5) [] b) { + // :: error: (assignment.type.incompatible) + int @ArrayLen(2) [] z = mergeValue(a, b); + // :: error: (assignment.type.incompatible) + int @ArrayLen(5) [] zz = mergeValue(a, b); + + int @ArrayLen({2, 5}) [] zzz = mergeValue(a, b); + } + + void testIntVal(@IntVal(2) int a, @IntVal(5) int b) { + // :: error: (assignment.type.incompatible) + @IntVal(2) int z = mergeValue(a, b); + // :: error: (assignment.type.incompatible) + @IntVal(5) int zz = mergeValue(a, b); + + @IntVal({2, 5}) int zzz = mergeValue(a, b); + } } diff --git a/framework/tests/value/RefineBoolean.java b/framework/tests/value/RefineBoolean.java index bc54cfb5dc3..5c47ab4ba87 100644 --- a/framework/tests/value/RefineBoolean.java +++ b/framework/tests/value/RefineBoolean.java @@ -2,51 +2,51 @@ public class RefineBoolean { - void test1(boolean x) { - if (x == false) { - @BoolVal(false) boolean y = x; + void test1(boolean x) { + if (x == false) { + @BoolVal(false) boolean y = x; + } } - } - void test2(boolean x) { - if (false == x) { - @BoolVal(false) boolean y = x; + void test2(boolean x) { + if (false == x) { + @BoolVal(false) boolean y = x; + } } - } - void test3(boolean x) { - if (x != true) { - @BoolVal(false) boolean y = x; + void test3(boolean x) { + if (x != true) { + @BoolVal(false) boolean y = x; + } } - } - void test4(boolean x) { - if (true != x) { - @BoolVal(false) boolean y = x; + void test4(boolean x) { + if (true != x) { + @BoolVal(false) boolean y = x; + } } - } - void test5(boolean x) { - if (x == true) { - @BoolVal(true) boolean y = x; + void test5(boolean x) { + if (x == true) { + @BoolVal(true) boolean y = x; + } } - } - void test6(boolean x) { - if (true == x) { - @BoolVal(true) boolean y = x; + void test6(boolean x) { + if (true == x) { + @BoolVal(true) boolean y = x; + } } - } - void test7(boolean x) { - if (false != x) { - @BoolVal(true) boolean y = x; + void test7(boolean x) { + if (false != x) { + @BoolVal(true) boolean y = x; + } } - } - void test8(boolean x) { - if (x != false) { - @BoolVal(true) boolean y = x; + void test8(boolean x) { + if (x != false) { + @BoolVal(true) boolean y = x; + } } - } } diff --git a/framework/tests/value/RefineUnknownToIntRange.java b/framework/tests/value/RefineUnknownToIntRange.java index f0b787b28bd..3f0547b05af 100644 --- a/framework/tests/value/RefineUnknownToIntRange.java +++ b/framework/tests/value/RefineUnknownToIntRange.java @@ -2,48 +2,48 @@ import org.checkerframework.common.value.qual.IntRange; public class RefineUnknownToIntRange { - void test1(int x) { - if (x > 1) { - @IntRange(from = 2) int z = x; - } + void test1(int x) { + if (x > 1) { + @IntRange(from = 2) int z = x; + } - if (x < 1) { - @IntRange(to = 0) int z = x; - } + if (x < 1) { + @IntRange(to = 0) int z = x; + } - if (1 < x) { - @IntRange(from = 2) int z = x; - } + if (1 < x) { + @IntRange(from = 2) int z = x; + } - if (1 > x) { - @IntRange(to = 0) int z = x; - } + if (1 > x) { + @IntRange(to = 0) int z = x; + } - if (x >= 1) { - @IntRange(from = 1) int z = x; - } + if (x >= 1) { + @IntRange(from = 1) int z = x; + } - if (x <= 1) { - @IntRange(to = 1) int z = x; - } + if (x <= 1) { + @IntRange(to = 1) int z = x; + } - if (x < 100 && x > 2) { - @IntRange(from = 3, to = 99) int z = x; + if (x < 100 && x > 2) { + @IntRange(from = 3, to = 99) int z = x; + } } - } - void test3(boolean x) { - // Make sure non int values are ignored. - if (x == false) { - @BoolVal(false) boolean y = x; - } + void test3(boolean x) { + // Make sure non int values are ignored. + if (x == false) { + @BoolVal(false) boolean y = x; + } - if (x != true) { - @BoolVal(false) boolean y = x; - } + if (x != true) { + @BoolVal(false) boolean y = x; + } - Object o = new Object(); - Object o2 = new Object(); - if (o == o2) {} - } + Object o = new Object(); + Object o2 = new Object(); + if (o == o2) {} + } } diff --git a/framework/tests/value/Refinement.java b/framework/tests/value/Refinement.java index 47b5639638e..3a17b7ea95f 100644 --- a/framework/tests/value/Refinement.java +++ b/framework/tests/value/Refinement.java @@ -2,311 +2,312 @@ public class Refinement { - void eq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x == 1) { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; + void eq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x == 1) { + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } + + if (x == w) { + @IntVal({1}) int y = x; + @IntVal({1}) int z = w; + } else { + // These two assignments are illegal because one of x or w could be 1, + // while the other is not. So no refinement can be applied. + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } } - if (x == w) { - @IntVal({1}) int y = x; - @IntVal({1}) int z = w; - } else { - // These two assignments are illegal because one of x or w could be 1, - // while the other is not. So no refinement can be applied. - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; + void lt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x < 2) { + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } + + if (x < w) { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + + @IntVal({5, 6}) int z = w; + } else { + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; + } } - } - - void lt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x < 2) { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; + void lte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x <= 1) { + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } + + if (x <= w) { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } else { + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; + } } - if (x < w) { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - - @IntVal({5, 6}) int z = w; - } else { - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; + void neq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x != 1) { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } else { + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } + + if (x != w) { + // These two assignments are illegal because one of x or w could be 1, + // while the other is not. So no refinement can be applied. + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } else { + @IntVal({1}) int y = x; + @IntVal({1}) int z = w; + } } - } - - void lte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x <= 1) { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; + void gte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x >= 2) { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } else { + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } + + if (x >= w) { + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; + } else { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + + @IntVal({5, 6}) int z = w; + } } - if (x <= w) { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } else { - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; + void gt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + if (x > 1) { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } else { + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } + + if (x > w) { + @IntVal({2}) int y = x; + @IntVal({1}) int z = w; + } else { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } } - } - void neq_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x != 1) { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } else { - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; + void boolean_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { + @BoolVal({true}) boolean b = x >= 0; + @BoolVal({false}) boolean c = w == 3; + @BoolVal({true, false}) boolean d = x < w; } - if (x != w) { - // These two assignments are illegal because one of x or w could be 1, - // while the other is not. So no refinement can be applied. - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } else { - @IntVal({1}) int y = x; - @IntVal({1}) int z = w; + void test_intrange_eq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { + if (x == y) { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 3, to = 5) int b = y; + } else { + @IntRange(from = 6, to = 10) + // :: error: (assignment.type.incompatible) + int a = x; + @IntRange(from = 1, to = 2) + // :: error: (assignment.type.incompatible) + int b = y; + } } - } - - void gte_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x >= 2) { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } else { - @IntVal({1}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; + void test_intrange_eq2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 0, to = 0) int y) { + if (x == y) { + @IntRange(from = 0, to = 0) int a = x; + @IntRange(from = 0, to = 0) int b = y; + } else { + @IntRange(from = 1, to = 10) int a = x; + @IntRange(from = 0, to = 0) int b = y; + } } - if (x >= w) { - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; - } else { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - - @IntVal({5, 6}) int z = w; + void test_intrange_eq3(@IntRange(from = 0, to = 10) int x, @IntVal(0) int y) { + if (x == y) { + @IntVal(0) int a = x; + @IntVal(0) int b = y; + } else { + @IntRange(from = 1, to = 10) int a = x; + @IntVal(0) int b = y; + } + + if (y != x) { + @IntRange(from = 1, to = 10) int a = x; + @IntVal(0) int b = y; + } } - } - - void gt_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - if (x > 1) { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } else { - @IntVal({1}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; + void test_intrange_neq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { + if (x != y) { + @IntRange(from = 6, to = 10) + // :: error: (assignment.type.incompatible) + int a = x; + @IntRange(from = 1, to = 2) + // :: error: (assignment.type.incompatible) + int b = y; + } else { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 3, to = 5) int b = y; + } } - if (x > w) { - @IntVal({2}) int y = x; - @IntVal({1}) int z = w; - } else { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } - } - - void boolean_test(@IntVal({1, 2}) int x, @IntVal({1, 5, 6}) int w) { - @BoolVal({true}) boolean b = x >= 0; - @BoolVal({false}) boolean c = w == 3; - @BoolVal({true, false}) boolean d = x < w; - } - - void test_intrange_eq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { - if (x == y) { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 3, to = 5) int b = y; - } else { - @IntRange(from = 6, to = 10) - // :: error: (assignment.type.incompatible) - int a = x; - @IntRange(from = 1, to = 2) - // :: error: (assignment.type.incompatible) - int b = y; - } - } - - void test_intrange_eq2(@IntRange(from = 0, to = 10) int x, @IntRange(from = 0, to = 0) int y) { - if (x == y) { - @IntRange(from = 0, to = 0) int a = x; - @IntRange(from = 0, to = 0) int b = y; - } else { - @IntRange(from = 1, to = 10) int a = x; - @IntRange(from = 0, to = 0) int b = y; - } - } - - void test_intrange_eq3(@IntRange(from = 0, to = 10) int x, @IntVal(0) int y) { - if (x == y) { - @IntVal(0) int a = x; - @IntVal(0) int b = y; - } else { - @IntRange(from = 1, to = 10) int a = x; - @IntVal(0) int b = y; + void test_intrange_neq2( + @IntRange(from = 3, to = 10) int x, @IntRange(from = 10, to = 10) int y) { + if (x != y) { + @IntRange(from = 3, to = 9) int a = x; + @IntRange(from = 10, to = 10) int b = y; + } else { + @IntRange(from = 10, to = 10) int a = x; + @IntRange(from = 10, to = 10) int b = y; + } } - if (y != x) { - @IntRange(from = 1, to = 10) int a = x; - @IntVal(0) int b = y; - } - } - - void test_intrange_neq(@IntRange(from = 3, to = 10) int x, @IntRange(from = 1, to = 5) int y) { - if (x != y) { - @IntRange(from = 6, to = 10) - // :: error: (assignment.type.incompatible) - int a = x; - @IntRange(from = 1, to = 2) - // :: error: (assignment.type.incompatible) - int b = y; - } else { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 3, to = 5) int b = y; + void test_intrange_gt(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 20) int y) { + if (x > y) { + @IntRange(from = 6, to = 10) int a = x; + @IntRange(from = 5, to = 9) int b = y; + } else { + @IntRange(from = 0, to = 10) int a = x; + @IntRange(from = 5, to = 20) int b = y; + } } - } - - void test_intrange_neq2(@IntRange(from = 3, to = 10) int x, @IntRange(from = 10, to = 10) int y) { - if (x != y) { - @IntRange(from = 3, to = 9) int a = x; - @IntRange(from = 10, to = 10) int b = y; - } else { - @IntRange(from = 10, to = 10) int a = x; - @IntRange(from = 10, to = 10) int b = y; - } - } - - void test_intrange_gt(@IntRange(from = 0, to = 10) int x, @IntRange(from = 5, to = 20) int y) { - if (x > y) { - @IntRange(from = 6, to = 10) int a = x; - @IntRange(from = 5, to = 9) int b = y; - } else { - @IntRange(from = 0, to = 10) int a = x; - @IntRange(from = 5, to = 20) int b = y; - } - } - - void test_intrange_gt2(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x > y) { - @IntRange(from = 5, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; - - @IntRange(from = 5, to = 7) - // :: error: (assignment.type.incompatible) - int c = x; - @IntRange(from = 5, to = 7) - // :: error: (assignment.type.incompatible) - int d = y; - } else { - @IntRange(from = 5, to = 7) int a = x; - @IntRange(from = 5, to = 7) int b = y; + + void test_intrange_gt2(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x > y) { + @IntRange(from = 5, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + + @IntRange(from = 5, to = 7) + // :: error: (assignment.type.incompatible) + int c = x; + @IntRange(from = 5, to = 7) + // :: error: (assignment.type.incompatible) + int d = y; + } else { + @IntRange(from = 5, to = 7) int a = x; + @IntRange(from = 5, to = 7) int b = y; + } } - } - - void test_intrange_lte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x <= y) { - @IntRange(from = 0, to = 7) int a = x; - @IntRange(from = 2, to = 7) int b = y; - } else { - @IntRange(from = 3, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; + + void test_intrange_lte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x <= y) { + @IntRange(from = 0, to = 7) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } else { + @IntRange(from = 3, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } } - } - - void test_intrange_lte2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { - if (x <= y) { - @IntRange(from = 2, to = 7) int a = x; - @IntRange(from = 2, to = 10) int b = y; - } else { - @IntRange(from = 6, to = 7) int a = x; - @IntRange(from = 5, to = 6) int b = y; + + void test_intrange_lte2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { + if (x <= y) { + @IntRange(from = 2, to = 7) int a = x; + @IntRange(from = 2, to = 10) int b = y; + } else { + @IntRange(from = 6, to = 7) int a = x; + @IntRange(from = 5, to = 6) int b = y; + } } - } - - void test_intrange_lt(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x < y) { - @IntRange(from = 5, to = 6) int a = x; - @IntRange(from = 6, to = 7) int b = y; - } else { - @IntRange(from = 5, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; + + void test_intrange_lt(@IntRange(from = 5, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x < y) { + @IntRange(from = 5, to = 6) int a = x; + @IntRange(from = 6, to = 7) int b = y; + } else { + @IntRange(from = 5, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } } - } - - void test_intrange_lt2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { - if (x < y) { - @IntRange(from = 2, to = 7) int a = x; - @IntRange(from = 2, to = 10) int b = y; - } else { - @IntRange(from = 5, to = 7) int a = x; - @IntRange(from = 5, to = 7) int b = y; + + void test_intrange_lt2(@IntRange(from = 2, to = 7) int x, @IntRange(from = 5, to = 10) int y) { + if (x < y) { + @IntRange(from = 2, to = 7) int a = x; + @IntRange(from = 2, to = 10) int b = y; + } else { + @IntRange(from = 5, to = 7) int a = x; + @IntRange(from = 5, to = 7) int b = y; + } } - } - - void test_intrange_gte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { - if (x >= y) { - @IntRange(from = 2, to = 10) int a = x; - @IntRange(from = 2, to = 7) int b = y; - } else { - @IntRange(from = 0, to = 6) int a = x; - @IntRange(from = 2, to = 7) int b = y; + + void test_intrange_gte(@IntRange(from = 0, to = 10) int x, @IntRange(from = 2, to = 7) int y) { + if (x >= y) { + @IntRange(from = 2, to = 10) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } else { + @IntRange(from = 0, to = 6) int a = x; + @IntRange(from = 2, to = 7) int b = y; + } } - } - - void test_intrange_gte2(@IntRange(from = 3, to = 5) int x, @IntRange(from = 2, to = 6) int y) { - if (x >= y) { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 2, to = 6) int b = y; - } else { - @IntRange(from = 3, to = 5) int a = x; - @IntRange(from = 4, to = 6) int b = y; + + void test_intrange_gte2(@IntRange(from = 3, to = 5) int x, @IntRange(from = 2, to = 6) int y) { + if (x >= y) { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 2, to = 6) int b = y; + } else { + @IntRange(from = 3, to = 5) int a = x; + @IntRange(from = 4, to = 6) int b = y; + } } - } } diff --git a/framework/tests/value/Refinement2.java b/framework/tests/value/Refinement2.java index 8065c506912..2b2b25b4b32 100644 --- a/framework/tests/value/Refinement2.java +++ b/framework/tests/value/Refinement2.java @@ -2,124 +2,124 @@ public class Refinement2 { - void eq_test(int x, @IntVal({1, 5, 6}) int w) { - if (x == 1) { - // :: error: (assignment.type.incompatible) - @BottomVal int q = x; - - } else if (x == 2) { - } else { - return; + void eq_test(int x, @IntVal({1, 5, 6}) int w) { + if (x == 1) { + // :: error: (assignment.type.incompatible) + @BottomVal int q = x; + + } else if (x == 2) { + } else { + return; + } + + if (x != 1) { + // :: error: (assignment.type.incompatible) + @IntVal({1}) int y = x; + @IntVal({2}) int z = x; + } + + if (x == 1) { + + @IntVal({1}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int z = x; + } else { + @IntVal({2}) int y = x; + + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + } + + if (x == w) { + @IntVal({1}) int y = x; + @IntVal({1}) int z = w; + } else { + // These two assignments are illegal because one of x or w could be 1, + // while the other is not. So no refinement can be applied. + + // :: error: (assignment.type.incompatible) + @IntVal({2}) int y = x; + // :: error: (assignment.type.incompatible) + @IntVal({5, 6}) int z = w; + } } - if (x != 1) { - // :: error: (assignment.type.incompatible) - @IntVal({1}) int y = x; - @IntVal({2}) int z = x; + void testDeadCode(int x) { + if (x == 4 || x == 5) { + @IntVal({4, 5}) int a = x; + // :: error: (assignment.type.incompatible) + @BottomVal int a2 = x; + } + if (x == 4 && x == 5) { + // This is dead, so x should be bottom. + @IntVal({4, 5}) int a = x; + @BottomVal int a2 = x; + } + if (x != 1 || x != 2) { + return; + } + if (x != 2) { + @IntVal(1) int a = x; + } + + if (x == 3) { + // This is actually dead code, so x is treated as bottom. + @IntVal(3) int y = x; + @IntVal(33) int v = x; + @BottomVal int z = x; + } } - if (x == 1) { - - @IntVal({1}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int z = x; - } else { - @IntVal({2}) int y = x; - - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - } - - if (x == w) { - @IntVal({1}) int y = x; - @IntVal({1}) int z = w; - } else { - // These two assignments are illegal because one of x or w could be 1, - // while the other is not. So no refinement can be applied. - - // :: error: (assignment.type.incompatible) - @IntVal({2}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal({5, 6}) int z = w; - } - } - - void testDeadCode(int x) { - if (x == 4 || x == 5) { - @IntVal({4, 5}) int a = x; - // :: error: (assignment.type.incompatible) - @BottomVal int a2 = x; - } - if (x == 4 && x == 5) { - // This is dead, so x should be bottom. - @IntVal({4, 5}) int a = x; - @BottomVal int a2 = x; - } - if (x != 1 || x != 2) { - return; - } - if (x != 2) { - @IntVal(1) int a = x; - } - - if (x == 3) { - // This is actually dead code, so x is treated as bottom. - @IntVal(3) int y = x; - @IntVal(33) int v = x; - @BottomVal int z = x; - } - } - - void simpleNull(@IntVal({1, 2, 3}) Integer x) { - if (x == null) { - @BottomVal int y = x; - } - } - - void moreTests(@IntVal({1, 2, 3}) Integer x, Integer y) { - if (x == null) { - if (y == x) { - // x and y should be @BottomVal - @BottomVal int z1 = x; - @BottomVal int z2 = y; - } else { - // y should be @UnknownVal here. - // :: error: (assignment.type.incompatible) - @IntVal({1, 2, 3}) int z = y; - } - } - - if (x == null) { - if (y < x) { - // x should be @BottomVal - @BottomVal int z1 = x; - // This is dead code since the unboxing of x in the if statement - // will throw a NPE, so the type of y doesn't matter. - // @BottomVal int z2 = y; - } else { - // This is dead code, too. - } - } - } - - void testArrayLen(boolean flag) { - int[] a; - if (flag) { - a = new int[] {1}; - } else { - a = new int[] {1, 2}; + void simpleNull(@IntVal({1, 2, 3}) Integer x) { + if (x == null) { + @BottomVal int y = x; + } } - if (a.length != 1) { - int @ArrayLen(2) [] b = a; + void moreTests(@IntVal({1, 2, 3}) Integer x, Integer y) { + if (x == null) { + if (y == x) { + // x and y should be @BottomVal + @BottomVal int z1 = x; + @BottomVal int z2 = y; + } else { + // y should be @UnknownVal here. + // :: error: (assignment.type.incompatible) + @IntVal({1, 2, 3}) int z = y; + } + } + + if (x == null) { + if (y < x) { + // x should be @BottomVal + @BottomVal int z1 = x; + // This is dead code since the unboxing of x in the if statement + // will throw a NPE, so the type of y doesn't matter. + // @BottomVal int z2 = y; + } else { + // This is dead code, too. + } + } } - if (a.length == 3) { - // Dead code - int @ArrayLen(3) [] b = a; - int @ArrayLen(5) [] c = a; - int @BottomVal [] bot = a; + void testArrayLen(boolean flag) { + int[] a; + if (flag) { + a = new int[] {1}; + } else { + a = new int[] {1, 2}; + } + + if (a.length != 1) { + int @ArrayLen(2) [] b = a; + } + + if (a.length == 3) { + // Dead code + int @ArrayLen(3) [] b = a; + int @ArrayLen(5) [] c = a; + int @BottomVal [] bot = a; + } } - } } diff --git a/framework/tests/value/RegexMatching.java b/framework/tests/value/RegexMatching.java index e3c7fa84abe..58646a426e1 100644 --- a/framework/tests/value/RegexMatching.java +++ b/framework/tests/value/RegexMatching.java @@ -3,134 +3,134 @@ import org.checkerframework.common.value.qual.*; public class RegexMatching { - void stringConstants() { - @MatchesRegex("a*") String a = "a"; - @MatchesRegex("a*") String blank = ""; - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String b = "b"; - - // NOTE: these tests show that there are implicit anchors in the regular expressions - // used by @MatchesRegex. - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String ab = "ab"; - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String baa = "baa"; - - @MatchesRegex("a") String a1 = "a"; - // :: error: assignment.type.incompatible - @MatchesRegex("a") String blank1 = ""; - // :: error: assignment.type.incompatible - @MatchesRegex("a") String b1 = "b"; - - @MatchesRegex("\\s") String space = " "; - @MatchesRegex("\\s+") String severalSpaces = " "; - // :: error: assignment.type.incompatible - @MatchesRegex("\\s") String b2 = "b"; - - @MatchesRegex("[^abc]") String d = "d"; - @MatchesRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); - // :: error: assignment.type.incompatible - @MatchesRegex("[^abc]") String c = "c"; - } - - void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - @MatchesRegex("a*") String a = aaa; - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String a1 = aab; - - @MatchesRegex("a+") String a2 = aaa; - // :: error: assignment.type.incompatible - @MatchesRegex("a+") String a3 = aab; - } - - void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - @MatchesRegex({"a*", "b*"}) String a = aaa; - @MatchesRegex({"a*", "b*"}) String a1 = aab; - - // :: error: assignment.type.incompatible - @MatchesRegex({"aa", "b*"}) String a2 = aaa; - @MatchesRegex({"aa", "b*"}) String a3 = aab; - } - - void regexSubtypingConstant(@MatchesRegex({"a", "b"}) String ab) { - // :: error: assignment.type.incompatible - @MatchesRegex("a") String a = ab; - @MatchesRegex({"a", "b"}) String ab1 = ab; - @MatchesRegex({"a", "b", "c"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b", "c"}) String abc1 = ab; - } - - void regexSubtyping2(@MatchesRegex({"a*", "b*"}) String ab) { - // :: error: assignment.type.incompatible - @MatchesRegex("a*") String a = ab; - @MatchesRegex({"a*", "b*"}) String ab1 = ab; - @MatchesRegex({"a*", "b*", "c*"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a*") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*", "c*"}) String abc1 = ab; - } - - void lubRegexes( - @MatchesRegex({"a*"}) String astar, @MatchesRegex({"b*"}) String bstar, boolean b) { - String s; - if (b) { - s = astar; - } else { - s = bstar; + void stringConstants() { + @MatchesRegex("a*") String a = "a"; + @MatchesRegex("a*") String blank = ""; + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String b = "b"; + + // NOTE: these tests show that there are implicit anchors in the regular expressions + // used by @MatchesRegex. + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String ab = "ab"; + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String baa = "baa"; + + @MatchesRegex("a") String a1 = "a"; + // :: error: assignment.type.incompatible + @MatchesRegex("a") String blank1 = ""; + // :: error: assignment.type.incompatible + @MatchesRegex("a") String b1 = "b"; + + @MatchesRegex("\\s") String space = " "; + @MatchesRegex("\\s+") String severalSpaces = " "; + // :: error: assignment.type.incompatible + @MatchesRegex("\\s") String b2 = "b"; + + @MatchesRegex("[^abc]") String d = "d"; + @MatchesRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); + // :: error: assignment.type.incompatible + @MatchesRegex("[^abc]") String c = "c"; } - @MatchesRegex({"a*", "b*"}) String s1 = s; - // :: error: assignment.type.incompatible - @MatchesRegex({"a*"}) String s2 = s; - // :: error: assignment.type.incompatible - @MatchesRegex({"b*"}) String s3 = s; - } - - void lubRegexWithStringVal( - @MatchesRegex({"a*"}) String astar, @StringVal({"b"}) String bval, boolean b) { - String s; - if (b) { - s = astar; - } else { - s = bval; + + void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + @MatchesRegex("a*") String a = aaa; + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String a1 = aab; + + @MatchesRegex("a+") String a2 = aaa; + // :: error: assignment.type.incompatible + @MatchesRegex("a+") String a3 = aab; + } + + void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + @MatchesRegex({"a*", "b*"}) String a = aaa; + @MatchesRegex({"a*", "b*"}) String a1 = aab; + + // :: error: assignment.type.incompatible + @MatchesRegex({"aa", "b*"}) String a2 = aaa; + @MatchesRegex({"aa", "b*"}) String a3 = aab; + } + + void regexSubtypingConstant(@MatchesRegex({"a", "b"}) String ab) { + // :: error: assignment.type.incompatible + @MatchesRegex("a") String a = ab; + @MatchesRegex({"a", "b"}) String ab1 = ab; + @MatchesRegex({"a", "b", "c"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b", "c"}) String abc1 = ab; + } + + void regexSubtyping2(@MatchesRegex({"a*", "b*"}) String ab) { + // :: error: assignment.type.incompatible + @MatchesRegex("a*") String a = ab; + @MatchesRegex({"a*", "b*"}) String ab1 = ab; + @MatchesRegex({"a*", "b*", "c*"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a*") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*", "c*"}) String abc1 = ab; + } + + void lubRegexes( + @MatchesRegex({"a*"}) String astar, @MatchesRegex({"b*"}) String bstar, boolean b) { + String s; + if (b) { + s = astar; + } else { + s = bstar; + } + @MatchesRegex({"a*", "b*"}) String s1 = s; + // :: error: assignment.type.incompatible + @MatchesRegex({"a*"}) String s2 = s; + // :: error: assignment.type.incompatible + @MatchesRegex({"b*"}) String s3 = s; + } + + void lubRegexWithStringVal( + @MatchesRegex({"a*"}) String astar, @StringVal({"b"}) String bval, boolean b) { + String s; + if (b) { + s = astar; + } else { + s = bval; + } + // NOTE: This depends on the internal implementation. Semantically identical code like this + // yields an error: + // @MatchesRegex({"a*", "b"}) String s0 = s; + @MatchesRegex({"a*", "\\Qb\\E"}) String s1 = s; + // :: error: assignment.type.incompatible + @MatchesRegex({"a*"}) String s2 = s; + // :: error: assignment.type.incompatible + @StringVal({"b"}) String s3 = s; + // :: error: assignment.type.incompatible + @MatchesRegex("b") String s4 = s; + // :: error: assignment.type.incompatible + @MatchesRegex("^b$") String s5 = s; + } + + void stringToRegex1(@StringVal({"(a)"}) String a) { + // :: error: assignment.type.incompatible + @MatchesRegex("(a)") String a2 = a; + } + + void stringToRegex2(@StringVal({"a"}) String a) { + @MatchesRegex("(a)") String a2 = a; + } + + void stringToRegex3(@StringVal({"a"}) String a) { + @MatchesRegex("^a$") String a2 = a; + } + + void regexToString(@MatchesRegex("^a$") String a) { + // TODO: This is a false positive. In the future, eliminate it. + // :: error: assignment.type.incompatible + @StringVal({"a"}) String a2 = a; } - // NOTE: This depends on the internal implementation. Semantically identical code like this - // yields an error: - // @MatchesRegex({"a*", "b"}) String s0 = s; - @MatchesRegex({"a*", "\\Qb\\E"}) String s1 = s; - // :: error: assignment.type.incompatible - @MatchesRegex({"a*"}) String s2 = s; - // :: error: assignment.type.incompatible - @StringVal({"b"}) String s3 = s; - // :: error: assignment.type.incompatible - @MatchesRegex("b") String s4 = s; - // :: error: assignment.type.incompatible - @MatchesRegex("^b$") String s5 = s; - } - - void stringToRegex1(@StringVal({"(a)"}) String a) { - // :: error: assignment.type.incompatible - @MatchesRegex("(a)") String a2 = a; - } - - void stringToRegex2(@StringVal({"a"}) String a) { - @MatchesRegex("(a)") String a2 = a; - } - - void stringToRegex3(@StringVal({"a"}) String a) { - @MatchesRegex("^a$") String a2 = a; - } - - void regexToString(@MatchesRegex("^a$") String a) { - // TODO: This is a false positive. In the future, eliminate it. - // :: error: assignment.type.incompatible - @StringVal({"a"}) String a2 = a; - } } diff --git a/framework/tests/value/RegexNonMatching.java b/framework/tests/value/RegexNonMatching.java index dd9d2435537..05d1dd5c4a9 100644 --- a/framework/tests/value/RegexNonMatching.java +++ b/framework/tests/value/RegexNonMatching.java @@ -4,246 +4,248 @@ public class RegexNonMatching { - void stringConstants() { - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a*") String aStar = "a"; - // :: error: assignment.type.incompatible - aStar = ""; - aStar = "b"; - @DoesNotMatchRegex({"a+"}) String aPlus = "b"; - // @DoesNotMatchRegex("a+") String aPlus = "b"; - aStar = "ab"; - aStar = "baa"; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a") String a1 = "a"; - @DoesNotMatchRegex("a") String blank1 = ""; - @DoesNotMatchRegex("a") String b1 = "b"; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("\\s") String space = " "; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("\\s+") String severalSpaces = " "; - // TODO: this should work - @DoesNotMatchRegex("\\s") String b2 = "b"; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("[^abc]") String d = "d"; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); - // TODO: this should work, shouldn't it? - @DoesNotMatchRegex("[^abc]") String c = "c"; - } - - void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a*") String a = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a*") String a1 = aab; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a+") String a2 = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("a+") String a3 = aab; - } - - void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*"}) String a = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*"}) String a1 = aab; - - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"aa", "b*"}) String a2 = aaa; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"aa", "b*"}) String a3 = aab; - } - - void regexSubtypingConstant(@DoesNotMatchRegex({"a", "b"}) String ab) { - @DoesNotMatchRegex("a") String a = ab; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("c") String c = ab; - @DoesNotMatchRegex({"a", "b"}) String ab1 = ab; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a", "b", "c"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a", "b", "c"}) String abc1 = ab; - } - - void regexSubtyping2(@DoesNotMatchRegex({"a*", "b*"}) String ab) { - @DoesNotMatchRegex("a*") String a = ab; - @DoesNotMatchRegex({"a*", "b*"}) String ab1 = ab; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*", "c*"}) String abc = ab; - // :: error: assignment.type.incompatible - @StringVal("a*") String a1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*"}) String ab2 = ab; - // :: error: assignment.type.incompatible - @StringVal({"a*", "b*", "c*"}) String abc1 = ab; - // :: error: assignment.type.incompatible - @StringVal({"c*"}) String c = ab; - } - - void lubRegexes( - @DoesNotMatchRegex({"a*"}) String astar, @DoesNotMatchRegex({"b*"}) String bstar, boolean b) { - String s; - if (b) { - s = astar; - } else { - s = bstar; + void stringConstants() { + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a*") String aStar = "a"; + // :: error: assignment.type.incompatible + aStar = ""; + aStar = "b"; + @DoesNotMatchRegex({"a+"}) String aPlus = "b"; + // @DoesNotMatchRegex("a+") String aPlus = "b"; + aStar = "ab"; + aStar = "baa"; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a") String a1 = "a"; + @DoesNotMatchRegex("a") String blank1 = ""; + @DoesNotMatchRegex("a") String b1 = "b"; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("\\s") String space = " "; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("\\s+") String severalSpaces = " "; + // TODO: this should work + @DoesNotMatchRegex("\\s") String b2 = "b"; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("[^abc]") String d = "d"; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("[^abc]") String d1 = String.valueOf(new char[] {'d'}); + // TODO: this should work, shouldn't it? + @DoesNotMatchRegex("[^abc]") String c = "c"; + } + + void severalString(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a*") String a = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a*") String a1 = aab; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a+") String a2 = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("a+") String a3 = aab; + } + + void multipleRegexes(@StringVal({"a", "aa"}) String aaa, @StringVal({"aa", "b"}) String aab) { + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*"}) String a = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*"}) String a1 = aab; + + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"aa", "b*"}) String a2 = aaa; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"aa", "b*"}) String a3 = aab; + } + + void regexSubtypingConstant(@DoesNotMatchRegex({"a", "b"}) String ab) { + @DoesNotMatchRegex("a") String a = ab; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("c") String c = ab; + @DoesNotMatchRegex({"a", "b"}) String ab1 = ab; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a", "b", "c"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a", "b", "c"}) String abc1 = ab; + } + + void regexSubtyping2(@DoesNotMatchRegex({"a*", "b*"}) String ab) { + @DoesNotMatchRegex("a*") String a = ab; + @DoesNotMatchRegex({"a*", "b*"}) String ab1 = ab; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*", "c*"}) String abc = ab; + // :: error: assignment.type.incompatible + @StringVal("a*") String a1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*"}) String ab2 = ab; + // :: error: assignment.type.incompatible + @StringVal({"a*", "b*", "c*"}) String abc1 = ab; + // :: error: assignment.type.incompatible + @StringVal({"c*"}) String c = ab; + } + + void lubRegexes( + @DoesNotMatchRegex({"a*"}) String astar, + @DoesNotMatchRegex({"b*"}) String bstar, + boolean b) { + String s; + if (b) { + s = astar; + } else { + s = bstar; + } + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*", "b*"}) String s1 = s; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"a*"}) String s2 = s; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({"b*"}) String s3 = s; + s3 = s1; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({}) String s4 = s; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex({".*"}) String s5 = s; + @UnknownVal() String s6 = s; + } + + void lubRegexWithStringVal( + @DoesNotMatchRegex({"a*"}) String astar, + @StringVal({"a", "aa", "b", "bb", "c", "cc"}) String dnmsNone, + @StringVal({"aa", "b", "bb", "c", "cc"}) String dnmsA, + @StringVal({"a", "aa", "bb", "c", "cc"}) String dnmsB, + @StringVal({"a", "aa", "b", "bb", "cc"}) String dnmsC, + @StringVal({"aa", "bb", "c", "cc"}) String dnmsAB, + @StringVal({"aa", "b", "bb", "cc"}) String dnmsAC, + @StringVal({"a", "aa", "bb", "cc"}) String dnmsBC, + @StringVal({"aa", "bb", "cc"}) String dnmsABC) { + + @DoesNotMatchRegex({}) String dnmNone; + @DoesNotMatchRegex({"a"}) String dnmA; + @DoesNotMatchRegex({"b"}) String dnmB; + @DoesNotMatchRegex({"c"}) String dnmC; + @DoesNotMatchRegex({"a", "b"}) String dnmAB; + @DoesNotMatchRegex({"a", "c"}) String dnmAC; + @DoesNotMatchRegex({"b", "c"}) String dnmBC; + @DoesNotMatchRegex({"a", "b", "c"}) String dnmABC; + + dnmNone = dnmsNone; + // :: error: assignment.type.incompatible + dnmA = dnmsNone; + // :: error: assignment.type.incompatible + dnmB = dnmsNone; + // :: error: assignment.type.incompatible + dnmC = dnmsNone; + // :: error: assignment.type.incompatible + dnmAB = dnmsNone; + // :: error: assignment.type.incompatible + dnmAC = dnmsNone; + // :: error: assignment.type.incompatible + dnmBC = dnmsNone; + // :: error: assignment.type.incompatible + dnmABC = dnmsNone; + + dnmNone = dnmsA; + dnmA = dnmsA; + // :: error: assignment.type.incompatible + dnmB = dnmsA; + // :: error: assignment.type.incompatible + dnmC = dnmsA; + // :: error: assignment.type.incompatible + dnmAB = dnmsA; + // :: error: assignment.type.incompatible + dnmAC = dnmsA; + // :: error: assignment.type.incompatible + dnmBC = dnmsA; + // :: error: assignment.type.incompatible + dnmABC = dnmsA; + + dnmNone = dnmsB; + // :: error: assignment.type.incompatible + dnmA = dnmsB; + dnmB = dnmsB; + // :: error: assignment.type.incompatible + dnmC = dnmsB; + // :: error: assignment.type.incompatible + dnmAB = dnmsB; + // :: error: assignment.type.incompatible + dnmAC = dnmsB; + // :: error: assignment.type.incompatible + dnmBC = dnmsB; + // :: error: assignment.type.incompatible + dnmABC = dnmsB; + + dnmNone = dnmsC; + // :: error: assignment.type.incompatible + dnmA = dnmsC; + // :: error: assignment.type.incompatible + dnmB = dnmsC; + dnmC = dnmsC; + // :: error: assignment.type.incompatible + dnmAB = dnmsC; + // :: error: assignment.type.incompatible + dnmAC = dnmsC; + // :: error: assignment.type.incompatible + dnmBC = dnmsC; + // :: error: assignment.type.incompatible + dnmABC = dnmsC; + + dnmNone = dnmsAC; + dnmA = dnmsAC; + // :: error: assignment.type.incompatible + dnmB = dnmsAC; + dnmC = dnmsAC; + // :: error: assignment.type.incompatible + dnmAB = dnmsAC; + dnmAC = dnmsAC; + // :: error: assignment.type.incompatible + dnmBC = dnmsAC; + // :: error: assignment.type.incompatible + dnmABC = dnmsAC; + + dnmNone = dnmsAB; + dnmA = dnmsAB; + dnmB = dnmsAB; + // :: error: assignment.type.incompatible + dnmC = dnmsAB; + dnmAB = dnmsAB; + // :: error: assignment.type.incompatible + dnmAC = dnmsAB; + // :: error: assignment.type.incompatible + dnmBC = dnmsAB; + // :: error: assignment.type.incompatible + dnmABC = dnmsAB; + + dnmNone = dnmsBC; + // :: error: assignment.type.incompatible + dnmA = dnmsBC; + dnmB = dnmsBC; + dnmC = dnmsBC; + // :: error: assignment.type.incompatible + dnmAB = dnmsBC; + // :: error: assignment.type.incompatible + dnmAC = dnmsBC; + dnmBC = dnmsBC; + // :: error: assignment.type.incompatible + dnmABC = dnmsBC; + + dnmNone = dnmsABC; + dnmA = dnmsABC; + dnmB = dnmsABC; + dnmC = dnmsABC; + dnmAB = dnmsABC; + dnmAC = dnmsABC; + dnmBC = dnmsABC; + dnmABC = dnmsABC; + } + + void stringToRegex1(@StringVal({"(a)"}) String a) { + @DoesNotMatchRegex("(a)") String a2 = a; + // :: error: assignment.type.incompatible + @DoesNotMatchRegex("\\(a\\)") String a3 = a; } - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*", "b*"}) String s1 = s; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"a*"}) String s2 = s; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({"b*"}) String s3 = s; - s3 = s1; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({}) String s4 = s; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex({".*"}) String s5 = s; - @UnknownVal() String s6 = s; - } - - void lubRegexWithStringVal( - @DoesNotMatchRegex({"a*"}) String astar, - @StringVal({"a", "aa", "b", "bb", "c", "cc"}) String dnmsNone, - @StringVal({"aa", "b", "bb", "c", "cc"}) String dnmsA, - @StringVal({"a", "aa", "bb", "c", "cc"}) String dnmsB, - @StringVal({"a", "aa", "b", "bb", "cc"}) String dnmsC, - @StringVal({"aa", "bb", "c", "cc"}) String dnmsAB, - @StringVal({"aa", "b", "bb", "cc"}) String dnmsAC, - @StringVal({"a", "aa", "bb", "cc"}) String dnmsBC, - @StringVal({"aa", "bb", "cc"}) String dnmsABC) { - - @DoesNotMatchRegex({}) String dnmNone; - @DoesNotMatchRegex({"a"}) String dnmA; - @DoesNotMatchRegex({"b"}) String dnmB; - @DoesNotMatchRegex({"c"}) String dnmC; - @DoesNotMatchRegex({"a", "b"}) String dnmAB; - @DoesNotMatchRegex({"a", "c"}) String dnmAC; - @DoesNotMatchRegex({"b", "c"}) String dnmBC; - @DoesNotMatchRegex({"a", "b", "c"}) String dnmABC; - - dnmNone = dnmsNone; - // :: error: assignment.type.incompatible - dnmA = dnmsNone; - // :: error: assignment.type.incompatible - dnmB = dnmsNone; - // :: error: assignment.type.incompatible - dnmC = dnmsNone; - // :: error: assignment.type.incompatible - dnmAB = dnmsNone; - // :: error: assignment.type.incompatible - dnmAC = dnmsNone; - // :: error: assignment.type.incompatible - dnmBC = dnmsNone; - // :: error: assignment.type.incompatible - dnmABC = dnmsNone; - - dnmNone = dnmsA; - dnmA = dnmsA; - // :: error: assignment.type.incompatible - dnmB = dnmsA; - // :: error: assignment.type.incompatible - dnmC = dnmsA; - // :: error: assignment.type.incompatible - dnmAB = dnmsA; - // :: error: assignment.type.incompatible - dnmAC = dnmsA; - // :: error: assignment.type.incompatible - dnmBC = dnmsA; - // :: error: assignment.type.incompatible - dnmABC = dnmsA; - - dnmNone = dnmsB; - // :: error: assignment.type.incompatible - dnmA = dnmsB; - dnmB = dnmsB; - // :: error: assignment.type.incompatible - dnmC = dnmsB; - // :: error: assignment.type.incompatible - dnmAB = dnmsB; - // :: error: assignment.type.incompatible - dnmAC = dnmsB; - // :: error: assignment.type.incompatible - dnmBC = dnmsB; - // :: error: assignment.type.incompatible - dnmABC = dnmsB; - - dnmNone = dnmsC; - // :: error: assignment.type.incompatible - dnmA = dnmsC; - // :: error: assignment.type.incompatible - dnmB = dnmsC; - dnmC = dnmsC; - // :: error: assignment.type.incompatible - dnmAB = dnmsC; - // :: error: assignment.type.incompatible - dnmAC = dnmsC; - // :: error: assignment.type.incompatible - dnmBC = dnmsC; - // :: error: assignment.type.incompatible - dnmABC = dnmsC; - - dnmNone = dnmsAC; - dnmA = dnmsAC; - // :: error: assignment.type.incompatible - dnmB = dnmsAC; - dnmC = dnmsAC; - // :: error: assignment.type.incompatible - dnmAB = dnmsAC; - dnmAC = dnmsAC; - // :: error: assignment.type.incompatible - dnmBC = dnmsAC; - // :: error: assignment.type.incompatible - dnmABC = dnmsAC; - - dnmNone = dnmsAB; - dnmA = dnmsAB; - dnmB = dnmsAB; - // :: error: assignment.type.incompatible - dnmC = dnmsAB; - dnmAB = dnmsAB; - // :: error: assignment.type.incompatible - dnmAC = dnmsAB; - // :: error: assignment.type.incompatible - dnmBC = dnmsAB; - // :: error: assignment.type.incompatible - dnmABC = dnmsAB; - - dnmNone = dnmsBC; - // :: error: assignment.type.incompatible - dnmA = dnmsBC; - dnmB = dnmsBC; - dnmC = dnmsBC; - // :: error: assignment.type.incompatible - dnmAB = dnmsBC; - // :: error: assignment.type.incompatible - dnmAC = dnmsBC; - dnmBC = dnmsBC; - // :: error: assignment.type.incompatible - dnmABC = dnmsBC; - - dnmNone = dnmsABC; - dnmA = dnmsABC; - dnmB = dnmsABC; - dnmC = dnmsABC; - dnmAB = dnmsABC; - dnmAC = dnmsABC; - dnmBC = dnmsABC; - dnmABC = dnmsABC; - } - - void stringToRegex1(@StringVal({"(a)"}) String a) { - @DoesNotMatchRegex("(a)") String a2 = a; - // :: error: assignment.type.incompatible - @DoesNotMatchRegex("\\(a\\)") String a3 = a; - } } diff --git a/framework/tests/value/RegexPatternSyntaxException.java b/framework/tests/value/RegexPatternSyntaxException.java index 9f279ae0745..35b73ee39ee 100644 --- a/framework/tests/value/RegexPatternSyntaxException.java +++ b/framework/tests/value/RegexPatternSyntaxException.java @@ -3,9 +3,9 @@ import org.checkerframework.common.value.qual.*; public class RegexPatternSyntaxException { - // :: warning: (invalid.matches.regex) - void stringConstants1(@MatchesRegex("(a*") String a) {} + // :: warning: (invalid.matches.regex) + void stringConstants1(@MatchesRegex("(a*") String a) {} - // :: warning: (invalid.doesnotmatch.regex) - void stringConstants2(@DoesNotMatchRegex("(a*") String a) {} + // :: warning: (invalid.doesnotmatch.regex) + void stringConstants2(@DoesNotMatchRegex("(a*") String a) {} } diff --git a/framework/tests/value/RepeatMinLenIf.java b/framework/tests/value/RepeatMinLenIf.java index af9f2f89da8..0dcb2213ba1 100644 --- a/framework/tests/value/RepeatMinLenIf.java +++ b/framework/tests/value/RepeatMinLenIf.java @@ -2,56 +2,56 @@ public class RepeatMinLenIf { - protected String a; - protected String b; - protected String c; + protected String a; + protected String b; + protected String c; - public boolean func1() { - a = "checker"; - c = "framework"; - b = "opensource"; - return true; - } + public boolean func1() { + a = "checker"; + c = "framework"; + b = "opensource"; + return true; + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean client2() { - return withcondpostconditionfunc1(); - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean client2() { + return withcondpostconditionfunc1(); + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean withcondpostconditionsfunc1() { - a = "checker"; - c = "framework"; - b = "opensource"; - return true; - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean withcondpostconditionsfunc1() { + a = "checker"; + c = "framework"; + b = "opensource"; + return true; + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) - public boolean withcondpostconditionfunc1() { - a = "checker"; - c = "framework"; - b = "opensource"; - return true; - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 4, result = true) + public boolean withcondpostconditionfunc1() { + a = "checker"; + c = "framework"; + b = "opensource"; + return true; + } } diff --git a/framework/tests/value/RepeatMinLenIfWithError.java b/framework/tests/value/RepeatMinLenIfWithError.java index e189875df6f..62c392ec06d 100644 --- a/framework/tests/value/RepeatMinLenIfWithError.java +++ b/framework/tests/value/RepeatMinLenIfWithError.java @@ -2,58 +2,58 @@ public class RepeatMinLenIfWithError { - protected String a; - protected String b; - protected String c; + protected String a; + protected String b; + protected String c; - public boolean func1() { - a = "checker"; - c = "framework"; - b = "hello"; - return true; - } + public boolean func1() { + a = "checker"; + c = "framework"; + b = "hello"; + return true; + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean client1() { - return withcondpostconditionsfunc1(); - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean client1() { + return withcondpostconditionsfunc1(); + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean client2() { - return withcondpostconditionfunc1(); - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean client2() { + return withcondpostconditionfunc1(); + } - @EnsuresMinLenIf( - expression = {"a", "b"}, - targetValue = 6, - result = true) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean withcondpostconditionsfunc1() { - a = "checker"; - c = "framework"; - b = "hello"; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresMinLenIf( + expression = {"a", "b"}, + targetValue = 6, + result = true) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean withcondpostconditionsfunc1() { + a = "checker"; + c = "framework"; + b = "hello"; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } - @EnsuresMinLenIf.List({ - @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), - @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) - }) - @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) - public boolean withcondpostconditionfunc1() { - a = "checker"; - c = "framework"; - b = "hello"; // condition not satisfied here - // :: error: (contracts.conditional.postcondition.not.satisfied) - return true; - } + @EnsuresMinLenIf.List({ + @EnsuresMinLenIf(expression = "a", targetValue = 6, result = true), + @EnsuresMinLenIf(expression = "b", targetValue = 6, result = true) + }) + @EnsuresMinLenIf(expression = "c", targetValue = 6, result = true) + public boolean withcondpostconditionfunc1() { + a = "checker"; + c = "framework"; + b = "hello"; // condition not satisfied here + // :: error: (contracts.conditional.postcondition.not.satisfied) + return true; + } } diff --git a/framework/tests/value/Repo.java b/framework/tests/value/Repo.java index f4adf00c3cf..8caa5f2a17b 100644 --- a/framework/tests/value/Repo.java +++ b/framework/tests/value/Repo.java @@ -1,15 +1,16 @@ -import java.util.BitSet; import org.checkerframework.common.value.qual.*; +import java.util.BitSet; + public class Repo { - private BitSet bitmap; - boolean flag = true; + private BitSet bitmap; + boolean flag = true; - void testLoop() { - for (int i = 0; i < 20; i++) { - // :: error: (assignment.type.incompatible) - @IntVal(0) int x = i; - int j = flag ? i : 3; + void testLoop() { + for (int i = 0; i < 20; i++) { + // :: error: (assignment.type.incompatible) + @IntVal(0) int x = i; + int j = flag ? i : 3; + } } - } } diff --git a/framework/tests/value/SplitAssignments.java b/framework/tests/value/SplitAssignments.java index 1aba24fd946..55498f5da19 100644 --- a/framework/tests/value/SplitAssignments.java +++ b/framework/tests/value/SplitAssignments.java @@ -1,19 +1,19 @@ import org.checkerframework.common.value.qual.*; public class SplitAssignments { - void foo(@IntRange(from = 5, to = 200) int x) { - int z; - if ((z = x) == 5) { - @IntRange(from = 5, to = 5) int w = x; - @IntRange(from = 5, to = 5) int q = z; + void foo(@IntRange(from = 5, to = 200) int x) { + int z; + if ((z = x) == 5) { + @IntRange(from = 5, to = 5) int w = x; + @IntRange(from = 5, to = 5) int q = z; + } } - } - void bar(@IntVal({1, 2}) int x) { - int z; - if ((z = x) == 1) { - @IntVal(1) int w = x; - @IntVal(1) int q = z; + void bar(@IntVal({1, 2}) int x) { + int z; + if ((z = x) == 1) { + @IntVal(1) int w = x; + @IntVal(1) int q = z; + } } - } } diff --git a/framework/tests/value/StartsEndsWith.java b/framework/tests/value/StartsEndsWith.java index 117b07d8065..85659b9261b 100644 --- a/framework/tests/value/StartsEndsWith.java +++ b/framework/tests/value/StartsEndsWith.java @@ -6,57 +6,57 @@ public class StartsEndsWith { - void refineStartsWith(String str) { - if (str.startsWith("prefix")) { - @MinLen(6) String s6 = str; - // :: error: (assignment.type.incompatible) - @MinLen(7) String s7 = str; - } else { - // :: error: (assignment.type.incompatible) - @MinLen(6) String s6 = str; + void refineStartsWith(String str) { + if (str.startsWith("prefix")) { + @MinLen(6) String s6 = str; + // :: error: (assignment.type.incompatible) + @MinLen(7) String s7 = str; + } else { + // :: error: (assignment.type.incompatible) + @MinLen(6) String s6 = str; + } } - } - - void refineEndsWith(String str) { - if (str.endsWith("suffix")) { - @MinLen(6) String s6 = str; - // :: error: (assignment.type.incompatible) - @MinLen(7) String s7 = str; - } else { - // :: error: (assignment.type.incompatible) - @MinLen(6) String s6 = str; + + void refineEndsWith(String str) { + if (str.endsWith("suffix")) { + @MinLen(6) String s6 = str; + // :: error: (assignment.type.incompatible) + @MinLen(7) String s7 = str; + } else { + // :: error: (assignment.type.incompatible) + @MinLen(6) String s6 = str; + } } - } - void refineStartsEndsWith(String str) { - if (str.startsWith("longprefix") && str.endsWith("prefix")) { - @MinLen(10) String s10 = str; - // :: error: (assignment.type.incompatible) - @MinLen(11) String s11 = str; + void refineStartsEndsWith(String str) { + if (str.startsWith("longprefix") && str.endsWith("prefix")) { + @MinLen(10) String s10 = str; + // :: error: (assignment.type.incompatible) + @MinLen(11) String s11 = str; + } } - } - void refineStartsArrayLen(String str, @ArrayLen(10) String prefix) { - if (str.startsWith(prefix)) { - @MinLen(10) String sg10 = str; - // :: error: (assignment.type.incompatible) - @ArrayLen(10) String s10 = str; + void refineStartsArrayLen(String str, @ArrayLen(10) String prefix) { + if (str.startsWith(prefix)) { + @MinLen(10) String sg10 = str; + // :: error: (assignment.type.incompatible) + @ArrayLen(10) String s10 = str; + } } - } - void noRefinement(@ArrayLen(10) String str) { - if (str.startsWith("x")) { - @ArrayLen(10) String s10 = str; + void noRefinement(@ArrayLen(10) String str) { + if (str.startsWith("x")) { + @ArrayLen(10) String s10 = str; + } } - } - void refineStartsStaticFinal(String str) { - if (str.startsWith(StartsEndsWithExternal.staticFinalField)) { - @MinLen(3) String s3 = str; + void refineStartsStaticFinal(String str) { + if (str.startsWith(StartsEndsWithExternal.staticFinalField)) { + @MinLen(3) String s3 = str; + } } - } } class StartsEndsWithExternal { - public static final String staticFinalField = "str"; + public static final String staticFinalField = "str"; } diff --git a/framework/tests/value/StaticExTest.java b/framework/tests/value/StaticExTest.java index dd1b7cd0a2d..6180eadca10 100644 --- a/framework/tests/value/StaticExTest.java +++ b/framework/tests/value/StaticExTest.java @@ -1,45 +1,45 @@ import org.checkerframework.common.value.qual.*; public class StaticExTest { - boolean flag; + boolean flag; - void test1() { - String s = "helloworlod"; - @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; - @IntVal({5, 0, 9}) int start = flag ? 9 : flag ? 5 : 0; - // flag?1:flag?6: - @IntVal({-1, 8, 9, 2, 4, 6}) int result = s.indexOf(subString, start); - } + void test1() { + String s = "helloworlod"; + @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; + @IntVal({5, 0, 9}) int start = flag ? 9 : flag ? 5 : 0; + // flag?1:flag?6: + @IntVal({-1, 8, 9, 2, 4, 6}) int result = s.indexOf(subString, start); + } - void test2() { - String s = flag ? "helloworlod" : "lololxxolxxxol"; - @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; - @IntVal({0, 9}) int start = flag ? 9 : 0; - // flag?1:flag?6: - @IntVal({-1, 0, 1, 2, 4, 9, 12, 13}) int result3 = s.indexOf(subString, start); - } + void test2() { + String s = flag ? "helloworlod" : "lololxxolxxxol"; + @StringVal({"o", "l"}) String subString = flag ? "o" : "l"; + @IntVal({0, 9}) int start = flag ? 9 : 0; + // flag?1:flag?6: + @IntVal({-1, 0, 1, 2, 4, 9, 12, 13}) int result3 = s.indexOf(subString, start); + } - void test3() { - @IntVal({0, 1}) int offset = flag ? 0 : 1; - char[] data = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; - @IntVal({5, 6}) int charCount = flag ? 5 : 6; - @StringVal({"hello", "ellob", "hellob", "elloby"}) String s = new String(data, offset, charCount); - } + void test3() { + @IntVal({0, 1}) int offset = flag ? 0 : 1; + char[] data = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; + @IntVal({5, 6}) int charCount = flag ? 5 : 6; + @StringVal({"hello", "ellob", "hellob", "elloby"}) String s = new String(data, offset, charCount); + } - void test4() { - @IntVal({0, 1}) int offset = flag ? 0 : 1; - char[] data1 = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; - char[] data2 = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; - char @StringVal({"hellobyeto", "abcdefghij"}) [] data = flag ? data1 : data2; - @IntVal({5, 6}) int charCount = flag ? 5 : 6; - @StringVal({"hello", "ellob", "hellob", "elloby", "abcde", "bcdef", "abcdef", "bcdefg"}) String s = new String(data, offset, charCount); - } + void test4() { + @IntVal({0, 1}) int offset = flag ? 0 : 1; + char[] data1 = {'h', 'e', 'l', 'l', 'o', 'b', 'y', 'e', 't', 'o'}; + char[] data2 = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + char @StringVal({"hellobyeto", "abcdefghij"}) [] data = flag ? data1 : data2; + @IntVal({5, 6}) int charCount = flag ? 5 : 6; + @StringVal({"hello", "ellob", "hellob", "elloby", "abcde", "bcdef", "abcdef", "bcdefg"}) String s = new String(data, offset, charCount); + } - static byte[] b = new byte[0]; + static byte[] b = new byte[0]; - void constructorsArrays() { - char @ArrayLen(100) [] c = new char[100]; - String s = new String(c); - new String(b); - } + void constructorsArrays() { + char @ArrayLen(100) [] c = new char[100]; + String s = new String(c); + new String(b); + } } diff --git a/framework/tests/value/StaticallyExecutableWarnings.java b/framework/tests/value/StaticallyExecutableWarnings.java index cdf662be4ac..d149d08a2a0 100644 --- a/framework/tests/value/StaticallyExecutableWarnings.java +++ b/framework/tests/value/StaticallyExecutableWarnings.java @@ -3,43 +3,43 @@ public class StaticallyExecutableWarnings { - @StaticallyExecutable - // :: warning: (statically.executable.not.pure) - static int addNotPure(int a, int b) { - return a + b; - } + @StaticallyExecutable + // :: warning: (statically.executable.not.pure) + static int addNotPure(int a, int b) { + return a + b; + } - @StaticallyExecutable - @Pure - static int add(Integer a, Integer b) { - return a + b; - } + @StaticallyExecutable + @Pure + static int add(Integer a, Integer b) { + return a + b; + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.parameter.type) - int receiverCannotBeConstant(int a, int b) { - return a + b; - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.parameter.type) + int receiverCannotBeConstant(int a, int b) { + return a + b; + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.parameter.type) - int explicitReceiverCannotBeConstant(StaticallyExecutableWarnings this, int a, int b) { - return a + b; - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.parameter.type) + int explicitReceiverCannotBeConstant(StaticallyExecutableWarnings this, int a, int b) { + return a + b; + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.return.type) - static StaticallyExecutableWarnings returnTypeCannotBeConstant(int a, int b) { - return new StaticallyExecutableWarnings(); - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.return.type) + static StaticallyExecutableWarnings returnTypeCannotBeConstant(int a, int b) { + return new StaticallyExecutableWarnings(); + } - @StaticallyExecutable - @Pure - // :: error: (statically.executable.nonconstant.parameter.type) - static int parameterCannotBeConstant(int a, int b, Object o) { - return a + b; - } + @StaticallyExecutable + @Pure + // :: error: (statically.executable.nonconstant.parameter.type) + static int parameterCannotBeConstant(int a, int b, Object o) { + return a + b; + } } diff --git a/framework/tests/value/StringConcats.java b/framework/tests/value/StringConcats.java index 7c50e316c5c..7187f1914f2 100644 --- a/framework/tests/value/StringConcats.java +++ b/framework/tests/value/StringConcats.java @@ -3,44 +3,44 @@ import org.checkerframework.common.value.qual.StringVal; public class StringConcats { - void stringConcat() { - @StringVal("helloa11.01.020truenull2626") String everything = "hello" + 'a' + 1 + 1.0 + 1.0f + 20L + true + null + 0x1a + 0b11010; + void stringConcat() { + @StringVal("helloa11.01.020truenull2626") String everything = "hello" + 'a' + 1 + 1.0 + 1.0f + 20L + true + null + 0x1a + 0b11010; - @StringVal("true") String bool = "" + true; - @StringVal("null") String nullV = "" + null; - // :: error: (assignment.type.incompatible) - @BottomVal String bottom = "" + null; - @StringVal("1") String intL = "" + 1; - @StringVal("$") String charL = "" + '$'; - @StringVal("1.0") String doubleDefault = "" + 1.0; - @StringVal("1.0") String doubleL = "" + 1.0d; - @StringVal("26") String hexVal = "" + 0x1a; - @StringVal("26") String binaryVal = "" + 0b11010; - @StringVal("12.3") String floatVal = "" + 12.3f; - @StringVal("123.0") String science = "" + 1.23e2; - } + @StringVal("true") String bool = "" + true; + @StringVal("null") String nullV = "" + null; + // :: error: (assignment.type.incompatible) + @BottomVal String bottom = "" + null; + @StringVal("1") String intL = "" + 1; + @StringVal("$") String charL = "" + '$'; + @StringVal("1.0") String doubleDefault = "" + 1.0; + @StringVal("1.0") String doubleL = "" + 1.0d; + @StringVal("26") String hexVal = "" + 0x1a; + @StringVal("26") String binaryVal = "" + 0b11010; + @StringVal("12.3") String floatVal = "" + 12.3f; + @StringVal("123.0") String science = "" + 1.23e2; + } - void compoundStringAssignement() { - String s = ""; - s += "hello"; - s += 'a'; - s += 1; - s += 1.0; - s += 1.0f; - s += 20L; - s += true; - s += null; - s += 0x1a; - s += 0b11010; - // TODO: this should pass - // compound assignments have not been implemented. - // :: error: (assignment.type.incompatible) - @StringVal("helloa11.01.020truenull2626") String all = s; - } + void compoundStringAssignement() { + String s = ""; + s += "hello"; + s += 'a'; + s += 1; + s += 1.0; + s += 1.0f; + s += 20L; + s += true; + s += null; + s += 0x1a; + s += 0b11010; + // TODO: this should pass + // compound assignments have not been implemented. + // :: error: (assignment.type.incompatible) + @StringVal("helloa11.01.020truenull2626") String all = s; + } - void stringIntRangeConcat( - @IntRange(from = 0, to = 1) int num, @IntRange(from = 'A', to = 'B') char letter) { - @StringVal({"num0", "num1"}) String numV = "num" + num; - @StringVal({"letterA", "letterB"}) String letterV = "letter" + letter; - } + void stringIntRangeConcat( + @IntRange(from = 0, to = 1) int num, @IntRange(from = 'A', to = 'B') char letter) { + @StringVal({"num0", "num1"}) String numV = "num" + num; + @StringVal({"letterA", "letterB"}) String letterV = "letter" + letter; + } } diff --git a/framework/tests/value/StringLen.java b/framework/tests/value/StringLen.java index eb98d591be2..9b08002271f 100644 --- a/framework/tests/value/StringLen.java +++ b/framework/tests/value/StringLen.java @@ -6,107 +6,107 @@ import org.checkerframework.common.value.qual.StringVal; public class StringLen { - void stringValArrayLen( - @StringVal("") String empty, - @StringVal("const") String constant, - @StringVal({"s", "longconstant"}) String values, - String unknown) { - - // Compatibility with ArrayLen - @ArrayLen(0) String len0 = empty; - @ArrayLen(5) String len5 = constant; - @ArrayLen({1, 12}) String len1_12 = values; - - // Compatibility with ArrayLenRange - @ArrayLenRange(from = 0, to = 0) String rng0 = empty; - @ArrayLenRange(from = 5, to = 5) String rng5 = constant; - @ArrayLenRange(from = 1, to = 12) String rng1_12 = values; - - // :: error: (assignment.type.incompatible) - @ArrayLen(4) String len4 = constant; - // :: error: (assignment.type.incompatible) - @ArrayLenRange(from = 1, to = 11) String rng1_10 = values; - } - - void stringValLubToArrayLen( - boolean flag, - @StringVal({"a", "b", "c", "d", "e"}) String ae, - @StringVal({"f", "g", "h", "i", "j", "k"}) String fk, - @StringVal({"ffff", "gggg", "hhhh", "iiii", "jjjj", "kkkkkkk"}) String fkR) { - - @ArrayLen(1) String ak = flag ? ae : fk; - @ArrayLen({1, 4, 7}) String akR = flag ? ae : fkR; - } - - void stringValLubToArrayLenRange( - boolean flag, - @StringVal({"a", "bb", "ccc", "dddd", "eeeee"}) String ae, - @StringVal({"ffffff", "ggggggg", "hhhhhhhh", "iiiiiiiii", "jjjjjjjjjj", "kkkkkkkkkkk"}) String fk) { - - @ArrayLenRange(from = 1, to = 11) String ak = flag ? ae : fk; - } - - void arrayLenStringVal( - @ArrayLen(0) String len0, - @ArrayLenRange(from = 0, to = 0) String rng0, - @ArrayLen({0, 1}) String nonEmpty) { - @StringVal("") String emptyLen = len0; - @StringVal("") String emptyRng = rng0; - - // :: error: (assignment.type.incompatible) - @StringVal("") String emptyError = nonEmpty; - // :: error: (assignment.type.incompatible) - @StringVal("a") String nonEmptyError = nonEmpty; - } - - void stringValLength( - @StringVal("") String empty, - @StringVal("const") String constant, - @StringVal({"s", "longconstant"}) String values, - String unknown) { - - @IntVal(0) int len0 = empty.length(); - @IntVal(5) int len5 = constant.length(); - @IntVal({1, 12}) int len1_12 = values.length(); - - // :: error: (assignment.type.incompatible) - @IntVal({1, 11}) int len1_11 = values.length(); - } - - void arrayLenLength( - @ArrayLen(0) String empty, - @ArrayLen(5) String constant, - @ArrayLen({1, 12}) String values, - String unknown) { - - @IntVal(0) int len0 = empty.length(); - @IntVal(5) int len5 = constant.length(); - @IntVal({1, 12}) int len1_12 = values.length(); - - // :: error: (assignment.type.incompatible) - @IntVal({1, 11}) int len1_11 = values.length(); - } - - void arrayLenRangeLength( - @ArrayLenRange(from = 0, to = 0) String empty, - @ArrayLenRange(from = 5, to = 5) String constant, - @ArrayLenRange(from = 1, to = 12) String values, - String unknown) { - - @IntRange(from = 0, to = 0) int len0 = empty.length(); - @IntRange(from = 5, to = 5) int len5 = constant.length(); - @IntRange(from = 1, to = 12) int len1_12 = values.length(); - - // :: error: (assignment.type.incompatible) - @IntRange(from = 1, to = 11) int len1_11 = values.length(); - } - - void minLenLength(@MinLen(5) String s) { - @IntRange(from = 5) int l = s.length(); - } - - void arrayCast(@ArrayLen(1) String array) { - @ArrayLen(1) String cast1 = (String) array; - @ArrayLen(1) String cast2 = (@ArrayLen(1) String) array; - } + void stringValArrayLen( + @StringVal("") String empty, + @StringVal("const") String constant, + @StringVal({"s", "longconstant"}) String values, + String unknown) { + + // Compatibility with ArrayLen + @ArrayLen(0) String len0 = empty; + @ArrayLen(5) String len5 = constant; + @ArrayLen({1, 12}) String len1_12 = values; + + // Compatibility with ArrayLenRange + @ArrayLenRange(from = 0, to = 0) String rng0 = empty; + @ArrayLenRange(from = 5, to = 5) String rng5 = constant; + @ArrayLenRange(from = 1, to = 12) String rng1_12 = values; + + // :: error: (assignment.type.incompatible) + @ArrayLen(4) String len4 = constant; + // :: error: (assignment.type.incompatible) + @ArrayLenRange(from = 1, to = 11) String rng1_10 = values; + } + + void stringValLubToArrayLen( + boolean flag, + @StringVal({"a", "b", "c", "d", "e"}) String ae, + @StringVal({"f", "g", "h", "i", "j", "k"}) String fk, + @StringVal({"ffff", "gggg", "hhhh", "iiii", "jjjj", "kkkkkkk"}) String fkR) { + + @ArrayLen(1) String ak = flag ? ae : fk; + @ArrayLen({1, 4, 7}) String akR = flag ? ae : fkR; + } + + void stringValLubToArrayLenRange( + boolean flag, + @StringVal({"a", "bb", "ccc", "dddd", "eeeee"}) String ae, + @StringVal({"ffffff", "ggggggg", "hhhhhhhh", "iiiiiiiii", "jjjjjjjjjj", "kkkkkkkkkkk"}) String fk) { + + @ArrayLenRange(from = 1, to = 11) String ak = flag ? ae : fk; + } + + void arrayLenStringVal( + @ArrayLen(0) String len0, + @ArrayLenRange(from = 0, to = 0) String rng0, + @ArrayLen({0, 1}) String nonEmpty) { + @StringVal("") String emptyLen = len0; + @StringVal("") String emptyRng = rng0; + + // :: error: (assignment.type.incompatible) + @StringVal("") String emptyError = nonEmpty; + // :: error: (assignment.type.incompatible) + @StringVal("a") String nonEmptyError = nonEmpty; + } + + void stringValLength( + @StringVal("") String empty, + @StringVal("const") String constant, + @StringVal({"s", "longconstant"}) String values, + String unknown) { + + @IntVal(0) int len0 = empty.length(); + @IntVal(5) int len5 = constant.length(); + @IntVal({1, 12}) int len1_12 = values.length(); + + // :: error: (assignment.type.incompatible) + @IntVal({1, 11}) int len1_11 = values.length(); + } + + void arrayLenLength( + @ArrayLen(0) String empty, + @ArrayLen(5) String constant, + @ArrayLen({1, 12}) String values, + String unknown) { + + @IntVal(0) int len0 = empty.length(); + @IntVal(5) int len5 = constant.length(); + @IntVal({1, 12}) int len1_12 = values.length(); + + // :: error: (assignment.type.incompatible) + @IntVal({1, 11}) int len1_11 = values.length(); + } + + void arrayLenRangeLength( + @ArrayLenRange(from = 0, to = 0) String empty, + @ArrayLenRange(from = 5, to = 5) String constant, + @ArrayLenRange(from = 1, to = 12) String values, + String unknown) { + + @IntRange(from = 0, to = 0) int len0 = empty.length(); + @IntRange(from = 5, to = 5) int len5 = constant.length(); + @IntRange(from = 1, to = 12) int len1_12 = values.length(); + + // :: error: (assignment.type.incompatible) + @IntRange(from = 1, to = 11) int len1_11 = values.length(); + } + + void minLenLength(@MinLen(5) String s) { + @IntRange(from = 5) int l = s.length(); + } + + void arrayCast(@ArrayLen(1) String array) { + @ArrayLen(1) String cast1 = (String) array; + @ArrayLen(1) String cast2 = (@ArrayLen(1) String) array; + } } diff --git a/framework/tests/value/StringLenConcats.java b/framework/tests/value/StringLenConcats.java index 777a3ffde89..4d7b51319e3 100644 --- a/framework/tests/value/StringLenConcats.java +++ b/framework/tests/value/StringLenConcats.java @@ -7,112 +7,113 @@ public class StringLenConcats { - void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { - @ArrayLen({7, 8, 9}) String ab = a + b; - @ArrayLen({6, 7}) String bxx = b + "xx"; - } - - void stringLenRangeConcat( - @ArrayLenRange(from = 3, to = 5) String a, @ArrayLenRange(from = 11, to = 19) String b) { - @ArrayLenRange(from = 7, to = 24) String ab = a + b; - @ArrayLenRange(from = 6, to = 21) String bxx = b + "xx"; - } - - void stringLenLenRangeConcat( - @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { - @ArrayLenRange(from = 7, to = 105) String ab = a + b; - } - - void stringValLenConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLen(11) String d) { - - @ArrayLen({8, 12, 15, 19}) String ad = a + d; - @ArrayLen({5, 8, 12, 15}) String bd = b + d; - @ArrayLenRange(from = 4, to = 15) String cd = c + d; - } - - void stringValLenRangeConcat( - @StringVal("constant") String a, - @StringVal({"a", "b", "c"}) String b, - @StringVal({"a", "xxx"}) String c, - @ArrayLenRange(from = 11, to = 19) String d) { - - @ArrayLenRange(from = 8, to = 27) String ad = a + d; - @ArrayLenRange(from = 5, to = 23) String bd = b + d; - @ArrayLenRange(from = 5, to = 23) String cd = c + d; - } - - void tooManyStringValConcat( - @StringVal({"a", "b", "c", "d"}) String a, - @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { - @ArrayLen({2, 5, 8}) String aa = a + a; - @ArrayLen({3, 5, 6, 8}) String ab = a + b; - } - - void charConversions( - char c, - @IntVal({1, 100, 10000}) char d, - @ArrayLen({100, 200}) String s, - @ArrayLenRange(from = 100, to = 200) String t, - @StringVal({"a", "bb", "ccc", "dddd"}) String u) { - @ArrayLen({5, 101, 201}) String sc = s + c; - @ArrayLen({5, 101, 201}) String sd = s + d; - - @ArrayLenRange(from = 5, to = 201) String tc = t + c; - - @ArrayLen({2, 3, 4, 5}) String uc = u + c; - @ArrayLen({2, 3, 4, 5}) String ud = u + d; - } - - void intConversions( - @IntVal(123) int intConst, - @IntRange(from = -100000, to = 100) int intRange, - @IntRange(from = 100, to = 100000) int positiveRange, - int unknownInt, - @ArrayLen(10) String a, - @ArrayLenRange(from = 10, to = 20) String b, - @StringVal({"aaa", "bbbbb"}) String c) { - @ArrayLen({7, 13}) String aConst = a + intConst; - @ArrayLenRange(from = 5, to = 17) String aRange = a + intRange; - @ArrayLen({7, 8, 9, 10, 13, 14, 15, 16}) String aPositive = a + positiveRange; - @ArrayLenRange(from = 5, to = 21) String aUnknown = a + unknownInt; - - @ArrayLenRange(from = 5, to = 23) String bConst = b + intConst; - @ArrayLenRange(from = 5, to = 27) String bRange = b + intRange; - @ArrayLenRange(from = 7, to = 26) String bPositive = b + positiveRange; - @ArrayLenRange(from = 5, to = 31) String bUnknown = b + unknownInt; - - @StringVal({"aaa123", "bbbbb123", "null123"}) String cConst = c + intConst; - @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; - @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; - } - - void longConversions( - @IntVal(1000000000000l) long longConst, - @IntRange(from = 10, to = 1000000000000l) long longRange, - long unknownLong, - @ArrayLen(10) String a) { - - @ArrayLen({17, 23}) String aConst = a + longConst; - @ArrayLenRange(from = 6, to = 23) String aRange = a + longRange; - @ArrayLenRange(from = 5, to = 30) String aUnknown = a + unknownLong; - } - - void byteConversions( - @IntVal(100) byte byteConst, - @IntRange(from = 2, to = 10) byte byteRange, - byte unknownByte, - @ArrayLen(10) String a) { - - @ArrayLen({7, 13}) String aConst = a + byteConst; - @ArrayLenRange(from = 5, to = 12) String aRange = a + byteRange; - @ArrayLenRange(from = 5, to = 14) String aUnknown = a + unknownByte; - } - - void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { - @MinLen(8) String st = s + t; - } + void stringLenConcat(@ArrayLen(3) String a, @ArrayLen(5) String b) { + @ArrayLen({7, 8, 9}) String ab = a + b; + @ArrayLen({6, 7}) String bxx = b + "xx"; + } + + void stringLenRangeConcat( + @ArrayLenRange(from = 3, to = 5) String a, + @ArrayLenRange(from = 11, to = 19) String b) { + @ArrayLenRange(from = 7, to = 24) String ab = a + b; + @ArrayLenRange(from = 6, to = 21) String bxx = b + "xx"; + } + + void stringLenLenRangeConcat( + @ArrayLen({3, 4, 5}) String a, @ArrayLenRange(from = 10, to = 100) String b) { + @ArrayLenRange(from = 7, to = 105) String ab = a + b; + } + + void stringValLenConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLen(11) String d) { + + @ArrayLen({8, 12, 15, 19}) String ad = a + d; + @ArrayLen({5, 8, 12, 15}) String bd = b + d; + @ArrayLenRange(from = 4, to = 15) String cd = c + d; + } + + void stringValLenRangeConcat( + @StringVal("constant") String a, + @StringVal({"a", "b", "c"}) String b, + @StringVal({"a", "xxx"}) String c, + @ArrayLenRange(from = 11, to = 19) String d) { + + @ArrayLenRange(from = 8, to = 27) String ad = a + d; + @ArrayLenRange(from = 5, to = 23) String bd = b + d; + @ArrayLenRange(from = 5, to = 23) String cd = c + d; + } + + void tooManyStringValConcat( + @StringVal({"a", "b", "c", "d"}) String a, + @StringVal({"ee", "ff", "gg", "hh", "ii"}) String b) { + @ArrayLen({2, 5, 8}) String aa = a + a; + @ArrayLen({3, 5, 6, 8}) String ab = a + b; + } + + void charConversions( + char c, + @IntVal({1, 100, 10000}) char d, + @ArrayLen({100, 200}) String s, + @ArrayLenRange(from = 100, to = 200) String t, + @StringVal({"a", "bb", "ccc", "dddd"}) String u) { + @ArrayLen({5, 101, 201}) String sc = s + c; + @ArrayLen({5, 101, 201}) String sd = s + d; + + @ArrayLenRange(from = 5, to = 201) String tc = t + c; + + @ArrayLen({2, 3, 4, 5}) String uc = u + c; + @ArrayLen({2, 3, 4, 5}) String ud = u + d; + } + + void intConversions( + @IntVal(123) int intConst, + @IntRange(from = -100000, to = 100) int intRange, + @IntRange(from = 100, to = 100000) int positiveRange, + int unknownInt, + @ArrayLen(10) String a, + @ArrayLenRange(from = 10, to = 20) String b, + @StringVal({"aaa", "bbbbb"}) String c) { + @ArrayLen({7, 13}) String aConst = a + intConst; + @ArrayLenRange(from = 5, to = 17) String aRange = a + intRange; + @ArrayLen({7, 8, 9, 10, 13, 14, 15, 16}) String aPositive = a + positiveRange; + @ArrayLenRange(from = 5, to = 21) String aUnknown = a + unknownInt; + + @ArrayLenRange(from = 5, to = 23) String bConst = b + intConst; + @ArrayLenRange(from = 5, to = 27) String bRange = b + intRange; + @ArrayLenRange(from = 7, to = 26) String bPositive = b + positiveRange; + @ArrayLenRange(from = 5, to = 31) String bUnknown = b + unknownInt; + + @StringVal({"aaa123", "bbbbb123", "null123"}) String cConst = c + intConst; + @ArrayLen({4, 5, 6, 7, 8, 9, 10, 11, 12}) String cRange = c + intRange; + @ArrayLen({6, 7, 8, 9, 10, 11}) String cPositive = c + positiveRange; + } + + void longConversions( + @IntVal(1000000000000l) long longConst, + @IntRange(from = 10, to = 1000000000000l) long longRange, + long unknownLong, + @ArrayLen(10) String a) { + + @ArrayLen({17, 23}) String aConst = a + longConst; + @ArrayLenRange(from = 6, to = 23) String aRange = a + longRange; + @ArrayLenRange(from = 5, to = 30) String aUnknown = a + unknownLong; + } + + void byteConversions( + @IntVal(100) byte byteConst, + @IntRange(from = 2, to = 10) byte byteRange, + byte unknownByte, + @ArrayLen(10) String a) { + + @ArrayLen({7, 13}) String aConst = a + byteConst; + @ArrayLenRange(from = 5, to = 12) String aRange = a + byteRange; + @ArrayLenRange(from = 5, to = 14) String aUnknown = a + unknownByte; + } + + void minLenConcat(@MinLen(5) String s, @MinLen(7) String t) { + @MinLen(8) String st = s + t; + } } diff --git a/framework/tests/value/StringLenMethods.java b/framework/tests/value/StringLenMethods.java index c7dd22c1783..329c7d03cef 100644 --- a/framework/tests/value/StringLenMethods.java +++ b/framework/tests/value/StringLenMethods.java @@ -4,47 +4,47 @@ import org.checkerframework.common.value.qual.StringVal; public class StringLenMethods { - void toString(boolean b, char c, byte y, short s, int i, long l) { - - @StringVal({"true", "false"}) String bs = Boolean.toString(b); - @ArrayLen(1) String cs = Character.toString(c); - @ArrayLen({1, 2, 3, 4}) String ys = Byte.toString(y); - @ArrayLen({1, 2, 3, 4, 5, 6}) String ss = Short.toString(s); - @ArrayLenRange(from = 1, to = 11) String is = Integer.toString(i); - @ArrayLenRange(from = 1, to = 20) String ls = Long.toString(l); - - @StringVal({"true", "false"}) String bbs = Boolean.valueOf(b).toString(); - @ArrayLen(1) String bcs = Character.valueOf(c).toString(); - @ArrayLen({1, 2, 3, 4}) String bys = Byte.valueOf(y).toString(); - @ArrayLen({1, 2, 3, 4, 5, 6}) String bss = Short.valueOf(s).toString(); - @ArrayLenRange(from = 1, to = 11) String bis = Integer.valueOf(i).toString(); - @ArrayLenRange(from = 1, to = 20) String bls = Long.valueOf(l).toString(); - - // Added in 1.8 - // @ArrayLenRange(from = 1, to = 10) String iu = Integer.toUnsignedString(i); - // @ArrayLenRange(from = 1, to = 19) String lu = Long.toUnsignedString(l); - - @StringVal({"true", "false"}) String sbs = String.valueOf(b); - @ArrayLen(1) String scs = String.valueOf(c); - @ArrayLenRange(from = 1, to = 11) String sis = String.valueOf(i); - @ArrayLenRange(from = 1, to = 20) String sls = String.valueOf(l); - } - - void toStringRadix(int i, long l, @IntRange(from = 2, to = 36) int radix) { - @ArrayLenRange(from = 1) String is = Integer.toString(i, radix); - @ArrayLenRange(from = 1) String ls = Long.toString(l, radix); - - // Added in 1.8 - // @ArrayLenRange(from = 1) String iu = Integer.toUnsignedString(i, radix); - // @ArrayLenRange(from = 1) String lu = Long.toUnsignedString(l, radix); - - @ArrayLenRange(from = 1, to = 32) String ib = Integer.toBinaryString(i); - @ArrayLenRange(from = 1, to = 64) String lb = Long.toBinaryString(l); - - @ArrayLenRange(from = 1, to = 8) String ix = Integer.toHexString(i); - @ArrayLenRange(from = 1, to = 16) String lx = Long.toHexString(l); - - @ArrayLenRange(from = 1, to = 11) String io = Integer.toOctalString(i); - @ArrayLenRange(from = 1, to = 22) String lo = Long.toOctalString(l); - } + void toString(boolean b, char c, byte y, short s, int i, long l) { + + @StringVal({"true", "false"}) String bs = Boolean.toString(b); + @ArrayLen(1) String cs = Character.toString(c); + @ArrayLen({1, 2, 3, 4}) String ys = Byte.toString(y); + @ArrayLen({1, 2, 3, 4, 5, 6}) String ss = Short.toString(s); + @ArrayLenRange(from = 1, to = 11) String is = Integer.toString(i); + @ArrayLenRange(from = 1, to = 20) String ls = Long.toString(l); + + @StringVal({"true", "false"}) String bbs = Boolean.valueOf(b).toString(); + @ArrayLen(1) String bcs = Character.valueOf(c).toString(); + @ArrayLen({1, 2, 3, 4}) String bys = Byte.valueOf(y).toString(); + @ArrayLen({1, 2, 3, 4, 5, 6}) String bss = Short.valueOf(s).toString(); + @ArrayLenRange(from = 1, to = 11) String bis = Integer.valueOf(i).toString(); + @ArrayLenRange(from = 1, to = 20) String bls = Long.valueOf(l).toString(); + + // Added in 1.8 + // @ArrayLenRange(from = 1, to = 10) String iu = Integer.toUnsignedString(i); + // @ArrayLenRange(from = 1, to = 19) String lu = Long.toUnsignedString(l); + + @StringVal({"true", "false"}) String sbs = String.valueOf(b); + @ArrayLen(1) String scs = String.valueOf(c); + @ArrayLenRange(from = 1, to = 11) String sis = String.valueOf(i); + @ArrayLenRange(from = 1, to = 20) String sls = String.valueOf(l); + } + + void toStringRadix(int i, long l, @IntRange(from = 2, to = 36) int radix) { + @ArrayLenRange(from = 1) String is = Integer.toString(i, radix); + @ArrayLenRange(from = 1) String ls = Long.toString(l, radix); + + // Added in 1.8 + // @ArrayLenRange(from = 1) String iu = Integer.toUnsignedString(i, radix); + // @ArrayLenRange(from = 1) String lu = Long.toUnsignedString(l, radix); + + @ArrayLenRange(from = 1, to = 32) String ib = Integer.toBinaryString(i); + @ArrayLenRange(from = 1, to = 64) String lb = Long.toBinaryString(l); + + @ArrayLenRange(from = 1, to = 8) String ix = Integer.toHexString(i); + @ArrayLenRange(from = 1, to = 16) String lx = Long.toHexString(l); + + @ArrayLenRange(from = 1, to = 11) String io = Integer.toOctalString(i); + @ArrayLenRange(from = 1, to = 22) String lo = Long.toOctalString(l); + } } diff --git a/framework/tests/value/StringLenWidening.java b/framework/tests/value/StringLenWidening.java index e3cfafb6b5e..fde94023901 100644 --- a/framework/tests/value/StringLenWidening.java +++ b/framework/tests/value/StringLenWidening.java @@ -2,18 +2,18 @@ public class StringLenWidening { - // Minimized example from java.util.logging.Logger.entering - public void entering(Object params[]) { - String msg = "ENTRY"; - for (int i = 0; i < params.length; i++) { - msg = msg + i; + // Minimized example from java.util.logging.Logger.entering + public void entering(Object params[]) { + String msg = "ENTRY"; + for (int i = 0; i < params.length; i++) { + msg = msg + i; + } } - } - public void repeat(int a) { - String str = ""; - for (int i = 0; i < a; i++) { - str += "a"; + public void repeat(int a) { + String str = ""; + for (int i = 0; i < a; i++) { + str += "a"; + } } - } } diff --git a/framework/tests/value/StringPolyValue.java b/framework/tests/value/StringPolyValue.java index a67e9eaefa3..e4771c6dc7c 100644 --- a/framework/tests/value/StringPolyValue.java +++ b/framework/tests/value/StringPolyValue.java @@ -1,13 +1,13 @@ import org.checkerframework.common.value.qual.StringVal; public class StringPolyValue { - void stringValArrayLen(@StringVal({"a", "b", "c"}) String abc) { + void stringValArrayLen(@StringVal({"a", "b", "c"}) String abc) { - @StringVal({"a", "b", "c"}) String ns = new String(abc); - @StringVal({"a", "b", "c"}) String ts = abc.toString(); - @StringVal({"a", "b", "c"}) String i = abc.intern(); - @StringVal({"a", "b", "c"}) String nstca = new String(abc.toCharArray()); - @StringVal({"a", "b", "c"}) String votca = String.valueOf(abc.toCharArray()); - @StringVal({"a", "b", "c"}) String cvotca = String.copyValueOf(abc.toCharArray()); - } + @StringVal({"a", "b", "c"}) String ns = new String(abc); + @StringVal({"a", "b", "c"}) String ts = abc.toString(); + @StringVal({"a", "b", "c"}) String i = abc.intern(); + @StringVal({"a", "b", "c"}) String nstca = new String(abc.toCharArray()); + @StringVal({"a", "b", "c"}) String votca = String.valueOf(abc.toCharArray()); + @StringVal({"a", "b", "c"}) String cvotca = String.copyValueOf(abc.toCharArray()); + } } diff --git a/framework/tests/value/StringSplit.java b/framework/tests/value/StringSplit.java index 05706ac3c30..aba7942c278 100644 --- a/framework/tests/value/StringSplit.java +++ b/framework/tests/value/StringSplit.java @@ -3,17 +3,17 @@ public class StringSplit { - void needsALR1(String @ArrayLenRange(from = 1) [] arg) {} + void needsALR1(String @ArrayLenRange(from = 1) [] arg) {} - void g(String compiler) { - needsALR1(compiler.trim().split(" +")); - } + void g(String compiler) { + needsALR1(compiler.trim().split(" +")); + } - void g2(String compiler) { - needsALR1(mySplit(compiler.trim(), " +")); - } + void g2(String compiler) { + needsALR1(mySplit(compiler.trim(), " +")); + } - String @MinLen(1) [] mySplit(String receiver, String regex) { - return null; - } + String @MinLen(1) [] mySplit(String receiver, String regex) { + return null; + } } diff --git a/framework/tests/value/StringValCrash.java b/framework/tests/value/StringValCrash.java index f96b91c831c..65ba0b4b90b 100644 --- a/framework/tests/value/StringValCrash.java +++ b/framework/tests/value/StringValCrash.java @@ -2,8 +2,8 @@ public class StringValCrash { - void foo() { - List path = null; - System.out.print(path.size() + "..."); - } + void foo() { + List path = null; + System.out.print(path.size() + "..."); + } } diff --git a/framework/tests/value/StringValNull.java b/framework/tests/value/StringValNull.java index 2adf1eae3f2..a0b8daa0bd0 100644 --- a/framework/tests/value/StringValNull.java +++ b/framework/tests/value/StringValNull.java @@ -4,63 +4,64 @@ public class StringValNull { - public static void main(String[] args) { - @StringVal("itsValue") String nbleString = null; - @StringVal("itsValue") String nnString = "itsValue"; - - System.out.println(toString1(nbleString)); - System.out.println(toString2(nbleString)); - - System.out.println(toString1(nnString)); - System.out.println(toString2(nnString)); - // System.out.println(toString3(nnString)); - - @IntVal(22) Integer nbleInteger = null; - @IntVal(22) Integer nnInteger = 22; - - System.out.println(toString4(nbleInteger)); - System.out.println(toString5(nbleInteger)); - - System.out.println(toString4(nnInteger)); - System.out.println(toString5(nnInteger)); - System.out.println(toString6(nnInteger)); - } - - static @StringVal("arg=itsValue") String toString1(@Nullable @StringVal("itsValue") String arg) { - // :: error: (return.type.incompatible) - return "arg=" + arg; - } - - static @StringVal({"arg=itsValue", "arg=null"}) String toString2( - @Nullable @StringVal("itsValue") String arg) { - return "arg=" + arg; - } - - /* static @StringVal("arg=itsValue") String toString3(@StringVal("itsValue") String arg) { - return "arg=" + arg; - } */ - - static @StringVal("arg=22") String toString4(@Nullable @IntVal(22) Integer arg) { - // :: error: (return.type.incompatible) - return "arg=" + arg; - } - - static @StringVal({"arg=22", "arg=null"}) String toString5(@Nullable @IntVal(22) Integer arg) { - return "arg=" + arg; - } - - static @StringVal("arg=22") String toString6(@IntVal(22) int arg) { - return "arg=" + arg; - } - - final @StringVal("hello") String s = null; - - @StringVal("hello") String s2 = null; - - void method2(StringValNull obj) { - // :: error: (assignment.type.incompatible) - @StringVal("hello") String l1 = "" + obj.s; - // :: error: (assignment.type.incompatible) - @StringVal("hello") String l2 = "" + obj.s2; - } + public static void main(String[] args) { + @StringVal("itsValue") String nbleString = null; + @StringVal("itsValue") String nnString = "itsValue"; + + System.out.println(toString1(nbleString)); + System.out.println(toString2(nbleString)); + + System.out.println(toString1(nnString)); + System.out.println(toString2(nnString)); + // System.out.println(toString3(nnString)); + + @IntVal(22) Integer nbleInteger = null; + @IntVal(22) Integer nnInteger = 22; + + System.out.println(toString4(nbleInteger)); + System.out.println(toString5(nbleInteger)); + + System.out.println(toString4(nnInteger)); + System.out.println(toString5(nnInteger)); + System.out.println(toString6(nnInteger)); + } + + static @StringVal("arg=itsValue") String toString1( + @Nullable @StringVal("itsValue") String arg) { + // :: error: (return.type.incompatible) + return "arg=" + arg; + } + + static @StringVal({"arg=itsValue", "arg=null"}) String toString2( + @Nullable @StringVal("itsValue") String arg) { + return "arg=" + arg; + } + + /* static @StringVal("arg=itsValue") String toString3(@StringVal("itsValue") String arg) { + return "arg=" + arg; + } */ + + static @StringVal("arg=22") String toString4(@Nullable @IntVal(22) Integer arg) { + // :: error: (return.type.incompatible) + return "arg=" + arg; + } + + static @StringVal({"arg=22", "arg=null"}) String toString5(@Nullable @IntVal(22) Integer arg) { + return "arg=" + arg; + } + + static @StringVal("arg=22") String toString6(@IntVal(22) int arg) { + return "arg=" + arg; + } + + final @StringVal("hello") String s = null; + + @StringVal("hello") String s2 = null; + + void method2(StringValNull obj) { + // :: error: (assignment.type.incompatible) + @StringVal("hello") String l1 = "" + obj.s; + // :: error: (assignment.type.incompatible) + @StringVal("hello") String l2 = "" + obj.s2; + } } diff --git a/framework/tests/value/StringValNullConcatLength.java b/framework/tests/value/StringValNullConcatLength.java index 0ef5cc8f0c7..e6f0443eff6 100644 --- a/framework/tests/value/StringValNullConcatLength.java +++ b/framework/tests/value/StringValNullConcatLength.java @@ -3,25 +3,25 @@ import org.checkerframework.common.value.qual.StringVal; public class StringValNullConcatLength { - @StringVal("a") String string1; + @StringVal("a") String string1; - @StringVal("b") String string2; + @StringVal("b") String string2; - @ArrayLen({1, 2}) String string3; + @ArrayLen({1, 2}) String string3; - @ArrayLen({2, 3}) String string4; + @ArrayLen({2, 3}) String string4; - @ArrayLenRange(from = 1, to = 3) String string5; + @ArrayLenRange(from = 1, to = 3) String string5; - @StringVal({"anull", "ab", "nullb", "nullnull"}) String string6 = string1 + string2; + @StringVal({"anull", "ab", "nullb", "nullnull"}) String string6 = string1 + string2; - @ArrayLen({2, 3, 5, 6, 8}) String string7 = string1 + string3; + @ArrayLen({2, 3, 5, 6, 8}) String string7 = string1 + string3; - @ArrayLen({3, 4, 5, 6, 7, 8}) String string8 = string3 + string4; + @ArrayLen({3, 4, 5, 6, 7, 8}) String string8 = string3 + string4; - @ArrayLenRange(from = 2, to = 8) String string10 = string1 + string5; + @ArrayLenRange(from = 2, to = 8) String string10 = string1 + string5; - // Omitting that string2 can be null - // :: error: (assignment.type.incompatible) - @ArrayLen({3, 4}) String string9 = string2 + string4; + // Omitting that string2 can be null + // :: error: (assignment.type.incompatible) + @ArrayLen({3, 4}) String string9 = string2 + string4; } diff --git a/framework/tests/value/StringValOfArrays.java b/framework/tests/value/StringValOfArrays.java index 564e1895db7..6076a7a9510 100644 --- a/framework/tests/value/StringValOfArrays.java +++ b/framework/tests/value/StringValOfArrays.java @@ -1,9 +1,9 @@ import org.checkerframework.common.value.qual.StringVal; public class StringValOfArrays { - void chars() { - String s = "$-hello@"; - char @StringVal("$-hello@") [] chars = s.toCharArray(); - @StringVal("$-hello@") String s2 = new String(chars); - } + void chars() { + String s = "$-hello@"; + char @StringVal("$-hello@") [] chars = s.toCharArray(); + @StringVal("$-hello@") String s2 = new String(chars); + } } diff --git a/framework/tests/value/Switch.java b/framework/tests/value/Switch.java index b93de0e2a45..408a0f8e3cc 100644 --- a/framework/tests/value/Switch.java +++ b/framework/tests/value/Switch.java @@ -4,190 +4,190 @@ // whether the semantics of switch are correct in general), but I needed some // checker to try it out on. public class Switch { - void test1(@IntVal({1, 2, 3, 4, 5}) int x) { - - // easy version, no fall through - switch (x) { - case 1: - @IntVal({1}) int y = x; - break; - case 2: - @IntVal({2}) int w = x; - // :: error: (assignment.type.incompatible) - @IntVal({1}) int z = x; - break; - default: - @IntVal({3, 4, 5}) int q = x; - break; + void test1(@IntVal({1, 2, 3, 4, 5}) int x) { + + // easy version, no fall through + switch (x) { + case 1: + @IntVal({1}) int y = x; + break; + case 2: + @IntVal({2}) int w = x; + // :: error: (assignment.type.incompatible) + @IntVal({1}) int z = x; + break; + default: + @IntVal({3, 4, 5}) int q = x; + break; + } } - } - - void test2(@IntVal({1, 2, 3, 4, 5}) int x) { - - // harder version, fall through - switch (x) { - case 1: - @IntVal({1}) int y = x; - case 2: - case 3: - @IntVal({1, 2, 3}) int w = x; - // :: error: (assignment.type.incompatible) - @IntVal({2, 3}) int z = x; - // :: error: (assignment.type.incompatible) - @IntVal({3}) int z1 = x; - break; - default: - @IntVal({4, 5}) int q = x; - // :: error: (assignment.type.incompatible) - @IntVal(5) int q2 = x; - break; + void test2(@IntVal({1, 2, 3, 4, 5}) int x) { + + // harder version, fall through + switch (x) { + case 1: + @IntVal({1}) int y = x; + case 2: + case 3: + @IntVal({1, 2, 3}) int w = x; + // :: error: (assignment.type.incompatible) + @IntVal({2, 3}) int z = x; + // :: error: (assignment.type.incompatible) + @IntVal({3}) int z1 = x; + break; + default: + @IntVal({4, 5}) int q = x; + + // :: error: (assignment.type.incompatible) + @IntVal(5) int q2 = x; + break; + } } - } - - void test3(@IntVal({1, 2, 3, 4, 5}) int x) { - // harder version, fall through - switch (x) { - case 1: - @IntVal({1}) int y = x; - case 2: - case 3: - @IntVal({1, 2, 3}) int w = x; - // :: error: (assignment.type.incompatible) - @IntVal({2, 3}) int z = x; - // :: error: (assignment.type.incompatible) - @IntVal({3}) int z1 = x; - break; - case 4: - default: - @IntVal({4, 5}) int q = x; + void test3(@IntVal({1, 2, 3, 4, 5}) int x) { + + // harder version, fall through + switch (x) { + case 1: + @IntVal({1}) int y = x; + case 2: + case 3: + @IntVal({1, 2, 3}) int w = x; + // :: error: (assignment.type.incompatible) + @IntVal({2, 3}) int z = x; + // :: error: (assignment.type.incompatible) + @IntVal({3}) int z1 = x; + break; + case 4: + default: + @IntVal({4, 5}) int q = x; + + // :: error: (assignment.type.incompatible) + @IntVal(5) int q2 = x; + break; + } + } + void test4(int x) { + switch (x) { + case 1: + @IntVal({1}) int y = x; + break; + case 2: + case 3: + @IntVal({2, 3}) int z = x; + break; + case 4: + default: + return; + } + @IntVal({1, 2, 3}) int y = x; // :: error: (assignment.type.incompatible) - @IntVal(5) int q2 = x; - break; + @IntVal(4) int y2 = x; } - } - - void test4(int x) { - switch (x) { - case 1: - @IntVal({1}) int y = x; - break; - case 2: - case 3: - @IntVal({2, 3}) int z = x; - break; - case 4: - default: - return; - } - @IntVal({1, 2, 3}) int y = x; - // :: error: (assignment.type.incompatible) - @IntVal(4) int y2 = x; - } - - void test5(@IntVal({0, 1, 2, 3, 4}) int x) { - @IntVal({0, 1, 2, 3, 4, 5}) int y = x; - switch (y = y + 1) { - case 1: - @IntVal({1}) int a = y; - // :: error: (assignment.type.incompatible) - @IntVal({2}) int b = y; - case 2: - case 3: - @IntVal({1, 2, 3}) int c = y; - break; - default: - // :: error: (assignment.type.incompatible) - @IntVal({4}) int d = y; - // :: error: (assignment.type.incompatible) - @IntVal({5}) int e = y; - @IntVal({4, 5}) int f = y; - break; + + void test5(@IntVal({0, 1, 2, 3, 4}) int x) { + @IntVal({0, 1, 2, 3, 4, 5}) int y = x; + switch (y = y + 1) { + case 1: + @IntVal({1}) int a = y; + // :: error: (assignment.type.incompatible) + @IntVal({2}) int b = y; + case 2: + case 3: + @IntVal({1, 2, 3}) int c = y; + break; + default: + // :: error: (assignment.type.incompatible) + @IntVal({4}) int d = y; + // :: error: (assignment.type.incompatible) + @IntVal({5}) int e = y; + @IntVal({4, 5}) int f = y; + break; + } } - } - - void testInts1(@IntRange(from = 0, to = 100) int x) { - switch (x) { - case 0: - case 1: - case 2: - @IntVal({0, 1, 2}) int z = x; - return; - default: + + void testInts1(@IntRange(from = 0, to = 100) int x) { + switch (x) { + case 0: + case 1: + case 2: + @IntVal({0, 1, 2}) int z = x; + return; + default: + } + + @IntRange(from = 3, to = 100) int z = x; } - @IntRange(from = 3, to = 100) int z = x; - } - - void testInts2(@IntRange(from = 0, to = 100) int x) { - - // harder version, fall through - switch (x) { - case 0: - @IntVal(0) int a = x; - break; - case 1: - @IntVal(1) int b = x; - break; - case 2: - @IntVal(2) int c = x; - default: - @IntRange(from = 2, to = 100) int d = x; - break; + void testInts2(@IntRange(from = 0, to = 100) int x) { + + // harder version, fall through + switch (x) { + case 0: + @IntVal(0) int a = x; + break; + case 1: + @IntVal(1) int b = x; + break; + case 2: + @IntVal(2) int c = x; + default: + @IntRange(from = 2, to = 100) int d = x; + break; + } } - } - - void testChars(char x) { - switch (x) { - case 'a': - case 2: - @IntVal({'a', 2}) int z = x; - break; - case 'b': - @IntVal('b') int v = x; - break; - default: - return; + + void testChars(char x) { + switch (x) { + case 'a': + case 2: + @IntVal({'a', 2}) int z = x; + break; + case 'b': + @IntVal('b') int v = x; + break; + default: + return; + } + @IntVal({'a', 2, 'b'}) int y = x; } - @IntVal({'a', 2, 'b'}) int y = x; - } - - void testStrings1(String s) { - switch (s) { - case "Good": - @StringVal("Good") String x = s; - case "Bye": - @StringVal({"Good", "Bye"}) String y = s; - break; - case "Hello": - @StringVal("Hello") String z = s; - break; - default: - return; + + void testStrings1(String s) { + switch (s) { + case "Good": + @StringVal("Good") String x = s; + case "Bye": + @StringVal({"Good", "Bye"}) String y = s; + break; + case "Hello": + @StringVal("Hello") String z = s; + break; + default: + return; + } + @StringVal({"Good", "Bye", "Hello"}) String q = s; } - @StringVal({"Good", "Bye", "Hello"}) String q = s; - } - - void testStrings2(String s) { - String a; - switch (a = s) { - case "Good": - @StringVal("Good") String x1 = a; - @StringVal("Good") String x2 = s; - case "Bye": - @StringVal({"Good", "Bye"}) String y1 = a; - @StringVal({"Good", "Bye"}) String y2 = s; - break; - case "Hello": - @StringVal("Hello") String z1 = a; - @StringVal("Hello") String z2 = s; - break; - default: - return; + + void testStrings2(String s) { + String a; + switch (a = s) { + case "Good": + @StringVal("Good") String x1 = a; + @StringVal("Good") String x2 = s; + case "Bye": + @StringVal({"Good", "Bye"}) String y1 = a; + @StringVal({"Good", "Bye"}) String y2 = s; + break; + case "Hello": + @StringVal("Hello") String z1 = a; + @StringVal("Hello") String z2 = s; + break; + default: + return; + } + @StringVal({"Good", "Bye", "Hello"}) String q1 = a; + @StringVal({"Good", "Bye", "Hello"}) String q2 = s; } - @StringVal({"Good", "Bye", "Hello"}) String q1 = a; - @StringVal({"Good", "Bye", "Hello"}) String q2 = s; - } } diff --git a/framework/tests/value/TooWideRange.java b/framework/tests/value/TooWideRange.java index 16766853c07..e6fb2d0b2e5 100644 --- a/framework/tests/value/TooWideRange.java +++ b/framework/tests/value/TooWideRange.java @@ -1,30 +1,30 @@ // test case for https://github.com/typetools/checker-framework/issues/5486 public class TooWideRange { - // From StringsPlume - @org.checkerframework.dataflow.qual.Pure - public static @org.checkerframework.common.value.qual.IntRange( - from = -2147483648, - to = 2147483647) int count(String s, int ch) { - int result = 0; - int pos = s.indexOf(ch); - while (pos > -1) { - result++; - pos = s.indexOf(ch, pos + 1); + // From StringsPlume + @org.checkerframework.dataflow.qual.Pure + public static @org.checkerframework.common.value.qual.IntRange( + from = -2147483648, + to = 2147483647) int count(String s, int ch) { + int result = 0; + int pos = s.indexOf(ch); + while (pos > -1) { + result++; + pos = s.indexOf(ch, pos + 1); + } + return result; } - return result; - } - // From ArraysPlume - @org.checkerframework.dataflow.qual.Pure - public static @org.checkerframework.common.value.qual.IntRange( - from = -2147483648, - to = 2147483647) int indexOfEq(Object[] a, Object elt) { - for (int i = 0; i < a.length; i++) { - if (elt == a[i]) { - return i; - } + // From ArraysPlume + @org.checkerframework.dataflow.qual.Pure + public static @org.checkerframework.common.value.qual.IntRange( + from = -2147483648, + to = 2147483647) int indexOfEq(Object[] a, Object elt) { + for (int i = 0; i < a.length; i++) { + if (elt == a[i]) { + return i; + } + } + return -1; } - return -1; - } } diff --git a/framework/tests/value/TypeCast.java b/framework/tests/value/TypeCast.java index 268df8f3ac9..959bb4d7c78 100644 --- a/framework/tests/value/TypeCast.java +++ b/framework/tests/value/TypeCast.java @@ -2,55 +2,55 @@ public class TypeCast { - public void charIntDoubleTest() { - int a = 98; - long b = 98; - double c = 98.0; - float d = 98.0f; - char e = 'b'; - short f = 98; - byte g = 98; - - @IntVal({'b'}) char h = (char) a; - h = (char) b; - // :: warning: (cast.unsafe) - h = (char) c; - // :: warning: (cast.unsafe) - h = (char) d; - h = (char) f; - h = (char) g; - - @IntVal({98}) int i = (int) b; - // :: warning: (cast.unsafe) - i = (int) c; - // :: warning: (cast.unsafe) - i = (int) d; - i = (int) e; - i = (int) f; - i = (int) g; - - @DoubleVal({98.0}) double j = (double) a; - j = (double) b; - j = (double) d; - j = (double) e; - j = (double) f; - j = (double) g; - } - - void otherCast() { - - byte[] b = (byte[]) null; - @BoolVal(true) boolean bool = (boolean) true; - } - - void rangeCast(@IntRange(from = 127, to = 128) int a, @IntRange(from = 128, to = 129) int b) { - @IntRange(from = 0, to = 128) - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe) - byte c = (byte) a; - // (byte) a is @IntRange(from = -128, to = 127) because of casting - - @IntRange(from = -128, to = -127) - // :: warning: (cast.unsafe) - byte d = (byte) b; - } + public void charIntDoubleTest() { + int a = 98; + long b = 98; + double c = 98.0; + float d = 98.0f; + char e = 'b'; + short f = 98; + byte g = 98; + + @IntVal({'b'}) char h = (char) a; + h = (char) b; + // :: warning: (cast.unsafe) + h = (char) c; + // :: warning: (cast.unsafe) + h = (char) d; + h = (char) f; + h = (char) g; + + @IntVal({98}) int i = (int) b; + // :: warning: (cast.unsafe) + i = (int) c; + // :: warning: (cast.unsafe) + i = (int) d; + i = (int) e; + i = (int) f; + i = (int) g; + + @DoubleVal({98.0}) double j = (double) a; + j = (double) b; + j = (double) d; + j = (double) e; + j = (double) f; + j = (double) g; + } + + void otherCast() { + + byte[] b = (byte[]) null; + @BoolVal(true) boolean bool = (boolean) true; + } + + void rangeCast(@IntRange(from = 127, to = 128) int a, @IntRange(from = 128, to = 129) int b) { + @IntRange(from = 0, to = 128) + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe) + byte c = (byte) a; + // (byte) a is @IntRange(from = -128, to = 127) because of casting + + @IntRange(from = -128, to = -127) + // :: warning: (cast.unsafe) + byte d = (byte) b; + } } diff --git a/framework/tests/value/TypeVars.java b/framework/tests/value/TypeVars.java index f9ce7923b4b..b0b97386637 100644 --- a/framework/tests/value/TypeVars.java +++ b/framework/tests/value/TypeVars.java @@ -1,49 +1,49 @@ public class TypeVars { - private void test(K key, V value) { - String s = "Negative size: " + key + "=" + value; - } + private void test(K key, V value) { + String s = "Negative size: " + key + "=" + value; + } - class MyClass { - public T myMethod() { - return null; + class MyClass { + public T myMethod() { + return null; + } } - } - public class TypeVarDefaults { - class ImplicitUpperBound {} + public class TypeVarDefaults { + class ImplicitUpperBound {} - class ExplicitUpperBound {} + class ExplicitUpperBound {} - void useImplicit() { - ImplicitUpperBound bottom; - } + void useImplicit() { + ImplicitUpperBound bottom; + } - void useExplicit() { - ExplicitUpperBound bottom; - } + void useExplicit() { + ExplicitUpperBound bottom; + } - void wildCardImplicit() { - ImplicitUpperBound bottom; - } + void wildCardImplicit() { + ImplicitUpperBound bottom; + } - void wildCardExplicit() { - ExplicitUpperBound bottom; - } + void wildCardExplicit() { + ExplicitUpperBound bottom; + } - void wildCardUpperBoundImplicit() { - ImplicitUpperBound bottom; - } + void wildCardUpperBoundImplicit() { + ImplicitUpperBound bottom; + } - void wildCardUpperBoundExplicit() { - ExplicitUpperBound bottom; - } + void wildCardUpperBoundExplicit() { + ExplicitUpperBound bottom; + } - void wildCardLowerImplicit() { - ImplicitUpperBound bottom; - } + void wildCardLowerImplicit() { + ImplicitUpperBound bottom; + } - void wildCardLowerBoundExplicit() { - ExplicitUpperBound bottom; + void wildCardLowerBoundExplicit() { + ExplicitUpperBound bottom; + } } - } } diff --git a/framework/tests/value/Unaries.java b/framework/tests/value/Unaries.java index 032c8cf1ad5..74404e776c7 100644 --- a/framework/tests/value/Unaries.java +++ b/framework/tests/value/Unaries.java @@ -2,59 +2,59 @@ public class Unaries { - public void complement() { - boolean a = false; - @BoolVal({true}) boolean b = !a; - - @IntVal({-5}) int c = ~4; - - @IntVal({-123456789}) long d = ~123456788; - } - - public void prefix() { - byte a = 1; - @IntVal({2}) byte b = ++a; - - @IntVal({3}) short c = ++a; - - @IntVal({4}) int d = ++a; - - @IntVal({5}) long e = ++a; - ++a; - e = --a; - d = --a; - c = --a; - b = --a; - } - - public void postfix() { - int a = 0; - @IntVal({0}) int b = a++; - @IntVal({1}) int c = a--; - b = a++; - - @IntVal({1}) long d = a--; - - double e = 0.25; - @DoubleVal({0.25}) double f = e++; - @DoubleVal({1.25}) double g = e--; - f = e; - } - - public void plusminus() { - @IntVal({48}) int a = +48; - @IntVal({-49}) int b = -49; - - @IntVal({34}) long c = +34; - @IntVal({-34}) long d = -34; - } - - public void intRange(@IntRange(from = 0, to = 2) int val) { - int a = val; - @IntRange(from = -2, to = 0) int b = -a; - @IntRange(from = 0, to = 2) int c = +a; - @IntRange(from = -3, to = -1) int d = ~a; - @IntRange(from = 1, to = 3) int e = ++a; - @IntRange(from = 1, to = 3) int f = a++; - } + public void complement() { + boolean a = false; + @BoolVal({true}) boolean b = !a; + + @IntVal({-5}) int c = ~4; + + @IntVal({-123456789}) long d = ~123456788; + } + + public void prefix() { + byte a = 1; + @IntVal({2}) byte b = ++a; + + @IntVal({3}) short c = ++a; + + @IntVal({4}) int d = ++a; + + @IntVal({5}) long e = ++a; + ++a; + e = --a; + d = --a; + c = --a; + b = --a; + } + + public void postfix() { + int a = 0; + @IntVal({0}) int b = a++; + @IntVal({1}) int c = a--; + b = a++; + + @IntVal({1}) long d = a--; + + double e = 0.25; + @DoubleVal({0.25}) double f = e++; + @DoubleVal({1.25}) double g = e--; + f = e; + } + + public void plusminus() { + @IntVal({48}) int a = +48; + @IntVal({-49}) int b = -49; + + @IntVal({34}) long c = +34; + @IntVal({-34}) long d = -34; + } + + public void intRange(@IntRange(from = 0, to = 2) int val) { + int a = val; + @IntRange(from = -2, to = 0) int b = -a; + @IntRange(from = 0, to = 2) int c = +a; + @IntRange(from = -3, to = -1) int d = ~a; + @IntRange(from = 1, to = 3) int e = ++a; + @IntRange(from = 1, to = 3) int f = a++; + } } diff --git a/framework/tests/value/UncheckedMinLen.java b/framework/tests/value/UncheckedMinLen.java index c75a0376883..e69f53b543f 100644 --- a/framework/tests/value/UncheckedMinLen.java +++ b/framework/tests/value/UncheckedMinLen.java @@ -4,15 +4,15 @@ // test case for kelloggm#183: https://github.com/kelloggm/checker-framework/issues/183 public class UncheckedMinLen { - void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToUnboundedIntRange(@IntRange(from = 0) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } - void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { - // :: error: (assignment.type.incompatible) - Object @MinLen(100) [] o = new Object[l + 1]; - o[99] = v; - } + void addToBoundedIntRangeOK(@IntRange(from = 0, to = 1) int l, Object v) { + // :: error: (assignment.type.incompatible) + Object @MinLen(100) [] o = new Object[l + 1]; + o[99] = v; + } } diff --git a/framework/tests/value/Underflows.java b/framework/tests/value/Underflows.java index 581d491cd85..70eddf4cdf4 100644 --- a/framework/tests/value/Underflows.java +++ b/framework/tests/value/Underflows.java @@ -1,43 +1,43 @@ import org.checkerframework.common.value.qual.*; public class Underflows { - static void bytes() { - byte min = Byte.MIN_VALUE; - @IntVal(127) byte maxPlus1 = (byte) (min - 1); - // :: error: (assignment.type.incompatible) - @IntVal(127) short maxPlus1Short = (short) (min - 1); - } + static void bytes() { + byte min = Byte.MIN_VALUE; + @IntVal(127) byte maxPlus1 = (byte) (min - 1); + // :: error: (assignment.type.incompatible) + @IntVal(127) short maxPlus1Short = (short) (min - 1); + } - static void chars() { - char min = Character.MIN_VALUE; - // :: warning: (cast.unsafe) - @IntVal(65535) char maxPlus1 = (char) (min - 1); - } + static void chars() { + char min = Character.MIN_VALUE; + // :: warning: (cast.unsafe) + @IntVal(65535) char maxPlus1 = (char) (min - 1); + } - static void shorts() { - short min = Short.MIN_VALUE; - @IntVal(32767) short maxPlus1 = (short) (min - 1); - // :: error: (assignment.type.incompatible) - @IntVal(32767) int maxPlus1Int = (int) (min - 1); - } + static void shorts() { + short min = Short.MIN_VALUE; + @IntVal(32767) short maxPlus1 = (short) (min - 1); + // :: error: (assignment.type.incompatible) + @IntVal(32767) int maxPlus1Int = (int) (min - 1); + } - static void ints() { - int min = Integer.MIN_VALUE; - @IntVal(2147483647) int maxPlus1 = min - 1; - } + static void ints() { + int min = Integer.MIN_VALUE; + @IntVal(2147483647) int maxPlus1 = min - 1; + } - static void longs() { - long min = Long.MIN_VALUE; - @IntVal(9223372036854775807L) long maxPlus1 = min - 1; - } + static void longs() { + long min = Long.MIN_VALUE; + @IntVal(9223372036854775807L) long maxPlus1 = min - 1; + } - static void doubles() { - double min = Double.MIN_VALUE; - @DoubleVal(-1.0) double maxPlus1 = min - 1.0; - } + static void doubles() { + double min = Double.MIN_VALUE; + @DoubleVal(-1.0) double maxPlus1 = min - 1.0; + } - static void floats() { - float min = Float.MIN_VALUE; - @DoubleVal(-1.0F) float maxPlus1 = min - 1.0f; - } + static void floats() { + float min = Float.MIN_VALUE; + @DoubleVal(-1.0F) float maxPlus1 = min - 1.0f; + } } diff --git a/framework/tests/value/ValueCast.java b/framework/tests/value/ValueCast.java index 6c6e34cf039..fb13b006839 100644 --- a/framework/tests/value/ValueCast.java +++ b/framework/tests/value/ValueCast.java @@ -3,57 +3,57 @@ import org.checkerframework.common.value.qual.*; public class ValueCast { - void testShort_plus(@IntRange(from = 0) short x) { - @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; - } - - void testIntFrom(@IntRange(from = 0) int x) { - @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; - } - - void testShortFrom(@IntRange(from = 0) short x) { - @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; - } - - void testCharFrom(@IntRange(from = 0) char x) { - @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; - } - - void testByteFrom(@IntRange(from = 0) byte x) { - @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; - } - - void testIntTo(@IntRange(to = 0) int x) { - @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; - } - - void testShortTo(@IntRange(to = 0) short x) { - @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; - } - - void testCharTo(@IntRange(to = 1) char x) { - @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; - } - - void testByteTo(@IntRange(to = 0) byte x) { - @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; - } + void testShort_plus(@IntRange(from = 0) short x) { + @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; + } + + void testIntFrom(@IntRange(from = 0) int x) { + @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; + } + + void testShortFrom(@IntRange(from = 0) short x) { + @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; + } + + void testCharFrom(@IntRange(from = 0) char x) { + @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; + } + + void testByteFrom(@IntRange(from = 0) byte x) { + @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; + } + + void testIntTo(@IntRange(to = 0) int x) { + @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; + } + + void testShortTo(@IntRange(to = 0) short x) { + @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; + } + + void testCharTo(@IntRange(to = 1) char x) { + @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; + } + + void testByteTo(@IntRange(to = 0) byte x) { + @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; + } } diff --git a/framework/tests/value/ValueCast2.java b/framework/tests/value/ValueCast2.java index be2dd26fe63..cdeaee697b3 100644 --- a/framework/tests/value/ValueCast2.java +++ b/framework/tests/value/ValueCast2.java @@ -3,23 +3,23 @@ import org.checkerframework.common.value.qual.*; public class ValueCast2 { - byte foo(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { - return (byte) x; - } + byte foo(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { + return (byte) x; + } - byte bar(@IntRange(from = -1000, to = 500) int x) { - return (byte) x; - } + byte bar(@IntRange(from = -1000, to = 500) int x) { + return (byte) x; + } - short foo1(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { - return (short) x; - } + short foo1(@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int x) { + return (short) x; + } - int foo2(@IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long x) { - return (int) x; - } + int foo2(@IntRange(from = Long.MIN_VALUE, to = Long.MAX_VALUE) long x) { + return (int) x; + } - int baz(@IntRange(from = Long.MIN_VALUE, to = 0) long x) { - return (int) x; - } + int baz(@IntRange(from = Long.MIN_VALUE, to = 0) long x) { + return (int) x; + } } diff --git a/framework/tests/value/ValueOpt.java b/framework/tests/value/ValueOpt.java index 602ee47ec05..462174df30f 100644 --- a/framework/tests/value/ValueOpt.java +++ b/framework/tests/value/ValueOpt.java @@ -1,14 +1,16 @@ -import java.util.Optional; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.common.value.qual.IntVal; +import java.util.Optional; + public class ValueOpt { - Optional<@NonNegative Long> method(Optional<@IntVal(Long.MAX_VALUE) Long> opt1) { - @NonNegative Long l = Long.MAX_VALUE; - @NonNegative long l2 = -1l; - Optional<@NonNegative Long> opt2 = opt1; - Optional<@NonNegative Long> opt3 = Optional.<@IntVal(Long.MAX_VALUE) Long>of(Long.MAX_VALUE); - return Optional.of(Long.MAX_VALUE); - } + Optional<@NonNegative Long> method(Optional<@IntVal(Long.MAX_VALUE) Long> opt1) { + @NonNegative Long l = Long.MAX_VALUE; + @NonNegative long l2 = -1l; + Optional<@NonNegative Long> opt2 = opt1; + Optional<@NonNegative Long> opt3 = + Optional.<@IntVal(Long.MAX_VALUE) Long>of(Long.MAX_VALUE); + return Optional.of(Long.MAX_VALUE); + } } diff --git a/framework/tests/value/ValueWrapperCast.java b/framework/tests/value/ValueWrapperCast.java index 26b239adc7b..994ec1cdbbd 100644 --- a/framework/tests/value/ValueWrapperCast.java +++ b/framework/tests/value/ValueWrapperCast.java @@ -3,57 +3,57 @@ import org.checkerframework.common.value.qual.*; public class ValueWrapperCast { - void testShort_plus(@IntRange(from = 0) Short x) { - @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; - // :: error: (assignment.type.incompatible) - @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; - } - - void testIntFrom(@IntRange(from = 0) Integer x) { - @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; - } - - void testShortFrom(@IntRange(from = 0) Short x) { - @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; - } - - void testCharFrom(@IntRange(from = 0) Character x) { - @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; - } - - void testByteFrom(@IntRange(from = 0) Byte x) { - @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; - } - - void testIntTo(@IntRange(to = 0) Integer x) { - @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; - } - - void testShortTo(@IntRange(to = 0) Short x) { - @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; - } - - void testCharTo(@IntRange(to = 1) Character x) { - @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; - } - - void testByteTo(@IntRange(to = 0) Byte x) { - @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; - // :: error: (assignment.type.incompatible) - @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; - } + void testShort_plus(@IntRange(from = 0) Short x) { + @IntRange(from = 1, to = Short.MAX_VALUE + 1) int y = x + 1; + // :: error: (assignment.type.incompatible) + @IntRange(from = 1, to = Short.MAX_VALUE - 1) int z = x; + } + + void testIntFrom(@IntRange(from = 0) Integer x) { + @IntRange(from = 0, to = Integer.MAX_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int z = x; + } + + void testShortFrom(@IntRange(from = 0) Short x) { + @IntRange(from = 0, to = Short.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Short.MAX_VALUE - 1) int z = x; + } + + void testCharFrom(@IntRange(from = 0) Character x) { + @IntRange(from = 0, to = Character.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Character.MAX_VALUE - 1) int z = x; + } + + void testByteFrom(@IntRange(from = 0) Byte x) { + @IntRange(from = 0, to = Byte.MAX_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(from = 0, to = Byte.MAX_VALUE - 1) int z = x; + } + + void testIntTo(@IntRange(to = 0) Integer x) { + @IntRange(to = 0, from = Integer.MIN_VALUE) long y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Integer.MIN_VALUE + 1) int z = x; + } + + void testShortTo(@IntRange(to = 0) Short x) { + @IntRange(to = 0, from = Short.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Short.MIN_VALUE + 1) int z = x; + } + + void testCharTo(@IntRange(to = 1) Character x) { + @IntRange(to = 1, from = Character.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 1, from = Character.MIN_VALUE + 1) int z = x; + } + + void testByteTo(@IntRange(to = 0) Byte x) { + @IntRange(to = 0, from = Byte.MIN_VALUE) int y = x; + // :: error: (assignment.type.incompatible) + @IntRange(to = 0, from = Byte.MIN_VALUE + 1) int z = x; + } } diff --git a/framework/tests/value/VarArgRe.java b/framework/tests/value/VarArgRe.java index 02a99fae6dc..7542f128665 100644 --- a/framework/tests/value/VarArgRe.java +++ b/framework/tests/value/VarArgRe.java @@ -2,24 +2,24 @@ import org.checkerframework.framework.testchecker.lib.VarArgMethods; public class VarArgRe { - // VarArgMethods is declarded in - // framework/tests/src/org/checkerframework/framework/testchecker/lib. - // All the methods return the length of the vararg. - public void use0() { - @IntVal(0) int i1 = VarArgMethods.test0(); - @IntVal(1) int i2 = VarArgMethods.test0(-1); - @IntVal(5) int i3 = VarArgMethods.test0(0, "sldfj", 0, 234, 234); - } + // VarArgMethods is declarded in + // framework/tests/src/org/checkerframework/framework/testchecker/lib. + // All the methods return the length of the vararg. + public void use0() { + @IntVal(0) int i1 = VarArgMethods.test0(); + @IntVal(1) int i2 = VarArgMethods.test0(-1); + @IntVal(5) int i3 = VarArgMethods.test0(0, "sldfj", 0, 234, 234); + } - public void use1() { - @IntVal(0) int i1 = VarArgMethods.test1("13"); - @IntVal(1) int i2 = VarArgMethods.test1("13", -1); - @IntVal(5) int i3 = VarArgMethods.test1("13", 0, "sldfj", 0, 234, 234); - } + public void use1() { + @IntVal(0) int i1 = VarArgMethods.test1("13"); + @IntVal(1) int i2 = VarArgMethods.test1("13", -1); + @IntVal(5) int i3 = VarArgMethods.test1("13", 0, "sldfj", 0, 234, 234); + } - public void use2() { - @IntVal(0) int i1 = VarArgMethods.test2("", ""); - @IntVal(1) int i2 = VarArgMethods.test2("", "", -1); - @IntVal(5) int i3 = VarArgMethods.test2("", "", 0, "sldfj", 0, 234, 234); - } + public void use2() { + @IntVal(0) int i1 = VarArgMethods.test2("", ""); + @IntVal(1) int i2 = VarArgMethods.test2("", "", -1); + @IntVal(5) int i3 = VarArgMethods.test2("", "", 0, "sldfj", 0, 234, 234); + } } diff --git a/framework/tests/value/WildcardIn.java b/framework/tests/value/WildcardIn.java index 000950908d4..d7a468b253e 100644 --- a/framework/tests/value/WildcardIn.java +++ b/framework/tests/value/WildcardIn.java @@ -1,10 +1,10 @@ public class WildcardIn { - void foo(GenericObject gen) { - Integer i = (Integer) gen.get(); - } + void foo(GenericObject gen) { + Integer i = (Integer) gen.get(); + } } interface GenericObject { - public abstract T get(); + public abstract T get(); } diff --git a/framework/tests/value/java17/MultiCaseConst.java b/framework/tests/value/java17/MultiCaseConst.java index 552e7e069ed..b9ae2b4b12e 100644 --- a/framework/tests/value/java17/MultiCaseConst.java +++ b/framework/tests/value/java17/MultiCaseConst.java @@ -3,43 +3,43 @@ public class MultiCaseConst { - void method(int selector) { - switch (selector) { - case 1, 2, 3: - // :: error: (assignment.type.incompatible) - @IntVal(0) int o = selector; - @IntVal({1, 2, 3}) int tmp = selector; - case 4, 5: - // :: error: (assignment.type.incompatible) - @IntVal({4, 5}) int tmp2 = selector; - @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; + void method(int selector) { + switch (selector) { + case 1, 2, 3: + // :: error: (assignment.type.incompatible) + @IntVal(0) int o = selector; + @IntVal({1, 2, 3}) int tmp = selector; + case 4, 5: + // :: error: (assignment.type.incompatible) + @IntVal({4, 5}) int tmp2 = selector; + @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; + } } - } - void method2(int selector) { - switch (selector) { - case 1: - // :: error: (assignment.type.incompatible) - @IntVal(0) int o = selector; - @IntVal({1, 2, 3}) int tmp = selector; - break; - case 4, 5: - @IntVal({4, 5}) int tmp2 = selector; - @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; + void method2(int selector) { + switch (selector) { + case 1: + // :: error: (assignment.type.incompatible) + @IntVal(0) int o = selector; + @IntVal({1, 2, 3}) int tmp = selector; + break; + case 4, 5: + @IntVal({4, 5}) int tmp2 = selector; + @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; + } } - } - void method3(int selector) { - switch (selector) { - case 1 -> { - // :: error: (assignment.type.incompatible) - @IntVal(0) int o = selector; - @IntVal({1, 2, 3}) int tmp = selector; - } - case 4, 5 -> { - @IntVal({4, 5}) int tmp2 = selector; - @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; - } + void method3(int selector) { + switch (selector) { + case 1 -> { + // :: error: (assignment.type.incompatible) + @IntVal(0) int o = selector; + @IntVal({1, 2, 3}) int tmp = selector; + } + case 4, 5 -> { + @IntVal({4, 5}) int tmp2 = selector; + @IntVal({1, 2, 3, 4, 5}) int tmp3 = selector; + } + } } - } } diff --git a/framework/tests/value/java17/SwitchExpressionTyping.java b/framework/tests/value/java17/SwitchExpressionTyping.java index 2bc5a49dd0e..0c508ab3814 100644 --- a/framework/tests/value/java17/SwitchExpressionTyping.java +++ b/framework/tests/value/java17/SwitchExpressionTyping.java @@ -2,99 +2,99 @@ import org.checkerframework.common.value.qual.IntVal; public class SwitchExpressionTyping { - public static boolean flag = false; + public static boolean flag = false; - void method0(String s) { - @IntVal({0, 1, 2, 3}) int o = - switch (s) { - case "Hello?" -> { - throw new RuntimeException(); - } - case "Hello" -> 0; - case "Bye" -> 1; - case "Later" -> 2; - case "What?" -> throw new RuntimeException(); - default -> 3; - }; - } - - void method1(String s) { - @IntVal({1, 2, 3}) int o = - switch (s) { - case "Hello?" -> 1; - case "Hello" -> 1; - case "Bye" -> 1; - case "Later" -> 1; - case "What?" -> { - if (flag) { - yield 2; - } - yield 3; - } - default -> 1; - }; - - @IntVal(1) int o2 = - // :: error: (assignment.type.incompatible) - switch (s) { - case "Hello?" -> 1; - case "Hello" -> 1; - case "Bye" -> 1; - case "Later" -> 1; - case "What?" -> { - if (flag) { - yield 2; - } - yield 3; - } - default -> 1; - }; - } + void method0(String s) { + @IntVal({0, 1, 2, 3}) int o = + switch (s) { + case "Hello?" -> { + throw new RuntimeException(); + } + case "Hello" -> 0; + case "Bye" -> 1; + case "Later" -> 2; + case "What?" -> throw new RuntimeException(); + default -> 3; + }; + } - void method2(String s, String r) { - @IntVal({0, 1, 2, 3}) int o = - switch (s) { - case "Hello?" -> { - if (flag) { - throw new RuntimeException(); - } - yield 2; - } - case "Hello" -> { - int i = - switch (r) { - case "Hello" -> 4; - case "Bye" -> 5; - case "Later" -> 6; - default -> 42; + void method1(String s) { + @IntVal({1, 2, 3}) int o = + switch (s) { + case "Hello?" -> 1; + case "Hello" -> 1; + case "Bye" -> 1; + case "Later" -> 1; + case "What?" -> { + if (flag) { + yield 2; + } + yield 3; + } + default -> 1; }; - yield 0; - } - case "Bye" -> 1; - case "Later" -> { - int i = - switch (r) { - case "Hello": - { - yield 4; + + @IntVal(1) int o2 = + // :: error: (assignment.type.incompatible) + switch (s) { + case "Hello?" -> 1; + case "Hello" -> 1; + case "Bye" -> 1; + case "Later" -> 1; + case "What?" -> { + if (flag) { + yield 2; + } + yield 3; } - case "Bye": - { - yield 5; + default -> 1; + }; + } + + void method2(String s, String r) { + @IntVal({0, 1, 2, 3}) int o = + switch (s) { + case "Hello?" -> { + if (flag) { + throw new RuntimeException(); + } + yield 2; } - case "Later": - { - yield 6; + case "Hello" -> { + int i = + switch (r) { + case "Hello" -> 4; + case "Bye" -> 5; + case "Later" -> 6; + default -> 42; + }; + yield 0; } - default: - { - yield 42; + case "Bye" -> 1; + case "Later" -> { + int i = + switch (r) { + case "Hello": + { + yield 4; + } + case "Bye": + { + yield 5; + } + case "Later": + { + yield 6; + } + default: + { + yield 42; + } + }; + yield 2; } + case "What?" -> throw new RuntimeException(); + default -> 3; }; - yield 2; - } - case "What?" -> throw new RuntimeException(); - default -> 3; - }; - } + } } diff --git a/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java b/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java index c38f1e1f547..38cb26e6bd4 100644 --- a/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java +++ b/framework/tests/value/java17/ValueSwitchExprNeedsDataflow.java @@ -3,69 +3,69 @@ public class ValueSwitchExprNeedsDataflow { - void method(int selector) { - @IntVal({2, 3}) int value1 = - switch (selector) { - case 1: - yield 1 + 2; - default: - yield 1 + 1; - }; - @IntVal({2, 3}) int value2 = - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 1; - }; + void method(int selector) { + @IntVal({2, 3}) int value1 = + switch (selector) { + case 1: + yield 1 + 2; + default: + yield 1 + 1; + }; + @IntVal({2, 3}) int value2 = + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 1; + }; - int tmp = - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 1; - }; - @IntVal({2, 3}) int value3 = tmp; - } + int tmp = + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 1; + }; + @IntVal({2, 3}) int value3 = tmp; + } - void method1(int selector) { + void method1(int selector) { - @IntVal(3) int value1 = - // :: error: (assignment.type.incompatible) - switch (selector) { - case 1: - yield 1 + 2; - default: - yield 1 + 1; - }; + @IntVal(3) int value1 = + // :: error: (assignment.type.incompatible) + switch (selector) { + case 1: + yield 1 + 2; + default: + yield 1 + 1; + }; - @IntVal(3) int value2 = - // :: error: (assignment.type.incompatible) - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 1; - }; - } + @IntVal(3) int value2 = + // :: error: (assignment.type.incompatible) + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 1; + }; + } - void method2(int selector, int selector2) { + void method2(int selector, int selector2) { - @IntVal({2, 3}) int value2 = - switch (selector) { - case 1 -> { - yield 1 + 2; - } - default -> { - yield switch (selector2) { - case 1: - { - @IntVal(3) int inner = - switch (selector) { - case 1 -> 1 + 2; - default -> 1 + 2; - }; - yield 1 + 2; - } - default: - yield 1 + 1; - }; - } - }; - } + @IntVal({2, 3}) int value2 = + switch (selector) { + case 1 -> { + yield 1 + 2; + } + default -> { + yield switch (selector2) { + case 1: + { + @IntVal(3) int inner = + switch (selector) { + case 1 -> 1 + 2; + default -> 1 + 2; + }; + yield 1 + 2; + } + default: + yield 1 + 1; + }; + } + }; + } } diff --git a/framework/tests/value/java17/ValueSwitchStatementRules.java b/framework/tests/value/java17/ValueSwitchStatementRules.java index ca728d6de9a..ac9947ddf3f 100644 --- a/framework/tests/value/java17/ValueSwitchStatementRules.java +++ b/framework/tests/value/java17/ValueSwitchStatementRules.java @@ -2,28 +2,28 @@ import org.checkerframework.common.value.qual.IntVal; public class ValueSwitchStatementRules { - private int field; + private int field; - void method(int selector) { - field = 300; - switch (selector) { - case 1: - field = 42; - @IntVal(42) int copyField = field; - case 2: - // :: error: (assignment.type.incompatible) - @IntVal(300) int copyField2 = field; - } + void method(int selector) { + field = 300; + switch (selector) { + case 1: + field = 42; + @IntVal(42) int copyField = field; + case 2: + // :: error: (assignment.type.incompatible) + @IntVal(300) int copyField2 = field; + } - field = 300; - switch (selector) { - case 1 -> { - field = 42; - @IntVal(42) int copyField = field; - } - case 2 -> { - @IntVal(300) int copyField = field; - } + field = 300; + switch (selector) { + case 1 -> { + field = 42; + @IntVal(42) int copyField = field; + } + case 2 -> { + @IntVal(300) int copyField = field; + } + } } - } } diff --git a/framework/tests/value/loops/DoWhile.java b/framework/tests/value/loops/DoWhile.java index 1b1f6eeedde..5e5d4e77fc3 100644 --- a/framework/tests/value/loops/DoWhile.java +++ b/framework/tests/value/loops/DoWhile.java @@ -7,33 +7,33 @@ @SuppressWarnings("value") public class DoWhile { - void doWhile() { - int d = 0; - do { - d++; - } while (d < 399); - @IntRange(from = 399) int after = d; - } + void doWhile() { + int d = 0; + do { + d++; + } while (d < 399); + @IntRange(from = 399) int after = d; + } - void another() { - int d = 0; - do { - d++; - if (d > 444) { - break; - } - @IntRange(from = 1, to = 444) int z = d; - } while (true); - } + void another() { + int d = 0; + do { + d++; + if (d > 444) { + break; + } + @IntRange(from = 1, to = 444) int z = d; + } while (true); + } - void fromAnno(@IntVal(2222) int param) { - int d = 0; - do { - d++; - if (d > param) { - break; - } - @IntRange(from = 1, to = 2222) int z = d; - } while (true); - } + void fromAnno(@IntVal(2222) int param) { + int d = 0; + do { + d++; + if (d > param) { + break; + } + @IntRange(from = 1, to = 2222) int z = d; + } while (true); + } } diff --git a/framework/tests/value/loops/NestedLoops.java b/framework/tests/value/loops/NestedLoops.java index 822bc4a36a5..a8ee9035920 100644 --- a/framework/tests/value/loops/NestedLoops.java +++ b/framework/tests/value/loops/NestedLoops.java @@ -5,52 +5,52 @@ // to make sure that dataflow reaches a fixed point. @SuppressWarnings("value") public class NestedLoops { - void test1() { - int doWhileIndex = 0; - do { - for (int forIndex = 0; forIndex < doWhileIndex; forIndex++) { - System.out.print("Hello"); - int whileIndex = 0; - while (whileIndex < forIndex) { - whileIndex++; - } - } - doWhileIndex++; - } while (doWhileIndex < Integer.MAX_VALUE); - } + void test1() { + int doWhileIndex = 0; + do { + for (int forIndex = 0; forIndex < doWhileIndex; forIndex++) { + System.out.print("Hello"); + int whileIndex = 0; + while (whileIndex < forIndex) { + whileIndex++; + } + } + doWhileIndex++; + } while (doWhileIndex < Integer.MAX_VALUE); + } - void test2() { - int doWhileIndex = 0; - do { - for (int forIndex = 0; forIndex < Integer.MAX_VALUE; forIndex++) { - System.out.print("Hello"); - int whileIndex = 0; - while (whileIndex < Integer.MAX_VALUE) { - whileIndex++; - } - } - doWhileIndex++; - } while (doWhileIndex < Integer.MAX_VALUE); - } + void test2() { + int doWhileIndex = 0; + do { + for (int forIndex = 0; forIndex < Integer.MAX_VALUE; forIndex++) { + System.out.print("Hello"); + int whileIndex = 0; + while (whileIndex < Integer.MAX_VALUE) { + whileIndex++; + } + } + doWhileIndex++; + } while (doWhileIndex < Integer.MAX_VALUE); + } - void test3() { - int doWhileIndex = 0; - int forIndex; - int whileIndex = 0; + void test3() { + int doWhileIndex = 0; + int forIndex; + int whileIndex = 0; - do { - @IntRange(to = 2999) int a = doWhileIndex; - for (forIndex = 0; forIndex < 4000; forIndex++) { + do { + @IntRange(to = 2999) int a = doWhileIndex; + for (forIndex = 0; forIndex < 4000; forIndex++) { - @IntRange(to = 3999) int b = forIndex; - System.out.print("Hello"); - whileIndex = 0; - while (whileIndex < 5000) { - @IntRange(to = 4999) int c = whileIndex; - whileIndex++; - } - } - doWhileIndex++; - } while (doWhileIndex < 3000); - } + @IntRange(to = 3999) int b = forIndex; + System.out.print("Hello"); + whileIndex = 0; + while (whileIndex < 5000) { + @IntRange(to = 4999) int c = whileIndex; + whileIndex++; + } + } + doWhileIndex++; + } while (doWhileIndex < 3000); + } } diff --git a/framework/tests/value/loops/OscillatingLoops.java b/framework/tests/value/loops/OscillatingLoops.java index bf4458b7694..92bbd97a6ca 100644 --- a/framework/tests/value/loops/OscillatingLoops.java +++ b/framework/tests/value/loops/OscillatingLoops.java @@ -7,60 +7,60 @@ @SuppressWarnings("value") public class OscillatingLoops { - void oscillatesDoWhile() { - int i = 0; - int d = 0; - do { - i++; - if (d > 4566) { - d = 0; - } else { - d++; - } - } while (i < Integer.MAX_VALUE); - @IntRange(from = 0, to = 4567) int after = d; - @IntVal(Integer.MAX_VALUE) int afterI = i; - } + void oscillatesDoWhile() { + int i = 0; + int d = 0; + do { + i++; + if (d > 4566) { + d = 0; + } else { + d++; + } + } while (i < Integer.MAX_VALUE); + @IntRange(from = 0, to = 4567) int after = d; + @IntVal(Integer.MAX_VALUE) int afterI = i; + } - void oscillatesWhile() { - int i = 0; - int d = 1; - while (i < Integer.MAX_VALUE) { - i++; - if (d > 4566) { - d = 0; - } else { - d++; - } + void oscillatesWhile() { + int i = 0; + int d = 1; + while (i < Integer.MAX_VALUE) { + i++; + if (d > 4566) { + d = 0; + } else { + d++; + } + } + @IntRange(from = 0, to = 4567) int after = d; + @IntVal(Integer.MAX_VALUE) int afterI = i; } - @IntRange(from = 0, to = 4567) int after = d; - @IntVal(Integer.MAX_VALUE) int afterI = i; - } - void oscillatesDoWhile2() { - int i = 0; - int d = 0; - do { - if (d > 4566) { - d = 0; - } else { - d++; - } - i++; - } while (i < Integer.MAX_VALUE); - @IntRange(from = -128, to = 32767) int after = d; - @IntVal(Integer.MAX_VALUE) int afterI = i; - } + void oscillatesDoWhile2() { + int i = 0; + int d = 0; + do { + if (d > 4566) { + d = 0; + } else { + d++; + } + i++; + } while (i < Integer.MAX_VALUE); + @IntRange(from = -128, to = 32767) int after = d; + @IntVal(Integer.MAX_VALUE) int afterI = i; + } - void oscillatesFor() { - int d = 0; - for (int i = 0; i < Integer.MAX_VALUE; i++) { - if (d > 4566) { - d = 0; - } else { - d++; - } + void oscillatesFor() { + int d = 0; + for (int i = 0; i < Integer.MAX_VALUE; i++) { + if (d > 4566) { + d = 0; + } else { + d++; + } + } + @IntRange(from = -128, to = 32767) int after = d; } - @IntRange(from = -128, to = 32767) int after = d; - } } diff --git a/framework/tests/value/loops/WidenedUpperBound.java b/framework/tests/value/loops/WidenedUpperBound.java index 1f0a374bd2a..ebc257c7b6e 100644 --- a/framework/tests/value/loops/WidenedUpperBound.java +++ b/framework/tests/value/loops/WidenedUpperBound.java @@ -1,6 +1,7 @@ -import java.util.List; import org.checkerframework.common.value.qual.IntRange; +import java.util.List; + // Because the analysis of loops isn't precise enough, the Value Checker issues // warnings on this test case. So, suppress those warnings, but run the tests // to make sure that dataflow reaches a fixed point. @@ -9,191 +10,191 @@ @SuppressWarnings("value") public class WidenedUpperBound { - void increment() { - int forIndex; - for (forIndex = 0; forIndex < 4323; forIndex++) { - @IntRange(from = 0, to = 4322) int x = forIndex; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 0, to = 4322) int x = forIndex; - @IntRange(from = 4323) int y = forIndex; - - int whileIndex = 0; - while (whileIndex < 1234) { - @IntRange(from = 0, to = 1233) int z = whileIndex; - whileIndex++; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 0, to = 1233) int a = whileIndex; - @IntRange(from = 1234) int b = whileIndex; - - int doWhileIndex = 0; - do { - @IntRange(from = 0, to = 2344) int c = doWhileIndex; - doWhileIndex++; - } while (doWhileIndex < 2345); - //// ::error: (assignment.type.incompatible) - @IntRange(from = 0, to = 2344) int d = doWhileIndex; - @IntRange(from = 2345) int e = doWhileIndex; - } - - void decrement() { - int forIndex; - for (forIndex = 4323; forIndex > 0; forIndex--) { - @IntRange(from = 1, to = 4323) int x = forIndex; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 1, to = 4323) int x = forIndex; - @IntRange(to = 0) int y = forIndex; - - int whileIndex = 1234; - while (whileIndex > 0) { - @IntRange(from = 1, to = 1234) int z = whileIndex; - whileIndex--; - } - //// ::error: (assignment.type.incompatible) - @IntRange(from = 1, to = 1234) int a = whileIndex; - @IntRange(to = 0) int b = whileIndex; - - int doWhileIndex = 2344; - do { - @IntRange(from = 1, to = 2344) int c = doWhileIndex; - doWhileIndex--; - } while (doWhileIndex > 0); - //// ::error: (assignment.type.incompatible) - @IntRange(from = 1, to = 2344) int d = doWhileIndex; - @IntRange(to = 0) int e = doWhileIndex; - } - - static void test1() { - for (int i = 40; i > 0; i--) { - @IntRange(from = 1, to = 40) int x = i; - } - } + void increment() { + int forIndex; + for (forIndex = 0; forIndex < 4323; forIndex++) { + @IntRange(from = 0, to = 4322) int x = forIndex; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 0, to = 4322) int x = forIndex; + @IntRange(from = 4323) int y = forIndex; + + int whileIndex = 0; + while (whileIndex < 1234) { + @IntRange(from = 0, to = 1233) int z = whileIndex; + whileIndex++; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 0, to = 1233) int a = whileIndex; + @IntRange(from = 1234) int b = whileIndex; + + int doWhileIndex = 0; + do { + @IntRange(from = 0, to = 2344) int c = doWhileIndex; + doWhileIndex++; + } while (doWhileIndex < 2345); + //// ::error: (assignment.type.incompatible) + @IntRange(from = 0, to = 2344) int d = doWhileIndex; + @IntRange(from = 2345) int e = doWhileIndex; + } + + void decrement() { + int forIndex; + for (forIndex = 4323; forIndex > 0; forIndex--) { + @IntRange(from = 1, to = 4323) int x = forIndex; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 1, to = 4323) int x = forIndex; + @IntRange(to = 0) int y = forIndex; + + int whileIndex = 1234; + while (whileIndex > 0) { + @IntRange(from = 1, to = 1234) int z = whileIndex; + whileIndex--; + } + //// ::error: (assignment.type.incompatible) + @IntRange(from = 1, to = 1234) int a = whileIndex; + @IntRange(to = 0) int b = whileIndex; + + int doWhileIndex = 2344; + do { + @IntRange(from = 1, to = 2344) int c = doWhileIndex; + doWhileIndex--; + } while (doWhileIndex > 0); + //// ::error: (assignment.type.incompatible) + @IntRange(from = 1, to = 2344) int d = doWhileIndex; + @IntRange(to = 0) int e = doWhileIndex; + } + + static void test1() { + for (int i = 40; i > 0; i--) { + @IntRange(from = 1, to = 40) int x = i; + } + } + + static void test1Explicit() { + for (@IntRange(from = 1, to = 40) int i = 40; i > 0; i--) { + @IntRange(from = 1, to = 40) int x = i; + } + } + + static void test2() { + for (int i = 0; i < 18; i++) { + @IntRange(from = 0, to = 17) int x = i; + } + } + + static void test2Explicit() { + for (@IntRange(from = 0, to = 17) int i = 0; i < 18; i++) { + @IntRange(from = 0, to = 17) int x = i; + } + } + + static void test3() { + for (int i = 0; i < 18; i++) { + @IntRange(from = 0, to = 17) int x = i; + } + } + + static void evenOdd(int param) { + for (int i = 0; i < 12; i++) { + if (i % 2 == 0) { + @IntRange(from = 0, to = 11) int z = i; + } + @IntRange(from = 0, to = 11) int x = i; + } + + for (int i = 0; i < 12; i++) { + if (param == 0) { + @IntRange(from = 0, to = 11) int z = i; + } + @IntRange(from = 0, to = 11) int x = i; + } + } + + static void evenOdd2(int param) { + for (int i = 0; i < 300; i++) { + if (i % 2 == 0) { + @IntRange(from = 0, to = 299) int z = i; + } + @IntRange(from = 0, to = 299) int x = i; + } + + for (int i = 0; i < 399; i++) { + if (param == 0) { + @IntRange(from = 0, to = 398) int z = i; + } + @IntRange(from = 0, to = 398) int x = i; + } + } + + void ifBlock(int param) { + int x = 10; + if (x < param) { + x = param; + } + int z = 40; + if (z < param) { + param = 40; + } + } + + void doWhile() { + int d = 0; + do { + @IntRange(from = 0, to = 399) int x = d; + if (d % 2 == 0) { + @IntRange(from = 0, to = 399) int y = d; + } - static void test1Explicit() { - for (@IntRange(from = 1, to = 40) int i = 40; i > 0; i--) { - @IntRange(from = 1, to = 40) int x = i; + d++; + } while (d < 399); + + @IntRange(from = 399) int z = d; } - } - static void test2() { - for (int i = 0; i < 18; i++) { - @IntRange(from = 0, to = 17) int x = i; - } - } + void doWhileMax() { + int d = 0; + do { + @IntRange(from = 0) int x = d; + if (d % 2 == 0) { + @IntRange(from = 0) int y = d; + } - static void test2Explicit() { - for (@IntRange(from = 0, to = 17) int i = 0; i < 18; i++) { - @IntRange(from = 0, to = 17) int x = i; - } - } + d++; + } while (d < Integer.MAX_VALUE); - static void test3() { - for (int i = 0; i < 18; i++) { - @IntRange(from = 0, to = 17) int x = i; - } - } - - static void evenOdd(int param) { - for (int i = 0; i < 12; i++) { - if (i % 2 == 0) { - @IntRange(from = 0, to = 11) int z = i; - } - @IntRange(from = 0, to = 11) int x = i; + @IntRange(from = 399) int z = d; } - for (int i = 0; i < 12; i++) { - if (param == 0) { - @IntRange(from = 0, to = 11) int z = i; - } - @IntRange(from = 0, to = 11) int x = i; - } - } - - static void evenOdd2(int param) { - for (int i = 0; i < 300; i++) { - if (i % 2 == 0) { - @IntRange(from = 0, to = 299) int z = i; - } - @IntRange(from = 0, to = 299) int x = i; - } + void whileLoop() { + int i = 0; + while (i < 399) { + @IntRange(from = 0, to = 399) int x = i; + if (i % 2 == 0) { + @IntRange(from = 0, to = 399) int y = i; + } - for (int i = 0; i < 399; i++) { - if (param == 0) { - @IntRange(from = 0, to = 398) int z = i; - } - @IntRange(from = 0, to = 398) int x = i; - } - } + i++; + } - void ifBlock(int param) { - int x = 10; - if (x < param) { - x = param; - } - int z = 40; - if (z < param) { - param = 40; - } - } - - void doWhile() { - int d = 0; - do { - @IntRange(from = 0, to = 399) int x = d; - if (d % 2 == 0) { - @IntRange(from = 0, to = 399) int y = d; - } - - d++; - } while (d < 399); - - @IntRange(from = 399) int z = d; - } - - void doWhileMax() { - int d = 0; - do { - @IntRange(from = 0) int x = d; - if (d % 2 == 0) { - @IntRange(from = 0) int y = d; - } - - d++; - } while (d < Integer.MAX_VALUE); - - @IntRange(from = 399) int z = d; - } - - void whileLoop() { - int i = 0; - while (i < 399) { - @IntRange(from = 0, to = 399) int x = i; - if (i % 2 == 0) { - @IntRange(from = 0, to = 399) int y = i; - } - - i++; + @IntRange(from = 399) int z = i; } - @IntRange(from = 399) int z = i; - } + static void testMax() { + for (int i = 0; i < Integer.MAX_VALUE; i++) { + @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int x = i; + } + } - static void testMax() { - for (int i = 0; i < Integer.MAX_VALUE; i++) { - @IntRange(from = 0, to = Integer.MAX_VALUE - 1) int x = i; - } - } - - void exceptionLoop(List list) { - int x = 0; - for (short z = 0; z < list.size(); z++) { - x = z; - if (z == 100) { - break; - } + void exceptionLoop(List list) { + int x = 0; + for (short z = 0; z < list.size(); z++) { + x = z; + if (z == 100) { + break; + } + } + @IntRange(from = 0, to = 127) int result = x; } - @IntRange(from = 0, to = 127) int result = x; - } } diff --git a/framework/tests/variablenamedefault/TestVariableNameDefault.java b/framework/tests/variablenamedefault/TestVariableNameDefault.java index 6ac84587372..8fdbe683902 100644 --- a/framework/tests/variablenamedefault/TestVariableNameDefault.java +++ b/framework/tests/variablenamedefault/TestVariableNameDefault.java @@ -2,204 +2,204 @@ public class TestVariableNameDefault { - int top; - - int middle; - int middlevar; - int mymiddle; - int notmiddle; - int notmiddlevar; - @VariableNameDefaultMiddle int namedbottombutnot; - - int bottom; - int bottomvar; - int mybottom; - int notbottom; - int notbottomvar; - @VariableNameDefaultBottom int namedmiddlebutnot; - - void testFields() { - - @VariableNameDefaultTop int t; - - t = top; - - t = middle; - t = middlevar; - t = mymiddle; - t = notmiddle; - t = notmiddlevar; - t = namedbottombutnot; - - t = bottom; - t = bottomvar; - t = mybottom; - t = notbottom; - t = notbottomvar; - t = namedmiddlebutnot; - - @VariableNameDefaultMiddle int m; - - // :: error: (assignment.type.incompatible) - m = top; - - m = middle; - m = middlevar; - m = mymiddle; - // :: error: (assignment.type.incompatible) - m = notmiddle; - // :: error: (assignment.type.incompatible) - m = notmiddlevar; - m = namedbottombutnot; - - m = bottom; - m = bottomvar; - m = mybottom; - // :: error: (assignment.type.incompatible) - m = notbottom; - // :: error: (assignment.type.incompatible) - m = notbottomvar; - m = namedmiddlebutnot; - - @VariableNameDefaultBottom int b; - - // :: error: (assignment.type.incompatible) - b = top; - - // :: error: (assignment.type.incompatible) - b = middle; - // :: error: (assignment.type.incompatible) - b = middlevar; - // :: error: (assignment.type.incompatible) - b = mymiddle; - // :: error: (assignment.type.incompatible) - b = notmiddle; - // :: error: (assignment.type.incompatible) - b = notmiddlevar; - // :: error: (assignment.type.incompatible) - b = namedbottombutnot; - - b = bottom; - b = bottomvar; - b = mybottom; - // :: error: (assignment.type.incompatible) - b = notbottom; - // :: error: (assignment.type.incompatible) - b = notbottomvar; - b = namedmiddlebutnot; - } - - void testFormals(int middle, int notmiddle, int bottom, int notbottom) { - - @VariableNameDefaultTop int t; - - t = middle; - t = notmiddle; - t = bottom; - t = notbottom; - - @VariableNameDefaultMiddle int m; - - m = middle; - // :: error: (assignment.type.incompatible) - m = notmiddle; - m = bottom; - // :: error: (assignment.type.incompatible) - m = notbottom; - - @VariableNameDefaultBottom int b; - - // :: error: (assignment.type.incompatible) - b = middle; - // :: error: (assignment.type.incompatible) - b = notmiddle; - b = bottom; - // :: error: (assignment.type.incompatible) - b = notbottom; - } - - int middlemethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int mymiddlemethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int notmiddlemethod() { - return 0; - } - - int mynotmiddlemethod() { - return 0; - } - - int bottommethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int mybottommethod() { - // :: error: (return.type.incompatible) - return 0; - } - - int notbottommethod() { - return 0; - } - - int mynotbottommethod() { - return 0; - } - - void testMethods() { - - @VariableNameDefaultTop int t; - - t = middlemethod(); - t = mymiddlemethod(); - t = notmiddlemethod(); - t = mynotmiddlemethod(); - - t = bottommethod(); - t = mybottommethod(); - t = notbottommethod(); - t = mynotbottommethod(); - - @VariableNameDefaultMiddle int m; - - m = middlemethod(); - m = mymiddlemethod(); - // :: error: (assignment.type.incompatible) - m = notmiddlemethod(); - // :: error: (assignment.type.incompatible) - m = mynotmiddlemethod(); - - m = bottommethod(); - m = mybottommethod(); - // :: error: (assignment.type.incompatible) - m = notbottommethod(); - // :: error: (assignment.type.incompatible) - m = mynotbottommethod(); - - @VariableNameDefaultBottom int b; - - // :: error: (assignment.type.incompatible) - b = middlemethod(); - // :: error: (assignment.type.incompatible) - b = mymiddlemethod(); - // :: error: (assignment.type.incompatible) - b = notmiddlemethod(); - // :: error: (assignment.type.incompatible) - b = mynotmiddlemethod(); - - b = bottommethod(); - b = mybottommethod(); - // :: error: (assignment.type.incompatible) - b = notbottommethod(); - // :: error: (assignment.type.incompatible) - b = mynotbottommethod(); - } + int top; + + int middle; + int middlevar; + int mymiddle; + int notmiddle; + int notmiddlevar; + @VariableNameDefaultMiddle int namedbottombutnot; + + int bottom; + int bottomvar; + int mybottom; + int notbottom; + int notbottomvar; + @VariableNameDefaultBottom int namedmiddlebutnot; + + void testFields() { + + @VariableNameDefaultTop int t; + + t = top; + + t = middle; + t = middlevar; + t = mymiddle; + t = notmiddle; + t = notmiddlevar; + t = namedbottombutnot; + + t = bottom; + t = bottomvar; + t = mybottom; + t = notbottom; + t = notbottomvar; + t = namedmiddlebutnot; + + @VariableNameDefaultMiddle int m; + + // :: error: (assignment.type.incompatible) + m = top; + + m = middle; + m = middlevar; + m = mymiddle; + // :: error: (assignment.type.incompatible) + m = notmiddle; + // :: error: (assignment.type.incompatible) + m = notmiddlevar; + m = namedbottombutnot; + + m = bottom; + m = bottomvar; + m = mybottom; + // :: error: (assignment.type.incompatible) + m = notbottom; + // :: error: (assignment.type.incompatible) + m = notbottomvar; + m = namedmiddlebutnot; + + @VariableNameDefaultBottom int b; + + // :: error: (assignment.type.incompatible) + b = top; + + // :: error: (assignment.type.incompatible) + b = middle; + // :: error: (assignment.type.incompatible) + b = middlevar; + // :: error: (assignment.type.incompatible) + b = mymiddle; + // :: error: (assignment.type.incompatible) + b = notmiddle; + // :: error: (assignment.type.incompatible) + b = notmiddlevar; + // :: error: (assignment.type.incompatible) + b = namedbottombutnot; + + b = bottom; + b = bottomvar; + b = mybottom; + // :: error: (assignment.type.incompatible) + b = notbottom; + // :: error: (assignment.type.incompatible) + b = notbottomvar; + b = namedmiddlebutnot; + } + + void testFormals(int middle, int notmiddle, int bottom, int notbottom) { + + @VariableNameDefaultTop int t; + + t = middle; + t = notmiddle; + t = bottom; + t = notbottom; + + @VariableNameDefaultMiddle int m; + + m = middle; + // :: error: (assignment.type.incompatible) + m = notmiddle; + m = bottom; + // :: error: (assignment.type.incompatible) + m = notbottom; + + @VariableNameDefaultBottom int b; + + // :: error: (assignment.type.incompatible) + b = middle; + // :: error: (assignment.type.incompatible) + b = notmiddle; + b = bottom; + // :: error: (assignment.type.incompatible) + b = notbottom; + } + + int middlemethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int mymiddlemethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int notmiddlemethod() { + return 0; + } + + int mynotmiddlemethod() { + return 0; + } + + int bottommethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int mybottommethod() { + // :: error: (return.type.incompatible) + return 0; + } + + int notbottommethod() { + return 0; + } + + int mynotbottommethod() { + return 0; + } + + void testMethods() { + + @VariableNameDefaultTop int t; + + t = middlemethod(); + t = mymiddlemethod(); + t = notmiddlemethod(); + t = mynotmiddlemethod(); + + t = bottommethod(); + t = mybottommethod(); + t = notbottommethod(); + t = mynotbottommethod(); + + @VariableNameDefaultMiddle int m; + + m = middlemethod(); + m = mymiddlemethod(); + // :: error: (assignment.type.incompatible) + m = notmiddlemethod(); + // :: error: (assignment.type.incompatible) + m = mynotmiddlemethod(); + + m = bottommethod(); + m = mybottommethod(); + // :: error: (assignment.type.incompatible) + m = notbottommethod(); + // :: error: (assignment.type.incompatible) + m = mynotbottommethod(); + + @VariableNameDefaultBottom int b; + + // :: error: (assignment.type.incompatible) + b = middlemethod(); + // :: error: (assignment.type.incompatible) + b = mymiddlemethod(); + // :: error: (assignment.type.incompatible) + b = notmiddlemethod(); + // :: error: (assignment.type.incompatible) + b = mynotmiddlemethod(); + + b = bottommethod(); + b = mybottommethod(); + // :: error: (assignment.type.incompatible) + b = notbottommethod(); + // :: error: (assignment.type.incompatible) + b = mynotbottommethod(); + } } diff --git a/framework/tests/viewpointtest/LostNonReflexive.java b/framework/tests/viewpointtest/LostNonReflexive.java index f09c4acaf99..eacc4c9b132 100644 --- a/framework/tests/viewpointtest/LostNonReflexive.java +++ b/framework/tests/viewpointtest/LostNonReflexive.java @@ -1,36 +1,36 @@ import viewpointtest.quals.*; public class LostNonReflexive { - @ReceiverDependentQual Object f; - - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - @ReceiverDependentQual LostNonReflexive(@ReceiverDependentQual Object args) {} - - @ReceiverDependentQual Object get() { - return null; - } - - void set(@ReceiverDependentQual Object o) {} - - void test(@Top LostNonReflexive obj, @Bottom Object bottomObj) { - // :: error: (assignment.type.incompatible) - this.f = obj.f; - this.f = bottomObj; - - // :: error: (assignment.type.incompatible) - @A Object aObj = obj.get(); - // :: error: (assignment.type.incompatible) - @B Object bObj = obj.get(); - // :: error: (assignment.type.incompatible) - @Bottom Object botObj = obj.get(); - - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - new LostNonReflexive(obj.f); - // :: error: (new.class.type.invalid) - new LostNonReflexive(bottomObj); - - // :: error: (argument.type.incompatible) - this.set(obj.f); - this.set(bottomObj); - } + @ReceiverDependentQual Object f; + + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @ReceiverDependentQual LostNonReflexive(@ReceiverDependentQual Object args) {} + + @ReceiverDependentQual Object get() { + return null; + } + + void set(@ReceiverDependentQual Object o) {} + + void test(@Top LostNonReflexive obj, @Bottom Object bottomObj) { + // :: error: (assignment.type.incompatible) + this.f = obj.f; + this.f = bottomObj; + + // :: error: (assignment.type.incompatible) + @A Object aObj = obj.get(); + // :: error: (assignment.type.incompatible) + @B Object bObj = obj.get(); + // :: error: (assignment.type.incompatible) + @Bottom Object botObj = obj.get(); + + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + new LostNonReflexive(obj.f); + // :: error: (new.class.type.invalid) + new LostNonReflexive(bottomObj); + + // :: error: (argument.type.incompatible) + this.set(obj.f); + this.set(bottomObj); + } } diff --git a/framework/tests/viewpointtest/PolyConstructor.java b/framework/tests/viewpointtest/PolyConstructor.java index c66f17dd03e..eec0533b8ae 100644 --- a/framework/tests/viewpointtest/PolyConstructor.java +++ b/framework/tests/viewpointtest/PolyConstructor.java @@ -2,53 +2,53 @@ public class PolyConstructor { - static class MyClass { - // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - @PolyVP MyClass(@PolyVP Object o) { - // :: error: (new.class.type.invalid) - throw new RuntimeException(" * You are filled with DETERMINATION."); // stub + static class MyClass { + // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) + @PolyVP MyClass(@PolyVP Object o) { + // :: error: (new.class.type.invalid) + throw new RuntimeException(" * You are filled with DETERMINATION."); // stub + } + + void throwTopException() { + // :: error: (new.class.type.invalid) + throw new @Top RuntimeException(); + } + + void throwBottomException() { + // :: warning: (cast.unsafe.constructor.invocation) + throw new @Bottom RuntimeException(); + } + + void throwAException() { + // :: warning: (cast.unsafe.constructor.invocation) + throw new @A RuntimeException(); + } + + void throwBException() { + // :: warning: (cast.unsafe.constructor.invocation) + throw new @B RuntimeException(); + } + + void throwLostException() { + // :: error: (new.class.type.invalid) :: warning: (cast.unsafe.constructor.invocation) + throw new @Lost RuntimeException(); + } } - void throwTopException() { - // :: error: (new.class.type.invalid) - throw new @Top RuntimeException(); + void tests(@A Object ao) { + // After poly resolution, the invocation resolved to @A MyClass + @A MyClass myA = new MyClass(ao); + // :: error: (assignment.type.incompatible) + @B MyClass myB = new MyClass(ao); + + // Both argument "ao" and @B are parts of poly resolution + // After poly resolution, the invocation resolved to @Top MyClass then casted to @B + // The @B acts as a downcasting and will issue a warning + // :: warning: (cast.unsafe.constructor.invocation) + MyClass myTop = new @B MyClass(ao); + // :: warning: (cast.unsafe.constructor.invocation) + myB = new @B MyClass(ao); + // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) + myA = new @B MyClass(ao); } - - void throwBottomException() { - // :: warning: (cast.unsafe.constructor.invocation) - throw new @Bottom RuntimeException(); - } - - void throwAException() { - // :: warning: (cast.unsafe.constructor.invocation) - throw new @A RuntimeException(); - } - - void throwBException() { - // :: warning: (cast.unsafe.constructor.invocation) - throw new @B RuntimeException(); - } - - void throwLostException() { - // :: error: (new.class.type.invalid) :: warning: (cast.unsafe.constructor.invocation) - throw new @Lost RuntimeException(); - } - } - - void tests(@A Object ao) { - // After poly resolution, the invocation resolved to @A MyClass - @A MyClass myA = new MyClass(ao); - // :: error: (assignment.type.incompatible) - @B MyClass myB = new MyClass(ao); - - // Both argument "ao" and @B are parts of poly resolution - // After poly resolution, the invocation resolved to @Top MyClass then casted to @B - // The @B acts as a downcasting and will issue a warning - // :: warning: (cast.unsafe.constructor.invocation) - MyClass myTop = new @B MyClass(ao); - // :: warning: (cast.unsafe.constructor.invocation) - myB = new @B MyClass(ao); - // :: error: (assignment.type.incompatible) :: warning: (cast.unsafe.constructor.invocation) - myA = new @B MyClass(ao); - } } diff --git a/framework/tests/viewpointtest/PolyWithVPA.java b/framework/tests/viewpointtest/PolyWithVPA.java index 27ddad6b37f..7b61dd50005 100644 --- a/framework/tests/viewpointtest/PolyWithVPA.java +++ b/framework/tests/viewpointtest/PolyWithVPA.java @@ -1,20 +1,20 @@ import viewpointtest.quals.*; public class PolyWithVPA { - static class PolyClass { - @ReceiverDependentQual Object foo(@PolyVP Object o) { - return null; + static class PolyClass { + @ReceiverDependentQual Object foo(@PolyVP Object o) { + return null; + } } - } - static void test1(@A PolyClass a, @B Object bObj) { - @A Object aObj = a.foo(bObj); - } + static void test1(@A PolyClass a, @B Object bObj) { + @A Object aObj = a.foo(bObj); + } - // only poly annos in decl are resolved - static void test2(@PolyVP PolyClass poly, @B Object bObj) { - @PolyVP Object polyObj = poly.foo(bObj); - // :: error: (assignment.type.incompatible) - @B Object anotherBObj = poly.foo(bObj); - } + // only poly annos in decl are resolved + static void test2(@PolyVP PolyClass poly, @B Object bObj) { + @PolyVP Object polyObj = poly.foo(bObj); + // :: error: (assignment.type.incompatible) + @B Object anotherBObj = poly.foo(bObj); + } } diff --git a/framework/tests/viewpointtest/SuperConstructorCalls.java b/framework/tests/viewpointtest/SuperConstructorCalls.java index a55a2281796..fc0e5a66f83 100644 --- a/framework/tests/viewpointtest/SuperConstructorCalls.java +++ b/framework/tests/viewpointtest/SuperConstructorCalls.java @@ -4,42 +4,42 @@ public class SuperConstructorCalls { - public SuperConstructorCalls() {} - - public SuperConstructorCalls(@ReceiverDependentQual Object obj) {} - - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - public @ReceiverDependentQual SuperConstructorCalls( - @ReceiverDependentQual Object obj, int dummy) {} - - class Inner extends SuperConstructorCalls { - public Inner() { - super(); - } - - // The constructor's return type is implicitly @Top by default. - // When calling the super constructor, @Top becomes @Lost in the super constructor's - // signature, causing a type mismatch with the expected @ReceiverDependentQual parameter. - public Inner(@Top Object objTop) { - // :: error: (argument.type.incompatible) - super(objTop); - } - - @SuppressWarnings("inconsistent.constructor.type") - public @A Inner(@A Object objA, int dummy) { - super(objA, 0); - } - - @SuppressWarnings("inconsistent.constructor.type") - public @A Inner(@A Object objA, @B Object objB) { - // :: error: (super.invocation.invalid) - super(objA); - } - - @SuppressWarnings("inconsistent.constructor.type") - public @A Inner(@A Object objA, @B Object objB, int dummy) { - // :: error: (argument.type.incompatible) - super(objB, 0); + public SuperConstructorCalls() {} + + public SuperConstructorCalls(@ReceiverDependentQual Object obj) {} + + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + public @ReceiverDependentQual SuperConstructorCalls( + @ReceiverDependentQual Object obj, int dummy) {} + + class Inner extends SuperConstructorCalls { + public Inner() { + super(); + } + + // The constructor's return type is implicitly @Top by default. + // When calling the super constructor, @Top becomes @Lost in the super constructor's + // signature, causing a type mismatch with the expected @ReceiverDependentQual parameter. + public Inner(@Top Object objTop) { + // :: error: (argument.type.incompatible) + super(objTop); + } + + @SuppressWarnings("inconsistent.constructor.type") + public @A Inner(@A Object objA, int dummy) { + super(objA, 0); + } + + @SuppressWarnings("inconsistent.constructor.type") + public @A Inner(@A Object objA, @B Object objB) { + // :: error: (super.invocation.invalid) + super(objA); + } + + @SuppressWarnings("inconsistent.constructor.type") + public @A Inner(@A Object objA, @B Object objB, int dummy) { + // :: error: (argument.type.incompatible) + super(objB, 0); + } } - } } diff --git a/framework/tests/viewpointtest/TestGetAnnotatedLhs.java b/framework/tests/viewpointtest/TestGetAnnotatedLhs.java index 807f61f75bb..bf2ee83a54c 100644 --- a/framework/tests/viewpointtest/TestGetAnnotatedLhs.java +++ b/framework/tests/viewpointtest/TestGetAnnotatedLhs.java @@ -4,41 +4,41 @@ import viewpointtest.quals.Top; @ReceiverDependentQual class TestGetAnnotatedLhs { - @ReceiverDependentQual Object f; + @ReceiverDependentQual Object f; - @SuppressWarnings({ - "inconsistent.constructor.type", - "super.invocation.invalid", - "cast.unsafe.constructor.invocation" - }) - @ReceiverDependentQual TestGetAnnotatedLhs() { - this.f = new @ReceiverDependentQual Object(); - } + @SuppressWarnings({ + "inconsistent.constructor.type", + "super.invocation.invalid", + "cast.unsafe.constructor.invocation" + }) + @ReceiverDependentQual TestGetAnnotatedLhs() { + this.f = new @ReceiverDependentQual Object(); + } - @SuppressWarnings({"cast.unsafe.constructor.invocation"}) - void topWithRefinement() { - TestGetAnnotatedLhs a = new @A TestGetAnnotatedLhs(); - // :: error: (new.class.type.invalid) - TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); - top = a; - // When checking the below assignment, GenericAnnotatedTypeFactory#getAnnotatedTypeLhs() - // will be called to get the type of the lhs tree (top.f). - // Previously this method will completely disable flow refinment, and top.f will have type - // @Top and thus accept the below assingment. But we should reject it, as top - // is refined to be @A before. As long as top is still pointing to the @A object, top.f - // will have type @A, which is not the supertype of @B. - // :: error: (assignment.type.incompatible) - top.f = new @B Object(); - top.f = new @A Object(); // no error here - } + @SuppressWarnings({"cast.unsafe.constructor.invocation"}) + void topWithRefinement() { + TestGetAnnotatedLhs a = new @A TestGetAnnotatedLhs(); + // :: error: (new.class.type.invalid) + TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); + top = a; + // When checking the below assignment, GenericAnnotatedTypeFactory#getAnnotatedTypeLhs() + // will be called to get the type of the lhs tree (top.f). + // Previously this method will completely disable flow refinment, and top.f will have type + // @Top and thus accept the below assingment. But we should reject it, as top + // is refined to be @A before. As long as top is still pointing to the @A object, top.f + // will have type @A, which is not the supertype of @B. + // :: error: (assignment.type.incompatible) + top.f = new @B Object(); + top.f = new @A Object(); // no error here + } - @SuppressWarnings({"cast.unsafe.constructor.invocation"}) - void topWithoutRefinement() { - // :: error: (new.class.type.invalid) - TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); - // :: error: (assignment.type.incompatible) - top.f = new @B Object(); - // :: error: (assignment.type.incompatible) - top.f = new @A Object(); - } + @SuppressWarnings({"cast.unsafe.constructor.invocation"}) + void topWithoutRefinement() { + // :: error: (new.class.type.invalid) + TestGetAnnotatedLhs top = new @Top TestGetAnnotatedLhs(); + // :: error: (assignment.type.incompatible) + top.f = new @B Object(); + // :: error: (assignment.type.incompatible) + top.f = new @A Object(); + } } diff --git a/framework/tests/viewpointtest/ThisConstructorCalls.java b/framework/tests/viewpointtest/ThisConstructorCalls.java index 0dad4a9d0ad..0c7567059e7 100644 --- a/framework/tests/viewpointtest/ThisConstructorCalls.java +++ b/framework/tests/viewpointtest/ThisConstructorCalls.java @@ -3,28 +3,28 @@ // Test case for EISOP issue #782: // https://github.com/eisop/checker-framework/issues/782 public class ThisConstructorCalls { - public ThisConstructorCalls() {} + public ThisConstructorCalls() {} - public ThisConstructorCalls(@ReceiverDependentQual Object obj) {} + public ThisConstructorCalls(@ReceiverDependentQual Object obj) {} - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - public @ReceiverDependentQual ThisConstructorCalls( - @ReceiverDependentQual Object obj, int dummy) {} + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + public @ReceiverDependentQual ThisConstructorCalls( + @ReceiverDependentQual Object obj, int dummy) {} - @SuppressWarnings("inconsistent.constructor.type") - public @A ThisConstructorCalls(@A Object objA, int dummy1, int dummy2) { - this(objA, 0); - } + @SuppressWarnings("inconsistent.constructor.type") + public @A ThisConstructorCalls(@A Object objA, int dummy1, int dummy2) { + this(objA, 0); + } - @SuppressWarnings("inconsistent.constructor.type") - public @A ThisConstructorCalls(@B Object objB, int dummy, int dummy2, int dummy3) { - // :: error: (argument.type.incompatible) - this(objB, 0); - } + @SuppressWarnings("inconsistent.constructor.type") + public @A ThisConstructorCalls(@B Object objB, int dummy, int dummy2, int dummy3) { + // :: error: (argument.type.incompatible) + this(objB, 0); + } - @SuppressWarnings("inconsistent.constructor.type") - public @A ThisConstructorCalls(@A Object objA, @B Object objB) { - // :: error: (this.invocation.invalid) - this(objA); - } + @SuppressWarnings("inconsistent.constructor.type") + public @A ThisConstructorCalls(@A Object objA, @B Object objB) { + // :: error: (this.invocation.invalid) + this(objA); + } } diff --git a/framework/tests/viewpointtest/VPAExamples.java b/framework/tests/viewpointtest/VPAExamples.java index 438f40efa78..8cc915c2a5e 100644 --- a/framework/tests/viewpointtest/VPAExamples.java +++ b/framework/tests/viewpointtest/VPAExamples.java @@ -2,36 +2,36 @@ public class VPAExamples { - static class RDContainer { - @ReceiverDependentQual Object get() { - return null; - } + static class RDContainer { + @ReceiverDependentQual Object get() { + return null; + } - void set(@ReceiverDependentQual Object o) {} + void set(@ReceiverDependentQual Object o) {} - @ReceiverDependentQual Object field; - } + @ReceiverDependentQual Object field; + } - void tests(@A RDContainer a, @B RDContainer b, @Top RDContainer top) { - @A Object aObj = a.get(); - @B Object bObj = b.get(); - @Top Object tObj = top.get(); - // :: error: (assignment.type.incompatible) - bObj = a.get(); - // :: error: (assignment.type.incompatible) - aObj = top.get(); - // :: error: (assignment.type.incompatible) - bObj = top.get(); + void tests(@A RDContainer a, @B RDContainer b, @Top RDContainer top) { + @A Object aObj = a.get(); + @B Object bObj = b.get(); + @Top Object tObj = top.get(); + // :: error: (assignment.type.incompatible) + bObj = a.get(); + // :: error: (assignment.type.incompatible) + aObj = top.get(); + // :: error: (assignment.type.incompatible) + bObj = top.get(); - a.set(aObj); - // :: error: (argument.type.incompatible) - a.set(bObj); - // :: error: (argument.type.incompatible) - b.set(aObj); - b.set(bObj); - // :: error: (argument.type.incompatible) - top.set(aObj); - // :: error: (argument.type.incompatible) - top.set(bObj); - } + a.set(aObj); + // :: error: (argument.type.incompatible) + a.set(bObj); + // :: error: (argument.type.incompatible) + b.set(aObj); + b.set(bObj); + // :: error: (argument.type.incompatible) + top.set(aObj); + // :: error: (argument.type.incompatible) + top.set(bObj); + } } diff --git a/framework/tests/viewpointtest/VarargsConstructor.java b/framework/tests/viewpointtest/VarargsConstructor.java index 63436b7d91a..bacd874f592 100644 --- a/framework/tests/viewpointtest/VarargsConstructor.java +++ b/framework/tests/viewpointtest/VarargsConstructor.java @@ -4,70 +4,70 @@ public class VarargsConstructor { - VarargsConstructor(String str, Object... args) {} + VarargsConstructor(String str, Object... args) {} - @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) - @ReceiverDependentQual VarargsConstructor(@ReceiverDependentQual Object... args) {} - - void foo() { - // :: warning: (cast.unsafe.constructor.invocation) - VarargsConstructor a = new @A VarargsConstructor("testStr", new @A Object()); - } - - void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { - @A Object a = new @A VarargsConstructor(aObj); - @B Object b = new @B VarargsConstructor(bObj); - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - @Top Object top = new @Top VarargsConstructor(topObj); - // :: error: (argument.type.incompatible) - new @A VarargsConstructor(bObj); - // :: error: (argument.type.incompatible) - new @B VarargsConstructor(aObj); - } - - class Inner { - // :: warning: (inconsistent.constructor.type) :: error:(super.invocation.invalid) - @ReceiverDependentQual Inner(@ReceiverDependentQual Object... args) {} + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @ReceiverDependentQual VarargsConstructor(@ReceiverDependentQual Object... args) {} void foo() { - // :: error: (new.class.type.invalid) - Inner a = new Inner(); - // :: warning: (cast.unsafe.constructor.invocation) - Inner b = new @A Inner(new @A Object()); - Inner c = VarargsConstructor.this.new @A Inner(); - // :: warning: (cast.unsafe.constructor.invocation) - Inner d = VarargsConstructor.this.new @A Inner(new @A Object()); + // :: warning: (cast.unsafe.constructor.invocation) + VarargsConstructor a = new @A VarargsConstructor("testStr", new @A Object()); } void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { - @A Object a = new @A Inner(aObj); - @B Object b = new @B Inner(bObj); - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - @Top Object top = new @Top Inner(topObj); - // :: error: (argument.type.incompatible) - new @A Inner(bObj); - // :: error: (argument.type.incompatible) - new @B Inner(aObj); + @A Object a = new @A VarargsConstructor(aObj); + @B Object b = new @B VarargsConstructor(bObj); + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + @Top Object top = new @Top VarargsConstructor(topObj); + // :: error: (argument.type.incompatible) + new @A VarargsConstructor(bObj); + // :: error: (argument.type.incompatible) + new @B VarargsConstructor(aObj); } - } - void testAnonymousClass(@A Object aObj, @B Object bObj, @Top Object topObj) { - Object o = - // :: warning: (cast.unsafe.constructor.invocation) - new @A VarargsConstructor("testStr", new @A Object()) { - void foo() { - VarargsConstructor a = + class Inner { + // :: warning: (inconsistent.constructor.type) :: error:(super.invocation.invalid) + @ReceiverDependentQual Inner(@ReceiverDependentQual Object... args) {} + + void foo() { + // :: error: (new.class.type.invalid) + Inner a = new Inner(); + // :: warning: (cast.unsafe.constructor.invocation) + Inner b = new @A Inner(new @A Object()); + Inner c = VarargsConstructor.this.new @A Inner(); + // :: warning: (cast.unsafe.constructor.invocation) + Inner d = VarargsConstructor.this.new @A Inner(new @A Object()); + } + + void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { + @A Object a = new @A Inner(aObj); + @B Object b = new @B Inner(bObj); + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + @Top Object top = new @Top Inner(topObj); + // :: error: (argument.type.incompatible) + new @A Inner(bObj); + // :: error: (argument.type.incompatible) + new @B Inner(aObj); + } + } + + void testAnonymousClass(@A Object aObj, @B Object bObj, @Top Object topObj) { + Object o = // :: warning: (cast.unsafe.constructor.invocation) - new @A VarargsConstructor("testStr", new @A Object()); - } - }; - @A Object a = new @A VarargsConstructor(aObj) {}; - @B Object b = new @B VarargsConstructor(bObj) {}; - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) - @Top Object top = new @Top VarargsConstructor(topObj) {}; - // :: error: (argument.type.incompatible) - new @A VarargsConstructor(bObj) {}; - // :: error: (argument.type.incompatible) - new @B VarargsConstructor(aObj) {}; - } + new @A VarargsConstructor("testStr", new @A Object()) { + void foo() { + VarargsConstructor a = + // :: warning: (cast.unsafe.constructor.invocation) + new @A VarargsConstructor("testStr", new @A Object()); + } + }; + @A Object a = new @A VarargsConstructor(aObj) {}; + @B Object b = new @B VarargsConstructor(bObj) {}; + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + @Top Object top = new @Top VarargsConstructor(topObj) {}; + // :: error: (argument.type.incompatible) + new @A VarargsConstructor(bObj) {}; + // :: error: (argument.type.incompatible) + new @B VarargsConstructor(aObj) {}; + } } diff --git a/gradle-mvn-push.gradle b/gradle-mvn-push.gradle index ceba044ad3c..74030cf5001 100644 --- a/gradle-mvn-push.gradle +++ b/gradle-mvn-push.gradle @@ -4,37 +4,37 @@ apply plugin: 'signing' final isSnapshot = version.contains('SNAPSHOT') // https://github.com/johnrengelman/shadow/issues/586#issuecomment-708375599 components.java.withVariantsFromConfiguration(configurations.shadowRuntimeElements) { - skip() + skip() } publishing { - repositories { - maven { - url = (isSnapshot - ? project.properties.getOrDefault('SNAPSHOT_REPOSITORY_URL', 'https://oss.sonatype.org/content/repositories/snapshots/') - : project.properties.getOrDefault('RELEASE_REPOSITORY_URL', 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') - ) - credentials { - username = project.properties.get('SONATYPE_NEXUS_USERNAME') - password = project.properties.get('SONATYPE_NEXUS_PASSWORD') - } + repositories { + maven { + url = (isSnapshot + ? project.properties.getOrDefault('SNAPSHOT_REPOSITORY_URL', 'https://oss.sonatype.org/content/repositories/snapshots/') + : project.properties.getOrDefault('RELEASE_REPOSITORY_URL', 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') + ) + credentials { + username = project.properties.get('SONATYPE_NEXUS_USERNAME') + password = project.properties.get('SONATYPE_NEXUS_PASSWORD') + } + } } - } } signing { - // Use external gpg cmd. This makes it easy to use gpg-agent, - // to avoid being prompted for a password once per artifact. - useGpgCmd() + // Use external gpg cmd. This makes it easy to use gpg-agent, + // to avoid being prompted for a password once per artifact. + useGpgCmd() - // If anything about signing is misconfigured, fail loudly rather than quietly continuing with - // unsigned artifacts. - required = true + // If anything about signing is misconfigured, fail loudly rather than quietly continuing with + // unsigned artifacts. + required = true } // Only sign releases; snapshots are unsigned. tasks.withType(Sign).configureEach { - onlyIf { - !isSnapshot && project.hasProperty("release") - } + onlyIf { + !isSnapshot && project.hasProperty("release") + } } diff --git a/javacutil/build.gradle b/javacutil/build.gradle index 3612f3ffc1f..f4a17396271 100644 --- a/javacutil/build.gradle +++ b/javacutil/build.gradle @@ -1,63 +1,63 @@ plugins { - id 'java-library' + id 'java-library' } repositories { - mavenCentral() + mavenCentral() } configurations { - implementation.extendsFrom(annotatedGuava) + implementation.extendsFrom(annotatedGuava) } dependencies { - implementation project(':checker-qual') - - // This is used by org.checkerframework.javacutil.TypesUtils.isImmutableTypeInJdk. - // https://mvnrepository.com/artifact/org.plumelib/plume-util - implementation "org.plumelib:plume-util:${versions.plumeUtil}" - implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" - // plume-util has an `implementation` dependency on hashmap-util. - // That follows Gradle's rules, but Gradle's rules are not entirely correct: - // https://github.com/gradle/gradle/issues/30054 - // To build with JDK 22+, we need to redeclare that dependency here. - implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" - - // External dependencies: - // If you add an external dependency, you must shadow its packages both in checker.jar and - // and dataflow-shaded.jar. - // Update relocations in these locations: - // * in ../build.gradle in the shadowJar block. - // * in ../dataflow/build.gradle in the createDataflowShaded task. + implementation project(':checker-qual') + + // This is used by org.checkerframework.javacutil.TypesUtils.isImmutableTypeInJdk. + // https://mvnrepository.com/artifact/org.plumelib/plume-util + implementation "org.plumelib:plume-util:${versions.plumeUtil}" + implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" + // plume-util has an `implementation` dependency on hashmap-util. + // That follows Gradle's rules, but Gradle's rules are not entirely correct: + // https://github.com/gradle/gradle/issues/30054 + // To build with JDK 22+, we need to redeclare that dependency here. + implementation "org.plumelib:hashmap-util:${versions.hashmapUtil}" + + // External dependencies: + // If you add an external dependency, you must shadow its packages both in checker.jar and + // and dataflow-shaded.jar. + // Update relocations in these locations: + // * in ../build.gradle in the shadowJar block. + // * in ../dataflow/build.gradle in the createDataflowShaded task. } apply from: rootProject.file('gradle-mvn-push.gradle') final javacUtilPom(publication) { - sharedPublicationConfiguration(publication) - publication.from components.java - - publication.pom { - name = 'Javacutil' - description = 'javacutil contains utility classes for the javac compiler.' - licenses { - license { - name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' - url = 'http://www.gnu.org/software/classpath/license.html' - distribution = 'repo' - } + sharedPublicationConfiguration(publication) + publication.from components.java + + publication.pom { + name = 'Javacutil' + description = 'javacutil contains utility classes for the javac compiler.' + licenses { + license { + name = 'GNU General Public License, version 2 (GPL2), with the classpath exception' + url = 'http://www.gnu.org/software/classpath/license.html' + distribution = 'repo' + } + } } - } } publishing { - publications { - javacUtil(MavenPublication) { - javacUtilPom it + publications { + javacUtil(MavenPublication) { + javacUtilPom it + } } - } } signing { - sign publishing.publications.javacUtil + sign publishing.publications.javacUtil } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java b/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java index cf611cf9762..d100ef4afd9 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AbstractTypeProcessor.java @@ -11,8 +11,12 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; + +import org.checkerframework.dataflow.qual.SideEffectFree; + import java.util.HashSet; import java.util.Set; + import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; @@ -20,7 +24,6 @@ import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementFilter; -import org.checkerframework.dataflow.qual.SideEffectFree; /** * This class is an abstract annotation processor designed to be a convenient superclass for @@ -61,139 +64,141 @@ * declaration annotation phase before classes are analyzed. */ public abstract class AbstractTypeProcessor extends AbstractProcessor { - /** - * The set of fully-qualified element names that should be type-checked. We store the names of the - * elements, in order to prevent possible confusion between different Element instantiations. - */ - private final Set elements = new HashSet<>(); - - /** - * Method {@link #typeProcessingStart()} must be invoked exactly once, before any invocation of - * {@link #typeProcess(TypeElement, TreePath)}. - */ - private boolean hasInvokedTypeProcessingStart = false; - - /** - * Method {@link #typeProcessingOver} must be invoked exactly once, after the last invocation of - * {@link #typeProcess(TypeElement, TreePath)}. - */ - private boolean hasInvokedTypeProcessingOver = false; - - /** The TaskListener registered for completion of attribution. */ - private final AttributionTaskListener listener = new AttributionTaskListener(); - - /** Constructor for subclasses to call. */ - protected AbstractTypeProcessor() {} - - /** - * {@inheritDoc} - * - *

                  Register a TaskListener that will get called after FLOW. - */ - @Override - public synchronized void init(ProcessingEnvironment env) { - super.init(env); - JavacTask.instance(env).addTaskListener(listener); - Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext(); - JavaCompiler compiler = JavaCompiler.instance(ctx); - compiler.shouldStopPolicyIfNoError = - CompileState.max(compiler.shouldStopPolicyIfNoError, CompileState.FLOW); - compiler.shouldStopPolicyIfError = - CompileState.max(compiler.shouldStopPolicyIfError, CompileState.FLOW); - } - - /** - * The use of this method is obsolete in type processors. The method is called during declaration - * annotation processing phase only. It registers the names of elements to process. - */ - @Override - public final boolean process(Set annotations, RoundEnvironment roundEnv) { - for (TypeElement elem : ElementFilter.typesIn(roundEnv.getRootElements())) { - elements.add(elem.getQualifiedName()); + /** + * The set of fully-qualified element names that should be type-checked. We store the names of + * the elements, in order to prevent possible confusion between different Element + * instantiations. + */ + private final Set elements = new HashSet<>(); + + /** + * Method {@link #typeProcessingStart()} must be invoked exactly once, before any invocation of + * {@link #typeProcess(TypeElement, TreePath)}. + */ + private boolean hasInvokedTypeProcessingStart = false; + + /** + * Method {@link #typeProcessingOver} must be invoked exactly once, after the last invocation of + * {@link #typeProcess(TypeElement, TreePath)}. + */ + private boolean hasInvokedTypeProcessingOver = false; + + /** The TaskListener registered for completion of attribution. */ + private final AttributionTaskListener listener = new AttributionTaskListener(); + + /** Constructor for subclasses to call. */ + protected AbstractTypeProcessor() {} + + /** + * {@inheritDoc} + * + *

                  Register a TaskListener that will get called after FLOW. + */ + @Override + public synchronized void init(ProcessingEnvironment env) { + super.init(env); + JavacTask.instance(env).addTaskListener(listener); + Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext(); + JavaCompiler compiler = JavaCompiler.instance(ctx); + compiler.shouldStopPolicyIfNoError = + CompileState.max(compiler.shouldStopPolicyIfNoError, CompileState.FLOW); + compiler.shouldStopPolicyIfError = + CompileState.max(compiler.shouldStopPolicyIfError, CompileState.FLOW); } - return false; - } - - /** - * A method to be called once before the first call to typeProcess. - * - *

                  Subclasses may override this method to do any initialization work. - */ - public void typeProcessingStart() {} - - /** - * Processes a fully-analyzed class that contains a supported annotation (see {@link - * #getSupportedAnnotationTypes()}). - * - *

                  The passed class is always valid type-checked Java code. - * - * @param element element of the analyzed class - * @param tree the tree path to the element, with the leaf being a {@link ClassTree} - */ - public abstract void typeProcess(TypeElement element, TreePath tree); - - /** - * A method to be called once all the classes are processed. - * - *

                  Subclasses may override this method to do any aggregate analysis (e.g. generate report, - * persistence) or resource deallocation. - * - *

                  Method {@link #getCompilerLog()} can be used to access the number of compiler errors. - */ - public void typeProcessingOver() {} - - /** - * Return the compiler log, which contains errors and warnings. - * - * @return the compiler log, which contains errors and warnings - */ - @SideEffectFree - public Log getCompilerLog() { - return Log.instance(((JavacProcessingEnvironment) processingEnv).getContext()); - } - - /** A task listener that invokes the processor whenever a class is fully analyzed. */ - private final class AttributionTaskListener implements TaskListener { + /** + * The use of this method is obsolete in type processors. The method is called during + * declaration annotation processing phase only. It registers the names of elements to process. + */ @Override - public void finished(TaskEvent e) { - if (e.getKind() != TaskEvent.Kind.ANALYZE) { - return; - } - - if (!hasInvokedTypeProcessingStart) { - typeProcessingStart(); - hasInvokedTypeProcessingStart = true; - } - - if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { - typeProcessingOver(); - hasInvokedTypeProcessingOver = true; - } - - if (e.getTypeElement() == null) { - throw new BugInCF("event task without a type element"); - } - if (e.getCompilationUnit() == null) { - throw new BugInCF("event task without compilation unit"); - } - - if (!elements.remove(e.getTypeElement().getQualifiedName())) { - return; - } - - TypeElement elem = e.getTypeElement(); - TreePath p = Trees.instance(processingEnv).getPath(elem); - - typeProcess(elem, p); - - if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { - typeProcessingOver(); - hasInvokedTypeProcessingOver = true; - } + public final boolean process( + Set annotations, RoundEnvironment roundEnv) { + for (TypeElement elem : ElementFilter.typesIn(roundEnv.getRootElements())) { + elements.add(elem.getQualifiedName()); + } + return false; } - @Override - public void started(TaskEvent e) {} - } + /** + * A method to be called once before the first call to typeProcess. + * + *

                  Subclasses may override this method to do any initialization work. + */ + public void typeProcessingStart() {} + + /** + * Processes a fully-analyzed class that contains a supported annotation (see {@link + * #getSupportedAnnotationTypes()}). + * + *

                  The passed class is always valid type-checked Java code. + * + * @param element element of the analyzed class + * @param tree the tree path to the element, with the leaf being a {@link ClassTree} + */ + public abstract void typeProcess(TypeElement element, TreePath tree); + + /** + * A method to be called once all the classes are processed. + * + *

                  Subclasses may override this method to do any aggregate analysis (e.g. generate report, + * persistence) or resource deallocation. + * + *

                  Method {@link #getCompilerLog()} can be used to access the number of compiler errors. + */ + public void typeProcessingOver() {} + + /** + * Return the compiler log, which contains errors and warnings. + * + * @return the compiler log, which contains errors and warnings + */ + @SideEffectFree + public Log getCompilerLog() { + return Log.instance(((JavacProcessingEnvironment) processingEnv).getContext()); + } + + /** A task listener that invokes the processor whenever a class is fully analyzed. */ + private final class AttributionTaskListener implements TaskListener { + + @Override + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ANALYZE) { + return; + } + + if (!hasInvokedTypeProcessingStart) { + typeProcessingStart(); + hasInvokedTypeProcessingStart = true; + } + + if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { + typeProcessingOver(); + hasInvokedTypeProcessingOver = true; + } + + if (e.getTypeElement() == null) { + throw new BugInCF("event task without a type element"); + } + if (e.getCompilationUnit() == null) { + throw new BugInCF("event task without compilation unit"); + } + + if (!elements.remove(e.getTypeElement().getQualifiedName())) { + return; + } + + TypeElement elem = e.getTypeElement(); + TreePath p = Trees.instance(processingEnv).getPath(elem); + + typeProcess(elem, p); + + if (!hasInvokedTypeProcessingOver && elements.isEmpty()) { + typeProcessingOver(); + hasInvokedTypeProcessingOver = true; + } + } + + @Override + public void started(TaskEvent e) {} + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java index c2e421375f5..0cfe6d2a6bb 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java @@ -1,5 +1,15 @@ package org.checkerframework.javacutil; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.plumelib.reflection.ReflectionPlume; +import org.plumelib.util.ArrayMap; +import org.plumelib.util.StringsPlume; + import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; @@ -8,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -26,15 +37,6 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.plumelib.reflection.ReflectionPlume; -import org.plumelib.util.ArrayMap; -import org.plumelib.util.StringsPlume; /** * Builds an annotation mirror that may have some values. @@ -57,784 +59,805 @@ */ public class AnnotationBuilder { - /** The element utilities to use. */ - private final Elements elements; - - /** The type utilities to use. */ - private final Types types; - - /** The type element of the annotation. */ - private final TypeElement annotationElt; - - /** The type of the annotation. */ - private final DeclaredType annotationType; - - /** A mapping from element to AnnotationValue. */ - private final Map elementValues; - - /** - * Create a new AnnotationBuilder for the given annotation and environment (with no - * elements/fields, but they can be added later). - * - * @param env the processing environment - * @param anno the class of the annotation to build - */ - @SuppressWarnings("nullness") // getCanonicalName expected to be non-null - public AnnotationBuilder(ProcessingEnvironment env, Class anno) { - this(env, anno.getCanonicalName()); - } - - /** - * Create a new AnnotationBuilder for the given annotation name (with no elements/fields, but they - * can be added later). - * - * @param env the processing environment - * @param name the canonical name of the annotation to build - */ - public AnnotationBuilder(ProcessingEnvironment env, @FullyQualifiedName CharSequence name) { - this.elements = env.getElementUtils(); - this.types = env.getTypeUtils(); - this.annotationElt = elements.getTypeElement(name); - if (annotationElt == null) { - throw new UserError("Could not find annotation: " + name + ". Is it on the classpath?"); - } - assert annotationElt.getKind() == ElementKind.ANNOTATION_TYPE; - this.annotationType = (DeclaredType) annotationElt.asType(); - this.elementValues = new ArrayMap<>(2); // most annotations have few elements - } - - /** - * Create a new AnnotationBuilder that copies the given annotation, including its elements/fields. - * - * @param env the processing environment - * @param annotation the annotation to copy - */ - public AnnotationBuilder(ProcessingEnvironment env, AnnotationMirror annotation) { - this.elements = env.getElementUtils(); - this.types = env.getTypeUtils(); - - this.annotationType = annotation.getAnnotationType(); - this.annotationElt = (TypeElement) annotationType.asElement(); - this.elementValues = new ArrayMap<>(annotation.getElementValues()); - } - - /** - * Returns the type element of the annotation that is being built. - * - * @return the type element of the annotation that is being built - */ - public TypeElement getAnnotationElt() { - return annotationElt; - } - - /** - * Creates a mapping between element/field names and values. - * - * @param elementName the name of an element/field to initialize - * @param elementValue the initial value for the element/field - * @return a mapping from the element name to the element value - */ - public static Map elementNamesValues( - String elementName, Object elementValue) { - return Collections.singletonMap(elementName, createValue(elementValue)); - } - - /** - * Creates an {@link AnnotationMirror} that uses default values for elements/fields. - * getElementValues on the result returns default values. If any element does not have a default, - * this method throws an exception. - * - *

                  Most clients should use {@link #fromName}, using a Name created by the compiler. This method - * is provided as a convenience to create an AnnotationMirror from scratch in a checker's code. - * - * @param elements the element utilities to use - * @param aClass the annotation class - * @return an {@link AnnotationMirror} of the given type - * @throws UserError if the annotation corresponding to the class could not be loaded - */ - public static AnnotationMirror fromClass(Elements elements, Class aClass) { - return fromClass(elements, aClass, Collections.emptyMap()); - } - - /** - * Creates an {@link AnnotationMirror} given by a particular annotation class and a name-to-value - * mapping for the elements/fields. - * - *

                  For other elements, getElementValues on the result returns default values. If any such - * element does not have a default, this method throws an exception. - * - *

                  Most clients should use {@link #fromName}, using a Name created by the compiler. This method - * is provided as a convenience to create an AnnotationMirror from scratch in a checker's code. - * - * @param elements the element utilities to use - * @param aClass the annotation class - * @param elementNamesValues the values for the annotation's elements/fields - * @return an {@link AnnotationMirror} of the given type - */ - public static AnnotationMirror fromClass( - Elements elements, - Class aClass, - Map elementNamesValues) { - String name = aClass.getCanonicalName(); - assert name != null : "@AssumeAssertion(nullness): assumption"; - AnnotationMirror res = fromName(elements, name, elementNamesValues); - if (res == null) { - String extra = - name.startsWith("org.checkerframework.") - ? "Is the class in checker-qual.jar?" - : "Is the class on the compilation classpath, which is:" - + System.lineSeparator() - + ReflectionPlume.classpathToString(); - throw new UserError("AnnotationBuilder: fromClass can't load class %s%n" + extra, name); - } - return res; - } - - /** - * Creates an {@link AnnotationMirror} given by a particular fully-qualified name. - * getElementValues on the result returns default values. If any element does not have a default, - * this method throws an exception. - * - *

                  This method returns null if the annotation corresponding to the name could not be loaded. - * - * @param elements the element utilities to use - * @param name the name of the annotation to create - * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't be - * loaded - */ - public static @Nullable AnnotationMirror fromName( - Elements elements, @FullyQualifiedName CharSequence name) { - return fromName(elements, name, Collections.emptyMap()); - } - - /** - * Creates an {@link AnnotationMirror} given by a particular fully-qualified name and - * element/field values. If any element is not specified by the {@code elementValues} argument, - * the default value is used. If any such element does not have a default, this method throws an - * exception. - * - *

                  This method returns null if the annotation corresponding to the name could not be loaded. - * - * @param elements the element utilities to use - * @param name the name of the annotation to create - * @param elementNamesValues the values for the annotation's elements/fields - * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't be - * loaded - */ - public static @Nullable AnnotationMirror fromName( - Elements elements, - @FullyQualifiedName CharSequence name, - Map elementNamesValues) { - TypeElement annoElt = elements.getTypeElement(name); - if (annoElt == null) { - return null; - } - if (annoElt.getKind() != ElementKind.ANNOTATION_TYPE) { - throw new BugInCF(annoElt + " is not an annotation"); - } - - DeclaredType annoType = (DeclaredType) annoElt.asType(); - if (annoType == null) { - return null; - } - - List methods = ElementFilter.methodsIn(annoElt.getEnclosedElements()); - Map elementValues = new ArrayMap<>(methods.size()); - for (ExecutableElement annoElement : methods) { - AnnotationValue elementValue = elementNamesValues.get(annoElement.getSimpleName().toString()); - if (elementValue == null) { - AnnotationValue defaultValue = annoElement.getDefaultValue(); - if (defaultValue == null) { - throw new BugInCF( - "AnnotationBuilder.fromName: no value for element %s of %s", annoElement, name); - } else { - elementValue = defaultValue; - } - } - elementValues.put(annoElement, elementValue); - } - - AnnotationMirror result = new CheckerFrameworkAnnotationMirror(annoType, elementValues); - return result; - } - - /** Whether or not {@link #build()} has been called. */ - private boolean wasBuilt = false; - - private void assertNotBuilt() { - if (wasBuilt) { - throw new BugInCF("AnnotationBuilder: error: type was already built"); - } - } - - public AnnotationMirror build() { - assertNotBuilt(); - wasBuilt = true; - return new CheckerFrameworkAnnotationMirror(annotationType, elementValues); - } - - /** - * Copies every element value from the given annotation. If an element in the given annotation - * doesn't exist in the annotation to be built, an error is raised unless the element is specified - * in {@code ignorableElements}. - * - * @param other the annotation that holds the values to be copied; need not be an annotation of - * the same type of the one being built - * @param ignorableElements the names of elements of {@code other} that can be safely dropped - */ - public void copyElementValuesFromAnnotation(AnnotationMirror other, String... ignorableElements) { - List ignorableElementsList = Arrays.asList(ignorableElements); - for (Map.Entry eltValToCopy : - other.getElementValues().entrySet()) { - Name eltNameToCopy = eltValToCopy.getKey().getSimpleName(); - if (ignorableElementsList.contains(eltNameToCopy.toString())) { - continue; - } - elementValues.put(findElement(eltNameToCopy), eltValToCopy.getValue()); - } - } - - /** - * Copies every element value from the given annotation. If an element in the given annotation - * doesn't exist in the annotation to be built, an error is raised unless the element is specified - * in {@code ignorableElements}. - * - * @param valueHolder the annotation that holds the values to be copied; must be the same type as - * the annotation being built - * @param ignorableElements the elements that can be safely dropped - */ - public void copyElementValuesFromAnnotation( - AnnotationMirror valueHolder, Collection ignorableElements) { - for (Map.Entry entry : - valueHolder.getElementValues().entrySet()) { - if (ignorableElements.contains(entry.getKey())) { - continue; - } - elementValues.put(entry.getKey(), entry.getValue()); - } - } - - /** - * Copies the specified element values from the given annotation, using the specified renaming - * map. Each value in the map must be an element name in the annotation being built. If an element - * from the given annotation is not a key in the map, it is ignored. - * - * @param valueHolder the annotation that holds the values to be copied - * @param elementNameRenaming a map from element names in {@code valueHolder} to element names of - * the annotation being built - */ - public void copyRenameElementValuesFromAnnotation( - AnnotationMirror valueHolder, Map elementNameRenaming) { - - for (Map.Entry eltValToCopy : - valueHolder.getElementValues().entrySet()) { - - String sourceName = eltValToCopy.getKey().getSimpleName().toString(); - String targetName = elementNameRenaming.get(sourceName); - if (targetName == null) { - continue; - } - elementValues.put(findElement(targetName), eltValToCopy.getValue()); - } - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, AnnotationMirror value) { - setValue(elementName, (Object) value); - return this; - } - - /** - * Set the element/field with the given name, to the given value. - * - * @param elementName the element/field name - * @param values the new value for the element/field - * @return this - */ - public AnnotationBuilder setValue(CharSequence elementName, List values) { - assertNotBuilt(); - ExecutableElement var = findElement(elementName); - return setValue(var, values); - } - - /** - * Set the element to the given value. - * - * @param element the element - * @param values the new value for the element - * @return this - */ - public AnnotationBuilder setValue( - ExecutableElement element, List values) { - assertNotBuilt(); - TypeMirror expectedType = element.getReturnType(); - if (expectedType.getKind() != TypeKind.ARRAY) { - throw new BugInCF("value is an array while expected type is not"); - } - expectedType = ((ArrayType) expectedType).getComponentType(); - - List avalues = new ArrayList<>(values.size()); - for (Object v : values) { - checkSubtype(expectedType, v); - avalues.add(createValue(v)); - } - AnnotationValue aval = createValue(avalues); - elementValues.put(element, aval); - return this; - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Object[] values) { - return setValue(elementName, Arrays.asList(values)); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Boolean value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Character value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Double value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Float value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Integer value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Long value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, Short value) { - return setValue(elementName, (Object) value); - } - - /** Set the element/field with the given name, to the given value. */ - public AnnotationBuilder setValue(CharSequence elementName, String value) { - return setValue(elementName, (Object) value); - } - - /** - * Remove the element/field with the given name. Does not err if no such element/field is present. - */ - public AnnotationBuilder removeElement(CharSequence elementName) { - assertNotBuilt(); - ExecutableElement var = findElement(elementName); - elementValues.remove(var); - return this; - } - - private TypeMirror getErasedOrBoxedType(TypeMirror type) { - // See com.sun.tools.javac.code.Attribute.Class.makeClassType() - return type.getKind().isPrimitive() - ? types.boxedClass((PrimitiveType) type).asType() - : types.erasure(type); - } - - public AnnotationBuilder setValue(CharSequence elementName, TypeMirror value) { - assertNotBuilt(); - value = getErasedOrBoxedType(value); - AnnotationValue val = createValue(value); - ExecutableElement var = findElement(elementName); - // Check subtyping - if (!TypesUtils.isClass(var.getReturnType())) { - throw new BugInCF("expected " + var.getReturnType()); - } - - elementValues.put(var, val); - return this; - } - - /** - * Given a class, return the corresponding TypeMirror. - * - * @param clazz a class - * @return the TypeMirror corresponding to the given class - */ - private TypeMirror typeFromClass(Class clazz) { - return TypesUtils.typeFromClass(clazz, types, elements); - } - - public AnnotationBuilder setValue(CharSequence elementName, Class value) { - TypeMirror type = typeFromClass(value); - return setValue(elementName, getErasedOrBoxedType(type)); - } - - public AnnotationBuilder setValue(CharSequence elementName, Enum value) { - assertNotBuilt(); - VariableElement enumElt = findEnumElement(value); - return setValue(elementName, enumElt); - } - - public AnnotationBuilder setValue(CharSequence elementName, VariableElement value) { - ExecutableElement var = findElement(elementName); - if (var.getReturnType().getKind() != TypeKind.DECLARED) { - throw new BugInCF("expected a non enum: " + var.getReturnType()); - } - if (!((DeclaredType) var.getReturnType()).asElement().equals(value.getEnclosingElement())) { - throw new BugInCF("expected a different type of enum: " + value.getEnclosingElement()); - } - elementValues.put(var, createValue(value)); - return this; - } - - // Keep this version synchronized with the VariableElement[] version below - public AnnotationBuilder setValue(CharSequence elementName, Enum[] values) { - assertNotBuilt(); - - if (values.length == 0) { - setValue(elementName, Collections.emptyList()); - return this; - } - - VariableElement enumElt = findEnumElement(values[0]); - ExecutableElement var = findElement(elementName); - - TypeMirror expectedType = var.getReturnType(); - if (expectedType.getKind() != TypeKind.ARRAY) { - throw new BugInCF("expected a non array: " + var.getReturnType()); - } - - expectedType = ((ArrayType) expectedType).getComponentType(); - if (expectedType.getKind() != TypeKind.DECLARED) { - throw new BugInCF("expected a non enum component type: " + var.getReturnType()); - } - if (!((DeclaredType) expectedType).asElement().equals(enumElt.getEnclosingElement())) { - throw new BugInCF("expected a different type of enum: " + enumElt.getEnclosingElement()); - } - - List res = new ArrayList<>(values.length); - for (Enum ev : values) { - checkSubtype(expectedType, ev); - enumElt = findEnumElement(ev); - res.add(createValue(enumElt)); - } - AnnotationValue val = createValue(res); - elementValues.put(var, val); - return this; - } - - // Keep this version synchronized with the Enum[] version above. - // Which one is more useful/general? Unifying adds overhead of creating - // another array. - public AnnotationBuilder setValue(CharSequence elementName, VariableElement[] values) { - assertNotBuilt(); - ExecutableElement var = findElement(elementName); - - TypeMirror expectedType = var.getReturnType(); - if (expectedType.getKind() != TypeKind.ARRAY) { - throw new BugInCF("expected an array, but found: " + expectedType); - } - - expectedType = ((ArrayType) expectedType).getComponentType(); - if (expectedType.getKind() != TypeKind.DECLARED) { - throw new BugInCF( - "expected a declared component type, but found: " - + expectedType - + " kind: " - + expectedType.getKind()); - } - if (!types.isSameType((DeclaredType) expectedType, values[0].asType())) { - throw new BugInCF( - "expected a different declared component type: " + expectedType + " vs. " + values[0]); - } - - List res = new ArrayList<>(values.length); - for (VariableElement ev : values) { - checkSubtype(expectedType, ev); - // Is there a better way to distinguish between enums and - // references to constants? - if (ev.getConstantValue() != null) { - res.add(createValue(ev.getConstantValue())); - } else { - res.add(createValue(ev)); - } - } - AnnotationValue val = createValue(res); - elementValues.put(var, val); - return this; - } - - /** Find the VariableElement for the given enum. */ - private VariableElement findEnumElement(Enum value) { - String enumClass = value.getDeclaringClass().getCanonicalName(); - assert enumClass != null : "@AssumeAssertion(nullness): assumption"; - TypeElement enumClassElt = elements.getTypeElement(enumClass); - assert enumClassElt != null; - for (Element enumElt : enumClassElt.getEnclosedElements()) { - if (enumElt.getSimpleName().contentEquals(value.name())) { - return (VariableElement) enumElt; - } - } - throw new BugInCF("cannot be here"); - } - - private AnnotationBuilder setValue(CharSequence key, Object value) { - assertNotBuilt(); - AnnotationValue val = createValue(value); - ExecutableElement var = findElement(key); - checkSubtype(var.getReturnType(), value); - elementValues.put(var, val); - return this; - } - - public ExecutableElement findElement(CharSequence key) { - for (ExecutableElement elt : ElementFilter.methodsIn(annotationElt.getEnclosedElements())) { - if (elt.getSimpleName().contentEquals(key)) { - return elt; - } - } - throw new BugInCF("Couldn't find " + key + " element in " + annotationElt); - } - - /** - * @param expected the expected type - * @param givenValue the object whose run-time class to check - * @throws BugInCF if the type of {@code givenValue} is not the same as {@code expected} - */ - private void checkSubtype(TypeMirror expected, Object givenValue) { - if (expected.getKind().isPrimitive()) { - expected = types.boxedClass((PrimitiveType) expected).asType(); - } - - if (expected.getKind() == TypeKind.DECLARED - && TypesUtils.isClass(expected) - && givenValue instanceof TypeMirror) { - return; - } - - TypeMirror found; - boolean isSubtype; - - if (expected.getKind() == TypeKind.DECLARED - && ((DeclaredType) expected).asElement().getKind() == ElementKind.ANNOTATION_TYPE - && givenValue instanceof AnnotationMirror) { - found = ((AnnotationMirror) givenValue).getAnnotationType(); - isSubtype = ((DeclaredType) expected).asElement().equals(((DeclaredType) found).asElement()); - } else if (givenValue instanceof AnnotationMirror) { - found = ((AnnotationMirror) givenValue).getAnnotationType(); - // TODO: why is this always failing??? - isSubtype = false; - } else if (givenValue instanceof VariableElement) { - found = ((VariableElement) givenValue).asType(); - if (expected.getKind() == TypeKind.DECLARED) { - isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); - } else { - isSubtype = false; - } - } else { - String name = givenValue.getClass().getCanonicalName(); - assert name != null : "@AssumeAssertion(nullness): assumption"; - found = elements.getTypeElement(name).asType(); - isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); - } - if (!isSubtype) { - // Annotations in stub files sometimes are the same type, but Types#isSubtype fails - // anyway. - isSubtype = found.toString().equals(expected.toString()); - } - - if (!isSubtype) { - throw new BugInCF( - "given value differs from expected; " + "found: " + found + "; expected: " + expected); - } - } - - /** - * Create an AnnotationValue -- a value for an annotation element/field. - * - * @param obj the value to be stored in an annotation element/field - * @return an AnnotationValue for the given Java value - */ - private static AnnotationValue createValue(Object obj) { - return new CheckerFrameworkAnnotationValue(obj); - } - - /** Implementation of AnnotationMirror used by the Checker Framework. */ - /* default visibility to allow access from within package. */ - static class CheckerFrameworkAnnotationMirror implements AnnotationMirror { - /** The interned toString value. */ - private @Nullable @Interned String toStringVal; - - /** The annotation type. */ + /** The element utilities to use. */ + private final Elements elements; + + /** The type utilities to use. */ + private final Types types; + + /** The type element of the annotation. */ + private final TypeElement annotationElt; + + /** The type of the annotation. */ private final DeclaredType annotationType; - /** The element values. */ + /** A mapping from element to AnnotationValue. */ private final Map elementValues; - /** The annotation name. */ - // default visibility to allow access from within package. - final @Interned @CanonicalName String annotationName; + /** + * Create a new AnnotationBuilder for the given annotation and environment (with no + * elements/fields, but they can be added later). + * + * @param env the processing environment + * @param anno the class of the annotation to build + */ + @SuppressWarnings("nullness") // getCanonicalName expected to be non-null + public AnnotationBuilder(ProcessingEnvironment env, Class anno) { + this(env, anno.getCanonicalName()); + } + + /** + * Create a new AnnotationBuilder for the given annotation name (with no elements/fields, but + * they can be added later). + * + * @param env the processing environment + * @param name the canonical name of the annotation to build + */ + public AnnotationBuilder(ProcessingEnvironment env, @FullyQualifiedName CharSequence name) { + this.elements = env.getElementUtils(); + this.types = env.getTypeUtils(); + this.annotationElt = elements.getTypeElement(name); + if (annotationElt == null) { + throw new UserError("Could not find annotation: " + name + ". Is it on the classpath?"); + } + assert annotationElt.getKind() == ElementKind.ANNOTATION_TYPE; + this.annotationType = (DeclaredType) annotationElt.asType(); + this.elementValues = new ArrayMap<>(2); // most annotations have few elements + } + + /** + * Create a new AnnotationBuilder that copies the given annotation, including its + * elements/fields. + * + * @param env the processing environment + * @param annotation the annotation to copy + */ + public AnnotationBuilder(ProcessingEnvironment env, AnnotationMirror annotation) { + this.elements = env.getElementUtils(); + this.types = env.getTypeUtils(); + + this.annotationType = annotation.getAnnotationType(); + this.annotationElt = (TypeElement) annotationType.asElement(); + this.elementValues = new ArrayMap<>(annotation.getElementValues()); + } + + /** + * Returns the type element of the annotation that is being built. + * + * @return the type element of the annotation that is being built + */ + public TypeElement getAnnotationElt() { + return annotationElt; + } + + /** + * Creates a mapping between element/field names and values. + * + * @param elementName the name of an element/field to initialize + * @param elementValue the initial value for the element/field + * @return a mapping from the element name to the element value + */ + public static Map elementNamesValues( + String elementName, Object elementValue) { + return Collections.singletonMap(elementName, createValue(elementValue)); + } + + /** + * Creates an {@link AnnotationMirror} that uses default values for elements/fields. + * getElementValues on the result returns default values. If any element does not have a + * default, this method throws an exception. + * + *

                  Most clients should use {@link #fromName}, using a Name created by the compiler. This + * method is provided as a convenience to create an AnnotationMirror from scratch in a checker's + * code. + * + * @param elements the element utilities to use + * @param aClass the annotation class + * @return an {@link AnnotationMirror} of the given type + * @throws UserError if the annotation corresponding to the class could not be loaded + */ + public static AnnotationMirror fromClass( + Elements elements, Class aClass) { + return fromClass(elements, aClass, Collections.emptyMap()); + } + + /** + * Creates an {@link AnnotationMirror} given by a particular annotation class and a + * name-to-value mapping for the elements/fields. + * + *

                  For other elements, getElementValues on the result returns default values. If any such + * element does not have a default, this method throws an exception. + * + *

                  Most clients should use {@link #fromName}, using a Name created by the compiler. This + * method is provided as a convenience to create an AnnotationMirror from scratch in a checker's + * code. + * + * @param elements the element utilities to use + * @param aClass the annotation class + * @param elementNamesValues the values for the annotation's elements/fields + * @return an {@link AnnotationMirror} of the given type + */ + public static AnnotationMirror fromClass( + Elements elements, + Class aClass, + Map elementNamesValues) { + String name = aClass.getCanonicalName(); + assert name != null : "@AssumeAssertion(nullness): assumption"; + AnnotationMirror res = fromName(elements, name, elementNamesValues); + if (res == null) { + String extra = + name.startsWith("org.checkerframework.") + ? "Is the class in checker-qual.jar?" + : "Is the class on the compilation classpath, which is:" + + System.lineSeparator() + + ReflectionPlume.classpathToString(); + throw new UserError("AnnotationBuilder: fromClass can't load class %s%n" + extra, name); + } + return res; + } + + /** + * Creates an {@link AnnotationMirror} given by a particular fully-qualified name. + * getElementValues on the result returns default values. If any element does not have a + * default, this method throws an exception. + * + *

                  This method returns null if the annotation corresponding to the name could not be loaded. + * + * @param elements the element utilities to use + * @param name the name of the annotation to create + * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't + * be loaded + */ + public static @Nullable AnnotationMirror fromName( + Elements elements, @FullyQualifiedName CharSequence name) { + return fromName(elements, name, Collections.emptyMap()); + } + + /** + * Creates an {@link AnnotationMirror} given by a particular fully-qualified name and + * element/field values. If any element is not specified by the {@code elementValues} argument, + * the default value is used. If any such element does not have a default, this method throws an + * exception. + * + *

                  This method returns null if the annotation corresponding to the name could not be loaded. + * + * @param elements the element utilities to use + * @param name the name of the annotation to create + * @param elementNamesValues the values for the annotation's elements/fields + * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't + * be loaded + */ + public static @Nullable AnnotationMirror fromName( + Elements elements, + @FullyQualifiedName CharSequence name, + Map elementNamesValues) { + TypeElement annoElt = elements.getTypeElement(name); + if (annoElt == null) { + return null; + } + if (annoElt.getKind() != ElementKind.ANNOTATION_TYPE) { + throw new BugInCF(annoElt + " is not an annotation"); + } + + DeclaredType annoType = (DeclaredType) annoElt.asType(); + if (annoType == null) { + return null; + } + + List methods = ElementFilter.methodsIn(annoElt.getEnclosedElements()); + Map elementValues = new ArrayMap<>(methods.size()); + for (ExecutableElement annoElement : methods) { + AnnotationValue elementValue = + elementNamesValues.get(annoElement.getSimpleName().toString()); + if (elementValue == null) { + AnnotationValue defaultValue = annoElement.getDefaultValue(); + if (defaultValue == null) { + throw new BugInCF( + "AnnotationBuilder.fromName: no value for element %s of %s", + annoElement, name); + } else { + elementValue = defaultValue; + } + } + elementValues.put(annoElement, elementValue); + } + + AnnotationMirror result = new CheckerFrameworkAnnotationMirror(annoType, elementValues); + return result; + } + + /** Whether or not {@link #build()} has been called. */ + private boolean wasBuilt = false; + + private void assertNotBuilt() { + if (wasBuilt) { + throw new BugInCF("AnnotationBuilder: error: type was already built"); + } + } + + public AnnotationMirror build() { + assertNotBuilt(); + wasBuilt = true; + return new CheckerFrameworkAnnotationMirror(annotationType, elementValues); + } + + /** + * Copies every element value from the given annotation. If an element in the given annotation + * doesn't exist in the annotation to be built, an error is raised unless the element is + * specified in {@code ignorableElements}. + * + * @param other the annotation that holds the values to be copied; need not be an annotation of + * the same type of the one being built + * @param ignorableElements the names of elements of {@code other} that can be safely dropped + */ + public void copyElementValuesFromAnnotation( + AnnotationMirror other, String... ignorableElements) { + List ignorableElementsList = Arrays.asList(ignorableElements); + for (Map.Entry eltValToCopy : + other.getElementValues().entrySet()) { + Name eltNameToCopy = eltValToCopy.getKey().getSimpleName(); + if (ignorableElementsList.contains(eltNameToCopy.toString())) { + continue; + } + elementValues.put(findElement(eltNameToCopy), eltValToCopy.getValue()); + } + } + + /** + * Copies every element value from the given annotation. If an element in the given annotation + * doesn't exist in the annotation to be built, an error is raised unless the element is + * specified in {@code ignorableElements}. + * + * @param valueHolder the annotation that holds the values to be copied; must be the same type + * as the annotation being built + * @param ignorableElements the elements that can be safely dropped + */ + public void copyElementValuesFromAnnotation( + AnnotationMirror valueHolder, Collection ignorableElements) { + for (Map.Entry entry : + valueHolder.getElementValues().entrySet()) { + if (ignorableElements.contains(entry.getKey())) { + continue; + } + elementValues.put(entry.getKey(), entry.getValue()); + } + } + + /** + * Copies the specified element values from the given annotation, using the specified renaming + * map. Each value in the map must be an element name in the annotation being built. If an + * element from the given annotation is not a key in the map, it is ignored. + * + * @param valueHolder the annotation that holds the values to be copied + * @param elementNameRenaming a map from element names in {@code valueHolder} to element names + * of the annotation being built + */ + public void copyRenameElementValuesFromAnnotation( + AnnotationMirror valueHolder, Map elementNameRenaming) { + + for (Map.Entry eltValToCopy : + valueHolder.getElementValues().entrySet()) { + + String sourceName = eltValToCopy.getKey().getSimpleName().toString(); + String targetName = elementNameRenaming.get(sourceName); + if (targetName == null) { + continue; + } + elementValues.put(findElement(targetName), eltValToCopy.getValue()); + } + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, AnnotationMirror value) { + setValue(elementName, (Object) value); + return this; + } + + /** + * Set the element/field with the given name, to the given value. + * + * @param elementName the element/field name + * @param values the new value for the element/field + * @return this + */ + public AnnotationBuilder setValue(CharSequence elementName, List values) { + assertNotBuilt(); + ExecutableElement var = findElement(elementName); + return setValue(var, values); + } + + /** + * Set the element to the given value. + * + * @param element the element + * @param values the new value for the element + * @return this + */ + public AnnotationBuilder setValue( + ExecutableElement element, List values) { + assertNotBuilt(); + TypeMirror expectedType = element.getReturnType(); + if (expectedType.getKind() != TypeKind.ARRAY) { + throw new BugInCF("value is an array while expected type is not"); + } + expectedType = ((ArrayType) expectedType).getComponentType(); + + List avalues = new ArrayList<>(values.size()); + for (Object v : values) { + checkSubtype(expectedType, v); + avalues.add(createValue(v)); + } + AnnotationValue aval = createValue(avalues); + elementValues.put(element, aval); + return this; + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Object[] values) { + return setValue(elementName, Arrays.asList(values)); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Boolean value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Character value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Double value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Float value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Integer value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Long value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, Short value) { + return setValue(elementName, (Object) value); + } + + /** Set the element/field with the given name, to the given value. */ + public AnnotationBuilder setValue(CharSequence elementName, String value) { + return setValue(elementName, (Object) value); + } + + /** + * Remove the element/field with the given name. Does not err if no such element/field is + * present. + */ + public AnnotationBuilder removeElement(CharSequence elementName) { + assertNotBuilt(); + ExecutableElement var = findElement(elementName); + elementValues.remove(var); + return this; + } + + private TypeMirror getErasedOrBoxedType(TypeMirror type) { + // See com.sun.tools.javac.code.Attribute.Class.makeClassType() + return type.getKind().isPrimitive() + ? types.boxedClass((PrimitiveType) type).asType() + : types.erasure(type); + } + + public AnnotationBuilder setValue(CharSequence elementName, TypeMirror value) { + assertNotBuilt(); + value = getErasedOrBoxedType(value); + AnnotationValue val = createValue(value); + ExecutableElement var = findElement(elementName); + // Check subtyping + if (!TypesUtils.isClass(var.getReturnType())) { + throw new BugInCF("expected " + var.getReturnType()); + } + + elementValues.put(var, val); + return this; + } /** - * Create a CheckerFrameworkAnnotationMirror. + * Given a class, return the corresponding TypeMirror. * - * @param annotationType the annotation type - * @param elementValues the element values + * @param clazz a class + * @return the TypeMirror corresponding to the given class */ - @SuppressWarnings("signature:assignment.type.incompatible") // needs JDK annotations - CheckerFrameworkAnnotationMirror( - DeclaredType annotationType, Map elementValues) { - this.annotationType = annotationType; - TypeElement elm = (TypeElement) annotationType.asElement(); - this.annotationName = elm.getQualifiedName().toString().intern(); - this.elementValues = elementValues; - } - - @Override - public DeclaredType getAnnotationType() { - return annotationType; - } - - @Override - public Map getElementValues() { - return Collections.unmodifiableMap(elementValues); - } - - @SideEffectFree - @Override - public String toString() { - if (toStringVal != null) { - return toStringVal; - } - StringBuilder buf = new StringBuilder(); - buf.append("@"); - buf.append(annotationName); - int len = elementValues.size(); - if (len > 0) { - buf.append('('); - boolean first = true; - for (Map.Entry pair : elementValues.entrySet()) { - if (!first) { - buf.append(", "); - } - first = false; - - String name = pair.getKey().getSimpleName().toString(); - if (len > 1 || !name.equals("value")) { - buf.append(name); - buf.append('='); - } - buf.append(pair.getValue()); - } - buf.append(')'); - } - toStringVal = buf.toString().intern(); - return toStringVal; - - // return "@" + annotationType + "(" + elementValues + ")"; - } - } - - /** Implementation of AnnotationValue used by the Checker Framework. */ - private static class CheckerFrameworkAnnotationValue implements AnnotationValue { - /** The value. */ - private final Object value; - - /** The interned value of toString. */ - private @Nullable @Interned String toStringVal; - - /** Create an annotation value. */ - CheckerFrameworkAnnotationValue(Object obj) { - this.value = obj; - } - - @Override - public Object getValue() { - return value; - } - - @SideEffectFree - @Override - public String toString() { - if (this.toStringVal != null) { - return this.toStringVal; - } - String toStringVal; - if (value instanceof String) { - toStringVal = "\"" + value + "\""; - } else if (value instanceof Character) { - toStringVal = "\'" + value + "\'"; - } else if (value instanceof List) { - List list = (List) value; - toStringVal = "{" + StringsPlume.join(", ", list) + "}"; - } else if (value instanceof VariableElement) { - // for Enums - VariableElement var = (VariableElement) value; - String encl = var.getEnclosingElement().toString(); - if (!encl.isEmpty()) { - encl = encl + '.'; - } - toStringVal = encl + var; - } else if (value instanceof TypeMirror && TypesUtils.isClassType((TypeMirror) value)) { - toStringVal = value.toString() + ".class"; - } else { - toStringVal = value.toString(); - } - this.toStringVal = toStringVal.intern(); - return this.toStringVal; - } - - @SuppressWarnings("unchecked") - @Override - public R accept(AnnotationValueVisitor v, P p) { - if (value instanceof AnnotationMirror) { - return v.visitAnnotation((AnnotationMirror) value, p); - } else if (value instanceof List) { - return v.visitArray((List) value, p); - } else if (value instanceof Boolean) { - return v.visitBoolean((Boolean) value, p); - } else if (value instanceof Character) { - return v.visitChar((Character) value, p); - } else if (value instanceof Double) { - return v.visitDouble((Double) value, p); - } else if (value instanceof VariableElement) { - return v.visitEnumConstant((VariableElement) value, p); - } else if (value instanceof Float) { - return v.visitFloat((Float) value, p); - } else if (value instanceof Integer) { - return v.visitInt((Integer) value, p); - } else if (value instanceof Long) { - return v.visitLong((Long) value, p); - } else if (value instanceof Short) { - return v.visitShort((Short) value, p); - } else if (value instanceof String) { - return v.visitString((String) value, p); - } else if (value instanceof TypeMirror) { - return v.visitType((TypeMirror) value, p); - } else { - assert false : " unknown type : " + v.getClass(); - return v.visitUnknown(this, p); - } - } - - @Override - public boolean equals(@Nullable Object obj) { - // System.out.printf("Calling CFAV.equals()%n"); - if (!(obj instanceof AnnotationValue)) { - return false; - } - AnnotationValue other = (AnnotationValue) obj; - return Objects.equals(this.getValue(), other.getValue()); - } - - @Override - public int hashCode() { - return Objects.hashCode(this.value); - } - } + private TypeMirror typeFromClass(Class clazz) { + return TypesUtils.typeFromClass(clazz, types, elements); + } + + public AnnotationBuilder setValue(CharSequence elementName, Class value) { + TypeMirror type = typeFromClass(value); + return setValue(elementName, getErasedOrBoxedType(type)); + } + + public AnnotationBuilder setValue(CharSequence elementName, Enum value) { + assertNotBuilt(); + VariableElement enumElt = findEnumElement(value); + return setValue(elementName, enumElt); + } + + public AnnotationBuilder setValue(CharSequence elementName, VariableElement value) { + ExecutableElement var = findElement(elementName); + if (var.getReturnType().getKind() != TypeKind.DECLARED) { + throw new BugInCF("expected a non enum: " + var.getReturnType()); + } + if (!((DeclaredType) var.getReturnType()).asElement().equals(value.getEnclosingElement())) { + throw new BugInCF("expected a different type of enum: " + value.getEnclosingElement()); + } + elementValues.put(var, createValue(value)); + return this; + } + + // Keep this version synchronized with the VariableElement[] version below + public AnnotationBuilder setValue(CharSequence elementName, Enum[] values) { + assertNotBuilt(); + + if (values.length == 0) { + setValue(elementName, Collections.emptyList()); + return this; + } + + VariableElement enumElt = findEnumElement(values[0]); + ExecutableElement var = findElement(elementName); + + TypeMirror expectedType = var.getReturnType(); + if (expectedType.getKind() != TypeKind.ARRAY) { + throw new BugInCF("expected a non array: " + var.getReturnType()); + } + + expectedType = ((ArrayType) expectedType).getComponentType(); + if (expectedType.getKind() != TypeKind.DECLARED) { + throw new BugInCF("expected a non enum component type: " + var.getReturnType()); + } + if (!((DeclaredType) expectedType).asElement().equals(enumElt.getEnclosingElement())) { + throw new BugInCF( + "expected a different type of enum: " + enumElt.getEnclosingElement()); + } + + List res = new ArrayList<>(values.length); + for (Enum ev : values) { + checkSubtype(expectedType, ev); + enumElt = findEnumElement(ev); + res.add(createValue(enumElt)); + } + AnnotationValue val = createValue(res); + elementValues.put(var, val); + return this; + } + + // Keep this version synchronized with the Enum[] version above. + // Which one is more useful/general? Unifying adds overhead of creating + // another array. + public AnnotationBuilder setValue(CharSequence elementName, VariableElement[] values) { + assertNotBuilt(); + ExecutableElement var = findElement(elementName); + + TypeMirror expectedType = var.getReturnType(); + if (expectedType.getKind() != TypeKind.ARRAY) { + throw new BugInCF("expected an array, but found: " + expectedType); + } + + expectedType = ((ArrayType) expectedType).getComponentType(); + if (expectedType.getKind() != TypeKind.DECLARED) { + throw new BugInCF( + "expected a declared component type, but found: " + + expectedType + + " kind: " + + expectedType.getKind()); + } + if (!types.isSameType((DeclaredType) expectedType, values[0].asType())) { + throw new BugInCF( + "expected a different declared component type: " + + expectedType + + " vs. " + + values[0]); + } + + List res = new ArrayList<>(values.length); + for (VariableElement ev : values) { + checkSubtype(expectedType, ev); + // Is there a better way to distinguish between enums and + // references to constants? + if (ev.getConstantValue() != null) { + res.add(createValue(ev.getConstantValue())); + } else { + res.add(createValue(ev)); + } + } + AnnotationValue val = createValue(res); + elementValues.put(var, val); + return this; + } + + /** Find the VariableElement for the given enum. */ + private VariableElement findEnumElement(Enum value) { + String enumClass = value.getDeclaringClass().getCanonicalName(); + assert enumClass != null : "@AssumeAssertion(nullness): assumption"; + TypeElement enumClassElt = elements.getTypeElement(enumClass); + assert enumClassElt != null; + for (Element enumElt : enumClassElt.getEnclosedElements()) { + if (enumElt.getSimpleName().contentEquals(value.name())) { + return (VariableElement) enumElt; + } + } + throw new BugInCF("cannot be here"); + } + + private AnnotationBuilder setValue(CharSequence key, Object value) { + assertNotBuilt(); + AnnotationValue val = createValue(value); + ExecutableElement var = findElement(key); + checkSubtype(var.getReturnType(), value); + elementValues.put(var, val); + return this; + } + + public ExecutableElement findElement(CharSequence key) { + for (ExecutableElement elt : ElementFilter.methodsIn(annotationElt.getEnclosedElements())) { + if (elt.getSimpleName().contentEquals(key)) { + return elt; + } + } + throw new BugInCF("Couldn't find " + key + " element in " + annotationElt); + } + + /** + * @param expected the expected type + * @param givenValue the object whose run-time class to check + * @throws BugInCF if the type of {@code givenValue} is not the same as {@code expected} + */ + private void checkSubtype(TypeMirror expected, Object givenValue) { + if (expected.getKind().isPrimitive()) { + expected = types.boxedClass((PrimitiveType) expected).asType(); + } + + if (expected.getKind() == TypeKind.DECLARED + && TypesUtils.isClass(expected) + && givenValue instanceof TypeMirror) { + return; + } + + TypeMirror found; + boolean isSubtype; + + if (expected.getKind() == TypeKind.DECLARED + && ((DeclaredType) expected).asElement().getKind() == ElementKind.ANNOTATION_TYPE + && givenValue instanceof AnnotationMirror) { + found = ((AnnotationMirror) givenValue).getAnnotationType(); + isSubtype = + ((DeclaredType) expected) + .asElement() + .equals(((DeclaredType) found).asElement()); + } else if (givenValue instanceof AnnotationMirror) { + found = ((AnnotationMirror) givenValue).getAnnotationType(); + // TODO: why is this always failing??? + isSubtype = false; + } else if (givenValue instanceof VariableElement) { + found = ((VariableElement) givenValue).asType(); + if (expected.getKind() == TypeKind.DECLARED) { + isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); + } else { + isSubtype = false; + } + } else { + String name = givenValue.getClass().getCanonicalName(); + assert name != null : "@AssumeAssertion(nullness): assumption"; + found = elements.getTypeElement(name).asType(); + isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); + } + if (!isSubtype) { + // Annotations in stub files sometimes are the same type, but Types#isSubtype fails + // anyway. + isSubtype = found.toString().equals(expected.toString()); + } + + if (!isSubtype) { + throw new BugInCF( + "given value differs from expected; " + + "found: " + + found + + "; expected: " + + expected); + } + } + + /** + * Create an AnnotationValue -- a value for an annotation element/field. + * + * @param obj the value to be stored in an annotation element/field + * @return an AnnotationValue for the given Java value + */ + private static AnnotationValue createValue(Object obj) { + return new CheckerFrameworkAnnotationValue(obj); + } + + /** Implementation of AnnotationMirror used by the Checker Framework. */ + /* default visibility to allow access from within package. */ + static class CheckerFrameworkAnnotationMirror implements AnnotationMirror { + /** The interned toString value. */ + private @Nullable @Interned String toStringVal; + + /** The annotation type. */ + private final DeclaredType annotationType; + + /** The element values. */ + private final Map elementValues; + + /** The annotation name. */ + // default visibility to allow access from within package. + final @Interned @CanonicalName String annotationName; + + /** + * Create a CheckerFrameworkAnnotationMirror. + * + * @param annotationType the annotation type + * @param elementValues the element values + */ + @SuppressWarnings("signature:assignment.type.incompatible") // needs JDK annotations + CheckerFrameworkAnnotationMirror( + DeclaredType annotationType, + Map elementValues) { + this.annotationType = annotationType; + TypeElement elm = (TypeElement) annotationType.asElement(); + this.annotationName = elm.getQualifiedName().toString().intern(); + this.elementValues = elementValues; + } + + @Override + public DeclaredType getAnnotationType() { + return annotationType; + } + + @Override + public Map getElementValues() { + return Collections.unmodifiableMap(elementValues); + } + + @SideEffectFree + @Override + public String toString() { + if (toStringVal != null) { + return toStringVal; + } + StringBuilder buf = new StringBuilder(); + buf.append("@"); + buf.append(annotationName); + int len = elementValues.size(); + if (len > 0) { + buf.append('('); + boolean first = true; + for (Map.Entry pair : + elementValues.entrySet()) { + if (!first) { + buf.append(", "); + } + first = false; + + String name = pair.getKey().getSimpleName().toString(); + if (len > 1 || !name.equals("value")) { + buf.append(name); + buf.append('='); + } + buf.append(pair.getValue()); + } + buf.append(')'); + } + toStringVal = buf.toString().intern(); + return toStringVal; + + // return "@" + annotationType + "(" + elementValues + ")"; + } + } + + /** Implementation of AnnotationValue used by the Checker Framework. */ + private static class CheckerFrameworkAnnotationValue implements AnnotationValue { + /** The value. */ + private final Object value; + + /** The interned value of toString. */ + private @Nullable @Interned String toStringVal; + + /** Create an annotation value. */ + CheckerFrameworkAnnotationValue(Object obj) { + this.value = obj; + } + + @Override + public Object getValue() { + return value; + } + + @SideEffectFree + @Override + public String toString() { + if (this.toStringVal != null) { + return this.toStringVal; + } + String toStringVal; + if (value instanceof String) { + toStringVal = "\"" + value + "\""; + } else if (value instanceof Character) { + toStringVal = "\'" + value + "\'"; + } else if (value instanceof List) { + List list = (List) value; + toStringVal = "{" + StringsPlume.join(", ", list) + "}"; + } else if (value instanceof VariableElement) { + // for Enums + VariableElement var = (VariableElement) value; + String encl = var.getEnclosingElement().toString(); + if (!encl.isEmpty()) { + encl = encl + '.'; + } + toStringVal = encl + var; + } else if (value instanceof TypeMirror && TypesUtils.isClassType((TypeMirror) value)) { + toStringVal = value.toString() + ".class"; + } else { + toStringVal = value.toString(); + } + this.toStringVal = toStringVal.intern(); + return this.toStringVal; + } + + @SuppressWarnings("unchecked") + @Override + public R accept(AnnotationValueVisitor v, P p) { + if (value instanceof AnnotationMirror) { + return v.visitAnnotation((AnnotationMirror) value, p); + } else if (value instanceof List) { + return v.visitArray((List) value, p); + } else if (value instanceof Boolean) { + return v.visitBoolean((Boolean) value, p); + } else if (value instanceof Character) { + return v.visitChar((Character) value, p); + } else if (value instanceof Double) { + return v.visitDouble((Double) value, p); + } else if (value instanceof VariableElement) { + return v.visitEnumConstant((VariableElement) value, p); + } else if (value instanceof Float) { + return v.visitFloat((Float) value, p); + } else if (value instanceof Integer) { + return v.visitInt((Integer) value, p); + } else if (value instanceof Long) { + return v.visitLong((Long) value, p); + } else if (value instanceof Short) { + return v.visitShort((Short) value, p); + } else if (value instanceof String) { + return v.visitString((String) value, p); + } else if (value instanceof TypeMirror) { + return v.visitType((TypeMirror) value, p); + } else { + assert false : " unknown type : " + v.getClass(); + return v.visitUnknown(this, p); + } + } + + @Override + public boolean equals(@Nullable Object obj) { + // System.out.printf("Calling CFAV.equals()%n"); + if (!(obj instanceof AnnotationValue)) { + return false; + } + AnnotationValue other = (AnnotationValue) obj; + return Objects.equals(this.getValue(), other.getValue()); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.value); + } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java index 3640e98bc2a..2d5c96fb90c 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorMap.java @@ -1,16 +1,18 @@ package org.checkerframework.javacutil; +import org.checkerframework.checker.nullness.qual.KeyFor; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.returnsreceiver.qual.This; +import org.checkerframework.dataflow.qual.Pure; + import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; + import javax.lang.model.element.AnnotationMirror; -import org.checkerframework.checker.nullness.qual.KeyFor; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.returnsreceiver.qual.This; -import org.checkerframework.dataflow.qual.Pure; /** * The Map interface defines some of its methods with respect to the equals method. This @@ -28,192 +30,192 @@ */ public class AnnotationMirrorMap implements Map<@KeyFor("this") AnnotationMirror, V> { - /** The actual map to which all work is delegated. */ - // Not final because makeUnmodifiable() can reassign it. - private NavigableMap<@KeyFor("this") AnnotationMirror, V> shadowMap = - new TreeMap<>(AnnotationUtils::compareAnnotationMirrors); - - /** The canonical unmodifiable empty set. */ - private static AnnotationMirrorMap emptyMap = unmodifiableSet(Collections.emptyMap()); - - /** Default constructor. */ - public AnnotationMirrorMap() {} - - /** - * Creates an annotation mirror map and adds all the mappings in {@code copy}. - * - * @param copy a map whose contents should be copied to the newly created map - */ - @SuppressWarnings("nullness:method.invocation") // initialization in constructor - public AnnotationMirrorMap(Map copy) { - this(); - this.putAll(copy); - } - - /** - * Returns an unmodifiable AnnotationMirrorSet with the given elements. - * - * @param annos the annotation mirrors that will constitute the new unmodifable set - * @return an unmodifiable AnnotationMirrorSet with the given elements - * @param the type of the values in the map - */ - public static AnnotationMirrorMap unmodifiableSet( - Map annos) { - AnnotationMirrorMap result = new AnnotationMirrorMap<>(annos); - result.makeUnmodifiable(); - return result; - } - - /** - * Returns an empty set. - * - * @return an empty set - * @param the type of the values in the map - */ - @SuppressWarnings("unchecked") - public static AnnotationMirrorMap emptyMap() { - return (AnnotationMirrorMap) emptyMap; - } - - /** - * Make this set unmodifiable. - * - * @return this set - */ - public @This AnnotationMirrorMap makeUnmodifiable() { - shadowMap = Collections.unmodifiableNavigableMap(shadowMap); - return this; - } - - @Override - public int size() { - return shadowMap.size(); - } - - @Override - public boolean isEmpty() { - return shadowMap.isEmpty(); - } - - @SuppressWarnings("keyfor:contracts.conditional.postcondition") // delegation - @Override - public boolean containsKey(Object key) { - if (key instanceof AnnotationMirror) { - return AnnotationUtils.containsSame(shadowMap.keySet(), (AnnotationMirror) key); - } else { - return false; - } - } - - @Override - public boolean containsValue(Object value) { - return shadowMap.containsValue(value); - } - - @Override - @Pure - public @Nullable V get(Object key) { - if (key instanceof AnnotationMirror) { - AnnotationMirror keyAnno = - AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); - if (keyAnno != null) { - return shadowMap.get(keyAnno); - } - } - return null; - } - - @SuppressWarnings({ - "keyfor:contracts.postcondition", - "keyfor:contracts.postcondition", - "keyfor:argument" - }) // delegation - @Override - public @Nullable V put(AnnotationMirror key, V value) { - V pre = get(key); - remove(key); - shadowMap.put(key, value); - return pre; - } - - @Override - public @Nullable V remove(Object key) { - if (key instanceof AnnotationMirror) { - AnnotationMirror keyAnno = - AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); - if (keyAnno != null) { - return shadowMap.remove(keyAnno); - } - } - return null; - } - - @Override - public void putAll(Map m) { - for (Map.Entry entry : m.entrySet()) { - put(entry.getKey(), entry.getValue()); - } - } - - @Override - public void clear() { - shadowMap.clear(); - } - - @Override - public AnnotationMirrorSet keySet() { - return new AnnotationMirrorSet(shadowMap.keySet()); - } - - @Override - public Collection values() { - return shadowMap.values(); - } - - @SuppressWarnings("keyfor:return") // delegation - @Override - public Set> entrySet() { - return shadowMap.entrySet(); - } - - @Override - public String toString() { - return shadowMap.toString(); - } - - @Override - @Pure - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (!(o instanceof AnnotationMirrorMap)) return false; - AnnotationMirrorMap m = (AnnotationMirrorMap) o; - if (m.size() != size()) { - return false; - } - - try { - for (Entry e : entrySet()) { - AnnotationMirror key = e.getKey(); - V value = e.getValue(); - if (value == null) { - if (!(m.get(key) == null && m.containsKey(key))) return false; + /** The actual map to which all work is delegated. */ + // Not final because makeUnmodifiable() can reassign it. + private NavigableMap<@KeyFor("this") AnnotationMirror, V> shadowMap = + new TreeMap<>(AnnotationUtils::compareAnnotationMirrors); + + /** The canonical unmodifiable empty set. */ + private static AnnotationMirrorMap emptyMap = unmodifiableSet(Collections.emptyMap()); + + /** Default constructor. */ + public AnnotationMirrorMap() {} + + /** + * Creates an annotation mirror map and adds all the mappings in {@code copy}. + * + * @param copy a map whose contents should be copied to the newly created map + */ + @SuppressWarnings("nullness:method.invocation") // initialization in constructor + public AnnotationMirrorMap(Map copy) { + this(); + this.putAll(copy); + } + + /** + * Returns an unmodifiable AnnotationMirrorSet with the given elements. + * + * @param annos the annotation mirrors that will constitute the new unmodifable set + * @return an unmodifiable AnnotationMirrorSet with the given elements + * @param the type of the values in the map + */ + public static AnnotationMirrorMap unmodifiableSet( + Map annos) { + AnnotationMirrorMap result = new AnnotationMirrorMap<>(annos); + result.makeUnmodifiable(); + return result; + } + + /** + * Returns an empty set. + * + * @return an empty set + * @param the type of the values in the map + */ + @SuppressWarnings("unchecked") + public static AnnotationMirrorMap emptyMap() { + return (AnnotationMirrorMap) emptyMap; + } + + /** + * Make this set unmodifiable. + * + * @return this set + */ + public @This AnnotationMirrorMap makeUnmodifiable() { + shadowMap = Collections.unmodifiableNavigableMap(shadowMap); + return this; + } + + @Override + public int size() { + return shadowMap.size(); + } + + @Override + public boolean isEmpty() { + return shadowMap.isEmpty(); + } + + @SuppressWarnings("keyfor:contracts.conditional.postcondition") // delegation + @Override + public boolean containsKey(Object key) { + if (key instanceof AnnotationMirror) { + return AnnotationUtils.containsSame(shadowMap.keySet(), (AnnotationMirror) key); } else { - if (!value.equals(m.get(key))) return false; + return false; + } + } + + @Override + public boolean containsValue(Object value) { + return shadowMap.containsValue(value); + } + + @Override + @Pure + public @Nullable V get(Object key) { + if (key instanceof AnnotationMirror) { + AnnotationMirror keyAnno = + AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); + if (keyAnno != null) { + return shadowMap.get(keyAnno); + } } - } - } catch (ClassCastException | NullPointerException unused) { - return false; + return null; + } + + @SuppressWarnings({ + "keyfor:contracts.postcondition", + "keyfor:contracts.postcondition", + "keyfor:argument" + }) // delegation + @Override + public @Nullable V put(AnnotationMirror key, V value) { + V pre = get(key); + remove(key); + shadowMap.put(key, value); + return pre; + } + + @Override + public @Nullable V remove(Object key) { + if (key instanceof AnnotationMirror) { + AnnotationMirror keyAnno = + AnnotationUtils.getSame(shadowMap.keySet(), (AnnotationMirror) key); + if (keyAnno != null) { + return shadowMap.remove(keyAnno); + } + } + return null; + } + + @Override + public void putAll(Map m) { + for (Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + shadowMap.clear(); + } + + @Override + public AnnotationMirrorSet keySet() { + return new AnnotationMirrorSet(shadowMap.keySet()); } - return true; - } + @Override + public Collection values() { + return shadowMap.values(); + } + + @SuppressWarnings("keyfor:return") // delegation + @Override + public Set> entrySet() { + return shadowMap.entrySet(); + } - @Override - public int hashCode() { - int result = 0; - for (Entry entry : entrySet()) result += entry.hashCode(); - return result; - } + @Override + public String toString() { + return shadowMap.toString(); + } + + @Override + @Pure + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (!(o instanceof AnnotationMirrorMap)) return false; + AnnotationMirrorMap m = (AnnotationMirrorMap) o; + if (m.size() != size()) { + return false; + } + + try { + for (Entry e : entrySet()) { + AnnotationMirror key = e.getKey(); + V value = e.getValue(); + if (value == null) { + if (!(m.get(key) == null && m.containsKey(key))) return false; + } else { + if (!value.equals(m.get(key))) return false; + } + } + } catch (ClassCastException | NullPointerException unused) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = 0; + for (Entry entry : entrySet()) result += entry.hashCode(); + return result; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java index dcd11fdcff2..608e90d8be7 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationMirrorSet.java @@ -1,12 +1,5 @@ package org.checkerframework.javacutil; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.NavigableSet; -import java.util.TreeSet; -import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; @@ -15,6 +8,15 @@ import org.checkerframework.common.returnsreceiver.qual.This; import org.plumelib.util.DeepCopyable; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.TreeSet; + +import javax.lang.model.element.AnnotationMirror; + /** * The Set interface defines many methods with respect to the equals method. This implementation of * Set violates those specifications, but fulfills the same property using {@link @@ -31,339 +33,341 @@ */ // TODO: Could extend AbstractSet to eliminate the need to implement a few methods. public class AnnotationMirrorSet - implements NavigableSet<@KeyFor("this") AnnotationMirror>, DeepCopyable { - - /** Backing set. */ - // Not final because makeUnmodifiable() can reassign it. - private NavigableSet<@KeyFor("this") AnnotationMirror> shadowSet = - new TreeSet<>(AnnotationUtils::compareAnnotationMirrors); - - /** The canonical unmodifiable empty set. */ - private static final AnnotationMirrorSet emptySet = unmodifiableSet(Collections.emptySet()); - - /// Constructors and factory methods - - /** Default constructor. */ - public AnnotationMirrorSet() {} - - // TODO: Should this be an unmodifiable set? - /** - * Creates a new {@link AnnotationMirrorSet} that contains {@code value}. - * - * @param value the AnnotationMirror to put in the set - */ - public AnnotationMirrorSet(AnnotationMirror value) { - this.add(value); - } - - /** - * Returns a new {@link AnnotationMirrorSet} that contains the given annotation mirrors. - * - * @param annos the AnnotationMirrors to put in the set - */ - public AnnotationMirrorSet(Collection annos) { - this.addAll(annos); - } - - @SuppressWarnings("keyfor:argument") // transferring keys from one map to another - @Override - public AnnotationMirrorSet deepCopy() { - AnnotationMirrorSet result = new AnnotationMirrorSet(); - result.shadowSet.addAll(shadowSet); - return result; - } - - /** - * Make this set unmodifiable. - * - * @return this set - */ - public @This AnnotationMirrorSet makeUnmodifiable() { - shadowSet = Collections.unmodifiableNavigableSet(shadowSet); - return this; - } - - /** - * Returns a new unmodifiable {@link AnnotationMirrorSet} that contains {@code value}. - * - * @param value the AnnotationMirror to put in the set - * @return a new unmodifiable {@link AnnotationMirrorSet} that contains only {@code value} - */ - public static AnnotationMirrorSet singleton(AnnotationMirror value) { - // The implementation could be more efficient if Collections.singleton returned a - // NavigableSet. - AnnotationMirrorSet result = new AnnotationMirrorSet(); - result.add(value); - result.makeUnmodifiable(); - return result; - } - - /** - * Returns an unmodifiable AnnotationMirrorSet with the given elements. - * - * @param annos the annotation mirrors that will constitute the new unmodifiable set - * @return an unmodifiable AnnotationMirrorSet with the given elements - */ - public static AnnotationMirrorSet unmodifiableSet(Collection annos) { - AnnotationMirrorSet result = new AnnotationMirrorSet(annos); - result.makeUnmodifiable(); - return result; - } - - /** - * Returns an empty set. - * - * @return an empty set - */ - public static AnnotationMirrorSet emptySet() { - return emptySet; - } - - /// Set methods - - @Override - public int size() { - return shadowSet.size(); - } - - @Override - public boolean isEmpty() { - return shadowSet.isEmpty(); - } - - @Override - public boolean contains( - @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, - @Nullable Object o) { - return o instanceof AnnotationMirror - && AnnotationUtils.containsSame(shadowSet, (AnnotationMirror) o); - } - - @Override - public Iterator<@KeyFor("this") AnnotationMirror> iterator() { - return shadowSet.iterator(); - } - - @Override - public Object[] toArray() { - return shadowSet.toArray(); - } - - @SuppressWarnings("nullness:toarray.nullable.elements.not.newarray") // delegation - @Override - public <@KeyForBottom T> @Nullable T[] toArray(@PolyNull T[] a) { - return shadowSet.toArray(a); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public boolean add( - @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, - AnnotationMirror annotationMirror) { - if (contains(annotationMirror)) { - return false; - } - shadowSet.add(annotationMirror); - return true; - } - - @Override - public boolean remove(@Nullable Object o) { - if (o instanceof AnnotationMirror) { - AnnotationMirror found = AnnotationUtils.getSame(shadowSet, (AnnotationMirror) o); - return found != null && shadowSet.remove(found); - } - return false; - } - - @Override - public boolean containsAll(Collection c) { - for (Object o : c) { - if (!contains(o)) { + implements NavigableSet<@KeyFor("this") AnnotationMirror>, + DeepCopyable { + + /** Backing set. */ + // Not final because makeUnmodifiable() can reassign it. + private NavigableSet<@KeyFor("this") AnnotationMirror> shadowSet = + new TreeSet<>(AnnotationUtils::compareAnnotationMirrors); + + /** The canonical unmodifiable empty set. */ + private static final AnnotationMirrorSet emptySet = unmodifiableSet(Collections.emptySet()); + + /// Constructors and factory methods + + /** Default constructor. */ + public AnnotationMirrorSet() {} + + // TODO: Should this be an unmodifiable set? + /** + * Creates a new {@link AnnotationMirrorSet} that contains {@code value}. + * + * @param value the AnnotationMirror to put in the set + */ + public AnnotationMirrorSet(AnnotationMirror value) { + this.add(value); + } + + /** + * Returns a new {@link AnnotationMirrorSet} that contains the given annotation mirrors. + * + * @param annos the AnnotationMirrors to put in the set + */ + public AnnotationMirrorSet(Collection annos) { + this.addAll(annos); + } + + @SuppressWarnings("keyfor:argument") // transferring keys from one map to another + @Override + public AnnotationMirrorSet deepCopy() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.shadowSet.addAll(shadowSet); + return result; + } + + /** + * Make this set unmodifiable. + * + * @return this set + */ + public @This AnnotationMirrorSet makeUnmodifiable() { + shadowSet = Collections.unmodifiableNavigableSet(shadowSet); + return this; + } + + /** + * Returns a new unmodifiable {@link AnnotationMirrorSet} that contains {@code value}. + * + * @param value the AnnotationMirror to put in the set + * @return a new unmodifiable {@link AnnotationMirrorSet} that contains only {@code value} + */ + public static AnnotationMirrorSet singleton(AnnotationMirror value) { + // The implementation could be more efficient if Collections.singleton returned a + // NavigableSet. + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.add(value); + result.makeUnmodifiable(); + return result; + } + + /** + * Returns an unmodifiable AnnotationMirrorSet with the given elements. + * + * @param annos the annotation mirrors that will constitute the new unmodifiable set + * @return an unmodifiable AnnotationMirrorSet with the given elements + */ + public static AnnotationMirrorSet unmodifiableSet( + Collection annos) { + AnnotationMirrorSet result = new AnnotationMirrorSet(annos); + result.makeUnmodifiable(); + return result; + } + + /** + * Returns an empty set. + * + * @return an empty set + */ + public static AnnotationMirrorSet emptySet() { + return emptySet; + } + + /// Set methods + + @Override + public int size() { + return shadowSet.size(); + } + + @Override + public boolean isEmpty() { + return shadowSet.isEmpty(); + } + + @Override + public boolean contains( + @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, + @Nullable Object o) { + return o instanceof AnnotationMirror + && AnnotationUtils.containsSame(shadowSet, (AnnotationMirror) o); + } + + @Override + public Iterator<@KeyFor("this") AnnotationMirror> iterator() { + return shadowSet.iterator(); + } + + @Override + public Object[] toArray() { + return shadowSet.toArray(); + } + + @SuppressWarnings("nullness:toarray.nullable.elements.not.newarray") // delegation + @Override + public <@KeyForBottom T> @Nullable T[] toArray(@PolyNull T[] a) { + return shadowSet.toArray(a); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public boolean add( + @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, + AnnotationMirror annotationMirror) { + if (contains(annotationMirror)) { + return false; + } + shadowSet.add(annotationMirror); + return true; + } + + @Override + public boolean remove(@Nullable Object o) { + if (o instanceof AnnotationMirror) { + AnnotationMirror found = AnnotationUtils.getSame(shadowSet, (AnnotationMirror) o); + return found != null && shadowSet.remove(found); + } return false; - } - } - return true; - } - - @Override - public boolean addAll( - @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, - Collection c) { - boolean result = true; - for (AnnotationMirror a : c) { - if (!add(a)) { - result = false; - } - } - return result; - } - - @Override - public boolean retainAll(Collection c) { - AnnotationMirrorSet newSet = new AnnotationMirrorSet(); - for (Object o : c) { - if (contains(o)) { - assert o != null - : "@AssumeAssertion(nullness): after contains, the argument should have" - + " the element type of the set"; - newSet.add((AnnotationMirror) o); - } - } - if (newSet.size() != shadowSet.size()) { - shadowSet = newSet; - return true; - } - return false; - } - - @Override - public boolean removeAll(Collection c) { - boolean result = true; - for (Object a : c) { - if (!remove(a)) { - result = false; - } - } - return result; - } - - @Override - public void clear() { - shadowSet.clear(); - } - - @Override - public String toString() { - return shadowSet.toString(); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (!(o instanceof AnnotationMirrorSet)) { - return false; - } - AnnotationMirrorSet s = (AnnotationMirrorSet) o; - if (this.size() != s.size()) { - return false; - } - return containsAll(s); - } - - @Override - public int hashCode() { - int result = 0; - Iterator i = iterator(); - while (i.hasNext()) { - AnnotationMirror am = i.next(); - if (am != null) { - result += am.hashCode(); - } - } - return result; - } - - /// NavigableSet methods - - @SuppressWarnings({ - "interning:override.return", // looks like a bug (in interning checker) - "signature:override.return", // " - "nullness:return", // wildcard types - "keyfor:return" // comparator wildcard - }) - @Override - public Comparator comparator() { - return shadowSet.comparator(); - } - - @Override - public @KeyFor("this") AnnotationMirror first() { - return shadowSet.first(); - } - - @Override - public @KeyFor("this") AnnotationMirror last() { - return shadowSet.last(); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror lower(AnnotationMirror e) { - return shadowSet.lower(e); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror floor(AnnotationMirror e) { - return shadowSet.floor(e); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror ceiling(AnnotationMirror e) { - return shadowSet.ceiling(e); - } - - @SuppressWarnings("keyfor:argument") // delegation - @Override - public @Nullable @KeyFor("this") AnnotationMirror higher(AnnotationMirror e) { - return shadowSet.higher(e); - } - - @Override - public @Nullable @KeyFor("this") AnnotationMirror pollFirst() { - return shadowSet.pollFirst(); - } - - @Override - public @Nullable @KeyFor("this") AnnotationMirror pollLast() { - return shadowSet.pollLast(); - } - - @Override - public AnnotationMirrorSet descendingSet() { - throw new Error("Not yet implemented."); - } - - @Override - public Iterator<@KeyFor("this") AnnotationMirror> descendingIterator() { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet subSet( - AnnotationMirror fromElement, - boolean fromInclusive, - AnnotationMirror toElement, - boolean toInclusive) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet headSet(AnnotationMirror toElement, boolean inclusive) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet tailSet(AnnotationMirror fromElement, boolean inclusive) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet subSet(AnnotationMirror fromElement, AnnotationMirror toElement) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet headSet(AnnotationMirror toElement) { - throw new Error("Not yet implemented."); - } - - @Override - public AnnotationMirrorSet tailSet(AnnotationMirror fromElement) { - throw new Error("Not yet implemented."); - } + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + + @Override + public boolean addAll( + @UnknownInitialization(AnnotationMirrorSet.class) AnnotationMirrorSet this, + Collection c) { + boolean result = true; + for (AnnotationMirror a : c) { + if (!add(a)) { + result = false; + } + } + return result; + } + + @Override + public boolean retainAll(Collection c) { + AnnotationMirrorSet newSet = new AnnotationMirrorSet(); + for (Object o : c) { + if (contains(o)) { + assert o != null + : "@AssumeAssertion(nullness): after contains, the argument should have" + + " the element type of the set"; + newSet.add((AnnotationMirror) o); + } + } + if (newSet.size() != shadowSet.size()) { + shadowSet = newSet; + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + boolean result = true; + for (Object a : c) { + if (!remove(a)) { + result = false; + } + } + return result; + } + + @Override + public void clear() { + shadowSet.clear(); + } + + @Override + public String toString() { + return shadowSet.toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (!(o instanceof AnnotationMirrorSet)) { + return false; + } + AnnotationMirrorSet s = (AnnotationMirrorSet) o; + if (this.size() != s.size()) { + return false; + } + return containsAll(s); + } + + @Override + public int hashCode() { + int result = 0; + Iterator i = iterator(); + while (i.hasNext()) { + AnnotationMirror am = i.next(); + if (am != null) { + result += am.hashCode(); + } + } + return result; + } + + /// NavigableSet methods + + @SuppressWarnings({ + "interning:override.return", // looks like a bug (in interning checker) + "signature:override.return", // " + "nullness:return", // wildcard types + "keyfor:return" // comparator wildcard + }) + @Override + public Comparator comparator() { + return shadowSet.comparator(); + } + + @Override + public @KeyFor("this") AnnotationMirror first() { + return shadowSet.first(); + } + + @Override + public @KeyFor("this") AnnotationMirror last() { + return shadowSet.last(); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror lower(AnnotationMirror e) { + return shadowSet.lower(e); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror floor(AnnotationMirror e) { + return shadowSet.floor(e); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror ceiling(AnnotationMirror e) { + return shadowSet.ceiling(e); + } + + @SuppressWarnings("keyfor:argument") // delegation + @Override + public @Nullable @KeyFor("this") AnnotationMirror higher(AnnotationMirror e) { + return shadowSet.higher(e); + } + + @Override + public @Nullable @KeyFor("this") AnnotationMirror pollFirst() { + return shadowSet.pollFirst(); + } + + @Override + public @Nullable @KeyFor("this") AnnotationMirror pollLast() { + return shadowSet.pollLast(); + } + + @Override + public AnnotationMirrorSet descendingSet() { + throw new Error("Not yet implemented."); + } + + @Override + public Iterator<@KeyFor("this") AnnotationMirror> descendingIterator() { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet subSet( + AnnotationMirror fromElement, + boolean fromInclusive, + AnnotationMirror toElement, + boolean toInclusive) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet headSet(AnnotationMirror toElement, boolean inclusive) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet tailSet(AnnotationMirror fromElement, boolean inclusive) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet subSet(AnnotationMirror fromElement, AnnotationMirror toElement) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet headSet(AnnotationMirror toElement) { + throw new Error("Not yet implemented."); + } + + @Override + public AnnotationMirrorSet tailSet(AnnotationMirror fromElement) { + throw new Error("Not yet implemented."); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java index ff026b753fb..7208c724e93 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationProvider.java @@ -1,59 +1,63 @@ package org.checkerframework.javacutil; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; + import java.lang.annotation.Annotation; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import org.checkerframework.checker.nullness.qual.Nullable; // This class exists to break a circular dependency between the dataflow framework and // type-checkers. /** An implementation of AnnotationProvider returns annotations on Java AST elements. */ public interface AnnotationProvider { - /** - * Returns the AnnotationMirror, of the given class or an alias of it, used to annotate the - * element. Returns null if no annotation equivalent to {@code anno} exists on {@code elt}. - * - * @param elt the element - * @param anno annotation class - * @return an annotation mirror of class {@code anno} on {@code elt}, or an equivalent one, or - * null if none exists on {@code anno} - */ - @Nullable AnnotationMirror getDeclAnnotation(Element elt, Class anno); - - /** - * Return the annotation on {@code tree} that is in the hierarchy that contains the qualifier - * {@code target}. Returns null if none exists. - * - * @param tree the tree of which the annotation is returned - * @param target the class of the annotation - * @return the annotation on {@code tree} that has the class {@code target}, or null - */ - @Nullable AnnotationMirror getAnnotationMirror(Tree tree, Class target); - - /** - * Returns true if the given method is side-effect-free according to this AnnotationProvider - * — that is, if a call to the given method does not undo flow-sensitive type refinement. - * - *

                  Note that this method takes account of this AnnotationProvider's semantics, whereas {@code - * org.checkerframework.dataflow.util.PurityUtils#isSideEffectFree} does not. - * - * @param methodElement a method - * @return true if a call to the method does not undo flow-sensitive type refinement - */ - boolean isSideEffectFree(ExecutableElement methodElement); - - /** - * Returns true if the given method is deterministic according to this AnnotationProvider — - * that is, if multiple calls to the given method (with the same arguments) return the same value. - * - *

                  Note that this method takes account of this AnnotationProvider's semantics, whereas {@code - * org.checkerframework.dataflow.util.PurityUtils#isDeterministic} does not. - * - * @param methodElement a method - * @return true if multiple calls to the method (with the same arguments) return the same value - */ - boolean isDeterministic(ExecutableElement methodElement); + /** + * Returns the AnnotationMirror, of the given class or an alias of it, used to annotate the + * element. Returns null if no annotation equivalent to {@code anno} exists on {@code elt}. + * + * @param elt the element + * @param anno annotation class + * @return an annotation mirror of class {@code anno} on {@code elt}, or an equivalent one, or + * null if none exists on {@code anno} + */ + @Nullable AnnotationMirror getDeclAnnotation(Element elt, Class anno); + + /** + * Return the annotation on {@code tree} that is in the hierarchy that contains the qualifier + * {@code target}. Returns null if none exists. + * + * @param tree the tree of which the annotation is returned + * @param target the class of the annotation + * @return the annotation on {@code tree} that has the class {@code target}, or null + */ + @Nullable AnnotationMirror getAnnotationMirror(Tree tree, Class target); + + /** + * Returns true if the given method is side-effect-free according to this AnnotationProvider + * — that is, if a call to the given method does not undo flow-sensitive type refinement. + * + *

                  Note that this method takes account of this AnnotationProvider's semantics, whereas {@code + * org.checkerframework.dataflow.util.PurityUtils#isSideEffectFree} does not. + * + * @param methodElement a method + * @return true if a call to the method does not undo flow-sensitive type refinement + */ + boolean isSideEffectFree(ExecutableElement methodElement); + + /** + * Returns true if the given method is deterministic according to this AnnotationProvider + * — that is, if multiple calls to the given method (with the same arguments) return the + * same value. + * + *

                  Note that this method takes account of this AnnotationProvider's semantics, whereas {@code + * org.checkerframework.dataflow.util.PurityUtils#isDeterministic} does not. + * + * @param methodElement a method + * @return true if multiple calls to the method (with the same arguments) return the same value + */ + boolean isDeterministic(ExecutableElement methodElement); } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java index e819b017ee4..f6853e7f22b 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java @@ -5,6 +5,20 @@ import com.sun.source.tree.ModifiersTree; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.model.JavacElements; + +import org.checkerframework.checker.interning.qual.CompareToMethod; +import org.checkerframework.checker.interning.qual.EqualsMethod; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror; +import org.plumelib.util.ArrayMap; +import org.plumelib.util.CollectionsPlume; + import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -21,6 +35,7 @@ import java.util.Set; import java.util.StringJoiner; import java.util.TreeSet; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ElementKind; @@ -30,18 +45,6 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.util.ElementFilter; -import org.checkerframework.checker.interning.qual.CompareToMethod; -import org.checkerframework.checker.interning.qual.EqualsMethod; -import org.checkerframework.checker.interning.qual.Interned; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror; -import org.plumelib.util.ArrayMap; -import org.plumelib.util.CollectionsPlume; /** * A utility class for working with annotations. @@ -50,1561 +53,1608 @@ */ public class AnnotationUtils { - // Class cannot be instantiated. - private AnnotationUtils() { - throw new AssertionError("Class AnnotationUtils cannot be instantiated."); - } - - // ********************************************************************** - // Helper methods to handle annotations. mainly workaround - // AnnotationMirror.equals undesired property - // (I think the undesired property is that it's reference equality.) - // ********************************************************************** - - /** - * Returns the fully-qualified name of an annotation as a String. - * - * @param annotation the annotation whose name to return - * @return the fully-qualified name of an annotation as a String - */ - public static final @CanonicalName String annotationName(AnnotationMirror annotation) { - if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { - return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; - } - DeclaredType annoType = annotation.getAnnotationType(); - TypeElement elm = (TypeElement) annoType.asElement(); - @SuppressWarnings("signature:assignment.type.incompatible") // JDK needs annotations - @CanonicalName String name = elm.getQualifiedName().toString(); - return name; - } - - /** - * Returns the fully-qualified name of an annotation as a String. - * - * @param annotation the annotation whose name to return - * @return the fully-qualified name of an annotation as a String - */ - public static final @CanonicalName @Interned String annotationNameInterned( - AnnotationMirror annotation) { - if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { - return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; - } - DeclaredType annoType = annotation.getAnnotationType(); - TypeElement elm = (TypeElement) annoType.asElement(); - @SuppressWarnings("signature:assignment") // JDK needs annotations - @CanonicalName String name = elm.getQualifiedName().toString(); - return name.intern(); - } - - /** - * Returns the binary name of an annotation as a String. - * - * @param annotation the annotation whose binary name to return - * @return the binary name of an annotation as a String - */ - public static final @BinaryName String annotationBinaryName(AnnotationMirror annotation) { - DeclaredType annoType = annotation.getAnnotationType(); - TypeElement elm = (TypeElement) annoType.asElement(); - return ElementUtils.getBinaryName(elm); - } - - /** - * Returns true iff both annotations are of the same type and have the same annotation values. - * - *

                  This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method - * returns true iff both annotations are the same and annotate the same annotation target (e.g. - * field, variable, etc) -- that is, if its arguments are the same annotation instance. - * - * @param a1 the first AnnotationMirror to compare - * @param a2 the second AnnotationMirror to compare - * @return true iff a1 and a2 are the same annotation - */ - @EqualsMethod - public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == a2) { - return true; - } - - if (!areSameByName(a1, a2)) { - return false; - } - - return sameElementValues(a1, a2); - } - - /** - * Return -1, 0, or 1 depending on whether the name of a1 is less, equal to, or greater than that - * of a2 (lexicographically). - * - * @param a1 the first AnnotationMirror to compare - * @param a2 the second AnnotationMirror to compare - * @return true iff a1 and a2 have the same annotation name - * @see #areSame(AnnotationMirror, AnnotationMirror) - */ - @EqualsMethod - public static int compareByName(AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == a2) { - return 0; - } - if (a1 == null || a2 == null) { - throw new BugInCF("Unexpected null argument: compareByName(%s, %s)", a1, a2); - } - - if (a1 instanceof CheckerFrameworkAnnotationMirror - && a2 instanceof CheckerFrameworkAnnotationMirror) { - @Interned @CanonicalName String name1 = ((CheckerFrameworkAnnotationMirror) a1).annotationName; - @Interned @CanonicalName String name2 = ((CheckerFrameworkAnnotationMirror) a2).annotationName; - if (name1 == name2) { + // Class cannot be instantiated. + private AnnotationUtils() { + throw new AssertionError("Class AnnotationUtils cannot be instantiated."); + } + + // ********************************************************************** + // Helper methods to handle annotations. mainly workaround + // AnnotationMirror.equals undesired property + // (I think the undesired property is that it's reference equality.) + // ********************************************************************** + + /** + * Returns the fully-qualified name of an annotation as a String. + * + * @param annotation the annotation whose name to return + * @return the fully-qualified name of an annotation as a String + */ + public static final @CanonicalName String annotationName(AnnotationMirror annotation) { + if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { + return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; + } + DeclaredType annoType = annotation.getAnnotationType(); + TypeElement elm = (TypeElement) annoType.asElement(); + @SuppressWarnings("signature:assignment.type.incompatible") // JDK needs annotations + @CanonicalName String name = elm.getQualifiedName().toString(); + return name; + } + + /** + * Returns the fully-qualified name of an annotation as a String. + * + * @param annotation the annotation whose name to return + * @return the fully-qualified name of an annotation as a String + */ + public static final @CanonicalName @Interned String annotationNameInterned( + AnnotationMirror annotation) { + if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { + return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; + } + DeclaredType annoType = annotation.getAnnotationType(); + TypeElement elm = (TypeElement) annoType.asElement(); + @SuppressWarnings("signature:assignment") // JDK needs annotations + @CanonicalName String name = elm.getQualifiedName().toString(); + return name.intern(); + } + + /** + * Returns the binary name of an annotation as a String. + * + * @param annotation the annotation whose binary name to return + * @return the binary name of an annotation as a String + */ + public static final @BinaryName String annotationBinaryName(AnnotationMirror annotation) { + DeclaredType annoType = annotation.getAnnotationType(); + TypeElement elm = (TypeElement) annoType.asElement(); + return ElementUtils.getBinaryName(elm); + } + + /** + * Returns true iff both annotations are of the same type and have the same annotation values. + * + *

                  This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method + * returns true iff both annotations are the same and annotate the same annotation target (e.g. + * field, variable, etc) -- that is, if its arguments are the same annotation instance. + * + * @param a1 the first AnnotationMirror to compare + * @param a2 the second AnnotationMirror to compare + * @return true iff a1 and a2 are the same annotation + */ + @EqualsMethod + public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == a2) { + return true; + } + + if (!areSameByName(a1, a2)) { + return false; + } + + return sameElementValues(a1, a2); + } + + /** + * Return -1, 0, or 1 depending on whether the name of a1 is less, equal to, or greater than + * that of a2 (lexicographically). + * + * @param a1 the first AnnotationMirror to compare + * @param a2 the second AnnotationMirror to compare + * @return true iff a1 and a2 have the same annotation name + * @see #areSame(AnnotationMirror, AnnotationMirror) + */ + @EqualsMethod + public static int compareByName(AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == a2) { + return 0; + } + if (a1 == null || a2 == null) { + throw new BugInCF("Unexpected null argument: compareByName(%s, %s)", a1, a2); + } + + if (a1 instanceof CheckerFrameworkAnnotationMirror + && a2 instanceof CheckerFrameworkAnnotationMirror) { + @Interned @CanonicalName String name1 = ((CheckerFrameworkAnnotationMirror) a1).annotationName; + @Interned @CanonicalName String name2 = ((CheckerFrameworkAnnotationMirror) a2).annotationName; + if (name1 == name2) { + return 0; + } else { + return name1.compareTo(name2); + } + } + + return annotationName(a1).compareTo(annotationName(a2)); + } + + /** + * Return true iff a1 and a2 have the same annotation type. + * + * @param a1 the first AnnotationMirror to compare + * @param a2 the second AnnotationMirror to compare + * @return true iff a1 and a2 have the same annotation name + * @see #areSame(AnnotationMirror, AnnotationMirror) + */ + @EqualsMethod + public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) { + return compareByName(a1, a2) == 0; + } + + /** + * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type + * name). Values are ignored. + * + * @param am the AnnotationMirror whose name to compare + * @param aname the string to compare + * @return true if aname is the name of am + */ + public static boolean areSameByName(AnnotationMirror am, String aname) { + return aname.equals(annotationName(am)); + } + + /** + * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. + * + *

                  This method is not very efficient. It is more efficient to use {@code + * AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}. + * + * @param am the AnnotationMirror whose class to compare + * @param annoClass the class to compare + * @return true if annoclass is the class of am + * @deprecated use {@code AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName} + */ + @Deprecated // for use only by the framework + public static boolean areSameByClass( + AnnotationMirror am, Class annoClass) { + String canonicalName = annoClass.getCanonicalName(); + assert canonicalName != null : "@AssumeAssertion(nullness): assumption"; + return areSameByName(am, canonicalName); + } + + /** + * Checks that two collections contain the same annotations. + * + * @param c1 the first collection to compare + * @param c2 the second collection to compare + * @return true iff c1 and c2 contain the same annotations, according to {@link + * #areSame(AnnotationMirror, AnnotationMirror)} + */ + public static boolean areSame( + Collection c1, Collection c2) { + if (c1.size() != c2.size()) { + return false; + } + if (c1.size() == 1) { + return areSame(c1.iterator().next(), c2.iterator().next()); + } + + // while loop depends on NavigableSet implementation. + AnnotationMirrorSet s1 = new AnnotationMirrorSet(); + AnnotationMirrorSet s2 = new AnnotationMirrorSet(); + s1.addAll(c1); + s2.addAll(c2); + Iterator iter1 = s1.iterator(); + Iterator iter2 = s2.iterator(); + + while (iter1.hasNext()) { + AnnotationMirror anno1 = iter1.next(); + AnnotationMirror anno2 = iter2.next(); + if (!areSame(anno1, anno2)) { + return false; + } + } + return true; + } + + /** + * Checks that the collection contains the annotation. Using Collection.contains does not always + * work, because it does not use areSame for comparison. + * + * @param c a collection of AnnotationMirrors + * @param anno the AnnotationMirror to search for in c + * @return true iff c contains anno, according to areSame + */ + public static boolean containsSame( + Collection c, AnnotationMirror anno) { + return getSame(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}. + * + * @param c a collection of AnnotationMirrors + * @param anno the AnnotationMirror to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according + * to areSame; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getSame( + Collection c, AnnotationMirror anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSame(an, anno)) { + return an; + } + } + return null; + } + + /** + * Checks that the collection contains the annotation. Using Collection.contains does not always + * work, because it does not use areSame for comparison. + * + *

                  This method is not very efficient. It is more efficient to use {@code + * AnnotatedTypeFactory#containsSameByClass} or {@link #containsSameByName}. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation class to search for in c + * @return true iff c contains anno, according to areSameByClass + */ + public static boolean containsSameByClass( + Collection c, Class anno) { + return getAnnotationByClass(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. + * + *

                  This method is not very efficient. It is more efficient to use {@code + * AnnotatedTypeFactory#getAnnotationByClass} or {@link #getAnnotationByName}. + * + * @param c a collection of AnnotationMirrors + * @param anno the class to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according + * to areSameByClass; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getAnnotationByClass( + Collection c, Class anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameByClass(an, anno)) { + return an; + } + } + return null; + } + + /** + * Checks that the collection contains an annotation of the given name. Differs from using + * Collection.contains, which does not use areSameByName for comparison. + * + * @param c a collection of AnnotationMirrors + * @param anno the name to search for in c + * @return true iff c contains anno, according to areSameByName + */ + public static boolean containsSameByName( + Collection c, String anno) { + return getAnnotationByName(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that has the same name as {@code anno}. + * + * @param c a collection of AnnotationMirrors + * @param anno the name to search for in c + * @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to + * areSameByName; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getAnnotationByName( + Collection c, String anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameByName(an, anno)) { + return an; + } + } + return null; + } + + /** + * Checks that the collection contains an annotation of the given name. Differs from using + * Collection.contains, which does not use areSameByName for comparison. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation whose name to search for in c + * @return true iff c contains anno, according to areSameByName + */ + public static boolean containsSameByName( + Collection c, AnnotationMirror anno) { + return getSameByName(c, anno) != null; + } + + /** + * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno} + * ignoring values. + * + * @param c a collection of AnnotationMirrors + * @param anno the annotation whose name to search for in c + * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according + * to areSameByName; otherwise, {@code null} + */ + public static @Nullable AnnotationMirror getSameByName( + Collection c, AnnotationMirror anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameByName(an, anno)) { + return an; + } + } + return null; + } + + /** + * Provide an ordering for {@link AnnotationMirror}s. AnnotationMirrors are first compared by + * their fully-qualified names, then by their element values in order of the name of the + * element. + * + * @param a1 the first annotation + * @param a2 the second annotation + * @return an ordering over AnnotationMirrors based on their name and values + */ + public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror a2) { + int nameComparison = compareByName(a1, a2); + if (nameComparison != 0) { + return nameComparison; + } + + // The annotations have the same name, but different values, so compare values. + Map vals1 = a1.getElementValues(); + Map vals2 = a2.getElementValues(); + Set sortedElements = + new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleSignature)); + sortedElements.addAll( + ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements())); + + // getDefaultValue() returns null if the method is not an annotation interface element. + for (ExecutableElement meth : sortedElements) { + AnnotationValue aval1 = vals1.get(meth); + if (aval1 == null) { + aval1 = meth.getDefaultValue(); + } + AnnotationValue aval2 = vals2.get(meth); + if (aval2 == null) { + aval2 = meth.getDefaultValue(); + } + int result = compareAnnotationValue(aval1, aval2); + if (result != 0) { + return result; + } + } return 0; - } else { - return name1.compareTo(name2); - } - } - - return annotationName(a1).compareTo(annotationName(a2)); - } - - /** - * Return true iff a1 and a2 have the same annotation type. - * - * @param a1 the first AnnotationMirror to compare - * @param a2 the second AnnotationMirror to compare - * @return true iff a1 and a2 have the same annotation name - * @see #areSame(AnnotationMirror, AnnotationMirror) - */ - @EqualsMethod - public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) { - return compareByName(a1, a2) == 0; - } - - /** - * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type name). - * Values are ignored. - * - * @param am the AnnotationMirror whose name to compare - * @param aname the string to compare - * @return true if aname is the name of am - */ - public static boolean areSameByName(AnnotationMirror am, String aname) { - return aname.equals(annotationName(am)); - } - - /** - * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. - * - *

                  This method is not very efficient. It is more efficient to use {@code - * AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}. - * - * @param am the AnnotationMirror whose class to compare - * @param annoClass the class to compare - * @return true if annoclass is the class of am - * @deprecated use {@code AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName} - */ - @Deprecated // for use only by the framework - public static boolean areSameByClass(AnnotationMirror am, Class annoClass) { - String canonicalName = annoClass.getCanonicalName(); - assert canonicalName != null : "@AssumeAssertion(nullness): assumption"; - return areSameByName(am, canonicalName); - } - - /** - * Checks that two collections contain the same annotations. - * - * @param c1 the first collection to compare - * @param c2 the second collection to compare - * @return true iff c1 and c2 contain the same annotations, according to {@link - * #areSame(AnnotationMirror, AnnotationMirror)} - */ - public static boolean areSame( - Collection c1, Collection c2) { - if (c1.size() != c2.size()) { - return false; - } - if (c1.size() == 1) { - return areSame(c1.iterator().next(), c2.iterator().next()); - } - - // while loop depends on NavigableSet implementation. - AnnotationMirrorSet s1 = new AnnotationMirrorSet(); - AnnotationMirrorSet s2 = new AnnotationMirrorSet(); - s1.addAll(c1); - s2.addAll(c2); - Iterator iter1 = s1.iterator(); - Iterator iter2 = s2.iterator(); - - while (iter1.hasNext()) { - AnnotationMirror anno1 = iter1.next(); - AnnotationMirror anno2 = iter2.next(); - if (!areSame(anno1, anno2)) { - return false; - } - } - return true; - } - - /** - * Checks that the collection contains the annotation. Using Collection.contains does not always - * work, because it does not use areSame for comparison. - * - * @param c a collection of AnnotationMirrors - * @param anno the AnnotationMirror to search for in c - * @return true iff c contains anno, according to areSame - */ - public static boolean containsSame( - Collection c, AnnotationMirror anno) { - return getSame(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}. - * - * @param c a collection of AnnotationMirrors - * @param anno the AnnotationMirror to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to - * areSame; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getSame( - Collection c, AnnotationMirror anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSame(an, anno)) { - return an; - } - } - return null; - } - - /** - * Checks that the collection contains the annotation. Using Collection.contains does not always - * work, because it does not use areSame for comparison. - * - *

                  This method is not very efficient. It is more efficient to use {@code - * AnnotatedTypeFactory#containsSameByClass} or {@link #containsSameByName}. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation class to search for in c - * @return true iff c contains anno, according to areSameByClass - */ - public static boolean containsSameByClass( - Collection c, Class anno) { - return getAnnotationByClass(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. - * - *

                  This method is not very efficient. It is more efficient to use {@code - * AnnotatedTypeFactory#getAnnotationByClass} or {@link #getAnnotationByName}. - * - * @param c a collection of AnnotationMirrors - * @param anno the class to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to - * areSameByClass; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getAnnotationByClass( - Collection c, Class anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSameByClass(an, anno)) { - return an; - } - } - return null; - } - - /** - * Checks that the collection contains an annotation of the given name. Differs from using - * Collection.contains, which does not use areSameByName for comparison. - * - * @param c a collection of AnnotationMirrors - * @param anno the name to search for in c - * @return true iff c contains anno, according to areSameByName - */ - public static boolean containsSameByName(Collection c, String anno) { - return getAnnotationByName(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that has the same name as {@code anno}. - * - * @param c a collection of AnnotationMirrors - * @param anno the name to search for in c - * @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to - * areSameByName; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getAnnotationByName( - Collection c, String anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSameByName(an, anno)) { - return an; - } - } - return null; - } - - /** - * Checks that the collection contains an annotation of the given name. Differs from using - * Collection.contains, which does not use areSameByName for comparison. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation whose name to search for in c - * @return true iff c contains anno, according to areSameByName - */ - public static boolean containsSameByName( - Collection c, AnnotationMirror anno) { - return getSameByName(c, anno) != null; - } - - /** - * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno} ignoring - * values. - * - * @param c a collection of AnnotationMirrors - * @param anno the annotation whose name to search for in c - * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to - * areSameByName; otherwise, {@code null} - */ - public static @Nullable AnnotationMirror getSameByName( - Collection c, AnnotationMirror anno) { - for (AnnotationMirror an : c) { - if (AnnotationUtils.areSameByName(an, anno)) { - return an; - } - } - return null; - } - - /** - * Provide an ordering for {@link AnnotationMirror}s. AnnotationMirrors are first compared by - * their fully-qualified names, then by their element values in order of the name of the element. - * - * @param a1 the first annotation - * @param a2 the second annotation - * @return an ordering over AnnotationMirrors based on their name and values - */ - public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror a2) { - int nameComparison = compareByName(a1, a2); - if (nameComparison != 0) { - return nameComparison; - } - - // The annotations have the same name, but different values, so compare values. - Map vals1 = a1.getElementValues(); - Map vals2 = a2.getElementValues(); - Set sortedElements = - new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleSignature)); - sortedElements.addAll( - ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements())); - - // getDefaultValue() returns null if the method is not an annotation interface element. - for (ExecutableElement meth : sortedElements) { - AnnotationValue aval1 = vals1.get(meth); - if (aval1 == null) { - aval1 = meth.getDefaultValue(); - } - AnnotationValue aval2 = vals2.get(meth); - if (aval2 == null) { - aval2 = meth.getDefaultValue(); - } - int result = compareAnnotationValue(aval1, aval2); - if (result != 0) { + } + + /** + * Return 0 iff the two AnnotationValue objects are the same. + * + * @param av1 the first AnnotationValue to compare + * @param av2 the second AnnotationValue to compare + * @return 0 if the two annotation values are the same + */ + @CompareToMethod + private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) { + if (av1 == av2) { + return 0; + } else if (av1 == null) { + return -1; + } else if (av2 == null) { + return 1; + } + return compareAnnotationValueValue(av1.getValue(), av2.getValue()); + } + + /** + * Compares two annotation values for order. + * + * @param val1 a value returned by {@code AnnotationValue.getValue()} + * @param val2 a value returned by {@code AnnotationValue.getValue()} + * @return a negative integer, zero, or a positive integer as the first annotation value is less + * than, equal to, or greater than the second annotation value + */ + @CompareToMethod + private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) { + if (val1 == val2) { + return 0; + } else if (val1 == null) { + return -1; + } else if (val2 == null) { + return 1; + } + // Can't use deepEquals() to compare val1 and val2, because they might have mismatched + // AnnotationValue vs. CheckerFrameworkAnnotationValue, and AnnotationValue doesn't override + // equals(). So, write my own version of deepEquals(). + if ((val1 instanceof List) && (val2 instanceof List)) { + List list1 = (List) val1; + List list2 = (List) val2; + if (list1.size() != list2.size()) { + return list1.size() - list2.size(); + } + // Don't compare setwise, because order can matter. These mean different things: + // @LTLengthOf(value={"a1","a2"}, offest={"0", "1"}) + // @LTLengthOf(value={"a2","a1"}, offest={"0", "1"}) + for (int i = 0; i < list1.size(); i++) { + Object v1 = list1.get(i); + Object v2 = list2.get(i); + int result = compareAnnotationValueValue(v1, v2); + if (result != 0) { + return result; + } + } + return 0; + } else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) { + return compareAnnotationMirrors((AnnotationMirror) val1, (AnnotationMirror) val2); + } else if ((val1 instanceof AnnotationValue) && (val2 instanceof AnnotationValue)) { + // This case occurs because of the recursive call when comparing arrays of annotation + // values. + return compareAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2); + } + + if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) { + // Type.ClassType does not override equals + if (TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2)) { + return 0; + } + } + if (Objects.equals(val1, val2)) { + return 0; + } + int result = val1.toString().compareTo(val2.toString()); + if (result == 0) { + result = -1; + } return result; - } - } - return 0; - } - - /** - * Return 0 iff the two AnnotationValue objects are the same. - * - * @param av1 the first AnnotationValue to compare - * @param av2 the second AnnotationValue to compare - * @return 0 if the two annotation values are the same - */ - @CompareToMethod - private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) { - if (av1 == av2) { - return 0; - } else if (av1 == null) { - return -1; - } else if (av2 == null) { - return 1; - } - return compareAnnotationValueValue(av1.getValue(), av2.getValue()); - } - - /** - * Compares two annotation values for order. - * - * @param val1 a value returned by {@code AnnotationValue.getValue()} - * @param val2 a value returned by {@code AnnotationValue.getValue()} - * @return a negative integer, zero, or a positive integer as the first annotation value is less - * than, equal to, or greater than the second annotation value - */ - @CompareToMethod - private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) { - if (val1 == val2) { - return 0; - } else if (val1 == null) { - return -1; - } else if (val2 == null) { - return 1; - } - // Can't use deepEquals() to compare val1 and val2, because they might have mismatched - // AnnotationValue vs. CheckerFrameworkAnnotationValue, and AnnotationValue doesn't override - // equals(). So, write my own version of deepEquals(). - if ((val1 instanceof List) && (val2 instanceof List)) { - List list1 = (List) val1; - List list2 = (List) val2; - if (list1.size() != list2.size()) { - return list1.size() - list2.size(); - } - // Don't compare setwise, because order can matter. These mean different things: - // @LTLengthOf(value={"a1","a2"}, offest={"0", "1"}) - // @LTLengthOf(value={"a2","a1"}, offest={"0", "1"}) - for (int i = 0; i < list1.size(); i++) { - Object v1 = list1.get(i); - Object v2 = list2.get(i); - int result = compareAnnotationValueValue(v1, v2); - if (result != 0) { - return result; - } - } - return 0; - } else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) { - return compareAnnotationMirrors((AnnotationMirror) val1, (AnnotationMirror) val2); - } else if ((val1 instanceof AnnotationValue) && (val2 instanceof AnnotationValue)) { - // This case occurs because of the recursive call when comparing arrays of annotation - // values. - return compareAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2); - } - - if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) { - // Type.ClassType does not override equals - if (TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2)) { - return 0; - } - } - if (Objects.equals(val1, val2)) { - return 0; - } - int result = val1.toString().compareTo(val2.toString()); - if (result == 0) { - result = -1; - } - return result; - } - - /** - * Returns true if the given annotation has a @Inherited meta-annotation. - * - * @param anno the annotation to check for an @Inherited meta-annotation - * @return true if the given annotation has a @Inherited meta-annotation - */ - public static boolean hasInheritedMeta(AnnotationMirror anno) { - return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; - } - - /** - * Returns the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE. - * - * @param target a location where an annotation can be written - * @return the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE - */ - public static EnumSet getElementKindsForTarget(@Nullable Target target) { - if (target == null) { - // A missing @Target implies that the annotation can be written everywhere. - return EnumSet.allOf(ElementKind.class); - } - EnumSet eleKinds = EnumSet.noneOf(ElementKind.class); - for (ElementType elementType : target.value()) { - eleKinds.addAll(getElementKindsForElementType(elementType)); - } - return eleKinds; - } - - /** - * Returns the set of {@link ElementKind}s corresponding to {@code elementType}. If the element - * type is TYPE_USE, then ElementKinds returned should be the same as those returned for TYPE and - * TYPE_PARAMETER, but this method returns the empty set instead. - * - * @param elementType the elementType to find ElementKinds for - * @return the set of {@link ElementKind}s corresponding to {@code elementType} - */ - public static EnumSet getElementKindsForElementType(ElementType elementType) { - switch (elementType) { - case TYPE: - return EnumSet.copyOf(ElementUtils.typeElementKinds()); - case FIELD: - return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT); - case METHOD: - return EnumSet.of(ElementKind.METHOD); - case PARAMETER: - return EnumSet.of(ElementKind.PARAMETER); - case CONSTRUCTOR: - return EnumSet.of(ElementKind.CONSTRUCTOR); - case LOCAL_VARIABLE: - return EnumSet.of( - ElementKind.LOCAL_VARIABLE, - ElementKind.RESOURCE_VARIABLE, - ElementKind.EXCEPTION_PARAMETER); - case ANNOTATION_TYPE: - return EnumSet.of(ElementKind.ANNOTATION_TYPE); - case PACKAGE: - return EnumSet.of(ElementKind.PACKAGE); - case TYPE_PARAMETER: - return EnumSet.of(ElementKind.TYPE_PARAMETER); - case TYPE_USE: - return EnumSet.noneOf(ElementKind.class); - default: - // TODO: Use MODULE enum constants directly instead of looking them up by name. - // (Java 11) - if (elementType.name().equals("MODULE")) { - return EnumSet.of(ElementKind.valueOf("MODULE")); - } - if (elementType.name().equals("RECORD_COMPONENT")) { - return EnumSet.of(ElementKind.valueOf("RECORD_COMPONENT")); - } - throw new BugInCF("Unrecognized ElementType: " + elementType); - } - } - - // ********************************************************************** - // Annotation values: inefficient extractors that take an element name - // ********************************************************************** - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}. The result - * has type {@code expectedType}. If there is no value for {@code elementName}, {@code - * defaultValue} is returned - * - *

                  This method is intended only for use when the class of the annotation is not on the user's - * classpath. This is for users of the Dataflow Framework that do not use the rest of the Checker - * Framework. Type-checkers can assume that checker-qual.jar is on the classpath and should use - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link - * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the type of the element and the return value - * @param defaultValue the value to return if the element is not present - * @param the class of the type - * @return the value of the element with the given name - */ - public static T getElementValueNotOnClasspath( - AnnotationMirror anno, CharSequence elementName, Class expectedType, T defaultValue) { - Map valmap = anno.getElementValues(); - - for (Map.Entry entry : - valmap.entrySet()) { - ExecutableElement elem = entry.getKey(); - if (elem.getSimpleName().contentEquals(elementName)) { - AnnotationValue val = entry.getValue(); + } + + /** + * Returns true if the given annotation has a @Inherited meta-annotation. + * + * @param anno the annotation to check for an @Inherited meta-annotation + * @return true if the given annotation has a @Inherited meta-annotation + */ + public static boolean hasInheritedMeta(AnnotationMirror anno) { + return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; + } + + /** + * Returns the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE. + * + * @param target a location where an annotation can be written + * @return the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE + */ + public static EnumSet getElementKindsForTarget(@Nullable Target target) { + if (target == null) { + // A missing @Target implies that the annotation can be written everywhere. + return EnumSet.allOf(ElementKind.class); + } + EnumSet eleKinds = EnumSet.noneOf(ElementKind.class); + for (ElementType elementType : target.value()) { + eleKinds.addAll(getElementKindsForElementType(elementType)); + } + return eleKinds; + } + + /** + * Returns the set of {@link ElementKind}s corresponding to {@code elementType}. If the element + * type is TYPE_USE, then ElementKinds returned should be the same as those returned for TYPE + * and TYPE_PARAMETER, but this method returns the empty set instead. + * + * @param elementType the elementType to find ElementKinds for + * @return the set of {@link ElementKind}s corresponding to {@code elementType} + */ + public static EnumSet getElementKindsForElementType(ElementType elementType) { + switch (elementType) { + case TYPE: + return EnumSet.copyOf(ElementUtils.typeElementKinds()); + case FIELD: + return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT); + case METHOD: + return EnumSet.of(ElementKind.METHOD); + case PARAMETER: + return EnumSet.of(ElementKind.PARAMETER); + case CONSTRUCTOR: + return EnumSet.of(ElementKind.CONSTRUCTOR); + case LOCAL_VARIABLE: + return EnumSet.of( + ElementKind.LOCAL_VARIABLE, + ElementKind.RESOURCE_VARIABLE, + ElementKind.EXCEPTION_PARAMETER); + case ANNOTATION_TYPE: + return EnumSet.of(ElementKind.ANNOTATION_TYPE); + case PACKAGE: + return EnumSet.of(ElementKind.PACKAGE); + case TYPE_PARAMETER: + return EnumSet.of(ElementKind.TYPE_PARAMETER); + case TYPE_USE: + return EnumSet.noneOf(ElementKind.class); + default: + // TODO: Use MODULE enum constants directly instead of looking them up by name. + // (Java 11) + if (elementType.name().equals("MODULE")) { + return EnumSet.of(ElementKind.valueOf("MODULE")); + } + if (elementType.name().equals("RECORD_COMPONENT")) { + return EnumSet.of(ElementKind.valueOf("RECORD_COMPONENT")); + } + throw new BugInCF("Unrecognized ElementType: " + elementType); + } + } + + // ********************************************************************** + // Annotation values: inefficient extractors that take an element name + // ********************************************************************** + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}. The result + * has type {@code expectedType}. If there is no value for {@code elementName}, {@code + * defaultValue} is returned + * + *

                  This method is intended only for use when the class of the annotation is not on the user's + * classpath. This is for users of the Dataflow Framework that do not use the rest of the + * Checker Framework. Type-checkers can assume that checker-qual.jar is on the classpath and + * should use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link + * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the type of the element and the return value + * @param defaultValue the value to return if the element is not present + * @param the class of the type + * @return the value of the element with the given name + */ + public static T getElementValueNotOnClasspath( + AnnotationMirror anno, + CharSequence elementName, + Class expectedType, + T defaultValue) { + Map valmap = + anno.getElementValues(); + + for (Map.Entry entry : + valmap.entrySet()) { + ExecutableElement elem = entry.getKey(); + if (elem.getSimpleName().contentEquals(elementName)) { + AnnotationValue val = entry.getValue(); + try { + return expectedType.cast(val.getValue()); + } catch (ClassCastException e) { + throw new BugInCF( + "getElementValueNotOnClasspath(%s, %s, %s): val=%s, val.getValue()=%s [%s]", + anno, + elementName, + expectedType, + val, + val.getValue(), + val.getValue().getClass()); + } + } + } + return defaultValue; + } + + /** + * Returns the values of an annotation's elements, including defaults. The method with the same + * name in JavacElements cannot be used directly, because it includes a cast to + * Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework. + * + *

                  This method is intended for use only by the framework. Clients should use a method that + * takes an {@link ExecutableElement}. + * + * @see AnnotationMirror#getElementValues() + * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror) + * @param ad annotation to examine + * @return the values of the annotation's elements, including defaults + */ + private static Map + getElementValuesWithDefaults(AnnotationMirror ad) { + // Most annotations have no elements. + Map valMap = new ArrayMap<>(0); + if (ad.getElementValues() != null) { + valMap.putAll(ad.getElementValues()); + } + for (ExecutableElement meth : + ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) { + AnnotationValue defaultValue = meth.getDefaultValue(); + if (defaultValue != null) { + valMap.putIfAbsent(meth, defaultValue); + } + } + return valMap; + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}. The result + * has type {@code expectedType}. + * + *

                  If the return type is an array, use {@link #getElementValueArray} instead. + * + *

                  If the return type is an enum, use {@link #getElementValueEnum} instead. + * + *

                  This method is intended only for use by the framework. A checker implementation should use + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link + * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the type of the element and the return value + * @param the class of the type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name + * @deprecated use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)} + */ + @Deprecated // for use only by the framework, not by clients + public static T getElementValue( + AnnotationMirror anno, + CharSequence elementName, + Class expectedType, + boolean useDefaults) { + Map valmap; + if (useDefaults) { + Map valmapTmp = + getElementValuesWithDefaults(anno); + valmap = valmapTmp; + } else { + valmap = anno.getElementValues(); + } + for (Map.Entry entry : + valmap.entrySet()) { + ExecutableElement elem = entry.getKey(); + if (elem.getSimpleName().contentEquals(elementName)) { + AnnotationValue val = entry.getValue(); + try { + return expectedType.cast(val.getValue()); + } catch (ClassCastException e) { + throw new BugInCF( + "getElementValue(%s, %s, %s, %s): val=%s, val.getValue()=%s [%s]", + anno, + elementName, + expectedType, + useDefaults, + val, + val.getValue(), + val.getValue().getClass()); + } + } + } + throw new NoSuchElementException( + String.format( + "No element with name \'%s\' in annotation %s; useDefaults=%s," + + " valmap.keySet()=%s", + elementName, anno, useDefaults, valmap.keySet())); + } + + /** + * Differentiates NoSuchElementException from other BugInCF, for use by getElementValueOrNull. + */ + @SuppressWarnings("serial") + private static class NoSuchElementException extends BugInCF { + /** + * Constructs a new NoSuchElementException. + * + * @param message the detail message + */ + @Pure + public NoSuchElementException(String message) { + super(message); + } + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}, or return + * null if no such element exists. + * + *

                  This method is intended only for use by the framework. A checker implementation should use + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the type of the element and the return value + * @param the class of the type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name, or null + */ + public static @Nullable T getElementValueOrNull( + AnnotationMirror anno, + CharSequence elementName, + Class expectedType, + boolean useDefaults) { + // This implementation permits getElementValue to give a more detailed error message than if + // getElementValue called getElementValueOrNull and threw an error if the result was null. try { - return expectedType.cast(val.getValue()); - } catch (ClassCastException e) { - throw new BugInCF( - "getElementValueNotOnClasspath(%s, %s, %s): val=%s, val.getValue()=%s [%s]", - anno, elementName, expectedType, val, val.getValue(), val.getValue().getClass()); - } - } - } - return defaultValue; - } - - /** - * Returns the values of an annotation's elements, including defaults. The method with the same - * name in JavacElements cannot be used directly, because it includes a cast to - * Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework. - * - *

                  This method is intended for use only by the framework. Clients should use a method that - * takes an {@link ExecutableElement}. - * - * @see AnnotationMirror#getElementValues() - * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror) - * @param ad annotation to examine - * @return the values of the annotation's elements, including defaults - */ - private static Map - getElementValuesWithDefaults(AnnotationMirror ad) { - // Most annotations have no elements. - Map valMap = new ArrayMap<>(0); - if (ad.getElementValues() != null) { - valMap.putAll(ad.getElementValues()); - } - for (ExecutableElement meth : - ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) { - AnnotationValue defaultValue = meth.getDefaultValue(); - if (defaultValue != null) { - valMap.putIfAbsent(meth, defaultValue); - } - } - return valMap; - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}. The result - * has type {@code expectedType}. - * - *

                  If the return type is an array, use {@link #getElementValueArray} instead. - * - *

                  If the return type is an enum, use {@link #getElementValueEnum} instead. - * - *

                  This method is intended only for use by the framework. A checker implementation should use - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link - * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the type of the element and the return value - * @param the class of the type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name - * @deprecated use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link - * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)} - */ - @Deprecated // for use only by the framework, not by clients - public static T getElementValue( - AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { - Map valmap; - if (useDefaults) { - Map valmapTmp = - getElementValuesWithDefaults(anno); - valmap = valmapTmp; - } else { - valmap = anno.getElementValues(); - } - for (Map.Entry entry : - valmap.entrySet()) { - ExecutableElement elem = entry.getKey(); - if (elem.getSimpleName().contentEquals(elementName)) { - AnnotationValue val = entry.getValue(); + return getElementValue(anno, elementName, expectedType, useDefaults); + } catch (NoSuchElementException e) { + return null; + } + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}, or return + * null if no such element exists. One element of the result has type {@code expectedType}. + * + *

                  This method is intended only for use by the framework. A checker implementation should use + * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation whose element to access + * @param elementName the name of the element to access + * @param expectedType the component type of the element and of the return value + * @param the class of the component type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name, or null + */ + public static @Nullable List getElementValueArrayOrNull( + AnnotationMirror anno, + CharSequence elementName, + Class expectedType, + boolean useDefaults) { + // This implementation permits getElementValue to give a more detailed error message than if + // getElementValue called getElementValueOrNull and threw an error if the result was null. try { - return expectedType.cast(val.getValue()); - } catch (ClassCastException e) { - throw new BugInCF( - "getElementValue(%s, %s, %s, %s): val=%s, val.getValue()=%s [%s]", - anno, - elementName, - expectedType, - useDefaults, - val, - val.getValue(), - val.getValue().getClass()); - } - } - } - throw new NoSuchElementException( - String.format( - "No element with name \'%s\' in annotation %s; useDefaults=%s," + " valmap.keySet()=%s", - elementName, anno, useDefaults, valmap.keySet())); - } - - /** Differentiates NoSuchElementException from other BugInCF, for use by getElementValueOrNull. */ - @SuppressWarnings("serial") - private static class NoSuchElementException extends BugInCF { - /** - * Constructs a new NoSuchElementException. - * - * @param message the detail message - */ - @Pure - public NoSuchElementException(String message) { - super(message); - } - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}, or return - * null if no such element exists. - * - *

                  This method is intended only for use by the framework. A checker implementation should use - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the type of the element and the return value - * @param the class of the type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name, or null - */ - public static @Nullable T getElementValueOrNull( - AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { - // This implementation permits getElementValue to give a more detailed error message than if - // getElementValue called getElementValueOrNull and threw an error if the result was null. - try { - return getElementValue(anno, elementName, expectedType, useDefaults); - } catch (NoSuchElementException e) { - return null; - } - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}, or return - * null if no such element exists. One element of the result has type {@code expectedType}. - * - *

                  This method is intended only for use by the framework. A checker implementation should use - * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation whose element to access - * @param elementName the name of the element to access - * @param expectedType the component type of the element and of the return value - * @param the class of the component type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name, or null - */ - public static @Nullable List getElementValueArrayOrNull( - AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { - // This implementation permits getElementValue to give a more detailed error message than if - // getElementValue called getElementValueOrNull and threw an error if the result was null. - try { - return getElementValueArray(anno, elementName, expectedType, useDefaults); - } catch (NoSuchElementException e) { - return null; - } - } - - /** - * Get the element with the name {@code elementName} of the annotation {@code anno}, where the - * element has an array type. One element of the result has type {@code expectedType}. - * - *

                  Parameter useDefaults is used to determine whether default values should be used for - * annotation values. Finding defaults requires more computation, so should be false when no - * defaulting is needed. - * - *

                  This method is intended only for use by the framework. A checker implementation should use - * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or {@code - * #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)}. - * - * @param anno the annotation to disassemble - * @param elementName the name of the element to access - * @param expectedType the component type of the element and of the return type - * @param the class of the type - * @param useDefaults whether to apply default values to the element - * @return the value of the element with the given name; it is a new list, so it is safe for - * clients to side-effect - * @deprecated use {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or - * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)} - */ - @Deprecated // for use only by the framework - public static List getElementValueArray( - AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { - @SuppressWarnings("unchecked") - List la = getElementValue(anno, elementName, List.class, useDefaults); - List result = new ArrayList<>(la.size()); - for (AnnotationValue a : la) { - try { - result.add(expectedType.cast(a.getValue())); - } catch (Throwable t) { - String err1 = - String.format( - "getElementValueArray(%n" - + " anno=%s,%n" - + " elementName=%s,%n" - + " expectedType=%s,%n" - + " useDefaults=%s)%n", - anno, elementName, expectedType, useDefaults); - String err2 = - String.format( - "Error in cast:%n expectedType=%s%n a=%s [%s]%n a.getValue()=%s" + " [%s]", - expectedType, a, a.getClass(), a.getValue(), a.getValue().getClass()); - throw new BugInCF(err1 + "; " + err2, t); - } - } - return result; - } - - /** - * Get the Name of the class that is referenced by element {@code elementName}. - * - *

                  This is a convenience method for the most common use-case. It is like {@code - * getElementValue(anno, elementName, ClassType.class).getQualifiedName()}, but this method - * ensures consistent use of the qualified name. - * - *

                  This method is intended only for use by the framework. A checker implementation should use - * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. - * - * @param anno the annotation to disassemble - * @param elementName the name of the element to access; it must be present in the annotation - * @param useDefaults whether to apply default values to the element - * @return the name of the class that is referenced by element with the given name; may be an - * empty name, for a local or anonymous class - * @deprecated use an ExecutableElement - */ - @Deprecated // for use only by the framework - public static @CanonicalName Name getElementValueClassName( - AnnotationMirror anno, CharSequence elementName, boolean useDefaults) { - Type.ClassType ct = getElementValue(anno, elementName, Type.ClassType.class, useDefaults); - // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? - @CanonicalName Name result = ct.asElement().getQualifiedName(); - return result; - } - - // ********************************************************************** - // Annotation values: efficient extractors that take an ExecutableElement - // ********************************************************************** - - /** - * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}. - * - *

                  If the return type is primitive, use {@link #getElementValueInt} or {@link - * #getElementValueLong} instead. - * - *

                  If the return type is an array, use {@link #getElementValueArray} instead. - * - *

                  If the return type is an enum, use {@link #getElementValueEnum} instead. - * - * @param anno the annotation whose element to access - * @param element the element to access; it must be present in the annotation - * @param expectedType the type of the element and the return value - * @param the class of the type - * @return the value of the element with the given name - */ - public static T getElementValue( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValue(%s, %s, ...)", anno, element); - } - return expectedType.cast(av.getValue()); - } - - /** - * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}. - * - *

                  If the return type is primitive, use {@link #getElementValueInt} or {@link - * #getElementValueLong} instead. - * - *

                  If the return type is an array, use {@link #getElementValueArray} instead. - * - *

                  If the return type is an enum, use {@link #getElementValueEnum} instead. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param expectedType the type of the element and the return value - * @param the class of the type - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static T getElementValue( - AnnotationMirror anno, ExecutableElement element, Class expectedType, T defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return expectedType.cast(av.getValue()); - } - } - - /** - * Get the given boolean element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static boolean getElementValueBoolean( - AnnotationMirror anno, ExecutableElement element, boolean defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return (boolean) av.getValue(); - } - } - - /** - * Get the given integer element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @return the value of the element with the given name - */ - public static int getElementValueInt(AnnotationMirror anno, ExecutableElement element) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueInt(%s, %s, ...)", anno, element); - } else { - return (int) av.getValue(); - } - } - - /** - * Get the given integer element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static int getElementValueInt( - AnnotationMirror anno, ExecutableElement element, int defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return (int) av.getValue(); - } - } - - /** - * Get the given long element of the annotation {@code anno}. - * - * @param anno the annotation whose element to access - * @param element the element to access - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static long getElementValueLong( - AnnotationMirror anno, ExecutableElement element, long defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return (long) av.getValue(); - } - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * enum of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @param expectedType the type of the element and the return value, an enum - * @param the class of the type - * @return the value of the element with the given name - */ - public static > T getElementValueEnum( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueEnum(%s, %s, ...)", anno, element); - } - VariableElement ve = (VariableElement) av.getValue(); - return Enum.valueOf(expectedType, ve.getSimpleName().toString()); - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * enum of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access - * @param expectedType the type of the element and the return value, an enum - * @param the class of the type - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name - */ - public static > T getElementValueEnum( - AnnotationMirror anno, ExecutableElement element, Class expectedType, T defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - VariableElement ve = (VariableElement) av.getValue(); - return Enum.valueOf(expectedType, ve.getSimpleName().toString()); - } - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * array of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @param expectedType the component type of the element and of the return value, an enum - * @param the enum class of the component type - * @return the value of the element with the given name - */ - public static > T[] getElementValueEnumArray( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueEnumArray(%s, %s, ...)", anno, element); - } - return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); - } - - /** - * Get the element with the name {@code name} of the annotation {@code anno}. The result is an - * array of type {@code T}. - * - * @param anno the annotation to disassemble - * @param element the element to access - * @param expectedType the component type of the element and of the return type - * @param the enum class of the component type - * @param defaultValue the value to return if the annotation does not have the element - * @return the value of the element with the given name - */ - public static > T[] getElementValueEnumArray( - AnnotationMirror anno, ExecutableElement element, Class expectedType, T[] defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); - } - } - - /** - * Get the given element of the annotation {@code anno}, where the element has an array type. One - * element of the result has type {@code expectedType}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @param expectedType the component type of the element and of the return type - * @param the class of the component type - * @return the value of the element with the given name; it is a new list, so it is safe for - * clients to side-effect - */ - public static List getElementValueArray( - AnnotationMirror anno, ExecutableElement element, Class expectedType) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - throw new BugInCF("getElementValueArray(%s, %s, ...)", anno, element); - } - return annotationValueToList(av, expectedType); - } - - /** - * Get the given element of the annotation {@code anno}, where the element has an array type. One - * element of the result has type {@code expectedType}. - * - * @param anno the annotation to disassemble - * @param element the element to access - * @param expectedType the component type of the element and of the return type - * @param the class of the component type - * @param defaultValue the value to return if the element is not present - * @return the value of the element with the given name; it is a new list, so it is safe for - * clients to side-effect - */ - public static List getElementValueArray( - AnnotationMirror anno, - ExecutableElement element, - Class expectedType, - List defaultValue) { - AnnotationValue av = anno.getElementValues().get(element); - if (av == null) { - return defaultValue; - } else { - return annotationValueToList(av, expectedType); - } - } - - /** - * Converts a list of AnnotationValue to an array of enum. - * - * @param the element type of the enum array - * @param avList a list of AnnotationValue - * @param expectedType the component type of the element and of the return type, an enum - * @return an array of enum, converted from the input list - */ - public static > T[] annotationValueListToEnumArray( - AnnotationValue avList, Class expectedType) { - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueListToEnumArray(list, expectedType); - } - - /** - * Converts a list of AnnotationValue to an array of enum. - * - * @param the element type of the enum array - * @param la a list of AnnotationValue - * @param expectedType the component type of the element and of the return type, an enum - * @return an array of enum, converted from the input list - */ - public static > T[] annotationValueListToEnumArray( - List la, Class expectedType) { - int size = la.size(); - @SuppressWarnings("unchecked") - T[] result = (T[]) Array.newInstance(expectedType, size); - for (int i = 0; i < size; i++) { - AnnotationValue a = la.get(i); - T value = Enum.valueOf(expectedType, a.getValue().toString()); - result[i] = value; - } - return result; - } - - /** - * Get the Name of the class that is referenced by element {@code element}. - * - *

                  This is a convenience method for the most common use-case. It is like {@code - * getElementValue(anno, element, ClassType.class).getQualifiedName()}, but this method ensures - * consistent use of the qualified name. - * - *

                  This method is intended only for use by the framework. A checker implementation should use - * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. - * - * @param anno the annotation to disassemble - * @param element the element to access; it must be present in the annotation - * @return the name of the class that is referenced by element with the given name; may be an - * empty name, for a local or anonymous class - */ - public static @CanonicalName Name getElementValueClassName( - AnnotationMirror anno, ExecutableElement element) { - Type.ClassType ct = getElementValue(anno, element, Type.ClassType.class); - if (ct == null) { - throw new BugInCF("getElementValueClassName(%s, %s, ...)", anno, element); - } - // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? - @CanonicalName Name result = ct.asElement().getQualifiedName(); - return result; - } - - /** - * Get the list of Names of the classes that are referenced by element {@code element}. It fails - * if the class wasn't found. - * - * @param anno the annotation whose field to access; it must be present in the annotation - * @param element the element/field of {@code anno} whose content is a list of classes - * @return the names of classes in {@code anno.annoElement} - */ - public static List<@CanonicalName Name> getElementValueClassNames( - AnnotationMirror anno, ExecutableElement element) { - List la = getElementValueArray(anno, element, Type.ClassType.class); - return CollectionsPlume.mapList( - (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la); - } - - // ********************************************************************** - // Annotation values: other methods (e.g., testing and transforming) - // ********************************************************************** - - /** - * Returns true if the two annotations have the same elements (fields). The arguments {@code am1} - * and {@code am2} must be the same type of annotation. - * - * @param am1 the first AnnotationMirror to compare - * @param am2 the second AnnotationMirror to compare - * @return true if the two annotations have the same elements (fields) - */ - @EqualsMethod - public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { - if (am1 == am2) { - return true; - } - - Map vals1 = am1.getElementValues(); - Map vals2 = am2.getElementValues(); - for (ExecutableElement meth : - ElementFilter.methodsIn(am1.getAnnotationType().asElement().getEnclosedElements())) { - AnnotationValue aval1 = vals1.get(meth); - AnnotationValue aval2 = vals2.get(meth); - @SuppressWarnings("interning:not.interned") // optimization via equality test - boolean identical = aval1 == aval2; - if (identical) { - // Handles when both aval1 and aval2 are null, and maybe other cases too. - continue; - } - if (aval1 == null) { - aval1 = meth.getDefaultValue(); - } - if (aval2 == null) { - aval2 = meth.getDefaultValue(); - } - if (!sameAnnotationValue(aval1, aval2)) { - return false; - } - } - return true; - } - - /** - * Return true iff the two AnnotationValue objects are the same. Use this instead of - * CheckerFrameworkAnnotationValue.equals, which wouldn't get called if the receiver is some - * AnnotationValue other than CheckerFrameworkAnnotationValue. - * - * @param av1 the first AnnotationValue to compare - * @param av2 the second AnnotationValue to compare - * @return true if the two annotation values are the same - */ - public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) { - return compareAnnotationValue(av1, av2) == 0; - } - - /** - * Returns true if an AnnotationValue list contains the given value. - * - *

                  Using this method is slightly cheaper than creating a new {@code List} just for the - * purpose of testing containment within it. - * - * @param avList an AnnotationValue that is null or a list of Strings - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContains(@Nullable AnnotationValue avList, String s) { - if (avList == null) { - return false; - } - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueContains(list, s); - } - - /** - * Returns true if an AnnotationValue list contains the given value. - * - *

                  Using this method is slightly cheaper than creating a new {@code List} just for the - * purpose of testing containment within it. - * - * @param avList a list of Strings (as {@code AnnotationValue}s) - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContains(List avList, String s) { - for (AnnotationValue av : avList) { - if (av.getValue().equals(s)) { - return true; - } - } - return false; - } - - /** - * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given - * string. - * - *

                  Using this method is slightly cheaper than creating a new {@code List} just for the purpose - * of testing containment within it. - * - * @param avList an AnnotationValue that is null or a list - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContainsToString( - @Nullable AnnotationValue avList, String s) { - if (avList == null) { - return false; - } - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueContainsToString(list, s); - } - - /** - * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given - * string. - * - *

                  Using this method is slightly cheaper than creating a new {@code List} just for the purpose - * of testing containment within it. - * - * @param avList a list of Strings (as {@code AnnotationValue}s) - * @param s a string - * @return true if {@code av} contains {@code s} - */ - public static boolean annotationValueContainsToString( - List avList, String s) { - for (AnnotationValue av : avList) { - if (av.getValue().toString().equals(s)) { - return true; - } - } - return false; - } - - /** - * Converts an annotation value to a list. - * - *

                  To test containment, use {@link #annotationValueContains(AnnotationValue, String)} or {@link - * #annotationValueContainsToString(AnnotationValue, String)}. - * - * @param avList an AnnotationValue that is a list of Strings - * @param expectedType the component type of the argument and of the return type, an enum - * @param the class of the type - * @return the annotation value, converted to a list - */ - public static List annotationValueToList(AnnotationValue avList, Class expectedType) { - @SuppressWarnings("unchecked") - List list = (List) avList.getValue(); - return annotationValueToList(list, expectedType); - } - - /** - * Converts an annotation value to a list. - * - *

                  To test containment, use {@link #annotationValueContains(List, String)} or {@link - * #annotationValueContainsToString(List, String)}. - * - * @param avList a list of Strings (as {@code AnnotationValue}s) - * @param expectedType the component type of the argument and of the return type, an enum - * @param the class of the type - * @return the annotation value, converted to a list - */ - public static List annotationValueToList( - List avList, Class expectedType) { - List result = new ArrayList<>(avList.size()); - for (AnnotationValue a : avList) { - try { - result.add(expectedType.cast(a.getValue())); - } catch (Throwable t) { - String err1 = String.format("annotationValueToList(%s, %s)", avList, expectedType); - String err2 = - String.format( - "a=%s [%s]%n a.getValue()=%s [%s]", - a, a.getClass(), a.getValue(), a.getValue().getClass()); - throw new BugInCF(err1 + " " + err2, t); - } - } - return result; - } - - // ********************************************************************** - // Other methods - // ********************************************************************** - - // The Javadoc doesn't use @link because framework is a different project than this one - // (javacutil). - /** - * Update a map, to add {@code newQual} to the set that {@code key} maps to. The mapped-to element - * is an unmodifiable set. - * - *

                  See - * org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, - * Map, Object, AnnotationMirror). - * - * @param map the map to update - * @param key the key whose value to update - * @param newQual the element to add to the given key's value - * @param the key type - */ - public static void updateMappingToImmutableSet( - Map map, T key, AnnotationMirrorSet newQual) { - - AnnotationMirrorSet result = new AnnotationMirrorSet(); - // TODO: if T is also an AnnotationMirror, should we use areSame? - if (!map.containsKey(key)) { - result.addAll(newQual); - } else { - result.addAll(map.get(key)); - result.addAll(newQual); - } - result.makeUnmodifiable(); - map.put(key, result); - } - - /** - * Returns the annotations explicitly written on a constructor result. Callers should check that - * {@code constructorDeclaration} is in fact a declaration of a constructor. - * - * @param constructorDeclaration declaration tree of constructor - * @return set of annotations explicit on the resulting type of the constructor - */ - public static AnnotationMirrorSet getExplicitAnnotationsOnConstructorResult( - MethodTree constructorDeclaration) { - AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); - ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); - if (modifiersTree != null) { - List annotationTrees = modifiersTree.getAnnotations(); - annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); - } - return annotationSet; - } - - /** - * Returns true if {@code anno} is not a type use annotation, that is, it cannot be written on - * uses of types. - * - * @param anno the AnnotationMirror - * @return true if anno is a declaration annotation - * @deprecated use {@link #isTypeUseAnnotation(AnnotationMirror)} instead - */ - @Deprecated // 2024-01-06 - public static boolean isDeclarationAnnotation(AnnotationMirror anno) { - return !isTypeUseAnnotation(anno); - } - - /** - * Returns true if {@code anno} is a type use annotation, that is, it can be written on uses of - * types. - * - * @param anno the AnnotationMirror - * @return true if anno is a declaration annotation - */ - public static boolean isTypeUseAnnotation(AnnotationMirror anno) { - TypeElement elem = (TypeElement) anno.getAnnotationType().asElement(); - Target t = elem.getAnnotation(Target.class); - if (t == null) { - return false; - } - - for (ElementType elementType : t.value()) { - if (elementType == ElementType.TYPE_USE) { + return getElementValueArray(anno, elementName, expectedType, useDefaults); + } catch (NoSuchElementException e) { + return null; + } + } + + /** + * Get the element with the name {@code elementName} of the annotation {@code anno}, where the + * element has an array type. One element of the result has type {@code expectedType}. + * + *

                  Parameter useDefaults is used to determine whether default values should be used for + * annotation values. Finding defaults requires more computation, so should be false when no + * defaulting is needed. + * + *

                  This method is intended only for use by the framework. A checker implementation should use + * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or {@code + * #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)}. + * + * @param anno the annotation to disassemble + * @param elementName the name of the element to access + * @param expectedType the component type of the element and of the return type + * @param the class of the type + * @param useDefaults whether to apply default values to the element + * @return the value of the element with the given name; it is a new list, so it is safe for + * clients to side-effect + * @deprecated use {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or + * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)} + */ + @Deprecated // for use only by the framework + public static List getElementValueArray( + AnnotationMirror anno, + CharSequence elementName, + Class expectedType, + boolean useDefaults) { + @SuppressWarnings("unchecked") + List la = getElementValue(anno, elementName, List.class, useDefaults); + List result = new ArrayList<>(la.size()); + for (AnnotationValue a : la) { + try { + result.add(expectedType.cast(a.getValue())); + } catch (Throwable t) { + String err1 = + String.format( + "getElementValueArray(%n" + + " anno=%s,%n" + + " elementName=%s,%n" + + " expectedType=%s,%n" + + " useDefaults=%s)%n", + anno, elementName, expectedType, useDefaults); + String err2 = + String.format( + "Error in cast:%n expectedType=%s%n a=%s [%s]%n a.getValue()=%s" + + " [%s]", + expectedType, + a, + a.getClass(), + a.getValue(), + a.getValue().getClass()); + throw new BugInCF(err1 + "; " + err2, t); + } + } + return result; + } + + /** + * Get the Name of the class that is referenced by element {@code elementName}. + * + *

                  This is a convenience method for the most common use-case. It is like {@code + * getElementValue(anno, elementName, ClassType.class).getQualifiedName()}, but this method + * ensures consistent use of the qualified name. + * + *

                  This method is intended only for use by the framework. A checker implementation should use + * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. + * + * @param anno the annotation to disassemble + * @param elementName the name of the element to access; it must be present in the annotation + * @param useDefaults whether to apply default values to the element + * @return the name of the class that is referenced by element with the given name; may be an + * empty name, for a local or anonymous class + * @deprecated use an ExecutableElement + */ + @Deprecated // for use only by the framework + public static @CanonicalName Name getElementValueClassName( + AnnotationMirror anno, CharSequence elementName, boolean useDefaults) { + Type.ClassType ct = getElementValue(anno, elementName, Type.ClassType.class, useDefaults); + // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? + @CanonicalName Name result = ct.asElement().getQualifiedName(); + return result; + } + + // ********************************************************************** + // Annotation values: efficient extractors that take an ExecutableElement + // ********************************************************************** + + /** + * Get the given element of the annotation {@code anno}. The result has type {@code + * expectedType}. + * + *

                  If the return type is primitive, use {@link #getElementValueInt} or {@link + * #getElementValueLong} instead. + * + *

                  If the return type is an array, use {@link #getElementValueArray} instead. + * + *

                  If the return type is an enum, use {@link #getElementValueEnum} instead. + * + * @param anno the annotation whose element to access + * @param element the element to access; it must be present in the annotation + * @param expectedType the type of the element and the return value + * @param the class of the type + * @return the value of the element with the given name + */ + public static T getElementValue( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValue(%s, %s, ...)", anno, element); + } + return expectedType.cast(av.getValue()); + } + + /** + * Get the given element of the annotation {@code anno}. The result has type {@code + * expectedType}. + * + *

                  If the return type is primitive, use {@link #getElementValueInt} or {@link + * #getElementValueLong} instead. + * + *

                  If the return type is an array, use {@link #getElementValueArray} instead. + * + *

                  If the return type is an enum, use {@link #getElementValueEnum} instead. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param expectedType the type of the element and the return value + * @param the class of the type + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static T getElementValue( + AnnotationMirror anno, + ExecutableElement element, + Class expectedType, + T defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return expectedType.cast(av.getValue()); + } + } + + /** + * Get the given boolean element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static boolean getElementValueBoolean( + AnnotationMirror anno, ExecutableElement element, boolean defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return (boolean) av.getValue(); + } + } + + /** + * Get the given integer element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @return the value of the element with the given name + */ + public static int getElementValueInt(AnnotationMirror anno, ExecutableElement element) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueInt(%s, %s, ...)", anno, element); + } else { + return (int) av.getValue(); + } + } + + /** + * Get the given integer element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static int getElementValueInt( + AnnotationMirror anno, ExecutableElement element, int defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return (int) av.getValue(); + } + } + + /** + * Get the given long element of the annotation {@code anno}. + * + * @param anno the annotation whose element to access + * @param element the element to access + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static long getElementValueLong( + AnnotationMirror anno, ExecutableElement element, long defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return (long) av.getValue(); + } + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * enum of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @param expectedType the type of the element and the return value, an enum + * @param the class of the type + * @return the value of the element with the given name + */ + public static > T getElementValueEnum( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueEnum(%s, %s, ...)", anno, element); + } + VariableElement ve = (VariableElement) av.getValue(); + return Enum.valueOf(expectedType, ve.getSimpleName().toString()); + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * enum of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access + * @param expectedType the type of the element and the return value, an enum + * @param the class of the type + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name + */ + public static > T getElementValueEnum( + AnnotationMirror anno, + ExecutableElement element, + Class expectedType, + T defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + VariableElement ve = (VariableElement) av.getValue(); + return Enum.valueOf(expectedType, ve.getSimpleName().toString()); + } + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * array of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @param expectedType the component type of the element and of the return value, an enum + * @param the enum class of the component type + * @return the value of the element with the given name + */ + public static > T[] getElementValueEnumArray( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueEnumArray(%s, %s, ...)", anno, element); + } + return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); + } + + /** + * Get the element with the name {@code name} of the annotation {@code anno}. The result is an + * array of type {@code T}. + * + * @param anno the annotation to disassemble + * @param element the element to access + * @param expectedType the component type of the element and of the return type + * @param the enum class of the component type + * @param defaultValue the value to return if the annotation does not have the element + * @return the value of the element with the given name + */ + public static > T[] getElementValueEnumArray( + AnnotationMirror anno, + ExecutableElement element, + Class expectedType, + T[] defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); + } + } + + /** + * Get the given element of the annotation {@code anno}, where the element has an array type. + * One element of the result has type {@code expectedType}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @param expectedType the component type of the element and of the return type + * @param the class of the component type + * @return the value of the element with the given name; it is a new list, so it is safe for + * clients to side-effect + */ + public static List getElementValueArray( + AnnotationMirror anno, ExecutableElement element, Class expectedType) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + throw new BugInCF("getElementValueArray(%s, %s, ...)", anno, element); + } + return annotationValueToList(av, expectedType); + } + + /** + * Get the given element of the annotation {@code anno}, where the element has an array type. + * One element of the result has type {@code expectedType}. + * + * @param anno the annotation to disassemble + * @param element the element to access + * @param expectedType the component type of the element and of the return type + * @param the class of the component type + * @param defaultValue the value to return if the element is not present + * @return the value of the element with the given name; it is a new list, so it is safe for + * clients to side-effect + */ + public static List getElementValueArray( + AnnotationMirror anno, + ExecutableElement element, + Class expectedType, + List defaultValue) { + AnnotationValue av = anno.getElementValues().get(element); + if (av == null) { + return defaultValue; + } else { + return annotationValueToList(av, expectedType); + } + } + + /** + * Converts a list of AnnotationValue to an array of enum. + * + * @param the element type of the enum array + * @param avList a list of AnnotationValue + * @param expectedType the component type of the element and of the return type, an enum + * @return an array of enum, converted from the input list + */ + public static > T[] annotationValueListToEnumArray( + AnnotationValue avList, Class expectedType) { + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueListToEnumArray(list, expectedType); + } + + /** + * Converts a list of AnnotationValue to an array of enum. + * + * @param the element type of the enum array + * @param la a list of AnnotationValue + * @param expectedType the component type of the element and of the return type, an enum + * @return an array of enum, converted from the input list + */ + public static > T[] annotationValueListToEnumArray( + List la, Class expectedType) { + int size = la.size(); + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(expectedType, size); + for (int i = 0; i < size; i++) { + AnnotationValue a = la.get(i); + T value = Enum.valueOf(expectedType, a.getValue().toString()); + result[i] = value; + } + return result; + } + + /** + * Get the Name of the class that is referenced by element {@code element}. + * + *

                  This is a convenience method for the most common use-case. It is like {@code + * getElementValue(anno, element, ClassType.class).getQualifiedName()}, but this method ensures + * consistent use of the qualified name. + * + *

                  This method is intended only for use by the framework. A checker implementation should use + * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. + * + * @param anno the annotation to disassemble + * @param element the element to access; it must be present in the annotation + * @return the name of the class that is referenced by element with the given name; may be an + * empty name, for a local or anonymous class + */ + public static @CanonicalName Name getElementValueClassName( + AnnotationMirror anno, ExecutableElement element) { + Type.ClassType ct = getElementValue(anno, element, Type.ClassType.class); + if (ct == null) { + throw new BugInCF("getElementValueClassName(%s, %s, ...)", anno, element); + } + // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? + @CanonicalName Name result = ct.asElement().getQualifiedName(); + return result; + } + + /** + * Get the list of Names of the classes that are referenced by element {@code element}. It fails + * if the class wasn't found. + * + * @param anno the annotation whose field to access; it must be present in the annotation + * @param element the element/field of {@code anno} whose content is a list of classes + * @return the names of classes in {@code anno.annoElement} + */ + public static List<@CanonicalName Name> getElementValueClassNames( + AnnotationMirror anno, ExecutableElement element) { + List la = getElementValueArray(anno, element, Type.ClassType.class); + return CollectionsPlume.mapList( + (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la); + } + + // ********************************************************************** + // Annotation values: other methods (e.g., testing and transforming) + // ********************************************************************** + + /** + * Returns true if the two annotations have the same elements (fields). The arguments {@code + * am1} and {@code am2} must be the same type of annotation. + * + * @param am1 the first AnnotationMirror to compare + * @param am2 the second AnnotationMirror to compare + * @return true if the two annotations have the same elements (fields) + */ + @EqualsMethod + public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { + if (am1 == am2) { + return true; + } + + Map vals1 = am1.getElementValues(); + Map vals2 = am2.getElementValues(); + for (ExecutableElement meth : + ElementFilter.methodsIn( + am1.getAnnotationType().asElement().getEnclosedElements())) { + AnnotationValue aval1 = vals1.get(meth); + AnnotationValue aval2 = vals2.get(meth); + @SuppressWarnings("interning:not.interned") // optimization via equality test + boolean identical = aval1 == aval2; + if (identical) { + // Handles when both aval1 and aval2 are null, and maybe other cases too. + continue; + } + if (aval1 == null) { + aval1 = meth.getDefaultValue(); + } + if (aval2 == null) { + aval2 = meth.getDefaultValue(); + } + if (!sameAnnotationValue(aval1, aval2)) { + return false; + } + } return true; - } - } - return false; - } - - /** - * Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise. - * - * @param elements an array of {@link ElementType} values - * @param cls the annotation class being tested; used for diagnostic messages only - * @return true iff the give array contains {@link ElementType#TYPE_USE} - * @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and something - * besides {@link ElementType#TYPE_PARAMETER} - */ - public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class cls) { - // True if the array contains TYPE_USE - boolean hasTypeUse = false; - // Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER - ElementType otherElementType = null; - - for (ElementType element : elements) { - if (element == ElementType.TYPE_USE) { - hasTypeUse = true; - } else if (element != ElementType.TYPE_PARAMETER) { - otherElementType = element; - } - if (hasTypeUse && otherElementType != null) { - throw new BugInCF( - "@Target meta-annotation should not contain both TYPE_USE and " - + otherElementType - + ", for annotation " - + cls.getName()); - } - } - - return hasTypeUse; - } - - /** - * Returns a string representation of the annotation mirrors, using simple (not fully-qualified) - * names. - * - * @param annos annotations to format - * @return the string representation, using simple (not fully-qualified) names - */ - @SideEffectFree - public static String toStringSimple(AnnotationMirrorSet annos) { - StringJoiner result = new StringJoiner(" "); - for (AnnotationMirror am : annos) { - result.add(toStringSimple(am)); - } - return result.toString(); - } - - /** - * Returns a string representation of the annotation mirror, using simple (not fully-qualified) - * names. - * - * @param am annotation to format - * @return the string representation, using simple (not fully-qualified) names - */ - @SideEffectFree - public static String toStringSimple(AnnotationMirror am) { - StringBuilder sb = new StringBuilder(); - toStringSimple(am, sb); - return sb.toString(); - } - - /** - * Appends a string representation of the annotation mirror, using simple (not fully-qualified) - * names, to the StringBuilder. - * - * @param am annotation to format - * @param sb StringBuilder to which the string representation of am, using simple (not - * fully-qualified) names, is appended - */ - public static void toStringSimple(AnnotationMirror am, StringBuilder sb) { - sb.append("@"); - sb.append(am.getAnnotationType().asElement().getSimpleName()); - Map args = removeDefaultValues(am.getElementValues()); - if (!args.isEmpty()) { - sb.append("("); - boolean oneValue = false; - if (args.size() == 1) { - Map.Entry first = args.entrySet().iterator().next(); - if (first.getKey().getSimpleName().contentEquals("value")) { - formatAnnotationMirrorArg(first.getValue(), sb); - oneValue = true; - } - } - if (!oneValue) { - boolean notfirst = false; - for (Map.Entry arg : args.entrySet()) { - if (!"{}".equals(arg.getValue().toString())) { - if (notfirst) { - sb.append(", "); + } + + /** + * Return true iff the two AnnotationValue objects are the same. Use this instead of + * CheckerFrameworkAnnotationValue.equals, which wouldn't get called if the receiver is some + * AnnotationValue other than CheckerFrameworkAnnotationValue. + * + * @param av1 the first AnnotationValue to compare + * @param av2 the second AnnotationValue to compare + * @return true if the two annotation values are the same + */ + public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) { + return compareAnnotationValue(av1, av2) == 0; + } + + /** + * Returns true if an AnnotationValue list contains the given value. + * + *

                  Using this method is slightly cheaper than creating a new {@code List} just for + * the purpose of testing containment within it. + * + * @param avList an AnnotationValue that is null or a list of Strings + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContains(@Nullable AnnotationValue avList, String s) { + if (avList == null) { + return false; + } + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueContains(list, s); + } + + /** + * Returns true if an AnnotationValue list contains the given value. + * + *

                  Using this method is slightly cheaper than creating a new {@code List} just for + * the purpose of testing containment within it. + * + * @param avList a list of Strings (as {@code AnnotationValue}s) + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContains( + List avList, String s) { + for (AnnotationValue av : avList) { + if (av.getValue().equals(s)) { + return true; + } + } + return false; + } + + /** + * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the + * given string. + * + *

                  Using this method is slightly cheaper than creating a new {@code List} just for the + * purpose of testing containment within it. + * + * @param avList an AnnotationValue that is null or a list + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContainsToString( + @Nullable AnnotationValue avList, String s) { + if (avList == null) { + return false; + } + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueContainsToString(list, s); + } + + /** + * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the + * given string. + * + *

                  Using this method is slightly cheaper than creating a new {@code List} just for the + * purpose of testing containment within it. + * + * @param avList a list of Strings (as {@code AnnotationValue}s) + * @param s a string + * @return true if {@code av} contains {@code s} + */ + public static boolean annotationValueContainsToString( + List avList, String s) { + for (AnnotationValue av : avList) { + if (av.getValue().toString().equals(s)) { + return true; + } + } + return false; + } + + /** + * Converts an annotation value to a list. + * + *

                  To test containment, use {@link #annotationValueContains(AnnotationValue, String)} or + * {@link #annotationValueContainsToString(AnnotationValue, String)}. + * + * @param avList an AnnotationValue that is a list of Strings + * @param expectedType the component type of the argument and of the return type, an enum + * @param the class of the type + * @return the annotation value, converted to a list + */ + public static List annotationValueToList(AnnotationValue avList, Class expectedType) { + @SuppressWarnings("unchecked") + List list = (List) avList.getValue(); + return annotationValueToList(list, expectedType); + } + + /** + * Converts an annotation value to a list. + * + *

                  To test containment, use {@link #annotationValueContains(List, String)} or {@link + * #annotationValueContainsToString(List, String)}. + * + * @param avList a list of Strings (as {@code AnnotationValue}s) + * @param expectedType the component type of the argument and of the return type, an enum + * @param the class of the type + * @return the annotation value, converted to a list + */ + public static List annotationValueToList( + List avList, Class expectedType) { + List result = new ArrayList<>(avList.size()); + for (AnnotationValue a : avList) { + try { + result.add(expectedType.cast(a.getValue())); + } catch (Throwable t) { + String err1 = String.format("annotationValueToList(%s, %s)", avList, expectedType); + String err2 = + String.format( + "a=%s [%s]%n a.getValue()=%s [%s]", + a, a.getClass(), a.getValue(), a.getValue().getClass()); + throw new BugInCF(err1 + " " + err2, t); } - notfirst = true; - sb.append(arg.getKey().getSimpleName() + "="); - formatAnnotationMirrorArg(arg.getValue(), sb); - } - } - } - sb.append(")"); - } - } - - /** - * Returns a new map that only has the values in {@code elementValues} that are not the same as - * the default value. - * - * @param elementValues a mapping of annotation element to annotation value - * @return a new map with only the non-default values of {@code elementValues} - */ - private static Map removeDefaultValues( - Map elementValues) { - // Most annotations have no elements. - Map nonDefaults = new ArrayMap<>(0); - elementValues.forEach( - (element, value) -> { - if (element.getDefaultValue() == null - || !Objects.equals(value.getValue(), element.getDefaultValue().getValue())) { - nonDefaults.put(element, value); - } - }); - return nonDefaults; - } - - /** - * A helper method to print AnnotationValues (annotation arguments), without showing full package - * names. - * - * @param av AnnotationValue to print - * @param sb StringBuilder to modify - */ - private static void formatAnnotationMirrorArg(AnnotationValue av, StringBuilder sb) { - Object val = av.getValue(); - if (List.class.isAssignableFrom(val.getClass())) { - @SuppressWarnings("unchecked") - List vallist = (List) val; - if (vallist.size() == 1) { - formatAnnotationMirrorArg(vallist.get(0), sb); - } else { - sb.append('{'); - boolean notfirst = false; - for (AnnotationValue nav : vallist) { - if (notfirst) { - sb.append(", "); - } - notfirst = true; - formatAnnotationMirrorArg(nav, sb); - } - sb.append('}'); - } - } else if (VariableElement.class.isAssignableFrom(val.getClass())) { - VariableElement ve = (VariableElement) val; - sb.append(ve.getEnclosingElement().getSimpleName() + "." + ve.getSimpleName()); - } else { - sb.append(av.toString()); - } - } - - /** - * Converts an AnnotationMirror to a Class. Throws an exception if it is not able to do so. - * - * @param am an AnnotationMirror - * @return the Class corresponding to the given AnnotationMirror - */ - public static Class annotationMirrorToClass(AnnotationMirror am) { - try { - return Class.forName(AnnotationUtils.annotationBinaryName(am)); - } catch (ClassNotFoundException e) { - throw new BugInCF(e); - } - } + } + return result; + } + + // ********************************************************************** + // Other methods + // ********************************************************************** + + // The Javadoc doesn't use @link because framework is a different project than this one + // (javacutil). + /** + * Update a map, to add {@code newQual} to the set that {@code key} maps to. The mapped-to + * element is an unmodifiable set. + * + *

                  See + * org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, + * Map, Object, AnnotationMirror). + * + * @param map the map to update + * @param key the key whose value to update + * @param newQual the element to add to the given key's value + * @param the key type + */ + public static void updateMappingToImmutableSet( + Map map, T key, AnnotationMirrorSet newQual) { + + AnnotationMirrorSet result = new AnnotationMirrorSet(); + // TODO: if T is also an AnnotationMirror, should we use areSame? + if (!map.containsKey(key)) { + result.addAll(newQual); + } else { + result.addAll(map.get(key)); + result.addAll(newQual); + } + result.makeUnmodifiable(); + map.put(key, result); + } + + /** + * Returns the annotations explicitly written on a constructor result. Callers should check that + * {@code constructorDeclaration} is in fact a declaration of a constructor. + * + * @param constructorDeclaration declaration tree of constructor + * @return set of annotations explicit on the resulting type of the constructor + */ + public static AnnotationMirrorSet getExplicitAnnotationsOnConstructorResult( + MethodTree constructorDeclaration) { + AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); + ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); + if (modifiersTree != null) { + List annotationTrees = modifiersTree.getAnnotations(); + annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); + } + return annotationSet; + } + + /** + * Returns true if {@code anno} is not a type use annotation, that is, it cannot be written on + * uses of types. + * + * @param anno the AnnotationMirror + * @return true if anno is a declaration annotation + * @deprecated use {@link #isTypeUseAnnotation(AnnotationMirror)} instead + */ + @Deprecated // 2024-01-06 + public static boolean isDeclarationAnnotation(AnnotationMirror anno) { + return !isTypeUseAnnotation(anno); + } + + /** + * Returns true if {@code anno} is a type use annotation, that is, it can be written on uses of + * types. + * + * @param anno the AnnotationMirror + * @return true if anno is a declaration annotation + */ + public static boolean isTypeUseAnnotation(AnnotationMirror anno) { + TypeElement elem = (TypeElement) anno.getAnnotationType().asElement(); + Target t = elem.getAnnotation(Target.class); + if (t == null) { + return false; + } + + for (ElementType elementType : t.value()) { + if (elementType == ElementType.TYPE_USE) { + return true; + } + } + return false; + } + + /** + * Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise. + * + * @param elements an array of {@link ElementType} values + * @param cls the annotation class being tested; used for diagnostic messages only + * @return true iff the give array contains {@link ElementType#TYPE_USE} + * @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and + * something besides {@link ElementType#TYPE_PARAMETER} + */ + public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class cls) { + // True if the array contains TYPE_USE + boolean hasTypeUse = false; + // Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER + ElementType otherElementType = null; + + for (ElementType element : elements) { + if (element == ElementType.TYPE_USE) { + hasTypeUse = true; + } else if (element != ElementType.TYPE_PARAMETER) { + otherElementType = element; + } + if (hasTypeUse && otherElementType != null) { + throw new BugInCF( + "@Target meta-annotation should not contain both TYPE_USE and " + + otherElementType + + ", for annotation " + + cls.getName()); + } + } + + return hasTypeUse; + } + + /** + * Returns a string representation of the annotation mirrors, using simple (not fully-qualified) + * names. + * + * @param annos annotations to format + * @return the string representation, using simple (not fully-qualified) names + */ + @SideEffectFree + public static String toStringSimple(AnnotationMirrorSet annos) { + StringJoiner result = new StringJoiner(" "); + for (AnnotationMirror am : annos) { + result.add(toStringSimple(am)); + } + return result.toString(); + } + + /** + * Returns a string representation of the annotation mirror, using simple (not fully-qualified) + * names. + * + * @param am annotation to format + * @return the string representation, using simple (not fully-qualified) names + */ + @SideEffectFree + public static String toStringSimple(AnnotationMirror am) { + StringBuilder sb = new StringBuilder(); + toStringSimple(am, sb); + return sb.toString(); + } + + /** + * Appends a string representation of the annotation mirror, using simple (not fully-qualified) + * names, to the StringBuilder. + * + * @param am annotation to format + * @param sb StringBuilder to which the string representation of am, using simple (not + * fully-qualified) names, is appended + */ + public static void toStringSimple(AnnotationMirror am, StringBuilder sb) { + sb.append("@"); + sb.append(am.getAnnotationType().asElement().getSimpleName()); + Map args = removeDefaultValues(am.getElementValues()); + if (!args.isEmpty()) { + sb.append("("); + boolean oneValue = false; + if (args.size() == 1) { + Map.Entry first = + args.entrySet().iterator().next(); + if (first.getKey().getSimpleName().contentEquals("value")) { + formatAnnotationMirrorArg(first.getValue(), sb); + oneValue = true; + } + } + if (!oneValue) { + boolean notfirst = false; + for (Map.Entry arg : args.entrySet()) { + if (!"{}".equals(arg.getValue().toString())) { + if (notfirst) { + sb.append(", "); + } + notfirst = true; + sb.append(arg.getKey().getSimpleName() + "="); + formatAnnotationMirrorArg(arg.getValue(), sb); + } + } + } + sb.append(")"); + } + } + + /** + * Returns a new map that only has the values in {@code elementValues} that are not the same as + * the default value. + * + * @param elementValues a mapping of annotation element to annotation value + * @return a new map with only the non-default values of {@code elementValues} + */ + private static Map removeDefaultValues( + Map elementValues) { + // Most annotations have no elements. + Map nonDefaults = new ArrayMap<>(0); + elementValues.forEach( + (element, value) -> { + if (element.getDefaultValue() == null + || !Objects.equals( + value.getValue(), element.getDefaultValue().getValue())) { + nonDefaults.put(element, value); + } + }); + return nonDefaults; + } + + /** + * A helper method to print AnnotationValues (annotation arguments), without showing full + * package names. + * + * @param av AnnotationValue to print + * @param sb StringBuilder to modify + */ + private static void formatAnnotationMirrorArg(AnnotationValue av, StringBuilder sb) { + Object val = av.getValue(); + if (List.class.isAssignableFrom(val.getClass())) { + @SuppressWarnings("unchecked") + List vallist = (List) val; + if (vallist.size() == 1) { + formatAnnotationMirrorArg(vallist.get(0), sb); + } else { + sb.append('{'); + boolean notfirst = false; + for (AnnotationValue nav : vallist) { + if (notfirst) { + sb.append(", "); + } + notfirst = true; + formatAnnotationMirrorArg(nav, sb); + } + sb.append('}'); + } + } else if (VariableElement.class.isAssignableFrom(val.getClass())) { + VariableElement ve = (VariableElement) val; + sb.append(ve.getEnclosingElement().getSimpleName() + "." + ve.getSimpleName()); + } else { + sb.append(av.toString()); + } + } + + /** + * Converts an AnnotationMirror to a Class. Throws an exception if it is not able to do so. + * + * @param am an AnnotationMirror + * @return the Class corresponding to the given AnnotationMirror + */ + public static Class annotationMirrorToClass(AnnotationMirror am) { + try { + return Class.forName(AnnotationUtils.annotationBinaryName(am)); + } catch (ClassNotFoundException e) { + throw new BugInCF(e); + } + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java b/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java index a160fd8a800..7893f6363cd 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/BasicAnnotationProvider.java @@ -1,86 +1,91 @@ package org.checkerframework.javacutil; import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; + import java.lang.annotation.Annotation; import java.util.List; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import org.checkerframework.checker.nullness.qual.Nullable; /** An AnnotationProvider that is independent of any type hierarchy. */ public class BasicAnnotationProvider implements AnnotationProvider { - /** - * Returns the AnnotationMirror, of the given class, used to annotate the element. Returns null if - * no such annotation exists. - */ - @Override - public @Nullable AnnotationMirror getDeclAnnotation( - Element elt, Class anno) { - List annotationMirrors = elt.getAnnotationMirrors(); + /** + * Returns the AnnotationMirror, of the given class, used to annotate the element. Returns null + * if no such annotation exists. + */ + @Override + public @Nullable AnnotationMirror getDeclAnnotation( + Element elt, Class anno) { + List annotationMirrors = elt.getAnnotationMirrors(); + + for (AnnotationMirror am : annotationMirrors) { + @SuppressWarnings("deprecation") // method intended for use by the hierarchy + boolean found = AnnotationUtils.areSameByClass(am, anno); + if (found) { + return am; + } + } - for (AnnotationMirror am : annotationMirrors) { - @SuppressWarnings("deprecation") // method intended for use by the hierarchy - boolean found = AnnotationUtils.areSameByClass(am, anno); - if (found) { - return am; - } + return null; } - return null; - } + /** + * {@inheritDoc} + * + *

                  This implementation always returns null, because it has no access to any type hierarchy. + */ + @Override + public @Nullable AnnotationMirror getAnnotationMirror( + Tree tree, Class target) { + return null; + } - /** - * {@inheritDoc} - * - *

                  This implementation always returns null, because it has no access to any type hierarchy. - */ - @Override - public @Nullable AnnotationMirror getAnnotationMirror( - Tree tree, Class target) { - return null; - } + /** + * {@inheritDoc} + * + *

                  This implementation returns true if the {@code @SideEffectFree} annotation is present on + * the given method. + */ + @Override + public boolean isSideEffectFree(ExecutableElement methodElement) { + List annotationMirrors = methodElement.getAnnotationMirrors(); - /** - * {@inheritDoc} - * - *

                  This implementation returns true if the {@code @SideEffectFree} annotation is present on the - * given method. - */ - @Override - public boolean isSideEffectFree(ExecutableElement methodElement) { - List annotationMirrors = methodElement.getAnnotationMirrors(); + for (AnnotationMirror am : annotationMirrors) { + boolean found = + AnnotationUtils.areSameByName( + am, "org.checkerframework.dataflow.qual.SideEffectFree"); + if (found) { + return true; + } + } - for (AnnotationMirror am : annotationMirrors) { - boolean found = - AnnotationUtils.areSameByName(am, "org.checkerframework.dataflow.qual.SideEffectFree"); - if (found) { - return true; - } + return false; } - return false; - } + /** + * {@inheritDoc} + * + *

                  This implementation returns true if the {@code @Deterministic} annotation is present on + * the given method. + */ + @Override + public boolean isDeterministic(ExecutableElement methodElement) { + List annotationMirrors = methodElement.getAnnotationMirrors(); - /** - * {@inheritDoc} - * - *

                  This implementation returns true if the {@code @Deterministic} annotation is present on the - * given method. - */ - @Override - public boolean isDeterministic(ExecutableElement methodElement) { - List annotationMirrors = methodElement.getAnnotationMirrors(); + for (AnnotationMirror am : annotationMirrors) { + boolean found = + AnnotationUtils.areSameByName( + am, "org.checkerframework.dataflow.qual.Deterministic"); + if (found) { + return true; + } + } - for (AnnotationMirror am : annotationMirrors) { - boolean found = - AnnotationUtils.areSameByName(am, "org.checkerframework.dataflow.qual.Deterministic"); - if (found) { - return true; - } + return false; } - - return false; - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java b/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java index f157b360f27..8467edbe5ef 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/BasicTypeProcessor.java @@ -3,41 +3,43 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; -import javax.lang.model.element.TypeElement; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import javax.lang.model.element.TypeElement; + /** * Process the types in an AST in a trivial manner, with hooks for derived classes to actually do * something. */ public abstract class BasicTypeProcessor extends AbstractTypeProcessor { - /** The source tree that's being scanned. */ - protected @MonotonicNonNull CompilationUnitTree currentRoot; + /** The source tree that's being scanned. */ + protected @MonotonicNonNull CompilationUnitTree currentRoot; - /** - * Create a TreePathScanner at the given root. - * - * @param root where to start the tree traversal - * @return a TreePathScanner at the given root - */ - protected abstract TreePathScanner createTreePathScanner(CompilationUnitTree root); + /** + * Create a TreePathScanner at the given root. + * + * @param root where to start the tree traversal + * @return a TreePathScanner at the given root + */ + protected abstract TreePathScanner createTreePathScanner(CompilationUnitTree root); - /** Visit the tree path for the type element. */ - @Override - public void typeProcess(TypeElement e, TreePath p) { - currentRoot = p.getCompilationUnit(); + /** Visit the tree path for the type element. */ + @Override + public void typeProcess(TypeElement e, TreePath p) { + currentRoot = p.getCompilationUnit(); - TreePathScanner scanner = null; - try { - scanner = createTreePathScanner(currentRoot); - scanner.scan(p, null); - } catch (Throwable t) { - System.err.println( - "BasicTypeProcessor.typeProcess: unexpected Throwable (" - + t.getClass().getSimpleName() - + ") when processing " - + currentRoot.getSourceFile().getName() - + (t.getMessage() != null ? "; message: " + t.getMessage() : "")); + TreePathScanner scanner = null; + try { + scanner = createTreePathScanner(currentRoot); + scanner.scan(p, null); + } catch (Throwable t) { + System.err.println( + "BasicTypeProcessor.typeProcess: unexpected Throwable (" + + t.getClass().getSimpleName() + + ") when processing " + + currentRoot.getSourceFile().getName() + + (t.getMessage() != null ? "; message: " + t.getMessage() : "")); + } } - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java b/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java index fb9daab8934..370201f683b 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/BugInCF.java @@ -12,64 +12,64 @@ @SuppressWarnings("serial") public class BugInCF extends RuntimeException { - /** - * Constructs a new BugInCF with the specified detail message and no cause (use this at the root - * cause). - * - * @param message the detail message - */ - public BugInCF(String message) { - this(message, new Throwable()); - } - - /** - * Constructs a new BugInCF with a detail message composed from the given arguments, and with no - * cause (use the current callstack as the root cause). - * - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public BugInCF(String fmt, @Nullable Object... args) { - this(String.format(fmt, args), new Throwable()); - } + /** + * Constructs a new BugInCF with the specified detail message and no cause (use this at the root + * cause). + * + * @param message the detail message + */ + public BugInCF(String message) { + this(message, new Throwable()); + } - /** - * Constructs a new BugInCF with the specified cause. - * - * @param cause the cause; its detail message will be used and must be non-null - */ - @SuppressWarnings("nullness") - public BugInCF(Throwable cause) { - this(cause.getMessage(), new Throwable()); - } + /** + * Constructs a new BugInCF with a detail message composed from the given arguments, and with no + * cause (use the current callstack as the root cause). + * + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public BugInCF(String fmt, @Nullable Object... args) { + this(String.format(fmt, args), new Throwable()); + } - /** - * Constructs a new BugInCF with the specified cause and with a detail message composed from the - * given arguments. - * - * @param cause the cause - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public BugInCF(Throwable cause, String fmt, @Nullable Object... args) { - this(String.format(fmt, args), cause); - } + /** + * Constructs a new BugInCF with the specified cause. + * + * @param cause the cause; its detail message will be used and must be non-null + */ + @SuppressWarnings("nullness") + public BugInCF(Throwable cause) { + this(cause.getMessage(), new Throwable()); + } - /** - * Constructs a new BugInCF with the specified detail message and cause. - * - * @param message the detail message - * @param cause the cause - */ - public BugInCF(String message, Throwable cause) { - super(message, cause); - if (message == null) { - throw new BugInCF("Must have a detail message."); + /** + * Constructs a new BugInCF with the specified cause and with a detail message composed from the + * given arguments. + * + * @param cause the cause + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public BugInCF(Throwable cause, String fmt, @Nullable Object... args) { + this(String.format(fmt, args), cause); } - if (cause == null) { - throw new BugInCF("Must have a cause throwable."); + + /** + * Constructs a new BugInCF with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause + */ + public BugInCF(String message, Throwable cause) { + super(message, cause); + if (message == null) { + throw new BugInCF("Must have a detail message."); + } + if (cause == null) { + throw new BugInCF("Must have a cause throwable."); + } } - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java index fd07e7f1240..f1783877b1f 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/CollectionUtils.java @@ -1,222 +1,225 @@ package org.checkerframework.javacutil; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.plumelib.util.DeepCopyable; import org.plumelib.util.UtilPlume; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + /** Utility methods related to Java Collections. */ public class CollectionUtils { - /** Do not instantiate. */ - private CollectionUtils() { - throw new Error("Do not instantiate"); - } - - /// - /// Deprecated utility methods - /// - - /** - * Creates a LRU cache. - * - * @param size size of the cache - * @return a new cache with the provided size - * @deprecated use org.plumelib.util.CollectionsPlume.createLruCache - */ - @Deprecated // 2023-06-02 - public static Map createLRUCache(int size) { - return new LinkedHashMap(size, .75F, true) { - - private static final long serialVersionUID = 5261489276168775084L; - - @Override - protected boolean removeEldestEntry(Map.Entry entry) { - return size() > size; - } - }; - } - - // A "deep copy" uses the deepCopy() method of the DeepCopyable interface. - - /** - * Returns a copy of {@code orig}, where each element of the result is a clone of the - * corresponding element of {@code orig}. - * - * @param the type of elements of the collection - * @param the type of the collection - * @param orig a collection - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements - */ - @SuppressWarnings({ - "signedness", // problem with clone() - "nullness" // generics problem - }) - @Deprecated // 2023-06-02 - public static > - @PolyNull C cloneElements(@PolyNull C orig) { - if (orig == null) { - return null; - } - C result = UtilPlume.clone(orig); - result.clear(); - for (T elt : orig) { - result.add(UtilPlume.clone(elt)); + /** Do not instantiate. */ + private CollectionUtils() { + throw new Error("Do not instantiate"); } - return result; - } - - /** - * Returns a copy of {@code orig}, where each key and value in the result is a clone of the - * corresponding element of {@code orig}. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static > @PolyNull M cloneElements(@PolyNull M orig) { - return cloneElements(orig, true); - } - - /** - * Returns a copy of {@code orig}, where each value of the result is a clone of the corresponding - * value of {@code orig}, but the keys are the same objects. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneValues - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static > @PolyNull M cloneValues(@PolyNull M orig) { - return cloneElements(orig, false); - } - - /** - * Returns a copy of {@code orig}, where each key and value in the result is a clone of the - * corresponding element of {@code orig}. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @param cloneKeys if true, clone keys; otherwise, re-use them - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - private static > @PolyNull M cloneElements( - @PolyNull M orig, boolean cloneKeys) { - if (orig == null) { - return null; + + /// + /// Deprecated utility methods + /// + + /** + * Creates a LRU cache. + * + * @param size size of the cache + * @return a new cache with the provided size + * @deprecated use org.plumelib.util.CollectionsPlume.createLruCache + */ + @Deprecated // 2023-06-02 + public static Map createLRUCache(int size) { + return new LinkedHashMap(size, .75F, true) { + + private static final long serialVersionUID = 5261489276168775084L; + + @Override + protected boolean removeEldestEntry(Map.Entry entry) { + return size() > size; + } + }; } - M result = UtilPlume.clone(orig); - result.clear(); - for (Map.Entry mapEntry : orig.entrySet()) { - K oldKey = mapEntry.getKey(); - K newKey = cloneKeys ? UtilPlume.clone(oldKey) : oldKey; - result.put(newKey, UtilPlume.clone(mapEntry.getValue())); + + // A "deep copy" uses the deepCopy() method of the DeepCopyable interface. + + /** + * Returns a copy of {@code orig}, where each element of the result is a clone of the + * corresponding element of {@code orig}. + * + * @param the type of elements of the collection + * @param the type of the collection + * @param orig a collection + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements + */ + @SuppressWarnings({ + "signedness", // problem with clone() + "nullness" // generics problem + }) + @Deprecated // 2023-06-02 + public static > + @PolyNull C cloneElements(@PolyNull C orig) { + if (orig == null) { + return null; + } + C result = UtilPlume.clone(orig); + result.clear(); + for (T elt : orig) { + result.add(UtilPlume.clone(elt)); + } + return result; } - return result; - } - - /** - * Returns a copy of {@code orig}, where each element of the result is a deep copy (according to - * the {@code DeepCopyable} interface) of the corresponding element of {@code orig}. - * - * @param the type of elements of the collection - * @param the type of the collection - * @param orig a collection - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"signedness", "nullness:argument"}) // problem with clone() - public static , C extends @Nullable Collection> - @PolyNull C deepCopy(@PolyNull C orig) { - if (orig == null) { - return null; + + /** + * Returns a copy of {@code orig}, where each key and value in the result is a clone of the + * corresponding element of {@code orig}. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static > @PolyNull M cloneElements( + @PolyNull M orig) { + return cloneElements(orig, true); } - C result = UtilPlume.clone(orig); - result.clear(); - for (T elt : orig) { - result.add(DeepCopyable.deepCopyOrNull(elt)); + + /** + * Returns a copy of {@code orig}, where each value of the result is a clone of the + * corresponding value of {@code orig}, but the keys are the same objects. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneValues + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static > @PolyNull M cloneValues(@PolyNull M orig) { + return cloneElements(orig, false); } - return result; - } - - // The following two methods cannot share an implementation because their generic bounds differ. - - /** - * Returns a copy of {@code orig}, where each key and value in the result is a deep copy - * (according to the {@code DeepCopyable} interface) of the corresponding element of {@code orig}. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static < - K extends @Nullable DeepCopyable, - V extends @Nullable DeepCopyable, - M extends @Nullable Map> - @PolyNull M deepCopy(@PolyNull M orig) { - if (orig == null) { - return null; + + /** + * Returns a copy of {@code orig}, where each key and value in the result is a clone of the + * corresponding element of {@code orig}. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @param cloneKeys if true, clone keys; otherwise, re-use them + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.cloneElements + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + private static > @PolyNull M cloneElements( + @PolyNull M orig, boolean cloneKeys) { + if (orig == null) { + return null; + } + M result = UtilPlume.clone(orig); + result.clear(); + for (Map.Entry mapEntry : orig.entrySet()) { + K oldKey = mapEntry.getKey(); + K newKey = cloneKeys ? UtilPlume.clone(oldKey) : oldKey; + result.put(newKey, UtilPlume.clone(mapEntry.getValue())); + } + return result; } - M result = UtilPlume.clone(orig); - result.clear(); - for (Map.Entry mapEntry : orig.entrySet()) { - K oldKey = mapEntry.getKey(); - V oldValue = mapEntry.getValue(); - result.put(DeepCopyable.deepCopyOrNull(oldKey), DeepCopyable.deepCopyOrNull(oldValue)); + + /** + * Returns a copy of {@code orig}, where each element of the result is a deep copy (according to + * the {@code DeepCopyable} interface) of the corresponding element of {@code orig}. + * + * @param the type of elements of the collection + * @param the type of the collection + * @param orig a collection + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"signedness", "nullness:argument"}) // problem with clone() + public static , C extends @Nullable Collection> + @PolyNull C deepCopy(@PolyNull C orig) { + if (orig == null) { + return null; + } + C result = UtilPlume.clone(orig); + result.clear(); + for (T elt : orig) { + result.add(DeepCopyable.deepCopyOrNull(elt)); + } + return result; } - return result; - } - - /** - * Returns a copy of {@code orig}, where each value of the result is a deep copy (according to the - * {@code DeepCopyable} interface) of the corresponding value of {@code orig}, but the keys are - * the same objects. - * - * @param the type of keys of the map - * @param the type of values of the map - * @param the type of the map - * @param orig a map - * @return a copy of {@code orig}, as described above - * @deprecated use org.plumelib.util.CollectionsPlume.deepCopyValues - */ - @Deprecated // 2023-06-02 - @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone - public static , M extends @Nullable Map> - @PolyNull M deepCopyValues(@PolyNull M orig) { - if (orig == null) { - return null; + + // The following two methods cannot share an implementation because their generic bounds differ. + + /** + * Returns a copy of {@code orig}, where each key and value in the result is a deep copy + * (according to the {@code DeepCopyable} interface) of the corresponding element of {@code + * orig}. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.deepCopy + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static < + K extends @Nullable DeepCopyable, + V extends @Nullable DeepCopyable, + M extends @Nullable Map> + @PolyNull M deepCopy(@PolyNull M orig) { + if (orig == null) { + return null; + } + M result = UtilPlume.clone(orig); + result.clear(); + for (Map.Entry mapEntry : orig.entrySet()) { + K oldKey = mapEntry.getKey(); + V oldValue = mapEntry.getValue(); + result.put(DeepCopyable.deepCopyOrNull(oldKey), DeepCopyable.deepCopyOrNull(oldValue)); + } + return result; } - M result = UtilPlume.clone(orig); - result.clear(); - for (Map.Entry mapEntry : orig.entrySet()) { - K oldKey = mapEntry.getKey(); - V oldValue = mapEntry.getValue(); - result.put(oldKey, DeepCopyable.deepCopyOrNull(oldValue)); + + /** + * Returns a copy of {@code orig}, where each value of the result is a deep copy (according to + * the {@code DeepCopyable} interface) of the corresponding value of {@code orig}, but the keys + * are the same objects. + * + * @param the type of keys of the map + * @param the type of values of the map + * @param the type of the map + * @param orig a map + * @return a copy of {@code orig}, as described above + * @deprecated use org.plumelib.util.CollectionsPlume.deepCopyValues + */ + @Deprecated // 2023-06-02 + @SuppressWarnings({"nullness", "signedness"}) // generics problem with clone + public static , M extends @Nullable Map> + @PolyNull M deepCopyValues(@PolyNull M orig) { + if (orig == null) { + return null; + } + M result = UtilPlume.clone(orig); + result.clear(); + for (Map.Entry mapEntry : orig.entrySet()) { + K oldKey = mapEntry.getKey(); + V oldValue = mapEntry.getValue(); + result.put(oldKey, DeepCopyable.deepCopyOrNull(oldValue)); + } + return result; } - return result; - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java b/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java index c892c67a11f..affdebdad2e 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/DeepCopyable.java @@ -12,29 +12,30 @@ @Deprecated // 2023-06-02 public interface DeepCopyable { - /** - * Returns a deep copy of this. A deep copy is equal to the original, but side effects to either - * object are not visible in the other. A deep copy may share immutable state with the original. - * - *

                  The run-time class of the result is identical to the run-time class of this. The deep copy - * is equal to {@code this} (per {@code equals()} if the object's class does not use reference - * equality as {@code Object.equals()} does). - * - * @return a deep copy of this - */ - T deepCopy(); + /** + * Returns a deep copy of this. A deep copy is equal to the original, but side effects to either + * object are not visible in the other. A deep copy may share immutable state with the original. + * + *

                  The run-time class of the result is identical to the run-time class of this. The deep copy + * is equal to {@code this} (per {@code equals()} if the object's class does not use reference + * equality as {@code Object.equals()} does). + * + * @return a deep copy of this + */ + T deepCopy(); - /** - * Returns the deep copy of a non-null argument, or {@code null} for a {@code null} argument. - * - * @param object object to copy - * @return the deep copy of a non-null argument, or {@code null} for a {@code null} argument - * @param the type of the object - */ - static > @PolyNull T2 deepCopyOrNull(@PolyNull T2 object) { - if (object == null) { - return null; + /** + * Returns the deep copy of a non-null argument, or {@code null} for a {@code null} argument. + * + * @param object object to copy + * @return the deep copy of a non-null argument, or {@code null} for a {@code null} argument + * @param the type of the object + */ + static > @PolyNull T2 deepCopyOrNull( + @PolyNull T2 object) { + if (object == null) { + return null; + } + return object.deepCopy(); } - return object.deepCopy(); - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java index ffcd027bb94..831fa8ac721 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java @@ -8,6 +8,14 @@ import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.CanonicalName; +import org.plumelib.util.ArraySet; +import org.plumelib.util.CollectionsPlume; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayDeque; @@ -20,6 +28,7 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -37,12 +46,6 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.JavaFileObject; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.CanonicalName; -import org.plumelib.util.ArraySet; -import org.plumelib.util.CollectionsPlume; /** * Utility methods for analyzing {@code Element}s. This complements {@link Elements}, providing @@ -50,1120 +53,1133 @@ */ public class ElementUtils { - // Class cannot be instantiated. - private ElementUtils() { - throw new AssertionError("Class ElementUtils cannot be instantiated."); - } - - /** The value of Flags.COMPACT_RECORD_CONSTRUCTOR which does not exist in Java 9 or 11. */ - private static final long Flags_COMPACT_RECORD_CONSTRUCTOR = 1L << 51; - - /** The value of Flags.GENERATED_MEMBER which does not exist in Java 9 or 11. */ - private static final long Flags_GENERATED_MEMBER = 16777216; - - /** - * Returns the innermost type element that is, or encloses, the given element. - * - *

                  Note that in this code: - * - *

                  {@code
                  -   * class Outer {
                  -   *   static class Inner {  }
                  -   * }
                  -   * }
                  - * - * {@code Inner} has no enclosing type, but this method returns {@code Outer}. - * - * @param elem the enclosed element of a class - * @return the innermost type element (possibly the argument itself), or null if {@code elem} is - * not, and is not enclosed by, a type element - */ - public static @Nullable TypeElement enclosingTypeElement(Element elem) { - Element result = elem; - while (result != null && !isTypeElement(result)) { - result = result.getEnclosingElement(); + // Class cannot be instantiated. + private ElementUtils() { + throw new AssertionError("Class ElementUtils cannot be instantiated."); } - return (TypeElement) result; - } - - /** - * Returns the innermost type element enclosing the given element, that is different from the - * element itself. By contrast, {@link #enclosingTypeElement} returns its argument if the argument - * is a type element. - * - * @param elem the enclosed element of a class - * @return the innermost type element, or null if no type element encloses {@code elem} - */ - public static @Nullable TypeElement strictEnclosingTypeElement(Element elem) { - Element enclosingElement = elem.getEnclosingElement(); - if (enclosingElement == null) { - return null; + + /** The value of Flags.COMPACT_RECORD_CONSTRUCTOR which does not exist in Java 9 or 11. */ + private static final long Flags_COMPACT_RECORD_CONSTRUCTOR = 1L << 51; + + /** The value of Flags.GENERATED_MEMBER which does not exist in Java 9 or 11. */ + private static final long Flags_GENERATED_MEMBER = 16777216; + + /** + * Returns the innermost type element that is, or encloses, the given element. + * + *

                  Note that in this code: + * + *

                  {@code
                  +     * class Outer {
                  +     *   static class Inner {  }
                  +     * }
                  +     * }
                  + * + * {@code Inner} has no enclosing type, but this method returns {@code Outer}. + * + * @param elem the enclosed element of a class + * @return the innermost type element (possibly the argument itself), or null if {@code elem} is + * not, and is not enclosed by, a type element + */ + public static @Nullable TypeElement enclosingTypeElement(Element elem) { + Element result = elem; + while (result != null && !isTypeElement(result)) { + result = result.getEnclosingElement(); + } + return (TypeElement) result; } - return enclosingTypeElement(enclosingElement); - } - - /** - * Returns the top-level type element that contains {@code element}. - * - * @param element the element whose enclosing tye element to find - * @return a type element containing {@code element} that isn't contained in another class - */ - public static TypeElement toplevelEnclosingTypeElement(Element element) { - TypeElement result = enclosingTypeElement(element); - if (result == null) { - return (TypeElement) element; + /** + * Returns the innermost type element enclosing the given element, that is different from the + * element itself. By contrast, {@link #enclosingTypeElement} returns its argument if the + * argument is a type element. + * + * @param elem the enclosed element of a class + * @return the innermost type element, or null if no type element encloses {@code elem} + */ + public static @Nullable TypeElement strictEnclosingTypeElement(Element elem) { + Element enclosingElement = elem.getEnclosingElement(); + if (enclosingElement == null) { + return null; + } + + return enclosingTypeElement(enclosingElement); } - TypeElement enclosing = strictEnclosingTypeElement(result); - while (enclosing != null) { - result = enclosing; - enclosing = strictEnclosingTypeElement(enclosing); + /** + * Returns the top-level type element that contains {@code element}. + * + * @param element the element whose enclosing tye element to find + * @return a type element containing {@code element} that isn't contained in another class + */ + public static TypeElement toplevelEnclosingTypeElement(Element element) { + TypeElement result = enclosingTypeElement(element); + if (result == null) { + return (TypeElement) element; + } + + TypeElement enclosing = strictEnclosingTypeElement(result); + while (enclosing != null) { + result = enclosing; + enclosing = strictEnclosingTypeElement(enclosing); + } + + return result; } - return result; - } - - /** - * Returns the binary name of the class enclosing {@code executableElement}. - * - * @param executableElement the ExecutableElement - * @return the binary name of the class enclosing {@code executableElement} - */ - public static @BinaryName String getEnclosingClassName(ExecutableElement executableElement) { - return getBinaryName(((MethodSymbol) executableElement).enclClass()); - } - - /** - * Returns the binary name of the class enclosing {@code variableElement}. - * - * @param variableElement the VariableElement - * @return the binary name of the class enclosing {@code variableElement} - */ - public static @BinaryName String getEnclosingClassName(VariableElement variableElement) { - TypeElement enclosingType = enclosingTypeElement(variableElement); - if (enclosingType == null) { - throw new BugInCF("enclosingTypeElement(%s) is null", variableElement); + /** + * Returns the binary name of the class enclosing {@code executableElement}. + * + * @param executableElement the ExecutableElement + * @return the binary name of the class enclosing {@code executableElement} + */ + public static @BinaryName String getEnclosingClassName(ExecutableElement executableElement) { + return getBinaryName(((MethodSymbol) executableElement).enclClass()); } - return getBinaryName(enclosingType); - } - - /** - * Returns the innermost package element enclosing the given element. The same effect as {@link - * javax.lang.model.util.Elements#getPackageOf(Element)}. Returns the element itself if it is a - * package. - * - * @param elem the enclosed element of a package - * @return the innermost package element - */ - public static PackageElement enclosingPackage(Element elem) { - Element result = elem; - while (result != null && result.getKind() != ElementKind.PACKAGE) { - result = result.getEnclosingElement(); + + /** + * Returns the binary name of the class enclosing {@code variableElement}. + * + * @param variableElement the VariableElement + * @return the binary name of the class enclosing {@code variableElement} + */ + public static @BinaryName String getEnclosingClassName(VariableElement variableElement) { + TypeElement enclosingType = enclosingTypeElement(variableElement); + if (enclosingType == null) { + throw new BugInCF("enclosingTypeElement(%s) is null", variableElement); + } + return getBinaryName(enclosingType); } - return (PackageElement) result; - } - - /** - * Returns the "parent" package element for the given package element. For package "A.B" it gives - * "A". For package "A" it gives the default package. For the default package it returns null. - * - *

                  Note that packages are not enclosed within each other, we have to manually climb the - * namespaces. Calling "enclosingPackage" on a package element returns the package element itself - * again. - * - * @param elem the package to start from - * @param elements the element - * @return the parent package element or {@code null} - */ - public static @Nullable PackageElement parentPackage(PackageElement elem, Elements elements) { - // The following might do the same thing: - // ((Symbol) elt).owner; - // TODO: verify and see whether the change is worth it. - String fqnstart = elem.getQualifiedName().toString(); - String fqn = fqnstart; - if (fqn != null && !fqn.isEmpty()) { - int dotPos = fqn.lastIndexOf('.'); - if (dotPos != -1) { - return elements.getPackageElement(fqn.substring(0, dotPos)); - } + + /** + * Returns the innermost package element enclosing the given element. The same effect as {@link + * javax.lang.model.util.Elements#getPackageOf(Element)}. Returns the element itself if it is a + * package. + * + * @param elem the enclosed element of a package + * @return the innermost package element + */ + public static PackageElement enclosingPackage(Element elem) { + Element result = elem; + while (result != null && result.getKind() != ElementKind.PACKAGE) { + result = result.getEnclosingElement(); + } + return (PackageElement) result; } - return null; - } - - /** - * Returns true if the element is a static element: whether it is a static field, static method, - * or static class. - * - * @return true if element is static - */ - public static boolean isStatic(Element element) { - return element.getModifiers().contains(Modifier.STATIC); - } - - /** - * Returns true if the element is a final element: a final field, final method, or final class. - * - * @return true if the element is final - */ - public static boolean isFinal(Element element) { - return element.getModifiers().contains(Modifier.FINAL); - } - - /** - * Returns true if the element is a effectively final element. - * - * @return true if the element is effectively final - */ - public static boolean isEffectivelyFinal(Element element) { - Symbol sym = (Symbol) element; - if (sym.getEnclosingElement().getKind() == ElementKind.METHOD - && (sym.getEnclosingElement().flags() & Flags.ABSTRACT) != 0) { - return true; + + /** + * Returns the "parent" package element for the given package element. For package "A.B" it + * gives "A". For package "A" it gives the default package. For the default package it returns + * null. + * + *

                  Note that packages are not enclosed within each other, we have to manually climb the + * namespaces. Calling "enclosingPackage" on a package element returns the package element + * itself again. + * + * @param elem the package to start from + * @param elements the element + * @return the parent package element or {@code null} + */ + public static @Nullable PackageElement parentPackage(PackageElement elem, Elements elements) { + // The following might do the same thing: + // ((Symbol) elt).owner; + // TODO: verify and see whether the change is worth it. + String fqnstart = elem.getQualifiedName().toString(); + String fqn = fqnstart; + if (fqn != null && !fqn.isEmpty()) { + int dotPos = fqn.lastIndexOf('.'); + if (dotPos != -1) { + return elements.getPackageElement(fqn.substring(0, dotPos)); + } + } + return null; } - return (sym.flags() & (Flags.FINAL | Flags.EFFECTIVELY_FINAL)) != 0; - } - - /** - * Returns the {@code TypeMirror} for usage of Element as a value. It returns the return type of a - * method element, the class type of a constructor, or simply the type mirror of the element - * itself. - * - * @param element the element whose type to obtain - * @return the type for the element used as a value - */ - @SuppressWarnings("nullness:dereference.of.nullable") // a constructor has an enclosing class - public static TypeMirror getType(Element element) { - if (element.getKind() == ElementKind.METHOD) { - return ((ExecutableElement) element).getReturnType(); - } else if (element.getKind() == ElementKind.CONSTRUCTOR) { - return enclosingTypeElement(element).asType(); - } else { - return element.asType(); + + /** + * Returns true if the element is a static element: whether it is a static field, static method, + * or static class. + * + * @return true if element is static + */ + public static boolean isStatic(Element element) { + return element.getModifiers().contains(Modifier.STATIC); } - } - - /** - * Returns the qualified name of the innermost class enclosing the provided {@code Element}. - * - * @param element an element enclosed by a class, or a {@code TypeElement} - * @return the qualified {@code Name} of the innermost class enclosing the element - */ - public static @Nullable Name getQualifiedClassName(Element element) { - if (element.getKind() == ElementKind.PACKAGE) { - PackageElement elem = (PackageElement) element; - return elem.getQualifiedName(); + + /** + * Returns true if the element is a final element: a final field, final method, or final class. + * + * @return true if the element is final + */ + public static boolean isFinal(Element element) { + return element.getModifiers().contains(Modifier.FINAL); } - TypeElement elem = enclosingTypeElement(element); - if (elem == null) { - return null; + /** + * Returns true if the element is a effectively final element. + * + * @return true if the element is effectively final + */ + public static boolean isEffectivelyFinal(Element element) { + Symbol sym = (Symbol) element; + if (sym.getEnclosingElement().getKind() == ElementKind.METHOD + && (sym.getEnclosingElement().flags() & Flags.ABSTRACT) != 0) { + return true; + } + return (sym.flags() & (Flags.FINAL | Flags.EFFECTIVELY_FINAL)) != 0; } - return elem.getQualifiedName(); - } - - /** - * Returns a verbose name that identifies the element. - * - * @param elt the element whose name to obtain - * @return the qualified name of the given element - */ - public static String getQualifiedName(Element elt) { - if (elt.getKind() == ElementKind.PACKAGE || isTypeElement(elt)) { - Name n = getQualifiedClassName(elt); - if (n == null) { - return "Unexpected element: " + elt; - } - return n.toString(); - } else { - return getQualifiedName(elt.getEnclosingElement()) + "." + elt; + /** + * Returns the {@code TypeMirror} for usage of Element as a value. It returns the return type of + * a method element, the class type of a constructor, or simply the type mirror of the element + * itself. + * + * @param element the element whose type to obtain + * @return the type for the element used as a value + */ + @SuppressWarnings("nullness:dereference.of.nullable") // a constructor has an enclosing class + public static TypeMirror getType(Element element) { + if (element.getKind() == ElementKind.METHOD) { + return ((ExecutableElement) element).getReturnType(); + } else if (element.getKind() == ElementKind.CONSTRUCTOR) { + return enclosingTypeElement(element).asType(); + } else { + return element.asType(); + } } - } - - /** - * Returns the binary name of the given type. - * - * @param te a type - * @return the binary name of the type - */ - @SuppressWarnings("signature:return.type.incompatible") // string manipulation - public static @BinaryName String getBinaryName(TypeElement te) { - Element enclosing = te.getEnclosingElement(); - String simpleName = te.getSimpleName().toString(); - if (enclosing == null) { // is this possible? - return simpleName; + + /** + * Returns the qualified name of the innermost class enclosing the provided {@code Element}. + * + * @param element an element enclosed by a class, or a {@code TypeElement} + * @return the qualified {@code Name} of the innermost class enclosing the element + */ + public static @Nullable Name getQualifiedClassName(Element element) { + if (element.getKind() == ElementKind.PACKAGE) { + PackageElement elem = (PackageElement) element; + return elem.getQualifiedName(); + } + + TypeElement elem = enclosingTypeElement(element); + if (elem == null) { + return null; + } + + return elem.getQualifiedName(); } - if (ElementUtils.isTypeElement(enclosing)) { - return getBinaryName((TypeElement) enclosing) + "$" + simpleName; - } else if (enclosing.getKind() == ElementKind.PACKAGE) { - PackageElement pe = (PackageElement) enclosing; - if (pe.isUnnamed()) { - return simpleName; - } else { - return pe.getQualifiedName() + "." + simpleName; - } - } else { - // This case occurs for anonymous inner classes. Fall back to the flatname method. - return ((ClassSymbol) te).flatName().toString(); + + /** + * Returns a verbose name that identifies the element. + * + * @param elt the element whose name to obtain + * @return the qualified name of the given element + */ + public static String getQualifiedName(Element elt) { + if (elt.getKind() == ElementKind.PACKAGE || isTypeElement(elt)) { + Name n = getQualifiedClassName(elt); + if (n == null) { + return "Unexpected element: " + elt; + } + return n.toString(); + } else { + return getQualifiedName(elt.getEnclosingElement()) + "." + elt; + } } - } - - /** - * Returns the canonical representation of the method declaration, which contains simple names of - * the types only. - * - * @param element a method declaration - * @return the simple name of the method, followed by the simple names of the formal parameter - * types - */ - public static String getSimpleSignature(ExecutableElement element) { - // note: constructor simple name is - StringJoiner sj = new StringJoiner(",", element.getSimpleName() + "(", ")"); - for (Iterator i = element.getParameters().iterator(); - i.hasNext(); ) { - sj.add(TypesUtils.simpleTypeName(i.next().asType())); + + /** + * Returns the binary name of the given type. + * + * @param te a type + * @return the binary name of the type + */ + @SuppressWarnings("signature:return.type.incompatible") // string manipulation + public static @BinaryName String getBinaryName(TypeElement te) { + Element enclosing = te.getEnclosingElement(); + String simpleName = te.getSimpleName().toString(); + if (enclosing == null) { // is this possible? + return simpleName; + } + if (ElementUtils.isTypeElement(enclosing)) { + return getBinaryName((TypeElement) enclosing) + "$" + simpleName; + } else if (enclosing.getKind() == ElementKind.PACKAGE) { + PackageElement pe = (PackageElement) enclosing; + if (pe.isUnnamed()) { + return simpleName; + } else { + return pe.getQualifiedName() + "." + simpleName; + } + } else { + // This case occurs for anonymous inner classes. Fall back to the flatname method. + return ((ClassSymbol) te).flatName().toString(); + } } - return sj.toString(); - } - - /** - * Returns a user-friendly name for the given method. Does not return {@code ""} or {@code - * ""} as ExecutableElement.getSimpleName() does. - * - * @param element a method declaration - * @return a user-friendly name for the method - * @deprecated use {@link #getSimpleDescription} - */ - @Deprecated // 2023-06-01 - public static CharSequence getSimpleNameOrDescription(ExecutableElement element) { - Name result = element.getSimpleName(); - switch (result.toString()) { - case "": - return element.getEnclosingElement().getSimpleName(); - case "": - return "class initializer"; - default: - return result; + + /** + * Returns the canonical representation of the method declaration, which contains simple names + * of the types only. + * + * @param element a method declaration + * @return the simple name of the method, followed by the simple names of the formal parameter + * types + */ + public static String getSimpleSignature(ExecutableElement element) { + // note: constructor simple name is + StringJoiner sj = new StringJoiner(",", element.getSimpleName() + "(", ")"); + for (Iterator i = element.getParameters().iterator(); + i.hasNext(); ) { + sj.add(TypesUtils.simpleTypeName(i.next().asType())); + } + return sj.toString(); } - } - - /** - * Returns a user-friendly name for the given method, which includes the name of the enclosing - * type. Does not return {@code ""} or {@code ""} as - * ExecutableElement.getSimpleName() does. - * - * @param element a method declaration - * @return a user-friendly name for the method - */ - public static CharSequence getSimpleDescription(ExecutableElement element) { - String enclosingTypeName = - ((TypeElement) element.getEnclosingElement()).getSimpleName().toString(); - Name methodName = element.getSimpleName(); - switch (methodName.toString()) { - case "": - return enclosingTypeName + " constructor"; - case "": - return "class initializer for " + enclosingTypeName; - default: - return enclosingTypeName + "." + methodName; + + /** + * Returns a user-friendly name for the given method. Does not return {@code ""} or {@code + * ""} as ExecutableElement.getSimpleName() does. + * + * @param element a method declaration + * @return a user-friendly name for the method + * @deprecated use {@link #getSimpleDescription} + */ + @Deprecated // 2023-06-01 + public static CharSequence getSimpleNameOrDescription(ExecutableElement element) { + Name result = element.getSimpleName(); + switch (result.toString()) { + case "": + return element.getEnclosingElement().getSimpleName(); + case "": + return "class initializer"; + default: + return result; + } } - } - - /** - * Check if the element is an element for 'java.lang.Object' - * - * @param element the type element - * @return true iff the element is java.lang.Object element - */ - public static boolean isObject(TypeElement element) { - return element.getQualifiedName().contentEquals("java.lang.Object"); - } - - /** - * Check if the element is an element for 'java.lang.String' - * - * @param element the type element - * @return true iff the element is java.lang.String element - */ - public static boolean isString(TypeElement element) { - return element.getQualifiedName().contentEquals("java.lang.String"); - } - - /** - * Returns true if the element is a reference to a compile-time constant. - * - * @param elt an element - * @return true if the element is a reference to a compile-time constant - */ - public static boolean isCompileTimeConstant(@Nullable Element elt) { - return elt != null - && (elt.getKind() == ElementKind.FIELD || elt.getKind() == ElementKind.LOCAL_VARIABLE) - && ((VariableElement) elt).getConstantValue() != null; - } - - /** - * Checks whether a given element came from a source file. - * - *

                  By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a - * classfile for the given element, even if there is also a source file. - * - * @param element the element to check, or null - * @return true if a source file containing the element is being compiled - */ - public static boolean isElementFromSourceCode(@Nullable Element element) { - if (element == null) { - return false; + + /** + * Returns a user-friendly name for the given method, which includes the name of the enclosing + * type. Does not return {@code ""} or {@code ""} as + * ExecutableElement.getSimpleName() does. + * + * @param element a method declaration + * @return a user-friendly name for the method + */ + public static CharSequence getSimpleDescription(ExecutableElement element) { + String enclosingTypeName = + ((TypeElement) element.getEnclosingElement()).getSimpleName().toString(); + Name methodName = element.getSimpleName(); + switch (methodName.toString()) { + case "": + return enclosingTypeName + " constructor"; + case "": + return "class initializer for " + enclosingTypeName; + default: + return enclosingTypeName + "." + methodName; + } } - TypeElement enclosingTypeElement = enclosingTypeElement(element); - if (enclosingTypeElement == null) { - throw new BugInCF("enclosingTypeElement(%s) is null", element); + + /** + * Check if the element is an element for 'java.lang.Object' + * + * @param element the type element + * @return true iff the element is java.lang.Object element + */ + public static boolean isObject(TypeElement element) { + return element.getQualifiedName().contentEquals("java.lang.Object"); } - return isElementFromSourceCodeImpl((Symbol.ClassSymbol) enclosingTypeElement); - } - - /** - * Checks whether a given ClassSymbol came from a source file. - * - *

                  By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a - * classfile for the given element, even if there is also a source file. - * - * @param symbol the class to check - * @return true if a source file containing the class is being compiled - */ - private static boolean isElementFromSourceCodeImpl(Symbol.ClassSymbol symbol) { - // This is a bit of a hack to avoid treating JDK as source files. JDK files' toUri() method - // returns just the name of the file (e.g. "Object.java"), but any file actually being - // compiled returns a file URI to the source file. - return symbol.sourcefile != null - && symbol.sourcefile.getKind() == JavaFileObject.Kind.SOURCE - && symbol.sourcefile.toUri().toString().startsWith("file:"); - } - - /** - * Returns true if the element is declared in ByteCode. Always return false if elt is a package. - * - * @param elt some element - * @return true if the element is declared in ByteCode - */ - public static boolean isElementFromByteCode(@Nullable Element elt) { - if (elt == null) { - return false; + + /** + * Check if the element is an element for 'java.lang.String' + * + * @param element the type element + * @return true iff the element is java.lang.String element + */ + public static boolean isString(TypeElement element) { + return element.getQualifiedName().contentEquals("java.lang.String"); } - if (elt instanceof Symbol.ClassSymbol) { - Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt; - if (null != clss.classfile) { - // The class file could be a .java file - return clss.classfile.getKind() == JavaFileObject.Kind.CLASS; - } else { - return elt.asType().getKind().isPrimitive(); - } + /** + * Returns true if the element is a reference to a compile-time constant. + * + * @param elt an element + * @return true if the element is a reference to a compile-time constant + */ + public static boolean isCompileTimeConstant(@Nullable Element elt) { + return elt != null + && (elt.getKind() == ElementKind.FIELD + || elt.getKind() == ElementKind.LOCAL_VARIABLE) + && ((VariableElement) elt).getConstantValue() != null; } - return isElementFromByteCode(elt.getEnclosingElement()); - } - - /** - * Returns the path to the source file containing {@code element}, which must be from source code. - * - * @param element the type element to look at - * @return path to the source file containing {@code element} - */ - public static String getSourceFilePath(TypeElement element) { - String path = ((ClassSymbol) element).sourcefile.toUri().getPath(); - if (path == null) { - throw new BugInCF("Unexpected null path for TypeElement: " + element); + + /** + * Checks whether a given element came from a source file. + * + *

                  By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is + * a classfile for the given element, even if there is also a source file. + * + * @param element the element to check, or null + * @return true if a source file containing the element is being compiled + */ + public static boolean isElementFromSourceCode(@Nullable Element element) { + if (element == null) { + return false; + } + TypeElement enclosingTypeElement = enclosingTypeElement(element); + if (enclosingTypeElement == null) { + throw new BugInCF("enclosingTypeElement(%s) is null", element); + } + return isElementFromSourceCodeImpl((Symbol.ClassSymbol) enclosingTypeElement); } - return path; - } - - /** - * Returns the field of the class or {@code null} if not found. - * - * @param type the TypeElement to search - * @param name name of a field - * @return the VariableElement for the field if it was found, null otherwise - */ - public static @Nullable VariableElement findFieldInType(TypeElement type, String name) { - for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { - if (field.getSimpleName().contentEquals(name)) { - return field; - } + + /** + * Checks whether a given ClassSymbol came from a source file. + * + *

                  By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is + * a classfile for the given element, even if there is also a source file. + * + * @param symbol the class to check + * @return true if a source file containing the class is being compiled + */ + private static boolean isElementFromSourceCodeImpl(Symbol.ClassSymbol symbol) { + // This is a bit of a hack to avoid treating JDK as source files. JDK files' toUri() method + // returns just the name of the file (e.g. "Object.java"), but any file actually being + // compiled returns a file URI to the source file. + return symbol.sourcefile != null + && symbol.sourcefile.getKind() == JavaFileObject.Kind.SOURCE + && symbol.sourcefile.toUri().toString().startsWith("file:"); } - return null; - } - - /** - * Returns the elements of the fields whose simple names are in {@code names} and are declared in - * {@code type}. - * - *

                  If a field isn't declared in {@code type}, its element isn't included in the returned set. - * If none of the fields is declared in {@code type}, the empty set is returned. - * - * @param type where to look for fields - * @param names simple names of fields that might be declared in {@code type} - * @return the elements of the fields whose simple names are {@code names} and are declared in - * {@code type} - */ - public static Set findFieldsInType(TypeElement type, Collection names) { - Set results = ArraySet.newArraySetOrHashSet(names.size()); - for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { - if (names.contains(field.getSimpleName().toString())) { - results.add(field); - } + + /** + * Returns true if the element is declared in ByteCode. Always return false if elt is a package. + * + * @param elt some element + * @return true if the element is declared in ByteCode + */ + public static boolean isElementFromByteCode(@Nullable Element elt) { + if (elt == null) { + return false; + } + + if (elt instanceof Symbol.ClassSymbol) { + Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt; + if (null != clss.classfile) { + // The class file could be a .java file + return clss.classfile.getKind() == JavaFileObject.Kind.CLASS; + } else { + return elt.asType().getKind().isPrimitive(); + } + } + return isElementFromByteCode(elt.getEnclosingElement()); } - return results; - } - - /** - * Returns non-private field elements, and side-effects {@code names} to remove them. For every - * field name in {@code names} that is declared in {@code type} or a supertype, add its element to - * the returned set and remove it from {@code names}. - * - *

                  When this routine returns, the combination of the return value and {@code names} has the - * same cardinality, and represents the same fields, as {@code names} did when the method was - * called. - * - * @param type where to look for fields - * @param names simple names of fields that might be declared in {@code type} or a supertype. - * Names that are found are removed from this list. - * @return the {@code VariableElement}s for non-private fields that are declared in {@code type} - * whose simple names were in {@code names} when the method was called. - */ - public static Set findFieldsInTypeOrSuperType( - TypeMirror type, Collection names) { - int origCardinality = names.size(); - Set elements = ArraySet.newArraySetOrHashSet(origCardinality); - findFieldsInTypeOrSuperType(type, names, elements); - // Since `names` may contain duplicates, I don't trust the claim in the documentation about - // cardinality. (Does any code depend on the invariant, though?) - if (origCardinality != names.size() + elements.size()) { - throw new BugInCF( - "Bad sizes: %d != %d + %d ; names=%s elements=%s", - origCardinality, names.size(), elements.size(), names, elements); + + /** + * Returns the path to the source file containing {@code element}, which must be from source + * code. + * + * @param element the type element to look at + * @return path to the source file containing {@code element} + */ + public static String getSourceFilePath(TypeElement element) { + String path = ((ClassSymbol) element).sourcefile.toUri().getPath(); + if (path == null) { + throw new BugInCF("Unexpected null path for TypeElement: " + element); + } + return path; } - return elements; - } - - /** - * Side-effects both {@code foundFields} (which starts empty) and {@code notFound}, conceptually - * moving elements from {@code notFound} to {@code foundFields}. - */ - private static void findFieldsInTypeOrSuperType( - TypeMirror type, Collection notFound, Set foundFields) { - if (TypesUtils.isObject(type)) { - return; + + /** + * Returns the field of the class or {@code null} if not found. + * + * @param type the TypeElement to search + * @param name name of a field + * @return the VariableElement for the field if it was found, null otherwise + */ + public static @Nullable VariableElement findFieldInType(TypeElement type, String name) { + for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { + if (field.getSimpleName().contentEquals(name)) { + return field; + } + } + return null; } - TypeElement elt = TypesUtils.getTypeElement(type); - assert elt != null : "@AssumeAssertion(nullness): assumption"; - Set fieldElts = findFieldsInType(elt, notFound); - // Use a new list to avoid a ConcurrentModificationException. - for (VariableElement field : new ArrayList<>(fieldElts)) { - if (!field.getModifiers().contains(Modifier.PRIVATE)) { - notFound.remove(field.getSimpleName().toString()); - } else { - fieldElts.remove(field); - } + + /** + * Returns the elements of the fields whose simple names are in {@code names} and are declared + * in {@code type}. + * + *

                  If a field isn't declared in {@code type}, its element isn't included in the returned set. + * If none of the fields is declared in {@code type}, the empty set is returned. + * + * @param type where to look for fields + * @param names simple names of fields that might be declared in {@code type} + * @return the elements of the fields whose simple names are {@code names} and are declared in + * {@code type} + */ + public static Set findFieldsInType( + TypeElement type, Collection names) { + Set results = ArraySet.newArraySetOrHashSet(names.size()); + for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { + if (names.contains(field.getSimpleName().toString())) { + results.add(field); + } + } + return results; } - foundFields.addAll(fieldElts); - if (!notFound.isEmpty()) { - findFieldsInTypeOrSuperType(elt.getSuperclass(), notFound, foundFields); + /** + * Returns non-private field elements, and side-effects {@code names} to remove them. For every + * field name in {@code names} that is declared in {@code type} or a supertype, add its element + * to the returned set and remove it from {@code names}. + * + *

                  When this routine returns, the combination of the return value and {@code names} has the + * same cardinality, and represents the same fields, as {@code names} did when the method was + * called. + * + * @param type where to look for fields + * @param names simple names of fields that might be declared in {@code type} or a supertype. + * Names that are found are removed from this list. + * @return the {@code VariableElement}s for non-private fields that are declared in {@code type} + * whose simple names were in {@code names} when the method was called. + */ + public static Set findFieldsInTypeOrSuperType( + TypeMirror type, Collection names) { + int origCardinality = names.size(); + Set elements = ArraySet.newArraySetOrHashSet(origCardinality); + findFieldsInTypeOrSuperType(type, names, elements); + // Since `names` may contain duplicates, I don't trust the claim in the documentation about + // cardinality. (Does any code depend on the invariant, though?) + if (origCardinality != names.size() + elements.size()) { + throw new BugInCF( + "Bad sizes: %d != %d + %d ; names=%s elements=%s", + origCardinality, names.size(), elements.size(), names, elements); + } + return elements; } - } - - /** - * Returns true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError". - * - * @param element the element to test - * @return true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError" - */ - public static boolean isError(Element element) { - return element.getClass().getName() - == "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"; // interned - } - - /** - * Does the given element need a receiver for accesses? For example, an access to a local variable - * does not require a receiver. - * - * @param element the element to test - * @return whether the element requires a receiver for accesses - */ - public static boolean hasReceiver(Element element) { - if (element.getKind() == ElementKind.CONSTRUCTOR) { - // The enclosing element of a constructor is the class it creates. - // A constructor can only have a receiver if the class it creates has an outer type. - TypeMirror t = element.getEnclosingElement().asType(); - return TypesUtils.hasEnclosingType(t); - } else if (element.getKind() == ElementKind.FIELD) { - if (ElementUtils.isStatic(element) - // Artificial fields in interfaces are not marked as static, so check that - // the field is not declared in an interface. - || element.getEnclosingElement().getKind().isInterface()) { - return false; - } else { - // In constructors, the element for "this" is a non-static field, but that field - // does not have a receiver. - return !element.getSimpleName().contentEquals("this"); - } + + /** + * Side-effects both {@code foundFields} (which starts empty) and {@code notFound}, conceptually + * moving elements from {@code notFound} to {@code foundFields}. + */ + private static void findFieldsInTypeOrSuperType( + TypeMirror type, Collection notFound, Set foundFields) { + if (TypesUtils.isObject(type)) { + return; + } + TypeElement elt = TypesUtils.getTypeElement(type); + assert elt != null : "@AssumeAssertion(nullness): assumption"; + Set fieldElts = findFieldsInType(elt, notFound); + // Use a new list to avoid a ConcurrentModificationException. + for (VariableElement field : new ArrayList<>(fieldElts)) { + if (!field.getModifiers().contains(Modifier.PRIVATE)) { + notFound.remove(field.getSimpleName().toString()); + } else { + fieldElts.remove(field); + } + } + foundFields.addAll(fieldElts); + + if (!notFound.isEmpty()) { + findFieldsInTypeOrSuperType(elt.getSuperclass(), notFound, foundFields); + } } - return element.getKind() == ElementKind.METHOD && !ElementUtils.isStatic(element); - } - - /** - * Returns a type's superclass, or null if it does not have a superclass (it is object or an - * interface, or the superclass is not on the classpath). - * - * @param typeElt a type element - * @return the superclass of {@code typeElt} - */ - public static @Nullable TypeElement getSuperClass(TypeElement typeElt) { - TypeMirror superTypeMirror; - try { - superTypeMirror = typeElt.getSuperclass(); - } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { - // Looking up a supertype failed. This sometimes happens when transitive dependencies - // are not on the classpath. As javac didn't complain, let's also not complain. - return null; + + /** + * Returns true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError". + * + * @param element the element to test + * @return true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError" + */ + public static boolean isError(Element element) { + return element.getClass().getName() + == "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"; // interned } - if (superTypeMirror == null || superTypeMirror.getKind() == TypeKind.NONE) { - return null; - } else { - return (TypeElement) ((DeclaredType) superTypeMirror).asElement(); + /** + * Does the given element need a receiver for accesses? For example, an access to a local + * variable does not require a receiver. + * + * @param element the element to test + * @return whether the element requires a receiver for accesses + */ + public static boolean hasReceiver(Element element) { + if (element.getKind() == ElementKind.CONSTRUCTOR) { + // The enclosing element of a constructor is the class it creates. + // A constructor can only have a receiver if the class it creates has an outer type. + TypeMirror t = element.getEnclosingElement().asType(); + return TypesUtils.hasEnclosingType(t); + } else if (element.getKind() == ElementKind.FIELD) { + if (ElementUtils.isStatic(element) + // Artificial fields in interfaces are not marked as static, so check that + // the field is not declared in an interface. + || element.getEnclosingElement().getKind().isInterface()) { + return false; + } else { + // In constructors, the element for "this" is a non-static field, but that field + // does not have a receiver. + return !element.getSimpleName().contentEquals("this"); + } + } + return element.getKind() == ElementKind.METHOD && !ElementUtils.isStatic(element); } - } - - /** - * Determine all type elements for the supertypes of the given type element. This is the - * transitive closure of the extends and implements clauses. - * - *

                  TODO: can we learn from the implementation of - * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)? - * - * @param type the type whose supertypes to return - * @param elements the Element utilities - * @return supertypes of {@code type} - */ - public static List getSuperTypes(TypeElement type, Elements elements) { - - if (type == null) { - return Collections.emptyList(); + + /** + * Returns a type's superclass, or null if it does not have a superclass (it is object or an + * interface, or the superclass is not on the classpath). + * + * @param typeElt a type element + * @return the superclass of {@code typeElt} + */ + public static @Nullable TypeElement getSuperClass(TypeElement typeElt) { + TypeMirror superTypeMirror; + try { + superTypeMirror = typeElt.getSuperclass(); + } catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) { + // Looking up a supertype failed. This sometimes happens when transitive dependencies + // are not on the classpath. As javac didn't complain, let's also not complain. + return null; + } + + if (superTypeMirror == null || superTypeMirror.getKind() == TypeKind.NONE) { + return null; + } else { + return (TypeElement) ((DeclaredType) superTypeMirror).asElement(); + } } - List superElems = new ArrayList<>(); + /** + * Determine all type elements for the supertypes of the given type element. This is the + * transitive closure of the extends and implements clauses. + * + *

                  TODO: can we learn from the implementation of + * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)? + * + * @param type the type whose supertypes to return + * @param elements the Element utilities + * @return supertypes of {@code type} + */ + public static List getSuperTypes(TypeElement type, Elements elements) { + + if (type == null) { + return Collections.emptyList(); + } - // Set up a stack containing `type`, which is our starting point. - Deque stack = new ArrayDeque<>(); - stack.push(type); + List superElems = new ArrayList<>(); + + // Set up a stack containing `type`, which is our starting point. + Deque stack = new ArrayDeque<>(); + stack.push(type); + + while (!stack.isEmpty()) { + TypeElement current = stack.pop(); + + // For each direct supertype of the current type element, if it + // hasn't already been visited, push it onto the stack and + // add it to our superElems set. + TypeElement supercls = ElementUtils.getSuperClass(current); + if (supercls != null) { + if (!superElems.contains(supercls)) { + stack.push(supercls); + superElems.add(supercls); + } + } + + for (TypeMirror supertypeitf : current.getInterfaces()) { + TypeElement superitf = (TypeElement) ((DeclaredType) supertypeitf).asElement(); + if (!superElems.contains(superitf)) { + stack.push(superitf); + superElems.add(superitf); + } + } + } - while (!stack.isEmpty()) { - TypeElement current = stack.pop(); + // Include java.lang.Object as implicit superclass for all classes and interfaces. + TypeElement jlobject = elements.getTypeElement("java.lang.Object"); + if (!superElems.contains(jlobject)) { + superElems.add(jlobject); + } - // For each direct supertype of the current type element, if it - // hasn't already been visited, push it onto the stack and - // add it to our superElems set. - TypeElement supercls = ElementUtils.getSuperClass(current); - if (supercls != null) { - if (!superElems.contains(supercls)) { - stack.push(supercls); - superElems.add(supercls); + return Collections.unmodifiableList(superElems); + } + + /** + * Determine all type elements for the direct supertypes of the given type element. This is the + * union of the extends and implements clauses. + * + * @param type the type whose supertypes to return + * @param elements the Element utilities + * @return direct supertypes of {@code type} + */ + public static List getDirectSuperTypeElements( + TypeElement type, Elements elements) { + TypeMirror superclass = type.getSuperclass(); + List interfaces = type.getInterfaces(); + List result = new ArrayList(interfaces.size() + 1); + if (superclass.getKind() != TypeKind.NONE) { + @SuppressWarnings("nullness:assignment") // Not null because the TypeKind is not NONE. + @NonNull TypeElement superclassElement = TypesUtils.getTypeElement(superclass); + result.add(superclassElement); + } + for (TypeMirror interfac : interfaces) { + @SuppressWarnings("nullness:assignment") // every interface is a type + @NonNull TypeElement interfaceElt = TypesUtils.getTypeElement(interfac); + result.add(interfaceElt); } - } + return result; + } - for (TypeMirror supertypeitf : current.getInterfaces()) { - TypeElement superitf = (TypeElement) ((DeclaredType) supertypeitf).asElement(); - if (!superElems.contains(superitf)) { - stack.push(superitf); - superElems.add(superitf); + /** + * Return all fields declared in the given type or any superclass/interface. + * + *

                  TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of + * our own getSuperTypes? + * + * @param type the type whose fields to return + * @param elements the Element utilities + * @return fields of {@code type} + */ + public static List getAllFieldsIn(TypeElement type, Elements elements) { + // ElementFilter.fieldsIn returns a new list + List fields = ElementFilter.fieldsIn(type.getEnclosedElements()); + List alltypes = getSuperTypes(type, elements); + for (TypeElement atype : alltypes) { + fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements())); } - } + return Collections.unmodifiableList(fields); } - // Include java.lang.Object as implicit superclass for all classes and interfaces. - TypeElement jlobject = elements.getTypeElement("java.lang.Object"); - if (!superElems.contains(jlobject)) { - superElems.add(jlobject); + /** + * Returns all enum constants declared in the given enumeration. + * + * @param type an Enum type + * @return all enum constants declared in the given enumeration + */ + public static List getEnumConstants(TypeElement type) { + List enclosedElements = type.getEnclosedElements(); + List enumConstants = new ArrayList<>(enclosedElements.size()); + for (Element e : enclosedElements) { + if (e.getKind() == ElementKind.ENUM_CONSTANT) { + enumConstants.add((VariableElement) e); + } + } + return enumConstants; } - return Collections.unmodifiableList(superElems); - } - - /** - * Determine all type elements for the direct supertypes of the given type element. This is the - * union of the extends and implements clauses. - * - * @param type the type whose supertypes to return - * @param elements the Element utilities - * @return direct supertypes of {@code type} - */ - public static List getDirectSuperTypeElements(TypeElement type, Elements elements) { - TypeMirror superclass = type.getSuperclass(); - List interfaces = type.getInterfaces(); - List result = new ArrayList(interfaces.size() + 1); - if (superclass.getKind() != TypeKind.NONE) { - @SuppressWarnings("nullness:assignment") // Not null because the TypeKind is not NONE. - @NonNull TypeElement superclassElement = TypesUtils.getTypeElement(superclass); - result.add(superclassElement); + /** + * Return all methods declared in the given type or any superclass/interface. Note that no + * constructors will be returned. + * + *

                  TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of + * our own getSuperTypes? + * + * @param type the type whose methods to return + * @param elements the Element utilities + * @return methods of {@code type} + */ + public static List getAllMethodsIn(TypeElement type, Elements elements) { + // ElementFilter.fieldsIn returns a new list + List meths = ElementFilter.methodsIn(type.getEnclosedElements()); + + List alltypes = getSuperTypes(type, elements); + for (TypeElement atype : alltypes) { + meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements())); + } + return Collections.unmodifiableList(meths); } - for (TypeMirror interfac : interfaces) { - @SuppressWarnings("nullness:assignment") // every interface is a type - @NonNull TypeElement interfaceElt = TypesUtils.getTypeElement(interfac); - result.add(interfaceElt); + + /** + * Return all nested/inner classes/interfaces declared in the given type. + * + * @param type a type + * @return all nested/inner classes/interfaces declared in {@code type} + */ + public static List getAllTypeElementsIn(TypeElement type) { + return ElementFilter.typesIn(type.getEnclosedElements()); } - return result; - } - - /** - * Return all fields declared in the given type or any superclass/interface. - * - *

                  TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of - * our own getSuperTypes? - * - * @param type the type whose fields to return - * @param elements the Element utilities - * @return fields of {@code type} - */ - public static List getAllFieldsIn(TypeElement type, Elements elements) { - // ElementFilter.fieldsIn returns a new list - List fields = ElementFilter.fieldsIn(type.getEnclosedElements()); - List alltypes = getSuperTypes(type, elements); - for (TypeElement atype : alltypes) { - fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements())); + + /** The set of kinds that represent types. */ + private static final Set typeElementKinds; + + static { + typeElementKinds = EnumSet.noneOf(ElementKind.class); + for (ElementKind kind : ElementKind.values()) { + if (kind.isClass() || kind.isInterface()) { + typeElementKinds.add(kind); + } + } } - return Collections.unmodifiableList(fields); - } - - /** - * Returns all enum constants declared in the given enumeration. - * - * @param type an Enum type - * @return all enum constants declared in the given enumeration - */ - public static List getEnumConstants(TypeElement type) { - List enclosedElements = type.getEnclosedElements(); - List enumConstants = new ArrayList<>(enclosedElements.size()); - for (Element e : enclosedElements) { - if (e.getKind() == ElementKind.ENUM_CONSTANT) { - enumConstants.add((VariableElement) e); - } + + /** + * Return the set of kinds that represent classes. + * + * @return the set of kinds that represent classes + */ + public static Set typeElementKinds() { + return typeElementKinds; } - return enumConstants; - } - - /** - * Return all methods declared in the given type or any superclass/interface. Note that no - * constructors will be returned. - * - *

                  TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of - * our own getSuperTypes? - * - * @param type the type whose methods to return - * @param elements the Element utilities - * @return methods of {@code type} - */ - public static List getAllMethodsIn(TypeElement type, Elements elements) { - // ElementFilter.fieldsIn returns a new list - List meths = ElementFilter.methodsIn(type.getEnclosedElements()); - - List alltypes = getSuperTypes(type, elements); - for (TypeElement atype : alltypes) { - meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements())); + + /** + * Is the given element kind a type, i.e., a class, enum, interface, or annotation type. + * + * @param element the element to test + * @return true, iff the given kind is a class kind + */ + public static boolean isTypeElement(Element element) { + return typeElementKinds().contains(element.getKind()); } - return Collections.unmodifiableList(meths); - } - - /** - * Return all nested/inner classes/interfaces declared in the given type. - * - * @param type a type - * @return all nested/inner classes/interfaces declared in {@code type} - */ - public static List getAllTypeElementsIn(TypeElement type) { - return ElementFilter.typesIn(type.getEnclosedElements()); - } - - /** The set of kinds that represent types. */ - private static final Set typeElementKinds; - - static { - typeElementKinds = EnumSet.noneOf(ElementKind.class); - for (ElementKind kind : ElementKind.values()) { - if (kind.isClass() || kind.isInterface()) { - typeElementKinds.add(kind); - } + + /** + * Return true if the element is a type declaration. + * + * @param elt the element to test + * @return true if the argument is a type declaration + */ + public static boolean isTypeDeclaration(Element elt) { + return isTypeElement(elt) || elt.getKind() == ElementKind.TYPE_PARAMETER; } - } - - /** - * Return the set of kinds that represent classes. - * - * @return the set of kinds that represent classes - */ - public static Set typeElementKinds() { - return typeElementKinds; - } - - /** - * Is the given element kind a type, i.e., a class, enum, interface, or annotation type. - * - * @param element the element to test - * @return true, iff the given kind is a class kind - */ - public static boolean isTypeElement(Element element) { - return typeElementKinds().contains(element.getKind()); - } - - /** - * Return true if the element is a type declaration. - * - * @param elt the element to test - * @return true if the argument is a type declaration - */ - public static boolean isTypeDeclaration(Element elt) { - return isTypeElement(elt) || elt.getKind() == ElementKind.TYPE_PARAMETER; - } - - /** The set of kinds that represent local variables. */ - private static final Set localVariableElementKinds = - EnumSet.of( - ElementKind.LOCAL_VARIABLE, - ElementKind.RESOURCE_VARIABLE, - ElementKind.EXCEPTION_PARAMETER); - - /** - * Return true if the element is a local variable. - * - * @param elt the element to test - * @return true if the argument is a local variable - */ - public static boolean isLocalVariable(Element elt) { - return localVariableElementKinds.contains(elt.getKind()); - } - - /** - * Return true if the element is a binding variable. - * - *

                  This implementation compiles and runs under JDK 8 and 11 as well as versions that contain - * {@code ElementKind.BINDING_VARIABLE}. - * - * @param element the element to test - * @return true if the element is a binding variable - */ - public static boolean isBindingVariable(Element element) { - return SystemUtil.jreVersion >= 16 && "BINDING_VARIABLE".equals(element.getKind().name()); - } - - /** - * Returns true if the element is a record accessor method. - * - * @param methodElement a method element - * @return true if the element is a record accessor method - */ - public static boolean isRecordAccessor(ExecutableElement methodElement) { - // Can only be a record accessor if it has no parameters. - if (!methodElement.getParameters().isEmpty()) { - return false; + + /** The set of kinds that represent local variables. */ + private static final Set localVariableElementKinds = + EnumSet.of( + ElementKind.LOCAL_VARIABLE, + ElementKind.RESOURCE_VARIABLE, + ElementKind.EXCEPTION_PARAMETER); + + /** + * Return true if the element is a local variable. + * + * @param elt the element to test + * @return true if the argument is a local variable + */ + public static boolean isLocalVariable(Element elt) { + return localVariableElementKinds.contains(elt.getKind()); + } + + /** + * Return true if the element is a binding variable. + * + *

                  This implementation compiles and runs under JDK 8 and 11 as well as versions that contain + * {@code ElementKind.BINDING_VARIABLE}. + * + * @param element the element to test + * @return true if the element is a binding variable + */ + public static boolean isBindingVariable(Element element) { + return SystemUtil.jreVersion >= 16 && "BINDING_VARIABLE".equals(element.getKind().name()); } - TypeElement enclosing = (TypeElement) methodElement.getEnclosingElement(); - if (isRecordElement(enclosing)) { - String methodName = methodElement.getSimpleName().toString(); - List encloseds = enclosing.getEnclosedElements(); - for (Element enclosed : encloseds) { - if (isRecordComponentElement(enclosed) - && enclosed.getSimpleName().toString().equals(methodName)) { - return true; + /** + * Returns true if the element is a record accessor method. + * + * @param methodElement a method element + * @return true if the element is a record accessor method + */ + public static boolean isRecordAccessor(ExecutableElement methodElement) { + // Can only be a record accessor if it has no parameters. + if (!methodElement.getParameters().isEmpty()) { + return false; + } + + TypeElement enclosing = (TypeElement) methodElement.getEnclosingElement(); + if (isRecordElement(enclosing)) { + String methodName = methodElement.getSimpleName().toString(); + List encloseds = enclosing.getEnclosedElements(); + for (Element enclosed : encloseds) { + if (isRecordComponentElement(enclosed) + && enclosed.getSimpleName().toString().equals(methodName)) { + return true; + } + } } - } + return false; } - return false; - } - - /** - * Returns true if the given {@link Element} is part of a record that has been automatically - * generated by the compiler. This can be a field that is derived from the record's header field - * list, or an automatically generated canonical constructor. - * - * @param e the {@link Element} for a member of a record - * @return true if the given element is generated by the compiler - */ - public static boolean isAutoGeneratedRecordMember(Element e) { - if (!(e instanceof Symbol)) { - return false; + + /** + * Returns true if the given {@link Element} is part of a record that has been automatically + * generated by the compiler. This can be a field that is derived from the record's header field + * list, or an automatically generated canonical constructor. + * + * @param e the {@link Element} for a member of a record + * @return true if the given element is generated by the compiler + */ + public static boolean isAutoGeneratedRecordMember(Element e) { + if (!(e instanceof Symbol)) { + return false; + } + // Generated constructors seem to get GENERATEDCONSTR even though the documentation + // seems to imply they would get GENERATED_MEMBER like the fields do. + return (((Symbol) e).flags() & (Flags_GENERATED_MEMBER | Flags.GENERATEDCONSTR)) != 0; } - // Generated constructors seem to get GENERATEDCONSTR even though the documentation - // seems to imply they would get GENERATED_MEMBER like the fields do. - return (((Symbol) e).flags() & (Flags_GENERATED_MEMBER | Flags.GENERATEDCONSTR)) != 0; - } - - /** - * Check that a method Element matches a signature. - * - *

                  Note: Matching the receiver type must be done elsewhere as the Element receiver type is only - * populated when annotated. - * - * @param method the method Element to be tested - * @param methodName the goal method name - * @param parameters the goal formal parameter Classes - * @return true if the method matches the methodName and parameters - */ - public static boolean matchesElement( - ExecutableElement method, String methodName, Class... parameters) { - - if (!method.getSimpleName().contentEquals(methodName)) { - return false; + + /** + * Check that a method Element matches a signature. + * + *

                  Note: Matching the receiver type must be done elsewhere as the Element receiver type is + * only populated when annotated. + * + * @param method the method Element to be tested + * @param methodName the goal method name + * @param parameters the goal formal parameter Classes + * @return true if the method matches the methodName and parameters + */ + public static boolean matchesElement( + ExecutableElement method, String methodName, Class... parameters) { + + if (!method.getSimpleName().contentEquals(methodName)) { + return false; + } + + if (method.getParameters().size() != parameters.length) { + return false; + } else { + for (int i = 0; i < method.getParameters().size(); i++) { + if (!method.getParameters() + .get(i) + .asType() + .toString() + .equals(parameters[i].getName())) { + + return false; + } + } + } + + return true; } - if (method.getParameters().size() != parameters.length) { - return false; - } else { - for (int i = 0; i < method.getParameters().size(); i++) { - if (!method.getParameters().get(i).asType().toString().equals(parameters[i].getName())) { + /** + * Returns true if the given element is, or overrides, {@code method}. + * + * @param questioned an element that might override {@code method} + * @param method a method that might be overridden + * @param env the processing environment + * @return true if {@code questioned} is, or overrides, {@code method} + */ + public static boolean isMethod( + ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) { + return questioned.equals(method) + || env.getElementUtils() + .overrides( + questioned, method, (TypeElement) questioned.getEnclosingElement()); + } - return false; + /** + * Given an annotation name, return true if the element has the annotation of that name. + * + *

                  It is more efficient to use {@code Element#getAnnotation(Class)}, but note that both + * methods ignore types from annotation files, such as stub or ajava files. + * + *

                  To include types from annotation files, use {@code AnnotatedTypeFactory#fromElement} or + * {@code AnnotatedTypeFactory#getDeclAnnotations}. + * + * @param element the element + * @param annotName name of the annotation + * @return true if the element has the annotation of that name + */ + public static boolean hasAnnotation(Element element, String annotName) { + for (AnnotationMirror anm : element.getAnnotationMirrors()) { + if (AnnotationUtils.areSameByName(anm, annotName)) { + return true; + } } - } + return false; } - return true; - } - - /** - * Returns true if the given element is, or overrides, {@code method}. - * - * @param questioned an element that might override {@code method} - * @param method a method that might be overridden - * @param env the processing environment - * @return true if {@code questioned} is, or overrides, {@code method} - */ - public static boolean isMethod( - ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) { - return questioned.equals(method) - || env.getElementUtils() - .overrides(questioned, method, (TypeElement) questioned.getEnclosingElement()); - } - - /** - * Given an annotation name, return true if the element has the annotation of that name. - * - *

                  It is more efficient to use {@code Element#getAnnotation(Class)}, but note that both methods - * ignore types from annotation files, such as stub or ajava files. - * - *

                  To include types from annotation files, use {@code AnnotatedTypeFactory#fromElement} or - * {@code AnnotatedTypeFactory#getDeclAnnotations}. - * - * @param element the element - * @param annotName name of the annotation - * @return true if the element has the annotation of that name - */ - public static boolean hasAnnotation(Element element, String annotName) { - for (AnnotationMirror anm : element.getAnnotationMirrors()) { - if (AnnotationUtils.areSameByName(anm, annotName)) { - return true; - } + /** + * Returns the TypeElement for the given class. + * + * @param processingEnv the processing environment + * @param clazz a class + * @return the TypeElement for the class + */ + public static TypeElement getTypeElement(ProcessingEnvironment processingEnv, Class clazz) { + @CanonicalName String className = clazz.getCanonicalName(); + if (className == null) { + throw new BugInCF("Anonymous class " + clazz + " has no canonical name"); + } + return processingEnv.getElementUtils().getTypeElement(className); + } + + /** + * Get all the supertypes of a given type, including the type itself. The result includes both + * superclasses and implemented interfaces. + * + * @param type a type + * @param env the processing environment + * @return list including the type and all its supertypes, with a guarantee that direct + * supertypes (i.e. those that appear in extends or implements clauses) appear before + * indirect supertypes + */ + public static List getAllSupertypes(TypeElement type, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return CollectionsPlume.mapList( + t -> (TypeElement) t.tsym, javacTypes.closure(((Symbol) type).type)); + } + + /** + * Returns the methods that are overridden or implemented by a given method. + * + * @param m a method + * @param types the type utilities + * @return the methods that {@code m} overrides or implements + */ + public static Set getOverriddenMethods( + ExecutableElement m, Types types) { + JavacTypes t = (JavacTypes) types; + return t.getOverriddenMethods(m); + } + + /** + * Returns true if the two elements are in the same class. The two elements should be class + * members, such as methods or fields. + * + * @param e1 an element + * @param e2 an element + * @return true if the two elements are in the same class + */ + public static boolean inSameClass(Element e1, Element e2) { + return e1.getEnclosingElement().equals(e2.getEnclosingElement()); } - return false; - } - - /** - * Returns the TypeElement for the given class. - * - * @param processingEnv the processing environment - * @param clazz a class - * @return the TypeElement for the class - */ - public static TypeElement getTypeElement(ProcessingEnvironment processingEnv, Class clazz) { - @CanonicalName String className = clazz.getCanonicalName(); - if (className == null) { - throw new BugInCF("Anonymous class " + clazz + " has no canonical name"); + + /** + * Determine whether the given element is of Kind RECORD, in a way that works on all versions of + * Java. + * + * @param elt the element to test + * @return whether the element is of the kind RECORD + */ + public static boolean isRecordElement(Element elt) { + ElementKind kind = elt.getKind(); + return kind.name().equals("RECORD"); } - return processingEnv.getElementUtils().getTypeElement(className); - } - - /** - * Get all the supertypes of a given type, including the type itself. The result includes both - * superclasses and implemented interfaces. - * - * @param type a type - * @param env the processing environment - * @return list including the type and all its supertypes, with a guarantee that direct supertypes - * (i.e. those that appear in extends or implements clauses) appear before indirect supertypes - */ - public static List getAllSupertypes(TypeElement type, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); - return CollectionsPlume.mapList( - t -> (TypeElement) t.tsym, javacTypes.closure(((Symbol) type).type)); - } - - /** - * Returns the methods that are overridden or implemented by a given method. - * - * @param m a method - * @param types the type utilities - * @return the methods that {@code m} overrides or implements - */ - public static Set getOverriddenMethods( - ExecutableElement m, Types types) { - JavacTypes t = (JavacTypes) types; - return t.getOverriddenMethods(m); - } - - /** - * Returns true if the two elements are in the same class. The two elements should be class - * members, such as methods or fields. - * - * @param e1 an element - * @param e2 an element - * @return true if the two elements are in the same class - */ - public static boolean inSameClass(Element e1, Element e2) { - return e1.getEnclosingElement().equals(e2.getEnclosingElement()); - } - - /** - * Determine whether the given element is of Kind RECORD, in a way that works on all versions of - * Java. - * - * @param elt the element to test - * @return whether the element is of the kind RECORD - */ - public static boolean isRecordElement(Element elt) { - ElementKind kind = elt.getKind(); - return kind.name().equals("RECORD"); - } - - /** - * Determine whether the given element is of Kind RECORD_COMPONENT, in a way that works on all - * versions of Java. - * - * @param elt the element to test - * @return whether the element is of the kind RECORD_COMPONENT - */ - public static boolean isRecordComponentElement(Element elt) { - ElementKind kind = elt.getKind(); - return kind.name().equals("RECORD_COMPONENT"); - } - - /** - * Calls getKind() on the given Element, but returns CLASS if the ElementKind is RECORD. This is - * needed because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD - * can't be used in case statements, and usually we want to treat them the same as classes. - * - * @param elt the element to get the kind for - * @return the kind of the element, but CLASS if the kind was RECORD - */ - public static ElementKind getKindRecordAsClass(Element elt) { - if (isRecordElement(elt)) { - return ElementKind.CLASS; + + /** + * Determine whether the given element is of Kind RECORD_COMPONENT, in a way that works on all + * versions of Java. + * + * @param elt the element to test + * @return whether the element is of the kind RECORD_COMPONENT + */ + public static boolean isRecordComponentElement(Element elt) { + ElementKind kind = elt.getKind(); + return kind.name().equals("RECORD_COMPONENT"); } - return elt.getKind(); - } - - /** The {@code TypeElement.getRecordComponents()} method. */ - private static final @Nullable Method TYPEELEMENT_GETRECORDCOMPONENTS; - - static { - if (SystemUtil.jreVersion >= 16) { - try { - TYPEELEMENT_GETRECORDCOMPONENTS = TypeElement.class.getMethod("getRecordComponents"); - } catch (NoSuchMethodException e) { - throw new BugInCF("Cannot access TypeElement.getRecordComponents()", e); - } - } else { - TYPEELEMENT_GETRECORDCOMPONENTS = null; + + /** + * Calls getKind() on the given Element, but returns CLASS if the ElementKind is RECORD. This is + * needed because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD + * can't be used in case statements, and usually we want to treat them the same as classes. + * + * @param elt the element to get the kind for + * @return the kind of the element, but CLASS if the kind was RECORD + */ + public static ElementKind getKindRecordAsClass(Element elt) { + if (isRecordElement(elt)) { + return ElementKind.CLASS; + } + return elt.getKind(); } - } - - /** - * Calls getRecordComponents on the given TypeElement. Uses reflection because this method is not - * available before JDK 16. On earlier JDKs, which don't support records anyway, an exception is - * thrown. - * - * @param element the type element to call getRecordComponents on - * @return the return value of calling getRecordComponents, or empty list if the method is not - * available - */ - @SuppressWarnings({"unchecked", "nullness"}) // because of cast from reflection - public static List getRecordComponents(TypeElement element) { - try { - return (@NonNull List) TYPEELEMENT_GETRECORDCOMPONENTS.invoke(element); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new Error("Cannot call TypeElement.getRecordComponents()", e); + + /** The {@code TypeElement.getRecordComponents()} method. */ + private static final @Nullable Method TYPEELEMENT_GETRECORDCOMPONENTS; + + static { + if (SystemUtil.jreVersion >= 16) { + try { + TYPEELEMENT_GETRECORDCOMPONENTS = + TypeElement.class.getMethod("getRecordComponents"); + } catch (NoSuchMethodException e) { + throw new BugInCF("Cannot access TypeElement.getRecordComponents()", e); + } + } else { + TYPEELEMENT_GETRECORDCOMPONENTS = null; + } } - } - - /** - * Check if the given element is a compact canonical record constructor. - * - * @param elt the element to check - * @return true if the element is a compact canonical constructor of a record - */ - public static boolean isCompactCanonicalRecordConstructor(Element elt) { - return elt.getKind() == ElementKind.CONSTRUCTOR - && (((Symbol) elt).flags() & Flags_COMPACT_RECORD_CONSTRUCTOR) != 0; - } - - /** - * Returns true iff the given element is a resource variable. - * - * @param elt an element; may be null, in which case this method always returns false - * @return true iff the given element represents a resource variable - */ - public static boolean isResourceVariable(@Nullable Element elt) { - return elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE; - } - - /** - * Returns true if the given element is a getter method. A getter method is an instance method - * with no formal parameters, whose name starts with "get", "is", "not", or "has" followed by an - * upper-case letter. - * - * @param methodElt a method - * @return true if the given element is a getter method - */ - public static boolean isGetter(@Nullable ExecutableElement methodElt) { - if (methodElt == null) { - return false; + + /** + * Calls getRecordComponents on the given TypeElement. Uses reflection because this method is + * not available before JDK 16. On earlier JDKs, which don't support records anyway, an + * exception is thrown. + * + * @param element the type element to call getRecordComponents on + * @return the return value of calling getRecordComponents, or empty list if the method is not + * available + */ + @SuppressWarnings({"unchecked", "nullness"}) // because of cast from reflection + public static List getRecordComponents(TypeElement element) { + try { + return (@NonNull List) + TYPEELEMENT_GETRECORDCOMPONENTS.invoke(element); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new Error("Cannot call TypeElement.getRecordComponents()", e); + } } - if (isStatic(methodElt)) { - return false; + + /** + * Check if the given element is a compact canonical record constructor. + * + * @param elt the element to check + * @return true if the element is a compact canonical constructor of a record + */ + public static boolean isCompactCanonicalRecordConstructor(Element elt) { + return elt.getKind() == ElementKind.CONSTRUCTOR + && (((Symbol) elt).flags() & Flags_COMPACT_RECORD_CONSTRUCTOR) != 0; } - if (!methodElt.getParameters().isEmpty()) { - return false; + + /** + * Returns true iff the given element is a resource variable. + * + * @param elt an element; may be null, in which case this method always returns false + * @return true iff the given element represents a resource variable + */ + public static boolean isResourceVariable(@Nullable Element elt) { + return elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE; } - // I could check that the method has a non-void return type, - // and that methods with prefix "is", "has", and "not" return boolean. + /** + * Returns true if the given element is a getter method. A getter method is an instance method + * with no formal parameters, whose name starts with "get", "is", "not", or "has" followed by an + * upper-case letter. + * + * @param methodElt a method + * @return true if the given element is a getter method + */ + public static boolean isGetter(@Nullable ExecutableElement methodElt) { + if (methodElt == null) { + return false; + } + if (isStatic(methodElt)) { + return false; + } + if (!methodElt.getParameters().isEmpty()) { + return false; + } + + // I could check that the method has a non-void return type, + // and that methods with prefix "is", "has", and "not" return boolean. - // Constructors and initializers don't have a name starting with a character. - String name = methodElt.getSimpleName().toString(); - // I expect this code is more efficient than use of a regular expression. - boolean nameOk = - nameStartsWith(name, "get") - || nameStartsWith(name, "is") - || nameStartsWith(name, "not") - || nameStartsWith(name, "has"); + // Constructors and initializers don't have a name starting with a character. + String name = methodElt.getSimpleName().toString(); + // I expect this code is more efficient than use of a regular expression. + boolean nameOk = + nameStartsWith(name, "get") + || nameStartsWith(name, "is") + || nameStartsWith(name, "not") + || nameStartsWith(name, "has"); - if (!nameOk) { - return false; + if (!nameOk) { + return false; + } + + return true; } - return true; - } - - /** - * Returns true if the name starts with the given prefix, followed by an upper-case letter. - * - * @param name a name - * @param prefix a prefix - * @return true if the name starts with the given prefix, followed by an upper-case letter - */ - private static boolean nameStartsWith(String name, String prefix) { - return name.startsWith(prefix) - && name.length() > prefix.length() - && Character.isUpperCase(name.charAt(prefix.length())); - } + /** + * Returns true if the name starts with the given prefix, followed by an upper-case letter. + * + * @param name a name + * @param prefix a prefix + * @return true if the name starts with the given prefix, followed by an upper-case letter + */ + private static boolean nameStartsWith(String name, String prefix) { + return name.startsWith(prefix) + && name.length() > prefix.length() + && Character.isUpperCase(name.charAt(prefix.length())); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java index 4d5a94633ce..4ec70420485 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/InternalUtils.java @@ -4,53 +4,55 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; -import javax.annotation.processing.ProcessingEnvironment; + import org.checkerframework.checker.nullness.qual.Nullable; +import javax.annotation.processing.ProcessingEnvironment; + /** Miscellaneous static utility methods. */ public class InternalUtils { - // Class cannot be instantiated. - private InternalUtils() { - throw new AssertionError("Class InternalUtils cannot be instantiated."); - } - - /** - * Helper function to extract the javac Context from the javac processing environment. - * - * @param env the processing environment - * @return the javac Context - */ - public static Context getJavacContext(ProcessingEnvironment env) { - return ((JavacProcessingEnvironment) env).getContext(); - } - - /** - * Obtain the class loader for {@code clazz}. If that is not available, return the system class - * loader. - * - * @param clazz the class whose class loader to find - * @return the class loader used to {@code clazz}, or the system class loader, or null if both are - * unavailable - */ - public static @Nullable ClassLoader getClassLoaderForClass(Class clazz) { - ClassLoader classLoader = clazz.getClassLoader(); - return classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader; - } - - /** - * Compares tree1 to tree2 by the position at which a diagnostic (e.g., an error message) for the - * tree should be printed. - */ - public static int compareDiagnosticPosition(Tree tree1, Tree tree2) { - DiagnosticPosition pos1 = (DiagnosticPosition) tree1; - DiagnosticPosition pos2 = (DiagnosticPosition) tree2; - - int preferred = Integer.compare(pos1.getPreferredPosition(), pos2.getPreferredPosition()); - if (preferred != 0) { - return preferred; + // Class cannot be instantiated. + private InternalUtils() { + throw new AssertionError("Class InternalUtils cannot be instantiated."); + } + + /** + * Helper function to extract the javac Context from the javac processing environment. + * + * @param env the processing environment + * @return the javac Context + */ + public static Context getJavacContext(ProcessingEnvironment env) { + return ((JavacProcessingEnvironment) env).getContext(); } - return Integer.compare(pos1.getStartPosition(), pos2.getStartPosition()); - } + /** + * Obtain the class loader for {@code clazz}. If that is not available, return the system class + * loader. + * + * @param clazz the class whose class loader to find + * @return the class loader used to {@code clazz}, or the system class loader, or null if both + * are unavailable + */ + public static @Nullable ClassLoader getClassLoaderForClass(Class clazz) { + ClassLoader classLoader = clazz.getClassLoader(); + return classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader; + } + + /** + * Compares tree1 to tree2 by the position at which a diagnostic (e.g., an error message) for + * the tree should be printed. + */ + public static int compareDiagnosticPosition(Tree tree1, Tree tree2) { + DiagnosticPosition pos1 = (DiagnosticPosition) tree1; + DiagnosticPosition pos2 = (DiagnosticPosition) tree2; + + int preferred = Integer.compare(pos1.getPreferredPosition(), pos2.getPreferredPosition()); + if (preferred != 0) { + return preferred; + } + + return Integer.compare(pos1.getStartPosition(), pos2.getStartPosition()); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java index 64751c1914a..71a25fff1c3 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java @@ -1,11 +1,12 @@ package org.checkerframework.javacutil; -import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.plumelib.util.UtilPlume; +import java.util.Objects; + // The class type variables are called V1 and V2 so that T1 and T2 can be used for method type // variables. /** @@ -18,122 +19,123 @@ @Deprecated // 2023-06-02 // TODO: as class is immutable, use @Covariant annotation. public class Pair { - /** The first element of the pair. */ - public final V1 first; - - /** The second element of the pair. */ - public final V2 second; - - private Pair(V1 first, V2 second) { - this.first = first; - this.second = second; - } - - public static Pair of(T1 first, T2 second) { - return new Pair<>(first, second); - } - - // The typical way to make a copy is to first call super.clone() and then modify it. - // That implementation strategy does not work for Pair because its fields are final, so the - // clone and deepCopy() methods use of() instead. - - /** - * Returns a copy of this in which each element is a clone of the corresponding element of this. - * {@code clone()} may or may not itself make a deep copy of the elements. - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a copy of {@code orig}, with all elements cloned - */ - // This method is static so that the pair element types can be constrained to be Cloneable. - @SuppressWarnings("nullness") // generics problem with deepCopy() - public static Pair cloneElements( - Pair orig) { - - T1 oldFirst = orig.first; - T1 newFirst = oldFirst == null ? oldFirst : UtilPlume.clone(oldFirst); - T2 oldSecond = orig.second; - T2 newSecond = oldSecond == null ? oldSecond : UtilPlume.clone(oldSecond); - return of(newFirst, newSecond); - } - - /** - * Returns a deep copy of this: each element is a deep copy (according to the {@code DeepCopyable} - * interface) of the corresponding element of this. - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a deep copy of {@code orig} - */ - @SuppressWarnings("nullness") // generics problem with deepCopy() - // This method is static so that the pair element types can be constrained to be DeepCopyable. - public static , T2 extends DeepCopyable> Pair deepCopy( - Pair orig) { - return of(DeepCopyable.deepCopyOrNull(orig.first), DeepCopyable.deepCopyOrNull(orig.second)); - } - - /** - * Returns a copy, where the {@code first} element is deep: the {@code first} element is a deep - * copy (according to the {@code DeepCopyable} interface), and the {@code second} element is - * identical to the argument. - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a copy of {@code orig}, where the first element is a deep copy - */ - @SuppressWarnings("nullness") // generics problem with deepCopy() - public static , T2> Pair deepCopyFirst(Pair orig) { - return of(DeepCopyable.deepCopyOrNull(orig.first), orig.second); - } - - /** - * Returns a copy, where the {@code second} element is deep: the {@code first} element is - * identical to the argument, and the {@code second} element is a deep copy (according to the - * {@code DeepCopyable} interface). - * - * @param the type of the first element of the pair - * @param the type of the second element of the pair - * @param orig a pair - * @return a copy of {@code orig}, where the second element is a deep copy - */ - @SuppressWarnings("nullness") // generics problem with deepCopy() - public static > Pair deepCopySecond(Pair orig) { - return of(orig.first, DeepCopyable.deepCopyOrNull(orig.second)); - } - - @Override - @Pure - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; + /** The first element of the pair. */ + public final V1 first; + + /** The second element of the pair. */ + public final V2 second; + + private Pair(V1 first, V2 second) { + this.first = first; + this.second = second; + } + + public static Pair of(T1 first, T2 second) { + return new Pair<>(first, second); + } + + // The typical way to make a copy is to first call super.clone() and then modify it. + // That implementation strategy does not work for Pair because its fields are final, so the + // clone and deepCopy() methods use of() instead. + + /** + * Returns a copy of this in which each element is a clone of the corresponding element of this. + * {@code clone()} may or may not itself make a deep copy of the elements. + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a copy of {@code orig}, with all elements cloned + */ + // This method is static so that the pair element types can be constrained to be Cloneable. + @SuppressWarnings("nullness") // generics problem with deepCopy() + public static Pair cloneElements( + Pair orig) { + + T1 oldFirst = orig.first; + T1 newFirst = oldFirst == null ? oldFirst : UtilPlume.clone(oldFirst); + T2 oldSecond = orig.second; + T2 newSecond = oldSecond == null ? oldSecond : UtilPlume.clone(oldSecond); + return of(newFirst, newSecond); } - if (!(obj instanceof Pair)) { - return false; + + /** + * Returns a deep copy of this: each element is a deep copy (according to the {@code + * DeepCopyable} interface) of the corresponding element of this. + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a deep copy of {@code orig} + */ + @SuppressWarnings("nullness") // generics problem with deepCopy() + // This method is static so that the pair element types can be constrained to be DeepCopyable. + public static , T2 extends DeepCopyable> Pair deepCopy( + Pair orig) { + return of( + DeepCopyable.deepCopyOrNull(orig.first), DeepCopyable.deepCopyOrNull(orig.second)); + } + + /** + * Returns a copy, where the {@code first} element is deep: the {@code first} element is a deep + * copy (according to the {@code DeepCopyable} interface), and the {@code second} element is + * identical to the argument. + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a copy of {@code orig}, where the first element is a deep copy + */ + @SuppressWarnings("nullness") // generics problem with deepCopy() + public static , T2> Pair deepCopyFirst(Pair orig) { + return of(DeepCopyable.deepCopyOrNull(orig.first), orig.second); } - // generics are not checked at run time! - @SuppressWarnings("unchecked") - Pair other = (Pair) obj; - return Objects.equals(this.first, other.first) && Objects.equals(this.second, other.second); - } - - /** The cached hash code. -1 means it needs to be computed. */ - private int hashCode = -1; - - @Pure - @Override - public int hashCode() { - if (hashCode == -1) { - hashCode = Objects.hash(first, second); + + /** + * Returns a copy, where the {@code second} element is deep: the {@code first} element is + * identical to the argument, and the {@code second} element is a deep copy (according to the + * {@code DeepCopyable} interface). + * + * @param the type of the first element of the pair + * @param the type of the second element of the pair + * @param orig a pair + * @return a copy of {@code orig}, where the second element is a deep copy + */ + @SuppressWarnings("nullness") // generics problem with deepCopy() + public static > Pair deepCopySecond(Pair orig) { + return of(orig.first, DeepCopyable.deepCopyOrNull(orig.second)); + } + + @Override + @Pure + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Pair)) { + return false; + } + // generics are not checked at run time! + @SuppressWarnings("unchecked") + Pair other = (Pair) obj; + return Objects.equals(this.first, other.first) && Objects.equals(this.second, other.second); + } + + /** The cached hash code. -1 means it needs to be computed. */ + private int hashCode = -1; + + @Pure + @Override + public int hashCode() { + if (hashCode == -1) { + hashCode = Objects.hash(first, second); + } + return hashCode; + } + + @SideEffectFree + @Override + public String toString() { + return "Pair(" + first + ", " + second + ")"; } - return hashCode; - } - - @SideEffectFree - @Override - public String toString() { - return "Pair(" + first + ", " + second + ")"; - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java index 158b65116f9..6e840fe0323 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java @@ -21,11 +21,16 @@ import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; @@ -33,8 +38,6 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; /** A utility class to find symbols corresponding to string references (identifiers). */ // This class reflectively accesses jdk.compiler/com.sun.tools.javac.comp. @@ -42,508 +45,544 @@ // running the Checker Framework. If this class is re-written, then that --add-opens should be // removed. public class Resolver { - private final Resolve resolve; - private final Names names; - private final Trees trees; - private final Log log; - - private static final Method FIND_METHOD; - private static final Method FIND_VAR; - private static final Method FIND_IDENT; - private static final Method FIND_IDENT_IN_TYPE; - private static final Method FIND_IDENT_IN_PACKAGE; - private static final Method FIND_TYPE; - - private static final Class ACCESSERROR; - // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError - private static final Method ACCESSERROR_ACCESS; - - /** The latest source version supported by this compiler. */ - private static final int sourceVersionNumber = - Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); - - /** Whether we are running on at least Java 13. */ - private static final boolean atLeastJava13 = sourceVersionNumber >= 13; - - /** Whether we are running on at least Java 23. */ - private static final boolean atLeastJava23 = sourceVersionNumber >= 23; - - static { - try { - FIND_METHOD = - Resolve.class.getDeclaredMethod( - "findMethod", - Env.class, - Type.class, - Name.class, - List.class, - List.class, - boolean.class, - boolean.class); - FIND_METHOD.setAccessible(true); - - if (atLeastJava23) { - // Changed in - // https://github.com/openjdk/jdk/commit/e227c7e37d4de0656f013f3a936b1acfa56cc2e0 - FIND_VAR = - Resolve.class.getDeclaredMethod( - "findVar", DiagnosticPosition.class, Env.class, Name.class); - } else { - FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class); - } - FIND_VAR.setAccessible(true); - - if (atLeastJava13) { - FIND_IDENT = - Resolve.class.getDeclaredMethod( - "findIdent", DiagnosticPosition.class, Env.class, Name.class, KindSelector.class); - } else { - FIND_IDENT = - Resolve.class.getDeclaredMethod("findIdent", Env.class, Name.class, KindSelector.class); - } - FIND_IDENT.setAccessible(true); - - if (atLeastJava13) { - FIND_IDENT_IN_TYPE = - Resolve.class.getDeclaredMethod( - "findIdentInType", - DiagnosticPosition.class, - Env.class, - Type.class, - Name.class, - KindSelector.class); - } else { - FIND_IDENT_IN_TYPE = - Resolve.class.getDeclaredMethod( - "findIdentInType", Env.class, Type.class, Name.class, KindSelector.class); - } - FIND_IDENT_IN_TYPE.setAccessible(true); - - if (atLeastJava13) { - FIND_IDENT_IN_PACKAGE = - Resolve.class.getDeclaredMethod( - "findIdentInPackage", - DiagnosticPosition.class, - Env.class, - TypeSymbol.class, - Name.class, - KindSelector.class); - } else { - FIND_IDENT_IN_PACKAGE = - Resolve.class.getDeclaredMethod( - "findIdentInPackage", Env.class, TypeSymbol.class, Name.class, KindSelector.class); - } - FIND_IDENT_IN_PACKAGE.setAccessible(true); - - FIND_TYPE = Resolve.class.getDeclaredMethod("findType", Env.class, Name.class); - FIND_TYPE.setAccessible(true); - } catch (Exception e) { - Error err = - new AssertionError("Compiler 'Resolve' class doesn't contain required 'find' method"); - err.initCause(e); - throw err; - } + private final Resolve resolve; + private final Names names; + private final Trees trees; + private final Log log; + + private static final Method FIND_METHOD; + private static final Method FIND_VAR; + private static final Method FIND_IDENT; + private static final Method FIND_IDENT_IN_TYPE; + private static final Method FIND_IDENT_IN_PACKAGE; + private static final Method FIND_TYPE; + + private static final Class ACCESSERROR; + // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError + private static final Method ACCESSERROR_ACCESS; + + /** The latest source version supported by this compiler. */ + private static final int sourceVersionNumber = + Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); + + /** Whether we are running on at least Java 13. */ + private static final boolean atLeastJava13 = sourceVersionNumber >= 13; + + /** Whether we are running on at least Java 23. */ + private static final boolean atLeastJava23 = sourceVersionNumber >= 23; + + static { + try { + FIND_METHOD = + Resolve.class.getDeclaredMethod( + "findMethod", + Env.class, + Type.class, + Name.class, + List.class, + List.class, + boolean.class, + boolean.class); + FIND_METHOD.setAccessible(true); + + if (atLeastJava23) { + // Changed in + // https://github.com/openjdk/jdk/commit/e227c7e37d4de0656f013f3a936b1acfa56cc2e0 + FIND_VAR = + Resolve.class.getDeclaredMethod( + "findVar", DiagnosticPosition.class, Env.class, Name.class); + } else { + FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class); + } + FIND_VAR.setAccessible(true); + + if (atLeastJava13) { + FIND_IDENT = + Resolve.class.getDeclaredMethod( + "findIdent", + DiagnosticPosition.class, + Env.class, + Name.class, + KindSelector.class); + } else { + FIND_IDENT = + Resolve.class.getDeclaredMethod( + "findIdent", Env.class, Name.class, KindSelector.class); + } + FIND_IDENT.setAccessible(true); + + if (atLeastJava13) { + FIND_IDENT_IN_TYPE = + Resolve.class.getDeclaredMethod( + "findIdentInType", + DiagnosticPosition.class, + Env.class, + Type.class, + Name.class, + KindSelector.class); + } else { + FIND_IDENT_IN_TYPE = + Resolve.class.getDeclaredMethod( + "findIdentInType", + Env.class, + Type.class, + Name.class, + KindSelector.class); + } + FIND_IDENT_IN_TYPE.setAccessible(true); + + if (atLeastJava13) { + FIND_IDENT_IN_PACKAGE = + Resolve.class.getDeclaredMethod( + "findIdentInPackage", + DiagnosticPosition.class, + Env.class, + TypeSymbol.class, + Name.class, + KindSelector.class); + } else { + FIND_IDENT_IN_PACKAGE = + Resolve.class.getDeclaredMethod( + "findIdentInPackage", + Env.class, + TypeSymbol.class, + Name.class, + KindSelector.class); + } + FIND_IDENT_IN_PACKAGE.setAccessible(true); + + FIND_TYPE = Resolve.class.getDeclaredMethod("findType", Env.class, Name.class); + FIND_TYPE.setAccessible(true); + } catch (Exception e) { + Error err = + new AssertionError( + "Compiler 'Resolve' class doesn't contain required 'find' method"); + err.initCause(e); + throw err; + } - try { - ACCESSERROR = Class.forName("com.sun.tools.javac.comp.Resolve$AccessError"); - ACCESSERROR_ACCESS = ACCESSERROR.getMethod("access", Name.class, TypeSymbol.class); - ACCESSERROR_ACCESS.setAccessible(true); - } catch (ClassNotFoundException e) { - throw new BugInCF("Compiler 'Resolve$AccessError' class could not be retrieved.", e); - } catch (NoSuchMethodException e) { - throw new BugInCF( - "Compiler 'Resolve$AccessError' class doesn't contain required 'access' method", e); + try { + ACCESSERROR = Class.forName("com.sun.tools.javac.comp.Resolve$AccessError"); + ACCESSERROR_ACCESS = ACCESSERROR.getMethod("access", Name.class, TypeSymbol.class); + ACCESSERROR_ACCESS.setAccessible(true); + } catch (ClassNotFoundException e) { + throw new BugInCF("Compiler 'Resolve$AccessError' class could not be retrieved.", e); + } catch (NoSuchMethodException e) { + throw new BugInCF( + "Compiler 'Resolve$AccessError' class doesn't contain required 'access' method", + e); + } } - } - - public Resolver(ProcessingEnvironment env) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - this.resolve = Resolve.instance(context); - this.names = Names.instance(context); - this.trees = Trees.instance(env); - this.log = Log.instance(context); - } - - /** - * Determine the environment for the given path. - * - * @param path the tree path to the local scope - * @return the corresponding attribution environment - */ - public Env getEnvForPath(TreePath path) { - TreePath iter = path; - JavacScope scope = null; - while (scope == null && iter != null) { - try { - scope = (JavacScope) trees.getScope(iter); - } catch (NullPointerException t) { - // This statement fixes https://github.com/typetools/checker-framework/issues/1059 . - // It work around the crash by skipping through the TreePath until something doesn't - // crash. This probably returns the class scope, so users might not get the - // variables they expect. But that is better than crashing. - iter = iter.getParentPath(); - } + + public Resolver(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + this.resolve = Resolve.instance(context); + this.names = Names.instance(context); + this.trees = Trees.instance(env); + this.log = Log.instance(context); } - if (scope != null) { - return scope.getEnv(); - } else { - throw new BugInCF("Could not determine any possible scope for path: " + path.getLeaf()); + + /** + * Determine the environment for the given path. + * + * @param path the tree path to the local scope + * @return the corresponding attribution environment + */ + public Env getEnvForPath(TreePath path) { + TreePath iter = path; + JavacScope scope = null; + while (scope == null && iter != null) { + try { + scope = (JavacScope) trees.getScope(iter); + } catch (NullPointerException t) { + // This statement fixes https://github.com/typetools/checker-framework/issues/1059 . + // It work around the crash by skipping through the TreePath until something doesn't + // crash. This probably returns the class scope, so users might not get the + // variables they expect. But that is better than crashing. + iter = iter.getParentPath(); + } + } + if (scope != null) { + return scope.getEnv(); + } else { + throw new BugInCF("Could not determine any possible scope for path: " + path.getLeaf()); + } } - } - - /** - * Finds the package with name {@code name}. - * - * @param name the name of the package - * @param path the tree path to the local scope - * @return the {@code PackageSymbol} for the package if it is found, {@code null} otherwise - */ - public @Nullable PackageSymbol findPackage(String name, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - final Element res; - if (atLeastJava13) { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT, null, env, names.fromString(name), Kinds.KindSelector.PCK); - } else { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT, env, names.fromString(name), Kinds.KindSelector.PCK); - } - - // findIdent will return a PackageSymbol even for a symbol that is not a package, - // such as a.b.c.MyClass.myStaticField. "exists()" must be called on it to ensure - // that it exists. - if (res.getKind() == ElementKind.PACKAGE) { - PackageSymbol ps = (PackageSymbol) res; - return ps.exists() ? ps : null; - } else { - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); + + /** + * Finds the package with name {@code name}. + * + * @param name the name of the package + * @param path the tree path to the local scope + * @return the {@code PackageSymbol} for the package if it is found, {@code null} otherwise + */ + public @Nullable PackageSymbol findPackage(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + final Element res; + if (atLeastJava13) { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT, + null, + env, + names.fromString(name), + Kinds.KindSelector.PCK); + } else { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT, env, names.fromString(name), Kinds.KindSelector.PCK); + } + + // findIdent will return a PackageSymbol even for a symbol that is not a package, + // such as a.b.c.MyClass.myStaticField. "exists()" must be called on it to ensure + // that it exists. + if (res.getKind() == ElementKind.PACKAGE) { + PackageSymbol ps = (PackageSymbol) res; + return ps.exists() ? ps : null; + } else { + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } } - } - - /** - * Finds the field with name {@code name} in {@code type} or a superclass or superinterface of - * {@code type}. - * - *

                  The method adheres to all the rules of Java's scoping (while also considering the imports) - * for name resolution. - * - * @param name the name of the field - * @param type the type of the receiver (i.e., the type in which to look for the field) - * @param path the tree path to the local scope - * @return the element for the field, {@code null} otherwise - */ - public @Nullable VariableElement findField(String name, TypeMirror type, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - final Element res; - if (atLeastJava13) { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_TYPE, - null, - env, - type, - names.fromString(name), - Kinds.KindSelector.VAR); - } else { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_TYPE, env, type, names.fromString(name), Kinds.KindSelector.VAR); - } - - if (res.getKind().isField()) { - return (VariableElement) res; - } else if (res.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(res)) { - // Return the inaccessible field that was found - return (VariableElement) wrapInvocation(res, ACCESSERROR_ACCESS, null, null); - } else { - // Most likely didn't find the field and the Element is a SymbolNotFoundError - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); + + /** + * Finds the field with name {@code name} in {@code type} or a superclass or superinterface of + * {@code type}. + * + *

                  The method adheres to all the rules of Java's scoping (while also considering the imports) + * for name resolution. + * + * @param name the name of the field + * @param type the type of the receiver (i.e., the type in which to look for the field) + * @param path the tree path to the local scope + * @return the element for the field, {@code null} otherwise + */ + public @Nullable VariableElement findField(String name, TypeMirror type, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + final Element res; + if (atLeastJava13) { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_TYPE, + null, + env, + type, + names.fromString(name), + Kinds.KindSelector.VAR); + } else { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_TYPE, + env, + type, + names.fromString(name), + Kinds.KindSelector.VAR); + } + + if (res.getKind().isField()) { + return (VariableElement) res; + } else if (res.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(res)) { + // Return the inaccessible field that was found + return (VariableElement) wrapInvocation(res, ACCESSERROR_ACCESS, null, null); + } else { + // Most likely didn't find the field and the Element is a SymbolNotFoundError + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } } - } - - /** - * Finds the local variable (including formal parameters) with name {@code name} in the given - * scope. - * - * @param name the name of the local variable - * @param path the tree path to the local scope - * @return the element for the local variable, {@code null} otherwise - */ - public @Nullable VariableElement findLocalVariableOrParameter(String name, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - // Either a VariableElement or a SymbolNotFoundError. - Element res; - if (atLeastJava23) { - DiagnosticPosition pos = (DiagnosticPosition) path.getLeaf(); - res = wrapInvocationOnResolveInstance(FIND_VAR, pos, env, names.fromString(name)); - } else { - res = wrapInvocationOnResolveInstance(FIND_VAR, env, names.fromString(name)); - } - // Every kind in the documentation of Element.getKind() is explicitly tested, possibly - // in the "default:" case. - switch (res.getKind()) { - case EXCEPTION_PARAMETER: - case LOCAL_VARIABLE: - case PARAMETER: - case RESOURCE_VARIABLE: - return (VariableElement) res; - case ENUM_CONSTANT: - case FIELD: - return null; - default: - if (ElementUtils.isBindingVariable(res)) { - return (VariableElement) res; - } - if (res instanceof VariableElement) { - throw new BugInCF("unhandled variable ElementKind " + res.getKind()); - } - // The Element might be a SymbolNotFoundError. - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); + + /** + * Finds the local variable (including formal parameters) with name {@code name} in the given + * scope. + * + * @param name the name of the local variable + * @param path the tree path to the local scope + * @return the element for the local variable, {@code null} otherwise + */ + public @Nullable VariableElement findLocalVariableOrParameter(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + // Either a VariableElement or a SymbolNotFoundError. + Element res; + if (atLeastJava23) { + DiagnosticPosition pos = (DiagnosticPosition) path.getLeaf(); + res = wrapInvocationOnResolveInstance(FIND_VAR, pos, env, names.fromString(name)); + } else { + res = wrapInvocationOnResolveInstance(FIND_VAR, env, names.fromString(name)); + } + // Every kind in the documentation of Element.getKind() is explicitly tested, possibly + // in the "default:" case. + switch (res.getKind()) { + case EXCEPTION_PARAMETER: + case LOCAL_VARIABLE: + case PARAMETER: + case RESOURCE_VARIABLE: + return (VariableElement) res; + case ENUM_CONSTANT: + case FIELD: + return null; + default: + if (ElementUtils.isBindingVariable(res)) { + return (VariableElement) res; + } + if (res instanceof VariableElement) { + throw new BugInCF("unhandled variable ElementKind " + res.getKind()); + } + // The Element might be a SymbolNotFoundError. + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } } - } - - /** - * Finds the class literal with name {@code name}. - * - *

                  The method adheres to all the rules of Java's scoping (while also considering the imports) - * for name resolution. - * - * @param name the name of the class - * @param path the tree path to the local scope - * @return the element for the class - */ - public Element findClass(String name, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - return wrapInvocationOnResolveInstance(FIND_TYPE, env, names.fromString(name)); - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); + + /** + * Finds the class literal with name {@code name}. + * + *

                  The method adheres to all the rules of Java's scoping (while also considering the imports) + * for name resolution. + * + * @param name the name of the class + * @param path the tree path to the local scope + * @return the element for the class + */ + public Element findClass(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + return wrapInvocationOnResolveInstance(FIND_TYPE, env, names.fromString(name)); + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } } - } - - /** - * Finds the class with name {@code name} in a given package. - * - * @param name the name of the class - * @param pck the PackageSymbol for the package - * @param path the tree path to the local scope - * @return the {@code ClassSymbol} for the class if it is found, {@code null} otherwise - */ - public @Nullable ClassSymbol findClassInPackage(String name, PackageSymbol pck, TreePath path) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - final Element res; - if (atLeastJava13) { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_PACKAGE, - null, - env, - pck, - names.fromString(name), - Kinds.KindSelector.TYP); - } else { - res = - wrapInvocationOnResolveInstance( - FIND_IDENT_IN_PACKAGE, env, pck, names.fromString(name), Kinds.KindSelector.TYP); - } - - if (ElementUtils.isTypeElement(res)) { - return (ClassSymbol) res; - } else { - return null; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); + + /** + * Finds the class with name {@code name} in a given package. + * + * @param name the name of the class + * @param pck the PackageSymbol for the package + * @param path the tree path to the local scope + * @return the {@code ClassSymbol} for the class if it is found, {@code null} otherwise + */ + public @Nullable ClassSymbol findClassInPackage(String name, PackageSymbol pck, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + final Element res; + if (atLeastJava13) { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_PACKAGE, + null, + env, + pck, + names.fromString(name), + Kinds.KindSelector.TYP); + } else { + res = + wrapInvocationOnResolveInstance( + FIND_IDENT_IN_PACKAGE, + env, + pck, + names.fromString(name), + Kinds.KindSelector.TYP); + } + + if (ElementUtils.isTypeElement(res)) { + return (ClassSymbol) res; + } else { + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } } - } - - /** - * Finds the method element for a given name and list of expected parameter types. - * - *

                  The method adheres to all the rules of Java's scoping (while also considering the imports) - * for name resolution. - * - *

                  (This method takes into account autoboxing.) - * - *

                  This method is a wrapper around {@code com.sun.tools.javac.comp.Resolve.findMethod}. - * - * @param methodName name of the method to find - * @param receiverType type of the receiver of the method - * @param path tree path - * @param argumentTypes types of arguments passed to the method call - * @return the method element (if found) - */ - public @Nullable ExecutableElement findMethod( - String methodName, - TypeMirror receiverType, - TreePath path, - java.util.List argumentTypes) { - Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); - try { - Env env = getEnvForPath(path); - - Type site = (Type) receiverType; - Name name = names.fromString(methodName); - List argtypes = List.nil(); - for (TypeMirror a : argumentTypes) { - argtypes = argtypes.append((Type) a); - } - List typeargtypes = List.nil(); - boolean allowBoxing = true; - boolean useVarargs = false; - - try { - // For some reason we have to set our own method context, which is rather ugly. - // TODO: find a nicer way to do this. - Object methodContext = buildMethodContext(); - Object oldContext = getField(resolve, "currentResolutionContext"); - setField(resolve, "currentResolutionContext", methodContext); - Element resolveResult = - wrapInvocationOnResolveInstance( - FIND_METHOD, env, site, name, argtypes, typeargtypes, allowBoxing, useVarargs); - setField(resolve, "currentResolutionContext", oldContext); - ExecutableElement methodResult; - if (resolveResult.getKind() == ElementKind.METHOD - || resolveResult.getKind() == ElementKind.CONSTRUCTOR) { - methodResult = (ExecutableElement) resolveResult; - } else if (resolveResult.getKind() == ElementKind.OTHER - && ACCESSERROR.isInstance(resolveResult)) { - // Return the inaccessible method that was found. - methodResult = - (ExecutableElement) wrapInvocation(resolveResult, ACCESSERROR_ACCESS, null, null); - } else { - methodResult = null; + + /** + * Finds the method element for a given name and list of expected parameter types. + * + *

                  The method adheres to all the rules of Java's scoping (while also considering the imports) + * for name resolution. + * + *

                  (This method takes into account autoboxing.) + * + *

                  This method is a wrapper around {@code com.sun.tools.javac.comp.Resolve.findMethod}. + * + * @param methodName name of the method to find + * @param receiverType type of the receiver of the method + * @param path tree path + * @param argumentTypes types of arguments passed to the method call + * @return the method element (if found) + */ + public @Nullable ExecutableElement findMethod( + String methodName, + TypeMirror receiverType, + TreePath path, + java.util.List argumentTypes) { + Log.DiagnosticHandler discardDiagnosticHandler = new Log.DiscardDiagnosticHandler(log); + try { + Env env = getEnvForPath(path); + + Type site = (Type) receiverType; + Name name = names.fromString(methodName); + List argtypes = List.nil(); + for (TypeMirror a : argumentTypes) { + argtypes = argtypes.append((Type) a); + } + List typeargtypes = List.nil(); + boolean allowBoxing = true; + boolean useVarargs = false; + + try { + // For some reason we have to set our own method context, which is rather ugly. + // TODO: find a nicer way to do this. + Object methodContext = buildMethodContext(); + Object oldContext = getField(resolve, "currentResolutionContext"); + setField(resolve, "currentResolutionContext", methodContext); + Element resolveResult = + wrapInvocationOnResolveInstance( + FIND_METHOD, + env, + site, + name, + argtypes, + typeargtypes, + allowBoxing, + useVarargs); + setField(resolve, "currentResolutionContext", oldContext); + ExecutableElement methodResult; + if (resolveResult.getKind() == ElementKind.METHOD + || resolveResult.getKind() == ElementKind.CONSTRUCTOR) { + methodResult = (ExecutableElement) resolveResult; + } else if (resolveResult.getKind() == ElementKind.OTHER + && ACCESSERROR.isInstance(resolveResult)) { + // Return the inaccessible method that was found. + methodResult = + (ExecutableElement) + wrapInvocation(resolveResult, ACCESSERROR_ACCESS, null, null); + } else { + methodResult = null; + } + return methodResult; + } catch (Throwable t) { + Error err = + new AssertionError( + String.format( + "Unexpected reflection error in findMethod(%s, %s, ...," + + " %s)", + methodName, + receiverType, + // path + argumentTypes)); + err.initCause(t); + throw err; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); } - return methodResult; - } catch (Throwable t) { - Error err = - new AssertionError( - String.format( - "Unexpected reflection error in findMethod(%s, %s, ...," + " %s)", - methodName, - receiverType, - // path - argumentTypes)); - err.initCause(t); - throw err; - } - } finally { - log.popDiagnosticHandler(discardDiagnosticHandler); } - } - - /** - * Build an instance of {@code Resolve$MethodResolutionContext}. - * - * @return a MethodResolutionContext - * @throws ClassNotFoundException if there is trouble constructing the instance - * @throws InstantiationException if there is trouble constructing the instance - * @throws IllegalAccessException if there is trouble constructing the instance - * @throws InvocationTargetException if there is trouble constructing the instance - * @throws NoSuchFieldException if there is trouble constructing the instance - */ - protected Object buildMethodContext() - throws ClassNotFoundException, - InstantiationException, - IllegalAccessException, - InvocationTargetException, - NoSuchFieldException { - // Class is not accessible, instantiate reflectively. - Class methCtxClss = - Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext"); - Constructor constructor = methCtxClss.getDeclaredConstructors()[0]; - constructor.setAccessible(true); - Object methodContext = constructor.newInstance(resolve); - // we need to also initialize the fields attrMode and step - setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK); - @SuppressWarnings("rawtypes") - List phases = (List) getField(resolve, "methodResolutionSteps"); - assert phases != null : "@AssumeAssertion(nullness): assumption"; - setField(methodContext, "step", phases.get(1)); - return methodContext; - } - - /** - * Reflectively set a field. - * - * @param receiver the receiver in which to set the field - * @param fieldName name of field to set - * @param value new value for field - * @throws NoSuchFieldException if the field does not exist in the receiver - * @throws IllegalAccessException if the field is not accessible - */ - @SuppressWarnings({ - "nullness:argument.type.incompatible", - "interning:argument.type.incompatible" - }) // assume that the fields all accept null and uninterned values - private void setField(Object receiver, String fieldName, @Nullable Object value) - throws NoSuchFieldException, IllegalAccessException { - Field f = receiver.getClass().getDeclaredField(fieldName); - f.setAccessible(true); - f.set(receiver, value); - } - - /** Reflectively get the value of a field. */ - private @Nullable Object getField(Object receiver, String fieldName) - throws NoSuchFieldException, IllegalAccessException { - Field f = receiver.getClass().getDeclaredField(fieldName); - f.setAccessible(true); - return f.get(receiver); - } - - /** - * Wrap a method invocation on the {@code resolve} object. - * - * @param method the method to called - * @param args the arguments to the call - * @return the result of invoking the method on {@code resolve} (as the receiver) and the - * arguments - */ - private Symbol wrapInvocationOnResolveInstance(Method method, @Nullable Object... args) { - return wrapInvocation(resolve, method, args); - } - - /** - * Invoke a method reflectively. This is like {@code Method.invoke()}, but it throws no checked - * exceptions. - * - * @param receiver the receiver - * @param method the method to called - * @param args the arguments to the call - * @return the result of invoking the method on the receiver and arguments - */ - private Symbol wrapInvocation(Object receiver, Method method, @Nullable Object... args) { - try { - @SuppressWarnings("nullness") // assume arguments are OK - @NonNull Symbol res = (Symbol) method.invoke(receiver, args); - return res; - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new BugInCF( - e, - "Unexpected reflection error in wrapInvocation(%s, %s, %s)", - receiver, - method, - Arrays.toString(args)); + + /** + * Build an instance of {@code Resolve$MethodResolutionContext}. + * + * @return a MethodResolutionContext + * @throws ClassNotFoundException if there is trouble constructing the instance + * @throws InstantiationException if there is trouble constructing the instance + * @throws IllegalAccessException if there is trouble constructing the instance + * @throws InvocationTargetException if there is trouble constructing the instance + * @throws NoSuchFieldException if there is trouble constructing the instance + */ + protected Object buildMethodContext() + throws ClassNotFoundException, + InstantiationException, + IllegalAccessException, + InvocationTargetException, + NoSuchFieldException { + // Class is not accessible, instantiate reflectively. + Class methCtxClss = + Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext"); + Constructor constructor = methCtxClss.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + Object methodContext = constructor.newInstance(resolve); + // we need to also initialize the fields attrMode and step + setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK); + @SuppressWarnings("rawtypes") + List phases = (List) getField(resolve, "methodResolutionSteps"); + assert phases != null : "@AssumeAssertion(nullness): assumption"; + setField(methodContext, "step", phases.get(1)); + return methodContext; + } + + /** + * Reflectively set a field. + * + * @param receiver the receiver in which to set the field + * @param fieldName name of field to set + * @param value new value for field + * @throws NoSuchFieldException if the field does not exist in the receiver + * @throws IllegalAccessException if the field is not accessible + */ + @SuppressWarnings({ + "nullness:argument.type.incompatible", + "interning:argument.type.incompatible" + }) // assume that the fields all accept null and uninterned values + private void setField(Object receiver, String fieldName, @Nullable Object value) + throws NoSuchFieldException, IllegalAccessException { + Field f = receiver.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(receiver, value); + } + + /** Reflectively get the value of a field. */ + private @Nullable Object getField(Object receiver, String fieldName) + throws NoSuchFieldException, IllegalAccessException { + Field f = receiver.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(receiver); + } + + /** + * Wrap a method invocation on the {@code resolve} object. + * + * @param method the method to called + * @param args the arguments to the call + * @return the result of invoking the method on {@code resolve} (as the receiver) and the + * arguments + */ + private Symbol wrapInvocationOnResolveInstance(Method method, @Nullable Object... args) { + return wrapInvocation(resolve, method, args); + } + + /** + * Invoke a method reflectively. This is like {@code Method.invoke()}, but it throws no checked + * exceptions. + * + * @param receiver the receiver + * @param method the method to called + * @param args the arguments to the call + * @return the result of invoking the method on the receiver and arguments + */ + private Symbol wrapInvocation(Object receiver, Method method, @Nullable Object... args) { + try { + @SuppressWarnings("nullness") // assume arguments are OK + @NonNull Symbol res = (Symbol) method.invoke(receiver, args); + return res; + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new BugInCF( + e, + "Unexpected reflection error in wrapInvocation(%s, %s, %s)", + receiver, + method, + Arrays.toString(args)); + } } - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java b/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java index 29ed927630c..6ff077b7b70 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/SwitchExpressionScanner.java @@ -5,14 +5,16 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreeScanner; -import java.util.List; -import java.util.function.BiFunction; + import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; +import java.util.List; +import java.util.function.BiFunction; + /** * A class that visits each result expression of a switch expression and calls {@link * #visitSwitchResultExpression(ExpressionTree, Object)} on each result expression. The results of @@ -29,153 +31,158 @@ */ public abstract class SwitchExpressionScanner extends TreeScanner { - /** - * This method is called for each result expression of the switch expression passed in {@link - * #scanSwitchExpression(Tree, Object)}. - * - * @param resultExpressionTree a result expression of the switch expression currently being - * scanned - * @param p a parameter - * @return the result of visiting the result expression - */ - protected abstract R visitSwitchResultExpression(ExpressionTree resultExpressionTree, P p); - - /** - * This method combines the result of two calls to {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} or {@code null} and the result of one - * call to {@link #visitSwitchResultExpression(ExpressionTree, Object)}. - * - * @param r1 a possibly null result returned by {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} - * @param r2 a possibly null result returned by {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} - * @return the combination of {@code r1} and {@code r2} - */ - protected abstract R combineResults(@Nullable R r1, @Nullable R r2); - - /** - * Scans the given switch expression and calls {@link #visitSwitchResultExpression(ExpressionTree, - * Object)} on each result expression of the switch expression. {@link #combineResults(Object, - * Object)} is called to combine the results of visiting multiple switch result expressions. - * - * @param switchExpression a switch expression tree - * @param p the parameter to pass to {@link #visitSwitchResultExpression(ExpressionTree, Object)} - * @return the result of calling {@link #visitSwitchResultExpression(ExpressionTree, Object)} on - * each result expression of {@code switchExpression} and combining the results using {@link - * #combineResults(Object, Object)} - */ - public R scanSwitchExpression(Tree switchExpression, P p) { - // TODO: use JCP to add version-specific behavior - assert SystemUtil.jreVersion >= 14 - && switchExpression.getKind().name().equals("SWITCH_EXPRESSION"); - List caseTrees = SwitchExpressionUtils.getCases(switchExpression); - R result = null; - for (CaseTree caseTree : caseTrees) { - if (caseTree.getStatements() != null) { - // This case is a switch labeled statement group, so scan the statements for yield - // statements. - result = combineResults(result, yieldVisitor.scan(caseTree.getStatements(), p)); - } else { - @SuppressWarnings( - "nullness:assignment") // caseTree.getStatements() == null, so the case has - // a body. - @NonNull Tree body = CaseUtils.getBody(caseTree); - // This case is a switch rule, so its body is either an expression, block, or throw. - // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.28.2. - if (body.getKind() == Tree.Kind.BLOCK) { - // Scan for yield statements. - result = combineResults(result, yieldVisitor.scan(((BlockTree) body).getStatements(), p)); - } else if (body.getKind() != Tree.Kind.THROW) { - // The expression is the result expression. - ExpressionTree expressionTree = (ExpressionTree) body; - result = combineResults(result, visitSwitchResultExpression(expressionTree, p)); + /** + * This method is called for each result expression of the switch expression passed in {@link + * #scanSwitchExpression(Tree, Object)}. + * + * @param resultExpressionTree a result expression of the switch expression currently being + * scanned + * @param p a parameter + * @return the result of visiting the result expression + */ + protected abstract R visitSwitchResultExpression(ExpressionTree resultExpressionTree, P p); + + /** + * This method combines the result of two calls to {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} or {@code null} and the result of one + * call to {@link #visitSwitchResultExpression(ExpressionTree, Object)}. + * + * @param r1 a possibly null result returned by {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} + * @param r2 a possibly null result returned by {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} + * @return the combination of {@code r1} and {@code r2} + */ + protected abstract R combineResults(@Nullable R r1, @Nullable R r2); + + /** + * Scans the given switch expression and calls {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} on each result expression of the switch + * expression. {@link #combineResults(Object, Object)} is called to combine the results of + * visiting multiple switch result expressions. + * + * @param switchExpression a switch expression tree + * @param p the parameter to pass to {@link #visitSwitchResultExpression(ExpressionTree, + * Object)} + * @return the result of calling {@link #visitSwitchResultExpression(ExpressionTree, Object)} on + * each result expression of {@code switchExpression} and combining the results using {@link + * #combineResults(Object, Object)} + */ + public R scanSwitchExpression(Tree switchExpression, P p) { + // TODO: use JCP to add version-specific behavior + assert SystemUtil.jreVersion >= 14 + && switchExpression.getKind().name().equals("SWITCH_EXPRESSION"); + List caseTrees = SwitchExpressionUtils.getCases(switchExpression); + R result = null; + for (CaseTree caseTree : caseTrees) { + if (caseTree.getStatements() != null) { + // This case is a switch labeled statement group, so scan the statements for yield + // statements. + result = combineResults(result, yieldVisitor.scan(caseTree.getStatements(), p)); + } else { + @SuppressWarnings( + "nullness:assignment") // caseTree.getStatements() == null, so the case has + // a body. + @NonNull Tree body = CaseUtils.getBody(caseTree); + // This case is a switch rule, so its body is either an expression, block, or throw. + // See https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.28.2. + if (body.getKind() == Tree.Kind.BLOCK) { + // Scan for yield statements. + result = + combineResults( + result, + yieldVisitor.scan(((BlockTree) body).getStatements(), p)); + } else if (body.getKind() != Tree.Kind.THROW) { + // The expression is the result expression. + ExpressionTree expressionTree = (ExpressionTree) body; + result = combineResults(result, visitSwitchResultExpression(expressionTree, p)); + } + } } - } - } - @SuppressWarnings( - "nullness:assignment" // switch expressions must have at least one case that results - // in a value, so {@code result} must be nonnull. - ) - @NonNull R nonNullResult = result; - return nonNullResult; - } - - /** - * A scanner that visits all the yield trees in a given tree and calls {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. It - * does not descend into switch expressions. - */ - protected final YieldVisitor yieldVisitor = new YieldVisitor(); - - /** - * A scanner that visits all the yield trees in a given tree and calls {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. It - * does not descend into switch expressions. - */ - protected class YieldVisitor extends TreeScanner<@Nullable R, P> { - - // TODO: use JCP to add version-specific behavior - @Override - public @Nullable R scan(Tree tree, P p) { - if (tree == null) { - return null; - } - if (tree.getKind().name().equals("SWITCH_EXPRESSION")) { - // Don't scan nested switch expressions. - return null; - } else if (tree.getKind().name().equals("YIELD")) { - ExpressionTree value = YieldUtils.getValue(tree); - return visitSwitchResultExpression(value, p); - } - return super.scan(tree, p); + @SuppressWarnings( + "nullness:assignment" // switch expressions must have at least one case that results + // in a value, so {@code result} must be nonnull. + ) + @NonNull R nonNullResult = result; + return nonNullResult; } - @Override - public R reduce(R r1, R r2) { - return combineResults(r1, r2); + /** + * A scanner that visits all the yield trees in a given tree and calls {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. + * It does not descend into switch expressions. + */ + protected final YieldVisitor yieldVisitor = new YieldVisitor(); + + /** + * A scanner that visits all the yield trees in a given tree and calls {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} on the expression in the yield trees. + * It does not descend into switch expressions. + */ + protected class YieldVisitor extends TreeScanner<@Nullable R, P> { + + // TODO: use JCP to add version-specific behavior + @Override + public @Nullable R scan(Tree tree, P p) { + if (tree == null) { + return null; + } + if (tree.getKind().name().equals("SWITCH_EXPRESSION")) { + // Don't scan nested switch expressions. + return null; + } else if (tree.getKind().name().equals("YIELD")) { + ExpressionTree value = YieldUtils.getValue(tree); + return visitSwitchResultExpression(value, p); + } + return super.scan(tree, p); + } + + @Override + public R reduce(R r1, R r2) { + return combineResults(r1, r2); + } } - } - - /** - * An implementation of {@link SwitchExpressionScanner} that uses functions passed to the - * constructor for {@link #visitSwitchResultExpression(ExpressionTree, Object)} and {@link - * #combineResults(Object, Object)}. - * - * @param the type result of {@link #visitSwitchResultExpression(ExpressionTree, Object)} - * @param the type of the parameter to pass to {@link - * #visitSwitchResultExpression(ExpressionTree, Object)} - */ - public static class FunctionalSwitchExpressionScanner - extends SwitchExpressionScanner { - - /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ - private final BiFunction switchValueExpressionFunction; - - /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ - private final BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc; /** - * Creates a {@link FunctionalSwitchExpressionScanner} that uses the given functions. + * An implementation of {@link SwitchExpressionScanner} that uses functions passed to the + * constructor for {@link #visitSwitchResultExpression(ExpressionTree, Object)} and {@link + * #combineResults(Object, Object)}. * - * @param switchValueExpressionFunc the function called on each switch result expression - * @param combineResultFunc the function used to combine the result of multiple calls to {@code - * switchValueExpressionFunc} + * @param the type result of {@link #visitSwitchResultExpression(ExpressionTree, Object)} + * @param the type of the parameter to pass to {@link + * #visitSwitchResultExpression(ExpressionTree, Object)} */ - public FunctionalSwitchExpressionScanner( - BiFunction switchValueExpressionFunc, - BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc) { - this.switchValueExpressionFunction = switchValueExpressionFunc; - this.combineResultFunc = combineResultFunc; - } + public static class FunctionalSwitchExpressionScanner + extends SwitchExpressionScanner { - @Override - protected R1 visitSwitchResultExpression(ExpressionTree resultExpressionTree, P1 p1) { - return switchValueExpressionFunction.apply(resultExpressionTree, p1); - } + /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ + private final BiFunction switchValueExpressionFunction; - @Override - protected R1 combineResults(@Nullable R1 r1, @Nullable R1 r2) { - return combineResultFunc.apply(r1, r2); + /** The function to use for {@link #visitSwitchResultExpression(ExpressionTree, Object)}. */ + private final BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc; + + /** + * Creates a {@link FunctionalSwitchExpressionScanner} that uses the given functions. + * + * @param switchValueExpressionFunc the function called on each switch result expression + * @param combineResultFunc the function used to combine the result of multiple calls to + * {@code switchValueExpressionFunc} + */ + public FunctionalSwitchExpressionScanner( + BiFunction switchValueExpressionFunc, + BiFunction<@Nullable R1, @Nullable R1, R1> combineResultFunc) { + this.switchValueExpressionFunction = switchValueExpressionFunc; + this.combineResultFunc = combineResultFunc; + } + + @Override + protected R1 visitSwitchResultExpression(ExpressionTree resultExpressionTree, P1 p1) { + return switchValueExpressionFunction.apply(resultExpressionTree, p1); + } + + @Override + protected R1 combineResults(@Nullable R1 r1, @Nullable R1 r2) { + return combineResultFunc.apply(r1, r2); + } } - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java index 6477dc895d8..19e78a87a25 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java @@ -5,133 +5,140 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Options; + +import org.checkerframework.checker.nullness.qual.Nullable; + import java.io.File; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; + import javax.annotation.processing.ProcessingEnvironment; -import org.checkerframework.checker.nullness.qual.Nullable; /** This file contains basic utility functions. */ public class SystemUtil { - /** Do not instantiate. */ - private SystemUtil() { - throw new AssertionError("Class SystemUtil cannot be instantiated."); - } - - /** A splitter that splits on periods. The result contains no empty strings. */ - public static final Splitter DOT_SPLITTER = Splitter.on('.').omitEmptyStrings(); - - /** A splitter that splits on commas. The result contains no empty strings. */ - public static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings(); - - /** A splitter that splits on colons. The result contains no empty strings. */ - public static final Splitter COLON_SPLITTER = Splitter.on(':').omitEmptyStrings(); - - /** A splitter that splits on {@code File.pathSeparator}. The result contains no empty strings. */ - public static final Splitter PATH_SEPARATOR_SPLITTER = - Splitter.on(File.pathSeparator).omitEmptyStrings(); - - /** - * Like {@code System.getProperty}, but splits on the path separator and never returns null. - * - * @param propName a system property name - * @return the paths in the system property; may be an empty array - */ - public static final List getPathsProperty(String propName) { - String propValue = System.getProperty(propName); - if (propValue == null) { - return Collections.emptyList(); - } else { - return PATH_SEPARATOR_SPLITTER.splitToList(propValue); - } - } - - /** The major version number of the Java runtime (JRE), such as 8, 11, or 17. */ - public static final int jreVersion = getJreVersion(); - - // Keep in sync with BCELUtil.java (in the bcel-util project). - /** - * Returns the major version number from the "java.version" system property, such as 8, 11, or 17. - * - *

                  This is different from the version passed to the compiler via {@code --release}; use {@link - * #getReleaseValue(ProcessingEnvironment)} to get that version. - * - *

                  Two possible formats of the "java.version" system property are considered. Up to Java 8, - * from a version string like `1.8.whatever`, this method extracts 8. Since Java 9, from a version - * string like `11.0.1`, this method extracts 11. - * - *

                  Starting in Java 9, there is the int {@code Runtime.version().feature()}, but that does not - * exist on JDK 8. - * - *

                  External users should use field {@link #jreVersion} instead. - * - * @return the major version of the Java runtime - */ - private static int getJreVersion() { - String version = System.getProperty("java.version"); - - // Up to Java 8, from a version string like "1.8.whatever", extract "8". - if (version.startsWith("1.")) { - return Integer.parseInt(version.substring(2, 3)); + /** Do not instantiate. */ + private SystemUtil() { + throw new AssertionError("Class SystemUtil cannot be instantiated."); } - // Since Java 9, from a version string like "11.0.1" or "11-ea" or "11u25", extract "11". - // The format is described at http://openjdk.org/jeps/223 . - Pattern newVersionPattern = Pattern.compile("^(\\d+).*$"); - Matcher newVersionMatcher = newVersionPattern.matcher(version); - if (newVersionMatcher.matches()) { - String v = newVersionMatcher.group(1); - assert v != null : "@AssumeAssertion(nullness): inspection"; - return Integer.parseInt(v); + /** A splitter that splits on periods. The result contains no empty strings. */ + public static final Splitter DOT_SPLITTER = Splitter.on('.').omitEmptyStrings(); + + /** A splitter that splits on commas. The result contains no empty strings. */ + public static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings(); + + /** A splitter that splits on colons. The result contains no empty strings. */ + public static final Splitter COLON_SPLITTER = Splitter.on(':').omitEmptyStrings(); + + /** + * A splitter that splits on {@code File.pathSeparator}. The result contains no empty strings. + */ + public static final Splitter PATH_SEPARATOR_SPLITTER = + Splitter.on(File.pathSeparator).omitEmptyStrings(); + + /** + * Like {@code System.getProperty}, but splits on the path separator and never returns null. + * + * @param propName a system property name + * @return the paths in the system property; may be an empty array + */ + public static final List getPathsProperty(String propName) { + String propValue = System.getProperty(propName); + if (propValue == null) { + return Collections.emptyList(); + } else { + return PATH_SEPARATOR_SPLITTER.splitToList(propValue); + } } - throw new RuntimeException("Could not determine version from property java.version=" + version); - } - - /** - * Returns the release value passed to the compiler or null if release was not passed. - * - * @param env the ProcessingEnvironment - * @return the release value or null if none was passed - */ - public static @Nullable String getReleaseValue(ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - Options options = Options.instance(ctx); - return options.get(Option.RELEASE); - } - - /** - * Returns the pathname to the tools.jar file, or null if it does not exist. Returns null on Java - * 9 and later. - * - * @return the pathname to the tools.jar file, or null - */ - public static @Nullable String getToolsJar() { - - if (jreVersion > 8) { - return null; + /** The major version number of the Java runtime (JRE), such as 8, 11, or 17. */ + public static final int jreVersion = getJreVersion(); + + // Keep in sync with BCELUtil.java (in the bcel-util project). + /** + * Returns the major version number from the "java.version" system property, such as 8, 11, or + * 17. + * + *

                  This is different from the version passed to the compiler via {@code --release}; use + * {@link #getReleaseValue(ProcessingEnvironment)} to get that version. + * + *

                  Two possible formats of the "java.version" system property are considered. Up to Java 8, + * from a version string like `1.8.whatever`, this method extracts 8. Since Java 9, from a + * version string like `11.0.1`, this method extracts 11. + * + *

                  Starting in Java 9, there is the int {@code Runtime.version().feature()}, but that does + * not exist on JDK 8. + * + *

                  External users should use field {@link #jreVersion} instead. + * + * @return the major version of the Java runtime + */ + private static int getJreVersion() { + String version = System.getProperty("java.version"); + + // Up to Java 8, from a version string like "1.8.whatever", extract "8". + if (version.startsWith("1.")) { + return Integer.parseInt(version.substring(2, 3)); + } + + // Since Java 9, from a version string like "11.0.1" or "11-ea" or "11u25", extract "11". + // The format is described at http://openjdk.org/jeps/223 . + Pattern newVersionPattern = Pattern.compile("^(\\d+).*$"); + Matcher newVersionMatcher = newVersionPattern.matcher(version); + if (newVersionMatcher.matches()) { + String v = newVersionMatcher.group(1); + assert v != null : "@AssumeAssertion(nullness): inspection"; + return Integer.parseInt(v); + } + + throw new RuntimeException( + "Could not determine version from property java.version=" + version); } - String javaHome = System.getenv("JAVA_HOME"); - if (javaHome == null) { - String javaHomeProperty = System.getProperty("java.home"); - if (javaHomeProperty.endsWith(File.separator + "jre")) { - javaHome = javaHomeProperty.substring(javaHomeProperty.length() - 4); - } else { - // Could also determine the location of javac on the path... - throw new Error("Can't infer Java home; java.home=" + javaHomeProperty); - } + /** + * Returns the release value passed to the compiler or null if release was not passed. + * + * @param env the ProcessingEnvironment + * @return the release value or null if none was passed + */ + public static @Nullable String getReleaseValue(ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + Options options = Options.instance(ctx); + return options.get(Option.RELEASE); } - File toolsJarFile = new File(new File(javaHome, "lib"), "tools.jar"); - if (!toolsJarFile.exists()) { - throw new Error( - String.format( - "File does not exist: %s ; JAVA_HOME=%s ; java.home=%s", - toolsJarFile, javaHome, System.getProperty("java.home"))); + + /** + * Returns the pathname to the tools.jar file, or null if it does not exist. Returns null on + * Java 9 and later. + * + * @return the pathname to the tools.jar file, or null + */ + public static @Nullable String getToolsJar() { + + if (jreVersion > 8) { + return null; + } + + String javaHome = System.getenv("JAVA_HOME"); + if (javaHome == null) { + String javaHomeProperty = System.getProperty("java.home"); + if (javaHomeProperty.endsWith(File.separator + "jre")) { + javaHome = javaHomeProperty.substring(javaHomeProperty.length() - 4); + } else { + // Could also determine the location of javac on the path... + throw new Error("Can't infer Java home; java.home=" + javaHomeProperty); + } + } + File toolsJarFile = new File(new File(javaHome, "lib"), "tools.jar"); + if (!toolsJarFile.exists()) { + throw new Error( + String.format( + "File does not exist: %s ; JAVA_HOME=%s ; java.home=%s", + toolsJarFile, javaHome, System.getProperty("java.home"))); + } + return toolsJarFile.toString(); } - return toolsJarFile.toString(); - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java index 27e19dbadf3..dd9d4be28c8 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java @@ -9,15 +9,18 @@ import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; +import org.plumelib.util.IPair; + import java.util.EnumSet; import java.util.Iterator; import java.util.Set; import java.util.StringJoiner; + import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; -import org.plumelib.util.IPair; /** * Utility methods for obtaining or analyzing a javac {@code TreePath}. @@ -26,466 +29,469 @@ */ public final class TreePathUtil { - /** Do not instantiate; this class is a collection of static methods. */ - private TreePathUtil() { - throw new BugInCF("Class TreeUtils cannot be instantiated."); - } - - /// - /// Retrieving a path (from another path) - /// - - /** - * Gets path to the first (innermost) enclosing tree of the given kind. May return {@code path} - * itself. - * - * @param path the path defining the tree node - * @param kind the kind of the desired tree - * @return the path to the enclosing tree of the given type, {@code null} otherwise - */ - public static @Nullable TreePath pathTillOfKind(TreePath path, Tree.Kind kind) { - return pathTillOfKind(path, EnumSet.of(kind)); - } - - /** - * Gets path to the first (innermost) enclosing tree with any one of the given kinds. May return - * {@code path} itself. - * - * @param path the path defining the tree node - * @param kinds the set of kinds of the desired tree - * @return the path to the enclosing tree of the given type, {@code null} otherwise - */ - public static @Nullable TreePath pathTillOfKind(TreePath path, Set kinds) { - for (TreePath p = path; p != null; p = p.getParentPath()) { - if (kinds.contains(p.getLeaf().getKind())) { - return p; - } + /** Do not instantiate; this class is a collection of static methods. */ + private TreePathUtil() { + throw new BugInCF("Class TreeUtils cannot be instantiated."); } - return null; - } - - /** - * Gets path to the first (innermost) enclosing class tree, where class is defined by the {@link - * TreeUtils#classTreeKinds()} method. May return {@code path} itself. - * - * @param path the path defining the tree node - * @return the path to the enclosing class tree, {@code null} otherwise - */ - public static @Nullable TreePath pathTillClass(TreePath path) { - return pathTillOfKind(path, TreeUtils.classTreeKinds()); - } - - /** - * Gets path to the first (innermost) enclosing method tree. May return {@code path} itself. - * - * @param path the path defining the tree node - * @return the path to the enclosing class tree, {@code null} otherwise - */ - public static @Nullable TreePath pathTillMethod(TreePath path) { - return pathTillOfKind(path, Tree.Kind.METHOD); - } - - /// - /// Retrieving a tree (from a path) - /// - - /** - * Gets the first (innermost) enclosing tree in path, of the given kind. May return the leaf of - * {@code path} itself. - * - * @param path the path defining the tree node - * @param kind the kind of the desired tree - * @return the enclosing tree of the given type as given by the path, {@code null} otherwise - */ - public static @Nullable Tree enclosingOfKind(TreePath path, Tree.Kind kind) { - return enclosingOfKind(path, EnumSet.of(kind)); - } - - /** - * Gets the first (innermost) enclosing tree in path, with any one of the given kinds. May return - * the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @param kinds the set of kinds of the desired tree - * @return the enclosing tree of the given type as given by the path, {@code null} otherwise - */ - public static @Nullable Tree enclosingOfKind(TreePath path, Set kinds) { - TreePath p = pathTillOfKind(path, kinds); - return (p == null) ? null : p.getLeaf(); - } - - /** - * Gets the first (innermost) enclosing tree in path, of the given class. May return the leaf of - * {@code path} itself. - * - * @param the type of {@code treeClass} - * @param path the path defining the tree node - * @param treeClass the class of the desired tree - * @return the enclosing tree of the given type as given by the path, {@code null} otherwise - */ - public static @Nullable T enclosingOfClass(TreePath path, Class treeClass) { - TreePath p = path; - - while (p != null) { - Tree leaf = p.getLeaf(); - if (treeClass.isInstance(leaf)) { - return treeClass.cast(leaf); - } - p = p.getParentPath(); + + /// + /// Retrieving a path (from another path) + /// + + /** + * Gets path to the first (innermost) enclosing tree of the given kind. May return {@code path} + * itself. + * + * @param path the path defining the tree node + * @param kind the kind of the desired tree + * @return the path to the enclosing tree of the given type, {@code null} otherwise + */ + public static @Nullable TreePath pathTillOfKind(TreePath path, Tree.Kind kind) { + return pathTillOfKind(path, EnumSet.of(kind)); } - return null; - } - - /** - * Gets the path to nearest enclosing declaration (class, method, or variable) of the tree node - * defined by the given {@link TreePath}. May return the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return path to the nearest enclosing class/method/variable in the path, or {@code null} if one - * does not exist - */ - public static @Nullable TreePath enclosingDeclarationPath(TreePath path) { - return pathTillOfKind(path, TreeUtils.declarationTreeKinds()); - } - - /** - * Gets the enclosing class of the tree node defined by the given {@link TreePath}. It returns a - * {@link Tree}, from which {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be - * obtained. May return the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return the enclosing class (or interface) as given by the path, or {@code null} if one does - * not exist - */ - public static @Nullable ClassTree enclosingClass(TreePath path) { - return (ClassTree) enclosingOfKind(path, TreeUtils.classTreeKinds()); - } - - /** - * Gets the enclosing variable of a tree node defined by the given {@link TreePath}. May return - * the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return the enclosing variable as given by the path, or {@code null} if one does not exist - */ - public static @Nullable VariableTree enclosingVariable(TreePath path) { - return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE); - } - - /** - * Gets the enclosing method of the tree node defined by the given {@link TreePath}. It returns a - * {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can - * be obtained. May return the leaf of {@code path} itself. - * - *

                  Also see {@code AnnotatedTypeFactory#getEnclosingMethod} and {@code - * AnnotatedTypeFactory#getEnclosingClassOrMethod}, which do not require a TreePath. - * - * @param path the path defining the tree node - * @return the enclosing method as given by the path, or {@code null} if one does not exist - */ - public static @Nullable MethodTree enclosingMethod(TreePath path) { - return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD); - } - - /** - * Gets the enclosing method or lambda expression of the tree node defined by the given {@link - * TreePath}. It returns a {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} - * or {@link Element} can be obtained. May return the leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return the enclosing method or lambda as given by the path, or {@code null} if one does not - * exist - */ - public static @Nullable Tree enclosingMethodOrLambda(TreePath path) { - return enclosingOfKind(path, EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); - } - - /** - * Returns the top-level block that encloses the given path, or null if none does. Never returns - * the leaf of {@code path} itself. - * - * @param path a path - * @return the top-level block that encloses the given path, or null if none does - */ - public static @Nullable BlockTree enclosingTopLevelBlock(TreePath path) { - TreePath parentPath = path.getParentPath(); - while (parentPath != null - && !TreeUtils.classTreeKinds().contains(parentPath.getLeaf().getKind())) { - path = parentPath; - parentPath = parentPath.getParentPath(); + /** + * Gets path to the first (innermost) enclosing tree with any one of the given kinds. May return + * {@code path} itself. + * + * @param path the path defining the tree node + * @param kinds the set of kinds of the desired tree + * @return the path to the enclosing tree of the given type, {@code null} otherwise + */ + public static @Nullable TreePath pathTillOfKind(TreePath path, Set kinds) { + for (TreePath p = path; p != null; p = p.getParentPath()) { + if (kinds.contains(p.getLeaf().getKind())) { + return p; + } + } + return null; } - if (path.getLeaf().getKind() == Tree.Kind.BLOCK) { - return (BlockTree) path.getLeaf(); + + /** + * Gets path to the first (innermost) enclosing class tree, where class is defined by the {@link + * TreeUtils#classTreeKinds()} method. May return {@code path} itself. + * + * @param path the path defining the tree node + * @return the path to the enclosing class tree, {@code null} otherwise + */ + public static @Nullable TreePath pathTillClass(TreePath path) { + return pathTillOfKind(path, TreeUtils.classTreeKinds()); } - return null; - } - - /** - * Gets the first (innermost) enclosing tree in path, that is not a parenthesis. Never returns the - * leaf of {@code path} itself. - * - * @param path the path defining the tree node - * @return a pair of a non-parenthesis tree that contains the argument, and its child that is the - * argument or is a parenthesized version of it - */ - public static IPair enclosingNonParen(TreePath path) { - TreePath parentPath = path.getParentPath(); - Tree enclosing = parentPath.getLeaf(); - Tree enclosingChild = path.getLeaf(); - while (enclosing.getKind() == Tree.Kind.PARENTHESIZED) { - parentPath = parentPath.getParentPath(); - enclosingChild = enclosing; - enclosing = parentPath.getLeaf(); + + /** + * Gets path to the first (innermost) enclosing method tree. May return {@code path} itself. + * + * @param path the path defining the tree node + * @return the path to the enclosing class tree, {@code null} otherwise + */ + public static @Nullable TreePath pathTillMethod(TreePath path) { + return pathTillOfKind(path, Tree.Kind.METHOD); } - return IPair.of(enclosing, enclosingChild); - } - - /** - * Returns the tree representing the context for the poly expression which is the leaf of {@code - * treePath}. The context then can be used to find the target type of the poly expression. Returns - * null if the leaf of {@code treePath} is not a poly expression. - * - * @param treePath a path. If the leaf of the path is a poly expression, then its context is - * returned. - * @return the tree representing the context for the poly expression which is the leaf of {@code - * treePath}; or null if the leaf is not a poly expression - */ - public static @Nullable Tree getContextForPolyExpression(TreePath treePath) { - // If a lambda or a method reference is the expression in a type cast, then the type cast is - // the context. If a method or constructor invocation is the expression in a type cast, - // then the invocation has no context. - boolean isLambdaOrMethodRef = - treePath.getLeaf().getKind() == Kind.LAMBDA_EXPRESSION - || treePath.getLeaf().getKind() == Kind.MEMBER_REFERENCE; - return getContextForPolyExpression(treePath, isLambdaOrMethodRef); - } - - /** - * Implementation of {@link #getContextForPolyExpression(TreePath)}. - * - * @param treePath a path - * @param isLambdaOrMethodRef if the call is getting the context of a lambda or method reference - * @return the assignment context as described, {@code null} otherwise - */ - private static @Nullable Tree getContextForPolyExpression( - TreePath treePath, boolean isLambdaOrMethodRef) { - TreePath parentPath = treePath.getParentPath(); - - if (parentPath == null) { - return null; + + /// + /// Retrieving a tree (from a path) + /// + + /** + * Gets the first (innermost) enclosing tree in path, of the given kind. May return the leaf of + * {@code path} itself. + * + * @param path the path defining the tree node + * @param kind the kind of the desired tree + * @return the enclosing tree of the given type as given by the path, {@code null} otherwise + */ + public static @Nullable Tree enclosingOfKind(TreePath path, Tree.Kind kind) { + return enclosingOfKind(path, EnumSet.of(kind)); } - Tree parent = parentPath.getLeaf(); - switch (parent.getKind()) { - case ASSIGNMENT: // See below for CompoundAssignmentTree. - case LAMBDA_EXPRESSION: - case METHOD_INVOCATION: - case NEW_ARRAY: - case NEW_CLASS: - case RETURN: - return parent; - case TYPE_CAST: - if (isLambdaOrMethodRef) { - return parent; - } else { - return null; - } - case VARIABLE: - if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) parent)) { - return null; - } - return parent; - case CONDITIONAL_EXPRESSION: - ConditionalExpressionTree cet = (ConditionalExpressionTree) parent; - @SuppressWarnings("interning:not.interned") // AST node comparison - boolean conditionIsLeaf = (cet.getCondition() == treePath.getLeaf()); - if (conditionIsLeaf) { - // The assignment context for the condition is simply boolean. - // No point in going on. - return null; - } - // Otherwise use the context of the ConditionalExpressionTree. - return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); - case PARENTHESIZED: - return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); - default: - if (TreeUtils.isYield(parent)) { - // A yield statement is only legal within a switch expression. Walk up the path - // to the case tree instead of the switch expression tree so the code remains - // backward compatible. - TreePath pathToCase = pathTillOfKind(parentPath, Kind.CASE); - assert pathToCase != null - : "@AssumeAssertion(nullness): yield statements must be enclosed in a CaseTree"; - parentPath = pathToCase.getParentPath(); - parent = parentPath.getLeaf(); - } - if (TreeUtils.isSwitchExpression(parent)) { - @SuppressWarnings("interning:not.interned") // AST node comparison - boolean switchIsLeaf = SwitchExpressionUtils.getExpression(parent) == treePath.getLeaf(); - if (switchIsLeaf) { - // The assignment context for the switch selector expression is simply - // boolean. - // No point in going on. - return null; - } - // Otherwise use the context of the ConditionalExpressionTree. - return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); - } - // 11 Tree.Kinds are CompoundAssignmentTrees, - // so use instanceof rather than listing all 11. - if (parent instanceof CompoundAssignmentTree) { - return parent; + /** + * Gets the first (innermost) enclosing tree in path, with any one of the given kinds. May + * return the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @param kinds the set of kinds of the desired tree + * @return the enclosing tree of the given type as given by the path, {@code null} otherwise + */ + public static @Nullable Tree enclosingOfKind(TreePath path, Set kinds) { + TreePath p = pathTillOfKind(path, kinds); + return (p == null) ? null : p.getLeaf(); + } + + /** + * Gets the first (innermost) enclosing tree in path, of the given class. May return the leaf of + * {@code path} itself. + * + * @param the type of {@code treeClass} + * @param path the path defining the tree node + * @param treeClass the class of the desired tree + * @return the enclosing tree of the given type as given by the path, {@code null} otherwise + */ + public static @Nullable T enclosingOfClass(TreePath path, Class treeClass) { + TreePath p = path; + + while (p != null) { + Tree leaf = p.getLeaf(); + if (treeClass.isInstance(leaf)) { + return treeClass.cast(leaf); + } + p = p.getParentPath(); } + return null; } - } - - /// - /// Predicates - /// - - /** - * Returns true if the tree is in a constructor or an initializer block. - * - * @param path the path to test - * @return true if the path is in a constructor or an initializer block - */ - public static boolean inConstructor(TreePath path) { - MethodTree method = enclosingMethod(path); - // If method is null, this is an initializer block. - return method == null || TreeUtils.isConstructor(method); - } - - /** - * Returns true if the leaf of the tree path is in a static scope. - * - * @param path a TreePath whose leaf may or may not be in static scope - * @return true if the leaf of the tree path is in a static scope - */ - public static boolean isTreeInStaticScope(TreePath path) { - MethodTree enclosingMethod = enclosingMethod(path); - - if (enclosingMethod != null) { - return enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC); + + /** + * Gets the path to nearest enclosing declaration (class, method, or variable) of the tree node + * defined by the given {@link TreePath}. May return the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return path to the nearest enclosing class/method/variable in the path, or {@code null} if + * one does not exist + */ + public static @Nullable TreePath enclosingDeclarationPath(TreePath path) { + return pathTillOfKind(path, TreeUtils.declarationTreeKinds()); } - // no enclosing method, check for static or initializer block - BlockTree block = enclosingTopLevelBlock(path); - if (block != null) { - return block.isStatic(); + + /** + * Gets the enclosing class of the tree node defined by the given {@link TreePath}. It returns a + * {@link Tree}, from which {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be + * obtained. May return the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return the enclosing class (or interface) as given by the path, or {@code null} if one does + * not exist + */ + public static @Nullable ClassTree enclosingClass(TreePath path) { + return (ClassTree) enclosingOfKind(path, TreeUtils.classTreeKinds()); } - // check if it's in a variable initializer - Tree t = enclosingVariable(path); - if (t != null) { - return ((VariableTree) t).getModifiers().getFlags().contains(Modifier.STATIC); + /** + * Gets the enclosing variable of a tree node defined by the given {@link TreePath}. May return + * the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return the enclosing variable as given by the path, or {@code null} if one does not exist + */ + public static @Nullable VariableTree enclosingVariable(TreePath path) { + return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE); } - ClassTree classTree = enclosingClass(path); - if (classTree != null) { - return classTree.getModifiers().getFlags().contains(Modifier.STATIC); + + /** + * Gets the enclosing method of the tree node defined by the given {@link TreePath}. It returns + * a {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} or {@link Element} + * can be obtained. May return the leaf of {@code path} itself. + * + *

                  Also see {@code AnnotatedTypeFactory#getEnclosingMethod} and {@code + * AnnotatedTypeFactory#getEnclosingClassOrMethod}, which do not require a TreePath. + * + * @param path the path defining the tree node + * @return the enclosing method as given by the path, or {@code null} if one does not exist + */ + public static @Nullable MethodTree enclosingMethod(TreePath path) { + return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD); } - return false; - } - - /** - * Returns true if the path is to a top-level (not within a loop) assignment within an initializer - * block. The initializer block might be instance or static. Will return true for a re-assignment - * even if there is another initialization (within this initializer block, another initializer - * block, a constructor, or the variable declaration). - * - * @param path the path to test - * @return true if the path is to an initialization within an initializer block - */ - public static boolean isTopLevelAssignmentInInitializerBlock(TreePath path) { - TreePath origPath = path; - if (path.getLeaf().getKind() != Tree.Kind.ASSIGNMENT) { - return false; + + /** + * Gets the enclosing method or lambda expression of the tree node defined by the given {@link + * TreePath}. It returns a {@link Tree}, from which an {@code + * checkers.types.AnnotatedTypeMirror} or {@link Element} can be obtained. May return the leaf + * of {@code path} itself. + * + * @param path the path defining the tree node + * @return the enclosing method or lambda as given by the path, or {@code null} if one does not + * exist + */ + public static @Nullable Tree enclosingMethodOrLambda(TreePath path) { + return enclosingOfKind(path, EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)); } - path = path.getParentPath(); - if (path.getLeaf().getKind() != Tree.Kind.EXPRESSION_STATEMENT) { - return false; + + /** + * Returns the top-level block that encloses the given path, or null if none does. Never returns + * the leaf of {@code path} itself. + * + * @param path a path + * @return the top-level block that encloses the given path, or null if none does + */ + public static @Nullable BlockTree enclosingTopLevelBlock(TreePath path) { + TreePath parentPath = path.getParentPath(); + while (parentPath != null + && !TreeUtils.classTreeKinds().contains(parentPath.getLeaf().getKind())) { + path = parentPath; + parentPath = parentPath.getParentPath(); + } + if (path.getLeaf().getKind() == Tree.Kind.BLOCK) { + return (BlockTree) path.getLeaf(); + } + return null; } - Tree prevLeaf = path.getLeaf(); - path = path.getParentPath(); - - for (Iterator itor = path.iterator(); itor.hasNext(); ) { - Tree leaf = itor.next(); - switch (leaf.getKind()) { - case CLASS: - case ENUM: - case PARAMETERIZED_TYPE: - return prevLeaf.getKind() == Tree.Kind.BLOCK; - - case COMPILATION_UNIT: - throw new BugInCF("found COMPILATION_UNIT in " + toString(origPath)); - - case DO_WHILE_LOOP: - case ENHANCED_FOR_LOOP: - case FOR_LOOP: - case LAMBDA_EXPRESSION: - case METHOD: - return false; - - default: - prevLeaf = leaf; - } + + /** + * Gets the first (innermost) enclosing tree in path, that is not a parenthesis. Never returns + * the leaf of {@code path} itself. + * + * @param path the path defining the tree node + * @return a pair of a non-parenthesis tree that contains the argument, and its child that is + * the argument or is a parenthesized version of it + */ + public static IPair enclosingNonParen(TreePath path) { + TreePath parentPath = path.getParentPath(); + Tree enclosing = parentPath.getLeaf(); + Tree enclosingChild = path.getLeaf(); + while (enclosing.getKind() == Tree.Kind.PARENTHESIZED) { + parentPath = parentPath.getParentPath(); + enclosingChild = enclosing; + enclosing = parentPath.getLeaf(); + } + return IPair.of(enclosing, enclosingChild); + } + + /** + * Returns the tree representing the context for the poly expression which is the leaf of {@code + * treePath}. The context then can be used to find the target type of the poly expression. + * Returns null if the leaf of {@code treePath} is not a poly expression. + * + * @param treePath a path. If the leaf of the path is a poly expression, then its context is + * returned. + * @return the tree representing the context for the poly expression which is the leaf of {@code + * treePath}; or null if the leaf is not a poly expression + */ + public static @Nullable Tree getContextForPolyExpression(TreePath treePath) { + // If a lambda or a method reference is the expression in a type cast, then the type cast is + // the context. If a method or constructor invocation is the expression in a type cast, + // then the invocation has no context. + boolean isLambdaOrMethodRef = + treePath.getLeaf().getKind() == Kind.LAMBDA_EXPRESSION + || treePath.getLeaf().getKind() == Kind.MEMBER_REFERENCE; + return getContextForPolyExpression(treePath, isLambdaOrMethodRef); } - throw new BugInCF("path did not contain method or class: " + toString(origPath)); - } - - /// - /// Formatting - /// - - /** - * Return a printed representation of a TreePath. - * - * @param path a TreePath - * @return a printed representation of the given TreePath - */ - public static String toString(TreePath path) { - StringJoiner result = new StringJoiner(System.lineSeparator() + " "); - result.add("TreePath:"); - for (Tree t : path) { - result.add(TreeUtils.toStringTruncated(t, 65) + " " + t.getKind()); + + /** + * Implementation of {@link #getContextForPolyExpression(TreePath)}. + * + * @param treePath a path + * @param isLambdaOrMethodRef if the call is getting the context of a lambda or method reference + * @return the assignment context as described, {@code null} otherwise + */ + private static @Nullable Tree getContextForPolyExpression( + TreePath treePath, boolean isLambdaOrMethodRef) { + TreePath parentPath = treePath.getParentPath(); + + if (parentPath == null) { + return null; + } + + Tree parent = parentPath.getLeaf(); + switch (parent.getKind()) { + case ASSIGNMENT: // See below for CompoundAssignmentTree. + case LAMBDA_EXPRESSION: + case METHOD_INVOCATION: + case NEW_ARRAY: + case NEW_CLASS: + case RETURN: + return parent; + case TYPE_CAST: + if (isLambdaOrMethodRef) { + return parent; + } else { + return null; + } + case VARIABLE: + if (TreeUtils.isVariableTreeDeclaredUsingVar((VariableTree) parent)) { + return null; + } + return parent; + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree cet = (ConditionalExpressionTree) parent; + @SuppressWarnings("interning:not.interned") // AST node comparison + boolean conditionIsLeaf = (cet.getCondition() == treePath.getLeaf()); + if (conditionIsLeaf) { + // The assignment context for the condition is simply boolean. + // No point in going on. + return null; + } + // Otherwise use the context of the ConditionalExpressionTree. + return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); + case PARENTHESIZED: + return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); + default: + if (TreeUtils.isYield(parent)) { + // A yield statement is only legal within a switch expression. Walk up the path + // to the case tree instead of the switch expression tree so the code remains + // backward compatible. + TreePath pathToCase = pathTillOfKind(parentPath, Kind.CASE); + assert pathToCase != null + : "@AssumeAssertion(nullness): yield statements must be enclosed in a CaseTree"; + parentPath = pathToCase.getParentPath(); + parent = parentPath.getLeaf(); + } + if (TreeUtils.isSwitchExpression(parent)) { + @SuppressWarnings("interning:not.interned") // AST node comparison + boolean switchIsLeaf = + SwitchExpressionUtils.getExpression(parent) == treePath.getLeaf(); + if (switchIsLeaf) { + // The assignment context for the switch selector expression is simply + // boolean. + // No point in going on. + return null; + } + // Otherwise use the context of the ConditionalExpressionTree. + return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); + } + // 11 Tree.Kinds are CompoundAssignmentTrees, + // so use instanceof rather than listing all 11. + if (parent instanceof CompoundAssignmentTree) { + return parent; + } + return null; + } } - return result.toString(); - } - - /** - * Returns a string representation of the leaf of the given path, using {@link - * TreeUtils#toStringTruncated}. - * - * @param path a path - * @param length the maximum length for the result; must be at least 6 - * @return a one-line string representation of the leaf of the given path that is no longer than - * {@code length} characters long - */ - public static String leafToStringTruncated(@Nullable TreePath path, int length) { - if (path == null) { - return "null"; + + /// + /// Predicates + /// + + /** + * Returns true if the tree is in a constructor or an initializer block. + * + * @param path the path to test + * @return true if the path is in a constructor or an initializer block + */ + public static boolean inConstructor(TreePath path) { + MethodTree method = enclosingMethod(path); + // If method is null, this is an initializer block. + return method == null || TreeUtils.isConstructor(method); } - return TreeUtils.toStringTruncated(path.getLeaf(), length); - } - - /** - * Retrieves the nearest enclosing method or class element for the specified path in the AST. This - * utility method prioritizes method elements over class elements. It returns the element of the - * closest method scope if available; otherwise, it defaults to the enclosing class scope. - * - * @param path the {@link TreePath} to analyze for the nearest enclosing scope. - * @return the {@link Element} of the nearest enclosing method or class, or {@code null} if no - * such enclosing element can be found. - */ - public static @Nullable Element findNearestEnclosingElement(TreePath path) { - MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(path); - if (enclosingMethodTree != null) { - return TreeUtils.elementFromDeclaration(enclosingMethodTree); + + /** + * Returns true if the leaf of the tree path is in a static scope. + * + * @param path a TreePath whose leaf may or may not be in static scope + * @return true if the leaf of the tree path is in a static scope + */ + public static boolean isTreeInStaticScope(TreePath path) { + MethodTree enclosingMethod = enclosingMethod(path); + + if (enclosingMethod != null) { + return enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC); + } + // no enclosing method, check for static or initializer block + BlockTree block = enclosingTopLevelBlock(path); + if (block != null) { + return block.isStatic(); + } + + // check if it's in a variable initializer + Tree t = enclosingVariable(path); + if (t != null) { + return ((VariableTree) t).getModifiers().getFlags().contains(Modifier.STATIC); + } + ClassTree classTree = enclosingClass(path); + if (classTree != null) { + return classTree.getModifiers().getFlags().contains(Modifier.STATIC); + } + return false; } - ClassTree enclosingClassTree = TreePathUtil.enclosingClass(path); - if (enclosingClassTree != null) { - return TreeUtils.elementFromDeclaration(enclosingClassTree); + + /** + * Returns true if the path is to a top-level (not within a loop) assignment within an + * initializer block. The initializer block might be instance or static. Will return true for a + * re-assignment even if there is another initialization (within this initializer block, another + * initializer block, a constructor, or the variable declaration). + * + * @param path the path to test + * @return true if the path is to an initialization within an initializer block + */ + public static boolean isTopLevelAssignmentInInitializerBlock(TreePath path) { + TreePath origPath = path; + if (path.getLeaf().getKind() != Tree.Kind.ASSIGNMENT) { + return false; + } + path = path.getParentPath(); + if (path.getLeaf().getKind() != Tree.Kind.EXPRESSION_STATEMENT) { + return false; + } + Tree prevLeaf = path.getLeaf(); + path = path.getParentPath(); + + for (Iterator itor = path.iterator(); itor.hasNext(); ) { + Tree leaf = itor.next(); + switch (leaf.getKind()) { + case CLASS: + case ENUM: + case PARAMETERIZED_TYPE: + return prevLeaf.getKind() == Tree.Kind.BLOCK; + + case COMPILATION_UNIT: + throw new BugInCF("found COMPILATION_UNIT in " + toString(origPath)); + + case DO_WHILE_LOOP: + case ENHANCED_FOR_LOOP: + case FOR_LOOP: + case LAMBDA_EXPRESSION: + case METHOD: + return false; + + default: + prevLeaf = leaf; + } + } + throw new BugInCF("path did not contain method or class: " + toString(origPath)); + } + + /// + /// Formatting + /// + + /** + * Return a printed representation of a TreePath. + * + * @param path a TreePath + * @return a printed representation of the given TreePath + */ + public static String toString(TreePath path) { + StringJoiner result = new StringJoiner(System.lineSeparator() + " "); + result.add("TreePath:"); + for (Tree t : path) { + result.add(TreeUtils.toStringTruncated(t, 65) + " " + t.getKind()); + } + return result.toString(); + } + + /** + * Returns a string representation of the leaf of the given path, using {@link + * TreeUtils#toStringTruncated}. + * + * @param path a path + * @param length the maximum length for the result; must be at least 6 + * @return a one-line string representation of the leaf of the given path that is no longer than + * {@code length} characters long + */ + public static String leafToStringTruncated(@Nullable TreePath path, int length) { + if (path == null) { + return "null"; + } + return TreeUtils.toStringTruncated(path.getLeaf(), length); + } + + /** + * Retrieves the nearest enclosing method or class element for the specified path in the AST. + * This utility method prioritizes method elements over class elements. It returns the element + * of the closest method scope if available; otherwise, it defaults to the enclosing class + * scope. + * + * @param path the {@link TreePath} to analyze for the nearest enclosing scope. + * @return the {@link Element} of the nearest enclosing method or class, or {@code null} if no + * such enclosing element can be found. + */ + public static @Nullable Element findNearestEnclosingElement(TreePath path) { + MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(path); + if (enclosingMethodTree != null) { + return TreeUtils.elementFromDeclaration(enclosingMethodTree); + } + ClassTree enclosingClassTree = TreePathUtil.enclosingClass(path); + if (enclosingClassTree != null) { + return TreeUtils.elementFromDeclaration(enclosingClassTree); + } + return null; } - return null; - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 6c83fc1e27e..a7aae61f564 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -67,6 +67,20 @@ import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Position; + +import org.checkerframework.checker.interning.qual.PolyInterned; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.InstanceOfUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; +import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.UniqueIdMap; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -76,6 +90,7 @@ import java.util.List; import java.util.Set; import java.util.StringJoiner; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; @@ -93,18 +108,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; -import org.checkerframework.checker.interning.qual.PolyInterned; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.InstanceOfUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils; -import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.UniqueIdMap; /** * Utility methods for analyzing a javac {@code Tree}. @@ -114,2809 +117,2835 @@ // TODO: use JCP to add version-specific behavior public final class TreeUtils { - // Class cannot be instantiated. - private TreeUtils() { - throw new AssertionError("Class TreeUtils cannot be instantiated."); - } - - /** Unique IDs for trees. Used instead of hash codes, so output is deterministic. */ - public static final UniqueIdMap treeUids = new UniqueIdMap<>(); - - /** The latest source version supported by this compiler. */ - private static final int sourceVersionNumber = - Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); - - /** Whether we are running on at least Java 21. */ - private static final boolean atLeastJava21 = sourceVersionNumber >= 21; - - /** - * The {@code TreeMaker.Select(JCExpression, Symbol)} method. Return type changes for JDK21+. Only - * needs to be used while the code is compiled with JDK below 21. - */ - private static final @Nullable Method TREEMAKER_SELECT; - - /** The value of Flags.RECORD which does not exist in Java 9 or 11. */ - private static final long Flags_RECORD = 2305843009213693952L; - - /** Tree kinds that represent a binary comparison. */ - private static final Set BINARY_COMPARISON_TREE_KINDS = - EnumSet.of( - Tree.Kind.EQUAL_TO, - Tree.Kind.NOT_EQUAL_TO, - Tree.Kind.LESS_THAN, - Tree.Kind.GREATER_THAN, - Tree.Kind.LESS_THAN_EQUAL, - Tree.Kind.GREATER_THAN_EQUAL); - - static { - try { - TREEMAKER_SELECT = TreeMaker.class.getMethod("Select", JCExpression.class, Symbol.class); - } catch (NoSuchMethodException e) { - throw new AssertionError("Unexpected error in TreeUtils static initializer", e); - } - } - - /** - * Checks if the provided method is a constructor method or no. - * - * @param tree a tree defining the method - * @return true iff tree describes a constructor - */ - public static boolean isConstructor(MethodTree tree) { - return tree.getName().contentEquals(""); - } - - /** - * Checks if the method invocation is a call to super. - * - * @param tree a tree defining a method invocation - * @return true iff tree describes a call to super - */ - public static boolean isSuperConstructorCall(MethodInvocationTree tree) { - return isNamedMethodCall("super", tree); - } - - /** - * Checks if the method invocation is a call to "this". - * - * @param tree a tree defining a method invocation - * @return true iff tree describes a call to this - */ - public static boolean isThisConstructorCall(MethodInvocationTree tree) { - return isNamedMethodCall("this", tree); - } - - /** - * Checks if the method call is a call to the given method name. - * - * @param name a method name - * @param tree a tree defining a method invocation - * @return true iff tree is a call to the given method - */ - private static boolean isNamedMethodCall(String name, MethodInvocationTree tree) { - return getMethodName(tree.getMethodSelect()).contentEquals(name); - } - - /** - * Returns true if the tree is a tree that 'looks like' either an access of a field or an - * invocation of a method that are owned by the same accessing instance. - * - *

                  It would only return true if the access tree is of the form: - * - *

                  -   *   field
                  -   *   this.field
                  -   *
                  -   *   method()
                  -   *   this.method()
                  -   * 
                  - * - * It does not perform any semantical check to differentiate between fields and local variables; - * local methods or imported static methods. - * - * @param tree expression tree representing an access to object member - * @return {@code true} iff the member is a member of {@code this} instance - */ - public static boolean isSelfAccess(ExpressionTree tree) { - ExpressionTree tr = TreeUtils.withoutParens(tree); - // If method invocation check the method select - if (tr.getKind() == Tree.Kind.ARRAY_ACCESS) { - return false; - } - - if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) { - tr = ((MethodInvocationTree) tree).getMethodSelect(); - } - tr = TreeUtils.withoutParens(tr); - if (tr.getKind() == Tree.Kind.TYPE_CAST) { - tr = ((TypeCastTree) tr).getExpression(); - } - tr = TreeUtils.withoutParens(tr); - - if (tr.getKind() == Tree.Kind.IDENTIFIER) { - return true; - } - - if (tr.getKind() == Tree.Kind.MEMBER_SELECT) { - tr = ((MemberSelectTree) tr).getExpression(); - if (tr.getKind() == Tree.Kind.IDENTIFIER) { - Name ident = ((IdentifierTree) tr).getName(); - return ident.contentEquals("this") || ident.contentEquals("super"); - } - } - - return false; - } - - /** - * If the given tree is a parenthesized tree, return the enclosed non-parenthesized tree. - * Otherwise, return the same tree. - * - * @param tree an expression tree - * @return the outermost non-parenthesized tree enclosed by the given tree - */ - @SuppressWarnings("interning:return.type.incompatible") // pol ymorphism implementation - public static @PolyInterned ExpressionTree withoutParens(@PolyInterned ExpressionTree tree) { - ExpressionTree t = tree; - while (t.getKind() == Tree.Kind.PARENTHESIZED) { - t = ((ParenthesizedTree) t).getExpression(); - } - return t; - } - - /** - * If the given tree is a parenthesized tree or cast tree, return the enclosed non-parenthesized, - * non-cast tree. Otherwise, return the same tree. - * - * @param tree an expression tree - * @return the outermost non-parenthesized non-cast tree enclosed by the given tree - */ - @SuppressWarnings("interning:return.type.incompatible") // polymorphism implementation - public static @PolyInterned ExpressionTree withoutParensOrCasts( - @PolyInterned ExpressionTree tree) { - ExpressionTree t = withoutParens(tree); - while (t.getKind() == Tree.Kind.TYPE_CAST) { - t = withoutParens(((TypeCastTree) t).getExpression()); - } - return t; - } - - // Obtaining Elements from Trees. - // There are three sets of methods: - // * use elementFromDeclaration whenever the tree is a declaration - // * use elementFromUse when the tree is a use - // * use elementFromTree in other cases; note that it may return null - // This section of the file groups methods by their receiver type; that is, it puts all - // `elementFrom*(FooTree)` methods together. - - /** - * Return the package element corresponding to the given package declaration. - * - * @param tree package declaration - * @return the package element for the given package - */ - public static PackageElement elementFromDeclaration(PackageTree tree) { - PackageElement result = (PackageElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for package tree %s", tree); - } - return result; - } - - // TODO: Document when this may return null. - /** - * Returns the type element corresponding to the given class declaration. - * - *

                  This method returns null instead of crashing when no element exists for the class tree, - * which can happen for certain kinds of anonymous classes, such as Ordering$1 in - * PolyCollectorTypeVar.java in the all-systems test suite and "class MyFileFilter" in - * PurgeTxnLog.java. - * - * @param tree class declaration - * @return the element for the given class - */ - public static TypeElement elementFromDeclaration(ClassTree tree) { - TypeElement result = (TypeElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for class tree %s", tree); - } - return result; - } - - /** - * Returns the type element corresponding to the given class declaration. - * - *

                  The TypeElement may be null for an anonymous class. - * - * @param tree the {@link ClassTree} node to get the element for - * @return the {@link TypeElement} for the given tree - * @deprecated use {@link #elementFromDeclaration(ClassTree)} - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static TypeElement elementFromTree(ClassTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the type element corresponding to the given class declaration. - * - * @param tree the {@link ClassTree} node to get the element for - * @return the {@link TypeElement} for the given tree - * @deprecated use {@link #elementFromDeclaration(ClassTree)} - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static TypeElement elementFromUse(ClassTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the element corresponding to the given tree. - * - * @param tree the tree corresponding to a use of an element - * @return the element for the corresponding declaration, {@code null} otherwise - * @deprecated use {@link #elementFromUse(ExpressionTree)} or {@link - * #elementFromTree(ExpressionTree)} - */ - @Pure - @Deprecated // not for removal; retain to prevent calls to this overload - public static @Nullable Element elementFromDeclaration(ExpressionTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the element corresponding to the given tree. - * - * @param tree the tree corresponding to a use of an element - * @return the element for the corresponding declaration, {@code null} otherwise - */ - @Pure - public static @Nullable Element elementFromTree(ExpressionTree tree) { - return TreeUtils.elementFromTree((Tree) tree); - } - - /** - * Gets the element for the declaration corresponding to this use of an element. To get the - * element for a declaration, use {@link #elementFromDeclaration(ClassTree)}, {@link - * #elementFromDeclaration(MethodTree)}, or {@link #elementFromDeclaration(VariableTree)} instead. - * - *

                  This method is just a wrapper around {@link TreeUtils#elementFromTree(Tree)}, but this class - * might be the first place someone looks for this functionality. - * - * @param tree the tree, which must be a use of an element - * @return the element for the corresponding declaration - */ - @Pure - public static Element elementFromUse(ExpressionTree tree) { - Element result = TreeUtils.elementFromTree(tree); - if (result == null) { - throw new BugInCF( - "argument to elementFromUse() has no element: %s [%s]", tree, tree.getClass()); - } - return result; - } - - /** - * Returns the VariableElement corresponding to the given use. - * - * @param tree the tree corresponding to a use of a VariableElement - * @return the element for the corresponding declaration - */ - @Pure - public static VariableElement variableElementFromUse(ExpressionTree tree) { - VariableElement result = TreeUtils.variableElementFromTree(tree); - if (result == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); - } - return result; - } - - /** - * Returns the element for the given expression. - * - * @param tree the {@link Tree} node to get the element for - * @return the element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable Element elementFromDeclaration(MemberSelectTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the element for the given expression. - * - * @param tree the {@link Tree} node to get the element for - * @return the element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable Element elementFromTree(MemberSelectTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the element for the given expression. - * - * @param tree a method call - * @return the element for the called method - */ - @Pure - public static Element elementFromUse(MemberSelectTree tree) { - Element result = TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("tree = " + tree); - } - return result; - } - - /** - * Returns the ExecutableElement for the called method. - * - * @param tree the {@link Tree} node to get the element for - * @return the Element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable ExecutableElement elementFromDeclaration(MethodInvocationTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the ExecutableElement for the called method. - * - * @param tree the {@link Tree} node to get the element for - * @return the Element for the given tree, or null if one could not be found - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static @Nullable ExecutableElement elementFromTree(MethodInvocationTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Returns the ExecutableElement for the called method. - * - * @param tree a method call - * @return the ExecutableElement for the called method - */ - @Pure - public static ExecutableElement elementFromUse(MethodInvocationTree tree) { - Element result = TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("tree = %s [%s]", tree, tree.getClass()); - } - if (!(result instanceof ExecutableElement)) { - throw new BugInCF( - "Method elements should be ExecutableElement. Found: %s [%s]", result, result.getClass()); - } - return (ExecutableElement) result; - } - - /** - * Returns the ExecutableElement for the method reference. - * - * @param tree a method reference - * @return the ExecutableElement for the method reference - */ - @Pure - public static ExecutableElement elementFromUse(MemberReferenceTree tree) { - Element result = elementFromUse((ExpressionTree) tree); - if (!(result instanceof ExecutableElement)) { - throw new BugInCF( - "Method reference elements should be ExecutableElement. Found: %s [%s]", - result, result.getClass()); - } - return (ExecutableElement) result; - } - - /** - * Returns the ExecutableElement for the given method declaration. - * - *

                  The result can be null, when {@code tree} is a method in an anonymous class and that class - * has not been processed yet. To work around this, adapt your processing order. - * - * @param tree a method declaration - * @return the element for the given method - */ - public static ExecutableElement elementFromDeclaration(MethodTree tree) { - ExecutableElement result = (ExecutableElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for method tree %s", tree); - } - return result; - } - - /** - * Returns the ExecutableElement for the given method declaration. - * - * @param tree the {@link MethodTree} node to get the element for - * @return the Element for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromTree(MethodTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the ExecutableElement for the given method declaration. - * - * @param tree the {@link MethodTree} node to get the element for - * @return the Element for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromUse(MethodTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the ExecutableElement for the given constructor invocation. - * - * @param tree the {@link NewClassTree} node to get the element for - * @return the {@link ExecutableElement} for the given tree, or null if one could not be found - * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal tree - * (JCTree) - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromDeclaration(NewClassTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Gets the ExecutableElement for the called constructor, from a constructor invocation. - * - * @param tree the {@link NewClassTree} node to get the element for - * @return the {@link ExecutableElement} for the given tree, or null if one could not be found - * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal tree - * (JCTree) - * @deprecated use elementFromUse - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static ExecutableElement elementFromTree(NewClassTree tree) { - return TreeUtils.elementFromUse(tree); - } - - /** - * Gets the ExecutableElement for the called constructor, from a constructor invocation. - * - * @param tree a constructor invocation - * @return the ExecutableElement for the called constructor - * @see #elementFromUse(NewClassTree) - */ - @Pure - public static ExecutableElement elementFromUse(NewClassTree tree) { - Element result = TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for %s", tree); - } - if (!(result instanceof ExecutableElement)) { - throw new BugInCF( - "Constructor elements should be ExecutableElement. Found: %s [%s]", - result, result.getClass()); - } - return (ExecutableElement) result; - } - - /** - * Returns the VariableElement corresponding to the given variable declaration. - * - * @param tree the variable - * @return the element for the given variable - */ - public static VariableElement elementFromDeclaration(VariableTree tree) { - VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); - // `result` can be null, for example for this variable declaration: - // PureFunc f1 = TestPure1::myPureMethod; - // TODO: check claim above. Initializer expression should have no impact on variable. - if (result == null) { - throw new BugInCF("null element for variable tree %s", tree); - } - return result; - } - - /** - * Returns the VariableElement corresponding to the given variable declaration. - * - * @param tree the {@link VariableTree} node to get the element for - * @return the {@link VariableElement} for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static VariableElement elementFromTree(VariableTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the VariableElement corresponding to the given variable declaration. - * - * @param tree the {@link VariableTree} node to get the element for - * @return the {@link VariableElement} for the given tree - * @deprecated use elementFromDeclaration - */ - @Deprecated // not for removal; retain to prevent calls to this overload - @Pure - public static VariableElement elementFromUse(VariableTree tree) { - return elementFromDeclaration(tree); - } - - /** - * Returns the {@link VariableElement} for the given Tree API node. - * - * @param tree the {@link Tree} node to get the element for - * @return the {@link VariableElement} for the given tree - * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal tree - * (JCTree) - */ - @Pure - public static VariableElement variableElementFromTree(Tree tree) { - VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); - if (result == null) { - throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); - } - return result; - } - - /** - * Returns the {@link Element} for the given Tree API node. For an object instantiation returns - * the value of the {@link JCNewClass#constructor} field. - * - *

                  Use this only when you do not statically know whether the tree is a declaration or a use of - * an element. - * - * @param tree the {@link Tree} node to get the element for - * @return the {@link Element} for the given tree, or null if one could not be found - * @throws BugInCF if {@code tree} is null or is not a valid javac-internal tree (JCTree) - */ - @Pure - public static @Nullable Element elementFromTree(Tree tree) { - if (tree == null) { - throw new BugInCF("TreeUtils.elementFromTree: tree is null"); - } - - if (!(tree instanceof JCTree)) { - throw new BugInCF( - "TreeUtils.elementFromTree: tree is not a valid Javac tree but a " + tree.getClass()); - } - - if (isExpressionTree(tree)) { - tree = withoutParensOrCasts((ExpressionTree) tree); - } - - switch (tree.getKind()) { - // symbol() only works on MethodSelects, so we need to get it manually - // for method invocations. - case METHOD_INVOCATION: - return TreeInfo.symbol(((JCMethodInvocation) tree).getMethodSelect()); - - case ASSIGNMENT: - return TreeInfo.symbol((JCTree) ((AssignmentTree) tree).getVariable()); - - case ARRAY_ACCESS: - return elementFromTree(((ArrayAccessTree) tree).getExpression()); - - case NEW_CLASS: - return ((JCNewClass) tree).constructor; - - case MEMBER_REFERENCE: - // TreeInfo.symbol, which is used in the default case, didn't handle - // member references until JDK8u20. So handle it here. - ExecutableElement memberResult = (ExecutableElement) ((JCMemberReference) tree).sym; - return memberResult; - - default: - Element defaultResult; - if (isTypeDeclaration(tree) - || tree.getKind() == Tree.Kind.VARIABLE - || tree.getKind() == Tree.Kind.METHOD) { - defaultResult = TreeInfo.symbolFor((JCTree) tree); - } else { - defaultResult = TreeInfo.symbol((JCTree) tree); - } - return defaultResult; - } - } - - /** - * Returns the constructor invoked by {@code newClassTree} unless {@code newClassTree} is creating - * an anonymous class. In which case, the super constructor is returned. - * - * @param newClassTree the constructor invocation - * @return the super constructor invoked in the body of the anonymous constructor; or {@link - * #elementFromUse(NewClassTree)} if {@code newClassTree} is not creating an anonymous class - */ - public static ExecutableElement getSuperConstructor(NewClassTree newClassTree) { - if (newClassTree.getClassBody() == null) { - return elementFromUse(newClassTree); - } - JCNewClass jcNewClass = (JCNewClass) newClassTree; - // Anonymous constructor bodies, which are always synthetic, contain exactly one statement - // in the form: - // super(arg1, ...) - // or - // o.super(arg1, ...) - // - // which is a method invocation of the super constructor. - - // The method call is guaranteed to return nonnull. - JCMethodDecl anonConstructor = - (JCMethodDecl) TreeInfo.declarationFor(jcNewClass.constructor, jcNewClass); - assert anonConstructor != null; - assert anonConstructor.body.stats.size() == 1; - JCExpressionStatement stmt = (JCExpressionStatement) anonConstructor.body.stats.head; - JCMethodInvocation superInvok = (JCMethodInvocation) stmt.expr; - return (ExecutableElement) TreeInfo.symbol(superInvok.meth); - } - - /** - * Determine whether the given ExpressionTree has an underlying element. - * - * @param tree the ExpressionTree to test - * @return whether the tree refers to an identifier, member select, or method invocation - */ - @EnsuresNonNullIf(result = true, expression = "elementFromTree(#1)") - @EnsuresNonNullIf(result = true, expression = "elementFromUse(#1)") - @Pure - public static boolean isUseOfElement(ExpressionTree tree) { - ExpressionTree realnode = TreeUtils.withoutParens(tree); - switch (realnode.getKind()) { - case IDENTIFIER: - case MEMBER_SELECT: - case METHOD_INVOCATION: - case NEW_CLASS: - assert elementFromTree(tree) != null : "@AssumeAssertion(nullness): inspection"; - assert elementFromUse(tree) != null : "@AssumeAssertion(nullness): inspection"; - return true; - default: - return false; + // Class cannot be instantiated. + private TreeUtils() { + throw new AssertionError("Class TreeUtils cannot be instantiated."); } - } - - /** - * Returns true if {@code tree} has a synthetic argument. - * - *

                  For some anonymous classes with an explicit enclosing expression, javac creates a synthetic - * argument to the constructor that is the enclosing expression of the NewClassTree. Suppose a - * programmer writes: - * - *

                  {@code class Outer {
                  -   *   class Inner { }
                  -   *     void method() {
                  -   *       this.new Inner(){};
                  -   *     }
                  -   * }}
                  - * - * Java 9 javac creates the following synthetic tree for {@code this.new Inner(){}}: - * - *
                  {@code new Inner(this) {
                  -   *   (.Outer x0) {
                  -   *     x0.super();
                  -   *   }
                  -   * }}
                  - * - * Java 11 javac creates a different tree without the synthetic argument for {@code this.new - * Inner(){}}; the first line in the below code differs: - * - *
                  {@code this.new Inner() {
                  -   *   (.Outer x0) {
                  -   *     x0.super();
                  -   *   }
                  -   * }}
                  - * - * @param tree a new class tree - * @return true if {@code tree} has a synthetic argument - */ - public static boolean hasSyntheticArgument(NewClassTree tree) { - if (tree.getClassBody() == null || tree.getEnclosingExpression() != null) { - return false; - } - for (Tree member : tree.getClassBody().getMembers()) { - if (member.getKind() == Tree.Kind.METHOD && isConstructor((MethodTree) member)) { - MethodTree methodTree = (MethodTree) member; - StatementTree f = methodTree.getBody().getStatements().get(0); - return TreeUtils.getReceiverTree(((ExpressionStatementTree) f).getExpression()) != null; - } - } - return false; - } - - /** - * Returns the name of the invoked method. - * - * @param tree the method invocation - * @return the name of the invoked method - */ - public static Name methodName(MethodInvocationTree tree) { - ExpressionTree expr = tree.getMethodSelect(); - if (expr.getKind() == Tree.Kind.IDENTIFIER) { - return ((IdentifierTree) expr).getName(); - } else if (expr.getKind() == Tree.Kind.MEMBER_SELECT) { - return ((MemberSelectTree) expr).getIdentifier(); - } - throw new BugInCF("TreeUtils.methodName: cannot be here: " + tree); - } - - /** - * Returns true if the first statement in the body is a self constructor invocation within a - * constructor. - * - * @param tree the method declaration - * @return true if the first statement in the body is a self constructor invocation within a - * constructor - */ - public static boolean containsThisConstructorInvocation(MethodTree tree) { - if (!TreeUtils.isConstructor(tree) || tree.getBody().getStatements().isEmpty()) { - return false; - } - - StatementTree st = tree.getBody().getStatements().get(0); - if (!(st instanceof ExpressionStatementTree) - || !(((ExpressionStatementTree) st).getExpression() instanceof MethodInvocationTree)) { - return false; - } - - MethodInvocationTree invocation = - (MethodInvocationTree) ((ExpressionStatementTree) st).getExpression(); - - return "this".contentEquals(TreeUtils.methodName(invocation)); - } - - /** - * Returns the first statement of the tree if it is a block. If it is not a block or an empty - * block, tree is returned. - * - * @param tree any kind of tree - * @return the first statement of the tree if it is a block. If it is not a block or an empty - * block, tree is returned. - */ - public static Tree firstStatement(Tree tree) { - Tree first; - if (tree.getKind() == Tree.Kind.BLOCK) { - BlockTree block = (BlockTree) tree; - if (block.getStatements().isEmpty()) { - first = block; - } else { - first = block.getStatements().iterator().next(); - } - } else { - first = tree; - } - return first; - } - - /** - * Determine whether the given class contains an explicit constructor. - * - * @param tree a class tree - * @return true iff there is an explicit constructor - */ - public static boolean hasExplicitConstructor(ClassTree tree) { - TypeElement elem = TreeUtils.elementFromDeclaration(tree); - for (ExecutableElement constructorElt : - ElementFilter.constructorsIn(elem.getEnclosedElements())) { - if (!isSynthetic(constructorElt)) { - return true; - } - } - return false; - } - - /** - * Returns true if the given method is synthetic. Also returns true if the method is a generated - * default constructor, which does not appear in source code but is not considered synthetic. - * - * @param ee a method or constructor element - * @return true iff the given method is synthetic - */ - public static boolean isSynthetic(ExecutableElement ee) { - MethodSymbol ms = (MethodSymbol) ee; - long mod = ms.flags(); - // GENERATEDCONSTR is for generated constructors, which do not have SYNTHETIC set. - return (mod & (Flags.SYNTHETIC | Flags.GENERATEDCONSTR)) != 0; - } - - /** - * Returns true if the given method is synthetic. - * - * @param tree a method declaration tree - * @return true iff the given method is synthetic - */ - public static boolean isSynthetic(MethodTree tree) { - ExecutableElement ee = TreeUtils.elementFromDeclaration(tree); - return isSynthetic(ee); - } - - /** - * Returns true if the tree is of a diamond type. In contrast to the implementation in TreeInfo, - * this version works on Trees. - * - * @param tree a tree - * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree) - */ - public static boolean isDiamondTree(Tree tree) { - switch (tree.getKind()) { - case ANNOTATED_TYPE: - return isDiamondTree(((AnnotatedTypeTree) tree).getUnderlyingType()); - case PARAMETERIZED_TYPE: - return ((ParameterizedTypeTree) tree).getTypeArguments().isEmpty(); - case NEW_CLASS: - return isDiamondTree(((NewClassTree) tree).getIdentifier()); - default: - return false; + + /** Unique IDs for trees. Used instead of hash codes, so output is deterministic. */ + public static final UniqueIdMap treeUids = new UniqueIdMap<>(); + + /** The latest source version supported by this compiler. */ + private static final int sourceVersionNumber = + Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); + + /** Whether we are running on at least Java 21. */ + private static final boolean atLeastJava21 = sourceVersionNumber >= 21; + + /** + * The {@code TreeMaker.Select(JCExpression, Symbol)} method. Return type changes for JDK21+. + * Only needs to be used while the code is compiled with JDK below 21. + */ + private static final @Nullable Method TREEMAKER_SELECT; + + /** The value of Flags.RECORD which does not exist in Java 9 or 11. */ + private static final long Flags_RECORD = 2305843009213693952L; + + /** Tree kinds that represent a binary comparison. */ + private static final Set BINARY_COMPARISON_TREE_KINDS = + EnumSet.of( + Tree.Kind.EQUAL_TO, + Tree.Kind.NOT_EQUAL_TO, + Tree.Kind.LESS_THAN, + Tree.Kind.GREATER_THAN, + Tree.Kind.LESS_THAN_EQUAL, + Tree.Kind.GREATER_THAN_EQUAL); + + static { + try { + TREEMAKER_SELECT = + TreeMaker.class.getMethod("Select", JCExpression.class, Symbol.class); + } catch (NoSuchMethodException e) { + throw new AssertionError("Unexpected error in TreeUtils static initializer", e); + } } - } - - /** - * Returns the type arguments to the given new class tree. - * - * @param tree a new class tree - * @return the type arguments to the given new class tree - */ - public static List getTypeArgumentsToNewClassTree(NewClassTree tree) { - Tree typeTree = tree.getIdentifier(); - if (typeTree.getKind() == Kind.ANNOTATED_TYPE) { - typeTree = ((AnnotatedTypeTree) typeTree).getUnderlyingType(); - } - - if (typeTree.getKind() == Kind.PARAMETERIZED_TYPE) { - return ((ParameterizedTypeTree) typeTree).getTypeArguments(); - } - return Collections.emptyList(); - } - - /** - * Returns true if the tree represents a {@code String} concatenation operation. - * - * @param tree a tree - * @return true if the tree represents a {@code String} concatenation operation - */ - public static boolean isStringConcatenation(Tree tree) { - return (tree.getKind() == Tree.Kind.PLUS && TypesUtils.isString(TreeUtils.typeOf(tree))); - } - - /** Returns true if the compound assignment tree is a string concatenation. */ - public static boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) { - return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT - && TypesUtils.isString(TreeUtils.typeOf(tree))); - } - - /** - * Is this method's declared return type "void"? - * - * @param tree a method declaration - * @return true iff method's declared return type is "void" - */ - public static boolean isVoidReturn(MethodTree tree) { - return typeOf(tree.getReturnType()).getKind() == TypeKind.VOID; - } - - /** - * Returns true if the tree is a constant-time expression. - * - *

                  A tree is a constant-time expression if it is: - * - *

                    - *
                  1. a literal tree - *
                  2. a reference to a final variable initialized with a compile time constant - *
                  3. a String concatenation of two compile time constants - *
                  - * - * @param tree the tree to check - * @return true if the tree is a constant-time expression - */ - public static boolean isCompileTimeString(ExpressionTree tree) { - tree = TreeUtils.withoutParens(tree); - if (tree instanceof LiteralTree) { - return true; - } - - if (TreeUtils.isUseOfElement(tree)) { - Element elt = TreeUtils.elementFromUse(tree); - return ElementUtils.isCompileTimeConstant(elt); - } else if (TreeUtils.isStringConcatenation(tree)) { - BinaryTree binOp = (BinaryTree) tree; - return isCompileTimeString(binOp.getLeftOperand()) - && isCompileTimeString(binOp.getRightOperand()); - } else { - return false; - } - } - - /** - * Returns the receiver tree of a field access or a method invocation. - * - * @param expression a field access or a method invocation - * @return the expression's receiver tree, or null if it does not have an explicit receiver - */ - public static @Nullable ExpressionTree getReceiverTree(ExpressionTree expression) { - ExpressionTree receiver; - switch (expression.getKind()) { - case METHOD_INVOCATION: - // Trying to handle receiver calls to trees of the form - // ((m).getArray()) - // returns the type of 'm' in this case - receiver = ((MethodInvocationTree) expression).getMethodSelect(); - - if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { - receiver = ((MemberSelectTree) receiver).getExpression(); - } else { - // It's a method call "m(foo)" without an explicit receiver - return null; - } - break; - case NEW_CLASS: - receiver = ((NewClassTree) expression).getEnclosingExpression(); - break; - case ARRAY_ACCESS: - receiver = ((ArrayAccessTree) expression).getExpression(); - break; - case MEMBER_SELECT: - receiver = ((MemberSelectTree) expression).getExpression(); - // Avoid int.class - if (receiver instanceof PrimitiveTypeTree) { - return null; - } - break; - case IDENTIFIER: - // It's a field access on implicit this or a local variable/parameter. - return null; - default: - return null; + + /** + * Checks if the provided method is a constructor method or no. + * + * @param tree a tree defining the method + * @return true iff tree describes a constructor + */ + public static boolean isConstructor(MethodTree tree) { + return tree.getName().contentEquals(""); } - if (receiver == null) { - return null; - } - - return TreeUtils.withoutParens(receiver); - } - - // TODO: What about anonymous classes? - // Adding Tree.Kind.NEW_CLASS here doesn't work, because then a - // tree gets cast to ClassTree when it is actually a NewClassTree, - // for example in enclosingClass above. - /** The kinds that represent classes. */ - private static final Set classTreeKinds; - - static { - classTreeKinds = EnumSet.noneOf(Tree.Kind.class); - for (Tree.Kind kind : Tree.Kind.values()) { - if (kind.asInterface() == ClassTree.class) { - classTreeKinds.add(kind); - } - } - } - - /** Kinds that represent a class or method tree. */ - private static final Set classAndMethodTreeKinds; - - static { - classAndMethodTreeKinds = EnumSet.copyOf(classTreeKinds()); - classAndMethodTreeKinds.add(Tree.Kind.METHOD); - } - - /** - * Returns the set of kinds that represent classes and methods. - * - * @return the set of kinds that represent classes and methods - */ - public static Set classAndMethodTreeKinds() { - return classAndMethodTreeKinds; - } - - /** - * Return the set of kinds that represent classes. - * - * @return the set of kinds that represent classes - */ - public static Set classTreeKinds() { - return classTreeKinds; - } - - /** - * Is the given tree kind a class, i.e. a class, enum, interface, or annotation type. - * - * @param tree the tree to test - * @return true, iff the given kind is a class kind - */ - public static boolean isClassTree(Tree tree) { - return classTreeKinds().contains(tree.getKind()); - } - - /** - * The kinds that represent declarations that might have {@code @SuppressWarnings} written on - * them: classes, methods, and variables. - */ - private static final Set declarationTreeKinds; - - static { - declarationTreeKinds = EnumSet.noneOf(Tree.Kind.class); - declarationTreeKinds.addAll(classTreeKinds); - declarationTreeKinds.add(Tree.Kind.METHOD); - declarationTreeKinds.add(Tree.Kind.VARIABLE); - } - - /** - * Return the set of kinds that represent declarations: classes, methods, and variables. - * - * @return the set of kinds that represent declarations - */ - public static Set declarationTreeKinds() { - return declarationTreeKinds; - } - - /** - * Returns true if the given tree is a declaration. - * - * @param tree the tree to test - * @return true if the given tree is a declaration - */ - public static boolean isDeclarationTree(Tree tree) { - return declarationTreeKinds.contains(tree.getKind()); - } - - /** The kinds that represent types. */ - private static final Set typeTreeKinds = - EnumSet.of( - Tree.Kind.PRIMITIVE_TYPE, - Tree.Kind.PARAMETERIZED_TYPE, - Tree.Kind.TYPE_PARAMETER, - Tree.Kind.ARRAY_TYPE, - Tree.Kind.UNBOUNDED_WILDCARD, - Tree.Kind.EXTENDS_WILDCARD, - Tree.Kind.SUPER_WILDCARD, - Tree.Kind.ANNOTATED_TYPE); - - /** - * Return the set of kinds that represent types. - * - * @return the set of kinds that represent types - */ - public static Set typeTreeKinds() { - return typeTreeKinds; - } - - /** - * Is the given tree a type instantiation? - * - *

                  TODO: this is an under-approximation: e.g. an identifier could be either a type use or an - * expression. How can we distinguish. - * - * @param tree the tree to test - * @return true, iff the given tree is a type - */ - public static boolean isTypeTree(Tree tree) { - return typeTreeKinds().contains(tree.getKind()); - } - - /** - * Returns true if the given element is an invocation of the method, or of any method that - * overrides that one. - */ - public static boolean isMethodInvocation( - Tree tree, ExecutableElement method, ProcessingEnvironment env) { - if (!(tree instanceof MethodInvocationTree)) { - return false; - } - MethodInvocationTree methInvok = (MethodInvocationTree) tree; - ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); - if (invoked == null) { - return false; - } - return ElementUtils.isMethod(invoked, method, env); - } - - /** - * Returns true if the argument is an invocation of one of the given methods, or of any method - * that overrides them. - * - * @param tree a tree that might be a method invocation - * @param methods the methods to check for - * @param processingEnv the processing environment - * @return true if the argument is an invocation of one of the given methods, or of any method - * that overrides them - */ - public static boolean isMethodInvocation( - Tree tree, List methods, ProcessingEnvironment processingEnv) { - if (!(tree instanceof MethodInvocationTree)) { - return false; - } - MethodInvocationTree methInvok = (MethodInvocationTree) tree; - ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); - if (invoked == null) { - return false; - } - for (ExecutableElement method : methods) { - if (ElementUtils.isMethod(invoked, method, processingEnv)) { - return true; - } - } - return false; - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one - * matching method. If more than one method takes the same number of formal parameters, then use - * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. - * - * @param type the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - Class type, String methodName, int params, ProcessingEnvironment env) { - String typeName = type.getCanonicalName(); - if (typeName == null) { - throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); - } - return getMethod(typeName, methodName, params, env); - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one - * matching method. If more than one method takes the same number of formal parameters, then use - * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - @FullyQualifiedName String typeName, - String methodName, - int params, - ProcessingEnvironment env) { - List methods = getMethods(typeName, methodName, params, env); - if (methods.size() == 1) { - return methods.get(0); - } - throw new BugInCF( - "TreeUtils.getMethod(%s, %s, %d): expected 1 match, found %d: %s", - typeName, methodName, params, methods.size(), methods); - } - - /** - * Returns the ExecutableElement for a method declaration. Returns null there is no matching - * method. Errs if there is more than one matching method. If more than one method takes the same - * number of formal parameters, then use {@link #getMethod(String, String, ProcessingEnvironment, - * String...)}. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElement for the specified method, or null - */ - public static @Nullable ExecutableElement getMethodOrNull( - @FullyQualifiedName String typeName, - String methodName, - int params, - ProcessingEnvironment env) { - List methods = getMethods(typeName, methodName, params, env); - if (methods.size() == 0) { - return null; - } else if (methods.size() == 1) { - return methods.get(0); - } else { - throw new BugInCF( - "TreeUtils.getMethod(%s, %s, %d): expected 0 or 1 match, found %d", - typeName, methodName, params, methods.size()); - } - } - - /** - * Returns all ExecutableElements for method declarations of methodName, in class typeName, with - * params formal parameters. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param params the number of formal parameters - * @param env the processing environment - * @return the ExecutableElements for all matching methods - */ - public static List getMethods( - @FullyQualifiedName String typeName, - String methodName, - int params, - ProcessingEnvironment env) { - List methods = new ArrayList<>(1); - TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); - if (typeElt == null) { - throw new UserError("Configuration problem! Could not load type: " + typeName); - } - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (exec.getSimpleName().contentEquals(methodName) && exec.getParameters().size() == params) { - methods.add(exec); - } - } - return methods; - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. - * - * @param type the class that contains the method - * @param methodName the name of the method - * @param env the processing environment - * @param paramTypes the method's formal parameter types - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - Class type, String methodName, ProcessingEnvironment env, String... paramTypes) { - String typeName = type.getCanonicalName(); - if (typeName == null) { - throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); - } - return getMethod(typeName, methodName, env, paramTypes); - } - - /** - * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. - * - * @param typeName the class that contains the method - * @param methodName the name of the method - * @param env the processing environment - * @param paramTypes the method's formal parameter types - * @return the ExecutableElement for the specified method - */ - public static ExecutableElement getMethod( - @FullyQualifiedName String typeName, - String methodName, - ProcessingEnvironment env, - String... paramTypes) { - TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (exec.getSimpleName().contentEquals(methodName) - && exec.getParameters().size() == paramTypes.length) { - boolean typesMatch = true; - List params = exec.getParameters(); - for (int i = 0; i < paramTypes.length; i++) { - VariableElement ve = params.get(i); - TypeMirror tm = TypeAnnotationUtils.unannotatedType(ve.asType()); - if (!tm.toString().equals(paramTypes[i])) { - typesMatch = false; - break; - } - } - if (typesMatch) { - return exec; - } - } - } - - // Didn't find an answer. Compose an error message. - List candidates = new ArrayList<>(); - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - if (exec.getSimpleName().contentEquals(methodName)) { - candidates.add(executableElementToString(exec)); - } - } - if (candidates.isEmpty()) { - for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { - candidates.add(executableElementToString(exec)); - } - } - throw new BugInCF( - "TreeUtils.getMethod: found no match for %s.%s(%s); candidates: %s", - typeName, methodName, Arrays.toString(paramTypes), candidates); - } - - /** - * Formats the ExecutableElement in the way that getMethod() expects it. - * - * @param exec an executable element - * @return the ExecutableElement, formatted in the way that getMethod() expects it - */ - private static String executableElementToString(ExecutableElement exec) { - StringJoiner result = new StringJoiner(", ", exec.getSimpleName() + "(", ")"); - for (VariableElement param : exec.getParameters()) { - result.add(TypeAnnotationUtils.unannotatedType(param.asType()).toString()); - } - return result.toString(); - } - - /** - * Determine whether the given expression is either "this" or an outer "C.this". - * - *

                  TODO: Should this also handle "super"? - */ - public static boolean isExplicitThisDereference(ExpressionTree tree) { - if (tree.getKind() == Tree.Kind.IDENTIFIER - && ((IdentifierTree) tree).getName().contentEquals("this")) { - // Explicit this reference "this" - return true; - } - - if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; - } - - MemberSelectTree memSelTree = (MemberSelectTree) tree; - if (memSelTree.getIdentifier().contentEquals("this")) { - // Outer this reference "C.this" - return true; - } - return false; - } - - /** - * Determine whether {@code tree} is a class literal, such as - * - *

                  -   *   Object . class
                  -   * 
                  - * - * @return true iff if tree is a class literal - */ - public static boolean isClassLiteral(Tree tree) { - if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { - return false; - } - return "class".equals(((MemberSelectTree) tree).getIdentifier().toString()); - } - - /** - * Determine whether {@code tree} is a field access expression, such as - * - *
                  -   *   f
                  -   *   obj . f
                  -   * 
                  - * - * This method currently also returns true for class literals and qualified this. - * - * @param tree a tree that might be a field access - * @return true iff if tree is a field access expression (implicit or explicit) - */ - public static boolean isFieldAccess(Tree tree) { - return asFieldAccess(tree) != null; - } - - /** - * Return the field that {@code tree} is a field access expression for, or null. - * - *
                  -   *   f
                  -   *   obj . f
                  -   * 
                  - * - * This method currently also returns a non-null value for class literals and qualified this. - * - * @param tree a tree that might be a field access - * @return the element if tree is a field access expression (implicit or explicit); null otherwise - */ - // TODO: fix value for class literals and qualified this, which are not field accesses. - public static @Nullable VariableElement asFieldAccess(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - // explicit member access (or a class literal or a qualified this) - MemberSelectTree memberSelect = (MemberSelectTree) tree; - assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(memberSelect); - if (el.getKind().isField()) { - return (VariableElement) el; - } - } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { - // implicit field access - IdentifierTree ident = (IdentifierTree) tree; - assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(ident); - if (el.getKind().isField() - && !ident.getName().contentEquals("this") - && !ident.getName().contentEquals("super")) { - return (VariableElement) el; - } - } - return null; - } - - /** - * Compute the name of the field that the field access {@code tree} accesses. Requires {@code - * tree} to be a field access, as determined by {@code isFieldAccess} (which currently also - * returns true for class literals and qualified this). - * - * @param tree a field access tree - * @return the name of the field accessed by {@code tree} - */ - public static String getFieldName(Tree tree) { - assert isFieldAccess(tree); - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree mtree = (MemberSelectTree) tree; - return mtree.getIdentifier().toString(); - } else { - IdentifierTree itree = (IdentifierTree) tree; - return itree.getName().toString(); - } - } - - /** - * Determine whether {@code tree} refers to a method element, such as. - * - *
                  -   *   m(...)
                  -   *   obj . m(...)
                  -   * 
                  - * - * @return true iff if tree is a method access expression (implicit or explicit) - */ - public static boolean isMethodAccess(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - // explicit method access - MemberSelectTree memberSelect = (MemberSelectTree) tree; - assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(memberSelect); - return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; - } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { - // implicit method access - IdentifierTree ident = (IdentifierTree) tree; - // The field "super" and "this" are also legal methods - if (ident.getName().contentEquals("super") || ident.getName().contentEquals("this")) { - return true; - } - assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; - Element el = TreeUtils.elementFromUse(ident); - return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; - } - return false; - } - - /** - * Compute the name of the method that the method access {@code tree} accesses. Requires {@code - * tree} to be a method access, as determined by {@code isMethodAccess}. - * - * @param tree a method access tree - * @return the name of the method accessed by {@code tree} - */ - public static String getMethodName(Tree tree) { - assert isMethodAccess(tree); - if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { - MemberSelectTree mtree = (MemberSelectTree) tree; - return mtree.getIdentifier().toString(); - } else { - IdentifierTree itree = (IdentifierTree) tree; - return itree.getName().toString(); - } - } - - /** - * Return {@code true} if and only if {@code tree} can have a type annotation. - * - * @return {@code true} if and only if {@code tree} can have a type annotation - */ - // TODO: is this implementation precise enough? E.g. does a .class literal work correctly? - public static boolean canHaveTypeAnnotation(Tree tree) { - return ((JCTree) tree).type != null; - } - - /** - * Returns true if and only if the given {@code tree} represents a field access of the given - * {@link VariableElement}. - */ - public static boolean isSpecificFieldAccess(Tree tree, VariableElement var) { - if (tree instanceof MemberSelectTree) { - MemberSelectTree memSel = (MemberSelectTree) tree; - assert isUseOfElement(memSel) : "@AssumeAssertion(nullness): tree kind"; - Element field = TreeUtils.elementFromUse(memSel); - return field.equals(var); - } else if (tree instanceof IdentifierTree) { - IdentifierTree idTree = (IdentifierTree) tree; - assert isUseOfElement(idTree) : "@AssumeAssertion(nullness): tree kind"; - Element field = TreeUtils.elementFromUse(idTree); - return field.equals(var); - } else { - return false; - } - } - - /** - * Returns the VariableElement for a field declaration. - * - * @param typeName the class where the field is declared - * @param fieldName the name of the field - * @param env the processing environment - * @return the VariableElement for typeName.fieldName - */ - public static VariableElement getField( - @FullyQualifiedName String typeName, String fieldName, ProcessingEnvironment env) { - TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); - for (VariableElement var : ElementFilter.fieldsIn(mapElt.getEnclosedElements())) { - if (var.getSimpleName().contentEquals(fieldName)) { - return var; - } - } - throw new BugInCF("TreeUtils.getField: shouldn't be here"); - } - - /** - * Determine whether the given tree represents an ExpressionTree. - * - * @param tree the Tree to test - * @return whether the tree is an ExpressionTree - */ - public static boolean isExpressionTree(Tree tree) { - return tree instanceof ExpressionTree; - } - - /** - * Returns true if this is a super call to the {@link Enum} constructor. - * - * @param tree the method invocation to check - * @return true if this is a super call to the {@link Enum} constructor - */ - public static boolean isEnumSuperCall(MethodInvocationTree tree) { - ExecutableElement ex = TreeUtils.elementFromUse(tree); - assert ex != null : "@AssumeAssertion(nullness): tree kind"; - Name name = ElementUtils.getQualifiedClassName(ex); - assert name != null : "@AssumeAssertion(nullness): assumption"; - boolean correctClass = "java.lang.Enum".contentEquals(name); - boolean correctMethod = "".contentEquals(ex.getSimpleName()); - return correctClass && correctMethod; - } - - /** - * Determine whether the given tree represents a declaration of a type (including type - * parameters). - * - * @param tree the Tree to test - * @return true if the tree is a type declaration - */ - public static boolean isTypeDeclaration(Tree tree) { - return isClassTree(tree) || tree.getKind() == Tree.Kind.TYPE_PARAMETER; - } - - /** - * Returns true if tree is an access of array length. - * - * @param tree tree to check - * @return true if tree is an access of array length - */ - public static boolean isArrayLengthAccess(Tree tree) { - if (tree.getKind() == Tree.Kind.MEMBER_SELECT - && isFieldAccess(tree) - && getFieldName(tree).equals("length")) { - ExpressionTree expressionTree = ((MemberSelectTree) tree).getExpression(); - if (TreeUtils.typeOf(expressionTree).getKind() == TypeKind.ARRAY) { - return true; - } - } - return false; - } - - /** - * Returns true if the given {@link MethodTree} is an anonymous constructor (the constructor for - * an anonymous class). - * - * @param method a method tree that may be an anonymous constructor - * @return true if the given path points to an anonymous constructor, false if it does not - */ - public static boolean isAnonymousConstructor(MethodTree method) { - Element e = elementFromTree(method); - if (e == null || e.getKind() != ElementKind.CONSTRUCTOR) { - return false; - } - TypeElement typeElement = (TypeElement) e.getEnclosingElement(); - return typeElement.getNestingKind() == NestingKind.ANONYMOUS; - } - - /** - * Returns true if the passed constructor is anonymous and has an explicit enclosing expression. - * - * @param con an ExecutableElement of a constructor declaration - * @param tree the NewClassTree of a constructor declaration - * @return true if there is an extra enclosing expression - */ - public static boolean isAnonymousConstructorWithExplicitEnclosingExpression( - ExecutableElement con, NewClassTree tree) { - - return (tree.getEnclosingExpression() != null) - && con.getKind() == ElementKind.CONSTRUCTOR - && ((TypeElement) con.getEnclosingElement()).getNestingKind() == NestingKind.ANONYMOUS; - } - - /** - * Returns true if the given {@link MethodTree} is a compact canonical constructor (the - * constructor for a record where the parameters are implicitly declared and implicitly assigned - * to the record's fields). This may be an explicitly declared compact canonical constructor or an - * implicitly generated one. - * - * @param method a method tree that may be a compact canonical constructor - * @return true if the given method is a compact canonical constructor - */ - public static boolean isCompactCanonicalRecordConstructor(MethodTree method) { - Symbol s = (Symbol) elementFromTree(method); - if (s == null) { - throw new BugInCF( - "TreeUtils.isCompactCanonicalRecordConstructor: null symbol for method tree: " + method); - } - return (s.flags() & Flags_RECORD) != 0; - } - - /** - * Returns true if the given {@link Tree} is part of a record that has been automatically - * generated by the compiler. This can be a field that is derived from the record's header field - * list, or an automatically generated canonical constructor. - * - * @param member the {@link Tree} for a member of a record - * @return true if the given path is generated by the compiler - */ - public static boolean isAutoGeneratedRecordMember(Tree member) { - Element e = elementFromTree(member); - if (e == null) { - throw new BugInCF( - "TreeUtils.isAutoGeneratedRecordMember: null element for member tree: " + member); - } - return ElementUtils.isAutoGeneratedRecordMember(e); - } - - /** - * Converts the given AnnotationTrees to AnnotationMirrors. - * - * @param annoTrees list of annotation trees to convert to annotation mirrors - * @return list of annotation mirrors that represent the given annotation trees - */ - public static List annotationsFromTypeAnnotationTrees( - List annoTrees) { - return CollectionsPlume.mapList(TreeUtils::annotationFromAnnotationTree, annoTrees); - } - - /** - * Converts the given AnnotationTree to an AnnotationMirror. - * - * @param tree annotation tree to convert to an annotation mirror - * @return annotation mirror that represent the given annotation tree - */ - public static AnnotationMirror annotationFromAnnotationTree(AnnotationTree tree) { - return ((JCAnnotation) tree).attribute; - } - - /** - * Converts the given AnnotatedTypeTree to a list of AnnotationMirrors. - * - * @param tree annotated type tree to convert - * @return list of AnnotationMirrors from the tree - */ - public static List annotationsFromTree(AnnotatedTypeTree tree) { - return annotationsFromTypeAnnotationTrees(((JCAnnotatedType) tree).annotations); - } - - /** - * Converts the given TypeParameterTree to a list of AnnotationMirrors. - * - * @param tree type parameter tree to convert - * @return list of AnnotationMirrors from the tree - */ - public static List annotationsFromTree(TypeParameterTree tree) { - return annotationsFromTypeAnnotationTrees(((JCTypeParameter) tree).annotations); - } - - /** - * Converts the given NewArrayTree to a list of AnnotationMirrors. - * - * @param tree new array tree - * @return list of AnnotationMirrors from the tree - */ - public static List annotationsFromArrayCreation( - NewArrayTree tree, int level) { - - assert tree instanceof JCNewArray; - JCNewArray newArray = ((JCNewArray) tree); - - if (level == -1) { - return annotationsFromTypeAnnotationTrees(newArray.annotations); - } - - if (newArray.dimAnnotations.length() > 0 - && (level >= 0) - && (level < newArray.dimAnnotations.size())) { - return annotationsFromTypeAnnotationTrees(newArray.dimAnnotations.get(level)); - } - - return Collections.emptyList(); - } - - /** - * Returns true if the tree is the declaration or use of a local variable. - * - * @param tree the tree to check - * @return true if the tree is the declaration or use of a local variable - */ - public static boolean isLocalVariable(Tree tree) { - if (tree.getKind() == Tree.Kind.VARIABLE) { - VariableElement varElt = elementFromDeclaration((VariableTree) tree); - return ElementUtils.isLocalVariable(varElt); - } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { - ExpressionTree etree = (ExpressionTree) tree; - assert isUseOfElement(etree) : "@AssumeAssertion(nullness): tree kind"; - return ElementUtils.isLocalVariable(elementFromUse(etree)); - } - return false; - } - - /** - * Returns the type as a TypeMirror of {@code tree}. To obtain {@code tree}'s AnnotatedTypeMirror, - * call {@code AnnotatedTypeFactory.getAnnotatedType()}. - * - * @return the type as a TypeMirror of {@code tree} - */ - public static TypeMirror typeOf(Tree tree) { - return ((JCTree) tree).type; - } - - /** - * Determines the type for a method invocation at its call site, which has all type variables - * substituted with the type arguments at the call site. - * - *

                  {@link javax.lang.model.type.TypeVariable} in the returned type should be compared using - * {@link TypesUtils#areSame(TypeVariable, TypeVariable)} because the {@code TypeVariable} will be - * freshly created by this method and will not be the same using {@link Object#equals(Object)} or - * {@link javax.lang.model.util.Types#isSameType(TypeMirror, TypeMirror)}. - * - * @param tree the method invocation - * @return the {@link ExecutableType} corresponding to the method invocation at its call site - */ - @Pure - public static ExecutableType typeFromUse(MethodInvocationTree tree) { - TypeMirror type = TreeUtils.typeOf(tree.getMethodSelect()); - if (!(type instanceof ExecutableType)) { - throw new BugInCF( - "TreeUtils.typeFromUse(MethodInvocationTree): type of method select in method" - + " invocation should be ExecutableType. Found: %s", - type); - } - ExecutableType executableType = (ExecutableType) type; - ExecutableElement element = elementFromUse(tree); - if (executableType.getParameterTypes().size() != element.getParameters().size()) { - // Sometimes when the method type is viewpoint-adapted, the vararg parameter disappears, - // just return the declared type. - // For example, - // static void call(MethodHandle methodHandle) throws Throwable { - // methodHandle.invoke(); - // } - return (ExecutableType) element.asType(); - } - return executableType; - } - - /** - * Determines the type for a constructor at its call site given an invocation via {@code new}, - * which has all type variables substituted with the type arguments at the call site. - * - * @param tree the constructor invocation - * @return the {@link ExecutableType} corresponding to the constructor call (i.e., the given - * {@code tree}) at its call site - */ - @Pure - public static ExecutableType typeFromUse(NewClassTree tree) { - if (!(tree instanceof JCTree.JCNewClass)) { - throw new BugInCF("TreeUtils.typeFromUse(NewClassTree): not a javac internal tree"); - } - - JCNewClass newClassTree = (JCNewClass) tree; - TypeMirror type = newClassTree.constructorType; - - if (!(type instanceof ExecutableType)) { - throw new BugInCF( - "TreeUtils.typeFromUse(NewClassTree): type of constructor in new class tree" - + " should be ExecutableType. Found: %s", - type); - } - return (ExecutableType) type; - } - - /** - * Determines the symbol for a constructor given an invocation via {@code new}. - * - * @see #elementFromUse(NewClassTree) - * @param tree the constructor invocation - * @return the {@link ExecutableElement} corresponding to the constructor call in {@code tree} - * @deprecated use elementFromUse instead - */ - @Deprecated // 2022-09-12 - public static ExecutableElement constructor(NewClassTree tree) { - return (ExecutableElement) ((JCNewClass) tree).constructor; - } - - /** - * The type of the lambda or method reference tree is a functional interface type. This method - * returns the single abstract method declared by that functional interface. (The type of this - * method is referred to as the function type.) - * - * @param tree lambda or member reference tree - * @param env the processing environment - * @return the single abstract method declared by the type of the tree - */ - public static ExecutableElement findFunction(Tree tree, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - Types javacTypes = Types.instance(ctx); - return (ExecutableElement) javacTypes.findDescriptorSymbol(((Type) typeOf(tree)).asElement()); - } - - /** - * Returns true if {@code tree} is an implicitly typed lambda. - * - *

                  A lambda expression whose formal type parameters have inferred types is an implicitly typed - * lambda. (See JLS 15.27.1) - * - * @param tree any kind of tree - * @return true iff {@code tree} is an implicitly typed lambda - */ - public static boolean isImplicitlyTypedLambda(Tree tree) { - return tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION - && ((JCLambda) tree).paramKind == ParameterKind.IMPLICIT; - } - - /** - * This is a duplication of {@code - * com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind}, which is not part of the - * supported javac API. - */ - public enum MemberReferenceKind { - /** super # instMethod */ - SUPER(ReferenceMode.INVOKE, false), - /** Type # instMethod */ - UNBOUND(ReferenceMode.INVOKE, true), - /** Type # staticMethod */ - STATIC(ReferenceMode.INVOKE, false), - /** Expr # instMethod */ - BOUND(ReferenceMode.INVOKE, false), - /** Inner # new */ - IMPLICIT_INNER(ReferenceMode.NEW, false), - /** Toplevel # new */ - TOPLEVEL(ReferenceMode.NEW, false), - /** ArrayType # new */ - ARRAY_CTOR(ReferenceMode.NEW, false); - - /** Whether this kind is a method reference or a constructor reference. */ - final ReferenceMode mode; - - /** Whether this kind is unbound. */ - final boolean unbound; - - /** - * Creates a MemberReferenceKind. - * - * @param mode whether this kind is a method reference or a constructor reference - * @param unbound whether the kind is not bound - */ - MemberReferenceKind(ReferenceMode mode, boolean unbound) { - this.mode = mode; - this.unbound = unbound; - } - - /** - * Whether this kind is unbound. - * - * @return Whether this kind is unbound - */ - public boolean isUnbound() { - return unbound; - } - - /** - * Returns whether this kind is a constructor reference. - * - * @return whether this kind is a constructor reference - */ - public boolean isConstructorReference() { - return mode == ReferenceMode.NEW; - } - - /** - * Returns the kind of member reference {@code tree} is. - * - * @param tree a member reference tree - * @return the kind of member reference {@code tree} is - */ - public static MemberReferenceKind getMemberReferenceKind(MemberReferenceTree tree) { - JCMemberReference memberTree = (JCMemberReference) tree; - switch (memberTree.kind) { - case SUPER: - return SUPER; - case UNBOUND: - return UNBOUND; - case STATIC: - return STATIC; - case BOUND: - return BOUND; - case IMPLICIT_INNER: - return IMPLICIT_INNER; - case TOPLEVEL: - return TOPLEVEL; - case ARRAY_CTOR: - return ARRAY_CTOR; - default: - throw new BugInCF("Unexpected ReferenceKind: %s", memberTree.kind); - } - } - } - - /** - * Determine whether an expression {@link ExpressionTree} has the constant value true, according - * to the compiler logic. - * - * @param tree the expression to be checked - * @return true if {@code tree} has the constant value true - */ - public static boolean isExprConstTrue(ExpressionTree tree) { - assert tree instanceof JCExpression; - if (((JCExpression) tree).type.isTrue()) { - return true; - } - tree = TreeUtils.withoutParens(tree); - if (tree instanceof JCTree.JCBinary) { - JCBinary binTree = (JCBinary) tree; - JCExpression ltree = binTree.lhs; - JCExpression rtree = binTree.rhs; - switch (binTree.getTag()) { - case AND: - return isExprConstTrue(ltree) && isExprConstTrue(rtree); - case OR: - return isExprConstTrue(ltree) || isExprConstTrue(rtree); - default: - break; - } - } - return false; - } - - /** - * Return toString(), but without line separators. - * - * @param tree a tree - * @return a one-line string representation of the tree - */ - public static String toStringOneLine(Tree tree) { - return tree.toString().trim().replaceAll("\\s+", " "); - } - - /** - * Return either {@link #toStringOneLine} if it is no more than {@code length} characters, or - * {@link #toStringOneLine} quoted and truncated. - * - * @param tree a tree - * @param length the maximum length for the result; must be at least 6 - * @return a one-line string representation of the tree that is no longer than {@code length} - * characters long - */ - public static String toStringTruncated(Tree tree, int length) { - if (length < 6) { - throw new BugInCF("TreeUtils.toStringTruncated: bad length " + length); - } - String result = toStringOneLine(tree); - if (result.length() > length) { - // The quoting increases the likelihood that all delimiters are balanced in the result. - // That makes it easier to manipulate the result (such as skipping over it) in an - // editor. The quoting also makes clear that the value is truncated. - result = "\"" + result.substring(0, length - 5) + "...\""; - } - return result; - } - - /** - * Given a javac ExpressionTree representing a fully qualified name such as "java.lang.Object", - * creates a String containing the name. - * - * @param nameExpr an ExpressionTree representing a fully qualified name - * @return a String representation of the fully qualified name - */ - public static String nameExpressionToString(ExpressionTree nameExpr) { - TreeVisitor visitor = - new SimpleTreeVisitor() { - @Override - public String visitIdentifier(IdentifierTree tree, Void p) { - return tree.toString(); - } - - @Override - public String visitMemberSelect(MemberSelectTree tree, Void p) { - return tree.getExpression().accept(this, null) + "." + tree.getIdentifier().toString(); - } - }; - return nameExpr.accept(visitor, null); - } - - /** - * Returns true if the binary operator may do a widening primitive conversion. See JLS chapter 5. - * - * @param tree a binary tree - * @return true if the tree's operator does numeric promotion on its arguments - */ - public static boolean isWideningBinary(BinaryTree tree) { - switch (tree.getKind()) { - case LEFT_SHIFT: - case LEFT_SHIFT_ASSIGNMENT: - case RIGHT_SHIFT: - case RIGHT_SHIFT_ASSIGNMENT: - case UNSIGNED_RIGHT_SHIFT: - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - // Strictly speaking, these operators do unary promotion on each argument - // separately. - return true; - - case MULTIPLY: - case MULTIPLY_ASSIGNMENT: - case DIVIDE: - case DIVIDE_ASSIGNMENT: - case REMAINDER: - case REMAINDER_ASSIGNMENT: - case PLUS: - case PLUS_ASSIGNMENT: - case MINUS: - case MINUS_ASSIGNMENT: - - case LESS_THAN: - case LESS_THAN_EQUAL: - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case EQUAL_TO: - case NOT_EQUAL_TO: - - case AND: - case XOR: - case OR: - // These operators do binary promotion on the two arguments together. - return true; - - // TODO: CONDITIONAL_EXPRESSION (?:) sometimes does numeric promotion. - - default: - return false; + + /** + * Checks if the method invocation is a call to super. + * + * @param tree a tree defining a method invocation + * @return true iff tree describes a call to super + */ + public static boolean isSuperConstructorCall(MethodInvocationTree tree) { + return isNamedMethodCall("super", tree); + } + + /** + * Checks if the method invocation is a call to "this". + * + * @param tree a tree defining a method invocation + * @return true iff tree describes a call to this + */ + public static boolean isThisConstructorCall(MethodInvocationTree tree) { + return isNamedMethodCall("this", tree); + } + + /** + * Checks if the method call is a call to the given method name. + * + * @param name a method name + * @param tree a tree defining a method invocation + * @return true iff tree is a call to the given method + */ + private static boolean isNamedMethodCall(String name, MethodInvocationTree tree) { + return getMethodName(tree.getMethodSelect()).contentEquals(name); } - } - - /** - * Returns the annotations explicitly written on the given type. - * - * @param annoTrees annotations written before a variable/method declaration; null if this type is - * not from such a location. This might contain type annotations that the Java parser attached - * to the declaration rather than to the type. - * @param typeTree the type whose annotations to return - * @return the annotations explicitly written on the given type - */ - public static List getExplicitAnnotationTrees( - @Nullable List annoTrees, Tree typeTree) { - while (true) { - switch (typeTree.getKind()) { - case IDENTIFIER: - case PRIMITIVE_TYPE: - if (annoTrees == null) { - return Collections.emptyList(); - } - return annoTrees; - case ANNOTATED_TYPE: - return ((AnnotatedTypeTree) typeTree).getAnnotations(); - case ARRAY_TYPE: - case TYPE_PARAMETER: - case UNBOUNDED_WILDCARD: - case EXTENDS_WILDCARD: - case SUPER_WILDCARD: - return Collections.emptyList(); - case MEMBER_SELECT: - if (annoTrees == null) { - return Collections.emptyList(); - } - typeTree = ((MemberSelectTree) typeTree).getExpression(); - break; - case PARAMETERIZED_TYPE: - typeTree = ((ParameterizedTypeTree) typeTree).getType(); - break; - case UNION_TYPE: - List alternatives = ((UnionTypeTree) typeTree).getTypeAlternatives(); - List result = new ArrayList<>(alternatives.size()); - for (Tree alternative : alternatives) { - result.addAll(getExplicitAnnotationTrees(null, alternative)); - } - return result; - default: - throw new BugInCF( - "TreeUtils.getExplicitAnnotationTrees: what typeTree? %s %s %s", - typeTree.getKind(), typeTree.getClass(), typeTree); - } - } - } - - /** - * Return a tree for the default value of the given type. The default value is 0, false, or null. - * - * @param typeMirror a type - * @param processingEnv the processing environment - * @return a tree for {@code type}'s default value - */ - public static LiteralTree getDefaultValueTree( - TypeMirror typeMirror, ProcessingEnvironment processingEnv) { - typeMirror = TypeAnnotationUtils.unannotatedType(typeMirror); - switch (typeMirror.getKind()) { - case BYTE: - case SHORT: - case INT: - // Byte should be (byte) 0, but this probably doesn't matter so just use int 0; - // Short should be (short) 0, but this probably doesn't matter so just use int 0; - return TreeUtils.createLiteral(TypeTag.INT, 0, typeMirror, processingEnv); - case CHAR: - // Value of a char literal needs to be stored as an integer because - // LiteralTree#getValue converts it from an integer to a char before being - // returned. - return TreeUtils.createLiteral(TypeTag.CHAR, (int) '\u0000', typeMirror, processingEnv); - case LONG: - return TreeUtils.createLiteral(TypeTag.LONG, 0L, typeMirror, processingEnv); - case FLOAT: - return TreeUtils.createLiteral(TypeTag.FLOAT, 0.0f, typeMirror, processingEnv); - case DOUBLE: - return TreeUtils.createLiteral(TypeTag.DOUBLE, 0.0d, typeMirror, processingEnv); - case BOOLEAN: - // Value of a boolean literal needs to be stored as an integer because - // LiteralTree#getValue converts it from an integer to a boolean before being - // returned. - return TreeUtils.createLiteral(TypeTag.BOOLEAN, 0, typeMirror, processingEnv); - default: - return TreeUtils.createLiteral( - TypeTag.BOT, null, processingEnv.getTypeUtils().getNullType(), processingEnv); - } - } - - /** - * Creates a LiteralTree for the given value. - * - * @param typeTag the literal's type tag - * @param value a wrapped primitive, null, or a String - * @param typeMirror the typeMirror for the literal - * @param processingEnv the processing environment - * @return a LiteralTree for the given type tag and value - */ - public static LiteralTree createLiteral( - TypeTag typeTag, - @Nullable Object value, - TypeMirror typeMirror, - ProcessingEnvironment processingEnv) { - Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); - TreeMaker maker = TreeMaker.instance(context); - LiteralTree result = maker.Literal(typeTag, value); - ((JCLiteral) result).type = (Type) typeMirror; - return result; - } - - /** - * Returns true if the given tree evaluates to {@code null}. - * - * @param t a tree - * @return true if the given tree evaluates to {@code null} - */ - public static boolean isNullExpression(Tree t) { - while (true) { - switch (t.getKind()) { - case PARENTHESIZED: - t = ((ParenthesizedTree) t).getExpression(); - break; - case TYPE_CAST: - t = ((TypeCastTree) t).getExpression(); - break; - case NULL_LITERAL: - return true; - default: - return false; - } - } - } - - /** - * Returns true if two expressions originating from the same scope are identical, i.e. they are - * syntactically represented in the same way (modulo parentheses) and represent the same value. - * - *

                  If the expression includes one or more method calls, assumes the method calls are - * deterministic. - * - * @param expr1 the first expression to compare - * @param expr2 the second expression to compare; expr2 must originate from the same scope as - * expr1 - * @return true if the expressions expr1 and expr2 are syntactically identical - */ - public static boolean sameTree(ExpressionTree expr1, ExpressionTree expr2) { - expr1 = TreeUtils.withoutParens(expr1); - expr2 = TreeUtils.withoutParens(expr2); - // Converting to a string in order to compare is somewhat inefficient, and it doesn't handle - // internal parentheses. We could create a visitor instead. - return expr1.getKind() == expr2.getKind() && expr1.toString().equals(expr2.toString()); - } - - /** - * Returns true if this is the default case for a switch statement or expression. (Also, returns - * true if {@code caseTree} is {@code case null, default:}.) - * - * @param caseTree a case tree - * @return true if {@code caseTree} is the default case for a switch statement or expression - * @deprecated use {@link CaseUtils#isDefaultCaseTree(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static boolean isDefaultCaseTree(CaseTree caseTree) { - return CaseUtils.isDefaultCaseTree(caseTree); - } - - /** - * Returns true if this is a case rule (as opposed to a case statement). - * - * @param caseTree a case tree - * @return true if {@code caseTree} is a case rule - * @deprecated use {@link CaseUtils#isCaseRule(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static boolean isCaseRule(CaseTree caseTree) { - return CaseUtils.isCaseRule(caseTree); - } - - /** - * Get the list of expressions from a case expression. For the default case, this is empty. - * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can be - * multiple expressions per case. - * - * @param caseTree the case expression to get the expressions from - * @return the list of expressions in the case - * @deprecated use {@link CaseUtils#getExpressions(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static List caseTreeGetExpressions(CaseTree caseTree) { - return CaseUtils.getExpressions(caseTree); - } - - /** - * Returns the body of the case statement if it is of the form {@code case -> - * }. This method should only be called if {@link CaseTree#getStatements()} returns - * null. - * - * @param caseTree the case expression to get the body from - * @return the body of the case tree - * @deprecated use {@link CaseUtils#getBody(CaseTree)} - */ - @Deprecated // 2023-09-26 - public static @Nullable Tree caseTreeGetBody(CaseTree caseTree) { - return CaseUtils.getBody(caseTree); - } - - /** - * Returns true if {@code tree} is a {@code BindingPatternTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code BindingPatternTree} - */ - public static boolean isBindingPatternTree(Tree tree) { - return tree.getKind().name().contentEquals("BINDING_PATTERN"); - } - - /** - * Returns the binding variable of {@code bindingPatternTree}. - * - * @param bindingPatternTree the BindingPatternTree whose binding variable is returned - * @return the binding variable of {@code bindingPatternTree} - * @deprecated use {@link BindingPatternUtils#getVariable(Tree)} - */ - @Deprecated // 2023-09-26 - public static VariableTree bindingPatternTreeGetVariable(Tree bindingPatternTree) { - return BindingPatternUtils.getVariable(bindingPatternTree); - } - - /** - * Returns true if {@code tree} is a {@code DeconstructionPatternTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code DeconstructionPatternTree} - */ - public static boolean isDeconstructionPatternTree(Tree tree) { - return tree.getKind().name().contentEquals("DECONSTRUCTION_PATTERN"); - } - - /** - * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does not - * have a pattern, including if the JDK version does not support instance-of patterns. - * - * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned - * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist - * @deprecated use {@link InstanceOfUtils#getPattern(InstanceOfTree)} - */ - @Deprecated // 2023-09-26 - public static @Nullable Tree instanceOfTreeGetPattern(InstanceOfTree instanceOfTree) { - return InstanceOfUtils.getPattern(instanceOfTree); - } - - /** - * Returns the selector expression of {@code switchExpressionTree}. For example - * - *

                  -   *   switch ( expression ) { ... }
                  -   * 
                  - * - * @param switchExpressionTree the switch expression whose selector expression is returned - * @return the selector expression of {@code switchExpressionTree} - * @deprecated use {@link SwitchExpressionUtils#getExpression(Tree)} - */ - @Deprecated // 2023-09-26 - public static ExpressionTree switchExpressionTreeGetExpression(Tree switchExpressionTree) { - return SwitchExpressionUtils.getExpression(switchExpressionTree); - } - - /** - * Returns the cases of {@code switchExpressionTree}. For example - * - *
                  -   *   switch ( expression ) {
                  -   *     cases
                  -   *   }
                  -   * 
                  - * - * @param switchExpressionTree the switch expression whose cases are returned - * @return the cases of {@code switchExpressionTree} - * @deprecated use {@link SwitchExpressionUtils#getCases(Tree)} - */ - @Deprecated // 2023-09-26 - public static List switchExpressionTreeGetCases(Tree switchExpressionTree) { - return SwitchExpressionUtils.getCases(switchExpressionTree); - } - - /** - * Returns true if {@code switchTree} has a null case label. - * - * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} - * @return true if {@code switchTree} has a null case label - */ - public static boolean hasNullCaseLabel(Tree switchTree) { - if (!atLeastJava21) { - return false; - } - List cases; - if (isSwitchStatement(switchTree)) { - cases = ((SwitchTree) switchTree).getCases(); - } else { - cases = SwitchExpressionUtils.getCases(switchTree); - } - for (CaseTree caseTree : cases) { - List labels = CaseUtils.getLabels(caseTree); - for (Tree label : labels) { - if (label.getKind() == Kind.NULL_LITERAL) { - return true; - } - } - } - return false; - } - - /** - * Returns true if the given tree is a switch statement (as opposed to a switch expression). - * - * @param tree the switch statement or expression to check - * @return true if the given tree is a switch statement (as opposed to a switch expression) - */ - public static boolean isSwitchStatement(Tree tree) { - return tree.getKind() == Tree.Kind.SWITCH; - } - - /** - * Returns true if the given switch statement tree is an enhanced switch statement, as described - * in JLS - * 14.11.2. - * - * @param switchTree the switch statement to check - * @return true if the given tree is an enhanced switch statement - */ - public static boolean isEnhancedSwitchStatement(SwitchTree switchTree) { - TypeMirror exprType = typeOf(switchTree.getExpression()); - // TODO: this should be only char, byte, short, int, Character, Byte, Short, Integer. Is the - // over-approximation a problem? - Element exprElem = TypesUtils.getTypeElement(exprType); - boolean isNotEnum = exprElem == null || exprElem.getKind() != ElementKind.ENUM; - if (!TypesUtils.isPrimitiveOrBoxed(exprType) && !TypesUtils.isString(exprType) && isNotEnum) { - return true; - } - - for (CaseTree caseTree : switchTree.getCases()) { - for (Tree caseLabel : CaseUtils.getLabels(caseTree)) { - if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL - || TreeUtils.isBindingPatternTree(caseLabel) - || TreeUtils.isDeconstructionPatternTree(caseLabel)) { - return true; - } - } - } - - return false; - } - - /** - * Returns true if the given tree is a switch expression. - * - * @param tree a tree to check - * @return true if the given tree is a switch expression - */ - public static boolean isSwitchExpression(Tree tree) { - return tree.getKind().name().equals("SWITCH_EXPRESSION"); - } - - /** - * Returns true if the given tree is a yield expression. - * - * @param tree a tree to check - * @return true if the given tree is a yield expression - */ - public static boolean isYield(Tree tree) { - return tree.getKind().name().equals("YIELD"); - } - - /** - * Returns the value (expression) for {@code yieldTree}. - * - * @param yieldTree the yield tree - * @return the value (expression) for {@code yieldTree} - * @deprecated use {@link YieldUtils#getValue(Tree)} - */ - @Deprecated // 2023-09-26 - public static ExpressionTree yieldTreeGetValue(Tree yieldTree) { - return YieldUtils.getValue(yieldTree); - } - - /** - * Returns true if the {@code variableTree} is declared using the {@code var} Java keyword. - * - * @param variableTree the variableTree to check - * @return true if the variableTree is declared using the {@code var} Java keyword - */ - public static boolean isVariableTreeDeclaredUsingVar(VariableTree variableTree) { - JCExpression type = (JCExpression) variableTree.getType(); - return type != null && type.pos == Position.NOPOS; - } - - /** - * Returns true if the given method/constructor invocation is a varargs invocation. - * - * @param tree a method/constructor invocation - * @return true if the given method/constructor invocation is a varargs invocation - */ - public static boolean isVarArgs(Tree tree) { - switch (tree.getKind()) { - case METHOD_INVOCATION: - return isVarArgs((MethodInvocationTree) tree); - case NEW_CLASS: - return isVarArgs((NewClassTree) tree); - default: - throw new BugInCF("TreeUtils.isVarArgs: unexpected kind of tree: " + tree); - } - } - - /** - * Returns true if the given method invocation is a varargs invocation. - * - * @param invok the method invocation - * @return true if the given method invocation is a varargs invocation - */ - public static boolean isVarArgs(MethodInvocationTree invok) { - return isVarArgs(elementFromUse(invok), invok.getArguments()); - } - - /** - * Returns true if the given method invocation is an invocation of a method with a vararg - * parameter, and the invocation has zero vararg actuals. - * - * @param invok the method invocation - * @return true if the given method invocation is an invocation of a method with a vararg - * parameter, and the invocation has with zero vararg actuals - */ - public static boolean isCallToVarArgsMethodWithZeroVarargsActuals(MethodInvocationTree invok) { - if (!TreeUtils.isVarArgs(invok)) { - return false; - } - int numParams = elementFromUse(invok).getParameters().size(); - // The comparison of the number of arguments to the number of formals (minus one) checks whether - // there are no varargs actuals - return invok.getArguments().size() == numParams - 1; - } - - /** - * Returns true if the given constructor invocation is a varargs invocation. - * - * @param newClassTree the constructor invocation - * @return true if the given method invocation is a varargs invocation - */ - public static boolean isVarArgs(NewClassTree newClassTree) { - return isVarArgs(elementFromUse(newClassTree), newClassTree.getArguments()); - } - - /** - * Returns true if a method/constructor invocation is a varargs invocation. - * - * @param method the method or constructor - * @param args the arguments passed at the invocation - * @return true if the given method/constructor invocation is a varargs invocation - */ - private static boolean isVarArgs(ExecutableElement method, List args) { - if (!method.isVarArgs()) { - return false; - } - - List parameters = method.getParameters(); - if (parameters.size() != args.size()) { - return true; - } - - TypeMirror lastArgType = typeOf(args.get(args.size() - 1)); - if (lastArgType.getKind() == TypeKind.NULL) { - return false; - } - if (lastArgType.getKind() != TypeKind.ARRAY) { - return true; - } - - TypeMirror varargsParamType = parameters.get(parameters.size() - 1).asType(); - return TypesUtils.getArrayDepth(varargsParamType) != TypesUtils.getArrayDepth(lastArgType); - } - - /** - * Determine whether the given tree is of Kind RECORD, in a way that works on all versions of - * Java. - * - * @param tree the tree to get the kind for - * @return whether the tree is of the kind RECORD - */ - public static boolean isRecordTree(Tree tree) { - Tree.Kind kind = tree.getKind(); - // Must use String comparison because we may be on an older JDK: - return kind.name().equals("RECORD"); - } - - /** - * Calls getKind() on the given tree, but returns CLASS if the Kind is RECORD. This is needed - * because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD can't be - * used in case statements, and usually we want to treat them the same as classes. - * - * @param tree the tree to get the kind for - * @return the kind of the tree, but CLASS if the kind was RECORD - */ - public static Tree.Kind getKindRecordAsClass(Tree tree) { - if (isRecordTree(tree)) { - return Tree.Kind.CLASS; - } - return tree.getKind(); - } - - /** - * Returns true if the {@code tree} is a binary tree that performs a comparison. - * - * @param tree the tree to check - * @return whether the tree represents a binary comparison - */ - public static boolean isBinaryComparison(BinaryTree tree) { - return BINARY_COMPARISON_TREE_KINDS.contains(tree.getKind()); - } - - /** - * Returns the result of {@code treeMaker.Select(base, sym)}. - * - * @param treeMaker the TreeMaker to use - * @param base the expression for the select - * @param sym the symbol to select - * @return the JCFieldAccess tree to select sym in base - */ - public static JCFieldAccess Select(TreeMaker treeMaker, Tree base, Symbol sym) { - // The return type of TreeMaker.Select changed in - // https://github.com/openjdk/jdk/commit/a917fb3fcf0fe1a4c4de86c08ae4041462848b82#diff-0f1b4da56622ccb5ff716ce5a9532819fc5573179a1eb2c803d053196824891aR726 - // When the ECF is compiled with Java 21+, even with `--source/target 8`, this will lead to - // a java.lang.NoSuchMethodError: 'com.sun.tools.javac.tree.JCTree$JCFieldAccess - // com.sun.tools.javac.tree.TreeMaker.Select(com.sun.tools.javac.tree.JCTree$JCExpression, - // com.sun.tools.javac.code.Symbol)' - // when executed on Java <21. - // Therefore, always use reflection to access TreeMaker.Select. - // Hopefully, the JVM optimizes the reflective access quickly. - try { - assert TREEMAKER_SELECT != null : "@AssumeAssertion(nullness): initialization"; - JCFieldAccess jfa = (JCFieldAccess) TREEMAKER_SELECT.invoke(treeMaker, base, sym); - if (jfa != null) { - return jfa; - } else { - throw new BugInCF("TreeUtils.Select: TreeMaker.Select returned null for tree: %s", base); - } - } catch (InvocationTargetException | IllegalAccessException e) { - throw new BugInCF("TreeUtils.Select: reflection failed for tree: %s", base, e); - } - } - - /** - * Returns the result of {@code treeMaker.Select(base, name)}. - * - * @param treeMaker the TreeMaker to use - * @param base the expression for the select - * @param name the name to select - * @return the JCFieldAccess tree to select sym in base - */ - public static JCFieldAccess Select( - TreeMaker treeMaker, Tree base, com.sun.tools.javac.util.Name name) { - // There's no need for reflection here. The only reason we even declare this method - // is so that callers don't have to remember which overload we provide a wrapper around. - return treeMaker.Select((JCExpression) base, name); - } - - /** - * Returns true if {@code tree} is an explicitly typed lambda. - * - *

                  An lambda whose formal type parameters have declared types or with no parameters is an - * explicitly typed lambda. (See JLS 15.27.1) - * - * @param tree any kind of tree - * @return true iff {@code tree} is an implicitly typed lambda - */ - public static boolean isExplicitlyTypeLambda(Tree tree) { - return tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION - && ((JCLambda) tree).paramKind == ParameterKind.EXPLICIT; - } - - /** - * Returns all expressions that might be the result of {@code lambda}. - * - * @param lambda a lambda with or without a body - * @return a list of expressions that are returned by {@code lambda} - */ - public static List getReturnedExpressions(LambdaExpressionTree lambda) { - if (lambda.getBodyKind() == BodyKind.EXPRESSION) { - return Collections.singletonList((ExpressionTree) lambda.getBody()); - } - - List returnExpressions = new ArrayList<>(); - TreeScanner scanner = - new TreeScanner() { - @Override - public Void visitReturn(ReturnTree tree, Void o) { - if (tree.getExpression() != null) { - returnExpressions.add(tree.getExpression()); + + /** + * Returns true if the tree is a tree that 'looks like' either an access of a field or an + * invocation of a method that are owned by the same accessing instance. + * + *

                  It would only return true if the access tree is of the form: + * + *

                  +     *   field
                  +     *   this.field
                  +     *
                  +     *   method()
                  +     *   this.method()
                  +     * 
                  + * + * It does not perform any semantical check to differentiate between fields and local variables; + * local methods or imported static methods. + * + * @param tree expression tree representing an access to object member + * @return {@code true} iff the member is a member of {@code this} instance + */ + public static boolean isSelfAccess(ExpressionTree tree) { + ExpressionTree tr = TreeUtils.withoutParens(tree); + // If method invocation check the method select + if (tr.getKind() == Tree.Kind.ARRAY_ACCESS) { + return false; + } + + if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) { + tr = ((MethodInvocationTree) tree).getMethodSelect(); + } + tr = TreeUtils.withoutParens(tr); + if (tr.getKind() == Tree.Kind.TYPE_CAST) { + tr = ((TypeCastTree) tr).getExpression(); + } + tr = TreeUtils.withoutParens(tr); + + if (tr.getKind() == Tree.Kind.IDENTIFIER) { + return true; + } + + if (tr.getKind() == Tree.Kind.MEMBER_SELECT) { + tr = ((MemberSelectTree) tr).getExpression(); + if (tr.getKind() == Tree.Kind.IDENTIFIER) { + Name ident = ((IdentifierTree) tr).getName(); + return ident.contentEquals("this") || ident.contentEquals("super"); } - return super.visitReturn(tree, o); - } + } - @Override - public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { - // Don't visit inside anther lambda. - return null; - } - }; - scanner.scan(lambda.getBody(), null); - return returnExpressions; - } - - /** - * Returns whether or not {@code ref} is an exact method reference. - * - *

                  From JLS 15.13.1 "If there is only one possible compile-time declaration with only one - * possible invocation, it is said to be exact." - * - * @param ref a method reference - * @return whether or not {@code ref} is an exact method reference - */ - public static boolean isExactMethodReference(MemberReferenceTree ref) { - // Seems like overloaded means the same thing as inexact. - // overloadKind is set - // com.sun.tools.javac.comp.DeferredAttr.DeferredChecker.visitReference() - // IsExact: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.1-400 - // Treat OverloadKind.ERROR as overloaded. - return ((JCMemberReference) ref).getOverloadKind() == OverloadKind.UNOVERLOADED; - } - - /** - * Returns whether or not {@code expression} is a poly expression as defined in JLS 15.2. - * - * @param expression expression - * @return whether or not {@code expression} is a poly expression - */ - public static boolean isPolyExpression(ExpressionTree expression) { - return !isStandaloneExpression(expression); - } - - /** - * Returns whether or not {@code expression} is a standalone expression as defined in JLS 15.2. - * - * @param expression expression - * @return whether or not {@code expression} is a standalone expression - */ - public static boolean isStandaloneExpression(ExpressionTree expression) { - if (expression instanceof JCTree.JCExpression) { - if (((JCTree.JCExpression) expression).isStandalone()) { - return true; - } - if (expression.getKind() == Tree.Kind.METHOD_INVOCATION) { - // This seems to be a bug in at least Java 11. If a method has type arguments, then - // it is a standalone expression. - return !((MethodInvocationTree) expression).getTypeArguments().isEmpty(); - } - } - return false; - } - - /** - * Was applicability by variable arity invocation necessary to determine the method signature? - * - *

                  This isn't the same as {@link ExecutableElement#isVarArgs()}. That method returns true if - * the method accepts a variable number of arguments. This method returns true if the method - * invocation actually used that fact to invoke the method. - * - * @param methodInvocation a method or constructor invocation - * @return whether applicability by variable arity invocation is necessary to determine the method - * signature - */ - public static boolean isVarArgMethodCall(ExpressionTree methodInvocation) { - if (methodInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { - return ((JCMethodInvocation) methodInvocation).varargsElement != null; - } else if (methodInvocation.getKind() == Tree.Kind.NEW_CLASS) { - return ((JCNewClass) methodInvocation).varargsElement != null; - } else { - return false; - } - } - - /** - * Is the tree a reference to a constructor of a generic class whose type argument isn't - * specified? For example, {@code HashSet::new)}. - * - * @param tree may or may not be a {@link MemberReferenceTree} - * @return true if tree is a reference to a constructor of a generic class whose type argument - * isn't specified - */ - public static boolean isDiamondMemberReference(ExpressionTree tree) { - if (tree.getKind() != Tree.Kind.MEMBER_REFERENCE) { - return false; - } - MemberReferenceTree memRef = (MemberReferenceTree) tree; - TypeMirror type = TreeUtils.typeOf(memRef.getQualifierExpression()); - if (memRef.getMode() == ReferenceMode.NEW && type.getKind() == TypeKind.DECLARED) { - // No need to check array::new because the generic arrays can't be created. - TypeElement classElt = (TypeElement) ((Type) type).asElement(); - DeclaredType classTypeMirror = (DeclaredType) classElt.asType(); - return !classTypeMirror.getTypeArguments().isEmpty() - && ((Type) type).getTypeArguments().isEmpty(); - } - return false; - } - - /** - * Return whether {@code tree} is a method reference with a raw type to the left of {@code ::}. - * For example, {@code Class::getName}. - * - * @param tree a tree - * @return whether {@code tree} is a method reference with a raw type to the left of {@code ::} - */ - public static boolean isLikeDiamondMemberReference(ExpressionTree tree) { - if (tree.getKind() != Tree.Kind.MEMBER_REFERENCE) { - return false; - } - MemberReferenceTree memberReferenceTree = (MemberReferenceTree) tree; - if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree).isUnbound()) { - TypeMirror preColonTreeType = typeOf(memberReferenceTree.getQualifierExpression()); - return TypesUtils.isRaw(preColonTreeType); - } - return false; - } - - /** - * Returns whether the method reference tree needs type argument inference. - * - * @param memberReferenceTree a method reference tree - * @return whether the method reference tree needs type argument inference - */ - public static boolean needsTypeArgInference(MemberReferenceTree memberReferenceTree) { - if (isDiamondMemberReference(memberReferenceTree) - || isLikeDiamondMemberReference(memberReferenceTree)) { - return true; - } - - ExecutableElement element = TreeUtils.elementFromUse(memberReferenceTree); - return !element.getTypeParameters().isEmpty() - && (memberReferenceTree.getTypeArguments() == null - || memberReferenceTree.getTypeArguments().isEmpty()); - } + return false; + } + + /** + * If the given tree is a parenthesized tree, return the enclosed non-parenthesized tree. + * Otherwise, return the same tree. + * + * @param tree an expression tree + * @return the outermost non-parenthesized tree enclosed by the given tree + */ + @SuppressWarnings("interning:return.type.incompatible") // pol ymorphism implementation + public static @PolyInterned ExpressionTree withoutParens(@PolyInterned ExpressionTree tree) { + ExpressionTree t = tree; + while (t.getKind() == Tree.Kind.PARENTHESIZED) { + t = ((ParenthesizedTree) t).getExpression(); + } + return t; + } + + /** + * If the given tree is a parenthesized tree or cast tree, return the enclosed + * non-parenthesized, non-cast tree. Otherwise, return the same tree. + * + * @param tree an expression tree + * @return the outermost non-parenthesized non-cast tree enclosed by the given tree + */ + @SuppressWarnings("interning:return.type.incompatible") // polymorphism implementation + public static @PolyInterned ExpressionTree withoutParensOrCasts( + @PolyInterned ExpressionTree tree) { + ExpressionTree t = withoutParens(tree); + while (t.getKind() == Tree.Kind.TYPE_CAST) { + t = withoutParens(((TypeCastTree) t).getExpression()); + } + return t; + } + + // Obtaining Elements from Trees. + // There are three sets of methods: + // * use elementFromDeclaration whenever the tree is a declaration + // * use elementFromUse when the tree is a use + // * use elementFromTree in other cases; note that it may return null + // This section of the file groups methods by their receiver type; that is, it puts all + // `elementFrom*(FooTree)` methods together. + + /** + * Return the package element corresponding to the given package declaration. + * + * @param tree package declaration + * @return the package element for the given package + */ + public static PackageElement elementFromDeclaration(PackageTree tree) { + PackageElement result = (PackageElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for package tree %s", tree); + } + return result; + } + + // TODO: Document when this may return null. + /** + * Returns the type element corresponding to the given class declaration. + * + *

                  This method returns null instead of crashing when no element exists for the class tree, + * which can happen for certain kinds of anonymous classes, such as Ordering$1 in + * PolyCollectorTypeVar.java in the all-systems test suite and "class MyFileFilter" in + * PurgeTxnLog.java. + * + * @param tree class declaration + * @return the element for the given class + */ + public static TypeElement elementFromDeclaration(ClassTree tree) { + TypeElement result = (TypeElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for class tree %s", tree); + } + return result; + } + + /** + * Returns the type element corresponding to the given class declaration. + * + *

                  The TypeElement may be null for an anonymous class. + * + * @param tree the {@link ClassTree} node to get the element for + * @return the {@link TypeElement} for the given tree + * @deprecated use {@link #elementFromDeclaration(ClassTree)} + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static TypeElement elementFromTree(ClassTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the type element corresponding to the given class declaration. + * + * @param tree the {@link ClassTree} node to get the element for + * @return the {@link TypeElement} for the given tree + * @deprecated use {@link #elementFromDeclaration(ClassTree)} + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static TypeElement elementFromUse(ClassTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the element corresponding to the given tree. + * + * @param tree the tree corresponding to a use of an element + * @return the element for the corresponding declaration, {@code null} otherwise + * @deprecated use {@link #elementFromUse(ExpressionTree)} or {@link + * #elementFromTree(ExpressionTree)} + */ + @Pure + @Deprecated // not for removal; retain to prevent calls to this overload + public static @Nullable Element elementFromDeclaration(ExpressionTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the element corresponding to the given tree. + * + * @param tree the tree corresponding to a use of an element + * @return the element for the corresponding declaration, {@code null} otherwise + */ + @Pure + public static @Nullable Element elementFromTree(ExpressionTree tree) { + return TreeUtils.elementFromTree((Tree) tree); + } + + /** + * Gets the element for the declaration corresponding to this use of an element. To get the + * element for a declaration, use {@link #elementFromDeclaration(ClassTree)}, {@link + * #elementFromDeclaration(MethodTree)}, or {@link #elementFromDeclaration(VariableTree)} + * instead. + * + *

                  This method is just a wrapper around {@link TreeUtils#elementFromTree(Tree)}, but this + * class might be the first place someone looks for this functionality. + * + * @param tree the tree, which must be a use of an element + * @return the element for the corresponding declaration + */ + @Pure + public static Element elementFromUse(ExpressionTree tree) { + Element result = TreeUtils.elementFromTree(tree); + if (result == null) { + throw new BugInCF( + "argument to elementFromUse() has no element: %s [%s]", tree, tree.getClass()); + } + return result; + } + + /** + * Returns the VariableElement corresponding to the given use. + * + * @param tree the tree corresponding to a use of a VariableElement + * @return the element for the corresponding declaration + */ + @Pure + public static VariableElement variableElementFromUse(ExpressionTree tree) { + VariableElement result = TreeUtils.variableElementFromTree(tree); + if (result == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + } + return result; + } + + /** + * Returns the element for the given expression. + * + * @param tree the {@link Tree} node to get the element for + * @return the element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable Element elementFromDeclaration(MemberSelectTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the element for the given expression. + * + * @param tree the {@link Tree} node to get the element for + * @return the element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable Element elementFromTree(MemberSelectTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the element for the given expression. + * + * @param tree a method call + * @return the element for the called method + */ + @Pure + public static Element elementFromUse(MemberSelectTree tree) { + Element result = TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("tree = " + tree); + } + return result; + } + + /** + * Returns the ExecutableElement for the called method. + * + * @param tree the {@link Tree} node to get the element for + * @return the Element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable ExecutableElement elementFromDeclaration(MethodInvocationTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the ExecutableElement for the called method. + * + * @param tree the {@link Tree} node to get the element for + * @return the Element for the given tree, or null if one could not be found + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static @Nullable ExecutableElement elementFromTree(MethodInvocationTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Returns the ExecutableElement for the called method. + * + * @param tree a method call + * @return the ExecutableElement for the called method + */ + @Pure + public static ExecutableElement elementFromUse(MethodInvocationTree tree) { + Element result = TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("tree = %s [%s]", tree, tree.getClass()); + } + if (!(result instanceof ExecutableElement)) { + throw new BugInCF( + "Method elements should be ExecutableElement. Found: %s [%s]", + result, result.getClass()); + } + return (ExecutableElement) result; + } + + /** + * Returns the ExecutableElement for the method reference. + * + * @param tree a method reference + * @return the ExecutableElement for the method reference + */ + @Pure + public static ExecutableElement elementFromUse(MemberReferenceTree tree) { + Element result = elementFromUse((ExpressionTree) tree); + if (!(result instanceof ExecutableElement)) { + throw new BugInCF( + "Method reference elements should be ExecutableElement. Found: %s [%s]", + result, result.getClass()); + } + return (ExecutableElement) result; + } + + /** + * Returns the ExecutableElement for the given method declaration. + * + *

                  The result can be null, when {@code tree} is a method in an anonymous class and that class + * has not been processed yet. To work around this, adapt your processing order. + * + * @param tree a method declaration + * @return the element for the given method + */ + public static ExecutableElement elementFromDeclaration(MethodTree tree) { + ExecutableElement result = (ExecutableElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for method tree %s", tree); + } + return result; + } + + /** + * Returns the ExecutableElement for the given method declaration. + * + * @param tree the {@link MethodTree} node to get the element for + * @return the Element for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromTree(MethodTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the ExecutableElement for the given method declaration. + * + * @param tree the {@link MethodTree} node to get the element for + * @return the Element for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromUse(MethodTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the ExecutableElement for the given constructor invocation. + * + * @param tree the {@link NewClassTree} node to get the element for + * @return the {@link ExecutableElement} for the given tree, or null if one could not be found + * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal + * tree (JCTree) + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromDeclaration(NewClassTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Gets the ExecutableElement for the called constructor, from a constructor invocation. + * + * @param tree the {@link NewClassTree} node to get the element for + * @return the {@link ExecutableElement} for the given tree, or null if one could not be found + * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal + * tree (JCTree) + * @deprecated use elementFromUse + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static ExecutableElement elementFromTree(NewClassTree tree) { + return TreeUtils.elementFromUse(tree); + } + + /** + * Gets the ExecutableElement for the called constructor, from a constructor invocation. + * + * @param tree a constructor invocation + * @return the ExecutableElement for the called constructor + * @see #elementFromUse(NewClassTree) + */ + @Pure + public static ExecutableElement elementFromUse(NewClassTree tree) { + Element result = TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for %s", tree); + } + if (!(result instanceof ExecutableElement)) { + throw new BugInCF( + "Constructor elements should be ExecutableElement. Found: %s [%s]", + result, result.getClass()); + } + return (ExecutableElement) result; + } + + /** + * Returns the VariableElement corresponding to the given variable declaration. + * + * @param tree the variable + * @return the element for the given variable + */ + public static VariableElement elementFromDeclaration(VariableTree tree) { + VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); + // `result` can be null, for example for this variable declaration: + // PureFunc f1 = TestPure1::myPureMethod; + // TODO: check claim above. Initializer expression should have no impact on variable. + if (result == null) { + throw new BugInCF("null element for variable tree %s", tree); + } + return result; + } + + /** + * Returns the VariableElement corresponding to the given variable declaration. + * + * @param tree the {@link VariableTree} node to get the element for + * @return the {@link VariableElement} for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static VariableElement elementFromTree(VariableTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the VariableElement corresponding to the given variable declaration. + * + * @param tree the {@link VariableTree} node to get the element for + * @return the {@link VariableElement} for the given tree + * @deprecated use elementFromDeclaration + */ + @Deprecated // not for removal; retain to prevent calls to this overload + @Pure + public static VariableElement elementFromUse(VariableTree tree) { + return elementFromDeclaration(tree); + } + + /** + * Returns the {@link VariableElement} for the given Tree API node. + * + * @param tree the {@link Tree} node to get the element for + * @return the {@link VariableElement} for the given tree + * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal + * tree (JCTree) + */ + @Pure + public static VariableElement variableElementFromTree(Tree tree) { + VariableElement result = (VariableElement) TreeInfo.symbolFor((JCTree) tree); + if (result == null) { + throw new BugInCF("null element for %s [%s]", tree, tree.getClass()); + } + return result; + } + + /** + * Returns the {@link Element} for the given Tree API node. For an object instantiation returns + * the value of the {@link JCNewClass#constructor} field. + * + *

                  Use this only when you do not statically know whether the tree is a declaration or a use + * of an element. + * + * @param tree the {@link Tree} node to get the element for + * @return the {@link Element} for the given tree, or null if one could not be found + * @throws BugInCF if {@code tree} is null or is not a valid javac-internal tree (JCTree) + */ + @Pure + public static @Nullable Element elementFromTree(Tree tree) { + if (tree == null) { + throw new BugInCF("TreeUtils.elementFromTree: tree is null"); + } + + if (!(tree instanceof JCTree)) { + throw new BugInCF( + "TreeUtils.elementFromTree: tree is not a valid Javac tree but a " + + tree.getClass()); + } + + if (isExpressionTree(tree)) { + tree = withoutParensOrCasts((ExpressionTree) tree); + } + + switch (tree.getKind()) { + // symbol() only works on MethodSelects, so we need to get it manually + // for method invocations. + case METHOD_INVOCATION: + return TreeInfo.symbol(((JCMethodInvocation) tree).getMethodSelect()); + + case ASSIGNMENT: + return TreeInfo.symbol((JCTree) ((AssignmentTree) tree).getVariable()); + + case ARRAY_ACCESS: + return elementFromTree(((ArrayAccessTree) tree).getExpression()); + + case NEW_CLASS: + return ((JCNewClass) tree).constructor; + + case MEMBER_REFERENCE: + // TreeInfo.symbol, which is used in the default case, didn't handle + // member references until JDK8u20. So handle it here. + ExecutableElement memberResult = (ExecutableElement) ((JCMemberReference) tree).sym; + return memberResult; + + default: + Element defaultResult; + if (isTypeDeclaration(tree) + || tree.getKind() == Tree.Kind.VARIABLE + || tree.getKind() == Tree.Kind.METHOD) { + defaultResult = TreeInfo.symbolFor((JCTree) tree); + } else { + defaultResult = TreeInfo.symbol((JCTree) tree); + } + return defaultResult; + } + } + + /** + * Returns the constructor invoked by {@code newClassTree} unless {@code newClassTree} is + * creating an anonymous class. In which case, the super constructor is returned. + * + * @param newClassTree the constructor invocation + * @return the super constructor invoked in the body of the anonymous constructor; or {@link + * #elementFromUse(NewClassTree)} if {@code newClassTree} is not creating an anonymous class + */ + public static ExecutableElement getSuperConstructor(NewClassTree newClassTree) { + if (newClassTree.getClassBody() == null) { + return elementFromUse(newClassTree); + } + JCNewClass jcNewClass = (JCNewClass) newClassTree; + // Anonymous constructor bodies, which are always synthetic, contain exactly one statement + // in the form: + // super(arg1, ...) + // or + // o.super(arg1, ...) + // + // which is a method invocation of the super constructor. + + // The method call is guaranteed to return nonnull. + JCMethodDecl anonConstructor = + (JCMethodDecl) TreeInfo.declarationFor(jcNewClass.constructor, jcNewClass); + assert anonConstructor != null; + assert anonConstructor.body.stats.size() == 1; + JCExpressionStatement stmt = (JCExpressionStatement) anonConstructor.body.stats.head; + JCMethodInvocation superInvok = (JCMethodInvocation) stmt.expr; + return (ExecutableElement) TreeInfo.symbol(superInvok.meth); + } + + /** + * Determine whether the given ExpressionTree has an underlying element. + * + * @param tree the ExpressionTree to test + * @return whether the tree refers to an identifier, member select, or method invocation + */ + @EnsuresNonNullIf(result = true, expression = "elementFromTree(#1)") + @EnsuresNonNullIf(result = true, expression = "elementFromUse(#1)") + @Pure + public static boolean isUseOfElement(ExpressionTree tree) { + ExpressionTree realnode = TreeUtils.withoutParens(tree); + switch (realnode.getKind()) { + case IDENTIFIER: + case MEMBER_SELECT: + case METHOD_INVOCATION: + case NEW_CLASS: + assert elementFromTree(tree) != null : "@AssumeAssertion(nullness): inspection"; + assert elementFromUse(tree) != null : "@AssumeAssertion(nullness): inspection"; + return true; + default: + return false; + } + } + + /** + * Returns true if {@code tree} has a synthetic argument. + * + *

                  For some anonymous classes with an explicit enclosing expression, javac creates a + * synthetic argument to the constructor that is the enclosing expression of the NewClassTree. + * Suppose a programmer writes: + * + *

                  {@code class Outer {
                  +     *   class Inner { }
                  +     *     void method() {
                  +     *       this.new Inner(){};
                  +     *     }
                  +     * }}
                  + * + * Java 9 javac creates the following synthetic tree for {@code this.new Inner(){}}: + * + *
                  {@code new Inner(this) {
                  +     *   (.Outer x0) {
                  +     *     x0.super();
                  +     *   }
                  +     * }}
                  + * + * Java 11 javac creates a different tree without the synthetic argument for {@code this.new + * Inner(){}}; the first line in the below code differs: + * + *
                  {@code this.new Inner() {
                  +     *   (.Outer x0) {
                  +     *     x0.super();
                  +     *   }
                  +     * }}
                  + * + * @param tree a new class tree + * @return true if {@code tree} has a synthetic argument + */ + public static boolean hasSyntheticArgument(NewClassTree tree) { + if (tree.getClassBody() == null || tree.getEnclosingExpression() != null) { + return false; + } + for (Tree member : tree.getClassBody().getMembers()) { + if (member.getKind() == Tree.Kind.METHOD && isConstructor((MethodTree) member)) { + MethodTree methodTree = (MethodTree) member; + StatementTree f = methodTree.getBody().getStatements().get(0); + return TreeUtils.getReceiverTree(((ExpressionStatementTree) f).getExpression()) + != null; + } + } + return false; + } + + /** + * Returns the name of the invoked method. + * + * @param tree the method invocation + * @return the name of the invoked method + */ + public static Name methodName(MethodInvocationTree tree) { + ExpressionTree expr = tree.getMethodSelect(); + if (expr.getKind() == Tree.Kind.IDENTIFIER) { + return ((IdentifierTree) expr).getName(); + } else if (expr.getKind() == Tree.Kind.MEMBER_SELECT) { + return ((MemberSelectTree) expr).getIdentifier(); + } + throw new BugInCF("TreeUtils.methodName: cannot be here: " + tree); + } + + /** + * Returns true if the first statement in the body is a self constructor invocation within a + * constructor. + * + * @param tree the method declaration + * @return true if the first statement in the body is a self constructor invocation within a + * constructor + */ + public static boolean containsThisConstructorInvocation(MethodTree tree) { + if (!TreeUtils.isConstructor(tree) || tree.getBody().getStatements().isEmpty()) { + return false; + } + + StatementTree st = tree.getBody().getStatements().get(0); + if (!(st instanceof ExpressionStatementTree) + || !(((ExpressionStatementTree) st).getExpression() + instanceof MethodInvocationTree)) { + return false; + } + + MethodInvocationTree invocation = + (MethodInvocationTree) ((ExpressionStatementTree) st).getExpression(); + + return "this".contentEquals(TreeUtils.methodName(invocation)); + } + + /** + * Returns the first statement of the tree if it is a block. If it is not a block or an empty + * block, tree is returned. + * + * @param tree any kind of tree + * @return the first statement of the tree if it is a block. If it is not a block or an empty + * block, tree is returned. + */ + public static Tree firstStatement(Tree tree) { + Tree first; + if (tree.getKind() == Tree.Kind.BLOCK) { + BlockTree block = (BlockTree) tree; + if (block.getStatements().isEmpty()) { + first = block; + } else { + first = block.getStatements().iterator().next(); + } + } else { + first = tree; + } + return first; + } + + /** + * Determine whether the given class contains an explicit constructor. + * + * @param tree a class tree + * @return true iff there is an explicit constructor + */ + public static boolean hasExplicitConstructor(ClassTree tree) { + TypeElement elem = TreeUtils.elementFromDeclaration(tree); + for (ExecutableElement constructorElt : + ElementFilter.constructorsIn(elem.getEnclosedElements())) { + if (!isSynthetic(constructorElt)) { + return true; + } + } + return false; + } + + /** + * Returns true if the given method is synthetic. Also returns true if the method is a generated + * default constructor, which does not appear in source code but is not considered synthetic. + * + * @param ee a method or constructor element + * @return true iff the given method is synthetic + */ + public static boolean isSynthetic(ExecutableElement ee) { + MethodSymbol ms = (MethodSymbol) ee; + long mod = ms.flags(); + // GENERATEDCONSTR is for generated constructors, which do not have SYNTHETIC set. + return (mod & (Flags.SYNTHETIC | Flags.GENERATEDCONSTR)) != 0; + } + + /** + * Returns true if the given method is synthetic. + * + * @param tree a method declaration tree + * @return true iff the given method is synthetic + */ + public static boolean isSynthetic(MethodTree tree) { + ExecutableElement ee = TreeUtils.elementFromDeclaration(tree); + return isSynthetic(ee); + } + + /** + * Returns true if the tree is of a diamond type. In contrast to the implementation in TreeInfo, + * this version works on Trees. + * + * @param tree a tree + * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree) + */ + public static boolean isDiamondTree(Tree tree) { + switch (tree.getKind()) { + case ANNOTATED_TYPE: + return isDiamondTree(((AnnotatedTypeTree) tree).getUnderlyingType()); + case PARAMETERIZED_TYPE: + return ((ParameterizedTypeTree) tree).getTypeArguments().isEmpty(); + case NEW_CLASS: + return isDiamondTree(((NewClassTree) tree).getIdentifier()); + default: + return false; + } + } + + /** + * Returns the type arguments to the given new class tree. + * + * @param tree a new class tree + * @return the type arguments to the given new class tree + */ + public static List getTypeArgumentsToNewClassTree(NewClassTree tree) { + Tree typeTree = tree.getIdentifier(); + if (typeTree.getKind() == Kind.ANNOTATED_TYPE) { + typeTree = ((AnnotatedTypeTree) typeTree).getUnderlyingType(); + } + + if (typeTree.getKind() == Kind.PARAMETERIZED_TYPE) { + return ((ParameterizedTypeTree) typeTree).getTypeArguments(); + } + return Collections.emptyList(); + } + + /** + * Returns true if the tree represents a {@code String} concatenation operation. + * + * @param tree a tree + * @return true if the tree represents a {@code String} concatenation operation + */ + public static boolean isStringConcatenation(Tree tree) { + return (tree.getKind() == Tree.Kind.PLUS && TypesUtils.isString(TreeUtils.typeOf(tree))); + } + + /** Returns true if the compound assignment tree is a string concatenation. */ + public static boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) { + return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT + && TypesUtils.isString(TreeUtils.typeOf(tree))); + } + + /** + * Is this method's declared return type "void"? + * + * @param tree a method declaration + * @return true iff method's declared return type is "void" + */ + public static boolean isVoidReturn(MethodTree tree) { + return typeOf(tree.getReturnType()).getKind() == TypeKind.VOID; + } + + /** + * Returns true if the tree is a constant-time expression. + * + *

                  A tree is a constant-time expression if it is: + * + *

                    + *
                  1. a literal tree + *
                  2. a reference to a final variable initialized with a compile time constant + *
                  3. a String concatenation of two compile time constants + *
                  + * + * @param tree the tree to check + * @return true if the tree is a constant-time expression + */ + public static boolean isCompileTimeString(ExpressionTree tree) { + tree = TreeUtils.withoutParens(tree); + if (tree instanceof LiteralTree) { + return true; + } + + if (TreeUtils.isUseOfElement(tree)) { + Element elt = TreeUtils.elementFromUse(tree); + return ElementUtils.isCompileTimeConstant(elt); + } else if (TreeUtils.isStringConcatenation(tree)) { + BinaryTree binOp = (BinaryTree) tree; + return isCompileTimeString(binOp.getLeftOperand()) + && isCompileTimeString(binOp.getRightOperand()); + } else { + return false; + } + } + + /** + * Returns the receiver tree of a field access or a method invocation. + * + * @param expression a field access or a method invocation + * @return the expression's receiver tree, or null if it does not have an explicit receiver + */ + public static @Nullable ExpressionTree getReceiverTree(ExpressionTree expression) { + ExpressionTree receiver; + switch (expression.getKind()) { + case METHOD_INVOCATION: + // Trying to handle receiver calls to trees of the form + // ((m).getArray()) + // returns the type of 'm' in this case + receiver = ((MethodInvocationTree) expression).getMethodSelect(); + + if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { + receiver = ((MemberSelectTree) receiver).getExpression(); + } else { + // It's a method call "m(foo)" without an explicit receiver + return null; + } + break; + case NEW_CLASS: + receiver = ((NewClassTree) expression).getEnclosingExpression(); + break; + case ARRAY_ACCESS: + receiver = ((ArrayAccessTree) expression).getExpression(); + break; + case MEMBER_SELECT: + receiver = ((MemberSelectTree) expression).getExpression(); + // Avoid int.class + if (receiver instanceof PrimitiveTypeTree) { + return null; + } + break; + case IDENTIFIER: + // It's a field access on implicit this or a local variable/parameter. + return null; + default: + return null; + } + if (receiver == null) { + return null; + } + + return TreeUtils.withoutParens(receiver); + } + + // TODO: What about anonymous classes? + // Adding Tree.Kind.NEW_CLASS here doesn't work, because then a + // tree gets cast to ClassTree when it is actually a NewClassTree, + // for example in enclosingClass above. + /** The kinds that represent classes. */ + private static final Set classTreeKinds; + + static { + classTreeKinds = EnumSet.noneOf(Tree.Kind.class); + for (Tree.Kind kind : Tree.Kind.values()) { + if (kind.asInterface() == ClassTree.class) { + classTreeKinds.add(kind); + } + } + } + + /** Kinds that represent a class or method tree. */ + private static final Set classAndMethodTreeKinds; + + static { + classAndMethodTreeKinds = EnumSet.copyOf(classTreeKinds()); + classAndMethodTreeKinds.add(Tree.Kind.METHOD); + } + + /** + * Returns the set of kinds that represent classes and methods. + * + * @return the set of kinds that represent classes and methods + */ + public static Set classAndMethodTreeKinds() { + return classAndMethodTreeKinds; + } + + /** + * Return the set of kinds that represent classes. + * + * @return the set of kinds that represent classes + */ + public static Set classTreeKinds() { + return classTreeKinds; + } + + /** + * Is the given tree kind a class, i.e. a class, enum, interface, or annotation type. + * + * @param tree the tree to test + * @return true, iff the given kind is a class kind + */ + public static boolean isClassTree(Tree tree) { + return classTreeKinds().contains(tree.getKind()); + } + + /** + * The kinds that represent declarations that might have {@code @SuppressWarnings} written on + * them: classes, methods, and variables. + */ + private static final Set declarationTreeKinds; + + static { + declarationTreeKinds = EnumSet.noneOf(Tree.Kind.class); + declarationTreeKinds.addAll(classTreeKinds); + declarationTreeKinds.add(Tree.Kind.METHOD); + declarationTreeKinds.add(Tree.Kind.VARIABLE); + } + + /** + * Return the set of kinds that represent declarations: classes, methods, and variables. + * + * @return the set of kinds that represent declarations + */ + public static Set declarationTreeKinds() { + return declarationTreeKinds; + } + + /** + * Returns true if the given tree is a declaration. + * + * @param tree the tree to test + * @return true if the given tree is a declaration + */ + public static boolean isDeclarationTree(Tree tree) { + return declarationTreeKinds.contains(tree.getKind()); + } + + /** The kinds that represent types. */ + private static final Set typeTreeKinds = + EnumSet.of( + Tree.Kind.PRIMITIVE_TYPE, + Tree.Kind.PARAMETERIZED_TYPE, + Tree.Kind.TYPE_PARAMETER, + Tree.Kind.ARRAY_TYPE, + Tree.Kind.UNBOUNDED_WILDCARD, + Tree.Kind.EXTENDS_WILDCARD, + Tree.Kind.SUPER_WILDCARD, + Tree.Kind.ANNOTATED_TYPE); + + /** + * Return the set of kinds that represent types. + * + * @return the set of kinds that represent types + */ + public static Set typeTreeKinds() { + return typeTreeKinds; + } + + /** + * Is the given tree a type instantiation? + * + *

                  TODO: this is an under-approximation: e.g. an identifier could be either a type use or an + * expression. How can we distinguish. + * + * @param tree the tree to test + * @return true, iff the given tree is a type + */ + public static boolean isTypeTree(Tree tree) { + return typeTreeKinds().contains(tree.getKind()); + } + + /** + * Returns true if the given element is an invocation of the method, or of any method that + * overrides that one. + */ + public static boolean isMethodInvocation( + Tree tree, ExecutableElement method, ProcessingEnvironment env) { + if (!(tree instanceof MethodInvocationTree)) { + return false; + } + MethodInvocationTree methInvok = (MethodInvocationTree) tree; + ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); + if (invoked == null) { + return false; + } + return ElementUtils.isMethod(invoked, method, env); + } + + /** + * Returns true if the argument is an invocation of one of the given methods, or of any method + * that overrides them. + * + * @param tree a tree that might be a method invocation + * @param methods the methods to check for + * @param processingEnv the processing environment + * @return true if the argument is an invocation of one of the given methods, or of any method + * that overrides them + */ + public static boolean isMethodInvocation( + Tree tree, List methods, ProcessingEnvironment processingEnv) { + if (!(tree instanceof MethodInvocationTree)) { + return false; + } + MethodInvocationTree methInvok = (MethodInvocationTree) tree; + ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); + if (invoked == null) { + return false; + } + for (ExecutableElement method : methods) { + if (ElementUtils.isMethod(invoked, method, processingEnv)) { + return true; + } + } + return false; + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one + * matching method. If more than one method takes the same number of formal parameters, then use + * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. + * + * @param type the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + Class type, String methodName, int params, ProcessingEnvironment env) { + String typeName = type.getCanonicalName(); + if (typeName == null) { + throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); + } + return getMethod(typeName, methodName, params, env); + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is not exactly one + * matching method. If more than one method takes the same number of formal parameters, then use + * {@link #getMethod(String, String, ProcessingEnvironment, String...)}. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + @FullyQualifiedName String typeName, + String methodName, + int params, + ProcessingEnvironment env) { + List methods = getMethods(typeName, methodName, params, env); + if (methods.size() == 1) { + return methods.get(0); + } + throw new BugInCF( + "TreeUtils.getMethod(%s, %s, %d): expected 1 match, found %d: %s", + typeName, methodName, params, methods.size(), methods); + } + + /** + * Returns the ExecutableElement for a method declaration. Returns null there is no matching + * method. Errs if there is more than one matching method. If more than one method takes the + * same number of formal parameters, then use {@link #getMethod(String, String, + * ProcessingEnvironment, String...)}. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElement for the specified method, or null + */ + public static @Nullable ExecutableElement getMethodOrNull( + @FullyQualifiedName String typeName, + String methodName, + int params, + ProcessingEnvironment env) { + List methods = getMethods(typeName, methodName, params, env); + if (methods.size() == 0) { + return null; + } else if (methods.size() == 1) { + return methods.get(0); + } else { + throw new BugInCF( + "TreeUtils.getMethod(%s, %s, %d): expected 0 or 1 match, found %d", + typeName, methodName, params, methods.size()); + } + } + + /** + * Returns all ExecutableElements for method declarations of methodName, in class typeName, with + * params formal parameters. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param params the number of formal parameters + * @param env the processing environment + * @return the ExecutableElements for all matching methods + */ + public static List getMethods( + @FullyQualifiedName String typeName, + String methodName, + int params, + ProcessingEnvironment env) { + List methods = new ArrayList<>(1); + TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); + if (typeElt == null) { + throw new UserError("Configuration problem! Could not load type: " + typeName); + } + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName) + && exec.getParameters().size() == params) { + methods.add(exec); + } + } + return methods; + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. + * + * @param type the class that contains the method + * @param methodName the name of the method + * @param env the processing environment + * @param paramTypes the method's formal parameter types + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + Class type, String methodName, ProcessingEnvironment env, String... paramTypes) { + String typeName = type.getCanonicalName(); + if (typeName == null) { + throw new BugInCF("TreeUtils.getMethod: class %s has no canonical name", type); + } + return getMethod(typeName, methodName, env, paramTypes); + } + + /** + * Returns the ExecutableElement for a method declaration. Errs if there is no matching method. + * + * @param typeName the class that contains the method + * @param methodName the name of the method + * @param env the processing environment + * @param paramTypes the method's formal parameter types + * @return the ExecutableElement for the specified method + */ + public static ExecutableElement getMethod( + @FullyQualifiedName String typeName, + String methodName, + ProcessingEnvironment env, + String... paramTypes) { + TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName) + && exec.getParameters().size() == paramTypes.length) { + boolean typesMatch = true; + List params = exec.getParameters(); + for (int i = 0; i < paramTypes.length; i++) { + VariableElement ve = params.get(i); + TypeMirror tm = TypeAnnotationUtils.unannotatedType(ve.asType()); + if (!tm.toString().equals(paramTypes[i])) { + typesMatch = false; + break; + } + } + if (typesMatch) { + return exec; + } + } + } + + // Didn't find an answer. Compose an error message. + List candidates = new ArrayList<>(); + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName)) { + candidates.add(executableElementToString(exec)); + } + } + if (candidates.isEmpty()) { + for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { + candidates.add(executableElementToString(exec)); + } + } + throw new BugInCF( + "TreeUtils.getMethod: found no match for %s.%s(%s); candidates: %s", + typeName, methodName, Arrays.toString(paramTypes), candidates); + } + + /** + * Formats the ExecutableElement in the way that getMethod() expects it. + * + * @param exec an executable element + * @return the ExecutableElement, formatted in the way that getMethod() expects it + */ + private static String executableElementToString(ExecutableElement exec) { + StringJoiner result = new StringJoiner(", ", exec.getSimpleName() + "(", ")"); + for (VariableElement param : exec.getParameters()) { + result.add(TypeAnnotationUtils.unannotatedType(param.asType()).toString()); + } + return result.toString(); + } + + /** + * Determine whether the given expression is either "this" or an outer "C.this". + * + *

                  TODO: Should this also handle "super"? + */ + public static boolean isExplicitThisDereference(ExpressionTree tree) { + if (tree.getKind() == Tree.Kind.IDENTIFIER + && ((IdentifierTree) tree).getName().contentEquals("this")) { + // Explicit this reference "this" + return true; + } + + if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + + MemberSelectTree memSelTree = (MemberSelectTree) tree; + if (memSelTree.getIdentifier().contentEquals("this")) { + // Outer this reference "C.this" + return true; + } + return false; + } + + /** + * Determine whether {@code tree} is a class literal, such as + * + *

                  +     *   Object . class
                  +     * 
                  + * + * @return true iff if tree is a class literal + */ + public static boolean isClassLiteral(Tree tree) { + if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + return "class".equals(((MemberSelectTree) tree).getIdentifier().toString()); + } + + /** + * Determine whether {@code tree} is a field access expression, such as + * + *
                  +     *   f
                  +     *   obj . f
                  +     * 
                  + * + * This method currently also returns true for class literals and qualified this. + * + * @param tree a tree that might be a field access + * @return true iff if tree is a field access expression (implicit or explicit) + */ + public static boolean isFieldAccess(Tree tree) { + return asFieldAccess(tree) != null; + } + + /** + * Return the field that {@code tree} is a field access expression for, or null. + * + *
                  +     *   f
                  +     *   obj . f
                  +     * 
                  + * + * This method currently also returns a non-null value for class literals and qualified this. + * + * @param tree a tree that might be a field access + * @return the element if tree is a field access expression (implicit or explicit); null + * otherwise + */ + // TODO: fix value for class literals and qualified this, which are not field accesses. + public static @Nullable VariableElement asFieldAccess(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + // explicit member access (or a class literal or a qualified this) + MemberSelectTree memberSelect = (MemberSelectTree) tree; + assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(memberSelect); + if (el.getKind().isField()) { + return (VariableElement) el; + } + } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { + // implicit field access + IdentifierTree ident = (IdentifierTree) tree; + assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(ident); + if (el.getKind().isField() + && !ident.getName().contentEquals("this") + && !ident.getName().contentEquals("super")) { + return (VariableElement) el; + } + } + return null; + } + + /** + * Compute the name of the field that the field access {@code tree} accesses. Requires {@code + * tree} to be a field access, as determined by {@code isFieldAccess} (which currently also + * returns true for class literals and qualified this). + * + * @param tree a field access tree + * @return the name of the field accessed by {@code tree} + */ + public static String getFieldName(Tree tree) { + assert isFieldAccess(tree); + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree mtree = (MemberSelectTree) tree; + return mtree.getIdentifier().toString(); + } else { + IdentifierTree itree = (IdentifierTree) tree; + return itree.getName().toString(); + } + } + + /** + * Determine whether {@code tree} refers to a method element, such as. + * + *
                  +     *   m(...)
                  +     *   obj . m(...)
                  +     * 
                  + * + * @return true iff if tree is a method access expression (implicit or explicit) + */ + public static boolean isMethodAccess(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + // explicit method access + MemberSelectTree memberSelect = (MemberSelectTree) tree; + assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(memberSelect); + return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; + } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { + // implicit method access + IdentifierTree ident = (IdentifierTree) tree; + // The field "super" and "this" are also legal methods + if (ident.getName().contentEquals("super") || ident.getName().contentEquals("this")) { + return true; + } + assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind"; + Element el = TreeUtils.elementFromUse(ident); + return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; + } + return false; + } + + /** + * Compute the name of the method that the method access {@code tree} accesses. Requires {@code + * tree} to be a method access, as determined by {@code isMethodAccess}. + * + * @param tree a method access tree + * @return the name of the method accessed by {@code tree} + */ + public static String getMethodName(Tree tree) { + assert isMethodAccess(tree); + if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree mtree = (MemberSelectTree) tree; + return mtree.getIdentifier().toString(); + } else { + IdentifierTree itree = (IdentifierTree) tree; + return itree.getName().toString(); + } + } + + /** + * Return {@code true} if and only if {@code tree} can have a type annotation. + * + * @return {@code true} if and only if {@code tree} can have a type annotation + */ + // TODO: is this implementation precise enough? E.g. does a .class literal work correctly? + public static boolean canHaveTypeAnnotation(Tree tree) { + return ((JCTree) tree).type != null; + } + + /** + * Returns true if and only if the given {@code tree} represents a field access of the given + * {@link VariableElement}. + */ + public static boolean isSpecificFieldAccess(Tree tree, VariableElement var) { + if (tree instanceof MemberSelectTree) { + MemberSelectTree memSel = (MemberSelectTree) tree; + assert isUseOfElement(memSel) : "@AssumeAssertion(nullness): tree kind"; + Element field = TreeUtils.elementFromUse(memSel); + return field.equals(var); + } else if (tree instanceof IdentifierTree) { + IdentifierTree idTree = (IdentifierTree) tree; + assert isUseOfElement(idTree) : "@AssumeAssertion(nullness): tree kind"; + Element field = TreeUtils.elementFromUse(idTree); + return field.equals(var); + } else { + return false; + } + } + + /** + * Returns the VariableElement for a field declaration. + * + * @param typeName the class where the field is declared + * @param fieldName the name of the field + * @param env the processing environment + * @return the VariableElement for typeName.fieldName + */ + public static VariableElement getField( + @FullyQualifiedName String typeName, String fieldName, ProcessingEnvironment env) { + TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); + for (VariableElement var : ElementFilter.fieldsIn(mapElt.getEnclosedElements())) { + if (var.getSimpleName().contentEquals(fieldName)) { + return var; + } + } + throw new BugInCF("TreeUtils.getField: shouldn't be here"); + } + + /** + * Determine whether the given tree represents an ExpressionTree. + * + * @param tree the Tree to test + * @return whether the tree is an ExpressionTree + */ + public static boolean isExpressionTree(Tree tree) { + return tree instanceof ExpressionTree; + } + + /** + * Returns true if this is a super call to the {@link Enum} constructor. + * + * @param tree the method invocation to check + * @return true if this is a super call to the {@link Enum} constructor + */ + public static boolean isEnumSuperCall(MethodInvocationTree tree) { + ExecutableElement ex = TreeUtils.elementFromUse(tree); + assert ex != null : "@AssumeAssertion(nullness): tree kind"; + Name name = ElementUtils.getQualifiedClassName(ex); + assert name != null : "@AssumeAssertion(nullness): assumption"; + boolean correctClass = "java.lang.Enum".contentEquals(name); + boolean correctMethod = "".contentEquals(ex.getSimpleName()); + return correctClass && correctMethod; + } + + /** + * Determine whether the given tree represents a declaration of a type (including type + * parameters). + * + * @param tree the Tree to test + * @return true if the tree is a type declaration + */ + public static boolean isTypeDeclaration(Tree tree) { + return isClassTree(tree) || tree.getKind() == Tree.Kind.TYPE_PARAMETER; + } + + /** + * Returns true if tree is an access of array length. + * + * @param tree tree to check + * @return true if tree is an access of array length + */ + public static boolean isArrayLengthAccess(Tree tree) { + if (tree.getKind() == Tree.Kind.MEMBER_SELECT + && isFieldAccess(tree) + && getFieldName(tree).equals("length")) { + ExpressionTree expressionTree = ((MemberSelectTree) tree).getExpression(); + if (TreeUtils.typeOf(expressionTree).getKind() == TypeKind.ARRAY) { + return true; + } + } + return false; + } + + /** + * Returns true if the given {@link MethodTree} is an anonymous constructor (the constructor for + * an anonymous class). + * + * @param method a method tree that may be an anonymous constructor + * @return true if the given path points to an anonymous constructor, false if it does not + */ + public static boolean isAnonymousConstructor(MethodTree method) { + Element e = elementFromTree(method); + if (e == null || e.getKind() != ElementKind.CONSTRUCTOR) { + return false; + } + TypeElement typeElement = (TypeElement) e.getEnclosingElement(); + return typeElement.getNestingKind() == NestingKind.ANONYMOUS; + } + + /** + * Returns true if the passed constructor is anonymous and has an explicit enclosing expression. + * + * @param con an ExecutableElement of a constructor declaration + * @param tree the NewClassTree of a constructor declaration + * @return true if there is an extra enclosing expression + */ + public static boolean isAnonymousConstructorWithExplicitEnclosingExpression( + ExecutableElement con, NewClassTree tree) { + + return (tree.getEnclosingExpression() != null) + && con.getKind() == ElementKind.CONSTRUCTOR + && ((TypeElement) con.getEnclosingElement()).getNestingKind() + == NestingKind.ANONYMOUS; + } + + /** + * Returns true if the given {@link MethodTree} is a compact canonical constructor (the + * constructor for a record where the parameters are implicitly declared and implicitly assigned + * to the record's fields). This may be an explicitly declared compact canonical constructor or + * an implicitly generated one. + * + * @param method a method tree that may be a compact canonical constructor + * @return true if the given method is a compact canonical constructor + */ + public static boolean isCompactCanonicalRecordConstructor(MethodTree method) { + Symbol s = (Symbol) elementFromTree(method); + if (s == null) { + throw new BugInCF( + "TreeUtils.isCompactCanonicalRecordConstructor: null symbol for method tree: " + + method); + } + return (s.flags() & Flags_RECORD) != 0; + } + + /** + * Returns true if the given {@link Tree} is part of a record that has been automatically + * generated by the compiler. This can be a field that is derived from the record's header field + * list, or an automatically generated canonical constructor. + * + * @param member the {@link Tree} for a member of a record + * @return true if the given path is generated by the compiler + */ + public static boolean isAutoGeneratedRecordMember(Tree member) { + Element e = elementFromTree(member); + if (e == null) { + throw new BugInCF( + "TreeUtils.isAutoGeneratedRecordMember: null element for member tree: " + + member); + } + return ElementUtils.isAutoGeneratedRecordMember(e); + } + + /** + * Converts the given AnnotationTrees to AnnotationMirrors. + * + * @param annoTrees list of annotation trees to convert to annotation mirrors + * @return list of annotation mirrors that represent the given annotation trees + */ + public static List annotationsFromTypeAnnotationTrees( + List annoTrees) { + return CollectionsPlume.mapList(TreeUtils::annotationFromAnnotationTree, annoTrees); + } + + /** + * Converts the given AnnotationTree to an AnnotationMirror. + * + * @param tree annotation tree to convert to an annotation mirror + * @return annotation mirror that represent the given annotation tree + */ + public static AnnotationMirror annotationFromAnnotationTree(AnnotationTree tree) { + return ((JCAnnotation) tree).attribute; + } + + /** + * Converts the given AnnotatedTypeTree to a list of AnnotationMirrors. + * + * @param tree annotated type tree to convert + * @return list of AnnotationMirrors from the tree + */ + public static List annotationsFromTree(AnnotatedTypeTree tree) { + return annotationsFromTypeAnnotationTrees(((JCAnnotatedType) tree).annotations); + } + + /** + * Converts the given TypeParameterTree to a list of AnnotationMirrors. + * + * @param tree type parameter tree to convert + * @return list of AnnotationMirrors from the tree + */ + public static List annotationsFromTree(TypeParameterTree tree) { + return annotationsFromTypeAnnotationTrees(((JCTypeParameter) tree).annotations); + } + + /** + * Converts the given NewArrayTree to a list of AnnotationMirrors. + * + * @param tree new array tree + * @return list of AnnotationMirrors from the tree + */ + public static List annotationsFromArrayCreation( + NewArrayTree tree, int level) { + + assert tree instanceof JCNewArray; + JCNewArray newArray = ((JCNewArray) tree); + + if (level == -1) { + return annotationsFromTypeAnnotationTrees(newArray.annotations); + } + + if (newArray.dimAnnotations.length() > 0 + && (level >= 0) + && (level < newArray.dimAnnotations.size())) { + return annotationsFromTypeAnnotationTrees(newArray.dimAnnotations.get(level)); + } + + return Collections.emptyList(); + } + + /** + * Returns true if the tree is the declaration or use of a local variable. + * + * @param tree the tree to check + * @return true if the tree is the declaration or use of a local variable + */ + public static boolean isLocalVariable(Tree tree) { + if (tree.getKind() == Tree.Kind.VARIABLE) { + VariableElement varElt = elementFromDeclaration((VariableTree) tree); + return ElementUtils.isLocalVariable(varElt); + } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { + ExpressionTree etree = (ExpressionTree) tree; + assert isUseOfElement(etree) : "@AssumeAssertion(nullness): tree kind"; + return ElementUtils.isLocalVariable(elementFromUse(etree)); + } + return false; + } + + /** + * Returns the type as a TypeMirror of {@code tree}. To obtain {@code tree}'s + * AnnotatedTypeMirror, call {@code AnnotatedTypeFactory.getAnnotatedType()}. + * + * @return the type as a TypeMirror of {@code tree} + */ + public static TypeMirror typeOf(Tree tree) { + return ((JCTree) tree).type; + } + + /** + * Determines the type for a method invocation at its call site, which has all type variables + * substituted with the type arguments at the call site. + * + *

                  {@link javax.lang.model.type.TypeVariable} in the returned type should be compared using + * {@link TypesUtils#areSame(TypeVariable, TypeVariable)} because the {@code TypeVariable} will + * be freshly created by this method and will not be the same using {@link + * Object#equals(Object)} or {@link javax.lang.model.util.Types#isSameType(TypeMirror, + * TypeMirror)}. + * + * @param tree the method invocation + * @return the {@link ExecutableType} corresponding to the method invocation at its call site + */ + @Pure + public static ExecutableType typeFromUse(MethodInvocationTree tree) { + TypeMirror type = TreeUtils.typeOf(tree.getMethodSelect()); + if (!(type instanceof ExecutableType)) { + throw new BugInCF( + "TreeUtils.typeFromUse(MethodInvocationTree): type of method select in method" + + " invocation should be ExecutableType. Found: %s", + type); + } + ExecutableType executableType = (ExecutableType) type; + ExecutableElement element = elementFromUse(tree); + if (executableType.getParameterTypes().size() != element.getParameters().size()) { + // Sometimes when the method type is viewpoint-adapted, the vararg parameter disappears, + // just return the declared type. + // For example, + // static void call(MethodHandle methodHandle) throws Throwable { + // methodHandle.invoke(); + // } + return (ExecutableType) element.asType(); + } + return executableType; + } + + /** + * Determines the type for a constructor at its call site given an invocation via {@code new}, + * which has all type variables substituted with the type arguments at the call site. + * + * @param tree the constructor invocation + * @return the {@link ExecutableType} corresponding to the constructor call (i.e., the given + * {@code tree}) at its call site + */ + @Pure + public static ExecutableType typeFromUse(NewClassTree tree) { + if (!(tree instanceof JCTree.JCNewClass)) { + throw new BugInCF("TreeUtils.typeFromUse(NewClassTree): not a javac internal tree"); + } + + JCNewClass newClassTree = (JCNewClass) tree; + TypeMirror type = newClassTree.constructorType; + + if (!(type instanceof ExecutableType)) { + throw new BugInCF( + "TreeUtils.typeFromUse(NewClassTree): type of constructor in new class tree" + + " should be ExecutableType. Found: %s", + type); + } + return (ExecutableType) type; + } + + /** + * Determines the symbol for a constructor given an invocation via {@code new}. + * + * @see #elementFromUse(NewClassTree) + * @param tree the constructor invocation + * @return the {@link ExecutableElement} corresponding to the constructor call in {@code tree} + * @deprecated use elementFromUse instead + */ + @Deprecated // 2022-09-12 + public static ExecutableElement constructor(NewClassTree tree) { + return (ExecutableElement) ((JCNewClass) tree).constructor; + } + + /** + * The type of the lambda or method reference tree is a functional interface type. This method + * returns the single abstract method declared by that functional interface. (The type of this + * method is referred to as the function type.) + * + * @param tree lambda or member reference tree + * @param env the processing environment + * @return the single abstract method declared by the type of the tree + */ + public static ExecutableElement findFunction(Tree tree, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + Types javacTypes = Types.instance(ctx); + return (ExecutableElement) + javacTypes.findDescriptorSymbol(((Type) typeOf(tree)).asElement()); + } + + /** + * Returns true if {@code tree} is an implicitly typed lambda. + * + *

                  A lambda expression whose formal type parameters have inferred types is an implicitly + * typed lambda. (See JLS 15.27.1) + * + * @param tree any kind of tree + * @return true iff {@code tree} is an implicitly typed lambda + */ + public static boolean isImplicitlyTypedLambda(Tree tree) { + return tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION + && ((JCLambda) tree).paramKind == ParameterKind.IMPLICIT; + } + + /** + * This is a duplication of {@code + * com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind}, which is not part of the + * supported javac API. + */ + public enum MemberReferenceKind { + /** super # instMethod */ + SUPER(ReferenceMode.INVOKE, false), + /** Type # instMethod */ + UNBOUND(ReferenceMode.INVOKE, true), + /** Type # staticMethod */ + STATIC(ReferenceMode.INVOKE, false), + /** Expr # instMethod */ + BOUND(ReferenceMode.INVOKE, false), + /** Inner # new */ + IMPLICIT_INNER(ReferenceMode.NEW, false), + /** Toplevel # new */ + TOPLEVEL(ReferenceMode.NEW, false), + /** ArrayType # new */ + ARRAY_CTOR(ReferenceMode.NEW, false); + + /** Whether this kind is a method reference or a constructor reference. */ + final ReferenceMode mode; + + /** Whether this kind is unbound. */ + final boolean unbound; + + /** + * Creates a MemberReferenceKind. + * + * @param mode whether this kind is a method reference or a constructor reference + * @param unbound whether the kind is not bound + */ + MemberReferenceKind(ReferenceMode mode, boolean unbound) { + this.mode = mode; + this.unbound = unbound; + } + + /** + * Whether this kind is unbound. + * + * @return Whether this kind is unbound + */ + public boolean isUnbound() { + return unbound; + } + + /** + * Returns whether this kind is a constructor reference. + * + * @return whether this kind is a constructor reference + */ + public boolean isConstructorReference() { + return mode == ReferenceMode.NEW; + } + + /** + * Returns the kind of member reference {@code tree} is. + * + * @param tree a member reference tree + * @return the kind of member reference {@code tree} is + */ + public static MemberReferenceKind getMemberReferenceKind(MemberReferenceTree tree) { + JCMemberReference memberTree = (JCMemberReference) tree; + switch (memberTree.kind) { + case SUPER: + return SUPER; + case UNBOUND: + return UNBOUND; + case STATIC: + return STATIC; + case BOUND: + return BOUND; + case IMPLICIT_INNER: + return IMPLICIT_INNER; + case TOPLEVEL: + return TOPLEVEL; + case ARRAY_CTOR: + return ARRAY_CTOR; + default: + throw new BugInCF("Unexpected ReferenceKind: %s", memberTree.kind); + } + } + } + + /** + * Determine whether an expression {@link ExpressionTree} has the constant value true, according + * to the compiler logic. + * + * @param tree the expression to be checked + * @return true if {@code tree} has the constant value true + */ + public static boolean isExprConstTrue(ExpressionTree tree) { + assert tree instanceof JCExpression; + if (((JCExpression) tree).type.isTrue()) { + return true; + } + tree = TreeUtils.withoutParens(tree); + if (tree instanceof JCTree.JCBinary) { + JCBinary binTree = (JCBinary) tree; + JCExpression ltree = binTree.lhs; + JCExpression rtree = binTree.rhs; + switch (binTree.getTag()) { + case AND: + return isExprConstTrue(ltree) && isExprConstTrue(rtree); + case OR: + return isExprConstTrue(ltree) || isExprConstTrue(rtree); + default: + break; + } + } + return false; + } + + /** + * Return toString(), but without line separators. + * + * @param tree a tree + * @return a one-line string representation of the tree + */ + public static String toStringOneLine(Tree tree) { + return tree.toString().trim().replaceAll("\\s+", " "); + } + + /** + * Return either {@link #toStringOneLine} if it is no more than {@code length} characters, or + * {@link #toStringOneLine} quoted and truncated. + * + * @param tree a tree + * @param length the maximum length for the result; must be at least 6 + * @return a one-line string representation of the tree that is no longer than {@code length} + * characters long + */ + public static String toStringTruncated(Tree tree, int length) { + if (length < 6) { + throw new BugInCF("TreeUtils.toStringTruncated: bad length " + length); + } + String result = toStringOneLine(tree); + if (result.length() > length) { + // The quoting increases the likelihood that all delimiters are balanced in the result. + // That makes it easier to manipulate the result (such as skipping over it) in an + // editor. The quoting also makes clear that the value is truncated. + result = "\"" + result.substring(0, length - 5) + "...\""; + } + return result; + } + + /** + * Given a javac ExpressionTree representing a fully qualified name such as "java.lang.Object", + * creates a String containing the name. + * + * @param nameExpr an ExpressionTree representing a fully qualified name + * @return a String representation of the fully qualified name + */ + public static String nameExpressionToString(ExpressionTree nameExpr) { + TreeVisitor visitor = + new SimpleTreeVisitor() { + @Override + public String visitIdentifier(IdentifierTree tree, Void p) { + return tree.toString(); + } + + @Override + public String visitMemberSelect(MemberSelectTree tree, Void p) { + return tree.getExpression().accept(this, null) + + "." + + tree.getIdentifier().toString(); + } + }; + return nameExpr.accept(visitor, null); + } + + /** + * Returns true if the binary operator may do a widening primitive conversion. See JLS chapter 5. + * + * @param tree a binary tree + * @return true if the tree's operator does numeric promotion on its arguments + */ + public static boolean isWideningBinary(BinaryTree tree) { + switch (tree.getKind()) { + case LEFT_SHIFT: + case LEFT_SHIFT_ASSIGNMENT: + case RIGHT_SHIFT: + case RIGHT_SHIFT_ASSIGNMENT: + case UNSIGNED_RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + // Strictly speaking, these operators do unary promotion on each argument + // separately. + return true; + + case MULTIPLY: + case MULTIPLY_ASSIGNMENT: + case DIVIDE: + case DIVIDE_ASSIGNMENT: + case REMAINDER: + case REMAINDER_ASSIGNMENT: + case PLUS: + case PLUS_ASSIGNMENT: + case MINUS: + case MINUS_ASSIGNMENT: + + case LESS_THAN: + case LESS_THAN_EQUAL: + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case EQUAL_TO: + case NOT_EQUAL_TO: + + case AND: + case XOR: + case OR: + // These operators do binary promotion on the two arguments together. + return true; + + // TODO: CONDITIONAL_EXPRESSION (?:) sometimes does numeric promotion. + + default: + return false; + } + } + + /** + * Returns the annotations explicitly written on the given type. + * + * @param annoTrees annotations written before a variable/method declaration; null if this type + * is not from such a location. This might contain type annotations that the Java parser + * attached to the declaration rather than to the type. + * @param typeTree the type whose annotations to return + * @return the annotations explicitly written on the given type + */ + public static List getExplicitAnnotationTrees( + @Nullable List annoTrees, Tree typeTree) { + while (true) { + switch (typeTree.getKind()) { + case IDENTIFIER: + case PRIMITIVE_TYPE: + if (annoTrees == null) { + return Collections.emptyList(); + } + return annoTrees; + case ANNOTATED_TYPE: + return ((AnnotatedTypeTree) typeTree).getAnnotations(); + case ARRAY_TYPE: + case TYPE_PARAMETER: + case UNBOUNDED_WILDCARD: + case EXTENDS_WILDCARD: + case SUPER_WILDCARD: + return Collections.emptyList(); + case MEMBER_SELECT: + if (annoTrees == null) { + return Collections.emptyList(); + } + typeTree = ((MemberSelectTree) typeTree).getExpression(); + break; + case PARAMETERIZED_TYPE: + typeTree = ((ParameterizedTypeTree) typeTree).getType(); + break; + case UNION_TYPE: + List alternatives = + ((UnionTypeTree) typeTree).getTypeAlternatives(); + List result = new ArrayList<>(alternatives.size()); + for (Tree alternative : alternatives) { + result.addAll(getExplicitAnnotationTrees(null, alternative)); + } + return result; + default: + throw new BugInCF( + "TreeUtils.getExplicitAnnotationTrees: what typeTree? %s %s %s", + typeTree.getKind(), typeTree.getClass(), typeTree); + } + } + } + + /** + * Return a tree for the default value of the given type. The default value is 0, false, or + * null. + * + * @param typeMirror a type + * @param processingEnv the processing environment + * @return a tree for {@code type}'s default value + */ + public static LiteralTree getDefaultValueTree( + TypeMirror typeMirror, ProcessingEnvironment processingEnv) { + typeMirror = TypeAnnotationUtils.unannotatedType(typeMirror); + switch (typeMirror.getKind()) { + case BYTE: + case SHORT: + case INT: + // Byte should be (byte) 0, but this probably doesn't matter so just use int 0; + // Short should be (short) 0, but this probably doesn't matter so just use int 0; + return TreeUtils.createLiteral(TypeTag.INT, 0, typeMirror, processingEnv); + case CHAR: + // Value of a char literal needs to be stored as an integer because + // LiteralTree#getValue converts it from an integer to a char before being + // returned. + return TreeUtils.createLiteral( + TypeTag.CHAR, (int) '\u0000', typeMirror, processingEnv); + case LONG: + return TreeUtils.createLiteral(TypeTag.LONG, 0L, typeMirror, processingEnv); + case FLOAT: + return TreeUtils.createLiteral(TypeTag.FLOAT, 0.0f, typeMirror, processingEnv); + case DOUBLE: + return TreeUtils.createLiteral(TypeTag.DOUBLE, 0.0d, typeMirror, processingEnv); + case BOOLEAN: + // Value of a boolean literal needs to be stored as an integer because + // LiteralTree#getValue converts it from an integer to a boolean before being + // returned. + return TreeUtils.createLiteral(TypeTag.BOOLEAN, 0, typeMirror, processingEnv); + default: + return TreeUtils.createLiteral( + TypeTag.BOT, + null, + processingEnv.getTypeUtils().getNullType(), + processingEnv); + } + } + + /** + * Creates a LiteralTree for the given value. + * + * @param typeTag the literal's type tag + * @param value a wrapped primitive, null, or a String + * @param typeMirror the typeMirror for the literal + * @param processingEnv the processing environment + * @return a LiteralTree for the given type tag and value + */ + public static LiteralTree createLiteral( + TypeTag typeTag, + @Nullable Object value, + TypeMirror typeMirror, + ProcessingEnvironment processingEnv) { + Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); + TreeMaker maker = TreeMaker.instance(context); + LiteralTree result = maker.Literal(typeTag, value); + ((JCLiteral) result).type = (Type) typeMirror; + return result; + } + + /** + * Returns true if the given tree evaluates to {@code null}. + * + * @param t a tree + * @return true if the given tree evaluates to {@code null} + */ + public static boolean isNullExpression(Tree t) { + while (true) { + switch (t.getKind()) { + case PARENTHESIZED: + t = ((ParenthesizedTree) t).getExpression(); + break; + case TYPE_CAST: + t = ((TypeCastTree) t).getExpression(); + break; + case NULL_LITERAL: + return true; + default: + return false; + } + } + } + + /** + * Returns true if two expressions originating from the same scope are identical, i.e. they are + * syntactically represented in the same way (modulo parentheses) and represent the same value. + * + *

                  If the expression includes one or more method calls, assumes the method calls are + * deterministic. + * + * @param expr1 the first expression to compare + * @param expr2 the second expression to compare; expr2 must originate from the same scope as + * expr1 + * @return true if the expressions expr1 and expr2 are syntactically identical + */ + public static boolean sameTree(ExpressionTree expr1, ExpressionTree expr2) { + expr1 = TreeUtils.withoutParens(expr1); + expr2 = TreeUtils.withoutParens(expr2); + // Converting to a string in order to compare is somewhat inefficient, and it doesn't handle + // internal parentheses. We could create a visitor instead. + return expr1.getKind() == expr2.getKind() && expr1.toString().equals(expr2.toString()); + } + + /** + * Returns true if this is the default case for a switch statement or expression. (Also, returns + * true if {@code caseTree} is {@code case null, default:}.) + * + * @param caseTree a case tree + * @return true if {@code caseTree} is the default case for a switch statement or expression + * @deprecated use {@link CaseUtils#isDefaultCaseTree(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static boolean isDefaultCaseTree(CaseTree caseTree) { + return CaseUtils.isDefaultCaseTree(caseTree); + } + + /** + * Returns true if this is a case rule (as opposed to a case statement). + * + * @param caseTree a case tree + * @return true if {@code caseTree} is a case rule + * @deprecated use {@link CaseUtils#isCaseRule(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static boolean isCaseRule(CaseTree caseTree) { + return CaseUtils.isCaseRule(caseTree); + } + + /** + * Get the list of expressions from a case expression. For the default case, this is empty. + * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can be + * multiple expressions per case. + * + * @param caseTree the case expression to get the expressions from + * @return the list of expressions in the case + * @deprecated use {@link CaseUtils#getExpressions(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static List caseTreeGetExpressions(CaseTree caseTree) { + return CaseUtils.getExpressions(caseTree); + } + + /** + * Returns the body of the case statement if it is of the form {@code case -> + * }. This method should only be called if {@link CaseTree#getStatements()} returns + * null. + * + * @param caseTree the case expression to get the body from + * @return the body of the case tree + * @deprecated use {@link CaseUtils#getBody(CaseTree)} + */ + @Deprecated // 2023-09-26 + public static @Nullable Tree caseTreeGetBody(CaseTree caseTree) { + return CaseUtils.getBody(caseTree); + } + + /** + * Returns true if {@code tree} is a {@code BindingPatternTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code BindingPatternTree} + */ + public static boolean isBindingPatternTree(Tree tree) { + return tree.getKind().name().contentEquals("BINDING_PATTERN"); + } + + /** + * Returns the binding variable of {@code bindingPatternTree}. + * + * @param bindingPatternTree the BindingPatternTree whose binding variable is returned + * @return the binding variable of {@code bindingPatternTree} + * @deprecated use {@link BindingPatternUtils#getVariable(Tree)} + */ + @Deprecated // 2023-09-26 + public static VariableTree bindingPatternTreeGetVariable(Tree bindingPatternTree) { + return BindingPatternUtils.getVariable(bindingPatternTree); + } + + /** + * Returns true if {@code tree} is a {@code DeconstructionPatternTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code DeconstructionPatternTree} + */ + public static boolean isDeconstructionPatternTree(Tree tree) { + return tree.getKind().name().contentEquals("DECONSTRUCTION_PATTERN"); + } + + /** + * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does not + * have a pattern, including if the JDK version does not support instance-of patterns. + * + * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned + * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist + * @deprecated use {@link InstanceOfUtils#getPattern(InstanceOfTree)} + */ + @Deprecated // 2023-09-26 + public static @Nullable Tree instanceOfTreeGetPattern(InstanceOfTree instanceOfTree) { + return InstanceOfUtils.getPattern(instanceOfTree); + } + + /** + * Returns the selector expression of {@code switchExpressionTree}. For example + * + *

                  +     *   switch ( expression ) { ... }
                  +     * 
                  + * + * @param switchExpressionTree the switch expression whose selector expression is returned + * @return the selector expression of {@code switchExpressionTree} + * @deprecated use {@link SwitchExpressionUtils#getExpression(Tree)} + */ + @Deprecated // 2023-09-26 + public static ExpressionTree switchExpressionTreeGetExpression(Tree switchExpressionTree) { + return SwitchExpressionUtils.getExpression(switchExpressionTree); + } + + /** + * Returns the cases of {@code switchExpressionTree}. For example + * + *
                  +     *   switch ( expression ) {
                  +     *     cases
                  +     *   }
                  +     * 
                  + * + * @param switchExpressionTree the switch expression whose cases are returned + * @return the cases of {@code switchExpressionTree} + * @deprecated use {@link SwitchExpressionUtils#getCases(Tree)} + */ + @Deprecated // 2023-09-26 + public static List switchExpressionTreeGetCases(Tree switchExpressionTree) { + return SwitchExpressionUtils.getCases(switchExpressionTree); + } + + /** + * Returns true if {@code switchTree} has a null case label. + * + * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} + * @return true if {@code switchTree} has a null case label + */ + public static boolean hasNullCaseLabel(Tree switchTree) { + if (!atLeastJava21) { + return false; + } + List cases; + if (isSwitchStatement(switchTree)) { + cases = ((SwitchTree) switchTree).getCases(); + } else { + cases = SwitchExpressionUtils.getCases(switchTree); + } + for (CaseTree caseTree : cases) { + List labels = CaseUtils.getLabels(caseTree); + for (Tree label : labels) { + if (label.getKind() == Kind.NULL_LITERAL) { + return true; + } + } + } + return false; + } + + /** + * Returns true if the given tree is a switch statement (as opposed to a switch expression). + * + * @param tree the switch statement or expression to check + * @return true if the given tree is a switch statement (as opposed to a switch expression) + */ + public static boolean isSwitchStatement(Tree tree) { + return tree.getKind() == Tree.Kind.SWITCH; + } + + /** + * Returns true if the given switch statement tree is an enhanced switch statement, as described + * in JLS + * 14.11.2. + * + * @param switchTree the switch statement to check + * @return true if the given tree is an enhanced switch statement + */ + public static boolean isEnhancedSwitchStatement(SwitchTree switchTree) { + TypeMirror exprType = typeOf(switchTree.getExpression()); + // TODO: this should be only char, byte, short, int, Character, Byte, Short, Integer. Is the + // over-approximation a problem? + Element exprElem = TypesUtils.getTypeElement(exprType); + boolean isNotEnum = exprElem == null || exprElem.getKind() != ElementKind.ENUM; + if (!TypesUtils.isPrimitiveOrBoxed(exprType) + && !TypesUtils.isString(exprType) + && isNotEnum) { + return true; + } + + for (CaseTree caseTree : switchTree.getCases()) { + for (Tree caseLabel : CaseUtils.getLabels(caseTree)) { + if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL + || TreeUtils.isBindingPatternTree(caseLabel) + || TreeUtils.isDeconstructionPatternTree(caseLabel)) { + return true; + } + } + } + + return false; + } + + /** + * Returns true if the given tree is a switch expression. + * + * @param tree a tree to check + * @return true if the given tree is a switch expression + */ + public static boolean isSwitchExpression(Tree tree) { + return tree.getKind().name().equals("SWITCH_EXPRESSION"); + } + + /** + * Returns true if the given tree is a yield expression. + * + * @param tree a tree to check + * @return true if the given tree is a yield expression + */ + public static boolean isYield(Tree tree) { + return tree.getKind().name().equals("YIELD"); + } + + /** + * Returns the value (expression) for {@code yieldTree}. + * + * @param yieldTree the yield tree + * @return the value (expression) for {@code yieldTree} + * @deprecated use {@link YieldUtils#getValue(Tree)} + */ + @Deprecated // 2023-09-26 + public static ExpressionTree yieldTreeGetValue(Tree yieldTree) { + return YieldUtils.getValue(yieldTree); + } + + /** + * Returns true if the {@code variableTree} is declared using the {@code var} Java keyword. + * + * @param variableTree the variableTree to check + * @return true if the variableTree is declared using the {@code var} Java keyword + */ + public static boolean isVariableTreeDeclaredUsingVar(VariableTree variableTree) { + JCExpression type = (JCExpression) variableTree.getType(); + return type != null && type.pos == Position.NOPOS; + } + + /** + * Returns true if the given method/constructor invocation is a varargs invocation. + * + * @param tree a method/constructor invocation + * @return true if the given method/constructor invocation is a varargs invocation + */ + public static boolean isVarArgs(Tree tree) { + switch (tree.getKind()) { + case METHOD_INVOCATION: + return isVarArgs((MethodInvocationTree) tree); + case NEW_CLASS: + return isVarArgs((NewClassTree) tree); + default: + throw new BugInCF("TreeUtils.isVarArgs: unexpected kind of tree: " + tree); + } + } + + /** + * Returns true if the given method invocation is a varargs invocation. + * + * @param invok the method invocation + * @return true if the given method invocation is a varargs invocation + */ + public static boolean isVarArgs(MethodInvocationTree invok) { + return isVarArgs(elementFromUse(invok), invok.getArguments()); + } + + /** + * Returns true if the given method invocation is an invocation of a method with a vararg + * parameter, and the invocation has zero vararg actuals. + * + * @param invok the method invocation + * @return true if the given method invocation is an invocation of a method with a vararg + * parameter, and the invocation has with zero vararg actuals + */ + public static boolean isCallToVarArgsMethodWithZeroVarargsActuals(MethodInvocationTree invok) { + if (!TreeUtils.isVarArgs(invok)) { + return false; + } + int numParams = elementFromUse(invok).getParameters().size(); + // The comparison of the number of arguments to the number of formals (minus one) checks + // whether + // there are no varargs actuals + return invok.getArguments().size() == numParams - 1; + } + + /** + * Returns true if the given constructor invocation is a varargs invocation. + * + * @param newClassTree the constructor invocation + * @return true if the given method invocation is a varargs invocation + */ + public static boolean isVarArgs(NewClassTree newClassTree) { + return isVarArgs(elementFromUse(newClassTree), newClassTree.getArguments()); + } + + /** + * Returns true if a method/constructor invocation is a varargs invocation. + * + * @param method the method or constructor + * @param args the arguments passed at the invocation + * @return true if the given method/constructor invocation is a varargs invocation + */ + private static boolean isVarArgs( + ExecutableElement method, List args) { + if (!method.isVarArgs()) { + return false; + } + + List parameters = method.getParameters(); + if (parameters.size() != args.size()) { + return true; + } + + TypeMirror lastArgType = typeOf(args.get(args.size() - 1)); + if (lastArgType.getKind() == TypeKind.NULL) { + return false; + } + if (lastArgType.getKind() != TypeKind.ARRAY) { + return true; + } + + TypeMirror varargsParamType = parameters.get(parameters.size() - 1).asType(); + return TypesUtils.getArrayDepth(varargsParamType) != TypesUtils.getArrayDepth(lastArgType); + } + + /** + * Determine whether the given tree is of Kind RECORD, in a way that works on all versions of + * Java. + * + * @param tree the tree to get the kind for + * @return whether the tree is of the kind RECORD + */ + public static boolean isRecordTree(Tree tree) { + Tree.Kind kind = tree.getKind(); + // Must use String comparison because we may be on an older JDK: + return kind.name().equals("RECORD"); + } + + /** + * Calls getKind() on the given tree, but returns CLASS if the Kind is RECORD. This is needed + * because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD can't + * be used in case statements, and usually we want to treat them the same as classes. + * + * @param tree the tree to get the kind for + * @return the kind of the tree, but CLASS if the kind was RECORD + */ + public static Tree.Kind getKindRecordAsClass(Tree tree) { + if (isRecordTree(tree)) { + return Tree.Kind.CLASS; + } + return tree.getKind(); + } + + /** + * Returns true if the {@code tree} is a binary tree that performs a comparison. + * + * @param tree the tree to check + * @return whether the tree represents a binary comparison + */ + public static boolean isBinaryComparison(BinaryTree tree) { + return BINARY_COMPARISON_TREE_KINDS.contains(tree.getKind()); + } + + /** + * Returns the result of {@code treeMaker.Select(base, sym)}. + * + * @param treeMaker the TreeMaker to use + * @param base the expression for the select + * @param sym the symbol to select + * @return the JCFieldAccess tree to select sym in base + */ + public static JCFieldAccess Select(TreeMaker treeMaker, Tree base, Symbol sym) { + // The return type of TreeMaker.Select changed in + // https://github.com/openjdk/jdk/commit/a917fb3fcf0fe1a4c4de86c08ae4041462848b82#diff-0f1b4da56622ccb5ff716ce5a9532819fc5573179a1eb2c803d053196824891aR726 + // When the ECF is compiled with Java 21+, even with `--source/target 8`, this will lead to + // a java.lang.NoSuchMethodError: 'com.sun.tools.javac.tree.JCTree$JCFieldAccess + // com.sun.tools.javac.tree.TreeMaker.Select(com.sun.tools.javac.tree.JCTree$JCExpression, + // com.sun.tools.javac.code.Symbol)' + // when executed on Java <21. + // Therefore, always use reflection to access TreeMaker.Select. + // Hopefully, the JVM optimizes the reflective access quickly. + try { + assert TREEMAKER_SELECT != null : "@AssumeAssertion(nullness): initialization"; + JCFieldAccess jfa = (JCFieldAccess) TREEMAKER_SELECT.invoke(treeMaker, base, sym); + if (jfa != null) { + return jfa; + } else { + throw new BugInCF( + "TreeUtils.Select: TreeMaker.Select returned null for tree: %s", base); + } + } catch (InvocationTargetException | IllegalAccessException e) { + throw new BugInCF("TreeUtils.Select: reflection failed for tree: %s", base, e); + } + } + + /** + * Returns the result of {@code treeMaker.Select(base, name)}. + * + * @param treeMaker the TreeMaker to use + * @param base the expression for the select + * @param name the name to select + * @return the JCFieldAccess tree to select sym in base + */ + public static JCFieldAccess Select( + TreeMaker treeMaker, Tree base, com.sun.tools.javac.util.Name name) { + // There's no need for reflection here. The only reason we even declare this method + // is so that callers don't have to remember which overload we provide a wrapper around. + return treeMaker.Select((JCExpression) base, name); + } + + /** + * Returns true if {@code tree} is an explicitly typed lambda. + * + *

                  An lambda whose formal type parameters have declared types or with no parameters is an + * explicitly typed lambda. (See JLS 15.27.1) + * + * @param tree any kind of tree + * @return true iff {@code tree} is an implicitly typed lambda + */ + public static boolean isExplicitlyTypeLambda(Tree tree) { + return tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION + && ((JCLambda) tree).paramKind == ParameterKind.EXPLICIT; + } + + /** + * Returns all expressions that might be the result of {@code lambda}. + * + * @param lambda a lambda with or without a body + * @return a list of expressions that are returned by {@code lambda} + */ + public static List getReturnedExpressions(LambdaExpressionTree lambda) { + if (lambda.getBodyKind() == BodyKind.EXPRESSION) { + return Collections.singletonList((ExpressionTree) lambda.getBody()); + } + + List returnExpressions = new ArrayList<>(); + TreeScanner scanner = + new TreeScanner() { + @Override + public Void visitReturn(ReturnTree tree, Void o) { + if (tree.getExpression() != null) { + returnExpressions.add(tree.getExpression()); + } + return super.visitReturn(tree, o); + } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { + // Don't visit inside anther lambda. + return null; + } + }; + scanner.scan(lambda.getBody(), null); + return returnExpressions; + } + + /** + * Returns whether or not {@code ref} is an exact method reference. + * + *

                  From JLS 15.13.1 "If there is only one possible compile-time declaration with only one + * possible invocation, it is said to be exact." + * + * @param ref a method reference + * @return whether or not {@code ref} is an exact method reference + */ + public static boolean isExactMethodReference(MemberReferenceTree ref) { + // Seems like overloaded means the same thing as inexact. + // overloadKind is set + // com.sun.tools.javac.comp.DeferredAttr.DeferredChecker.visitReference() + // IsExact: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.1-400 + // Treat OverloadKind.ERROR as overloaded. + return ((JCMemberReference) ref).getOverloadKind() == OverloadKind.UNOVERLOADED; + } + + /** + * Returns whether or not {@code expression} is a poly expression as defined in JLS 15.2. + * + * @param expression expression + * @return whether or not {@code expression} is a poly expression + */ + public static boolean isPolyExpression(ExpressionTree expression) { + return !isStandaloneExpression(expression); + } + + /** + * Returns whether or not {@code expression} is a standalone expression as defined in JLS 15.2. + * + * @param expression expression + * @return whether or not {@code expression} is a standalone expression + */ + public static boolean isStandaloneExpression(ExpressionTree expression) { + if (expression instanceof JCTree.JCExpression) { + if (((JCTree.JCExpression) expression).isStandalone()) { + return true; + } + if (expression.getKind() == Tree.Kind.METHOD_INVOCATION) { + // This seems to be a bug in at least Java 11. If a method has type arguments, then + // it is a standalone expression. + return !((MethodInvocationTree) expression).getTypeArguments().isEmpty(); + } + } + return false; + } + + /** + * Was applicability by variable arity invocation necessary to determine the method signature? + * + *

                  This isn't the same as {@link ExecutableElement#isVarArgs()}. That method returns true if + * the method accepts a variable number of arguments. This method returns true if the method + * invocation actually used that fact to invoke the method. + * + * @param methodInvocation a method or constructor invocation + * @return whether applicability by variable arity invocation is necessary to determine the + * method signature + */ + public static boolean isVarArgMethodCall(ExpressionTree methodInvocation) { + if (methodInvocation.getKind() == Tree.Kind.METHOD_INVOCATION) { + return ((JCMethodInvocation) methodInvocation).varargsElement != null; + } else if (methodInvocation.getKind() == Tree.Kind.NEW_CLASS) { + return ((JCNewClass) methodInvocation).varargsElement != null; + } else { + return false; + } + } + + /** + * Is the tree a reference to a constructor of a generic class whose type argument isn't + * specified? For example, {@code HashSet::new)}. + * + * @param tree may or may not be a {@link MemberReferenceTree} + * @return true if tree is a reference to a constructor of a generic class whose type argument + * isn't specified + */ + public static boolean isDiamondMemberReference(ExpressionTree tree) { + if (tree.getKind() != Tree.Kind.MEMBER_REFERENCE) { + return false; + } + MemberReferenceTree memRef = (MemberReferenceTree) tree; + TypeMirror type = TreeUtils.typeOf(memRef.getQualifierExpression()); + if (memRef.getMode() == ReferenceMode.NEW && type.getKind() == TypeKind.DECLARED) { + // No need to check array::new because the generic arrays can't be created. + TypeElement classElt = (TypeElement) ((Type) type).asElement(); + DeclaredType classTypeMirror = (DeclaredType) classElt.asType(); + return !classTypeMirror.getTypeArguments().isEmpty() + && ((Type) type).getTypeArguments().isEmpty(); + } + return false; + } + + /** + * Return whether {@code tree} is a method reference with a raw type to the left of {@code ::}. + * For example, {@code Class::getName}. + * + * @param tree a tree + * @return whether {@code tree} is a method reference with a raw type to the left of {@code ::} + */ + public static boolean isLikeDiamondMemberReference(ExpressionTree tree) { + if (tree.getKind() != Tree.Kind.MEMBER_REFERENCE) { + return false; + } + MemberReferenceTree memberReferenceTree = (MemberReferenceTree) tree; + if (TreeUtils.MemberReferenceKind.getMemberReferenceKind(memberReferenceTree).isUnbound()) { + TypeMirror preColonTreeType = typeOf(memberReferenceTree.getQualifierExpression()); + return TypesUtils.isRaw(preColonTreeType); + } + return false; + } + + /** + * Returns whether the method reference tree needs type argument inference. + * + * @param memberReferenceTree a method reference tree + * @return whether the method reference tree needs type argument inference + */ + public static boolean needsTypeArgInference(MemberReferenceTree memberReferenceTree) { + if (isDiamondMemberReference(memberReferenceTree) + || isLikeDiamondMemberReference(memberReferenceTree)) { + return true; + } + + ExecutableElement element = TreeUtils.elementFromUse(memberReferenceTree); + return !element.getTypeParameters().isEmpty() + && (memberReferenceTree.getTypeArguments() == null + || memberReferenceTree.getTypeArguments().isEmpty()); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java index 5e62ba65131..6b37401301d 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtilsAfterJava11.java @@ -5,15 +5,18 @@ import com.sun.source.tree.InstanceOfTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.ClassGetName; +import org.checkerframework.dataflow.qual.Pure; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; + import javax.lang.model.SourceVersion; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.ClassGetName; -import org.checkerframework.dataflow.qual.Pure; /** * This class contains util methods for reflective accessing Tree classes and methods that were @@ -21,556 +24,569 @@ */ public class TreeUtilsAfterJava11 { - /** Don't use. */ - private TreeUtilsAfterJava11() { - throw new AssertionError("Cannot be instantiated."); - } - - /** The latest source version supported by this compiler. */ - private static final int sourceVersionNumber = - Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); - - /** Utility methods for accessing {@code BindingPatternTree} methods. */ - public static class BindingPatternUtils { - /** Don't use. */ - private BindingPatternUtils() { - throw new AssertionError("Cannot be instantiated."); + private TreeUtilsAfterJava11() { + throw new AssertionError("Cannot be instantiated."); } - /** The {@code BindingPatternTree.getVariable} method for Java 16 and higher; null otherwise. */ - private static @Nullable Method GET_VARIABLE = null; + /** The latest source version supported by this compiler. */ + private static final int sourceVersionNumber = + Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); - /** - * Returns the binding variable of {@code bindingPatternTree}. - * - * @param bindingPatternTree the BindingPatternTree whose binding variable is returned - * @return the binding variable of {@code bindingPatternTree} - */ - public static VariableTree getVariable(Tree bindingPatternTree) { - assertVersionAtLeast(16); - if (GET_VARIABLE == null) { - Class bindingPatternClass = classForName("com.sun.source.tree.BindingPatternTree"); - GET_VARIABLE = getMethod(bindingPatternClass, "getVariable"); - } - return (VariableTree) invokeNonNullResult(GET_VARIABLE, bindingPatternTree); - } - } + /** Utility methods for accessing {@code BindingPatternTree} methods. */ + public static class BindingPatternUtils { - /** Utility methods for accessing {@code CaseTree} methods. */ - public static class CaseUtils { + /** Don't use. */ + private BindingPatternUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** Don't use. */ - private CaseUtils() { - throw new AssertionError("Cannot be instantiated."); + /** + * The {@code BindingPatternTree.getVariable} method for Java 16 and higher; null otherwise. + */ + private static @Nullable Method GET_VARIABLE = null; + + /** + * Returns the binding variable of {@code bindingPatternTree}. + * + * @param bindingPatternTree the BindingPatternTree whose binding variable is returned + * @return the binding variable of {@code bindingPatternTree} + */ + public static VariableTree getVariable(Tree bindingPatternTree) { + assertVersionAtLeast(16); + if (GET_VARIABLE == null) { + Class bindingPatternClass = + classForName("com.sun.source.tree.BindingPatternTree"); + GET_VARIABLE = getMethod(bindingPatternClass, "getVariable"); + } + return (VariableTree) invokeNonNullResult(GET_VARIABLE, bindingPatternTree); + } } - /** The {@code CaseTree.getExpressions} method for Java 12 and higher; null otherwise. */ - private static @Nullable Method GET_EXPRESSIONS = null; + /** Utility methods for accessing {@code CaseTree} methods. */ + public static class CaseUtils { - /** The {@code CaseTree.getBody} method for Java 12 and higher; null otherwise. */ - private static @Nullable Method GET_BODY = null; + /** Don't use. */ + private CaseUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** The {@code CaseTree.getKind()} method for Java 12 and higher; null otherwise. */ - private static @Nullable Method GET_KIND = null; + /** The {@code CaseTree.getExpressions} method for Java 12 and higher; null otherwise. */ + private static @Nullable Method GET_EXPRESSIONS = null; - /** The {@code CaseTree.getLabels} method for Java 21 and higher; null otherwise. */ - private static @Nullable Method GET_LABELS = null; + /** The {@code CaseTree.getBody} method for Java 12 and higher; null otherwise. */ + private static @Nullable Method GET_BODY = null; - /** The {@code CaseTree.getGuard} method for Java 21 and higher; null otherwise. */ - private static @Nullable Method GET_GUARD = null; + /** The {@code CaseTree.getKind()} method for Java 12 and higher; null otherwise. */ + private static @Nullable Method GET_KIND = null; - /** - * Returns true if this is a case rule (as opposed to a case statement). - * - * @param caseTree a case tree - * @return true if {@code caseTree} is a case rule - */ - public static boolean isCaseRule(CaseTree caseTree) { - if (sourceVersionNumber < 12) { - return false; - } - - if (GET_KIND == null) { - GET_KIND = getMethod(CaseTree.class, "getCaseKind"); - } - Enum kind = (Enum) invokeNonNullResult(GET_KIND, caseTree); - return kind.name().contentEquals("RULE"); - } + /** The {@code CaseTree.getLabels} method for Java 21 and higher; null otherwise. */ + private static @Nullable Method GET_LABELS = null; - /** - * Returns the body of the case statement if it is of the form {@code case -> - * }. This method should only be called if {@link CaseTree#getStatements()} returns - * null. - * - * @param caseTree the case expression to get the body from - * @return the body of the case tree - */ - public static @Nullable Tree getBody(CaseTree caseTree) { - assertVersionAtLeast(12); - if (GET_BODY == null) { - GET_BODY = getMethod(CaseTree.class, "getBody"); - } - return (Tree) invoke(GET_BODY, caseTree); - } + /** The {@code CaseTree.getGuard} method for Java 21 and higher; null otherwise. */ + private static @Nullable Method GET_GUARD = null; - /** - * Returns true if this is the default case for a switch statement or expression. (Also, returns - * true if {@code caseTree} is {@code case null, default:}.) - * - * @param caseTree a case tree - * @return true if {@code caseTree} is the default case for a switch statement or expression - */ - public static boolean isDefaultCaseTree(CaseTree caseTree) { - if (sourceVersionNumber >= 21) { - for (Tree label : getLabels(caseTree, true)) { - if (isDefaultCaseLabelTree(label)) { - return true; - } - } - return false; - } else { - return getExpressions(caseTree).isEmpty(); - } - } + /** + * Returns true if this is a case rule (as opposed to a case statement). + * + * @param caseTree a case tree + * @return true if {@code caseTree} is a case rule + */ + public static boolean isCaseRule(CaseTree caseTree) { + if (sourceVersionNumber < 12) { + return false; + } - /** - * Returns true if {@code tree} is a {@code DefaultCaseLabelTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code DefaultCaseLabelTree} - */ - public static boolean isDefaultCaseLabelTree(Tree tree) { - return tree.getKind().name().contentEquals("DEFAULT_CASE_LABEL"); - } + if (GET_KIND == null) { + GET_KIND = getMethod(CaseTree.class, "getCaseKind"); + } + Enum kind = (Enum) invokeNonNullResult(GET_KIND, caseTree); + return kind.name().contentEquals("RULE"); + } - /** - * Get the list of labels from a case expression. For {@code default}, this is empty. For {@code - * case null, default}, the list contains {@code null}. Otherwise, in JDK 11 and earlier, this - * is a list of a single expression tree. In JDK 12+, the list may have multiple expression - * trees. In JDK 21+, the list might contain a single pattern tree. - * - * @param caseTree the case expression to get the labels from - * @return the list of case labels in the case - */ - public static List getLabels(CaseTree caseTree) { - return getLabels(caseTree, false); - } + /** + * Returns the body of the case statement if it is of the form {@code case -> + * }. This method should only be called if {@link CaseTree#getStatements()} + * returns null. + * + * @param caseTree the case expression to get the body from + * @return the body of the case tree + */ + public static @Nullable Tree getBody(CaseTree caseTree) { + assertVersionAtLeast(12); + if (GET_BODY == null) { + GET_BODY = getMethod(CaseTree.class, "getBody"); + } + return (Tree) invoke(GET_BODY, caseTree); + } - /** - * Get the list of labels from a case expression. - * - *

                  For JDKs before 21, if {@code caseTree} is the default case, then the returned list is - * empty. - * - *

                  For 21+ JDK, if {@code useDefaultCaseLabelTree} is false, then if {@code caseTree} is the - * default case or {@code case null, default}, then the returned list is empty. If {@code - * useDefaultCaseLabelTree} is true, then if {@code caseTree} is the default case the returned - * contains just a {@code DefaultCaseLabelTree}. If {@code useDefaultCaseLabelTree} is false, - * then if {@code caseTree} is {@code case null, default} the returned list is a {@code - * DefaultCaseLabelTree} and the expression tree for {@code null}. - * - *

                  Otherwise, in JDK 11 and earlier, this is a list of a single expression tree. In JDK 12+, - * the list may have multiple expression trees. In JDK 21+, the list might contain a single - * pattern tree. - * - * @param caseTree the case expression to get the labels from - * @param useDefaultCaseLabelTree weather the result should contain a {@code - * DefaultCaseLabelTree}. - * @return the list of case labels in the case - */ - private static List getLabels( - CaseTree caseTree, boolean useDefaultCaseLabelTree) { - if (sourceVersionNumber >= 21) { - if (GET_LABELS == null) { - GET_LABELS = getMethod(CaseTree.class, "getLabels"); + /** + * Returns true if this is the default case for a switch statement or expression. (Also, + * returns true if {@code caseTree} is {@code case null, default:}.) + * + * @param caseTree a case tree + * @return true if {@code caseTree} is the default case for a switch statement or expression + */ + public static boolean isDefaultCaseTree(CaseTree caseTree) { + if (sourceVersionNumber >= 21) { + for (Tree label : getLabels(caseTree, true)) { + if (isDefaultCaseLabelTree(label)) { + return true; + } + } + return false; + } else { + return getExpressions(caseTree).isEmpty(); + } } - @SuppressWarnings("unchecked") - List caseLabelTrees = - (List) invokeNonNullResult(GET_LABELS, caseTree); - List labels = new ArrayList<>(); - for (Tree caseLabel : caseLabelTrees) { - if (isDefaultCaseLabelTree(caseLabel)) { - if (useDefaultCaseLabelTree) { - labels.add(caseLabel); + + /** + * Returns true if {@code tree} is a {@code DefaultCaseLabelTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code DefaultCaseLabelTree} + */ + public static boolean isDefaultCaseLabelTree(Tree tree) { + return tree.getKind().name().contentEquals("DEFAULT_CASE_LABEL"); + } + + /** + * Get the list of labels from a case expression. For {@code default}, this is empty. For + * {@code case null, default}, the list contains {@code null}. Otherwise, in JDK 11 and + * earlier, this is a list of a single expression tree. In JDK 12+, the list may have + * multiple expression trees. In JDK 21+, the list might contain a single pattern tree. + * + * @param caseTree the case expression to get the labels from + * @return the list of case labels in the case + */ + public static List getLabels(CaseTree caseTree) { + return getLabels(caseTree, false); + } + + /** + * Get the list of labels from a case expression. + * + *

                  For JDKs before 21, if {@code caseTree} is the default case, then the returned list is + * empty. + * + *

                  For 21+ JDK, if {@code useDefaultCaseLabelTree} is false, then if {@code caseTree} is + * the default case or {@code case null, default}, then the returned list is empty. If + * {@code useDefaultCaseLabelTree} is true, then if {@code caseTree} is the default case the + * returned contains just a {@code DefaultCaseLabelTree}. If {@code useDefaultCaseLabelTree} + * is false, then if {@code caseTree} is {@code case null, default} the returned list is a + * {@code DefaultCaseLabelTree} and the expression tree for {@code null}. + * + *

                  Otherwise, in JDK 11 and earlier, this is a list of a single expression tree. In JDK + * 12+, the list may have multiple expression trees. In JDK 21+, the list might contain a + * single pattern tree. + * + * @param caseTree the case expression to get the labels from + * @param useDefaultCaseLabelTree weather the result should contain a {@code + * DefaultCaseLabelTree}. + * @return the list of case labels in the case + */ + private static List getLabels( + CaseTree caseTree, boolean useDefaultCaseLabelTree) { + if (sourceVersionNumber >= 21) { + if (GET_LABELS == null) { + GET_LABELS = getMethod(CaseTree.class, "getLabels"); + } + @SuppressWarnings("unchecked") + List caseLabelTrees = + (List) invokeNonNullResult(GET_LABELS, caseTree); + List labels = new ArrayList<>(); + for (Tree caseLabel : caseLabelTrees) { + if (isDefaultCaseLabelTree(caseLabel)) { + if (useDefaultCaseLabelTree) { + labels.add(caseLabel); + } + } else if (ConstantCaseLabelUtils.isConstantCaseLabelTree(caseLabel)) { + labels.add(ConstantCaseLabelUtils.getConstantExpression(caseLabel)); + } else if (PatternCaseLabelUtils.isPatternCaseLabelTree(caseLabel)) { + labels.add(PatternCaseLabelUtils.getPattern(caseLabel)); + } + } + return labels; } - } else if (ConstantCaseLabelUtils.isConstantCaseLabelTree(caseLabel)) { - labels.add(ConstantCaseLabelUtils.getConstantExpression(caseLabel)); - } else if (PatternCaseLabelUtils.isPatternCaseLabelTree(caseLabel)) { - labels.add(PatternCaseLabelUtils.getPattern(caseLabel)); - } - } - return labels; - } - return getExpressions(caseTree); - } + return getExpressions(caseTree); + } - /** - * Get the list of expressions from a case expression. For the default case, this is empty. - * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can be - * multiple expressions per case. - * - * @param caseTree the case expression to get the expressions from - * @return the list of expressions in the case - */ - @SuppressWarnings("unchecked") - public static List getExpressions(CaseTree caseTree) { - if (sourceVersionNumber >= 12) { - if (GET_EXPRESSIONS == null) { - GET_EXPRESSIONS = getMethod(CaseTree.class, "getExpressions"); - } - return (List) invokeNonNullResult(GET_EXPRESSIONS, caseTree); - } - @SuppressWarnings("deprecation") // getExpression is deprecated in Java 21 - ExpressionTree expression = caseTree.getExpression(); - if (expression == null) { - return Collections.emptyList(); - } - return Collections.singletonList(expression); - } + /** + * Get the list of expressions from a case expression. For the default case, this is empty. + * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can + * be multiple expressions per case. + * + * @param caseTree the case expression to get the expressions from + * @return the list of expressions in the case + */ + @SuppressWarnings("unchecked") + public static List getExpressions(CaseTree caseTree) { + if (sourceVersionNumber >= 12) { + if (GET_EXPRESSIONS == null) { + GET_EXPRESSIONS = getMethod(CaseTree.class, "getExpressions"); + } + return (List) + invokeNonNullResult(GET_EXPRESSIONS, caseTree); + } + @SuppressWarnings("deprecation") // getExpression is deprecated in Java 21 + ExpressionTree expression = caseTree.getExpression(); + if (expression == null) { + return Collections.emptyList(); + } + return Collections.singletonList(expression); + } - /** - * Returns the guard, the expression after {@code when}, of {@code caseTree}. Wrapper around - * {@code CaseTree#getGuard} that can be called on any version of Java. - * - * @param caseTree the case tree - * @return the guard on the case tree or null if one does not exist - */ - public static @Nullable ExpressionTree getGuard(CaseTree caseTree) { - if (sourceVersionNumber < 21) { - return null; - } - if (GET_GUARD == null) { - GET_GUARD = getMethod(CaseTree.class, "getGuard"); - } - return (ExpressionTree) invoke(GET_GUARD, caseTree); + /** + * Returns the guard, the expression after {@code when}, of {@code caseTree}. Wrapper around + * {@code CaseTree#getGuard} that can be called on any version of Java. + * + * @param caseTree the case tree + * @return the guard on the case tree or null if one does not exist + */ + public static @Nullable ExpressionTree getGuard(CaseTree caseTree) { + if (sourceVersionNumber < 21) { + return null; + } + if (GET_GUARD == null) { + GET_GUARD = getMethod(CaseTree.class, "getGuard"); + } + return (ExpressionTree) invoke(GET_GUARD, caseTree); + } } - } - /** Utility methods for accessing {@code ConstantCaseLabelTree} methods. */ - public static class ConstantCaseLabelUtils { + /** Utility methods for accessing {@code ConstantCaseLabelTree} methods. */ + public static class ConstantCaseLabelUtils { - /** Don't use. */ - private ConstantCaseLabelUtils() { - throw new AssertionError("Cannot be instantiated."); - } + /** Don't use. */ + private ConstantCaseLabelUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** - * The {@code ConstantCaseLabelTree.getConstantExpression} method for Java 21 and higher; null - * otherwise. - */ - private static @Nullable Method GET_CONSTANT_EXPRESSION = null; + /** + * The {@code ConstantCaseLabelTree.getConstantExpression} method for Java 21 and higher; + * null otherwise. + */ + private static @Nullable Method GET_CONSTANT_EXPRESSION = null; + + /** + * Returns true if {@code tree} is a {@code ConstantCaseLabelTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code ConstantCaseLabelTree} + */ + public static boolean isConstantCaseLabelTree(Tree tree) { + return tree.getKind().name().contentEquals("CONSTANT_CASE_LABEL"); + } - /** - * Returns true if {@code tree} is a {@code ConstantCaseLabelTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code ConstantCaseLabelTree} - */ - public static boolean isConstantCaseLabelTree(Tree tree) { - return tree.getKind().name().contentEquals("CONSTANT_CASE_LABEL"); + /** + * Wrapper around {@code ConstantCaseLabelTree#getConstantExpression}. + * + * @param constantCaseLabelTree a ConstantCaseLabelTree tree + * @return the expression in the {@code constantCaseLabelTree} + */ + public static ExpressionTree getConstantExpression(Tree constantCaseLabelTree) { + assertVersionAtLeast(21); + if (GET_CONSTANT_EXPRESSION == null) { + Class constantCaseLabelTreeClass = + classForName("com.sun.source.tree.ConstantCaseLabelTree"); + GET_CONSTANT_EXPRESSION = + getMethod(constantCaseLabelTreeClass, "getConstantExpression"); + } + return (ExpressionTree) + invokeNonNullResult(GET_CONSTANT_EXPRESSION, constantCaseLabelTree); + } } - /** - * Wrapper around {@code ConstantCaseLabelTree#getConstantExpression}. - * - * @param constantCaseLabelTree a ConstantCaseLabelTree tree - * @return the expression in the {@code constantCaseLabelTree} - */ - public static ExpressionTree getConstantExpression(Tree constantCaseLabelTree) { - assertVersionAtLeast(21); - if (GET_CONSTANT_EXPRESSION == null) { - Class constantCaseLabelTreeClass = - classForName("com.sun.source.tree.ConstantCaseLabelTree"); - GET_CONSTANT_EXPRESSION = getMethod(constantCaseLabelTreeClass, "getConstantExpression"); - } - return (ExpressionTree) invokeNonNullResult(GET_CONSTANT_EXPRESSION, constantCaseLabelTree); - } - } + /** Utility methods for accessing {@code DeconstructionPatternTree} methods. */ + public static class DeconstructionPatternUtils { - /** Utility methods for accessing {@code DeconstructionPatternTree} methods. */ - public static class DeconstructionPatternUtils { + /** Don't use. */ + private DeconstructionPatternUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** Don't use. */ - private DeconstructionPatternUtils() { - throw new AssertionError("Cannot be instantiated."); + /** + * The {@code DeconstructionPatternTree.getDeconstructor} method for Java 21 and higher; + * null otherwise. + */ + private static @Nullable Method GET_DECONSTRUCTOR = null; + + /** + * The {@code DeconstructionPatternTree.getNestedPatterns} method for Java 21 and higher; + * null otherwise. + */ + private static @Nullable Method GET_NESTED_PATTERNS = null; + + /** + * Returns the deconstruction type of {@code tree}. Wrapper around {@code + * DeconstructionPatternTree#getDeconstructor}. + * + * @param tree the DeconstructionPatternTree + * @return the deconstructor of {@code DeconstructionPatternTree} + */ + public static ExpressionTree getDeconstructor(Tree tree) { + assertVersionAtLeast(21); + if (GET_DECONSTRUCTOR == null) { + Class deconstructionPatternClass = + classForName("com.sun.source.tree.DeconstructionPatternTree"); + GET_DECONSTRUCTOR = getMethod(deconstructionPatternClass, "getDeconstructor"); + } + return (ExpressionTree) invokeNonNullResult(GET_DECONSTRUCTOR, tree); + } + + /** + * Wrapper around {@code DeconstructionPatternTree#getNestedPatterns}. + * + * @param tree the DeconstructionPatternTree + * @return the nested patterns of {@code DeconstructionPatternTree} + */ + @SuppressWarnings("unchecked") + public static List getNestedPatterns(Tree tree) { + assertVersionAtLeast(21); + if (GET_NESTED_PATTERNS == null) { + Class deconstructionPatternClass = + classForName("com.sun.source.tree.DeconstructionPatternTree"); + GET_NESTED_PATTERNS = getMethod(deconstructionPatternClass, "getNestedPatterns"); + } + return (List) invokeNonNullResult(GET_NESTED_PATTERNS, tree); + } } - /** - * The {@code DeconstructionPatternTree.getDeconstructor} method for Java 21 and higher; null - * otherwise. - */ - private static @Nullable Method GET_DECONSTRUCTOR = null; + /** Utility methods for accessing {@code PatternCaseLabelTree} methods. */ + public static class PatternCaseLabelUtils { - /** - * The {@code DeconstructionPatternTree.getNestedPatterns} method for Java 21 and higher; null - * otherwise. - */ - private static @Nullable Method GET_NESTED_PATTERNS = null; + /** Don't use. */ + private PatternCaseLabelUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** - * Returns the deconstruction type of {@code tree}. Wrapper around {@code - * DeconstructionPatternTree#getDeconstructor}. - * - * @param tree the DeconstructionPatternTree - * @return the deconstructor of {@code DeconstructionPatternTree} - */ - public static ExpressionTree getDeconstructor(Tree tree) { - assertVersionAtLeast(21); - if (GET_DECONSTRUCTOR == null) { - Class deconstructionPatternClass = - classForName("com.sun.source.tree.DeconstructionPatternTree"); - GET_DECONSTRUCTOR = getMethod(deconstructionPatternClass, "getDeconstructor"); - } - return (ExpressionTree) invokeNonNullResult(GET_DECONSTRUCTOR, tree); - } + /** The PatternCaseLabelTree.getPattern method for Java 21 and higher; null otherwise. */ + private static @Nullable Method GET_PATTERN = null; + + /** + * Returns whether {@code tree} is a {@code PatternCaseLabelTree}. + * + * @param tree a tree to check + * @return true if {@code tree} is a {@code PatternCaseLabelTree} + */ + public static boolean isPatternCaseLabelTree(Tree tree) { + return tree.getKind().name().contentEquals("PATTERN_CASE_LABEL"); + } - /** - * Wrapper around {@code DeconstructionPatternTree#getNestedPatterns}. - * - * @param tree the DeconstructionPatternTree - * @return the nested patterns of {@code DeconstructionPatternTree} - */ - @SuppressWarnings("unchecked") - public static List getNestedPatterns(Tree tree) { - assertVersionAtLeast(21); - if (GET_NESTED_PATTERNS == null) { - Class deconstructionPatternClass = - classForName("com.sun.source.tree.DeconstructionPatternTree"); - GET_NESTED_PATTERNS = getMethod(deconstructionPatternClass, "getNestedPatterns"); - } - return (List) invokeNonNullResult(GET_NESTED_PATTERNS, tree); + /** + * Wrapper around {@code PatternCaseLabelTree#getPattern}. + * + * @param patternCaseLabelTree a PatternCaseLabelTree tree + * @return the {@code PatternTree} in the {@code patternCaseLabelTree} + */ + public static Tree getPattern(Tree patternCaseLabelTree) { + assertVersionAtLeast(21); + if (GET_PATTERN == null) { + Class patternCaseLabelClass = + classForName("com.sun.source.tree.PatternCaseLabelTree"); + GET_PATTERN = getMethod(patternCaseLabelClass, "getPattern"); + } + return (Tree) invokeNonNullResult(GET_PATTERN, patternCaseLabelTree); + } } - } - /** Utility methods for accessing {@code PatternCaseLabelTree} methods. */ - public static class PatternCaseLabelUtils { + /** Utility methods for accessing {@code SwitchExpressionTree} methods. */ + public static class SwitchExpressionUtils { - /** Don't use. */ - private PatternCaseLabelUtils() { - throw new AssertionError("Cannot be instantiated."); - } + /** Don't use. */ + private SwitchExpressionUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** The PatternCaseLabelTree.getPattern method for Java 21 and higher; null otherwise. */ - private static @Nullable Method GET_PATTERN = null; + /** + * The {@code SwitchExpressionTree.getExpression} method for Java 12 and higher; null + * otherwise. + */ + private static @Nullable Method GET_EXPRESSION = null; + + /** + * The {@code SwitchExpressionTree.getCases} method for Java 12 and higher; null otherwise. + */ + private static @Nullable Method GET_CASES = null; + + /** + * Returns the cases of {@code switchExpressionTree}. For example + * + *

                  +         *   switch ( expression ) {
                  +         *     cases
                  +         *   }
                  +         * 
                  + * + * @param switchExpressionTree the switch expression whose cases are returned + * @return the cases of {@code switchExpressionTree} + */ + @SuppressWarnings("unchecked") + public static List getCases(Tree switchExpressionTree) { + assertVersionAtLeast(12); + if (GET_CASES == null) { + Class switchExpressionClass = + classForName("com.sun.source.tree.SwitchExpressionTree"); + GET_CASES = getMethod(switchExpressionClass, "getCases"); + } + return (List) invokeNonNullResult(GET_CASES, switchExpressionTree); + } - /** - * Returns whether {@code tree} is a {@code PatternCaseLabelTree}. - * - * @param tree a tree to check - * @return true if {@code tree} is a {@code PatternCaseLabelTree} - */ - public static boolean isPatternCaseLabelTree(Tree tree) { - return tree.getKind().name().contentEquals("PATTERN_CASE_LABEL"); + /** + * Returns the selector expression of {@code switchExpressionTree}. For example + * + *
                  +         *   switch ( expression ) { ... }
                  +         * 
                  + * + * @param switchExpressionTree the switch expression whose selector expression is returned + * @return the selector expression of {@code switchExpressionTree} + */ + public static ExpressionTree getExpression(Tree switchExpressionTree) { + assertVersionAtLeast(12); + if (GET_EXPRESSION == null) { + Class switchExpressionClass = + classForName("com.sun.source.tree.SwitchExpressionTree"); + GET_EXPRESSION = getMethod(switchExpressionClass, "getExpression"); + } + return (ExpressionTree) invokeNonNullResult(GET_EXPRESSION, switchExpressionTree); + } } - /** - * Wrapper around {@code PatternCaseLabelTree#getPattern}. - * - * @param patternCaseLabelTree a PatternCaseLabelTree tree - * @return the {@code PatternTree} in the {@code patternCaseLabelTree} - */ - public static Tree getPattern(Tree patternCaseLabelTree) { - assertVersionAtLeast(21); - if (GET_PATTERN == null) { - Class patternCaseLabelClass = classForName("com.sun.source.tree.PatternCaseLabelTree"); - GET_PATTERN = getMethod(patternCaseLabelClass, "getPattern"); - } - return (Tree) invokeNonNullResult(GET_PATTERN, patternCaseLabelTree); - } - } + /** Utility methods for accessing {@code YieldTree} methods. */ + public static class YieldUtils { - /** Utility methods for accessing {@code SwitchExpressionTree} methods. */ - public static class SwitchExpressionUtils { + /** Don't use. */ + private YieldUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** Don't use. */ - private SwitchExpressionUtils() { - throw new AssertionError("Cannot be instantiated."); + /** The {@code YieldTree.getValue} method for Java 13 and higher; null otherwise. */ + private static @Nullable Method GET_VALUE = null; + + /** + * Returns the value (expression) for {@code yieldTree}. + * + * @param yieldTree the yield tree + * @return the value (expression) for {@code yieldTree} + */ + public static ExpressionTree getValue(Tree yieldTree) { + assertVersionAtLeast(13); + if (GET_VALUE == null) { + Class yieldTreeClass = classForName("com.sun.source.tree.YieldTree"); + GET_VALUE = getMethod(yieldTreeClass, "getValue"); + } + return (ExpressionTree) invokeNonNullResult(GET_VALUE, yieldTree); + } } - /** - * The {@code SwitchExpressionTree.getExpression} method for Java 12 and higher; null otherwise. - */ - private static @Nullable Method GET_EXPRESSION = null; + /** Utility methods for accessing {@code InstanceOfTree} methods. */ + public static class InstanceOfUtils { + + /** Don't use. */ + private InstanceOfUtils() { + throw new AssertionError("Cannot be instantiated."); + } - /** The {@code SwitchExpressionTree.getCases} method for Java 12 and higher; null otherwise. */ - private static @Nullable Method GET_CASES = null; + /** The {@code InstanceOfTree.getPattern} method for Java 16 and higher; null otherwise. */ + private static @Nullable Method GET_PATTERN = null; + + /** + * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does + * not have a pattern, including if the JDK version does not support instance-of patterns. + * + * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned + * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist + */ + @Pure + public static @Nullable Tree getPattern(InstanceOfTree instanceOfTree) { + if (sourceVersionNumber < 16) { + return null; + } + if (GET_PATTERN == null) { + GET_PATTERN = getMethod(InstanceOfTree.class, "getPattern"); + } + return (Tree) invoke(GET_PATTERN, instanceOfTree); + } + } /** - * Returns the cases of {@code switchExpressionTree}. For example - * - *
                  -     *   switch ( expression ) {
                  -     *     cases
                  -     *   }
                  -     * 
                  + * Asserts that the latest source version is at least {@code version}. * - * @param switchExpressionTree the switch expression whose cases are returned - * @return the cases of {@code switchExpressionTree} + * @param version version to check + * @throws BugInCF if the latest version is smaller than {@code version} */ - @SuppressWarnings("unchecked") - public static List getCases(Tree switchExpressionTree) { - assertVersionAtLeast(12); - if (GET_CASES == null) { - Class switchExpressionClass = classForName("com.sun.source.tree.SwitchExpressionTree"); - GET_CASES = getMethod(switchExpressionClass, "getCases"); - } - return (List) invokeNonNullResult(GET_CASES, switchExpressionTree); + private static void assertVersionAtLeast(int version) { + if (sourceVersionNumber < version) { + throw new BugInCF( + "Method call requires at least Java version %s, but the current version is %s", + version, sourceVersionNumber); + } } /** - * Returns the selector expression of {@code switchExpressionTree}. For example - * - *
                  -     *   switch ( expression ) { ... }
                  -     * 
                  + * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as + * {@code BugInCF} exceptions. If the results is {@code null} a {@code BugInCF} is thrown. * - * @param switchExpressionTree the switch expression whose selector expression is returned - * @return the selector expression of {@code switchExpressionTree} + * @param method a method + * @param receiver the receiver for the method + * @return the result of invoking {@code method} on {@code receiver} */ - public static ExpressionTree getExpression(Tree switchExpressionTree) { - assertVersionAtLeast(12); - if (GET_EXPRESSION == null) { - Class switchExpressionClass = classForName("com.sun.source.tree.SwitchExpressionTree"); - GET_EXPRESSION = getMethod(switchExpressionClass, "getExpression"); - } - return (ExpressionTree) invokeNonNullResult(GET_EXPRESSION, switchExpressionTree); - } - } - - /** Utility methods for accessing {@code YieldTree} methods. */ - public static class YieldUtils { - - /** Don't use. */ - private YieldUtils() { - throw new AssertionError("Cannot be instantiated."); + private static Object invokeNonNullResult(Method method, Tree receiver) { + Object result = invoke(method, receiver); + if (result != null) { + return result; + } + throw new BugInCF( + "Expected nonnull result for method invocation: %s for tree: %s", + method.getName(), receiver); } - /** The {@code YieldTree.getValue} method for Java 13 and higher; null otherwise. */ - private static @Nullable Method GET_VALUE = null; - /** - * Returns the value (expression) for {@code yieldTree}. + * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as + * {@code BugInCF} exceptions. * - * @param yieldTree the yield tree - * @return the value (expression) for {@code yieldTree} + * @param method a method + * @param receiver the receiver for the method + * @return the result of invoking {@code method} on {@code receiver} */ - public static ExpressionTree getValue(Tree yieldTree) { - assertVersionAtLeast(13); - if (GET_VALUE == null) { - Class yieldTreeClass = classForName("com.sun.source.tree.YieldTree"); - GET_VALUE = getMethod(yieldTreeClass, "getValue"); - } - return (ExpressionTree) invokeNonNullResult(GET_VALUE, yieldTree); + private static @Nullable Object invoke(Method method, Tree receiver) { + try { + return method.invoke(receiver); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new BugInCF( + e, "Reflection failed for method: %s for tree: %s", method.getName(), receiver); + } } - } - - /** Utility methods for accessing {@code InstanceOfTree} methods. */ - public static class InstanceOfUtils { - /** Don't use. */ - private InstanceOfUtils() { - throw new AssertionError("Cannot be instantiated."); + /** + * Returns the {@link Method} object for the method with name {@code name} in class {@code + * clazz}. Rethrowing any exceptions as {@code BugInCF} exceptions. + * + * @param clazz a class + * @param name a method name + * @return the {@link Method} object for the method with name {@code name} in class {@code + * clazz} + */ + private static Method getMethod(Class clazz, String name) { + try { + return clazz.getMethod(name); + } catch (NoSuchMethodException e) { + throw new BugInCF("Method %s not found in class %s", name, clazz); + } } - /** The {@code InstanceOfTree.getPattern} method for Java 16 and higher; null otherwise. */ - private static @Nullable Method GET_PATTERN = null; - /** - * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does not - * have a pattern, including if the JDK version does not support instance-of patterns. + * Returns the class named {@code name}. Rethrows any exceptions as {@code BugInCF} exceptions. * - * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned - * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist + * @param name a class name + * @return the class named {@code name} */ - @Pure - public static @Nullable Tree getPattern(InstanceOfTree instanceOfTree) { - if (sourceVersionNumber < 16) { - return null; - } - if (GET_PATTERN == null) { - GET_PATTERN = getMethod(InstanceOfTree.class, "getPattern"); - } - return (Tree) invoke(GET_PATTERN, instanceOfTree); - } - } - - /** - * Asserts that the latest source version is at least {@code version}. - * - * @param version version to check - * @throws BugInCF if the latest version is smaller than {@code version} - */ - private static void assertVersionAtLeast(int version) { - if (sourceVersionNumber < version) { - throw new BugInCF( - "Method call requires at least Java version %s, but the current version is %s", - version, sourceVersionNumber); - } - } - - /** - * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as {@code - * BugInCF} exceptions. If the results is {@code null} a {@code BugInCF} is thrown. - * - * @param method a method - * @param receiver the receiver for the method - * @return the result of invoking {@code method} on {@code receiver} - */ - private static Object invokeNonNullResult(Method method, Tree receiver) { - Object result = invoke(method, receiver); - if (result != null) { - return result; - } - throw new BugInCF( - "Expected nonnull result for method invocation: %s for tree: %s", - method.getName(), receiver); - } - - /** - * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as {@code - * BugInCF} exceptions. - * - * @param method a method - * @param receiver the receiver for the method - * @return the result of invoking {@code method} on {@code receiver} - */ - private static @Nullable Object invoke(Method method, Tree receiver) { - try { - return method.invoke(receiver); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new BugInCF( - e, "Reflection failed for method: %s for tree: %s", method.getName(), receiver); - } - } - - /** - * Returns the {@link Method} object for the method with name {@code name} in class {@code clazz}. - * Rethrowing any exceptions as {@code BugInCF} exceptions. - * - * @param clazz a class - * @param name a method name - * @return the {@link Method} object for the method with name {@code name} in class {@code clazz} - */ - private static Method getMethod(Class clazz, String name) { - try { - return clazz.getMethod(name); - } catch (NoSuchMethodException e) { - throw new BugInCF("Method %s not found in class %s", name, clazz); - } - } - - /** - * Returns the class named {@code name}. Rethrows any exceptions as {@code BugInCF} exceptions. - * - * @param name a class name - * @return the class named {@code name} - */ - private static Class classForName(@ClassGetName String name) { - try { - return Class.forName(name); - } catch (ClassNotFoundException e) { - throw new BugInCF("Class not found " + name); + private static Class classForName(@ClassGetName String name) { + try { + return Class.forName(name); + } catch (ClassNotFoundException e) { + throw new BugInCF("Class not found " + name); + } } - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java index 844a182660b..e62289abf47 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java @@ -11,9 +11,13 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Pair; + +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.Arrays; import java.util.Iterator; import java.util.Map; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -27,7 +31,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import org.checkerframework.checker.nullness.qual.NonNull; /** * A collection of helper methods related to type annotation handling. @@ -36,629 +39,646 @@ */ public class TypeAnnotationUtils { - // Class cannot be instantiated. - private TypeAnnotationUtils() { - throw new AssertionError("Class TypeAnnotationUtils cannot be instantiated."); - } - - /** - * Check whether a TypeCompound is contained in a list of TypeCompounds. - * - * @param list the input list of TypeCompounds - * @param tc the TypeCompound to find - * @param types type utilities - * @return true, iff a TypeCompound equal to tc is contained in list - */ - public static boolean isTypeCompoundContained( - List list, TypeCompound tc, Types types) { - for (Attribute.TypeCompound rawat : list) { - if (typeCompoundEquals(rawat, tc, types)) { - return true; - } + // Class cannot be instantiated. + private TypeAnnotationUtils() { + throw new AssertionError("Class TypeAnnotationUtils cannot be instantiated."); } - return false; - } - - /** - * Compares two TypeCompound objects (e.g., annotations). - * - * @param tc1 the first TypeCompound to compare - * @param tc2 the second TypeCompound to compare - * @param types type utilities - * @return true if the TypeCompounds represent the same compound element value - */ - private static boolean typeCompoundEquals(TypeCompound tc1, TypeCompound tc2, Types types) { - // For the first conjunct, both of these forms fail in some cases: - // tc1.type == tc2.type - // types.isSameType(tc1.type, tc2.type) - return contentEquals(tc1.type.tsym.name, tc2.type.tsym.name) - && typeCompoundValuesEquals(tc1.values, tc2.values, types) - && isSameTAPositionExceptTreePos(tc1.position, tc2.position); - } - - /** - * Returns true if the two names represent the same string. - * - * @param n1 the first Name to compare - * @param n2 the second Name to compare - * @return true if the two names represent the same string - */ - @SuppressWarnings( - "interning:unnecessary.equals" // Name is interned within a single instance of javac, - // but call equals anyway out of paranoia. - ) - private static boolean contentEquals(Name n1, Name n2) { - if (n1.getClass() == n2.getClass()) { - return n1.equals(n2); - } else { - // Slightly less efficient because it makes a copy. - return n1.contentEquals(n2); + + /** + * Check whether a TypeCompound is contained in a list of TypeCompounds. + * + * @param list the input list of TypeCompounds + * @param tc the TypeCompound to find + * @param types type utilities + * @return true, iff a TypeCompound equal to tc is contained in list + */ + public static boolean isTypeCompoundContained( + List list, TypeCompound tc, Types types) { + for (Attribute.TypeCompound rawat : list) { + if (typeCompoundEquals(rawat, tc, types)) { + return true; + } + } + return false; } - } - - /** - * Compares the {@code values} fields of two TypeCompound objects (e.g., annotations). Is more - * lenient than {@code List.equals}, which uses {@code Object.equals} on list elements. - * - * @param values1 the first {@code values} field - * @param values2 the second {@code values} field - * @param types type utilities - * @return true if the two {@code values} fields represent the same name-to-value mapping, in the - * same order - */ - @SuppressWarnings("InvalidParam") // Error Prone tries to be clever, but it is not - private static boolean typeCompoundValuesEquals( - List> values1, - List> values2, - Types types) { - if (values1.size() != values2.size()) { - return false; + + /** + * Compares two TypeCompound objects (e.g., annotations). + * + * @param tc1 the first TypeCompound to compare + * @param tc2 the second TypeCompound to compare + * @param types type utilities + * @return true if the TypeCompounds represent the same compound element value + */ + private static boolean typeCompoundEquals(TypeCompound tc1, TypeCompound tc2, Types types) { + // For the first conjunct, both of these forms fail in some cases: + // tc1.type == tc2.type + // types.isSameType(tc1.type, tc2.type) + return contentEquals(tc1.type.tsym.name, tc2.type.tsym.name) + && typeCompoundValuesEquals(tc1.values, tc2.values, types) + && isSameTAPositionExceptTreePos(tc1.position, tc2.position); } - for (Iterator> iter1 = values1.iterator(), - iter2 = values2.iterator(); - iter1.hasNext(); ) { - Pair pair1 = iter1.next(); - Pair pair2 = iter2.next(); - if (!(pair1.fst.equals(pair2.fst) && attributeEquals(pair1.snd, pair2.snd, types))) { - return false; - } + /** + * Returns true if the two names represent the same string. + * + * @param n1 the first Name to compare + * @param n2 the second Name to compare + * @return true if the two names represent the same string + */ + @SuppressWarnings( + "interning:unnecessary.equals" // Name is interned within a single instance of javac, + // but call equals anyway out of paranoia. + ) + private static boolean contentEquals(Name n1, Name n2) { + if (n1.getClass() == n2.getClass()) { + return n1.equals(n2); + } else { + // Slightly less efficient because it makes a copy. + return n1.contentEquals(n2); + } } - return true; - } - - /** - * Compares two attributes. Is more lenient for constants than {@code Attribute.equals}, which is - * reference equality. - * - * @param a1 the first attribute to compare - * @param a2 the second attribute to compare - * @param types type utilities - * @return true if the two attributes are the same - */ - private static boolean attributeEquals(Attribute a1, Attribute a2, Types types) { - if (a1 instanceof Attribute.Array && a2 instanceof Attribute.Array) { - List list1 = ((Attribute.Array) a1).getValue(); - List list2 = ((Attribute.Array) a2).getValue(); - if (list1.size() != list2.size()) { - return false; - } - // This requires the array elements to be in the same order. Is that the right thing? - for (int i = 0; i < list1.size(); i++) { - if (!attributeEquals(list1.get(i), list2.get(i), types)) { - return false; + + /** + * Compares the {@code values} fields of two TypeCompound objects (e.g., annotations). Is more + * lenient than {@code List.equals}, which uses {@code Object.equals} on list elements. + * + * @param values1 the first {@code values} field + * @param values2 the second {@code values} field + * @param types type utilities + * @return true if the two {@code values} fields represent the same name-to-value mapping, in + * the same order + */ + @SuppressWarnings("InvalidParam") // Error Prone tries to be clever, but it is not + private static boolean typeCompoundValuesEquals( + List> values1, + List> values2, + Types types) { + if (values1.size() != values2.size()) { + return false; } - } - return true; - } else if (a1 instanceof Attribute.Class && a2 instanceof Attribute.Class) { - Type t1 = ((Attribute.Class) a1).getValue(); - Type t2 = ((Attribute.Class) a2).getValue(); - return types.isSameType(t1, t2); - } else if (a1 instanceof Attribute.Constant && a2 instanceof Attribute.Constant) { - Object v1 = ((Attribute.Constant) a1).getValue(); - Object v2 = ((Attribute.Constant) a2).getValue(); - return v1.equals(v2); - } else if (a1 instanceof Attribute.Compound && a2 instanceof Attribute.Compound) { - // The annotation value is another annotation. `a1` and `a2` implement - // AnnotationMirror. - DeclaredType t1 = ((Attribute.Compound) a1).getAnnotationType(); - DeclaredType t2 = ((Attribute.Compound) a2).getAnnotationType(); - if (!types.isSameType(t1, t2)) { - return false; - } - Map map1 = ((Attribute.Compound) a1).getElementValues(); - Map map2 = ((Attribute.Compound) a2).getElementValues(); - // Is this test, which uses equals() for the keys, too strict? - if (!map1.keySet().equals(map2.keySet())) { - return false; - } - for (Map.Entry map1entry : map1.entrySet()) { - Attribute attr1 = map1entry.getValue(); - @SuppressWarnings("nullness:assignment.type.incompatible") // same keys in map1 & map2 - @NonNull Attribute attr2 = map2.get(map1entry.getKey()); - if (!attributeEquals(attr1, attr2, types)) { - return false; + + for (Iterator> iter1 = values1.iterator(), + iter2 = values2.iterator(); + iter1.hasNext(); ) { + Pair pair1 = iter1.next(); + Pair pair2 = iter2.next(); + if (!(pair1.fst.equals(pair2.fst) && attributeEquals(pair1.snd, pair2.snd, types))) { + return false; + } } - } - return true; - } else if (a1 instanceof Attribute.Enum && a2 instanceof Attribute.Enum) { - Symbol.VarSymbol s1 = ((Attribute.Enum) a1).getValue(); - Symbol.VarSymbol s2 = ((Attribute.Enum) a2).getValue(); - // VarSymbol.equals() is reference equality. - return s1.equals(s2) || s1.toString().equals(s2.toString()); - } else if (a1 instanceof Attribute.Error && a2 instanceof Attribute.Error) { - String s1 = ((Attribute.Error) a1).getValue(); - String s2 = ((Attribute.Error) a2).getValue(); - return s1.equals(s2); - } else { - return a1.equals(a2); - } - } - - /** - * Compare two TypeAnnotationPositions for equality. - * - * @param p1 the first position - * @param p2 the second position - * @return true, iff the two positions are equal - */ - public static boolean isSameTAPosition(TypeAnnotationPosition p1, TypeAnnotationPosition p2) { - return isSameTAPositionExceptTreePos(p1, p2) && p1.pos == p2.pos; - } - - /** - * Compare two TypeAnnotationPositions for equality, ignoring the source tree position. - * - * @param p1 the first position - * @param p2 the second position - * @return true, iff the two positions are equal except for the source tree position - */ - @SuppressWarnings("interning:not.interned") // reference equality for onLambda field - public static boolean isSameTAPositionExceptTreePos( - TypeAnnotationPosition p1, TypeAnnotationPosition p2) { - return p1.type == p2.type - && p1.type_index == p2.type_index - && p1.bound_index == p2.bound_index - && p1.onLambda == p2.onLambda - && p1.parameter_index == p2.parameter_index - && p1.isValidOffset == p2.isValidOffset - && p1.offset == p2.offset - && p1.location.equals(p2.location) - && Arrays.equals(p1.lvarIndex, p2.lvarIndex) - && Arrays.equals(p1.lvarLength, p2.lvarLength) - && Arrays.equals(p1.lvarOffset, p2.lvarOffset) - && (!p1.hasExceptionIndex() - || !p2.hasExceptionIndex() - || (p1.getExceptionIndex() == p2.getExceptionIndex())); - } - - /** - * Returns a newly created Attribute.Compound corresponding to an argument AnnotationMirror. - * - * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass - * @return a new Attribute.Compound corresponding to the AnnotationMirror - */ - public static Attribute.Compound createCompoundFromAnnotationMirror( - AnnotationMirror am, ProcessingEnvironment env) { - // Create a new Attribute to match the AnnotationMirror. - List> values = List.nil(); - for (Map.Entry entry : - am.getElementValues().entrySet()) { - Attribute attribute = attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); - values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); + return true; } - return new Attribute.Compound((Type.ClassType) am.getAnnotationType(), values); - } - - /** - * Returns a newly created Attribute.TypeCompound corresponding to an argument AnnotationMirror. - * - * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass - * @param tapos the type annotation position to use - * @return a new Attribute.TypeCompound corresponding to the AnnotationMirror - */ - public static Attribute.TypeCompound createTypeCompoundFromAnnotationMirror( - AnnotationMirror am, TypeAnnotationPosition tapos, ProcessingEnvironment env) { - // Create a new Attribute to match the AnnotationMirror. - List> values = List.nil(); - for (Map.Entry entry : - am.getElementValues().entrySet()) { - Attribute attribute = attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); - values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); + + /** + * Compares two attributes. Is more lenient for constants than {@code Attribute.equals}, which + * is reference equality. + * + * @param a1 the first attribute to compare + * @param a2 the second attribute to compare + * @param types type utilities + * @return true if the two attributes are the same + */ + private static boolean attributeEquals(Attribute a1, Attribute a2, Types types) { + if (a1 instanceof Attribute.Array && a2 instanceof Attribute.Array) { + List list1 = ((Attribute.Array) a1).getValue(); + List list2 = ((Attribute.Array) a2).getValue(); + if (list1.size() != list2.size()) { + return false; + } + // This requires the array elements to be in the same order. Is that the right thing? + for (int i = 0; i < list1.size(); i++) { + if (!attributeEquals(list1.get(i), list2.get(i), types)) { + return false; + } + } + return true; + } else if (a1 instanceof Attribute.Class && a2 instanceof Attribute.Class) { + Type t1 = ((Attribute.Class) a1).getValue(); + Type t2 = ((Attribute.Class) a2).getValue(); + return types.isSameType(t1, t2); + } else if (a1 instanceof Attribute.Constant && a2 instanceof Attribute.Constant) { + Object v1 = ((Attribute.Constant) a1).getValue(); + Object v2 = ((Attribute.Constant) a2).getValue(); + return v1.equals(v2); + } else if (a1 instanceof Attribute.Compound && a2 instanceof Attribute.Compound) { + // The annotation value is another annotation. `a1` and `a2` implement + // AnnotationMirror. + DeclaredType t1 = ((Attribute.Compound) a1).getAnnotationType(); + DeclaredType t2 = ((Attribute.Compound) a2).getAnnotationType(); + if (!types.isSameType(t1, t2)) { + return false; + } + Map map1 = ((Attribute.Compound) a1).getElementValues(); + Map map2 = ((Attribute.Compound) a2).getElementValues(); + // Is this test, which uses equals() for the keys, too strict? + if (!map1.keySet().equals(map2.keySet())) { + return false; + } + for (Map.Entry map1entry : map1.entrySet()) { + Attribute attr1 = map1entry.getValue(); + @SuppressWarnings( + "nullness:assignment.type.incompatible") // same keys in map1 & map2 + @NonNull Attribute attr2 = map2.get(map1entry.getKey()); + if (!attributeEquals(attr1, attr2, types)) { + return false; + } + } + return true; + } else if (a1 instanceof Attribute.Enum && a2 instanceof Attribute.Enum) { + Symbol.VarSymbol s1 = ((Attribute.Enum) a1).getValue(); + Symbol.VarSymbol s2 = ((Attribute.Enum) a2).getValue(); + // VarSymbol.equals() is reference equality. + return s1.equals(s2) || s1.toString().equals(s2.toString()); + } else if (a1 instanceof Attribute.Error && a2 instanceof Attribute.Error) { + String s1 = ((Attribute.Error) a1).getValue(); + String s2 = ((Attribute.Error) a2).getValue(); + return s1.equals(s2); + } else { + return a1.equals(a2); + } } - return new Attribute.TypeCompound((Type.ClassType) am.getAnnotationType(), values, tapos); - } - - /** - * Returns a newly created Attribute corresponding to an argument AnnotationValue. - * - * @param meth the ExecutableElement that is assigned the value, needed for empty arrays - * @param av an AnnotationValue, which may be part of an AST or an internally created subclass - * @return a new Attribute corresponding to the AnnotationValue - */ - public static Attribute attributeFromAnnotationValue( - ExecutableElement meth, AnnotationValue av, ProcessingEnvironment env) { - return av.accept(new AttributeCreator(env, meth), null); - } - - private static class AttributeCreator implements AnnotationValueVisitor { - private final ProcessingEnvironment processingEnv; - private final Types modelTypes; - private final Elements elements; - private final com.sun.tools.javac.code.Types javacTypes; - - private final ExecutableElement meth; - - public AttributeCreator(ProcessingEnvironment env, ExecutableElement meth) { - this.processingEnv = env; - Context context = ((JavacProcessingEnvironment) env).getContext(); - this.elements = env.getElementUtils(); - this.modelTypes = env.getTypeUtils(); - this.javacTypes = com.sun.tools.javac.code.Types.instance(context); - - this.meth = meth; + + /** + * Compare two TypeAnnotationPositions for equality. + * + * @param p1 the first position + * @param p2 the second position + * @return true, iff the two positions are equal + */ + public static boolean isSameTAPosition(TypeAnnotationPosition p1, TypeAnnotationPosition p2) { + return isSameTAPositionExceptTreePos(p1, p2) && p1.pos == p2.pos; } - @Override - public Attribute visit(AnnotationValue av, Void p) { - return av.accept(this, p); + /** + * Compare two TypeAnnotationPositions for equality, ignoring the source tree position. + * + * @param p1 the first position + * @param p2 the second position + * @return true, iff the two positions are equal except for the source tree position + */ + @SuppressWarnings("interning:not.interned") // reference equality for onLambda field + public static boolean isSameTAPositionExceptTreePos( + TypeAnnotationPosition p1, TypeAnnotationPosition p2) { + return p1.type == p2.type + && p1.type_index == p2.type_index + && p1.bound_index == p2.bound_index + && p1.onLambda == p2.onLambda + && p1.parameter_index == p2.parameter_index + && p1.isValidOffset == p2.isValidOffset + && p1.offset == p2.offset + && p1.location.equals(p2.location) + && Arrays.equals(p1.lvarIndex, p2.lvarIndex) + && Arrays.equals(p1.lvarLength, p2.lvarLength) + && Arrays.equals(p1.lvarOffset, p2.lvarOffset) + && (!p1.hasExceptionIndex() + || !p2.hasExceptionIndex() + || (p1.getExceptionIndex() == p2.getExceptionIndex())); } - @Override - public Attribute visit(AnnotationValue av) { - return visit(av, null); + /** + * Returns a newly created Attribute.Compound corresponding to an argument AnnotationMirror. + * + * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass + * @return a new Attribute.Compound corresponding to the AnnotationMirror + */ + public static Attribute.Compound createCompoundFromAnnotationMirror( + AnnotationMirror am, ProcessingEnvironment env) { + // Create a new Attribute to match the AnnotationMirror. + List> values = List.nil(); + for (Map.Entry entry : + am.getElementValues().entrySet()) { + Attribute attribute = + attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); + values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); + } + return new Attribute.Compound((Type.ClassType) am.getAnnotationType(), values); } - @Override - public Attribute visitBoolean(boolean b, Void p) { - TypeMirror booleanType = modelTypes.getPrimitiveType(TypeKind.BOOLEAN); - return new Attribute.Constant((Type) booleanType, b ? 1 : 0); + /** + * Returns a newly created Attribute.TypeCompound corresponding to an argument AnnotationMirror. + * + * @param am an AnnotationMirror, which may be part of an AST or an internally created subclass + * @param tapos the type annotation position to use + * @return a new Attribute.TypeCompound corresponding to the AnnotationMirror + */ + public static Attribute.TypeCompound createTypeCompoundFromAnnotationMirror( + AnnotationMirror am, TypeAnnotationPosition tapos, ProcessingEnvironment env) { + // Create a new Attribute to match the AnnotationMirror. + List> values = List.nil(); + for (Map.Entry entry : + am.getElementValues().entrySet()) { + Attribute attribute = + attributeFromAnnotationValue(entry.getKey(), entry.getValue(), env); + values = values.append(new Pair<>((Symbol.MethodSymbol) entry.getKey(), attribute)); + } + return new Attribute.TypeCompound((Type.ClassType) am.getAnnotationType(), values, tapos); } - @Override - public Attribute visitByte(byte b, Void p) { - TypeMirror byteType = modelTypes.getPrimitiveType(TypeKind.BYTE); - return new Attribute.Constant((Type) byteType, b); + /** + * Returns a newly created Attribute corresponding to an argument AnnotationValue. + * + * @param meth the ExecutableElement that is assigned the value, needed for empty arrays + * @param av an AnnotationValue, which may be part of an AST or an internally created subclass + * @return a new Attribute corresponding to the AnnotationValue + */ + public static Attribute attributeFromAnnotationValue( + ExecutableElement meth, AnnotationValue av, ProcessingEnvironment env) { + return av.accept(new AttributeCreator(env, meth), null); } - @Override - public Attribute visitChar(char c, Void p) { - TypeMirror charType = modelTypes.getPrimitiveType(TypeKind.CHAR); - return new Attribute.Constant((Type) charType, c); + private static class AttributeCreator implements AnnotationValueVisitor { + private final ProcessingEnvironment processingEnv; + private final Types modelTypes; + private final Elements elements; + private final com.sun.tools.javac.code.Types javacTypes; + + private final ExecutableElement meth; + + public AttributeCreator(ProcessingEnvironment env, ExecutableElement meth) { + this.processingEnv = env; + Context context = ((JavacProcessingEnvironment) env).getContext(); + this.elements = env.getElementUtils(); + this.modelTypes = env.getTypeUtils(); + this.javacTypes = com.sun.tools.javac.code.Types.instance(context); + + this.meth = meth; + } + + @Override + public Attribute visit(AnnotationValue av, Void p) { + return av.accept(this, p); + } + + @Override + public Attribute visit(AnnotationValue av) { + return visit(av, null); + } + + @Override + public Attribute visitBoolean(boolean b, Void p) { + TypeMirror booleanType = modelTypes.getPrimitiveType(TypeKind.BOOLEAN); + return new Attribute.Constant((Type) booleanType, b ? 1 : 0); + } + + @Override + public Attribute visitByte(byte b, Void p) { + TypeMirror byteType = modelTypes.getPrimitiveType(TypeKind.BYTE); + return new Attribute.Constant((Type) byteType, b); + } + + @Override + public Attribute visitChar(char c, Void p) { + TypeMirror charType = modelTypes.getPrimitiveType(TypeKind.CHAR); + return new Attribute.Constant((Type) charType, c); + } + + @Override + public Attribute visitDouble(double d, Void p) { + TypeMirror doubleType = modelTypes.getPrimitiveType(TypeKind.DOUBLE); + return new Attribute.Constant((Type) doubleType, d); + } + + @Override + public Attribute visitFloat(float f, Void p) { + TypeMirror floatType = modelTypes.getPrimitiveType(TypeKind.FLOAT); + return new Attribute.Constant((Type) floatType, f); + } + + @Override + public Attribute visitInt(int i, Void p) { + TypeMirror intType = modelTypes.getPrimitiveType(TypeKind.INT); + return new Attribute.Constant((Type) intType, i); + } + + @Override + public Attribute visitLong(long i, Void p) { + TypeMirror longType = modelTypes.getPrimitiveType(TypeKind.LONG); + return new Attribute.Constant((Type) longType, i); + } + + @Override + public Attribute visitShort(short s, Void p) { + TypeMirror shortType = modelTypes.getPrimitiveType(TypeKind.SHORT); + return new Attribute.Constant((Type) shortType, s); + } + + @Override + public Attribute visitString(String s, Void p) { + TypeMirror stringType = elements.getTypeElement("java.lang.String").asType(); + return new Attribute.Constant((Type) stringType, s); + } + + @Override + public Attribute visitType(TypeMirror t, Void p) { + if (t instanceof Type) { + return new Attribute.Class(javacTypes, (Type) t); + } else { + throw new BugInCF("Unexpected type of TypeMirror: " + t.getClass()); + } + } + + @Override + public Attribute visitEnumConstant(VariableElement c, Void p) { + if (c instanceof Symbol.VarSymbol) { + Symbol.VarSymbol sym = (Symbol.VarSymbol) c; + if (sym.getKind() == ElementKind.ENUM_CONSTANT) { + return new Attribute.Enum(sym.type, sym); + } + } + throw new BugInCF("Unexpected type of VariableElement: " + c.getClass()); + } + + @Override + public Attribute visitAnnotation(AnnotationMirror a, Void p) { + return createCompoundFromAnnotationMirror(a, processingEnv); + } + + @Override + public Attribute visitArray(java.util.List vals, Void p) { + if (!vals.isEmpty()) { + List valAttrs = List.nil(); + for (AnnotationValue av : vals) { + valAttrs = valAttrs.append(av.accept(this, p)); + } + ArrayType arrayType = modelTypes.getArrayType(valAttrs.get(0).type); + return new Attribute.Array((Type) arrayType, valAttrs); + } else { + return new Attribute.Array((Type) meth.getReturnType(), List.nil()); + } + } + + @Override + public Attribute visitUnknown(AnnotationValue av, Void p) { + throw new BugInCF("Unexpected type of AnnotationValue: " + av.getClass()); + } } - @Override - public Attribute visitDouble(double d, Void p) { - TypeMirror doubleType = modelTypes.getPrimitiveType(TypeKind.DOUBLE); - return new Attribute.Constant((Type) doubleType, d); + /** + * Create an unknown TypeAnnotationPosition. + * + * @return an unkown TypeAnnotationPosition + */ + public static TypeAnnotationPosition unknownTAPosition() { + return TypeAnnotationPosition.unknown; } - @Override - public Attribute visitFloat(float f, Void p) { - TypeMirror floatType = modelTypes.getPrimitiveType(TypeKind.FLOAT); - return new Attribute.Constant((Type) floatType, f); + /** + * Create a method return TypeAnnotationPosition. + * + * @param pos the source tree position + * @return a method return TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodReturnTAPosition(int pos) { + return TypeAnnotationPosition.methodReturn(pos); } - @Override - public Attribute visitInt(int i, Void p) { - TypeMirror intType = modelTypes.getPrimitiveType(TypeKind.INT); - return new Attribute.Constant((Type) intType, i); + /** + * Create a method receiver TypeAnnotationPosition. + * + * @param pos the source tree position + * @return a method receiver TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodReceiverTAPosition(int pos) { + return TypeAnnotationPosition.methodReceiver(pos); } - @Override - public Attribute visitLong(long i, Void p) { - TypeMirror longType = modelTypes.getPrimitiveType(TypeKind.LONG); - return new Attribute.Constant((Type) longType, i); + /** + * Create a method parameter TypeAnnotationPosition. + * + * @param pidx the parameter index + * @param pos the source tree position + * @return a method parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodParameterTAPosition(int pidx, int pos) { + return TypeAnnotationPosition.methodParameter(pidx, pos); } - @Override - public Attribute visitShort(short s, Void p) { - TypeMirror shortType = modelTypes.getPrimitiveType(TypeKind.SHORT); - return new Attribute.Constant((Type) shortType, s); + /** + * Create a method throws TypeAnnotationPosition. + * + * @param tidx the throws index + * @param pos the source tree position + * @return a method throws TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodThrowsTAPosition(int tidx, int pos) { + return TypeAnnotationPosition.methodThrows( + TypeAnnotationPosition.emptyPath, null, tidx, pos); } - @Override - public Attribute visitString(String s, Void p) { - TypeMirror stringType = elements.getTypeElement("java.lang.String").asType(); - return new Attribute.Constant((Type) stringType, s); + /** + * Create a field TypeAnnotationPosition. + * + * @param pos the source tree position + * @return a field TypeAnnotationPosition + */ + public static TypeAnnotationPosition fieldTAPosition(int pos) { + return TypeAnnotationPosition.field(pos); } - @Override - public Attribute visitType(TypeMirror t, Void p) { - if (t instanceof Type) { - return new Attribute.Class(javacTypes, (Type) t); - } else { - throw new BugInCF("Unexpected type of TypeMirror: " + t.getClass()); - } + /** + * Create a class extends TypeAnnotationPosition. + * + * @param implidx the class extends index + * @param pos the source tree position + * @return a class extends TypeAnnotationPosition + */ + public static TypeAnnotationPosition classExtendsTAPosition(int implidx, int pos) { + return TypeAnnotationPosition.classExtends(implidx, pos); } - @Override - public Attribute visitEnumConstant(VariableElement c, Void p) { - if (c instanceof Symbol.VarSymbol) { - Symbol.VarSymbol sym = (Symbol.VarSymbol) c; - if (sym.getKind() == ElementKind.ENUM_CONSTANT) { - return new Attribute.Enum(sym.type, sym); - } - } - throw new BugInCF("Unexpected type of VariableElement: " + c.getClass()); + /** + * Create a type parameter TypeAnnotationPosition. + * + * @param tpidx the type parameter index + * @param pos the source tree position + * @return a type parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition typeParameterTAPosition(int tpidx, int pos) { + return TypeAnnotationPosition.typeParameter( + TypeAnnotationPosition.emptyPath, null, tpidx, pos); } - @Override - public Attribute visitAnnotation(AnnotationMirror a, Void p) { - return createCompoundFromAnnotationMirror(a, processingEnv); + /** + * Create a method type parameter TypeAnnotationPosition. + * + * @param tpidx the method type parameter index + * @param pos the source tree position + * @return a method type parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodTypeParameterTAPosition(int tpidx, int pos) { + return TypeAnnotationPosition.methodTypeParameter( + TypeAnnotationPosition.emptyPath, null, tpidx, pos); } - @Override - public Attribute visitArray(java.util.List vals, Void p) { - if (!vals.isEmpty()) { - List valAttrs = List.nil(); - for (AnnotationValue av : vals) { - valAttrs = valAttrs.append(av.accept(this, p)); - } - ArrayType arrayType = modelTypes.getArrayType(valAttrs.get(0).type); - return new Attribute.Array((Type) arrayType, valAttrs); - } else { - return new Attribute.Array((Type) meth.getReturnType(), List.nil()); - } + /** + * Create a type parameter bound TypeAnnotationPosition. + * + * @param tpidx the type parameter index + * @param bndidx the bound index + * @param pos the source tree position + * @return a method parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition typeParameterBoundTAPosition( + int tpidx, int bndidx, int pos) { + return TypeAnnotationPosition.typeParameterBound( + TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); } - @Override - public Attribute visitUnknown(AnnotationValue av, Void p) { - throw new BugInCF("Unexpected type of AnnotationValue: " + av.getClass()); + /** + * Create a method type parameter bound TypeAnnotationPosition. + * + * @param tpidx the type parameter index + * @param bndidx the bound index + * @param pos the source tree position + * @return a method parameter TypeAnnotationPosition + */ + public static TypeAnnotationPosition methodTypeParameterBoundTAPosition( + int tpidx, int bndidx, int pos) { + return TypeAnnotationPosition.methodTypeParameterBound( + TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); } - } - - /** - * Create an unknown TypeAnnotationPosition. - * - * @return an unkown TypeAnnotationPosition - */ - public static TypeAnnotationPosition unknownTAPosition() { - return TypeAnnotationPosition.unknown; - } - - /** - * Create a method return TypeAnnotationPosition. - * - * @param pos the source tree position - * @return a method return TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodReturnTAPosition(int pos) { - return TypeAnnotationPosition.methodReturn(pos); - } - - /** - * Create a method receiver TypeAnnotationPosition. - * - * @param pos the source tree position - * @return a method receiver TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodReceiverTAPosition(int pos) { - return TypeAnnotationPosition.methodReceiver(pos); - } - - /** - * Create a method parameter TypeAnnotationPosition. - * - * @param pidx the parameter index - * @param pos the source tree position - * @return a method parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodParameterTAPosition(int pidx, int pos) { - return TypeAnnotationPosition.methodParameter(pidx, pos); - } - - /** - * Create a method throws TypeAnnotationPosition. - * - * @param tidx the throws index - * @param pos the source tree position - * @return a method throws TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodThrowsTAPosition(int tidx, int pos) { - return TypeAnnotationPosition.methodThrows(TypeAnnotationPosition.emptyPath, null, tidx, pos); - } - - /** - * Create a field TypeAnnotationPosition. - * - * @param pos the source tree position - * @return a field TypeAnnotationPosition - */ - public static TypeAnnotationPosition fieldTAPosition(int pos) { - return TypeAnnotationPosition.field(pos); - } - - /** - * Create a class extends TypeAnnotationPosition. - * - * @param implidx the class extends index - * @param pos the source tree position - * @return a class extends TypeAnnotationPosition - */ - public static TypeAnnotationPosition classExtendsTAPosition(int implidx, int pos) { - return TypeAnnotationPosition.classExtends(implidx, pos); - } - - /** - * Create a type parameter TypeAnnotationPosition. - * - * @param tpidx the type parameter index - * @param pos the source tree position - * @return a type parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition typeParameterTAPosition(int tpidx, int pos) { - return TypeAnnotationPosition.typeParameter(TypeAnnotationPosition.emptyPath, null, tpidx, pos); - } - - /** - * Create a method type parameter TypeAnnotationPosition. - * - * @param tpidx the method type parameter index - * @param pos the source tree position - * @return a method type parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodTypeParameterTAPosition(int tpidx, int pos) { - return TypeAnnotationPosition.methodTypeParameter( - TypeAnnotationPosition.emptyPath, null, tpidx, pos); - } - - /** - * Create a type parameter bound TypeAnnotationPosition. - * - * @param tpidx the type parameter index - * @param bndidx the bound index - * @param pos the source tree position - * @return a method parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition typeParameterBoundTAPosition( - int tpidx, int bndidx, int pos) { - return TypeAnnotationPosition.typeParameterBound( - TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); - } - - /** - * Create a method type parameter bound TypeAnnotationPosition. - * - * @param tpidx the type parameter index - * @param bndidx the bound index - * @param pos the source tree position - * @return a method parameter TypeAnnotationPosition - */ - public static TypeAnnotationPosition methodTypeParameterBoundTAPosition( - int tpidx, int bndidx, int pos) { - return TypeAnnotationPosition.methodTypeParameterBound( - TypeAnnotationPosition.emptyPath, null, tpidx, bndidx, pos); - } - - /** - * Copy a TypeAnnotationPosition. - * - * @param tapos the input TypeAnnotationPosition - * @return a copied TypeAnnotationPosition - */ - public static TypeAnnotationPosition copyTAPosition(TypeAnnotationPosition tapos) { - TypeAnnotationPosition res; - switch (tapos.type) { - case CAST: - res = - TypeAnnotationPosition.typeCast( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case CLASS_EXTENDS: - res = - TypeAnnotationPosition.classExtends( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case CLASS_TYPE_PARAMETER: - res = - TypeAnnotationPosition.typeParameter( - tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); - break; - case CLASS_TYPE_PARAMETER_BOUND: - res = - TypeAnnotationPosition.typeParameterBound( - tapos.location, - tapos.onLambda, - tapos.parameter_index, - tapos.bound_index, - tapos.pos); - break; - case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.constructorInvocationTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case CONSTRUCTOR_REFERENCE: - res = TypeAnnotationPosition.constructorRef(tapos.location, tapos.onLambda, tapos.pos); - break; - case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.constructorRefTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case EXCEPTION_PARAMETER: - res = TypeAnnotationPosition.exceptionParameter(tapos.location, tapos.onLambda, tapos.pos); - break; - case FIELD: - res = TypeAnnotationPosition.field(tapos.location, tapos.onLambda, tapos.pos); - break; - case INSTANCEOF: - res = TypeAnnotationPosition.instanceOf(tapos.location, tapos.onLambda, tapos.pos); - break; - case LOCAL_VARIABLE: - res = TypeAnnotationPosition.localVariable(tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_FORMAL_PARAMETER: - res = - TypeAnnotationPosition.methodParameter( - tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); - break; - case METHOD_INVOCATION_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.methodInvocationTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case METHOD_RECEIVER: - res = TypeAnnotationPosition.methodReceiver(tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_REFERENCE: - res = TypeAnnotationPosition.methodRef(tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_REFERENCE_TYPE_ARGUMENT: - res = - TypeAnnotationPosition.methodRefTypeArg( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case METHOD_RETURN: - res = TypeAnnotationPosition.methodReturn(tapos.location, tapos.onLambda, tapos.pos); - break; - case METHOD_TYPE_PARAMETER: - res = - TypeAnnotationPosition.methodTypeParameter( - tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); - break; - case METHOD_TYPE_PARAMETER_BOUND: - res = - TypeAnnotationPosition.methodTypeParameterBound( - tapos.location, - tapos.onLambda, - tapos.parameter_index, - tapos.bound_index, - tapos.pos); - break; - case NEW: - res = TypeAnnotationPosition.newObj(tapos.location, tapos.onLambda, tapos.pos); - break; - case RESOURCE_VARIABLE: - res = TypeAnnotationPosition.resourceVariable(tapos.location, tapos.onLambda, tapos.pos); - break; - case THROWS: - res = - TypeAnnotationPosition.methodThrows( - tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); - break; - case UNKNOWN: - default: - throw new BugInCF("Unexpected target type: " + tapos + " at " + tapos.type); + + /** + * Copy a TypeAnnotationPosition. + * + * @param tapos the input TypeAnnotationPosition + * @return a copied TypeAnnotationPosition + */ + public static TypeAnnotationPosition copyTAPosition(TypeAnnotationPosition tapos) { + TypeAnnotationPosition res; + switch (tapos.type) { + case CAST: + res = + TypeAnnotationPosition.typeCast( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case CLASS_EXTENDS: + res = + TypeAnnotationPosition.classExtends( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case CLASS_TYPE_PARAMETER: + res = + TypeAnnotationPosition.typeParameter( + tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); + break; + case CLASS_TYPE_PARAMETER_BOUND: + res = + TypeAnnotationPosition.typeParameterBound( + tapos.location, + tapos.onLambda, + tapos.parameter_index, + tapos.bound_index, + tapos.pos); + break; + case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.constructorInvocationTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case CONSTRUCTOR_REFERENCE: + res = + TypeAnnotationPosition.constructorRef( + tapos.location, tapos.onLambda, tapos.pos); + break; + case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.constructorRefTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case EXCEPTION_PARAMETER: + res = + TypeAnnotationPosition.exceptionParameter( + tapos.location, tapos.onLambda, tapos.pos); + break; + case FIELD: + res = TypeAnnotationPosition.field(tapos.location, tapos.onLambda, tapos.pos); + break; + case INSTANCEOF: + res = TypeAnnotationPosition.instanceOf(tapos.location, tapos.onLambda, tapos.pos); + break; + case LOCAL_VARIABLE: + res = + TypeAnnotationPosition.localVariable( + tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_FORMAL_PARAMETER: + res = + TypeAnnotationPosition.methodParameter( + tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); + break; + case METHOD_INVOCATION_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.methodInvocationTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case METHOD_RECEIVER: + res = + TypeAnnotationPosition.methodReceiver( + tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_REFERENCE: + res = TypeAnnotationPosition.methodRef(tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_REFERENCE_TYPE_ARGUMENT: + res = + TypeAnnotationPosition.methodRefTypeArg( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case METHOD_RETURN: + res = + TypeAnnotationPosition.methodReturn( + tapos.location, tapos.onLambda, tapos.pos); + break; + case METHOD_TYPE_PARAMETER: + res = + TypeAnnotationPosition.methodTypeParameter( + tapos.location, tapos.onLambda, tapos.parameter_index, tapos.pos); + break; + case METHOD_TYPE_PARAMETER_BOUND: + res = + TypeAnnotationPosition.methodTypeParameterBound( + tapos.location, + tapos.onLambda, + tapos.parameter_index, + tapos.bound_index, + tapos.pos); + break; + case NEW: + res = TypeAnnotationPosition.newObj(tapos.location, tapos.onLambda, tapos.pos); + break; + case RESOURCE_VARIABLE: + res = + TypeAnnotationPosition.resourceVariable( + tapos.location, tapos.onLambda, tapos.pos); + break; + case THROWS: + res = + TypeAnnotationPosition.methodThrows( + tapos.location, tapos.onLambda, tapos.type_index, tapos.pos); + break; + case UNKNOWN: + default: + throw new BugInCF("Unexpected target type: " + tapos + " at " + tapos.type); + } + return res; } - return res; - } - - /** - * Remove type annotations from the given type. - * - * @param in the input type - * @return the same underlying type, but without type annotations - */ - public static Type unannotatedType(TypeMirror in) { - Type impl = (Type) in; - if (impl.isPrimitive()) { - // TODO: file an issue that stripMetadata doesn't work for primitives. - // See eisop/checker-framework issue #21. - return impl.baseType(); - } else { - return impl.stripMetadata(); + + /** + * Remove type annotations from the given type. + * + * @param in the input type + * @return the same underlying type, but without type annotations + */ + public static Type unannotatedType(TypeMirror in) { + Type impl = (Type) in; + if (impl.isPrimitive()) { + // TODO: file an issue that stripMetadata doesn't work for primitives. + // See eisop/checker-framework issue #21. + return impl.baseType(); + } else { + return impl.stripMetadata(); + } } - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java index bef718488a1..211610614b3 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java @@ -1,306 +1,309 @@ package org.checkerframework.javacutil; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; + import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; + import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; /** A utility class that helps with {@link TypeKind}s. */ public final class TypeKindUtils { - /** Map of a boxed primitive type's fully-qualified name to its primitive {@link TypeKind}. */ - private static final Map<@FullyQualifiedName String, TypeKind> boxedToPrimitiveType; - - static { - Map<@FullyQualifiedName String, TypeKind> map = new LinkedHashMap<>(); - map.put("java.lang.Byte", TypeKind.BYTE); - map.put("java.lang.Boolean", TypeKind.BOOLEAN); - map.put("java.lang.Character", TypeKind.CHAR); - map.put("java.lang.Double", TypeKind.DOUBLE); - map.put("java.lang.Float", TypeKind.FLOAT); - map.put("java.lang.Integer", TypeKind.INT); - map.put("java.lang.Long", TypeKind.LONG); - map.put("java.lang.Short", TypeKind.SHORT); - boxedToPrimitiveType = Collections.unmodifiableMap(map); - } + /** Map of a boxed primitive type's fully-qualified name to its primitive {@link TypeKind}. */ + private static final Map<@FullyQualifiedName String, TypeKind> boxedToPrimitiveType; - /** This class cannot be instantiated. */ - private TypeKindUtils() { - throw new AssertionError("Class TypeKindUtils cannot be instantiated."); - } - - /** - * Return true if the argument is one of INT, SHORT, BYTE, CHAR, LONG. - * - * @param typeKind the TypeKind to inspect - * @return true if typeKind is a primitive integral type kind - */ - public static boolean isIntegral(TypeKind typeKind) { - switch (typeKind) { - case INT: - case SHORT: - case BYTE: - case CHAR: - case LONG: - return true; - default: - return false; + static { + Map<@FullyQualifiedName String, TypeKind> map = new LinkedHashMap<>(); + map.put("java.lang.Byte", TypeKind.BYTE); + map.put("java.lang.Boolean", TypeKind.BOOLEAN); + map.put("java.lang.Character", TypeKind.CHAR); + map.put("java.lang.Double", TypeKind.DOUBLE); + map.put("java.lang.Float", TypeKind.FLOAT); + map.put("java.lang.Integer", TypeKind.INT); + map.put("java.lang.Long", TypeKind.LONG); + map.put("java.lang.Short", TypeKind.SHORT); + boxedToPrimitiveType = Collections.unmodifiableMap(map); } - } - /** - * Return true if the argument is one of FLOAT, DOUBLE. - * - * @param typeKind the TypeKind to inspect - * @return true if typeKind is a primitive floating point type kind - */ - public static boolean isFloatingPoint(TypeKind typeKind) { - switch (typeKind) { - case FLOAT: - case DOUBLE: - return true; - default: - return false; + /** This class cannot be instantiated. */ + private TypeKindUtils() { + throw new AssertionError("Class TypeKindUtils cannot be instantiated."); } - } - /** - * Returns true iff the argument is a primitive numeric type kind. - * - * @param typeKind a type kind - * @return true if the argument is a primitive numeric type kind - */ - public static boolean isNumeric(TypeKind typeKind) { - switch (typeKind) { - case BYTE: - case CHAR: - case DOUBLE: - case FLOAT: - case INT: - case LONG: - case SHORT: - return true; - default: - return false; + /** + * Return true if the argument is one of INT, SHORT, BYTE, CHAR, LONG. + * + * @param typeKind the TypeKind to inspect + * @return true if typeKind is a primitive integral type kind + */ + public static boolean isIntegral(TypeKind typeKind) { + switch (typeKind) { + case INT: + case SHORT: + case BYTE: + case CHAR: + case LONG: + return true; + default: + return false; + } } - } - // Cannot create an overload that takes an AnnotatedTypeMirror because the javacutil - // package must not depend on the framework package. - /** - * Given a primitive type, return its kind. Given a boxed primitive type, return the corresponding - * primitive type kind. Otherwise, return null. - * - * @param type a primitive or boxed primitive type - * @return a primitive type kind, or null - */ - public static @Nullable TypeKind primitiveOrBoxedToTypeKind(TypeMirror type) { - TypeKind typeKind = type.getKind(); - if (typeKind.isPrimitive()) { - return typeKind; + /** + * Return true if the argument is one of FLOAT, DOUBLE. + * + * @param typeKind the TypeKind to inspect + * @return true if typeKind is a primitive floating point type kind + */ + public static boolean isFloatingPoint(TypeKind typeKind) { + switch (typeKind) { + case FLOAT: + case DOUBLE: + return true; + default: + return false; + } } - return boxedToTypeKind(type); - } - - /** - * Given a boxed primitive type, return the corresponding primitive type kind. Otherwise, return - * null. - * - * @param type a boxed primitive type - * @return a primitive type kind, or null - */ - public static @Nullable TypeKind boxedToTypeKind(TypeMirror type) { - if (type.getKind() != TypeKind.DECLARED) { - return null; + /** + * Returns true iff the argument is a primitive numeric type kind. + * + * @param typeKind a type kind + * @return true if the argument is a primitive numeric type kind + */ + public static boolean isNumeric(TypeKind typeKind) { + switch (typeKind) { + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + return true; + default: + return false; + } } - String typeString = TypesUtils.getQualifiedName((DeclaredType) type); - return boxedToPrimitiveType.get(typeString); - } - - // No overload that takes AnnotatedTypeMirror because javacutil cannot depend on framework. - /** - * Returns the widened numeric type for an arithmetic operation performed on a value of the left - * type and the right type. Defined in JLS 5.6.2. We return a {@link TypeKind} because creating a - * {@link TypeMirror} requires a {@link javax.lang.model.util.Types} object from the {@link - * javax.annotation.processing.ProcessingEnvironment}. - * - * @param left a type mirror - * @param right a type mirror - * @return the result of widening numeric conversion, or NONE when the conversion cannot be - * performed - */ - public static TypeKind widenedNumericType(TypeMirror left, TypeMirror right) { - return widenedNumericType(left.getKind(), right.getKind()); - } + // Cannot create an overload that takes an AnnotatedTypeMirror because the javacutil + // package must not depend on the framework package. + /** + * Given a primitive type, return its kind. Given a boxed primitive type, return the + * corresponding primitive type kind. Otherwise, return null. + * + * @param type a primitive or boxed primitive type + * @return a primitive type kind, or null + */ + public static @Nullable TypeKind primitiveOrBoxedToTypeKind(TypeMirror type) { + TypeKind typeKind = type.getKind(); + if (typeKind.isPrimitive()) { + return typeKind; + } - /** - * Given two type kinds, return the type kind they are widened to, when an arithmetic operation is - * performed on them. Defined in JLS 5.6.2. - * - * @param a a type kind - * @param b a type kind - * @return the type kind to which they are widened, when an operation is performed on them - */ - public static TypeKind widenedNumericType(TypeKind a, TypeKind b) { - if (!isNumeric(a) || !isNumeric(b)) { - return TypeKind.NONE; + return boxedToTypeKind(type); } - if (a == TypeKind.DOUBLE || b == TypeKind.DOUBLE) { - return TypeKind.DOUBLE; - } + /** + * Given a boxed primitive type, return the corresponding primitive type kind. Otherwise, return + * null. + * + * @param type a boxed primitive type + * @return a primitive type kind, or null + */ + public static @Nullable TypeKind boxedToTypeKind(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return null; + } - if (a == TypeKind.FLOAT || b == TypeKind.FLOAT) { - return TypeKind.FLOAT; + String typeString = TypesUtils.getQualifiedName((DeclaredType) type); + return boxedToPrimitiveType.get(typeString); } - if (a == TypeKind.LONG || b == TypeKind.LONG) { - return TypeKind.LONG; + // No overload that takes AnnotatedTypeMirror because javacutil cannot depend on framework. + /** + * Returns the widened numeric type for an arithmetic operation performed on a value of the left + * type and the right type. Defined in JLS 5.6.2. We return a {@link TypeKind} because creating + * a {@link TypeMirror} requires a {@link javax.lang.model.util.Types} object from the {@link + * javax.annotation.processing.ProcessingEnvironment}. + * + * @param left a type mirror + * @param right a type mirror + * @return the result of widening numeric conversion, or NONE when the conversion cannot be + * performed + */ + public static TypeKind widenedNumericType(TypeMirror left, TypeMirror right) { + return widenedNumericType(left.getKind(), right.getKind()); } - return TypeKind.INT; - } - - /** The type of primitive conversion: narrowing, widening, or same. */ - public enum PrimitiveConversionKind { - /** The two primitive kinds are the same. */ - SAME, /** - * The conversion is a widening primitive conversion. + * Given two type kinds, return the type kind they are widened to, when an arithmetic operation + * is performed on them. Defined in JLS 5.6.2. * - *

                  This includes byte to char, even though that is strictly a "widening and narrowing - * primitive conversion", according to JLS 5.1.4. + * @param a a type kind + * @param b a type kind + * @return the type kind to which they are widened, when an operation is performed on them */ - WIDENING, - /** The conversion is a narrowing primitive conversion. */ - NARROWING - } + public static TypeKind widenedNumericType(TypeKind a, TypeKind b) { + if (!isNumeric(a) || !isNumeric(b)) { + return TypeKind.NONE; + } - /** - * Return the type of primitive conversion between {@code from} and {@code to}. - * - *

                  The narrowing conversions include both short to char and char to short. - * - * @param from a primitive type - * @param to a primitive type - * @return the type of primitive conversion between {@code from} and {@code to} - */ - public static PrimitiveConversionKind getPrimitiveConversionKind(TypeKind from, TypeKind to) { - if (from == TypeKind.BOOLEAN && to == TypeKind.BOOLEAN) { - return PrimitiveConversionKind.SAME; - } + if (a == TypeKind.DOUBLE || b == TypeKind.DOUBLE) { + return TypeKind.DOUBLE; + } - assert (isIntegral(from) || isFloatingPoint(from)) && (isIntegral(to) || isFloatingPoint(to)) - : "getPrimitiveConversionKind " + from + " " + to; + if (a == TypeKind.FLOAT || b == TypeKind.FLOAT) { + return TypeKind.FLOAT; + } - if (from == to) { - return PrimitiveConversionKind.SAME; - } + if (a == TypeKind.LONG || b == TypeKind.LONG) { + return TypeKind.LONG; + } - boolean fromIntegral = isIntegral(from); - boolean toFloatingPoint = isFloatingPoint(to); - if (fromIntegral && toFloatingPoint) { - return PrimitiveConversionKind.WIDENING; + return TypeKind.INT; } - boolean toIntegral = isIntegral(to); - boolean fromFloatingPoint = isFloatingPoint(from); - if (fromFloatingPoint && toIntegral) { - return PrimitiveConversionKind.NARROWING; + /** The type of primitive conversion: narrowing, widening, or same. */ + public enum PrimitiveConversionKind { + /** The two primitive kinds are the same. */ + SAME, + /** + * The conversion is a widening primitive conversion. + * + *

                  This includes byte to char, even though that is strictly a "widening and narrowing + * primitive conversion", according to JLS 5.1.4. + */ + WIDENING, + /** The conversion is a narrowing primitive conversion. */ + NARROWING } - if (numBits(from) < numBits(to)) { - return PrimitiveConversionKind.WIDENING; - } else { - // If same number of bits (char to short or short to char), it is a narrowing. - return PrimitiveConversionKind.NARROWING; + /** + * Return the type of primitive conversion between {@code from} and {@code to}. + * + *

                  The narrowing conversions include both short to char and char to short. + * + * @param from a primitive type + * @param to a primitive type + * @return the type of primitive conversion between {@code from} and {@code to} + */ + public static PrimitiveConversionKind getPrimitiveConversionKind(TypeKind from, TypeKind to) { + if (from == TypeKind.BOOLEAN && to == TypeKind.BOOLEAN) { + return PrimitiveConversionKind.SAME; + } + + assert (isIntegral(from) || isFloatingPoint(from)) + && (isIntegral(to) || isFloatingPoint(to)) + : "getPrimitiveConversionKind " + from + " " + to; + + if (from == to) { + return PrimitiveConversionKind.SAME; + } + + boolean fromIntegral = isIntegral(from); + boolean toFloatingPoint = isFloatingPoint(to); + if (fromIntegral && toFloatingPoint) { + return PrimitiveConversionKind.WIDENING; + } + + boolean toIntegral = isIntegral(to); + boolean fromFloatingPoint = isFloatingPoint(from); + if (fromFloatingPoint && toIntegral) { + return PrimitiveConversionKind.NARROWING; + } + + if (numBits(from) < numBits(to)) { + return PrimitiveConversionKind.WIDENING; + } else { + // If same number of bits (char to short or short to char), it is a narrowing. + return PrimitiveConversionKind.NARROWING; + } } - } - /** - * Returns the number of bits in the representation of a primitive type. Returns -1 if the type is - * not a primitive type. - * - * @param tk a primitive type kind - * @return the number of bits in its representation, or -1 if not integral - */ - private static int numBits(TypeKind tk) { - switch (tk) { - case BYTE: - return 8; - case SHORT: - return 16; - case CHAR: - return 16; - case INT: - return 32; - case LONG: - return 64; - case FLOAT: - return 32; - case DOUBLE: - return 64; - case BOOLEAN: - default: - return -1; + /** + * Returns the number of bits in the representation of a primitive type. Returns -1 if the type + * is not a primitive type. + * + * @param tk a primitive type kind + * @return the number of bits in its representation, or -1 if not integral + */ + private static int numBits(TypeKind tk) { + switch (tk) { + case BYTE: + return 8; + case SHORT: + return 16; + case CHAR: + return 16; + case INT: + return 32; + case LONG: + return 64; + case FLOAT: + return 32; + case DOUBLE: + return 64; + case BOOLEAN: + default: + return -1; + } } - } - /** - * Returns the minimum value representable by the given integral primitive type. - * - * @param tk a primitive type kind - * @return the minimum value representable by the given integral primitive type - */ - public static long minValue(TypeKind tk) { - switch (tk) { - case BYTE: - return Byte.MIN_VALUE; - case SHORT: - return Short.MIN_VALUE; - case CHAR: - return Character.MIN_VALUE; - case INT: - return Integer.MIN_VALUE; - case LONG: - return Long.MIN_VALUE; - case FLOAT: - case DOUBLE: - case BOOLEAN: - default: - throw new BugInCF(tk + " does not have a minimum value"); + /** + * Returns the minimum value representable by the given integral primitive type. + * + * @param tk a primitive type kind + * @return the minimum value representable by the given integral primitive type + */ + public static long minValue(TypeKind tk) { + switch (tk) { + case BYTE: + return Byte.MIN_VALUE; + case SHORT: + return Short.MIN_VALUE; + case CHAR: + return Character.MIN_VALUE; + case INT: + return Integer.MIN_VALUE; + case LONG: + return Long.MIN_VALUE; + case FLOAT: + case DOUBLE: + case BOOLEAN: + default: + throw new BugInCF(tk + " does not have a minimum value"); + } } - } - /** - * Returns the maximum value representable by the given integral primitive type. - * - * @param tk a primitive type kind - * @return the maximum value representable by the given integral primitive type - */ - public static long maxValue(TypeKind tk) { - switch (tk) { - case BYTE: - return Byte.MAX_VALUE; - case SHORT: - return Short.MAX_VALUE; - case CHAR: - return Character.MAX_VALUE; - case INT: - return Integer.MAX_VALUE; - case LONG: - return Long.MAX_VALUE; - case FLOAT: - case DOUBLE: - case BOOLEAN: - default: - throw new BugInCF(tk + " does not have a maximum value"); + /** + * Returns the maximum value representable by the given integral primitive type. + * + * @param tk a primitive type kind + * @return the maximum value representable by the given integral primitive type + */ + public static long maxValue(TypeKind tk) { + switch (tk) { + case BYTE: + return Byte.MAX_VALUE; + case SHORT: + return Short.MAX_VALUE; + case CHAR: + return Character.MAX_VALUE; + case INT: + return Integer.MAX_VALUE; + case LONG: + return Long.MAX_VALUE; + case FLOAT: + case DOUBLE: + case BOOLEAN: + default: + throw new BugInCF(tk + " does not have a maximum value"); + } } - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java index 5743e99fc85..128a5d6e920 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java @@ -13,26 +13,26 @@ @SuppressWarnings("serial") public class TypeSystemError extends RuntimeException { - /** - * Constructs a new TypeSystemError with the specified detail message. - * - * @param message the detail message - */ - public TypeSystemError(String message) { - super(message); - if (message == null) { - throw new BugInCF("Must have a detail message."); + /** + * Constructs a new TypeSystemError with the specified detail message. + * + * @param message the detail message + */ + public TypeSystemError(String message) { + super(message); + if (message == null) { + throw new BugInCF("Must have a detail message."); + } } - } - /** - * Constructs a new TypeSystemError with a detail message composed from the given arguments. - * - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public TypeSystemError(String fmt, @Nullable Object... args) { - this(String.format(fmt, args)); - } + /** + * Constructs a new TypeSystemError with a detail message composed from the given arguments. + * + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public TypeSystemError(String fmt, @Nullable Object... args) { + this(String.format(fmt, args)); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index ef64d3448a4..a52867341eb 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -11,11 +11,22 @@ import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Names; + +import org.checkerframework.checker.interning.qual.EqualsMethod; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.signature.qual.BinaryName; +import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; +import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; +import org.plumelib.util.CollectionsPlume; +import org.plumelib.util.ImmutableTypes; +import org.plumelib.util.StringsPlume; + import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.StringJoiner; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -35,14 +46,6 @@ import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import org.checkerframework.checker.interning.qual.EqualsMethod; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.signature.qual.BinaryName; -import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; -import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; -import org.plumelib.util.CollectionsPlume; -import org.plumelib.util.ImmutableTypes; -import org.plumelib.util.StringsPlume; /** * A utility class that helps with {@link TypeMirror}s. It complements {@link Types}, providing @@ -50,1390 +53,1403 @@ */ public final class TypesUtils { - /** Class cannot be instantiated. */ - private TypesUtils() { - throw new AssertionError("Class TypesUtils cannot be instantiated."); - } - - /// Creating types - - /** - * Returns the {@link TypeMirror} for a given {@link Class}. - * - * @param clazz a class - * @param types the type utilities - * @param elements the element utilities - * @return the TypeMirror for {@code clazz} - */ - public static TypeMirror typeFromClass(Class clazz, Types types, Elements elements) { - if (clazz == void.class) { - return types.getNoType(TypeKind.VOID); - } else if (clazz.isPrimitive()) { - String primitiveName = clazz.getName().toUpperCase(Locale.ROOT); - TypeKind primitiveKind = TypeKind.valueOf(primitiveName); - return types.getPrimitiveType(primitiveKind); - } else if (clazz.isArray()) { - TypeMirror componentType = typeFromClass(clazz.getComponentType(), types, elements); - return types.getArrayType(componentType); - } else { - String name = clazz.getCanonicalName(); - assert name != null : "@AssumeAssertion(nullness): assumption"; - TypeElement element = elements.getTypeElement(name); - if (element == null) { - throw new BugInCF("No element for: " + clazz); - } - return element.asType(); - } - } - - /** - * Returns an {@link ArrayType} with elements of type {@code componentType}. - * - * @param componentType the component type of the created array type - * @param types the type utilities - * @return an {@link ArrayType} whose elements have type {@code componentType} - */ - public static ArrayType createArrayType(TypeMirror componentType, Types types) { - JavacTypes t = (JavacTypes) types; - return t.getArrayType(componentType); - } - - /// Creating a Class - - /** - * Returns the {@link Class} for a given {@link TypeMirror}. Returns {@code Object.class} if it - * cannot determine anything more specific. - * - * @param typeMirror a TypeMirror - * @return the class for {@code typeMirror} - */ - public static Class getClassFromType(TypeMirror typeMirror) { - - switch (typeMirror.getKind()) { - case INT: - return int.class; - case LONG: - return long.class; - case SHORT: - return short.class; - case BYTE: - return byte.class; - case CHAR: - return char.class; - case DOUBLE: - return double.class; - case FLOAT: - return float.class; - case BOOLEAN: - return boolean.class; - - case ARRAY: - Class componentClass = getClassFromType(((ArrayType) typeMirror).getComponentType()); - // In Java 12, use this instead: - // return fooClass.arrayType(); - return java.lang.reflect.Array.newInstance(componentClass, 0).getClass(); - - case DECLARED: - // BUG: need to compute a @ClassGetName, but this code computes a - // @CanonicalNameOrEmpty. They are different for inner classes. - @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Names.toString - @DotSeparatedIdentifiers String typeString = TypesUtils.getQualifiedName((DeclaredType) typeMirror); - if (typeString.equals("")) { - return void.class; + /** Class cannot be instantiated. */ + private TypesUtils() { + throw new AssertionError("Class TypesUtils cannot be instantiated."); + } + + /// Creating types + + /** + * Returns the {@link TypeMirror} for a given {@link Class}. + * + * @param clazz a class + * @param types the type utilities + * @param elements the element utilities + * @return the TypeMirror for {@code clazz} + */ + public static TypeMirror typeFromClass(Class clazz, Types types, Elements elements) { + if (clazz == void.class) { + return types.getNoType(TypeKind.VOID); + } else if (clazz.isPrimitive()) { + String primitiveName = clazz.getName().toUpperCase(Locale.ROOT); + TypeKind primitiveKind = TypeKind.valueOf(primitiveName); + return types.getPrimitiveType(primitiveKind); + } else if (clazz.isArray()) { + TypeMirror componentType = typeFromClass(clazz.getComponentType(), types, elements); + return types.getArrayType(componentType); + } else { + String name = clazz.getCanonicalName(); + assert name != null : "@AssumeAssertion(nullness): assumption"; + TypeElement element = elements.getTypeElement(name); + if (element == null) { + throw new BugInCF("No element for: " + clazz); + } + return element.asType(); } + } - try { - return Class.forName(typeString); - } catch (ClassNotFoundException | LinkageError e) { - return Object.class; + /** + * Returns an {@link ArrayType} with elements of type {@code componentType}. + * + * @param componentType the component type of the created array type + * @param types the type utilities + * @return an {@link ArrayType} whose elements have type {@code componentType} + */ + public static ArrayType createArrayType(TypeMirror componentType, Types types) { + JavacTypes t = (JavacTypes) types; + return t.getArrayType(componentType); + } + + /// Creating a Class + + /** + * Returns the {@link Class} for a given {@link TypeMirror}. Returns {@code Object.class} if it + * cannot determine anything more specific. + * + * @param typeMirror a TypeMirror + * @return the class for {@code typeMirror} + */ + public static Class getClassFromType(TypeMirror typeMirror) { + + switch (typeMirror.getKind()) { + case INT: + return int.class; + case LONG: + return long.class; + case SHORT: + return short.class; + case BYTE: + return byte.class; + case CHAR: + return char.class; + case DOUBLE: + return double.class; + case FLOAT: + return float.class; + case BOOLEAN: + return boolean.class; + + case ARRAY: + Class componentClass = + getClassFromType(((ArrayType) typeMirror).getComponentType()); + // In Java 12, use this instead: + // return fooClass.arrayType(); + return java.lang.reflect.Array.newInstance(componentClass, 0).getClass(); + + case DECLARED: + // BUG: need to compute a @ClassGetName, but this code computes a + // @CanonicalNameOrEmpty. They are different for inner classes. + @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Names.toString + @DotSeparatedIdentifiers String typeString = TypesUtils.getQualifiedName((DeclaredType) typeMirror); + if (typeString.equals("")) { + return void.class; + } + + try { + return Class.forName(typeString); + } catch (ClassNotFoundException | LinkageError e) { + return Object.class; + } + + default: + return Object.class; } + } - default: - return Object.class; - } - } - - /// Getters - - /** - * Gets the fully qualified name for a provided type. It returns an empty name if type is an - * anonymous type. - * - * @param type the declared type - * @return the name corresponding to that type - */ - @SuppressWarnings("signature:return") // todo: add fake override of Name.toString. - public static @CanonicalNameOrEmpty String getQualifiedName(DeclaredType type) { - TypeElement element = (TypeElement) type.asElement(); - @CanonicalNameOrEmpty Name name = element.getQualifiedName(); - return name.toString(); - } - - /** - * Returns the simple type name, without annotations but including array brackets. - * - * @param type a type - * @return the simple type name - */ - public static String simpleTypeName(TypeMirror type) { - switch (type.getKind()) { - case ARRAY: - return simpleTypeName(((ArrayType) type).getComponentType()) + "[]"; - case TYPEVAR: - return ((TypeVariable) type).asElement().getSimpleName().toString(); - case DECLARED: - return ((DeclaredType) type).asElement().getSimpleName().toString(); - case INTERSECTION: - StringJoiner sjI = new StringJoiner(" & "); - for (TypeMirror bound : ((IntersectionType) type).getBounds()) { - sjI.add(simpleTypeName(bound)); + /// Getters + + /** + * Gets the fully qualified name for a provided type. It returns an empty name if type is an + * anonymous type. + * + * @param type the declared type + * @return the name corresponding to that type + */ + @SuppressWarnings("signature:return") // todo: add fake override of Name.toString. + public static @CanonicalNameOrEmpty String getQualifiedName(DeclaredType type) { + TypeElement element = (TypeElement) type.asElement(); + @CanonicalNameOrEmpty Name name = element.getQualifiedName(); + return name.toString(); + } + + /** + * Returns the simple type name, without annotations but including array brackets. + * + * @param type a type + * @return the simple type name + */ + public static String simpleTypeName(TypeMirror type) { + switch (type.getKind()) { + case ARRAY: + return simpleTypeName(((ArrayType) type).getComponentType()) + "[]"; + case TYPEVAR: + return ((TypeVariable) type).asElement().getSimpleName().toString(); + case DECLARED: + return ((DeclaredType) type).asElement().getSimpleName().toString(); + case INTERSECTION: + StringJoiner sjI = new StringJoiner(" & "); + for (TypeMirror bound : ((IntersectionType) type).getBounds()) { + sjI.add(simpleTypeName(bound)); + } + return sjI.toString(); + case NULL: + return ""; + case VOID: + return "void"; + case WILDCARD: + WildcardType wildcard = (WildcardType) type; + TypeMirror extendsBound = wildcard.getExtendsBound(); + TypeMirror superBound = wildcard.getSuperBound(); + return "?" + + (extendsBound != null ? " extends " + simpleTypeName(extendsBound) : "") + + (superBound != null ? " super " + simpleTypeName(superBound) : ""); + case UNION: + StringJoiner sj = new StringJoiner(" | "); + for (TypeMirror alternative : ((UnionType) type).getAlternatives()) { + sj.add(simpleTypeName(alternative)); + } + return sj.toString(); + case PACKAGE: + return "PACKAGE:" + type; + default: + if (type.getKind().isPrimitive()) { + return TypeAnnotationUtils.unannotatedType(type).toString(); + } else { + throw new BugInCF( + "simpleTypeName: unhandled type kind: %s, type: %s", + type.getKind(), type); + } } - return sjI.toString(); - case NULL: - return ""; - case VOID: - return "void"; - case WILDCARD: - WildcardType wildcard = (WildcardType) type; - TypeMirror extendsBound = wildcard.getExtendsBound(); - TypeMirror superBound = wildcard.getSuperBound(); - return "?" - + (extendsBound != null ? " extends " + simpleTypeName(extendsBound) : "") - + (superBound != null ? " super " + simpleTypeName(superBound) : ""); - case UNION: - StringJoiner sj = new StringJoiner(" | "); - for (TypeMirror alternative : ((UnionType) type).getAlternatives()) { - sj.add(simpleTypeName(alternative)); + } + + /** + * Returns the binary name of a type. + * + * @param type a type + * @return its binary name + */ + public static @BinaryName String binaryName(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + throw new BugInCF("Only declared types have a binary name"); } - return sj.toString(); - case PACKAGE: - return "PACKAGE:" + type; - default: - if (type.getKind().isPrimitive()) { - return TypeAnnotationUtils.unannotatedType(type).toString(); - } else { - throw new BugInCF( - "simpleTypeName: unhandled type kind: %s, type: %s", type.getKind(), type); + return ElementUtils.getBinaryName((TypeElement) ((DeclaredType) type).asElement()); + } + + /** + * Returns the type element for {@code type} if {@code type} is a class, interface, annotation + * type, or enum. Otherwise, returns null. + * + * @param type whose element is returned + * @return the type element for {@code type} if {@code type} is a class, interface, annotation + * type, or enum; otherwise, returns {@code null} + */ + public static @Nullable TypeElement getTypeElement(TypeMirror type) { + Element element = ((Type) type).asElement(); + if (element == null) { + return null; + } + if (ElementUtils.isTypeElement(element)) { + return (TypeElement) element; + } + return null; + } + + /** + * Given an array type, returns the type with all array levels stripped off. + * + * @param at an array type + * @return the type with all array levels stripped off + */ + public static TypeMirror getInnermostComponentType(ArrayType at) { + TypeMirror result = at; + while (result.getKind() == TypeKind.ARRAY) { + result = ((ArrayType) result).getComponentType(); + } + return result; + } + + /// Equality + + /** + * Returns true iff the arguments are both the same declared types. + * + *

                  This is needed because class {@code Type.ClassType} does not override equals. + * + * @param t1 the first type to test + * @param t2 the second type to test + * @return whether the arguments are the same declared types + */ + public static boolean areSameDeclaredTypes(Type.ClassType t1, Type.ClassType t2) { + // Do a cheaper test first + if (t1.tsym.name != t2.tsym.name) { + return false; } + return t1.toString().equals(t2.toString()); + } + + /** + * Returns true iff the arguments are both the same primitive type. + * + * @param left a type + * @param right a type + * @return whether the arguments are the same primitive type + */ + public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) { + if (!isPrimitive(left) || !isPrimitive(right)) { + return false; + } + + return (left.getKind() == right.getKind()); + } + + /// Predicates + + /** + * Returns true iff the type represents a java.lang.Object declared type. + * + * @param type the type to check + * @return true iff type represents java.lang.Object + */ + public static boolean isObject(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.Object"); + } + + /** + * Return true iff the type represents a java.lang.Class declared type. + * + * @param type the type to check + * @return true iff type represents java.lang.Class + */ + public static boolean isClass(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.Class"); + } + + /** + * Returns true iff the type represents a java.lang.String declared type. + * + * @param type the type to check + * @return true iff type represents java.lang.String + */ + public static boolean isString(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.String"); + } + + /** + * Returns true if the type is either {@code boolean} (primitive type) or {@code + * java.lang.Boolean}. + * + * @param type the type to check + * @return true iff type represents a boolean type + */ + public static boolean isBooleanType(TypeMirror type) { + return type.getKind() == TypeKind.BOOLEAN || isDeclaredOfName(type, "java.lang.Boolean"); } - } - - /** - * Returns the binary name of a type. - * - * @param type a type - * @return its binary name - */ - public static @BinaryName String binaryName(TypeMirror type) { - if (type.getKind() != TypeKind.DECLARED) { - throw new BugInCF("Only declared types have a binary name"); - } - return ElementUtils.getBinaryName((TypeElement) ((DeclaredType) type).asElement()); - } - - /** - * Returns the type element for {@code type} if {@code type} is a class, interface, annotation - * type, or enum. Otherwise, returns null. - * - * @param type whose element is returned - * @return the type element for {@code type} if {@code type} is a class, interface, annotation - * type, or enum; otherwise, returns {@code null} - */ - public static @Nullable TypeElement getTypeElement(TypeMirror type) { - Element element = ((Type) type).asElement(); - if (element == null) { - return null; - } - if (ElementUtils.isTypeElement(element)) { - return (TypeElement) element; - } - return null; - } - - /** - * Given an array type, returns the type with all array levels stripped off. - * - * @param at an array type - * @return the type with all array levels stripped off - */ - public static TypeMirror getInnermostComponentType(ArrayType at) { - TypeMirror result = at; - while (result.getKind() == TypeKind.ARRAY) { - result = ((ArrayType) result).getComponentType(); - } - return result; - } - - /// Equality - - /** - * Returns true iff the arguments are both the same declared types. - * - *

                  This is needed because class {@code Type.ClassType} does not override equals. - * - * @param t1 the first type to test - * @param t2 the second type to test - * @return whether the arguments are the same declared types - */ - public static boolean areSameDeclaredTypes(Type.ClassType t1, Type.ClassType t2) { - // Do a cheaper test first - if (t1.tsym.name != t2.tsym.name) { - return false; - } - return t1.toString().equals(t2.toString()); - } - - /** - * Returns true iff the arguments are both the same primitive type. - * - * @param left a type - * @param right a type - * @return whether the arguments are the same primitive type - */ - public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) { - if (!isPrimitive(left) || !isPrimitive(right)) { - return false; - } - - return (left.getKind() == right.getKind()); - } - - /// Predicates - - /** - * Returns true iff the type represents a java.lang.Object declared type. - * - * @param type the type to check - * @return true iff type represents java.lang.Object - */ - public static boolean isObject(TypeMirror type) { - return isDeclaredOfName(type, "java.lang.Object"); - } - - /** - * Return true iff the type represents a java.lang.Class declared type. - * - * @param type the type to check - * @return true iff type represents java.lang.Class - */ - public static boolean isClass(TypeMirror type) { - return isDeclaredOfName(type, "java.lang.Class"); - } - - /** - * Returns true iff the type represents a java.lang.String declared type. - * - * @param type the type to check - * @return true iff type represents java.lang.String - */ - public static boolean isString(TypeMirror type) { - return isDeclaredOfName(type, "java.lang.String"); - } - - /** - * Returns true if the type is either {@code boolean} (primitive type) or {@code - * java.lang.Boolean}. - * - * @param type the type to check - * @return true iff type represents a boolean type - */ - public static boolean isBooleanType(TypeMirror type) { - return type.getKind() == TypeKind.BOOLEAN || isDeclaredOfName(type, "java.lang.Boolean"); - } - - /** - * Returns true if the type is {@code char} or {@code Character}. - * - * @param type a type - * @return true if the type is {@code char} or {@code Character} - */ - public static boolean isCharType(TypeMirror type) { - return type.getKind() == TypeKind.CHAR || isDeclaredOfName(type, "java.lang.Character"); - } - - /** - * Returns true iff the type represents a declared type of the given qualified name. - * - * @param type the type - * @param qualifiedName the name to check {@code type} against - * @return true iff type represents a declared type of the qualified name - */ - public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) { - return type.getKind() == TypeKind.DECLARED - && getQualifiedName((DeclaredType) type).contentEquals(qualifiedName); - } - - /** - * Returns true iff the type represents a declared type whose fully-qualified name is any of the - * given names. - * - * @param type the type - * @param qualifiedNames fully-qualified type names to check for - * @return true iff type represents a declared type whose fully-qualified name is one of the given - * names - */ - public static boolean isDeclaredOfName(TypeMirror type, Collection qualifiedNames) { - return type.getKind() == TypeKind.DECLARED - && qualifiedNames.contains(getQualifiedName((DeclaredType) type)); - } - - /** - * Returns true iff the {@code type} represents a boxed primitive type. - * - * @param type the type to check - * @return true iff type represents a boxed primitive type - */ - public static boolean isBoxedPrimitive(TypeMirror type) { - return TypeKindUtils.boxedToTypeKind(type) != null; - } - - /** - * Returns true iff this is an immutable type in the JDK. - * - *

                  This does not use immutability annotations and always returns false for user-defined - * classes. - * - * @param type the type to check - * @return true iff this is an immutable type in the JDK - */ - public static boolean isImmutableTypeInJdk(TypeMirror type) { - return isPrimitive(type) - || (type.getKind() == TypeKind.DECLARED - && ImmutableTypes.isImmutable(getQualifiedName((DeclaredType) type))); - } - - /** - * Returns true iff type represents a Throwable type (e.g. Exception, Error). - * - * @param type the type to check - * @return true iff type represents a Throwable type (e.g. Exception, Error) - */ - public static boolean isThrowable(TypeMirror type) { - while (type != null && type.getKind() == TypeKind.DECLARED) { - DeclaredType dt = (DeclaredType) type; - TypeElement elem = (TypeElement) dt.asElement(); - Name name = elem.getQualifiedName(); - if ("java.lang.Throwable".contentEquals(name)) { - return true; - } - type = elem.getSuperclass(); - } - return false; - } - - /** - * Returns true iff the argument is an anonymous type. - * - * @param type the type to check - * @return whether the argument is an anonymous type - */ - public static boolean isAnonymous(TypeMirror type) { - return (type instanceof DeclaredType) - && ((TypeElement) ((DeclaredType) type).asElement()).getNestingKind() - == NestingKind.ANONYMOUS; - } - - /** - * Returns true iff the argument is a primitive type. - * - * @param type the type to check - * @return whether the argument is a primitive type - */ - public static boolean isPrimitive(TypeMirror type) { - return type.getKind().isPrimitive(); - } - - /** - * Returns true iff the argument is a primitive type or a boxed primitive type. - * - * @param type a type - * @return true if the argument is a primitive type or a boxed primitive type - */ - public static boolean isPrimitiveOrBoxed(TypeMirror type) { - return isPrimitive(type) || isBoxedPrimitive(type); - } - - /** - * Returns true iff the argument is a primitive numeric type. - * - * @param type a type - * @return true if the argument is a primitive numeric type - */ - public static boolean isNumeric(TypeMirror type) { - return TypeKindUtils.isNumeric(type.getKind()); - } - - /** - * Returns true iff the argument is a boxed numeric type. - * - * @param type a type - * @return true if the argument is a boxed numeric type - */ - public static boolean isNumericBoxed(TypeMirror type) { - TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); - return boxedPrimitiveKind != null && TypeKindUtils.isNumeric(boxedPrimitiveKind); - } - - /** - * Returns true iff the argument is an integral primitive type. - * - * @param type a type - * @return whether the argument is an integral primitive type - */ - public static boolean isIntegralPrimitive(TypeMirror type) { - return TypeKindUtils.isIntegral(type.getKind()); - } - - /** - * Return true if the argument TypeMirror is a (possibly boxed) integral type. - * - * @param type the type to inspect - * @return true if type is an integral type - */ - public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) { - TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - return kind != null && TypeKindUtils.isIntegral(kind); - } - - /** - * Returns true if declaredType is a Class that is used to box primitive type (e.g. - * declaredType=java.lang.Double and primitiveType=22.5d ) - * - * @param declaredType a type that might be a boxed type - * @param primitiveType a type that might be a primitive type - * @return true if {@code declaredType} is a box of {@code primitiveType} - */ - public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) { - TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(declaredType); - return boxedPrimitiveKind == primitiveType.getKind(); - } - - /** - * Returns true iff the argument is a boxed floating point type. - * - * @param type type to test - * @return whether the argument is a boxed floating point type - */ - public static boolean isBoxedFloating(TypeMirror type) { - TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); - return boxedPrimitiveKind != null && TypeKindUtils.isFloatingPoint(boxedPrimitiveKind); - } - - /** - * Returns true iff the argument is a primitive floating point type. - * - * @param type type mirror - * @return whether the argument is a primitive floating point type - */ - public static boolean isFloatingPrimitive(TypeMirror type) { - return TypeKindUtils.isFloatingPoint(type.getKind()); - } - - /** - * Return true if the argument TypeMirror is a (possibly boxed) floating point type. - * - * @param type the type to inspect - * @return true if type is a floating point type - */ - public static boolean isFloatingPoint(TypeMirror type) { - TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - return kind != null && TypeKindUtils.isFloatingPoint(kind); - } - - /** - * Returns whether a TypeMirror represents a class type. - * - * @param type a type that might be a class type - * @return true if {@code} is a class type - */ - public static boolean isClassType(TypeMirror type) { - return (type instanceof Type.ClassType); - } - - /** - * Returns whether or not {@code type} is a functional interface type (as defined in JLS 9.8). - * - * @param type possible functional interface type - * @param env the processing environment - * @return whether or not {@code type} is a functional interface type (as defined in JLS 9.8) - */ - public static boolean isFunctionalInterface(TypeMirror type, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); - return javacTypes.isFunctionalInterface((Type) type); - } - - /** - * Returns true if the given type is a compound type. - * - * @param type a type - * @return true if the given type is a compound type - */ - public static boolean isCompoundType(TypeMirror type) { - switch (type.getKind()) { - case ARRAY: - case EXECUTABLE: - case INTERSECTION: - case UNION: - case TYPEVAR: - case WILDCARD: - return true; - - case DECLARED: - DeclaredType declaredType = (DeclaredType) type; - return !declaredType.getTypeArguments().isEmpty(); - - default: + + /** + * Returns true if the type is {@code char} or {@code Character}. + * + * @param type a type + * @return true if the type is {@code char} or {@code Character} + */ + public static boolean isCharType(TypeMirror type) { + return type.getKind() == TypeKind.CHAR || isDeclaredOfName(type, "java.lang.Character"); + } + + /** + * Returns true iff the type represents a declared type of the given qualified name. + * + * @param type the type + * @param qualifiedName the name to check {@code type} against + * @return true iff type represents a declared type of the qualified name + */ + public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) { + return type.getKind() == TypeKind.DECLARED + && getQualifiedName((DeclaredType) type).contentEquals(qualifiedName); + } + + /** + * Returns true iff the type represents a declared type whose fully-qualified name is any of the + * given names. + * + * @param type the type + * @param qualifiedNames fully-qualified type names to check for + * @return true iff type represents a declared type whose fully-qualified name is one of the + * given names + */ + public static boolean isDeclaredOfName(TypeMirror type, Collection qualifiedNames) { + return type.getKind() == TypeKind.DECLARED + && qualifiedNames.contains(getQualifiedName((DeclaredType) type)); + } + + /** + * Returns true iff the {@code type} represents a boxed primitive type. + * + * @param type the type to check + * @return true iff type represents a boxed primitive type + */ + public static boolean isBoxedPrimitive(TypeMirror type) { + return TypeKindUtils.boxedToTypeKind(type) != null; + } + + /** + * Returns true iff this is an immutable type in the JDK. + * + *

                  This does not use immutability annotations and always returns false for user-defined + * classes. + * + * @param type the type to check + * @return true iff this is an immutable type in the JDK + */ + public static boolean isImmutableTypeInJdk(TypeMirror type) { + return isPrimitive(type) + || (type.getKind() == TypeKind.DECLARED + && ImmutableTypes.isImmutable(getQualifiedName((DeclaredType) type))); + } + + /** + * Returns true iff type represents a Throwable type (e.g. Exception, Error). + * + * @param type the type to check + * @return true iff type represents a Throwable type (e.g. Exception, Error) + */ + public static boolean isThrowable(TypeMirror type) { + while (type != null && type.getKind() == TypeKind.DECLARED) { + DeclaredType dt = (DeclaredType) type; + TypeElement elem = (TypeElement) dt.asElement(); + Name name = elem.getQualifiedName(); + if ("java.lang.Throwable".contentEquals(name)) { + return true; + } + type = elem.getSuperclass(); + } return false; } - } - - /** - * Returns true if {@code type} has an enclosing type. - * - * @param type type to checker - * @return true if {@code type} has an enclosing type - */ - public static boolean hasEnclosingType(TypeMirror type) { - Type e = ((Type) type).getEnclosingType(); - return e.getKind() != TypeKind.NONE; - } - - /// Type variables and wildcards - - /** - * If the argument is a bounded TypeVariable or WildcardType, return its non-variable, - * non-wildcard upper bound. Otherwise, return the type itself. - * - * @param type a type - * @return the non-variable, non-wildcard upper bound of a type, if it has one, or itself if it - * has no bounds - */ - public static TypeMirror upperBound(TypeMirror type) { - do { - if (type instanceof TypeVariable) { - TypeVariable tvar = (TypeVariable) type; - if (tvar.getUpperBound() != null) { - type = tvar.getUpperBound(); + + /** + * Returns true iff the argument is an anonymous type. + * + * @param type the type to check + * @return whether the argument is an anonymous type + */ + public static boolean isAnonymous(TypeMirror type) { + return (type instanceof DeclaredType) + && ((TypeElement) ((DeclaredType) type).asElement()).getNestingKind() + == NestingKind.ANONYMOUS; + } + + /** + * Returns true iff the argument is a primitive type. + * + * @param type the type to check + * @return whether the argument is a primitive type + */ + public static boolean isPrimitive(TypeMirror type) { + return type.getKind().isPrimitive(); + } + + /** + * Returns true iff the argument is a primitive type or a boxed primitive type. + * + * @param type a type + * @return true if the argument is a primitive type or a boxed primitive type + */ + public static boolean isPrimitiveOrBoxed(TypeMirror type) { + return isPrimitive(type) || isBoxedPrimitive(type); + } + + /** + * Returns true iff the argument is a primitive numeric type. + * + * @param type a type + * @return true if the argument is a primitive numeric type + */ + public static boolean isNumeric(TypeMirror type) { + return TypeKindUtils.isNumeric(type.getKind()); + } + + /** + * Returns true iff the argument is a boxed numeric type. + * + * @param type a type + * @return true if the argument is a boxed numeric type + */ + public static boolean isNumericBoxed(TypeMirror type) { + TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); + return boxedPrimitiveKind != null && TypeKindUtils.isNumeric(boxedPrimitiveKind); + } + + /** + * Returns true iff the argument is an integral primitive type. + * + * @param type a type + * @return whether the argument is an integral primitive type + */ + public static boolean isIntegralPrimitive(TypeMirror type) { + return TypeKindUtils.isIntegral(type.getKind()); + } + + /** + * Return true if the argument TypeMirror is a (possibly boxed) integral type. + * + * @param type the type to inspect + * @return true if type is an integral type + */ + public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) { + TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + return kind != null && TypeKindUtils.isIntegral(kind); + } + + /** + * Returns true if declaredType is a Class that is used to box primitive type (e.g. + * declaredType=java.lang.Double and primitiveType=22.5d ) + * + * @param declaredType a type that might be a boxed type + * @param primitiveType a type that might be a primitive type + * @return true if {@code declaredType} is a box of {@code primitiveType} + */ + public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) { + TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(declaredType); + return boxedPrimitiveKind == primitiveType.getKind(); + } + + /** + * Returns true iff the argument is a boxed floating point type. + * + * @param type type to test + * @return whether the argument is a boxed floating point type + */ + public static boolean isBoxedFloating(TypeMirror type) { + TypeKind boxedPrimitiveKind = TypeKindUtils.boxedToTypeKind(type); + return boxedPrimitiveKind != null && TypeKindUtils.isFloatingPoint(boxedPrimitiveKind); + } + + /** + * Returns true iff the argument is a primitive floating point type. + * + * @param type type mirror + * @return whether the argument is a primitive floating point type + */ + public static boolean isFloatingPrimitive(TypeMirror type) { + return TypeKindUtils.isFloatingPoint(type.getKind()); + } + + /** + * Return true if the argument TypeMirror is a (possibly boxed) floating point type. + * + * @param type the type to inspect + * @return true if type is a floating point type + */ + public static boolean isFloatingPoint(TypeMirror type) { + TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + return kind != null && TypeKindUtils.isFloatingPoint(kind); + } + + /** + * Returns whether a TypeMirror represents a class type. + * + * @param type a type that might be a class type + * @return true if {@code} is a class type + */ + public static boolean isClassType(TypeMirror type) { + return (type instanceof Type.ClassType); + } + + /** + * Returns whether or not {@code type} is a functional interface type (as defined in JLS 9.8). + * + * @param type possible functional interface type + * @param env the processing environment + * @return whether or not {@code type} is a functional interface type (as defined in JLS 9.8) + */ + public static boolean isFunctionalInterface(TypeMirror type, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return javacTypes.isFunctionalInterface((Type) type); + } + + /** + * Returns true if the given type is a compound type. + * + * @param type a type + * @return true if the given type is a compound type + */ + public static boolean isCompoundType(TypeMirror type) { + switch (type.getKind()) { + case ARRAY: + case EXECUTABLE: + case INTERSECTION: + case UNION: + case TYPEVAR: + case WILDCARD: + return true; + + case DECLARED: + DeclaredType declaredType = (DeclaredType) type; + return !declaredType.getTypeArguments().isEmpty(); + + default: + return false; + } + } + + /** + * Returns true if {@code type} has an enclosing type. + * + * @param type type to checker + * @return true if {@code type} has an enclosing type + */ + public static boolean hasEnclosingType(TypeMirror type) { + Type e = ((Type) type).getEnclosingType(); + return e.getKind() != TypeKind.NONE; + } + + /// Type variables and wildcards + + /** + * If the argument is a bounded TypeVariable or WildcardType, return its non-variable, + * non-wildcard upper bound. Otherwise, return the type itself. + * + * @param type a type + * @return the non-variable, non-wildcard upper bound of a type, if it has one, or itself if it + * has no bounds + */ + public static TypeMirror upperBound(TypeMirror type) { + do { + if (type instanceof TypeVariable) { + TypeVariable tvar = (TypeVariable) type; + if (tvar.getUpperBound() != null) { + type = tvar.getUpperBound(); + } else { + break; + } + } else if (type instanceof WildcardType) { + WildcardType wc = (WildcardType) type; + if (wc.getExtendsBound() != null) { + type = wc.getExtendsBound(); + } else { + break; + } + } else { + break; + } + } while (true); + return type; + } + + /** + * Get the type parameter for this wildcard from the underlying type's bound field. This field + * is sometimes null, in that case this method will return null. + * + * @param wildcard wildcard type + * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise + */ + public static @Nullable TypeParameterElement wildcardToTypeParam(WildcardType wildcard) { + return wildcardToTypeParam((Type.WildcardType) wildcard); + } + + /** + * Get the type parameter for this wildcard from the underlying type's bound field. This field + * is sometimes null, in that case this method will return null. + * + * @param wildcard wildcard type + * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise + */ + public static @Nullable TypeParameterElement wildcardToTypeParam(Type.WildcardType wildcard) { + + final Element typeParamElement; + if (wildcard.bound != null) { + typeParamElement = wildcard.bound.asElement(); } else { - break; + typeParamElement = null; } - } else if (type instanceof WildcardType) { - WildcardType wc = (WildcardType) type; - if (wc.getExtendsBound() != null) { - type = wc.getExtendsBound(); + + return (TypeParameterElement) typeParamElement; + } + + /** + * Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) that works with both jdk8 + * (called upperBound there) and jdk8u. + */ + // TODO: contrast to upperBound. + public static Type wildUpperBound(TypeMirror tm, ProcessingEnvironment env) { + Type t = (Type) tm; + if (t.hasTag(TypeTag.WILDCARD)) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); + if (w.isSuperBound()) { // returns true if w is unbound + Symtab syms = Symtab.instance(context); + // w.bound is null if the wildcard is from bytecode. + return w.bound == null ? syms.objectType : w.bound.getUpperBound(); + } else { + return wildUpperBound(w.type, env); + } } else { - break; + return TypeAnnotationUtils.unannotatedType(t); + } + } + + /** + * Returns the {@code DeclaredType} for {@code java.lang.Object}. + * + * @param env {@link ProcessingEnvironment} + * @return the {@code DeclaredType} for {@code java.lang.Object} + */ + public static DeclaredType getObjectTypeMirror(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Symtab syms = Symtab.instance(context); + return (DeclaredType) syms.objectType; + } + + /** + * Returns the lower bound of {@code typeVariable}. If it does not have a lower bound, returns + * the null type. + * + * @param typeVariable a type variable + * @param env the proceProcessingEnvironment + * @return the lower bound of {@code typeVariable} or the null type + */ + public static TypeMirror getTypeVariableLowerBound( + TypeVariable typeVariable, ProcessingEnvironment env) { + TypeMirror lb = typeVariable.getLowerBound(); + if (lb != null) { + return lb; } - } else { - break; - } - } while (true); - return type; - } - - /** - * Get the type parameter for this wildcard from the underlying type's bound field. This field is - * sometimes null, in that case this method will return null. - * - * @param wildcard wildcard type - * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise - */ - public static @Nullable TypeParameterElement wildcardToTypeParam(WildcardType wildcard) { - return wildcardToTypeParam((Type.WildcardType) wildcard); - } - - /** - * Get the type parameter for this wildcard from the underlying type's bound field. This field is - * sometimes null, in that case this method will return null. - * - * @param wildcard wildcard type - * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise - */ - public static @Nullable TypeParameterElement wildcardToTypeParam(Type.WildcardType wildcard) { - - final Element typeParamElement; - if (wildcard.bound != null) { - typeParamElement = wildcard.bound.asElement(); - } else { - typeParamElement = null; - } - - return (TypeParameterElement) typeParamElement; - } - - /** - * Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) that works with both jdk8 - * (called upperBound there) and jdk8u. - */ - // TODO: contrast to upperBound. - public static Type wildUpperBound(TypeMirror tm, ProcessingEnvironment env) { - Type t = (Type) tm; - if (t.hasTag(TypeTag.WILDCARD)) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); - if (w.isSuperBound()) { // returns true if w is unbound + + // Use bottom type to ensure there is a lower bound. + Context context = ((JavacProcessingEnvironment) env).getContext(); Symtab syms = Symtab.instance(context); - // w.bound is null if the wildcard is from bytecode. - return w.bound == null ? syms.objectType : w.bound.getUpperBound(); - } else { - return wildUpperBound(w.type, env); - } - } else { - return TypeAnnotationUtils.unannotatedType(t); - } - } - - /** - * Returns the {@code DeclaredType} for {@code java.lang.Object}. - * - * @param env {@link ProcessingEnvironment} - * @return the {@code DeclaredType} for {@code java.lang.Object} - */ - public static DeclaredType getObjectTypeMirror(ProcessingEnvironment env) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - Symtab syms = Symtab.instance(context); - return (DeclaredType) syms.objectType; - } - - /** - * Returns the lower bound of {@code typeVariable}. If it does not have a lower bound, returns the - * null type. - * - * @param typeVariable a type variable - * @param env the proceProcessingEnvironment - * @return the lower bound of {@code typeVariable} or the null type - */ - public static TypeMirror getTypeVariableLowerBound( - TypeVariable typeVariable, ProcessingEnvironment env) { - TypeMirror lb = typeVariable.getLowerBound(); - if (lb != null) { - return lb; - } - - // Use bottom type to ensure there is a lower bound. - Context context = ((JavacProcessingEnvironment) env).getContext(); - Symtab syms = Symtab.instance(context); - return syms.botType; - } - - /** - * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8 - * (called upperBound there) and jdk8u. - */ - public static Type wildLowerBound(TypeMirror tm, ProcessingEnvironment env) { - Type t = (Type) tm; - if (t.hasTag(TypeTag.WILDCARD)) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - Symtab syms = Symtab.instance(context); - Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); - return w.isExtendsBound() ? syms.botType : wildLowerBound(w.type, env); - } else { - return TypeAnnotationUtils.unannotatedType(t); - } - } - - /** - * Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If the - * bounded type extends other bounded types, this method will iterate through their bounds until a - * class, interface, or intersection is found. - * - * @return a type that is not a wildcard or typevar, or {@code null} if this type is an unbounded - * wildcard - */ - public static @Nullable TypeMirror findConcreteUpperBound(TypeMirror boundedType) { - TypeMirror effectiveUpper = boundedType; - outerLoop: - while (true) { - switch (effectiveUpper.getKind()) { - case WILDCARD: - effectiveUpper = ((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound(); - if (effectiveUpper == null) { + return syms.botType; + } + + /** + * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8 + * (called upperBound there) and jdk8u. + */ + public static Type wildLowerBound(TypeMirror tm, ProcessingEnvironment env) { + Type t = (Type) tm; + if (t.hasTag(TypeTag.WILDCARD)) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Symtab syms = Symtab.instance(context); + Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); + return w.isExtendsBound() ? syms.botType : wildLowerBound(w.type, env); + } else { + return TypeAnnotationUtils.unannotatedType(t); + } + } + + /** + * Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If the + * bounded type extends other bounded types, this method will iterate through their bounds until + * a class, interface, or intersection is found. + * + * @return a type that is not a wildcard or typevar, or {@code null} if this type is an + * unbounded wildcard + */ + public static @Nullable TypeMirror findConcreteUpperBound(TypeMirror boundedType) { + TypeMirror effectiveUpper = boundedType; + outerLoop: + while (true) { + switch (effectiveUpper.getKind()) { + case WILDCARD: + effectiveUpper = + ((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound(); + if (effectiveUpper == null) { + return null; + } + break; + + case TYPEVAR: + effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound(); + break; + + default: + break outerLoop; + } + } + return effectiveUpper; + } + + // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. + // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". + + /** + * Returns true if {@code type} is an unbounded wildcard. + * + * @param type the type to check + * @return true if the given type is an unbounded wildcard + */ + public static boolean hasNoExplicitBound(TypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && ((Type.WildcardType) type).kind == BoundKind.UNBOUND; + } + + /** + * Returns true if {@code type} is a wildcard with an explicit super bound. + * + * @param type the {@code type} to test + * @return true if {@code type} is explicitly super bounded + */ + public static boolean hasExplicitSuperBound(TypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && !hasNoExplicitBound(type) + && ((Type.WildcardType) type).isSuperBound(); + } + + /** + * Returns true if {@code type} is a wildcard with an explicit extends bound. + * + * @param type the type to test + * @return true if {@code type} is a wildcard with an explicit extends bound + */ + public static boolean hasExplicitExtendsBound(TypeMirror type) { + return type.getKind() == TypeKind.WILDCARD + && !hasNoExplicitBound(type) + && ((Type.WildcardType) type).isExtendsBound(); + } + + /** + * Returns true if this type is super bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is super bounded or unbounded + */ + public static boolean isUnboundedOrSuperBounded(WildcardType wildcardType) { + return ((Type.WildcardType) wildcardType).isSuperBound(); + } + + /** + * Returns true if this type is extends bounded or unbounded. + * + * @param wildcardType the wildcard type to test + * @return true if this type is extends bounded or unbounded + */ + public static boolean isUnboundedOrExtendsBounded(WildcardType wildcardType) { + return ((Type.WildcardType) wildcardType).isExtendsBound(); + } + + /** + * Returns true if the erased type of {@code subtype} is a subtype of the erased type of {@code + * supertype}. + * + * @param subtype possible subtype + * @param supertype possible supertype + * @param types a Types object + * @return true if the erased type of subtype is a subtype of the erased type of supertype + */ + public static boolean isErasedSubtype(TypeMirror subtype, TypeMirror supertype, Types types) { + return types.isSubtype(types.erasure(subtype), types.erasure(supertype)); + } + + /** + * Returns true if {@code type} is a type variable created during capture conversion. + * + * @param type a type mirror + * @return true if {@code type} is a type variable created during capture conversion + */ + public static boolean isCapturedTypeVariable(TypeMirror type) { + if (type.getKind() != TypeKind.TYPEVAR) { + return false; + } + return ((Type.TypeVar) TypeAnnotationUtils.unannotatedType(type)).isCaptured(); + } + + /** + * If {@code typeVar} is a captured type variable, then returns its underlying wildcard; + * otherwise returns {@code null}. + * + * @param typeVar a type variable that might be a captured type variable + * @return {@code typeVar} is a captured type variable, then returns its underlying wildcard; + * otherwise returns {@code null} + */ + public static @Nullable WildcardType getCapturedWildcard(TypeVariable typeVar) { + if (isCapturedTypeVariable(typeVar)) { + return ((CapturedType) TypeAnnotationUtils.unannotatedType(typeVar)).wildcard; + } + return null; + } + + /// Least upper bound and greatest lower bound + + /** + * Returns the least upper bound of two {@link TypeMirror}s, ignoring any annotations on the + * types. + * + *

                  Wrapper around Types.lub to add special handling for null types, primitives, and + * wildcards. + * + * @param tm1 a {@link TypeMirror} + * @param tm2 a {@link TypeMirror} + * @param processingEnv the {@link ProcessingEnvironment} to use + * @return the least upper bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror leastUpperBound( + TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + // Handle the 'null' type manually (not done by types.lub). + if (t1.getKind() == TypeKind.NULL) { + return t2; + } + if (t2.getKind() == TypeKind.NULL) { + return t1; + } + if (t1.getKind() == TypeKind.WILDCARD) { + WildcardType wc1 = (WildcardType) t1; + t1 = (Type) wc1.getExtendsBound(); + if (t1 == null) { + // Implicit upper bound of java.lang.Object + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + if (t2.getKind() == TypeKind.WILDCARD) { + WildcardType wc2 = (WildcardType) t2; + t2 = (Type) wc2.getExtendsBound(); + if (t2 == null) { + // Implicit upper bound of java.lang.Object + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + if (types.isSameType(t1, t2)) { + // Special case if the two types are equal. + return t1; + } + // Special case for primitives. + if (isPrimitive(t1) || isPrimitive(t2)) { + // NOTE: we need to know which type is primitive because e.g. int and Integer + // are assignable to each other. + if (isPrimitive(t1) && types.isAssignable(t1, t2)) { + return t2; + } else if (isPrimitive(t2) && types.isAssignable(t2, t1)) { + return t1; + } else { + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + + try { + return types.lub(t1, t2); + } catch (Exception e) { + // typetools issue #3025: In at least Java 8/9, types.lub throws an NPE + // on capture/wildcard combinations, see test case + // checker/tests/nullness/generics/Issue3025.java. + // Using j.l.Object is too coarse in case the type actually matters. + // This problem doesn't exist anymore in Java 11+, so let's + // see whether this is a problem for anyone in practice. + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + } + + /** + * Returns the greatest lower bound of two {@link TypeMirror}s, ignoring any annotations on the + * types. + * + *

                  Wrapper around Types.glb to add special handling for null types, primitives, and + * wildcards. + * + * @param tm1 a {@link TypeMirror} + * @param tm2 a {@link TypeMirror} + * @param processingEnv the {@link ProcessingEnvironment} to use + * @return the greatest lower bound of {@code tm1} and {@code tm2} + */ + public static TypeMirror greatestLowerBound( + TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { + Type t1 = TypeAnnotationUtils.unannotatedType(tm1); + Type t2 = TypeAnnotationUtils.unannotatedType(tm2); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + if (types.isSameType(t1, t2)) { + // Special case if the two types are equal. + return t1; + } + // Handle the 'null' type manually. + if (t1.getKind() == TypeKind.NULL) { + return t1; + } + if (t2.getKind() == TypeKind.NULL) { + return t2; + } + // Special case for primitives. + if (isPrimitive(t1) || isPrimitive(t2)) { + if (types.isAssignable(t1, t2)) { + return t1; + } else if (types.isAssignable(t2, t1)) { + return t2; + } else { + // Javac types.glb returns TypeKind.Error when the GLB does + // not exist, but we can't create one. Use TypeKind.NONE + // instead. + return processingEnv.getTypeUtils().getNoType(TypeKind.NONE); + } + } + if (t1.getKind() == TypeKind.WILDCARD) { + return t2; + } + if (t2.getKind() == TypeKind.WILDCARD) { + return t1; + } + + // If neither type is a primitive type, null type, or wildcard + // and if the types are not the same, use javac types.glb + return types.glb(t1, t2); + } + + /** + * Returns the most specific type from the list, or null if none exists. + * + * @param typeMirrors a list of types + * @param processingEnv the {@link ProcessingEnvironment} to use + * @return the most specific of the types, or null if none exists + */ + public static @Nullable TypeMirror mostSpecific( + List typeMirrors, ProcessingEnvironment processingEnv) { + if (typeMirrors.size() == 1) { + return typeMirrors.get(0); + } else { + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + com.sun.tools.javac.util.List typeList = typeMirrorListToTypeList(typeMirrors); + Type glb = types.glb(typeList); + for (Type candidate : typeList) { + if (types.isSameType(glb, candidate)) { + return candidate; + } + } + return null; + } + } + + /** + * Given a list of TypeMirror, return a list of Type. + * + * @param typeMirrors a list of TypeMirrors + * @return the argument, converted to a javac list + */ + private static com.sun.tools.javac.util.List typeMirrorListToTypeList( + List typeMirrors) { + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. + List typeList = CollectionsPlume.mapList(Type.class::cast, typeMirrors); + return com.sun.tools.javac.util.List.from(typeList); + } + + /// Substitutions + + /** + * Returns the return type of a method, given the receiver of the method call. + * + * @param methodElement a method + * @param substitutedReceiverType the receiver type, after substitution + * @param env the environment + * @return the return type of the method + */ + public static TypeMirror substituteMethodReturnType( + Element methodElement, TypeMirror substitutedReceiverType, ProcessingEnvironment env) { + + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(InternalUtils.getJavacContext(env)); + + Type substitutedMethodType = + types.memberType((Type) substitutedReceiverType, (Symbol) methodElement); + return substitutedMethodType.getReturnType(); + } + + /** + * Returns {@code type} as {@code superType} if {@code superType} is a super type of {@code + * type}; otherwise, null. + * + * @return {@code type} as {@code superType} if {@code superType} is a super type of {@code + * type}; otherwise, null + */ + public static @Nullable TypeMirror asSuper( + TypeMirror type, TypeMirror superType, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return javacTypes.asSuper((Type) type, ((Type) superType).tsym); + } + + /** + * Returns the superclass of the given class. Returns null if there is not one. + * + * @param type a type + * @param types type utilities + * @return the superclass of the given class, or null + */ + public static @Nullable TypeMirror getSuperclass(TypeMirror type, Types types) { + List superTypes = types.directSupertypes(type); + for (TypeMirror t : superTypes) { + // ignore interface types + if (!(t instanceof ClassType)) { + continue; + } + ClassType tt = (ClassType) t; + if (!tt.isInterface()) { + return t; + } + } + return null; + } + + /** + * Returns the superclass the given type. If there is no superclass the first interface returned + * by {@link Types#directSupertypes(TypeMirror)} is returned. If the type has neither a + * superclass nor a superinterface, then null is returned. + * + * @param type a type + * @param types type utilities + * @return the superclass or super interface of the given type, or null + */ + public static @Nullable DeclaredType getSuperClassOrInterface(TypeMirror type, Types types) { + List superTypes = types.directSupertypes(type); + for (TypeMirror t : superTypes) { + if (t.getKind() == TypeKind.DECLARED) { + return (DeclaredType) t; + } + } + return null; + } + + /** + * Returns the type of primitive conversion from {@code from} to {@code to}. + * + * @param from a primitive type + * @param to a primitive type + * @return the type of primitive conversion from {@code from} to {@code to} + */ + public static TypeKindUtils.PrimitiveConversionKind getPrimitiveConversionKind( + PrimitiveType from, PrimitiveType to) { + return TypeKindUtils.getPrimitiveConversionKind(from.getKind(), to.getKind()); + } + + /** + * Returns a new type mirror with the same type as {@code type} where all the type variables in + * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs}. + * + *

                  This is a wrapper around {@link com.sun.tools.javac.code.Types#subst(Type, + * com.sun.tools.javac.util.List, com.sun.tools.javac.util.List)}. + * + * @param type type to do substitution in + * @param typeVariables type variables that should be replaced with the type mirror at the same + * index of {@code typeArgs} + * @param typeArgs type mirrors that should replace the type variable at the same index of + * {@code typeVariables} + * @param env processing environment + * @return a new type mirror with the same type as {@code type} where all the type variables in + * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs} + */ + public static TypeMirror substitute( + TypeMirror type, + List typeVariables, + List typeArgs, + ProcessingEnvironment env) { + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. + List newP = CollectionsPlume.mapList(Type.class::cast, typeVariables); + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. + List newT = CollectionsPlume.mapList(Type.class::cast, typeArgs); + + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + return types.subst( + (Type) type, + com.sun.tools.javac.util.List.from(newP), + com.sun.tools.javac.util.List.from(newT)); + } + + /** + * Returns the depth of an array type. + * + * @param arrayType an array type + * @return the depth of {@code arrayType} + */ + public static int getArrayDepth(TypeMirror arrayType) { + int counter = 0; + TypeMirror type = arrayType; + while (type.getKind() == TypeKind.ARRAY) { + counter++; + type = ((ArrayType) type).getComponentType(); + } + return counter; + } + + /** + * If {@code typeMirror} is a wildcard, returns a fresh type variable that will be used as a + * captured type variable for it. If {@code typeMirror} is not a wildcard, returns {@code + * typeMirror}. + * + * @param typeMirror a type + * @param env processing environment + * @return a fresh type variable if {@code typeMirror} is a wildcard, otherwise {@code + * typeMirror} + */ + public static TypeMirror freshTypeVariable(TypeMirror typeMirror, ProcessingEnvironment env) { + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + com.sun.tools.javac.code.Types types = + com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); + return types.freshTypeVariables(com.sun.tools.javac.util.List.of((Type) typeMirror)).head; + } + + /** + * Creates a fresh type variable with bounds {@code upper} and {@code lower}. + * + * @param upper the upper bound to use, or if {@code null}, then {@code Object} is the upper + * bound + * @param lower the lower bound to use, or if {@code null}, then {@code NullType} is the lower + * bound + * @param env processing environment + * @return a fresh type variable + */ + public static TypeMirror freshTypeVariable( + @Nullable TypeMirror upper, @Nullable TypeMirror lower, ProcessingEnvironment env) { + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; + Names names = Names.instance(javacEnv.getContext()); + Symtab syms = Symtab.instance(javacEnv.getContext()); + com.sun.tools.javac.util.Name capturedName = names.fromString(""); + WildcardType wildcardType = null; + if (lower != null + && (lower.getKind() == TypeKind.ARRAY + || lower.getKind() == TypeKind.DECLARED + || lower.getKind() == TypeKind.TYPEVAR)) { + wildcardType = env.getTypeUtils().getWildcardType(null, lower); + } else if (upper != null + && (upper.getKind() == TypeKind.ARRAY + || upper.getKind() == TypeKind.DECLARED + || upper.getKind() == TypeKind.TYPEVAR)) { + wildcardType = env.getTypeUtils().getWildcardType(upper, null); + } else { + wildcardType = env.getTypeUtils().getWildcardType(null, null); + } + if (lower == null) { + lower = syms.botType; + } + if (upper == null) { + upper = syms.objectType; + } + return new CapturedType( + capturedName, + syms.noSymbol, + (Type) upper, + (Type) lower, + (Type.WildcardType) wildcardType); + } + + /** + * Returns the list of type variables such that a type variable in the list only references type + * variables at a lower index than itself. + * + * @param collection a collection of type variables + * @param types type utilities + * @return the type variables ordered so that each type variable only references earlier type + * variables + */ + public static List order(Collection collection, Types types) { + List list = new ArrayList<>(collection); + List ordered = new ArrayList<>(list.size()); + while (!list.isEmpty()) { + TypeVariable free = doesNotContainOthers(list, types); + list.remove(free); + ordered.add(free); + } + return ordered; + } + + /** + * Returns the first TypeVariable in {@code collection} that does not contain any other type in + * the collection. + * + * @param collection a collection of type variables + * @param types types + * @return the first TypeVariable in {@code collection} that does not contain any other type in + * the collection, but maybe itsself + */ + @SuppressWarnings("interning:not.interned") // must be the same object from collection + private static TypeVariable doesNotContainOthers( + Collection collection, Types types) { + for (TypeVariable candidate : collection) { + boolean doesNotContain = true; + for (TypeVariable other : collection) { + if (candidate != other && types.contains(candidate, other)) { + doesNotContain = false; + break; + } + } + if (doesNotContain) { + return candidate; + } + } + throw new BugInCF("Not found: %s", StringsPlume.join(",", collection)); + } + + /** + * This method returns the single abstract method declared by {@code functionalInterfaceType}. + * (The type of this method is referred to as the function type.) + * + * @param functionalInterfaceType a functional interface type + * @param env the processing environment + * @return the single abstract method declared by the type + */ + public static ExecutableElement findFunction( + TypeMirror functionalInterfaceType, ProcessingEnvironment env) { + Context ctx = ((JavacProcessingEnvironment) env).getContext(); + com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); + return (ExecutableElement) + javacTypes.findDescriptorSymbol(((Type) functionalInterfaceType).asElement()); + } + + /** + * This method returns the type of the single abstract method declared by {@code + * functionalInterfaceType}. + * + * @param functionalInterfaceType functional interface + * @param env ProcessingEnvironment + * @return the single abstract method declared by the type of the tree + */ + public static ExecutableType findFunctionType( + TypeMirror functionalInterfaceType, ProcessingEnvironment env) { + return (ExecutableType) findFunction(functionalInterfaceType, env).asType(); + } + + /** + * Return whether or not {@code type} is raw. + * + * @param type the type to check + * @return whether or not {@code type} is raw + */ + public static boolean isRaw(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + TypeElement typeelem = (TypeElement) ((DeclaredType) type).asElement(); + DeclaredType declType = (DeclaredType) typeelem.asType(); + return !declType.getTypeArguments().isEmpty() + && ((DeclaredType) type).getTypeArguments().isEmpty(); + } + + /** + * Returns the most specific supertype of {@code type} that is an array, or null if {@code type} + * is not a subtype of an array. + * + * @param type a type + * @param types TypesUtils + * @return the most specific supertype of {@code type} that is an array, or null if {@code type} + * is not a subtype of an array + */ + public static @Nullable TypeMirror getMostSpecificArrayType(TypeMirror type, Types types) { + if (type.getKind() == TypeKind.ARRAY) { + return type; + } else { + for (TypeMirror superType : types.directSupertypes(type)) { + TypeMirror arrayType = getMostSpecificArrayType(superType, types); + if (arrayType != null) { + // Only one of the types can be an array type, so return the first one found. + return arrayType; + } + } return null; - } - break; - - case TYPEVAR: - effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound(); - break; - - default: - break outerLoop; - } - } - return effectiveUpper; - } - - // For Wildcards, isSuperBound() and isExtendsBound() will return true if isUnbound() does. - // But don't use isUnbound(), because as of Java 18, it returns true for "? extends Object". - - /** - * Returns true if {@code type} is an unbounded wildcard. - * - * @param type the type to check - * @return true if the given type is an unbounded wildcard - */ - public static boolean hasNoExplicitBound(TypeMirror type) { - return type.getKind() == TypeKind.WILDCARD - && ((Type.WildcardType) type).kind == BoundKind.UNBOUND; - } - - /** - * Returns true if {@code type} is a wildcard with an explicit super bound. - * - * @param type the {@code type} to test - * @return true if {@code type} is explicitly super bounded - */ - public static boolean hasExplicitSuperBound(TypeMirror type) { - return type.getKind() == TypeKind.WILDCARD - && !hasNoExplicitBound(type) - && ((Type.WildcardType) type).isSuperBound(); - } - - /** - * Returns true if {@code type} is a wildcard with an explicit extends bound. - * - * @param type the type to test - * @return true if {@code type} is a wildcard with an explicit extends bound - */ - public static boolean hasExplicitExtendsBound(TypeMirror type) { - return type.getKind() == TypeKind.WILDCARD - && !hasNoExplicitBound(type) - && ((Type.WildcardType) type).isExtendsBound(); - } - - /** - * Returns true if this type is super bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is super bounded or unbounded - */ - public static boolean isUnboundedOrSuperBounded(WildcardType wildcardType) { - return ((Type.WildcardType) wildcardType).isSuperBound(); - } - - /** - * Returns true if this type is extends bounded or unbounded. - * - * @param wildcardType the wildcard type to test - * @return true if this type is extends bounded or unbounded - */ - public static boolean isUnboundedOrExtendsBounded(WildcardType wildcardType) { - return ((Type.WildcardType) wildcardType).isExtendsBound(); - } - - /** - * Returns true if the erased type of {@code subtype} is a subtype of the erased type of {@code - * supertype}. - * - * @param subtype possible subtype - * @param supertype possible supertype - * @param types a Types object - * @return true if the erased type of subtype is a subtype of the erased type of supertype - */ - public static boolean isErasedSubtype(TypeMirror subtype, TypeMirror supertype, Types types) { - return types.isSubtype(types.erasure(subtype), types.erasure(supertype)); - } - - /** - * Returns true if {@code type} is a type variable created during capture conversion. - * - * @param type a type mirror - * @return true if {@code type} is a type variable created during capture conversion - */ - public static boolean isCapturedTypeVariable(TypeMirror type) { - if (type.getKind() != TypeKind.TYPEVAR) { - return false; - } - return ((Type.TypeVar) TypeAnnotationUtils.unannotatedType(type)).isCaptured(); - } - - /** - * If {@code typeVar} is a captured type variable, then returns its underlying wildcard; otherwise - * returns {@code null}. - * - * @param typeVar a type variable that might be a captured type variable - * @return {@code typeVar} is a captured type variable, then returns its underlying wildcard; - * otherwise returns {@code null} - */ - public static @Nullable WildcardType getCapturedWildcard(TypeVariable typeVar) { - if (isCapturedTypeVariable(typeVar)) { - return ((CapturedType) TypeAnnotationUtils.unannotatedType(typeVar)).wildcard; - } - return null; - } - - /// Least upper bound and greatest lower bound - - /** - * Returns the least upper bound of two {@link TypeMirror}s, ignoring any annotations on the - * types. - * - *

                  Wrapper around Types.lub to add special handling for null types, primitives, and wildcards. - * - * @param tm1 a {@link TypeMirror} - * @param tm2 a {@link TypeMirror} - * @param processingEnv the {@link ProcessingEnvironment} to use - * @return the least upper bound of {@code tm1} and {@code tm2} - */ - public static TypeMirror leastUpperBound( - TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { - Type t1 = TypeAnnotationUtils.unannotatedType(tm1); - Type t2 = TypeAnnotationUtils.unannotatedType(tm2); - // Handle the 'null' type manually (not done by types.lub). - if (t1.getKind() == TypeKind.NULL) { - return t2; - } - if (t2.getKind() == TypeKind.NULL) { - return t1; - } - if (t1.getKind() == TypeKind.WILDCARD) { - WildcardType wc1 = (WildcardType) t1; - t1 = (Type) wc1.getExtendsBound(); - if (t1 == null) { - // Implicit upper bound of java.lang.Object - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - if (t2.getKind() == TypeKind.WILDCARD) { - WildcardType wc2 = (WildcardType) t2; - t2 = (Type) wc2.getExtendsBound(); - if (t2 == null) { - // Implicit upper bound of java.lang.Object - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - if (types.isSameType(t1, t2)) { - // Special case if the two types are equal. - return t1; - } - // Special case for primitives. - if (isPrimitive(t1) || isPrimitive(t2)) { - // NOTE: we need to know which type is primitive because e.g. int and Integer - // are assignable to each other. - if (isPrimitive(t1) && types.isAssignable(t1, t2)) { - return t2; - } else if (isPrimitive(t2) && types.isAssignable(t2, t1)) { - return t1; - } else { - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - - try { - return types.lub(t1, t2); - } catch (Exception e) { - // typetools issue #3025: In at least Java 8/9, types.lub throws an NPE - // on capture/wildcard combinations, see test case - // checker/tests/nullness/generics/Issue3025.java. - // Using j.l.Object is too coarse in case the type actually matters. - // This problem doesn't exist anymore in Java 11+, so let's - // see whether this is a problem for anyone in practice. - Elements elements = processingEnv.getElementUtils(); - return elements.getTypeElement("java.lang.Object").asType(); - } - } - - /** - * Returns the greatest lower bound of two {@link TypeMirror}s, ignoring any annotations on the - * types. - * - *

                  Wrapper around Types.glb to add special handling for null types, primitives, and wildcards. - * - * @param tm1 a {@link TypeMirror} - * @param tm2 a {@link TypeMirror} - * @param processingEnv the {@link ProcessingEnvironment} to use - * @return the greatest lower bound of {@code tm1} and {@code tm2} - */ - public static TypeMirror greatestLowerBound( - TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { - Type t1 = TypeAnnotationUtils.unannotatedType(tm1); - Type t2 = TypeAnnotationUtils.unannotatedType(tm2); - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - if (types.isSameType(t1, t2)) { - // Special case if the two types are equal. - return t1; - } - // Handle the 'null' type manually. - if (t1.getKind() == TypeKind.NULL) { - return t1; - } - if (t2.getKind() == TypeKind.NULL) { - return t2; - } - // Special case for primitives. - if (isPrimitive(t1) || isPrimitive(t2)) { - if (types.isAssignable(t1, t2)) { - return t1; - } else if (types.isAssignable(t2, t1)) { - return t2; - } else { - // Javac types.glb returns TypeKind.Error when the GLB does - // not exist, but we can't create one. Use TypeKind.NONE - // instead. - return processingEnv.getTypeUtils().getNoType(TypeKind.NONE); - } - } - if (t1.getKind() == TypeKind.WILDCARD) { - return t2; - } - if (t2.getKind() == TypeKind.WILDCARD) { - return t1; - } - - // If neither type is a primitive type, null type, or wildcard - // and if the types are not the same, use javac types.glb - return types.glb(t1, t2); - } - - /** - * Returns the most specific type from the list, or null if none exists. - * - * @param typeMirrors a list of types - * @param processingEnv the {@link ProcessingEnvironment} to use - * @return the most specific of the types, or null if none exists - */ - public static @Nullable TypeMirror mostSpecific( - List typeMirrors, ProcessingEnvironment processingEnv) { - if (typeMirrors.size() == 1) { - return typeMirrors.get(0); - } else { - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - com.sun.tools.javac.util.List typeList = typeMirrorListToTypeList(typeMirrors); - Type glb = types.glb(typeList); - for (Type candidate : typeList) { - if (types.isSameType(glb, candidate)) { - return candidate; } - } - return null; - } - } - - /** - * Given a list of TypeMirror, return a list of Type. - * - * @param typeMirrors a list of TypeMirrors - * @return the argument, converted to a javac list - */ - private static com.sun.tools.javac.util.List typeMirrorListToTypeList( - List typeMirrors) { - @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. - List typeList = CollectionsPlume.mapList(Type.class::cast, typeMirrors); - return com.sun.tools.javac.util.List.from(typeList); - } - - /// Substitutions - - /** - * Returns the return type of a method, given the receiver of the method call. - * - * @param methodElement a method - * @param substitutedReceiverType the receiver type, after substitution - * @param env the environment - * @return the return type of the method - */ - public static TypeMirror substituteMethodReturnType( - Element methodElement, TypeMirror substitutedReceiverType, ProcessingEnvironment env) { - - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(InternalUtils.getJavacContext(env)); - - Type substitutedMethodType = - types.memberType((Type) substitutedReceiverType, (Symbol) methodElement); - return substitutedMethodType.getReturnType(); - } - - /** - * Returns {@code type} as {@code superType} if {@code superType} is a super type of {@code type}; - * otherwise, null. - * - * @return {@code type} as {@code superType} if {@code superType} is a super type of {@code type}; - * otherwise, null - */ - public static @Nullable TypeMirror asSuper( - TypeMirror type, TypeMirror superType, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); - return javacTypes.asSuper((Type) type, ((Type) superType).tsym); - } - - /** - * Returns the superclass of the given class. Returns null if there is not one. - * - * @param type a type - * @param types type utilities - * @return the superclass of the given class, or null - */ - public static @Nullable TypeMirror getSuperclass(TypeMirror type, Types types) { - List superTypes = types.directSupertypes(type); - for (TypeMirror t : superTypes) { - // ignore interface types - if (!(t instanceof ClassType)) { - continue; - } - ClassType tt = (ClassType) t; - if (!tt.isInterface()) { - return t; - } - } - return null; - } - - /** - * Returns the superclass the given type. If there is no superclass the first interface returned - * by {@link Types#directSupertypes(TypeMirror)} is returned. If the type has neither a superclass - * nor a superinterface, then null is returned. - * - * @param type a type - * @param types type utilities - * @return the superclass or super interface of the given type, or null - */ - public static @Nullable DeclaredType getSuperClassOrInterface(TypeMirror type, Types types) { - List superTypes = types.directSupertypes(type); - for (TypeMirror t : superTypes) { - if (t.getKind() == TypeKind.DECLARED) { - return (DeclaredType) t; - } - } - return null; - } - - /** - * Returns the type of primitive conversion from {@code from} to {@code to}. - * - * @param from a primitive type - * @param to a primitive type - * @return the type of primitive conversion from {@code from} to {@code to} - */ - public static TypeKindUtils.PrimitiveConversionKind getPrimitiveConversionKind( - PrimitiveType from, PrimitiveType to) { - return TypeKindUtils.getPrimitiveConversionKind(from.getKind(), to.getKind()); - } - - /** - * Returns a new type mirror with the same type as {@code type} where all the type variables in - * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs}. - * - *

                  This is a wrapper around {@link com.sun.tools.javac.code.Types#subst(Type, - * com.sun.tools.javac.util.List, com.sun.tools.javac.util.List)}. - * - * @param type type to do substitution in - * @param typeVariables type variables that should be replaced with the type mirror at the same - * index of {@code typeArgs} - * @param typeArgs type mirrors that should replace the type variable at the same index of {@code - * typeVariables} - * @param env processing environment - * @return a new type mirror with the same type as {@code type} where all the type variables in - * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs} - */ - public static TypeMirror substitute( - TypeMirror type, - List typeVariables, - List typeArgs, - ProcessingEnvironment env) { - @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. - List newP = CollectionsPlume.mapList(Type.class::cast, typeVariables); - @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. - List newT = CollectionsPlume.mapList(Type.class::cast, typeArgs); - - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - return types.subst( - (Type) type, - com.sun.tools.javac.util.List.from(newP), - com.sun.tools.javac.util.List.from(newT)); - } - - /** - * Returns the depth of an array type. - * - * @param arrayType an array type - * @return the depth of {@code arrayType} - */ - public static int getArrayDepth(TypeMirror arrayType) { - int counter = 0; - TypeMirror type = arrayType; - while (type.getKind() == TypeKind.ARRAY) { - counter++; - type = ((ArrayType) type).getComponentType(); - } - return counter; - } - - /** - * If {@code typeMirror} is a wildcard, returns a fresh type variable that will be used as a - * captured type variable for it. If {@code typeMirror} is not a wildcard, returns {@code - * typeMirror}. - * - * @param typeMirror a type - * @param env processing environment - * @return a fresh type variable if {@code typeMirror} is a wildcard, otherwise {@code typeMirror} - */ - public static TypeMirror freshTypeVariable(TypeMirror typeMirror, ProcessingEnvironment env) { - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; - com.sun.tools.javac.code.Types types = - com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); - return types.freshTypeVariables(com.sun.tools.javac.util.List.of((Type) typeMirror)).head; - } - - /** - * Creates a fresh type variable with bounds {@code upper} and {@code lower}. - * - * @param upper the upper bound to use, or if {@code null}, then {@code Object} is the upper bound - * @param lower the lower bound to use, or if {@code null}, then {@code NullType} is the lower - * bound - * @param env processing environment - * @return a fresh type variable - */ - public static TypeMirror freshTypeVariable( - @Nullable TypeMirror upper, @Nullable TypeMirror lower, ProcessingEnvironment env) { - JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; - Names names = Names.instance(javacEnv.getContext()); - Symtab syms = Symtab.instance(javacEnv.getContext()); - com.sun.tools.javac.util.Name capturedName = names.fromString(""); - WildcardType wildcardType = null; - if (lower != null - && (lower.getKind() == TypeKind.ARRAY - || lower.getKind() == TypeKind.DECLARED - || lower.getKind() == TypeKind.TYPEVAR)) { - wildcardType = env.getTypeUtils().getWildcardType(null, lower); - } else if (upper != null - && (upper.getKind() == TypeKind.ARRAY - || upper.getKind() == TypeKind.DECLARED - || upper.getKind() == TypeKind.TYPEVAR)) { - wildcardType = env.getTypeUtils().getWildcardType(upper, null); - } else { - wildcardType = env.getTypeUtils().getWildcardType(null, null); - } - if (lower == null) { - lower = syms.botType; - } - if (upper == null) { - upper = syms.objectType; - } - return new CapturedType( - capturedName, syms.noSymbol, (Type) upper, (Type) lower, (Type.WildcardType) wildcardType); - } - - /** - * Returns the list of type variables such that a type variable in the list only references type - * variables at a lower index than itself. - * - * @param collection a collection of type variables - * @param types type utilities - * @return the type variables ordered so that each type variable only references earlier type - * variables - */ - public static List order(Collection collection, Types types) { - List list = new ArrayList<>(collection); - List ordered = new ArrayList<>(list.size()); - while (!list.isEmpty()) { - TypeVariable free = doesNotContainOthers(list, types); - list.remove(free); - ordered.add(free); - } - return ordered; - } - - /** - * Returns the first TypeVariable in {@code collection} that does not contain any other type in - * the collection. - * - * @param collection a collection of type variables - * @param types types - * @return the first TypeVariable in {@code collection} that does not contain any other type in - * the collection, but maybe itsself - */ - @SuppressWarnings("interning:not.interned") // must be the same object from collection - private static TypeVariable doesNotContainOthers( - Collection collection, Types types) { - for (TypeVariable candidate : collection) { - boolean doesNotContain = true; - for (TypeVariable other : collection) { - if (candidate != other && types.contains(candidate, other)) { - doesNotContain = false; - break; + } + + /** + * Returns true if {@code type} is a parameterized type. A declared type is parameterized if it + * has parameters. An array type is parameterized if the inner-most component type has + * parameters. + * + * @param type type to check + * @return true if {@code type} is a parameterized declared type or array type + */ + public static boolean isParameterizedType(TypeMirror type) { + return ((Type) type).isParameterized(); + } + + /** + * Return true if {@code typeMirror} is a declared type that has at least one wildcard as a type + * argument. + * + * @param typeMirror type to check + * @return true if {@code typeMirror} is a declared type that has at least one wildcard as a + * type argument + */ + public static boolean isWildcardParameterized(TypeMirror typeMirror) { + if (isParameterizedType(typeMirror) && typeMirror.getKind() == TypeKind.DECLARED) { + for (TypeMirror t : ((DeclaredType) typeMirror).getTypeArguments()) { + if (t.getKind() == TypeKind.WILDCARD) { + return true; + } + } } - } - if (doesNotContain) { - return candidate; - } - } - throw new BugInCF("Not found: %s", StringsPlume.join(",", collection)); - } - - /** - * This method returns the single abstract method declared by {@code functionalInterfaceType}. - * (The type of this method is referred to as the function type.) - * - * @param functionalInterfaceType a functional interface type - * @param env the processing environment - * @return the single abstract method declared by the type - */ - public static ExecutableElement findFunction( - TypeMirror functionalInterfaceType, ProcessingEnvironment env) { - Context ctx = ((JavacProcessingEnvironment) env).getContext(); - com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); - return (ExecutableElement) - javacTypes.findDescriptorSymbol(((Type) functionalInterfaceType).asElement()); - } - - /** - * This method returns the type of the single abstract method declared by {@code - * functionalInterfaceType}. - * - * @param functionalInterfaceType functional interface - * @param env ProcessingEnvironment - * @return the single abstract method declared by the type of the tree - */ - public static ExecutableType findFunctionType( - TypeMirror functionalInterfaceType, ProcessingEnvironment env) { - return (ExecutableType) findFunction(functionalInterfaceType, env).asType(); - } - - /** - * Return whether or not {@code type} is raw. - * - * @param type the type to check - * @return whether or not {@code type} is raw - */ - public static boolean isRaw(TypeMirror type) { - if (type.getKind() != TypeKind.DECLARED) { - return false; - } - TypeElement typeelem = (TypeElement) ((DeclaredType) type).asElement(); - DeclaredType declType = (DeclaredType) typeelem.asType(); - return !declType.getTypeArguments().isEmpty() - && ((DeclaredType) type).getTypeArguments().isEmpty(); - } - - /** - * Returns the most specific supertype of {@code type} that is an array, or null if {@code type} - * is not a subtype of an array. - * - * @param type a type - * @param types TypesUtils - * @return the most specific supertype of {@code type} that is an array, or null if {@code type} - * is not a subtype of an array - */ - public static @Nullable TypeMirror getMostSpecificArrayType(TypeMirror type, Types types) { - if (type.getKind() == TypeKind.ARRAY) { - return type; - } else { - for (TypeMirror superType : types.directSupertypes(type)) { - TypeMirror arrayType = getMostSpecificArrayType(superType, types); - if (arrayType != null) { - // Only one of the types can be an array type, so return the first one found. - return arrayType; + return false; + } + + /** + * Creates a wildcard with the given bounds. If {@code lowerBound} is non-null, the {@code + * upperBound} must be {@code null} or {@code Object}. If {@code upperBound} is non-null and not + * {@code Object}, then {@code lowerBound} must be {@code null}; + * + * @param lowerBound the lower bound for the wildcard + * @param upperBound the upper bound for the wildcard + * @param types TypesUtils + * @return a wildcard with the given bounds + */ + public static TypeMirror createWildcard( + TypeMirror lowerBound, TypeMirror upperBound, Types types) { + TypeMirror nonObjectUpperBound = upperBound; + if (isObject(upperBound)) { + nonObjectUpperBound = null; } - } - return null; - } - } - - /** - * Returns true if {@code type} is a parameterized type. A declared type is parameterized if it - * has parameters. An array type is parameterized if the inner-most component type has parameters. - * - * @param type type to check - * @return true if {@code type} is a parameterized declared type or array type - */ - public static boolean isParameterizedType(TypeMirror type) { - return ((Type) type).isParameterized(); - } - - /** - * Return true if {@code typeMirror} is a declared type that has at least one wildcard as a type - * argument. - * - * @param typeMirror type to check - * @return true if {@code typeMirror} is a declared type that has at least one wildcard as a type - * argument - */ - public static boolean isWildcardParameterized(TypeMirror typeMirror) { - if (isParameterizedType(typeMirror) && typeMirror.getKind() == TypeKind.DECLARED) { - for (TypeMirror t : ((DeclaredType) typeMirror).getTypeArguments()) { - if (t.getKind() == TypeKind.WILDCARD) { - return true; + + assert lowerBound == null || nonObjectUpperBound == null; + WildcardType wildcardType = types.getWildcardType(nonObjectUpperBound, lowerBound); + return com.sun.tools.javac.util.List.of((Type) wildcardType).head; + } + + /** + * Returns true if the type is byte, short, char, Byte, Short, or Character. All other + * narrowings require a cast. See JLS 5.1.3. + * + * @param type a type + * @param types the type utilities + * @return true if assignment to the type may be a narrowing + */ + public static boolean canBeNarrowingPrimitiveConversion(TypeMirror type, Types types) { + // See CFGBuilder.CFGTranslationPhaseOne#conversionRequiresNarrowing() + TypeMirror unboxedType = isBoxedPrimitive(type) ? types.unboxedType(type) : type; + TypeKind unboxedKind = unboxedType.getKind(); + return unboxedKind == TypeKind.BYTE + || unboxedKind == TypeKind.SHORT + || unboxedKind == TypeKind.CHAR; + } + + /** + * Returns true if the two type variables are the same type variable. Meaning they have the same + * name and the same enclosing element. Unlike {@link Types#isSameType(TypeMirror, TypeMirror)}, + * they do not have to be the same object. + * + *

                  This method is needed when a type has gone through type variable substitution, but only + * some of the type variables were substituted. Also, a new {@link TypeVariable} object is + * created as the type of a tree created by {@link + * org.checkerframework.javacutil.trees.TreeBuilder}. + * + * @param typeVariable1 a type variable + * @param typeVariable2 a type variable + * @return if the two type variables are the same type variable + */ + @EqualsMethod + public static boolean areSame(TypeVariable typeVariable1, TypeVariable typeVariable2) { + if (typeVariable1 == typeVariable2) { + return true; } - } - } - return false; - } - - /** - * Creates a wildcard with the given bounds. If {@code lowerBound} is non-null, the {@code - * upperBound} must be {@code null} or {@code Object}. If {@code upperBound} is non-null and not - * {@code Object}, then {@code lowerBound} must be {@code null}; - * - * @param lowerBound the lower bound for the wildcard - * @param upperBound the upper bound for the wildcard - * @param types TypesUtils - * @return a wildcard with the given bounds - */ - public static TypeMirror createWildcard( - TypeMirror lowerBound, TypeMirror upperBound, Types types) { - TypeMirror nonObjectUpperBound = upperBound; - if (isObject(upperBound)) { - nonObjectUpperBound = null; - } - - assert lowerBound == null || nonObjectUpperBound == null; - WildcardType wildcardType = types.getWildcardType(nonObjectUpperBound, lowerBound); - return com.sun.tools.javac.util.List.of((Type) wildcardType).head; - } - - /** - * Returns true if the type is byte, short, char, Byte, Short, or Character. All other narrowings - * require a cast. See JLS 5.1.3. - * - * @param type a type - * @param types the type utilities - * @return true if assignment to the type may be a narrowing - */ - public static boolean canBeNarrowingPrimitiveConversion(TypeMirror type, Types types) { - // See CFGBuilder.CFGTranslationPhaseOne#conversionRequiresNarrowing() - TypeMirror unboxedType = isBoxedPrimitive(type) ? types.unboxedType(type) : type; - TypeKind unboxedKind = unboxedType.getKind(); - return unboxedKind == TypeKind.BYTE - || unboxedKind == TypeKind.SHORT - || unboxedKind == TypeKind.CHAR; - } - - /** - * Returns true if the two type variables are the same type variable. Meaning they have the same - * name and the same enclosing element. Unlike {@link Types#isSameType(TypeMirror, TypeMirror)}, - * they do not have to be the same object. - * - *

                  This method is needed when a type has gone through type variable substitution, but only some - * of the type variables were substituted. Also, a new {@link TypeVariable} object is created as - * the type of a tree created by {@link org.checkerframework.javacutil.trees.TreeBuilder}. - * - * @param typeVariable1 a type variable - * @param typeVariable2 a type variable - * @return if the two type variables are the same type variable - */ - @EqualsMethod - public static boolean areSame(TypeVariable typeVariable1, TypeVariable typeVariable2) { - if (typeVariable1 == typeVariable2) { - return true; - } - Name otherName = typeVariable2.asElement().getSimpleName(); - Element otherEnclosingElement = typeVariable2.asElement().getEnclosingElement(); - - return typeVariable1.asElement().getSimpleName().contentEquals(otherName) - && otherEnclosingElement.equals(typeVariable1.asElement().getEnclosingElement()); - } + Name otherName = typeVariable2.asElement().getSimpleName(); + Element otherEnclosingElement = typeVariable2.asElement().getEnclosingElement(); + + return typeVariable1.asElement().getSimpleName().contentEquals(otherName) + && otherEnclosingElement.equals(typeVariable1.asElement().getEnclosingElement()); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java b/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java index 5e5d71b105a..440f0933b80 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/UserError.java @@ -13,26 +13,26 @@ @SuppressWarnings("serial") public class UserError extends RuntimeException { - /** - * Constructs a new CheckerError with the specified detail message. - * - * @param message the detail message - */ - public UserError(String message) { - super(message); - if (message == null) { - throw new BugInCF("Must have a detail message."); + /** + * Constructs a new CheckerError with the specified detail message. + * + * @param message the detail message + */ + public UserError(String message) { + super(message); + if (message == null) { + throw new BugInCF("Must have a detail message."); + } } - } - /** - * Constructs a new CheckerError with a detail message composed from the given arguments. - * - * @param fmt the format string - * @param args the arguments for the format string - */ - @FormatMethod - public UserError(String fmt, @Nullable Object... args) { - this(String.format(fmt, args)); - } + /** + * Constructs a new CheckerError with a detail message composed from the given arguments. + * + * @param fmt the format string + * @param args the arguments for the format string + */ + @FormatMethod + public UserError(String fmt, @Nullable Object... args) { + this(String.format(fmt, args)); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java index 2b08359b30a..2edcbed71fd 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java @@ -4,6 +4,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.util.Name; + import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -13,21 +14,21 @@ */ public class DetachedVarSymbol extends Symbol.VarSymbol { - protected @Nullable VariableTree decl; + protected @Nullable VariableTree decl; - /** Construct a detached variable symbol, given its flags, name, type and owner. */ - public DetachedVarSymbol(long flags, Name name, Type type, Symbol owner) { - super(flags, name, type, owner); - this.decl = null; - } + /** Construct a detached variable symbol, given its flags, name, type and owner. */ + public DetachedVarSymbol(long flags, Name name, Type type, Symbol owner) { + super(flags, name, type, owner); + this.decl = null; + } - /** Set the declaration tree for the variable. */ - public void setDeclaration(VariableTree decl) { - this.decl = decl; - } + /** Set the declaration tree for the variable. */ + public void setDeclaration(VariableTree decl) { + this.decl = decl; + } - /** Get the declaration tree for the variable. */ - public @Nullable VariableTree getDeclaration() { - return decl; - } + /** Get the declaration tree for the variable. */ + public @Nullable VariableTree getDeclaration() { + return decl; + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java index 4a5e16ef5ad..7cc104a9a2f 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java @@ -24,7 +24,14 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; + +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.plumelib.util.CollectionsPlume; + import java.util.List; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -37,10 +44,6 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.CollectionsPlume; /** * The TreeBuilder permits the creation of new AST Trees using the non-public Java compiler API @@ -48,680 +51,694 @@ */ public class TreeBuilder { - /** The javac {@link Elements} object. */ - protected final Elements elements; - - /** The javac {@link javax.lang.model.util.Types} object. */ - protected final Types modelTypes; - - /** The internal javac {@link com.sun.tools.javac.code.Types} object. */ - protected final com.sun.tools.javac.code.Types javacTypes; - - /** For constructing trees */ - protected final TreeMaker maker; - - /** The javac {@link Names} object. */ - protected final Names names; - - /** The javac {@link Symtab} object. */ - protected final Symtab symtab; - - /** The javac {@link ProcessingEnvironment} */ - protected final ProcessingEnvironment env; - - /** - * {@link Name} object for "close", used when building a tree for a call to {@code close()}. - * - * @see #buildCloseMethodAccess(ExpressionTree) - */ - private final Name closeName; - - /** - * Creates a new TreeBuilder. - * - * @param env the javac {@link ProcessingEnvironment} - */ - public TreeBuilder(ProcessingEnvironment env) { - this.env = env; - Context context = ((JavacProcessingEnvironment) env).getContext(); - elements = env.getElementUtils(); - modelTypes = env.getTypeUtils(); - javacTypes = com.sun.tools.javac.code.Types.instance(context); - maker = TreeMaker.instance(context); - names = Names.instance(context); - symtab = Symtab.instance(context); - closeName = names.fromString("close"); - } - - /** - * Builds an AST Tree to access the iterator() method of some iterable expression. - * - * @param iterableExpr an expression whose type is a subtype of Iterable - * @return a MemberSelectTree that accesses the iterator() method of the expression - */ - public MemberSelectTree buildIteratorMethodAccess(ExpressionTree iterableExpr) { - DeclaredType exprType = (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(iterableExpr)); - assert exprType != null : "expression must be of declared type Iterable<>"; - - TypeElement exprElement = (TypeElement) exprType.asElement(); - - // Find the iterator() method of the iterable type - Symbol.MethodSymbol iteratorMethod = null; - - for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("iterator")) { - iteratorMethod = (Symbol.MethodSymbol) method; - } + /** The javac {@link Elements} object. */ + protected final Elements elements; + + /** The javac {@link javax.lang.model.util.Types} object. */ + protected final Types modelTypes; + + /** The internal javac {@link com.sun.tools.javac.code.Types} object. */ + protected final com.sun.tools.javac.code.Types javacTypes; + + /** For constructing trees */ + protected final TreeMaker maker; + + /** The javac {@link Names} object. */ + protected final Names names; + + /** The javac {@link Symtab} object. */ + protected final Symtab symtab; + + /** The javac {@link ProcessingEnvironment} */ + protected final ProcessingEnvironment env; + + /** + * {@link Name} object for "close", used when building a tree for a call to {@code close()}. + * + * @see #buildCloseMethodAccess(ExpressionTree) + */ + private final Name closeName; + + /** + * Creates a new TreeBuilder. + * + * @param env the javac {@link ProcessingEnvironment} + */ + public TreeBuilder(ProcessingEnvironment env) { + this.env = env; + Context context = ((JavacProcessingEnvironment) env).getContext(); + elements = env.getElementUtils(); + modelTypes = env.getTypeUtils(); + javacTypes = com.sun.tools.javac.code.Types.instance(context); + maker = TreeMaker.instance(context); + names = Names.instance(context); + symtab = Symtab.instance(context); + closeName = names.fromString("close"); + } + + /** + * Builds an AST Tree to access the iterator() method of some iterable expression. + * + * @param iterableExpr an expression whose type is a subtype of Iterable + * @return a MemberSelectTree that accesses the iterator() method of the expression + */ + public MemberSelectTree buildIteratorMethodAccess(ExpressionTree iterableExpr) { + DeclaredType exprType = + (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(iterableExpr)); + assert exprType != null : "expression must be of declared type Iterable<>"; + + TypeElement exprElement = (TypeElement) exprType.asElement(); + + // Find the iterator() method of the iterable type + Symbol.MethodSymbol iteratorMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + if (method.getParameters().isEmpty() + && method.getSimpleName().contentEquals("iterator")) { + iteratorMethod = (Symbol.MethodSymbol) method; + } + } + + assert iteratorMethod != null + : "@AssumeAssertion(nullness): no iterator method declared for expression type"; + + Type.MethodType methodType = (Type.MethodType) iteratorMethod.asType(); + Symbol.TypeSymbol methodClass = methodType.asElement(); + DeclaredType iteratorType = (DeclaredType) methodType.getReturnType(); + iteratorType = + (DeclaredType) + javacTypes.asSuper((Type) iteratorType, symtab.iteratorType.asElement()); + + int numIterTypeArgs = iteratorType.getTypeArguments().size(); + assert numIterTypeArgs <= 1 : "expected at most one type argument for Iterator"; + + if (numIterTypeArgs == 1) { + TypeMirror elementType = iteratorType.getTypeArguments().get(0); + // Remove captured type variable from a wildcard. + if (elementType instanceof Type.CapturedType) { + elementType = ((Type.CapturedType) elementType).wildcard; + TypeElement iteratorElt = (TypeElement) modelTypes.asElement(iteratorType); + assert iteratorElt != null + : "@AssumeAssertion(nullness): the iterator type always has an element"; + + iteratorType = modelTypes.getDeclaredType(iteratorElt, elementType); + } + } + + // Replace the iterator method's generic return type with + // the actual element type of the expression. + Type.MethodType updatedMethodType = + new Type.MethodType( + com.sun.tools.javac.util.List.nil(), + (Type) iteratorType, + com.sun.tools.javac.util.List.nil(), + methodClass); + + JCTree.JCFieldAccess iteratorAccess = TreeUtils.Select(maker, iterableExpr, iteratorMethod); + iteratorAccess.setType(updatedMethodType); + + return iteratorAccess; } - assert iteratorMethod != null - : "@AssumeAssertion(nullness): no iterator method declared for expression type"; - - Type.MethodType methodType = (Type.MethodType) iteratorMethod.asType(); - Symbol.TypeSymbol methodClass = methodType.asElement(); - DeclaredType iteratorType = (DeclaredType) methodType.getReturnType(); - iteratorType = - (DeclaredType) javacTypes.asSuper((Type) iteratorType, symtab.iteratorType.asElement()); - - int numIterTypeArgs = iteratorType.getTypeArguments().size(); - assert numIterTypeArgs <= 1 : "expected at most one type argument for Iterator"; - - if (numIterTypeArgs == 1) { - TypeMirror elementType = iteratorType.getTypeArguments().get(0); - // Remove captured type variable from a wildcard. - if (elementType instanceof Type.CapturedType) { - elementType = ((Type.CapturedType) elementType).wildcard; - TypeElement iteratorElt = (TypeElement) modelTypes.asElement(iteratorType); - assert iteratorElt != null - : "@AssumeAssertion(nullness): the iterator type always has an element"; - - iteratorType = modelTypes.getDeclaredType(iteratorElt, elementType); - } + /** + * Build a {@link MemberSelectTree} for accessing the {@code close} method of an expression that + * implements {@link AutoCloseable}. This method is used when desugaring try-with-resources + * statements during CFG construction. + * + * @param autoCloseableExpr the expression + * @return the member select tree + */ + public MemberSelectTree buildCloseMethodAccess(ExpressionTree autoCloseableExpr) { + DeclaredType exprType = + (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(autoCloseableExpr)); + assert exprType != null + : "expression must be of declared type AutoCloseable: " + autoCloseableExpr; + + TypeElement exprElement = (TypeElement) exprType.asElement(); + + // Find the close() method + Symbol.MethodSymbol closeMethod = null; + + // We could use elements.getAllMembers(exprElement) to find the close method, but in rare + // cases calling that method crashes with a Symbol$CompletionFailure exception. See + // https://github.com/typetools/checker-framework/issues/6396. The code below directly + // searches all supertypes for the method and avoids the crash. + for (Type s : javacTypes.closure(((Symbol) exprElement).type)) { + for (Symbol m : s.tsym.members().getSymbolsByName(closeName)) { + if (!(m instanceof Symbol.MethodSymbol)) { + continue; + } + Symbol.MethodSymbol msym = (Symbol.MethodSymbol) m; + if (!msym.isStatic() && msym.getParameters().isEmpty()) { + closeMethod = msym; + break; + } + } + } + + assert closeMethod != null + : "@AssumeAssertion(nullness): no close method declared for expression type"; + + JCTree.JCFieldAccess closeAccess = TreeUtils.Select(maker, autoCloseableExpr, closeMethod); + + return closeAccess; } - // Replace the iterator method's generic return type with - // the actual element type of the expression. - Type.MethodType updatedMethodType = - new Type.MethodType( - com.sun.tools.javac.util.List.nil(), - (Type) iteratorType, - com.sun.tools.javac.util.List.nil(), - methodClass); - - JCTree.JCFieldAccess iteratorAccess = TreeUtils.Select(maker, iterableExpr, iteratorMethod); - iteratorAccess.setType(updatedMethodType); - - return iteratorAccess; - } - - /** - * Build a {@link MemberSelectTree} for accessing the {@code close} method of an expression that - * implements {@link AutoCloseable}. This method is used when desugaring try-with-resources - * statements during CFG construction. - * - * @param autoCloseableExpr the expression - * @return the member select tree - */ - public MemberSelectTree buildCloseMethodAccess(ExpressionTree autoCloseableExpr) { - DeclaredType exprType = - (DeclaredType) TypesUtils.upperBound(TreeUtils.typeOf(autoCloseableExpr)); - assert exprType != null - : "expression must be of declared type AutoCloseable: " + autoCloseableExpr; - - TypeElement exprElement = (TypeElement) exprType.asElement(); - - // Find the close() method - Symbol.MethodSymbol closeMethod = null; - - // We could use elements.getAllMembers(exprElement) to find the close method, but in rare - // cases calling that method crashes with a Symbol$CompletionFailure exception. See - // https://github.com/typetools/checker-framework/issues/6396. The code below directly - // searches all supertypes for the method and avoids the crash. - for (Type s : javacTypes.closure(((Symbol) exprElement).type)) { - for (Symbol m : s.tsym.members().getSymbolsByName(closeName)) { - if (!(m instanceof Symbol.MethodSymbol)) { - continue; + /** + * Builds an AST Tree to access the hasNext() method of an iterator. + * + * @param iteratorExpr an expression whose type is a subtype of Iterator + * @return a MemberSelectTree that accesses the hasNext() method of the expression + */ + public MemberSelectTree buildHasNextMethodAccess(ExpressionTree iteratorExpr) { + DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); + assert exprType != null : "expression must be of declared type Iterator<>"; + + TypeElement exprElement = (TypeElement) exprType.asElement(); + + // Find the hasNext() method of the iterator type + Symbol.MethodSymbol hasNextMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + if (method.getParameters().isEmpty() + && method.getSimpleName().contentEquals("hasNext")) { + hasNextMethod = (Symbol.MethodSymbol) method; + break; + } } - Symbol.MethodSymbol msym = (Symbol.MethodSymbol) m; - if (!msym.isStatic() && msym.getParameters().isEmpty()) { - closeMethod = msym; - break; + + if (hasNextMethod == null) { + throw new BugInCF("no hasNext method declared for " + exprElement); } - } + + JCTree.JCFieldAccess hasNextAccess = TreeUtils.Select(maker, iteratorExpr, hasNextMethod); + hasNextAccess.setType(hasNextMethod.asType()); + + return hasNextAccess; } - assert closeMethod != null - : "@AssumeAssertion(nullness): no close method declared for expression type"; + /** + * Builds an AST Tree to access the next() method of an iterator. + * + * @param iteratorExpr an expression whose type is a subtype of Iterator + * @return a MemberSelectTree that accesses the next() method of the expression + */ + public MemberSelectTree buildNextMethodAccess(ExpressionTree iteratorExpr) { + DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); + assert exprType != null : "expression must be of declared type Iterator<>"; + + TypeElement exprElement = (TypeElement) exprType.asElement(); + + // Find the next() method of the iterator type + Symbol.MethodSymbol nextMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("next")) { + nextMethod = (Symbol.MethodSymbol) method; + } + } - JCTree.JCFieldAccess closeAccess = TreeUtils.Select(maker, autoCloseableExpr, closeMethod); + assert nextMethod != null + : "@AssumeAssertion(nullness): no next method declared for expression type"; - return closeAccess; - } + Type.MethodType methodType = (Type.MethodType) nextMethod.asType(); + Symbol.TypeSymbol methodClass = methodType.asElement(); + Type elementType; - /** - * Builds an AST Tree to access the hasNext() method of an iterator. - * - * @param iteratorExpr an expression whose type is a subtype of Iterator - * @return a MemberSelectTree that accesses the hasNext() method of the expression - */ - public MemberSelectTree buildHasNextMethodAccess(ExpressionTree iteratorExpr) { - DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); - assert exprType != null : "expression must be of declared type Iterator<>"; + if (exprType.getTypeArguments().isEmpty()) { + elementType = symtab.objectType; + } else { + elementType = (Type) exprType.getTypeArguments().get(0); + } - TypeElement exprElement = (TypeElement) exprType.asElement(); + // Replace the next method's generic return type with + // the actual element type of the expression. + Type.MethodType updatedMethodType = + new Type.MethodType( + com.sun.tools.javac.util.List.nil(), + elementType, + com.sun.tools.javac.util.List.nil(), + methodClass); - // Find the hasNext() method of the iterator type - Symbol.MethodSymbol hasNextMethod = null; + JCTree.JCFieldAccess nextAccess = TreeUtils.Select(maker, iteratorExpr, nextMethod); + nextAccess.setType(updatedMethodType); - for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("hasNext")) { - hasNextMethod = (Symbol.MethodSymbol) method; - break; - } + return nextAccess; } - if (hasNextMethod == null) { - throw new BugInCF("no hasNext method declared for " + exprElement); + /** + * Builds an AST Tree to dereference the length field of an array. + * + * @param expression the array expression whose length is being accessed + * @return a MemberSelectTree to dereference the length of the array + */ + public MemberSelectTree buildArrayLengthAccess(ExpressionTree expression) { + return TreeUtils.Select(maker, expression, symtab.lengthVar); } - JCTree.JCFieldAccess hasNextAccess = TreeUtils.Select(maker, iteratorExpr, hasNextMethod); - hasNextAccess.setType(hasNextMethod.asType()); + /** + * Builds an AST Tree to call a method designated by the argument expression. + * + * @param methodExpr an expression denoting a method with no arguments + * @return a MethodInvocationTree to call the argument method + */ + public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr) { + return maker.App((JCTree.JCExpression) methodExpr); + } - return hasNextAccess; - } + /** + * Builds an AST Tree to call a method designated by methodExpr, with one argument designated by + * argExpr. + * + * @param methodExpr an expression denoting a method with one argument + * @param argExpr an expression denoting an argument to the method + * @return a MethodInvocationTree to call the argument method + */ + public MethodInvocationTree buildMethodInvocation( + ExpressionTree methodExpr, ExpressionTree argExpr) { + return maker.App( + (JCTree.JCExpression) methodExpr, + com.sun.tools.javac.util.List.of((JCTree.JCExpression) argExpr)); + } - /** - * Builds an AST Tree to access the next() method of an iterator. - * - * @param iteratorExpr an expression whose type is a subtype of Iterator - * @return a MemberSelectTree that accesses the next() method of the expression - */ - public MemberSelectTree buildNextMethodAccess(ExpressionTree iteratorExpr) { - DeclaredType exprType = (DeclaredType) TreeUtils.typeOf(iteratorExpr); - assert exprType != null : "expression must be of declared type Iterator<>"; + /** + * Builds an AST Tree to declare and initialize a variable, with no modifiers. + * + * @param type the type of the variable + * @param name the name of the variable + * @param owner the element containing the new symbol + * @param initializer the initializer expression + * @return a VariableDeclTree declaring the new variable + */ + public VariableTree buildVariableDecl( + TypeMirror type, String name, Element owner, ExpressionTree initializer) { + DetachedVarSymbol sym = + new DetachedVarSymbol(0, names.fromString(name), (Type) type, (Symbol) owner); + VariableTree tree = maker.VarDef(sym, (JCTree.JCExpression) initializer); + sym.setDeclaration(tree); + return tree; + } + + /** + * Builds an AST Tree to declare and initialize a variable. The type of the variable is + * specified by a Tree. + * + * @param type the type of the variable, as a Tree + * @param name the name of the variable + * @param owner the element containing the new symbol + * @param initializer the initializer expression + * @return a VariableDeclTree declaring the new variable + */ + public VariableTree buildVariableDecl( + Tree type, String name, Element owner, ExpressionTree initializer) { + Type typeMirror = (Type) TreeUtils.typeOf(type); + DetachedVarSymbol sym = + new DetachedVarSymbol(0, names.fromString(name), typeMirror, (Symbol) owner); + JCTree.JCModifiers mods = maker.Modifiers(0); + JCTree.JCVariableDecl decl = + maker.VarDef( + mods, + sym.name, + (JCTree.JCExpression) type, + (JCTree.JCExpression) initializer); + decl.setType(typeMirror); + decl.sym = sym; + sym.setDeclaration(decl); + return decl; + } + + /** + * Builds an AST Tree to refer to a variable. + * + * @param decl the declaration of the variable + * @return an IdentifierTree to refer to the variable + */ + public IdentifierTree buildVariableUse(VariableTree decl) { + return (IdentifierTree) maker.Ident((JCTree.JCVariableDecl) decl); + } + + /** + * Builds an AST Tree to cast the type of an expression. + * + * @param type the type to cast to + * @param expr the expression to be cast + * @return a cast of the expression to the type + */ + public TypeCastTree buildTypeCast(TypeMirror type, ExpressionTree expr) { + return maker.TypeCast((Type) type, (JCTree.JCExpression) expr); + } - TypeElement exprElement = (TypeElement) exprType.asElement(); + /** + * Builds an AST Tree to assign an expression to a variable. + * + * @param variable the declaration of the variable to assign to + * @param expr the expression to be assigned + * @return a statement assigning the expression to the variable + */ + public StatementTree buildAssignment(VariableTree variable, ExpressionTree expr) { + return maker.Assignment(TreeInfo.symbolFor((JCTree) variable), (JCTree.JCExpression) expr); + } - // Find the next() method of the iterator type - Symbol.MethodSymbol nextMethod = null; + /** + * Builds an AST Tree to assign an RHS expression to an LHS expression. + * + * @param lhs the expression to be assigned to + * @param rhs the expression to be assigned + * @return a statement assigning the expression to the variable + */ + public AssignmentTree buildAssignment(ExpressionTree lhs, ExpressionTree rhs) { + JCTree.JCAssign assign = maker.Assign((JCTree.JCExpression) lhs, (JCTree.JCExpression) rhs); + assign.setType((Type) TreeUtils.typeOf(lhs)); + return assign; + } + + /** Builds an AST Tree representing a literal value of primitive or String type. */ + public LiteralTree buildLiteral(Object value) { + return maker.Literal(value); + } + + /** + * Builds an AST Tree to compare two operands with less than. + * + * @param left the left operand tree + * @param right the right operand tree + * @return a Tree representing "left < right" + */ + public BinaryTree buildLessThan(ExpressionTree left, ExpressionTree right) { + JCTree.JCBinary binary = + maker.Binary( + JCTree.Tag.LT, (JCTree.JCExpression) left, (JCTree.JCExpression) right); + binary.setType((Type) modelTypes.getPrimitiveType(TypeKind.BOOLEAN)); + return binary; + } + + /** + * Builds an AST Tree to dereference an array. + * + * @param array the array to dereference + * @param index the index at which to dereference + * @return a Tree representing the dereference + */ + public ArrayAccessTree buildArrayAccess(ExpressionTree array, ExpressionTree index) { + ArrayType arrayType = (ArrayType) TreeUtils.typeOf(array); + JCTree.JCArrayAccess access = + maker.Indexed((JCTree.JCExpression) array, (JCTree.JCExpression) index); + access.setType((Type) arrayType.getComponentType()); + return access; + } - for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("next")) { - nextMethod = (Symbol.MethodSymbol) method; - } + /** + * Builds an AST Tree to refer to a class name. + * + * @param elt an element representing the class + * @return an IdentifierTree referring to the class + */ + public IdentifierTree buildClassUse(Element elt) { + return maker.Ident((Symbol) elt); } - assert nextMethod != null - : "@AssumeAssertion(nullness): no next method declared for expression type"; + /** + * Builds an AST Tree to access the valueOf() method of boxed type such as Short or Float. + * + * @param expr an expression whose type is a boxed type + * @return a MemberSelectTree that accesses the valueOf() method of the expression + */ + public MemberSelectTree buildValueOfMethodAccess(Tree expr) { + TypeMirror boxedType = TreeUtils.typeOf(expr); + + assert TypesUtils.isBoxedPrimitive(boxedType); + + // Find the valueOf(unboxedType) method of the boxed type + Symbol.MethodSymbol valueOfMethod = getValueOfMethod(env, boxedType); + + Type.MethodType methodType = (Type.MethodType) valueOfMethod.asType(); + + JCTree.JCFieldAccess valueOfAccess = TreeUtils.Select(maker, expr, valueOfMethod); + valueOfAccess.setType(methodType); + + return valueOfAccess; + } + + /** Returns the valueOf method of a boxed type such as Short or Float. */ + public static Symbol.MethodSymbol getValueOfMethod( + ProcessingEnvironment env, TypeMirror boxedType) { + Symbol.MethodSymbol valueOfMethod = null; + + TypeMirror unboxedType = env.getTypeUtils().unboxedType(boxedType); + TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); + for (ExecutableElement method : + ElementFilter.methodsIn(env.getElementUtils().getAllMembers(boxedElement))) { + if (method.getSimpleName().contentEquals("valueOf")) { + List params = method.getParameters(); + if (params.size() == 1 + && env.getTypeUtils().isSameType(params.get(0).asType(), unboxedType)) { + valueOfMethod = (Symbol.MethodSymbol) method; + } + } + } + + assert valueOfMethod != null + : "@AssumeAssertion(nullness): no valueOf method declared for boxed type"; + return valueOfMethod; + } + + /** + * Builds an AST Tree to access the *Value() method of a boxed type such as Short or Float, + * where * is the corresponding primitive type (i.e. shortValue or floatValue). + * + * @param expr an expression whose type is a boxed type + * @return a MemberSelectTree that accesses the *Value() method of the expression + */ + public MemberSelectTree buildPrimValueMethodAccess(Tree expr) { + TypeMirror boxedType = TreeUtils.typeOf(expr); + TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); + + assert TypesUtils.isBoxedPrimitive(boxedType); + TypeMirror unboxedType = modelTypes.unboxedType(boxedType); + + // Find the *Value() method of the boxed type + String primValueName = unboxedType.toString() + "Value"; + Symbol.MethodSymbol primValueMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(boxedElement))) { + if (method.getSimpleName().contentEquals(primValueName) + && method.getParameters().isEmpty()) { + primValueMethod = (Symbol.MethodSymbol) method; + } + } + + assert primValueMethod != null + : "@AssumeAssertion(nullness): no *Value method declared for boxed type"; + + Type.MethodType methodType = (Type.MethodType) primValueMethod.asType(); - Type.MethodType methodType = (Type.MethodType) nextMethod.asType(); - Symbol.TypeSymbol methodClass = methodType.asElement(); - Type elementType; + JCTree.JCFieldAccess primValueAccess = TreeUtils.Select(maker, expr, primValueMethod); + primValueAccess.setType(methodType); - if (exprType.getTypeArguments().isEmpty()) { - elementType = symtab.objectType; - } else { - elementType = (Type) exprType.getTypeArguments().get(0); + return primValueAccess; } - // Replace the next method's generic return type with - // the actual element type of the expression. - Type.MethodType updatedMethodType = - new Type.MethodType( - com.sun.tools.javac.util.List.nil(), - elementType, - com.sun.tools.javac.util.List.nil(), - methodClass); - - JCTree.JCFieldAccess nextAccess = TreeUtils.Select(maker, iteratorExpr, nextMethod); - nextAccess.setType(updatedMethodType); - - return nextAccess; - } - - /** - * Builds an AST Tree to dereference the length field of an array. - * - * @param expression the array expression whose length is being accessed - * @return a MemberSelectTree to dereference the length of the array - */ - public MemberSelectTree buildArrayLengthAccess(ExpressionTree expression) { - return TreeUtils.Select(maker, expression, symtab.lengthVar); - } - - /** - * Builds an AST Tree to call a method designated by the argument expression. - * - * @param methodExpr an expression denoting a method with no arguments - * @return a MethodInvocationTree to call the argument method - */ - public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr) { - return maker.App((JCTree.JCExpression) methodExpr); - } - - /** - * Builds an AST Tree to call a method designated by methodExpr, with one argument designated by - * argExpr. - * - * @param methodExpr an expression denoting a method with one argument - * @param argExpr an expression denoting an argument to the method - * @return a MethodInvocationTree to call the argument method - */ - public MethodInvocationTree buildMethodInvocation( - ExpressionTree methodExpr, ExpressionTree argExpr) { - return maker.App( - (JCTree.JCExpression) methodExpr, - com.sun.tools.javac.util.List.of((JCTree.JCExpression) argExpr)); - } - - /** - * Builds an AST Tree to declare and initialize a variable, with no modifiers. - * - * @param type the type of the variable - * @param name the name of the variable - * @param owner the element containing the new symbol - * @param initializer the initializer expression - * @return a VariableDeclTree declaring the new variable - */ - public VariableTree buildVariableDecl( - TypeMirror type, String name, Element owner, ExpressionTree initializer) { - DetachedVarSymbol sym = - new DetachedVarSymbol(0, names.fromString(name), (Type) type, (Symbol) owner); - VariableTree tree = maker.VarDef(sym, (JCTree.JCExpression) initializer); - sym.setDeclaration(tree); - return tree; - } - - /** - * Builds an AST Tree to declare and initialize a variable. The type of the variable is specified - * by a Tree. - * - * @param type the type of the variable, as a Tree - * @param name the name of the variable - * @param owner the element containing the new symbol - * @param initializer the initializer expression - * @return a VariableDeclTree declaring the new variable - */ - public VariableTree buildVariableDecl( - Tree type, String name, Element owner, ExpressionTree initializer) { - Type typeMirror = (Type) TreeUtils.typeOf(type); - DetachedVarSymbol sym = - new DetachedVarSymbol(0, names.fromString(name), typeMirror, (Symbol) owner); - JCTree.JCModifiers mods = maker.Modifiers(0); - JCTree.JCVariableDecl decl = - maker.VarDef(mods, sym.name, (JCTree.JCExpression) type, (JCTree.JCExpression) initializer); - decl.setType(typeMirror); - decl.sym = sym; - sym.setDeclaration(decl); - return decl; - } - - /** - * Builds an AST Tree to refer to a variable. - * - * @param decl the declaration of the variable - * @return an IdentifierTree to refer to the variable - */ - public IdentifierTree buildVariableUse(VariableTree decl) { - return (IdentifierTree) maker.Ident((JCTree.JCVariableDecl) decl); - } - - /** - * Builds an AST Tree to cast the type of an expression. - * - * @param type the type to cast to - * @param expr the expression to be cast - * @return a cast of the expression to the type - */ - public TypeCastTree buildTypeCast(TypeMirror type, ExpressionTree expr) { - return maker.TypeCast((Type) type, (JCTree.JCExpression) expr); - } - - /** - * Builds an AST Tree to assign an expression to a variable. - * - * @param variable the declaration of the variable to assign to - * @param expr the expression to be assigned - * @return a statement assigning the expression to the variable - */ - public StatementTree buildAssignment(VariableTree variable, ExpressionTree expr) { - return maker.Assignment(TreeInfo.symbolFor((JCTree) variable), (JCTree.JCExpression) expr); - } - - /** - * Builds an AST Tree to assign an RHS expression to an LHS expression. - * - * @param lhs the expression to be assigned to - * @param rhs the expression to be assigned - * @return a statement assigning the expression to the variable - */ - public AssignmentTree buildAssignment(ExpressionTree lhs, ExpressionTree rhs) { - JCTree.JCAssign assign = maker.Assign((JCTree.JCExpression) lhs, (JCTree.JCExpression) rhs); - assign.setType((Type) TreeUtils.typeOf(lhs)); - return assign; - } - - /** Builds an AST Tree representing a literal value of primitive or String type. */ - public LiteralTree buildLiteral(Object value) { - return maker.Literal(value); - } - - /** - * Builds an AST Tree to compare two operands with less than. - * - * @param left the left operand tree - * @param right the right operand tree - * @return a Tree representing "left < right" - */ - public BinaryTree buildLessThan(ExpressionTree left, ExpressionTree right) { - JCTree.JCBinary binary = - maker.Binary(JCTree.Tag.LT, (JCTree.JCExpression) left, (JCTree.JCExpression) right); - binary.setType((Type) modelTypes.getPrimitiveType(TypeKind.BOOLEAN)); - return binary; - } - - /** - * Builds an AST Tree to dereference an array. - * - * @param array the array to dereference - * @param index the index at which to dereference - * @return a Tree representing the dereference - */ - public ArrayAccessTree buildArrayAccess(ExpressionTree array, ExpressionTree index) { - ArrayType arrayType = (ArrayType) TreeUtils.typeOf(array); - JCTree.JCArrayAccess access = - maker.Indexed((JCTree.JCExpression) array, (JCTree.JCExpression) index); - access.setType((Type) arrayType.getComponentType()); - return access; - } - - /** - * Builds an AST Tree to refer to a class name. - * - * @param elt an element representing the class - * @return an IdentifierTree referring to the class - */ - public IdentifierTree buildClassUse(Element elt) { - return maker.Ident((Symbol) elt); - } - - /** - * Builds an AST Tree to access the valueOf() method of boxed type such as Short or Float. - * - * @param expr an expression whose type is a boxed type - * @return a MemberSelectTree that accesses the valueOf() method of the expression - */ - public MemberSelectTree buildValueOfMethodAccess(Tree expr) { - TypeMirror boxedType = TreeUtils.typeOf(expr); - - assert TypesUtils.isBoxedPrimitive(boxedType); - - // Find the valueOf(unboxedType) method of the boxed type - Symbol.MethodSymbol valueOfMethod = getValueOfMethod(env, boxedType); - - Type.MethodType methodType = (Type.MethodType) valueOfMethod.asType(); - - JCTree.JCFieldAccess valueOfAccess = TreeUtils.Select(maker, expr, valueOfMethod); - valueOfAccess.setType(methodType); - - return valueOfAccess; - } - - /** Returns the valueOf method of a boxed type such as Short or Float. */ - public static Symbol.MethodSymbol getValueOfMethod( - ProcessingEnvironment env, TypeMirror boxedType) { - Symbol.MethodSymbol valueOfMethod = null; - - TypeMirror unboxedType = env.getTypeUtils().unboxedType(boxedType); - TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); - for (ExecutableElement method : - ElementFilter.methodsIn(env.getElementUtils().getAllMembers(boxedElement))) { - if (method.getSimpleName().contentEquals("valueOf")) { - List params = method.getParameters(); - if (params.size() == 1 - && env.getTypeUtils().isSameType(params.get(0).asType(), unboxedType)) { - valueOfMethod = (Symbol.MethodSymbol) method; + /** Map public AST Tree.Kinds to internal javac JCTree.Tags. */ + public JCTree.Tag kindToTag(Tree.Kind kind) { + switch (kind) { + case AND: + return JCTree.Tag.BITAND; + case AND_ASSIGNMENT: + return JCTree.Tag.BITAND_ASG; + case ANNOTATION: + return JCTree.Tag.ANNOTATION; + case ANNOTATION_TYPE: + return JCTree.Tag.TYPE_ANNOTATION; + case ARRAY_ACCESS: + return JCTree.Tag.INDEXED; + case ARRAY_TYPE: + return JCTree.Tag.TYPEARRAY; + case ASSERT: + return JCTree.Tag.ASSERT; + case ASSIGNMENT: + return JCTree.Tag.ASSIGN; + case BITWISE_COMPLEMENT: + return JCTree.Tag.COMPL; + case BLOCK: + return JCTree.Tag.BLOCK; + case BREAK: + return JCTree.Tag.BREAK; + case CASE: + return JCTree.Tag.CASE; + case CATCH: + return JCTree.Tag.CATCH; + case CLASS: + return JCTree.Tag.CLASSDEF; + case CONDITIONAL_AND: + return JCTree.Tag.AND; + case CONDITIONAL_EXPRESSION: + return JCTree.Tag.CONDEXPR; + case CONDITIONAL_OR: + return JCTree.Tag.OR; + case CONTINUE: + return JCTree.Tag.CONTINUE; + case DIVIDE: + return JCTree.Tag.DIV; + case DIVIDE_ASSIGNMENT: + return JCTree.Tag.DIV_ASG; + case DO_WHILE_LOOP: + return JCTree.Tag.DOLOOP; + case ENHANCED_FOR_LOOP: + return JCTree.Tag.FOREACHLOOP; + case EQUAL_TO: + return JCTree.Tag.EQ; + case EXPRESSION_STATEMENT: + return JCTree.Tag.EXEC; + case FOR_LOOP: + return JCTree.Tag.FORLOOP; + case GREATER_THAN: + return JCTree.Tag.GT; + case GREATER_THAN_EQUAL: + return JCTree.Tag.GE; + case IDENTIFIER: + return JCTree.Tag.IDENT; + case IF: + return JCTree.Tag.IF; + case IMPORT: + return JCTree.Tag.IMPORT; + case INSTANCE_OF: + return JCTree.Tag.TYPETEST; + case LABELED_STATEMENT: + return JCTree.Tag.LABELLED; + case LEFT_SHIFT: + return JCTree.Tag.SL; + case LEFT_SHIFT_ASSIGNMENT: + return JCTree.Tag.SL_ASG; + case LESS_THAN: + return JCTree.Tag.LT; + case LESS_THAN_EQUAL: + return JCTree.Tag.LE; + case LOGICAL_COMPLEMENT: + return JCTree.Tag.NOT; + case MEMBER_SELECT: + return JCTree.Tag.SELECT; + case METHOD: + return JCTree.Tag.METHODDEF; + case METHOD_INVOCATION: + return JCTree.Tag.APPLY; + case MINUS: + return JCTree.Tag.MINUS; + case MINUS_ASSIGNMENT: + return JCTree.Tag.MINUS_ASG; + case MODIFIERS: + return JCTree.Tag.MODIFIERS; + case MULTIPLY: + return JCTree.Tag.MUL; + case MULTIPLY_ASSIGNMENT: + return JCTree.Tag.MUL_ASG; + case NEW_ARRAY: + return JCTree.Tag.NEWARRAY; + case NEW_CLASS: + return JCTree.Tag.NEWCLASS; + case NOT_EQUAL_TO: + return JCTree.Tag.NE; + case OR: + return JCTree.Tag.BITOR; + case OR_ASSIGNMENT: + return JCTree.Tag.BITOR_ASG; + case PARENTHESIZED: + return JCTree.Tag.PARENS; + case PLUS: + return JCTree.Tag.PLUS; + case PLUS_ASSIGNMENT: + return JCTree.Tag.PLUS_ASG; + case POSTFIX_DECREMENT: + return JCTree.Tag.POSTDEC; + case POSTFIX_INCREMENT: + return JCTree.Tag.POSTINC; + case PREFIX_DECREMENT: + return JCTree.Tag.PREDEC; + case PREFIX_INCREMENT: + return JCTree.Tag.PREINC; + case REMAINDER: + return JCTree.Tag.MOD; + case REMAINDER_ASSIGNMENT: + return JCTree.Tag.MOD_ASG; + case RETURN: + return JCTree.Tag.RETURN; + case RIGHT_SHIFT: + return JCTree.Tag.SR; + case RIGHT_SHIFT_ASSIGNMENT: + return JCTree.Tag.SR_ASG; + case SWITCH: + return JCTree.Tag.SWITCH; + case SYNCHRONIZED: + return JCTree.Tag.SYNCHRONIZED; + case THROW: + return JCTree.Tag.THROW; + case TRY: + return JCTree.Tag.TRY; + case TYPE_CAST: + return JCTree.Tag.TYPECAST; + case TYPE_PARAMETER: + return JCTree.Tag.TYPEPARAMETER; + case UNARY_MINUS: + return JCTree.Tag.NEG; + case UNARY_PLUS: + return JCTree.Tag.POS; + case UNION_TYPE: + return JCTree.Tag.TYPEUNION; + case UNSIGNED_RIGHT_SHIFT: + return JCTree.Tag.USR; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + return JCTree.Tag.USR_ASG; + case VARIABLE: + return JCTree.Tag.VARDEF; + case WHILE_LOOP: + return JCTree.Tag.WHILELOOP; + case XOR: + return JCTree.Tag.BITXOR; + case XOR_ASSIGNMENT: + return JCTree.Tag.BITXOR_ASG; + default: + return JCTree.Tag.NO_TAG; } - } } - assert valueOfMethod != null - : "@AssumeAssertion(nullness): no valueOf method declared for boxed type"; - return valueOfMethod; - } - - /** - * Builds an AST Tree to access the *Value() method of a boxed type such as Short or Float, where - * * is the corresponding primitive type (i.e. shortValue or floatValue). - * - * @param expr an expression whose type is a boxed type - * @return a MemberSelectTree that accesses the *Value() method of the expression - */ - public MemberSelectTree buildPrimValueMethodAccess(Tree expr) { - TypeMirror boxedType = TreeUtils.typeOf(expr); - TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); - - assert TypesUtils.isBoxedPrimitive(boxedType); - TypeMirror unboxedType = modelTypes.unboxedType(boxedType); - - // Find the *Value() method of the boxed type - String primValueName = unboxedType.toString() + "Value"; - Symbol.MethodSymbol primValueMethod = null; - - for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(boxedElement))) { - if (method.getSimpleName().contentEquals(primValueName) && method.getParameters().isEmpty()) { - primValueMethod = (Symbol.MethodSymbol) method; - } + /** + * Builds an AST Tree to perform a binary operation. + * + * @param type result type of the operation + * @param op an AST Tree operator + * @param left the left operand tree + * @param right the right operand tree + * @return a Tree representing "left < right" + */ + public BinaryTree buildBinary( + TypeMirror type, Tree.Kind op, ExpressionTree left, ExpressionTree right) { + JCTree.Tag jcOp = kindToTag(op); + JCTree.JCBinary binary = + maker.Binary(jcOp, (JCTree.JCExpression) left, (JCTree.JCExpression) right); + binary.setType((Type) type); + return binary; } - assert primValueMethod != null - : "@AssumeAssertion(nullness): no *Value method declared for boxed type"; - - Type.MethodType methodType = (Type.MethodType) primValueMethod.asType(); - - JCTree.JCFieldAccess primValueAccess = TreeUtils.Select(maker, expr, primValueMethod); - primValueAccess.setType(methodType); - - return primValueAccess; - } - - /** Map public AST Tree.Kinds to internal javac JCTree.Tags. */ - public JCTree.Tag kindToTag(Tree.Kind kind) { - switch (kind) { - case AND: - return JCTree.Tag.BITAND; - case AND_ASSIGNMENT: - return JCTree.Tag.BITAND_ASG; - case ANNOTATION: - return JCTree.Tag.ANNOTATION; - case ANNOTATION_TYPE: - return JCTree.Tag.TYPE_ANNOTATION; - case ARRAY_ACCESS: - return JCTree.Tag.INDEXED; - case ARRAY_TYPE: - return JCTree.Tag.TYPEARRAY; - case ASSERT: - return JCTree.Tag.ASSERT; - case ASSIGNMENT: - return JCTree.Tag.ASSIGN; - case BITWISE_COMPLEMENT: - return JCTree.Tag.COMPL; - case BLOCK: - return JCTree.Tag.BLOCK; - case BREAK: - return JCTree.Tag.BREAK; - case CASE: - return JCTree.Tag.CASE; - case CATCH: - return JCTree.Tag.CATCH; - case CLASS: - return JCTree.Tag.CLASSDEF; - case CONDITIONAL_AND: - return JCTree.Tag.AND; - case CONDITIONAL_EXPRESSION: - return JCTree.Tag.CONDEXPR; - case CONDITIONAL_OR: - return JCTree.Tag.OR; - case CONTINUE: - return JCTree.Tag.CONTINUE; - case DIVIDE: - return JCTree.Tag.DIV; - case DIVIDE_ASSIGNMENT: - return JCTree.Tag.DIV_ASG; - case DO_WHILE_LOOP: - return JCTree.Tag.DOLOOP; - case ENHANCED_FOR_LOOP: - return JCTree.Tag.FOREACHLOOP; - case EQUAL_TO: - return JCTree.Tag.EQ; - case EXPRESSION_STATEMENT: - return JCTree.Tag.EXEC; - case FOR_LOOP: - return JCTree.Tag.FORLOOP; - case GREATER_THAN: - return JCTree.Tag.GT; - case GREATER_THAN_EQUAL: - return JCTree.Tag.GE; - case IDENTIFIER: - return JCTree.Tag.IDENT; - case IF: - return JCTree.Tag.IF; - case IMPORT: - return JCTree.Tag.IMPORT; - case INSTANCE_OF: - return JCTree.Tag.TYPETEST; - case LABELED_STATEMENT: - return JCTree.Tag.LABELLED; - case LEFT_SHIFT: - return JCTree.Tag.SL; - case LEFT_SHIFT_ASSIGNMENT: - return JCTree.Tag.SL_ASG; - case LESS_THAN: - return JCTree.Tag.LT; - case LESS_THAN_EQUAL: - return JCTree.Tag.LE; - case LOGICAL_COMPLEMENT: - return JCTree.Tag.NOT; - case MEMBER_SELECT: - return JCTree.Tag.SELECT; - case METHOD: - return JCTree.Tag.METHODDEF; - case METHOD_INVOCATION: - return JCTree.Tag.APPLY; - case MINUS: - return JCTree.Tag.MINUS; - case MINUS_ASSIGNMENT: - return JCTree.Tag.MINUS_ASG; - case MODIFIERS: - return JCTree.Tag.MODIFIERS; - case MULTIPLY: - return JCTree.Tag.MUL; - case MULTIPLY_ASSIGNMENT: - return JCTree.Tag.MUL_ASG; - case NEW_ARRAY: - return JCTree.Tag.NEWARRAY; - case NEW_CLASS: - return JCTree.Tag.NEWCLASS; - case NOT_EQUAL_TO: - return JCTree.Tag.NE; - case OR: - return JCTree.Tag.BITOR; - case OR_ASSIGNMENT: - return JCTree.Tag.BITOR_ASG; - case PARENTHESIZED: - return JCTree.Tag.PARENS; - case PLUS: - return JCTree.Tag.PLUS; - case PLUS_ASSIGNMENT: - return JCTree.Tag.PLUS_ASG; - case POSTFIX_DECREMENT: - return JCTree.Tag.POSTDEC; - case POSTFIX_INCREMENT: - return JCTree.Tag.POSTINC; - case PREFIX_DECREMENT: - return JCTree.Tag.PREDEC; - case PREFIX_INCREMENT: - return JCTree.Tag.PREINC; - case REMAINDER: - return JCTree.Tag.MOD; - case REMAINDER_ASSIGNMENT: - return JCTree.Tag.MOD_ASG; - case RETURN: - return JCTree.Tag.RETURN; - case RIGHT_SHIFT: - return JCTree.Tag.SR; - case RIGHT_SHIFT_ASSIGNMENT: - return JCTree.Tag.SR_ASG; - case SWITCH: - return JCTree.Tag.SWITCH; - case SYNCHRONIZED: - return JCTree.Tag.SYNCHRONIZED; - case THROW: - return JCTree.Tag.THROW; - case TRY: - return JCTree.Tag.TRY; - case TYPE_CAST: - return JCTree.Tag.TYPECAST; - case TYPE_PARAMETER: - return JCTree.Tag.TYPEPARAMETER; - case UNARY_MINUS: - return JCTree.Tag.NEG; - case UNARY_PLUS: - return JCTree.Tag.POS; - case UNION_TYPE: - return JCTree.Tag.TYPEUNION; - case UNSIGNED_RIGHT_SHIFT: - return JCTree.Tag.USR; - case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: - return JCTree.Tag.USR_ASG; - case VARIABLE: - return JCTree.Tag.VARDEF; - case WHILE_LOOP: - return JCTree.Tag.WHILELOOP; - case XOR: - return JCTree.Tag.BITXOR; - case XOR_ASSIGNMENT: - return JCTree.Tag.BITXOR_ASG; - default: - return JCTree.Tag.NO_TAG; + /** + * Builds an AST Tree to create a new array with initializers. + * + * @param componentType component type of the new array + * @param elems expression trees of initializers + * @return a NewArrayTree to create a new array with initializers + */ + public NewArrayTree buildNewArray(TypeMirror componentType, List elems) { + @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. + List exprs = CollectionsPlume.mapList(JCExpression.class::cast, elems); + + JCTree.JCNewArray newArray = + maker.NewArray( + (JCTree.JCExpression) buildClassUse(((Type) componentType).tsym), + com.sun.tools.javac.util.List.nil(), + com.sun.tools.javac.util.List.from(exprs)); + newArray.setType(javacTypes.makeArrayType((Type) componentType)); + return newArray; } - } - - /** - * Builds an AST Tree to perform a binary operation. - * - * @param type result type of the operation - * @param op an AST Tree operator - * @param left the left operand tree - * @param right the right operand tree - * @return a Tree representing "left < right" - */ - public BinaryTree buildBinary( - TypeMirror type, Tree.Kind op, ExpressionTree left, ExpressionTree right) { - JCTree.Tag jcOp = kindToTag(op); - JCTree.JCBinary binary = - maker.Binary(jcOp, (JCTree.JCExpression) left, (JCTree.JCExpression) right); - binary.setType((Type) type); - return binary; - } - - /** - * Builds an AST Tree to create a new array with initializers. - * - * @param componentType component type of the new array - * @param elems expression trees of initializers - * @return a NewArrayTree to create a new array with initializers - */ - public NewArrayTree buildNewArray(TypeMirror componentType, List elems) { - @SuppressWarnings("nullness:type.arguments.not.inferred") // Poly + inference bug. - List exprs = CollectionsPlume.mapList(JCExpression.class::cast, elems); - - JCTree.JCNewArray newArray = - maker.NewArray( - (JCTree.JCExpression) buildClassUse(((Type) componentType).tsym), - com.sun.tools.javac.util.List.nil(), - com.sun.tools.javac.util.List.from(exprs)); - newArray.setType(javacTypes.makeArrayType((Type) componentType)); - return newArray; - } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java index d5bdb4d9dee..0f76852c86a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeParser.java @@ -8,11 +8,14 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Names; -import java.util.StringTokenizer; -import javax.annotation.processing.ProcessingEnvironment; + import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; +import java.util.StringTokenizer; + +import javax.annotation.processing.ProcessingEnvironment; + /** * A utility class for parsing Java expression snippets, and converting them to proper Javac AST * nodes. @@ -34,131 +37,131 @@ *

                  It's implemented via a Recursive-Descend parser. */ public class TreeParser { - /** Valid delimiters. */ - private static final String DELIMS = ".[](),"; - - /** A sentinel value. */ - private static final String SENTINEL = ""; - - /** The TreeMaker instance. */ - private final TreeMaker maker; - - /** The names instance. */ - private final Names names; - - /** Create a TreeParser. */ - public TreeParser(ProcessingEnvironment env) { - Context context = ((JavacProcessingEnvironment) env).getContext(); - maker = TreeMaker.instance(context); - names = Names.instance(context); - } - - /** - * Parses the snippet in the string as an internal Javac AST expression node. - * - * @param s the Java snippet - * @return the AST corresponding to the snippet - */ - public ExpressionTree parseTree(String s) { - StringTokenizer tokenizer = new StringTokenizer(s, DELIMS, true); - String token = tokenizer.nextToken(); - - try { - return parseExpression(tokenizer, token).first; - } catch (Exception e) { - throw new ParseError(e); - } finally { - tokenizer = null; - token = null; + /** Valid delimiters. */ + private static final String DELIMS = ".[](),"; + + /** A sentinel value. */ + private static final String SENTINEL = ""; + + /** The TreeMaker instance. */ + private final TreeMaker maker; + + /** The names instance. */ + private final Names names; + + /** Create a TreeParser. */ + public TreeParser(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + maker = TreeMaker.instance(context); + names = Names.instance(context); } - } - - /** The next token from the tokenizer, or the {@code SENTINEL} if none is available. */ - private String nextToken(StringTokenizer tokenizer) { - return tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINEL; - } - - /** The parsed expression tree for the given token. */ - private JCExpression fromToken(String token) { - // Optimization - if ("true".equals(token)) { - return maker.Literal(true); - } else if ("false".equals(token)) { - return maker.Literal(false); + + /** + * Parses the snippet in the string as an internal Javac AST expression node. + * + * @param s the Java snippet + * @return the AST corresponding to the snippet + */ + public ExpressionTree parseTree(String s) { + StringTokenizer tokenizer = new StringTokenizer(s, DELIMS, true); + String token = tokenizer.nextToken(); + + try { + return parseExpression(tokenizer, token).first; + } catch (Exception e) { + throw new ParseError(e); + } finally { + tokenizer = null; + token = null; + } } - if (Character.isLetter(token.charAt(0))) { - return maker.Ident(names.fromString(token)); + /** The next token from the tokenizer, or the {@code SENTINEL} if none is available. */ + private String nextToken(StringTokenizer tokenizer) { + return tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINEL; } - Object value; - try { - value = Integer.valueOf(token); - } catch (Exception e2) { - try { - value = Double.valueOf(token); - } catch (Exception ef) { - throw new Error("Can't parse as integer or double: " + token); - } + /** The parsed expression tree for the given token. */ + private JCExpression fromToken(String token) { + // Optimization + if ("true".equals(token)) { + return maker.Literal(true); + } else if ("false".equals(token)) { + return maker.Literal(false); + } + + if (Character.isLetter(token.charAt(0))) { + return maker.Ident(names.fromString(token)); + } + + Object value; + try { + value = Integer.valueOf(token); + } catch (Exception e2) { + try { + value = Double.valueOf(token); + } catch (Exception ef) { + throw new Error("Can't parse as integer or double: " + token); + } + } + return maker.Literal(value); } - return maker.Literal(value); - } - - /** - * Parse an expression. - * - * @param tokenizer the tokenizer - * @param token the first token - * @return a pair of a parsed expression and the next token - */ - private IPair parseExpression(StringTokenizer tokenizer, String token) { - JCExpression tree = fromToken(token); - - while (tokenizer.hasMoreTokens()) { - String delim = nextToken(tokenizer); - token = delim; - if (".".equals(delim)) { - token = nextToken(tokenizer); - tree = TreeUtils.Select(maker, tree, names.fromString(token)); - } else if ("(".equals(delim)) { - token = nextToken(tokenizer); - ListBuffer args = new ListBuffer<>(); - while (!")".equals(token)) { - IPair p = parseExpression(tokenizer, token); - JCExpression arg = p.first; - token = p.second; - args.append(arg); - if (",".equals(token)) { - token = nextToken(tokenizer); - } + + /** + * Parse an expression. + * + * @param tokenizer the tokenizer + * @param token the first token + * @return a pair of a parsed expression and the next token + */ + private IPair parseExpression(StringTokenizer tokenizer, String token) { + JCExpression tree = fromToken(token); + + while (tokenizer.hasMoreTokens()) { + String delim = nextToken(tokenizer); + token = delim; + if (".".equals(delim)) { + token = nextToken(tokenizer); + tree = TreeUtils.Select(maker, tree, names.fromString(token)); + } else if ("(".equals(delim)) { + token = nextToken(tokenizer); + ListBuffer args = new ListBuffer<>(); + while (!")".equals(token)) { + IPair p = parseExpression(tokenizer, token); + JCExpression arg = p.first; + token = p.second; + args.append(arg); + if (",".equals(token)) { + token = nextToken(tokenizer); + } + } + // For now, handle empty args only + assert ")".equals(token) : "Unexpected token: " + token; + tree = maker.Apply(List.nil(), tree, args.toList()); + } else if ("[".equals(token)) { + token = nextToken(tokenizer); + IPair p = parseExpression(tokenizer, token); + JCExpression index = p.first; + token = p.second; + assert "]".equals(token) : "Unexpected token: " + token; + tree = maker.Indexed(tree, index); + } else { + return IPair.of(tree, token); + } + assert tokenizer != null : "@AssumeAssertion(nullness): side effects"; } - // For now, handle empty args only - assert ")".equals(token) : "Unexpected token: " + token; - tree = maker.Apply(List.nil(), tree, args.toList()); - } else if ("[".equals(token)) { - token = nextToken(tokenizer); - IPair p = parseExpression(tokenizer, token); - JCExpression index = p.first; - token = p.second; - assert "]".equals(token) : "Unexpected token: " + token; - tree = maker.Indexed(tree, index); - } else { + return IPair.of(tree, token); - } - assert tokenizer != null : "@AssumeAssertion(nullness): side effects"; } - return IPair.of(tree, token); - } + /** An internal error. */ + private static class ParseError extends RuntimeException { + /** The serial version UID. */ + private static final long serialVersionUID = 1887754619522101929L; - /** An internal error. */ - private static class ParseError extends RuntimeException { - /** The serial version UID. */ - private static final long serialVersionUID = 1887754619522101929L; - - /** Create a ParseError. */ - ParseError(Throwable cause) { - super(cause); + /** Create a ParseError. */ + ParseError(Throwable cause) { + super(cause); + } } - } } From 5408cec9d63945c94d1247eac4c4ffe316d04ff0 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 30 Jan 2025 17:08:20 -0500 Subject: [PATCH 173/173] Restore formatting of .ajava files --- build.gradle | 2 +- ...ork.checker.nullness.NullnessChecker.ajava | 12 +- ...testchecker.ainfer.AinferTestChecker.ajava | 40 +- ...framework.checker.index.IndexChecker.ajava | 1647 ++++++++-------- ...ker.index.inequality.LessThanChecker.ajava | 1648 ++++++++-------- ...r.index.lowerbound.LowerBoundChecker.ajava | 1656 ++++++++-------- ...index.searchindex.SearchIndexChecker.ajava | 1648 ++++++++-------- ...substringindex.SubstringIndexChecker.ajava | 1648 ++++++++-------- ...rframework.common.value.ValueChecker.ajava | 1752 +++++++++-------- 9 files changed, 5058 insertions(+), 4995 deletions(-) diff --git a/build.gradle b/build.gradle index d51f81974ba..dab2f2e34bd 100644 --- a/build.gradle +++ b/build.gradle @@ -313,7 +313,7 @@ allprojects { currentProj -> target targets } else { // not the root project; format all .java files in the sub-project - target '**/*.java' + target '**/*.java', '**/*.ajava' } targetExclude doNotFormat diff --git a/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava b/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava index 55d54a405e7..11336ca0f7e 100644 --- a/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava +++ b/checker/tests/ainfer-nullness/input-annotation-files/TypeVarReturnAnnotated-org.checkerframework.checker.nullness.NullnessChecker.ajava @@ -2,12 +2,12 @@ // was only sometimes inferred. Based on an example from // https://github.com/dd482IT/cache2k-wpi/blob/0eaa156bdecd617b2aa4c745d0f8844a32609697/cache2k-api/src/main/java/org/cache2k/config/ToggleFeature.java#L73 @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.nullness.NullnessChecker") + "org.checkerframework.checker.nullness.NullnessChecker") public class TypeVarReturnAnnotated { - public static - @org.checkerframework.checker.initialization.qual.FBCBottom @org.checkerframework.checker.nullness.qual.Nullable T - extract() { - return null; - } + public static + @org.checkerframework.checker.initialization.qual.FBCBottom @org.checkerframework.checker.nullness.qual.Nullable T + extract() { + return null; + } } diff --git a/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava b/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava index 7f358123edd..995fe6b92e1 100644 --- a/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava +++ b/checker/tests/ainfer-testchecker/input-annotation-files/ExistingPurityAnnotations-org.checkerframework.checker.testchecker.ainfer.AinferTestChecker.ajava @@ -6,31 +6,31 @@ import org.checkerframework.checker.testchecker.ainfer.qual.AinferSibling1; import org.checkerframework.framework.qual.EnsuresQualifierIf; @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.testchecker.ainfer.AinferTestChecker") + "org.checkerframework.checker.testchecker.ainfer.AinferTestChecker") public class ExistingPurityAnnotations { - Object obj; + Object obj; - @org.checkerframework.dataflow.qual.Pure - public Object pureMethod(Object object) { - return null; - } + @org.checkerframework.dataflow.qual.Pure + public Object pureMethod(Object object) { + return null; + } - @SuppressWarnings("ainfertest") - @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) - public boolean checkAinferSibling1(Object obj1) { - return true; - } + @SuppressWarnings("ainfertest") + @EnsuresQualifierIf(expression = "#1", result = true, qualifier = AinferSibling1.class) + public boolean checkAinferSibling1(Object obj1) { + return true; + } - public @AinferSibling1 Object usePureMethod() { + public @AinferSibling1 Object usePureMethod() { - if (checkAinferSibling1(obj)) { - // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should - // unrefine the type of obj, and an error will be issued when - // we try to return obj on the next line. - pureMethod(obj); - return obj; + if (checkAinferSibling1(obj)) { + // If pureMethod doesn't have (and can't infer) an @Pure annotation, this call should + // unrefine the type of obj, and an error will be issued when + // we try to return obj on the next line. + pureMethod(obj); + return obj; + } + return null; } - return null; - } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava index 34b0a97db61..5930e52e1f7 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.IndexChecker.ajava @@ -34,6 +34,9 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.plumelib.options.Options; + import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -51,7 +54,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -62,878 +64,885 @@ import org.plumelib.options.Options; @org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.index.IndexChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no problems - * should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

                  Trivial getters and setters are of the form: - * - *

                  {@code
                  -   * SomeType getFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * SomeType foo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * void setFoo(SomeType foo) {
                  -   *   this.foo = foo;
                  -   * }
                  -   *
                  -   * boolean hasFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean isFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean notFoo() {
                  -   *   return !foo;
                  -   * }
                  -   * }
                  - */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if the - * command-line arguments were absolute pathnames, or no command-line arguments were supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); - } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } - } + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no + * problems should be reported. + */ + public Pattern dont_require = null; - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + /** If true, don't check elements with private access. */ + public boolean dont_require_private; - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; - public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); + /** + * If true, don't check trivial getters and setters. + * + *

                  Trivial getters and setters are of the form: + * + *

                  {@code
                  +     * SomeType getFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * SomeType foo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * void setFoo(SomeType foo) {
                  +     *   this.foo = foo;
                  +     * }
                  +     *
                  +     * boolean hasFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean isFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean notFoo() {
                  +     *   return !foo;
                  +     * }
                  +     * }
                  + */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if + * the command-line arguments were absolute pathnames, or no command-line arguments were + * supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } + } + for (String error : rj.errors) { + System.out.println(error); } - } - return FileVisitResult.CONTINUE; + System.exit(rj.errors.isEmpty() ? 0 : 1); } - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) - public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - } - - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { + + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} + + public FileVisitResult visitFile( + JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); + } + } + return FileVisitResult.CONTINUE; + } + + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) + public FileVisitResult postVisitDirectory( + JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.UpperBoundBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; /** - * Create a new PropertyKind. + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; } /** - * Returns true if this is a getter. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @return true if this is a getter + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; } /** - * Return the PropertyKind for the given method. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @param md the method to check - * @return the PropertyKind for the given method + * @param path a Java file or directory + * @return true if the file or directory should be skipped */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } - } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
                    - *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or - * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} - * or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and - * has a body of the form {@code this.foo = foo}. - *
                  - * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); - } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private @org.checkerframework.checker.index.qual.UpperBoundBottom boolean isTrivialGetterOrSetter( - MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); - } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

                  Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } - } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; - } - - /** - * Returns true if the body of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } - } - - /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. - * - * @param md a method declaration - * @return its sole statement, or null - */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); - } + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; + + /** + * Create a new PropertyKind. + * + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type + */ + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; + } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { + /** + * Returns true if this is a getter. + * + * @return true if this is a getter + */ + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; + } - /** The file being visited. Used for constructing error messages. */ - private Path filename; + /** + * Return the PropertyKind for the given method. + * + * @param md the method to check + * @return the PropertyKind for the given method + */ + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } /** - * Create a new RequireJavadocVisitor. + * Return true if this method declaration is a trivial getter or setter. + * + *

                    + *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, + * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return + * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, + * and has a body of the form {@code this.foo = foo}. + *
                  * - * @param filename the file being visited; used for diagnostic messages + * @param md the method to check + * @return true if this method is a trivial getter or setter */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** - * Return a string stating that documentation is missing on the given construct. + * Return true if this method declaration is a trivial getter or setter of the given kind. * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } + private @org.checkerframework.checker.index.qual.UpperBoundBottom boolean + isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); } - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

                  Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } } - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; } - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + /** The file being visited. Used for constructing error messages. */ + private Path filename; - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + /** + * Create a new RequireJavadocVisitor. + * + * @param filename the file being visited; used for diagnostic messages + */ + public RequireJavadocVisitor(Path filename) { + this.filename = filename; + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + /** + * Return a string stating that documentation is missing on the given construct. + * + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct + */ + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", + path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } + } + + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf( + "skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } + + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } + + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } + + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } + + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); + } + + public void visit( + RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); + } + + /** + * Return true if this method is annotated with {@code @Override}. + * + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} + */ + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } } /** - * Return true if this method is annotated with {@code @Override}. + * Return true if this node has a Javadoc comment. * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } - } - - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; - } - - /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

                  {@code
                  -   * /** ... *}{@code /
                  -   * // text 1
                  -   * // text 2
                  -   * void m() { ... }
                  -   * }
                  - * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. - */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
                  {@code
                  +     * /** ... *}{@code /
                  +     * // text 1
                  +     * // text 2
                  +     * void m() { ... }
                  +     * }
                  + * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); + } } - } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava index 592af8c9719..8d2c6ce13e7 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.inequality.LessThanChecker.ajava @@ -34,6 +34,9 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.plumelib.options.Options; + import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -51,7 +54,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -60,880 +62,888 @@ import org.plumelib.options.Options; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.inequality.LessThanChecker") + "org.checkerframework.checker.index.inequality.LessThanChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no problems - * should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

                  Trivial getters and setters are of the form: - * - *

                  {@code
                  -   * SomeType getFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * SomeType foo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * void setFoo(SomeType foo) {
                  -   *   this.foo = foo;
                  -   * }
                  -   *
                  -   * boolean hasFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean isFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean notFoo() {
                  -   *   return !foo;
                  -   * }
                  -   * }
                  - */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if the - * command-line arguments were absolute pathnames, or no command-line arguments were supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); - } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } - } + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no + * problems should be reported. + */ + public Pattern dont_require = null; - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + /** If true, don't check elements with private access. */ + public boolean dont_require_private; - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; - public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); + /** + * If true, don't check trivial getters and setters. + * + *

                  Trivial getters and setters are of the form: + * + *

                  {@code
                  +     * SomeType getFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * SomeType foo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * void setFoo(SomeType foo) {
                  +     *   this.foo = foo;
                  +     * }
                  +     *
                  +     * boolean hasFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean isFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean notFoo() {
                  +     *   return !foo;
                  +     * }
                  +     * }
                  + */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if + * the command-line arguments were absolute pathnames, or no command-line arguments were + * supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } + } + for (String error : rj.errors) { + System.out.println(error); } - } - return FileVisitResult.CONTINUE; + System.exit(rj.errors.isEmpty() ? 0 : 1); } - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) - public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - } - - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { + + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} + + public FileVisitResult visitFile( + JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); + } + } + return FileVisitResult.CONTINUE; + } + + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) + public FileVisitResult postVisitDirectory( + JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LessThanBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; /** - * Create a new PropertyKind. + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; } /** - * Returns true if this is a getter. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @return true if this is a getter + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; } /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor method. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @param md the method to check - * @return the PropertyKind for the given method, or null + * @param path a Java file or directory + * @return true if the file or directory should be skipped */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } - } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
                    - *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or - * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} - * or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and - * has a body of the form {@code this.foo = foo}. - *
                  - * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); - } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); - } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

                  Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } - } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; - } - - /** - * Returns true if the body of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } - } - - /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. - * - * @param md a method declaration - * @return its sole statement, or null - */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); - } + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; + + /** + * Create a new PropertyKind. + * + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type + */ + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; + } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { + /** + * Returns true if this is a getter. + * + * @return true if this is a getter + */ + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; + } - /** The file being visited. Used for constructing error messages. */ - private Path filename; + /** + * Return the PropertyKind for the given method, or null if it isn't a property accessor + * method. + * + * @param md the method to check + * @return the PropertyKind for the given method, or null + */ + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } /** - * Create a new RequireJavadocVisitor. + * Return true if this method declaration is a trivial getter or setter. + * + *

                    + *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, + * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return + * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, + * and has a body of the form {@code this.foo = foo}. + *
                  * - * @param filename the file being visited; used for diagnostic messages + * @param md the method to check + * @return true if this method is a trivial getter or setter */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** - * Return a string stating that documentation is missing on the given construct. + * Return true if this method declaration is a trivial getter or setter of the given kind. * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); } - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

                  Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } } - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; } - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + /** The file being visited. Used for constructing error messages. */ + private Path filename; - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + /** + * Create a new RequireJavadocVisitor. + * + * @param filename the file being visited; used for diagnostic messages + */ + public RequireJavadocVisitor(Path filename) { + this.filename = filename; + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + /** + * Return a string stating that documentation is missing on the given construct. + * + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct + */ + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", + path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } + } + + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf( + "skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } + + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } + + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } + + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } + + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); + } + + public void visit( + RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); + } + + /** + * Return true if this method is annotated with {@code @Override}. + * + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} + */ + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } } /** - * Return true if this method is annotated with {@code @Override}. + * Return true if this node has a Javadoc comment. * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } - } - - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; - } - - /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

                  {@code
                  -   * /** ... *}{@code /
                  -   * // text 1
                  -   * // text 2
                  -   * void m() { ... }
                  -   * }
                  - * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. - */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
                  {@code
                  +     * /** ... *}{@code /
                  +     * // text 1
                  +     * // text 2
                  +     * void m() { ... }
                  +     * }
                  + * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); + } } - } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava index fd72c691ea9..0e224203a8a 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.lowerbound.LowerBoundChecker.ajava @@ -34,6 +34,9 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.plumelib.options.Options; + import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -51,7 +54,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -60,884 +62,892 @@ import org.plumelib.options.Options; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.lowerbound.LowerBoundChecker") + "org.checkerframework.checker.index.lowerbound.LowerBoundChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no problems - * should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

                  Trivial getters and setters are of the form: - * - *

                  {@code
                  -   * SomeType getFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * SomeType foo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * void setFoo(SomeType foo) {
                  -   *   this.foo = foo;
                  -   * }
                  -   *
                  -   * boolean hasFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean isFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean notFoo() {
                  -   *   return !foo;
                  -   * }
                  -   * }
                  - */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if the - * command-line arguments were absolute pathnames, or no command-line arguments were supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); - } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } - } + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no + * problems should be reported. + */ + public Pattern dont_require = null; - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + /** If true, don't check elements with private access. */ + public boolean dont_require_private; - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; - public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); + /** + * If true, don't check trivial getters and setters. + * + *

                  Trivial getters and setters are of the form: + * + *

                  {@code
                  +     * SomeType getFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * SomeType foo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * void setFoo(SomeType foo) {
                  +     *   this.foo = foo;
                  +     * }
                  +     *
                  +     * boolean hasFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean isFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean notFoo() {
                  +     *   return !foo;
                  +     * }
                  +     * }
                  + */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if + * the command-line arguments were absolute pathnames, or no command-line arguments were + * supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } + } + for (String error : rj.errors) { + System.out.println(error); } - } - return FileVisitResult.CONTINUE; + System.exit(rj.errors.isEmpty() ? 0 : 1); } - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) - public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - } - - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { + + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} + + public FileVisitResult visitFile( + JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); + } + } + return FileVisitResult.CONTINUE; + } + + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) + public FileVisitResult postVisitDirectory( + JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.LowerBoundBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams; - - /** The return type. */ - final ReturnType returnType; /** - * Create a new PropertyKind. + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element */ - PropertyKind( - String prefix, - @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams, - ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; } /** - * Returns true if this is a getter. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @return true if this is a getter + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; } /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor method. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @param md the method to check - * @return the PropertyKind for the given method, or null + * @param path a Java file or directory + * @return true if the file or directory should be skipped */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } - } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
                    - *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or - * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} - * or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and - * has a body of the form {@code this.foo = foo}. - *
                  - * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); - } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private @org.checkerframework.checker.index.qual.LowerBoundBottom boolean isTrivialGetterOrSetter( - MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); - } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

                  Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } - } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; - } - - /** - * Returns true if the body of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } - } - - /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. - * - * @param md a method declaration - * @return its sole statement, or null - */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); - } + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams; + + /** The return type. */ + final ReturnType returnType; + + /** + * Create a new PropertyKind. + * + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type + */ + PropertyKind( + String prefix, + @org.checkerframework.checker.index.qual.GTENegativeOne int requiredParams, + ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; + } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { + /** + * Returns true if this is a getter. + * + * @return true if this is a getter + */ + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; + } - /** The file being visited. Used for constructing error messages. */ - private Path filename; + /** + * Return the PropertyKind for the given method, or null if it isn't a property accessor + * method. + * + * @param md the method to check + * @return the PropertyKind for the given method, or null + */ + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } /** - * Create a new RequireJavadocVisitor. + * Return true if this method declaration is a trivial getter or setter. + * + *

                    + *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, + * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return + * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, + * and has a body of the form {@code this.foo = foo}. + *
                  * - * @param filename the file being visited; used for diagnostic messages + * @param md the method to check + * @return true if this method is a trivial getter or setter */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** - * Return a string stating that documentation is missing on the given construct. + * Return true if this method declaration is a trivial getter or setter of the given kind. * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } + private @org.checkerframework.checker.index.qual.LowerBoundBottom boolean + isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); } - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

                  Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } } - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; } - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + /** The file being visited. Used for constructing error messages. */ + private Path filename; - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + /** + * Create a new RequireJavadocVisitor. + * + * @param filename the file being visited; used for diagnostic messages + */ + public RequireJavadocVisitor(Path filename) { + this.filename = filename; + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + /** + * Return a string stating that documentation is missing on the given construct. + * + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct + */ + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", + path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } + } + + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf( + "skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } + + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } + + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } + + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } + + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); + } + + public void visit( + RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); + } + + /** + * Return true if this method is annotated with {@code @Override}. + * + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} + */ + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } } /** - * Return true if this method is annotated with {@code @Override}. + * Return true if this node has a Javadoc comment. * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } - } - - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; - } - - /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

                  {@code
                  -   * /** ... *}{@code /
                  -   * // text 1
                  -   * // text 2
                  -   * void m() { ... }
                  -   * }
                  - * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. - */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
                  {@code
                  +     * /** ... *}{@code /
                  +     * // text 1
                  +     * // text 2
                  +     * void m() { ... }
                  +     * }
                  + * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); + } } - } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava index 89ce44fd341..020c5fdb273 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.searchindex.SearchIndexChecker.ajava @@ -34,6 +34,9 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.plumelib.options.Options; + import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -51,7 +54,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -60,880 +62,888 @@ import org.plumelib.options.Options; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.searchindex.SearchIndexChecker") + "org.checkerframework.checker.index.searchindex.SearchIndexChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no problems - * should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

                  Trivial getters and setters are of the form: - * - *

                  {@code
                  -   * SomeType getFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * SomeType foo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * void setFoo(SomeType foo) {
                  -   *   this.foo = foo;
                  -   * }
                  -   *
                  -   * boolean hasFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean isFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean notFoo() {
                  -   *   return !foo;
                  -   * }
                  -   * }
                  - */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if the - * command-line arguments were absolute pathnames, or no command-line arguments were supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); - } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } - } + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no + * problems should be reported. + */ + public Pattern dont_require = null; - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + /** If true, don't check elements with private access. */ + public boolean dont_require_private; - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; - public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); + /** + * If true, don't check trivial getters and setters. + * + *

                  Trivial getters and setters are of the form: + * + *

                  {@code
                  +     * SomeType getFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * SomeType foo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * void setFoo(SomeType foo) {
                  +     *   this.foo = foo;
                  +     * }
                  +     *
                  +     * boolean hasFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean isFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean notFoo() {
                  +     *   return !foo;
                  +     * }
                  +     * }
                  + */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if + * the command-line arguments were absolute pathnames, or no command-line arguments were + * supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } + } + for (String error : rj.errors) { + System.out.println(error); } - } - return FileVisitResult.CONTINUE; + System.exit(rj.errors.isEmpty() ? 0 : 1); } - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) - public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - } - - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { + + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} + + public FileVisitResult visitFile( + JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); + } + } + return FileVisitResult.CONTINUE; + } + + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) + public FileVisitResult postVisitDirectory( + JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SearchIndexBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; /** - * Create a new PropertyKind. + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; } /** - * Returns true if this is a getter. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @return true if this is a getter + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; } /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor method. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @param md the method to check - * @return the PropertyKind for the given method, or null + * @param path a Java file or directory + * @return true if the file or directory should be skipped */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } - } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
                    - *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or - * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} - * or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and - * has a body of the form {@code this.foo = foo}. - *
                  - * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); - } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); - } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

                  Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } - } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; - } - - /** - * Returns true if the body of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } - } - - /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. - * - * @param md a method declaration - * @return its sole statement, or null - */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); - } + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; + + /** + * Create a new PropertyKind. + * + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type + */ + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; + } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { + /** + * Returns true if this is a getter. + * + * @return true if this is a getter + */ + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; + } - /** The file being visited. Used for constructing error messages. */ - private Path filename; + /** + * Return the PropertyKind for the given method, or null if it isn't a property accessor + * method. + * + * @param md the method to check + * @return the PropertyKind for the given method, or null + */ + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } /** - * Create a new RequireJavadocVisitor. + * Return true if this method declaration is a trivial getter or setter. + * + *

                    + *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, + * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return + * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, + * and has a body of the form {@code this.foo = foo}. + *
                  * - * @param filename the file being visited; used for diagnostic messages + * @param md the method to check + * @return true if this method is a trivial getter or setter */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** - * Return a string stating that documentation is missing on the given construct. + * Return true if this method declaration is a trivial getter or setter of the given kind. * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); } - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

                  Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } } - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; } - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + /** The file being visited. Used for constructing error messages. */ + private Path filename; - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + /** + * Create a new RequireJavadocVisitor. + * + * @param filename the file being visited; used for diagnostic messages + */ + public RequireJavadocVisitor(Path filename) { + this.filename = filename; + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + /** + * Return a string stating that documentation is missing on the given construct. + * + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct + */ + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", + path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } + } + + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf( + "skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } + + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } + + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } + + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } + + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); + } + + public void visit( + RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); + } + + /** + * Return true if this method is annotated with {@code @Override}. + * + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} + */ + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } } /** - * Return true if this method is annotated with {@code @Override}. + * Return true if this node has a Javadoc comment. * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } - } - - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; - } - - /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

                  {@code
                  -   * /** ... *}{@code /
                  -   * // text 1
                  -   * // text 2
                  -   * void m() { ... }
                  -   * }
                  - * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. - */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
                  {@code
                  +     * /** ... *}{@code /
                  +     * // text 1
                  +     * // text 2
                  +     * void m() { ... }
                  +     * }
                  + * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); + } } - } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava index b583c6d100b..5dece81dc7c 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.checker.index.substringindex.SubstringIndexChecker.ajava @@ -34,6 +34,9 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.plumelib.options.Options; + import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -51,7 +54,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -60,880 +62,888 @@ import org.plumelib.options.Options; * href="https://github.com/plume-lib/require-javadoc#readme">https://github.com/plume-lib/require-javadoc#readme. */ @org.checkerframework.framework.qual.AnnotatedFor( - "org.checkerframework.checker.index.substringindex.SubstringIndexChecker") + "org.checkerframework.checker.index.substringindex.SubstringIndexChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no problems - * should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

                  Trivial getters and setters are of the form: - * - *

                  {@code
                  -   * SomeType getFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * SomeType foo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * void setFoo(SomeType foo) {
                  -   *   this.foo = foo;
                  -   * }
                  -   *
                  -   * boolean hasFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean isFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean notFoo() {
                  -   *   return !foo;
                  -   * }
                  -   * }
                  - */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if the - * command-line arguments were absolute pathnames, or no command-line arguments were supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); - } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } - } + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; + + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no + * problems should be reported. + */ + public Pattern dont_require = null; - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + /** If true, don't check elements with private access. */ + public boolean dont_require_private; - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; - public FileVisitResult visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); + /** + * If true, don't check trivial getters and setters. + * + *

                  Trivial getters and setters are of the form: + * + *

                  {@code
                  +     * SomeType getFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * SomeType foo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * void setFoo(SomeType foo) {
                  +     *   this.foo = foo;
                  +     * }
                  +     *
                  +     * boolean hasFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean isFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean notFoo() {
                  +     *   return !foo;
                  +     * }
                  +     * }
                  + */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if + * the command-line arguments were absolute pathnames, or no command-line arguments were + * supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } + } + for (String error : rj.errors) { + System.out.println(error); } - } - return FileVisitResult.CONTINUE; + System.exit(rj.errors.isEmpty() ? 0 : 1); } - public FileVisitResult preVisitDirectory( - JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) - public FileVisitResult postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) - public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - } - - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { + + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} + + public FileVisitResult visitFile( + JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); + } + } + return FileVisitResult.CONTINUE; + } + + public FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) + public FileVisitResult postVisitDirectory( + JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.checker.index.qual.SubstringIndexBottom.class) + public FileVisitResult visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final int requiredParams; - - /** The return type. */ - final ReturnType returnType; /** - * Create a new PropertyKind. + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element */ - PropertyKind(String prefix, int requiredParams, ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; } /** - * Returns true if this is a getter. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @return true if this is a getter + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; } /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor method. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @param md the method to check - * @return the PropertyKind for the given method, or null + * @param path a Java file or directory + * @return true if the file or directory should be skipped */ - static PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } - } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
                    - *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or - * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} - * or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and - * has a body of the form {@code this.foo = foo}. - *
                  - * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); - } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); - } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

                  Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } - } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectSignature( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; - } - - /** - * Returns true if the body of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private boolean hasCorrectBody( - MethodDeclaration md, PropertyKind propertyKind, String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } - } - - /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. - * - * @param md a method declaration - * @return its sole statement, or null - */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; - } - return statements.get(0); - } + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final int requiredParams; + + /** The return type. */ + final ReturnType returnType; + + /** + * Create a new PropertyKind. + * + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type + */ + PropertyKind(String prefix, int requiredParams, ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; + } - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { + /** + * Returns true if this is a getter. + * + * @return true if this is a getter + */ + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; + } - /** The file being visited. Used for constructing error messages. */ - private Path filename; + /** + * Return the PropertyKind for the given method, or null if it isn't a property accessor + * method. + * + * @param md the method to check + * @return the PropertyKind for the given method, or null + */ + static PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } + } /** - * Create a new RequireJavadocVisitor. + * Return true if this method declaration is a trivial getter or setter. + * + *

                    + *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, + * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return + * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, + * and has a body of the form {@code this.foo = foo}. + *
                  * - * @param filename the file being visited; used for diagnostic messages + * @param md the method to check + * @return true if this method is a trivial getter or setter */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** - * Return a string stating that documentation is missing on the given construct. + * Return true if this method declaration is a trivial getter or setter of the given kind. * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } + private boolean isTrivialGetterOrSetter(MethodDeclaration md, PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); } - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

                  Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private String propertyName(MethodDeclaration md, PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } } - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectSignature( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; } - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private boolean hasCorrectBody( + MethodDeclaration md, PropertyKind propertyKind, String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + /** The file being visited. Used for constructing error messages. */ + private Path filename; - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + /** + * Create a new RequireJavadocVisitor. + * + * @param filename the file being visited; used for diagnostic messages + */ + public RequireJavadocVisitor(Path filename) { + this.filename = filename; + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + /** + * Return a string stating that documentation is missing on the given construct. + * + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct + */ + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", + path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } + } + + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } - public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf( + "skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } + + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } + + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } + + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } + + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); + } + + public void visit( + RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); + } + + /** + * Return true if this method is annotated with {@code @Override}. + * + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} + */ + private boolean isOverride(MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } } /** - * Return true if this method is annotated with {@code @Override}. + * Return true if this node has a Javadoc comment. * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment */ - private boolean isOverride(MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } - } - - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private boolean hasJavadocComment(Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; - } - - /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

                  {@code
                  -   * /** ... *}{@code /
                  -   * // text 1
                  -   * // text 2
                  -   * void m() { ... }
                  -   * }
                  - * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. - */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + private boolean hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
                  {@code
                  +     * /** ... *}{@code /
                  +     * // text 1
                  +     * // text 2
                  +     * void m() { ... }
                  +     * }
                  + * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); + } } - } } diff --git a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava index 83889095b0b..847d60dbb5e 100644 --- a/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava +++ b/checker/tests/index-initializedfields/input-annotation-files/RequireJavadoc-org.checkerframework.common.value.ValueChecker.ajava @@ -34,6 +34,9 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import org.plumelib.options.Options; + import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -51,7 +54,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; -import org.plumelib.options.Options; /** * A program that issues an error for any class, constructor, method, or field that lacks a Javadoc @@ -62,933 +64,945 @@ import org.plumelib.options.Options; @org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.common.value.ValueChecker") public class RequireJavadoc { - /** Matches name of file or directory where no problems should be reported. */ - public Pattern exclude = null; - - // TODO: It would be nice to support matching fully-qualified class names, but matching - // packages will have to do for now. - /** - * Matches simple name of class/constructor/method/field, or full package name, where no problems - * should be reported. - */ - public Pattern dont_require = null; - - /** If true, don't check elements with private access. */ - public boolean dont_require_private; - - /** - * If true, don't check constructors with zero formal parameters. These are sometimes called - * "default constructors", though that term means a no-argument constructor that the compiler - * synthesized when the programmer didn't write one. - */ - public boolean dont_require_noarg_constructor; - - /** - * If true, don't check trivial getters and setters. - * - *

                  Trivial getters and setters are of the form: - * - *

                  {@code
                  -   * SomeType getFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * SomeType foo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * void setFoo(SomeType foo) {
                  -   *   this.foo = foo;
                  -   * }
                  -   *
                  -   * boolean hasFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean isFoo() {
                  -   *   return foo;
                  -   * }
                  -   *
                  -   * boolean notFoo() {
                  -   *   return !foo;
                  -   * }
                  -   * }
                  - */ - public boolean dont_require_trivial_properties; - - /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ - public boolean dont_require_type; - - /** If true, don't check fields. */ - public boolean dont_require_field; - - /** If true, don't check methods, constructors, and annotation members. */ - public boolean dont_require_method; - - /** If true, warn if any package lacks a package-info.java file. */ - public boolean require_package_info; - - /** - * If true, print filenames relative to working directory. Setting this only has an effect if the - * command-line arguments were absolute pathnames, or no command-line arguments were supplied. - */ - public boolean relative = false; - - /** If true, output debug information. */ - public boolean verbose = false; - - /** All the errors this program will report. */ - private List errors = new ArrayList<>(); - - /** The Java files to be checked. */ - private List javaFiles = new ArrayList(); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirRelative = Paths.get(""); - - /** The current working directory, for making relative pathnames. */ - private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); - - /** - * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. - * - * @param args the command-line arguments; see the README file - */ - public static void main(String[] args) { - RequireJavadoc rj = new RequireJavadoc(); - Options options = - new Options( - "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", rj); - String[] remainingArgs = options.parse(true, args); - rj.setJavaFiles(remainingArgs); - for (Path javaFile : rj.javaFiles) { - if (rj.verbose) { - System.out.println("Checking " + javaFile); - } - try { - CompilationUnit cu = StaticJavaParser.parse(javaFile); - RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); - visitor.visit(cu, null); - } catch (IOException e) { - System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); - System.exit(2); - } catch (ParseProblemException e) { - System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); - System.exit(2); - } - } - for (String error : rj.errors) { - System.out.println(error); - } - System.exit(rj.errors.isEmpty() ? 0 : 1); - } - - /** Creates a new RequireJavadoc instance. */ - @org.checkerframework.dataflow.qual.SideEffectFree - private RequireJavadoc() {} - - /** - * Set the Java files to be processed from the command-line arguments. - * - * @param args the directories and files listed on the command line - */ - private void setJavaFiles(String[] args) { - if (args.length == 0) { - args = new String[] {workingDirAbsolute.toString()}; - } - FileVisitor walker = new JavaFilesVisitor(); - for (String arg : args) { - if (shouldExclude(arg)) { - continue; - } - Path p = Paths.get(arg); - File f = p.toFile(); - if (!f.exists()) { - System.out.println("File not found: " + f); - System.exit(2); - } - if (f.isDirectory()) { - try { - Files.walkFileTree(p, walker); - } catch (IOException e) { - System.out.println("Problem while reading " + f + ": " + e.getMessage()); - System.exit(2); - } - } else { - javaFiles.add(Paths.get(arg)); - } - } - javaFiles.sort(Comparator.comparing(Object::toString)); - Set missingPackageInfoFiles = new LinkedHashSet<>(); - if (require_package_info) { - for (Path javaFile : javaFiles) { - Path javaFileParent = javaFile.getParent(); - // Java 11 has Path.of() instead of creating a new File. - Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); - if (!javaFiles.contains(packageInfo)) { - missingPackageInfoFiles.add(packageInfo); - } - } - for (Path packageInfo : missingPackageInfoFiles) { - errors.add("missing package documentation: no file " + packageInfo); - } - } - } + /** Matches name of file or directory where no problems should be reported. */ + public Pattern exclude = null; - /** Collects files into the {@link #javaFiles} variable. */ - private class JavaFilesVisitor extends SimpleFileVisitor { + // TODO: It would be nice to support matching fully-qualified class names, but matching + // packages will have to do for now. + /** + * Matches simple name of class/constructor/method/field, or full package name, where no + * problems should be reported. + */ + public Pattern dont_require = null; - /** Create a new JavaFilesVisitor. */ - public JavaFilesVisitor() {} + /** If true, don't check elements with private access. */ + public boolean dont_require_private; - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult - visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { - if (attr.isRegularFile() && file.toString().endsWith(".java")) { - if (!shouldExclude(file)) { - javaFiles.add(file); + /** + * If true, don't check constructors with zero formal parameters. These are sometimes called + * "default constructors", though that term means a no-argument constructor that the compiler + * synthesized when the programmer didn't write one. + */ + public boolean dont_require_noarg_constructor; + + /** + * If true, don't check trivial getters and setters. + * + *

                  Trivial getters and setters are of the form: + * + *

                  {@code
                  +     * SomeType getFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * SomeType foo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * void setFoo(SomeType foo) {
                  +     *   this.foo = foo;
                  +     * }
                  +     *
                  +     * boolean hasFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean isFoo() {
                  +     *   return foo;
                  +     * }
                  +     *
                  +     * boolean notFoo() {
                  +     *   return !foo;
                  +     * }
                  +     * }
                  + */ + public boolean dont_require_trivial_properties; + + /** If true, don't check type declarations: classes, interfaces, enums, annotations. */ + public boolean dont_require_type; + + /** If true, don't check fields. */ + public boolean dont_require_field; + + /** If true, don't check methods, constructors, and annotation members. */ + public boolean dont_require_method; + + /** If true, warn if any package lacks a package-info.java file. */ + public boolean require_package_info; + + /** + * If true, print filenames relative to working directory. Setting this only has an effect if + * the command-line arguments were absolute pathnames, or no command-line arguments were + * supplied. + */ + public boolean relative = false; + + /** If true, output debug information. */ + public boolean verbose = false; + + /** All the errors this program will report. */ + private List errors = new ArrayList<>(); + + /** The Java files to be checked. */ + private List javaFiles = new ArrayList(); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirRelative = Paths.get(""); + + /** The current working directory, for making relative pathnames. */ + private Path workingDirAbsolute = Paths.get("").toAbsolutePath(); + + /** + * The main entry point for the require-javadoc program. See documentation at https://github.com/plume-lib/require-javadoc#readme. + * + * @param args the command-line arguments; see the README file + */ + public static void main(String[] args) { + RequireJavadoc rj = new RequireJavadoc(); + Options options = + new Options( + "java org.plumelib.javadoc.RequireJavadoc [options] [directory-or-file ...]", + rj); + String[] remainingArgs = options.parse(true, args); + rj.setJavaFiles(remainingArgs); + for (Path javaFile : rj.javaFiles) { + if (rj.verbose) { + System.out.println("Checking " + javaFile); + } + try { + CompilationUnit cu = StaticJavaParser.parse(javaFile); + RequireJavadocVisitor visitor = rj.new RequireJavadocVisitor(javaFile); + visitor.visit(cu, null); + } catch (IOException e) { + System.out.println("Problem while reading " + javaFile + ": " + e.getMessage()); + System.exit(2); + } catch (ParseProblemException e) { + System.out.println("Problem while parsing " + javaFile + ": " + e.getMessage()); + System.exit(2); + } + } + for (String error : rj.errors) { + System.out.println(error); } - } - return FileVisitResult.CONTINUE; + System.exit(rj.errors.isEmpty() ? 0 : 1); } - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE", "SKIP_SUBTREE"}) FileVisitResult preVisitDirectory(JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { - if (shouldExclude(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } + /** Creates a new RequireJavadoc instance. */ + @org.checkerframework.dataflow.qual.SideEffectFree + private RequireJavadoc() {} - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.common.value.qual.BottomVal.class) - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult - postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; + /** + * Set the Java files to be processed from the command-line arguments. + * + * @param args the directories and files listed on the command line + */ + private void setJavaFiles(String[] args) { + if (args.length == 0) { + args = new String[] {workingDirAbsolute.toString()}; + } + FileVisitor walker = new JavaFilesVisitor(); + for (String arg : args) { + if (shouldExclude(arg)) { + continue; + } + Path p = Paths.get(arg); + File f = p.toFile(); + if (!f.exists()) { + System.out.println("File not found: " + f); + System.exit(2); + } + if (f.isDirectory()) { + try { + Files.walkFileTree(p, walker); + } catch (IOException e) { + System.out.println("Problem while reading " + f + ": " + e.getMessage()); + System.exit(2); + } + } else { + javaFiles.add(Paths.get(arg)); + } + } + javaFiles.sort(Comparator.comparing(Object::toString)); + Set missingPackageInfoFiles = new LinkedHashSet<>(); + if (require_package_info) { + for (Path javaFile : javaFiles) { + Path javaFileParent = javaFile.getParent(); + // Java 11 has Path.of() instead of creating a new File. + Path packageInfo = javaFileParent.resolve(new File("package-info.java").toPath()); + if (!javaFiles.contains(packageInfo)) { + missingPackageInfoFiles.add(packageInfo); + } + } + for (Path packageInfo : missingPackageInfoFiles) { + errors.add("missing package documentation: no file " + packageInfo); + } + } } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = {"#2"}, - qualifier = org.checkerframework.common.value.qual.BottomVal.class) - public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult - visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { - if (exc != null) { - System.out.println("Problem visiting " + file + ": " + exc.getMessage()); - System.exit(2); - } - return FileVisitResult.CONTINUE; - } - } - - /** - * Return true if the given Java element should not be checked, based on the {@code - * --dont-require} command-line argument. - * - * @param name the name of a Java element. It is a simple name, except for packages. - * @return true if no warnings should be issued about the element - */ - private boolean shouldNotRequire(String name) { - if (dont_require == null) { - return false; - } - boolean result = dont_require.matcher(name).find(); - if (verbose) { - System.out.printf("shouldNotRequire(%s) => %s%n", name, result); - } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param fileName the name of a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(String fileName) { - if (exclude == null) { - return false; - } - boolean result = exclude.matcher(fileName).find(); - if (verbose) { - System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + /** Collects files into the {@link #javaFiles} variable. */ + private class JavaFilesVisitor extends SimpleFileVisitor { + + /** Create a new JavaFilesVisitor. */ + public JavaFilesVisitor() {} + + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult + visitFile(JavaFilesVisitor this, Path file, BasicFileAttributes attr) { + if (attr.isRegularFile() && file.toString().endsWith(".java")) { + if (!shouldExclude(file)) { + javaFiles.add(file); + } + } + return FileVisitResult.CONTINUE; + } + + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE", "SKIP_SUBTREE"}) FileVisitResult preVisitDirectory( + JavaFilesVisitor this, Path dir, BasicFileAttributes attr) { + if (shouldExclude(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.common.value.qual.BottomVal.class) + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult + postVisitDirectory(JavaFilesVisitor this, Path dir, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + dir + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#2"}, + qualifier = org.checkerframework.common.value.qual.BottomVal.class) + public @org.checkerframework.common.value.qual.StringVal({"CONTINUE"}) FileVisitResult + visitFileFailed(JavaFilesVisitor this, Path file, IOException exc) { + if (exc != null) { + System.out.println("Problem visiting " + file + ": " + exc.getMessage()); + System.exit(2); + } + return FileVisitResult.CONTINUE; + } } - return result; - } - - /** - * Return true if the given file or directory should be skipped, based on the {@code --exclude} - * command-line argument. - * - * @param path a Java file or directory - * @return true if the file or directory should be skipped - */ - private boolean shouldExclude(Path path) { - return shouldExclude(path.toString()); - } - - /** A property method's return type. */ - private enum ReturnType { - - /** The return type is void. */ - VOID, - /** The return type is boolean. */ - BOOLEAN, - /** The return type is non-void. */ - NON_VOID - } - - /** The type of property method: a getter or setter. */ - private enum PropertyKind { - - /** A method of the form {@code SomeType getFoo()}. */ - GETTER("get", 0, ReturnType.NON_VOID), - /** A method of the form {@code SomeType foo()}. */ - GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), - /** A method of the form {@code boolean hasFoo()}. */ - GETTER_HAS("has", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean isFoo()}. */ - GETTER_IS("is", 0, ReturnType.BOOLEAN), - /** A method of the form {@code boolean notFoo()}. */ - GETTER_NOT("not", 0, ReturnType.BOOLEAN), - /** A method of the form {@code void setFoo(SomeType arg)}. */ - SETTER("set", 1, ReturnType.VOID), - /** Not a getter or setter. */ - NOT_PROPERTY("", -1, ReturnType.VOID); - - /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ - final @org.checkerframework.common.value.qual.StringVal({"set", "not", "is", "has", "", "get"}) String prefix; - - /** The number of required formal parameters: 0 or 1. */ - final @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams; - - /** The return type. */ - final @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType; /** - * Create a new PropertyKind. + * Return true if the given Java element should not be checked, based on the {@code + * --dont-require} command-line argument. * - * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" - * @param requiredParams the number of required formal parameters: 0 or 1 - * @param returnType the return type + * @param name the name of a Java element. It is a simple name, except for packages. + * @return true if no warnings should be issued about the element */ - PropertyKind( - @org.checkerframework.common.value.qual.StringVal({"set", "not", "is", "has", "", "get"}) String prefix, - @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams, - @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType) { - this.prefix = prefix; - this.requiredParams = requiredParams; - this.returnType = returnType; + private boolean shouldNotRequire(String name) { + if (dont_require == null) { + return false; + } + boolean result = dont_require.matcher(name).find(); + if (verbose) { + System.out.printf("shouldNotRequire(%s) => %s%n", name, result); + } + return result; } /** - * Returns true if this is a getter. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @return true if this is a getter + * @param fileName the name of a Java file or directory + * @return true if the file or directory should be skipped */ - @org.checkerframework.dataflow.qual.Pure - boolean isGetter() { - return this != SETTER; + private boolean shouldExclude(String fileName) { + if (exclude == null) { + return false; + } + boolean result = exclude.matcher(fileName).find(); + if (verbose) { + System.out.printf("shouldExclude(%s) => %s%n", fileName, result); + } + return result; } /** - * Return the PropertyKind for the given method, or null if it isn't a property accessor method. + * Return true if the given file or directory should be skipped, based on the {@code --exclude} + * command-line argument. * - * @param md the method to check - * @return the PropertyKind for the given method, or null + * @param path a Java file or directory + * @return true if the file or directory should be skipped */ - static @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) PropertyKind fromMethodDeclaration(MethodDeclaration md) { - String methodName = md.getNameAsString(); - if (methodName.startsWith("get")) { - return GETTER; - } else if (methodName.startsWith("has")) { - return GETTER_HAS; - } else if (methodName.startsWith("is")) { - return GETTER_IS; - } else if (methodName.startsWith("not")) { - return GETTER_NOT; - } else if (methodName.startsWith("set")) { - return SETTER; - } else { - return GETTER_NO_PREFIX; - } - } - } - - /** - * Return true if this method declaration is a trivial getter or setter. - * - *
                    - *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, or - * {@code notFoo}, has no formal parameters, and has a body of the form {@code return foo} - * or {@code return this.foo} (except for {@code notFoo}, in which case the body is - * negated). - *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, and - * has a body of the form {@code this.foo = foo}. - *
                  - * - * @param md the method to check - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter(MethodDeclaration md) { - PropertyKind kind = PropertyKind.fromMethodDeclaration(md); - if (kind != PropertyKind.GETTER_NO_PREFIX) { - if (isTrivialGetterOrSetter(md, kind)) { - return true; - } - } - return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); - } - - /** - * Return true if this method declaration is a trivial getter or setter of the given kind. - * - * @see #isTrivialGetterOrSetter(MethodDeclaration) - * @param md the method to check - * @param propertyKind the kind of property - * @return true if this method is a trivial getter or setter - */ - private boolean isTrivialGetterOrSetter( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) - PropertyKind propertyKind) { - String propertyName = propertyName(md, propertyKind); - return propertyName != null - && hasCorrectSignature(md, propertyKind, propertyName) - && hasCorrectBody(md, propertyKind, propertyName); - } - - /** - * Returns the name of the property, if the method is a getter or setter of the given kind. - * Otherwise returns null. - * - *

                  Examines the method's name, but not its signature or body. Also does not check that the - * given property name corresponds to an existing field. - * - * @param md the method to test - * @param propertyKind the type of property method - * @return the name of the property, or null - */ - private @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String - propertyName( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) - PropertyKind propertyKind) { - String methodName = md.getNameAsString(); - assert methodName.startsWith(propertyKind.prefix); - String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); - if (upperCamelCaseProperty.length() == 0) { - return null; - } - if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { - return upperCamelCaseProperty; - } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { - return null; - } else { - return "" - + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) - + upperCamelCaseProperty.substring(1); - } - } - - /** - * Returns true if the signature of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean - hasCorrectSignature( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ - "GETTER_NO_PREFIX", - "SETTER", - "GETTER_NOT", - "GETTER_IS", - "GETTER_HAS", - "GETTER" - }) - PropertyKind propertyKind, - @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { - NodeList parameters = md.getParameters(); - if (parameters.size() != propertyKind.requiredParams) { - return false; - } - if (parameters.size() == 1) { - Parameter parameter = parameters.get(0); - if (!parameter.getNameAsString().equals(propertyName)) { - return false; - } - } - // Check presence/absence of return type. (The Java compiler will verify - // that the type is consistent with the method body.) - Type returnType = md.getType(); - switch (propertyKind.returnType) { - case VOID: - if (!returnType.isVoidType()) { - return false; - } - break; - case BOOLEAN: - if (!returnType.equals(PrimitiveType.booleanType())) { - return false; - } - break; - case NON_VOID: - if (returnType.isVoidType()) { - return false; - } - break; - default: - throw new Error("Unexpected enum value " + propertyKind.returnType); - } - return true; - } - - /** - * Returns true if the body of the given method is a property accessor of the given kind. - * - * @param md the method - * @param propertyKind the kind of property - * @param propertyName the name of the property - * @return true if the body of the given method is a property accessor - */ - private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean hasCorrectBody( - MethodDeclaration md, - @org.checkerframework.common.value.qual.StringVal({ + private boolean shouldExclude(Path path) { + return shouldExclude(path.toString()); + } + + /** A property method's return type. */ + private enum ReturnType { + + /** The return type is void. */ + VOID, + /** The return type is boolean. */ + BOOLEAN, + /** The return type is non-void. */ + NON_VOID + } + + /** The type of property method: a getter or setter. */ + private enum PropertyKind { + + /** A method of the form {@code SomeType getFoo()}. */ + GETTER("get", 0, ReturnType.NON_VOID), + /** A method of the form {@code SomeType foo()}. */ + GETTER_NO_PREFIX("", 0, ReturnType.NON_VOID), + /** A method of the form {@code boolean hasFoo()}. */ + GETTER_HAS("has", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean isFoo()}. */ + GETTER_IS("is", 0, ReturnType.BOOLEAN), + /** A method of the form {@code boolean notFoo()}. */ + GETTER_NOT("not", 0, ReturnType.BOOLEAN), + /** A method of the form {@code void setFoo(SomeType arg)}. */ + SETTER("set", 1, ReturnType.VOID), + /** Not a getter or setter. */ + NOT_PROPERTY("", -1, ReturnType.VOID); + + /** The prefix for the method name: "get", "", "has", "is", "not", or "set". */ + final @org.checkerframework.common.value.qual.StringVal({ + "set", "not", "is", "has", "", "get" + }) String prefix; + + /** The number of required formal parameters: 0 or 1. */ + final @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams; + + /** The return type. */ + final @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType; + + /** + * Create a new PropertyKind. + * + * @param prefix the prefix for the method name: "get", "has", "is", "not", or "set" + * @param requiredParams the number of required formal parameters: 0 or 1 + * @param returnType the return type + */ + PropertyKind( + @org.checkerframework.common.value.qual.StringVal({ + "set", "not", "is", "has", "", "get" + }) + String prefix, + @org.checkerframework.common.value.qual.IntVal({-1, 1, 0}) int requiredParams, + @org.checkerframework.common.value.qual.StringVal({"VOID", "BOOLEAN", "NON_VOID"}) ReturnType returnType) { + this.prefix = prefix; + this.requiredParams = requiredParams; + this.returnType = returnType; + } + + /** + * Returns true if this is a getter. + * + * @return true if this is a getter + */ + @org.checkerframework.dataflow.qual.Pure + boolean isGetter() { + return this != SETTER; + } + + /** + * Return the PropertyKind for the given method, or null if it isn't a property accessor + * method. + * + * @param md the method to check + * @return the PropertyKind for the given method, or null + */ + static @org.checkerframework.common.value.qual.StringVal({ "GETTER_NO_PREFIX", "SETTER", "GETTER_NOT", "GETTER_IS", "GETTER_HAS", "GETTER" - }) - PropertyKind propertyKind, - @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { - Statement statement = getOnlyStatement(md); - if (propertyKind.isGetter()) { - if (!(statement instanceof ReturnStmt)) { - return false; - } - Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); - if (!oReturnExpr.isPresent()) { - return false; - } - Expression returnExpr = oReturnExpr.get(); - // Does not handle parentheses. - if (propertyKind == PropertyKind.GETTER_NOT) { - if (!(returnExpr instanceof UnaryExpr)) { - return false; - } - UnaryExpr unary = (UnaryExpr) returnExpr; - if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - return false; - } - returnExpr = unary.getExpression(); - } - String returnName; - // Does not handle parentheses. - if (returnExpr instanceof NameExpr) { - returnName = ((NameExpr) returnExpr).getNameAsString(); - } else if (returnExpr instanceof FieldAccessExpr) { - FieldAccessExpr fa = (FieldAccessExpr) returnExpr; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - returnName = fa.getNameAsString(); - } else { - return false; - } - if (!returnName.equals(propertyName)) { - return false; - } - return true; - } else if (propertyKind == PropertyKind.SETTER) { - if (!(statement instanceof ExpressionStmt)) { - return false; - } - Expression expr = ((ExpressionStmt) statement).getExpression(); - if (!(expr instanceof AssignExpr)) { - return false; - } - AssignExpr assignExpr = (AssignExpr) expr; - Expression target = assignExpr.getTarget(); - AssignExpr.Operator op = assignExpr.getOperator(); - Expression value = assignExpr.getValue(); - if (!(target instanceof FieldAccessExpr)) { - return false; - } - FieldAccessExpr fa = (FieldAccessExpr) target; - Expression receiver = fa.getScope(); - if (!(receiver instanceof ThisExpr)) { - return false; - } - if (!fa.getNameAsString().equals(propertyName)) { - return false; - } - if (op != AssignExpr.Operator.ASSIGN) { - return false; - } - if (!(value instanceof NameExpr - && ((NameExpr) value).getNameAsString().equals(propertyName))) { - return false; - } - return true; - } else { - throw new Error("unexpected PropertyKind " + propertyKind); - } - } - - /** - * If the body contains exactly one statement, returns it. Otherwise, returns null. - * - * @param md a method declaration - * @return its sole statement, or null - */ - private Statement getOnlyStatement(MethodDeclaration md) { - Optional body = md.getBody(); - if (!body.isPresent()) { - return null; - } - NodeList statements = body.get().getStatements(); - if (statements.size() != 1) { - return null; + }) PropertyKind fromMethodDeclaration(MethodDeclaration md) { + String methodName = md.getNameAsString(); + if (methodName.startsWith("get")) { + return GETTER; + } else if (methodName.startsWith("has")) { + return GETTER_HAS; + } else if (methodName.startsWith("is")) { + return GETTER_IS; + } else if (methodName.startsWith("not")) { + return GETTER_NOT; + } else if (methodName.startsWith("set")) { + return SETTER; + } else { + return GETTER_NO_PREFIX; + } + } } - return statements.get(0); - } - - /** Visits an AST and collects warnings about missing Javadoc. */ - private class RequireJavadocVisitor extends VoidVisitorAdapter { - - /** The file being visited. Used for constructing error messages. */ - private Path filename; /** - * Create a new RequireJavadocVisitor. + * Return true if this method declaration is a trivial getter or setter. * - * @param filename the file being visited; used for diagnostic messages + *

                    + *
                  • A trivial getter is named {@code getFoo}, {@code foo}, {@code hasFoo}, {@code isFoo}, + * or {@code notFoo}, has no formal parameters, and has a body of the form {@code return + * foo} or {@code return this.foo} (except for {@code notFoo}, in which case the body is + * negated). + *
                  • A trivial setter is named {@code setFoo}, has one formal parameter named {@code foo}, + * and has a body of the form {@code this.foo = foo}. + *
                  + * + * @param md the method to check + * @return true if this method is a trivial getter or setter */ - public RequireJavadocVisitor(Path filename) { - this.filename = filename; + private boolean isTrivialGetterOrSetter(MethodDeclaration md) { + PropertyKind kind = PropertyKind.fromMethodDeclaration(md); + if (kind != PropertyKind.GETTER_NO_PREFIX) { + if (isTrivialGetterOrSetter(md, kind)) { + return true; + } + } + return isTrivialGetterOrSetter(md, PropertyKind.GETTER_NO_PREFIX); } /** - * Return a string stating that documentation is missing on the given construct. + * Return true if this method declaration is a trivial getter or setter of the given kind. * - * @param node a Java language construct (class, constructor, method, field, etc.) - * @param simpleName the construct's simple name, used in diagnostic messages - * @return an error message for the given construct + * @see #isTrivialGetterOrSetter(MethodDeclaration) + * @param md the method to check + * @param propertyKind the kind of property + * @return true if this method is a trivial getter or setter */ - private String errorString(Node node, String simpleName) { - Optional range = node.getRange(); - if (range.isPresent()) { - Position begin = range.get().begin; - Path path = - (relative - ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) - .relativize(filename) - : filename); - return String.format( - "%s:%d:%d: missing documentation for %s", path, begin.line, begin.column, simpleName); - } else { - return "missing documentation for " + simpleName; - } + private boolean isTrivialGetterOrSetter( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) + PropertyKind propertyKind) { + String propertyName = propertyName(md, propertyKind); + return propertyName != null + && hasCorrectSignature(md, propertyKind, propertyName) + && hasCorrectBody(md, propertyKind, propertyName); } - public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { - Optional opd = cu.getPackageDeclaration(); - if (opd.isPresent()) { - String packageName = opd.get().getName().asString(); - if (shouldNotRequire(packageName)) { - return; - } - Optional oTypeName = cu.getPrimaryTypeName(); - if (oTypeName.isPresent() - && oTypeName.get().equals("package-info") - && !hasJavadocComment(opd.get()) - && !hasJavadocComment(cu)) { - errors.add(errorString(opd.get(), packageName)); - } - } - if (verbose) { - System.out.printf("Visiting compilation unit%n"); - } - super.visit(cu, ignore); + /** + * Returns the name of the property, if the method is a getter or setter of the given kind. + * Otherwise returns null. + * + *

                  Examines the method's name, but not its signature or body. Also does not check that the + * given property name corresponds to an existing field. + * + * @param md the method to test + * @param propertyKind the type of property method + * @return the name of the property, or null + */ + private @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String + propertyName( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) + PropertyKind propertyKind) { + String methodName = md.getNameAsString(); + assert methodName.startsWith(propertyKind.prefix); + String upperCamelCaseProperty = methodName.substring(propertyKind.prefix.length()); + if (upperCamelCaseProperty.length() == 0) { + return null; + } + if (propertyKind == PropertyKind.GETTER_NO_PREFIX) { + return upperCamelCaseProperty; + } else if (!Character.isUpperCase(upperCamelCaseProperty.charAt(0))) { + return null; + } else { + return "" + + Character.toLowerCase(upperCamelCaseProperty.charAt(0)) + + upperCamelCaseProperty.substring(1); + } } - public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting type %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the signature of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean + hasCorrectSignature( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) + PropertyKind propertyKind, + @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { + NodeList parameters = md.getParameters(); + if (parameters.size() != propertyKind.requiredParams) { + return false; + } + if (parameters.size() == 1) { + Parameter parameter = parameters.get(0); + if (!parameter.getNameAsString().equals(propertyName)) { + return false; + } + } + // Check presence/absence of return type. (The Java compiler will verify + // that the type is consistent with the method body.) + Type returnType = md.getType(); + switch (propertyKind.returnType) { + case VOID: + if (!returnType.isVoidType()) { + return false; + } + break; + case BOOLEAN: + if (!returnType.equals(PrimitiveType.booleanType())) { + return false; + } + break; + case NON_VOID: + if (returnType.isVoidType()) { + return false; + } + break; + default: + throw new Error("Unexpected enum value " + propertyKind.returnType); + } + return true; } - public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { - if (dont_require_private && cd.isPrivate()) { - return; - } - if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { - return; - } - String name = cd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting constructor %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(cd)) { - errors.add(errorString(cd, name)); - } - super.visit(cd, ignore); + /** + * Returns true if the body of the given method is a property accessor of the given kind. + * + * @param md the method + * @param propertyKind the kind of property + * @param propertyName the name of the property + * @return true if the body of the given method is a property accessor + */ + private @org.checkerframework.common.value.qual.BoolVal({true, false}) boolean hasCorrectBody( + MethodDeclaration md, + @org.checkerframework.common.value.qual.StringVal({ + "GETTER_NO_PREFIX", + "SETTER", + "GETTER_NOT", + "GETTER_IS", + "GETTER_HAS", + "GETTER" + }) + PropertyKind propertyKind, + @org.checkerframework.common.value.qual.ArrayLenRange(from = 1, to = 2147483647) String propertyName) { + Statement statement = getOnlyStatement(md); + if (propertyKind.isGetter()) { + if (!(statement instanceof ReturnStmt)) { + return false; + } + Optional oReturnExpr = ((ReturnStmt) statement).getExpression(); + if (!oReturnExpr.isPresent()) { + return false; + } + Expression returnExpr = oReturnExpr.get(); + // Does not handle parentheses. + if (propertyKind == PropertyKind.GETTER_NOT) { + if (!(returnExpr instanceof UnaryExpr)) { + return false; + } + UnaryExpr unary = (UnaryExpr) returnExpr; + if (unary.getOperator() != UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return false; + } + returnExpr = unary.getExpression(); + } + String returnName; + // Does not handle parentheses. + if (returnExpr instanceof NameExpr) { + returnName = ((NameExpr) returnExpr).getNameAsString(); + } else if (returnExpr instanceof FieldAccessExpr) { + FieldAccessExpr fa = (FieldAccessExpr) returnExpr; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + returnName = fa.getNameAsString(); + } else { + return false; + } + if (!returnName.equals(propertyName)) { + return false; + } + return true; + } else if (propertyKind == PropertyKind.SETTER) { + if (!(statement instanceof ExpressionStmt)) { + return false; + } + Expression expr = ((ExpressionStmt) statement).getExpression(); + if (!(expr instanceof AssignExpr)) { + return false; + } + AssignExpr assignExpr = (AssignExpr) expr; + Expression target = assignExpr.getTarget(); + AssignExpr.Operator op = assignExpr.getOperator(); + Expression value = assignExpr.getValue(); + if (!(target instanceof FieldAccessExpr)) { + return false; + } + FieldAccessExpr fa = (FieldAccessExpr) target; + Expression receiver = fa.getScope(); + if (!(receiver instanceof ThisExpr)) { + return false; + } + if (!fa.getNameAsString().equals(propertyName)) { + return false; + } + if (op != AssignExpr.Operator.ASSIGN) { + return false; + } + if (!(value instanceof NameExpr + && ((NameExpr) value).getNameAsString().equals(propertyName))) { + return false; + } + return true; + } else { + throw new Error("unexpected PropertyKind " + propertyKind); + } } - public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { - if (dont_require_private && md.isPrivate()) { - return; - } - if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { - if (verbose) { - System.out.printf("skipping trivial property method %s%n", md.getNameAsString()); - } - return; - } - String name = md.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting method %s%n", md.getName()); - } - if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { - errors.add(errorString(md, name)); - } - super.visit(md, ignore); + /** + * If the body contains exactly one statement, returns it. Otherwise, returns null. + * + * @param md a method declaration + * @return its sole statement, or null + */ + private Statement getOnlyStatement(MethodDeclaration md) { + Optional body = md.getBody(); + if (!body.isPresent()) { + return null; + } + NodeList statements = body.get().getStatements(); + if (statements.size() != 1) { + return null; + } + return statements.get(0); } - public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { - if (dont_require_private && fd.isPrivate()) { - return; - } - // True if shouldNotRequire is false for at least one of the fields - boolean shouldRequire = false; - if (verbose) { - System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); - } - boolean hasJavadocComment = hasJavadocComment(fd); - for (VariableDeclarator vd : fd.getVariables()) { - String name = vd.getNameAsString(); - // TODO: Also check the type of the serialVersionUID variable. - if (name.equals("serialVersionUID")) { - continue; - } - if (shouldNotRequire(name)) { - continue; - } - shouldRequire = true; - if (!dont_require_field && !hasJavadocComment) { - errors.add(errorString(vd, name)); - } - } - if (shouldRequire) { - super.visit(fd, ignore); - } - } + /** Visits an AST and collects warnings about missing Javadoc. */ + private class RequireJavadocVisitor extends VoidVisitorAdapter { - public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { - if (dont_require_private && ed.isPrivate()) { - return; - } - String name = ed.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ed)) { - errors.add(errorString(ed, name)); - } - super.visit(ed, ignore); - } + /** The file being visited. Used for constructing error messages. */ + private Path filename; - public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { - String name = ecd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting enum constant %s%n", name); - } - if (!dont_require_field && !hasJavadocComment(ecd)) { - errors.add(errorString(ecd, name)); - } - super.visit(ecd, ignore); - } + /** + * Create a new RequireJavadocVisitor. + * + * @param filename the file being visited; used for diagnostic messages + */ + public RequireJavadocVisitor(Path filename) { + this.filename = filename; + } - public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { - if (dont_require_private && ad.isPrivate()) { - return; - } - String name = ad.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation %s%n", name); - } - if (!dont_require_type && !hasJavadocComment(ad)) { - errors.add(errorString(ad, name)); - } - super.visit(ad, ignore); - } + /** + * Return a string stating that documentation is missing on the given construct. + * + * @param node a Java language construct (class, constructor, method, field, etc.) + * @param simpleName the construct's simple name, used in diagnostic messages + * @return an error message for the given construct + */ + private String errorString(Node node, String simpleName) { + Optional range = node.getRange(); + if (range.isPresent()) { + Position begin = range.get().begin; + Path path = + (relative + ? (filename.isAbsolute() ? workingDirAbsolute : workingDirRelative) + .relativize(filename) + : filename); + return String.format( + "%s:%d:%d: missing documentation for %s", + path, begin.line, begin.column, simpleName); + } else { + return "missing documentation for " + simpleName; + } + } - public void visit(RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { - String name = amd.getNameAsString(); - if (shouldNotRequire(name)) { - return; - } - if (verbose) { - System.out.printf("Visiting annotation member %s%n", name); - } - if (!dont_require_method && !hasJavadocComment(amd)) { - errors.add(errorString(amd, name)); - } - super.visit(amd, ignore); + public void visit(RequireJavadocVisitor this, CompilationUnit cu, Void ignore) { + Optional opd = cu.getPackageDeclaration(); + if (opd.isPresent()) { + String packageName = opd.get().getName().asString(); + if (shouldNotRequire(packageName)) { + return; + } + Optional oTypeName = cu.getPrimaryTypeName(); + if (oTypeName.isPresent() + && oTypeName.get().equals("package-info") + && !hasJavadocComment(opd.get()) + && !hasJavadocComment(cu)) { + errors.add(errorString(opd.get(), packageName)); + } + } + if (verbose) { + System.out.printf("Visiting compilation unit%n"); + } + super.visit(cu, ignore); + } + + public void visit(RequireJavadocVisitor this, ClassOrInterfaceDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting type %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, ConstructorDeclaration cd, Void ignore) { + if (dont_require_private && cd.isPrivate()) { + return; + } + if (dont_require_noarg_constructor && cd.getParameters().size() == 0) { + return; + } + String name = cd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting constructor %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(cd)) { + errors.add(errorString(cd, name)); + } + super.visit(cd, ignore); + } + + public void visit(RequireJavadocVisitor this, MethodDeclaration md, Void ignore) { + if (dont_require_private && md.isPrivate()) { + return; + } + if (dont_require_trivial_properties && isTrivialGetterOrSetter(md)) { + if (verbose) { + System.out.printf( + "skipping trivial property method %s%n", md.getNameAsString()); + } + return; + } + String name = md.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting method %s%n", md.getName()); + } + if (!dont_require_method && !isOverride(md) && !hasJavadocComment(md)) { + errors.add(errorString(md, name)); + } + super.visit(md, ignore); + } + + public void visit(RequireJavadocVisitor this, FieldDeclaration fd, Void ignore) { + if (dont_require_private && fd.isPrivate()) { + return; + } + // True if shouldNotRequire is false for at least one of the fields + boolean shouldRequire = false; + if (verbose) { + System.out.printf("Visiting field %s%n", fd.getVariables().get(0).getName()); + } + boolean hasJavadocComment = hasJavadocComment(fd); + for (VariableDeclarator vd : fd.getVariables()) { + String name = vd.getNameAsString(); + // TODO: Also check the type of the serialVersionUID variable. + if (name.equals("serialVersionUID")) { + continue; + } + if (shouldNotRequire(name)) { + continue; + } + shouldRequire = true; + if (!dont_require_field && !hasJavadocComment) { + errors.add(errorString(vd, name)); + } + } + if (shouldRequire) { + super.visit(fd, ignore); + } + } + + public void visit(RequireJavadocVisitor this, EnumDeclaration ed, Void ignore) { + if (dont_require_private && ed.isPrivate()) { + return; + } + String name = ed.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ed)) { + errors.add(errorString(ed, name)); + } + super.visit(ed, ignore); + } + + public void visit(RequireJavadocVisitor this, EnumConstantDeclaration ecd, Void ignore) { + String name = ecd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting enum constant %s%n", name); + } + if (!dont_require_field && !hasJavadocComment(ecd)) { + errors.add(errorString(ecd, name)); + } + super.visit(ecd, ignore); + } + + public void visit(RequireJavadocVisitor this, AnnotationDeclaration ad, Void ignore) { + if (dont_require_private && ad.isPrivate()) { + return; + } + String name = ad.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation %s%n", name); + } + if (!dont_require_type && !hasJavadocComment(ad)) { + errors.add(errorString(ad, name)); + } + super.visit(ad, ignore); + } + + public void visit( + RequireJavadocVisitor this, AnnotationMemberDeclaration amd, Void ignore) { + String name = amd.getNameAsString(); + if (shouldNotRequire(name)) { + return; + } + if (verbose) { + System.out.printf("Visiting annotation member %s%n", name); + } + if (!dont_require_method && !hasJavadocComment(amd)) { + errors.add(errorString(amd, name)); + } + super.visit(amd, ignore); + } + + /** + * Return true if this method is annotated with {@code @Override}. + * + * @param md the method to check for an {@code @Override} annotation + * @return true if this method is annotated with {@code @Override} + */ + private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean isOverride( + MethodDeclaration md) { + for (AnnotationExpr anno : md.getAnnotations()) { + String annoName = anno.getName().toString(); + if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { + return true; + } + } + return false; + } } /** - * Return true if this method is annotated with {@code @Override}. + * Return true if this node has a Javadoc comment. * - * @param md the method to check for an {@code @Override} annotation - * @return true if this method is annotated with {@code @Override} + * @param n the node to check for a Javadoc comment + * @return true if this node has a Javadoc comment */ - private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean isOverride( - MethodDeclaration md) { - for (AnnotationExpr anno : md.getAnnotations()) { - String annoName = anno.getName().toString(); - if (annoName.equals("Override") || annoName.equals("java.lang.Override")) { - return true; - } - } - return false; - } - } - - /** - * Return true if this node has a Javadoc comment. - * - * @param n the node to check for a Javadoc comment - * @return true if this node has a Javadoc comment - */ - private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean hasJavadocComment( - Node n) { - if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { - return true; - } - List orphans = new ArrayList<>(); - getOrphanCommentsBeforeThisChildNode(n, orphans); - for (Comment orphan : orphans) { - if (orphan.isJavadocComment()) { - return true; - } - } - Optional oc = n.getComment(); - if (oc.isPresent() - && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { - return true; - } - return false; - } - - /** - * Get "orphan comments": comments before the comment before this node. For example, in - * - *

                  {@code
                  -   * /** ... *}{@code /
                  -   * // text 1
                  -   * // text 2
                  -   * void m() { ... }
                  -   * }
                  - * - * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is - * associated with the method. - * - * @param node the node whose orphan comments to collect - * @param result the list to add orphan comments to. Is side-effected by this method. The - * implementation uses this to minimize the diffs against upstream. - */ - private static // to provide such functionality in JavaParser proper. - void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { - if (node instanceof Comment) { - return; - } - Node parent = node.getParentNode().orElse(null); - if (parent == null) { - return; - } - List everything = new LinkedList<>(parent.getChildNodes()); - sortByBeginPosition(everything); - int positionOfTheChild = -1; - for (int i = 0; i < everything.size(); i++) { - if (everything.get(i) == node) positionOfTheChild = i; - } - if (positionOfTheChild == -1) { - throw new AssertionError("I am not a child of my parent."); - } - int positionOfPreviousChild = -1; - for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { - if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + private @org.checkerframework.common.value.qual.BoolVal({false, true}) boolean + hasJavadocComment(Node n) { + if (n instanceof NodeWithJavadoc && ((NodeWithJavadoc) n).hasJavaDocComment()) { + return true; + } + List orphans = new ArrayList<>(); + getOrphanCommentsBeforeThisChildNode(n, orphans); + for (Comment orphan : orphans) { + if (orphan.isJavadocComment()) { + return true; + } + } + Optional oc = n.getComment(); + if (oc.isPresent() + && (oc.get().isJavadocComment() || oc.get().getContent().startsWith("/**"))) { + return true; + } + return false; } - for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { - Node nodeToPrint = everything.get(i); - if (!(nodeToPrint instanceof Comment)) - throw new RuntimeException( - "Expected comment, instead " - + nodeToPrint.getClass() - + ". Position of previous child: " - + positionOfPreviousChild - + ", position of child " - + positionOfTheChild); - result.add((Comment) nodeToPrint); + + /** + * Get "orphan comments": comments before the comment before this node. For example, in + * + *
                  {@code
                  +     * /** ... *}{@code /
                  +     * // text 1
                  +     * // text 2
                  +     * void m() { ... }
                  +     * }
                  + * + * the Javadoc comment and {@code // text 1} are an orphan comment, and only {@code // text2} is + * associated with the method. + * + * @param node the node whose orphan comments to collect + * @param result the list to add orphan comments to. Is side-effected by this method. The + * implementation uses this to minimize the diffs against upstream. + */ + private static // to provide such functionality in JavaParser proper. + void getOrphanCommentsBeforeThisChildNode(final Node node, List result) { + if (node instanceof Comment) { + return; + } + Node parent = node.getParentNode().orElse(null); + if (parent == null) { + return; + } + List everything = new LinkedList<>(parent.getChildNodes()); + sortByBeginPosition(everything); + int positionOfTheChild = -1; + for (int i = 0; i < everything.size(); i++) { + if (everything.get(i) == node) positionOfTheChild = i; + } + if (positionOfTheChild == -1) { + throw new AssertionError("I am not a child of my parent."); + } + int positionOfPreviousChild = -1; + for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) { + if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i; + } + for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) { + Node nodeToPrint = everything.get(i); + if (!(nodeToPrint instanceof Comment)) + throw new RuntimeException( + "Expected comment, instead " + + nodeToPrint.getClass() + + ". Position of previous child: " + + positionOfPreviousChild + + ", position of child " + + positionOfTheChild); + result.add((Comment) nodeToPrint); + } } - } }

        9. {@code -Aannotationserror}: histogram is issued as a warning, not just printed * * diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java index 469373c9175..dfc3246d7c8 100644 --- a/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java +++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportChecker.java @@ -6,7 +6,7 @@ /** * The Report Checker performs semantic searches over a program, for example, to find all methods * that override a specific method, all classes that inherit from a specific class, or all uses of - * do-while-loops (and not also while loops!). + * {@code do-while} loops (and not also {@code while} loops!). * *

          The search is specified in two different ways. * From 49714d4167893eeb3c825676ffe685dc4959ac5f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 15 Jan 2024 16:28:08 -0800 Subject: [PATCH 020/173] Improve formatting --- docs/manual/advanced-features.tex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 2a44aab819f..a565679b1a9 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1252,8 +1252,8 @@ The set of permitted expressions is a subset of all Java expressions, with a few extensions. -The extensions are formal parameters like \<\#1> and (for some type -systems) \code{}. +The extensions are formal parameters like ``\<\#1>'' and (for some type +systems) ``\code{}''. \begin{itemize} \item @@ -1279,7 +1279,7 @@ \item a formal parameter, e.g., \<\#2>. It is represented as \<\#> followed by the \textbf{one-based} parameter index. For example: \<\#1>, \<\#3>. It is not permitted to write \<\#0> to - refer to the receiver object; use \ instead. + refer to the receiver object; use ``\'' instead. The formal parameter syntax \<\#1> is less natural in source code than writing the formal parameter name. This syntax is necessary for @@ -1341,7 +1341,7 @@ This states that if \ is called immediately after \ returns true, then \ returns a non-null value. -\item a binary expression, e.g., \ or <\#1 - 1>. These are used by +\item a binary expression, e.g., \ or \<\#1 - 1>. These are used by the Index Checker, for example. \item a class name expression within another expression, e.g., ``String'' From 85b86c6675da87d205d1bb18ebeb786de643df6b Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 16 Jan 2024 12:09:19 -0800 Subject: [PATCH 021/173] Add passing testcase for Issue #6393 (#6394) --- checker/tests/nullness/Issue6393.java | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 checker/tests/nullness/Issue6393.java diff --git a/checker/tests/nullness/Issue6393.java b/checker/tests/nullness/Issue6393.java new file mode 100644 index 00000000000..77bd93f343b --- /dev/null +++ b/checker/tests/nullness/Issue6393.java @@ -0,0 +1,26 @@ +import java.io.IOException; +import java.io.Serializable; +import org.checkerframework.checker.nullness.qual.Nullable; + +class Issue6393 { + + public static class AClass implements Serializable {} + + static class GClass { + public @Nullable T g(Class e) throws IOException { + throw new AssertionError(); + } + } + + @SuppressWarnings("unchecked") + static T f(Class x, GClass y) { + AClass z; + try { + z = y.g(x); + } catch (IOException e) { + throw new IllegalStateException(e); + } + // :: warning: (cast.unsafe) + return (T) z; + } +} From 59addbcbdf4b57dfdfd6de775288921a91a1f644 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 01:04:32 +0000 Subject: [PATCH 022/173] Update dependency org.plumelib:reflection-util to v1.1.3 (#6399) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 20c6a69e30f7b1b9bca5d1a69d0159de5cd091d5 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 18 Jan 2024 12:34:34 -0800 Subject: [PATCH 023/173] When (not) to use the mailing list --- docs/manual/faq.tex | 10 +++++----- docs/manual/troubleshooting.tex | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/manual/faq.tex b/docs/manual/faq.tex index 9c7509f3b47..1b7b856a137 100644 --- a/docs/manual/faq.tex +++ b/docs/manual/faq.tex @@ -313,12 +313,13 @@ Anyone is welcome to send mail to the \code{checker-framework-dev@googlegroups.com} mailing list --- for implementation details it is generally a better place for discussions than -the general \code{checker-framework-discuss@googlegroups.com} mailing list, -which is for user-focused discussions. +the general \code{checker-framework-discuss@googlegroups.com} mailing list. Anyone is welcome to \href{https://groups.google.com/forum/#!forum/checker-framework-discuss}{join \code{checker-framework-discuss@googlegroups.com}} and send mail to it. +This list is for user-focused discussions. It is not for submitting bug reports, +which should use the issue tracker (Section~\ref{reporting-bugs}). \sectionAndLabel{Usability of pluggable type-checking}{faq-usability-section} @@ -499,9 +500,8 @@ that appear in it. If nothing else explains it, then -open an issue (Section~\ref{reporting-bugs}) or ask on the -\href{https://groups.google.com/forum/#!forum/checker-framework-discuss}{mailing - list}. Be sure to say what you think it means or what specific part does +open an issue (Section~\ref{reporting-bugs}). +Be sure to say what you think it means or what specific part does not make sense to you, and what you have already done to try to understand it. diff --git a/docs/manual/troubleshooting.tex b/docs/manual/troubleshooting.tex index d16b29b0da6..00f000dd8b7 100644 --- a/docs/manual/troubleshooting.tex +++ b/docs/manual/troubleshooting.tex @@ -5,10 +5,11 @@ The manual might already answer your question, so first please look for your answer in the manual, including this chapter and the FAQ (Chapter~\ref{faq}). -If not, you can use the mailing list, -\code{checker-framework-discuss@googlegroups.com}, to ask other users for -help. For archives and to subscribe, see \url{https://groups.google.com/forum/#!forum/checker-framework-discuss}. -To report bugs, please see Section~\ref{reporting-bugs}. +If you think you have found a bug in the Checker Framework, +please report it (see Section~\ref{reporting-bugs}). +For discussions of broad interest, you can use the mailing list, +\code{checker-framework-discuss@googlegroups.com}. +For archives and to subscribe, see \url{https://groups.google.com/forum/#!forum/checker-framework-discuss}. If you want to help out, you can give feedback (including on the documentation), choose a bug and fix it (start with those labeled \href{https://github.com/eisop/checker-framework/issues?q=is\%3Aissue+is\%3Aopen+label\%3A\%22good+first+issue%22}{``good From b896aeb69cc1a7f4f472cb965919e0033ca427bb Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 18 Jan 2024 13:28:24 -0800 Subject: [PATCH 024/173] Maven bug might not be fixed yet --- docs/manual/external-tools.tex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/manual/external-tools.tex b/docs/manual/external-tools.tex index 12fc7f236de..29cafa5dd87 100644 --- a/docs/manual/external-tools.tex +++ b/docs/manual/external-tools.tex @@ -1001,7 +1001,8 @@ Debugging your Maven configuration can be tricky because of a \ahreforurl{https://issues.apache.org/jira/browse/MCOMPILER-434}{bug in - maven-compiler-plugin versions before 3.10.1}: Maven does not report + maven-compiler-plugin versions before 3.10.1, and still affecting some + users with 3.11.0}: Maven does not report annotation processor exceptions, even when the \<-X> command-line argument is passed. From 20f496f4ab78a648f6a5350f3abad1faf84597e2 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Thu, 18 Jan 2024 19:43:39 -0800 Subject: [PATCH 025/173] Add missing dependencies for copyAndMinimizeAnnotatedJdkFiles task (#6403) --- framework/build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/build.gradle b/framework/build.gradle index c0d3e747f55..abdb31510e8 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -125,7 +125,11 @@ task cloneAnnotatedJdk() { task copyAndMinimizeAnnotatedJdkFiles(dependsOn: cloneAnnotatedJdk, group: 'Build') { - dependsOn ':framework:compileJava', project(':javacutil').tasks.jar + dependsOn ':framework:compileJava' + // we need the next two dependencies because we run JavaStubifier using this project's runtimeClasspath, + // which refers to the jars for these other projects + dependsOn ':javacutil:jar' + dependsOn ':dataflow:jar' def inputDir = "${annotatedJdkHome}/src" def outputDir = "${buildDir}/generated/resources/annotated-jdk/" From 283bebf6b55c453eb043af5dc502c7f636e4ba8d Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 19 Jan 2024 09:21:27 -0800 Subject: [PATCH 026/173] Fix Symbol$CompletionFailure crash when building CFG (#6397) Co-authored-by: Michael Ernst --- checker/build.gradle | 4 ++ .../resourceleak/SparkSessionCFGCrash.java | 10 ++++ .../javacutil/trees/TreeBuilder.java | 46 +++++++++++++++++-- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 checker/tests/resourceleak/SparkSessionCFGCrash.java diff --git a/checker/build.gradle b/checker/build.gradle index 8a2aa670175..63aba6dd520 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -90,6 +90,9 @@ dependencies { testImplementation 'org.apiguardian:apiguardian-api:1.1.2' // For tests that use JSpecify annotations testImplementation 'org.jspecify:jspecify:1.0.0' + // To test for an obscure crash in CFG construction for try-with-resources; + // see https://github.com/typetools/checker-framework/issues/6396 + testImplementation 'org.apache.spark:spark-sql_2.12:3.3.2' // Required for checker/tests/index-initializedfields/RequireJavadoc.java if (JavaVersion.current() == JavaVersion.VERSION_1_8) { @@ -1201,3 +1204,4 @@ publishing { signing { sign publishing.publications.checker } + diff --git a/checker/tests/resourceleak/SparkSessionCFGCrash.java b/checker/tests/resourceleak/SparkSessionCFGCrash.java new file mode 100644 index 00000000000..971b86e4dcc --- /dev/null +++ b/checker/tests/resourceleak/SparkSessionCFGCrash.java @@ -0,0 +1,10 @@ +// To test crash in CFG construction for try-with-resources +// See https://github.com/typetools/checker-framework/issues/6396 +import org.apache.spark.sql.SparkSession; + +public class SparkSessionCFGCrash { + + private void run() { + try (SparkSession session = SparkSession.builder().getOrCreate()) {} + } +} diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java index c380dc4c292..ae3f8415ad6 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java @@ -22,6 +22,7 @@ import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; import java.util.List; import javax.annotation.processing.ProcessingEnvironment; @@ -46,14 +47,40 @@ * TreeMaker. */ public class TreeBuilder { + + /** The javac {@link Elements} object. */ protected final Elements elements; + + /** The javac {@link javax.lang.model.util.Types} object. */ protected final Types modelTypes; + + /** The internal javac {@link com.sun.tools.javac.code.Types} object. */ protected final com.sun.tools.javac.code.Types javacTypes; + + /** For constructing trees */ protected final TreeMaker maker; + + /** The javac {@link Names} object. */ protected final Names names; + + /** The javac {@link Symtab} object. */ protected final Symtab symtab; + + /** The javac {@link ProcessingEnvironment} */ protected final ProcessingEnvironment env; + /** + * {@link Name} object for "close", used when building a tree for a call to {@code close()}. + * + * @see #buildCloseMethodAccess(ExpressionTree) + */ + private final Name closeName; + + /** + * Creates a new TreeBuilder. + * + * @param env the javac {@link ProcessingEnvironment} + */ public TreeBuilder(ProcessingEnvironment env) { this.env = env; Context context = ((JavacProcessingEnvironment) env).getContext(); @@ -63,6 +90,7 @@ public TreeBuilder(ProcessingEnvironment env) { maker = TreeMaker.instance(context); names = Names.instance(context); symtab = Symtab.instance(context); + closeName = names.fromString("close"); } /** @@ -145,10 +173,20 @@ public MemberSelectTree buildCloseMethodAccess(ExpressionTree autoCloseableExpr) // Find the close() method Symbol.MethodSymbol closeMethod = null; - for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { - if (method.getParameters().isEmpty() && method.getSimpleName().contentEquals("close")) { - closeMethod = (Symbol.MethodSymbol) method; - break; + // We could use elements.getAllMembers(exprElement) to find the close method, but in rare cases + // calling that method crashes with a Symbol$CompletionFailure exception. See + // https://github.com/typetools/checker-framework/issues/6396. The code below directly searches + // all supertypes for the method and avoids the crash. + for (Type s : javacTypes.closure(((Symbol) exprElement).type)) { + for (Symbol m : s.tsym.members().getSymbolsByName(closeName)) { + if (!(m instanceof Symbol.MethodSymbol)) { + continue; + } + Symbol.MethodSymbol msym = (Symbol.MethodSymbol) m; + if (!msym.isStatic() && msym.getParameters().isEmpty()) { + closeMethod = msym; + break; + } } } From 5e3b7ad0872b0e167778016171ec212596a3b43f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 21 Jan 2024 23:40:47 -0800 Subject: [PATCH 027/173] Refactor `checkPurity()` --- .../common/basetype/BaseTypeVisitor.java | 129 +++++++++++------- 1 file changed, 78 insertions(+), 51 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index ec0d51161d0..e2630bdcdcb 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -259,7 +259,7 @@ public class BaseTypeVisitor kinds = PurityUtils.getPurityKinds(atypeFactory, tree); - // @Deterministic makes no sense for a void method or constructor - boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); - if (isDeterministic) { - if (TreeUtils.isConstructor(tree)) { - checker.reportWarning(tree, "purity.deterministic.constructor"); - } else if (TreeUtils.isVoidReturn(tree)) { - checker.reportWarning(tree, "purity.deterministic.void.method"); + // `body` is lazily assigned. + TreePath body = null; + boolean bodyAssigned = false; + + if (suggestPureMethods || PurityUtils.hasPurityAnnotation(atypeFactory, tree)) { + // check "no" purity + EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, tree); + // @Deterministic makes no sense for a void method or constructor + boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); + if (isDeterministic) { + if (TreeUtils.isConstructor(tree)) { + checker.reportWarning(tree, "purity.deterministic.constructor"); + } else if (TreeUtils.isVoidReturn(tree)) { + checker.reportWarning(tree, "purity.deterministic.void.method"); + } } - } - - TreePath body = atypeFactory.getPath(tree.getBody()); - PurityResult r; - if (body == null) { - r = new PurityResult(); - } else { - r = - PurityChecker.checkPurity( - body, atypeFactory, assumeSideEffectFree, assumeDeterministic, assumePureGetters); - } - if (!r.isPure(kinds)) { - reportPurityErrors(r, tree, kinds); - } - if (suggestPureMethods && !TreeUtils.isSynthetic(tree)) { - // Issue a warning if the method is pure, but not annotated as such. - EnumSet additionalKinds = r.getKinds().clone(); - /* NO-AFU - if (!infer) { - // During WPI, propagate all purity kinds, even those that are already - // present (because they were inferred in a previous WPI round). - */ - additionalKinds.removeAll(kinds); - /* NO-AFU + body = atypeFactory.getPath(tree.getBody()); + bodyAssigned = true; + PurityResult r; + if (body == null) { + r = new PurityResult(); + } else { + r = + PurityChecker.checkPurity( + body, atypeFactory, assumeSideEffectFree, assumeDeterministic, assumePureGetters); } - */ - if (TreeUtils.isConstructor(tree) || TreeUtils.isVoidReturn(tree)) { - additionalKinds.remove(Pure.Kind.DETERMINISTIC); + if (!r.isPure(kinds)) { + reportPurityErrors(r, tree, kinds); } - if (additionalKinds.isEmpty()) { - // No need to suggest @Impure, since it is equivalent to no annotation. - } else if (additionalKinds.size() == 2) { - checker.reportWarning(tree, "purity.more.pure", tree.getName()); - } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - checker.reportWarning(tree, "purity.more.sideeffectfree", tree.getName()); - } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { - checker.reportWarning(tree, "purity.more.deterministic", tree.getName()); - } else { - throw new BugInCF("Unexpected purity kind in " + additionalKinds); + + if (suggestPureMethods && !TreeUtils.isSynthetic(tree)) { + // Issue a warning if the method is pure, but not annotated as such. + EnumSet additionalKinds = r.getKinds().clone(); + if (!infer) { + // During WPI, propagate all purity kinds, even those that are already + // present (because they were inferred in a previous WPI round). + additionalKinds.removeAll(kinds); + } + if (TreeUtils.isConstructor(tree) || TreeUtils.isVoidReturn(tree)) { + additionalKinds.remove(Pure.Kind.DETERMINISTIC); + } + if (infer) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); + inferPurityAnno(additionalKinds, wpi, methodElt); + // The purity of overridden methods is impacted by the purity of this method. If a + // superclass method is pure, but an implementation in a subclass is not, WPI ought to + // treat **neither** as pure. The purity kind of the superclass method is the LUB of + // its own purity and the purity of all the methods that override it. Logically, this + // rule is the same as the WPI rule for overrides, but purity isn't a type system and + // therefore must be special-cased. + Set overriddenMethods = + ElementUtils.getOverriddenMethods(methodElt, types); + for (ExecutableElement overriddenElt : overriddenMethods) { + inferPurityAnno(additionalKinds, wpi, overriddenElt); + } + } else if (additionalKinds.isEmpty()) { + // No need to suggest @Impure, since it is equivalent to no annotation. + } else if (additionalKinds.size() == 2) { + checker.reportWarning(tree, "purity.more.pure", tree.getName()); + } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + checker.reportWarning(tree, "purity.more.sideeffectfree", tree.getName()); + } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { + checker.reportWarning(tree, "purity.more.deterministic", tree.getName()); + } else { + throw new BugInCF("Unexpected purity kind in " + additionalKinds); + } } /* NO-AFU if (infer) { @@ -1171,6 +1189,15 @@ protected void checkPurity(MethodTree tree) { } */ } + + // There will be code here that *may* use `body` (and may set `body` before using it). + // The below is just a placeholder so `bodyAssigned` is not a dead variable. + // ... + if (!bodyAssigned) { + body = atypeFactory.getPath(tree.getBody()); + bodyAssigned = true; + } + // ... } /* NO-AFU From c5126535cafac7275b63e7bad99b0987e57604eb Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 22 Jan 2024 15:57:32 -0800 Subject: [PATCH 028/173] Reorder sections, add text about `Map.Entry` (#6404) --- docs/manual/faq.tex | 118 +++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/docs/manual/faq.tex b/docs/manual/faq.tex index 1b7b856a137..02f6bf49355 100644 --- a/docs/manual/faq.tex +++ b/docs/manual/faq.tex @@ -67,9 +67,10 @@ \\ \ref{faq-no-annotation-on-types-and-declarations}: Why shouldn't a qualifier apply to both types and declarations? \\ \ref{faq-annotate-fully-qualified-name}: How do I annotate a fully-qualified type name? -\\ \ref{faq-declaration-annotations-moved}: How does the Checker Framework handle obsolete declaration annotations? \\ \ref{faq-type-vs-declaration-annotations}: What is the difference between type annotations and declaration annotations? \\ \ref{faq-type-annotation-formatting}: How should type annotations be formatted in source code? Where should I write type annotations? +\\ \ref{faq-declaration-annotations-moved}: How does the Checker Framework handle obsolete declaration annotations? +\\ \ref{faq-declaration-annotations-convert}: How do I convert from other tools' declaration annotations to type annotations? \faqtocpara{\ref{faq-semantics-section}: Semantics of type annotations} \\ \ref{faq-typestate}: How can I handle typestate, or phases of my program with different data properties? @@ -1227,49 +1228,6 @@ \end{Verbatim} -\label{declaration-annotations-moved} % 2021-04-16 temporary, for backward compatibility -\subsectionAndLabel{How does the Checker Framework handle obsolete declaration annotations?}{faq-declaration-annotations-moved} - -When a declaration annotation is an alias for a type annotation, the -Checker Framework may move the annotation before replacing it by the -canonical version. (If the declaration annotation is in an \ -package, it is not moved.) - -For example, - -\begin{Verbatim} - import android.support.annotation.NonNull; - ... - @NonNull Object [] returnsArray(); -\end{Verbatim} - -\noindent -is treated as if the programmer had written - -\begin{Verbatim} - import org.checkerframework.checker.nullness.qual.NonNull; - ... - Object @NonNull [] returnsArray(); -\end{Verbatim} - -\noindent -because Android's \<@NonNull> annotation is a declaration annotation, which -is understood to apply to the top-level return type of the annotated method. - -When possible, you should use type annotations rather than declaration -annotations. - -Users who are using old Java 5--7 declaration annotations (for instance, -from the FindBugs tool, which has not been maintained since 2015, and from -its successor SpotBugs, which has not yet adopted type annotations, even -though type annotations were added to Java in 2014) can use annotations in -package \ to avoid name -conflicts. These are available in package -\href{https://search.maven.org/search?q=a:checker-compat-qual}{\} -on Maven Central. Once the users are ready to upgrade to Java 8+ type -annotations, those compatibility annotations are no longer necessary. - - \subsectionAndLabel{What is the difference between type annotations and declaration annotations?}{faq-type-vs-declaration-annotations} Java has two distinct varieties of annotation: type annotations and @@ -1416,6 +1374,78 @@ \end{itemize} +\label{declaration-annotations-moved} % 2021-04-16 temporary, for backward compatibility +\subsectionAndLabel{How does the Checker Framework handle obsolete declaration annotations?}{faq-declaration-annotations-moved} + +When a declaration annotation is an alias for a type annotation, the +Checker Framework may move the annotation before replacing it by the +canonical version. (If the declaration annotation is in an \ +package, it is not moved.) + +For example, + +\begin{Verbatim} + import android.support.annotation.NonNull; + ... + @NonNull Object [] returnsArray(); +\end{Verbatim} + +\noindent +is treated as if the programmer had written + +\begin{Verbatim} + import org.checkerframework.checker.nullness.qual.NonNull; + ... + Object @NonNull [] returnsArray(); +\end{Verbatim} + +\noindent +because Android's \<@NonNull> annotation is a declaration annotation, which +is understood to apply to the top-level return type of the annotated method. + +When possible, you should use type annotations rather than declaration +annotations, and write them in the correct location +(Section~\ref{faq-type-annotation-formatting}). + +Users who are using old Java 5--7 declaration annotations (for instance, +from the FindBugs tool, which has not been maintained since 2015, and from +its successor SpotBugs, which has not yet adopted type annotations, even +though type annotations were added to Java in 2014) can use annotations in +package \ to avoid name +conflicts. These are available in package +\href{https://search.maven.org/search?q=a:checker-compat-qual}{\} +on Maven Central. Once the users are ready to upgrade to Java 8+ type +annotations, those compatibility annotations are no longer necessary. + + +\subsectionAndLabel{How do I convert from other tools' declaration annotations to type annotations?}{faq-declaration-annotations-convert} + +In order to convert from another tool's declaration annotations to the Checker Framework's type annotations, +you need to change imports, rename annotations, move annotations written on arrays (see Section~\ref{faq-declaration-annotations-moved}), and move annotations to simple type names. + +For example, if a user writes + +\begin{Verbatim} +import android.support.annotation.Nullable; +... +@Nullable +public Map.Entry intersects() { ... } +\end{Verbatim} + +\noindent +and then changes the import to +\, the code will not +compile. (See Section~\ref{common-problems-non-typechecking} for more details.) +The user needs to also move the \<@Nullable> annotation to a location where +Java permits type annotations: + +\begin{Verbatim} +import org.checkerframework.checker.nullness.qual.Nullable; +... +public Map.@Nullable Entry intersects() { ... } +\end{Verbatim} + + \sectionAndLabel{Semantics of type annotations}{faq-semantics-section} From f5ff775bfc6fd9b5b37a2a9429ace293c6e20b3e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:39:52 -0800 Subject: [PATCH 029/173] Update plugin com.diffplug.spotless to v6.25.0 (#6418) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From b3373d0d38ec287e1e5f91f7d8bb32770951acd2 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 23 Jan 2024 11:40:39 -0800 Subject: [PATCH 030/173] Programmers use WPI rather than `-Ainfer` --- docs/manual/introduction.tex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index ec907729a3d..df3e255e99d 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -686,8 +686,9 @@ \item \<-Ainfer=\emph{outputformat}> Output suggested annotations for method signatures and fields. These annotations may reduce the number of type-checking - errors when running type-checking in the future; see - Section~\ref{whole-program-inference}. + errors on subsequent type-checking runs. This option is + typically used by whole-program inference (WPI; see + Section~\ref{whole-program-inference}) rather than by programmers. Using \<-Ainfer=jaifs> produces \<.jaif> files. Using \<-Ainfer=stubs> produces \<.astub> files. Using \<-Ainfer=ajava> produces \<.ajava> files. From bf7ccf9550ecc6971f29d1de53b2c0a0a241773f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 24 Jan 2024 15:53:18 -0800 Subject: [PATCH 031/173] Mostly comments about `AnnotationStatistics` --- .../util/count/AnnotationStatistics.java | 28 +++++++++++-------- .../common/util/report/ReportVisitor.java | 1 + framework/tests/report/reporttest.astub | 4 +-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java index a392df97d0d..a82d0570d05 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java @@ -38,26 +38,32 @@ * javac -proc:only -processor org.checkerframework.common.util.count.AnnotationStatistics MyFile.java ... * * - *

          You probably want to pipe the output through another program: + *

          By default, this utility displays annotation locations only, but not the annotations + * themselves. Further, the ouput includes all annotations (including {@code @Override}, etc.), + * which is not very useful. * - *

            - *
          • Total annotation count: {@code ... | wc}. - *
          • Breakdown by location type: {@code ... | sort | uniq -c} - *
          • Count for only certain location types: use {@code grep} - *
          - * - *

          By default, this utility displays annotation locations only. The following options may be used - * to adjust the output: + *

          The following options may be used to adjust the output: * *

            - *
          • {@code -Aannotations}: prints information about the annotations, such as whether it is in a - * signature or in a body + *
          • {@code -Aannotations}: prints the annotation name, the file that contains it, and whether + * it is in a signature or in a body *
          • {@code -Anolocations}: suppresses location output; only makes sense in conjunction with * {@code -Aannotations} *
          • {@code -Aannotationsummaryonly}: with both of the above, only outputs a summary *
          • {@code -Aannotationserror}: histogram is issued as a warning, not just printed *
          * + *

          These use cases are not very useful, because they include all annotations including + * {@code @Override}, etc. + * + *

            + *
          • Output the locations of annotations, but not the annotations themselves: normal invocation, + * as above + *
          • Histogram of the locations of annotations, by location type: {@code ... | sort | uniq -c} + *
          • Total annotation count: {@code ... | wc}. + *
          • Count for only certain location types: use {@code grep} + *
          + * * @see JavaCodeStatistics * @see org.checkerframework.common.util.report.ReportChecker */ diff --git a/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java index ad8e15e3a2c..c7f22c00832 100644 --- a/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/util/report/ReportVisitor.java @@ -70,6 +70,7 @@ public ReportVisitor(BaseTypeChecker checker) { @Override public Void scan(Tree tree, Void p) { if ((tree != null) && (treeKinds != null) && treeKinds.contains(tree.getKind())) { + // TODO: Also output the tree itself: TreeUtils.toStringTruncated(tree, 60) checker.reportError(tree, "Tree.Kind." + tree.getKind()); } return super.scan(tree, p); diff --git a/framework/tests/report/reporttest.astub b/framework/tests/report/reporttest.astub index aa66e28c469..a7e49f6cc09 100644 --- a/framework/tests/report/reporttest.astub +++ b/framework/tests/report/reporttest.astub @@ -3,8 +3,8 @@ import org.checkerframework.common.util.report.qual.*; package java.lang; class Class { - @ReportCall - Class forName(String s); + @ReportCall + Class forName(String s); } @ReportUse From 1ad66bcf26183e2f6cfc74149ca88d42ff8c9b3b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 24 Jan 2024 15:53:44 -0800 Subject: [PATCH 032/173] Miscellaneous tweaks --- .../checker/guieffect/GuiEffectTypeFactory.java | 7 ++++--- checker/tests/ainfer-README | 2 +- docs/manual/advanced-features.tex | 4 ++-- .../checkerframework/common/basetype/BaseTypeVisitor.java | 8 ++++---- .../checkerframework/framework/source/SourceChecker.java | 2 +- .../org/checkerframework/framework/util/Contract.java | 5 ++++- .../framework/util/DefaultContractsFromMethod.java | 4 ++-- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java index 433175f42c6..6eb98ab88b3 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java @@ -282,10 +282,11 @@ public Effect getComputedEffectAtCallsite( Effect targetEffect = getDeclaredEffect(methodElt); if (targetEffect.isPoly()) { AnnotatedTypeMirror srcType = null; - if (tree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { - ExpressionTree src = ((MemberSelectTree) tree.getMethodSelect()).getExpression(); + ExpressionTree methodSelect = tree.getMethodSelect(); + if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { + ExpressionTree src = ((MemberSelectTree) methodSelect).getExpression(); srcType = getAnnotatedType(src); - } else if (tree.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { + } else if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()" if (callerReceiver == null) { // Not enought information provided to instantiate this type-polymorphic effects diff --git a/checker/tests/ainfer-README b/checker/tests/ainfer-README index 6fc34a8cb64..fd88f352f44 100644 --- a/checker/tests/ainfer-README +++ b/checker/tests/ainfer-README @@ -7,7 +7,7 @@ The task ainferTest tests the -Ainfer command-line argument in three steps: 1. The TestChecker will type-check all files in the "non-annotated" folder -using the -Ainfer command-liner argument, which write the inferred types of +using the -Ainfer command-line argument, which writes the inferred types of some elements into annotation files. The inferred types are written into annotation files, but are not considered during this type-check -- for that reason the expected error comments are necessary. diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index a565679b1a9..2f71c423c22 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -735,8 +735,8 @@ However, the programmer must write type annotations for method signatures (arguments and return values) and fields, unless the default annotations are correct. -Local type refinement does not change the source code; it re-runs every time -you run a checker. +Local type refinement does not change the source code to insert the +inferred annotations on local variables. \subsectionAndLabel{Type refinement examples}{type-refinement-examples} diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index e2630bdcdcb..3b5aae0cdda 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -285,7 +285,7 @@ public class BaseTypeVisitor methods = ElementFilter.methodsIn(anno.getEnclosedElements()); + List methods = ElementFilter.methodsIn(annoType.getEnclosedElements()); // Mapping from argument simple name to its annotated type. Map annoTypes = ArrayMap.newArrayMapOrHashMap(methods.size()); for (ExecutableElement meth : methods) { diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index e323c0c4a0b..a8c7f85e102 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2200,7 +2200,7 @@ private boolean shouldSuppressWarnings(@Nullable Object src, String errKey) { } else if (src == null) { return false; } else { - throw new BugInCF("Unexpected source " + src); + throw new BugInCF("Unexpected source [" + src.getClass() + "] " + src); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/Contract.java b/framework/src/main/java/org/checkerframework/framework/util/Contract.java index 58aea0f6a52..a69da26a177 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Contract.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Contract.java @@ -38,7 +38,10 @@ public abstract class Contract { /** The annotation on the type of expression, according to this contract. */ public final AnnotationMirror annotation; - /** The annotation that expressed this contract; used for diagnostic messages. */ + /** + * The annotation that expressed this contract; used for diagnostic messages, but not for the + * location of the diagnostic message. + */ public final AnnotationMirror contractAnnotation; // This is redundant with the contract's class and is not used in this file, but the field diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java index ba9946aea2a..492b7bfd792 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java @@ -249,8 +249,8 @@ private Set getContract( * @param argumentAnno annotation containing the element {@code values}, or {@code null} * @param argumentRenaming renaming of argument names, which maps from names in {@code * argumentAnno} to names used in the returned annotation, or {@code null} - * @return a qualifier whose type is that of {@code contract.qualifier}, or an alias for it, or - * null if it is not a supported qualifier of the type system + * @return a qualifier whose type is that of {@code contractAnno.qualifier}, or an alias for it, + * or null if it is not a supported qualifier of the type system */ private @Nullable AnnotationMirror getQualifierEnforcedByContractAnnotation( AnnotationMirror contractAnno, From 7bc769339d75d4a8343d772178c185dfe9730c75 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 24 Jan 2024 15:54:55 -0800 Subject: [PATCH 033/173] Always rename JavaParser classes in jar files --- build.gradle | 20 ++++---------------- gradle-mvn-push.gradle | 2 +- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index 746c1313965..3a6f4fde209 100644 --- a/build.gradle +++ b/build.gradle @@ -42,8 +42,6 @@ def majorVersionToInt(majorVersionString) { } ext { - release = false - // On a Java 8 JVM, use error-prone javac and source/target 8. // On a Java 9+ JVM, use the host javac, default source/target, and required module flags. isJava8 = JavaVersion.current() == JavaVersion.VERSION_1_8 @@ -960,6 +958,7 @@ subprojects { // For an example work-around see NullnessAnnotatedTypeFactory#NONNULL_ALIASES. // Relocate packages that might conflict with user's classpath. + relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser' relocate 'org.apache', 'org.checkerframework.org.apache' relocate 'org.relaxng', 'org.checkerframework.org.relaxng' relocate 'org.plumelib', 'org.checkerframework.org.plumelib' @@ -973,13 +972,6 @@ subprojects { exclude '**/module-info.class' - doFirst { - if (release) { - // Only relocate JavaParser during a release: - relocate 'com.github.javaparser', 'org.checkerframework.com.github.javaparser' - } - } - minimize() } @@ -1319,17 +1311,13 @@ task buildAll(group: 'Build') { dependsOn("${subproject.name}:sourcesJar") } dependsOn('framework:allJavadocJar', 'framework:allSourcesJar', 'checker:allJavadocJar', 'checker:allSourcesJar', 'checker-qual:jar', 'checker-util:jar') + dependsOn('checker:assembleForJavac') } task releaseBuild(group: 'Build') { - description = 'Build everything required for a release' + description = 'Cleans, then builds everything required for a release' dependsOn(clean) - doFirst { - release = true - } - // Use finalizedBy rather than dependsOn so that release is set to true before any of the tasks are run. - finalizedBy(buildAll) - finalizedBy('checker:assembleForJavac') + dependsOn(buildAll) } // No group so it does not show up in the output of `gradlew tasks` diff --git a/gradle-mvn-push.gradle b/gradle-mvn-push.gradle index af33054759e..79f5d394cb1 100644 --- a/gradle-mvn-push.gradle +++ b/gradle-mvn-push.gradle @@ -35,6 +35,6 @@ signing { // Only sign releases; snapshots are unsigned. tasks.withType(Sign).configureEach { onlyIf { - release + !isSnapshot } } From 1faed4a5b3e6364cd94abf62546a48dbc33e7993 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 24 Jan 2024 15:56:43 -0800 Subject: [PATCH 034/173] Make AnnotatedTypeMirror#toString side-effect free --- .../framework/type/AnnotatedTypeMirror.java | 24 +++++---- .../type/DefaultAnnotatedTypeFormatter.java | 53 ++++++++++++------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index ae8da45f731..49b3e2baaa5 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -939,12 +939,11 @@ public int getUnderlyingTypeHashCode() { public static class AnnotatedDeclaredType extends AnnotatedTypeMirror { /** Parametrized Type Arguments. */ - protected List typeArgs; + protected @MonotonicNonNull List typeArgs; /** * Whether the type was initially raw, i.e. the user did not provide the type arguments. * typeArgs will contain inferred type arguments, which might be too conservative at the moment. - * TODO: improve inference. * *

          Ideally, the field would be final. However, when we determine the supertype of a raw type, * we need to set isUnderlyingTypeRaw for the supertype. @@ -1184,14 +1183,21 @@ public void setEnclosingType(@Nullable AnnotatedDeclaredType enclosingType) { /** Represents a type of an executable. An executable is a method, constructor, or initializer. */ public static class AnnotatedExecutableType extends AnnotatedTypeMirror { - private @MonotonicNonNull ExecutableElement element; + /** The element of the method. */ + /*package-private*/ @MonotonicNonNull ExecutableElement element; + /** + * Creates an {@link AnnotatedExecutableType}. + * + * @param type the Java type + * @param factory the factory + */ private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factory) { super(type, factory); } /** The parameter types; an unmodifiable list. */ - private @MonotonicNonNull List paramTypes = null; + /*package-private*/ @MonotonicNonNull List paramTypes = null; /** Whether {@link #paramTypes} has been computed. */ private boolean paramTypesComputed = false; @@ -1200,7 +1206,7 @@ private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factor * The receiver type of this executable type; null for static methods and constructors of * top-level classes. */ - private @Nullable AnnotatedDeclaredType receiverType; + /*package-private*/ @Nullable AnnotatedDeclaredType receiverType; /** * The varargs type is the last element of {@link #paramTypes} if the method or constructor @@ -1215,19 +1221,19 @@ private AnnotatedExecutableType(ExecutableType type, AnnotatedTypeFactory factor private boolean receiverTypeComputed = false; /** The return type. */ - private AnnotatedTypeMirror returnType; + /*package-private*/ @MonotonicNonNull AnnotatedTypeMirror returnType; /** Whether {@link #returnType} has been computed. */ private boolean returnTypeComputed = false; /** The thrown types; an unmodifiable list. */ - private List thrownTypes; + /*package-private*/ @MonotonicNonNull List thrownTypes; /** Whether {@link #thrownTypes} has been computed. */ private boolean thrownTypesComputed = false; /** The type variables; an unmodifiable list. */ - private List typeVarTypes; + /*package-private*/ @MonotonicNonNull List typeVarTypes; /** Whether {@link #typeVarTypes} has been computed. */ private boolean typeVarTypesComputed = false; @@ -1649,7 +1655,7 @@ private AnnotatedArrayType(ArrayType type, AnnotatedTypeFactory factory) { } /** The component type of this array type. */ - private AnnotatedTypeMirror componentType; + /*package-private*/ @MonotonicNonNull AnnotatedTypeMirror componentType; @Override public R accept(AnnotatedTypeVisitor v, P p) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java index d7687e3e935..7a3f52d4ded 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultAnnotatedTypeFormatter.java @@ -234,8 +234,8 @@ public String visitDeclared(AnnotatedDeclaredType type, Set sb.append("/*DECL*/ "); } - if (type.getEnclosingType() != null) { - sb.append(this.visit(type.getEnclosingType(), visiting)); + if (type.enclosingType != null) { + sb.append(this.visit(type.enclosingType, visiting)); sb.append('.'); } Element typeElt = type.getUnderlyingType().asElement(); @@ -268,6 +268,8 @@ public String visitDeclared(AnnotatedDeclaredType type, Set } sb.append(sj); } + } else { + sb.append("<" + "/*Type args not initialized*/" + ">"); } currentlyPrintingRaw = oldPrintingRaw; return sb.toString(); @@ -276,6 +278,10 @@ public String visitDeclared(AnnotatedDeclaredType type, Set @Override public String visitIntersection( AnnotatedIntersectionType type, Set visiting) { + if (type.bounds == null) { + return "/*Intersection not initialized*/"; + } + StringBuilder sb = new StringBuilder(); boolean isFirst = true; @@ -291,6 +297,10 @@ public String visitIntersection( @Override public String visitUnion(AnnotatedUnionType type, Set visiting) { + if (type.alternatives == null) { + return "/*Union not initialized*/"; + } + StringBuilder sb = new StringBuilder(); boolean isFirst = true; @@ -307,17 +317,21 @@ public String visitUnion(AnnotatedUnionType type, Set visit @Override public String visitExecutable(AnnotatedExecutableType type, Set visiting) { StringBuilder sb = new StringBuilder(); - if (!type.getTypeVariables().isEmpty()) { + if (type.typeVarTypes == null || !type.typeVarTypes.isEmpty()) { StringJoiner sj = new StringJoiner(", ", "<", "> "); - for (AnnotatedTypeVariable atv : type.getTypeVariables()) { - sj.add(visit(atv, visiting)); + if (type.typeVarTypes == null) { + sj.add("/*Type var not initialized*/"); + } else { + for (AnnotatedTypeVariable atv : type.getTypeVariables()) { + sj.add(visit(atv, visiting)); + } } - sb.append(sj.toString()); + sb.append(sj); } - if (type.getReturnType() != null) { + if (type.returnType != null) { sb.append(visit(type.getReturnType(), visiting)); } else { - sb.append(""); + sb.append("/*Return type not initialized*/"); } sb.append(' '); if (type.getElement() != null) { @@ -326,20 +340,16 @@ public String visitExecutable(AnnotatedExecutableType type, Set 0) { sb.append(", "); } @@ -351,7 +361,9 @@ public String visitExecutable(AnnotatedExecutableType type, Set visit AnnotatedArrayType array = type; AnnotatedTypeMirror component; while (true) { - component = array.getComponentType(); + component = array.componentType; if (!array.getAnnotations().isEmpty()) { sb.append(' '); sb.append( @@ -376,6 +388,9 @@ public String visitArray(AnnotatedArrayType type, Set visit } sb.append("[]"); if (!(component instanceof AnnotatedArrayType)) { + if (component == null) { + sb.insert(0, "/*Not Initialized*/"); + } sb.insert(0, visit(component, visiting)); break; } From a00bc0174b607a668935d430915095c08a7477f9 Mon Sep 17 00:00:00 2001 From: Pratik Bhusal Date: Thu, 25 Jan 2024 16:53:44 -0600 Subject: [PATCH 035/173] Mention JSpecify annotations --- docs/manual/contributors.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index 0a75b249d6e..632e136b52a 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -107,6 +107,7 @@ Paulo Barros, Philip Lai, Piyush Jha, +Pratik Bhusal, Prionti Nasir, Priti Chattopadhyay, Rashmi Mudduluru, From 8002ec84869d1f86ffa944dd378deb80581b2b62 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 25 Jan 2024 15:03:27 -0800 Subject: [PATCH 036/173] Don't update org.apache.spark:spark-sql_2.12 --- .github/renovate.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/renovate.json b/.github/renovate.json index 8a198f693d5..4f37f927ceb 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -13,5 +13,10 @@ "matchPackageNames": ["com.amazonaws:aws-java-sdk-bom"], "schedule": ["before 3am on the first day of the month"] } + { + "_comment": "We want specifically version 3.3.2, which caused a crash before", + "matchPackageNames": ["org.apache.spark:spark-sql_2.12"], + "enabled": false + } ] } From ae4d7045856f4082bd92793235c7fe5ff6934da0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 25 Jan 2024 15:23:25 -0800 Subject: [PATCH 037/173] Fix Renovate configuration --- .github/renovate.json | 2 +- build.gradle | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 4f37f927ceb..b5819af8329 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -12,7 +12,7 @@ { "matchPackageNames": ["com.amazonaws:aws-java-sdk-bom"], "schedule": ["before 3am on the first day of the month"] - } + }, { "_comment": "We want specifically version 3.3.2, which caused a crash before", "matchPackageNames": ["org.apache.spark:spark-sql_2.12"], diff --git a/build.gradle b/build.gradle index 3a6f4fde209..e6df4726d01 100644 --- a/build.gradle +++ b/build.gradle @@ -248,16 +248,17 @@ allprojects { currentProj -> def perProjectDoNotFormat = [ 'checker': [ 'bin-devel/.plume-scripts/**', + 'dist/**', 'tests/ainfer-*/annotated/*', - 'tests/nullness-javac-errors/*', - 'tests/calledmethods-delomboked/*', 'tests/build/**', - 'dist/**' + 'tests/calledmethods-delomboked/*', + 'tests/nullness-javac-errors/*', + 'tests/returnsreceiverdelomboked/*', ], 'dataflow': ['manual/examples/'], 'framework': [ + 'tests/build/**', 'tests/returnsrcvrdelomboked/*', - 'tests/build/**' ], ] spotless { From 208998aaaaa82935bf940b60bf844a95b3aae0e8 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 25 Jan 2024 15:24:58 -0800 Subject: [PATCH 038/173] Fix Renovate configuration --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index b5819af8329..69151a9046b 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -14,7 +14,7 @@ "schedule": ["before 3am on the first day of the month"] }, { - "_comment": "We want specifically version 3.3.2, which caused a crash before", + "description": "We want specifically version 3.3.2, which caused a crash before", "matchPackageNames": ["org.apache.spark:spark-sql_2.12"], "enabled": false } From af018488599da82bdfa38732dffb872c3be533da Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Fri, 26 Jan 2024 08:41:10 -0800 Subject: [PATCH 039/173] Disable parallel tests on CI to address flakiness (#6422) --- build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e6df4726d01..87a3c0e6f06 100644 --- a/build.gradle +++ b/build.gradle @@ -1172,7 +1172,11 @@ subprojects { jvmArgs += compilerArgsForRunningCF } - maxParallelForks = Integer.MAX_VALUE + // Run tests in parallel, except on CI where it seems to lead to flaky failures. + // The TF_BUILD environment variable is set to 'True' for jobs running on Azure Pipelines. + if (!System.getenv('TF_BUILD')?.equals('True')) { + maxParallelForks = Integer.MAX_VALUE + } if (project.name.is('checker')) { dependsOn('assembleForJavac') From 054d9eddc23c1272dc7d1ab056c60bf4e4b575c0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 27 Jan 2024 18:28:25 -0800 Subject: [PATCH 040/173] Get `git-clone-related` from git-scripts repository, not plume-scripts --- build.gradle | 1 + checker/bin-devel/checkout-historical.sh | 12 ++++++++++++ docs/developer/release/README-release-process.html | 4 ++-- docs/developer/release/release_vars.py | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 87a3c0e6f06..c43b1aa26e8 100644 --- a/build.gradle +++ b/build.gradle @@ -247,6 +247,7 @@ allprojects { currentProj -> // patterns of files to skip for individual sub-projects def perProjectDoNotFormat = [ 'checker': [ + 'bin-devel/.git-scripts/**', 'bin-devel/.plume-scripts/**', 'dist/**', 'tests/ainfer-*/annotated/*', diff --git a/checker/bin-devel/checkout-historical.sh b/checker/bin-devel/checkout-historical.sh index dab417805cf..47507737948 100755 --- a/checker/bin-devel/checkout-historical.sh +++ b/checker/bin-devel/checkout-historical.sh @@ -85,6 +85,18 @@ echo "Commit ${commit_sha}, date ${commit_date}" git checkout -B __merge_eval__ +# Initial commit is September 2023 +echo "git-scripts" +GIT_SCRIPTS="checker/bin-devel/.git-scripts" +if [ ! -d "$GIT_SCRIPTS" ] ; then + git clone -q https://github.com/plume-lib/git-scripts.git "${GIT_SCRIPTS}" +fi +COMMIT="$(cd "${GIT_SCRIPTS}" && git rev-list -n 1 --first-parent --before="${commit_date}" master)" +if [ -n "${COMMIT}" ] ; then + # COMMIT is non-empty + (cd "${GIT_SCRIPTS}" && git checkout -B __merge_eval__ "${COMMIT}") +fi + # Initial commit is June 2018 echo "plume-scripts" PLUME_SCRIPTS="checker/bin-devel/.plume-scripts" diff --git a/docs/developer/release/README-release-process.html b/docs/developer/release/README-release-process.html index 3932425d042..03bddf26be1 100644 --- a/docs/developer/release/README-release-process.html +++ b/docs/developer/release/README-release-process.html @@ -446,7 +446,7 @@

          File Layout

          build Contains repositories for: - annotation-tools, checker-framework, plume-bib, plume-scripts
          + annotation-tools, checker-framework, git-scripts, plume-bib, plume-scripts
          These repositories are used to build the Checker Framework and its dependencies. @@ -454,7 +454,7 @@

          File Layout

          interm Contains repositories for: - annotation-tools, checker-framework, plume-bib, plume-scripts
          + annotation-tools, checker-framework, git-scripts, plume-bib, plume-scripts
          The repositories in build are clones of repositories in interm. The repositories in interm are clones of the GitHub repositories. This is so that we can test and push the release to the interm repositories then check the diff --git a/docs/developer/release/release_vars.py b/docs/developer/release/release_vars.py index 16b98f6b107..ea46216dcd3 100755 --- a/docs/developer/release/release_vars.py +++ b/docs/developer/release/release_vars.py @@ -88,6 +88,7 @@ def execute(command_args, halt_if_fail=True, capture_output=False, working_dir=N # The central repositories for Checker Framework related projects LIVE_ANNO_REPO = "git@github.com:eisop/annotation-tools.git" LIVE_CHECKER_REPO = "git@github.com:eisop/checker-framework.git" +GIT_SCRIPTS_REPO = "https://github.com/eisop-plume-lib/git-scripts" PLUME_SCRIPTS_REPO = "https://github.com/eisop-plume-lib/plume-scripts" CHECKLINK_REPO = "https://github.com/eisop-plume-lib/checklink" PLUME_BIB_REPO = "https://github.com/mernst/plume-bib" @@ -111,6 +112,7 @@ def execute(command_args, halt_if_fail=True, capture_output=False, working_dir=N ANNO_TOOLS = os.path.join(BUILD_DIR, "annotation-tools") ANNO_FILE_UTILITIES = os.path.join(ANNO_TOOLS, "annotation-file-utilities") +GIT_SCRIPTS = os.path.join(BUILD_DIR, "git-scripts") PLUME_SCRIPTS = os.path.join(BUILD_DIR, "plume-scripts") CHECKLINK = os.path.join(BUILD_DIR, "checklink") PLUME_BIB = os.path.join(BUILD_DIR, "plume-bib") From 6878ac8887397b6d64c019c43077c8338f14c6b0 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Sat, 27 Jan 2024 21:43:46 -0800 Subject: [PATCH 041/173] Add manual section on RLC inference (#6340) --- docs/CHANGELOG.md | 3 +++ docs/manual/resource-leak-checker.tex | 17 +++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8b1440a1c40..1da27d0fb8d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,9 @@ Method, constructor, lambda, and method reference type inference has been greatly improved. The `-AconservativeUninferredTypeArguments` option is no longer necessary and has been removed. +A specialized inference algorithm for the Resource Leak Checker runs +automatically as part of whole-program inference. + **Implementation details:** **Closed issues:** diff --git a/docs/manual/resource-leak-checker.tex b/docs/manual/resource-leak-checker.tex index 248ea0d0231..3bca9f041c7 100644 --- a/docs/manual/resource-leak-checker.tex +++ b/docs/manual/resource-leak-checker.tex @@ -632,16 +632,13 @@ then verify the code using a method other than the Resource Leak Checker. -%% TODO: Uncomment when the feature is ready to be publicized. -% \sectionAndLabel{Resource Leak Checker Annotation Inference Algorithm}{resource-leak-checker-inference-algo} -% -% We are developing a special inference mechanism to infer annotations for -% the Resource Leak Checker. By default, this mechanism operates when the -% -Ainfer flag is enabled. To utilize the whole-program inference mechanism outlined in -% section \ref{whole-program-inference} in addition to this mechanism for the Resource -% Leak Checker and its sub-checkers, you should provide the \<-AenableWpiForRlc> flag. -% -% The paper \href={https://arxiv.org/pdf/2306.11953.pdf}{Automatic Inference of Resource Leak Specifications}, published at OOPSLA 2023, gives more details on the inference algorithm. Currently, only the first phase of the algorithm described in the paper is available, and the complete implementation will be available soon. +\sectionAndLabel{Resource Leak Checker annotation inference algorithm}{resource-leak-checker-inference-algo} + +The Resource Leak Checker uses a specialized algorithm to infer annotations +when whole program inference (WPI, Section~\ref{whole-program-inference}) +is enabled. The algorithm is described in the paper ``Inference of +Resource Management Specifications''~\cite{ShadabGTEKLLS2023} (OOPSLA 2023, +\myurl{https://homes.cs.washington.edu/~mernst/pubs/resource-inference-oopsla2023-abstract.html}). % LocalWords: de subchecker OutputStream MustCall MustCallUnknown RAII Un From 2ce4779db21694431a3e840ce7d83369a4375edf Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 29 Jan 2024 10:39:42 -0800 Subject: [PATCH 042/173] Fix spotless rule (#6423) --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c43b1aa26e8..98dd012fb1e 100644 --- a/build.gradle +++ b/build.gradle @@ -254,12 +254,11 @@ allprojects { currentProj -> 'tests/build/**', 'tests/calledmethods-delomboked/*', 'tests/nullness-javac-errors/*', - 'tests/returnsreceiverdelomboked/*', ], 'dataflow': ['manual/examples/'], 'framework': [ 'tests/build/**', - 'tests/returnsrcvrdelomboked/*', + 'tests/returnsreceiverdelomboked/*', ], ] spotless { From 9e94ec610eee6292cf78c78ffe9dfa7b327a863c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 29 Jan 2024 19:02:15 -0800 Subject: [PATCH 043/173] Annotations for org.apache.logging.log4j.core (no subpackages) --- checker/jtreg/nullness/issue824/Class2.out | 3 - .../checker/nullness/NullnessChecker.java | 1 + .../checker/nullness/log4j.astub | 472 ++++++++++++++++++ docs/manual/annotating-libraries.tex | 4 +- 4 files changed, 475 insertions(+), 5 deletions(-) create mode 100644 checker/src/main/java/org/checkerframework/checker/nullness/log4j.astub diff --git a/checker/jtreg/nullness/issue824/Class2.out b/checker/jtreg/nullness/issue824/Class2.out index bc751a73c8b..d569cb18958 100644 --- a/checker/jtreg/nullness/issue824/Class2.out +++ b/checker/jtreg/nullness/issue824/Class2.out @@ -1,5 +1,3 @@ -- compiler.warn.proc.messager: junit-assertions.astub:(line 1,col 1): Package not found: org.junit.jupiter.api -- compiler.warn.proc.messager: junit-assertions.astub:(line 14,col 1): Type not found: org.junit.jupiter.api.Assertions Class2.java:14:39: compiler.err.proc.messager: (type.argument) Class2.java:15:20: compiler.err.proc.messager: (type.argument) Class2.java:15:45: compiler.err.proc.messager: (type.argument) @@ -9,4 +7,3 @@ Class2.java:20:26: compiler.err.proc.messager: (argument) Class2.java:24:14: compiler.err.proc.messager: (override.return) Class2.java:25:33: compiler.err.proc.messager: (type.arguments.not.inferred) 8 errors -2 warnings diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java index 9ec35cdece4..8c0e2ba95b1 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessChecker.java @@ -52,6 +52,7 @@ "jspecifyNullMarkedAlias", "conservativeArgumentNullnessAfterInvocation" }) +@StubFiles({"junit-assertions.astub", "log4j.astub"}) public class NullnessChecker extends InitializationChecker { /** Should we be strict about initialization of {@link MonotonicNonNull} variables? */ diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/log4j.astub b/checker/src/main/java/org/checkerframework/checker/nullness/log4j.astub new file mode 100644 index 00000000000..1eb8c35453e --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/nullness/log4j.astub @@ -0,0 +1,472 @@ +package org.apache.logging.log4j.core; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.TimeUnit; +import java.util.EventListener; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationListener; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.LocationAwareReliabilityStrategy; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.apache.logging.log4j.core.config.ReliabilityStrategy; +import org.apache.logging.log4j.core.filter.CompositeFilter; +import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.ThreadContextDataInjector; +import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.core.jmx.Server; +import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.Encoder; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.util.Cancellable; +import org.apache.logging.log4j.core.util.ExecutorServices; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.apache.logging.log4j.spi.LoggerContextShutdownAware; +import org.apache.logging.log4j.spi.LoggerContextShutdownEnabled; +import org.apache.logging.log4j.spi.LoggerRegistry; +import org.apache.logging.log4j.spi.Terminable; +import org.apache.logging.log4j.spi.ThreadContextMapFactory; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.ThreadContext.ContextStack; +import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.util.EnglishEnums; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.Supplier; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.PolyNull; +import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER; + +public class AbstractLifeCycle implements LifeCycle2 { + protected boolean equalsImpl(final @Nullable Object obj); + protected boolean stop(final @Nullable Future future); +} + +public abstract class AbstractLogEvent implements LogEvent { + public @Nullable ReadOnlyStringMap getContextData(); + public @Nullable Level getLevel(); + public @Nullable String getLoggerFqcn(); + public @Nullable String getLoggerName(); + public @Nullable Marker getMarker(); + public @Nullable Message getMessage(); + public @Nullable StackTraceElement getSource(); + public @Nullable String getThreadName(); + public @Nullable Throwable getThrown(); + public @Nullable ThrowableProxy getThrownProxy(); +} + +public interface Appender extends LifeCycle { + @Nullable String getName(); + @Nullable Layout getLayout(); +} + +public interface ContextDataInjector { + StringMap injectContextData(final @Nullable List properties, final StringMap reusable); +} + +public class Core { +} + +public class DefaultLoggerContextAccessor implements LoggerContextAccessor { +} + +public interface ErrorHandler { +} + +public interface Filter extends LifeCycle { + enum Result { + ACCEPT, + NEUTRAL, + DENY; + + public static @PolyNull Result toResult(final @PolyNull String name); + } + + Result filter(Logger logger, Level level, @Nullable Marker marker, String msg, Object @Nullable ... params); + Result filter(Logger logger, Level level, @Nullable Marker marker, String message, Object p0); + Result filter(Logger logger, Level level, @Nullable Marker marker, String message, Object p0, Object p1); + Result filter(Logger logger, Level level, @Nullable Marker marker, String message, Object p0, Object p1, Object p2); + Result filter( + Logger logger, Level level, @Nullable Marker marker, String message, Object p0, Object p1, Object p2, Object p3); + Result filter( + Logger logger, + Level level, + @Nullable Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4); + Result filter( + Logger logger, + Level level, + @Nullable Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5); + Result filter( + Logger logger, + Level level, + @Nullable Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6); + Result filter( + Logger logger, + Level level, + @Nullable Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7); + Result filter( + Logger logger, + Level level, + @Nullable Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); + Result filter( + Logger logger, + Level level, + @Nullable Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8, + Object p9); + Result filter(Logger logger, Level level, @Nullable Marker marker, Object msg, @Nullable Throwable t); + Result filter(Logger logger, Level level, @Nullable Marker marker, Message msg, @Nullable Throwable t); + default Result filter(Logger logger, Level level, @Nullable Marker marker, String msg); +} + +public interface Layout extends Encoder { +} + +public interface LifeCycle { + enum State { + INITIALIZING, + INITIALIZED, + STARTING, + STARTED, + STOPPING, + STOPPED + } +} + +public interface LifeCycle2 extends LifeCycle { +} + +public interface LogEvent extends Serializable { + @Nullable String getLoggerName(); + @Nullable Marker getMarker(); + @Nullable StackTraceElement getSource(); + @Nullable String getThreadName(); + @Nullable Throwable getThrown(); + @Nullable ThrowableProxy getThrownProxy(); +} + +public class LogEventListener implements EventListener { + public void log(final @Nullable LogEvent event); +} + +public class Logger extends AbstractLogger implements Supplier { + private final @Nullable LoggerContext context; + public @Nullable Logger getParent(); + public synchronized void setLevel(final @Nullable Level level); + public void logMessage( + final String fqcn, final Level level, final @Nullable Marker marker, final @Nullable Message message, final Throwable t); + protected void log( + final Level level, + final @Nullable Marker marker, + final String fqcn, + final StackTraceElement location, + final Message message, + final Throwable throwable); + public boolean isEnabled(final Level level, final @Nullable Marker marker, final String message, final Throwable t); + public boolean isEnabled(final Level level, final @Nullable Marker marker, final String message); + public boolean isEnabled(final Level level, final @Nullable Marker marker, final String message, final Object... params); + public boolean isEnabled(final Level level, final @Nullable Marker marker, final String message, final Object p0); + public boolean isEnabled( + final Level level, final @Nullable Marker marker, final String message, final Object p0, final Object p1); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7, + final Object p8); + public boolean isEnabled( + final Level level, + final @Nullable Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7, + final Object p8, + final Object p9); + public boolean isEnabled(final Level level, final @Nullable Marker marker, final CharSequence message, final Throwable t); + public boolean isEnabled(final Level level, final @Nullable Marker marker, final Object message, final Throwable t); + public boolean isEnabled(final Level level, final @Nullable Marker marker, final Message message, final Throwable t); + + protected class PrivateConfig { + boolean filter(final @Nullable Level level, final @Nullable Marker marker, final String msg); + boolean filter(final @Nullable Level level, final @Nullable Marker marker, final String msg, final Throwable t); + boolean filter(final @Nullable Level level, final @Nullable Marker marker, final String msg, final Object... p1); + boolean filter(final Level level, final @Nullable Marker marker, final String msg, final Object p0); + boolean filter(final @Nullable Level level, final @Nullable Marker marker, final String msg, final Object p0, final Object p1); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7, + final Object p8); + boolean filter( + final @Nullable Level level, + final @Nullable Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7, + final Object p8, + final Object p9); + boolean filter(final @Nullable Level level, final @Nullable Marker marker, final CharSequence msg, final Throwable t); + boolean filter(final @Nullable Level level, final @Nullable Marker marker, final Object msg, final Throwable t); + boolean filter(final @Nullable Level level, final @Nullable Marker marker, final Message msg, final Throwable t); + } + public boolean equals(final @Nullable Object o); +} + +public class LoggerContext extends AbstractLifeCycle + implements org.apache.logging.log4j.spi.LoggerContext, + AutoCloseable, + Terminable, + ConfigurationListener, + LoggerContextShutdownEnabled { + public LoggerContext(final String name, final @Nullable Object externalContext); + public LoggerContext(final String name, final @Nullable Object externalContext, final @Nullable URI configLocn); + public LoggerContext(final String name, final @Nullable Object externalContext, final @Nullable String configLocn); + public static LoggerContext getContext( + final @Nullable ClassLoader loader, final boolean currentContext, final URI configLocation); + public void setExternalContext(final @Nullable Object context); + public Logger getLogger(final String name, final @Nullable MessageFactory messageFactory); + public @Nullable URI getConfigLocation(); +} + +public interface LoggerContextAccessor { +} + +public interface StringLayout extends Layout { +} + +public class Version { +} diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index 9a7d7ef4e0d..d94d9cea306 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -875,7 +875,7 @@ file provided on the command line, but does not warn about built-in stub files. These command-line options turn the warnings on or off (respectively) for all stub files. \\ - The \<@NoStubParserWarning> annotation on a package or type in a stub file + The \<@NoAnnotationFileParserWarning> annotation on a package or type in a stub file causes no warning to be issued for that package or type, regardless of the command-line options. @@ -1094,6 +1094,6 @@ % LocalWords: AstubWarnIfNotFoundIgnoresClasses AmergeStubsWithSource % LocalWords: AstubWarnIfRedundantWithBytecode JavaStubifier StubFiles % LocalWords: BaseTypeChecker ImageObserver Graphics2D AstubWarnNote -% LocalWords: AstubNoWarnIfNotFound NoStubParserWarning AstubWarn Werror +% LocalWords: AstubNoWarnIfNotFound AstubWarn Werror % LocalWords: Aajava substring outerpackage innerpackage myMethod MyClass % LocalWords: InsertAjavaAnnotations From b348f05c5a875509c91ce5d86a0435e1d0c5567d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 30 Jan 2024 09:30:45 -0800 Subject: [PATCH 044/173] Documentation (#6426) --- .../resourceleak/ResourceLeakVisitor.java | 6 ++++-- .../framework/source/SourceChecker.java | 3 ++- .../framework/util/Contract.java | 17 +++++++++++++---- .../util/DefaultContractsFromMethod.java | 9 +++++++-- .../javacutil/TypeSystemError.java | 6 +++--- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java index 5b72bc66aa4..2e10d0b1e1b 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java @@ -281,9 +281,11 @@ private void checkMustCallAliasAnnoMismatch( String message = isMustCallAliasAnnotationOnParameter ? String.format( - "there is no @MustCallAlias annotation on %s, even though the parameter %s is annotated with @MustCallAlias", + "there is no @MustCallAlias annotation on %s, even though the parameter %s is" + + " annotated with @MustCallAlias", locationOfCheck, paramWithMustCallAliasAnno) - : "no parameter has a @MustCallAlias annotation, even though the return type is annotated with @MustCallAlias"; + : "no parameter has a @MustCallAlias annotation, even though the return type is" + + " annotated with @MustCallAlias"; checker.reportWarning(tree, "mustcallalias.method.return.and.param", message); } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index a8c7f85e102..836653a5ccb 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -1257,7 +1257,8 @@ private void report( } else if (preciseSource instanceof Tree) { printOrStoreMessage(kind, messageText, (Tree) preciseSource, currentRoot); } else { - throw new BugInCF("invalid position source, class=" + preciseSource.getClass()); + throw new BugInCF( + "invalid position source of class " + preciseSource.getClass() + ": " + preciseSource); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/Contract.java b/framework/src/main/java/org/checkerframework/framework/util/Contract.java index a69da26a177..3883adc4ecd 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Contract.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Contract.java @@ -73,13 +73,22 @@ public enum Kind { /** Used for constructing error messages. */ public final String errorKey; - /** The meta-annotation identifying annotations of this kind. */ + /** + * The meta-annotation identifying annotations of this kind: PreconditionAnnotation, + * PostconditionAnnotation, or ConditionalPostconditionAnnotation. + */ public final Class metaAnnotation; - /** The built-in framework qualifier for this contract. */ + /** + * The built-in framework qualifier for this contract: RequiresQualifier, EnsuresQualifier, or + * EnsuresQualifierIf. + */ public final Class frameworkContractClass; - /** The built-in framework qualifier for repeated occurrences of this contract. */ + /** + * The built-in framework qualifier for repeated occurrences of this contract: + * RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. + */ public final Class frameworkContractListClass; /** @@ -125,7 +134,7 @@ private Contract( } /** - * Creates a new Contract. + * Creates a new {@code Contract}. * * @param kind precondition, postcondition, or conditional postcondition * @param expressionString the Java expression that should have a type qualifier diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java index 492b7bfd792..9b38a840952 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java @@ -125,12 +125,14 @@ private Set getContracts( ExecutableElement executableElement, Contract.Kind kind, Class clazz) { Set result = new LinkedHashSet<>(); // Check for a single framework-defined contract annotation. + // The result is RequiresQualifier, EnsuresQualifier, EnsuresQualifierIf, or null. AnnotationMirror frameworkContractAnno = atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractClass); result.addAll(getContract(kind, frameworkContractAnno, clazz)); // Check for a framework-defined wrapper around contract annotations. // The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. + // Add its elements to `result`. AnnotationMirror frameworkContractListAnno = atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractListClass); if (frameworkContractListAnno != null) { @@ -141,12 +143,15 @@ private Set getContracts( } } - // Check for type-system specific annotations. + // Check for type-system specific annotations. These are the annotations that are + // meta-annotated by `kind.metaAnnotation`, which is PreconditionAnnotation, + // PostconditionAnnotation, or ConditionalPostconditionAnnotation. List> declAnnotations = atypeFactory.getDeclAnnotationWithMetaAnnotation(executableElement, kind.metaAnnotation); for (IPair r : declAnnotations) { AnnotationMirror anno = r.first; - // contractAnno is the meta-annotation on anno. + // contractAnno is the meta-annotation on anno, such as PreconditionAnnotation, + // PostconditionAnnotation, or ConditionalPostconditionAnnotation. AnnotationMirror contractAnno = r.second; AnnotationMirror enforcedQualifier = getQualifierEnforcedByContractAnnotation(contractAnno, anno); diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java index a6de9d49c70..5743e99fc85 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java @@ -4,11 +4,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; /** - * Exception type indicating a mistake by a type system built using the Checker Framework. For + * Exception type indicating a mistake in a type system built using the Checker Framework. For * example, misusing a meta-annotation on a qualifier. * - *

          To indicate a bug in the framework, use {@link BugInCF}. To indicate that an end user made a - * mistake, use {@link UserError}. + *

          To indicate a bug in the framework, use {@link BugInCF}. To indicate that an end user (a + * programmer running a type-checker) made a mistake, use {@link UserError}. */ @SuppressWarnings("serial") public class TypeSystemError extends RuntimeException { From c8f056dfad2a1d4f493a9ee78eb42ed6948d087d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 30 Jan 2024 09:31:10 -0800 Subject: [PATCH 045/173] Rename one override of `getContracts` to `getContractsOfKind` (#6427) --- .../framework/util/DefaultContractsFromMethod.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java index 9b38a840952..307cc128483 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java @@ -79,7 +79,8 @@ public Set getContracts(ExecutableElement executableElement) { */ @Override public Set getPreconditions(ExecutableElement executableElement) { - return getContracts(executableElement, Contract.Kind.PRECONDITION, Contract.Precondition.class); + return getContractsOfKind( + executableElement, Contract.Kind.PRECONDITION, Contract.Precondition.class); } /** @@ -90,7 +91,7 @@ public Set getPreconditions(ExecutableElement executableE */ @Override public Set getPostconditions(ExecutableElement executableElement) { - return getContracts( + return getContractsOfKind( executableElement, Contract.Kind.POSTCONDITION, Contract.Postcondition.class); } @@ -103,7 +104,7 @@ public Set getPostconditions(ExecutableElement executabl @Override public Set getConditionalPostconditions( ExecutableElement methodElement) { - return getContracts( + return getContractsOfKind( methodElement, Contract.Kind.CONDITIONALPOSTCONDITION, Contract.ConditionalPostcondition.class); @@ -121,7 +122,7 @@ public Set getConditionalPostconditions( * @param clazz the class to determine the return type * @return the contracts on {@code executableElement} */ - private Set getContracts( + private Set getContractsOfKind( ExecutableElement executableElement, Contract.Kind kind, Class clazz) { Set result = new LinkedHashSet<>(); // Check for a single framework-defined contract annotation. From 03377abd49fe12f4efc751d17d670e6676d863ab Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 30 Jan 2024 15:34:15 -0800 Subject: [PATCH 046/173] Don't permit null to be passed to `getContract()` (#6428) --- .../framework/util/DefaultContractsFromMethod.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java index 307cc128483..5e8582387b8 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultContractsFromMethod.java @@ -129,7 +129,9 @@ private Set getContractsOfKind( // The result is RequiresQualifier, EnsuresQualifier, EnsuresQualifierIf, or null. AnnotationMirror frameworkContractAnno = atypeFactory.getDeclAnnotation(executableElement, kind.frameworkContractClass); - result.addAll(getContract(kind, frameworkContractAnno, clazz)); + if (frameworkContractAnno != null) { + result.addAll(getContract(kind, frameworkContractAnno, clazz)); + } // Check for a framework-defined wrapper around contract annotations. // The result is RequiresQualifier.List, EnsuresQualifier.List, or EnsuresQualifierIf.List. @@ -178,14 +180,14 @@ private Set getContractsOfKind( * * @param the type of {@link Contract} to return * @param kind the kind of {@code contractAnnotation} - * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, {@link - * EnsuresQualifierIf}, or null + * @param contractAnnotation a {@link RequiresQualifier}, {@link EnsuresQualifier}, or {@link + * EnsuresQualifierIf} * @param clazz the class to determine the return type * @return the contracts expressed by the given annotation, or the empty set if the argument is * null */ private Set getContract( - Contract.Kind kind, @Nullable AnnotationMirror contractAnnotation, Class clazz) { + Contract.Kind kind, AnnotationMirror contractAnnotation, Class clazz) { if (contractAnnotation == null) { return Collections.emptySet(); } From 6232da1a09c427400b0d592ed72c084477dd872f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 30 Jan 2024 17:31:23 -0800 Subject: [PATCH 047/173] Define `isTop(AnnotationMirror)` (#6431) --- .../framework/type/AnnotatedTypeFactory.java | 10 ++++++++++ .../framework/type/QualifierHierarchy.java | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 80fbeb1b686..b293312e3ec 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -1122,6 +1122,16 @@ public final QualifierHierarchy getQualifierHierarchy() { return qualHierarchy; } + /** + * Returns true if the given qualifer is one of the top annotations for the qualifer hierarchy. + * + * @param qualifier a type qualifier + * @return true if the given qualifer is one of the top annotations for the qualifer hierarchy + */ + public final boolean isTop(AnnotationMirror qualifier) { + return qualHierarchy.isTop(qualifier); + } + /** * Creates the type hierarchy to be used by this factory. * diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java index daa1950ddde..c92cf034997 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java @@ -11,6 +11,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; import org.plumelib.util.StringsPlume; @@ -72,6 +73,16 @@ public int getWidth() { */ public abstract AnnotationMirrorSet getTopAnnotations(); + /** + * Returns true if the given qualifer is one of the top annotations for this qualifer hierarchy. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return true if the given qualifer is one of the top annotations for this qualifer hierarchy + */ + public boolean isTop(AnnotationMirror qualifier) { + return AnnotationUtils.containsSame(getTopAnnotations(), qualifier); + } + /** * Return the top qualifier for the given qualifier, that is, the qualifier that is a supertype of * {@code qualifier} but no further supertypes exist. From 181d56ba34ca49906351c6120bbd2cf7821f812a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 31 Jan 2024 08:01:26 -0800 Subject: [PATCH 048/173] Don't permit source to be null --- .../checkerframework/framework/source/SourceChecker.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 836653a5ccb..c60231254c0 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -1189,7 +1189,7 @@ public void report(@Nullable Object source, DiagMessage d) { * Reports a diagnostic message. By default, it prints it to the screen via the compiler's * internal messager; however, it might also store it for later output. * - * @param source the source position information; may be an Element, a Tree, or null + * @param source the source position information; may be an Element or a Tree * @param kind the type of message * @param messageKey the message key * @param args arguments for interpolation in the string corresponding to the given message key @@ -1199,10 +1199,7 @@ public void report(@Nullable Object source, DiagMessage d) { // @FormatMethod @SuppressWarnings("formatter:format.string.invalid") // arg is a format string or a property key private void report( - @Nullable Object source, - Diagnostic.Kind kind, - @CompilerMessageKey String messageKey, - Object... args) { + Object source, Diagnostic.Kind kind, @CompilerMessageKey String messageKey, Object... args) { assert messagesProperties != null : "null messagesProperties"; if (shouldSuppressWarnings(source, messageKey)) { From e06df16aeb2673e453d9172080d1d6cb60a80156 Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Mon, 5 Feb 2024 08:30:08 -0800 Subject: [PATCH 049/173] Fix issue 6433 (#6439) Co-authored-by: Werner Dietl --- .../dataflow/cfg/builder/CFGTranslationPhaseThree.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java index 9fc0616e865..47e261d14f9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java @@ -167,7 +167,8 @@ protected static void mergeConsecutiveBlocks(ControlFlowGraph cfg) { if (succ.getType() == BlockType.REGULAR_BLOCK) { RegularBlockImpl rs = (RegularBlockImpl) succ; if (rs.getRegularSuccessor() == rs) { - // An infinite loop, do not try to merge. + // Do not attempt to merge a block with a self edge, as it leads to non-termination in + // the merging algorithm. break; } if (rs.getPredecessors().size() == 1) { From deddf76e856c137c016bcc750498700ffd5c2250 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:24:38 +0000 Subject: [PATCH 050/173] Update dependency com.amazonaws:aws-java-sdk-bom to v1.12.651 (#6436) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- checker/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checker/build.gradle b/checker/build.gradle index 63aba6dd520..421614242d4 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -80,7 +80,7 @@ dependencies { testImplementation 'com.amazonaws:aws-java-sdk-ec2' testImplementation 'com.amazonaws:aws-java-sdk-kms' // The AWS SDK is used for testing the Called Methods Checker. - testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.634') + testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.651') // For the Resource Leak Checker's support for JavaEE. testImplementation 'javax.servlet:javax.servlet-api:4.0.1' // For the Resource Leak Checker's support for IOUtils. From 830f686d35a48d974ba6e3c603206e9484190f58 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 05:06:46 +0000 Subject: [PATCH 051/173] Update dependency gradle to v8.6 (#6440) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From e64bfd0563b9fd7a2df4c62ae09aeabe11eeea8d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 6 Feb 2024 07:03:29 -0800 Subject: [PATCH 052/173] Ignore VS Code files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 92d3d0cd76e..de0719952ee 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ tags *.swp *.orig .DS_Store +dot_files # Eclipse files .metadata @@ -20,7 +21,6 @@ tags .project .externalToolBuilders .settings -dot_files # Intellij files .idea From 7c485f8fa1fc66816d140daf720df6ceacbd3325 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 6 Feb 2024 15:23:35 -0800 Subject: [PATCH 053/173] Rename variable --- checker/bin/infer-and-annotate.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/checker/bin/infer-and-annotate.sh b/checker/bin/infer-and-annotate.sh index e1adb561fda..452b61a5c1d 100755 --- a/checker/bin/infer-and-annotate.sh +++ b/checker/bin/infer-and-annotate.sh @@ -48,9 +48,9 @@ PREV_ITERATION_DIR=build/prev-whole-program-inference debug= interactive= -# For debugging +# For debugging. # debug=1 -# Require user confirmation before running each command +# Require user confirmation before running each command. # interactive=1 CHECKERBIN=$(dirname "$0") @@ -82,9 +82,9 @@ read_input() { esac done - # First two arguments are processor and cp. + # First two arguments are processor and classpath. processor=$1 - cp=$2 + classpath=$2 shift shift @@ -131,13 +131,13 @@ infer_and_annotate() { mkdir -p $WHOLE_PROGRAM_INFERENCE_DIR # Runs CF's javac - command="$CHECKERBIN/javac -d $TEMP_DIR/ -cp $cp -processor $processor -Ainfer=jaifs -Awarns -Xmaxwarns 10000 ${extra_args[*]} ${java_files[*]}" + command="$CHECKERBIN/javac -d $TEMP_DIR/ -cp $classpath -processor $processor -Ainfer=jaifs -Awarns -Xmaxwarns 10000 ${extra_args[*]} ${java_files[*]}" echo "About to run: ${command}" if [ "$interactive" ]; then echo "Press any key to run command... " IFS="" read -r _ fi - "$CHECKERBIN"/javac -d "$TEMP_DIR/" -cp "$cp" -processor "$processor" -Ainfer -Awarns -Xmaxwarns 10000 "${extra_args[@]}" "${java_files[@]}" || true + "$CHECKERBIN"/javac -d "$TEMP_DIR/" -cp "$classpath" -processor "$processor" -Ainfer -Awarns -Xmaxwarns 10000 "${extra_args[@]}" "${java_files[@]}" || true # Deletes .unannotated backup files. This is necessary otherwise the # insert-annotations-to-source tool will use this file instead of the # updated .java one. From a1e2647dd011a4365f6879855af808517a8b4492 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 7 Feb 2024 08:10:38 -0800 Subject: [PATCH 054/173] Add (passing) test case for Issue 6438 (#6441) From 20a9982059bd964a402e15428021bfb5a16e902b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 7 Feb 2024 08:38:01 -0800 Subject: [PATCH 055/173] Set classpath when calling `insert-annotations-to-source` --- checker/bin/infer-and-annotate.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/checker/bin/infer-and-annotate.sh b/checker/bin/infer-and-annotate.sh index 452b61a5c1d..0857f0b9a69 100755 --- a/checker/bin/infer-and-annotate.sh +++ b/checker/bin/infer-and-annotate.sh @@ -137,7 +137,7 @@ infer_and_annotate() { echo "Press any key to run command... " IFS="" read -r _ fi - "$CHECKERBIN"/javac -d "$TEMP_DIR/" -cp "$classpath" -processor "$processor" -Ainfer -Awarns -Xmaxwarns 10000 "${extra_args[@]}" "${java_files[@]}" || true + "$CHECKERBIN"/javac -d "$TEMP_DIR/" -cp "$classpath" -processor "$processor" -Ainfer=jaifs -Awarns -Xmaxwarns 10000 "${extra_args[@]}" "${java_files[@]}" || true # Deletes .unannotated backup files. This is necessary otherwise the # insert-annotations-to-source tool will use this file instead of the # updated .java one. @@ -146,11 +146,15 @@ infer_and_annotate() { do rm -f "${file}.unannotated" done - if [ ! "$(find $WHOLE_PROGRAM_INFERENCE_DIR -prune -empty)" ] - then - # Only insert annotations if there is at least one .jaif file. - # shellcheck disable=SC2046 - insert-annotations-to-source "${insert_to_source_args[@]}" -i $(find $WHOLE_PROGRAM_INFERENCE_DIR -name "*.jaif") "${java_files[@]}" + if [ "$(find $WHOLE_PROGRAM_INFERENCE_DIR -prune -empty)" ] ; then + echo "No .jaif files found." + else + # There is at least one .jaif file, so insert annotations. + new_jaif_files="$(find $WHOLE_PROGRAM_INFERENCE_DIR -name "*.jaif")" + command="insert-annotations-to-source -cp $classpath ${insert_to_source_args[*]} -i $new_jaif_files ${java_files[*]}" + echo "About to run: ${command}" + # shellcheck disable=SC2086 + insert-annotations-to-source -cp $classpath "${insert_to_source_args[@]}" -i $new_jaif_files "${java_files[@]}" fi # Updates DIFF_JAIF variable. # diff returns exit-value 1 when there are differences between files. From 5a20d27dc7693cf3715da12ed85bf266ef9c1d6e Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 7 Feb 2024 10:04:14 -0800 Subject: [PATCH 056/173] Clarify changelog --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1da27d0fb8d..adef21a2190 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -141,7 +141,7 @@ possibly throws an assertion. Using it can make flow-sensitive type refinement more effective. In `org.checkerframework.common.util.debug`, renamed `EmptyProcessor` to `DoNothingProcessor`. -Removed `org.checkerframework.common.util.report.DoNothingChecker`. +Removed `org.checkerframework.common.util.report.DoNothingChecker`; use `DoNothingProcessor`. Moved `ReportChecker` from `org.checkerframework.common.util.report` to `org.checkerframework.common.util.count.report`. (EISOP note: we did not follow this renaming - if anything, `counting` could be a special case of `reporting`, not the other way around.) From 3015e46047c90cd1fd8851e915aac9fb7b568bff Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Thu, 8 Feb 2024 09:49:59 -0800 Subject: [PATCH 057/173] Don't call Optional.isEmpty(), for Java 8 compatibility (#6446) --- .../WholeProgramInferenceJavaParserStorage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index 191f0054c12..829542a7698 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -747,7 +747,7 @@ private void addClass(ClassTree tree, @Nullable TypeDeclaration javaParserNod // the wrong name is computed, the worst outcome is a false positive // because WPI inferred an untrue annotation. Optional ofqn = javaParserClass.getFullyQualifiedName(); - if (ofqn.isEmpty()) { + if (!ofqn.isPresent()) { throw new BugInCF("Missing getFullyQualifiedName() for " + javaParserClass); } if ("".contentEquals(tree.getSimpleName())) { From 2ded87162845b85a5d964df093ffeec7b69729f7 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 16 Feb 2024 17:46:54 -0800 Subject: [PATCH 058/173] Support EISOP formatting --- build.gradle | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 98dd012fb1e..e8fc47190a5 100644 --- a/build.gradle +++ b/build.gradle @@ -313,8 +313,12 @@ allprojects { currentProj -> } targetExclude doNotFormat - googleJavaFormat(versions.googleJavaFormat)// .aosp() - // importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') + if (project.hasProperty('eisopFormatting')) { + googleJavaFormat(versions.googleJavaFormat).aosp() + importOrder('com', 'jdk', 'lib', 'lombok', 'org', 'java', 'javax') + } else { + googleJavaFormat(versions.googleJavaFormat) // the formatter to apply to Java files + } formatAnnotations().addTypeAnnotation("PolyInitialized").addTypeAnnotation("PolyVP").addTypeAnnotation("ReceiverDependentQual") } @@ -324,7 +328,11 @@ allprojects { currentProj -> target '**/*.gradle' targetExclude doNotFormat greclipse() // which formatter Spotless should use to format .gradle files. - indentWithSpaces(2) + if (project.hasProperty('eisopFormatting')) { + indentWithSpaces(4) + } else { + indentWithSpaces(2) + } trimTrailingWhitespace() // endWithNewline() // Don't want to end empty files with a newline } From 8d10df8d44ab58794d981a0086bb8b73dbfba7b1 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 16 Feb 2024 18:01:45 -0800 Subject: [PATCH 059/173] Use htmlpreview.github.io instead of rawgit.com --- docs/developer/developer-manual.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer/developer-manual.html b/docs/developer/developer-manual.html index eb5f82affdd..5ec91cf9529 100644 --- a/docs/developer/developer-manual.html +++ b/docs/developer/developer-manual.html @@ -477,7 +477,7 @@

          Making a Checker Framework release local version (link works from a clone) or -web version (from previous release). +web version (from previous release).

          From b8d86b36a7241de2159e0bcabfa0162e4f2e0525 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 16 Feb 2024 19:42:39 -0800 Subject: [PATCH 060/173] Shorten indented comments --- .../checker/lock/qual/PolyGuardedBy.java | 7 +++ .../calledmethods/CalledMethodsTransfer.java | 4 +- .../MustCallAnnotatedTypeFactory.java | 22 ++++----- .../MustCallConsistencyAnalyzer.java | 47 ++++++++++--------- .../resourceleak/MustCallInference.java | 26 +++++----- .../ResourceLeakAnnotatedTypeFactory.java | 6 +-- .../resourceleak/ResourceLeakChecker.java | 4 +- .../resourceleak/ResourceLeakVisitor.java | 6 +-- checker/tests/index/RandomTestLBC.java | 4 +- checker/tests/index/UpperBoundRefinement.java | 4 +- .../mustcall/PolyMustCallDifferentNames.java | 2 +- .../nullness/java8inference/Issue1630.java | 4 +- .../resourceleak/MustCallAliasNormalExit.java | 18 +++---- .../resourceleak/MustCallAliasNotThis.java | 16 +++---- .../cfg/builder/CFGTranslationPhaseOne.java | 10 ++-- .../cfg/builder/CFGTranslationPhaseThree.java | 4 +- .../cfg/visualize/CFGVisualizeLauncher.java | 6 +-- .../common/aliasing/AliasingVisitor.java | 5 +- .../common/basetype/BaseTypeChecker.java | 4 +- .../common/basetype/BaseTypeValidator.java | 3 +- .../common/basetype/BaseTypeVisitor.java | 22 ++++----- .../common/value/ValueTreeAnnotator.java | 3 +- .../common/value/ValueVisitor.java | 6 +-- .../WholeProgramInferenceImplementation.java | 8 ++-- ...holeProgramInferenceJavaParserStorage.java | 27 +++++------ .../ajava/JointJavacJavaParserVisitor.java | 12 ++--- .../framework/flow/CFAbstractTransfer.java | 7 ++- .../framework/source/SourceChecker.java | 5 +- .../stub/RemoveAnnotationsForInference.java | 5 +- .../framework/type/AnnotatedTypeFactory.java | 16 +++---- .../framework/type/AnnotatedTypeMirror.java | 6 +-- .../framework/type/TypeFromMemberVisitor.java | 12 ++--- .../util/JavaExpressionParseUtil.java | 8 ++-- .../DefaultTypeArgumentInference.java | 14 +++--- .../InvocationTypeInference.java | 22 +++++---- .../typeinference8/constraint/Typing.java | 3 +- .../types/InferenceFactory.java | 4 +- .../util/typeinference8/types/ProperType.java | 3 +- .../typeinference8/types/UseOfVariable.java | 7 ++- .../java8inference/GuavaCrash.java | 4 +- .../org/checkerframework/javacutil/Pair.java | 3 +- .../javacutil/TreePathUtil.java | 13 ++--- .../checkerframework/javacutil/TreeUtils.java | 4 +- .../javacutil/trees/TreeBuilder.java | 8 ++-- 44 files changed, 215 insertions(+), 209 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/PolyGuardedBy.java b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/PolyGuardedBy.java index 36ed882977c..10214047b82 100644 --- a/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/PolyGuardedBy.java +++ b/checker-qual/src/main/java/org/checkerframework/checker/lock/qual/PolyGuardedBy.java @@ -3,6 +3,13 @@ package org.checkerframework.checker.lock.qual; +// import java.lang.annotation.Documented; +// import java.lang.annotation.ElementType; +// import java.lang.annotation.Retention; +// import java.lang.annotation.RetentionPolicy; +// import java.lang.annotation.Target; +// +// import org.checkerframework.framework.qual.PolymorphicQualifier; /** * A polymorphic qualifier for the GuardedBy type system. diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java index c60408ccd01..1f60219fd64 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java @@ -285,8 +285,8 @@ private void handleEnsuresCalledMethodsOnException( } // NOTE: this code is a little inefficient; it creates a single-method annotation and - // calls `insertOrRefine` in a loop. Even worse, this code appears within a loop. For - // now we aren't too worried about it, since the number of + // calls `insertOrRefine` in a loop. Even worse, this code appears within a loop. + // For now we aren't too worried about it, since the number of // EnsuresCalledMethodsOnException annotations should be small. AnnotationMirror calledMethod = atypeFactory.createAccumulatorAnnotation(postcond.getMethod()); diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java index 8574f004cd1..343f7901354 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallAnnotatedTypeFactory.java @@ -259,8 +259,8 @@ protected void replace( getMustCallValueElement(), String.class, Collections.emptyList()); - // replacement only customized when parameter type has a non-empty must-call - // obligation + // Replacement is only customized when the parameter type has a non-empty + // must-call obligation. if (!extentReplacementVals.isEmpty()) { AnnotationMirror inheritableMustCall = getDeclAnnotation(typeElement, InheritableMustCall.class); @@ -273,9 +273,9 @@ protected void replace( Collections.emptyList()); if (!inheritableMustCallVals.equals(extentReplacementVals)) { // Use the must call values from the @InheritableMustCall annotation - // instead. - // This allows for wrapper types to have a must-call method with a - // different name than the must-call method for the wrapped type + // instead. This allows for wrapper types to have a must-call method + // with a different name than the must-call method for the wrapped + // type. AnnotationMirror mustCall = createMustCall(inheritableMustCallVals); realReplacements = new AnnotationMirrorMap<>(); realReplacements.put(POLY, mustCall); @@ -305,9 +305,9 @@ protected QualifierPolymorphism createQualifierPolymorphism() { private void changeNonOwningParameterTypesToTop( ExecutableElement declaration, AnnotatedExecutableType type) { // Formal parameters without a declared owning annotation are disregarded by the RLC - // _analysis_, as their @MustCall obligation is set to Top in this method. However, this - // computation is not desirable for RLC _inference_ in unannotated programs, where a goal is - // to infer and add @Owning annotations to formal parameters. + // _analysis_, as their @MustCall obligation is set to Top in this method. However, + // this computation is not desirable for RLC _inference_ in unannotated programs, + // where a goal is to infer and add @Owning annotations to formal parameters. /* NO-AFU if (getWholeProgramInference() != null && !isWpiEnabledForRLC()) { return; @@ -508,9 +508,9 @@ public MustCallTreeAnnotator(MustCallAnnotatedTypeFactory mustCallAnnotatedTypeF public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) { Element elt = TreeUtils.elementFromUse(tree); // The following changes are not desired for RLC _inference_ in unannotated programs, - // where a goal is to infer and add @Owning annotations to formal parameters. Therefore, - // if WPI is enabled, they should not be executed. - if ( // NO-AFU getWholeProgramInference() == null + // where a goal is to infer and add @Owning annotations to formal parameters. + // Therefore, if WPI is enabled, they should not be executed. + if ( // NO-AFU getWholeProgramInference() == null && elt.getKind() == ElementKind.PARAMETER && (noLightweightOwnership || getDeclAnnotation(elt, Owning.class) == null)) { if (!type.hasAnnotation(POLY)) { diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java index 27739ea9ece..0ba4ce537a8 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallConsistencyAnalyzer.java @@ -1552,13 +1552,13 @@ && varTrackedInObligations(obligations, (LocalVariableNode) receiver)) // The following code handles a special case where the field being assigned is itself // getting passed in an owning position to another method on the RHS of the assignment. // For example, if the field's type is a class whose constructor takes another instance - // of itself (such as a node in a linked list) in an owning position, re-assigning the field - // to a new instance that takes the field's value as an owning parameter is safe (the new - // value has taken responsibility for closing the old value). In such a case, it is not - // required that the must-call obligation of the field be satisfied via method calls before - // the assignment, since the invoked method will take ownership of the object previously - // referenced by the field and handle the obligation. This fixes the false positive in - // https://github.com/typetools/checker-framework/issues/5971. + // of itself (such as a node in a linked list) in an owning position, re-assigning the + // field to a new instance that takes the field's value as an owning parameter is safe + // (the new value has taken responsibility for closing the old value). In such a case, + // it is not required that the must-call obligation of the field be satisfied via method + // calls before the assignment, since the invoked method will take ownership of the + // object previously referenced by the field and handle the obligation. This fixes the + // false positive in https://github.com/typetools/checker-framework/issues/5971. Node rhs = node.getExpression(); if (!noLightweightOwnership && (rhs instanceof ObjectCreationNode || rhs instanceof MethodInvocationNode)) { @@ -2100,14 +2100,15 @@ private void propagateObligationsToSuccessorBlock( continue; } - // Which stores from the called-methods and must-call checkers are used in the consistency - // check varies depending on the context. Generally speaking, we would like to use the - // store propagated along the CFG edge from currentBlock to successor. But, there are - // special cases to consider. The rules are: - // 1. if the current block has no nodes, it is either a ConditionalBlock or a SpecialBlock. - // For the called-methods store, we obtain the exact CFG edge store that we need (see - // getStoreForEdgeFromEmptyBlock()). For the must-call store, due to API limitations, - // we use the following heuristics: + // Which stores from the called-methods and must-call checkers are used in the + // consistency check varies depending on the context. Generally speaking, we would + // like to use the store propagated along the CFG edge from currentBlock to + // successor. But, there are special cases to consider. The rules are: + // 1. if the current block has no nodes, it is either a ConditionalBlock or a + // SpecialBlock. + // For the called-methods store, we obtain the exact CFG edge store that we need + // (see getStoreForEdgeFromEmptyBlock()). For the must-call store, due to API + // limitations, we use the following heuristics: // 1a. if there is information about any alias in the resource alias set // in the successor store, use the successor's MC store, which // contains whatever information is true after this block finishes. @@ -2130,11 +2131,12 @@ private void propagateObligationsToSuccessorBlock( AccumulationStore cmStore; if (currentBlockNodes.size() == 0 /* currentBlock is special or conditional */) { cmStore = getStoreForEdgeFromEmptyBlock(currentBlock, successor); // 1. (CM) - // For the Must Call Checker, we currently apply a less precise handling and do not get - // the store for the specific CFG edge from currentBlock to successor. We do not believe - // this will impact precision except in convoluted and uncommon cases. If we find that - // we need more precision, we can revisit this, but it will require additional API support - // in the AnalysisResult type to get the information that we need. + // For the Must Call Checker, we currently apply a less precise handling and do + // not get the store for the specific CFG edge from currentBlock to successor. + // We do not believe this will impact precision except in convoluted and + // uncommon cases. If we find that we need more precision, we can revisit this, + // but it will require additional API support in the AnalysisResult type to get + // the information that we need. mcStore = mcAtf.getStoreForBlock( obligationGoesOutOfScopeBeforeSuccessor, @@ -2331,8 +2333,9 @@ private void checkMustCall( Map> mustCallValues = obligation.getMustCallMethods(typeFactory, mcStore); - // optimization: if mustCallValues is null, always issue a warning (there is no way to - // satisfy the check). A null mustCallValue occurs when the type is top (@MustCallUnknown). + // Optimization: if mustCallValues is null, always issue a warning (there is no way to + // satisfy the check). A null mustCallValue occurs when the type is top + // (@MustCallUnknown). if (mustCallValues == null) { // Report the error at the first alias' definition. This choice is arbitrary but // consistent. diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java index c37dc4d3b86..a9a1c31d59f 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java @@ -452,9 +452,9 @@ private void analyzeAssignmentNode(Set obligations, AssignmentNode a // If the owning field is present in the disposedFields map and there is an assignment // to the field, it must be removed from the set. This is essential since the // disposedFields map is used for adding @EnsuresCalledMethods annotations to the - // current method later. Note that this removal doesn't affect the owning annotation we - // inferred for the field, as the owningField set is updated with the inferred owning - // field in the 'inferOwningField' method. + // current method later. Note that this removal doesn't affect the owning annotation + // we inferred for the field, as the owningField set is updated with the inferred + // owning field in the 'inferOwningField' method. if (!TreeUtils.isConstructor(methodTree)) { disposedFields.remove((VariableElement) lhsElement); } @@ -580,11 +580,11 @@ private void addInheritableMustCallToClass() { // If the enclosing class already has a non-empty @MustCall type, either added by // programmers or inferred in previous iterations (not-inherited), we do not change it - // in the current analysis round to prevent potential inconsistencies and guarantee the - // termination of the inference algorithm. This becomes particularly important when - // multiple methods could satisfy the must-call obligation of the enclosing class. To - // ensure the existing @MustCall annotation is included in the inference result for this - // iteration, we re-add it. + // in the current analysis round to prevent potential inconsistencies and guarantee + // the termination of the inference algorithm. This becomes particularly important + // when multiple methods could satisfy the must-call obligation of the enclosing + // class. To ensure the existing @MustCall annotation is included in the inference + // result for this iteration, we re-add it. assert currentMustCallValues.size() == 1 : "TODO: Handle multiple must-call values"; AnnotationMirror am = createInheritableMustCall(new String[] {currentMustCallValues.get(0)}); wpi.addClassDeclarationAnnotation(classElt, am); @@ -595,9 +595,9 @@ private void addInheritableMustCallToClass() { // fields, then add (to the class) an InheritableMustCall annotation with the name of this // method. if (!methodTree.getModifiers().getFlags().contains(Modifier.PRIVATE)) { - // Since the result of getOwningFields() is a superset of the key set in disposedFields - // map, it is sufficient to check the equality of their sizes to determine if both sets - // are equal. + // Since the result of getOwningFields() is a superset of the key set in + // disposedFields map, it is sufficient to check the equality of their sizes to + // determine if both sets are equal. if (!disposedFields.isEmpty() && disposedFields.size() == getOwningFields().size()) { AnnotationMirror am = createInheritableMustCall(new String[] {methodTree.getName().toString()}); @@ -685,8 +685,8 @@ private void inferOwningForRecieverOrFormalParamPassedToCall( for (Node argument : mcca.getArgumentsOfInvocation(invocation)) { Node arg = mcca.removeCastsAndGetTmpVarIfPresent(argument); // In the CFG, explicit passing of multiple arguments in the varargs position is - // represented via an ArrayCreationNode. In this case, it checks the called methods set - // of each argument passed in this position. + // represented via an ArrayCreationNode. In this case, it checks the called methods + // set of each argument passed in this position. if (arg instanceof ArrayCreationNode) { ArrayCreationNode varArgsNode = (ArrayCreationNode) arg; for (Node varArgNode : varArgsNode.getInitializers()) { diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java index a0befd944f2..69e56cb00cd 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java @@ -175,8 +175,7 @@ protected ResourceLeakAnalysis createFlowAnalysis() { return getMustCallValues(mustCallAnnotation).isEmpty(); } else { // Indicates @MustCallUnknown, which should be treated (conservatively) as if it - // contains - // some must call values. + // contains some must call values. return false; } } @@ -201,8 +200,7 @@ protected ResourceLeakAnalysis createFlowAnalysis() { return getMustCallValues(mustCallAnnotation).isEmpty(); } else { // Indicates @MustCallUnknown, which should be treated (conservatively) as if it - // contains - // some must call values. + // contains some must call values. return false; } } diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java index 3acc526b50e..e3005ab940e 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java @@ -265,7 +265,8 @@ protected SetOfTypes parseIgnoredExceptions(String ignoredExceptionsOptionValue) } else if (!exceptionSpecifier.trim().isEmpty()) { message( Diagnostic.Kind.WARNING, - "The string '%s' appears in the -A%s=%s option, but it is not a legal exception specifier", + "The string '%s' appears in the -A%s=%s option," + + " but it is not a legal exception specifier", exceptionSpecifier, IGNORED_EXCEPTIONS, ignoredExceptionsOptionValue); @@ -281,7 +282,6 @@ protected SetOfTypes parseIgnoredExceptions(String ignoredExceptionsOptionValue) */ @SuppressWarnings({ "signature:argument", // `s` is not a qualified name, but we pass it to getTypeElement - // anyway }) protected @Nullable TypeMirror checkCanonicalName(String s) { TypeElement elem = getProcessingEnvironment().getElementUtils().getTypeElement(s); diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java index 2e10d0b1e1b..aff5596e7bc 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakVisitor.java @@ -180,8 +180,8 @@ private void checkOwningOverrides( MethodTree tree, ExecutableElement overrider, MustCallAnnotatedTypeFactory mcAtf) { for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(overrider, this.types)) { // Check for @Owning parameters. Must use an explicitly-indexed for loop so that the - // same parameter index can be accessed in the overrider's parameter list, which is the - // same length. + // same parameter index can be accessed in the overrider's parameter list, which is + // the same length. for (int i = 0; i < overridden.getParameters().size(); i++) { if (mcAtf.getDeclAnnotation(overridden.getParameters().get(i), Owning.class) != null) { if (mcAtf.getDeclAnnotation(overrider.getParameters().get(i), Owning.class) == null) { @@ -555,7 +555,7 @@ private void checkOwningField(VariableElement field) { } // Optimization: stop early as soon as we've exhausted the list of - // obligations + // obligations. if (unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { return; } diff --git a/checker/tests/index/RandomTestLBC.java b/checker/tests/index/RandomTestLBC.java index ad73b87eed7..55ae50e8d2d 100644 --- a/checker/tests/index/RandomTestLBC.java +++ b/checker/tests/index/RandomTestLBC.java @@ -5,8 +5,8 @@ public class RandomTestLBC { void test() { Random rand = new Random(); int[] a = new int[8]; - // Math.random() and Math.nextDouble() are always non-negative, but the Index Checker does - // not reason about floating-point values. + // Math.random() and Math.nextDouble() are always non-negative, but the Index Checker + // does not reason about floating-point values. // :: error: (anno.on.irrelevant) @NonNegative double d1 = Math.random() * a.length; // :: error: (assignment.type.incompatible) diff --git a/checker/tests/index/UpperBoundRefinement.java b/checker/tests/index/UpperBoundRefinement.java index ce08686ae18..9209b5f2554 100644 --- a/checker/tests/index/UpperBoundRefinement.java +++ b/checker/tests/index/UpperBoundRefinement.java @@ -32,8 +32,8 @@ public void test3(double[] a, double[] sub) { for (int i = 0; i <= a_index_max; i++) { // i has the same type as a_index_max for (int j = 0; j < sub.length; j++) { // j is @LTL("sub") - // i + j is safe here. Because j is LTL("sub"), it should count as ("-1 + - // sub.length") + // i + j is safe here. + // Because j is LTL("sub"), it should count as ("-1 + sub.length") double d = a[i + j]; } } diff --git a/checker/tests/mustcall/PolyMustCallDifferentNames.java b/checker/tests/mustcall/PolyMustCallDifferentNames.java index 51f2bf8403b..edc6caa92ee 100644 --- a/checker/tests/mustcall/PolyMustCallDifferentNames.java +++ b/checker/tests/mustcall/PolyMustCallDifferentNames.java @@ -17,7 +17,7 @@ static class Wrapper1 { public @PolyMustCall Wrapper1(@PolyMustCall Wrapped w) { // we get this error since we only have a field-assignment special case for - // @MustCallAlias, not @PolyMustCall + // @MustCallAlias, not @PolyMustCall. // :: error: (assignment.type.incompatible) this.field = w; } diff --git a/checker/tests/nullness/java8inference/Issue1630.java b/checker/tests/nullness/java8inference/Issue1630.java index ec587374da2..0b3e2286eee 100644 --- a/checker/tests/nullness/java8inference/Issue1630.java +++ b/checker/tests/nullness/java8inference/Issue1630.java @@ -17,8 +17,8 @@ public class Issue1630 { public static List f2(@Nullable List xs) { return xs != null - // TODO: we could refine the type of filter is the postconditions of the predicate is - // @EnsuresNonNull("#1"). + // TODO: we could refine the type of filter is the postconditions of the predicate + // is @EnsuresNonNull("#1"). // :: error: (type.arguments.not.inferred) ? xs.stream().map(Issue1630::toString).filter(Objects::nonNull).collect(Collectors.toList()) : Collections.emptyList(); diff --git a/checker/tests/resourceleak/MustCallAliasNormalExit.java b/checker/tests/resourceleak/MustCallAliasNormalExit.java index 679ded3f8d2..c79bcf60b0a 100644 --- a/checker/tests/resourceleak/MustCallAliasNormalExit.java +++ b/checker/tests/resourceleak/MustCallAliasNormalExit.java @@ -51,22 +51,22 @@ public static void testUse2(@Owning InputStream inputStream) throws IOException } public static void testUse3(@Owning InputStream inputStream) throws Exception { - // If mcaneFactory throws, then inputStream goes out of scope w/o being closed. But, @Owning - // only requires that the resource be closed at normal exit, so this is OK. + // If mcaneFactory throws, then inputStream goes out of scope w/o being closed. But, + // @Owning only requires that the resource be closed at normal exit, so this is OK. MustCallAliasNormalExit mcane = mcaneFactory(inputStream); mcane.close(); } // TODO: this is a false positive due to imprecision in our analysis. At the program point // before the call to mcane.close(), the inferred @CalledMethods type of inputStream is - // @CalledMethods(""), due to the control-flow merge. Further, this program point may be reached - // with mcane _not_ being a resource alias of inputStream, if mcaneFactory throws an exception. - // In this scenario, the analysis reasons that the exit may be reached without close() being - // called on inputStream. But, if mcane is not a resource alias of inputStream, then - // the inputStream.close() call in the catch block must have executed, so this is a false + // @CalledMethods(""), due to the control-flow merge. Further, this program point may be + // reached with mcane _not_ being a resource alias of inputStream, if mcaneFactory throws an + // exception. In this scenario, the analysis reasons that the exit may be reached without + // close() being called on inputStream. But, if mcane is not a resource alias of inputStream, + // then the inputStream.close() call in the catch block must have executed, so this is a false // positive. Removing this false positive would require greater path sensitivity in the - // consistency analyzer, by tracking both resource aliases and @CalledMethods types for each path - // in an Obligation. See https://github.com/typetools/checker-framework/issues/5658 + // consistency analyzer, by tracking both resource aliases and @CalledMethods types for each + // path in an Obligation. See https://github.com/typetools/checker-framework/issues/5658 . // :: error: required.method.not.called public static void testUse4(@Owning InputStream inputStream) throws Exception { MustCallAliasNormalExit mcane = null; diff --git a/checker/tests/resourceleak/MustCallAliasNotThis.java b/checker/tests/resourceleak/MustCallAliasNotThis.java index 7f267d016c9..bbc3373e14b 100644 --- a/checker/tests/resourceleak/MustCallAliasNotThis.java +++ b/checker/tests/resourceleak/MustCallAliasNotThis.java @@ -10,14 +10,14 @@ public class MustCallAliasNotThis implements Closeable { @Owning Closeable foo; // Both of these constructors are wrong: the first assigns to the owning field of another - // object of the same class, but not the "this" object; the second assigns to another class' - // owning field entirely. Both of these assignments would require @CreatesMustCallFor - // annotations - // to verify, so it's okay that the @MustCallAlias annotations are verified here (because it - // is impossible to write the required @CreatesMustCallFor annotations, since they can only be - // written on methods, not on constructors). If we ever permit @CreatesMustCallFor annotations - // on constructors, this test should be revisited: it might be necessary to make a corresponding - // change in the rules for verifying @MustCallAlias. + // object of the same class, but not the "this" object; the second assigns to another + // class' owning field entirely. Both of these assignments would require + // @CreatesMustCallFor annotations to verify, so it's okay that the @MustCallAlias + // annotations are verified here (because it is impossible to write the required + // @CreatesMustCallFor annotations, since they can only be written on methods, not on + // constructors). If we ever permit @CreatesMustCallFor annotations on constructors, this + // test should be revisited: it might be necessary to make a corresponding change in the + // rules for verifying @MustCallAlias. // :: error: missing.creates.mustcall.for public @MustCallAlias MustCallAliasNotThis( diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index 8eccbb698a8..ec6cf0ebf37 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -3758,8 +3758,8 @@ public Node visitTry(TryTree tree, Void p) { */ private void handleTryResourcesAndBlock(TryTree tryTree, Void p, List resources) { if (resources.isEmpty()) { - // Either `tryTree` was not a try-with-resources, or this method was called recursively - // and all the resources have been handled. Just scan the main try block. + // Either `tryTree` was not a try-with-resources, or this method was called + // recursively and all the resources have been handled. Just scan the main try block. scan(tryTree.getBlock(), p); return; } @@ -3785,9 +3785,9 @@ private void handleTryResourcesAndBlock(TryTree tryTree, Void p, List getSupportedLintOptions() { } } else if (t instanceof NoSuchMethodException) { // Note: it's possible that NoSuchMethodException was caused by - // `ctor.newInstance(args)`, if - // the constructor itself uses reflection. But this case is unlikely. + // `ctor.newInstance(args)`, if the constructor itself uses reflection. + // But this case is unlikely. throw new TypeSystemError( "Could not find constructor %s(%s)", name, StringsPlume.join(", ", paramTypes)); } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java index 4f88bf26b0a..b9efcb937f6 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -705,8 +705,7 @@ public boolean areBoundsValid(AnnotatedTypeMirror upperBound, AnnotatedTypeMirro } else { // When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will // be reported as invalid. Therefore, we do not do any other comparisons nor do we - // report - // a bound. + // report a bound. return true; } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 3b5aae0cdda..1a35e3c62a3 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1147,12 +1147,12 @@ protected void checkPurityAnnotations(MethodTree tree) { WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); ExecutableElement methodElt = TreeUtils.elementFromDeclaration(tree); inferPurityAnno(additionalKinds, wpi, methodElt); - // The purity of overridden methods is impacted by the purity of this method. If a - // superclass method is pure, but an implementation in a subclass is not, WPI ought to - // treat **neither** as pure. The purity kind of the superclass method is the LUB of - // its own purity and the purity of all the methods that override it. Logically, this - // rule is the same as the WPI rule for overrides, but purity isn't a type system and - // therefore must be special-cased. + // The purity of overridden methods is impacted by the purity of this method. If + // a superclass method is pure, but an implementation in a subclass is not, WPI + // ought to treat **neither** as pure. The purity kind of the superclass method + // is the LUB of its own purity and the purity of all the methods that override + // it. Logically, this rule is the same as the WPI rule for overrides, but + // purity isn't a type system and therefore must be special-cased. Set overriddenMethods = ElementUtils.getOverriddenMethods(methodElt, types); for (ExecutableElement overriddenElt : overriddenMethods) { @@ -2486,9 +2486,9 @@ public Void visitAnnotation(AnnotationTree tree, Void p) { public Void visitConditionalExpression(ConditionalExpressionTree tree, Void p) { if (TreeUtils.isPolyExpression(tree)) { // From the JLS: - // A poly reference conditional expression is compatible with a target type T if its second - // and third operand expressions are compatible with T. In the Checker Framework this check - // happens in #commonAssignmentCheck. + // A poly reference conditional expression is compatible with a target type T if its + // second and third operand expressions are compatible with T. In the Checker + // Framework this check happens in #commonAssignmentCheck. return super.visitConditionalExpression(tree, p); } @@ -4100,8 +4100,8 @@ protected boolean checkMethodReferenceAsOverride( functionTypeReturnType); return overrideChecker.checkOverride(); } else { - // If the functionalInterface is not a declared type, it must be from a wildcard from a raw - // type. In that case, only return false if raw types should not be ignored. + // If the functionalInterface is not a declared type, it must be from a wildcard from a + // raw type. In that case, only return false if raw types should not be ignored. return !atypeFactory.ignoreRawTypeArguments; } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java index 78f57f6aa4c..cf692774162 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java @@ -260,7 +260,8 @@ public Void visitTypeCast(TypeCastTree tree, AnnotatedTypeMirror atm) { return null; } - // I would like to call ((AnnotatedTypeTree) castTree).hasAnnotation(Unsigned.class), + // I would like to call + // ((AnnotatedTypeTree) castTree).hasPrimaryAnnotation(Unsigned.class), // but `Unsigned` is in the checker package and this code is in the common package. List annoTrees = TreeUtils.getExplicitAnnotationTrees(null, tree.getType()); diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java index 00171b8d4c3..6aa78aef9c9 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java @@ -331,9 +331,9 @@ public Void visitTypeCast(TypeCastTree tree, Void p) { } // At this point, types are like: (@IntVal(-1) byte, @IntVal(255) int) and knowledge of - // signedness is gone. So, use castType's underlying type to infer correctness of the cast. - // This method returns true for (@IntVal(-1), @IntVal(255)) if the underlying type is `byte`, - // but not for any other underlying type. + // signedness is gone. So, use castType's underlying type to infer correctness of the + // cast. This method returns true for (@IntVal(-1), @IntVal(255)) if the underlying type + // is `byte`, but not for any other underlying type. @Override protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { TypeKind castTypeKind = TypeKindUtils.primitiveOrBoxedToTypeKind(castType.getUnderlyingType()); diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java index a1061b458d2..551db8eb0ee 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceImplementation.java @@ -798,10 +798,10 @@ public void addMethodDeclarationAnnotation( annoToAdd = anno; } else { // It's a purity annotation and `lubPurity` is true. Do a "least upper bound" between - // the current purity annotation inferred for the method and anno. This is necessary to - // avoid WPI inferring incompatible purity annotations on methods that override methods - // from their superclass. TODO: this would be unnecessary if purity was implemented as a - // type system. + // the current purity annotation inferred for the method and anno. This is necessary + // to avoid WPI inferring incompatible purity annotations on methods that override + // methods from their superclass. + // TODO: this would be unnecessary if purity was implemented as a type system. AnnotationMirror currentPurityAnno = getPurityAnnotation(methodElt); if (currentPurityAnno == null) { annoToAdd = anno; diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index 829542a7698..56f4556572c 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -751,13 +751,13 @@ private void addClass(ClassTree tree, @Nullable TypeDeclaration javaParserNod throw new BugInCF("Missing getFullyQualifiedName() for " + javaParserClass); } if ("".contentEquals(tree.getSimpleName())) { - @SuppressWarnings("signature:assignment") // computed from string - // concatenation + @SuppressWarnings("signature:assignment" // computed from string concatenation + ) @BinaryName String computedName = ofqn.get() + "$" + ++innerClassCount; className = computedName; } else { - @SuppressWarnings("signature:assignment") // computed from string - // concatenation + @SuppressWarnings("signature:assignment" // computed from string concatenation + ) @BinaryName String computedName = ofqn.get() + "$" + tree.getSimpleName().toString(); className = computedName; } @@ -993,8 +993,7 @@ private List findOverrides( // programmer-written annotations. The latter are stored in elements and, with the given formal // parameter list, are not accessible to this method. In the future, the annotations stored in // elements should also be passed to this method (or maybe they are already available to the - // type - // factory?). I'm leaving that enhancement until later. + // type factory?). I'm leaving that enhancement until later. public void wpiPrepareMethodForWriting( CallableDeclarationAnnos methodAnnos, Collection inSupertypes, @@ -1067,12 +1066,11 @@ public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checke */ private void writeAjavaFile(File outputPath, CompilationUnitAnnos root) { try (Writer writer = new BufferedWriter(new FileWriter(outputPath))) { - - // JavaParser can output using lexical preserving printing, which writes the file such - // that its formatting is close to the original source file it was parsed from as - // possible. Currently, this feature is very buggy and crashes when adding annotations - // in certain locations. This implementation could be used instead if it's fixed in - // JavaParser.LexicalPreservingPrinter.print(root.declaration, writer); + // This implementation uses JavaParser's lexical preserving printing, which writes the + // file such that its formatting is close to the original source file it was parsed from + // as possible. It is commented out because, this feature is very buggy and crashes when + // adding annotations in certain locations. + // LexicalPreservingPrinter.print(root.declaration, writer); // Do not print invisible qualifiers, to avoid cluttering the output. Set invisibleQualifierNames = getInvisibleQualifierNames(this.atypeFactory); @@ -1354,9 +1352,8 @@ public String toString() { String fieldsString = fields.toString(); if (fieldsString.length() > 100) { // The quoting increases the likelihood that all delimiters are balanced in the - // result. - // That makes it easier to manipulate the result (such as skipping over it) in an - // editor. The quoting also makes clear that the value is truncated. + // result. That makes it easier to manipulate the result (such as skipping over it) + // in an editor. The quoting also makes clear that the value is truncated. fieldsString = "\"" + fieldsString.substring(0, 95) + "...\""; } diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java index 531cf6fa8c7..3543e8380d4 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java @@ -763,9 +763,9 @@ public Void visitForLoop(ForLoopTree javacTree, Node javaParserNode) { // "index++", as in the test all-systems/LightWeightCache.java. ((ExpressionStatementTree) javacInitializer).getExpression().accept(this, initializer); } else { - // This is likely to lead to a crash, if it ever happens: javacInitializer - // is a StatementTree of some kind, but initializer is a raw expression (not - // wrapped in a statement). + // This is likely to lead to a crash, if it ever happens: javacInitializer is a + // StatementTree of some kind, but initializer is a raw expression (not wrapped + // in a statement). javacInitializer.accept(this, initializer); } } @@ -1127,9 +1127,9 @@ public Void visitNewArray(NewArrayTree javacTree, Node javaParserNode) { // TODO: Implement this. // // Some notes: - // - javacTree.getAnnotations() seems to always return empty, any annotations on the - // base type seem to go on the type itself in javacTree.getType(). The JavaParser version - // doesn't even have a corresponding getAnnotations method. + // - javacTree.getPrimaryAnnotations() seems to always return empty, any annotations on + // the base type seem to go on the type itself in javacTree.getType(). The JavaParser + // version doesn't even have a corresponding getPrimaryAnnotations method. // - When there are no initializers, both systems use similar representations. The // dimensions line up. // - When there is an initializer, they differ greatly for multi-dimensional arrays. Javac diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java index de74ab45690..1df6049f0db 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -684,10 +684,9 @@ public TransferResult visitTernaryExpression( V resultValue = null; if (thenValue != null && elseValue != null) { // If a conditional expression is a poly expression, then its Java type is the type of - // its context. (For example, the type of the conditional expression in `Object o = b ? - // "" : "";` is `Object`, not `String`.) - // So, use the Java type of the conditional expression and the annotations for each - // branch. + // its context. (For example, the type of the conditional expression in `Object o = b + // ? "" : "";` is `Object`, not `String`.) So, use the Java type of the conditional + // expression and the annotations for each branch. TypeMirror conditionalType = TreeUtils.typeOf(n.getTree()); // The resulting abstract value is the merge of the 'then' and 'else' branch. resultValue = thenValue.leastUpperBound(elseValue, conditionalType); diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index c60231254c0..ff8001285d8 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -325,9 +325,8 @@ // Whether to print [] around a set of type parameters in order to clearly see where they end // e.g. - // without this option the E is printed: E extends F extends Object - // with this option: E [ extends F [ extends Object super Void ] super Void - // ] + // without this option E is printed: E extends F extends Object + // with this option: E [ extends F [ extends Object super Void ] super Void ] // when multiple type variables are used this becomes useful very quickly "printVerboseGenerics", diff --git a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java index bb243681f55..be1787512ab 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java @@ -573,9 +573,8 @@ static boolean suppresses(AnnotationExpr suppressor, Collection suppress } else if (v instanceof NameExpr) { // TODO: is it better to return null here, thus causing nothing under this // warning to be treated as "suppressed", or to return any keys that are string - // literals? - // Returning null here ensures that if any argument to the SW annotation isn't a - // string literal, then none of them are considered. + // literals? Returning null here ensures that if any argument to the SW + // annotation isn't a string literal, then none of them are considered. return null; } else { throw new BugInCF("Unexpected annotation element of type %s: %s", v.getClass(), v); diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index b293312e3ec..199cce61ff1 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -2618,8 +2618,8 @@ protected ParameterizedExecutableType methodFromUse( if (typeParamToTypeArg.get(tv.getUnderlyingType()) == null) { // throw new BugInCF( // "AnnotatedTypeFactory.methodFromUse:mismatch between" - // + " declared method type variables and the inferred method type - // arguments." + // + " declared method type variables and the inferred method + // type arguments." // + " Method type variables: " // + methodType.getTypeVariables() // + "; " @@ -4905,8 +4905,8 @@ private AnnotatedDeclaredType makeGroundTargetType( TypeMirror wildcardUbType = wildcardType.getExtendsBound().getUnderlyingType(); if (wildcardType.isTypeArgOfRawType()) { - // Keep the type arguments from raw types so that it is ignored by later subtyping and - // containment checks. + // Keep the type arguments from raw types so that it is ignored by later + // subtyping and containment checks. typeVarToTypeArg.put(typeVariable, wildcardType); } else if (isExtendsWildcard(wildcardType)) { TypeMirror correctArgType; @@ -5056,8 +5056,8 @@ public AnnotatedTypeMirror applyCaptureConversion(AnnotatedTypeMirror typeToCapt */ public AnnotatedTypeMirror applyCaptureConversion( AnnotatedTypeMirror type, TypeMirror typeMirror) { - // If the type contains type arguments of raw types, don't capture, but mark all wildcards that - // should have been captured as "raw" before it is returned. + // If the type contains type arguments of raw types, don't capture, but mark all + // wildcards that should have been captured as "raw" before it is returned. if (typeMirror.getKind() == TypeKind.DECLARED && type.getKind() == TypeKind.DECLARED) { boolean fromRawType = false; AnnotatedDeclaredType uncapturedType = (AnnotatedDeclaredType) type; @@ -5767,8 +5767,8 @@ protected void makeConditionConsistentWithOtherMethod( // `!otherConditionMap.containsKey(expr)` test. // If a condition map contains the key "every expression", that means that inference // completed without inferring any conditions of that type. For example, if no - // @EnsuresCalledMethods was inferred for any expression, the map would contain the key - // "every expression", which is not a legal Java expression. + // @EnsuresCalledMethods was inferred for any expression, the map would contain the + // key "every expression", which is not a legal Java expression. if (otherConditionMap.containsKey("every expression") || !otherConditionMap.containsKey(expr)) { // `otherInferredType` was inferred to be the top type. diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index 49b3e2baaa5..2bee0ff0e6b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -385,8 +385,7 @@ protected final AnnotationMirrorSet getAnnotationsField() { * @return a set of the annotations on this */ // TODO: When the current, deprecated `getAnnotations()` (deprecation date 2023-06-15) is - // removed, - // rename all the "getEffectiveAnnotation...()" methods to just "getAnnotation...()". + // removed, rename all the "getEffectiveAnnotation...()" methods to just "getAnnotation...()". // EISOP will not do this renaming, it would introduce inconsistent behavior with how // getAnnotations in javac APIs works. // Removed getEffectiveAnnotation @@ -1079,7 +1078,8 @@ public List getTypeArguments() { if (typeArgs != null) { return typeArgs; } else if (isUnderlyingTypeRaw()) { - // Initialize the type arguments with wildcards marks as type arguments from raw types. + // Initialize the type arguments with wildcards marks as type arguments from raw + // types. BoundsInitializer.initializeTypeArgs(this); return typeArgs; } else if (getUnderlyingType().getTypeArguments().isEmpty()) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java index e0884a70d72..016b80de345 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java @@ -172,12 +172,12 @@ public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) int index = lambdaDecl.getParameters().indexOf(f.declarationFromElement(paramElement)); AnnotatedExecutableType functionType = f.getFunctionTypeFromTree(lambdaDecl); AnnotatedTypeMirror funcTypeParam = functionType.getParameterTypes().get(index); - // During type argument inference, the type of the parameters is assumed to be the same as - // the function parameter. - // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-18.html#jls-18.2.1). - // So if the underlying types are not the same type, then assume the lambda parameter is the - // same as the function type. (Use the erased types because the - // type arguments are not substituted when the annotated type arguments are.) + // During type argument inference, the type of the parameters is assumed to be the + // same as the function parameter. + // (https://docs.oracle.com/javase/specs/jls/se11/html/jls-18.html#jls-18.2.1). So if + // the underlying types are not the same type, then assume the lambda parameter is the + // same as the function type. (Use the erased types because the type arguments are not + // substituted when the annotated type arguments are.) if (TypesUtils.isErasedSubtype( funcTypeParam.underlyingType, lambdaParam.underlyingType, f.types)) { return AnnotatedTypes.asSuper(f, funcTypeParam, lambdaParam); diff --git a/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java index 35e4c0fbb41..0d1020af27d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java @@ -794,10 +794,10 @@ private ExecutableElement getMethodElement( } // `expr` should be a field access, a fully qualified class name, or a class name qualified - // with another class name (e.g. {@code OuterClass.InnerClass}). - // If the expression refers to a class that is not available to the resolver (the class - // wasn't passed to javac on the command line), then the argument can be - // "outerpackage.innerpackage", which will lead to a confusing error message. + // with another class name (e.g. {@code OuterClass.InnerClass}). If the expression refers + // to a class that is not available to the resolver (the class wasn't passed to javac on + // the command line), then the argument can be "outerpackage.innerpackage", which will lead + // to a confusing error message. @Override public JavaExpression visit(FieldAccessExpr expr, Void aVoid) { setResolverField(); diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java index e2a96905afe..1a95b66c923 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java @@ -46,17 +46,17 @@ public InferenceResult inferTypeArgs( AnnotatedExecutableType methodType) { TreePath pathToExpression = typeFactory.getPath(expressionTree); - // In order to find the type arguments for expressionTree, type arguments for outer method calls - // may need be inferred, too. + // In order to find the type arguments for expressionTree, type arguments for outer method + // calls may need be inferred, too. // So, first find the outermost tree that is required to infer the type arguments for // expressionTree ExpressionTree outerTree = outerInference(expressionTree, pathToExpression.getParentPath()); for (InvocationTypeInference i : java8InferenceStack) { if (i.getInferenceExpression() == outerTree) { - // Inference is running and is asking for the type of the method before type arguments are - // substituted. So don't infer any type arguments. This happens when getting the type of a - // lambda's returned expression. + // Inference is running and is asking for the type of the method before type + // arguments are substituted. So don't infer any type arguments. This happens when + // getting the type of a lambda's returned expression. List instantiated = new ArrayList<>(); Theta m = i.context.maps.get(expressionTree); if (m == null) { @@ -115,8 +115,8 @@ public InferenceResult inferTypeArgs( } } catch (Exception ex) { // This should never happen, if javac infers type arguments so should the Checker - // Framework. However, given how buggy javac inference is, this probably will, so deal with it - // gracefully. + // Framework. However, given how buggy javac inference is, this probably will, so deal + // with it gracefully. return new InferenceResult( Collections.emptyList(), false, diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java index bb045931048..c6ac3207240 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java @@ -303,10 +303,11 @@ public BoundSet createB2MethodRef(InvocationType methodType, List List formals = methodType.getParameterTypes(map, args.size()); if (TreeUtils.isLikeDiamondMemberReference(methodType.getInvocation())) { // https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.13.1 - // If ReferenceType is a raw type, and there exists a parameterization of this type, G<.. - // .>, that is a supertype of P1, the type to search is the result of capture conversion - // (§5.1.10) applied to G<...>; otherwise, the type to search is the same as the type of - // the first search. Type arguments, if any, are given by the method reference expression. + // If ReferenceType is a raw type, and there exists a parameterization of this type, + // G<...>, that is a supertype of P1, the type to search is the result of capture + // conversion (§5.1.10) applied to G<...>; otherwise, the type to search is the same + // as the type of the first search. Type arguments, if any, are given by the method + // reference expression. AbstractType receiver = args.remove(0); args.add(0, receiver.capture(context)); } @@ -400,8 +401,8 @@ public BoundSet createB3( } if (target.isProper()) { // From the JLS: - // "T is a primitive type, and one of the primitive wrapper classes mentioned in 5.1.7 is - // an instantiation, upper bound, or lower bound for [the variable] in B2." + // "T is a primitive type, and one of the primitive wrapper classes mentioned in + // 5.1.7 is an instantiation, upper bound, or lower bound for [the variable] in B2." ConstraintSet constraintSet = new ConstraintSet(new Typing(r, target, Kind.SUBTYPE)); BoundSet newBounds = constraintSet.reduce(context); b2.incorporateToFixedPoint(newBounds); @@ -444,9 +445,9 @@ public ConstraintSet createC( c.addAll(aa.reduce(context)); } } else { - // Wait to reduce additional argument constraints from lambdas and method references because - // the additional constraints might require other inference variables to be resolved before - // the constraint can be created. + // Wait to reduce additional argument constraints from lambdas and method references + // because the additional constraints might require other inference variables to be + // resolved before the constraint can be created. c.addAll(createAdditionalArgConstraints(ei, fi, map)); } } @@ -610,7 +611,8 @@ private BoundSet getB4(BoundSet b3, ConstraintSet c) { c.applyInstantiations(); } if (!alphas.isEmpty()) { - // Resolve any remaining variables that have bounds that are variable or inference types. + // Resolve any remaining variables that have bounds that are variable or inference + // types. Resolution.resolve(alphas, b3, context); c.applyInstantiations(); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java index 841f0f5dcb8..4dc62859ddd 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Typing.java @@ -236,7 +236,8 @@ private ReductionResult reduceSubtypeClass(Java8InferenceContext context) { return set; } else { - // The constraint reduces to true if T is among the supertypes of S, and false otherwise. + // The constraint reduces to true if T is among the supertypes of S, and false + // otherwise. return ((InferenceType) S).isSubType((ProperType) T); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java index 8ab07a901b6..b3850190ccb 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java @@ -593,8 +593,8 @@ public Theta createThetaForMethodReference( TypeMirror preColonTreeType = TreeUtils.typeOf(memRef.getQualifierExpression()); if (TreeUtils.isDiamondMemberReference(memRef) || TreeUtils.isLikeDiamondMemberReference(memRef)) { - // If memRef is a constructor or method of a generic class whose type argument isn't specified - // such as HashSet::new or HashSet::put + // If memRef is a constructor or method of a generic class whose type argument isn't + // specified such as HashSet::new or HashSet::put // then add variables for the type arguments to the class. TypeElement classEle = (TypeElement) ((Type) preColonTreeType).asElement(); DeclaredType classTypeMirror = (DeclaredType) classEle.asType(); diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java index 0b59c324c02..2f9455c944d 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/ProperType.java @@ -100,7 +100,8 @@ private static void verifyTypeKinds(AnnotatedTypeMirror atm, TypeMirror typeMirr assert typeMirror != null && typeMirror.getKind() != TypeKind.VOID && atm != null; if (typeMirror.getKind() != atm.getKind()) { - // throw new BugInCF("type: %s annotated type: %s", typeMirror, atm.getUnderlyingType()); + // throw new BugInCF("type: %s annotated type: %s", typeMirror, + // atm.getUnderlyingType()); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java index e4ec904b501..5cb7e136f01 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/UseOfVariable.java @@ -162,10 +162,9 @@ public void addBound(BoundKind kind, AbstractType bound) { if (!hasPrimaryAnno) { variable.getBounds().addBound(kind, bound); } else { - // If the use has a primary annotation, then add the bound but with that annotations set to - // bottom - // or top. This makes it so that the java type is still a bound, but the qualifiers do not - // change the results of inference. + // If the use has a primary annotation, then add the bound but with that annotations + // set to bottom or top. This makes it so that the java type is still a bound, but + // the qualifiers do not change the results of inference. if (kind == BoundKind.LOWER) { bound.getAnnotatedType().replaceAnnotations(bots); variable.getBounds().addBound(kind, bound); diff --git a/framework/tests/all-systems/java8inference/GuavaCrash.java b/framework/tests/all-systems/java8inference/GuavaCrash.java index 99d6d17c91d..eea2067df16 100644 --- a/framework/tests/all-systems/java8inference/GuavaCrash.java +++ b/framework/tests/all-systems/java8inference/GuavaCrash.java @@ -75,8 +75,8 @@ void mergeTables( (table1, table2) -> { // for (Table.Cell cell2 : table2.cellSet()) { // mergeTables( - // table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), - // mergeFunction); + // table1, cell2.getRowKey(), cell2.getColumnKey(), + // cell2.getValue(), mergeFunction); // } return table1; }); diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java index 5e72a391922..64751c1914a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/Pair.java @@ -35,8 +35,7 @@ public static Pair of(T1 first, T2 second) { // The typical way to make a copy is to first call super.clone() and then modify it. // That implementation strategy does not work for Pair because its fields are final, so the - // clone - // and deepCopy() methods use of() instead. + // clone and deepCopy() methods use of() instead. /** * Returns a copy of this in which each element is a clone of the corresponding element of this. diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java index 821071ca62e..a00ceac2f4f 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java @@ -253,8 +253,8 @@ public static IPair enclosingNonParen(TreePath path) { */ public static @Nullable Tree getContextForPolyExpression(TreePath treePath) { // If a lambda or a method reference is the expression in a type cast, then the type cast is - // the context. If a method or constructor invocation is the expression in a type cast, then - // the invocation has no context. + // the context. If a method or constructor invocation is the expression in a type cast, + // then the invocation has no context. boolean isLambdaOrMethodRef = treePath.getLeaf().getKind() == Kind.LAMBDA_EXPRESSION || treePath.getLeaf().getKind() == Kind.MEMBER_REFERENCE; @@ -311,9 +311,9 @@ public static IPair enclosingNonParen(TreePath path) { return getContextForPolyExpression(parentPath, isLambdaOrMethodRef); default: if (TreeUtils.isYield(parent)) { - // A yield statement is only legal within a switch expression. Walk up the path to the - // case tree instead of the switch expression tree so the code remains backward - // compatible. + // A yield statement is only legal within a switch expression. Walk up the path + // to the case tree instead of the switch expression tree so the code remains + // backward compatible. TreePath pathToCase = pathTillOfKind(parentPath, Kind.CASE); assert pathToCase != null : "@AssumeAssertion(nullness): yield statements must be enclosed in a CaseTree"; @@ -324,7 +324,8 @@ public static IPair enclosingNonParen(TreePath path) { @SuppressWarnings("interning:not.interned") // AST node comparison boolean switchIsLeaf = SwitchExpressionUtils.getExpression(parent) == treePath.getLeaf(); if (switchIsLeaf) { - // The assignment context for the switch selector expression is simply boolean. + // The assignment context for the switch selector expression is simply + // boolean. // No point in going on. return null; } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index a4fb85b08c5..4a6fe93adb5 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -2784,8 +2784,8 @@ public static boolean isStandaloneExpression(ExpressionTree expression) { return true; } if (expression.getKind() == Tree.Kind.METHOD_INVOCATION) { - // This seems to be a bug in at least Java 11. If a method has type arguments, then it is - // a standalone expression. + // This seems to be a bug in at least Java 11. If a method has type arguments, then + // it is a standalone expression. return !((MethodInvocationTree) expression).getTypeArguments().isEmpty(); } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java index ae3f8415ad6..4a5e16ef5ad 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java @@ -173,10 +173,10 @@ public MemberSelectTree buildCloseMethodAccess(ExpressionTree autoCloseableExpr) // Find the close() method Symbol.MethodSymbol closeMethod = null; - // We could use elements.getAllMembers(exprElement) to find the close method, but in rare cases - // calling that method crashes with a Symbol$CompletionFailure exception. See - // https://github.com/typetools/checker-framework/issues/6396. The code below directly searches - // all supertypes for the method and avoids the crash. + // We could use elements.getAllMembers(exprElement) to find the close method, but in rare + // cases calling that method crashes with a Symbol$CompletionFailure exception. See + // https://github.com/typetools/checker-framework/issues/6396. The code below directly + // searches all supertypes for the method and avoids the crash. for (Type s : javacTypes.closure(((Symbol) exprElement).type)) { for (Symbol m : s.tsym.members().getSymbolsByName(closeName)) { if (!(m instanceof Symbol.MethodSymbol)) { From ec3c0493451034aab7a410583cc1c0fae19c35aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 04:18:09 -0800 Subject: [PATCH 061/173] Update versions.errorprone to v2.25.0 From 9e53598ea5130363e5e00bc6a18abcca1426bdd8 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 04:21:15 -0800 Subject: [PATCH 062/173] Fix typos Co-authored-by: Aosen Xiong <82676488+Ao-senXiong@users.noreply.github.com> Co-authored-by: Werner Dietl --- .../checker/initialization/InitializationTransfer.java | 5 +++++ .../checkerframework/checker/optional/OptionalChecker.java | 2 +- .../common/reflection/MethodValAnnotatedTypeFactory.java | 2 +- .../framework/ajava/JointJavacJavaParserVisitor.java | 4 ++-- .../framework/type/TypeVariableSubstitutor.java | 3 +++ .../framework/type/visitor/AtmComboVisitor.java | 6 +++--- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java index ba1c068b2a3..2ca38944d7c 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java @@ -136,6 +136,11 @@ public TransferResult visitAssignment( return result; } + /** + * If an invariant field is initialized and has the invariant annotation, then it has at least the + * invariant annotation. Note that only fields of the 'this' receiver are tracked for + * initialization. + */ @Override public TransferResult visitMethodInvocation( MethodInvocationNode n, TransferInput in) { diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java index 1c6db443bef..6604299464d 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalChecker.java @@ -11,7 +11,7 @@ * * @checker_framework.manual #optional-checker Optional Checker */ -// TODO: For a call to ofNullable, if the argument has type +// TODO: For a call to `@Optional#ofNullable`, if the argument has type // @NonNull, make the return type have type @Present. @RelevantJavaTypes(Optional.class) @StubFiles({"javaparser.astub"}) diff --git a/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java index 94d8a118cc1..e544ced8ab3 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/MethodValAnnotatedTypeFactory.java @@ -410,7 +410,7 @@ private List getConstructorParamsLen(List arg *
        10. 1: otherwise * * - * @param argument the single argument in a call to {@code getMethod} or {@code getConstrutor} + * @param argument the single argument in a call to {@code getMethod} or {@code getConstructor} * @return a list, each of whose elementts is a possible the number of parameters; it is often, * but not always, a singleton list */ diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java index 3543e8380d4..c76d79831a9 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JointJavacJavaParserVisitor.java @@ -2319,7 +2319,7 @@ public T castNode(Class type, Node javaParserNode, Tree javacTree) { } /** - * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other, + * Given a javac tree and JavaParser node which were visited but didn't correspond to each other, * throws an exception indicating that the visiting process failed for those nodes. * * @param javacTree a tree that was visited @@ -2340,7 +2340,7 @@ private void throwUnexpectedNodeType(Tree javacTree, Node javaParserNode) { } /** - * Given a javac tree and JavaPaser node which were visited but didn't correspond to each other, + * Given a javac tree and JavaParser node which were visited but didn't correspond to each other, * throws an exception indicating that the visiting process failed for those nodes because {@code * javaParserNode} was expected to be of type {@code expectedType}. * diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java index a1fb12986ef..349113a5e0c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java @@ -16,6 +16,9 @@ /** TypeVariableSubstitutor replaces type variables from a declaration with arguments to its use. */ public class TypeVariableSubstitutor { + /** Create a TypeVariableSubstitutor. */ + public TypeVariableSubstitutor() {} + /** * Given a mapping from type variable to its type argument, replace each instance of a type * variable with a copy of type argument. diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java index 40579704994..6334d03aee4 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AtmComboVisitor.java @@ -51,9 +51,9 @@ default String defaultErrorMessage( } /** - * Called by the default implementation of every AbstractAtmComboVisitor visit method. This - * methodnS issues a runtime exception by default. In general, it should handle the case where a - * visit method has been called with a pair of type mirrors that should never be passed to this + * Called by the default implementation of every AbstractAtmComboVisitor visit method. This method + * issues a runtime exception by default. In general, it should handle the case where a visit + * method has been called with a pair of type mirrors that should never be passed to this * particular visitor. * * @param type1 the first AnnotatedTypeMirror parameter to the visit method called From 57293c53fd08664d2b6dfab408d34339ca269efc Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 04:29:50 -0800 Subject: [PATCH 063/173] Make toString output consistent with NarrowingConversionNode Co-authored-by: Werner Dietl From cc76b084c75f86ec4a4e12b3457e03c3f8199418 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 11:44:08 -0800 Subject: [PATCH 064/173] `Tree.Kind.PLUS_ASSIGNMENT` never appears in the CFG Co-authored-by: Werner Dietl From 3f35f87044922c9882d3b6c82caa40a7498ff861 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 13:54:15 -0800 Subject: [PATCH 065/173] Remove methods that have been deprecated for over two years --- dataflow/manual/content.tex | 3 ++- .../dataflow/cfg/builder/CFGTranslationPhaseThree.java | 4 ++-- .../org/checkerframework/dataflow/cfg/node/ReturnNode.java | 6 +++++- .../org/checkerframework/framework/util/AnnotatedTypes.java | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dataflow/manual/content.tex b/dataflow/manual/content.tex index 0c3d9b04ddd..bc5c0dad440 100644 --- a/dataflow/manual/content.tex +++ b/dataflow/manual/content.tex @@ -1939,7 +1939,8 @@ \subsection{The Checker Framework Store and Dealing with Aliasing} A side-effect-free method has no visible side-effects, such as setting a field of an object that existed before the method was called. -A deterministic method returns the same value (according to ==) every time it is called with the same arguments and in the same environment. +A deterministic method returns the same value (according to ==) every time +it is called with the same arguments and in the same environment. A pure method is both side-effect-free and deterministic. The Dataflow Framework respects the \href{https://eisop.github.io/cf/api/org/checkerframework/dataflow/qual/SideEffectFree.html}{\code{@SideEffectFree}}, diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java index f13c38254de..cfc2fb9f6c6 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java @@ -167,8 +167,8 @@ protected static void mergeConsecutiveBlocks(ControlFlowGraph cfg) { if (succ.getType() == BlockType.REGULAR_BLOCK) { RegularBlockImpl rs = (RegularBlockImpl) succ; if (rs.getRegularSuccessor() == rs) { - // Do not attempt to merge a block with a self edge, as it leads to - // non-termination in the merging algorithm. + // Do not attempt to merge a block with a self edge (which would infinite-loop + // if it were run), as it leads to non-termination in the merging algorithm. break; } if (rs.getPredecessors().size() == 1) { diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java index f84e746b6fc..cfea031f9af 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java @@ -40,7 +40,11 @@ public ReturnNode(ReturnTree returnTree, @Nullable Node result, Types types) { this.returnTree = returnTree; } - /** The result of the return node, {@code null} otherwise. */ + /** + * The result of the return node, {@code null} otherwise. + * + * @return the result of the return node, {@code null} otherwise + */ public @Nullable Node getResult() { return result; } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index a29fdca2e68..e24c6b992fa 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -1543,7 +1543,7 @@ public static boolean hasNoExplicitBound(AnnotatedTypeMirror wildcardType) { * @return true if wildcard type is explicitly super bounded * @deprecated Use {@link #hasExplicitSuperBound(AnnotatedTypeMirror)} */ - @Deprecated // 2023-03-01 + @Deprecated // 2023-02-28 public static boolean isExplicitlySuperBounded(AnnotatedWildcardType wildcardType) { return hasExplicitSuperBound(wildcardType); } @@ -1565,7 +1565,7 @@ public static boolean hasExplicitSuperBound(AnnotatedTypeMirror wildcardType) { * @return true if wildcard type is explicitly extends bounded * @deprecated Use {@link #hasExplicitExtendsBound(AnnotatedTypeMirror)}. */ - @Deprecated // 2023-03-01 + @Deprecated // 2023-02-28 public static boolean isExplicitlyExtendsBounded(AnnotatedWildcardType wildcardType) { return hasExplicitExtendsBound(wildcardType); } From 7e5a309e256da90e19dd7fbc25ee54be5b58ea0a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 14:26:05 -0800 Subject: [PATCH 066/173] An instance receiver after a static access is not necessarily non-null Co-authored-by: Aosen Xiong <82676488+Ao-senXiong@users.noreply.github.com> Co-authored-by: Werner Dietl From 719d539cc14a859fa1638b7646eb6b149d343431 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 14:58:39 -0800 Subject: [PATCH 067/173] Documentation improvements --- .../dataflow/analysis/AbstractAnalysis.java | 4 ++-- docs/manual/creating-a-checker.tex | 11 ++++++++--- docs/manual/manual-style.tex | 4 ++-- docs/manual/manual.bbl | 7 +++++++ .../framework/flow/CFAbstractStore.java | 5 +++-- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java index 0d9a181c5f0..6fdb944d180 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java @@ -56,8 +56,8 @@ public abstract class AbstractAnalysis< protected @MonotonicNonNull ControlFlowGraph cfg; /** - * The transfer inputs of every basic block (assumed to be 'no information' if not present, inputs - * before blocks in forward analysis, after blocks in backward analysis). + * The transfer inputs of every basic block; assumed to be 'no information' if not present. The + * inputs are before blocks in forward analysis, and are after blocks in backward analysis. */ protected final IdentityHashMap> inputs = new IdentityHashMap<>(); diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index a85bbe83fec..deae3a68e8d 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -2154,10 +2154,15 @@ The graph also contains information about flow-sensitively refined types of various expressions at many program points. - The argument is a comma-separated sequence of keys or key--value pairs. - The first argument is the fully-qualified name of the + The argument is a comma-separated sequence. + The first element is the fully-qualified name of the \ implementation - that should be used. The remaining keys or key--value pairs are + that should be used. Currently, the possibilities are: + \begin{itemize} + \item \\refclass{org/checkerframework/dataflow/cfg/visualize}{DOTCFGVisualizer} + \item \\refclass{org/checkerframework/dataflow/cfg/visualize}{StringCFGVisualizer} + \end{itemize} + The remaining keys or key--value pairs are passed to \. Supported keys include \begin{itemize} \item \ diff --git a/docs/manual/manual-style.tex b/docs/manual/manual-style.tex index 188af0b0761..db3510d1fc6 100644 --- a/docs/manual/manual-style.tex +++ b/docs/manual/manual-style.tex @@ -150,7 +150,7 @@ % qualifier name and includes parentheses around the parameters. \newcommand{\refqualclasswithparams}[3]{\href{../api/org/checkerframework/#1/#2.html}{\<@#2>}\<(\allowbreak #3)>} -% Reference to Checker Framework Javadoc for a method or field.). +% Reference to Checker Framework Javadoc for a method or field. % Arg 1: package name under org/checkerframework, using "/" as a separator, % with no leading or trailing "/". example: "checker/nullness/qual" % Arg 2: class name. @@ -170,7 +170,7 @@ % ``Class.field''. The last argument is usually empty {}. \newcommand{\refenum}[4]{\href{../api/org/checkerframework/#1/#2.html\##3#4}{\<#3>}} -% Reference to Sun Javadoc. +% Reference to Oracle (previously Sun) Javadoc. % Arg 1: .html reference, without the .../api/ prefix % Arg 2: What will appear in the formatted manual. % Do not use \url in the body of any macro diff --git a/docs/manual/manual.bbl b/docs/manual/manual.bbl index 4015be92a7e..5d7380d1b16 100644 --- a/docs/manual/manual.bbl +++ b/docs/manual/manual.bbl @@ -585,6 +585,13 @@ Fausto Spoto and Michael~D. Ernst. \newblock In {\em ICSE 2011, Proceedings of the 33rd International Conference on Software Engineering}, pages 231--240, Waikiki, Hawaii, USA, May 2011. +\bibitem[SGT{\etalchar{+}}23]{ShadabGTEKLLS2023} +Narges Shadab, Pritam Gharat, Shrey Tiwari, Michael~D. Ernst, Martin Kellogg, + Shuvendu Lahiri, Akash Lal, and Manu Sridharan. +\newblock Inference of resource management specifications. +\newblock {\em Proceedings of the ACM on Programming Languages}, 7(OOPSLA2, + article \#282):1705--1728, October 2023. + \bibitem[She11]{Sherwany2011} Amanj Sherwany. \newblock The design, implementation and evaluation of a pluggable type checker diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index d00696dd1e6..b06413652d3 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1305,8 +1305,9 @@ public String toString() { @Override public String visualize(CFGVisualizer viz) { - /* This cast is guaranteed to be safe, as long as the CFGVisualizer is created by - * CFGVisualizer createCFGVisualizer() of GenericAnnotatedTypeFactory */ + // This cast is guaranteed to be safe, as long as the CFGVisualizer is created by + // CFGVisualizer createCFGVisualizer() of + // GenericAnnotatedTypeFactory. @SuppressWarnings("unchecked") CFGVisualizer castedViz = (CFGVisualizer) viz; String internal = internalVisualize(castedViz); From 3eb43431f8e05c0ae3137594afc067ea7c84f6ee Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 14:59:05 -0800 Subject: [PATCH 068/173] Rename `AbstractCFGVisualizer.visualizeBlockHelper()` to `visualizeBlockWithSeparator()` --- .../cfg/visualize/AbstractCFGVisualizer.java | 2 +- .../dataflow/cfg/visualize/DOTCFGVisualizer.java | 2 +- .../dataflow/cfg/visualize/StringCFGVisualizer.java | 2 +- docs/CHANGELOG.md | 13 +++++++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java index 7036fde635f..62baf2132a1 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java @@ -196,7 +196,7 @@ protected void addBlock(Block b, Set visited, Queue workList) { * {@link StringCFGVisualizer} * @return the String representation of the block */ - protected String visualizeBlockHelper( + protected String visualizeBlockWithSeparator( Block bb, @Nullable Analysis analysis, String separator) { StringBuilder sbBlock = new StringBuilder(); String contents = loopOverBlockContents(bb, analysis, separator); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java index d8da5ae0487..e49d0eb1baf 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java @@ -153,7 +153,7 @@ protected String visualizeEdge(Object sId, Object eId, String flowRule) { @Override public String visualizeBlock(Block bb, @Nullable Analysis analysis) { - return super.visualizeBlockHelper(bb, analysis, getSeparator()); + return super.visualizeBlockWithSeparator(bb, analysis, getSeparator()); } @Override diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java index ca81680ec0f..46891f908ed 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/StringCFGVisualizer.java @@ -97,7 +97,7 @@ protected String visualizeEdge(Object sId, Object eId, String flowRule) { @Override public String visualizeBlock(Block bb, @Nullable Analysis analysis) { - return super.visualizeBlockHelper(bb, analysis, lineSeparator).trim(); + return super.visualizeBlockWithSeparator(bb, analysis, lineSeparator).trim(); } @Override diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index adef21a2190..9103a98dfc7 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,16 @@ +Version 3.44.0 (February ??, 2024) +---------------------------------- + +**User-visible changes:** + +**Implementation details:** + +Renamed `AbstractCFGVisualizer.visualizeBlockHelper()` to +`visualizeBlockWithSeparator()`. + +**Closed issues:** + + Version 3.43.0 (January 2, 2024) -------------------------------- From 0733e1ba63a221763565ce4ca8f8fac42694d69c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 17 Feb 2024 15:00:27 -0800 Subject: [PATCH 069/173] Avoid crash when there are no edges to the exceptional exit --- checker/tests/calledmethods/CmCfgCrash.java | 6 +++++ .../cfg/visualize/AbstractCFGVisualizer.java | 23 ++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 checker/tests/calledmethods/CmCfgCrash.java diff --git a/checker/tests/calledmethods/CmCfgCrash.java b/checker/tests/calledmethods/CmCfgCrash.java new file mode 100644 index 00000000000..5cd97cf3602 --- /dev/null +++ b/checker/tests/calledmethods/CmCfgCrash.java @@ -0,0 +1,6 @@ +public class CmCfgCrash { + + static void f() { + for (int i = 0; ; ) {} + } +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java index 62baf2132a1..244a2288855 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java @@ -324,21 +324,28 @@ protected String visualizeBlockTransferInputHelper( storesFrom = analysis.getResult(); } else { TransferInput input = analysis.getInput(bb); - assert input != null : "@AssumeAssertion(nullness): invariant"; - storesFrom = input; - isTwoStores = input.containsTwoStores(); - regularStore = input.getRegularStore(); - thenStore = input.getThenStore(); - elseStore = input.getElseStore(); + // Per the documentation of AbstractAnalysis#inputs, null means no information. + if (input == null) { + regularStore = null; + storesFrom = null; + } else { + storesFrom = input; + isTwoStores = input.containsTwoStores(); + regularStore = input.getRegularStore(); + thenStore = input.getThenStore(); + elseStore = input.getElseStore(); + } } StringBuilder sbStore = new StringBuilder(); if (verbose) { - sbStore.append(storesFrom.getClassAndUid() + separator); + sbStore.append((storesFrom == null ? "null" : storesFrom.getClassAndUid()) + separator); } sbStore.append(where == VisualizeWhere.BEFORE ? "Before: " : "After: "); - if (!isTwoStores) { + if (regularStore == null) { + sbStore.append("()"); + } else if (!isTwoStores) { sbStore.append(visualizeStore(regularStore)); } else { assert thenStore != null : "@AssumeAssertion(nullness): invariant"; From 15b661b88cf96cf832e6c25a5da7c88723679587 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 18 Feb 2024 13:08:51 -0800 Subject: [PATCH 070/173] Improve punctuation --- .../checker/calledmethods/CalledMethodsAnalysis.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java index 607067af8ed..6eed186bdce 100644 --- a/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsAnalysis.java @@ -23,8 +23,8 @@ public class CalledMethodsAnalysis extends AccumulationAnalysis { // Use the Nullness Checker instead. NullPointerException.class.getCanonicalName(), // Ignore run-time errors, which cannot be predicted statically. Doing - // so is unsound in the sense that they could still occur - e.g., the - // program could run out of memory - but if they did, the checker's + // so is unsound in the sense that they could still occur -- e.g., the + // program could run out of memory -- but if they did, the checker's // results would be useless anyway. Error.class.getCanonicalName(), RuntimeException.class.getCanonicalName()); From 73c8c3957c4023b8e581fa5014b114b8beaa8606 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 18 Feb 2024 13:09:32 -0800 Subject: [PATCH 071/173] Make error message more explicit --- .../framework/type/NoElementQualifierHierarchy.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java index 3d8e177200c..0d35d4f94f2 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/NoElementQualifierHierarchy.java @@ -97,7 +97,10 @@ protected Map createAnnotationMirrors( Map quals = new TreeMap<>(); for (QualifierKind kind : qualifierKindHierarchy.allQualifierKinds()) { if (kind.hasElements()) { - throw new TypeSystemError(kind + " has elements"); + throw new TypeSystemError( + kind + + " has elements, so the checker cannot use NoElementQualifierHierarchy." + + " The checker should override createQualifierHierarchy()."); } quals.put(kind, AnnotationBuilder.fromClass(elements, kind.getAnnotationClass())); } From cf61444a6ed019b625c167f165d7f8513d6397a3 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 18 Feb 2024 13:28:28 -0800 Subject: [PATCH 072/173] Increase timeout From 541f2701718a975d0fd032b5536fdde0840c0728 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:29:47 -0800 Subject: [PATCH 073/173] Update plugin org.ajoberstar.grgit to v5.2.2 From d8476de436103b090c97944884a257905defa01c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 18 Feb 2024 13:40:48 -0800 Subject: [PATCH 074/173] Fix changelog --- docs/CHANGELOG.md | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9103a98dfc7..e823185ff00 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,18 +1,5 @@ -Version 3.44.0 (February ??, 2024) ----------------------------------- - -**User-visible changes:** - -**Implementation details:** - -Renamed `AbstractCFGVisualizer.visualizeBlockHelper()` to -`visualizeBlockWithSeparator()`. - -**Closed issues:** - - -Version 3.43.0 (January 2, 2024) --------------------------------- +Version 3.43.0 (?? ??, 2024) +---------------------------- **User-visible changes:** @@ -25,6 +12,9 @@ automatically as part of whole-program inference. **Implementation details:** +Renamed `AbstractCFGVisualizer.visualizeBlockHelper()` to +`visualizeBlockWithSeparator()`. + **Closed issues:** From 7b828fe7ab96eb0758550f4e4ae2fb58b2944dce Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Thu, 22 Feb 2024 20:19:46 -0800 Subject: [PATCH 075/173] Update manual with example of a compound checker (Signedness) --- docs/manual/creating-a-checker.tex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index deae3a68e8d..7e8baf58de7 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1109,6 +1109,10 @@ calling \refmethodterse{common/basetype}{BaseTypeChecker}{getTypeFactoryOfSubcheckerOrNull}{(java.lang.Class)}. +An example of a compound checker is +\refclass{checker/signedness}{SignednessChecker} +(see \chapterpageref{signedness-checker}) for which \refclass{common/value}{ValueChecker} +is a subchecker. \end{enumerate} From c4c38106e8a5a4cb15aa573adb88c47f9d7902f0 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Sat, 24 Feb 2024 08:43:08 -0800 Subject: [PATCH 076/173] Use `CFAbstractStore.replaceValue` in `MustCallTransfer` --- .../checkerframework/checker/mustcall/MustCallTransfer.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java index baea3862d71..97d709db3d9 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java @@ -155,8 +155,7 @@ private void lubWithStoreValue(CFStore store, JavaExpression expr, AnnotationMir CFValue defaultTypeAsCFValue = analysis.createSingleAnnotationValue(defaultType, expr.getType()); CFValue newValue = defaultTypeAsCFValue.leastUpperBound(value); - store.clearValue(expr); - store.insertValue(expr, newValue); + store.replaceValue(expr, newValue); } /* NO-AFU From 0ba1d2b03d0e1a0916d8a89e0af737673ef9d674 Mon Sep 17 00:00:00 2001 From: Hamed Taghani Date: Mon, 26 Feb 2024 01:57:58 -0500 Subject: [PATCH 077/173] Fix typo for word 'everything' in README file --- checker/tests/README | 2 +- docs/manual/contributors.tex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/checker/tests/README b/checker/tests/README index c634f569f03..54ad88051ef 100644 --- a/checker/tests/README +++ b/checker/tests/README @@ -107,7 +107,7 @@ If a warning rather than an error is expected, insert the line // :: warning: () If a stub parser warning is expected, insert the line //warning: AnnotationFileParser: -If multiple errors are expected on a single line, duplicate everthing +If multiple errors are expected on a single line, duplicate everything except the "//" comment characters, as in // :: error: () :: error: () If the expected failures line would be very long, you may break it across diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index 632e136b52a..adc85980150 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -46,6 +46,7 @@ Google Inc.\ (via @wmdietlGC), Haaris Ahmed, Haifeng Shi, +Hamed Taghani, Heath Borders, Jakub Vr\'ana, James Yoo, From e4fca8d0349010f6885e020d7e870ff3de185cae Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 26 Feb 2024 08:24:22 -0800 Subject: [PATCH 078/173] Fix visibility modifiers (#6455) Co-authored-by: Werner Dietl From 7a76c47440618f4e24c66f0241ee23c454da183f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 26 Feb 2024 08:26:16 -0800 Subject: [PATCH 079/173] Use consistent relocate clause (#6457) Co-authored-by: Werner Dietl From e3f38d90ca7db0723d7335b03b217928a0f520b3 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 26 Feb 2024 09:23:08 -0800 Subject: [PATCH 080/173] Fix rules for switch statement exhaustiveness (#6458) Co-authored-by: Werner Dietl --- .../cfg/builder/CFGTranslationPhaseOne.java | 2 +- .../checkerframework/javacutil/TreeUtils.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index ec6cf0ebf37..7b8acbbe9cf 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -2520,7 +2520,7 @@ private SwitchBuilder(Tree switchTree) { env.getTypeUtils())); } - // JSL 14.11.2 + // JLS 14.11.2 // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 // states "For compatibility reasons, switch statements that are not enhanced switch // statements are not required to be exhaustive". diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 4a6fe93adb5..8446259918f 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -2531,6 +2531,37 @@ public static boolean isYield(Tree tree) { return tree.getKind().name().equals("YIELD"); } + /** + * Returns true if the given switch statement tree is an enhanced switch statement, as described + * in JSL + * 14.11.2. + * + * @param switchTree the switch statement to check + * @return true if the given tree is an enhanced switch statement + */ + public static boolean isEnhancedSwitchStatement(SwitchTree switchTree) { + TypeMirror exprType = typeOf(switchTree.getExpression()); + // TODO: this should be only char, byte, short, int, Character, Byte, Short, Integer. Is the + // over-approximation a problem? + Element exprElem = TypesUtils.getTypeElement(exprType); + boolean isNotEnum = exprElem == null || exprElem.getKind() != ElementKind.ENUM; + if (!TypesUtils.isPrimitiveOrBoxed(exprType) && !TypesUtils.isString(exprType) && isNotEnum) { + return true; + } + + for (CaseTree caseTree : switchTree.getCases()) { + for (Tree caseLabel : CaseUtils.getLabels(caseTree)) { + if (caseLabel.getKind() == Tree.Kind.NULL_LITERAL + || TreeUtils.isBindingPatternTree(caseLabel) + || TreeUtils.isDeconstructionPatternTree(caseLabel)) { + return true; + } + } + } + + return false; + } + /** * Returns the value (expression) for {@code yieldTree}. * From a662743d9c55fb7abac99da0af8df4d64aad33a0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 26 Feb 2024 15:25:46 -0800 Subject: [PATCH 081/173] Improve naming and documentation in ObjectCreationNode (#6450) Co-authored-by: Alex Liu Co-authored-by: Werner Dietl --- docs/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e823185ff00..aa9a68f9fe0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,8 @@ automatically as part of whole-program inference. **Implementation details:** +Deprecated `ObjectCreationNode#getConstructor` in favor of new `ObjectCreationNode#getTypeToInstantiate()`. + Renamed `AbstractCFGVisualizer.visualizeBlockHelper()` to `visualizeBlockWithSeparator()`. From 7d02301a3a71bccda1fd923acd49c07fd048c5e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 21:06:28 +0000 Subject: [PATCH 082/173] Update plugin de.undercouch.download to v5.6.0 (#6470) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 3e95a8ad7355a130221da27f7f4fdd477c20a606 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Fri, 1 Mar 2024 00:12:27 -0800 Subject: [PATCH 083/173] Update dev manual instructions for Azure CI --- docs/developer/developer-manual.html | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/developer/developer-manual.html b/docs/developer/developer-manual.html index 5ec91cf9529..f44fb93e24f 100644 --- a/docs/developer/developer-manual.html +++ b/docs/developer/developer-manual.html @@ -42,6 +42,7 @@

          Checker Framework developer manualGitHub configuration

        11. Continuous Integration
        12. Documenting refactoring ideas
        13. @@ -389,7 +390,11 @@

          Continuous Integration

        14. Browse to dev.azure.com. (You might need to create a (free) account.)
        15. Click "Create a project to get started" and enter an appropriate name
        16. -
        17. Click "public".
        18. +
        19. Click "public". + +
        20. Click "create project"
        21. At the left, click the blue rocket labeled "pipelines"
        22. Click "new pipeline"
        23. @@ -414,6 +419,18 @@

          Continuous Integration

        24. Find "checker-framework" in the list, and click the slider to enable it.
        25. +

          How to change project visibility settings

          + +

          +To change your project's visibility settings (usually to "Public" to enable CI): +

          + +
            +
          • Browse to your project on dev.azure.com.
          • +
          • Click the "Project Settings" button.
          • +
          • Click the "Visibility" dropdown.
          • +
          • Click "Public".
          • +

          What to do if a Continuous Integration build fails

          From b66aa500467feb2523ab2c6dc53c52fe6c204215 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:12:40 -0800 Subject: [PATCH 084/173] Update dependency com.amazonaws:aws-java-sdk-bom to v1.12.670 (#6472) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- checker/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checker/build.gradle b/checker/build.gradle index 421614242d4..e53d583acdc 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -80,7 +80,7 @@ dependencies { testImplementation 'com.amazonaws:aws-java-sdk-ec2' testImplementation 'com.amazonaws:aws-java-sdk-kms' // The AWS SDK is used for testing the Called Methods Checker. - testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.651') + testImplementation platform('com.amazonaws:aws-java-sdk-bom:1.12.670') // For the Resource Leak Checker's support for JavaEE. testImplementation 'javax.servlet:javax.servlet-api:4.0.1' // For the Resource Leak Checker's support for IOUtils. From b783ab48105146c625747912446154caf1268634 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 1 Mar 2024 09:52:11 -0800 Subject: [PATCH 085/173] Rewrite BoundsInitializer --- .../framework/type/AnnotatedTypeFactory.java | 2 +- .../framework/type/AnnotatedTypeMirror.java | 58 +- .../framework/type/BoundsInitializer.java | 1420 ++--------------- .../framework/type/TypeFromMemberVisitor.java | 2 + .../type/TypeFromTypeTreeVisitor.java | 32 +- .../dependenttypes/DependentTypesHelper.java | 3 + framework/tests/all-systems/Issue6373.java | 89 ++ .../javacutil/TypesUtils.java | 21 + 8 files changed, 350 insertions(+), 1277 deletions(-) create mode 100644 framework/tests/all-systems/Issue6373.java diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 199cce61ff1..123a064d590 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -3717,7 +3717,7 @@ protected final AnnotatedTypeMirror toAnnotatedType(TypeMirror t, boolean declar * @return the type of {@code tree}, without any annotations */ protected final AnnotatedTypeMirror type(Tree tree) { - boolean isDeclaration = TreeUtils.isTypeDeclaration(tree); + boolean isDeclaration = TreeUtils.isClassTree(tree); // Attempt to obtain the type via JCTree. if (TreeUtils.typeOf(tree) != null) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index 2bee0ff0e6b..0fb9ec44666 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -1070,26 +1070,54 @@ public void setTypeArguments(List ts) { } /** - * Returns the type argument for this type. + * Returns the type arguments for this type. * - * @return the type argument for this type + * @return the type arguments for this type */ public List getTypeArguments() { if (typeArgs != null) { return typeArgs; - } else if (isUnderlyingTypeRaw()) { - // Initialize the type arguments with wildcards marks as type arguments from raw - // types. - BoundsInitializer.initializeTypeArgs(this); - return typeArgs; - } else if (getUnderlyingType().getTypeArguments().isEmpty()) { - typeArgs = Collections.emptyList(); - return typeArgs; + } + + DeclaredType t = getUnderlyingType(); + typeArgs = new ArrayList<>(t.getTypeArguments().size()); + + if (isUnderlyingTypeRaw()) { + TypeElement typeElement = (TypeElement) atypeFactory.types.asElement(t); + Map typeParameterToWildcard = new HashMap<>(); + for (TypeParameterElement typeParameterEle : typeElement.getTypeParameters()) { + TypeVariable typeParameterVar = (TypeVariable) typeParameterEle.asType(); + TypeMirror wildcard = + BoundsInitializer.getUpperBoundAsWildcard(typeParameterVar, atypeFactory.types); + AnnotatedWildcardType atmWild = + (AnnotatedWildcardType) AnnotatedTypeMirror.createType(wildcard, atypeFactory, false); + atmWild.setTypeArgOfRawType(); + BoundsInitializer.initializeBounds(atmWild); + typeArgs.add(atmWild); + typeParameterToWildcard.put(typeParameterVar, atmWild); + } + TypeVariableSubstitutor suber = atypeFactory.getTypeVarSubstitutor(); + for (AnnotatedTypeMirror atm : typeArgs) { + AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) atm; + wildcardType.setExtendsBound( + suber.substituteWithoutCopyingTypeArguments( + typeParameterToWildcard, wildcardType.getExtendsBound())); + } + } else if (isDeclaration()) { + for (TypeMirror javaTypeArg : t.getTypeArguments()) { + AnnotatedTypeVariable tv = + (AnnotatedTypeVariable) + AnnotatedTypeMirror.createType(javaTypeArg, atypeFactory, true); + typeArgs.add(tv); + } } else { - // Initialize type argument for a non-raw declared type that has type arguments/ - BoundsInitializer.initializeTypeArgs(this); - return typeArgs; + for (TypeMirror javaTypeArg : t.getTypeArguments()) { + AnnotatedTypeMirror typeArg = + AnnotatedTypeMirror.createType(javaTypeArg, atypeFactory, false); + typeArgs.add(typeArg); + } } + return typeArgs; } /** @@ -2205,7 +2233,7 @@ public AnnotatedTypeMirror getSuperBoundField() { */ public AnnotatedTypeMirror getSuperBound() { if (superBound == null) { - BoundsInitializer.initializeSuperBound(this); + BoundsInitializer.initializeBounds(this); fixupBoundAnnotations(); } return this.superBound; @@ -2235,7 +2263,7 @@ public AnnotatedTypeMirror getExtendsBoundField() { */ public AnnotatedTypeMirror getExtendsBound() { if (extendsBound == null) { - BoundsInitializer.initializeExtendsBound(this); + BoundsInitializer.initializeBounds(this); fixupBoundAnnotations(); } return this.extendsBound; diff --git a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java index 89af6fc9449..a9240ef177c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java @@ -1,1384 +1,304 @@ package org.checkerframework.framework.type; -import com.sun.tools.javac.code.Symtab; -import com.sun.tools.javac.processing.JavacProcessingEnvironment; -import com.sun.tools.javac.util.Context; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.ExecutableType; import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.TypeVisitor; +import javax.lang.model.type.UnionType; import javax.lang.model.type.WildcardType; -import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.checker.nullness.qual.Nullable; +import javax.lang.model.util.Types; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; -import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor; -import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TypeAnnotationUtils; import org.checkerframework.javacutil.TypesUtils; -import org.plumelib.util.IPair; -import org.plumelib.util.StringsPlume; +import org.plumelib.util.CollectionsPlume; /** - * BoundsInitializer creates AnnotatedTypeMirrors (without annotations) for the bounds of type - * variables and wildcards. Its static helper methods are called from AnnotatedTypeMirror. When an - * initializer method is called for a particular bound, the entirety of that bound, including - * circular references, will be created. + * {@code BoundsInitializer} creates AnnotatedTypeMirrors (without annotations) for the bounds of + * type variables and wildcards. It ensures that recursive type variables refer to themselves at the + * correct location. Other kinds of composite types are lazily initialized in {@code + * AnnotatedTypeMirror}. + * + *

          Its static helper methods are called from AnnotatedTypeMirror. When an initializer method is + * called for a particular bound, the entirety of that bound, including circular references, is + * created. */ public class BoundsInitializer { - // ============================================================================================ - // Static helper methods called from AnnotatedTypeMirror to initialize bounds of wildcards or - // type variables - // ============================================================================================ - /** - * Initializes the type arguments of {@code declaredType}. The upper bound of unbound wildcards is - * set to the upper bound of the type parameter for which it is an argument. If {@code - * declaredType} is raw, then the type arguments are wildcards marked as from raw type ({@link - * AnnotatedWildcardType#isTypeArgOfRawType()}). - * - * @param declaredType type whose arguments are initialized - */ - public static void initializeTypeArgs(AnnotatedDeclaredType declaredType) { - DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType; - if (underlyingType.getTypeArguments().isEmpty() && !declaredType.isUnderlyingTypeRaw()) { - // No type arguments to initialize. - return; - } - - TypeElement typeElement = - (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType); - int numTypeParameters = typeElement.getTypeParameters().size(); - List typeArgs = new ArrayList<>(numTypeParameters); - - // Create AnnotatedTypeMirror for each type argument and store them in the typeArgsMap. - // Take un-annotated type variables as the key for this map. - Map typeArgMap = new HashMap<>(numTypeParameters); - for (int i = 0; i < numTypeParameters; i++) { - TypeMirror javaTypeArg; - if (declaredType.isUnderlyingTypeRaw()) { - TypeVariable typeVariable = (TypeVariable) typeElement.getTypeParameters().get(i).asType(); - javaTypeArg = getUpperBoundAsWildcard(typeVariable, declaredType.atypeFactory); - } else { - javaTypeArg = declaredType.getUnderlyingType().getTypeArguments().get(i); - } - - AnnotatedTypeMirror typeArg = - AnnotatedTypeMirror.createType( - javaTypeArg, declaredType.atypeFactory, declaredType.isDeclaration()); - if (typeArg.getKind() == TypeKind.WILDCARD) { - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg; - wildcardType.setTypeVariable(typeElement.getTypeParameters().get(i)); - if (declaredType.isUnderlyingTypeRaw()) { - wildcardType.setTypeArgOfRawType(); - } - } - typeArgs.add(typeArg); - - // Add mapping from type parameter to the annotated type argument. - TypeVariable key = - (TypeVariable) - TypeAnnotationUtils.unannotatedType(typeElement.getTypeParameters().get(i).asType()); - typeArgMap.put(key, typeArg); - - if (javaTypeArg.getKind() == TypeKind.TYPEVAR) { - // Add mapping from Java type argument to the annotated type argument. - key = (TypeVariable) TypeAnnotationUtils.unannotatedType(javaTypeArg); - typeArgMap.put(key, typeArg); - } - } - - // Initialize type argument bounds using the typeArgsMap. - for (AnnotatedTypeMirror typeArg : typeArgs) { - switch (typeArg.getKind()) { - case WILDCARD: - AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) typeArg; - initializeExtendsBound(wildcardType, typeArgMap); - initializeSuperBound(wildcardType, typeArgMap); - break; - case TYPEVAR: - initializeBounds((AnnotatedTypeVariable) typeArg, typeArgMap); - break; - default: - // do nothing - } - } - declaredType.typeArgs = Collections.unmodifiableList(typeArgs); - } - - /** - * Returns a wildcard whose upper bound is the same as {@code typeVariable}. If the upper bound is - * an intersection, then this method returns an unbound wildcard. - */ - private static WildcardType getUpperBoundAsWildcard( - TypeVariable typeVariable, AnnotatedTypeFactory factory) { - TypeMirror upperBound = typeVariable.getUpperBound(); - switch (upperBound.getKind()) { - case ARRAY: - case DECLARED: - case TYPEVAR: - return factory.types.getWildcardType(upperBound, null); - case INTERSECTION: - // Can't create a wildcard with an intersection as the upper bound, so use - // an unbound wildcard instead. The extends bound of the - // AnnotatedWildcardType will be initialized properly by this class. - return factory.types.getWildcardType(null, null); - default: - throw new BugInCF( - "Unexpected upper bound kind: %s type: %s", upperBound.getKind(), upperBound); - } + /** Class cannot be instantiated. */ + private BoundsInitializer() { + throw new AssertionError("Class BoundsInitializer cannot be instantiated."); } /** - * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a - * typeVar is recursive the appropriate cycles will be introduced in the type + * Creates and sets the upper and lower bounds of {@code typeVar} that match the upper and lower + * bounds of the underlying type of {@code typeVar}. * - * @param typeVar the type variable whose lower bound is being initialized + * @param typeVar an {@link AnnotatedTypeVariable} */ public static void initializeBounds(AnnotatedTypeVariable typeVar) { - initializeBounds(typeVar, Collections.singletonMap(typeVar.getUnderlyingType(), typeVar)); - } - - /** - * Create the entire lower bound and upper bound, with no missing information, for typeVar. If a - * typeVar is recursive the appropriate cycles will be introduced in the type - * - * @param typeVar the type variable whose lower bound is being initialized - * @param map a mapping of type parameters to type arguments. May be null. - */ - private static void initializeBounds( - AnnotatedTypeVariable typeVar, @Nullable Map map) { - AnnotationMirrorSet annos = saveAnnotations(typeVar); - - InitializerVisitor visitor = new InitializerVisitor(new TypeVariableStructure(typeVar), map); - visitor.initializeLowerBound(typeVar); - visitor.resolveTypeVarReferences(typeVar); - - InitializerVisitor visitor2 = new InitializerVisitor(new TypeVariableStructure(typeVar), map); - visitor2.initializeUpperBound(typeVar); - visitor2.resolveTypeVarReferences(typeVar); - - restoreAnnotations(typeVar, annos); - } - - /** - * Returns a type's primary annotations, and clears those annotations. - * - *

          If we are initializing a type variable with a primary annotation than we should first - * initialize it as if it were a declaration (i.e. as if it had no primary annotations) and then - * apply the primary annotations. We do this so that when we make copies of the original type to - * represent recursive references the recursive references don't have the primary annotation. - * - *

          Example: The declaration {@code >}.
          - * If we do not do this, the NonNull on the use @NonNull E would be copied to the primary - * annotation on E in the bound {@code List}.
          - * i.e. the use would be {@code <@NonNull E extends @NonNull List<@NonNull E>>}
          - * rather than {@code <@NonNull E extends @NonNull List>} - * - * @param type a type whose annotations to read, clear, and return - * @return the original primary annotations on {@code type}, or null if none - */ - private static @Nullable AnnotationMirrorSet saveAnnotations(AnnotatedTypeMirror type) { - if (!type.getAnnotationsField().isEmpty()) { - AnnotationMirrorSet annos = new AnnotationMirrorSet(type.getAnnotations()); - type.clearAnnotations(); - return annos; - } - - return null; - } - - private static void restoreAnnotations(AnnotatedTypeMirror type, AnnotationMirrorSet annos) { - if (annos != null) { - type.addAnnotations(annos); - } - } - - /** - * Create the entire super bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type - * - * @param wildcard the wildcard whose lower bound is being initialized - */ - public static void initializeSuperBound(AnnotatedWildcardType wildcard) { - initializeSuperBound(wildcard, null); - } - - /** - * Create the entire super bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type - * - * @param wildcard the wildcard whose lower bound is being initialized - * @param map a mapping of type parameters to type arguments. May be null. - */ - private static void initializeSuperBound( - AnnotatedWildcardType wildcard, @Nullable Map map) { - AnnotationMirrorSet annos = saveAnnotations(wildcard); - - InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map); - visitor.initializeSuperBound(wildcard); - visitor.resolveTypeVarReferences(wildcard); - - restoreAnnotations(wildcard, annos); + new BoundInitializerVisitor(typeVar.atypeFactory).initializeTypeVariable(typeVar); } /** - * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type + * Creates and sets the extends and super bounds of {@code wildcard} that match the extends and + * super bounds of the underlying type of {@code wildcard}. * - * @param wildcard the wildcard whose extends bound is being initialized + * @param wildcard an {@link AnnotatedWildcardType} */ - public static void initializeExtendsBound(AnnotatedWildcardType wildcard) { - initializeExtendsBound(wildcard, null); + public static void initializeBounds(AnnotatedWildcardType wildcard) { + new BoundInitializerVisitor(wildcard.atypeFactory).initializeWildcard(wildcard); } /** - * Create the entire extends bound, with no missing information, for wildcard. If a wildcard is - * recursive the appropriate cycles will be introduced in the type + * Returns a wildcard whose extends bound is the same as {@code typeVariable}'s upper bound. If + * the upper bound is an intersection, then this method returns an unbound wildcard. * - * @param wildcard the wildcard whose extends bound is being initialized - * @param map a mapping of type parameters to type arguments. May be null. + * @param typeVariable a type variable + * @param types types util + * @return a wildcard whose extends bound is the same as the upper bound of {@code typeVariable} */ - private static void initializeExtendsBound( - AnnotatedWildcardType wildcard, @Nullable Map map) { - AnnotationMirrorSet annos = saveAnnotations(wildcard); - InitializerVisitor visitor = new InitializerVisitor(new RecursiveTypeStructure(), map); - visitor.initializeExtendsBound(wildcard); - visitor.resolveTypeVarReferences(wildcard); - restoreAnnotations(wildcard, annos); - } - - // ============================================================================================ - // Classes and methods used to make the above static helper methods work - // ============================================================================================ - - /** - * Creates the AnnotatedTypeMirrors (without annotations) for the bounds of all type variables and - * wildcards in a given type. If the type is recursive, {@code T extends Comparable}, then all - * references to the same type variable are references to the same AnnotatedTypeMirror. - */ - private static class InitializerVisitor implements AnnotatedTypeVisitor { - /** - * The {@link RecursiveTypeStructure} corresponding to the first wildcard or type variable bound - * initialization that kicked this visitation off. - */ - private final RecursiveTypeStructure topLevelStructure; - - /** - * The {@link RecursiveTypeStructure} corresponding to the wildcard or type variable that is - * currently being visited. - */ - private RecursiveTypeStructure currentStructure; - - /** A mapping from TypeVariable to its {@link TypeVariableStructure}. */ - private final Map typeVarToStructure = new HashMap<>(); - - /** - * A mapping from WildcardType to its AnnotatedWildcardType. The first time this visitor - * encounters a wildcard it creates an annotated type and adds it to this map. The next time the - * wilcard is encounter, the annotated type in this map is returned. - */ - private final Map wildcards = new HashMap<>(); - - /** - * A mapping from IntersectionType to its AnnotatedIntersectionType. The first time this visitor - * encounters an intersection it creates an annotated type and adds it to this map. The next - * time the intersection is encounter, the annotated type in this map is returned. - */ - private final Map intersections = new HashMap<>(); - - /** - * Mapping from TypeVariable to AnnotatedTypeMirror. The annotated type mirror should be used - * for any use of the type variable rather than creating and initializing a new annotated type. - * This is used for type arguments that have already been initialized outside of this visitor. - */ - private final Map typevars; - - /** - * Creates an InitializerVisitor. - * - * @param recursiveTypeStructure structure for the type being initialized - * @param typevars a mapping from type variable to annotated types that have already been - * initialized - */ - public InitializerVisitor( - RecursiveTypeStructure recursiveTypeStructure, - Map typevars) { - this.topLevelStructure = recursiveTypeStructure; - this.currentStructure = recursiveTypeStructure; - if (typevars != null) { - this.typevars = typevars; - } else { - this.typevars = Collections.emptyMap(); - } - if (recursiveTypeStructure instanceof TypeVariableStructure) { - TypeVariableStructure typeVarStruct = (TypeVariableStructure) recursiveTypeStructure; - typeVarToStructure.put(typeVarStruct.typeVar, typeVarStruct); - } - } - - // ---------------------------------------------------------------------------------------- - // Visit methods that keep track of the path traversed through type variable bounds, and the - // wildcards/intersections that have been encountered. - // ---------------------------------------------------------------------------------------- - - @Override - public Void visit(AnnotatedTypeMirror type) { - type.accept(this, null); - return null; - } - - @Override - public Void visit(AnnotatedTypeMirror type, Void aVoid) { - visit(type); - return null; - } - - @Override - public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { - initializeTypeArgs(type); - if (type.enclosingType != null) { - TypePathNode node = currentStructure.addPathNode(new EnclosingTypeNode()); - visit(type.enclosingType); - currentStructure.removePathNode(node); - } - return null; - } - - @Override - public Void visitIntersection(AnnotatedIntersectionType type, Void aVoid) { - if (intersections.containsKey(type.getUnderlyingType())) { - return null; - } - - intersections.put(type.getUnderlyingType(), type); - - List bounds = type.getBounds(); - for (int i = 0; i < bounds.size(); i++) { - AnnotatedTypeMirror supertype = bounds.get(i); - TypePathNode node = currentStructure.addPathNode(new IntersectionBoundNode(i)); - visit(supertype); - currentStructure.removePathNode(node); - } - return null; - } - - @Override - public Void visitUnion(AnnotatedUnionType type, Void aVoid) { - List alts = type.getAlternatives(); - for (int i = 0; i < alts.size(); i++) { - AnnotatedDeclaredType alt = alts.get(i); - TypePathNode node = currentStructure.addPathNode(new AlternativeTypeNode(i)); - visit(alt); - currentStructure.removePathNode(node); - } - return null; - } - - @Override - public Void visitArray(AnnotatedArrayType type, Void aVoid) { - if (!TypesUtils.isPrimitive(type.getComponentType().getUnderlyingType())) { - // Only recur on component type if it's not a primitive. - // Array component types are the only place a primitive is allowed in bounds - TypePathNode componentNode = currentStructure.addPathNode(new ArrayComponentNode()); - type.setComponentType(getOrVisit(type.getComponentType())); - currentStructure.removePathNode(componentNode); - } - return null; - } - - @Override - public Void visitTypeVariable(AnnotatedTypeVariable type, Void aVoid) { - this.currentStructure.addTypeVar(type.getUnderlyingType()); - if (typeVarToStructure.containsKey(type.getUnderlyingType())) { - return null; - } - TypeVariableStructure typeVarStruct = new TypeVariableStructure(type); - typeVarToStructure.put(type.getUnderlyingType(), typeVarStruct); - RecursiveTypeStructure parentStructure = this.currentStructure; - - // If type is a captured type variable, then its type variables should be created new, - // rather than using one from the rest of the type. So, clear the typevars map of all - // but the mapping with key type. - Map hold = new HashMap<>(); - if (TypesUtils.isCapturedTypeVariable(type.getUnderlyingType())) { - for (Map.Entry entry : - new ArrayList<>(typevars.entrySet())) { - if (!type.atypeFactory.types.isSameType( - entry.getKey(), entry.getValue().underlyingType)) { - hold.put(entry.getKey(), entry.getValue()); - typevars.remove(entry.getKey(), entry.getValue()); - } - } - } - this.currentStructure = typeVarStruct; - initializeUpperBound(type); - initializeLowerBound(type); - this.currentStructure = parentStructure; - typevars.putAll(hold); - - return null; - } - - @Override - public Void visitNull(AnnotatedNullType type, Void aVoid) { - return null; - } - - @Override - public Void visitWildcard(AnnotatedWildcardType wildcard, Void aVoid) { - if (wildcard.getSuperBoundField() == null) { - initializeSuperBound(wildcard); - } else { - throw new BugInCF( - "Wildcard super field should not be initialized:%n" - + "wildcard=%s%n" - + "currentStructure=%s%n", - wildcard, currentStructure); - } - - if (wildcard.getExtendsBoundField() == null) { - initializeExtendsBound(wildcard); - } else { + public static WildcardType getUpperBoundAsWildcard(TypeVariable typeVariable, Types types) { + TypeMirror upperBound = typeVariable.getUpperBound(); + switch (upperBound.getKind()) { + case ARRAY: + case DECLARED: + case TYPEVAR: + return types.getWildcardType(upperBound, null); + case INTERSECTION: + // Can't create a wildcard with an intersection as an extends bound, so use + // an unbound wildcard instead. + return types.getWildcardType(null, null); + default: throw new BugInCF( - "Wildcard extends field should not be initialized:%n" - + "wildcard=%s%n" - + "currentStructure=%s%n", - wildcard, currentStructure); - } - - return null; - } - - @Override - public Void visitPrimitive(AnnotatedPrimitiveType type, Void aVoid) { - throw new BugInCF("Unexpected AnnotatedPrimitiveType " + type); - } - - @Override - public Void visitNoType(AnnotatedNoType type, Void aVoid) { - throw new BugInCF("Unexpected AnnotatedNoType " + type); - } - - @Override - public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { - throw new BugInCF("Unexpected AnnotatedExecutableType " + type); - } - - /** - * If the underlying type of {@code type} has been visited before, return the previous - * AnnotatedTypeMirror. Otherwise, visit {@code type} and return it. - * - * @param type type to visit - * @return {@code type} or an AnnotatedTypeMirror with the same underlying type that was - * previously visited. - */ - public AnnotatedTypeMirror getOrVisit(AnnotatedTypeMirror type) { - switch (type.getKind()) { - case WILDCARD: - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; - if (wildcards.containsKey(wildcard.getUnderlyingType())) { - return wildcards.get(wildcard.getUnderlyingType()); - } - break; - case INTERSECTION: - if (intersections.containsKey(type.getUnderlyingType())) { - return intersections.get(type.getUnderlyingType()); - } - break; - case TYPEVAR: - TypeVariable key = - (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()); - if (typevars.containsKey(key)) { - return typevars.get(key); - } - break; - default: - // do nothing - } - visit(type); - return type; - } - - // ---------------------------------------------------------------------------------------- - // - - /** - * Initialize {@code typeVar}'s upper bound. - * - * @param typeVar type variable whose upper bound is initialized - */ - public void initializeUpperBound(AnnotatedTypeVariable typeVar) { - AnnotatedTypeMirror upperBound = createAndSetUpperBound(typeVar); - - TypePathNode pathNode = new UpperBoundNode(); - currentStructure.addPathNode(pathNode); - visit(upperBound); - currentStructure.removePathNode(pathNode); - } - - /** - * Initialize {@code typeVar}'s lower bound. - * - * @param typeVar type variable whose lower bound is initialized - */ - public void initializeLowerBound(AnnotatedTypeVariable typeVar) { - AnnotatedTypeMirror lowerBound = createAndSetLowerBound(typeVar); - - TypePathNode pathNode = new LowerBoundNode(); - currentStructure.addPathNode(pathNode); - visit(lowerBound); - currentStructure.removePathNode(pathNode); - } - - /** - * Initialize {@code wildcard}'s super bound. - * - * @param wildcard wildcard whose super bound is initialized - */ - public void initializeSuperBound(AnnotatedWildcardType wildcard) { - AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; - - WildcardType underlyingType = wildcard.getUnderlyingType(); - TypeMirror underlyingSuperBound = underlyingType.getSuperBound(); - if (underlyingSuperBound == null) { - underlyingSuperBound = - TypesUtils.wildLowerBound(underlyingType, wildcard.atypeFactory.processingEnv); - } - - AnnotatedTypeMirror superBound = - AnnotatedTypeMirror.createType(underlyingSuperBound, typeFactory, false); - wildcard.setSuperBound(superBound); - - this.wildcards.put(wildcard.getUnderlyingType(), wildcard); - - TypePathNode superNode = currentStructure.addPathNode(new SuperBoundNode()); - visit(superBound); - currentStructure.removePathNode(superNode); - } - - /** - * Initialize {@code wildcard}'s extends bound. - * - * @param wildcard wildcard whose extends bound is initialized - */ - public void initializeExtendsBound(AnnotatedWildcardType wildcard) { - AnnotatedTypeFactory typeFactory = wildcard.atypeFactory; - - WildcardType javaWildcardType = wildcard.getUnderlyingType(); - TypeMirror javaExtendsBound; - if (javaWildcardType.getExtendsBound() != null) { - javaExtendsBound = javaWildcardType.getExtendsBound(); - } else { - javaExtendsBound = TypesUtils.getObjectTypeMirror(typeFactory.processingEnv); - } - - if (wildcard.isTypeArgOfRawType()) { - rawTypeWildcards.put(wildcard.getTypeVariable(), wildcard.getUnderlyingType()); - } - - AnnotatedTypeMirror extendsBound = - AnnotatedTypeMirror.createType(javaExtendsBound, typeFactory, false); - wildcard.setExtendsBound(extendsBound); - - this.wildcards.put(wildcard.getUnderlyingType(), wildcard); - - TypePathNode extendsNode = currentStructure.addPathNode(new ExtendsBoundNode()); - visit(extendsBound); - currentStructure.removePathNode(extendsNode); - } - - /** - * Initialize {@code declaredType}'s type arguments. - * - * @param declaredType declared type whose type arguments are initialized - */ - private void initializeTypeArgs(AnnotatedDeclaredType declaredType) { - DeclaredType underlyingType = (DeclaredType) declaredType.underlyingType; - if (underlyingType.getTypeArguments().isEmpty() && !declaredType.isUnderlyingTypeRaw()) { - return; - } - TypeElement typeElement = - (TypeElement) declaredType.atypeFactory.types.asElement(underlyingType); - List typeArgs; - if (declaredType.typeArgs == null) { - int numTypeParameters = typeElement.getTypeParameters().size(); - typeArgs = new ArrayList<>(numTypeParameters); - for (int i = 0; i < numTypeParameters; i++) { - TypeMirror javaTypeArg = getJavaType(declaredType, typeElement.getTypeParameters(), i); - AnnotatedTypeMirror atmArg = - AnnotatedTypeMirror.createType(javaTypeArg, declaredType.atypeFactory, false); - typeArgs.add(atmArg); - if (atmArg.getKind() == TypeKind.WILDCARD && declaredType.isUnderlyingTypeRaw()) { - ((AnnotatedWildcardType) atmArg).setTypeArgOfRawType(); - } - } - } else { - typeArgs = declaredType.typeArgs; - } - - List typeArgReplacements = new ArrayList<>(typeArgs.size()); - for (int i = 0; i < typeArgs.size(); i++) { - AnnotatedTypeMirror typeArg = typeArgs.get(i); - TypePathNode node = currentStructure.addPathNode(new TypeArgNode(i)); - if (typeArg.getKind() == TypeKind.WILDCARD) { - ((AnnotatedWildcardType) typeArg).setTypeVariable(typeElement.getTypeParameters().get(i)); - } - typeArgReplacements.add(getOrVisit(typeArg)); - currentStructure.removePathNode(node); - } - - declaredType.setTypeArguments(typeArgReplacements); - } - - /** - * Store the wildcards created as type arguments to raw types. - * - *

          {@code class Foo {}} The upper bound of the wildcard in {@code Foo} is - * {@code Foo}. The type argument of {@code Foo} is initialized to {@code ? extends Foo}. The - * type argument of {@code Foo} in {@code ? extends Foo} needs to be initialized to the same - * type argument as the first {@code Foo} so that - * BoundsInitializer.InitializerVisitor#getOrVisit will return the cached AnnotatedWildcardType. - */ - private final Map rawTypeWildcards = new HashMap<>(); - - /** - * Returns the underlying Java type of the {@code i}-th type argument of {@code type}. If {@code - * type} is raw, then a new wildcard is created or returned from {@code rawTypeWildcards}. - * - * @param type declared type - * @param parameters elements of the type parameters - * @param i index of the type parameter - * @return the underlying Java type of the {@code i}-th type argument of {@code type} - */ - private TypeMirror getJavaType( - AnnotatedDeclaredType type, List parameters, int i) { - if (type.isUnderlyingTypeRaw()) { - TypeVariable typeVariable = (TypeVariable) parameters.get(i).asType(); - if (rawTypeWildcards.containsKey(typeVariable)) { - return rawTypeWildcards.get(typeVariable); - } - WildcardType wildcard = getUpperBoundAsWildcard(typeVariable, type.atypeFactory); - rawTypeWildcards.put(typeVariable, wildcard); - return wildcard; - } else { - return type.getUnderlyingType().getTypeArguments().get(i); - } - } - - /** - * Replace all type variables in type with the AnnotatedTypeMirrors created when initializing - * it. - * - * @param type all type variables are replaced - */ - public void resolveTypeVarReferences(AnnotatedTypeMirror type) { - List annotatedTypeVars = new ArrayList<>(); - if (type.getKind() == TypeKind.TYPEVAR) { - annotatedTypeVars.add((AnnotatedTypeVariable) type); - } - - // Gather a list of all AnnotatedTypeVariables and all the replacements to perform. - for (TypeVariableStructure typeVarStruct : typeVarToStructure.values()) { - typeVarStruct.findAllReplacements(typeVarToStructure); - annotatedTypeVars.addAll(typeVarStruct.getAnnotatedTypeVars()); - } - - // Do the replacements. - for (AnnotatedTypeVariable atv : annotatedTypeVars) { - TypeVariableStructure list = typeVarToStructure.get(atv.getUnderlyingType()); - list.replaceTypeVariablesInType(atv); - list.annotatedTypeVar = atv; - } - - if (type.getKind() == TypeKind.WILDCARD) { - // Do the "top level" replacements. - AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type; - topLevelStructure.findAllReplacements(typeVarToStructure); - topLevelStructure.replaceTypeVariablesInType(wildcard); - } - } - } - - /** - * Creates the upper bound type for {@code typeVar} and sets it. - * - * @param typeVar type variable - * @return the newly created upper bound - */ - private static AnnotatedTypeMirror createAndSetUpperBound(AnnotatedTypeVariable typeVar) { - AnnotatedTypeMirror upperBound = - AnnotatedTypeMirror.createType( - typeVar.getUnderlyingType().getUpperBound(), typeVar.atypeFactory, false); - typeVar.setUpperBound(upperBound); - return upperBound; - } - - /** - * Creates the lower bound type for {@code typeVar} and sets it. If the type variable does not - * have a lower bound, then a null type is created. - * - * @param typeVar type variable - * @return the newly created lower bound - */ - private static AnnotatedTypeMirror createAndSetLowerBound(AnnotatedTypeVariable typeVar) { - TypeMirror lb = typeVar.getUnderlyingType().getLowerBound(); - if (lb == null) { - // Use bottom type to ensure there is a lower bound. - Context context = - ((JavacProcessingEnvironment) typeVar.atypeFactory.processingEnv).getContext(); - Symtab syms = Symtab.instance(context); - lb = syms.botType; + "Unexpected upper bound kind: %s type: %s", upperBound.getKind(), upperBound); } - AnnotatedTypeMirror lowerBound = - AnnotatedTypeMirror.createType(lb, typeVar.atypeFactory, false); - typeVar.setLowerBound(lowerBound); - return lowerBound; } /** - * Contains all the type variables and the type path to reach them found when scanning a - * particular type variable or wildcard. Then uses this information to replace the type variables - * with AnnotatedTypeVariables. + * A class that creates an {@link AnnotatedTypeMirror} to match a TypeMirror. This visitor is only + * used to initialize recursive type variables or wildcards, because at some point instead of + * creating a new type, a previously created type is returned. This makes the {@code + * AnnotatedTypeMirror} recursive. */ - private static class RecursiveTypeStructure { - - /** List of TypePath and TypeVariables that were found will traversing this type. */ - private final List> typeVarsInType = new ArrayList<>(); + private static class BoundInitializerVisitor implements TypeVisitor { - /** Current path used to mark the locations of TypeVariables. */ - private final TypePath currentPath = new TypePath(); - - /** - * Add a type variable found at the current path while visiting the type variable or wildcard - * associated with this structure. - * - * @param typeVariable a type variable - */ - public void addTypeVar(TypeVariable typeVariable) { - typeVarsInType.add(IPair.of(this.currentPath.copy(), typeVariable)); - } + /** AnnotatedTypeFactory used to create AnnotatedTypeMirrors. */ + private final AnnotatedTypeFactory atypeFactory; /** - * Add a node in the path. - * - * @param node node to add - * @return {@code node} + * A map from a Java type variable to its {@link AnnotatedTypeVariable}. Used to set up + * recursive type variables. */ - public TypePathNode addPathNode(TypePathNode node) { - currentPath.add(node); - return node; - } + private final Map typeVarToAtm = new HashMap<>(); /** - * Remove the last node in the path if it is {@code node}; otherwise, throw an exception. - * - * @param node last node in the path + * A map from a Java wildcard to its {@link AnnotatedWildcardType}. Used to set up recursive + * wildcards. */ - public void removePathNode(@FindDistinct TypePathNode node) { - if (currentPath.getLeaf() != node) { - throw new BugInCF( - "Cannot remove node: %s. It is not the last node. currentPath= %s", node, currentPath); - } - currentPath.removeLeaf(); - } + private final Map wildcardToAtm = new HashMap<>(); /** - * For all type variables contained within the type variable or wildcard that this structure - * represents, this a list of the replacement {@link AnnotatedTypeVariable} for the location - * specified by the {@link TypePath}. + * A map from a Java type variable in a raw type to an {@link AnnotatedWildcardType}. Used to + * set up recursive type variables. */ - private List> replacementList; + private final Map typeParamToWildcard = new HashMap<>(); /** - * Find the AnnotatedTypeVariables that should replace the type variables found in this type. + * Creates a {@link BoundInitializerVisitor}. * - * @param typeVarToStructure a mapping from TypeVariable to TypeVariableStructure + * @param atypeFactory the type factory */ - public void findAllReplacements(Map typeVarToStructure) { - this.annotatedTypeVariables = new ArrayList<>(typeVarsInType.size()); - this.replacementList = new ArrayList<>(typeVarsInType.size()); - for (IPair pair : typeVarsInType) { - TypeVariableStructure targetStructure = typeVarToStructure.get(pair.second); - AnnotatedTypeVariable template = targetStructure.annotatedTypeVar.deepCopy().asUse(); - annotatedTypeVariables.add(template); - replacementList.add(IPair.of(pair.first, template)); - } + public BoundInitializerVisitor(AnnotatedTypeFactory atypeFactory) { + this.atypeFactory = atypeFactory; } - /** List of {@link AnnotatedTypeVariable}s found in this type. */ - private List annotatedTypeVariables; - /** - * A list of all AnnotatedTypeVariables found in this type. {@link #findAllReplacements(Map)} - * must be called first. + * Creates a {@link AnnotatedTypeMirror} with the same structure as {@code javaType}. * - * @return a list of all AnnotatedTypeVariables found in this type + * @param javaType a Java type + * @return a new {@link AnnotatedTypeMirror} with the same structure as {@code javaType} */ - public List getAnnotatedTypeVars() { - if (annotatedTypeVariables == null) { - throw new BugInCF("Call createReplacementList before calling this method."); - } - return annotatedTypeVariables; + private AnnotatedTypeMirror createAnnotatedType(TypeMirror javaType) { + return AnnotatedTypeMirror.createType(javaType, atypeFactory, false); } /** - * Replaces all type variables in {@code type} with their replacements. ({@link - * #findAllReplacements(Map)} must be called first so that the replacements can be found.) + * Sets the upper and lower bounds of {@code annotatedTypeVariable} to {@code + * AnnotatedTypeMirror} that match the upper and lower bounds of the underlying type of {@code + * annotatedTypeVariable} by visiting each bound. This method should only be called once per + * {@link TypeVariable}. * - * @param type annotated type whose type variables are replaced + * @param annotatedTypeVariable an annotated type variable */ - public void replaceTypeVariablesInType(AnnotatedTypeMirror type) { - if (replacementList == null) { - throw new BugInCF("Call createReplacementList before calling this method."); + private void initializeTypeVariable(AnnotatedTypeVariable annotatedTypeVariable) { + TypeVariable t = annotatedTypeVariable.getUnderlyingType(); + if (!annotatedTypeVariable.isDeclaration()) { + t = (TypeVariable) TypeAnnotationUtils.unannotatedType(t); + typeVarToAtm.put(t, annotatedTypeVariable); } - for (IPair entry : replacementList) { - TypePath path = entry.first; - AnnotatedTypeVariable replacement = entry.second; - path.replaceTypeVariable(type, replacement); - } - } - } - - /** A {@link RecursiveTypeStructure} for a type variable. */ - private static class TypeVariableStructure extends RecursiveTypeStructure { - /** The type variable whose structure is being described. */ - public final TypeVariable typeVar; - /** - * The first annotated type variable that was encountered and traversed in order to describe - * typeVar. It is expanded during visitation and it is later used as a template for other uses - * of typeVar - */ - public AnnotatedTypeVariable annotatedTypeVar; - - /** - * Creates an {@link TypeVariableStructure} - * - * @param annotatedTypeVar annotated type for the type variable whose structure is being - * described - */ - public TypeVariableStructure(AnnotatedTypeVariable annotatedTypeVar) { - this.typeVar = annotatedTypeVar.getUnderlyingType(); - this.annotatedTypeVar = annotatedTypeVar; - } - } - - /** - * A list of {@link TypePathNode}s. Each node represents a "location" of a composite type. For - * example, an {@link UpperBoundNode} represents the upper bound type of a type variable - */ - @SuppressWarnings("serial") - private static class TypePath extends ArrayList { - - @Override - public String toString() { - return StringsPlume.join(",", this); - } - - /** - * Create a copy of this path. - * - * @return a copy of this path - */ - public TypePath copy() { - TypePath copy = new TypePath(); - for (TypePathNode node : this) { - copy.add(node.copy()); - } - return copy; + TypeMirror lowerBound = TypesUtils.getTypeVariableLowerBound(t, atypeFactory.processingEnv); + annotatedTypeVariable.setLowerBound(visit(lowerBound)); + annotatedTypeVariable.setUpperBound(visit(t.getUpperBound())); } /** - * Return the leaf node of this path. + * Sets the extends and super bounds of {@code annotatedWildcardType} to {@code + * AnnotatedTypeMirror} that match the upper and lower bounds of the underlying type of {@code + * annotatedWildcardType} by calling visiting each bound. This method should only be called once + * per {@link WildcardType}. * - * @return the leaf node or null if the path is empty + * @param annotatedWildcardType an annotated wildcard type */ - public @Nullable TypePathNode getLeaf() { - if (this.isEmpty()) { - return null; - } - return this.get(size() - 1); - } + private void initializeWildcard(AnnotatedWildcardType annotatedWildcardType) { + WildcardType t = annotatedWildcardType.getUnderlyingType(); + wildcardToAtm.put(t, annotatedWildcardType); - /** Remove the leaf node if one exists. */ - public void removeLeaf() { - if (this.isEmpty()) { - return; + TypeMirror lowerBound = TypesUtils.wildLowerBound(t, atypeFactory.processingEnv); + annotatedWildcardType.setSuperBound(visit(lowerBound)); + TypeMirror upperBound = t.getExtendsBound(); + if (upperBound == null) { + upperBound = TypesUtils.getObjectTypeMirror(atypeFactory.processingEnv); } - this.remove(size() - 1); - } - - /** - * In {@code type}, replace the type at the location specified by this path with {@code - * replacement}. - * - * @param type annotated type that is side-effected - * @param replacement annotated type to add to {@code type} - */ - public void replaceTypeVariable(AnnotatedTypeMirror type, AnnotatedTypeVariable replacement) { - AnnotatedTypeMirror current = type; - for (int i = 0; i < size() - 1; i++) { - current = get(i).getType(current); - } - this.getLeaf().replaceType(current, replacement); - } - } - - /** - * A {@link TypePathNode} represents a "location" of a composite type. For example, an {@link - * UpperBoundNode} represents the upper bound type of a type variable. - */ - private abstract static class TypePathNode { - - /** The {@link TypeKind} of the parent of this node. */ - public final TypeKind parentTypeKind; - - /** - * Creates a {@link TypePathNode}. - * - * @param parentTypeKind kind of parent of this node - */ - TypePathNode(TypeKind parentTypeKind) { - this.parentTypeKind = parentTypeKind; - } - - /** - * A copy constructor. - * - * @param template node to copy - */ - TypePathNode(TypePathNode template) { - this.parentTypeKind = template.parentTypeKind; + annotatedWildcardType.setExtendsBound(visit(upperBound)); } @Override - public String toString() { - return this.getClass().getSimpleName(); - } - - /** - * Returns the annotated type at the location represented by this node in {@code type}. - * - * @param type parent type - * @return the annotated type at the location represented by this node in {@code type} - * @throws BugInCF if {@code type} does not have a type at this location - */ - public final AnnotatedTypeMirror getType(AnnotatedTypeMirror type) { - abortIfNotKind(parentTypeKind, null, type); - return getTypeInternal(type); - } - - /** - * Internal implementation of {@link #getType(AnnotatedTypeMirror)}. - * - * @param parent type that is sideffected by this method - * @return the annotated type at the location represented by this node in {@code type} - */ - protected abstract AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent); - - /** - * Replaces the type at the location represented by this node in {@code parent} with {@code - * replacement}. - * - * @param parent type that is sideffected by this method - * @param replacement the replacement - * @throws BugInCF if {@code type} does not have a type at this location - */ - public final void replaceType(AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - abortIfNotKind(parentTypeKind, replacement, parent); - replaceTypeInternal(parent, replacement); - } - - /** - * Internal implementation of #replaceType. - * - * @param parent type that is sideffected by this method - * @param replacement the replacement - */ - protected abstract void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement); - - /** - * Returns a copy of the node. - * - * @return a copy of this node - */ - public abstract TypePathNode copy(); - - /** - * Throws a {@link BugInCF} if {@code parent} is {@code typeKind}. - * - * @param typeKind the desired TypeKind - * @param replacement for debugging - * @param parent possible parent type of this node - * @throws BugInCF if {@code parent.getKind()} is not {@code typeKind} - */ - private void abortIfNotKind( - TypeKind typeKind, AnnotatedTypeVariable replacement, AnnotatedTypeMirror parent) { - if (parent.getKind() == typeKind) { - return; - } - - throw new BugInCF( - "Unexpected parent kind:%nparent= %s%nreplacements= %s%n expected= %s", - parent, replacement, typeKind); - } - } - - /** Represents an enclosing type of a declared type. */ - private static class EnclosingTypeNode extends TypePathNode { - - /** Create an enclosing node. */ - EnclosingTypeNode() { - super(TypeKind.DECLARED); + public AnnotatedTypeMirror visit(TypeMirror t, Void unused) { + return t.accept(this, null); } @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - // An enclosing type cannot be a type variable, so do nothing. + public AnnotatedTypeMirror visitPrimitive(PrimitiveType t, Void unused) { + return createAnnotatedType(t); } @Override - public AnnotatedDeclaredType getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedDeclaredType) parent).getEnclosingType(); + public AnnotatedTypeMirror visitNull(NullType t, Void unused) { + return createAnnotatedType(t); } @Override - public TypePathNode copy() { - return new EnclosingTypeNode(); - } - } - - /** Represents an extends bound of a wildcard. */ - private static class ExtendsBoundNode extends TypePathNode { - /** Creates an ExtendsBoundNode. */ - ExtendsBoundNode() { - super(TypeKind.WILDCARD); + public AnnotatedTypeMirror visitArray(ArrayType t, Void unused) { + AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) createAnnotatedType(t); + annotatedArrayType.setComponentType(visit(t.getComponentType())); + return annotatedArrayType; } @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedWildcardType) parent).setExtendsBound(replacement); - } - - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedWildcardType) parent).getExtendsBound(); - } - - @Override - public TypePathNode copy() { - return new ExtendsBoundNode(); - } - } - - /** Represents a super bound of a wildcard. */ - private static class SuperBoundNode extends TypePathNode { - /** Creates a SuperBoundNode. */ - SuperBoundNode() { - super(TypeKind.WILDCARD); - } - - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedWildcardType) parent).setSuperBound(replacement); - } - - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedWildcardType) parent).getSuperBound(); - } - - @Override - public TypePathNode copy() { - return new SuperBoundNode(); - } - } - - /** Represents an upper bound of a type variable. */ - private static class UpperBoundNode extends TypePathNode { - - /** Creates an UpperBoundNode. */ - UpperBoundNode() { - super(TypeKind.TYPEVAR); - } - - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedTypeVariable) parent).setUpperBound(replacement); - } - - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; - if (parentAtv.getUpperBoundField() != null) { - return parentAtv.getUpperBoundField(); + public AnnotatedTypeMirror visitDeclared(DeclaredType t, Void unused) { + AnnotatedDeclaredType annotatedDeclaredType = (AnnotatedDeclaredType) createAnnotatedType(t); + if (t.getEnclosingType() != null && t.getEnclosingType().getKind() == TypeKind.DECLARED) { + annotatedDeclaredType.setEnclosingType((AnnotatedDeclaredType) visit(t.getEnclosingType())); } - return createAndSetUpperBound((AnnotatedTypeVariable) parent); - } - - @Override - public TypePathNode copy() { - return new UpperBoundNode(); - } - } - - /** Represents a lower bound of a type variable. */ - private static class LowerBoundNode extends TypePathNode { - /** Creates a LowerBoundNode. */ - LowerBoundNode() { - super(TypeKind.TYPEVAR); - } - - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedTypeVariable) parent).setLowerBound(replacement); - } - - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedTypeVariable parentAtv = (AnnotatedTypeVariable) parent; - if (parentAtv.getLowerBoundField() != null) { - return parentAtv.getLowerBoundField(); + TypeElement typeElement = (TypeElement) atypeFactory.types.asElement(t); + List typeArgs = new ArrayList<>(typeElement.getTypeParameters().size()); + if (annotatedDeclaredType.isUnderlyingTypeRaw()) { + for (TypeParameterElement typeParameterEle : typeElement.getTypeParameters()) { + TypeVariable typeVar = (TypeVariable) typeParameterEle.asType(); + AnnotatedWildcardType wildcardType = typeParamToWildcard.get(typeVar); + if (wildcardType == null) { + TypeMirror javaTypeArg = getUpperBoundAsWildcard(typeVar, atypeFactory.types); + wildcardType = (AnnotatedWildcardType) createAnnotatedType(javaTypeArg); + wildcardType.setTypeArgOfRawType(); + typeParamToWildcard.put(typeVar, wildcardType); + initializeWildcard(wildcardType); + } + typeArgs.add(wildcardType); + } + } else { + for (TypeMirror javaTypeArg : t.getTypeArguments()) { + typeArgs.add(visit(javaTypeArg)); + } } - // else // TODO: I think this should never happen at this point, throw exception - return createAndSetLowerBound((AnnotatedTypeVariable) parent); - } - @Override - public TypePathNode copy() { - return new LowerBoundNode(); - } - } - - /** Represents a component type of an array type. */ - private static class ArrayComponentNode extends TypePathNode { - - /** Create ArrayComponentNode. */ - ArrayComponentNode() { - super(TypeKind.ARRAY); - } - - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - ((AnnotatedArrayType) parent).setComponentType(replacement); - } - - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - return ((AnnotatedArrayType) parent).getComponentType(); + annotatedDeclaredType.setTypeArguments(typeArgs); + return annotatedDeclaredType; } @Override - public TypePathNode copy() { - return new ArrayComponentNode(); - } - } - - /** A bound type of an intersection type. */ - private static class IntersectionBoundNode extends TypePathNode { - - /** The index of the particular bound type of an intersection type this node represents. */ - public final int boundIndex; - - /** - * Creates an IntersectionBoundNode. - * - * @param boundIndex the index of the particular bound type of an intersection type this node - * represents - */ - IntersectionBoundNode(int boundIndex) { - super(TypeKind.INTERSECTION); - this.boundIndex = boundIndex; - } - - /** - * Copy constructor. - * - * @param template node to copy - */ - IntersectionBoundNode(IntersectionBoundNode template) { - super(template); - boundIndex = template.boundIndex; - } + public AnnotatedTypeMirror visitTypeVariable(TypeVariable t, Void unused) { + t = (TypeVariable) TypeAnnotationUtils.unannotatedType(t); + AnnotatedTypeVariable annotatedTypeVariable = typeVarToAtm.get(t); + if (annotatedTypeVariable != null) { + return annotatedTypeVariable; + } - @Override - public String toString() { - return super.toString() + "( superIndex=" + boundIndex + " )"; - } + annotatedTypeVariable = (AnnotatedTypeVariable) createAnnotatedType(t); + initializeTypeVariable(annotatedTypeVariable); - @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) parent; - List bounds = new ArrayList<>(intersection.bounds); - bounds.set(boundIndex, replacement); - intersection.setBounds(bounds); + return annotatedTypeVariable; } @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedIntersectionType isect = (AnnotatedIntersectionType) parent; - if (isect.getBounds().size() <= boundIndex) { - throw new BugInCF("Invalid superIndex %d: parent=%s", boundIndex, parent); + public AnnotatedTypeMirror visitWildcard(WildcardType t, Void unused) { + AnnotatedWildcardType annotatedWildcardType = wildcardToAtm.get(t); + if (annotatedWildcardType != null) { + return annotatedWildcardType; } + annotatedWildcardType = (AnnotatedWildcardType) createAnnotatedType(t); + initializeWildcard(annotatedWildcardType); - return isect.getBounds().get(boundIndex); + return annotatedWildcardType; } @Override - public TypePathNode copy() { - return new IntersectionBoundNode(this); - } - } - - /** Represents an alternative type of a union node. */ - private static class AlternativeTypeNode extends TypePathNode { - - /** The index of the particular alternative type of the union node that this node represents. */ - public final int altIndex; - - /** - * Creates a AlternativeTypeNode. - * - * @param altIndex the index of the particular alternative type of the union node that this node - * represents - */ - AlternativeTypeNode(int altIndex) { - super(TypeKind.UNION); - this.altIndex = altIndex; - } - - /** - * Copy constructor. - * - * @param template node to copy - */ - AlternativeTypeNode(AlternativeTypeNode template) { - super(template); - altIndex = template.altIndex; + public AnnotatedTypeMirror visitExecutable(ExecutableType t, Void unused) { + throw new RuntimeException("Don't do this"); } @Override - public String toString() { - return super.toString() + "( altIndex=" + altIndex + " )"; + public AnnotatedTypeMirror visitNoType(NoType t, Void unused) { + return createAnnotatedType(t); } @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - throw new BugInCF( - "Union types cannot be intersection bounds.%nparent=%s%nreplacement=%s", - parent, replacement); - } + public AnnotatedTypeMirror visitUnion(UnionType t, Void unused) { + AnnotatedUnionType annotatedUnionType = (AnnotatedUnionType) createAnnotatedType(t); - @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedUnionType isect = (AnnotatedUnionType) parent; - if (parent.directSupertypes().size() <= altIndex) { - throw new BugInCF("Invalid altIndex( %s ):%nparent=%s", altIndex, parent); - } - - return isect.directSupertypes().get(altIndex); - } - - @Override - public TypePathNode copy() { - return new AlternativeTypeNode(this); - } - } - - /** Represents a type argument of a declared type. */ - private static class TypeArgNode extends TypePathNode { - - /** The index of the type argument that this node represents. */ - public final int argIndex; - - /** - * Creates a TypeArgumentNode. - * - * @param argIndex index of the type argument that this node represents - */ - TypeArgNode(int argIndex) { - super(TypeKind.DECLARED); - this.argIndex = argIndex; - } - - /** - * Copy constructor. - * - * @param template node to copy - */ - TypeArgNode(TypeArgNode template) { - super(template); - this.argIndex = template.argIndex; - } - - @Override - public String toString() { - return super.toString() + "( argIndex=" + argIndex + " )"; + annotatedUnionType.alternatives = + CollectionsPlume.mapList( + alternative -> (AnnotatedDeclaredType) visit(alternative), t.getAlternatives()); + return annotatedUnionType; } @Override - protected void replaceTypeInternal( - AnnotatedTypeMirror parent, AnnotatedTypeVariable replacement) { - AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; - List typeArgs = new ArrayList<>(parentAdt.getTypeArguments()); - if (argIndex >= typeArgs.size()) { - throw new BugInCF( - StringsPlume.joinLines( - "Invalid type arg index.", - "parent=" + parent, - "replacement=" + replacement, - "argIndex=" + argIndex)); - } - typeArgs.add(argIndex, replacement); - typeArgs.remove(argIndex + 1); - parentAdt.setTypeArguments(typeArgs); + public AnnotatedTypeMirror visitIntersection(IntersectionType t, Void unused) { + AnnotatedIntersectionType annotatedIntersectionType = + (AnnotatedIntersectionType) createAnnotatedType(t); + annotatedIntersectionType.bounds = CollectionsPlume.mapList(this::visit, t.getBounds()); + return annotatedIntersectionType; } @Override - protected AnnotatedTypeMirror getTypeInternal(AnnotatedTypeMirror parent) { - AnnotatedDeclaredType parentAdt = (AnnotatedDeclaredType) parent; - - List typeArgs = parentAdt.getTypeArguments(); - if (argIndex >= typeArgs.size()) { - throw new BugInCF( - StringsPlume.joinLines( - "Invalid type arg index.", "parent=" + parent, "argIndex=" + argIndex)); - } - - return typeArgs.get(argIndex); + public AnnotatedTypeMirror visitError(ErrorType t, Void unused) { + return createAnnotatedType(t); } @Override - public TypePathNode copy() { - return new TypeArgNode(this); + public AnnotatedTypeMirror visitUnknown(TypeMirror t, Void unused) { + return createAnnotatedType(t); } } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java index 016b80de345..7598ed49326 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java @@ -136,6 +136,8 @@ public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) AnnotatedExecutableType result = (AnnotatedExecutableType) f.toAnnotatedType(elt.asType(), false); result.setElement(elt); + f.initializeAtm(result); + // Make sure the return type field gets initialized... otherwise // some code throws NPE. This should be cleaned up. result.getReturnType(); diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java index 611e74509f1..099691dc7d3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java @@ -49,7 +49,15 @@ */ class TypeFromTypeTreeVisitor extends TypeFromTreeVisitor { - private final Map visitedBounds = new HashMap<>(); + /** Creates a TypeFromTypeTreeVisitor. */ + public TypeFromTypeTreeVisitor() {} + + /** + * A mapping from TypeParameterTree to its type. This is used to correctly initialize recursive + * type variables. + */ + private final Map visitedTypeParameter = + new HashMap<>(); @Override public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedTypeFactory f) { @@ -189,20 +197,21 @@ public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree tree, AnnotatedT @Override public AnnotatedTypeVariable visitTypeParameter( TypeParameterTree tree, @FindDistinct AnnotatedTypeFactory f) { + if (visitedTypeParameter.containsKey(tree)) { + return visitedTypeParameter.get(tree); + } + + AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(tree); + // If this type parameter is recursive and it is found again while visiting the bounds, then + // use the same AnnotateTypeVariable object. + visitedTypeParameter.put(tree, result); + List bounds = new ArrayList<>(tree.getBounds().size()); for (Tree t : tree.getBounds()) { - AnnotatedTypeMirror bound; - if (visitedBounds.containsKey(t) && f == visitedBounds.get(t).atypeFactory) { - bound = visitedBounds.get(t); - } else { - visitedBounds.put(t, f.type(t)); - bound = visit(t, f); - visitedBounds.remove(t); - } - bounds.add(bound); + bounds.add(visit(t, f)); } + visitedTypeParameter.remove(tree); - AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(tree); List annotations = TreeUtils.annotationsFromTree(tree); result.getLowerBound().addAnnotations(annotations); @@ -226,6 +235,7 @@ public AnnotatedTypeMirror visitWildcard(WildcardTree tree, AnnotatedTypeFactory AnnotatedTypeMirror bound = visit(tree.getBound(), f); AnnotatedTypeMirror result = f.type(tree); assert result instanceof AnnotatedWildcardType; + f.initializeAtm(result); // for wildcards unlike type variables there are bounds that differ in type from // result. These occur for RAW types. In this case, use the newly created bound diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java index cc782a00847..42d8b989727 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesHelper.java @@ -988,6 +988,9 @@ public Void visitTypeVariable( @Override protected Void scan( AnnotatedTypeMirror type, Function func) { + if (visitedNodes.containsKey(type)) { + return null; + } for (AnnotationMirror anno : new AnnotationMirrorSet(type.getAnnotations())) { AnnotationMirror newAnno = func.apply(anno); if (newAnno != null) { diff --git a/framework/tests/all-systems/Issue6373.java b/framework/tests/all-systems/Issue6373.java new file mode 100644 index 00000000000..b21fbaf5e5d --- /dev/null +++ b/framework/tests/all-systems/Issue6373.java @@ -0,0 +1,89 @@ +public class Issue6373 { + + abstract static class C1< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5> + extends C6 {} + + static class C6 {} + + abstract static class C2< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + RT extends C5> + implements C7 {} + + abstract static class C3< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> {} + + abstract static class C4< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + R extends C5> { + interface I {} + } + + abstract static class C5> implements C7 {} + + interface C7 {} + + abstract static class C8< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> { + + public static < + CM extends C1, + QM extends C2, + BM extends C3, + DM extends C4, + CRM extends C5, + RpTM extends C5> + Builder n(QM q) { + throw new AssertionError(); + } + + abstract static class Builder< + C extends C1, + Q extends C2, + B extends C3, + D extends C4, + CR extends C5, + RpT extends C5> {} + } + + abstract static class C9> {} + + static class C13 { + + static final class C14 extends C1 {} + + static final class C15 extends C2 {} + + static final class C18 extends C5 {} + + static class C17 extends C4 implements C4.I, C19 {} + + static final class C16 extends C3 {} + } + + interface C19 {} + + void f(C13.C15 c) { + C8.n(c); + } +} diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index 60457e513d6..ef64d3448a4 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -696,6 +696,27 @@ public static DeclaredType getObjectTypeMirror(ProcessingEnvironment env) { return (DeclaredType) syms.objectType; } + /** + * Returns the lower bound of {@code typeVariable}. If it does not have a lower bound, returns the + * null type. + * + * @param typeVariable a type variable + * @param env the proceProcessingEnvironment + * @return the lower bound of {@code typeVariable} or the null type + */ + public static TypeMirror getTypeVariableLowerBound( + TypeVariable typeVariable, ProcessingEnvironment env) { + TypeMirror lb = typeVariable.getLowerBound(); + if (lb != null) { + return lb; + } + + // Use bottom type to ensure there is a lower bound. + Context context = ((JavacProcessingEnvironment) env).getContext(); + Symtab syms = Symtab.instance(context); + return syms.botType; + } + /** * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8 * (called upperBound there) and jdk8u. From 705f80253041dcc20ab0afc9d652a459f3879d3e Mon Sep 17 00:00:00 2001 From: Sanjay Malakar Date: Mon, 4 Mar 2024 10:05:51 -0800 Subject: [PATCH 086/173] Fix incorrect handling of temporary variable enclosing element in RL inference; fixes #6473 --- .../checker/mustcall/MustCallTransfer.java | 7 ++-- .../non-annotated/CrashForTempVar.java | 26 +++++++++++++ .../cfg/builder/CFGTranslationPhaseOne.java | 39 +++++++++---------- docs/manual/contributors.tex | 1 + .../javacutil/TreePathUtil.java | 21 ++++++++++ 5 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java diff --git a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java index 97d709db3d9..ed606c0cfdc 100644 --- a/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/mustcall/MustCallTransfer.java @@ -1,6 +1,5 @@ package org.checkerframework.checker.mustcall; -import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.VariableTree; @@ -293,8 +292,10 @@ public void updateStoreWithTempVar(TransferResult result, Node if (path == null) { enclosingElement = TreeUtils.elementFromUse(tree).getEnclosingElement(); } else { - ClassTree classTree = TreePathUtil.enclosingClass(path); - enclosingElement = TreeUtils.elementFromDeclaration(classTree); + // Issue 6473 + // Adjusts handling of nearest enclosing element for temporary variables. + // This approach ensures the correct enclosing element (method or class) is determined. + enclosingElement = TreePathUtil.findNearestEnclosingElement(path); } if (enclosingElement == null) { return null; diff --git a/checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java b/checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java new file mode 100644 index 00000000000..ed72d1603f2 --- /dev/null +++ b/checker/tests/ainfer-resourceleak/non-annotated/CrashForTempVar.java @@ -0,0 +1,26 @@ +/** + * Demonstrates an issue in the Checker Framework with handling the nearest enclosing element for + * temporary variable declarations, leading to a crash during analysis. + */ +public abstract class CrashForTempVar { + + private final CrashForTempVar _base; + + protected CrashForTempVar(final CrashForTempVar base) { + _base = base; + } + + public T getValue() { + return _base.getValue(); + } + + protected CrashForTempVar getBase() { + return _base; + } + + protected abstract boolean evaluateLayer(final T baseValue, final T testValue); + + public boolean evaluate(final T testValue) { + return evaluateLayer(getBase().getValue(), testValue); + } +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index 7b8acbbe9cf..2ce2b83d53c 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -1696,7 +1696,7 @@ protected boolean assumeAssertionsEnabledFor(AssertTree tree) { protected VariableTree getAssertionsEnabledVariable() { if (ea == null) { String name = uniqueName("assertionsEnabled"); - Element owner = findOwner(); + Element owner = TreePathUtil.findNearestEnclosingElement(getCurrentPath()); ExpressionTree initializer = null; ea = treeBuilder.buildVariableDecl( @@ -1706,21 +1706,6 @@ protected VariableTree getAssertionsEnabledVariable() { return ea; } - /** - * Find nearest owner element (Method or Class) which holds current tree. - * - * @return nearest owner element of current tree - */ - private Element findOwner() { - MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); - if (enclosingMethod != null) { - return TreeUtils.elementFromDeclaration(enclosingMethod); - } else { - ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); - return TreeUtils.elementFromDeclaration(enclosingClass); - } - } - /** * Translates an assertion statement to the correct CFG nodes. The translation assumes that * assertions are enabled. @@ -2592,7 +2577,11 @@ private void buildSelector() { // Create a synthetic variable to which the switch selector expression will be assigned TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree); VariableTree selectorVarTree = - treeBuilder.buildVariableDecl(selectorExprType, uniqueName("switch"), findOwner(), null); + treeBuilder.buildVariableDecl( + selectorExprType, + uniqueName("switch"), + TreePathUtil.findNearestEnclosingElement(getCurrentPath()), + null); handleArtificialTree(selectorVarTree); VariableDeclarationNode selectorVarNode = new VariableDeclarationNode(selectorVarTree); @@ -2628,7 +2617,10 @@ private void buildSwitchExpressionVar() { TypeMirror switchExprType = TreeUtils.typeOf(switchTree); switchExprVarTree = treeBuilder.buildVariableDecl( - switchExprType, uniqueName("switchExpr"), findOwner(), null); + switchExprType, + uniqueName("switchExpr"), + TreePathUtil.findNearestEnclosingElement(getCurrentPath()), + null); handleArtificialTree(switchExprVarTree); VariableDeclarationNode switchExprVarNode = new VariableDeclarationNode(switchExprVarTree); @@ -2779,7 +2771,11 @@ public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) { // create a synthetic variable for the value of the conditional expression VariableTree condExprVarTree = - treeBuilder.buildVariableDecl(exprType, uniqueName("condExpr"), findOwner(), null); + treeBuilder.buildVariableDecl( + exprType, + uniqueName("condExpr"), + TreePathUtil.findNearestEnclosingElement(getCurrentPath()), + null); handleArtificialTree(condExprVarTree); VariableDeclarationNode condExprVarNode = new VariableDeclarationNode(condExprVarTree); condExprVarNode.setInSource(false); @@ -4209,7 +4205,10 @@ public Node visitUnary(UnaryTree tree, Void p) { TypeMirror exprType = TreeUtils.typeOf(exprTree); VariableTree tempVarDecl = treeBuilder.buildVariableDecl( - exprType, uniqueName("tempPostfix"), findOwner(), tree.getExpression()); + exprType, + uniqueName("tempPostfix"), + TreePathUtil.findNearestEnclosingElement(getCurrentPath()), + tree.getExpression()); handleArtificialTree(tempVarDecl); VariableDeclarationNode tempVarDeclNode = new VariableDeclarationNode(tempVarDecl); tempVarDeclNode.setInSource(false); diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index adc85980150..a66642e3405 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -122,6 +122,7 @@ Ryan Oblak, Sadaf Tajik, Sagar Tewari, +Sanjay Malakar, Sean C. Sullivan, Sean McLaughlin, Sebastian Schuberth, diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java index a00ceac2f4f..27e19dbadf3 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreePathUtil.java @@ -467,4 +467,25 @@ public static String leafToStringTruncated(@Nullable TreePath path, int length) } return TreeUtils.toStringTruncated(path.getLeaf(), length); } + + /** + * Retrieves the nearest enclosing method or class element for the specified path in the AST. This + * utility method prioritizes method elements over class elements. It returns the element of the + * closest method scope if available; otherwise, it defaults to the enclosing class scope. + * + * @param path the {@link TreePath} to analyze for the nearest enclosing scope. + * @return the {@link Element} of the nearest enclosing method or class, or {@code null} if no + * such enclosing element can be found. + */ + public static @Nullable Element findNearestEnclosingElement(TreePath path) { + MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(path); + if (enclosingMethodTree != null) { + return TreeUtils.elementFromDeclaration(enclosingMethodTree); + } + ClassTree enclosingClassTree = TreePathUtil.enclosingClass(path); + if (enclosingClassTree != null) { + return TreeUtils.elementFromDeclaration(enclosingClassTree); + } + return null; + } } From 77cff112dcb2e420c7d299a9044322414432112f Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 6 Mar 2024 18:35:36 +0100 Subject: [PATCH 087/173] Add module-info.java to checker-qual (#6326) --- build.gradle | 9 ++++- checker-qual/build.gradle | 28 +++++++++++++ checker-qual/src/main/java/module-info.java | 44 +++++++++++++++++++++ docs/manual/contributors.tex | 1 + 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 checker-qual/src/main/java/module-info.java diff --git a/build.gradle b/build.gradle index e8fc47190a5..eb2034eb258 100644 --- a/build.gradle +++ b/build.gradle @@ -712,9 +712,14 @@ task allJavadoc(type: Javadoc, group: 'Documentation') { } classpath = configurations.allProjects + if (isJava8) { classpath += configurations.javacJar } + + // disable interpreting module-info.java files until all sub-modules support them + modularity.inferModulePath = false + doLast { copy { from 'docs/logo/Checkmark/CFCheckmark_favicon.png' @@ -772,6 +777,8 @@ def createJavadocTask(taskName, taskDescription, memberLevel) { } classpath = configurations.allProjects + // disable interpreting module-info.java files until all sub-modules support them + modularity.inferModulePath = false destinationDir.deleteDir() options.memberLevel = memberLevel @@ -1014,7 +1021,7 @@ subprojects { manifest { attributes('Implementation-Version': "${project.version}") attributes('Implementation-URL': 'https://eisop.github.io/') - if (! archiveFileName.get().endsWith('source.jar')) { + if (! archiveFileName.get().endsWith('source.jar') && ! archiveFileName.get().startsWith('checker-qual')) { attributes('Automatic-Module-Name': 'org.checkerframework.' + project.name.replaceAll('-', '.')) } if (archiveFileName.get().startsWith('checker-qual') || archiveFileName.get().startsWith('checker-util')) { diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle index d77d0348788..28af96cd7c4 100644 --- a/checker-qual/build.gradle +++ b/checker-qual/build.gradle @@ -16,6 +16,34 @@ if (JavaVersion.current() >= JavaVersion.VERSION_17) { apply plugin: 'biz.aQute.bnd.builder' } +sourceSets { + main { + java { + exclude 'module-info.java' + } + } + module_info { + java { + srcDirs ('src/main') + } + } +} + +task compileJava9(type: JavaCompile) { + source = sourceSets.module_info.java + destinationDirectory = sourceSets.main.output.classesDirs[0] + classpath = configurations.allProjects + options.release = 9 +} + +compileJava { + dependsOn compileJava9 +} + +javadoc { + modularity.inferModulePath = false +} + jar { manifest { attributes('Export-Package': '*') diff --git a/checker-qual/src/main/java/module-info.java b/checker-qual/src/main/java/module-info.java new file mode 100644 index 00000000000..4c8fab8909d --- /dev/null +++ b/checker-qual/src/main/java/module-info.java @@ -0,0 +1,44 @@ +/** + * This module contains annotations (type qualifiers) that a programmer writes to specify Java code + * for type-checking by the Checker Framework. + */ +module org.checkerframework.checker.qual { + // javadoc-only dependencies + requires static java.compiler; + requires static java.desktop; + requires static jdk.compiler; + + // the .jar file (for the Automatic-Module-Name) is not ready during javadoc + // requires static org.checkerframework.checker; + + exports org.checkerframework.checker.builder.qual; + exports org.checkerframework.checker.calledmethods.qual; + exports org.checkerframework.checker.compilermsgs.qual; + exports org.checkerframework.checker.fenum.qual; + exports org.checkerframework.checker.formatter.qual; + exports org.checkerframework.checker.guieffect.qual; + exports org.checkerframework.checker.i18n.qual; + exports org.checkerframework.checker.i18nformatter.qual; + exports org.checkerframework.checker.index.qual; + exports org.checkerframework.checker.initialization.qual; + exports org.checkerframework.checker.interning.qual; + exports org.checkerframework.checker.lock.qual; + exports org.checkerframework.checker.mustcall.qual; + exports org.checkerframework.checker.nullness.qual; + exports org.checkerframework.checker.optional.qual; + exports org.checkerframework.checker.propkey.qual; + exports org.checkerframework.checker.regex.qual; + exports org.checkerframework.checker.signature.qual; + exports org.checkerframework.checker.signedness.qual; + exports org.checkerframework.checker.tainting.qual; + exports org.checkerframework.checker.units.qual; + exports org.checkerframework.common.aliasing.qual; + exports org.checkerframework.common.initializedfields.qual; + exports org.checkerframework.common.reflection.qual; + exports org.checkerframework.common.returnsreceiver.qual; + exports org.checkerframework.common.subtyping.qual; + exports org.checkerframework.common.util.count.report.qual; + exports org.checkerframework.common.value.qual; + exports org.checkerframework.dataflow.qual; + exports org.checkerframework.framework.qual; +} diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index a66642e3405..0692bac9b51 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -80,6 +80,7 @@ Mahmood Ali, Manu Sridharan, Mark Roberts, +Markus Frohme, Martin Kellogg, Matt Mullen, Maximilian Gama, From 5fc9abd2191f40fecdfb9a020634ac1e5201e8cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:05:09 +0000 Subject: [PATCH 088/173] Update dependency io.github.classgraph:classgraph to v4.8.167 (#6476) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- framework/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/build.gradle b/framework/build.gradle index abdb31510e8..0d0337b9b11 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation "org.plumelib:plume-util:${versions.plumeUtil}" implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" // Add this dependency if needed to debug classpath issues. - // implementation 'io.github.classgraph:classgraph:4.8.165' + // implementation 'io.github.classgraph:classgraph:4.8.167' testImplementation "junit:junit:${versions.junit}" testImplementation project(':framework-test') From 8eb57848298b4e7836c8b3a76d0ab3e357e3e6ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 07:48:16 -0800 Subject: [PATCH 089/173] Update dependency io.github.classgraph:classgraph to v4.8.168 (#6482) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- framework/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/build.gradle b/framework/build.gradle index 0d0337b9b11..24e5c04fbd5 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation "org.plumelib:plume-util:${versions.plumeUtil}" implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" // Add this dependency if needed to debug classpath issues. - // implementation 'io.github.classgraph:classgraph:4.8.167' + // implementation 'io.github.classgraph:classgraph:4.8.168' testImplementation "junit:junit:${versions.junit}" testImplementation project(':framework-test') From 9f334275bc3d0e226c59640a1407207d067fd6f2 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 9 Mar 2024 17:46:37 -0800 Subject: [PATCH 090/173] Refill comment --- .../dataflow/cfg/builder/CFGTranslationPhaseThree.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java index cfc2fb9f6c6..9d75d9e9742 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java @@ -167,8 +167,9 @@ protected static void mergeConsecutiveBlocks(ControlFlowGraph cfg) { if (succ.getType() == BlockType.REGULAR_BLOCK) { RegularBlockImpl rs = (RegularBlockImpl) succ; if (rs.getRegularSuccessor() == rs) { - // Do not attempt to merge a block with a self edge (which would infinite-loop - // if it were run), as it leads to non-termination in the merging algorithm. + // Do not attempt to merge a block with a self edge (which would + // infinite-loop if it were run), as it leads to non-termination + // in the merging algorithm. break; } if (rs.getPredecessors().size() == 1) { From eb082978e4e84e5f50b4fa04df24fd5323680015 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 10 Mar 2024 15:00:21 -0700 Subject: [PATCH 091/173] Purity for methods in `JavaExpression.java` --- SKIP-REQUIRE-JAVADOC | 1 + .../dataflow/expression/JavaExpression.java | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 SKIP-REQUIRE-JAVADOC diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC new file mode 100644 index 00000000000..5683b04f8a7 --- /dev/null +++ b/SKIP-REQUIRE-JAVADOC @@ -0,0 +1 @@ +Remove this file after the pull request is merged. diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java index 8b4be624c93..d62910f7ce8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java @@ -43,6 +43,7 @@ import org.checkerframework.dataflow.cfg.node.UnaryOperationNode; import org.checkerframework.dataflow.cfg.node.ValueLiteralNode; import org.checkerframework.dataflow.cfg.node.WideningConversionNode; +import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; @@ -88,8 +89,10 @@ public TypeMirror getType() { return type; } + @Pure public abstract boolean containsOfClass(Class clazz); + @Pure public boolean containsUnknown() { return containsOfClass(Unknown.class); } @@ -100,6 +103,7 @@ public boolean containsUnknown() { * @param provider an annotation provider (a type factory) * @return true if this expression is deterministic */ + @Pure public abstract boolean isDeterministic(AnnotationProvider provider); /** @@ -109,6 +113,7 @@ public boolean containsUnknown() { * @param provider an annotation provider (a type factory) * @return true if all the expressions in the list are deterministic */ + @Pure public static boolean listIsDeterministic( List list, AnnotationProvider provider) { return list.stream().allMatch(je -> je == null || je.isDeterministic(provider)); @@ -122,6 +127,7 @@ public static boolean listIsDeterministic( * * @see #isUnmodifiableByOtherCode */ + @Pure public abstract boolean isUnassignableByOtherCode(); /** @@ -133,6 +139,7 @@ public static boolean listIsDeterministic( * * @see #isUnassignableByOtherCode */ + @Pure public abstract boolean isUnmodifiableByOtherCode(); /** @@ -144,6 +151,7 @@ public static boolean listIsDeterministic( * @return true if and only if the two Java expressions are syntactically identical */ @EqualsMethod + @Pure public abstract boolean syntacticEquals(JavaExpression je); /** @@ -153,6 +161,7 @@ public static boolean listIsDeterministic( * @param lst2 the second list to compare * @return true if the corresponding list elements satisfy {@link #syntacticEquals} */ + @Pure public static boolean syntacticEqualsList( List lst1, List lst2) { @@ -183,6 +192,7 @@ public static boolean syntacticEqualsList( * @return true if and only if this contains a JavaExpression that is syntactically equal to * {@code other} */ + @Pure public abstract boolean containsSyntacticEqualJavaExpression(JavaExpression other); /** @@ -194,6 +204,7 @@ public static boolean syntacticEqualsList( * @return true if and only if the list contains a JavaExpression that is syntactically equal to * {@code other} */ + @Pure public static boolean listContainsSyntacticEqualJavaExpression( List list, JavaExpression other) { return list.stream() @@ -207,6 +218,7 @@ public static boolean listContainsSyntacticEqualJavaExpression( *

          This is always true, except for cases where the Java type information prevents aliasing and * none of the subexpressions can alias 'other'. */ + @Pure public boolean containsModifiableAliasOf(Store store, JavaExpression other) { return this.equals(other) || store.canAlias(this, other); } @@ -216,6 +228,7 @@ public boolean containsModifiableAliasOf(Store store, JavaExpression other) { * * @return a verbose string representation of this */ + @Pure public String toStringDebug() { return String.format("%s(%s): %s", getClass().getSimpleName(), type, toString()); } From ea87d7d063aa1ea26ad338358888b4f6fa9f19da Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 10 Mar 2024 15:00:46 -0700 Subject: [PATCH 092/173] Remove file `SKIP-REQUIRE-JAVADOC` --- SKIP-REQUIRE-JAVADOC | 1 - 1 file changed, 1 deletion(-) delete mode 100644 SKIP-REQUIRE-JAVADOC diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC deleted file mode 100644 index 5683b04f8a7..00000000000 --- a/SKIP-REQUIRE-JAVADOC +++ /dev/null @@ -1 +0,0 @@ -Remove this file after the pull request is merged. From 2746483ec95a83dd7da858b707d6757fa9987ab3 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 10 Mar 2024 15:01:21 -0700 Subject: [PATCH 093/173] Iterate in a null-safe way --- .../framework/type/QualifierHierarchy.java | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java index c92cf034997..57005a451e7 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java @@ -1,6 +1,7 @@ package org.checkerframework.framework.type; import java.util.Collection; +import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.lang.model.element.AnnotationMirror; @@ -375,13 +376,11 @@ public Set leastUpperBoundsQualifiersOnly( if (qualifiers.isEmpty()) { return AnnotationMirrorSet.emptySet(); } - Set result = null; - for (Collection annos : qualifiers) { - if (result == null) { - result = new AnnotationMirrorSet(annos); - } else { - result = leastUpperBoundsQualifiersOnly(result, annos); - } + Iterator> itor = qualifiers.iterator(); + Set result = new AnnotationMirrorSet(itor.next()); + while (itor.hasNext()) { + Collection annos = itor.next(); + result = leastUpperBoundsQualifiersOnly(result, annos); } return result; } @@ -685,13 +684,11 @@ public Set greatestLowerBoundsQualifiersOnly( if (qualifiers.isEmpty()) { return AnnotationMirrorSet.emptySet(); } - Set result = null; - for (Collection annos : qualifiers) { - if (result == null) { - result = new AnnotationMirrorSet(annos); - } else { - result = greatestLowerBoundsQualifiersOnly(result, annos); - } + Iterator> itor = qualifiers.iterator(); + Set result = new AnnotationMirrorSet(itor.next()); + while (itor.hasNext()) { + Collection annos = itor.next(); + result = greatestLowerBoundsQualifiersOnly(result, annos); } return result; } From 2d178f20dac54fa921a34bc1fddf4f958f6b7a2d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 11 Mar 2024 09:17:19 -0700 Subject: [PATCH 094/173] Better names than `polyScanner` (#6484) --- .../common/basetype/BaseTypeVisitor.java | 16 ++++++++-------- .../type/poly/AbstractQualifierPolymorphism.java | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 1a35e3c62a3..20c01a01b2e 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -742,15 +742,15 @@ protected void checkQualifierParameter(ClassTree classTree) { for (Tree mem : classTree.getMembers()) { if (mem.getKind() == Tree.Kind.VARIABLE) { AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(mem); - List hasIllegalPoly; + List hasInvalidPoly; if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration((VariableTree) mem))) { // A polymorphic qualifier is not allowed on a static field even if the class // has a qualifier parameter. - hasIllegalPoly = polyScanner.visit(fieldType, polys); + hasInvalidPoly = hasInvalidPolyScanner.visit(fieldType, polys); } else { - hasIllegalPoly = polyScanner.visit(fieldType, illegalOnFieldsPolyQual); + hasInvalidPoly = hasInvalidPolyScanner.visit(fieldType, illegalOnFieldsPolyQual); } - for (DiagMessage dm : hasIllegalPoly) { + for (DiagMessage dm : hasInvalidPoly) { checker.report(mem, dm); } } @@ -761,17 +761,17 @@ protected void checkQualifierParameter(ClassTree classTree) { * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use * of one of the polymorphic qualifiers. */ - private final PolyTypeScanner polyScanner = new PolyTypeScanner(); + private final HasInvalidPolyScanner hasInvalidPolyScanner = new HasInvalidPolyScanner(); /** * A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use * of one of the polymorphic qualifiers. */ - static class PolyTypeScanner + static class HasInvalidPolyScanner extends SimpleAnnotatedTypeScanner, AnnotationMirrorSet> { - /** Create PolyTypeScanner. */ - private PolyTypeScanner() { + /** Create HasInvalidPolyScanner. */ + private HasInvalidPolyScanner() { super(DiagMessage::mergeLists, Collections.emptyList()); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java index 63c3d15d8bb..c2b190f11df 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java @@ -90,7 +90,7 @@ public abstract class AbstractQualifierPolymorphism implements QualifierPolymorp new AnnotationMirrorMap<>(); /** The visit method returns true if the passed type has any polymorphic qualifiers. */ - protected final SimpleAnnotatedTypeScanner polyScanner; + protected final SimpleAnnotatedTypeScanner hasPolyScanner; /** * Creates an {@link AbstractQualifierPolymorphism} instance that uses the given checker for @@ -130,7 +130,7 @@ protected AbstractQualifierPolymorphism(ProcessingEnvironment env, AnnotatedType return null; }); - this.polyScanner = + this.hasPolyScanner = new SimpleAnnotatedTypeScanner<>( (type, notused) -> { for (AnnotationMirror a : type.getAnnotations()) { @@ -158,7 +158,7 @@ protected void reset() { @Override public boolean hasPolymorphicQualifiers(AnnotatedTypeMirror type) { - return polyScanner.visit(type); + return hasPolyScanner.visit(type); } /** From 5d4b6146d6cc20ac46d283084b314c44c659f9b8 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 11 Mar 2024 09:18:08 -0700 Subject: [PATCH 095/173] Rename `BaseTypeVisitor.checkForPolymorphicQualifiers()` to `warnInvalidPolymorphicQualifier()` (#6483) --- .../checker/guieffect/GuiEffectVisitor.java | 2 +- docs/CHANGELOG.md | 6 +++++- .../checkerframework/common/basetype/BaseTypeVisitor.java | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java index 5a295a5ffe3..bcb690571c8 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java @@ -257,7 +257,7 @@ protected void checkConstructorResult( } @Override - protected void checkForPolymorphicQualifiers(ClassTree classTree) { + protected void warnInvalidPolymorphicQualifier(ClassTree classTree) { // Polymorphic qualifiers are legal on classes, so skip this check. } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index aa9a68f9fe0..56bf03d1c31 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,11 +12,15 @@ automatically as part of whole-program inference. **Implementation details:** -Deprecated `ObjectCreationNode#getConstructor` in favor of new `ObjectCreationNode#getTypeToInstantiate()`. +Deprecated `ObjectCreationNode#getConstructor` in favor of new +`ObjectCreationNode#getTypeToInstantiate()`. Renamed `AbstractCFGVisualizer.visualizeBlockHelper()` to `visualizeBlockWithSeparator()`. +Renamed `BaseTypeVisitor.checkForPolymorphicQualifiers()` to +`warnInvalidPolymorphicQualifier()`. + **Closed issues:** diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 20c01a01b2e..662c3007b25 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -623,7 +623,7 @@ public void processClassTree(ClassTree classTree) { } } - checkForPolymorphicQualifiers(classTree); + warnInvalidPolymorphicQualifier(classTree); checkExtendsAndImplements(classTree); @@ -656,7 +656,7 @@ public Void visitAnnotation(AnnotationTree annoTree, String location) { * * @param classTree the class to check */ - protected void checkForPolymorphicQualifiers(ClassTree classTree) { + protected void warnInvalidPolymorphicQualifier(ClassTree classTree) { if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) { // Anonymous class can have polymorphic annotations, so don't check them. return; @@ -679,7 +679,7 @@ protected void checkForPolymorphicQualifiers(ClassTree classTree) { * * @param typeParameterTrees the type parameters to check */ - protected void checkForPolymorphicQualifiers( + protected void warnInvalidPolymorphicQualifier( List typeParameterTrees) { for (Tree tree : typeParameterTrees) { tree.accept(polyTreeScanner, "in a type parameter"); @@ -1061,7 +1061,7 @@ public Void visitMethod(MethodTree tree, Void p) { } */ - checkForPolymorphicQualifiers(tree.getTypeParameters()); + warnInvalidPolymorphicQualifier(tree.getTypeParameters()); return super.visitMethod(tree, p); } finally { From 6ee7dadeba711e45ce4b0f68b3bf19c9078ea761 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:17:23 -0700 Subject: [PATCH 096/173] Add `.factorypath` files to `.gitignore` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index de0719952ee..be06f84f3e2 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ dot_files .project .externalToolBuilders .settings +**/*.factorypath # Intellij files .idea From 5b58ccca07f75521849980658e8e308ce78c4688 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 12 Mar 2024 09:05:42 -0700 Subject: [PATCH 097/173] Use Error Prone 2.26.0 From 54a2d733d6a5b24d0a9ec5e94783f9e066e3aaa6 Mon Sep 17 00:00:00 2001 From: Sanjay Malakar Date: Tue, 12 Mar 2024 09:47:07 -0700 Subject: [PATCH 098/173] =?UTF-8?q?Refactor=20MustCallAnnotation=20Retriev?= =?UTF-8?q?al=20into=20Central=20Method=20in=20Resource=E2=80=A6=20(#6489)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResourceLeakAnnotatedTypeFactory.java | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java index 69e56cb00cd..a42ede79343 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java @@ -156,6 +156,31 @@ protected ResourceLeakAnalysis createFlowAnalysis() { return new ResourceLeakAnalysis((ResourceLeakChecker) checker, this); } + /** + * Retrieves the {@code @MustCall} annotation for the given object, which can be either an {@link + * Element} or a {@link Tree}. This method delegates to the {@code MustCallAnnotatedTypeFactory} + * to get the annotated type of the input object and then extracts the primary {@code @MustCall} + * annotation from it. + * + * @param obj the object for which to retrieve the {@code @MustCall} annotation. Must be either an + * instance of {@link Element} or {@link Tree}. + * @return the {@code @MustCall} annotation if present, null otherwise + * @throws IllegalArgumentException if the input object type is not supported + */ + public AnnotationMirror getMustCallAnnotation(Object obj) { + MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = + getTypeFactoryOfSubchecker(MustCallChecker.class); + AnnotatedTypeMirror mustCallAnnotatedType; + if (obj instanceof Element) { + mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType((Element) obj); + } else if (obj instanceof Tree) { + mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType((Tree) obj); + } else { + throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getName()); + } + return mustCallAnnotatedType.getPrimaryAnnotation(MustCall.class); + } + /** * Returns whether the {@link MustCall#value} element/argument of the @MustCall annotation on the * type of {@code tree} is definitely empty. @@ -167,10 +192,7 @@ protected ResourceLeakAnalysis createFlowAnalysis() { * @return true if the Must Call type is non-empty or top */ /*package-private*/ boolean hasEmptyMustCallValue(Tree tree) { - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotatedTypeMirror mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType(tree); - AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); + AnnotationMirror mustCallAnnotation = getMustCallAnnotation(tree); if (mustCallAnnotation != null) { return getMustCallValues(mustCallAnnotation).isEmpty(); } else { @@ -191,11 +213,7 @@ protected ResourceLeakAnalysis createFlowAnalysis() { * @return true if the Must Call type is non-empty or top */ /*package-private*/ boolean hasEmptyMustCallValue(Element element) { - MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory = - getTypeFactoryOfSubchecker(MustCallChecker.class); - AnnotatedTypeMirror mustCallAnnotatedType = - mustCallAnnotatedTypeFactory.getAnnotatedType(element); - AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class); + AnnotationMirror mustCallAnnotation = getMustCallAnnotation(element); if (mustCallAnnotation != null) { return getMustCallValues(mustCallAnnotation).isEmpty(); } else { From 2dcb612616cdda3f886b9f98e38d139760438431 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 12 Mar 2024 10:20:55 -0700 Subject: [PATCH 099/173] Move methods from `TreeUtils` methods into nested classes (#6486) Co-authored-by: Werner Dietl --- docs/CHANGELOG.md | 5 ++++ ...holeProgramInferenceJavaParserStorage.java | 6 +--- .../checkerframework/javacutil/TreeUtils.java | 30 ++++++++++++++----- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 56bf03d1c31..162d9fe78ea 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -18,6 +18,11 @@ Deprecated `ObjectCreationNode#getConstructor` in favor of new Renamed `AbstractCFGVisualizer.visualizeBlockHelper()` to `visualizeBlockWithSeparator()`. +Moved methods from `TreeUtils` to subclasses of `TreeUtilsAfterJava11`: + * isConstantCaseLabelTree + * isDefaultCaseLabelTree + * isPatternCaseLabelTree + Renamed `BaseTypeVisitor.checkForPolymorphicQualifiers()` to `warnInvalidPolymorphicQualifier()`. diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index 56f4556572c..e14409a448a 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -735,13 +735,9 @@ public void processNewClass(NewClassTree javacTree, ObjectCreationExpr javaParse */ private void addClass(ClassTree tree, @Nullable TypeDeclaration javaParserNode) { String className; - // elementFromDeclaration returns null instead of crashing when no element - // exists for the class tree, which can happen for certain kinds of - // anonymous classes, such as classes, such as Ordering$1 in - // PolyCollectorTypeVar.java in the all-systems test suite. TypeElement classElt = TreeUtils.elementFromDeclaration(tree); if (classElt == null) { - // If such an element does not exist, compute the name of the class, + // If such an element does not exist, compute the name of the class // instead. This method of computing the name is not 100% guaranteed to // be reliable, but it should be sufficient for WPI's purposes here: if // the wrong name is computed, the worst outcome is a false positive diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 8446259918f..045b1420242 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -307,6 +307,11 @@ public static PackageElement elementFromDeclaration(PackageTree tree) { /** * Returns the type element corresponding to the given class declaration. * + *

          This method returns null instead of crashing when no element exists for the class tree, + * which can happen for certain kinds of anonymous classes, such as Ordering$1 in + * PolyCollectorTypeVar.java in the all-systems test suite and "class MyFileFilter" in + * PurgeTxnLog.java. + * * @param tree class declaration * @return the element for the given class */ @@ -514,7 +519,7 @@ public static ExecutableElement elementFromUse(MemberReferenceTree tree) { * Returns the ExecutableElement for the given method declaration. * *

          The result can be null, when {@code tree} is a method in an anonymous class and that class - * has not been processed yet. An exception will be raised. Adapt your processing order. + * has not been processed yet. To work around this, adapt your processing order. * * @param tree a method declaration * @return the element for the given method @@ -1908,6 +1913,19 @@ public static ExecutableType typeFromUse(NewClassTree tree) { return (ExecutableType) type; } + /** + * Determines the symbol for a constructor given an invocation via {@code new}. + * + * @see #elementFromUse(NewClassTree) + * @param tree the constructor invocation + * @return the {@link ExecutableElement} corresponding to the constructor call in {@code tree} + * @deprecated use elementFromUse instead + */ + @Deprecated // 2022-09-12 + public static ExecutableElement constructor(NewClassTree tree) { + return (ExecutableElement) ((JCNewClass) tree).constructor; + } + /** * The type of the lambda or method reference tree is a functional interface type. This method * returns the single abstract method declared by that functional interface. (The type of this @@ -2263,8 +2281,8 @@ public static LiteralTree createLiteral( ProcessingEnvironment processingEnv) { Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); TreeMaker maker = TreeMaker.instance(context); - JCLiteral result = maker.Literal(typeTag, value); - result.type = (Type) typeMirror; + LiteralTree result = maker.Literal(typeTag, value); + ((JCLiteral) result).type = (Type) typeMirror; return result; } @@ -2730,10 +2748,8 @@ public static JCFieldAccess Select(TreeMaker treeMaker, Tree base, Symbol sym) { */ public static JCFieldAccess Select( TreeMaker treeMaker, Tree base, com.sun.tools.javac.util.Name name) { - /* - * There's no need for reflection here. The only reason we even declare this method is so that - * callers don't have to remember which overload we provide a wrapper around. - */ + // There's no need for reflection here. The only reason we even declare this method + // is so that callers don't have to remember which overload we provide a wrapper around. return treeMaker.Select((JCExpression) base, name); } From 0c585d74b3a7eb8519ecc87db2e4c23399d896aa Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 12 Mar 2024 10:30:02 -0700 Subject: [PATCH 100/173] Fix commit 1a86450a8174b96d94de75ecc603510509eb64f2 --- SKIP-REQUIRE-JAVADOC | 1 + .../main/java/org/checkerframework/framework/util/AtmCombo.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 SKIP-REQUIRE-JAVADOC diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC new file mode 100644 index 00000000000..9ef0f0065ec --- /dev/null +++ b/SKIP-REQUIRE-JAVADOC @@ -0,0 +1 @@ +Remove this file. diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java index 93d0d49b300..ea19e66a529 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java @@ -79,7 +79,7 @@ public static AtmKind valueOf(AnnotatedTypeMirror atm) { * * @see AtmCombo#accept */ -@SuppressWarnings("EnumOrdinal") // Use enum ordinals as array indices. +@SuppressWarnings("EnumOrdinal") // Uses arrays instead of maps for access public enum AtmCombo { ARRAY_ARRAY(AtmKind.ARRAY, AtmKind.ARRAY), ARRAY_DECLARED(AtmKind.ARRAY, AtmKind.DECLARED), From 8b977222fbdd1ef3080a7f3f533232d02438bb40 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 12 Mar 2024 10:30:26 -0700 Subject: [PATCH 101/173] Remove file `SKIP-REQUIRE-JAVADOC` --- SKIP-REQUIRE-JAVADOC | 1 - 1 file changed, 1 deletion(-) delete mode 100644 SKIP-REQUIRE-JAVADOC diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC deleted file mode 100644 index 9ef0f0065ec..00000000000 --- a/SKIP-REQUIRE-JAVADOC +++ /dev/null @@ -1 +0,0 @@ -Remove this file. From e2f0ded764815a3f5d519d86a6459f9ff5259b92 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 12 Mar 2024 12:33:53 -0700 Subject: [PATCH 102/173] Add an option to convert type argument inference crashes into warnings (#6479) --- .../CheckerFrameworkPerDirectoryTest.java | 4 +++- .../framework/source/SourceChecker.java | 8 +++++++ .../DefaultTypeArgumentInference.java | 21 ++++++++++++------- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java index 8891041925a..623dc9d25b6 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkPerDirectoryTest.java @@ -136,7 +136,9 @@ protected CheckerFrameworkPerDirectoryTest( this.checkerNames = checkerNames; this.testDir = testDir; this.classpathExtra = classpathExtra; - this.checkerOptions = Arrays.asList(checkerOptions); + this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions)); + this.checkerOptions.add("-AajavaChecks"); + this.checkerOptions.add("-AconvertTypeArgInferenceCrashToWarning=false"); } /** Run the tests. */ diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index ff8001285d8..117ae16a4ca 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -443,6 +443,11 @@ // Also checks that annotations can be inserted. For each Java file, clears all annotations and // reinserts them, then checks if the original and modified ASTs are equivalent. "ajavaChecks", + + // Converts type argument inference crashes into errors. By default, this option is true. + // Use "-AconvertTypeArgInferenceCrashToWarning=false" to turn this option off and allow type + // argument inference crashes to crash the type checker. + "convertTypeArgInferenceCrashToWarning" }) public abstract class SourceChecker extends AbstractTypeProcessor implements OptionConfiguration { @@ -624,6 +629,9 @@ protected SourceChecker() {} /** True if the -AwarnUnneededSuppressions command-line argument was passed. */ private boolean warnUnneededSuppressions; + /** Creates a source checker. */ + protected SourceChecker() {} + // Also see initChecker(). @Override public final synchronized void init(ProcessingEnvironment env) { diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java index 1a95b66c923..0def0d9fcdb 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/DefaultTypeArgumentInference.java @@ -114,14 +114,19 @@ public InferenceResult inferTypeArgs( return result.swapTypeVariables(methodType, expressionTree); } } catch (Exception ex) { - // This should never happen, if javac infers type arguments so should the Checker - // Framework. However, given how buggy javac inference is, this probably will, so deal - // with it gracefully. - return new InferenceResult( - Collections.emptyList(), - false, - true, - "An exception occurred: " + ex.getLocalizedMessage()); + if (typeFactory + .getChecker() + .getBooleanOption("convertTypeArgInferenceCrashToWarning", true)) { + // This should never happen, if javac infers type arguments so should the Checker + // Framework. However, given how buggy javac inference is, this probably will, so deal + // with it gracefully. + return new InferenceResult( + Collections.emptyList(), + false, + true, + "An exception occurred: " + ex.getLocalizedMessage()); + } + throw ex; } finally { if (!java8InferenceStack.isEmpty()) { java8Inference = java8InferenceStack.pop(); From aa79504419db7981126646985f8a6b38b7ccd1da Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 12 Mar 2024 12:34:47 -0700 Subject: [PATCH 103/173] Fix type argument inference crash (#6491) This crash was because a type argument contained the type variable for which it was an argument. Fixes #6442. --- .../InvocationTypeInference.java | 2 +- .../typeinference8/types/InferenceType.java | 37 ++++++++++++++++++- .../util/typeinference8/util/Theta.java | 19 ++++++++++ framework/tests/all-systems/Issue6442.java | 34 +++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 framework/tests/all-systems/Issue6442.java diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java index c6ac3207240..f43bbc3d4ca 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java @@ -399,7 +399,7 @@ public BoundSet createB3( resolve.incorporateToFixedPoint(newBounds); return resolve; } - if (target.isProper()) { + if (target.isProper() && target.getJavaType().getKind().isPrimitive()) { // From the JLS: // "T is a primitive type, and one of the primitive wrapper classes mentioned in // 5.1.7 is an instantiation, upper bound, or lower bound for [the variable] in B2." diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java index 1f4921b73ef..3b4f8487679 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java @@ -132,6 +132,40 @@ public static AbstractType create( } } + /** + * Same as {@link #create(AnnotatedTypeMirror, TypeMirror, Theta, AnnotationMirrorMap, + * Java8InferenceContext)}, but if {@code type} contains any type variables that are in {@code + * map}, but already have an instantiation, they are treated as proper types. + * + * @param type the annotated type mirror + * @param typeMirror the java type + * @param map a mapping from type variable to inference variable + * @param qualifierVars a mapping from polymorphic annotation to {@link QualifierVar} + * @param context the context + * @return the abstract type for the given TypeMirror and AnnotatedTypeMirror + */ + public static AbstractType createIgnoreInstantiated( + AnnotatedTypeMirror type, + TypeMirror typeMirror, + @Nullable Theta map, + AnnotationMirrorMap qualifierVars, + Java8InferenceContext context) { + assert type != null; + if (map == null) { + return new ProperType(type, typeMirror, qualifierVars, context); + } + + if (typeMirror.getKind() == TypeKind.TYPEVAR && map.containsKey(type.getUnderlyingType())) { + return new UseOfVariable( + (AnnotatedTypeVariable) type, map.get(type.getUnderlyingType()), qualifierVars, context); + } else if (AnnotatedContainsInferenceVariable.hasAnyTypeVariable( + map.getNotInstantiated(), type)) { + return new InferenceType(type, typeMirror, map, qualifierVars, context); + } else { + return new ProperType(type, typeMirror, qualifierVars, context); + } + } + /** * Creates abstract types for each TypeMirror. The created type is an {@link InferenceType} if it * contains any type variables that are mapped to inference variables as specified by {@code map}. @@ -256,7 +290,8 @@ public AbstractType applyInstantiations() { } AnnotatedTypeMirror newType = typeFactory.getTypeVarSubstitutor().substitute(mapping, type); - return create(newType, newTypeJava, map, context); + return createIgnoreInstantiated( + newType, newTypeJava, map, AnnotationMirrorMap.emptyMap(), context); } @Override diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java index 84523f8b073..3ae9182408b 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/util/Theta.java @@ -1,6 +1,9 @@ package org.checkerframework.framework.util.typeinference8.util; +import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; +import java.util.List; import javax.lang.model.type.TypeVariable; import org.checkerframework.framework.util.typeinference8.types.Variable; import org.checkerframework.javacutil.TypesUtils; @@ -46,4 +49,20 @@ public Variable get(Object key) { } return super.get(key); } + + /** + * Returns a list of type variables that do not yet have a value. + * + * @return a list of type variables that do not yet have a value + */ + public Collection getNotInstantiated() { + List list = new ArrayList<>(); + forEach( + (typevar, var) -> { + if (var.getInstantiation() == null) { + list.add(typevar); + } + }); + return list; + } } diff --git a/framework/tests/all-systems/Issue6442.java b/framework/tests/all-systems/Issue6442.java new file mode 100644 index 00000000000..a25fb6cf8cd --- /dev/null +++ b/framework/tests/all-systems/Issue6442.java @@ -0,0 +1,34 @@ +package beamcrash; + +public class Issue6442 { + + public static class Crash + extends Issue6442.PTransform>, PCollection>>> { + + public PCollection, Iterable>> expand( + PCollection, I>> x) { + return x.apply(new Crash<>()); + } + } + + public interface PValue extends POutput, PInput {} + + public interface POutput {} + + public interface PInput {} + + public static class Pair {} + + public static class PCollection extends PValueBase implements PValue { + + public O1 apply(PTransform, O1> t) { + throw new RuntimeException(); + } + } + + public abstract static class PValueBase implements PValue {} + + public abstract static class PTransform {} + + public static class ShardedKey {} +} From ed03a1f711293d844f06b87989592694887f1140 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 12 Mar 2024 18:08:57 -0700 Subject: [PATCH 104/173] Use version-specific versions of methods reflectively Co-authored-by: Werner Dietl --- .../checkerframework/javacutil/Resolver.java | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java index 627d44fee85..158b65116f9 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java @@ -58,36 +58,18 @@ public class Resolver { // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError private static final Method ACCESSERROR_ACCESS; + /** The latest source version supported by this compiler. */ + private static final int sourceVersionNumber = + Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length())); + /** Whether we are running on at least Java 13. */ - private static final boolean atLeastJava13; + private static final boolean atLeastJava13 = sourceVersionNumber >= 13; /** Whether we are running on at least Java 23. */ - private static final boolean atLeastJava23; - - /** - * Determines whether the given {@link SourceVersion} release version string is supported. - * - * @param release the {@link SourceVersion} release version - * @return whether the given version is supported - */ - private static boolean atLeastJava(String release) { - final SourceVersion latestSource = SourceVersion.latest(); - SourceVersion javaVersion; - try { - javaVersion = SourceVersion.valueOf(release); - } catch (IllegalArgumentException e) { - javaVersion = null; - } - @SuppressWarnings("EnumOrdinal") // No better way to compare. - boolean atLeastJava = javaVersion != null && latestSource.ordinal() >= javaVersion.ordinal(); - return atLeastJava; - } + private static final boolean atLeastJava23 = sourceVersionNumber >= 23; static { try { - atLeastJava13 = atLeastJava("RELEASE_13"); - atLeastJava23 = atLeastJava("RELEASE_23"); - FIND_METHOD = Resolve.class.getDeclaredMethod( "findMethod", From 721ec9792f5f0b8f8efc92e087a23730df707691 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 13 Mar 2024 06:52:09 -0700 Subject: [PATCH 105/173] Suppress deprecation for removal warning in test (#6498) Co-authored-by: Werner Dietl --- checker/tests/regex/RawTypeTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/checker/tests/regex/RawTypeTest.java b/checker/tests/regex/RawTypeTest.java index b692173c335..c2fdf1c3923 100644 --- a/checker/tests/regex/RawTypeTest.java +++ b/checker/tests/regex/RawTypeTest.java @@ -30,6 +30,7 @@ public void m3(Class c) { m2(c); } + @SuppressWarnings("removal") // AccessController in JDK 17 public void m4() { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { From d2addd4a4328185be2c660da4c6b29969e18c68c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 13 Mar 2024 07:00:03 -0700 Subject: [PATCH 106/173] Remove uid fields that shadow fields from a superclass (#6502) Co-authored-by: Werner Dietl From 4656a117c170d2d1d5ccd00e3c9eb216a80e0538 Mon Sep 17 00:00:00 2001 From: Sanjay Malakar Date: Wed, 13 Mar 2024 16:28:05 -0700 Subject: [PATCH 107/173] Fix AssertionError in MustCallInference::inferOwningField with @MustCallUnknown fields, fixes #6480 (#6481) --- .../resourceleak/MustCallInference.java | 24 ++++++++++++++++++- .../ResourceLeakAnnotatedTypeFactory.java | 11 --------- .../non-annotated/GenericClassFieldCrash.java | 18 ++++++++++++++ 3 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java index a9a1c31d59f..5fba553a26f 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/MustCallInference.java @@ -380,6 +380,28 @@ private void addOwningToParam(int index) { wpi.addDeclarationAnnotationToFormalParameter(methodElt, index, OWNING); } + /** + * This method checks if a field is an owning candidate. A field is an owning candidate if it has + * a non-empty must-call obligation, unless it is {code @MustCallUnknown}. For a + * {code @MustCallUnknown} field, we don't want to infer anything. So, we conservatively treat it + * as a non-owning candidate. + * + * @param resourceLeakAtf the type factory + * @param field the field to check + * @return true if the field is an owning candidate, false otherwise + */ + private boolean isFieldOwningCandidate( + ResourceLeakAnnotatedTypeFactory resourceLeakAtf, Element field) { + AnnotationMirror mustCallAnnotation = resourceLeakAtf.getMustCallAnnotation(field); + if (mustCallAnnotation == null) { + // Indicates @MustCallUnknown. We want to conservatively avoid inferring an @Owning + // annotation for @MustCallUnknown. + return false; + } + // Otherwise, the field is an @Owning candidate if it has a non-empty @MustCall obligation + return !resourceLeakAtf.getMustCallValues(mustCallAnnotation).isEmpty(); + } + /** * Adds the node to the disposedFields map and the owningFields set if it is a field and its * must-call obligation is satisfied by the given method call. If so, it will be given an @Owning @@ -393,7 +415,7 @@ private void inferOwningField(Node node, MethodInvocationNode invocation) { if (nodeElt == null || !nodeElt.getKind().isField()) { return; } - if (resourceLeakAtf.isFieldWithNonemptyMustCallValue(nodeElt)) { + if (isFieldOwningCandidate(resourceLeakAtf, nodeElt)) { node = NodeUtils.removeCasts(node); JavaExpression nodeJe = JavaExpression.fromNode(node); AnnotationMirror cmAnno = getCalledMethodsAnno(invocation, nodeJe); diff --git a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java index a42ede79343..ba14af629d0 100644 --- a/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakAnnotatedTypeFactory.java @@ -104,17 +104,6 @@ public ResourceLeakAnnotatedTypeFactory(BaseTypeChecker checker) { this.postInit(); } - /** - * Is the given element a candidate to be an owning field? A candidate owning field must have a - * non-empty must-call obligation. - * - * @param element a element - * @return true iff the given element is a field with non-empty @MustCall obligation - */ - /*package-private*/ boolean isFieldWithNonemptyMustCallValue(Element element) { - return element.getKind().isField() && !hasEmptyMustCallValue(element); - } - @Override protected Set> createSupportedTypeQualifiers() { return getBundledTypeQualifiers( diff --git a/checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java b/checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java new file mode 100644 index 00000000000..b5143071b21 --- /dev/null +++ b/checker/tests/ainfer-resourceleak/non-annotated/GenericClassFieldCrash.java @@ -0,0 +1,18 @@ +/** + * This test case targets the scenario where a generic field's @MustCall obligation might be + * unknown, ensuring the framework does not erroneously infer @Owning annotations or throw + * AssertionErrors when processing such cases. + */ +class Generic { + public T data; + + public Generic(T data) { + this.data = data; + } +} + +public class GenericClassFieldCrash { + private void onPacket(Generic foo) { + String.format("socket received: data '%s'", foo.data); + } +} From b276fd94942b57b611d62c261d1dbb8e57ceee41 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 04:23:36 +0000 Subject: [PATCH 108/173] Update versions.errorprone to v2.26.1 (#6492) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 88139a73d3459bc01ff1f07ce032c6fb7eab03d9 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 14 Mar 2024 11:16:46 -0700 Subject: [PATCH 109/173] Add more aliases for nullness annotations Co-authored-by: Werner Dietl --- .../NullnessNoInitAnnotatedTypeFactory.java | 24 ++++++++++++------- ...holeProgramInferenceJavaParserStorage.java | 2 ++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java index 4eae71b13f6..b5b74cd97c8 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java @@ -114,10 +114,12 @@ public class NullnessNoInitAnnotatedTypeFactory // "rest". // Keep the original string constant in a comment to allow searching for it. /** Aliases for {@code @Nonnull}. */ - @SuppressWarnings( - "signature:assignment.type.incompatible") // Class names intentionally obfuscated + @SuppressWarnings({ + "signature:argument.type.incompatible", // Class names intentionally obfuscated + "signature:assignment.type.incompatible" // Class names intentionally obfuscated + }) private static final List<@FullyQualifiedName String> NONNULL_ALIASES = - Arrays.asList( + Arrays.<@FullyQualifiedName String>asList( // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/NonNull.java // https://developer.android.com/reference/androidx/annotation/NonNull "android.annotation.NonNull", @@ -207,10 +209,12 @@ public class NullnessNoInitAnnotatedTypeFactory // ../../../../../../../../docs/manual/nullness-checker.tex . // See more comments with NONNULL_ALIASES above. /** Aliases for {@code @Nullable}. */ - @SuppressWarnings( - "signature:assignment.type.incompatible") // Class names intentionally obfuscated + @SuppressWarnings({ + "signature:argument.type.incompatible", // Class names intentionally obfuscated + "signature:assignment.type.incompatible" // Class names intentionally obfuscated + }) private static final List<@FullyQualifiedName String> NULLABLE_ALIASES = - Arrays.asList( + Arrays.<@FullyQualifiedName String>asList( // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/Nullable.java // https://developer.android.com/reference/androidx/annotation/Nullable "android.annotation.Nullable", @@ -340,10 +344,12 @@ public class NullnessNoInitAnnotatedTypeFactory // ../../../../../../../../docs/manual/nullness-checker.tex . // See more comments with NONNULL_ALIASES above. /** Aliases for {@code @PolyNull}. */ - @SuppressWarnings( - "signature:assignment.type.incompatible") // Class names intentionally obfuscated + @SuppressWarnings({ + "signature:argument.type.incompatible", // Class names intentionally obfuscated + "signature:assignment.type.incompatible" // Class names intentionally obfuscated + }) private static final List<@FullyQualifiedName String> POLYNULL_ALIASES = - Arrays.asList( + Arrays.<@FullyQualifiedName String>asList( // "com.google.protobuf.Internal.ProtoPassThroughNullness", "com.go".toString() + "ogle.protobuf.Internal.ProtoPassThroughNullness"); diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index e14409a448a..ea64cf56520 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -75,6 +75,7 @@ import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.wholeprograminference.WholeProgramInference.OutputFormat; import org.checkerframework.dataflow.analysis.Analysis; +import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.framework.ajava.AnnotationMirrorToAnnotationExprConversion; import org.checkerframework.framework.ajava.AnnotationTransferVisitor; import org.checkerframework.framework.ajava.DefaultJointVisitor; @@ -164,6 +165,7 @@ public static Set getInvisibleQualifierNames(AnnotatedTypeFactory atypeF * @param qual an annotation class * @return true iff {@code qual} is meta-annotated with {@link InvisibleQualifier} */ + @Pure public static boolean isInvisible(Class qual) { return Arrays.stream(qual.getAnnotations()) .anyMatch(anno -> anno.annotationType() == InvisibleQualifier.class); From 3dba0d01054195d965c8b2f712e70f28fa89b154 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:42:15 -0700 Subject: [PATCH 110/173] Handle zero-argument invocations of methods with polymorphic vararg parameters (#6485) Co-authored-by: Michael Ernst --- checker/tests/tainting/PolyVarArgs.java | 51 +++++++++++++++++++ .../poly/AbstractQualifierPolymorphism.java | 3 +- .../poly/DefaultQualifierPolymorphism.java | 10 ++++ .../checkerframework/javacutil/TreeUtils.java | 18 +++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 checker/tests/tainting/PolyVarArgs.java diff --git a/checker/tests/tainting/PolyVarArgs.java b/checker/tests/tainting/PolyVarArgs.java new file mode 100644 index 00000000000..de2848d06f2 --- /dev/null +++ b/checker/tests/tainting/PolyVarArgs.java @@ -0,0 +1,51 @@ +import org.checkerframework.checker.tainting.qual.*; + +class PolyVarArgs { + + void testVarArgsNoFormals() { + @Tainted String tainted = varArgsNoFormals(); + @Untainted String untainted = varArgsNoFormals("a"); + @Untainted String untainted2 = varArgsNoFormals("b", "c"); + } + + void testVarArgsNoFormalsInvalid() { + // :: error: (assignment) + @Untainted String tainted = varArgsNoFormals(); + } + + void testVarArgsWithFormals() { + @Tainted String tainted = varArgsWithFormals(1); + @Untainted String untainted = varArgsWithFormals(1, "a"); + @Untainted String untainted2 = varArgsWithFormals(1, "a", "b"); + } + + void testVarArgsWithFormalsInvalid() { + // :: error: (assignment) + @Untainted String tainted = varArgsWithFormals(1); + } + + void testVarArgsWithPolyFormals() { + @Tainted String tainted = varArgsWithPolyFormals(1); + + // :: warning: (cast.unsafe) + @Untainted int safeInt = (@Untainted int) 1; + @Untainted String untainted = varArgsWithPolyFormals(safeInt, "a"); + } + + void testVarArgsWithPolyFormalsInvalid() { + // :: error: (assignment) + @Untainted String tainted = varArgsWithPolyFormals(1); + } + + @PolyTainted String varArgsNoFormals(@PolyTainted String... s) { + throw new Error(); + } + + @PolyTainted String varArgsWithFormals(int a, @PolyTainted String... s) { + throw new Error(); + } + + @PolyTainted String varArgsWithPolyFormals(@PolyTainted int a, @PolyTainted String... s) { + throw new Error(); + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java index c2b190f11df..892e501a27b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java @@ -200,7 +200,8 @@ public void resolve(MethodInvocationTree tree, AnnotatedExecutableType type) { collector.visit(atypeFactory.getReceiverType(tree), type.getReceiverType())); } - if (instantiationMapping != null && !instantiationMapping.isEmpty()) { + if ((instantiationMapping != null && !instantiationMapping.isEmpty()) + || TreeUtils.isCallToVarArgsMethodWithZeroVarargsActuals(tree)) { replacer.visit(type, instantiationMapping); } else { completer.visit(type); diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java index f571fc5eec4..8d203c05d41 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java @@ -35,6 +35,16 @@ public DefaultQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFact @Override protected void replace( AnnotatedTypeMirror type, AnnotationMirrorMap replacements) { + if (replacements.isEmpty() && type.getEffectiveAnnotation() != null) { + // If the 'replacements' map is empty, it is likely a case where a method with + // a varargs parameter was invoked with zero varargs actuals. + // In this case, the polymorphic qualifiers should be replaced with the top type in + // the qualifier hierarchy, since there is no further information to deduce. + AnnotationMirror effectiveAnno = type.getEffectiveAnnotation(); + if (qualHierarchy.isPolymorphicQualifier(effectiveAnno)) { + replacements.put(effectiveAnno, qualHierarchy.getTopAnnotation(effectiveAnno)); + } + } for (Map.Entry pqentry : replacements.entrySet()) { AnnotationMirror poly = pqentry.getKey(); if (type.hasAnnotation(poly)) { diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 045b1420242..5f2b3a92d4a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -2630,6 +2630,24 @@ public static boolean isVarArgs(MethodInvocationTree invok) { return isVarArgs(elementFromUse(invok), invok.getArguments()); } + /** + * Returns true if the given method invocation is an invocation of a method with a vararg + * parameter, and the invocation has zero vararg actuals. + * + * @param invok the method invocation + * @return true if the given method invocation is an invocation of a method with a vararg + * parameter, and the invocation has with zero vararg actuals + */ + public static boolean isCallToVarArgsMethodWithZeroVarargsActuals(MethodInvocationTree invok) { + if (!TreeUtils.isVarArgs(invok)) { + return false; + } + int numParams = elementFromUse(invok).getParameters().size(); + // The comparison of the number of arguments to the number of formals (minus one) checks whether + // there are no varargs actuals + return invok.getArguments().size() == numParams - 1; + } + /** * Returns true if the given constructor invocation is a varargs invocation. * From e0918aa6d771f719e842b84253bc0d024d99b208 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 14 Mar 2024 12:08:53 -0700 Subject: [PATCH 111/173] Provide a method to extend message key matching (#6495) Co-authored-by: Werner Dietl --- .../checkerframework/framework/source/SourceChecker.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 117ae16a4ca..fb515e4ad2c 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2486,8 +2486,10 @@ private boolean shouldSuppress( * Does the given messageKey match a messageKey that appears in a SuppressWarnings? Subclasses * should override this method if they need additional logic to compare message keys. * - * @param messageKey the message key - * @param messageKeyInSuppressWarningsString the message key in a SuppressWarnings + * @param messageKey the message key of the error that is being emitted, without any "checker:" + * prefix + * @param messageKeyInSuppressWarningsString the message key in a {@code @SuppressWarnings} + * annotation * @return true if the arguments match */ protected boolean messageKeyMatches( From b5372dfbbbbcb40da97116350c852042ffb732b0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 14 Mar 2024 12:15:19 -0700 Subject: [PATCH 112/173] Remove workaround for illegal-access warnings (#6497) Co-authored-by: Werner Dietl --- docs/tutorial/tests/ant.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/tutorial/tests/ant.xml b/docs/tutorial/tests/ant.xml index 9ce812aa5b6..080de0a4ce5 100644 --- a/docs/tutorial/tests/ant.xml +++ b/docs/tutorial/tests/ant.xml @@ -97,10 +97,6 @@ - - - - From d21178fede2610b2e6737c4f188928ad5e08ce6f Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Thu, 14 Mar 2024 12:33:35 -0700 Subject: [PATCH 113/173] Fix stackoverflow from Issue 6421 --- .../framework/util/typeinference8/types/AbstractType.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java index 7f858f6ae88..cf673f31a28 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java @@ -409,6 +409,8 @@ public boolean isParameterizedType() { public AbstractType getMostSpecificArrayType() { if (getTypeKind() == TypeKind.ARRAY) { return this; + } else if (TypesUtils.isObject(getJavaType())) { + return null; } else { AnnotatedTypeMirror msat = mostSpecificArrayType(getAnnotatedType()); TypeMirror typeMirror = @@ -430,6 +432,8 @@ public AbstractType getMostSpecificArrayType() { private AnnotatedTypeMirror mostSpecificArrayType(AnnotatedTypeMirror type) { if (type.getKind() == TypeKind.ARRAY) { return type; + } else if (TypesUtils.isObject(type.getUnderlyingType())) { + return null; } else { for (AnnotatedTypeMirror superType : this.getAnnotatedType().directSupertypes()) { AnnotatedTypeMirror arrayType = mostSpecificArrayType(superType); From 7ee53cac47bc4e89bc360fe75b87232c3237877b Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:32:22 -0700 Subject: [PATCH 114/173] Link Accumulation Checker Javadoc to manual (#6504) --- .../common/accumulation/AccumulationChecker.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java index d6b6cc5763c..2d9716a5405 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationChecker.java @@ -6,8 +6,6 @@ import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.returnsreceiver.ReturnsReceiverChecker; -// TODO: This Javadoc comment should reference the Checker Framework manual, once the Accumulation -// Checker chapter is uncommented in the manual's LaTeX source. /** * An accumulation checker is one that accumulates some property: method calls, map keys, etc. * @@ -20,6 +18,8 @@ * *

          The primary extension point is the constructor of {@link AccumulationAnnotatedTypeFactory}, * which every subclass should override to provide custom annotations. + * + * @checker_framework.manual #accumulation-checker Building an accumulation checker */ public abstract class AccumulationChecker extends BaseTypeChecker { From 4cfb9a0bc17f2f41c98fc7ad9cd05578c142fb16 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 15 Mar 2024 10:28:22 -0700 Subject: [PATCH 115/173] Do not silently ignore catastrophic problem Co-authored-by: Werner Dietl --- .../main/java/org/checkerframework/javacutil/TreeUtils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 5f2b3a92d4a..91347493fe4 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -1755,7 +1755,11 @@ public static boolean isCompactCanonicalRecordConstructor(MethodTree method) { */ public static boolean isAutoGeneratedRecordMember(Tree member) { Element e = elementFromTree(member); - return e != null && ElementUtils.isAutoGeneratedRecordMember(e); + if (e == null) { + throw new BugInCF( + "TreeUtils.isAutoGeneratedRecordMember: null element for member tree: " + member); + } + return ElementUtils.isAutoGeneratedRecordMember(e); } /** From 39f4c239c06d6b916112d0181cfc07f56f5d56a7 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 18 Mar 2024 08:37:28 -0700 Subject: [PATCH 116/173] Remove duplicated option (#6496) Co-authored-by: Werner Dietl From 581a223caf716a0d54a695e4d859d0eb29d835ec Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Mon, 18 Mar 2024 16:45:36 +0000 Subject: [PATCH 117/173] Add alias for Index's Checker's NonNegative annotation with javax.annotation.Nonnegative --- .../lowerbound/LowerBoundAnnotatedTypeFactory.java | 1 + checker/tests/index/JavaxAnnotationNonnegative.java | 10 ++++++++++ .../common/value/ValueAnnotatedTypeFactory.java | 1 + 3 files changed, 12 insertions(+) create mode 100644 checker/tests/index/JavaxAnnotationNonnegative.java diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java index 05c6be9f793..cbf9e24a6e2 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java @@ -123,6 +123,7 @@ public LowerBoundAnnotatedTypeFactory(BaseTypeChecker checker) { // Any annotations that are aliased to @NonNegative, @Positive, or @GTENegativeOne must also // be aliased in the constructor of ValueAnnotatedTypeFactory to the appropriate // @IntRangeFrom* annotation. + addAliasedTypeAnnotation("javax.annotation.Nonnegative", NN); addAliasedTypeAnnotation(IndexFor.class, NN); addAliasedTypeAnnotation(IndexOrLow.class, GTEN1); addAliasedTypeAnnotation(IndexOrHigh.class, NN); diff --git a/checker/tests/index/JavaxAnnotationNonnegative.java b/checker/tests/index/JavaxAnnotationNonnegative.java new file mode 100644 index 00000000000..36c1300c66d --- /dev/null +++ b/checker/tests/index/JavaxAnnotationNonnegative.java @@ -0,0 +1,10 @@ +// Test for https://github.com/typetools/checker-framework/issues/6507 + +public class JavaxAnnotationNonnegative { + + public static void test(@javax.annotation.Nonnegative int y) { + get(y); + } + + public static void get(@org.checkerframework.checker.index.qual.NonNegative int x) {} +} diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index 0e144af7151..5e166c28110 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -261,6 +261,7 @@ public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { addAliasedTypeAnnotation( "org.checkerframework.checker.index.qual.SubstringIndexFor", createIntRangeFromGTENegativeOne()); + addAliasedTypeAnnotation("javax.annotation.Nonnegative", createIntRangeFromNonNegative()); // PolyLength is syntactic sugar for both @PolySameLen and @PolyValue addAliasedTypeAnnotation("org.checkerframework.checker.index.qual.PolyLength", POLY); From 08a3fb1f1c1ed35900f2eac8ade83a1897ea0744 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:10:52 -0700 Subject: [PATCH 118/173] Emit error when `@HasQualifierParameter` is not supplied the top qualifier --- checker/tests/tainting/ExtendHasQual.java | 2 +- .../tests/tainting/HasQualifierParameterIsNonTop.java | 8 ++++++++ .../common/basetype/BaseTypeVisitor.java | 11 ++++++++++- .../common/basetype/messages.properties | 1 + 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 checker/tests/tainting/HasQualifierParameterIsNonTop.java diff --git a/checker/tests/tainting/ExtendHasQual.java b/checker/tests/tainting/ExtendHasQual.java index 7c1af290fe5..4ff22baf860 100644 --- a/checker/tests/tainting/ExtendHasQual.java +++ b/checker/tests/tainting/ExtendHasQual.java @@ -18,7 +18,7 @@ static class MyBuffer1 extends Buffer {} static class MyBuffer2 extends Buffer {} @HasQualifierParameter(Nullable.class) - // :: error: (missing.has.qual.param) + // :: error: (invalid.qual.param) static class MyBuffer3 extends Buffer {} @HasQualifierParameter({Tainted.class, Nullable.class}) diff --git a/checker/tests/tainting/HasQualifierParameterIsNonTop.java b/checker/tests/tainting/HasQualifierParameterIsNonTop.java new file mode 100644 index 00000000000..c95da332ecd --- /dev/null +++ b/checker/tests/tainting/HasQualifierParameterIsNonTop.java @@ -0,0 +1,8 @@ +import org.checkerframework.checker.tainting.qual.*; +import org.checkerframework.framework.qual.HasQualifierParameter; + +@HasQualifierParameter(Untainted.class) +// :: error: (invalid.qual.param) +class HasQualifierParameterIsNonTop { + @PolyTainted String input; +} diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 662c3007b25..c134da502f7 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -100,6 +100,7 @@ import org.checkerframework.framework.flow.CFAbstractStore; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.framework.qual.DefaultQualifier; +import org.checkerframework.framework.qual.HasQualifierParameter; import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.qual.Unused; @@ -698,7 +699,7 @@ protected void warnInvalidPolymorphicQualifier( */ protected void checkQualifierParameter(ClassTree classTree) { // Set of polymorphic qualifiers for hierarchies that do not have a qualifier parameter and - // therefor cannot appear on a field. + // therefore cannot appear on a field. AnnotationMirrorSet illegalOnFieldsPolyQual = new AnnotationMirrorSet(); // Set of polymorphic annotations for all hierarchies AnnotationMirrorSet polys = new AnnotationMirrorSet(); @@ -713,6 +714,14 @@ protected void checkQualifierParameter(ClassTree classTree) { // @HasQualifierParameter that must be checked. // } + if (!atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) + && atypeFactory.getDeclAnnotation(classElement, HasQualifierParameter.class) != null) { + // The argument to a @HasQualifierParameter annotation must be the top type in the + // type system. + checker.reportError(classTree, "invalid.qual.param", top); + break; + } + if (atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top) && atypeFactory.hasExplicitNoQualifierParameterInHierarchy(classElement, top)) { checker.reportError(classTree, "conflicting.qual.param", top); diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 3d91b4bfe4d..41658642aa5 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -107,6 +107,7 @@ invalid.polymorphic.qualifier=Cannot use polymorphic qualifier %s %s invalid.polymorphic.qualifier.use=Cannot use polymorphic qualifier %s on a field unless the class is annotated with @HasQualifierParameter missing.has.qual.param=Missing @HasQualifierParameter for hierarchy %s.%nClass extends or implements a type that has a qualifier parameter. conflicting.qual.param=Conflicting @HasQualifierParameter.%nClass has both @HasQualifierParameter and @NoQualifierParameter for %s. +invalid.qual.param=The argument to @HasQualifierParameter must be the top type in the type system field.invariant.not.found=the field invariant annotation refers to fields not found in a superclass%nfields not found: %s From da1eb7e621ddb4deb560db73c80c15a562b38d7b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 00:20:32 +0000 Subject: [PATCH 119/173] Update dependency org.projectlombok:lombok to v1.18.32 (#6509) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 52ed2918b19f0861d37261e8425f9fad2ecfa59e Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 21 Mar 2024 08:18:09 -0700 Subject: [PATCH 120/173] Replace deprecated Gradle APIs (#6451) Co-authored-by: Aosen Xiong <82676488+Ao-senXiong@users.noreply.github.com> --- checker/build.gradle | 4 +++- dataflow/build.gradle | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/checker/build.gradle b/checker/build.gradle index e53d583acdc..a1c277507fe 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -1,7 +1,6 @@ plugins { id 'java-library' id 'base' - // https://github.com/n0mer/gradle-git-properties // Generates file build/resources/main/git.properties when the `classes` task runs. id 'com.gorylenko.gradle-git-properties' version '2.4.2' @@ -202,6 +201,9 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar task checkerJar(type: ShadowJar, dependsOn: compileJava, group: 'Build') { description = "Builds checker-${project.version}.jar with all dependencies except checker-qual and checker-util." includeEmptyDirs = false + base { + archivesName = 'checker' + } from shadowJar.source configurations = shadowJar.configurations diff --git a/dataflow/build.gradle b/dataflow/build.gradle index 847eef0fb70..9b3d3350927 100644 --- a/dataflow/build.gradle +++ b/dataflow/build.gradle @@ -40,7 +40,9 @@ def createDataflowShaded(shadedPkgName) { tasks.create(name: "dataflow${shadedPkgName}Jar", type: ShadowJar, dependsOn: compileJava, group: 'Build') { description = "Builds dataflow-${shadedPkgName}.jar." includeEmptyDirs = false - archiveBaseName.set("dataflow-${shadedPkgName}") + base { + archivesName = "dataflow-${shadedPkgName}" + } // Without this line, the Maven artifact will have the classifier "all". archiveClassifier.set('') From a76a12e0be8e3ed206b6b355cdd59ad2cd7b41b9 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 21 Mar 2024 08:20:02 -0700 Subject: [PATCH 121/173] `-AskipFiles` and `-AonlyFiles` command-line options (#6506) --- .../test/junit/NullnessSkipDirsTest.java | 4 +- docs/CHANGELOG.md | 9 ++- docs/manual/annotating-libraries.tex | 5 +- docs/manual/introduction.tex | 17 +++-- docs/manual/warnings.tex | 40 +++++++--- .../common/basetype/BaseTypeVisitor.java | 2 +- .../framework/source/SourceChecker.java | 75 +++++++++++++------ 7 files changed, 106 insertions(+), 46 deletions(-) diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java index 2be0a4b0a2b..d3a4d224c50 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSkipDirsTest.java @@ -5,7 +5,7 @@ import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -/** JUnit tests for the Nullness Checker -- testing {@code -AskipDirs} command-line argument. */ +/** JUnit tests for the Nullness Checker -- testing {@code -AskipFiles} command-line argument. */ public class NullnessSkipDirsTest extends CheckerFrameworkPerDirectoryTest { public NullnessSkipDirsTest(List testFiles) { @@ -13,7 +13,7 @@ public NullnessSkipDirsTest(List testFiles) { testFiles, org.checkerframework.checker.nullness.NullnessChecker.class, "nullness", - "-AskipDirs=/skip/this/.*"); + "-AskipFiles=/skip/this/.*"); } @Parameters diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 162d9fe78ea..cade775a96d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,13 @@ Version 3.43.0 (?? ??, 2024) **User-visible changes:** +Renamed command-line arguments: + * `-AskipDirs` has been renamed to `-AskipFiles`. + `-AskipDirs` will continue to work for the time being. + +New command-line arguments: + * `-AonlyFiles` complements `-AskipFiles` + Method, constructor, lambda, and method reference type inference has been greatly improved. The `-AconservativeUninferredTypeArguments` option is no longer necessary and has been removed. @@ -180,7 +187,7 @@ Version 3.41.0 (December 4, 2023) **User-visible changes:** New command-line options: -* `-AassumePureGetters`: Unsoundly assume that every getter method is pure. + * `-AassumePureGetters` Unsoundly assume that every getter method is pure **Implementation details:** diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index d94d9cea306..f4cdbf24f8a 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -359,7 +359,8 @@ \begin{itemize} \item Ignoring type-checking errors in unannotated parts of the library. - Use the \<-AskipDefs> or \<-AonlyDefs> command-line arguments; see + Use the \<-AskipDefs>, \<-AonlyDefs>, \<-AskipFiles>, or \<-AonlyFiles> + command-line arguments; see Section~\ref{askipdefs}. \item @@ -1094,6 +1095,6 @@ % LocalWords: AstubWarnIfNotFoundIgnoresClasses AmergeStubsWithSource % LocalWords: AstubWarnIfRedundantWithBytecode JavaStubifier StubFiles % LocalWords: BaseTypeChecker ImageObserver Graphics2D AstubWarnNote -% LocalWords: AstubNoWarnIfNotFound AstubWarn Werror +% LocalWords: AstubNoWarnIfNotFound AstubWarn Werror AonlyFiles AskipFiles % LocalWords: Aajava substring outerpackage innerpackage myMethod MyClass % LocalWords: InsertAjavaAnnotations diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index df3e255e99d..9770f5980f4 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -575,12 +575,13 @@ Suppress all errors and warnings at all uses of a given class --- or at all uses except those of a given class. See Section~\ref{askipuses}. \item \<-AskipDefs>, \<-AonlyDefs> - Suppress all errors and warnings within the definition of a given class - --- or everywhere except within the definition of a given class. See + Suppress all errors and warnings within the definition of given classes + --- or everywhere except within the definition of given classes. See Section~\ref{askipdefs}. -\item \<-AskipDirs> - Suppress all errors and warnings within a given directory/folder. See - Section~\ref{askipdirs}. +\item \<-AskipFiles>, \<-AonlyFiles> + Suppress all errors and warnings within given files or directories/folders + --- or everywhere except within given files or directories/folders. See + Section~\ref{askipfiles}. \item \<-AassumeSideEffectFree>, \<-AassumeDeterministic>, \<-AassumePure>, \<-AassumePureGetters> Unsoundly assume that every method is side-effect-free, deterministic, or both; or that every getter method is pure. @@ -1019,7 +1020,9 @@ external libraries supplied as a \code{.class} file and native methods (because the implementation is not Java code, it cannot be checked). \item - Code compiled with the \code{-AskipUses}, \code{-AonlyUses}, \code{-AskipDefs} or \code{-AonlyDefs} + Code compiled with the \code{-AskipUses}, \code{-AonlyUses}, + \code{-AskipDefs}, \code{-AonlyDefs}, \code{-AskipFiles}, or + \code{-AonlyFiles} command-line arguments (see Chapter~\ref{suppressing-warnings}). \item Dynamically generated code, such as generated by Spring or MyBatis. @@ -1737,4 +1740,4 @@ % LocalWords: requireNonNull ApermitUnsupportedJdkVersion AstubWarnNote % LocalWords: AwarnRedundantAnnotations AinferOutputOriginal % LocalWords: AshowPrefixInWarningMessages AstubNoWarnIfNotFound -% LocalWords: AshowWpiFailedInferences AassumePureGetters +% LocalWords: AshowWpiFailedInferences AassumePureGetters AonlyFiles AskipFiles diff --git a/docs/manual/warnings.tex b/docs/manual/warnings.tex index a2d1d81abdd..e258a39f1c9 100644 --- a/docs/manual/warnings.tex +++ b/docs/manual/warnings.tex @@ -51,7 +51,7 @@ \item the \code{-AskipDefs} and \code{-AonlyDefs} command-line options (Section~\ref{askipdefs}), \item - the \code{-AskipDirs} command-line option (Section~\ref{askipdirs}), + the \code{-AskipFiles} and \code{-AonlyFiles} command-line options (Section~\ref{askipfiles}), \item the \code{-AuseConservativeDefaultsForUncheckedCode=source} command-line option (Section~\ref{compiling-libraries}), @@ -568,7 +568,8 @@ You can suppress all errors and warnings at all \emph{uses} of a given class, or suppress all errors and warnings \emph{except} those at uses of a given class. (The class itself is still type-checked, unless you also use -the \code{-AskipDefs} or \code{-AonlyDefs} command-line option, see~\ref{askipdefs}). +the \code{-AskipDefs}, \code{-AonlyDefs}, \code{-AskipFiles}, or \code{-AonlyFiles} +command-line option, see~\ref{askipdefs}). You can also use these options to affect entire packages or directories/folders. Set the \code{-AskipUses} command-line option to a @@ -577,6 +578,8 @@ Or, set the \code{-AonlyUses} command-line option to a regular expression that matches fully-qualified class names (not file names) for which warnings and errors should be emitted; warnings about uses of all other classes will be suppressed. +The regular expressions are unanchored, unless you anchor them yourself +with ``\codesize\verb|^|'' and/or ``\codesize\verb|$|''. For example, suppose that you use ``{\codesize\verb|-AskipUses=^java\.|}'' on the command line @@ -629,6 +632,10 @@ definitions should be type-checked. (This is somewhat similar to NullAway's \<-XepOpt:NullAway:AnnotatedPackages> command-line argument.) +(For file names, use \code{-AskipFiles} and \code{-AonlyFiles} instead; see +Section~\ref{askipfiles}.) +The regular expressions are unanchored, unless you anchor them yourself +with ``\codesize\verb|^|'' and/or ``\codesize\verb|$|''. For example, if you use ``{\codesize\verb|-AskipDefs=^mypackage\.|}'' on the command line @@ -655,27 +662,36 @@ most important parts, you can incrementally check more classes until you are type-checking the whole thing. -\sectionAndLabel{\code{-AskipDirs} command-line option}{askipdirs} + +\sectionAndLabel{\code{-AskipFiles} and \code{-AonlyFiles} command-line options}{askipfiles} You can suppresss all errors and warnings originating from classes that are -located in a given directory/folder. -Errors and warnings may also be suppressed on a more granular level. -For example, the \code{-AonlyDefs} or the \code{-AskipDefs} command-line -options enable you to include or exclude classes from checking. +located in a given file or directory/folder, or all those that are \emph{not} in a +given file or directory/folder. -Set the \code{-AskipDirs} command-line option to a -regular expression that matches the path to directories/folders containing +Set the \code{-AskipFiles} command-line option to a +regular expression that matches the path to files or directories/folders containing classes for which errors and warnings should be suppressed. +Similarly, \code{-AonlyFiles} suppresses warnings everywhere +\emph{except} in the given files. +The regular expressions are unanchored, unless you anchor them yourself +with ``\codesize\verb|^|'' and/or ``\codesize\verb|$|''. For example, if you use -``{\codesize\verb|-AskipDirs=/build/generated|}'' on the command line -(with appropriate quoting) when invoking -\code{javac}, then the definitions of classes located in the +``{\codesize\verb|-AskipFiles=/build/generated/|}'' on the command line +when invoking +\code{javac}, then the definitions of classes located in any \codesize\verb|/build/generated| directory will not be checked. +If you supply both \code{-AskipFiles} and \code{-AonlyFiles}, then +\code{-AskipFiles} takes precedence. + A common scenario for using this argument is when you want to exclude certain directories/folders (such as those containing generated or legacy code) from type-checking. +Another scenario is when a file contains multiple top-level classes, only +one of which has a name that corresponds to the file name. + \sectionAndLabel{\code{-Alint} command-line option\label{lint-options}}{alint} diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index c134da502f7..5141e7dd7f1 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -570,7 +570,7 @@ private String oneLine(Object arg) { */ @Override public final Void visitClass(ClassTree classTree, Void p) { - if (checker.shouldSkipDefs(classTree) || checker.shouldSkipDirs(classTree)) { + if (checker.shouldSkipDefs(classTree) || checker.shouldSkipFiles(classTree)) { // Not "return super.visitClass(classTree, p);" because that would recursively call // visitors on subtrees; we want to skip the class entirely. return null; diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index fb515e4ad2c..1e9b97a8b09 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -118,15 +118,15 @@ // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings "suppressWarnings", - // Set inclusion/exclusion of type uses or definitions + // Set inclusion/exclusion of type uses, definitions, or files. // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar "skipUses", "onlyUses", "skipDefs", "onlyDefs", - - // Set inclusion/exclusion of files based on directory - "skipDirs", + "skipFiles", + "onlyFiles", + "skipDirs", // Obsolete as of 2024-03-15, replaced by "skipFiles". // Unsoundly assume all methods have no side effects, are deterministic, or both. "assumeSideEffectFree", @@ -558,12 +558,20 @@ public abstract class SourceChecker extends AbstractTypeProcessor implements Opt private @MonotonicNonNull Pattern onlyDefsPattern; /** - * Regular expression pattern to specify directories that should not be checked. + * Regular expression pattern to specify files or directories that should not be checked. + * + *

          It contains the pattern specified by the user, through the option {@code + * checkers.skipFiles}; otherwise it contains a pattern that can match no directory. + */ + private @MonotonicNonNull Pattern skipFilesPattern; + + /** + * Regular expression pattern to specify files or directories that should be checked. * - *

          It contains the pattern specified by the user, through the option {@code checkers.skipDirs}; - * otherwise it contains a pattern that can match no directory. + *

          It contains the pattern specified by the user, through the option {@code + * checkers.onlyFiles}; otherwise it contains a pattern that can match no directory. */ - private @MonotonicNonNull Pattern skipDirsPattern; + private @MonotonicNonNull Pattern onlyFilesPattern; /** The supported lint options. */ private @MonotonicNonNull Set supportedLints; @@ -940,14 +948,35 @@ private Pattern getOnlyDefsPattern(Map options) { } /** - * Extract the value of the {@code skipDirs} option given the value of the options passed to the + * Extract the value of the {@code skipFiles} option given the value of the options passed to the * checker. * * @param options the map of options and their values passed to the checker - * @return the value of the {@code skipDirs} option + * @return the value of the {@code skipFiles} option */ - private Pattern getSkipDirsPattern(Map options) { - return getSkipPattern("skipDirs", options); + private Pattern getSkipFilesPattern(Map options) { + boolean hasSkipFiles = options.containsKey("skipFiles"); + boolean hasSkipDirs = options.containsKey("skipDirs"); + if (hasSkipFiles && hasSkipDirs) { + throw new UserError("Do not supply both -AskipFiles and -AskipDirs command-line options."); + } + // This logic isn't quite right because the checker.skipDirs property might exist. + if (hasSkipDirs) { + return getSkipPattern("skipDirs", options); + } else { + return getSkipPattern("skipFiles", options); + } + } + + /** + * Extract the value of the {@code onlyFiles} option given the value of the options passed to the + * checker. + * + * @param options the map of options and their values passed to the checker + * @return the value of the {@code onlyFiles} option + */ + private Pattern getOnlyFilesPattern(Map options) { + return getOnlyPattern("onlyFiles", options); } /////////////////////////////////////////////////////////////////////////// @@ -2709,17 +2738,17 @@ public boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { } /////////////////////////////////////////////////////////////////////////// - /// Skipping dirs + /// Skipping files /// /** * Tests whether the enclosing file path of the passed tree matches the pattern specified in the - * {@code checker.skipDirs} property. + * {@code checker.skipFiles} property. * * @param tree a tree * @return true iff the enclosing directory of the tree should be skipped */ - public final boolean shouldSkipDirs(ClassTree tree) { + public final boolean shouldSkipFiles(ClassTree tree) { if (tree == null) { return false; } @@ -2728,21 +2757,25 @@ public final boolean shouldSkipDirs(ClassTree tree) { throw new BugInCF("elementFromDeclaration(%s [%s]) => null%n", tree, tree.getClass()); } String sourceFilePathForElement = ElementUtils.getSourceFilePath(typeElement); - return shouldSkipDirs(sourceFilePathForElement); + return shouldSkipFiles(sourceFilePathForElement); } /** * Tests whether the file at the file path should be not be checked because it matches the {@code - * checker.skipDirs} property. + * checker.skipFiles} property. * * @param path the path to the file to potentially skip * @return true iff the checker should not check the file at {@code path} */ - private boolean shouldSkipDirs(String path) { - if (skipDirsPattern == null) { - skipDirsPattern = getSkipDirsPattern(getOptions()); + private boolean shouldSkipFiles(String path) { + if (skipFilesPattern == null) { + skipFilesPattern = getSkipFilesPattern(getOptions()); } - return skipDirsPattern.matcher(path).find(); + if (onlyFilesPattern == null) { + onlyFilesPattern = getOnlyFilesPattern(getOptions()); + } + + return skipFilesPattern.matcher(path).find() || !onlyFilesPattern.matcher(path).find(); } /////////////////////////////////////////////////////////////////////////// From eefe2716c1221df6b9a78c093530acc0d503d403 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 27 Mar 2024 22:11:50 -0700 Subject: [PATCH 122/173] Correct equals method of InferenceType --- .../typeinference8/types/InferenceType.java | 4 ++ framework/tests/all-systems/Issue6421.java | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 framework/tests/all-systems/Issue6421.java diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java index 3b4f8487679..08d9405c53f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceType.java @@ -200,6 +200,7 @@ public AbstractType create(AnnotatedTypeMirror type, TypeMirror typeMirror) { } @Override + @SuppressWarnings("interning:not.interned") // maps should be == public boolean equals(Object o) { if (this == o) { return true; @@ -209,6 +210,9 @@ public boolean equals(Object o) { } InferenceType variable = (InferenceType) o; + if (map != variable.map) { + return false; + } if (!type.equals(variable.type)) { return false; } diff --git a/framework/tests/all-systems/Issue6421.java b/framework/tests/all-systems/Issue6421.java new file mode 100644 index 00000000000..4b7f64553fa --- /dev/null +++ b/framework/tests/all-systems/Issue6421.java @@ -0,0 +1,50 @@ +import java.util.List; + +@SuppressWarnings("all") // Just check for crashes. +public class Issue6421 { + + public static final List> DEFAULT_ARBITRARY_INTROSPECTORS = + list( + MatcherOperator.exactTypeMatchOperator( + UnidentifiableType.class, NullArbitraryIntrospector.INSTANCE), + MatcherOperator.exactTypeMatchOperator( + GeneratingWildcardType.class, context -> new ArbitraryIntrospectorResult())); + + static List list(E e1, E e2) { + throw new RuntimeException(); + } + + public interface ArbitraryIntrospector { + ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context); + } + + public interface CombinableArbitrary {} + + public static final class ArbitraryGeneratorContext {} + + public static final class MatcherOperator { + public static MatcherOperator exactTypeMatchOperator(Class type, T operator) { + throw new RuntimeException(); + } + } + + public static class GeneratingWildcardType {} + + public static class UnidentifiableType {} + + public static final class NullArbitraryIntrospector implements ArbitraryIntrospector { + + public static final NullArbitraryIntrospector INSTANCE = new NullArbitraryIntrospector(); + + @Override + public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context) { + return null; + } + } + + public static final class ArbitraryIntrospectorResult { + public ArbitraryIntrospectorResult() {} + + public ArbitraryIntrospectorResult(CombinableArbitrary value) {} + } +} From f6fb34c6a50ddcd20ff7c0186696249c4b8050b3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:49:32 -0700 Subject: [PATCH 123/173] Update dependency commons-io:commons-io to v2.16.0 (#6519) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 4929238e8ead03e9bcada8d3b04d4d585222cb38 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 2 Apr 2024 09:02:51 -0700 Subject: [PATCH 124/173] Fix crash with array constructor method references (#6516) --- .../framework/type/AnnotatedTypeFactory.java | 31 +++++++++++++++++++ .../type/GenericAnnotatedTypeFactory.java | 30 ------------------ .../typeinference8/types/AbstractType.java | 4 +-- .../typeinference8/types/InvocationType.java | 5 ++- framework/tests/all-systems/Issue6421C.java | 16 ++++++++++ 5 files changed, 53 insertions(+), 33 deletions(-) create mode 100644 framework/tests/all-systems/Issue6421C.java diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 123a064d590..8488619a487 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -2867,6 +2867,37 @@ public ParameterizedExecutableType constructorFromUseWithoutTypeArgInference(New return constructorFromUse(tree, false); } + /** + * Gets the type of the resulting constructor call of a MemberReferenceTree. + * + * @param memberReferenceTree MemberReferenceTree where the member is a constructor + * @param constructorType AnnotatedExecutableType of the declaration of the constructor + * @return AnnotatedTypeMirror of the resulting type of the constructor + */ + public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference( + MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) { + assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW; + + // The return type for constructors should only have explicit annotations from the + // constructor. The code below recreates some of the logic from TypeFromTree.visitNewClass to do + // this. + + // The return type of the constructor will be the type of the expression of the member + // reference tree. + AnnotatedTypeMirror constructorReturnType = + fromTypeTree(memberReferenceTree.getQualifierExpression()); + + if (constructorReturnType.getKind() == TypeKind.DECLARED) { + // Keep only explicit annotations and those from @Poly + AnnotatedTypes.copyOnlyExplicitConstructorAnnotations( + this, (AnnotatedDeclaredType) constructorReturnType, constructorType); + } + + // Now add back defaulting. + addComputedTypeAnnotations(memberReferenceTree.getQualifierExpression(), constructorReturnType); + return constructorReturnType; + } + /** * The implementation of {@link #constructorFromUse(NewClassTree)} and {@link * #constructorFromUseWithoutTypeArgInference(NewClassTree)}. diff --git a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java index 0969de784f0..a789b79dd73 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -7,7 +7,6 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.LambdaExpressionTree; -import com.sun.source.tree.MemberReferenceTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewClassTree; @@ -101,7 +100,6 @@ import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator; import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator; import org.checkerframework.framework.type.typeannotator.TypeAnnotator; -import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.framework.util.Contract; import org.checkerframework.framework.util.ContractsFromMethod; import org.checkerframework.framework.util.DefaultContractsFromMethod; @@ -917,34 +915,6 @@ protected void postDirectSuperTypes( } } - /** - * Gets the type of the resulting constructor call of a MemberReferenceTree. - * - * @param memberReferenceTree MemberReferenceTree where the member is a constructor - * @param constructorType AnnotatedExecutableType of the declaration of the constructor - * @return AnnotatedTypeMirror of the resulting type of the constructor - */ - public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference( - MemberReferenceTree memberReferenceTree, AnnotatedExecutableType constructorType) { - assert memberReferenceTree.getMode() == MemberReferenceTree.ReferenceMode.NEW; - - // The return type for constructors should only have explicit annotations from the - // constructor. Recreate some of the logic from TypeFromTree.visitNewClass here. - - // The return type of the constructor will be the type of the expression of the member - // reference tree. - AnnotatedDeclaredType constructorReturnType = - (AnnotatedDeclaredType) fromTypeTree(memberReferenceTree.getQualifierExpression()); - - // Keep only explicit annotations and those from @Poly - AnnotatedTypes.copyOnlyExplicitConstructorAnnotations( - this, constructorReturnType, constructorType); - - // Now add back defaulting. - addComputedTypeAnnotations(memberReferenceTree.getQualifierExpression(), constructorReturnType); - return constructorReturnType; - } - /** * Returns the primary annotation on expression if it were evaluated at path. * diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java index cf673f31a28..0da513b7018 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/AbstractType.java @@ -429,13 +429,13 @@ public AbstractType getMostSpecificArrayType() { * @param type annotated type mirror * @return the first supertype of {@code type} that is an array */ - private AnnotatedTypeMirror mostSpecificArrayType(AnnotatedTypeMirror type) { + private static AnnotatedTypeMirror mostSpecificArrayType(AnnotatedTypeMirror type) { if (type.getKind() == TypeKind.ARRAY) { return type; } else if (TypesUtils.isObject(type.getUnderlyingType())) { return null; } else { - for (AnnotatedTypeMirror superType : this.getAnnotatedType().directSupertypes()) { + for (AnnotatedTypeMirror superType : type.directSupertypes()) { AnnotatedTypeMirror arrayType = mostSpecificArrayType(superType); if (arrayType != null) { return arrayType; diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java index f86b459835d..71751add6c6 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InvocationType.java @@ -139,12 +139,15 @@ public AbstractType getReturnType(Theta map) { returnType = typeFactory.getAnnotatedType(e); } else if (invocation.getKind() == Tree.Kind.METHOD_INVOCATION || invocation.getKind() == Tree.Kind.MEMBER_REFERENCE) { - returnType = annotatedExecutableType.getReturnType(); if (invocation.getKind() == Kind.MEMBER_REFERENCE && ((MemberReferenceTree) invocation).getMode() == ReferenceMode.NEW) { + returnType = + context.typeFactory.getResultingTypeOfConstructorMemberReference( + (MemberReferenceTree) invocation, annotatedExecutableType); returnTypeJava = returnType.getUnderlyingType(); } else { returnTypeJava = methodType.getReturnType(); + returnType = annotatedExecutableType.getReturnType(); } } else { diff --git a/framework/tests/all-systems/Issue6421C.java b/framework/tests/all-systems/Issue6421C.java new file mode 100644 index 00000000000..3c5a08032a5 --- /dev/null +++ b/framework/tests/all-systems/Issue6421C.java @@ -0,0 +1,16 @@ +import java.lang.reflect.Constructor; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.stream.Stream; + +@SuppressWarnings("all") +public class Issue6421C { + private static String[] getParameterNames(Constructor constructor) { + Parameter[] parameters = constructor.getParameters(); + return Arrays.stream(parameters).map(Parameter::getName).toArray(String[]::new); + } + + public void processConstructor(Stream> stream2) { + Class[] arguments = stream2.toArray(Class[]::new); + } +} From 0c2df80304acda0fca53d6849484d2cf83c604ac Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 2 Apr 2024 09:03:26 -0700 Subject: [PATCH 125/173] Fix crash with additional argument constraints. (#6517) --- .../InvocationTypeInference.java | 53 ++++++++++++++++++- framework/tests/all-systems/Issue6421D.java | 38 +++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 framework/tests/all-systems/Issue6421D.java diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java index f43bbc3d4ca..8a933fd6c51 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java @@ -490,7 +490,7 @@ private ConstraintSet createAdditionalArgConstraints( c.add(new CheckedExceptionConstraint(ei, fi, map)); LambdaExpressionTree lambda = (LambdaExpressionTree) ei; for (ExpressionTree expression : TreeUtils.getReturnedExpressions(lambda)) { - c.addAll(createAdditionalArgConstraints(expression, fi, map)); + c.addAll(createAdditionalArgConstraintsNoLambda(expression)); } break; case METHOD_INVOCATION: @@ -524,6 +524,57 @@ private ConstraintSet createAdditionalArgConstraints( return c; } + /** + * Recursively search for method invocations and new class trees. If any are found, the additional + * variables, bounds, and constraints are returned. This method is called by {@link + * #createAdditionalArgConstraints(ExpressionTree, AbstractType, Theta)} when that method + * encounters a lambda. This method is different because it does not add checked exception + * constraints for lambdas or method references. + * + * @param expression expression to search + * @return additional constraints + */ + private ConstraintSet createAdditionalArgConstraintsNoLambda(ExpressionTree expression) { + ConstraintSet c = new ConstraintSet(); + + switch (expression.getKind()) { + case LAMBDA_EXPRESSION: + LambdaExpressionTree lambda = (LambdaExpressionTree) expression; + for (ExpressionTree returnedExpression : TreeUtils.getReturnedExpressions(lambda)) { + c.addAll(createAdditionalArgConstraintsNoLambda(returnedExpression)); + } + break; + case METHOD_INVOCATION: + case NEW_CLASS: + if (TreeUtils.isPolyExpression(expression)) { + c.add(new AdditionalArgument(expression)); + } + break; + case PARENTHESIZED: + c.addAll(createAdditionalArgConstraintsNoLambda(TreeUtils.withoutParens(expression))); + break; + case CONDITIONAL_EXPRESSION: + ConditionalExpressionTree conditional = (ConditionalExpressionTree) expression; + c.addAll(createAdditionalArgConstraintsNoLambda(conditional.getTrueExpression())); + c.addAll(createAdditionalArgConstraintsNoLambda(conditional.getFalseExpression())); + break; + default: + if (TreeUtils.isSwitchExpression(expression)) { + SwitchExpressionScanner scanner = + new FunctionalSwitchExpressionScanner<>( + (ExpressionTree tree, Void unused) -> { + c.addAll(createAdditionalArgConstraintsNoLambda(tree)); + return null; + }, + (c1, c2) -> null); + scanner.scanSwitchExpression(expression, null); + } + // no constraints + } + + return c; + } + /** * JLS * 15.12.2.2 (Assuming the method is a generic method and the method invocation does not diff --git a/framework/tests/all-systems/Issue6421D.java b/framework/tests/all-systems/Issue6421D.java new file mode 100644 index 00000000000..e9e2063641a --- /dev/null +++ b/framework/tests/all-systems/Issue6421D.java @@ -0,0 +1,38 @@ +import java.util.function.Function; +import java.util.function.Supplier; + +@SuppressWarnings("all") // Check for crashes. +public class Issue6421D { + + private Function + generateJavaTypeArbitrarySet = null; + private JavaTypeArbitraryGenerator javaTypeArbitraryGenerator = null; + private JavaArbitraryResolver javaArbitraryResolver = null; + + void method() { + this.generateJavaTypeArbitrarySet = + defaultIfNull( + this.generateJavaTypeArbitrarySet, + () -> + constraintGenerator -> + new JqwikJavaTypeArbitraryGeneratorSet( + this.javaTypeArbitraryGenerator, javaArbitraryResolver)); + } + + public interface JavaArbitraryResolver {} + + public interface JavaConstraintGenerator {} + + public interface JavaTypeArbitraryGeneratorSet {} + + public final class JqwikJavaTypeArbitraryGeneratorSet implements JavaTypeArbitraryGeneratorSet { + public JqwikJavaTypeArbitraryGeneratorSet( + JavaTypeArbitraryGenerator arbitraryGenerator, JavaArbitraryResolver arbitraryResolver) {} + } + + public interface JavaTypeArbitraryGenerator {} + + private static T defaultIfNull(T obj, Supplier defaultValue) { + return obj != null ? obj : defaultValue.get(); + } +} From 56afc3e06e1890716ef146f74048327bed977684 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 2 Apr 2024 20:39:00 -0700 Subject: [PATCH 126/173] Fix crash with void lambdas --- .../tests/all-systems/PlaceholderCrash1.java | 64 +++++++++++++++++++ .../checkerframework/javacutil/TreeUtils.java | 4 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 framework/tests/all-systems/PlaceholderCrash1.java diff --git a/framework/tests/all-systems/PlaceholderCrash1.java b/framework/tests/all-systems/PlaceholderCrash1.java new file mode 100644 index 00000000000..d181d88cf80 --- /dev/null +++ b/framework/tests/all-systems/PlaceholderCrash1.java @@ -0,0 +1,64 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +@SuppressWarnings("all") // Just check for crashes. +public class PlaceholderCrash1 { + void method(PlaceholderAPIPlugin plugin) { + final List expansions = new ArrayList<>(); + onMainThread( + plugin, + downloadAndDiscover(expansions, plugin), + (classes, exception) -> { + if (exception != null) { + return; + } + final String message = + classes.stream() + .filter(Objects::nonNull) + .map(plugin.getLocalExpansionManager()::register) + .filter(Optional::isPresent) + .map(Optional::get) + .map(expansion -> " &a" + expansion.getName()) + .collect(Collectors.joining("\n")); + return; + }); + } + + public static void onMainThread( + final Plugin plugin, + final CompletableFuture future, + final BiConsumer consumer) {} + + private static CompletableFuture>> downloadAndDiscover( + final List expansions, final PlaceholderAPIPlugin plugin) { + throw new RuntimeException(); + } + + static class Plugin {} + + static class PlaceholderAPIPlugin extends Plugin { + LocalExpansionManager getLocalExpansionManager() { + throw new RuntimeException(); + } + } + + static class LocalExpansionManager { + Optional register(Class p) { + throw new RuntimeException(); + } + } + + static class PlaceholderExpansion { + + public String getName() { + return ""; + } + } + + static class CloudExpansion {} +} diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 91347493fe4..9dc4fd734b1 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -2805,7 +2805,9 @@ public static List getReturnedExpressions(LambdaExpressionTree l new TreeScanner() { @Override public Void visitReturn(ReturnTree tree, Void o) { - returnExpressions.add(tree.getExpression()); + if (tree.getExpression() != null) { + returnExpressions.add(tree.getExpression()); + } return super.visitReturn(tree, o); } }; From f88cacaadb5075386fa054a585da60b9cab003f9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 08:32:40 -0700 Subject: [PATCH 127/173] Update dependency org.plumelib:require-javadoc to v1.0.9 (#6518) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 9e23c55187fb4d3375933f0308df724cdf445302 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 4 Apr 2024 01:47:35 -0700 Subject: [PATCH 128/173] Don't use TreeAnnotator for variable and method declarations --- docs/manual/creating-a-checker.tex | 18 +++++++++++++----- .../type/treeannotator/TreeAnnotator.java | 9 ++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index 7e8baf58de7..da19225f49f 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1320,9 +1320,16 @@ \refclass{framework/type/treeannotator}{TreeAnnotator}, typically as a private inner class of your \. There is a method of \ for every AST node, and the visitor - has access to both the tree (the AST node) and its type. In your - subclass of \, override \ to - return a \ containing that annotator, as in + has access to both the tree (the AST node) and its type. + + A \ is best suited to changing the type of an expression. + Don't try to use a \ to change the type of a variable or + method declaration, because that change will not generally be seen at + uses of the variable or method. + + After defining your \, override \ (in + your subclass of \) to return a + \ containing your new \, as in: \begin{Verbatim} @Override @@ -1332,10 +1339,11 @@ \end{Verbatim} \noindent - (or put your TreeAnnotator first; several tree annotators are run by + In code like the above, you might choose to put your TreeAnnotator first. + Several tree annotators are run by default, and among them, \ adds annotations to \s that do not have an annotation, - but has no effect on those that have an annotation). + but has no effect on those that have an annotation. \item Define a subclass of a diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java index dcb88343745..93e140d7bc8 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/TreeAnnotator.java @@ -12,9 +12,16 @@ /** * {@link TreeAnnotator} is an abstract SimpleTreeVisitor to be used with {@link ListTreeAnnotator}. * + *

          A TreeAnnotator is mainly intended to change the type of an expression. If a TreeAnnotator + * changes the type of a variable declaration, that change will not generally be seen at uses of the + * variable, because uses of a variable tend to obtain the variable's type from an Element (which + * always exists) rather than from a Tree (which might or might not be available at the time of the + * variable's use). + * *

          This class does not visit component parts of the tree. By default, the visit methods all call * {@link #defaultAction(Tree, Object)}, which does nothing unless overridden. Therefore, subclass - * do not need to call super unless they override {@link #defaultAction(Tree, Object)}. + * implementations of methods do not need to call {@code super()} unless they override {@link + * #defaultAction(Tree, Object)}. * * @see ListTreeAnnotator * @see PropagationTreeAnnotator From 1a104d772f96045c78be94a2d3f0daf5bf313e39 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Thu, 4 Apr 2024 15:42:25 -0700 Subject: [PATCH 129/173] Fix problem with method reference whose throws clause is a type var --- .../types/InferenceFactory.java | 14 +++++++ framework/tests/all-systems/Quarkus1.java | 38 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 framework/tests/all-systems/Quarkus1.java diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java index b3850190ccb..982a019726e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java @@ -998,6 +998,20 @@ public ConstraintSet getCheckedExceptionConstraints( compileTimeDeclarationType((MemberReferenceTree) expression) .getAnnotatedType() .getThrownTypes(); + if (thrownTypes.size() != thrownTypeMirrors.size()) { + // TODO: the thrown types are not stored in the ExecutableElements, so the above method + // doesn't find any thrown types. Below gets the types thrown type from the ExecutableType + // and just adds default annotations. This is just a work around for this problem. We need + // to figure out how to get the type with the correct annotations. + List thrownTypesNew = new ArrayList<>(thrownTypeMirrors.size()); + for (TypeMirror thrown : thrownTypeMirrors) { + AnnotatedTypeMirror thrownATM = + AnnotatedTypeMirror.createType(thrown, context.typeFactory, false); + context.typeFactory.addDefaultAnnotations(thrownATM); + thrownTypesNew.add(thrownATM); + } + thrownTypes = thrownTypesNew; + } } Iterator iter2 = thrownTypes.iterator(); diff --git a/framework/tests/all-systems/Quarkus1.java b/framework/tests/all-systems/Quarkus1.java new file mode 100644 index 00000000000..e5def3e8fcc --- /dev/null +++ b/framework/tests/all-systems/Quarkus1.java @@ -0,0 +1,38 @@ +@SuppressWarnings("all") // Just check for crashes. +public class Quarkus1 { + void method(ModifiableModelEx libraryModel) { + ApplicationManager.getApplication() + .invokeAndWait( + () -> { + WriteAction.run(libraryModel::commit); + }); + } + + public static class ApplicationManager { + protected static Application ourApplication; + + public static Application getApplication() { + return ourApplication; + } + } + + public interface Application { // extends ComponentManager {} + void invokeAndWait(Runnable runnable); + } + + public abstract static class WriteAction extends BaseActionRunnable { + public static void run(ThrowableRunnable action) throws E { + throw new RuntimeException(); + } + } + + public abstract static class BaseActionRunnable {} + + public interface ThrowableRunnable { + void run() throws T; + } + + interface ModifiableModelEx { + void commit(); + } +} From 2e3aff391ca991b16e68e980c06e5df603e4e331 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 5 Apr 2024 11:01:29 -0700 Subject: [PATCH 130/173] Fix problem with wilcard card array elements. (#6522) --- .../framework/type/DefaultTypeHierarchy.java | 19 +++++++++++++++++++ framework/tests/all-systems/HiveCrash2.java | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 framework/tests/all-systems/HiveCrash2.java diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 4e1f8218a6c..604a7a42a8b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -387,6 +387,25 @@ protected boolean isContainedBy( canBeCovariant); areEqualVisitHistory.put(inside, outside, currentTop, result); return result; + } else if (TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType())) { + // Sometimes the wildcard has been captured too early, so treat the captured type variable + // as wildcard. + // This is all cases except bullet 6, "T <= T". + AnnotatedTypeVariable outsideTypeVar = (AnnotatedTypeVariable) outside; + + // Add a placeholder in case of recursion, to prevent infinite regress. + areEqualVisitHistory.put(inside, outside, currentTop, true); + boolean result = + isContainedWithinBounds( + inside, + outsideTypeVar.getLowerBound(), + outsideTypeVar.getUpperBound(), + canBeCovariant); + areEqualVisitHistory.put(inside, outside, currentTop, result); + if (result) { + return true; + } + areEqualVisitHistory.remove(inside, outsideTypeVar, currentTop); } // The remainder of the method is bullet 6, "T <= T". diff --git a/framework/tests/all-systems/HiveCrash2.java b/framework/tests/all-systems/HiveCrash2.java new file mode 100644 index 00000000000..259945201cc --- /dev/null +++ b/framework/tests/all-systems/HiveCrash2.java @@ -0,0 +1,17 @@ +@SuppressWarnings("all") // Just check for crashes. +public class HiveCrash2 { + + protected final MqttMessageEncoder[] encoders = new MqttMessageEncoder[16]; + + HiveCrash2(Mqtt3ConnectEncoder connectEncoder) { + encoders[0] = connectEncoder; + } + + public static class Mqtt3ConnectEncoder extends MqttMessageEncoder {} + + static class MqttStatefulConnect implements MqttMessage {} + + public abstract static class MqttMessageEncoder {} + + public interface MqttMessage {} +} From dea9287e31cc39f63cfe6dd20eb1ee7aa754eec1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:13:51 -0700 Subject: [PATCH 131/173] Update dependency io.github.classgraph:classgraph to v4.8.170 (#6525) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- framework/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/build.gradle b/framework/build.gradle index 24e5c04fbd5..e37bbd08a6c 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation "org.plumelib:plume-util:${versions.plumeUtil}" implementation "org.plumelib:reflection-util:${versions.reflectionUtil}" // Add this dependency if needed to debug classpath issues. - // implementation 'io.github.classgraph:classgraph:4.8.168' + // implementation 'io.github.classgraph:classgraph:4.8.170' testImplementation "junit:junit:${versions.junit}" testImplementation project(':framework-test') From 1230a1fdd655257efb0109bbfd0a38fbe3e09061 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 7 Apr 2024 16:31:10 -0700 Subject: [PATCH 132/173] Use Gradle 8.6 From 5c93c294521e7109413f72c4bb3cbd15b9964343 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 8 Apr 2024 09:51:38 -0700 Subject: [PATCH 133/173] Specify character set --- SKIP-REQUIRE-JAVADOC | 1 + build.gradle | 2 - .../cfg/visualize/CFGVisualizeLauncher.java | 3 +- .../cfg/visualize/DOTCFGVisualizer.java | 15 +++++++- .../CheckerFrameworkWPIPerDirectoryTest.java | 7 ++-- .../framework/test/TestUtilities.java | 38 +++++++++++++++---- .../diagnostics/JavaDiagnosticReader.java | 6 ++- .../common/util/TypeVisualizer.java | 5 ++- .../SceneToStubWriter.java | 3 +- ...holeProgramInferenceJavaParserStorage.java | 5 +-- .../scenelib/ASceneWrapper.java | 7 +++- .../framework/source/SourceChecker.java | 4 +- .../framework/stub/AddAnnotatedFor.java | 16 ++++++-- .../stub/RemoveAnnotationsForInference.java | 6 +-- .../framework/stub/ToIndexFileConverter.java | 3 +- .../framework/type/AnnotatedTypeFactory.java | 4 +- .../framework/util/CheckerMain.java | 11 ++++-- .../framework/util/ExecUtil.java | 6 ++- 18 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 SKIP-REQUIRE-JAVADOC diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC new file mode 100644 index 00000000000..d076b691ad8 --- /dev/null +++ b/SKIP-REQUIRE-JAVADOC @@ -0,0 +1 @@ +Remove this file when the pull request is merged. diff --git a/build.gradle b/build.gradle index eb2034eb258..399b583abff 100644 --- a/build.gradle +++ b/build.gradle @@ -544,8 +544,6 @@ allprojects { currentProj -> options.errorprone.errorproneArgs = [ // Many compiler classes are interned. '-Xep:ReferenceEquality:OFF', - // These might be worth fixing. - '-Xep:DefaultCharset:OFF', // Not useful to suggest Splitter; maybe clean up. '-Xep:StringSplitter:OFF', // Too broad, rejects seemingly-correct code. diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java index 63d6d18edfc..0e7a3e01009 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; import javax.tools.JavaFileManager; @@ -283,7 +284,7 @@ public void write(int b) throws IOException {} public static void writeStringOfCFG( String inputFile, String method, String clas, String outputFile, Analysis analysis) { Map res = generateStringOfCFG(inputFile, method, clas, true, analysis); - try (FileWriter out = new FileWriter(outputFile)) { + try (FileWriter out = new FileWriter(outputFile, StandardCharsets.UTF_8)) { if (res != null && res.get("stringGraph") != null) { out.write(res.get("stringGraph").toString()); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java index e49d0eb1baf..f23b5114d8a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/DOTCFGVisualizer.java @@ -6,6 +6,11 @@ import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; @@ -96,7 +101,8 @@ public Map visualizeWithAction( } String dotFileName = dotOutputFileName(cfg.underlyingAST); - try (BufferedWriter out = new BufferedWriter(new FileWriter(dotFileName))) { + try (BufferedWriter out = + new BufferedWriter(new FileWriter(dotFileName, StandardCharsets.UTF_8))) { out.write(dotGraph); } catch (IOException e) { throw new UserError("Error creating dot file (is the path valid?): " + dotFileName, e); @@ -342,7 +348,12 @@ private static String escapeString(Object obj) { @Override public void shutdown() { // Open for append, in case of multiple sub-checkers. - try (FileWriter fstream = new FileWriter(outDir + "/methods.txt", true); + try (Writer fstream = + Files.newBufferedWriter( + Paths.get(outDir + "/methods.txt"), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND); BufferedWriter out = new BufferedWriter(fstream)) { for (Map.Entry kv : generated.entrySet()) { out.write(kv.getKey()); diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java index 1c2304c53c7..4f6dedbc2bd 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/CheckerFrameworkWPIPerDirectoryTest.java @@ -1,7 +1,8 @@ package org.checkerframework.framework.test; import java.io.File; -import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Scanner; @@ -103,14 +104,14 @@ protected void doNotTypecheck( * @return whether {@code file} contains {@code skipComment} */ public static boolean hasSkipComment(File file, String skipComment) { - try (Scanner in = new Scanner(file)) { + try (Scanner in = new Scanner(file, StandardCharsets.UTF_8)) { while (in.hasNext()) { String nextLine = in.nextLine(); if (nextLine.contains(skipComment)) { return true; } } - } catch (FileNotFoundException e) { + } catch (IOException e) { throw new RuntimeException(e); } return false; diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java index 7288da58ade..4240c73af3d 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java @@ -3,14 +3,14 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; -import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -278,7 +278,7 @@ public static boolean isJavaTestFile(File file) { return false; } - try (Scanner in = new Scanner(file)) { + try (Scanner in = new Scanner(file, StandardCharsets.UTF_8)) { while (in.hasNext()) { String nextLine = in.nextLine(); if (nextLine.contains("@skip-test") @@ -298,7 +298,7 @@ public static boolean isJavaTestFile(File file) { return false; } } - } catch (FileNotFoundException e) { + } catch (IOException e) { throw new RuntimeException(e); } @@ -410,7 +410,12 @@ public static List optionMapToList(Map options * @param lines what lines to write */ public static void writeLines(File file, Iterable lines) { - try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) { + try (BufferedWriter bw = + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND)) { Iterator iter = lines.iterator(); while (iter.hasNext()) { Object next = iter.next(); @@ -436,7 +441,13 @@ public static void writeDiagnostics( List missing, boolean usingNoMsgText, boolean testFailed) { - try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) { + try (PrintWriter pw = + new PrintWriter( + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND))) { pw.println("File: " + testFile.getAbsolutePath()); pw.println("TestFailed: " + testFailed); pw.println("Using nomsgtxt: " + usingNoMsgText); @@ -473,7 +484,12 @@ public static void writeDiagnostics( * @param config the configuration to append to the end of the file */ public static void writeTestConfiguration(File file, TestConfiguration config) { - try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) { + try (BufferedWriter bw = + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND)) { bw.write(config.toString()); bw.newLine(); bw.newLine(); @@ -488,7 +504,13 @@ public static void writeJavacArguments( Iterable files, Iterable options, Iterable processors) { - try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) { + try (PrintWriter pw = + new PrintWriter( + Files.newBufferedWriter( + file.toPath(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND))) { pw.println("Files:"); for (JavaFileObject f : files) { pw.println(" " + f.getName()); diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java index ed6058c5501..75efeb437d7 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java @@ -2,9 +2,10 @@ import java.io.Closeable; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -177,7 +178,8 @@ private JavaDiagnosticReader(File toRead, StringToTestDiagnosticLine codec) { this.filename = toRead.getName(); LineNumberReader reader = null; try { - reader = new LineNumberReader(new FileReader(toRead)); + reader = + new LineNumberReader(Files.newBufferedReader(toRead.toPath(), StandardCharsets.UTF_8)); this.reader = reader; advance(); } catch (IOException e) { diff --git a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java index a2e7d6667ef..0f0aa99c8e7 100644 --- a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java +++ b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java @@ -2,8 +2,9 @@ import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -245,7 +246,7 @@ private void addConnections() { * @param file the file to write to */ private void write(File file) { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { writer.write("digraph " + graphName + "{"); writer.newLine(); for (String line : lines) { diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java index 8519c4e3bab..bfd21da0445 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java @@ -4,6 +4,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -745,7 +746,7 @@ private static void writeImpl(ASceneWrapper scene, String filename, BaseTypeChec if (fileWriter != null || printWriter != null) { throw new Error("This can't happen"); } - fileWriter = new FileWriter(filename); + fileWriter = new FileWriter(filename, StandardCharsets.UTF_8); printWriter = new PrintWriter(fileWriter); } catch (IOException e) { throw new BugInCF("error writing file during WPI: " + filename); diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index ea64cf56520..0def33fd8db 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -36,13 +36,12 @@ import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; -import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.lang.annotation.Annotation; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; @@ -1063,7 +1062,7 @@ public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checke * @param root the compilation unit to be written */ private void writeAjavaFile(File outputPath, CompilationUnitAnnos root) { - try (Writer writer = new BufferedWriter(new FileWriter(outputPath))) { + try (Writer writer = Files.newBufferedWriter(outputPath.toPath(), StandardCharsets.UTF_8)) { // This implementation uses JavaParser's lexical preserving printing, which writes the // file such that its formatting is close to the original source file it was parsed from // as possible. It is commented out because, this feature is very buggy and crashes when diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java index 31f8311629d..aca2fb064e9 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java @@ -2,8 +2,11 @@ import com.sun.tools.javac.code.Symbol.ClassSymbol; import java.io.File; -import java.io.FileWriter; import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Set; @@ -167,7 +170,7 @@ public void writeToFile( aMethod.contracts = contractAnnotations; } } - try (FileWriter fw = new FileWriter(filepath)) { + try (Writer fw = Files.newBufferedWriter(Paths.get(filepath), StandardCharsets.UTF_8)) { IndexFileWriter.write(scene, fw); } break; diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 1e9b97a8b09..b1a6c2f0785 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -27,6 +27,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayDeque; import java.util.ArrayList; @@ -3002,7 +3003,8 @@ private void printGitProperties() { gitPropertiesPrinted = true; try (InputStream in = getClass().getResourceAsStream("/git.properties"); - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); ) { + BufferedReader reader = + new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); ) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java index 8489ff852cd..19c56e7ed33 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AddAnnotatedFor.java @@ -1,11 +1,15 @@ package org.checkerframework.framework.stub; -import java.io.FileReader; +import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.LineNumberReader; +import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -84,12 +88,18 @@ public static void main(String[] args) throws IOException, DefException, ParseEx AScene scene = new AScene(); boolean useFile = args.length == 1; String filename = useFile ? args[0] : "System.in"; - try (Reader r = useFile ? new FileReader(filename) : new InputStreamReader(System.in)) { + try (Reader r = + useFile + ? Files.newBufferedReader(Paths.get(filename), StandardCharsets.UTF_8) + : new InputStreamReader(System.in, StandardCharsets.UTF_8)) { IndexFileParser.parse(new LineNumberReader(r), filename, scene); } scene.prune(); addAnnotatedFor(scene); - IndexFileWriter.write(scene, new PrintWriter(System.out, true)); + IndexFileWriter.write( + scene, + new PrintWriter( + new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8)), true)); } /** diff --git a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java index be1787512ab..85f3ef7c34b 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java @@ -25,12 +25,11 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.common.reflect.ClassPath; -import java.io.BufferedWriter; import java.io.FileNotFoundException; -import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -272,7 +271,8 @@ static void removeAnnotations(Path absolutePath, List removals) } try (PrintWriter pw = - new PrintWriter(new BufferedWriter(new FileWriter(absolutePath.toString())))) { + new PrintWriter( + Files.newBufferedWriter(Paths.get(absolutePath.toString()), StandardCharsets.UTF_8))) { for (String line : lines) { pw.println(line); } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java index ca029981a40..41ac774eec3 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/ToIndexFileConverter.java @@ -44,6 +44,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -197,7 +198,7 @@ private static void convert(AScene scene, InputStream in, OutputStream out) + e.getMessage()); } extractScene(iu, scene); - try (Writer w = new BufferedWriter(new OutputStreamWriter(out))) { + try (Writer w = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) { IndexFileWriter.write(scene, w); } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 8488619a487..710b591bf10 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -36,6 +36,7 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Target; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -905,7 +906,8 @@ public List getCheckerNames() { Enumeration urls = getClass().getClassLoader().getResources(filename); while (urls.hasMoreElements()) { URL url = urls.nextElement(); - try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) { + try (BufferedReader in = + new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { result.addAll(in.lines().collect(Collectors.toList())); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java index af4646cbd9c..9f21b3162d6 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java +++ b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java @@ -1,16 +1,19 @@ package org.checkerframework.framework.util; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -582,7 +585,8 @@ private static void outputArgumentsToFile(String outputFilename, List ar @SuppressWarnings("builder:required.method.not.called") // called when needed PrintWriter writer = (outputFilename.equals("-") - ? new PrintWriter(System.out) + ? new PrintWriter( + new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8))) : new PrintWriter(outputFilename, "UTF-8")); for (int i = 0; i < args.size(); i++) { String arg = args.get(i); @@ -597,7 +601,8 @@ private static void outputArgumentsToFile(String outputFilename, List ar // Read argfile and include its parameters in the output file. String inputFilename = arg.substring(1); - try (BufferedReader br = new BufferedReader(new FileReader(inputFilename))) { + try (BufferedReader br = + Files.newBufferedReader(Paths.get(inputFilename), StandardCharsets.UTF_8)) { String line; while ((line = br.readLine()) != null) { writer.print(line); diff --git a/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java index efe142b3160..21f890cbdeb 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ExecUtil.java @@ -5,6 +5,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; /** Utilities for executing external processes. */ public class ExecUtil { @@ -51,7 +52,7 @@ public static class Redirection { public Redirection(OutputStream out, int bufferSize) { this.buffer = new char[bufferSize]; - this.out = new OutputStreamWriter(out); + this.out = new OutputStreamWriter(out, StandardCharsets.UTF_8); } public void redirect(InputStream inStream) { @@ -61,7 +62,8 @@ public void redirect(InputStream inStream) { this.thread = new Thread( () -> { - try (InputStreamReader in = new InputStreamReader(inStream)) { + try (InputStreamReader in = + new InputStreamReader(inStream, StandardCharsets.UTF_8)) { int read = 0; while (read > -1) { read = in.read(buffer); From 8c76653279a1e83dbc9421b547c17a000573af46 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 8 Apr 2024 09:52:04 -0700 Subject: [PATCH 134/173] Remove file `SKIP-REQUIRE-JAVADOC` --- SKIP-REQUIRE-JAVADOC | 1 - 1 file changed, 1 deletion(-) delete mode 100644 SKIP-REQUIRE-JAVADOC diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC deleted file mode 100644 index d076b691ad8..00000000000 --- a/SKIP-REQUIRE-JAVADOC +++ /dev/null @@ -1 +0,0 @@ -Remove this file when the pull request is merged. From 8473446e3da53d1f4d4a31096b40a5faedd1a09b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:34:23 +0000 Subject: [PATCH 135/173] Update dependency commons-io:commons-io to v2.16.1 (#6530) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From bad6020db1e841a10b783c8164c6de7a66d06046 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:46:05 -0700 Subject: [PATCH 136/173] Add manual section on customizing default checker options (#6511) --- docs/manual/creating-a-checker.tex | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index da19225f49f..dfde864f0f7 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1173,6 +1173,26 @@ The value is returned as a single string and you have to perform the required parsing of the option. +\subsubsectionAndLabel{Default Options}{creating-providing-command-line-options-default-options} + +The default options passed to a custom checker may be customized by overriding the +\refmethodterse{framework/source}{BaseTypeChecker}{getOptions}{()} method in the +custom checker class. + +For example, to ignore the \ warnings raised by the +Optional Checker (Chapter~\ref{optional-checker}), override +\refmethodterse{framework/source}{BaseTypeChecker}{getOptions}{()} as follows: + +\begin{Verbatim} + @Override + public Map getOptions() { + Map options = new HashMap<>(super.getOptions()); + options.put("suppressWarnings", "optional:introduce.eliminate"); + return options; + } +\end{Verbatim} + +in the \refclass{checker/optional}{OptionalChecker} class. % TODO: describe -ANullnessChecker_option=value mechanism. % For the implementation, see SourceChecker#OPTION_SEPARATOR. From 9cf2237d8362fb899b1a19a67b2873f76fdbee44 Mon Sep 17 00:00:00 2001 From: James Yoo <24359440+jyoo980@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:53:25 -0700 Subject: [PATCH 137/173] Ensure subcheckers respect options passed to parent checker (#6513) --- .../framework/source/SourceChecker.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index b1a6c2f0785..004ed2825ac 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -47,6 +47,7 @@ import java.util.TreeSet; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; @@ -2103,7 +2104,7 @@ public final Set getSupportedAnnotationTypes() { private String @Nullable [] getSuppressWarningsStringsFromOption() { if (!computedSuppressWarningsStringsFromOption) { computedSuppressWarningsStringsFromOption = true; - Map options = getOptions(); + Map options = getAllOptions(); if (options.containsKey("suppressWarnings")) { String swStrings = options.get("suppressWarnings"); if (swStrings != null) { @@ -2115,6 +2116,48 @@ public final Set getSupportedAnnotationTypes() { return this.suppressWarningsStringsFromOption; } + /** + * Returns the options passed to this checker and its immediate parent checker. + * + * @return the options passed to this checker and its immediate parent checker + */ + private Map getAllOptions() { + if (parentChecker == null) { + return getOptions(); + } + Map allOptions = new HashMap<>(this.getOptions()); + parentChecker + .getOptions() + .forEach( + (parentOptKey, parentOptVal) -> { + if (parentOptVal != null) { + allOptions.merge(parentOptKey, parentOptVal, this::combineOptionValues); + } + }); + return Collections.unmodifiableMap(allOptions); + } + + /** + * Combines two comma-delimited strings into a single comma-delimited string that does not contain + * duplicates. + * + *

          Checker option values are comma-delimited. This method combines two option values while + * discarding possible duplicates. + * + * @param optionValueA the first comma-delimited string + * @param optionValueB the second comma-delimited string + * @return a comma-delimited string containing values from the first and second string, with no + * duplicates + */ + private String combineOptionValues(String optionValueA, String optionValueB) { + Set optionValueASet = + Arrays.stream(optionValueA.split(",")).collect(Collectors.toSet()); + Set optionValueBSet = + Arrays.stream(optionValueB.split(",")).collect(Collectors.toSet()); + optionValueASet.addAll(optionValueBSet); + return String.join(",", optionValueASet); + } + /** * Issues a warning about any {@code @SuppressWarnings} that didn't suppress a warning, but starts * with this checker name or "allcheckers". From 54016756635956d61c25e3368e7878d514e41f94 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 15 Apr 2024 13:49:54 -0700 Subject: [PATCH 138/173] Don't descend into a nested lambda when collecting returned expressions --- framework/tests/all-systems/Quarkus2.java | 28 +++++++++++++++++++ framework/tests/all-systems/Quarkus3.java | 17 +++++++++++ .../checkerframework/javacutil/TreeUtils.java | 8 +++++- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 framework/tests/all-systems/Quarkus2.java create mode 100644 framework/tests/all-systems/Quarkus3.java diff --git a/framework/tests/all-systems/Quarkus2.java b/framework/tests/all-systems/Quarkus2.java new file mode 100644 index 00000000000..c414fd8ecf5 --- /dev/null +++ b/framework/tests/all-systems/Quarkus2.java @@ -0,0 +1,28 @@ +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@SuppressWarnings("all") // Just check for crashes. +public class Quarkus2 { + private void method( + CompletableFuture server, + CompletableFuture>> list, + CompletableFuture>> x) { + CompletableFuture>> q = + server.thenCompose( + ls -> { + x.thenApply( + y -> { + return y; + }); + return list; + }); + } + + public interface InferfaceA {} + + static class ClassB {} + + static class ClassC {} + + public static class ClassD {} +} diff --git a/framework/tests/all-systems/Quarkus3.java b/framework/tests/all-systems/Quarkus3.java new file mode 100644 index 00000000000..b1172d8feed --- /dev/null +++ b/framework/tests/all-systems/Quarkus3.java @@ -0,0 +1,17 @@ +import java.util.List; +import java.util.stream.Collectors; + +public class Quarkus3 { + void method(List commands) { + String text = + commands.stream() + .map( + param -> { + if (param.indexOf(' ') != -1) { + return "\"" + param + "\""; + } + return param; + }) + .collect(Collectors.joining(" ")); + } +} diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 9dc4fd734b1..d2ae940d758 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -2810,8 +2810,14 @@ public Void visitReturn(ReturnTree tree, Void o) { } return super.visitReturn(tree, o); } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { + // Don't visit inside anther lambda. + return null; + } }; - scanner.scan(lambda, null); + scanner.scan(lambda.getBody(), null); return returnExpressions; } From adca6dc0404674f5c17e9f83808ef3d22c567a82 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 17 Apr 2024 09:58:32 -0700 Subject: [PATCH 139/173] How to reproduce CI failures (#6533) --- docs/developer/developer-manual.html | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/developer/developer-manual.html b/docs/developer/developer-manual.html index f44fb93e24f..1f1acc34bab 100644 --- a/docs/developer/developer-manual.html +++ b/docs/developer/developer-manual.html @@ -43,7 +43,7 @@

          Checker Framework developer manualContinuous Integration